biryani 0.0.11 → 0.0.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e46693c04c711c4911f7748510f64e66ca84409574a917975b23775c70630543
4
- data.tar.gz: bceed32a74b6e5a792231ba721d9391bf6f15f2440593de4c36e60878b422c48
3
+ metadata.gz: 96b9dc1294ce1840c02e6cf1700b1ebae51409b586dffd25b7df814acdd65034
4
+ data.tar.gz: 66d417773c04e090101661650748415566e9f4ca0f91ff95720b2cb7f8668f7b
5
5
  SHA512:
6
- metadata.gz: 188361e32796146bb331ffa9745b5231566646670d9da983ec6c51d19db53176242d0af5bdded03410cb761ff6546203ff44c49dbcc2574386b4a2e66fbe1add
7
- data.tar.gz: 51ec657491b308328a3c9addf58e7a1fb0e78cb4aad1f41a40d7f8a831c5cccbd642c70f09aa37b97f520d1a48028835cbf1f53c28ea9cf91c279c1fb4af3669
6
+ metadata.gz: '079d3ab7c12394a9fd340757c5f076cbe937bd1f1a1e60cd24e98f7535526e320189a35f6baa2a5dab3c621b467fec52566e2cfec43a9b994d9f4e5c9dd818cf'
7
+ data.tar.gz: 7eed1c453cef71438ee5e625bd7b954c38c8cb4aa417db79c00894bf00e4f1f500a759a16d8c6ea58d3c90f273b5bcbbbefbe3a2de1059367b087095921aaec8
@@ -12,20 +12,23 @@ module Biryani
12
12
  class Connection
13
13
  CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
14
14
  CONNECTION_PREFACE_LENGTH = CONNECTION_PREFACE.length
15
+ DEFAULT_HEADER_TABLE_SIZE = 4_096
16
+ INITIAL_CONNECTION_WINDOW_SIZE = 65_535
15
17
 
16
- private_constant :CONNECTION_PREFACE, :CONNECTION_PREFACE_LENGTH
18
+ private_constant :CONNECTION_PREFACE, :CONNECTION_PREFACE_LENGTH, :DEFAULT_HEADER_TABLE_SIZE, :INITIAL_CONNECTION_WINDOW_SIZE
17
19
  Ractor.make_shareable(CONNECTION_PREFACE)
18
20
  Ractor.make_shareable(CONNECTION_PREFACE_LENGTH)
19
21
 
20
22
  # proc [Proc]
21
23
  def initialize(proc)
22
- @sock = nil # Ractor::Port
24
+ @port = Ractor::Port.new
25
+ @recv_done = false
23
26
  @proc = proc
24
- @streams_ctx = StreamsContext.new(proc)
25
- @encoder = HPACK::Encoder.new(4_096)
26
- @decoder = HPACK::Decoder.new(4_096)
27
- @send_window = Window.new(65_535)
28
- @recv_window = Window.new(65_535)
27
+ @streams_ctx = StreamsContext.new(proc, @port)
28
+ @encoder = HPACK::Encoder.new(DEFAULT_HEADER_TABLE_SIZE)
29
+ @decoder = HPACK::Decoder.new(DEFAULT_HEADER_TABLE_SIZE)
30
+ @send_window = Window.new(INITIAL_CONNECTION_WINDOW_SIZE)
31
+ @recv_window = Window.new(INITIAL_CONNECTION_WINDOW_SIZE)
29
32
  @data_buffer = DataBuffer.new
30
33
  @settings = self.class.default_settings # Hash<Integer, Integer>
31
34
  @peer_settings = self.class.default_settings # Hash<Integer, Integer>
@@ -53,12 +56,15 @@ module Biryani
53
56
 
54
57
  # @param io [IO]
55
58
  def recv_loop(io)
56
- Ractor.new(io, @sock = Ractor::Port.new) do |io_, sock_|
59
+ Ractor.new(io, @port) do |io_, port_|
57
60
  loop do
58
61
  obj = Frame.read(io_)
59
- break if obj.nil?
62
+ if obj.nil?
63
+ port_.send(:eof, move: true)
64
+ break
65
+ end
60
66
 
61
- sock_.send(obj, move: true)
67
+ port_.send([:frame, obj], move: true)
62
68
  end
63
69
  end
64
70
  end
@@ -66,13 +72,14 @@ module Biryani
66
72
  # @param io [IO]
67
73
  def select_loop(io)
68
74
  loop do
69
- break if @sock.closed? && @streams_ctx.empty?
75
+ break if @recv_done && @streams_ctx.empty?
70
76
 
