diff options
Diffstat (limited to 'Tox.hs')
-rw-r--r-- | Tox.hs | 173 |
1 files changed, 158 insertions, 15 deletions
@@ -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 #-} | ||
10 | module Tox where | 11 | module Tox where |
11 | 12 | ||
12 | import Control.Arrow | 13 | import Control.Arrow |
@@ -21,9 +22,10 @@ import Crypto.PubKey.Curve25519 | |||
21 | import Crypto.PubKey.ECC.Types | 22 | import Crypto.PubKey.ECC.Types |
22 | import Crypto.Random | 23 | import Crypto.Random |
23 | import Data.Bool | 24 | import Data.Bool |
24 | import Data.ByteArray as BA | 25 | import qualified Data.ByteArray as BA |
25 | import Data.ByteString (ByteString) | 26 | ;import Data.ByteArray (ByteArrayAccess,Bytes) |
26 | import Data.ByteString as B | 27 | import qualified Data.ByteString as B |
28 | ;import Data.ByteString (ByteString) | ||
27 | import qualified Data.ByteString.Base16 as Base16 | 29 | import qualified Data.ByteString.Base16 as Base16 |
28 | import qualified Data.ByteString.Char8 as C8 | 30 | import qualified Data.ByteString.Char8 as C8 |
29 | import Data.ByteString.Lazy (toStrict) | 31 | import Data.ByteString.Lazy (toStrict) |
@@ -39,26 +41,122 @@ import Foreign.Ptr | |||
39 | import Foreign.Storable | 41 | import Foreign.Storable |
40 | import GHC.Generics (Generic) | 42 | import GHC.Generics (Generic) |
41 | import Network.Address (Address, fromSockAddr, sockAddrPort, | 43 | import Network.Address (Address, fromSockAddr, sockAddrPort, |
42 | toSockAddr, withPort) | 44 | toSockAddr, setPort,un4map) |
43 | import Network.QueryResponse | 45 | import Network.QueryResponse |
44 | import Network.Socket | 46 | import Network.Socket |
45 | import System.Endian | 47 | import System.Endian |
48 | import Data.Hashable | ||
49 | import Data.Bits | ||
50 | import Data.Bits.ByteString () | ||
51 | import qualified Text.ParserCombinators.ReadP as RP | ||
52 | import Data.Char | ||
46 | 53 | ||
47 | newtype NodeId = NodeId ByteString | 54 | newtype NodeId = NodeId ByteString |
48 | deriving (Eq,Ord,Show,ByteArrayAccess) | 55 | deriving (Eq,Ord,ByteArrayAccess, Bits) |
56 | |||
57 | instance Show NodeId where | ||
58 | show (NodeId bs) = C8.unpack $ Base16.encode bs | ||
49 | 59 | ||
50 | instance S.Serialize NodeId where | 60 | instance 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 | ||
64 | instance FiniteBits NodeId where | ||
65 | finiteBitSize _ = 256 | ||
66 | |||
67 | instance 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 | |||
74 | zeroID :: NodeId | ||
75 | zeroID = NodeId $ B.replicate 32 0 | ||
76 | |||
54 | data NodeInfo = NodeInfo | 77 | data 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 | |||
84 | instance 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 | |||
112 | hexdigit :: Char -> Bool | ||
113 | hexdigit c = ('0' <= c && c <= '9') || ( 'a' <= c && c <= 'f') || ( 'A' <= c && c <= 'F') | ||
114 | |||
115 | instance 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. | ||
144 | instance Hashable NodeInfo where | ||
145 | hashWithSalt s ni = hashWithSalt s (nodeIP ni , nodePort ni) | ||
146 | {-# INLINE hashWithSalt #-} | ||
147 | |||
148 | |||
149 | instance 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 | ||
60 | nodeAddr :: NodeInfo -> SockAddr | 158 | nodeAddr :: NodeInfo -> SockAddr |
61 | nodeAddr (NodeInfo _ ip port) = toSockAddr ip `withPort` port | 159 | nodeAddr (NodeInfo _ ip port) = setPort port $ toSockAddr ip |
62 | 160 | ||
63 | nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo | 161 | nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo |
64 | nodeInfo nid saddr | 162 | nodeInfo nid saddr |
@@ -113,13 +211,13 @@ quoted :: ShowS -> ShowS | |||
113 | quoted shows s = '"':shows ('"':s) | 211 | quoted shows s = '"':shows ('"':s) |
114 | 212 | ||
115 | bin2hex :: ByteArrayAccess bs => bs -> String | 213 | bin2hex :: ByteArrayAccess bs => bs -> String |
116 | bin2hex = C8.unpack . Base16.encode . convert | 214 | bin2hex = C8.unpack . Base16.encode . BA.convert |
117 | 215 | ||
118 | 216 | ||
119 | data Message a = Message | 217 | data 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 | {- | ||
252 | data Plain a = Plain | ||
253 | { plainId :: Nonce8 -- transactionKey of TransactionId | ||
254 | , plainPayload :: a | ||
255 | } | ||
256 | deriving (Eq, Show, Generic, Functor, Foldable, Traversable) | ||
257 | |||
258 | instance 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. |
154 | data SecretsCache = SecretsCache | 264 | data SecretsCache = SecretsCache |
155 | newEmptyCache = return SecretsCache | 265 | newEmptyCache = 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 | |||
323 | handlers GetNodesType = error "find_node" | 433 | handlers GetNodesType = error "find_node" |
324 | handlers _ = Nothing | 434 | handlers _ = Nothing |
325 | 435 | ||
326 | data Ping = Ping | 436 | data Ping = Ping deriving Show |
437 | data Pong = Pong deriving Show | ||
438 | |||
439 | instance 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 | |||
446 | instance 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 | |||
453 | newtype GetNodes = GetNodes NodeId | ||
454 | deriving (Eq,Ord,Show,Read,S.Serialize) | ||
455 | |||
456 | newtype SendNodes = SendNodes [NodeInfo] | ||
457 | deriving (Eq,Ord,Show,Read) | ||
458 | |||
459 | instance 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 | ||
328 | pingH :: NodeInfo -> Ping -> IO Ping | 471 | pingH :: NodeInfo -> Ping -> IO Pong |
329 | pingH = error "pingH" | 472 | pingH _ Ping = return Pong |
330 | 473 | ||
331 | intKey :: TransactionId -> Int | 474 | intKey :: TransactionId -> Int |
332 | intKey (TransactionId (Nonce8 w) _) = fromIntegral w | 475 | intKey (TransactionId (Nonce8 w) _) = fromIntegral w |