biryani 0.0.4 → 0.0.5

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/biryani/connection.rb +2 -2
  3. data/lib/biryani/data_buffer.rb +1 -1
  4. data/lib/biryani/frame/continuation.rb +4 -7
  5. data/lib/biryani/frame/data.rb +9 -12
  6. data/lib/biryani/frame/goaway.rb +6 -6
  7. data/lib/biryani/frame/headers.rb +25 -18
  8. data/lib/biryani/frame/ping.rb +5 -7
  9. data/lib/biryani/frame/priority.rb +6 -5
  10. data/lib/biryani/frame/push_promise.rb +14 -15
  11. data/lib/biryani/frame/rst_stream.rb +5 -5
  12. data/lib/biryani/frame/settings.rb +5 -5
  13. data/lib/biryani/frame/unknown.rb +0 -12
  14. data/lib/biryani/frame/window_update.rb +5 -5
  15. data/lib/biryani/frame.rb +5 -4
  16. data/lib/biryani/hpack/field.rb +42 -42
  17. data/lib/biryani/hpack/fields.rb +2 -1
  18. data/lib/biryani/hpack/huffman.rb +290 -21
  19. data/lib/biryani/hpack/integer.rb +10 -6
  20. data/lib/biryani/hpack/string.rb +6 -6
  21. data/lib/biryani/http_request.rb +1 -1
  22. data/lib/biryani/state.rb +2 -0
  23. data/lib/biryani/streams_context.rb +2 -6
  24. data/lib/biryani/version.rb +1 -1
  25. data/lib/biryani.rb +0 -1
  26. data/spec/connection/handle_data_spec.rb +2 -2
  27. data/spec/connection/handle_headers_spec.rb +1 -1
  28. data/spec/frame/continuation_spec.rb +2 -2
  29. data/spec/frame/data_spec.rb +1 -1
  30. data/spec/frame/goaway_spec.rb +1 -1
  31. data/spec/frame/headers_spec.rb +2 -2
  32. data/spec/frame/ping_spec.rb +1 -1
  33. data/spec/frame/priority_spec.rb +1 -1
  34. data/spec/frame/push_promise_spec.rb +1 -1
  35. data/spec/frame/rst_stream_spec.rb +1 -1
  36. data/spec/frame/settings_spec.rb +1 -1
  37. data/spec/frame/window_update_spec.rb +1 -1
  38. data/spec/hpack/field_spec.rb +10 -9
  39. data/spec/hpack/huffman_spec.rb +4 -4
  40. data/spec/hpack/integer_spec.rb +5 -5
  41. data/spec/hpack/string_spec.rb +2 -2
  42. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ccf97951b31acac8912b7286f6377dedae6e404aab32e8c0eb619ef223d7349
4
- data.tar.gz: 94563414ccb1a066ca85a9dc553541ea3772bc398e3d80a970e9f26b68d7f31b
3
+ metadata.gz: be854e5993fe96b137d1d4db219551155b3103b213a97058c64f756ad2971ba3
4
+ data.tar.gz: 2256eea9aee63134060398084d904651a1d0fde583564dc211e13ddb5b134e78
5
5
  SHA512:
6
- metadata.gz: c5e5cf9d6fc22cfe738483638b414bf0a82575030d9b4c0e400d5beab27da13b5b826390b563b972eca7fca8f5cbc44e8f693bf44abeab6eb360d9b09a91a3bd
7
- data.tar.gz: f961a868e92ba46f300784f12568b0cd47cebc1cda484008b870480c22f63d5de66bf3d76fcddcbc96e192179c2d493a0fb1fbfc0b6cc189ba2852ee99af009a
6
+ metadata.gz: 0abdc8a1ce8d2b0a4d149830e14c0435e08b884950c026ab7d67af0726aeff53eff9d8dc06da83c4819badd967d6e1f0f2fb0b5833432c508ec3874c117ceaf8
7
+ data.tar.gz: e11b1fdde4debd807fb8ddfd7c9b7eaec3be56510be8758e1c42b815589418c5711848ae2ded681247578509aad0f20951558ee54a0b0d6d92825f7704bb3256
@@ -349,7 +349,7 @@ module Biryani
349
349
 
