diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2014-04-04 21:44:18 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2014-04-04 21:44:18 +0400 |
commit | 052bed30a3d83aa8fb7b8b42509ad96f573439af (patch) | |
tree | 106b44c06226595870c3bf54f890824eec2b7f0e /src/Data | |
parent | 88ef120511caae5ed74a48a87617b43aec4b7f76 (diff) |
Move HashList to Torrent module
Diffstat (limited to 'src/Data')
-rw-r--r-- | src/Data/Torrent.hs | 199 | ||||
-rw-r--r-- | src/Data/Torrent/Bitfield.hs | 2 | ||||
-rw-r--r-- | src/Data/Torrent/Piece.hs | 232 |
3 files changed, 199 insertions, 234 deletions
diff --git a/src/Data/Torrent.hs b/src/Data/Torrent.hs index 701da9dd..98d6f94e 100644 --- a/src/Data/Torrent.hs +++ b/src/Data/Torrent.hs | |||
@@ -86,6 +86,34 @@ module Data.Torrent | |||
86 | -- ** Internal | 86 | -- ** Internal |
87 | , sizeInBase | 87 | , sizeInBase |
88 | 88 | ||
89 | -- * Pieces | ||
90 | -- ** Attributes | ||
91 | , PieceIx | ||
92 | , PieceCount | ||
93 | , PieceSize | ||
94 | , minPieceSize | ||
95 | , maxPieceSize | ||
96 | , defaultPieceSize | ||
97 | , PieceHash | ||
98 | |||
99 | -- ** Piece data | ||
100 | , Piece (..) | ||
101 | , pieceSize | ||
102 | , hashPiece | ||
103 | |||
104 | -- ** Piece control | ||
105 | , HashList (..) | ||
106 | , PieceInfo (..) | ||
107 | , pieceCount | ||
108 | |||
109 | -- ** Lens | ||
110 | , pieceLength | ||
111 | , pieceHashes | ||
112 | |||
113 | -- ** Validation | ||
114 | , pieceHash | ||
115 | , checkPieceLazy | ||
116 | |||
89 | -- * Info dictionary | 117 | -- * Info dictionary |
90 | , InfoDict (..) | 118 | , InfoDict (..) |
91 | , infoDictionary | 119 | , infoDictionary |
@@ -133,8 +161,11 @@ import Control.Exception | |||
133 | import Control.Lens hiding (unsnoc) | 161 | import Control.Lens hiding (unsnoc) |
134 | import Control.Monad | 162 | import Control.Monad |
135 | import qualified Crypto.Hash.SHA1 as C | 163 | import qualified Crypto.Hash.SHA1 as C |
164 | import qualified Crypto.Hash.SHA1 as SHA1 | ||
136 | import Data.BEncode as BE | 165 | import Data.BEncode as BE |
137 | import Data.BEncode.Types as BE | 166 | import Data.BEncode.Types as BE |
167 | import Data.Bits | ||
168 | import Data.Bits.Extras | ||
138 | import Data.ByteString as BS | 169 | import Data.ByteString as BS |
139 | import Data.ByteString.Base16 as Base16 | 170 | import Data.ByteString.Base16 as Base16 |
140 | import Data.ByteString.Base32 as Base32 | 171 | import Data.ByteString.Base32 as Base32 |
@@ -146,6 +177,7 @@ import Data.Convertible | |||
146 | import Data.Default | 177 | import Data.Default |
147 | import Data.Foldable as F | 178 | import Data.Foldable as F |
148 | import Data.Hashable as Hashable | 179 | import Data.Hashable as Hashable |
180 | import Data.Int | ||
149 | import qualified Data.List as L | 181 | import qualified Data.List as L |
150 | import Data.Map as M | 182 | import Data.Map as M |
151 | import Data.Maybe | 183 | import Data.Maybe |
@@ -166,7 +198,6 @@ import Text.PrettyPrint.Class | |||
166 | import System.FilePath | 198 | import System.FilePath |
167 | import System.Posix.Types | 199 | import System.Posix.Types |
168 | 200 | ||
169 | import Data.Torrent.Piece | ||
170 | import Network.BitTorrent.Core.NodeInfo | 201 | import Network.BitTorrent.Core.NodeInfo |
171 | 202 | ||
172 | 203 | ||
@@ -527,6 +558,171 @@ sizeInBase n b = fromIntegral (n `div` fromIntegral b) + align | |||
527 | {-# SPECIALIZE sizeInBase :: Integer -> Int -> Int #-} | 558 | {-# SPECIALIZE sizeInBase :: Integer -> Int -> Int #-} |
528 | 559 | ||
529 | {----------------------------------------------------------------------- | 560 | {----------------------------------------------------------------------- |
561 | -- Piece attributes | ||
562 | -----------------------------------------------------------------------} | ||
563 | |||
564 | -- | Zero-based index of piece in torrent content. | ||
565 | type PieceIx = Int | ||
566 | |||
567 | -- | Size of piece in bytes. Should be a power of 2. | ||
568 | -- | ||
569 | -- NOTE: Have max and min size constrained to wide used | ||
570 | -- semi-standard values. This bounds should be used to make decision | ||
571 | -- about piece size for new torrents. | ||
572 | -- | ||
573 | type PieceSize = Int | ||
574 | |||
575 | -- | Number of pieces in torrent or a part of torrent. | ||
576 | type PieceCount = Int | ||
577 | |||
578 | defaultBlockSize :: Int | ||
579 | defaultBlockSize = 16 * 1024 | ||
580 | |||
581 | -- | Optimal number of pieces in torrent. | ||
582 | optimalPieceCount :: PieceCount | ||
583 | optimalPieceCount = 1000 | ||
584 | {-# INLINE optimalPieceCount #-} | ||
585 | |||
586 | -- | Piece size should not be less than this value. | ||
587 | minPieceSize :: Int | ||
588 | minPieceSize = defaultBlockSize * 4 | ||
589 | {-# INLINE minPieceSize #-} | ||
590 | |||
591 | -- | To prevent transfer degradation piece size should not exceed this | ||
592 | -- value. | ||
593 | maxPieceSize :: Int | ||
594 | maxPieceSize = 4 * 1024 * 1024 | ||
595 | {-# INLINE maxPieceSize #-} | ||
596 | |||
597 | toPow2 :: Int -> Int | ||
598 | toPow2 x = bit $ fromIntegral (leadingZeros (0 :: Int) - leadingZeros x) | ||
599 | |||
600 | -- | Find the optimal piece size for a given torrent size. | ||
601 | defaultPieceSize :: Int64 -> Int | ||
602 | defaultPieceSize x = max minPieceSize $ min maxPieceSize $ toPow2 pc | ||
603 | where | ||
604 | pc = fromIntegral (x `div` fromIntegral optimalPieceCount) | ||
605 | |||
606 | {----------------------------------------------------------------------- | ||
607 | -- Piece data | ||
608 | -----------------------------------------------------------------------} | ||
609 | |||
610 | type PieceHash = ByteString | ||
611 | |||
612 | hashsize :: Int | ||
613 | hashsize = 20 | ||
614 | {-# INLINE hashsize #-} | ||
615 | |||
616 | -- TODO check if pieceLength is power of 2 | ||
617 | -- | Piece payload should be strict or lazy bytestring. | ||
618 | data Piece a = Piece | ||
619 | { -- | Zero-based piece index in torrent. | ||
620 | pieceIndex :: {-# UNPACK #-} !PieceIx | ||
621 | |||
622 | -- | Payload. | ||
623 | , pieceData :: !a | ||
624 | } deriving (Show, Read, Eq, Functor, Typeable) | ||
625 | |||
626 | instance NFData (Piece a) | ||
627 | |||
628 | -- | Payload bytes are omitted. | ||
629 | instance Pretty (Piece a) where | ||
630 | pretty Piece {..} = "Piece" <+> braces ("index" <+> "=" <+> int pieceIndex) | ||
631 | |||
632 | -- | Get size of piece in bytes. | ||
633 | pieceSize :: Piece BL.ByteString -> PieceSize | ||
634 | pieceSize Piece {..} = fromIntegral (BL.length pieceData) | ||
635 | |||
636 | -- | Get piece hash. | ||
637 | hashPiece :: Piece BL.ByteString -> PieceHash | ||
638 | hashPiece Piece {..} = SHA1.hashlazy pieceData | ||
639 | |||
640 | {----------------------------------------------------------------------- | ||
641 | -- Piece control | ||
642 | -----------------------------------------------------------------------} | ||
643 | |||
644 | -- | A flat array of SHA1 hash for each piece. | ||
645 | newtype HashList = HashList { unHashList :: ByteString } | ||
646 | deriving (Show, Read, Eq, BEncode, Typeable) | ||
647 | |||
648 | -- | Empty hash list. | ||
649 | instance Default HashList where | ||
650 | def = HashList "" | ||
651 | |||
652 | -- | Part of torrent file used for torrent content validation. | ||
653 | data PieceInfo = PieceInfo | ||
654 | { piPieceLength :: {-# UNPACK #-} !PieceSize | ||
655 | -- ^ Number of bytes in each piece. | ||
656 | |||
657 | , piPieceHashes :: !HashList | ||
658 | -- ^ Concatenation of all 20-byte SHA1 hash values. | ||
659 | } deriving (Show, Read, Eq, Typeable) | ||
660 | |||
661 | -- | Number of bytes in each piece. | ||
662 | makeLensesFor [("piPieceLength", "pieceLength")] ''PieceInfo | ||
663 | |||
664 | -- | Concatenation of all 20-byte SHA1 hash values. | ||
665 | makeLensesFor [("piPieceHashes", "pieceHashes")] ''PieceInfo | ||
666 | |||
667 | instance NFData PieceInfo | ||
668 | |||
669 | instance Default PieceInfo where | ||
670 | def = PieceInfo 1 def | ||
671 | |||
672 | class Lint a where | ||
673 | lint :: a -> Either String a | ||
674 | |||
675 | instance Lint PieceInfo where | ||
676 | lint pinfo @ PieceInfo {..} | ||
677 | | BS.length (unHashList piPieceHashes) `rem` hashsize == 0 | ||
678 | , piPieceLength >= 0 = return pinfo | ||
679 | | otherwise = Left undefined | ||
680 | |||
681 | |||
682 | putPieceInfo :: Data.Torrent.Put PieceInfo | ||
683 | putPieceInfo PieceInfo {..} cont = | ||
684 | "piece length" .=! piPieceLength | ||
685 | .: "pieces" .=! piPieceHashes | ||
686 | .: cont | ||
687 | |||
688 | getPieceInfo :: BE.Get PieceInfo | ||
689 | getPieceInfo = do | ||
690 | PieceInfo <$>! "piece length" | ||
691 | <*>! "pieces" | ||
692 | |||
693 | instance BEncode PieceInfo where | ||
694 | toBEncode = toDict . (`putPieceInfo` endDict) | ||
695 | fromBEncode = fromDict getPieceInfo | ||
696 | |||
697 | -- | Hashes are omitted. | ||
698 | instance Pretty PieceInfo where | ||
699 | pretty PieceInfo {..} = "Piece size: " <> int piPieceLength | ||
700 | |||
701 | slice :: Int -> Int -> ByteString -> ByteString | ||
702 | slice start len = BS.take len . BS.drop start | ||
703 | {-# INLINE slice #-} | ||
704 | |||
705 | -- | Extract validation hash by specified piece index. | ||
706 | pieceHash :: PieceInfo -> PieceIx -> PieceHash | ||
707 | pieceHash PieceInfo {..} i = slice (hashsize * i) hashsize (unHashList piPieceHashes) | ||
708 | |||
709 | -- | Find count of pieces in the torrent. If torrent size is not a | ||
710 | -- multiple of piece size then the count is rounded up. | ||
711 | pieceCount :: PieceInfo -> PieceCount | ||
712 | pieceCount PieceInfo {..} = BS.length (unHashList piPieceHashes) `quot` hashsize | ||
713 | |||
714 | -- | Test if this is last piece in torrent content. | ||
715 | isLastPiece :: PieceInfo -> PieceIx -> Bool | ||
716 | isLastPiece ci i = pieceCount ci == succ i | ||
717 | |||
718 | -- | Validate piece with metainfo hash. | ||
719 | checkPieceLazy :: PieceInfo -> Piece BL.ByteString -> Bool | ||
720 | checkPieceLazy pinfo @ PieceInfo {..} Piece {..} | ||
721 | = (fromIntegral (BL.length pieceData) == piPieceLength | ||
722 | || isLastPiece pinfo pieceIndex) | ||
723 | && SHA1.hashlazy pieceData == pieceHash pinfo pieceIndex | ||
724 | |||
725 | {----------------------------------------------------------------------- | ||
530 | -- Info dictionary | 726 | -- Info dictionary |
531 | -----------------------------------------------------------------------} | 727 | -----------------------------------------------------------------------} |
532 | 728 | ||
@@ -620,6 +816,7 @@ instance Pretty InfoDict where | |||
620 | {----------------------------------------------------------------------- | 816 | {----------------------------------------------------------------------- |
621 | -- Torrent info | 817 | -- Torrent info |
622 | -----------------------------------------------------------------------} | 818 | -----------------------------------------------------------------------} |
819 | -- TODO add torrent file validation | ||
623 | 820 | ||
624 | -- | Metainfo about particular torrent. | 821 | -- | Metainfo about particular torrent. |
625 | data Torrent = Torrent | 822 | data Torrent = Torrent |
diff --git a/src/Data/Torrent/Bitfield.hs b/src/Data/Torrent/Bitfield.hs index b65f058b..ff701d75 100644 --- a/src/Data/Torrent/Bitfield.hs +++ b/src/Data/Torrent/Bitfield.hs | |||
@@ -92,7 +92,7 @@ import Data.List (foldl') | |||
92 | import Data.Monoid | 92 | import Data.Monoid |
93 | import Data.Ratio | 93 | import Data.Ratio |
94 | 94 | ||
95 | import Data.Torrent.Piece | 95 | import Data.Torrent |
96 | 96 | ||
97 | -- TODO cache some operations | 97 | -- TODO cache some operations |
98 | 98 | ||
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 | ||