summaryrefslogtreecommitdiff
path: root/src/Data/Torrent/Piece.hs
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2014-04-26 07:42:57 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2014-04-26 07:42:57 +0400
commita7fda9d39ed82cb9d3ad0c28e76e88e59539a492 (patch)
tree925183a691bbb57ca5f7140614e1fdbc610b3b1e /src/Data/Torrent/Piece.hs
parent4587ffd5406162bb06a6549ffd2ff277e0a93916 (diff)
parent85bf8475bbbce79b1bedde641192fa945614283d (diff)
Merge branch 'tidy' into dev
Diffstat (limited to 'src/Data/Torrent/Piece.hs')
-rw-r--r--src/Data/Torrent/Piece.hs232
1 files changed, 0 insertions, 232 deletions
diff --git a/src/Data/Torrent/Piece.hs b/src/Data/Torrent/Piece.hs
deleted file mode 100644
index d4b2c399..00000000
--- a/src/Data/Torrent/Piece.hs
+++ /dev/null
@@ -1,232 +0,0 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2013
3-- License : BSD3
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8-- Pieces are used to validate torrent content.
9--
10{-# LANGUAGE TemplateHaskell #-}
11{-# LANGUAGE DeriveDataTypeable #-}
12{-# LANGUAGE DeriveFunctor #-}
13{-# LANGUAGE GeneralizedNewtypeDeriving #-}
14module Data.Torrent.Piece
15 ( -- * Piece attributes
16 PieceIx
17 , PieceCount
18 , PieceSize
19 , minPieceSize
20 , maxPieceSize
21 , defaultPieceSize
22 , PieceHash
23
24 -- * Piece data
25 , Piece (..)
26 , pieceSize
27 , hashPiece
28
29 -- * Piece control
30 , HashList (..)
31 , PieceInfo (..)
32 , pieceCount
33
34 -- * Lens
35 , pieceLength
36 , pieceHashes
37
38 -- * Validation
39 , pieceHash
40 , checkPieceLazy
41
42 -- * Internal
43 , getPieceInfo
44 , putPieceInfo
45 ) where
46
47import Control.DeepSeq
48import Control.Lens
49import qualified Crypto.Hash.SHA1 as SHA1
50import Data.BEncode
51import Data.BEncode.Types
52import Data.Bits
53import Data.Bits.Extras
54import Data.ByteString as BS
55import qualified Data.ByteString.Lazy as BL
56import qualified Data.ByteString.Base64 as Base64
57import Data.Default
58import Data.Int
59import Data.Text.Encoding as T
60import Data.Typeable
61import Text.PrettyPrint
62import Text.PrettyPrint.Class
63
64
65-- TODO add torrent file validation
66class Lint a where
67 lint :: a -> Either String a
68
69--class Validation a where
70-- validate :: PieceInfo -> Piece a -> Bool
71
72{-----------------------------------------------------------------------
73-- Piece attributes
74-----------------------------------------------------------------------}
75
76-- | Zero-based index of piece in torrent content.
77type PieceIx = Int
78
79-- | Size of piece in bytes. Should be a power of 2.
80--
81-- NOTE: Have max and min size constrained to wide used
82-- semi-standard values. This bounds should be used to make decision
83-- about piece size for new torrents.
84--
85type PieceSize = Int
86
87-- | Number of pieces in torrent or a part of torrent.
88type PieceCount = Int
89
90defaultBlockSize :: Int
91defaultBlockSize = 16 * 1024
92
93-- | Optimal number of pieces in torrent.
94optimalPieceCount :: PieceCount
95optimalPieceCount = 1000
96{-# INLINE optimalPieceCount #-}
97
98-- | Piece size should not be less than this value.
99minPieceSize :: Int
100minPieceSize = defaultBlockSize * 4
101{-# INLINE minPieceSize #-}
102
103-- | To prevent transfer degradation piece size should not exceed this
104-- value.
105maxPieceSize :: Int
106maxPieceSize = 4 * 1024 * 1024
107{-# INLINE maxPieceSize #-}
108
109toPow2 :: Int -> Int
110toPow2 x = bit $ fromIntegral (leadingZeros (0 :: Int) - leadingZeros x)
111
112-- | Find the optimal piece size for a given torrent size.
113defaultPieceSize :: Int64 -> Int
114defaultPieceSize x = max minPieceSize $ min maxPieceSize $ toPow2 pc
115 where
116 pc = fromIntegral (x `div` fromIntegral optimalPieceCount)
117
118{-----------------------------------------------------------------------
119-- Piece data
120-----------------------------------------------------------------------}
121
122type PieceHash = ByteString
123
124hashsize :: Int
125hashsize = 20
126{-# INLINE hashsize #-}
127
128-- TODO check if pieceLength is power of 2
129-- | Piece payload should be strict or lazy bytestring.
130data Piece a = Piece
131 { -- | Zero-based piece index in torrent.
132 pieceIndex :: {-# UNPACK #-} !PieceIx
133
134 -- | Payload.
135 , pieceData :: !a
136 } deriving (Show, Read, Eq, Functor, Typeable)
137
138instance NFData (Piece a)
139
140-- | Payload bytes are omitted.
141instance Pretty (Piece a) where
142 pretty Piece {..} = "Piece" <+> braces ("index" <+> "=" <+> int pieceIndex)
143
144-- | Get size of piece in bytes.
145pieceSize :: Piece BL.ByteString -> PieceSize
146pieceSize Piece {..} = fromIntegral (BL.length pieceData)
147
148-- | Get piece hash.
149hashPiece :: Piece BL.ByteString -> PieceHash
150hashPiece Piece {..} = SHA1.hashlazy pieceData
151
152{-----------------------------------------------------------------------
153-- Piece control
154-----------------------------------------------------------------------}
155
156-- | A flat array of SHA1 hash for each piece.
157newtype HashList = HashList { unHashList :: ByteString }
158 deriving (Show, Read, Eq, BEncode, Typeable)
159
160-- | Empty hash list.
161instance Default HashList where
162 def = HashList ""
163
164-- | Part of torrent file used for torrent content validation.
165data PieceInfo = PieceInfo
166 { piPieceLength :: {-# UNPACK #-} !PieceSize
167 -- ^ Number of bytes in each piece.
168
169 , piPieceHashes :: !HashList
170 -- ^ Concatenation of all 20-byte SHA1 hash values.
171 } deriving (Show, Read, Eq, Typeable)
172
173-- | Number of bytes in each piece.
174makeLensesFor [("piPieceLength", "pieceLength")] ''PieceInfo
175
176-- | Concatenation of all 20-byte SHA1 hash values.
177makeLensesFor [("piPieceHashes", "pieceHashes")] ''PieceInfo
178
179instance NFData PieceInfo
180
181instance Default PieceInfo where
182 def = PieceInfo 1 def
183
184instance Lint PieceInfo where
185 lint pinfo @ PieceInfo {..}
186 | BS.length (unHashList piPieceHashes) `rem` hashsize == 0
187 , piPieceLength >= 0 = return pinfo
188 | otherwise = Left undefined
189
190
191putPieceInfo :: PieceInfo -> BDict -> BDict
192putPieceInfo PieceInfo {..} cont =
193 "piece length" .=! piPieceLength
194 .: "pieces" .=! piPieceHashes
195 .: cont
196
197getPieceInfo :: Get PieceInfo
198getPieceInfo = do
199 PieceInfo <$>! "piece length"
200 <*>! "pieces"
201
202instance BEncode PieceInfo where
203 toBEncode = toDict . (`putPieceInfo` endDict)
204 fromBEncode = fromDict getPieceInfo
205
206-- | Hashes are omitted.
207instance Pretty PieceInfo where
208 pretty PieceInfo {..} = "Piece size: " <> int piPieceLength
209
210slice :: Int -> Int -> ByteString -> ByteString
211slice start len = BS.take len . BS.drop start
212{-# INLINE slice #-}
213
214-- | Extract validation hash by specified piece index.
215pieceHash :: PieceInfo -> PieceIx -> PieceHash
216pieceHash PieceInfo {..} i = slice (hashsize * i) hashsize (unHashList piPieceHashes)
217
218-- | Find count of pieces in the torrent. If torrent size is not a
219-- multiple of piece size then the count is rounded up.
220pieceCount :: PieceInfo -> PieceCount
221pieceCount PieceInfo {..} = BS.length (unHashList piPieceHashes) `quot` hashsize
222
223-- | Test if this is last piece in torrent content.
224isLastPiece :: PieceInfo -> PieceIx -> Bool
225isLastPiece ci i = pieceCount ci == succ i
226
227-- | Validate piece with metainfo hash.
228checkPieceLazy :: PieceInfo -> Piece BL.ByteString -> Bool
229checkPieceLazy pinfo @ PieceInfo {..} Piece {..}
230 = (fromIntegral (BL.length pieceData) == piPieceLength
231 || isLastPiece pinfo pieceIndex)
232 && SHA1.hashlazy pieceData == pieceHash pinfo pieceIndex