summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dht-client.cabal3
-rw-r--r--src/Crypto/Tox.hs29
-rw-r--r--src/Data/PacketQueue.hs170
-rw-r--r--src/Network/Tox/Crypto/Handlers.hs396
-rw-r--r--src/Network/Tox/Crypto/Transport.hs104
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
299lookupSharedSecret :: TransportCrypto -> SecretKey -> PublicKey -> Nonce24 -> IO State 301lookupSharedSecret :: TransportCrypto -> SecretKey -> PublicKey -> Nonce24 -> IO State
300lookupSharedSecret TransportCrypto{secretsCache} sk recipient nonce = do 302lookupSharedSecret crypto sk recipient nonce
303 = ($ nonce) <$> lookupNonceFunction crypto sk recipient
304
305{-# INLINE lookupNonceFunction #-}
306lookupNonceFunction :: TransportCrypto -> SecretKey -> PublicKey -> IO (Nonce24 -> State)
307lookupNonceFunction 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.
316lookupNonceFunctionSTM :: POSIXTime -> TransportCrypto -> SecretKey -> PublicKey -> STM (Nonce24 -> State)
317lookupNonceFunctionSTM 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
320hsalsa20 :: (ByteArrayAccess t, ByteArrayAccess t1) => t1 -> t -> BA.ScrubbedBytes 335hsalsa20 :: (ByteArrayAccess t, ByteArrayAccess t1) => t1 -> t -> BA.ScrubbedBytes
@@ -328,7 +343,10 @@ hsalsa20 k n = BA.append a b
328 343
329 344
330newtype Nonce24 = Nonce24 ByteString 345newtype Nonce24 = Nonce24 ByteString
331 deriving (Eq, Ord, ByteArrayAccess,Data) 346 deriving (Eq, Ord, ByteArrayAccess, Data)
347
348nonce24ToWord16 :: Nonce24 -> Word16
349nonce24ToWord16 (Nonce24 n24) = fromIntegral (B.index n24 23) + 256 * fromIntegral (B.index n24 22)
332 350
333addtoNonce24 :: Nonce24 -> Word -> IO Nonce24 351addtoNonce24 :: Nonce24 -> Word -> IO Nonce24
334addtoNonce24 (Nonce24 n24) n = Nonce24 <$> BA.copy n24 init 352addtoNonce24 (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 #-}
375incrementNonce24 :: Nonce24 -> IO Nonce24 394incrementNonce24 :: Nonce24 -> IO Nonce24
376incrementNonce24 nonce24 = addtoNonce24 nonce24 1 395incrementNonce24 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 #-}
7module 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
21import Control.Concurrent.STM
22import Control.Concurrent.STM.TArray
23import Control.Monad
24import Data.Word
25import Data.Array.MArray
26
27data 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.
35new :: Word32 -- ^ Capacity of queue.
36 -> Word32 -- ^ Initial sequence number.
37 -> STM (PacketQueue a)
38new 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
50observeOutOfBand :: PacketQueue a -> Word32-> STM ()
51observeOutOfBand 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.
60dequeue :: PacketQueue a -> STM a
61dequeue 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.
72enqueue :: PacketQueue a -- ^ The packet queue.
73 -> Word32 -- ^ Sequence number of the packet.
74 -> a -- ^ The packet.
75 -> STM ()
76enqueue 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
92data 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
105mapOutGoing :: ((Word32,towire) -> Maybe (Word32,towire)) -> PacketOutQueue extra msg towire fromwire -> STM ()
106mapOutGoing 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
113newOutGoing :: 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)
123newOutGoing 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
134data 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.)
140tryAppendQueueOutgoing :: STM extra -> PacketOutQueue extra msg wire fromwire -> msg -> STM OutGoingResult
141tryAppendQueueOutgoing 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
162dequeueOutgoing :: PacketOutQueue extra msg wire fromwire -> STM (Word32,wire)
163dequeueOutgoing (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 #-}
3module Network.Tox.Crypto.Handlers where 4module Network.Tox.Crypto.Handlers where
4 5
6import Network.Tox.NodeId
5import Network.Tox.Crypto.Transport 7import Network.Tox.Crypto.Transport
6import Network.Tox.DHT.Transport (Cookie(..),CookieData(..)) 8import Network.Tox.DHT.Transport (Cookie(..),CookieData(..), CookieRequest(..))
9import Network.Tox.DHT.Handlers (Client, cookieRequest, cookieRequestH )
7import Crypto.Tox 10import Crypto.Tox
8import Control.Concurrent.STM 11import Control.Concurrent.STM
9import Network.Address 12import Network.Address
@@ -13,12 +16,25 @@ import Control.Applicative
13import Control.Monad 16import Control.Monad
14import Data.Time.Clock.POSIX 17import Data.Time.Clock.POSIX
15import qualified Data.ByteString as B 18import qualified Data.ByteString as B
19import Data.ByteString (ByteString)
16import Control.Lens 20import Control.Lens
17import Data.Function 21import Data.Function
22import qualified Data.PacketQueue as PQ
23 ;import Data.PacketQueue (PacketQueue)
18import Data.Serialize as S 24import Data.Serialize as S
19import Data.Word 25import Data.Word
26import qualified Data.Word64Map as W64
20import GHC.Conc (unsafeIOToSTM) 27import GHC.Conc (unsafeIOToSTM)
21import qualified Data.Set as Set 28import qualified Data.Set as Set
29import qualified Data.Array.Unboxed as A
30import SensibleDir
31import System.FilePath
32import System.IO.Temp
33import System.Environment
34import System.Directory
35import Control.Concurrent
36import GHC.Conc (labelThread)
37import System.IO.Unsafe(unsafeDupablePerformIO {- unsafeIOToSTM -})
22 38
23-- util, todo: move to another module 39-- util, todo: move to another module
24maybeToEither :: Maybe b -> Either String b 40maybeToEither :: Maybe b -> Either String b
@@ -31,59 +47,232 @@ data NetCryptoSessionStatus = Unaccepted | Accepted | Confirmed
31 47
32type IOHook addr x = addr -> x -> IO (Maybe (x -> x)) 48type IOHook addr x = addr -> x -> IO (Maybe (x -> x))
33type NetCryptoHook = IOHook NetCryptoSession CryptoData 49type NetCryptoHook = IOHook NetCryptoSession CryptoData
50type MsgTypeArray = A.UArray Word8 Word16
51type MsgOutMap = W64.Word64Map Word8
52-- type MsgOutMap = A.UArray Word64 Word8 -- if above is too slow, switch to this, but use reasonable bounds
53msgOutMapLookup :: Word64 -> MsgOutMap -> Maybe Word8
54msgOutMapLookup = 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--
59data 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
36data NetCryptoSession = NCrypto { ncState :: TVar NetCryptoSessionStatus 74type SessionID = Word64
75
76data 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
53data NetCryptoSessions = NCSessions { netCryptoSessions :: TVar (Map.Map SockAddr NetCryptoSession) 106data 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
59newSessionsState :: TransportCrypto -> (MessageType -> NetCryptoHook) -> Map.Map MessageType [NetCryptoHook] -> IO NetCryptoSessions 118forgetCrypto :: TransportCrypto -> NetCryptoSessions -> NetCryptoSession -> STM ()
119forgetCrypto 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
134netCrypto :: TransportCrypto -> NetCryptoSessions -> SecretKey -> PublicKey -> IO NetCryptoSession
135netCrypto 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
145newSessionsState :: TransportCrypto
146 -> (MessageType -> NetCryptoHook) -- ^ default hook
147 -> Map.Map MessageType [NetCryptoHook] -- ^ all hooks, can be empty to start
148 -> IO NetCryptoSessions
60newSessionsState crypto unrechook hooks = do 149newSessionsState 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
68data HandshakeParams 183data 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 }
77newHandShakeData :: TransportCrypto -> HandshakeParams -> HandshakeData 192newHandShakeData :: TransportCrypto -> Nonce24 -> HandshakeParams -> SockAddr -> IO (Maybe HandshakeData)
78newHandShakeData = error "todo" 193newHandShakeData 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
220type XMessage = CryptoMessage -- todo
221
222ncToWire :: 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-}))
228ncToWire 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)
81freshCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> IO () 270freshCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> IO ()
82freshCryptoSession sessions 271freshCryptoSession 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.
130updateCryptoSession :: NetCryptoSessions -> SockAddr -> HandshakeParams -> NetCryptoSession -> IO () 360updateCryptoSession :: 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
490runCryptoHook :: NetCryptoSession -> CryptoData -> IO (Maybe (x -> x))
491runCryptoHook 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.
528mkMsgTypes :: [MessageType] -> MsgTypeArray
529mkMsgTypes 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.
537allMsgTypes :: MsgTypeArray
538allMsgTypes = 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
273defaultCryptoDataHooks :: Map.Map MessageType [NetCryptoHook] 563defaultCryptoDataHooks :: Map.Map MessageType [NetCryptoHook]
274defaultCryptoDataHooks = Map.empty 564defaultCryptoDataHooks = 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
51import Crypto.Tox 57import Crypto.Tox
@@ -59,6 +65,7 @@ import Data.ByteString as B
59import Data.Maybe 65import Data.Maybe
60import Data.Monoid 66import Data.Monoid
61import Data.Word 67import Data.Word
68import Data.Bits
62import Crypto.Hash 69import Crypto.Hash
63import Control.Lens 70import Control.Lens
64import Data.Text as T 71import 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{-
162Note [Padding]
163
164TODO: The 'bufferData' field of 'CryptoData' should probably be something like
165/Padded CryptoMessage/ because c-toxcore strips leading zeros on incoming and
166pads leading zeros on outgoing packets.
167
168After studying c-toxcore (at commit c49a6e7f5bc245a51a3c85cc2c8b7f881c412998),
169I've determined the following behavior.
170
171Incoming: All leading zero bytes are stripped until possibly the whole packet
172is consumed (in which case it is discarded). This happens at
173toxcore/net_crypto.c:1366:handle_data_packet_core().
174
175Outgoing: The number of zeros added is:
176
177 padding_length len = (1373 - len) `mod` 8 where
178
179where /len/ is the size of the non-padded CryptoMessage. This happens at
180toxcore/net_crypto.c:936:send_data_packet_helper()
181
182The number 1373 is written in C as MAX_CRYPTO_DATA_SIZE which is defined in
183terms of the max /NetCrypto/ packet size (1400) minus the minimum possible size
184of an id-byte (1) and a /CryptoPacket Encrypted/ ( 2 + 4 + 4 + 16 ).
185
186One effect of this is that short messages will be padded to at least 5 bytes.
187-}
188
154instance Serialize CryptoData where 189instance 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
403messageName :: (Functor f, HasMessageName x) => (MessageName -> f MessageName) -> (x -> f x) 438messageName :: (Functor f, HasMessageName x) => (MessageName -> f MessageName) -> (x -> f x)
404messageName = lens getMessageName setMessageName 439messageName = lens getMessageName setMessageName
405 440
441data KnownLossyness = KnownLossy | KnownLossless
442 deriving (Eq,Ord,Show,Enum)
443
406data MessageType = Msg MessageID 444data MessageType = Msg MessageID
407 | GrpMsg MessageName 445 | GrpMsg KnownLossyness MessageName
408 deriving (Eq,Show) 446 deriving (Eq,Show)
409 447
448class AsWord16 a where
449 toWord16 :: a -> Word16
450 fromWord16 :: Word16 -> a
451
452class AsWord64 a where
453 toWord64 :: a -> Word64
454 fromWord64 :: Word64 -> a
455
456
457toEnum8 :: (Enum a, Integral word8) => word8 -> a
458toEnum8 = toEnum . fromIntegral
459fromEnum8 :: Enum a => a -> Word8
460fromEnum8 = fromIntegral . fromEnum
461
462fromEnum16 :: Enum a => a -> Word16
463fromEnum16 = fromIntegral . fromEnum
464
465fromEnum64 :: Enum a => a -> Word64
466fromEnum64 = 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)
473instance 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
480instance 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
487word16 :: (Functor f, AsWord16 x) => (Word16 -> f Word16) -> (x -> f x)
488word16 = lens toWord16 (\_ x -> fromWord16 x)
489
410instance Ord MessageType where 490instance 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
416class HasMessageType x where 497class HasMessageType x where
417 getMessageType :: x -> MessageType 498 getMessageType :: x -> MessageType
@@ -420,13 +501,16 @@ class HasMessageType x where
420instance HasMessageType CryptoMessage where 501instance 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