1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Tracker
8 import Prelude hiding (lookup, splitAt)
10 import System.IO (Handle)
11 import Control.Applicative (liftA2)
12 import Control.Concurrent (threadDelay)
13 import Control.Concurrent.MVar (MVar, newEmptyMVar, newMVar, readMVar, putMVar)
14 import Data.Binary (Binary(..), encode, decode)
15 import Data.Binary.Put (putWord16be, putWord32be, putWord64be, putByteString)
16 import Data.Binary.Get (Get, isEmpty, getByteString, getWord16be, getWord32be)
17 import Data.ByteString (ByteString, hGet, hPut)
18 import Data.ByteString.Char8 as BC (pack, unpack, splitAt)
19 import Data.ByteString.Lazy (fromStrict, toStrict)
20 import Data.Char (chr)
21 import Data.List (intercalate)
22 import Data.Map as M (lookup)
23 import Network (PortNumber)
24 import Network.HTTP.Base (urlEncode)
25 import qualified Data.ByteString.Base16 as B16 (encode)
27 import FuncTorrent.Bencode (BVal(..))
28 import qualified FuncTorrent.Bencode as Benc
29 import FuncTorrent.Metainfo (Info(..), Metainfo(..))
30 import FuncTorrent.Network (sendGetRequest)
31 import FuncTorrent.Peer (Peer(..))
32 import FuncTorrent.Utils (splitN)
35 data TrackerResponse = TrackerResponse {
38 , complete :: Maybe Integer
39 , incomplete :: Maybe Integer
42 data TrackerEventState = None
48 data TState = TState {
49 uploaded :: MVar Integer
50 , downloaded :: MVar Integer
52 , currentState :: TrackerEventState
53 , connectedPeers :: MVar [Peer]
56 -- UDP tracker: http://bittorrent.org/beps/bep_0015.html
65 data UDPRequest = ConnectReq Integer
66 | AnnounceReq Integer Integer ByteString String Integer Integer Integer TrackerEventState Integer
67 | ScrapeReq Integer Integer ByteString
70 data UDPResponse = ConnectResp Integer Integer -- transaction_id connection_id
71 | AnnounceResp Integer Integer Integer Integer [(IP, Port)] -- transaction_id interval leechers seeders [(ip, port)]
72 | ScrapeResp Integer Integer Integer Integer
75 actionToInteger :: Action -> Integer
76 actionToInteger Connect = 0
77 actionToInteger Announce = 1
78 actionToInteger Scrape = 2
80 intToAction :: Integer -> Action
81 intToAction 0 = Connect
82 intToAction 1 = Announce
83 intToAction 2 = Scrape
85 eventToInteger :: TrackerEventState -> Integer
86 eventToInteger None = 0
87 eventToInteger Completed = 1
88 eventToInteger Started = 2
89 eventToInteger Stopped = 3
91 instance Binary UDPRequest where
92 put (ConnectReq transId) = do
93 putWord64be 0x41727101980
94 putWord32be $ fromIntegral (actionToInteger Connect)
95 putWord32be (fromIntegral transId)
96 put (AnnounceReq connId transId infohash peerId down left up event port) = do
97 putWord64be $ fromIntegral connId
98 putWord32be $ fromIntegral (actionToInteger Announce)
99 putWord32be $ fromIntegral transId
100 putByteString infohash
101 putByteString (BC.pack peerId)
102 putWord64be (fromIntegral down)
103 putWord64be (fromIntegral left)
104 putWord64be (fromIntegral up)
105 putWord32be $ fromIntegral (eventToInteger None)
107 -- key is optional, we will not send it for now
108 putWord32be $ fromIntegral (-1)
109 putWord16be $ fromIntegral port
110 put (ScrapeReq _ _ _) = undefined
113 instance Binary UDPResponse where
116 a <- getWord32be -- action
118 0 -> liftA2 ConnectResp (fromIntegral <$> getWord32be) (fromIntegral <$> getWord32be)
120 tid <- fromIntegral <$> getWord32be
121 interval' <- fromIntegral <$> getWord32be
122 _ <- getWord32be -- leechers
123 _ <- getWord32be -- seeders
124 ipportpairs <- getIPPortPairs -- [(ip, port)]
125 return $ AnnounceResp tid interval' 0 0 ipportpairs
127 tid <- fromIntegral <$> getWord32be
131 return $ ScrapeResp tid 0 0 0
132 _ -> error ("unknown response action type: " ++ show a)
134 getIPPortPairs :: Get [(IP, Port)]
140 ip <- toIP <$> getByteString 6
141 port <- toPort <$> getByteString 2
142 ipportpairs <- getIPPortPairs
143 return $ (ip, port) : ipportpairs
145 initialTrackerState :: Integer -> IO TState
146 initialTrackerState sz = do
150 return $ TState { currentState = None
151 , connectedPeers = ps
156 -- | Deserialize tracker response
157 mkTrackerResponse :: BVal -> Either ByteString TrackerResponse
158 mkTrackerResponse resp =
159 case lookup "failure reason" body of
160 Just (Bstr err) -> Left err
161 Just _ -> Left "Unknown failure"
163 let (Just (Bint i)) = lookup "interval" body
164 (Just (Bstr peersBS)) = lookup "peers" body
165 pl = map makePeer (splitN 6 peersBS)
166 in Right TrackerResponse {
170 , incomplete = Nothing
175 toInt :: String -> Integer
178 makePeer :: ByteString -> Peer
179 makePeer peer = Peer "" (toIP ip') (toPort port')
180 where (ip', port') = splitAt 4 peer
182 toPort :: ByteString -> Port
183 toPort = read . ("0x" ++) . unpack . B16.encode
185 toIP :: ByteString -> IP
186 toIP = Data.List.intercalate "." .
187 map (show . toInt . ("0x" ++) . unpack) .
188 splitN 2 . B16.encode
190 --- | URL encode hash as per RFC1738
192 --- REVIEW: Why is this not written in terms of `Network.HTTP.Base.urlEncode` or
193 --- equivalent library function?
194 urlEncodeHash :: ByteString -> String
195 urlEncodeHash bs = concatMap (encode' . unpack) (splitN 2 bs)
196 where encode' b@[c1, c2] = let c = chr (read ("0x" ++ b))
199 escape i c1 c2 | i `elem` nonSpecialChars = [i]
200 | otherwise = "%" ++ [c1] ++ [c2]
202 nonSpecialChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "-_.~"
204 -- | Make arguments that should be posted to tracker.
205 -- This is a separate pure function for testability.
206 mkArgs :: PortNumber -> String -> Integer -> Integer -> Metainfo -> [(String, ByteString)]
207 mkArgs port peer_id up down m =
208 let fileSize = lengthInBytes $ info m
209 bytesLeft = fileSize - down
211 [("info_hash", pack . urlEncodeHash . B16.encode . infoHash $ m),
212 ("peer_id", pack . urlEncode $ peer_id),
213 ("port", pack $ show port),
214 ("uploaded", pack $ show up),
215 ("downloaded", pack $ show down),
216 ("left", pack $ show bytesLeft),
218 ("event", "started")]
220 trackerLoop :: PortNumber -> String -> Metainfo -> TState -> IO ByteString
221 trackerLoop port peerId m st = do
222 up <- readMVar $ uploaded st
223 down <- readMVar $ downloaded st
224 resp <- sendGetRequest (head . announceList $ m) $ mkArgs port peerId up down m
225 case Benc.decode resp of
226 Left e -> return $ pack (show e)
228 case mkTrackerResponse trackerInfo of
231 _ <- threadDelay $ fromIntegral (interval tresp)
232 _ <- putMVar (connectedPeers st) (peers tresp)
233 trackerLoop port peerId m st
236 getResponse :: Handle -> IO UDPResponse
238 -- connect packet is 16 bytes long
239 -- announce packet is atleast 20 bytes long
240 bs <- hGet h (16*1024)
241 return $ decode $ fromStrict bs
243 sendRequest :: Handle -> UDPRequest -> IO ()
244 sendRequest h req = hPut h bsReq
245 where bsReq = toStrict $ encode req