summaryrefslogtreecommitdiff
path: root/dht/src/Network/Tox/TCP.hs
blob: e3780675aa5721772e2e92c900abd925bb8db76d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleContexts #-}
module Network.Tox.TCP
    ( module Network.Tox.TCP
    , NodeInfo(..)
    ) where

import Debug.Trace
import Control.Arrow
import Control.Concurrent
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import Crypto.Random
import Data.Aeson (ToJSON(..),FromJSON(..))
import qualified Data.Aeson as JSON
import Data.ByteArray (withByteArray)
import Data.Functor.Contravariant
import Data.Functor.Identity
import Data.Hashable
import qualified Data.HashMap.Strict as HashMap
import qualified Data.IntMap.Strict as IntMap
import Data.IP
import Data.Maybe
import Data.Monoid
import Data.Serialize
import Data.Word
import qualified Data.Vector as Vector
import Foreign.Storable (peek)
import Network.Socket (SockAddr(..))
import qualified Text.ParserCombinators.ReadP as RP
import System.IO.Error
import System.IO.Unsafe (unsafeDupablePerformIO)
import System.Timeout

import ControlMaybe
import Crypto.Tox
import Data.ByteString (hPut,hGet,ByteString,length)
import Data.TableMethods
import Data.Tox.Relay
import qualified Data.Word64Map
import DebugTag
import DPut
import Network.Address (setPort,PortNumber,localhost4,fromSockAddr,nullAddress4)
import Network.Kademlia.Routing
import Network.Kademlia.Search hiding (sendQuery)
import Network.QueryResponse as QR
import Network.QueryResponse.TCP
import Network.Tox.TCP.NodeId ()
import Network.Tox.DHT.Transport (toxSpace)
import Network.Tox.Onion.Transport hiding (encrypt,decrypt)
import Network.Tox.Onion.Transport (unwrapAnnounceResponse)
import qualified Network.Tox.NodeId as UDP
import Text.XXD
import Data.Proxy

withSize :: Sized x => (Size x -> m (p x)) -> m (p x)
withSize f = case size of len -> f len


type NodeId = UDP.NodeId


nodeId :: NodeInfo -> NodeId
nodeId ni = UDP.nodeId $ udpNodeInfo ni

nodeAddr :: NodeInfo -> SockAddr
nodeAddr ni = setPort (tcpPort ni) $ UDP.nodeAddr $ udpNodeInfo ni

nodeIP :: NodeInfo -> IP
nodeIP ni = UDP.nodeIP $ udpNodeInfo ni

tcpStream :: (Show y, Show x, Serialize y, Sized y, Serialize x, Sized x) =>
                   TransportCrypto -> (NodeInfo -> IO st) -> StreamHandshake NodeInfo (st,x) y
