summaryrefslogtreecommitdiff
path: root/Tox.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Tox.hs')
-rw-r--r--Tox.hs173
1 files changed, 158 insertions, 15 deletions
diff --git a/Tox.hs b/Tox.hs
index 77cd0ae0..25c650b3 100644
--- a/Tox.hs
+++ b/Tox.hs
@@ -7,6 +7,7 @@
7{-# LANGUAGE GeneralizedNewtypeDeriving #-} 7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8{-# LANGUAGE PatternSynonyms #-} 8{-# LANGUAGE PatternSynonyms #-}
9{-# LANGUAGE ScopedTypeVariables #-} 9{-# LANGUAGE ScopedTypeVariables #-}
10{-# LANGUAGE TupleSections #-}
10module Tox where 11module Tox where
11 12
12import Control.Arrow 13import Control.Arrow
@@ -21,9 +22,10 @@ import Crypto.PubKey.Curve25519
21import Crypto.PubKey.ECC.Types 22import Crypto.PubKey.ECC.Types
22import Crypto.Random 23import Crypto.Random
23import Data.Bool 24import Data.Bool
24import Data.ByteArray as BA 25import qualified Data.ByteArray as BA
25import Data.ByteString (ByteString) 26 ;import Data.ByteArray (ByteArrayAccess,Bytes)
26import Data.ByteString as B 27import qualified Data.ByteString as B
28 ;import Data.ByteString (ByteString)
27import qualified Data.ByteString.Base16 as Base16 29import qualified Data.ByteString.Base16 as Base16
28import qualified Data.ByteString.Char8 as C8 30import qualified Data.ByteString.Char8 as C8
29import Data.ByteString.Lazy (toStrict) 31import Data.ByteString.Lazy (toStrict)
@@ -39,26 +41,122 @@ import Foreign.Ptr
39import Foreign.Storable 41import Foreign.Storable
40import GHC.Generics (Generic) 42import GHC.Generics (Generic)
41import Network.Address (Address, fromSockAddr, sockAddrPort, 43import Network.Address (Address, fromSockAddr, sockAddrPort,
42 toSockAddr, withPort) 44 toSockAddr, setPort,un4map)
43import Network.QueryResponse 45import Network.QueryResponse
44import Network.Socket 46import Network.Socket
45import System.Endian 47import System.Endian
48import Data.Hashable
49import Data.Bits
50import Data.Bits.ByteString ()
51import qualified Text.ParserCombinators.ReadP as RP
52import Data.Char
46 53
47newtype NodeId = NodeId ByteString 54newtype NodeId = NodeId ByteString
48 deriving (Eq,Ord,Show,ByteArrayAccess) 55 deriving (Eq,Ord,ByteArrayAccess, Bits)
56
57instance Show NodeId where
58 show (NodeId bs) = C8.unpack $ Base16.encode bs
49 59
50instance S.Serialize NodeId where 60instance S.Serialize NodeId where
51 get = NodeId <$> S.getBytes 32 61 get = NodeId <$> S.getBytes 32
52 put (NodeId bs) = S.putByteString bs 62 put (NodeId bs) = S.putByteString bs
53 63
64instance FiniteBits NodeId where
65 finiteBitSize _ = 256
66
67instance Read NodeId where
68 readsPrec _ str
69 | (bs, xs) <- Base16.decode $ C8.pack str
70 , B.length bs == 32
71 = [ (NodeId bs, drop 40 str) ]
72 | otherwise = []
73
74zeroID :: NodeId
75zeroID = NodeId $ B.replicate 32 0
76
54data NodeInfo = NodeInfo 77data NodeInfo = NodeInfo
55 { nodeId :: NodeId 78 { nodeId :: NodeId
56 , nodeIP :: IP 79 , nodeIP :: IP
57 , nodePort :: PortNumber 80 , nodePort :: PortNumber
58 } 81 }
82 deriving (Eq,Ord)
83
84instance S.Serialize NodeInfo where
85 get = do
86 nid <- S.get
87 addrfam <- S.get :: S.Get Word8
88 ip <- case addrfam of
89 2 -> IPv4 <$> S.get
90 10 -> IPv6 <$> S.get
91 130 -> IPv4 <$> S.get -- TODO: TCP
92 138 -> IPv6 <$> S.get -- TODO: TCP
93 _ -> fail "unsupported address family"
94 port <- S.get :: S.Get PortNumber
95 return $ NodeInfo nid ip port
96
97 put (NodeInfo nid ip port) = do
98 S.put nid
99 case ip of
100 IPv4 ip4 -> S.put (2 :: Word8) >> S.put ip4
101 IPv6 ip6 -> S.put (10 :: Word8) >> S.put ip6
102 S.put port
103
104-- node format:
105-- [uint8_t family (2 == IPv4, 10 == IPv6, 130 == TCP IPv4, 138 == TCP IPv6)]
106-- [ip (in network byte order), length=4 bytes if ipv4, 16 bytes if ipv6]
107-- [port (in network byte order), length=2 bytes]
108-- [char array (node_id), length=32 bytes]
109--
110
111
112hexdigit :: Char -> Bool
113hexdigit c = ('0' <= c && c <= '9') || ( 'a' <= c && c <= 'f') || ( 'A' <= c && c <= 'F')
114
115instance Read NodeInfo where
116 readsPrec i = RP.readP_to_S $ do
117 RP.skipSpaces
118 let n = 64 -- characters in node id.
119 parseAddr = RP.between (RP.char '(') (RP.char ')') (RP.munch (/=')'))
120 RP.+++ RP.munch (not . isSpace)
121 nodeidAt = do hexhash <- sequence $ replicate n (RP.satisfy hexdigit)
122 RP.char '@' RP.+++ RP.satisfy isSpace
123 addrstr <- parseAddr
124 nid <- case Base16.decode $ C8.pack hexhash of
125 (bs,_) | B.length bs==32 -> return (NodeId bs)
126 _ -> fail "Bad node id."
127 return (nid,addrstr)
128 (nid,addrstr) <- ( nodeidAt RP.+++ ( (zeroID,) <$> parseAddr) )
129 let raddr = do
130 ip <- RP.between (RP.char '[') (RP.char ']')
131 (IPv6 <$> RP.readS_to_P (readsPrec i))
132 RP.+++ (IPv4 <$> RP.readS_to_P (readsPrec i))
133 _ <- RP.char ':'
134 port <- toEnum <$> RP.readS_to_P (readsPrec i)
135 return (ip, port)
136
137 (ip,port) <- case RP.readP_to_S raddr addrstr of
138 [] -> fail "Bad address."
139 ((ip,port),_):_ -> return (ip,port)
140 return $ NodeInfo nid ip port
141
142
143-- The Hashable instance depends only on the IP address and port number.
144instance Hashable NodeInfo where
145 hashWithSalt s ni = hashWithSalt s (nodeIP ni , nodePort ni)
146 {-# INLINE hashWithSalt #-}
147
148
149instance Show NodeInfo where
150 showsPrec _ (NodeInfo nid ip port) =
151 shows nid . ('@' :) . showsip . (':' :) . shows port
152 where
153 showsip
154 | IPv4 ip4 <- ip = shows ip4
155 | IPv6 ip6 <- ip , Just ip4 <- un4map ip6 = shows ip4
156 | otherwise = ('[' :) . shows ip . (']' :)
59 157
60nodeAddr :: NodeInfo -> SockAddr 158nodeAddr :: NodeInfo -> SockAddr
61nodeAddr (NodeInfo _ ip port) = toSockAddr ip `withPort` port 159nodeAddr (NodeInfo _ ip port) = setPort port $ toSockAddr ip
62 160
63nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo 161nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo
64nodeInfo nid saddr 162nodeInfo nid saddr
@@ -113,13 +211,13 @@ quoted :: ShowS -> ShowS
113quoted shows s = '"':shows ('"':s) 211quoted shows s = '"':shows ('"':s)
114 212
115bin2hex :: ByteArrayAccess bs => bs -> String 213bin2hex :: ByteArrayAccess bs => bs -> String
116bin2hex = C8.unpack . Base16.encode . convert 214bin2hex = C8.unpack . Base16.encode . BA.convert
117 215
118 216
119data Message a = Message 217data Message a = Message
120 { msgType :: Method 218 { msgType :: Method
121 , msgOrigin :: NodeId 219 , msgOrigin :: NodeId
122 , msgNonce :: Nonce24 220 , msgNonce :: Nonce24 -- cryptoNonce of TransactionId
123 , msgPayload :: a 221 , msgPayload :: a
124 } 222 }
125 deriving (Eq, Show, Generic, Functor, Foldable, Traversable) 223 deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
@@ -133,7 +231,7 @@ getMessage = do
133 typ <- S.get 231 typ <- S.get
134 nid <- S.get 232 nid <- S.get
135 tid <- S.get 233 tid <- S.get
136 mac <- Poly1305.Auth . convert <$> S.getBytes 16 234 mac <- Poly1305.Auth . BA.convert <$> S.getBytes 16
137 cnt <- S.remaining 235 cnt <- S.remaining
138 bs <- S.getBytes cnt 236 bs <- S.getBytes cnt
139 return Message { msgType = typ 237 return Message { msgType = typ
@@ -147,9 +245,21 @@ putMessage (Message {..}) = do
147 S.put msgOrigin 245 S.put msgOrigin
148 S.put msgNonce 246 S.put msgNonce
149 let Ciphered (Poly1305.Auth mac) bs = msgPayload 247 let Ciphered (Poly1305.Auth mac) bs = msgPayload
150 S.putByteString (convert mac) 248 S.putByteString (BA.convert mac)
151 S.putByteString bs 249 S.putByteString bs
152 250
251{-
252data Plain a = Plain
253 { plainId :: Nonce8 -- transactionKey of TransactionId
254 , plainPayload :: a
255 }
256 deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
257
258instance Serialize a => Serialize (Plain a) where
259 get = flip Plain <$> get get
260 put (Plain tid a) = put a >> put tid
261-}
262
153-- TODO: Cache symmetric keys. 263-- TODO: Cache symmetric keys.
154data SecretsCache = SecretsCache 264data SecretsCache = SecretsCache
155newEmptyCache = return SecretsCache 265newEmptyCache = return SecretsCache
@@ -268,7 +378,7 @@ newClient addr = do
268 , clientDispatcher = dispatch tbl 378 , clientDispatcher = dispatch tbl
269 , clientErrorReporter = ignoreErrors -- TODO 379 , clientErrorReporter = ignoreErrors -- TODO
270 , clientPending = var 380 , clientPending = var
271 , clientAddress = atomically (readTVar self) 381 , clientAddress = \maddr -> atomically (readTVar self)
272 , clientResponseId = genNonce24 var 382 , clientResponseId = genNonce24 var
273 } 383 }
274 if fitsInInt (Proxy :: Proxy Word64) 384 if fitsInInt (Proxy :: Proxy Word64)
@@ -323,10 +433,43 @@ handlers PingType = handler PingType pingH
323handlers GetNodesType = error "find_node" 433handlers GetNodesType = error "find_node"
324handlers _ = Nothing 434handlers _ = Nothing
325 435
326data Ping = Ping 436data Ping = Ping deriving Show
437data Pong = Pong deriving Show
438
439instance S.Serialize Ping where
440 get = do w8 <- S.get
441 if (w8 :: Word8) /= 0
442 then fail "Malformed ping."
443 else return Ping
444 put Ping = S.put (0 :: Word8)
445
446instance S.Serialize Pong where
447 get = do w8 <- S.get
448 if (w8 :: Word8) /= 1
449 then fail "Malformed pong."
450 else return Pong
451 put Pong = S.put (1 :: Word8)
452
453newtype GetNodes = GetNodes NodeId
454 deriving (Eq,Ord,Show,Read,S.Serialize)
455
456newtype SendNodes = SendNodes [NodeInfo]
457 deriving (Eq,Ord,Show,Read)
458
459instance S.Serialize SendNodes where
460 get = do
461 cnt <- S.get :: S.Get Word8
462 ns <- sequence $ replicate (fromIntegral cnt) S.get
463 return $ SendNodes ns
464
465 put (SendNodes ns) = do
466 let ns' = take 4 ns
467 S.put (fromIntegral (length ns') :: Word8)
468 mapM_ S.put ns'
469
327 470
328pingH :: NodeInfo -> Ping -> IO Ping 471pingH :: NodeInfo -> Ping -> IO Pong
329pingH = error "pingH" 472pingH _ Ping = return Pong
330 473
331intKey :: TransactionId -> Int 474intKey :: TransactionId -> Int
332intKey (TransactionId (Nonce8 w) _) = fromIntegral w 475intKey (TransactionId (Nonce8 w) _) = fromIntegral w