biryani 0.0.1 → 0.0.3

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: 890de8c1fbcd3833f63207a420161b289a4497275fbdbb747625d1350b1f0d6c
4
+ data.tar.gz: 1c587a3643953d2acbd1b0022c1ff1b228cc382cc10c75442fb99d68b370f2b4
5
5
  SHA512:
6
- metadata.gz: 11759d99704bbd2d7fc525c30e30022eebd42396c6db918802b8424832fa4abfc9a47390e1abbb0bcbd9063e2acb74f9fb95b685fac9dd9a389a931e71ef433d
7
- data.tar.gz: 4c55e5e2a86a5b65e0e92d34b1e216fc699b18fc5d84486d07c0103a3b813173039bd7ec123b5b781b46c396d87d6d87ae263a1215150df3633ca012e2070238
6
+ metadata.gz: fe5ed688fe4aff77533de4b37ee5e668f7904435a2c345410c4b87c85ab68af33d03b3741fc3266e605554e057c81cfe28e267b1d94e2f916718e2190d6ee948
7
+ data.tar.gz: 19cfdad4968d33626135242e33629ed5105069a30f50e377367fa0aae45e2c594e2cfa388c7ea8b635ab17541749a147c04414738633787a3b720043b38ac6aa
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!'
@@ -21,14 +21,14 @@ module Biryani
21
21
  def initialize(proc)
22
22
  @sock = nil # Ractor
23
23
  @proc = proc
24
- @streams_ctx = StreamsContext.new
24
+ @streams_ctx = StreamsContext.new(proc)
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,36 +76,43 @@ 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
- reply_frame = self.class.unwrap(obj, @streams_ctx.last_stream_id)
79
+ if Biryani.err?(obj)
80
+ reply_frame = Biryani.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
86
87
  recv_dispatch(obj).each do |frame|
87
- reply_frame = self.class.unwrap(frame, @streams_ctx.last_stream_id)
88
+ reply_frame = Biryani.unwrap(frame, @streams_ctx.last_stream_id)
88
89
  self.class.do_send(io, reply_frame, true)
90
+ if reply_frame.f_type == FrameType::WINDOW_UPDATE && reply_frame.stream_id.zero?
91
+ @recv_window.increase!(reply_frame.window_size_increment)
92
+ elsif reply_frame.f_type == FrameType::WINDOW_UPDATE
93
+ @streams_ctx[reply_frame.stream_id].recv_window.increase!(reply_frame.window_size_increment)
94
+ end
95
+
89
96
  close if self.class.transition_state_send(reply_frame, @streams_ctx)
90
97
  end
91
98
  end
92
99
  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
100
+ res, stream_id = obj
101
+ fragment, data = self.class.http_response(res, @encoder)
102
+ max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
103
+ self.class.send_headers(io, stream_id, fragment, data.empty?, max_frame_size, @streams_ctx)
104
+ self.class.send_data(io, stream_id, data, @send_window, max_frame_size, @streams_ctx, @data_buffer) unless data.empty?
96
105
 
97
- self.class.remove_closed_streams(@streams_ctx, @data_buffer)
106
+ @streams_ctx.remove_closed(@data_buffer)
98
107
  end
99
108
 
100
109
  break if closed?
101
110
  end
102
- ensure
103
- self.class.clear_all_streams(@streams_ctx)
104
111
  end
105
112
  # rubocop: enable Metrics/AbcSize
106
113
  # rubocop: enable Metrics/BlockLength
107
114
  # rubocop: enable Metrics/CyclomaticComplexity
115
+ # rubocop: enable Metrics/MethodLength
108
116
  # rubocop: enable Metrics/PerceivedComplexity
109
117
 
110
118
  # @param frame [Object]
@@ -128,7 +136,7 @@ module Biryani
128
136
  when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
