summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Tracker
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Tracker')
-rw-r--r--src/Network/BitTorrent/Tracker/Protocol.hs167
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 #-}
25module Network.BitTorrent.Tracker.Protocol 26module Network.BitTorrent.Tracker.Protocol
26 ( -- * Announce 27 ( -- * Announce
@@ -46,24 +47,29 @@ module Network.BitTorrent.Tracker.Protocol
46import Control.Applicative 47import Control.Applicative
47import Control.Exception 48import Control.Exception
48import Control.Monad 49import Control.Monad
50import Data.Aeson (ToJSON, FromJSON)
49import Data.Aeson.TH 51import Data.Aeson.TH
52import Data.BEncode as BE
53import Data.BEncode.BDict as BE
50import Data.Char as Char 54import Data.Char as Char
55import Data.List as L
51import Data.Map as M 56import Data.Map as M
52import Data.Maybe 57import Data.Maybe
53import Data.List as L
54import Data.Word
55import Data.Monoid 58import Data.Monoid
56import Data.BEncode 59import Data.Serialize as S hiding (Result)
57import Data.Text (Text) 60import Data.Text (Text)
58import Data.Text.Encoding 61import Data.Text.Encoding
59import Data.Serialize hiding (Result) 62import Data.Typeable
60import Data.URLEncoded as URL 63import Data.URLEncoded as URL
61import Data.Torrent 64import Data.Word
62import Network 65import Network
63import Network.URI 66import Network.URI
64import Network.Socket 67import Network.Socket
65 68
66import Network.BitTorrent.Peer 69import Data.Torrent.InfoHash
70import Data.Torrent.Progress
71import Network.BitTorrent.Core.PeerId
72import 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
124newtype 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--
122data AnnounceInfo = 131data 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
168instance BEncodable AnnounceInfo where 177instance 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 184instance 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
195instance URLShow PortNumber where 210instance 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
222instance URLShow Word64 where
223 urlShow = show
224
225instance 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.
207instance URLEncode AnnounceQuery where 234instance 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)
233putEvent Nothing = putWord32be 0 260putEvent Nothing = putWord32be 0
234putEvent (Just e) = putWord32be (eventId e) 261putEvent (Just e) = putWord32be (eventId e)
235 262
236getEvent :: Get (Maybe Event) 263getEvent :: S.Get (Maybe Event)
237getEvent = do 264getEvent = 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.
247instance Serialize AnnounceQuery where 274instance 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.
282instance Serialize AnnounceInfo where 310instance 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.
331type Scrape = Map InfoHash ScrapeInfo 359type Scrape = Map InfoHash ScrapeInfo
332 360
333instance BEncodable ScrapeInfo where 361-- | HTTP tracker protocol compatible encoding.
334 toBEncode ScrapeInfo {..} = fromAssocs 362instance 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.
348instance Serialize ScrapeInfo where 377instance Serialize ScrapeInfo where
349 put ScrapeInfo {..} = do 378 put ScrapeInfo {..} = do
350 putWord32be $ fromIntegral siComplete 379 putWord32be $ fromIntegral siComplete