summaryrefslogtreecommitdiff
path: root/src/Network
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network')
-rw-r--r--src/Network/SessionTransports.hs97
-rw-r--r--src/Network/Tox/Session.hs138
2 files changed, 235 insertions, 0 deletions
diff --git a/src/Network/SessionTransports.hs b/src/Network/SessionTransports.hs
new file mode 100644
index 00000000..17763e4e
--- /dev/null
+++ b/src/Network/SessionTransports.hs
@@ -0,0 +1,97 @@
1{-# LANGUAGE NamedFieldPuns #-}
2module Network.SessionTransports
3 ( Sessions
4 , initSessions
5 , newSession
6 , sessionHandler
7 ) where
8
9import Control.Concurrent
10import Control.Concurrent.STM
11import Control.Monad
12import qualified Data.IntMap.Strict as IntMap
13 ;import Data.IntMap.Strict (IntMap)
14import qualified Data.Map.Strict as Map
15 ;import Data.Map.Strict (Map)
16
17import Network.Address (SockAddr,either4or6)
18import Network.QueryResponse
19import qualified Data.IntervalSet as S
20 ;import Data.IntervalSet (IntSet)
21
22data Sessions x = Sessions
23 { sessionsByAddr :: TVar (Map SockAddr (IntMap (x -> IO Bool)))
24 , sessionsById :: TVar (IntMap SockAddr)
25 , sessionIds :: TVar IntSet
26 , sessionsSendRaw :: SockAddr -> x -> IO ()
27 }
28
29initSessions :: (SockAddr -> x -> IO ()) -> IO (Sessions x)
30initSessions send = atomically $ do
31 byaddr <- newTVar Map.empty
32 byid <- newTVar IntMap.empty
33 idset <- newTVar S.empty
34 return Sessions { sessionsByAddr = byaddr
35 , sessionsById = byid
36 , sessionIds = idset
37 , sessionsSendRaw = send
38 }
39
40
41
42rmSession :: Int -> (Maybe (IntMap x)) -> (Maybe (IntMap x))
43rmSession sid Nothing = Nothing
44rmSession sid (Just m) = case IntMap.delete sid m of
45 m' | IntMap.null m' -> Nothing
46 | otherwise -> Just m'
47
48newSession :: Sessions raw
49 -> (addr -> y -> IO raw)
50 -> (SockAddr -> raw -> IO (Maybe (x, addr)))
51 -> SockAddr
52 -> IO (Maybe (TransportA err addr x y))
53newSession Sessions{sessionsByAddr,sessionsById,sessionIds,sessionsSendRaw} unwrap wrap addr0 = do
54 mvar <- newEmptyMVar
55 let saddr = -- Canonical in case of 6-mapped-4 addresses.
56 either id id $ either4or6 addr0
57 handlePacket x = do
58 m <- wrap saddr x
59 case m of
60 Nothing -> return False
61 Just x' -> do putMVar mvar $! Just $! x'
62 return True
63 msid <- atomically $ do
64 msid <- S.nearestOutsider 0 <$> readTVar sessionIds
65 forM msid $ \sid -> do
66 modifyTVar' sessionIds $ S.insert sid
67 modifyTVar' sessionsById $ IntMap.insert sid saddr
68 modifyTVar' sessionsByAddr $ Map.insertWith IntMap.union saddr
69 $ IntMap.singleton sid handlePacket
70 return sid
71 forM msid $ \sid -> do
72 return Transport
73 { awaitMessage = \kont -> do
74 x <- takeMVar mvar
75 kont $! Right <$> x
76 , sendMessage = \addr x -> do
77 x' <- unwrap addr x
78 sessionsSendRaw saddr x'
79 , closeTransport = do
80 tryTakeMVar mvar
81 putMVar mvar Nothing
82 atomically $ do
83 modifyTVar' sessionIds $ S.delete sid
84 modifyTVar' sessionsById $ IntMap.delete sid
85 modifyTVar' sessionsByAddr $ Map.alter (rmSession sid) saddr
86 }
87
88sessionHandler :: Sessions x -> (SockAddr -> x -> IO (Maybe (x -> x)))
89sessionHandler Sessions{sessionsByAddr} = \addr0 x -> do
90 let addr = -- Canonical in case of 6-mapped-4 addresses.
91 either id id $ either4or6 addr0
92 dispatch [] = return ()
93 dispatch (f:fs) = do b <- f x
94 when (not b) $ dispatch fs
95 fs <- atomically $ Map.lookup addr <$> readTVar sessionsByAddr
96 mapM_ (dispatch . IntMap.elems) fs
97 return Nothing -- consume all packets.
diff --git a/src/Network/Tox/Session.hs b/src/Network/Tox/Session.hs
new file mode 100644
index 00000000..a52e9478
--- /dev/null
+++ b/src/Network/Tox/Session.hs
@@ -0,0 +1,138 @@
1{-# LANGUAGE TupleSections #-}
2module Network.Tox.Session where
3
4import Control.Concurrent.STM
5import Control.Monad
6import Data.Functor.Identity
7import Data.Word
8import Network.Socket
9
10import Crypto.Tox
11import Data.PacketBuffer (PacketInboundEvent (..))
12import Data.Tox.Message
13import Network.Lossless
14import Network.QueryResponse
15import Network.SessionTransports
16import Network.Tox.Crypto.Transport
17import Network.Tox.DHT.Transport (Cookie)
18import Network.Tox.Handshake
19
20type SessionKey = SecretKey
21
22data SessionParams = SessionParams
23 { spCrypto :: TransportCrypto
24 , spSessions :: Sessions (CryptoPacket Encrypted)
25 , spGetSentHandshake :: SecretKey -> SockAddr
26 -> Cookie Identity
27 -> Cookie Encrypted
28 -> IO (Maybe (SessionKey, HandshakeData))
29 , spOnNewSession :: Session -> IO ()
30 }
31
32data Session = Session
33 { sOurKey :: SecretKey
34 , sTheirAddr :: SockAddr
35 , sSentHandshake :: HandshakeData
36 , sReceivedHandshake :: Handshake Identity
37 , sResendPackets :: [Word32] -> IO ()
38 -- ^ If they request that we re-send certain packets, this method is how
39 -- that is accomplished.
40 , sMissingInbound :: IO ([Word32],Word32)
41 -- ^ This list of sequence numbers should be periodically polled and if
42 -- it is not empty, we should request they re-send these packets. For
43 -- convenience, a lower bound for the numbers in the list is also
44 -- returned. Suggested polling interval: a few seconds.
45 , sTransport :: Transport String () CryptoMessage
46 }
47
48handshakeH :: SessionParams
49 -> SockAddr
50 -> Handshake Encrypted
51 -> IO (Maybe a)
52handshakeH sp saddr handshake = do
53 decryptHandshake (spCrypto sp) handshake
54 >>= either (\err -> return ())
55 (uncurry $ plainHandshakeH sp saddr)
56 return Nothing
57
58
59plainHandshakeH :: SessionParams
60 -> SockAddr
61 -> SecretKey
62 -> Handshake Identity
63 -> IO ()
64plainHandshakeH sp saddr skey handshake = do
65 let hd = runIdentity $ handshakeData handshake
66 sent <- spGetSentHandshake sp skey saddr (handshakeCookie handshake) (otherCookie hd)
67 forM_ sent $ \(hd_skey,hd_sent) -> do
68 sk <- SessionKeys (spCrypto sp)
69 hd_skey
70 (sessionKey hd)
71 <$> atomically (newTVar $ baseNonce hd)
72 <*> atomically (newTVar $ baseNonce hd_sent)
73 m <- newSession (spSessions sp) (\() p -> return p) (decryptPacket sk) saddr
74 forM_ m $ \t -> do
75 (t2,resend,getMissing)
76 <- lossless (\cp a -> return $ fmap (,a) $ checkLossless $ runIdentity $ pktData cp)
77 (\seqno p _ -> encryptPacket sk $ bookKeeping seqno p)
78 ()
79 t
80 let _ = t :: TransportA String () (CryptoPacket Identity) (CryptoPacket Encrypted)
81 _ = t2 :: Transport String () CryptoMessage
82 spOnNewSession sp Session
83 { sOurKey = skey
84 , sTheirAddr = saddr
85 , sSentHandshake = hd_sent
86 , sReceivedHandshake = handshake
87 , sResendPackets = resend
88 , sMissingInbound = getMissing
89 , sTransport = t2
90 }
91 return ()
92
93decryptPacket :: SessionKeys -> SockAddr -> CryptoPacket Encrypted -> IO (Maybe (CryptoPacket Identity, ()))
94decryptPacket sk saddr (CryptoPacket n16 ciphered) = do
95 (n,δ) <- atomically $ do
96 n <- readTVar (skNonceIncoming sk)
97 let δ = n16 - nonce24ToWord16 n
98 return ( n `addtoNonce24` fromIntegral δ, δ )
99 secret <- lookupSharedSecret (skCrypto sk) (skMe sk) (skThem sk) n
100 case decodePlain =<< decrypt secret ciphered of
101 Left e -> return Nothing
102 Right x -> do
103 when ( δ > 43690 )
104 $ atomically $ writeTVar (skNonceIncoming sk) (n `addtoNonce24` 21845)
105 return $ Just ( CryptoPacket n16 (pure x), () )
106
107encryptPacket :: SessionKeys -> CryptoData -> IO (CryptoPacket Encrypted)
108encryptPacket sk plain = do
109 n24 <- atomically $ do
110 n24 <- readTVar (skNonceOutgoing sk)
111 modifyTVar' (skNonceOutgoing sk) incrementNonce24
112 return n24
113 secret <- lookupSharedSecret (skCrypto sk) (skMe sk) (skThem sk) n24
114 let ciphered = encrypt secret $ encodePlain $ plain
115 return $ CryptoPacket (nonce24ToWord16 n24) ciphered
116
117data SessionKeys = SessionKeys
118 { skCrypto :: TransportCrypto
119 , skMe :: SecretKey
120 , skThem :: PublicKey
121 , skNonceIncoming :: TVar Nonce24 -- +21845 when a threshold is reached.
122 , skNonceOutgoing :: TVar Nonce24 -- +1 on every packet
123 }
124
125bookKeeping :: SequenceInfo -> CryptoMessage -> CryptoData
126bookKeeping (SequenceInfo seqno ack) m = CryptoData
127 { bufferStart = seqno :: Word32
128 , bufferEnd = ack :: Word32
129 , bufferData = m
130 }
131
132checkLossless :: CryptoData -> PacketInboundEvent CryptoMessage
133checkLossless CryptoData{ bufferStart = ack
134 , bufferEnd = no
135 , bufferData = x } = tag no x ack
136 where
137 tag = case lossyness (msgID x) of Lossy -> PacketReceivedLossy
138 _ -> PacketReceived