summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs148
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
71import Control.Applicative 80import Control.Applicative
81import Control.Arrow ((&&&), (***))
72import Data.BEncode as BE 82import Data.BEncode as BE
73import Data.BEncode.BDict as BE 83import Data.BEncode.BDict as BE
74import Data.BEncode.Internal (ppBEncode) 84import Data.BEncode.Internal (ppBEncode)
85import Data.BEncode.Types (BDict)
75import Data.Bits 86import Data.Bits
76import Data.ByteString as BS 87import Data.ByteString as BS
77import Data.ByteString.Char8 as BC 88import Data.ByteString.Char8 as BC
78import Data.ByteString.Lazy as BL 89import Data.ByteString.Lazy as BL
79import Data.Default 90import Data.Default
80import Data.IntMap as IM
81import Data.List as L 91import Data.List as L
92import Data.Map.Strict as M
93import Data.Maybe
82import Data.Monoid 94import Data.Monoid
83import Data.Ord 95import Data.Ord
84import Data.Serialize as S 96import Data.Serialize as S
97import Data.String
85import Data.Text as T 98import Data.Text as T
86import Data.Typeable 99import Data.Typeable
87import Data.Word 100import Data.Word
@@ -106,7 +119,7 @@ import Network.BitTorrent.Exchange.Block
106data Extension 119data 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'.
245class PeerMessage a where 258class 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
397type ExtendedMessageId = Word8 412{-----------------------------------------------------------------------
398type ExtendedIdMap = IntMap 413-- Extended capabilities
414-----------------------------------------------------------------------}
399 415
400data ExtendedExtension 416data 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
420instance IsString ExtendedExtension where
421 fromString = fromMaybe (error msg) . fromKey . fromString
422 where
423 msg = "fromString: could not parse ExtendedExtension"
403 424
404instance Pretty ExtendedExtension where 425instance 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
428fromKey :: BKey -> Maybe ExtendedExtension
429fromKey "ut_metadata" = Just ExtMetadata
430fromKey _ = Nothing
431{-# INLINE fromKey #-}
432
433toKey :: ExtendedExtension -> BKey
434toKey ExtMetadata = "ut_metadata"
435{-# INLINE toKey #-}
436
437type ExtendedMessageId = Word8
438
407extId :: ExtendedExtension -> ExtendedMessageId 439extId :: ExtendedExtension -> ExtendedMessageId
408extId ExtMetadata = 1 440extId ExtMetadata = 1
409{-# INLINE extId #-} 441{-# INLINE extId #-}
410 442
411extString :: ExtendedExtension -> BS.ByteString 443type ExtendedMap = Map ExtendedExtension ExtendedMessageId
412extString ExtMetadata = "ut_metadata"
413{-# INLINE extString #-}
414
415fromS :: BS.ByteString -> ExtendedExtension
416fromS "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--
421newtype ExtendedCaps = ExtendedCaps 448newtype ExtendedCaps = ExtendedCaps { extendedCaps :: ExtendedMap }
422 { extendedCaps :: ExtendedIdMap ExtendedExtension 449 deriving (Show, Eq)
423 } deriving (Show, Eq, Monoid)
424
425-- | Empty set.
426instance Default ExtendedCaps where
427 def = ExtendedCaps IM.empty
428 450
429instance Pretty ExtendedCaps where 451instance Pretty ExtendedCaps where
430 pretty = ppBEncode . toBEncode 452 pretty = hcat . punctuate ", " . L.map pretty . fromExtCaps
453
454-- | The empty set.
455instance 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--
465instance Monoid ExtendedCaps where
466 mempty = toExtCaps [minBound..maxBound]
467 mappend (ExtendedCaps a) (ExtendedCaps b) =
468 ExtendedCaps (M.intersection a b)
469
470appendBDict :: BDict -> ExtendedMap -> ExtendedMap
471appendBDict (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
475appendBDict Nil caps = caps
476
477-- | Handshake compatible encoding.
432instance BEncode ExtendedCaps where 478instance 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
485toExtCaps :: [ExtendedExtension] -> ExtendedCaps
486toExtCaps = ExtendedCaps . M.fromList . L.map (id &&& extId)
487
488fromExtCaps :: ExtendedCaps -> [ExtendedExtension]
489fromExtCaps = M.keys . extendedCaps
490{-# INLINE fromExtCaps #-}
491
492extendedAllowed :: ExtendedExtension -> ExtendedCaps -> Bool
493extendedAllowed 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
535extendedHandshakeId :: ExtendedMessageId
536extendedHandshakeId = 0
537
478instance Default ExtendedHandshake where 538instance 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
519type MetadataId = Int 579type 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
615remoteMessageId :: ExtendedExtension -> ExtendedCaps -> ExtendedMessageId
616remoteMessageId ext = fromMaybe (extId ext) . M.lookup ext . extendedCaps
617
555instance PeerMessage ExtendedMetadata where 618instance 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>
563data ExtendedMessage 626data 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
569instance Pretty ExtendedMessage where 632instance 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
574instance PeerMessage ExtendedMessage where 637instance 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
762extendedMessageId :: MessageId 825extendedMessageId :: MessageId
763extendedMessageId = 20 826extendedMessageId = 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!
767putExtendedMessage :: ExtendedMessage -> S.Put 830putExtendedMessage :: Putter ExtendedMessage
768putExtendedMessage (EHandshake hs) = do 831putExtendedMessage (EHandshake hs) = do
769 putExtendedMessage $ EUnknown 0 $ BL.toStrict $ BE.encode hs 832 putExtendedMessage $ EUnknown extendedHandshakeId
770putExtendedMessage (EMetadata msg) = do 833 $ BL.toStrict $ BE.encode hs
771 putExtendedMessage $ EUnknown (extId ExtMetadata) 834putExtendedMessage (EMetadata mid msg) = do
835 putExtendedMessage $ EUnknown mid
772 $ BL.toStrict $ BE.encode msg 836 $ BL.toStrict $ BE.encode msg
773putExtendedMessage (EUnknown mid bs) = do 837putExtendedMessage (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