ciri-p2p 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +15 -0
- data/.vscode/launch.json +90 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +6 -0
- data/bin/bundle +105 -0
- data/bin/console +14 -0
- data/bin/htmldiff +29 -0
- data/bin/ldiff +29 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/ciri-p2p.gemspec +37 -0
- data/lib/ciri/p2p.rb +7 -0
- data/lib/ciri/p2p/address.rb +51 -0
- data/lib/ciri/p2p/dial_scheduler.rb +73 -0
- data/lib/ciri/p2p/dialer.rb +55 -0
- data/lib/ciri/p2p/discovery/protocol.rb +237 -0
- data/lib/ciri/p2p/discovery/service.rb +255 -0
- data/lib/ciri/p2p/errors.rb +36 -0
- data/lib/ciri/p2p/kad.rb +301 -0
- data/lib/ciri/p2p/network_state.rb +223 -0
- data/lib/ciri/p2p/node.rb +96 -0
- data/lib/ciri/p2p/peer.rb +151 -0
- data/lib/ciri/p2p/peer_store.rb +183 -0
- data/lib/ciri/p2p/protocol.rb +62 -0
- data/lib/ciri/p2p/protocol_context.rb +54 -0
- data/lib/ciri/p2p/protocol_io.rb +65 -0
- data/lib/ciri/p2p/rlpx.rb +29 -0
- data/lib/ciri/p2p/rlpx/connection.rb +182 -0
- data/lib/ciri/p2p/rlpx/encryption_handshake.rb +143 -0
- data/lib/ciri/p2p/rlpx/errors.rb +34 -0
- data/lib/ciri/p2p/rlpx/frame_io.rb +229 -0
- data/lib/ciri/p2p/rlpx/message.rb +45 -0
- data/lib/ciri/p2p/rlpx/protocol_handshake.rb +56 -0
- data/lib/ciri/p2p/rlpx/protocol_messages.rb +71 -0
- data/lib/ciri/p2p/rlpx/secrets.rb +49 -0
- data/lib/ciri/p2p/server.rb +159 -0
- data/lib/ciri/p2p/version.rb +5 -0
- metadata +229 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
|
4
|
+
# Copyright (c) 2018 by Jiang Jinyang <jjyruby@gmail.com>
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
|
25
|
+
require 'ciri/key'
|
26
|
+
require_relative 'secrets'
|
27
|
+
|
28
|
+
module Ciri
|
29
|
+
module P2P
|
30
|
+
module RLPX
|
31
|
+
|
32
|
+
SHA_LENGTH = 32
|
33
|
+
SIGNATURE_LENGTH = 65
|
34
|
+
PUBLIC_KEY_LENGTH = 64
|
35
|
+
ECIES_OVERHEAD = 65 + 16 + 32
|
36
|
+
AUTH_MSG_LENGTH = SIGNATURE_LENGTH + SHA_LENGTH + PUBLIC_KEY_LENGTH + SHA_LENGTH + 1
|
37
|
+
AUTH_RESP_MSG_LENGTH = PUBLIC_KEY_LENGTH + SHA_LENGTH + 1
|
38
|
+
|
39
|
+
HANDSHAKE_TIMEOUT = 5
|
40
|
+
|
41
|
+
ENC_AUTH_MSG_LENGTH = AUTH_MSG_LENGTH + ECIES_OVERHEAD
|
42
|
+
ENC_AUTH_RESP_MSG_LENGTH = AUTH_RESP_MSG_LENGTH + ECIES_OVERHEAD
|
43
|
+
|
44
|
+
# handle key exchange handshake
|
45
|
+
class EncryptionHandshake
|
46
|
+
attr_reader :private_key, :remote_key, :remote_random_key, :initiator_nonce, :receiver_nonce, :remote_id
|
47
|
+
|
48
|
+
def initialize(private_key:, remote_id:)
|
49
|
+
@private_key = private_key
|
50
|
+
@remote_id = remote_id
|
51
|
+
end
|
52
|
+
|
53
|
+
def remote_key
|
54
|
+
@remote_key || @remote_id.key
|
55
|
+
end
|
56
|
+
|
57
|
+
def random_key
|
58
|
+
@random_key ||= Ciri::Key.random
|
59
|
+
end
|
60
|
+
|
61
|
+
def auth_msg
|
62
|
+
# make nonce bytes
|
63
|
+
nonce = random_nonce(SHA_LENGTH)
|
64
|
+
@initiator_nonce = nonce
|
65
|
+
# remote first byte tag
|
66
|
+
token = dh_compute_key(private_key, remote_key)
|
67
|
+
raise StandardError.new("token size #{token.size} not correct") if token.size != nonce.size
|
68
|
+
# xor
|
69
|
+
signed = xor(token, nonce)
|
70
|
+
|
71
|
+
signature = random_key.ecdsa_signature(signed).signature
|
72
|
+
initiator_pubkey = private_key.raw_public_key[1..-1]
|
73
|
+
AuthMsgV4.new(signature: signature, initiator_pubkey: initiator_pubkey, nonce: nonce, version: 4)
|
74
|
+
end
|
75
|
+
|
76
|
+
def handle_auth_msg(msg)
|
77
|
+
@remote_key = Ciri::Key.new(raw_public_key: "\x04" + msg.initiator_pubkey)
|
78
|
+
@initiator_nonce = msg.nonce
|
79
|
+
|
80
|
+
token = dh_compute_key(private_key, @remote_key)
|
81
|
+
signed = xor(token, msg.nonce)
|
82
|
+
@remote_random_key = Ciri::Key.ecdsa_recover(signed, msg.signature)
|
83
|
+
end
|
84
|
+
|
85
|
+
def auth_ack_msg
|
86
|
+
# make nonce bytes
|
87
|
+
nonce = random_nonce(SHA_LENGTH)
|
88
|
+
@receiver_nonce = nonce
|
89
|
+
random_pubkey = random_key.raw_public_key[1..-1]
|
90
|
+
AuthRespV4.new(random_pubkey: random_pubkey, nonce: nonce, version: 4)
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_auth_ack_msg(msg)
|
94
|
+
# make nonce bytes
|
95
|
+
@receiver_nonce = msg.nonce
|
96
|
+
@remote_random_key = Ciri::Key.new(raw_public_key: "\x04" + msg.random_pubkey)
|
97
|
+
end
|
98
|
+
|
99
|
+
def extract_secrets(auth_packet, auth_ack_packet, initiator:)
|
100
|
+
secret = dh_compute_key(random_key, remote_random_key)
|
101
|
+
shared_secret = Ciri::Utils.keccak(secret, Ciri::Utils.keccak(receiver_nonce, initiator_nonce))
|
102
|
+
aes_secret = Ciri::Utils.keccak(secret, shared_secret)
|
103
|
+
mac = Ciri::Utils.keccak(secret, aes_secret)
|
104
|
+
secrets = Secrets.new(remote_id: remote_id, aes: aes_secret, mac: mac)
|
105
|
+
|
106
|
+
# initial secrets macs
|
107
|
+
mac1 = Digest::SHA3.new(256)
|
108
|
+
mac1.update xor(mac, receiver_nonce)
|
109
|
+
mac1.update auth_packet
|
110
|
+
|
111
|
+
mac2 = Digest::SHA3.new(256)
|
112
|
+
mac2.update xor(mac, initiator_nonce)
|
113
|
+
mac2.update auth_ack_packet
|
114
|
+
|
115
|
+
if initiator
|
116
|
+
secrets.egress_mac = mac1
|
117
|
+
secrets.ingress_mac = mac2
|
118
|
+
else
|
119
|
+
secrets.egress_mac = mac2
|
120
|
+
secrets.ingress_mac = mac1
|
121
|
+
end
|
122
|
+
secrets
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def dh_compute_key(private_key, public_key)
|
128
|
+
private_key.ec_key.dh_compute_key(public_key.ec_key.public_key)
|
129
|
+
end
|
130
|
+
|
131
|
+
def xor(b1, b2)
|
132
|
+
b1.each_byte.with_index.map {|b, i| b ^ b2[i].ord}.pack('c*')
|
133
|
+
end
|
134
|
+
|
135
|
+
def random_nonce(size)
|
136
|
+
size.times.map {rand(8)}.pack('c*')
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 by Jiang Jinyang <jjyruby@gmail.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
|
24
|
+
module Ciri
|
25
|
+
module P2P
|
26
|
+
module RLPX
|
27
|
+
|
28
|
+
# RLPX basic error
|
29
|
+
class Error < StandardError
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 by Jiang Jinyang <jjyruby@gmail.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
|
24
|
+
require 'stringio'
|
25
|
+
require 'forwardable'
|
26
|
+
require 'ciri/core_ext'
|
27
|
+
require 'ciri/rlp/serializable'
|
28
|
+
require_relative 'errors'
|
29
|
+
require_relative 'message'
|
30
|
+
|
31
|
+
require 'snappy'
|
32
|
+
|
33
|
+
using Ciri::CoreExt
|
34
|
+
|
35
|
+
module Ciri
|
36
|
+
module P2P
|
37
|
+
module RLPX
|
38
|
+
|
39
|
+
class FrameIO
|
40
|
+
extend Forwardable
|
41
|
+
def_delegators :@io, :closed?, :close, :flush
|
42
|
+
|
43
|
+
# max message size, took 3 byte to store message size, equal to uint24 max size
|
44
|
+
MAX_MESSAGE_SIZE = (1 << 24) - 1
|
45
|
+
|
46
|
+
class Error < RLPX::Error
|
47
|
+
end
|
48
|
+
|
49
|
+
class OverflowError < Error
|
50
|
+
end
|
51
|
+
|
52
|
+
class InvalidError < Error
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_accessor :snappy
|
56
|
+
|
57
|
+
def initialize(io, secrets)
|
58
|
+
@io = io
|
59
|
+
@secrets = secrets
|
60
|
+
@snappy = false # snappy compress
|
61
|
+
|
62
|
+
mac_aes_version = secrets.mac.size * 8
|
63
|
+
@mac = OpenSSL::Cipher.new("AES#{mac_aes_version}")
|
64
|
+
@mac.encrypt
|
65
|
+
@mac.key = secrets.mac
|
66
|
+
|
67
|
+
# init encrypt/decrypt
|
68
|
+
aes_version = secrets.aes.size * 8
|
69
|
+
@encrypt = OpenSSL::Cipher::AES.new(aes_version, :CTR)
|
70
|
+
@decrypt = OpenSSL::Cipher::AES.new(aes_version, :CTR)
|
71
|
+
zero_iv = "\x00".b * @encrypt.iv_len
|
72
|
+
@encrypt.iv = zero_iv
|
73
|
+
@encrypt.key = secrets.aes
|
74
|
+
@decrypt.iv = zero_iv
|
75
|
+
@decrypt.key = secrets.aes
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_data(code, data)
|
79
|
+
msg = Message.new(code: code, size: data.size, payload: data)
|
80
|
+
write_msg(msg)
|
81
|
+
end
|
82
|
+
|
83
|
+
def write_msg(msg)
|
84
|
+
pkg_type = RLP.encode_with_type msg.code, Integer, zero: "\x00"
|
85
|
+
|
86
|
+
# use snappy compress if enable
|
87
|
+
if snappy
|
88
|
+
if msg.size > MAX_MESSAGE_SIZE
|
89
|
+
raise OverflowError.new("Message size is overflow, msg size: #{msg.size}")
|
90
|
+
end
|
91
|
+
msg.payload = Snappy.deflate(msg.payload)
|
92
|
+
msg.size = msg.payload.size
|
93
|
+
end
|
94
|
+
|
95
|
+
# write header
|
96
|
+
head_buf = "\x00".b * 32
|
97
|
+
|
98
|
+
frame_size = pkg_type.size + msg.size
|
99
|
+
if frame_size > MAX_MESSAGE_SIZE
|
100
|
+
raise OverflowError.new("Message size is overflow, frame size: #{frame_size}")
|
101
|
+
end
|
102
|
+
|
103
|
+
write_frame_size(head_buf, frame_size)
|
104
|
+
|
105
|
+
# Can't find related RFC or RLPX Spec, below code is copy from geth
|
106
|
+
# write zero header, but I can't find spec or explanations of 'zero header'
|
107
|
+
head_buf[3..5] = [0xC2, 0x80, 0x80].pack('c*')
|
108
|
+
# encrypt first half
|
109
|
+
head_buf[0...16] = @encrypt.update(head_buf[0...16]) + @encrypt.final
|
110
|
+
# write header mac
|
111
|
+
head_buf[16...32] = update_mac(@secrets.egress_mac, head_buf[0...16])
|
112
|
+
@io.write head_buf
|
113
|
+
# write encrypt frame
|
114
|
+
write_frame(pkg_type)
|
115
|
+
write_frame(msg.payload)
|
116
|
+
# pad to n*16 bytes
|
117
|
+
if (need_padding = frame_size % 16) > 0
|
118
|
+
write_frame("\x00".b * (16 - need_padding))
|
119
|
+
end
|
120
|
+
finish_write_frame
|
121
|
+
# because we use Async::IO::Stream as IO object, we must invoke flush to make sure data is send
|
122
|
+
flush
|
123
|
+
end
|
124
|
+
|
125
|
+
def read_msg
|
126
|
+
# verify header mac
|
127
|
+
head_buf = read(32)
|
128
|
+
verify_mac = update_mac(@secrets.ingress_mac, head_buf[0...16])
|
129
|
+
unless Ciri::Utils.secret_compare(verify_mac, head_buf[16...32])
|
130
|
+
raise InvalidError.new('bad header mac')
|
131
|
+
end
|
132
|
+
|
133
|
+
# decrypt header
|
134
|
+
head_buf[0...16] = @decrypt.update(head_buf[0...16]) + @decrypt.final
|
135
|
+
|
136
|
+
# read frame
|
137
|
+
frame_size = read_frame_size head_buf
|
138
|
+
# frame size should padded to n*16 bytes
|
139
|
+
need_padding = frame_size % 16
|
140
|
+
padded_frame_size = need_padding > 0 ? frame_size + (16 - need_padding) : frame_size
|
141
|
+
frame_buf = read(padded_frame_size)
|
142
|
+
|
143
|
+
# verify frame mac
|
144
|
+
@secrets.ingress_mac.update(frame_buf)
|
145
|
+
frame_digest = @secrets.ingress_mac.digest
|
146
|
+
verify_mac = update_mac(@secrets.ingress_mac, frame_digest)
|
147
|
+
# clear head_buf 16...32 bytes(header mac), since we will not need it
|
148
|
+
frame_mac = head_buf[16...32] = read(16)
|
149
|
+
unless Ciri::Utils.secret_compare(verify_mac, frame_mac)
|
150
|
+
raise InvalidError.new('bad frame mac')
|
151
|
+
end
|
152
|
+
|
153
|
+
# decrypt frame
|
154
|
+
frame_content = @decrypt.update(frame_buf) + @decrypt.final
|
155
|
+
frame_content = frame_content[0...frame_size]
|
156
|
+
msg_code = RLP.decode_with_type frame_content[0], Integer
|
157
|
+
msg = Message.new(code: msg_code, size: frame_content.size - 1, payload: frame_content[1..-1])
|
158
|
+
|
159
|
+
# snappy decompress if enable
|
160
|
+
if snappy
|
161
|
+
msg.payload = Snappy.inflate(msg.payload)
|
162
|
+
msg.size = msg.payload.size
|
163
|
+
end
|
164
|
+
|
165
|
+
msg
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
def read(length)
|
170
|
+
if (buf = @io.read(length)).nil?
|
171
|
+
@io.close
|
172
|
+
raise EOFError.new('read EOF, connection closed')
|
173
|
+
end
|
174
|
+
buf
|
175
|
+
end
|
176
|
+
|
177
|
+
def write_frame_size(buf, frame_size)
|
178
|
+
# frame-size: 3-byte integer size of frame, big endian encoded (excludes padding)
|
179
|
+
bytes_of_frame_size = [
|
180
|
+
frame_size >> 16,
|
181
|
+
frame_size >> 8,
|
182
|
+
frame_size % 256
|
183
|
+
]
|
184
|
+
buf[0..2] = bytes_of_frame_size.pack('c*')
|
185
|
+
end
|
186
|
+
|
187
|
+
def read_frame_size(buf)
|
188
|
+
size_bytes = buf[0..2].each_byte.map(&:ord)
|
189
|
+
(size_bytes[0] << 16) + (size_bytes[1] << 8) + (size_bytes[2])
|
190
|
+
end
|
191
|
+
|
192
|
+
def update_mac(mac, seed)
|
193
|
+
# reset mac each time
|
194
|
+
@mac.reset
|
195
|
+
aes_buf = (@mac.update(mac.digest) + @mac.final)[0...@mac.block_size]
|
196
|
+
aes_buf = aes_buf.each_byte.with_index.map {|b, i| b ^ seed[i].ord}.pack('c*')
|
197
|
+
mac.update(aes_buf)
|
198
|
+
# return first 16 byte
|
199
|
+
mac.digest[0...16]
|
200
|
+
end
|
201
|
+
|
202
|
+
# write encrypt content to @io, and update @secrets.egress_mac
|
203
|
+
def write_frame(string_or_io)
|
204
|
+
if string_or_io.is_a?(IO)
|
205
|
+
while (s = string_or_io.read(4096))
|
206
|
+
write_frame_string(s)
|
207
|
+
end
|
208
|
+
else
|
209
|
+
write_frame_string(string_or_io)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def write_frame_string(s)
|
214
|
+
encrypt_content = @encrypt.update(s) + @encrypt.final
|
215
|
+
# update egress_mac
|
216
|
+
@secrets.egress_mac.update encrypt_content
|
217
|
+
@io.write encrypt_content
|
218
|
+
end
|
219
|
+
|
220
|
+
def finish_write_frame
|
221
|
+
# get frame digest
|
222
|
+
frame_digest = @secrets.egress_mac.digest
|
223
|
+
@io.write update_mac(@secrets.egress_mac, frame_digest)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018 by Jiang Jinyang <jjyruby@gmail.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
|
24
|
+
require 'ciri/rlp'
|
25
|
+
|
26
|
+
module Ciri
|
27
|
+
module P2P
|
28
|
+
module RLPX
|
29
|
+
|
30
|
+
# RLPX message
|
31
|
+
class Message
|
32
|
+
include Ciri::RLP::Serializable
|
33
|
+
|
34
|
+
attr_accessor :received_at
|
35
|
+
|
36
|
+
schema(
|
37
|
+
code: Integer,
|
38
|
+
size: Integer,
|
39
|
+
payload: RLP::Bytes
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|