devp2p 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +22 -0
  4. data/lib/devp2p.rb +57 -0
  5. data/lib/devp2p/app_helper.rb +85 -0
  6. data/lib/devp2p/base_app.rb +80 -0
  7. data/lib/devp2p/base_protocol.rb +136 -0
  8. data/lib/devp2p/base_service.rb +55 -0
  9. data/lib/devp2p/command.rb +82 -0
  10. data/lib/devp2p/configurable.rb +32 -0
  11. data/lib/devp2p/connection_monitor.rb +77 -0
  12. data/lib/devp2p/control.rb +32 -0
  13. data/lib/devp2p/crypto.rb +73 -0
  14. data/lib/devp2p/crypto/ecc_x.rb +133 -0
  15. data/lib/devp2p/crypto/ecies.rb +134 -0
  16. data/lib/devp2p/discovery.rb +118 -0
  17. data/lib/devp2p/discovery/address.rb +83 -0
  18. data/lib/devp2p/discovery/kademlia_protocol_adapter.rb +11 -0
  19. data/lib/devp2p/discovery/node.rb +32 -0
  20. data/lib/devp2p/discovery/protocol.rb +342 -0
  21. data/lib/devp2p/discovery/transport.rb +105 -0
  22. data/lib/devp2p/exception.rb +30 -0
  23. data/lib/devp2p/frame.rb +197 -0
  24. data/lib/devp2p/kademlia.rb +48 -0
  25. data/lib/devp2p/kademlia/k_bucket.rb +178 -0
  26. data/lib/devp2p/kademlia/node.rb +40 -0
  27. data/lib/devp2p/kademlia/protocol.rb +284 -0
  28. data/lib/devp2p/kademlia/routing_table.rb +131 -0
  29. data/lib/devp2p/kademlia/wire_interface.rb +30 -0
  30. data/lib/devp2p/multiplexed_session.rb +110 -0
  31. data/lib/devp2p/multiplexer.rb +358 -0
  32. data/lib/devp2p/p2p_protocol.rb +170 -0
  33. data/lib/devp2p/packet.rb +35 -0
  34. data/lib/devp2p/peer.rb +329 -0
  35. data/lib/devp2p/peer_errors.rb +35 -0
  36. data/lib/devp2p/peer_manager.rb +274 -0
  37. data/lib/devp2p/rlpx_session.rb +434 -0
  38. data/lib/devp2p/sync_queue.rb +76 -0
  39. data/lib/devp2p/utils.rb +106 -0
  40. data/lib/devp2p/version.rb +13 -0
  41. data/lib/devp2p/wired_service.rb +30 -0
  42. metadata +227 -0
