biryani 0.0.6 → 0.0.7

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: 2a6614c6a74a069a08281c0f3846da8512f379e347ce77e3812c7cd22f2506bc
4
- data.tar.gz: 5a0f61a32f576c0a693abeb2773490ddcbbdcc573b82dac16a6d3a3e82bf2e0e
3
+ metadata.gz: 24efc38cdd56ee46ac5e48dcbf52f7ddb73a38c73872c8b05be3d8b26aea5105
4
+ data.tar.gz: f0d0e70ef0dfc20587711637856f8a7ffe7a310d437d3e53401e81f8ef014391
5
5
  SHA512:
6
- metadata.gz: 0c0add40f15c4e1fa9110a295f9fda392aae21a6ad4dd062a0d3a31fd5dd208f8a384fbcf9009a6fc29e6a940f4fc755f9da0f3e244f68292ee41323cd10e82f
7
- data.tar.gz: 1af73e5c917c095dfcb635fd7c09548b4330114125e00df539356fb270d8f083e76c0d25262652f6781e4b8415fc3c2da91083b0af19b20ac7175a70b5c28bc1
6
+ metadata.gz: 2c3001f6c1f679690c63bcfe45baa1d85fedf764850c6fa2ca44debe71e89c286ca066715592632b8d19b03a1957f98f2a362aace2e8e171533e3595580a3b8a
7
+ data.tar.gz: 84a1b0ee18b7eaad1a7f8336ffe912038bd18c8a229c119dac83b7cb9539daee79ab25ea13286db2706e707955b23f6a7801b96c2acf09dbbdc889fc38ddc4a3
@@ -0,0 +1,46 @@
1
+ name: h2spec
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - '*'
10
+
11
+ jobs:
12
+ conformance:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ checks: write
17
+ strategy:
18
+ matrix:
19
+ ruby-version: ['4.0']
20
+ steps:
21
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22
+ - name: Set up Ruby
23
+ uses: ruby/setup-ruby@ae195bbe749a7cef685ac729197124a48305c1cb # v1.276.0
24
+ with:
25
+ ruby-version: ${{ matrix.ruby-version }}
26
+ - name: Install dependencies
27
+ run: |
28
+ gem --version
29
+ gem install bundler
30
+ bundle --version
31
+ bundle install
32
+ - name: Install h2spec
33
+ run: |
34
+ set -eu
35
+ curl -Ls https://github.com/summerwind/h2spec/releases/download/v2.6.0/h2spec_linux_amd64.tar.gz | tar xz
36
+ mkdir -p $GITHUB_WORKSPACE/bin
37
+ mv h2spec $GITHUB_WORKSPACE/bin/h2spec
38
+ chmod +x $GITHUB_WORKSPACE/bin/h2spec
39
+ echo $GITHUB_WORKSPACE/bin >> $GITHUB_PATH
40
+ - name: Run conformance test
41
+ run: bundle exec rake conformance
42
+ - uses: mikepenz/action-junit-report@a294a61c909bd8a4b563024a2faa28897fd53ebc # v6.1.0
43
+ if: success()
44
+ with:
45
+ report_paths: conformance/reports/h2spec.xml
46
+ detailed_summary: true
data/.gitignore CHANGED
@@ -12,6 +12,7 @@
12
12
  /rdoc/
13
13
  /coverage/
14
14
  /spec/reports/
15
+ /conformance/reports/
15
16
  /tmp/
16
17
  .DS_Store
17
18
  Gemfile.lock
data/.rubocop.yml CHANGED
@@ -10,6 +10,7 @@ Metrics/AbcSize:
10
10
  Metrics/BlockLength:
11
11
  Exclude:
12
12
  - 'spec/**/*'
13
+ - 'conformance/**/*'
13
14
 
14
15
  Metrics/ClassLength:
15
16
  Max: 200
@@ -6,7 +6,7 @@ RSpec.describe Server do
6
6
 
7
7
  Ractor.new(@tcpserver) do |socket|
