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



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

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.



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

def buffer
  @buffer
end

#contextObject (readonly)

Returns the value of attribute context.



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

def context
  @context
end

#offsetObject (readonly)

Returns the value of attribute offset.



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

def offset
  @offset
end

#table_size_limitObject (readonly)

Returns the value of attribute table_size_limit.



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

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.



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

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



108
109
110
# File 'lib/protocol/hpack/compressor.rb', line 108

def huffman
  @context.huffman
end

#write_byte(byte) ⇒ Object



66
67
68
# File 'lib/protocol/hpack/compressor.rb', line 66

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

#write_bytes(bytes) ⇒ Object



70
71
72
# File 'lib/protocol/hpack/compressor.rb', line 70

def write_bytes(bytes)
  @buffer << bytes
end

#write_header(command) ⇒ Buffer

Encodes header command with appropriate header representation.



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

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


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

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


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

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