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/Protocol.hs387
-rw-r--r--src/Network/BitTorrent/Exchange/Selection.hs94
-rw-r--r--src/Network/BitTorrent/Exchange/Status.hs65
3 files changed, 546 insertions, 0 deletions
diff --git a/src/Network/BitTorrent/Exchange/Protocol.hs b/src/Network/BitTorrent/Exchange/Protocol.hs
new file mode 100644
index 00000000..cab54ef5
--- /dev/null
+++ b/src/Network/BitTorrent/Exchange/Protocol.hs
@@ -0,0 +1,387 @@
1-- |
2-- Copyright : (c) Sam T. 2013
3-- License : MIT
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- In order to establish the connection between peers we should send
9-- 'Handshake' message. The 'Handshake' is a required message and
10-- must be the first message transmitted by the peer to the another
11-- peer.
12--
13{-# LANGUAGE OverloadedStrings #-}
14{-# LANGUAGE RecordWildCards #-}
15module Network.BitTorrent.PeerWire.Protocol
16 ( -- * Inital handshake
17 Handshake(..), ppHandshake
18 , handshake , handshakeCaps
19
20 -- ** Defaults
21 , defaultHandshake, defaultBTProtocol, defaultReserved
22 , handshakeMaxSize
23
24 -- * Block
25 , PieceIx, BlockLIx, PieceLIx
26 , BlockIx(..), ppBlockIx
27 , Block(..), ppBlock ,blockSize
28 , pieceIx, blockIx
29 , blockRange, ixRange, isPiece
30
31 -- ** Defaults
32 , defaultBlockSize
33
34 -- * Regular messages
35 , Message(..)
36 , ppMessage
37 ) where
38
39import Control.Applicative
40import Control.Monad
41import Control.Exception
42import Data.ByteString (ByteString)
43import qualified Data.ByteString as B
44import qualified Data.ByteString.Char8 as BC
45import qualified Data.ByteString.Lazy as Lazy
46import Data.Serialize as S
47import Data.Int
48import Data.Word
49import Text.PrettyPrint
50
51import Network
52import Network.Socket.ByteString
53
54import Data.Bitfield
55import Data.Torrent
56import Network.BitTorrent.Extension
57import Network.BitTorrent.Peer
58
59
60
61{-----------------------------------------------------------------------
62 Handshake
63-----------------------------------------------------------------------}
64
65data Handshake = Handshake {
66 -- | Identifier of the protocol.
67 hsProtocol :: ByteString
68
69 -- | Reserved bytes used to specify supported BEP's.
70 , hsReserved :: Capabilities
71
72 -- | Info hash of the info part of the metainfo file. that is
73 -- transmitted in tracker requests. Info hash of the initiator
74 -- handshake and response handshake should match, otherwise
75 -- initiator should break the connection.
76 --
77 , hsInfoHash :: InfoHash
78
79 -- | Peer id of the initiator. This is usually the same peer id
80 -- that is transmitted in tracker requests.
81 --
82 , hsPeerID :: PeerID
83
84 } deriving (Show, Eq)
85
86instance Serialize Handshake where
87 put hs = do
88 putWord8 (fromIntegral (B.length (hsProtocol hs)))
89 putByteString (hsProtocol hs)
90 putWord64be (hsReserved hs)
91 put (hsInfoHash hs)
92 put (hsPeerID hs)
93
94 get = do
95 len <- getWord8
96 Handshake <$> getBytes (fromIntegral len)
97 <*> getWord64be
98 <*> get
99 <*> get
100
101
102handshakeCaps :: Handshake -> Capabilities
103handshakeCaps = hsReserved
104
105-- | Format handshake in human readable form.
106ppHandshake :: Handshake -> Doc
107ppHandshake Handshake {..} =
108 text (BC.unpack hsProtocol) <+> ppClientInfo (clientInfo hsPeerID)
109
110-- | Get handshake message size in bytes from the length of protocol string.
111handshakeSize :: Word8 -> Int
112handshakeSize n = 1 + fromIntegral n + 8 + 20 + 20
113
114-- | Maximum size of handshake message in bytes.
115handshakeMaxSize :: Int
116handshakeMaxSize = handshakeSize 255
117
118-- | Default protocol string "BitTorrent protocol" as is.
119defaultBTProtocol :: ByteString
120defaultBTProtocol = "BitTorrent protocol"
121
122-- | Default reserved word is 0.
123defaultReserved :: Word64
124defaultReserved = 0
125
126-- | Length of info hash and peer id is unchecked, so it /should/ be equal 20.
127defaultHandshake :: InfoHash -> PeerID -> Handshake
128defaultHandshake = Handshake defaultBTProtocol defaultReserved
129
130-- | Handshaking with a peer specified by the second argument.
131handshake :: Socket -> Handshake -> IO Handshake
132handshake sock hs = do
133 sendAll sock (S.encode hs)
134
135 header <- recv sock 1
136 when (B.length header == 0) $
137 throw $ userError "Unable to receive handshake."
138
139 let protocolLen = B.head header
140 let restLen = handshakeSize protocolLen - 1
141 body <- recv sock restLen
142 let resp = B.cons protocolLen body
143
144 case checkIH (S.decode resp) of
145 Right hs' -> return hs'
146 Left msg -> throw $ userError msg
147 where
148 checkIH (Right hs')
149 | hsInfoHash hs /= hsInfoHash hs'
150 = Left "Handshake info hash do not match."
151 checkIH x = x
152
153{-----------------------------------------------------------------------
154 Blocks
155-----------------------------------------------------------------------}
156
157type BlockLIx = Int
158type PieceLIx = Int
159
160
161data BlockIx = BlockIx {
162 -- | Zero-based piece index.
163 ixPiece :: {-# UNPACK #-} !PieceLIx
164
165 -- | Zero-based byte offset within the piece.
166 , ixOffset :: {-# UNPACK #-} !Int
167
168 -- | Block size starting from offset.
169 , ixLength :: {-# UNPACK #-} !Int
170 } deriving (Show, Eq)
171
172getInt :: Get Int
173getInt = fromIntegral <$> getWord32be
174{-# INLINE getInt #-}
175
176putInt :: Putter Int
177putInt = putWord32be . fromIntegral
178{-# INLINE putInt #-}
179
180instance Serialize BlockIx where
181 {-# SPECIALIZE instance Serialize BlockIx #-}
182 get = BlockIx <$> getInt <*> getInt <*> getInt
183 {-# INLINE get #-}
184
185 put ix = do putInt (ixPiece ix)
186 putInt (ixOffset ix)
187 putInt (ixLength ix)
188 {-# INLINE put #-}
189
190ppBlockIx :: BlockIx -> Doc
191ppBlockIx BlockIx {..} =
192 "piece = " <> int ixPiece <> "," <+>
193 "offset = " <> int ixOffset <> "," <+>
194 "length = " <> int ixLength
195
196data Block = Block {
197 -- | Zero-based piece index.
198 blkPiece :: !PieceLIx
199
200 -- | Zero-based byte offset within the piece.
201 , blkOffset :: !Int
202
203 -- | Payload.
204 , blkData :: !ByteString
205 } deriving (Show, Eq)
206
207ppBlock :: Block -> Doc
208ppBlock = ppBlockIx . blockIx
209
210blockSize :: Block -> Int
211blockSize blk = B.length (blkData blk)
212
213-- | Widely used semi-official block size.
214defaultBlockSize :: Int
215defaultBlockSize = 16 * 1024
216
217
218isPiece :: Int -> Block -> Bool
219isPiece pieceSize (Block i offset bs) =
220 offset == 0 && B.length bs == pieceSize && i >= 0
221{-# INLINE isPiece #-}
222
223pieceIx :: Int -> Int -> BlockIx
224pieceIx i = BlockIx i 0
225{-# INLINE pieceIx #-}
226
227blockIx :: Block -> BlockIx
228blockIx = BlockIx <$> blkPiece <*> blkOffset <*> B.length . blkData
229
230blockRange :: (Num a, Integral a) => Int -> Block -> (a, a)
231blockRange pieceSize blk = (offset, offset + len)
232 where
233 offset = fromIntegral pieceSize * fromIntegral (blkPiece blk)
234 + fromIntegral (blkOffset blk)
235 len = fromIntegral (B.length (blkData blk))
236{-# INLINE blockRange #-}
237{-# SPECIALIZE blockRange :: Int -> Block -> (Int64, Int64) #-}
238
239ixRange :: (Num a, Integral a) => Int -> BlockIx -> (a, a)
240ixRange pieceSize ix = (offset, offset + len)
241 where
242 offset = fromIntegral pieceSize * fromIntegral (ixPiece ix)
243 + fromIntegral (ixOffset ix)
244 len = fromIntegral (ixLength ix)
245{-# INLINE ixRange #-}
246{-# SPECIALIZE ixRange :: Int -> BlockIx -> (Int64, Int64) #-}
247
248
249{-----------------------------------------------------------------------
250 Handshake
251-----------------------------------------------------------------------}
252
253-- | Messages used in communication between peers.
254--
255-- Note: If some extensions are disabled (not present in extension
256-- mask) and client receive message used by the disabled
257-- extension then the client MUST close the connection.
258--
259data Message = KeepAlive
260 | Choke
261 | Unchoke
262 | Interested
263 | NotInterested
264
265 -- | Zero-based index of a piece that has just been
266 -- successfully downloaded and verified via the hash.
267 | Have !PieceIx
268
269 -- | The bitfield message may only be sent immediately
270 -- after the handshaking sequence is complete, and
271 -- before any other message are sent. If client have no
272 -- pieces then bitfield need not to be sent.
273 | Bitfield !Bitfield
274
275 -- | Request for a particular block. If a client is
276 -- requested a block that another peer do not have the
277 -- peer might not answer at all.
278 | Request !BlockIx
279
280 -- | Response for a request for a block.
281 | Piece !Block
282
283 -- | Used to cancel block requests. It is typically
284 -- used during "End Game".
285 | Cancel !BlockIx
286
287 | Port !PortNumber
288
289 -- | BEP 6: Then peer have all pieces it might send the
290 -- 'HaveAll' message instead of 'Bitfield'
291 -- message. Used to save bandwidth.
292 | HaveAll
293
294 -- | BEP 6: Then peer have no pieces it might send
295 -- 'HaveNone' message intead of 'Bitfield'
296 -- message. Used to save bandwidth.
297 | HaveNone
298
299 -- | BEP 6: This is an advisory message meaning "you
300 -- might like to download this piece." Used to avoid
301 -- excessive disk seeks and amount of IO.
302 | SuggestPiece !PieceIx
303
304 -- | BEP 6: Notifies a requesting peer that its request
305 -- will not be satisfied.
306 | RejectRequest !BlockIx
307
308 -- | BEP 6: This is an advisory messsage meaning "if
309 -- you ask for this piece, I'll give it to you even if
310 -- you're choked." Used to shorten starting phase.
311 | AllowedFast !PieceIx
312 deriving (Show, Eq)
313
314
315instance Serialize Message where
316 get = do
317 len <- getInt
318-- _ <- lookAhead $ ensure len
319 if len == 0 then return KeepAlive
320 else do
321 mid <- getWord8
322 case mid of
323 0x00 -> return Choke
324 0x01 -> return Unchoke
325 0x02 -> return Interested
326 0x03 -> return NotInterested
327 0x04 -> Have <$> getInt
328 0x05 -> (Bitfield . fromBitmap) <$> getByteString (pred len)
329 0x06 -> Request <$> get
330 0x07 -> Piece <$> getBlock (len - 9)
331 0x08 -> Cancel <$> get
332 0x09 -> (Port . fromIntegral) <$> getWord16be
333 0x0E -> return HaveAll
334 0x0F -> return HaveNone
335 0x0D -> SuggestPiece <$> getInt
336 0x10 -> RejectRequest <$> get
337 0x11 -> AllowedFast <$> getInt
338 _ -> do
339 rm <- remaining >>= getBytes
340 fail $ "unknown message ID: " ++ show mid ++ "\n"
341 ++ "remaining available bytes: " ++ show rm
342
343 where
344 getBlock :: Int -> Get Block
345 getBlock len = Block <$> getInt <*> getInt <*> getBytes len
346 {-# INLINE getBlock #-}
347
348
349 put KeepAlive = putInt 0
350 put Choke = putInt 1 >> putWord8 0x00
351 put Unchoke = putInt 1 >> putWord8 0x01
352 put Interested = putInt 1 >> putWord8 0x02
353 put NotInterested = putInt 1 >> putWord8 0x03
354 put (Have i) = putInt 5 >> putWord8 0x04 >> putInt i
355 put (Bitfield bf) = putInt l >> putWord8 0x05 >> putLazyByteString b
356 where b = toBitmap bf
357 l = succ (fromIntegral (Lazy.length b))
358 {-# INLINE l #-}
359 put (Request blk) = putInt 13 >> putWord8 0x06 >> put blk
360 put (Piece blk) = putInt l >> putWord8 0x07 >> putBlock
361 where l = 9 + B.length (blkData blk)
362 {-# INLINE l #-}
363 putBlock = do putInt (blkPiece blk)
364 putInt (blkOffset blk)
365 putByteString (blkData blk)
366 {-# INLINE putBlock #-}
367
368 put (Cancel blk) = putInt 13 >> putWord8 0x08 >> put blk
369 put (Port p ) = putInt 3 >> putWord8 0x09 >> putWord16be (fromIntegral p)
370 put HaveAll = putInt 1 >> putWord8 0x0E
371 put HaveNone = putInt 1 >> putWord8 0x0F
372 put (SuggestPiece pix) = putInt 5 >> putWord8 0x0D >> putInt pix
373 put (RejectRequest ix) = putInt 13 >> putWord8 0x10 >> put ix
374 put (AllowedFast ix) = putInt 5 >> putWord8 0x11 >> putInt ix
375
376
377-- | Format messages in human readable form. Note that output is
378-- compact and suitable for logging: only useful information but not
379-- payload bytes.
380--
381ppMessage :: Message -> Doc
382ppMessage (Bitfield _) = "Bitfield"
383ppMessage (Piece blk) = "Piece" <+> ppBlock blk
384ppMessage (Cancel ix) = "Cancel" <+> ppBlockIx ix
385ppMessage (SuggestPiece pix) = "Suggest" <+> int pix
386ppMessage (RejectRequest ix) = "Reject" <+> ppBlockIx ix
387ppMessage msg = text (show msg) \ No newline at end of file
diff --git a/src/Network/BitTorrent/Exchange/Selection.hs b/src/Network/BitTorrent/Exchange/Selection.hs
new file mode 100644
index 00000000..db9e04f4
--- /dev/null
+++ b/src/Network/BitTorrent/Exchange/Selection.hs
@@ -0,0 +1,94 @@
1-- TODO tests
2-- |
3-- Copyright : (c) Sam T. 2013
4-- License : MIT
5-- Maintainer : pxqr.sta@gmail.com
6-- Stability : experimental
7-- Portability : portable
8--
9-- This module provides commonly used piece seletion algorithms
10-- which used to find out which one next piece to download.
11-- Selectors considered to be used in the following order:
12--
13-- * Random first - at the start.
14--
15-- * Rarest first selection - performed to avoid situation when
16-- rarest piece is unaccessible.
17--
18-- * _End game_ seletion - performed after a peer has requested all
19-- the subpieces of the content.
20--
21-- Note that BitTorrent applies the strict priority policy for
22-- /subpiece/ or /blocks/ selection.
23--
24module Network.BitTorrent.PeerWire.Selection
25 ( Selector
26
27 -- * Construction
28 , selector, strategyClass
29
30 -- * Strategies
31 , strictFirst, strictLast
32 , rarestFirst, randomFirst, endGame
33 ) where
34
35import Data.Bitfield
36import Data.Ratio
37import Network.BitTorrent.PeerWire.Protocol
38
39
40type Selector = Bitfield -- ^ Indices of client /have/ pieces.
41 -> Bitfield -- ^ Indices of peer /have/ pieces.
42 -> [Bitfield] -- ^ Indices of other peers /have/ pieces.
43 -> Maybe PieceIx -- ^ Zero-based index of piece to request
44 -- to, if any.
45
46selector :: Selector -- ^ Selector to use at the start.
47 -> Ratio PieceCount
48 -> Selector -- ^ Selector to use after the client have the C pieces.
49 -> Selector -- ^ Selector that changes behaviour based on completeness.
50selector start pt ready h a xs =
51 case strategyClass pt h of
52 SCBeginning -> start h a xs
53 SCReady -> ready h a xs
54 SCEnd -> endGame h a xs
55
56data StartegyClass
57 = SCBeginning
58 | SCReady
59 | SCEnd
60 deriving (Show, Eq, Ord, Enum, Bounded)
61
62
63strategyClass :: Ratio PieceCount -> Bitfield -> StartegyClass
64strategyClass threshold = classify . completeness
65 where
66 classify have
67 | have < threshold = SCBeginning
68 | have + 1 % numerator have < 1 = SCReady -- FIXME numerator have is not total count
69 | otherwise = SCEnd
70
71
72-- | Select the first available piece.
73strictFirst :: Selector
74strictFirst h a _ = findMin (difference a h)
75
76-- | Select the last available piece.
77strictLast :: Selector
78strictLast h a _ = findMax (difference a h)
79
80-- |
81rarestFirst :: Selector
82rarestFirst h a xs = rarest (map (intersection want) xs)
83 where
84 want = difference h a
85
86-- | In average random first is faster than rarest first strategy but
87-- only if all pieces are available.
88randomFirst :: Selector
89randomFirst = do
90-- randomIO
91 error "randomFirst"
92
93endGame :: Selector
94endGame = strictLast
diff --git a/src/Network/BitTorrent/Exchange/Status.hs b/src/Network/BitTorrent/Exchange/Status.hs
new file mode 100644
index 00000000..806ba77d
--- /dev/null
+++ b/src/Network/BitTorrent/Exchange/Status.hs
@@ -0,0 +1,65 @@
1-- |
2-- Copyright : (c) Sam T. 2013
3-- License : MIT
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8module Network.BitTorrent.Peer.Status
9 ( PeerStatus(..)
10 , setChoking, setInterested
11 , initPeerStatus
12
13 , SessionStatus(..)
14 , initSessionStatus
15 , setClientStatus, setPeerStatus
16 , canUpload, canDownload
17
18 -- * Defaults
19 , defaultUnchokeSlots
20 ) where
21
22data PeerStatus = PeerStatus {
23 psChoking :: Bool
24 , psInterested :: Bool
25 }
26
27-- | Any session between peers starts as choking and not interested.
28initPeerStatus :: PeerStatus
29initPeerStatus = PeerStatus True False
30
31setChoking :: Bool -> PeerStatus -> PeerStatus
32setChoking b ps = ps { psChoking = b }
33
34setInterested :: Bool -> PeerStatus -> PeerStatus
35setInterested b ps = ps { psInterested = b }
36
37
38
39data SessionStatus = SessionStatus {
40 seClientStatus :: PeerStatus
41 , sePeerStatus :: PeerStatus
42 }
43
44initSessionStatus :: SessionStatus
45initSessionStatus = SessionStatus initPeerStatus initPeerStatus
46
47setClientStatus :: (PeerStatus -> PeerStatus) -> SessionStatus -> SessionStatus
48setClientStatus f ss = ss { seClientStatus = f (seClientStatus ss) }
49
50setPeerStatus :: (PeerStatus -> PeerStatus) -> SessionStatus -> SessionStatus
51setPeerStatus f ss = ss { sePeerStatus = f (sePeerStatus ss) }
52
53-- | Can the /client/ to upload to the /peer/?
54canUpload :: SessionStatus -> Bool
55canUpload SessionStatus { seClientStatus = client, sePeerStatus = peer} =
56 psInterested peer && not (psChoking client)
57
58-- | Can the /client/ download from the /peer/?
59canDownload :: SessionStatus -> Bool
60canDownload SessionStatus { seClientStatus = client, sePeerStatus = peer } =
61 psInterested client && not (psChoking peer)
62
63-- | Indicates have many peers are allowed to download from the client.
64defaultUnchokeSlots :: Int
65defaultUnchokeSlots = 4 \ No newline at end of file