biryani 0.0.4 → 0.0.6

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Rakefile +7 -0
  4. data/conformance/server_spec.rb +31 -0
  5. data/conformance/spec_helper.rb +14 -0
  6. data/example/echo.rb +0 -1
  7. data/example/raise_error.rb +19 -0
  8. data/lib/biryani/connection.rb +23 -18
  9. data/lib/biryani/data_buffer.rb +1 -1
  10. data/lib/biryani/error.rb +8 -0
  11. data/lib/biryani/frame/continuation.rb +4 -7
  12. data/lib/biryani/frame/data.rb +9 -12
  13. data/lib/biryani/frame/goaway.rb +6 -6
  14. data/lib/biryani/frame/headers.rb +25 -18
  15. data/lib/biryani/frame/ping.rb +5 -7
  16. data/lib/biryani/frame/priority.rb +6 -5
  17. data/lib/biryani/frame/push_promise.rb +14 -15
  18. data/lib/biryani/frame/rst_stream.rb +5 -5
  19. data/lib/biryani/frame/settings.rb +5 -5
  20. data/lib/biryani/frame/unknown.rb +1 -15
  21. data/lib/biryani/frame/window_update.rb +5 -5
  22. data/lib/biryani/frame.rb +10 -4
  23. data/lib/biryani/hpack/field.rb +42 -42
  24. data/lib/biryani/hpack/fields.rb +2 -1
  25. data/lib/biryani/hpack/huffman.rb +290 -21
  26. data/lib/biryani/hpack/integer.rb +10 -6
  27. data/lib/biryani/hpack/string.rb +6 -6
  28. data/lib/biryani/http_request.rb +3 -2
  29. data/lib/biryani/http_response.rb +23 -2
  30. data/lib/biryani/server.rb +1 -1
  31. data/lib/biryani/state.rb +14 -7
  32. data/lib/biryani/stream.rb +10 -3
  33. data/lib/biryani/streams_context.rb +12 -6
  34. data/lib/biryani/version.rb +1 -1
  35. data/lib/biryani.rb +1 -1
  36. data/spec/connection/handle_data_spec.rb +2 -2
  37. data/spec/connection/handle_headers_spec.rb +1 -1
  38. data/spec/connection/{transition_state_send_spec.rb → transition_stream_state_send_spec.rb} +3 -3
  39. data/spec/frame/continuation_spec.rb +2 -2
  40. data/spec/frame/data_spec.rb +1 -1
  41. data/spec/frame/goaway_spec.rb +1 -1
  42. data/spec/frame/headers_spec.rb +2 -2
  43. data/spec/frame/ping_spec.rb +1 -1
  44. data/spec/frame/priority_spec.rb +1 -1
  45. data/spec/frame/push_promise_spec.rb +1 -1
  46. data/spec/frame/rst_stream_spec.rb +1 -1
  47. data/spec/frame/settings_spec.rb +1 -1
  48. data/spec/frame/window_update_spec.rb +1 -1
  49. data/spec/hpack/field_spec.rb +10 -9
  50. data/spec/hpack/huffman_spec.rb +4 -4
  51. data/spec/hpack/integer_spec.rb +5 -5
  52. data/spec/hpack/string_spec.rb +2 -2
  53. data/spec/http_response_spec.rb +12 -0
  54. metadata +9 -3
@@ -98,7 +98,7 @@ module Biryani
98
98
  "\x40#{String.encode(name)}#{String.encode(value)}"
99
99
  end
100
100
 
101
- # @param s [String]
101
+ # @param io [IO::Buffer]
102
102
  # @param cursor [Integer]
103
103
  # @param dynamic_table [DynamicTable]
104
104
  #
@@ -106,24 +106,24 @@ module Biryani
106
106
  # @return [Integer]
107
107
  # rubocop: disable Metrics/CyclomaticComplexity
108
108
  # rubocop: disable Metrics/PerceivedComplexity
109
- def self.decode(s, cursor, dynamic_table)
110
- byte = s.getbyte(cursor)
109
+ def self.decode(io, cursor, dynamic_table)
110
+ byte = io.get_value(:U8, cursor)
111
111
  if (byte & 0b10000000).positive?
