1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Tracker
8 import Prelude hiding (lookup, splitAt)
10 import Control.Applicative (liftA2)
11 import Control.Concurrent (threadDelay)
12 import Control.Concurrent.MVar (MVar, newEmptyMVar, newMVar, readMVar, putMVar)
13 import Data.Binary (Binary(..))
14 import Data.Binary.Put (putWord16be, putWord32be, putWord64be, putByteString)
15 import Data.Binary.Get (Get, isEmpty, getByteString, getWord16be, getWord32be)
16 import Data.ByteString (ByteString)
17 import Data.ByteString.Char8 as BC (pack, unpack, splitAt)
18 import Data.Char (chr)
19 import Data.List (intercalate)
20 import Data.Map as M (lookup)
21 import Network (PortNumber)
22 import Network.HTTP.Base (urlEncode)
23 import qualified Data.ByteString.Base16 as B16 (encode)
25 import FuncTorrent.Bencode (BVal(..), decode)
26 import FuncTorrent.Metainfo (Info(..), Metainfo(..))
27 import FuncTorrent.Network (sendGetRequest)
28 import FuncTorrent.Peer (Peer(..))
29 import FuncTorrent.Utils (splitN)
32 data TrackerResponse = TrackerResponse {
35 , complete :: Maybe Integer
36 , incomplete :: Maybe Integer
39 data TrackerEventState = None
45 data TState = TState {
46 uploaded :: MVar Integer
47 , downloaded :: MVar Integer
49 , currentState :: TrackerEventState
50 , connectedPeers :: MVar [Peer]
53 -- UDP tracker: http://bittorrent.org/beps/bep_0015.html
62 data UDPRequest = ConnectReq Integer
63 | AnnounceReq Integer Integer ByteString String Integer Integer Integer TrackerEventState Integer
64 | ScrapeReq Integer Integer ByteString
67 data UDPResponse = ConnectResp Integer Integer -- transaction_id connection_id
68 | AnnounceResp Integer Integer Integer Integer [(IP, Port)] -- transaction_id interval leechers seeders [(ip, port)]
69 | ScrapeResp Integer Integer Integer Integer
72 actionToInteger :: Action -> Integer
73 actionToInteger Connect = 0
74 actionToInteger Announce = 1
75 actionToInteger Scrape = 2
77 intToAction :: Integer -> Action
78 intToAction 0 = Connect
79 intToAction 1 = Announce
80 intToAction 2 = Scrape
82 eventToInteger :: TrackerEventState -> Integer
83 eventToInteger None = 0
84 eventToInteger Completed = 1
85 eventToInteger Started = 2
86 eventToInteger Stopped = 3
88 instance Binary UDPRequest where
89 put (ConnectReq transId) = do
90 putWord64be 0x41727101980
91 putWord32be $ fromIntegral (actionToInteger Connect)
92 putWord32be (fromIntegral transId)
93 put (AnnounceReq connId transId infohash peerId down left up event port) = do
94 putWord64be $ fromIntegral connId
95 putWord32be $ fromIntegral (actionToInteger Announce)
96 putWord32be $ fromIntegral transId
97 putByteString infohash
98 putByteString (BC.pack peerId)
99 putWord64be (fromIntegral down)
100 putWord64be (fromIntegral left)
101 putWord64be (fromIntegral up)
102 putWord32be $ fromIntegral (eventToInteger None)
104 -- key is optional, we will not send it for now
105 putWord32be $ fromIntegral (-1)
106 putWord16be $ fromIntegral port
107 put (ScrapeReq _ _ _) = undefined
110 instance Binary UDPResponse where
113 a <- getWord32be -- action
115 0 -> liftA2 ConnectResp (fromIntegral <$> getWord32be) (fromIntegral <$> getWord32be)
117 tid <- fromIntegral <$> getWord32be
118 interval' <- fromIntegral <$> getWord32be
119 _ <- getWord32be -- leechers
120 _ <- getWord32be -- seeders
121 ipportpairs <- getIPPortPairs -- [(ip, port)]
122 return $ AnnounceResp tid interval' 0 0 ipportpairs
124 tid <- fromIntegral <$> getWord32be
128 return $ ScrapeResp tid 0 0 0
129 _ -> error ("unknown response action type: " ++ show a)
131 getIPPortPairs :: Get [(IP, Port)]
137 ip <- toIP <$> getByteString 6
138 port <- toPort <$> getByteString 2
139 ipportpairs <- getIPPortPairs
140 return $ (ip, port) : ipportpairs
142 initialTrackerState :: Integer -> IO TState
143 initialTrackerState sz = do
147 return $ TState { currentState = None
148 , connectedPeers = ps
153 -- | Deserialize tracker response
154 mkTrackerResponse :: BVal -> Either ByteString TrackerResponse
155 mkTrackerResponse resp =
156 case lookup "failure reason" body of
157 Just (Bstr err) -> Left err
158 Just _ -> Left "Unknown failure"
160 let (Just (Bint i)) = lookup "interval" body
161 (Just (Bstr peersBS)) = lookup "peers" body
162 pl = map makePeer (splitN 6 peersBS)
163 in Right TrackerResponse {
167 , incomplete = Nothing
172 toInt :: String -> Integer
175 makePeer :: ByteString -> Peer
176 makePeer peer = Peer "" (toIP ip') (toPort port')
177 where (ip', port') = splitAt 4 peer
179 toPort :: ByteString -> Port
180 toPort = read . ("0x" ++) . unpack . B16.encode
182 toIP :: ByteString -> IP
183 toIP = Data.List.intercalate "." .
184 map (show . toInt . ("0x" ++) . unpack) .
185 splitN 2 . B16.encode
187 --- | URL encode hash as per RFC1738
189 --- REVIEW: Why is this not written in terms of `Network.HTTP.Base.urlEncode` or
190 --- equivalent library function?
191 urlEncodeHash :: ByteString -> String
192 urlEncodeHash bs = concatMap (encode' . unpack) (splitN 2 bs)
193 where encode' b@[c1, c2] = let c = chr (read ("0x" ++ b))
196 escape i c1 c2 | i `elem` nonSpecialChars = [i]
197 | otherwise = "%" ++ [c1] ++ [c2]
199 nonSpecialChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "-_.~"
201 -- | Make arguments that should be posted to tracker.
202 -- This is a separate pure function for testability.
203 mkArgs :: PortNumber -> String -> Integer -> Integer -> Metainfo -> [(String, ByteString)]
204 mkArgs port peer_id up down m =
205 let fileSize = lengthInBytes $ info m
206 bytesLeft = fileSize - down
208 [("info_hash", pack . urlEncodeHash . B16.encode . infoHash $ m),
209 ("peer_id", pack . urlEncode $ peer_id),
210 ("port", pack $ show port),
211 ("uploaded", pack $ show up),
212 ("downloaded", pack $ show down),
213 ("left", pack $ show bytesLeft),
215 ("event", "started")]
217 trackerLoop :: PortNumber -> String -> Metainfo -> TState -> IO ByteString
218 trackerLoop port peerId m st = do
219 up <- readMVar $ uploaded st
220 down <- readMVar $ downloaded st
221 resp <- sendGetRequest (head . announceList $ m) $ mkArgs port peerId up down m
223 Left e -> return $ pack (show e)
225 case mkTrackerResponse trackerInfo of
228 _ <- threadDelay $ fromIntegral (interval tresp)
229 _ <- putMVar (connectedPeers st) (peers tresp)
230 trackerLoop port peerId m st