diff options
author | joe <joe@jerkface.net> | 2013-06-25 18:09:08 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2013-06-25 18:09:08 -0400 |
commit | e74b4448748af7e068a9d162b62fccc0ede0e81a (patch) | |
tree | de08edd1b13cb922f908684c0d8e28020f18e430 /Presence/XMPPServer.hs | |
parent | 4ae6bf78a836cf35450387431aea93d522ce8f84 (diff) |
SEnd addresses on the wire between peers rather than domain names.
Diffstat (limited to 'Presence/XMPPServer.hs')
-rw-r--r-- | Presence/XMPPServer.hs | 222 |
1 files changed, 183 insertions, 39 deletions
diff --git a/Presence/XMPPServer.hs b/Presence/XMPPServer.hs index 387b223e..aa3140e5 100644 --- a/Presence/XMPPServer.hs +++ b/Presence/XMPPServer.hs | |||
@@ -21,6 +21,9 @@ import Data.ByteString.Lazy.Char8 as L | |||
21 | ( hPutStrLn | 21 | ( hPutStrLn |
22 | , unlines | 22 | , unlines |
23 | , lines | 23 | , lines |
24 | , uncons | ||
25 | , takeWhile | ||
26 | , concat | ||
24 | , splitWith | 27 | , splitWith |
25 | , drop | 28 | , drop |
26 | , ByteString | 29 | , ByteString |
@@ -57,19 +60,69 @@ import Data.Binary.Builder as B | |||
57 | import Data.Binary.Put | 60 | import Data.Binary.Put |
58 | import qualified Data.Map as Map | 61 | import qualified Data.Map as Map |
59 | import GHC.Conc | 62 | import GHC.Conc |
60 | import Network.BSD | 63 | import Network.BSD hiding (getHostByAddr) |
61 | import Control.Concurrent.Async | 64 | import Control.Concurrent.Async |
62 | import qualified Data.Set as Set | 65 | import qualified Data.Set as Set |
66 | import GetHostByAddr | ||
67 | |||
68 | data Peer = LocalHost | RemotePeer SockAddr | ||
69 | deriving (Eq,Prelude.Show) | ||
70 | |||
71 | instance Ord Peer where | ||
72 | LocalHost <= _ | ||
73 | = True | ||
74 | RemotePeer (SockAddrUnix a) <= RemotePeer (SockAddrUnix b) | ||
75 | = a <= b | ||
76 | RemotePeer (SockAddrUnix _) <= _ | ||
77 | = True | ||
78 | RemotePeer (SockAddrInet aport a) <= RemotePeer (SockAddrInet bport b) | ||
79 | = (a,aport) <= (b,bport) | ||
80 | RemotePeer (SockAddrInet aport a) <= _ | ||
81 | = True | ||
82 | RemotePeer (SockAddrInet6 aport aflow a ascope) <= RemotePeer (SockAddrInet6 bport bflow b bscope) | ||
83 | = (a,aport,ascope,aflow) <= (b,bport,bscope,bflow) | ||
84 | a <= b = False | ||
63 | 85 | ||
64 | -- | Jabber ID (JID) datatype | 86 | -- | Jabber ID (JID) datatype |
65 | data JID = JID { name :: Maybe ByteString | 87 | data JID = JID { name :: Maybe ByteString |
66 | , server :: ByteString | 88 | , peer :: Peer |
67 | , resource :: Maybe ByteString | 89 | , resource :: Maybe ByteString |
68 | } | 90 | } |
69 | deriving (Ord,Eq) | 91 | deriving (Eq,Ord) |
92 | |||
93 | is_remote (RemotePeer _) = True | ||
94 | is_remote _ = False | ||
95 | |||
96 | getNamesForPeer :: Peer -> IO [ByteString] | ||
97 | getNamesForPeer LocalHost = fmap ((:[]) . pack) getHostName | ||
98 | getNamesForPeer peer@(RemotePeer addr) = do | ||
99 | {- | ||
100 | let hints = Just $ defaultHints { addrFlags = [ AI_CANONNAME ] } | ||
101 | L.putStrLn $ "getAddrInfo 1 " <++> showPeer peer | ||
102 | infos <- getAddrInfo hints (Just . unpack . showPeer $ peer) Nothing | ||
103 | return . map pack . mapMaybe addrCanonName $ infos | ||
104 | -} | ||
105 | -- ent <- getHostByName (unpack . showPeer $ peer) | ||
106 | ent <- getHostByAddr addr -- AF_UNSPEC addr | ||
107 | let names = hostName ent : hostAliases ent | ||
108 | return . map pack $ names | ||
109 | |||
110 | |||
111 | showPeer :: Peer -> ByteString | ||
112 | showPeer LocalHost = "localhost" | ||
113 | showPeer (RemotePeer addr@(SockAddrInet _ _)) = pack $ stripColon (Prelude.show addr) | ||
114 | where stripColon s = pre where (pre,port) = break (==':') s | ||
115 | showPeer (RemotePeer addr@(SockAddrInet6 _ _ _ _)) = pack $ stripColon (Prelude.show addr) | ||
116 | where stripColon s = if null bracket then pre else pre ++ "]" | ||
117 | where | ||
118 | (pre,bracket) = break (==']') s | ||
119 | |||
120 | peerAddr :: Peer -> SockAddr | ||
121 | peerAddr (RemotePeer addr) = addr | ||
122 | -- peerAddr LocalHost = throw exception | ||
70 | 123 | ||
71 | instance L.Show JID where | 124 | instance L.Show JID where |
72 | showp (JID n s r ) = putBuilder . B.fromLazyByteString $ n <$++> "@" <?++> s <++?> "/" <++$> r | 125 | showp (JID n s r ) = putBuilder . B.fromLazyByteString $ n <$++> "@" <?++> showPeer s <++?> "/" <++$> r |
73 | 126 | ||
74 | instance Prelude.Show JID where | 127 | instance Prelude.Show JID where |
75 | show jid = L.unpack $ L.show jid | 128 | show jid = L.unpack $ L.show jid |
@@ -87,12 +140,38 @@ data JabberShow = Offline | |||
87 | data Presence = Presence JID JabberShow | 140 | data Presence = Presence JID JabberShow |
88 | deriving Prelude.Show | 141 | deriving Prelude.Show |
89 | 142 | ||
90 | xmlifyPresence (Presence jid stat) = L.unlines | 143 | xmlifyPresenceForPeer sock (Presence jid stat) = do |
91 | [ "<presence from='" <++> L.show jid <++> "' " <++> typ stat <++> ">" | 144 | -- TODO: accept socket argument and determine local ip address |
92 | , "<show>" <++> shw stat <++> "</show>" | 145 | -- connected to this peer. |
93 | , "</presence>" | 146 | addr <- getSocketName sock |
94 | ] | 147 | let n = name jid |
148 | rsc = resource jid | ||
149 | jid_str = n <$++> "@" <?++> showPeer (RemotePeer addr) <++?> "/" <++$> rsc | ||
150 | return . L.unlines $ | ||
151 | [ "<presence from='" <++> jid_str <++> "' " <++> typ stat <++> ">" | ||
152 | , "<show>" <++> shw stat <++> "</show>" | ||
153 | , "</presence>" | ||
154 | ] | ||
155 | where | ||
156 | typ Offline = " type='unavailable'" | ||
157 | typ _ = "" | ||
158 | shw Available = "chat" | ||
159 | shw Away = "away" | ||
160 | shw Offline = "away" -- Is this right? | ||
161 | |||
162 | xmlifyPresenceForClient (Presence jid stat) = do | ||
163 | let n = name jid | ||
164 | rsc = resource jid | ||
165 | names <- getNamesForPeer (peer jid) | ||
166 | let tostr p = n <$++> "@" <?++> p <++?> "/" <++$> rsc | ||
167 | jidstrs = fmap tostr names | ||
168 | return (L.concat $ map doit jidstrs) | ||
95 | where | 169 | where |
170 | doit jidstr = L.unlines | ||
171 | [ "<presence from='" <++> jidstr <++> "' " <++> typ stat <++> ">" | ||
172 | , "<show>" <++> shw stat <++> "</show>" | ||
173 | , "</presence>" | ||
174 | ] | ||
96 | typ Offline = " type='unavailable'" | 175 | typ Offline = " type='unavailable'" |
97 | typ _ = "" | 176 | typ _ = "" |
98 | shw Available = "chat" | 177 | shw Available = "chat" |
@@ -160,7 +239,7 @@ startCon session_factory sock st = do | |||
160 | L.putStrLn $ "PRESENCE: " <++> bshow presence | 239 | L.putStrLn $ "PRESENCE: " <++> bshow presence |
161 | -- TODO: it violates spec to send presence information before | 240 | -- TODO: it violates spec to send presence information before |
162 | -- a resource is bound. | 241 | -- a resource is bound. |
163 | let r = xmlifyPresence presence | 242 | r <- xmlifyPresenceForClient presence |
164 | hPutStrLn h r | 243 | hPutStrLn h r |
165 | L.putStrLn $ "\nOUT:\n" <++> r | 244 | L.putStrLn $ "\nOUT:\n" <++> r |
166 | Right (Send r) -> | 245 | Right (Send r) -> |
@@ -282,7 +361,11 @@ doCon st elem cont = do | |||
282 | atomically $ writeTChan cmdChan (Send r) | 361 | atomically $ writeTChan cmdChan (Send r) |
283 | -- hPutStrLn h r | 362 | -- hPutStrLn h r |
284 | L.putStrLn $ "\nOUT:\n" <++> r | 363 | L.putStrLn $ "\nOUT:\n" <++> r |
285 | host <- fmap server $ getJID session | 364 | -- host <- fmap pack $ getHostName -- Assume localhost for client session JID |
365 | host <- do | ||
366 | jid <- getJID session | ||
367 | names <- getNamesForPeer (peer jid) | ||
368 | return (head names) | ||
286 | 369 | ||
287 | putStrLn $ (Prelude.show $ hang (text "\nIN:") 2 $ pp elem) ++ "\n" | 370 | putStrLn $ (Prelude.show $ hang (text "\nIN:") 2 $ pp elem) ++ "\n" |
288 | 371 | ||
@@ -369,16 +452,18 @@ doPeer st elem cont = do | |||
369 | case (jid,typ) of | 452 | case (jid,typ) of |
370 | (Just jid,Just "unavailable") -> do | 453 | (Just jid,Just "unavailable") -> do |
371 | L.putStrLn $ "INBOUND PRESENCE! Offline jid=" <++> jid | 454 | L.putStrLn $ "INBOUND PRESENCE! Offline jid=" <++> jid |
372 | announcePresence session (Presence (parseJID jid) Offline) | 455 | -- parseAddressJID -- convert peer reported user@address to JID data structure |
456 | peer_jid <- parseAddressJID jid | ||
457 | announcePresence session (Presence peer_jid Offline) | ||
373 | (Just jid,Just typ) -> | 458 | (Just jid,Just typ) -> |
374 | -- possible probe, ignored for now | 459 | -- possible probe, ignored for now |
375 | L.putStrLn $ "INBOUND PRESENCE! "<++>typ<++>" jid="<++>jid | 460 | L.putStrLn $ "INBOUND PRESENCE! "<++>typ<++>" jid="<++>jid |
376 | (Just jid,Nothing) -> do | 461 | (Just jid,Nothing) -> do |
377 | let string (CString _ s _) = [s] | 462 | let string (CString _ s _) = [s] |
378 | show = listToMaybe . Prelude.concatMap string . tagcontent "show" $ content | 463 | stat = listToMaybe . Prelude.concatMap string . tagcontent "show" $ content |
379 | |||
380 | -- Available or Away. | 464 | -- Available or Away. |
381 | L.putStrLn $ "INBOUND PRESENCE! avail/away jid=" <++> jid | 465 | names <- parseAddressJID jid >>= getNamesForPeer . peer |
466 | L.putStrLn $ "INBOUND PRESENCE! "<++>bshow (stat,names)<++>" avail/away jid=" <++> jid | ||
382 | -- todo: announcePresence | 467 | -- todo: announcePresence |
383 | _ -> return () -- putStrLn $ "inbound unhandled: "++show v | 468 | _ -> return () -- putStrLn $ "inbound unhandled: "++show v |
384 | cont () | 469 | cont () |
@@ -426,16 +511,16 @@ sendMessage cons msg peer = do | |||
426 | let newEntry = do | 511 | let newEntry = do |
427 | chan <- atomically newTChan | 512 | chan <- atomically newTChan |
428 | t <- forkIO $ connect_to_server chan peer | 513 | t <- forkIO $ connect_to_server chan peer |
429 | L.putStrLn $ "remote-map new: " <++> peer | 514 | L.putStrLn $ "remote-map new: " <++> showPeer peer |
430 | return (True,(chan,t)) | 515 | return (True,(chan,t)) |
431 | (is_new,entry) <- maybe newEntry | 516 | (is_new,entry) <- maybe newEntry |
432 | ( \(chan,t) -> do | 517 | ( \(chan,t) -> do |
433 | st <- threadStatus t | 518 | st <- threadStatus t |
434 | let running = do | 519 | let running = do |
435 | L.putStrLn $ "remote-map, thread running: " <++> peer | 520 | L.putStrLn $ "remote-map, thread running: " <++> showPeer peer |
436 | return (False,(chan,t)) | 521 | return (False,(chan,t)) |
437 | died = do | 522 | died = do |
438 | L.putStrLn $ "remote-map, thread died("<++>bshow st<++>"): " <++> peer | 523 | L.putStrLn $ "remote-map, thread died("<++>bshow st<++>"): " <++> showPeer peer |
439 | newEntry | 524 | newEntry |
440 | case st of | 525 | case st of |
441 | ThreadRunning -> running | 526 | ThreadRunning -> running |
@@ -444,7 +529,7 @@ sendMessage cons msg peer = do | |||
444 | ThreadFinished -> died | 529 | ThreadFinished -> died |
445 | ) | 530 | ) |
446 | found | 531 | found |
447 | L.putStrLn $ "sendMessage ->"<++>peer<++>": "<++>bshow msg | 532 | L.putStrLn $ "sendMessage ->"<++>showPeer peer<++>": "<++>bshow msg |
448 | atomically $ writeTChan (fst entry) msg | 533 | atomically $ writeTChan (fst entry) msg |
449 | when is_new . atomically $ | 534 | when is_new . atomically $ |
450 | readTVar cons >>= writeTVar cons . Map.insert peer entry | 535 | readTVar cons >>= writeTVar cons . Map.insert peer entry |
@@ -452,7 +537,7 @@ sendMessage cons msg peer = do | |||
452 | connect_to_server chan peer = (>> return ()) . runMaybeT $ do | 537 | connect_to_server chan peer = (>> return ()) . runMaybeT $ do |
453 | let port = "5269" | 538 | let port = "5269" |
454 | 539 | ||
455 | connected <- liftIO . async $ connect' (unpack peer) port | 540 | connected <- liftIO . async $ connect' (peerAddr peer) port |
456 | 541 | ||
457 | -- We'll cache Presence notifications until the socket | 542 | -- We'll cache Presence notifications until the socket |
458 | -- is ready. | 543 | -- is ready. |
@@ -486,14 +571,14 @@ connect_to_server chan peer = (>> return ()) . runMaybeT $ do | |||
486 | cache <- fmap Map.assocs . readIORef $ cached | 571 | cache <- fmap Map.assocs . readIORef $ cached |
487 | writeIORef cached Map.empty -- hint garbage collector: we're done with this | 572 | writeIORef cached Map.empty -- hint garbage collector: we're done with this |
488 | forM_ cache $ \(jid,st) -> do | 573 | forM_ cache $ \(jid,st) -> do |
489 | let r = xmlifyPresence (Presence jid st) | 574 | r <- xmlifyPresenceForPeer sock (Presence jid st) |
490 | hPutStrLn h r | 575 | hPutStrLn h r |
491 | L.putStrLn $ "REMOTE-OUT (cache):\n" <++> r <++> "\n" | 576 | L.putStrLn $ "REMOTE-OUT (cache):\n" <++> r <++> "\n" |
492 | fix $ \loop -> do | 577 | fix $ \loop -> do |
493 | event <- atomically $ readTChan chan | 578 | event <- atomically $ readTChan chan |
494 | case event of | 579 | case event of |
495 | OutBoundPresence p -> do | 580 | OutBoundPresence p -> do |
496 | let r = xmlifyPresence p | 581 | r <- xmlifyPresenceForPeer sock p |
497 | hPutStrLn h r | 582 | hPutStrLn h r |
498 | L.putStrLn $ "REMOTE-OUT:\n" <++> r <++> "\n" | 583 | L.putStrLn $ "REMOTE-OUT:\n" <++> r <++> "\n" |
499 | loop | 584 | loop |
@@ -501,34 +586,90 @@ connect_to_server chan peer = (>> return ()) . runMaybeT $ do | |||
501 | L.putStrLn $ "REMOTE-OUT: </stream>" | 586 | L.putStrLn $ "REMOTE-OUT: </stream>" |
502 | 587 | ||
503 | 588 | ||
504 | parseJID :: ByteString -> JID | 589 | splitJID :: ByteString -> (Maybe ByteString,ByteString,Maybe ByteString) |
505 | parseJID bjid = | 590 | splitJID bjid = |
506 | let xs = L.splitWith (=='@') bjid | 591 | let xs = L.splitWith (=='@') bjid |
507 | ys = L.splitWith (=='/') (last xs) | 592 | ys = L.splitWith (=='/') (last xs) |
508 | (name,server) | 593 | server = head ys |
594 | name | ||
509 | = case xs of | 595 | = case xs of |
510 | (n:s:_) -> (Just n,s) | 596 | (n:s:_) -> Just n |
511 | (s:_) -> (Nothing,s) | 597 | (s:_) -> Nothing |
512 | rsrc = case ys of | 598 | rsrc = case ys of |
513 | (s:_:_) -> Just $ last ys | 599 | (s:_:_) -> Just $ last ys |
514 | _ -> Nothing | 600 | _ -> Nothing |
515 | in JID name server rsrc | 601 | in (name,server,rsrc) |
516 | 602 | ||
517 | connect' :: HostName -> ServiceName -> IO (Maybe Socket) | 603 | strip_brackets s = |
518 | connect' host serv = do | 604 | case L.uncons s of |
605 | Just ('[',t) -> L.takeWhile (/=']') t | ||
606 | _ -> s | ||
607 | |||
608 | parseAddressJID :: ByteString -> IO JID | ||
609 | parseAddressJID jid = do | ||
610 | let (name,peer_string,rsc) = splitJID jid | ||
611 | hints = Just $ defaultHints { addrFlags = [ {- AI_NUMERICHOST, -} AI_CANONNAME ] } | ||
612 | peer_string' = unpack . strip_brackets $ peer_string | ||
613 | peer <- do | ||
614 | putStrLn $ "getAddrInfo 2 " ++ Prelude.show ( Just (unpack peer_string)) | ||
615 | info <- getAddrInfo hints (Just peer_string') Nothing -- (Just "xmpp-server") | ||
616 | let info0 = head info | ||
617 | return . RemotePeer . addrAddress $ info0 | ||
618 | return $ JID name peer rsc | ||
619 | |||
620 | parseHostNameJID :: ByteString -> IO JID | ||
621 | parseHostNameJID jid = do | ||
622 | let (name,peer_string,rsc) = splitJID jid | ||
623 | hints = Just $ defaultHints { addrFlags = [ AI_CANONNAME ] } | ||
624 | peer <- do | ||
625 | if peer_string=="localhost" | ||
626 | then return LocalHost | ||
627 | else do | ||
628 | putStrLn $ "getAddrInfo 3 " ++ Prelude.show ( Just (unpack peer_string)) | ||
629 | info <- getAddrInfo hints (Just (unpack peer_string)) Nothing -- (Just "xmpp-server") | ||
630 | let info0 = head info | ||
631 | cname = addrCanonName info0 | ||
632 | if cname==Just "localhost" | ||
633 | then return LocalHost | ||
634 | else do | ||
635 | self <- getHostName | ||
636 | return $ if Just self==cname | ||
637 | then LocalHost | ||
638 | else RemotePeer (addrAddress info0) | ||
639 | return $ JID name peer rsc | ||
640 | |||
641 | socketFamily (SockAddrInet _ _) = AF_INET | ||
642 | socketFamily (SockAddrInet6 _ _ _ _) = AF_INET6 | ||
643 | socketFamily (SockAddrUnix _) = AF_UNIX | ||
644 | |||
645 | connect' :: SockAddr -> ServiceName -> IO (Maybe Socket) | ||
646 | connect' addr serv = do | ||
519 | proto <- getProtocolNumber "tcp" | 647 | proto <- getProtocolNumber "tcp" |
648 | {- | ||
649 | -- Given (host :: HostName) ... | ||
520 | let hints = defaultHints { addrFlags = [AI_ADDRCONFIG] | 650 | let hints = defaultHints { addrFlags = [AI_ADDRCONFIG] |
521 | , addrProtocol = proto | 651 | , addrProtocol = proto |
522 | , addrSocketType = Stream } | 652 | , addrSocketType = Stream } |
523 | addrs <- getAddrInfo (Just hints) (Just host) (Just serv) | 653 | addrs <- getAddrInfo (Just hints) (Just host) (Just serv) |
524 | firstSuccessful $ map tryToConnect addrs | 654 | firstSuccessful $ map tryToConnect addrs |
655 | -} | ||
656 | let getport (SockAddrInet port _) = port | ||
657 | getport (SockAddrInet6 port _ _ _) = port | ||
658 | let port = getport addr | ||
659 | let withPort (SockAddrInet _ a) port = SockAddrInet port a | ||
660 | withPort (SockAddrInet6 _ a b c) port = SockAddrInet6 port a b c | ||
661 | let doException (SomeException e) = do | ||
662 | L.putStrLn $ "\nFailed to reach "<++> showPeer (RemotePeer addr) <++> " on port "<++>bshow port<++>": " <++> bshow e | ||
663 | return Nothing | ||
664 | handle doException | ||
665 | $ tryToConnect proto (addr `withPort` 5269) | ||
525 | where | 666 | where |
526 | tryToConnect addr = | 667 | tryToConnect proto addr = |
527 | bracketOnError | 668 | bracketOnError |
528 | (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)) | 669 | (socket (socketFamily addr) Stream proto) |
529 | (sClose ) -- only done if there's an error | 670 | (sClose ) -- only done if there's an error |
530 | (\sock -> do | 671 | (\sock -> do |
531 | connect sock (addrAddress addr) | 672 | connect sock addr |
532 | return (Just sock) -- socketToHandle sock ReadWriteMode | 673 | return (Just sock) -- socketToHandle sock ReadWriteMode |
533 | ) | 674 | ) |
534 | 675 | ||
@@ -547,8 +688,8 @@ firstSuccessful (p:ps) = catchIO p $ \e -> | |||
547 | 688 | ||
548 | 689 | ||
549 | seekRemotePeers :: XMPPConfig config => | 690 | seekRemotePeers :: XMPPConfig config => |
550 | (ByteString -> Bool) -> config -> TChan Presence -> IO b0 | 691 | config -> TChan Presence -> IO b0 |
551 | seekRemotePeers is_peer config chan = do | 692 | seekRemotePeers config chan = do |
552 | server_connections <- newServerConnections | 693 | server_connections <- newServerConnections |
553 | fix $ \loop -> do | 694 | fix $ \loop -> do |
554 | event <- atomically $ readTChan chan | 695 | event <- atomically $ readTChan chan |
@@ -557,10 +698,13 @@ seekRemotePeers is_peer config chan = do | |||
557 | L.putStrLn $ "seekRemotePeers: " <++> L.show jid <++> " " <++> bshow stat | 698 | L.putStrLn $ "seekRemotePeers: " <++> L.show jid <++> " " <++> bshow stat |
558 | runMaybeT $ do | 699 | runMaybeT $ do |
559 | u <- MaybeT . return $ name jid | 700 | u <- MaybeT . return $ name jid |
560 | subscribers <- liftIO $ getSubscribers config u | 701 | subscribers <- liftIO $ do |
702 | subs <- getSubscribers config u | ||
703 | mapM parseHostNameJID subs | ||
561 | liftIO . L.putStrLn $ "subscribers: " <++> bshow subscribers | 704 | liftIO . L.putStrLn $ "subscribers: " <++> bshow subscribers |
562 | let peers = Set.map (server . parseJID) (Set.fromList subscribers) | 705 | -- parseJID -- get subscriber list reported user@hostname to JID data structure |
706 | let peers = Set.map peer (Set.fromList subscribers) | ||
563 | forM_ (Set.toList peers) $ \peer -> do | 707 | forM_ (Set.toList peers) $ \peer -> do |
564 | when (is_peer peer) $ | 708 | when (is_remote peer) $ |
565 | liftIO $ sendMessage server_connections (OutBoundPresence p) peer | 709 | liftIO $ sendMessage server_connections (OutBoundPresence p) peer |
566 | loop | 710 | loop |