summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Tracker
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Tracker')
-rw-r--r--src/Network/BitTorrent/Tracker/UDP.hs246
1 files changed, 192 insertions, 54 deletions
diff --git a/src/Network/BitTorrent/Tracker/UDP.hs b/src/Network/BitTorrent/Tracker/UDP.hs
index 13e1298b..23c6cacc 100644
--- a/src/Network/BitTorrent/Tracker/UDP.hs
+++ b/src/Network/BitTorrent/Tracker/UDP.hs
@@ -6,50 +6,93 @@
6-- Portability : portable 6-- Portability : portable
7-- 7--
8-- This module implement low-level UDP tracker protocol. 8-- This module implement low-level UDP tracker protocol.
9-- For more info see: http://www.bittorrent.org/beps/bep_0015.html 9-- For more info see:
10-- <http://www.bittorrent.org/beps/bep_0015.html>
10-- 11--
11{-# LANGUAGE RecordWildCards #-} 12{-# LANGUAGE RecordWildCards #-}
12{-# LANGUAGE FlexibleInstances #-} 13{-# LANGUAGE FlexibleInstances #-}
13{-# LANGUAGE GeneralizedNewtypeDeriving #-} 14{-# LANGUAGE GeneralizedNewtypeDeriving #-}
14module Network.BitTorrent.Tracker.UDP 15module Network.BitTorrent.Tracker.UDP
15 ( Request(..), Response(..) 16 ( UDPTracker
17 , initialTracker
18 , putTracker
19 , connectUDP
20 , freshConnection
16 ) where 21 ) where
17 22
18import Control.Applicative 23import Control.Applicative
24import Control.Exception
19import Control.Monad 25import Control.Monad
26import Data.ByteString (ByteString)
27import Data.IORef
28import Data.List as L
29import Data.Maybe
30import Data.Monoid
20import Data.Serialize 31import Data.Serialize
21import Data.Word
22import Data.Text 32import Data.Text
23import Data.Text.Encoding 33import Data.Text.Encoding
34import Data.Time
35import Data.Word
36import Text.Read (readMaybe)
24import Network.Socket hiding (Connected) 37import Network.Socket hiding (Connected)
25import Network.Socket.ByteString as BS 38import Network.Socket.ByteString as BS
39import Network.URI
40import System.Entropy
41import Numeric
26 42
27import Data.Torrent.Metainfo () 43import Data.Torrent.Metainfo ()
28import Network.BitTorrent.Tracker.Protocol 44import Network.BitTorrent.Tracker.Protocol
29 45
46{-----------------------------------------------------------------------
47 Tokens
48-----------------------------------------------------------------------}
30 49
50genToken :: IO Word64
51genToken = do
52 bs <- getEntropy 8
53 either err return $ runGet getWord64be bs
54 where
55 err = error "genToken: impossible happen"
56
57-- TODO rename
31-- | Connection Id is used for entire tracker session. 58-- | Connection Id is used for entire tracker session.
32newtype ConnId = ConnId { getConnId :: Word64 } 59newtype ConnId = ConnId Word64
33 deriving (Show, Eq, Serialize) 60 deriving (Eq, Serialize)
34 61
35-- | Transaction Id is used for within UDP RPC. 62instance Show ConnId where
36newtype TransId = TransId { getTransId :: Word32 } 63 showsPrec _ (ConnId cid) = showString "0x" <> showHex cid
37 deriving (Show, Eq, Serialize)
38 64
39genTransactionId :: IO TransId 65genConnectionId :: IO ConnId
40genTransactionId = return (TransId 0) 66genConnectionId = ConnId <$> genToken
41 67
42initialConnectionId :: ConnId 68initialConnectionId :: ConnId
43initialConnectionId = ConnId 0 69initialConnectionId = ConnId 0x41727101980
70
71-- TODO rename
72-- | Transaction Id is used within a UDP RPC.
73newtype TransId = TransId Word32
74 deriving (Eq, Serialize)
75
76instance Show TransId where
77 showsPrec _ (TransId tid) = showString "0x" <> showHex tid
78
79genTransactionId :: IO TransId
80genTransactionId = (TransId . fromIntegral) <$> genToken
81
82{-----------------------------------------------------------------------
83 Transactions
84-----------------------------------------------------------------------}
44 85
45data Request = Connect 86data Request = Connect
46 | Announce AnnounceQuery 87 | Announce AnnounceQuery
47 | Scrape ScrapeQuery 88 | Scrape ScrapeQuery
89 deriving Show
48 90
49data Response = Connected 91data Response = Connected ConnId
50 | Announced AnnounceInfo 92 | Announced AnnounceInfo
51 | Scraped [ScrapeInfo] 93 | Scraped [ScrapeInfo]
52 | Failed Text 94 | Failed Text
95 deriving Show
53 96
54-- TODO rename to message? 97-- TODO rename to message?
55data Transaction a = Transaction 98data Transaction a = Transaction
@@ -70,7 +113,7 @@ instance Serialize (Transaction Request) where
70 put Transaction {..} = do 113 put Transaction {..} = do
71 case body of 114 case body of
72 Connect -> do 115 Connect -> do
73 put connId 116 put initialConnectionId
74 put connectId 117 put connectId
75 put transId 118 put transId
76 119
@@ -82,15 +125,15 @@ instance Serialize (Transaction Request) where
82 125
83 Scrape hashes -> do 126 Scrape hashes -> do
84 put connId 127 put connId
85 put announceId 128 put scrapeId
86 put transId 129 put transId
87 forM_ hashes put 130 forM_ hashes put
88 131
89 get = do 132 get = do
90 cid <- get 133 cid <- get
91 rid <- getWord32be 134 mid <- getWord32be
92 tid <- get 135 tid <- get
93 bod <- getBody rid 136 bod <- getBody mid
94 137
95 return $ Transaction { 138 return $ Transaction {
96 connId = cid 139 connId = cid
@@ -100,7 +143,7 @@ instance Serialize (Transaction Request) where
100 where 143 where
101 getBody :: MessageId -> Get Request 144 getBody :: MessageId -> Get Request
102 getBody msgId 145 getBody msgId
103 | msgId == connectId = return Connect 146 | msgId == connectId = pure Connect
104 | msgId == announceId = Announce <$> get 147 | msgId == announceId = Announce <$> get
105 | msgId == scrapeId = Scrape <$> many get 148 | msgId == scrapeId = Scrape <$> many get
106 | otherwise = fail "unknown message id" 149 | otherwise = fail "unknown message id"
@@ -108,45 +151,42 @@ instance Serialize (Transaction Request) where
108instance Serialize (Transaction Response) where 151instance Serialize (Transaction Response) where
109 put Transaction {..} = do 152 put Transaction {..} = do
110 case body of 153 case body of
111 Connected -> do 154 Connected conn -> do
112 put connId
113 put connectId 155 put connectId
114 put transId 156 put transId
157 put conn
115 158
116 Announced info -> do 159 Announced info -> do
117 put connId
118 put announceId 160 put announceId
119 put transId 161 put transId
120 put info 162 put info
121 163
122 Scraped infos -> do 164 Scraped infos -> do
123 put connId
124 put scrapeId 165 put scrapeId
125 put transId 166 put transId
126 forM_ infos put 167 forM_ infos put
127 168
128 Failed info -> do 169 Failed info -> do
129 put connId
130 put errorId 170 put errorId
131 put transId 171 put transId
132 put (encodeUtf8 info) 172 put (encodeUtf8 info)
133 173
134 174
135 get = do 175 get = do
136 cid <- get 176-- cid <- get
137 rid <- getWord32be 177 mid <- getWord32be
138 tid <- get 178 tid <- get
139 bod <- getBody rid 179 bod <- getBody mid
140 180
141 return $ Transaction { 181 return $ Transaction {
142 connId = cid 182 connId = initialConnectionId -- TODO
143 , transId = tid 183 , transId = tid
144 , body = bod 184 , body = bod
145 } 185 }
146 where 186 where
147 getBody :: MessageId -> Get Response 187 getBody :: MessageId -> Get Response
148 getBody msgId 188 getBody msgId
149 | msgId == connectId = return $ Connected 189 | msgId == connectId = Connected <$> get
150 | msgId == announceId = Announced <$> get 190 | msgId == announceId = Announced <$> get
151 | msgId == scrapeId = Scraped <$> many get 191 | msgId == scrapeId = Scraped <$> many get
152 | msgId == errorId = do 192 | msgId == errorId = do
@@ -154,38 +194,136 @@ instance Serialize (Transaction Response) where
154 case decodeUtf8' bs of 194 case decodeUtf8' bs of
155 Left ex -> fail (show ex) 195 Left ex -> fail (show ex)
156 Right msg -> return $ Failed msg 196 Right msg -> return $ Failed msg
157 | otherwise = fail "unknown message id" 197 | otherwise = fail $ "unknown message id: " ++ show msgId
158 198
159maxPacketSize :: Int 199{-----------------------------------------------------------------------
160maxPacketSize = 98 -- announce request packet 200 Connection
201-----------------------------------------------------------------------}
161 202
162call :: Request -> IO Response 203connectionLifetime :: NominalDiffTime
163call request = do 204connectionLifetime = 60
164 tid <- genTransactionId 205
165 let trans = Transaction initialConnectionId tid request 206connectionLifetimeServer :: NominalDiffTime
166 207connectionLifetimeServer = 120
167 let addr = error "TODO"
168 sock <- socket AF_INET Datagram defaultProtocol
169 BS.sendAllTo sock (encode trans) addr
170 (resp, addr') <- BS.recvFrom sock 4096
171 if addr' /= addr
172 then error "address mismatch"
173 else case decode resp of
174 Left msg -> error msg
175 Right (Transaction {..}) -> do
176 if tid /= transId
177 then error "transaction id mismatch"
178 else return body
179 208
180data Connection = Connection 209data Connection = Connection
210 { connectionId :: ConnId
211 , connectionTimestamp :: UTCTime
212 } deriving Show
213
214initialConnection :: IO Connection
215initialConnection = Connection initialConnectionId <$> getCurrentTime
181 216
182type URI = () 217isExpired :: Connection -> IO Bool
218isExpired Connection {..} = do
219 currentTime <- getCurrentTime
220 let timeDiff = diffUTCTime currentTime connectionTimestamp
221 return $ timeDiff > connectionLifetime
183 222
184connectTracker :: URI -> IO Connection 223{-----------------------------------------------------------------------
185connectTracker = undefined 224 RPC
225-----------------------------------------------------------------------}
186 226
187announceTracker :: Connection -> AnnounceQuery -> IO AnnounceInfo 227maxPacketSize :: Int
188announceTracker = undefined 228maxPacketSize = 98 -- announce request packet
189 229
190scrape :: Connection -> ScrapeQuery -> IO [ScrapeInfo] 230setPort :: PortNumber -> SockAddr -> SockAddr
191scrape = undefined \ No newline at end of file 231setPort p (SockAddrInet _ h) = SockAddrInet p h
232setPort p (SockAddrInet6 _ f h s) = SockAddrInet6 p f h s
233setPort _ addr = addr
234
235getTrackerAddr :: URI -> IO SockAddr
236getTrackerAddr URI { uriAuthority = Just (URIAuth {..}) } = do
237 infos <- getAddrInfo Nothing (Just uriRegName) Nothing
238 let port = fromMaybe 0 (readMaybe (L.drop 1 uriPort) :: Maybe Int)
239 case infos of
240 AddrInfo {..} : _ -> return $ setPort (fromIntegral port) addrAddress
241 _ -> fail "getTrackerAddr: unable to lookup host addr"
242getTrackerAddr _ = fail "getTrackerAddr: hostname unknown"
243
244call :: SockAddr -> ByteString -> IO ByteString
245call addr arg = bracket open close rpc
246 where
247 open = socket AF_INET Datagram defaultProtocol
248 rpc sock = do
249 BS.sendAllTo sock arg addr
250 (res, addr') <- BS.recvFrom sock maxPacketSize
251 unless (addr' == addr) $ do
252 throwIO $ userError "address mismatch"
253 return res
254
255-- TODO retransmissions
256-- TODO blocking
257data UDPTracker = UDPTracker
258 { trackerURI :: URI
259 , trackerConnection :: IORef Connection
260 }
261
262updateConnection :: ConnId -> UDPTracker -> IO ()
263updateConnection cid UDPTracker {..} = do
264 newConnection <- Connection cid <$> getCurrentTime
265 writeIORef trackerConnection newConnection
266
267getConnectionId :: UDPTracker -> IO ConnId
268getConnectionId UDPTracker {..}
269 = connectionId <$> readIORef trackerConnection
270
271putTracker :: UDPTracker -> IO ()
272putTracker UDPTracker {..} = do
273 print trackerURI
274 print =<< readIORef trackerConnection
275
276transaction :: UDPTracker -> Request -> IO (Transaction Response)
277transaction tracker @ UDPTracker {..} request = do
278 cid <- getConnectionId tracker
279 tid <- genTransactionId
280 let trans = Transaction cid tid request
281
282 addr <- getTrackerAddr trackerURI
283 res <- call addr (encode trans)
284 case decode res of
285 Right (response @ Transaction {..})
286 | tid == transId -> return response
287 | otherwise -> throwIO $ userError "transaction id mismatch"
288 Left msg -> throwIO $ userError msg
289
290connectUDP :: UDPTracker -> IO ConnId
291connectUDP tracker = do
292 Transaction _ tid resp <- transaction tracker Connect
293 case resp of
294 Connected cid -> return cid
295
296initialTracker :: URI -> IO UDPTracker
297initialTracker uri = do
298 tracker <- UDPTracker uri <$> (newIORef =<< initialConnection)
299 connId <- connectUDP tracker
300 updateConnection connId tracker
301 return tracker
302
303freshConnection :: UDPTracker -> IO ()
304freshConnection tracker @ UDPTracker {..} = do
305 conn <- readIORef trackerConnection
306 expired <- isExpired conn
307 when expired $ do
308 connId <- connectUDP tracker
309 updateConnection connId tracker
310
311{-
312
313announceUDP :: UDPTracker -> AnnounceQuery -> IO AnnounceInfo
314announceUDP t query = do
315 Transaction tid cid resp <- call transaction (Announce query)
316 case resp of
317 Announced info -> return info
318 _ -> fail "response type mismatch"
319
320scrapeUDP :: UDPTracker -> ScrapeQuery -> IO Scrape
321scrapeUDP UDPTracker {..} query = do
322 resp <- call trackerURI $ Scrape query
323 case resp of
324 Scraped scrape -> return undefined
325
326instance Tracker UDPTracker where
327 announce = announceUDP
328 scrape_ = scrapeUDP
329-} \ No newline at end of file