@@ -0,0 +1,30 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+ module Kademlia
5
+
6
+ ##
7
+ # defines the methods used by KademliaProtocol
8
+ #
9
+ class WireInterface
10
+
11
+ def send_ping(node)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def send_pong(node, id)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def send_find_node(nodeid)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def send_neighbours(node, neighbours)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,110 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+
5
+ class MultiplexedSession < Multiplexer
6
+
7
+ def initialize(privkey, hello_packet, remote_pubkey=nil)
8
+ @hello_packet = hello_packet
9
+ @remote_pubkey = remote_pubkey
10
+
11
+ @message_queue = SyncQueue.new # wire msg egress queue
12
+ @packet_queue = SyncQueue.new # packet ingress queue
13
+
14
+ @is_initiator = !!remote_pubkey
15
+ @handshake_finished = false
16
+
17
+ ecc = Crypto::ECCx.new privkey
18
+ @rlpx_session = RLPxSession.new ecc, @is_initiator
19
+
20
+ super(@rlpx_session)
21
+
22
+ send_init_msg if @is_initiator
23
+ end
24
+
25
+ def get_message
26
+ @message_queue.deq
27
+ end
28
+
29
+ def get_packet
30
+ @packet_queue.deq
31
+ end
32
+
33
+ def ready?
34
+ @rlpx_session.ready?
35
+ end
36
+
37
+ def initiator?
38
+ @is_initiator
39
+ end
40
+
41
+ def remote_pubkey
42
+ @remote_pubkey || @rlpx_session.remote_pubkey
43
+ end
44
+
45
+ def remote_pubkey=(key)
46
+ @remote_pubkey = key
47
+ end
48
+
49
+ def add_message(msg)
50
+ @handshake_finished ? add_message_post_handshake(msg) : add_message_during_handshake(msg)
51
+ end
52
+
53
+ ##
54
+ # encodes a packet and adds the message(s) to the message queue.
55
+ #
56
+ def add_packet(packet)
57
+ raise MultiplexedSessionError, 'session is not ready' unless ready?
58
+ raise ArgumentError, 'packet must be instance of Packet' unless packet.is_a?(Packet)
59
+
60
+ super(packet)
61
+
62
+ pop_all_frames.each {|f| @message_queue.enq f.as_bytes }
63
+ end
64
+
65
+ private
66
+
67
+ def send_init_msg
68
+ auth_msg = @rlpx_session.create_auth_message @remote_pubkey
69
+ auth_msg_ct = @rlpx_session.encrypt_auth_message auth_msg
70
+
71
+ @message_queue.enq auth_msg_ct
72
+ end
73
+
74
+ def add_message_during_handshake(msg)
75
+ raise MultiplexedSessionError, 'handshake after ready is not allowed' if ready?
76
+
77
+ if initiator?
78
+ # expecting auth ack message
79
+ rest = @rlpx_session.decode_auth_ack_message msg
80
+ @rlpx_session.setup_cipher
81
+
82
+ # add remains (hello) to queue
83
+ add_message_post_handshake(rest) unless rest.empty?
84
+ else
85
+ # expecting auth_init
86
+ rest = @rlpx_session.decode_authentication msg
87
+ auth_ack_msg = @rlpx_session.create_auth_ack_message
88
+ auth_ack_msg_ct = @rlpx_session.encrypt_auth_ack_message auth_ack_msg
89
+
90
+ @message_queue.enq auth_ack_msg_ct
91
+
92
+ @rlpx_session.setup_cipher
93
+ add_message_post_handshake(rest) unless rest.empty?
94
+ end
95
+
96
+ @handshake_finished = true
97
+ raise MultiplexedSessionError, 'session is not ready after handshake' unless @rlpx_session.ready?
98
+
99
+ add_packet @hello_packet
100
+ end
101
+
102
+ def add_message_post_handshake(msg)
103
+ decode(msg).each do |packet|
104
+ @packet_queue.enq packet
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,358 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+
5
+ ##
6
+ # Multiplexing of protocols is performed via dynamic framing and fair
7
+ # queueing. Dequeuing packets is performed in a cycle which dequeues one or
8
+ # more packets from the queue(s) of each active protocol. The multiplexor
9
+ # determines the amount of bytes to send for each protocol prior to each
10
+ # round of dequeuing packets.
11
+ #
12
+ # If the size of an RLP-encoded packet is less than 1KB then the protocol may
13
+ # request that the network layer prioritize the delivery of the packet. This
14
+ # should be used if and only if the packet must be delivered before all other
15
+ # packets.
16
+ #
17
+ # The network layer maintains two queues and three buffers per protocol:
18
+ #
19
+ # * a queue for normal packets, a queue for priority packets
20
+ # * a chunked-frame buffer, a normal-frame buffer, and a priority-frame buffer
21
+ #
22
+ # Implemented Variant:
23
+ #
24
+ # each sub protocol has three queues: prio, normal, chunked
25
+ #
26
+ # protocols are queried round robin
27
+ #
28
+ class Multiplexer
29
+
30
+ extend Configurable
31
+ add_config(
32
+ max_window_size: 8 * 1024,
33
+ max_priority_frame_size: 1024,
34
+ max_payload_size: 10 * 1024**2,
35
+ frame_cipher: nil,
36
+ )
37
+
38
+ attr :decode_buffer
39
+
40
+ def initialize(frame_cipher=nil)
41
+ @frame_cipher = frame_cipher || self.class.frame_cipher
42
+ @last_protocol = nil
43
+ @decode_buffer = "" # byte array
44
+
45
+ # protocol_id: {normal: queue, chunked: queue, prio: queue}
46
+ @queues = {}
47
+
48
+ # protocol_id: counter
49
+ @sequence_id = {}
50
+
51
+ # decode: {protocol_id: {sequence_id: buffer}
52
+ @chunked_buffers = {}
53
+ end
54
+
55
+ ##
56
+ # A protocol is considered active if it's queue contains one or more
57
+ # packets.
58
+ #
59
+ def num_active_protocols
60
+ @queues.keys.select {|id| active_protocol?(id) }.size
61
+ end
62
+
63
+ def active_protocol?(id)
64
+ !@queues[id].values.all?(&:empty?)
65
+ end
66
+
67
+ # pws = protocol_window_size = window_size / active_protocol_count
68
+ def protocol_window_size(id=nil)
69
+ if id && !active_protocol?(id)
70
+ s = max_window_size / (1 + num_active_protocols)
71
+ else
72
+ s = max_window_size / [1, num_active_protocols].max
73
+ end
74
+
75
+ s - s % 16 # should be a multiple of padding size # FIXME: 16 should be constant
76
+ end
77
+
78
+ def add_protocol(id)
79
+ raise ArgumentError, 'protocol already added' if @queues.include?(id)
80
+
81
+ @queues[id] = {
82
+ normal: SyncQueue.new,
83
+ chunked: SyncQueue.new,
84
+ priority: SyncQueue.new
85
+ }
86
+ @sequence_id[id] = 0
87
+ @chunked_buffers[id] = {}
88
+ @last_protocol = id
89
+ end
90
+
91
+ def next_protocol
92
+ protocols = @queues.keys
93
+ if @last_protocol == protocols.last
94
+ proto = protocols.first
95
+ else
96
+ proto = protocols[protocols.index(@last_protocol) + 1]
97
+ end
98
+
99
+ @last_protocol = proto
100
+ proto
101
+ end
102
+
103
+ def add_packet(packet)
104
+ sid = @sequence_id[packet.protocol_id]
105
+ @sequence_id[packet.protocol_id] = (sid + 1) % TT16
106
+
107
+ frames = Frame.new(
108
+ packet.protocol_id, packet.cmd_id, packet.payload, sid,
109
+ protocol_window_size(packet.protocol_id),
110
+ false, nil, @frame_cipher
111
+ ).frames
112
+
113
+ queues = @queues[packet.protocol_id]
114
+
115
+ if packet.prioritize
116
+ raise FrameError, "invalid priority packet frames" unless frames.size == 1
117
+ raise FrameError, "frame too large for priority packet" unless frames[0].frame_size <= max_priority_frame_size
118
+
119
+ queues[:priority].enq frames[0]
120
+ elsif frames.size == 1
121
+ queues[:normal].enq frames[0]
122
+ else
123
+ frames.each {|f| queues[:chunked].enq f }
124
+ end
125
+ end
126
+
127
+ ##
128
+ # If priority packet and normal packet exist:
129
+ # send up to pws/2 bytes from each (priority first)
130
+ # else if priority packet and chunked-frame exist:
131
+ # send up to pws/2 bytes from each
132
+ # else if normal packet and chunked-frame exist:
133
+ # send up to pws/2 bytes from each
134
+ # else
135
+ # read pws bytes from active buffer
136
+ #
137
+ # If there are bytes leftover -- for example, if the bytes sent is < pws,
138
+ # then repeat the cycle.
139
+ #
140
+ def pop_frames_for_protocol(id)
141
+ pws = protocol_window_size
142
+ queues = @queues[id]
143
+
144
+ frames = []
145
+ size = 0
146
+
147
+ while size < pws
148
+ frames_added = 0
149
+
150
+ %i(priority normal chunked).each do |qn|
151
+ q = queues[qn]
152
+
153
+ if !q.empty?
154
+ fs = q.peek.frame_size
155
+ if size + fs <= pws
156
+ frames.push q.deq
157
+ size += fs
158
+ frames_added += 1
159
+ end
160
+ end
161
+
162
+ # add no more than two in order to send normal and priority first
163
+ # i.e. next is 'priority' again
164
+ #
165
+ # FIXME: too weird
166
+ #
167
+ break if frames_added == 2
168
+ end
169
+
170
+ break if frames_added == 0 # empty queues
171
+ end
172
+
173
+ # the following can not be guaranteed, as pws might have been different
174
+ # at the time where packets were framed and added to the queues
175
+ #
176
+ # frames.map(&:frame_size).sum <= pws
177
+ return frames
178
+ end
179
+
180
+ ##
181
+ # Returns the frames for the next protocol up to protocol window size bytes.
182
+ #
183
+ def pop_frames
184
+ protocols = @queues.keys
185
+ idx = protocols.index next_protocol
186
+ protocols = protocols[idx..-1] + protocols[0,idx]
187
+
188
+ protocols.each do |id|
189
+ frames = pop_frames_for_protocol id
190
+ return frames unless frames.empty?
191
+ end
192
+
193
+ []
194
+ end
195
+
196
+ def pop_all_frames
197
+ frames = []
198
+ loop do
199
+ r = pop_frames
200
+ frames.concat r
201
+ break if r.empty?
202
+ end
203
+ frames
204
+ end
205
+
206
+ def pop_all_frames_as_bytes
207
+ pop_all_frames.map(&:as_bytes).join
208
+ end
209
+
210
+ def decode_header(buffer)
211
+ raise ArgumentError, "buffer too small" unless buffer.size >= 32
212
+
213
+ if @frame_cipher
214
+ header = @frame_cipher.decrypt_header(buffer[0, Frame.header_size + Frame.mac_size])
215
+ else
216
+ # header: frame-size || header-data || padding
217
+ header = buffer[0, Frame.header_size]
218
+ end
219
+
220
+ header
221
+ end
222
+
223
+ ##
224
+ # w/o encryption
225
+ # peak info buffer for body_size
226
+ #
227
+ # return nil if buffer is not long enough to decode frame
228
+ #
229
+ def decode_body(buffer, header=nil)
230
+ return [nil, buffer] if buffer.size < Frame.header_size
231
+
232
+ header ||= decode_header buffer[0, Frame.header_size + Frame.mac_size]
233
+ body_size = Frame.decode_body_size header
234
+
235
+ if @frame_cipher
236
+ body = @frame_cipher.decrypt_body(buffer[(Frame.header_size+Frame.mac_size)..-1], body_size)
237
+ raise MultiplexerError, 'body length mismatch' unless body.size == body_size
238
+
239
+ bytes_read = Frame.header_size + Frame.mac_size + Utils.ceil16(body.size) + Frame.mac_size
240
+ else
241
+ header = buffer[0, Frame.header_size]
242
+ body_offset = Frame.header_size + Frame.mac_size
243
+ body = buffer[body_offset, body_size]
244
+ raise MultiplexerError, 'body length mismatch' unless body.size == body_size
245
+
246
+ bytes_read = Utils.ceil16(body_offset + body_size + Frame.mac_size)
247
+ end
248
+ raise MultiplexerError, "bytes not padded" unless bytes_read % Frame.padding == 0
249
+
250
+ # normal, chunked-n: RLP::List.new(protocol_type[, sequence_id])
251
+ # chunked-0: RLP::List.new(protocol_type, sequence_id, total_packet_size)
252
+ header_data = nil
253
+ begin
254
+ header_data = RLP.decode(header[3..-1], sedes: Frame.header_sedes, strict: false)
255
+ rescue RLP::Error::RLPException => e
256
+ logger.error(e)
257
+ raise MultiplexerError, 'invalid rlp data'
258
+ end
259
+
260
+ if header_data.size == 3
261
+ chunked_0 = true
262
+ total_payload_size = header_data[2]
263
+ raise MultiplexerError, "invalid total payload size" unless total_payload_size < 2**32
264
+ else
265
+ chunked_0 = false
266
+ total_payload_size = nil
267
+ end
268
+
269
+ protocol_id = header_data[0]
270
+ raise MultiplexerError, "invalid protocol id" unless protocol_id < TT16
271
+
272
+ if header_data.size > 1
273
+ sequence_id = header_data[1]
274
+ raise MultiplexerError, "invalid sequence id" unless sequence_id < TT16
275
+ else
276
+ sequence_id = nil
277
+ end
278
+
279
+ raise MultiplexerError, "unknown protocol id #{protocol_id}" unless @chunked_buffers.has_key?(protocol_id)
280
+
281
+ chunkbuf = @chunked_buffers[protocol_id]
282
+ if chunkbuf.has_key?(sequence_id)
283
+ packet = chunkbuf[sequence_id]
284
+
285
+ raise MultiplexerError, "received chunked_0 frame for existing buffer #{sequence_id} of protocol #{protocol_id}" if chunked_0
286
+ raise MultiplexerError, "too much data for chunked buffer #{sequence_id} of protocol #{protocol_id}" if body.size > (packet.total_payload_size - packet.payload.size)
287
+
288
+ packet.payload += body
289
+ if packet.total_payload_size == packet.payload.size
290
+ packet.total_payload_size = nil
291
+ chunkbuf.delete sequence_id
292
+ return packet
293
+ end
294
+ else
295
+ # body of normal, chunked_0: rlp(packet-type) [|| rlp(packet-data)] || padding
296
+ item, item_end = RLP.consume_item(body, 0)
297
+ cmd_id = RLP::Sedes.big_endian_int.deserialize item
298
+
299
+ if chunked_0
300
+ payload = body[item_end..-1]
301
+ total_payload_size -= item_end
302
+ else
303
+ payload = body[item_end..-1]
304
+ end
305
+
306
+ packet = Packet.new protocol_id, cmd_id, payload
307
+ if chunked_0
308
+ raise MultiplexerError, "total payload size smaller than initial chunk" if total_payload_size < payload.size
309
+
310
+ # shouldn't have been chunked, whatever
311
+ return packet if total_payload_size == payload.size
312
+
313
+ raise MultiplexerError, 'chunked_0 must have sequence id' if sequence_id.nil?
314
+
315
+ packet.total_payload_size = total_payload_size
316
+ chunkbuf[sequence_id] = packet
317
+
318
+ return nil
319
+ else
320
+ return packet # normal (non-chunked)
321
+ end
322
+ end
323
+ end
324
+
325
+ def decode(data='')
326
+ @decode_buffer.concat(data) unless data.empty?
327
+
328
+ unless @cached_decode_header
329
+ if @decode_buffer.size < Frame.header_size + Frame.mac_size
330
+ return []
331
+ else
332
+ @cached_decode_header = decode_header @decode_buffer
333
+ end
334
+ end
335
+
336
+ body_size = Frame.decode_body_size @cached_decode_header
337
+ required_len = Frame.header_size + Frame.mac_size + Utils.ceil16(body_size) + Frame.mac_size
338
+
339
+ if @decode_buffer.size >= required_len
340
+ packet = decode_body @decode_buffer, @cached_decode_header
341
+ @cached_decode_header = nil
342
+ @decode_buffer = @decode_buffer[required_len..-1]
343
+
344
+ return packet ? ([packet] + decode) : decode
345
+ end
346
+
347
+ []
348
+ end
349
+
350
+ private
351
+
352
+ def logger
353
+ @logger ||= Logger.new('multiplexer')
354
+ end
355
+
356
+ end
357
+
358
+ end