diff options
-rw-r--r-- | dht-client.cabal | 3 | ||||
-rw-r--r-- | src/Crypto/Tox.hs | 29 | ||||
-rw-r--r-- | src/Data/PacketQueue.hs | 170 | ||||
-rw-r--r-- | src/Network/Tox/Crypto/Handlers.hs | 396 | ||||
-rw-r--r-- | src/Network/Tox/Crypto/Transport.hs | 104 |
5 files changed, 634 insertions, 68 deletions
diff --git a/dht-client.cabal b/dht-client.cabal index 6ad4ad37..b17ceb14 100644 --- a/dht-client.cabal +++ b/dht-client.cabal | |||
@@ -80,6 +80,7 @@ library | |||
80 | Network.BitTorrent.MainlineDHT | 80 | Network.BitTorrent.MainlineDHT |
81 | Network.BitTorrent.MainlineDHT.Symbols | 81 | Network.BitTorrent.MainlineDHT.Symbols |
82 | System.Global6 | 82 | System.Global6 |
83 | Data.PacketQueue | ||
83 | Data.Word64Map | 84 | Data.Word64Map |
84 | OnionRouter | 85 | OnionRouter |
85 | Network.Tox | 86 | Network.Tox |
@@ -170,6 +171,8 @@ library | |||
170 | , transformers-base | 171 | , transformers-base |
171 | , mtl | 172 | , mtl |
172 | , ghc-prim | 173 | , ghc-prim |
174 | , sensible-directory | ||
175 | , temporary | ||
173 | , transformers | 176 | , transformers |
174 | , conduit | 177 | , conduit |
175 | , conduit-extra | 178 | , conduit-extra |
diff --git a/src/Crypto/Tox.hs b/src/Crypto/Tox.hs index 665a38dd..624da233 100644 --- a/src/Crypto/Tox.hs +++ b/src/Crypto/Tox.hs | |||
@@ -34,11 +34,13 @@ module Crypto.Tox | |||
34 | , decodePlain | 34 | , decodePlain |
35 | -- , computeSharedSecret | 35 | -- , computeSharedSecret |
36 | , lookupSharedSecret | 36 | , lookupSharedSecret |
37 | , lookupNonceFunction | ||
37 | , encrypt | 38 | , encrypt |
38 | , decrypt | 39 | , decrypt |
39 | , Nonce8(..) | 40 | , Nonce8(..) |
40 | , Nonce24(..) | 41 | , Nonce24(..) |
41 | , incrementNonce24 | 42 | , incrementNonce24 |
43 | , nonce24ToWord16 | ||
42 | , addtoNonce24 | 44 | , addtoNonce24 |
43 | , Nonce32(..) | 45 | , Nonce32(..) |
44 | , getRemainingEncrypted | 46 | , getRemainingEncrypted |
@@ -297,16 +299,29 @@ unsafeCompare32Bytes' !n !pa !pb = do | |||
297 | 299 | ||
298 | 300 | ||
299 | lookupSharedSecret :: TransportCrypto -> SecretKey -> PublicKey -> Nonce24 -> IO State | 301 | lookupSharedSecret :: TransportCrypto -> SecretKey -> PublicKey -> Nonce24 -> IO State |
300 | lookupSharedSecret TransportCrypto{secretsCache} sk recipient nonce = do | 302 | lookupSharedSecret crypto sk recipient nonce |
303 | = ($ nonce) <$> lookupNonceFunction crypto sk recipient | ||
304 | |||
305 | {-# INLINE lookupNonceFunction #-} | ||
306 | lookupNonceFunction :: TransportCrypto -> SecretKey -> PublicKey -> IO (Nonce24 -> State) | ||
307 | lookupNonceFunction c@(TransportCrypto{secretsCache}) sk recipient = do | ||
301 | now <- getPOSIXTime | 308 | now <- getPOSIXTime |
302 | atomically $ do | 309 | atomically $ lookupNonceFunctionSTM now c sk recipient |
310 | |||
311 | {-# INLINE lookupNonceFunctionSTM #-} | ||
312 | -- | This version of 'lookupNonceFunction' is STM instead of IO, this means if some later part of | ||
313 | -- of the transaction fails, we may end up forgoing a computation that could have been cached. | ||
314 | -- Use with care. In most circumstances you probably want 'lookupNonceFunction'. It also commits | ||
315 | -- us to using TVars to store the cache. | ||
316 | lookupNonceFunctionSTM :: POSIXTime -> TransportCrypto -> SecretKey -> PublicKey -> STM (Nonce24 -> State) | ||
317 | lookupNonceFunctionSTM now TransportCrypto{secretsCache} sk recipient = do | ||
303 | mm <- readTVar $ sharedSecret secretsCache | 318 | mm <- readTVar $ sharedSecret secretsCache |
304 | case MM.lookup' recipient mm of | 319 | case MM.lookup' recipient mm of |
305 | Nothing -> do | 320 | Nothing -> do |
306 | let miss = computeSharedSecret sk recipient | 321 | let miss = computeSharedSecret sk recipient |
307 | writeTVar (sharedSecret secretsCache) | 322 | writeTVar (sharedSecret secretsCache) |
308 | (MM.insertTake' 160 recipient (MM.singleton' sk miss (Down now)) (Down now) mm) | 323 | (MM.insertTake' 160 recipient (MM.singleton' sk miss (Down now)) (Down now) mm) |
309 | return $ miss nonce | 324 | return miss |
310 | Just (stamp,smm) -> do | 325 | Just (stamp,smm) -> do |
311 | let (r,v) = case MM.lookup' sk smm of | 326 | let (r,v) = case MM.lookup' sk smm of |
312 | Nothing | let miss = computeSharedSecret sk recipient | 327 | Nothing | let miss = computeSharedSecret sk recipient |
@@ -314,7 +329,7 @@ lookupSharedSecret TransportCrypto{secretsCache} sk recipient nonce = do | |||
314 | Just (stamp2,hit) -> (hit , MM.insert' sk hit (Down now) smm) | 329 | Just (stamp2,hit) -> (hit , MM.insert' sk hit (Down now) smm) |
315 | writeTVar (sharedSecret secretsCache) | 330 | writeTVar (sharedSecret secretsCache) |
316 | (MM.insertTake' 160 recipient v (Down now) mm) | 331 | (MM.insertTake' 160 recipient v (Down now) mm) |
317 | return $ r nonce | 332 | return r |
318 | 333 | ||
319 | 334 | ||
320 | hsalsa20 :: (ByteArrayAccess t, ByteArrayAccess t1) => t1 -> t -> BA.ScrubbedBytes | 335 | hsalsa20 :: (ByteArrayAccess t, ByteArrayAccess t1) => t1 -> t -> BA.ScrubbedBytes |
@@ -328,7 +343,10 @@ hsalsa20 k n = BA.append a b | |||
328 | 343 | ||
329 | 344 | ||
330 | newtype Nonce24 = Nonce24 ByteString | 345 | newtype Nonce24 = Nonce24 ByteString |
331 | deriving (Eq, Ord, ByteArrayAccess,Data) | 346 | deriving (Eq, Ord, ByteArrayAccess, Data) |
347 | |||
348 | nonce24ToWord16 :: Nonce24 -> Word16 | ||
349 | nonce24ToWord16 (Nonce24 n24) = fromIntegral (B.index n24 23) + 256 * fromIntegral (B.index n24 22) | ||
332 | 350 | ||
333 | addtoNonce24 :: Nonce24 -> Word -> IO Nonce24 | 351 | addtoNonce24 :: Nonce24 -> Word -> IO Nonce24 |
334 | addtoNonce24 (Nonce24 n24) n = Nonce24 <$> BA.copy n24 init | 352 | addtoNonce24 (Nonce24 n24) n = Nonce24 <$> BA.copy n24 init |
@@ -372,6 +390,7 @@ addtoNonce24 (Nonce24 n24) n = Nonce24 <$> BA.copy n24 init | |||
372 | pokeElemOff ptr 5 $ tBE32 (W# sum_) | 390 | pokeElemOff ptr 5 $ tBE32 (W# sum_) |
373 | init _ = error "incrementNonce24: I only support 64 and 32 bits" | 391 | init _ = error "incrementNonce24: I only support 64 and 32 bits" |
374 | 392 | ||
393 | {-# INLINE incrementNonce24 #-} | ||
375 | incrementNonce24 :: Nonce24 -> IO Nonce24 | 394 | incrementNonce24 :: Nonce24 -> IO Nonce24 |
376 | incrementNonce24 nonce24 = addtoNonce24 nonce24 1 | 395 | incrementNonce24 nonce24 = addtoNonce24 nonce24 1 |
377 | 396 | ||
diff --git a/src/Data/PacketQueue.hs b/src/Data/PacketQueue.hs new file mode 100644 index 00000000..927d6c53 --- /dev/null +++ b/src/Data/PacketQueue.hs | |||
@@ -0,0 +1,170 @@ | |||
1 | -- | This module is useful for implementing a lossess protocol on top of a | ||
2 | -- lossy datagram style protocol. It implements a buffer in which packets may | ||
3 | -- be stored out of order, but from which they are extracted in the proper | ||
4 | -- sequence. | ||
5 | {-# LANGUAGE NamedFieldPuns #-} | ||
6 | {-# LANGUAGE FlexibleContexts #-} | ||
7 | module Data.PacketQueue | ||
8 | ( PacketQueue | ||
9 | , new | ||
10 | , dequeue | ||
11 | , enqueue | ||
12 | , observeOutOfBand | ||
13 | , PacketOutQueue | ||
14 | , newOutGoing | ||
15 | , tryAppendQueueOutgoing | ||
16 | , dequeueOutgoing | ||
17 | , mapOutGoing | ||
18 | , OutGoingResult(..) | ||
19 | ) where | ||
20 | |||
21 | import Control.Concurrent.STM | ||
22 | import Control.Concurrent.STM.TArray | ||
23 | import Control.Monad | ||
24 | import Data.Word | ||
25 | import Data.Array.MArray | ||
26 | |||
27 | data PacketQueue a = PacketQueue | ||
28 | { pktq :: TArray Word32 (Maybe a) | ||
29 | , seqno :: TVar Word32 | ||
30 | , qsize :: Word32 | ||
31 | , buffend :: TVar Word32 -- on incoming, highest packet number handled + 1 | ||
32 | } | ||
33 | |||
34 | -- | Create a new PacketQueue. | ||
35 | new :: Word32 -- ^ Capacity of queue. | ||
36 | -> Word32 -- ^ Initial sequence number. | ||
37 | -> STM (PacketQueue a) | ||
38 | new capacity seqstart = do | ||
39 | let cap = if capacity `mod` 2 == 0 then capacity else capacity + 1 | ||
40 | q <- newArray (0,cap - 1) Nothing | ||
41 | seqv <- newTVar seqstart | ||
42 | bufe <- newTVar 0 | ||
43 | return PacketQueue | ||
44 | { pktq = q | ||
45 | , seqno = seqv | ||
46 | , qsize = cap | ||
47 | , buffend = bufe | ||
48 | } | ||
49 | |||
50 | observeOutOfBand :: PacketQueue a -> Word32-> STM () | ||
51 | observeOutOfBand PacketQueue { seqno, qsize, buffend } no = do | ||
52 | low <- readTVar seqno | ||
53 | let proj = no - low | ||
54 | -- Ignore packet if out of range. | ||
55 | when ( proj < qsize) $ do | ||
56 | modifyTVar' buffend (\be -> if be - low <= proj then no + 1 else be) | ||
57 | |||
58 | |||
59 | -- | Retry until the next expected packet is enqueued. Then return it. | ||
60 | dequeue :: PacketQueue a -> STM a | ||
61 | dequeue PacketQueue { pktq, seqno, qsize } = do | ||
62 | i0 <- readTVar seqno | ||
63 | let i = i0 `mod` qsize | ||
64 | x <- maybe retry return =<< readArray pktq i | ||
65 | writeArray pktq i Nothing | ||
66 | modifyTVar' seqno succ | ||
67 | return x | ||
68 | |||
69 | -- | Enqueue a packet. Packets need not be enqueued in order as long as there | ||
70 | -- is spare capacity in the queue. If there is not, the packet will be | ||
71 | -- silently discarded without blocking. | ||
72 | enqueue :: PacketQueue a -- ^ The packet queue. | ||
73 | -> Word32 -- ^ Sequence number of the packet. | ||
74 | -> a -- ^ The packet. | ||
75 | -> STM () | ||
76 | enqueue PacketQueue{ pktq, seqno, qsize, buffend } no x = do | ||
77 | low <- readTVar seqno | ||
78 | let proj = no - low | ||
79 | -- Ignore packet if out of range. | ||
80 | when ( proj < qsize) $ do | ||
81 | let i = no `mod` qsize | ||
82 | writeArray pktq i (Just x) | ||
83 | modifyTVar' buffend (\be -> if be - low <= proj then no + 1 else be) | ||
84 | |||
85 | -- lookup :: PacketQueue a -> Word32 -> STM (Maybe a) | ||
86 | -- lookup PacketQueue{ pktq, seqno, qsize } no = _todo | ||
87 | |||
88 | ----------------------------------------------------- | ||
89 | -- * PacketOutQueue | ||
90 | -- | ||
91 | |||
92 | data PacketOutQueue extra msg toWire fromWire = PacketOutQueue | ||
93 | { pktoInPQ :: PacketQueue fromWire -- ^ reference to the incoming 'PacketQueue' | ||
94 | , pktoOutPQ :: PacketQueue (Word32,toWire) | ||
95 | , pktoPacketNo :: TVar Word32 | ||
96 | , pktoToWireIO :: IO (STM extra) | ||
97 | , pktoToWire :: STM extra | ||
98 | -> Word32{-packet number we expect to recieve-} | ||
99 | -> Word32{- buffer_end -} | ||
100 | -> Word32{- packet number -} | ||
101 | -> msg | ||
102 | -> STM (Maybe (toWire,Word32{-next packet no-})) | ||
103 | } | ||
104 | |||
105 | mapOutGoing :: ((Word32,towire) -> Maybe (Word32,towire)) -> PacketOutQueue extra msg towire fromwire -> STM () | ||
106 | mapOutGoing f q@(PacketOutQueue { pktoOutPQ=PacketQueue{ pktq } }) = do | ||
107 | (z,n) <- getBounds pktq | ||
108 | let ff i = do | ||
109 | e <- readArray pktq i | ||
110 | writeArray pktq i (e>>=f) | ||
111 | mapM_ ff [z .. n] | ||
112 | |||
113 | newOutGoing :: PacketQueue fromwire | ||
114 | -- ^ Incoming queue | ||
115 | -> (STM io -> Word32 {-packet number we expect to recieve-} -> Word32{-buffer_end-} -> Word32{-packet number-} -> msg -> STM (Maybe (wire,Word32{-next packet no-}))) | ||
116 | -- ^ toWire callback | ||
117 | -> IO (STM io) | ||
118 | -- ^ io action to get extra parameter | ||
119 | -> Word32 -- ^ packet number of first outgoing packet | ||
120 | -> Word32 -- ^ Capacity of queue. | ||
121 | -> Word32 -- ^ Initial sequence number. | ||
122 | -> STM (PacketOutQueue io msg wire fromwire) | ||
123 | newOutGoing inq towire toWireIO num capacity seqstart = do | ||
124 | outq <- new capacity seqstart | ||
125 | numVar <- newTVar num | ||
126 | return $ PacketOutQueue | ||
127 | { pktoInPQ = inq | ||
128 | , pktoOutPQ = outq | ||
129 | , pktoPacketNo = numVar | ||
130 | , pktoToWireIO = toWireIO | ||
131 | , pktoToWire = towire | ||
132 | } | ||
133 | |||
134 | data OutGoingResult = OGSuccess | OGFull | OGEncodeFail | ||
135 | deriving (Eq,Show) | ||
136 | |||
137 | -- | Convert a message to packet format and append it to the front of a queue | ||
138 | -- used for outgoing messages. (Note that ‘front‛ usually means the higher | ||
139 | -- index in this implementation.) | ||
140 | tryAppendQueueOutgoing :: STM extra -> PacketOutQueue extra msg wire fromwire -> msg -> STM OutGoingResult | ||
141 | tryAppendQueueOutgoing getExtra q@(PacketOutQueue { pktoInPQ, pktoOutPQ, pktoPacketNo, pktoToWireIO, pktoToWire }) msg = do | ||
142 | be <- readTVar (buffend pktoOutPQ) | ||
143 | let i = be `mod` (qsize pktoOutPQ) | ||
144 | mbPkt <- readArray (pktq pktoOutPQ) i | ||
145 | pktno <- readTVar pktoPacketNo | ||
146 | nextno <- readTVar (seqno pktoInPQ) | ||
147 | mbWire <- pktoToWire getExtra nextno be pktno msg | ||
148 | case mbWire of | ||
149 | Just (pkt,pktno') | ||
150 | -> case mbPkt of | ||
151 | -- slot is free, insert element | ||
152 | Nothing -> do | ||
153 | modifyTVar' (buffend pktoOutPQ) (+1) | ||
154 | writeTVar pktoPacketNo $! pktno' | ||
155 | writeArray (pktq pktoOutPQ) i (Just (pktno,pkt)) | ||
156 | return OGSuccess | ||
157 | -- queue is full, block until its not | ||
158 | _ -> return OGFull | ||
159 | -- don't know how to send this message | ||
160 | Nothing -> return OGEncodeFail | ||
161 | |||
162 | dequeueOutgoing :: PacketOutQueue extra msg wire fromwire -> STM (Word32,wire) | ||
163 | dequeueOutgoing (PacketOutQueue {pktoOutPQ=PacketQueue { pktq, seqno, qsize }}) = do | ||
164 | i0 <- readTVar seqno | ||
165 | let i = i0 `mod` qsize | ||
166 | x <- maybe retry return =<< readArray pktq i | ||
167 | -- writeArray pktq i Nothing -- not cleaning | ||
168 | modifyTVar' seqno succ | ||
169 | return x | ||
170 | |||
diff --git a/src/Network/Tox/Crypto/Handlers.hs b/src/Network/Tox/Crypto/Handlers.hs index ac3d1ef0..6a79da1b 100644 --- a/src/Network/Tox/Crypto/Handlers.hs +++ b/src/Network/Tox/Crypto/Handlers.hs | |||
@@ -1,9 +1,12 @@ | |||
1 | {-# LANGUAGE NamedFieldPuns #-} | 1 | {-# LANGUAGE NamedFieldPuns #-} |
2 | {-# LANGUAGE TupleSections #-} | 2 | {-# LANGUAGE TupleSections #-} |
3 | {-# LANGUAGE TypeOperators #-} | ||
3 | module Network.Tox.Crypto.Handlers where | 4 | module Network.Tox.Crypto.Handlers where |
4 | 5 | ||
6 | import Network.Tox.NodeId | ||
5 | import Network.Tox.Crypto.Transport | 7 | import Network.Tox.Crypto.Transport |
6 | import Network.Tox.DHT.Transport (Cookie(..),CookieData(..)) | 8 | import Network.Tox.DHT.Transport (Cookie(..),CookieData(..), CookieRequest(..)) |
9 | import Network.Tox.DHT.Handlers (Client, cookieRequest, cookieRequestH ) | ||
7 | import Crypto.Tox | 10 | import Crypto.Tox |
8 | import Control.Concurrent.STM | 11 | import Control.Concurrent.STM |
9 | import Network.Address | 12 | import Network.Address |
@@ -13,12 +16,25 @@ import Control.Applicative | |||
13 | import Control.Monad | 16 | import Control.Monad |
14 | import Data.Time.Clock.POSIX | 17 | import Data.Time.Clock.POSIX |
15 | import qualified Data.ByteString as B | 18 | import qualified Data.ByteString as B |
19 | import Data.ByteString (ByteString) | ||
16 | import Control.Lens | 20 | import Control.Lens |
17 | import Data.Function | 21 | import Data.Function |
22 | import qualified Data.PacketQueue as PQ | ||
23 | ;import Data.PacketQueue (PacketQueue) | ||
18 | import Data.Serialize as S | 24 | import Data.Serialize as S |
19 | import Data.Word | 25 | import Data.Word |
26 | import qualified Data.Word64Map as W64 | ||
20 | import GHC.Conc (unsafeIOToSTM) | 27 | import GHC.Conc (unsafeIOToSTM) |
21 | import qualified Data.Set as Set | 28 | import qualified Data.Set as Set |
29 | import qualified Data.Array.Unboxed as A | ||
30 | import SensibleDir | ||
31 | import System.FilePath | ||
32 | import System.IO.Temp | ||
33 | import System.Environment | ||
34 | import System.Directory | ||
35 | import Control.Concurrent | ||
36 | import GHC.Conc (labelThread) | ||
37 | import System.IO.Unsafe(unsafeDupablePerformIO {- unsafeIOToSTM -}) | ||
22 | 38 | ||
23 | -- util, todo: move to another module | 39 | -- util, todo: move to another module |
24 | maybeToEither :: Maybe b -> Either String b | 40 | maybeToEither :: Maybe b -> Either String b |
@@ -31,59 +47,232 @@ data NetCryptoSessionStatus = Unaccepted | Accepted | Confirmed | |||
31 | 47 | ||
32 | type IOHook addr x = addr -> x -> IO (Maybe (x -> x)) | 48 | type IOHook addr x = addr -> x -> IO (Maybe (x -> x)) |
33 | type NetCryptoHook = IOHook NetCryptoSession CryptoData | 49 | type NetCryptoHook = IOHook NetCryptoSession CryptoData |
50 | type MsgTypeArray = A.UArray Word8 Word16 | ||
51 | type MsgOutMap = W64.Word64Map Word8 | ||
52 | -- type MsgOutMap = A.UArray Word64 Word8 -- if above is too slow, switch to this, but use reasonable bounds | ||
53 | msgOutMapLookup :: Word64 -> MsgOutMap -> Maybe Word8 | ||
54 | msgOutMapLookup = W64.lookup | ||
34 | 55 | ||
56 | -- | Information, that may be made visible in multiple sessions, as well | ||
57 | -- as displayed in some way to the user via mutiple views. | ||
58 | -- | ||
59 | data SessionView = SessionView | ||
60 | { svNick :: TVar ByteString | ||
61 | , svStatus :: TVar UserStatus | ||
62 | , svStatusMsg :: TVar ByteString | ||
63 | , svGroups :: TVar (Map.Map GroupChatId (Set.Set SockAddr)) | ||
64 | -- allthough these directories are not visible to others on the net | ||
65 | -- they are included in this type, because it facilitates organizing | ||
66 | -- the disk according to your public image. | ||
67 | , svCacheDir :: FilePath -- ^ directory path used if the session has to use the disk for cache | ||
68 | -- clean up only if space is needed | ||
69 | , svTmpDir :: FilePath -- ^ Once off storage goes here, should clean up quickly | ||
70 | , svConfigDir :: FilePath -- ^ profile related storage, etc, never clean up | ||
71 | , svDownloadDir :: TVar FilePath -- ^ where to put files the user downloads | ||
72 | } | ||
35 | 73 | ||
36 | data NetCryptoSession = NCrypto { ncState :: TVar NetCryptoSessionStatus | 74 | type SessionID = Word64 |
75 | |||
76 | data NetCryptoSession = NCrypto { ncState :: TVar NetCryptoSessionStatus | ||
77 | , ncSessionId :: SessionID | ||
78 | , ncTheirPublicKey :: PublicKey -- Tox id w/o nospam | ||
37 | , ncTheirBaseNonce :: TVar Nonce24 -- base nonce + packet number | 79 | , ncTheirBaseNonce :: TVar Nonce24 -- base nonce + packet number |
38 | , ncMyPacketNonce :: TVar Nonce24 -- base nonce + packet number | 80 | , ncMyPacketNonce :: TVar Nonce24 -- base nonce + packet number |
39 | , ncHandShake :: TVar (Maybe (Handshake Encrypted)) | 81 | , ncHandShake :: TVar (Maybe (Handshake Encrypted)) |
40 | , ncCookie :: TVar (Maybe Cookie) | 82 | , ncCookie :: TVar (Maybe Cookie) -- ^ Cookie issued by remote peer |
41 | , ncTheirDHTKey :: PublicKey | 83 | , ncTheirDHTKey :: PublicKey |
42 | , ncTheirSessionPublic :: Maybe PublicKey | 84 | , ncTheirSessionPublic :: Maybe PublicKey |
43 | , ncSessionSecret :: SecretKey | 85 | , ncSessionSecret :: SecretKey |
44 | , ncSockAddr :: SockAddr | 86 | , ncSockAddr :: SockAddr |
45 | , ncHooks :: TVar (Map.Map MessageType [NetCryptoHook]) | 87 | , ncHooks :: TVar (Map.Map MessageType [NetCryptoHook]) |
46 | , ncUnrecognizedHook :: TVar (MessageType -> NetCryptoHook) | 88 | , ncUnrecognizedHook :: TVar (MessageType -> NetCryptoHook) |
89 | , ncIncomingTypeArray :: TVar MsgTypeArray | ||
90 | -- ^ supported messages, 0 for unsupported, | ||
91 | -- otherwise the messageType, some message types | ||
92 | -- may not be in ncHooks yet, but they should appear | ||
93 | -- here if ncUnrecognizedHook will add them to ncHooks | ||
94 | -- on an as-need basis. | ||
95 | , ncOutgoingIdMap :: TVar MsgOutMap | ||
47 | , ncAllSessions :: NetCryptoSessions -- ^ may be needed if one net-crypto session | 96 | , ncAllSessions :: NetCryptoSessions -- ^ may be needed if one net-crypto session |
48 | -- needs to possibly start another, as is | 97 | -- needs to possibly start another, as is |
49 | -- the case in group chats | 98 | -- the case in group chats |
50 | , ncGroups :: TVar (Map.Map GroupChatId (Set.Set SockAddr)) | 99 | , ncView :: TVar SessionView |
100 | , ncPacketQueue :: PacketQueue CryptoData | ||
101 | , ncBufferStart :: TVar Word32 | ||
102 | , ncDequeueThread :: Maybe ThreadId | ||
103 | , ncOutgoingQueue :: PQ.PacketOutQueue (State,Nonce24,TVar MsgOutMap) CryptoMessage (CryptoPacket Encrypted) CryptoData | ||
51 | } | 104 | } |
52 | 105 | ||
53 | data NetCryptoSessions = NCSessions { netCryptoSessions :: TVar (Map.Map SockAddr NetCryptoSession) | 106 | data NetCryptoSessions = NCSessions { netCryptoSessions :: TVar (Map.Map SockAddr NetCryptoSession) |
107 | , netCryptoSessionsByKey :: TVar (Map.Map PublicKey [NetCryptoSession]) | ||
54 | , transportCrypto :: TransportCrypto | 108 | , transportCrypto :: TransportCrypto |
55 | , defaultHooks :: Map.Map MessageType [NetCryptoHook] | 109 | , defaultHooks :: Map.Map MessageType [NetCryptoHook] |
56 | , defaultUnrecognizedHook :: MessageType -> NetCryptoHook | 110 | , defaultUnrecognizedHook :: MessageType -> NetCryptoHook |
111 | , sessionView :: SessionView | ||
112 | , msgTypeArray :: MsgTypeArray | ||
113 | , inboundQueueCapacity :: Word32 | ||
114 | , outboundQueueCapacity :: Word32 | ||
115 | , nextSessionId :: TVar SessionID | ||
57 | } | 116 | } |
58 | 117 | ||
59 | newSessionsState :: TransportCrypto -> (MessageType -> NetCryptoHook) -> Map.Map MessageType [NetCryptoHook] -> IO NetCryptoSessions | 118 | forgetCrypto :: TransportCrypto -> NetCryptoSessions -> NetCryptoSession -> STM () |
119 | forgetCrypto crypto (NCSessions {netCryptoSessions,netCryptoSessionsByKey}) session = do | ||
120 | let addr = ncSockAddr session | ||
121 | sid = ncSessionId session | ||
122 | sPubKey = ncTheirPublicKey session | ||
123 | byAddrMap <- readTVar netCryptoSessions | ||
124 | {- byKeyMap <- readTVar netCryptoSessionsByKey -} | ||
125 | case Map.lookup addr byAddrMap of | ||
126 | Nothing -> return () -- already gone | ||
127 | Just _ -> do | ||
128 | modifyTVar netCryptoSessions (Map.delete addr) | ||
129 | modifyTVar netCryptoSessionsByKey (Map.update (\xs -> case filter (\x -> ncSessionId x /= sid) xs of | ||
130 | [] -> Nothing | ||
131 | ys -> Just ys) sPubKey) | ||
132 | |||
133 | -- | initiate a netcrypto session, blocking | ||
134 | netCrypto :: TransportCrypto -> NetCryptoSessions -> SecretKey -> PublicKey -> IO NetCryptoSession | ||
135 | netCrypto crypto allsessions myseckey theirpubkey = do | ||
136 | -- convert public key to NodeInfo check Roster | ||
137 | -- if no session: | ||
138 | -- 1) send dht key, actually maybe send dht-key regardless | ||
139 | -- 2) send handshakes to last seen ip's, if any | ||
140 | -- | ||
141 | -- if sessions found, is it using this private key? | ||
142 | -- if not, send handshake, this is separate session | ||
143 | error "todo" | ||
144 | |||
145 | newSessionsState :: TransportCrypto | ||
146 | -> (MessageType -> NetCryptoHook) -- ^ default hook | ||
147 | -> Map.Map MessageType [NetCryptoHook] -- ^ all hooks, can be empty to start | ||
148 | -> IO NetCryptoSessions | ||
60 | newSessionsState crypto unrechook hooks = do | 149 | newSessionsState crypto unrechook hooks = do |
61 | x <- atomically $ newTVar Map.empty | 150 | x <- atomically $ newTVar Map.empty |
151 | x2 <- atomically $ newTVar Map.empty | ||
152 | nick <- atomically $ newTVar B.empty | ||
153 | status <- atomically $ newTVar Online | ||
154 | statusmsg <- atomically $ newTVar B.empty | ||
155 | grps <- atomically $ newTVar Map.empty | ||
156 | pname <- getProgName | ||
157 | cachedir <- sensibleCacheDirCreateIfMissing pname | ||
158 | tmpdir <- (</> pname) <$> (getTemporaryDirectory >>= canonicalizePath) -- getCanonicalTemporaryDirectory | ||
159 | configdir <- sensibleVarLib pname | ||
160 | homedir <- getHomeDirectory | ||
161 | svDownloadDir0 <- atomically $ newTVar (homedir </> "Downloads") | ||
162 | nextSessionId0 <- atomically $ newTVar 0 | ||
62 | return NCSessions { netCryptoSessions = x | 163 | return NCSessions { netCryptoSessions = x |
164 | , netCryptoSessionsByKey = x2 | ||
63 | , transportCrypto = crypto | 165 | , transportCrypto = crypto |
64 | , defaultHooks = hooks | 166 | , defaultHooks = hooks |
65 | , defaultUnrecognizedHook = unrechook | 167 | , defaultUnrecognizedHook = unrechook |
168 | , sessionView = SessionView { svNick = nick | ||
169 | , svStatus = status | ||
170 | , svStatusMsg = statusmsg | ||
171 | , svGroups = grps | ||
172 | , svCacheDir = cachedir | ||
173 | , svTmpDir = tmpdir | ||
174 | , svConfigDir = configdir | ||
175 | , svDownloadDir = svDownloadDir0 | ||
176 | } | ||
177 | , msgTypeArray = allMsgTypes -- todo make this a parameter | ||
178 | , inboundQueueCapacity = 200 | ||
179 | , outboundQueueCapacity = 400 | ||
180 | , nextSessionId = nextSessionId0 | ||
66 | } | 181 | } |
67 | 182 | ||
68 | data HandshakeParams | 183 | data HandshakeParams |
69 | = HParam | 184 | = HParam |
70 | { hpTheirBaseNonce :: Maybe Nonce24 -- ignore and generate your own | 185 | { hpTheirBaseNonce :: Maybe Nonce24 -- ignore and generate your own |
71 | , hpOtherCookie :: Maybe Cookie | 186 | , hpOtherCookie :: Cookie |
72 | , hpTheirSessionKeyPublic :: PublicKey | 187 | , hpTheirSessionKeyPublic :: PublicKey |
73 | , hpMySecretKey :: SecretKey | 188 | , hpMySecretKey :: SecretKey |
74 | , hpCookieRemotePubkey :: PublicKey | 189 | , hpCookieRemotePubkey :: PublicKey |
75 | , hpCookieRemoteDhtkey :: PublicKey | 190 | , hpCookieRemoteDhtkey :: PublicKey |
76 | } | 191 | } |
77 | newHandShakeData :: TransportCrypto -> HandshakeParams -> HandshakeData | 192 | newHandShakeData :: TransportCrypto -> Nonce24 -> HandshakeParams -> SockAddr -> IO (Maybe HandshakeData) |
78 | newHandShakeData = error "todo" | 193 | newHandShakeData crypto basenonce (HParam {hpOtherCookie,hpMySecretKey,hpCookieRemotePubkey,hpCookieRemoteDhtkey}) addr |
194 | = do | ||
195 | freshCookie | ||
196 | <- case nodeInfo (key2id hpCookieRemoteDhtkey) addr of | ||
197 | Right nodeinfo -> Just <$> cookieRequestH crypto nodeinfo (CookieRequest hpCookieRemotePubkey) | ||
198 | Left er -> return Nothing | ||
199 | let hinit = hashInit | ||
200 | Cookie n24 encrypted = hpOtherCookie | ||
201 | hctx = hashUpdate hinit n24 | ||
202 | hctx' = hashUpdate hctx encrypted | ||
203 | digest = hashFinalize hctx' | ||
204 | -- parameters addr {--> SockAddr -} | ||
205 | -- mbcookie <- case hpOtherCookie of | ||
206 | -- Nothing -> case (nodeInfo hpCookieRemoteDhtkey addr) of | ||
207 | -- Right nodeinfo -> cookieRequest crypto netCryptoDHTClient (toPublic hpMySecretKey) nodeinfo | ||
208 | -- Left er -> return Nothing | ||
209 | -- Just c -> return (Just c) | ||
210 | |||
211 | return $ | ||
212 | fmap (\freshCookie' -> | ||
213 | HandshakeData | ||
214 | { baseNonce = basenonce | ||
215 | , sessionKey = toPublic hpMySecretKey | ||
216 | , cookieHash = digest | ||
217 | , otherCookie = freshCookie' | ||
218 | }) freshCookie | ||
219 | |||
220 | type XMessage = CryptoMessage -- todo | ||
221 | |||
222 | ncToWire :: STM (State,Nonce24,TVar MsgOutMap) | ||
223 | -> Word32{- packet number we expect to recieve -} | ||
224 | -> Word32{- buffer_end -} | ||
225 | -> Word32{- packet number -} | ||
226 | -> XMessage | ||
227 | -> STM (Maybe (CryptoPacket Encrypted,Word32{-next packet no-})) | ||
228 | ncToWire getState seqno bufend pktno msg = do | ||
229 | let typ = getMessageType msg | ||
230 | typ64 = toWord64 typ | ||
231 | let lsness msg = | ||
232 | case typ of | ||
233 | Msg mid -> lossyness mid | ||
234 | GrpMsg KnownLossy _ -> Lossy | ||
235 | GrpMsg KnownLossless _ -> Lossless | ||
236 | (state,n24,msgOutMapVar) <- getState | ||
237 | msgOutMap <- readTVar msgOutMapVar | ||
238 | case msgOutMapLookup typ64 msgOutMap of | ||
239 | Just outid -> do | ||
240 | let setMessageId (OneByte _) mid = OneByte (toEnum8 mid) | ||
241 | setMessageId (TwoByte _ x) mid = TwoByte (toEnum8 mid) x | ||
242 | setMessageId (UpToN _ x) mid = UpToN (toEnum8 mid) x | ||
243 | msg' = setMessageId msg outid | ||
244 | in case lsness msg of | ||
245 | UnknownLossyness -> return Nothing | ||
246 | Lossy -> let cd = | ||
247 | CryptoData | ||
248 | { bufferStart = seqno | ||
249 | , bufferEnd = bufend | ||
250 | , bufferData = msg' | ||
251 | } | ||
252 | plain = encodePlain cd | ||
253 | encrypted = encrypt state plain | ||
254 | pkt = CryptoPacket { pktNonce = nonce24ToWord16 n24, pktData = encrypted } | ||
255 | in return (Just (pkt, pktno)) | ||
256 | Lossless -> let cd = | ||
257 | CryptoData | ||
258 | { bufferStart = seqno | ||
259 | , bufferEnd = pktno | ||
260 | , bufferData = msg' | ||
261 | } | ||
262 | plain = encodePlain cd | ||
263 | encrypted = encrypt state plain | ||
264 | pkt = CryptoPacket { pktNonce = nonce24ToWord16 n24, pktData = encrypted } | ||
265 | in return (Just (pkt, pktno+1)) | ||
79 | 266 | ||
80 | -- | called when we recieve a crypto handshake with valid cookie | 267 | -- | called when we recieve a crypto handshake with valid cookie |
268 | -- TODO set priority on contact addr to 0 if it is older than ForgetPeriod, | ||
269 | -- then increment it regardless. (Keep addr in MinMaxPSQ in Roster.Contact) | ||
81 | freshCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> IO () | 270 | freshCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> IO () |
82 | freshCryptoSession sessions | 271 | freshCryptoSession sessions |
83 | addr | 272 | addr |
84 | hp@(HParam | 273 | hp@(HParam |
85 | { hpTheirBaseNonce = Just theirBaseNonce | 274 | { hpTheirBaseNonce = Just theirBaseNonce |
86 | , hpOtherCookie = Just otherCookie | 275 | , hpOtherCookie = otherCookie |
87 | , hpTheirSessionKeyPublic = theirSessionKey | 276 | , hpTheirSessionKeyPublic = theirSessionKey |
88 | , hpMySecretKey = key | 277 | , hpMySecretKey = key |
89 | , hpCookieRemotePubkey = remotePublicKey | 278 | , hpCookieRemotePubkey = remotePublicKey |
@@ -91,26 +280,48 @@ freshCryptoSession sessions | |||
91 | }) = do | 280 | }) = do |
92 | let crypto = transportCrypto sessions | 281 | let crypto = transportCrypto sessions |
93 | allsessions = netCryptoSessions sessions | 282 | allsessions = netCryptoSessions sessions |
283 | allsessionsByKey = netCryptoSessionsByKey sessions | ||
284 | sessionId <- atomically $ do | ||
285 | x <- readTVar (nextSessionId sessions) | ||
286 | modifyTVar (nextSessionId sessions) (+1) | ||
287 | return x | ||
94 | ncState0 <- atomically $ newTVar Accepted | 288 | ncState0 <- atomically $ newTVar Accepted |
95 | ncTheirBaseNonce0 <- atomically $ newTVar theirBaseNonce | 289 | ncTheirBaseNonce0 <- atomically $ newTVar theirBaseNonce |
96 | n24 <- atomically $ transportNewNonce crypto | 290 | n24 <- atomically $ transportNewNonce crypto |
97 | state <- lookupSharedSecret crypto key remoteDhtPublicKey n24 | 291 | state <- lookupSharedSecret crypto key remoteDhtPublicKey n24 |
98 | let myhandshakeData = newHandShakeData crypto hp | 292 | newBaseNonce <- atomically $ transportNewNonce crypto |
99 | plain = encodePlain myhandshakeData | 293 | mbMyhandshakeData <- newHandShakeData crypto newBaseNonce hp addr |
100 | encrypted = encrypt state plain | 294 | let encodeHandshake myhandshakeData = let plain = encodePlain myhandshakeData |
101 | myhandshake = Handshake { handshakeCookie = otherCookie | 295 | -- state = computeSharedSecret key remoteDhtPublicKey n24 |
102 | , handshakeNonce = n24 | 296 | encrypted = encrypt state plain |
103 | , handshakeData = encrypted | 297 | in Handshake { handshakeCookie = otherCookie |
104 | } | 298 | , handshakeNonce = n24 |
105 | ncMyPacketNonce0 <- atomically $ newTVar (baseNonce myhandshakeData) | 299 | , handshakeData = encrypted |
106 | ncHandShake0 <- atomically $ newTVar (Just myhandshake) | 300 | } |
301 | let myhandshake= encodeHandshake <$> mbMyhandshakeData | ||
302 | ncMyPacketNonce0 <- atomically $ newTVar newBaseNonce | ||
303 | ncHandShake0 <- atomically $ newTVar myhandshake | ||
107 | cookie0 <- atomically $ newTVar (Just otherCookie) | 304 | cookie0 <- atomically $ newTVar (Just otherCookie) |
108 | newsession <- generateSecretKey | 305 | newsession <- generateSecretKey |
109 | ncHooks0 <- atomically $ newTVar (defaultHooks sessions) | 306 | ncHooks0 <- atomically $ newTVar (defaultHooks sessions) |
110 | ncUnrecognizedHook0 <- atomically $ newTVar (defaultUnrecognizedHook sessions) | 307 | ncUnrecognizedHook0 <- atomically $ newTVar (defaultUnrecognizedHook sessions) |
111 | ncGroups0 <- atomically $ newTVar (Map.empty) | 308 | ncIncomingTypeArray0 <- atomically $ newTVar (msgTypeArray sessions) |
112 | let netCryptoSession = | 309 | ncOutgoingIdMap0 <- atomically $ newTVar W64.empty |
310 | ncView0 <- atomically $ newTVar (sessionView sessions) | ||
311 | pktq <- atomically $ PQ.new (inboundQueueCapacity sessions) 0 | ||
312 | bufstart <- atomically $ newTVar 0 | ||
313 | let toWireIO = do | ||
314 | f <- lookupNonceFunction crypto newsession theirSessionKey | ||
315 | atomically $ do | ||
316 | n24 <- readTVar ncMyPacketNonce0 | ||
317 | let n24plus1 = unsafeDupablePerformIO (incrementNonce24 n24) | ||
318 | writeTVar ncMyPacketNonce0 n24plus1 | ||
319 | return (return (f n24, n24, ncOutgoingIdMap0)) | ||
320 | pktoq <- atomically $ PQ.newOutGoing pktq ncToWire toWireIO 0 (outboundQueueCapacity sessions) 0 | ||
321 | let netCryptoSession0 = | ||
113 | NCrypto { ncState = ncState0 | 322 | NCrypto { ncState = ncState0 |
323 | , ncSessionId = sessionId | ||
324 | , ncTheirPublicKey = remotePublicKey | ||
114 | , ncTheirBaseNonce= ncTheirBaseNonce0 | 325 | , ncTheirBaseNonce= ncTheirBaseNonce0 |
115 | , ncMyPacketNonce = ncMyPacketNonce0 | 326 | , ncMyPacketNonce = ncMyPacketNonce0 |
116 | , ncHandShake = ncHandShake0 | 327 | , ncHandShake = ncHandShake0 |
@@ -122,9 +333,28 @@ freshCryptoSession sessions | |||
122 | , ncHooks = ncHooks0 | 333 | , ncHooks = ncHooks0 |
123 | , ncUnrecognizedHook = ncUnrecognizedHook0 | 334 | , ncUnrecognizedHook = ncUnrecognizedHook0 |
124 | , ncAllSessions = sessions | 335 | , ncAllSessions = sessions |
125 | , ncGroups = ncGroups0 | 336 | , ncIncomingTypeArray = ncIncomingTypeArray0 |
337 | , ncOutgoingIdMap = ncOutgoingIdMap0 | ||
338 | , ncView = ncView0 | ||
339 | , ncPacketQueue = pktq | ||
340 | , ncBufferStart = bufstart | ||
341 | , ncDequeueThread = Nothing -- error "you want the NetCrypto-Dequeue thread id, but is it started?" | ||
342 | , ncOutgoingQueue = pktoq | ||
126 | } | 343 | } |
127 | atomically $ modifyTVar allsessions (Map.insert addr netCryptoSession) | 344 | threadid <- forkIO $ do |
345 | tid <- myThreadId | ||
346 | labelThread tid ("NetCryptoDequeue." ++ show (key2id remotePublicKey)) | ||
347 | fix $ \loop -> do | ||
348 | cd <- atomically $ PQ.dequeue pktq | ||
349 | _ <- runCryptoHook (netCryptoSession0 {ncDequeueThread=Just tid}) cd | ||
350 | loop | ||
351 | let netCryptoSession = netCryptoSession0 {ncDequeueThread=Just threadid} | ||
352 | atomically $ do | ||
353 | modifyTVar allsessions (Map.insert addr netCryptoSession) | ||
354 | byKeyResult <- readTVar allsessionsByKey >>= return . Map.lookup remotePublicKey | ||
355 | case byKeyResult of | ||
356 | Nothing -> modifyTVar allsessionsByKey (Map.insert remotePublicKey [netCryptoSession]) | ||
357 | Just xs -> modifyTVar allsessionsByKey (Map.insert remotePublicKey (netCryptoSession:xs)) | ||
128 | 358 | ||
129 | -- | Called when we get a handshake, but there's already a session entry. | 359 | -- | Called when we get a handshake, but there's already a session entry. |
130 | updateCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> NetCryptoSession -> IO () | 360 | updateCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> NetCryptoSession -> IO () |
@@ -137,7 +367,7 @@ updateCryptoSession sessions addr hp session = do | |||
137 | -- duplicate handshake packet, otherwise disregard everything, and | 367 | -- duplicate handshake packet, otherwise disregard everything, and |
138 | -- refresh all state. | 368 | -- refresh all state. |
139 | -- | 369 | -- |
140 | then when ( Just ncTheirBaseNonce0 /= hpTheirBaseNonce hp | 370 | then when ( Just ncTheirBaseNonce0 /= hpTheirBaseNonce hp -- XXX: Do we really want to compare base nonce here? |
141 | || ncTheirDHTKey session /= hpCookieRemoteDhtkey hp | 371 | || ncTheirDHTKey session /= hpCookieRemoteDhtkey hp |
142 | ) $ freshCryptoSession sessions addr hp | 372 | ) $ freshCryptoSession sessions addr hp |
143 | else if ( Just ncTheirBaseNonce0 /= hpTheirBaseNonce hp) | 373 | else if ( Just ncTheirBaseNonce0 /= hpTheirBaseNonce hp) |
@@ -172,11 +402,12 @@ cryptoNetHandler sessions addr (NetHandshake (Handshake (Cookie n24 ecookie) non | |||
172 | digest = hashFinalize hctx' | 402 | digest = hashFinalize hctx' |
173 | guard (cookieHash == digest) | 403 | guard (cookieHash == digest) |
174 | -- known friend? | 404 | -- known friend? |
175 | -- todo | 405 | -- todo TODO, see Roster.hs, |
406 | -- talk to not yet existent Network-Manager to ascertain current permissions | ||
176 | return | 407 | return |
177 | HParam | 408 | HParam |
178 | { hpTheirBaseNonce = Just baseNonce | 409 | { hpTheirBaseNonce = Just baseNonce |
179 | , hpOtherCookie = Just otherCookie | 410 | , hpOtherCookie = otherCookie |
180 | , hpTheirSessionKeyPublic = sessionKey | 411 | , hpTheirSessionKeyPublic = sessionKey |
181 | , hpMySecretKey = key | 412 | , hpMySecretKey = key |
182 | , hpCookieRemotePubkey = remotePubkey | 413 | , hpCookieRemotePubkey = remotePubkey |
@@ -186,7 +417,7 @@ cryptoNetHandler sessions addr (NetHandshake (Handshake (Cookie n24 ecookie) non | |||
186 | Left _ -> return () | 417 | Left _ -> return () |
187 | Right hp@(HParam | 418 | Right hp@(HParam |
188 | { hpTheirBaseNonce = Just theirBaseNonce | 419 | { hpTheirBaseNonce = Just theirBaseNonce |
189 | , hpOtherCookie = Just otherCookie | 420 | , hpOtherCookie = otherCookie |
190 | , hpTheirSessionKeyPublic = theirSessionKey | 421 | , hpTheirSessionKeyPublic = theirSessionKey |
191 | , hpMySecretKey = key | 422 | , hpMySecretKey = key |
192 | , hpCookieRemotePubkey = remotePublicKey | 423 | , hpCookieRemotePubkey = remotePublicKey |
@@ -207,7 +438,7 @@ cryptoNetHandler sessions addr (NetCrypto (CryptoPacket nonce16 encrypted)) = do | |||
207 | -- Handle Encrypted Message | 438 | -- Handle Encrypted Message |
208 | case Map.lookup addr sessionsmap of | 439 | case Map.lookup addr sessionsmap of |
209 | Nothing -> return Nothing -- drop packet, we have no session | 440 | Nothing -> return Nothing -- drop packet, we have no session |
210 | Just session@(NCrypto {ncState, ncHooks,ncSessionSecret,ncTheirSessionPublic,ncTheirBaseNonce}) -> do | 441 | Just session@(NCrypto {ncIncomingTypeArray, ncState, ncPacketQueue, ncHooks,ncSessionSecret,ncTheirSessionPublic,ncTheirBaseNonce}) -> do |
211 | theirBaseNonce <- atomically $ readTVar ncTheirBaseNonce | 442 | theirBaseNonce <- atomically $ readTVar ncTheirBaseNonce |
212 | -- Try to decrypt message | 443 | -- Try to decrypt message |
213 | let diff :: Word16 | 444 | let diff :: Word16 |
@@ -236,32 +467,18 @@ cryptoNetHandler sessions addr (NetCrypto (CryptoPacket nonce16 encrypted)) = do | |||
236 | writeTVar ncTheirBaseNonce y | 467 | writeTVar ncTheirBaseNonce y |
237 | -- then set session confirmed, | 468 | -- then set session confirmed, |
238 | atomically $ writeTVar ncState Confirmed | 469 | atomically $ writeTVar ncState Confirmed |
239 | hookmap <- atomically $ readTVar ncHooks | 470 | msgTypes <- atomically $ readTVar ncIncomingTypeArray |
240 | -- run hook | 471 | let msgTyp = cd ^. messageType |
241 | flip fix (cd,hookmap) $ \lookupAgain (cd,hookmap) -> do | 472 | msgTypMapped16 = msgTypes A.! fromEnum8 (msgID cm) |
242 | let msgTyp = cd ^. messageType | 473 | msgTypMapped = fromWord16 $ msgTypMapped16 |
243 | case Map.lookup msgTyp hookmap of | 474 | isLossy (GrpMsg KnownLossy _) = True |
244 | Nothing -> do -- no recognizing hook, run ncUnrecognizedHook0, loopAgain on result | 475 | isLossy (Msg mid) | lossyness mid == Lossy = True |
245 | unrecognize <- atomically $ readTVar (ncUnrecognizedHook session) | 476 | isLossy _ = False |
246 | mbConsume <- unrecognize msgTyp session cd | 477 | if isLossy msgTypMapped |
247 | case mbConsume of | 478 | then do atomically $ PQ.observeOutOfBand ncPacketQueue bufferEnd |
248 | Just f -> do | 479 | runCryptoHook session cd |
249 | -- ncUnrecognizedHook0 may have updated the hookmap | 480 | else do atomically $ PQ.enqueue ncPacketQueue bufferEnd cd |
250 | hookmap' <- atomically $ readTVar ncHooks | 481 | return Nothing |
251 | lookupAgain (f cd,hookmap') | ||
252 | Nothing -> return Nothing | ||
253 | Just hooks -> flip fix (hooks,cd,msgTyp) $ \loop (hooks,cd,typ) -> do | ||
254 | let _ = cd :: CryptoData | ||
255 | case (hooks,cd) of | ||
256 | ([],_) -> return Nothing | ||
257 | (hook:more,cd) -> do | ||
258 | r <- hook session cd :: IO (Maybe (CryptoData -> CryptoData)) | ||
259 | case r of | ||
260 | Just f -> let newcd = f cd | ||
261 | newtyp = newcd ^. messageType | ||
262 | in if newtyp == typ then loop (more,newcd,newtyp) | ||
263 | else lookupAgain (newcd,hookmap) | ||
264 | Nothing -> return Nothing -- message consumed | ||
265 | where | 482 | where |
266 | last2Bytes :: Nonce24 -> Word | 483 | last2Bytes :: Nonce24 -> Word |
267 | last2Bytes (Nonce24 bs) = case S.decode (B.drop 22 bs) of | 484 | last2Bytes (Nonce24 bs) = case S.decode (B.drop 22 bs) of |
@@ -269,6 +486,79 @@ cryptoNetHandler sessions addr (NetCrypto (CryptoPacket nonce16 encrypted)) = do | |||
269 | _ -> error "unreachable-last2Bytes" | 486 | _ -> error "unreachable-last2Bytes" |
270 | dATA_NUM_THRESHOLD = 21845 -- = 65535 / 3 | 487 | dATA_NUM_THRESHOLD = 21845 -- = 65535 / 3 |
271 | 488 | ||
489 | -- | TODO: make this accept CrytpoMessage instead | ||
490 | runCryptoHook :: NetCryptoSession -> CryptoData -> IO (Maybe (x -> x)) | ||
491 | runCryptoHook session@(NCrypto {ncState, ncHooks,ncSessionSecret,ncTheirSessionPublic,ncTheirBaseNonce,ncIncomingTypeArray}) | ||
492 | cd@(CryptoData {bufferStart, bufferEnd, bufferData=cm}) = do | ||
493 | hookmap <- atomically $ readTVar ncHooks | ||
494 | -- run hook | ||
495 | flip fix (cd,hookmap) $ \lookupAgain (cd,hookmap) -> do | ||
496 | msgTypes <- atomically $ readTVar ncIncomingTypeArray | ||
497 | let msgTyp = cd ^. messageType | ||
498 | msgTypMapped16 = msgTypes A.! fromEnum8 (msgID cm) | ||
499 | msgTypMapped = fromWord16 $ msgTypMapped16 | ||
500 | if msgTypMapped16 == 0 | ||
501 | then return Nothing | ||
502 | else | ||
503 | case Map.lookup msgTypMapped hookmap of | ||
504 | Nothing -> do -- no recognizing hook, run ncUnrecognizedHook0, loopAgain on result | ||
505 | unrecognize <- atomically $ readTVar (ncUnrecognizedHook session) | ||
506 | mbConsume <- unrecognize msgTypMapped session cd | ||
507 | case mbConsume of | ||
508 | Just f -> do | ||
509 | -- ncUnrecognizedHook0 may have updated the hookmap | ||
510 | hookmap' <- atomically $ readTVar ncHooks | ||
511 | lookupAgain (f cd,hookmap') | ||
512 | Nothing -> return Nothing | ||
513 | Just hooks -> flip fix (hooks,cd,msgTypMapped) $ \loop (hooks,cd,typ) -> do | ||
514 | let _ = cd :: CryptoData | ||
515 | case (hooks,cd) of | ||
516 | ([],_) -> return Nothing | ||
517 | (hook:more,cd) -> do | ||
518 | r <- hook session cd :: IO (Maybe (CryptoData -> CryptoData)) | ||
519 | case r of | ||
520 | Just f -> let newcd = f cd | ||
521 | newtyp = newcd ^. messageType | ||
522 | in if newtyp == typ then loop (more,newcd,newtyp) | ||
523 | else lookupAgain (newcd,hookmap) | ||
524 | Nothing -> return Nothing -- message consumed | ||
525 | |||
526 | -- | construct a 'MsgTypeArray' for specified types, using their known common positions | ||
527 | -- in the MessageId space if they have such a thing. | ||
528 | mkMsgTypes :: [MessageType] -> MsgTypeArray | ||
529 | mkMsgTypes msgs = let zeros = A.listArray (0,255) (replicate 256 0) | ||
530 | in zeros A.// map (\x -> (toIndex x,toWord16 x)) msgs | ||
531 | where | ||
532 | toIndex (Msg mid) = fromIntegral . fromEnum $ mid | ||
533 | toIndex (GrpMsg KnownLossless nam) = 0x63 -- fromEnum MESSAGE_GROUPCHAT | ||
534 | toIndex (GrpMsg KnownLossy nam) = 0xC7 -- fromEnum LOSSY_GROUPCHAT | ||
535 | |||
536 | -- | Handle all Tox messages that this code base is aware of. | ||
537 | allMsgTypes :: MsgTypeArray | ||
538 | allMsgTypes = A.listArray (minBound,maxBound) (0:knownMsgs) | ||
539 | where | ||
540 | knownMsgs :: [Word16] | ||
541 | knownMsgs = | ||
542 | concat [ map (fromIntegral . fromEnum) [ PacketRequest .. KillPacket ] | ||
543 | , map (const 0) [ 3 .. 15 ] -- UnspecifiedPacket | ||
544 | , map (const 0) [ 16 .. 23 ] -- MessengerLoseless | ||
545 | , map (fromIntegral . fromEnum) [ ONLINE .. OFFLINE ] | ||
546 | , map (const 0) [ 26 .. 47 ] -- MessengerLoseless | ||
547 | , map (fromIntegral . fromEnum) [ NICKNAME .. TYPING ] | ||
548 | , map (const 0) [ 52 .. 63 ] -- MessengerLoseless | ||
549 | , map (fromIntegral . fromEnum) [ MESSAGE .. ACTION ] | ||
550 | , map (const 0) [ 66 .. 68 ] -- MessengerLoseless | ||
551 | , map (fromIntegral . fromEnum) [ MSI ] | ||
552 | , map (const 0) [ 70 .. 79 ] -- MessengerLoseless | ||
553 | , map (fromIntegral . fromEnum) [ FILE_SENDREQUEST .. FILE_DATA ] | ||
554 | , map (const 0) [ 83 .. 95 ] -- MessengerLoseless | ||
555 | , map (fromIntegral . fromEnum) [ INVITE_GROUPCHAT .. MESSAGE_GROUPCHAT ] | ||
556 | , map (const 0) [ 100 .. 191 ] -- MessengerLoseless | ||
557 | , map (const 0) [ 192 .. 198 ] -- MessengerLossy | ||
558 | , map (fromIntegral . fromEnum) [ LOSSY_GROUPCHAT ] | ||
559 | , map (const 0) [ 200 .. 255 ] -- All lossy, exept the last | ||
560 | ] | ||
561 | |||
272 | -- | handles nothing | 562 | -- | handles nothing |
273 | defaultCryptoDataHooks :: Map.Map MessageType [NetCryptoHook] | 563 | defaultCryptoDataHooks :: Map.Map MessageType [NetCryptoHook] |
274 | defaultCryptoDataHooks = Map.empty | 564 | defaultCryptoDataHooks = Map.empty |
diff --git a/src/Network/Tox/Crypto/Transport.hs b/src/Network/Tox/Crypto/Transport.hs index 8739c853..3133ee9b 100644 --- a/src/Network/Tox/Crypto/Transport.hs +++ b/src/Network/Tox/Crypto/Transport.hs | |||
@@ -21,6 +21,9 @@ module Network.Tox.Crypto.Transport | |||
21 | , TypingStatus(..) | 21 | , TypingStatus(..) |
22 | , GroupChatId(..) | 22 | , GroupChatId(..) |
23 | , MessageType(..) | 23 | , MessageType(..) |
24 | , KnownLossyness(..) | ||
25 | , AsWord16(..) | ||
26 | , AsWord64(..) | ||
24 | -- feild name classes | 27 | -- feild name classes |
25 | , HasGroupChatID(..) | 28 | , HasGroupChatID(..) |
26 | , HasGroupNumber(..) | 29 | , HasGroupNumber(..) |
@@ -46,6 +49,9 @@ module Network.Tox.Crypto.Transport | |||
46 | , isIndirectGrpChat | 49 | , isIndirectGrpChat |
47 | , LossyOrLossless(..) | 50 | , LossyOrLossless(..) |
48 | , lossyness | 51 | , lossyness |
52 | , fromEnum8 | ||
53 | , fromEnum16 | ||
54 | , toEnum8 | ||
49 | ) where | 55 | ) where |
50 | 56 | ||
51 | import Crypto.Tox | 57 | import Crypto.Tox |
@@ -59,6 +65,7 @@ import Data.ByteString as B | |||
59 | import Data.Maybe | 65 | import Data.Maybe |
60 | import Data.Monoid | 66 | import Data.Monoid |
61 | import Data.Word | 67 | import Data.Word |
68 | import Data.Bits | ||
62 | import Crypto.Hash | 69 | import Crypto.Hash |
63 | import Control.Lens | 70 | import Control.Lens |
64 | import Data.Text as T | 71 | import Data.Text as T |
@@ -147,10 +154,38 @@ data CryptoData = CryptoData | |||
147 | -- | [ uint32_t packet number if lossless | 154 | -- | [ uint32_t packet number if lossless |
148 | -- , sendbuffer buffer_end if lossy , (big endian)] | 155 | -- , sendbuffer buffer_end if lossy , (big endian)] |
149 | , bufferEnd :: Word32 | 156 | , bufferEnd :: Word32 |
150 | -- | [data] | 157 | -- | [data] (TODO See Note [Padding]) |
151 | , bufferData :: CryptoMessage | 158 | , bufferData :: CryptoMessage |
152 | } | 159 | } |
153 | 160 | ||
161 | {- | ||
162 | Note [Padding] | ||
163 | |||
164 | TODO: The 'bufferData' field of 'CryptoData' should probably be something like | ||
165 | /Padded CryptoMessage/ because c-toxcore strips leading zeros on incoming and | ||
166 | pads leading zeros on outgoing packets. | ||
167 | |||
168 | After studying c-toxcore (at commit c49a6e7f5bc245a51a3c85cc2c8b7f881c412998), | ||
169 | I've determined the following behavior. | ||
170 | |||
171 | Incoming: All leading zero bytes are stripped until possibly the whole packet | ||
172 | is consumed (in which case it is discarded). This happens at | ||
173 | toxcore/net_crypto.c:1366:handle_data_packet_core(). | ||
174 | |||
175 | Outgoing: The number of zeros added is: | ||
176 | |||
177 | padding_length len = (1373 - len) `mod` 8 where | ||
178 | |||
179 | where /len/ is the size of the non-padded CryptoMessage. This happens at | ||
180 | toxcore/net_crypto.c:936:send_data_packet_helper() | ||
181 | |||
182 | The number 1373 is written in C as MAX_CRYPTO_DATA_SIZE which is defined in | ||
183 | terms of the max /NetCrypto/ packet size (1400) minus the minimum possible size | ||
184 | of an id-byte (1) and a /CryptoPacket Encrypted/ ( 2 + 4 + 4 + 16 ). | ||
185 | |||
186 | One effect of this is that short messages will be padded to at least 5 bytes. | ||
187 | -} | ||
188 | |||
154 | instance Serialize CryptoData where | 189 | instance Serialize CryptoData where |
155 | get = CryptoData <$> get <*> get <*> get | 190 | get = CryptoData <$> get <*> get <*> get |
156 | put (CryptoData start end dta) = put start >> put end >> put dta | 191 | put (CryptoData start end dta) = put start >> put end >> put dta |
@@ -403,15 +438,61 @@ instance HasMessageName CryptoMessage where | |||
403 | messageName :: (Functor f, HasMessageName x) => (MessageName -> f MessageName) -> (x -> f x) | 438 | messageName :: (Functor f, HasMessageName x) => (MessageName -> f MessageName) -> (x -> f x) |
404 | messageName = lens getMessageName setMessageName | 439 | messageName = lens getMessageName setMessageName |
405 | 440 | ||
441 | data KnownLossyness = KnownLossy | KnownLossless | ||
442 | deriving (Eq,Ord,Show,Enum) | ||
443 | |||
406 | data MessageType = Msg MessageID | 444 | data MessageType = Msg MessageID |
407 | | GrpMsg MessageName | 445 | | GrpMsg KnownLossyness MessageName |
408 | deriving (Eq,Show) | 446 | deriving (Eq,Show) |
409 | 447 | ||
448 | class AsWord16 a where | ||
449 | toWord16 :: a -> Word16 | ||
450 | fromWord16 :: Word16 -> a | ||
451 | |||
452 | class AsWord64 a where | ||
453 | toWord64 :: a -> Word64 | ||
454 | fromWord64 :: Word64 -> a | ||
455 | |||
456 | |||
457 | toEnum8 :: (Enum a, Integral word8) => word8 -> a | ||
458 | toEnum8 = toEnum . fromIntegral | ||
459 | fromEnum8 :: Enum a => a -> Word8 | ||
460 | fromEnum8 = fromIntegral . fromEnum | ||
461 | |||
462 | fromEnum16 :: Enum a => a -> Word16 | ||
463 | fromEnum16 = fromIntegral . fromEnum | ||
464 | |||
465 | fromEnum64 :: Enum a => a -> Word64 | ||
466 | fromEnum64 = fromIntegral . fromEnum | ||
467 | |||
468 | |||
469 | -- MessageType, for our client keep it inside 16 bits | ||
470 | -- but we should extend it to 32 or even 64 on the wire. | ||
471 | -- Bits: 000000glxxxxxxxx, x = message id or extension specific, l = if extended, lossy/lossless, g = if extended, nongroup/group | ||
472 | -- (at least one bit set in high byte means extended, if none but the g flag and possibly l flag, assume default grp extension) | ||
473 | instance AsWord16 MessageType where | ||
474 | toWord16 (Msg msgID) = fromIntegral (fromIntegral (fromEnum16 msgID) :: Word8) | ||
475 | toWord16 (GrpMsg lsy msgName) = 512 + 256 * (fromEnum16 lsy) + fromIntegral (fromEnum8 msgName) | ||
476 | fromWord16 x | x < 256 = Msg (toEnum $ fromIntegral x) | ||
477 | fromWord16 x | x < 1024, x .|. 0x0200 == 0x0200 = GrpMsg (toEnum8 ((x - 512) `div` 256)) (toEnum8 x) | ||
478 | fromWord16 x = error "Not clear how to convert Word16 to MessageType" | ||
479 | |||
480 | instance AsWord64 MessageType where | ||
481 | toWord64 (Msg msgID) = fromIntegral (fromIntegral (fromEnum16 msgID) :: Word8) | ||
482 | toWord64 (GrpMsg lsy msgName) = 512 + 256 * (fromEnum64 lsy) + fromIntegral (fromEnum8 msgName) | ||
483 | fromWord64 x | x < 256 = Msg (toEnum $ fromIntegral x) | ||
484 | fromWord64 x | x < 1024, x .|. 0x0200 == 0x0200 = GrpMsg (toEnum8 ((x - 512) `div` 256)) (toEnum8 x) | ||
485 | fromWord64 x = error "Not clear how to convert Word64 to MessageType" | ||
486 | |||
487 | word16 :: (Functor f, AsWord16 x) => (Word16 -> f Word16) -> (x -> f x) | ||
488 | word16 = lens toWord16 (\_ x -> fromWord16 x) | ||
489 | |||
410 | instance Ord MessageType where | 490 | instance Ord MessageType where |
411 | compare (Msg x) (Msg y) = compare x y | 491 | compare (Msg x) (Msg y) = compare x y |
412 | compare (GrpMsg x) (GrpMsg y) = compare x y | 492 | compare (GrpMsg lx x) (GrpMsg ly y) = let r1 = compare lx ly |
413 | compare (Msg _) (GrpMsg _) = LT | 493 | in if r1==EQ then compare x y else r1 |
414 | compare (GrpMsg _) (Msg _) = GT | 494 | compare (Msg _) (GrpMsg _ _) = LT |
495 | compare (GrpMsg _ _) (Msg _) = GT | ||
415 | 496 | ||
416 | class HasMessageType x where | 497 | class HasMessageType x where |
417 | getMessageType :: x -> MessageType | 498 | getMessageType :: x -> MessageType |
@@ -420,13 +501,16 @@ class HasMessageType x where | |||
420 | instance HasMessageType CryptoMessage where | 501 | instance HasMessageType CryptoMessage where |
421 | getMessageType (OneByte mid) = Msg mid | 502 | getMessageType (OneByte mid) = Msg mid |
422 | getMessageType (TwoByte mid _) = Msg mid | 503 | getMessageType (TwoByte mid _) = Msg mid |
423 | getMessageType m@(UpToN mid _) | isIndirectGrpChat mid = GrpMsg (getMessageName m) | 504 | getMessageType m@(UpToN MESSAGE_GROUPCHAT _) = GrpMsg KnownLossless (getMessageName m) |
505 | getMessageType m@(UpToN LOSSY_GROUPCHAT _) = GrpMsg KnownLossy (getMessageName m) | ||
424 | getMessageType (UpToN mid _) = Msg mid | 506 | getMessageType (UpToN mid _) = Msg mid |
425 | 507 | ||
426 | setMessageType m@(UpToN mid _) (GrpMsg mname) | isIndirectGrpChat mid = setMessageName m mname | 508 | setMessageType (OneByte _ ) (GrpMsg KnownLossless mname) = setMessageName (UpToN MESSAGE_GROUPCHAT B.empty ) mname |
427 | setMessageType (OneByte _ ) (GrpMsg mname) = setMessageName (UpToN MESSAGE_GROUPCHAT B.empty ) mname | 509 | setMessageType (TwoByte _ x) (GrpMsg KnownLossless mname) = setMessageName (UpToN MESSAGE_GROUPCHAT (B.singleton x)) mname |
428 | setMessageType (TwoByte _ x) (GrpMsg mname) = setMessageName (UpToN MESSAGE_GROUPCHAT (B.singleton x)) mname | 510 | setMessageType (OneByte _ ) (GrpMsg KnownLossy mname) = setMessageName (UpToN LOSSY_GROUPCHAT B.empty ) mname |
429 | setMessageType (UpToN _ x) (GrpMsg mname) = setMessageName (UpToN MESSAGE_GROUPCHAT x) mname | 511 | setMessageType (TwoByte _ x) (GrpMsg KnownLossy mname) = setMessageName (UpToN LOSSY_GROUPCHAT (B.singleton x)) mname |
512 | setMessageType (UpToN _ x) (GrpMsg KnownLossless mname) = setMessageName (UpToN MESSAGE_GROUPCHAT x) mname | ||
513 | setMessageType (UpToN _ x) (GrpMsg KnownLossy mname) = setMessageName (UpToN LOSSY_GROUPCHAT x) mname | ||
430 | setMessageType m (Msg mid) | Just (True,1) <- msgSizeParam mid = OneByte mid | 514 | setMessageType m (Msg mid) | Just (True,1) <- msgSizeParam mid = OneByte mid |
431 | setMessageType (OneByte mid0 ) (Msg mid) | Just (True,2) <- msgSizeParam mid = TwoByte mid 0 | 515 | setMessageType (OneByte mid0 ) (Msg mid) | Just (True,2) <- msgSizeParam mid = TwoByte mid 0 |
432 | setMessageType (TwoByte mid0 x) (Msg mid) | Just (True,2) <- msgSizeParam mid = TwoByte mid x | 516 | setMessageType (TwoByte mid0 x) (Msg mid) | Just (True,2) <- msgSizeParam mid = TwoByte mid x |