diff options
author | James Crayne <jim.crayne@gmail.com> | 2019-09-28 13:43:29 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-01-01 19:27:53 -0500 |
commit | 11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch) | |
tree | 5716463275c2d3e902889db619908ded2a73971c /src/Data | |
parent | add2c76bced51fde5e9917e7449ef52be70faf87 (diff) |
Factor out some new libraries
word64-map:
Data.Word64Map
network-addr:
Network.Address
tox-crypto:
Crypto.Tox
lifted-concurrent:
Control.Concurrent.Lifted.Instrument
Control.Concurrent.Async.Lifted.Instrument
psq-wrap:
Data.Wrapper.PSQInt
Data.Wrapper.PSQ
minmax-psq:
Data.MinMaxPSQ
tasks:
Control.Concurrent.Tasks
kad:
Network.Kademlia
Network.Kademlia.Bootstrap
Network.Kademlia.Routing
Network.Kademlia.CommonAPI
Network.Kademlia.Persistence
Network.Kademlia.Search
Diffstat (limited to 'src/Data')
-rw-r--r-- | src/Data/BEncode/Pretty.hs | 81 | ||||
-rw-r--r-- | src/Data/Bits/ByteString.hs | 132 | ||||
-rw-r--r-- | src/Data/Digest/CRC32C.hs | 100 | ||||
-rw-r--r-- | src/Data/IntervalSet.hs | 129 | ||||
-rw-r--r-- | src/Data/MinMaxPSQ.hs | 112 | ||||
-rw-r--r-- | src/Data/PacketBuffer.hs | 148 | ||||
-rw-r--r-- | src/Data/PacketQueue.hs | 217 | ||||
-rw-r--r-- | src/Data/Sized.hs | 14 | ||||
-rw-r--r-- | src/Data/TableMethods.hs | 105 | ||||
-rw-r--r-- | src/Data/Torrent.hs | 1347 | ||||
-rw-r--r-- | src/Data/Tox/Message.hs | 84 | ||||
-rw-r--r-- | src/Data/Tox/Msg.hs | 311 | ||||
-rw-r--r-- | src/Data/Tox/Onion.hs | 1029 | ||||
-rw-r--r-- | src/Data/Tox/Relay.hs | 232 | ||||
-rw-r--r-- | src/Data/Word64Map.hs | 66 | ||||
-rw-r--r-- | src/Data/Wrapper/PSQ.hs | 91 | ||||
-rw-r--r-- | src/Data/Wrapper/PSQInt.hs | 53 |
17 files changed, 0 insertions, 4251 deletions
diff --git a/src/Data/BEncode/Pretty.hs b/src/Data/BEncode/Pretty.hs deleted file mode 100644 index 8beb101b..00000000 --- a/src/Data/BEncode/Pretty.hs +++ /dev/null | |||
@@ -1,81 +0,0 @@ | |||
1 | {-# LANGUAGE CPP #-} | ||
2 | module Data.BEncode.Pretty where -- (showBEncode) where | ||
3 | |||
4 | import Data.BEncode.Types | ||
5 | import qualified Data.ByteString as BS | ||
6 | import qualified Data.ByteString.Lazy as BL | ||
7 | import Data.Text (Text) | ||
8 | import qualified Data.Text as T | ||
9 | import Data.Text.Encoding | ||
10 | import qualified Data.ByteString.Base16 as Base16 | ||
11 | #ifdef BENCODE_AESON | ||
12 | import Data.BEncode.BDict hiding (map) | ||
13 | import Data.Aeson.Types hiding (parse) | ||
14 | import Data.Aeson.Encode.Pretty | ||
15 | import qualified Data.HashMap.Strict as HashMap | ||
16 | import qualified Data.Vector as Vector | ||
17 | import Data.Foldable as Foldable | ||
18 | #endif | ||
19 | |||
20 | {- | ||
21 | unhex :: Text -> BS.ByteString | ||
22 | unhex t = BS.pack $ map unhex1 [0 .. BS.length nibs `div` 2] | ||
23 | where | ||
24 | nibs = encodeUtf8 t | ||
25 | unhex1 i = unnib (BS.index nibs (i * 2)) * 0x10 | ||
26 | + unnib (BS.index nibs (i * 2 + 1)) | ||
27 | unnib a | a <= 0x39 = a - 0x30 | ||
28 | | otherwise = a - (0x41 - 10) | ||
29 | |||
30 | hex :: BS.ByteString -> Text | ||
31 | hex bs = T.concat $ map (T.pack . printf "%02X") $ BS.unpack bs | ||
32 | -} | ||
33 | |||
34 | #ifdef BENCODE_AESON | ||
35 | |||
36 | quote_chr :: Char | ||
37 | quote_chr = ' ' | ||
38 | |||
39 | quote :: Text -> Text | ||
40 | quote t = quote_chr `T.cons` t `T.snoc` quote_chr | ||
41 | |||
42 | encodeByteString :: BS.ByteString -> Text | ||
43 | encodeByteString s = either (const . decodeUtf8 $ Base16.encode s) quote $ decodeUtf8' s | ||
44 | |||
45 | decodeByteString :: Text -> BS.ByteString | ||
46 | decodeByteString s | ||
47 | | T.head s==quote_chr = encodeUtf8 (T.takeWhile (/=quote_chr) $ T.drop 1 s) | ||
48 | | otherwise = fst (Base16.decode (encodeUtf8 s)) | ||
49 | |||
50 | instance ToJSON BValue where | ||
51 | toJSON (BInteger x) = Number $ fromIntegral x | ||
52 | toJSON (BString s) = String $ encodeByteString s | ||
53 | toJSON (BList xs) = Array $ Vector.fromList $ map toJSON xs | ||
54 | toJSON (BDict d) = toJSON d | ||
55 | |||
56 | instance ToJSON a => ToJSON (BDictMap a) where | ||
57 | toJSON d = Object $ HashMap.fromList $ map convert $ toAscList d | ||
58 | where | ||
59 | convert (k,v) = (encodeByteString k,toJSON v) | ||
60 | |||
61 | instance FromJSON BValue where | ||
62 | parseJSON (Number x) = pure $ BInteger (truncate x) | ||
63 | parseJSON (Bool x) = pure $ BInteger $ if x then 1 else 0 | ||
64 | parseJSON (String s) = pure $ BString $ decodeByteString s | ||
65 | parseJSON (Array v) = BList <$> traverse parseJSON (Foldable.toList v) | ||
66 | parseJSON (Object d) = BDict <$> parseJSON (Object d) | ||
67 | parseJSON (Null) = pure $ BDict Nil | ||
68 | |||
69 | instance FromJSON v => FromJSON (BDictMap v) where | ||
70 | parseJSON (Object d) = fromAscList <$> traverse convert (HashMap.toList d) | ||
71 | where | ||
72 | convert (k,v) = (,) (decodeByteString k) <$> parseJSON v | ||
73 | parseJSON _ = fail "Not a BDict" | ||
74 | #endif | ||
75 | |||
76 | showBEncode :: BValue -> BL.ByteString | ||
77 | #ifdef BENCODE_AESON | ||
78 | showBEncode b = encodePretty $ toJSON b | ||
79 | #else | ||
80 | showBEncode b = BL8.pack (show b) | ||
81 | #endif | ||
diff --git a/src/Data/Bits/ByteString.hs b/src/Data/Bits/ByteString.hs deleted file mode 100644 index bf0316fd..00000000 --- a/src/Data/Bits/ByteString.hs +++ /dev/null | |||
@@ -1,132 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | ------------------------------------------------------------------------------- | ||
4 | -- | | ||
5 | -- Module : Data.Bits.ByteString | ||
6 | -- Copyright : (c) 2016 Michael Carpenter | ||
7 | -- License : BSD3 | ||
8 | -- Maintainer : Michael Carpenter <oldmanmike.dev@gmail.com> | ||
9 | -- Stability : experimental | ||
10 | -- Portability : portable | ||
11 | -- | ||
12 | ------------------------------------------------------------------------------- | ||
13 | module Data.Bits.ByteString where | ||
14 | |||
15 | import Data.Bits | ||
16 | import qualified Data.ByteString as B | ||
17 | import Data.Word | ||
18 | |||
19 | instance Bits B.ByteString where | ||
20 | |||
21 | (.&.) a b = B.pack $ B.zipWith (.&.) a b | ||
22 | {-# INLINE (.&.) #-} | ||
23 | |||
24 | (.|.) a b = B.pack $ B.zipWith (.|.) a b | ||
25 | {-# INLINE (.|.) #-} | ||
26 | |||
27 | xor a b = B.pack $ B.zipWith xor a b | ||
28 | {-# INLINE xor #-} | ||
29 | |||
30 | complement = B.map complement | ||
31 | {-# INLINE complement #-} | ||
32 | |||
33 | shift x i | ||
34 | | i < 0 = x `shiftR` (-i) | ||
35 | | i > 0 = x `shiftL` i | ||
36 | | otherwise = x | ||
37 | {-# INLINE shift #-} | ||
38 | |||
39 | shiftR bs 0 = bs | ||
40 | shiftR "" _ = B.empty | ||
41 | shiftR bs i | ||
42 | | i `mod` 8 == 0 = | ||
43 | B.take (B.length bs) $ B.append | ||
44 | (B.replicate (i `div` 8) 0) | ||
45 | (B.drop (i `div` 8) bs) | ||
46 | | i `mod` 8 /= 0 = | ||
47 | B.pack $ take (B.length bs) | ||
48 | $ (replicate (i `div` 8) (0 :: Word8)) | ||
49 | ++ (go (i `mod` 8) 0 $ B.unpack (B.take (B.length bs - (i `div` 8)) bs)) | ||
50 | where | ||
51 | go _ _ [] = [] | ||
52 | go j w1 (w2:wst) = (maskR j w1 w2) : go j w2 wst | ||
53 | maskR j w1 w2 = (shiftL w1 (8-j)) .|. (shiftR w2 j) | ||
54 | shiftR _ _ = error "I can't believe you've done this." | ||
55 | {-# INLINE shiftR #-} | ||
56 | |||
57 | shiftL bs 0 = bs | ||
58 | shiftL "" _ = B.empty | ||
59 | shiftL bs i | ||
60 | | i `mod` 8 == 0 = | ||
61 | B.take (B.length bs) $ B.append | ||
62 | (B.drop (i `div` 8) bs) | ||
63 | (B.replicate (i `div` 8) 0) | ||
64 | | i `mod` 8 /= 0 = | ||
65 | B.pack $ drop ((i `div` 8) - B.length bs) | ||
66 | $ (tail (go (i `mod` 8) 0 $ B.unpack (B.drop (i `div` 8) bs))) | ||
67 | ++ (replicate (i `div` 8) 0) | ||
68 | where | ||
69 | go j w1 [] = [shiftL w1 j] | ||
70 | go j w1 (w2:wst) = (maskL j w1 w2) : go j w2 wst | ||
71 | maskL j w1 w2 = (shiftL w1 j) .|. (shiftR w2 (8-j)) | ||
72 | shiftL _ _ = error "I can't believe you've done this." | ||
73 | {-# INLINE shiftL #-} | ||
74 | |||
75 | rotate x i | ||
76 | | i < 0 = x `rotateR` (-i) | ||
77 | | i > 0 = x `rotateL` i | ||
78 | | otherwise = x | ||
79 | {-# INLINE rotate #-} | ||
80 | |||
81 | rotateR bs 0 = bs | ||
82 | rotateR bs i | ||
83 | | B.length bs == 0 = B.empty | ||
84 | | B.length bs == 1 = B.singleton (rotateR (bs `B.index` 0) i) | ||
85 | | B.length bs > 1 = do | ||
86 | let shiftedWords = | ||
87 | B.append | ||
88 | (B.drop (nWholeWordsToShift i) bs) | ||
89 | (B.take (nWholeWordsToShift i) bs) | ||
90 | let tmpShiftedBits = (shiftR shiftedWords (i `mod` 8)) | ||
91 | let rotatedBits = (shiftL (B.last shiftedWords) (8 - (i `mod` 8))) .|. (B.head tmpShiftedBits) | ||
92 | rotatedBits `B.cons` (B.tail tmpShiftedBits) | ||
93 | where | ||
94 | nWholeWordsToShift n = (B.length bs - (n `div` 8)) | ||
95 | rotateR _ _ = error "I can't believe you've done this." | ||
96 | {-# INLINE rotateR #-} | ||
97 | |||
98 | rotateL bs 0 = bs | ||
99 | rotateL bs i | ||
100 | | B.length bs == 0 = B.empty | ||
101 | | B.length bs == 1 = B.singleton (rotateL (bs `B.index` 0) i) | ||
102 | | i `mod` 8 == 0 = B.append | ||
103 | (B.drop (i `div` 8) bs) | ||
104 | (B.take (i `div` 8) bs) | ||
105 | | B.length bs > 1 = do | ||
106 | let shiftedWords = | ||
107 | B.append | ||
108 | (B.drop (i `div` 8) bs) | ||
109 | (B.take (i `div` 8) bs) | ||
110 | let tmpShiftedBits = (shiftL shiftedWords (i `mod` 8)) | ||
111 | let rotatedBits = (shiftR (B.head shiftedWords) (8 - (i `mod` 8))) .|. (B.last tmpShiftedBits) | ||
112 | (B.init tmpShiftedBits) `B.snoc` rotatedBits | ||
113 | rotateL _ _ = error "I can't believe you've done this." | ||
114 | {-# INLINE rotateL #-} | ||
115 | |||
116 | bitSize x = 8 * B.length x | ||
117 | {-# INLINE bitSize #-} | ||
118 | |||
119 | bitSizeMaybe x = Just (8 * B.length x) | ||
120 | {-# INLINE bitSizeMaybe #-} | ||
121 | |||
122 | isSigned _ = False | ||
123 | {-# INLINE isSigned #-} | ||
124 | |||
125 | testBit x i = testBit (B.index x (B.length x - (i `div` 8) - 1)) (i `mod` 8) | ||
126 | {-# INLINE testBit #-} | ||
127 | |||
128 | bit i = (bit $ mod i 8) `B.cons` (B.replicate (div i 8) (255 :: Word8)) | ||
129 | {-# INLINE bit #-} | ||
130 | |||
131 | popCount x = sum $ map popCount $ B.unpack x | ||
132 | {-# INLINE popCount #-} | ||
diff --git a/src/Data/Digest/CRC32C.hs b/src/Data/Digest/CRC32C.hs deleted file mode 100644 index 18c1314f..00000000 --- a/src/Data/Digest/CRC32C.hs +++ /dev/null | |||
@@ -1,100 +0,0 @@ | |||
1 | module Data.Digest.CRC32C | ||
2 | ( crc32c | ||
3 | , crc32c_update | ||
4 | ) where | ||
5 | |||
6 | import Data.Bits | ||
7 | import Data.ByteString (ByteString) | ||
8 | import Data.Word | ||
9 | import Data.Array.Base (unsafeAt) | ||
10 | import Data.Array.Unboxed | ||
11 | |||
12 | import qualified Data.ByteString as B | ||
13 | |||
14 | |||
15 | crc32c :: ByteString -> Word32 | ||
16 | crc32c = crc32c_update 0 | ||
17 | |||
18 | crc32c_update :: Word32 -> ByteString -> Word32 | ||
19 | crc32c_update crc bs = flipd $ step (flipd crc) bs | ||
20 | where | ||
21 | flipd = xor 0xffffffff | ||
22 | |||
23 | step :: Word32 -> ByteString -> Word32 | ||
24 | step crc bs = B.foldl step' crc bs | ||
25 | where | ||
26 | step' acc b = let x = table !!! ((acc .&. 0xff) `xor` fromIntegral b) | ||
27 | in x `xor` (acc `shiftR` 8) | ||
28 | {-# INLINEABLE step #-} | ||
29 | |||
30 | (!!!) :: (IArray a e, Ix i, Integral i) => a i e -> i -> e | ||
31 | arr !!! i = unsafeAt arr $ fromIntegral i | ||
32 | {-# INLINEABLE (!!!) #-} | ||
33 | |||
34 | table :: UArray Word32 Word32 | ||
35 | table = listArray (0,255) $ | ||
36 | [ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4 | ||
37 | , 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb | ||
38 | , 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b | ||
39 | , 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24 | ||
40 | , 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b | ||
41 | , 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384 | ||
42 | , 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54 | ||
43 | , 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b | ||
44 | , 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a | ||
45 | , 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35 | ||
46 | , 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5 | ||
47 | , 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa | ||
48 | , 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45 | ||
49 | , 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a | ||
50 | , 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a | ||
51 | , 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595 | ||
52 | , 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48 | ||
53 | , 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957 | ||
54 | , 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687 | ||
55 | , 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198 | ||
56 | , 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927 | ||
57 | , 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38 | ||
58 | , 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8 | ||
59 | , 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7 | ||
60 | , 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096 | ||
61 | , 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789 | ||
62 | , 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859 | ||
63 | , 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46 | ||
64 | , 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9 | ||
65 | , 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6 | ||
66 | , 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36 | ||
67 | , 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829 | ||
68 | , 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c | ||
69 | , 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93 | ||
70 | , 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043 | ||
71 | , 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c | ||
72 | , 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3 | ||
73 | , 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc | ||
74 | , 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c | ||
75 | , 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033 | ||
76 | , 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652 | ||
77 | , 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d | ||
78 | , 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d | ||
79 | , 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982 | ||
80 | , 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d | ||
81 | , 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622 | ||
82 | , 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2 | ||
83 | , 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed | ||
84 | , 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530 | ||
85 | , 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f | ||
86 | , 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff | ||
87 | , 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0 | ||
88 | , 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f | ||
89 | , 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540 | ||
90 | , 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90 | ||
91 | , 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f | ||
92 | , 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee | ||
93 | , 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1 | ||
94 | , 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321 | ||
95 | , 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e | ||
96 | , 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81 | ||
97 | , 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e | ||
98 | , 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e | ||
99 | , 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351 | ||
100 | ] | ||
diff --git a/src/Data/IntervalSet.hs b/src/Data/IntervalSet.hs deleted file mode 100644 index f1205274..00000000 --- a/src/Data/IntervalSet.hs +++ /dev/null | |||
@@ -1,129 +0,0 @@ | |||
1 | module Data.IntervalSet | ||
2 | ( IntSet | ||
3 | , null | ||
4 | , empty | ||
5 | , insert | ||
6 | , delete | ||
7 | , interval | ||
8 | , toIntervals | ||
9 | , nearestOutsider | ||
10 | , Data.IntervalSet.lookup | ||
11 | ) where | ||
12 | |||
13 | import Prelude hiding (null) | ||
14 | import qualified Data.IntMap.Strict as IntMap | ||
15 | ;import Data.IntMap.Strict (IntMap) | ||
16 | import qualified Data.List as List | ||
17 | import Data.Ord | ||
18 | |||
19 | |||
20 | -- A set of integers. | ||
21 | newtype IntSet = IntSet (IntMap Interval) | ||
22 | deriving Show | ||
23 | |||
24 | -- Note: the intervalMin is not stored here but is the lookup key in an IntMap. | ||
25 | data Interval = Interval | ||
26 | { intervalMax :: {-# UNPACK #-} !Int -- ^ Maximum value contained in this interval. | ||
27 | , intervalNext :: {-# UNPACK #-} !Int -- ^ Minimum value in next interval if there is one. | ||
28 | } | ||
29 | deriving Show | ||
30 | |||
31 | null :: IntSet -> Bool | ||
32 | null (IntSet m) = IntMap.null m | ||
33 | |||
34 | empty :: IntSet | ||
35 | empty = IntSet IntMap.empty | ||
36 | |||
37 | |||
38 | insert :: Int -> IntSet -> IntSet | ||
39 | insert x (IntSet m) = IntSet $ case IntMap.lookupLE x m of | ||
40 | Just (lb,Interval mx ub) | ||
41 | | x <= mx -> m | ||
42 | | otherwise -> case ub == maxBound of | ||
43 | |||
44 | True | x == mx + 1 -> IntMap.insert lb (Interval x maxBound) m | ||
45 | | otherwise -> IntMap.insert lb (Interval mx x) | ||
46 | $ IntMap.insert x (Interval x maxBound) m | ||
47 | |||
48 | False | mx + 2 == ub -> let (Just v', m') | ||
49 | = IntMap.updateLookupWithKey (\_ _ -> Nothing) ub m | ||
50 | in IntMap.insert lb v' m' | ||
51 | | mx + 1 == x -> IntMap.insert lb (Interval x ub) m | ||
52 | | otherwise -> IntMap.insert lb (Interval mx x) | ||
53 | $ if ub == x + 1 | ||
54 | then let (Just v', m') | ||
55 | = IntMap.updateLookupWithKey | ||
56 | (\_ _ -> Nothing) ub m | ||
57 | in IntMap.insert x v' m' | ||
58 | else IntMap.insert x (Interval x ub) m | ||
59 | |||
60 | Nothing -> case IntMap.minViewWithKey m of | ||
61 | |||
62 | Just ((ub,v),m') | ||
63 | | x + 1 == ub -> IntMap.insert x v m' | ||
64 | | otherwise -> IntMap.insert x (Interval x ub) m | ||
65 | |||
66 | Nothing -> IntMap.singleton x (Interval x maxBound) | ||
67 | |||
68 | member :: Int -> IntSet -> Bool | ||
69 | member x (IntSet m) = case IntMap.lookupLE x m of | ||
70 | Just (lb,Interval mx _) -> x <= mx | ||
71 | Nothing -> False | ||
72 | |||
73 | nearestOutsider :: Int -> IntSet -> Maybe Int | ||
74 | nearestOutsider x (IntSet m) | ||
75 | | List.null xs = Nothing -- There are no integers outside the set! | ||
76 | | otherwise = Just $ List.minimumBy (comparing (\y -> abs (x - y))) xs | ||
77 | where | ||
78 | xs = case IntMap.lookupLE x m of | ||
79 | Nothing -> [x] | ||
80 | Just (lb,Interval mx ub) | ||
81 | -> if ub < maxBound | ||
82 | then case () of | ||
83 | () | x > mx -> [x] | ||
84 | | minBound < lb -> [lb-1, mx+1, ub-1] | ||
85 | | otherwise -> [mx+1, ub-1] | ||
86 | else case () of | ||
87 | () | x > mx -> [x] | ||
88 | | minBound < lb && mx < maxBound -> [lb-1, mx+1] | ||
89 | | minBound < lb -> [lb-1] | ||
90 | | mx < maxBound -> [mx+1] | ||
91 | | otherwise -> [] | ||
92 | |||
93 | -- Note this could possibly benefit from a intervalPrev field. | ||
94 | delete :: Int -> IntSet -> IntSet | ||
95 | delete x (IntSet m) = IntSet $ case IntMap.lookupLE x m of | ||
96 | Nothing -> m | ||
97 | Just (lb,Interval mx nxt) -> case compare x mx of | ||
98 | |||
99 | GT -> m | ||
100 | |||
101 | EQ | lb < mx -> IntMap.insert lb (Interval (mx - 1) nxt) m | ||
102 | | otherwise -> case IntMap.lookupLE (x-1) m of -- no intervalPrev | ||
103 | Just (lb',Interval mx' _) -> IntMap.insert lb' (Interval mx' nxt) | ||
104 | $ IntMap.delete lb m | ||
105 | Nothing -> IntMap.delete lb m | ||
106 | |||
107 | LT -> let m' = IntMap.insert (x+1) (Interval mx nxt) m | ||
108 | in if lb < x | ||
109 | then IntMap.insert lb (Interval (x - 1) (x+1)) m' | ||
110 | else if x == minBound | ||
111 | then IntMap.delete minBound m' | ||
112 | else case IntMap.lookupLE (x-1) m' of -- no intervalPrev | ||
113 | Just (lb',Interval mx' _) -> IntMap.insert lb' (Interval mx' (x+1)) | ||
114 | $ IntMap.delete lb m' | ||
115 | Nothing -> IntMap.delete lb m' | ||
116 | |||
117 | toIntervals :: IntSet -> [(Int,Int)] | ||
118 | toIntervals (IntSet m) = List.map (\(lb,(Interval mx _)) -> (lb,mx)) | ||
119 | $ IntMap.toList m | ||
120 | |||
121 | interval :: Int -> Int -> IntSet | ||
122 | interval lb mx | ||
123 | | lb <= mx = IntSet $ IntMap.singleton lb (Interval mx maxBound) | ||
124 | | otherwise = IntSet IntMap.empty | ||
125 | |||
126 | lookup :: Int -> IntSet -> Maybe (Int,Int) | ||
127 | lookup k (IntSet m) = case IntMap.lookupLE k m of | ||
128 | Nothing -> Nothing | ||
129 | Just (lb,Interval mx _) -> Just (lb,mx) | ||
diff --git a/src/Data/MinMaxPSQ.hs b/src/Data/MinMaxPSQ.hs deleted file mode 100644 index e7d7c760..00000000 --- a/src/Data/MinMaxPSQ.hs +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | {-# LANGUAGE BangPatterns, PatternSynonyms #-} | ||
2 | module Data.MinMaxPSQ | ||
3 | ( module Data.MinMaxPSQ | ||
4 | , Binding' | ||
5 | , pattern Binding | ||
6 | ) where | ||
7 | |||
8 | import Data.Ord | ||
9 | import qualified Data.Wrapper.PSQ as PSQ | ||
10 | ;import Data.Wrapper.PSQ as PSQ hiding (insert, insert', null, size) | ||
11 | import Prelude hiding (null, take) | ||
12 | |||
13 | data MinMaxPSQ' k p v = MinMaxPSQ !Int !(PSQ' k p v) !(PSQ' k (Down p) v) | ||
14 | type MinMaxPSQ k p = MinMaxPSQ' k p () | ||
15 | |||
16 | empty :: MinMaxPSQ' k p v | ||
17 | empty = MinMaxPSQ 0 PSQ.empty PSQ.empty | ||
18 | |||
19 | singleton' :: (PSQKey k, Ord p) => k -> v -> p -> MinMaxPSQ' k p v | ||
20 | singleton' k v p = MinMaxPSQ 1 (PSQ.singleton' k v p) (PSQ.singleton' k v (Down p)) | ||
21 | |||
22 | null :: MinMaxPSQ' k p v -> Bool | ||
23 | null (MinMaxPSQ sz _ _) = sz==0 | ||
24 | {-# INLINE null #-} | ||
25 | |||
26 | size :: MinMaxPSQ' k p v -> Int | ||
27 | size (MinMaxPSQ sz _ _) = sz | ||
28 | {-# INLINE size #-} | ||
29 | |||
30 | toList :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> [Binding' k p v] | ||
31 | toList (MinMaxPSQ _ nq xq) = PSQ.toList nq | ||
32 | |||
33 | fromList :: (PSQKey k, Ord p) => [Binding' k p v] -> MinMaxPSQ' k p v | ||
34 | fromList kps = let nq = PSQ.fromList kps | ||
35 | xq = PSQ.fromList $ map (\(Binding k v p) -> Binding k v (Down p)) kps | ||
36 | in MinMaxPSQ (PSQ.size nq) nq xq | ||
37 | |||
38 | findMin :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> Maybe (Binding' k p v) | ||
39 | findMin (MinMaxPSQ _ nq xq) = PSQ.findMin nq | ||
40 | |||
41 | findMax :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> Maybe (Binding' k p v) | ||
42 | findMax (MinMaxPSQ _ nq xq) = fmap (\(Binding k v (Down p)) -> Binding k v p) $ PSQ.findMin xq | ||
43 | |||
44 | insert :: (PSQKey k, Ord p) => k -> p -> MinMaxPSQ k p -> MinMaxPSQ k p | ||
45 | insert k p (MinMaxPSQ sz nq xq) = case PSQ.insertView k p () nq of | ||
46 | (Just _ ,nq') -> MinMaxPSQ sz nq' (PSQ.insert k (Down p) xq) | ||
47 | (Nothing,nq') -> MinMaxPSQ (sz+1) nq' (PSQ.insert k (Down p) xq) | ||
48 | |||
49 | insert' :: (PSQKey k, Ord p) => k -> v -> p -> MinMaxPSQ' k p v -> MinMaxPSQ' k p v | ||
50 | insert' k v p (MinMaxPSQ sz nq xq) = case PSQ.insertView k p v nq of | ||
51 | (Just _ ,nq') -> MinMaxPSQ sz nq' (PSQ.insert' k v (Down p) xq) | ||
52 | (Nothing,nq') -> MinMaxPSQ (sz+1) nq' (PSQ.insert' k v (Down p) xq) | ||
53 | |||
54 | delete :: (PSQKey k, Ord p) => k -> MinMaxPSQ' k p v -> MinMaxPSQ' k p v | ||
55 | delete k q@(MinMaxPSQ sz nq xq) = case PSQ.deleteView k nq of | ||
56 | Just (_,_,nq') -> MinMaxPSQ (sz - 1) nq' (PSQ.delete k xq) | ||
57 | Nothing -> q | ||
58 | |||
59 | deleteMin :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> MinMaxPSQ' k p v | ||
60 | deleteMin q@(MinMaxPSQ sz nq xq) = case PSQ.minView nq of | ||
61 | Just (Binding k _ _, nq') -> MinMaxPSQ (sz - 1) nq' (PSQ.delete k xq) | ||
62 | Nothing -> q | ||
63 | |||
64 | deleteMax :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> MinMaxPSQ' k p v | ||
65 | deleteMax q@(MinMaxPSQ sz nq xq) = case PSQ.minView xq of | ||
66 | Just (Binding k _ _, xq') -> MinMaxPSQ (sz - 1) (PSQ.delete k nq) xq' | ||
67 | Nothing -> q | ||
68 | |||
69 | minView :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> Maybe (Binding' k p v, MinMaxPSQ' k p v) | ||
70 | minView (MinMaxPSQ sz nq xq) = fmap (\(Binding k v p, nq') -> (Binding k v p, MinMaxPSQ (sz-1) nq' (PSQ.delete k xq))) | ||
71 | $ PSQ.minView nq | ||
72 | |||
73 | maxView :: (PSQKey k, Ord p) => MinMaxPSQ' k p v -> Maybe (Binding' k p v, MinMaxPSQ' k p v) | ||
74 | maxView (MinMaxPSQ sz nq xq) = fmap (\(Binding k v (Down p), xq') -> (Binding k v p, MinMaxPSQ (sz-1) (PSQ.delete k nq) xq')) | ||
75 | $ PSQ.minView xq | ||
76 | |||
77 | -- | Maintains a bounded 'MinMaxPSQ' by deleting the maximum element if the | ||
78 | -- insertion would cause the queue to have too many elements. | ||
79 | insertTake :: (PSQKey k, Ord p) => Int -> k -> p -> MinMaxPSQ k p -> MinMaxPSQ k p | ||
80 | insertTake n k p q | ||
81 | | size q < n = insert k p q | ||
82 | | size q == n = insert k p $ deleteMax q | ||
83 | | otherwise = take n $ insert k p q | ||
84 | |||
85 | -- | Maintains a bounded 'MinMaxPSQ\'' by deleting the maximum element if the | ||
86 | -- insertion would cause the queue to have too many elements. | ||
87 | insertTake' :: (PSQKey k, Ord p) => Int -> k -> v -> p -> MinMaxPSQ' k p v -> MinMaxPSQ' k p v | ||
88 | insertTake' n k v p q | ||
89 | | size q < n = insert' k v p q | ||
90 | | size q == n = insert' k v p $ deleteMax q | ||
91 | | otherwise = take n $ insert' k v p q | ||
92 | |||
93 | |||
94 | -- | Truncate the 'MinMaxPSQ' to the given number of lowest priority elements. | ||
95 | take :: (PSQKey k, Ord p) => Int -> MinMaxPSQ' k p v -> MinMaxPSQ' k p v | ||
96 | take !n !q | (size q <= n) = q | ||
97 | | null q = q | ||
98 | | otherwise = take n $ deleteMax q | ||
99 | |||
100 | -- | Like 'take', except it provides a list deleted bindings. | ||
101 | takeView :: (PSQKey k, Ord p) => Int -> MinMaxPSQ' k p v -> ( [Binding' k p v], MinMaxPSQ' k p v ) | ||
102 | takeView !n !q | (size q <= n) = ([], q) | ||
103 | | null q = ([], q) | ||
104 | | otherwise = let Just (x,q') = maxView q | ||
105 | (xs,q'') = takeView n q' | ||
106 | ys = x:xs | ||
107 | in (ys, ys `seq` q'') | ||
108 | |||
109 | |||
110 | |||
111 | lookup' :: (PSQKey k, Ord p) => k -> MinMaxPSQ' k p v -> Maybe (p, v) | ||
112 | lookup' k (MinMaxPSQ _ q _) = PSQ.lookup k q | ||
diff --git a/src/Data/PacketBuffer.hs b/src/Data/PacketBuffer.hs deleted file mode 100644 index 17745664..00000000 --- a/src/Data/PacketBuffer.hs +++ /dev/null | |||
@@ -1,148 +0,0 @@ | |||
1 | {-# LANGUAGE TupleSections #-} | ||
2 | {-# LANGUAGE DeriveFunctor #-} | ||
3 | module Data.PacketBuffer | ||
4 | ( PacketBuffer | ||
5 | , newPacketBuffer | ||
6 | , PacketOutboundEvent(..) | ||
7 | , PacketInboundEvent(..) | ||
8 | , grokOutboundPacket | ||
9 | , grokInboundPacket | ||
10 | , awaitReadyPacket | ||
11 | , packetNumbersToRequest | ||
12 | , expectingSequenceNumber | ||
13 | , nextToSendSequenceNumber | ||
14 | , retrieveForResend | ||
15 | , decompressSequenceNumbers | ||
16 | , compressSequenceNumbers | ||
17 | , pbReport | ||
18 | ) where | ||
19 | |||
20 | import Data.PacketQueue as Q | ||
21 | import DPut | ||
22 | import DebugTag | ||
23 | |||
24 | import Control.Concurrent.STM | ||
25 | import Control.Monad | ||
26 | import Data.Maybe | ||
27 | import Data.Word | ||
28 | |||
29 | data PacketBuffer a b = PacketBuffer | ||
30 | { inQueue :: PacketQueue a | ||
31 | , outBuffer :: PacketQueue b } | ||
32 | |||
33 | -- | Initialize the packet buffers. Note, the capacity of the inbound packet | ||
34 | -- queue is currently hardcoded to 200 packets and the capacity of the outbound | ||
35 | -- buffer is hardcoded to 400 packets. | ||
36 | newPacketBuffer :: STM (PacketBuffer a b) | ||
37 | newPacketBuffer = PacketBuffer <$> Q.new 200 0 | ||
38 | <*> Q.new 400 0 | ||
39 | |||
40 | -- | Input for 'grokPacket'. | ||
41 | data PacketOutboundEvent b | ||
42 | = PacketSent { poSeqNum :: Word32 -- ^ Sequence number for payload. | ||
43 | , poSentPayload :: b -- ^ Payload packet we sent to them. | ||
44 | } | ||
45 | deriving Functor | ||
46 | |||
47 | data PacketInboundEvent a | ||
48 | = PacketReceived { peSeqNum :: Word32 -- ^ Sequence number for payload. | ||
49 | , peReceivedPayload :: a -- ^ Payload packet they sent to us. | ||
50 | , peAcknowledgedNum :: Word32 -- ^ Earliest sequence number they've seen from us. | ||
51 | } | ||
52 | | PacketReceivedLossy { peSeqNum :: Word32 -- ^ Sequence number for lossy packet. | ||
53 | , peReceivedPayload :: a -- ^ Payload packet they sent to us (ignored). | ||
54 | , peAcknowledgedNum :: Word32 -- ^ Earliest sequence number they've seen from us. | ||
55 | } | ||
56 | deriving Functor | ||
57 | |||
58 | -- | Whenever a packet is received or sent (but not resent) from the network, | ||
59 | -- this function should be called to update the relevant buffers. | ||
60 | -- | ||
61 | -- On outgoing packets, if the outbound buffer is full, this will return | ||
62 | -- True. In this case, the caller may retry to enable blocking until | ||
63 | -- 'grokInboundPacket' is called in another thread. | ||
64 | grokOutboundPacket :: PacketBuffer a b -> PacketOutboundEvent b -> STM (Bool,(Word32,Word32)) | ||
65 | grokOutboundPacket (PacketBuffer _ outb) (PacketSent seqno a) | ||
66 | = do (n,r) <- Q.enqueue outb seqno a | ||
67 | return (n/=0,(n,r)) | ||
68 | |||
69 | grokInboundPacket :: PacketBuffer a b -> PacketInboundEvent a -> STM () | ||
70 | grokInboundPacket (PacketBuffer inb outb) (PacketReceived seqno a ack) | ||
71 | = do Q.enqueue inb seqno a | ||
72 | Q.dropPacketsBefore outb ack | ||
73 | grokInboundPacket (PacketBuffer inb outb) (PacketReceivedLossy seqno _ ack) | ||
74 | = do Q.observeOutOfBand inb seqno | ||
75 | Q.dropPacketsBefore outb ack | ||
76 | |||
77 | -- | Wait until an inbound packet is ready to process. Any necessary | ||
78 | -- re-ordering will have been done. | ||
79 | awaitReadyPacket :: PacketBuffer a b -> STM a | ||
80 | awaitReadyPacket (PacketBuffer inb _) = Q.dequeue inb | ||
81 | |||
82 | -- | Obtain a list of sequence numbers that may have been dropped. This would | ||
83 | -- be any number not yet received that is prior to the maxium sequence number | ||
84 | -- we have received. For convenience, a lowerbound for the missing squence numbers | ||
85 | -- is also returned as the second item of the pair. | ||
86 | packetNumbersToRequest :: PacketBuffer a b -> STM ([Word32],Word32) | ||
87 | packetNumbersToRequest (PacketBuffer inb _) = do | ||
88 | ns <- Q.getMissing inb | ||
89 | lb <- Q.getLastDequeuedPlus1 inb | ||
90 | return (ns,lb) | ||
91 | |||
92 | expectingSequenceNumber :: PacketBuffer a b -> STM Word32 | ||
93 | expectingSequenceNumber (PacketBuffer inb _ ) = Q.getLastDequeuedPlus1 inb | ||
94 | |||
95 | nextToSendSequenceNumber :: PacketBuffer a b -> STM Word32 | ||
96 | nextToSendSequenceNumber (PacketBuffer _ outb) = Q.getLastEnqueuedPlus1 outb | ||
97 | |||
98 | -- | Retrieve already-sent packets by their sequence numbers. See | ||
99 | -- 'decompressSequenceNumbers' to obtain the sequence number list from a | ||
100 | -- compressed encoding. There is no need to call 'grokPacket' when sending the | ||
101 | -- packets returned from this call. | ||
102 | retrieveForResend :: PacketBuffer a b -> [Word32] -> STM [(Word32,b)] | ||
103 | retrieveForResend (PacketBuffer _ outb) seqnos = | ||
104 | catMaybes <$> forM seqnos (\no -> fmap (no,) <$> Q.lookup outb no) | ||
105 | |||
106 | -- | Expand a compressed set of sequence numbers. The first sequence number is | ||
107 | -- given directly and the rest are computed using 8-bit offsets. This is | ||
108 | -- normally used to obtain input for 'retrieveForResend'. | ||
109 | decompressSequenceNumbers :: Word32 -> [Word8] -> [Word32] | ||
110 | decompressSequenceNumbers baseno ns = foldr doOne (const []) ns (baseno-1) | ||
111 | where | ||
112 | doOne :: Word8 -> (Word32 -> [Word32]) -> Word32 -> [Word32] | ||
113 | doOne 0 f addend = f (addend + 255) | ||
114 | doOne x f addend = let y = fromIntegral x + addend | ||
115 | in y : f y | ||
116 | |||
117 | compressSequenceNumbers :: Word32 -> [Word32] -> [Word8] | ||
118 | compressSequenceNumbers baseno xs = foldr doOne (const []) xs (baseno-1) | ||
119 | where | ||
120 | doOne :: Word32 -> (Word32 -> [Word8]) -> Word32 -> [Word8] | ||
121 | doOne y f addend = case y - addend of | ||
122 | x | x < 255 -> fromIntegral x : f y | ||
123 | | otherwise -> 0 : doOne y f (addend + 255) | ||
124 | |||
125 | {- | ||
126 | compressSequenceNumbers :: Word32 -> [Word32] -> [Word8] | ||
127 | compressSequenceNumbers seqno xs = let r = map fromIntegral (reduceToSums ys >>= makeZeroes) | ||
128 | in dtrace XNetCrypto ("compressSequenceNumbers " ++ show seqno ++ show xs ++ " --> "++show r) r | ||
129 | where | ||
130 | ys = Prelude.map (subtract (seqno - 1)) xs | ||
131 | reduceToSums [] = [] | ||
132 | reduceToSums (x:xs) = x:(reduceToSums $ Prelude.map (subtract x) xs) | ||
133 | makeZeroes :: Word32 -> [Word32] | ||
134 | -- makeZeroes 0 = [] | ||
135 | makeZeroes x | ||
136 | = let (d,m)= x `divMod` 255 | ||
137 | zeros= Prelude.replicate (fromIntegral d) 0 | ||
138 | in zeros ++ [m] | ||
139 | -} | ||
140 | |||
141 | pbReport :: String -> PacketBuffer a b -> STM String | ||
142 | pbReport what (PacketBuffer inb outb) = do | ||
143 | inb_seqno <- getLastDequeuedPlus1 inb | ||
144 | inb_buffend <- getLastEnqueuedPlus1 inb | ||
145 | outb_seqno <- getLastDequeuedPlus1 outb | ||
146 | outb_bufend <- getLastEnqueuedPlus1 outb | ||
147 | return $ "PacketBuffer<"++what++"> Inbound" ++ show (inb_seqno, inb_buffend) | ||
148 | ++" Outbound" ++ show (outb_seqno, outb_bufend) | ||
diff --git a/src/Data/PacketQueue.hs b/src/Data/PacketQueue.hs deleted file mode 100644 index 15a3b436..00000000 --- a/src/Data/PacketQueue.hs +++ /dev/null | |||
@@ -1,217 +0,0 @@ | |||
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 | module Data.PacketQueue | ||
7 | ( PacketQueue | ||
8 | , getCapacity | ||
9 | , getLastDequeuedPlus1 | ||
10 | , getLastEnqueuedPlus1 | ||
11 | , new | ||
12 | , dequeue | ||
13 | , dropPacketsLogic | ||
14 | , dropPacketsBefore | ||
15 | , getMissing | ||
16 | -- , dequeueOrGetMissing | ||
17 | -- , markButNotDequeue | ||
18 | , enqueue | ||
19 | , observeOutOfBand | ||
20 | , packetQueueViewList | ||
21 | -- , mapQ | ||
22 | , Data.PacketQueue.lookup | ||
23 | ) where | ||
24 | |||
25 | import Control.Concurrent.STM | ||
26 | import Control.Monad | ||
27 | import Data.Word | ||
28 | import Data.Array.MArray | ||
29 | import Data.Maybe | ||
30 | |||
31 | data PacketQueue a = PacketQueue | ||
32 | { pktq :: TArray Word32 (Maybe a) | ||
33 | , seqno :: TVar Word32 -- (buffer_start) | ||
34 | , qsize :: Word32 | ||
35 | , buffend :: TVar Word32 -- on incoming, next packet they'll send + 1 | ||
36 | -- i.e. one more than the largest seen sequence number. | ||
37 | -- Written by: | ||
38 | -- observeOutOfBand | ||
39 | -- dropPacketsBefore | ||
40 | -- enqueue | ||
41 | } | ||
42 | |||
43 | -- | Obtain a list of non-empty slots in the 'PacketQueue'. The numeric value | ||
44 | -- is an index into the underlying array, not a sequence number. | ||
45 | packetQueueViewList :: PacketQueue a -> STM [(Word32,a)] | ||
46 | packetQueueViewList p = do | ||
47 | let f (n,Nothing) = Nothing | ||
48 | f (n,Just x) = Just (n,x) | ||
49 | catMaybes . map f <$> getAssocs (pktq p) | ||
50 | |||
51 | -- | This returns the earliest sequence number with a slot in the queue. | ||
52 | getLastDequeuedPlus1 :: PacketQueue a -> STM Word32 | ||
53 | getLastDequeuedPlus1 PacketQueue {seqno} = readTVar seqno | ||
54 | |||
55 | -- | This returns the least upper bound of sequence numbers that have been | ||
56 | -- enqueued. | ||
57 | getLastEnqueuedPlus1 :: PacketQueue a -> STM Word32 | ||
58 | getLastEnqueuedPlus1 PacketQueue {buffend} = readTVar buffend | ||
59 | |||
60 | |||
61 | -- | This is the number of consequetive sequence numbers, starting at | ||
62 | -- 'getLastDequeuedPlus1' that can be stored in the queue | ||
63 | getCapacity :: Applicative m => PacketQueue t -> m Word32 | ||
64 | getCapacity (PacketQueue { qsize }) = pure qsize | ||
65 | |||
66 | -- | Create a new PacketQueue. | ||
67 | new :: Word32 -- ^ Capacity of queue. | ||
68 | -> Word32 -- ^ Initial sequence number. | ||
69 | -> STM (PacketQueue a) | ||
70 | new capacity seqstart = do | ||
71 | let cap = if capacity `mod` 2 == 0 then capacity else capacity + 1 | ||
72 | q <- newArray (0,cap - 1) Nothing | ||
73 | seqv <- newTVar seqstart | ||
74 | bufe <- newTVar seqstart | ||
75 | return PacketQueue | ||
76 | { pktq = q | ||
77 | , seqno = seqv | ||
78 | , qsize = cap | ||
79 | , buffend = bufe | ||
80 | } | ||
81 | |||
82 | -- | Update the packet queue given: | ||
83 | -- | ||
84 | -- * packet queue | ||
85 | -- | ||
86 | -- * the number of next lossless packet they intend to send you | ||
87 | -- | ||
88 | -- This behaves exactly like 'enqueue' except that no packet data is written to | ||
89 | -- the queue. | ||
90 | observeOutOfBand :: PacketQueue a -> Word32-> STM () | ||
91 | observeOutOfBand PacketQueue { seqno, qsize, buffend } numberOfNextLosslessPacketThatTheyWillSend = do | ||
92 | low <- readTVar seqno | ||
93 | let proj = numberOfNextLosslessPacketThatTheyWillSend - low | ||
94 | -- Ignore packet if out of range. | ||
95 | when ( proj < qsize) $ do | ||
96 | modifyTVar' buffend (\be -> if be - low <= proj then numberOfNextLosslessPacketThatTheyWillSend + 1 else be) | ||
97 | |||
98 | -- | If seqno < buffend then return expected packet numbers for all | ||
99 | -- the Nothings in the array between them. | ||
100 | -- Otherwise, return empty list. | ||
101 | getMissing :: PacketQueue a -> STM [Word32] | ||
102 | getMissing PacketQueue { pktq, seqno, qsize, buffend } = do | ||
103 | seqno0 <- readTVar seqno | ||
104 | buffend0 <- readTVar buffend | ||
105 | -- note relying on fact that [ b .. a ] is null when a < b | ||
106 | let indices = take (fromIntegral qsize) $ [ seqno0 .. buffend0 - 1] | ||
107 | maybes <- forM indices $ \i -> do | ||
108 | x <- readArray pktq $ i `mod` qsize | ||
109 | return (i,x) | ||
110 | let nums = map fst . filter (isNothing . snd) $ maybes | ||
111 | return nums | ||
112 | |||
113 | -- -- | If seqno < buffend then return expected packet numbers for all | ||
114 | -- -- the Nothings in the array between them. | ||
115 | -- -- Otherwise, behave as 'dequeue' would. | ||
116 | -- -- TODO: Do we need this function? Delete it if not. | ||
117 | -- dequeueOrGetMissing :: PacketQueue a -> STM (Either [Word32] a) | ||
118 | -- dequeueOrGetMissing PacketQueue { pktq, seqno, qsize, buffend } = do | ||
119 | -- seqno0 <- readTVar seqno | ||
120 | -- buffend0 <- readTVar buffend | ||
121 | -- if seqno0 < buffend0 | ||
122 | -- then do | ||
123 | -- maybes <- mapM (readArray pktq) (take (fromIntegral qsize) $ map (`mod` qsize) [ seqno0 .. buffend0 ]) | ||
124 | -- let nums = map fst . filter (isNothing . snd) $ zip [buffend0 ..] maybes | ||
125 | -- return (Left nums) | ||
126 | -- else do | ||
127 | -- let i = seqno0 `mod` qsize | ||
128 | -- x <- maybe retry return =<< readArray pktq i | ||
129 | -- writeArray pktq i Nothing | ||
130 | -- modifyTVar' seqno succ | ||
131 | -- return (Right x) | ||
132 | |||
133 | -- | Retry until the next expected packet is enqueued. Then return it. | ||
134 | dequeue :: PacketQueue a -> STM a | ||
135 | dequeue PacketQueue { pktq, seqno, qsize } = do | ||
136 | i0 <- readTVar seqno | ||
137 | let i = i0 `mod` qsize | ||
138 | x <- maybe retry return =<< readArray pktq i | ||
139 | writeArray pktq i Nothing | ||
140 | modifyTVar' seqno succ | ||
141 | return x | ||
142 | |||
143 | -- | Helper to 'dropPacketsBefore'. | ||
144 | dropPacketsLogic :: Word32 -> Word32 -> Word32 -> (Maybe Word32, Word32, [(Word32,Word32)]) | ||
145 | dropPacketsLogic qsize low no0 = | ||
146 | let no = no0 - 1 -- Unsigned: could overflow | ||
147 | proj = no - low -- Unsigned: could overflow | ||
148 | in if proj < qsize | ||
149 | then | ||
150 | let ilow = low `mod` qsize | ||
151 | i = no `mod` qsize | ||
152 | ranges = if ilow <= i then [(ilow, i)] | ||
153 | else [(0,i),(ilow,qsize-1)] | ||
154 | in (Nothing,no0,ranges) -- Clear some, but not all, slots. | ||
155 | else (Nothing,low,[]) -- out of bounds, do nothing -- (Just no0, no0, [(0,qsize - 1)]) -- Reset to empty queue. | ||
156 | |||
157 | |||
158 | -- | Drop all packets preceding the given packet number. | ||
159 | dropPacketsBefore :: PacketQueue a -> Word32 -> STM () | ||
160 | dropPacketsBefore PacketQueue{ pktq, seqno, qsize, buffend } no0 = do | ||
161 | low <- readTVar seqno | ||
162 | let (mbuffend, no, ranges) = dropPacketsLogic qsize low no0 | ||
163 | mapM_ (writeTVar buffend) mbuffend | ||
164 | writeTVar seqno no | ||
165 | forM_ ranges $ \(lo,hi) -> forM_ [lo .. hi] $ \i -> writeArray pktq i Nothing | ||
166 | |||
167 | |||
168 | -- -- | Like dequeue, but marks as viewed rather than removing | ||
169 | -- markButNotDequeue :: PacketQueue (Bool,a) -> STM a | ||
170 | -- markButNotDequeue PacketQueue { pktq, seqno, qsize } = do | ||
171 | -- i0 <- readTVar seqno | ||
172 | -- let i = i0 `mod` qsize | ||
173 | -- (b,x) <- maybe retry return =<< readArray pktq i | ||
174 | -- writeArray pktq i (Just (True,x)) | ||
175 | -- modifyTVar' seqno succ | ||
176 | -- return x | ||
177 | |||
178 | -- | Enqueue a packet. Packets need not be enqueued in order as long as there | ||
179 | -- is spare capacity in the queue. If there is not, the packet will be | ||
180 | -- silently discarded without blocking. (Unless this is an Overwrite-queue, in | ||
181 | -- which case, the packets will simply wrap around overwriting the old ones.) | ||
182 | -- | ||
183 | -- If the packet was enqueued, (0,i) will be retuned where /i/ is the index at | ||
184 | -- which the new packet was stored in the buffer. If the queue was full, the | ||
185 | -- first of the returned pair will be non-zero. | ||
186 | enqueue :: PacketQueue a -- ^ The packet queue. | ||
187 | -> Word32 -- ^ Sequence number of the packet. | ||
188 | -> a -- ^ The packet. | ||
189 | -> STM (Word32,Word32) | ||
190 | enqueue PacketQueue{ pktq, seqno, qsize, buffend} no x = do | ||
191 | low <- readTVar seqno | ||
192 | let proj = no - low | ||
193 | -- Ignore packet if out of range. | ||
194 | when ( proj < qsize) $ do | ||
195 | let i = no `mod` qsize | ||
196 | writeArray pktq i (Just x) | ||
197 | modifyTVar' buffend (\be -> if be - low <= proj then no + 1 else be) | ||
198 | return (proj `divMod` qsize) | ||
199 | |||
200 | -- | Obtain the packet with the given sequence number if it is stored in the | ||
201 | -- queue, otherwise /Nothing/ is returned without blocking. | ||
202 | lookup :: PacketQueue a -> Word32 -> STM (Maybe a) | ||
203 | lookup PacketQueue{ pktq, seqno, qsize } no = do | ||
204 | low <- readTVar seqno | ||
205 | let proj = no - low | ||
206 | if proj < qsize | ||
207 | then let i = no `mod` qsize | ||
208 | in readArray pktq i | ||
209 | else return Nothing | ||
210 | |||
211 | -- -- | For each item in the queue, modify or delete it. | ||
212 | -- mapQ :: (a -> Maybe a) -> PacketQueue a -> STM () | ||
213 | -- mapQ f PacketQueue{pktq} = do | ||
214 | -- (z,n) <- getBounds pktq | ||
215 | -- forM_ [z .. n] $ \i -> do | ||
216 | -- e <- readArray pktq i | ||
217 | -- writeArray pktq i (e>>=f) | ||
diff --git a/src/Data/Sized.hs b/src/Data/Sized.hs deleted file mode 100644 index 0d3d5845..00000000 --- a/src/Data/Sized.hs +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | module Data.Sized where | ||
2 | |||
3 | import Data.Typeable | ||
4 | |||
5 | |||
6 | -- | Info about a type's serialized length. Either the length is known | ||
7 | -- independently of the value, or the length depends on the value. | ||
8 | data Size a | ||
9 | = VarSize (a -> Int) | ||
10 | | ConstSize !Int | ||
11 | deriving Typeable | ||
12 | |||
13 | class Sized a where size :: Size a | ||
14 | |||
diff --git a/src/Data/TableMethods.hs b/src/Data/TableMethods.hs deleted file mode 100644 index e4208a69..00000000 --- a/src/Data/TableMethods.hs +++ /dev/null | |||
@@ -1,105 +0,0 @@ | |||
1 | {-# LANGUAGE CPP #-} | ||
2 | {-# LANGUAGE GADTs #-} | ||
3 | {-# LANGUAGE LambdaCase #-} | ||
4 | {-# LANGUAGE PartialTypeSignatures #-} | ||
5 | {-# LANGUAGE RankNTypes #-} | ||
6 | {-# LANGUAGE ScopedTypeVariables #-} | ||
7 | {-# LANGUAGE TupleSections #-} | ||
8 | module Data.TableMethods where | ||
9 | |||
10 | import Data.Functor.Contravariant | ||
11 | import Data.Time.Clock.POSIX | ||
12 | import Data.Word | ||
13 | import qualified Data.IntMap.Strict as IntMap | ||
14 | ;import Data.IntMap.Strict (IntMap) | ||
15 | import qualified Data.Map.Strict as Map | ||
16 | ;import Data.Map.Strict (Map) | ||
17 | import qualified Data.Word64Map as W64Map | ||
18 | ;import Data.Word64Map (Word64Map) | ||
19 | |||
20 | import Data.Wrapper.PSQ as PSQ | ||
21 | |||
22 | type Priority = POSIXTime | ||
23 | |||
24 | data OptionalPriority t tid x | ||
25 | = NoPriority | ||
26 | | HasPriority (Priority -> t x -> ([(tid, Priority, x)], t x)) | ||
27 | |||
28 | -- | The standard lookup table methods. | ||
29 | data TableMethods t tid = TableMethods | ||
30 | { -- | Insert a new /tid/ entry into the transaction table. | ||
31 | tblInsert :: forall a. tid -> a -> Priority -> t a -> t a | ||
32 | -- | Delete transaction /tid/ from the transaction table. | ||
33 | , tblDelete :: forall a. tid -> t a -> t a | ||
34 | -- | Lookup the value associated with transaction /tid/. | ||
35 | , tblLookup :: forall a. tid -> t a -> Maybe a | ||
36 | } | ||
37 | |||
38 | data QMethods t tid x = QMethods | ||
39 | { qTbl :: TableMethods t tid | ||
40 | , qAtMostView :: OptionalPriority t tid x | ||
41 | } | ||
42 | |||
43 | vanillaTable :: TableMethods t tid -> QMethods t tid x | ||
44 | vanillaTable tbl = QMethods tbl NoPriority | ||
45 | |||
46 | priorityTable :: TableMethods t tid | ||
47 | -> (Priority -> t x -> ([(k, Priority, x)], t x)) | ||
48 | -> (k -> x -> tid) | ||
49 | -> QMethods t tid x | ||
50 | priorityTable tbl atmost f = QMethods | ||
51 | { qTbl = tbl | ||
52 | , qAtMostView = HasPriority $ \p t -> case atmost p t of | ||
53 | (es,t') -> (map (\(k,p,a) -> (f k a, p, a)) es, t') | ||
54 | } | ||
55 | |||
56 | -- | Methods for using 'Data.IntMap'. | ||
57 | intMapMethods :: TableMethods IntMap Int | ||
58 | intMapMethods = TableMethods | ||
59 | { tblInsert = \tid a p -> IntMap.insert tid a | ||
60 | , tblDelete = IntMap.delete | ||
61 | , tblLookup = IntMap.lookup | ||
62 | } | ||
63 | |||
64 | -- | Methods for using 'Data.Word64Map'. | ||
65 | w64MapMethods :: TableMethods Word64Map Word64 | ||
66 | w64MapMethods = TableMethods | ||
67 | { tblInsert = \tid a p -> W64Map.insert tid a | ||
68 | , tblDelete = W64Map.delete | ||
69 | , tblLookup = W64Map.lookup | ||
70 | } | ||
71 | |||
72 | -- | Methods for using 'Data.Map' | ||
73 | mapMethods :: Ord tid => TableMethods (Map tid) tid | ||
74 | mapMethods = TableMethods | ||
75 | { tblInsert = \tid a p -> Map.insert tid a | ||
76 | , tblDelete = Map.delete | ||
77 | , tblLookup = Map.lookup | ||
78 | } | ||
79 | |||
80 | -- psqMethods :: PSQKey tid => QMethods (HashPSQ tid Priority) tid x | ||
81 | psqMethods :: PSQKey k => (tid -> k) -> (k -> x -> tid) -> QMethods (PSQ' k Priority) tid x | ||
82 | psqMethods g f = priorityTable (contramap g tbl) PSQ.atMostView f | ||
83 | where | ||
84 | tbl :: PSQKey tid => TableMethods (PSQ' tid Priority) tid | ||
85 | tbl = TableMethods | ||
86 | { tblInsert = PSQ.insert' | ||
87 | , tblDelete = PSQ.delete | ||
88 | , tblLookup = \tid t -> case PSQ.lookup tid t of | ||
89 | Just (p,a) -> Just a | ||
90 | Nothing -> Nothing | ||
91 | } | ||
92 | |||
93 | |||
94 | -- | Change the key type for a lookup table implementation. | ||
95 | -- | ||
96 | -- This can be used with 'intMapMethods' or 'mapMethods' to restrict lookups to | ||
97 | -- only a part of the generated /tid/ value. This is useful for /tid/ types | ||
98 | -- that are especially large due their use for other purposes, such as secure | ||
99 | -- nonces for encryption. | ||
100 | instance Contravariant (TableMethods t) where | ||
101 | -- contramap :: (tid -> t1) -> TableMethods t t1 -> TableMethods t tid | ||
102 | contramap f (TableMethods ins del lookup) = | ||
103 | TableMethods (\k p v t -> ins (f k) p v t) | ||
104 | (\k t -> del (f k) t) | ||
105 | (\k t -> lookup (f k) t) | ||
diff --git a/src/Data/Torrent.hs b/src/Data/Torrent.hs deleted file mode 100644 index 32c709be..00000000 --- a/src/Data/Torrent.hs +++ /dev/null | |||
@@ -1,1347 +0,0 @@ | |||
1 | -- | | ||
2 | -- Copyright : (c) Sam Truzjan 2013 | ||
3 | -- License : BSD3 | ||
4 | -- Maintainer : pxqr.sta@gmail.com | ||
5 | -- Stability : experimental | ||
6 | -- Portability : portable | ||
7 | -- | ||
8 | -- Torrent file contains metadata about files and folders but not | ||
9 | -- content itself. The files are bencoded dictionaries. There is | ||
10 | -- also other info which is used to help join the swarm. | ||
11 | -- | ||
12 | -- This module provides torrent metainfo serialization and info hash | ||
13 | -- extraction. | ||
14 | -- | ||
15 | -- For more info see: | ||
16 | -- <http://www.bittorrent.org/beps/bep_0003.html#metainfo-files>, | ||
17 | -- <https://wiki.theory.org/BitTorrentSpecification#Metainfo_File_Structure> | ||
18 | -- | ||
19 | {-# LANGUAGE CPP #-} | ||
20 | {-# LANGUAGE NamedFieldPuns #-} | ||
21 | {-# LANGUAGE FlexibleInstances #-} | ||
22 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
23 | {-# LANGUAGE BangPatterns #-} | ||
24 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
25 | {-# LANGUAGE StandaloneDeriving #-} | ||
26 | {-# LANGUAGE DeriveDataTypeable #-} | ||
27 | {-# LANGUAGE DeriveFunctor #-} | ||
28 | {-# LANGUAGE DeriveFoldable #-} | ||
29 | {-# LANGUAGE DeriveTraversable #-} | ||
30 | {-# LANGUAGE TemplateHaskell #-} | ||
31 | {-# OPTIONS -fno-warn-orphans #-} | ||
32 | module Data.Torrent | ||
33 | ( -- * InfoHash | ||
34 | -- $infohash | ||
35 | InfoHash(..) | ||
36 | , textToInfoHash | ||
37 | , longHex | ||
38 | , shortHex | ||
39 | |||
40 | -- * File layout | ||
41 | -- ** FileInfo | ||
42 | , FileOffset | ||
43 | , FileSize | ||
44 | , FileInfo (..) | ||
45 | #ifdef USE_lens | ||
46 | , fileLength | ||
47 | , filePath | ||
48 | , fileMD5Sum | ||
49 | #endif | ||
50 | |||
51 | -- ** Layout info | ||
52 | , LayoutInfo (..) | ||
53 | #ifdef USE_lens | ||
54 | , singleFile | ||
55 | , multiFile | ||
56 | , rootDirName | ||
57 | #endif | ||
58 | , joinFilePath | ||
59 | , isSingleFile | ||
60 | , isMultiFile | ||
61 | , suggestedName | ||
62 | , contentLength | ||
63 | , fileCount | ||
64 | , blockCount | ||
65 | |||
66 | -- ** Flat layout info | ||
67 | , FileLayout | ||
68 | , flatLayout | ||
69 | , accumPositions | ||
70 | , fileOffset | ||
71 | |||
72 | -- ** Internal | ||
73 | , sizeInBase | ||
74 | |||
75 | -- * Pieces | ||
76 | -- ** Attributes | ||
77 | , PieceIx | ||
78 | , PieceCount | ||
79 | , PieceSize | ||
80 | , minPieceSize | ||
81 | , maxPieceSize | ||
82 | , defaultPieceSize | ||
83 | , PieceHash | ||
84 | |||
85 | -- ** Piece data | ||
86 | , Piece (..) | ||
87 | , pieceSize | ||
88 | , hashPiece | ||
89 | |||
90 | -- ** Piece control | ||
91 | , HashList (..) | ||
92 | , PieceInfo (..) | ||
93 | #ifdef USE_lens | ||
94 | , pieceLength | ||
95 | , pieceHashes | ||
96 | #endif | ||
97 | , pieceCount | ||
98 | |||
99 | -- ** Validation | ||
100 | , pieceHash | ||
101 | , checkPieceLazy | ||
102 | |||
103 | -- * Info dictionary | ||
104 | , InfoDict (..) | ||
105 | #ifdef USE_lens | ||
106 | , infohash | ||
107 | , layoutInfo | ||
108 | , pieceInfo | ||
109 | , isPrivate | ||
110 | #endif | ||
111 | #ifdef VERSION_bencoding | ||
112 | , infoDictionary | ||
113 | #endif | ||
114 | |||
115 | -- * Torrent file | ||
116 | , Torrent(..) | ||
117 | |||
118 | #ifdef USE_lens | ||
119 | -- ** Lenses | ||
120 | , announce | ||
121 | , announceList | ||
122 | , comment | ||
123 | , createdBy | ||
124 | , creationDate | ||
125 | , encoding | ||
126 | , infoDict | ||
127 | , publisher | ||
128 | , publisherURL | ||
129 | , signature | ||
130 | #endif | ||
131 | |||
132 | -- ** Utils | ||
133 | , nullTorrent | ||
134 | , typeTorrent | ||
135 | , torrentExt | ||
136 | , isTorrentPath | ||
137 | #ifdef VERSION_bencoding | ||
138 | , fromFile | ||
139 | , toFile | ||
140 | #endif | ||
141 | |||
142 | -- * Magnet | ||
143 | -- $magnet-link | ||
144 | , Magnet(..) | ||
145 | , nullMagnet | ||
146 | , simpleMagnet | ||
147 | , detailedMagnet | ||
148 | , parseMagnet | ||
149 | , renderMagnet | ||
150 | |||
151 | -- ** URN | ||
152 | , URN (..) | ||
153 | , NamespaceId | ||
154 | , btih | ||
155 | , infohashURN | ||
156 | , parseURN | ||
157 | , renderURN | ||
158 | ) where | ||
159 | |||
160 | import Prelude hiding ((<>)) | ||
161 | import Control.Applicative | ||
162 | import Control.DeepSeq | ||
163 | import Control.Exception | ||
164 | -- import Control.Lens | ||
165 | import Control.Monad | ||
166 | import Crypto.Hash | ||
167 | #ifdef VERSION_bencoding | ||
168 | import Data.BEncode as BE | ||
169 | import Data.BEncode.Types as BE | ||
170 | #endif | ||
171 | import Data.Bits | ||
172 | #ifdef VERSION_bits_extras | ||
173 | import Data.Bits.Extras | ||
174 | #endif | ||
175 | import qualified Data.ByteArray as Bytes | ||
176 | import Data.ByteString as BS | ||
177 | import Data.ByteString.Base16 as Base16 | ||
178 | import Data.ByteString.Base32 as Base32 | ||
179 | import Data.ByteString.Base64 as Base64 | ||
180 | import Data.ByteString.Char8 as BC (pack, unpack) | ||
181 | import Data.ByteString.Lazy as BL | ||
182 | import Data.Char | ||
183 | import Data.Convertible | ||
184 | import Data.Default | ||
185 | import Data.Hashable as Hashable | ||
186 | import Data.Int | ||
187 | import Data.List as L | ||
188 | import Data.Map as M | ||
189 | import Data.Maybe | ||
190 | import Data.Serialize as S | ||
191 | import Data.String | ||
192 | import Data.Text as T | ||
193 | import Data.Text.Encoding as T | ||
194 | import Data.Text.Read | ||
195 | import Data.Time.Clock.POSIX | ||
196 | import Data.Typeable | ||
197 | import Network (HostName) | ||
198 | import Network.HTTP.Types.QueryLike | ||
199 | import Network.HTTP.Types.URI | ||
200 | import Network.URI | ||
201 | import Text.ParserCombinators.ReadP as P | ||
202 | import Text.PrettyPrint as PP | ||
203 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) | ||
204 | import System.FilePath | ||
205 | import System.Posix.Types | ||
206 | |||
207 | import Network.Address | ||
208 | |||
209 | |||
210 | {----------------------------------------------------------------------- | ||
211 | -- Info hash | ||
212 | -----------------------------------------------------------------------} | ||
213 | -- TODO | ||
214 | -- | ||
215 | -- data Word160 = Word160 {-# UNPACK #-} !Word64 | ||
216 | -- {-# UNPACK #-} !Word64 | ||
217 | -- {-# UNPACK #-} !Word32 | ||
218 | -- | ||
219 | -- newtype InfoHash = InfoHash Word160 | ||
220 | -- | ||
221 | -- reason: bytestring have overhead = 8 words, while infohash have length 20 bytes | ||
222 | |||
223 | -- $infohash | ||
224 | -- | ||
225 | -- Infohash is a unique identifier of torrent. | ||
226 | |||
227 | -- | Exactly 20 bytes long SHA1 hash of the info part of torrent file. | ||
228 | newtype InfoHash = InfoHash { getInfoHash :: BS.ByteString } | ||
229 | deriving (Eq, Ord, Typeable) | ||
230 | |||
231 | infoHashLen :: Int | ||
232 | infoHashLen = 20 | ||
233 | |||
234 | -- | Meaningless placeholder value. | ||
235 | instance Default InfoHash where | ||
236 | def = "0123456789012345678901234567890123456789" | ||
237 | |||
238 | -- | Hash raw bytes. (no encoding) | ||
239 | instance Hashable InfoHash where | ||
240 | hashWithSalt s (InfoHash ih) = hashWithSalt s ih | ||
241 | {-# INLINE hashWithSalt #-} | ||
242 | |||
243 | #ifdef VERSION_bencoding | ||
244 | -- | Convert to\/from raw bencoded string. (no encoding) | ||
245 | instance BEncode InfoHash where | ||
246 | toBEncode = toBEncode . getInfoHash | ||
247 | fromBEncode be = InfoHash <$> fromBEncode be | ||
248 | #endif | ||
249 | |||
250 | #if 0 | ||
251 | instance TableKey KMessageOf InfoHash where | ||
252 | toNodeId = either (error msg) id . S.decode . S.encode | ||
253 | where -- TODO unsafe coerse? | ||
254 | msg = "tableKey: impossible" | ||
255 | #endif | ||
256 | |||
257 | |||
258 | -- | Convert to\/from raw bytestring. (no encoding) | ||
259 | instance Serialize InfoHash where | ||
260 | put (InfoHash ih) = putByteString ih | ||
261 | {-# INLINE put #-} | ||
262 | |||
263 | get = InfoHash <$> getBytes infoHashLen | ||
264 | {-# INLINE get #-} | ||
265 | |||
266 | -- | Convert to raw query value. (no encoding) | ||
267 | instance QueryValueLike InfoHash where | ||
268 | toQueryValue (InfoHash ih) = Just ih | ||
269 | {-# INLINE toQueryValue #-} | ||
270 | |||
271 | -- | Convert to base16 encoded string. | ||
272 | instance Show InfoHash where | ||
273 | show (InfoHash ih) = BC.unpack (Base16.encode ih) | ||
274 | |||
275 | -- | Convert to base16 encoded Doc string. | ||
276 | instance Pretty InfoHash where | ||
277 | pPrint = text . show | ||
278 | |||
279 | -- | Read base16 encoded string. | ||
280 | instance Read InfoHash where | ||
281 | readsPrec _ = readP_to_S $ do | ||
282 | str <- replicateM (infoHashLen * 2) (satisfy isHexDigit) | ||
283 | return $ InfoHash $ decodeIH str | ||
284 | where | ||
285 | decodeIH = BS.pack . L.map fromHex . pair | ||
286 | fromHex (a, b) = read $ '0' : 'x' : a : b : [] | ||
287 | |||
288 | pair (a : b : xs) = (a, b) : pair xs | ||
289 | pair _ = [] | ||
290 | |||
291 | -- | Convert raw bytes to info hash. | ||
292 | instance Convertible BS.ByteString InfoHash where | ||
293 | safeConvert bs | ||
294 | | BS.length bs == infoHashLen = pure (InfoHash bs) | ||
295 | | otherwise = convError "invalid length" bs | ||
296 | |||
297 | -- | Parse infohash from base16\/base32\/base64 encoded string. | ||
298 | instance Convertible Text InfoHash where | ||
299 | safeConvert t | ||
300 | | 20 == hashLen = pure (InfoHash hashStr) | ||
301 | | 26 <= hashLen && hashLen <= 28 = | ||
302 | case Base64.decode hashStr of | ||
303 | Left msg -> convError ("invalid base64 encoding " ++ msg) t | ||
304 | Right ihStr -> safeConvert ihStr | ||
305 | |||
306 | | hashLen == 32 = | ||
307 | case Base32.decode hashStr of | ||
308 | Left msg -> convError msg t | ||
309 | Right ihStr -> safeConvert ihStr | ||
310 | |||
311 | | hashLen == 40 = | ||
312 | let (ihStr, inv) = Base16.decode hashStr | ||
313 | in if BS.length inv /= 0 | ||
314 | then convError "invalid base16 encoding" t | ||
315 | else safeConvert ihStr | ||
316 | |||
317 | | otherwise = convError "invalid length" t | ||
318 | where | ||
319 | hashLen = BS.length hashStr | ||
320 | hashStr = T.encodeUtf8 t | ||
321 | |||
322 | -- | Decode from base16\/base32\/base64 encoded string. | ||
323 | instance IsString InfoHash where | ||
324 | fromString = either (error . prettyConvertError) id . safeConvert . T.pack | ||
325 | |||
326 | ignoreErrorMsg :: Either a b -> Maybe b | ||
327 | ignoreErrorMsg = either (const Nothing) Just | ||
328 | |||
329 | -- | Tries both base16 and base32 while decoding info hash. | ||
330 | -- | ||
331 | -- Use 'safeConvert' for detailed error messages. | ||
332 | -- | ||
333 | textToInfoHash :: Text -> Maybe InfoHash | ||
334 | textToInfoHash = ignoreErrorMsg . safeConvert | ||
335 | |||
336 | -- | Hex encode infohash to text, full length. | ||
337 | longHex :: InfoHash -> Text | ||
338 | longHex = T.decodeUtf8 . Base16.encode . getInfoHash | ||
339 | |||
340 | -- | The same as 'longHex', but only first 7 characters. | ||
341 | shortHex :: InfoHash -> Text | ||
342 | shortHex = T.take 7 . longHex | ||
343 | |||
344 | {----------------------------------------------------------------------- | ||
345 | -- File info | ||
346 | -----------------------------------------------------------------------} | ||
347 | |||
348 | -- | Size of a file in bytes. | ||
349 | type FileSize = FileOffset | ||
350 | |||
351 | #ifdef VERSION_bencoding | ||
352 | deriving instance BEncode FileOffset | ||
353 | #endif | ||
354 | |||
355 | -- | Contain metainfo about one single file. | ||
356 | data FileInfo a = FileInfo { | ||
357 | fiLength :: {-# UNPACK #-} !FileSize | ||
358 | -- ^ Length of the file in bytes. | ||
359 | |||
360 | -- TODO unpacked MD5 sum | ||
361 | , fiMD5Sum :: !(Maybe BS.ByteString) | ||
362 | -- ^ 32 character long MD5 sum of the file. Used by third-party | ||
363 | -- tools, not by bittorrent protocol itself. | ||
364 | |||
365 | , fiName :: !a | ||
366 | -- ^ One or more string elements that together represent the | ||
367 | -- path and filename. Each element in the list corresponds to | ||
368 | -- either a directory name or (in the case of the last element) | ||
369 | -- the filename. For example, the file: | ||
370 | -- | ||
371 | -- > "dir1/dir2/file.ext" | ||
372 | -- | ||
373 | -- would consist of three string elements: | ||
374 | -- | ||
375 | -- > ["dir1", "dir2", "file.ext"] | ||
376 | -- | ||
377 | } deriving (Show, Read, Eq, Typeable | ||
378 | , Functor, Foldable | ||
379 | ) | ||
380 | |||
381 | #ifdef USE_lens | ||
382 | makeLensesFor | ||
383 | [ ("fiLength", "fileLength") | ||
384 | , ("fiMD5Sum", "fileMD5Sum") | ||
385 | , ("fiName" , "filePath" ) | ||
386 | ] | ||
387 | ''FileInfo | ||
388 | #endif | ||
389 | |||
390 | instance NFData a => NFData (FileInfo a) where | ||
391 | rnf FileInfo {..} = rnf fiName | ||
392 | {-# INLINE rnf #-} | ||
393 | |||
394 | #ifdef VERSION_bencoding | ||
395 | instance BEncode (FileInfo [BS.ByteString]) where | ||
396 | toBEncode FileInfo {..} = toDict $ | ||
397 | "length" .=! fiLength | ||
398 | .: "md5sum" .=? fiMD5Sum | ||
399 | .: "path" .=! fiName | ||
400 | .: endDict | ||
401 | {-# INLINE toBEncode #-} | ||
402 | |||
403 | fromBEncode = fromDict $ do | ||
404 | FileInfo <$>! "length" | ||
405 | <*>? "md5sum" | ||
406 | <*>! "path" | ||
407 | {-# INLINE fromBEncode #-} | ||
408 | |||
409 | type Put a = a -> BDict -> BDict | ||
410 | #endif | ||
411 | |||
412 | #ifdef VERSION_bencoding | ||
413 | putFileInfoSingle :: Data.Torrent.Put (FileInfo BS.ByteString) | ||
414 | putFileInfoSingle FileInfo {..} cont = | ||
415 | "length" .=! fiLength | ||
416 | .: "md5sum" .=? fiMD5Sum | ||
417 | .: "name" .=! fiName | ||
418 | .: cont | ||
419 | |||
420 | getFileInfoSingle :: BE.Get (FileInfo BS.ByteString) | ||
421 | getFileInfoSingle = do | ||
422 | FileInfo <$>! "length" | ||
423 | <*>? "md5sum" | ||
424 | <*>! "name" | ||
425 | |||
426 | instance BEncode (FileInfo BS.ByteString) where | ||
427 | toBEncode = toDict . (`putFileInfoSingle` endDict) | ||
428 | {-# INLINE toBEncode #-} | ||
429 | |||
430 | fromBEncode = fromDict getFileInfoSingle | ||
431 | {-# INLINE fromBEncode #-} | ||
432 | #endif | ||
433 | |||
434 | instance Pretty (FileInfo BS.ByteString) where | ||
435 | pPrint FileInfo {..} = | ||
436 | "Path: " <> text (T.unpack (T.decodeUtf8 fiName)) | ||
437 | $$ "Size: " <> text (show fiLength) | ||
438 | $$ maybe PP.empty ppMD5 fiMD5Sum | ||
439 | where | ||
440 | ppMD5 md5 = "MD5 : " <> text (show (Base16.encode md5)) | ||
441 | |||
442 | -- | Join file path. | ||
443 | joinFilePath :: FileInfo [BS.ByteString] -> FileInfo BS.ByteString | ||
444 | joinFilePath = fmap (BS.intercalate "/") | ||
445 | |||
446 | {----------------------------------------------------------------------- | ||
447 | -- Layout info | ||
448 | -----------------------------------------------------------------------} | ||
449 | |||
450 | -- | Original (found in torrent file) layout info is either: | ||
451 | -- | ||
452 | -- * Single file with its /name/. | ||
453 | -- | ||
454 | -- * Multiple files with its relative file /paths/. | ||
455 | -- | ||
456 | data LayoutInfo | ||
457 | = SingleFile | ||
458 | { -- | Single file info. | ||
459 | liFile :: !(FileInfo BS.ByteString) | ||
460 | } | ||
461 | | MultiFile | ||
462 | { -- | List of the all files that torrent contains. | ||
463 | liFiles :: ![FileInfo [BS.ByteString]] | ||
464 | |||
465 | -- | The /suggested/ name of the root directory in which to | ||
466 | -- store all the files. | ||
467 | , liDirName :: !BS.ByteString | ||
468 | } deriving (Show, Read, Eq, Typeable) | ||
469 | |||
470 | #ifdef USE_lens | ||
471 | makeLensesFor | ||
472 | [ ("liFile" , "singleFile" ) | ||
473 | , ("liFiles" , "multiFile" ) | ||
474 | , ("liDirName", "rootDirName") | ||
475 | ] | ||
476 | ''LayoutInfo | ||
477 | #endif | ||
478 | |||
479 | instance NFData LayoutInfo where | ||
480 | rnf SingleFile {..} = () | ||
481 | rnf MultiFile {..} = rnf liFiles | ||
482 | |||
483 | -- | Empty multifile layout. | ||
484 | instance Default LayoutInfo where | ||
485 | def = MultiFile [] "" | ||
486 | |||
487 | #ifdef VERSION_bencoding | ||
488 | getLayoutInfo :: BE.Get LayoutInfo | ||
489 | getLayoutInfo = single <|> multi | ||
490 | where | ||
491 | single = SingleFile <$> getFileInfoSingle | ||
492 | multi = MultiFile <$>! "files" <*>! "name" | ||
493 | |||
494 | putLayoutInfo :: Data.Torrent.Put LayoutInfo | ||
495 | putLayoutInfo SingleFile {..} = putFileInfoSingle liFile | ||
496 | putLayoutInfo MultiFile {..} = \ cont -> | ||
497 | "files" .=! liFiles | ||
498 | .: "name" .=! liDirName | ||
499 | .: cont | ||
500 | |||
501 | instance BEncode LayoutInfo where | ||
502 | toBEncode = toDict . (`putLayoutInfo` endDict) | ||
503 | fromBEncode = fromDict getLayoutInfo | ||
504 | #endif | ||
505 | |||
506 | instance Pretty LayoutInfo where | ||
507 | pPrint SingleFile {..} = pPrint liFile | ||
508 | pPrint MultiFile {..} = vcat $ L.map (pPrint . joinFilePath) liFiles | ||
509 | |||
510 | -- | Test if this is single file torrent. | ||
511 | isSingleFile :: LayoutInfo -> Bool | ||
512 | isSingleFile SingleFile {} = True | ||
513 | isSingleFile _ = False | ||
514 | {-# INLINE isSingleFile #-} | ||
515 | |||
516 | -- | Test if this is multifile torrent. | ||
517 | isMultiFile :: LayoutInfo -> Bool | ||
518 | isMultiFile MultiFile {} = True | ||
519 | isMultiFile _ = False | ||
520 | {-# INLINE isMultiFile #-} | ||
521 | |||
522 | -- | Get name of the torrent based on the root path piece. | ||
523 | suggestedName :: LayoutInfo -> BS.ByteString | ||
524 | suggestedName (SingleFile FileInfo {..}) = fiName | ||
525 | suggestedName MultiFile {..} = liDirName | ||
526 | {-# INLINE suggestedName #-} | ||
527 | |||
528 | -- | Find sum of sizes of the all torrent files. | ||
529 | contentLength :: LayoutInfo -> FileSize | ||
530 | contentLength SingleFile { liFile = FileInfo {..} } = fiLength | ||
531 | contentLength MultiFile { liFiles = tfs } = L.sum (L.map fiLength tfs) | ||
532 | |||
533 | -- | Get number of all files in torrent. | ||
534 | fileCount :: LayoutInfo -> Int | ||
535 | fileCount SingleFile {..} = 1 | ||
536 | fileCount MultiFile {..} = L.length liFiles | ||
537 | |||
538 | -- | Find number of blocks of the specified size. If torrent size is | ||
539 | -- not a multiple of block size then the count is rounded up. | ||
540 | blockCount :: Int -> LayoutInfo -> Int | ||
541 | blockCount blkSize ci = contentLength ci `sizeInBase` blkSize | ||
542 | |||
543 | ------------------------------------------------------------------------ | ||
544 | |||
545 | -- | File layout specifies the order and the size of each file in the | ||
546 | -- storage. Note that order of files is highly important since we | ||
547 | -- coalesce all the files in the given order to get the linear block | ||
548 | -- address space. | ||
549 | -- | ||
550 | type FileLayout a = [(FilePath, a)] | ||
551 | |||
552 | -- | Extract files layout from torrent info with the given root path. | ||
553 | flatLayout | ||
554 | :: FilePath -- ^ Root path for the all torrent files. | ||
555 | -> LayoutInfo -- ^ Torrent content information. | ||
556 | -> FileLayout FileSize -- ^ The all file paths prefixed with the given root. | ||
557 | flatLayout prefixPath SingleFile { liFile = FileInfo {..} } | ||
558 | = [(prefixPath </> BC.unpack fiName, fiLength)] | ||
559 | flatLayout prefixPath MultiFile {..} = L.map mkPath liFiles | ||
560 | where -- TODO use utf8 encoding in name | ||
561 | mkPath FileInfo {..} = (_path, fiLength) | ||
562 | where | ||
563 | _path = prefixPath </> BC.unpack liDirName | ||
564 | </> joinPath (L.map BC.unpack fiName) | ||
565 | |||
566 | -- | Calculate offset of each file based on its length, incrementally. | ||
567 | accumPositions :: FileLayout FileSize -> FileLayout (FileOffset, FileSize) | ||
568 | accumPositions = go 0 | ||
569 | where | ||
570 | go !_ [] = [] | ||
571 | go !offset ((n, s) : xs) = (n, (offset, s)) : go (offset + s) xs | ||
572 | |||
573 | -- | Gives global offset of a content file for a given full path. | ||
574 | fileOffset :: FilePath -> FileLayout FileOffset -> Maybe FileOffset | ||
575 | fileOffset = L.lookup | ||
576 | {-# INLINE fileOffset #-} | ||
577 | |||
578 | ------------------------------------------------------------------------ | ||
579 | |||
580 | -- | Divide and round up. | ||
581 | sizeInBase :: Integral a => a -> Int -> Int | ||
582 | sizeInBase n b = fromIntegral (n `div` fromIntegral b) + align | ||
583 | where | ||
584 | align = if n `mod` fromIntegral b == 0 then 0 else 1 | ||
585 | {-# SPECIALIZE sizeInBase :: Int -> Int -> Int #-} | ||
586 | {-# SPECIALIZE sizeInBase :: Integer -> Int -> Int #-} | ||
587 | |||
588 | {----------------------------------------------------------------------- | ||
589 | -- Piece attributes | ||
590 | -----------------------------------------------------------------------} | ||
591 | |||
592 | -- | Zero-based index of piece in torrent content. | ||
593 | type PieceIx = Int | ||
594 | |||
595 | -- | Size of piece in bytes. Should be a power of 2. | ||
596 | -- | ||
597 | -- NOTE: Have max and min size constrained to wide used | ||
598 | -- semi-standard values. This bounds should be used to make decision | ||
599 | -- about piece size for new torrents. | ||
600 | -- | ||
601 | type PieceSize = Int | ||
602 | |||
603 | -- | Number of pieces in torrent or a part of torrent. | ||
604 | type PieceCount = Int | ||
605 | |||
606 | defaultBlockSize :: Int | ||
607 | defaultBlockSize = 16 * 1024 | ||
608 | |||
609 | -- | Optimal number of pieces in torrent. | ||
610 | optimalPieceCount :: PieceCount | ||
611 | optimalPieceCount = 1000 | ||
612 | {-# INLINE optimalPieceCount #-} | ||
613 | |||
614 | -- | Piece size should not be less than this value. | ||
615 | minPieceSize :: Int | ||
616 | minPieceSize = defaultBlockSize * 4 | ||
617 | {-# INLINE minPieceSize #-} | ||
618 | |||
619 | -- | To prevent transfer degradation piece size should not exceed this | ||
620 | -- value. | ||
621 | maxPieceSize :: Int | ||
622 | maxPieceSize = 4 * 1024 * 1024 | ||
623 | {-# INLINE maxPieceSize #-} | ||
624 | |||
625 | toPow2 :: Int -> Int | ||
626 | #ifdef VERSION_bits_extras | ||
627 | toPow2 x = bit $ fromIntegral (leadingZeros (0 :: Int) - leadingZeros x) | ||
628 | #else | ||
629 | toPow2 x = bit $ fromIntegral (countLeadingZeros (0 :: Int) - countLeadingZeros x) | ||
630 | #endif | ||
631 | |||
632 | -- | Find the optimal piece size for a given torrent size. | ||
633 | defaultPieceSize :: Int64 -> Int | ||
634 | defaultPieceSize x = max minPieceSize $ min maxPieceSize $ toPow2 pc | ||
635 | where | ||
636 | pc = fromIntegral (x `div` fromIntegral optimalPieceCount) | ||
637 | |||
638 | {----------------------------------------------------------------------- | ||
639 | -- Piece data | ||
640 | -----------------------------------------------------------------------} | ||
641 | |||
642 | type PieceHash = BS.ByteString | ||
643 | |||
644 | hashsize :: Int | ||
645 | hashsize = 20 | ||
646 | {-# INLINE hashsize #-} | ||
647 | |||
648 | -- TODO check if pieceLength is power of 2 | ||
649 | -- | Piece payload should be strict or lazy bytestring. | ||
650 | data Piece a = Piece | ||
651 | { -- | Zero-based piece index in torrent. | ||
652 | pieceIndex :: {-# UNPACK #-} !PieceIx | ||
653 | |||
654 | -- | Payload. | ||
655 | , pieceData :: !a | ||
656 | } deriving (Show, Read, Eq, Functor, Typeable) | ||
657 | |||
658 | instance NFData a => NFData (Piece a) where | ||
659 | rnf (Piece a b) = rnf a `seq` rnf b | ||
660 | |||
661 | -- | Payload bytes are omitted. | ||
662 | instance Pretty (Piece a) where | ||
663 | pPrint Piece {..} = "Piece" <+> braces ("index" <+> "=" <+> int pieceIndex) | ||
664 | |||
665 | -- | Get size of piece in bytes. | ||
666 | pieceSize :: Piece BL.ByteString -> PieceSize | ||
667 | pieceSize Piece {..} = fromIntegral (BL.length pieceData) | ||
668 | |||
669 | -- | Get piece hash. | ||
670 | hashPiece :: Piece BL.ByteString -> PieceHash | ||
671 | hashPiece Piece {..} = Bytes.convert (hashlazy pieceData :: Digest SHA1) | ||
672 | |||
673 | {----------------------------------------------------------------------- | ||
674 | -- Piece control | ||
675 | -----------------------------------------------------------------------} | ||
676 | |||
677 | -- | A flat array of SHA1 hash for each piece. | ||
678 | newtype HashList = HashList { unHashList :: BS.ByteString } | ||
679 | deriving ( Show, Read, Eq, Typeable | ||
680 | #ifdef VERSION_bencoding | ||
681 | , BEncode | ||
682 | #endif | ||
683 | ) | ||
684 | |||
685 | -- | Empty hash list. | ||
686 | instance Default HashList where | ||
687 | def = HashList "" | ||
688 | |||
689 | -- | Part of torrent file used for torrent content validation. | ||
690 | data PieceInfo = PieceInfo | ||
691 | { piPieceLength :: {-# UNPACK #-} !PieceSize | ||
692 | -- ^ Number of bytes in each piece. | ||
693 | |||
694 | , piPieceHashes :: !HashList | ||
695 | -- ^ Concatenation of all 20-byte SHA1 hash values. | ||
696 | } deriving (Show, Read, Eq, Typeable) | ||
697 | |||
698 | #ifdef USE_lens | ||
699 | -- | Number of bytes in each piece. | ||
700 | makeLensesFor [("piPieceLength", "pieceLength")] ''PieceInfo | ||
701 | |||
702 | -- | Concatenation of all 20-byte SHA1 hash values. | ||
703 | makeLensesFor [("piPieceHashes", "pieceHashes")] ''PieceInfo | ||
704 | #endif | ||
705 | |||
706 | instance NFData PieceInfo where | ||
707 | rnf (PieceInfo a (HashList b)) = rnf a `seq` rnf b | ||
708 | |||
709 | instance Default PieceInfo where | ||
710 | def = PieceInfo 1 def | ||
711 | |||
712 | |||
713 | #ifdef VERSION_bencoding | ||
714 | putPieceInfo :: Data.Torrent.Put PieceInfo | ||
715 | putPieceInfo PieceInfo {..} cont = | ||
716 | "piece length" .=! piPieceLength | ||
717 | .: "pieces" .=! piPieceHashes | ||
718 | .: cont | ||
719 | |||
720 | getPieceInfo :: BE.Get PieceInfo | ||
721 | getPieceInfo = do | ||
722 | PieceInfo <$>! "piece length" | ||
723 | <*>! "pieces" | ||
724 | |||
725 | instance BEncode PieceInfo where | ||
726 | toBEncode = toDict . (`putPieceInfo` endDict) | ||
727 | fromBEncode = fromDict getPieceInfo | ||
728 | #endif | ||
729 | |||
730 | -- | Hashes are omitted. | ||
731 | instance Pretty PieceInfo where | ||
732 | pPrint PieceInfo {..} = "Piece size: " <> int piPieceLength | ||
733 | |||
734 | slice :: Int -> Int -> BS.ByteString -> BS.ByteString | ||
735 | slice start len = BS.take len . BS.drop start | ||
736 | {-# INLINE slice #-} | ||
737 | |||
738 | -- | Extract validation hash by specified piece index. | ||
739 | pieceHash :: PieceInfo -> PieceIx -> PieceHash | ||
740 | pieceHash PieceInfo {..} i = slice (hashsize * i) hashsize (unHashList piPieceHashes) | ||
741 | |||
742 | -- | Find count of pieces in the torrent. If torrent size is not a | ||
743 | -- multiple of piece size then the count is rounded up. | ||
744 | pieceCount :: PieceInfo -> PieceCount | ||
745 | pieceCount PieceInfo {..} = BS.length (unHashList piPieceHashes) `quot` hashsize | ||
746 | |||
747 | -- | Test if this is last piece in torrent content. | ||
748 | isLastPiece :: PieceInfo -> PieceIx -> Bool | ||
749 | isLastPiece ci i = pieceCount ci == succ i | ||
750 | |||
751 | -- | Validate piece with metainfo hash. | ||
752 | checkPieceLazy :: PieceInfo -> Piece BL.ByteString -> Bool | ||
753 | checkPieceLazy pinfo @ PieceInfo {..} Piece {..} | ||
754 | = (fromIntegral (BL.length pieceData) == piPieceLength | ||
755 | || isLastPiece pinfo pieceIndex) | ||
756 | && Bytes.convert (hashlazy pieceData :: Digest SHA1) == pieceHash pinfo pieceIndex | ||
757 | |||
758 | {----------------------------------------------------------------------- | ||
759 | -- Info dictionary | ||
760 | -----------------------------------------------------------------------} | ||
761 | |||
762 | {- note that info hash is actually reduntant field | ||
763 | but it's better to keep it here to avoid heavy recomputations | ||
764 | -} | ||
765 | |||
766 | -- | Info part of the .torrent file contain info about each content file. | ||
767 | data InfoDict = InfoDict | ||
768 | { idInfoHash :: !InfoHash | ||
769 | -- ^ SHA1 hash of the (other) 'DictInfo' fields. | ||
770 | |||
771 | , idLayoutInfo :: !LayoutInfo | ||
772 | -- ^ File layout (name, size, etc) information. | ||
773 | |||
774 | , idPieceInfo :: !PieceInfo | ||
775 | -- ^ Content validation information. | ||
776 | |||
777 | , idPrivate :: !Bool | ||
778 | -- ^ If set the client MUST publish its presence to get other | ||
779 | -- peers ONLY via the trackers explicity described in the | ||
780 | -- metainfo file. | ||
781 | -- | ||
782 | -- BEP 27: <http://www.bittorrent.org/beps/bep_0027.html> | ||
783 | } deriving (Show, Read, Eq, Typeable) | ||
784 | |||
785 | #ifdef VERISON_lens | ||
786 | makeLensesFor | ||
787 | [ ("idInfoHash" , "infohash" ) | ||
788 | , ("idLayoutInfo", "layoutInfo") | ||
789 | , ("idPieceInfo" , "pieceInfo" ) | ||
790 | , ("idPrivate" , "isPrivate" ) | ||
791 | ] | ||
792 | ''InfoDict | ||
793 | #endif | ||
794 | |||
795 | instance NFData InfoDict where | ||
796 | rnf InfoDict {..} = rnf idLayoutInfo | ||
797 | |||
798 | instance Hashable InfoDict where | ||
799 | hashWithSalt = Hashable.hashUsing idInfoHash | ||
800 | {-# INLINE hashWithSalt #-} | ||
801 | |||
802 | -- | Hash lazy bytestring using SHA1 algorithm. | ||
803 | hashLazyIH :: BL.ByteString -> InfoHash | ||
804 | hashLazyIH = either (const (error msg)) id . safeConvert . (Bytes.convert :: Digest SHA1 -> BS.ByteString) . hashlazy | ||
805 | where | ||
806 | msg = "Infohash.hash: impossible: SHA1 is always 20 bytes long" | ||
807 | |||
808 | #ifdef VERSION_bencoding | ||
809 | -- | Empty info dictionary with zero-length content. | ||
810 | instance Default InfoDict where | ||
811 | def = infoDictionary def def False | ||
812 | |||
813 | -- | Smart constructor: add a info hash to info dictionary. | ||
814 | infoDictionary :: LayoutInfo -> PieceInfo -> Bool -> InfoDict | ||
815 | infoDictionary li pinfo private = InfoDict ih li pinfo private | ||
816 | where | ||
817 | ih = hashLazyIH $ BE.encode $ InfoDict def li pinfo private | ||
818 | |||
819 | getPrivate :: BE.Get Bool | ||
820 | getPrivate = (Just True ==) <$>? "private" | ||
821 | |||
822 | putPrivate :: Bool -> BDict -> BDict | ||
823 | putPrivate False = id | ||
824 | putPrivate True = \ cont -> "private" .=! True .: cont | ||
825 | |||
826 | instance BEncode InfoDict where | ||
827 | toBEncode InfoDict {..} = toDict $ | ||
828 | putLayoutInfo idLayoutInfo $ | ||
829 | putPieceInfo idPieceInfo $ | ||
830 | putPrivate idPrivate $ | ||
831 | endDict | ||
832 | |||
833 | fromBEncode dict = (`fromDict` dict) $ do | ||
834 | InfoDict ih <$> getLayoutInfo | ||
835 | <*> getPieceInfo | ||
836 | <*> getPrivate | ||
837 | where | ||
838 | ih = hashLazyIH (BE.encode dict) | ||
839 | #endif | ||
840 | |||
841 | ppPrivacy :: Bool -> Doc | ||
842 | ppPrivacy privacy = "Privacy: " <> if privacy then "private" else "public" | ||
843 | |||
844 | --ppAdditionalInfo :: InfoDict -> Doc | ||
845 | --ppAdditionalInfo layout = PP.empty | ||
846 | |||
847 | instance Pretty InfoDict where | ||
848 | pPrint InfoDict {..} = | ||
849 | pPrint idLayoutInfo $$ | ||
850 | pPrint idPieceInfo $$ | ||
851 | ppPrivacy idPrivate | ||
852 | |||
853 | {----------------------------------------------------------------------- | ||
854 | -- Torrent info | ||
855 | -----------------------------------------------------------------------} | ||
856 | -- TODO add torrent file validation | ||
857 | |||
858 | -- | Metainfo about particular torrent. | ||
859 | data Torrent = Torrent | ||
860 | { tAnnounce :: !(Maybe URI) | ||
861 | -- ^ The URL of the tracker. | ||
862 | |||
863 | , tAnnounceList :: !(Maybe [[URI]]) | ||
864 | -- ^ Announce list add multiple tracker support. | ||
865 | -- | ||
866 | -- BEP 12: <http://www.bittorrent.org/beps/bep_0012.html> | ||
867 | |||
868 | , tComment :: !(Maybe Text) | ||
869 | -- ^ Free-form comments of the author. | ||
870 | |||
871 | , tCreatedBy :: !(Maybe Text) | ||
872 | -- ^ Name and version of the program used to create the .torrent. | ||
873 | |||
874 | , tCreationDate :: !(Maybe POSIXTime) | ||
875 | -- ^ Creation time of the torrent, in standard UNIX epoch. | ||
876 | |||
877 | , tEncoding :: !(Maybe Text) | ||
878 | -- ^ String encoding format used to generate the pieces part of | ||
879 | -- the info dictionary in the .torrent metafile. | ||
880 | |||
881 | , tInfoDict :: !InfoDict | ||
882 | -- ^ Info about each content file. | ||
883 | |||
884 | , tNodes :: !(Maybe [NodeAddr HostName]) | ||
885 | -- ^ This key should be set to the /K closest/ nodes in the | ||
886 | -- torrent generating client's routing table. Alternatively, the | ||
887 | -- key could be set to a known good 'Network.Address.Node' | ||
888 | -- such as one operated by the person generating the torrent. | ||
889 | -- | ||
890 | -- Please do not automatically add \"router.bittorrent.com\" to | ||
891 | -- this list because different bittorrent software may prefer to | ||
892 | -- use different bootstrap node. | ||
893 | |||
894 | , tPublisher :: !(Maybe URI) | ||
895 | -- ^ Containing the RSA public key of the publisher of the | ||
896 | -- torrent. Private counterpart of this key that has the | ||
897 | -- authority to allow new peers onto the swarm. | ||
898 | |||
899 | , tPublisherURL :: !(Maybe URI) | ||
900 | , tSignature :: !(Maybe BS.ByteString) | ||
901 | -- ^ The RSA signature of the info dictionary (specifically, the | ||
902 | -- encrypted SHA-1 hash of the info dictionary). | ||
903 | } deriving (Show, Eq, Typeable) | ||
904 | |||
905 | #ifdef USE_lens | ||
906 | makeLensesFor | ||
907 | [ ("tAnnounce" , "announce" ) | ||
908 | , ("tAnnounceList", "announceList") | ||
909 | , ("tComment" , "comment" ) | ||
910 | , ("tCreatedBy" , "createdBy" ) | ||
911 | , ("tCreationDate", "creationDate") | ||
912 | , ("tEncoding" , "encoding" ) | ||
913 | , ("tInfoDict" , "infoDict" ) | ||
914 | , ("tPublisher" , "publisher" ) | ||
915 | , ("tPublisherURL", "publisherURL") | ||
916 | , ("tSignature" , "signature" ) | ||
917 | ] | ||
918 | ''Torrent | ||
919 | #endif | ||
920 | |||
921 | instance NFData Torrent where | ||
922 | rnf Torrent {..} = rnf tInfoDict | ||
923 | |||
924 | #ifdef VERSION_bencoding | ||
925 | -- TODO move to bencoding | ||
926 | instance BEncode URI where | ||
927 | toBEncode uri = toBEncode (BC.pack (uriToString id uri "")) | ||
928 | {-# INLINE toBEncode #-} | ||
929 | |||
930 | fromBEncode (BString s) | Just url <- parseURI (BC.unpack s) = return url | ||
931 | fromBEncode b = decodingError $ "url <" ++ show b ++ ">" | ||
932 | {-# INLINE fromBEncode #-} | ||
933 | |||
934 | --pico2uni :: Pico -> Uni | ||
935 | --pico2uni = undefined | ||
936 | |||
937 | -- TODO move to bencoding | ||
938 | instance BEncode POSIXTime where | ||
939 | toBEncode pt = toBEncode (floor pt :: Integer) | ||
940 | fromBEncode (BInteger i) = return $ fromIntegral i | ||
941 | fromBEncode _ = decodingError $ "POSIXTime" | ||
942 | |||
943 | -- TODO to bencoding package | ||
944 | instance {-# OVERLAPPING #-} BEncode String where | ||
945 | toBEncode = toBEncode . T.pack | ||
946 | fromBEncode v = T.unpack <$> fromBEncode v | ||
947 | |||
948 | instance BEncode Torrent where | ||
949 | toBEncode Torrent {..} = toDict $ | ||
950 | "announce" .=? tAnnounce | ||
951 | .: "announce-list" .=? tAnnounceList | ||
952 | .: "comment" .=? tComment | ||
953 | .: "created by" .=? tCreatedBy | ||
954 | .: "creation date" .=? tCreationDate | ||
955 | .: "encoding" .=? tEncoding | ||
956 | .: "info" .=! tInfoDict | ||
957 | .: "nodes" .=? tNodes | ||
958 | .: "publisher" .=? tPublisher | ||
959 | .: "publisher-url" .=? tPublisherURL | ||
960 | .: "signature" .=? tSignature | ||
961 | .: endDict | ||
962 | |||
963 | fromBEncode = fromDict $ do | ||
964 | Torrent <$>? "announce" | ||
965 | <*>? "announce-list" | ||
966 | <*>? "comment" | ||
967 | <*>? "created by" | ||
968 | <*>? "creation date" | ||
969 | <*>? "encoding" | ||
970 | <*>! "info" | ||
971 | <*>? "nodes" | ||
972 | <*>? "publisher" | ||
973 | <*>? "publisher-url" | ||
974 | <*>? "signature" | ||
975 | #endif | ||
976 | |||
977 | (<:>) :: Doc -> Doc -> Doc | ||
978 | name <:> v = name <> ":" <+> v | ||
979 | |||
980 | (<:>?) :: Doc -> Maybe Doc -> Doc | ||
981 | _ <:>? Nothing = PP.empty | ||
982 | name <:>? (Just d) = name <:> d | ||
983 | |||
984 | instance Pretty Torrent where | ||
985 | pPrint Torrent {..} = | ||
986 | "InfoHash: " <> pPrint (idInfoHash tInfoDict) | ||
987 | $$ hang "General" 4 generalInfo | ||
988 | $$ hang "Tracker" 4 trackers | ||
989 | $$ pPrint tInfoDict | ||
990 | where | ||
991 | trackers = case tAnnounceList of | ||
992 | Nothing -> text (show tAnnounce) | ||
993 | Just xxs -> vcat $ L.map ppTier $ L.zip [1..] xxs | ||
994 | where | ||
995 | ppTier (n, xs) = "Tier #" <> int n <:> vcat (L.map (text . show) xs) | ||
996 | |||
997 | generalInfo = | ||
998 | "Comment" <:>? ((text . T.unpack) <$> tComment) $$ | ||
999 | "Created by" <:>? ((text . T.unpack) <$> tCreatedBy) $$ | ||
1000 | "Created on" <:>? ((text . show . posixSecondsToUTCTime) | ||
1001 | <$> tCreationDate) $$ | ||
1002 | "Encoding" <:>? ((text . T.unpack) <$> tEncoding) $$ | ||
1003 | "Publisher" <:>? ((text . show) <$> tPublisher) $$ | ||
1004 | "Publisher URL" <:>? ((text . show) <$> tPublisherURL) $$ | ||
1005 | "Signature" <:>? ((text . show) <$> tSignature) | ||
1006 | |||
1007 | #ifdef VERSION_bencoding | ||
1008 | -- | No files, no trackers, no nodes, etc... | ||
1009 | instance Default Torrent where | ||
1010 | def = nullTorrent def | ||
1011 | #endif | ||
1012 | |||
1013 | -- | A simple torrent contains only required fields. | ||
1014 | nullTorrent :: InfoDict -> Torrent | ||
1015 | nullTorrent info = Torrent | ||
1016 | Nothing Nothing Nothing Nothing Nothing Nothing | ||
1017 | info Nothing Nothing Nothing Nothing | ||
1018 | |||
1019 | -- | Mime type of torrent files. | ||
1020 | typeTorrent :: BS.ByteString | ||
1021 | typeTorrent = "application/x-bittorrent" | ||
1022 | |||
1023 | -- | Extension usually used for torrent files. | ||
1024 | torrentExt :: String | ||
1025 | torrentExt = "torrent" | ||
1026 | |||
1027 | -- | Test if this path has proper extension. | ||
1028 | isTorrentPath :: FilePath -> Bool | ||
1029 | isTorrentPath filepath = takeExtension filepath == extSeparator : torrentExt | ||
1030 | |||
1031 | #ifdef VERSION_bencoding | ||
1032 | -- | Read and decode a .torrent file. | ||
1033 | fromFile :: FilePath -> IO Torrent | ||
1034 | fromFile filepath = do | ||
1035 | contents <- BS.readFile filepath | ||
1036 | case BE.decode contents of | ||
1037 | Right !t -> return t | ||
1038 | Left msg -> throwIO $ userError $ msg ++ " while reading torrent file" | ||
1039 | |||
1040 | -- | Encode and write a .torrent file. | ||
1041 | toFile :: FilePath -> Torrent -> IO () | ||
1042 | toFile filepath = BL.writeFile filepath . BE.encode | ||
1043 | #endif | ||
1044 | |||
1045 | {----------------------------------------------------------------------- | ||
1046 | -- URN | ||
1047 | -----------------------------------------------------------------------} | ||
1048 | |||
1049 | -- | Namespace identifier determines the syntactic interpretation of | ||
1050 | -- namespace-specific string. | ||
1051 | type NamespaceId = [Text] | ||
1052 | |||
1053 | -- | BitTorrent Info Hash (hence the name) namespace | ||
1054 | -- identifier. Namespace-specific string /should/ be a base16\/base32 | ||
1055 | -- encoded SHA1 hash of the corresponding torrent /info/ dictionary. | ||
1056 | -- | ||
1057 | btih :: NamespaceId | ||
1058 | btih = ["btih"] | ||
1059 | |||
1060 | -- | URN is pesistent location-independent identifier for | ||
1061 | -- resources. In particular, URNs are used represent torrent names | ||
1062 | -- as a part of magnet link, see 'Data.Torrent.Magnet.Magnet' for | ||
1063 | -- more info. | ||
1064 | -- | ||
1065 | data URN = URN | ||
1066 | { urnNamespace :: NamespaceId -- ^ a namespace identifier; | ||
1067 | , urnString :: Text -- ^ a corresponding | ||
1068 | -- namespace-specific string. | ||
1069 | } deriving (Eq, Ord, Typeable) | ||
1070 | |||
1071 | ----------------------------------------------------------------------- | ||
1072 | |||
1073 | instance Convertible URN InfoHash where | ||
1074 | safeConvert u @ URN {..} | ||
1075 | | urnNamespace /= btih = convError "invalid namespace" u | ||
1076 | | otherwise = safeConvert urnString | ||
1077 | |||
1078 | -- | Make resource name for torrent with corresponding | ||
1079 | -- infohash. Infohash is base16 (hex) encoded. | ||
1080 | -- | ||
1081 | infohashURN :: InfoHash -> URN | ||
1082 | infohashURN = URN btih . longHex | ||
1083 | |||
1084 | -- | Meaningless placeholder value. | ||
1085 | instance Default URN where | ||
1086 | def = infohashURN def | ||
1087 | |||
1088 | ------------------------------------------------------------------------ | ||
1089 | |||
1090 | -- | Render URN to its text representation. | ||
1091 | renderURN :: URN -> Text | ||
1092 | renderURN URN {..} | ||
1093 | = T.intercalate ":" $ "urn" : urnNamespace ++ [urnString] | ||
1094 | |||
1095 | instance Pretty URN where | ||
1096 | pPrint = text . T.unpack . renderURN | ||
1097 | |||
1098 | instance Show URN where | ||
1099 | showsPrec n = showsPrec n . T.unpack . renderURN | ||
1100 | |||
1101 | instance QueryValueLike URN where | ||
1102 | toQueryValue = toQueryValue . renderURN | ||
1103 | {-# INLINE toQueryValue #-} | ||
1104 | |||
1105 | ----------------------------------------------------------------------- | ||
1106 | |||
1107 | _unsnoc :: [a] -> Maybe ([a], a) | ||
1108 | _unsnoc [] = Nothing | ||
1109 | _unsnoc xs = Just (L.init xs, L.last xs) | ||
1110 | |||
1111 | instance Convertible Text URN where | ||
1112 | safeConvert t = case T.split (== ':') t of | ||
1113 | uriScheme : body | ||
1114 | | T.toLower uriScheme == "urn" -> | ||
1115 | case _unsnoc body of | ||
1116 | Just (namespace, val) -> pure URN | ||
1117 | { urnNamespace = namespace | ||
1118 | , urnString = val | ||
1119 | } | ||
1120 | Nothing -> convError "missing URN string" body | ||
1121 | | otherwise -> convError "invalid URN scheme" uriScheme | ||
1122 | [] -> convError "missing URN scheme" t | ||
1123 | |||
1124 | instance IsString URN where | ||
1125 | fromString = either (error . prettyConvertError) id | ||
1126 | . safeConvert . T.pack | ||
1127 | |||
1128 | -- | Try to parse an URN from its text representation. | ||
1129 | -- | ||
1130 | -- Use 'safeConvert' for detailed error messages. | ||
1131 | -- | ||
1132 | parseURN :: Text -> Maybe URN | ||
1133 | parseURN = either (const Nothing) pure . safeConvert | ||
1134 | |||
1135 | {----------------------------------------------------------------------- | ||
1136 | -- Magnet | ||
1137 | -----------------------------------------------------------------------} | ||
1138 | -- $magnet-link | ||
1139 | -- | ||
1140 | -- Magnet URI scheme is an standard defining Magnet links. Magnet | ||
1141 | -- links are refer to resources by hash, in particular magnet links | ||
1142 | -- can refer to torrent using corresponding infohash. In this way, | ||
1143 | -- magnet links can be used instead of torrent files. | ||
1144 | -- | ||
1145 | -- This module provides bittorrent specific implementation of magnet | ||
1146 | -- links. | ||
1147 | -- | ||
1148 | -- For generic magnet uri scheme see: | ||
1149 | -- <http://magnet-uri.sourceforge.net/magnet-draft-overview.txt>, | ||
1150 | -- <http://www.iana.org/assignments/uri-schemes/prov/magnet> | ||
1151 | -- | ||
1152 | -- Bittorrent specific details: | ||
1153 | -- <http://www.bittorrent.org/beps/bep_0009.html> | ||
1154 | -- | ||
1155 | |||
1156 | -- TODO multiple exact topics | ||
1157 | -- TODO render/parse supplement for URI/query | ||
1158 | |||
1159 | -- | An URI used to identify torrent. | ||
1160 | data Magnet = Magnet | ||
1161 | { -- | Torrent infohash hash. Can be used in DHT queries if no | ||
1162 | -- 'tracker' provided. | ||
1163 | exactTopic :: !InfoHash -- TODO InfoHash -> URN? | ||
1164 | |||
1165 | -- | A filename for the file to download. Can be used to | ||
1166 | -- display name while waiting for metadata. | ||
1167 | , displayName :: Maybe Text | ||
1168 | |||
1169 | -- | Size of the resource in bytes. | ||
1170 | , exactLength :: Maybe Integer | ||
1171 | |||
1172 | -- | URI pointing to manifest, e.g. a list of further items. | ||
1173 | , manifest :: Maybe Text | ||
1174 | |||
1175 | -- | Search string. | ||
1176 | , keywordTopic :: Maybe Text | ||
1177 | |||
1178 | -- | A source to be queried after not being able to find and | ||
1179 | -- download the file in the bittorrent network in a defined | ||
1180 | -- amount of time. | ||
1181 | , acceptableSource :: Maybe URI | ||
1182 | |||
1183 | -- | Direct link to the resource. | ||
1184 | , exactSource :: Maybe URI | ||
1185 | |||
1186 | -- | URI to the tracker. | ||
1187 | , tracker :: Maybe URI | ||
1188 | |||
1189 | -- | Additional or experimental parameters. | ||
1190 | , supplement :: Map Text Text | ||
1191 | } deriving (Eq, Ord, Typeable) | ||
1192 | |||
1193 | instance QueryValueLike Integer where | ||
1194 | toQueryValue = toQueryValue . show | ||
1195 | |||
1196 | instance QueryValueLike URI where | ||
1197 | toQueryValue = toQueryValue . show | ||
1198 | |||
1199 | instance QueryLike Magnet where | ||
1200 | toQuery Magnet {..} = | ||
1201 | [ ("xt", toQueryValue $ infohashURN exactTopic) | ||
1202 | , ("dn", toQueryValue displayName) | ||
1203 | , ("xl", toQueryValue exactLength) | ||
1204 | , ("mt", toQueryValue manifest) | ||
1205 | , ("kt", toQueryValue keywordTopic) | ||
1206 | , ("as", toQueryValue acceptableSource) | ||
1207 | , ("xs", toQueryValue exactSource) | ||
1208 | , ("tr", toQueryValue tracker) | ||
1209 | ] | ||
1210 | |||
1211 | instance QueryValueLike Magnet where | ||
1212 | toQueryValue = toQueryValue . renderMagnet | ||
1213 | |||
1214 | instance Convertible QueryText Magnet where | ||
1215 | safeConvert xs = do | ||
1216 | urnStr <- getTextMsg "xt" "exact topic not defined" xs | ||
1217 | infoHash <- convertVia (error "safeConvert" :: URN) urnStr | ||
1218 | return Magnet | ||
1219 | { exactTopic = infoHash | ||
1220 | , displayName = getText "dn" xs | ||
1221 | , exactLength = getText "xl" xs >>= getInt | ||
1222 | , manifest = getText "mt" xs | ||
1223 | , keywordTopic = getText "kt" xs | ||
1224 | , acceptableSource = getText "as" xs >>= getURI | ||
1225 | , exactSource = getText "xs" xs >>= getURI | ||
1226 | , tracker = getText "tr" xs >>= getURI | ||
1227 | , supplement = M.empty | ||
1228 | } | ||
1229 | where | ||
1230 | getInt = either (const Nothing) (Just . fst) . signed decimal | ||
1231 | getURI = parseURI . T.unpack | ||
1232 | getText p = join . L.lookup p | ||
1233 | getTextMsg p msg ps = maybe (convError msg xs) pure $ getText p ps | ||
1234 | |||
1235 | magnetScheme :: URI | ||
1236 | magnetScheme = URI | ||
1237 | { uriScheme = "magnet:" | ||
1238 | , uriAuthority = Nothing | ||
1239 | , uriPath = "" | ||
1240 | , uriQuery = "" | ||
1241 | , uriFragment = "" | ||
1242 | } | ||
1243 | |||
1244 | isMagnetURI :: URI -> Bool | ||
1245 | isMagnetURI u = u { uriQuery = "" } == magnetScheme | ||
1246 | |||
1247 | -- | Can be used instead of 'parseMagnet'. | ||
1248 | instance Convertible URI Magnet where | ||
1249 | safeConvert u @ URI {..} | ||
1250 | | not (isMagnetURI u) = convError "this is not a magnet link" u | ||
1251 | | otherwise = safeConvert $ parseQueryText $ BC.pack uriQuery | ||
1252 | |||
1253 | -- | Can be used instead of 'renderMagnet'. | ||
1254 | instance Convertible Magnet URI where | ||
1255 | safeConvert m = pure $ magnetScheme | ||
1256 | { uriQuery = BC.unpack $ renderQuery True $ toQuery m } | ||
1257 | |||
1258 | instance Convertible String Magnet where | ||
1259 | safeConvert str | ||
1260 | | Just uri <- parseURI str = safeConvert uri | ||
1261 | | otherwise = convError "unable to parse uri" str | ||
1262 | |||
1263 | ------------------------------------------------------------------------ | ||
1264 | |||
1265 | -- | Meaningless placeholder value. | ||
1266 | instance Default Magnet where | ||
1267 | def = Magnet | ||
1268 | { exactTopic = def | ||
1269 | , displayName = Nothing | ||
1270 | , exactLength = Nothing | ||
1271 | , manifest = Nothing | ||
1272 | , keywordTopic = Nothing | ||
1273 | , acceptableSource = Nothing | ||
1274 | , exactSource = Nothing | ||
1275 | , tracker = Nothing | ||
1276 | , supplement = M.empty | ||
1277 | } | ||
1278 | |||
1279 | -- | Set 'exactTopic' ('xt' param) only, other params are empty. | ||
1280 | nullMagnet :: InfoHash -> Magnet | ||
1281 | nullMagnet u = Magnet | ||
1282 | { exactTopic = u | ||
1283 | , displayName = Nothing | ||
1284 | , exactLength = Nothing | ||
1285 | , manifest = Nothing | ||
1286 | , keywordTopic = Nothing | ||
1287 | , acceptableSource = Nothing | ||
1288 | , exactSource = Nothing | ||
1289 | , tracker = Nothing | ||
1290 | , supplement = M.empty | ||
1291 | } | ||
1292 | |||
1293 | -- | Like 'nullMagnet' but also include 'displayName' ('dn' param). | ||
1294 | simpleMagnet :: Torrent -> Magnet | ||
1295 | simpleMagnet Torrent {tInfoDict = InfoDict {..}} | ||
1296 | = (nullMagnet idInfoHash) | ||
1297 | { displayName = Just $ T.decodeUtf8 $ suggestedName idLayoutInfo | ||
1298 | } | ||
1299 | |||
1300 | -- | Like 'simpleMagnet' but also include 'exactLength' ('xl' param) and | ||
1301 | -- 'tracker' ('tr' param). | ||
1302 | -- | ||
1303 | detailedMagnet :: Torrent -> Magnet | ||
1304 | detailedMagnet t @ Torrent {tInfoDict = InfoDict {..}, tAnnounce} | ||
1305 | = (simpleMagnet t) | ||
1306 | { exactLength = Just $ fromIntegral $ contentLength idLayoutInfo | ||
1307 | , tracker = tAnnounce | ||
1308 | } | ||
1309 | |||
1310 | ----------------------------------------------------------------------- | ||
1311 | |||
1312 | parseMagnetStr :: String -> Maybe Magnet | ||
1313 | parseMagnetStr = either (const Nothing) Just . safeConvert | ||
1314 | |||
1315 | renderMagnetStr :: Magnet -> String | ||
1316 | renderMagnetStr = show . (convert :: Magnet -> URI) | ||
1317 | |||
1318 | instance Pretty Magnet where | ||
1319 | pPrint = PP.text . renderMagnetStr | ||
1320 | |||
1321 | instance Show Magnet where | ||
1322 | show = renderMagnetStr | ||
1323 | {-# INLINE show #-} | ||
1324 | |||
1325 | instance Read Magnet where | ||
1326 | readsPrec _ xs | ||
1327 | | Just m <- parseMagnetStr mstr = [(m, rest)] | ||
1328 | | otherwise = [] | ||
1329 | where | ||
1330 | (mstr, rest) = L.break (== ' ') xs | ||
1331 | |||
1332 | instance IsString Magnet where | ||
1333 | fromString str = fromMaybe (error msg) $ parseMagnetStr str | ||
1334 | where | ||
1335 | msg = "unable to parse magnet: " ++ str | ||
1336 | |||
1337 | -- | Try to parse magnet link from urlencoded string. Use | ||
1338 | -- 'safeConvert' to find out error location. | ||
1339 | -- | ||
1340 | parseMagnet :: Text -> Maybe Magnet | ||
1341 | parseMagnet = parseMagnetStr . T.unpack | ||
1342 | {-# INLINE parseMagnet #-} | ||
1343 | |||
1344 | -- | Render magnet link to urlencoded string | ||
1345 | renderMagnet :: Magnet -> Text | ||
1346 | renderMagnet = T.pack . renderMagnetStr | ||
1347 | {-# INLINE renderMagnet #-} | ||
diff --git a/src/Data/Tox/Message.hs b/src/Data/Tox/Message.hs deleted file mode 100644 index 9f1ce339..00000000 --- a/src/Data/Tox/Message.hs +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
1 | -- | This module assigns meaningful symbolic names to Tox message ids and | ||
2 | -- classifies messages as lossy or lossless. | ||
3 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
4 | {-# LANGUAGE PatternSynonyms #-} | ||
5 | {-# LANGUAGE ViewPatterns #-} | ||
6 | module Data.Tox.Message where | ||
7 | |||
8 | import Data.Word | ||
9 | |||
10 | -- | The one-byte type code prefix that classifies a 'CryptoMessage'. | ||
11 | newtype MessageID = MessageID Word8 deriving (Eq,Enum,Ord,Bounded) | ||
12 | pattern Padding = MessageID 0 -- ^ 0 padding (skipped until we hit a non zero (data id) byte) | ||
13 | pattern PacketRequest = MessageID 1 -- ^ 1 packet request packet (lossy packet) | ||
14 | pattern KillPacket = MessageID 2 -- ^ 2 connection kill packet (lossy packet) | ||
15 | pattern UnspecifiedPacket003 = MessageID 3 -- ^ 3+ unspecified | ||
16 | pattern PING = MessageID 16 -- ^ 16+ reserved for Messenger usage (lossless packets) | ||
17 | -- TODO: rename to ALIVE 16 | ||
18 | -- SHARE_RELAYS 17 | ||
19 | -- FRIEND_REQUESTS 18 | ||
20 | pattern ONLINE = MessageID 24 -- 1 byte | ||
21 | pattern OFFLINE = MessageID 25 -- 1 byte | ||
22 | -- LOSSLESS_RANGE_SIZE 32 | ||
23 | pattern NICKNAME = MessageID 48 -- up to 129 bytes | ||
24 | pattern STATUSMESSAGE = MessageID 49 -- up to 1008 bytes | ||
25 | pattern USERSTATUS = MessageID 50 -- 2 bytes | ||
26 | pattern TYPING = MessageID 51 -- 2 bytes | ||
27 | -- LOSSY_RANGE_SIZE 63 | ||
28 | pattern MESSAGE = MessageID 64 -- up to 1373 bytes | ||
29 | pattern ACTION = MessageID 65 -- up to 1373 bytes | ||
30 | pattern MSI = MessageID 69 | ||
31 | pattern FILE_SENDREQUEST = MessageID 80 -- 1+1+4+8+32+max255 = up to 301 | ||
32 | pattern FILE_CONTROL = MessageID 81 -- 8 bytes if seek, otherwise 4 | ||
33 | pattern FILE_DATA = MessageID 82 -- up to 1373 | ||
34 | pattern INVITE_GROUPCHAT = MessageID 95 | ||
35 | pattern INVITE_GROUPCHAT0 = MessageID 96 -- 0x60 | ||
36 | -- TODO: rename to INVITE_CONFERENCE 96 | ||
37 | pattern ONLINE_PACKET = MessageID 97 -- 0x61 | ||
38 | pattern DIRECT_GROUPCHAT = MessageID 98 -- 0x62 | ||
39 | -- TODO: rename to DIRECT_CONFERENCE 98 | ||
40 | pattern MESSAGE_GROUPCHAT = MessageID 99 -- 0x63 | ||
41 | -- TODO: rename to MESSAGE_CONFERENCE 99 | ||
42 | -- LOSSLESS_RANGE_START 160 | ||
43 | pattern MessengerLossy192 = MessageID 192 -- ^ 192+ reserved for Messenger usage (lossy packets) | ||
44 | pattern LOSSY_GROUPCHAT = MessageID 199 -- 0xC7 | ||
45 | pattern Messenger255 = MessageID 255 -- ^ 255 reserved for Messenger usage (lossless packet) | ||
46 | |||
47 | instance Show MessageID where | ||
48 | show Padding = "Padding" | ||
49 | show PacketRequest = "PacketRequest" | ||
50 | show KillPacket = "KillPacket" | ||
51 | show UnspecifiedPacket003 = "UnspecifiedPacket003" | ||
52 | show PING = "PING" | ||
53 | show ONLINE = "ONLINE" | ||
54 | show OFFLINE = "OFFLINE" | ||
55 | show NICKNAME = "NICKNAME" | ||
56 | show STATUSMESSAGE = "STATUSMESSAGE" | ||
57 | show USERSTATUS = "USERSTATUS" | ||
58 | show TYPING = "TYPING" | ||
59 | show MESSAGE = "MESSAGE" | ||
60 | show ACTION = "ACTION" | ||
61 | show MSI = "MSI" | ||
62 | show FILE_SENDREQUEST = "FILE_SENDREQUEST" | ||
63 | show FILE_CONTROL = "FILE_CONTROL" | ||
64 | show FILE_DATA = "FILE_DATA" | ||
65 | show INVITE_GROUPCHAT = "INVITE_GROUPCHAT" | ||
66 | show ONLINE_PACKET = "ONLINE_PACKET" | ||
67 | show DIRECT_GROUPCHAT = "DIRECT_GROUPCHAT" | ||
68 | show MESSAGE_GROUPCHAT = "MESSAGE_GROUPCHAT" | ||
69 | show MessengerLossy192 = "MessengerLossy192" | ||
70 | show LOSSY_GROUPCHAT = "LOSSY_GROUPCHAT" | ||
71 | show Messenger255 = "Messenger255" | ||
72 | show (MessageID n) = "MessageID " ++ show n | ||
73 | |||
74 | data LossyOrLossless = Lossless | Lossy | ||
75 | deriving (Eq,Ord,Enum,Show,Bounded) | ||
76 | |||
77 | -- | Classify a packet as lossy or lossless. | ||
78 | lossyness :: MessageID -> LossyOrLossless | ||
79 | lossyness (fromEnum -> x) | x < 3 = Lossy | ||
80 | lossyness (fromEnum -> x) | {-16 <= x,-} x < 192 = Lossless | ||
81 | lossyness (fromEnum -> x) | 192 <= x, x < 255 = Lossy | ||
82 | lossyness (fromEnum -> 255) = Lossless | ||
83 | |||
84 | |||
diff --git a/src/Data/Tox/Msg.hs b/src/Data/Tox/Msg.hs deleted file mode 100644 index 66ec6eb1..00000000 --- a/src/Data/Tox/Msg.hs +++ /dev/null | |||
@@ -1,311 +0,0 @@ | |||
1 | {-# LANGUAGE DataKinds #-} | ||
2 | {-# LANGUAGE DefaultSignatures #-} | ||
3 | {-# LANGUAGE FlexibleInstances #-} | ||
4 | {-# LANGUAGE GADTs #-} | ||
5 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
6 | {-# LANGUAGE KindSignatures #-} | ||
7 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
8 | {-# LANGUAGE PolyKinds #-} | ||
9 | {-# LANGUAGE StandaloneDeriving #-} | ||
10 | {-# LANGUAGE TypeFamilies #-} | ||
11 | module Data.Tox.Msg where | ||
12 | |||
13 | import Crypto.Error | ||
14 | import qualified Crypto.PubKey.Ed25519 as Ed25519 | ||
15 | import Data.ByteArray as BA | ||
16 | import Data.ByteString as B | ||
17 | import Data.Dependent.Sum | ||
18 | import Data.Functor.Contravariant | ||
19 | import Data.Functor.Identity | ||
20 | import Data.GADT.Compare | ||
21 | import Data.GADT.Show | ||
22 | import Data.Monoid | ||
23 | import Data.Serialize | ||
24 | import Data.Text as T | ||
25 | import Data.Text.Encoding as T | ||
26 | import Data.Typeable | ||
27 | import Data.Word | ||
28 | import GHC.TypeLits | ||
29 | |||
30 | import Crypto.Tox | ||
31 | import Data.PacketBuffer (compressSequenceNumbers, decompressSequenceNumbers) | ||
32 | import Network.Tox.NodeId | ||
33 | |||
34 | newtype Unknown = Unknown B.ByteString deriving (Eq,Show) | ||
35 | newtype Padded = Padded B.ByteString deriving (Eq,Show) | ||
36 | |||
37 | -- The 'UserStatus' equivalent in Presence is: | ||
38 | -- | ||
39 | -- data JabberShow = Offline | ||
40 | -- | ExtendedAway | ||
41 | -- | Away -- Tox equiv: Away (1) | ||
42 | -- | DoNotDisturb -- Tox equiv: Busy (2) | ||
43 | -- | Available -- Tox equiv: Online (0) | ||
44 | -- | Chatty | ||
45 | -- deriving (Show,Enum,Ord,Eq,Read) | ||
46 | -- | ||
47 | -- The Enum instance on 'UserStatus' is not arbitrary. It corresponds | ||
48 | -- to on-the-wire id numbers. | ||
49 | data UserStatus = Online | Away | Busy deriving (Show,Read,Eq,Ord,Enum) | ||
50 | |||
51 | instance Serialize UserStatus where | ||
52 | get = do | ||
53 | x <- get :: Get Word8 | ||
54 | return (toEnum8 x) | ||
55 | put x = put (fromEnum8 x) | ||
56 | |||
57 | |||
58 | newtype MissingPackets = MissingPackets [Word32] | ||
59 | deriving (Eq,Show) | ||
60 | |||
61 | data Msg (n :: Nat) t where | ||
62 | Padding :: Msg 0 Padded | ||
63 | PacketRequest :: Msg 1 MissingPackets | ||
64 | KillPacket :: Msg 2 () | ||
65 | ALIVE :: Msg 16 () | ||
66 | SHARE_RELAYS :: Msg 17 Unknown | ||
67 | FRIEND_REQUESTS :: Msg 18 Unknown | ||
68 | ONLINE :: Msg 24 () | ||
69 | OFFLINE :: Msg 25 () | ||
70 | NICKNAME :: Msg 48 Text | ||
71 | STATUSMESSAGE :: Msg 49 Text | ||
72 | USERSTATUS :: Msg 50 UserStatus | ||
73 | TYPING :: Msg 51 Bool | ||
74 | MESSAGE :: Msg 64 Text | ||
75 | ACTION :: Msg 65 Text | ||
76 | MSI :: Msg 69 Unknown | ||
77 | FILE_SENDREQUEST :: Msg 80 Unknown | ||
78 | FILE_CONTROL :: Msg 81 Unknown | ||
79 | FILE_DATA :: Msg 82 Unknown | ||
80 | INVITE_GROUPCHAT :: Msg 95 Invite | ||
81 | INVITE_CONFERENCE :: Msg 96 Unknown | ||
82 | ONLINE_PACKET :: Msg 97 Unknown | ||
83 | DIRECT_CONFERENCE :: Msg 98 Unknown | ||
84 | MESSAGE_CONFERENCE :: Msg 99 Unknown | ||
85 | LOSSY_CONFERENCE :: Msg 199 Unknown | ||
86 | |||
87 | deriving instance Show (Msg n a) | ||
88 | |||
89 | msgbyte :: KnownNat n => Msg n a -> Word8 | ||
90 | msgbyte m = fromIntegral (natVal $ proxy m) | ||
91 | where proxy :: Msg n a -> Proxy n | ||
92 | proxy _ = Proxy | ||
93 | |||
94 | data Pkt a where Pkt :: (KnownNat n, Packet a, KnownMsg n) => Msg n a -> Pkt a | ||
95 | |||
96 | deriving instance (Show (Pkt a)) | ||
97 | |||
98 | type CryptoMessage = DSum Pkt Identity | ||
99 | |||
100 | msgID (Pkt mid :=> Identity _) = M mid | ||
101 | |||
102 | -- TODO | ||
103 | instance GShow Pkt where gshowsPrec = showsPrec | ||
104 | instance ShowTag Pkt Identity where | ||
105 | showTaggedPrec (Pkt _) = showsPrec | ||
106 | |||
107 | instance GEq Pkt where geq (Pkt _) (Pkt _) = eqT | ||
108 | instance EqTag Pkt Identity where eqTagged (Pkt _) (Pkt _) = (==) | ||
109 | |||
110 | someMsgVal :: KnownMsg n => Msg n a -> SomeMsg | ||
111 | someMsgVal m = msgid (proxy m) | ||
112 | where proxy :: Msg n a -> Proxy n | ||
113 | proxy _ = Proxy | ||
114 | |||
115 | class KnownMsg (n::Nat) where msgid :: proxy n -> SomeMsg | ||
116 | |||
117 | instance KnownMsg 0 where msgid _ = M Padding | ||
118 | instance KnownMsg 1 where msgid _ = M PacketRequest | ||
119 | instance KnownMsg 2 where msgid _ = M KillPacket | ||
120 | instance KnownMsg 16 where msgid _ = M ALIVE | ||
121 | instance KnownMsg 17 where msgid _ = M SHARE_RELAYS | ||
122 | instance KnownMsg 18 where msgid _ = M FRIEND_REQUESTS | ||
123 | instance KnownMsg 24 where msgid _ = M ONLINE | ||
124 | instance KnownMsg 25 where msgid _ = M OFFLINE | ||
125 | instance KnownMsg 48 where msgid _ = M NICKNAME | ||
126 | instance KnownMsg 49 where msgid _ = M STATUSMESSAGE | ||
127 | instance KnownMsg 50 where msgid _ = M USERSTATUS | ||
128 | instance KnownMsg 51 where msgid _ = M TYPING | ||
129 | instance KnownMsg 64 where msgid _ = M MESSAGE | ||
130 | instance KnownMsg 65 where msgid _ = M ACTION | ||
131 | instance KnownMsg 69 where msgid _ = M MSI | ||
132 | instance KnownMsg 80 where msgid _ = M FILE_SENDREQUEST | ||
133 | instance KnownMsg 81 where msgid _ = M FILE_CONTROL | ||
134 | instance KnownMsg 82 where msgid _ = M FILE_DATA | ||
135 | instance KnownMsg 95 where msgid _ = M INVITE_GROUPCHAT | ||
136 | instance KnownMsg 96 where msgid _ = M INVITE_CONFERENCE | ||
137 | instance KnownMsg 97 where msgid _ = M ONLINE_PACKET | ||
138 | instance KnownMsg 98 where msgid _ = M DIRECT_CONFERENCE | ||
139 | instance KnownMsg 99 where msgid _ = M MESSAGE_CONFERENCE | ||
140 | |||
141 | msgTag :: Word8 -> Maybe SomeMsg | ||
142 | msgTag 0 = Just $ M Padding | ||
143 | msgTag 1 = Just $ M PacketRequest | ||
144 | msgTag 2 = Just $ M KillPacket | ||
145 | msgTag 16 = Just $ M ALIVE | ||
146 | msgTag 17 = Just $ M SHARE_RELAYS | ||
147 | msgTag 18 = Just $ M FRIEND_REQUESTS | ||
148 | msgTag 24 = Just $ M ONLINE | ||
149 | msgTag 25 = Just $ M OFFLINE | ||
150 | msgTag 48 = Just $ M NICKNAME | ||
151 | msgTag 49 = Just $ M STATUSMESSAGE | ||
152 | msgTag 50 = Just $ M USERSTATUS | ||
153 | msgTag 51 = Just $ M TYPING | ||
154 | msgTag 64 = Just $ M MESSAGE | ||
155 | msgTag 65 = Just $ M ACTION | ||
156 | msgTag 69 = Just $ M MSI | ||
157 | msgTag 80 = Just $ M FILE_SENDREQUEST | ||
158 | msgTag 81 = Just $ M FILE_CONTROL | ||
159 | msgTag 82 = Just $ M FILE_DATA | ||
160 | msgTag 95 = Just $ M INVITE_GROUPCHAT | ||
161 | msgTag 96 = Just $ M INVITE_CONFERENCE | ||
162 | msgTag 97 = Just $ M ONLINE_PACKET | ||
163 | msgTag 98 = Just $ M DIRECT_CONFERENCE | ||
164 | msgTag 99 = Just $ M MESSAGE_CONFERENCE | ||
165 | msgTag _ = Nothing | ||
166 | |||
167 | |||
168 | class (Typeable t, Eq t, Show t, Sized t) => Packet t where | ||
169 | getPacket :: Word32 -> Get t | ||
170 | putPacket :: Word32 -> t -> Put | ||
171 | default getPacket :: Serialize t => Word32 -> Get t | ||
172 | getPacket _ = get | ||
173 | default putPacket :: Serialize t => Word32 -> t -> Put | ||
174 | putPacket _ t = put t | ||
175 | |||
176 | instance Sized UserStatus where size = ConstSize 1 | ||
177 | instance Packet UserStatus | ||
178 | |||
179 | instance Sized () where size = ConstSize 0 | ||
180 | instance Packet () where | ||
181 | getPacket _ = return () | ||
182 | putPacket _ _ = return () | ||
183 | |||
184 | instance Sized MissingPackets where size = VarSize $ \(MissingPackets ws) -> Prelude.length ws | ||
185 | instance Packet MissingPackets where | ||
186 | getPacket seqno = do | ||
187 | bs <- B.unpack <$> (remaining >>= getBytes) | ||
188 | return $ MissingPackets (decompressSequenceNumbers seqno bs) | ||
189 | putPacket seqno (MissingPackets ws) = do | ||
190 | mapM_ putWord8 $ compressSequenceNumbers seqno ws | ||
191 | |||
192 | instance Sized Unknown where size = VarSize $ \(Unknown bs) -> B.length bs | ||
193 | instance Packet Unknown where | ||
194 | getPacket _ = Unknown <$> (remaining >>= getBytes) | ||
195 | putPacket _ (Unknown bs) = putByteString bs | ||
196 | |||
197 | instance Sized Padded where size = VarSize $ \(Padded bs) -> B.length bs | ||
198 | instance Packet Padded where | ||
199 | getPacket _ = Padded <$> (remaining >>= getBytes) | ||
200 | putPacket _ (Padded bs) = putByteString bs | ||
201 | |||
202 | instance Sized Text where size = VarSize (B.length . T.encodeUtf8) | ||
203 | instance Packet Text where | ||
204 | getPacket _ = T.decodeUtf8 <$> (remaining >>= getBytes) | ||
205 | putPacket _ = putByteString . T.encodeUtf8 | ||
206 | |||
207 | instance Sized Bool where size = ConstSize 1 | ||
208 | instance Packet Bool where | ||
209 | getPacket _ = (/= 0) <$> getWord8 | ||
210 | putPacket _ False = putWord8 0 | ||
211 | putPacket _ True = putWord8 1 | ||
212 | |||
213 | data SomeMsg where | ||
214 | M :: (KnownMsg n, KnownNat n, Packet t) => Msg n t -> SomeMsg | ||
215 | |||
216 | instance Eq SomeMsg where | ||
217 | M m == M n = msgbyte m == msgbyte n | ||
218 | |||
219 | instance Show SomeMsg where | ||
220 | show (M m) = show m | ||
221 | |||
222 | toEnum8 :: (Enum a, Integral word8) => word8 -> a | ||
223 | toEnum8 = toEnum . fromIntegral | ||
224 | |||
225 | fromEnum8 :: Enum a => a -> Word8 | ||
226 | fromEnum8 = fromIntegral . fromEnum | ||
227 | |||
228 | data LossyOrLossless = Lossless | Lossy deriving (Eq,Ord,Enum,Show,Bounded) | ||
229 | |||
230 | someLossyness (M m) = lossyness m | ||
231 | |||
232 | lossyness :: KnownNat n => Msg n t -> LossyOrLossless | ||
233 | lossyness m = case msgbyte m of | ||
234 | x | x < 3 -> Lossy | ||
235 | | {-16 <= x,-} x < 192 -> Lossless | ||
236 | | 192 <= x, x < 255 -> Lossy | ||
237 | | otherwise -> Lossless | ||
238 | |||
239 | |||
240 | newtype ChatID = ChatID Ed25519.PublicKey | ||
241 | deriving Eq | ||
242 | |||
243 | instance Sized ChatID where size = ConstSize 32 | ||
244 | |||
245 | instance Serialize ChatID where | ||
246 | get = do | ||
247 | bs <- getBytes 32 | ||
248 | case Ed25519.publicKey bs of | ||
249 | CryptoPassed ed -> return $ ChatID ed | ||
250 | CryptoFailed e -> fail (show e) | ||
251 | put (ChatID ed) = putByteString $ BA.convert ed | ||
252 | |||
253 | instance Read ChatID where | ||
254 | readsPrec _ s | ||
255 | | Right bs <- parseToken32 s | ||
256 | , CryptoPassed ed <- Ed25519.publicKey bs | ||
257 | = [ (ChatID ed, Prelude.drop 43 s) ] | ||
258 | | otherwise = [] | ||
259 | |||
260 | instance Show ChatID where | ||
261 | show (ChatID ed) = showToken32 ed | ||
262 | |||
263 | data InviteType = GroupInvite { groupName :: Text } | ||
264 | | AcceptedInvite | ||
265 | | ConfirmedInvite { inviteNodes :: [NodeInfo] } | ||
266 | deriving (Eq,Show) | ||
267 | |||
268 | instance Sized InviteType where | ||
269 | size = VarSize $ \x -> case x of | ||
270 | GroupInvite name -> B.length (T.encodeUtf8 name) | ||
271 | AcceptedInvite -> 0 | ||
272 | ConfirmedInvite ns -> 0 -- TODO: size of node list. | ||
273 | |||
274 | data Invite = Invite | ||
275 | { inviteChatID :: ChatID | ||
276 | , inviteChatKey :: PublicKey | ||
277 | , invite :: InviteType | ||
278 | } | ||
279 | deriving (Eq,Show) | ||
280 | |||
281 | instance Sized Invite where | ||
282 | size = contramap inviteChatID size | ||
283 | <> contramap (key2id . inviteChatKey) size | ||
284 | <> contramap invite size | ||
285 | |||
286 | instance Serialize Invite where | ||
287 | get = do | ||
288 | group_packet_id <- getWord8 -- expecting 254=GP_FRIEND_INVITE | ||
289 | invite_type <- getWord8 | ||
290 | chatid <- get | ||
291 | chatkey <- getPublicKey | ||
292 | Invite chatid chatkey <$> case invite_type of | ||
293 | 0 -> do bs <- remaining >>= getBytes -- TODO: size can be determined from group shared state. | ||
294 | return $ GroupInvite $ decodeUtf8 bs | ||
295 | 1 -> return AcceptedInvite | ||
296 | 2 -> return $ ConfirmedInvite [] -- TODO: decode nodes | ||
297 | |||
298 | put x = do | ||
299 | putWord8 254 -- GP_FRIEND_INVITE | ||
300 | putWord8 $ case invite x of | ||
301 | GroupInvite {} -> 0 -- GROUP_INVITE | ||
302 | AcceptedInvite -> 1 -- GROUP_INVITE_ACCEPTED | ||
303 | ConfirmedInvite {} -> 2 -- GROUP_INVITE_CONFIRMATION | ||
304 | put $ inviteChatID x | ||
305 | putPublicKey $ inviteChatKey x | ||
306 | case invite x of | ||
307 | GroupInvite name -> putByteString $ encodeUtf8 name | ||
308 | AcceptedInvite -> return () | ||
309 | ConfirmedInvite ns -> return () -- TODO: encode nodes. | ||
310 | |||
311 | instance Packet Invite where | ||
diff --git a/src/Data/Tox/Onion.hs b/src/Data/Tox/Onion.hs deleted file mode 100644 index bd802c75..00000000 --- a/src/Data/Tox/Onion.hs +++ /dev/null | |||
@@ -1,1029 +0,0 @@ | |||
1 | {-# LANGUAGE CPP #-} | ||
2 | {-# LANGUAGE DataKinds #-} | ||
3 | {-# LANGUAGE DeriveDataTypeable #-} | ||
4 | {-# LANGUAGE FlexibleContexts #-} | ||
5 | {-# LANGUAGE FlexibleInstances #-} | ||
6 | {-# LANGUAGE GADTs #-} | ||
7 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
8 | {-# LANGUAGE KindSignatures #-} | ||
9 | {-# LANGUAGE LambdaCase #-} | ||
10 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
11 | {-# LANGUAGE PartialTypeSignatures #-} | ||
12 | {-# LANGUAGE RankNTypes #-} | ||
13 | {-# LANGUAGE ScopedTypeVariables #-} | ||
14 | {-# LANGUAGE StandaloneDeriving #-} | ||
15 | {-# LANGUAGE TupleSections #-} | ||
16 | {-# LANGUAGE TypeFamilies #-} | ||
17 | {-# LANGUAGE TypeOperators #-} | ||
18 | {-# LANGUAGE UndecidableInstances #-} | ||
19 | module Data.Tox.Onion where | ||
20 | |||
21 | |||
22 | import Network.Address (fromSockAddr,toSockAddr,setPort,either4or6,sockAddrPort) | ||
23 | import Network.QueryResponse | ||
24 | import Crypto.Tox hiding (encrypt,decrypt) | ||
25 | import Network.Tox.NodeId | ||
26 | import qualified Crypto.Tox as ToxCrypto | ||
27 | import Network.Tox.DHT.Transport (NodeInfo(..),NodeId(..),SendNodes(..),nodeInfo,DHTPublicKey(..),FriendRequest,asymNodeInfo) | ||
28 | |||
29 | import Control.Applicative | ||
30 | import Control.Arrow | ||
31 | import Control.Concurrent.STM | ||
32 | import Control.Monad | ||
33 | import qualified Data.ByteString as B | ||
34 | ;import Data.ByteString (ByteString) | ||
35 | import Data.Data | ||
36 | import Data.Function | ||
37 | import Data.Functor.Contravariant | ||
38 | import Data.Functor.Identity | ||
39 | #if MIN_VERSION_iproute(1,7,4) | ||
40 | import Data.IP hiding (fromSockAddr) | ||
41 | #else | ||
42 | import Data.IP | ||
43 | #endif | ||
44 | import Data.Maybe | ||
45 | import Data.Monoid | ||
46 | import Data.Serialize as S | ||
47 | import Data.Type.Equality | ||
48 | import Data.Typeable | ||
49 | import Data.Word | ||
50 | import GHC.Generics () | ||
51 | import GHC.TypeLits | ||
52 | import Network.Socket | ||
53 | import qualified Text.ParserCombinators.ReadP as RP | ||
54 | import Data.Hashable | ||
55 | import DPut | ||
56 | import DebugTag | ||
57 | import Data.Word64Map (fitsInInt) | ||
58 | import Data.Bits (shiftR,shiftL) | ||
59 | import qualified Rank2 | ||
60 | |||
61 | type HandleLo a = Maybe (Either String (ByteString, SockAddr)) -> IO a | ||
62 | |||
63 | type UDPTransport = Transport String SockAddr ByteString | ||
64 | |||
65 | |||
66 | getOnionAsymm :: Get (Asymm (Encrypted DataToRoute)) | ||
67 | getOnionAsymm = getAliasedAsymm | ||
68 | |||
69 | putOnionAsymm :: Serialize a => Word8 -> Put -> Asymm a -> Put | ||
70 | putOnionAsymm typ p a = put typ >> p >> putAliasedAsymm a | ||
71 | |||
72 | data OnionMessage (f :: * -> *) | ||
73 | = OnionAnnounce (Asymm (f (AnnounceRequest,Nonce8))) | ||
74 | | OnionAnnounceResponse Nonce8 Nonce24 (f AnnounceResponse) -- XXX: Why is Nonce8 transmitted in the clear? | ||
75 | | OnionToRoute PublicKey (Asymm (Encrypted DataToRoute)) -- destination key, aliased Asymm | ||
76 | | OnionToRouteResponse (Asymm (Encrypted DataToRoute)) | ||
77 | |||
78 | deriving instance ( Eq (f (AnnounceRequest, Nonce8)) | ||
79 | , Eq (f AnnounceResponse) | ||
80 | , Eq (f DataToRoute) | ||
81 | ) => Eq (OnionMessage f) | ||
82 | |||
83 | deriving instance ( Ord (f (AnnounceRequest, Nonce8)) | ||
84 | , Ord (f AnnounceResponse) | ||
85 | , Ord (f DataToRoute) | ||
86 | ) => Ord (OnionMessage f) | ||
87 | |||
88 | deriving instance ( Show (f (AnnounceRequest, Nonce8)) | ||
89 | , Show (f AnnounceResponse) | ||
90 | , Show (f DataToRoute) | ||
91 | ) => Show (OnionMessage f) | ||
92 | |||
93 | instance Data (OnionMessage Encrypted) where | ||
94 | gfoldl f z txt = z (either error id . S.decode) `f` S.encode txt | ||
95 | toConstr _ = error "OnionMessage.toConstr" | ||
96 | gunfold _ _ = error "OnionMessage.gunfold" | ||
97 | #if MIN_VERSION_base(4,2,0) | ||
98 | dataTypeOf _ = mkNoRepType "Network.Tox.Onion.Transport.OnionMessage" | ||
99 | #else | ||
100 | dataTypeOf _ = mkNorepType "Network.Tox.Onion.Transport.OnionMessage" | ||
101 | #endif | ||
102 | |||
103 | instance Rank2.Functor OnionMessage where | ||
104 | f <$> m = mapPayload (Proxy :: Proxy Serialize) f m | ||
105 | |||
106 | instance Payload Serialize OnionMessage where | ||
107 | mapPayload _ f (OnionAnnounce a) = OnionAnnounce (fmap f a) | ||
108 | mapPayload _ f (OnionAnnounceResponse n8 n24 a) = OnionAnnounceResponse n8 n24 (f a) | ||
109 | mapPayload _ f (OnionToRoute k a) = OnionToRoute k a | ||
110 | mapPayload _ f (OnionToRouteResponse a) = OnionToRouteResponse a | ||
111 | |||
112 | |||
113 | msgNonce :: OnionMessage f -> Nonce24 | ||
114 | msgNonce (OnionAnnounce a) = asymmNonce a | ||
115 | msgNonce (OnionAnnounceResponse _ n24 _) = n24 | ||
116 | msgNonce (OnionToRoute _ a) = asymmNonce a | ||
117 | msgNonce (OnionToRouteResponse a) = asymmNonce a | ||
118 | |||
119 | data AliasSelector = SearchingAlias | AnnouncingAlias SecretKey PublicKey | ||
120 | deriving (Eq,Show) | ||
121 | |||
122 | data OnionDestination r | ||
123 | = OnionToOwner | ||
124 | { onionNodeInfo :: NodeInfo | ||
125 | , onionReturnPath :: ReturnPath N3 -- ^ Somebody else's path to us. | ||
126 | } | ||
127 | | OnionDestination | ||
128 | { onionAliasSelector' :: AliasSelector | ||
129 | , onionNodeInfo :: NodeInfo | ||
130 | , onionRouteSpec :: Maybe r -- ^ Our own onion-path. | ||
131 | } | ||
132 | deriving Show | ||
133 | |||
134 | onionAliasSelector :: OnionDestination r -> AliasSelector | ||
135 | onionAliasSelector (OnionToOwner {} ) = SearchingAlias | ||
136 | onionAliasSelector (OnionDestination{onionAliasSelector' = sel}) = sel | ||
137 | |||
138 | onionKey :: OnionDestination r -> PublicKey | ||
139 | onionKey od = id2key . nodeId $ onionNodeInfo od | ||
140 | |||
141 | instance Sized (OnionMessage Encrypted) where | ||
142 | size = VarSize $ \case | ||
143 | OnionAnnounce a -> case size of ConstSize n -> n + 1 | ||
144 | VarSize f -> f a + 1 | ||
145 | OnionAnnounceResponse n8 n24 x -> case size of ConstSize n -> n + 33 | ||
146 | VarSize f -> f x + 33 | ||
147 | OnionToRoute pubkey a -> case size of ConstSize n -> n + 33 | ||
148 | VarSize f -> f a + 33 | ||
149 | OnionToRouteResponse a -> case size of ConstSize n -> n + 1 | ||
150 | VarSize f -> f a + 1 | ||
151 | |||
152 | instance Serialize (OnionMessage Encrypted) where | ||
153 | get = do | ||
154 | typ <- get | ||
155 | case typ :: Word8 of | ||
156 | 0x83 -> OnionAnnounce <$> getAliasedAsymm | ||
157 | 0x85 -> OnionToRoute <$> getPublicKey <*> getAliasedAsymm | ||
158 | t -> fail ("Unknown onion payload: " ++ show t) | ||
159 | `fromMaybe` getOnionReply t | ||
160 | put (OnionAnnounce a) = putWord8 0x83 >> putAliasedAsymm a | ||
161 | put (OnionToRoute k a) = putWord8 0x85 >> putPublicKey k >> putAliasedAsymm a | ||
162 | put (OnionAnnounceResponse n8 n24 x) = putWord8 0x84 >> put n8 >> put n24 >> put x | ||
163 | put (OnionToRouteResponse a) = putWord8 0x86 >> putAliasedAsymm a | ||
164 | |||
165 | onionToOwner :: Asymm a -> ReturnPath N3 -> SockAddr -> Either String (OnionDestination r) | ||
166 | onionToOwner asymm ret3 saddr = do | ||
167 | ni <- nodeInfo (key2id $ senderKey asymm) saddr | ||
168 | return $ OnionToOwner ni ret3 | ||
169 | -- data CookieAddress = WithoutCookie NodeInfo | CookieAddress Cookie SockAddr | ||
170 | |||
171 | |||
172 | onion :: Sized msg => | ||
173 | ByteString | ||
174 | -> SockAddr | ||
175 | -> Get (Asymm (Encrypted msg) -> t) | ||
176 | -> Either String (t, OnionDestination r) | ||
177 | onion bs saddr getf = do (f,(asymm,ret3)) <- runGet ((,) <$> getf <*> getOnionRequest) bs | ||
178 | oaddr <- onionToOwner asymm ret3 saddr | ||
179 | return (f asymm, oaddr) | ||
180 | |||
181 | parseOnionAddr :: (SockAddr -> Nonce8 -> IO (Maybe (OnionDestination r))) | ||
182 | -> (ByteString, SockAddr) | ||
183 | -> IO (Either (OnionMessage Encrypted,OnionDestination r) | ||
184 | (ByteString,SockAddr)) | ||
185 | parseOnionAddr lookupSender (msg,saddr) | ||
186 | | Just (typ,bs) <- B.uncons msg | ||
187 | , let right = Right (msg,saddr) | ||
188 | query = return . either (const right) Left | ||
189 | = case typ of | ||
190 | 0x83 -> query $ onion bs saddr (pure OnionAnnounce) -- Announce Request | ||
191 | 0x85 -> query $ onion bs saddr (OnionToRoute <$> getPublicKey) -- Onion Data Request | ||
192 | _ -> case flip runGet bs <$> getOnionReply typ of | ||
193 | Just (Right msg@(OnionAnnounceResponse n8 _ _)) -> do | ||
194 | maddr <- lookupSender saddr n8 | ||
195 | maybe (return right) -- Response unsolicited or too late. | ||
196 | (return . Left . \od -> (msg,od)) | ||
197 | maddr | ||
198 | Just (Right msg@(OnionToRouteResponse asym)) -> do | ||
199 | let ni = asymNodeInfo saddr asym | ||
200 | return $ Left (msg, OnionDestination SearchingAlias ni Nothing) | ||
201 | _ -> return right | ||
202 | |||
203 | getOnionReply :: Word8 -> Maybe (Get (OnionMessage Encrypted)) | ||
204 | getOnionReply 0x84 = Just $ OnionAnnounceResponse <$> get <*> get <*> get | ||
205 | getOnionReply 0x86 = Just $ OnionToRouteResponse <$> getOnionAsymm | ||
206 | getOnionReply _ = Nothing | ||
207 | |||
208 | putOnionMsg :: OnionMessage Encrypted -> Put | ||
209 | putOnionMsg (OnionAnnounce a) = putOnionAsymm 0x83 (return ()) a | ||
210 | putOnionMsg (OnionToRoute pubkey a) = putOnionAsymm 0x85 (putPublicKey pubkey) a | ||
211 | putOnionMsg (OnionAnnounceResponse n8 n24 x) = put (0x84 :: Word8) >> put n8 >> put n24 >> put x | ||
212 | putOnionMsg (OnionToRouteResponse a) = putOnionAsymm 0x86 (return ()) a | ||
213 | |||
214 | newtype RouteId = RouteId Int | ||
215 | deriving Show | ||
216 | |||
217 | |||
218 | -- We used to derive the RouteId from the Nonce8 associated with the query. | ||
219 | -- This is problematic because a nonce generated by toxcore will not validate | ||
220 | -- if it is received via a different route than it was issued. This is | ||
221 | -- described by the Tox spec: | ||
222 | -- | ||
223 | -- Toxcore generates `ping_id`s by taking a 32 byte sha hash of the current | ||
224 | -- time, some secret bytes generated when the instance is created, the | ||
225 | -- current time divided by a 20 second timeout, the public key of the | ||
226 | -- requester and the source ip/port that the packet was received from. Since | ||
227 | -- the ip/port that the packet was received from is in the `ping_id`, the | ||
228 | -- announce packets being sent with a ping id must be sent using the same | ||
229 | -- path as the packet that we received the `ping_id` from or announcing will | ||
230 | -- fail. | ||
231 | -- | ||
232 | -- The original idea was: | ||
233 | -- | ||
234 | -- > routeId :: Nonce8 -> RouteId | ||
235 | -- > routeId (Nonce8 w8) = RouteId $ mod (fromIntegral w8) 12 | ||
236 | -- | ||
237 | -- Instead, we'll just hash the destination node id. | ||
238 | routeId :: NodeId -> RouteId | ||
239 | routeId nid = RouteId $ mod (hash nid) 12 | ||
240 | |||
241 | |||
242 | |||
243 | forwardOnions :: TransportCrypto -> UDPTransport -> (Int -> OnionMessage Encrypted -> IO ()) {- ^ TCP relay send -} -> UDPTransport | ||
244 | forwardOnions crypto udp sendTCP = udp { awaitMessage = forwardAwait crypto udp sendTCP } | ||
245 | |||
246 | forwardAwait :: TransportCrypto -> UDPTransport -> (Int -> OnionMessage Encrypted -> IO ()) {- ^ TCP relay send -} -> HandleLo a -> IO a | ||
247 | forwardAwait crypto udp sendTCP kont = do | ||
248 | fix $ \another -> do | ||
249 | awaitMessage udp $ \case | ||
250 | m@(Just (Right (bs,saddr))) -> case B.head bs of | ||
251 | 0x80 -> forward kont bs $ handleOnionRequest (Proxy :: Proxy N0) crypto (Addressed saddr) udp another | ||
252 | 0x81 -> forward kont bs $ handleOnionRequest (Proxy :: Proxy N1) crypto (Addressed saddr) udp another | ||
253 | 0x82 -> forward kont bs $ handleOnionRequest (Proxy :: Proxy N2) crypto (Addressed saddr) udp another | ||
254 | 0x8c -> forward kont bs $ handleOnionResponse (Proxy :: Proxy N3) crypto saddr udp sendTCP another | ||
255 | 0x8d -> forward kont bs $ handleOnionResponse (Proxy :: Proxy N2) crypto saddr udp sendTCP another | ||
256 | 0x8e -> forward kont bs $ handleOnionResponse (Proxy :: Proxy N1) crypto saddr udp sendTCP another | ||
257 | _ -> kont m | ||
258 | m -> kont m | ||
259 | |||
260 | forward :: forall c b b1. (Serialize b, Show b) => | ||
261 | (Maybe (Either String b1) -> c) -> ByteString -> (b -> c) -> c | ||
262 | forward kont bs f = either (kont . Just . Left) f $ decode $ B.tail bs | ||
263 | |||
264 | class SumToThree a b | ||
265 | |||
266 | instance SumToThree N0 N3 | ||
267 | instance SumToThree (S a) b => SumToThree a (S b) | ||
268 | |||
269 | class ( Serialize (ReturnPath n) | ||
270 | , Serialize (ReturnPath (S n)) | ||
271 | , Serialize (Forwarding (ThreeMinus (S n)) (OnionMessage Encrypted)) | ||
272 | , ThreeMinus n ~ S (ThreeMinus (S n)) | ||
273 | ) => LessThanThree n | ||
274 | |||
275 | instance LessThanThree N0 | ||
276 | instance LessThanThree N1 | ||
277 | instance LessThanThree N2 | ||
278 | |||
279 | type family ThreeMinus n where | ||
280 | ThreeMinus N3 = N0 | ||
281 | ThreeMinus N2 = N1 | ||
282 | ThreeMinus N1 = N2 | ||
283 | ThreeMinus N0 = N3 | ||
284 | |||
285 | -- n = 0, 1, 2 | ||
286 | data OnionRequest n = OnionRequest | ||
287 | { onionNonce :: Nonce24 | ||
288 | , onionForward :: Forwarding (ThreeMinus n) (OnionMessage Encrypted) | ||
289 | , pathFromOwner :: ReturnPath n | ||
290 | } | ||
291 | deriving (Eq,Ord) | ||
292 | |||
293 | |||
294 | {- | ||
295 | instance (Typeable n, Sized (ReturnPath n), Serialize (ReturnPath n) | ||
296 | , Serialize (Forwarding (ThreeMinus n) (OnionMessage Encrypted)) | ||
297 | ) => Data (OnionRequest n) where | ||
298 | gfoldl f z txt = z (either error id . S.decode) `f` S.encode txt | ||
299 | toConstr _ = error "OnionRequest.toConstr" | ||
300 | gunfold _ _ = error "OnionRequest.gunfold" | ||
301 | #if MIN_VERSION_base(4,2,0) | ||
302 | dataTypeOf _ = mkNoRepType "Network.Tox.Onion.Transport.OnionRequest" | ||
303 | #else | ||
304 | dataTypeOf _ = mkNorepType "Network.Tox.Onion.Transport.OnionRequest" | ||
305 | #endif | ||
306 | -} | ||
307 | |||
308 | |||
309 | instance (Typeable n, Serialize (ReturnPath n)) => Data (OnionResponse n) where | ||
310 | gfoldl f z txt = z (either error id . S.decode) `f` S.encode txt | ||
311 | toConstr _ = error "OnionResponse.toConstr" | ||
312 | gunfold _ _ = error "OnionResponse.gunfold" | ||
313 | #if MIN_VERSION_base(4,2,0) | ||
314 | dataTypeOf _ = mkNoRepType "Network.Tox.Onion.Transport.OnionResponse" | ||
315 | #else | ||
316 | dataTypeOf _ = mkNorepType "Network.Tox.Onion.Transport.OnionResponse" | ||
317 | #endif | ||
318 | |||
319 | deriving instance ( Show (Forwarding (ThreeMinus n) (OnionMessage Encrypted)) | ||
320 | , KnownNat (PeanoNat n) | ||
321 | ) => Show (OnionRequest n) | ||
322 | |||
323 | instance Sized (OnionRequest N0) where -- N1 and N2 are the same, N3 does not encode the nonce. | ||
324 | size = contramap onionNonce size | ||
325 | <> contramap onionForward size | ||
326 | <> contramap pathFromOwner size | ||
327 | |||
328 | instance ( Serialize (Forwarding (ThreeMinus n) (OnionMessage Encrypted)) | ||
329 | , Sized (ReturnPath n) | ||
330 | , Serialize (ReturnPath n) | ||
331 | , Typeable n | ||
332 | ) => Serialize (OnionRequest n) where | ||
333 | get = do | ||
334 | -- TODO share code with 'getOnionRequest' | ||
335 | n24 <- case eqT :: Maybe (n :~: N3) of | ||
336 | Just Refl -> return $ Nonce24 zeros24 | ||
337 | Nothing -> get | ||
338 | cnt <- remaining | ||
339 | let fwdsize = case size :: Size (ReturnPath n) of ConstSize n -> cnt - n | ||
340 | fwd <- isolate fwdsize get | ||
341 | rpath <- get | ||
342 | return $ OnionRequest n24 fwd rpath | ||
343 | put (OnionRequest n f p) = maybe (put n) (\Refl -> return ()) (eqT :: Maybe (n :~: N3)) >> put f >> put p | ||
344 | |||
345 | -- getRequest :: _ | ||
346 | -- getRequest = OnionRequest <$> get <*> get <*> get | ||
347 | |||
348 | -- n = 1, 2, 3 | ||
349 | -- Attributed (Encrypted ( | ||
350 | |||
351 | data OnionResponse n = OnionResponse | ||
352 | { pathToOwner :: ReturnPath n | ||
353 | , msgToOwner :: OnionMessage Encrypted | ||
354 | } | ||
355 | deriving (Eq,Ord) | ||
356 | |||
357 | deriving instance KnownNat (PeanoNat n) => Show (OnionResponse n) | ||
358 | |||
359 | instance ( Serialize (ReturnPath n) ) => Serialize (OnionResponse n) where | ||
360 | get = OnionResponse <$> get <*> (get >>= fromMaybe (fail "illegal onion forwarding") | ||
361 | . getOnionReply) | ||
362 | put (OnionResponse p m) = put p >> putOnionMsg m | ||
363 | |||
364 | instance (Sized (ReturnPath n)) => Sized (OnionResponse (S n)) where | ||
365 | size = contramap pathToOwner size <> contramap msgToOwner size | ||
366 | |||
367 | data Addressed a = Addressed { sockAddr :: SockAddr, unaddressed :: a } | ||
368 | | TCPIndex { tcpIndex :: Int, unaddressed :: a } | ||
369 | deriving (Eq,Ord,Show) | ||
370 | |||
371 | instance (Typeable a, Serialize a) => Data (Addressed a) where | ||
372 | gfoldl f z a = z (either error id . S.decode) `f` S.encode a | ||
373 | toConstr _ = error "Addressed.toConstr" | ||
374 | gunfold _ _ = error "Addressed.gunfold" | ||
375 | #if MIN_VERSION_base(4,2,0) | ||
376 | dataTypeOf _ = mkNoRepType "Network.Tox.Onion.Transport.Addressed" | ||
377 | #else | ||
378 | dataTypeOf _ = mkNorepType "Network.Tox.Onion.Transport.Addressed" | ||
379 | #endif | ||
380 | |||
381 | instance Sized a => Sized (Addressed a) where | ||
382 | size = case size :: Size a of | ||
383 | ConstSize n -> ConstSize $ 1{-family-} + 16{-ip-} + 2{-port-} + n | ||
384 | VarSize f -> VarSize $ \x -> 1{-family-} + 16{-ip-} + 2{-port-} + f (unaddressed x) | ||
385 | |||
386 | getForwardAddr :: S.Get SockAddr | ||
387 | getForwardAddr = do | ||
388 | addrfam <- S.get :: S.Get Word8 | ||
389 | ip <- getIP addrfam | ||
390 | case ip of IPv4 _ -> S.skip 12 -- compliant peers would zero-fill this. | ||
391 | IPv6 _ -> return () | ||
392 | port <- S.get :: S.Get PortNumber | ||
393 | return $ setPort port $ toSockAddr ip | ||
394 | |||
395 | |||
396 | putForwardAddr :: SockAddr -> S.Put | ||
397 | putForwardAddr saddr = fromMaybe (return $ error "unsupported SockAddr family") $ do | ||
398 | port <- sockAddrPort saddr | ||
399 | ip <- fromSockAddr $ either id id $ either4or6 saddr | ||
400 | return $ do | ||
401 | case ip of | ||
402 | IPv4 ip4 -> S.put (0x02 :: Word8) >> S.put ip4 >> S.putByteString (B.replicate 12 0) | ||
403 | IPv6 ip6 -> S.put (0x0a :: Word8) >> S.put ip6 | ||
404 | S.put port | ||
405 | |||
406 | addrToIndex :: SockAddr -> Int | ||
407 | addrToIndex (SockAddrInet6 _ _ (lo, hi, _, _) _) = | ||
408 | if fitsInInt (Proxy :: Proxy Word64) | ||
409 | then fromIntegral lo + (fromIntegral hi `shiftL` 32) | ||
410 | else fromIntegral lo | ||
411 | addrToIndex _ = 0 | ||
412 | |||
413 | indexToAddr :: Int -> SockAddr | ||
414 | indexToAddr x = SockAddrInet6 0 0 (fromIntegral x, fromIntegral (x `shiftR` 32),0,0) 0 | ||
415 | |||
416 | -- Note, toxcore would check an address family byte here to detect a TCP-bound | ||
417 | -- packet, but we instead use the IPv6 id and rely on the port number being | ||
418 | -- zero. Since it will be symmetrically encrypted for our eyes only, it's not | ||
419 | -- important to conform on this point. | ||
420 | instance Serialize a => Serialize (Addressed a) where | ||
421 | get = do saddr <- getForwardAddr | ||
422 | a <- get | ||
423 | case sockAddrPort saddr of | ||
424 | Just 0 -> return $ TCPIndex (addrToIndex saddr) a | ||
425 | _ -> return $ Addressed saddr a | ||
426 | put (Addressed addr x) = putForwardAddr addr >> put x | ||
427 | put (TCPIndex idx x) = putForwardAddr (indexToAddr idx) >> put x | ||
428 | |||
429 | data N0 | ||
430 | data S n | ||
431 | type N1 = S N0 | ||
432 | type N2 = S N1 | ||
433 | type N3 = S N2 | ||
434 | |||
435 | deriving instance Data N0 | ||
436 | deriving instance Data n => Data (S n) | ||
437 | |||
438 | class KnownPeanoNat n where | ||
439 | peanoVal :: p n -> Int | ||
440 | |||
441 | instance KnownPeanoNat N0 where | ||
442 | peanoVal _ = 0 | ||
443 | instance KnownPeanoNat n => KnownPeanoNat (S n) where | ||
444 | peanoVal _ = 1 + peanoVal (Proxy :: Proxy n) | ||
445 | |||
446 | type family PeanoNat p where | ||
447 | PeanoNat N0 = 0 | ||
448 | PeanoNat (S n) = 1 + PeanoNat n | ||
449 | |||
450 | data ReturnPath n where | ||
451 | NoReturnPath :: ReturnPath N0 | ||
452 | ReturnPath :: Nonce24 -> Encrypted (Addressed (ReturnPath n)) -> ReturnPath (S n) | ||
453 | |||
454 | deriving instance Eq (ReturnPath n) | ||
455 | deriving instance Ord (ReturnPath n) | ||
456 | |||
457 | -- Size: 59 = 1(family) + 16(ip) + 2(port) +16(mac) + 24(nonce) | ||
458 | instance Sized (ReturnPath N0) where size = ConstSize 0 | ||
459 | instance Sized (ReturnPath n) => Sized (ReturnPath (S n)) where | ||
460 | size = ConstSize 59 <> contramap (\x -> let _ = x :: ReturnPath (S n) | ||
461 | in error "non-constant ReturnPath size") | ||
462 | (size :: Size (ReturnPath n)) | ||
463 | |||
464 | {- | ||
465 | instance KnownNat (PeanoNat n) => Sized (ReturnPath n) where | ||
466 | size = ConstSize $ 59 * fromIntegral (natVal (Proxy :: Proxy (PeanoNat n))) | ||
467 | -} | ||
468 | |||
469 | instance Serialize (ReturnPath N0) where get = pure NoReturnPath | ||
470 | put NoReturnPath = pure () | ||
471 | |||
472 | instance Serialize (ReturnPath N1) where | ||
473 | get = ReturnPath <$> get <*> get | ||
474 | put (ReturnPath n24 p) = put n24 >> put p | ||
475 | |||
476 | instance (Sized (ReturnPath n), Serialize (ReturnPath n)) => Serialize (ReturnPath (S (S n))) where | ||
477 | get = ReturnPath <$> get <*> get | ||
478 | put (ReturnPath n24 p) = put n24 >> put p | ||
479 | |||
480 | |||
481 | {- | ||
482 | -- This doesn't work because it tried to infer it for (0 - 1) | ||
483 | instance (Serialize (Encrypted (Addressed (ReturnPath (n - 1))))) => Serialize (ReturnPath n) where | ||
484 | get = ReturnPath <$> get <*> get | ||
485 | put (ReturnPath n24 p) = put n24 >> put p | ||
486 | -} | ||
487 | |||
488 | instance KnownNat (PeanoNat n) => Show (ReturnPath n) where | ||
489 | show rpath = "ReturnPath" ++ show (natVal (Proxy :: Proxy (PeanoNat n))) | ||
490 | |||
491 | |||
492 | -- instance KnownNat n => Serialize (ReturnPath n) where | ||
493 | -- -- Size: 59 = 1(family) + 16(ip) + 2(port) +16(mac) + 24(nonce) | ||
494 | -- get = ReturnPath <$> getBytes ( 59 * (fromIntegral $ natVal $ Proxy @n) ) | ||
495 | -- put (ReturnPath bs) = putByteString bs | ||
496 | |||
497 | |||
498 | data Forwarding n msg where | ||
499 | NotForwarded :: msg -> Forwarding N0 msg | ||
500 | Forwarding :: PublicKey -> Encrypted (Addressed (Forwarding n msg)) -> Forwarding (S n) msg | ||
501 | |||
502 | deriving instance Eq msg => Eq (Forwarding n msg) | ||
503 | deriving instance Ord msg => Ord (Forwarding n msg) | ||
504 | |||
505 | instance Show msg => Show (Forwarding N0 msg) where | ||
506 | show (NotForwarded x) = "NotForwarded "++show x | ||
507 | |||
508 | instance ( KnownNat (PeanoNat (S n)) | ||
509 | , Show (Encrypted (Addressed (Forwarding n msg))) | ||
510 | ) => Show (Forwarding (S n) msg) where | ||
511 | show (Forwarding k a) = unwords [ "Forwarding" | ||
512 | , "("++show (natVal (Proxy :: Proxy (PeanoNat (S n))))++")" | ||
513 | , show (key2id k) | ||
514 | , show a | ||
515 | ] | ||
516 | |||
517 | instance Sized msg => Sized (Forwarding N0 msg) | ||
518 | where size = case size :: Size msg of | ||
519 | ConstSize n -> ConstSize n | ||
520 | VarSize f -> VarSize $ \(NotForwarded x) -> f x | ||
521 | |||
522 | instance Sized (Forwarding n msg) => Sized (Forwarding (S n) msg) | ||
523 | where size = ConstSize 32 | ||
524 | <> contramap (\(Forwarding _ e) -> e) | ||
525 | (size :: Size (Encrypted (Addressed (Forwarding n msg)))) | ||
526 | |||
527 | instance Serialize msg => Serialize (Forwarding N0 msg) where | ||
528 | get = NotForwarded <$> get | ||
529 | put (NotForwarded msg) = put msg | ||
530 | |||
531 | instance (Serialize (Encrypted (Addressed (Forwarding n msg)))) => Serialize (Forwarding (S n) msg) where | ||
532 | get = Forwarding <$> getPublicKey <*> get | ||
533 | put (Forwarding k x) = putPublicKey k >> put x | ||
534 | |||
535 | {- | ||
536 | rewrap :: (ThreeMinus n ~ S (ThreeMinus (S n)), | ||
537 | Serialize (ReturnPath n), | ||
538 | Serialize | ||
539 | (Forwarding (ThreeMinus (S n)) (OnionMessage Encrypted))) => | ||
540 | TransportCrypto | ||
541 | -> (forall x. x -> Addressed x) | ||
542 | -> OnionRequest n | ||
543 | -> IO (Either String (OnionRequest (S n), SockAddr)) | ||
544 | rewrap crypto saddr (OnionRequest nonce msg rpath) = do | ||
545 | (sym, snonce) <- atomically ( (,) <$> transportSymmetric crypto | ||
546 | <*> transportNewNonce crypto ) | ||
547 | peeled <- peelOnion crypto nonce msg | ||
548 | return $ peeled >>= \case | ||
549 | Addressed dst msg' | ||
550 | -> Right (OnionRequest nonce msg' $ wrapSymmetric sym snonce saddr rpath, dst) | ||
551 | _ -> Left "Onion forward to TCP client?" | ||
552 | -} | ||
553 | |||
554 | handleOnionRequest :: forall a proxy n. | ||
555 | ( LessThanThree n | ||
556 | , KnownPeanoNat n | ||
557 | , Sized (ReturnPath n) | ||
558 | , Typeable n | ||
559 | ) => proxy n -> TransportCrypto -> (forall x. x -> Addressed x) -> UDPTransport -> IO a -> OnionRequest n -> IO a | ||
560 | handleOnionRequest proxy crypto saddr udp kont (OnionRequest nonce msg rpath) = do | ||
561 | let n = peanoVal rpath | ||
562 | dput XOnion $ "handleOnionRequest " ++ show n | ||
563 | (sym, snonce) <- atomically ( (,) <$> transportSymmetric crypto | ||
564 | <*> transportNewNonce crypto ) | ||
565 | peeled <- peelOnion crypto nonce msg | ||
566 | let showDestination = case saddr () of | ||
567 | Addressed a _ -> either show show $ either4or6 a | ||
568 | TCPIndex i _ -> "TCP" ++ show [i] | ||
569 | |||
570 | case peeled of | ||
571 | Left e -> do | ||
572 | dput XOnion $ unwords [ "peelOnion:", show n, showDestination, e] | ||
573 | kont | ||
574 | Right (Addressed dst msg') -> do | ||
575 | dput XOnion $ unwords [ "peelOnion:", show n, showDestination, "-->", either show show (either4or6 dst), "SUCCESS"] | ||
576 | sendMessage udp dst (runPut $ putRequest $ OnionRequest nonce msg' $ wrapSymmetric sym snonce saddr rpath) | ||
577 | kont | ||
578 | Right (TCPIndex {}) -> do | ||
579 | dput XUnexpected "handleOnionRequest: Onion forward to TCP client?" | ||
580 | kont | ||
581 | |||
582 | wrapSymmetric :: Serialize (ReturnPath n) => | ||
583 | SymmetricKey -> Nonce24 -> (forall x. x -> Addressed x) -> ReturnPath n -> ReturnPath (S n) | ||
584 | wrapSymmetric sym n saddr rpath = ReturnPath n $ encryptSymmetric sym n (encodePlain $ saddr rpath) | ||
585 | |||
586 | peelSymmetric :: Serialize (Addressed (ReturnPath n)) | ||
587 | => SymmetricKey -> ReturnPath (S n) -> Either String (Addressed (ReturnPath n)) | ||
588 | peelSymmetric sym (ReturnPath nonce e) = decryptSymmetric sym nonce e >>= decodePlain | ||
589 | |||
590 | |||
591 | peelOnion :: Serialize (Addressed (Forwarding n t)) | ||
592 | => TransportCrypto | ||
593 | -> Nonce24 | ||
594 | -> Forwarding (S n) t | ||
595 | -> IO (Either String (Addressed (Forwarding n t))) | ||
596 | peelOnion crypto nonce (Forwarding k fwd) = do | ||
597 | fmap runIdentity . uncomposed <$> decryptMessage crypto (dhtKey crypto) nonce (Right $ Asymm k nonce fwd) | ||
598 | |||
599 | handleOnionResponse :: (KnownPeanoNat n, Sized (ReturnPath n), Serialize (ReturnPath n), Typeable n) => | ||
600 | proxy (S n) | ||
601 | -> TransportCrypto | ||
602 | -> SockAddr | ||
603 | -> UDPTransport | ||
604 | -> (Int -> OnionMessage Encrypted -> IO ()) -- ^ TCP-relay onion send. | ||
605 | -> IO a | ||
606 | -> OnionResponse (S n) | ||
607 | -> IO a | ||
608 | handleOnionResponse proxy crypto saddr udp sendTCP kont (OnionResponse path msg) = do | ||
609 | sym <- atomically $ transportSymmetric crypto | ||
610 | case peelSymmetric sym path of | ||
611 | Left e -> do | ||
612 | -- todo report encryption error | ||
613 | let n = peanoVal path | ||
614 | dput XMisc $ unwords [ "peelSymmetric:", show n, either show show (either4or6 saddr), e] | ||
615 | kont | ||
616 | Right (Addressed dst path') -> do | ||
617 | sendMessage udp dst (runPut $ putResponse $ OnionResponse path' msg) | ||
618 | kont | ||
619 | Right (TCPIndex dst path') -> do | ||
620 | case peanoVal path' of | ||
621 | 0 -> sendTCP dst msg | ||
622 | n -> dput XUnexpected $ "handleOnionResponse: TCP-bound OnionResponse" ++ show n ++ " not supported." | ||
623 | kont | ||
624 | |||
625 | |||
626 | data AnnounceRequest = AnnounceRequest | ||
627 | { announcePingId :: Nonce32 -- Ping ID | ||
628 | , announceSeeking :: NodeId -- Public key we are searching for | ||
629 | , announceKey :: NodeId -- Public key that we want those sending back data packets to use | ||
630 | } | ||
631 | deriving Show | ||
632 | |||
633 | instance Sized AnnounceRequest where size = ConstSize (32*3) | ||
634 | |||
635 | instance S.Serialize AnnounceRequest where | ||
636 | get = AnnounceRequest <$> S.get <*> S.get <*> S.get | ||
637 | put (AnnounceRequest p s k) = S.put (p,s,k) | ||
638 | |||
639 | getOnionRequest :: Sized msg => Get (Asymm (Encrypted msg), ReturnPath N3) | ||
640 | getOnionRequest = do | ||
641 | -- Assumes return path is constant size so that we can isolate | ||
642 | -- the variable-sized prefix. | ||
643 | cnt <- remaining | ||
644 | a <- isolate (case size :: Size (ReturnPath N3) of ConstSize n -> cnt - n) | ||
645 | getAliasedAsymm | ||
646 | path <- get | ||
647 | return (a,path) | ||
648 | |||
649 | putRequest :: ( KnownPeanoNat n | ||
650 | , Serialize (OnionRequest n) | ||
651 | , Typeable n | ||
652 | ) => OnionRequest n -> Put | ||
653 | putRequest req = do | ||
654 | let tag = 0x80 + fromIntegral (peanoVal req) | ||
655 | when (tag <= 0x82) (putWord8 tag) | ||
656 | put req | ||
657 | |||
658 | putResponse :: (KnownPeanoNat n, Serialize (OnionResponse n)) => OnionResponse n -> Put | ||
659 | putResponse resp = do | ||
660 | let tag = 0x8f - fromIntegral (peanoVal resp) | ||
661 | -- OnionResponse N0 is an alias for the OnionMessage Encrypted type which includes a tag | ||
662 | -- in it's Serialize instance. | ||
663 | when (tag /= 0x8f) (putWord8 tag) | ||
664 | put resp | ||
665 | |||
666 | |||
667 | data KeyRecord = NotStored Nonce32 | ||
668 | | SendBackKey PublicKey | ||
669 | | Acknowledged Nonce32 | ||
670 | deriving Show | ||
671 | |||
672 | instance Sized KeyRecord where size = ConstSize 33 | ||
673 | |||
674 | instance S.Serialize KeyRecord where | ||
675 | get = do | ||
676 | is_stored <- S.get :: S.Get Word8 | ||
677 | case is_stored of | ||
678 | 1 -> SendBackKey <$> getPublicKey | ||
679 | 2 -> Acknowledged <$> S.get | ||
680 | _ -> NotStored <$> S.get | ||
681 | put (NotStored n32) = S.put (0 :: Word8) >> S.put n32 | ||
682 | put (SendBackKey key) = S.put (1 :: Word8) >> putPublicKey key | ||
683 | put (Acknowledged n32) = S.put (2 :: Word8) >> S.put n32 | ||
684 | |||
685 | data AnnounceResponse = AnnounceResponse | ||
686 | { is_stored :: KeyRecord | ||
687 | , announceNodes :: SendNodes | ||
688 | } | ||
689 | deriving Show | ||
690 | |||
691 | instance Sized AnnounceResponse where | ||
692 | size = contramap is_stored size <> contramap announceNodes size | ||
693 | |||
694 | getNodeList :: S.Get [NodeInfo] | ||
695 | getNodeList = do | ||
696 | n <- S.get | ||
697 | (:) n <$> (getNodeList <|> pure []) | ||
698 | |||
699 | instance S.Serialize AnnounceResponse where | ||
700 | get = AnnounceResponse <$> S.get <*> (SendNodes <$> getNodeList) | ||
701 | put (AnnounceResponse st (SendNodes ns)) = S.put st >> mapM_ S.put ns | ||
702 | |||
703 | data DataToRoute = DataToRoute | ||
704 | { dataFromKey :: PublicKey -- Real public key of sender | ||
705 | , dataToRoute :: Encrypted OnionData -- (Word8,ByteString) -- DHTPK 0x9c | ||
706 | } | ||
707 | deriving Show | ||
708 | |||
709 | instance Sized DataToRoute where | ||
710 | size = ConstSize 32 <> contramap dataToRoute size | ||
711 | |||
712 | instance Serialize DataToRoute where | ||
713 | get = DataToRoute <$> getPublicKey <*> get | ||
714 | put (DataToRoute k dta) = putPublicKey k >> put dta | ||
715 | |||
716 | data OnionData | ||
717 | = -- | type 0x9c | ||
718 | -- | ||
719 | -- We send this packet every 30 seconds if there is more than one peer (in | ||
720 | -- the 8) that says they our friend is announced on them. This packet can | ||
721 | -- also be sent through the DHT module as a DHT request packet (see DHT) if | ||
722 | -- we know the DHT public key of the friend and are looking for them in the | ||
723 | -- DHT but have not connected to them yet. 30 second is a reasonable | ||
724 | -- timeout to not flood the network with too many packets while making sure | ||
725 | -- the other will eventually receive the packet. Since packets are sent | ||
726 | -- through every peer that knows the friend, resending it right away | ||
727 | -- without waiting has a high likelihood of failure as the chances of | ||
728 | -- packet loss happening to all (up to to 8) packets sent is low. | ||
729 | -- | ||
730 | -- If a friend is online and connected to us, the onion will stop all of | ||
731 | -- its actions for that friend. If the peer goes offline it will restart | ||
732 | -- searching for the friend as if toxcore was just started. | ||
733 | OnionDHTPublicKey DHTPublicKey | ||
734 | | -- | type 0x20 | ||
735 | -- | ||
736 | -- | ||
737 | OnionFriendRequest FriendRequest -- 0x20 | ||
738 | deriving (Eq,Show) | ||
739 | |||
740 | instance Sized OnionData where | ||
741 | size = VarSize $ \case | ||
742 | OnionDHTPublicKey dhtpk -> case size of | ||
743 | ConstSize n -> n -- Override because OnionData probably | ||
744 | -- should be treated as variable sized. | ||
745 | VarSize f -> f dhtpk | ||
746 | -- FIXME: inconsitantly, we have to add in the tag byte for this case. | ||
747 | OnionFriendRequest req -> 1 + case size of | ||
748 | ConstSize n -> n | ||
749 | VarSize f -> f req | ||
750 | |||
751 | instance Serialize OnionData where | ||
752 | get = do | ||
753 | tag <- get | ||
754 | case tag :: Word8 of | ||
755 | 0x9c -> OnionDHTPublicKey <$> get | ||
756 | 0x20 -> OnionFriendRequest <$> get | ||
757 | _ -> fail $ "Unknown onion data: "++show tag | ||
758 | put (OnionDHTPublicKey dpk) = put (0x9c :: Word8) >> put dpk | ||
759 | put (OnionFriendRequest fr) = put (0x20 :: Word8) >> put fr | ||
760 | |||
761 | selectKey :: TransportCrypto -> OnionMessage f -> OnionDestination r -> IO (SecretKey, PublicKey) | ||
762 | selectKey crypto _ rpath@(OnionDestination (AnnouncingAlias skey pkey) _ _) | ||
763 | = return (skey, pkey) | ||
764 | selectKey crypto msg rpath = return $ aliasKey crypto rpath | ||
765 | |||
766 | encrypt :: TransportCrypto | ||
767 | -> OnionMessage Identity | ||
768 | -> OnionDestination r | ||
769 | -> IO (OnionMessage Encrypted, OnionDestination r) | ||
770 | encrypt crypto msg rpath = do | ||
771 | (skey,pkey) <- selectKey crypto msg rpath -- source key | ||
772 | let okey = onionKey rpath -- destination key | ||
773 | encipher1 :: Serialize a => SecretKey -> PublicKey -> Nonce24 -> a -> (IO ∘ Encrypted) a | ||
774 | encipher1 sk pk n a = Composed $ do | ||
775 | secret <- lookupSharedSecret crypto sk pk n | ||
776 | return $ ToxCrypto.encrypt secret $ encodePlain a | ||
777 | encipher :: Serialize a => Nonce24 -> Either (Identity a) (Asymm (Identity a)) -> (IO ∘ Encrypted) a | ||
778 | encipher n d = encipher1 skey okey n $ either runIdentity (runIdentity . asymmData) d | ||
779 | m <- sequenceMessage $ transcode encipher msg | ||
780 | return (m, rpath) | ||
781 | |||
782 | decrypt :: TransportCrypto -> OnionMessage Encrypted -> OnionDestination r -> IO (Either String (OnionMessage Identity, OnionDestination r)) | ||
783 | decrypt crypto msg addr = do | ||
784 | (skey,pkey) <- selectKey crypto msg addr | ||
785 | let decipher1 :: Serialize a => | ||
786 | TransportCrypto -> SecretKey -> Nonce24 | ||
787 | -> Either (PublicKey,Encrypted a) (Asymm (Encrypted a)) | ||
788 | -> (IO ∘ Either String ∘ Identity) a | ||
789 | decipher1 crypto k n arg = Composed $ do | ||
790 | let (sender,e) = either id (senderKey &&& asymmData) arg | ||
791 | secret <- lookupSharedSecret crypto k sender n | ||
792 | return $ Composed $ do | ||
793 | plain <- ToxCrypto.decrypt secret e | ||
794 | Identity <$> decodePlain plain | ||
795 | decipher :: Serialize a | ||
796 | => Nonce24 -> Either (Encrypted a) (Asymm (Encrypted a)) | ||
797 | -> (IO ∘ Either String ∘ Identity) a | ||
798 | decipher = (\n -> decipher1 crypto skey n . left (senderkey addr)) | ||
799 | foo <- sequenceMessage $ transcode decipher msg | ||
800 | return $ do | ||
801 | msg <- sequenceMessage foo | ||
802 | Right (msg, addr) | ||
803 | |||
804 | senderkey :: OnionDestination r -> t -> (PublicKey, t) | ||
805 | senderkey addr e = (onionKey addr, e) | ||
806 | |||
807 | aliasKey :: TransportCrypto -> OnionDestination r -> (SecretKey,PublicKey) | ||
808 | aliasKey crypto (OnionToOwner {}) = (transportSecret &&& transportPublic) crypto | ||
809 | aliasKey crypto (OnionDestination {}) = (onionAliasSecret &&& onionAliasPublic) crypto | ||
810 | |||
811 | dhtKey :: TransportCrypto -> (SecretKey,PublicKey) | ||
812 | dhtKey crypto = (transportSecret &&& transportPublic) crypto | ||
813 | |||
814 | decryptMessage :: Serialize x => | ||
815 | TransportCrypto | ||
816 | -> (SecretKey,PublicKey) | ||
817 | -> Nonce24 | ||
818 | -> Either (PublicKey, Encrypted x) | ||
819 | (Asymm (Encrypted x)) | ||
820 | -> IO ((Either String ∘ Identity) x) | ||
821 | decryptMessage crypto (sk,pk) n arg = do | ||
822 | let (sender,e) = either id (senderKey &&& asymmData) arg | ||
823 | plain = Composed . fmap Identity . (>>= decodePlain) | ||
824 | secret <- lookupSharedSecret crypto sk sender n | ||
825 | return $ plain $ ToxCrypto.decrypt secret e | ||
826 | |||
827 | sequenceMessage :: Applicative m => OnionMessage (m ∘ f) -> m (OnionMessage f) | ||
828 | sequenceMessage (OnionAnnounce a) = fmap OnionAnnounce $ sequenceA $ fmap uncomposed a | ||
829 | sequenceMessage (OnionAnnounceResponse n8 n24 dta) = OnionAnnounceResponse n8 n24 <$> uncomposed dta | ||
830 | sequenceMessage (OnionToRoute pub a) = pure $ OnionToRoute pub a | ||
831 | sequenceMessage (OnionToRouteResponse a) = pure $ OnionToRouteResponse a | ||
832 | -- sequenceMessage (OnionToRouteResponse a) = fmap OnionToRouteResponse $ sequenceA $ fmap uncomposed a | ||
833 | |||
834 | transcode :: forall f g. (forall a. Serialize a => Nonce24 -> Either (f a) (Asymm (f a)) -> g a) -> OnionMessage f -> OnionMessage g | ||
835 | transcode f (OnionAnnounce a) = OnionAnnounce $ a { asymmData = f (asymmNonce a) (Right a) } | ||
836 | transcode f (OnionAnnounceResponse n8 n24 dta) = OnionAnnounceResponse n8 n24 $ f n24 $ Left dta | ||
837 | transcode f (OnionToRoute pub a) = OnionToRoute pub a | ||
838 | transcode f (OnionToRouteResponse a) = OnionToRouteResponse a | ||
839 | -- transcode f (OnionToRouteResponse a) = OnionToRouteResponse $ a { asymmData = f (asymmNonce a) (Right a) } | ||
840 | |||
841 | |||
842 | data OnionRoute = OnionRoute | ||
843 | { routeAliasA :: SecretKey | ||
844 | , routeAliasB :: SecretKey | ||
845 | , routeAliasC :: SecretKey | ||
846 | , routeNodeA :: NodeInfo | ||
847 | , routeNodeB :: NodeInfo | ||
848 | , routeNodeC :: NodeInfo | ||
849 | , routeRelayPort :: Maybe PortNumber | ||
850 | } | ||
851 | |||
852 | |||
853 | wrapOnion :: Serialize (Forwarding n msg) => | ||
854 | TransportCrypto | ||
855 | -> SecretKey | ||
856 | -> Nonce24 | ||
857 | -> PublicKey | ||
858 | -> SockAddr | ||
859 | -> Forwarding n msg | ||
860 | -> IO (Forwarding (S n) msg) | ||
861 | wrapOnion crypto skey nonce destkey saddr fwd = do | ||
862 | let plain = encodePlain $ Addressed saddr fwd | ||
863 | secret <- lookupSharedSecret crypto skey destkey nonce | ||
864 | return $ Forwarding (toPublic skey) $ ToxCrypto.encrypt secret plain | ||
865 | |||
866 | wrapOnionPure :: Serialize (Forwarding n msg) => | ||
867 | SecretKey | ||
868 | -> ToxCrypto.State | ||
869 | -> SockAddr | ||
870 | -> Forwarding n msg | ||
871 | -> Forwarding (S n) msg | ||
872 | wrapOnionPure skey st saddr fwd = Forwarding (toPublic skey) (ToxCrypto.encrypt st plain) | ||
873 | where | ||
874 | plain = encodePlain $ Addressed saddr fwd | ||
875 | |||
876 | |||
877 | |||
878 | -- TODO | ||
879 | -- Two types of packets may be sent to Rendezvous via OnionToRoute requests. | ||
880 | -- | ||
881 | -- (1) DHT public key packet (0x9c) | ||
882 | -- | ||
883 | -- (2) Friend request | ||
884 | data Rendezvous = Rendezvous | ||
885 | { rendezvousKey :: PublicKey | ||
886 | , rendezvousNode :: NodeInfo | ||
887 | } | ||
888 | deriving Eq | ||
889 | |||
890 | instance Show Rendezvous where | ||
891 | showsPrec d (Rendezvous k ni) | ||
892 | = showsPrec d (key2id k) | ||
893 | . (':' :) | ||
894 | . showsPrec d ni | ||
895 | |||
896 | instance Read Rendezvous where | ||
897 | readsPrec d = RP.readP_to_S $ do | ||
898 | rkstr <- RP.munch (/=':') | ||
899 | RP.char ':' | ||
900 | nistr <- RP.munch (const True) | ||
901 | return Rendezvous | ||
902 | { rendezvousKey = id2key $ read rkstr | ||
903 | , rendezvousNode = read nistr | ||
904 | } | ||
905 | |||
906 | |||
907 | data AnnouncedRendezvous = AnnouncedRendezvous | ||
908 | { remoteUserKey :: PublicKey | ||
909 | , rendezvous :: Rendezvous | ||
910 | } | ||
911 | deriving Eq | ||
912 | |||
913 | instance Show AnnouncedRendezvous where | ||
914 | showsPrec d (AnnouncedRendezvous remote rendez) | ||
915 | = showsPrec d (key2id remote) | ||
916 | . (':' :) | ||
917 | . showsPrec d rendez | ||
918 | |||
919 | instance Read AnnouncedRendezvous where | ||
920 | readsPrec d = RP.readP_to_S $ do | ||
921 | ukstr <- RP.munch (/=':') | ||
922 | RP.char ':' | ||
923 | rkstr <- RP.munch (/=':') | ||
924 | RP.char ':' | ||
925 | nistr <- RP.munch (const True) | ||
926 | return AnnouncedRendezvous | ||
927 | { remoteUserKey = id2key $ read ukstr | ||
928 | , rendezvous = Rendezvous | ||
929 | { rendezvousKey = id2key $ read rkstr | ||
930 | , rendezvousNode = read nistr | ||
931 | } | ||
932 | } | ||
933 | |||
934 | |||
935 | selectAlias :: TransportCrypto -> NodeId -> STM AliasSelector | ||
936 | selectAlias crypto pkey = do | ||
937 | ks <- filter (\(sk,pk) -> pk == id2key pkey) | ||
938 | <$> userKeys crypto | ||
939 | maybe (return SearchingAlias) | ||
940 | (return . uncurry AnnouncingAlias) | ||
941 | (listToMaybe ks) | ||
942 | |||
943 | |||
944 | parseDataToRoute | ||
945 | :: TransportCrypto | ||
946 | -> (OnionMessage Encrypted,OnionDestination r) | ||
947 | -> IO (Either ((PublicKey,OnionData),AnnouncedRendezvous) (OnionMessage Encrypted, OnionDestination r)) | ||
948 | parseDataToRoute crypto (OnionToRouteResponse dta, od) = do | ||
949 | ks <- atomically $ userKeys crypto | ||
950 | |||
951 | omsg0 <- decryptMessage crypto (rendezvousSecret crypto,rendezvousPublic crypto) | ||
952 | (asymmNonce dta) | ||
953 | (Right dta) -- using Asymm{senderKey} as remote key | ||
954 | let eOuter = fmap runIdentity $ uncomposed omsg0 | ||
955 | |||
956 | anyRight [] f = return $ Left "parseDataToRoute: no user key" | ||
957 | anyRight (x:xs) f = f x >>= either (const $ anyRight xs f) (return . Right) | ||
958 | |||
959 | -- TODO: We don't currently have a way to look up which user key we | ||
960 | -- announced using along this onion route. Therefore, for now, we will | ||
961 | -- try all our user keys to see if any can decrypt the packet. | ||
962 | eInner <- case eOuter of | ||
963 | Left e -> return $ Left e | ||
964 | Right dtr -> anyRight ks $ \(sk,pk) -> do | ||
965 | omsg0 <- decryptMessage crypto | ||
966 | (sk,pk) | ||
967 | (asymmNonce dta) | ||
968 | (Left (dataFromKey dtr, dataToRoute dtr)) | ||
969 | return $ do | ||
970 | omsg <- fmap runIdentity . uncomposed $ omsg0 | ||
971 | Right (pk,dtr,omsg) | ||
972 | |||
973 | let e = do | ||
974 | (pk,dtr,omsg) <- eInner | ||
975 | return ( (pk, omsg) | ||
976 | , AnnouncedRendezvous | ||
977 | (dataFromKey dtr) | ||
978 | $ Rendezvous (rendezvousPublic crypto) $ onionNodeInfo od ) | ||
979 | r = either (const $ Right (OnionToRouteResponse dta,od)) Left e | ||
980 | -- parseDataToRoute OnionToRouteResponse decipherAndAuth: auth fail | ||
981 | case e of | ||
982 | Left _ -> dput XMisc $ "Failed keys: " ++ show (map (key2id . snd) ks) | ||
983 | Right _ -> return () | ||
984 | dput XMisc $ unlines | ||
985 | [ "parseDataToRoute " ++ either id (const "Right") e | ||
986 | , " crypto inner.me = " ++ either id (\(pk,_,_) -> show $ key2id pk) eInner | ||
987 | , " inner.them = " ++ either id (show . key2id . dataFromKey) eOuter | ||
988 | , " outer.me = " ++ show (key2id $ rendezvousPublic crypto) | ||
989 | , " outer.them = " ++ show (key2id $ senderKey dta) | ||
990 | ] | ||
991 | return r | ||
992 | parseDataToRoute _ msg = return $ Right msg | ||
993 | |||
994 | encodeDataToRoute :: TransportCrypto | ||
995 | -> ((PublicKey,OnionData),AnnouncedRendezvous) | ||
996 | -> IO (Maybe (OnionMessage Encrypted,OnionDestination r)) | ||
997 | encodeDataToRoute crypto ((me,omsg), AnnouncedRendezvous toxid (Rendezvous pub ni)) = do | ||
998 | nonce <- atomically $ transportNewNonce crypto | ||
999 | asel <- atomically $ selectAlias crypto (key2id me) | ||
1000 | let (sk,pk) = case asel of | ||
1001 | AnnouncingAlias sk pk -> (sk,pk) | ||
1002 | _ -> (onionAliasSecret crypto, onionAliasPublic crypto) | ||
1003 | innerSecret <- lookupSharedSecret crypto sk toxid nonce | ||
1004 | let plain = encodePlain $ DataToRoute { dataFromKey = pk | ||
1005 | , dataToRoute = ToxCrypto.encrypt innerSecret $ encodePlain omsg | ||
1006 | } | ||
1007 | outerSecret <- lookupSharedSecret crypto (onionAliasSecret crypto) pub nonce | ||
1008 | let dta = ToxCrypto.encrypt outerSecret plain | ||
1009 | dput XOnion $ unlines | ||
1010 | [ "encodeDataToRoute me=" ++ show (key2id me) | ||
1011 | , " dhtpk=" ++ case omsg of | ||
1012 | OnionDHTPublicKey dmsg -> show (key2id $ dhtpk dmsg) | ||
1013 | OnionFriendRequest fr -> "friend request" | ||
1014 | , " ns=" ++ case omsg of | ||
1015 | OnionDHTPublicKey dmsg -> show (dhtpkNodes dmsg) | ||
1016 | OnionFriendRequest fr -> "friend request" | ||
1017 | , " crypto inner.me =" ++ show (key2id pk) | ||
1018 | , " inner.you=" ++ show (key2id toxid) | ||
1019 | , " outer.me =" ++ show (key2id $ onionAliasPublic crypto) | ||
1020 | , " outer.you=" ++ show (key2id pub) | ||
1021 | , " " ++ show (AnnouncedRendezvous toxid (Rendezvous pub ni)) | ||
1022 | , " " ++ show dta | ||
1023 | ] | ||
1024 | return $ Just ( OnionToRoute toxid -- Public key of destination node | ||
1025 | Asymm { senderKey = onionAliasPublic crypto | ||
1026 | , asymmNonce = nonce | ||
1027 | , asymmData = dta | ||
1028 | } | ||
1029 | , OnionDestination SearchingAlias ni Nothing ) | ||
diff --git a/src/Data/Tox/Relay.hs b/src/Data/Tox/Relay.hs deleted file mode 100644 index c563db8d..00000000 --- a/src/Data/Tox/Relay.hs +++ /dev/null | |||
@@ -1,232 +0,0 @@ | |||
1 | {-# LANGUAGE ConstraintKinds #-} | ||
2 | {-# LANGUAGE DeriveDataTypeable #-} | ||
3 | {-# LANGUAGE FlexibleInstances #-} | ||
4 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
5 | {-# LANGUAGE KindSignatures #-} | ||
6 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
7 | {-# LANGUAGE PatternSynonyms #-} | ||
8 | {-# LANGUAGE StandaloneDeriving #-} | ||
9 | {-# LANGUAGE UndecidableInstances #-} | ||
10 | module Data.Tox.Relay where | ||
11 | |||
12 | import Data.Aeson (ToJSON(..),FromJSON(..)) | ||
13 | import qualified Data.Aeson as JSON | ||
14 | import Data.ByteString as B | ||
15 | import Data.Data | ||
16 | import Data.Functor.Contravariant | ||
17 | import Data.Hashable | ||
18 | import qualified Data.HashMap.Strict as HashMap | ||
19 | import Data.Monoid | ||
20 | import Data.Serialize | ||
21 | import qualified Data.Vector as Vector | ||
22 | import Data.Word | ||
23 | import Network.Socket | ||
24 | import qualified Rank2 | ||
25 | import qualified Text.ParserCombinators.ReadP as RP | ||
26 | |||
27 | import Crypto.Tox | ||
28 | import Data.Tox.Onion | ||
29 | import qualified Network.Tox.NodeId as UDP | ||
30 | |||
31 | newtype ConId = ConId Word8 | ||
32 | deriving (Eq,Show,Ord,Data,Serialize) | ||
33 | |||
34 | badcon :: ConId | ||
35 | badcon = ConId 0 | ||
36 | |||
37 | -- Maps to a range -120 .. 119 | ||
38 | c2key :: ConId -> Maybe Int | ||
39 | c2key (ConId x) | x < 16 = Nothing | ||
40 | | otherwise = Just $ case divMod (x - 15) 2 of | ||
41 | (q,0) -> negate $ fromIntegral q | ||
42 | (q,1) -> fromIntegral q | ||
43 | |||
44 | -- Maps to range 16 .. 255 | ||
45 | -- negatives become odds | ||
46 | key2c :: Int -> ConId | ||
47 | key2c y = ConId $ if y < 0 then 15 + fromIntegral (negate y * 2) | ||
48 | else 16 + fromIntegral (y * 2) | ||
49 | |||
50 | data RelayPacket | ||
51 | = RoutingRequest PublicKey | ||
52 | | RoutingResponse ConId PublicKey -- 0 for refusal, 16-255 for success. | ||
53 | | ConnectNotification ConId | ||
54 | | DisconnectNotification ConId | ||
55 | | RelayPing Nonce8 | ||
56 | | RelayPong Nonce8 | ||
57 | | OOBSend PublicKey ByteString | ||
58 | | OOBRecv PublicKey ByteString | ||
59 | | OnionPacket Nonce24 (Addressed (Forwarding N2 (OnionMessage Encrypted))) -- (OnionRequest N0) | ||
60 | | OnionPacketResponse (OnionMessage Encrypted) | ||
61 | -- 0x0A through 0x0F reserved for future use. | ||
62 | | RelayData ByteString ConId | ||
63 | deriving (Eq,Ord,Show,Data) | ||
64 | |||
65 | newtype PacketNumber = PacketNumber { packetNumberToWord8 :: Word8 } | ||
66 | deriving (Eq,Ord,Show) | ||
67 | |||
68 | pattern PingPacket = PacketNumber 4 | ||
69 | pattern OnionPacketID = PacketNumber 8 | ||
70 | |||
71 | packetNumber :: RelayPacket -> PacketNumber | ||
72 | packetNumber (RelayData _ (ConId conid)) = PacketNumber $ conid -- 0 to 15 not allowed. | ||
73 | packetNumber rp = PacketNumber $ fromIntegral $ pred $ constrIndex $ toConstr rp | ||
74 | |||
75 | instance Sized RelayPacket where | ||
76 | size = mappend (ConstSize 1) $ VarSize $ \x -> case x of | ||
77 | RoutingRequest k -> 32 | ||
78 | RoutingResponse rpid k -> 33 | ||
79 | ConnectNotification conid -> 1 | ||
80 | DisconnectNotification conid -> 1 | ||
81 | RelayPing pingid -> 8 | ||
82 | RelayPong pingid -> 8 | ||
83 | OOBSend k bs -> 32 + B.length bs | ||
84 | OOBRecv k bs -> 32 + B.length bs | ||
85 | OnionPacket n24 query -> 24 + case contramap (`asTypeOf` query) size of | ||
86 | ConstSize n -> n | ||
87 | VarSize f -> f query | ||
88 | OnionPacketResponse answer -> case contramap (`asTypeOf` answer) size of | ||
89 | ConstSize n -> n | ||
90 | VarSize f -> f answer | ||
91 | RelayData bs _ -> B.length bs | ||
92 | |||
93 | instance Serialize RelayPacket where | ||
94 | |||
95 | get = do | ||
96 | pktid <- getWord8 | ||
97 | case pktid of | ||
98 | 0 -> RoutingRequest <$> getPublicKey | ||
99 | 1 -> RoutingResponse <$> get <*> getPublicKey | ||
100 | 2 -> ConnectNotification <$> get | ||
101 | 3 -> DisconnectNotification <$> get | ||
102 | 4 -> RelayPing <$> get | ||
103 | 5 -> RelayPong <$> get | ||
104 | 6 -> OOBSend <$> getPublicKey <*> (remaining >>= getBytes) | ||
105 | 7 -> OOBRecv <$> getPublicKey <*> (remaining >>= getBytes) | ||
106 | 8 -> OnionPacket <$> get <*> get | ||
107 | 9 -> OnionPacketResponse <$> get | ||
108 | conid -> (`RelayData` ConId conid) <$> (remaining >>= getBytes) | ||
109 | |||
110 | put rp = do | ||
111 | putWord8 $ packetNumberToWord8 $ packetNumber rp | ||
112 | case rp of | ||
113 | RoutingRequest k -> putPublicKey k | ||
114 | RoutingResponse rpid k -> put rpid >> putPublicKey k | ||
115 | ConnectNotification conid -> put conid | ||
116 | DisconnectNotification conid -> put conid | ||
117 | RelayPing pingid -> put pingid | ||
118 | RelayPong pingid -> put pingid | ||
119 | OOBSend k bs -> putPublicKey k >> putByteString bs | ||
120 | OOBRecv k bs -> putPublicKey k >> putByteString bs | ||
121 | OnionPacket n24 query -> put n24 >> put query | ||
122 | OnionPacketResponse answer -> put answer | ||
123 | RelayData bs _ -> putByteString bs | ||
124 | |||
125 | -- | Initial client-to-server handshake message. | ||
126 | newtype Hello (f :: * -> *) = Hello (Asymm (f HelloData)) | ||
127 | |||
128 | deriving instance Show (f HelloData) => Show (Hello f) | ||
129 | |||
130 | helloFrom :: Hello f -> PublicKey | ||
131 | helloFrom (Hello x) = senderKey x | ||
132 | |||
133 | helloNonce :: Hello f -> Nonce24 | ||
134 | helloNonce (Hello x) = asymmNonce x | ||
135 | |||
136 | helloData :: Hello f -> f HelloData | ||
137 | helloData (Hello x) = asymmData x | ||
138 | |||
139 | instance Rank2.Functor Hello where | ||
140 | f <$> Hello (Asymm k n dta) = Hello $ Asymm k n (f dta) | ||
141 | |||
142 | instance Payload Serialize Hello where | ||
143 | mapPayload _ f (Hello (Asymm k n dta)) = Hello $ Asymm k n (f dta) | ||
144 | |||
145 | instance Rank2.Foldable Hello where | ||
146 | foldMap f (Hello (Asymm k n dta)) = f dta | ||
147 | |||
148 | instance Rank2.Traversable Hello where | ||
149 | traverse f (Hello (Asymm k n dta)) = Hello . Asymm k n <$> f dta | ||
150 | |||
151 | instance Sized (Hello Encrypted) where | ||
152 | size = ConstSize 56 <> contramap helloData size | ||
153 | |||
154 | instance Serialize (Hello Encrypted) where | ||
155 | get = Hello <$> getAsymm | ||
156 | put (Hello asym) = putAsymm asym | ||
157 | |||
158 | data HelloData = HelloData | ||
159 | { sessionPublicKey :: PublicKey | ||
160 | , sessionBaseNonce :: Nonce24 | ||
161 | } | ||
162 | deriving Show | ||
163 | |||
164 | instance Sized HelloData where size = ConstSize 56 | ||
165 | |||
166 | instance Serialize HelloData where | ||
167 | get = HelloData <$> getPublicKey <*> get | ||
168 | put (HelloData k n) = putPublicKey k >> put n | ||
169 | |||
170 | -- | Handshake server-to-client response packet. | ||
171 | data Welcome (f :: * -> *) = Welcome | ||
172 | { welcomeNonce :: Nonce24 | ||
173 | , welcomeData :: f HelloData | ||
174 | } | ||
175 | |||
176 | deriving instance Show (f HelloData) => Show (Welcome f) | ||
177 | |||
178 | instance Rank2.Functor Welcome where | ||
179 | f <$> Welcome n dta = Welcome n (f dta) | ||
180 | |||
181 | instance Payload Serialize Welcome where | ||
182 | mapPayload _ f (Welcome n dta) = Welcome n (f dta) | ||
183 | |||
184 | instance Rank2.Foldable Welcome where | ||
185 | foldMap f (Welcome _ dta) = f dta | ||
186 | |||
187 | instance Rank2.Traversable Welcome where | ||
188 | traverse f (Welcome n dta) = Welcome n <$> f dta | ||
189 | |||
190 | instance Sized (Welcome Encrypted) where | ||
191 | size = ConstSize 24 <> contramap welcomeData size | ||
192 | |||
193 | instance Serialize (Welcome Encrypted) where | ||
194 | get = Welcome <$> get <*> get | ||
195 | put (Welcome n dta) = put n >> put dta | ||
196 | |||
197 | data NodeInfo = NodeInfo | ||
198 | { udpNodeInfo :: UDP.NodeInfo | ||
199 | , tcpPort :: PortNumber | ||
200 | } | ||
201 | deriving (Eq,Ord) | ||
202 | |||
203 | instance Read NodeInfo where | ||
204 | readsPrec _ = RP.readP_to_S $ do | ||
205 | udp <- RP.readS_to_P reads | ||
206 | port <- RP.between (RP.char '{') (RP.char '}') $ do | ||
207 | mapM_ RP.char ("tcp:" :: String) | ||
208 | w16 <- RP.readS_to_P reads | ||
209 | return $ fromIntegral (w16 :: Word16) | ||
210 | return $ NodeInfo udp port | ||
211 | |||
212 | instance ToJSON NodeInfo where | ||
213 | toJSON (NodeInfo udp port) = case (toJSON udp) of | ||
214 | JSON.Object tbl -> JSON.Object $ HashMap.insert "tcp_ports" | ||
215 | (JSON.Array $ Vector.fromList | ||
216 | [JSON.Number (fromIntegral port)]) | ||
217 | tbl | ||
218 | x -> x -- Shouldn't happen. | ||
219 | |||
220 | instance FromJSON NodeInfo where | ||
221 | parseJSON json = do | ||
222 | udp <- parseJSON json | ||
223 | port <- case json of | ||
224 | JSON.Object v -> do | ||
225 | portnum:_ <- v JSON..: "tcp_ports" | ||
226 | return (fromIntegral (portnum :: Word16)) | ||
227 | _ -> fail "TCP.NodeInfo: Expected JSON object." | ||
228 | return $ NodeInfo udp port | ||
229 | |||
230 | instance Hashable NodeInfo where | ||
231 | hashWithSalt s n = hashWithSalt s (udpNodeInfo n) | ||
232 | |||
diff --git a/src/Data/Word64Map.hs b/src/Data/Word64Map.hs deleted file mode 100644 index adc9c27e..00000000 --- a/src/Data/Word64Map.hs +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | {-# LANGUAGE RankNTypes #-} | ||
2 | {-# LANGUAGE ScopedTypeVariables #-} | ||
3 | {-# LANGUAGE UnboxedTuples #-} | ||
4 | module Data.Word64Map where | ||
5 | |||
6 | import Data.Bits | ||
7 | import qualified Data.IntMap as IntMap | ||
8 | ;import Data.IntMap (IntMap) | ||
9 | import Data.Monoid | ||
10 | import Data.Typeable | ||
11 | import Data.Word | ||
12 | |||
13 | -- | Since 'Int' may be 32 or 64 bits, this function is provided as a | ||
14 | -- convenience to test if an integral type, such as 'Data.Word.Word64', can be | ||
15 | -- safely transformed into an 'Int' for use with 'IntMap'. | ||
16 | -- | ||
17 | -- Returns 'True' if the proxied type can be losslessly converted to 'Int' using | ||
18 | -- 'fromIntegral'. | ||
19 | fitsInInt :: forall proxy word. (Bounded word, Integral word) => proxy word -> Bool | ||
20 | fitsInInt proxy = (original == casted) | ||
21 | where | ||
22 | original = div maxBound 2 :: word | ||
23 | casted = fromIntegral (fromIntegral original :: Int) :: word | ||
24 | |||
25 | newtype Word64Map a = Word64Map (IntMap (IntMap a)) | ||
26 | |||
27 | size :: Word64Map a -> Int | ||
28 | size (Word64Map m) = getSum $ foldMap (\n -> Sum (IntMap.size n)) m | ||
29 | |||
30 | empty :: Word64Map a | ||
31 | empty = Word64Map IntMap.empty | ||
32 | |||
33 | -- Warning: This function assumes an 'Int' is either 64 or 32 bits. | ||
34 | keyFrom64 :: Word64 -> (# Int,Int #) | ||
35 | keyFrom64 w8 = | ||
36 | if fitsInInt (Proxy :: Proxy Word64) | ||
37 | then (# fromIntegral w8 , 0 #) | ||
38 | else (# fromIntegral (w8 `shiftR` 32), fromIntegral w8 #) | ||
39 | {-# INLINE keyFrom64 #-} | ||
40 | |||
41 | lookup :: Word64 -> Word64Map b -> Maybe b | ||
42 | lookup w8 (Word64Map m) | (# hi,lo #) <- keyFrom64 w8 = do | ||
43 | m' <- IntMap.lookup hi m | ||
44 | IntMap.lookup lo m' | ||
45 | {-# INLINE lookup #-} | ||
46 | |||
47 | insert :: Word64 -> b -> Word64Map b -> Word64Map b | ||
48 | insert w8 b (Word64Map m) | (# hi,lo #) <- keyFrom64 w8 | ||
49 | = Word64Map $ IntMap.alter (Just . maybe (IntMap.singleton lo b) | ||
50 | (IntMap.insert lo b)) | ||
51 | hi | ||
52 | m | ||
53 | {-# INLINE insert #-} | ||
54 | |||
55 | delete :: Word64 -> Word64Map b -> Word64Map b | ||
56 | delete w8 (Word64Map m) | (# hi,lo #) <- keyFrom64 w8 | ||
57 | = Word64Map $ IntMap.alter (maybe Nothing | ||
58 | (\m' -> case IntMap.delete lo m' of | ||
59 | m'' | IntMap.null m'' -> Nothing | ||
60 | m'' -> Just m'')) | ||
61 | hi | ||
62 | m | ||
63 | {-# INLINE delete #-} | ||
64 | |||
65 | |||
66 | |||
diff --git a/src/Data/Wrapper/PSQ.hs b/src/Data/Wrapper/PSQ.hs deleted file mode 100644 index 4fdeec67..00000000 --- a/src/Data/Wrapper/PSQ.hs +++ /dev/null | |||
@@ -1,91 +0,0 @@ | |||
1 | {-# LANGUAGE PatternSynonyms #-} | ||
2 | {-# LANGUAGE CPP #-} | ||
3 | {-# LANGUAGE ConstraintKinds #-} | ||
4 | module Data.Wrapper.PSQ | ||
5 | #if 0 | ||
6 | ( module Data.Wrapper.PSQ , module Data.PSQueue ) where | ||
7 | |||
8 | import Data.PSQueue hiding (foldr, foldl) | ||
9 | import qualified Data.PSQueue as PSQueue | ||
10 | |||
11 | type PSQKey k = (Ord k) | ||
12 | |||
13 | -- | Wrapper over PSQueue-style foldr to implement a psqueues-style interface. | ||
14 | fold' :: (Ord k, Ord p) => (k -> p -> () -> a -> a) -> a -> PSQ k p -> a | ||
15 | fold' f a q = PSQueue.foldr f' a q | ||
16 | where | ||
17 | f' (k :-> prio) x = f k prio () x | ||
18 | |||
19 | #else | ||
20 | ( module Data.Wrapper.PSQ , module HashPSQ ) where | ||
21 | |||
22 | -- import Data.OrdPSQ as OrdPSQ hiding (insert, map, singleton, minView) | ||
23 | -- import qualified Data.OrdPSQ as OrdPSQ | ||
24 | |||
25 | import Data.Hashable | ||
26 | import qualified Data.HashPSQ as Q | ||
27 | ;import Data.HashPSQ as HashPSQ hiding (insert, map, minView, | ||
28 | singleton) | ||
29 | import Data.Time.Clock.POSIX (POSIXTime) | ||
30 | |||
31 | -- type PSQ' k p v = HashPSQ k p v | ||
32 | type PSQ' = HashPSQ | ||
33 | type PSQ k p = PSQ' k p () | ||
34 | |||
35 | type Binding' k p v = (k,p,v) | ||
36 | type Binding k p = Binding' k p () | ||
37 | |||
38 | type PSQKey k = (Hashable k, Ord k) | ||
39 | |||
40 | pattern (:->) :: k -> p -> Binding k p | ||
41 | pattern k :-> p <- (k,p,_) where k :-> p = (k,p,()) | ||
42 | |||
43 | -- I tried defining (::->) :: (k,v) -> p -> Binding' k p v | ||
44 | -- but no luck... | ||
45 | pattern Binding :: k -> v -> p -> Binding' k p v | ||
46 | pattern Binding k v p <- (k,p,v) where Binding k v p = (k,p,v) | ||
47 | |||
48 | key :: (k,p,v) -> k | ||
49 | key (k,p,v) = k | ||
50 | {-# INLINE key #-} | ||
51 | |||
52 | prio :: (k,p,v) -> p | ||
53 | prio (k,p,v) = p | ||
54 | {-# INLINE prio #-} | ||
55 | |||
56 | insert :: (PSQKey k, Ord p) => k -> p -> PSQ k p -> PSQ k p | ||
57 | insert k p q = Q.insert k p () q | ||
58 | {-# INLINE insert #-} | ||
59 | |||
60 | insert' :: (PSQKey k, Ord p) => k -> v -> p -> PSQ' k p v -> PSQ' k p v | ||
61 | insert' k v p q = Q.insert k p v q | ||
62 | {-# INLINE insert' #-} | ||
63 | |||
64 | insertWith :: (PSQKey k, Ord p) => (p -> p -> p) -> k -> p -> PSQ k p -> PSQ k p | ||
65 | insertWith f k p0 q = snd $ Q.alter f' k q | ||
66 | where | ||
67 | f' (Just (p,())) = ((),Just (f p0 p, ())) | ||
68 | f' Nothing = ((),Just (p0,())) | ||
69 | {-# INLINE insertWith #-} | ||
70 | |||
71 | singleton :: (PSQKey k, Ord p) => k -> p -> PSQ k p | ||
72 | singleton k p = Q.singleton k p () | ||
73 | {-# INLINE singleton #-} | ||
74 | |||
75 | singleton' :: (PSQKey k, Ord p) => k -> v -> p -> PSQ' k p v | ||
76 | singleton' k v p = Q.singleton k p v | ||
77 | {-# INLINE singleton' #-} | ||
78 | |||
79 | |||
80 | minView :: (PSQKey k, Ord p) => PSQ' k p v -> Maybe (Binding' k p v, PSQ' k p v) | ||
81 | minView q = fmap (\(k,p,v,q') -> (Binding k v p, q')) $ Q.minView q | ||
82 | {-# INLINE minView #-} | ||
83 | |||
84 | |||
85 | -- | Utility to convert a 'POSIXTime' delta into microseconds suitable for | ||
86 | -- passing to 'threadDelay'. | ||
87 | toMicroseconds :: POSIXTime -> Int | ||
88 | toMicroseconds = round . (* 1000) . (* 1000) | ||
89 | |||
90 | |||
91 | #endif | ||
diff --git a/src/Data/Wrapper/PSQInt.hs b/src/Data/Wrapper/PSQInt.hs deleted file mode 100644 index 5badb8b2..00000000 --- a/src/Data/Wrapper/PSQInt.hs +++ /dev/null | |||
@@ -1,53 +0,0 @@ | |||
1 | {-# LANGUAGE PatternSynonyms #-} | ||
2 | {-# LANGUAGE CPP #-} | ||
3 | {-# LANGUAGE ConstraintKinds #-} | ||
4 | module Data.Wrapper.PSQInt | ||
5 | #if 0 | ||
6 | ( module Data.Wrapper.PSQInt , module Data.PSQueue ) where | ||
7 | |||
8 | import Data.PSQueue hiding (foldr, foldl, PSQ) | ||
9 | import qualified Data.PSQueue as PSQueue | ||
10 | |||
11 | type PSQ p = PSQueue.PSQ Int p | ||
12 | |||
13 | -- | Wrapper over PSQueue-style foldr to implement a psqueues-style interface. | ||
14 | fold' :: (Ord p) => (Int -> p -> () -> a -> a) -> a -> PSQ p -> a | ||
15 | fold' f a q = PSQueue.foldr f' a q | ||
16 | where | ||
17 | f' (k :-> prio) x = f k prio () x | ||
18 | |||
19 | #else | ||
20 | ( module Data.Wrapper.PSQInt | ||
21 | , module IntPSQ | ||
22 | , module Data.Wrapper.PSQ | ||
23 | ) where | ||
24 | |||
25 | import Data.Wrapper.PSQ (Binding, pattern (:->), key, prio, toMicroseconds) | ||
26 | |||
27 | import Data.IntPSQ as IntPSQ hiding (insert, map, singleton, minView) | ||
28 | import qualified Data.IntPSQ as Q | ||
29 | |||
30 | type PSQ p = IntPSQ p () | ||
31 | |||
32 | type PSQKey = () | ||
33 | |||
34 | insert :: (Ord p) => Int -> p -> PSQ p -> PSQ p | ||
35 | insert k p q = Q.insert k p () q | ||
36 | {-# INLINE insert #-} | ||
37 | |||
38 | insertWith :: (Ord p) => (p -> p -> p) -> Int -> p -> PSQ p -> PSQ p | ||
39 | insertWith f k p0 q = snd $ Q.alter f' k q | ||
40 | where | ||
41 | f' (Just (p,())) = ((),Just (f p0 p, ())) | ||
42 | f' Nothing = ((),Nothing) | ||
43 | {-# INLINE insertWith #-} | ||
44 | |||
45 | singleton :: (Ord p) => Int -> p -> PSQ p | ||
46 | singleton k p = Q.singleton k p () | ||
47 | {-# INLINE singleton #-} | ||
48 | |||
49 | minView :: (Ord p) => PSQ p -> Maybe (Binding Int p, PSQ p) | ||
50 | minView q = fmap (\(k,p,(),q') -> (k :-> p, q')) $ Q.minView q | ||
51 | {-# INLINE minView #-} | ||
52 | |||
53 | #endif | ||