summaryrefslogtreecommitdiff
path: root/src/Data
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2014-04-04 21:44:18 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2014-04-04 21:44:18 +0400
commit052bed30a3d83aa8fb7b8b42509ad96f573439af (patch)
tree106b44c06226595870c3bf54f890824eec2b7f0e /src/Data
parent88ef120511caae5ed74a48a87617b43aec4b7f76 (diff)
Move HashList to Torrent module
Diffstat (limited to 'src/Data')
-rw-r--r--src/Data/Torrent.hs199
-rw-r--r--src/Data/Torrent/Bitfield.hs2
-rw-r--r--src/Data/Torrent/Piece.hs232
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
133import Control.Lens hiding (unsnoc) 161import Control.Lens hiding (unsnoc)
134import Control.Monad 162import Control.Monad
135import qualified Crypto.Hash.SHA1 as C 163import qualified Crypto.Hash.SHA1 as C
164import qualified Crypto.Hash.SHA1 as SHA1
136import Data.BEncode as BE 165import Data.BEncode as BE
137import Data.BEncode.Types as BE 166import Data.BEncode.Types as BE
167import Data.Bits
168import Data.Bits.Extras
138import Data.ByteString as BS 169import Data.ByteString as BS
139import Data.ByteString.Base16 as Base16 170import Data.ByteString.Base16 as Base16
140import Data.ByteString.Base32 as Base32 171import Data.ByteString.Base32 as Base32
@@ -146,6 +177,7 @@ import Data.Convertible
146import Data.Default 177import Data.Default
147import Data.Foldable as F 178import Data.Foldable as F
148import Data.Hashable as Hashable 179import Data.Hashable as Hashable
180import Data.Int
149import qualified Data.List as L 181import qualified Data.List as L
150import Data.Map as M 182import Data.Map as M
151import Data.Maybe 183import Data.Maybe
@@ -166,7 +198,6 @@ import Text.PrettyPrint.Class
166import System.FilePath 198import System.FilePath
167import System.Posix.Types 199import System.Posix.Types
168 200
169import Data.Torrent.Piece
170import Network.BitTorrent.Core.NodeInfo 201import 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.
565type 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--
573type PieceSize = Int
574
575-- | Number of pieces in torrent or a part of torrent.
576type PieceCount = Int
577
578defaultBlockSize :: Int
579defaultBlockSize = 16 * 1024
580
581-- | Optimal number of pieces in torrent.
582optimalPieceCount :: PieceCount
583optimalPieceCount = 1000
584{-# INLINE optimalPieceCount #-}
585
586-- | Piece size should not be less than this value.
587minPieceSize :: Int
588minPieceSize = defaultBlockSize * 4
589{-# INLINE minPieceSize #-}
590
591-- | To prevent transfer degradation piece size should not exceed this
592-- value.
593maxPieceSize :: Int
594maxPieceSize = 4 * 1024 * 1024
595{-# INLINE maxPieceSize #-}
596
597toPow2 :: Int -> Int
598toPow2 x = bit $ fromIntegral (leadingZeros (0 :: Int) - leadingZeros x)
599
600-- | Find the optimal piece size for a given torrent size.
601defaultPieceSize :: Int64 -> Int
602defaultPieceSize x = max minPieceSize $ min maxPieceSize $ toPow2 pc
603 where
604 pc = fromIntegral (x `div` fromIntegral optimalPieceCount)
605
606{-----------------------------------------------------------------------
607-- Piece data
608-----------------------------------------------------------------------}
609
610type PieceHash = ByteString
611
612hashsize :: Int
613hashsize = 20
614{-# INLINE hashsize #-}
615
616-- TODO check if pieceLength is power of 2
617-- | Piece payload should be strict or lazy bytestring.
618data 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
626instance NFData (Piece a)
627
628-- | Payload bytes are omitted.
629instance Pretty (Piece a) where
630 pretty Piece {..} = "Piece" <+> braces ("index" <+> "=" <+> int pieceIndex)
631
632-- | Get size of piece in bytes.
633pieceSize :: Piece BL.ByteString -> PieceSize
634pieceSize Piece {..} = fromIntegral (BL.length pieceData)
635
636-- | Get piece hash.
637hashPiece :: Piece BL.ByteString -> PieceHash
638hashPiece Piece {..} = SHA1.hashlazy pieceData
639
640{-----------------------------------------------------------------------
641-- Piece control
642-----------------------------------------------------------------------}
643
644-- | A flat array of SHA1 hash for each piece.
645newtype HashList = HashList { unHashList :: ByteString }
646 deriving (Show, Read, Eq, BEncode, Typeable)
647
648-- | Empty hash list.
649instance Default HashList where
650 def = HashList ""
651
652-- | Part of torrent file used for torrent content validation.
653data 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.
662makeLensesFor [("piPieceLength", "pieceLength")] ''PieceInfo
663
664-- | Concatenation of all 20-byte SHA1 hash values.
665makeLensesFor [("piPieceHashes", "pieceHashes")] ''PieceInfo
666
667instance NFData PieceInfo
668
669instance Default PieceInfo where
670 def = PieceInfo 1 def
671
672class Lint a where
673 lint :: a -> Either String a
674
675instance 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
682putPieceInfo :: Data.Torrent.Put PieceInfo
683putPieceInfo PieceInfo {..} cont =
684 "piece length" .=! piPieceLength
685 .: "pieces" .=! piPieceHashes
686 .: cont
687
688getPieceInfo :: BE.Get PieceInfo
689getPieceInfo = do
690 PieceInfo <$>! "piece length"
691 <*>! "pieces"
692
693instance BEncode PieceInfo where
694 toBEncode = toDict . (`putPieceInfo` endDict)
695 fromBEncode = fromDict getPieceInfo
696
697-- | Hashes are omitted.
698instance Pretty PieceInfo where
699 pretty PieceInfo {..} = "Piece size: " <> int piPieceLength
700
701slice :: Int -> Int -> ByteString -> ByteString
702slice start len = BS.take len . BS.drop start
703{-# INLINE slice #-}
704
705-- | Extract validation hash by specified piece index.
706pieceHash :: PieceInfo -> PieceIx -> PieceHash
707pieceHash 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.
711pieceCount :: PieceInfo -> PieceCount
712pieceCount PieceInfo {..} = BS.length (unHashList piPieceHashes) `quot` hashsize
713
714-- | Test if this is last piece in torrent content.
715isLastPiece :: PieceInfo -> PieceIx -> Bool
716isLastPiece ci i = pieceCount ci == succ i
717
718-- | Validate piece with metainfo hash.
719checkPieceLazy :: PieceInfo -> Piece BL.ByteString -> Bool
720checkPieceLazy 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.
625data Torrent = Torrent 822data 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')
92import Data.Monoid 92import Data.Monoid
93import Data.Ratio 93import Data.Ratio
94 94
95import Data.Torrent.Piece 95import 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 #-}
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