summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Truzjan <pxqr.sta@gmail.com>2013-11-29 18:37:42 +0400
committerSam Truzjan <pxqr.sta@gmail.com>2013-11-29 18:37:42 +0400
commit88b590239ad66f8624723beefefa8b0ef56942e1 (patch)
treec5e2e3afcb0ca97d469b371cbfada675a462442c
parentc689257a818c0a7581666f4bdfd4549e52dbd075 (diff)
More safiety in InfoHash convertions
-rw-r--r--src/Data/Torrent/Block.hs23
-rw-r--r--src/Data/Torrent/InfoHash.hs16
-rw-r--r--src/Network/BitTorrent/Exchange/Extension.hs67
-rw-r--r--src/Network/BitTorrent/Exchange/Message.hs225
4 files changed, 132 insertions, 199 deletions
diff --git a/src/Data/Torrent/Block.hs b/src/Data/Torrent/Block.hs
index 987de653..37889a7a 100644
--- a/src/Data/Torrent/Block.hs
+++ b/src/Data/Torrent/Block.hs
@@ -37,9 +37,6 @@ import Data.Aeson.TH
37import qualified Data.ByteString.Lazy as Lazy 37import qualified Data.ByteString.Lazy as Lazy
38import Data.Char 38import Data.Char
39import Data.List as L 39import Data.List as L
40import Data.Binary as B
41import Data.Binary.Get as B
42import Data.Binary.Put as B
43import Data.Serialize as S 40import Data.Serialize as S
44import Text.PrettyPrint 41import Text.PrettyPrint
45import Text.PrettyPrint.Class 42import Text.PrettyPrint.Class
@@ -110,14 +107,6 @@ putInt :: S.Putter Int
110putInt = S.putWord32be . fromIntegral 107putInt = S.putWord32be . fromIntegral
111{-# INLINE putInt #-} 108{-# INLINE putInt #-}
112 109
113getIntB :: B.Get Int
114getIntB = fromIntegral <$> B.getWord32be
115{-# INLINE getIntB #-}
116
117putIntB :: Int -> B.Put
118putIntB = B.putWord32be . fromIntegral
119{-# INLINE putIntB #-}
120
121instance Serialize BlockIx where 110instance Serialize BlockIx where
122 {-# SPECIALIZE instance Serialize BlockIx #-} 111 {-# SPECIALIZE instance Serialize BlockIx #-}
123 get = BlockIx <$> getInt 112 get = BlockIx <$> getInt
@@ -131,18 +120,6 @@ instance Serialize BlockIx where
131 putInt ixLength 120 putInt ixLength
132 {-# INLINE put #-} 121 {-# INLINE put #-}
133 122
134instance Binary BlockIx where
135 {-# SPECIALIZE instance Binary BlockIx #-}
136 get = BlockIx <$> getIntB
137 <*> getIntB
138 <*> getIntB
139 {-# INLINE get #-}
140
141 put BlockIx {..} = do
142 putIntB ixPiece
143 putIntB ixOffset
144 putIntB ixLength
145
146instance Pretty BlockIx where 123instance Pretty BlockIx where
147 pretty BlockIx {..} = 124 pretty BlockIx {..} =
148 "piece = " <> int ixPiece <> "," <+> 125 "piece = " <> int ixPiece <> "," <+>
diff --git a/src/Data/Torrent/InfoHash.hs b/src/Data/Torrent/InfoHash.hs
index 6313948b..15682250 100644
--- a/src/Data/Torrent/InfoHash.hs
+++ b/src/Data/Torrent/InfoHash.hs
@@ -125,21 +125,21 @@ instance Convertible BS.ByteString InfoHash where
125-- | Parse infohash from base16\/base32\/base64 encoded string. 125-- | Parse infohash from base16\/base32\/base64 encoded string.
126instance Convertible Text InfoHash where 126instance Convertible Text InfoHash where
127 safeConvert t 127 safeConvert t
128 | hashLen <= 28 = 128 | 26 <= hashLen && hashLen <= 28 =
129 case Base64.decode hashStr of 129 case Base64.decode hashStr of
130 Left msg -> convError ("invalid base64 encoding " ++ msg) t 130 Left msg -> convError ("invalid base64 encoding " ++ msg) t
131 Right ihStr -> pure $ InfoHash ihStr 131 Right ihStr -> safeConvert ihStr
132 132
133 | hashLen == 32 = 133 | hashLen == 32 =
134 case Base32.decode hashStr of 134 case Base32.decode hashStr of
135 Left msg -> convError msg t 135 Left msg -> convError msg t
136 Right ihStr -> pure $ InfoHash ihStr 136 Right ihStr -> safeConvert ihStr
137 137
138 | hashLen == 40 = 138 | hashLen == 40 =
139 let (ihStr, inv) = Base16.decode hashStr 139 let (ihStr, inv) = Base16.decode hashStr
140 in if BS.length inv == 0 140 in if BS.length inv /= 0
141 then pure $ InfoHash ihStr 141 then convError "invalid base16 encoding" t
142 else convError "invalid base16 encoding" t 142 else safeConvert ihStr
143 143
144 | otherwise = convError "invalid length" t 144 | otherwise = convError "invalid length" t
145 where 145 where
@@ -156,8 +156,8 @@ instance ToJSON InfoHash where
156 156
157-- | Convert from base16\/base32\/base64 encoded JSON string. 157-- | Convert from base16\/base32\/base64 encoded JSON string.
158instance FromJSON InfoHash where 158instance FromJSON InfoHash where
159 parseJSON = withText "JSON" $ -- TODO 159 parseJSON = withText "InfoHash" $
160 maybe (fail "could not parse InfoHash") pure . textToInfoHash 160 either (fail . prettyConvertError) pure . safeConvert
161 161
162ignoreErrorMsg :: Either a b -> Maybe b 162ignoreErrorMsg :: Either a b -> Maybe b
163ignoreErrorMsg = either (const Nothing) Just 163ignoreErrorMsg = either (const Nothing) Just
diff --git a/src/Network/BitTorrent/Exchange/Extension.hs b/src/Network/BitTorrent/Exchange/Extension.hs
index a4d72f96..e81cdb87 100644
--- a/src/Network/BitTorrent/Exchange/Extension.hs
+++ b/src/Network/BitTorrent/Exchange/Extension.hs
@@ -12,54 +12,59 @@
12-- 12--
13module Network.BitTorrent.Exchange.Extension 13module Network.BitTorrent.Exchange.Extension
14 ( -- * Capabilities 14 ( -- * Capabilities
15 Capabilities 15 Caps
16 , ppCaps, defaultCaps
17 , enabledCaps
18 16
19 -- * Extensions 17 -- * Extensions
20 , Extension(..) 18 , Extension(..)
21 , defaultExtensions, ppExtension
22 , encodeExts, decodeExts
23 ) where 19 ) where
24 20
25import Data.Bits 21import Data.Bits
22import Data.Default
23import Data.Monoid
26import Data.Word 24import Data.Word
27import Text.PrettyPrint 25import Text.PrettyPrint
26import Text.PrettyPrint.Class
28 27
28class (Enum a, Bounded a) => Capability a where
29 capMask :: a -> Word64
30 capRequires :: a -> Word64
29 31
30type Capabilities = Word64 32newtype Caps a = Caps Word64
31 33
32ppCaps :: Capabilities -> Doc 34instance (Pretty a, Capability a) => Pretty (Caps a) where
33ppCaps = hcat . punctuate ", " . map ppExtension . decodeExts 35 pretty = hcat . punctuate ", " . map pretty . toList
34 36
35defaultCaps :: Capabilities 37instance Default (Caps a) where
36defaultCaps = 0 38 def = Caps 0
39 {-# INLINE def #-}
37 40
38enabledCaps :: Capabilities -- ^ of the client. 41instance Monoid (Caps a) where
39 -> Capabilities -- ^ of the peer. 42 mempty = Caps (-1)
40 -> Capabilities -- ^ should be considered as enabled. 43 {-# INLINE mempty #-}
41enabledCaps = (.&.)
42 44
45 mappend (Caps a) (Caps b) = Caps (a .&. b)
46 {-# INLINE mappend #-}
43 47
44data Extension = ExtDHT -- ^ BEP 5 48allowed :: Capability a => a -> Caps a -> Bool
45 | ExtFast -- ^ BEP 6 49allowed = member
46 deriving (Show, Eq, Ord, Enum, Bounded) 50fromList :: Capability a => [a] -> Caps a
51fromList = Caps . foldr (.&.) 0 . map capMask
47 52
48ppExtension :: Extension -> Doc 53toList :: Capability a => Caps a -> [a]
49ppExtension ExtDHT = "DHT" 54toList (Caps rb) = filter (testMask rb . capMask) [minBound..maxBound]
50ppExtension ExtFast = "Fast Extension" 55 where
56 testMask bits x = bits .&. x > 0
51 57
52extensionMask :: Extension -> Word64
53extensionMask ExtDHT = 0x01
54extensionMask ExtFast = 0x04
55 58
56defaultExtensions :: [Extension] 59data Extension
57defaultExtensions = [] 60 = ExtDHT -- ^ BEP 5
61 | ExtFast -- ^ BEP 6
62 deriving (Show, Eq, Ord, Enum, Bounded)
58 63
59encodeExts :: [Extension] -> Capabilities 64instance Pretty Extension where
60encodeExts = foldr (.&.) 0 . map extensionMask 65 pretty ExtDHT = "DHT"
66 pretty ExtFast = "Fast Extension"
61 67
62decodeExts :: Capabilities -> [Extension] 68instance Capability Extension where
63decodeExts rb = filter (testMask rb . extensionMask) [minBound..maxBound] 69 capMask ExtDHT = 0x01
64 where 70 capMask ExtFast = 0x04
65 testMask bits x = bits .&. x > 0
diff --git a/src/Network/BitTorrent/Exchange/Message.hs b/src/Network/BitTorrent/Exchange/Message.hs
index 4d4a97e2..546288b2 100644
--- a/src/Network/BitTorrent/Exchange/Message.hs
+++ b/src/Network/BitTorrent/Exchange/Message.hs
@@ -41,21 +41,22 @@ module Network.BitTorrent.Exchange.Message
41 , defaultReserved 41 , defaultReserved
42 , handshakeMaxSize 42 , handshakeMaxSize
43 43
44 -- * Regular messages 44 -- * Messages
45 , Message(..) 45 , Message (..)
46 , StatusUpdate (..)
47 , RegularMessage (..)
48 , FastMessage (..)
46 ) where 49 ) where
47 50
48import Control.Applicative 51import Control.Applicative
49import Control.Exception 52import Control.Exception
50import Control.Monad 53import Control.Monad
51import Data.Binary as B
52import Data.Binary.Get as B
53import Data.Binary.Put as B
54import Data.ByteString as BS 54import Data.ByteString as BS
55import Data.ByteString.Char8 as BC 55import Data.ByteString.Char8 as BC
56import Data.ByteString.Lazy as BL 56import Data.ByteString.Lazy as BL
57import Data.Default 57import Data.Default
58import Data.Serialize as S 58import Data.Serialize as S
59import Data.Word
59import Network 60import Network
60import Network.Socket.ByteString 61import Network.Socket.ByteString
61import Text.PrettyPrint 62import Text.PrettyPrint
@@ -64,26 +65,9 @@ import Text.PrettyPrint.Class
64import Data.Torrent.Bitfield 65import Data.Torrent.Bitfield
65import Data.Torrent.Block 66import Data.Torrent.Block
66import Data.Torrent.InfoHash 67import Data.Torrent.InfoHash
67import Network.BitTorrent.Extension
68import Network.BitTorrent.Core.PeerId 68import Network.BitTorrent.Core.PeerId
69import Network.BitTorrent.Core.PeerAddr () 69import Network.BitTorrent.Core.PeerAddr ()
70 70import Network.BitTorrent.Exchange.Extension
71
72getInt :: S.Get Int
73getInt = fromIntegral <$> S.getWord32be
74{-# INLINE getInt #-}
75
76putInt :: S.Putter Int
77putInt = S.putWord32be . fromIntegral
78{-# INLINE putInt #-}
79
80getIntB :: B.Get Int
81getIntB = fromIntegral <$> B.getWord32be
82{-# INLINE getIntB #-}
83
84putIntB :: Int -> B.Put
85putIntB = B.putWord32be . fromIntegral
86{-# INLINE putIntB #-}
87 71
88{----------------------------------------------------------------------- 72{-----------------------------------------------------------------------
89 Handshake 73 Handshake
@@ -195,6 +179,9 @@ data StatusUpdate
195 | NotInterested 179 | NotInterested
196 deriving (Show, Eq, Ord, Enum, Bounded) 180 deriving (Show, Eq, Ord, Enum, Bounded)
197 181
182instance Pretty StatusUpdate where
183 pretty = text . show
184
198data RegularMessage = 185data RegularMessage =
199 -- | Zero-based index of a piece that has just been successfully 186 -- | Zero-based index of a piece that has just been successfully
200 -- downloaded and verified via the hash. 187 -- downloaded and verified via the hash.
@@ -219,9 +206,12 @@ data RegularMessage =
219 | Cancel !BlockIx 206 | Cancel !BlockIx
220 deriving (Show, Eq) 207 deriving (Show, Eq)
221 208
222data DHTMessage 209instance Pretty RegularMessage where
223 = Port !PortNumber 210 pretty (Have ix ) = "Have" <+> int ix
224 deriving (Show, Eq) 211 pretty (Bitfield _ ) = "Bitfield"
212 pretty (Request ix ) = "Request" <+> pretty ix
213 pretty (Piece blk) = "Piece" <+> pretty blk
214 pretty (Cancel i ) = "Cancel" <+> pretty i
225 215
226-- | BEP6 messages. 216-- | BEP6 messages.
227data FastMessage = 217data FastMessage =
@@ -247,7 +237,12 @@ data FastMessage =
247 | AllowedFast !PieceIx 237 | AllowedFast !PieceIx
248 deriving (Show, Eq) 238 deriving (Show, Eq)
249 239
250-- TODO make Network.BitTorrent.Exchange.Session 240instance Pretty FastMessage where
241 pretty (HaveAll ) = "Have all"
242 pretty (HaveNone ) = "Have none"
243 pretty (SuggestPiece pix) = "Suggest" <+> int pix
244 pretty (RejectRequest bix) = "Reject" <+> pretty bix
245 pretty (AllowedFast pix) = "Allowed fast" <+> int pix
251 246
252-- | Messages used in communication between peers. 247-- | Messages used in communication between peers.
253-- 248--
@@ -262,46 +257,53 @@ data Message
262 | Regular !RegularMessage 257 | Regular !RegularMessage
263 258
264 -- extensions 259 -- extensions
265 | DHT !DHTMessage 260 | Port !PortNumber
266 | Fast !FastMessage 261 | Fast !FastMessage
267 deriving (Show, Eq) 262 deriving (Show, Eq)
268 263
269instance Default Message where 264instance Default Message where
270 def = KeepAlive 265 def = KeepAlive
271 {-# INLINE def #-} 266 {-# INLINE def #-}
272{- 267
273-- | Payload bytes are omitted. 268-- | Payload bytes are omitted.
274instance Pretty Message where 269instance Pretty Message where
275 pretty (Bitfield _) = "Bitfield" 270 pretty (KeepAlive ) = "Keep alive"
276 pretty (Piece blk) = "Piece" <+> pretty blk 271 pretty (Status m) = pretty m
277 pretty (Cancel i ) = "Cancel" <+> pretty i 272 pretty (Regular m) = pretty m
278 pretty (SuggestPiece pix) = "Suggest" <+> int pix 273 pretty (Port p) = "Port" <+> int (fromEnum p)
279 pretty (RejectRequest i ) = "Reject" <+> pretty i 274 pretty (Fast m) = pretty m
280 pretty msg = text (show msg) 275
276getInt :: S.Get Int
277getInt = fromIntegral <$> S.getWord32be
278{-# INLINE getInt #-}
279
280putInt :: S.Putter Int
281putInt = S.putWord32be . fromIntegral
282{-# INLINE putInt #-}
281 283
282instance Serialize Message where 284instance Serialize Message where
283 get = do 285 get = do
284 len <- getInt 286 len <- getInt
285-- _ <- lookAhead $ ensure len
286 if len == 0 then return KeepAlive 287 if len == 0 then return KeepAlive
287 else do 288 else do
288 mid <- S.getWord8 289 mid <- S.getWord8
289 case mid of 290 case mid of
290 0x00 -> return Choke 291 0x00 -> return $ Status Choke
291 0x01 -> return Unchoke 292 0x01 -> return $ Status Unchoke
292 0x02 -> return Interested 293 0x02 -> return $ Status Interested
293 0x03 -> return NotInterested 294 0x03 -> return $ Status NotInterested
294 0x04 -> Have <$> getInt 295 0x04 -> (Regular . Have) <$> getInt
295 0x05 -> (Bitfield . fromBitmap) <$> S.getByteString (pred len) 296 0x05 -> (Regular . Bitfield . fromBitmap)
296 0x06 -> Request <$> S.get 297 <$> S.getByteString (pred len)
297 0x07 -> Piece <$> getBlock (len - 9) 298 0x06 -> (Regular . Request) <$> S.get
298 0x08 -> Cancel <$> S.get 299 0x07 -> (Regular . Piece) <$> getBlock (len - 9)
300 0x08 -> (Regular . Cancel) <$> S.get
299 0x09 -> Port <$> S.get 301 0x09 -> Port <$> S.get
300 0x0D -> SuggestPiece <$> getInt 302 0x0D -> (Fast . SuggestPiece) <$> getInt
301 0x0E -> return HaveAll 303 0x0E -> return $ Fast HaveAll
302 0x0F -> return HaveNone 304 0x0F -> return $ Fast HaveNone
303 0x10 -> RejectRequest <$> S.get 305 0x10 -> (Fast . RejectRequest) <$> S.get
304 0x11 -> AllowedFast <$> getInt 306 0x11 -> (Fast . AllowedFast) <$> getInt
305 _ -> do 307 _ -> do
306 rm <- S.remaining >>= S.getBytes 308 rm <- S.remaining >>= S.getBytes
307 fail $ "unknown message ID: " ++ show mid ++ "\n" 309 fail $ "unknown message ID: " ++ show mid ++ "\n"
@@ -309,92 +311,41 @@ instance Serialize Message where
309 311
310 where 312 where
311 getBlock :: Int -> S.Get (Block BL.ByteString) 313 getBlock :: Int -> S.Get (Block BL.ByteString)
312 getBlock len = Block <$> getInt <*> getInt <*> S.getLazyByteString (fromIntegral len) 314 getBlock len = Block <$> getInt <*> getInt
313 {-# INLINE getBlock #-} 315 <*> S.getLazyByteString (fromIntegral len)
314
315
316 put KeepAlive = putInt 0
317 put Choke = putInt 1 >> S.putWord8 0x00
318 put Unchoke = putInt 1 >> S.putWord8 0x01
319 put Interested = putInt 1 >> S.putWord8 0x02
320 put NotInterested = putInt 1 >> S.putWord8 0x03
321 put (Have i) = putInt 5 >> S.putWord8 0x04 >> putInt i
322 put (Bitfield bf) = putInt l >> S.putWord8 0x05 >> S.putLazyByteString b
323 where b = toBitmap bf
324 l = succ (fromIntegral (BL.length b))
325 {-# INLINE l #-}
326 put (Request blk) = putInt 13 >> S.putWord8 0x06 >> S.put blk
327 put (Piece blk) = putInt l >> S.putWord8 0x07 >> putBlock
328 where l = 9 + fromIntegral (BL.length (blkData blk))
329 {-# INLINE l #-}
330 putBlock = do putInt (blkPiece blk)
331 putInt (blkOffset blk)
332 S.putLazyByteString (blkData blk)
333 {-# INLINE putBlock #-}
334
335 put (Cancel blk) = putInt 13 >> S.putWord8 0x08 >> S.put blk
336 put (Port p ) = putInt 3 >> S.putWord8 0x09 >> S.put p
337 put HaveAll = putInt 1 >> S.putWord8 0x0E
338 put HaveNone = putInt 1 >> S.putWord8 0x0F
339 put (SuggestPiece pix) = putInt 5 >> S.putWord8 0x0D >> putInt pix
340 put (RejectRequest i ) = putInt 13 >> S.putWord8 0x10 >> S.put i
341 put (AllowedFast i ) = putInt 5 >> S.putWord8 0x11 >> putInt i
342-}
343{-
344instance Binary Message where
345 get = do
346 len <- getIntB
347-- _ <- lookAhead $ ensure len
348 if len == 0 then return KeepAlive
349 else do
350 mid <- B.getWord8
351 case mid of
352 0x00 -> return Choke
353 0x01 -> return Unchoke
354 0x02 -> return Interested
355 0x03 -> return NotInterested
356 0x04 -> Have <$> getIntB
357 0x05 -> (Bitfield . fromBitmap) <$> B.getByteString (pred len)
358 0x06 -> Request <$> B.get
359 0x07 -> Piece <$> getBlock (len - 9)
360 0x08 -> Cancel <$> B.get
361 0x09 -> (Port . fromIntegral) <$> B.getWord16be
362 0x0E -> return HaveAll
363 0x0F -> return HaveNone
364 0x0D -> SuggestPiece <$> getIntB
365 0x10 -> RejectRequest <$> B.get
366 0x11 -> AllowedFast <$> getIntB
367 _ -> fail $ "unknown message ID: " ++ show mid
368 where
369 getBlock :: Int -> B.Get (Block BL.ByteString)
370 getBlock len = Block <$> getIntB <*> getIntB
371 <*> B.getLazyByteString (fromIntegral len)
372 {-# INLINE getBlock #-} 316 {-# INLINE getBlock #-}
373 317
374 put KeepAlive = putIntB 0 318 put KeepAlive = putInt 0
375 put Choke = putIntB 1 >> B.putWord8 0x00 319 put (Status msg) = putStatus msg
376 put Unchoke = putIntB 1 >> B.putWord8 0x01 320 put (Regular msg) = putRegular msg
377 put Interested = putIntB 1 >> B.putWord8 0x02 321 put (Port p ) = putPort p
378 put NotInterested = putIntB 1 >> B.putWord8 0x03 322 put (Fast msg) = putFast msg
379 put (Have i) = putIntB 5 >> B.putWord8 0x04 >> putIntB i 323
380 put (Bitfield bf) = putIntB l >> B.putWord8 0x05 >> B.putLazyByteString b 324putStatus :: Putter StatusUpdate
381 where b = toBitmap bf 325putStatus su = putInt 1 >> S.putWord8 (fromIntegral (fromEnum su))
382 l = succ (fromIntegral (BL.length b)) 326
383 {-# INLINE l #-} 327putRegular :: Putter RegularMessage
384 put (Request blk) = putIntB 13 >> B.putWord8 0x06 >> B.put blk 328putRegular (Have i) = putInt 5 >> S.putWord8 0x04 >> putInt i
385 put (Piece blk) = putIntB l >> B.putWord8 0x07 >> putBlock 329putRegular (Bitfield bf) = putInt l >> S.putWord8 0x05 >> S.putLazyByteString b
386 where l = 9 + fromIntegral (BL.length (blkData blk)) 330 where b = toBitmap bf
387 {-# INLINE l #-} 331 l = succ (fromIntegral (BL.length b))
388 putBlock = do putIntB (blkPiece blk) 332 {-# INLINE l #-}
389 putIntB (blkOffset blk) 333putRegular (Request blk) = putInt 13 >> S.putWord8 0x06 >> S.put blk
390 B.putLazyByteString (blkData blk) 334putRegular (Piece blk) = putInt l >> S.putWord8 0x07 >> putBlock
391 {-# INLINE putBlock #-} 335 where l = 9 + fromIntegral (BL.length (blkData blk))
392 336 {-# INLINE l #-}
393 put (Cancel blk) = putIntB 13 >> B.putWord8 0x08 >> B.put blk 337 putBlock = do putInt (blkPiece blk)
394 put (Port p ) = putIntB 3 >> B.putWord8 0x09 >> B.putWord16be (fromIntegral p) 338 putInt (blkOffset blk)
395 put HaveAll = putIntB 1 >> B.putWord8 0x0E 339 S.putLazyByteString (blkData blk)
396 put HaveNone = putIntB 1 >> B.putWord8 0x0F 340 {-# INLINE putBlock #-}
397 put (SuggestPiece pix) = putIntB 5 >> B.putWord8 0x0D >> putIntB pix 341putRegular (Cancel blk) = putInt 13 >> S.putWord8 0x08 >> S.put blk
398 put (RejectRequest i ) = putIntB 13 >> B.putWord8 0x10 >> B.put i 342
399 put (AllowedFast i ) = putIntB 5 >> B.putWord8 0x11 >> putIntB i 343putPort :: Putter PortNumber
400-} \ No newline at end of file 344putPort p = putInt 3 >> S.putWord8 0x09 >> S.put p
345
346putFast :: Putter FastMessage
347putFast HaveAll = putInt 1 >> S.putWord8 0x0E
348putFast HaveNone = putInt 1 >> S.putWord8 0x0F
349putFast (SuggestPiece pix) = putInt 5 >> S.putWord8 0x0D >> putInt pix
350putFast (RejectRequest i ) = putInt 13 >> S.putWord8 0x10 >> S.put i
351putFast (AllowedFast i ) = putInt 5 >> S.putWord8 0x11 >> putInt i