import qualified FuncTorrent.FileSystem as FS (MsgChannel, Stats(..), getStats)
import FuncTorrent.Network (sendGetRequest)
import FuncTorrent.Peer (Peer(..))
-import FuncTorrent.Utils (splitN)
-import FuncTorrent.Tracker.Types(TState(..), TrackerResponse(..), Port, IP)
+import FuncTorrent.Utils (splitN, toIP, toPort, IP, Port)
+import FuncTorrent.Tracker.Types(TState(..), TrackerResponse(..))
--- | URL encode hash as per RFC1738
makePeer :: ByteString -> Peer
makePeer peer = Peer "" (toIP ip') (toPort port')
where (ip', port') = splitAt 4 peer
-
-toPort :: ByteString -> Port
-toPort = read . ("0x" ++) . unpack . B16.encode
-
-toIP :: ByteString -> IP
-toIP = Data.List.intercalate "." .
- map (show . toInt . ("0x" ++) . unpack) .
- splitN 2 . B16.encode
-
-toInt :: String -> Integer
-toInt = read
-}
{-# LANGUAGE OverloadedStrings #-}
-module Functorrent.Tracker.Udp
+module FuncTorrent.Tracker.Udp
(
) where
import Control.Applicative (liftA2)
-import Control.Monad.Error (ErrorT)
-import Control.Monad.Reader (ReaderT, runReaderT, ask)
+import Control.Monad.Reader (ReaderT, runReaderT, ask, liftIO)
import Data.Binary (Binary(..), encode, decode)
import Data.Binary.Get (Get, isEmpty, getWord32be, getByteString)
import Data.Binary.Put (putWord16be, putWord64be, putWord32be, putByteString)
-import Data.ByteString.Char8 as BC
-import Data.ByteString.Lazy (fromStrict)
-import Data.Word (Word32)
-import Network.Socket (Socket, SockAddr, sendTo, recvFrom)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as BC
+import Data.ByteString.Lazy (fromStrict, toStrict)
+import Data.Word (Word32, Word64)
+import Network.Socket (Socket, Family( AF_INET ), SocketType( Datagram ), defaultProtocol, SockAddr(..), socket, inet_addr)
+import Network.Socket.ByteString (sendTo, recvFrom)
import System.Random (randomIO)
-import FuncTorrent.Tracker.Types (TrackerEventState(..), IP, Port)
+import FuncTorrent.Tracker.Types (TrackerEventState(..))
+import FuncTorrent.Utils (IP, Port, toIP, toPort)
-- UDP tracker: http://bittorrent.org/beps/bep_0015.html
data Action = Connect
| ScrapeReq Integer Integer ByteString
deriving (Show, Eq)
-data UDPResponse = ConnectResp Integer Integer -- transaction_id connection_id
+data UDPResponse = ConnectResp Word32 Word64 -- transaction_id connection_id
| AnnounceResp Integer Integer Integer Integer [(IP, Port)] -- transaction_id interval leechers seeders [(ip, port)]
| ScrapeResp Integer Integer Integer Integer
| ErrorResp Integer String
3 -> do -- error response
tid <- fromIntegral <$> getWord32be
bs <- getByteString 4
- return $ ErrorResp tid $ unpack bs
+ return $ ErrorResp tid $ BC.unpack bs
_ -> error ("unknown response action type: " ++ show a)
-sendRequest :: UDPTrackerHandle -> UDPRequest -> IO ()
+sendRequest :: UDPTrackerHandle -> ByteString -> IO ()
sendRequest h req = do
n <- sendTo (sock h) req (addr h)
-- sanity check with n?
return ()
-recvResponse :: UDPTrackerHandle -> ErrorT String IO UDPResponse
+recvResponse :: UDPTrackerHandle -> IO UDPResponse
recvResponse h = do
- (bs, nbytes, saddr) <- recvFrom (sock h) 20
- -- check if nbytes is at least 16 bytes long
+ (bs, saddr) <- recvFrom (sock h) 32
return $ decode $ fromStrict bs
-connectRequest :: ReaderT UDPTrackerHandle IO Integer
+connectRequest :: ReaderT UDPTrackerHandle IO ()
connectRequest = do
h <- ask
let pkt = encode $ ConnectReq (tid h)
- sendRequest h pkt
+ liftIO $ sendRequest h (toStrict pkt)
-connectResponse :: ReaderT UDPTrackerHandle IO Bool
-connectResponse = do
+connectResponse :: Word32 -> ReaderT UDPTrackerHandle IO Bool
+connectResponse itid = do
h <- ask
-
+ resp <- liftIO $ recvResponse h
+ -- check if nbytes is at least 16 bytes long
+ case resp of
+ (ConnectResp tid cid) -> return $ tid == itid
+ _ -> return False
getIPPortPairs :: Get [(IP, Port)]
getIPPortPairs = do
ipportpairs <- getIPPortPairs
return $ (ip, port) : ipportpairs
-getResponse :: Socket -> IO UDPResponse
-getResponse s = do
- -- connect packet is 16 bytes long
- -- announce packet is atleast 20 bytes long
- bs <- recv s (16*1024)
- return $ decode $ fromStrict bs
-
-
-udpTrackerLoop :: PortNumber -> String -> Metainfo -> TState -> IO String
-udpTrackerLoop port peerId m st = do
- -- h <- connectTo "exodus.desync.com" (PortNumber 6969)
+startSession :: IP -> Port -> IO UDPTrackerHandle
+startSession ip port = do
s <- socket AF_INET Datagram defaultProtocol
- hostAddr <- inet_addr "185.37.101.229"
+ hostAddr <- inet_addr ip
putStrLn "connected to tracker"
- _ <- sendTo s (toStrict $ encode (ConnectReq 42)) (SockAddrInet 2710 hostAddr)
- putStrLn "--> sent ConnectReq to tracker"
- resp <- recv s 16
- putStrLn "<-- recv ConnectResp from tracker"
- return $ show resp
+ r <- randomIO
+ return $ UDPTrackerHandle { sock = s
+ , tid = r
+ , addr = (SockAddrInet (fromIntegral port) hostAddr) }
+
+-- closeSession :: UDPTrackerHandle
+
-}
module FuncTorrent.Utils
- (createDummyFile,
- writeFileAtOffset,
- readFileAtOffset,
- splitNum,
- splitN,
- verifyHash
+ ( createDummyFile
+ , writeFileAtOffset
+ , readFileAtOffset
+ , splitNum
+ , splitN
+ , verifyHash
+ , IP
+ , Port
+ , toIP
+ , toPort
)
where
import qualified Crypto.Hash.SHA1 as SHA1 (hash)
import Control.Exception.Base (IOException, try)
import Data.ByteString (ByteString, writeFile, hPut, hGet, take)
+import qualified Data.ByteString.Base16 as B16 (encode)
import qualified Data.ByteString.Char8 as BC
+import Data.List (intercalate)
import System.IO (Handle, hSeek, SeekMode(..))
import System.Directory (doesFileExist)
+type IP = String
+type Port = Integer
+
splitN :: Int -> BC.ByteString -> [BC.ByteString]
splitN n bs | BC.null bs = []
| otherwise = BC.take n bs : splitN n (BC.drop n bs)
verifyHash :: ByteString -> ByteString -> Bool
verifyHash bs pieceHash =
take 20 (SHA1.hash bs) == pieceHash
+
+toPort :: ByteString -> Port
+toPort = read . ("0x" ++) . BC.unpack . B16.encode
+
+toIP :: ByteString -> IP
+toIP = Data.List.intercalate "." .
+ map (show . toInt . ("0x" ++) . BC.unpack) .
+ splitN 2 . B16.encode
+
+toInt :: String -> Integer
+toInt = read