8
8
  server = Server.new(
9
- Ractor.shareable_proc do |_req, res|
9
+ Ractor.shareable_proc do |_, res|
10
10
  res.status = 200
11
11
  res.content = 'OK'
12
12
  end
@@ -16,9 +16,16 @@ RSpec.describe Server do
16
16
  end
17
17
  end
18
18
 
19
+ let(:junit_report_file_path) do
20
+ Dir.mkdir(JUNIT_REPORT_DIR) unless Dir.exist?(JUNIT_REPORT_DIR)
21
+
22
+ "#{JUNIT_REPORT_DIR}/h2spec.xml"
23
+ end
24
+
19
25
  let(:client) do
20
26
  which('h2spec')
21
- "h2spec --port #{PORT} --verbose"
27
+
28
+ "h2spec --port #{PORT} --verbose --junit-report #{junit_report_file_path}"
22
29
  end
23
30
 
24
31
  after do
@@ -7,6 +7,7 @@ include Biryani
7
7
  # rubocop: enable Style/MixinUsage
8
8
 
9
9
  PORT = 8888
10
+ JUNIT_REPORT_DIR = "#{__dir__}/reports".freeze
10
11
 
11
12
  def which(cmd)
12
13
  o, = Open3.capture3("which #{cmd}")
@@ -118,6 +118,10 @@ module Biryani
118
118
  #
119
119
  # @return [Array<Object>, Array<ConnectionError>, Array<StreamError>] frames or errors
120
120
  def recv_dispatch(frame)
121
+ receiving_continuation_stream_id = @streams_ctx.receiving_continuation_stream_id
122
+ return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
123
+ if !receiving_continuation_stream_id.nil? && frame.stream_id != receiving_continuation_stream_id
124
+
121
125
  if frame.stream_id.zero?
122
126
  handle_connection_frame(frame)
123
127
  else
@@ -129,13 +133,8 @@ module Biryani
129
133
  #
130
134
  # @return [Array<Object>, Array<ConnectionError>, Array<StreamError>] frames or errors
131
135
  # rubocop: disable Metrics/CyclomaticComplexity
132
- # rubocop: disable Metrics/PerceivedComplexity
133
136
  def handle_connection_frame(frame)
134
- typ = frame.f_type
135
- return [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier #{format('0x%02x', stream_id)}")] \
136
- if @streams_ctx.receiving_continuation? && ([FrameType::SETTINGS, FrameType::PING, FrameType::WINDOW_UPDATE].include?(typ) || FrameType.unknown?(typ))
137
-
138
- case typ
137
+ case frame.f_type
139
138
  when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
140
139
  [ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
141
140
  when FrameType::SETTINGS
@@ -167,7 +166,6 @@ module Biryani
167
166
  end
168
167
  end
169
168
  # rubocop: enable Metrics/CyclomaticComplexity
170
- # rubocop: enable Metrics/PerceivedComplexity
171
169
 
172
170
  # @param frame [Object]
173
171
  #
@@ -253,7 +251,7 @@ module Biryani
253
251
  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
254
252
 
255
253
  ctx = streams_ctx.new_context(stream_id, send_initial_window_size, recv_initial_window_size) if ctx.nil?
256
- obj = ctx.state.transition!(recv_frame, :recv)
254
+ obj = ctx.state_transition!(recv_frame, :recv)
257
255
  return obj if Biryani.err?(obj)
258
256
 
259
257
  ctx
@@ -275,7 +273,7 @@ module Biryani
275
273
  streams_ctx.close_all
276
274
  true
277
275
  else
278
- streams_ctx[stream_id].state.transition!(send_frame, :send) unless stream_id.zero?
276
+ streams_ctx[stream_id].state_transition!(send_frame, :send) unless stream_id.zero?
279
277
  false
280
278
  end
281
279
  end
@@ -357,7 +355,7 @@ module Biryani
357
355
  obj = http_request(ctx.fragment, ctx.content, decoder)
358
356
  return obj if Biryani.err?(obj)
359
357
 
360
- ctx.stream.rx.send(obj, move: true)
358
+ ctx << obj
361
359
  end
362
360
 
363
361
  window_updates = []
@@ -378,7 +376,7 @@ module Biryani
378
376
  obj = http_request(ctx.fragment, ctx.content, decoder)
379
377
  return obj if Biryani.err?(obj)
380
378
 
381
- ctx.stream.rx.send(obj, move: true)
379
+ ctx << obj
382
380
  end
383
381
 
384
382
  nil
@@ -387,7 +385,7 @@ module Biryani
387
385
  # @param _rst_stream [RstStream]
388
386
  # @param ctx [StreamContext]
389
387
  def self.handle_rst_stream(_rst_stream, ctx)
390
- ctx.state.close
388
+ ctx.close
391
389
  end
392
390
 
393
391
  # @param settings [Settings]
@@ -4,7 +4,7 @@ module Biryani
4
4
 
5
5
  # @param method [String]
6
6
  # @param uri [URI]
7
- # @param fields [Hash<String, String>]
7
+ # @param fields [Hash<String, Array<String>>]
8
8
  # @param content [String]
9
9
  def initialize(method, uri, fields, content)
10
10
  @method = method
@@ -12,6 +12,13 @@ module Biryani
12
12
  @fields = fields
13
13
  @content = content
14
14
  end
15
+
16
+ # @return [Array<String>, nil]
17
+ def trailers
18
+ # https://datatracker.ietf.org/doc/html/rfc9110#section-6.6.2-4
19
+ keys = (@fields['trailer'] || []).flat_map { |x| x.split(',').map(&:strip) }
20
+ @fields.slice(*keys)
21
+ end
15
22
  end
16
23
 
17
24
  class HTTPRequestBuilder
@@ -26,6 +33,7 @@ module Biryani
26
33
  # @param value [String]
27
34
  #
28
35
  # @return [nil, ConnectioError]
36
+ # rubocop: disable Metrics/AbcSize
29
37
  # rubocop: disable Metrics/CyclomaticComplexity
30
38
  # rubocop: disable Metrics/PerceivedComplexity
31
39
  def field(name, value)
@@ -35,17 +43,14 @@ module Biryani
35
43
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'duplicated pseudo-header fields') if PSEUDO_HEADER_FIELDS.include?(name) && @h.key?(name)
36
44
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid `#{name}` field") if PSEUDO_HEADER_FIELDS.include?(name) && value.empty?
37
45
  return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'connection-specific field is forbidden') if name == 'connection'
46
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, '`TE` field has a value other than `trailers`') if name == 'te' && value != 'trailers'
38
47
 