129
137
  [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
130
138
  when FrameType::SETTINGS
131
- obj = self.class.handle_settings(frame, @send_settings, @decoder)
139
+ obj = self.class.handle_settings(frame, @peer_settings, @decoder, @streams_ctx)
132
140
  return [] if obj.nil?
133
141
 
134
142
  settings_ack = obj
@@ -137,18 +145,21 @@ module Biryani
137
145
  obj = self.class.handle_ping(frame)
138
146
  return [] if obj.nil?
139
147
 
140
- [obj]
148
+ ping_ack = obj
149
+ [ping_ack]
141
150
  when FrameType::GOAWAY
142
151
  self.class.handle_goaway(frame)
143
- # TODO: logging error
152
+
144
153
  []
145
154
  when FrameType::WINDOW_UPDATE
146
155
  err = self.class.handle_connection_window_update(frame, @send_window)
147
156
  return [err] unless err.nil?
148
157
 
149
- @data_buffer.take!(@send_window, @streams_ctx)
158
+ max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
159
+ @data_buffer.take!(@send_window, @streams_ctx, max_frame_size) # return DATA Frames
150
160
  else
151
- # ignore unknown frame type
161
+ # ignore UNKNOWN Frame
162
+
152
163
  []
153
164
  end
154
165
  end
@@ -160,61 +171,57 @@ module Biryani
160
171
  # rubocop: disable Metrics/AbcSize
161
172
  # rubocop: disable Metrics/CyclomaticComplexity
162
173
  # rubocop: disable Metrics/MethodLength
163
- # rubocop: disable Metrics/PerceivedComplexity
164
174
  def handle_stream_frame(frame)
165
175
  stream_id = frame.stream_id
166
176
  typ = frame.f_type
167
177
  return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
168
178
  if [FrameType::SETTINGS, FrameType::PING, FrameType::GOAWAY].include?(typ)
169
179
 
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)
180
+ max_streams = @peer_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS]
181
+ send_initial_window_size = @peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
182
+ recv_initial_window_size = @settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
183
+ obj = self.class.transition_state_recv(frame, @streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size)
184
+ return [obj] if Biryani.err?(obj)
172
185
 
173
186
  ctx = obj
174
187
  case typ
175
188
  when FrameType::DATA
176
- ctx.content << frame.data
177
- if ctx.state.half_closed_remote?
178
- 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
+ obj = self.class.handle_data(stream_id, frame.data, @recv_window, @streams_ctx, @decoder)
190
+ return [obj] if Biryani.err?(obj)
180
191
 
181
- ctx.stream.rx << obj
182
- end
183
-
184
- []
192
+ obj # return WINDOW_UPDATE Frames
185
193
  when FrameType::HEADERS, FrameType::CONTINUATION
186
- ctx.fragment << frame.fragment
187
- if ctx.state.half_closed_remote?
188
- obj = self.class.http_request(ctx.fragment.string, ctx.content.string, @decoder)
189
- return [obj] if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
190
-
191
- ctx.stream.rx << obj
192
- end
194
+ err = self.class.handle_headers(frame, ctx, @decoder)
195
+ return [err] unless err.nil?
193
196
 
194
197
  []
195
198
  when FrameType::PRIORITY
196
199
  # ignore PRIORITY Frame
200
+
197
201
  []
198
202
  when FrameType::PUSH_PROMISE
199
203
  # TODO
204
+
200
205
  []
201
206
  when FrameType::RST_STREAM
202
- self.class.handle_rst_stream(frame, @streams_ctx)
207
+ self.class.handle_rst_stream(frame, ctx)
208
+
203
209
  []
204
210
  when FrameType::WINDOW_UPDATE
205
211
  err = self.class.handle_stream_window_update(frame, @streams_ctx)
206
212
  return [err] unless err.nil?
207
213
 
208
- @data_buffer.take!(@send_window, @streams_ctx)
214
+ max_frame_size = @peer_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
215
+ @data_buffer.take!(@send_window, @streams_ctx, max_frame_size)
209
216
  else
210
217
  # ignore UNKNOWN Frame
218
+
211
219
  []
212
220
  end
213
221
  end
214
222
  # rubocop: enable Metrics/AbcSize
215
223
  # rubocop: enable Metrics/CyclomaticComplexity
216
224
  # rubocop: enable Metrics/MethodLength
