ciri-p2p 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +15 -0
  5. data/.vscode/launch.json +90 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +65 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +45 -0
  11. data/Rakefile +6 -0
  12. data/bin/bundle +105 -0
  13. data/bin/console +14 -0
  14. data/bin/htmldiff +29 -0
  15. data/bin/ldiff +29 -0
  16. data/bin/rake +29 -0
  17. data/bin/rspec +29 -0
  18. data/bin/setup +8 -0
  19. data/ciri-p2p.gemspec +37 -0
  20. data/lib/ciri/p2p.rb +7 -0
  21. data/lib/ciri/p2p/address.rb +51 -0
  22. data/lib/ciri/p2p/dial_scheduler.rb +73 -0
  23. data/lib/ciri/p2p/dialer.rb +55 -0
  24. data/lib/ciri/p2p/discovery/protocol.rb +237 -0
  25. data/lib/ciri/p2p/discovery/service.rb +255 -0
  26. data/lib/ciri/p2p/errors.rb +36 -0
  27. data/lib/ciri/p2p/kad.rb +301 -0
  28. data/lib/ciri/p2p/network_state.rb +223 -0
  29. data/lib/ciri/p2p/node.rb +96 -0
  30. data/lib/ciri/p2p/peer.rb +151 -0
  31. data/lib/ciri/p2p/peer_store.rb +183 -0
  32. data/lib/ciri/p2p/protocol.rb +62 -0
  33. data/lib/ciri/p2p/protocol_context.rb +54 -0
  34. data/lib/ciri/p2p/protocol_io.rb +65 -0
  35. data/lib/ciri/p2p/rlpx.rb +29 -0
  36. data/lib/ciri/p2p/rlpx/connection.rb +182 -0
  37. data/lib/ciri/p2p/rlpx/encryption_handshake.rb +143 -0
  38. data/lib/ciri/p2p/rlpx/errors.rb +34 -0
  39. data/lib/ciri/p2p/rlpx/frame_io.rb +229 -0
  40. data/lib/ciri/p2p/rlpx/message.rb +45 -0
  41. data/lib/ciri/p2p/rlpx/protocol_handshake.rb +56 -0
  42. data/lib/ciri/p2p/rlpx/protocol_messages.rb +71 -0
  43. data/lib/ciri/p2p/rlpx/secrets.rb +49 -0
  44. data/lib/ciri/p2p/server.rb +159 -0
  45. data/lib/ciri/p2p/version.rb +5 -0
  46. metadata +229 -0