39
- # TODO: trailers
40
-
41
- if name == 'cookie' && @h.key?('cookie')
42
- @h[name] << "; #{value}"
43
- else
44
- @h[name] = value
45
- end
48
+ @h[name] = [] unless @h.key?(name)
49
+ @h[name] << value
46
50
 
47
51
  nil
48
52
  end
53
+ # rubocop: enable Metrics/AbcSize
49
54
  # rubocop: enable Metrics/CyclomaticComplexity
50
55
  # rubocop: enable Metrics/PerceivedComplexity
51
56
 
@@ -69,16 +74,21 @@ module Biryani
69
74
  self.class.http_request(h, s)
70
75
  end
71
76
 
72
- # @param fields [Hash<String, String>]
77
+ # @param h [Hash<String, Array<String>>]
73
78
  # @param s [String]
74
79
  #
75
80
  # @return [HTTPRequest, ConnectionError]
76
- def self.http_request(fields, s)
77
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'missing pseudo-header fields') unless PSEUDO_HEADER_FIELDS.all? { |x| fields.key?(x) }
78
- return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid content-length') if fields.key?('content-length') && !s.empty? && s.length != fields['content-length'].to_i
81
+ def self.http_request(h, s)
82
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'missing pseudo-header fields') unless PSEUDO_HEADER_FIELDS.all? { |x| h.key?(x) }
83
+ return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid content-length') if h.key?('content-length') && !s.empty? && s.length != h['content-length'].to_i
79
84
 
80
- uri = URI("#{fields[':scheme']}://#{fields[':authority']}#{fields[':path']}")
81
- HTTPRequest.new(fields[':method'], uri, fields.reject { |name, _| PSEUDO_HEADER_FIELDS.include?(name) }, s)
85
+ scheme = h[':scheme'][0]
86
+ domain = h[':authority'][0]
87
+ path = h[':path'][0]
88
+ uri = URI("#{scheme}://#{domain}#{path}")
89
+ method = h[':method'][0]
90
+ h['cookie'] = [h['cookie'].join('; ')] if h.key?('cookie')
91
+ HTTPRequest.new(method, uri, h, s)
82
92
  end
83
93
  end
84
94
  end
data/lib/biryani/state.rb CHANGED
@@ -56,58 +56,82 @@ module Biryani
56
56
  # rubocop: disable Metrics/PerceivedComplexity
57
57
  def self.next(state, frame, direction)