71
- port, obj = Ractor.select(@sock, @streams_ctx.tx)
72
- if port == @sock
77
+ case @port.receive
78
+ in :eof
79
+ @recv_done = true
80
+ in [:frame, obj]
73
81
  recv_dispatch(io, obj)
74
- else
75
- res, stream_id = obj
82
+ in [:response, res, stream_id]
76
83
  handle_response(io, res, stream_id)
77
84
  end
78
85
 
@@ -118,7 +125,7 @@ module Biryani
118
125
  # @return [Array<Object>, Array<ConnectionError>, Array<StreamError>] frames or errors
119
126
  def do_recv_dispatch(frame)
120
127
  receiving_continuation_stream_id = @streams_ctx.receiving_continuation_stream_id
121
- return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
128
+ return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', frame.f_type)} for stream identifier #{format('0x%02x', frame.stream_id)}")] \
122
129
  if !receiving_continuation_stream_id.nil? && frame.stream_id != receiving_continuation_stream_id
123
130
 
124
131
  if frame.stream_id.zero?
@@ -135,7 +142,7 @@ module Biryani
135
142
  def handle_connection_frame(frame)
136
143
  case frame.f_type
137
144
  when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
138
- [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
145
+ [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', frame.f_type)} for stream identifier 0x00")]
139
146
  when FrameType::SETTINGS
140
147
  obj = self.class.handle_settings(frame, @peer_settings, @decoder, @streams_ctx)
141
148
  return [] if obj.nil?
@@ -486,7 +493,7 @@ module Biryani
486
493
  def self.default_settings
487
494
  # https://datatracker.ietf.org/doc/html/rfc9113#section-6.5.2
488
495
  {
489
- SettingsID::SETTINGS_HEADER_TABLE_SIZE => 4_096,
496
+ SettingsID::SETTINGS_HEADER_TABLE_SIZE => DEFAULT_HEADER_TABLE_SIZE,
490
497
  SettingsID::SETTINGS_ENABLE_PUSH => 1,
491
498
  SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS => 0xffffffff,
492
499
  SettingsID::SETTINGS_INITIAL_WINDOW_SIZE => 65_535,
@@ -20,14 +20,14 @@ module Biryani
20
20
  datas = []
21
21
  @buffer.each do |stream_id, data|
22
22
  frames, remains = streams_ctx.sendable_datas(stream_id, data, send_window, max_frame_size)
23
- datas += frames
23
+ datas.concat(frames)
24
24
  if remains.empty?
25
25
  @buffer.delete(stream_id)
26
26
  else
27
27
  @buffer[stream_id] = remains
28
28
  end
29
29
 
30
- len = frames.map(&:length).sum
30
+ len = frames.sum(&:length)
31
31
  send_window.consume!(len)
32
32
  streams_ctx[stream_id].send_window.consume!(len)
33
33
  end
@@ -51,10 +51,7 @@ module Biryani
51
51
 
52
52
  if padded
53
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)
54
+ data, padding, pad_length = Frame.read_padded_payload(io, s, 1)
58
55
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
59
56
  else
60
57
  data = s
@@ -64,7 +64,6 @@ module Biryani
64
64
  # @return [Headers]
65
65
  # rubocop: disable Metrics/AbcSize
66
66
  # rubocop: disable Metrics/CyclomaticComplexity
67
- # rubocop: disable Metrics/MethodLength
68
67
  # rubocop: disable Metrics/PerceivedComplexity
69
68
  def self.read(s, flags, stream_id)
70
69
  priority = Frame.read_priority(flags)
@@ -74,14 +73,12 @@ module Biryani
74
73
 
75
74
  if priority && padded
76
75
  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
76
+ _pad_length, stream_dependency, weight = io.get_values(%i[U8 U32 U8], 0)
79
77
  # exclusive = (stream_dependency / 2**31).positive?
80
78
  stream_dependency %= 2**31
81
79
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
82
80
 
83
- fragment = io.get_string(6, fragment_length)
84
- padding = io.get_string(6 + fragment_length)
81
+ fragment, padding, pad_length = Frame.read_padded_payload(io, s, 6)
85
82
  return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if padding.bytesize != pad_length
86
83
  elsif priority
87
84
  io = IO::Buffer.for(s)
@@ -94,10 +91,7 @@ module Biryani
94
91
  return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if fragment.bytesize + 5 != s.bytesize
95
92
  elsif padded
96
93
  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)
94
+ fragment, padding, pad_length = Frame.read_padded_payload(io, s, 1)
101
95
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= s.bytesize
102
96
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
103
97
  else
@@ -108,7 +102,6 @@ module Biryani
108
102
  end
109
103
  # rubocop: enable Metrics/AbcSize
110
104
  # rubocop: enable Metrics/CyclomaticComplexity
111
- # rubocop: enable Metrics/MethodLength
112
105
  # rubocop: enable Metrics/PerceivedComplexity
113
106
  end
114
107
  end
@@ -53,11 +53,9 @@ module Biryani
53
53
 
54
54
  io = IO::Buffer.for(s)
55
55
  if padded
56
- pad_length, promised_stream_id = io.get_values(%i[U8 U32], 0)
56
+ _pad_length, promised_stream_id = io.get_values(%i[U8 U32], 0)
57
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)
58
+ fragment, padding, pad_length = Frame.read_padded_payload(io, s, 5)
61
59
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= s.bytesize
62
60
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
63
61
  else
