summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2017-08-27 18:59:23 -0400
committerjoe <joe@jerkface.net>2017-08-27 18:59:23 -0400
commit5472805a6a8fb3c3d64cbeff5bda1d78a898c602 (patch)
tree015eed92ebbbe72d3ed07b1959dc5d15719d91b2
parent396b6daf475b1769a214e0d3ee8b476ff415d2f9 (diff)
reworking... ToxTransport and related modules.
-rw-r--r--ToxAddress.hs383
-rw-r--r--ToxCrypto.hs203
-rw-r--r--ToxData.hs23
-rw-r--r--ToxPacket.hs76
-rw-r--r--ToxTransport.hs199
-rwxr-xr-xc2
6 files changed, 885 insertions, 1 deletions
diff --git a/ToxAddress.hs b/ToxAddress.hs
new file mode 100644
index 00000000..08c9031b
--- /dev/null
+++ b/ToxAddress.hs
@@ -0,0 +1,383 @@
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE CPP #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DeriveDataTypeable #-}
5{-# LANGUAGE DeriveFunctor #-}
6{-# LANGUAGE DeriveTraversable #-}
7{-# LANGUAGE ExistentialQuantification #-}
8{-# LANGUAGE FlexibleInstances #-}
9{-# LANGUAGE GADTs #-}
10{-# LANGUAGE GeneralizedNewtypeDeriving #-}
11{-# LANGUAGE KindSignatures #-}
12{-# LANGUAGE PatternSynonyms #-}
13{-# LANGUAGE ScopedTypeVariables #-}
14{-# LANGUAGE TupleSections #-}
15{-# LANGUAGE TypeApplications #-}
16module ToxAddress where
17
18import Control.Applicative
19import Control.Monad
20import qualified Data.Aeson as JSON
21 ;import Data.Aeson (FromJSON, ToJSON, (.=))
22import Data.Bits
23import Data.Bits.ByteString ()
24import Data.ByteArray as BA (ByteArrayAccess, Bytes)
25import qualified Data.ByteArray as BA
26import qualified Data.ByteString as B
27 ;import Data.ByteString (ByteString)
28import qualified Data.ByteString.Base16 as Base16
29import qualified Data.ByteString.Char8 as C8
30import Data.Char
31import Data.Data
32import Data.Hashable
33import Data.IP
34import Data.Serialize as S
35import Data.Word
36import Network.Address hiding (nodePort)
37import Network.Socket
38import qualified Text.ParserCombinators.ReadP as RP
39import Text.Read
40import GHC.TypeLits
41import Crypto.PubKey.Curve25519
42
43data Address
44 = DHTNode NodeInfo -- A direct DHT exchange.
45 | OnionFromOwner NodeInfo (ForwardPath 3) -- Your own created onion path.
46 | OnionToOwner NodeInfo (ReturnPath 3) -- An onion path end point.
47
48-- | perform io for hashes that do allocation and ffi.
49-- unsafeDupablePerformIO is used when possible as the
50-- computation is pure and the output is directly linked
51-- to the input. we also do not modify anything after it has
52-- been returned to the user.
53unsafeDoIO :: IO a -> a
54#if __GLASGOW_HASKELL__ > 704
55unsafeDoIO = unsafeDupablePerformIO
56#else
57unsafeDoIO = unsafePerformIO
58#endif
59
60unpackPublicKey :: PublicKey -> [Word64]
61unpackPublicKey bs = loop 0
62 where loop i
63 | i == 4 = []
64 | otherwise =
65 let !v = unsafeDoIO $ BA.withByteArray bs (\p -> peekElemOff p i)
66 in v : loop (i+1)
67
68newtype NodeId = NodeId PublicKey
69 deriving (Eq,ByteArrayAccess) -- (Eq,Ord,ByteArrayAccess, Bits, Hashable)
70
71instance Ord NodeId where
72 compare (NodeId a) (NodeId b) = compare (unpackPublicKey a) (unpackPublicKey b)
73
74zeroID :: NodeId
75zeroID = NodeId $ throwCryptoError $ publicKey $ B.replicate 32 0
76
77{-
78instance Read NodeId where
79 readsPrec _ str
80 | (bs, xs) <- Base16.decode $ C8.pack str
81 , B.length bs == 32
82 = [ (NodeId bs, drop 64 str) ]
83 | otherwise = []
84-}
85
86instance Show NodeId where
87 show (NodeId bs) = C8.unpack $ Base16.encode $ BA.convert bs
88
89instance S.Serialize NodeId where
90 get = NodeId . throwCryptoError . publicKey <$> S.getBytes 32
91 put (NodeId bs) = S.putByteString $ BA.convert bs
92
93data NodeInfo = NodeInfo
94 { nodeId :: NodeId
95 , nodeIP :: IP
96 , nodePort :: PortNumber
97 }
98 deriving (Eq,Ord)
99
100nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo
101nodeInfo nid saddr
102 | Just ip <- fromSockAddr saddr
103 , Just port <- sockAddrPort saddr = Right $ NodeInfo nid ip port
104 | otherwise = Left "Address family not supported."
105
106
107instance ToJSON NodeInfo where
108 toJSON (NodeInfo nid (IPv4 ip) port)
109 = JSON.object [ "public_key" .= show nid
110 , "ipv4" .= show ip
111 , "port" .= (fromIntegral port :: Int)
112 ]
113 toJSON (NodeInfo nid (IPv6 ip6) port)
114 | Just ip <- un4map ip6
115 = JSON.object [ "public_key" .= show nid
116 , "ipv4" .= show ip
117 , "port" .= (fromIntegral port :: Int)
118 ]
119 | otherwise
120 = JSON.object [ "public_key" .= show nid
121 , "ipv6" .= show ip6
122 , "port" .= (fromIntegral port :: Int)
123 ]
124instance FromJSON NodeInfo where
125 parseJSON (JSON.Object v) = do
126 nidstr <- v JSON..: "public_key"
127 ip6str <- v JSON..:? "ipv6"
128 ip4str <- v JSON..:? "ipv4"
129 portnum <- v JSON..: "port"
130 ip <- maybe empty (return . IPv6) (ip6str >>= readMaybe)
131 <|> maybe empty (return . IPv4) (ip4str >>= readMaybe)
132 let (bs,_) = Base16.decode (C8.pack nidstr)
133 guard (B.length bs == 32)
134 return $ NodeInfo (NodeId $ throwCryptoError . publicKey $ bs) ip (fromIntegral (portnum :: Word16))
135
136getIP :: Word8 -> S.Get IP
137getIP 0x02 = IPv4 <$> S.get
138getIP 0x0a = IPv6 <$> S.get
139getIP 0x82 = IPv4 <$> S.get -- TODO: TCP
140getIP 0x8a = IPv6 <$> S.get -- TODO: TCP
141getIP x = fail ("unsupported address family ("++show x++")")
142
143instance S.Serialize NodeInfo where
144 get = do
145 addrfam <- S.get :: S.Get Word8
146 ip <- getIP addrfam
147 port <- S.get :: S.Get PortNumber
148 nid <- S.get
149 return $ NodeInfo nid ip port
150
151 put (NodeInfo nid ip port) = do
152 case ip of
153 IPv4 ip4 -> S.put (2 :: Word8) >> S.put ip4
154 IPv6 ip6 -> S.put (10 :: Word8) >> S.put ip6
155 S.put port
156 S.put nid
157
158hexdigit :: Char -> Bool
159hexdigit c = ('0' <= c && c <= '9') || ( 'a' <= c && c <= 'f') || ( 'A' <= c && c <= 'F')
160
161instance Read NodeInfo where
162 readsPrec i = RP.readP_to_S $ do
163 RP.skipSpaces
164 let n = 64 -- characters in node id.
165 parseAddr = RP.between (RP.char '(') (RP.char ')') (RP.munch (/=')'))
166 RP.+++ RP.munch (not . isSpace)
167 nodeidAt = do hexhash <- sequence $ replicate n (RP.satisfy hexdigit)
168 RP.char '@' RP.+++ RP.satisfy isSpace
169 addrstr <- parseAddr
170 nid <- case Base16.decode $ C8.pack hexhash of
171 (bs,_) | B.length bs==32 -> return (NodeId $ throwCryptoError . publicKey $ bs)
172 _ -> fail "Bad node id."
173 return (nid,addrstr)
174 (nid,addrstr) <- ( nodeidAt RP.+++ ( (zeroID,) <$> parseAddr) )
175 let raddr = do
176 ip <- RP.between (RP.char '[') (RP.char ']')
177 (IPv6 <$> RP.readS_to_P (readsPrec i))
178 RP.+++ (IPv4 <$> RP.readS_to_P (readsPrec i))
179 _ <- RP.char ':'
180 port <- toEnum <$> RP.readS_to_P (readsPrec i)
181 return (ip, port)
182
183 (ip,port) <- case RP.readP_to_S raddr addrstr of
184 [] -> fail "Bad address."
185 ((ip,port),_):_ -> return (ip,port)
186 return $ NodeInfo nid ip port
187
188-- The Hashable instance depends only on the IP address and port number.
189instance Hashable NodeInfo where
190 hashWithSalt s ni = hashWithSalt s (nodeIP ni , nodePort ni)
191 {-# INLINE hashWithSalt #-}
192
193
194instance Show NodeInfo where
195 showsPrec _ (NodeInfo nid ip port) =
196 shows nid . ('@' :) . showsip . (':' :) . shows port
197 where
198 showsip
199 | IPv4 ip4 <- ip = shows ip4
200 | IPv6 ip6 <- ip , Just ip4 <- un4map ip6 = shows ip4
201 | otherwise = ('[' :) . shows ip . (']' :)
202
203
204
205
206{-
207type NodeId = PubKey
208
209pattern NodeId bs = PubKey bs
210
211-- TODO: This should probably be represented by Curve25519.PublicKey, but
212-- ByteString has more instances...
213newtype PubKey = PubKey ByteString
214 deriving (Eq,Ord,Data, ByteArrayAccess, Bits, Hashable)
215
216instance Serialize PubKey where
217 get = PubKey <$> getBytes 32
218 put (PubKey bs) = putByteString bs
219
220instance Show PubKey where
221 show (PubKey bs) = C8.unpack $ Base16.encode bs
222
223instance FiniteBits PubKey where
224 finiteBitSize _ = 256
225
226instance Read PubKey where
227 readsPrec _ str
228 | (bs, xs) <- Base16.decode $ C8.pack str
229 , B.length bs == 32
230 = [ (PubKey bs, drop 64 str) ]
231 | otherwise = []
232
233
234
235
236data NodeInfo = NodeInfo
237 { nodeId :: NodeId
238 , nodeIP :: IP
239 , nodePort :: PortNumber
240 }
241 deriving (Eq,Ord,Data)
242
243instance Data PortNumber where
244 dataTypeOf _ = mkNoRepType "PortNumber"
245 toConstr _ = error "PortNumber.toConstr"
246 gunfold _ _ = error "PortNumber.gunfold"
247
248instance ToJSON NodeInfo where
249 toJSON (NodeInfo nid (IPv4 ip) port)
250 = JSON.object [ "public_key" .= show nid
251 , "ipv4" .= show ip
252 , "port" .= (fromIntegral port :: Int)
253 ]
254 toJSON (NodeInfo nid (IPv6 ip6) port)
255 | Just ip <- un4map ip6
256 = JSON.object [ "public_key" .= show nid
257 , "ipv4" .= show ip
258 , "port" .= (fromIntegral port :: Int)
259 ]
260 | otherwise
261 = JSON.object [ "public_key" .= show nid
262 , "ipv6" .= show ip6
263 , "port" .= (fromIntegral port :: Int)
264 ]
265instance FromJSON NodeInfo where
266 parseJSON (JSON.Object v) = do
267 nidstr <- v JSON..: "public_key"
268 ip6str <- v JSON..:? "ipv6"
269 ip4str <- v JSON..:? "ipv4"
270 portnum <- v JSON..: "port"
271 ip <- maybe empty (return . IPv6) (ip6str >>= readMaybe)
272 <|> maybe empty (return . IPv4) (ip4str >>= readMaybe)
273 let (bs,_) = Base16.decode (C8.pack nidstr)
274 guard (B.length bs == 32)
275 return $ NodeInfo (NodeId bs) ip (fromIntegral (portnum :: Word16))
276
277getIP :: Word8 -> S.Get IP
278getIP 0x02 = IPv4 <$> S.get
279getIP 0x0a = IPv6 <$> S.get
280getIP 0x82 = IPv4 <$> S.get -- TODO: TCP
281getIP 0x8a = IPv6 <$> S.get -- TODO: TCP
282getIP x = fail ("unsupported address family ("++show x++")")
283
284instance S.Serialize NodeInfo where
285 get = do
286 addrfam <- S.get :: S.Get Word8
287 ip <- getIP addrfam
288 port <- S.get :: S.Get PortNumber
289 nid <- S.get
290 return $ NodeInfo nid ip port
291
292 put (NodeInfo nid ip port) = do
293 case ip of
294 IPv4 ip4 -> S.put (2 :: Word8) >> S.put ip4
295 IPv6 ip6 -> S.put (10 :: Word8) >> S.put ip6
296 S.put port
297 S.put nid
298
299-- node format:
300-- [uint8_t family (2 == IPv4, 10 == IPv6, 130 == TCP IPv4, 138 == TCP IPv6)]
301-- [ip (in network byte order), length=4 bytes if ipv4, 16 bytes if ipv6]
302-- [port (in network byte order), length=2 bytes]
303-- [char array (node_id), length=32 bytes]
304--
305
306
307hexdigit :: Char -> Bool
308hexdigit c = ('0' <= c && c <= '9') || ( 'a' <= c && c <= 'f') || ( 'A' <= c && c <= 'F')
309
310instance Read NodeInfo where
311 readsPrec i = RP.readP_to_S $ do
312 RP.skipSpaces
313 let n = 64 -- characters in node id.
314 parseAddr = RP.between (RP.char '(') (RP.char ')') (RP.munch (/=')'))
315 RP.+++ RP.munch (not . isSpace)
316 nodeidAt = do hexhash <- sequence $ replicate n (RP.satisfy hexdigit)
317 RP.char '@' RP.+++ RP.satisfy isSpace
318 addrstr <- parseAddr
319 nid <- case Base16.decode $ C8.pack hexhash of
320 (bs,_) | B.length bs==32 -> return (PubKey bs)
321 _ -> fail "Bad node id."
322 return (nid,addrstr)
323 (nid,addrstr) <- ( nodeidAt RP.+++ ( (zeroID,) <$> parseAddr) )
324 let raddr = do
325 ip <- RP.between (RP.char '[') (RP.char ']')
326 (IPv6 <$> RP.readS_to_P (readsPrec i))
327 RP.+++ (IPv4 <$> RP.readS_to_P (readsPrec i))
328 _ <- RP.char ':'
329 port <- toEnum <$> RP.readS_to_P (readsPrec i)
330 return (ip, port)
331
332 (ip,port) <- case RP.readP_to_S raddr addrstr of
333 [] -> fail "Bad address."
334 ((ip,port),_):_ -> return (ip,port)
335 return $ NodeInfo nid ip port
336
337
338-- The Hashable instance depends only on the IP address and port number.
339instance Hashable NodeInfo where
340 hashWithSalt s ni = hashWithSalt s (nodeIP ni , nodePort ni)
341 {-# INLINE hashWithSalt #-}
342
343
344instance Show NodeInfo where
345 showsPrec _ (NodeInfo nid ip port) =
346 shows nid . ('@' :) . showsip . (':' :) . shows port
347 where
348 showsip
349 | IPv4 ip4 <- ip = shows ip4
350 | IPv6 ip6 <- ip , Just ip4 <- un4map ip6 = shows ip4
351 | otherwise = ('[' :) . shows ip . (']' :)
352
353nodeAddr :: NodeInfo -> SockAddr
354nodeAddr (NodeInfo _ ip port) = setPort port $ toSockAddr ip
355
356nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo
357nodeInfo nid saddr
358 | Just ip <- fromSockAddr saddr
359 , Just port <- sockAddrPort saddr = Right $ NodeInfo nid ip port
360 | otherwise = Left "Address family not supported."
361
362zeroID :: NodeId
363zeroID = PubKey $ B.replicate 32 0
364
365-}
366
367newtype ReturnPath (n::Nat) = ReturnPath ByteString
368 deriving (Eq, Ord,Data)
369
370instance KnownNat n => Serialize (ReturnPath n) where
371 -- Size: 59 = 1(family) + 16(ip) + 2(port) +16(mac) + 24(nonce)
372 get = ReturnPath <$> getBytes ( 59 * (fromIntegral $ natVal $ Proxy @n) )
373 put (ReturnPath bs) = putByteString bs
374
375newtype ForwardPath (n::Nat) = ForwardPath ByteString
376 deriving (Eq, Ord,Data)
377
378{-
379class KnownNat n => OnionPacket n where
380 mkOnion :: ReturnPath n -> Packet -> Packet
381instance OnionPacket 0 where mkOnion _ = id
382instance OnionPacket 3 where mkOnion = OnionResponse3
383-}
diff --git a/ToxCrypto.hs b/ToxCrypto.hs
new file mode 100644
index 00000000..98e02e91
--- /dev/null
+++ b/ToxCrypto.hs
@@ -0,0 +1,203 @@
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE KindSignatures #-}
4{-# LANGUAGE DeriveDataTypeable #-}
5module ToxCrypto
6 ( PublicKey
7 , publicKey
8 , SecretKey
9 , Encrypted
10 , Plain
11 , computeSharedSecret
12 , encrypt
13 , decrypt
14 , Nonce8
15 , Nonce24
16 , Nonce32
17 , getRemainingEncrypted
18 , putEncrypted
19 , Auth
20 ) where
21
22import qualified Crypto.Cipher.Salsa as Salsa
23import qualified Crypto.Cipher.XSalsa as XSalsa
24import Crypto.ECC.Class
25import qualified Crypto.Error as Cryptonite
26import qualified Crypto.MAC.Poly1305 as Poly1305
27import Crypto.PubKey.Curve25519
28import qualified Data.ByteArray as BA
29 ;import Data.ByteArray as BA (ByteArrayAccess, Bytes)
30import Data.ByteString as B
31import qualified Data.ByteString.Base16 as Base16
32import qualified Data.ByteString.Char8 as C8
33import Data.Data
34import Data.Kind
35import Data.Ord
36import Data.Serialize
37import Data.Word
38import Foreign.Marshal.Alloc
39import Foreign.Ptr
40import Foreign.Storable
41import System.Endian
42import qualified Data.ByteString.Internal
43
44-- | A 16-byte mac and an arbitrary-length encrypted stream.
45newtype Encrypted a = Encrypted ByteString
46 deriving (Eq,Ord,Data)
47
48newtype Auth = Auth Poly1305.Auth deriving (Eq, ByteArrayAccess)
49instance Ord Auth where
50 compare (Auth a) (Auth b) = comparing (BA.convert :: Poly1305.Auth -> Bytes) a b
51instance Data Auth where
52 gfoldl k z x = z x
53 -- Well, this is a little wonky... XXX
54 gunfold k z c = k (z (Auth . Poly1305.Auth . (BA.convert :: ByteString -> Bytes)))
55 toConstr _ = con_Auth
56 dataTypeOf _ = mkDataType "ToxMessage" [con_Auth]
57con_Auth = mkConstr (dataTypeOf (Auth (error "con_Auth"))) "Auth" [] Prefix
58instance Serialize Auth where
59 get = Auth . Poly1305.Auth . BA.convert <$> getBytes 16
60 put (Auth (Poly1305.Auth bs)) = putByteString $ BA.convert bs
61
62encryptedAuth :: Encrypted a -> Auth
63encryptedAuth (Encrypted bs)
64 | Right auth <- decode (B.take 16 bs) = auth
65 | otherwise = error "encryptedAuth: insufficient bytes"
66
67authAndBytes :: Encrypted a -> (Auth, ByteString)
68authAndBytes (Encrypted bs) = (auth,bs')
69 where
70 (as,bs') = B.splitAt 16 bs
71 Right auth = decode as
72
73data Size a = ConstSize Int
74 | VarSize (a -> Int)
75
76class Sized a where size :: Size a
77
78instance Sized a => Serialize (Encrypted a) where
79 get = case size :: Size a of
80 VarSize _ -> Encrypted <$> (remaining >>= getBytes)
81 ConstSize n -> Encrypted <$> getBytes (16 + n) -- 16 extra for Poly1305 mac
82 put = putEncrypted
83
84getRemainingEncrypted :: Get (Encrypted a)
85getRemainingEncrypted = Encrypted <$> (remaining >>= getBytes)
86
87putEncrypted :: Encrypted a -> Put
88putEncrypted (Encrypted bs) = putByteString bs
89
90newtype Plain (s:: * -> Constraint) a = Plain ByteString
91
92
93decodePlain :: Serialize a => Plain Serialize a -> Either String a
94decodePlain (Plain bs) = decode bs
95
96encodePlain :: Serialize a => a -> Plain Serialize a
97encodePlain a = Plain $ encode a
98
99storePlain :: Storable a => a -> IO (Plain Storable a)
100storePlain a = Plain <$> BA.create (sizeOf a) (`poke` a)
101
102retrievePlain :: Storable a => Plain Storable a -> IO a
103retrievePlain (Plain bs) = BA.withByteArray bs peek
104
105data State = State Poly1305.State XSalsa.State
106
107decrypt :: State -> Encrypted a -> Either String (Plain s a)
108decrypt (State hash crypt) ciphertext
109 | (a == mac) = Right (Plain m)
110 | otherwise = Left "decipherAndAuth: auth fail"
111 where
112 (mac, c) = authAndBytes ciphertext
113 m = fst . XSalsa.combine crypt $ c
114 a = Auth . Poly1305.finalize . Poly1305.update hash $ c
115
116-- Encrypt-then-Mac: Encrypt the cleartext, then compute the MAC on the
117-- ciphertext, and prepend it to the ciphertext
118encrypt :: State -> Plain s a -> Encrypted a
119encrypt (State hash crypt) (Plain m) = Encrypted $ B.append (encode a) c
120 where
121 c = fst . XSalsa.combine crypt $ m
122 a = Auth . Poly1305.finalize . Poly1305.update hash $ c
123
124-- (Poly1305.State, XSalsa.State)
125computeSharedSecret :: SecretKey -> PublicKey -> Nonce24 -> State
126computeSharedSecret sk recipient nonce = State hash crypt
127 where
128 -- diffie helman
129 shared = ecdh (Proxy :: Proxy Curve_X25519) sk recipient
130 -- shared secret XSalsa key
131 k = hsalsa20 shared zeros24
132 -- cipher state
133 st0 = XSalsa.initialize 20 k nonce
134 -- Poly1305 key
135 (rs, crypt) = XSalsa.combine st0 zs where Nonce32 zs = zeros32
136 -- Since rs is 32 bytes, this pattern should never fail...
137 Cryptonite.CryptoPassed hash = Poly1305.initialize rs
138
139hsalsa20 k n = BA.append a b
140 where
141 Salsa.State st = XSalsa.initialize 20 k n
142 (_, as) = BA.splitAt 4 st
143 (a, xs) = BA.splitAt 16 as
144 (_, bs) = BA.splitAt 24 xs
145 (b, _ ) = BA.splitAt 16 bs
146
147
148newtype Nonce24 = Nonce24 ByteString
149 deriving (Eq, Ord, ByteArrayAccess,Data)
150
151quoted :: ShowS -> ShowS
152quoted shows s = '"':shows ('"':s)
153
154bin2hex :: ByteArrayAccess bs => bs -> String
155bin2hex = C8.unpack . Base16.encode . BA.convert
156
157instance Show Nonce24 where
158 showsPrec d nonce = quoted (mappend $ bin2hex nonce)
159
160instance Sized Nonce24 where size = ConstSize 24
161
162instance Serialize Nonce24 where
163 get = Nonce24 <$> getBytes 24
164 put (Nonce24 bs) = putByteString bs
165
166newtype Nonce8 = Nonce8 Word64
167 deriving (Eq, Ord, Data, Serialize)
168
169-- Note: Big-endian to match Serialize instance.
170instance Storable Nonce8 where
171 sizeOf _ = 8
172 alignment _ = alignment (undefined::Word64)
173 peek ptr = Nonce8 . fromBE64 <$> peek (castPtr ptr)
174 poke ptr (Nonce8 w) = poke (castPtr ptr) (toBE64 w)
175
176instance Sized Nonce8 where size = ConstSize 8
177
178instance ByteArrayAccess Nonce8 where
179 length _ = 8
180 withByteArray (Nonce8 w64) kont =
181 allocaBytes 8 $ \p -> do
182 poke (castPtr p :: Ptr Word64) $ toBE64 w64
183 kont p
184
185instance Show Nonce8 where
186 showsPrec d nonce = quoted (mappend $ bin2hex nonce)
187
188
189newtype Nonce32 = Nonce32 ByteString
190 deriving (Eq, Ord, ByteArrayAccess, Data)
191
192instance Serialize Nonce32 where
193 get = Nonce32 <$> getBytes 32
194 put (Nonce32 bs) = putByteString bs
195
196instance Sized Nonce32 where size = ConstSize 32
197
198
199zeros32 :: Nonce32
200zeros32 = Nonce32 $ BA.replicate 32 0
201
202zeros24 :: ByteString
203zeros24 = BA.take 24 zs where Nonce32 zs = zeros32
diff --git a/ToxData.hs b/ToxData.hs
new file mode 100644
index 00000000..06a9b3b8
--- /dev/null
+++ b/ToxData.hs
@@ -0,0 +1,23 @@
1module ToxData where
2
3import Crypto
4
5-- data DHTPacketKind = Ping | Pong | GetNodes | SendNodes
6data DHTPacket a = DHTPacket
7 { dhtSender :: PublicKey
8 , dhtEncrypted :: WithNonce24 (Encrypted (WithNonce8 a))
9 }
10
11data WithNonce8 a = WithNonce8 a Nonce8
12
13data WithNonce24 a = WithNonce24 a Nonce24
14
15data AliasedRequest a = AliasedRequest
16 { alias :: PublicKey
17 , aliasPayload :: WithNonce24 (Encrypted (WithNonce8 a))
18 }
19
20data ForwardedRequest a = ForwardedRequest
21 { forwardTo :: PublicKey
22 , forwarded :: AliasedRequest a
23 }
diff --git a/ToxPacket.hs b/ToxPacket.hs
new file mode 100644
index 00000000..d10a7597
--- /dev/null
+++ b/ToxPacket.hs
@@ -0,0 +1,76 @@
1{-# LANGUAGE BangPatterns #-}
2{-# LANGUAGE CPP #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4{-# LANGUAGE TupleSections #-}
5module ToxPacket where
6
7import ToxCrypto
8import Data.Serialize as S
9import Data.Aeson as JSON
10import Data.IP
11import qualified Data.ByteString.Char8 as C8
12import qualified Data.ByteString as B
13import Data.Word
14import qualified Data.ByteString.Base16 as Base16
15import Network.Socket
16import Data.ByteArray (ByteArrayAccess)
17import qualified Data.ByteArray as BA
18import Data.Hashable
19import Data.Bits
20import System.IO.Unsafe
21import qualified Text.ParserCombinators.ReadP as RP
22import Foreign.Storable
23import ToxAddress -- import Network.Address hiding (nodePort,nodeInfo)
24import Text.Read
25import Control.Applicative
26import Data.Char
27import Control.Monad
28import Crypto.Error.Types (throwCryptoError)
29
30-- ## DHT Request packets
31--
32-- | Length | Contents |
33-- |:-------|:--------------------------|
34-- | `1` | `uint8_t` (0x20) |
35-- | `32` | receiver's DHT public key |
36-- ... ...
37
38data DHTRequestPacket = DHTRequestPacket
39 { requestTarget :: PublicKey
40 , request :: Assym (Encrypted DHTRequest)
41 }
42
43instance Serialize DHTRequestPacket where
44 get = _todo
45 put = _todo
46
47
48data DHTRequest
49 = NATPing Nonce8
50 | NATPong Nonce8
51 | DHTPK DHTPublicKey
52
53-- | Length | Contents |
54-- |:------------|:------------------------------------|
55-- | `1` | `uint8_t` (0x9c) |
56-- | `8` | `uint64_t` `no_replay` |
57-- | `32` | Our DHT public key |
58-- | `[39, 204]` | Maximum of 4 nodes in packed format |
59data DHTPublicKey = DHTPublicKey
60 { dhtpkNonce :: Nonce8
61 , dhtpk :: PublicKey
62 , dhtpkNodes :: SendNodes
63 }
64
65-- | `32` | sender's DHT public key |
66-- | `24` | nonce |
67-- | `?` | encrypted message |
68data Assym a = Assym
69 { senderKey :: PublicKey
70 , assymNonce :: Nonce24
71 , assymData :: a
72 }
73
74newtype SendNodes = SendNodes [NodeInfo]
75 deriving (Eq,Ord,Show,Read)
76
diff --git a/ToxTransport.hs b/ToxTransport.hs
new file mode 100644
index 00000000..1b2bcbe4
--- /dev/null
+++ b/ToxTransport.hs
@@ -0,0 +1,199 @@
1{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE DataKinds,KindSignatures #-}
3{-# LANGUAGE GADTs #-}
4{-# LANGUAGE TypeOperators #-}
5module ToxTransport where
6
7import Network.QueryResponse
8import ToxCrypto
9import ToxAddress as Tox hiding (ReturnPath)
10import ToxPacket
11
12import Control.Concurrent.STM
13import qualified Data.ByteString as B
14 ;import Data.ByteString (ByteString)
15import Data.Word
16import Network.Socket
17import Data.Serialize as S (decode, Serialize, get, put, Get, Put)
18import GHC.TypeLits
19import Data.Typeable
20
21newtype SymmetricKey = SymmetricKey ByteString
22
23data TransportCrypto = TransportCrypto
24 { transportSecret :: SecretKey
25 , transportPublic :: PublicKey
26 , transportSymmetric :: STM SymmetricKey
27 }
28
29transportDecrypt :: TransportCrypto -> Assym (Encrypted a) -> Either String a
30transportDecrypt = _todo
31
32-- layer :: TransportCrypto
33 -- -> Transport String SockAddr ByteString
34 -- -> Transport String Tox.Address ByteString
35-- layer crypto = layerTransport (toxParse crypto) (toxEncode crypto)
36
37toxEncode :: TransportCrypto -> ByteString -> Tox.Address -> (ByteString, SockAddr)
38toxEncode = _todo
39
40-- toxParse :: TransportCrypto -> ByteString -> SockAddr -> Either String (ByteString, Tox.Address)
41-- toxParse crypto bs saddr = case B.head bs of _todo
42
43data Message = Todo | DHTReq DHTRequest | AnnounceReq AnnounceRequest
44
45-- awaitMessage :: forall a. (Maybe (Either err (x, addr)) -> IO a) -> IO a
46
47toxTransport :: TransportCrypto -> Transport String SockAddr ByteString -> Transport String Tox.Address Message
48toxTransport crypto (Transport await send close) = Transport await' send' close
49 where
50 await' :: HandleHi a -> IO a
51 await' forMe = fix $ await . handleOnion crypto forMe
52
53 send' = _todo
54
55type HandleHi a = Maybe (Either String (Message, Tox.Address)) -> IO a
56type HandleLo a = Maybe (Either String (ByteString, SockAddr)) -> IO a
57
58-- Byte value Packet Kind Return address
59-- :----------- :--------------------
60-- `0x00` Ping Request DHTNode
61-- `0x01` Ping Response -
62-- `0x02` Nodes Request DHTNode
63-- `0x04` Nodes Response -
64-- `0x18` Cookie Request DHTNode, but without sending pubkey
65-- `0x19` Cookie Response -
66--
67-- `0x21` LAN Discovery DHTNode (No reply, port 33445, trigger Nodes Request/Response)
68--
69-- `0x20` DHT Request DHTNode/-forward
70--
71-- `0x1a` Crypto Handshake CookieAddress
72--
73-- `0x1b` Crypto Data SessionAddress
74--
75-- `0x83` Announce Request OnionToOwner
76-- `0x84` Announce Response -
77-- `0x85` Onion Data Request OnionToOwner
78-- `0x86` Onion Data Response -
79--
80-- `0xf0` Bootstrap Info SockAddr?
81--
82-- `0x80` Onion Request 0 -forward
83-- `0x81` Onion Request 1 -forward
84-- `0x82` Onion Request 2 -forward
85-- `0x8c` Onion Response 3 -return
86-- `0x8d` Onion Response 2 -return
87-- `0x8e` Onion Response 1 -return
88
89handleOnion :: forall a. TransportCrypto -> HandleHi a -> IO a -> HandleLo a
90handleOnion crypto forMe forThem (Just (Right (bs,saddr))) = case B.head bs of
91 0x20 -> forward forMe bs $ handleDHTRequest crypto saddr forMe forThem
92 0x80 -> forward forMe bs $ handleOnionRequest (Proxy :: Proxy 0) crypto saddr forThem
93 0x81 -> forward forMe bs $ handleOnionRequest (Proxy :: Proxy 1) crypto saddr forThem
94 0x82 -> forward forMe bs $ handleOnionRequest (Proxy :: Proxy 2) crypto saddr forThem
95 0x8c -> forward forMe bs $ handleOnionResponse (Proxy :: Proxy 3) crypto saddr forThem
96 0x8d -> forward forMe bs $ handleOnionResponse (Proxy :: Proxy 2) crypto saddr forThem
97 0x8e -> forward forMe bs $ handleOnionResponse (Proxy :: Proxy 1) crypto saddr forThem
98 typ -> go typ (B.tail bs)
99 where
100 go :: Word8 -> ByteString -> IO a
101 go typ bs = forMe $ Just (parseMessage typ bs)
102
103forward :: forall c b b1.
104 Serialize b =>
105 (Maybe (Either String b1) -> c) -> ByteString -> (b -> c) -> c
106forward forMe bs f = either (forMe . Just . Left) f $ decode $ B.tail bs
107
108parseMessage :: Word8 -> ByteString -> Either String (Message,Address)
109-- Announce :: Aliased Assymetric -> ReturnPath 3 -> Packet --0x83
110parseMessage 0x83 bs = _todo -- Announce Request OnionToOwner
111parseMessage _ _ = _todo
112
113handleDHTRequest :: forall a. TransportCrypto -> SockAddr -> HandleHi a -> IO a -> DHTRequestPacket -> IO a
114handleDHTRequest crypto saddr forMe forThem (DHTRequestPacket target payload)
115 | target == transportPublic crypto = forMe' payload
116 | otherwise = _todo -- lookup target in close list, forward message
117 >> forThem
118 where
119 forMe' :: Assym (Encrypted DHTRequest) -> IO a
120 forMe' payload = do
121 case (,) <$> transportDecrypt crypto payload <*> eaddr of
122 Left e -> forMe (Just (Left e))
123 Right (p,addr) -> forMe (Just (Right (DHTReq p,addr)))
124
125 eaddr :: Either String Tox.Address
126 eaddr = fmap DHTNode $ nodeInfo (NodeId $ senderKey payload) saddr
127
128data Attributed a = Attributed
129 { author :: PublicKey
130 , attributedNonce :: Nonce24
131 , attributed :: a
132 }
133
134-- `0x83` Announce Request OnionToOwner
135-- `0x85` Onion Data Request OnionToOwner
136data OPacket -- payload of an onion request
137
138
139-- `0x84` Announce Response -
140-- `0x86` Onion Data Response -
141data RPacket -- payload of an onion response
142
143-- n = 0, 1, 2
144data OnionRequest (n :: Nat) = OnionRequest
145 { onionNonce :: Nonce24
146 , onionForward :: Forwarding (3 - n) OPacket
147 , pathFromOwner :: ReturnPath n
148 }
149
150instance Serialize (OnionRequest n) where { get = _todo; put = _todo }
151instance Serialize (OnionResponse n) where { get = _todo; put = _todo }
152
153-- n = 1, 2, 3
154-- Attributed (Encrypted (
155
156data OnionResponse (n :: Nat) = OnionResponse
157 { pathToOwner :: ReturnPath n
158 , msgToOwner :: RPacket
159 }
160
161data Addressed a = Addressed { sockAddr :: SockAddr, unaddressed :: a }
162
163data ReturnPath (n :: Nat) where
164 NoReturnPath :: ReturnPath 0
165 ReturnPath :: Nonce24 -> Encrypted (Addressed (ReturnPath n)) -> ReturnPath (n + 1)
166
167data Forwarding (n :: Nat) msg where
168 NotForwarded :: msg -> Forwarding 0 msg
169 Forwarding :: Attributed (Encrypted (Addressed (Forwarding n msg))) -> Forwarding (n + 1) msg
170
171handleOnionRequest :: KnownNat n => proxy n -> TransportCrypto -> SockAddr -> IO a -> OnionRequest n -> IO a
172handleOnionRequest = _todo
173
174handleOnionResponse :: KnownNat n => proxy n -> TransportCrypto -> SockAddr -> IO a -> OnionResponse n -> IO a
175handleOnionResponse = _todo
176
177{-
178data Size a = ConstSize Int
179 | VarSize (a -> Int)
180
181data PacketChunk a
182 = Plain a
183 | Assymetric a
184 | Symmetric a
185 -}
186
187
188data AnnounceRequest = AnnounceRequest
189 { announcePingId :: Nonce32 -- Ping ID
190 , announceSeeking :: NodeId -- Public key we are searching for
191 , announceKey :: NodeId -- Public key that we want those sending back data packets to use
192 }
193
194instance S.Serialize AnnounceRequest where
195 get = AnnounceRequest <$> S.get <*> S.get <*> S.get
196 put (AnnounceRequest p s k) = S.put (p,s,k)
197
198getOnionRequest :: Get (Assym (Encrypted msg), ReturnPath 3)
199getOnionRequest = _todo
diff --git a/c b/c
index e98a6d7b..f11856e2 100755
--- a/c
+++ b/c
@@ -2,4 +2,4 @@
2defs="-DBENCODE_AESON -DTHREAD_DEBUG" 2defs="-DBENCODE_AESON -DTHREAD_DEBUG"
3hide="-hide-package crypto-random -hide-package crypto-api -hide-package crypto-numbers -hide-package cryptohash -hide-package prettyclass" 3hide="-hide-package crypto-random -hide-package crypto-api -hide-package crypto-numbers -hide-package cryptohash -hide-package prettyclass"
4cbits="cbits/*.c" 4cbits="cbits/*.c"
5ghc -freverse-errors $hide -isrc -XOverloadedStrings -XRecordWildCards $defs $cbits "$@" 5ghc -fdefer-typed-holes -Wno-typed-holes -freverse-errors $hide -isrc -XOverloadedStrings -XRecordWildCards $defs $cbits "$@"