-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPeer.hs
132 lines (104 loc) · 3.46 KB
/
Peer.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
--
{-# OPTIONS_GHC -Wno-partial-fields #-}
module Peer (Handshake (..), buildHandshake, decodeHandshake, handshakeSize) where
import Parser.Core (Parser, expectByte, expectChar, nextN)
import qualified Data.ByteString as B
import Control.Lens
import Control.Monad (forM_, replicateM)
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe (fromMaybe)
import Data.Word (Word32, Word8)
import Prelude as P
data Handshake = Handshake
{ _extensionFlags :: [Bool]
, _infoHash :: B.ByteString
, _peerId :: B.ByteString
}
deriving (Eq, Show)
makeLenses ''Handshake
data Message
= KeepAlive
| Choke
| UnChoke
| Interested
| NotInterested
| Have {index :: Word32}
| BitField {flags :: [Bool]}
| Request {index :: Word32, begin :: Word32, length :: Word32}
| Piece {index :: Word32, begin :: Word32, piece :: B.ByteString}
| Cancel {index :: Word32, begin :: Word32, length :: Word32}
deriving (Show, Eq)
class Encodable a where
encode :: a -> B.ByteString
instance Encodable B.ByteString where
encode :: B.ByteString -> B.ByteString
encode = id
encodeIntegral :: (Integral a) => Int -> a -> [Word8]
encodeIntegral n x = go n x []
where
go 0 _ words = words
go n x words = go (n - 1) (x `div` 256) $ convert (x `mod` 256) : words
convert = toEnum . fromEnum
instance Encodable Word8 where
encode :: Word8 -> B.ByteString
encode word8 = B.pack $ encodeIntegral 1 word8
instance Encodable Word32 where
encode :: Word32 -> B.ByteString
encode word32 = B.pack $ encodeIntegral 4 word32
instance Encodable [Bool] where
encode :: [Bool] -> B.ByteString
encode flags = flip evalState flags $ do
let pop = state $ \case
[] -> (Nothing, [])
(x : xs) -> (Just x, xs)
isEmpty = gets P.null
readWord8 = do
bits <- P.map (fromMaybe False) <$> replicateM 8 pop
return $ P.foldr (\bit acc -> 2 * acc + if bit then 1 else 0) 0 bits
loop bytes = do
stop <- isEmpty
if stop
then return $ P.reverse bytes
else do
byte <- readWord8
loop (byte : bytes)
B.pack <$> loop []
bitUnpack :: B.ByteString -> [Bool]
bitUnpack bstr =
let packed :: Integer
packed = P.foldr (\byte acc -> acc * 256 + toInteger byte) 0 (B.unpack bstr)
go :: Int -> Integer -> [Bool] -> [Bool]
go 0 _ flags = P.reverse flags
go n x flags =
if even x
then go (n - 1) (x `div` 2) (False : flags)
else go (n - 1) (x `div` 2) (True : flags)
in go ((B.length bstr) * 8) packed []
handshakeBytes :: B.ByteString
handshakeBytes = B.pack $ (19 :) $ B.unpack "BitTorrent protocol"
handshakeSize :: Int
handshakeSize = 1 + 19 + 8 + 20 + 20
buildHandshake :: Handshake -> B.ByteString
buildHandshake handshake = execWriter $ do
tell handshakeBytes
tell $ encode $ view extensionFlags handshake
tell $ handshake ^. Peer.infoHash
tell $ handshake ^. Peer.peerId
decodeHandshake :: Parser Handshake
decodeHandshake = do
expectByte 19
forM_ ("BitTorrent protocol" :: String) $ \char -> do
expectChar char
extensionFlags <- bitUnpack <$> nextN 8
infoHash <- nextN 20
peerId <- nextN 20
return $ Handshake extensionFlags infoHash peerId