@@ -21,133 +21,31 @@ module Kademlia
2121 STANDARD_PACKET = 0xE4
2222 # The header that compressed Kad messages use, which is currently unsupported
2323 COMPRESSED_PACKET = 0xE5
24- # Opcode for a BOOTSTRAP request
25- BOOTSTRAP_REQ = 0x01
26- # Opcode for a BOOTSTRAP response
27- BOOTSTRAP_RES = 0x09
28- # Opcode for a PING request
29- PING = 0x60
30- # Opcode for a PING response
31- PONG = 0x61
32- # The minimum size of a peer in a KADEMLIA2_BOOTSTRAP_RES message:
33- # peer ID (16-bytes), IP (4 bytes), UDP port (2 bytes), TCP port (2 bytes)
34- # and version (1 byte)
35- BOOTSTRAP_PEER_SIZE = 25
3624
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
41- def decode_message ( message )
42- # minimum size is header (1) + type (1) + stuff (0+)
43- return if message . length < 2
44- header , type = message . unpack ( 'CC' )
45- if header == COMPRESSED_PACKET
46- fail NotImplementedError , "Unable to handle #{ message . length } -byte compressed Kademlia message"
47- end
48- return if header != STANDARD_PACKET
49- [ type , message [ 2 , message . length ] ]
50- end
25+ class Message
26+ attr_accessor :type , :body
5127
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
59- end
60-
61- # Builds a BOOTSTRAP request
62- #
63- # @return [String] a BOOTSTRAP request
64- def bootstrap
65- encode_message ( BOOTSTRAP_REQ )
66- end
67-
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 )
75- # abort if this isn't a valid response
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 )
83- # abort if the peer data was invalid
84- return nil unless peers
85- [ peer_id , tcp_port , version , peers ]
86- end
87-
88- # Builds a PING request
89- #
90- # @return [String] a PING request
91- def ping
92- encode_message ( PING )
93- end
94-
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 )
101- # abort if this isn't a pong
102- return nil unless type == PONG
103- # abort if the response is too large/small
104- return nil unless port && port . size == 2
105- # this should always be equivalent to the source port from which the PING was received
106- port . unpack ( 'v' ) [ 0 ]
107- end
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
114- def decode_bootstrap_peers ( peers_data )
115- # sanity check total size
116- return nil unless peers_data . size % BOOTSTRAP_PEER_SIZE == 0
117- peers = [ ]
118- until peers_data . blank?
119- peers << decode_bootstrap_peer ( peers_data . slice! ( 0 , BOOTSTRAP_PEER_SIZE ) )
28+ # @param type [String] the message type
29+ # @param body [String] the message body
30+ def initialize ( type , body = '' )
31+ @type = type
32+ @body = body
12033 end
121- peers
122- end
12334
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
128- def decode_bootstrap_peer ( peer_data )
129- # sanity check the size of this peer's data
130- return nil unless peer_data . size == BOOTSTRAP_PEER_SIZE
131- # TODO; interpret this properly
132- peer_id = peer_data . slice! ( 0 , 16 )
133- ip , udp_port , tcp_port , version = peer_data . unpack ( 'VvvC' )
134- [ decode_peer_id ( peer_id ) , Rex ::Socket . addr_itoa ( ip ) , udp_port , tcp_port , version ]
135- end
35+ def self . from_data ( data )
36+ return if data . length < 2
37+ header , type = data . unpack ( 'CC' )
38+ if header == COMPRESSED_PACKET
39+ fail NotImplementedError , "Unable to handle #{ message . length } -byte compressed Kademlia message"
40+ end
41+ return if header != STANDARD_PACKET
42+ Message . new ( type , data [ 2 , data . length ] ] )
43+ end
13644
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
141- def decode_peer_id ( bytes )
142- peer_id = 0
143- return nil unless bytes . size == 16
144- bytes . unpack ( 'VVVV' ) . map { |p | peer_id <<= 32 ; peer_id ^= p ; }
145- peer_id . to_s ( 16 ) . upcase
45+ def to_s
46+ [ STANDARD_PACKET , @type ] . pack ( 'CC' ) + @body
47+ end
14648 end
147-
148- # TODO
149- # def encode_peer_id(id)
150- # end
15149end
15250end
15351end
0 commit comments