2 - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
4 - This file is part of FuncTorrent.
6 - FuncTorrent is free software; you can redistribute it and/or modify
7 - it under the terms of the GNU General Public License as published by
8 - the Free Software Foundation; either version 3 of the License, or
9 - (at your option) any later version.
11 - FuncTorrent is distributed in the hope that it will be useful,
12 - but WITHOUT ANY WARRANTY; without even the implied warranty of
13 - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 - GNU General Public License for more details.
16 - You should have received a copy of the GNU General Public License
17 - along with FuncTorrent; if not, see <http://www.gnu.org/licenses/>
20 {-# LANGUAGE OverloadedStrings #-}
22 module FuncTorrent.Peer
28 import Prelude hiding (lookup, concat, replicate, splitAt, take, drop)
30 import Control.Monad.State
31 import Data.ByteString (ByteString, unpack, concat, hGet, hPut, take, drop, empty)
33 import Data.Word (Word8)
34 import Data.Map ((!), adjust)
35 import Network (connectTo, PortID(..))
36 import System.IO (Handle, BufferMode(..), hSetBuffering, hClose)
38 import FuncTorrent.Metainfo (Metainfo(..))
39 import FuncTorrent.PeerMsgs (Peer(..), PeerMsg(..), sendMsg, getMsg, genHandshakeMsg)
40 import FuncTorrent.Utils (splitNum, verifyHash)
41 import FuncTorrent.PieceManager (PieceDlState(..), PieceData(..), PieceMap, pickPiece, updatePieceAvailability)
42 import qualified FuncTorrent.FileSystem as FS (MsgChannel, writePieceToDisk)
44 data PState = PState { handle :: Handle
47 , meInterested :: Bool
49 , heInterested :: Bool}
51 havePiece :: PieceMap -> Integer -> Bool
53 dlstate (pm ! index) == Have
55 connectToPeer :: Peer -> IO Handle
56 connectToPeer (Peer _ ip port) = do
57 h <- connectTo ip (PortNumber (fromIntegral port))
58 hSetBuffering h LineBuffering
61 doHandshake :: Bool -> Handle -> Peer -> ByteString -> String -> IO ()
62 doHandshake True h p infohash peerid = do
63 let hs = genHandshakeMsg infohash peerid
65 putStrLn $ "--> handhake to peer: " ++ show p
66 _ <- hGet h (length (unpack hs))
67 putStrLn $ "<-- handshake from peer: " ++ show p
69 doHandshake False h p infohash peerid = do
70 let hs = genHandshakeMsg infohash peerid
71 putStrLn "waiting for a handshake"
72 hsMsg <- hGet h (length (unpack hs))
73 putStrLn $ "<-- handshake from peer: " ++ show p
74 let rxInfoHash = take 20 $ drop 28 hsMsg
75 if rxInfoHash /= infohash
77 putStrLn "infoHashes does not match"
82 putStrLn $ "--> handhake to peer: " ++ show p
85 bitfieldToList :: [Word8] -> [Integer]
86 bitfieldToList bs = go bs 0
89 let setBits = [pos*8 + toInteger i | i <- [0..8], testBit b i]
91 setBits ++ go bs' (pos + 1)
93 -- helper functions to manipulate PeerState
96 -> Bool -- ^ meChoking
97 -> Bool -- ^ meInterested
98 -> Bool -- ^ heChoking
99 -> Bool -- ^ heInterested
101 toPeerState h p meCh meIn heCh heIn =
105 , heInterested = heIn
107 , meInterested = meIn }
109 handlePeerMsgs :: Peer -> String -> Metainfo -> PieceMap -> Bool -> FS.MsgChannel -> IO ()
110 handlePeerMsgs p peerId m pieceMap isClient c = do
112 doHandshake isClient h p (infoHash m) peerId
113 let pstate = toPeerState h p False False True True
114 _ <- runStateT (msgLoop pieceMap c) pstate
117 msgLoop :: PieceMap -> FS.MsgChannel -> StateT PState IO ()
118 msgLoop pieceStatus msgchannel = do
122 PState { meInterested = False, heChoking = True } -> do
123 liftIO $ sendMsg h InterestedMsg
124 gets peer >>= (\p -> liftIO $ putStrLn $ "--> InterestedMsg to peer: " ++ show p)
125 modify (\st' -> st' { meInterested = True })
126 msgLoop pieceStatus msgchannel
127 PState { meInterested = True, heChoking = False } ->
128 case pickPiece pieceStatus of
129 Nothing -> liftIO $ putStrLn "Nothing to download"
131 let pLen = len (pieceStatus ! workPiece)
132 liftIO $ putStrLn $ "piece length = " ++ show pLen
133 pBS <- liftIO $ downloadPiece h workPiece pLen
134 if not $ verifyHash pBS (hash (pieceStatus ! workPiece))
136 liftIO $ putStrLn "Hash mismatch"
138 liftIO $ putStrLn $ "Write piece: " ++ show workPiece
139 liftIO $ FS.writePieceToDisk msgchannel workPiece pBS
140 msgLoop (adjust (\pieceData -> pieceData { dlstate = Have }) workPiece pieceStatus) msgchannel
142 msg <- liftIO $ getMsg h
143 gets peer >>= (\p -> liftIO $ putStrLn $ "<-- " ++ show msg ++ " from peer: " ++ show p)
146 liftIO $ sendMsg h KeepAliveMsg
147 gets peer >>= (\p -> liftIO $ putStrLn $ "--> " ++ "KeepAliveMsg to peer: " ++ show p)
148 msgLoop pieceStatus msgchannel
149 BitFieldMsg bss -> do
151 let pieceList = bitfieldToList (unpack bss)
152 pieceStatus' = updatePieceAvailability pieceStatus p pieceList
153 liftIO $ putStrLn $ show (length pieceList) ++ " Pieces"
154 -- for each pieceIndex in pieceList, make an entry in the pieceStatus
155 -- map with pieceIndex as the key and modify the value to add the peer.
156 -- download each of the piece in order
157 msgLoop pieceStatus' msgchannel
159 modify (\st' -> st' {heChoking = False })
160 msgLoop pieceStatus msgchannel
162 modify (\st' -> st' {heChoking = True })
163 msgLoop pieceStatus msgchannel
165 modify (\st' -> st' {heInterested = True})
166 msgLoop pieceStatus msgchannel
167 NotInterestedMsg -> do
168 modify (\st' -> st' {heInterested = False})
169 msgLoop pieceStatus msgchannel
170 CancelMsg _ _ _ -> -- check if valid index, begin, length
171 msgLoop pieceStatus msgchannel
173 msgLoop pieceStatus msgchannel
176 let pieceStatus' = updatePieceAvailability pieceStatus p [idx]
177 msgLoop pieceStatus' msgchannel
179 liftIO $ putStrLn $ ".. not doing anything with the msg"
180 msgLoop pieceStatus msgchannel
181 -- No need to handle PieceMsg and RequestMsg here.
184 downloadPiece :: Handle -> Integer -> Integer -> IO ByteString
185 downloadPiece h index pieceLength = do
186 let chunks = splitNum pieceLength 16384
187 concat `liftM` forM (zip [0..] chunks) (\(i, pLen) -> do
188 sendMsg h (RequestMsg index (i*pLen) pLen)
189 putStrLn $ "--> " ++ "RequestMsg for Piece "
190 ++ show index ++ ", part: " ++ show i ++ " of length: "
194 PieceMsg index begin block -> do
195 putStrLn $ " <-- PieceMsg for Piece: "
201 putStrLn $ "ignoring irrelevant msg: " ++ show msg