diff options
author | joe <joe@jerkface.net> | 2018-06-25 17:56:14 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2018-06-25 18:18:28 -0400 |
commit | 53b72dd253ce01a24430429cef400675401292dc (patch) | |
tree | cda8a4e081f14eb9148a402eb209cbd07b63b5c4 /ToxToXMPP.hs | |
parent | 214fd246d0d2c0240d6adbf43a0a68d772eb71b7 (diff) |
Separated conduit parser and general tox-manager stuff.
Diffstat (limited to 'ToxToXMPP.hs')
-rw-r--r-- | ToxToXMPP.hs | 500 |
1 files changed, 11 insertions, 489 deletions
diff --git a/ToxToXMPP.hs b/ToxToXMPP.hs index 9979526a..1493827a 100644 --- a/ToxToXMPP.hs +++ b/ToxToXMPP.hs | |||
@@ -3,80 +3,22 @@ | |||
3 | {-# LANGUAGE NamedFieldPuns #-} | 3 | {-# LANGUAGE NamedFieldPuns #-} |
4 | {-# LANGUAGE NondecreasingIndentation #-} | 4 | {-# LANGUAGE NondecreasingIndentation #-} |
5 | {-# LANGUAGE ViewPatterns #-} | 5 | {-# LANGUAGE ViewPatterns #-} |
6 | module ToxToXMPP | 6 | module ToxToXMPP where |
7 | ( forkAccountWatcher | ||
8 | , JabberClients | ||
9 | , PerClient | ||
10 | , initPerClient | ||
11 | , toxQSearch | ||
12 | , toxAnnounceInterval | ||
13 | , xmppToTox | ||
14 | , toxToXmpp | ||
15 | , interweave | ||
16 | ) where | ||
17 | 7 | ||
18 | import Control.Applicative | 8 | import Crypto.Tox |
19 | import Data.Conduit as C | 9 | import Data.Conduit as C |
20 | import qualified Data.Conduit.List as CL | 10 | import qualified Data.Conduit.List as CL |
21 | import Data.Monoid | 11 | import Data.Monoid |
22 | import Data.Text.Encoding as T | 12 | import qualified Data.Text as T |
23 | import Data.XML.Types as XML | 13 | ;import Data.Text (Text) |
14 | import Data.Text.Encoding as T | ||
15 | import Data.XML.Types as XML | ||
24 | import EventUtil | 16 | import EventUtil |
25 | import Network.Tox.Crypto.Transport as Tox | ||
26 | import Network.Tox.Handshake (HandshakeParams (..)) | ||
27 | import qualified Text.XML.Stream.Parse as XML | ||
28 | import Util (unsplitJID) | ||
29 | import XMPPServer as XMPP | ||
30 | |||
31 | |||
32 | import Announcer | ||
33 | import Announcer.Tox | ||
34 | import Connection | ||
35 | import Network.QueryResponse | ||
36 | import Network.Tox.DHT.Handlers (nodeSearch, nodesOfInterest) | ||
37 | -- import Control.Concurrent | ||
38 | import Control.Concurrent.STM | ||
39 | import Control.Monad | ||
40 | import Crypto.Tox | ||
41 | import qualified Data.HashMap.Strict as HashMap | ||
42 | import Data.Maybe | ||
43 | import qualified Data.Set as Set | ||
44 | import qualified Data.Text as T | ||
45 | import Data.Time.Clock.POSIX | ||
46 | import Network.Address | 17 | import Network.Address |
47 | import Network.Kademlia.Search | 18 | import Network.Tox.Crypto.Transport as Tox |
48 | import qualified Network.Tox as Tox | ||
49 | import Network.Tox.ContactInfo as Tox | ||
50 | import qualified Network.Tox.Crypto.Handlers as Tox | ||
51 | ;import Network.Tox.Crypto.Handlers (UponCookie (..)) | ||
52 | -- import qualified Network.Tox.DHT.Handlers as Tox | ||
53 | import ClientState | ||
54 | import Data.Bits | ||
55 | import Data.Function | ||
56 | import qualified Data.Map as Map | ||
57 | import Data.Text (Text) | ||
58 | import Data.Word | ||
59 | import qualified Network.Kademlia.Routing as R | ||
60 | import Network.Tox | ||
61 | import Network.Tox.DHT.Handlers | ||
62 | import qualified Network.Tox.DHT.Transport as Tox | ||
63 | ;import Network.Tox.DHT.Transport (FriendRequest (..), dhtpk) | ||
64 | import Network.Tox.NodeId | 19 | import Network.Tox.NodeId |
65 | import qualified Network.Tox.Onion.Handlers as Tox | 20 | import Util (unsplitJID) |
66 | import qualified Network.Tox.Onion.Transport as Tox | 21 | import XMPPServer as XMPP |
67 | ;import Network.Tox.Onion.Transport (OnionData (..)) | ||
68 | import Presence | ||
69 | import Text.Read | ||
70 | import XMPPServer (ClientAddress) | ||
71 | #ifdef THREAD_DEBUG | ||
72 | import Control.Concurrent.Lifted.Instrument | ||
73 | #else | ||
74 | import Control.Concurrent.Lifted | ||
75 | import GHC.Conc (labelThread) | ||
76 | #endif | ||
77 | import DPut | ||
78 | import Nesting | ||
79 | import XMPPToTox | ||
80 | 22 | ||
81 | toxToXmpp :: Monad m => SockAddr -> PublicKey -> Text -> Conduit Tox.CryptoMessage m XML.Event | 23 | toxToXmpp :: Monad m => SockAddr -> PublicKey -> Text -> Conduit Tox.CryptoMessage m XML.Event |
82 | toxToXmpp laddr me theirhost = do | 24 | toxToXmpp laddr me theirhost = do |
@@ -126,423 +68,3 @@ xmppInstantMessage namespace mfrom mto style text = do | |||
126 | C.yield $ EventEndElement "{http://jabber.org/protocol/xhtml-im}html" | 68 | C.yield $ EventEndElement "{http://jabber.org/protocol/xhtml-im}html" |
127 | C.yield $ EventEndElement (ns "message") | 69 | C.yield $ EventEndElement (ns "message") |
128 | 70 | ||
129 | key2jid :: Word32 -> PublicKey -> Text | ||
130 | key2jid nospam key = T.pack $ show $ NoSpamId nsp key | ||
131 | where | ||
132 | nsp = NoSpam nospam (Just sum) | ||
133 | sum = nlo `xor` nhi `xor` xorsum key | ||
134 | nlo = fromIntegral (0x0FFFF .&. nospam) :: Word16 | ||
135 | nhi = fromIntegral (0x0FFFF .&. (nospam `shiftR` 16)) :: Word16 | ||
136 | |||
137 | type JabberClients = Map.Map ClientAddress PerClient | ||
138 | |||
139 | data PerClient = PerClient | ||
140 | { pcDeliveredFRs :: TVar (Set.Set Tox.FriendRequest) | ||
141 | } | ||
142 | |||
143 | initPerClient :: STM PerClient | ||
144 | initPerClient = do | ||
145 | frs <- newTVar Set.empty | ||
146 | return PerClient | ||
147 | { pcDeliveredFRs = frs | ||
148 | } | ||
149 | |||
150 | data ToxToXMPP = ToxToXMPP | ||
151 | { txAnnouncer :: Announcer | ||
152 | , txAccount :: Account JabberClients | ||
153 | , txPresence :: PresenceState | ||
154 | , txTox :: Tox JabberClients | ||
155 | } | ||
156 | |||
157 | default_nospam :: Word32 | ||
158 | default_nospam = 0x6a7a27fc -- big-endian base64: anon/A== | ||
159 | |||
160 | nodeinfoStaleTime :: POSIXTime | ||
161 | nodeinfoStaleTime = 600 | ||
162 | |||
163 | nodeinfoSearchInterval :: POSIXTime | ||
164 | nodeinfoSearchInterval = 15 | ||
165 | |||
166 | data Awaiting v = Since POSIXTime | ||
167 | data Acquired v = At POSIXTime v | ||
168 | data Moot v = Moot | ||
169 | |||
170 | data NNS a b c = NNS { -- NetcryptoNegotiationState | ||
171 | sessionDesired :: Bool, | ||
172 | theirPublicKey :: a Tox.DHTPublicKey, | ||
173 | theirAddress :: b NodeInfo, | ||
174 | theirCookie :: c (Tox.Cookie Encrypted), | ||
175 | sessionIsActive :: Bool | ||
176 | } | ||
177 | |||
178 | data NS | ||
179 | = Stage1 (NNS Moot Moot Moot) | ||
180 | | Stage2 (NNS Awaiting Moot Moot) | ||
181 | | Stage3 (NNS Acquired Awaiting Moot) | ||
182 | | Stage4 (NNS Acquired Acquired Awaiting) | ||
183 | | Stage5 (NNS Acquired Acquired Acquired) | ||
184 | |||
185 | gotDhtPubkey :: Tox.DHTPublicKey -> ToxToXMPP -> PublicKey -> IO () | ||
186 | gotDhtPubkey theirDhtKey tx theirKey = do | ||
187 | contact <- atomically $ getContact theirKey (txAccount tx) >>= mapM (readTVar . contactLastSeenAddr) | ||
188 | forM_ contact $ \lastSeen -> do | ||
189 | case lastSeen of | ||
190 | Nothing -> doSearch | ||
191 | Just (tm, _) -> do | ||
192 | now <- getPOSIXTime | ||
193 | when (now - tm > nodeinfoStaleTime) doSearch | ||
194 | where | ||
195 | tox :: Tox JabberClients | ||
196 | tox = txTox tx | ||
197 | |||
198 | myPublicKey = toPublic $ userSecret (txAccount tx) | ||
199 | me = key2id myPublicKey | ||
200 | |||
201 | doSearch = do | ||
202 | let akey = akeyConnect (txAnnouncer tx) me theirKey | ||
203 | atomically $ registerNodeCallback (toxRouting tox) (nic akey) | ||
204 | scheduleSearch (txAnnouncer tx) akey meth theirDhtKey | ||
205 | |||
206 | target :: NodeId | ||
207 | target = key2id $ dhtpk theirDhtKey | ||
208 | |||
209 | meth :: SearchMethod Tox.DHTPublicKey | ||
210 | meth = | ||
211 | SearchMethod | ||
212 | { sSearch = nodeSearch (toxDHT tox) (nodesOfInterest $ toxRouting tox) | ||
213 | , sNearestNodes = nearNodes tox | ||
214 | , sTarget = target | ||
215 | , sInterval = nodeinfoSearchInterval | ||
216 | , sWithResult = \r sr -> return () | ||
217 | } | ||
218 | nic akey = | ||
219 | NodeInfoCallback | ||
220 | { interestingNodeId = target | ||
221 | , listenerId = 2 | ||
222 | , observedAddress = observe akey | ||
223 | , rumoredAddress = assume akey | ||
224 | } | ||
225 | |||
226 | assume :: AnnounceKey -> POSIXTime -> SockAddr -> NodeInfo -> STM () | ||
227 | assume akey time addr ni = | ||
228 | tput XNodeinfoSearch $ show ("rumor", akey, time, addr, ni) | ||
229 | |||
230 | observe :: AnnounceKey -> POSIXTime -> NodeInfo -> STM () | ||
231 | observe akey time ni@(nodeAddr -> addr) = do | ||
232 | tput XNodeinfoSearch $ show ("observation", akey, time, addr) | ||
233 | setContactAddr time theirKey ni (txAccount tx) | ||
234 | |||
235 | gotAddr :: NodeInfo -> ToxToXMPP -> PublicKey -> IO () | ||
236 | gotAddr ni@(nodeAddr -> addr) tx theirKey = do | ||
237 | dhtkey <- (fmap.fmap) snd $ | ||
238 | fmap join $ | ||
239 | atomically $ | ||
240 | traverse readTVar =<< fmap contactKeyPacket <$> getContact theirKey (txAccount tx) | ||
241 | forM_ dhtkey $ gotAddr' ni tx theirKey | ||
242 | |||
243 | gotAddr' :: NodeInfo -> ToxToXMPP -> PublicKey -> Tox.DHTPublicKey -> IO () | ||
244 | gotAddr' ni@(nodeAddr -> addr) tx theirKey theirDhtKey = atomically blee | ||
245 | |||
246 | where | ||
247 | myPublicKey = toPublic $ userSecret (txAccount tx) | ||
248 | me = key2id myPublicKey | ||
249 | akey = akeyConnect (txAnnouncer tx) me theirKey | ||
250 | |||
251 | blee = do | ||
252 | scheduleImmediately (txAnnouncer tx) akey $ | ||
253 | ScheduledItem $ getCookie ni (activeSesh addr) (getContact theirKey (txAccount tx)) | ||
254 | |||
255 | tox :: Tox JabberClients | ||
256 | tox = txTox tx | ||
257 | |||
258 | byAddr :: TVar (Map.Map SockAddr Tox.NetCryptoSession) | ||
259 | byAddr = Tox.netCryptoSessions (toxCryptoSessions tox) | ||
260 | |||
261 | crypto = Tox.transportCrypto $ toxCryptoSessions tox | ||
262 | |||
263 | readNcVar :: (Tox.NetCryptoSession -> TVar b) -> SockAddr -> STM (Maybe b) | ||
264 | readNcVar v addr = traverse readTVar =<< fmap v . Map.lookup addr <$> readTVar byAddr | ||
265 | |||
266 | chillSesh :: SockAddr -> STM (Maybe (Status Tox.ToxProgress)) | ||
267 | chillSesh = readNcVar Tox.ncState | ||
268 | |||
269 | activeSesh :: SockAddr -> STM Bool | ||
270 | activeSesh a = chillSesh a >>= return . \case | ||
271 | Just Established -> True | ||
272 | _ -> False | ||
273 | |||
274 | readCookie :: SockAddr -> STM (Maybe (UponCookie (Tox.Cookie Encrypted))) | ||
275 | readCookie = readNcVar Tox.ncCookie | ||
276 | readCookie' :: SockAddr -> STM (Maybe (Tox.Cookie Encrypted)) | ||
277 | readCookie' = fmap join . (fmap.fmap) Tox.toMaybe . readCookie | ||
278 | |||
279 | client :: Network.Tox.DHT.Handlers.Client | ||
280 | client = toxDHT tox | ||
281 | |||
282 | getCookie | ||
283 | :: NodeInfo | ||
284 | -> STM Bool | ||
285 | -> STM (Maybe Contact) | ||
286 | -> Announcer | ||
287 | -> AnnounceKey | ||
288 | -> POSIXTime | ||
289 | -> STM (IO ()) | ||
290 | getCookie ni isActive getC ann akey now = getCookieAgain | ||
291 | where | ||
292 | getCookieAgain = do | ||
293 | tput XNodeinfoSearch $ show ("getCookieAgain", akey) | ||
294 | mbContact <- getC | ||
295 | case mbContact of | ||
296 | Nothing -> return $ return () | ||
297 | Just contact -> do | ||
298 | active <- isActive | ||
299 | return $ when (not active) getCookieIO | ||
300 | |||
301 | callRealShakeHands = realShakeHands (userSecret (txAccount tx)) theirKey (dhtpk theirDhtKey) (toxCryptoSessions tox) (nodeAddr ni) | ||
302 | |||
303 | reschedule n f = scheduleRel ann akey f n | ||
304 | reschedule' n f = reschedule n (ScheduledItem $ \_ _ now -> f now) | ||
305 | |||
306 | cookieMaxAge = 60 * 5 | ||
307 | |||
308 | getCookieIO :: IO () | ||
309 | getCookieIO = do | ||
310 | dput XUnused "getCookieIO - entered" | ||
311 | cookieRequest crypto client myPublicKey ni >>= \case | ||
312 | Nothing -> atomically $ reschedule' 5 (const getCookieAgain) | ||
313 | Just cookie -> do | ||
314 | void $ callRealShakeHands cookie | ||
315 | cookieCreationStamp <- getPOSIXTime | ||
316 | let shaker :: POSIXTime -> STM (IO ()) | ||
317 | shaker now = do | ||
318 | active <- isActive | ||
319 | if (active) | ||
320 | then return $ return () | ||
321 | else if (now > cookieCreationStamp + cookieMaxAge) | ||
322 | then return $ | ||
323 | dput XUnused "getCookieIO/shaker - cookie expired" >> | ||
324 | getCookieIO | ||
325 | else do | ||
326 | reschedule' 5 shaker | ||
327 | return . void $ callRealShakeHands cookie | ||
328 | atomically $ reschedule' 5 shaker | ||
329 | |||
330 | realShakeHands :: SecretKey -> PublicKey -> PublicKey -> Tox.NetCryptoSessions -> SockAddr -> Tox.Cookie Encrypted -> IO Bool | ||
331 | realShakeHands myseckey theirpubkey theirDhtKey allsessions saddr cookie = do | ||
332 | dput XUnused "realShakeHands" | ||
333 | let hp = | ||
334 | HParam | ||
335 | { hpOtherCookie = cookie | ||
336 | , hpMySecretKey = myseckey | ||
337 | , hpCookieRemotePubkey = theirpubkey | ||
338 | , hpCookieRemoteDhtkey = theirDhtKey | ||
339 | , hpTheirBaseNonce = Nothing | ||
340 | , hpTheirSessionKeyPublic = Nothing | ||
341 | } | ||
342 | newsession <- generateSecretKey | ||
343 | timestamp <- getPOSIXTime | ||
344 | (myhandshake, ioAction) <- | ||
345 | atomically $ | ||
346 | Tox.freshCryptoSession allsessions saddr newsession timestamp hp | ||
347 | ioAction | ||
348 | -- send handshake | ||
349 | isJust <$> forM myhandshake (Tox.sendHandshake allsessions saddr) | ||
350 | |||
351 | dispatch :: ToxToXMPP -> ContactEvent -> IO () | ||
352 | dispatch tx (SessionEstablished theirKey) = stopConnecting tx theirKey "established" | ||
353 | dispatch tx (SessionTerminated theirKey) = startConnecting tx theirKey "terminated" | ||
354 | dispatch tx (AddrChange theirKey saddr) = gotAddr saddr tx theirKey | ||
355 | dispatch tx (PolicyChange theirkey TryingToConnect ) = startConnecting tx theirkey "policy" | ||
356 | dispatch tx (PolicyChange theirkey policy ) = stopConnecting tx theirkey "policy" | ||
357 | dispatch tx (OnionRouted theirKey (OnionDHTPublicKey pkey)) = gotDhtPubkey pkey tx theirKey | ||
358 | dispatch tx (OnionRouted theirkey (OnionFriendRequest fr) ) = do | ||
359 | let ToxToXMPP { txAnnouncer = acr | ||
360 | , txAccount = acnt | ||
361 | , txPresence = st } = tx | ||
362 | k2c <- atomically $ do | ||
363 | refs <- readTVar (accountExtra acnt) | ||
364 | k2c <- Map.intersectionWith (,) refs <$> readTVar (ckeyToChan st) | ||
365 | clients <- readTVar (clients st) | ||
366 | return $ Map.intersectionWith (,) k2c clients | ||
367 | -- TODO: Below we're using a hard coded default as their jabber user id. | ||
368 | -- This isn't the right thing, but we don't know their user-id. Perhaps | ||
369 | -- there should be a way to parse it out of the friend request text. Maybe | ||
370 | -- after a zero-termination, or as visible text (nospam:...). | ||
371 | let theirjid = key2jid default_nospam theirkey | ||
372 | forM_ k2c $ \((PerClient{pcDeliveredFRs},conn),client) -> do | ||
373 | alreadyDelivered <- atomically $ do | ||
374 | frs <- readTVar pcDeliveredFRs | ||
375 | writeTVar pcDeliveredFRs $ Set.insert fr frs | ||
376 | return $ Set.member fr frs | ||
377 | when (not alreadyDelivered) $ do | ||
378 | self <- localJID (clientUser client) (clientProfile client) (clientResource client) | ||
379 | ask <- presenceSolicitation theirjid self | ||
380 | -- TODO Send friend-request text as an instant message or at least | ||
381 | -- embed it in the stanza as a <status> element. | ||
382 | sendModifiedStanzaToClient ask (connChan conn) | ||
383 | |||
384 | interweave :: [a] -> [a] -> [a] | ||
385 | interweave [] ys = ys | ||
386 | interweave (x:xs) ys = x : interweave ys xs | ||
387 | |||
388 | akeyDHTKeyShare :: Announcer -> NodeId -> PublicKey -> AnnounceKey | ||
389 | akeyDHTKeyShare announcer me them = | ||
390 | packAnnounceKey announcer $ "dhtkey(" ++ (take 8 $ show me) ++ ")" ++ show (key2id them) | ||
391 | |||
392 | akeyConnect :: Announcer -> NodeId -> PublicKey -> AnnounceKey | ||
393 | akeyConnect announcer me them = | ||
394 | packAnnounceKey announcer $ "connect(" ++ (take 8 $ show me) ++ ")" ++ show (key2id them) | ||
395 | |||
396 | |||
397 | -- | Returns a list of nospam values to use for friend requests to send to a | ||
398 | -- remote peer. This list is non-empty only when it is desirable to send | ||
399 | -- friend requests. | ||
400 | checkSoliciting :: PresenceState -> PublicKey -> PublicKey -> Contact -> IO [NoSpam] | ||
401 | checkSoliciting presence me them contact = do | ||
402 | let theirhost = T.pack $ show (key2id them) ++ ".tox" | ||
403 | myhost = T.pack $ show (key2id me) ++ ".tox" | ||
404 | xs <- getBuddiesAndSolicited presence myhost $ \h -> do | ||
405 | return $ T.toLower h == T.toLower theirhost | ||
406 | return $ do | ||
407 | (is_buddy,their_u,my_uid,xmpp_client_profile) <- xs | ||
408 | guard $ xmpp_client_profile == myhost | ||
409 | NoSpamId nospam _ <- case fmap T.unpack $ their_u of | ||
410 | Just ('$':_) -> maybeToList $ readMaybe $ T.unpack $ unsplitJID (their_u,theirhost,Nothing) | ||
411 | Just ('0':'x':_) -> maybeToList $ readMaybe $ T.unpack $ unsplitJID (their_u,theirhost,Nothing) | ||
412 | _ -> maybeToList $ readMaybe $ T.unpack $ key2jid default_nospam them | ||
413 | return nospam | ||
414 | |||
415 | nearNodes :: Tox extra -> NodeId -> STM [NodeInfo] | ||
416 | nearNodes tox nid = do | ||
417 | bkts4 <- readTVar $ routing4 $ toxRouting tox | ||
418 | bkts6 <- readTVar $ routing6 $ toxRouting tox | ||
419 | let nss = | ||
420 | map | ||
421 | (R.kclosest (searchSpace (toxQSearch tox)) searchK nid) | ||
422 | [bkts4, bkts6] | ||
423 | return $ foldr interweave [] nss | ||
424 | |||
425 | startConnecting0 :: ToxToXMPP -> PublicKey -> Contact -> String -> IO () | ||
426 | startConnecting0 tx them contact reason = do | ||
427 | dput XMan $ "START CONNECTING " ++ show (key2id them) ++ "("++reason++")" | ||
428 | -- TODO When a connection is already established, this function should | ||
429 | -- be a no-op. This occurs when an XMPP client disconnects and | ||
430 | -- reconnects while a session is established. | ||
431 | let ToxToXMPP { txTox = tox | ||
432 | , txAnnouncer = announcer | ||
433 | , txAccount = acnt } = tx | ||
434 | let nearNodes nid = do | ||
435 | bkts4 <- readTVar $ routing4 $ toxRouting tox | ||
436 | bkts6 <- readTVar $ routing6 $ toxRouting tox | ||
437 | let nss = map (R.kclosest (searchSpace (toxQSearch tox)) searchK nid) | ||
438 | [bkts4,bkts6] | ||
439 | return $ foldr interweave [] nss | ||
440 | wanted <- atomically $ (==Just TryingToConnect) <$> readTVar (contactPolicy contact) | ||
441 | let mypub = toPublic $ userSecret acnt | ||
442 | me = key2id mypub | ||
443 | soliciting <- checkSoliciting (txPresence tx) mypub them contact | ||
444 | when wanted $ do | ||
445 | akey <- return $ akeyDHTKeyShare announcer me them | ||
446 | -- We send this packet every 30 seconds if there is more | ||
447 | -- than one peer (in the 8) that says they our friend is | ||
448 | -- announced on them. This packet can also be sent through | ||
449 | -- the DHT module as a DHT request packet (see DHT) if we | ||
450 | -- know the DHT public key of the friend and are looking | ||
451 | -- for them in the DHT but have not connected to them yet. | ||
452 | -- 30 second is a reasonable timeout to not flood the | ||
453 | -- network with too many packets while making sure the | ||
454 | -- other will eventually receive the packet. Since packets | ||
455 | -- are sent through every peer that knows the friend, | ||
456 | -- resending it right away without waiting has a high | ||
457 | -- likelihood of failure as the chances of packet loss | ||
458 | -- happening to all (up to to 8) packets sent is low. | ||
459 | -- | ||
460 | let meth = SearchMethod (toxQSearch tox) onResult nearNodes (key2id them) 30 | ||
461 | where | ||
462 | onResult theirkey rendezvous = do | ||
463 | dkey <- Tox.getContactInfo tox | ||
464 | let tr = Tox.toxToRoute tox | ||
465 | route = Tox.AnnouncedRendezvous theirkey rendezvous | ||
466 | dput XMan $ unwords [ take 8 (show $ key2id mypub) ++ ":" | ||
467 | , "Sending my DHT-key" | ||
468 | , show (key2id $ Tox.dhtpk dkey) | ||
469 | , "to" | ||
470 | , show (key2id theirkey) | ||
471 | , "via" | ||
472 | , show (Tox.rendezvousNode rendezvous) | ||
473 | ] | ||
474 | sendMessage tr route (mypub,Tox.OnionDHTPublicKey dkey) | ||
475 | forM_ soliciting $ \cksum@(NoSpam nospam _)-> do | ||
476 | dput XMan $ unwords [ take 8 (show $ key2id mypub) ++ ":" | ||
477 | , "Sending friend-request" | ||
478 | , "with nospam" | ||
479 | , "(" ++ nospam64 cksum ++ "," ++nospam16 cksum ++ ")" | ||
480 | , "to" | ||
481 | , show (key2id theirkey) | ||
482 | , "via" | ||
483 | , show (Tox.rendezvousNode rendezvous) | ||
484 | ] | ||
485 | let fr = FriendRequest | ||
486 | { friendNoSpam = nospam | ||
487 | , friendRequestText = "XMPP friend request" | ||
488 | } | ||
489 | sendMessage tr route (mypub,Tox.OnionFriendRequest fr) | ||
490 | scheduleSearch announcer akey meth them | ||
491 | |||
492 | startConnecting :: ToxToXMPP -> PublicKey -> String -> IO () | ||
493 | startConnecting tx them reason = do | ||
494 | mc <- atomically $ HashMap.lookup (key2id them) | ||
495 | <$> readTVar (contacts $ txAccount tx) | ||
496 | forM_ mc $ flip (startConnecting0 tx them) reason | ||
497 | |||
498 | |||
499 | stopConnecting :: ToxToXMPP -> PublicKey -> String -> IO () | ||
500 | stopConnecting ToxToXMPP{txAnnouncer=announcer,txAccount=acnt} them reason = do | ||
501 | dput XMan $ "STOP CONNECTING " ++ show (key2id them) ++ "("++reason++")" | ||
502 | let pub = toPublic $ userSecret acnt | ||
503 | me = key2id pub | ||
504 | akeyC = akeyConnect announcer me them | ||
505 | akeyD = akeyDHTKeyShare announcer me them | ||
506 | cancel announcer akeyC | ||
507 | cancel announcer akeyD | ||
508 | |||
509 | forkAccountWatcher :: Account JabberClients -> Tox JabberClients -> PresenceState -> Announcer -> IO ThreadId | ||
510 | forkAccountWatcher acc tox st announcer = forkIO $ do | ||
511 | myThreadId >>= flip labelThread ("tox-xmpp:" | ||
512 | ++ show (key2id $ toPublic $ userSecret acc)) | ||
513 | (chan,cs) <- atomically $ do | ||
514 | chan <- dupTChan $ eventChan acc -- duplicate broadcast channel for reading. | ||
515 | contacts <- readTVar (contacts acc) | ||
516 | return (chan,contacts) | ||
517 | let tx = ToxToXMPP { txAnnouncer = announcer | ||
518 | , txAccount = acc | ||
519 | , txPresence = st | ||
520 | , txTox = tox | ||
521 | } | ||
522 | forM_ (HashMap.toList cs) $ \(them,c) -> do | ||
523 | startConnecting0 tx (id2key them) c "enabled account" | ||
524 | |||
525 | -- Loop endlessly until accountExtra is null. | ||
526 | fix $ \loop -> do | ||
527 | mev <- atomically $ | ||
528 | (Just <$> readTChan chan) | ||
529 | `orElse` do | ||
530 | refs <- readTVar $ accountExtra acc | ||
531 | check $ Map.null refs | ||
532 | return Nothing | ||
533 | |||
534 | forM_ mev $ \ev -> dispatch tx ev >> loop | ||
535 | |||
536 | cs <- atomically $ readTVar (contacts acc) | ||
537 | forM_ (HashMap.toList cs) $ \(them,c) -> do | ||
538 | stopConnecting tx (id2key them) "disabled account" | ||
539 | |||
540 | |||
541 | toxQSearch :: Tox extra -> Search Tox.NodeId (IP, PortNumber) Nonce32 Tox.NodeInfo Tox.Rendezvous | ||
542 | toxQSearch tox = Tox.toxidSearch (Tox.onionTimeout tox) (Tox.toxCryptoKeys tox) (Tox.toxOnion tox) | ||
543 | |||
544 | toxAnnounceInterval :: POSIXTime | ||
545 | toxAnnounceInterval = 15 | ||
546 | |||
547 | |||
548 | |||