112
- decode_indexed(s, cursor, dynamic_table)
112
+ decode_indexed(io, cursor, dynamic_table)
113
113
  elsif byte == 0b01000000
114
- decode_literal_field_incremental_indexing(s, cursor, dynamic_table)
114
+ decode_literal_field_incremental_indexing(io, cursor, dynamic_table)
115
115
  elsif (byte & 0b01000000).positive?
116
- decode_literal_value_incremental_indexing(s, cursor, dynamic_table)
116
+ decode_literal_value_incremental_indexing(io, cursor, dynamic_table)
117
117
  elsif (byte & 0b00100000).positive?
118
- decode_dynamic_table_size_update(s, cursor, dynamic_table)
118
+ decode_dynamic_table_size_update(io, cursor, dynamic_table)
119
119
  elsif byte == 0b00010000
120
- decode_literal_field_never_indexed(s, cursor)
120
+ decode_literal_field_never_indexed(io, cursor)
121
121
  elsif (byte & 0b00010000).positive?
122
- decode_literal_value_never_indexed(s, cursor, dynamic_table)
122
+ decode_literal_value_never_indexed(io, cursor, dynamic_table)
123
123
  elsif byte.zero?
124
- decode_literal_field_without_indexing(s, cursor)
124
+ decode_literal_field_without_indexing(io, cursor)
125
125
  elsif (byte & 0b11110000).zero?
126
- decode_literal_value_without_indexing(s, cursor, dynamic_table)
126
+ decode_literal_value_without_indexing(io, cursor, dynamic_table)
127
127
  else
128
128
  raise 'unreachable'
129
129
  end
@@ -137,14 +137,14 @@ module Biryani
137
137
  # +---+---------------------------+
138
138
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.1
139
139
  #
140
- # @param s [String]
140
+ # @param io [IO::Buffer]
141
141
  # @param cursor [Integer]
142
142
  # @param dynamic_table [DynamicTable]
143
143
  #
144
144
  # @return [Array]
145
145
  # @return [Integer]
146
- def self.decode_indexed(s, cursor, dynamic_table)
147
- index, c = Integer.decode(s, 7, cursor)
146
+ def self.decode_indexed(io, cursor, dynamic_table)
147
+ index, c = Integer.decode(io, 7, cursor)
148
148
  raise Error::HPACKDecodeError if index.zero?
149
149
  raise Error::HPACKDecodeError if index > STATIC_TABLE_SIZE + dynamic_table.count_entries
150
150
 
@@ -171,15 +171,15 @@ module Biryani
171
171
  # +-------------------------------+
172
172
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.2.1
173
173
  #
174
- # @param s [String]
174
+ # @param io [IO::Buffer]
175
175
  # @param cursor [Integer]
176
176
  # @param dynamic_table [DynamicTable]
177
177
  #
178
178
  # @return [Array]
179
179
  # @return [Integer]
180
- def self.decode_literal_field_incremental_indexing(s, cursor, dynamic_table)
181
- name, c = String.decode(s, cursor + 1)
182
- value, c = String.decode(s, c)
180
+ def self.decode_literal_field_incremental_indexing(io, cursor, dynamic_table)
181
+ name, c = String.decode(io, cursor + 1)
182
+ value, c = String.decode(io, c)
183
183
  dynamic_table.store(name, value)
184
184
 
185
185
  [[name, value], c]
@@ -195,14 +195,14 @@ module Biryani
195
195
  # +-------------------------------+
196
196
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.2.1
197
197
  #
198
- # @param s [String]
198
+ # @param io [IO::Buffer]
199
199
  # @param cursor [Integer]
200
200
  # @param dynamic_table [DynamicTable]
201
201
  #
202
202
  # @return [Array]
203
203
  # @return [Integer]
204
- def self.decode_literal_value_incremental_indexing(s, cursor, dynamic_table)
205
- index, c = Integer.decode(s, 6, cursor)
204
+ def self.decode_literal_value_incremental_indexing(io, cursor, dynamic_table)
205
+ index, c = Integer.decode(io, 6, cursor)
206
206
  raise Error::HPACKDecodeError if index.zero?