350
350
  ctx.content << data
351
351
  if ctx.half_closed_remote?
352
- obj = http_request(ctx.fragment.string, ctx.content.string, decoder)
352
+ obj = http_request(ctx.fragment, ctx.content, decoder)
353
353
  return obj if Biryani.err?(obj)
354
354
 
355
355
  ctx.stream.rx << obj
@@ -370,7 +370,7 @@ module Biryani
370
370
  def self.handle_headers(headers, ctx, decoder)
371
371
  ctx.fragment << headers.fragment
372
372
  if ctx.half_closed_remote?
373
- obj = http_request(ctx.fragment.string, ctx.content.string, decoder)
373
+ obj = http_request(ctx.fragment, ctx.content, decoder)
374
374
  return [obj] if Biryani.err?(obj)
375
375
 
376
376
  ctx.stream.rx << obj
@@ -8,7 +8,7 @@ module Biryani
8
8
  # @param data [String]
9
9
  def store(stream_id, data)
10
10
  @buffer[stream_id] = '' unless @buffer.key?(stream_id)
11
- @buffer[stream_id] += data
11
+ @buffer[stream_id] << data
12
12
  end
13
13
 
14
14
  # @param send_window [Window]
@@ -32,16 +32,13 @@ module Biryani
32
32
  end
33
33
 
34
34
  # @param s [String]
35
+ # @param flags [Integer]
36
+ # @param stream_id [Integer]
35
37
  #
36
38
  # @return [Continuation]
37
- def self.read(s)
38
- payload_length, _, flags, stream_id = Frame.read_header(s)
39
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
40
-
39
+ def self.read(s, flags, stream_id)
41
40
  end_headers = Frame.read_end_headers(flags)
42
- fragment = s[9..]
43
-
44
- Continuation.new(end_headers, stream_id, fragment)
41
+ Continuation.new(end_headers, stream_id, s)
45
42
  end
46
43
  end
47
44
  end
@@ -41,26 +41,23 @@ module Biryani
41
41
  end
42
42
 
43
43
  # @param s [String]
44
+ # @param flags [Integer]
45
+ # @param stream_id [Integer]
44
46
  #
45
47
  # @return [Data]
46
- def self.read(s)
47
- payload_length, _, flags, stream_id = Frame.read_header(s)
48
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
49
-
48
+ def self.read(s, flags, stream_id)
50
49
  padded = Frame.read_padded(flags)
51
50
  end_stream = Frame.read_end_stream(flags)
52
51
 
53
52
  if padded
54
- pad_length = s[9].unpack1('C')
55
- data_length = payload_length - pad_length - 1
56
- data = s[10...10 + data_length]
57
- padding = s[10 + data_length..]
58
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= payload_length
53
+ io = IO::Buffer.for(s)
54
+ pad_length = io.get_value(:U8, 0)
55
+ data_length = s.bytesize - pad_length - 1
56
+ data = io.get_string(1, data_length)
57
+ padding = io.get_string(1 + data_length)
59
58
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
60
59
  else
61
- data = s[9..]
62
- padding = nil
63
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if data.bytesize != payload_length
60
+ data = s
64
61
  end
65
62
 
66
63
  Data.new(end_stream, stream_id, data, padding)
@@ -28,14 +28,14 @@ module Biryani
28
28
  end
29
29
 
30
30
  # @param s [String]
31
+ # @param _flags [Integer]
32
+ # @param stream_id [Integer]
31
33
  #
32
34
  # @return [Goaway]
33
- def self.read(s)
34
- payload_length, _, _, stream_id = Frame.read_header(s)
35
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
36
-
37
- last_stream_id, error_code = s[9..16].unpack('NN')
38
- debug = s[17..]
35
+ def self.read(s, _flags, stream_id)
36
+ io = IO::Buffer.for(s)
37
+ last_stream_id, error_code = io.get_values(%i[U32 U32], 0)
38
+ debug = io.get_string(8)
39
39
 
40
40
  Goaway.new(stream_id, last_stream_id, error_code, debug)
