quicsilver 0.2.0 → 0.3.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -4
  3. data/CHANGELOG.md +49 -0
  4. data/Gemfile.lock +8 -4
  5. data/README.md +7 -6
  6. data/Rakefile +29 -2
  7. data/benchmarks/components.rb +191 -0
  8. data/benchmarks/concurrent.rb +110 -0
  9. data/benchmarks/helpers.rb +88 -0
  10. data/benchmarks/quicsilver_server.rb +1 -1
  11. data/benchmarks/rails.rb +170 -0
  12. data/benchmarks/throughput.rb +113 -0
  13. data/ext/quicsilver/quicsilver.c +529 -181
  14. data/lib/quicsilver/client/client.rb +250 -0
  15. data/lib/quicsilver/client/request.rb +98 -0
  16. data/lib/quicsilver/{http3.rb → protocol/frames.rb} +133 -28
  17. data/lib/quicsilver/protocol/qpack/decoder.rb +165 -0
  18. data/lib/quicsilver/protocol/qpack/encoder.rb +189 -0
  19. data/lib/quicsilver/protocol/qpack/header_block_decoder.rb +125 -0
  20. data/lib/quicsilver/protocol/qpack/huffman.rb +459 -0
  21. data/lib/quicsilver/protocol/request_encoder.rb +47 -0
  22. data/lib/quicsilver/protocol/request_parser.rb +387 -0
  23. data/lib/quicsilver/protocol/response_encoder.rb +72 -0
  24. data/lib/quicsilver/protocol/response_parser.rb +249 -0
  25. data/lib/quicsilver/server/listener_data.rb +14 -0
  26. data/lib/quicsilver/server/request_handler.rb +86 -0
  27. data/lib/quicsilver/server/request_registry.rb +50 -0
  28. data/lib/quicsilver/server/server.rb +336 -0
  29. data/lib/quicsilver/transport/configuration.rb +132 -0
  30. data/lib/quicsilver/transport/connection.rb +350 -0
  31. data/lib/quicsilver/transport/event_loop.rb +38 -0
  32. data/lib/quicsilver/transport/inbound_stream.rb +33 -0
  33. data/lib/quicsilver/transport/stream.rb +28 -0
  34. data/lib/quicsilver/transport/stream_event.rb +26 -0
  35. data/lib/quicsilver/version.rb +1 -1
  36. data/lib/quicsilver.rb +31 -13
  37. data/lib/rackup/handler/quicsilver.rb +1 -2
  38. data/quicsilver.gemspec +3 -1
  39. metadata +58 -18
  40. data/benchmarks/benchmark.rb +0 -68
  41. data/lib/quicsilver/client.rb +0 -261
  42. data/lib/quicsilver/connection.rb +0 -42
  43. data/lib/quicsilver/event_loop.rb +0 -38
  44. data/lib/quicsilver/http3/request_encoder.rb +0 -133
  45. data/lib/quicsilver/http3/request_parser.rb +0 -176
  46. data/lib/quicsilver/http3/response_encoder.rb +0 -186
  47. data/lib/quicsilver/http3/response_parser.rb +0 -160
  48. data/lib/quicsilver/listener_data.rb +0 -29
  49. data/lib/quicsilver/quic_stream.rb +0 -36
  50. data/lib/quicsilver/request_registry.rb +0 -48
  51. data/lib/quicsilver/server.rb +0 -355
  52. data/lib/quicsilver/server_configuration.rb +0 -78
