# frozen_string_literal: true# Released under the MIT License.# Copyright, 2019-2024, by Samuel Williams.# Copyright, 2019, by Yuta Iwama.require_relative"error"moduleProtocolmoduleHTTP2END_STREAM=0x1END_HEADERS=0x4PADDED=0x8PRIORITY=0x20MAXIMUM_ALLOWED_WINDOW_SIZE=0x7FFFFFFFMINIMUM_ALLOWED_FRAME_SIZE=0x4000MAXIMUM_ALLOWED_FRAME_SIZE=0xFFFFFFclassFrameincludeComparable# Stream Identifier cannot be bigger than this:# https://http2.github.stream/http2-spec/#rfc.section.4.1VALID_STREAM_ID=0..0x7fffffff# The absolute maximum bounds for the length field:VALID_LENGTH=0..0xffffff# Used for generating 24-bit frame length:LENGTH_HISHIFT=16LENGTH_LOMASK=0xFFFF# The base class does not have any specific type index:TYPE=nil# @param length [Integer] the length of the payload, or nil if the header has not been read yet.definitialize(stream_id=0,flags=0,type=self.class::TYPE,length=nil,payload=nil)@stream_id=stream_id@flags=flags@type=type@length=length@payload=payloadenddefvalid_type?@type==self.class::TYPEenddef<=>otherto_ary<=>other.to_aryenddefto_ary[@length,@type,@flags,@stream_id,@payload]end# The generic frame header uses the following binary representation:## +-----------------------------------------------+# | Length (24) |# +---------------+---------------+---------------+# | Type (8) | Flags (8) |# +-+-------------+---------------+-------------------------------+# |R| Stream Identifier (31) |# +=+=============================================================+# | Frame Payload (0...) ...# +---------------------------------------------------------------+attr_accessor:lengthattr_accessor:typeattr_accessor:flagsattr_accessor:stream_idattr_accessor:payloaddefunpack@payloadenddefpack(payload,maximum_size: nil)@payload=payload@length=payload.bytesizeifmaximum_sizeand@length>maximum_sizeraiseProtocolError,"Frame length bigger than maximum allowed: #{@length} > #{maximum_size}"endenddefset_flags(mask)@flags|=maskenddefclear_flags(mask)@flags&=~maskenddefflag_set?(mask)@flags&mask!=0end# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any# frame addressed to stream ID = 0.## @return [Boolean]defconnection?@stream_id.zero?endHEADER_FORMAT="CnCCN".freezeSTREAM_ID_MASK=0x7fffffff# Generates common 9-byte frame header.# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1## @return [String]defheaderunlessVALID_LENGTH.include?@lengthraiseProtocolError,"Invalid frame length: #{@length.inspect}"endunlessVALID_STREAM_ID.include?@stream_idraiseProtocolError,"Invalid stream identifier: #{@stream_id.inspect}"end[# These are guaranteed correct due to the length check above.@length>>LENGTH_HISHIFT,@length&LENGTH_LOMASK,@type,@flags,@stream_id].pack(HEADER_FORMAT)end# Decodes common 9-byte header.## @param buffer [String]defself.parse_header(buffer)length_hi,length_lo,type,flags,stream_id=buffer.unpack(HEADER_FORMAT)length=(length_hi<<LENGTH_HISHIFT)|length_lostream_id=stream_id&STREAM_ID_MASK# puts "parse_header: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id}"returnlength,type,flags,stream_idenddefread_header(stream)ifbuffer=stream.read(9)andbuffer.bytesize==9@length,@type,@flags,@stream_id=Frame.parse_header(buffer)# puts "read_header: #{@length} #{@type} #{@flags} #{@stream_id}"elseraiseEOFError,"Could not read frame header!"endenddefread_payload(stream)ifpayload=stream.read(@length)andpayload.bytesize==@length@payload=payloadelseraiseEOFError,"Could not read frame payload!"endenddefread(stream,maximum_frame_size=MAXIMUM_ALLOWED_FRAME_SIZE)read_header(stream)unless@lengthif@length>maximum_frame_sizeraiseFrameSizeError,"#{self.class} (type=#{@type}) frame length #{@length} exceeds maximum frame size #{maximum_frame_size}!"endread_payload(stream)enddefwrite_header(stream)stream.writeself.headerenddefwrite_payload(stream)stream.write(@payload)if@payloadenddefwrite(stream)# Validate the payload size:if@payload.nil?if@length!=0raiseProtocolError,"Invalid frame length: #{@length} != 0"endelseif@length!=@payload.bytesizeraiseProtocolError,"Invalid payload size: #{@length} != #{@payload.bytesize}"endendself.write_header(stream)self.write_payload(stream)enddefapply(connection)connection.receive_frame(self)enddefinspect"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} payload=#{self.unpack}>"endendendend