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(..))
23 import FuncTorrent.Metainfo (Info(..), Metainfo(..))
28 data PeerState = PeerState { handle :: Handle
30 , am_interested :: Bool
31 , peer_choking :: Bool
32 , peer_interested :: Bool}
34 -- Maintain info on every piece and the current state of it.
35 -- should probably be a TVar.
36 type Pieces = [PieceData]
38 data PieceState = 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 :: PieceState } -- ^ state of the piece from download perspective.
47 -- which piece is with which peers
48 type PieceMap = Map Integer PieceData
50 -- | Peer is a PeerID, IP address, port tuple
51 data Peer = Peer ID IP Port
54 data PeerMsg = KeepAliveMsg
60 | BitFieldMsg ByteString
61 | RequestMsg Integer Integer Integer
62 | PieceMsg Integer Integer ByteString
63 | CancelMsg Integer Integer Integer
67 genHandShakeMsg :: ByteString -> String -> ByteString
68 genHandShakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
69 where pstrlen = singleton 19
70 pstr = BC.pack "BitTorrent protocol"
71 reserved = BC.replicate 8 '\0'
72 peerID = BC.pack peer_id
74 handShake :: Peer -> ByteString -> String -> IO Handle
75 handShake (Peer _ ip port) infoHash peerid = do
76 let hs = genHandShakeMsg infoHash peerid
77 h <- connectTo ip (PortNumber (fromIntegral port))
78 hSetBuffering h LineBuffering
80 rlenBS <- hGet h (length (unpack hs))
81 putStrLn $ "got handshake from peer: " ++ show rlenBS
84 instance Binary PeerMsg where
86 KeepAliveMsg -> putWord32be 0
87 ChokeMsg -> do putWord32be 1
89 UnChokeMsg -> do putWord32be 1
91 InterestedMsg -> do putWord32be 1
93 NotInterestedMsg -> do putWord32be 1
95 HaveMsg i -> do putWord32be 5
97 putWord32be (fromIntegral i)
98 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
100 mapM_ putWord8 bfList
101 where bfList = unpack bf
102 bfListLen = length bfList
103 RequestMsg i o l -> do putWord32be 13
105 putWord32be (fromIntegral i)
106 putWord32be (fromIntegral o)
107 putWord32be (fromIntegral l)
108 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
110 putWord32be (fromIntegral i)
111 putWord32be (fromIntegral o)
112 mapM_ putWord8 blockList
113 where blockList = unpack b
114 blocklen = length blockList
115 CancelMsg i o l -> do putWord32be 13
117 putWord32be (fromIntegral i)
118 putWord32be (fromIntegral o)
119 putWord32be (fromIntegral l)
120 PortMsg p -> do putWord32be 3
122 putWord16be (fromIntegral p)
128 1 -> return UnChokeMsg
129 2 -> return InterestedMsg
130 3 -> return NotInterestedMsg
131 4 -> liftM (HaveMsg . fromIntegral) getWord32be
132 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
133 6 -> liftA3 RequestMsg getInteger getInteger getInteger
134 where getInteger = fromIntegral <$> getWord32be
135 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
136 where getInteger = fromIntegral <$> getWord32be
137 8 -> liftA3 CancelMsg getInteger getInteger getInteger
138 where getInteger = fromIntegral <$> getWord32be
139 9 -> liftM (PortMsg . fromIntegral) getWord16be
140 _ -> error ("unknown message ID: " ++ show msgid)
142 getMsg :: Handle -> IO PeerMsg
147 then return KeepAliveMsg
150 return $ decode $ fromStrict $ concat [lBS, msg]
153 bsToInt :: ByteString -> Int
154 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))
156 bitfieldToList :: [Word8] -> [Integer]
157 bitfieldToList bs = go bs 0
160 let setBits = [pos*8 + (toInteger i) | i <- [0..8], testBit b i]
162 setBits ++ (go bs' (pos + 1))
164 -- downloadPiece :: Integer -> Handle -> IO ()
166 createDummyFile :: FilePath -> Int -> IO ()
167 createDummyFile path size = do
168 writeFile path (BC.replicate size '\0')
170 -- loop1 :: shake hands with all peers, find out the pieces they have, form PieceData.
171 -- recvMsg :: Peer -> Handle -> Msg
172 msgLoop :: Handle -> ByteString -> IO ()
173 msgLoop h pieceHash =
174 let numPieces = (toInteger . (`quot` 20) . BC.length) pieceHash
178 putStrLn $ "got a " ++ show msg
180 BitFieldMsg bss -> do
181 let pieceList = bitfieldToList (unpack bss)
182 putStrLn (show pieceList)
183 -- download each of the piece in order
184 _ -> putStrLn (show msg)
186 handlePeerMsgs :: Peer -> Metainfo -> String -> (String -> IO ()) -> IO ()
187 handlePeerMsgs p m peerId logFn = do
188 h <- handShake p (infoHash m) peerId
190 msgLoop h (pieces (info m))