Protocol::HTTP2SourceProtocolHTTP2Frame

class Frame

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

Definitions

VALID_STREAM_ID = 0..0x7fffffff

Stream Identifier cannot be bigger than this: https://http2.github.stream/http2-spec/#rfc.section.4.1

VALID_LENGTH = 0..0xffffff

The absolute maximum bounds for the length field:

LENGTH_HISHIFT = 16

Used for generating 24-bit frame length:

TYPE = nil

The base class does not have any specific type index:

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

Signature

parameter length Integer

the length of the payload, or nil if the header has not been read yet.

Implementation

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 valid_type?

Check if the frame has a valid type identifier.

Signature

returns Boolean

True if the frame type is valid.

Implementation

def valid_type?
	@type == self.class::TYPE
end

def <=>(other)

Compare frames based on their essential properties.

Signature

parameter other Frame

The frame to compare with.

returns Integer

-1, 0, or 1 for comparison result.

Implementation

def <=> other
	to_ary <=> other.to_ary
end

def to_ary

Convert frame to array representation for comparison.

Signature

returns Array

Frame properties as an array.

Implementation

def to_ary
	[@length, @type, @flags, @stream_id, @payload]
end

def unpack

Unpack the frame payload data.

Signature

returns String

The frame payload.

Implementation

def unpack
	@payload
end

def pack(payload, maximum_size: nil)

Pack payload data into the frame.

Signature

parameter payload String

The payload data to pack.

parameter maximum_size Integer | Nil

Optional maximum payload size.

raises ProtocolError

If payload exceeds maximum size.

Implementation

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 set_flags(mask)

Set specific flags on the frame.

Signature

parameter mask Integer

The flag bits to set.

Implementation

def set_flags(mask)
	@flags |= mask
end

def clear_flags(mask)

Clear specific flags on the frame.

Signature

parameter mask Integer

The flag bits to clear.

Implementation

def clear_flags(mask)
	@flags &= ~mask
end

def flag_set?(mask)

Check if specific flags are set on the frame.

Signature

parameter mask Integer

The flag bits to check.

returns Boolean

True if any of the flags are set.

Implementation

def flag_set?(mask)
	@flags & mask != 0
end

def connection?

Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any frame addressed to stream ID = 0.

Implementation

def connection?
	@stream_id.zero?
end

def header

Generates common 9-byte frame header.

  • http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-4.1

Implementation

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 self.parse_header(buffer)

Decodes common 9-byte header.

Signature

parameter buffer String

Implementation

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 read_header(stream)

Read the frame header from a stream.

Signature

parameter stream IO

The stream to read from.

raises EOFError

If the header cannot be read completely.

Implementation

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)

Read the frame payload from a stream.

Signature

parameter stream IO

The stream to read from.

raises EOFError

If the payload cannot be read completely.

Implementation

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 read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)

Read the complete frame (header and payload) from a stream.

Signature

parameter stream IO

The stream to read from.

parameter maximum_frame_size Integer

The maximum allowed frame size.

raises FrameSizeError

If the frame exceeds the maximum size.

returns Frame

Self for method chaining.

Implementation

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 write_header(stream)

Write the frame header to a stream.

Signature

parameter stream IO

The stream to write to.

Implementation

def write_header(stream)
	stream.write self.header
end

def write_payload(stream)

Write the frame payload to a stream.

Signature

parameter stream IO

The stream to write to.

Implementation

def write_payload(stream)
	stream.write(@payload) if @payload
end

def write(stream)

Write the complete frame (header and payload) to a stream.

Signature

parameter stream IO

The stream to write to.

raises ProtocolError

If frame validation fails.

Implementation

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 apply(connection)

Apply the frame to a connection for processing.

Signature

parameter connection Connection

The connection to apply the frame to.

Implementation

def apply(connection)
	connection.receive_frame(self)
end

def inspect

Provide a readable representation of the frame for debugging.

Signature

returns String

A formatted string representation of the frame.

Implementation

def inspect
	"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} payload=#{self.unpack}>"
end