41
41
  end
@@ -58,50 +58,57 @@ module Biryani
58
58
  end
59
59
 
60
60
  # @param s [String]
61
+ # @param flags [Integer]
62
+ # @param stream_id [Integer]
61
63
  #
62
64
  # @return [Headers]
63
65
  # rubocop: disable Metrics/AbcSize
64
66
  # rubocop: disable Metrics/CyclomaticComplexity
67
+ # rubocop: disable Metrics/MethodLength
65
68
  # rubocop: disable Metrics/PerceivedComplexity
66
- def self.read(s)
67
- payload_length, _, flags, stream_id = Frame.read_header(s)
68
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
69
-
69
+ def self.read(s, flags, stream_id)
70
70
  priority = Frame.read_priority(flags)
71
71
  padded = Frame.read_padded(flags)
72
72
  end_headers = Frame.read_end_headers(flags)
73
73
  end_stream = Frame.read_end_stream(flags)
74
74
 
75
75
  if priority && padded
76
- pad_length, stream_dependency, weight = s[9..14].unpack('CNC')
77
- fragment_length = payload_length - pad_length - 6
76
+ io = IO::Buffer.for(s)
77
+ pad_length, stream_dependency, weight = io.get_values(%i[U8 U32 U8], 0)
78
+ fragment_length = s.bytesize - pad_length - 6
78
79
  # exclusive = (stream_dependency / 2**31).positive?
79
80
  stream_dependency %= 2**31
80
- fragment = s[15...15 + fragment_length]
81
- padding = s[15 + fragment_length..]
81
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
82
+
83
+ fragment = io.get_string(6, fragment_length)
84
+ padding = io.get_string(6 + fragment_length)
82
85
  return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if padding.bytesize != pad_length
83
86
  elsif priority
84
- stream_dependency, weight = s[9..13].unpack('NC')
87
+ io = IO::Buffer.for(s)
88
+ stream_dependency, weight = io.get_values(%i[U32 U8], 0)
85
89
  # exclusive = (stream_dependency / 2**31).positive?
86
90
  stream_dependency %= 2**31
87
- fragment = s[14..]
88
- return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if fragment.bytesize + 5 != payload_length
91
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
92
+
93
+ fragment = io.get_string(5)
94
+ return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if fragment.bytesize + 5 != s.bytesize
89
95
  elsif padded
90
- pad_length = s[9].unpack1('C')
91
- fragment_length = payload_length - pad_length - 1
92
- fragment = s[10...10 + fragment_length]
93
- padding = s[10 + fragment_length..]
94
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= payload_length
96
+ io = IO::Buffer.for(s)
97
+ pad_length = io.get_value(:U8, 0)
98
+ fragment_length = s.bytesize - pad_length - 1
99
+ fragment = io.get_string(1, fragment_length)
100
+ padding = io.get_string(1 + fragment_length)
101
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= s.bytesize
95
102
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
96
103
  else
97
- fragment = s[9..]
98
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if fragment.bytesize != payload_length
104
+ fragment = s
99
105
  end
100
106
 
101
107
  Headers.new(end_headers, end_stream, stream_id, stream_dependency, weight, fragment, padding)
102
108
  end
103
109
  # rubocop: enable Metrics/AbcSize
104
110
  # rubocop: enable Metrics/CyclomaticComplexity
111
+ # rubocop: enable Metrics/MethodLength
105
112
  # rubocop: enable Metrics/PerceivedComplexity
106
113
  end
107
114
  end
@@ -32,17 +32,15 @@ module Biryani
32
32
  end
33
33
 
34
34
  # @param s [String]
35
+ # @param flags [Integer]
36
+ # @param stream_id [Integer]
35
37
  #
36
38
  # @return [Ping]
37
- def self.read(s)
38
- payload_length, _, flags, stream_id = Frame.read_header(s)
39
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
40
- return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PING payload length MUST be 8') if s[9..].bytesize != 8
39
+ def self.read(s, flags, stream_id)
40
+ return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PING payload length MUST be 8') if s.bytesize != 8
41
41
 
42
42
  ack = Frame.read_ack(flags)