207
207
  raise Error::HPACKDecodeError if index > STATIC_TABLE_SIZE + dynamic_table.count_entries
208
208
 
@@ -211,7 +211,7 @@ module Biryani
211
211
  else
212
212
  dynamic_table[index - 1 - STATIC_TABLE_SIZE][0]
213
213
  end
214
- value, c = String.decode(s, c)
214
+ value, c = String.decode(io, c)
215
215
  dynamic_table.store(name, value)
216
216
 
217
217
  [[name, value], c]
@@ -223,16 +223,16 @@ module Biryani
223
223
  # +---+---------------------------+
224
224
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.3
225
225
  #
226
- # @param s [String]
226
+ # @param io [IO::Buffer]
227
227
  # @param cursor [Integer]
228
228
  # @param dynamic_table [DynamicTable]
229
229
  #
230
230
  # @return [nil]
231
231
  # @return [Integer]
232
- def self.decode_dynamic_table_size_update(s, cursor, dynamic_table)
233
- raise Error::HPACKDecodeError unless cursor.zero? || (s.getbyte(0) & 0b00100000).positive? && Integer.decode(s, 5, 0)[1] == cursor
232
+ def self.decode_dynamic_table_size_update(io, cursor, dynamic_table)
233
+ raise Error::HPACKDecodeError unless cursor.zero? || (io.get_value(:U8, 0) & 0b00100000).positive? && Integer.decode(io, 5, 0)[1] == cursor
234
234
 
235
- max_size, c = Integer.decode(s, 5, cursor)
235
+ max_size, c = Integer.decode(io, 5, cursor)
236
236
  raise Error::HPACKDecodeError if max_size > dynamic_table.limit
237
237
 
238
238
  dynamic_table.chomp!(max_size)
@@ -253,14 +253,14 @@ module Biryani
253
253
  # +-------------------------------+
254
254
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.2.3
255
255
  #
256
- # @param s [String]
256
+ # @param io [IO::Buffer]
257
257
  # @param cursor [Integer]
258
258
  #
259
259
  # @return [Array]
260
260
  # @return [Integer]
261
- def self.decode_literal_field_never_indexed(s, cursor)
262
- name, c = String.decode(s, cursor + 1)
263
- value, c = String.decode(s, c)
261
+ def self.decode_literal_field_never_indexed(io, cursor)
262
+ name, c = String.decode(io, cursor + 1)
263
+ value, c = String.decode(io, c)
264
264
 
265
265
  [[name, value], c]
266
266
  end
@@ -275,14 +275,14 @@ module Biryani
275
275
  # +-------------------------------+
276
276
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.2.3
277
277
  #
278
- # @param s [String]
278
+ # @param io [IO::Buffer]
279
279
  # @param cursor [Integer]
280
280
  # @param dynamic_table [DynamicTable]
281
281
  #
282
282
  # @return [Array]
283
283
  # @return [Integer]
284
- def self.decode_literal_value_never_indexed(s, cursor, dynamic_table)
285
- index, c = Integer.decode(s, 4, cursor)
284
+ def self.decode_literal_value_never_indexed(io, cursor, dynamic_table)
285
+ index, c = Integer.decode(io, 4, cursor)
286
286
  raise Error::HPACKDecodeError if index.zero?
287
287
  raise Error::HPACKDecodeError if index > STATIC_TABLE_SIZE + dynamic_table.count_entries
288
288
 
@@ -291,7 +291,7 @@ module Biryani
291
291
  else
292
292
  dynamic_table[index - 1 - STATIC_TABLE_SIZE][0]
293
293
  end
294
- value, c = String.decode(s, c)
294
+ value, c = String.decode(io, c)
295
295
 
296
296
  [[name, value], c]
297
297
  end
@@ -310,14 +310,14 @@ module Biryani
310
310
  # +-------------------------------+
311
311
  # https://datatracker.ietf.org/doc/html/rfc7541#section-6.2.2
312
312
  #
313
- # @param s [String]
313
+ # @param io [IO::Buffer]
314
314
  # @param cursor [Integer]
315
315
  #
