diff options
Diffstat (limited to 'dht/bittorrent/src/Network/BitTorrent/Exchange/Block.hs')
-rw-r--r-- | dht/bittorrent/src/Network/BitTorrent/Exchange/Block.hs | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/dht/bittorrent/src/Network/BitTorrent/Exchange/Block.hs b/dht/bittorrent/src/Network/BitTorrent/Exchange/Block.hs new file mode 100644 index 00000000..bc9a3d24 --- /dev/null +++ b/dht/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 #-} | ||
16 | module 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 | |||
58 | import Prelude hiding (span) | ||
59 | import Control.Applicative | ||
60 | import Data.ByteString as BS hiding (span) | ||
61 | import Data.ByteString.Lazy as BL hiding (span) | ||
62 | import Data.ByteString.Lazy.Builder as BS | ||
63 | import Data.Default | ||
64 | import Data.Monoid | ||
65 | import Data.List as L hiding (span) | ||
66 | import Data.Serialize as S | ||
67 | import Data.Typeable | ||
68 | import Numeric | ||
69 | import Text.PrettyPrint as PP hiding ((<>)) | ||
70 | import Text.PrettyPrint.HughesPJClass hiding ((<>),($$)) | ||
71 | |||
72 | import 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. | ||
80 | type 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 | -- | ||
86 | type BlockSize = Int | ||
87 | |||
88 | -- | Number of block in a piece of a torrent. Used to distinguish | ||
89 | -- block count from piece count. | ||
90 | type 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 | -- | ||
96 | defaultTransferSize :: BlockSize | ||
97 | defaultTransferSize = 16 * 1024 | ||
98 | |||
99 | {----------------------------------------------------------------------- | ||
100 | Block Index | ||
101 | -----------------------------------------------------------------------} | ||
102 | |||
103 | -- | BlockIx correspond. | ||
104 | data 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. | ||
116 | instance Default BlockIx where | ||
117 | def = BlockIx 0 0 defaultTransferSize | ||
118 | |||
119 | getInt :: S.Get Int | ||
120 | getInt = fromIntegral <$> S.getWord32be | ||
121 | {-# INLINE getInt #-} | ||
122 | |||
123 | putInt :: S.Putter Int | ||
124 | putInt = S.putWord32be . fromIntegral | ||
125 | {-# INLINE putInt #-} | ||
126 | |||
127 | instance 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 | |||
140 | instance 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. | ||
147 | blockIxRange :: (Num a, Integral a) => PieceSize -> BlockIx -> (a, a) | ||
148 | blockIxRange 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 | |||
159 | data 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. | ||
171 | instance Pretty (Block BL.ByteString) where | ||
172 | pPrint = pPrint . blockIx | ||
173 | {-# INLINE pPrint #-} | ||
174 | |||
175 | -- | Get size of block /payload/ in bytes. | ||
176 | blockSize :: Block BL.ByteString -> BlockSize | ||
177 | blockSize = fromIntegral . BL.length . blkData | ||
178 | {-# INLINE blockSize #-} | ||
179 | |||
180 | -- | Get block index of a block. | ||
181 | blockIx :: Block BL.ByteString -> BlockIx | ||
182 | blockIx = BlockIx <$> blkPiece <*> blkOffset <*> blockSize | ||
183 | |||
184 | -- | Get location of payload bytes in the torrent content. | ||
185 | blockRange :: (Num a, Integral a) | ||
186 | => PieceSize -> Block BL.ByteString -> (a, a) | ||
187 | blockRange piSize = blockIxRange piSize . blockIx | ||
188 | {-# INLINE blockRange #-} | ||
189 | |||
190 | -- | Test if a block can be safely turned into a piece. | ||
191 | isPiece :: PieceSize -> Block BL.ByteString -> Bool | ||
192 | isPiece pieceLen blk @ (Block i offset _) = | ||
193 | offset == 0 && blockSize blk == pieceLen && i >= 0 | ||
194 | {-# INLINE isPiece #-} | ||
195 | |||
196 | -- | First block in the piece. | ||
197 | leadingBlock :: PieceIx -> BlockSize -> BlockIx | ||
198 | leadingBlock pix blockSize = BlockIx | ||
199 | { ixPiece = pix | ||
200 | , ixOffset = 0 | ||
201 | , ixLength = blockSize | ||
202 | } | ||
203 | {-# INLINE leadingBlock #-} | ||
204 | |||
205 | {----------------------------------------------------------------------- | ||
206 | -- Bucket | ||
207 | -----------------------------------------------------------------------} | ||
208 | |||
209 | type Pos = Int | ||
210 | type ChunkSize = Int | ||
211 | |||
212 | -- | A sparse set of blocks used to represent an /in progress/ piece. | ||
213 | data Bucket | ||
214 | = Nil | ||
215 | | Span {-# UNPACK #-} !ChunkSize !Bucket | ||
216 | | Fill {-# UNPACK #-} !ChunkSize !Builder !Bucket | ||
217 | |||
218 | instance 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'. | ||
226 | nilInvFailed :: a | ||
227 | nilInvFailed = error "Nil: bucket invariant failed" | ||
228 | |||
229 | valid :: Bucket -> Bool | ||
230 | valid = 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 | |||
243 | instance 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. | ||
253 | span :: ChunkSize -> Bucket -> Bucket | ||
254 | span sz (Span sz' xs) = Span (sz + sz') xs | ||
255 | span sz xxs = Span sz xxs | ||
256 | {-# INLINE span #-} | ||
257 | |||
258 | -- | Smart constructor: use it when some block is /inserted/ to | ||
259 | -- bucket. | ||
260 | fill :: ChunkSize -> Builder -> Bucket -> Bucket | ||
261 | fill sz b (Fill sz' b' xs) = Fill (sz + sz') (b <> b') xs | ||
262 | fill 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. | ||
270 | null :: Bucket -> Bool | ||
271 | null Nil = nilInvFailed | ||
272 | null (Span _ Nil) = True | ||
273 | null _ = False | ||
274 | {-# INLINE null #-} | ||
275 | |||
276 | -- | /O(1)/. Test if this bucket is complete. | ||
277 | full :: Bucket -> Bool | ||
278 | full Nil = nilInvFailed | ||
279 | full (Fill _ _ Nil) = True | ||
280 | full _ = False | ||
281 | {-# INLINE full #-} | ||
282 | |||
283 | -- | /O(n)/. Total size of the incompleted piece. | ||
284 | size :: Bucket -> PieceSize | ||
285 | size Nil = nilInvFailed | ||
286 | size 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. | ||
296 | spans :: BlockSize -> Bucket -> [(BlockOffset, BlockSize)] | ||
297 | spans 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. | ||
315 | empty :: PieceSize -> Bucket | ||
316 | empty sz | ||
317 | | sz < 0 = error "empty: Bucket size must be a non-negative value" | ||
318 | | otherwise = Span sz Nil | ||
319 | {-# INLINE empty #-} | ||
320 | |||
321 | insertSpan :: Pos -> BS.ByteString -> ChunkSize -> Bucket -> Bucket | ||
322 | insertSpan !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 | -- | ||
339 | insert :: Pos -> BS.ByteString -> Bucket -> Bucket | ||
340 | insert _ _ Nil = nilInvFailed | ||
341 | insert 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 | |||
353 | fromList :: PieceSize -> [(Pos, BS.ByteString)] -> Bucket | ||
354 | fromList s = L.foldr (uncurry Network.BitTorrent.Exchange.Block.insert) | ||
355 | (Network.BitTorrent.Exchange.Block.empty s) | ||
356 | |||
357 | -- TODO zero-copy | ||
358 | insertLazy :: Pos -> BL.ByteString -> Bucket -> Bucket | ||
359 | insertLazy pos bl = Network.BitTorrent.Exchange.Block.insert pos (BL.toStrict bl) | ||
360 | |||
361 | -- | /O(n)/. | ||
362 | merge :: Bucket -> Bucket -> Bucket | ||
363 | merge = error "Bucket.merge: not implemented" | ||
364 | |||
365 | -- | /O(1)/. | ||
366 | toPiece :: Bucket -> Maybe BL.ByteString | ||
367 | toPiece Nil = nilInvFailed | ||
368 | toPiece (Fill _ b Nil) = Just (toLazyByteString b) | ||
369 | toPiece _ = Nothing | ||