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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
-- | For more info see:
-- <http://www.bittorrent.org/beps/bep_0005.html#dht-queries>
--
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module Network.BitTorrent.DHT.Message
( -- * Envelopes
Query (..)
, Response (..)
-- * Queries
-- ** ping
, Ping (..)
-- ** find_node
, FindNode (..)
, NodeFound (..)
-- ** get_peers
, GetPeers (..)
, GotPeers (..)
-- ** announce_peer
, Announce (..)
, Announced (..)
) where
import Control.Applicative
import Data.BEncode as BE
import Data.BEncode.BDict
import Data.List as L
import Data.Monoid
import Data.Serialize as S
import Data.Typeable
import Network
import Network.KRPC
import Data.Torrent.InfoHash
import Network.BitTorrent.Core
import Network.BitTorrent.DHT.Token
import Network.KRPC ()
{-----------------------------------------------------------------------
-- envelopes
-----------------------------------------------------------------------}
node_id_key :: BKey
node_id_key = "id"
-- | All queries have an "id" key and value containing the node ID of
-- the querying node.
data Query a = Query
{ thisNodeId :: NodeId
, queryParams :: a
} deriving (Show, Eq)
instance BEncode a => BEncode (Query a) where
toBEncode Query {..} = toDict $
node_id_key .=! thisNodeId .: endDict
<>
dict (toBEncode queryParams)
where
dict (BDict d) = d
dict _ = error "impossible: instance BEncode (Query a)"
fromBEncode v = do
Query <$> fromDict (field (req node_id_key)) v
<*> fromBEncode v
-- | All responses have an "id" key and value containing the node ID
-- of the responding node.
data Response a = Response
{ remoteNodeId :: NodeId
, responseVals :: a
} deriving (Show, Eq)
instance BEncode a => BEncode (Response a) where
toBEncode = toBEncode . toQuery
where
toQuery (Response nid a) = Query nid a
fromBEncode b = fromQuery <$> fromBEncode b
where
fromQuery (Query nid a) = Response nid a
{-----------------------------------------------------------------------
-- ping method
-----------------------------------------------------------------------}
-- | The most basic query is a ping. Ping query is used to check if a
-- quered node is still alive.
data Ping = Ping
deriving (Show, Eq, Typeable)
instance BEncode Ping where
toBEncode Ping = toDict endDict
fromBEncode _ = pure Ping
-- | \"q\" = \"ping\"
instance KRPC (Query Ping) (Response Ping) where
method = "ping"
{-----------------------------------------------------------------------
-- find_node method
-----------------------------------------------------------------------}
-- | Find node is used to find the contact information for a node
-- given its ID.
newtype FindNode = FindNode NodeId
deriving (Show, Eq, Typeable)
target_key :: BKey
target_key = "target"
instance BEncode FindNode where
toBEncode (FindNode nid) = toDict $ target_key .=! nid .: endDict
fromBEncode = fromDict $ FindNode <$>! target_key
-- | When a node receives a 'FindNode' query, it should respond with a
-- the compact node info for the target node or the K (8) closest good
-- nodes in its own routing table.
--
newtype NodeFound ip = NodeFound [NodeInfo ip]
deriving (Show, Eq, Typeable)
nodes_key :: BKey
nodes_key = "nodes"
binary :: Serialize a => BKey -> BE.Get [a]
binary k = field (req k) >>= either (fail . format) return .
runGet (many get)
where
format str = "fail to deserialize " ++ show k ++ " field: " ++ str
instance (Typeable ip, Serialize ip) => BEncode (NodeFound ip) where
toBEncode (NodeFound ns) = toDict $
nodes_key .=! runPut (mapM_ put ns)
.: endDict
fromBEncode = fromDict $ NodeFound <$> binary nodes_key
-- | \"q\" == \"find_node\"
instance (Serialize ip, Typeable ip)
=> KRPC (Query FindNode) (Response (NodeFound ip)) where
method = "find_node"
{-----------------------------------------------------------------------
-- get_peers method
-----------------------------------------------------------------------}
-- | Get peers associated with a torrent infohash.
newtype GetPeers = GetPeers InfoHash
deriving (Show, Eq, Typeable)
info_hash_key :: BKey
info_hash_key = "info_hash"
instance BEncode GetPeers where
toBEncode (GetPeers ih) = toDict $ info_hash_key .=! ih .: endDict
fromBEncode = fromDict $ GetPeers <$>! info_hash_key
data GotPeers ip = GotPeers
{ -- | If the queried node has no peers for the infohash, returned
-- the K nodes in the queried nodes routing table closest to the
-- infohash supplied in the query.
peers :: Either [NodeInfo ip] [PeerAddr ip]
-- | The token value is a required argument for a future
-- announce_peer query.
, grantedToken :: Token
} deriving (Show, Eq, Typeable)
peers_key :: BKey
peers_key = "values"
token_key :: BKey
token_key = "token"
instance (Typeable ip, Serialize ip) => BEncode (GotPeers ip) where
toBEncode GotPeers {..} = toDict $
case peers of
Left ns ->
nodes_key .=! runPut (mapM_ put ns)
.: token_key .=! grantedToken
.: endDict
Right ps ->
token_key .=! grantedToken
.: peers_key .=! L.map S.encode ps
.: endDict
fromBEncode = fromDict $ do
mns <- optional (binary nodes_key) -- "nodes"
tok <- field (req token_key) -- "token"
mps <- optional (field (req peers_key) >>= decodePeers) -- "values"
case (Right <$> mps) <|> (Left <$> mns) of
Nothing -> fail "get_peers: neihter peers nor nodes key is valid"
Just xs -> pure $ GotPeers xs tok
where
decodePeers = either fail pure . mapM S.decode
instance (Typeable ip, Serialize ip) =>
KRPC (Query GetPeers) (Response (GotPeers ip)) where
method = "get_peers"
{-----------------------------------------------------------------------
-- announce method
-----------------------------------------------------------------------}
-- | Announce that the peer, controlling the querying node, is
-- downloading a torrent on a port.
data Announce = Announce
{ -- | If set, the 'port' field should be ignored and the source
-- port of the UDP packet should be used as the peer's port
-- instead. This is useful for peers behind a NAT that may not
-- know their external port, and supporting uTP, they accept
-- incoming connections on the same port as the DHT port.
impliedPort :: Bool
-- | infohash of the torrent;
, topic :: InfoHash
-- | the port /this/ peer is listening;
, port :: PortNumber
-- | received in response to a previous get_peers query.
, sessionToken :: Token
} deriving (Show, Eq, Typeable)
port_key :: BKey
port_key = "port"
implied_port_key :: BKey
implied_port_key = "implied_port"
instance BEncode Announce where
toBEncode Announce {..} = toDict $
implied_port_key .=? flagField impliedPort
.: info_hash_key .=! topic
.: port_key .=! port
.: token_key .=! sessionToken
.: endDict
where
flagField flag = if flag then Just (1 :: Int) else Nothing
fromBEncode = fromDict $ do
Announce <$> (boolField <$> optional (field (req implied_port_key)))
<*>! info_hash_key
<*>! port_key
<*>! token_key
where
boolField = maybe False (/= (0 :: Int))
-- | The queried node must verify that the token was previously sent
-- to the same IP address as the querying node. Then the queried node
-- should store the IP address of the querying node and the supplied
-- port number under the infohash in its store of peer contact
-- information.
data Announced = Announced
deriving (Show, Eq, Typeable)
instance BEncode Announced where
toBEncode _ = toBEncode Ping
fromBEncode _ = pure Announced
instance KRPC (Query Announce) (Response Announced) where
method = "announce_peer"
|