1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Peer
7 import Prelude hiding (lookup, concat, replicate, splitAt, empty, writeFile)
9 import System.IO (Handle, BufferMode(..), hSetBuffering)
10 import Data.ByteString (ByteString, pack, unpack, concat, hGet, hPut, singleton, writeFile)
11 import Data.ByteString.Lazy (fromStrict, fromChunks)
12 import qualified Data.ByteString.Char8 as BC (replicate, pack, length)
13 import Network (connectTo, PortID(..))
14 import Data.Binary (Binary(..), decode)
15 import Data.Binary.Put (putWord32be, putWord16be, putWord8)
16 import Data.Binary.Get (getWord32be, getWord16be, getWord8, runGet)
17 import Control.Monad (replicateM, liftM, forever)
18 import Control.Applicative ((<$>), liftA3)
20 import Data.Word (Word8)
21 import Data.Map (Map(..), fromList)
23 import FuncTorrent.Metainfo (Info(..), Metainfo(..))
24 import FuncTorrent.Utils (splitN)
30 data PeerState = PeerState { handle :: Handle
32 , am_interested :: Bool
33 , peer_choking :: Bool
34 , peer_interested :: Bool}
36 -- Maintain info on every piece and the current state of it.
37 -- should probably be a TVar.
38 type Pieces = [PieceData]
40 data PieceDlState = Pending
45 -- todo - map with index to a new data structure (peers who have that piece amd state)
46 data PieceData = PieceData { peers :: [Peer] -- ^ list of peers who have this piece
47 , state :: PieceDlState -- ^ state of the piece from download perspective.
48 , hash :: ByteString } -- ^ piece hash
50 -- which piece is with which peers
51 type PieceMap = Map Integer PieceData
53 -- | Peer is a PeerID, IP address, port tuple
54 data Peer = Peer ID IP Port
57 data PeerMsg = KeepAliveMsg
63 | BitFieldMsg ByteString
64 | RequestMsg Integer Integer Integer
65 | PieceMsg Integer Integer ByteString
66 | CancelMsg Integer Integer Integer
70 -- Make the initial Piece map, with the assumption that no peer has the
71 -- piece and that every piece is pending download.
72 mkPieceMap :: Integer -> ByteString -> Map Integer PieceData
73 mkPieceMap numPieces pieceHash = fromList kvs
74 where kvs = [(i, PieceData { peers = []
76 , hash = h }) | (i, h) <- zip [0..numPieces] hashes]
77 hashes = splitN (fromIntegral numPieces) pieceHash
79 genHandShakeMsg :: ByteString -> String -> ByteString
80 genHandShakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
81 where pstrlen = singleton 19
82 pstr = BC.pack "BitTorrent protocol"
83 reserved = BC.replicate 8 '\0'
84 peerID = BC.pack peer_id
86 handShake :: Peer -> ByteString -> String -> IO Handle
87 handShake (Peer _ ip port) infoHash peerid = do
88 let hs = genHandShakeMsg infoHash peerid
89 h <- connectTo ip (PortNumber (fromIntegral port))
90 hSetBuffering h LineBuffering
92 rlenBS <- hGet h (length (unpack hs))
93 putStrLn $ "got handshake from peer: " ++ show rlenBS
96 instance Binary PeerMsg where
98 KeepAliveMsg -> putWord32be 0
99 ChokeMsg -> do putWord32be 1
101 UnChokeMsg -> do putWord32be 1
103 InterestedMsg -> do putWord32be 1
105 NotInterestedMsg -> do putWord32be 1
107 HaveMsg i -> do putWord32be 5
109 putWord32be (fromIntegral i)
110 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
112 mapM_ putWord8 bfList
113 where bfList = unpack bf
114 bfListLen = length bfList
115 RequestMsg i o l -> do putWord32be 13
117 putWord32be (fromIntegral i)
118 putWord32be (fromIntegral o)
119 putWord32be (fromIntegral l)
120 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
122 putWord32be (fromIntegral i)
123 putWord32be (fromIntegral o)
124 mapM_ putWord8 blockList
125 where blockList = unpack b
126 blocklen = length blockList
127 CancelMsg i o l -> do putWord32be 13
129 putWord32be (fromIntegral i)
130 putWord32be (fromIntegral o)
131 putWord32be (fromIntegral l)
132 PortMsg p -> do putWord32be 3
134 putWord16be (fromIntegral p)
140 1 -> return UnChokeMsg
141 2 -> return InterestedMsg
142 3 -> return NotInterestedMsg
143 4 -> liftM (HaveMsg . fromIntegral) getWord32be
144 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
145 6 -> liftA3 RequestMsg getInteger getInteger getInteger
146 where getInteger = fromIntegral <$> getWord32be
147 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
148 where getInteger = fromIntegral <$> getWord32be
149 8 -> liftA3 CancelMsg getInteger getInteger getInteger
150 where getInteger = fromIntegral <$> getWord32be
151 9 -> liftM (PortMsg . fromIntegral) getWord16be
152 _ -> error ("unknown message ID: " ++ show msgid)
154 getMsg :: Handle -> IO PeerMsg
159 then return KeepAliveMsg
162 return $ decode $ fromStrict $ concat [lBS, msg]
165 bsToInt :: ByteString -> Int
166 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))
168 bitfieldToList :: [Word8] -> [Integer]
169 bitfieldToList bs = go bs 0
172 let setBits = [pos*8 + (toInteger i) | i <- [0..8], testBit b i]
174 setBits ++ (go bs' (pos + 1))
176 -- downloadPiece :: Integer -> Handle -> IO ()
178 createDummyFile :: FilePath -> Int -> IO ()
179 createDummyFile path size = do
180 writeFile path (BC.replicate size '\0')
182 -- loop1 :: shake hands with all peers, find out the pieces they have, form PieceData.
183 -- recvMsg :: Peer -> Handle -> Msg
184 msgLoop :: Handle -> ByteString -> IO ()
185 msgLoop h pieceHash =
186 let numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
187 pieceStatus = mkPieceMap numPieces pieceHash
191 putStrLn $ "got a " ++ show msg
193 BitFieldMsg bss -> do
194 let pieceList = bitfieldToList (unpack bss)
195 putStrLn (show pieceList)
196 -- download each of the piece in order
197 _ -> putStrLn (show msg)
199 handlePeerMsgs :: Peer -> Metainfo -> String -> (String -> IO ()) -> IO ()
200 handlePeerMsgs p m peerId logFn = do
201 h <- handShake p (infoHash m) peerId
203 msgLoop h (pieces (info m))