@@ -0,0 +1,183 @@
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/utils/logger'
26
+
27
+ module Ciri
28
+ module P2P
29
+
30
+ # PeerStore store information of all peers we have seen
31
+ #TODO rewrite with a database(sqlite)
32
+ # Support score peers
33
+ class PeerStore
34
+ PEER_LAST_SEEN_VALID = 12 * 3600 # consider peer is valid if we seen it within 12 hours
35
+ PING_EXPIRATION_IN = 10 * 60 # allow ping within 10 minutes
36
+
37
+ # report peer behaviours
38
+ module Behaviours
39
+ INVALID_DATA = :invalid_data
40
+ CONNECT = :connect
41
+ PING = :ping
42
+ FAILED_TO_CONNECT = :failed_to_connect
43
+ FAILED_TO_PING = :failed_to_ping
44
+ UNEXPECT_DISCONNECT = :unexpect_disconnect
45
+ end
46
+
47
+ include Behaviours
48
+
49
+ # peer status
50
+ module Status
51
+ CONNECTED = :connected
52
+ DISCONNECTED = :disconnected
53
+ UNKNOWN = :unknown
54
+ end
55
+
56
+ include Status
57
+
58
+ PEER_INITIAL_SCORE = 100
59
+ DEFAULT_SCORE_SCHEMA = {
60
+ INVALID_DATA => -50,
61
+ CONNECT => 10,
62
+ PING => 5,
63
+ FAILED_TO_PING => -10,
64
+ FAILED_TO_CONNECT => -10,
65
+ UNEXPECT_DISCONNECT => -20,
66
+ }
67
+
68
+ def initialize(score_schema:{})
69
+ @peers_ping_records = {}
70
+ @peers_seen_records = {}
71
+ @peers = {}
72
+ @bootnodes = []
73
+ @ban_peers = {}
74
+ @score_schema = DEFAULT_SCORE_SCHEMA.merge(score_schema)
75
+ end
76
+
77
+ def has_ping?(raw_node_id, ping_hash, expires_in: PING_EXPIRATION_IN)
78
+ return false if has_ban?(raw_node_id)
79
+ record = @peers_ping_records[raw_node_id]
80
+ if record && record[:ping_hash] == ping_hash && (record[:ping_at] + expires_in) > Time.now.to_i
81
+ return true
82
+ elsif record
83
+ @peers_ping_records.delete(raw_node_id)
84
+ end
85
+ false
86
+ end
87
+
88
+ # record ping message
89
+ def update_ping(raw_node_id, ping_hash, ping_at: Time.now.to_i)
90
+ @peers_ping_records[raw_node_id] = {ping_hash: ping_hash, ping_at: ping_at}
91
+ end
92
+
93
+ def update_last_seen(raw_node_id, at: Time.now.to_i)
94
+ @peers_seen_records[raw_node_id] = at
95
+ end
96
+
97
+ def has_seen?(raw_node_id, expires_in: PEER_LAST_SEEN_VALID)
98
+ return false if has_ban?(raw_node_id)
99
+ seen = (last_seen_at = @peers_seen_records[raw_node_id]) && (last_seen_at + expires_in > Time.now.to_i)
100
+ # convert to bool
101
+ !!seen
102
+ end
103
+
104
+ def add_bootnode(node)
105
+ @bootnodes << node
106
+ add_node(node)
107
+ end
108
+
109
+ def has_ban?(raw_node_id, now: Time.now)
110
+ record = @ban_peers[raw_node_id]
111
+ if record && (record[:ban_at].to_i + record[:timeout_secs]) > now.to_i
112
+ true
113
+ else
114
+ @ban_peers.delete(raw_node_id)
115
+ false
116
+ end
117
+ end
118
+
119
+ def ban_peer(raw_node_id, now: Time.now, timeout_secs:600)
120
+ @ban_peers[raw_node_id] = {ban_at: now, timeout_secs: timeout_secs}
121
+ end
122
+
123
+ def report_peer(raw_node_id, behaviour)
124
+ score = @score_schema[behaviour]
125
+ raise ValueError.new("unsupport report behaviour: #{behaviour}") if score.nil?
126
+ if (node_info = @peers[raw_node_id])
127
+ node_info[:score] += score
128
+ end
129
+ end
130
+
131
+ # TODO find high scoring peers, use bootnodes as fallback
132
+ def find_bootnodes(count)
133
+ nodes = @bootnodes.sample(count)
134
+ nodes + find_attempt_peers(count - nodes.size)
135
+ end
136
+
137
+ # TODO find high scoring peers
138
+ def find_attempt_peers(count)
139
+ @peers.values.reject do |peer_info|
140
+ # reject already connected peers and bootnodes
141
+ @bootnodes.include?(peer_info[:node]) || peer_status(peer_info[:node].raw_node_id) == Status::CONNECTED
142
+ end.sort_by do |peer_info|
143
+ -peer_info[:score]
144
+ end.map do |peer_info|
145
+ peer_info[:node]
146
+ end.take(count)
147
+ end
148
+
149
+ def add_node_addresses(raw_node_id, addresses)
150
+ node_info = @peers[raw_node_id]
151
+ node = node_info && node_info[:node]
152
+ if node
153
+ node.addresses = (node.addresses + addresses).uniq
154
+ end
155
+ end
156
+
157
+ def get_node_addresses(raw_node_id)
158
+ peer_info = @peers[raw_node_id]
159
+ peer_info && peer_info[:node].addresses
160
+ end
161
+
162
+ def add_node(node)
163
+ @peers[node.raw_node_id] = {node: node, score: PEER_INITIAL_SCORE, status: Status::UNKNOWN}
164
+ end
165
+
166
+ def peer_status(raw_node_id)
167
+ if (peer_info = @peers[raw_node_id])
168
+ peer_info[:status]
169
+ else
170
+ Status::UNKNOWN
171
+ end
172
+ end
173
+
174
+ def update_peer_status(raw_node_id, status)
175
+ if (peer_info = @peers[raw_node_id])
176
+ peer_info[:status] = status
177
+ end
178
+ end
179
+ end
180
+
181
+ end
182
+ end
183
+
@@ -0,0 +1,62 @@
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/utils/logger'
26
+
27
+ module Ciri
28
+ module P2P
29
+
30
+ # protocol represent P2P sub protocols
31
+ class Protocol
32
+
33
+ include Utils::Logger
34
+
35
+ attr_reader :name, :version, :length
36
+
37
+ def initialize(name:, version:, length:)
38
+ @name = name
39
+ @version = version
40
+ @length = length
41
+ end
42
+
43
+ def initialized(context)
44
+ debug("not implemented Protocol#initialized callback")
45
+ end
46
+
47
+ def received(context, data)
48
+ debug("not implemented Protocol#received callback")
49
+ end
50
+
51
+ def connected(context)
52
+ debug("not implemented Protocol#connected callback")
53
+ end
54
+
55
+ def disconnected(context)
56
+ debug("not implemented Protocol#disconnected callback")
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+
@@ -0,0 +1,54 @@
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 'forwardable'
26
+
27
+ module Ciri
28
+ module P2P
29
+
30
+ # ProtocolContext is used to manuaplate
31
+ class ProtocolContext
32
+
33
+ extend Forwardable
34
+
35
+ attr_reader :peer, :protocol, :protocol_io
36
+
37
+ def_delegators :protocol_io, :send_data
38
+ def_delegators :@network_state, :local_node_id
39
+
40
+ def initialize(network_state, peer: nil, protocol: nil, protocol_io: nil)
41
+ @network_state = network_state
42
+ @peer = peer
43
+ @protocol = protocol
44
+ @protocol_io = protocol_io
45
+ end
46
+
47
+ def raw_local_node_id
48
+ @raw_local_node_id ||= local_node_id.to_bytes
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+
@@ -0,0 +1,65 @@
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 'forwardable'
25
+ require 'async/semaphore'
26
+ require_relative 'rlpx/message'
27
+
28
+ module Ciri
29
+ module P2P
30
+
31
+ # send/read sub protocol msg
32
+ class ProtocolIO
33
+
34
+ class Error < StandardError
35
+ end
36
+ class InvalidMessageCode < Error
37
+ end
38
+
39
+ attr_reader :protocol, :offset
40
+
41
+ def initialize(protocol, offset, frame_io)
42
+ @protocol = protocol
43
+ @offset = offset
44
+ @frame_io = frame_io
45
+ @semaphore = Async::Semaphore.new
46
+ end
47
+
48
+ def send_data(code, data)
49
+ @semaphore.acquire do
50
+ msg = RLPX::Message.new(code: code, size: data.size, payload: data)
51
+ write_msg(msg)
52
+ end
53
+ end
54
+
55
+ def write_msg(msg)
56
+ raise InvalidMessageCode, "code #{msg.code} must less than length #{protocol.length}" if msg.code > protocol.length
57
+ msg.code += offset
58
+ @frame_io.write_msg(msg)
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end
65
+
@@ -0,0 +1,29 @@
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
+ require_relative 'rlpx/message'
25
+ require_relative 'rlpx/frame_io'
26
+ require_relative 'rlpx/protocol_messages'
27
+ require_relative 'rlpx/protocol_handshake'
28
+ require_relative 'rlpx/encryption_handshake'
29
+
@@ -0,0 +1,182 @@
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
+ require 'socket'
26
+ require 'forwardable'
27
+ require_relative 'frame_io'
28
+ require_relative 'protocol_messages'
29
+ require_relative 'errors'
30
+ require_relative 'encryption_handshake'
31
+
32
+ module Ciri
33
+ module P2P
34
+ module RLPX
35
+
36
+ # RLPX::Connection implement RLPX protocol operations
37
+ # all operations end with bang(!)
38
+ class Connection
39
+ extend Forwardable
40
+
41
+ def_delegators :@frame_io, :read_msg, :write_msg, :send_data, :closed?, :close
42
+
43
+ class Error < RLPX::Error
44
+ end
45
+
46
+ class MessageOverflowError < Error
47
+ end
48
+
49
+ class UnexpectedMessageError < Error
50
+ end
51
+
52
+ class FormatError < Error
53
+ end
54
+
55
+ def initialize(io)
56
+ set_timeout(io)
57
+ @io = io
58
+ @frame_io = nil
59
+ end
60
+
61
+ # Encryption handshake, exchange keys with node, must been invoked before other operations
62
+ def encryption_handshake!(private_key:, remote_node_id: nil)
63
+ enc_handshake = EncryptionHandshake.new(private_key: private_key, remote_id: remote_node_id)
64
+ secrets = remote_node_id.nil? ? receiver_enc_handshake(enc_handshake) : initiator_enc_handshake(enc_handshake)
65
+ @frame_io = FrameIO.new(@io, secrets)
66
+ end
67
+
68
+ # protocol handshake
69
+ def protocol_handshake!(our_hs)
70
+ @frame_io.send_data(Code::HANDSHAKE, our_hs.rlp_encode)
71
+ remote_hs = read_protocol_handshake
72
+ # enable snappy compress if remote peer support
73
+ @frame_io.snappy = remote_hs.version >= SNAPPY_PROTOCOL_VERSION
74
+ remote_hs
75
+ end
76
+
77
+ private
78
+
79
+ def receiver_enc_handshake(receiver)
80
+ auth_msg_binary, auth_packet = read_enc_handshake_msg(ENC_AUTH_MSG_LENGTH, receiver.private_key)
81
+ auth_msg = AuthMsgV4.rlp_decode(auth_msg_binary)
82
+ receiver.handle_auth_msg(auth_msg)
83
+
84
+ auth_ack_msg = receiver.auth_ack_msg
85
+ auth_ack_msg_plain_text = auth_ack_msg.rlp_encode
86
+ auth_ack_packet = if auth_msg.got_plain
87
+ raise NotImplementedError.new('not support pre eip8 plain text seal')
88
+ else
89
+ seal_eip8(auth_ack_msg_plain_text, receiver)
90
+ end
91
+ @io.write(auth_ack_packet)
92
+ @io.flush
93
+
94
+ receiver.extract_secrets(auth_packet, auth_ack_packet, initiator: false)
95
+ end
96
+
97
+ def initiator_enc_handshake(initiator)
98
+ initiator_auth_msg = initiator.auth_msg
99
+ auth_msg_plain_text = initiator_auth_msg.rlp_encode
100
+ # seal eip8
101
+ auth_packet = seal_eip8(auth_msg_plain_text, initiator)
102
+ @io.write(auth_packet)
103
+ @io.flush
104
+
105
+ auth_ack_mgs_binary, auth_ack_packet = read_enc_handshake_msg(ENC_AUTH_RESP_MSG_LENGTH, initiator.private_key)
106
+ auth_ack_msg = AuthRespV4.rlp_decode auth_ack_mgs_binary
107
+ initiator.handle_auth_ack_msg(auth_ack_msg)
108
+
109
+ initiator.extract_secrets(auth_packet, auth_ack_packet, initiator: true)
110
+ end
111
+
112
+ def read_enc_handshake_msg(plain_size, private_key)
113
+ packet = @io.read(plain_size)
114
+
115
+ decrypt_binary_msg = begin
116
+ private_key.ecies_decrypt(packet)
117
+ rescue Crypto::ECIESDecryptionError => e
118
+ nil
119
+ end
120
+
121
+ # pre eip old plain format
122
+ return decrypt_binary_msg if decrypt_binary_msg
123
+
124
+ # try decode eip8 format
125
+ prefix = packet[0...2]
126
+ size = Ciri::Utils.big_endian_decode(prefix)
127
+ raise FormatError.new("EIP8 format message size #{size} less than plain_size #{plain_size}") if size < plain_size
128
+
129
+ # continue read remain bytes
130
+ packet << @io.read(size - plain_size + 2)
131
+ # decrypt message
132
+ [private_key.ecies_decrypt(packet[2..-1], prefix), packet]
133
+ end
134
+
135
+ def read_protocol_handshake
136
+ msg = @frame_io.read_msg
137
+
138
+ if msg.size > BASE_PROTOCOL_MAX_MSG_SIZE
139
+ raise MessageOverflowError.new("message size #{msg.size} is too big")
140
+ end
141
+ if msg.code == Code::DISCONNECT
142
+ payload = RLP.decode(msg.payload)
143
+ raise UnexpectedMessageError.new("expected handshake, get disconnect, reason: #{payload}")
144
+ end
145
+ if msg.code != Code::HANDSHAKE
146
+ raise UnexpectedMessageError.new("expected handshake, get #{msg.code}")
147
+ end
148
+ ProtocolHandshake.rlp_decode(msg.payload)
149
+ end
150
+
151
+ def set_timeout(io)
152
+ timeout = HANDSHAKE_TIMEOUT
153
+
154
+ if io.is_a?(BasicSocket)
155
+ secs = Integer(timeout)
156
+ usecs = Integer((timeout - secs) * 1_000_000)
157
+ optval = [secs, usecs].pack("l_2")
158
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
159
+ io.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
160
+ end
161
+ end
162
+
163
+ def seal_eip8(encoded_msg, handshake)
164
+ # padding encoded message, make message distinguished from pre eip8
165
+ encoded_msg += "\x00".b * rand(100..300)
166
+ prefix = encoded_prefix(encoded_msg.size + ECIES_OVERHEAD)
167
+
168
+ enc = handshake.remote_key.ecies_encrypt(encoded_msg, prefix)
169
+ prefix + enc
170
+ end
171
+
172
+ # encode 16 uint prefix
173
+ def encoded_prefix(n)
174
+ prefix = Utils.big_endian_encode(n)
175
+ # pad to 2 bytes
176
+ prefix.ljust(2, "\x00".b)
177
+ end
178
+ end
179
+
180
+ end
181
+ end
182
+ end