58
58
  typ = frame.f_type
59
- case [state, typ, direction]
59
+ case [state, direction, typ]
60
60
  # idle
61
- in [:idle, FrameType::HEADERS, :recv] if frame.end_stream? && frame.end_headers?
61
+ in [:idle, :send, FrameType::PUSH_PROMISE]
62
+ :reserved_local
63
+ in [:idle, :recv, FrameType::HEADERS] if frame.end_stream? && frame.end_headers?
62
64
  :half_closed_remote
63
- in [:idle, FrameType::HEADERS, :recv] if frame.end_headers?
65
+ in [:idle, :recv, FrameType::HEADERS] if frame.end_headers?
64
66
  :receiving_data
65
- in [:idle, FrameType::HEADERS, :recv] if frame.end_stream?
67
+ in [:idle, :recv, FrameType::HEADERS] if frame.end_stream?
66
68
  :receiving_continuation
67
- in [:idle, FrameType::HEADERS, :recv]
69
+ in [:idle, :recv, FrameType::HEADERS]
68
70
  :receiving_continuation_and_data
69
- in [:idle, FrameType::PRIORITY, :recv]
71
+ in [:idle, :recv, FrameType::PRIORITY]
70
72
  state
71
- in [:idle, FrameType::PUSH_PROMISE, :send]
72
- :reserved_local
73
- in [:idle, _, _]
74
- unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
75
73
 
76
74
  # receiving_continuation_and_data
77
- in [:receiving_continuation_and_data, FrameType::RST_STREAM, _]
78
- :closed
79
- in [:receiving_continuation_and_data, FrameType::WINDOW_UPDATE, :recv]
75
+ in [:receiving_continuation_and_data, :recv, FrameType::WINDOW_UPDATE]
80
76
  state
81
- in [:receiving_continuation_and_data, FrameType::CONTINUATION, :recv] if frame.end_headers?
77
+ in [:receiving_continuation_and_data, :recv, FrameType::CONTINUATION] if frame.end_headers?
82
78
  :receiving_data
83
- in [:receiving_continuation_and_data, FrameType::CONTINUATION, :recv]
79
+ in [:receiving_continuation_and_data, :recv, FrameType::CONTINUATION]
84
80
  state
85
- in [:receiving_continuation_and_data, _, _]
86
- unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
81
+ in [:receiving_continuation_and_data, _, FrameType::RST_STREAM]
82
+ :closed
87
83
 
88
84
  # receiving_continuation
89
- in [:receiving_continuation, FrameType::RST_STREAM, _]
90
- :closed
91
- in [:receiving_continuation, FrameType::WINDOW_UPDATE, :recv]
85
+ in [:receiving_continuation, :recv, FrameType::DATA] if frame.end_stream?
86
+ :half_closed_remote
87
+ in [:receiving_continuation, :recv, FrameType::DATA]
88
+ :receiving_data
89
+ in [:receiving_continuation, :recv, FrameType::WINDOW_UPDATE]
92
90
  state
93
- in [:receiving_continuation, FrameType::CONTINUATION, :recv] if frame.end_headers?
91
+ in [:receiving_continuation, :recv, FrameType::CONTINUATION] if frame.end_headers?
94
92
  :half_closed_remote
95
- in [:receiving_continuation, FrameType::CONTINUATION, :recv]
93
+ in [:receiving_continuation, :recv, FrameType::CONTINUATION]
96
94
  state
97
- in [:receiving_continuation, _, _]
98
- unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
95
+ in [:receiving_continuation, _, FrameType::RST_STREAM]
96
+ :closed
99
97
 
100
98
  # receiving_data
101
- in [:receiving_data, FrameType::DATA, :recv] if frame.end_stream?
99
+ in [:receiving_data, :recv, FrameType::DATA] if frame.end_stream?
102
100
  :half_closed_remote
103
- in [:receiving_data, FrameType::DATA, :recv]
101
+ in [:receiving_data, :recv, FrameType::DATA]
104
102
  state
105
- in [:receiving_data, FrameType::RST_STREAM, _]
103
+ in [:receiving_data, :recv, FrameType::HEADERS] if !frame.end_stream?
104
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
105
+ in [:receiving_data, :recv, FrameType::HEADERS] if frame.end_headers?
106
+ :half_closed_remote
107
+ in [:receiving_data, :recv, FrameType::HEADERS]
108
+ :receiving_trailer_continuation
109
+ in [:receiving_data, :recv, FrameType::PRIORITY]
110
+ state
111
+ in [:receiving_data, _, FrameType::RST_STREAM]
106
112
  :closed
