protocol-hpack 1.4.3 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3192190834a35550921cbff8e50482499716791371f521a77c3f7c4b5a0efcff
4
- data.tar.gz: 40d443b19eaac26aaaaae63da3baab5ae384246804643fdc7fb01f5455595dc3
3
+ metadata.gz: ad81d71708c7c92bb94e9616655872045a31052aa62c3543487064ddea8c99ad
4
+ data.tar.gz: 1945361f8e8576ef3db44de1e0cf44554795699d910c40b8659232a499ded6b8
5
5
  SHA512:
6
- metadata.gz: c19a7524c1a544016e0feb7d88099a67fbccf3fe200214f7bc21639512da55e72a9e3442c8917f69fec1568a3027487e701a2f1e99deb652942ebae54c4752a7
7
- data.tar.gz: 3f105d7bba7111585325db6bc8f414c34366bfb189f2263fa497e8ec53ed1502c3694c0dcd6749151fb10456c52a5a333daa9b7f5f8b362026d8be745c568375
6
+ metadata.gz: c0537372d9278691fa9621622b84f32facde4f9efdfbfd0c36eb7573bba4b19ec05bc91f5191a7463405cbed952c4c6ac614531c4c0fc0b26707196db8d800b3
7
+ data.tar.gz: fc53196d54e9b02a56df5c1294e819f5a4002c49201811c12d2666725a0325e27df75603afc4b04a5fc5c1845237937097bf51352ea4d5f1c3741d1b73fa244a
checksums.yaml.gz.sig CHANGED
Binary file
@@ -11,9 +11,10 @@
11
11
  # Copyright, 2018, by Kenichi Nakamura.
12
12
  # Copyright, 2019, by Jingyi Chen.
13
13
  # Copyright, 2020, by Justin Mazzocchi.
14
+ # Copyright, 2024, by Nathan Froyd.
14
15
 
15
- require_relative 'context'
16
- require_relative 'huffman'
16
+ require_relative "context"
17
+ require_relative "huffman"
17
18
 
18
19
  module Protocol
19
20
  module HPACK
@@ -74,22 +75,19 @@ module Protocol
74
75
  # @param bits [Integer] number of available bits
75
76
  # @return [String] binary string
76
77
  def write_integer(value, bits)
77
- limit = 2**bits - 1
78
+ limit = (1 << bits) - 1
78
79
 
79
- return write_bytes([value].pack('C')) if value < limit
80
+ return @buffer << value if value < limit
80
81
 
81
- bytes = []
82
- bytes.push(limit) unless bits.zero?
82
+ @buffer << limit unless bits.zero?
83
83
 
84
84
  value -= limit
85
85
  while value >= 128
86
- bytes.push((value % 128) + 128)
86
+ @buffer << ((value & 0x7f) + 128)
87
87
  value /= 128
88
88
  end
89
89
 
90
- bytes.push(value)
91
-
92
- write_bytes(bytes.pack('C*'))
90
+ @buffer << value
93
91
  end
94
92
 
95
93
  def huffman
@@ -118,7 +116,7 @@ module Protocol
118
116
  # @return [String] binary string
119
117
  def write_string(string, huffman = self.huffman)
120
118
  if huffman != :never
121
- encoded = Huffman.new.encode(string)
119
+ encoded = Huffman.encode(string)
122
120
 
123
121
  if huffman == :shorter and encoded.bytesize >= string.bytesize
124
122
  encoded = nil
@@ -2,8 +2,10 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
+ # Copyright, 2024, by Maruth Goyal.
6
+ # Copyright, 2024, by Nathan Froyd.
5
7
 
6
- require_relative 'huffman'
8
+ require_relative "huffman"
7
9
 
8
10
  module Protocol
9
11
  # Implementation of header compression for HTTP 2.0 (HPACK) format adapted
@@ -94,7 +96,20 @@ module Protocol
94
96
  ["via", ""],
95
97
  ["www-authenticate", ""],
96
98
  ].each(&:freeze).freeze
97
-
99
+
100
+ STATIC_EXACT_LOOKUP = {}
101
+ STATIC_NAME_LOOKUP = {}
102
+
103
+ STATIC_TABLE.each_with_index do |(name, value), i|
104
+ exact_header_values = (STATIC_EXACT_LOOKUP[name] ||= [])
105
+ exact_header_values << [value, i]
106
+ STATIC_NAME_LOOKUP[name] = i if STATIC_NAME_LOOKUP[name].nil?
107
+ end
108
+
109
+ STATIC_EXACT_LOOKUP.each {|k, v| v.freeze}
110
+ STATIC_EXACT_LOOKUP.freeze
111
+ STATIC_NAME_LOOKUP.freeze
112
+
98
113
  # Initializes compression context with appropriate client/server defaults and maximum size of the dynamic table.
