biryani 0.0.1 → 0.0.2

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: 00cbfab00ec034ee2b79096e4192e363426dbcd0c70e59578b9ead09b2a05865
4
- data.tar.gz: d0f78a6b2541464bbf9e0295b13ce576defd8f7a057c200c0c9118a8650ca882
3
+ metadata.gz: 5e8b17b7b795ee787a07fe5be9e453d4486fdce914609539446932e4fb988912
4
+ data.tar.gz: 45f9196ca41aecf8efecaee4e29e459e20a6ac05116b8e384858f52d365c4726
5
5
  SHA512:
6
- metadata.gz: 11759d99704bbd2d7fc525c30e30022eebd42396c6db918802b8424832fa4abfc9a47390e1abbb0bcbd9063e2acb74f9fb95b685fac9dd9a389a931e71ef433d
7
- data.tar.gz: 4c55e5e2a86a5b65e0e92d34b1e216fc699b18fc5d84486d07c0103a3b813173039bd7ec123b5b781b46c396d87d6d87ae263a1215150df3633ca012e2070238
6
+ metadata.gz: 13ba324d9792cde457634d20d72bb7f276772aa6b1eacd878dd82cd3f2fade7f5025472e7bf4b057208f71b68bf8f6df7e2f07bfaeda62baa0c9c6f7c5f58018
7
+ data.tar.gz: ed9846fa55648102b94242338429b45d6bea100eb1427a736c5a90886c6e3933602138407f0bd474f5c3f024887100f2fc241b46c16bfa2f035ca0ce08f8c1f2
data/.rubocop.yml CHANGED
@@ -17,6 +17,9 @@ Metrics/ClassLength:
17
17
  Metrics/MethodLength:
18
18
  Max: 30
19
19
 
20
+ Metrics/ParameterLists:
21
+ Max: 7
22
+
20
23
  Naming/MethodParameterName:
21
24
  MinNameLength: 1
22
25
 
data/README.md CHANGED
@@ -32,8 +32,8 @@ port = ARGV[0] || 8888
32
32
  socket = TCPServer.new(port)
33
33
 
