Class: DEVp2p::Frame
- Inherits:
-
Object
- Object
- DEVp2p::Frame
- Extended by:
- Configurable
- Defined in:
- lib/devp2p/frame.rb
Overview
When sending a packet over RLPx, the packet will be framed. The frame provides information about the size of the packet and the packet’s source protocol. There are three slightly different frames, depending on whether or not the frame is delivering a multi-frame packet. A multi-frame packet is a packet which is split (aka chunked) into multiple frames because it’s size is larger than the protocol window size (pws, see Multiplexing). When a packet is chunked into multiple frames, there is an implicit difference between the first frame and all subsequent frames.
Thus, the three frame types are normal, chunked-0 (first frame of a multi-frame packet), and chunked-n (subsequent frames of a multi-frame packet).
-
Single-frame packet:
header || header-mac || frame || mac
-
Multi-frame packet:
header || header-mac || frame-0 ||
- header || header-mac || frame-n || … ||
-
header || header-mac || frame-last || mac
Instance Attribute Summary collapse
-
#cmd_id ⇒ Object
readonly
Returns the value of attribute cmd_id.
-
#frames ⇒ Object
readonly
Returns the value of attribute frames.
-
#is_chunked_n ⇒ Object
readonly
Returns the value of attribute is_chunked_n.
-
#payload ⇒ Object
readonly
Returns the value of attribute payload.
-
#protocol_id ⇒ Object
readonly
Returns the value of attribute protocol_id.
-
#sequence_id ⇒ Object
readonly
Returns the value of attribute sequence_id.
-
#total_payload_size ⇒ Object
readonly
Returns the value of attribute total_payload_size.
Class Method Summary collapse
Instance Method Summary collapse
- #as_bytes ⇒ Object
-
#body ⇒ Object
frame: normal: rlp(packet_type) [|| rlp(packet_data)] || padding chunked_0: rlp(packet_type) || rlp(packet_data …) chunked_n: rlp(…packet_data) || padding padding: zero-fill to 16-byte boundary (only necessary for last frame).
-
#body_size(padded = false) ⇒ Object
frame-size: 3-byte integer, size of frame, big endian encoded (excludes padding).
- #enc_cmd_id ⇒ Object
- #frame_size ⇒ Object
- #frame_type ⇒ Object
-
#header ⇒ Object
header: frame-size || header-data || padding.
-
#initialize(protocol_id, cmd_id, payload, sequence_id, window_size, is_chunked_n = false, frames = nil, frame_cipher = nil) ⇒ Frame
constructor
A new instance of Frame.
- #normal? ⇒ Boolean
- #to_s ⇒ Object (also: #inspect)
Methods included from Configurable
Constructor Details
#initialize(protocol_id, cmd_id, payload, sequence_id, window_size, is_chunked_n = false, frames = nil, frame_cipher = nil) ⇒ Frame
Returns a new instance of Frame.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/devp2p/frame.rb', line 51 def initialize(protocol_id, cmd_id, payload, sequence_id, window_size, is_chunked_n=false, frames=nil, frame_cipher=nil) raise ArgumentError, 'invalid protocol_id' unless protocol_id < TT16 raise ArgumentError, 'invalid sequence_id' unless sequence_id.nil? || sequence_id < TT16 raise ArgumentError, 'invalid window_size' unless window_size % padding == 0 raise ArgumentError, 'invalid cmd_id' unless cmd_id < 256 @protocol_id = protocol_id @cmd_id = cmd_id @payload = payload @sequence_id = sequence_id @is_chunked_n = is_chunked_n @frame_cipher = frame_cipher @frames = frames || [] @frames.push self # chunk payloads resulting in frames exceeing window_size fs = frame_size if fs > window_size unless is_chunked_n @is_chunked_0 = true @total_payload_size = body_size end # chunk payload @payload = payload[0...(window_size-fs)] raise FrameError, "invalid frame size" unless frame_size <= window_size remain = payload[@payload.size..-1] raise FrameError, "invalid remain size" unless (remain.size + @payload.size) == payload.size Frame.new(protocol_id, cmd_id, remain, sequence_id, window_size, true, @frames, frame_cipher) end raise FrameError, "invalid frame size" unless frame_size <= window_size end |
Instance Attribute Details
#cmd_id ⇒ Object (readonly)
Returns the value of attribute cmd_id.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def cmd_id @cmd_id end |
#frames ⇒ Object (readonly)
Returns the value of attribute frames.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def frames @frames end |
#is_chunked_n ⇒ Object (readonly)
Returns the value of attribute is_chunked_n.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def is_chunked_n @is_chunked_n end |
#payload ⇒ Object (readonly)
Returns the value of attribute payload.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def payload @payload end |
#protocol_id ⇒ Object (readonly)
Returns the value of attribute protocol_id.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def protocol_id @protocol_id end |
#sequence_id ⇒ Object (readonly)
Returns the value of attribute sequence_id.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def sequence_id @sequence_id end |
#total_payload_size ⇒ Object (readonly)
Returns the value of attribute total_payload_size.
49 50 51 |
# File 'lib/devp2p/frame.rb', line 49 def total_payload_size @total_payload_size end |
Class Method Details
.decode_body_size(header) ⇒ Object
44 45 46 |
# File 'lib/devp2p/frame.rb', line 44 def decode_body_size(header) "\x00#{header[0,3]}".unpack('I>').first end |
.encode_body_size(size) ⇒ Object
40 41 42 |
# File 'lib/devp2p/frame.rb', line 40 def encode_body_size(size) [size].pack('I>')[1..-1] end |
Instance Method Details
#as_bytes ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/devp2p/frame.rb', line 167 def as_bytes raise FrameError, 'can only be called once' if @cipher_called if @frame_cipher @cipher_called = true e = @frame_cipher.encrypt(header, body) raise FrameError, 'invalid frame size of encrypted frame' unless e.size == frame_size e else h = header raise FrameError, 'invalid header size' unless h.size == header_size b = body raise FrameError, 'invalid body size' unless b.size == body_size(true) dummy_mac = "\x00" * mac_size r = h + dummy_mac + b + dummy_mac raise FrameError, 'invalid frame' unless r.size == frame_size r end end |
#body ⇒ Object
frame:
normal: rlp(packet_type) [|| rlp(packet_data)] || padding
chunked_0: rlp(packet_type) || rlp(packet_data ...)
chunked_n: rlp(...packet_data) || padding
padding: zero-fill to 16-byte boundary (only necessary for last frame)
163 164 165 |
# File 'lib/devp2p/frame.rb', line 163 def body Utils.rzpad16 "#{enc_cmd_id}#{payload}" end |
#body_size(padded = false) ⇒ Object
frame-size: 3-byte integer, size of frame, big endian encoded (excludes padding)
102 103 104 105 |
# File 'lib/devp2p/frame.rb', line 102 def body_size(padded=false) l = enc_cmd_id.size + payload.size padded ? Utils.ceil16(l) : l end |
#enc_cmd_id ⇒ Object
152 153 154 |
# File 'lib/devp2p/frame.rb', line 152 def enc_cmd_id @is_chunked_n ? '' : RLP.encode(cmd_id, sedes: RLP::Sedes.big_endian_int) end |
#frame_size ⇒ Object
93 94 95 96 |
# File 'lib/devp2p/frame.rb', line 93 def frame_size # header16 || mac16 || dataN + [padding] || mac16 header_size + mac_size + body_size(true) + mac_size end |
#frame_type ⇒ Object
88 89 90 91 |
# File 'lib/devp2p/frame.rb', line 88 def frame_type return :normal if normal? @is_chunked_n ? :chunked_n : :chunked_0 end |
#header ⇒ Object
header: frame-size || header-data || padding
frame-size: 3-byte integer, size of frame, big endian encoded header-data:
normal: RLP::Sedes::List.new(protocol_type[, sequence_id])
chunked_0: RLP::Sedes::List.new(protocol_type, sequence_id, total_packet_size)
chunked_n: RLP::Sedes::List.new(protocol_type, sequence_id)
normal, chunked_n: RLP::Sedes::List.new(protocol_type[, sequence_id])
values:
protocol_type: < 2**16
sequence_id: < 2**16 (this value is optional for normal frames)
total_packet_size: < 2**32
padding: zero-fill to 16-byte boundary
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 |
# File 'lib/devp2p/frame.rb', line 126 def header raise FrameError, "invalid protocol id" unless protocol_id < 2**16 raise FrameError, "invalid sequence id" unless sequence_id.nil? || sequence_id < TT16 l = [protocol_id] if @is_chunked_0 raise FrameError, 'chunked_0 must have sequence_id' if sequence_id.nil? l.push sequence_id l.push total_payload_size elsif sequence_id l.push sequence_id end header_data = RLP.encode l, sedes: header_sedes raise FrameError, 'invalid rlp' unless l == RLP.decode(header_data, sedes: header_sedes, strict: false) bs = body_size raise FrameError, 'invalid body size' unless bs < 256**3 header = Frame.encode_body_size(body_size) + header_data header = Utils.rzpad16 header raise FrameError, 'invalid header' unless header.size == header_size header end |
#normal? ⇒ Boolean
107 108 109 |
# File 'lib/devp2p/frame.rb', line 107 def normal? !@is_chunked_n && !@is_chunked_0 end |
#to_s ⇒ Object Also known as: inspect
190 191 192 |
# File 'lib/devp2p/frame.rb', line 190 def to_s "<Frame(#{frame_type}, len=#{frame_size}, protocol=#{protocol_id} sid=#{sequence_id})" end |