diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2014-04-26 07:42:57 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2014-04-26 07:42:57 +0400 |
commit | a7fda9d39ed82cb9d3ad0c28e76e88e59539a492 (patch) | |
tree | 925183a691bbb57ca5f7140614e1fdbc610b3b1e /src/Data/Torrent/Piece.hs | |
parent | 4587ffd5406162bb06a6549ffd2ff277e0a93916 (diff) | |
parent | 85bf8475bbbce79b1bedde641192fa945614283d (diff) |
Merge branch 'tidy' into dev
Diffstat (limited to 'src/Data/Torrent/Piece.hs')
-rw-r--r-- | src/Data/Torrent/Piece.hs | 232 |
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 #-} | ||
14 | module 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 | |||
47 | import Control.DeepSeq | ||
48 | import Control.Lens | ||
49 | import qualified Crypto.Hash.SHA1 as SHA1 | ||
50 | import Data.BEncode | ||
51 | import Data.BEncode.Types | ||
52 | import Data.Bits | ||
53 | import Data.Bits.Extras | ||
54 | import Data.ByteString as BS | ||
55 | import qualified Data.ByteString.Lazy as BL | ||
56 | import qualified Data.ByteString.Base64 as Base64 | ||
57 | import Data.Default | ||
58 | import Data.Int | ||
59 | import Data.Text.Encoding as T | ||
60 | import Data.Typeable | ||
61 | import Text.PrettyPrint | ||
62 | import Text.PrettyPrint.Class | ||
63 | |||
64 | |||
65 | -- TODO add torrent file validation | ||
66 | class 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. | ||
77 | type 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 | -- | ||
85 | type PieceSize = Int | ||
86 | |||
87 | -- | Number of pieces in torrent or a part of torrent. | ||
88 | type PieceCount = Int | ||
89 | |||
90 | defaultBlockSize :: Int | ||
91 | defaultBlockSize = 16 * 1024 | ||
92 | |||
93 | -- | Optimal number of pieces in torrent. | ||
94 | optimalPieceCount :: PieceCount | ||
95 | optimalPieceCount = 1000 | ||
96 | {-# INLINE optimalPieceCount #-} | ||
97 | |||
98 | -- | Piece size should not be less than this value. | ||
99 | minPieceSize :: Int | ||
100 | minPieceSize = defaultBlockSize * 4 | ||
101 | {-# INLINE minPieceSize #-} | ||
102 | |||
103 | -- | To prevent transfer degradation piece size should not exceed this | ||
104 | -- value. | ||
105 | maxPieceSize :: Int | ||
106 | maxPieceSize = 4 * 1024 * 1024 | ||
107 | {-# INLINE maxPieceSize #-} | ||
108 | |||
109 | toPow2 :: Int -> Int | ||
110 | toPow2 x = bit $ fromIntegral (leadingZeros (0 :: Int) - leadingZeros x) | ||
111 | |||
112 | -- | Find the optimal piece size for a given torrent size. | ||
113 | defaultPieceSize :: Int64 -> Int | ||
114 | defaultPieceSize x = max minPieceSize $ min maxPieceSize $ toPow2 pc | ||
115 | where | ||
116 | pc = fromIntegral (x `div` fromIntegral optimalPieceCount) | ||
117 | |||
118 | {----------------------------------------------------------------------- | ||
119 | -- Piece data | ||
120 | -----------------------------------------------------------------------} | ||
121 | |||
122 | type PieceHash = ByteString | ||
123 | |||
124 | hashsize :: Int | ||
125 | hashsize = 20 | ||
126 | {-# INLINE hashsize #-} | ||
127 | |||
128 | -- TODO check if pieceLength is power of 2 | ||
129 | -- | Piece payload should be strict or lazy bytestring. | ||
130 | data 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 | |||
138 | instance NFData (Piece a) | ||
139 | |||
140 | -- | Payload bytes are omitted. | ||
141 | instance Pretty (Piece a) where | ||
142 | pretty Piece {..} = "Piece" <+> braces ("index" <+> "=" <+> int pieceIndex) | ||
143 | |||
144 | -- | Get size of piece in bytes. | ||
145 | pieceSize :: Piece BL.ByteString -> PieceSize | ||
146 | pieceSize Piece {..} = fromIntegral (BL.length pieceData) | ||
147 | |||
148 | -- | Get piece hash. | ||
149 | hashPiece :: Piece BL.ByteString -> PieceHash | ||
150 | hashPiece Piece {..} = SHA1.hashlazy pieceData | ||
151 | |||
152 | {----------------------------------------------------------------------- | ||
153 | -- Piece control | ||
154 | -----------------------------------------------------------------------} | ||
155 | |||
156 | -- | A flat array of SHA1 hash for each piece. | ||
157 | newtype HashList = HashList { unHashList :: ByteString } | ||
158 | deriving (Show, Read, Eq, BEncode, Typeable) | ||
159 | |||
160 | -- | Empty hash list. | ||
161 | instance Default HashList where | ||
162 | def = HashList "" | ||
163 | |||
164 | -- | Part of torrent file used for torrent content validation. | ||
165 | data 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. | ||
174 | makeLensesFor [("piPieceLength", "pieceLength")] ''PieceInfo | ||
175 | |||
176 | -- | Concatenation of all 20-byte SHA1 hash values. | ||
177 | makeLensesFor [("piPieceHashes", "pieceHashes")] ''PieceInfo | ||
178 | |||
179 | instance NFData PieceInfo | ||
180 | |||
181 | instance Default PieceInfo where | ||
182 | def = PieceInfo 1 def | ||
183 | |||
184 | instance 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 | |||
191 | putPieceInfo :: PieceInfo -> BDict -> BDict | ||
192 | putPieceInfo PieceInfo {..} cont = | ||
193 | "piece length" .=! piPieceLength | ||
194 | .: "pieces" .=! piPieceHashes | ||
195 | .: cont | ||
196 | |||
197 | getPieceInfo :: Get PieceInfo | ||
198 | getPieceInfo = do | ||
199 | PieceInfo <$>! "piece length" | ||
200 | <*>! "pieces" | ||
201 | |||
202 | instance BEncode PieceInfo where | ||
203 | toBEncode = toDict . (`putPieceInfo` endDict) | ||
204 | fromBEncode = fromDict getPieceInfo | ||
205 | |||
206 | -- | Hashes are omitted. | ||
207 | instance Pretty PieceInfo where | ||
208 | pretty PieceInfo {..} = "Piece size: " <> int piPieceLength | ||
209 | |||
210 | slice :: Int -> Int -> ByteString -> ByteString | ||
211 | slice start len = BS.take len . BS.drop start | ||
212 | {-# INLINE slice #-} | ||
213 | |||
214 | -- | Extract validation hash by specified piece index. | ||
215 | pieceHash :: PieceInfo -> PieceIx -> PieceHash | ||
216 | pieceHash 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. | ||
220 | pieceCount :: PieceInfo -> PieceCount | ||
221 | pieceCount PieceInfo {..} = BS.length (unHashList piPieceHashes) `quot` hashsize | ||
222 | |||
223 | -- | Test if this is last piece in torrent content. | ||
224 | isLastPiece :: PieceInfo -> PieceIx -> Bool | ||
225 | isLastPiece ci i = pieceCount ci == succ i | ||
226 | |||
227 | -- | Validate piece with metainfo hash. | ||
228 | checkPieceLazy :: PieceInfo -> Piece BL.ByteString -> Bool | ||
229 | checkPieceLazy pinfo @ PieceInfo {..} Piece {..} | ||
230 | = (fromIntegral (BL.length pieceData) == piPieceLength | ||
231 | || isLastPiece pinfo pieceIndex) | ||
232 | && SHA1.hashlazy pieceData == pieceHash pinfo pieceIndex | ||