107
- in [:receiving_data, FrameType::WINDOW_UPDATE, _]
113
+ in [:receiving_data, _, FrameType::WINDOW_UPDATE]
108
114
  state
109
- in [:receiving_data, _, _]
115
+
116
+ # receiving_trailer_headers
117
+ in [:receiving_trailer_headers, :recv, FrameType::HEADERS] if !frame.end_stream?
110
118
  unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
119
+ in [:receiving_trailer_headers, :recv, FrameType::HEADERS] if frame.end_headers?
120
+ :half_closed_remote
121
+ in [:receiving_trailer_headers, :recv, FrameType::HEADERS]
122
+ :receiving_trailer_continuation
123
+ in [:receiving_trailer_headers, :recv, FrameType::WINDOW_UPDATE]
124
+ state
125
+
126
+ # receiving_trailer_continuation
127
+ in [:receiving_trailer_continuation, :recv, FrameType::WINDOW_UPDATE]
128
+ state
129
+ in [:receiving_trailer_continuation, :recv, FrameType::CONTINUATION] if frame.end_headers?
130
+ :half_closed_remote
131
+ in [:receiving_trailer_continuation, :recv, FrameType::CONTINUATION]
132
+ :receiving_trailer_continuation
133
+ in [:receiving_trailer_continuation, _, FrameType::RST_STREAM]
134
+ :closed
111
135
 
112
136
  # reserved_remote
113
137
  in [:reserved_remote, _, _]
@@ -120,81 +144,75 @@ module Biryani
120
144
  state
121
145
 
122
146
  # half_closed_remote
123
- in [:half_closed_remote, FrameType::HEADERS, :send] if frame.end_stream? && frame.end_headers?
147
+ in [:half_closed_remote, :send, FrameType::HEADERS] if frame.end_stream? && frame.end_headers?
124
148
  :closed
125
- in [:half_closed_remote, FrameType::HEADERS, :send] if frame.end_headers?
149
+ in [:half_closed_remote, :send, FrameType::HEADERS] if frame.end_headers?
126
150
  :sending_data
127
- in [:half_closed_remote, FrameType::HEADERS, :send] if frame.end_stream?
151
+ in [:half_closed_remote, :send, FrameType::HEADERS] if frame.end_stream?
128
152
  :sending_continuation
129
- in [:half_closed_remote, FrameType::HEADERS, :send]
130
- :sending_continuation_data
131
- in [:half_closed_remote, FrameType::PRIORITY, :recv]
153
+ in [:half_closed_remote, :send, FrameType::HEADERS]
154
+ :sending_continuation_and_data
155
+ in [:half_closed_remote, :recv, FrameType::PRIORITY]
132
156
  state
133
- in [:half_closed_remote, FrameType::RST_STREAM, _]
157
+ in [:half_closed_remote, _, FrameType::RST_STREAM]
134
158
  :closed
135
- in [:half_closed_remote, FrameType::WINDOW_UPDATE, _]
159
+ in [:half_closed_remote, _, FrameType::WINDOW_UPDATE]
136
160
  state
137
- in [:half_closed_remote, _, :recv]
138
- unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
139
- in [:half_closed_local, _, :send]
140
- unreachable(state, typ, direction)
141
161
 
142
- # sending_continuation_data
143
- in [:sending_continuation_data, FrameType::RST_STREAM, :send]
144
- :closed
145
- in [:sending_continuation_data, FrameType::WINDOW_UPDATE, :recv]
146
- state
147
- in [:sending_continuation_data, FrameType::CONTINUATION, :send] if frame.end_headers?
162
+ # sending_continuation_and_data
163
+ in [:sending_continuation_and_data, :send, FrameType::CONTINUATION] if frame.end_headers?
148
164
  :sending_data
149
- in [:sending_continuation_data, FrameType::CONTINUATION, :send]
165
+ in [:sending_continuation_and_data, :send, FrameType::CONTINUATION]
150
166
  state
151
- in [:sending_continuation_data, _, :send]
167
+ in [:sending_continuation_and_data, :send, _]
152
168
  unreachable(state, typ, direction)
