summaryrefslogtreecommitdiff
path: root/src/Network/Torrent/PWP.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Network/Torrent/PWP.hs')
-rw-r--r--src/Network/Torrent/PWP.hs100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/Network/Torrent/PWP.hs b/src/Network/Torrent/PWP.hs
new file mode 100644
index 00000000..059dd106
--- /dev/null
+++ b/src/Network/Torrent/PWP.hs
@@ -0,0 +1,100 @@
1{-# LANGUAGE DoAndIfThenElse #-}
2module Network.Torrent.PWP
3 ( Message(..), Block(..), BlockIx(..),
4 ) where
5
6import Control.Applicative
7import Data.ByteString (ByteString)
8import qualified Data.ByteString as B
9
10import Data.Serialize
11
12data BlockIx = BlockIx {
13 ixPiece :: {-# UNPACK #-} !Int -- ^ Zero-based piece index.
14 , ixOffset :: {-# UNPACK #-} !Int -- ^ Zero-based byte offset within the piece.
15 , ixLength :: {-# UNPACK #-} !Int -- ^ Block size starting from offset.
16 } deriving (Show, Read, Eq)
17
18data Block = Block {
19 blkPiece :: Int -- ^ Zero-based piece index.
20 , blkOffset :: Int -- ^ Zero-based byte offset within the piece.
21 , blkData :: ByteString -- ^ Payload.
22 } deriving (Show, Read, Eq)
23
24-- TODO comment message constructors
25data Message = KeepAlive
26 | Choke
27 | Unchoke
28 | Interested
29 | NotInterested
30 | Have Int
31 | Bitfield ByteString
32 | Request BlockIx
33 | Piece Block
34 | Cancel BlockIx
35 | Port Int
36 deriving (Show, Read, Eq)
37
38getInt :: Get Int
39getInt = fromIntegral <$> getWord32be
40{-# INLINE getInt #-}
41
42putInt :: Putter Int
43putInt = putWord32be . fromIntegral
44{-# INLINE putInt #-}
45
46instance Serialize BlockIx where
47 {-# SPECIALIZE instance Serialize BlockIx #-}
48 get = BlockIx <$> getInt <*> getInt <*> getInt
49 {-# INLINE get #-}
50
51 put ix = do putInt (ixPiece ix)
52 putInt (ixOffset ix)
53 putInt (ixLength ix)
54 {-# INLINE put #-}
55
56instance Serialize Message where
57 get = do
58 len <- getInt
59 lookAhead $ ensure len
60 if len == 0 then return KeepAlive
61 else do
62 mid <- getWord8
63 case mid of
64 0 -> return Choke
65 1 -> return Unchoke
66 2 -> return Interested
67 3 -> return NotInterested
68 4 -> Have <$> getInt
69 5 -> Bitfield <$> getBytes (pred len)
70 6 -> Request <$> get
71 7 -> Piece <$> getBlock (len - 9)
72 8 -> Cancel <$> get
73 9 -> (Port . fromIntegral) <$> getWord16be
74 _ -> fail $ "unknown message ID: " ++ show mid
75
76 where
77 getBlock :: Int -> Get Block
78 getBlock len = Block <$> getInt <*> getInt <*> getBytes len
79 {-# INLINE getBlock #-}
80
81 put KeepAlive = putInt 0
82 put Choke = putInt 1 >> putWord8 0
83 put Unchoke = putInt 1 >> putWord8 1
84 put Interested = putInt 1 >> putWord8 2
85 put NotInterested = putInt 1 >> putWord8 3
86 put (Have i) = putInt 5 >> putWord8 4 >> putInt i
87 put (Bitfield b) = putInt l >> putWord8 5 >> putByteString b
88 where l = succ (B.length b)
89 {-# INLINE l #-}
90 put (Request blk) = putInt 13 >> putWord8 6 >> put blk
91 put (Piece blk) = putInt l >> putWord8 7 >> putBlock
92 where l = 9 + B.length (blkData blk)
93 {-# INLINE l #-}
94 putBlock = do putInt (blkPiece blk)
95 putInt (blkOffset blk)
96 putByteString (blkData blk)
97 {-# INLINE putBlock #-}
98
99 put (Cancel blk) = putInt 13 >> putWord8 8 >> put blk
100 put (Port p ) = putInt 3 >> putWord8 9 >> putWord16be (fromIntegral p) \ No newline at end of file