ciri-p2p 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.
- 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
|