diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2013-12-06 03:59:51 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2013-12-06 03:59:51 +0400 |
commit | be414f0ef8d2bd5078177b7334045b3b7eedc482 (patch) | |
tree | 0ae5479b8cb0923775288d530e8078e39ddde42b /src/Network/BitTorrent | |
parent | 56cf0e26eaa3ef528431b07b35558987cd447cf5 (diff) |
Implement extended message id convention
Diffstat (limited to 'src/Network/BitTorrent')
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 148 |
1 files changed, 106 insertions, 42 deletions
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs index 65b05737..33937a93 100644 --- a/src/Network/BitTorrent/Exchange/Message.hs +++ b/src/Network/BitTorrent/Exchange/Message.hs | |||
@@ -61,27 +61,40 @@ module Network.BitTorrent.Exchange.Message | |||
61 | 61 | ||
62 | -- ** Extension protocol | 62 | -- ** Extension protocol |
63 | , ExtendedMessage (..) | 63 | , ExtendedMessage (..) |
64 | , ExtendedExtension | 64 | |
65 | -- *** Capabilities | ||
66 | , ExtendedExtension (..) | ||
65 | , ExtendedCaps (..) | 67 | , ExtendedCaps (..) |
68 | , toExtCaps | ||
69 | , fromExtCaps | ||
70 | , extendedAllowed | ||
71 | |||
72 | -- *** Handshake | ||
66 | , ExtendedHandshake (..) | 73 | , ExtendedHandshake (..) |
67 | , nullExtendedHandshake | 74 | , nullExtendedHandshake |
75 | |||
76 | -- *** Metadata | ||
68 | , ExtendedMetadata (..) | 77 | , ExtendedMetadata (..) |
69 | ) where | 78 | ) where |
70 | 79 | ||
71 | import Control.Applicative | 80 | import Control.Applicative |
81 | import Control.Arrow ((&&&), (***)) | ||
72 | import Data.BEncode as BE | 82 | import Data.BEncode as BE |
73 | import Data.BEncode.BDict as BE | 83 | import Data.BEncode.BDict as BE |
74 | import Data.BEncode.Internal (ppBEncode) | 84 | import Data.BEncode.Internal (ppBEncode) |
85 | import Data.BEncode.Types (BDict) | ||
75 | import Data.Bits | 86 | import Data.Bits |
76 | import Data.ByteString as BS | 87 | import Data.ByteString as BS |
77 | import Data.ByteString.Char8 as BC | 88 | import Data.ByteString.Char8 as BC |
78 | import Data.ByteString.Lazy as BL | 89 | import Data.ByteString.Lazy as BL |
79 | import Data.Default | 90 | import Data.Default |
80 | import Data.IntMap as IM | ||
81 | import Data.List as L | 91 | import Data.List as L |
92 | import Data.Map.Strict as M | ||
93 | import Data.Maybe | ||
82 | import Data.Monoid | 94 | import Data.Monoid |
83 | import Data.Ord | 95 | import Data.Ord |
84 | import Data.Serialize as S | 96 | import Data.Serialize as S |
97 | import Data.String | ||
85 | import Data.Text as T | 98 | import Data.Text as T |
86 | import Data.Typeable | 99 | import Data.Typeable |
87 | import Data.Word | 100 | import Data.Word |
@@ -106,7 +119,7 @@ import Network.BitTorrent.Exchange.Block | |||
106 | data Extension | 119 | data Extension |
107 | = ExtDHT -- ^ BEP 5: allow to send PORT messages. | 120 | = ExtDHT -- ^ BEP 5: allow to send PORT messages. |
108 | | ExtFast -- ^ BEP 6: allow to send FAST messages. | 121 | | ExtFast -- ^ BEP 6: allow to send FAST messages. |
109 | | ExtExtended -- ^ BEP 10: allow to send extension protocol messages. | 122 | | ExtExtended -- ^ BEP 10: allow to send the extension protocol messages. |
110 | deriving (Show, Eq, Ord, Enum, Bounded) | 123 | deriving (Show, Eq, Ord, Enum, Bounded) |
111 | 124 | ||
112 | -- | Full extension names, suitable for logging. | 125 | -- | Full extension names, suitable for logging. |
@@ -243,7 +256,9 @@ defaultHandshake = Handshake defaultBTProtocol def | |||
243 | -- | Messages which can be sent after handshaking. Minimal complete | 256 | -- | Messages which can be sent after handshaking. Minimal complete |
244 | -- definition: 'envelop'. | 257 | -- definition: 'envelop'. |
245 | class PeerMessage a where | 258 | class PeerMessage a where |
246 | -- | Construct a message to be /sent/. | 259 | -- | Construct a message to be /sent/. Note that if 'ExtendedCaps' |
260 | -- do not contain mapping for this message the default | ||
261 | -- 'ExtendedMessageId' is used. | ||
247 | envelop :: ExtendedCaps -- ^ The /receiver/ extended capabilities; | 262 | envelop :: ExtendedCaps -- ^ The /receiver/ extended capabilities; |
248 | -> a -- ^ An regular message; | 263 | -> a -- ^ An regular message; |
249 | -> Message -- ^ Enveloped message to sent. | 264 | -> Message -- ^ Enveloped message to sent. |
@@ -391,54 +406,96 @@ instance PeerMessage FastMessage where | |||
391 | {-# INLINE requires #-} | 406 | {-# INLINE requires #-} |
392 | 407 | ||
393 | {----------------------------------------------------------------------- | 408 | {----------------------------------------------------------------------- |
394 | -- Extended messages | 409 | -- Extension protocol |
395 | -----------------------------------------------------------------------} | 410 | -----------------------------------------------------------------------} |
396 | 411 | ||
397 | type ExtendedMessageId = Word8 | 412 | {----------------------------------------------------------------------- |
398 | type ExtendedIdMap = IntMap | 413 | -- Extended capabilities |
414 | -----------------------------------------------------------------------} | ||
399 | 415 | ||
400 | data ExtendedExtension | 416 | data ExtendedExtension |
401 | = ExtMetadata -- ^ BEP 9 | 417 | = ExtMetadata -- ^ BEP 9: Extension for Peers to Send Metadata Files |
402 | deriving (Show, Eq, Typeable) | 418 | deriving (Show, Eq, Ord, Enum, Bounded, Typeable) |
419 | |||
420 | instance IsString ExtendedExtension where | ||
421 | fromString = fromMaybe (error msg) . fromKey . fromString | ||
422 | where | ||
423 | msg = "fromString: could not parse ExtendedExtension" | ||
403 | 424 | ||
404 | instance Pretty ExtendedExtension where | 425 | instance Pretty ExtendedExtension where |
405 | pretty ExtMetadata = "Extension for Peers to Send Metadata Files" | 426 | pretty ExtMetadata = "Extension for Peers to Send Metadata Files" |
406 | 427 | ||
428 | fromKey :: BKey -> Maybe ExtendedExtension | ||
429 | fromKey "ut_metadata" = Just ExtMetadata | ||
430 | fromKey _ = Nothing | ||
431 | {-# INLINE fromKey #-} | ||
432 | |||
433 | toKey :: ExtendedExtension -> BKey | ||
434 | toKey ExtMetadata = "ut_metadata" | ||
435 | {-# INLINE toKey #-} | ||
436 | |||
437 | type ExtendedMessageId = Word8 | ||
438 | |||
407 | extId :: ExtendedExtension -> ExtendedMessageId | 439 | extId :: ExtendedExtension -> ExtendedMessageId |
408 | extId ExtMetadata = 1 | 440 | extId ExtMetadata = 1 |
409 | {-# INLINE extId #-} | 441 | {-# INLINE extId #-} |
410 | 442 | ||
411 | extString :: ExtendedExtension -> BS.ByteString | 443 | type ExtendedMap = Map ExtendedExtension ExtendedMessageId |
412 | extString ExtMetadata = "ut_metadata" | ||
413 | {-# INLINE extString #-} | ||
414 | |||
415 | fromS :: BS.ByteString -> ExtendedExtension | ||
416 | fromS "ut_metadata" = ExtMetadata | ||
417 | 444 | ||
418 | -- | The extension IDs must be stored for every peer, because every | 445 | -- | The extension IDs must be stored for every peer, because every |
419 | -- peer may have different IDs for the same extension. | 446 | -- peer may have different IDs for the same extension. |
420 | -- | 447 | -- |
421 | newtype ExtendedCaps = ExtendedCaps | 448 | newtype ExtendedCaps = ExtendedCaps { extendedCaps :: ExtendedMap } |
422 | { extendedCaps :: ExtendedIdMap ExtendedExtension | 449 | deriving (Show, Eq) |
423 | } deriving (Show, Eq, Monoid) | ||
424 | |||
425 | -- | Empty set. | ||
426 | instance Default ExtendedCaps where | ||
427 | def = ExtendedCaps IM.empty | ||
428 | 450 | ||
429 | instance Pretty ExtendedCaps where | 451 | instance Pretty ExtendedCaps where |
430 | pretty = ppBEncode . toBEncode | 452 | pretty = hcat . punctuate ", " . L.map pretty . fromExtCaps |
453 | |||
454 | -- | The empty set. | ||
455 | instance Default ExtendedCaps where | ||
456 | def = ExtendedCaps M.empty | ||
431 | 457 | ||
458 | -- | Monoid under intersection: | ||
459 | -- | ||
460 | -- * The 'mempty' caps include all known extensions; | ||
461 | -- | ||
462 | -- * the 'mappend' operation is NOT commutative: it return message | ||
463 | -- id from the first caps for the extensions existing in both caps. | ||
464 | -- | ||
465 | instance Monoid ExtendedCaps where | ||
466 | mempty = toExtCaps [minBound..maxBound] | ||
467 | mappend (ExtendedCaps a) (ExtendedCaps b) = | ||
468 | ExtendedCaps (M.intersection a b) | ||
469 | |||
470 | appendBDict :: BDict -> ExtendedMap -> ExtendedMap | ||
471 | appendBDict (Cons key val xs) caps | ||
472 | | Just ext <- fromKey key | ||
473 | , Right eid <- fromBEncode val = M.insert ext eid (appendBDict xs caps) | ||
474 | | otherwise = caps | ||
475 | appendBDict Nil caps = caps | ||
476 | |||
477 | -- | Handshake compatible encoding. | ||
432 | instance BEncode ExtendedCaps where | 478 | instance BEncode ExtendedCaps where |
433 | toBEncode = BDict . BE.fromAscList . L.sortBy (comparing fst) | 479 | toBEncode = BDict . BE.fromAscList . L.sortBy (comparing fst) |
434 | . L.map mkPair . IM.toList . extendedCaps | 480 | . L.map (toKey *** toBEncode) . M.toList . extendedCaps |
435 | where | ||
436 | mkPair (eid, ex) = (extString ex, toBEncode eid) | ||
437 | |||
438 | fromBEncode (BDict bd) = ExtendedCaps <$> undefined | ||
439 | 481 | ||
482 | fromBEncode (BDict bd) = pure $ ExtendedCaps $ appendBDict bd M.empty | ||
440 | fromBEncode _ = decodingError "ExtendedCaps" | 483 | fromBEncode _ = decodingError "ExtendedCaps" |
441 | 484 | ||
485 | toExtCaps :: [ExtendedExtension] -> ExtendedCaps | ||
486 | toExtCaps = ExtendedCaps . M.fromList . L.map (id &&& extId) | ||
487 | |||
488 | fromExtCaps :: ExtendedCaps -> [ExtendedExtension] | ||
489 | fromExtCaps = M.keys . extendedCaps | ||
490 | {-# INLINE fromExtCaps #-} | ||
491 | |||
492 | extendedAllowed :: ExtendedExtension -> ExtendedCaps -> Bool | ||
493 | extendedAllowed e (ExtendedCaps caps) = M.member e caps | ||
494 | {-# INLINE extendedAllowed #-} | ||
495 | |||
496 | {----------------------------------------------------------------------- | ||
497 | -- Extended handshake | ||
498 | -----------------------------------------------------------------------} | ||
442 | 499 | ||
443 | -- | This message should be sent immediately after the standard | 500 | -- | This message should be sent immediately after the standard |
444 | -- bittorrent handshake to any peer that supports this extension | 501 | -- bittorrent handshake to any peer that supports this extension |
@@ -475,6 +532,9 @@ data ExtendedHandshake = ExtendedHandshake | |||
475 | -- , yourip :: Maybe (Either HostAddress HostAddress6) | 532 | -- , yourip :: Maybe (Either HostAddress HostAddress6) |
476 | } deriving (Show, Eq, Typeable) | 533 | } deriving (Show, Eq, Typeable) |
477 | 534 | ||
535 | extendedHandshakeId :: ExtendedMessageId | ||
536 | extendedHandshakeId = 0 | ||
537 | |||
478 | instance Default ExtendedHandshake where | 538 | instance Default ExtendedHandshake where |
479 | def = nullExtendedHandshake def | 539 | def = nullExtendedHandshake def |
480 | 540 | ||
@@ -513,7 +573,7 @@ nullExtendedHandshake caps | |||
513 | = ExtendedHandshake Nothing Nothing caps Nothing Nothing Nothing | 573 | = ExtendedHandshake Nothing Nothing caps Nothing Nothing Nothing |
514 | 574 | ||
515 | {----------------------------------------------------------------------- | 575 | {----------------------------------------------------------------------- |
516 | -- Metadata exchange | 576 | -- Metadata exchange extension |
517 | -----------------------------------------------------------------------} | 577 | -----------------------------------------------------------------------} |
518 | 578 | ||
519 | type MetadataId = Int | 579 | type MetadataId = Int |
@@ -552,8 +612,11 @@ instance Pretty ExtendedMetadata where | |||
552 | pretty (MetadataReject pix ) = "Reject" <+> PP.int pix | 612 | pretty (MetadataReject pix ) = "Reject" <+> PP.int pix |
553 | pretty (MetadataUnknown bval ) = ppBEncode bval | 613 | pretty (MetadataUnknown bval ) = ppBEncode bval |
554 | 614 | ||
615 | remoteMessageId :: ExtendedExtension -> ExtendedCaps -> ExtendedMessageId | ||
616 | remoteMessageId ext = fromMaybe (extId ext) . M.lookup ext . extendedCaps | ||
617 | |||
555 | instance PeerMessage ExtendedMetadata where | 618 | instance PeerMessage ExtendedMetadata where |
556 | envelop c = envelop c . EMetadata | 619 | envelop c = envelop c . EMetadata (remoteMessageId ExtMetadata c) |
557 | {-# INLINE envelop #-} | 620 | {-# INLINE envelop #-} |
558 | 621 | ||
559 | requires _ = Just ExtExtended | 622 | requires _ = Just ExtExtended |
@@ -562,14 +625,14 @@ instance PeerMessage ExtendedMetadata where | |||
562 | -- | For more info see <http://www.bittorrent.org/beps/bep_0010.html> | 625 | -- | For more info see <http://www.bittorrent.org/beps/bep_0010.html> |
563 | data ExtendedMessage | 626 | data ExtendedMessage |
564 | = EHandshake ExtendedHandshake | 627 | = EHandshake ExtendedHandshake |
565 | | EMetadata ExtendedMetadata | 628 | | EMetadata ExtendedMessageId ExtendedMetadata |
566 | | EUnknown ExtendedMessageId BS.ByteString | 629 | | EUnknown ExtendedMessageId BS.ByteString |
567 | deriving (Show, Eq, Typeable) | 630 | deriving (Show, Eq, Typeable) |
568 | 631 | ||
569 | instance Pretty ExtendedMessage where | 632 | instance Pretty ExtendedMessage where |
570 | pretty (EHandshake ehs) = pretty ehs | 633 | pretty (EHandshake ehs) = pretty ehs |
571 | pretty (EMetadata msg) = pretty msg | 634 | pretty (EMetadata _ msg) = pretty msg |
572 | pretty (EUnknown mid _) = "Unknown" <+> PP.text (show mid) | 635 | pretty (EUnknown mid _ ) = "Unknown" <+> PP.text (show mid) |
573 | 636 | ||
574 | instance PeerMessage ExtendedMessage where | 637 | instance PeerMessage ExtendedMessage where |
575 | envelop _ = Extended | 638 | envelop _ = Extended |
@@ -755,23 +818,24 @@ getExtendedMessage messageSize = do | |||
755 | msgId <- getWord8 | 818 | msgId <- getWord8 |
756 | let msgBodySize = messageSize - 1 | 819 | let msgBodySize = messageSize - 1 |
757 | case msgId of | 820 | case msgId of |
758 | 0 -> EHandshake <$> getExtendedHandshake msgBodySize | 821 | 0 -> EHandshake <$> getExtendedHandshake msgBodySize |
759 | 1 -> EMetadata <$> undefined | 822 | 1 -> EMetadata msgId <$> undefined |
760 | _ -> EUnknown msgId <$> getByteString msgBodySize | 823 | _ -> EUnknown msgId <$> getByteString msgBodySize |
761 | 824 | ||
762 | extendedMessageId :: MessageId | 825 | extendedMessageId :: MessageId |
763 | extendedMessageId = 20 | 826 | extendedMessageId = 20 |
764 | 827 | ||
765 | -- NOTE: in contrast to getExtendedMessage this function put length | 828 | -- NOTE: in contrast to getExtendedMessage this function put length |
766 | -- and message id too! | 829 | -- and message id too! |
767 | putExtendedMessage :: ExtendedMessage -> S.Put | 830 | putExtendedMessage :: Putter ExtendedMessage |
768 | putExtendedMessage (EHandshake hs) = do | 831 | putExtendedMessage (EHandshake hs) = do |
769 | putExtendedMessage $ EUnknown 0 $ BL.toStrict $ BE.encode hs | 832 | putExtendedMessage $ EUnknown extendedHandshakeId |
770 | putExtendedMessage (EMetadata msg) = do | 833 | $ BL.toStrict $ BE.encode hs |
771 | putExtendedMessage $ EUnknown (extId ExtMetadata) | 834 | putExtendedMessage (EMetadata mid msg) = do |
835 | putExtendedMessage $ EUnknown mid | ||
772 | $ BL.toStrict $ BE.encode msg | 836 | $ BL.toStrict $ BE.encode msg |
773 | putExtendedMessage (EUnknown mid bs) = do | 837 | putExtendedMessage (EUnknown mid bs) = do |
774 | putWord32be $ fromIntegral (4 + 1 + BS.length bs) | 838 | putWord32be $ fromIntegral (1 + 1 + BS.length bs) |
775 | putWord8 extendedMessageId | 839 | putWord8 extendedMessageId |
776 | putWord8 mid | 840 | putWord8 mid |
777 | putByteString bs | 841 | putByteString bs |