diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Network/BitTorrent/Tracker/Protocol.hs | 167 |
1 files changed, 98 insertions, 69 deletions
diff --git a/src/Network/BitTorrent/Tracker/Protocol.hs b/src/Network/BitTorrent/Tracker/Protocol.hs index 0aac4f47..8f4c9228 100644 --- a/src/Network/BitTorrent/Tracker/Protocol.hs +++ b/src/Network/BitTorrent/Tracker/Protocol.hs | |||
@@ -21,6 +21,7 @@ | |||
21 | {-# LANGUAGE FlexibleInstances #-} | 21 | {-# LANGUAGE FlexibleInstances #-} |
22 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 22 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
23 | {-# LANGUAGE TemplateHaskell #-} | 23 | {-# LANGUAGE TemplateHaskell #-} |
24 | {-# LANGUAGE DeriveDataTypeable #-} | ||
24 | {-# OPTIONS -fno-warn-orphans #-} | 25 | {-# OPTIONS -fno-warn-orphans #-} |
25 | module Network.BitTorrent.Tracker.Protocol | 26 | module Network.BitTorrent.Tracker.Protocol |
26 | ( -- * Announce | 27 | ( -- * Announce |
@@ -46,24 +47,29 @@ module Network.BitTorrent.Tracker.Protocol | |||
46 | import Control.Applicative | 47 | import Control.Applicative |
47 | import Control.Exception | 48 | import Control.Exception |
48 | import Control.Monad | 49 | import Control.Monad |
50 | import Data.Aeson (ToJSON, FromJSON) | ||
49 | import Data.Aeson.TH | 51 | import Data.Aeson.TH |
52 | import Data.BEncode as BE | ||
53 | import Data.BEncode.BDict as BE | ||
50 | import Data.Char as Char | 54 | import Data.Char as Char |
55 | import Data.List as L | ||
51 | import Data.Map as M | 56 | import Data.Map as M |
52 | import Data.Maybe | 57 | import Data.Maybe |
53 | import Data.List as L | ||
54 | import Data.Word | ||
55 | import Data.Monoid | 58 | import Data.Monoid |
56 | import Data.BEncode | 59 | import Data.Serialize as S hiding (Result) |
57 | import Data.Text (Text) | 60 | import Data.Text (Text) |
58 | import Data.Text.Encoding | 61 | import Data.Text.Encoding |
59 | import Data.Serialize hiding (Result) | 62 | import Data.Typeable |
60 | import Data.URLEncoded as URL | 63 | import Data.URLEncoded as URL |
61 | import Data.Torrent | 64 | import Data.Word |
62 | import Network | 65 | import Network |
63 | import Network.URI | 66 | import Network.URI |
64 | import Network.Socket | 67 | import Network.Socket |
65 | 68 | ||
66 | import Network.BitTorrent.Peer | 69 | import Data.Torrent.InfoHash |
70 | import Data.Torrent.Progress | ||
71 | import Network.BitTorrent.Core.PeerId | ||
72 | import Network.BitTorrent.Core.PeerAddr | ||
67 | 73 | ||
68 | {----------------------------------------------------------------------- | 74 | {----------------------------------------------------------------------- |
69 | Announce messages | 75 | Announce messages |
@@ -76,7 +82,7 @@ data Event = Started | |||
76 | -- ^ Sent when the peer is shutting down. | 82 | -- ^ Sent when the peer is shutting down. |
77 | | Completed | 83 | | Completed |
78 | -- ^ To be sent when the peer completes a download. | 84 | -- ^ To be sent when the peer completes a download. |
79 | deriving (Show, Read, Eq, Ord, Enum, Bounded) | 85 | deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable) |
80 | 86 | ||
81 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''Event) | 87 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''Event) |
82 | 88 | ||
@@ -111,38 +117,41 @@ data AnnounceQuery = AnnounceQuery { | |||
111 | 117 | ||
112 | , reqEvent :: Maybe Event | 118 | , reqEvent :: Maybe Event |
113 | -- ^ If not specified, the request is regular periodic request. | 119 | -- ^ If not specified, the request is regular periodic request. |
114 | } deriving Show | 120 | } deriving (Show, Typeable) |
115 | 121 | ||
116 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceQuery) | 122 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceQuery) |
117 | 123 | ||
124 | newtype PeerList = PeerList { getPeerList :: [PeerAddr] } | ||
125 | deriving (Show, Eq, ToJSON, FromJSON, Typeable) | ||
126 | |||
118 | -- | The tracker response includes a peer list that helps the client | 127 | -- | The tracker response includes a peer list that helps the client |
119 | -- participate in the torrent. The most important is 'respPeer' list | 128 | -- participate in the torrent. The most important is 'respPeer' list |
120 | -- used to join the swarm. | 129 | -- used to join the swarm. |
121 | -- | 130 | -- |
122 | data AnnounceInfo = | 131 | data AnnounceInfo = |
123 | Failure Text -- ^ Failure reason in human readable form. | 132 | Failure !Text -- ^ Failure reason in human readable form. |
124 | | AnnounceInfo { | 133 | | AnnounceInfo { |
125 | respWarning :: Maybe Text | 134 | -- | Number of peers completed the torrent. (seeders) |
126 | -- ^ Human readable warning. | 135 | respComplete :: !(Maybe Int) |
127 | 136 | ||
137 | -- | Number of peers downloading the torrent. (leechers) | ||
138 | , respIncomplete :: !(Maybe Int) | ||
139 | |||
140 | -- | Recommended interval to wait between requests. | ||
128 | , respInterval :: !Int | 141 | , respInterval :: !Int |
129 | -- ^ Recommended interval to wait between requests. | ||
130 | 142 | ||
131 | , respMinInterval :: Maybe Int | 143 | -- | Minimal amount of time between requests. A peer /should/ |
132 | -- ^ Minimal amount of time between requests. A peer /should/ | ||
133 | -- make timeout with at least 'respMinInterval' value, | 144 | -- make timeout with at least 'respMinInterval' value, |
134 | -- otherwise tracker might not respond. If not specified the | 145 | -- otherwise tracker might not respond. If not specified the |
135 | -- same applies to 'respInterval'. | 146 | -- same applies to 'respInterval'. |
147 | , respMinInterval :: !(Maybe Int) | ||
136 | 148 | ||
137 | , respComplete :: Maybe Int | 149 | -- | Peers that must be contacted. |
138 | -- ^ Number of peers completed the torrent. (seeders) | 150 | , respPeers :: !PeerList |
139 | |||
140 | , respIncomplete :: Maybe Int | ||
141 | -- ^ Number of peers downloading the torrent. (leechers) | ||
142 | 151 | ||
143 | , respPeers :: ![PeerAddr] | 152 | -- | Human readable warning. |
144 | -- ^ Peers that must be contacted. | 153 | , respWarning :: !(Maybe Text) |
145 | } deriving Show | 154 | } deriving (Show, Typeable) |
146 | 155 | ||
147 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceInfo) | 156 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''AnnounceInfo) |
148 | 157 | ||
@@ -165,32 +174,38 @@ defaultNumWant = 50 | |||
165 | Bencode announce encoding | 174 | Bencode announce encoding |
166 | -----------------------------------------------------------------------} | 175 | -----------------------------------------------------------------------} |
167 | 176 | ||
168 | instance BEncodable AnnounceInfo where | 177 | instance BEncode PeerList where |
169 | toBEncode (Failure t) = fromAssocs ["failure reason" --> t] | 178 | toBEncode (PeerList xs) = toBEncode xs |
170 | toBEncode AnnounceInfo {..} = fromAssocs | 179 | fromBEncode (BList l ) = PeerList <$> fromBEncode (BList l) |
171 | [ "interval" --> respInterval | 180 | fromBEncode (BString s ) = PeerList <$> runGet getCompactPeerList s |
172 | , "min interval" -->? respMinInterval | 181 | fromBEncode _ = decodingError "Peer list" |
173 | , "complete" -->? respComplete | 182 | |
174 | , "incomplete" -->? respIncomplete | 183 | -- | HTTP tracker protocol compatible encoding. |
175 | , "peers" --> respPeers | 184 | instance BEncode AnnounceInfo where |
176 | ] | 185 | toBEncode (Failure t) = toDict $ |
186 | "failure reason" .=! t | ||
187 | .: endDict | ||
188 | |||
189 | toBEncode AnnounceInfo {..} = toDict $ | ||
190 | "complete" .=? respComplete | ||
191 | .: "incomplete" .=? respIncomplete | ||
192 | .: "interval" .=! respInterval | ||
193 | .: "min interval" .=? respMinInterval | ||
194 | .: "peers" .=! respPeers | ||
195 | .: "warning message" .=? respWarning | ||
196 | .: endDict | ||
177 | 197 | ||
178 | fromBEncode (BDict d) | 198 | fromBEncode (BDict d) |
179 | | Just t <- M.lookup "failure reason" d = Failure <$> fromBEncode t | 199 | | Just t <- BE.lookup "failure reason" d = Failure <$> fromBEncode t |
180 | | otherwise = AnnounceInfo | 200 | | otherwise = (`fromDict` (BDict d)) $ do |
181 | <$> d >--? "warning message" | 201 | AnnounceInfo |
182 | <*> d >-- "interval" | 202 | <$>? "complete" |
183 | <*> d >--? "min interval" | 203 | <*>? "incomplete" |
184 | <*> d >--? "complete" | 204 | <*>! "interval" |
185 | <*> d >--? "incomplete" | 205 | <*>? "min interval" |
186 | <*> getPeers (M.lookup "peers" d) | 206 | <*>! "peers" |
187 | where | 207 | <*>? "warning message" |
188 | getPeers :: Maybe BEncode -> Result [PeerAddr] | 208 | fromBEncode _ = decodingError "Announce info" |
189 | getPeers (Just (BList l)) = fromBEncode (BList l) | ||
190 | getPeers (Just (BString s)) = runGet getCompactPeerList s | ||
191 | getPeers _ = decodingError "Peers" | ||
192 | |||
193 | fromBEncode _ = decodingError "AnnounceInfo" | ||
194 | 209 | ||
195 | instance URLShow PortNumber where | 210 | instance URLShow PortNumber where |
196 | urlShow = urlShow . fromEnum | 211 | urlShow = urlShow . fromEnum |
@@ -204,13 +219,25 @@ instance URLShow Event where | |||
204 | -- INVARIANT: this is always nonempty list | 219 | -- INVARIANT: this is always nonempty list |
205 | (x : xs) = show e | 220 | (x : xs) = show e |
206 | 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. | ||
207 | instance URLEncode AnnounceQuery where | 234 | instance URLEncode AnnounceQuery where |
208 | urlEncode AnnounceQuery {..} = mconcat | 235 | urlEncode AnnounceQuery {..} = mconcat |
209 | [ s "peer_id" %= reqPeerId | 236 | [ s "peer_id" %= reqPeerId |
210 | , s "port" %= reqPort | 237 | , s "port" %= reqPort |
211 | , s "uploaded" %= _uploaded reqProgress | 238 | , urlEncode reqProgress |
212 | , s "left" %= _left reqProgress | 239 | |
213 | , s "downloaded" %= _downloaded reqProgress | 240 | |
214 | , s "ip" %=? reqIP | 241 | , s "ip" %=? reqIP |
215 | , s "numwant" %=? reqNumWant | 242 | , s "numwant" %=? reqNumWant |
216 | , s "event" %=? reqEvent | 243 | , s "event" %=? reqEvent |
@@ -233,7 +260,7 @@ putEvent :: Putter (Maybe Event) | |||
233 | putEvent Nothing = putWord32be 0 | 260 | putEvent Nothing = putWord32be 0 |
234 | putEvent (Just e) = putWord32be (eventId e) | 261 | putEvent (Just e) = putWord32be (eventId e) |
235 | 262 | ||
236 | getEvent :: Get (Maybe Event) | 263 | getEvent :: S.Get (Maybe Event) |
237 | getEvent = do | 264 | getEvent = do |
238 | eid <- getWord32be | 265 | eid <- getWord32be |
239 | case eid of | 266 | case eid of |
@@ -243,7 +270,7 @@ getEvent = do | |||
243 | 3 -> return $ Just Stopped | 270 | 3 -> return $ Just Stopped |
244 | _ -> fail "unknown event id" | 271 | _ -> fail "unknown event id" |
245 | 272 | ||
246 | 273 | -- | UDP tracker protocol compatible encoding. | |
247 | instance Serialize AnnounceQuery where | 274 | instance Serialize AnnounceQuery where |
248 | put AnnounceQuery {..} = do | 275 | put AnnounceQuery {..} = do |
249 | put reqInfoHash | 276 | put reqInfoHash |
@@ -279,13 +306,14 @@ instance Serialize AnnounceQuery where | |||
279 | , reqEvent = ev | 306 | , reqEvent = ev |
280 | } | 307 | } |
281 | 308 | ||
309 | -- | UDP tracker protocol compatible encoding. | ||
282 | instance Serialize AnnounceInfo where | 310 | instance Serialize AnnounceInfo where |
283 | put (Failure msg) = put $ encodeUtf8 msg | 311 | put (Failure msg) = put $ encodeUtf8 msg |
284 | put AnnounceInfo {..} = do | 312 | put AnnounceInfo {..} = do |
285 | putWord32be $ fromIntegral respInterval | 313 | putWord32be $ fromIntegral respInterval |
286 | putWord32be $ fromIntegral $ fromMaybe 0 respIncomplete | 314 | putWord32be $ fromIntegral $ fromMaybe 0 respIncomplete |
287 | putWord32be $ fromIntegral $ fromMaybe 0 respComplete | 315 | putWord32be $ fromIntegral $ fromMaybe 0 respComplete |
288 | forM_ respPeers put | 316 | forM_ (getPeerList respPeers) put |
289 | 317 | ||
290 | get = do | 318 | get = do |
291 | interval <- getWord32be | 319 | interval <- getWord32be |
@@ -299,7 +327,7 @@ instance Serialize AnnounceInfo where | |||
299 | , respMinInterval = Nothing | 327 | , respMinInterval = Nothing |
300 | , respIncomplete = Just $ fromIntegral leechers | 328 | , respIncomplete = Just $ fromIntegral leechers |
301 | , respComplete = Just $ fromIntegral seeders | 329 | , respComplete = Just $ fromIntegral seeders |
302 | , respPeers = peers | 330 | , respPeers = PeerList peers |
303 | } | 331 | } |
304 | 332 | ||
305 | {----------------------------------------------------------------------- | 333 | {----------------------------------------------------------------------- |
@@ -322,7 +350,7 @@ data ScrapeInfo = ScrapeInfo { | |||
322 | -- | Name of the torrent file, as specified by the "name" | 350 | -- | Name of the torrent file, as specified by the "name" |
323 | -- file in the info section of the .torrent file. | 351 | -- file in the info section of the .torrent file. |
324 | , siName :: !(Maybe Text) | 352 | , siName :: !(Maybe Text) |
325 | } deriving (Show, Eq) | 353 | } deriving (Show, Eq, Typeable) |
326 | 354 | ||
327 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''ScrapeInfo) | 355 | $(deriveJSON (L.map toLower . L.dropWhile isLower) ''ScrapeInfo) |
328 | 356 | ||
@@ -330,21 +358,22 @@ $(deriveJSON (L.map toLower . L.dropWhile isLower) ''ScrapeInfo) | |||
330 | -- | Scrape info about a set of torrents. | 358 | -- | Scrape info about a set of torrents. |
331 | type Scrape = Map InfoHash ScrapeInfo | 359 | type Scrape = Map InfoHash ScrapeInfo |
332 | 360 | ||
333 | instance BEncodable ScrapeInfo where | 361 | -- | HTTP tracker protocol compatible encoding. |
334 | toBEncode ScrapeInfo {..} = fromAssocs | 362 | instance BEncode ScrapeInfo where |
335 | [ "complete" --> siComplete | 363 | toBEncode ScrapeInfo {..} = toDict $ |
336 | , "downloaded" --> siDownloaded | 364 | "complete" .=! siComplete |
337 | , "incomplete" --> siIncomplete | 365 | .: "downloaded" .=! siDownloaded |
338 | , "name" -->? siName | 366 | .: "incomplete" .=! siIncomplete |
339 | ] | 367 | .: "name" .=? siName |
340 | 368 | .: endDict | |
341 | fromBEncode (BDict d) = | 369 | |
342 | ScrapeInfo <$> d >-- "complete" | 370 | fromBEncode = fromDict $ do |
343 | <*> d >-- "downloaded" | 371 | ScrapeInfo <$>! "complete" |
344 | <*> d >-- "incomplete" | 372 | <*>! "downloaded" |
345 | <*> d >--? "name" | 373 | <*>! "incomplete" |
346 | fromBEncode _ = decodingError "ScrapeInfo" | 374 | <*>? "name" |
347 | 375 | ||
376 | -- | UDP tracker protocol complatble encoding. | ||
348 | instance Serialize ScrapeInfo where | 377 | instance Serialize ScrapeInfo where |
349 | put ScrapeInfo {..} = do | 378 | put ScrapeInfo {..} = do |
350 | putWord32be $ fromIntegral siComplete | 379 | putWord32be $ fromIntegral siComplete |