diff options
-rw-r--r-- | ConduitServer.hs | 48 | ||||
-rw-r--r-- | Presence/XMPP.hs | 1461 | ||||
-rw-r--r-- | simplechat.hs | 66 | ||||
-rw-r--r-- | test-server.hs | 541 |
4 files changed, 0 insertions, 2116 deletions
diff --git a/ConduitServer.hs b/ConduitServer.hs deleted file mode 100644 index 0838ce26..00000000 --- a/ConduitServer.hs +++ /dev/null | |||
@@ -1,48 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | module Main where | ||
3 | |||
4 | import Data.Conduit.Binary | ||
5 | import Data.Conduit.Network | ||
6 | import Data.Conduit | ||
7 | import qualified Data.Conduit.List as CL | ||
8 | import qualified Data.ByteString.Char8 as S | ||
9 | |||
10 | import Network.Socket (Family(..)) | ||
11 | import Data.HList | ||
12 | import ServerC | ||
13 | |||
14 | {- | ||
15 | data AppData m = AppData | ||
16 | { appSource :: Source m ByteString | ||
17 | , appSink :: Sink ByteString m () | ||
18 | , appSockAddr :: SockAddr | ||
19 | , appLocalAddr :: Maybe SockAddr | ||
20 | } | ||
21 | -} | ||
22 | |||
23 | -- handleConnection will simply output everything | ||
24 | -- it sees from the connection to the terminal | ||
25 | handleConnection :: AppData IO -> IO () | ||
26 | handleConnection appdata = do | ||
27 | sourceLbs "<stream>\n" $$ appSink appdata -- send bytestring | ||
28 | appSource appdata $$ CL.mapM_ S.putStrLn -- display inbound bytestring | ||
29 | |||
30 | mainOld = do | ||
31 | -- Listen to port 5222 and invoke handleConnection on every | ||
32 | -- inbound connection. | ||
33 | runTCPServer (serverSettings 5222 HostAny) handleConnection | ||
34 | return () | ||
35 | |||
36 | |||
37 | |||
38 | |||
39 | handleC st src snk = do | ||
40 | sourceLbs "<stream>\n" $$ snk | ||
41 | src $$ CL.mapM_ S.putStrLn | ||
42 | |||
43 | mainC = do | ||
44 | doServer (AF_INET .*. 5222 .*. HNil) handleC | ||
45 | _ <- getLine | ||
46 | return () | ||
47 | |||
48 | main = mainC | ||
diff --git a/Presence/XMPP.hs b/Presence/XMPP.hs deleted file mode 100644 index eab57da5..00000000 --- a/Presence/XMPP.hs +++ /dev/null | |||
@@ -1,1461 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | {-# LANGUAGE FlexibleContexts #-} | ||
3 | {-# LANGUAGE ViewPatterns #-} | ||
4 | {-# LANGUAGE TypeFamilies #-} | ||
5 | {-# LANGUAGE CPP #-} | ||
6 | module XMPP | ||
7 | ( module XMPPTypes | ||
8 | , listenForXmppClients | ||
9 | , listenForRemotePeers | ||
10 | , newServerConnections | ||
11 | , seekRemotePeers | ||
12 | , quitListening | ||
13 | , OutBoundMessage(..) | ||
14 | , OutgoingConnections | ||
15 | , CachedMessages | ||
16 | , toPeer | ||
17 | , newOutgoingConnections | ||
18 | , sendMessage | ||
19 | ) where | ||
20 | |||
21 | import ServerC | ||
22 | import XMPPTypes | ||
23 | import ByteStringOperators | ||
24 | import ControlMaybe | ||
25 | import XMLToByteStrings | ||
26 | import SendMessage | ||
27 | import Logging | ||
28 | import Todo | ||
29 | |||
30 | import Data.Maybe (catMaybes) | ||
31 | import Data.HList hiding (hHead) | ||
32 | import Network.Socket ( Family ) | ||
33 | import Control.Concurrent.STM | ||
34 | import Control.Concurrent.STM.Delay | ||
35 | import Data.Conduit | ||
36 | import Data.Maybe | ||
37 | import Data.ByteString (ByteString) | ||
38 | import qualified Data.ByteString.Lazy.Char8 as L | ||
39 | ( fromChunks | ||
40 | ) | ||
41 | import Control.Concurrent.Async | ||
42 | import Control.Exception as E ( finally ) | ||
43 | import System.IO.Error (isDoesNotExistError) | ||
44 | import Control.Monad.IO.Class | ||
45 | import Control.Monad.Trans.Class | ||
46 | import Control.Monad.Trans.Maybe | ||
47 | import Text.XML.Stream.Parse (def,parseBytes,content) | ||
48 | import Data.XML.Types as XML | ||
49 | import qualified Data.Text as S (Text,takeWhile) | ||
50 | import Data.Text.Encoding as S (decodeUtf8,encodeUtf8) | ||
51 | import Data.Text.Lazy.Encoding as L (decodeUtf8) | ||
52 | import Data.Text.Lazy (toStrict) | ||
53 | import qualified Data.Sequence as Seq | ||
54 | import Data.Foldable (toList) | ||
55 | import Data.List (find) | ||
56 | import qualified Text.Show.ByteString as L | ||
57 | import NestingXML | ||
58 | import Data.Set as Set (Set,(\\)) | ||
59 | import qualified Data.Set as Set | ||
60 | import qualified Data.Map as Map | ||
61 | import Data.Map as Map (Map) | ||
62 | |||
63 | #if MIN_VERSION_HList(0,3,0) | ||
64 | #define HCONS HCons' | ||
65 | #else | ||
66 | #define HCONS HCons | ||
67 | #endif | ||
68 | |||
69 | hHead (HCONS x _) = x | ||
70 | |||
71 | textToByteString x = L.fromChunks [S.encodeUtf8 x] | ||
72 | |||
73 | |||
74 | |||
75 | xmlifyPresenceForClient :: Presence -> IO [XML.Event] | ||
76 | xmlifyPresenceForClient (Presence jid stat) = do | ||
77 | let n = name jid | ||
78 | rsc = resource jid | ||
79 | names <- getNamesForPeer (peer jid) | ||
80 | let tostr p = L.decodeUtf8 $ n <$++> "@" <?++> L.fromChunks [p] <++?> "/" <++$> rsc | ||
81 | jidstrs = fmap (toStrict . tostr) names | ||
82 | return (concatMap presenceEvents jidstrs) | ||
83 | where | ||
84 | presenceEvents jidstr = | ||
85 | [ EventBeginElement "{jabber:client}presence" (("from",[ContentText jidstr]):typ stat) ] | ||
86 | ++ ( shw stat >>= jabberShow ) ++ | ||
87 | [ EventEndElement "{jabber:client}presence" ] | ||
88 | typ Offline = [("type",[ContentText "unavailable"])] | ||
89 | typ _ = [] | ||
90 | shw ExtendedAway = ["xa"] | ||
91 | shw Chatty = ["chat"] | ||
92 | shw Away = ["away"] | ||
93 | shw DoNotDisturb = ["dnd"] | ||
94 | shw _ = [] | ||
95 | jabberShow stat = | ||
96 | [ EventBeginElement "{jabber:client}show" [] | ||
97 | , EventContent (ContentText stat) | ||
98 | , EventEndElement "{jabber:client}show" ] | ||
99 | |||
100 | prefix ## name = Name name Nothing (Just prefix) | ||
101 | |||
102 | streamP name = Name name (Just "http://etherx.jabber.org/streams") (Just "stream") | ||
103 | |||
104 | greet host = | ||
105 | [ EventBeginDocument | ||
106 | , EventBeginElement (streamP "stream") | ||
107 | [("from",[ContentText host]) | ||
108 | ,("id",[ContentText "someid"]) | ||
109 | ,("xmlns",[ContentText "jabber:client"]) | ||
110 | ,("xmlns:stream",[ContentText "http://etherx.jabber.org/streams"]) | ||
111 | ,("version",[ContentText "1.0"]) | ||
112 | ] | ||
113 | , EventBeginElement (streamP "features") [] | ||
114 | , EventBeginElement "{urn:ietf:params:xml:ns:xmpp-bind}bind" [] | ||
115 | , EventEndElement "{urn:ietf:params:xml:ns:xmpp-bind}bind" | ||
116 | |||
117 | {- | ||
118 | -- , " <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>" | ||
119 | , " <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" | ||
120 | -- , " <mechanism>DIGEST-MD5</mechanism>" | ||
121 | , " <mechanism>PLAIN</mechanism>" | ||
122 | , " </mechanisms> " | ||
123 | -} | ||
124 | |||
125 | , EventEndElement (streamP "features") | ||
126 | ] | ||
127 | |||
128 | |||
129 | -- type Consumer i m r = forall o. ConduitM i o m r | ||
130 | mawait :: Monad m => MaybeT (ConduitM i o m) i | ||
131 | mawait = MaybeT await | ||
132 | |||
133 | -- Note: This function ignores name space qualification | ||
134 | elementAttrs expected (EventBeginElement name attrs) | ||
135 | | nameLocalName name==expected | ||
136 | = return attrs | ||
137 | elementAttrs _ _ = mzero | ||
138 | |||
139 | eventIsBeginElement (EventBeginElement _ _) = True | ||
140 | eventIsBeginElement _ = False | ||
141 | |||
142 | eventIsEndElement (EventEndElement _) = True | ||
143 | eventIsEndElement _ = False | ||
144 | |||
145 | filterMapElement:: | ||
146 | (Monad m, MonadPlus mp) => | ||
147 | (Event -> mp a) -> Event -> mp a -> MaybeT (ConduitM Event o m) (mp a) | ||
148 | filterMapElement ret opentag empty = loop (empty `mplus` ret opentag) 1 | ||
149 | where | ||
150 | loop ts 0 = return ts | ||
151 | loop ts cnt = do | ||
152 | tag <- mawait | ||
153 | let ts' = mplus ts (ret tag) | ||
154 | case () of | ||
155 | _ | eventIsEndElement tag -> loop ts' (cnt-1) | ||
156 | _ | eventIsBeginElement tag -> loop ts' (cnt+1) | ||
157 | _ -> loop ts' cnt | ||
158 | |||
159 | gatherElement :: | ||
160 | (Monad m, MonadPlus mp) => | ||
161 | Event -> mp Event -> NestingXML o m (mp Event) | ||
162 | gatherElement opentag empty = loop (empty `mplus` return opentag) 1 | ||
163 | where | ||
164 | loop ts 0 = return ts | ||
165 | loop ts cnt = do | ||
166 | maybeXML (return ts) $ \tag -> do | ||
167 | let ts' = mplus ts (return tag) | ||
168 | case () of | ||
169 | _ | eventIsEndElement tag -> loop ts' (cnt-1) | ||
170 | _ | eventIsBeginElement tag -> loop ts' (cnt+1) | ||
171 | _ -> loop ts' cnt | ||
172 | |||
173 | |||
174 | voidMaybeT body = (>> return ()) . runMaybeT $ body | ||
175 | fixMaybeT f = (>> return ()) . runMaybeT . fix $ f | ||
176 | |||
177 | iq_bind_reply id jid = | ||
178 | [ EventBeginElement "{jabber:client}iq" [("type",[ContentText "result"]),("id",[ContentText id])] | ||
179 | |||
180 | , EventBeginElement "{urn:ietf:params:xml:ns:xmpp-bind}bind" | ||
181 | [("xmlns",[ContentText "urn:ietf:params:xml:ns:xmpp-bind"])] | ||
182 | , EventBeginElement "{urn:ietf:params:xml:ns:xmpp-bind}jid" [] | ||
183 | , EventContent (ContentText jid) | ||
184 | , EventEndElement "{urn:ietf:params:xml:ns:xmpp-bind}jid" | ||
185 | , EventEndElement "{urn:ietf:params:xml:ns:xmpp-bind}bind" | ||
186 | , EventEndElement "{jabber:client}iq" | ||
187 | ] | ||
188 | |||
189 | uncontent cs = head $ map getText cs | ||
190 | where | ||
191 | getText (ContentText x) = x | ||
192 | getText (ContentEntity x ) = x | ||
193 | |||
194 | tagAttrs (EventBeginElement _ xs) = xs | ||
195 | tagAttrs _ = [] | ||
196 | |||
197 | tagName (EventBeginElement n _) = n | ||
198 | tagName _ = "" | ||
199 | |||
200 | handleIQSetBind session cmdChan stanza_id = do | ||
201 | mchild <- nextElement | ||
202 | rsc <- case mchild of | ||
203 | Just child -> do | ||
204 | let unhandledBind = do | ||
205 | liftIO $ debugStr $ "unhandled-bind: "++show child | ||
206 | return "" | ||
207 | case tagName child of | ||
208 | "{urn:ietf:params:xml:ns:xmpp-bind}resource" | ||
209 | -> do | ||
210 | rsc <- lift content | ||
211 | return . textToByteString $ rsc | ||
212 | _ -> unhandledBind | ||
213 | Nothing -> do | ||
214 | liftIO $ debugStr $ "empty bind request!" | ||
215 | return "" | ||
216 | liftIO $ do | ||
217 | debugL $ "iq-set-bind-resource " <++> rsc | ||
218 | setResource session rsc | ||
219 | jid <- getJID session | ||
220 | atomically $ do | ||
221 | writeTChan cmdChan (Send $ iq_bind_reply stanza_id (toStrict $ L.decodeUtf8 $ L.show jid) ) | ||
222 | writeTChan cmdChan BoundToResource | ||
223 | forCachedPresence session $ \presence -> do | ||
224 | xs <- xmlifyPresenceForClient presence | ||
225 | atomically . writeTChan cmdChan . Send $ xs | ||
226 | |||
227 | |||
228 | iq_session_reply host stanza_id = | ||
229 | [ EventBeginElement "{jabber:client}iq" | ||
230 | [("id",[ContentText stanza_id]) | ||
231 | ,("from",[ContentText host]) | ||
232 | ,("type",[ContentText "result"]) | ||
233 | ] | ||
234 | , EventEndElement "{jabber:client}iq" | ||
235 | ] | ||
236 | |||
237 | handleIQSetSession session cmdChan stanza_id = do | ||
238 | host <- liftIO $ do | ||
239 | jid <- getJID session | ||
240 | names <- getNamesForPeer (peer jid) | ||
241 | return (S.decodeUtf8 . head $ names) | ||
242 | liftIO . atomically . writeTChan cmdChan . Send $ iq_session_reply host stanza_id | ||
243 | |||
244 | handleIQSet session cmdChan tag = do | ||
245 | withJust (lookupAttrib "id" (tagAttrs tag)) $ \stanza_id -> do | ||
246 | whenJust nextElement $ \child -> do | ||
247 | let unhandledSet = liftIO $ debugStr ("iq-set: "++show (stanza_id,child)) | ||
248 | case tagName child of | ||
249 | "{urn:ietf:params:xml:ns:xmpp-bind}bind" | ||
250 | -> handleIQSetBind session cmdChan stanza_id | ||
251 | "{urn:ietf:params:xml:ns:xmpp-session}session" | ||
252 | -> handleIQSetSession session cmdChan stanza_id | ||
253 | _ -> unhandledSet | ||
254 | |||
255 | matchAttrib name value attrs = | ||
256 | case find ( (==name) . fst) attrs of | ||
257 | Just (_,[ContentText x]) | x==value -> True | ||
258 | Just (_,[ContentEntity x]) | x==value -> True | ||
259 | _ -> False | ||
260 | |||
261 | lookupAttrib name attrs = | ||
262 | case find ( (==name) . fst) attrs of | ||
263 | Just (_,[ContentText x]) -> Just x | ||
264 | Just (_,[ContentEntity x]) -> Just x | ||
265 | _ -> Nothing | ||
266 | |||
267 | iqTypeSet = "set" | ||
268 | iqTypeGet = "get" | ||
269 | iqTypeResult = "result" | ||
270 | iqTypeError = "error" | ||
271 | |||
272 | isIQOf (EventBeginElement name attrs) testType | ||
273 | | name=="{jabber:client}iq" | ||
274 | && matchAttrib "type" testType attrs | ||
275 | = True | ||
276 | isIQOf _ _ = False | ||
277 | |||
278 | isServerIQOf (EventBeginElement name attrs) testType | ||
279 | | name=="{jabber:server}iq" | ||
280 | && matchAttrib "type" testType attrs | ||
281 | = True | ||
282 | isServerIQOf _ _ = False | ||
283 | |||
284 | iq_service_unavailable host iq_id mjid req = | ||
285 | [ EventBeginElement "{jabber:client}iq" | ||
286 | [("type",[ContentText "error"]) | ||
287 | ,("id",[ContentText iq_id]) | ||
288 | -- , TODO: set "from" if isJust mjid | ||
289 | ] | ||
290 | , EventBeginElement req [] | ||
291 | , EventEndElement req | ||
292 | , EventBeginElement "{jabber:client}error" [("type",[ContentText "cancel"])] | ||
293 | , EventBeginElement "{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable" [] | ||
294 | , EventEndElement "{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable" | ||
295 | , EventEndElement "{jabber:client}error" | ||
296 | , EventEndElement "{jabber:client}iq" | ||
297 | ] | ||
298 | |||
299 | attr name value = (name,[ContentText value]) | ||
300 | attrbs name value = (name,[ContentText (toStrict . L.decodeUtf8 $ value)]) | ||
301 | |||
302 | |||
303 | getRoster session iqid = do | ||
304 | let getlist f = do | ||
305 | bs <- f session | ||
306 | -- js <- mapM parseHostNameJID bs | ||
307 | return (Set.fromList bs) -- js) | ||
308 | buddies <- getlist getMyBuddies | ||
309 | subscribers <- getlist getMySubscribers | ||
310 | solicited <- getlist getMySolicited | ||
311 | subnone0 <- getlist getMyOthers | ||
312 | let subnone = subnone0 \\ (Set.union buddies subscribers) | ||
313 | let subto = buddies \\ subscribers | ||
314 | let subfrom = subscribers \\ buddies | ||
315 | let subboth = Set.intersection buddies subscribers | ||
316 | -- solicited -> ask='subscribe' | ||
317 | jid <- getJID session | ||
318 | let dest = toStrict . L.decodeUtf8 . bshow $ jid | ||
319 | let items= (xmlify solicited "to" subto) | ||
320 | ++(xmlify solicited "from" subfrom) | ||
321 | ++(xmlify solicited "both" subboth) | ||
322 | ++(xmlify solicited "none" subnone) | ||
323 | openiq = [EventBeginElement "{jabber:client}iq" | ||
324 | [ attr "id" iqid | ||
325 | , attr "to" dest | ||
326 | , attr "type" "result" ] | ||
327 | ,EventBeginElement "{jabber:iq:roster}query" | ||
328 | [] -- todo: ver? | ||
329 | ] | ||
330 | closeiq = [EventEndElement "{jabber:iq:roster}query" | ||
331 | ,EventEndElement "{jabber:client}iq"] | ||
332 | return $ openiq ++ items ++ closeiq | ||
333 | where | ||
334 | xmlify solicited stype set = flip concatMap (Set.toList set) | ||
335 | $ \jid -> | ||
336 | [ EventBeginElement "item" | ||
337 | ([ attr "jid" (toStrict . L.decodeUtf8 $ jid) | ||
338 | , attr "subscription" stype | ||
339 | ]++if Set.member jid solicited | ||
340 | then [attr "ask" "subscribe"] | ||
341 | else [] ) | ||
342 | , EventEndElement "item" | ||
343 | ] | ||
344 | |||
345 | handleIQGet session cmdChan tag = do | ||
346 | withJust (lookupAttrib "id" (tagAttrs tag)) $ \stanza_id -> do | ||
347 | whenJust nextElement $ \child -> do | ||
348 | host <- liftIO $ do | ||
349 | jid <- getJID session | ||
350 | names <- getNamesForPeer (peer jid) | ||
351 | return (S.decodeUtf8 . head $ names) | ||
352 | let unhandledGet req = do | ||
353 | liftIO $ debugStr ("iq-get: "++show (stanza_id,child)) | ||
354 | liftIO . atomically . writeTChan cmdChan . Send $ iq_service_unavailable host stanza_id Nothing req | ||
355 | case tagName child of | ||
356 | -- "{http://jabber.org/protocol/disco#items}query" -> liftIO $ debugStr "iq-get-query-items" | ||
357 | "{urn:xmpp:ping}ping" -> liftIO $ do | ||
358 | let mjid = lookupAttrib "from" (tagAttrs tag) | ||
359 | let pong = [ EventBeginElement "{jabber:client}iq" | ||
360 | $ (case mjid of | ||
361 | Just jid -> (attr "to" jid :) | ||
362 | _ -> id ) | ||
363 | [ attr "type" "result" | ||
364 | , attr "id" stanza_id | ||
365 | , attr "from" host | ||
366 | ] | ||
367 | , EventEndElement "{jabber:client}iq" | ||
368 | ] | ||
369 | atomically . writeTChan cmdChan . Send $ pong | ||
370 | "{jabber:iq:roster}query" -> liftIO $ do | ||
371 | debugStr $ "REQUESTED ROSTER " ++ show tag | ||
372 | roster <- getRoster session stanza_id | ||
373 | atomically $ do | ||
374 | writeTChan cmdChan InterestedInRoster | ||
375 | writeTChan cmdChan . Send $ roster | ||
376 | sendPending session | ||
377 | req -> unhandledGet req | ||
378 | |||
379 | |||
380 | handleClientPresence session stanza = do | ||
381 | -- online (Available or Away) | ||
382 | let log = liftIO . debugL . ("(C) " <++>) | ||
383 | log $ "handleClientPresence "<++>bshow stanza | ||
384 | jid <- liftIO $ getJID session | ||
385 | -- cjid <- liftIO $ parseAddressJID (textToByteString jid) | ||
386 | let parseChildren stat = do | ||
387 | child <- nextElement | ||
388 | log $ " child: "<++> bshow child | ||
389 | case child of | ||
390 | Just tag | tagName tag=="{jabber:client}show" | ||
391 | -> fmap toStat (lift content) | ||
392 | Just tag | otherwise -> parseChildren stat | ||
393 | Nothing -> return stat | ||
394 | toStat "away" = Away | ||
395 | toStat "xa" = ExtendedAway | ||
396 | toStat "dnd" = DoNotDisturb | ||
397 | toStat "chat" = Chatty | ||
398 | |||
399 | stat' <- parseChildren Available | ||
400 | liftIO $ setPresence session stat' | ||
401 | log $ "requesting presence: "<++>bshow stat' | ||
402 | return () | ||
403 | |||
404 | |||
405 | fromClient :: (MonadThrow m,MonadIO m, JabberClientSession session) => | ||
406 | session -> TChan ClientCommands -> Sink XML.Event m () | ||
407 | fromClient session cmdChan = doNestingXML $ do | ||
408 | let log = liftIO . debugL . ("(C) " <++>) | ||
409 | send = liftIO . atomically . writeTChan cmdChan . Send | ||
410 | withXML $ \begindoc -> do | ||
411 | when (begindoc==EventBeginDocument) $ do | ||
412 | log "begin-doc" | ||
413 | withXML $ \xml -> do | ||
414 | withJust (elementAttrs "stream" xml) $ \stream_attrs -> do | ||
415 | log $ "stream atributes: " <++> bshow stream_attrs | ||
416 | host <- liftIO $ do | ||
417 | jid <- getJID session | ||
418 | names <- getNamesForPeer (peer jid) | ||
419 | return (S.decodeUtf8 . head $ names) | ||
420 | send $ greet host | ||
421 | |||
422 | fix $ \loop -> do | ||
423 | log "waiting for stanza." | ||
424 | whenJust nextElement $ \stanza -> do | ||
425 | stanza_lvl <- nesting | ||
426 | |||
427 | liftIO . debugStr $ "stanza: "++show stanza | ||
428 | |||
429 | let unhandledStanza = do | ||
430 | xs <- gatherElement stanza Seq.empty | ||
431 | prettyPrint "unhandled-C: " (toList xs) | ||
432 | case () of | ||
433 | _ | stanza `isIQOf` iqTypeSet -> handleIQSet session cmdChan stanza | ||
434 | _ | stanza `isIQOf` iqTypeGet -> handleIQGet session cmdChan stanza | ||
435 | _ | stanza `isClientPresenceOf` presenceTypeSubscribe | ||
436 | -> clientRequestsSubscription session cmdChan stanza | ||
437 | _ | stanza `isClientPresenceOf` presenceTypeSubscribed | ||
438 | -> clientApprovesSubscription session stanza | ||
439 | _ | stanza `isClientPresenceOf` presenceTypeUnsubscribed | ||
440 | -> clientRejectsSubscription session stanza | ||
441 | _ | stanza `isClientPresenceOf` presenceTypeOnline | ||
442 | -> handleClientPresence session stanza | ||
443 | _ | isMessageStanza stanza -> handleClientMessage session stanza | ||
444 | _ | otherwise -> unhandledStanza | ||
445 | |||
446 | awaitCloser stanza_lvl | ||
447 | loop | ||
448 | |||
449 | log $ "end of stream" | ||
450 | withXML $ \xml -> do | ||
451 | log $ "end-of-document: " <++> bshow xml | ||
452 | |||
453 | |||
454 | rosterPush to contact attrs = do | ||
455 | let n = name to | ||
456 | rsc = resource to | ||
457 | names <- getNamesForPeer (peer to) | ||
458 | let tostr p = L.decodeUtf8 $ n <$++> "@" <?++> L.fromChunks [p] <++?> "/" <++$> rsc | ||
459 | jidstrs = fmap (toStrict . tostr) names | ||
460 | tojid = head jidstrs | ||
461 | return | ||
462 | [ EventBeginElement "{jabber:client}iq" | ||
463 | [ attr "to" tojid | ||
464 | , attr "id" "someid" | ||
465 | , attr "type" "set" | ||
466 | ] | ||
467 | , EventBeginElement "{jabber:iq:roster}query" [] | ||
468 | , EventBeginElement "{jabber:iq:roster}item" (attr "jid" contact : attrs) | ||
469 | , EventEndElement "{jabber:iq:roster}item" | ||
470 | , EventEndElement "{jabber:iq:roster}query" | ||
471 | , EventEndElement "{jabber:client}iq" | ||
472 | ] | ||
473 | |||
474 | data EventsForClient = CmdChan ClientCommands | ||
475 | | PChan Presence | ||
476 | | RChan RosterEvent | ||
477 | |||
478 | toClient :: (MonadIO m, JabberClientSession session ) => | ||
479 | session -> TChan Presence -> TChan ClientCommands -> TChan RosterEvent -> Source m [XML.Event] | ||
480 | toClient session pchan cmdChan rchan = toClient' False False | ||
481 | where | ||
482 | toClient' isBound isInterested = do | ||
483 | let loop = toClient' isBound isInterested | ||
484 | send xs = yield xs >> prettyPrint ">C: " xs | ||
485 | event <- liftIO . atomically $ | ||
486 | foldr1 orElse [fmap CmdChan $ readTChan cmdChan | ||
487 | ,fmap RChan $ readTChan rchan | ||
488 | ,fmap PChan $ readTChan pchan | ||
489 | ] | ||
490 | case event of | ||
491 | CmdChan QuitThread -> return () | ||
492 | CmdChan (Send xs) -> send xs >> loop | ||
493 | CmdChan BoundToResource -> toClient' True isInterested | ||
494 | CmdChan InterestedInRoster -> do | ||
495 | liftIO . debugStr $ "Roster: interested" | ||
496 | toClient' isBound True | ||
497 | CmdChan (Chat msg) -> do | ||
498 | xs <- liftIO $ xmlifyMessageForClient msg | ||
499 | send xs | ||
500 | loop | ||
501 | -- CmdChan cmd -> liftIO (debugStr $ "unhandled event: "++show cmd) >> loop | ||
502 | RChan (RequestedSubscription who contact) -> do | ||
503 | jid <- liftIO $ getJID session | ||
504 | when (isInterested && Just who==name jid) $ do | ||
505 | r <- liftIO $ rosterPush jid (toStrict . L.decodeUtf8 $ contact) [attr "ask" "subscribe"] | ||
506 | send r | ||
507 | loop | ||
508 | RChan (NewBuddy who contact) -> do | ||
509 | liftIO . debugStr $ "Roster push: NewBuddy "++show (isInterested,who,contact) | ||
510 | (jid,me) <- liftIO $ do | ||
511 | jid <- getJID session | ||
512 | me <- asHostNameJID jid | ||
513 | return (jid,me) | ||
514 | withJust me $ \me -> do | ||
515 | when (isInterested && Just who==name jid) $ do | ||
516 | send [ EventBeginElement "{jabber:client}presence" | ||
517 | [ attrbs "from" contact | ||
518 | , attrbs "to" me | ||
519 | , attr "type" "subscribed" | ||
520 | ] | ||
521 | , EventEndElement "{jabber:client}presence" ] | ||
522 | let f True = "both" | ||
523 | f False = "to" | ||
524 | subscription <- fmap f (liftIO $ isSubscribed session contact) | ||
525 | r <- liftIO . handleIO (\e -> debugStr ("Roster NewBuddy error: "++show e) >> return []) $ do | ||
526 | rosterPush jid | ||
527 | (toStrict . L.decodeUtf8 $ contact) | ||
528 | [attr "subscription" subscription] | ||
529 | send r | ||
530 | loop | ||
531 | RChan (RemovedBuddy who contact) -> do | ||
532 | liftIO . debugStr $ "Roster push: RemovedBuddy "++show (isInterested,who,contact) | ||
533 | (jid,me) <- liftIO $ do | ||
534 | jid <- getJID session | ||
535 | me <- asHostNameJID jid | ||
536 | return (jid,me) | ||
537 | withJust me $ \me -> do | ||
538 | when (isInterested && Just who==name jid) $ do | ||
539 | send [ EventBeginElement "{jabber:client}presence" | ||
540 | [ attrbs "from" contact | ||
541 | , attrbs "to" me | ||
542 | , attr "type" "unsubscribed" | ||
543 | ] | ||
544 | , EventEndElement "{jabber:client}presence" ] | ||
545 | let f True = "from" | ||
546 | f False = "none" | ||
547 | subscription <- fmap f (liftIO $ isSubscribed session contact) | ||
548 | r <- liftIO . handleIO (\e -> debugStr ("Roster RemovedBuddy error: "++show e) >> return []) $ do | ||
549 | rosterPush jid | ||
550 | (toStrict . L.decodeUtf8 $ contact) | ||
551 | [attr "subscription" subscription] | ||
552 | send r | ||
553 | loop | ||
554 | RChan (NewSubscriber who contact) -> do | ||
555 | liftIO . debugStr $ "Roster push: NewSubscriber "++show (isInterested,who,contact) | ||
556 | (jid,me) <- liftIO $ do | ||
557 | jid <- getJID session | ||
558 | me <- asHostNameJID jid | ||
559 | return (jid,me) | ||
560 | withJust me $ \me -> do | ||
561 | when (isInterested && Just who==name jid) $ do | ||
562 | let f True = "both" | ||
563 | f False = "from" | ||
564 | subscription <- fmap f (liftIO $ isBuddy session contact) | ||
565 | r <- liftIO . handleIO (\e -> debugStr ("Roster NewSubscriber error: "++show e) >> return []) $ do | ||
566 | rosterPush jid | ||
567 | (toStrict . L.decodeUtf8 $ contact) | ||
568 | [attr "subscription" subscription] | ||
569 | send r | ||
570 | loop | ||
571 | RChan (RejectSubscriber who contact) -> do | ||
572 | liftIO . debugStr $ "Roster push: RejectSubscriber "++show (isInterested,who,contact) | ||
573 | (jid,me) <- liftIO $ do | ||
574 | jid <- getJID session | ||
575 | me <- asHostNameJID jid | ||
576 | return (jid,me) | ||
577 | withJust me $ \me -> do | ||
578 | when (isInterested && Just who==name jid) $ do | ||
579 | let f True = "to" | ||
580 | f False = "none" | ||
581 | subscription <- fmap f (liftIO $ isBuddy session contact) | ||
582 | r <- liftIO . handleIO (\e -> debugStr ("Roster RejectSubscriber error: "++show e) >> return []) $ do | ||
583 | rosterPush jid | ||
584 | (toStrict . L.decodeUtf8 $ contact) | ||
585 | [attr "subscription" subscription] | ||
586 | send r | ||
587 | loop | ||
588 | RChan (PendingSubscriber who contact) -> do | ||
589 | liftIO . debugStr $ "Roster: Pending buddy "++show (isInterested,who,contact) | ||
590 | (jid,me) <- liftIO $ do | ||
591 | jid <- getJID session | ||
592 | me <- asHostNameJID jid | ||
593 | return (jid,me) | ||
594 | withJust me $ \me -> do | ||
595 | when (isInterested && Just who==name jid) $ do | ||
596 | send [ EventBeginElement "{jabber:client}presence" | ||
597 | [ attrbs "from" contact | ||
598 | , attrbs "to" me | ||
599 | , attr "type" "subscribe" | ||
600 | ] | ||
601 | , EventEndElement "{jabber:client}presence" ] | ||
602 | loop | ||
603 | PChan presence -> do | ||
604 | when isBound $ do | ||
605 | xs <- liftIO $ xmlifyPresenceForClient presence | ||
606 | send xs | ||
607 | loop | ||
608 | |||
609 | |||
610 | {- | ||
611 | handleClient | ||
612 | :: (SocketLike sock, HHead l (XMPPClass session), | ||
613 | JabberClientSession session) => | ||
614 | HCONS sock (HCONS t l) -> Source IO ByteString -> Sink ByteString IO () -> IO () | ||
615 | -} | ||
616 | handleClient st src snk = do | ||
617 | #if MIN_VERSION_HList(0,3,0) | ||
618 | let HCons' sock (HCons' _ st') = st | ||
619 | #else | ||
620 | let HCons sock (HCons _ st') = st | ||
621 | #endif | ||
622 | session_factory = hHead st' | ||
623 | pname <- getPeerName sock | ||
624 | session <- newSession session_factory sock | ||
625 | debugStr $ "PEER NAME: "++Prelude.show pname | ||
626 | pchan <- subscribe session Nothing | ||
627 | rchan <- subscribeToRoster session | ||
628 | let cmdChan = clientChannel session | ||
629 | |||
630 | writer <- async ( toClient session pchan cmdChan rchan `xmlToByteStrings` snk ) | ||
631 | finally ( src $= parseBytes def $$ fromClient session cmdChan ) | ||
632 | $ do | ||
633 | atomically $ writeTChan cmdChan QuitThread | ||
634 | wait writer | ||
635 | closeSession session | ||
636 | |||
637 | {- | ||
638 | listenForXmppClients :: | ||
639 | (HList l, HHead l (XMPPClass session), HExtend e1 l2 l1, | ||
640 | HExtend e l1 (HCONS PortNumber l), JabberClientSession session) => | ||
641 | Family -> e1 -> e -> l2 -> IO ServerHandle | ||
642 | -} | ||
643 | listenForXmppClients addr_family session_factory port st = do | ||
644 | #if MIN_VERSION_HList(0,3,0) | ||
645 | doServer (HCons' addr_family $ HCons' port $ HCons' session_factory st) handleClient | ||
646 | #else | ||
647 | doServer (HCons addr_family $ HCons port $ HCons session_factory st) handleClient | ||
648 | #endif | ||
649 | |||
650 | |||
651 | {- | ||
652 | listenForRemotePeers | ||
653 | :: (HList l, HHead l (XMPPPeerClass session), | ||
654 | HExtend e l1 (HCONS PortNumber l), HExtend e1 l2 l1, | ||
655 | JabberPeerSession session) => | ||
656 | Family -> e1 -> e -> l2 -> IO ServerHandle | ||
657 | -} | ||
658 | listenForRemotePeers addrfamily session_factory port st = do | ||
659 | #if MIN_VERSION_HList(0,3,0) | ||
660 | doServer (HCons' addrfamily $ HCons' port $ HCons' session_factory st) handlePeer | ||
661 | #else | ||
662 | doServer (HCons addrfamily $ HCons port $ HCons session_factory st) handlePeer | ||
663 | #endif | ||
664 | |||
665 | {- | ||
666 | handlePeer | ||
667 | :: (HHead l (XMPPPeerClass session), | ||
668 | JabberPeerSession session) => | ||
669 | HCONS RestrictedSocket (HCONS t1 l) -> Source IO ByteString -> t -> IO () | ||
670 | -} | ||
671 | handlePeer st src snk = do | ||
672 | #if MIN_VERSION_HList(0,3,0) | ||
673 | let HCons' sock (HCons' _ st') = st | ||
674 | #else | ||
675 | let HCons sock (HCons _ st') = st | ||
676 | #endif | ||
677 | session_factory = hHead st' | ||
678 | name <- fmap bshow $ getPeerName sock | ||
679 | debugL $ "(P) connected " <++> name | ||
680 | session <- newPeerSession session_factory sock | ||
681 | |||
682 | didClose <- newTVarIO False | ||
683 | finally ( src $= parseBytes def $$ fromPeer sock session didClose ) | ||
684 | $ do | ||
685 | debugL $ "(P) disconnected " <++> name | ||
686 | didc <- readTVarIO didClose | ||
687 | when (not didc) $ closePeerSession session | ||
688 | |||
689 | |||
690 | handlePeerPresence session stanza False = do | ||
691 | -- Offline | ||
692 | liftIO . debugStr $ "PEER-OFFLINE: "++show stanza | ||
693 | withJust (lookupAttrib "from" (tagAttrs stanza)) $ \jid -> do | ||
694 | peer_jid <- liftIO $ parseAddressJID (textToByteString jid) | ||
695 | liftIO . debugStr $ "PEER-OFFLINE-JID: "++show peer_jid | ||
696 | liftIO $ announcePresence session (Presence peer_jid Offline) | ||
697 | handlePeerPresence session stanza True = do | ||
698 | -- online (Available or Away) | ||
699 | let log = liftIO . debugL . ("(P) " <++>) | ||
700 | withJust (lookupAttrib "from" (tagAttrs stanza)) $ \jid -> do | ||
701 | pjid <- liftIO $ parseAddressJID (textToByteString jid) | ||
702 | -- stat <- show element content | ||
703 | let parseChildren stat = do | ||
704 | child <- nextElement | ||
705 | case child of | ||
706 | Just tag | tagName tag=="{jabber:server}show" | ||
707 | -> fmap toStat (lift content) | ||
708 | Just tag | otherwise -> parseChildren stat | ||
709 | Nothing -> return stat | ||
710 | toStat "away" = Away | ||
711 | toStat "xa" = ExtendedAway | ||
712 | toStat "dnd" = DoNotDisturb | ||
713 | toStat "chat" = Chatty | ||
714 | |||
715 | stat' <- parseChildren Available | ||
716 | liftIO . debugStr $ "announcing peer online: "++show (pjid,stat') | ||
717 | liftIO $ announcePresence session (Presence pjid stat') | ||
718 | log $ bshow (Presence pjid stat') | ||
719 | |||
720 | handlePeerMessage session stanza = do | ||
721 | withJust (lookupAttrib "from" (tagAttrs stanza)) $ \fromstr-> do | ||
722 | withJust (lookupAttrib "to" (tagAttrs stanza)) $ \tostr -> do | ||
723 | fromjid <- liftIO $ parseAddressJID (textToByteString fromstr) | ||
724 | tojid <- liftIO $ parseAddressJID (textToByteString tostr) | ||
725 | let log = liftIO . debugL . ("(P) " <++>) | ||
726 | log $ "handlePeerMessage "<++>bshow stanza | ||
727 | msg <- parseMessage ("{jabber:server}body" | ||
728 | ,"{jabber:server}subject" | ||
729 | ,"{jabber:server}thread" | ||
730 | ) | ||
731 | log | ||
732 | fromjid | ||
733 | tojid | ||
734 | stanza | ||
735 | liftIO $ sendChatToClient session msg | ||
736 | |||
737 | matchAttribMaybe name (Just value) attrs = | ||
738 | case find ( (==name) . fst) attrs of | ||
739 | Just (_,[ContentText x]) | x==value -> True | ||
740 | Just (_,[ContentEntity x]) | x==value -> True | ||
741 | _ -> False | ||
742 | matchAttribMaybe name Nothing attrs | ||
743 | | find ( (==name) . fst) attrs==Nothing | ||
744 | = True | ||
745 | matchAttribMaybe name Nothing attrs | ||
746 | | otherwise | ||
747 | = False | ||
748 | |||
749 | presenceTypeOffline = Just "unavailable" | ||
750 | presenceTypeOnline = Nothing | ||
751 | presenceTypeProbe = Just "probe" | ||
752 | presenceTypeSubscribe = Just "subscribe" | ||
753 | presenceTypeSubscribed = Just "subscribed" | ||
754 | presenceTypeUnsubscribed = Just "unsubscribed" | ||
755 | |||
756 | isPresenceOf (EventBeginElement name attrs) testType | ||
757 | | name=="{jabber:server}presence" | ||
758 | && matchAttribMaybe "type" testType attrs | ||
759 | = True | ||
760 | isPresenceOf _ _ = False | ||
761 | |||
762 | isMessageStanza (EventBeginElement name attrs) | ||
763 | | name=="{jabber:client}message" | ||
764 | = True | ||
765 | isMessageStanza (EventBeginElement name attrs) | ||
766 | | name=="{jabber:server}message" | ||
767 | = True | ||
768 | isMessageStanza _ = False | ||
769 | |||
770 | isClientPresenceOf (EventBeginElement name attrs) testType | ||
771 | | name=="{jabber:client}presence" | ||
772 | && matchAttribMaybe "type" testType attrs | ||
773 | = True | ||
774 | isClientPresenceOf _ _ = False | ||
775 | |||
776 | |||
777 | handlePresenceProbe session stanza = do | ||
778 | withJust (lookupAttrib "to" (tagAttrs stanza)) $ \to -> do | ||
779 | -- withJust (lookupAttrib "from" (tagAttrs stanza)) $ \from -> do | ||
780 | jid <- liftIO $ parseAddressJID $ textToByteString to | ||
781 | withJust (name jid) $ \user -> do | ||
782 | liftIO $ debugL $ "RECEIVED PROBE "<++>bshow (peerAddress session,to) | ||
783 | liftIO $ do | ||
784 | subs <- getSubscribers (peerSessionFactory session) user | ||
785 | liftIO $ debugL $ "subscribers for "<++>bshow user<++>": " <++>bshow subs | ||
786 | forM_ subs $ \jidstr -> do | ||
787 | handleIO_ (return ()) $ do | ||
788 | debugL $ "parsing " <++>jidstr | ||
789 | sub <- parseHostNameJID jidstr | ||
790 | debugStr $ "comparing " ++show (peer sub , peerAddress session) | ||
791 | when (peer sub == discardPort (peerAddress session)) $ do | ||
792 | ps <- userStatus session user | ||
793 | -- todo: Consider making this a directed presence | ||
794 | forM_ ps $ \p -> do | ||
795 | debugStr ("PROBE-REPLY: "++show p) | ||
796 | mapM_ (sendPeerMessage session . OutBoundPresence) ps | ||
797 | return () | ||
798 | |||
799 | subscribeToPresence subscribers peer_jid user = do | ||
800 | pjid <- parseAddressJID peer_jid | ||
801 | if Set.member pjid subscribers | ||
802 | then return () | ||
803 | else return () | ||
804 | |||
805 | bare (JID n host _) = JID n host Nothing | ||
806 | |||
807 | presenceErrorRemoteNotFound iqid from to = return | ||
808 | [ EventBeginElement "{stream:client}presence" | ||
809 | ( case iqid of { Nothing -> id; Just iqid -> ( attr "id" iqid :) } | ||
810 | $ [ attr "from" to | ||
811 | , attr "type" "error" | ||
812 | ] ) | ||
813 | , EventBeginElement "{stream:client}error" | ||
814 | [ attr "type" "modify"] | ||
815 | , EventBeginElement "{urn:ietf:params:xml:ns:xmpp-stanzas}remote-server-not-found" | ||
816 | [] | ||
817 | , EventEndElement "{urn:ietf:params:xml:ns:xmpp-stanzas}remote-server-not-found" | ||
818 | , EventEndElement "{stream:client}error" | ||
819 | , EventEndElement "{stream:client}presence" | ||
820 | ] | ||
821 | |||
822 | presenceSubscribed from = return | ||
823 | [ EventBeginElement "{stream:client}presence" | ||
824 | [ attr "from" from | ||
825 | , attr "type" "subscribed" | ||
826 | ] | ||
827 | , EventEndElement "{stream:client}presence" | ||
828 | ] | ||
829 | |||
830 | clientRequestsSubscription session cmdChan stanza = do | ||
831 | liftIO $ do | ||
832 | debugStr $ "CLIENT PRESENCE SUBSCRIBE " ++ show stanza | ||
833 | withJust (lookupAttrib "to" (tagAttrs stanza)) $ \to_str0 -> do | ||
834 | let to_str = S.takeWhile (/='/') to_str0 | ||
835 | from = lookupAttrib "from" (tagAttrs stanza) | ||
836 | iqid = lookupAttrib "id" (tagAttrs stanza) | ||
837 | let handleError e | isDoesNotExistError e = do | ||
838 | debugStr $ "remote-server-not-found" | ||
839 | r <- presenceErrorRemoteNotFound iqid from to_str | ||
840 | atomically $ writeTChan cmdChan (Send r) | ||
841 | handleError e = do | ||
842 | debugStr $ "ERROR: "++ show e | ||
843 | handleIO handleError $ do | ||
844 | let to_str' = textToByteString to_str | ||
845 | to_jid <- fmap bare $ parseHostNameJID to_str' | ||
846 | if (is_remote . peer) to_jid | ||
847 | then do | ||
848 | addSolicited session to_str' to_jid | ||
849 | debugStr $ "added to solicited: " ++ show to_jid | ||
850 | else do | ||
851 | -- addLocalSubscriber session to_str | ||
852 | -- self <- getJID session | ||
853 | r <- presenceSubscribed to_str -- self | ||
854 | atomically $ writeTChan cmdChan (Send r) | ||
855 | return () | ||
856 | |||
857 | |||
858 | stanzaFromTo :: | ||
859 | JabberPeerSession session => | ||
860 | session -> Event -> IO (Maybe (JID, JID)) | ||
861 | stanzaFromTo session stanza = | ||
862 | let lookup key = fmap textToByteString (lookupAttrib key (tagAttrs stanza)) | ||
863 | parse jidstr = handleIO_ (return Nothing) (fmap Just $ parseAddressJID jidstr) | ||
864 | in case liftM2 (,) (lookup "from") (lookup "to") of | ||
865 | Nothing -> return Nothing | ||
866 | Just (from,to) -> do | ||
867 | mfrom <- parse from | ||
868 | mto <- parse to | ||
869 | case liftM2 (,) mfrom mto of | ||
870 | Nothing -> return Nothing | ||
871 | Just (from,to) -> do | ||
872 | let fromjid = JID (name from) (peerAddress session) Nothing | ||
873 | return $ Just (fromjid,to) | ||
874 | |||
875 | peerRequestsSubsription session stanza = do | ||
876 | liftIO $ debugStr $ "PEER PRESENCE SUBSCRIBE " ++ show stanza | ||
877 | |||
878 | whenJust (liftIO . handleIO (\e -> debugStr ("peerRequestsSubsription: "++show e) >> return Nothing) | ||
879 | $ stanzaFromTo session stanza) $ \(fromjid,tojid) -> do | ||
880 | withJust (name tojid) $ \user -> do | ||
881 | |||
882 | subs <- liftIO $ do | ||
883 | subs <- getSubscribers (peerSessionFactory session) user | ||
884 | msubs <- flip mapM subs $ \str -> do | ||
885 | handleIO_ (return Nothing) | ||
886 | (fmap Just $ parseHostNameJID str) | ||
887 | return (catMaybes msubs) | ||
888 | if elem fromjid subs | ||
889 | then do | ||
890 | liftIO . debugL $ bshow fromjid <++> " already subscribed to " <++> user | ||
891 | -- if already subscribed, reply | ||
892 | liftIO $ do | ||
893 | sendPeerMessage session (Approval tojid fromjid) | ||
894 | ps <- userStatus session user | ||
895 | -- todo: consider making this a directed presence | ||
896 | mapM_ (sendPeerMessage session . OutBoundPresence) ps | ||
897 | else | ||
898 | liftIO $ processRequest session user fromjid | ||
899 | |||
900 | clientApprovesSubscription session stanza = do | ||
901 | liftIO $ debugStr $ "CLIENT APPROVES SUBSCRIPTION" | ||
902 | withJust (lookupAttrib "to" (tagAttrs stanza)) $ \to_str -> do | ||
903 | liftIO $ approveSubscriber session (textToByteString to_str) | ||
904 | |||
905 | clientRejectsSubscription session stanza = do | ||
906 | liftIO $ debugStr $ "CLIENT REJECTS SUBSCRIPTION" | ||
907 | withJust (lookupAttrib "to" (tagAttrs stanza)) $ \to_str -> do | ||
908 | liftIO $ rejectSubscriber session (textToByteString to_str) | ||
909 | |||
910 | peerApprovesSubscription session stanza = do | ||
911 | liftIO $ debugStr $ "PEER APPROVES SUBSCRIPTION" | ||
912 | whenJust (liftIO . handleIO (\e -> debugStr ("peerApprovesSubscription: "++show e) >> return Nothing) | ||
913 | $ stanzaFromTo session stanza) $ \(fromjid,tojid) -> do | ||
914 | withJust (name tojid) $ \user -> do | ||
915 | liftIO $ processApproval session user fromjid | ||
916 | |||
917 | peerRejectsSubscription session stanza = do | ||
918 | liftIO $ debugStr $ "PEER REJECTS SUBSCRIPTION" | ||
919 | whenJust (liftIO . handleIO (\e -> debugStr ("peerRejectsSubscription: "++show e) >> return Nothing) | ||
920 | $ stanzaFromTo session stanza) $ \(fromjid,tojid) -> do | ||
921 | withJust (name tojid) $ \user -> do | ||
922 | liftIO $ processRejection session user fromjid | ||
923 | |||
924 | handlePeerIQGet :: (JabberPeerSession session, MonadIO m) => | ||
925 | session -> XML.Event -> NestingXML o m () | ||
926 | handlePeerIQGet session tag = do | ||
927 | -- TODO: Pings should not require an id field. | ||
928 | withJust (lookupAttrib "id" (tagAttrs tag)) $ \stanza_id -> do | ||
929 | whenJust nextElement $ \child -> do | ||
930 | let unhandledGet req = do | ||
931 | liftIO $ debugStr ("iq-peer-get: "++show (stanza_id,child)) | ||
932 | liftIO $ | ||
933 | sendPeerMessage session (Unsupported (JID Nothing LocalHost Nothing) | ||
934 | (JID Nothing (peerAddress session) Nothing) | ||
935 | (Just (ContentText stanza_id)) | ||
936 | req) | ||
937 | -- Client equiv: liftIO . atomically . writeTChan cmdChan . Send $ iq_service_unavailable host stanza_id Nothing req | ||
938 | case tagName child of | ||
939 | -- "{http://jabber.org/protocol/disco#items}query" -> liftIO $ debugStr "iq-get-query-items" | ||
940 | "{urn:xmpp:ping}ping" -> liftIO $ do | ||
941 | sendPeerMessage session (Pong (JID Nothing LocalHost Nothing) | ||
942 | (JID Nothing (peerAddress session) Nothing) | ||
943 | (Just (ContentText stanza_id))) | ||
944 | -- Client equiv: atomically . writeTChan cmdChan . Send $ pong | ||
945 | return () | ||
946 | |||
947 | req -> unhandledGet req | ||
948 | |||
949 | fromPeer :: (MonadThrow m,MonadIO m, JabberPeerSession session) => | ||
950 | RestrictedSocket -> session -> TVar Bool -> Sink XML.Event m () | ||
951 | fromPeer sock session didClose = doNestingXML $ do | ||
952 | let log = liftIO . debugL . ("(P) " <++>) | ||
953 | withXML $ \begindoc -> do | ||
954 | when (begindoc==EventBeginDocument) $ do | ||
955 | log "begin-doc" | ||
956 | withXML $ \xml -> do | ||
957 | withJust (elementAttrs "stream" xml) $ \stream_attrs -> do | ||
958 | log $ "stream atributes: " <++> bshow stream_attrs | ||
959 | |||
960 | let doTimeout = Thunk $ do | ||
961 | atomically $ writeTVar didClose True | ||
962 | closePeerSession session | ||
963 | |||
964 | fix $ \loop -> do | ||
965 | log "waiting for stanza." | ||
966 | whenJust nextElement $ \stanza -> do | ||
967 | stanza_lvl <- nesting | ||
968 | |||
969 | liftIO $ sendPeerMessage session (ActivityBump doTimeout) -- reset ping timer | ||
970 | |||
971 | let unhandledStanza = do | ||
972 | xs <- gatherElement stanza Seq.empty | ||
973 | prettyPrint "P: " (toList xs) | ||
974 | case () of | ||
975 | _ | stanza `isServerIQOf` iqTypeGet -> handlePeerIQGet session stanza | ||
976 | _ | stanza `isPresenceOf` presenceTypeOnline | ||
977 | -> handlePeerPresence session stanza True | ||
978 | _ | stanza `isPresenceOf` presenceTypeOffline | ||
979 | -> handlePeerPresence session stanza False | ||
980 | _ | stanza `isPresenceOf` presenceTypeProbe | ||
981 | -> handlePresenceProbe session stanza | ||
982 | _ | stanza `isPresenceOf` presenceTypeSubscribe | ||
983 | -> peerRequestsSubsription session stanza | ||
984 | _ | stanza `isPresenceOf` presenceTypeSubscribed | ||
985 | -> peerApprovesSubscription session stanza | ||
986 | _ | stanza `isPresenceOf` presenceTypeUnsubscribed | ||
987 | -> peerRejectsSubscription session stanza | ||
988 | _ | isMessageStanza stanza | ||
989 | -> handlePeerMessage session stanza | ||
990 | _ -> unhandledStanza | ||
991 | |||
992 | awaitCloser stanza_lvl | ||
993 | loop | ||
994 | |||
995 | log $ "end of stream" | ||
996 | withXML $ \xml -> do | ||
997 | log $ "end-of-document: " <++> bshow xml | ||
998 | |||
999 | |||
1000 | |||
1001 | |||
1002 | newServerConnections = newTVar Map.empty | ||
1003 | |||
1004 | data CachedMessages = CachedMessages | ||
1005 | { presences :: Map JID JabberShow | ||
1006 | , probes :: Map JID (Set (Bool,JID)) -- False means solicitation rather than probe | ||
1007 | , approvals :: Map JID (Set (Bool,JID) ) -- False means rejection rather than approval | ||
1008 | } | ||
1009 | |||
1010 | instance CommandCache CachedMessages where | ||
1011 | type CacheableCommand CachedMessages = OutBoundMessage | ||
1012 | emptyCache = CachedMessages Map.empty Map.empty Map.empty | ||
1013 | |||
1014 | updateCache (OutBoundPresence (Presence jid Offline)) cache = | ||
1015 | cache { presences=Map.delete jid . presences $ cache } | ||
1016 | updateCache (OutBoundPresence p@(Presence jid st)) cache = | ||
1017 | cache { presences=Map.insert jid st . presences $ cache } | ||
1018 | updateCache (PresenceProbe from to) cache = | ||
1019 | cache { probes = mmInsert (True,from) to $ probes cache } | ||
1020 | updateCache (Solicitation from to) cache = | ||
1021 | cache { probes= mmInsert (False,from) to $ probes cache } | ||
1022 | updateCache (Approval from to) cache = | ||
1023 | cache { approvals= mmInsert (True,from) to $ approvals cache } | ||
1024 | updateCache (Rejection from to) cache = | ||
1025 | cache { approvals= mmInsert (False,from) to $ approvals cache } | ||
1026 | updateCache (OutBoundMessage msg) cache = cache -- TODO: cache chat? | ||
1027 | updateCache (Pong _ _ _) cache = trace "(DISCARDING Pong)" cache -- pings are not cached | ||
1028 | updateCache (Unsupported _ _ _ _) cache = cache -- error messages are not cached | ||
1029 | updateCache (ActivityBump sock) cache = cache | ||
1030 | |||
1031 | instance ThreadChannelCommand OutBoundMessage where | ||
1032 | isQuitCommand Disconnect = True | ||
1033 | isQuitCommand _ = False | ||
1034 | |||
1035 | mmInsert val key mm = Map.alter f key mm | ||
1036 | where | ||
1037 | f Nothing = Just $ Set.singleton val | ||
1038 | f (Just set) = Just $ Set.insert val set | ||
1039 | |||
1040 | |||
1041 | greetPeer = | ||
1042 | [ EventBeginDocument | ||
1043 | , EventBeginElement (streamP "stream") | ||
1044 | [ attr "xmlns" "jabber:server" | ||
1045 | , attr "version" "1.0" | ||
1046 | ] | ||
1047 | ] | ||
1048 | |||
1049 | goodbyePeer = | ||
1050 | [ EventEndElement (streamP "stream") | ||
1051 | , EventEndDocument | ||
1052 | ] | ||
1053 | |||
1054 | peerJidTextLocal sock jid = do | ||
1055 | addr <- getSocketName sock | ||
1056 | return . toStrict . L.decodeUtf8 | ||
1057 | $ name jid <$++> "@" | ||
1058 | <?++> showPeer (RemotePeer addr) | ||
1059 | <++?> "/" <++$> resource jid | ||
1060 | |||
1061 | peerJidTextRemote sock jid = do | ||
1062 | addr <- getPeerName sock | ||
1063 | return . toStrict . L.decodeUtf8 | ||
1064 | $ name jid <$++> "@" | ||
1065 | <?++> showPeer (RemotePeer addr) | ||
1066 | <++?> "/" <++$> resource jid | ||
1067 | |||
1068 | presenceStanza sock fromjid tojid typ = do | ||
1069 | from <- peerJidTextLocal sock fromjid | ||
1070 | let to = toStrict . L.decodeUtf8 | ||
1071 | $ name tojid <$++> "@" | ||
1072 | <?++> showPeer (peer tojid) | ||
1073 | return | ||
1074 | [ EventBeginElement "{jabber:server}presence" | ||
1075 | [ attr "from" from | ||
1076 | , attr "to" to | ||
1077 | , attr "type" typ | ||
1078 | ] | ||
1079 | , EventEndElement "{jabber:server}presence" | ||
1080 | ] | ||
1081 | |||
1082 | |||
1083 | toPeer | ||
1084 | :: SocketLike sock => | ||
1085 | sock | ||
1086 | -> CachedMessages | ||
1087 | -> TChan OutBoundMessage | ||
1088 | -> (Maybe OutBoundMessage -> IO ()) | ||
1089 | -> ConduitM i [Event] IO () | ||
1090 | toPeer sock cache chan fail = do | ||
1091 | let -- log = liftIO . debugL . ("(>P) " <++>) | ||
1092 | send xs = yield xs >> prettyPrint ">P: " xs -- >> return (3::Int) | ||
1093 | checkConnection cmd = do | ||
1094 | liftIO $ catchIO (getPeerName sock >> return ()) | ||
1095 | (\_ -> fail . Just $ cmd) | ||
1096 | sendOrFail getXML cmd = do | ||
1097 | checkConnection cmd | ||
1098 | r <- liftIO $ getXML | ||
1099 | -- handleIO (\e -> debugStr ("ERROR: "++show e) >> return []) getXML | ||
1100 | yieldOr r (fail . Just $ cmd) | ||
1101 | prettyPrint ">P: " r | ||
1102 | sendPresence presence = | ||
1103 | sendOrFail (xmlifyPresenceForPeer sock presence) | ||
1104 | (OutBoundPresence presence) | ||
1105 | sendProbe from to = | ||
1106 | sendOrFail (presenceStanza sock from to "probe") | ||
1107 | (PresenceProbe from to) | ||
1108 | sendSolicitation from to = | ||
1109 | sendOrFail (presenceStanza sock from to "subscribe") | ||
1110 | (Solicitation from to) | ||
1111 | sendApproval approve from to = | ||
1112 | sendOrFail (presenceStanza sock from to | ||
1113 | (if approve then "subscribed" else "unsubscribed")) | ||
1114 | (if approve then Approval from to | ||
1115 | else Rejection from to) | ||
1116 | sendMessage msg = | ||
1117 | sendOrFail (xmlifyMessageForPeer sock msg) | ||
1118 | (OutBoundMessage msg) | ||
1119 | |||
1120 | sendPong from to mid = do | ||
1121 | liftIO . debugL $ "SEND PONG" | ||
1122 | sendOrFail (xmlifyPong sock from to mid) | ||
1123 | (Pong from to mid) | ||
1124 | where | ||
1125 | xmlifyPong sock from to mid = do | ||
1126 | fromjid <- peerJidTextLocal sock to | ||
1127 | tojid <- peerJidTextRemote sock to | ||
1128 | return $ [ EventBeginElement "{jabber:server}iq" | ||
1129 | $ (case mid of | ||
1130 | Just c -> (("id",[c]):) | ||
1131 | _ -> id ) | ||
1132 | [ attr "type" "result" | ||
1133 | , attr "to" tojid | ||
1134 | , attr "from" fromjid | ||
1135 | ] | ||
1136 | , EventEndElement "{jabber:server}iq" | ||
1137 | ] | ||
1138 | sendUnsupported from to mid tag = | ||
1139 | sendOrFail (xmlifyUnsupported sock from to mid tag) | ||
1140 | (Unsupported from to mid tag) | ||
1141 | where | ||
1142 | xmlifyUnsupported sock from to mid req = do | ||
1143 | fromjid <- peerJidTextLocal sock to | ||
1144 | tojid <- peerJidTextRemote sock to | ||
1145 | return $ | ||
1146 | [ EventBeginElement "{jabber:server}iq" | ||
1147 | $ (case mid of | ||
1148 | Just c -> (("id",[c]):) | ||
1149 | _ -> id ) | ||
1150 | [("type",[ContentText "error"]) | ||
1151 | , attr "to" tojid | ||
1152 | , attr "from" fromjid | ||
1153 | ] | ||
1154 | , EventBeginElement req [] | ||
1155 | , EventEndElement req | ||
1156 | , EventBeginElement "{jabber:server}error" [("type",[ContentText "cancel"])] | ||
1157 | , EventBeginElement "{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable" [] | ||
1158 | , EventEndElement "{urn:ietf:params:xml:ns:xmpp-stanzas}service-unavailable" | ||
1159 | , EventEndElement "{jabber:server}error" | ||
1160 | , EventEndElement "{jabber:server}iq" | ||
1161 | ] | ||
1162 | |||
1163 | |||
1164 | send greetPeer | ||
1165 | forM_ (Map.assocs . approvals $ cache) $ \(to,froms) -> do | ||
1166 | forM_ (Set.toList froms) $ \(approve,from) -> do | ||
1167 | liftIO $ debugL "sending cached approval/rejection..." | ||
1168 | sendApproval approve from to | ||
1169 | forM_ (Map.assocs . presences $ cache) $ \(jid,st) -> do | ||
1170 | sendPresence (Presence jid st) | ||
1171 | forM_ (Map.assocs . probes $ cache) $ \(to,froms) -> do | ||
1172 | forM_ (Set.toList froms) $ \(got,from) -> do | ||
1173 | if got | ||
1174 | then do | ||
1175 | liftIO $ debugL "sending cached probe..." | ||
1176 | sendProbe from to | ||
1177 | else do | ||
1178 | liftIO $ debugL "sending cached solicitation..." | ||
1179 | sendSolicitation from to | ||
1180 | |||
1181 | |||
1182 | let five_sec = 5 * 1000000 :: Int | ||
1183 | pingref <- liftIO $ do | ||
1184 | ping_timer <- liftIO $ newDelay five_sec | ||
1185 | newTVarIO (ping_timer,0::Int) | ||
1186 | |||
1187 | sockref <- liftIO $ atomically newEmptyTMVar | ||
1188 | let bump fromsock = do | ||
1189 | remote <- liftIO $ catchIO (fmap Just $ getPeerName sock) | ||
1190 | (\_ -> return Nothing) | ||
1191 | debugL $ "PING BUMP" <++?> fmap (showPeer . RemotePeer) remote | ||
1192 | timer <- atomically $ do | ||
1193 | tryTakeTMVar sockref | ||
1194 | putTMVar sockref fromsock | ||
1195 | (timer,v) <- readTVar pingref | ||
1196 | writeTVar pingref (timer,0) | ||
1197 | return timer | ||
1198 | updateDelay timer five_sec | ||
1199 | waitPing = do | ||
1200 | (timer,v) <- readTVar pingref | ||
1201 | waitDelay timer | ||
1202 | return v | ||
1203 | |||
1204 | fix $ \loop -> do | ||
1205 | liftIO . debugStr $ "LOOP waiting..." | ||
1206 | event <- lift . atomically $ orElse (Left `fmap` readTChan chan) | ||
1207 | (Right `fmap` waitPing) | ||
1208 | liftIO . debugStr $ "LOOP event = " ++ show event | ||
1209 | let sendPing n = do | ||
1210 | case n of | ||
1211 | 0 -> do | ||
1212 | ping <- liftIO makePing | ||
1213 | yield ping | ||
1214 | liftIO . debugL $ "SEND PING" | ||
1215 | prettyPrint ">P: " ping | ||
1216 | ping_timer <- liftIO $ newDelay five_sec | ||
1217 | liftIO . atomically $ writeTVar pingref (ping_timer,1) | ||
1218 | loop | ||
1219 | 1 -> do | ||
1220 | remote <- liftIO $ getPeerName sock | ||
1221 | liftIO . debugL $ "PING TIMEOUT: " <++> showPeer (RemotePeer remote) | ||
1222 | fromsock <- liftIO $ atomically $ readTMVar sockref | ||
1223 | -- liftIO $ sClose fromsock | ||
1224 | liftIO $ runThunk fromsock | ||
1225 | |||
1226 | return () -- PING TIMEOUT (loop quits) | ||
1227 | x -> error ("What? "++show x) | ||
1228 | where makePing = do | ||
1229 | addr <- getSocketName sock | ||
1230 | remote <- getPeerName sock | ||
1231 | let from = toStrict . L.decodeUtf8 . showPeer $ RemotePeer addr | ||
1232 | to = toStrict . L.decodeUtf8 . showPeer $ RemotePeer remote | ||
1233 | mid = Just (ContentText "iduno") | ||
1234 | return $ | ||
1235 | [ EventBeginElement "{jabber:server}iq" | ||
1236 | $ (case mid of | ||
1237 | Just c -> (("id",[c]):) | ||
1238 | _ -> id ) | ||
1239 | [ ("type",[ContentText "get"]) | ||
1240 | , attr "to" to | ||
1241 | , attr "from" from | ||
1242 | ] | ||
1243 | , EventBeginElement "{urn:xmpp:ping}ping" [] | ||
1244 | , EventEndElement "{urn:xmpp:ping}ping" | ||
1245 | , EventEndElement "{jabber:server}iq" ] | ||
1246 | chanEvent event = do | ||
1247 | case event of | ||
1248 | OutBoundPresence p -> sendPresence p | ||
1249 | PresenceProbe from to -> do | ||
1250 | liftIO $ debugL "sending live probe..." | ||
1251 | sendProbe from to | ||
1252 | Solicitation from to -> do | ||
1253 | liftIO $ debugL "sending live solicitation..." | ||
1254 | sendSolicitation from to | ||
1255 | Approval from to -> do | ||
1256 | liftIO . debugL $ "sending approval "<++>bshow (from,to) | ||
1257 | sendApproval True from to | ||
1258 | Rejection from to -> do | ||
1259 | liftIO . debugL $ "sending rejection "<++>bshow (from,to) | ||
1260 | sendApproval False from to | ||
1261 | OutBoundMessage msg -> sendMessage msg | ||
1262 | Pong from to mid -> do | ||
1263 | liftIO . debugL $ "sending pong "<++>bshow (from,to) | ||
1264 | sendPong from to mid | ||
1265 | Unsupported from to mid tag -> sendUnsupported from to mid tag | ||
1266 | Disconnect -> return () | ||
1267 | ActivityBump fromsock -> liftIO (bump fromsock) | ||
1268 | when (not . isQuitCommand $ event) loop | ||
1269 | either chanEvent sendPing event | ||
1270 | return () | ||
1271 | -- send goodbyePeer -- TODO: why does this cause an exception? | ||
1272 | -- Text/XML/Stream/Render.hs:169:5-15: | ||
1273 | -- Irrefutable pattern failed for pattern (sl : s') | ||
1274 | |||
1275 | |||
1276 | |||
1277 | |||
1278 | seekRemotePeers :: JabberPeerSession config => | ||
1279 | XMPPPeerClass config -> TChan Presence -> OutgoingConnections CachedMessages -> IO b0 | ||
1280 | seekRemotePeers config chan server_connections = do | ||
1281 | fix $ \loop -> do | ||
1282 | event <- atomically $ readTChan chan | ||
1283 | case event of | ||
1284 | p@(Presence jid stat) | not (is_remote (peer jid)) -> do | ||
1285 | -- debugL $ "seekRemotePeers: " <++> L.show jid <++> " " <++> bshow stat | ||
1286 | runMaybeT $ do | ||
1287 | u <- MaybeT . return $ name jid | ||
1288 | subscribers <- liftIO $ do | ||
1289 | subs <- getSubscribers config u | ||
1290 | mapM parseHostNameJID subs | ||
1291 | -- liftIO . debugL $ "subscribers: " <++> bshow subscribers | ||
1292 | let peers = Set.map peer (Set.fromList subscribers) | ||
1293 | forM_ (Set.toList peers) $ \peer -> do | ||
1294 | when (is_remote peer) $ | ||
1295 | liftIO $ sendMessage server_connections (OutBoundPresence p) peer | ||
1296 | _ -> return (Just ()) | ||
1297 | loop | ||
1298 | |||
1299 | xmlifyPresenceForPeer sock (Presence jid stat) = do | ||
1300 | addr <- getSocketName sock | ||
1301 | let n = name jid | ||
1302 | rsc = resource jid | ||
1303 | jidstr = toStrict . L.decodeUtf8 | ||
1304 | $ n <$++> "@" <?++> showPeer (RemotePeer addr) <++?> "/" <++$> rsc | ||
1305 | return $ | ||
1306 | [ EventBeginElement "{jabber:server}presence" | ||
1307 | (attr "from" jidstr:typ stat) ] | ||
1308 | ++ ( shw stat >>= jabberShow ) ++ | ||
1309 | [ EventEndElement "{jabber:server}presence" ] | ||
1310 | where | ||
1311 | typ Offline = [attr "type" "unavailable"] | ||
1312 | typ _ = [] | ||
1313 | |||
1314 | shw ExtendedAway = ["xa"] | ||
1315 | shw Chatty = ["chat"] | ||
1316 | shw Away = ["away"] | ||
1317 | shw DoNotDisturb = ["dnd"] | ||
1318 | shw _ = [] | ||
1319 | jabberShow stat = | ||
1320 | [ EventBeginElement "{jabber:server}show" [] | ||
1321 | , EventContent (ContentText stat) | ||
1322 | , EventEndElement "{jabber:server}show" ] | ||
1323 | |||
1324 | xmlifyMessageForClient msg = do | ||
1325 | let tojid = msgTo msg | ||
1326 | fromjid = msgFrom msg | ||
1327 | tonames <- getNamesForPeer (peer tojid) | ||
1328 | fromnames <- getNamesForPeer (peer fromjid) | ||
1329 | let mk_str ns jid = toStrict . L.decodeUtf8 $ name jid <$++> "@" <?++> L.fromChunks [head ns] <++?> "/" <++$> resource jid | ||
1330 | to_str = mk_str tonames tojid | ||
1331 | from_str = mk_str fromnames fromjid | ||
1332 | tags = ( "{jabber:client}subject" | ||
1333 | , "{jabber:client}body" | ||
1334 | ) | ||
1335 | return $ | ||
1336 | [ EventBeginElement "{jabber:client}message" | ||
1337 | [ attr "from" from_str | ||
1338 | , attr "to" to_str | ||
1339 | ] | ||
1340 | ] | ||
1341 | ++ xmlifyMsgElements tags (msgLangMap msg) ++ | ||
1342 | [ EventEndElement "{jabber:client}message" ] | ||
1343 | |||
1344 | |||
1345 | xmlifyMessageForPeer sock msg = do | ||
1346 | addr <- getSocketName sock | ||
1347 | remote <- getPeerName sock | ||
1348 | let n = name (msgFrom msg) | ||
1349 | rsc = resource (msgFrom msg) | ||
1350 | jidstr = toStrict . L.decodeUtf8 | ||
1351 | $ n <$++> "@" <?++> showPeer (RemotePeer addr) <++?> "/" <++$> rsc | ||
1352 | tostr = toStrict . L.decodeUtf8 | ||
1353 | $ name (msgTo msg) <$++> "@" | ||
1354 | <?++> showPeer (RemotePeer remote) <++?> "/" | ||
1355 | <++$> resource (msgTo msg) | ||
1356 | tags = ( "{jabber:server}subject" | ||
1357 | , "{jabber:server}body" | ||
1358 | ) | ||
1359 | return $ | ||
1360 | [ EventBeginElement "{jabber:server}message" | ||
1361 | [ attr "from" jidstr | ||
1362 | , attr "to" tostr | ||
1363 | ] | ||
1364 | ] | ||
1365 | ++ xmlifyMsgElements tags (msgLangMap msg) ++ | ||
1366 | [ EventEndElement "{jabber:server}message" ] | ||
1367 | |||
1368 | xmlifyMsgElements tags langmap = concatMap (uncurry (langElements tags)) . Map.toList $ langmap | ||
1369 | |||
1370 | langElements (subjecttag,bodytag) lang msg = | ||
1371 | ( maybeToList (msgSubject msg) | ||
1372 | >>= wrap subjecttag ) | ||
1373 | ++ ( maybeToList (msgBody msg) | ||
1374 | >>= wrap bodytag ) | ||
1375 | ++ ( Set.toList (msgElements msg) | ||
1376 | >>= wrapTriple ) | ||
1377 | where | ||
1378 | wrap name content = | ||
1379 | [ EventBeginElement name | ||
1380 | ( if lang/="" then [attr "xml:lang" lang] | ||
1381 | else [] ) | ||
1382 | , EventContent (ContentText content) | ||
1383 | , EventEndElement name | ||
1384 | ] | ||
1385 | wrapTriple (name,attrs,content) = | ||
1386 | [ EventBeginElement name attrs -- Note: we assume lang specified in attrs | ||
1387 | , EventContent (ContentText content) | ||
1388 | , EventEndElement name | ||
1389 | ] | ||
1390 | |||
1391 | |||
1392 | handleClientMessage session stanza = do | ||
1393 | let log = liftIO . debugL . ("(C) " <++>) | ||
1394 | log $ "handleClientMessage "<++>bshow stanza | ||
1395 | from <- liftIO $ getJID session | ||
1396 | withJust (lookupAttrib "to" (tagAttrs stanza)) $ \to_str -> do | ||
1397 | log $ " to = "<++>bshow to_str | ||
1398 | tojid <- liftIO $ parseHostNameJID (textToByteString to_str) | ||
1399 | msg <- parseMessage ("{jabber:client}body" | ||
1400 | ,"{jabber:client}subject" | ||
1401 | ,"{jabber:client}thread" | ||
1402 | ) | ||
1403 | log | ||
1404 | from | ||
1405 | tojid | ||
1406 | stanza | ||
1407 | liftIO $ sendChat session msg | ||
1408 | |||
1409 | {- | ||
1410 | unhandled-C: <message | ||
1411 | unhandled-C: type="chat" | ||
1412 | unhandled-C: id="purplea0a7fd24" | ||
1413 | unhandled-C: to="user@vm2" | ||
1414 | unhandled-C: xmlns="jabber:client"> | ||
1415 | unhandled-C: <active xmlns="http://jabber.org/protocol/chatstates"/> | ||
1416 | unhandled-C: <body> | ||
1417 | unhandled-C: hello dude | ||
1418 | unhandled-C: </body> | ||
1419 | unhandled-C: </message> | ||
1420 | -} | ||
1421 | parseMessage (bodytag,subjecttag,threadtag) log from tojid stanza = do | ||
1422 | let emptyMsg = LangSpecificMessage { msgBody=Nothing, msgSubject=Nothing, msgElements=Set.empty } | ||
1423 | parseChildren (th,cmap) = do | ||
1424 | child <- nextElement | ||
1425 | lvl <- nesting | ||
1426 | xmllang <- xmlLang | ||
1427 | let lang = maybe "" id xmllang | ||
1428 | let c = maybe emptyMsg id (Map.lookup lang cmap) | ||
1429 | log $ " child: "<++> bshow child | ||
1430 | case child of | ||
1431 | Just tag | tagName tag==bodytag | ||
1432 | -> do | ||
1433 | txt <- lift content | ||
1434 | awaitCloser lvl | ||
1435 | parseChildren (th,Map.insert lang (c { msgBody=Just txt }) cmap) | ||
1436 | Just tag | tagName tag==subjecttag | ||
1437 | -> do | ||
1438 | txt <- lift content | ||
1439 | awaitCloser lvl | ||
1440 | parseChildren (th,Map.insert lang (c { msgSubject=Just txt }) cmap) | ||
1441 | Just tag | tagName tag==threadtag | ||
1442 | -> do | ||
1443 | txt <- lift content | ||
1444 | awaitCloser lvl | ||
1445 | parseChildren (th {msgThreadContent=txt},cmap) | ||
1446 | Just tag -> do | ||
1447 | let nm = tagName tag | ||
1448 | attrs = tagAttrs tag | ||
1449 | elems = msgElements c | ||
1450 | txt <- lift content | ||
1451 | awaitCloser lvl | ||
1452 | parseChildren (th,Map.insert lang (c {msgElements=Set.insert (nm,attrs,txt) elems}) cmap) | ||
1453 | Nothing -> return (th,cmap) | ||
1454 | (th,langmap) <- parseChildren ( MessageThread {msgThreadParent=Nothing, msgThreadContent=""} | ||
1455 | , Map.empty ) | ||
1456 | return Message { | ||
1457 | msgTo = tojid, | ||
1458 | msgFrom = from, | ||
1459 | msgLangMap = langmap, | ||
1460 | msgThread = if msgThreadContent th/="" then Just th else Nothing | ||
1461 | } | ||
diff --git a/simplechat.hs b/simplechat.hs deleted file mode 100644 index 84b33e13..00000000 --- a/simplechat.hs +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | {-# LANGUAGE ScopedTypeVariables #-} | ||
3 | |||
4 | import Data.HList.TypeEqGeneric1() | ||
5 | import Data.HList.TypeCastGeneric1() | ||
6 | import ByteStringOperators | ||
7 | |||
8 | import Data.ByteString.Lazy.Char8 as L | ||
9 | ( ByteString | ||
10 | , hPutStrLn | ||
11 | , init ) | ||
12 | import System.IO | ||
13 | ( Handle | ||
14 | ) | ||
15 | import Control.Concurrent (forkIO) | ||
16 | import Control.Concurrent.Chan | ||
17 | import Data.HList | ||
18 | |||
19 | import Connection.Tcp | ||
20 | |||
21 | |||
22 | startCon socket st = do | ||
23 | let chan = hOccursFst st | ||
24 | nr = hOccursFst st :: ConnId | ||
25 | hdl = hOccursFst st :: Handle | ||
26 | quit = writeChan chan (nr,Nothing) | ||
27 | broadcast msg = writeChan chan (nr,Just msg) | ||
28 | chan' <- dupChan chan | ||
29 | reader <- forkIO $ fix $ \loop -> do | ||
30 | (nr', line) <- readChan chan' | ||
31 | case ( line, nr==nr') of | ||
32 | ( Nothing , True ) -> Prelude.putStrLn "quit-client." | ||
33 | ( Just msg , False ) -> hPutStrLn hdl msg >> loop | ||
34 | _ -> loop | ||
35 | |||
36 | hPutStrLn hdl "Hi, what's your name?" | ||
37 | line <- getPacket hdl | ||
38 | let name = L.init line | ||
39 | Prelude.putStrLn $ "readFst: " ++ show line | ||
40 | hPutStrLn hdl ("Welcome, " <++> name <++> "!") | ||
41 | broadcast ("--> " <++> name <++> " entered.") | ||
42 | |||
43 | return (name .*. ConnectionFinalizer quit .*. st) | ||
44 | |||
45 | doCon st bs cont = do | ||
46 | let hdl = hOccursFst st :: Handle | ||
47 | nr = hOccursFst st :: ConnId | ||
48 | chan = hOccursFst st | ||
49 | broadcast msg = writeChan chan (nr,Just msg) | ||
50 | name = hHead st | ||
51 | Prelude.putStrLn $ "read: " ++ show bs | ||
52 | case bs of | ||
53 | "quit\n" -> hPutStrLn hdl "Bye!" | ||
54 | _ -> do | ||
55 | broadcast (name <++> ": " <++> L.init bs) | ||
56 | cont () | ||
57 | |||
58 | |||
59 | main = do | ||
60 | (chan :: Chan (ConnId, Maybe ByteString)) <- newChan | ||
61 | doServer (5222 .*. chan .*. HNil) | ||
62 | doCon | ||
63 | startCon | ||
64 | getLine | ||
65 | |||
66 | |||
diff --git a/test-server.hs b/test-server.hs deleted file mode 100644 index b47a4bcb..00000000 --- a/test-server.hs +++ /dev/null | |||
@@ -1,541 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | module Main where | ||
3 | |||
4 | import Debug.Trace | ||
5 | import Control.Exception (evaluate) -- ,handle,SomeException(..),bracketOnError) | ||
6 | import Control.Exception ({-evaluate,-}handle,SomeException(..),bracketOnError,ErrorCall(..)) | ||
7 | import System.IO.Error (ioeGetErrorType) | ||
8 | import Connection.Tcp | ||
9 | import Control.Monad | ||
10 | import Control.Monad.Trans.Resource | ||
11 | import Control.Monad.IO.Class | ||
12 | import Control.Monad.STM | ||
13 | import Control.Monad.Fix | ||
14 | import Control.Concurrent (threadDelay,forkOS,forkIO) | ||
15 | import Control.Concurrent.STM.TMVar | ||
16 | import Control.Concurrent.STM.TChan | ||
17 | import Network.Socket | ||
18 | import System.Process.Internals | ||
19 | import System.Process | ||
20 | import Data.Char | ||
21 | import System.IO hiding (isEOF) | ||
22 | import System.Posix.Signals hiding (Ignore) | ||
23 | |||
24 | |||
25 | makeGots sv = do | ||
26 | let chan = serverEvent sv | ||
27 | gotchan <- atomically $ dupTChan chan | ||
28 | forkIO $ do | ||
29 | fix $ \loop -> do | ||
30 | (k,e) <- atomically $ readTChan chan | ||
31 | case e of | ||
32 | Connection pingflag read write -> todo | ||
33 | EOF -> todo | ||
34 | loop | ||
35 | return gotchan | ||
36 | |||
37 | isConnection (_,Connection {}) = True | ||
38 | isConnection _ = False | ||
39 | isPending want (_,HalfConnection got) | got/=want = True | ||
40 | isPending _ _ = False | ||
41 | isRead str (_,Got r) | r==str = True | ||
42 | isRead _ _ = False | ||
43 | isEOF (_,EOF) = True | ||
44 | isEOF _ = False | ||
45 | |||
46 | -- localhost port = SockAddrInet port 127 | ||
47 | localhost port = SockAddrInet6 port 0 (0,0,0,1) 0 | ||
48 | |||
49 | withServer f = runResourceT $ do | ||
50 | sv <- server | ||
51 | result <- liftIO $ f sv | ||
52 | release (serverReleaseKey sv) | ||
53 | return result | ||
54 | |||
55 | writeToPort port str = do | ||
56 | s <- readProcess "/usr/bin/socat" ["-","TCP6:localhost:"++show port] str | ||
57 | reverse s `seq` return () | ||
58 | |||
59 | connectToPort port = do | ||
60 | (inp,outp,err,p) <- | ||
61 | runInteractiveProcess "/usr/bin/socat" | ||
62 | ["-","TCP6:localhost:"++show port] | ||
63 | Nothing | ||
64 | Nothing | ||
65 | hSetBuffering inp NoBuffering | ||
66 | hSetBuffering outp NoBuffering | ||
67 | hSetBuffering err NoBuffering | ||
68 | forkOS $ do putStrLn $ "SOCAT-ConnectToPort:"++show (port,(inp,outp,err)) | ||
69 | hGetContents err >>= putStrLn | ||
70 | return (inp,outp,err,p) | ||
71 | |||
72 | listenOnPort port = do | ||
73 | putStrLn $ "socat - TCP6-LISTEN:"++show port++",fork" | ||
74 | (inp,outp,err,p) <- | ||
75 | runInteractiveProcess "/usr/bin/socat" | ||
76 | ["-","TCP6-LISTEN:"++show port++",fork"] | ||
77 | Nothing | ||
78 | Nothing | ||
79 | hSetBuffering inp NoBuffering | ||
80 | hSetBuffering outp NoBuffering | ||
81 | hSetBuffering err NoBuffering | ||
82 | forkOS $ do { putStrLn "SOCAT-listenOnPort:"; hGetContents err >>= putStrLn } | ||
83 | -- forkOS $ do { putStrLn "SOCAT-listenOnPort:"; hGetContents outp >>= putStrLn } | ||
84 | return (inp,outp,err,p) | ||
85 | |||
86 | stopListening (inp,outp,err,p) = do | ||
87 | {- | ||
88 | forkOS $ do | ||
89 | o <- hGetContents outp | ||
90 | void $ evaluate (length o) | ||
91 | forkOS $ do | ||
92 | e <- hGetContents err | ||
93 | void $ evaluate (length e) | ||
94 | threadDelay 1000000 | ||
95 | hClose inp | ||
96 | threadDelay 1000000 | ||
97 | forkOS $ do | ||
98 | threadDelay 100000 | ||
99 | mapM_ hClose [outp,err] | ||
100 | -} | ||
101 | withProcessHandle p $ \p -> do | ||
102 | case p of | ||
103 | OpenHandle pid -> trace ("interrupting: "++show pid) | ||
104 | $ signalProcess sigINT pid | ||
105 | _ -> return () | ||
106 | return (p,()) | ||
107 | {- | ||
108 | threadDelay 500000 | ||
109 | terminateProcess p | ||
110 | -} | ||
111 | e <- waitForProcess p | ||
112 | putStrLn $ "SOCAT-LISTEN exit: "++show e | ||
113 | |||
114 | |||
115 | |||
116 | chanContents ch = do | ||
117 | x <- atomically $ do | ||
118 | bempty <- isEmptyTChan ch | ||
119 | if bempty | ||
120 | then return Nothing | ||
121 | else fmap Just $ readTChan ch | ||
122 | maybe (return []) | ||
123 | (\x -> do | ||
124 | xs <- chanContents ch | ||
125 | return (x:xs)) | ||
126 | x | ||
127 | |||
128 | control sv = atomically . putTMVar (serverCommand sv) | ||
129 | |||
130 | testListen = do | ||
131 | let send con str = do hPutStr con str | ||
132 | hFlush con | ||
133 | threadDelay 100000 | ||
134 | events <- withServer $ \sv -> do | ||
135 | let _ = sv :: Server SockAddr | ||
136 | events = serverEvent sv | ||
137 | params :: ConnectionParameters SockAddr | ||
138 | params = connectionDefaults (return . snd) | ||
139 | control sv (Listen 39242 params) | ||
140 | h1@(con1,con2,_,_)<- connectToPort 39242 | ||
141 | send con1 "Joe was here" | ||
142 | control sv (Ignore 39242) | ||
143 | threadDelay 500000 | ||
144 | stopListening h1 | ||
145 | threadDelay 500000 | ||
146 | return events | ||
147 | e <- chanContents events | ||
148 | putStrLn $ "testListen: "++ show e | ||
149 | return $ | ||
150 | and [ length e == 3 | ||
151 | , isConnection $ e !! 0 | ||
152 | , isRead "Joe was here" $ e !! 1 | ||
153 | , isEOF $ e !! 2 | ||
154 | ] | ||
155 | |||
156 | |||
157 | testReplace = do | ||
158 | let send con str = do hPutStrLn con str | ||
159 | hFlush con | ||
160 | threadDelay 100000 | ||
161 | events <- withServer $ \sv -> do | ||
162 | let _ = sv :: Server () | ||
163 | events = serverEvent sv | ||
164 | params = connectionDefaults (const $ return ()) | ||
165 | control sv (Listen 39242 params) | ||
166 | h1@(con1,_,_,_)<- connectToPort 39242 | ||
167 | send con1 "Joe was here" | ||
168 | h2@(con2,_,_,_) <- connectToPort 39242 | ||
169 | send con2 "Jim also" | ||
170 | send con1 "What?" | ||
171 | threadDelay 500000 | ||
172 | stopListening h1 -- misnomer | ||
173 | stopListening h2 -- misnomer | ||
174 | threadDelay 500000 | ||
175 | return events | ||
176 | e <- chanContents events | ||
177 | -- [([::ffff:127.0.0.1]:43722,Connection) | ||
178 | -- ,([::ffff:127.0.0.1]:43722,Got "Joe was here\n") | ||
179 | -- ,([::ffff:127.0.0.1]:43723,Connection) | ||
180 | -- ,([::ffff:127.0.0.1]:43723,Got "Jim also\n") | ||
181 | -- ,([::ffff:127.0.0.1]:43722,Got "What?\n") | ||
182 | -- ,([::ffff:127.0.0.1,EOF)]:43722 | ||
183 | -- ,([::ffff:127.0.0.1,EOF)]:43723] | ||
184 | -- putStrLn $ show e | ||
185 | putStrLn $ "testReplace: "++ show e | ||
186 | return $ case e of | ||
187 | [(_,Connection {}) | ||
188 | ,(_,Got "Joe was here\n") | ||
189 | ,(_,EOF) | ||
190 | ,(_,Connection {}) | ||
191 | ,(_,Got "Jim also\n") | ||
192 | ,(_,EOF)] | ||
193 | -> True | ||
194 | _ -> False | ||
195 | |||
196 | testPendingOut = do | ||
197 | let send con str = do hPutStr con str | ||
198 | hFlush con | ||
199 | threadDelay 100000 | ||
200 | events <- withServer $ \sv -> do | ||
201 | let _ = sv :: Server SockAddr | ||
202 | events = serverEvent sv | ||
203 | params :: ConnectionParameters SockAddr | ||
204 | params = (connectionDefaults (return . snd)) | ||
205 | { duplex = False } | ||
206 | control sv (Listen 39249 params) | ||
207 | h1@(con1,con2,_,_)<- connectToPort 39249 | ||
208 | send con1 "Joe was here" | ||
209 | stopListening h1 | ||
210 | threadDelay 500000 | ||
211 | return events | ||
212 | |||
213 | e <- chanContents events | ||
214 | putStrLn $ "testPendingOut: "++ show e | ||
215 | return $ | ||
216 | and [ length e == 3 | ||
217 | , isPending Out $ e !! 0 | ||
218 | , isRead "Joe was here" $ e !! 1 | ||
219 | , isEOF $ e !! 2 | ||
220 | ] | ||
221 | |||
222 | testReplacePendingOut = do | ||
223 | let send con str = do hPutStrLn con str | ||
224 | hFlush con | ||
225 | threadDelay 100000 | ||
226 | events <- withServer $ \sv -> do | ||
227 | let _ = sv :: Server () | ||
228 | events = serverEvent sv | ||
229 | params = (connectionDefaults (const $ return ())) | ||
230 | { duplex = False } | ||
231 | control sv (Ignore 39242) | ||
232 | control sv (Listen 39242 params) | ||
233 | threadDelay 500000 | ||
234 | h1@(con1,_,_,_) <- connectToPort 39242 | ||
235 | send con1 "Joe was here" | ||
236 | h2@(con2,_,_,_) <- connectToPort 39242 | ||
237 | send con2 "Jim also" | ||
238 | send con1 "What?" | ||
239 | threadDelay 500000 | ||
240 | control sv (Ignore 39242) | ||
241 | threadDelay 500000 | ||
242 | stopListening h1 -- misnomer | ||
243 | stopListening h2 -- misnomer | ||
244 | threadDelay 500000 | ||
245 | return events | ||
246 | e <- chanContents events | ||
247 | putStrLn $ "testReplacePendingOut: "++ show e | ||
248 | return $ case e of | ||
249 | [ (_,HalfConnection In) | ||
250 | ,(_,Got "Joe was here\n") | ||
251 | ,(_,EOF) | ||
252 | ,(_,HalfConnection In) | ||
253 | ,(_,Got "Jim also\n") | ||
254 | ,(_,EOF)] | ||
255 | -> True | ||
256 | _ -> False | ||
257 | |||
258 | testReplacePendingIn = do | ||
259 | let send con str = do hPutStrLn con str | ||
260 | hFlush con | ||
261 | threadDelay 100000 | ||
262 | (events,socat) <- withServer $ \sv -> do | ||
263 | let _ = sv :: Server () | ||
264 | events = serverEvent sv | ||
265 | params = (connectionDefaults (const $ return ())) | ||
266 | { duplex = False } | ||
267 | threadDelay 500000 | ||
268 | socat <- listenOnPort 39242 | ||
269 | threadDelay 500000 | ||
270 | control sv (Connect (localhost 39242) params) | ||
271 | control sv (Connect (localhost 39242) params) | ||
272 | return (events,socat) | ||
273 | threadDelay 500000 | ||
274 | stopListening socat | ||
275 | threadDelay 500000 | ||
276 | e <- chanContents events | ||
277 | putStrLn $ "testReplacePendingIn: "++ show e | ||
278 | return $ e==[((),HalfConnection Out) | ||
279 | ,((),EOF) | ||
280 | ,((),HalfConnection Out) | ||
281 | ,((),EOF)] | ||
282 | |||
283 | testPromotePendingOut = do | ||
284 | putStrLn "----------- testPromotePendingOut" | ||
285 | hFlush stdout | ||
286 | let send con str = do hPutStr con str | ||
287 | hFlush con | ||
288 | threadDelay 100000 | ||
289 | (events,s,socat) <- withServer $ \sv -> do | ||
290 | let _ = sv :: Server () | ||
291 | events = serverEvent sv | ||
292 | params = (connectionDefaults (const $ return ())) | ||
293 | { duplex = False } | ||
294 | control sv (Ignore 39244) | ||
295 | threadDelay 500000 | ||
296 | control sv (Listen 39244 params) | ||
297 | socat <- listenOnPort 39243 | ||
298 | h@(con1,con2,_,_) <- connectToPort 39244 | ||
299 | putStrLn $ "connected to 39244, Sending Joe was here" | ||
300 | send con1 "Joe was here" | ||
301 | threadDelay 500000 | ||
302 | putStrLn $ "Connecting to 39243" | ||
303 | control sv (Connect (localhost 39243) params) | ||
304 | threadDelay 500000 | ||
305 | putStrLn $ "probably connected to 39243" | ||
306 | control sv (Send () "and jim") | ||
307 | putStrLn $ "connected to 39244, Sending Joe was here twice" | ||
308 | hFlush stdout | ||
309 | send con1 "Joe was here twice" | ||
310 | threadDelay 500000 | ||
311 | s <- fmap (take 7) $ hGetContents ((\(_,x,_,_)->x) socat) | ||
312 | last s `seq` threadDelay 500000 | ||
313 | stopListening h -- misnomer | ||
314 | threadDelay 50000 | ||
315 | return (events,s,socat) | ||
316 | stopListening socat | ||
317 | threadDelay 500000 | ||
318 | e <- chanContents events | ||
319 | putStrLn $ "testPromotePendingOut: "++ show (s,e) | ||
320 | -- testPromotePendingOut: ("and jim",[HalfConnection () In,((),Got "Joe was here"),((),Read () "Joe was here twice",((),EOF)]),Connection) | ||
321 | -- | ||
322 | return . and $ | ||
323 | [ s== "and jim" | ||
324 | , e== [ ((),HalfConnection In) | ||
325 | , ((),Got "Joe was here") | ||
326 | , ((),Connection {}) | ||
327 | , ((),Got "Joe was here twice") | ||
328 | , ((),EOF) ] | ||
329 | ] | ||
330 | |||
331 | testPromotePendingIn = do | ||
332 | putStrLn "----------- testPromotePendingIn" | ||
333 | hFlush stdout | ||
334 | let send con str = do handle (\e -> putStrLn . show $ ioeGetErrorType e) $ do | ||
335 | hPutStrLn con str | ||
336 | hFlush con | ||
337 | threadDelay 500000 | ||
338 | (events,socat,s) <- withServer $ \sv -> do | ||
339 | let _ = sv :: Server () | ||
340 | events = serverEvent sv | ||
341 | params = (connectionDefaults (const $ return ())) | ||
342 | { duplex = False } | ||
343 | |||
344 | -- Outgoing connection | ||
345 | socat <- listenOnPort 39248 | ||
346 | threadDelay 500000 | ||
347 | putStrLn $ "Connecting to 39248..." | ||
348 | control sv (Connect (localhost 39248) params) | ||
349 | threadDelay 1000000 | ||
350 | putStrLn $ "...probably connected to 39248" | ||
351 | control sv (Send () "and jim") | ||
352 | threadDelay 1000000 | ||
353 | |||
354 | s <- fmap (take 7) $ hGetContents ((\(_,x,_,_)->x) socat) | ||
355 | length s `seq` threadDelay 500000 | ||
356 | |||
357 | control sv (Listen 39247 params) | ||
358 | threadDelay 500000 | ||
359 | h@(con1,con2,_,_) <- connectToPort 39247 | ||
360 | send con1 "howdy!" | ||
361 | h2@(con1',con2',_,_) <- connectToPort 39247 | ||
362 | threadDelay 500000 | ||
363 | send con1' "what?" | ||
364 | threadDelay 1000000 | ||
365 | hClose con1 | ||
366 | hClose con1' | ||
367 | threadDelay 1000000 | ||
368 | stopListening socat | ||
369 | threadDelay 1000000 | ||
370 | |||
371 | return (events,socat,s) | ||
372 | e <- chanContents events | ||
373 | putStrLn $ "testPromotePendingIn: "++ show (s,e) | ||
374 | -- testPromotePendingIn: ("and jim",[HalfConnection () Out,((),((),Got "howdy!\n"),((),EOF (),HalfConnection () In,Read () "what?\n",EOF (),EOF)]),Connection) | ||
375 | -- ("and jim",[HalfConnection () Out,((),((),Got "howdy!\n"),((),HalfConnection () In,Read () "what?\n",EOF (),EOF)]),Connection) | ||
376 | return . and $ | ||
377 | [ s == "and jim" | ||
378 | , e == [ ((),HalfConnection Out) | ||
379 | ,((),Connection {}) | ||
380 | ,((),Got "howdy!\n") | ||
381 | ,((),EOF) | ||
382 | ,((),HalfConnection In) | ||
383 | ,((),Got "what?\n") | ||
384 | ,((),EOF)] | ||
385 | ] | ||
386 | |||
387 | testPing = do | ||
388 | putStrLn "----------- testPing" | ||
389 | let send con str = do handle (\e -> putStrLn . show $ ioeGetErrorType e) $ do | ||
390 | hPutStrLn con str | ||
391 | hFlush con | ||
392 | threadDelay 500000 | ||
393 | events <- withServer $ \sv -> do | ||
394 | let _ = sv :: Server () | ||
395 | events = serverEvent sv | ||
396 | params = (connectionDefaults (const $ return ())) | ||
397 | { pingInterval = 2000 | ||
398 | , timeout = 1000 } | ||
399 | control sv (Listen 32957 params) | ||
400 | threadDelay 500000 | ||
401 | socat@(h,_,_,_) <- connectToPort 32957 | ||
402 | |||
403 | -- putStrLn $ "sending hey you!" | ||
404 | send h "hey you!" | ||
405 | -- putStrLn $ "delay" | ||
406 | threadDelay 3500000 -- ping timeout | ||
407 | -- putStrLn $ "sending what?" | ||
408 | send h "what?" -- lost due to timeout | ||
409 | -- putStrLn $ "delay-2" | ||
410 | threadDelay 500000 | ||
411 | -- putStrLn $ "close h" | ||
412 | hClose h | ||
413 | |||
414 | threadDelay 500000 | ||
415 | socat@(h,_,_,_) <- connectToPort 32957 | ||
416 | send h "try 2: hey you!" | ||
417 | threadDelay 2500000 -- ping warning | ||
418 | send h "try 2: what?" | ||
419 | threadDelay 1000000 -- no warning or timeout | ||
420 | send h "try 2: yes." | ||
421 | stopListening socat | ||
422 | threadDelay 1000000 | ||
423 | return events | ||
424 | |||
425 | e <- chanContents events | ||
426 | putStrLn $ "testPing: "++show e | ||
427 | -- testPing: [((),Connection <STM action> <IO action> <function>) | ||
428 | {- | ||
429 | ,((),Got "hey you!\n") | ||
430 | ,((),RequiresPing) | ||
431 | ,((),RequiresPing) | ||
432 | ,((),Got "what?\n") | ||
433 | ,((),EOF) | ||
434 | ,((),Connection <STM action> <IO action> <function>) | ||
435 | ,((),Got "try 2: hey you!\n") | ||
436 | ,((),RequiresPing) | ||
437 | ,((),Got "try 2: what?\n") | ||
438 | ,((),Got "try 2: yes.\n") | ||
439 | ,((),EOF)] | ||
440 | testPing: [((),Connection <STM action> <IO action> <function>) | ||
441 | ,((),Got "hey you!\n") | ||
442 | ,((),RequiresPing) | ||
443 | ,((),Got "what?\n") | ||
444 | ,((),RequiresPing) | ||
445 | ,((),EOF) | ||
446 | ,((),Connection <STM action> <IO action> <function>) | ||
447 | ,((),Got "try 2: hey you!\n") | ||
448 | ,((),RequiresPing) | ||
449 | ,((),Got "try 2: what?\n") | ||
450 | ,((),RequiresPing)] | ||
451 | testPing: [((),Connection <STM action> <IO action> <function>) | ||
452 | ,((),Got "hey you!\n") | ||
453 | ,((),RequiresPing) | ||
454 | ,((),Got "what?\n") | ||
455 | ,((),RequiresPing) | ||
456 | ,((),EOF) | ||
457 | ,((),Connection <STM action> <IO action> <function>) | ||
458 | ,((),Got "try 2: hey you!\n") | ||
459 | ,((),RequiresPing) | ||
460 | ,((),Got "try 2: what?\n") | ||
461 | ,((),Got "try 2: yes.\n") | ||
462 | ,((),EOF)] | ||
463 | |||
464 | -} | ||
465 | |||
466 | return $ e == [((),Connection {}) | ||
467 | ,((),Got "hey you!\n") | ||
468 | ,((),RequiresPing) | ||
469 | ,((),EOF) | ||
470 | ,((),Connection {}) | ||
471 | ,((),Got "try 2: hey you!\n") | ||
472 | ,((),RequiresPing) | ||
473 | ,((),Got "try 2: what?\n") | ||
474 | ,((),Got "try 2: yes.\n") | ||
475 | ,((),EOF)] | ||
476 | |||
477 | testDisabledPing = do | ||
478 | let send con str = do hPutStrLn con str | ||
479 | hFlush con | ||
480 | threadDelay 500000 | ||
481 | events <- withServer $ \sv -> do | ||
482 | let _ = sv :: Server () | ||
483 | events = serverEvent sv | ||
484 | params = (connectionDefaults (const $ return ())) | ||
485 | { pingInterval = 0 | ||
486 | , timeout = 0 } | ||
487 | control sv (Listen 32958 params) | ||
488 | threadDelay 500000 | ||
489 | socat@(h,_,_,_) <- connectToPort 32958 | ||
490 | |||
491 | send h "hey you!" | ||
492 | threadDelay 3500000 -- ping timeout | ||
493 | send h "what?" -- lost due to timeout | ||
494 | threadDelay 500000 | ||
495 | hClose h | ||
496 | |||
497 | threadDelay 500000 | ||
498 | socat@(h,_,_,_) <- connectToPort 32958 | ||
499 | send h "try 2: hey you!" | ||
500 | threadDelay 2500000 -- ping warning | ||
501 | send h "try 2: what?" | ||
502 | threadDelay 1000000 -- no warning or timeout | ||
503 | send h "try 2: yes." | ||
504 | stopListening socat | ||
505 | threadDelay 5000000 | ||
506 | return events | ||
507 | |||
508 | e <- chanContents events | ||
509 | putStrLn $ "testDisabledPing: "++show e | ||
510 | return $ e == [((),Connection {}) | ||
511 | ,((),Got "hey you!\n") | ||
512 | ,((),Got "what?\n") | ||
513 | ,((),EOF) | ||
514 | ,((),Connection {}) | ||
515 | ,((),Got "try 2: hey you!\n") | ||
516 | ,((),Got "try 2: what?\n") | ||
517 | ,((),Got "try 2: yes.\n") | ||
518 | ,((),EOF)] | ||
519 | main = do | ||
520 | result1 <- testListen | ||
521 | result2 <- testReplace | ||
522 | result3 <- testPendingOut | ||
523 | result4 <- testReplacePendingOut | ||
524 | threadDelay 100000 | ||
525 | result5 <- testReplacePendingIn | ||
526 | result6 <- testPromotePendingOut | ||
527 | result7 <- testPromotePendingIn | ||
528 | result8 <- testPing | ||
529 | result9 <- testDisabledPing | ||
530 | let passOrFail str True = putStrLn $ str ++ ": passed" | ||
531 | passOrFail str False = putStrLn $ str ++ ": failed" | ||
532 | passOrFail "testListen" result1 | ||
533 | passOrFail "testReplace" result2 | ||
534 | passOrFail "testPendingOut" result3 | ||
535 | passOrFail "testReplacePendingOut" result4 | ||
536 | passOrFail "testReplacePendingIn" result5 | ||
537 | passOrFail "testPromotePendingOut" result6 | ||
538 | passOrFail "testPromotePendingIn" result7 | ||
539 | passOrFail "testPing" result8 | ||
540 | passOrFail "testDisabledPing" result9 | ||
541 | return () | ||