316
316
  # @return [Array]
317
317
  # @return [Integer]
318
- def self.decode_literal_field_without_indexing(s, cursor)
319
- name, c = String.decode(s, cursor + 1)
320
- value, c = String.decode(s, c)
318
+ def self.decode_literal_field_without_indexing(io, cursor)
319
+ name, c = String.decode(io, cursor + 1)
320
+ value, c = String.decode(io, c)
321
321
 
322
322
  [[name, value], c]
323
323
  end
@@ -331,14 +331,14 @@ module Biryani
331
331
  # | Value String (Length octets) |
332
332
  # +-------------------------------+
333
333
  #
334
- # @param s [String]
334
+ # @param io [IO::Buffer]
335
335
  # @param cursor [Integer]
336
336
  # @param dynamic_table [DynamicTable]
337
337
  #
338
338
  # @return [Array]
339
339
  # @return [Integer]
340
- def self.decode_literal_value_without_indexing(s, cursor, dynamic_table)
341
- index, c = Integer.decode(s, 4, cursor)
340
+ def self.decode_literal_value_without_indexing(io, cursor, dynamic_table)
341
+ index, c = Integer.decode(io, 4, cursor)
342
342
  raise Error::HPACKDecodeError if index.zero?
343
343
  raise Error::HPACKDecodeError if index > STATIC_TABLE_SIZE + dynamic_table.count_entries
344
344
 
@@ -347,7 +347,7 @@ module Biryani
347
347
  else
348
348
  dynamic_table[index - 1 - STATIC_TABLE_SIZE][0]
349
349
  end
350
- value, c = String.decode(s, c)
350
+ value, c = String.decode(io, c)
351
351
 
352
352
  [[name, value], c]
353
353
  end
@@ -14,10 +14,11 @@ module Biryani
14
14
  #
15
15
  # @return [Array]
16
16
  def self.decode(s, dynamic_table)
17
+ io = IO::Buffer.for(s)
17
18
  cursor = 0
18
19
  fields = []
19
20
  while cursor < s.bytesize
20
- field, cursor = Field.decode(s, cursor, dynamic_table)
21
+ field, cursor = Field.decode(io, cursor, dynamic_table)
21
22
  fields << field unless field.nil?
22
23
  end
23
24
 
@@ -262,46 +262,315 @@ module Biryani
262
262
  11111111111111111111101110
263
263
  111111111111111111111111111111
264
264
  ].freeze
