summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2014-02-16 16:02:17 -0500
committerjoe <joe@jerkface.net>2014-02-16 16:02:17 -0500
commit585e8aca57f0fdb09dd6cf42cb67af23448cdc6a (patch)
treefc62c1c72934394b1d38ecefe5cd43ee9ed3f0a8
parent02a5b4dd3ac448e1e025234ce42e78b731de6fe9 (diff)
send roster
-rw-r--r--Presence/XMPPServer.hs96
-rw-r--r--xmppServer.hs6
2 files changed, 95 insertions, 7 deletions
diff --git a/Presence/XMPPServer.hs b/Presence/XMPPServer.hs
index 52933440..d3df48d0 100644
--- a/Presence/XMPPServer.hs
+++ b/Presence/XMPPServer.hs
@@ -6,8 +6,9 @@ module XMPPServer
6 , Stanza(..) 6 , Stanza(..)
7 , StanzaType(..) 7 , StanzaType(..)
8 , StanzaOrigin(..) 8 , StanzaOrigin(..)
9 , dupStanza 9 , cloneStanza
10 ) where 10 ) where
11import Debug.Trace
11import Control.Monad.Trans.Resource (runResourceT) 12import Control.Monad.Trans.Resource (runResourceT)
12import Control.Monad.Trans (lift) 13import Control.Monad.Trans (lift)
13import Control.Monad.IO.Class (MonadIO, liftIO) 14import Control.Monad.IO.Class (MonadIO, liftIO)
@@ -33,13 +34,16 @@ import Data.Conduit.Blaze (builderToByteStringFlush)
33import qualified Text.XML.Stream.Render as XML 34import qualified Text.XML.Stream.Render as XML
34import qualified Text.XML.Stream.Parse as XML 35import qualified Text.XML.Stream.Parse as XML
35import Data.XML.Types as XML 36import Data.XML.Types as XML
36import Data.Maybe (catMaybes,fromJust,isNothing) 37import Data.Maybe (catMaybes,fromJust,isJust,isNothing)
37import Data.Monoid ( (<>) ) 38import Data.Monoid ( (<>) )
38import Data.Text (Text) 39import Data.Text (Text)
39import qualified Data.Text as Text (pack,unpack) 40import qualified Data.Text as Text (pack,unpack)
40import Data.Char (toUpper) 41import Data.Char (toUpper)
41import Data.Map (Map) 42import Data.Map (Map)
42import qualified Data.Map as Map 43import qualified Data.Map as Map
44import Data.Set (Set, (\\) )
45import qualified Data.Set as Set
46import qualified System.Random
43 47
44import qualified Control.Concurrent.STM.UpdateStream as Slotted 48import qualified Control.Concurrent.STM.UpdateStream as Slotted
45import ControlMaybe 49import ControlMaybe
@@ -85,6 +89,8 @@ data StanzaType
85 | SetResource 89 | SetResource
86 | SessionRequest 90 | SessionRequest
87 | UnrecognizedQuery Name 91 | UnrecognizedQuery Name
92 | RequestRoster
93 | Roster
88 | Error 94 | Error
89 | PresenceStatus { presenceShow :: JabberShow 95 | PresenceStatus { presenceShow :: JabberShow
90 , presencePriority :: Maybe Int8 96 , presencePriority :: Maybe Int8
@@ -117,6 +123,12 @@ data XMPPServerParameters =
117 { xmppChooseResourceName :: ConnectionKey -> Socket -> Maybe Text -> IO Text 123 { xmppChooseResourceName :: ConnectionKey -> Socket -> Maybe Text -> IO Text
118 , xmppNewConnection :: ConnectionKey -> TChan Stanza -> IO () 124 , xmppNewConnection :: ConnectionKey -> TChan Stanza -> IO ()
119 , xmppEOF :: ConnectionKey -> IO () 125 , xmppEOF :: ConnectionKey -> IO ()
126 , xmppRosterBuddies :: ConnectionKey -> IO [Text]
127 , xmppRosterSubscribers :: ConnectionKey -> IO [Text]
128 , xmppRosterSolicited :: ConnectionKey -> IO [Text]
129 , xmppRosterOthers :: ConnectionKey -> IO [Text]
130 , xmppLookupClientJID :: ConnectionKey -> IO Text
131 , xmppLookupPeerName :: ConnectionKey -> IO Text
120 } 132 }
121 133
122-- TODO: http://xmpp.org/rfcs/rfc6120.html#rules-remote-error 134-- TODO: http://xmpp.org/rfcs/rfc6120.html#rules-remote-error
@@ -167,8 +179,8 @@ type FlagCommand = STM Bool
167type ReadCommand = IO (Maybe ByteString) 179type ReadCommand = IO (Maybe ByteString)
168type WriteCommand = ByteString -> IO Bool 180type WriteCommand = ByteString -> IO Bool
169 181
170dupStanza stanza = do 182cloneStanza stanza = do
171 dupped <- dupTChan (stanzaChan stanza) 183 dupped <- cloneTChan (stanzaChan stanza)
172 return stanza { stanzaChan = dupped } 184 return stanza { stanzaChan = dupped }
173 185
174copyToChannel f chan closer_stack = awaitForever copy 186copyToChannel f chan closer_stack = awaitForever copy
@@ -223,6 +235,7 @@ grokStanzaIQGet stanza = do
223 flip (maybe $ return Nothing) mtag $ \tag -> do 235 flip (maybe $ return Nothing) mtag $ \tag -> do
224 case tagName tag of 236 case tagName tag of
225 "{urn:xmpp:ping}ping" -> return $ Just Ping 237 "{urn:xmpp:ping}ping" -> return $ Just Ping
238 "{jabber:iq:roster}query" -> return $ Just RequestRoster
226 name -> return . Just $ UnrecognizedQuery name 239 name -> return . Just $ UnrecognizedQuery name
227 240
228grokStanzaIQResult :: Monad m => XML.Event -> NestingXML o m (Maybe StanzaType) 241grokStanzaIQResult :: Monad m => XML.Event -> NestingXML o m (Maybe StanzaType)
@@ -644,7 +657,7 @@ forkConnection sv k pingflag src snk stanzas = do
644 fix $ \loop -> do 657 fix $ \loop -> do
645 what <- atomically $ foldr1 orElse 658 what <- atomically $ foldr1 orElse
646 [readTChan output >>= \stanza -> return $ do 659 [readTChan output >>= \stanza -> return $ do
647 dup <- atomically $ dupStanza stanza 660 dup <- atomically $ cloneStanza stanza
648 stanzaToConduit dup $$ prettyPrint $ case k of 661 stanzaToConduit dup $$ prettyPrint $ case k of
649 ClientKey {} -> "C<-" <> bshow (stanzaType dup) <> " " 662 ClientKey {} -> "C<-" <> bshow (stanzaType dup) <> " "
650 PeerKey {} -> "P<-" <> bshow (stanzaType dup) <> " " 663 PeerKey {} -> "P<-" <> bshow (stanzaType dup) <> " "
@@ -712,12 +725,74 @@ stanzaToConduit stanza = do
712 cempty <- isEmptyTChan xchan 725 cempty <- isEmptyTChan xchan
713 if isNothing mb 726 if isNothing mb
714 then if cempty then return loop else retry 727 then if cempty then return loop else retry
715 else retry -- todo: send closers 728 else do done <- tryReadTMVar rdone
729 check (isJust done)
730 trace "todo: send closers" retry
716 ,do isEmptyTChan xchan >>= check 731 ,do isEmptyTChan xchan >>= check
717 readTMVar rdone 732 readTMVar rdone
718 return (return ())] 733 return (return ())]
719 what 734 what
720 735
736xmlifyRosterItems :: Monad m => Set Text -> Text -> Set Text -> ConduitM i Event m ()
737xmlifyRosterItems solicited stype set = mapM_ item (Set.toList set)
738 where
739 item jid = do yield $ EventBeginElement "item"
740 ([ attr "jid" jid
741 , attr "subscription" stype
742 ]++if Set.member jid solicited
743 then [attr "ask" "subscribe"]
744 else [] )
745 yield $ EventEndElement "item"
746
747sendRoster query xmpp replyto = do
748 let k = case stanzaOrigin query of
749 NetworkOrigin k _ -> Just k
750 LocalPeer -> Nothing -- local peer requested roster?
751 flip (maybe $ return ()) k $ \k -> do
752 jid <- case k of
753 ClientKey {} -> xmppLookupClientJID xmpp k
754 PeerKey {} -> xmppLookupPeerName xmpp k
755 let getlist f = do
756 bs <- f xmpp k
757 -- js <- mapM parseHostNameJID bs
758 return (Set.fromList bs) -- js)
759 buddies <- getlist xmppRosterBuddies
760 subscribers <- getlist xmppRosterSubscribers
761 solicited <- getlist xmppRosterSolicited
762 subnone0 <- getlist xmppRosterOthers
763 let subnone = subnone0 \\ (Set.union buddies subscribers)
764 let subto = buddies \\ subscribers
765 let subfrom = subscribers \\ buddies
766 let subboth = Set.intersection buddies subscribers
767 let roster = do
768 yield $ EventBeginElement "{jabber:client}iq"
769 (consid (stanzaId query)
770 [ attr "to" jid
771 , attr "type" "result" ])
772 yield $ EventBeginElement "{jabber:iq:roster}query" [] -- todo: ver?
773 xmlifyRosterItems solicited "to" subto
774 xmlifyRosterItems solicited "from" subfrom
775 xmlifyRosterItems solicited "both" subboth
776 xmlifyRosterItems solicited "none" subnone
777 yield $ EventEndElement "{jabber:iq:roster}query"
778 yield $ EventEndElement "{jabber:client}iq"
779 chan <- atomically newTChan
780 clsrs <- atomically $ newTVar (Just [])
781 quitvar <- atomically $ newEmptyTMVar
782 forkIO $ do
783 roster =$= copyToChannel id chan clsrs $$ awaitForever (const $ return ())
784 atomically $ writeTVar clsrs Nothing
785 ioWriteChan replyto
786 Stanza { stanzaType = Roster
787 , stanzaId = (stanzaId query)
788 , stanzaTo = Just jid
789 , stanzaFrom = Nothing
790 , stanzaChan = chan
791 , stanzaClosers = clsrs
792 , stanzaInterrupt = quitvar
793 , stanzaOrigin = LocalPeer
794 }
795
721socketFromKey :: Server k -> k -> IO Socket 796socketFromKey :: Server k -> k -> IO Socket
722socketFromKey sv k = do 797socketFromKey sv k = do
723 return todo 798 return todo
@@ -758,6 +833,8 @@ monitor sv params xmpp = do
758 SessionRequest -> do 833 SessionRequest -> do
759 let reply = iq_session_reply (stanzaId stanza) "localhost" 834 let reply = iq_session_reply (stanzaId stanza) "localhost"
760 sendReply quitVar Pong reply replyto 835 sendReply quitVar Pong reply replyto
836 RequestRoster ->
837 sendRoster stanza xmpp replyto
761 UnrecognizedQuery query -> do 838 UnrecognizedQuery query -> do
762 let reply = iq_service_unavailable (stanzaId stanza) "localhost" query 839 let reply = iq_service_unavailable (stanzaId stanza) "localhost" query
763 sendReply quitVar Error reply replyto 840 sendReply quitVar Error reply replyto
@@ -784,8 +861,13 @@ xmppServer :: ( MonadResource m
784 ) => XMPPServerParameters -> m (Server ConnectionKey,ConnectionParameters ConnectionKey) 861 ) => XMPPServerParameters -> m (Server ConnectionKey,ConnectionParameters ConnectionKey)
785xmppServer xmpp = do 862xmppServer xmpp = do
786 sv <- server 863 sv <- server
864 -- some fuzz helps avoid simultaneity
865 pingfuzz <- liftIO $ do
866 gen <- System.Random.getStdGen
867 let (r,gen') = System.Random.next gen
868 return $ r `mod` 2000 -- maximum 2 seconds of fuzz
787 let peer_params = (connectionDefaults peerKey) 869 let peer_params = (connectionDefaults peerKey)
788 { pingInterval = 15000 870 { pingInterval = 15000 + pingfuzz
789 , timeout = 2000 871 , timeout = 2000
790 , duplex = False } 872 , duplex = False }
791 client_params = (connectionDefaults clientKey) 873 client_params = (connectionDefaults clientKey)
diff --git a/xmppServer.hs b/xmppServer.hs
index 4fcdd2c2..4601dbe9 100644
--- a/xmppServer.hs
+++ b/xmppServer.hs
@@ -21,6 +21,12 @@ main = runResourceT $ do
21 { xmppChooseResourceName = \k sock desired -> return "nobody@localhost/tty666" 21 { xmppChooseResourceName = \k sock desired -> return "nobody@localhost/tty666"
22 , xmppNewConnection = \k outchan -> return () 22 , xmppNewConnection = \k outchan -> return ()
23 , xmppEOF = \k -> return () 23 , xmppEOF = \k -> return ()
24 , xmppRosterBuddies = \k -> return []
25 , xmppRosterSubscribers = \k -> return []
26 , xmppRosterSolicited = \k -> return []
27 , xmppRosterOthers = \k -> return []
28 , xmppLookupPeerName = \k -> return "localhost"
29 , xmppLookupClientJID = \k -> return "nobody@localhost/tty666"
24 } 30 }
25 liftIO $ do 31 liftIO $ do
26 let testaddr0 = "fd97:ca88:fa7c:b94b:c8b8:fad4:1021:a54d" 32 let testaddr0 = "fd97:ca88:fa7c:b94b:c8b8:fad4:1021:a54d"