1 {-# LANGUAGE OverloadedStrings #-}
3 Copyright (C) 2015-2016 Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
5 This file is part of FuncTorrent.
7 FuncTorrent is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 FuncTorrent is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with FuncTorrent; if not, see <http://www.gnu.org/licenses/>
21 module FuncTorrent.PeerMsgs
29 import Prelude hiding (lookup, concat, replicate, splitAt, take)
31 import System.IO (Handle)
32 import Data.ByteString (ByteString, pack, unpack, concat, hGet, hPut, singleton)
33 import Data.ByteString.Lazy (fromStrict, fromChunks, toStrict)
34 import qualified Data.ByteString.Char8 as BC (replicate, pack)
35 import Control.Monad (replicateM, liftM)
36 import Control.Applicative (liftA3)
38 import Data.Binary (Binary(..), decode, encode)
39 import Data.Binary.Put (putWord32be, putWord16be, putWord8)
40 import Data.Binary.Get (getWord32be, getWord16be, getWord8, runGet)
42 -- | Peer is a PeerID, IP address, port tuple
43 data Peer = Peer ID IP Port
50 data PeerMsg = KeepAliveMsg
56 | BitFieldMsg ByteString
57 | RequestMsg Integer Integer Integer
58 | PieceMsg Integer Integer ByteString
59 | CancelMsg Integer Integer Integer
63 instance Binary PeerMsg where
65 KeepAliveMsg -> putWord32be 0
66 ChokeMsg -> do putWord32be 1
68 UnChokeMsg -> do putWord32be 1
70 InterestedMsg -> do putWord32be 1
72 NotInterestedMsg -> do putWord32be 1
74 HaveMsg i -> do putWord32be 5
76 putWord32be (fromIntegral i)
77 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
80 where bfList = unpack bf
81 bfListLen = length bfList
82 RequestMsg i o l -> do putWord32be 13
84 putWord32be (fromIntegral i)
85 putWord32be (fromIntegral o)
86 putWord32be (fromIntegral l)
87 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
89 putWord32be (fromIntegral i)
90 putWord32be (fromIntegral o)
91 mapM_ putWord8 blockList
92 where blockList = unpack b
93 blocklen = length blockList
94 CancelMsg i o l -> do putWord32be 13
96 putWord32be (fromIntegral i)
97 putWord32be (fromIntegral o)
98 putWord32be (fromIntegral l)
99 PortMsg p -> do putWord32be 3
101 putWord16be (fromIntegral p)
107 1 -> return UnChokeMsg
108 2 -> return InterestedMsg
109 3 -> return NotInterestedMsg
110 4 -> liftM (HaveMsg . fromIntegral) getWord32be
111 5 -> liftM (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
112 6 -> liftA3 RequestMsg getInteger getInteger getInteger
113 where getInteger = fromIntegral <$> getWord32be
114 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
115 where getInteger = fromIntegral <$> getWord32be
116 8 -> liftA3 CancelMsg getInteger getInteger getInteger
117 where getInteger = fromIntegral <$> getWord32be
118 9 -> liftM (PortMsg . fromIntegral) getWord16be
119 _ -> error ("unknown message ID: " ++ show msgid)
121 getMsg :: Handle -> IO PeerMsg
126 then return KeepAliveMsg
129 return $ decode $ fromStrict $ concat [lBS, msg]
131 sendMsg :: Handle -> PeerMsg -> IO ()
132 sendMsg h msg = hPut h bsMsg
133 where bsMsg = toStrict $ encode msg
135 genHandshakeMsg :: ByteString -> String -> ByteString
136 genHandshakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved, infoHash, peerID]
137 where pstrlen = singleton 19
138 pstr = BC.pack "BitTorrent protocol"
139 reserved = BC.replicate 8 '\0'
140 peerID = BC.pack peer_id
142 bsToInt :: ByteString -> Int
143 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))