summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2013-12-05 23:27:10 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2013-12-05 23:27:10 +0400
commit0362306da8401c8fc4d60fbb537c73afb42a250e (patch)
tree2482e6895733741afa450e4b874a1d8678571d87 /src/Network/BitTorrent
parent0cbf9eb7a02083ecd25954144f62f2ffb07a94d3 (diff)
Document status messages
Diffstat (limited to 'src/Network/BitTorrent')
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs69
-rw-r--r--src/Network/BitTorrent/Exchange/Status.hs102
-rw-r--r--src/Network/BitTorrent/Exchange/Wire.hs5
3 files changed, 132 insertions, 44 deletions
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs
index 8fcf582f..8ef9f3da 100644
--- a/src/Network/BitTorrent/Exchange/Message.hs
+++ b/src/Network/BitTorrent/Exchange/Message.hs
@@ -210,9 +210,12 @@ instance Serialize Handshake where
210 <*> S.get 210 <*> S.get
211 <*> S.get 211 <*> S.get
212 212
213-- | Show handshake protocol string, caps and fingerprint.
213instance Pretty Handshake where 214instance Pretty Handshake where
214 pretty Handshake {..} 215 pretty Handshake {..}
215 = text (BC.unpack hsProtocol) <+> pretty (fingerprint hsPeerId) 216 = text (BC.unpack hsProtocol) $$
217 pretty hsReserved $$
218 pretty (fingerprint hsPeerId)
216 219
217-- | Get handshake message size in bytes from the length of protocol 220-- | Get handshake message size in bytes from the length of protocol
218-- string. 221-- string.
@@ -227,31 +230,49 @@ handshakeMaxSize = handshakeSize maxBound
227defaultBTProtocol :: BS.ByteString 230defaultBTProtocol :: BS.ByteString
228defaultBTProtocol = "BitTorrent protocol" 231defaultBTProtocol = "BitTorrent protocol"
229 232
230-- | Length of info hash and peer id is unchecked, so it /should/ be 233-- | Handshake with default protocol string and reserved bitmask.
231-- equal 20.
232defaultHandshake :: InfoHash -> PeerId -> Handshake 234defaultHandshake :: InfoHash -> PeerId -> Handshake
233defaultHandshake = Handshake defaultBTProtocol def 235defaultHandshake = Handshake defaultBTProtocol def
234 236
235{----------------------------------------------------------------------- 237{-----------------------------------------------------------------------
236 Regular messages 238-- Regular messages
237-----------------------------------------------------------------------} 239-----------------------------------------------------------------------}
238 240
239class PeerMessage a where 241class PeerMessage a where
240 envelop :: ExtendedCaps -> a -> Message 242 -- | Construct a message to be /sent/.
243 envelop :: ExtendedCaps -- ^ The /receiver/ extended capabilities;
244 -> a -- ^ An regular message;
245 -> Message -- ^ Enveloped message to sent.
241 246
247{-----------------------------------------------------------------------
248-- Status messages
249-----------------------------------------------------------------------}
250
251-- | Notification that the sender have updated its
252-- 'Network.BitTorrent.Exchange.Status.PeerStatus'.
242data StatusUpdate 253data StatusUpdate
243 = Choke 254 -- | Notification that the sender will not upload data to the
244 | Unchoke 255 -- receiver until unchoking happen.
245 | Interested 256 = Choking !Bool
246 | NotInterested 257
247 deriving (Show, Eq, Ord, Enum, Bounded) 258 -- | Notification that the sender is interested (or not interested)
259 -- in any of the receiver's data pieces.
260 | Interested !Bool
261 deriving (Show, Eq, Ord, Typeable)
248 262
249instance Pretty StatusUpdate where 263instance Pretty StatusUpdate where
250 pretty = text . show 264 pretty (Choking False) = "not choking"
265 pretty (Choking True ) = "choking"
266 pretty (Interested False) = "not interested"
267 pretty (Interested True ) = "interested"
251 268
252instance PeerMessage StatusUpdate where 269instance PeerMessage StatusUpdate where
253 envelop _ = Status 270 envelop _ = Status
254 271
272{-----------------------------------------------------------------------
273-- Available and transfer messages
274-----------------------------------------------------------------------}
275
255data RegularMessage = 276data RegularMessage =
256 -- | Zero-based index of a piece that has just been successfully 277 -- | Zero-based index of a piece that has just been successfully
257 -- downloaded and verified via the hash. 278 -- downloaded and verified via the hash.
@@ -303,6 +324,10 @@ instance PeerMessage BlockIx where
303instance PeerMessage (Block BL.ByteString) where 324instance PeerMessage (Block BL.ByteString) where
304 envelop c = envelop c . Piece 325 envelop c = envelop c . Piece
305 326
327{-----------------------------------------------------------------------
328-- Fast messages
329-----------------------------------------------------------------------}
330
306-- | BEP6 messages. 331-- | BEP6 messages.
307data FastMessage = 332data FastMessage =
308 -- | If a peer have all pieces it might send the 'HaveAll' message 333 -- | If a peer have all pieces it might send the 'HaveAll' message
@@ -526,12 +551,14 @@ type MessageId = Word8
526-- extension then the client MUST close the connection. 551-- extension then the client MUST close the connection.
527-- 552--
528data Message 553data Message
529 -- core 554 -- | Peers may close the TCP connection if they have not received
555 -- any messages for a given period of time, generally 2
556 -- minutes. Thus, the "keep-alive" message is sent tot keep the
557 -- connection between two peers alive, if no /other/ message has
558 -- been sentin a given period of time.
530 = KeepAlive 559 = KeepAlive
531 | Status !StatusUpdate 560 | Status !StatusUpdate
532 | Regular !RegularMessage 561 | Regular !RegularMessage
533
534 -- extensions
535 | Port !PortNumber 562 | Port !PortNumber
536 | Fast !FastMessage 563 | Fast !FastMessage
537 | Extended !ExtendedMessage 564 | Extended !ExtendedMessage
@@ -581,10 +608,10 @@ instance Serialize Message where
581 else do 608 else do
582 mid <- S.getWord8 609 mid <- S.getWord8
583 case mid of 610 case mid of
584 0x00 -> return $ Status Choke 611 0x00 -> return $ Status (Choking True)
585 0x01 -> return $ Status Unchoke 612 0x01 -> return $ Status (Choking False)
586 0x02 -> return $ Status Interested 613 0x02 -> return $ Status (Interested True)
587 0x03 -> return $ Status NotInterested 614 0x03 -> return $ Status (Interested False)
588 0x04 -> (Regular . Have) <$> getInt 615 0x04 -> (Regular . Have) <$> getInt
589 0x05 -> (Regular . Bitfield . fromBitmap) 616 0x05 -> (Regular . Bitfield . fromBitmap)
590 <$> S.getByteString (pred len) 617 <$> S.getByteString (pred len)
@@ -616,8 +643,12 @@ instance Serialize Message where
616 put (Fast msg) = putFast msg 643 put (Fast msg) = putFast msg
617 put (Extended m ) = putExtendedMessage m 644 put (Extended m ) = putExtendedMessage m
618 645
646statusUpdateId :: StatusUpdate -> MessageId
647statusUpdateId (Choking choking) = fromIntegral (0 + fromEnum choking)
648statusUpdateId (Interested choking) = fromIntegral (2 + fromEnum choking)
649
619putStatus :: Putter StatusUpdate 650putStatus :: Putter StatusUpdate
620putStatus su = putInt 1 >> S.putWord8 (fromIntegral (fromEnum su)) 651putStatus su = putInt 1 >> S.putWord8 (statusUpdateId su)
621 652
622putRegular :: Putter RegularMessage 653putRegular :: Putter RegularMessage
623putRegular (Have i) = putInt 5 >> S.putWord8 0x04 >> putInt i 654putRegular (Have i) = putInt 5 >> S.putWord8 0x04 >> putInt i
diff --git a/src/Network/BitTorrent/Exchange/Status.hs b/src/Network/BitTorrent/Exchange/Status.hs
index ae323e09..42766428 100644
--- a/src/Network/BitTorrent/Exchange/Status.hs
+++ b/src/Network/BitTorrent/Exchange/Status.hs
@@ -1,80 +1,134 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2013
3-- License : BSD3
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- Each P2P connection endpoint should keep track status of both
9-- sides.
10--
1{-# LANGUAGE TemplateHaskell #-} 11{-# LANGUAGE TemplateHaskell #-}
2module Network.BitTorrent.Exchange.Status 12module Network.BitTorrent.Exchange.Status
3 ( -- * Peer status 13 ( -- * Peer status
4 PeerStatus(..) 14 PeerStatus(..)
5 , choking 15 , choking
6 , interested 16 , interested
17
18 -- ** Query
7 , updateStatus 19 , updateStatus
20 , statusUpdates
8 21
9 -- * Session status 22 -- * Session status
10 , SessionStatus(..) 23 , SessionStatus(..)
11 , clientStatus 24 , clientStatus
12 , peerStatus 25 , remoteStatus
13 26
14 -- ** Query 27 -- ** Query
15 , canUpload 28 , canUpload
16 , canDownload 29 , canDownload
17 30
18 -- * Extra 31 -- * Extra
19 , inverseStatus
20 , defaultUnchokeSlots 32 , defaultUnchokeSlots
33 , defaultRechokeInterval
21 ) where 34 ) where
22 35
23import Control.Lens 36import Control.Lens
24import Data.Aeson.TH 37import Data.Aeson.TH
25import Data.List as L
26import Data.Default 38import Data.Default
39import Data.List as L
40import Data.Maybe
41import Data.Monoid
42import Text.PrettyPrint as PP hiding ((<>))
43import Text.PrettyPrint.Class
27 44
28import Network.BitTorrent.Exchange.Message 45import Network.BitTorrent.Exchange.Message
29 46
47{-----------------------------------------------------------------------
48-- Peer status
49-----------------------------------------------------------------------}
30 50
31-- | 51-- | Connections contain two bits of state on either end: choked or
32data PeerStatus = PeerStatus { 52-- not, and interested or not.
53data PeerStatus = PeerStatus
54 { -- | Choking is a notification that no data will be sent until
55 -- unchoking happens.
33 _choking :: !Bool 56 _choking :: !Bool
57
58 -- |
34 , _interested :: !Bool 59 , _interested :: !Bool
35 } deriving (Show, Eq) 60 } deriving (Show, Eq, Ord)
36 61
37$(makeLenses ''PeerStatus) 62$(makeLenses ''PeerStatus)
38$(deriveJSON defaultOptions { fieldLabelModifier = L.tail } ''PeerStatus) 63$(deriveJSON defaultOptions { fieldLabelModifier = L.tail } ''PeerStatus)
39 64
65instance Pretty PeerStatus where
66 pretty PeerStatus {..} =
67 pretty (Choking _choking) <+> "and" <+> pretty (Interested _interested)
68
69-- | Connections start out choked and not interested.
40instance Default PeerStatus where 70instance Default PeerStatus where
41 def = PeerStatus True False 71 def = PeerStatus True False
42 72
73instance Monoid PeerStatus where
74 mempty = def
75 mappend a b = PeerStatus
76 { _choking = _choking a && _choking b
77 , _interested = _interested a || _interested b
78 }
79
80-- | Can be used to update remote peer status using incoming 'Status'
81-- message.
43updateStatus :: StatusUpdate -> PeerStatus -> PeerStatus 82updateStatus :: StatusUpdate -> PeerStatus -> PeerStatus
44updateStatus Choke = choking .~ True 83updateStatus (Choking b) = choking .~ b
45updateStatus Unchoke = choking .~ False 84updateStatus (Interested b) = interested .~ b
46updateStatus Interested = interested .~ True
47updateStatus NotInterested = interested .~ False
48 85
86-- | Can be used to generate outcoming messages.
49statusUpdates :: PeerStatus -> PeerStatus -> [StatusUpdate] 87statusUpdates :: PeerStatus -> PeerStatus -> [StatusUpdate]
50statusUpdates a b = undefined 88statusUpdates a b = catMaybes $
51 89 [ if _choking a == _choking b then Nothing
52-- | 90 else Just $ Choking $ _choking b
53data SessionStatus = SessionStatus { 91 , if _interested a == _interested b then Nothing
54 _clientStatus :: !PeerStatus 92 else Just $ Interested $ _interested b
55 , _peerStatus :: !PeerStatus 93 ]
94
95{-----------------------------------------------------------------------
96-- Session status
97-----------------------------------------------------------------------}
98
99-- | Status of the both endpoints.
100data SessionStatus = SessionStatus
101 { _clientStatus :: !PeerStatus
102 , _remoteStatus :: !PeerStatus
56 } deriving (Show, Eq) 103 } deriving (Show, Eq)
57 104
58$(makeLenses ''SessionStatus) 105$(makeLenses ''SessionStatus)
59$(deriveJSON defaultOptions { fieldLabelModifier = L.tail } ''SessionStatus) 106$(deriveJSON defaultOptions { fieldLabelModifier = L.tail } ''SessionStatus)
60 107
108instance Pretty SessionStatus where
109 pretty SessionStatus {..} =
110 "this " <+> pretty _clientStatus $$
111 "remote" <+> pretty _remoteStatus
112
113-- | Connections start out choked and not interested.
61instance Default SessionStatus where 114instance Default SessionStatus where
62 def = SessionStatus def def 115 def = SessionStatus def def
63 116
64-- | Can the /client/ transfer to the /peer/? 117-- | Can the client transfer to the remote peer?
65canUpload :: SessionStatus -> Bool 118canUpload :: SessionStatus -> Bool
66canUpload SessionStatus {..} 119canUpload SessionStatus {..}
67 = _interested _peerStatus && not (_choking _clientStatus) 120 = _interested _remoteStatus && not (_choking _clientStatus)
68 121
69-- | Can the /client/ transfer from the /peer/? 122-- | Can the client transfer from the remote peer?
70canDownload :: SessionStatus -> Bool 123canDownload :: SessionStatus -> Bool
71canDownload SessionStatus {..} 124canDownload SessionStatus {..}
72 = _interested _clientStatus && not (_choking _peerStatus) 125 = _interested _clientStatus && not (_choking _remoteStatus)
73
74inverseStatus :: SessionStatus -> SessionStatus
75inverseStatus SessionStatus {..} = SessionStatus _peerStatus _clientStatus
76 126
77-- | Indicates how many peers are allowed to download from the client 127-- | Indicates how many peers are allowed to download from the client
78-- by default. 128-- by default.
79defaultUnchokeSlots :: Int 129defaultUnchokeSlots :: Int
80defaultUnchokeSlots = 4 \ No newline at end of file 130defaultUnchokeSlots = 4
131
132-- |
133defaultRechokeInterval :: Int
134defaultRechokeInterval = 10 * 1000 * 1000 \ No newline at end of file
diff --git a/src/Network/BitTorrent/Exchange/Wire.hs b/src/Network/BitTorrent/Exchange/Wire.hs
index 6a161762..680da059 100644
--- a/src/Network/BitTorrent/Exchange/Wire.hs
+++ b/src/Network/BitTorrent/Exchange/Wire.hs
@@ -61,13 +61,16 @@ data ChannelSide
61 | RemotePeer 61 | RemotePeer
62 deriving (Show, Eq, Enum) 62 deriving (Show, Eq, Enum)
63 63
64-- TODO pretty instance
65
64-- | Errors occur when a remote peer violates protocol specification. 66-- | Errors occur when a remote peer violates protocol specification.
65data ProtocolError 67data ProtocolError
66 = UnexpectedTopic InfoHash -- ^ peer replied with unexpected infohash. 68 = UnexpectedTopic InfoHash -- ^ peer replied with unexpected infohash.
67 | UnexpectedPeerId PeerId -- ^ peer replied with unexpected peer id. 69 | UnexpectedPeerId PeerId -- ^ peer replied with unexpected peer id.
68 | UnknownTopic InfoHash -- ^ peer requested unknown torrent. 70 | UnknownTopic InfoHash -- ^ peer requested unknown torrent.
69 | HandshakeRefused -- ^ peer do not send an extended handshake back. 71 | HandshakeRefused -- ^ peer do not send an extended handshake back.
70 | InvalidMessage 72 | BitfieldAlreadSend ChannelSide
73 | InvalidMessage -- TODO caps violation
71 { violentSender :: ChannelSide -- ^ endpoint sent invalid message 74 { violentSender :: ChannelSide -- ^ endpoint sent invalid message
72 , extensionRequired :: Extension -- ^ 75 , extensionRequired :: Extension -- ^
73 } 76 }