protocol-hpack 1.4.3 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/hpack/compressor.rb +9 -11
- data/lib/protocol/hpack/context.rb +47 -19
- data/lib/protocol/hpack/decompressor.rb +5 -3
- data/lib/protocol/hpack/huffman/generator.rb +186 -0
- data/lib/protocol/hpack/huffman/machine.rb +254 -254
- data/lib/protocol/hpack/huffman.rb +14 -11
- data/lib/protocol/hpack/version.rb +1 -1
- data/lib/protocol/hpack.rb +2 -2
- data/license.md +2 -0
- data/readme.md +8 -8
- data.tar.gz.sig +0 -0
- metadata +8 -6
- metadata.gz.sig +0 -0
- data/tasks/huffman.rake +0 -10
- data/tasks/huffman.rb +0 -173
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad81d71708c7c92bb94e9616655872045a31052aa62c3543487064ddea8c99ad
|
4
|
+
data.tar.gz: 1945361f8e8576ef3db44de1e0cf44554795699d910c40b8659232a499ded6b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
16
|
-
require_relative
|
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 =
|
78
|
+
limit = (1 << bits) - 1
|
78
79
|
|
79
|
-
return
|
80
|
+
return @buffer << value if value < limit
|
80
81
|
|
81
|
-
|
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
|
-
|
86
|
+
@buffer << ((value & 0x7f) + 128)
|
87
87
|
value /= 128
|
88
88
|
end
|
89
89
|
|
90
|
-
|
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.
|
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
|
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
|
251
|
+
# @param name [String]
|
252
|
+
# @param value [String]
|
237
253
|
# @return [Hash] command
|
238
|
-
def add_command(
|
254
|
+
def add_command(name, value)
|
239
255
|
exact = nil
|
240
256
|
name_only = nil
|
241
257
|
|
242
|
-
if
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
276
|
+
|
277
|
+
if @index == :all && !exact
|
253
278
|
@table.each_index do |i|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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:
|
294
|
+
{name: name_only, value: value, type: :incremental}
|
267
295
|
else
|
268
|
-
{name:
|
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
|
7
|
-
require_relative
|
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.
|
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
|