data/lib/biryani/frame.rb CHANGED
@@ -98,6 +98,22 @@ module Biryani
98
98
  def self.to_binary_s_header(payload_length, f_type, flags, stream_id)
99
99
  [payload_length, f_type, flags, stream_id].pack('NCCN')[1..]
100
100
  end
101
+
102
+ # @param io [IO::Buffer]
103
+ # @param s [String]
104
+ # @param header_length [Integer] bytes preceding the payload, including the 1-byte Pad Length field
105
+ #
106
+ # @return [String] payload
107
+ # @return [String] padding
108
+ # @return [Integer] pad_length
109
+ def self.read_padded_payload(io, s, header_length)
110
+ pad_length = io.get_value(:U8, 0)
111
+ payload_length = s.bytesize - pad_length - header_length
112
+ payload = io.get_string(header_length, payload_length)
113
+ padding = io.get_string(header_length + payload_length)
114
+
115
+ [payload, padding, pad_length]
116
+ end
101
117
  end
102
118
  end
103
119
 
@@ -47,6 +47,26 @@ module Biryani
47
47
  @table[index]
48
48
  end
49
49
 
50
+ # @param index [Integer]
51
+ #
52
+ # @return [Array] [name, value]
53
+ def entry_at(index)
54
+ raise Error::HPACKDecodeError if index.zero? || index > STATIC_TABLE_SIZE + count_entries
55
+
56
+ if index <= STATIC_TABLE_SIZE
57
+ STATIC_TABLE[index - 1]
58
+ else
59
+ self[index - 1 - STATIC_TABLE_SIZE]
60
+ end
61
+ end
62
+
63
+ # @param index [Integer]
64
+ #
65
+ # @return [String]
66
+ def name_at(index)
67
+ entry_at(index)[0]
68
+ end
69
+
50
70
  # @param new_limit [Integer]
51
71
  def chomp!(new_limit)
52
72
  while @size > new_limit
@@ -145,15 +145,8 @@ module Biryani
145
145
  # @return [Integer]
146
146
  def self.decode_indexed(io, cursor, dynamic_table)
147
147
  index, c = Integer.decode(io, 7, cursor)
148
- raise Error::HPACKDecodeError if index.zero? || index > STATIC_TABLE_SIZE + dynamic_table.count_entries
149
148
 
150
- field = if index <= STATIC_TABLE_SIZE
151
- STATIC_TABLE[index - 1]
152
- else
153
- dynamic_table[index - 1 - STATIC_TABLE_SIZE]
154
- end
155
-
156
- [field, c]
149
+ [dynamic_table.entry_at(index), c]
157
150
  end
158
151
 
159
152
  # 0 1 2 3 4 5 6 7
@@ -202,13 +195,7 @@ module Biryani
202
195
  # @return [Integer]
203
196
  def self.decode_literal_value_incremental_indexing(io, cursor, dynamic_table)
204
197
  index, c = Integer.decode(io, 6, cursor)
205
- raise Error::HPACKDecodeError if index.zero? || index > STATIC_TABLE_SIZE + dynamic_table.count_entries
206
-
207
- name = if index <= STATIC_TABLE_SIZE
208
- STATIC_TABLE[index - 1][0]
209
- else
210
- dynamic_table[index - 1 - STATIC_TABLE_SIZE][0]
211
- end
198
+ name = dynamic_table.name_at(index)
212
199
  value, c = String.decode(io, c)
213
200
  dynamic_table.store(name, value)
214
201
 
@@ -281,13 +268,7 @@ module Biryani
281
268
  # @return [Integer]
282
269
  def self.decode_literal_value_never_indexed(io, cursor, dynamic_table)
283
270
  index, c = Integer.decode(io, 4, cursor)
284
- raise Error::HPACKDecodeError if index.zero? || index > STATIC_TABLE_SIZE + dynamic_table.count_entries
285
-
286
- name = if index <= STATIC_TABLE_SIZE
287
- STATIC_TABLE[index - 1][0]
288
- else
289
- dynamic_table[index - 1 - STATIC_TABLE_SIZE][0]
290
- end
271
+ name = dynamic_table.name_at(index)
291
272
  value, c = String.decode(io, c)
