diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2014-03-15 20:37:11 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2014-03-15 20:37:11 +0400 |
commit | 442a7c0941b4f2659988193404263348593551a2 (patch) | |
tree | b22870011b7a27a508f1500670bf7513f34df3b2 | |
parent | e2c997424da60cae727a934fd9627f00ec1be0fd (diff) |
Expose UDP tracker specific message types
Those can be used to implement UDP tracker server by third party
libraries or projects.
-rw-r--r-- | src/Network/BitTorrent/Tracker/Message.hs | 163 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/RPC/HTTP.hs | 2 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/RPC/UDP.hs | 147 | ||||
-rw-r--r-- | src/Network/BitTorrent/Tracker/Session.hs | 12 |
4 files changed, 169 insertions, 155 deletions
diff --git a/src/Network/BitTorrent/Tracker/Message.hs b/src/Network/BitTorrent/Tracker/Message.hs index edafdaba..8131ecf0 100644 --- a/src/Network/BitTorrent/Tracker/Message.hs +++ b/src/Network/BitTorrent/Tracker/Message.hs | |||
@@ -25,6 +25,7 @@ | |||
25 | {-# LANGUAGE DeriveDataTypeable #-} | 25 | {-# LANGUAGE DeriveDataTypeable #-} |
26 | {-# LANGUAGE DeriveFunctor #-} | 26 | {-# LANGUAGE DeriveFunctor #-} |
27 | {-# LANGUAGE ScopedTypeVariables #-} | 27 | {-# LANGUAGE ScopedTypeVariables #-} |
28 | {-# LANGUAGE TypeFamilies #-} | ||
28 | {-# OPTIONS -fno-warn-orphans #-} | 29 | {-# OPTIONS -fno-warn-orphans #-} |
29 | module Network.BitTorrent.Tracker.Message | 30 | module Network.BitTorrent.Tracker.Message |
30 | ( -- * Announce | 31 | ( -- * Announce |
@@ -76,6 +77,21 @@ module Network.BitTorrent.Tracker.Message | |||
76 | 77 | ||
77 | -- ** Extra | 78 | -- ** Extra |
78 | , queryToSimpleQuery | 79 | , queryToSimpleQuery |
80 | |||
81 | -- * UDP specific | ||
82 | -- ** Connection | ||
83 | , ConnectionId | ||
84 | , initialConnectionId | ||
85 | |||
86 | -- ** Messages | ||
87 | , Request (..) | ||
88 | , Response (..) | ||
89 | , responseName | ||
90 | |||
91 | -- ** Transaction | ||
92 | , genTransactionId | ||
93 | , TransactionId | ||
94 | , Transaction (..) | ||
79 | ) | 95 | ) |
80 | where | 96 | where |
81 | 97 | ||
@@ -104,7 +120,9 @@ import Network | |||
104 | import Network.HTTP.Types.QueryLike | 120 | import Network.HTTP.Types.QueryLike |
105 | import Network.HTTP.Types.URI hiding (urlEncode) | 121 | import Network.HTTP.Types.URI hiding (urlEncode) |
106 | import Network.HTTP.Types.Status | 122 | import Network.HTTP.Types.Status |
107 | import Network.Socket | 123 | import Network.Socket hiding (Connected) |
124 | import Numeric | ||
125 | import System.Entropy | ||
108 | import Text.Read (readMaybe) | 126 | import Text.Read (readMaybe) |
109 | 127 | ||
110 | import Data.Torrent.InfoHash | 128 | import Data.Torrent.InfoHash |
@@ -765,3 +783,146 @@ scrapeType = "text/plain" | |||
765 | -- | 783 | -- |
766 | parseFailureStatus :: ParamParseFailure -> Status | 784 | parseFailureStatus :: ParamParseFailure -> Status |
767 | parseFailureStatus = mkStatus <$> parseFailureCode <*> parseFailureMessage | 785 | parseFailureStatus = mkStatus <$> parseFailureCode <*> parseFailureMessage |
786 | |||
787 | {----------------------------------------------------------------------- | ||
788 | -- UDP specific message types | ||
789 | -----------------------------------------------------------------------} | ||
790 | |||
791 | genToken :: IO Word64 | ||
792 | genToken = do | ||
793 | bs <- getEntropy 8 | ||
794 | either err return $ runGet getWord64be bs | ||
795 | where | ||
796 | err = error "genToken: impossible happen" | ||
797 | |||
798 | -- | Connection Id is used for entire tracker session. | ||
799 | newtype ConnectionId = ConnectionId Word64 | ||
800 | deriving (Eq, Serialize) | ||
801 | |||
802 | instance Show ConnectionId where | ||
803 | showsPrec _ (ConnectionId cid) = showString "0x" <> showHex cid | ||
804 | |||
805 | initialConnectionId :: ConnectionId | ||
806 | initialConnectionId = ConnectionId 0x41727101980 | ||
807 | |||
808 | -- | Transaction Id is used within a UDP RPC. | ||
809 | newtype TransactionId = TransactionId Word32 | ||
810 | deriving (Eq, Ord, Enum, Bounded, Serialize) | ||
811 | |||
812 | instance Show TransactionId where | ||
813 | showsPrec _ (TransactionId tid) = showString "0x" <> showHex tid | ||
814 | |||
815 | genTransactionId :: IO TransactionId | ||
816 | genTransactionId = (TransactionId . fromIntegral) <$> genToken | ||
817 | |||
818 | data Request | ||
819 | = Connect | ||
820 | | Announce AnnounceQuery | ||
821 | | Scrape ScrapeQuery | ||
822 | deriving Show | ||
823 | |||
824 | data Response | ||
825 | = Connected ConnectionId | ||
826 | | Announced AnnounceInfo | ||
827 | | Scraped [ScrapeEntry] | ||
828 | | Failed Text | ||
829 | deriving Show | ||
830 | |||
831 | responseName :: Response -> String | ||
832 | responseName (Connected _) = "connected" | ||
833 | responseName (Announced _) = "announced" | ||
834 | responseName (Scraped _) = "scraped" | ||
835 | responseName (Failed _) = "failed" | ||
836 | |||
837 | data family Transaction a | ||
838 | data instance Transaction Request = TransactionQ | ||
839 | { connIdQ :: {-# UNPACK #-} !ConnectionId | ||
840 | , transIdQ :: {-# UNPACK #-} !TransactionId | ||
841 | , request :: !Request | ||
842 | } deriving Show | ||
843 | data instance Transaction Response = TransactionR | ||
844 | { transIdR :: {-# UNPACK #-} !TransactionId | ||
845 | , response :: !Response | ||
846 | } deriving Show | ||
847 | |||
848 | -- TODO newtype | ||
849 | newtype MessageId = MessageId Word32 | ||
850 | deriving (Show, Eq, Num, Serialize) | ||
851 | |||
852 | connectId, announceId, scrapeId, errorId :: MessageId | ||
853 | connectId = 0 | ||
854 | announceId = 1 | ||
855 | scrapeId = 2 | ||
856 | errorId = 3 | ||
857 | |||
858 | instance Serialize (Transaction Request) where | ||
859 | put TransactionQ {..} = do | ||
860 | case request of | ||
861 | Connect -> do | ||
862 | put initialConnectionId | ||
863 | put connectId | ||
864 | put transIdQ | ||
865 | |||
866 | Announce ann -> do | ||
867 | put connIdQ | ||
868 | put announceId | ||
869 | put transIdQ | ||
870 | put ann | ||
871 | |||
872 | Scrape hashes -> do | ||
873 | put connIdQ | ||
874 | put scrapeId | ||
875 | put transIdQ | ||
876 | forM_ hashes put | ||
877 | |||
878 | get = do | ||
879 | cid <- get | ||
880 | mid <- get | ||
881 | TransactionQ cid <$> S.get <*> getBody mid | ||
882 | where | ||
883 | getBody :: MessageId -> S.Get Request | ||
884 | getBody msgId | ||
885 | | msgId == connectId = pure Connect | ||
886 | | msgId == announceId = Announce <$> get | ||
887 | | msgId == scrapeId = Scrape <$> many get | ||
888 | | otherwise = fail errMsg | ||
889 | where | ||
890 | errMsg = "unknown request: " ++ show msgId | ||
891 | |||
892 | instance Serialize (Transaction Response) where | ||
893 | put TransactionR {..} = do | ||
894 | case response of | ||
895 | Connected conn -> do | ||
896 | put connectId | ||
897 | put transIdR | ||
898 | put conn | ||
899 | |||
900 | Announced info -> do | ||
901 | put announceId | ||
902 | put transIdR | ||
903 | put info | ||
904 | |||
905 | Scraped infos -> do | ||
906 | put scrapeId | ||
907 | put transIdR | ||
908 | forM_ infos put | ||
909 | |||
910 | Failed info -> do | ||
911 | put errorId | ||
912 | put transIdR | ||
913 | put (encodeUtf8 info) | ||
914 | |||
915 | |||
916 | get = do | ||
917 | mid <- get | ||
918 | TransactionR <$> get <*> getBody mid | ||
919 | where | ||
920 | getBody :: MessageId -> S.Get Response | ||
921 | getBody msgId | ||
922 | | msgId == connectId = Connected <$> get | ||
923 | | msgId == announceId = Announced <$> get | ||
924 | | msgId == scrapeId = Scraped <$> many get | ||
925 | | msgId == errorId = (Failed . decodeUtf8) <$> get | ||
926 | | otherwise = fail msg | ||
927 | where | ||
928 | msg = "unknown response: " ++ show msgId | ||
diff --git a/src/Network/BitTorrent/Tracker/RPC/HTTP.hs b/src/Network/BitTorrent/Tracker/RPC/HTTP.hs index cc5bd318..4a8e5f79 100644 --- a/src/Network/BitTorrent/Tracker/RPC/HTTP.hs +++ b/src/Network/BitTorrent/Tracker/RPC/HTTP.hs | |||
@@ -49,7 +49,7 @@ import Network.HTTP.Types.URI (SimpleQuery, renderSimpleQuery) | |||
49 | 49 | ||
50 | import Data.Torrent.InfoHash (InfoHash) | 50 | import Data.Torrent.InfoHash (InfoHash) |
51 | import Network.BitTorrent.Core.Fingerprint (libUserAgent) | 51 | import Network.BitTorrent.Core.Fingerprint (libUserAgent) |
52 | import Network.BitTorrent.Tracker.Message | 52 | import Network.BitTorrent.Tracker.Message hiding (Request, Response) |
53 | 53 | ||
54 | {----------------------------------------------------------------------- | 54 | {----------------------------------------------------------------------- |
55 | -- Exceptions | 55 | -- Exceptions |
diff --git a/src/Network/BitTorrent/Tracker/RPC/UDP.hs b/src/Network/BitTorrent/Tracker/RPC/UDP.hs index 0c9c3367..35e8b7b6 100644 --- a/src/Network/BitTorrent/Tracker/RPC/UDP.hs +++ b/src/Network/BitTorrent/Tracker/RPC/UDP.hs | |||
@@ -15,7 +15,6 @@ | |||
15 | {-# LANGUAGE FlexibleInstances #-} | 15 | {-# LANGUAGE FlexibleInstances #-} |
16 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 16 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
17 | {-# LANGUAGE DeriveDataTypeable #-} | 17 | {-# LANGUAGE DeriveDataTypeable #-} |
18 | {-# LANGUAGE TypeFamilies #-} | ||
19 | module Network.BitTorrent.Tracker.RPC.UDP | 18 | module Network.BitTorrent.Tracker.RPC.UDP |
20 | ( -- * Manager | 19 | ( -- * Manager |
21 | Options (..) | 20 | Options (..) |
@@ -52,7 +51,6 @@ import Text.Read (readMaybe) | |||
52 | import Network.Socket hiding (Connected, connect, listen) | 51 | import Network.Socket hiding (Connected, connect, listen) |
53 | import Network.Socket.ByteString as BS | 52 | import Network.Socket.ByteString as BS |
54 | import Network.URI | 53 | import Network.URI |
55 | import System.Entropy | ||
56 | import System.Timeout | 54 | import System.Timeout |
57 | import Numeric | 55 | import Numeric |
58 | 56 | ||
@@ -259,151 +257,6 @@ getTrackerAddr _ uri | |||
259 | | otherwise = throwIO (UnrecognizedScheme (uriScheme uri)) | 257 | | otherwise = throwIO (UnrecognizedScheme (uriScheme uri)) |
260 | 258 | ||
261 | {----------------------------------------------------------------------- | 259 | {----------------------------------------------------------------------- |
262 | Tokens | ||
263 | -----------------------------------------------------------------------} | ||
264 | |||
265 | genToken :: IO Word64 | ||
266 | genToken = do | ||
267 | bs <- getEntropy 8 | ||
268 | either err return $ runGet getWord64be bs | ||
269 | where | ||
270 | err = error "genToken: impossible happen" | ||
271 | |||
272 | -- | Connection Id is used for entire tracker session. | ||
273 | newtype ConnectionId = ConnectionId Word64 | ||
274 | deriving (Eq, Serialize) | ||
275 | |||
276 | instance Show ConnectionId where | ||
277 | showsPrec _ (ConnectionId cid) = showString "0x" <> showHex cid | ||
278 | |||
279 | initialConnectionId :: ConnectionId | ||
280 | initialConnectionId = ConnectionId 0x41727101980 | ||
281 | |||
282 | -- | Transaction Id is used within a UDP RPC. | ||
283 | newtype TransactionId = TransactionId Word32 | ||
284 | deriving (Eq, Ord, Enum, Bounded, Serialize) | ||
285 | |||
286 | instance Show TransactionId where | ||
287 | showsPrec _ (TransactionId tid) = showString "0x" <> showHex tid | ||
288 | |||
289 | genTransactionId :: IO TransactionId | ||
290 | genTransactionId = (TransactionId . fromIntegral) <$> genToken | ||
291 | |||
292 | {----------------------------------------------------------------------- | ||
293 | Transactions | ||
294 | -----------------------------------------------------------------------} | ||
295 | |||
296 | data Request = Connect | ||
297 | | Announce AnnounceQuery | ||
298 | | Scrape ScrapeQuery | ||
299 | deriving Show | ||
300 | |||
301 | data Response = Connected ConnectionId | ||
302 | | Announced AnnounceInfo | ||
303 | | Scraped [ScrapeEntry] | ||
304 | | Failed Text | ||
305 | deriving Show | ||
306 | |||
307 | responseName :: Response -> String | ||
308 | responseName (Connected _) = "connected" | ||
309 | responseName (Announced _) = "announced" | ||
310 | responseName (Scraped _) = "scraped" | ||
311 | responseName (Failed _) = "failed" | ||
312 | |||
313 | data family Transaction a | ||
314 | data instance Transaction Request = TransactionQ | ||
315 | { connIdQ :: {-# UNPACK #-} !ConnectionId | ||
316 | , transIdQ :: {-# UNPACK #-} !TransactionId | ||
317 | , request :: !Request | ||
318 | } deriving Show | ||
319 | data instance Transaction Response = TransactionR | ||
320 | { transIdR :: {-# UNPACK #-} !TransactionId | ||
321 | , response :: !Response | ||
322 | } deriving Show | ||
323 | |||
324 | -- TODO newtype | ||
325 | newtype MessageId = MessageId Word32 | ||
326 | deriving (Show, Eq, Num, Serialize) | ||
327 | |||
328 | connectId, announceId, scrapeId, errorId :: MessageId | ||
329 | connectId = 0 | ||
330 | announceId = 1 | ||
331 | scrapeId = 2 | ||
332 | errorId = 3 | ||
333 | |||
334 | instance Serialize (Transaction Request) where | ||
335 | put TransactionQ {..} = do | ||
336 | case request of | ||
337 | Connect -> do | ||
338 | put initialConnectionId | ||
339 | put connectId | ||
340 | put transIdQ | ||
341 | |||
342 | Announce ann -> do | ||
343 | put connIdQ | ||
344 | put announceId | ||
345 | put transIdQ | ||
346 | put ann | ||
347 | |||
348 | Scrape hashes -> do | ||
349 | put connIdQ | ||
350 | put scrapeId | ||
351 | put transIdQ | ||
352 | forM_ hashes put | ||
353 | |||
354 | get = do | ||
355 | cid <- get | ||
356 | mid <- get | ||
357 | TransactionQ cid <$> get <*> getBody mid | ||
358 | where | ||
359 | getBody :: MessageId -> Get Request | ||
360 | getBody msgId | ||
361 | | msgId == connectId = pure Connect | ||
362 | | msgId == announceId = Announce <$> get | ||
363 | | msgId == scrapeId = Scrape <$> many get | ||
364 | | otherwise = fail errMsg | ||
365 | where | ||
366 | errMsg = "unknown request: " ++ show msgId | ||
367 | |||
368 | instance Serialize (Transaction Response) where | ||
369 | put TransactionR {..} = do | ||
370 | case response of | ||
371 | Connected conn -> do | ||
372 | put connectId | ||
373 | put transIdR | ||
374 | put conn | ||
375 | |||
376 | Announced info -> do | ||
377 | put announceId | ||
378 | put transIdR | ||
379 | put info | ||
380 | |||
381 | Scraped infos -> do | ||
382 | put scrapeId | ||
383 | put transIdR | ||
384 | forM_ infos put | ||
385 | |||
386 | Failed info -> do | ||
387 | put errorId | ||
388 | put transIdR | ||
389 | put (encodeUtf8 info) | ||
390 | |||
391 | |||
392 | get = do | ||
393 | mid <- get | ||
394 | TransactionR <$> get <*> getBody mid | ||
395 | where | ||
396 | getBody :: MessageId -> Get Response | ||
397 | getBody msgId | ||
398 | | msgId == connectId = Connected <$> get | ||
399 | | msgId == announceId = Announced <$> get | ||
400 | | msgId == scrapeId = Scraped <$> many get | ||
401 | | msgId == errorId = (Failed . decodeUtf8) <$> get | ||
402 | | otherwise = fail msg | ||
403 | where | ||
404 | msg = "unknown response: " ++ show msgId | ||
405 | |||
406 | {----------------------------------------------------------------------- | ||
407 | Connection | 260 | Connection |
408 | -----------------------------------------------------------------------} | 261 | -----------------------------------------------------------------------} |
409 | 262 | ||
diff --git a/src/Network/BitTorrent/Tracker/Session.hs b/src/Network/BitTorrent/Tracker/Session.hs index 7be16fd6..9fe02b52 100644 --- a/src/Network/BitTorrent/Tracker/Session.hs +++ b/src/Network/BitTorrent/Tracker/Session.hs | |||
@@ -50,13 +50,13 @@ import Network.BitTorrent.Tracker.RPC as RPC | |||
50 | -- Tracker entry | 50 | -- Tracker entry |
51 | -----------------------------------------------------------------------} | 51 | -----------------------------------------------------------------------} |
52 | 52 | ||
53 | data Scrape = Scrape | 53 | data LastScrape = LastScrape |
54 | { leechersCount :: Maybe Int | 54 | { leechersCount :: Maybe Int |
55 | , seedersCount :: Maybe Int | 55 | , seedersCount :: Maybe Int |
56 | } deriving (Show, Eq) | 56 | } deriving (Show, Eq) |
57 | 57 | ||
58 | instance Default Scrape where | 58 | instance Default LastScrape where |
59 | def = Scrape Nothing Nothing | 59 | def = LastScrape Nothing Nothing |
60 | 60 | ||
61 | 61 | ||
62 | data Status | 62 | data Status |
@@ -102,7 +102,7 @@ data TrackerEntry = TrackerEntry | |||
102 | , peersCache :: Cached [PeerAddr IP] | 102 | , peersCache :: Cached [PeerAddr IP] |
103 | 103 | ||
104 | -- | May be used to show brief swarm stats in client GUI. | 104 | -- | May be used to show brief swarm stats in client GUI. |
105 | , scrapeCache :: Cached Scrape | 105 | , scrapeCache :: Cached LastScrape |
106 | } | 106 | } |
107 | 107 | ||
108 | nullEntry :: URI -> TrackerEntry | 108 | nullEntry :: URI -> TrackerEntry |
@@ -140,11 +140,11 @@ cachePeers AnnounceInfo {..} = | |||
140 | (seconds (fromMaybe respInterval respMinInterval)) | 140 | (seconds (fromMaybe respInterval respMinInterval)) |
141 | (getPeerList respPeers) | 141 | (getPeerList respPeers) |
142 | 142 | ||
143 | cacheScrape :: AnnounceInfo -> IO (Cached Scrape) | 143 | cacheScrape :: AnnounceInfo -> IO (Cached LastScrape) |
144 | cacheScrape AnnounceInfo {..} = | 144 | cacheScrape AnnounceInfo {..} = |
145 | newCached (seconds respInterval) | 145 | newCached (seconds respInterval) |
146 | (seconds (fromMaybe respInterval respMinInterval)) | 146 | (seconds (fromMaybe respInterval respMinInterval)) |
147 | Scrape | 147 | LastScrape |
148 | { seedersCount = respComplete | 148 | { seedersCount = respComplete |
149 | , leechersCount = respIncomplete | 149 | , leechersCount = respIncomplete |
150 | } | 150 | } |