class Protocol::HPACK::Context

No other state information is needed.
dynamic table as a decoding context.
To decompress header blocks, a decoder only needs to maintain a

def add_command(name, value)

Returns:
  • (Hash) - command

Parameters:
  • value (String) --
  • name (String) --
def add_command(name, value)
	exact = nil
	name_only = nil
	if @index == :all || @index == :static
		if (values_and_indices = STATIC_EXACT_LOOKUP[name])
			values_and_indices.each do |known_value, index|
				if value == known_value
					exact = index
					break
				end
			end
			
			needs_name_lookup = exact.nil?
		else
			needs_name_lookup = true
		end
		if needs_name_lookup && (static_value = STATIC_NAME_LOOKUP[name])
			name_only = static_value
		end
	end
	if @index == :all && !exact
		@table.each_index do |i|
			entry = @table[i]
			if entry.first == name
				if entry.last == value
					exact ||= i + STATIC_TABLE.size
					break
				else
					name_only ||= i + STATIC_TABLE.size
				end
			end
		end
	end
	if exact
		{name: exact, type: :indexed}
	elsif name_only
		{name: name_only, value: value, type: :incremental}
	else
		{name: name, value: value, type: :incremental}
	end
end

def add_to_table(command)

Parameters:
  • command (Array) -- +[name, value]+
def add_to_table(command)
	return unless size_check(command)
	
	command.each(&:freeze)
	command.freeze
	
	@table.unshift(command)
	@current_table_size += entry_size(command)
end

def change_table_size(size)

def change_table_size(size)
	self.table_size = size
	
	# The command to resize the table:
	return {type: :change_table_size, value: size}
end

def compute_current_table_size

Returns:
  • (Integer) -
def compute_current_table_size
	@table.sum { |k, v| k.bytesize + v.bytesize + 32 }
end

def decode(command)

Returns:
  • (Array) - +[name, value]+ header field that is added to the decoded header list

Parameters:
  • command (Hash) -- {type:, name:, value:, index:}
def decode(command)
	emit = nil
	case command[:type]
	when :change_table_size
		self.table_size = command[:value]
	when :indexed
		# Indexed Representation
		# An _indexed representation_ entails the following actions:
		# o  The header field corresponding to the referenced entry in either
		# the static table or dynamic table is added to the decoded header
		# list.
		idx = command[:name]
		k, v = dereference(idx)
		emit = [k, v]
	when :incremental, :no_index, :never_indexed
		# A _literal representation_ that is _not added_ to the dynamic table
		# entails the following action:
		# o  The header field is added to the decoded header list.
		# A _literal representation_ that is _added_ to the dynamic table
		# entails the following actions:
		# o  The header field is added to the decoded header list.
		# o  The header field is inserted at the beginning of the dynamic table.
		if command[:name].is_a? Integer
			k, v = dereference(command[:name])
			command = command.dup
			command[:index] ||= command[:name]
			command[:value] ||= v
			command[:name] = k
		end
		emit = [command[:name], command[:value]]
		add_to_table(emit) if command[:type] == :incremental
	else
		raise CompressionError, "Invalid type: #{command[:type]}"
	end
	return emit
end

def dereference(index)

Returns:
  • (Array) - +[key, value]+

Parameters:
  • index (Integer) -- zero-based index in the dynamic table.
def dereference(index)
	# NOTE: index is zero-based in this module.
	value = STATIC_TABLE[index] || @table[index - STATIC_TABLE.size]
	
	if value.nil?
		raise CompressionError, "Index #{index} too large!"
	end
	
	return value
end

def encode(headers)

Returns:
  • (Array) - array of commands

Parameters:
  • headers (Array) -- +[[name, value], ...]+
def encode(headers)
	commands = []
	
	# Literals commands are marked with :no_index when index is not used
	no_index = [:static, :never].include?(@index)
	
	headers.each do |field, value|
		command = add_command(field, value)
		command[:type] = :no_index if no_index && command[:type] == :incremental
		commands << command
		
		decode(command)
	end
	
	return commands
end

def entry_size(e)

def entry_size(e)
	e[0].bytesize + e[1].bytesize + 32
end

def initialize(table = nil, huffman: :shorter, index: :all, table_size: 4096)

Options Hash: (**table_size)
  • The (Integer) -- current maximum dynamic table size.
  • One (Symbol) -- of `:all`, `:static`, `:never`. Controls use of static/dynamic tables.
  • One (Symbol) -- of `:always`, `:never`, `:shorter`. Controls use of compression.

Parameters:
  • table (Array) -- Table of header key-value pairs.
def initialize(table = nil, huffman: :shorter, index: :all, table_size: 4096)
	@huffman = huffman
	@index = index
	
	@table_size = table_size
	
	@table = (table&.dup) || []
end

def initialize_copy(other)

def initialize_copy(other)
	super
	
	# This is the only mutable state:
	@table = @table.dup
end

def size_check(command)

Returns:
  • (Boolean) - whether +command+ fits in the dynamic table.

Parameters:
  • command (Hash) --
def size_check(command)
	
	@current_table_size ||= compute_current_table_size
	cmdsize = command.nil? ? 0 : command[0].bytesize + command[1].bytesize + 32
	while @current_table_size + cmdsize > @table_size
		break if @table.empty?
		e = @table.pop
		@current_table_size -= e[0].bytesize + e[1].bytesize + 32
	end
	cmdsize <= @table_size
end

def table_size= size

When the size is reduced, some headers might be evicted.
Alter dynamic table size.
def table_size= size
	@table_size = size
	size_check(nil)
end