217
- # rubocop: enable Metrics/PerceivedComplexity
218
225
 
219
226
  def close
220
227
  @closed = true
@@ -225,39 +232,25 @@ module Biryani
225
232
  @closed
226
233
  end
227
234
 
228
- # @param obj [Object, ConnectionError, StreamError] frame or error
229
- # @param last_stream_id [Integer]
230
- #
231
- # @return [Frame]
232
- def self.unwrap(obj, last_stream_id)
233
- case obj
234
- when ConnectionError
235
- obj.goaway(last_stream_id)
236
- when StreamError
237
- obj.rst_stream
238
- else
239
- obj
240
- end
241
- end
242
-
243
235
  # @param recv_frame [Object]
244
236
  # @param streams_ctx [StreamsContext]
245
237
  # @param stream_id [Integer]
246
238
  # @param max_streams [Integer]
247
- # @param proc [Proc]
239
+ # @param send_initial_window_size [Integer]
240
+ # @param recv_initial_window_size [Integer]
248
241
  #
249
242
  # @return [StreamContext, StreamError, ConnectionError]
250
243
  # rubocop: disable Metrics/CyclomaticComplexity
251
244
  # rubocop: disable Metrics/PerceivedComplexity
252
- def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, proc)
245
+ def self.transition_state_recv(recv_frame, streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size)
253
246
  ctx = streams_ctx[stream_id]
254
247
  return StreamError.new(ErrorCode::PROTOCOL_ERROR, stream_id, 'exceed max concurrent streams') if ctx.nil? && streams_ctx.count_active + 1 > max_streams
255
248
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'even-numbered stream identifier') if ctx.nil? && stream_id.even?
256
249
  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
250
 
258
- ctx = streams_ctx.new_context(stream_id, proc) if ctx.nil?
251
+ ctx = streams_ctx.new_context(stream_id, send_initial_window_size, recv_initial_window_size) if ctx.nil?
259
252
  obj = ctx.state.transition!(recv_frame, :recv)
260
- return obj if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
253
+ return obj if Biryani.err?(obj)
261
254
 
262
255
  ctx
263
256
  end
@@ -275,46 +268,14 @@ module Biryani
275
268
  when FrameType::SETTINGS, FrameType::PING
276
269
  false
277
270
  when FrameType::GOAWAY
278
- close_all_streams(streams_ctx)
271
+ streams_ctx.close_all
279
272
  true
280
273
  else
281
- streams_ctx[stream_id].state.transition!(send_frame, :send)
274
+ streams_ctx[stream_id].state.transition!(send_frame, :send) unless stream_id.zero?
282
275
  false
283
276
  end
284
277
  end
285
278
 
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
279
  # @param io [IO]
319
280
  # @param frame [Object]
320
281
  # @param flush [Boolean]
@@ -324,39 +285,46 @@ module Biryani
324
285
  end
325
286
 
326
287
  # @param io [IO]
327
- # @param frame [Object]
288
+ # @param stream_id [Integer]
289
+ # @param data [String]
328
290
  # @param send_window [Window]
291
+ # @param max_frame_size [Integer]
329
292
  # @param streams_ctx [StreamsContext]
330
293
  # @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
294
+ def self.send_data(io, stream_id, data, send_window, max_frame_size, streams_ctx, data_buffer)
295
+ frames, remains = streams_ctx.sendable_datas(stream_id, data, send_window, max_frame_size)
338
296
 
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)
297
+ frames.each do |frame|
298
+ do_send(io, frame, false)
299
+ send_window.consume!(frame.length)
300
+ streams_ctx[stream_id].send_window.consume!(frame.length)
301
+ transition_state_send(frame, streams_ctx)
345
302
  end
346
303
 
347
- data_buffer << data
348
- false
304
+ data_buffer.store(stream_id, remains) unless remains.empty?
349
305
  end
350
306
 
351
- # @param data [Data]
352
- # @param send_window [Window]
307
+ # @param io [IO]
308
+ # @param stream_id [Integer]
309
+ # @param fragment [String]
310
+ # @param only_headers [Boolean]
311
+ # @param max_frame_size [Integer]
353
312
  # @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)
