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
31 import Prelude hiding (lookup, concat, replicate, splitAt, take)
33 import System.IO (Handle)
34 import Data.ByteString (ByteString, pack, unpack, concat, hGet, hPut, singleton)
35 import Data.ByteString.Lazy (fromStrict, fromChunks, toStrict)
36 import Data.ByteString.Char8 as BC (splitAt)
37 import qualified Data.ByteString.Char8 as BC (replicate, pack)
38 import Control.Monad (replicateM)
39 import Control.Applicative (liftA3)
41 import Data.Binary (Binary(..), decode, encode)
42 import Data.Binary.Put (putWord32be, putWord16be, putWord8)
43 import Data.Binary.Get (getWord32be, getWord16be, getWord8, runGet)
45 import FuncTorrent.Utils (toIP, toPort)
47 -- | Peer is a IP address, port tuple
48 data Peer = Peer IP Port
55 data PeerMsg = KeepAliveMsg
61 | BitFieldMsg ByteString
62 | RequestMsg Integer Integer Integer
63 | PieceMsg Integer Integer ByteString
64 | CancelMsg Integer Integer Integer
66 | ExtendedMsg Integer ByteString
69 data ExtMetadataMsg = Request Integer
70 | Data Integer Integer
74 instance Binary PeerMsg where
76 KeepAliveMsg -> putWord32be 0
77 ChokeMsg -> do putWord32be 1
79 UnChokeMsg -> do putWord32be 1
81 InterestedMsg -> do putWord32be 1
83 NotInterestedMsg -> do putWord32be 1
85 HaveMsg i -> do putWord32be 5
87 putWord32be (fromIntegral i)
88 BitFieldMsg bf -> do putWord32be $ fromIntegral (1 + bfListLen)
91 where bfList = unpack bf
92 bfListLen = length bfList
93 RequestMsg i o l -> do putWord32be 13
95 putIndexOffsetLength i o l
96 PieceMsg i o b -> do putWord32be $ fromIntegral (9 + blocklen)
98 putWord32be (fromIntegral i)
99 putWord32be (fromIntegral o)
100 mapM_ putWord8 blockList
101 where blockList = unpack b
102 blocklen = length blockList
103 CancelMsg i o l -> do putWord32be 13
105 putIndexOffsetLength i o l
106 PortMsg p -> do putWord32be 3
108 putWord16be (fromIntegral p)
109 ExtendedMsg t b-> do putWord32be (fromIntegral blockLen)
111 putWord8 (fromIntegral t) -- 0 => handshake msg
112 -- actual extension msg follows
113 mapM_ putWord8 blockList
114 where blockList = unpack b
115 blockLen = length blockList
117 where putIndexOffsetLength i o l = do
118 putWord32be (fromIntegral i)
119 putWord32be (fromIntegral o)
120 putWord32be (fromIntegral l)
127 1 -> return UnChokeMsg
128 2 -> return InterestedMsg
129 3 -> return NotInterestedMsg
130 4 -> fmap (HaveMsg . fromIntegral) getWord32be
131 5 -> fmap (BitFieldMsg . pack) (replicateM (fromIntegral l - 1) getWord8)
132 6 -> liftA3 RequestMsg getInteger getInteger getInteger
133 where getInteger = fromIntegral <$> getWord32be
134 7 -> liftA3 PieceMsg getInteger getInteger (pack <$> replicateM (fromIntegral l - 9) getWord8)
135 where getInteger = fromIntegral <$> getWord32be
136 8 -> liftA3 CancelMsg getInteger getInteger getInteger
137 where getInteger = fromIntegral <$> getWord32be
138 9 -> fmap (PortMsg . fromIntegral) getWord16be
139 _ -> error ("unknown message ID: " ++ show msgid)
141 getMsg :: Handle -> IO PeerMsg
146 then return KeepAliveMsg
149 return $ decode $ fromStrict $ concat [lBS, msg]
151 sendMsg :: Handle -> PeerMsg -> IO ()
152 sendMsg h msg = hPut h bsMsg
153 where bsMsg = toStrict $ encode msg
155 genHandshakeMsg :: ByteString -> String -> ByteString
156 genHandshakeMsg infoHash peer_id = concat [pstrlen, pstr, reserved1, reserved2, reserved3, infoHash, peerID]
157 where pstrlen = singleton 19
158 pstr = BC.pack "BitTorrent protocol"
159 reserved1 = BC.replicate 5 '\0'
160 reserved2 = singleton 0x10 -- support extension protocol
161 reserved3 = BC.replicate 2 '\0'
162 peerID = BC.pack peer_id
164 bsToInt :: ByteString -> Int
165 bsToInt x = fromIntegral (runGet getWord32be (fromChunks (return x)))
167 makePeer :: ByteString -> Peer
168 makePeer peer = Peer (toIP ip') (toPort port')
169 where (ip', port') = splitAt 4 peer