diff options
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 69 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Status.hs | 102 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Wire.hs | 5 |
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. | ||
213 | instance Pretty Handshake where | 214 | instance 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 | |||
227 | defaultBTProtocol :: BS.ByteString | 230 | defaultBTProtocol :: BS.ByteString |
228 | defaultBTProtocol = "BitTorrent protocol" | 231 | defaultBTProtocol = "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. | ||
232 | defaultHandshake :: InfoHash -> PeerId -> Handshake | 234 | defaultHandshake :: InfoHash -> PeerId -> Handshake |
233 | defaultHandshake = Handshake defaultBTProtocol def | 235 | defaultHandshake = Handshake defaultBTProtocol def |
234 | 236 | ||
235 | {----------------------------------------------------------------------- | 237 | {----------------------------------------------------------------------- |
236 | Regular messages | 238 | -- Regular messages |
237 | -----------------------------------------------------------------------} | 239 | -----------------------------------------------------------------------} |
238 | 240 | ||
239 | class PeerMessage a where | 241 | class 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'. | ||
242 | data StatusUpdate | 253 | data 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 | ||
249 | instance Pretty StatusUpdate where | 263 | instance 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 | ||
252 | instance PeerMessage StatusUpdate where | 269 | instance PeerMessage StatusUpdate where |
253 | envelop _ = Status | 270 | envelop _ = Status |
254 | 271 | ||
272 | {----------------------------------------------------------------------- | ||
273 | -- Available and transfer messages | ||
274 | -----------------------------------------------------------------------} | ||
275 | |||
255 | data RegularMessage = | 276 | data 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 | |||
303 | instance PeerMessage (Block BL.ByteString) where | 324 | instance 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. |
307 | data FastMessage = | 332 | data 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 | -- |
528 | data Message | 553 | data 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 | ||
646 | statusUpdateId :: StatusUpdate -> MessageId | ||
647 | statusUpdateId (Choking choking) = fromIntegral (0 + fromEnum choking) | ||
648 | statusUpdateId (Interested choking) = fromIntegral (2 + fromEnum choking) | ||
649 | |||
619 | putStatus :: Putter StatusUpdate | 650 | putStatus :: Putter StatusUpdate |
620 | putStatus su = putInt 1 >> S.putWord8 (fromIntegral (fromEnum su)) | 651 | putStatus su = putInt 1 >> S.putWord8 (statusUpdateId su) |
621 | 652 | ||
622 | putRegular :: Putter RegularMessage | 653 | putRegular :: Putter RegularMessage |
623 | putRegular (Have i) = putInt 5 >> S.putWord8 0x04 >> putInt i | 654 | putRegular (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 #-} |
2 | module Network.BitTorrent.Exchange.Status | 12 | module 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 | ||
23 | import Control.Lens | 36 | import Control.Lens |
24 | import Data.Aeson.TH | 37 | import Data.Aeson.TH |
25 | import Data.List as L | ||
26 | import Data.Default | 38 | import Data.Default |
39 | import Data.List as L | ||
40 | import Data.Maybe | ||
41 | import Data.Monoid | ||
42 | import Text.PrettyPrint as PP hiding ((<>)) | ||
43 | import Text.PrettyPrint.Class | ||
27 | 44 | ||
28 | import Network.BitTorrent.Exchange.Message | 45 | import 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 |
32 | data PeerStatus = PeerStatus { | 52 | -- not, and interested or not. |
53 | data 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 | ||
65 | instance Pretty PeerStatus where | ||
66 | pretty PeerStatus {..} = | ||
67 | pretty (Choking _choking) <+> "and" <+> pretty (Interested _interested) | ||
68 | |||
69 | -- | Connections start out choked and not interested. | ||
40 | instance Default PeerStatus where | 70 | instance Default PeerStatus where |
41 | def = PeerStatus True False | 71 | def = PeerStatus True False |
42 | 72 | ||
73 | instance 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. | ||
43 | updateStatus :: StatusUpdate -> PeerStatus -> PeerStatus | 82 | updateStatus :: StatusUpdate -> PeerStatus -> PeerStatus |
44 | updateStatus Choke = choking .~ True | 83 | updateStatus (Choking b) = choking .~ b |
45 | updateStatus Unchoke = choking .~ False | 84 | updateStatus (Interested b) = interested .~ b |
46 | updateStatus Interested = interested .~ True | ||
47 | updateStatus NotInterested = interested .~ False | ||
48 | 85 | ||
86 | -- | Can be used to generate outcoming messages. | ||
49 | statusUpdates :: PeerStatus -> PeerStatus -> [StatusUpdate] | 87 | statusUpdates :: PeerStatus -> PeerStatus -> [StatusUpdate] |
50 | statusUpdates a b = undefined | 88 | statusUpdates a b = catMaybes $ |
51 | 89 | [ if _choking a == _choking b then Nothing | |
52 | -- | | 90 | else Just $ Choking $ _choking b |
53 | data 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. | ||
100 | data 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 | ||
108 | instance Pretty SessionStatus where | ||
109 | pretty SessionStatus {..} = | ||
110 | "this " <+> pretty _clientStatus $$ | ||
111 | "remote" <+> pretty _remoteStatus | ||
112 | |||
113 | -- | Connections start out choked and not interested. | ||
61 | instance Default SessionStatus where | 114 | instance 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? |
65 | canUpload :: SessionStatus -> Bool | 118 | canUpload :: SessionStatus -> Bool |
66 | canUpload SessionStatus {..} | 119 | canUpload 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? |
70 | canDownload :: SessionStatus -> Bool | 123 | canDownload :: SessionStatus -> Bool |
71 | canDownload SessionStatus {..} | 124 | canDownload SessionStatus {..} |
72 | = _interested _clientStatus && not (_choking _peerStatus) | 125 | = _interested _clientStatus && not (_choking _remoteStatus) |
73 | |||
74 | inverseStatus :: SessionStatus -> SessionStatus | ||
75 | inverseStatus 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. |
79 | defaultUnchokeSlots :: Int | 129 | defaultUnchokeSlots :: Int |
80 | defaultUnchokeSlots = 4 \ No newline at end of file | 130 | defaultUnchokeSlots = 4 |
131 | |||
132 | -- | | ||
133 | defaultRechokeInterval :: Int | ||
134 | defaultRechokeInterval = 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. |
65 | data ProtocolError | 67 | data 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 | } |