153
- in [:sending_continuation_data, _, :recv]
154
- unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
169
+ in [:sending_continuation_and_data, :recv, FrameType::PRIORITY]
170
+ state
171
+ in [:sending_continuation_and_data, :recv, FrameType::WINDOW_UPDATE]
172
+ state
173
+ in [:sending_continuation_and_data, _, FrameType::RST_STREAM]
174
+ :closed
155
175
 
156
176
  # sending_continuation
157
- in [:sending_continuation, FrameType::RST_STREAM, :send]
177
+ in [:sending_continuation, :send, FrameType::CONTINUATION] if frame.end_headers?
158
178
  :closed
159
- in [:sending_continuation, FrameType::WINDOW_UPDATE, :recv]
179
+ in [:sending_continuation, :send, FrameType::CONTINUATION]
160
180
  state
161
- in [:sending_continuation, FrameType::CONTINUATION, :send] if frame.end_headers?
162
- :closed
163
- in [:sending_continuation, FrameType::CONTINUATION, :send]
164
- state
165
- in [:sending_continuation, _, :send]
181
+ in [:sending_continuation, :send, _]
166
182
  unreachable(state, typ, direction)
167
- in [:sending_continuation, _, :recv]
168
- unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
183
+ in [:sending_continuation, :recv, FrameType::PRIORITY]
184
+ state
185
+ in [:sending_continuation, :recv, FrameType::WINDOW_UPDATE]
186
+ state
187
+ in [:sending_continuation, _, FrameType::RST_STREAM]
188
+ :closed
169
189
 
170
190
  # sending_data
171
- in [:sending_data, FrameType::DATA, :send] if frame.end_stream?
191
+ in [:sending_data, :send, FrameType::DATA] if frame.end_stream?
172
192
  :closed
173
- in [:sending_data, FrameType::WINDOW_UPDATE, :recv]
193
+ in [:sending_data, :send, FrameType::DATA]
194
+ state
195
+ in [:sending_data, :send, _]
196
+ unreachable(state, typ, direction)
197
+ in [:sending_data, :recv, FrameType::PRIORITY]
174
198
  state
175
- in [:sending_data, FrameType::DATA, :send]
199
+ in [:sending_data, :recv, FrameType::WINDOW_UPDATE]
176
200
  state
177
- in [:sending_data, FrameType::RST_STREAM, :send]
201
+ in [:sending_data, _, FrameType::RST_STREAM]
178
202
  :closed
179
- in [:sending_continuation, _, :send]
180
- unreachable(state, typ, direction)
181
- in [:sending_continuation, _, :recv]
182
- unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
183
203
 
184
204
  # closed
185
- in [:closed, FrameType::PRIORITY, :recv]
205
+ in [:closed, :send, _]
206
+ unreachable(state, typ, direction)
207
+ in [:closed, :recv, FrameType::PRIORITY]
186
208
  state
187
- in [:closed, FrameType::RST_STREAM, :recv]
209
+ in [:closed, _, FrameType::RST_STREAM]
188
210
  state
189
- in [:closed, _, :send]
190
- unreachable(state, typ, direction)
191
- in [:closed, _, :recv]
192
- unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
193
211
 
194
212
  # other
195
- in [_, _, :send]
213
+ in [_, :send, _]
196
214
  unreachable(state, typ, direction)
197
- in [_, _, :recv]
215
+ in [_, :recv, _]
198
216
  unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
199
217
  end
200
218
  end
@@ -47,9 +47,9 @@ module Biryani
47
47
  @h.values.filter(&:active?).length
48
48
  end
49
49
 
50
- # @return [Boolean]
51
- def receiving_continuation?
52
- @h.values.any?(&:receiving_continuation?)
50
+ # @return [Integer, nil]
51
+ def receiving_continuation_stream_id
52
+ @h.find { |_, ctx| ctx.receiving_continuation? }&.first
53
53
  end
54
54
 
55
55
  # @return [Array<Integer>]
@@ -95,13 +95,13 @@ module Biryani
95
95
  def close_all
96
96
  each do |ctx|
97
97
  ctx.tx.close
98
- ctx.state.close
98
+ ctx.close
99
99
  end
100
100
  end
101
101
  end
102
102
 
103
103
  class StreamContext
