summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Exchange/Message.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Exchange/Message.hs')
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs400
1 files changed, 400 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs
new file mode 100644
index 00000000..4ef7baf3
--- /dev/null
+++ b/src/Network/BitTorrent/Exchange/Message.hs
@@ -0,0 +1,400 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2013
3-- License : BSD3
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- Normally peer to peer communication consisting of the following
9-- steps:
10--
11-- * In order to establish the connection between peers we should
12-- send 'Handshake' message. The 'Handshake' is a required message
13-- and must be the first message transmitted by the peer to the
14-- another peer. Another peer should reply with a handshake as well.
15--
16-- * Next peer might sent bitfield message, but might not. In the
17-- former case we should update bitfield peer have. Again, if we
18-- have some pieces we should send bitfield. Normally bitfield
19-- message should sent after the handshake message.
20--
21-- * Regular exchange messages. TODO docs
22--
23-- For more high level API see "Network.BitTorrent.Exchange" module.
24--
25-- For more infomation see:
26-- <https://wiki.theory.org/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29>
27--
28{-# LANGUAGE TemplateHaskell #-}
29{-# OPTIONS -fno-warn-orphans #-}
30module Network.BitTorrent.Exchange.Protocol
31 ( -- * Initial handshake
32 Handshake(..)
33 , handshake
34 , handshakeCaps
35 , recvHandshake
36 , sendHandshake
37
38 -- ** Defaults
39 , defaultHandshake
40 , defaultBTProtocol
41 , defaultReserved
42 , handshakeMaxSize
43
44 -- * Regular messages
45 , Message(..)
46 ) where
47
48import Control.Applicative
49import Control.Exception
50import Control.Monad
51import Data.Binary as B
52import Data.Binary.Get as B
53import Data.Binary.Put as B
54import Data.ByteString as BS
55import Data.ByteString.Char8 as BC
56import Data.ByteString.Lazy as BL
57import Data.Default
58import Data.Serialize as S
59import Network
60import Network.Socket.ByteString
61import Text.PrettyPrint
62import Text.PrettyPrint.Class
63
64import Data.Torrent.Bitfield
65import Data.Torrent.Block
66import Data.Torrent.InfoHash
67import Network.BitTorrent.Extension
68import Network.BitTorrent.Core.PeerId
69import Network.BitTorrent.Core.PeerAddr ()
70
71
72getInt :: S.Get Int
73getInt = fromIntegral <$> S.getWord32be
74{-# INLINE getInt #-}
75
76putInt :: S.Putter Int
77putInt = S.putWord32be . fromIntegral
78{-# INLINE putInt #-}
79
80getIntB :: B.Get Int
81getIntB = fromIntegral <$> B.getWord32be
82{-# INLINE getIntB #-}
83
84putIntB :: Int -> B.Put
85putIntB = B.putWord32be . fromIntegral
86{-# INLINE putIntB #-}
87
88{-----------------------------------------------------------------------
89 Handshake
90-----------------------------------------------------------------------}
91
92-- | Handshake message is used to exchange all information necessary
93-- to establish connection between peers.
94--
95data Handshake = Handshake {
96 -- | Identifier of the protocol.
97 hsProtocol :: BS.ByteString
98
99 -- | Reserved bytes used to specify supported BEP's.
100 , hsReserved :: Capabilities
101
102 -- | Info hash of the info part of the metainfo file. that is
103 -- transmitted in tracker requests. Info hash of the initiator
104 -- handshake and response handshake should match, otherwise
105 -- initiator should break the connection.
106 --
107 , hsInfoHash :: InfoHash
108
109 -- | Peer id of the initiator. This is usually the same peer id
110 -- that is transmitted in tracker requests.
111 --
112 , hsPeerId :: PeerId
113
114 } deriving (Show, Eq)
115
116instance Serialize Handshake where
117 put hs = do
118 S.putWord8 (fromIntegral (BS.length (hsProtocol hs)))
119 S.putByteString (hsProtocol hs)
120 S.putWord64be (hsReserved hs)
121 S.put (hsInfoHash hs)
122 S.put (hsPeerId hs)
123
124 get = do
125 len <- S.getWord8
126 Handshake <$> S.getBytes (fromIntegral len)
127 <*> S.getWord64be
128 <*> S.get
129 <*> S.get
130
131instance Pretty Handshake where
132 pretty Handshake {..}
133 = text (BC.unpack hsProtocol) <+> pretty (clientInfo hsPeerId)
134
135-- | Extract capabilities from a peer handshake message.
136handshakeCaps :: Handshake -> Capabilities
137handshakeCaps = hsReserved
138
139
140-- | Get handshake message size in bytes from the length of protocol
141-- string.
142handshakeSize :: Word8 -> Int
143handshakeSize n = 1 + fromIntegral n + 8 + 20 + 20
144
145-- | Maximum size of handshake message in bytes.
146handshakeMaxSize :: Int
147handshakeMaxSize = handshakeSize 255
148
149-- | Default protocol string "BitTorrent protocol" as is.
150defaultBTProtocol :: BS.ByteString
151defaultBTProtocol = "BitTorrent protocol"
152
153-- | Default reserved word is 0.
154defaultReserved :: Word64
155defaultReserved = 0
156
157-- | Length of info hash and peer id is unchecked, so it /should/ be
158-- equal 20.
159defaultHandshake :: InfoHash -> PeerId -> Handshake
160defaultHandshake = Handshake defaultBTProtocol defaultReserved
161
162sendHandshake :: Socket -> Handshake -> IO ()
163sendHandshake sock hs = sendAll sock (S.encode hs)
164
165recvHandshake :: Socket -> IO Handshake
166recvHandshake sock = do
167 header <- recv sock 1
168 unless (BS.length header == 1) $
169 throw $ userError "Unable to receive handshake header."
170
171 let protocolLen = BS.head header
172 let restLen = handshakeSize protocolLen - 1
173
174 body <- recv sock restLen
175 let resp = BS.cons protocolLen body
176 either (throwIO . userError) return $ S.decode resp
177
178-- | Handshaking with a peer specified by the second argument.
179handshake :: Socket -> Handshake -> IO Handshake
180handshake sock hs = do
181 sendHandshake sock hs
182 hs' <- recvHandshake sock
183 when (hsInfoHash hs /= hsInfoHash hs') $ do
184 throwIO $ userError "Handshake info hash do not match."
185 return hs'
186
187{-----------------------------------------------------------------------
188 Regular messages
189-----------------------------------------------------------------------}
190
191data StatusUpdate
192 = Choke
193 | Unchoke
194 | Interested
195 | NotInterested
196 deriving (Show, Eq, Ord, Enum, Bounded)
197
198data RegularMessage =
199 -- | Zero-based index of a piece that has just been successfully
200 -- downloaded and verified via the hash.
201 Have ! PieceIx
202
203 -- | The bitfield message may only be sent immediately after the
204 -- handshaking sequence is complete, and before any other message
205 -- are sent. If client have no pieces then bitfield need not to be
206 -- sent.
207 | Bitfield !Bitfield
208
209 -- | Request for a particular block. If a client is requested a
210 -- block that another peer do not have the peer might not answer
211 -- at all.
212 | Request ! BlockIx
213
214 -- | Response to a request for a block.
215 | Piece !(Block BL.ByteString)
216
217 -- | Used to cancel block requests. It is typically used during
218 -- "End Game".
219 | Cancel !BlockIx
220 deriving (Show, Eq)
221
222data DHTMessage
223 = Port !PortNumber
224 deriving (Show, Eq)
225
226-- | BEP6 messages.
227data FastMessage =
228 -- | If a peer have all pieces it might send the 'HaveAll' message
229 -- instead of 'Bitfield' message. Used to save bandwidth.
230 HaveAll
231
232 -- | If a peer have no pieces it might send 'HaveNone' message
233 -- intead of 'Bitfield' message. Used to save bandwidth.
234 | HaveNone
235
236 -- | This is an advisory message meaning "you might like to
237 -- download this piece." Used to avoid excessive disk seeks and
238 -- amount of IO.
239 | SuggestPiece !PieceIx
240
241 -- | Notifies a requesting peer that its request will not be satisfied.
242 | RejectRequest !BlockIx
243
244 -- | This is an advisory messsage meaning "if you ask for this
245 -- piece, I'll give it to you even if you're choked." Used to
246 -- shorten starting phase.
247 | AllowedFast !PieceIx
248 deriving (Show, Eq)
249
250-- TODO make Network.BitTorrent.Exchange.Session
251
252-- | Messages used in communication between peers.
253--
254-- Note: If some extensions are disabled (not present in extension
255-- mask) and client receive message used by the disabled
256-- extension then the client MUST close the connection.
257--
258data Message
259 -- core
260 = KeepAlive
261 | Status !StatusUpdate
262 | Regular !RegularMessage
263
264 -- extensions
265 | DHT !DHTMessage
266 | Fast !FastMessage
267 deriving (Show, Eq)
268
269instance Default Message where
270 def = KeepAlive
271 {-# INLINE def #-}
272{-
273-- | Payload bytes are omitted.
274instance Pretty Message where
275 pretty (Bitfield _) = "Bitfield"
276 pretty (Piece blk) = "Piece" <+> pretty blk
277 pretty (Cancel i ) = "Cancel" <+> pretty i
278 pretty (SuggestPiece pix) = "Suggest" <+> int pix
279 pretty (RejectRequest i ) = "Reject" <+> pretty i
280 pretty msg = text (show msg)
281
282instance Serialize Message where
283 get = do
284 len <- getInt
285-- _ <- lookAhead $ ensure len
286 if len == 0 then return KeepAlive
287 else do
288 mid <- S.getWord8
289 case mid of
290 0x00 -> return Choke
291 0x01 -> return Unchoke
292 0x02 -> return Interested
293 0x03 -> return NotInterested
294 0x04 -> Have <$> getInt
295 0x05 -> (Bitfield . fromBitmap) <$> S.getByteString (pred len)
296 0x06 -> Request <$> S.get
297 0x07 -> Piece <$> getBlock (len - 9)
298 0x08 -> Cancel <$> S.get
299 0x09 -> Port <$> S.get
300 0x0D -> SuggestPiece <$> getInt
301 0x0E -> return HaveAll
302 0x0F -> return HaveNone
303 0x10 -> RejectRequest <$> S.get
304 0x11 -> AllowedFast <$> getInt
305 _ -> do
306 rm <- S.remaining >>= S.getBytes
307 fail $ "unknown message ID: " ++ show mid ++ "\n"
308 ++ "remaining available bytes: " ++ show rm
309
310 where
311 getBlock :: Int -> S.Get (Block BL.ByteString)
312 getBlock len = Block <$> getInt <*> getInt <*> S.getLazyByteString (fromIntegral len)
313 {-# INLINE getBlock #-}
314
315
316 put KeepAlive = putInt 0
317 put Choke = putInt 1 >> S.putWord8 0x00
318 put Unchoke = putInt 1 >> S.putWord8 0x01
319 put Interested = putInt 1 >> S.putWord8 0x02
320 put NotInterested = putInt 1 >> S.putWord8 0x03
321 put (Have i) = putInt 5 >> S.putWord8 0x04 >> putInt i
322 put (Bitfield bf) = putInt l >> S.putWord8 0x05 >> S.putLazyByteString b
323 where b = toBitmap bf
324 l = succ (fromIntegral (BL.length b))
325 {-# INLINE l #-}
326 put (Request blk) = putInt 13 >> S.putWord8 0x06 >> S.put blk
327 put (Piece blk) = putInt l >> S.putWord8 0x07 >> putBlock
328 where l = 9 + fromIntegral (BL.length (blkData blk))
329 {-# INLINE l #-}
330 putBlock = do putInt (blkPiece blk)
331 putInt (blkOffset blk)
332 S.putLazyByteString (blkData blk)
333 {-# INLINE putBlock #-}
334
335 put (Cancel blk) = putInt 13 >> S.putWord8 0x08 >> S.put blk
336 put (Port p ) = putInt 3 >> S.putWord8 0x09 >> S.put p
337 put HaveAll = putInt 1 >> S.putWord8 0x0E
338 put HaveNone = putInt 1 >> S.putWord8 0x0F
339 put (SuggestPiece pix) = putInt 5 >> S.putWord8 0x0D >> putInt pix
340 put (RejectRequest i ) = putInt 13 >> S.putWord8 0x10 >> S.put i
341 put (AllowedFast i ) = putInt 5 >> S.putWord8 0x11 >> putInt i
342-}
343{-
344instance Binary Message where
345 get = do
346 len <- getIntB
347-- _ <- lookAhead $ ensure len
348 if len == 0 then return KeepAlive
349 else do
350 mid <- B.getWord8
351 case mid of
352 0x00 -> return Choke
353 0x01 -> return Unchoke
354 0x02 -> return Interested
355 0x03 -> return NotInterested
356 0x04 -> Have <$> getIntB
357 0x05 -> (Bitfield . fromBitmap) <$> B.getByteString (pred len)
358 0x06 -> Request <$> B.get
359 0x07 -> Piece <$> getBlock (len - 9)
360 0x08 -> Cancel <$> B.get
361 0x09 -> (Port . fromIntegral) <$> B.getWord16be
362 0x0E -> return HaveAll
363 0x0F -> return HaveNone
364 0x0D -> SuggestPiece <$> getIntB
365 0x10 -> RejectRequest <$> B.get
366 0x11 -> AllowedFast <$> getIntB
367 _ -> fail $ "unknown message ID: " ++ show mid
368 where
369 getBlock :: Int -> B.Get (Block BL.ByteString)
370 getBlock len = Block <$> getIntB <*> getIntB
371 <*> B.getLazyByteString (fromIntegral len)
372 {-# INLINE getBlock #-}
373
374 put KeepAlive = putIntB 0
375 put Choke = putIntB 1 >> B.putWord8 0x00
376 put Unchoke = putIntB 1 >> B.putWord8 0x01
377 put Interested = putIntB 1 >> B.putWord8 0x02
378 put NotInterested = putIntB 1 >> B.putWord8 0x03
379 put (Have i) = putIntB 5 >> B.putWord8 0x04 >> putIntB i
380 put (Bitfield bf) = putIntB l >> B.putWord8 0x05 >> B.putLazyByteString b
381 where b = toBitmap bf
382 l = succ (fromIntegral (BL.length b))
383 {-# INLINE l #-}
384 put (Request blk) = putIntB 13 >> B.putWord8 0x06 >> B.put blk
385 put (Piece blk) = putIntB l >> B.putWord8 0x07 >> putBlock
386 where l = 9 + fromIntegral (BL.length (blkData blk))
387 {-# INLINE l #-}
388 putBlock = do putIntB (blkPiece blk)
389 putIntB (blkOffset blk)
390 B.putLazyByteString (blkData blk)
391 {-# INLINE putBlock #-}
392
393 put (Cancel blk) = putIntB 13 >> B.putWord8 0x08 >> B.put blk
394 put (Port p ) = putIntB 3 >> B.putWord8 0x09 >> B.putWord16be (fromIntegral p)
395 put HaveAll = putIntB 1 >> B.putWord8 0x0E
396 put HaveNone = putIntB 1 >> B.putWord8 0x0F
397 put (SuggestPiece pix) = putIntB 5 >> B.putWord8 0x0D >> putIntB pix
398 put (RejectRequest i ) = putIntB 13 >> B.putWord8 0x10 >> B.put i
399 put (AllowedFast i ) = putIntB 5 >> B.putWord8 0x11 >> putIntB i
400-} \ No newline at end of file