summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs193
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 #-}
31module Network.BitTorrent.Exchange.Message 33module 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
66import Control.Applicative 68import Control.Applicative
67import Control.Exception
68import Control.Monad
69import Data.BEncode as BE 69import Data.BEncode as BE
70import Data.BEncode.BDict as BE
71import Data.BEncode.Internal (ppBEncode)
70import Data.Bits 72import Data.Bits
71import Data.ByteString as BS 73import Data.ByteString as BS
72import Data.ByteString.Char8 as BC 74import Data.ByteString.Char8 as BC
@@ -75,9 +77,11 @@ import Data.Default
75import Data.IntMap as IM 77import Data.IntMap as IM
76import Data.List as L 78import Data.List as L
77import Data.Monoid 79import Data.Monoid
80import Data.Ord
78import Data.Serialize as S 81import Data.Serialize as S
79import Data.Text as T 82import Data.Text as T
80import Data.Typeable 83import Data.Typeable
84import Data.Tuple
81import Data.Word 85import Data.Word
82import Network 86import Network
83import Network.Socket hiding (KeepAlive) 87import Network.Socket hiding (KeepAlive)
@@ -219,36 +223,13 @@ defaultBTProtocol = "BitTorrent protocol"
219defaultHandshake :: InfoHash -> PeerId -> Handshake 223defaultHandshake :: InfoHash -> PeerId -> Handshake
220defaultHandshake = Handshake defaultBTProtocol def 224defaultHandshake = Handshake defaultBTProtocol def
221 225
222-- | TODO remove socket stuff to corresponding module
223sendHandshake :: Socket -> Handshake -> IO ()
224sendHandshake sock hs = sendAll sock (S.encode hs)
225
226recvHandshake :: Socket -> IO Handshake
227recvHandshake 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.
240handshake :: Socket -> Handshake -> IO Handshake
241handshake 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
230class PeerMessage a where
231 envelop :: ExtendedCaps -> a -> Message
232
252data StatusUpdate 233data StatusUpdate
253 = Choke 234 = Choke
254 | Unchoke 235 | Unchoke
@@ -259,6 +240,9 @@ data StatusUpdate
259instance Pretty StatusUpdate where 240instance Pretty StatusUpdate where
260 pretty = text . show 241 pretty = text . show
261 242
243instance PeerMessage StatusUpdate where
244 envelop _ = Status
245
262data RegularMessage = 246data 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
285instance PeerMessage RegularMessage where
286 envelop _ = Regular
287
288instance PeerMessage Bitfield where
289 envelop c = envelop c . Bitfield
290
291instance PeerMessage BlockIx where
292 envelop c = envelop c . Request
293
294instance PeerMessage (Block BL.ByteString) where
295 envelop c = envelop c . Piece
296
301-- | BEP6 messages. 297-- | BEP6 messages.
302data FastMessage = 298data 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
328instance PeerMessage FastMessage where
329 envelop _ = Fast
330
332{----------------------------------------------------------------------- 331{-----------------------------------------------------------------------
333-- Extended messages 332-- Extended messages
334-----------------------------------------------------------------------} 333-----------------------------------------------------------------------}
335 334
336type ExtendedExtension = ()
337
338type ExtendedMessageId = Word8 335type ExtendedMessageId = Word8
339
340type ExtendedIdMap = IntMap 336type ExtendedIdMap = IntMap
341 337
342-- | The extension IDs must be stored for every peer, becuase every 338data ExtendedExtension
339 = ExtMetadata -- ^ BEP 9
340 deriving (Show, Eq, Typeable)
341
342instance Pretty ExtendedExtension where
343 pretty ExtMetadata = "Extension for Peers to Send Metadata Files"
344
345extId :: ExtendedExtension -> ExtendedMessageId
346extId ExtMetadata = 1
347{-# INLINE extId #-}
348
349extString :: ExtendedExtension -> BS.ByteString
350extString ExtMetadata = "ut_metadata"
351{-# INLINE extString #-}
352
353fromS :: BS.ByteString -> ExtendedExtension
354fromS "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--
345newtype ExtendedCaps = ExtendedCaps 359newtype ExtendedCaps = ExtendedCaps
346 { extendedCaps :: ExtendedIdMap ExtendedExtension 360 { extendedCaps :: ExtendedIdMap ExtendedExtension
347 } deriving (Show, Eq) 361 } deriving (Show, Eq, Monoid)
362
363-- | Empty set.
364instance Default ExtendedCaps where
365 def = ExtendedCaps IM.empty
366
367instance Pretty ExtendedCaps where
368 pretty = ppBEncode . toBEncode
348 369
349instance BEncode ExtendedCaps where 370instance 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
416instance Default ExtendedHandshake where
417 def = ExtendedHandshake Nothing Nothing def Nothing Nothing Nothing
418
388instance BEncode ExtendedHandshake where 419instance 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
408instance Pretty ExtendedHandshake where 439instance Pretty ExtendedHandshake where
409 pretty = PP.text . show 440 pretty = PP.text . show
410 441
442instance PeerMessage ExtendedHandshake where
443 envelop c = envelop c . EHandshake
444
445{-----------------------------------------------------------------------
446-- Metadata exchange
447-----------------------------------------------------------------------}
448
449type MetadataId = Int
450
451pieceSize :: Int
452pieceSize = 16 * 1024
453
454data ExtendedMetadata
455 = MetadataRequest PieceIx
456 | MetadataData PieceIx Int
457 | MetadataReject PieceIx
458 | MetadataUnknown BValue
459 deriving (Show, Eq, Typeable)
460
461instance 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
479instance 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
485instance 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>
412data ExtendedMessage 489data 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
417instance Pretty ExtendedMessage where 495instance 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
500instance 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
540instance PeerMessage Message where
541 envelop _ = id
542
543instance 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.
460requires :: Message -> Maybe Extension 548requires :: 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
560extendedMessageId :: MessageId 649extendedMessageId :: MessageId
@@ -565,7 +654,9 @@ extendedMessageId = 20
565putExtendedMessage :: ExtendedMessage -> S.Put 654putExtendedMessage :: ExtendedMessage -> S.Put
566putExtendedMessage (EHandshake hs) = do 655putExtendedMessage (EHandshake hs) = do
567 putExtendedMessage $ EUnknown 0 $ BL.toStrict $ BE.encode hs 656 putExtendedMessage $ EUnknown 0 $ BL.toStrict $ BE.encode hs
568 657putExtendedMessage (EMetadata msg) = do
658 putExtendedMessage $ EUnknown (extId ExtMetadata)
659 $ BL.toStrict $ BE.encode msg
569putExtendedMessage (EUnknown mid bs) = do 660putExtendedMessage (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