diff options
-rw-r--r-- | bittorrent.cabal | 8 | ||||
-rw-r--r-- | src/Data/Torrent/Block.hs | 11 | ||||
-rw-r--r-- | src/Data/Torrent/Layout.hs | 14 | ||||
-rw-r--r-- | src/Data/Torrent/Piece.hs | 7 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/RPC/Message.hs | 49 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/Wai.hs | 102 |
6 files changed, 167 insertions, 24 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal index 762bff01..c5a8a476 100644 --- a/bittorrent.cabal +++ b/bittorrent.cabal | |||
@@ -59,21 +59,21 @@ library | |||
59 | -- , Network.BitTorrent.DHT.Protocol | 59 | -- , Network.BitTorrent.DHT.Protocol |
60 | -- , Network.BitTorrent.DHT.Session | 60 | -- , Network.BitTorrent.DHT.Session |
61 | -- , Network.BitTorrent.Exchange | 61 | -- , Network.BitTorrent.Exchange |
62 | , Network.BitTorrent.Exchange.Assembler | 62 | -- , Network.BitTorrent.Exchange.Assembler |
63 | , Network.BitTorrent.Exchange.Message | 63 | -- , Network.BitTorrent.Exchange.Message |
64 | -- , Network.BitTorrent.Exchange.Session | 64 | -- , Network.BitTorrent.Exchange.Session |
65 | -- , Network.BitTorrent.Exchange.Status | 65 | -- , Network.BitTorrent.Exchange.Status |
66 | -- , Network.BitTorrent.Exchange.Wire | 66 | -- , Network.BitTorrent.Exchange.Wire |
67 | -- , Network.BitTorrent.Extension | 67 | -- , Network.BitTorrent.Extension |
68 | -- , Network.BitTorrent.Tracker | 68 | -- , Network.BitTorrent.Tracker |
69 | -- , Network.BitTorrent.Tracker.RPC | 69 | -- , Network.BitTorrent.Tracker.RPC |
70 | , Network.BitTorrent.Tracker.Wai | ||
70 | , Network.BitTorrent.Tracker.RPC.Message | 71 | , Network.BitTorrent.Tracker.RPC.Message |
71 | , Network.BitTorrent.Tracker.RPC.HTTP | 72 | , Network.BitTorrent.Tracker.RPC.HTTP |
72 | , Network.BitTorrent.Tracker.RPC.UDP | 73 | , Network.BitTorrent.Tracker.RPC.UDP |
73 | -- , Network.BitTorrent.Tracker.Session | 74 | -- , Network.BitTorrent.Tracker.Session |
74 | -- , Network.BitTorrent.Session | 75 | -- , Network.BitTorrent.Session |
75 | -- , Network.BitTorrent.Session.Types | 76 | -- , Network.BitTorrent.Session.Types |
76 | -- , System.IO.MMap.Fixed | ||
77 | -- , System.Torrent.Storage | 77 | -- , System.Torrent.Storage |
78 | other-modules: Paths_bittorrent | 78 | other-modules: Paths_bittorrent |
79 | 79 | ||
@@ -86,7 +86,6 @@ library | |||
86 | -- Control | 86 | -- Control |
87 | , deepseq | 87 | , deepseq |
88 | , lens | 88 | , lens |
89 | -- , mtl | ||
90 | , resourcet | 89 | , resourcet |
91 | -- , transformers | 90 | -- , transformers |
92 | 91 | ||
@@ -131,6 +130,7 @@ library | |||
131 | , network >= 2.4 | 130 | , network >= 2.4 |
132 | , http-types >= 0.7 | 131 | , http-types >= 0.7 |
133 | , http-conduit | 132 | , http-conduit |
133 | , wai | ||
134 | -- , krpc | 134 | -- , krpc |
135 | 135 | ||
136 | -- System | 136 | -- System |
diff --git a/src/Data/Torrent/Block.hs b/src/Data/Torrent/Block.hs index fd12b7a0..089217fa 100644 --- a/src/Data/Torrent/Block.hs +++ b/src/Data/Torrent/Block.hs | |||
@@ -7,9 +7,11 @@ | |||
7 | -- | 7 | -- |
8 | -- Blocks are used to transfer pieces. | 8 | -- Blocks are used to transfer pieces. |
9 | -- | 9 | -- |
10 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
11 | {-# LANGUAGE TemplateHaskell #-} | ||
12 | {-# LANGUAGE FlexibleInstances #-} | 10 | {-# LANGUAGE FlexibleInstances #-} |
11 | {-# LANGUAGE TemplateHaskell #-} | ||
12 | {-# LANGUAGE DeriveFunctor #-} | ||
13 | {-# LANGUAGE DeriveDataTypeable #-} | ||
14 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
13 | module Data.Torrent.Block | 15 | module Data.Torrent.Block |
14 | ( -- * Piece attributes | 16 | ( -- * Piece attributes |
15 | PieceIx | 17 | PieceIx |
@@ -38,6 +40,7 @@ import qualified Data.ByteString.Lazy as Lazy | |||
38 | import Data.Char | 40 | import Data.Char |
39 | import Data.List as L | 41 | import Data.List as L |
40 | import Data.Serialize as S | 42 | import Data.Serialize as S |
43 | import Data.Typeable | ||
41 | import Text.PrettyPrint | 44 | import Text.PrettyPrint |
42 | import Text.PrettyPrint.Class | 45 | import Text.PrettyPrint.Class |
43 | 46 | ||
@@ -95,7 +98,7 @@ data BlockIx = BlockIx { | |||
95 | 98 | ||
96 | -- | Block size starting from offset. | 99 | -- | Block size starting from offset. |
97 | , ixLength :: {-# UNPACK #-} !BlockSize | 100 | , ixLength :: {-# UNPACK #-} !BlockSize |
98 | } deriving (Show, Eq) | 101 | } deriving (Show, Eq, Typeable) |
99 | 102 | ||
100 | $(deriveJSON defaultOptions { fieldLabelModifier = (L.map toLower . L.dropWhile isLower) } ''BlockIx) | 103 | $(deriveJSON defaultOptions { fieldLabelModifier = (L.map toLower . L.dropWhile isLower) } ''BlockIx) |
101 | 104 | ||
@@ -148,7 +151,7 @@ data Block payload = Block { | |||
148 | 151 | ||
149 | -- | Payload bytes. | 152 | -- | Payload bytes. |
150 | , blkData :: !payload | 153 | , blkData :: !payload |
151 | } deriving (Show, Eq) | 154 | } deriving (Show, Eq, Functor, Typeable) |
152 | 155 | ||
153 | -- | Payload is ommitted. | 156 | -- | Payload is ommitted. |
154 | instance Pretty (Block Lazy.ByteString) where | 157 | instance Pretty (Block Lazy.ByteString) where |
diff --git a/src/Data/Torrent/Layout.hs b/src/Data/Torrent/Layout.hs index d39c6c04..c1e26d48 100644 --- a/src/Data/Torrent/Layout.hs +++ b/src/Data/Torrent/Layout.hs | |||
@@ -50,7 +50,7 @@ module Data.Torrent.Layout | |||
50 | , blockCount | 50 | , blockCount |
51 | 51 | ||
52 | -- * Flat file layout | 52 | -- * Flat file layout |
53 | , Layout | 53 | , FileLayout |
54 | , flatLayout | 54 | , flatLayout |
55 | , accumOffsets | 55 | , accumOffsets |
56 | , fileOffset | 56 | , fileOffset |
@@ -286,13 +286,13 @@ blockCount blkSize ci = contentLength ci `sizeInBase` blkSize | |||
286 | -- coalesce all the files in the given order to get the linear block | 286 | -- coalesce all the files in the given order to get the linear block |
287 | -- address space. | 287 | -- address space. |
288 | -- | 288 | -- |
289 | type Layout a = [(FilePath, a)] | 289 | type FileLayout a = [(FilePath, a)] |
290 | 290 | ||
291 | -- | Extract files layout from torrent info with the given root path. | 291 | -- | Extract files layout from torrent info with the given root path. |
292 | flatLayout | 292 | flatLayout |
293 | :: FilePath -- ^ Root path for the all torrent files. | 293 | :: FilePath -- ^ Root path for the all torrent files. |
294 | -> LayoutInfo -- ^ Torrent content information. | 294 | -> LayoutInfo -- ^ Torrent content information. |
295 | -> Layout FileSize -- ^ The all file paths prefixed with the given root. | 295 | -> FileLayout FileSize -- ^ The all file paths prefixed with the given root. |
296 | flatLayout prefixPath SingleFile { liFile = FileInfo {..} } | 296 | flatLayout prefixPath SingleFile { liFile = FileInfo {..} } |
297 | = [(prefixPath </> BC.unpack fiName, fiLength)] | 297 | = [(prefixPath </> BC.unpack fiName, fiLength)] |
298 | flatLayout prefixPath MultiFile {..} = L.map mkPath liFiles | 298 | flatLayout prefixPath MultiFile {..} = L.map mkPath liFiles |
@@ -303,14 +303,14 @@ flatLayout prefixPath MultiFile {..} = L.map mkPath liFiles | |||
303 | </> joinPath (L.map BC.unpack fiName) | 303 | </> joinPath (L.map BC.unpack fiName) |
304 | 304 | ||
305 | -- | Calculate offset of each file based on its length, incrementally. | 305 | -- | Calculate offset of each file based on its length, incrementally. |
306 | accumOffsets :: Layout FileSize -> Layout FileOffset | 306 | accumOffsets :: FileLayout FileSize -> FileLayout FileOffset |
307 | accumOffsets = go 0 | 307 | accumOffsets = go 0 |
308 | where | 308 | where |
309 | go !_ [] = [] | 309 | go !_ [] = [] |
310 | go !offset ((n, s) : xs) = (n, offset) : go (offset + s) xs | 310 | go !offset ((n, s) : xs) = (n, offset) : go (offset + s) xs |
311 | 311 | ||
312 | -- | Gives global offset of a content file for a given full path. | 312 | -- | Gives global offset of a content file for a given full path. |
313 | fileOffset :: FilePath -> Layout FileOffset -> Maybe FileOffset | 313 | fileOffset :: FilePath -> FileLayout FileOffset -> Maybe FileOffset |
314 | fileOffset = lookup | 314 | fileOffset = lookup |
315 | {-# INLINE fileOffset #-} | 315 | {-# INLINE fileOffset #-} |
316 | 316 | ||
diff --git a/src/Data/Torrent/Piece.hs b/src/Data/Torrent/Piece.hs index f7c6257b..31680ce8 100644 --- a/src/Data/Torrent/Piece.hs +++ b/src/Data/Torrent/Piece.hs | |||
@@ -7,8 +7,9 @@ | |||
7 | -- | 7 | -- |
8 | -- Pieces are used to validate torrent content. | 8 | -- Pieces are used to validate torrent content. |
9 | -- | 9 | -- |
10 | {-# LANGUAGE TemplateHaskell #-} | 10 | {-# LANGUAGE TemplateHaskell #-} |
11 | {-# LANGUAGE DeriveDataTypeable #-} | 11 | {-# LANGUAGE DeriveDataTypeable #-} |
12 | {-# LANGUAGE DeriveFunctor #-} | ||
12 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 13 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
13 | module Data.Torrent.Piece | 14 | module Data.Torrent.Piece |
14 | ( -- * Piece attributes | 15 | ( -- * Piece attributes |
@@ -115,7 +116,7 @@ data Piece a = Piece | |||
115 | 116 | ||
116 | -- | Payload. | 117 | -- | Payload. |
117 | , pieceData :: !a | 118 | , pieceData :: !a |
118 | } deriving (Show, Read, Eq, Typeable) | 119 | } deriving (Show, Read, Eq, Functor, Typeable) |
119 | 120 | ||
120 | $(deriveJSON defaultOptions { fieldLabelModifier = (L.map toLower . L.dropWhile isLower) } ''Piece) | 121 | $(deriveJSON defaultOptions { fieldLabelModifier = (L.map toLower . L.dropWhile isLower) } ''Piece) |
121 | 122 | ||
diff --git a/src/Network/BitTorrent/Tracker/RPC/Message.hs b/src/Network/BitTorrent/Tracker/RPC/Message.hs index 213f1e83..3900ff64 100644 --- a/src/Network/BitTorrent/Tracker/RPC/Message.hs +++ b/src/Network/BitTorrent/Tracker/RPC/Message.hs | |||
@@ -32,10 +32,18 @@ module Network.BitTorrent.Tracker.RPC.Message | |||
32 | , ParamParseFailure | 32 | , ParamParseFailure |
33 | , parseAnnounceQuery | 33 | , parseAnnounceQuery |
34 | 34 | ||
35 | -- ** Request | ||
36 | , AnnounceQueryExt (..) | ||
37 | , AnnounceRequest (..) | ||
38 | , parseAnnounceRequest | ||
39 | , renderAnnounceRequest | ||
40 | |||
35 | -- ** Info | 41 | -- ** Info |
36 | , PeerList (..) | 42 | , PeerList (..) |
37 | , AnnounceInfo(..) | 43 | , AnnounceInfo(..) |
38 | , defaultNumWant | 44 | , defaultNumWant |
45 | , defaultMaxNumWant | ||
46 | , defaultReannounceInterval | ||
39 | , parseFailureStatus | 47 | , parseFailureStatus |
40 | 48 | ||
41 | -- * Scrape | 49 | -- * Scrape |
@@ -47,6 +55,9 @@ module Network.BitTorrent.Tracker.RPC.Message | |||
47 | -- ** Info | 55 | -- ** Info |
48 | , ScrapeEntry (..) | 56 | , ScrapeEntry (..) |
49 | , ScrapeInfo | 57 | , ScrapeInfo |
58 | |||
59 | -- ** Extra | ||
60 | , queryToSimpleQuery | ||
50 | ) | 61 | ) |
51 | where | 62 | where |
52 | 63 | ||
@@ -223,15 +234,15 @@ instance QueryLike AnnounceQuery where | |||
223 | , ("event" , toQueryValue reqEvent) | 234 | , ("event" , toQueryValue reqEvent) |
224 | ] | 235 | ] |
225 | 236 | ||
226 | filterMaybes :: [(a, Maybe b)] -> [(a, b)] | 237 | queryToSimpleQuery :: Query -> SimpleQuery |
227 | filterMaybes = catMaybes . L.map f | 238 | queryToSimpleQuery = catMaybes . L.map f |
228 | where | 239 | where |
229 | f (_, Nothing) = Nothing | 240 | f (_, Nothing) = Nothing |
230 | f (a, Just b ) = Just (a, b) | 241 | f (a, Just b ) = Just (a, b) |
231 | 242 | ||
232 | -- | Encode announce query and add it to the base tracker URL. | 243 | -- | Encode announce query and add it to the base tracker URL. |
233 | renderAnnounceQuery :: AnnounceQuery -> SimpleQuery | 244 | renderAnnounceQuery :: AnnounceQuery -> SimpleQuery |
234 | renderAnnounceQuery = filterMaybes . toQuery | 245 | renderAnnounceQuery = queryToSimpleQuery . toQuery |
235 | 246 | ||
236 | data QueryParam | 247 | data QueryParam |
237 | = ParamInfoHash | 248 | = ParamInfoHash |
@@ -320,8 +331,27 @@ parseAnnounceQuery params = AnnounceQuery | |||
320 | <*> optParam ParamNumWant params | 331 | <*> optParam ParamNumWant params |
321 | <*> optParam ParamEvent params | 332 | <*> optParam ParamEvent params |
322 | 333 | ||
323 | -- TODO add extension datatype | 334 | data AnnounceQueryExt = AnnounceQueryExt |
324 | --type AnnounceRequest = () | 335 | { extCompact :: Maybe Bool -- | "compact" param |
336 | , extNoPeerId :: Maybe Bool -- | "no_peer_id" param | ||
337 | } deriving (Show, Eq, Typeable) | ||
338 | |||
339 | parseAnnounceQueryExt :: SimpleQuery -> AnnounceQueryExt | ||
340 | parseAnnounceQueryExt = undefined | ||
341 | |||
342 | renderAnnounceQueryExt :: AnnounceQueryExt -> SimpleQuery | ||
343 | renderAnnounceQueryExt = undefined | ||
344 | |||
345 | data AnnounceRequest = AnnounceRequest | ||
346 | { announceQuery :: AnnounceQuery | ||
347 | , announceAdvises :: AnnounceQueryExt | ||
348 | } deriving (Show, Eq, Typeable) | ||
349 | |||
350 | parseAnnounceRequest :: SimpleQuery -> Either ParamParseFailure AnnounceRequest | ||
351 | parseAnnounceRequest = undefined | ||
352 | |||
353 | renderAnnounceRequest :: AnnounceRequest -> SimpleQuery | ||
354 | renderAnnounceRequest = undefined | ||
325 | 355 | ||
326 | {----------------------------------------------------------------------- | 356 | {----------------------------------------------------------------------- |
327 | -- Announce response | 357 | -- Announce response |
@@ -449,6 +479,13 @@ instance Serialize AnnounceInfo where | |||
449 | defaultNumWant :: Int | 479 | defaultNumWant :: Int |
450 | defaultNumWant = 50 | 480 | defaultNumWant = 50 |
451 | 481 | ||
482 | defaultMaxNumWant :: Int | ||
483 | defaultMaxNumWant = 200 | ||
484 | |||
485 | defaultReannounceInterval :: Int | ||
486 | defaultReannounceInterval = 30 * 60 | ||
487 | |||
488 | |||
452 | missingOffset :: Int | 489 | missingOffset :: Int |
453 | missingOffset = 101 | 490 | missingOffset = 101 |
454 | 491 | ||
@@ -500,7 +537,7 @@ isScrapeParam :: BS.ByteString -> Bool | |||
500 | isScrapeParam = (==) scrapeParam | 537 | isScrapeParam = (==) scrapeParam |
501 | 538 | ||
502 | renderScrapeQuery :: ScrapeQuery -> SimpleQuery | 539 | renderScrapeQuery :: ScrapeQuery -> SimpleQuery |
503 | renderScrapeQuery = filterMaybes . L.map mkPair | 540 | renderScrapeQuery = queryToSimpleQuery . L.map mkPair |
504 | where | 541 | where |
505 | mkPair ih = (scrapeParam, toQueryValue ih) | 542 | mkPair ih = (scrapeParam, toQueryValue ih) |
506 | 543 | ||
diff --git a/src/Network/BitTorrent/Tracker/Wai.hs b/src/Network/BitTorrent/Tracker/Wai.hs new file mode 100644 index 00000000..f290c380 --- /dev/null +++ b/src/Network/BitTorrent/Tracker/Wai.hs | |||
@@ -0,0 +1,102 @@ | |||
1 | -- supported extensions: | ||
2 | -- | ||
3 | -- no_peer_id - do not send peer id if no_peer_id=1 specified | ||
4 | -- http://www.bittorrent.org/beps/bep_0023.html | ||
5 | -- | ||
6 | -- compact - compact=1 or compact=0 | ||
7 | -- http://permalink.gmane.org/gmane.network.bit-torrent.general/4030 | ||
8 | -- | ||
9 | -- | ||
10 | {-# LANGUAGE RecordWildCards #-} | ||
11 | module Network.BitTorrent.Tracker.Wai | ||
12 | ( tracker | ||
13 | ) where | ||
14 | |||
15 | import Control.Monad.Trans.Resource | ||
16 | import Data.BEncode as BE | ||
17 | import Data.ByteString | ||
18 | import Data.Default | ||
19 | import Data.List as L | ||
20 | import Network.HTTP.Types | ||
21 | import Network.Wai | ||
22 | |||
23 | import Data.Torrent.Progress | ||
24 | import Network.BitTorrent.Core.PeerId | ||
25 | import Network.BitTorrent.Core.PeerAddr | ||
26 | import Network.BitTorrent.Tracker.RPC.Message | ||
27 | |||
28 | |||
29 | data TrackerSettings = TrackerSettings | ||
30 | { -- | If peer did not specified the "numwant" then this value is | ||
31 | -- used. | ||
32 | defNumWant :: {-# UNPACK #-} !Int | ||
33 | |||
34 | -- | If peer specified to big numwant value. | ||
35 | , maxNumWant :: {-# UNPACK #-} !Int | ||
36 | |||
37 | -- | Recommended time interval to wait between regular announce | ||
38 | -- requests. | ||
39 | , reannounceInterval :: {-# UNPACK #-} !Int | ||
40 | |||
41 | -- | Minimum time interval to wait between regular announce | ||
42 | -- requests. | ||
43 | , reannounceMinInterval :: !(Maybe Int) | ||
44 | |||
45 | -- | Whether to send count of seeders. | ||
46 | , completePeers :: !Bool | ||
47 | |||
48 | -- | Whether to send count of leechers. | ||
49 | , incompletePeers :: !Bool | ||
50 | |||
51 | -- | Do not send peer id in response. Peer can override this value | ||
52 | -- by setting "no_peer_id" to 0 or 1. | ||
53 | , noPeerId :: !Bool | ||
54 | |||
55 | -- | Whether to send compact peer list. Peer can override this | ||
56 | -- value by setting "compact" to 0 or 1. | ||
57 | , compactPeerList :: !Bool | ||
58 | } | ||
59 | |||
60 | instance Default TrackerSettings where | ||
61 | def = TrackerSettings | ||
62 | { defNumWant = defaultNumWant | ||
63 | , maxNumWant = defaultMaxNumWant | ||
64 | , reannounceInterval = defaultReannounceInterval | ||
65 | , reannounceMinInterval = Nothing | ||
66 | , compactPeerList = False | ||
67 | , completePeers = False | ||
68 | , incompletePeers = False | ||
69 | , noPeerId = False | ||
70 | } | ||
71 | |||
72 | getAnnounceR :: AnnounceRequest -> ResourceT IO AnnounceInfo | ||
73 | getAnnounceR = undefined | ||
74 | |||
75 | getScrapeR :: ScrapeQuery -> ResourceT IO ScrapeInfo | ||
76 | getScrapeR = undefined | ||
77 | |||
78 | -- content-type: "text/plain" ? | ||
79 | tracker :: Application | ||
80 | tracker Request {..} | ||
81 | | requestMethod /= methodGet | ||
82 | = return $ responseLBS methodNotAllowed405 [] "" | ||
83 | |||
84 | | otherwise = do | ||
85 | case pathInfo of | ||
86 | ["announce"] -> | ||
87 | case parseAnnounceRequest $ queryToSimpleQuery queryString of | ||
88 | Right query -> do | ||
89 | info <- getAnnounceR query | ||
90 | return $ responseLBS ok200 [] $ BE.encode info | ||
91 | Left msg -> | ||
92 | return $ responseLBS (parseFailureStatus msg) [] "" | ||
93 | |||
94 | ["scrape"] -> | ||
95 | case Right $ parseScrapeQuery $ queryToSimpleQuery queryString of -- TODO | ||
96 | Right query -> do | ||
97 | info <- getScrapeR query | ||
98 | return $ responseLBS ok200 [] $ BE.encode info | ||
99 | Left _ -> | ||
100 | return $ responseLBS badRequest400 [] "" | ||
101 | |||
102 | _ -> undefined --badPath | ||