class Ciri::P2P::RLPX::Connection

RLPX::Connection implement RLPX protocol operations all operations end with bang(!)

Public Class Methods

new(io) click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 55
def initialize(io)
  set_timeout(io)
  @io = io
  @frame_io = nil
end

Public Instance Methods

encryption_handshake!(private_key:, remote_node_id: nil) click to toggle source

Encryption handshake, exchange keys with node, must been invoked before other operations

# File lib/ciri/p2p/rlpx/connection.rb, line 62
def encryption_handshake!(private_key:, remote_node_id: nil)
  enc_handshake = EncryptionHandshake.new(private_key: private_key, remote_id: remote_node_id)
  secrets = remote_node_id.nil? ? receiver_enc_handshake(enc_handshake) : initiator_enc_handshake(enc_handshake)
  @frame_io = FrameIO.new(@io, secrets)
end
protocol_handshake!(our_hs) click to toggle source

protocol handshake

# File lib/ciri/p2p/rlpx/connection.rb, line 69
def protocol_handshake!(our_hs)
  @frame_io.send_data(Code::HANDSHAKE, our_hs.rlp_encode)
  remote_hs = read_protocol_handshake
  # enable snappy compress if remote peer support
  @frame_io.snappy = remote_hs.version >= SNAPPY_PROTOCOL_VERSION
  remote_hs
end

Private Instance Methods

encoded_prefix(n) click to toggle source

encode 16 uint prefix

# File lib/ciri/p2p/rlpx/connection.rb, line 173
def encoded_prefix(n)
  prefix = Utils.big_endian_encode(n)
  # pad to 2 bytes
  prefix.ljust(2, "\x00".b)
end
initiator_enc_handshake(initiator) click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 97
def initiator_enc_handshake(initiator)
  initiator_auth_msg = initiator.auth_msg
  auth_msg_plain_text = initiator_auth_msg.rlp_encode
  # seal eip8
  auth_packet = seal_eip8(auth_msg_plain_text, initiator)
  @io.write(auth_packet)
  @io.flush

  auth_ack_mgs_binary, auth_ack_packet = read_enc_handshake_msg(ENC_AUTH_RESP_MSG_LENGTH, initiator.private_key)
  auth_ack_msg = AuthRespV4.rlp_decode auth_ack_mgs_binary
  initiator.handle_auth_ack_msg(auth_ack_msg)

  initiator.extract_secrets(auth_packet, auth_ack_packet, initiator: true)
end
read_enc_handshake_msg(plain_size, private_key) click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 112
def read_enc_handshake_msg(plain_size, private_key)
  packet = @io.read(plain_size)

  decrypt_binary_msg = begin
    private_key.ecies_decrypt(packet)
  rescue Crypto::ECIESDecryptionError => e
    nil
  end

  # pre eip old plain format
  return decrypt_binary_msg if decrypt_binary_msg

  # try decode eip8 format
  prefix = packet[0...2]
  size = Ciri::Utils.big_endian_decode(prefix)
  raise FormatError.new("EIP8 format message size #{size} less than plain_size #{plain_size}") if size < plain_size

  # continue read remain bytes
  packet << @io.read(size - plain_size + 2)
  # decrypt message
  [private_key.ecies_decrypt(packet[2..-1], prefix), packet]
end
read_protocol_handshake() click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 135
def read_protocol_handshake
  msg = @frame_io.read_msg

  if msg.size > BASE_PROTOCOL_MAX_MSG_SIZE
    raise MessageOverflowError.new("message size #{msg.size} is too big")
  end
  if msg.code == Code::DISCONNECT
    payload = RLP.decode(msg.payload)
    raise UnexpectedMessageError.new("expected handshake, get disconnect, reason: #{payload}")
  end
  if msg.code != Code::HANDSHAKE
    raise UnexpectedMessageError.new("expected handshake, get #{msg.code}")
  end
  ProtocolHandshake.rlp_decode(msg.payload)
end
receiver_enc_handshake(receiver) click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 79
def receiver_enc_handshake(receiver)
  auth_msg_binary, auth_packet = read_enc_handshake_msg(ENC_AUTH_MSG_LENGTH, receiver.private_key)
  auth_msg = AuthMsgV4.rlp_decode(auth_msg_binary)
  receiver.handle_auth_msg(auth_msg)

  auth_ack_msg = receiver.auth_ack_msg
  auth_ack_msg_plain_text = auth_ack_msg.rlp_encode
  auth_ack_packet = if auth_msg.got_plain
                      raise NotImplementedError.new('not support pre eip8 plain text seal')
                    else
                      seal_eip8(auth_ack_msg_plain_text, receiver)
                    end
  @io.write(auth_ack_packet)
  @io.flush

  receiver.extract_secrets(auth_packet, auth_ack_packet, initiator: false)
end
seal_eip8(encoded_msg, handshake) click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 163
def seal_eip8(encoded_msg, handshake)
  # padding encoded message, make message distinguished from pre eip8
  encoded_msg += "\x00".b * rand(100..300)
  prefix = encoded_prefix(encoded_msg.size + ECIES_OVERHEAD)

  enc = handshake.remote_key.ecies_encrypt(encoded_msg, prefix)
  prefix + enc
end
set_timeout(io) click to toggle source
# File lib/ciri/p2p/rlpx/connection.rb, line 151
def set_timeout(io)
  timeout = HANDSHAKE_TIMEOUT

  if io.is_a?(BasicSocket)
    secs = Integer(timeout)
    usecs = Integer((timeout - secs) * 1_000_000)
    optval = [secs, usecs].pack("l_2")
    io.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
    io.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
  end
end