99
114
  #
100
115
  # @param table [Array] Table of header key-value pairs.
@@ -233,29 +248,42 @@ module Protocol
233
248
  # :static Use static table only.
234
249
  # :all Use all of them.
235
250
  #
236
- # @param header [Array] +[name, value]+
251
+ # @param name [String]
252
+ # @param value [String]
237
253
  # @return [Hash] command
238
- def add_command(*header)
254
+ def add_command(name, value)
239
255
  exact = nil
240
256
  name_only = nil
241
257
 
242
- if [:all, :static].include?(@index)
243
- STATIC_TABLE.each_index do |i|
244
- if STATIC_TABLE[i] == header
245
- exact ||= i
246
- break
247
- elsif STATIC_TABLE[i].first == header.first
248
- name_only ||= i
258
+ if @index == :all || @index == :static
259
+ if (values_and_indices = STATIC_EXACT_LOOKUP[name])
260
+ values_and_indices.each do |known_value, index|
261
+ if value == known_value
262
+ exact = index
263
+ break
264
+ end
249
265
  end
266
+
267
+ needs_name_lookup = exact.nil?
268
+ else
269
+ needs_name_lookup = true
270
+ end
271
+
272
+ if needs_name_lookup && (static_value = STATIC_NAME_LOOKUP[name])
273
+ name_only = static_value
250
274
  end
251
275
  end
252
- if [:all].include?(@index) && !exact
276
+
277
+ if @index == :all && !exact
253
278
  @table.each_index do |i|
254
- if @table[i] == header
255
- exact ||= i + STATIC_TABLE.size
256
- break
257
- elsif @table[i].first == header.first
258
- name_only ||= i + STATIC_TABLE.size
279
+ entry = @table[i]
280
+ if entry.first == name
281
+ if entry.last == value
282
+ exact ||= i + STATIC_TABLE.size
283
+ break
284
+ else
285
+ name_only ||= i + STATIC_TABLE.size
286
+ end
259
287
  end
260
288
  end
261
289
  end
@@ -263,9 +291,9 @@ module Protocol
263
291
  if exact
264
292
  {name: exact, type: :indexed}
265
293
  elsif name_only
266
- {name: name_only, value: header.last, type: :incremental}
294
+ {name: name_only, value: value, type: :incremental}
267
295
  else
268
- {name: header.first, value: header.last, type: :incremental}
296
+ {name: name, value: value, type: :incremental}
269
297
  end
270
298
  end
271
299
 
@@ -2,9 +2,11 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
+ # Copyright, 2024, by Maruth Goyal.
6
+ # Copyright, 2024, by Nathan Froyd.
5
7
 
6
- require_relative 'context'
7
- require_relative 'huffman'
8
+ require_relative "context"
9
+ require_relative "huffman"
8
10
 
9
11
  module Protocol
10
12
  module HPACK
@@ -88,7 +90,7 @@ module Protocol
88
90
 
89
91
  raise CompressionError, "Invalid string length, got #{string.bytesize}, expecting #{length}!" unless string.bytesize == length
90
92
 
91
- string = Huffman.new.decode(string) if huffman
93
+ string = Huffman.decode(string) if huffman
92
94
 
93
95
  return string.force_encoding(Encoding::UTF_8)
94
96
  end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2014, by Kaoru Maeda.
