@@ -17,70 +17,100 @@ module Proto
17
17
#
18
18
##
19
19
module Kademlia
20
+ # The header that non-compressed Kad messages use
20
21
STANDARD_PACKET = 0xE4
21
- # TODO: support this format
22
+ # The header that compressed Kad messages use, which is currently unsupported
22
23
COMPRESSED_PACKET = 0xE5
23
-
24
+ # Opcode for a BOOTSTRAP request
24
25
BOOTSTRAP_REQ = 0x01
26
+ # Opcode for a BOOTSTRAP response
25
27
BOOTSTRAP_RES = 0x09
28
+ # Opcode for a PING request
26
29
PING = 0x60
30
+ # Opcode for a PING response
27
31
PONG = 0x61
28
-
29
32
# The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
30
33
# peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
31
34
# and version (1 byte)
32
35
BOOTSTRAP_PEER_SIZE = 25
33
36
37
+ # Decodes a Kademlia message.
38
+ #
39
+ # @param message [String] the message to decode
40
+ # @return [Array] the message type and body if valid, nil otherwise
34
41
def decode_message ( message )
35
- # minimum size is header (1) + opcode (1) + stuff (0+)
42
+ # minimum size is header (1) + type (1) + stuff (0+)
36
43
return if message . length < 2
37
- header , opcode = message . unpack ( 'CC' )
44
+ header , type = message . unpack ( 'CC' )
38
45
if header == COMPRESSED_PACKET
39
- fail NotImplementedError , "Unable to handle compressed #{ message . length } -byte compressed Kademlia message"
46
+ fail NotImplementedError , "Unable to handle #{ message . length } -byte compressed Kademlia message"
40
47
end
41
48
return if header != STANDARD_PACKET
42
- [ opcode , message [ 2 , message . length ] ]
49
+ [ type , message [ 2 , message . length ] ]
43
50
end
44
51
45
- def encode_message ( type , payload = '' )
46
- [ STANDARD_PACKET , type ] . pack ( 'CC' ) + payload
52
+ # Encodes a Kademlia message.
53
+ #
54
+ # @param type [String] the message type
55
+ # @param body [String] the message body
56
+ # @return [String] the encoded Kademlia message
57
+ def encode_message ( type , body = '' )
58
+ [ STANDARD_PACKET , type ] . pack ( 'CC' ) + body
47
59
end
48
60
61
+ # Builds a BOOTSTRAP request
62
+ #
63
+ # @return [String] a BOOTSTRAP request
49
64
def bootstrap
50
65
encode_message ( BOOTSTRAP_REQ )
51
66
end
52
67
53
- def decode_bootstrap_res ( message )
54
- opcode , payload = decode_message ( message )
68
+ # Decodes a BOOTSTRAP response
69
+ #
70
+ # @param response [String] the response to decode
71
+ # @return [Array] the discovered peer ID, TCP port, version and a list of peers
72
+ # if the response if valid, nil otherwise
73
+ def decode_bootstrap_res ( response )
74
+ type , body = decode_message ( response )
55
75
# abort if this isn't a valid response
56
- return nil unless opcode = BOOTSTRAP_RES
57
- return nil unless payload . size >= 23
58
- peer_id = decode_peer_id ( payload . slice! ( 0 , 16 ) )
59
- tcp_port , version , num_peers = payload . slice! ( 0 , 5 ) . unpack ( 'vCv' )
60
- # protocol says there are no peers and the payload confirms this, so just return with no peers
61
- return [ tcp_port , version , [ ] ] if num_peers == 0 && payload . blank?
62
- peers = decode_bootstrap_peers ( payload )
76
+ return nil unless type = BOOTSTRAP_RES
77
+ return nil unless body . size >= 23
78
+ peer_id = decode_peer_id ( body . slice! ( 0 , 16 ) )
79
+ tcp_port , version , num_peers = body . slice! ( 0 , 5 ) . unpack ( 'vCv' )
80
+ # protocol says there are no peers and the body confirms this, so just return with no peers
81
+ return [ tcp_port , version , [ ] ] if num_peers == 0 && body . blank?
82
+ peers = decode_bootstrap_peers ( body )
63
83
# abort if the peer data was invalid
64
84
return nil unless peers
65
85
[ peer_id , tcp_port , version , peers ]
66
86
end
67
87
68
- # Returns a PING message
88
+ # Builds a PING request
89
+ #
90
+ # @return [String] a PING request
69
91
def ping
70
92
encode_message ( PING )
71
93
end
72
94
73
- # Decodes a PONG message, returning the port used by the peer
74
- def decode_pong ( message )
75
- opcode , port = decode_message ( message )
95
+ # Decode a PING response, PONG
96
+ #
97
+ # @param response [String] the response to decode
98
+ # @return [Integer] the source port from the PING response if the response is valid, nil otherwise
99
+ def decode_pong ( response )
100
+ type , port = decode_message ( response )
76
101
# abort if this isn't a pong
77
- return nil unless opcode == PONG
102
+ return nil unless type == PONG
78
103
# abort if the response is too large/small
79
104
return nil unless port && port . size == 2
80
105
# this should always be equivalent to the source port from which the PING was received
81
106
port . unpack ( 'v' ) [ 0 ]
82
107
end
83
108
109
+ # Decode a list of peers from a BOOTSTRAP response
110
+ #
111
+ # @param peers_data [String] the peers data from a BOOTSTRAP response
112
+ # @return [Array] a list of the peers and their associated metadata extracted
113
+ # from the response if valid, nil otherwise
84
114
def decode_bootstrap_peers ( peers_data )
85
115
# sanity check total size
86
116
return nil unless peers_data . size % BOOTSTRAP_PEER_SIZE == 0
@@ -91,6 +121,10 @@ def decode_bootstrap_peers(peers_data)
91
121
peers
92
122
end
93
123
124
+ # Decodes a single set of peer data from a BOOTSTRAP reseponse
125
+ #
126
+ # @param peer-data [String] the peer data for one peer from a BOOSTRAP response
127
+ # @return [Array] the peer ID, IPv4 addresss, UDP port, TCP port and version of this peer
94
128
def decode_bootstrap_peer ( peer_data )
95
129
# sanity check the size of this peer's data
96
130
return nil unless peer_data . size == BOOTSTRAP_PEER_SIZE
@@ -100,17 +134,20 @@ def decode_bootstrap_peer(peer_data)
100
134
[ decode_peer_id ( peer_id ) , Rex ::Socket . addr_itoa ( ip ) , udp_port , tcp_port , version ]
101
135
end
102
136
103
-
137
+ # Decodes an on-the-wire representation of a Kademlia peer to its 16-character hex equivalent
138
+ #
139
+ # @param bytes [String] the on-the-wire representation of a Kademlia peer
140
+ # @return [String] the peer ID if valid, nil otherwise
104
141
def decode_peer_id ( bytes )
105
142
peer_id = 0
106
143
return nil unless bytes . size == 16
107
144
bytes . unpack ( 'VVVV' ) . map { |p | peer_id <<= 32 ; peer_id ^= p ; }
108
145
peer_id . to_s ( 16 ) . upcase
109
146
end
110
147
111
- # TODO?
112
- def encode_peer_id ( id )
113
- end
148
+ # TODO
149
+ # def encode_peer_id(id)
150
+ # end
151
+ end
114
152
end
115
153
end
116
- end
0 commit comments