@@ -17,70 +17,100 @@ module Proto
1717#
1818##
1919module Kademlia
20+ # The header that non-compressed Kad messages use
2021 STANDARD_PACKET = 0xE4
21- # TODO: support this format
22+ # The header that compressed Kad messages use, which is currently unsupported
2223 COMPRESSED_PACKET = 0xE5
23-
24+ # Opcode for a BOOTSTRAP request
2425 BOOTSTRAP_REQ = 0x01
26+ # Opcode for a BOOTSTRAP response
2527 BOOTSTRAP_RES = 0x09
28+ # Opcode for a PING request
2629 PING = 0x60
30+ # Opcode for a PING response
2731 PONG = 0x61
28-
2932 # The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
3033 # peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
3134 # and version (1 byte)
3235 BOOTSTRAP_PEER_SIZE = 25
3336
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
3441 def decode_message ( message )
35- # minimum size is header (1) + opcode (1) + stuff (0+)
42+ # minimum size is header (1) + type (1) + stuff (0+)
3643 return if message . length < 2
37- header , opcode = message . unpack ( 'CC' )
44+ header , type = message . unpack ( 'CC' )
3845 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"
4047 end
4148 return if header != STANDARD_PACKET
42- [ opcode , message [ 2 , message . length ] ]
49+ [ type , message [ 2 , message . length ] ]
4350 end
4451
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
4759 end
4860
61+ # Builds a BOOTSTRAP request
62+ #
63+ # @return [String] a BOOTSTRAP request
4964 def bootstrap
5065 encode_message ( BOOTSTRAP_REQ )
5166 end
5267
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 )
5575 # 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 )
6383 # abort if the peer data was invalid
6484 return nil unless peers
6585 [ peer_id , tcp_port , version , peers ]
6686 end
6787
68- # Returns a PING message
88+ # Builds a PING request
89+ #
90+ # @return [String] a PING request
6991 def ping
7092 encode_message ( PING )
7193 end
7294
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 )
76101 # abort if this isn't a pong
77- return nil unless opcode == PONG
102+ return nil unless type == PONG
78103 # abort if the response is too large/small
79104 return nil unless port && port . size == 2
80105 # this should always be equivalent to the source port from which the PING was received
81106 port . unpack ( 'v' ) [ 0 ]
82107 end
83108
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
84114 def decode_bootstrap_peers ( peers_data )
85115 # sanity check total size
86116 return nil unless peers_data . size % BOOTSTRAP_PEER_SIZE == 0
@@ -91,6 +121,10 @@ def decode_bootstrap_peers(peers_data)
91121 peers
92122 end
93123
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
94128 def decode_bootstrap_peer ( peer_data )
95129 # sanity check the size of this peer's data
96130 return nil unless peer_data . size == BOOTSTRAP_PEER_SIZE
@@ -100,17 +134,20 @@ def decode_bootstrap_peer(peer_data)
100134 [ decode_peer_id ( peer_id ) , Rex ::Socket . addr_itoa ( ip ) , udp_port , tcp_port , version ]
101135 end
102136
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
104141 def decode_peer_id ( bytes )
105142 peer_id = 0
106143 return nil unless bytes . size == 16
107144 bytes . unpack ( 'VVVV' ) . map { |p | peer_id <<= 32 ; peer_id ^= p ; }
108145 peer_id . to_s ( 16 ) . upcase
109146 end
110147
111- # TODO?
112- def encode_peer_id ( id )
113- end
148+ # TODO
149+ # def encode_peer_id(id)
150+ # end
151+ end
114152end
115153end
116- end
0 commit comments