Class: Ciri::DevP2P::RLPX::FrameIO

Inherits:
Object
  • Object
show all
Defined in:
lib/ciri/devp2p/rlpx/frame_io.rb

Defined Under Namespace

Classes: Error, InvalidError, OverflowError

Constant Summary collapse

MAX_MESSAGE_SIZE =

max message size, took 3 byte to store message size, equal to uint24 max size

(1 << 24) - 1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, secrets) ⇒ FrameIO

Returns a new instance of FrameIO.


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ciri/devp2p/rlpx/frame_io.rb', line 51

def initialize(io, secrets)
  @io = io
  @secrets = secrets
  @snappy = false # snappy compress

  mac_aes_version = secrets.mac.size * 8
  @mac = OpenSSL::Cipher.new("AES#{mac_aes_version}")
  @mac.encrypt
  @mac.key = secrets.mac

  # init encrypt/decrypt
  aes_version = secrets.aes.size * 8
  @encrypt = OpenSSL::Cipher::AES.new(aes_version, :CTR)
  @decrypt = OpenSSL::Cipher::AES.new(aes_version, :CTR)
  zero_iv = "\x00".b * @encrypt.iv_len
  @encrypt.iv = zero_iv
  @encrypt.key = secrets.aes
  @decrypt.iv = zero_iv
  @decrypt.key = secrets.aes
end

Instance Attribute Details

#snappyObject

Returns the value of attribute snappy


49
50
51
# File 'lib/ciri/devp2p/rlpx/frame_io.rb', line 49

def snappy
  @snappy
end

Instance Method Details

#read_msgObject


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ciri/devp2p/rlpx/frame_io.rb', line 117

def read_msg  # verify header mac

  head_buf = read(32)
  verify_mac = update_mac(@secrets.ingress_mac, head_buf[0...16])
  unless Ciri::Utils.secret_compare(verify_mac, head_buf[16...32])
    raise InvalidError.new('bad header mac')
  end

  # decrypt header
  head_buf[0...16] = @decrypt.update(head_buf[0...16]) + @decrypt.final

  # read frame
  frame_size = read_frame_size head_buf  # frame size should padded to n*16 bytes

  need_padding = frame_size % 16
  padded_frame_size = need_padding > 0 ? frame_size + (16 - need_padding) : frame_size
  frame_buf = read(padded_frame_size)

  # verify frame mac
  @secrets.ingress_mac.update(frame_buf)
  frame_digest = @secrets.ingress_mac.digest
  verify_mac = update_mac(@secrets.ingress_mac, frame_digest)  # clear head_buf 16...32 bytes(header mac), since we will not need it

  frame_mac = head_buf[16...32] = read(16)
  unless Ciri::Utils.secret_compare(verify_mac, frame_mac)
    raise InvalidError.new('bad frame mac')
  end

  # decrypt frame
  frame_content = @decrypt.update(frame_buf) + @decrypt.final
  frame_content = frame_content[0...frame_size]
  msg_code = RLP.decode_with_type frame_content[0], Integer
  msg = Message.new(code: msg_code, size: frame_content.size - 1, payload: frame_content[1..-1])

  # snappy decompress if enable
  if snappy
    msg.payload = Snappy.inflate(msg.payload)
    msg.size = msg.payload.size
  end

  msg
end

#send_data(code, data) ⇒ Object


72
73
74
75
# File 'lib/ciri/devp2p/rlpx/frame_io.rb', line 72

def send_data(code, data)
  msg = Message.new(code: code, size: data.size, payload: data)
  write_msg(msg)
end

#write_msg(msg) ⇒ Object


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ciri/devp2p/rlpx/frame_io.rb', line 77

def write_msg(msg)
  pkg_type = RLP.encode_with_type msg.code, Integer, zero: "\x00"

  # use snappy compress if enable
  if snappy
    if msg.size > MAX_MESSAGE_SIZE
      raise OverflowError.new("Message size is overflow, msg size: #{msg.size}")
    end
    msg.payload = Snappy.deflate(msg.payload)
    msg.size = msg.payload.size
  end

  # write header
  head_buf = "\x00".b * 32

  frame_size = pkg_type.size + msg.size
  if frame_size > MAX_MESSAGE_SIZE
    raise OverflowError.new("Message size is overflow, frame size: #{frame_size}")
  end

  write_frame_size(head_buf, frame_size)

  # Can't find related RFC or RLPX Spec, below code is copy from geth
  # write zero header, but I can't find spec or explanations of 'zero header'
  head_buf[3..5] = [0xC2, 0x80, 0x80].pack('c*')  # encrypt first half

  head_buf[0...16] = @encrypt.update(head_buf[0...16]) + @encrypt.final  # write header mac

  head_buf[16...32] = update_mac(@secrets.egress_mac, head_buf[0...16])
  @io.write head_buf  # write encrypt frame

  write_frame(pkg_type)
  write_frame(msg.payload)  # pad to n*16 bytes

  if (need_padding = frame_size % 16) > 0
    write_frame("\x00".b * (16 - need_padding))
  end
  finish_write_frame
end