265
- DECODE_TABLE = ENCODE_TABLE.each_with_index.to_h.freeze
266
- EOS = '1' * 30
265
+ DECODE_TABLE = [
266
+ [0b1111111111000, 13],
267
+ [0b11111111111111111011000, 23],
268
+ [0b1111111111111111111111100010, 28],
269
+ [0b1111111111111111111111100011, 28],
270
+ [0b1111111111111111111111100100, 28],
271
+ [0b1111111111111111111111100101, 28],
272
+ [0b1111111111111111111111100110, 28],
273
+ [0b1111111111111111111111100111, 28],
274
+ [0b1111111111111111111111101000, 28],
275
+ [0b111111111111111111101010, 24],
276
+ [0b111111111111111111111111111100, 30],
277
+ [0b1111111111111111111111101001, 28],
278
+ [0b1111111111111111111111101010, 28],
279
+ [0b111111111111111111111111111101, 30],
280
+ [0b1111111111111111111111101011, 28],
281
+ [0b1111111111111111111111101100, 28],
282
+ [0b1111111111111111111111101101, 28],
283
+ [0b1111111111111111111111101110, 28],
284
+ [0b1111111111111111111111101111, 28],
285
+ [0b1111111111111111111111110000, 28],
286
+ [0b1111111111111111111111110001, 28],
287
+ [0b1111111111111111111111110010, 28],
288
+ [0b111111111111111111111111111110, 30],
289
+ [0b1111111111111111111111110011, 28],
290
+ [0b1111111111111111111111110100, 28],
291
+ [0b1111111111111111111111110101, 28],
292
+ [0b1111111111111111111111110110, 28],
293
+ [0b1111111111111111111111110111, 28],
294
+ [0b1111111111111111111111111000, 28],
295
+ [0b1111111111111111111111111001, 28],
296
+ [0b1111111111111111111111111010, 28],
297
+ [0b1111111111111111111111111011, 28],
298
+ [0b010100, 6],
299
+ [0b1111111000, 10],
300
+ [0b1111111001, 10],
301
+ [0b111111111010, 12],
302
+ [0b1111111111001, 13],
303
+ [0b010101, 6],
304
+ [0b11111000, 8],
305
+ [0b11111111010, 11],
306
+ [0b1111111010, 10],
307
+ [0b1111111011, 10],
308
+ [0b11111001, 8],
309
+ [0b11111111011, 11],
310
+ [0b11111010, 8],
311
+ [0b010110, 6],
312
+ [0b010111, 6],
313
+ [0b011000, 6],
314
+ [0b00000, 5],
315
+ [0b00001, 5],
316
+ [0b00010, 5],
317
+ [0b011001, 6],
318
+ [0b011010, 6],
319
+ [0b011011, 6],
320
+ [0b011100, 6],
321
+ [0b011101, 6],
322
+ [0b011110, 6],
323
+ [0b011111, 6],
324
+ [0b1011100, 7],
325
+ [0b11111011, 8],
326
+ [0b111111111111100, 15],
327
+ [0b100000, 6],
328
+ [0b111111111011, 12],
329
+ [0b1111111100, 10],
330
+ [0b1111111111010, 13],
331
+ [0b100001, 6],
332
+ [0b1011101, 7],
333
+ [0b1011110, 7],
334
+ [0b1011111, 7],
335
+ [0b1100000, 7],
336
+ [0b1100001, 7],
337
+ [0b1100010, 7],
338
+ [0b1100011, 7],
339
+ [0b1100100, 7],
340
+ [0b1100101, 7],
341
+ [0b1100110, 7],
342
+ [0b1100111, 7],
343
+ [0b1101000, 7],
344
+ [0b1101001, 7],
345
+ [0b1101010, 7],
346
+ [0b1101011, 7],
347
+ [0b1101100, 7],
348
+ [0b1101101, 7],
349
+ [0b1101110, 7],
350
+ [0b1101111, 7],
351
+ [0b1110000, 7],
352
+ [0b1110001, 7],
353
+ [0b1110010, 7],
354
+ [0b11111100, 8],
355
+ [0b1110011, 7],
356
+ [0b11111101, 8],
357
+ [0b1111111111011, 13],
358
+ [0b1111111111111110000, 19],
359
+ [0b1111111111100, 13],
360
+ [0b11111111111100, 14],
361
+ [0b100010, 6],
362
+ [0b111111111111101, 15],
363
+ [0b00011, 5],
364
+ [0b100011, 6],
365
+ [0b00100, 5],
366
+ [0b100100, 6],
367
+ [0b00101, 5],
368
+ [0b100101, 6],
369
+ [0b100110, 6],
370
+ [0b100111, 6],
371
+ [0b00110, 5],
372
+ [0b1110100, 7],
373
+ [0b1110101, 7],
374
+ [0b101000, 6],
375
+ [0b101001, 6],
376
+ [0b101010, 6],
377
+ [0b00111, 5],
378
+ [0b101011, 6],
379
+ [0b1110110, 7],
380
+ [0b101100, 6],
381
+ [0b01000, 5],
382
+ [0b01001, 5],
383
+ [0b101101, 6],
384
+ [0b1110111, 7],
385
+ [0b1111000, 7],
386
+ [0b1111001, 7],
387
+ [0b1111010, 7],
388
+ [0b1111011, 7],
389
+ [0b111111111111110, 15],
390
+ [0b11111111100, 11],
391
+ [0b11111111111101, 14],
392
+ [0b1111111111101, 13],
393
+ [0b1111111111111111111111111100, 28],
394
+ [0b11111111111111100110, 20],
395
+ [0b1111111111111111010010, 22],
396
+ [0b11111111111111100111, 20],
397
+ [0b11111111111111101000, 20],
398
+ [0b1111111111111111010011, 22],
399
+ [0b1111111111111111010100, 22],
400
+ [0b1111111111111111010101, 22],
401
+ [0b11111111111111111011001, 23],
402
+ [0b1111111111111111010110, 22],
403
+ [0b11111111111111111011010, 23],
404
+ [0b11111111111111111011011, 23],
405
+ [0b11111111111111111011100, 23],
406
+ [0b11111111111111111011101, 23],
407
+ [0b11111111111111111011110, 23],
408
+ [0b111111111111111111101011, 24],
409
+ [0b11111111111111111011111, 23],
410
+ [0b111111111111111111101100, 24],
411
+ [0b111111111111111111101101, 24],
412
+ [0b1111111111111111010111, 22],
413
+ [0b11111111111111111100000, 23],
414
+ [0b111111111111111111101110, 24],
415
+ [0b11111111111111111100001, 23],
416
+ [0b11111111111111111100010, 23],
417
+ [0b11111111111111111100011, 23],
418
+ [0b11111111111111111100100, 23],
419
+ [0b111111111111111011100, 21],
420
+ [0b1111111111111111011000, 22],
421
+ [0b11111111111111111100101, 23],
422
+ [0b1111111111111111011001, 22],
423
+ [0b11111111111111111100110, 23],
424
+ [0b11111111111111111100111, 23],
425
+ [0b111111111111111111101111, 24],
426
+ [0b1111111111111111011010, 22],
427
+ [0b111111111111111011101, 21],
428
+ [0b11111111111111101001, 20],
429
+ [0b1111111111111111011011, 22],
430
+ [0b1111111111111111011100, 22],
431
+ [0b11111111111111111101000, 23],
432
+ [0b11111111111111111101001, 23],
433
+ [0b111111111111111011110, 21],
434
+ [0b11111111111111111101010, 23],
435
+ [0b1111111111111111011101, 22],
436
+ [0b1111111111111111011110, 22],
437
+ [0b111111111111111111110000, 24],
438
+ [0b111111111111111011111, 21],
439
+ [0b1111111111111111011111, 22],
440
+ [0b11111111111111111101011, 23],
441
+ [0b11111111111111111101100, 23],
442
+ [0b111111111111111100000, 21],
443
+ [0b111111111111111100001, 21],
444
+ [0b1111111111111111100000, 22],
445
+ [0b111111111111111100010, 21],
446
+ [0b11111111111111111101101, 23],
447
+ [0b1111111111111111100001, 22],
448
+ [0b11111111111111111101110, 23],
449
+ [0b11111111111111111101111, 23],
450
+ [0b11111111111111101010, 20],
451
+ [0b1111111111111111100010, 22],
452
+ [0b1111111111111111100011, 22],
453
+ [0b1111111111111111100100, 22],
454
+ [0b11111111111111111110000, 23],
455
+ [0b1111111111111111100101, 22],
456
+ [0b1111111111111111100110, 22],
457
+ [0b11111111111111111110001, 23],
458
+ [0b11111111111111111111100000, 26],
459
+ [0b11111111111111111111100001, 26],
460
+ [0b11111111111111101011, 20],
461
+ [0b1111111111111110001, 19],
462
+ [0b1111111111111111100111, 22],
463
+ [0b11111111111111111110010, 23],
464
+ [0b1111111111111111101000, 22],
465
+ [0b1111111111111111111101100, 25],
466
+ [0b11111111111111111111100010, 26],
467
+ [0b11111111111111111111100011, 26],
468
+ [0b11111111111111111111100100, 26],
469
+ [0b111111111111111111111011110, 27],
470
+ [0b111111111111111111111011111, 27],
471
+ [0b11111111111111111111100101, 26],
472
+ [0b111111111111111111110001, 24],
473
+ [0b1111111111111111111101101, 25],
474
+ [0b1111111111111110010, 19],
475
+ [0b111111111111111100011, 21],
476
+ [0b11111111111111111111100110, 26],
477
+ [0b111111111111111111111100000, 27],
478
+ [0b111111111111111111111100001, 27],
479
+ [0b11111111111111111111100111, 26],
480
+ [0b111111111111111111111100010, 27],
481
+ [0b111111111111111111110010, 24],
482
+ [0b111111111111111100100, 21],
483
+ [0b111111111111111100101, 21],
484
+ [0b11111111111111111111101000, 26],
485
+ [0b11111111111111111111101001, 26],
486
+ [0b1111111111111111111111111101, 28],
487
+ [0b111111111111111111111100011, 27],
488
+ [0b111111111111111111111100100, 27],
489
+ [0b111111111111111111111100101, 27],
490
+ [0b11111111111111101100, 20],
491
+ [0b111111111111111111110011, 24],
492
+ [0b11111111111111101101, 20],
493
+ [0b111111111111111100110, 21],
494
+ [0b1111111111111111101001, 22],
495
+ [0b111111111111111100111, 21],
496
+ [0b111111111111111101000, 21],
497
+ [0b11111111111111111110011, 23],
498
+ [0b1111111111111111101010, 22],
499
+ [0b1111111111111111101011, 22],
500
+ [0b1111111111111111111101110, 25],
501
+ [0b1111111111111111111101111, 25],
502
+ [0b111111111111111111110100, 24],
503
+ [0b111111111111111111110101, 24],
504
+ [0b11111111111111111111101010, 26],
505
+ [0b11111111111111111110100, 23],
506
+ [0b11111111111111111111101011, 26],
507
+ [0b111111111111111111111100110, 27],
508
+ [0b11111111111111111111101100, 26],
509
+ [0b11111111111111111111101101, 26],
510
+ [0b111111111111111111111100111, 27],
511
+ [0b111111111111111111111101000, 27],
512
+ [0b111111111111111111111101001, 27],
513
+ [0b111111111111111111111101010, 27],
514
+ [0b111111111111111111111101011, 27],
515
+ [0b1111111111111111111111111110, 28],
516
+ [0b111111111111111111111101100, 27],
517
+ [0b111111111111111111111101101, 27],
518
+ [0b111111111111111111111101110, 27],
519
+ [0b111111111111111111111101111, 27],
520
+ [0b111111111111111111111110000, 27],
521
+ [0b11111111111111111111101110, 26],
522
+ [0b111111111111111111111111111111, 30]
523
+ ].each_with_index.to_h.freeze
524
+ EOS = (1 << 30) - 1
267
525
 
