summaryrefslogtreecommitdiff
path: root/dht/src/Network/Tox/DHT
diff options
context:
space:
mode:
authorJames Crayne <jim.crayne@gmail.com>2019-09-28 13:43:29 -0400
committerJoe Crayne <joe@jerkface.net>2020-01-01 19:27:53 -0500
commit11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch)
tree5716463275c2d3e902889db619908ded2a73971c /dht/src/Network/Tox/DHT
parentadd2c76bced51fde5e9917e7449ef52be70faf87 (diff)
Factor out some new libraries
word64-map: Data.Word64Map network-addr: Network.Address tox-crypto: Crypto.Tox lifted-concurrent: Control.Concurrent.Lifted.Instrument Control.Concurrent.Async.Lifted.Instrument psq-wrap: Data.Wrapper.PSQInt Data.Wrapper.PSQ minmax-psq: Data.MinMaxPSQ tasks: Control.Concurrent.Tasks kad: Network.Kademlia Network.Kademlia.Bootstrap Network.Kademlia.Routing Network.Kademlia.CommonAPI Network.Kademlia.Persistence Network.Kademlia.Search
Diffstat (limited to 'dht/src/Network/Tox/DHT')
-rw-r--r--dht/src/Network/Tox/DHT/Handlers.hs573
-rw-r--r--dht/src/Network/Tox/DHT/Transport.hs460
2 files changed, 1033 insertions, 0 deletions
diff --git a/dht/src/Network/Tox/DHT/Handlers.hs b/dht/src/Network/Tox/DHT/Handlers.hs
new file mode 100644
index 00000000..1eec93b9
--- /dev/null
+++ b/dht/src/Network/Tox/DHT/Handlers.hs
@@ -0,0 +1,573 @@
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE NamedFieldPuns #-}
4{-# LANGUAGE PatternSynonyms #-}
5{-# LANGUAGE TupleSections #-}
6module Network.Tox.DHT.Handlers where
7
8import Debug.Trace
9import Network.Tox.DHT.Transport as DHTTransport
10import Network.QueryResponse as QR hiding (Client)
11import qualified Network.QueryResponse as QR (Client)
12import Crypto.Tox
13import Network.Kademlia.Search
14import qualified Data.Wrapper.PSQInt as Int
15import Network.Kademlia
16import Network.Kademlia.Bootstrap
17import Network.Address (WantIP (..), ipFamily, fromSockAddr, sockAddrPort)
18import qualified Network.Kademlia.Routing as R
19import Control.TriadCommittee
20import System.Global6
21import DPut
22import DebugTag
23
24import qualified Data.ByteArray as BA
25import qualified Data.ByteString.Char8 as C8
26import qualified Data.ByteString.Base16 as Base16
27import Control.Arrow
28import Control.Monad
29import Control.Concurrent.Lifted.Instrument
30import Control.Concurrent.STM
31import Data.Hashable
32import Data.Ord
33import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
34import Network.Socket
35import qualified Data.HashMap.Strict as HashMap
36 ;import Data.HashMap.Strict (HashMap)
37#if MIN_VERSION_iproute(1,7,4)
38import Data.IP hiding (fromSockAddr)
39#else
40import Data.IP
41#endif
42import Data.Maybe
43import Data.Serialize (Serialize)
44import Data.Word
45
46data TransactionId = TransactionId
47 { transactionKey :: Nonce8 -- ^ Used to lookup pending query.
48 , cryptoNonce :: Nonce24 -- ^ Used during the encryption layer.
49 }
50 deriving (Eq,Ord,Show)
51
52newtype PacketKind = PacketKind Word8
53 deriving (Eq, Ord, Serialize)
54
55pattern OnionRequest0Type = PacketKind 128 -- 0x80 Onion Request 0
56pattern OnionRequest1Type = PacketKind 129 -- 0x81 Onion Request 1
57pattern OnionRequest2Type = PacketKind 130 -- 0x82 Onion Request 2
58pattern AnnounceType = PacketKind 131 -- 0x83 Announce Request
59pattern AnnounceResponseType = PacketKind 132 -- 0x84 Announce Response
60
61pattern DataRequestType = PacketKind 133 -- 0x85 Onion Data Request (data to route request packet)
62pattern DataResponseType = PacketKind 134 -- 0x86 Onion Data Response (data to route response packet)
63-- 0x8c Onion Response 3
64-- 0x8d Onion Response 2
65pattern OnionResponse3Type = PacketKind 140 -- 0x8c Onion Response 3
66pattern OnionResponse2Type = PacketKind 141 -- 0x8d Onion Response 2
67pattern OnionResponse1Type = PacketKind 142 -- 0x8e Onion Response 1
68-- 0xf0 Bootstrap Info
69
70pattern DHTRequestType = PacketKind 32 -- 0x20 DHT Request
71
72pattern CookieRequestType = PacketKind 0x18
73pattern CookieResponseType = PacketKind 0x19
74
75pattern PingType = PacketKind 0 -- 0x00 Ping Request
76pattern PongType = PacketKind 1 -- 0x01 Ping Response
77pattern GetNodesType = PacketKind 2 -- 0x02 Nodes Request
78pattern SendNodesType = PacketKind 4 -- 0x04 Nodes Response
79
80
81instance Show PacketKind where
82 showsPrec d PingType = mappend "PingType"
83 showsPrec d PongType = mappend "PongType"
84 showsPrec d GetNodesType = mappend "GetNodesType"
85 showsPrec d SendNodesType = mappend "SendNodesType"
86 showsPrec d DHTRequestType = mappend "DHTRequestType"
87 showsPrec d OnionRequest0Type = mappend "OnionRequest0Type"
88 showsPrec d OnionResponse1Type = mappend "OnionResponse1Type"
89 showsPrec d OnionResponse3Type = mappend "OnionResponse3Type"
90 showsPrec d AnnounceType = mappend "AnnounceType"
91 showsPrec d AnnounceResponseType = mappend "AnnounceResponseType"
92 showsPrec d DataRequestType = mappend "DataRequestType"
93 showsPrec d DataResponseType = mappend "DataResponseType"
94 showsPrec d CookieRequestType = mappend "CookieRequestType"
95 showsPrec d CookieResponseType = mappend "CookieResponseType"
96 showsPrec d (PacketKind x) = mappend "PacketKind " . showsPrec (d+1) x
97
98msgType :: ( Serialize (f DHTRequest)
99 , Serialize (f (Cookie Encrypted)), Serialize (f CookieRequest)
100 , Serialize (f SendNodes), Serialize (f GetNodes)
101 , Serialize (f Pong), Serialize (f Ping)
102 ) => DHTMessage f -> PacketKind
103msgType msg = PacketKind $ fst $ dhtMessageType msg
104
105classify :: Client -> Message -> MessageClass String PacketKind TransactionId NodeInfo Message
106classify client (DHTLanDiscovery {}) = IsUnsolicited (lanDiscoveryH client)
107classify client msg = fromMaybe (IsUnknown "unknown")
108 $ mapMessage (\nonce24 (nonce8,_) -> go msg (TransactionId nonce8 nonce24)) msg
109 where
110 go (DHTPing {}) = IsQuery PingType
111 go (DHTGetNodes {}) = IsQuery GetNodesType
112 go (DHTPong {}) = IsResponse
113 go (DHTSendNodes {}) = IsResponse
114 go (DHTCookieRequest {}) = IsQuery CookieRequestType
115 go (DHTCookie {}) = IsResponse
116 go (DHTDHTRequest {}) = IsQuery DHTRequestType
117
118data NodeInfoCallback = NodeInfoCallback
119 { interestingNodeId :: NodeId
120 , listenerId :: Int
121 , observedAddress :: POSIXTime -> NodeInfo -- Address and port for interestingNodeId
122 -> STM ()
123 , rumoredAddress :: POSIXTime -> SockAddr -- source of information
124 -> NodeInfo -- Address and port for interestingNodeId
125 -> STM ()
126 }
127
128data Routing = Routing
129 { tentativeId :: NodeInfo
130 , committee4 :: TriadCommittee NodeId SockAddr
131 , committee6 :: TriadCommittee NodeId SockAddr
132 , refresher4 :: BucketRefresher NodeId NodeInfo
133 , refresher6 :: BucketRefresher NodeId NodeInfo
134 , nodesOfInterest :: TVar (HashMap NodeId [NodeInfoCallback])
135 }
136
137registerNodeCallback :: Routing -> NodeInfoCallback -> STM ()
138registerNodeCallback Routing{nodesOfInterest} cb = do
139 cbm <- readTVar nodesOfInterest
140 let ns = fromMaybe [] $ HashMap.lookup (interestingNodeId cb) cbm
141 bs = filter nonMatching ns
142 where nonMatching n = (listenerId n /= listenerId cb)
143 writeTVar nodesOfInterest $ HashMap.insert (interestingNodeId cb)
144 (cb : bs)
145 cbm
146
147unregisterNodeCallback :: Int -> Routing -> NodeId -> STM ()
148unregisterNodeCallback callbackId Routing{nodesOfInterest} nid = do
149 cbm <- readTVar nodesOfInterest
150 let ns = fromMaybe [] $ HashMap.lookup nid cbm
151 bs = filter nonMatching ns
152 where nonMatching n = (listenerId n /= callbackId)
153 writeTVar nodesOfInterest
154 $ if null bs
155 then HashMap.delete nid cbm
156 else HashMap.insert nid bs cbm
157
158
159sched4 :: Routing -> TVar (Int.PSQ POSIXTime)
160sched4 Routing { refresher4 = BucketRefresher { refreshQueue } } = refreshQueue
161
162sched6 :: Routing -> TVar (Int.PSQ POSIXTime)
163sched6 Routing { refresher6 = BucketRefresher { refreshQueue } } = refreshQueue
164
165routing4 :: Routing -> TVar (R.BucketList NodeInfo)
166routing4 Routing { refresher4 = BucketRefresher { refreshBuckets } } = refreshBuckets
167
168routing6 :: Routing -> TVar (R.BucketList NodeInfo)
169routing6 Routing { refresher6 = BucketRefresher { refreshBuckets } } = refreshBuckets
170
171newRouting :: SockAddr -> TransportCrypto
172 -> (TVar (R.BucketList NodeInfo) -> SockAddr -> STM ()) -- ^ invoked on IPv4 change
173 -> (TVar (R.BucketList NodeInfo) -> SockAddr -> STM ()) -- ^ invoked on IPv6 change
174 -> IO (Client -> Routing)
175newRouting addr crypto update4 update6 = do
176 let tentative_ip4 = fromMaybe (IPv4 $ toEnum 0) (IPv4 <$> fromSockAddr addr)
177 tentative_ip6 = fromMaybe (IPv6 $ toEnum 0) (IPv6 <$> fromSockAddr addr)
178 tentative_info = NodeInfo
179 { nodeId = key2id $ transportPublic crypto
180 , nodeIP = fromMaybe (toEnum 0) (fromSockAddr addr)
181 , nodePort = fromMaybe 0 $ sockAddrPort addr
182 }
183 tentative_info4 = tentative_info { nodeIP = tentative_ip4 }
184 tentative_info6 <-
185 maybe (tentative_info { nodeIP = tentative_ip6 })
186 (\ip6 -> tentative_info { nodeIP = IPv6 ip6 })
187 <$> case addr of
188 SockAddrInet {} -> return Nothing
189 _ -> global6
190 atomically $ do
191 -- We defer initializing the refreshSearch and refreshPing until we
192 -- have a client to send queries with.
193 let nullPing = const $ return False
194 nullSearch = Search
195 { searchSpace = toxSpace
196 , searchNodeAddress = nodeIP &&& nodePort
197 , searchQuery = Left $ \_ _ -> return Nothing
198 , searchAlpha = 1
199 , searchK = 2
200 }
201 tbl4 <- newTVar $ R.nullTable (comparing nodeId) (\s -> hashWithSalt s . nodeId) tentative_info4 R.defaultBucketCount
202 tbl6 <- newTVar $ R.nullTable (comparing nodeId) (\s -> hashWithSalt s . nodeId) tentative_info6 R.defaultBucketCount
203 refresher4 <- newBucketRefresher tbl4 nullSearch nullPing
204 refresher6 <- newBucketRefresher tbl6 nullSearch nullPing
205 committee4 <- newTriadCommittee (update4 tbl4) -- updateIPVote tbl4 addr4
206 committee6 <- newTriadCommittee (update6 tbl6) -- updateIPVote tbl6 addr6
207 cbvar <- newTVar HashMap.empty
208 return $ \client ->
209 -- Now we have a client, so tell the BucketRefresher how to search and ping.
210 let updIO r = updateRefresherIO (nodeSearch client cbvar) (ping client) r
211 in Routing { tentativeId = tentative_info
212 , committee4 = committee4
213 , committee6 = committee6
214 , refresher4 = updIO refresher4
215 , refresher6 = updIO refresher6
216 , nodesOfInterest = cbvar
217 }
218
219
220-- TODO: This should cover more cases
221isLocal :: IP -> Bool
222isLocal (IPv6 ip6) = (ip6 == toEnum 0)
223isLocal (IPv4 ip4) = (ip4 == toEnum 0)
224
225isGlobal :: IP -> Bool
226isGlobal = not . isLocal
227
228prefer4or6 :: NodeInfo -> Maybe WantIP -> WantIP
229prefer4or6 addr iptyp = fromMaybe (ipFamily $ nodeIP addr) iptyp
230
231toxSpace :: R.KademliaSpace NodeId NodeInfo
232toxSpace = R.KademliaSpace
233 { R.kademliaLocation = nodeId
234 , R.kademliaTestBit = testNodeIdBit
235 , R.kademliaXor = xorNodeId
236 , R.kademliaSample = sampleNodeId
237 }
238
239
240pingH :: NodeInfo -> Ping -> IO Pong
241pingH _ Ping = return Pong
242
243getNodesH :: Routing -> NodeInfo -> GetNodes -> IO SendNodes
244getNodesH routing addr (GetNodes nid) = do
245 let preferred = prefer4or6 addr Nothing
246
247 (append4,append6) <- atomically $ do
248 ni4 <- R.thisNode <$> readTVar (routing4 routing)
249 ni6 <- R.thisNode <$> readTVar (routing6 routing)
250 return $ case ipFamily (nodeIP addr) of
251 Want_IP4 | isGlobal (nodeIP ni6) -> (id, (++ [ni6]))
252 Want_IP6 | isGlobal (nodeIP ni4) -> ((++ [ni4]), id)
253 _ -> (id, id)
254 ks <- go append4 $ routing4 routing
255 ks6 <- go append6 $ routing6 routing
256 let (ns1,ns2) = case preferred of Want_IP6 -> (ks6,ks)
257 Want_IP4 -> (ks,ks6)
258 Want_Both -> error $ "BUG:unreachable at " ++ __FILE__ ++ ":" ++ show __LINE__
259 return $ SendNodes
260 $ if null ns2 then ns1
261 else take 4 (take 3 ns1 ++ ns2)
262 where
263 go f var = f . R.kclosest toxSpace k nid <$> atomically (readTVar var)
264
265 k = 4
266
267createCookie :: TransportCrypto -> NodeInfo -> PublicKey -> IO (Cookie Encrypted)
268createCookie crypto ni remoteUserKey = do
269 (n24,sym) <- atomically $ do
270 n24 <- transportNewNonce crypto
271 sym <- transportSymmetric crypto
272 return (n24,sym)
273 timestamp <- round . (* 1000000) <$> getPOSIXTime
274 let dta = encodePlain $ CookieData
275 { cookieTime = timestamp
276 , longTermKey = remoteUserKey
277 , dhtKey = id2key $ nodeId ni -- transportPublic crypto
278 }
279 edta = encryptSymmetric sym n24 dta
280 return $ Cookie n24 edta
281
282createCookieSTM :: POSIXTime -> TransportCrypto -> NodeInfo -> PublicKey -> STM (Cookie Encrypted)
283createCookieSTM now crypto ni remoteUserKey = do
284 let dmsg msg = trace msg (return ())
285 (n24,sym) <- do
286 n24 <- transportNewNonce crypto
287 sym <- transportSymmetric crypto
288 return (n24,sym)
289 let timestamp = round (now * 1000000)
290 let dta = encodePlain $ CookieData
291 { cookieTime = timestamp
292 , longTermKey = remoteUserKey
293 , dhtKey = id2key $ nodeId ni -- transportPublic crypto
294 }
295 edta = encryptSymmetric sym n24 dta
296 return $ Cookie n24 edta
297
298cookieRequestH :: TransportCrypto -> NodeInfo -> CookieRequest -> IO (Cookie Encrypted)
299cookieRequestH crypto ni (CookieRequest remoteUserKey) = do
300 dput XNetCrypto $ unlines
301 [ show (nodeAddr ni) ++ " --> request cookie: remoteUserKey=" ++ show (key2id remoteUserKey)
302 , show (nodeAddr ni) ++ " --> sender=" ++ show (nodeId ni) ]
303 x <- createCookie crypto ni remoteUserKey
304 dput XNetCrypto $ show (nodeAddr ni) ++ " <-- cookie " ++ show (key2id remoteUserKey)
305 return x
306
307lanDiscoveryH :: Client -> NodeInfo -> NodeInfo -> IO (Maybe (Message -> Message))
308lanDiscoveryH client _ ni = do
309 dput XLan $ show (nodeAddr ni) ++ " --> LanAnnounce " ++ show (nodeId ni)
310 forkIO $ do
311 myThreadId >>= flip labelThread "lan-discover-ping"
312 ping client ni
313 return ()
314 return Nothing
315
316type Message = DHTMessage ((,) Nonce8)
317
318type Client = QR.Client String PacketKind TransactionId NodeInfo Message
319
320
321wrapAsymm :: TransactionId -> NodeInfo -> NodeInfo -> (Nonce8 -> dta) -> Asymm dta
322wrapAsymm (TransactionId n8 n24) src dst dta = Asymm
323 { senderKey = id2key $ nodeId src
324 , asymmNonce = n24
325 , asymmData = dta n8
326 }
327
328serializer :: PacketKind
329 -> (Asymm (Nonce8,ping) -> Message)
330 -> (Message -> Maybe (Asymm (Nonce8,pong)))
331 -> MethodSerializer TransactionId NodeInfo Message PacketKind ping (Maybe pong)
332serializer pktkind mkping mkpong = MethodSerializer
333 { methodTimeout = \tid addr -> return (addr, 5000000)
334 , method = pktkind
335 -- wrapQuery :: tid -> addr -> addr -> qry -> x
336 , wrapQuery = \tid src dst ping -> mkping $ wrapAsymm tid src dst (, ping)
337 -- unwrapResponse :: x -> b
338 , unwrapResponse = fmap (snd . asymmData) . mkpong
339 }
340
341
342unpong :: Message -> Maybe (Asymm (Nonce8,Pong))
343unpong (DHTPong asymm) = Just asymm
344unpong _ = Nothing
345
346ping :: Client -> NodeInfo -> IO Bool
347ping client addr = do
348 dput XPing $ show addr ++ " <-- ping"
349 reply <- QR.sendQuery client (serializer PingType DHTPing unpong) Ping addr
350 dput XPing $ show addr ++ " -pong-> " ++ show reply
351 maybe (return False) (\Pong -> return True) $ join reply
352
353
354saveCookieKey :: TVar [(SockAddr, (Int, PublicKey))] -> SockAddr -> PublicKey -> STM ()
355saveCookieKey var saddr pk = do
356 cookiekeys <- readTVar var
357 case break (\(stored,_) -> stored == saddr) cookiekeys of
358 (xs,[]) -> writeTVar var $ (saddr, (1 ,pk)) : xs
359 (xs,(_,(c,stored)):ys) | stored == pk -> writeTVar var $ (saddr, (c+1,pk)) : xs ++ ys
360 _ -> retry -- Wait for requests to this address
361 -- under a different key to time out
362 -- before we try this key.
363
364loseCookieKey :: TVar [(SockAddr, (Int, PublicKey))] -> SockAddr -> PublicKey -> STM ()
365loseCookieKey var saddr pk = do
366 cookiekeys <- readTVar var
367 case break (\(stored,_) -> stored == saddr) cookiekeys of
368 (xs,(_,(1,stored)):ys) | stored == pk -> writeTVar var $ xs ++ ys
369 (xs,(_,(c,stored)):ys) | stored == pk -> writeTVar var $ (saddr, (c-1,pk)) : xs ++ ys
370 _ -> return () -- unreachable?
371
372
373cookieRequest :: TransportCrypto -> Client -> PublicKey -> NodeInfo -> IO (Maybe (Cookie Encrypted))
374cookieRequest crypto client localUserKey addr = do
375 let sockAddr = nodeAddr addr
376 nid = id2key $ nodeId addr
377 cookieSerializer
378 = MethodSerializer
379 { methodTimeout = \tid addr -> return (addr, 5000000)
380 , method = CookieRequestType
381 , wrapQuery = \tid src dst cr -> DHTCookieRequest $ wrapAsymm tid src dst (, cr)
382 , unwrapResponse = fmap snd . unCookie
383 }
384 cookieRequest = CookieRequest localUserKey
385 atomically $ saveCookieKey (pendingCookies crypto) sockAddr nid
386 dput XNetCrypto $ show addr ++ " <-- cookieRequest"
387 reply <- QR.sendQuery client cookieSerializer cookieRequest addr
388 atomically $ loseCookieKey (pendingCookies crypto) sockAddr nid
389 dput XNetCrypto $ show addr ++ " -cookieResponse-> " ++ show reply
390 return $ join reply
391
392unCookie :: DHTMessage t -> Maybe (t (Cookie Encrypted))
393unCookie (DHTCookie n24 fcookie) = Just fcookie
394unCookie _ = Nothing
395
396unsendNodes :: Message -> Maybe (Asymm (Nonce8,SendNodes))
397unsendNodes (DHTSendNodes asymm) = Just asymm
398unsendNodes _ = Nothing
399
400unwrapNodes :: SendNodes -> ( [NodeInfo], [NodeInfo], Maybe () )
401unwrapNodes (SendNodes ns) = (ns,ns,Just ())
402
403data SendableQuery x a b = SendableQuery
404 { sendableSerializer :: MethodSerializer TransactionId NodeInfo Message PacketKind a (Maybe x)
405 , sendableQuery :: NodeId -> a
406 , sendableResult :: Maybe (Maybe x) -> IO b
407 }
408
409sendQ :: SendableQuery x a b
410 -> QR.Client err PacketKind TransactionId NodeInfo Message
411 -> NodeId
412 -> NodeInfo
413 -> IO b
414sendQ s client nid addr = do
415 reply <- QR.sendQuery client (sendableSerializer s) (sendableQuery s nid) addr
416 sendableResult s reply
417
418asyncQ :: SendableQuery x a b
419 -> QR.Client err PacketKind TransactionId NodeInfo Message
420 -> NodeId
421 -> NodeInfo
422 -> (b -> IO ())
423 -> IO ()
424asyncQ s client nid addr go = do
425 QR.asyncQuery client (sendableSerializer s) (sendableQuery s nid) addr
426 $ sendableResult s >=> go
427
428getNodesSendable :: TVar (HashMap NodeId [NodeInfoCallback])
429 -> NodeInfo
430 -> SendableQuery SendNodes GetNodes (Maybe ([NodeInfo], [NodeInfo], Maybe ()))
431getNodesSendable cbvar addr = SendableQuery (serializer GetNodesType DHTGetNodes unsendNodes)
432 GetNodes
433 go
434 where
435 go reply = do
436 forM_ (join reply) $ \(SendNodes ns) ->
437 forM_ ns $ \n -> do
438 now <- getPOSIXTime
439 atomically $ do
440 mcbs <- HashMap.lookup (nodeId n) <$> readTVar cbvar
441 forM_ mcbs $ \cbs -> do
442 forM_ cbs $ \cb -> do
443 rumoredAddress cb now (nodeAddr addr) n
444 return $ fmap unwrapNodes $ join reply
445
446getNodes :: Client -> TVar (HashMap NodeId [NodeInfoCallback]) -> NodeId -> NodeInfo -> IO (Maybe ([NodeInfo],[NodeInfo],Maybe ()))
447getNodes client cbvar nid addr =
448 sendQ (getNodesSendable cbvar addr) client nid addr
449
450asyncGetNodes :: QR.Client err PacketKind TransactionId NodeInfo Message
451 -> TVar (HashMap NodeId [NodeInfoCallback])
452 -> NodeId
453 -> NodeInfo
454 -> (Maybe ([NodeInfo], [NodeInfo], Maybe ()) -> IO ())
455 -> IO ()
456asyncGetNodes client cbvar nid addr go =
457 asyncQ (getNodesSendable cbvar addr) client nid addr go
458
459updateRouting :: Client -> Routing
460 -> (TVar (R.BucketList NodeInfo) -> RoutingTransition NodeInfo -> STM ())
461 -> NodeInfo
462 -> Message
463 -> IO ()
464updateRouting client routing orouter naddr msg
465 | PacketKind 0x21 <- msgType msg = -- dput XLan "(tox)updateRouting: ignoring lan discovery" -- ignore lan discovery
466 -- Ignore lan announcements until they reply to our ping.
467 -- We do this because the lan announce is not authenticated.
468 return ()
469 | otherwise = do
470 now <- getPOSIXTime
471 atomically $ do
472 m <- HashMap.lookup (nodeId naddr) <$> readTVar (nodesOfInterest routing)
473 forM_ m $ mapM_ $ \NodeInfoCallback{interestingNodeId,observedAddress} -> do
474 when (interestingNodeId == nodeId naddr)
475 $ observedAddress now naddr
476 case prefer4or6 naddr Nothing of
477 Want_IP4 -> updateTable client naddr orouter (committee4 routing) (refresher4 routing)
478 Want_IP6 -> updateTable client naddr orouter (committee6 routing) (refresher6 routing)
479 Want_Both -> do dput XMisc "BUG:unreachable"
480 error $ "BUG:unreachable at " ++ __FILE__ ++ ":" ++ show __LINE__
481
482updateTable :: Client -> NodeInfo
483 -> (TVar (R.BucketList NodeInfo) -> RoutingTransition NodeInfo -> STM ())
484 -> TriadCommittee NodeId SockAddr
485 -> BucketRefresher NodeId NodeInfo
486 -> IO ()
487updateTable client naddr orouter committee refresher = do
488 self <- atomically $ R.thisNode <$> readTVar (refreshBuckets refresher)
489 -- dput XMisc $ "(tox)updateRouting: " ++ show (nodeIP self, nodeIP naddr)
490 when (self /= naddr) $ do
491 -- TODO: IP address vote?
492 insertNode (toxKademlia client committee orouter refresher) naddr
493
494toxKademlia :: Client
495 -> TriadCommittee NodeId SockAddr
496 -> (TVar (R.BucketList NodeInfo) -> RoutingTransition NodeInfo -> STM ())
497 -> BucketRefresher NodeId NodeInfo
498 -> Kademlia NodeId NodeInfo
499toxKademlia client committee orouter refresher
500 = Kademlia quietInsertions
501 toxSpace
502 (vanillaIO (refreshBuckets refresher) $ ping client)
503 { tblTransition = \tr -> do
504 io1 <- transitionCommittee committee tr
505 io2 <- touchBucket refresher tr -- toxSpace (15*60) var sched tr
506 -- hookBucketList toxSpace (refreshBuckets refresher) orouter tr
507 orouter (refreshBuckets refresher) tr
508 return $ do
509 io1 >> io2
510 {-
511 dput XMisc $ unwords
512 [ show (transitionedTo tr)
513 , show (transitioningNode tr)
514 ]
515 -}
516 return ()
517 }
518
519transitionCommittee :: TriadCommittee NodeId SockAddr -> RoutingTransition NodeInfo -> STM (IO ())
520transitionCommittee committee (RoutingTransition ni Stranger) = do
521 delVote committee (nodeId ni)
522 return $ do
523 -- dput XMisc $ "delVote "++show (nodeId ni)
524 return ()
525transitionCommittee committee _ = return $ return ()
526
527type Handler = MethodHandler String TransactionId NodeInfo Message
528
529isPing :: (f Ping -> Ping) -> DHTMessage f -> Either String Ping
530isPing unpack (DHTPing a) = Right $ unpack $ asymmData a
531isPing _ _ = Left "Bad ping"
532
533mkPong :: TransactionId -> NodeInfo -> NodeInfo -> Pong -> DHTMessage ((,) Nonce8)
534mkPong tid src dst pong = DHTPong $ wrapAsymm tid src dst (, pong)
535
536isGetNodes :: (f GetNodes -> GetNodes) -> DHTMessage f -> Either String GetNodes
537isGetNodes unpack (DHTGetNodes a) = Right $ unpack $ asymmData a
538isGetNodes _ _ = Left "Bad GetNodes"
539
540mkSendNodes :: TransactionId -> NodeInfo -> NodeInfo -> SendNodes -> DHTMessage ((,) Nonce8)
541mkSendNodes tid src dst sendnodes = DHTSendNodes $ wrapAsymm tid src dst (, sendnodes)
542
543isCookieRequest :: (f CookieRequest -> CookieRequest) -> DHTMessage f -> Either String CookieRequest
544isCookieRequest unpack (DHTCookieRequest a) = Right $ unpack $ asymmData a
545isCookieRequest _ _ = Left "Bad cookie request"
546
547mkCookie :: TransactionId -> NodeInfo -> NodeInfo -> Cookie Encrypted -> DHTMessage ((,) Nonce8)
548mkCookie (TransactionId n8 n24) src dst cookie = DHTCookie n24 (n8,cookie)
549
550isDHTRequest :: (f DHTRequest -> DHTRequest) -> DHTMessage f -> Either String DHTRequest
551isDHTRequest unpack (DHTDHTRequest pubkey a) = Right $ unpack $ asymmData a
552isDHTRequest _ _ = Left "Bad dht relay request"
553
554dhtRequestH :: NodeInfo -> DHTRequest -> IO ()
555dhtRequestH ni req = do
556 dput XMisc $ "Unhandled DHT Request: " ++ show req
557
558handlers :: TransportCrypto -> Routing -> PacketKind -> Maybe Handler
559handlers _ routing PingType = Just $ MethodHandler (isPing snd) mkPong $ pingH
560handlers _ routing GetNodesType = Just $ MethodHandler (isGetNodes snd) mkSendNodes $ getNodesH routing
561handlers crypto _ CookieRequestType = Just $ MethodHandler (isCookieRequest snd) mkCookie $ cookieRequestH crypto
562handlers _ _ DHTRequestType = Just $ NoReply (isDHTRequest snd) $ dhtRequestH
563handlers _ _ typ = error $ "TODO DHT handlers " ++ show typ
564
565nodeSearch :: Client -> TVar (HashMap NodeId [NodeInfoCallback]) -> Search NodeId (IP,PortNumber) () NodeInfo NodeInfo
566nodeSearch client cbvar = Search
567 { searchSpace = toxSpace
568 , searchNodeAddress = nodeIP &&& nodePort
569 , searchQuery = Right $ asyncGetNodes client cbvar
570 , searchAlpha = 8
571 , searchK = 16
572
573 }
diff --git a/dht/src/Network/Tox/DHT/Transport.hs b/dht/src/Network/Tox/DHT/Transport.hs
new file mode 100644
index 00000000..b9b63165
--- /dev/null
+++ b/dht/src/Network/Tox/DHT/Transport.hs
@@ -0,0 +1,460 @@
1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE KindSignatures #-}
6{-# LANGUAGE LambdaCase #-}
7{-# LANGUAGE RankNTypes #-}
8{-# LANGUAGE StandaloneDeriving #-}
9{-# LANGUAGE TupleSections #-}
10{-# LANGUAGE TypeOperators #-}
11{-# LANGUAGE UndecidableInstances #-}
12module Network.Tox.DHT.Transport
13 ( parseDHTAddr
14 , encodeDHTAddr
15 , forwardDHTRequests
16 , module Network.Tox.NodeId
17 , DHTMessage(..)
18 , Ping(..)
19 , Pong(..)
20 , GetNodes(..)
21 , SendNodes(..)
22 , DHTPublicKey(..)
23 , FriendRequest(..)
24 , NoSpam(..)
25 , CookieRequest(..)
26 , CookieResponse(..)
27 , Cookie(..)
28 , CookieData(..)
29 , DHTRequest
30 , mapMessage
31 , encrypt
32 , decrypt
33 , dhtMessageType
34 , asymNodeInfo
35 , putMessage -- Convenient for serializing DHTLanDiscovery
36 ) where
37
38import Network.Tox.NodeId
39import Crypto.Tox hiding (encrypt,decrypt)
40import qualified Crypto.Tox as ToxCrypto
41import Network.QueryResponse
42
43import Control.Applicative
44import Control.Arrow
45import Control.Concurrent.STM
46import Control.Monad
47import Data.Bool
48import qualified Data.ByteString as B
49 ;import Data.ByteString (ByteString)
50import Data.Functor.Contravariant
51import Data.Hashable
52import Data.Maybe
53import Data.Monoid
54import Data.Serialize as S
55import Data.Tuple
56import Data.Word
57import GHC.Generics
58import Network.Socket
59
60type DHTTransport = Transport String NodeInfo (DHTMessage Encrypted8)
61type HandleHi a = Maybe (Either String (DHTMessage Encrypted8, NodeInfo)) -> IO a
62
63
64data DHTMessage (f :: * -> *)
65 = DHTPing (Asymm (f Ping))
66 | DHTPong (Asymm (f Pong))
67 | DHTGetNodes (Asymm (f GetNodes))
68 | DHTSendNodes (Asymm (f SendNodes))
69 | DHTCookieRequest (Asymm (f CookieRequest))
70 | DHTCookie Nonce24 (f (Cookie Encrypted))
71 | DHTDHTRequest PublicKey (Asymm (f DHTRequest))
72 | DHTLanDiscovery NodeId
73
74deriving instance ( Show (f (Cookie Encrypted))
75 , Show (f Ping)
76 , Show (f Pong)
77 , Show (f GetNodes)
78 , Show (f SendNodes)
79 , Show (f CookieRequest)
80 , Show (f DHTRequest)
81 ) => Show (DHTMessage f)
82
83mapMessage :: forall f b. (forall a. Nonce24 -> f a -> b) -> DHTMessage f -> Maybe b
84mapMessage f (DHTPing a) = Just $ f (asymmNonce a) (asymmData a)
85mapMessage f (DHTPong a) = Just $ f (asymmNonce a) (asymmData a)
86mapMessage f (DHTGetNodes a) = Just $ f (asymmNonce a) (asymmData a)
87mapMessage f (DHTSendNodes a) = Just $ f (asymmNonce a) (asymmData a)
88mapMessage f (DHTCookieRequest a) = Just $ f (asymmNonce a) (asymmData a)
89mapMessage f (DHTDHTRequest _ a) = Just $ f (asymmNonce a) (asymmData a)
90mapMessage f (DHTCookie nonce fcookie) = Just $ f nonce fcookie
91mapMessage f (DHTLanDiscovery nid) = Nothing
92
93
94instance Sized Ping where size = ConstSize 1
95instance Sized Pong where size = ConstSize 1
96
97parseDHTAddr :: TransportCrypto -> (ByteString, SockAddr) -> IO (Either (DHTMessage Encrypted8,NodeInfo) (ByteString,SockAddr))
98parseDHTAddr crypto (msg,saddr)
99 | Just (typ,bs) <- B.uncons msg
100 , let right = return $ Right (msg,saddr)
101 left = either (const right) (return . Left)
102 = case typ of
103 0x00 -> left $ direct bs saddr DHTPing
104 0x01 -> left $ direct bs saddr DHTPong
105 0x02 -> left $ direct bs saddr DHTGetNodes
106 0x04 -> left $ direct bs saddr DHTSendNodes
107 0x18 -> left $ direct bs saddr DHTCookieRequest
108 0x19 -> do
109 cs <- atomically $ readTVar (pendingCookies crypto)
110 let ni = fromMaybe (noReplyAddr saddr) $ do
111 (cnt,key) <- lookup saddr cs <|> listToMaybe (map snd cs)
112 either (const Nothing) Just $ nodeInfo (key2id key) saddr
113 left $ fanGet bs getCookie (uncurry DHTCookie) (const $ ni)
114 0x20 -> left $ fanGet bs getDHTReqest (uncurry DHTDHTRequest) (asymNodeInfo saddr . snd)
115 0x21 -> left $ do
116 nid <- runGet get bs
117 ni <- nodeInfo nid saddr
118 return (DHTLanDiscovery nid, ni)
119 _ -> right
120
121encodeDHTAddr :: (DHTMessage Encrypted8,NodeInfo) -> IO (ByteString, SockAddr)
122encodeDHTAddr (msg,ni) = return (runPut $ putMessage msg, nodeAddr ni)
123
124dhtMessageType :: ( Serialize (f DHTRequest)
125 , Serialize (f (Cookie Encrypted)), Serialize (f CookieRequest)
126 , Serialize (f SendNodes), Serialize (f GetNodes)
127 , Serialize (f Pong), Serialize (f Ping)
128 ) => DHTMessage f -> (Word8, Put)
129dhtMessageType (DHTPing a) = (0x00, putAsymm a)
130dhtMessageType (DHTPong a) = (0x01, putAsymm a)
131dhtMessageType (DHTGetNodes a) = (0x02, putAsymm a)
132dhtMessageType (DHTSendNodes a) = (0x04, putAsymm a)
133dhtMessageType (DHTCookieRequest a) = (0x18, putAsymm a)
134dhtMessageType (DHTCookie n x) = (0x19, put n >> put x)
135dhtMessageType (DHTDHTRequest k a) = (0x20, putPublicKey k >> putAsymm a)
136dhtMessageType (DHTLanDiscovery nid) = (0x21, put nid)
137
138putMessage :: DHTMessage Encrypted8 -> Put
139putMessage msg = case dhtMessageType msg of (typ,p) -> put typ >> p
140
141getCookie :: Get (Nonce24, Encrypted8 (Cookie Encrypted))
142getCookie = get
143
144getDHTReqest :: Get (PublicKey, Asymm (Encrypted8 DHTRequest))
145getDHTReqest = (,) <$> getPublicKey <*> getAsymm
146
147-- ## DHT Request packets
148--
149-- | Length | Contents |
150-- |:-------|:--------------------------|
151-- | `1` | `uint8_t` (0x20) |
152-- | `32` | receiver's DHT public key |
153-- ... ...
154
155
156getDHT :: Sized a => Get (Asymm (Encrypted8 a))
157getDHT = getAsymm
158
159
160-- Throws an error if called with a non-internet socket.
161direct :: Sized a => ByteString
162 -> SockAddr
163 -> (Asymm (Encrypted8 a) -> DHTMessage Encrypted8)
164 -> Either String (DHTMessage Encrypted8, NodeInfo)
165direct bs saddr f = fanGet bs getDHT f (asymNodeInfo saddr)
166
167-- Throws an error if called with a non-internet socket.
168asymNodeInfo :: SockAddr -> Asymm a -> NodeInfo
169asymNodeInfo saddr asym = either (error . mappend "asymNodeInfo: ") id $ nodeInfo (key2id $ senderKey asym) saddr
170
171
172fanGet :: ByteString -> Get x -> (x -> a) -> (x -> b) -> Either String (a,b)
173fanGet bs getIt f nid = fmap (f &&& nid) $ runGet getIt bs
174
175-- Throws an error if called with a non-internet socket.
176noReplyAddr :: SockAddr -> NodeInfo
177noReplyAddr saddr = either (error . mappend "noReplyAddr: ") id $ nodeInfo zeroID saddr
178
179
180data DHTRequest
181 -- #### NAT ping request
182 --
183 -- Length Contents
184 -- :------- :-------------------------
185 -- `1` `uint8_t` (0xfe)
186 -- `1` `uint8_t` (0x00)
187 -- `8` `uint64_t` random number
188 = NATPing Nonce8
189 -- #### NAT ping response
190 --
191 -- Length Contents
192 -- :------- :-----------------------------------------------------------------
193 -- `1` `uint8_t` (0xfe)
194 -- `1` `uint8_t` (0x01)
195 -- `8` `uint64_t` random number (the same that was received in request)
196 | NATPong Nonce8
197 | DHTPK LongTermKeyWrap
198 -- From docs/Hardening_docs.txt
199 --
200 -- All hardening requests must contain exactly 384 bytes of data. (The data sent
201 -- must be padded with zeros if it is smaller than that.)
202 --
203 -- [byte with value: 02 (get nodes test request)][struct Node_format (the node to
204 -- test.)][client_id(32 bytes) the id to query the node with.][padding]
205 --
206 -- packet id: CRYPTO_PACKET_HARDENING (48)
207 | Hardening -- TODO
208 deriving Show
209
210instance Sized DHTRequest where
211 size = VarSize $ \case
212 NATPing _ -> 10
213 NATPong _ -> 10
214 DHTPK wrap -> 1{-typ-} + 32{-key-} + 24{-nonce-}
215 + case size of
216 ConstSize n -> n
217 VarSize f -> f (wrapData wrap)
218 Hardening -> 1{-typ-} + 384
219
220instance Serialize DHTRequest where
221 get = do
222 tag <- get
223 case tag :: Word8 of
224 0xfe -> do
225 direction <- get
226 bool NATPong NATPing (direction==(0::Word8)) <$> get
227 0x9c -> DHTPK <$> get
228 0x30 -> pure Hardening -- TODO: CRYPTO_PACKET_HARDENING
229 _ -> fail ("unrecognized DHT request: "++show tag)
230 put (NATPing n) = put (0xfe00 :: Word16) >> put n
231 put (NATPong n) = put (0xfe01 :: Word16) >> put n
232 put (DHTPK pk) = put (0x9c :: Word8) >> put pk
233 put (Hardening) = put (0x30 :: Word8) >> putByteString (B.replicate 384 0) -- TODO
234
235-- DHT public key packet:
236-- (As Onion data packet?)
237--
238-- | Length | Contents |
239-- |:------------|:------------------------------------|
240-- | `1` | `uint8_t` (0x9c) |
241-- | `8` | `uint64_t` `no_replay` |
242-- | `32` | Our DHT public key |
243-- | `[39, 204]` | Maximum of 4 nodes in packed format |
244data DHTPublicKey = DHTPublicKey
245 { dhtpkNonce :: Word64 -- ^ The `no_replay` number is protection if
246 -- someone tries to replay an older packet and
247 -- should be set to an always increasing number.
248 -- It is 8 bytes so you should set a high
249 -- resolution monotonic time as the value.
250 , dhtpk :: PublicKey -- dht public key
251 , dhtpkNodes :: SendNodes -- other reachable nodes
252 }
253 deriving (Eq, Show)
254
255
256-- int8_t (0x20 sent over onion, 0x12 for sent over net_crypto)
257-- [uint32_t nospam][Message (UTF8) 1 to ONION_CLIENT_MAX_DATA_SIZE bytes]
258data FriendRequest = FriendRequest
259 { friendNoSpam :: Word32
260 , friendRequestText :: ByteString -- UTF8
261 }
262 deriving (Eq, Ord, Show)
263
264
265-- When sent as a DHT request packet (this is the data sent in the DHT request
266-- packet):
267--
268-- Length Contents
269-- :--------- :-------------------------------
270-- `1` `uint8_t` (0x9c)
271-- `32` Long term public key of sender
272-- `24` Nonce
273-- variable Encrypted payload
274data LongTermKeyWrap = LongTermKeyWrap
275 { wrapLongTermKey :: PublicKey
276 , wrapNonce :: Nonce24
277 , wrapData :: Encrypted DHTPublicKey
278 }
279 deriving Show
280
281instance Serialize LongTermKeyWrap where
282 get = LongTermKeyWrap <$> getPublicKey <*> get <*> get
283 put (LongTermKeyWrap key nonce dta) = putPublicKey key >> put nonce >> put dta
284
285
286instance Sized DHTPublicKey where
287 -- NOTE: 41 bytes includes the 1-byte tag 0x9c in the size.
288 -- WARNING: Serialize instance does not include this byte FIXME
289 size = VarSize $ \(DHTPublicKey _ _ nodes) -> 41 + case size of
290 ConstSize nodes -> nodes
291 VarSize sznodes -> sznodes nodes
292
293instance Sized Word32 where size = ConstSize 4
294
295-- FIXME: Inconsitently, this type does not include the 0x20 or 0x12 tag byte
296-- where the DHTPublicKey type does include its tag.
297instance Sized FriendRequest where
298 size = contramap friendNoSpam size <> contramap friendRequestText (VarSize B.length)
299
300instance Serialize DHTPublicKey where
301 -- TODO: This should agree with Sized instance.
302 get = DHTPublicKey <$> get <*> getPublicKey <*> get
303 put (DHTPublicKey nonce key nodes) = do
304 put nonce
305 putPublicKey key
306 put nodes
307
308instance Serialize FriendRequest where
309 get = FriendRequest <$> get <*> (remaining >>= getBytes)
310 put (FriendRequest nospam txt) = put nospam >> putByteString txt
311
312newtype GetNodes = GetNodes NodeId
313 deriving (Eq,Ord,Show,Read,S.Serialize)
314
315instance Sized GetNodes where
316 size = ConstSize 32 -- TODO This right?
317
318newtype SendNodes = SendNodes [NodeInfo]
319 deriving (Eq,Ord,Show,Read)
320
321instance Sized SendNodes where
322 size = VarSize $ \(SendNodes ns) -> case size of
323 ConstSize nodeFormatSize -> nodeFormatSize * length ns
324 VarSize nsize -> sum $ map nsize ns
325
326instance S.Serialize SendNodes where
327 get = do
328 cnt <- S.get :: S.Get Word8
329 ns <- sequence $ replicate (fromIntegral cnt) S.get
330 return $ SendNodes ns
331
332 put (SendNodes ns) = do
333 let ns' = take 4 ns
334 S.put (fromIntegral (length ns') :: Word8)
335 mapM_ S.put ns'
336
337data Ping = Ping deriving Show
338data Pong = Pong deriving Show
339
340instance S.Serialize Ping where
341 get = do w8 <- S.get
342 if (w8 :: Word8) /= 0
343 then fail "Malformed ping."
344 else return Ping
345 put Ping = S.put (0 :: Word8)
346
347instance S.Serialize Pong where
348 get = do w8 <- S.get
349 if (w8 :: Word8) /= 1
350 then fail "Malformed pong."
351 else return Pong
352 put Pong = S.put (1 :: Word8)
353
354newtype CookieRequest = CookieRequest PublicKey
355 deriving (Eq, Show)
356newtype CookieResponse = CookieResponse (Cookie Encrypted)
357 deriving (Eq, Show)
358
359data Cookie (f :: * -> *) = Cookie Nonce24 (f CookieData)
360
361deriving instance Eq (f CookieData) => Eq (Cookie f)
362deriving instance Ord (f CookieData) => Ord (Cookie f)
363deriving instance Show (f CookieData) => Show (Cookie f)
364deriving instance Generic (f CookieData) => Generic (Cookie f)
365
366instance Hashable (Cookie Encrypted)
367
368instance Sized (Cookie Encrypted) where size = ConstSize 112 -- 24 byte nonce + 88 byte cookie data
369
370instance Serialize (Cookie Encrypted) where
371 get = Cookie <$> get <*> get
372 put (Cookie nonce dta) = put nonce >> put dta
373
374data CookieData = CookieData -- 16 (mac)
375 { cookieTime :: Word64 -- 8
376 , longTermKey :: PublicKey -- 32
377 , dhtKey :: PublicKey -- + 32
378 } -- = 88 bytes when encrypted.
379 deriving (Show, Generic)
380
381instance Sized CookieData where
382 size = ConstSize 72
383
384instance Serialize CookieData where
385 get = CookieData <$> get <*> getPublicKey <*> getPublicKey
386 put (CookieData tm userkey dhtkey) = do
387 put tm
388 putPublicKey userkey
389 putPublicKey userkey
390
391instance Sized CookieRequest where
392 size = ConstSize 64 -- 32 byte key + 32 byte padding
393
394instance Serialize CookieRequest where
395 get = CookieRequest <$> getPublicKey <* {- padding -} getPublicKey
396 put (CookieRequest k) = putPublicKey k >> {- padding -} putPublicKey k
397
398forwardDHTRequests :: TransportCrypto -> (PublicKey -> IO (Maybe NodeInfo)) -> DHTTransport -> DHTTransport
399forwardDHTRequests crypto closeLookup dht = dht { awaitMessage = await' }
400 where
401 await' :: HandleHi a -> IO a
402 await' pass = awaitMessage dht $ \case
403 Just (Right (m@(DHTDHTRequest target payload),src)) | target /= transportPublic crypto
404 -> do mni <- closeLookup target
405 -- Forward the message if the target is in our close list.
406 forM_ mni $ \ni -> sendMessage dht ni m
407 await' pass
408 m -> pass m
409
410encrypt :: TransportCrypto -> DHTMessage ((,) Nonce8) -> NodeInfo -> IO (DHTMessage Encrypted8, NodeInfo)
411encrypt crypto msg ni = do
412 let cipher n plain = Composed $ encryptMessage crypto (id2key $ nodeId ni) n plain
413 m <- sequenceMessage $ transcode cipher msg
414 return (m, ni)
415
416encryptMessage :: Serialize a =>
417 TransportCrypto ->
418 PublicKey ->
419 Nonce24 -> Either (Nonce8,a) (Asymm (Nonce8,a)) -> IO (Encrypted8 a)
420encryptMessage crypto destKey n arg = do
421 let plain = encodePlain $ swap $ either id asymmData arg
422 secret <- lookupSharedSecret crypto (transportSecret crypto) destKey n
423 return $ E8 $ ToxCrypto.encrypt secret plain
424
425decrypt :: TransportCrypto -> DHTMessage Encrypted8 -> NodeInfo -> IO (Either String (DHTMessage ((,) Nonce8), NodeInfo))
426decrypt crypto msg ni = do
427 let decipher n c = Composed $ decryptMessage crypto n . left ((,) $ id2key $ nodeId ni) $ c
428 msg' <- sequenceMessage $ transcode decipher msg
429 return $ fmap (, ni) $ sequenceMessage msg'
430
431decryptMessage :: Serialize x =>
432 TransportCrypto
433 -> Nonce24
434 -> Either (PublicKey, Encrypted8 x) (Asymm (Encrypted8 x))
435 -> IO ((Either String ∘ ((,) Nonce8)) x)
436decryptMessage crypto n arg = do
437 let (remotekey,E8 e) = either id (senderKey &&& asymmData) arg
438 plain8 = Composed . fmap swap . (>>= decodePlain)
439 secret <- lookupSharedSecret crypto (transportSecret crypto) remotekey n
440 return $ plain8 $ ToxCrypto.decrypt secret e
441
442sequenceMessage :: Applicative m => DHTMessage (m ∘ f) -> m (DHTMessage f)
443sequenceMessage (DHTPing asym) = fmap DHTPing $ sequenceA $ fmap uncomposed asym
444sequenceMessage (DHTPong asym) = fmap DHTPong $ sequenceA $ fmap uncomposed asym
445sequenceMessage (DHTGetNodes asym) = fmap DHTGetNodes $ sequenceA $ fmap uncomposed asym
446sequenceMessage (DHTSendNodes asym) = fmap DHTSendNodes $ sequenceA $ fmap uncomposed asym
447sequenceMessage (DHTCookieRequest asym) = fmap DHTCookieRequest $ sequenceA $ fmap uncomposed asym
448sequenceMessage (DHTCookie n dta) = fmap (DHTCookie n) $ uncomposed dta
449sequenceMessage (DHTDHTRequest pubkey asym) = fmap (DHTDHTRequest pubkey) $ sequenceA $ fmap uncomposed asym
450sequenceMessage (DHTLanDiscovery nid) = pure $ DHTLanDiscovery nid
451
452transcode :: forall f g. (forall a. Serialize a => Nonce24 -> Either (f a) (Asymm (f a)) -> g a) -> DHTMessage f -> DHTMessage g
453transcode f (DHTPing asym) = DHTPing $ asym { asymmData = f (asymmNonce asym) (Right asym) }
454transcode f (DHTPong asym) = DHTPong $ asym { asymmData = f (asymmNonce asym) (Right asym) }
455transcode f (DHTGetNodes asym) = DHTGetNodes $ asym { asymmData = f (asymmNonce asym) (Right asym) }
456transcode f (DHTSendNodes asym) = DHTSendNodes $ asym { asymmData = f (asymmNonce asym) (Right asym) }
457transcode f (DHTCookieRequest asym) = DHTCookieRequest $ asym { asymmData = f (asymmNonce asym) (Right asym) }
458transcode f (DHTCookie n dta) = DHTCookie n $ f n $ Left dta
459transcode f (DHTDHTRequest pubkey asym) = DHTDHTRequest pubkey $ asym { asymmData = f (asymmNonce asym) (Right asym) }
460transcode f (DHTLanDiscovery nid) = DHTLanDiscovery nid