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, 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, 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 -- PeerState is a misnomer
31 data PeerState = PeerState { handle :: Handle
34 , meInterested :: Bool
36 , heInterested :: Bool}
38 -- Maintain info on every piece and the current state of it.
39 -- should probably be a TVar.
40 type Pieces = [PieceData]
42 data PieceDlState = Pending
47 -- todo - map with index to a new data structure (peers who have that piece amd state)
48 data PieceData = PieceData { peers :: [Peer] -- ^ list of peers who have this piece
49 , state :: PieceDlState -- ^ state of the piece from download perspective.
50 , hash :: ByteString } -- ^ piece hash
52 -- which piece is with which peers
53 type PieceMap = Map Integer PieceData
55 -- | Peer is a PeerID, IP address, port tuple
56 data Peer = Peer ID IP Port
59 data PeerMsg = KeepAliveMsg
65 | BitFieldMsg ByteString
66 | RequestMsg Integer Integer Integer
67 | PieceMsg Integer Integer ByteString
68 | CancelMsg Integer Integer Integer
72 -- Make the initial Piece map, with the assumption that no peer has the
73 -- piece and that every piece is pending download.
74 mkPieceMap :: Integer -> ByteString -> PieceMap
75 mkPieceMap numPieces pieceHash = fromList kvs
76 where kvs = [(i, PieceData { peers = []
78 , hash = h }) | (i, h) <- zip [0..numPieces] hashes]
79 hashes = splitN (fromIntegral numPieces) pieceHash
81 havePiece :: PieceMap -> Integer -> Bool
83 state (pm ! index) == Have
85 genHandShakeMsg :: ByteString -> String -> ByteString
86 genHandShakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
87 where pstrlen = singleton 19
88 pstr = BC.pack "BitTorrent protocol"
89 reserved = BC.replicate 8 '\0'
90 peerID = BC.pack peer_id
92 handShake :: Peer -> ByteString -> String -> IO Handle
93 handShake peer@(Peer _ ip port) infoHash peerid = do
94 let hs = genHandShakeMsg infoHash peerid
95 h <- connectTo ip (PortNumber (fromIntegral port))
96 hSetBuffering h LineBuffering
98 putStrLn $ "--> handhake to peer: " ++ show peer
99 rlenBS <- hGet h (length (unpack hs))
100 putStrLn $ "<-- handshake from peer: " ++ show peer
103 instance Binary PeerMsg where
104 put msg = case msg of
105 KeepAliveMsg -> putWord32be 0
106 ChokeMsg -> do putWord32be 1
108 UnChokeMsg -> do putWord32be 1
110 InterestedMsg -> do putWord32be 1
112 NotInterestedMsg -> do putWord32be 1
114 HaveMsg i -> do putWord32be 5
116 putWord32be (fromIntegral i)
117 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
119 mapM_ putWord8 bfList
120 where bfList = unpack bf
121 bfListLen = length bfList
122 RequestMsg i o l -> do putWord32be 13
124 putWord32be (fromIntegral i)
125 putWord32be (fromIntegral o)
126 putWord32be (fromIntegral l)
127 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
129 putWord32be (fromIntegral i)
130 putWord32be (fromIntegral o)
131 mapM_ putWord8 blockList
132 where blockList = unpack b
133 blocklen = length blockList
134 CancelMsg i o l -> do putWord32be 13
136 putWord32be (fromIntegral i)
137 putWord32be (fromIntegral o)
138 putWord32be (fromIntegral l)
139 PortMsg p -> do putWord32be 3
141 putWord16be (fromIntegral p)
147 1 -> return UnChokeMsg
148 2 -> return InterestedMsg
149 3 -> return NotInterestedMsg
150 4 -> liftM (HaveMsg . fromIntegral) getWord32be
151 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
152 6 -> liftA3 RequestMsg getInteger getInteger getInteger
153 where getInteger = fromIntegral <$> getWord32be
154 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
155 where getInteger = fromIntegral <$> getWord32be
156 8 -> liftA3 CancelMsg getInteger getInteger getInteger
157 where getInteger = fromIntegral <$> getWord32be
158 9 -> liftM (PortMsg . fromIntegral) getWord16be
159 _ -> error ("unknown message ID: " ++ show msgid)
161 getMsg :: Handle -> IO PeerMsg
166 then return KeepAliveMsg
169 return $ decode $ fromStrict $ concat [lBS, msg]
171 sendMsg :: Handle -> PeerMsg -> IO ()
173 let bsMsg = toStrict $ encode msg
177 bsToInt :: ByteString -> Int
178 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))
180 bitfieldToList :: [Word8] -> [Integer]
181 bitfieldToList bs = go bs 0
184 let setBits = [pos*8 + toInteger i | i <- [0..8], testBit b i]
186 setBits ++ go bs' (pos + 1)
188 -- downloadPiece :: Integer -> Handle -> IO ()
190 createDummyFile :: FilePath -> Int -> IO ()
191 createDummyFile path size =
192 writeFile path (BC.replicate size '\0')
194 -- loop1 :: shake hands with all peers, find out the pieces they have, form PieceData.
195 -- recvMsg :: Peer -> Handle -> Msg
196 msgLoop :: PeerState -> PieceMap -> IO ()
197 msgLoop state pieceStatus = do
198 -- if meInterested and he NOT Choking, pick a piece to download
199 -- and send a requestmsg.
200 let isMeInterested = meInterested state
201 isHeChoking = heChoking state
202 if (isMeInterested && isHeChoking)
206 sendMsg h InterestedMsg
207 putStrLn $ "--> InterestedMsg to peer: " ++ show (peer state)
208 msgLoop state pieceStatus
211 msg <- getMsg (handle state)
212 putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show (peer state)
215 sendMsg (handle state) KeepAliveMsg
216 msgLoop state pieceStatus
217 BitFieldMsg bss -> do
218 let pieceList = bitfieldToList (unpack bss)
220 -- for each pieceIndex in pieceList, make an entry in the pieceStatus
221 -- map with pieceIndex as the key and modify the value to add the peer.
222 -- download each of the piece in order
224 msgLoop state pieceStatus
226 msgLoop (state {heChoking = False}) pieceStatus
228 msgLoop state pieceStatus
230 handlePeerMsgs :: Peer -> Metainfo -> String -> (String -> IO ()) -> IO ()
231 handlePeerMsgs p m peerId logFn = do
232 h <- handShake p (infoHash m) peerId
234 let state = PeerState { handle = h
236 , heInterested = False
238 , meInterested = True
239 , meChoking = False }
240 pieceHash = (pieces (info m))
241 numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
242 pieceStatus = mkPieceMap numPieces pieceHash
243 msgLoop state pieceStatus