@@ -0,0 +1,459 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Protocol
5
+ module Qpack
6
+ class Huffman
7
+ # RFC 7541 Appendix B — HPACK Huffman Code Table (reused by QPACK per RFC 9204)
8
+ # Format: byte_value => [hex_code, bit_length] # |MSB binary with byte boundaries|
9
+ TABLE = {
10
+ 0 => [0x1ff8, 13], # |11111111|11000
11
+ 1 => [0x7fffd8, 23], # |11111111|11111111|1011000
12
+ 2 => [0xfffffe2, 28], # |11111111|11111111|11111110|0010
13
+ 3 => [0xfffffe3, 28], # |11111111|11111111|11111110|0011
14
+ 4 => [0xfffffe4, 28], # |11111111|11111111|11111110|0100
15
+ 5 => [0xfffffe5, 28], # |11111111|11111111|11111110|0101
16
+ 6 => [0xfffffe6, 28], # |11111111|11111111|11111110|0110
17
+ 7 => [0xfffffe7, 28], # |11111111|11111111|11111110|0111
18
+ 8 => [0xfffffe8, 28], # |11111111|11111111|11111110|1000
19
+ 9 => [0xffffea, 24], # |11111111|11111111|11101010
20
+ 10 => [0x3ffffffc, 30], # |11111111|11111111|11111111|111100
21
+ 11 => [0xfffffe9, 28], # |11111111|11111111|11111110|1001
22
+ 12 => [0xfffffea, 28], # |11111111|11111111|11111110|1010
23
+ 13 => [0x3ffffffd, 30], # |11111111|11111111|11111111|111101
24
+ 14 => [0xfffffeb, 28], # |11111111|11111111|11111110|1011
25
+ 15 => [0xfffffec, 28], # |11111111|11111111|11111110|1100
26
+ 16 => [0xfffffed, 28], # |11111111|11111111|11111110|1101
27
+ 17 => [0xfffffee, 28], # |11111111|11111111|11111110|1110
28
+ 18 => [0xfffffef, 28], # |11111111|11111111|11111110|1111
29
+ 19 => [0xffffff0, 28], # |11111111|11111111|11111111|0000
30
+ 20 => [0xffffff1, 28], # |11111111|11111111|11111111|0001
31
+ 21 => [0xffffff2, 28], # |11111111|11111111|11111111|0010
32
+ 22 => [0x3ffffffe, 30], # |11111111|11111111|11111111|111110
33
+ 23 => [0xffffff3, 28], # |11111111|11111111|11111111|0011
34
+ 24 => [0xffffff4, 28], # |11111111|11111111|11111111|0100
35
+ 25 => [0xffffff5, 28], # |11111111|11111111|11111111|0101
36
+ 26 => [0xffffff6, 28], # |11111111|11111111|11111111|0110
37
+ 27 => [0xffffff7, 28], # |11111111|11111111|11111111|0111
38
+ 28 => [0xffffff8, 28], # |11111111|11111111|11111111|1000
39
+ 29 => [0xffffff9, 28], # |11111111|11111111|11111111|1001
40
+ 30 => [0xffffffa, 28], # |11111111|11111111|11111111|1010
41
+ 31 => [0xffffffb, 28], # |11111111|11111111|11111111|1011
42
+ 32 => [0x14, 6], # |010100
43
+ 33 => [0x3f8, 10], # |11111110|00
44
+ 34 => [0x3f9, 10], # |11111110|01
45
+ 35 => [0xffa, 12], # |11111111|1010
46
+ 36 => [0x1ff9, 13], # |11111111|11001
47
+ 37 => [0x15, 6], # |010101
48
+ 38 => [0xf8, 8], # |11111000
49
+ 39 => [0x7fa, 11], # |11111111|010
50
+ 40 => [0x3fa, 10], # |11111110|10
51
+ 41 => [0x3fb, 10], # |11111110|11
52
+ 42 => [0xf9, 8], # |11111001
53
+ 43 => [0x7fb, 11], # |11111111|011
54
+ 44 => [0xfa, 8], # |11111010
55
+ 45 => [0x16, 6], # |010110
56
+ 46 => [0x17, 6], # |010111
57
+ 47 => [0x18, 6], # |011000
58
+ 48 => [0x0, 5], # |00000
59
+ 49 => [0x1, 5], # |00001
60
+ 50 => [0x2, 5], # |00010
61
+ 51 => [0x19, 6], # |011001
62
+ 52 => [0x1a, 6], # |011010
63
+ 53 => [0x1b, 6], # |011011
64
+ 54 => [0x1c, 6], # |011100
65
+ 55 => [0x1d, 6], # |011101
66
+ 56 => [0x1e, 6], # |011110
67
+ 57 => [0x1f, 6], # |011111
68
+ 58 => [0x5c, 7], # |1011100
69
+ 59 => [0xfb, 8], # |11111011
70
+ 60 => [0x7ffc, 15], # |11111111|1111100
71
+ 61 => [0x20, 6], # |100000
72
+ 62 => [0xffb, 12], # |11111111|1011
73
+ 63 => [0x3fc, 10], # |11111111|00
74
+ 64 => [0x1ffa, 13], # |11111111|11010
75
+ 65 => [0x21, 6], # |100001
76
+ 66 => [0x5d, 7], # |1011101
77
+ 67 => [0x5e, 7], # |1011110
78
+ 68 => [0x5f, 7], # |1011111
79
+ 69 => [0x60, 7], # |1100000
80
+ 70 => [0x61, 7], # |1100001
81
+ 71 => [0x62, 7], # |1100010
82
+ 72 => [0x63, 7], # |1100011
83
+ 73 => [0x64, 7], # |1100100
84
+ 74 => [0x65, 7], # |1100101
85
+ 75 => [0x66, 7], # |1100110
86
+ 76 => [0x67, 7], # |1100111
87
+ 77 => [0x68, 7], # |1101000
88
+ 78 => [0x69, 7], # |1101001
89
+ 79 => [0x6a, 7], # |1101010
90
+ 80 => [0x6b, 7], # |1101011
91
+ 81 => [0x6c, 7], # |1101100
92
+ 82 => [0x6d, 7], # |1101101
93
+ 83 => [0x6e, 7], # |1101110
94
+ 84 => [0x6f, 7], # |1101111
95
+ 85 => [0x70, 7], # |1110000
96
+ 86 => [0x71, 7], # |1110001
97
+ 87 => [0x72, 7], # |1110010
98
+ 88 => [0xfc, 8], # |11111100
99
+ 89 => [0x73, 7], # |1110011
100
+ 90 => [0xfd, 8], # |11111101
101
+ 91 => [0x1ffb, 13], # |11111111|11011
102
+ 92 => [0x7fff0, 19], # |11111111|11111110|000
103
+ 93 => [0x1ffc, 13], # |11111111|11100
104
+ 94 => [0x3ffc, 14], # |11111111|111100
105
+ 95 => [0x22, 6], # |100010
106
+ 96 => [0x7ffd, 15], # |11111111|1111101
107
+ 97 => [0x3, 5], # |00011
108
+ 98 => [0x23, 6], # |100011
109
+ 99 => [0x4, 5], # |00100
110
+ 100 => [0x24, 6], # |100100
111
+ 101 => [0x5, 5], # |00101
112
+ 102 => [0x25, 6], # |100101
113
+ 103 => [0x26, 6], # |100110
114
+ 104 => [0x27, 6], # |100111
115
+ 105 => [0x6, 5], # |00110
116
+ 106 => [0x74, 7], # |1110100
117
+ 107 => [0x75, 7], # |1110101
118
+ 108 => [0x28, 6], # |101000
119
+ 109 => [0x29, 6], # |101001
120
+ 110 => [0x2a, 6], # |101010
121
+ 111 => [0x7, 5], # |00111
122
+ 112 => [0x2b, 6], # |101011
123
+ 113 => [0x76, 7], # |1110110
124
+ 114 => [0x2c, 6], # |101100
125
+ 115 => [0x8, 5], # |01000
126
+ 116 => [0x9, 5], # |01001
127
+ 117 => [0x2d, 6], # |101101
128
+ 118 => [0x77, 7], # |1110111
129
+ 119 => [0x78, 7], # |1111000
130
+ 120 => [0x79, 7], # |1111001
131
+ 121 => [0x7a, 7], # |1111010
132
+ 122 => [0x7b, 7], # |1111011
133
+ 123 => [0x7ffe, 15], # |11111111|1111110
134
+ 124 => [0x7fc, 11], # |11111111|100
135
+ 125 => [0x3ffd, 14], # |11111111|111101
136
+ 126 => [0x1ffd, 13], # |11111111|11101
137
+ 127 => [0xffffffc, 28], # |11111111|11111111|11111111|1100
138
+ 128 => [0xfffe6, 20], # |11111111|11111110|0110
139
+ 129 => [0x3fffd2, 22], # |11111111|11111111|010010
140
+ 130 => [0xfffe7, 20], # |11111111|11111110|0111
141
+ 131 => [0xfffe8, 20], # |11111111|11111110|1000
142
+ 132 => [0x3fffd3, 22], # |11111111|11111111|010011
143
+ 133 => [0x3fffd4, 22], # |11111111|11111111|010100
144
+ 134 => [0x3fffd5, 22], # |11111111|11111111|010101
145
+ 135 => [0x7fffd9, 23], # |11111111|11111111|1011001
146
+ 136 => [0x3fffd6, 22], # |11111111|11111111|010110
147
+ 137 => [0x7fffda, 23], # |11111111|11111111|1011010
148
+ 138 => [0x7fffdb, 23], # |11111111|11111111|1011011
149
+ 139 => [0x7fffdc, 23], # |11111111|11111111|1011100
150
+ 140 => [0x7fffdd, 23], # |11111111|11111111|1011101
151
+ 141 => [0x7fffde, 23], # |11111111|11111111|1011110
152
+ 142 => [0xffffeb, 24], # |11111111|11111111|11101011
153
+ 143 => [0x7fffdf, 23], # |11111111|11111111|1011111
154
+ 144 => [0xffffec, 24], # |11111111|11111111|11101100
155
+ 145 => [0xffffed, 24], # |11111111|11111111|11101101
156
+ 146 => [0x3fffd7, 22], # |11111111|11111111|010111
157
+ 147 => [0x7fffe0, 23], # |11111111|11111111|1100000
158
+ 148 => [0xffffee, 24], # |11111111|11111111|11101110
159
+ 149 => [0x7fffe1, 23], # |11111111|11111111|1100001
160
+ 150 => [0x7fffe2, 23], # |11111111|11111111|1100010
161
+ 151 => [0x7fffe3, 23], # |11111111|11111111|1100011
162
+ 152 => [0x7fffe4, 23], # |11111111|11111111|1100100
163
+ 153 => [0x1fffdc, 21], # |11111111|11111110|11100
164
+ 154 => [0x3fffd8, 22], # |11111111|11111111|011000
165
+ 155 => [0x7fffe5, 23], # |11111111|11111111|1100101
166
+ 156 => [0x3fffd9, 22], # |11111111|11111111|011001
167
+ 157 => [0x7fffe6, 23], # |11111111|11111111|1100110
168
+ 158 => [0x7fffe7, 23], # |11111111|11111111|1100111
169
+ 159 => [0xffffef, 24], # |11111111|11111111|11101111
170
+ 160 => [0x3fffda, 22], # |11111111|11111111|011010
171
+ 161 => [0x1fffdd, 21], # |11111111|11111110|11101
172
+ 162 => [0xfffe9, 20], # |11111111|11111110|1001
173
+ 163 => [0x3fffdb, 22], # |11111111|11111111|011011
174
+ 164 => [0x3fffdc, 22], # |11111111|11111111|011100
175
+ 165 => [0x7fffe8, 23], # |11111111|11111111|1101000
176
+ 166 => [0x7fffe9, 23], # |11111111|11111111|1101001
177
+ 167 => [0x1fffde, 21], # |11111111|11111110|11110
178
+ 168 => [0x7fffea, 23], # |11111111|11111111|1101010
179
+ 169 => [0x3fffdd, 22], # |11111111|11111111|011101
180
+ 170 => [0x3fffde, 22], # |11111111|11111111|011110
181
+ 171 => [0xfffff0, 24], # |11111111|11111111|11110000
182
+ 172 => [0x1fffdf, 21], # |11111111|11111110|11111
183
+ 173 => [0x3fffdf, 22], # |11111111|11111111|011111
184
+ 174 => [0x7fffeb, 23], # |11111111|11111111|1101011
185
+ 175 => [0x7fffec, 23], # |11111111|11111111|1101100
186
+ 176 => [0x1fffe0, 21], # |11111111|11111111|00000
187
+ 177 => [0x1fffe1, 21], # |11111111|11111111|00001
188
+ 178 => [0x3fffe0, 22], # |11111111|11111111|100000
189
+ 179 => [0x1fffe2, 21], # |11111111|11111111|00010
190
+ 180 => [0x7fffed, 23], # |11111111|11111111|1101101
191
+ 181 => [0x3fffe1, 22], # |11111111|11111111|100001
192
+ 182 => [0x7fffee, 23], # |11111111|11111111|1101110
193
+ 183 => [0x7fffef, 23], # |11111111|11111111|1101111
194
+ 184 => [0xfffea, 20], # |11111111|11111110|1010
195
+ 185 => [0x3fffe2, 22], # |11111111|11111111|100010
196
+ 186 => [0x3fffe3, 22], # |11111111|11111111|100011
197
+ 187 => [0x3fffe4, 22], # |11111111|11111111|100100
198
+ 188 => [0x7ffff0, 23], # |11111111|11111111|1110000
199
+ 189 => [0x3fffe5, 22], # |11111111|11111111|100101
200
+ 190 => [0x3fffe6, 22], # |11111111|11111111|100110
201
+ 191 => [0x7ffff1, 23], # |11111111|11111111|1110001
202
+ 192 => [0x3ffffe0, 26], # |11111111|11111111|11111110|00
203
+ 193 => [0x3ffffe1, 26], # |11111111|11111111|11111110|01
204
+ 194 => [0xfffeb, 20], # |11111111|11111110|1011
205
+ 195 => [0x7fff1, 19], # |11111111|11111110|001
206
+ 196 => [0x3fffe7, 22], # |11111111|11111111|100111
207
+ 197 => [0x7ffff2, 23], # |11111111|11111111|1110010
208
+ 198 => [0x3fffe8, 22], # |11111111|11111111|101000
209
+ 199 => [0x1ffffec, 25], # |11111111|11111111|11111110|1100
210
+ 200 => [0x3ffffe2, 26], # |11111111|11111111|11111110|10
211
+ 201 => [0x3ffffe3, 26], # |11111111|11111111|11111110|11
212
+ 202 => [0x3ffffe4, 26], # |11111111|11111111|11111111|00
213
+ 203 => [0x7ffffde, 27], # |11111111|11111111|11111111|011
214
+ 204 => [0x7ffffdf, 27], # |11111111|11111111|11111111|100
215
+ 205 => [0x3ffffe5, 26], # |11111111|11111111|11111111|01
216
+ 206 => [0xfffff1, 24], # |11111111|11111111|11110001
217
+ 207 => [0x1ffffed, 25], # |11111111|11111111|11111110|1101
218
+ 208 => [0x7fff2, 19], # |11111111|11111110|010
219
+ 209 => [0x1fffe3, 21], # |11111111|11111111|00011
220
+ 210 => [0x3ffffe6, 26], # |11111111|11111111|11111111|10
221
+ 211 => [0x7ffffe0, 27], # |11111111|11111111|11111111|110
222
+ 212 => [0x7ffffe1, 27], # |11111111|11111111|11111111|111
223
+ 213 => [0x3ffffe7, 26], # |11111111|11111111|11111111|11
224
+ 214 => [0x7ffffe2, 27], # |11111111|11111111|11111110|000
225
+ 215 => [0xfffff2, 24], # |11111111|11111111|11110010
226
+ 216 => [0x1fffe4, 21], # |11111111|11111111|00100
227
+ 217 => [0x1fffe5, 21], # |11111111|11111111|00101
228
+ 218 => [0x3ffffe8, 26], # |11111111|11111111|11111110|0000
229
+ 219 => [0x3ffffe9, 26], # |11111111|11111111|11111110|0001
230
+ 220 => [0xffffffd, 28], # |11111111|11111111|11111111|1101
231
+ 221 => [0x7ffffe3, 27], # |11111111|11111111|11111110|001
232
+ 222 => [0x7ffffe4, 27], # |11111111|11111111|11111110|010
233
+ 223 => [0x7ffffe5, 27], # |11111111|11111111|11111110|011
234
+ 224 => [0xfffec, 20], # |11111111|11111110|1100
235
+ 225 => [0xfffff3, 24], # |11111111|11111111|11110011
236
+ 226 => [0xfffed, 20], # |11111111|11111110|1101
237
+ 227 => [0x1fffe6, 21], # |11111111|11111111|00110
238
+ 228 => [0x3fffe9, 22], # |11111111|11111111|101001
239
+ 229 => [0x1fffe7, 21], # |11111111|11111111|00111
240
+ 230 => [0x1fffe8, 21], # |11111111|11111111|01000
241
+ 231 => [0x7ffff3, 23], # |11111111|11111111|1110011
242
+ 232 => [0x3fffea, 22], # |11111111|11111111|101010
243
+ 233 => [0x3fffeb, 22], # |11111111|11111111|101011
244
+ 234 => [0x1ffffee, 25], # |11111111|11111111|11111110|1110
245
+ 235 => [0x1ffffef, 25], # |11111111|11111111|11111110|1111
246
+ 236 => [0xfffff4, 24], # |11111111|11111111|11110100
247
+ 237 => [0xfffff5, 24], # |11111111|11111111|11110101
248
+ 238 => [0x3ffffea, 26], # |11111111|11111111|11111110|1010
249
+ 239 => [0x7ffff4, 23], # |11111111|11111111|1110100
250
+ 240 => [0x3ffffeb, 26], # |11111111|11111111|11111110|1011
251
+ 241 => [0x7ffffe6, 27], # |11111111|11111111|11111110|110
252
+ 242 => [0x3ffffec, 26], # |11111111|11111111|11111110|1100
253
+ 243 => [0x3ffffed, 26], # |11111111|11111111|11111110|1101
254
+ 244 => [0x7ffffe7, 27], # |11111111|11111111|11111110|111
255
+ 245 => [0x7ffffe8, 27], # |11111111|11111111|11111111|000
256
+ 246 => [0x7ffffe9, 27], # |11111111|11111111|11111111|001
257
+ 247 => [0x7ffffea, 27], # |11111111|11111111|11111111|010
258
+ 248 => [0x7ffffeb, 27], # |11111111|11111111|11111111|011
259
+ 249 => [0xffffffe, 28], # |11111111|11111111|11111111|1110
260
+ 250 => [0x7ffffec, 27], # |11111111|11111111|11111111|100
261
+ 251 => [0x7ffffed, 27], # |11111111|11111111|11111111|101
262
+ 252 => [0x7ffffee, 27], # |11111111|11111111|11111111|110
263
+ 253 => [0x7ffffef, 27], # |11111111|11111111|11111111|111
264
+ 254 => [0x7fffff0, 27], # |11111111|11111111|11111110|0000
265
+ 255 => [0x3ffffee, 26], # |11111111|11111111|11111110|1110
266
+ }.freeze
267
+
268
+ EOS = [0x3fffffff, 30] # |11111111|11111111|11111111|111111
269
+
270
+ REVERSE_TABLE = TABLE.each_with_object({}) do |(byte, (code, length)), tree|
271
+ node = tree
272
+ # Walk bits MSB-first
273
+ (length - 1).downto(0) do |i|
274
+ bit = (code >> i) & 1
275
+ node[bit] ||= {}
276
+ node = node[bit]
277
+ end
278
+ node[:value] = byte
279
+ end
280
+
281
+ # Build 8-bit-at-a-time decode acceleration table.
282
+ # DECODE_ACCEL[state_id][byte] = [new_state_id, emitted_bytes_array]
283
+ # State 0 = root node.
284
+ DECODE_ACCEL = begin
285
+ # Assign integer IDs to all tree nodes
286
+ node_to_id = {}
287
+ id_to_node = []
288
+ queue = [REVERSE_TABLE]
289
+ node_to_id[REVERSE_TABLE.object_id] = 0
290
+ id_to_node << REVERSE_TABLE
291
+
292
+ i = 0
293
+ while i < queue.size
294
+ node = queue[i]
295
+ [0, 1].each do |bit|
296
+ child = node[bit]
297
+ next unless child
298
+ unless node_to_id.key?(child.object_id)
299
+ node_to_id[child.object_id] = id_to_node.size
300
+ id_to_node << child
301
+ queue << child
302
+ end
303
+ end
304
+ i += 1
305
+ end
306
+
307
+ num_states = id_to_node.size
308
+ table = Array.new(num_states) { Array.new(256) }
309
+
310
+ num_states.times do |sid|
311
+ 256.times do |byte_val|
312
+ node = id_to_node[sid]
313
+ emitted = []
314
+ valid = true
315
+ trailing_bits = 0 # bits since last emitted symbol
316
+ 7.downto(0) do |bit_idx|
317
+ bit = (byte_val >> bit_idx) & 1
318
+ child = node[bit]
319
+ unless child
320
+ valid = false
321
+ break
322
+ end
323
+ if child.key?(:value)
324
+ emitted << child[:value]
325
+ node = REVERSE_TABLE
326
+ trailing_bits = 0
327
+ else
328
+ node = child
329
+ trailing_bits += 1
330
+ end
331
+ end
332
+ if valid
333
+ new_sid = node_to_id[node.object_id]
334
+ table[sid][byte_val] = [new_sid, emitted, trailing_bits].freeze
335
+ else
336
+ table[sid][byte_val] = nil
337
+ end
338
+ end
339
+ table[sid].freeze
340
+ end
341
+ table.freeze
342
+ end
343
+
344
+
345
+
346
+
347
+ # Pre-compute flat arrays for faster table access (avoid hash lookup per byte)
348
+ ENCODE_CODES = Array.new(256) { |i| TABLE[i][0] }
349
+ ENCODE_LENGTHS = Array.new(256) { |i| TABLE[i][1] }
350
+
351
+ # Thread-safe caches for frequently encoded/decoded strings
352
+ # Using module-level constants for caches — YJIT can inline constant access
353
+ HUFF_ENCODE_CACHE = {}
354
+ HUFF_ENCODE_MUTEX = Mutex.new
355
+ HUFF_DECODE_CACHE = {}
356
+ HUFF_DECODE_MUTEX = Mutex.new
357
+ CACHE_MAX = 256
358
+
359
+ class << self
360
+ def encode(data)
361
+ # Check cache first (fast path) — returns frozen string
362
+ cached = HUFF_ENCODE_CACHE[data]
363
+ return cached if cached
364
+
365
+ result = encode_uncached(data)
366
+
367
+ # Cache short strings (frozen)
368
+ if data.bytesize <= 128 && HUFF_ENCODE_CACHE.size < CACHE_MAX
369
+ HUFF_ENCODE_MUTEX.synchronize do
370
+ HUFF_ENCODE_CACHE[data.frozen? ? data : data.dup.freeze] = result.freeze if HUFF_ENCODE_CACHE.size < CACHE_MAX
371
+ end
372
+ end
373
+
374
+ result
375
+ end
376
+
377
+ def encode_uncached(data)
378
+ buf = 0
379
+ buf_len = 0
380
+ out = []
381
+
382
+ data.each_byte do |byte|
383
+ code = ENCODE_CODES[byte]
384
+ length = ENCODE_LENGTHS[byte]
385
+ buf = (buf << length) | code
386
+ buf_len += length
387
+
388
+ # Flush complete bytes
389
+ while buf_len >= 8
390
+ buf_len -= 8
391
+ out << ((buf >> buf_len) & 0xff)
392
+ end
393
+ end
394
+
395
+ # Pad with EOS prefix (1-bits) to next byte boundary
396
+ if buf_len > 0
397
+ padding = 8 - buf_len
398
+ out << (((buf << padding) | ((1 << padding) - 1)) & 0xff)
399
+ end
400
+
401
+ out.pack("C*")
402
+ end
403
+
404
+ def decode(encoded_data)
405
+ # Check cache first (fast path) — returns frozen string
406
+ cached = HUFF_DECODE_CACHE[encoded_data]
407
+ return cached if cached
408
+
409
+ return "".b if encoded_data.empty?
410
+
411
+ result = decode_uncached(encoded_data)
412
+
413
+ # Cache short encoded data (frozen)
414
+ if encoded_data.bytesize <= 128 && result && HUFF_DECODE_CACHE.size < CACHE_MAX
415
+ HUFF_DECODE_MUTEX.synchronize do
416
+ key = encoded_data.frozen? ? encoded_data : encoded_data.dup.freeze
417
+ HUFF_DECODE_CACHE[key] = result.freeze if HUFF_DECODE_CACHE.size < CACHE_MAX
418
+ end
419
+ end
420
+
421
+ result
422
+ end
423
+
424
+ def decode_uncached(encoded_data)
425
+ return "".b if encoded_data.empty?
426
+
427
+ state = 0
428
+ output = []
429
+ bits_since_symbol = 0
430
+
431
+ encoded_data.each_byte do |byte|
432
+ entry = DECODE_ACCEL[state][byte]
433
+ return nil unless entry # invalid code
434
+
435
+ new_state, emitted, trailing = entry
436
+ output.concat(emitted) unless emitted.empty?
437
+ # trailing = bits since last emitted symbol within this byte processing
438
+ # If no symbols were emitted, add 8 to running count
439
+ bits_since_symbol = emitted.empty? ? bits_since_symbol + 8 : trailing
440
+ state = new_state
441
+ end
442
+
443
+ # Padding validation (RFC 7541 §5.2):
444
+ return nil if bits_since_symbol >= 8
445
+
446
+ # Padding bits must all be 1
447
+ if bits_since_symbol > 0
448
+ last_byte = encoded_data.getbyte(-1)
449
+ mask = (1 << bits_since_symbol) - 1
450
+ return nil if (last_byte & mask) != mask
451
+ end
452
+
453
+ output.pack("C*")
454
+ end
455
+ end
456
+ end
457
+ end
458
+ end
459
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Quicsilver
4
+ module Protocol
5
+ class RequestEncoder
6
+ def initialize(method:, path:, scheme: "https", authority: "localhost:4433", headers: {}, body: nil, encoder: Qpack::Encoder.new)
7
+ @method = method.upcase
8
+ @path = path
9
+ @scheme = scheme
10
+ @authority = authority
11
+ @headers = headers
12
+ @body = body
13
+ @encoder = encoder
14
+ end
15
+
16
+ def encode
17
+ frames = "".b
18
+ frames << build_frame(FRAME_HEADERS, @encoder.encode(all_headers))
19
+
20
+ if @body && !@body.empty?
21
+ body_data = @body.is_a?(String) ? @body : @body.join
22
+ frames << build_frame(FRAME_DATA, body_data)
23
+ end
24
+
25
+ frames
26
+ end
27
+
28
+ private
29
+
30
+ def all_headers
31
+ headers = [[":method", @method]]
32
+ if @method == "CONNECT"
33
+ headers << [":authority", @authority]
34
+ else
35
+ headers << [":scheme", @scheme]
36
+ headers << [":authority", @authority]
37
+ headers << [":path", @path]
38
+ end
39
+ headers + @headers.map { |k, v| [k.to_s, v.to_s] }
40
+ end
41
+
42
+ def build_frame(type, payload)
43
+ Protocol.encode_varint(type) + Protocol.encode_varint(payload.bytesize) + payload
44
+ end
45
+ end
46
+ end
47
+ end