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
|
-- | 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.ByteString as BS
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.KRPC ()
{-----------------------------------------------------------------------
-- envelopes
-----------------------------------------------------------------------}
type Token = ByteString
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) [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 => BE.Get BS.ByteString -> BE.Get a
binary m = m >>= either fail return . S.decode
instance (Typeable ip, Serialize ip) => BEncode (NodeFound ip) where
toBEncode (NodeFound ns) = toDict $
nodes_key .=! S.encode ns
.: endDict
fromBEncode = fromDict $ NodeFound <$> binary (field (req 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 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 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 $
putPeerList peers
.: token_key .=! grantedToken
.: endDict
where
putPeerList (Right ps) = peers_key .=! S.encode ps
putPeerList (Left ns) = nodes_key .=! S.encode ns
fromBEncode = fromDict $ GotPeers <$> getPeerList <*>! token_key
where
getPeerList = Right <$> binary (field (req peers_key))
<|> Left <$> binary (field (req nodes_key))
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
{ -- | 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 Typeable
port_key :: BKey
port_key = "port"
instance BEncode Announce where
toBEncode Announce {..} = toDict $
info_hash_key .=! topic
.: port_key .=! port
.: token_key .=! sessionToken
.: endDict
fromBEncode = fromDict $ do
Announce <$>! info_hash_key
<*>! port_key
<*>! token_key
-- | 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
instance BEncode Announced where
toBEncode _ = toBEncode Ping
fromBEncode _ = pure Announced
instance KRPC (Query Announce) (Response Announced) where
method = "announce_peer"
|