class Ciri::P2P::RLPX::FrameIO
Constants
- MAX_MESSAGE_SIZE
max message size, took 3 byte to store message size, equal to uint24 max size
Attributes
snappy[RW]
Public Class Methods
new(io, secrets)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 57 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
Public Instance Methods
read_msg()
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 125 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)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 78 def send_data(code, data) msg = Message.new(code: code, size: data.size, payload: data) write_msg(msg) end
write_msg(msg)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 83 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 # because we use Async::IO::Stream as IO object, we must invoke flush to make sure data is send flush end
Private Instance Methods
finish_write_frame()
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 220 def finish_write_frame # get frame digest frame_digest = @secrets.egress_mac.digest @io.write update_mac(@secrets.egress_mac, frame_digest) end
read(length)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 169 def read(length) if (buf = @io.read(length)).nil? @io.close raise EOFError.new('read EOF, connection closed') end buf end
read_frame_size(buf)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 187 def read_frame_size(buf) size_bytes = buf[0..2].each_byte.map(&:ord) (size_bytes[0] << 16) + (size_bytes[1] << 8) + (size_bytes[2]) end
update_mac(mac, seed)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 192 def update_mac(mac, seed) # reset mac each time @mac.reset aes_buf = (@mac.update(mac.digest) + @mac.final)[0...@mac.block_size] aes_buf = aes_buf.each_byte.with_index.map {|b, i| b ^ seed[i].ord}.pack('c*') mac.update(aes_buf) # return first 16 byte mac.digest[0...16] end
write_frame(string_or_io)
click to toggle source
write encrypt content to @io, and update @secrets.egress_mac
# File lib/ciri/p2p/rlpx/frame_io.rb, line 203 def write_frame(string_or_io) if string_or_io.is_a?(IO) while (s = string_or_io.read(4096)) write_frame_string(s) end else write_frame_string(string_or_io) end end
write_frame_size(buf, frame_size)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 177 def write_frame_size(buf, frame_size) # frame-size: 3-byte integer size of frame, big endian encoded (excludes padding) bytes_of_frame_size = [ frame_size >> 16, frame_size >> 8, frame_size % 256 ] buf[0..2] = bytes_of_frame_size.pack('c*') end
write_frame_string(s)
click to toggle source
# File lib/ciri/p2p/rlpx/frame_io.rb, line 213 def write_frame_string(s) encrypt_content = @encrypt.update(s) + @encrypt.final # update egress_mac @secrets.egress_mac.update encrypt_content @io.write encrypt_content end