diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2013-11-21 22:19:23 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2013-11-21 22:19:23 +0400 |
commit | 6634363e46211ebb65bae32d4e6cccd940f401c1 (patch) | |
tree | 9cd41e246bd6c2d9c8470c64e4f0f1788e90460b /src | |
parent | 08d1d4674ef38542e5db24fc398efefbc7bb35af (diff) |
Refactor tracker messages
Diffstat (limited to 'src')
-rw-r--r-- | src/Data/Torrent/Progress.hs | 18 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/Message.hs | 246 |
2 files changed, 136 insertions, 128 deletions
diff --git a/src/Data/Torrent/Progress.hs b/src/Data/Torrent/Progress.hs index c42af248..18a9cd7d 100644 --- a/src/Data/Torrent/Progress.hs +++ b/src/Data/Torrent/Progress.hs | |||
@@ -34,13 +34,14 @@ module Data.Torrent.Progress | |||
34 | ) where | 34 | ) where |
35 | 35 | ||
36 | import Control.Applicative | 36 | import Control.Applicative |
37 | import Control.Lens | 37 | import Control.Lens hiding ((%=)) |
38 | import Data.Aeson.TH | 38 | import Data.Aeson.TH |
39 | import Data.Default | 39 | import Data.Default |
40 | import Data.List as L | 40 | import Data.List as L |
41 | import Data.Monoid | 41 | import Data.Monoid |
42 | import Data.Serialize as S | 42 | import Data.Serialize as S |
43 | import Data.Ratio | 43 | import Data.Ratio |
44 | import Data.URLEncoded | ||
44 | import Data.Word | 45 | import Data.Word |
45 | 46 | ||
46 | 47 | ||
@@ -58,6 +59,7 @@ data Progress = Progress | |||
58 | $(makeLenses ''Progress) | 59 | $(makeLenses ''Progress) |
59 | $(deriveJSON L.tail ''Progress) | 60 | $(deriveJSON L.tail ''Progress) |
60 | 61 | ||
62 | -- | UDP tracker compatible encoding. | ||
61 | instance Serialize Progress where | 63 | instance Serialize Progress where |
62 | put Progress {..} = do | 64 | put Progress {..} = do |
63 | putWord64be $ fromIntegral _downloaded | 65 | putWord64be $ fromIntegral _downloaded |
@@ -73,6 +75,7 @@ instance Default Progress where | |||
73 | def = Progress 0 0 0 | 75 | def = Progress 0 0 0 |
74 | {-# INLINE def #-} | 76 | {-# INLINE def #-} |
75 | 77 | ||
78 | -- | Can be used to aggregate total progress. | ||
76 | instance Monoid Progress where | 79 | instance Monoid Progress where |
77 | mempty = def | 80 | mempty = def |
78 | {-# INLINE mempty #-} | 81 | {-# INLINE mempty #-} |
@@ -84,6 +87,19 @@ instance Monoid Progress where | |||
84 | } | 87 | } |
85 | {-# INLINE mappend #-} | 88 | {-# INLINE mappend #-} |
86 | 89 | ||
90 | instance URLShow Word64 where | ||
91 | urlShow = show | ||
92 | {-# INLINE urlShow #-} | ||
93 | |||
94 | -- | HTTP Tracker protocol compatible encoding. | ||
95 | instance URLEncode Progress where | ||
96 | urlEncode Progress {..} = mconcat | ||
97 | [ s "uploaded" %= _uploaded | ||
98 | , s "left" %= _left | ||
99 | , s "downloaded" %= _downloaded | ||
100 | ] | ||
101 | where s :: String -> String; s = id; {-# INLINE s #-} | ||
102 | |||
87 | -- | Initial progress is used when there are no session before. | 103 | -- | Initial progress is used when there are no session before. |
88 | -- | 104 | -- |
89 | -- Please note that tracker might penalize client some way if the do | 105 | -- Please note that tracker might penalize client some way if the do |
diff --git a/src/Network/BitTorrent/Tracker/Message.hs b/src/Network/BitTorrent/Tracker/Message.hs index 8f4c9228..469ca0ec 100644 --- a/src/Network/BitTorrent/Tracker/Message.hs +++ b/src/Network/BitTorrent/Tracker/Message.hs | |||
@@ -23,7 +23,7 @@ | |||
23 | {-# LANGUAGE TemplateHaskell #-} | 23 | {-# LANGUAGE TemplateHaskell #-} |
24 | {-# LANGUAGE DeriveDataTypeable #-} | 24 | {-# LANGUAGE DeriveDataTypeable #-} |
25 | {-# OPTIONS -fno-warn-orphans #-} | 25 | {-# OPTIONS -fno-warn-orphans #-} |
26 | module Network.BitTorrent.Tracker.Protocol | 26 | module Network.BitTorrent.Tracker.Message |
27 | ( -- * Announce | 27 | ( -- * Announce |
28 | Event(..) | 28 | Event(..) |
29 | , AnnounceQuery(..) | 29 | , AnnounceQuery(..) |
@@ -71,8 +71,9 @@ import Data.Torrent.Progress | |||
71 | import Network.BitTorrent.Core.PeerId | 71 | import Network.BitTorrent.Core.PeerId |
72 | import Network.BitTorrent.Core.PeerAddr | 72 | import Network.BitTorrent.Core.PeerAddr |
73 | 73 | ||
74 | |||
74 | {----------------------------------------------------------------------- | 75 | {----------------------------------------------------------------------- |
75 | Announce messages | 76 | -- Events |
76 | -----------------------------------------------------------------------} | 77 | -----------------------------------------------------------------------} |
77 | 78 | ||
78 | -- | Events used to specify which kind of tracker request is performed. | 79 | -- | Events used to specify which kind of tracker request is performed. |
@@ -86,6 +87,40 @@ data Event = Started | |||
86 | 87 | ||
87 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''Event) | 88 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''Event) |
88 | 89 | ||
90 | -- | HTTP tracker protocol compatible encoding. | ||
91 | instance URLShow Event where | ||
92 | urlShow e = urlShow (Char.toLower x : xs) | ||
93 | where | ||
94 | -- INVARIANT: this is always nonempty list | ||
95 | (x : xs) = show e | ||
96 | |||
97 | type EventId = Word32 | ||
98 | |||
99 | -- | UDP tracker encoding event codes. | ||
100 | eventId :: Event -> EventId | ||
101 | eventId Completed = 1 | ||
102 | eventId Started = 2 | ||
103 | eventId Stopped = 3 | ||
104 | |||
105 | -- TODO add Regular event | ||
106 | putEvent :: Putter (Maybe Event) | ||
107 | putEvent Nothing = putWord32be 0 | ||
108 | putEvent (Just e) = putWord32be (eventId e) | ||
109 | |||
110 | getEvent :: S.Get (Maybe Event) | ||
111 | getEvent = do | ||
112 | eid <- getWord32be | ||
113 | case eid of | ||
114 | 0 -> return Nothing | ||
115 | 1 -> return $ Just Completed | ||
116 | 2 -> return $ Just Started | ||
117 | 3 -> return $ Just Stopped | ||
118 | _ -> fail "unknown event id" | ||
119 | |||
120 | {----------------------------------------------------------------------- | ||
121 | Announce query | ||
122 | -----------------------------------------------------------------------} | ||
123 | |||
89 | -- | A tracker request is HTTP GET request; used to include metrics | 124 | -- | A tracker request is HTTP GET request; used to include metrics |
90 | -- from clients that help the tracker keep overall statistics about | 125 | -- from clients that help the tracker keep overall statistics about |
91 | -- the torrent. The most important, requests are used by the tracker | 126 | -- the torrent. The most important, requests are used by the tracker |
@@ -121,9 +156,74 @@ data AnnounceQuery = AnnounceQuery { | |||
121 | 156 | ||
122 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceQuery) | 157 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceQuery) |
123 | 158 | ||
159 | instance URLShow PortNumber where | ||
160 | urlShow = urlShow . fromEnum | ||
161 | |||
162 | instance URLShow Word32 where | ||
163 | urlShow = show | ||
164 | {-# INLINE urlShow #-} | ||
165 | |||
166 | -- | HTTP tracker protocol compatible encoding. | ||
167 | instance URLEncode AnnounceQuery where | ||
168 | urlEncode AnnounceQuery {..} = mconcat | ||
169 | [ s "peer_id" %= reqPeerId | ||
170 | , s "port" %= reqPort | ||
171 | , urlEncode reqProgress | ||
172 | , s "ip" %=? reqIP | ||
173 | , s "numwant" %=? reqNumWant | ||
174 | , s "event" %=? reqEvent | ||
175 | ] | ||
176 | where s :: String -> String; s = id; {-# INLINE s #-} | ||
177 | |||
178 | -- | UDP tracker protocol compatible encoding. | ||
179 | instance Serialize AnnounceQuery where | ||
180 | put AnnounceQuery {..} = do | ||
181 | put reqInfoHash | ||
182 | put reqPeerId | ||
183 | put reqProgress | ||
184 | putEvent reqEvent | ||
185 | putWord32be $ fromMaybe 0 reqIP | ||
186 | putWord32be $ 0 -- TODO what the fuck is "key"? | ||
187 | putWord32be $ fromIntegral $ fromMaybe (-1) reqNumWant | ||
188 | |||
189 | put reqPort | ||
190 | |||
191 | get = do | ||
192 | ih <- get | ||
193 | pid <- get | ||
194 | |||
195 | progress <- get | ||
196 | |||
197 | ev <- getEvent | ||
198 | ip <- getWord32be | ||
199 | -- key <- getWord32be -- TODO | ||
200 | want <- getWord32be | ||
201 | |||
202 | port <- get | ||
203 | |||
204 | return $ AnnounceQuery { | ||
205 | reqInfoHash = ih | ||
206 | , reqPeerId = pid | ||
207 | , reqPort = port | ||
208 | , reqProgress = progress | ||
209 | , reqIP = if ip == 0 then Nothing else Just ip | ||
210 | , reqNumWant = if want == -1 then Nothing else Just (fromIntegral want) | ||
211 | , reqEvent = ev | ||
212 | } | ||
213 | |||
214 | {----------------------------------------------------------------------- | ||
215 | -- Announce response | ||
216 | -----------------------------------------------------------------------} | ||
217 | |||
124 | newtype PeerList = PeerList { getPeerList :: [PeerAddr] } | 218 | newtype PeerList = PeerList { getPeerList :: [PeerAddr] } |
125 | deriving (Show, Eq, ToJSON, FromJSON, Typeable) | 219 | deriving (Show, Eq, ToJSON, FromJSON, Typeable) |
126 | 220 | ||
221 | instance BEncode PeerList where | ||
222 | toBEncode (PeerList xs) = toBEncode xs | ||
223 | fromBEncode (BList l ) = PeerList <$> fromBEncode (BList l) | ||
224 | fromBEncode (BString s ) = PeerList <$> runGet getCompactPeerList s | ||
225 | fromBEncode _ = decodingError "Peer list" | ||
226 | |||
127 | -- | The tracker response includes a peer list that helps the client | 227 | -- | The tracker response includes a peer list that helps the client |
128 | -- participate in the torrent. The most important is 'respPeer' list | 228 | -- participate in the torrent. The most important is 'respPeer' list |
129 | -- used to join the swarm. | 229 | -- used to join the swarm. |
@@ -155,31 +255,6 @@ data AnnounceInfo = | |||
155 | 255 | ||
156 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceInfo) | 256 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceInfo) |
157 | 257 | ||
158 | -- | Ports typically reserved for bittorrent P2P listener. | ||
159 | defaultPorts :: [PortNumber] | ||
160 | defaultPorts = [6881..6889] | ||
161 | |||
162 | -- | Above 25, new peers are highly unlikely to increase download | ||
163 | -- speed. Even 30 peers is /plenty/, the official client version 3 | ||
164 | -- in fact only actively forms new connections if it has less than | ||
165 | -- 30 peers and will refuse connections if it has 55. | ||
166 | -- | ||
167 | -- So the default value is set to 50 because usually 30-50% of peers | ||
168 | -- are not responding. | ||
169 | -- | ||
170 | defaultNumWant :: Int | ||
171 | defaultNumWant = 50 | ||
172 | |||
173 | {----------------------------------------------------------------------- | ||
174 | Bencode announce encoding | ||
175 | -----------------------------------------------------------------------} | ||
176 | |||
177 | instance BEncode PeerList where | ||
178 | toBEncode (PeerList xs) = toBEncode xs | ||
179 | fromBEncode (BList l ) = PeerList <$> fromBEncode (BList l) | ||
180 | fromBEncode (BString s ) = PeerList <$> runGet getCompactPeerList s | ||
181 | fromBEncode _ = decodingError "Peer list" | ||
182 | |||
183 | -- | HTTP tracker protocol compatible encoding. | 258 | -- | HTTP tracker protocol compatible encoding. |
184 | instance BEncode AnnounceInfo where | 259 | instance BEncode AnnounceInfo where |
185 | toBEncode (Failure t) = toDict $ | 260 | toBEncode (Failure t) = toDict $ |
@@ -207,105 +282,6 @@ instance BEncode AnnounceInfo where | |||
207 | <*>? "warning message" | 282 | <*>? "warning message" |
208 | fromBEncode _ = decodingError "Announce info" | 283 | fromBEncode _ = decodingError "Announce info" |
209 | 284 | ||
210 | instance URLShow PortNumber where | ||
211 | urlShow = urlShow . fromEnum | ||
212 | |||
213 | instance URLShow Word32 where | ||
214 | urlShow = show | ||
215 | |||
216 | instance URLShow Event where | ||
217 | urlShow e = urlShow (Char.toLower x : xs) | ||
218 | where | ||
219 | -- INVARIANT: this is always nonempty list | ||
220 | (x : xs) = show e | ||
221 | |||
222 | instance URLShow Word64 where | ||
223 | urlShow = show | ||
224 | |||
225 | instance URLEncode Progress where | ||
226 | urlEncode Progress {..} = mconcat | ||
227 | [ s "uploaded" %= _uploaded | ||
228 | , s "left" %= _left | ||
229 | , s "downloaded" %= _downloaded | ||
230 | ] | ||
231 | where s :: String -> String; s = id; {-# INLINE s #-} | ||
232 | |||
233 | -- | HTTP tracker protocol compatible encoding. | ||
234 | instance URLEncode AnnounceQuery where | ||
235 | urlEncode AnnounceQuery {..} = mconcat | ||
236 | [ s "peer_id" %= reqPeerId | ||
237 | , s "port" %= reqPort | ||
238 | , urlEncode reqProgress | ||
239 | |||
240 | |||
241 | , s "ip" %=? reqIP | ||
242 | , s "numwant" %=? reqNumWant | ||
243 | , s "event" %=? reqEvent | ||
244 | ] | ||
245 | where s :: String -> String; s = id; {-# INLINE s #-} | ||
246 | |||
247 | {----------------------------------------------------------------------- | ||
248 | Binary announce encoding | ||
249 | -----------------------------------------------------------------------} | ||
250 | |||
251 | type EventId = Word32 | ||
252 | |||
253 | eventId :: Event -> EventId | ||
254 | eventId Completed = 1 | ||
255 | eventId Started = 2 | ||
256 | eventId Stopped = 3 | ||
257 | |||
258 | -- TODO add Regular event | ||
259 | putEvent :: Putter (Maybe Event) | ||
260 | putEvent Nothing = putWord32be 0 | ||
261 | putEvent (Just e) = putWord32be (eventId e) | ||
262 | |||
263 | getEvent :: S.Get (Maybe Event) | ||
264 | getEvent = do | ||
265 | eid <- getWord32be | ||
266 | case eid of | ||
267 | 0 -> return Nothing | ||
268 | 1 -> return $ Just Completed | ||
269 | 2 -> return $ Just Started | ||
270 | 3 -> return $ Just Stopped | ||
271 | _ -> fail "unknown event id" | ||
272 | |||
273 | -- | UDP tracker protocol compatible encoding. | ||
274 | instance Serialize AnnounceQuery where | ||
275 | put AnnounceQuery {..} = do | ||
276 | put reqInfoHash | ||
277 | put reqPeerId | ||
278 | put reqProgress | ||
279 | putEvent reqEvent | ||
280 | putWord32be $ fromMaybe 0 reqIP | ||
281 | putWord32be $ 0 -- TODO what the fuck is "key"? | ||
282 | putWord32be $ fromIntegral $ fromMaybe (-1) reqNumWant | ||
283 | |||
284 | put reqPort | ||
285 | |||
286 | get = do | ||
287 | ih <- get | ||
288 | pid <- get | ||
289 | |||
290 | progress <- get | ||
291 | |||
292 | ev <- getEvent | ||
293 | ip <- getWord32be | ||
294 | -- key <- getWord32be -- TODO | ||
295 | want <- getWord32be | ||
296 | |||
297 | port <- get | ||
298 | |||
299 | return $ AnnounceQuery { | ||
300 | reqInfoHash = ih | ||
301 | , reqPeerId = pid | ||
302 | , reqPort = port | ||
303 | , reqProgress = progress | ||
304 | , reqIP = if ip == 0 then Nothing else Just ip | ||
305 | , reqNumWant = if want == -1 then Nothing else Just (fromIntegral want) | ||
306 | , reqEvent = ev | ||
307 | } | ||
308 | |||
309 | -- | UDP tracker protocol compatible encoding. | 285 | -- | UDP tracker protocol compatible encoding. |
310 | instance Serialize AnnounceInfo where | 286 | instance Serialize AnnounceInfo where |
311 | put (Failure msg) = put $ encodeUtf8 msg | 287 | put (Failure msg) = put $ encodeUtf8 msg |
@@ -330,8 +306,24 @@ instance Serialize AnnounceInfo where | |||
330 | , respPeers = PeerList peers | 306 | , respPeers = PeerList peers |
331 | } | 307 | } |
332 | 308 | ||
309 | -- TODO move this somewhere else | ||
310 | -- | Ports typically reserved for bittorrent P2P listener. | ||
311 | defaultPorts :: [PortNumber] | ||
312 | defaultPorts = [6881..6889] | ||
313 | |||
314 | -- | Above 25, new peers are highly unlikely to increase download | ||
315 | -- speed. Even 30 peers is /plenty/, the official client version 3 | ||
316 | -- in fact only actively forms new connections if it has less than | ||
317 | -- 30 peers and will refuse connections if it has 55. | ||
318 | -- | ||
319 | -- So the default value is set to 50 because usually 30-50% of peers | ||
320 | -- are not responding. | ||
321 | -- | ||
322 | defaultNumWant :: Int | ||
323 | defaultNumWant = 50 | ||
324 | |||
333 | {----------------------------------------------------------------------- | 325 | {----------------------------------------------------------------------- |
334 | Scrape messages | 326 | Scrape message |
335 | -----------------------------------------------------------------------} | 327 | -----------------------------------------------------------------------} |
336 | 328 | ||
337 | type ScrapeQuery = [InfoHash] | 329 | type ScrapeQuery = [InfoHash] |