1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Peer
8 import Prelude hiding (lookup, concat, replicate, splitAt, take, drop)
10 import Control.Concurrent.Chan (writeChan)
11 import Control.Monad.State
12 import Data.ByteString (ByteString, unpack, concat, hGet, hPut, take, drop, empty)
14 import Data.Word (Word8)
15 import Data.Map ((!), adjust)
16 import Network (connectTo, PortID(..))
17 import System.IO (Handle, BufferMode(..), hSetBuffering, hClose)
19 import FuncTorrent.Metainfo (Metainfo(..))
20 import FuncTorrent.PeerMsgs (Peer(..), PeerMsg(..), sendMsg, getMsg, genHandshakeMsg)
21 import FuncTorrent.Utils (splitNum, verifyHash)
22 import FuncTorrent.PieceManager (PieceDlState(..), PieceData(..), PieceMap, pickPiece, updatePieceAvailability)
23 import qualified FuncTorrent.FileSystem as FS (MsgChannel, Msg(..), Piece(..))
25 data PState = PState { handle :: Handle
28 , meInterested :: Bool
30 , heInterested :: Bool}
32 havePiece :: PieceMap -> Integer -> Bool
34 dlstate (pm ! index) == Have
36 connectToPeer :: Peer -> IO Handle
37 connectToPeer (Peer _ ip port) = do
38 h <- connectTo ip (PortNumber (fromIntegral port))
39 hSetBuffering h LineBuffering
42 doHandshake :: Bool -> Handle -> Peer -> ByteString -> String -> IO ()
43 doHandshake True h p infohash peerid = do
44 let hs = genHandshakeMsg infohash peerid
46 putStrLn $ "--> handhake to peer: " ++ show p
47 _ <- hGet h (length (unpack hs))
48 putStrLn $ "<-- handshake from peer: " ++ show p
50 doHandshake False h p infohash peerid = do
51 let hs = genHandshakeMsg infohash peerid
52 putStrLn "waiting for a handshake"
53 hsMsg <- hGet h (length (unpack hs))
54 putStrLn $ "<-- handshake from peer: " ++ show p
55 let rxInfoHash = take 20 $ drop 28 hsMsg
56 if rxInfoHash /= infohash
58 putStrLn "infoHashes does not match"
63 putStrLn $ "--> handhake to peer: " ++ show p
66 bitfieldToList :: [Word8] -> [Integer]
67 bitfieldToList bs = go bs 0
70 let setBits = [pos*8 + toInteger i | i <- [0..8], testBit b i]
72 setBits ++ go bs' (pos + 1)
74 -- helper functions to manipulate PeerState
77 -> Bool -- ^ meChoking
78 -> Bool -- ^ meInterested
79 -> Bool -- ^ heChoking
80 -> Bool -- ^ heInterested
82 toPeerState h p meCh meIn heCh heIn =
88 , meInterested = meIn }
90 handlePeerMsgs :: Peer -> String -> Metainfo -> PieceMap -> Bool -> FS.MsgChannel -> IO ()
91 handlePeerMsgs p peerId m pieceMap isClient c = do
93 doHandshake isClient h p (infoHash m) peerId
94 let pstate = toPeerState h p False False True True
95 _ <- runStateT (msgLoop pieceMap c) pstate
98 msgLoop :: PieceMap -> FS.MsgChannel -> StateT PState IO ()
99 msgLoop pieceStatus msgchannel = do
103 PState { meInterested = False, heChoking = True } -> do
104 liftIO $ sendMsg h InterestedMsg
105 gets peer >>= (\p -> liftIO $ putStrLn $ "--> InterestedMsg to peer: " ++ show p)
106 modify (\st' -> st' { meInterested = True })
107 msgLoop pieceStatus msgchannel
108 PState { meInterested = True, heChoking = False } ->
109 case pickPiece pieceStatus of
110 Nothing -> liftIO $ putStrLn "Nothing to download"
112 let pLen = len (pieceStatus ! workPiece)
113 liftIO $ putStrLn $ "piece length = " ++ show pLen
114 pBS <- liftIO $ downloadPiece h workPiece pLen
115 if not $ verifyHash pBS (hash (pieceStatus ! workPiece))
117 liftIO $ putStrLn "Hash mismatch"
119 liftIO $ putStrLn $ "Write piece: " ++ show workPiece
120 liftIO $ writeChan msgchannel $ FS.WritePiece (FS.Piece workPiece pBS)
121 msgLoop (adjust (\pieceData -> pieceData { dlstate = Have }) workPiece pieceStatus) msgchannel
123 msg <- liftIO $ getMsg h
124 gets peer >>= (\p -> liftIO $ putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show p)
127 liftIO $ sendMsg h KeepAliveMsg
128 gets peer >>= (\p -> liftIO $ putStrLn $ "--> " ++ "KeepAliveMsg to peer: " ++ show p)
129 msgLoop pieceStatus msgchannel
130 BitFieldMsg bss -> do
132 let pieceList = bitfieldToList (unpack bss)
133 pieceStatus' = updatePieceAvailability pieceStatus p pieceList
134 liftIO $ putStrLn $ show (length pieceList) ++ " Pieces"
135 -- for each pieceIndex in pieceList, make an entry in the pieceStatus
136 -- map with pieceIndex as the key and modify the value to add the peer.
137 -- download each of the piece in order
138 msgLoop pieceStatus' msgchannel
140 modify (\st' -> st' {heChoking = False })
141 msgLoop pieceStatus msgchannel
143 modify (\st' -> st' {heChoking = True })
144 msgLoop pieceStatus msgchannel
146 modify (\st' -> st' {heInterested = True})
147 msgLoop pieceStatus msgchannel
148 NotInterestedMsg -> do
149 modify (\st' -> st' {heInterested = False})
150 msgLoop pieceStatus msgchannel
151 CancelMsg _ _ _ -> -- check if valid index, begin, length
152 msgLoop pieceStatus msgchannel
154 msgLoop pieceStatus msgchannel
155 -- handle RequestMsg, HaveMsg. No need to handle PieceMsg here.
159 downloadPiece :: Handle -> Integer -> Integer -> IO ByteString
160 downloadPiece h index pieceLength = do
161 let chunks = splitNum pieceLength 16384
162 concat `liftM` forM (zip [0..] chunks) (\(i, pLen) -> do
163 sendMsg h (RequestMsg index (i*pLen) pLen)
164 putStrLn $ "--> " ++ "RequestMsg for Piece "
165 ++ show index ++ ", part: " ++ show i ++ " of length: "
169 PieceMsg index begin block -> do
170 putStrLn $ " <-- PieceMsg for Piece: "
176 putStrLn $ "ignoring irrelevant msg: " ++ show msg