devp2p 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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