summaryrefslogtreecommitdiff
path: root/src/Network/BitTorrent/Exchange
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/BitTorrent/Exchange')
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs124
-rw-r--r--src/Network/BitTorrent/Exchange/Wire.hs61
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
292defaultHandshake = Handshake def def 297defaultHandshake = Handshake def def
293 298
294{----------------------------------------------------------------------- 299{-----------------------------------------------------------------------
300-- Stats
301-----------------------------------------------------------------------}
302
303-- | Number of bytes.
304type 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.
309data 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.
324instance Default ByteStats where
325 def = ByteStats 0 0 0
326
327-- | Monoid under addition.
328instance 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.
337byteLength :: ByteStats -> Int
338byteLength 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
362instance PeerMessage Available where 422instance PeerMessage Available where
363 envelop _ = Available 423 envelop _ = Available
364
365-- | BITFIELD message.
366instance 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
399instance 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.
404instance 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.
590defaultQueueLength :: Int 653defaultQueueLength :: Int
591defaultQueueLength = 0 654defaultQueueLength = 1
592 655
593-- | All fields are empty. 656-- | All fields are empty.
594instance Default ExtendedHandshake where 657instance Default ExtendedHandshake where
@@ -619,6 +682,7 @@ instance BEncode ExtendedHandshake where
619instance Pretty ExtendedHandshake where 682instance Pretty ExtendedHandshake where
620 pretty = PP.text . show 683 pretty = PP.text . show
621 684
685-- | NOTE: Approximated 'stats'.
622instance PeerMessage ExtendedHandshake where 686instance 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'.
630nullExtendedHandshake :: ExtendedCaps -> ExtendedHandshake 697nullExtendedHandshake :: ExtendedCaps -> ExtendedHandshake
631nullExtendedHandshake caps = ExtendedHandshake 698nullExtendedHandshake 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'.
724instance PeerMessage ExtendedMetadata where 792instance 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.
731metadataPieceSize :: Int 807metadataPieceSize :: Int
732metadataPieceSize = 16 * 1024 808metadataPieceSize = 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.
853instance PeerMessage PortNumber where 941instance 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
70import Data.Torrent.InfoHash 73import Data.Torrent.InfoHash
71import Network.BitTorrent.Core 74import Network.BitTorrent.Core
72import Network.BitTorrent.Exchange.Message 75import Network.BitTorrent.Exchange.Message
76import Data.Torrent
77import Data.Torrent.Piece
78import 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
133type ByteCount = Int 140data FlowStats = FlowStats
134 141 { messageBytes :: {-# UNPACK #-} !ByteStats
135data 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
140instance Default MessageStats where 146-- | Note that this is stats is completely different from Progress:
141 def = MessageStats 0 0 147-- TODO explain why.
142
143instance 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
151messageSize :: MessageStats -> Int
152messageSize MessageStats {..} = overhead + payload
153
154messageStats :: Message -> MessageStats
155messageStats = undefined
156
157data ConnectionStats = ConnectionStats 148data ConnectionStats = ConnectionStats
158 { incomingFlow :: !MessageStats 149 { incomingFlow :: !ByteStats
159 , outcomingFlow :: !MessageStats 150 , outcomingFlow :: !ByteStats
160 } deriving Show 151 } deriving Show
161 152
162instance Default ConnectionStats where 153instance Default ConnectionStats where
163 def = ConnectionStats def def 154 def = ConnectionStats def def
164 155
165addStats :: ChannelSide -> MessageStats -> ConnectionStats -> ConnectionStats 156addStats :: ChannelSide -> ByteStats -> ConnectionStats -> ConnectionStats
166addStats ThisPeer x s = s { outcomingFlow = outcomingFlow s <> x } 157addStats ThisPeer x s = s { outcomingFlow = outcomingFlow s <> x }
167addStats RemotePeer x s = s { incomingFlow = incomingFlow s <> x } 158addStats RemotePeer x s = s { incomingFlow = incomingFlow s <> x }
168 159
169recvBytes :: ConnectionStats -> Int 160recvBytes :: ConnectionStats -> Int
170recvBytes = messageSize . incomingFlow 161recvBytes = byteLength . incomingFlow
171 162
172sentBytes :: ConnectionStats -> Int 163sentBytes :: ConnectionStats -> Int
173sentBytes = messageSize . outcomingFlow 164sentBytes = byteLength . outcomingFlow
174 165
175wastedBytes :: ConnectionStats -> Int 166wastedBytes :: ConnectionStats -> Int
176wastedBytes (ConnectionStats _in out) = overhead _in + overhead out 167wastedBytes (ConnectionStats _in out) = overhead _in + overhead out
177 168
178payloadBytes :: ConnectionStats -> Int 169payloadBytes :: ConnectionStats -> Int
179payloadBytes (ConnectionStats _in out) = payload _in + payload out 170payloadBytes (ConnectionStats _in out) = payload _in + payload out
@@ -184,11 +175,11 @@ payloadBytes (ConnectionStats _in out) = payload _in + payload out
184 175
185data Connection = Connection 176data 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
194instance Pretty Connection where 185instance Pretty Connection where
@@ -278,7 +269,7 @@ askStats :: (ConnectionStats -> a) -> Wire a
278askStats f = f <$> getStats 269askStats f = f <$> getStats
279 270
280putStats :: ChannelSide -> Message -> Wire () 271putStats :: ChannelSide -> Message -> Wire ()
281putStats side msg = modifyRef connStats (addStats side (messageStats msg)) 272putStats side msg = modifyRef connStats (addStats side (stats msg))
282 273
283 274
284getConnection :: Wire Connection 275getConnection :: Wire Connection
@@ -301,8 +292,8 @@ validateBoth action = do
301 action 292 action
302 validate ThisPeer 293 validate ThisPeer
303 294
304keepStats :: Wire () 295trackStats :: Wire ()
305keepStats = do 296trackStats = 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