@@ -21,133 +21,31 @@ module Kademlia
21
21
STANDARD_PACKET = 0xE4
22
22
# The header that compressed Kad messages use, which is currently unsupported
23
23
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
36
24
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
51
27
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
120
33
end
121
- peers
122
- end
123
34
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
136
44
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
146
48
end
147
-
148
- # TODO
149
- # def encode_peer_id(id)
150
- # end
151
49
end
152
50
end
153
51
end
0 commit comments