diff options
Diffstat (limited to 'src/Network/BitTorrent/Exchange')
-rw-r--r-- | src/Network/BitTorrent/Exchange/Message.hs | 124 | ||||
-rw-r--r-- | src/Network/BitTorrent/Exchange/Wire.hs | 61 |
2 files changed, 132 insertions, 53 deletions
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs index 38a8ac33..b3100269 100644 --- a/src/Network/BitTorrent/Exchange/Message.hs +++ b/src/Network/BitTorrent/Exchange/Message.hs | |||
@@ -46,10 +46,15 @@ module Network.BitTorrent.Exchange.Message | |||
46 | , handshakeSize | 46 | , handshakeSize |
47 | , handshakeMaxSize | 47 | , handshakeMaxSize |
48 | 48 | ||
49 | -- * Stats | ||
50 | , ByteCount | ||
51 | , ByteStats (..) | ||
52 | , byteLength | ||
53 | |||
49 | -- * Messages | 54 | -- * Messages |
50 | , Message (..) | 55 | , Message (..) |
51 | , PeerMessage (..) | ||
52 | , defaultKeepAliveInterval | 56 | , defaultKeepAliveInterval |
57 | , PeerMessage (..) | ||
53 | 58 | ||
54 | -- ** Core messages | 59 | -- ** Core messages |
55 | , StatusUpdate (..) | 60 | , StatusUpdate (..) |
@@ -292,6 +297,47 @@ defaultHandshake :: InfoHash -> PeerId -> Handshake | |||
292 | defaultHandshake = Handshake def def | 297 | defaultHandshake = Handshake def def |
293 | 298 | ||
294 | {----------------------------------------------------------------------- | 299 | {----------------------------------------------------------------------- |
300 | -- Stats | ||
301 | -----------------------------------------------------------------------} | ||
302 | |||
303 | -- | Number of bytes. | ||
304 | type ByteCount = Int | ||
305 | |||
306 | -- | Summary of encoded message byte layout can be used to collect | ||
307 | -- stats about message flow in both directions. This data can be | ||
308 | -- retrieved using 'stats' function. | ||
309 | data ByteStats = ByteStats | ||
310 | { -- | Number of bytes used to help encode 'control' and 'payload' | ||
311 | -- bytes: message size, message ID's, etc | ||
312 | overhead :: {-# UNPACK #-} !ByteCount | ||
313 | |||
314 | -- | Number of bytes used to exchange peers state\/options: piece | ||
315 | -- and block indexes, infohash, port numbers, peer ID\/IP, etc. | ||
316 | , control :: {-# UNPACK #-} !ByteCount | ||
317 | |||
318 | -- | Number of payload bytes: torrent data blocks and infodict | ||
319 | -- metadata. | ||
320 | , payload :: {-# UNPACK #-} !ByteCount | ||
321 | } deriving Show | ||
322 | |||
323 | -- | Empty byte sequences. | ||
324 | instance Default ByteStats where | ||
325 | def = ByteStats 0 0 0 | ||
326 | |||
327 | -- | Monoid under addition. | ||
328 | instance Monoid ByteStats where | ||
329 | mempty = def | ||
330 | mappend a b = ByteStats | ||
331 | { overhead = overhead a + overhead b | ||
332 | , control = control a + control b | ||
333 | , payload = payload a + payload b | ||
334 | } | ||
335 | |||
336 | -- | Sum of the all byte sequences. | ||
337 | byteLength :: ByteStats -> Int | ||
338 | byteLength ByteStats {..} = overhead + control + payload | ||
339 | |||
340 | {----------------------------------------------------------------------- | ||
295 | -- Regular messages | 341 | -- Regular messages |
296 | -----------------------------------------------------------------------} | 342 | -----------------------------------------------------------------------} |
297 | 343 | ||
@@ -311,6 +357,17 @@ class PeerMessage a where | |||
311 | requires :: a -> Maybe Extension | 357 | requires :: a -> Maybe Extension |
312 | requires _ = Nothing | 358 | requires _ = Nothing |
313 | 359 | ||
360 | -- | Get sizes of overhead\/control\/payload byte sequences of | ||
361 | -- binary message representation without encoding message to binary | ||
362 | -- bytestring. | ||
363 | -- | ||
364 | -- This function should obey one law: | ||
365 | -- | ||
366 | -- * 'byteLength' ('stats' msg) == 'BL.length' ('encode' msg) | ||
367 | -- | ||
368 | stats :: a -> ByteStats | ||
369 | stats _ = ByteStats 4 0 0 | ||
370 | |||
314 | {----------------------------------------------------------------------- | 371 | {----------------------------------------------------------------------- |
315 | -- Status messages | 372 | -- Status messages |
316 | -----------------------------------------------------------------------} | 373 | -----------------------------------------------------------------------} |
@@ -337,6 +394,9 @@ instance PeerMessage StatusUpdate where | |||
337 | envelop _ = Status | 394 | envelop _ = Status |
338 | {-# INLINE envelop #-} | 395 | {-# INLINE envelop #-} |
339 | 396 | ||
397 | stats _ = ByteStats 4 1 0 | ||
398 | {-# INLINE stats #-} | ||
399 | |||
340 | {----------------------------------------------------------------------- | 400 | {----------------------------------------------------------------------- |
341 | -- Available messages | 401 | -- Available messages |
342 | -----------------------------------------------------------------------} | 402 | -----------------------------------------------------------------------} |
@@ -361,12 +421,14 @@ instance Pretty Available where | |||
361 | 421 | ||
362 | instance PeerMessage Available where | 422 | instance PeerMessage Available where |
363 | envelop _ = Available | 423 | envelop _ = Available |
364 | |||
365 | -- | BITFIELD message. | ||
366 | instance PeerMessage Bitfield where | ||
367 | envelop c = envelop c . Bitfield | ||
368 | {-# INLINE envelop #-} | 424 | {-# INLINE envelop #-} |
369 | 425 | ||
426 | stats (Have _) = ByteStats (4 + 1) 4 0 | ||
427 | stats (Bitfield bf) = ByteStats (4 + 1) (q + trailing) 0 | ||
428 | where | ||
429 | trailing = if r == 0 then 0 else 1 | ||
430 | (q, r) = quotRem (totalCount bf) 8 | ||
431 | |||
370 | {----------------------------------------------------------------------- | 432 | {----------------------------------------------------------------------- |
371 | -- Transfer messages | 433 | -- Transfer messages |
372 | -----------------------------------------------------------------------} | 434 | -----------------------------------------------------------------------} |
@@ -395,15 +457,9 @@ instance PeerMessage Transfer where | |||
395 | envelop _ = Transfer | 457 | envelop _ = Transfer |
396 | {-# INLINE envelop #-} | 458 | {-# INLINE envelop #-} |
397 | 459 | ||
398 | -- | REQUEST message. | 460 | stats (Request _ ) = ByteStats (4 + 1) (3 * 4) 0 |
399 | instance PeerMessage BlockIx where | 461 | stats (Piece pi ) = ByteStats (4 + 1) (4 + 4 + blockSize pi) 0 |
400 | envelop c = envelop c . Request | 462 | stats (Cancel _ ) = ByteStats (4 + 1) (3 * 4) 0 |
401 | {-# INLINE envelop #-} | ||
402 | |||
403 | -- | PIECE message. | ||
404 | instance PeerMessage (Block BL.ByteString) where | ||
405 | envelop c = envelop c . Piece | ||
406 | {-# INLINE envelop #-} | ||
407 | 463 | ||
408 | {----------------------------------------------------------------------- | 464 | {----------------------------------------------------------------------- |
409 | -- Fast messages | 465 | -- Fast messages |
@@ -424,11 +480,12 @@ data FastMessage = | |||
424 | -- amount of IO. | 480 | -- amount of IO. |
425 | | SuggestPiece !PieceIx | 481 | | SuggestPiece !PieceIx |
426 | 482 | ||
427 | -- | Notifies a requesting peer that its request will not be satisfied. | 483 | -- | Notifies a requesting peer that its request will not be |
484 | -- satisfied. | ||
428 | | RejectRequest !BlockIx | 485 | | RejectRequest !BlockIx |
429 | 486 | ||
430 | -- | This is an advisory messsage meaning "if you ask for this | 487 | -- | This is an advisory messsage meaning \"if you ask for this |
431 | -- piece, I'll give it to you even if you're choked." Used to | 488 | -- piece, I'll give it to you even if you're choked.\" Used to |
432 | -- shorten starting phase. | 489 | -- shorten starting phase. |
433 | | AllowedFast !PieceIx | 490 | | AllowedFast !PieceIx |
434 | deriving (Show, Eq) | 491 | deriving (Show, Eq) |
@@ -447,6 +504,12 @@ instance PeerMessage FastMessage where | |||
447 | requires _ = Just ExtFast | 504 | requires _ = Just ExtFast |
448 | {-# INLINE requires #-} | 505 | {-# INLINE requires #-} |
449 | 506 | ||
507 | stats HaveAll = ByteStats 4 1 0 | ||
508 | stats HaveNone = ByteStats 4 1 0 | ||
509 | stats (SuggestPiece _) = ByteStats 5 4 0 | ||
510 | stats (RejectRequest _) = ByteStats 5 12 0 | ||
511 | stats (AllowedFast _) = ByteStats 5 4 0 | ||
512 | |||
450 | {----------------------------------------------------------------------- | 513 | {----------------------------------------------------------------------- |
451 | -- Extension protocol | 514 | -- Extension protocol |
452 | -----------------------------------------------------------------------} | 515 | -----------------------------------------------------------------------} |
@@ -588,7 +651,7 @@ extHandshakeId = 0 | |||
588 | 651 | ||
589 | -- | Default 'Request' queue size. | 652 | -- | Default 'Request' queue size. |
590 | defaultQueueLength :: Int | 653 | defaultQueueLength :: Int |
591 | defaultQueueLength = 0 | 654 | defaultQueueLength = 1 |
592 | 655 | ||
593 | -- | All fields are empty. | 656 | -- | All fields are empty. |
594 | instance Default ExtendedHandshake where | 657 | instance Default ExtendedHandshake where |
@@ -619,6 +682,7 @@ instance BEncode ExtendedHandshake where | |||
619 | instance Pretty ExtendedHandshake where | 682 | instance Pretty ExtendedHandshake where |
620 | pretty = PP.text . show | 683 | pretty = PP.text . show |
621 | 684 | ||
685 | -- | NOTE: Approximated 'stats'. | ||
622 | instance PeerMessage ExtendedHandshake where | 686 | instance PeerMessage ExtendedHandshake where |
623 | envelop c = envelop c . EHandshake | 687 | envelop c = envelop c . EHandshake |
624 | {-# INLINE envelop #-} | 688 | {-# INLINE envelop #-} |
@@ -626,6 +690,9 @@ instance PeerMessage ExtendedHandshake where | |||
626 | requires _ = Just ExtExtended | 690 | requires _ = Just ExtExtended |
627 | {-# INLINE requires #-} | 691 | {-# INLINE requires #-} |
628 | 692 | ||
693 | stats _ = ByteStats (4 + 1 + 1) 100 {- is it ok? -} 0 -- FIXME | ||
694 | {-# INLINE stats #-} | ||
695 | |||
629 | -- | Set default values and the specified 'ExtendedCaps'. | 696 | -- | Set default values and the specified 'ExtendedCaps'. |
630 | nullExtendedHandshake :: ExtendedCaps -> ExtendedHandshake | 697 | nullExtendedHandshake :: ExtendedCaps -> ExtendedHandshake |
631 | nullExtendedHandshake caps = ExtendedHandshake | 698 | nullExtendedHandshake caps = ExtendedHandshake |
@@ -721,6 +788,7 @@ instance Pretty ExtendedMetadata where | |||
721 | pretty (MetadataReject pix ) = "Reject" <+> PP.int pix | 788 | pretty (MetadataReject pix ) = "Reject" <+> PP.int pix |
722 | pretty (MetadataUnknown bval ) = "Unknown" <+> ppBEncode bval | 789 | pretty (MetadataUnknown bval ) = "Unknown" <+> ppBEncode bval |
723 | 790 | ||
791 | -- | NOTE: Approximated 'stats'. | ||
724 | instance PeerMessage ExtendedMetadata where | 792 | instance PeerMessage ExtendedMetadata where |
725 | envelop c = envelop c . EMetadata (remoteMessageId ExtMetadata c) | 793 | envelop c = envelop c . EMetadata (remoteMessageId ExtMetadata c) |
726 | {-# INLINE envelop #-} | 794 | {-# INLINE envelop #-} |
@@ -728,6 +796,14 @@ instance PeerMessage ExtendedMetadata where | |||
728 | requires _ = Just ExtExtended | 796 | requires _ = Just ExtExtended |
729 | {-# INLINE requires #-} | 797 | {-# INLINE requires #-} |
730 | 798 | ||
799 | stats (MetadataRequest _) = ByteStats (4 + 1 + 1) {- ~ -} 25 0 | ||
800 | stats (MetadataData pi t) = ByteStats (4 + 1 + 1) {- ~ -} 41 $ | ||
801 | BS.length (Data.pieceData pi) | ||
802 | stats (MetadataReject _) = ByteStats (4 + 1 + 1) {- ~ -} 25 0 | ||
803 | stats (MetadataUnknown _) = ByteStats (4 + 1 + 1) {- ? -} 0 0 | ||
804 | |||
805 | -- | All 'Piece's in 'MetadataData' messages MUST have size equal to | ||
806 | -- this value. The last trailing piece can be shorter. | ||
731 | metadataPieceSize :: Int | 807 | metadataPieceSize :: Int |
732 | metadataPieceSize = 16 * 1024 | 808 | metadataPieceSize = 16 * 1024 |
733 | 809 | ||
@@ -791,6 +867,10 @@ instance PeerMessage ExtendedMessage where | |||
791 | requires _ = Just ExtExtended | 867 | requires _ = Just ExtExtended |
792 | {-# INLINE requires #-} | 868 | {-# INLINE requires #-} |
793 | 869 | ||
870 | stats (EHandshake hs) = stats hs | ||
871 | stats (EMetadata _ msg) = stats msg | ||
872 | stats (EUnknown _ msg) = ByteStats (4 + 1 + 1) (BS.length msg) 0 | ||
873 | |||
794 | {----------------------------------------------------------------------- | 874 | {----------------------------------------------------------------------- |
795 | -- The message datatype | 875 | -- The message datatype |
796 | -----------------------------------------------------------------------} | 876 | -----------------------------------------------------------------------} |
@@ -849,6 +929,14 @@ instance PeerMessage Message where | |||
849 | requires (Fast _) = Just ExtFast | 929 | requires (Fast _) = Just ExtFast |
850 | requires (Extended _) = Just ExtExtended | 930 | requires (Extended _) = Just ExtExtended |
851 | 931 | ||
932 | stats KeepAlive = ByteStats 4 0 0 | ||
933 | stats (Status m) = stats m | ||
934 | stats (Available m) = stats m | ||
935 | stats (Transfer m) = stats m | ||
936 | stats (Port _) = ByteStats 5 2 0 | ||
937 | stats (Fast m) = stats m | ||
938 | stats (Extended m) = stats m | ||
939 | |||
852 | -- | PORT message. | 940 | -- | PORT message. |
853 | instance PeerMessage PortNumber where | 941 | instance PeerMessage PortNumber where |
854 | envelop _ = Port | 942 | envelop _ = Port |
diff --git a/src/Network/BitTorrent/Exchange/Wire.hs b/src/Network/BitTorrent/Exchange/Wire.hs index a6ee35d8..fe4086bc 100644 --- a/src/Network/BitTorrent/Exchange/Wire.hs +++ b/src/Network/BitTorrent/Exchange/Wire.hs | |||
@@ -33,9 +33,12 @@ module Network.BitTorrent.Exchange.Wire | |||
33 | , getExtCaps | 33 | , getExtCaps |
34 | 34 | ||
35 | -- ** Messaging | 35 | -- ** Messaging |
36 | , recvMessage | ||
37 | , sendMessage | ||
38 | |||
36 | , validate | 39 | , validate |
37 | , validateBoth | 40 | , validateBoth |
38 | , keepStats | 41 | , trackStats |
39 | 42 | ||
40 | -- ** Stats | 43 | -- ** Stats |
41 | , ConnectionStats (..) | 44 | , ConnectionStats (..) |
@@ -70,12 +73,16 @@ import Text.PrettyPrint.Class | |||
70 | import Data.Torrent.InfoHash | 73 | import Data.Torrent.InfoHash |
71 | import Network.BitTorrent.Core | 74 | import Network.BitTorrent.Core |
72 | import Network.BitTorrent.Exchange.Message | 75 | import Network.BitTorrent.Exchange.Message |
76 | import Data.Torrent | ||
77 | import Data.Torrent.Piece | ||
78 | import Data.BEncode as BE | ||
73 | 79 | ||
74 | -- TODO handle port message? | 80 | -- TODO handle port message? |
75 | -- TODO handle limits? | 81 | -- TODO handle limits? |
76 | -- TODO filter not requested PIECE messages | 82 | -- TODO filter not requested PIECE messages |
77 | -- TODO metadata piece request flood protection | 83 | -- TODO metadata piece request flood protection |
78 | -- TODO piece request flood protection | 84 | -- TODO piece request flood protection |
85 | -- TODO protect against flood attacks | ||
79 | {----------------------------------------------------------------------- | 86 | {----------------------------------------------------------------------- |
80 | -- Exceptions | 87 | -- Exceptions |
81 | -----------------------------------------------------------------------} | 88 | -----------------------------------------------------------------------} |
@@ -130,50 +137,34 @@ isWireFailure _ = return () | |||
130 | -- Stats | 137 | -- Stats |
131 | -----------------------------------------------------------------------} | 138 | -----------------------------------------------------------------------} |
132 | 139 | ||
133 | type ByteCount = Int | 140 | data FlowStats = FlowStats |
134 | 141 | { messageBytes :: {-# UNPACK #-} !ByteStats | |
135 | data MessageStats = MessageStats | 142 | , messageCount :: {-# UNPACK #-} !Int |
136 | { overhead :: {-# UNPACK #-} !ByteCount | 143 | -- msgTypes :: Map MessageType Int |
137 | , payload :: {-# UNPACK #-} !ByteCount | ||
138 | } deriving Show | 144 | } deriving Show |
139 | 145 | ||
140 | instance Default MessageStats where | 146 | -- | Note that this is stats is completely different from Progress: |
141 | def = MessageStats 0 0 | 147 | -- TODO explain why. |
142 | |||
143 | instance Monoid MessageStats where | ||
144 | mempty = mempty | ||
145 | mappend a b = MessageStats | ||
146 | { overhead = overhead a + overhead b | ||
147 | , payload = payload a + payload b | ||
148 | } | ||
149 | |||
150 | |||
151 | messageSize :: MessageStats -> Int | ||
152 | messageSize MessageStats {..} = overhead + payload | ||
153 | |||
154 | messageStats :: Message -> MessageStats | ||
155 | messageStats = undefined | ||
156 | |||
157 | data ConnectionStats = ConnectionStats | 148 | data ConnectionStats = ConnectionStats |
158 | { incomingFlow :: !MessageStats | 149 | { incomingFlow :: !ByteStats |
159 | , outcomingFlow :: !MessageStats | 150 | , outcomingFlow :: !ByteStats |
160 | } deriving Show | 151 | } deriving Show |
161 | 152 | ||
162 | instance Default ConnectionStats where | 153 | instance Default ConnectionStats where |
163 | def = ConnectionStats def def | 154 | def = ConnectionStats def def |
164 | 155 | ||
165 | addStats :: ChannelSide -> MessageStats -> ConnectionStats -> ConnectionStats | 156 | addStats :: ChannelSide -> ByteStats -> ConnectionStats -> ConnectionStats |
166 | addStats ThisPeer x s = s { outcomingFlow = outcomingFlow s <> x } | 157 | addStats ThisPeer x s = s { outcomingFlow = outcomingFlow s <> x } |
167 | addStats RemotePeer x s = s { incomingFlow = incomingFlow s <> x } | 158 | addStats RemotePeer x s = s { incomingFlow = incomingFlow s <> x } |
168 | 159 | ||
169 | recvBytes :: ConnectionStats -> Int | 160 | recvBytes :: ConnectionStats -> Int |
170 | recvBytes = messageSize . incomingFlow | 161 | recvBytes = byteLength . incomingFlow |
171 | 162 | ||
172 | sentBytes :: ConnectionStats -> Int | 163 | sentBytes :: ConnectionStats -> Int |
173 | sentBytes = messageSize . outcomingFlow | 164 | sentBytes = byteLength . outcomingFlow |
174 | 165 | ||
175 | wastedBytes :: ConnectionStats -> Int | 166 | wastedBytes :: ConnectionStats -> Int |
176 | wastedBytes (ConnectionStats _in out) = overhead _in + overhead out | 167 | wastedBytes (ConnectionStats _in out) = overhead _in + overhead out |
177 | 168 | ||
178 | payloadBytes :: ConnectionStats -> Int | 169 | payloadBytes :: ConnectionStats -> Int |
179 | payloadBytes (ConnectionStats _in out) = payload _in + payload out | 170 | payloadBytes (ConnectionStats _in out) = payload _in + payload out |
@@ -184,11 +175,11 @@ payloadBytes (ConnectionStats _in out) = payload _in + payload out | |||
184 | 175 | ||
185 | data Connection = Connection | 176 | data Connection = Connection |
186 | { connCaps :: !Caps | 177 | { connCaps :: !Caps |
187 | , connExtCaps :: !(IORef ExtendedCaps) | ||
188 | , connTopic :: !InfoHash | 178 | , connTopic :: !InfoHash |
189 | , connRemotePeerId :: !PeerId | 179 | , connRemotePeerId :: !PeerId |
190 | , connThisPeerId :: !PeerId | 180 | , connThisPeerId :: !PeerId |
191 | , connStats :: !(IORef ConnectionStats) | 181 | , connStats :: !(IORef ConnectionStats) |
182 | , connExtCaps :: !(IORef ExtendedCaps) | ||
192 | } | 183 | } |
193 | 184 | ||
194 | instance Pretty Connection where | 185 | instance Pretty Connection where |
@@ -278,7 +269,7 @@ askStats :: (ConnectionStats -> a) -> Wire a | |||
278 | askStats f = f <$> getStats | 269 | askStats f = f <$> getStats |
279 | 270 | ||
280 | putStats :: ChannelSide -> Message -> Wire () | 271 | putStats :: ChannelSide -> Message -> Wire () |
281 | putStats side msg = modifyRef connStats (addStats side (messageStats msg)) | 272 | putStats side msg = modifyRef connStats (addStats side (stats msg)) |
282 | 273 | ||
283 | 274 | ||
284 | getConnection :: Wire Connection | 275 | getConnection :: Wire Connection |
@@ -301,8 +292,8 @@ validateBoth action = do | |||
301 | action | 292 | action |
302 | validate ThisPeer | 293 | validate ThisPeer |
303 | 294 | ||
304 | keepStats :: Wire () | 295 | trackStats :: Wire () |
305 | keepStats = do | 296 | trackStats = do |
306 | mmsg <- await | 297 | mmsg <- await |
307 | case mmsg of | 298 | case mmsg of |
308 | Nothing -> return () | 299 | Nothing -> return () |
@@ -329,7 +320,7 @@ extendedHandshake caps = do | |||
329 | sendMessage $ nullExtendedHandshake caps | 320 | sendMessage $ nullExtendedHandshake caps |
330 | msg <- recvMessage | 321 | msg <- recvMessage |
331 | case msg of | 322 | case msg of |
332 | Extended (EHandshake ExtendedHandshake {..}) -> | 323 | Extended (EHandshake ExtendedHandshake {..}) -> do |
333 | setExtCaps $ ehsCaps <> caps | 324 | setExtCaps $ ehsCaps <> caps |
334 | _ -> protocolError HandshakeRefused | 325 | _ -> protocolError HandshakeRefused |
335 | 326 | ||
@@ -356,10 +347,10 @@ connectWire hs addr extCaps wire = | |||
356 | statsRef <- newIORef def | 347 | statsRef <- newIORef def |
357 | runWire wire' sock $ Connection | 348 | runWire wire' sock $ Connection |
358 | { connCaps = caps | 349 | { connCaps = caps |
359 | , connExtCaps = extCapsRef | ||
360 | , connTopic = hsInfoHash hs | 350 | , connTopic = hsInfoHash hs |
361 | , connRemotePeerId = hsPeerId hs' | 351 | , connRemotePeerId = hsPeerId hs' |
362 | , connThisPeerId = hsPeerId hs | 352 | , connThisPeerId = hsPeerId hs |
353 | , connExtCaps = extCapsRef | ||
363 | , connStats = statsRef | 354 | , connStats = statsRef |
364 | } | 355 | } |
365 | 356 | ||