43
- opaque = s[9..]
44
-
45
- Ping.new(ack, stream_id, opaque)
43
+ Ping.new(ack, stream_id, s)
46
44
  end
47
45
  end
48
46
  end
@@ -27,15 +27,16 @@ module Biryani
27
27
  end
28
28
 
29
29
  # @param s [String]
30
+ # @param _flags [Integer]
31
+ # @param stream_id [Integer]
30
32
  #
31
33
  # @return [Priority]
32
- def self.read(s)
33
- payload_length, _, _, stream_id = Frame.read_header(s)
34
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
35
- return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PRIORITY payload length MUST be 5') if payload_length != 5
34
+ def self.read(s, _flags, stream_id)
35
+ return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PRIORITY payload length MUST be 5') if s.bytesize != 5
36
36
 
37
- stream_dependency, weight = s[9..13].unpack('NC')
37
+ stream_dependency, weight = s.unpack('NC')
38
38
  stream_dependency %= 2**31
39
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
39
40
 
40
41
  Priority.new(stream_id, stream_dependency, weight)
41
42
  end
@@ -43,33 +43,32 @@ module Biryani
43
43
  end
44
44
 
45
45
  # @param s [String]
46
+ # @param flags [Integer]
47
+ # @param stream_id [Integer]
46
48
  #
47
49
  # @return [PushPromise]
48
- # rubocop: disable Metrics/AbcSize
49
- def self.read(s)
50
- payload_length, _, flags, stream_id = Frame.read_header(s)
51
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
52
-
50
+ def self.read(s, flags, stream_id)
53
51
  padded = Frame.read_padded(flags)
54
52
  end_headers = Frame.read_end_headers(flags)
55
53
 
54
+ io = IO::Buffer.for(s)
56
55
  if padded
57
- pad_length = s[9].unpack1('C')
58
- promised_stream_id = s[10..13].unpack1('N')
59
- fragment_length = payload_length - pad_length - 5
60
- fragment = s[14...14 + fragment_length]
61
- padding = s[14 + fragment_length..]
62
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= payload_length
56
+ pad_length, promised_stream_id = io.get_values(%i[U8 U32], 0)
57
+ promised_stream_id %= 2**31 # Promised Stream ID (31)
58
+ fragment_length = s.bytesize - pad_length - 5
59
+ fragment = io.get_string(5, fragment_length)
60
+ padding = io.get_string(5 + fragment_length)
61
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= s.bytesize
63
62
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
64
63
  else
65
- promised_stream_id = s[9..12].unpack1('N')
66
- fragment = s[13..]
67
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if fragment.bytesize + 4 != payload_length
64
+ promised_stream_id = io.get_value(:U32, 0)
65
+ promised_stream_id %= 2**31 # Promised Stream ID (31)
66
+ fragment = io.get_string(4)
67
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if fragment.bytesize + 4 != s.bytesize
68
68
  end
69
69
 
70
70
  PushPromise.new(end_headers, stream_id, promised_stream_id, fragment, padding)
71
71
  end
72
- # rubocop: enable Metrics/AbcSize
73
72
  end
74
73
  end
75
74
  end
@@ -25,14 +25,14 @@ module Biryani
25
25
  end
26
26
 
27
27
  # @param s [String]
28
+ # @param _flags [Integer]
29
+ # @param stream_id [Integer]
28
30
  #
29
31
  # @return [RstStream]
30
- def self.read(s)
31
- payload_length, _, _, stream_id = Frame.read_header(s)
32
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
33
- return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'RST_STREAM payload length MUST be 4') if s[9..].bytesize != 4
32
+ def self.read(s, _flags, stream_id)
33
+ return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'RST_STREAM payload length MUST be 4') if s.bytesize != 4
34
34
 
35
- error_code = s[9..].unpack1('N')
35
+ error_code = s.unpack1('N')
36
36
  RstStream.new(stream_id, error_code)
37
37
  end
38
38
  end
@@ -33,18 +33,18 @@ module Biryani
33
33
  end
34
34
 
35
35
  # @param s [String]
36
+ # @param flags [Integer]
37
+ # @param stream_id [Integer]
36
38
  #