268
526
  private_constant :ENCODE_TABLE, :DECODE_TABLE, :EOS
269
527
  Ractor.make_shareable(ENCODE_TABLE)
270
528
  Ractor.make_shareable(DECODE_TABLE)
271
- Ractor.make_shareable(EOS)
272
529
 
273
530
  # @param s [String]
274
531
  #
275
532
  # @return [String]
276
533
  def self.encode(s)
277
- bits = s.bytes.map { |sym| ENCODE_TABLE[sym] }.join
278
- bits << '1' * ((8 - bits.bytesize) % 8)
279
- [bits].pack('B*')
280
- end
534
+ bits = s.bytes.map { |sym| ENCODE_TABLE[sym] }
281
535
 
282
- Accumulator = Struct.new(:s, :buf)
536
+ acc = IO::Buffer.new(bits.sum(&:bytesize))
537
+ offset = 0
538
+ bits.each do |s|
539
+ acc.set_string(s, offset)
540
+ offset += s.bytesize
541
+ end
283
542
 
284
- # @param encoded [String]
543
+ [acc.get_string.ljust((acc.size + 7) & ~7, '1')].pack('B*')
544
+ end
545
+
546
+ # @param io [IO::Buffer]
547
+ # @param cursor [Integer]
548
+ # @param length [Integer]
285
549
  #