34
34
  server = Biryani::Server.new(
35
- # @params _req [Biryani::HTTPRequest]
36
- # @params res [Biryani::HTTPResponse]
35
+ # @param _req [Biryani::HTTPRequest]
36
+ # @param res [Biryani::HTTPResponse]
37
37
  Ractor.shareable_proc do |_req, res|
38
38
  res.status = 200
39
39
  res.content = 'Hello, world!'
@@ -42,6 +42,11 @@ server = Biryani::Server.new(
42
42
  server.run(socket)
43
43
  ```
44
44
 
45
+ ```sh-session
46
+ $ curl --http2-prior-knowledge http://localhost:8888
47
+ Hello, world!
48
+ ```
49
+
45
50
 
46
51
  ## License
47
52
 
data/example/echo.rb CHANGED
@@ -9,8 +9,8 @@ port = ARGV[0] || 8888
9
9
  socket = TCPServer.new(port)
10
10
 
11
11
  server = Biryani::Server.new(
12
- # @params req [Biryani::HTTPRequest]
13
- # @params res [Biryani::HTTPResponse]
12
+ # @param req [Biryani::HTTPRequest]
13
+ # @param res [Biryani::HTTPResponse]
14
14
  Ractor.shareable_proc do |req, res|
15
15
  res.status = 200
16
16
 
@@ -9,8 +9,8 @@ port = ARGV[0] || 8888
9
9
  socket = TCPServer.new(port)
10
10
 
11
11
  server = Biryani::Server.new(
12
- # @params _req [Biryani::HTTPRequest]
13
- # @params res [Biryani::HTTPResponse]
12
+ # @param _req [Biryani::HTTPRequest]
13
+ # @param res [Biryani::HTTPResponse]
14
14
  Ractor.shareable_proc do |_req, res|
15
15
  res.status = 200
16
16
  res.content = 'Hello, world!'
@@ -24,11 +24,11 @@ module Biryani
24
24
  @streams_ctx = StreamsContext.new
25
25
  @encoder = HPACK::Encoder.new(4_096)
26
26
  @decoder = HPACK::Decoder.new(4_096)
27
- @send_window = Window.new
28
- @recv_window = Window.new
27
+ @send_window = Window.new(65_535)
28
+ @recv_window = Window.new(65_535)
29
29
  @data_buffer = DataBuffer.new
30
- @send_settings = self.class.default_settings # Hash<Integer, Integer>
31
- @recv_settings = self.class.default_settings # Hash<Integer, Integer>
30
+ @settings = self.class.default_settings # Hash<Integer, Integer>
31
+ @peer_settings = self.class.default_settings # Hash<Integer, Integer>
32
32
  @closed = false
33
33
  end
34
34
 
@@ -66,6 +66,7 @@ module Biryani
66
66
  # rubocop: disable Metrics/AbcSize
67
67
  # rubocop: disable Metrics/BlockLength
68
68
  # rubocop: disable Metrics/CyclomaticComplexity
69
+ # rubocop: disable Metrics/MethodLength
69
70
  # rubocop: disable Metrics/PerceivedComplexity
70
71
  def send_loop(io)
71
72
  loop do
@@ -75,11 +76,11 @@ module Biryani
75
76
 
76
77
  port, obj = Ractor.select(*ports)
77
78
  if port == @sock
78
- if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
79
+ if Biryani.err?(obj)
79
80
  reply_frame = self.class.unwrap(obj, @streams_ctx.last_stream_id)
80
81
  self.class.do_send(io, reply_frame, true)
81
82
  close if self.class.transition_state_send(reply_frame, @streams_ctx)
82
- elsif obj.length > @send_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
83
+ elsif obj.length > @settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
83
84
  self.class.do_send(io, Frame::Goaway.new(0, @streams_ctx.last_stream_id, ErrorCode::FRAME_SIZE_ERROR, 'payload length greater than SETTINGS_MAX_FRAME_SIZE'), true)
84
85
  close
85
86
  else
@@ -90,21 +91,24 @@ module Biryani
90
91
  end
91
92
  end
92
93
  else
93
- self.class.http_response(*obj, @encoder, @send_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]).each do |send_frame|
94
- close if self.class.send(io, send_frame, @send_window, @streams_ctx, @data_buffer)
95
- end
94
+ res, stream_id = obj
95
+ fragment, data = self.class.http_response(res, @encoder)
96
+ max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
97
+ self.class.send_headers(io, stream_id, fragment, data.empty?, max_frame_size, @streams_ctx)
98
+ self.class.send_data(io, stream_id, data, @send_window, max_frame_size, @streams_ctx, @data_buffer) unless data.empty?
96
99
 
97
- self.class.remove_closed_streams(@streams_ctx, @data_buffer)
100
+ @streams_ctx.remove_closed(@data_buffer)
98
101
  end
99
102
 
100
103
  break if closed?
101
104
  end
102
105
  ensure
103
- self.class.clear_all_streams(@streams_ctx)
106
+ @streams_ctx.clear_all
104
107
  end
105
108
  # rubocop: enable Metrics/AbcSize
106
109
  # rubocop: enable Metrics/BlockLength
107
110
  # rubocop: enable Metrics/CyclomaticComplexity
111
+ # rubocop: enable Metrics/MethodLength
108
112
  # rubocop: enable Metrics/PerceivedComplexity
109
113
 
110
114
  # @param frame [Object]
@@ -128,7 +132,7 @@ module Biryani
128
132
  when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
129
133
  [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
130
134
  when FrameType::SETTINGS
131
- obj = self.class.handle_settings(frame, @send_settings, @decoder)
135
+ obj = self.class.handle_settings(frame, @peer_settings, @decoder, @streams_ctx)
132
136
  return [] if obj.nil?
133
137
 
134
138
  settings_ack = obj
@@ -137,7 +141,8 @@ module Biryani
137
141
  obj = self.class.handle_ping(frame)
138
142
  return [] if obj.nil?
139
143
 
140
- [obj]
144
+ ping_ack = obj
145
+ [ping_ack]
141
146
  when FrameType::GOAWAY
142
147
  self.class.handle_goaway(frame)
143
148
  # TODO: logging error
@@ -146,7 +151,8 @@ module Biryani
146
151
  err = self.class.handle_connection_window_update(frame, @send_window)
147
152
  return [err] unless err.nil?
148
153
 
149
- @data_buffer.take!(@send_window, @streams_ctx)
154
+ max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
155
+ @data_buffer.take!(@send_window, @streams_ctx, max_frame_size)
150
156
  else
151
157
  # ignore unknown frame type
152
158
  []
@@ -167,16 +173,20 @@ module Biryani
167
173
  return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
168
174
  if [FrameType::SETTINGS, FrameType::PING, FrameType::GOAWAY].include?(typ)
169
175
 
170
- obj = self.class.transition_state_recv(frame, @streams_ctx, stream_id, @send_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS], @proc)
171
- return [obj] if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
176
+ max_streams = @peer_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS]
177
+ send_initial_window_size = @peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
178
+ recv_initial_window_size = @settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
179
+ obj = self.class.transition_state_recv(frame, @streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size, @proc)
180
+ return [obj] if Biryani.err?(obj)
172
181
 
173
182
  ctx = obj
174
183
  case typ
175
184
  when FrameType::DATA
185
+ # TODO: flow-control using @recv_window & ctx.recv_window
176
186
  ctx.content << frame.data
177
187
  if ctx.state.half_closed_remote?
178
188
  obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
179
- return [obj] if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
189
+ return [obj] if Biryani.err?(obj)
180
190
 
181
191
  ctx.stream.rx << obj
182
192
  end
@@ -186,7 +196,7 @@ module Biryani
186
196
  ctx.fragment << frame.fragment
187
197
  if ctx.state.half_closed_remote?
188
198
  obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
189
- return [obj] if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
199
+ return [obj] if Biryani.err?(obj)
190
200
 
191
201
  ctx.stream.rx << obj
192
202
  end
@@ -205,7 +215,8 @@ module Biryani
205
215
  err = self.class.handle_stream_window_update(frame, @streams_ctx)
206
216
  return [err] unless err.nil?
207
217
 
208
- @data_buffer.take!(@send_window, @streams_ctx)
218
+ max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
219
+ @data_buffer.take!(@send_window, @streams_ctx, max_frame_size)
209
220
  else
210
221
  # ignore UNKNOWN Frame
211
222
  []
@@ -244,20 +255,22 @@ module Biryani
244
255
  # @param streams_ctx [StreamsContext]
245
256
  # @param stream_id [Integer]
246
257
  # @param max_streams [Integer]
258
+ # @param send_initial_window_size [Integer]
259
+ # @param recv_initial_window_size [Integer]
247
260
  # @param proc [Proc]
248
261
  #
249
262
  # @return [StreamContext, StreamError, ConnectionError]
250
263
  # rubocop: disable Metrics/CyclomaticComplexity
251
264
  # rubocop: disable Metrics/PerceivedComplexity
252
- def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, proc)
265
+ def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size, proc)
253
266
  ctx = streams_ctx[stream_id]
254
267
  return StreamError.new(ErrorCode::PROTOCOL_ERROR, stream_id, 'exceed max concurrent streams') if ctx.nil? && streams_ctx.count_active + 1 > max_streams
255
268
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'even-numbered stream identifier') if ctx.nil? && stream_id.even?
256
269
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'new stream identifier is less than the existing stream identifiers') if ctx.nil? && streams_ctx.last_stream_id > stream_id
257
270
 
258
- ctx = streams_ctx.new_context(stream_id, proc) if ctx.nil?
271
+ ctx = streams_ctx.new_context(stream_id, send_initial_window_size, recv_initial_window_size, proc) if ctx.nil?
259
272
  obj = ctx.state.transition!(recv_frame, :recv)
260
- return obj if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
273
+ return obj if Biryani.err?(obj)
261
274
 
262
275
  ctx
263
276
  end
@@ -275,7 +288,7 @@ module Biryani
275
288
  when FrameType::SETTINGS, FrameType::PING
276
289
  false
277
290
  when FrameType::GOAWAY
278
- close_all_streams(streams_ctx)
291
+ streams_ctx.close_all
279
292
  true
280
293
  else
281
294
  streams_ctx[stream_id].state.transition!(send_frame, :send)
@@ -283,38 +296,6 @@ module Biryani
283
296
  end
284
297
  end
285
298
 
286
- # @param streams_ctx [StreamsContext]
287
- def self.clear_all_streams(streams_ctx)
288
- streams_ctx.each do |ctx|
289
- ctx.tx.close
290
- ctx.stream.rx << nil
291
- ctx.fragment.close
292
- ctx.content.close
293
- end
294
- end
295
-
296
- # @param streams_ctx [StreamsContext]
297
- def self.close_all_streams(streams_ctx)
298
- streams_ctx.each do |ctx|
299
- ctx.tx.close
300
- ctx.fragment.close
301
- ctx.content.close
302
- ctx.state.close
303
- end
304
- end
305
-
306
- # @param streams_ctx [StreamsContext]
307
- # @param data_buffer [DataBuffer]
308
- def self.remove_closed_streams(streams_ctx, data_buffer)
309
- closed_ids = streams_ctx.closed_stream_ids.filter { |id| !data_buffer.has?(id) }
310
- closed_ids.each do |id|
311
- streams_ctx[id].tx.close
312
- streams_ctx[id].stream.rx << nil
313
- streams_ctx[id].fragment.close
314
- streams_ctx[id].content.close
315
- end
316
- end
317
-
318
299
  # @param io [IO]
319
300
  # @param frame [Object]
320
301
  # @param flush [Boolean]
@@ -324,39 +305,46 @@ module Biryani
324
305
  end
325
306
 
326
307
  # @param io [IO]
327
- # @param frame [Object]
308
+ # @param stream_id [Integer]
309
+ # @param data [String]
328
310
  # @param send_window [Window]
311
+ # @param max_frame_size [Integer]
329
312
  # @param streams_ctx [StreamsContext]
330
313
  # @param data_buffer [DataBuffer]
331
- #
332
- # @return [Boolean] should close connection?
333
- def self.send(io, frame, send_window, streams_ctx, data_buffer)
334
- if frame.f_type != FrameType::DATA
335
- do_send(io, frame, false)
336
- return transition_state_send(frame, streams_ctx)
337
- end
314
+ def self.send_data(io, stream_id, data, send_window, max_frame_size, streams_ctx, data_buffer)
315
+ frames, remains = streams_ctx.sendable_data_frames(stream_id, data, send_window, max_frame_size)
338
316
 
339
- data = frame
340
- if sendable?(data, send_window, streams_ctx)
341
- do_send(io, data, false)
342
- send_window.consume!(data.length)
343
- streams_ctx[data.stream_id].send_window.consume!(data.length)
344
- return transition_state_send(frame, streams_ctx)
317
+ frames.each do |frame|
318
+ do_send(io, frame, false)
319
+ send_window.consume!(frame.length)
320
+ streams_ctx[stream_id].send_window.consume!(frame.length)
321
+ transition_state_send(frame, streams_ctx)
345
322
  end
346
323
 
347
- data_buffer << data
348
- false
324
+ data_buffer.store(stream_id, remains) unless remains.empty?
349
325
  end
350
326
 
351
- # @param data [Data]
352
- # @param send_window [Window]
327
+ # @param io [IO]
328
+ # @param stream_id [Integer]
329
+ # @param fragment [String]
330
+ # @param only_headers [Boolean]
331
+ # @param max_frame_size [Integer]
353
332
  # @param streams_ctx [StreamsContext]
354
- #
355
- # @return [Boolean]
356
- def self.sendable?(data, send_window, streams_ctx)
357
- length = data.length
358
- stream_id = data.stream_id
359
- send_window.available?(length) && streams_ctx[stream_id].send_window.available?(length)
333
+ def self.send_headers(io, stream_id, fragment, only_headers, max_frame_size, streams_ctx)
334
+ len = (fragment.bytesize + max_frame_size - 1) / max_frame_size
335
+ frames = fragment.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
336
+ end_headers = index == len - 1
337
+ if index.zero?
338
+ Frame::Headers.new(end_headers, only_headers, stream_id, nil, nil, s, nil)
339
+ else
340
+ Frame::Continuation.new(end_headers, stream_id, s)
341
+ end
342
+ end
343
+
344
+ frames.each do |frame|
345
+ do_send(io, frame, false)
346
+ transition_state_send(frame, streams_ctx)
347
+ end
360
348
  end
361
349
 
362
350
  # @param io [IO]
@@ -375,21 +363,28 @@ module Biryani
375
363
  end
376
364
 
377
365
  # @param settings [Settings]
378
- # @param send_settings [Hash<Integer, Integer>]
366
+ # @param peer_settings [Hash<Integer, Integer>]
379
367
  # @param decoder [Decoder]
368
+ # @param streams_ctx [StreamsContext]
380
369
  #
381
370
  # @return [Settings]
382
- def self.handle_settings(settings, send_settings, decoder)
371
+ def self.handle_settings(settings, peer_settings, decoder, streams_ctx)
383
372
  return nil if settings.ack?
384
373
 
385
- send_settings.merge!(settings.setting)
386
- decoder.limit!(send_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE])
374
+ peer_settings.merge!(settings.setting)
375
+ new_limit = peer_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE]
376
+ decoder.limit!(new_limit)
377
+ send_initial_window_size = peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
378
+ streams_ctx.each do |ctx|
379
+ ctx.send_window.update!(send_initial_window_size)
380
+ end
381
+
387
382
  Frame::Settings.new(true, 0, {})
388
383
  end
389
384
 
390
385
  # @param ping [Ping]
391
386
  #
392
- # @return [Ping, nil, ConnectionError]
387
+ # @return [Ping, nil]
393
388
  def self.handle_ping(ping)
394
389
  Frame::Ping.new(true, 0, ping.opaque) unless ping.ack?
395
390
  end
@@ -402,21 +397,22 @@ module Biryani
402
397
  #
403
398
  # @return [nil, ConnectionError]
404
399
  def self.handle_connection_window_update(window_update, send_window)
405
- # TODO: send WINDOW_UPDATE
400
+ # TODO: send WINDOW_UPDATE to do the flow-conrol
406
401
  send_window.increase!(window_update.window_size_increment)
402
+ return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'flow-control window exceeds 2^31-1') if send_window.length > 2**31 - 1
403
+
407
404
  nil
408
405
  end
409
406
 
410
407
  # @param window_update [WindowUpdate]
411
408
  # @param streams_ctx [StreamsContext]
412
409
  #
413
- # @return [nil, ConnectionError]
410
+ # @return [nil, StreamError]
414
411
  def self.handle_stream_window_update(window_update, streams_ctx)
415
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'WINDOW_UPDATE invalid window size increment 0') if window_update.window_size_increment.zero?
416
- return StreamError.new(ErrorCode::FLOW_CONTROL_ERROR, window_update.stream_id, 'WINDOW_UPDATE invalid window size increment greater than 2^31-1') \
417
- if window_update.window_size_increment > 2**31 - 1
412
+ stream_id = window_update.stream_id
413
+ streams_ctx[stream_id].send_window.increase!(window_update.window_size_increment)
414
+ return StreamError.new(ErrorCode::FLOW_CONTROL_ERROR, stream_id, 'flow-control window exceeds 2^31-1') if streams_ctx[stream_id].send_window.length > 2**31 - 1
418
415
 
419
- streams_ctx[window_update.stream_id].send_window.increase!(window_update.window_size_increment)
420
416
  nil
421
417
  end
422
418
 
@@ -427,7 +423,7 @@ module Biryani
427
423
  # @return [HTTPRequest, ConnectionError]
428
424
  def self.http_request(fragment, content, decoder)
429
425
  obj = decoder.decode(fragment)
430
- return obj if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
426
+ return obj if Biryani.err?(obj)
431
427
 
432
428
  fields = obj
433
429
  builder = HTTPRequestBuilder.new
@@ -438,13 +434,12 @@ module Biryani
438
434
  end
439
435
 
440
436
  # @param res [HTTPResponse]
441
- # @param stream_id [Integer]
442
437
  # @param encoder [Encoder]
443
- # @param max_frame_size [Integer]
444
438
  #
445
- # @return [Array<Object>] frames
446
- def self.http_response(res, stream_id, encoder, max_frame_size)
447
- HTTPResponseParser.new(res).parse(stream_id, encoder, max_frame_size)
439
+ # @return [String] fragment
440
+ # @return [String] data
441
+ def self.http_response(res, encoder)
442
+ HTTPResponseParser.new(res).parse(encoder)
448
443
  end
449
444
 
450
445
  # @return [Hash<Integer, Integer>]
@@ -1,30 +1,40 @@
1
1
  module Biryani
2
2
  class DataBuffer
3
3
  def initialize
4
- @buffer = [] # Array<Data>
4
+ @buffer = {} # Hash<Integer, String>
5
5
  end
6
6
 
7
- # @param data [Data]
8
- def <<(data)
9
- @buffer << data
7
+ # @param stream_id [Integer]
8
+ # @param data [String]
9
+ def store(stream_id, data)
10
+ @buffer[stream_id] = '' unless @buffer.key?(stream_id)
11
+ @buffer[stream_id] += data
10
12
  end
11
13
 
12
14
  # @param send_window [Window]
13
- # @param stream_ctxs [Hash<Integer, StreamContext>]
15
+ # @param streams_ctx [StreamsContext]
16
+ # @param max_frame_size [Intger]
14
17
  #
15
- # @return [Array<Data>]
16
- def take!(send_window, stream_ctxs)
17
- datas = {}
18
- @buffer.each_with_index.each do |data, i|
19
- next unless Connection.sendable?(data, send_window, stream_ctxs)
18
+ # @return [Array<Object>] frames
19
+ def take!(send_window, streams_ctx, max_frame_size)
20
+ datas = []
21
+ @buffer.each do |stream_id, data|
22
+ frames, remains = streams_ctx.sendable_data_frames(stream_id, data, send_window, max_frame_size)
23
+ next if frames.empty?
24
+
25
+ datas += frames
26
+ if remains.empty?
27
+ @buffer.delete(stream_id)
28
+ else
29
+ @buffer[stream_id] = remains
30
+ end
20
31
 
21
- send_window.consume!(data.length)
22
- stream_ctxs[data.stream_id].send_window.consume!(data.length)
23
- datas[i] = data
32
+ len = frames.map(&:length).sum
33
+ send_window.consume!(len)
34
+ streams_ctx[stream_id].send_window.consume!(len)
24
35
  end
25
36
 
26
- @buffer = @buffer.each_with_index.filter { |_, i| datas.keys.include?(i) }.map(&:first)
27
- datas.values
37
+ datas
28
38
  end
29
39
 
30
40
  # @return [Integer]
@@ -36,7 +46,7 @@ module Biryani
36
46
  #
37
47
  # @return [Boolean]
38
48
  def has?(stream_id)
39
- @buffer.filter { |data| data.stream_id == stream_id }.any?
49
+ @buffer.key?(stream_id)
40
50
  end
41
51
  end
42
52
  end
@@ -10,7 +10,6 @@ module Biryani
10
10
  # @param weight [Integer, nil]
11
11
  # @param fragment [String]
12
12
  # @param padding [String, nil]
13
- # rubocop: disable Metrics/ParameterLists
14
13
  def initialize(end_headers, end_stream, stream_id, stream_dependency, weight, fragment, padding)
15
14
  @f_type = FrameType::HEADERS
16
15
  @end_headers = end_headers
@@ -21,7 +20,6 @@ module Biryani
21
20
  @fragment = fragment
22
21
  @padding = padding
23
22
  end
24
- # rubocop: enable Metrics/ParameterLists
25
23
 
26
24
  # @return [Boolean]
27
25
  def priority?
@@ -34,28 +34,12 @@ module Biryani
34
34
  @res.content
35
35
  end
36
36
 
37
- # @param stream_id [Integer]
38
37
  # @param encoder [Encoder]
39
- # @param max_frame_size [Integer]
40
38
  #
41
- # @return [Array<Object>] frames
42
- def parse(stream_id, encoder, max_frame_size)
43
- fragment = encoder.encode(fields)
44
- len = (fragment.bytesize + max_frame_size - 1) / max_frame_size
45
- frames = fragment.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
46
- if index.zero?
47
- Frame::Headers.new(len < 2, content.empty?, stream_id, nil, nil, s, nil)
48
- else
49
- Frame::Continuation.new(index == len - 1, stream_id, s)
50
- end
51
- end
52
-
53
- len = (content.bytesize + max_frame_size - 1) / max_frame_size
54
- frames += content.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
55
- Frame::Data.new(index == len - 1, stream_id, s, nil)
56
- end
57
-
58
- frames
39
+ # @return [String] fragment
40
+ # @return [String] data
41
+ def parse(encoder)
42
+ [encoder.encode(fields), content]
59
43
  end
60
44
  end
61
45
  end
data/lib/biryani/state.rb CHANGED
@@ -8,7 +8,7 @@ module Biryani
8
8
  # @param direction [:send, :recv]
9
9
  def transition!(frame, direction)
10
10
  obj = self.class.next(@state, frame, direction)
11
- return obj if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
11
+ return obj if Biryani.err?(obj)
12
12
 
13
13
  @state = obj
14
14
  end
@@ -76,6 +76,8 @@ module Biryani
76
76
  # receiving_continuation_data
77
77
  in [:receiving_continuation_data, FrameType::RST_STREAM, _]
78
78
  :closed
79
+ in [:receiving_continuation_data, FrameType::WINDOW_UPDATE, :recv]
80
+ state
79
81
  in [:receiving_continuation_data, FrameType::CONTINUATION, :recv] if frame.end_headers?
80
82
  :receiving_data
81
83
  in [:receiving_continuation_data, FrameType::CONTINUATION, :recv]
@@ -86,6 +88,8 @@ module Biryani
86
88
  # receiving_continuation
87
89
  in [:receiving_continuation, FrameType::RST_STREAM, _]
88
90
  :closed
91
+ in [:receiving_continuation, FrameType::WINDOW_UPDATE, :recv]
92
+ state
89
93
  in [:receiving_continuation, FrameType::CONTINUATION, :recv] if frame.end_headers?
90
94
  :half_closed_remote
91
95
  in [:receiving_continuation, FrameType::CONTINUATION, :recv]
@@ -96,6 +100,8 @@ module Biryani
96
100
  # receiving_data
97
101
  in [:receiving_data, FrameType::DATA, :recv] if frame.end_stream?
98
102
  :half_closed_remote
103
+ in [:receiving_data, FrameType::WINDOW_UPDATE, :recv]
104
+ state
99
105
  in [:receiving_data, FrameType::DATA, :recv]
100
106
  state
101
107
  in [:receiving_data, FrameType::RST_STREAM, _]
@@ -136,6 +142,8 @@ module Biryani
136
142
  # sending_continuation_data
137
143
  in [:sending_continuation_data, FrameType::RST_STREAM, :send]
138
144
  :closed
145
+ in [:sending_continuation_data, FrameType::WINDOW_UPDATE, :recv]
146
+ state
139
147
  in [:sending_continuation_data, FrameType::CONTINUATION, :send] if frame.end_headers?
140
148
  :sending_data
141
149
  in [:sending_continuation_data, FrameType::CONTINUATION, :send]
@@ -148,6 +156,8 @@ module Biryani
148
156
  # sending_continuation
149
157
  in [:sending_continuation, FrameType::RST_STREAM, :send]
150
158
  :closed
159
+ in [:sending_continuation, FrameType::WINDOW_UPDATE, :recv]
160
+ state
151
161
  in [:sending_continuation, FrameType::CONTINUATION, :send] if frame.end_headers?
152
162
  :closed
153
163
  in [:sending_continuation, FrameType::CONTINUATION, :send]
@@ -160,6 +170,8 @@ module Biryani
160
170
  # sending_data
161
171
  in [:sending_data, FrameType::DATA, :send] if frame.end_stream?
162
172
  :closed
173
+ in [:sending_data, FrameType::WINDOW_UPDATE, :recv]
174
+ state
163
175
  in [:sending_data, FrameType::DATA, :send]
164
176
  state
165
177
  in [:sending_data, FrameType::RST_STREAM, :send]
@@ -213,7 +225,7 @@ module Biryani
213
225
 
214
226
  # @return [Boolean]
215
227
  def active?
216
- @state == :open || @state == :half_closed_local || @state == :half_closed_remote
228
+ !%i[idle reserved_local reserved_remote closed].include?(@state)
217
229
  end
218
230
 
219
231
  # @return [Boolean]