104
- attr_accessor :stream, :tx, :send_window, :recv_window, :fragment, :content, :state
104
+ attr_accessor :tx, :send_window, :recv_window, :fragment, :content
105
105
 
106
106
  # @param stream_id [Integer]
107
107
  # @param send_initial_window_size [Integer]
@@ -117,6 +117,21 @@ module Biryani
117
117
  @state = State.new
118
118
  end
119
119
 
120
+ # @param req [HTTPRequest]
121
+ def <<(req)
122
+ @stream.rx.send(req, move: true)
123
+ end
124
+
125
+ def close
126
+ @state.close
127
+ end
128
+
129
+ # @param frame [Object]
130
+ # @param direction [:send, :recv]
131
+ def state_transition!(frame, direction)
132
+ @state.transition!(frame, direction)
133
+ end
134
+
120
135
  # @return [Boolean]
121
136
  def closed?
122
137
  @state.closed?
@@ -1,3 +1,3 @@
1
1
  module Biryani
2
- VERSION = '0.0.6'.freeze
2
+ VERSION = '0.0.7'.freeze
3
3
  end
@@ -22,7 +22,7 @@ RSpec.describe Connection do
22
22
  streams_ctx
23
23
  end
24
24
  it 'should send' do
25
- streams_ctx1[2].state.transition!(headers1, :recv)
25
+ streams_ctx1[2].state_transition!(headers1, :recv)
26
26
  Connection.send_headers(io, 2, "\x88".b, false, 16_384, streams_ctx1)
27
27
  Connection.send_data(io, 2, 'Hello, world!'.b, send_window1, 16_384, streams_ctx1, data_buffer)
28
28
  expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq "\x00\x00\x01\x01\x04\x00\x00\x00\x02\x88\x00\x00\x0d\x00\x01\x00\x00\x00\x02Hello, world!".b
@@ -45,7 +45,7 @@ RSpec.describe Connection do
45
45
  streams_ctx
46
46
  end
47
47
  it 'should send' do
48
- streams_ctx2[1].state.transition!(headers2, :recv)
48
+ streams_ctx2[1].state_transition!(headers2, :recv)
49
49
  Connection.send_headers(io, 1, "\x88".b, true, 16_384, streams_ctx2)
50
50
  expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq "\x00\x00\x01\x01\x05\x00\x00\x00\x01\x88".b
51
51
  expect(send_window2.length).to eq 65_535
@@ -68,7 +68,7 @@ RSpec.describe Connection do
68
68
  streams_ctx
69
69
  end
70
70
  it 'should send' do
71
- streams_ctx3[2].state.transition!(headers3, :recv)
71
+ streams_ctx3[2].state_transition!(headers3, :recv)
72
72
  Connection.send_data(io, 2, 'Hello, world!'.b, send_window3, 16_384, streams_ctx3, data_buffer)
73
73
  expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq ''.b
74
74
  expect(send_window3.length).to eq 65_535
@@ -92,7 +92,7 @@ RSpec.describe Connection do
92
92
  streams_ctx
93
93
  end
94
94
  it 'should send' do
95
- streams_ctx4[2].state.transition!(headers4, :recv)
95
+ streams_ctx4[2].state_transition!(headers4, :recv)
96
96
  Connection.send_data(io, 2, 'Hello, world!'.b, send_window4, 16_384, streams_ctx4, data_buffer)
97
97
  expect(io.string.force_encoding(Encoding::ASCII_8BIT)).to eq ''.b
98
98
  expect(send_window4.length).to eq 0
@@ -13,14 +13,14 @@ RSpec.describe Connection do
13
13
  Frame::Headers.new(true, true, 2, nil, nil, 'this is dummy', nil)
14
14
  end
15
15
  it 'should transition' do
16
- streams_ctx[2].state.transition!(headers, :recv)
16
+ streams_ctx[2].state_transition!(headers, :recv)
17
17
  Connection.transition_stream_state_send(headers, streams_ctx)
18
18
  expect(streams_ctx.length).to eq 2
19
19
  end
20
20
 
21
21
  it 'should transition' do
22
- streams_ctx[1].state.transition!(headers, :recv)
23
- streams_ctx[2].state.transition!(headers, :recv)
22
+ streams_ctx[1].state_transition!(headers, :recv)
23
+ streams_ctx[2].state_transition!(headers, :recv)
24
24
  streams_ctx.close_all
