1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Peer
7 import Prelude hiding (lookup, concat, replicate, splitAt, writeFile)
9 import System.IO (Handle, BufferMode(..), IOMode(..), SeekMode(..), withFile, hSeek, hSetBuffering)
10 import Data.ByteString (ByteString, pack, unpack, concat, hGet, hPut, singleton, writeFile)
11 import Data.ByteString.Lazy (fromStrict, fromChunks, toStrict)
12 import qualified Data.ByteString.Char8 as BC (replicate, pack, length)
13 import Network (connectTo, PortID(..))
14 import Data.Binary (Binary(..), decode, encode)
15 import Data.Binary.Put (putWord32be, putWord16be, putWord8)
16 import Data.Binary.Get (getWord32be, getWord16be, getWord8, runGet)
17 import Control.Monad (replicateM, liftM, forM)
18 import Control.Applicative ((<$>), liftA3)
20 import Data.Word (Word8)
21 import Data.Map (Map, fromList, toList, (!), mapWithKey, adjust)
23 import FuncTorrent.Metainfo (Info(..), Metainfo(..))
24 import FuncTorrent.Utils (splitN, splitNum)
30 -- PeerState is a misnomer
31 data PeerState = PeerState { handle :: Handle
34 , meInterested :: Bool
36 , heInterested :: Bool}
38 data PieceDlState = Pending
43 -- todo - map with index to a new data structure (peers who have that piece amd state)
44 data PieceData = PieceData { peers :: [Peer] -- ^ list of peers who have this piece
45 , state :: PieceDlState -- ^ state of the piece from download perspective.
46 , hash :: ByteString -- ^ piece hash
47 , len :: Integer } -- ^ piece length
49 -- which piece is with which peers
50 type PieceMap = Map Integer PieceData
52 -- | Peer is a PeerID, IP address, port tuple
53 data Peer = Peer ID IP Port
56 data PeerMsg = KeepAliveMsg
62 | BitFieldMsg ByteString
63 | RequestMsg Integer Integer Integer
64 | PieceMsg Integer Integer ByteString
65 | CancelMsg Integer Integer Integer
69 -- Make the initial Piece map, with the assumption that no peer has the
70 -- piece and that every piece is pending download.
71 mkPieceMap :: Integer -> ByteString -> [Integer] -> PieceMap
72 mkPieceMap numPieces pieceHash pLengths = fromList kvs
73 where kvs = [(i, PieceData { peers = []
77 | (i, h, pLen) <- zip3 [0..numPieces] hashes pLengths]
78 hashes = splitN (fromIntegral numPieces) pieceHash
80 havePiece :: PieceMap -> Integer -> Bool
82 state (pm ! index) == Have
84 genHandShakeMsg :: ByteString -> String -> ByteString
85 genHandShakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
86 where pstrlen = singleton 19
87 pstr = BC.pack "BitTorrent protocol"
88 reserved = BC.replicate 8 '\0'
89 peerID = BC.pack peer_id
91 handShake :: Peer -> ByteString -> String -> IO Handle
92 handShake peer@(Peer _ ip port) infoHash peerid = do
93 let hs = genHandShakeMsg infoHash peerid
94 h <- connectTo ip (PortNumber (fromIntegral port))
95 hSetBuffering h LineBuffering
97 putStrLn $ "--> handhake to peer: " ++ show peer
98 _ <- hGet h (length (unpack hs))
99 putStrLn $ "<-- handshake from peer: " ++ show peer
102 instance Binary PeerMsg where
103 put msg = case msg of
104 KeepAliveMsg -> putWord32be 0
105 ChokeMsg -> do putWord32be 1
107 UnChokeMsg -> do putWord32be 1
109 InterestedMsg -> do putWord32be 1
111 NotInterestedMsg -> do putWord32be 1
113 HaveMsg i -> do putWord32be 5
115 putWord32be (fromIntegral i)
116 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
118 mapM_ putWord8 bfList
119 where bfList = unpack bf
120 bfListLen = length bfList
121 RequestMsg i o l -> do putWord32be 13
123 putWord32be (fromIntegral i)
124 putWord32be (fromIntegral o)
125 putWord32be (fromIntegral l)
126 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
128 putWord32be (fromIntegral i)
129 putWord32be (fromIntegral o)
130 mapM_ putWord8 blockList
131 where blockList = unpack b
132 blocklen = length blockList
133 CancelMsg i o l -> do putWord32be 13
135 putWord32be (fromIntegral i)
136 putWord32be (fromIntegral o)
137 putWord32be (fromIntegral l)
138 PortMsg p -> do putWord32be 3
140 putWord16be (fromIntegral p)
146 1 -> return UnChokeMsg
147 2 -> return InterestedMsg
148 3 -> return NotInterestedMsg
149 4 -> liftM (HaveMsg . fromIntegral) getWord32be
150 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
151 6 -> liftA3 RequestMsg getInteger getInteger getInteger
152 where getInteger = fromIntegral <$> getWord32be
153 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
154 where getInteger = fromIntegral <$> getWord32be
155 8 -> liftA3 CancelMsg getInteger getInteger getInteger
156 where getInteger = fromIntegral <$> getWord32be
157 9 -> liftM (PortMsg . fromIntegral) getWord16be
158 _ -> error ("unknown message ID: " ++ show msgid)
160 getMsg :: Handle -> IO PeerMsg
165 then return KeepAliveMsg
168 return $ decode $ fromStrict $ concat [lBS, msg]
170 sendMsg :: Handle -> PeerMsg -> IO ()
172 let bsMsg = toStrict $ encode msg
176 bsToInt :: ByteString -> Int
177 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))
179 bitfieldToList :: [Word8] -> [Integer]
180 bitfieldToList bs = go bs 0
183 let setBits = [pos*8 + toInteger i | i <- [0..8], testBit b i]
185 setBits ++ go bs' (pos + 1)
187 createDummyFile :: FilePath -> Int -> IO ()
188 createDummyFile path size =
189 writeFile path (BC.replicate size '\0')
191 -- write into a file at a specific offet
192 writeFileAtOffset :: FilePath -> Integer -> ByteString -> IO ()
193 writeFileAtOffset path offset block =
194 withFile path WriteMode $ (\h -> do
195 _ <- hSeek h AbsoluteSeek offset
198 -- recvMsg :: Peer -> Handle -> Msg
199 msgLoop :: PeerState -> PieceMap -> IO ()
200 msgLoop pState pieceStatus | meInterested pState == False &&
201 heChoking pState == True = do
202 -- if me NOT Interested and she is Choking, tell her that
204 let h = handle pState
205 sendMsg h InterestedMsg
206 putStrLn $ "--> InterestedMsg to peer: " ++ show (peer pState)
207 msgLoop (pState { meInterested = True }) pieceStatus
208 | meInterested pState == True &&
209 heChoking pState == False =
210 -- if me Interested and she not Choking, send her a request
212 case pickPiece pieceStatus of
213 Nothing -> putStrLn "Nothing to download"
215 let pLen = len (pieceStatus ! workPiece)
216 _ <- downloadPiece (handle pState) workPiece pLen
217 -- TODO: verify the hash
218 msgLoop pState (adjust (\pieceData -> pieceData { state = Have }) workPiece pieceStatus)
220 msg <- getMsg (handle pState)
221 putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show (peer pState)
224 sendMsg (handle pState) KeepAliveMsg
225 putStrLn $ "--> " ++ "KeepAliveMsg to peer: " ++ show (peer pState)
226 msgLoop pState pieceStatus
227 BitFieldMsg bss -> do
228 let pieceList = bitfieldToList (unpack bss)
229 pieceStatus' = updatePieceAvailability pieceStatus (peer pState) pieceList
231 -- for each pieceIndex in pieceList, make an entry in the pieceStatus
232 -- map with pieceIndex as the key and modify the value to add the peer.
233 -- download each of the piece in order
234 msgLoop pState pieceStatus'
236 msgLoop (pState { heChoking = False }) pieceStatus
238 msgLoop pState pieceStatus
240 -- simple algorithm to pick piece.
241 -- pick the first piece from 0 that is not downloaded yet.
242 pickPiece :: PieceMap -> Maybe Integer
244 let pieceList = toList m
245 allPending = filter (\(_, v) -> state v == Pending) pieceList
251 updatePieceAvailability :: PieceMap -> Peer -> [Integer] -> PieceMap
252 updatePieceAvailability pieceStatus p pieceList =
253 mapWithKey (\k pd -> if k `elem` pieceList
254 then (pd { peers = p : (peers pd) })
257 handlePeerMsgs :: Peer -> Metainfo -> String -> IO ()
258 handlePeerMsgs p m peerId = do
259 h <- handShake p (infoHash m) peerId
260 let state = PeerState { handle = h
262 , heInterested = False
264 , meInterested = False
266 pieceHash = pieces (info m)
267 numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
268 pLen = pieceLength (info m)
269 fileLen = lengthInBytes (info m)
270 pieceStatus = mkPieceMap numPieces pieceHash (splitNum fileLen pLen)
271 msgLoop state pieceStatus
273 downloadPiece :: Handle -> Integer -> Integer -> IO [ByteString]
274 downloadPiece h index pieceLength = do
275 let chunks = splitNum pieceLength 16384
276 forM (zip [0..] chunks) (\(i, pLen) -> do
277 sendMsg h (RequestMsg index (i*pLen) pLen)
278 putStrLn $ "--> " ++ "RequestMsg for Piece "
279 ++ (show index) ++ ", part: " ++ show i ++ " of length: "
281 PieceMsg index begin block <- getMsg h
282 putStrLn $ " <-- PieceMsg for Piece: "