diff options
Diffstat (limited to 'src/Network/BitTorrent')
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 193 |
1 files changed, 142 insertions, 51 deletions
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs index 0a535517..2f85d729 100644 --- a/src/Network/BitTorrent/Exchange/Message.hs +++ b/src/Network/BitTorrent/Exchange/Message.hs | |||
@@ -25,48 +25,50 @@ | |||
25 | -- For more infomation see: | 25 | -- For more infomation see: |
26 | -- <https://wiki.theory.org/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29> | 26 | -- <https://wiki.theory.org/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29> |
27 | -- | 27 | -- |
28 | {-# LANGUAGE TemplateHaskell #-} | 28 | {-# LANGUAGE FlexibleInstances #-} |
29 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
29 | {-# LANGUAGE DeriveDataTypeable #-} | 30 | {-# LANGUAGE DeriveDataTypeable #-} |
31 | {-# LANGUAGE TemplateHaskell #-} | ||
30 | {-# OPTIONS -fno-warn-orphans #-} | 32 | {-# OPTIONS -fno-warn-orphans #-} |
31 | module Network.BitTorrent.Exchange.Message | 33 | module Network.BitTorrent.Exchange.Message |
32 | ( -- * Extensions | 34 | ( -- * Capabilities |
33 | Extension (..) | 35 | Extension (..) |
34 | , Caps | 36 | , Caps |
35 | , requires | ||
36 | , allowed | ||
37 | , toCaps | 37 | , toCaps |
38 | , fromCaps | 38 | , fromCaps |
39 | , allowed | ||
39 | 40 | ||
40 | -- * Handshake | 41 | -- * Handshake |
41 | , Handshake(..) | 42 | , Handshake(..) |
42 | , defaultHandshake | 43 | , defaultHandshake |
43 | , defaultBTProtocol | 44 | , defaultBTProtocol |
45 | , handshakeSize | ||
44 | , handshakeMaxSize | 46 | , handshakeMaxSize |
45 | 47 | ||
46 | -- * TODO remove this section from this module | ||
47 | , handshake | ||
48 | , recvHandshake | ||
49 | , sendHandshake | ||
50 | |||
51 | -- * Messages | 48 | -- * Messages |
52 | , Message (..) | 49 | , Message (..) |
50 | , PeerMessage (..) | ||
51 | , requires | ||
52 | |||
53 | -- ** Core messages | ||
53 | , StatusUpdate (..) | 54 | , StatusUpdate (..) |
54 | , RegularMessage (..) | 55 | , RegularMessage (..) |
55 | 56 | ||
56 | -- * Fast extension | 57 | -- ** Fast extension |
57 | , FastMessage (..) | 58 | , FastMessage (..) |
58 | 59 | ||
59 | -- * Extension protocol | 60 | -- ** Extension protocol |
61 | , ExtendedMessage (..) | ||
60 | , ExtendedExtension | 62 | , ExtendedExtension |
61 | , ExtendedCaps (..) | 63 | , ExtendedCaps (..) |
62 | , ExtendedHandshake (..) | 64 | , ExtendedHandshake (..) |
63 | , ExtendedMessage (..) | 65 | , ExtendedMetadata (..) |
64 | ) where | 66 | ) where |
65 | 67 | ||
66 | import Control.Applicative | 68 | import Control.Applicative |
67 | import Control.Exception | ||
68 | import Control.Monad | ||
69 | import Data.BEncode as BE | 69 | import Data.BEncode as BE |
70 | import Data.BEncode.BDict as BE | ||
71 | import Data.BEncode.Internal (ppBEncode) | ||
70 | import Data.Bits | 72 | import Data.Bits |
71 | import Data.ByteString as BS | 73 | import Data.ByteString as BS |
72 | import Data.ByteString.Char8 as BC | 74 | import Data.ByteString.Char8 as BC |
@@ -75,9 +77,11 @@ import Data.Default | |||
75 | import Data.IntMap as IM | 77 | import Data.IntMap as IM |
76 | import Data.List as L | 78 | import Data.List as L |
77 | import Data.Monoid | 79 | import Data.Monoid |
80 | import Data.Ord | ||
78 | import Data.Serialize as S | 81 | import Data.Serialize as S |
79 | import Data.Text as T | 82 | import Data.Text as T |
80 | import Data.Typeable | 83 | import Data.Typeable |
84 | import Data.Tuple | ||
81 | import Data.Word | 85 | import Data.Word |
82 | import Network | 86 | import Network |
83 | import Network.Socket hiding (KeepAlive) | 87 | import Network.Socket hiding (KeepAlive) |
@@ -219,36 +223,13 @@ defaultBTProtocol = "BitTorrent protocol" | |||
219 | defaultHandshake :: InfoHash -> PeerId -> Handshake | 223 | defaultHandshake :: InfoHash -> PeerId -> Handshake |
220 | defaultHandshake = Handshake defaultBTProtocol def | 224 | defaultHandshake = Handshake defaultBTProtocol def |
221 | 225 | ||
222 | -- | TODO remove socket stuff to corresponding module | ||
223 | sendHandshake :: Socket -> Handshake -> IO () | ||
224 | sendHandshake sock hs = sendAll sock (S.encode hs) | ||
225 | |||
226 | recvHandshake :: Socket -> IO Handshake | ||
227 | recvHandshake sock = do | ||
228 | header <- BS.recv sock 1 | ||
229 | unless (BS.length header == 1) $ | ||
230 | throw $ userError "Unable to receive handshake header." | ||
231 | |||
232 | let protocolLen = BS.head header | ||
233 | let restLen = handshakeSize protocolLen - 1 | ||
234 | |||
235 | body <- BS.recv sock restLen | ||
236 | let resp = BS.cons protocolLen body | ||
237 | either (throwIO . userError) return $ S.decode resp | ||
238 | |||
239 | -- | Handshaking with a peer specified by the second argument. | ||
240 | handshake :: Socket -> Handshake -> IO Handshake | ||
241 | handshake sock hs = do | ||
242 | sendHandshake sock hs | ||
243 | hs' <- recvHandshake sock | ||
244 | when (hsInfoHash hs /= hsInfoHash hs') $ do | ||
245 | throwIO $ userError "Handshake info hash do not match." | ||
246 | return hs' | ||
247 | |||
248 | {----------------------------------------------------------------------- | 226 | {----------------------------------------------------------------------- |
249 | Regular messages | 227 | Regular messages |
250 | -----------------------------------------------------------------------} | 228 | -----------------------------------------------------------------------} |
251 | 229 | ||
230 | class PeerMessage a where | ||
231 | envelop :: ExtendedCaps -> a -> Message | ||
232 | |||
252 | data StatusUpdate | 233 | data StatusUpdate |
253 | = Choke | 234 | = Choke |
254 | | Unchoke | 235 | | Unchoke |
@@ -259,6 +240,9 @@ data StatusUpdate | |||
259 | instance Pretty StatusUpdate where | 240 | instance Pretty StatusUpdate where |
260 | pretty = text . show | 241 | pretty = text . show |
261 | 242 | ||
243 | instance PeerMessage StatusUpdate where | ||
244 | envelop _ = Status | ||
245 | |||
262 | data RegularMessage = | 246 | data RegularMessage = |
263 | -- | Zero-based index of a piece that has just been successfully | 247 | -- | Zero-based index of a piece that has just been successfully |
264 | -- downloaded and verified via the hash. | 248 | -- downloaded and verified via the hash. |
@@ -298,6 +282,18 @@ instance Pretty RegularMessage where | |||
298 | pretty (Piece blk) = "Piece" <+> pretty blk | 282 | pretty (Piece blk) = "Piece" <+> pretty blk |
299 | pretty (Cancel i ) = "Cancel" <+> pretty i | 283 | pretty (Cancel i ) = "Cancel" <+> pretty i |
300 | 284 | ||
285 | instance PeerMessage RegularMessage where | ||
286 | envelop _ = Regular | ||
287 | |||
288 | instance PeerMessage Bitfield where | ||
289 | envelop c = envelop c . Bitfield | ||
290 | |||
291 | instance PeerMessage BlockIx where | ||
292 | envelop c = envelop c . Request | ||
293 | |||
294 | instance PeerMessage (Block BL.ByteString) where | ||
295 | envelop c = envelop c . Piece | ||
296 | |||
301 | -- | BEP6 messages. | 297 | -- | BEP6 messages. |
302 | data FastMessage = | 298 | data FastMessage = |
303 | -- | If a peer have all pieces it might send the 'HaveAll' message | 299 | -- | If a peer have all pieces it might send the 'HaveAll' message |
@@ -329,26 +325,58 @@ instance Pretty FastMessage where | |||
329 | pretty (RejectRequest bix) = "Reject" <+> pretty bix | 325 | pretty (RejectRequest bix) = "Reject" <+> pretty bix |
330 | pretty (AllowedFast pix) = "Allowed fast" <+> int pix | 326 | pretty (AllowedFast pix) = "Allowed fast" <+> int pix |
331 | 327 | ||
328 | instance PeerMessage FastMessage where | ||
329 | envelop _ = Fast | ||
330 | |||
332 | {----------------------------------------------------------------------- | 331 | {----------------------------------------------------------------------- |
333 | -- Extended messages | 332 | -- Extended messages |
334 | -----------------------------------------------------------------------} | 333 | -----------------------------------------------------------------------} |
335 | 334 | ||
336 | type ExtendedExtension = () | ||
337 | |||
338 | type ExtendedMessageId = Word8 | 335 | type ExtendedMessageId = Word8 |
339 | |||
340 | type ExtendedIdMap = IntMap | 336 | type ExtendedIdMap = IntMap |
341 | 337 | ||
342 | -- | The extension IDs must be stored for every peer, becuase every | 338 | data ExtendedExtension |
339 | = ExtMetadata -- ^ BEP 9 | ||
340 | deriving (Show, Eq, Typeable) | ||
341 | |||
342 | instance Pretty ExtendedExtension where | ||
343 | pretty ExtMetadata = "Extension for Peers to Send Metadata Files" | ||
344 | |||
345 | extId :: ExtendedExtension -> ExtendedMessageId | ||
346 | extId ExtMetadata = 1 | ||
347 | {-# INLINE extId #-} | ||
348 | |||
349 | extString :: ExtendedExtension -> BS.ByteString | ||
350 | extString ExtMetadata = "ut_metadata" | ||
351 | {-# INLINE extString #-} | ||
352 | |||
353 | fromS :: BS.ByteString -> ExtendedExtension | ||
354 | fromS "ut_metadata" = ExtMetadata | ||
355 | |||
356 | -- | The extension IDs must be stored for every peer, because every | ||
343 | -- peer may have different IDs for the same extension. | 357 | -- peer may have different IDs for the same extension. |
344 | -- | 358 | -- |
345 | newtype ExtendedCaps = ExtendedCaps | 359 | newtype ExtendedCaps = ExtendedCaps |
346 | { extendedCaps :: ExtendedIdMap ExtendedExtension | 360 | { extendedCaps :: ExtendedIdMap ExtendedExtension |
347 | } deriving (Show, Eq) | 361 | } deriving (Show, Eq, Monoid) |
362 | |||
363 | -- | Empty set. | ||
364 | instance Default ExtendedCaps where | ||
365 | def = ExtendedCaps IM.empty | ||
366 | |||
367 | instance Pretty ExtendedCaps where | ||
368 | pretty = ppBEncode . toBEncode | ||
348 | 369 | ||
349 | instance BEncode ExtendedCaps where | 370 | instance BEncode ExtendedCaps where |
350 | fromBEncode = undefined | 371 | toBEncode = BDict . BE.fromAscList . L.sortBy (comparing fst) |
351 | toBEncode = undefined | 372 | . L.map mkPair . IM.toList . extendedCaps |
373 | where | ||
374 | mkPair (eid, ex) = (extString ex, toBEncode eid) | ||
375 | |||
376 | fromBEncode (BDict bd) = ExtendedCaps <$> undefined | ||
377 | |||
378 | fromBEncode _ = decodingError "ExtendedCaps" | ||
379 | |||
352 | 380 | ||
353 | -- | This message should be sent immediately after the standard | 381 | -- | This message should be sent immediately after the standard |
354 | -- bittorrent handshake to any peer that supports this extension | 382 | -- bittorrent handshake to any peer that supports this extension |
@@ -385,10 +413,13 @@ data ExtendedHandshake = ExtendedHandshake | |||
385 | -- , yourip :: Maybe (Either HostAddress HostAddress6) | 413 | -- , yourip :: Maybe (Either HostAddress HostAddress6) |
386 | } deriving (Show, Eq, Typeable) | 414 | } deriving (Show, Eq, Typeable) |
387 | 415 | ||
416 | instance Default ExtendedHandshake where | ||
417 | def = ExtendedHandshake Nothing Nothing def Nothing Nothing Nothing | ||
418 | |||
388 | instance BEncode ExtendedHandshake where | 419 | instance BEncode ExtendedHandshake where |
389 | toBEncode ExtendedHandshake {..} = toDict $ | 420 | toBEncode ExtendedHandshake {..} = toDict $ |
390 | "ipv4" .=? ehsIPv4 | 421 | "ipv4" .=? ehsIPv4 -- FIXME invalid encoding |
391 | .: "ipv6" .=? ehsIPv6 | 422 | .: "ipv6" .=? ehsIPv6 -- FIXME invalid encoding |
392 | .: "m" .=! ehsCaps | 423 | .: "m" .=! ehsCaps |
393 | .: "p" .=? ehsPort | 424 | .: "p" .=? ehsPort |
394 | .: "reqq" .=? ehsQueueLength | 425 | .: "reqq" .=? ehsQueueLength |
@@ -408,16 +439,67 @@ instance BEncode ExtendedHandshake where | |||
408 | instance Pretty ExtendedHandshake where | 439 | instance Pretty ExtendedHandshake where |
409 | pretty = PP.text . show | 440 | pretty = PP.text . show |
410 | 441 | ||
442 | instance PeerMessage ExtendedHandshake where | ||
443 | envelop c = envelop c . EHandshake | ||
444 | |||
445 | {----------------------------------------------------------------------- | ||
446 | -- Metadata exchange | ||
447 | -----------------------------------------------------------------------} | ||
448 | |||
449 | type MetadataId = Int | ||
450 | |||
451 | pieceSize :: Int | ||
452 | pieceSize = 16 * 1024 | ||
453 | |||
454 | data ExtendedMetadata | ||
455 | = MetadataRequest PieceIx | ||
456 | | MetadataData PieceIx Int | ||
457 | | MetadataReject PieceIx | ||
458 | | MetadataUnknown BValue | ||
459 | deriving (Show, Eq, Typeable) | ||
460 | |||
461 | instance BEncode ExtendedMetadata where | ||
462 | toBEncode (MetadataRequest pix) = toDict $ | ||
463 | "msg_type" .=! (0 :: MetadataId) | ||
464 | .: "piece" .=! pix | ||
465 | .: endDict | ||
466 | toBEncode (MetadataData pix totalSize) = toDict $ | ||
467 | "msg_type" .=! (1 :: MetadataId) | ||
468 | .: "piece" .=! pix | ||
469 | .: "total_size" .=! totalSize | ||
470 | .: endDict | ||
471 | toBEncode (MetadataReject pix) = toDict $ | ||
472 | "msg_type" .=! (2 :: MetadataId) | ||
473 | .: "piece" .=! pix | ||
474 | .: endDict | ||
475 | toBEncode (MetadataUnknown bval) = bval | ||
476 | |||
477 | fromBEncode = undefined | ||
478 | |||
479 | instance Pretty ExtendedMetadata where | ||
480 | pretty (MetadataRequest pix ) = "Request" <+> PP.int pix | ||
481 | pretty (MetadataData pix s) = "Data" <+> PP.int pix <+> PP.int s | ||
482 | pretty (MetadataReject pix ) = "Reject" <+> PP.int pix | ||
483 | pretty (MetadataUnknown bval ) = ppBEncode bval | ||
484 | |||
485 | instance PeerMessage ExtendedMetadata where | ||
486 | envelop c = envelop c . EMetadata | ||
487 | |||
411 | -- | For more info see <http://www.bittorrent.org/beps/bep_0010.html> | 488 | -- | For more info see <http://www.bittorrent.org/beps/bep_0010.html> |
412 | data ExtendedMessage | 489 | data ExtendedMessage |
413 | = EHandshake ExtendedHandshake | 490 | = EHandshake ExtendedHandshake |
491 | | EMetadata ExtendedMetadata | ||
414 | | EUnknown ExtendedMessageId BS.ByteString | 492 | | EUnknown ExtendedMessageId BS.ByteString |
415 | deriving (Show, Eq) | 493 | deriving (Show, Eq, Typeable) |
416 | 494 | ||
417 | instance Pretty ExtendedMessage where | 495 | instance Pretty ExtendedMessage where |
418 | pretty (EHandshake ehs) = pretty ehs | 496 | pretty (EHandshake ehs) = pretty ehs |
497 | pretty (EMetadata msg) = pretty msg | ||
419 | pretty (EUnknown mid _) = "Unknown" <+> PP.text (show mid) | 498 | pretty (EUnknown mid _) = "Unknown" <+> PP.text (show mid) |
420 | 499 | ||
500 | instance PeerMessage ExtendedMessage where | ||
501 | envelop _ = Extended | ||
502 | |||
421 | {----------------------------------------------------------------------- | 503 | {----------------------------------------------------------------------- |
422 | -- The message datatype | 504 | -- The message datatype |
423 | -----------------------------------------------------------------------} | 505 | -----------------------------------------------------------------------} |
@@ -455,6 +537,12 @@ instance Pretty Message where | |||
455 | pretty (Fast m) = pretty m | 537 | pretty (Fast m) = pretty m |
456 | pretty (Extended m) = pretty m | 538 | pretty (Extended m) = pretty m |
457 | 539 | ||
540 | instance PeerMessage Message where | ||
541 | envelop _ = id | ||
542 | |||
543 | instance PeerMessage PortNumber where | ||
544 | envelop _ = Port | ||
545 | |||
458 | -- | Can be used to check if this message is allowed to send\/recv in | 546 | -- | Can be used to check if this message is allowed to send\/recv in |
459 | -- current session. | 547 | -- current session. |
460 | requires :: Message -> Maybe Extension | 548 | requires :: Message -> Maybe Extension |
@@ -555,6 +643,7 @@ getExtendedMessage messageSize = do | |||
555 | let msgBodySize = messageSize - 1 | 643 | let msgBodySize = messageSize - 1 |
556 | case msgId of | 644 | case msgId of |
557 | 0 -> EHandshake <$> getExtendedHandshake msgBodySize | 645 | 0 -> EHandshake <$> getExtendedHandshake msgBodySize |
646 | 1 -> EMetadata <$> undefined | ||
558 | _ -> EUnknown msgId <$> getByteString msgBodySize | 647 | _ -> EUnknown msgId <$> getByteString msgBodySize |
559 | 648 | ||
560 | extendedMessageId :: MessageId | 649 | extendedMessageId :: MessageId |
@@ -565,7 +654,9 @@ extendedMessageId = 20 | |||
565 | putExtendedMessage :: ExtendedMessage -> S.Put | 654 | putExtendedMessage :: ExtendedMessage -> S.Put |
566 | putExtendedMessage (EHandshake hs) = do | 655 | putExtendedMessage (EHandshake hs) = do |
567 | putExtendedMessage $ EUnknown 0 $ BL.toStrict $ BE.encode hs | 656 | putExtendedMessage $ EUnknown 0 $ BL.toStrict $ BE.encode hs |
568 | 657 | putExtendedMessage (EMetadata msg) = do | |
658 | putExtendedMessage $ EUnknown (extId ExtMetadata) | ||
659 | $ BL.toStrict $ BE.encode msg | ||
569 | putExtendedMessage (EUnknown mid bs) = do | 660 | putExtendedMessage (EUnknown mid bs) = do |
570 | putWord32be $ fromIntegral (4 + 1 + BS.length bs) | 661 | putWord32be $ fromIntegral (4 + 1 + BS.length bs) |
571 | putWord8 extendedMessageId | 662 | putWord8 extendedMessageId |