summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2014-03-15 20:37:11 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2014-03-15 20:37:11 +0400
commit442a7c0941b4f2659988193404263348593551a2 (patch)
treeb22870011b7a27a508f1500670bf7513f34df3b2
parente2c997424da60cae727a934fd9627f00ec1be0fd (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.hs163
-rw-r--r--src/Network/BitTorrent/Tracker/RPC/HTTP.hs2
-rw-r--r--src/Network/BitTorrent/Tracker/RPC/UDP.hs147
-rw-r--r--src/Network/BitTorrent/Tracker/Session.hs12
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 #-}
29module Network.BitTorrent.Tracker.Message 30module 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
104import Network.HTTP.Types.QueryLike 120import Network.HTTP.Types.QueryLike
105import Network.HTTP.Types.URI hiding (urlEncode) 121import Network.HTTP.Types.URI hiding (urlEncode)
106import Network.HTTP.Types.Status 122import Network.HTTP.Types.Status
107import Network.Socket 123import Network.Socket hiding (Connected)
124import Numeric
125import System.Entropy
108import Text.Read (readMaybe) 126import Text.Read (readMaybe)
109 127
110import Data.Torrent.InfoHash 128import Data.Torrent.InfoHash
@@ -765,3 +783,146 @@ scrapeType = "text/plain"
765-- 783--
766parseFailureStatus :: ParamParseFailure -> Status 784parseFailureStatus :: ParamParseFailure -> Status
767parseFailureStatus = mkStatus <$> parseFailureCode <*> parseFailureMessage 785parseFailureStatus = mkStatus <$> parseFailureCode <*> parseFailureMessage
786
787{-----------------------------------------------------------------------
788-- UDP specific message types
789-----------------------------------------------------------------------}
790
791genToken :: IO Word64
792genToken = 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.
799newtype ConnectionId = ConnectionId Word64
800 deriving (Eq, Serialize)
801
802instance Show ConnectionId where
803 showsPrec _ (ConnectionId cid) = showString "0x" <> showHex cid
804
805initialConnectionId :: ConnectionId
806initialConnectionId = ConnectionId 0x41727101980
807
808-- | Transaction Id is used within a UDP RPC.
809newtype TransactionId = TransactionId Word32
810 deriving (Eq, Ord, Enum, Bounded, Serialize)
811
812instance Show TransactionId where
813 showsPrec _ (TransactionId tid) = showString "0x" <> showHex tid
814
815genTransactionId :: IO TransactionId
816genTransactionId = (TransactionId . fromIntegral) <$> genToken
817
818data Request
819 = Connect
820 | Announce AnnounceQuery
821 | Scrape ScrapeQuery
822 deriving Show
823
824data Response
825 = Connected ConnectionId
826 | Announced AnnounceInfo
827 | Scraped [ScrapeEntry]
828 | Failed Text
829 deriving Show
830
831responseName :: Response -> String
832responseName (Connected _) = "connected"
833responseName (Announced _) = "announced"
834responseName (Scraped _) = "scraped"
835responseName (Failed _) = "failed"
836
837data family Transaction a
838data instance Transaction Request = TransactionQ
839 { connIdQ :: {-# UNPACK #-} !ConnectionId
840 , transIdQ :: {-# UNPACK #-} !TransactionId
841 , request :: !Request
842 } deriving Show
843data instance Transaction Response = TransactionR
844 { transIdR :: {-# UNPACK #-} !TransactionId
845 , response :: !Response
846 } deriving Show
847
848-- TODO newtype
849newtype MessageId = MessageId Word32
850 deriving (Show, Eq, Num, Serialize)
851
852connectId, announceId, scrapeId, errorId :: MessageId
853connectId = 0
854announceId = 1
855scrapeId = 2
856errorId = 3
857
858instance 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
892instance 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
50import Data.Torrent.InfoHash (InfoHash) 50import Data.Torrent.InfoHash (InfoHash)
51import Network.BitTorrent.Core.Fingerprint (libUserAgent) 51import Network.BitTorrent.Core.Fingerprint (libUserAgent)
52import Network.BitTorrent.Tracker.Message 52import 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 #-}
19module Network.BitTorrent.Tracker.RPC.UDP 18module Network.BitTorrent.Tracker.RPC.UDP
20 ( -- * Manager 19 ( -- * Manager
21 Options (..) 20 Options (..)
@@ -52,7 +51,6 @@ import Text.Read (readMaybe)
52import Network.Socket hiding (Connected, connect, listen) 51import Network.Socket hiding (Connected, connect, listen)
53import Network.Socket.ByteString as BS 52import Network.Socket.ByteString as BS
54import Network.URI 53import Network.URI
55import System.Entropy
56import System.Timeout 54import System.Timeout
57import Numeric 55import 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
265genToken :: IO Word64
266genToken = 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.
273newtype ConnectionId = ConnectionId Word64
274 deriving (Eq, Serialize)
275
276instance Show ConnectionId where
277 showsPrec _ (ConnectionId cid) = showString "0x" <> showHex cid
278
279initialConnectionId :: ConnectionId
280initialConnectionId = ConnectionId 0x41727101980
281
282-- | Transaction Id is used within a UDP RPC.
283newtype TransactionId = TransactionId Word32
284 deriving (Eq, Ord, Enum, Bounded, Serialize)
285
286instance Show TransactionId where
287 showsPrec _ (TransactionId tid) = showString "0x" <> showHex tid
288
289genTransactionId :: IO TransactionId
290genTransactionId = (TransactionId . fromIntegral) <$> genToken
291
292{-----------------------------------------------------------------------
293 Transactions
294-----------------------------------------------------------------------}
295
296data Request = Connect
297 | Announce AnnounceQuery
298 | Scrape ScrapeQuery
299 deriving Show
300
301data Response = Connected ConnectionId
302 | Announced AnnounceInfo
303 | Scraped [ScrapeEntry]
304 | Failed Text
305 deriving Show
306
307responseName :: Response -> String
308responseName (Connected _) = "connected"
309responseName (Announced _) = "announced"
310responseName (Scraped _) = "scraped"
311responseName (Failed _) = "failed"
312
313data family Transaction a
314data instance Transaction Request = TransactionQ
315 { connIdQ :: {-# UNPACK #-} !ConnectionId
316 , transIdQ :: {-# UNPACK #-} !TransactionId
317 , request :: !Request
318 } deriving Show
319data instance Transaction Response = TransactionR
320 { transIdR :: {-# UNPACK #-} !TransactionId
321 , response :: !Response
322 } deriving Show
323
324-- TODO newtype
325newtype MessageId = MessageId Word32
326 deriving (Show, Eq, Num, Serialize)
327
328connectId, announceId, scrapeId, errorId :: MessageId
329connectId = 0
330announceId = 1
331scrapeId = 2
332errorId = 3
333
334instance 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
368instance 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
53data Scrape = Scrape 53data 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
58instance Default Scrape where 58instance Default LastScrape where
59 def = Scrape Nothing Nothing 59 def = LastScrape Nothing Nothing
60 60
61 61
62data Status 62data 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
108nullEntry :: URI -> TrackerEntry 108nullEntry :: 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
143cacheScrape :: AnnounceInfo -> IO (Cached Scrape) 143cacheScrape :: AnnounceInfo -> IO (Cached LastScrape)
144cacheScrape AnnounceInfo {..} = 144cacheScrape 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 }