286
550
  # @raise [HuffmanDecodeError]
287
551
  #
288
552
  # @return [String]
289
- def self.decode(encoded)
290
- bits = encoded.unpack1('B*').chars
291
- res = bits.each_with_object(Accumulator.new(s: '', buf: '')) do |bit, acc|
292
- acc.buf << bit
293
- raise Error::HuffmanDecodeError if acc.buf == EOS
553
+ def self.decode(io, cursor, length)
554
+ res = ''.b
555
+ bits = 0
556
+ bits_len = 0
557
+ bytes = io.get_values([:U8] * length, cursor)
558
+ (length * 8).times do |i|
559
+ bits_len += 1
560
+ bits += 1 if (bytes[i / 8] & (1 << 7 - (i % 8))).positive?
561
+ raise Error::HuffmanDecodeError if bits == EOS
294
562
 
295
- if (chr = DECODE_TABLE[acc.buf])
296
- acc.s << chr
297
- acc.buf.clear
563
+ if (chr = DECODE_TABLE[[bits, bits_len]])
564
+ res << chr
565
+ bits = 0
566
+ bits_len = 0
567
+ else
568
+ bits <<= 1
298
569
  end
299
-
300
- acc
301
570
  end
302
- raise Error::HuffmanDecodeError if res.buf.chars.any? { |c| c != '1' } || res.buf.bytesize >= 8
571
+ raise Error::HuffmanDecodeError if bits_len.positive? && bits != (2 << bits_len) - 2 || bits_len >= 8
303
572
 
