summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Tracker/Message.hs
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 /src/Network/BitTorrent/Tracker/Message.hs
parente2c997424da60cae727a934fd9627f00ec1be0fd (diff)
Expose UDP tracker specific message types
Those can be used to implement UDP tracker server by third party libraries or projects.
Diffstat (limited to 'src/Network/BitTorrent/Tracker/Message.hs')
-rw-r--r--src/Network/BitTorrent/Tracker/Message.hs163
1 files changed, 162 insertions, 1 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