292
273
 
293
274
  [[name, value], c]
@@ -336,13 +317,7 @@ module Biryani
336
317
  # @return [Integer]
337
318
  def self.decode_literal_value_without_indexing(io, cursor, dynamic_table)
338
319
  index, c = Integer.decode(io, 4, cursor)
339
- raise Error::HPACKDecodeError if index.zero? || index > STATIC_TABLE_SIZE + dynamic_table.count_entries
340
-
341
- name = if index <= STATIC_TABLE_SIZE
342
- STATIC_TABLE[index - 1][0]
343
- else
344
- dynamic_table[index - 1 - STATIC_TABLE_SIZE][0]
345
- end
320
+ name = dynamic_table.name_at(index)
346
321
  value, c = String.decode(io, c)
347
322
 
348
323
  [[name, value], c]
@@ -531,16 +531,8 @@ module Biryani
531
531
  #
532
532
  # @return [String]
533
533
  def self.encode(s)
534
- bits = s.bytes.map { |sym| ENCODE_TABLE[sym] }
535
-
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
542
-
543
- [acc.get_string.ljust((acc.size + 7) & ~7, '1')].pack('B*')
534
+ bits = s.bytes.map { |sym| ENCODE_TABLE[sym] }.join
535
+ [bits.ljust((bits.bytesize + 7) & ~7, '1')].pack('B*')
544
536
  end
545
537
 
546
538
  # @param io [IO::Buffer]
@@ -554,7 +546,7 @@ module Biryani
554
546
  res = ''.b
555
547
  bits = 0
556
548
  bits_len = 0
557
- bytes = io.get_values([:U8] * length, cursor)
549
+ bytes = io.get_string(cursor, length).bytes
558
550
  (length * 8).times do |i|
559
551
  bits_len += 1
560
552
  bits += 1 if (bytes[i / 8] & (1 << 7 - (i % 8))).positive?
@@ -32,7 +32,7 @@ module Biryani
32
32
 
33
33
  bytes = [limit | mask]
34
34
  i -= limit
35
- while i > 128
35
+ while i >= 128
36
36
  bytes << i % 128 + 128
37
37
  i /= 128
38
38
  end
@@ -56,7 +56,7 @@ module Biryani
56
56
  i = 0
57
57
  loop do
58
58
  byte = io.get_value(:U8, c + i)
59
- res += (byte & 127) * 2**(i * 7)
59
+ res += (byte & 127) << (i * 7)
60
60
 
61
61
  break if (byte & 128).zero?
62
62
 
@@ -82,15 +82,15 @@ module Biryani
82
82
  # @return [Request, ConnectionError]
83
83
  def self.build(h, s)
84
84
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'missing pseudo-header fields') unless PSEUDO_HEADER_FIELDS.all? { |x| h.key?(x) }
85
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid content-length') if h.key?('content-length') && !s.empty? && s.length != h['content-length'].to_i
85
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid content-length') if h.key?('content-length') && !s.empty? && s.length != h['content-length'][0].to_i
86
86
 
87
87
  scheme = h[':scheme'][0]
88
88
  domain = h[':authority'][0]
89
89
  path = h[':path'][0]
90
90
  uri = URI("#{scheme}://#{domain}#{path}")
91
- method = h[':method'][0]
91
+ method = h[':method'][0].upcase
92
92
  h['cookie'] = [h['cookie'].join('; ')] if h.key?('cookie')
93
- Request.new(method, uri, h, s)
93
+ Request.new(method, uri, h.except(*PSEUDO_HEADER_FIELDS), s)
94
94
  end
95
95
  end
96
96
  end
@@ -18,7 +18,7 @@ module Biryani
18
18
  res = HTTP::Response.internal_server_error
19
19
  end
20
20
 
21
- tx.send([res, stream_id], move: true)
21
+ tx.send([:response, res, stream_id], move: true)
22
22
  end
23
23
  end
24
24
  end
@@ -2,10 +2,10 @@ module Biryani
2
2
  class StreamsContext
3
3
  attr_accessor :tx
4
4
 
5
- def initialize(proc)
5
+ def initialize(proc, tx)
6
6
  @h = {} # Hash<Integer, StreamContext>
7
7
  @proc = proc
8
- @tx = Ractor::Port.new
8
+ @tx = tx
9
9
  end
10
10
 
11
11
  # @param stream_id [Integer]
@@ -1,3 +1,3 @@
1
1
  module Biryani
2
- VERSION = '0.0.11'.freeze
2
+ VERSION = '0.0.14'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: biryani
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - thekuwayama