304
- res.s
573
+ res
305
574
  end
306
575
  end
307
576
  # rubocop: enable Metrics/ModuleLength
@@ -40,26 +40,30 @@ module Biryani
40
40
  bytes.pack('C*')
41
41
  end
42
42
 
43
- # @param s [String]
43
+ # @param io [IO::Buffer]
44
44
  # @param cursor [Integer]
45
45
  # @param n [Integer]
46
46
  #
47
47
  # @return [Integer]
48
48
  # @return [Integer]
49
- def self.decode(s, n, cursor)
49
+ def self.decode(io, n, cursor)
50
50
  limit = (1 << n) - 1
51
- h = s.getbyte(cursor)
51
+ h = io.get_value(:U8, cursor)
52
52
  return [h & limit, cursor + 1] if (h & limit) != limit
53
53
 
54
54
  res = limit
55
- s[cursor + 1..].each_byte.each_with_index.each do |byte, i|
55
+ c = cursor + 1
56
+ i = 0
57
+ loop do
58
+ byte = io.get_value(:U8, c + i)
56
59
  res += (byte & 127) * 2**(i * 7)
57
- cursor += 1
58
60
 
59
61
  break if (byte & 128).zero?
62
+
63
+ i += 1
60
64
  end
61
65
 
62
- [res, cursor + 1]
66
+ [res, c + i + 1]
63
67
  end
64
68
  end
65
69
  end
@@ -23,17 +23,17 @@ module Biryani
23
23
  Integer.encode(res.bytesize, 7, mask) + res
24
24
  end
25
25
 
26
- # @param s [String]
26
+ # @param io [IO::Buffer]
27
27
  # @param cursor [Integer]
28
28
  #
29
29
  # @return [String]
30
30
  # @return [Integer]
31
- def self.decode(s, cursor)
32
- h = (s.getbyte(cursor) & 0b10000000).positive?
33
- len, c = Integer.decode(s, 7, cursor)
34
- return [Huffman.decode(s[c...c + len]), c + len] if h
31
+ def self.decode(io, cursor)
32
+ h = (io.get_value(:U8, cursor) & 0b10000000).positive?
33
+ len, c = Integer.decode(io, 7, cursor)
34
+ return [Huffman.decode(io, c, len), c + len] if h
35
35
 
36
- [s[c...c + len], c + len]
36
+ [io.get_string(c, len), c + len]
37
37
  end
38
38
  end
39
39
  end
@@ -39,7 +39,7 @@ module Biryani
39
39
  # TODO: trailers
40
40
 
41
41
  if name == 'cookie' && @h.key?('cookie')
42
- @h[name] += "; #{value}"
42
+ @h[name] << "; #{value}"
43
43
  else
44
44
  @h[name] = value
45
45
  end
@@ -65,7 +65,8 @@ module Biryani
65
65
  #
66
66
  # @return [HTTPRequest]
67
67
  def build(s)
68
- self.class.http_request(@h, s)
68
+ h = @h.transform_values(&:dup)
69
+ self.class.http_request(h, s)
69
70
  end
70
71
 
71
72
  # @param fields [Hash<String, String>]