diff options
author | Joe Crayne <joe@jerkface.net> | 2018-08-21 02:13:10 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2018-08-21 02:29:53 -0400 |
commit | 9dded0e540876c9e928cfcb3c69666ce00b5852c (patch) | |
tree | 8c4642ad9b868ff5f8b4870b9e29b6a7f012333b | |
parent | 9d32e8b0a5399328c670228630bd81177ebda87d (diff) |
Alternate session manager using IntervalSet for uniqs.
-rw-r--r-- | src/Network/SessionTransports.hs | 97 | ||||
-rw-r--r-- | src/Network/Tox/Session.hs | 138 |
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 #-} | ||
2 | module Network.SessionTransports | ||
3 | ( Sessions | ||
4 | , initSessions | ||
5 | , newSession | ||
6 | , sessionHandler | ||
7 | ) where | ||
8 | |||
9 | import Control.Concurrent | ||
10 | import Control.Concurrent.STM | ||
11 | import Control.Monad | ||
12 | import qualified Data.IntMap.Strict as IntMap | ||
13 | ;import Data.IntMap.Strict (IntMap) | ||
14 | import qualified Data.Map.Strict as Map | ||
15 | ;import Data.Map.Strict (Map) | ||
16 | |||
17 | import Network.Address (SockAddr,either4or6) | ||
18 | import Network.QueryResponse | ||
19 | import qualified Data.IntervalSet as S | ||
20 | ;import Data.IntervalSet (IntSet) | ||
21 | |||
22 | data 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 | |||
29 | initSessions :: (SockAddr -> x -> IO ()) -> IO (Sessions x) | ||
30 | initSessions 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 | |||
42 | rmSession :: Int -> (Maybe (IntMap x)) -> (Maybe (IntMap x)) | ||
43 | rmSession sid Nothing = Nothing | ||
44 | rmSession sid (Just m) = case IntMap.delete sid m of | ||
45 | m' | IntMap.null m' -> Nothing | ||
46 | | otherwise -> Just m' | ||
47 | |||
48 | newSession :: Sessions raw | ||
49 | -> (addr -> y -> IO raw) | ||
50 | -> (SockAddr -> raw -> IO (Maybe (x, addr))) | ||
51 | -> SockAddr | ||
52 | -> IO (Maybe (TransportA err addr x y)) | ||
53 | newSession 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 | |||
88 | sessionHandler :: Sessions x -> (SockAddr -> x -> IO (Maybe (x -> x))) | ||
89 | sessionHandler 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 #-} | ||
2 | module Network.Tox.Session where | ||
3 | |||
4 | import Control.Concurrent.STM | ||
5 | import Control.Monad | ||
6 | import Data.Functor.Identity | ||
7 | import Data.Word | ||
8 | import Network.Socket | ||
9 | |||
10 | import Crypto.Tox | ||
11 | import Data.PacketBuffer (PacketInboundEvent (..)) | ||
12 | import Data.Tox.Message | ||
13 | import Network.Lossless | ||
14 | import Network.QueryResponse | ||
15 | import Network.SessionTransports | ||
16 | import Network.Tox.Crypto.Transport | ||
17 | import Network.Tox.DHT.Transport (Cookie) | ||
18 | import Network.Tox.Handshake | ||
19 | |||
20 | type SessionKey = SecretKey | ||
21 | |||
22 | data 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 | |||
32 | data 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 | |||
48 | handshakeH :: SessionParams | ||
49 | -> SockAddr | ||
50 | -> Handshake Encrypted | ||
51 | -> IO (Maybe a) | ||
52 | handshakeH sp saddr handshake = do | ||
53 | decryptHandshake (spCrypto sp) handshake | ||
54 | >>= either (\err -> return ()) | ||
55 | (uncurry $ plainHandshakeH sp saddr) | ||
56 | return Nothing | ||
57 | |||
58 | |||
59 | plainHandshakeH :: SessionParams | ||
60 | -> SockAddr | ||
61 | -> SecretKey | ||
62 | -> Handshake Identity | ||
63 | -> IO () | ||
64 | plainHandshakeH 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 | |||
93 | decryptPacket :: SessionKeys -> SockAddr -> CryptoPacket Encrypted -> IO (Maybe (CryptoPacket Identity, ())) | ||
94 | decryptPacket 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 | |||
107 | encryptPacket :: SessionKeys -> CryptoData -> IO (CryptoPacket Encrypted) | ||
108 | encryptPacket 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 | |||
117 | data 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 | |||
125 | bookKeeping :: SequenceInfo -> CryptoMessage -> CryptoData | ||
126 | bookKeeping (SequenceInfo seqno ack) m = CryptoData | ||
127 | { bufferStart = seqno :: Word32 | ||
128 | , bufferEnd = ack :: Word32 | ||
129 | , bufferData = m | ||
130 | } | ||
131 | |||
132 | checkLossless :: CryptoData -> PacketInboundEvent CryptoMessage | ||
133 | checkLossless 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 | ||