25
25
  expect { streams_ctx[1].tx << nil }.to raise_error Ractor::ClosedError
26
26
  expect { streams_ctx[2].tx << nil }.to raise_error Ractor::ClosedError
@@ -30,7 +30,7 @@ RSpec.describe Connection do
30
30
  Frame::RstStream.new(2, 0)
31
31
  end
32
32
  it 'should transition' do
33
- streams_ctx[2].state.transition!(headers, :recv)
33
+ streams_ctx[2].state_transition!(headers, :recv)
34
34
  Connection.transition_stream_state_send(rst_stream, streams_ctx)
35
35
  expect { streams_ctx[1].tx << nil }.to_not raise_error
36
36
  expect { streams_ctx[2].tx << nil }.to_not raise_error
@@ -1,5 +1,22 @@
1
1
  require_relative 'spec_helper'
2
2
 
3
+ RSpec.describe HTTPRequest do
4
+ context 'trailers' do
5
+ it 'should be empty' do
6
+ expect(HTTPRequest.new('get', 'http://localhost:8888/', {}, '').trailers.empty?).to eq true
7
+ end
8
+
9
+ let(:req) do
10
+ HTTPRequest.new('get', 'http://localhost:8888/', { 'trailer' => %w[a b], 'a' => ['1'], 'b' => ['2'], 'c' => ['3'] }, '')
11
+ end
12
+ it 'should return' do
13
+ expect(req.trailers['a']).to eq ['1']
14
+ expect(req.trailers['b']).to eq ['2']
15
+ expect(req.trailers['c']).to eq nil
16
+ end
17
+ end
18
+ end
19
+
3
20
  RSpec.describe HTTPRequestBuilder do
4
21
  context 'field' do
5
22
  let(:builder) do
@@ -39,7 +56,7 @@ RSpec.describe HTTPRequestBuilder do
39
56
  builder.build('')
40
57
  end
41
58
  it 'should field' do
42
- expect(request.fields['cookie']).to eq 'a=1; b=2; c=3; d=4'
59
+ expect(request.fields['cookie']).to eq ['a=1; b=2; c=3; d=4']
43
60
  end
44
61
  end
45
62
  end
@@ -48,7 +48,7 @@ RSpec.describe StreamsContext do
48
48
  streams_ctx = StreamsContext.new(do_nothing_proc)
49
49
  streams_ctx.new_context(1, 65_535, 65_535)
50
50
  streams_ctx.new_context(2, 65_535, 65_535)
51
- streams_ctx[2].state.close
51
+ streams_ctx[2].close
52
52
  streams_ctx
53
53
  end
54
54
  let(:data_buffer2) do
@@ -63,7 +63,7 @@ RSpec.describe StreamsContext do
63
63
  streams_ctx = StreamsContext.new(do_nothing_proc)
64
64
  streams_ctx.new_context(1, 65_535, 65_535)
65
65
  streams_ctx.new_context(2, 65_535, 65_535)
66
- streams_ctx[2].state.close
66
+ streams_ctx[2].close
67
67
  streams_ctx
68
68
  end
69
69
  let(:data_buffer3) do
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.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - thekuwayama
@@ -31,6 +31,7 @@ extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
33
  - ".github/workflows/ci.yml"
34
+ - ".github/workflows/conformance.yml"
34
35
  - ".gitignore"
35
36
  - ".rubocop.yml"
36
37
  - ".ruby-version"
@@ -111,7 +112,7 @@ files:
111
112
  - spec/hpack/huffman_spec.rb
112
113
  - spec/hpack/integer_spec.rb
113
114
  - spec/hpack/string_spec.rb
114
- - spec/http_request_builder_spec.rb
115
+ - spec/http_request_spec.rb
115
116
  - spec/http_response_spec.rb
116
117
  - spec/spec_helper.rb
117
118
  - spec/streams_context_spec.rb
@@ -167,7 +168,7 @@ test_files:
167
168
  - spec/hpack/huffman_spec.rb
168
169
  - spec/hpack/integer_spec.rb
169
170
  - spec/hpack/string_spec.rb
170
- - spec/http_request_builder_spec.rb
171
+ - spec/http_request_spec.rb
171
172
  - spec/http_response_spec.rb
172
173
  - spec/spec_helper.rb
173
174
  - spec/streams_context_spec.rb