]> git.rkrishnan.org Git - functorrent.git/blobdiff - src/FuncTorrent/Tracker/Udp.hs
starting with a clean slate
[functorrent.git] / src / FuncTorrent / Tracker / Udp.hs
diff --git a/src/FuncTorrent/Tracker/Udp.hs b/src/FuncTorrent/Tracker/Udp.hs
deleted file mode 100644 (file)
index de99ce2..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-{-
- - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
- -
- - This file is part of FuncTorrent.
- -
- - FuncTorrent is free software; you can redistribute it and/or modify
- - it under the terms of the GNU General Public License as published by
- - the Free Software Foundation; either version 3 of the License, or
- - (at your option) any later version.
- -
- - FuncTorrent is distributed in the hope that it will be useful,
- - but WITHOUT ANY WARRANTY; without even the implied warranty of
- - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- - GNU General Public License for more details.
- -
- - You should have received a copy of the GNU General Public License
- - along with FuncTorrent; if not,  see <http://www.gnu.org/licenses/>
- -}
-
-{-# LANGUAGE OverloadedStrings #-}
-module FuncTorrent.Tracker.Udp
-       (trackerLoop
-       ) where
-
-import Control.Applicative (liftA2)
-import Control.Monad (forever, void)
-import Control.Concurrent (threadDelay)
-import Control.Concurrent.MVar (readMVar, putMVar, isEmptyMVar, swapMVar)
-import Control.Monad.Reader (ReaderT, runReaderT, ask, liftIO)
-import Data.Binary (Binary(..), encode, decode)
-import Data.Binary.Get (Get, isEmpty, getWord32be, getWord64be, getByteString)
-import Data.Binary.Put (putWord16be, putWord64be, putWord32be, putByteString)
-import Data.ByteString (ByteString)
-import qualified Data.ByteString.Char8 as BC
-import Data.ByteString.Lazy (fromStrict, toStrict)
-import Data.Word (Word16, Word32, Word64)
-import Network (PortNumber)
-import Network.Socket (Socket, Family( AF_INET ), SocketType( Datagram ), defaultProtocol, SockAddr(..), socket, close, getAddrInfo, addrAddress, SockAddr(..))
-import Network.Socket.ByteString (sendTo, recvFrom)
-import System.Random (randomIO)
-import System.Timeout (timeout)
-
-import FuncTorrent.PeerMsgs (Peer(..))
-import FuncTorrent.Tracker.Types (TrackerEventState(..), TState(..), UdpTrackerResponse(..))
-import FuncTorrent.Utils (Port, toIP, toPort, getHostname, getPort)
-import qualified FuncTorrent.FileSystem as FS (MsgChannel, Stats(..), getStats)
-
--- UDP tracker: http://bittorrent.org/beps/bep_0015.html
-data Action = Connect
-            | Announce
-            | Scrape
-            deriving (Show, Eq)
-
-data UDPRequest = ConnectReq Word32
-                | AnnounceReq Word64 Word32 ByteString String Word64 Word64 Word64 TrackerEventState Word16
-                | ScrapeReq Integer Integer ByteString
-                deriving (Show, Eq)
-
-data UDPResponse = ConnectResp Word32 Word64 -- transaction_id connection_id
-                 | AnnounceResp Word32 Word32 Word32 Word32 [Peer] -- transaction_id interval leechers seeders [(ip, port)]
-                 | ScrapeResp Integer Integer Integer Integer
-                 | ErrorResp Integer String
-                 deriving (Show, Eq)
-
-data UDPTrackerHandle = UDPTrackerHandle { sock :: Socket
-                                         , addr :: SockAddr
-                                         }
-
-actionToInteger :: Action -> Integer
-actionToInteger Connect  = 0
-actionToInteger Announce = 1
-actionToInteger Scrape   = 2
-
-eventToInteger :: TrackerEventState -> Integer
-eventToInteger None = 0
-eventToInteger Completed = 1
-eventToInteger Started = 2
-eventToInteger Stopped = 3
-
-instance Binary UDPRequest where
-  put (ConnectReq transId) = do
-    putWord64be 0x41727101980
-    putWord32be $ fromIntegral (actionToInteger Connect)
-    putWord32be (fromIntegral transId)
-  put (AnnounceReq connId transId infohash peerId down left' up event port) = do
-    putWord64be $ fromIntegral connId
-    putWord32be $ fromIntegral (actionToInteger Announce)
-    putWord32be $ fromIntegral transId
-    putByteString infohash
-    putByteString (BC.pack peerId)
-    putWord64be (fromIntegral down)
-    putWord64be (fromIntegral left')
-    putWord64be (fromIntegral up)
-    putWord32be $ fromIntegral (eventToInteger event)
-    putWord32be 0
-    putWord32be 0
-    putWord32be 10
-    putWord16be $ fromIntegral port
-  put ScrapeReq {} = undefined
-  get = undefined
-
-instance Binary UDPResponse where
-  put = undefined
-  get = do
-    a <- getWord32be -- action
-    case a of
-      0 -> liftA2 ConnectResp (fromIntegral <$> getWord32be) (fromIntegral <$> getWord64be)
-      1 -> do
-        tid' <- fromIntegral <$> getWord32be
-        interval' <- fromIntegral <$> getWord32be
-        l <- getWord32be -- leechers
-        s <- getWord32be -- seeders
-        ipportpairs <- getIPPortPairs -- [(ip, port)]
-        return $ AnnounceResp tid' interval' l s ipportpairs
-      2 -> do
-        tid' <- fromIntegral <$> getWord32be
-        _ <- getWord32be
-        _ <- getWord32be
-        _ <- getWord32be
-        return $ ScrapeResp tid' 0 0 0
-      3 -> do -- error response
-        tid' <- fromIntegral <$> getWord32be
-        bs  <- getByteString 4
-        return $ ErrorResp tid' $ BC.unpack bs
-      _ -> error ("unknown response action type: " ++ show a)
-
-sendRequest :: UDPTrackerHandle -> ByteString -> IO ()
-sendRequest h req = do
-  n <- sendTo (sock h) req (addr h)
-  -- sanity check with n?
-  return ()
-
-recvResponse :: UDPTrackerHandle -> IO UDPResponse
-recvResponse h = do
-  (bs, saddr) <- recvFrom (sock h) (16*1024)
-  return $ decode $ fromStrict bs
-
-connectRequest :: ReaderT UDPTrackerHandle IO Word32
-connectRequest = do
-  h <- ask
-  tidi <- liftIO randomIO
-  let pkt = encode $ ConnectReq tidi
-  liftIO $ sendRequest h (toStrict pkt)
-  return tidi
-
-connectResponse :: Word32 -> ReaderT UDPTrackerHandle IO Word64
-connectResponse tid = do
-  h <- ask
-  resp <- liftIO $ recvResponse h
-  -- check if nbytes is at least 16 bytes long
-  case resp of
-    (ConnectResp tidr cid) ->
-      if tidr == tid
-      then do
-        liftIO $ putStrLn "connect succeeded"
-        return cid
-      else
-        return 0
-    _                      -> return 0
-
-announceRequest :: Word64 -> ByteString -> String -> Word64 -> Word64 -> Word64 -> Word16 -> ReaderT UDPTrackerHandle IO Word32
-announceRequest cid infohash peerId up down left' port = do
-  h <- ask
-  tidi <- liftIO randomIO
-  let pkt = encode $ AnnounceReq cid tidi infohash peerId down left' up None port
-  liftIO $ sendRequest h (toStrict pkt)
-  return tidi
-
-announceResponse :: Word32 -> ReaderT UDPTrackerHandle IO UdpTrackerResponse
-announceResponse tid = do
-  h <- ask
-  resp <- liftIO $ recvResponse h
-  case resp of
-    (AnnounceResp tidr interval' ss ls xs) ->
-      if tidr == tid
-      then do
-        liftIO $ putStrLn "announce succeeded"
-        return $ UdpTrackerResponse ls ss interval' xs
-      else
-        return $ UdpTrackerResponse 0 0 0 []
-    _ -> return $ UdpTrackerResponse 0 0 0 []
-
-getIPPortPairs :: Get [Peer]
-getIPPortPairs = do
-  empty <- isEmpty
-  if empty
-    then return []
-    else do
-    ip <- toIP <$> getByteString 4
-    port <- toPort <$> getByteString 2
-    ipportpairs <- getIPPortPairs
-    return $ Peer ip port : ipportpairs
-
-startSession :: String -> Port -> IO UDPTrackerHandle
-startSession host port = do
-  s <- socket AF_INET Datagram defaultProtocol
-  addrinfos <- getAddrInfo Nothing (Just host) (Just (show port))
-  let (SockAddrInet p ip) = addrAddress $ head addrinfos
-  putStrLn "connected to tracker"
-  return UDPTrackerHandle { sock = s
-                          , addr = SockAddrInet (fromIntegral port) ip }
-  
-closeSession :: UDPTrackerHandle -> IO ()
-closeSession (UDPTrackerHandle s _) = close s
-
-trackerLoop :: String -> PortNumber -> String -> ByteString -> FS.MsgChannel -> TState -> IO ()
-trackerLoop url sport peerId infohash fschan tstate = forever $ do
-  st <- readMVar <$> FS.getStats fschan
-  up <- fmap FS.bytesRead st
-  down <- fmap FS.bytesWritten st
-  handle <- startSession host port
-  stats <- timeout (15*oneSec) $ worker handle up down
-  case stats of
-    Nothing -> closeSession handle
-    Just stats' -> do
-      ps <- isEmptyMVar $ connectedPeers tstate
-      if ps
-        then
-        putMVar (connectedPeers tstate) (peers stats')
-        else
-        void $ swapMVar (connectedPeers tstate) (peers stats')
-      threadDelay $ fromIntegral (interval stats') * oneSec
-      return ()
-  where
-    oneSec = 1000000
-    port = getPort url
-    host = getHostname url
-    worker handle up down = flip runReaderT handle $ do
-      t1 <- connectRequest
-      cid <- connectResponse t1
-      t2 <- announceRequest cid infohash peerId (fromIntegral up) (fromIntegral down) (fromIntegral (left tstate)) (fromIntegral sport)
-      announceResponse t2