summaryrefslogtreecommitdiff
path: root/bittorrent/src/Network/BitTorrent/Exchange/Block.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2017-09-15 06:22:10 -0400
committerjoe <joe@jerkface.net>2017-09-15 06:22:10 -0400
commit12cbb3af2413dc28838ed271351dda16df8f7bdb (patch)
tree2db77a787e18a81a8369a8d73fee369d8826f064 /bittorrent/src/Network/BitTorrent/Exchange/Block.hs
parent362357c6d08cbd8dffa627a1e80199dcb9ba231f (diff)
Separating dht-client library from bittorrent package.
Diffstat (limited to 'bittorrent/src/Network/BitTorrent/Exchange/Block.hs')
-rw-r--r--bittorrent/src/Network/BitTorrent/Exchange/Block.hs369
1 files changed, 369 insertions, 0 deletions
diff --git a/bittorrent/src/Network/BitTorrent/Exchange/Block.hs b/bittorrent/src/Network/BitTorrent/Exchange/Block.hs
new file mode 100644
index 00000000..bc9a3d24
--- /dev/null
+++ b/bittorrent/src/Network/BitTorrent/Exchange/Block.hs
@@ -0,0 +1,369 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2013
3-- License : BSD3
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- Blocks are used to transfer pieces.
9--
10{-# LANGUAGE BangPatterns #-}
11{-# LANGUAGE FlexibleInstances #-}
12{-# LANGUAGE TemplateHaskell #-}
13{-# LANGUAGE DeriveFunctor #-}
14{-# LANGUAGE DeriveDataTypeable #-}
15{-# LANGUAGE GeneralizedNewtypeDeriving #-}
16module Network.BitTorrent.Exchange.Block
17 ( -- * Block attributes
18 BlockOffset
19 , BlockCount
20 , BlockSize
21 , defaultTransferSize
22
23 -- * Block index
24 , BlockIx(..)
25 , blockIxRange
26
27 -- * Block data
28 , Block(..)
29 , blockIx
30 , blockSize
31 , blockRange
32 , isPiece
33 , leadingBlock
34
35 -- * Block bucket
36 , Bucket
37
38 -- ** Query
39 , Network.BitTorrent.Exchange.Block.null
40 , Network.BitTorrent.Exchange.Block.full
41 , Network.BitTorrent.Exchange.Block.size
42 , Network.BitTorrent.Exchange.Block.spans
43
44 -- ** Construction
45 , Network.BitTorrent.Exchange.Block.empty
46 , Network.BitTorrent.Exchange.Block.insert
47 , Network.BitTorrent.Exchange.Block.insertLazy
48 , Network.BitTorrent.Exchange.Block.merge
49 , Network.BitTorrent.Exchange.Block.fromList
50
51 -- ** Rendering
52 , Network.BitTorrent.Exchange.Block.toPiece
53
54 -- ** Debug
55 , Network.BitTorrent.Exchange.Block.valid
56 ) where
57
58import Prelude hiding (span)
59import Control.Applicative
60import Data.ByteString as BS hiding (span)
61import Data.ByteString.Lazy as BL hiding (span)
62import Data.ByteString.Lazy.Builder as BS
63import Data.Default
64import Data.Monoid
65import Data.List as L hiding (span)
66import Data.Serialize as S
67import Data.Typeable
68import Numeric
69import Text.PrettyPrint as PP hiding ((<>))
70import Text.PrettyPrint.HughesPJClass hiding ((<>),($$))
71
72import Data.Torrent
73
74{-----------------------------------------------------------------------
75-- Block attributes
76-----------------------------------------------------------------------}
77
78-- | Offset of a block in a piece in bytes. Should be multiple of
79-- the choosen block size.
80type BlockOffset = Int
81
82-- | Size of a block in bytes. Should be power of 2.
83--
84-- Normally block size is equal to 'defaultTransferSize'.
85--
86type BlockSize = Int
87
88-- | Number of block in a piece of a torrent. Used to distinguish
89-- block count from piece count.
90type BlockCount = Int
91
92-- | Widely used semi-official block size. Some clients can ignore if
93-- block size of BlockIx in Request message is not equal to this
94-- value.
95--
96defaultTransferSize :: BlockSize
97defaultTransferSize = 16 * 1024
98
99{-----------------------------------------------------------------------
100 Block Index
101-----------------------------------------------------------------------}
102
103-- | BlockIx correspond.
104data BlockIx = BlockIx {
105 -- | Zero-based piece index.
106 ixPiece :: {-# UNPACK #-} !PieceIx
107
108 -- | Zero-based byte offset within the piece.
109 , ixOffset :: {-# UNPACK #-} !BlockOffset
110
111 -- | Block size starting from offset.
112 , ixLength :: {-# UNPACK #-} !BlockSize
113 } deriving (Show, Eq, Typeable)
114
115-- | First block in torrent. Useful for debugging.
116instance Default BlockIx where
117 def = BlockIx 0 0 defaultTransferSize
118
119getInt :: S.Get Int
120getInt = fromIntegral <$> S.getWord32be
121{-# INLINE getInt #-}
122
123putInt :: S.Putter Int
124putInt = S.putWord32be . fromIntegral
125{-# INLINE putInt #-}
126
127instance Serialize BlockIx where
128 {-# SPECIALIZE instance Serialize BlockIx #-}
129 get = BlockIx <$> getInt
130 <*> getInt
131 <*> getInt
132 {-# INLINE get #-}
133
134 put BlockIx {..} = do
135 putInt ixPiece
136 putInt ixOffset
137 putInt ixLength
138 {-# INLINE put #-}
139
140instance Pretty BlockIx where
141 pPrint BlockIx {..} =
142 ("piece = " <> int ixPiece <> ",") <+>
143 ("offset = " <> int ixOffset <> ",") <+>
144 ("length = " <> int ixLength)
145
146-- | Get location of payload bytes in the torrent content.
147blockIxRange :: (Num a, Integral a) => PieceSize -> BlockIx -> (a, a)
148blockIxRange piSize BlockIx {..} = (offset, offset + len)
149 where
150 offset = fromIntegral piSize * fromIntegral ixPiece
151 + fromIntegral ixOffset
152 len = fromIntegral ixLength
153{-# INLINE blockIxRange #-}
154
155{-----------------------------------------------------------------------
156 Block
157-----------------------------------------------------------------------}
158
159data Block payload = Block {
160 -- | Zero-based piece index.
161 blkPiece :: {-# UNPACK #-} !PieceIx
162
163 -- | Zero-based byte offset within the piece.
164 , blkOffset :: {-# UNPACK #-} !BlockOffset
165
166 -- | Payload bytes.
167 , blkData :: !payload
168 } deriving (Show, Eq, Functor, Typeable)
169
170-- | Payload is ommitted.
171instance Pretty (Block BL.ByteString) where
172 pPrint = pPrint . blockIx
173 {-# INLINE pPrint #-}
174
175-- | Get size of block /payload/ in bytes.
176blockSize :: Block BL.ByteString -> BlockSize
177blockSize = fromIntegral . BL.length . blkData
178{-# INLINE blockSize #-}
179
180-- | Get block index of a block.
181blockIx :: Block BL.ByteString -> BlockIx
182blockIx = BlockIx <$> blkPiece <*> blkOffset <*> blockSize
183
184-- | Get location of payload bytes in the torrent content.
185blockRange :: (Num a, Integral a)
186 => PieceSize -> Block BL.ByteString -> (a, a)
187blockRange piSize = blockIxRange piSize . blockIx
188{-# INLINE blockRange #-}
189
190-- | Test if a block can be safely turned into a piece.
191isPiece :: PieceSize -> Block BL.ByteString -> Bool
192isPiece pieceLen blk @ (Block i offset _) =
193 offset == 0 && blockSize blk == pieceLen && i >= 0
194{-# INLINE isPiece #-}
195
196-- | First block in the piece.
197leadingBlock :: PieceIx -> BlockSize -> BlockIx
198leadingBlock pix blockSize = BlockIx
199 { ixPiece = pix
200 , ixOffset = 0
201 , ixLength = blockSize
202 }
203{-# INLINE leadingBlock #-}
204
205{-----------------------------------------------------------------------
206-- Bucket
207-----------------------------------------------------------------------}
208
209type Pos = Int
210type ChunkSize = Int
211
212-- | A sparse set of blocks used to represent an /in progress/ piece.
213data Bucket
214 = Nil
215 | Span {-# UNPACK #-} !ChunkSize !Bucket
216 | Fill {-# UNPACK #-} !ChunkSize !Builder !Bucket
217
218instance Show Bucket where
219 showsPrec i Nil = showString ""
220 showsPrec i (Span s xs) = showString "Span " <> showInt s
221 <> showString " " <> showsPrec i xs
222 showsPrec i (Fill s _ xs) = showString "Fill " <> showInt s
223 <> showString " " <> showsPrec i xs
224
225-- | INVARIANT: 'Nil' should appear only after 'Span' of 'Fill'.
226nilInvFailed :: a
227nilInvFailed = error "Nil: bucket invariant failed"
228
229valid :: Bucket -> Bool
230valid = check Nothing
231 where
232 check Nothing Nil = False -- see 'nilInvFailed'
233 check (Just _) _ = True
234 check prevIsSpan (Span sz xs) =
235 prevIsSpan /= Just True && -- Span n (NotSpan .. ) invariant
236 sz > 0 && -- Span is always non-empty
237 check (Just True) xs
238 check prevIsSpan (Fill sz b xs) =
239 prevIsSpan /= Just True && -- Fill n (NotFill .. ) invariant
240 sz > 0 && -- Fill is always non-empty
241 check (Just False) xs
242
243instance Pretty Bucket where
244 pPrint Nil = nilInvFailed
245 pPrint bkt = go bkt
246 where
247 go Nil = PP.empty
248 go (Span sz xs) = "Span" <+> PP.int sz <+> go xs
249 go (Fill sz b xs) = "Fill" <+> PP.int sz <+> go xs
250
251-- | Smart constructor: use it when some block is /deleted/ from
252-- bucket.
253span :: ChunkSize -> Bucket -> Bucket
254span sz (Span sz' xs) = Span (sz + sz') xs
255span sz xxs = Span sz xxs
256{-# INLINE span #-}
257
258-- | Smart constructor: use it when some block is /inserted/ to
259-- bucket.
260fill :: ChunkSize -> Builder -> Bucket -> Bucket
261fill sz b (Fill sz' b' xs) = Fill (sz + sz') (b <> b') xs
262fill sz b xxs = Fill sz b xxs
263{-# INLINE fill #-}
264
265{-----------------------------------------------------------------------
266-- Bucket queries
267-----------------------------------------------------------------------}
268
269-- | /O(1)/. Test if this bucket is empty.
270null :: Bucket -> Bool
271null Nil = nilInvFailed
272null (Span _ Nil) = True
273null _ = False
274{-# INLINE null #-}
275
276-- | /O(1)/. Test if this bucket is complete.
277full :: Bucket -> Bool
278full Nil = nilInvFailed
279full (Fill _ _ Nil) = True
280full _ = False
281{-# INLINE full #-}
282
283-- | /O(n)/. Total size of the incompleted piece.
284size :: Bucket -> PieceSize
285size Nil = nilInvFailed
286size bkt = go bkt
287 where
288 go Nil = 0
289 go (Span sz xs) = sz + go xs
290 go (Fill sz _ xs) = sz + go xs
291
292-- | /O(n)/. List incomplete blocks to download. If some block have
293-- size more than the specified 'BlockSize' then block is split into
294-- smaller blocks to satisfy given 'BlockSize'. Small (for
295-- e.g. trailing) blocks is not ignored, but returned in-order.
296spans :: BlockSize -> Bucket -> [(BlockOffset, BlockSize)]
297spans expectedSize = go 0
298 where
299 go _ Nil = []
300 go off (Span sz xs) = listChunks off sz ++ go (off + sz) xs
301 go off (Fill sz _ xs) = go (off + sz) xs
302
303 listChunks off restSize
304 | restSize <= 0 = []
305 | otherwise = (off, blkSize)
306 : listChunks (off + blkSize) (restSize - blkSize)
307 where
308 blkSize = min expectedSize restSize
309
310{-----------------------------------------------------------------------
311-- Bucket contstruction
312-----------------------------------------------------------------------}
313
314-- | /O(1)/. A new empty bucket capable to alloof specified size.
315empty :: PieceSize -> Bucket
316empty sz
317 | sz < 0 = error "empty: Bucket size must be a non-negative value"
318 | otherwise = Span sz Nil
319{-# INLINE empty #-}
320
321insertSpan :: Pos -> BS.ByteString -> ChunkSize -> Bucket -> Bucket
322insertSpan !pos !bs !span_sz !xs =
323 let pref_len = pos
324 fill_len = span_sz - pos `min` BS.length bs
325 suff_len = (span_sz - pos) - fill_len
326 in mkSpan pref_len $
327 fill fill_len (byteString (BS.take fill_len bs)) $
328 mkSpan suff_len $
329 xs
330 where
331 mkSpan 0 xs = xs
332 mkSpan sz xs = Span sz xs
333
334-- | /O(n)/. Insert a strict bytestring at specified position.
335--
336-- Best case: if blocks are inserted in sequential order, then this
337-- operation should take /O(1)/.
338--
339insert :: Pos -> BS.ByteString -> Bucket -> Bucket
340insert _ _ Nil = nilInvFailed
341insert dstPos bs bucket = go 0 bucket
342 where
343 intersects curPos sz = dstPos >= curPos && dstPos <= curPos + sz
344
345 go _ Nil = Nil
346 go curPos (Span sz xs)
347 | intersects curPos sz = insertSpan (dstPos - curPos) bs sz xs
348 | otherwise = span sz (go (curPos + sz) xs)
349 go curPos bkt @ (Fill sz br xs)
350 | intersects curPos sz = bkt
351 | otherwise = fill sz br (go (curPos + sz) xs)
352
353fromList :: PieceSize -> [(Pos, BS.ByteString)] -> Bucket
354fromList s = L.foldr (uncurry Network.BitTorrent.Exchange.Block.insert)
355 (Network.BitTorrent.Exchange.Block.empty s)
356
357-- TODO zero-copy
358insertLazy :: Pos -> BL.ByteString -> Bucket -> Bucket
359insertLazy pos bl = Network.BitTorrent.Exchange.Block.insert pos (BL.toStrict bl)
360
361-- | /O(n)/.
362merge :: Bucket -> Bucket -> Bucket
363merge = error "Bucket.merge: not implemented"
364
365-- | /O(1)/.
366toPiece :: Bucket -> Maybe BL.ByteString
367toPiece Nil = nilInvFailed
368toPiece (Fill _ b Nil) = Just (toLazyByteString b)
369toPiece _ = Nothing