5
+ # Copyright, 2015, by Tamir Duberstein.
6
+ # Copyright, 2015, by Ilya Grigorik.
7
+ # Copyright, 2016, by George Ulmer.
8
+ # Copyright, 2018-2024, by Samuel Williams.
9
+ # Copyright, 2024, by Nathan Froyd.
10
+
11
+ require_relative "../huffman"
12
+ require "set"
13
+
14
+ module Protocol
15
+ module HPACK
16
+ class Huffman
17
+ module Generator
18
+ EOS = 256
19
+
20
+ class Node
21
+ attr_accessor :next, :emit, :final, :depth
22
+ attr_accessor :transitions
23
+ attr_accessor :id
24
+
25
+ @@id = 0
26
+
27
+ def initialize(depth)
28
+ @next = [nil, nil]
29
+ @id = @@id
30
+ @@id += 1
31
+ @final = false
32
+ @depth = depth
33
+ end
34
+
35
+ def add(code, len, chr)
36
+ self.final = true if chr == EOS && @depth <= 7
37
+ if len.zero?
38
+ @emit = chr
39
+ else
40
+ bit = (code & (1 << (len - 1))).zero? ? 0 : 1
41
+ node = @next[bit] ||= Node.new(@depth + 1)
42
+ node.add(code, len - 1, chr)
43
+ end
44
+ end
45
+
46
+ class Transition
47
+ attr_accessor :emit, :node
48
+ def initialize(emit, node)
49
+ @emit = emit
50
+ @node = node
51
+ end
52
+ end
53
+
54
+ def self.generate_tree
55
+ @root = new(0)
56
+ Protocol::HPACK::Huffman::CODES.each_with_index do |c, chr|
57
+ code, len = c
58
+ @root.add(code, len, chr)
59
+ end
60
+ puts "#{@@id} nodes"
61
+ @root
62
+ end
63
+
64
+ def self.generate_machine
65
+ generate_tree
66
+
67
+ # Using un-ordered sets (potentially) produces non-deterministic results:
68
+ togo = Set[@root]
69
+ @states = Set[@root]
70
+
71
+ until togo.empty?
72
+ node = togo.first
73
+ togo.delete(node)
74
+
75
+ next if node.transitions
76
+ node.transitions = Array[1 << BITS_AT_ONCE]
77
+
78
+ (1 << BITS_AT_ONCE).times do |input|
79
+ n = node
80
+ emit = +""
81
+ (BITS_AT_ONCE - 1).downto(0) do |i|
82
+ bit = (input & (1 << i)).zero? ? 0 : 1
83
+ n = n.next[bit]
84
+ next unless n.emit
85
+ if n.emit == EOS
86
+ emit = EOS # cause error on decoding
87
+ else
88
+ emit << n.emit.chr(Encoding::BINARY) unless emit == EOS
89
+ end
90
+ n = @root
91
+ end
92
+ node.transitions[input] = Transition.new(emit, n)
93
+ togo << n
94
+ @states << n
95
+ end
96
+ end
97
+ puts "#{@states.size} states"
98
+ @root
99
+ end
100
+
101
+ MACHINE_PATH = File.expand_path("machine.rb", __dir__)
102
+
103
+ def self.generate_state_table(output_path = MACHINE_PATH)
104
+ generate_machine
105
+ state_id = {}
106
+ id_state = {}
107
+ state_id[@root] = 0
108
+ id_state[0] = @root
109
+ max_final = 0
110
+ id = 1
111
+ (@states - [@root]).sort_by {|s| s.final ? 0 : 1}.each do |s|
112
+ state_id[s] = id
113
+ id_state[id] = s
114
+ max_final = id if s.final
115
+ id += 1
116
+ end
117
+
118
+ File.open(output_path, "w") do |file|
119
+ file.print <<~HEADER
120
+ # frozen_string_literal: true
121
+
122
+ # Released under the MIT License.
123
+ # Copyright, 2018-2024, by Samuel Williams.
124
+
125
+ # Machine generated Huffman decoder state machine.
126
+ # DO NOT EDIT THIS FILE.
127
+
128
+ module Protocol
129
+ module HPACK
130
+ class Huffman
131
+ # :nodoc:
132
+ MAX_FINAL_STATE = #{max_final}
133
+ MACHINE = [
134
+ HEADER
135
+
136
+ id.times do |i|
137
+ n = id_state[i]
138
+ file.print "\t\t\t\t["
139
+ string = (1 << BITS_AT_ONCE).times.map do |t|
140
+ transition = n.transitions.fetch(t)
141
+ emit = transition.emit
142
+ unless emit == EOS
143
+ bytes = emit.bytes
144
+ fail ArgumentError if bytes.size > 1
145
+ emit = bytes.first
146
+ end
147
+ "[#{emit.inspect}, #{state_id.fetch(transition.node)}]"
148
+ end.join(", ")
149
+ file.print(string)
150
+ file.print "],\n"
151
+ end
152
+
153
+ file.print <<~FOOTER
154
+ ].each {|arr| arr.each {|subarr| subarr.each(&:freeze)}.freeze}.freeze
155
+ end
156
+ end
157
+ end
158
+ FOOTER
159
+ end
160
+ end
161
+
162
+ class << self
163
+ attr_reader :root
164
+ end
165
+
166
+ # Test decoder
167
+ def self.decode(input)
168
+ emit = ""
169
+ n = root
170
+ nibbles = input.unpack("C*").flat_map {|b| [((b & 0xf0) >> 4), b & 0xf]}
171
+ until nibbles.empty?
172
+ nb = nibbles.shift
173
+ t = n.transitions[nb]
174
+ emit << t.emit
175
+ n = t.node
176
+ end
177
+ unless n.final && nibbles.all? {|x| x == 0xf}
178
+ puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}"
179
+ end
180
+ emit
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end