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 -> PieceMap
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 havePiece :: PieceMap -> Integer -> Bool
81 state (pm ! index) == Have
83 genHandShakeMsg :: ByteString -> String -> ByteString
84 genHandShakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
85 where pstrlen = singleton 19
86 pstr = BC.pack "BitTorrent protocol"
87 reserved = BC.replicate 8 '\0'
88 peerID = BC.pack peer_id
90 handShake :: Peer -> ByteString -> String -> IO Handle
91 handShake (Peer _ ip port) infoHash peerid = do
92 let hs = genHandShakeMsg infoHash peerid
93 h <- connectTo ip (PortNumber (fromIntegral port))
94 hSetBuffering h LineBuffering
96 rlenBS <- hGet h (length (unpack hs))
97 putStrLn $ "got handshake from peer: " ++ show rlenBS
100 instance Binary PeerMsg where
101 put msg = case msg of
102 KeepAliveMsg -> putWord32be 0
103 ChokeMsg -> do putWord32be 1
105 UnChokeMsg -> do putWord32be 1
107 InterestedMsg -> do putWord32be 1
109 NotInterestedMsg -> do putWord32be 1
111 HaveMsg i -> do putWord32be 5
113 putWord32be (fromIntegral i)
114 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
116 mapM_ putWord8 bfList
117 where bfList = unpack bf
118 bfListLen = length bfList
119 RequestMsg i o l -> do putWord32be 13
121 putWord32be (fromIntegral i)
122 putWord32be (fromIntegral o)
123 putWord32be (fromIntegral l)
124 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
126 putWord32be (fromIntegral i)
127 putWord32be (fromIntegral o)
128 mapM_ putWord8 blockList
129 where blockList = unpack b
130 blocklen = length blockList
131 CancelMsg i o l -> do putWord32be 13
133 putWord32be (fromIntegral i)
134 putWord32be (fromIntegral o)
135 putWord32be (fromIntegral l)
136 PortMsg p -> do putWord32be 3
138 putWord16be (fromIntegral p)
144 1 -> return UnChokeMsg
145 2 -> return InterestedMsg
146 3 -> return NotInterestedMsg
147 4 -> liftM (HaveMsg . fromIntegral) getWord32be
148 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
149 6 -> liftA3 RequestMsg getInteger getInteger getInteger
150 where getInteger = fromIntegral <$> getWord32be
151 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
152 where getInteger = fromIntegral <$> getWord32be
153 8 -> liftA3 CancelMsg getInteger getInteger getInteger
154 where getInteger = fromIntegral <$> getWord32be
155 9 -> liftM (PortMsg . fromIntegral) getWord16be
156 _ -> error ("unknown message ID: " ++ show msgid)
158 getMsg :: Handle -> IO PeerMsg
163 then return KeepAliveMsg
166 return $ decode $ fromStrict $ concat [lBS, msg]
169 bsToInt :: ByteString -> Int
170 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))
172 bitfieldToList :: [Word8] -> [Integer]
173 bitfieldToList bs = go bs 0
176 let setBits = [pos*8 + (toInteger i) | i <- [0..8], testBit b i]
178 setBits ++ (go bs' (pos + 1))
180 -- downloadPiece :: Integer -> Handle -> IO ()
182 createDummyFile :: FilePath -> Int -> IO ()
183 createDummyFile path size = do
184 writeFile path (BC.replicate size '\0')
186 -- loop1 :: shake hands with all peers, find out the pieces they have, form PieceData.
187 -- recvMsg :: Peer -> Handle -> Msg
188 msgLoop :: Handle -> ByteString -> IO ()
189 msgLoop h pieceHash =
190 let numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
191 pieceStatus = mkPieceMap numPieces pieceHash
195 putStrLn $ "got a " ++ show msg
197 BitFieldMsg bss -> do
198 let pieceList = bitfieldToList (unpack bss)
199 putStrLn (show pieceList)
200 -- download each of the piece in order
201 _ -> putStrLn (show msg)
203 handlePeerMsgs :: Peer -> Metainfo -> String -> (String -> IO ()) -> IO ()
204 handlePeerMsgs p m peerId logFn = do
205 h <- handShake p (infoHash m) peerId
207 msgLoop h (pieces (info m))