summaryrefslogtreecommitdiff
path: root/src/Data/Torrent/Piece.hs
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2013-10-31 11:25:59 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2013-10-31 11:25:59 +0400
commit01cef3fafc27d39d88c94cacdcd8e204c5f66b86 (patch)
tree01040aca19e49f4e7937383fef53b8c82bcec12b /src/Data/Torrent/Piece.hs
parentc1fec260f47084300ac30de2e43d52966316a2c7 (diff)
Merge bittorrent package with torrent-content
Diffstat (limited to 'src/Data/Torrent/Piece.hs')
-rw-r--r--src/Data/Torrent/Piece.hs203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/Data/Torrent/Piece.hs b/src/Data/Torrent/Piece.hs
new file mode 100644
index 00000000..ea4e6253
--- /dev/null
+++ b/src/Data/Torrent/Piece.hs
@@ -0,0 +1,203 @@
1-- |
2-- Copyright : (c) Sam Truzjan 2013
3-- License : BSD3
4-- Maintainer : pxqr.sta@gmail.com
5-- Stability : experimental
6-- Portability : portable
7--
8{-# LANGUAGE TemplateHaskell #-}
9{-# LANGUAGE DeriveDataTypeable #-}
10{-# LANGUAGE GeneralizedNewtypeDeriving #-}
11module Data.Torrent.Piece
12 ( -- * Piece attributes
13 -- ** Piece size
14 PieceSize (..)
15 , defaultBlockSize -- TODO use data-default
16 , optimalPieceCount
17 , defaultPieceSize -- TODO use data-default
18
19 -- ** Piece index
20 , PieceIx
21
22 -- * Piece data
23 , Piece (..)
24 , ppPiece
25
26 -- * Piece control
27 , PieceInfo (..)
28 , ppPieceInfo
29 , pieceLength
30 , pieceHashes
31 , pieceHash
32 , pieceCount
33 , checkPieceLazy
34
35
36 -- * Internal
37 , getPieceInfo
38 , putPieceInfo
39 ) where
40
41import Control.DeepSeq
42import Control.Lens
43import qualified Crypto.Hash.SHA1 as SHA1
44import Data.Aeson (ToJSON, FromJSON)
45import Data.Aeson.TH
46import Data.BEncode
47import Data.BEncode.Types
48import Data.Bits
49import Data.Bits.Extras
50import Data.ByteString as BS
51import qualified Data.ByteString.Lazy as BL
52import Data.Char
53import Data.Int
54import Data.List as L
55import Data.Typeable
56import Text.PrettyPrint
57
58
59class Lint a where
60 lint :: a -> Either String a
61
62type PieceCount = Int -- TODO newtype
63type PieceIx = Int -- TODO remove
64
65newtype PieceIndex = PieceIndex Int
66
67-- | An int used to denote piece size.
68newtype PieceSize = PieceSize Int
69 deriving (Show, Read, Typeable
70 , Eq, Ord, Enum
71 , Num, Real, Integral
72 , BEncode, ToJSON, FromJSON
73 )
74
75-- | Widely used semi-official block size.
76defaultBlockSize :: Int
77defaultBlockSize = 16 * 1024
78
79maxPieceSize :: Int
80maxPieceSize = 4 * 1024 * 1024
81{-# INLINE maxPieceSize #-}
82
83minPieceSize :: Int
84minPieceSize = defaultBlockSize * 4
85{-# INLINE minPieceSize #-}
86
87-- | NOTE: Have max and min size constrained to wide used
88-- semi-standard values. This bounds should be used to make decision
89-- about piece size for new torrents.
90--
91instance Bounded PieceSize where
92 maxBound = PieceSize maxPieceSize
93 {-# INLINE maxBound #-}
94
95 minBound = PieceSize minPieceSize
96 {-# INLINE minBound #-}
97
98-- | TODO
99optimalPieceCount :: Int
100optimalPieceCount = 1000
101{-# INLINE optimalPieceCount #-}
102
103toPow2 :: Int -> Int
104toPow2 x = bit $ fromIntegral (leadingZeros (0 :: Int) - leadingZeros x)
105
106-- | Find the optimal piece size for a given torrent size.
107defaultPieceSize :: Int64 -> Int
108defaultPieceSize x = max minPieceSize $ min maxPieceSize $ toPow2 pc
109 where
110 pc = fromIntegral (x `div` fromIntegral optimalPieceCount)
111
112-- TODO check if pieceLength is power of 2
113-- | Piece payload should be strict or lazy bytestring.
114data Piece a = Piece
115 { -- | Zero-based piece index in torrent. TODO how pieces are indexed?
116 pieceIndex :: {-# UNPACK #-} !PieceIx
117 -- | Payload.
118 , pieceData :: !a
119 } deriving (Show, Read, Eq, Typeable)
120
121$(deriveJSON (L.map toLower . L.dropWhile isLower) ''Piece)
122
123instance NFData (Piece a)
124
125-- | Format piece in human readable form. Payload bytes are omitted.
126ppPiece :: Piece a -> Doc
127ppPiece Piece {..}
128 = "Piece" <+> braces ("index" <+> "=" <+> int pieceIndex)
129
130data PieceInfo = PieceInfo
131 { piPieceLength :: {-# UNPACK #-} !PieceSize
132 -- ^ Number of bytes in each piece.
133
134 , piPieceHashes :: !ByteString
135 -- ^ Concatenation of all 20-byte SHA1 hash values.
136 } deriving (Show, Read, Eq, Typeable)
137
138$(deriveJSON (L.map toLower . L.dropWhile isLower) ''PieceInfo)
139
140-- | Number of bytes in each piece.
141makeLensesFor [("piPieceLength", "pieceLength")] ''PieceInfo
142
143-- | Concatenation of all 20-byte SHA1 hash values.
144makeLensesFor [("piPieceHashes", "pieceHashes")] ''PieceInfo
145
146instance NFData PieceInfo
147
148instance Lint PieceInfo where
149 lint pinfo @ PieceInfo {..}
150 | BS.length piPieceHashes `rem` hashsize == 0
151 , piPieceLength >= 0 = return pinfo
152 | otherwise = Left undefined
153
154
155putPieceInfo :: PieceInfo -> BDict -> BDict
156putPieceInfo PieceInfo {..} cont =
157 "piece length" .=! piPieceLength
158 .: "pieces" .=! piPieceHashes
159 .: cont
160
161getPieceInfo :: Get PieceInfo
162getPieceInfo = do
163 PieceInfo <$>! "piece length"
164 <*>! "pieces"
165
166instance BEncode PieceInfo where
167 toBEncode = toDict . (`putPieceInfo` endDict)
168 fromBEncode = fromDict getPieceInfo
169
170-- | Format piece info in human readable form. Hashes are omitted.
171ppPieceInfo :: PieceInfo -> Doc
172ppPieceInfo PieceInfo { piPieceLength = PieceSize len } =
173 "PieceInfo" <+> braces ("length" <+> "=" <+> int len)
174
175hashsize :: Int
176hashsize = 20
177{-# INLINE hashsize #-}
178
179slice :: Int -> Int -> ByteString -> ByteString
180slice start len = BS.take len . BS.drop start
181{-# INLINE slice #-}
182
183-- | Extract validation hash by specified piece index.
184pieceHash :: PieceInfo -> PieceIx -> ByteString
185pieceHash PieceInfo {..} i = slice (hashsize * i) hashsize piPieceHashes
186
187-- | Find count of pieces in the torrent. If torrent size is not a
188-- multiple of piece size then the count is rounded up.
189pieceCount :: PieceInfo -> PieceCount
190pieceCount PieceInfo {..} = BS.length piPieceHashes `quot` hashsize
191
192isLastPiece :: PieceInfo -> PieceIx -> Bool
193isLastPiece ci i = pieceCount ci == succ i
194
195class Validation a where
196 validate :: PieceInfo -> Piece a -> Bool
197
198-- | Validate piece with metainfo hash.
199checkPieceLazy :: PieceInfo -> Piece BL.ByteString -> Bool
200checkPieceLazy pinfo @ PieceInfo {..} Piece {..}
201 = (fromIntegral (BL.length pieceData) == piPieceLength
202 || isLastPiece pinfo pieceIndex)
203 && SHA1.hashlazy pieceData == pieceHash pinfo pieceIndex