summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/toxrelay.hs173
-rw-r--r--src/Data/Tox/Relay.hs56
2 files changed, 189 insertions, 40 deletions
diff --git a/examples/toxrelay.hs b/examples/toxrelay.hs
index fdf0c011..f03605f9 100644
--- a/examples/toxrelay.hs
+++ b/examples/toxrelay.hs
@@ -1,20 +1,30 @@
1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE RecordWildCards #-}
1{-# LANGUAGE ScopedTypeVariables #-} 3{-# LANGUAGE ScopedTypeVariables #-}
2 4
5import Control.Concurrent.MVar
6import Control.Concurrent.STM
3import Control.Exception 7import Control.Exception
4import Control.Monad 8import Control.Monad
5import Control.Concurrent.STM
6import qualified Data.ByteString as B 9import qualified Data.ByteString as B
7import Data.Function 10import Data.Function
8import qualified Data.IntMap as IntMap 11import Data.Functor.Identity
9 ;import Data.IntMap (IntMap) 12import qualified Data.IntMap as IntMap
13 ;import Data.IntMap (IntMap)
14import qualified Data.Map as Map
15 ;import Data.Map (Map)
10import Data.Serialize 16import Data.Serialize
17import Data.Word
11import System.IO 18import System.IO
12import Data.Functor.Identity 19import System.IO.Error
20import System.Timeout
13 21
14import Crypto.Tox 22import Crypto.Tox
23import qualified Data.IntervalSet as IntSet
24 ;import Data.IntervalSet (IntSet)
15import Data.Tox.Relay 25import Data.Tox.Relay
16import Network.StreamServer
17import Network.Address (getBindAddress) 26import Network.Address (getBindAddress)
27import Network.StreamServer
18import Network.Tox (newCrypto) 28import Network.Tox (newCrypto)
19 29
20 30
@@ -22,16 +32,49 @@ import Network.Tox (newCrypto)
22hGetPrefixed :: Serialize a => Handle -> IO (Either String a) 32hGetPrefixed :: Serialize a => Handle -> IO (Either String a)
23hGetPrefixed h = do 33hGetPrefixed h = do
24 mlen <- runGet getWord16be <$> B.hGet h 2 34 mlen <- runGet getWord16be <$> B.hGet h 2
35 -- We treat parse-fail the same as EOF.
25 fmap join $ forM mlen $ \len -> runGet get <$> B.hGet h (fromIntegral len) 36 fmap join $ forM mlen $ \len -> runGet get <$> B.hGet h (fromIntegral len)
26 37
27hGetSized :: forall x. (Sized x, Serialize x) => Handle -> IO (Either String x) 38hGetSized :: forall x. (Sized x, Serialize x) => Handle -> IO (Either String x)
28hGetSized h = runGet get <$> B.hGet h len 39hGetSized h = runGet get <$> B.hGet h len -- We treat parse-fail the same as EOF.
29 where 40 where
30 ConstSize len = size :: Size x 41 ConstSize len = size :: Size x
31 42
32relaySession :: TransportCrypto -> TVar (IntMap Handle) -> (RelayPacket -> IO ()) -> sock -> Int -> Handle -> IO () 43data RelaySession = RelaySession
33relaySession crypto cons dispatch _ conid h = do 44 { indexPool :: IntSet -- ^ Ints that are either solicited or associated.
34 atomically $ modifyTVar' cons $ IntMap.insert conid h 45 , solicited :: Map PublicKey Int -- ^ Reserved ids, not yet in associated.
46 , associated :: IntMap (RelayPacket -> IO ()) -- ^ Peers this session is connected to.
47 }
48
49freshSession :: RelaySession
50freshSession = RelaySession
51 { indexPool = IntSet.empty
52 , solicited = Map.empty
53 , associated = IntMap.empty
54 }
55
56disconnect :: TVar (Map PublicKey (RelayPacket -> IO (),TVar RelaySession))
57 -> PublicKey
58 -> IO ()
59disconnect cons who = join $ atomically $ do
60 Map.lookup who <$> readTVar cons
61 >>= \case
62 Nothing -> return $ return ()
63 Just (_,session) -> do
64 modifyTVar' cons $ Map.delete who
65 RelaySession { associated = cs } <- readTVar session
66 return $ let notifyPeer i send = (send (DisconnectNotification $ key2c i) >>)
67 in IntMap.foldrWithKey notifyPeer (return ()) cs
68
69relaySession :: TransportCrypto
70 -> TVar (Map PublicKey (RelayPacket -> IO (),TVar RelaySession))
71 -> sock
72 -> Int
73 -> Handle
74 -> IO ()
75relaySession crypto cons _ conid h = do
76 -- atomically $ modifyTVar' cons $ IntMap.insert conid h
77
35 -- mhello <- fmap (>>= \h -> decryptPayload (computeSharedSecret me (helloFrom h) (helloNonce h)) h) $ hGetSized h 78 -- mhello <- fmap (>>= \h -> decryptPayload (computeSharedSecret me (helloFrom h) (helloNonce h)) h) $ hGetSized h
36 79
37 (hGetSized h >>=) $ mapM_ $ \helloE -> do 80 (hGetSized h >>=) $ mapM_ $ \helloE -> do
@@ -41,38 +84,124 @@ relaySession crypto cons dispatch _ conid h = do
41 84
42 noncef <- lookupNonceFunction crypto me them 85 noncef <- lookupNonceFunction crypto me them
43 let mhello = decryptPayload (noncef $ helloNonce helloE) helloE 86 let mhello = decryptPayload (noncef $ helloNonce helloE) helloE
44
45 forM_ mhello $ \hello -> do 87 forM_ mhello $ \hello -> do
88 let _ = hello :: Hello Identity
46 89
47 (me',welcome) <- atomically $ do 90 (me',welcome) <- atomically $ do
48 skey <- transportNewKey crypto 91 skey <- transportNewKey crypto
49 dta <- HelloData (toPublic skey) <$> transportNewNonce crypto 92 dta <- HelloData (toPublic skey) <$> transportNewNonce crypto
50 w24 <- transportNewNonce crypto 93 w24 <- transportNewNonce crypto
51 return (skey, Welcome w24 $ pure dta) 94 return (skey, Welcome w24 $ pure dta)
95
52 B.hPut h $ encode $ encryptPayload (noncef $ welcomeNonce welcome) welcome 96 B.hPut h $ encode $ encryptPayload (noncef $ welcomeNonce welcome) welcome
53 97
54 let them' = sessionPublicKey (runIdentity $ helloData hello) 98 noncef' <- let them' = sessionPublicKey (runIdentity $ helloData hello)
55 noncef' <- lookupNonceFunction crypto me' them' 99 in lookupNonceFunction crypto me' them'
100
101 sendPacket <- do
102 v <- newMVar (sessionBaseNonce $ runIdentity $ welcomeData welcome)
103 return $ \p -> do
104 n24 <- takeMVar v
105 let bs = encode $ encrypt (noncef' n24) $ encodePlain (p :: RelayPacket)
106 do B.hPut h $ encode (fromIntegral (B.length bs) :: Word16)
107 B.hPut h bs
108 `catchIOError` \_ -> return ()
109 putMVar v (incrementNonce24 n24)
110
111 let readPacket n24 = (>>= decrypt (noncef' n24) >=> decodePlain) <$> hGetPrefixed h
112 base = sessionBaseNonce $ runIdentity $ helloData hello
113
114 -- You get 3 seconds to send a session packet.
115 mpkt0 <- join <$> timeout 3000000 (either (const Nothing) Just <$> readPacket base)
116 forM_ mpkt0 $ \pkt0 -> do
117
118 disconnect cons (helloFrom hello)
119 session <- atomically $ do
120 session <- newTVar freshSession
121 modifyTVar' cons $ Map.insert (helloFrom hello) (sendPacket,session)
122 return session
123
124 handlePacket cons (helloFrom hello) sendPacket session pkt0
125
126 flip fix (incrementNonce24 base) $ \loop n24 -> do
127 m <- readPacket n24
128 forM_ m $ \p -> do
129 handlePacket cons (helloFrom hello) sendPacket session p
130 loop (incrementNonce24 n24)
131 `finally`
132 disconnect cons (helloFrom hello)
133
134data R = R { routingRequest :: PublicKey -> IO ConId
135 , reply :: RelayPacket -> IO ()
136 , routeOOB :: PublicKey -> IO (Maybe (RelayPacket -> IO ()))
137 }
138
139handlePacket :: TVar (Map PublicKey (RelayPacket -> IO (), TVar RelaySession))
140 -> PublicKey
141 -> (RelayPacket -> IO ())
142 -> TVar RelaySession
143 -> RelayPacket
144 -> IO ()
145handlePacket cons me sendToMe session = \case
146 RoutingRequest them -> join $ atomically $ do
147 mySession <- readTVar session
148 mi <- case Map.lookup them (solicited mySession) of
149 Nothing -> fmap join $ forM (IntSet.nearestOutsider 0 (indexPool mySession)) $ \i -> do
150 if -120 <= i && i <= 119
151 then do
152 writeTVar session mySession
153 { indexPool = IntSet.insert i (indexPool mySession)
154 , solicited = Map.insert them i (solicited mySession)
155 }
156 return $ Just i
157 else return Nothing -- No more slots available.
158 Just i -> return $ Just i
159 notifyConnect <- fmap (join . join) $ forM mi $ \i -> do
160 mp <- Map.lookup them <$> readTVar cons
161 forM mp $ \(sendToThem,peer) -> do
162 theirSession <- readTVar peer
163 forM (Map.lookup me $ solicited theirSession) $ \reserved_id -> do
164 writeTVar peer theirSession
165 { solicited = Map.delete me (solicited theirSession)
166 , associated = IntMap.insert reserved_id sendToMe (associated theirSession)
167 }
168 writeTVar session mySession
169 { solicited = Map.delete them (solicited mySession)
170 , associated = IntMap.insert i sendToThem (associated mySession)
171 }
172 return $ do sendToThem $ ConnectNotification (key2c reserved_id)
173 sendToMe $ ConnectNotification (key2c i)
174 return $ do sendToMe $ RoutingResponse (maybe badcon key2c mi) them
175 sequence_ notifyConnect
56 176
57 let _ = hello :: Hello Identity 177 RelayPing x -> sendToMe $ RelayPong x -- TODO x==0 is invalid. Do we care?
58 flip fix (sessionBaseNonce $ runIdentity $ helloData hello) $ \loop n24 -> do 178
59 m <- (>>= decrypt (noncef' n24) >=> decodePlain) <$> hGetPrefixed h 179 OOBSend them bs -> do
60 forM_ m $ \p -> do 180 m <- atomically $ Map.lookup them <$> readTVar cons
61 dispatch p 181 forM_ m $ \(sendToThem,_) -> sendToThem $ OOBRecv me bs
62 loop (incrementNonce24 n24) 182
63 `finally` 183 RelayData con bs -> join $ atomically $ do
64 atomically (modifyTVar' cons $ IntMap.delete conid) 184 -- Data: Data packets can only be sent and received if the
185 -- corresponding connection_id is connection (a Connect notification
186 -- has been received from it) if the server receives a Data packet for
187 -- a non connected or existent connection it will discard it.
188 mySession <- readTVar session
189 return $ sequence_ $ do
190 i <- c2key con
191 sendToThem <- IntMap.lookup i $ associated mySession
192 return $ sendToThem $ RelayData _todo bs
65 193
194 _ -> return ()
66 195
67 196
68main :: IO () 197main :: IO ()
69main = do 198main = do
70 crypto <- newCrypto 199 crypto <- newCrypto
71 cons <- newTVarIO IntMap.empty 200 cons <- newTVarIO Map.empty
72 a <- getBindAddress "33445" True 201 a <- getBindAddress "33445" True
73 h <- streamServer ServerConfig 202 h <- streamServer ServerConfig
74 { serverWarn = hPutStrLn stderr 203 { serverWarn = hPutStrLn stderr
75 , serverSession = relaySession crypto cons print 204 , serverSession = relaySession crypto cons
76 } 205 }
77 a 206 a
78 207
diff --git a/src/Data/Tox/Relay.hs b/src/Data/Tox/Relay.hs
index bd0e5968..f801d1cd 100644
--- a/src/Data/Tox/Relay.hs
+++ b/src/Data/Tox/Relay.hs
@@ -1,8 +1,9 @@
1{-# LANGUAGE DeriveDataTypeable #-} 1{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE FlexibleInstances #-} 2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE KindSignatures #-} 3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE MultiParamTypeClasses #-} 4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE ConstraintKinds #-} 5{-# LANGUAGE KindSignatures #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
6module Data.Tox.Relay where 7module Data.Tox.Relay where
7 8
8import Data.ByteString as B 9import Data.ByteString as B
@@ -16,11 +17,30 @@ import qualified Rank2
16import Crypto.Tox 17import Crypto.Tox
17import Network.Tox.Onion.Transport 18import Network.Tox.Onion.Transport
18 19
20newtype ConId = ConId Word8
21 deriving (Eq,Show,Ord,Data,Serialize)
22
23badcon :: ConId
24badcon = ConId 0
25
26-- Maps to a range -120 .. 119
27c2key :: ConId -> Maybe Int
28c2key (ConId x) | x < 16 = Nothing
29 | otherwise = Just $ case divMod (x - 15) 2 of
30 (q,0) -> negate $ fromIntegral q
31 (q,1) -> fromIntegral q
32
33-- Maps to range 16 .. 255
34-- negatives become odds
35key2c :: Int -> ConId
36key2c y = ConId $ if y < 0 then 15 + fromIntegral (negate y * 2)
37 else 16 + fromIntegral (y * 2)
38
19data RelayPacket 39data RelayPacket
20 = RoutingRequest PublicKey 40 = RoutingRequest PublicKey
21 | RoutingResponse Word8 PublicKey 41 | RoutingResponse ConId PublicKey -- 0 for refusal, 16-255 for success.
22 | ConnectNotification Word8 42 | ConnectNotification ConId
23 | DisconnectNotification Word8 43 | DisconnectNotification ConId
24 | RelayPing Word64 44 | RelayPing Word64
25 | RelayPong Word64 45 | RelayPong Word64
26 | OOBSend PublicKey ByteString 46 | OOBSend PublicKey ByteString
@@ -28,12 +48,12 @@ data RelayPacket
28 | OnionPacket (OnionRequest N0) 48 | OnionPacket (OnionRequest N0)
29 | OnionPacketResponse (OnionResponse N1) 49 | OnionPacketResponse (OnionResponse N1)
30 -- 0x0A through 0x0F reserved for future use. 50 -- 0x0A through 0x0F reserved for future use.
31 | RelayData Word8 ByteString -- Word8 is a connection id. Encoded as number 16 to 255. 51 | RelayData ConId ByteString -- Word8 is a connection id. Encoded as number 16 to 255.
32 deriving (Eq,Ord,Show,Data) 52 deriving (Eq,Ord,Show,Data)
33 53
34packetNumber :: RelayPacket -> Word8 54packetNumber :: RelayPacket -> Word8
35packetNumber (RelayData conid _) = conid -- 0 to 15 not allowed. 55packetNumber (RelayData (ConId conid) _) = conid -- 0 to 15 not allowed.
36packetNumber rp = fromIntegral $ pred $ constrIndex $ toConstr rp 56packetNumber rp = fromIntegral $ pred $ constrIndex $ toConstr rp
37 57
38instance Sized RelayPacket where 58instance Sized RelayPacket where
39 size = mappend (ConstSize 1) $ VarSize $ \x -> case x of 59 size = mappend (ConstSize 1) $ VarSize $ \x -> case x of
@@ -59,24 +79,24 @@ instance Serialize RelayPacket where
59 pktid <- getWord8 79 pktid <- getWord8
60 case pktid of 80 case pktid of
61 0 -> RoutingRequest <$> getPublicKey 81 0 -> RoutingRequest <$> getPublicKey
62 1 -> RoutingResponse <$> getWord8 <*> getPublicKey 82 1 -> RoutingResponse <$> get <*> getPublicKey
63 2 -> ConnectNotification <$> getWord8 83 2 -> ConnectNotification <$> get
64 3 -> DisconnectNotification <$> getWord8 84 3 -> DisconnectNotification <$> get
65 4 -> RelayPing <$> getWord64be 85 4 -> RelayPing <$> getWord64be
66 5 -> RelayPong <$> getWord64be 86 5 -> RelayPong <$> getWord64be
67 6 -> OOBSend <$> getPublicKey <*> (remaining >>= getBytes) 87 6 -> OOBSend <$> getPublicKey <*> (remaining >>= getBytes)
68 7 -> OOBRecv <$> getPublicKey <*> (remaining >>= getBytes) 88 7 -> OOBRecv <$> getPublicKey <*> (remaining >>= getBytes)
69 8 -> OnionPacket <$> get 89 8 -> OnionPacket <$> get
70 9 -> OnionPacketResponse <$> get 90 9 -> OnionPacketResponse <$> get
71 conid -> RelayData conid <$> (remaining >>= getBytes) 91 conid -> RelayData (ConId conid) <$> (remaining >>= getBytes)
72 92
73 put rp = do 93 put rp = do
74 putWord8 $ packetNumber rp 94 putWord8 $ packetNumber rp
75 case rp of 95 case rp of
76 RoutingRequest k -> putPublicKey k 96 RoutingRequest k -> putPublicKey k
77 RoutingResponse rpid k -> putWord8 rpid >> putPublicKey k 97 RoutingResponse rpid k -> put rpid >> putPublicKey k
78 ConnectNotification conid -> putWord8 conid 98 ConnectNotification conid -> put conid
79 DisconnectNotification conid -> putWord8 conid 99 DisconnectNotification conid -> put conid
80 RelayPing pingid -> putWord64be pingid 100 RelayPing pingid -> putWord64be pingid
81 RelayPong pingid -> putWord64be pingid 101 RelayPong pingid -> putWord64be pingid
82 OOBSend k bs -> putPublicKey k >> putByteString bs 102 OOBSend k bs -> putPublicKey k >> putByteString bs