+{-
+ - Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
+ -
+ - This file is part of FuncTorrent.
+ -
+ - FuncTorrent is free software; you can redistribute it and/or modify
+ - it under the terms of the GNU General Public License as published by
+ - the Free Software Foundation; either version 3 of the License, or
+ - (at your option) any later version.
+ -
+ - FuncTorrent is distributed in the hope that it will be useful,
+ - but WITHOUT ANY WARRANTY; without even the implied warranty of
+ - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ - GNU General Public License for more details.
+ -
+ - You should have received a copy of the GNU General Public License
+ - along with FuncTorrent; if not, see <http://www.gnu.org/licenses/>
+ -}
+
{-# LANGUAGE OverloadedStrings #-}
+
module FuncTorrent.Peer
- (Peer(..),
- PieceMap,
- handlePeerMsgs
+ (handlePeerMsgs
) where
import Prelude hiding (lookup, concat, replicate, splitAt, take, drop)
-import Control.Concurrent.Chan (writeChan)
import Control.Monad.State
import Data.ByteString (ByteString, unpack, concat, hGet, hPut, take, drop, empty)
import Data.Bits
import FuncTorrent.PeerMsgs (Peer(..), PeerMsg(..), sendMsg, getMsg, genHandshakeMsg)
import FuncTorrent.Utils (splitNum, verifyHash)
import FuncTorrent.PieceManager (PieceDlState(..), PieceData(..), PieceMap, pickPiece, updatePieceAvailability)
-import qualified FuncTorrent.FileSystem as FS (MsgChannel, Msg(..), Piece(..))
+import qualified FuncTorrent.FileSystem as FS (MsgChannel, writePieceToDisk)
data PState = PState { handle :: Handle
, peer :: Peer
dlstate (pm ! index) == Have
connectToPeer :: Peer -> IO Handle
-connectToPeer (Peer _ ip port) = do
+connectToPeer (Peer ip port) = do
h <- connectTo ip (PortNumber (fromIntegral port))
hSetBuffering h LineBuffering
return h
liftIO $ putStrLn "Hash mismatch"
else do
liftIO $ putStrLn $ "Write piece: " ++ show workPiece
- liftIO $ writeChan msgchannel $ FS.WritePiece (FS.Piece workPiece pBS)
+ liftIO $ FS.writePieceToDisk msgchannel workPiece pBS
msgLoop (adjust (\pieceData -> pieceData { dlstate = Have }) workPiece pieceStatus) msgchannel
_ -> do
msg <- liftIO $ getMsg h
- gets peer >>= (\p -> liftIO $ putStrLn $ "<-- " ++ show msg ++ "from peer: " ++ show p)
+ gets peer >>= (\p -> liftIO $ putStrLn $ "<-- " ++ show msg ++ " from peer: " ++ show p)
case msg of
KeepAliveMsg -> do
liftIO $ sendMsg h KeepAliveMsg
NotInterestedMsg -> do
modify (\st' -> st' {heInterested = False})
msgLoop pieceStatus msgchannel
- CancelMsg _ _ _ -> -- check if valid index, begin, length
+ CancelMsg {} -> -- check if valid index, begin, length
msgLoop pieceStatus msgchannel
PortMsg _ ->
msgLoop pieceStatus msgchannel
- -- handle RequestMsg, HaveMsg. No need to handle PieceMsg here.
- -- also BitFieldMsg
+ HaveMsg idx -> do
+ p <- gets peer
+ let pieceStatus' = updatePieceAvailability pieceStatus p [idx]
+ msgLoop pieceStatus' msgchannel
+ _ -> do
+ liftIO $ putStrLn ".. not doing anything with the msg"
+ msgLoop pieceStatus msgchannel
+ -- No need to handle PieceMsg and RequestMsg here.
downloadPiece :: Handle -> Integer -> Integer -> IO ByteString
putStrLn $ "ignoring irrelevant msg: " ++ show msg
return empty)
+
+{-
+ -- Extension messages support (BEP-0010) --
+
+
+ In the regular peer handshake, adventise support for extension protocol. Protocol
+ extensions are done via the reserved bytes (8 of them) in the handshake message
+ as detailed in BEP-0003. For this particular "Extension Protocol" extension, we use
+ 20th bit (counted from the right, from 0) is set to 1.
+
+ Once support for the extension protocol is established by the peer, the Peer is supposed
+ to support one message with the ID 20. This is sent like a regular message with 4-byte
+ length prefix and the msg id (20) in this case.
+
+ First byte of the payload of this message is either 0, which means it is a handshake
+ msg.
+
+ The rest of the payload is a dictionary with various keys. All of them are optional. The
+ one of interest at the moment for me is the one with key 'm' whose value is another
+ dictionary of all supported extensions.
+
+ Here is where it gets interesting for us (to support magneturi. When the torrent client
+ has only got a magneturi to look at, it has only got the list of trackers with it (we
+ are not looking at the DHT case for the time being). So, it somehow needs to get the info
+ dictionary. It gets this by talking to another peer in the network. To do that, the client
+ needs to talk tracker protocol, get the list of peers and talk to peers using the above
+ extension protocol to get the infodict as payload. Let us see how we can do that now.
+
+ If a peer already has the full infodict, then, the handshake message sent by that peer
+ is something like this:
+
+ {'m': {'ut_metadata', 3}, 'metadata_size': 31235}
+
+ Note that the 'metadata_size' is not part of the value of the key 'm'.
+ If we are a new client and are requesting the handshake to a peer, then we don't have
+ the infodict yet, in which case, we only send the first part:
+
+ {'m': {'ut_metadata', 3}}
+
+ This is bencoded and sent across the wire. The value "3" (integer) against the key
+ 'ut_metadata" is an ordered integer within a client that identifies the extention.
+ No two extension supported by the same client shares the same value. If the value is
+ '0', then the extension is unsupported.
+
+ Here we use the BEP-0009, the metadata extension protocol. The metadata in this case
+ is the infodict. The infodict itself is divided into 16KB sized pieces.
+
+ Here is a possible interaction between two peers:
+
+ 1. Peer Pn comes up, gets the ip/ports of other peers, P0, P1.... Pn does not have the
+ size of the infodict. Pn has advertised itself as supporting the extension protocol.
+ It sends the handshake msg to other peers with this bit on in the reserved bytes.
+ 2. Let us say, P1 replied with a handshake. We check if it also supports the extension
+ mechanism.
+ 3. Now we get into the extension message passing so that we have the info dict.
+ To do that, we send the extension handshake (ut_metadata) m dict without the
+ metadata_size. We get back the extension handshake with metadata_size. We take
+ note of the size.
+ 4. We calculate the number of 16384 chunks in the total size of the metadata. That
+ gives us the number of pieces the metadata has.
+ 5. We send a "request" extension msg:
+ {'msg_type': 0, 'piece': 0}
+ 6. We recieve the "data" message.
+ {'msg_type': 1, 'piece': 0, 'total_size': 3425} in bencoded format, followed by
+ total_size bytes. total_size is 16KiB except perhaps for the last piece.
+ 7. If the peer does not have the requested piece, it sends the "reject" message.
+ {'msg_type': 2, 'piece': 0}
+ 8. Repeat 5, 6/7 for every piece.
+
+ At this point, we have the infodict.
+
+-)
+