summaryrefslogtreecommitdiff
path: root/src/Network
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network')
-rw-r--r--src/Network/BitTorrent/Exchange/Block.hs172
1 files changed, 167 insertions, 5 deletions
diff --git a/src/Network/BitTorrent/Exchange/Block.hs b/src/Network/BitTorrent/Exchange/Block.hs
index ca126acb..d713bcf1 100644
--- a/src/Network/BitTorrent/Exchange/Block.hs
+++ b/src/Network/BitTorrent/Exchange/Block.hs
@@ -7,6 +7,7 @@
7-- 7--
8-- Blocks are used to transfer pieces. 8-- Blocks are used to transfer pieces.
9-- 9--
10{-# LANGUAGE BangPatterns #-}
10{-# LANGUAGE FlexibleInstances #-} 11{-# LANGUAGE FlexibleInstances #-}
11{-# LANGUAGE TemplateHaskell #-} 12{-# LANGUAGE TemplateHaskell #-}
12{-# LANGUAGE DeriveFunctor #-} 13{-# LANGUAGE DeriveFunctor #-}
@@ -29,15 +30,40 @@ module Network.BitTorrent.Exchange.Block
29 , blockSize 30 , blockSize
30 , blockRange 31 , blockRange
31 , isPiece 32 , isPiece
33
34 -- * Block bucket
35 , Bucket
36
37 -- ** Query
38 , Network.BitTorrent.Exchange.Block.null
39 , Network.BitTorrent.Exchange.Block.full
40 , Network.BitTorrent.Exchange.Block.size
41 , Network.BitTorrent.Exchange.Block.spans
42
43 -- ** Construction
44 , Network.BitTorrent.Exchange.Block.empty
45 , Network.BitTorrent.Exchange.Block.insert
46 , Network.BitTorrent.Exchange.Block.merge
47
48 -- ** Rendering
49 , Network.BitTorrent.Exchange.Block.toPiece
50
51 -- ** Debug
52 , Network.BitTorrent.Exchange.Block.valid
32 ) where 53 ) where
33 54
55import Prelude hiding (span)
34import Control.Applicative 56import Control.Applicative
35import Data.Aeson.TH 57import Data.Aeson.TH
36import Data.ByteString.Lazy as BL 58import Data.ByteString as BS hiding (span)
59import Data.ByteString.Lazy as BL hiding (span)
60import Data.ByteString.Lazy.Builder as BS
37import Data.Default 61import Data.Default
62import Data.Monoid
63import Data.List hiding (span)
38import Data.Serialize as S 64import Data.Serialize as S
39import Data.Typeable 65import Data.Typeable
40import Text.PrettyPrint 66import Text.PrettyPrint as PP hiding ((<>))
41import Text.PrettyPrint.Class 67import Text.PrettyPrint.Class
42 68
43import Data.Torrent.JSON 69import Data.Torrent.JSON
@@ -113,9 +139,9 @@ instance Serialize BlockIx where
113 139
114instance Pretty BlockIx where 140instance Pretty BlockIx where
115 pretty BlockIx {..} = 141 pretty BlockIx {..} =
116 "piece = " <> int ixPiece <> "," <+> 142 ("piece = " <> int ixPiece <> ",") <+>
117 "offset = " <> int ixOffset <> "," <+> 143 ("offset = " <> int ixOffset <> ",") <+>
118 "length = " <> int ixLength 144 ("length = " <> int ixLength)
119 145
120-- | Get location of payload bytes in the torrent content. 146-- | Get location of payload bytes in the torrent content.
121blockIxRange :: (Num a, Integral a) => PieceSize -> BlockIx -> (a, a) 147blockIxRange :: (Num a, Integral a) => PieceSize -> BlockIx -> (a, a)
@@ -166,3 +192,139 @@ isPiece :: PieceSize -> Block BL.ByteString -> Bool
166isPiece pieceLen blk @ (Block i offset _) = 192isPiece pieceLen blk @ (Block i offset _) =
167 offset == 0 && blockSize blk == pieceLen && i >= 0 193 offset == 0 && blockSize blk == pieceLen && i >= 0
168{-# INLINE isPiece #-} 194{-# INLINE isPiece #-}
195
196{-----------------------------------------------------------------------
197-- Bucket
198-----------------------------------------------------------------------}
199
200type Pos = Int
201type ChunkSize = Int
202
203-- | A sparse set of blocks used to represent an /in progress/ piece.
204data Bucket
205 = Nil
206 | Span {-# UNPACK #-} !ChunkSize !Bucket
207 | Fill {-# UNPACK #-} !ChunkSize !Builder !Bucket
208
209-- | INVARIANT: 'Nil' should appear only after 'Span' of 'Fill'.
210nilInvFailed :: a
211nilInvFailed = error "Nil: bucket invariant failed"
212
213valid :: Bucket -> Bool
214valid = check Nothing
215 where
216 check Nothing Nil = False -- see 'nilInvFailed'
217 check (Just _) _ = True
218 check prevIsSpan (Span sz xs) =
219 prevIsSpan /= Just True && -- Span n (NotSpan .. ) invariant
220 sz > 0 && -- Span is always non-empty
221 check (Just True) xs
222 check prevIsSpan (Fill sz b xs) =
223 prevIsSpan /= Just True && -- Fill n (NotFill .. ) invariant
224 sz > 0 && -- Fill is always non-empty
225 check (Just False) xs
226
227instance Pretty Bucket where
228 pretty Nil = nilInvFailed
229 pretty bkt = go bkt
230 where
231 go Nil = PP.empty
232 go (Span sz xs) = "Span" <+> PP.int sz <+> go xs
233 go (Fill sz b xs) = "Fill" <+> PP.int sz <+> go xs
234
235-- | Smart constructor: use it when some block is /deleted/ from
236-- bucket.
237span :: ChunkSize -> Bucket -> Bucket
238span sz (Span sz' xs) = Span (sz + sz') xs
239span sz xxs = Span sz xxs
240{-# INLINE span #-}
241
242-- | Smart constructor: use it when some block is /inserted/ to
243-- bucket.
244fill :: ChunkSize -> Builder -> Bucket -> Bucket
245fill sz b (Fill sz' b' xs) = Fill (sz + sz') (b <> b') xs
246fill sz b xxs = Fill sz b xxs
247{-# INLINE fill #-}
248
249{-----------------------------------------------------------------------
250-- Bucket queries
251-----------------------------------------------------------------------}
252
253-- | /O(1)/. Test if this bucket is empty.
254null :: Bucket -> Bool
255null Nil = nilInvFailed
256null (Span _ Nil) = True
257null _ = False
258{-# INLINE null #-}
259
260-- | /O(1)/. Test if this bucket is complete.
261full :: Bucket -> Bool
262full Nil = nilInvFailed
263full (Fill _ _ Nil) = True
264full _ = False
265{-# INLINE full #-}
266
267-- | /O(n)/. Total size of the incompleted piece.
268size :: Bucket -> PieceSize
269size Nil = nilInvFailed
270size bkt = go bkt
271 where
272 go Nil = 0
273 go (Span sz xs) = sz + go xs
274 go (Fill sz _ xs) = sz + go xs
275
276-- | /O(n)/. List incomplete blocks to download.
277spans :: BlockSize -> Bucket -> [(BlockOffset, BlockSize)]
278spans = undefined
279
280{-----------------------------------------------------------------------
281-- Bucket contstruction
282-----------------------------------------------------------------------}
283
284-- | /O(1)/. A new empty bucket capable to alloof specified size.
285empty :: PieceSize -> Bucket
286empty 0 = error "empty: Bucket size is zero"
287empty sz = Span sz Nil
288{-# INLINE empty #-}
289
290insertSpan :: Pos -> BS.ByteString -> ChunkSize -> Bucket -> Bucket
291insertSpan !pos !bs !span_sz !xs =
292 let pref_len = pos
293 fill_len = span_sz - pos `min` BS.length bs
294 suff_len = (span_sz - pos) - fill_len
295 in mkSpan pref_len $
296 fill fill_len (byteString (BS.take fill_len bs)) $
297 mkSpan suff_len $
298 xs
299 where
300 mkSpan 0 xs = xs
301 mkSpan sz xs = Span sz xs
302
303-- | /O(n)/. Insert a strict bytestring at specified position.
304--
305-- Best case: if blocks are inserted in sequential order, then this
306-- operation should take /O(1)/.
307--
308insert :: Pos -> BS.ByteString -> Bucket -> Bucket
309insert _ _ Nil = nilInvFailed
310insert dstPos bs bucket = go 0 bucket
311 where
312 intersects curPos sz = dstPos >= curPos && dstPos <= curPos + sz
313
314 go _ Nil = Nil
315 go curPos (Span sz xs)
316 | intersects curPos sz = insertSpan (dstPos - curPos) bs sz xs
317 | otherwise = span sz (go (curPos + sz) xs)
318 go curPos bkt @ (Fill sz br xs)
319 | intersects curPos sz = bkt
320 | otherwise = fill sz br (go (curPos + sz) xs)
321
322-- | /O(n)/.
323merge :: Bucket -> Bucket -> Bucket
324merge = undefined
325
326-- | /O(1)/.
327toPiece :: Bucket -> Maybe BL.ByteString
328toPiece Nil = nilInvFailed
329toPiece (Fill _ b Nil) = Just (toLazyByteString b)
330toPiece _ = Nothing