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 /dht/src/Network/BitTorrent/DHT/ContactInfo.hs | |
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 'dht/src/Network/BitTorrent/DHT/ContactInfo.hs')
-rw-r--r-- | dht/src/Network/BitTorrent/DHT/ContactInfo.hs | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/dht/src/Network/BitTorrent/DHT/ContactInfo.hs b/dht/src/Network/BitTorrent/DHT/ContactInfo.hs new file mode 100644 index 00000000..ec7e6658 --- /dev/null +++ b/dht/src/Network/BitTorrent/DHT/ContactInfo.hs | |||
@@ -0,0 +1,254 @@ | |||
1 | {-# LANGUAGE BangPatterns #-} | ||
2 | module Network.BitTorrent.DHT.ContactInfo | ||
3 | ( PeerStore | ||
4 | , PeerAddr(..) | ||
5 | , Network.BitTorrent.DHT.ContactInfo.lookup | ||
6 | , Network.BitTorrent.DHT.ContactInfo.freshPeers | ||
7 | , Network.BitTorrent.DHT.ContactInfo.insertPeer | ||
8 | , deleteOlderThan | ||
9 | , knownSwarms | ||
10 | ) where | ||
11 | |||
12 | import Control.Applicative | ||
13 | import Data.Default | ||
14 | import Data.List as L | ||
15 | import Data.Maybe | ||
16 | import Data.HashMap.Strict as HM | ||
17 | import Data.Serialize | ||
18 | import Data.Semigroup | ||
19 | import Data.Wrapper.PSQ as PSQ | ||
20 | import Data.Time.Clock.POSIX | ||
21 | import Data.ByteString (ByteString) | ||
22 | import Data.Word | ||
23 | |||
24 | import Data.Torrent | ||
25 | import Network.Address | ||
26 | |||
27 | -- {- | ||
28 | -- import Data.HashMap.Strict as HM | ||
29 | -- | ||
30 | -- import Data.Torrent.InfoHash | ||
31 | -- import Network.Address | ||
32 | -- | ||
33 | -- -- increase prefix when table is too large | ||
34 | -- -- decrease prefix when table is too small | ||
35 | -- -- filter outdated peers | ||
36 | -- | ||
37 | -- {----------------------------------------------------------------------- | ||
38 | -- -- PeerSet | ||
39 | -- -----------------------------------------------------------------------} | ||
40 | -- | ||
41 | -- type PeerSet a = [(PeerAddr, NodeInfo a, Timestamp)] | ||
42 | -- | ||
43 | -- -- compare PSQueue vs Ordered list | ||
44 | -- | ||
45 | -- takeNewest :: PeerSet a -> [PeerAddr] | ||
46 | -- takeNewest = undefined | ||
47 | -- | ||
48 | -- dropOld :: Timestamp -> PeerSet a -> PeerSet a | ||
49 | -- dropOld = undefined | ||
50 | -- | ||
51 | -- insert :: PeerAddr -> Timestamp -> PeerSet a -> PeerSet a | ||
52 | -- insert = undefined | ||
53 | -- | ||
54 | -- type Mask = Int | ||
55 | -- type Size = Int | ||
56 | -- type Timestamp = Int | ||
57 | -- | ||
58 | -- {----------------------------------------------------------------------- | ||
59 | -- -- InfoHashMap | ||
60 | -- -----------------------------------------------------------------------} | ||
61 | -- | ||
62 | -- -- compare handwritten prefix tree versus IntMap | ||
63 | -- | ||
64 | -- data Tree a | ||
65 | -- = Nil | ||
66 | -- | Tip !InfoHash !(PeerSet a) | ||
67 | -- | Bin !InfoHash !Mask !Size !Timestamp (Tree a) (Tree a) | ||
68 | -- | ||
69 | -- insertTree :: InfoHash -> a -> Tree a -> Tree a | ||
70 | -- insertTree = undefined | ||
71 | -- | ||
72 | -- type Prio = Int | ||
73 | -- | ||
74 | -- --shrink :: ContactInfo ip -> Int | ||
75 | -- shrink Nil = Nil | ||
76 | -- shrink (Tip _ _) = undefined | ||
77 | -- shrink (Bin _ _) = undefined | ||
78 | -- | ||
79 | -- {----------------------------------------------------------------------- | ||
80 | -- -- InfoHashMap | ||
81 | -- -----------------------------------------------------------------------} | ||
82 | -- | ||
83 | -- -- compare new design versus HashMap | ||
84 | -- | ||
85 | -- data IntMap k p a | ||
86 | -- type ContactInfo = Map InfoHash Timestamp (Set (PeerAddr IP) Timestamp) | ||
87 | -- | ||
88 | -- data ContactInfo ip = PeerStore | ||
89 | -- { maxSize :: Int | ||
90 | -- , prefixSize :: Int | ||
91 | -- , thisNodeId :: NodeId | ||
92 | -- | ||
93 | -- , count :: Int -- ^ Cached size of the 'peerSet' | ||
94 | -- , peerSet :: HashMap InfoHash [PeerAddr ip] | ||
95 | -- } | ||
96 | -- | ||
97 | -- size :: ContactInfo ip -> Int | ||
98 | -- size = undefined | ||
99 | -- | ||
100 | -- prefixSize :: ContactInfo ip -> Int | ||
101 | -- prefixSize = undefined | ||
102 | -- | ||
103 | -- lookup :: InfoHash -> ContactInfo ip -> [PeerAddr ip] | ||
104 | -- lookup = undefined | ||
105 | -- | ||
106 | -- insert :: InfoHash -> PeerAddr ip -> ContactInfo ip -> ContactInfo ip | ||
107 | -- insert = undefined | ||
108 | -- | ||
109 | -- -- | Limit in size. | ||
110 | -- prune :: NodeId -> Int -> ContactInfo ip -> ContactInfo ip | ||
111 | -- prune pref targetSize Nil = Nil | ||
112 | -- prune pref targetSize (Tip _ _) = undefined | ||
113 | -- | ||
114 | -- -- | Remove expired entries. | ||
115 | -- splitGT :: Timestamp -> ContactInfo ip -> ContactInfo ip | ||
116 | -- splitGT = undefined | ||
117 | -- -} | ||
118 | |||
119 | -- | Storage used to keep track a set of known peers in client, | ||
120 | -- tracker or DHT sessions. | ||
121 | newtype PeerStore = PeerStore (HashMap InfoHash SwarmData) | ||
122 | |||
123 | type Timestamp = POSIXTime | ||
124 | |||
125 | data SwarmData = SwarmData | ||
126 | { peers :: !(PSQ PeerAddr Timestamp) | ||
127 | , name :: !(Maybe ByteString) | ||
128 | } | ||
129 | |||
130 | -- | This wrapper will serialize an ip address with a '4' or '6' prefix byte | ||
131 | -- to indicate whether it is IPv4 or IPv6. | ||
132 | -- | ||
133 | -- Note: it does not serialize port numbers. | ||
134 | newtype SerializeAddress a = SerializeAddress { unserializeAddress :: a } | ||
135 | |||
136 | instance Address a => Serialize (SerializeAddress a) where | ||
137 | get = SerializeAddress <$> do | ||
138 | c <- get | ||
139 | case (c::Word8) of | ||
140 | 0x34 -> do ip4 <- get | ||
141 | return $ fromJust $ fromAddr (ip4::IPv4) | ||
142 | 0x36 -> do ip6 <- get | ||
143 | return $ fromJust $ fromAddr (ip6::IPv6) | ||
144 | _ -> return $ error "cannot deserialize non-IP SerializeAddress" | ||
145 | put (SerializeAddress a) | ||
146 | | Just ip4 <- fromAddr a | ||
147 | = put (0x34::Word8) >> put (ip4::IPv4) | ||
148 | | Just ip6 <- fromAddr a | ||
149 | = put (0x36::Word8) >> put (ip6::IPv6) | ||
150 | | otherwise = return $ error "cannot serialize non-IP SerializeAddress" | ||
151 | |||
152 | |||
153 | instance Serialize SwarmData where | ||
154 | get = flip SwarmData <$> get | ||
155 | <*> ( PSQ.fromList . L.map parseAddr <$> get ) | ||
156 | where | ||
157 | parseAddr (pid,addr,port) = PeerAddr { peerId = pid | ||
158 | , peerHost = unserializeAddress addr | ||
159 | , peerPort = port | ||
160 | } | ||
161 | :-> 0 | ||
162 | |||
163 | put SwarmData{..} = do | ||
164 | put name | ||
165 | put $ L.map (\(addr :-> _) -> (peerId addr, SerializeAddress addr, peerPort addr)) | ||
166 | -- XXX: should we serialize the timestamp? | ||
167 | $ PSQ.toList peers | ||
168 | |||
169 | knownSwarms :: PeerStore -> [ (InfoHash, Int, Maybe ByteString) ] | ||
170 | knownSwarms (PeerStore m) = L.map (\(ih,SwarmData q n) -> (ih,PSQ.size q,n)) $ HM.toList m | ||
171 | |||
172 | swarmSingleton :: PeerAddr -> SwarmData | ||
173 | swarmSingleton a = SwarmData | ||
174 | { peers = PSQ.singleton a 0 | ||
175 | , name = Nothing } | ||
176 | |||
177 | swarmInsert :: SwarmData -> SwarmData -> SwarmData | ||
178 | swarmInsert new old = SwarmData | ||
179 | { peers = L.foldl' (\q (a :-> t) -> PSQ.insertWith newerTimeStamp a t q) (peers old) (PSQ.toList $ peers new) | ||
180 | , name = name new <|> name old -- TODO: decodeUtf8' check | ||
181 | } | ||
182 | where | ||
183 | newerTimeStamp newtime oldtime = if newtime > oldtime then newtime else oldtime | ||
184 | |||
185 | isSwarmOccupied :: SwarmData -> Bool | ||
186 | isSwarmOccupied SwarmData{..} = not $ PSQ.null peers | ||
187 | |||
188 | -- | Empty store. | ||
189 | instance Default (PeerStore) where | ||
190 | def = PeerStore HM.empty | ||
191 | {-# INLINE def #-} | ||
192 | |||
193 | instance Semigroup PeerStore where | ||
194 | PeerStore a <> PeerStore b = | ||
195 | PeerStore (HM.unionWith swarmInsert a b) | ||
196 | {-# INLINE (<>) #-} | ||
197 | |||
198 | -- | Monoid under union operation. | ||
199 | instance Monoid PeerStore where | ||
200 | mempty = def | ||
201 | {-# INLINE mempty #-} | ||
202 | |||
203 | mappend (PeerStore a) (PeerStore b) = | ||
204 | PeerStore (HM.unionWith swarmInsert a b) | ||
205 | {-# INLINE mappend #-} | ||
206 | |||
207 | -- | Can be used to store peers between invocations of the client | ||
208 | -- software. | ||
209 | instance Serialize PeerStore where | ||
210 | get = PeerStore . HM.fromList <$> get | ||
211 | put (PeerStore m) = put (L.filter (isSwarmOccupied . snd) $ HM.toList m) | ||
212 | |||
213 | -- | Returns all peers associated with a given info hash. | ||
214 | lookup :: InfoHash -> PeerStore -> [PeerAddr] | ||
215 | lookup ih (PeerStore m) = maybe [] (PSQ.keys . peers) $ HM.lookup ih m | ||
216 | |||
217 | batchSize :: Int | ||
218 | batchSize = 64 | ||
219 | |||
220 | -- | Used in 'get_peers' DHT queries. | ||
221 | freshPeers :: InfoHash -> Timestamp -> PeerStore -> ([PeerAddr], PeerStore) | ||
222 | freshPeers ih tm (PeerStore m) = fromMaybe ([],PeerStore m) $ do | ||
223 | swarm <- HM.lookup ih m | ||
224 | let ps0 = take batchSize $ unfoldr (incomp minView) (peers swarm) | ||
225 | peers' = case reverse ps0 of | ||
226 | (_,psq):_ -> psq | ||
227 | _ -> peers swarm | ||
228 | ps = L.map (key . fst) ps0 | ||
229 | m' = HM.insert ih swarm { peers = L.foldl' (\q p -> PSQ.insert p tm q) peers' ps } m | ||
230 | return $! m' `seq` (ps,PeerStore m') | ||
231 | |||
232 | incomp :: (x -> Maybe (r,x)) -> x -> Maybe ((r,x),x) | ||
233 | incomp !f !x = do | ||
234 | (result,x') <- f x | ||
235 | pure $! ( (result,x'), x' ) | ||
236 | |||
237 | -- | Used in 'announce_peer' DHT queries. | ||
238 | insertPeer :: InfoHash -> Maybe ByteString -> PeerAddr -> PeerStore -> PeerStore | ||
239 | insertPeer !ih !name !a !(PeerStore m) = seq a' $ PeerStore (HM.insertWith swarmInsert ih a' m) | ||
240 | where | ||
241 | a' = SwarmData { peers = PSQ.singleton a 0 | ||
242 | , name = name } | ||
243 | |||
244 | deleteOlderThan :: POSIXTime -> PeerStore -> PeerStore | ||
245 | deleteOlderThan cutoff (PeerStore m) = PeerStore $ HM.mapMaybe gc m | ||
246 | where | ||
247 | gc :: SwarmData -> Maybe SwarmData | ||
248 | gc swarms = fmap (\ps -> swarms { peers = ps }) $ gcPSQ (peers swarms) | ||
249 | |||
250 | gcPSQ :: PSQKey a => PSQ a Timestamp -> Maybe (PSQ a Timestamp) | ||
251 | gcPSQ ps = case minView ps of | ||
252 | Nothing -> Nothing | ||
253 | Just (_ :-> tm, ps') | tm < cutoff -> gcPSQ ps' | ||
254 | Just _ -> Just ps | ||