313
+ def self.send_headers(io, stream_id, fragment, only_headers, max_frame_size, streams_ctx)
314
+ len = (fragment.bytesize + max_frame_size - 1) / max_frame_size
315
+ frames = fragment.gsub(/.{1,#{max_frame_size}}/m).with_index.map do |s, index|
316
+ end_headers = index == len - 1
317
+ if index.zero?
318
+ Frame::Headers.new(end_headers, only_headers, stream_id, nil, nil, s, nil)
319
+ else
320
+ Frame::Continuation.new(end_headers, stream_id, s)
321
+ end
322
+ end
323
+
324
+ frames.each do |frame|
325
+ do_send(io, frame, false)
326
+ transition_state_send(frame, streams_ctx)
327
+ end
360
328
  end
361
329
 
362
330
  # @param io [IO]
@@ -367,29 +335,80 @@ module Biryani
367
335
  ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid connection preface') if s != CONNECTION_PREFACE
368
336
  end
369
337
 
370
- # @param rst_stream [RstStream]
338
+ # @param stream_id [Integer]
339
+ # @param data [String]
340
+ # @param recv_window [Window]
371
341
  # @param streams_ctx [StreamsContext]
372
- def self.handle_rst_stream(rst_stream, streams_ctx)
373
- stream_id = rst_stream.stream_id
374
- streams_ctx[stream_id].state.close
342
+ # @param decoder [Decoder]
343
+ #
344
+ # @return [Array<WindowUpdate>, ConnectionError]
345
+ # rubocop: disable Metrics/AbcSize
346
+ def self.handle_data(stream_id, data, recv_window, streams_ctx, decoder)
347
+ ctx = streams_ctx[stream_id]
348
+ return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'DATA Frame length exceeds flow-control window size') \
349
+ if recv_window.consume!(data.bytesize).negative? || ctx.recv_window.consume!(data.bytesize).negative?
350
+
351
+ ctx.content << data
352
+ if ctx.state.half_closed_remote?
353
+ obj = http_request(ctx.fragment.string, ctx.content.string, decoder)
354
+ return obj if Biryani.err?(obj)
355
+
356
+ ctx.stream.rx << obj
357
+ end
358
+
359
+ window_updates = []
360
+ window_updates << Frame::WindowUpdate.new(0, recv_window.capacity - recv_window.length) if recv_window.length < recv_window.capacity / 2
361
+ window_updates << Frame::WindowUpdate.new(stream_id, ctx.recv_window.capacity - ctx.recv_window.length) if ctx.recv_window.length < ctx.recv_window.capacity / 2
362
+ window_updates
363
+ end
364
+ # rubocop: enable Metrics/AbcSize
365
+
366
+ # @param headers [Headers]
367
+ # @param ctx [StreamContext]
368
+ # @param decoder [Decoder]
369
+ #
370
+ # @return [nil, ConnectionError]
371
+ def self.handle_headers(headers, ctx, decoder)
372
+ ctx.fragment << headers.fragment
373
+ if ctx.state.half_closed_remote?
374
+ obj = http_request(ctx.fragment.string, ctx.content.string, decoder)
375
+ return [obj] if Biryani.err?(obj)
376
+
377
+ ctx.stream.rx << obj
378
+ end
379
+
380
+ nil
381
+ end
382
+
383
+ # @param _rst_stream [RstStream]
384
+ # @param ctx [StreamContext]
385
+ def self.handle_rst_stream(_rst_stream, ctx)
386
+ ctx.state.close
375
387
  end
376
388
 
377
389
  # @param settings [Settings]
378
- # @param send_settings [Hash<Integer, Integer>]
390
+ # @param peer_settings [Hash<Integer, Integer>]
379
391
  # @param decoder [Decoder]
392
+ # @param streams_ctx [StreamsContext]
380
393
  #
381
394
  # @return [Settings]
382
- def self.handle_settings(settings, send_settings, decoder)
395
+ def self.handle_settings(settings, peer_settings, decoder, streams_ctx)
383
396
  return nil if settings.ack?
384
397
 
385
- send_settings.merge!(settings.setting)
386
- decoder.limit!(send_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE])
398
+ peer_settings.merge!(settings.setting)
399
+ new_limit = peer_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE]
400
+ decoder.limit!(new_limit)
401
+ send_initial_window_size = peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
402
+ streams_ctx.each do |ctx|
403
+ ctx.send_window.update!(send_initial_window_size)
404
+ end
405
+
387
406
  Frame::Settings.new(true, 0, {})
