class Protocol::HTTP2::Frame

and manipulation according to RFC 7540.
This class provides common functionality for frame parsing, serialization,
Represents the base class for all HTTP/2 frames.

def self.parse_header(buffer)

@parameter buffer [String]

Decodes common 9-byte header.
def self.parse_header(buffer)
	length_hi, length_lo, type, flags, stream_id = buffer.unpack(HEADER_FORMAT)
	length = (length_hi << LENGTH_HISHIFT) | length_lo
	stream_id = stream_id & STREAM_ID_MASK
	
	# puts "parse_header: length=#{length} type=#{type} flags=#{flags} stream_id=#{stream_id}"
	
	return length, type, flags, stream_id
end

def <=> other

@returns [Integer] -1, 0, or 1 for comparison result.
@parameter other [Frame] The frame to compare with.
Compare frames based on their essential properties.
def <=> other
	to_ary <=> other.to_ary
end

def apply(connection)

@parameter connection [Connection] The connection to apply the frame to.
Apply the frame to a connection for processing.
def apply(connection)
	connection.receive_frame(self)
end

def clear_flags(mask)

@parameter mask [Integer] The flag bits to clear.
Clear specific flags on the frame.
def clear_flags(mask)
	@flags &= ~mask
end

def connection?

Returns:
  • (Boolean) - If this is a connection frame.
def connection?
	@stream_id.zero?
end

def flag_set?(mask)

@returns [Boolean] True if any of the flags are set.
@parameter mask [Integer] The flag bits to check.
Check if specific flags are set on the frame.
def flag_set?(mask)
	@flags & mask != 0
end

def header

Returns:
  • (String) -
def header
	unless VALID_LENGTH.include? @length
		raise ProtocolError, "Invalid frame length: #{@length.inspect}"
	end
	
	unless VALID_STREAM_ID.include? @stream_id
		raise ProtocolError, "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

def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)

@parameter length [Integer] the length of the payload, or nil if the header has not been read yet.
def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)
	@stream_id = stream_id
	@flags = flags
	@type = type
	@length = length
	@payload = payload
end

def inspect

@returns [String] A formatted string representation of the frame.
Provide a readable representation of the frame for debugging.
def inspect
	"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} payload=#{self.unpack}>"
end

def pack(payload, maximum_size: nil)

@raises [ProtocolError] If payload exceeds maximum size.
@parameter maximum_size [Integer | Nil] Optional maximum payload size.
@parameter payload [String] The payload data to pack.
Pack payload data into the frame.
def pack(payload, maximum_size: nil)
	@payload = payload
	@length = payload.bytesize
	
	if maximum_size and @length > maximum_size
		raise ProtocolError, "Frame length bigger than maximum allowed: #{@length} > #{maximum_size}"
	end
end

def read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)

@returns [Frame] Self for method chaining.
@raises [FrameSizeError] If the frame exceeds the maximum size.
@parameter maximum_frame_size [Integer] The maximum allowed frame size.
@parameter stream [IO] The stream to read from.
Read the complete frame (header and payload) from a stream.
def read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
	read_header(stream) unless @length
	
	if @length > maximum_frame_size
		raise FrameSizeError, "#{self.class} (type=#{@type}) frame length #{@length} exceeds maximum frame size #{maximum_frame_size}!"
	end
	
	read_payload(stream)
end

def read_header(stream)

@raises [EOFError] If the header cannot be read completely.
@parameter stream [IO] The stream to read from.
Read the frame header from a stream.
def read_header(stream)
	if buffer = stream.read(9) and buffer.bytesize == 9
		@length, @type, @flags, @stream_id = Frame.parse_header(buffer)
		# puts "read_header: #{@length} #{@type} #{@flags} #{@stream_id}"
	else
		raise EOFError, "Could not read frame header!"
	end
end

def read_payload(stream)

@raises [EOFError] If the payload cannot be read completely.
@parameter stream [IO] The stream to read from.
Read the frame payload from a stream.
def read_payload(stream)
	if payload = stream.read(@length) and payload.bytesize == @length
		@payload = payload
	else
		raise EOFError, "Could not read frame payload!"
	end
end

def set_flags(mask)

@parameter mask [Integer] The flag bits to set.
Set specific flags on the frame.
def set_flags(mask)
	@flags |= mask
end

def to_ary

@returns [Array] Frame properties as an array.
Convert frame to array representation for comparison.
def to_ary
	[@length, @type, @flags, @stream_id, @payload]
end

def unpack

@returns [String] The frame payload.
Unpack the frame payload data.
def unpack
	@payload
end

def valid_type?

@returns [Boolean] True if the frame type is valid.
Check if the frame has a valid type identifier.
def valid_type?
	@type == self.class::TYPE
end

def write(stream)

@raises [ProtocolError] If frame validation fails.
@parameter stream [IO] The stream to write to.
Write the complete frame (header and payload) to a stream.
def write(stream)
	# Validate the payload size:
	if @payload.nil?
		if @length != 0
			raise ProtocolError, "Invalid frame length: #{@length} != 0"
		end
	else
		if @length != @payload.bytesize
			raise ProtocolError, "Invalid payload size: #{@length} != #{@payload.bytesize}"
		end
	end
	
	self.write_header(stream)
	self.write_payload(stream)
end

def write_header(stream)

@parameter stream [IO] The stream to write to.
Write the frame header to a stream.
def write_header(stream)
	stream.write self.header
end

def write_payload(stream)

@parameter stream [IO] The stream to write to.
Write the frame payload to a stream.
def write_payload(stream)
	stream.write(@payload) if @payload
end