Class: Protocol::HPACK::Compressor

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/hpack/compressor.rb

Overview

Responsible for encoding header key-value pairs using HPACK algorithm.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(buffer, context = Context.new, table_size_limit: nil) ⇒ Compressor

Returns a new instance of Compressor.



51
52
53
54
55
56
# File 'lib/protocol/hpack/compressor.rb', line 51

def initialize(buffer, context = Context.new, table_size_limit: nil)
  @buffer = buffer
  @context = context
  
  @table_size_limit = table_size_limit
end

Instance Attribute Details

#bufferObject (readonly)

Returns the value of attribute buffer.



60
61
62
# File 'lib/protocol/hpack/compressor.rb', line 60

def buffer
  @buffer
end

#contextObject (readonly)

Returns the value of attribute context.



61
62
63
# File 'lib/protocol/hpack/compressor.rb', line 61

def context
  @context
end

#offsetObject (readonly)

Returns the value of attribute offset.



62
63
64
# File 'lib/protocol/hpack/compressor.rb', line 62

def offset
  @offset
end

#table_size_limitObject (readonly)

Returns the value of attribute table_size_limit.



58
59
60
# File 'lib/protocol/hpack/compressor.rb', line 58

def table_size_limit
  @table_size_limit
end

Instance Method Details

#encode(headers, table_size = @table_size_limit) ⇒ Buffer

Encodes provided list of HTTP headers.

Parameters:

  • headers (Array)

    [[name, value], …]

Returns:

  • (Buffer)


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/protocol/hpack/compressor.rb', line 186

def encode(headers, table_size = @table_size_limit)
  if table_size and table_size != @context.table_size
    command = @context.change_table_size(table_size)
    
    write_header(command)
  end
  
  commands = @context.encode(headers)
  
  commands.each do |command|
    write_header(command)
  end
  
  return @buffer
end

#huffmanObject



106
107
108
# File 'lib/protocol/hpack/compressor.rb', line 106

def huffman
  @context.huffman
end

#write_byte(byte) ⇒ Object



64
65
66
# File 'lib/protocol/hpack/compressor.rb', line 64

def write_byte(byte)
  @buffer << byte.chr
end

#write_bytes(bytes) ⇒ Object



68
69
70
# File 'lib/protocol/hpack/compressor.rb', line 68

def write_bytes(bytes)
  @buffer << bytes
end

#write_header(command) ⇒ Buffer

Encodes header command with appropriate header representation.

Parameters:

  • h (Hash)

    header command

  • buffer (String)

Returns:

  • (Buffer)


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/protocol/hpack/compressor.rb', line 157

def write_header(command)
  representation = HEADER_REPRESENTATION[command[:type]]
  
  first = @buffer.bytesize
  
  case command[:type]
  when :indexed
    write_integer(command[:name] + 1, representation[:prefix])
  when :change_table_size
    write_integer(command[:value], representation[:prefix])
  else
    if command[:name].is_a? Integer
      write_integer(command[:name] + 1, representation[:prefix])
    else
      write_integer(0, representation[:prefix])
      write_string(command[:name])
    end
    
    write_string(command[:value])
  end

  # set header representation pattern on first byte
  @buffer.setbyte(first, @buffer.getbyte(first) | representation[:pattern])
end

#write_integer(value, bits) ⇒ String

Encodes provided value via integer representation.

If I < 2^N - 1, encode I on N bits
Else
    encode 2^N - 1 on N bits
    I = I - (2^N - 1)
    While I >= 128
         Encode (I % 128 + 128) on 8 bits
         I = I / 128
    encode (I) on 8 bits

Parameters:

  • value (Integer)

    value to encode

  • bits (Integer)

    number of available bits

Returns:

  • (String)

    binary string



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/protocol/hpack/compressor.rb', line 87

def write_integer(value, bits)
  limit = 2**bits - 1
  
  return write_bytes([value].pack('C')) if value < limit
  
  bytes = []
  bytes.push(limit) unless bits.zero?
  
  value -= limit
  while value >= 128
    bytes.push((value % 128) + 128)
    value /= 128
  end
  
  bytes.push(value)
  
  write_bytes(bytes.pack('C*'))
end

#write_string(string, huffman = self.huffman) ⇒ String

Encodes provided value via string literal representation.

  • tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-5.2

  • The string length, defined as the number of bytes needed to store its UTF-8 representation, is represented as an integer with a seven bits prefix. If the string length is strictly less than 127, it is represented as one byte.

  • If the bit 7 of the first byte is 1, the string value is represented as a list of Huffman encoded octets (padded with bit 1’s until next octet boundary).

  • If the bit 7 of the first byte is 0, the string value is represented as a list of UTF-8 encoded octets.

@options [:huffman] controls whether to use Huffman encoding:

:never   Do not use Huffman encoding
:always  Always use Huffman encoding
:shorter Use Huffman when the result is strictly shorter

Parameters:

  • string (String)

Returns:

  • (String)

    binary string



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/protocol/hpack/compressor.rb', line 130

def write_string(string, huffman = self.huffman)
  if huffman != :never
    encoded = Huffman.new.encode(string)
    
    if huffman == :shorter and encoded.bytesize >= string.bytesize
      encoded = nil
    end
  end
  
  if encoded
    first = @buffer.bytesize
    
    write_integer(encoded.bytesize, 7)
    write_bytes(encoded.b)
    
    @buffer.setbyte(first, @buffer.getbyte(first).ord | 0x80)
  else
    write_integer(string.bytesize, 7)
    write_bytes(string.b)
  end
end