388
407
  end
389
408
 
390
409
  # @param ping [Ping]
391
410
  #
392
- # @return [Ping, nil, ConnectionError]
411
+ # @return [Ping, nil]
393
412
  def self.handle_ping(ping)
394
413
  Frame::Ping.new(true, 0, ping.opaque) unless ping.ack?
395
414
  end
@@ -402,21 +421,21 @@ module Biryani
402
421
  #
403
422
  # @return [nil, ConnectionError]
404
423
  def self.handle_connection_window_update(window_update, send_window)
405
- # TODO: send WINDOW_UPDATE
406
424
  send_window.increase!(window_update.window_size_increment)
425
+ return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'flow-control window exceeds 2^31-1') if send_window.length > 2**31 - 1
426
+
407
427
  nil
408
428
  end
409
429
 
410
430
  # @param window_update [WindowUpdate]
411
431
  # @param streams_ctx [StreamsContext]
412
432
  #
413
- # @return [nil, ConnectionError]
433
+ # @return [nil, StreamError]
414
434
  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
435
+ stream_id = window_update.stream_id
436
+ streams_ctx[stream_id].send_window.increase!(window_update.window_size_increment)
437
+ 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
438
 
419
- streams_ctx[window_update.stream_id].send_window.increase!(window_update.window_size_increment)
420
439
  nil
421
440
  end
422
441
 
@@ -427,7 +446,7 @@ module Biryani
427
446
  # @return [HTTPRequest, ConnectionError]
428
447
  def self.http_request(fragment, content, decoder)
429
448
  obj = decoder.decode(fragment)
430
- return obj if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
449
+ return obj if Biryani.err?(obj)
431
450
 
432
451
  fields = obj
433
452
  builder = HTTPRequestBuilder.new
@@ -438,13 +457,12 @@ module Biryani
438
457
  end
439
458
 
440
459
  # @param res [HTTPResponse]
441
- # @param stream_id [Integer]
442
460
  # @param encoder [Encoder]
443
- # @param max_frame_size [Integer]
444
461
  #
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)
462
+ # @return [String] fragment
463
+ # @return [String] data
464
+ def self.http_response(res, encoder)
465
+ HTTPResponseParser.new(res).parse(encoder)
448
466
  end
449
467
 
450
468
  # @return [Hash<Integer, Integer>]
@@ -1,30 +1,38 @@
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_datas(stream_id, data, send_window, max_frame_size)
23
+ datas += frames
24
+ if remains.empty?
25
+ @buffer.delete(stream_id)
26
+ else
27
+ @buffer[stream_id] = remains
28
+ end
20
29
 
21
- send_window.consume!(data.length)
22
- stream_ctxs[data.stream_id].send_window.consume!(data.length)
23
- datas[i] = data
30
+ len = frames.map(&:length).sum
31
+ send_window.consume!(len)
32
+ streams_ctx[stream_id].send_window.consume!(len)
24
33
  end
25
34
 
26
- @buffer = @buffer.each_with_index.filter { |_, i| datas.keys.include?(i) }.map(&:first)
27
- datas.values
35
+ datas
28
36
  end
29
37
 
30
38
  # @return [Integer]
@@ -36,7 +44,7 @@ module Biryani
36
44
  #
37
45
  # @return [Boolean]
38
46
  def has?(stream_id)
39
- @buffer.filter { |data| data.stream_id == stream_id }.any?
47
+ @buffer.key?(stream_id)
40
48
  end
41
49
  end
42
50
  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?