tcpStream crypto mkst = StreamHandshake
    { streamHello = \addr h -> do
        (skey, hello) <- atomically $ do
            n24 <- transportNewNonce crypto
            skey <- transportNewKey crypto
            base24 <- transportNewNonce crypto
            return $ (,) skey $ Hello $ Asymm
                { senderKey  = transportPublic crypto
                , asymmNonce = n24
                , asymmData  = pure HelloData
                                { sessionPublicKey = toPublic $ skey
                                , sessionBaseNonce = base24
                                }
                }
        noncef <- lookupNonceFunction crypto (transportSecret crypto) (UDP.id2key $ nodeId addr)
        dput XTCP $ "TCP:" ++ show addr ++ " <-- " ++ show hello
        hPut h $ encode $ encryptPayload (noncef $ helloNonce hello) hello
        (welcomeE, wbs) <- do
            let sz0 = size
                sz = constSize sz0
            bs <- hGet h sz
            return ( fmap (`asProxyTypeOf` sz0) $ decode bs, bs )
        let mwelcome = welcomeE >>= \w -> decryptPayload (noncef $ welcomeNonce w) w
            nil = SessionProtocol
                { streamGoodbye = return ()
                , streamDecode = return Nothing
                , streamEncode = \y -> dput XTCP $ "TCP nil <-- " ++ show y
                }
        either (\e -> do
                        dput XTCP $ "welcome: " ++ show (Data.ByteString.length wbs) ++ " bytes."
                        forM_ (xxd2 0 wbs) $ dput XTCP
                        dput XTCP $ "TCP(fail welcome): " ++ e
                        return nil
                ) id $ mwelcome <&> \welcome -> do
        dput XTCP $ "TCP:" ++ show addr ++ " --> " ++ show welcome
        noncef' <- lookupNonceFunction crypto skey (sessionPublicKey $ runIdentity $ welcomeData welcome)
        nsend <- newMVar (sessionBaseNonce $ runIdentity $ helloData hello)
        nread <- newMVar (sessionBaseNonce $ runIdentity $ welcomeData welcome)
        let them = sessionPublicKey $ runIdentity $ welcomeData welcome
        hvar <- newMVar h
        st <- mkst addr
        return SessionProtocol
            { streamGoodbye = do
                dput XTCP $ "Closing " ++ show addr
                return () -- No goodbye packet?  Seems rude.
            , streamDecode =
                let go h = decode <$> hGet h 2 >>= \case
                        Left e    -> do
                            dput XTCP $ "TCP: (" ++ show addr ++ ") Failed to get length: " ++ e
                            return Nothing
                        Right len -> do
                            decode <$> hGet h (fromIntegral (len :: Word16)) >>= \case
                                Left e  -> do
                                    dput XTCP $ "TCP: Failed to decode packet."
                                    return Nothing
                                Right x -> do
                                    dput XTCP $ "TCP:"++ show addr ++ " --> packet!"
                                    m24 <- timeout 1000000 (takeMVar nread)
                                    fmap join $ forM m24 $ \n24 -> do
                                        let r = decrypt (noncef' n24) x >>= decodePlain
                                        putMVar nread (incrementNonce24 n24)
                                        either (dput XTCP . ("TCP decryption: " ++))
                                               (\x' -> do
                                                    dput XTCP $ "TCP:" ++ show addr ++ " --> " ++ show x'
                                                    return ())
                                               r
                                        return $ either (const Nothing) (Just . (,) st) r
                 in bracket (takeMVar hvar) (putMVar hvar)
                     $ \h -> go h `catchIOError` \e -> do
                                        dput XTCP $ "TCP exception: " ++ show e
                                        return Nothing
            , streamEncode = \y -> do
                -- We need this to throw so the tcp session state can be cleaned up elsewhere.
                bracket (takeMVar nsend) (putMVar nsend . incrementNonce24)
                    $ \n24 -> do
                        let bs = encode $ encrypt (noncef' n24) $ encodePlain y
                        hPut h (encode (fromIntegral $ Data.ByteString.length bs :: Word16) <> bs)
                        dput XTCP $ "TCP: " ++ show addr ++ " <-- " ++ show y
            }
    , streamAddr = nodeAddr
    }

newtype SessionData = SessionData (TMVar (IntMap.IntMap NodeId))

newSessionData :: NodeInfo -> IO SessionData
newSessionData _ = atomically $ SessionData <$> newTMVar IntMap.empty

getRelayedRemote :: SessionData -> ConId -> STM NodeId
getRelayedRemote (SessionData keymapVar) (ConId i) = do
    keymap <- takeTMVar keymapVar
    let k = fromMaybe UDP.zeroID $ IntMap.lookup (fromIntegral i) keymap
    putTMVar keymapVar keymap
    return k

setRelayedRemote :: SessionData -> ConId -> NodeId -> STM ()
setRelayedRemote (SessionData keymapVar) (ConId conid) nid = do
    keymap <- takeTMVar keymapVar
    putTMVar keymapVar $ IntMap.insert (fromIntegral conid) nid keymap

toxTCP :: TransportCrypto -> IO ( TCPCache (SessionProtocol (SessionData,RelayPacket) RelayPacket)
                                , TransportA err NodeInfo (SessionData,RelayPacket) (Bool,RelayPacket) )
toxTCP crypto = tcpTransport 30 (tcpStream crypto newSessionData)

tcpSpace :: KademliaSpace NodeId NodeInfo
tcpSpace = contramap udpNodeInfo toxSpace

{-
nodeSearch :: TCPClient err () Nonce8 -> Search NodeId (IP, PortNumber) () NodeInfo NodeInfo
nodeSearch tcp = Search
    { searchSpace       = tcpSpace
    , searchNodeAddress = nodeIP &&& tcpPort
    , searchQuery       = getNodes tcp
    }
-}

data TCPClient err tid = TCPClient
    { tcpCrypto     :: TransportCrypto
    , tcpClient     :: Client err PacketNumber tid NodeInfo (Bool,RelayPacket)
    , tcpGetGateway :: UDP.NodeInfo -> STM (Maybe NodeInfo)
    }

{-
getTCPNodes :: TCPClient err () Nonce8 -> NodeId -> NodeInfo -> IO (Maybe ([NodeInfo],[NodeInfo],Maybe ()))
getTCPNodes tcp seeking dst = do
    r <- getUDPNodes' tcp seeking (udpNodeInfo dst)
    let tcps (ns,_,mb) = (ns',ns',mb)
         where ns' = do
                    n <- ns
                    [ NodeInfo n (fromIntegral 443) , NodeInfo n (fromIntegral 80) , NodeInfo n (UDP.nodePort n) ]
    fmap join $ forM r $ \(ns,gw) -> do
        let ts = tcps ns
        {-
        if nodeId gw == nodeId dst
            then return $ Just ts
            else do
                forkIO $ void $ tcpPing (tcpClient tcp) dst
                return $ Just ts
        -}
        forM_ ((\(xs,_,_) -> xs) ts) (forkIO . void . tcpPing (tcpClient tcp))
        return $ Just ts
-}

getUDPNodes :: TCPClient err Nonce8 -> NodeId -> UDP.NodeInfo -> IO (Maybe ([UDP.NodeInfo], [UDP.NodeInfo], Maybe ()))
getUDPNodes tcp seeking dst = fmap fst . resultToMaybe <$> getUDPNodes' tcp seeking dst


getUDPNodes' :: TCPClient err Nonce8 -> NodeId -> UDP.NodeInfo -> IO (QR.Result (([UDP.NodeInfo], [UDP.NodeInfo], Maybe ()), NodeInfo))
getUDPNodes' tcp seeking dst0 = do
    goGetUDPNodes tcp seeking dst0 (return Canceled) $ \meth gateway dst -> do
        r <- sendQuery (tcpClient tcp) meth (AnnounceRequest zeros32 seeking UDP.zeroID) gateway
        forM r $ \response -> do
            let (ns,_,mb) = either (const ([],[],Nothing)) (unwrapAnnounceResponse Nothing dst) $ response
            return ( (ns,ns, const () <$> mb), gateway )

-- Failure case, currently not treated as special.
-- The current searchQuery type demands a valid Nonce8 is returned
-- even if we were unable to send a query.
fixmeNonce :: Nonce8
fixmeNonce = Nonce8 0

asyncUDPNodes :: TCPClient err Nonce8
                       -> NodeId
                       -> UDP.NodeInfo
                       -> (Nonce8
                           -> QR.Result (([UDP.NodeInfo], [UDP.NodeInfo], Maybe ()), NodeInfo)
                           -> IO ())
                       -> IO Nonce8
asyncUDPNodes tcp seeking dst0 withResult =
    goGetUDPNodes tcp seeking dst0 (return fixmeNonce) $ \meth gateway dst -> do
        asyncQuery (tcpClient tcp) meth (AnnounceRequest zeros32 seeking UDP.zeroID) gateway $
            \qid response -> do
                let wut response =
                        let (ns,_,mb) = either (const ([],[],Nothing)) (unwrapAnnounceResponse Nothing dst) $ response
                        in ( (ns,ns, const () <$> mb), gateway )
                withResult qid $ fmap wut response

type Meth x = MethodSerializer
                        Nonce8
                        x -- NodeInfo
                        (Bool, RelayPacket)
                        PacketNumber
                        AnnounceRequest
                        (Either String AnnounceResponse)

goGetUDPNodes :: TCPClient err Nonce8 -> NodeId -> UDP.NodeInfo
                 -> IO a
                 -> (Meth x -> NodeInfo -> UDP.NodeInfo -> IO a)
                 -> IO a
goGetUDPNodes tcp seeking dst0 fail go = do
    mgateway <- atomically $ tcpGetGateway tcp dst0
    case mgateway of
      Nothing -> fail
      Just gateway -> do
        (b,c,n24) <- atomically $ do
            b <- transportNewKey (tcpCrypto tcp)
            c <- transportNewKey (tcpCrypto tcp)
            n24 <- transportNewNonce (tcpCrypto tcp)
            return (b,c,n24)
        let (dst,gateway') = if UDP.nodeId dst0 == nodeId gateway
                                then ( dst0 { UDP.nodeIP = fromJust $ Network.Address.fromSockAddr localhost4 }
                                     , gateway { udpNodeInfo = (udpNodeInfo gateway)
                                                    { UDP.nodeIP = fromJust $ Network.Address.fromSockAddr localhost4 }})
                                else (dst0,gateway)
        wrap2 <- lookupNonceFunction (tcpCrypto tcp) b (UDP.id2key $ UDP.nodeId dst)
        wrap1 <- lookupNonceFunction (tcpCrypto tcp) c (UDP.id2key $ nodeId gateway)
        wrap0 <- lookupNonceFunction (tcpCrypto tcp) (transportSecret $ tcpCrypto tcp) (UDP.id2key $ UDP.nodeId dst)
        let meth :: MethodSerializer
                        Nonce8
                        a -- NodeInfo
                        (Bool, RelayPacket)
                        PacketNumber
                        AnnounceRequest
                        (Either String AnnounceResponse)
            meth = MethodSerializer
                    { methodTimeout = \addr -> return (addr,12000000) -- 12 second timeout
                    , method = OnionPacketID -- meth
                    , wrapQuery = \n8 src gateway x -> (,) True $
                        OnionPacket n24 $ Addressed (UDP.nodeAddr dst)
                            $ wrapOnionPure b (wrap2 n24) (UDP.nodeAddr $ udpNodeInfo gateway')
                                $ wrapOnionPure c (wrap1 n24) (UDP.nodeAddr dst)
                                    $ NotForwarded $ encryptPayload (wrap0 n24)
                                       $ OnionAnnounce Asymm
                                            { senderKey  = transportPublic (tcpCrypto tcp)
                                            , asymmNonce = n24
                                            , asymmData  = pure (x,n8)
                                            }
                    , unwrapResponse = \case
                        (_,OnionPacketResponse (OnionAnnounceResponse _ n24' r))
                            -> decrypt (wrap0 n24') r >>= decodePlain
                        x   -> Left $ "getUDPNodes: unwrapResponse fail " ++ show x
                    }
        go meth gateway dst


handleOOB :: PublicKey -> ByteString -> NodeInfo -> NodeInfo -> IO (Maybe (x -> x))
handleOOB k bs src dst = do
    dput XMisc $ "TODO: handleOOB " ++ show src
    return Nothing

handle2route :: OnionMessage Encrypted -> NodeInfo -> NodeInfo -> IO (Maybe (x -> x))
handle2route o src dst = do
    dput XMisc $ "TODO: handle2route " ++ show src
    return Nothing

tcpPing :: Show addr => Client err PacketNumber Nonce8 addr (Bool,RelayPacket) -> addr -> IO (Maybe ())
tcpPing client dst = do
    dput XTCP $ "tcpPing " ++ show dst
    resultToMaybe <$> sendQuery client meth () dst
 where meth = MethodSerializer
                { wrapQuery      = \n8 src dst () -> (True,RelayPing n8)
                , unwrapResponse = \_ -> ()
                , methodTimeout  = \dst -> return (dst,5000000)
                , method         = PingPacket
                }

sendConnectionRequest :: Client err PacketNumber tid addr (Bool, RelayPacket)
                            -> PublicKey -> addr -> IO ()
sendConnectionRequest client pubkey ni =
    sendMessage (clientNet client) ni (True,RoutingRequest pubkey)

tcpConnectionRequest_ :: Client err PacketNumber tid addr (Bool, RelayPacket)
                        -> PublicKey -> addr -> IO (Maybe ConId)
tcpConnectionRequest_ client pubkey ni = do
    resultToMaybe <$> sendQuery client meth pubkey ni
 where
    meth = MethodSerializer
        { wrapQuery      = \n8 src dst pubkey -> (True,RoutingRequest pubkey)
        , unwrapResponse = \(_,RoutingResponse cid pubkey) -> cid
        , methodTimeout  = \dst -> return (dst,5000000)
        , method         = RoutingRequestPacket
        }

type RelayClient = Client String PacketNumber Nonce8 NodeInfo (Bool,RelayPacket)

keyToNonce :: PublicKey -> Nonce8
keyToNonce k = unsafeDupablePerformIO $ withByteArray k $ \ptr -> do
    w8 <- peek ptr
    return $ Nonce8 w8

type RelayCache = TCPCache (SessionProtocol (SessionData,RelayPacket) RelayPacket)

-- | Create a new TCP relay client.  Because polymorphic existential record
-- updates are currently hard with GHC, this function accepts parameters for
-- generalizing the table-entry type for pending transactions.  Safe trivial
-- defaults are 'id' and 'tryPutMVar'.  The resulting customized table state
-- will be returned to the caller along with the new client.
newClient :: TransportCrypto
               -> ((Nonce8 -> QR.Result (Bool,RelayPacket) -> IO ()) -> a)      -- ^ store mvar for relay query
               -> (a -> Nonce8 -> RelayPacket -> IO void)                       -- ^ load mvar for relay query
               -> (SockAddr -> Nonce8 -> STM (Maybe (OnionDestination RouteId))) -- ^ lookup sender of onion query
               -> (UDP.NodeInfo -> RouteId -> IO (Maybe OnionRoute))            -- ^ lookup OnionRoute by id
               -> IO ( ( TVar (ChaChaDRG, Data.Word64Map.Word64Map a)
                       , RelayCache
                       , Transport String ViaRelay ByteString
                       , Transport String (OnionDestination RouteId) (OnionMessage Encrypted) )
                     , RelayClient )
newClient crypto store load lookupSender getRoute = do
    let runio io = return () -- TODO: run IO action
    (tcpcache,net0) <- toxTCP crypto
    (relaynet,net1) <- partitionRelay runio net0
    (onionnet,net2) <- partitionOnion crypto lookupSender getRoute net1
    let net3 = {- XXX: Client type forces this pointless layering. -}
               layerTransport ((Right .) . (,) . (,) False . snd) (,) net2
    drg <- drgNew
    map_var <- atomically $ newTVar (drg, Data.Word64Map.empty)
    return $ (,) (map_var,tcpcache,relaynet,onionnet) Client
        { clientNet = net3
        , clientDispatcher = DispatchMethods
            { classifyInbound = (. snd) $ \case
                RelayPing n             -> IsQuery PingPacket n
                RelayPong n             -> IsResponse n
                RoutingRequest k        -> IsQuery RoutingRequestPacket (keyToNonce k)
                RoutingResponse conId k -> IsResponse (keyToNonce k)
                OnionPacketResponse (OnionAnnounceResponse n8 n24 ciphered) -> IsResponse n8
                OnionPacketResponse o@(OnionToRouteResponse _) -> IsUnsolicited $ handle2route o
                OOBRecv k bs            -> IsUnsolicited $ handleOOB k bs
                wut                     -> IsUnknown (show wut)
            , lookupHandler   = \case
                PingPacket -> Just MethodHandler
                    { methodParse     = \case (_,RelayPing n8) -> Right ()
                                              _                -> Left "TCP: Non-ping?"
                    , methodSerialize = \n8 src dst () -> (False, RelayPong n8)
                    , methodAction    = \src () -> dput XTCP $ "TCP pinged by "++show src
                    }
                w -> Just NoReply
                            { methodParse = \x -> Left $ "tcp-lookuphandler? " ++ (concat $ take 1 $ words $ show x) -- :: x -> Either err a
                            , noreplyAction = \addr a -> dput XTCP $ "tcp-lookupHandler: "++show w
                            }
            , tableMethods = transactionMethods'
                                store
                                (\qid x -> mapM_ (load qid x . snd))
                                (contramap (\(Nonce8 w64) -> w64) w64MapMethods)
                                $ first (either error Nonce8 . decode) . randomBytesGenerate 8
            }
        , clientErrorReporter = logErrors
        , clientPending = map_var
        , clientAddress = \_ -> return $ NodeInfo
            { udpNodeInfo = either error id $ UDP.nodeInfo (UDP.key2id $ transportPublic crypto) (SockAddrInet 0 0)
            , tcpPort = 0
            }
        , clientResponseId = return
        }

data ViaRelay = ViaRelay (Maybe ConId) UDP.NodeId NodeInfo
 deriving (Eq,Ord,Show)

showViaRelay :: ViaRelay -> String
showViaRelay (ViaRelay mcon nid tcp) =
    "TCP:" ++ maybe "(oob)" (\(ConId con) -> "(" ++ show con ++ ")") mcon
           ++ show nid ++ "@@" ++ show (nodeAddr tcp)

partitionRelay :: (IO () -> STM ())
                      -> TransportA err NodeInfo (SessionData,RelayPacket) (Bool,RelayPacket)
                      -> IO ( Transport err ViaRelay ByteString
                            , TransportA err NodeInfo (SessionData,RelayPacket) (Bool,RelayPacket))
partitionRelay runio tr = partitionTransportM parse encode tr
  where
    parse :: ((SessionData,RelayPacket), NodeInfo) -> STM (Either (ByteString, ViaRelay) ((SessionData,RelayPacket),NodeInfo))
    parse ((st,RelayData bs conid), ni) = do
        nid <- getRelayedRemote st conid
        return $ Left (bs, ViaRelay (Just conid) nid ni)
    parse ((_,OOBRecv key bs), ni) =
        return $ Left (bs, ViaRelay Nothing (UDP.key2id key) ni)
    parse ((st,RoutingResponse conid k),ni) = do
        setRelayedRemote st conid (UDP.key2id k)
        -- Note: Rewriting inbound RoutingResponse to be a RoutingRequest
        -- instead.  This is because the routing reqest is not yet fullfilled
        -- until a ConnectNotification is received.
        --
        -- We could use Left here instead as inbound RoutingRequest packets are
        -- not normally responded to by a client.
        return $ Right ((st,RoutingRequest k),ni)
    parse ((st,ConnectNotification conid),ni) = do
        nid <- getRelayedRemote st conid
        -- Note: Rewriting inbound ConnectNotification to a RoutingResponse
        -- because we want to include the public key and connection id in a
        -- single message.
        return $ Right ((st,RoutingResponse conid (UDP.id2key nid)),ni)
    parse passthrough =
        return $ Right passthrough

    encode :: (ByteString, ViaRelay) -> IO (Maybe ((Bool,RelayPacket), NodeInfo))
    encode (bs, ViaRelay (Just conid) _ ni) = return $ Just ((False,RelayData bs conid), ni)
    encode (bs, ViaRelay Nothing    nid ni) = return $ Just ((True,OOBSend (UDP.id2key nid) bs), ni)


partitionOnion :: TransportCrypto
                  -> (SockAddr -> Nonce8 -> STM (Maybe (OnionDestination RouteId)))
                  -> (UDP.NodeInfo -> RouteId -> IO (Maybe OnionRoute))
                  -> TransportA err NodeInfo (SessionData,RelayPacket) (Bool,RelayPacket)
                  -> IO ( Transport err (OnionDestination RouteId) (OnionMessage Encrypted)
                        , TransportA err NodeInfo (SessionData,RelayPacket) (Bool,RelayPacket))
partitionOnion crypto lookupSender getRoute tr = partitionTransportM parse encode tr
 where
    parse :: ((SessionData,RelayPacket), NodeInfo)
                -> STM (Either (OnionMessage Encrypted   , OnionDestination RouteId)
                               ((SessionData,RelayPacket), NodeInfo))
    parse pass@((_,OnionPacketResponse msg@(OnionAnnounceResponse n8 _ _)), nodeA) = do
        m <- lookupSender (nodeAddr nodeA) n8
        case m of
            Nothing -> return $ Right pass
            Just od -> return $ Left (msg, od)
    parse ((_,OnionPacketResponse msg@(OnionToRouteResponse asym)), nodeA) = do
        -- dput XOnion $ "TCP data-to-route response from " ++ show (UDP.key2id $ senderKey asym)
        return $
            let Right ni = UDP.nodeInfo (UDP.key2id $ senderKey asym) nullAddress4
                -- -- We have this information, but currently, we're discarding it...
                -- r = dummyRoute { routeNodeA     = udpNodeInfo nodeA
                --                , routeRelayPort = Just $ tcpPort nodeA }
                tryAllKeys = SearchingAlias -- We unfortunately don't know what toxid was used to encrypt this.
                                            -- Toxcore only supports a single toxid per DHT node and in that case,
                                            -- it is unambiguous.
            in Left (msg, OnionDestination tryAllKeys ni Nothing)
    parse pass = return $ Right pass

    encode :: (OnionMessage Encrypted,OnionDestination RouteId) -> IO (Maybe ((Bool,RelayPacket),NodeInfo))
    encode (msg,OnionDestination _ ni (Just rid)) = do
        moroute <- getRoute ni rid
        forM (moroute >>= \r -> (,) r <$> routeRelayPort r) $ \(oroute,tcpport) ->
            wrapIndirectHops crypto msg ni oroute $ \nonce saddr fwd ->
                return ( (True,OnionPacket nonce $ Addressed saddr fwd)
                       , NodeInfo (routeNodeA oroute) tcpport )
    encode _ = return Nothing