37
39
  # @return [Settings]
38
40
  # rubocop: disable Metrics/AbcSize
39
41
  # rubocop: disable Metrics/CyclomaticComplexity
40
42
  # rubocop: disable Metrics/PerceivedComplexity
41
- def self.read(s)
42
- payload_length, _, flags, stream_id = Frame.read_header(s)
43
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
44
- return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'SETTINGS payload length MUST be a multiple of 6') if payload_length % 6 != 0
43
+ def self.read(s, flags, stream_id)
44
+ return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'SETTINGS payload length MUST be a multiple of 6') if s.bytesize % 6 != 0
45
45
 
46
46
  ack = Frame.read_ack(flags)
47
- setting = s[9..].unpack('nN' * (payload_length / 6)).each_slice(2).to_h
47
+ setting = s.unpack('nN' * (s.bytesize / 6)).each_slice(2).to_h
48
48
  return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'SETTINGS MUST NOT have setting with ack') \
49
49
  if ack && setting.any?
50
50
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid SETTINGS_ENABLE_PUSH') \
@@ -25,18 +25,6 @@ module Biryani
25
25
 
26
26
  Frame.to_binary_s_header(payload_length, @f_type, flags, @stream_id) + @payload
27
27
  end
28
-
29
- # @param s [String]
30
- #
31
- # @return [Unknown]
32
- def self.read(s)
33
- payload_length, f_type, flags, stream_id = Frame.read_header(s)
34
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
35
-
36
- payload = s[9..]
37
-
38
- Unknown.new(f_type, flags, stream_id, payload)
39
- end
40
28
  end
41
29
  end
42
30
  end
@@ -25,14 +25,14 @@ module Biryani
25
25
  end
26
26
 
27
27
  # @param s [String]
28
+ # @param _flags [Integer]
29
+ # @param stream_id [Integer]
28
30
  #
29
31
  # @return [WindowUpdate]
30
- def self.read(s)
31
- payload_length, _, _, stream_id = Frame.read_header(s)
32
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
33
- return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'WINDOW_UPDATE payload length MUST be 4') if s[9..].bytesize != 4
32
+ def self.read(s, _flags, stream_id)
33
+ return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'WINDOW_UPDATE payload length MUST be 4') if s.bytesize != 4
34
34
 
35
- window_size_increment = s[9..].unpack1('N')
35
+ window_size_increment = s.unpack1('N')
36
36
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'WINDOW_UPDATE invalid window size increment 0') if window_size_increment.zero?
37
37
  return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'WINDOW_UPDATE invalid window size increment greater than 2^31-1') if window_size_increment > 2**31 - 1
38
38
 
data/lib/biryani/frame.rb CHANGED
@@ -37,11 +37,11 @@ module Biryani
37
37
  # @return [Integer]
38
38
  # @return [Integer]
39
39
  def self.read_header(s)
40
- b0, b1, b2, f_type, uint8 = s[0..4].bytes
40
+ b0, b1, b2, f_type, flags, stream_id = s.unpack('CCCCCN')
41
41
  payload_length = (b0 << 16) | (b1 << 8) | b2
42
- stream_id = s[5..8].unpack1('N') % 2**31 # Stream Identifier (31)
42
+ stream_id %= 2**31 # Stream Identifier (31)
43
43
 
44
- [payload_length, f_type, uint8, stream_id]
44
+ [payload_length, f_type, flags, stream_id]
45
45
  end
46
46
 
47
47
  # @param uint8 [Integer]
@@ -136,9 +136,10 @@ module Biryani
136
136
 
137
137
  payload_length, f_type, flags, stream_id = read_header(s)
138
138
  payload = io.read(payload_length)
139
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if payload.bytesize != payload_length
139
140
  return Frame::Unknown.new(f_type, flags, stream_id, payload) unless FRAME_MAP.key?(f_type)
140
141
 
141
- FRAME_MAP[f_type].read(s + payload)
142
+ FRAME_MAP[f_type].read(payload, flags, stream_id)
142
143
  rescue StandardError
143
144
  ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame')
144
145
  end
@@ -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