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.PeerMsgs
30 import Prelude hiding (lookup, concat, replicate, splitAt, take)
32 import System.IO (Handle)
33 import Data.ByteString (ByteString, pack, unpack, concat, hGet, hPut, singleton)
34 import Data.ByteString.Lazy (fromStrict, fromChunks, toStrict)
35 import qualified Data.ByteString.Char8 as BC (replicate, pack)
36 import Control.Monad (replicateM, liftM)
37 import Control.Applicative (liftA3)
39 import Data.Binary (Binary(..), decode, encode)
40 import Data.Binary.Put (putWord32be, putWord16be, putWord8)
41 import Data.Binary.Get (getWord32be, getWord16be, getWord8, runGet)
43 -- | Peer is a PeerID, IP address, port tuple
44 data Peer = Peer ID IP Port
51 data PeerMsg = KeepAliveMsg
57 | BitFieldMsg ByteString
58 | RequestMsg Integer Integer Integer
59 | PieceMsg Integer Integer ByteString
60 | CancelMsg Integer Integer Integer
64 instance Binary PeerMsg where
66 KeepAliveMsg -> putWord32be 0
67 ChokeMsg -> do putWord32be 1
69 UnChokeMsg -> do putWord32be 1
71 InterestedMsg -> do putWord32be 1
73 NotInterestedMsg -> do putWord32be 1
75 HaveMsg i -> do putWord32be 5
77 putWord32be (fromIntegral i)
78 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
81 where bfList = unpack bf
82 bfListLen = length bfList
83 RequestMsg i o l -> do putWord32be 13
85 putWord32be (fromIntegral i)
86 putWord32be (fromIntegral o)
87 putWord32be (fromIntegral l)
88 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
90 putWord32be (fromIntegral i)
91 putWord32be (fromIntegral o)
92 mapM_ putWord8 blockList
93 where blockList = unpack b
94 blocklen = length blockList
95 CancelMsg i o l -> do putWord32be 13
97 putWord32be (fromIntegral i)
98 putWord32be (fromIntegral o)
99 putWord32be (fromIntegral l)
100 PortMsg p -> do putWord32be 3
102 putWord16be (fromIntegral p)
108 1 -> return UnChokeMsg
109 2 -> return InterestedMsg
110 3 -> return NotInterestedMsg
111 4 -> liftM (HaveMsg . fromIntegral) getWord32be
112 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
113 6 -> liftA3 RequestMsg getInteger getInteger getInteger
114 where getInteger = fromIntegral <$> getWord32be
115 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
116 where getInteger = fromIntegral <$> getWord32be
117 8 -> liftA3 CancelMsg getInteger getInteger getInteger
118 where getInteger = fromIntegral <$> getWord32be
119 9 -> liftM (PortMsg . fromIntegral) getWord16be
120 _ -> error ("unknown message ID: " ++ show msgid)
122 getMsg :: Handle -> IO PeerMsg
127 then return KeepAliveMsg
130 return $ decode $ fromStrict $ concat [lBS, msg]
132 sendMsg :: Handle -> PeerMsg -> IO ()
133 sendMsg h msg = hPut h bsMsg
134 where bsMsg = toStrict $ encode msg
136 genHandshakeMsg :: ByteString -> String -> ByteString
137 genHandshakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
138 where pstrlen = singleton 19
139 pstr = BC.pack "BitTorrent protocol"
140 reserved = BC.replicate 8 '\0'
141 peerID = BC.pack peer_id
143 bsToInt :: ByteString -> Int
144 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))