diff options
Diffstat (limited to 'bittorrent/src/Network/BitTorrent/Exchange/Message.hs')
-rw-r--r-- | bittorrent/src/Network/BitTorrent/Exchange/Message.hs | 1232 |
1 files changed, 1232 insertions, 0 deletions
diff --git a/bittorrent/src/Network/BitTorrent/Exchange/Message.hs b/bittorrent/src/Network/BitTorrent/Exchange/Message.hs new file mode 100644 index 00000000..2c6770f7 --- /dev/null +++ b/bittorrent/src/Network/BitTorrent/Exchange/Message.hs | |||
@@ -0,0 +1,1232 @@ | |||
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 ViewPatterns #-} | ||
29 | {-# LANGUAGE FlexibleInstances #-} | ||
30 | {-# LANGUAGE FlexibleContexts #-} | ||
31 | {-# LANGUAGE TypeFamilies #-} | ||
32 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
33 | {-# LANGUAGE DeriveDataTypeable #-} | ||
34 | {-# LANGUAGE TemplateHaskell #-} | ||
35 | {-# OPTIONS -fno-warn-orphans #-} | ||
36 | module Network.BitTorrent.Exchange.Message | ||
37 | ( -- * Capabilities | ||
38 | Capabilities (..) | ||
39 | , Extension (..) | ||
40 | , Caps | ||
41 | |||
42 | -- * Handshake | ||
43 | , ProtocolName | ||
44 | , Handshake(..) | ||
45 | , defaultHandshake | ||
46 | , handshakeSize | ||
47 | , handshakeMaxSize | ||
48 | , handshakeStats | ||
49 | |||
50 | -- * Stats | ||
51 | , ByteCount | ||
52 | , ByteStats (..) | ||
53 | , byteLength | ||
54 | |||
55 | -- * Messages | ||
56 | , Message (..) | ||
57 | , defaultKeepAliveTimeout | ||
58 | , defaultKeepAliveInterval | ||
59 | , PeerMessage (..) | ||
60 | |||
61 | -- ** Core messages | ||
62 | , StatusUpdate (..) | ||
63 | , Available (..) | ||
64 | , Transfer (..) | ||
65 | , defaultRequestQueueLength | ||
66 | |||
67 | -- ** Fast extension | ||
68 | , FastMessage (..) | ||
69 | |||
70 | -- ** Extension protocol | ||
71 | , ExtendedMessage (..) | ||
72 | |||
73 | -- *** Capabilities | ||
74 | , ExtendedExtension (..) | ||
75 | , ExtendedCaps (..) | ||
76 | |||
77 | -- *** Handshake | ||
78 | , ExtendedHandshake (..) | ||
79 | , defaultQueueLength | ||
80 | , nullExtendedHandshake | ||
81 | |||
82 | -- *** Metadata | ||
83 | , ExtendedMetadata (..) | ||
84 | , metadataPieceSize | ||
85 | , defaultMetadataFactor | ||
86 | , defaultMaxInfoDictSize | ||
87 | , isLastPiece | ||
88 | , isValidPiece | ||
89 | ) where | ||
90 | |||
91 | import Control.Applicative | ||
92 | import Control.Arrow ((&&&), (***)) | ||
93 | import Control.Monad (when) | ||
94 | import Data.Attoparsec.ByteString.Char8 as BS | ||
95 | import Data.BEncode as BE | ||
96 | import Data.BEncode.BDict as BE | ||
97 | import Data.BEncode.Internal as BE (ppBEncode, parser) | ||
98 | import Data.BEncode.Types (BDict) | ||
99 | import Data.Bits | ||
100 | import Data.ByteString as BS | ||
101 | import Data.ByteString.Char8 as BC | ||
102 | import Data.ByteString.Lazy as BL | ||
103 | import Data.Default | ||
104 | import Data.List as L | ||
105 | import Data.Map.Strict as M | ||
106 | import Data.Maybe | ||
107 | import Data.Monoid | ||
108 | import Data.Ord | ||
109 | import Data.Serialize as S | ||
110 | import Data.String | ||
111 | import Data.Text as T | ||
112 | import Data.Typeable | ||
113 | import Data.Word | ||
114 | import Data.IP | ||
115 | import Network | ||
116 | import Network.Socket hiding (KeepAlive) | ||
117 | import Text.PrettyPrint as PP hiding ((<>)) | ||
118 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) | ||
119 | |||
120 | import Data.Torrent hiding (Piece (..)) | ||
121 | import qualified Data.Torrent as P (Piece (..)) | ||
122 | import Network.Address | ||
123 | import Network.BitTorrent.Exchange.Bitfield | ||
124 | import Network.BitTorrent.Exchange.Block | ||
125 | |||
126 | {----------------------------------------------------------------------- | ||
127 | -- Capabilities | ||
128 | -----------------------------------------------------------------------} | ||
129 | |||
130 | -- | | ||
131 | class Capabilities caps where | ||
132 | type Ext caps :: * | ||
133 | |||
134 | -- | Pack extensions to caps. | ||
135 | toCaps :: [Ext caps] -> caps | ||
136 | |||
137 | -- | Unpack extensions from caps. | ||
138 | fromCaps :: caps -> [Ext caps] | ||
139 | |||
140 | -- | Check if an extension is a member of the specified set. | ||
141 | allowed :: Ext caps -> caps -> Bool | ||
142 | |||
143 | ppCaps :: Capabilities caps => Pretty (Ext caps) => caps -> Doc | ||
144 | ppCaps = hcat . punctuate ", " . L.map pPrint . fromCaps | ||
145 | |||
146 | {----------------------------------------------------------------------- | ||
147 | -- Extensions | ||
148 | -----------------------------------------------------------------------} | ||
149 | |||
150 | -- | Enumeration of message extension protocols. | ||
151 | -- | ||
152 | -- For more info see: <http://www.bittorrent.org/beps/bep_0004.html> | ||
153 | -- | ||
154 | data Extension | ||
155 | = ExtDHT -- ^ BEP 5: allow to send PORT messages. | ||
156 | | ExtFast -- ^ BEP 6: allow to send FAST messages. | ||
157 | | ExtExtended -- ^ BEP 10: allow to send the extension protocol messages. | ||
158 | deriving (Show, Eq, Ord, Enum, Bounded) | ||
159 | |||
160 | -- | Full extension names, suitable for logging. | ||
161 | instance Pretty Extension where | ||
162 | pPrint ExtDHT = "Distributed Hash Table Protocol" | ||
163 | pPrint ExtFast = "Fast Extension" | ||
164 | pPrint ExtExtended = "Extension Protocol" | ||
165 | |||
166 | -- | Extension bitmask as specified by BEP 4. | ||
167 | extMask :: Extension -> Word64 | ||
168 | extMask ExtDHT = 0x01 | ||
169 | extMask ExtFast = 0x04 | ||
170 | extMask ExtExtended = 0x100000 | ||
171 | |||
172 | {----------------------------------------------------------------------- | ||
173 | -- Capabilities | ||
174 | -----------------------------------------------------------------------} | ||
175 | |||
176 | -- | Capabilities is a set of 'Extension's usually sent in 'Handshake' | ||
177 | -- messages. | ||
178 | newtype Caps = Caps Word64 | ||
179 | deriving (Show, Eq) | ||
180 | |||
181 | -- | Render set of extensions as comma separated list. | ||
182 | instance Pretty Caps where | ||
183 | pPrint = ppCaps | ||
184 | {-# INLINE pPrint #-} | ||
185 | |||
186 | -- | The empty set. | ||
187 | instance Default Caps where | ||
188 | def = Caps 0 | ||
189 | {-# INLINE def #-} | ||
190 | |||
191 | -- | Monoid under intersection. 'mempty' includes all known extensions. | ||
192 | instance Monoid Caps where | ||
193 | mempty = toCaps [minBound .. maxBound] | ||
194 | {-# INLINE mempty #-} | ||
195 | |||
196 | mappend (Caps a) (Caps b) = Caps (a .&. b) | ||
197 | {-# INLINE mappend #-} | ||
198 | |||
199 | -- | 'Handshake' compatible encoding. | ||
200 | instance Serialize Caps where | ||
201 | put (Caps caps) = S.putWord64be caps | ||
202 | {-# INLINE put #-} | ||
203 | |||
204 | get = Caps <$> S.getWord64be | ||
205 | {-# INLINE get #-} | ||
206 | |||
207 | instance Capabilities Caps where | ||
208 | type Ext Caps = Extension | ||
209 | |||
210 | allowed e (Caps caps) = (extMask e .&. caps) /= 0 | ||
211 | {-# INLINE allowed #-} | ||
212 | |||
213 | toCaps = Caps . L.foldr (.|.) 0 . L.map extMask | ||
214 | fromCaps caps = L.filter (`allowed` caps) [minBound..maxBound] | ||
215 | |||
216 | {----------------------------------------------------------------------- | ||
217 | Handshake | ||
218 | -----------------------------------------------------------------------} | ||
219 | |||
220 | maxProtocolNameSize :: Word8 | ||
221 | maxProtocolNameSize = maxBound | ||
222 | |||
223 | -- | The protocol name is used to identify to the local peer which | ||
224 | -- version of BTP the remote peer uses. | ||
225 | newtype ProtocolName = ProtocolName BS.ByteString | ||
226 | deriving (Eq, Ord, Typeable) | ||
227 | |||
228 | -- | In BTP/1.0 the name is 'BitTorrent protocol'. If this string is | ||
229 | -- different from the local peers own protocol name, then the | ||
230 | -- connection is to be dropped. | ||
231 | instance Default ProtocolName where | ||
232 | def = ProtocolName "BitTorrent protocol" | ||
233 | |||
234 | instance Show ProtocolName where | ||
235 | show (ProtocolName bs) = show bs | ||
236 | |||
237 | instance Pretty ProtocolName where | ||
238 | pPrint (ProtocolName bs) = PP.text $ BC.unpack bs | ||
239 | |||
240 | instance IsString ProtocolName where | ||
241 | fromString str | ||
242 | | L.length str <= fromIntegral maxProtocolNameSize | ||
243 | = ProtocolName (fromString str) | ||
244 | | otherwise = error $ "fromString: ProtocolName too long: " ++ str | ||
245 | |||
246 | instance Serialize ProtocolName where | ||
247 | put (ProtocolName bs) = do | ||
248 | putWord8 $ fromIntegral $ BS.length bs | ||
249 | putByteString bs | ||
250 | |||
251 | get = do | ||
252 | len <- getWord8 | ||
253 | bs <- getByteString $ fromIntegral len | ||
254 | return (ProtocolName bs) | ||
255 | |||
256 | -- | Handshake message is used to exchange all information necessary | ||
257 | -- to establish connection between peers. | ||
258 | -- | ||
259 | data Handshake = Handshake { | ||
260 | -- | Identifier of the protocol. This is usually equal to 'def'. | ||
261 | hsProtocol :: ProtocolName | ||
262 | |||
263 | -- | Reserved bytes used to specify supported BEP's. | ||
264 | , hsReserved :: Caps | ||
265 | |||
266 | -- | Info hash of the info part of the metainfo file. that is | ||
267 | -- transmitted in tracker requests. Info hash of the initiator | ||
268 | -- handshake and response handshake should match, otherwise | ||
269 | -- initiator should break the connection. | ||
270 | -- | ||
271 | , hsInfoHash :: InfoHash | ||
272 | |||
273 | -- | Peer id of the initiator. This is usually the same peer id | ||
274 | -- that is transmitted in tracker requests. | ||
275 | -- | ||
276 | , hsPeerId :: PeerId | ||
277 | |||
278 | } deriving (Show, Eq) | ||
279 | |||
280 | instance Serialize Handshake where | ||
281 | put Handshake {..} = do | ||
282 | put hsProtocol | ||
283 | put hsReserved | ||
284 | put hsInfoHash | ||
285 | put hsPeerId | ||
286 | get = Handshake <$> get <*> get <*> get <*> get | ||
287 | |||
288 | -- | Show handshake protocol string, caps and fingerprint. | ||
289 | instance Pretty Handshake where | ||
290 | pPrint Handshake {..} | ||
291 | = pPrint hsProtocol $$ | ||
292 | pPrint hsReserved $$ | ||
293 | pPrint (fingerprint hsPeerId) | ||
294 | |||
295 | -- | Get handshake message size in bytes from the length of protocol | ||
296 | -- string. | ||
297 | handshakeSize :: Word8 -> Int | ||
298 | handshakeSize n = 1 + fromIntegral n + 8 + 20 + 20 | ||
299 | |||
300 | -- | Maximum size of handshake message in bytes. | ||
301 | handshakeMaxSize :: Int | ||
302 | handshakeMaxSize = handshakeSize maxProtocolNameSize | ||
303 | |||
304 | -- | Handshake with default protocol string and reserved bitmask. | ||
305 | defaultHandshake :: InfoHash -> PeerId -> Handshake | ||
306 | defaultHandshake = Handshake def def | ||
307 | |||
308 | handshakeStats :: Handshake -> ByteStats | ||
309 | handshakeStats (Handshake (ProtocolName bs) _ _ _) | ||
310 | = ByteStats 1 (BS.length bs + 8 + 20 + 20) 0 | ||
311 | |||
312 | {----------------------------------------------------------------------- | ||
313 | -- Stats | ||
314 | -----------------------------------------------------------------------} | ||
315 | |||
316 | -- | Number of bytes. | ||
317 | type ByteCount = Int | ||
318 | |||
319 | -- | Summary of encoded message byte layout can be used to collect | ||
320 | -- stats about message flow in both directions. This data can be | ||
321 | -- retrieved using 'stats' function. | ||
322 | data ByteStats = ByteStats | ||
323 | { -- | Number of bytes used to help encode 'control' and 'payload' | ||
324 | -- bytes: message size, message ID's, etc | ||
325 | overhead :: {-# UNPACK #-} !ByteCount | ||
326 | |||
327 | -- | Number of bytes used to exchange peers state\/options: piece | ||
328 | -- and block indexes, infohash, port numbers, peer ID\/IP, etc. | ||
329 | , control :: {-# UNPACK #-} !ByteCount | ||
330 | |||
331 | -- | Number of payload bytes: torrent data blocks and infodict | ||
332 | -- metadata. | ||
333 | , payload :: {-# UNPACK #-} !ByteCount | ||
334 | } deriving Show | ||
335 | |||
336 | instance Pretty ByteStats where | ||
337 | pPrint s @ ByteStats {..} = fsep | ||
338 | [ PP.int overhead, "overhead" | ||
339 | , PP.int control, "control" | ||
340 | , PP.int payload, "payload" | ||
341 | , "bytes" | ||
342 | ] $+$ fsep | ||
343 | [ PP.int (byteLength s), "total bytes" | ||
344 | ] | ||
345 | |||
346 | -- | Empty byte sequences. | ||
347 | instance Default ByteStats where | ||
348 | def = ByteStats 0 0 0 | ||
349 | |||
350 | -- | Monoid under addition. | ||
351 | instance Monoid ByteStats where | ||
352 | mempty = def | ||
353 | mappend a b = ByteStats | ||
354 | { overhead = overhead a + overhead b | ||
355 | , control = control a + control b | ||
356 | , payload = payload a + payload b | ||
357 | } | ||
358 | |||
359 | -- | Sum of the all byte sequences. | ||
360 | byteLength :: ByteStats -> Int | ||
361 | byteLength ByteStats {..} = overhead + control + payload | ||
362 | |||
363 | {----------------------------------------------------------------------- | ||
364 | -- Regular messages | ||
365 | -----------------------------------------------------------------------} | ||
366 | |||
367 | -- | Messages which can be sent after handshaking. Minimal complete | ||
368 | -- definition: 'envelop'. | ||
369 | class PeerMessage a where | ||
370 | -- | Construct a message to be /sent/. Note that if 'ExtendedCaps' | ||
371 | -- do not contain mapping for this message the default | ||
372 | -- 'ExtendedMessageId' is used. | ||
373 | envelop :: ExtendedCaps -- ^ The /receiver/ extended capabilities; | ||
374 | -> a -- ^ An regular message; | ||
375 | -> Message -- ^ Enveloped message to sent. | ||
376 | |||
377 | -- | Find out the extension this message belong to. Can be used to | ||
378 | -- check if this message is allowed to send\/recv in current | ||
379 | -- session. | ||
380 | requires :: a -> Maybe Extension | ||
381 | requires _ = Nothing | ||
382 | |||
383 | -- | Get sizes of overhead\/control\/payload byte sequences of | ||
384 | -- binary message representation without encoding message to binary | ||
385 | -- bytestring. | ||
386 | -- | ||
387 | -- This function should obey one law: | ||
388 | -- | ||
389 | -- * 'byteLength' ('stats' msg) == 'BL.length' ('encode' msg) | ||
390 | -- | ||
391 | stats :: a -> ByteStats | ||
392 | stats _ = ByteStats 4 0 0 | ||
393 | |||
394 | {----------------------------------------------------------------------- | ||
395 | -- Status messages | ||
396 | -----------------------------------------------------------------------} | ||
397 | |||
398 | -- | Notification that the sender have updated its | ||
399 | -- 'Network.BitTorrent.Exchange.Status.PeerStatus'. | ||
400 | data StatusUpdate | ||
401 | -- | Notification that the sender will not upload data to the | ||
402 | -- receiver until unchoking happen. | ||
403 | = Choking !Bool | ||
404 | |||
405 | -- | Notification that the sender is interested (or not interested) | ||
406 | -- in any of the receiver's data pieces. | ||
407 | | Interested !Bool | ||
408 | deriving (Show, Eq, Ord, Typeable) | ||
409 | |||
410 | instance Pretty StatusUpdate where | ||
411 | pPrint (Choking False) = "not choking" | ||
412 | pPrint (Choking True ) = "choking" | ||
413 | pPrint (Interested False) = "not interested" | ||
414 | pPrint (Interested True ) = "interested" | ||
415 | |||
416 | instance PeerMessage StatusUpdate where | ||
417 | envelop _ = Status | ||
418 | {-# INLINE envelop #-} | ||
419 | |||
420 | stats _ = ByteStats 4 1 0 | ||
421 | {-# INLINE stats #-} | ||
422 | |||
423 | {----------------------------------------------------------------------- | ||
424 | -- Available messages | ||
425 | -----------------------------------------------------------------------} | ||
426 | |||
427 | -- | Messages used to inform receiver which pieces of the torrent | ||
428 | -- sender have. | ||
429 | data Available = | ||
430 | -- | Zero-based index of a piece that has just been successfully | ||
431 | -- downloaded and verified via the hash. | ||
432 | Have ! PieceIx | ||
433 | |||
434 | -- | The bitfield message may only be sent immediately after the | ||
435 | -- handshaking sequence is complete, and before any other message | ||
436 | -- are sent. If client have no pieces then bitfield need not to be | ||
437 | -- sent. | ||
438 | | Bitfield !Bitfield | ||
439 | deriving (Show, Eq) | ||
440 | |||
441 | instance Pretty Available where | ||
442 | pPrint (Have ix ) = "Have" <+> int ix | ||
443 | pPrint (Bitfield _ ) = "Bitfield" | ||
444 | |||
445 | instance PeerMessage Available where | ||
446 | envelop _ = Available | ||
447 | {-# INLINE envelop #-} | ||
448 | |||
449 | stats (Have _) = ByteStats (4 + 1) 4 0 | ||
450 | stats (Bitfield bf) = ByteStats (4 + 1) (q + trailing) 0 | ||
451 | where | ||
452 | trailing = if r == 0 then 0 else 1 | ||
453 | (q, r) = quotRem (totalCount bf) 8 | ||
454 | |||
455 | {----------------------------------------------------------------------- | ||
456 | -- Transfer messages | ||
457 | -----------------------------------------------------------------------} | ||
458 | |||
459 | -- | Messages used to transfer 'Block's. | ||
460 | data Transfer | ||
461 | -- | Request for a particular block. If a client is requested a | ||
462 | -- block that another peer do not have the peer might not answer | ||
463 | -- at all. | ||
464 | = Request ! BlockIx | ||
465 | |||
466 | -- | Response to a request for a block. | ||
467 | | Piece !(Block BL.ByteString) | ||
468 | |||
469 | -- | Used to cancel block requests. It is typically used during | ||
470 | -- "End Game". | ||
471 | | Cancel !BlockIx | ||
472 | deriving (Show, Eq) | ||
473 | |||
474 | instance Pretty Transfer where | ||
475 | pPrint (Request ix ) = "Request" <+> pPrint ix | ||
476 | pPrint (Piece blk) = "Piece" <+> pPrint blk | ||
477 | pPrint (Cancel i ) = "Cancel" <+> pPrint i | ||
478 | |||
479 | instance PeerMessage Transfer where | ||
480 | envelop _ = Transfer | ||
481 | {-# INLINE envelop #-} | ||
482 | |||
483 | stats (Request _ ) = ByteStats (4 + 1) (3 * 4) 0 | ||
484 | stats (Piece p ) = ByteStats (4 + 1) (4 + 4 + blockSize p) 0 | ||
485 | stats (Cancel _ ) = ByteStats (4 + 1) (3 * 4) 0 | ||
486 | |||
487 | -- TODO increase | ||
488 | -- | Max number of pending 'Request's inflight. | ||
489 | defaultRequestQueueLength :: Int | ||
490 | defaultRequestQueueLength = 1 | ||
491 | |||
492 | {----------------------------------------------------------------------- | ||
493 | -- Fast messages | ||
494 | -----------------------------------------------------------------------} | ||
495 | |||
496 | -- | BEP6 messages. | ||
497 | data FastMessage = | ||
498 | -- | If a peer have all pieces it might send the 'HaveAll' message | ||
499 | -- instead of 'Bitfield' message. Used to save bandwidth. | ||
500 | HaveAll | ||
501 | |||
502 | -- | If a peer have no pieces it might send 'HaveNone' message | ||
503 | -- intead of 'Bitfield' message. Used to save bandwidth. | ||
504 | | HaveNone | ||
505 | |||
506 | -- | This is an advisory message meaning "you might like to | ||
507 | -- download this piece." Used to avoid excessive disk seeks and | ||
508 | -- amount of IO. | ||
509 | | SuggestPiece !PieceIx | ||
510 | |||
511 | -- | Notifies a requesting peer that its request will not be | ||
512 | -- satisfied. | ||
513 | | RejectRequest !BlockIx | ||
514 | |||
515 | -- | This is an advisory messsage meaning \"if you ask for this | ||
516 | -- piece, I'll give it to you even if you're choked.\" Used to | ||
517 | -- shorten starting phase. | ||
518 | | AllowedFast !PieceIx | ||
519 | deriving (Show, Eq) | ||
520 | |||
521 | instance Pretty FastMessage where | ||
522 | pPrint (HaveAll ) = "Have all" | ||
523 | pPrint (HaveNone ) = "Have none" | ||
524 | pPrint (SuggestPiece pix) = "Suggest" <+> int pix | ||
525 | pPrint (RejectRequest bix) = "Reject" <+> pPrint bix | ||
526 | pPrint (AllowedFast pix) = "Allowed fast" <+> int pix | ||
527 | |||
528 | instance PeerMessage FastMessage where | ||
529 | envelop _ = Fast | ||
530 | {-# INLINE envelop #-} | ||
531 | |||
532 | requires _ = Just ExtFast | ||
533 | {-# INLINE requires #-} | ||
534 | |||
535 | stats HaveAll = ByteStats 4 1 0 | ||
536 | stats HaveNone = ByteStats 4 1 0 | ||
537 | stats (SuggestPiece _) = ByteStats 5 4 0 | ||
538 | stats (RejectRequest _) = ByteStats 5 12 0 | ||
539 | stats (AllowedFast _) = ByteStats 5 4 0 | ||
540 | |||
541 | {----------------------------------------------------------------------- | ||
542 | -- Extension protocol | ||
543 | -----------------------------------------------------------------------} | ||
544 | |||
545 | {----------------------------------------------------------------------- | ||
546 | -- Extended capabilities | ||
547 | -----------------------------------------------------------------------} | ||
548 | |||
549 | data ExtendedExtension | ||
550 | = ExtMetadata -- ^ BEP 9: Extension for Peers to Send Metadata Files | ||
551 | deriving (Show, Eq, Ord, Enum, Bounded, Typeable) | ||
552 | |||
553 | instance IsString ExtendedExtension where | ||
554 | fromString = fromMaybe (error msg) . fromKey . fromString | ||
555 | where | ||
556 | msg = "fromString: could not parse ExtendedExtension" | ||
557 | |||
558 | instance Pretty ExtendedExtension where | ||
559 | pPrint ExtMetadata = "Extension for Peers to Send Metadata Files" | ||
560 | |||
561 | fromKey :: BKey -> Maybe ExtendedExtension | ||
562 | fromKey "ut_metadata" = Just ExtMetadata | ||
563 | fromKey _ = Nothing | ||
564 | {-# INLINE fromKey #-} | ||
565 | |||
566 | toKey :: ExtendedExtension -> BKey | ||
567 | toKey ExtMetadata = "ut_metadata" | ||
568 | {-# INLINE toKey #-} | ||
569 | |||
570 | type ExtendedMessageId = Word8 | ||
571 | |||
572 | extId :: ExtendedExtension -> ExtendedMessageId | ||
573 | extId ExtMetadata = 1 | ||
574 | {-# INLINE extId #-} | ||
575 | |||
576 | type ExtendedMap = Map ExtendedExtension ExtendedMessageId | ||
577 | |||
578 | -- | The extension IDs must be stored for every peer, because every | ||
579 | -- peer may have different IDs for the same extension. | ||
580 | -- | ||
581 | newtype ExtendedCaps = ExtendedCaps { extendedCaps :: ExtendedMap } | ||
582 | deriving (Show, Eq) | ||
583 | |||
584 | instance Pretty ExtendedCaps where | ||
585 | pPrint = ppCaps | ||
586 | {-# INLINE pPrint #-} | ||
587 | |||
588 | -- | The empty set. | ||
589 | instance Default ExtendedCaps where | ||
590 | def = ExtendedCaps M.empty | ||
591 | |||
592 | -- | Monoid under intersection: | ||
593 | -- | ||
594 | -- * The 'mempty' caps includes all known extensions; | ||
595 | -- | ||
596 | -- * the 'mappend' operation is NOT commutative: it return message | ||
597 | -- id from the first caps for the extensions existing in both caps. | ||
598 | -- | ||
599 | instance Monoid ExtendedCaps where | ||
600 | mempty = toCaps [minBound..maxBound] | ||
601 | mappend (ExtendedCaps a) (ExtendedCaps b) = | ||
602 | ExtendedCaps (M.intersection a b) | ||
603 | |||
604 | appendBDict :: BDict -> ExtendedMap -> ExtendedMap | ||
605 | appendBDict (Cons key val xs) caps | ||
606 | | Just ext <- fromKey key | ||
607 | , Right eid <- fromBEncode val = M.insert ext eid (appendBDict xs caps) | ||
608 | | otherwise = appendBDict xs caps | ||
609 | appendBDict Nil caps = caps | ||
610 | |||
611 | -- | Handshake compatible encoding. | ||
612 | instance BEncode ExtendedCaps where | ||
613 | toBEncode = BDict . BE.fromAscList . L.sortBy (comparing fst) | ||
614 | . L.map (toKey *** toBEncode) . M.toList . extendedCaps | ||
615 | |||
616 | fromBEncode (BDict bd) = pure $ ExtendedCaps $ appendBDict bd M.empty | ||
617 | fromBEncode _ = decodingError "ExtendedCaps" | ||
618 | |||
619 | instance Capabilities ExtendedCaps where | ||
620 | type Ext ExtendedCaps = ExtendedExtension | ||
621 | |||
622 | toCaps = ExtendedCaps . M.fromList . L.map (id &&& extId) | ||
623 | |||
624 | fromCaps = M.keys . extendedCaps | ||
625 | {-# INLINE fromCaps #-} | ||
626 | |||
627 | allowed e (ExtendedCaps caps) = M.member e caps | ||
628 | {-# INLINE allowed #-} | ||
629 | |||
630 | remoteMessageId :: ExtendedExtension -> ExtendedCaps -> ExtendedMessageId | ||
631 | remoteMessageId ext = fromMaybe (extId ext) . M.lookup ext . extendedCaps | ||
632 | |||
633 | {----------------------------------------------------------------------- | ||
634 | -- Extended handshake | ||
635 | -----------------------------------------------------------------------} | ||
636 | |||
637 | -- | This message should be sent immediately after the standard | ||
638 | -- bittorrent handshake to any peer that supports this extension | ||
639 | -- protocol. Extended handshakes can be sent more than once, however | ||
640 | -- an implementation may choose to ignore subsequent handshake | ||
641 | -- messages. | ||
642 | -- | ||
643 | data ExtendedHandshake = ExtendedHandshake | ||
644 | { -- | If this peer has an IPv4 interface, this is the compact | ||
645 | -- representation of that address. | ||
646 | ehsIPv4 :: Maybe HostAddress | ||
647 | |||
648 | -- | If this peer has an IPv6 interface, this is the compact | ||
649 | -- representation of that address. | ||
650 | , ehsIPv6 :: Maybe HostAddress6 | ||
651 | |||
652 | -- | Dictionary of supported extension messages which maps names | ||
653 | -- of extensions to an extended message ID for each extension | ||
654 | -- message. | ||
655 | , ehsCaps :: ExtendedCaps | ||
656 | |||
657 | -- | Size of 'Data.Torrent.InfoDict' in bytes. This field should | ||
658 | -- be added if 'ExtMetadata' is enabled in current session /and/ | ||
659 | -- peer have the torrent file. | ||
660 | , ehsMetadataSize :: Maybe Int | ||
661 | |||
662 | -- | Local TCP /listen/ port. Allows each side to learn about the | ||
663 | -- TCP port number of the other side. | ||
664 | , ehsPort :: Maybe PortNumber | ||
665 | |||
666 | -- | Request queue the number of outstanding 'Request' messages | ||
667 | -- this client supports without dropping any. | ||
668 | , ehsQueueLength :: Maybe Int | ||
669 | |||
670 | -- | Client name and version. | ||
671 | , ehsVersion :: Maybe Text | ||
672 | |||
673 | -- | IP of the remote end | ||
674 | , ehsYourIp :: Maybe IP | ||
675 | } deriving (Show, Eq, Typeable) | ||
676 | |||
677 | extHandshakeId :: ExtendedMessageId | ||
678 | extHandshakeId = 0 | ||
679 | |||
680 | -- | Default 'Request' queue size. | ||
681 | defaultQueueLength :: Int | ||
682 | defaultQueueLength = 1 | ||
683 | |||
684 | -- | All fields are empty. | ||
685 | instance Default ExtendedHandshake where | ||
686 | def = ExtendedHandshake def def def def def def def def | ||
687 | |||
688 | instance Monoid ExtendedHandshake where | ||
689 | mempty = def { ehsCaps = mempty } | ||
690 | mappend old new = ExtendedHandshake { | ||
691 | ehsCaps = ehsCaps old <> ehsCaps new, | ||
692 | ehsIPv4 = ehsIPv4 old `mergeOld` ehsIPv4 new, | ||
693 | ehsIPv6 = ehsIPv6 old `mergeOld` ehsIPv6 new, | ||
694 | ehsMetadataSize = ehsMetadataSize old `mergeNew` ehsMetadataSize new, | ||
695 | ehsPort = ehsPort old `mergeOld` ehsPort new, | ||
696 | ehsQueueLength = ehsQueueLength old `mergeNew` ehsQueueLength new, | ||
697 | ehsVersion = ehsVersion old `mergeOld` ehsVersion new, | ||
698 | ehsYourIp = ehsYourIp old `mergeOld` ehsYourIp new | ||
699 | } | ||
700 | where | ||
701 | mergeOld mold mnew = mold <|> mnew | ||
702 | mergeNew mold mnew = mnew <|> mold | ||
703 | |||
704 | |||
705 | instance BEncode ExtendedHandshake where | ||
706 | toBEncode ExtendedHandshake {..} = toDict $ | ||
707 | "ipv4" .=? (S.encode <$> ehsIPv4) | ||
708 | .: "ipv6" .=? (S.encode <$> ehsIPv6) | ||
709 | .: "m" .=! ehsCaps | ||
710 | .: "metadata_size" .=? ehsMetadataSize | ||
711 | .: "p" .=? ehsPort | ||
712 | .: "reqq" .=? ehsQueueLength | ||
713 | .: "v" .=? ehsVersion | ||
714 | .: "yourip" .=? (runPut <$> either put put <$> toEither <$> ehsYourIp) | ||
715 | .: endDict | ||
716 | where | ||
717 | toEither (IPv4 v4) = Left v4 | ||
718 | toEither (IPv6 v6) = Right v6 | ||
719 | |||
720 | fromBEncode = fromDict $ ExtendedHandshake | ||
721 | <$>? "ipv4" | ||
722 | <*>? "ipv6" | ||
723 | <*>! "m" | ||
724 | <*>? "metadata_size" | ||
725 | <*>? "p" | ||
726 | <*>? "reqq" | ||
727 | <*>? "v" | ||
728 | <*> (opt "yourip" >>= getYourIp) | ||
729 | |||
730 | getYourIp :: Maybe BValue -> BE.Get (Maybe IP) | ||
731 | getYourIp f = | ||
732 | return $ do | ||
733 | BString ip <- f | ||
734 | either (const Nothing) Just $ | ||
735 | case BS.length ip of | ||
736 | 4 -> IPv4 <$> S.decode ip | ||
737 | 16 -> IPv6 <$> S.decode ip | ||
738 | _ -> fail "" | ||
739 | |||
740 | instance Pretty ExtendedHandshake where | ||
741 | pPrint = PP.text . show | ||
742 | |||
743 | -- | NOTE: Approximated 'stats'. | ||
744 | instance PeerMessage ExtendedHandshake where | ||
745 | envelop c = envelop c . EHandshake | ||
746 | {-# INLINE envelop #-} | ||
747 | |||
748 | requires _ = Just ExtExtended | ||
749 | {-# INLINE requires #-} | ||
750 | |||
751 | stats _ = ByteStats (4 + 1 + 1) 100 {- is it ok? -} 0 -- FIXME | ||
752 | {-# INLINE stats #-} | ||
753 | |||
754 | -- | Set default values and the specified 'ExtendedCaps'. | ||
755 | nullExtendedHandshake :: ExtendedCaps -> ExtendedHandshake | ||
756 | nullExtendedHandshake caps = ExtendedHandshake | ||
757 | { ehsIPv4 = Nothing | ||
758 | , ehsIPv6 = Nothing | ||
759 | , ehsCaps = caps | ||
760 | , ehsMetadataSize = Nothing | ||
761 | , ehsPort = Nothing | ||
762 | , ehsQueueLength = Just defaultQueueLength | ||
763 | , ehsVersion = Just $ T.pack $ render $ pPrint libFingerprint | ||
764 | , ehsYourIp = Nothing | ||
765 | } | ||
766 | |||
767 | {----------------------------------------------------------------------- | ||
768 | -- Metadata exchange extension | ||
769 | -----------------------------------------------------------------------} | ||
770 | |||
771 | -- | A peer MUST verify that any piece it sends passes the info-hash | ||
772 | -- verification. i.e. until the peer has the entire metadata, it | ||
773 | -- cannot run SHA-1 to verify that it yields the same hash as the | ||
774 | -- info-hash. | ||
775 | -- | ||
776 | data ExtendedMetadata | ||
777 | -- | This message requests the a specified metadata piece. The | ||
778 | -- response to this message, from a peer supporting the extension, | ||
779 | -- is either a 'MetadataReject' or a 'MetadataData' message. | ||
780 | = MetadataRequest PieceIx | ||
781 | |||
782 | -- | If sender requested a valid 'PieceIx' and receiver have the | ||
783 | -- corresponding piece then receiver should respond with this | ||
784 | -- message. | ||
785 | | MetadataData | ||
786 | { -- | A piece of 'Data.Torrent.InfoDict'. | ||
787 | piece :: P.Piece BS.ByteString | ||
788 | |||
789 | -- | This key has the same semantics as the 'ehsMetadataSize' in | ||
790 | -- the 'ExtendedHandshake' — it is size of the torrent info | ||
791 | -- dict. | ||
792 | , totalSize :: Int | ||
793 | } | ||
794 | |||
795 | -- | Peers that do not have the entire metadata MUST respond with | ||
796 | -- a reject message to any metadata request. | ||
797 | -- | ||
798 | -- Clients MAY implement flood protection by rejecting request | ||
799 | -- messages after a certain number of them have been | ||
800 | -- served. Typically the number of pieces of metadata times a | ||
801 | -- factor. | ||
802 | | MetadataReject PieceIx | ||
803 | |||
804 | -- | Reserved. By specification we should ignore unknown metadata | ||
805 | -- messages. | ||
806 | | MetadataUnknown BValue | ||
807 | deriving (Show, Eq, Typeable) | ||
808 | |||
809 | -- | Extended metadata message id used in 'msg_type_key'. | ||
810 | type MetadataId = Int | ||
811 | |||
812 | msg_type_key, piece_key, total_size_key :: BKey | ||
813 | msg_type_key = "msg_type" | ||
814 | piece_key = "piece" | ||
815 | total_size_key = "total_size" | ||
816 | |||
817 | -- | BEP9 compatible encoding. | ||
818 | instance BEncode ExtendedMetadata where | ||
819 | toBEncode (MetadataRequest pix) = toDict $ | ||
820 | msg_type_key .=! (0 :: MetadataId) | ||
821 | .: piece_key .=! pix | ||
822 | .: endDict | ||
823 | toBEncode (MetadataData (P.Piece pix _) totalSize) = toDict $ | ||
824 | msg_type_key .=! (1 :: MetadataId) | ||
825 | .: piece_key .=! pix | ||
826 | .: total_size_key .=! totalSize | ||
827 | .: endDict | ||
828 | toBEncode (MetadataReject pix) = toDict $ | ||
829 | msg_type_key .=! (2 :: MetadataId) | ||
830 | .: piece_key .=! pix | ||
831 | .: endDict | ||
832 | toBEncode (MetadataUnknown bval) = bval | ||
833 | |||
834 | fromBEncode bval = (`fromDict` bval) $ do | ||
835 | mid <- field $ req msg_type_key | ||
836 | case mid :: MetadataId of | ||
837 | 0 -> MetadataRequest <$>! piece_key | ||
838 | 1 -> metadataData <$>! piece_key <*>! total_size_key | ||
839 | 2 -> MetadataReject <$>! piece_key | ||
840 | _ -> pure (MetadataUnknown bval) | ||
841 | where | ||
842 | metadataData pix s = MetadataData (P.Piece pix BS.empty) s | ||
843 | |||
844 | -- | Piece data bytes are omitted. | ||
845 | instance Pretty ExtendedMetadata where | ||
846 | pPrint (MetadataRequest pix ) = "Request" <+> PP.int pix | ||
847 | pPrint (MetadataData p t) = "Data" <+> pPrint p <+> PP.int t | ||
848 | pPrint (MetadataReject pix ) = "Reject" <+> PP.int pix | ||
849 | pPrint (MetadataUnknown bval ) = "Unknown" <+> ppBEncode bval | ||
850 | |||
851 | -- | NOTE: Approximated 'stats'. | ||
852 | instance PeerMessage ExtendedMetadata where | ||
853 | envelop c = envelop c . EMetadata (remoteMessageId ExtMetadata c) | ||
854 | {-# INLINE envelop #-} | ||
855 | |||
856 | requires _ = Just ExtExtended | ||
857 | {-# INLINE requires #-} | ||
858 | |||
859 | stats (MetadataRequest _) = ByteStats (4 + 1 + 1) {- ~ -} 25 0 | ||
860 | stats (MetadataData p _) = ByteStats (4 + 1 + 1) {- ~ -} 41 $ | ||
861 | BS.length (P.pieceData p) | ||
862 | stats (MetadataReject _) = ByteStats (4 + 1 + 1) {- ~ -} 25 0 | ||
863 | stats (MetadataUnknown _) = ByteStats (4 + 1 + 1) {- ? -} 0 0 | ||
864 | |||
865 | -- | All 'Piece's in 'MetadataData' messages MUST have size equal to | ||
866 | -- this value. The last trailing piece can be shorter. | ||
867 | metadataPieceSize :: PieceSize | ||
868 | metadataPieceSize = 16 * 1024 | ||
869 | |||
870 | isLastPiece :: P.Piece a -> Int -> Bool | ||
871 | isLastPiece P.Piece {..} total = succ pieceIndex == pcnt | ||
872 | where | ||
873 | pcnt = q + if r > 0 then 1 else 0 | ||
874 | (q, r) = quotRem total metadataPieceSize | ||
875 | |||
876 | -- TODO we can check if the piece payload bytestring have appropriate | ||
877 | -- length; otherwise serialization MUST fail. | ||
878 | isValidPiece :: P.Piece BL.ByteString -> Int -> Bool | ||
879 | isValidPiece p @ P.Piece {..} total | ||
880 | | isLastPiece p total = pieceSize p <= metadataPieceSize | ||
881 | | otherwise = pieceSize p == metadataPieceSize | ||
882 | |||
883 | setMetadataPayload :: BS.ByteString -> ExtendedMetadata -> ExtendedMetadata | ||
884 | setMetadataPayload bs (MetadataData (P.Piece pix _) t) = | ||
885 | MetadataData (P.Piece pix bs) t | ||
886 | setMetadataPayload _ msg = msg | ||
887 | |||
888 | getMetadataPayload :: ExtendedMetadata -> Maybe BS.ByteString | ||
889 | getMetadataPayload (MetadataData (P.Piece _ bs) _) = Just bs | ||
890 | getMetadataPayload _ = Nothing | ||
891 | |||
892 | -- | Metadata BDict usually contain only 'msg_type_key', 'piece_key' | ||
893 | -- and 'total_size_key' fields so it normally should take less than | ||
894 | -- 100 bytes. This limit is two order of magnitude larger to be | ||
895 | -- permissive to 'MetadataUnknown' messages. | ||
896 | -- | ||
897 | -- See 'maxMessageSize' for further explanation. | ||
898 | -- | ||
899 | maxMetadataBDictSize :: Int | ||
900 | maxMetadataBDictSize = 16 * 1024 | ||
901 | |||
902 | maxMetadataSize :: Int | ||
903 | maxMetadataSize = maxMetadataBDictSize + metadataPieceSize | ||
904 | |||
905 | -- to make MetadataData constructor fields a little bit prettier we | ||
906 | -- cheat here: first we read empty 'pieceData' from bdict, but then we | ||
907 | -- fill that field with the actual piece data — trailing bytes of | ||
908 | -- the message | ||
909 | getMetadata :: Int -> S.Get ExtendedMetadata | ||
910 | getMetadata len | ||
911 | | len > maxMetadataSize = fail $ parseError "size exceeded limit" | ||
912 | | otherwise = do | ||
913 | bs <- getByteString len | ||
914 | parseRes $ BS.parse BE.parser bs | ||
915 | where | ||
916 | parseError reason = "unable to parse metadata message: " ++ reason | ||
917 | |||
918 | parseRes (BS.Fail _ _ m) = fail $ parseError $ "bdict: " ++ m | ||
919 | parseRes (BS.Partial _) = fail $ parseError "bdict: not enough bytes" | ||
920 | parseRes (BS.Done piece bvalueBS) | ||
921 | | BS.length piece > metadataPieceSize | ||
922 | = fail "infodict piece: size exceeded limit" | ||
923 | | otherwise = do | ||
924 | metadata <- either (fail . parseError) pure $ fromBEncode bvalueBS | ||
925 | return $ setMetadataPayload piece metadata | ||
926 | |||
927 | putMetadata :: ExtendedMetadata -> BL.ByteString | ||
928 | putMetadata msg | ||
929 | | Just bs <- getMetadataPayload msg = BE.encode msg <> BL.fromStrict bs | ||
930 | | otherwise = BE.encode msg | ||
931 | |||
932 | -- | Allows a requesting peer to send 2 'MetadataRequest's for the | ||
933 | -- each piece. | ||
934 | -- | ||
935 | -- See 'Network.BitTorrent.Wire.Options.metadataFactor' for | ||
936 | -- explanation why do we need this limit. | ||
937 | defaultMetadataFactor :: Int | ||
938 | defaultMetadataFactor = 2 | ||
939 | |||
940 | -- | Usually torrent size do not exceed 1MB. This value limit torrent | ||
941 | -- /content/ size to about 8TB. | ||
942 | -- | ||
943 | -- See 'Network.BitTorrent.Wire.Options.maxInfoDictSize' for | ||
944 | -- explanation why do we need this limit. | ||
945 | defaultMaxInfoDictSize :: Int | ||
946 | defaultMaxInfoDictSize = 10 * 1024 * 1024 | ||
947 | |||
948 | {----------------------------------------------------------------------- | ||
949 | -- Extension protocol messages | ||
950 | -----------------------------------------------------------------------} | ||
951 | |||
952 | -- | For more info see <http://www.bittorrent.org/beps/bep_0010.html> | ||
953 | data ExtendedMessage | ||
954 | = EHandshake ExtendedHandshake | ||
955 | | EMetadata ExtendedMessageId ExtendedMetadata | ||
956 | | EUnknown ExtendedMessageId BS.ByteString | ||
957 | deriving (Show, Eq, Typeable) | ||
958 | |||
959 | instance Pretty ExtendedMessage where | ||
960 | pPrint (EHandshake ehs) = pPrint ehs | ||
961 | pPrint (EMetadata _ msg) = "Metadata" <+> pPrint msg | ||
962 | pPrint (EUnknown mid _ ) = "Unknown" <+> PP.text (show mid) | ||
963 | |||
964 | instance PeerMessage ExtendedMessage where | ||
965 | envelop _ = Extended | ||
966 | {-# INLINE envelop #-} | ||
967 | |||
968 | requires _ = Just ExtExtended | ||
969 | {-# INLINE requires #-} | ||
970 | |||
971 | stats (EHandshake hs) = stats hs | ||
972 | stats (EMetadata _ msg) = stats msg | ||
973 | stats (EUnknown _ msg) = ByteStats (4 + 1 + 1) (BS.length msg) 0 | ||
974 | |||
975 | {----------------------------------------------------------------------- | ||
976 | -- The message datatype | ||
977 | -----------------------------------------------------------------------} | ||
978 | |||
979 | type MessageId = Word8 | ||
980 | |||
981 | -- | Messages used in communication between peers. | ||
982 | -- | ||
983 | -- Note: If some extensions are disabled (not present in extension | ||
984 | -- mask) and client receive message used by the disabled | ||
985 | -- extension then the client MUST close the connection. | ||
986 | -- | ||
987 | data Message | ||
988 | -- | Peers may close the TCP connection if they have not received | ||
989 | -- any messages for a given period of time, generally 2 | ||
990 | -- minutes. Thus, the KeepAlive message is sent to keep the | ||
991 | -- connection between two peers alive, if no /other/ message has | ||
992 | -- been sent in a given period of time. | ||
993 | = KeepAlive | ||
994 | | Status !StatusUpdate -- ^ Messages used to update peer status. | ||
995 | | Available !Available -- ^ Messages used to inform availability. | ||
996 | | Transfer !Transfer -- ^ Messages used to transfer 'Block's. | ||
997 | |||
998 | -- | Peer receiving a handshake indicating the remote peer | ||
999 | -- supports the 'ExtDHT' should send a 'Port' message. Peers that | ||
1000 | -- receive this message should attempt to ping the node on the | ||
1001 | -- received port and IP address of the remote peer. | ||
1002 | | Port !PortNumber | ||
1003 | | Fast !FastMessage | ||
1004 | | Extended !ExtendedMessage | ||
1005 | deriving (Show, Eq) | ||
1006 | |||
1007 | instance Default Message where | ||
1008 | def = KeepAlive | ||
1009 | {-# INLINE def #-} | ||
1010 | |||
1011 | -- | Payload bytes are omitted. | ||
1012 | instance Pretty Message where | ||
1013 | pPrint (KeepAlive ) = "Keep alive" | ||
1014 | pPrint (Status m) = "Status" <+> pPrint m | ||
1015 | pPrint (Available m) = pPrint m | ||
1016 | pPrint (Transfer m) = pPrint m | ||
1017 | pPrint (Port p) = "Port" <+> int (fromEnum p) | ||
1018 | pPrint (Fast m) = pPrint m | ||
1019 | pPrint (Extended m) = pPrint m | ||
1020 | |||
1021 | instance PeerMessage Message where | ||
1022 | envelop _ = id | ||
1023 | {-# INLINE envelop #-} | ||
1024 | |||
1025 | requires KeepAlive = Nothing | ||
1026 | requires (Status _) = Nothing | ||
1027 | requires (Available _) = Nothing | ||
1028 | requires (Transfer _) = Nothing | ||
1029 | requires (Port _) = Just ExtDHT | ||
1030 | requires (Fast _) = Just ExtFast | ||
1031 | requires (Extended _) = Just ExtExtended | ||
1032 | |||
1033 | stats KeepAlive = ByteStats 4 0 0 | ||
1034 | stats (Status m) = stats m | ||
1035 | stats (Available m) = stats m | ||
1036 | stats (Transfer m) = stats m | ||
1037 | stats (Port _) = ByteStats 5 2 0 | ||
1038 | stats (Fast m) = stats m | ||
1039 | stats (Extended m) = stats m | ||
1040 | |||
1041 | -- | PORT message. | ||
1042 | instance PeerMessage PortNumber where | ||
1043 | envelop _ = Port | ||
1044 | {-# INLINE envelop #-} | ||
1045 | |||
1046 | requires _ = Just ExtDHT | ||
1047 | {-# INLINE requires #-} | ||
1048 | |||
1049 | -- | How long /this/ peer should wait before dropping connection, in | ||
1050 | -- seconds. | ||
1051 | defaultKeepAliveTimeout :: Int | ||
1052 | defaultKeepAliveTimeout = 2 * 60 | ||
1053 | |||
1054 | -- | How often /this/ peer should send 'KeepAlive' messages, in | ||
1055 | -- seconds. | ||
1056 | defaultKeepAliveInterval :: Int | ||
1057 | defaultKeepAliveInterval = 60 | ||
1058 | |||
1059 | getInt :: S.Get Int | ||
1060 | getInt = fromIntegral <$> S.getWord32be | ||
1061 | {-# INLINE getInt #-} | ||
1062 | |||
1063 | putInt :: S.Putter Int | ||
1064 | putInt = S.putWord32be . fromIntegral | ||
1065 | {-# INLINE putInt #-} | ||
1066 | |||
1067 | -- | This limit should protect against "out-of-memory" attacks: if a | ||
1068 | -- malicious peer have sent a long varlength message then receiver can | ||
1069 | -- accumulate too long bytestring in the 'Get'. | ||
1070 | -- | ||
1071 | -- Normal messages should never exceed this limits. | ||
1072 | -- | ||
1073 | -- See also 'maxBitfieldSize', 'maxBlockSize' limits. | ||
1074 | -- | ||
1075 | maxMessageSize :: Int | ||
1076 | maxMessageSize = 20 + 1024 * 1024 | ||
1077 | |||
1078 | -- | This also limit max torrent size to: | ||
1079 | -- | ||
1080 | -- max_bitfield_size * piece_ix_per_byte * max_piece_size = | ||
1081 | -- 2 ^ 20 * 8 * 1MB = | ||
1082 | -- 8TB | ||
1083 | -- | ||
1084 | maxBitfieldSize :: Int | ||
1085 | maxBitfieldSize = 1024 * 1024 | ||
1086 | |||
1087 | getBitfield :: Int -> S.Get Bitfield | ||
1088 | getBitfield len | ||
1089 | | len > maxBitfieldSize = fail "BITFIELD message size exceeded limit" | ||
1090 | | otherwise = fromBitmap <$> getByteString len | ||
1091 | |||
1092 | maxBlockSize :: Int | ||
1093 | maxBlockSize = 4 * defaultTransferSize | ||
1094 | |||
1095 | getBlock :: Int -> S.Get (Block BL.ByteString) | ||
1096 | getBlock len | ||
1097 | | len > maxBlockSize = fail "BLOCK message size exceeded limit" | ||
1098 | | otherwise = Block <$> getInt <*> getInt | ||
1099 | <*> getLazyByteString (fromIntegral len) | ||
1100 | {-# INLINE getBlock #-} | ||
1101 | |||
1102 | instance Serialize Message where | ||
1103 | get = do | ||
1104 | len <- getInt | ||
1105 | |||
1106 | when (len > maxMessageSize) $ do | ||
1107 | fail "message body size exceeded the limit" | ||
1108 | |||
1109 | if len == 0 then return KeepAlive | ||
1110 | else do | ||
1111 | mid <- S.getWord8 | ||
1112 | case mid of | ||
1113 | 0x00 -> return $ Status (Choking True) | ||
1114 | 0x01 -> return $ Status (Choking False) | ||
1115 | 0x02 -> return $ Status (Interested True) | ||
1116 | 0x03 -> return $ Status (Interested False) | ||
1117 | 0x04 -> (Available . Have) <$> getInt | ||
1118 | 0x05 -> (Available . Bitfield) <$> getBitfield (pred len) | ||
1119 | 0x06 -> (Transfer . Request) <$> S.get | ||
1120 | 0x07 -> (Transfer . Piece) <$> getBlock (len - 9) | ||
1121 | 0x08 -> (Transfer . Cancel) <$> S.get | ||
1122 | 0x09 -> Port <$> S.get | ||
1123 | 0x0D -> (Fast . SuggestPiece) <$> getInt | ||
1124 | 0x0E -> return $ Fast HaveAll | ||
1125 | 0x0F -> return $ Fast HaveNone | ||
1126 | 0x10 -> (Fast . RejectRequest) <$> S.get | ||
1127 | 0x11 -> (Fast . AllowedFast) <$> getInt | ||
1128 | 0x14 -> Extended <$> getExtendedMessage (pred len) | ||
1129 | _ -> do | ||
1130 | rm <- S.remaining >>= S.getBytes | ||
1131 | fail $ "unknown message ID: " ++ show mid ++ "\n" | ||
1132 | ++ "remaining available bytes: " ++ show rm | ||
1133 | |||
1134 | put KeepAlive = putInt 0 | ||
1135 | put (Status msg) = putStatus msg | ||
1136 | put (Available msg) = putAvailable msg | ||
1137 | put (Transfer msg) = putTransfer msg | ||
1138 | put (Port p ) = putPort p | ||
1139 | put (Fast msg) = putFast msg | ||
1140 | put (Extended m ) = putExtendedMessage m | ||
1141 | |||
1142 | statusUpdateId :: StatusUpdate -> MessageId | ||
1143 | statusUpdateId (Choking choking) = fromIntegral (0 + fromEnum choking) | ||
1144 | statusUpdateId (Interested choking) = fromIntegral (2 + fromEnum choking) | ||
1145 | |||
1146 | putStatus :: Putter StatusUpdate | ||
1147 | putStatus su = do | ||
1148 | putInt 1 | ||
1149 | putWord8 (statusUpdateId su) | ||
1150 | |||
1151 | putAvailable :: Putter Available | ||
1152 | putAvailable (Have i) = do | ||
1153 | putInt 5 | ||
1154 | putWord8 0x04 | ||
1155 | putInt i | ||
1156 | putAvailable (Bitfield (toBitmap -> bs)) = do | ||
1157 | putInt $ 1 + fromIntegral (BL.length bs) | ||
1158 | putWord8 0x05 | ||
1159 | putLazyByteString bs | ||
1160 | |||
1161 | putBlock :: Putter (Block BL.ByteString) | ||
1162 | putBlock Block {..} = do | ||
1163 | putInt blkPiece | ||
1164 | putInt blkOffset | ||
1165 | putLazyByteString blkData | ||
1166 | |||
1167 | putTransfer :: Putter Transfer | ||
1168 | putTransfer (Request blk) = putInt 13 >> S.putWord8 0x06 >> S.put blk | ||
1169 | putTransfer (Piece blk) = do | ||
1170 | putInt (9 + blockSize blk) | ||
1171 | putWord8 0x07 | ||
1172 | putBlock blk | ||
1173 | putTransfer (Cancel blk) = putInt 13 >> S.putWord8 0x08 >> S.put blk | ||
1174 | |||
1175 | putPort :: Putter PortNumber | ||
1176 | putPort p = do | ||
1177 | putInt 3 | ||
1178 | putWord8 0x09 | ||
1179 | put p | ||
1180 | |||
1181 | putFast :: Putter FastMessage | ||
1182 | putFast HaveAll = putInt 1 >> putWord8 0x0E | ||
1183 | putFast HaveNone = putInt 1 >> putWord8 0x0F | ||
1184 | putFast (SuggestPiece pix) = putInt 5 >> putWord8 0x0D >> putInt pix | ||
1185 | putFast (RejectRequest i ) = putInt 13 >> putWord8 0x10 >> put i | ||
1186 | putFast (AllowedFast i ) = putInt 5 >> putWord8 0x11 >> putInt i | ||
1187 | |||
1188 | maxEHandshakeSize :: Int | ||
1189 | maxEHandshakeSize = 16 * 1024 | ||
1190 | |||
1191 | getExtendedHandshake :: Int -> S.Get ExtendedHandshake | ||
1192 | getExtendedHandshake messageSize | ||
1193 | | messageSize > maxEHandshakeSize | ||
1194 | = fail "extended handshake size exceeded limit" | ||
1195 | | otherwise = do | ||
1196 | bs <- getByteString messageSize | ||
1197 | either fail pure $ BE.decode bs | ||
1198 | |||
1199 | maxEUnknownSize :: Int | ||
1200 | maxEUnknownSize = 64 * 1024 | ||
1201 | |||
1202 | getExtendedUnknown :: Int -> S.Get BS.ByteString | ||
1203 | getExtendedUnknown len | ||
1204 | | len > maxEUnknownSize = fail "unknown extended message size exceeded limit" | ||
1205 | | otherwise = getByteString len | ||
1206 | |||
1207 | getExtendedMessage :: Int -> S.Get ExtendedMessage | ||
1208 | getExtendedMessage messageSize = do | ||
1209 | msgId <- getWord8 | ||
1210 | let msgBodySize = messageSize - 1 | ||
1211 | case msgId of | ||
1212 | 0 -> EHandshake <$> getExtendedHandshake msgBodySize | ||
1213 | 1 -> EMetadata msgId <$> getMetadata msgBodySize | ||
1214 | _ -> EUnknown msgId <$> getExtendedUnknown msgBodySize | ||
1215 | |||
1216 | -- | By spec. | ||
1217 | extendedMessageId :: MessageId | ||
1218 | extendedMessageId = 20 | ||
1219 | |||
1220 | putExt :: ExtendedMessageId -> BL.ByteString -> Put | ||
1221 | putExt mid lbs = do | ||
1222 | putWord32be $ fromIntegral (1 + 1 + BL.length lbs) | ||
1223 | putWord8 extendedMessageId | ||
1224 | putWord8 mid | ||
1225 | putLazyByteString lbs | ||
1226 | |||
1227 | -- NOTE: in contrast to getExtendedMessage this function put length | ||
1228 | -- and message id too! | ||
1229 | putExtendedMessage :: Putter ExtendedMessage | ||
1230 | putExtendedMessage (EHandshake hs) = putExt extHandshakeId $ BE.encode hs | ||
1231 | putExtendedMessage (EMetadata mid msg) = putExt mid $ putMetadata msg | ||
1232 | putExtendedMessage (EUnknown mid bs) = putExt mid $ BL.fromStrict bs | ||