biryani 0.0.4 → 0.0.6
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 +4 -4
- data/.ruby-version +1 -1
- data/Rakefile +7 -0
- data/conformance/server_spec.rb +31 -0
- data/conformance/spec_helper.rb +14 -0
- data/example/echo.rb +0 -1
- data/example/raise_error.rb +19 -0
- data/lib/biryani/connection.rb +23 -18
- data/lib/biryani/data_buffer.rb +1 -1
- data/lib/biryani/error.rb +8 -0
- data/lib/biryani/frame/continuation.rb +4 -7
- data/lib/biryani/frame/data.rb +9 -12
- data/lib/biryani/frame/goaway.rb +6 -6
- data/lib/biryani/frame/headers.rb +25 -18
- data/lib/biryani/frame/ping.rb +5 -7
- data/lib/biryani/frame/priority.rb +6 -5
- data/lib/biryani/frame/push_promise.rb +14 -15
- data/lib/biryani/frame/rst_stream.rb +5 -5
- data/lib/biryani/frame/settings.rb +5 -5
- data/lib/biryani/frame/unknown.rb +1 -15
- data/lib/biryani/frame/window_update.rb +5 -5
- data/lib/biryani/frame.rb +10 -4
- data/lib/biryani/hpack/field.rb +42 -42
- data/lib/biryani/hpack/fields.rb +2 -1
- data/lib/biryani/hpack/huffman.rb +290 -21
- data/lib/biryani/hpack/integer.rb +10 -6
- data/lib/biryani/hpack/string.rb +6 -6
- data/lib/biryani/http_request.rb +3 -2
- data/lib/biryani/http_response.rb +23 -2
- data/lib/biryani/server.rb +1 -1
- data/lib/biryani/state.rb +14 -7
- data/lib/biryani/stream.rb +10 -3
- data/lib/biryani/streams_context.rb +12 -6
- data/lib/biryani/version.rb +1 -1
- data/lib/biryani.rb +1 -1
- data/spec/connection/handle_data_spec.rb +2 -2
- data/spec/connection/handle_headers_spec.rb +1 -1
- data/spec/connection/{transition_state_send_spec.rb → transition_stream_state_send_spec.rb} +3 -3
- data/spec/frame/continuation_spec.rb +2 -2
- data/spec/frame/data_spec.rb +1 -1
- data/spec/frame/goaway_spec.rb +1 -1
- data/spec/frame/headers_spec.rb +2 -2
- data/spec/frame/ping_spec.rb +1 -1
- data/spec/frame/priority_spec.rb +1 -1
- data/spec/frame/push_promise_spec.rb +1 -1
- data/spec/frame/rst_stream_spec.rb +1 -1
- data/spec/frame/settings_spec.rb +1 -1
- data/spec/frame/window_update_spec.rb +1 -1
- data/spec/hpack/field_spec.rb +10 -9
- data/spec/hpack/huffman_spec.rb +4 -4
- data/spec/hpack/integer_spec.rb +5 -5
- data/spec/hpack/string_spec.rb +2 -2
- data/spec/http_response_spec.rb +12 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a6614c6a74a069a08281c0f3846da8512f379e347ce77e3812c7cd22f2506bc
|
|
4
|
+
data.tar.gz: 5a0f61a32f576c0a693abeb2773490ddcbbdcc573b82dac16a6d3a3e82bf2e0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c0add40f15c4e1fa9110a295f9fda392aae21a6ad4dd062a0d3a31fd5dd208f8a384fbcf9009a6fc29e6a940f4fc755f9da0f3e244f68292ee41323cd10e82f
|
|
7
|
+
data.tar.gz: 1af73e5c917c095dfcb635fd7c09548b4330114125e00df539356fb270d8f083e76c0d25262652f6781e4b8415fc3c2da91083b0af19b20ac7175a70b5c28bc1
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.1
|
data/Rakefile
CHANGED
|
@@ -5,4 +5,11 @@ require 'rubocop/rake_task'
|
|
|
5
5
|
RuboCop::RakeTask.new
|
|
6
6
|
RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
|
|
8
|
+
desc 'conformance test using h2spec'
|
|
9
|
+
RSpec::Core::RakeTask.new(:conformance) do |t|
|
|
10
|
+
t.pattern = 'conformance/server_spec.rb'
|
|
11
|
+
t.rspec_opts = %w[--out /dev/null]
|
|
12
|
+
t.verbose = false
|
|
13
|
+
end
|
|
14
|
+
|
|
8
15
|
task default: %i[rubocop spec]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require_relative 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Server do
|
|
4
|
+
before do
|
|
5
|
+
@tcpserver = TCPServer.open(PORT)
|
|
6
|
+
|
|
7
|
+
Ractor.new(@tcpserver) do |socket|
|
|
8
|
+
server = Server.new(
|
|
9
|
+
Ractor.shareable_proc do |_req, res|
|
|
10
|
+
res.status = 200
|
|
11
|
+
res.content = 'OK'
|
|
12
|
+
end
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
server.run(socket)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let(:client) do
|
|
20
|
+
which('h2spec')
|
|
21
|
+
"h2spec --port #{PORT} --verbose"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
after do
|
|
25
|
+
@tcpserver.close
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'should run' do
|
|
29
|
+
system(client)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'socket'
|
|
3
|
+
require 'biryani'
|
|
4
|
+
|
|
5
|
+
# rubocop: disable Style/MixinUsage
|
|
6
|
+
include Biryani
|
|
7
|
+
# rubocop: enable Style/MixinUsage
|
|
8
|
+
|
|
9
|
+
PORT = 8888
|
|
10
|
+
|
|
11
|
+
def which(cmd)
|
|
12
|
+
o, = Open3.capture3("which #{cmd}")
|
|
13
|
+
warn "conformance task require `#{cmd}`. Install `#{cmd}`." if o.empty?
|
|
14
|
+
end
|
data/example/echo.rb
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH << "#{__dir__}/../lib"
|
|
4
|
+
|
|
5
|
+
require 'socket'
|
|
6
|
+
require 'biryani'
|
|
7
|
+
|
|
8
|
+
port = ARGV[0] || 8888
|
|
9
|
+
socket = TCPServer.new(port)
|
|
10
|
+
|
|
11
|
+
server = Biryani::Server.new(
|
|
12
|
+
Ractor.shareable_proc do
|
|
13
|
+
raise 'error'
|
|
14
|
+
end
|
|
15
|
+
)
|
|
16
|
+
server.run(socket)
|
|
17
|
+
|
|
18
|
+
# $ bundle exec ruby example/raise_error.rb
|
|
19
|
+
# $ curl -v --http2-prior-knowledge http://localhost:8888
|
data/lib/biryani/connection.rb
CHANGED
|
@@ -44,10 +44,11 @@ module Biryani
|
|
|
44
44
|
|
|
45
45
|
recv_loop(io.clone)
|
|
46
46
|
send_loop(io)
|
|
47
|
-
rescue StandardError
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
puts e.backtrace
|
|
48
49
|
self.class.do_send(io, Frame::Goaway.new(0, @streams_ctx.last_stream_id, ErrorCode::INTERNAL_ERROR, 'internal error'), true)
|
|
49
50
|
ensure
|
|
50
|
-
io
|
|
51
|
+
io.close_write
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
# @param io [IO]
|
|
@@ -57,7 +58,7 @@ module Biryani
|
|
|
57
58
|
obj = Frame.read(io_)
|
|
58
59
|
break if obj.nil?
|
|
59
60
|
|
|
60
|
-
sock_
|
|
61
|
+
sock_.send(obj, move: true)
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
end
|
|
@@ -71,15 +72,14 @@ module Biryani
|
|
|
71
72
|
def send_loop(io)
|
|
72
73
|
loop do
|
|
73
74
|
ports = @streams_ctx.txs
|
|
74
|
-
ports
|
|
75
|
-
next if ports.empty?
|
|
75
|
+
break if ports.empty? && @sock.closed?
|
|
76
76
|
|
|
77
|
-
port, obj = Ractor.select(*ports)
|
|
77
|
+
port, obj = Ractor.select(@sock, *ports)
|
|
78
78
|
if port == @sock
|
|
79
79
|
if Biryani.err?(obj)
|
|
80
80
|
reply_frame = Biryani.unwrap(obj, @streams_ctx.last_stream_id)
|
|
81
81
|
self.class.do_send(io, reply_frame, true)
|
|
82
|
-
close if self.class.
|
|
82
|
+
close if self.class.transition_stream_state_send(reply_frame, @streams_ctx)
|
|
83
83
|
elsif obj.length > @settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]
|
|
84
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)
|
|
85
85
|
close
|
|
@@ -93,7 +93,7 @@ module Biryani
|
|
|
93
93
|
@streams_ctx[reply_frame.stream_id].recv_window.increase!(reply_frame.window_size_increment)
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
close if self.class.
|
|
96
|
+
close if self.class.transition_stream_state_send(reply_frame, @streams_ctx)
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
else
|
|
@@ -129,8 +129,12 @@ module Biryani
|
|
|
129
129
|
#
|
|
130
130
|
# @return [Array<Object>, Array<ConnectionError>, Array<StreamError>] frames or errors
|
|
131
131
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
132
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
|
132
133
|
def handle_connection_frame(frame)
|
|
133
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
|
+
|
|
134
138
|
case typ
|
|
135
139
|
when FrameType::DATA, FrameType::HEADERS, FrameType::PRIORITY, FrameType::RST_STREAM, FrameType::PUSH_PROMISE, FrameType::CONTINUATION
|
|
136
140
|
[ConnectionError.new(ErrorCode::PROTOCOL_ERROR, "invalid frame type #{format('0x%02x', typ)} for stream identifier 0x00")]
|
|
@@ -163,6 +167,7 @@ module Biryani
|
|
|
163
167
|
end
|
|
164
168
|
end
|
|
165
169
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
170
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
|
166
171
|
|
|
167
172
|
# @param frame [Object]
|
|
168
173
|
#
|
|
@@ -179,7 +184,7 @@ module Biryani
|
|
|
179
184
|
max_streams = @peer_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS]
|
|
180
185
|
send_initial_window_size = @peer_settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
181
186
|
recv_initial_window_size = @settings[SettingsID::SETTINGS_INITIAL_WINDOW_SIZE]
|
|
182
|
-
obj = self.class.
|
|
187
|
+
obj = self.class.transition_stream_state_recv(frame, @streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size)
|
|
183
188
|
return [obj] if Biryani.err?(obj)
|
|
184
189
|
|
|
185
190
|
ctx = obj
|
|
@@ -241,7 +246,7 @@ module Biryani
|
|
|
241
246
|
# @return [StreamContext, StreamError, ConnectionError]
|
|
242
247
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
243
248
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
244
|
-
def self.
|
|
249
|
+
def self.transition_stream_state_recv(recv_frame, streams_ctx, stream_id, max_streams, send_initial_window_size, recv_initial_window_size)
|
|
245
250
|
ctx = streams_ctx[stream_id]
|
|
246
251
|
return StreamError.new(ErrorCode::PROTOCOL_ERROR, stream_id, 'exceed max concurrent streams') if ctx.nil? && streams_ctx.count_active + 1 > max_streams
|
|
247
252
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'even-numbered stream identifier') if ctx.nil? && stream_id.even?
|
|
@@ -260,7 +265,7 @@ module Biryani
|
|
|
260
265
|
# @param streams_ctx [StreamsContext]
|
|
261
266
|
#
|
|
262
267
|
# @return [Boolean] should close connection?
|
|
263
|
-
def self.
|
|
268
|
+
def self.transition_stream_state_send(send_frame, streams_ctx)
|
|
264
269
|
stream_id = send_frame.stream_id
|
|
265
270
|
typ = send_frame.f_type
|
|
266
271
|
case typ
|
|
@@ -297,7 +302,7 @@ module Biryani
|
|
|
297
302
|
do_send(io, frame, false)
|
|
298
303
|
send_window.consume!(frame.length)
|
|
299
304
|
streams_ctx[stream_id].send_window.consume!(frame.length)
|
|
300
|
-
|
|
305
|
+
transition_stream_state_send(frame, streams_ctx)
|
|
301
306
|
end
|
|
302
307
|
|
|
303
308
|
data_buffer.store(stream_id, remains) unless remains.empty?
|
|
@@ -322,7 +327,7 @@ module Biryani
|
|
|
322
327
|
|
|
323
328
|
frames.each do |frame|
|
|
324
329
|
do_send(io, frame, false)
|
|
325
|
-
|
|
330
|
+
transition_stream_state_send(frame, streams_ctx)
|
|
326
331
|
end
|
|
327
332
|
end
|
|
328
333
|
|
|
@@ -349,10 +354,10 @@ module Biryani
|
|
|
349
354
|
|
|
350
355
|
ctx.content << data
|
|
351
356
|
if ctx.half_closed_remote?
|
|
352
|
-
obj = http_request(ctx.fragment
|
|
357
|
+
obj = http_request(ctx.fragment, ctx.content, decoder)
|
|
353
358
|
return obj if Biryani.err?(obj)
|
|
354
359
|
|
|
355
|
-
ctx.stream.rx
|
|
360
|
+
ctx.stream.rx.send(obj, move: true)
|
|
356
361
|
end
|
|
357
362
|
|
|
358
363
|
window_updates = []
|
|
@@ -370,10 +375,10 @@ module Biryani
|
|
|
370
375
|
def self.handle_headers(headers, ctx, decoder)
|
|
371
376
|
ctx.fragment << headers.fragment
|
|
372
377
|
if ctx.half_closed_remote?
|
|
373
|
-
obj = http_request(ctx.fragment
|
|
374
|
-
return
|
|
378
|
+
obj = http_request(ctx.fragment, ctx.content, decoder)
|
|
379
|
+
return obj if Biryani.err?(obj)
|
|
375
380
|
|
|
376
|
-
ctx.stream.rx
|
|
381
|
+
ctx.stream.rx.send(obj, move: true)
|
|
377
382
|
end
|
|
378
383
|
|
|
379
384
|
nil
|
data/lib/biryani/data_buffer.rb
CHANGED
|
@@ -32,16 +32,13 @@ module Biryani
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# @param s [String]
|
|
35
|
+
# @param flags [Integer]
|
|
36
|
+
# @param stream_id [Integer]
|
|
35
37
|
#
|
|
36
38
|
# @return [Continuation]
|
|
37
|
-
def self.read(s)
|
|
38
|
-
payload_length, _, flags, stream_id = Frame.read_header(s)
|
|
39
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
40
|
-
|
|
39
|
+
def self.read(s, flags, stream_id)
|
|
41
40
|
end_headers = Frame.read_end_headers(flags)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Continuation.new(end_headers, stream_id, fragment)
|
|
41
|
+
Continuation.new(end_headers, stream_id, s)
|
|
45
42
|
end
|
|
46
43
|
end
|
|
47
44
|
end
|
data/lib/biryani/frame/data.rb
CHANGED
|
@@ -41,26 +41,23 @@ module Biryani
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# @param s [String]
|
|
44
|
+
# @param flags [Integer]
|
|
45
|
+
# @param stream_id [Integer]
|
|
44
46
|
#
|
|
45
47
|
# @return [Data]
|
|
46
|
-
def self.read(s)
|
|
47
|
-
payload_length, _, flags, stream_id = Frame.read_header(s)
|
|
48
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
49
|
-
|
|
48
|
+
def self.read(s, flags, stream_id)
|
|
50
49
|
padded = Frame.read_padded(flags)
|
|
51
50
|
end_stream = Frame.read_end_stream(flags)
|
|
52
51
|
|
|
53
52
|
if padded
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
io = IO::Buffer.for(s)
|
|
54
|
+
pad_length = io.get_value(:U8, 0)
|
|
55
|
+
data_length = s.bytesize - pad_length - 1
|
|
56
|
+
data = io.get_string(1, data_length)
|
|
57
|
+
padding = io.get_string(1 + data_length)
|
|
59
58
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
|
|
60
59
|
else
|
|
61
|
-
data = s
|
|
62
|
-
padding = nil
|
|
63
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if data.bytesize != payload_length
|
|
60
|
+
data = s
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
Data.new(end_stream, stream_id, data, padding)
|
data/lib/biryani/frame/goaway.rb
CHANGED
|
@@ -28,14 +28,14 @@ module Biryani
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# @param s [String]
|
|
31
|
+
# @param _flags [Integer]
|
|
32
|
+
# @param stream_id [Integer]
|
|
31
33
|
#
|
|
32
34
|
# @return [Goaway]
|
|
33
|
-
def self.read(s)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
last_stream_id, error_code = s[9..16].unpack('NN')
|
|
38
|
-
debug = s[17..]
|
|
35
|
+
def self.read(s, _flags, stream_id)
|
|
36
|
+
io = IO::Buffer.for(s)
|
|
37
|
+
last_stream_id, error_code = io.get_values(%i[U32 U32], 0)
|
|
38
|
+
debug = io.get_string(8)
|
|
39
39
|
|
|
40
40
|
Goaway.new(stream_id, last_stream_id, error_code, debug)
|
|
41
41
|
end
|
|
@@ -58,50 +58,57 @@ module Biryani
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# @param s [String]
|
|
61
|
+
# @param flags [Integer]
|
|
62
|
+
# @param stream_id [Integer]
|
|
61
63
|
#
|
|
62
64
|
# @return [Headers]
|
|
63
65
|
# rubocop: disable Metrics/AbcSize
|
|
64
66
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
67
|
+
# rubocop: disable Metrics/MethodLength
|
|
65
68
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
66
|
-
def self.read(s)
|
|
67
|
-
payload_length, _, flags, stream_id = Frame.read_header(s)
|
|
68
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
69
|
-
|
|
69
|
+
def self.read(s, flags, stream_id)
|
|
70
70
|
priority = Frame.read_priority(flags)
|
|
71
71
|
padded = Frame.read_padded(flags)
|
|
72
72
|
end_headers = Frame.read_end_headers(flags)
|
|
73
73
|
end_stream = Frame.read_end_stream(flags)
|
|
74
74
|
|
|
75
75
|
if priority && padded
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
io = IO::Buffer.for(s)
|
|
77
|
+
pad_length, stream_dependency, weight = io.get_values(%i[U8 U32 U8], 0)
|
|
78
|
+
fragment_length = s.bytesize - pad_length - 6
|
|
78
79
|
# exclusive = (stream_dependency / 2**31).positive?
|
|
79
80
|
stream_dependency %= 2**31
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
|
|
82
|
+
|
|
83
|
+
fragment = io.get_string(6, fragment_length)
|
|
84
|
+
padding = io.get_string(6 + fragment_length)
|
|
82
85
|
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if padding.bytesize != pad_length
|
|
83
86
|
elsif priority
|
|
84
|
-
|
|
87
|
+
io = IO::Buffer.for(s)
|
|
88
|
+
stream_dependency, weight = io.get_values(%i[U32 U8], 0)
|
|
85
89
|
# exclusive = (stream_dependency / 2**31).positive?
|
|
86
90
|
stream_dependency %= 2**31
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
|
|
92
|
+
|
|
93
|
+
fragment = io.get_string(5)
|
|
94
|
+
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame') if fragment.bytesize + 5 != s.bytesize
|
|
89
95
|
elsif padded
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
io = IO::Buffer.for(s)
|
|
97
|
+
pad_length = io.get_value(:U8, 0)
|
|
98
|
+
fragment_length = s.bytesize - pad_length - 1
|
|
99
|
+
fragment = io.get_string(1, fragment_length)
|
|
100
|
+
padding = io.get_string(1 + fragment_length)
|
|
101
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= s.bytesize
|
|
95
102
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
|
|
96
103
|
else
|
|
97
|
-
fragment = s
|
|
98
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if fragment.bytesize != payload_length
|
|
104
|
+
fragment = s
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
Headers.new(end_headers, end_stream, stream_id, stream_dependency, weight, fragment, padding)
|
|
102
108
|
end
|
|
103
109
|
# rubocop: enable Metrics/AbcSize
|
|
104
110
|
# rubocop: enable Metrics/CyclomaticComplexity
|
|
111
|
+
# rubocop: enable Metrics/MethodLength
|
|
105
112
|
# rubocop: enable Metrics/PerceivedComplexity
|
|
106
113
|
end
|
|
107
114
|
end
|
data/lib/biryani/frame/ping.rb
CHANGED
|
@@ -32,17 +32,15 @@ module Biryani
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# @param s [String]
|
|
35
|
+
# @param flags [Integer]
|
|
36
|
+
# @param stream_id [Integer]
|
|
35
37
|
#
|
|
36
38
|
# @return [Ping]
|
|
37
|
-
def self.read(s)
|
|
38
|
-
|
|
39
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
40
|
-
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PING payload length MUST be 8') if s[9..].bytesize != 8
|
|
39
|
+
def self.read(s, flags, stream_id)
|
|
40
|
+
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PING payload length MUST be 8') if s.bytesize != 8
|
|
41
41
|
|
|
42
42
|
ack = Frame.read_ack(flags)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Ping.new(ack, stream_id, opaque)
|
|
43
|
+
Ping.new(ack, stream_id, s)
|
|
46
44
|
end
|
|
47
45
|
end
|
|
48
46
|
end
|
|
@@ -27,15 +27,16 @@ module Biryani
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# @param s [String]
|
|
30
|
+
# @param _flags [Integer]
|
|
31
|
+
# @param stream_id [Integer]
|
|
30
32
|
#
|
|
31
33
|
# @return [Priority]
|
|
32
|
-
def self.read(s)
|
|
33
|
-
|
|
34
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
35
|
-
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PRIORITY payload length MUST be 5') if payload_length != 5
|
|
34
|
+
def self.read(s, _flags, stream_id)
|
|
35
|
+
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'PRIORITY payload length MUST be 5') if s.bytesize != 5
|
|
36
36
|
|
|
37
|
-
stream_dependency, weight = s
|
|
37
|
+
stream_dependency, weight = s.unpack('NC')
|
|
38
38
|
stream_dependency %= 2**31
|
|
39
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'cannot depend on itself') if stream_dependency == stream_id
|
|
39
40
|
|
|
40
41
|
Priority.new(stream_id, stream_dependency, weight)
|
|
41
42
|
end
|
|
@@ -43,33 +43,32 @@ module Biryani
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
# @param s [String]
|
|
46
|
+
# @param flags [Integer]
|
|
47
|
+
# @param stream_id [Integer]
|
|
46
48
|
#
|
|
47
49
|
# @return [PushPromise]
|
|
48
|
-
|
|
49
|
-
def self.read(s)
|
|
50
|
-
payload_length, _, flags, stream_id = Frame.read_header(s)
|
|
51
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
52
|
-
|
|
50
|
+
def self.read(s, flags, stream_id)
|
|
53
51
|
padded = Frame.read_padded(flags)
|
|
54
52
|
end_headers = Frame.read_end_headers(flags)
|
|
55
53
|
|
|
54
|
+
io = IO::Buffer.for(s)
|
|
56
55
|
if padded
|
|
57
|
-
pad_length =
|
|
58
|
-
promised_stream_id
|
|
59
|
-
fragment_length =
|
|
60
|
-
fragment =
|
|
61
|
-
padding =
|
|
62
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >=
|
|
56
|
+
pad_length, promised_stream_id = io.get_values(%i[U8 U32], 0)
|
|
57
|
+
promised_stream_id %= 2**31 # Promised Stream ID (31)
|
|
58
|
+
fragment_length = s.bytesize - pad_length - 5
|
|
59
|
+
fragment = io.get_string(5, fragment_length)
|
|
60
|
+
padding = io.get_string(5 + fragment_length)
|
|
61
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if pad_length >= s.bytesize
|
|
63
62
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if padding.bytesize != pad_length
|
|
64
63
|
else
|
|
65
|
-
promised_stream_id =
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
promised_stream_id = io.get_value(:U32, 0)
|
|
65
|
+
promised_stream_id %= 2**31 # Promised Stream ID (31)
|
|
66
|
+
fragment = io.get_string(4)
|
|
67
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if fragment.bytesize + 4 != s.bytesize
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
PushPromise.new(end_headers, stream_id, promised_stream_id, fragment, padding)
|
|
71
71
|
end
|
|
72
|
-
# rubocop: enable Metrics/AbcSize
|
|
73
72
|
end
|
|
74
73
|
end
|
|
75
74
|
end
|
|
@@ -25,14 +25,14 @@ module Biryani
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# @param s [String]
|
|
28
|
+
# @param _flags [Integer]
|
|
29
|
+
# @param stream_id [Integer]
|
|
28
30
|
#
|
|
29
31
|
# @return [RstStream]
|
|
30
|
-
def self.read(s)
|
|
31
|
-
|
|
32
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
33
|
-
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'RST_STREAM payload length MUST be 4') if s[9..].bytesize != 4
|
|
32
|
+
def self.read(s, _flags, stream_id)
|
|
33
|
+
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'RST_STREAM payload length MUST be 4') if s.bytesize != 4
|
|
34
34
|
|
|
35
|
-
error_code = s
|
|
35
|
+
error_code = s.unpack1('N')
|
|
36
36
|
RstStream.new(stream_id, error_code)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -33,18 +33,18 @@ module Biryani
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# @param s [String]
|
|
36
|
+
# @param flags [Integer]
|
|
37
|
+
# @param stream_id [Integer]
|
|
36
38
|
#
|
|
37
39
|
# @return [Settings]
|
|
38
40
|
# rubocop: disable Metrics/AbcSize
|
|
39
41
|
# rubocop: disable Metrics/CyclomaticComplexity
|
|
40
42
|
# rubocop: disable Metrics/PerceivedComplexity
|
|
41
|
-
def self.read(s)
|
|
42
|
-
|
|
43
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
44
|
-
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'SETTINGS payload length MUST be a multiple of 6') if payload_length % 6 != 0
|
|
43
|
+
def self.read(s, flags, stream_id)
|
|
44
|
+
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'SETTINGS payload length MUST be a multiple of 6') if s.bytesize % 6 != 0
|
|
45
45
|
|
|
46
46
|
ack = Frame.read_ack(flags)
|
|
47
|
-
setting = s
|
|
47
|
+
setting = s.unpack('nN' * (s.bytesize / 6)).each_slice(2).to_h
|
|
48
48
|
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'SETTINGS MUST NOT have setting with ack') \
|
|
49
49
|
if ack && setting.any?
|
|
50
50
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid SETTINGS_ENABLE_PUSH') \
|
|
@@ -21,21 +21,7 @@ module Biryani
|
|
|
21
21
|
|
|
22
22
|
# @return [String]
|
|
23
23
|
def to_binary_s
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Frame.to_binary_s_header(payload_length, @f_type, flags, @stream_id) + @payload
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# @param s [String]
|
|
30
|
-
#
|
|
31
|
-
# @return [Unknown]
|
|
32
|
-
def self.read(s)
|
|
33
|
-
payload_length, f_type, flags, stream_id = Frame.read_header(s)
|
|
34
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
35
|
-
|
|
36
|
-
payload = s[9..]
|
|
37
|
-
|
|
38
|
-
Unknown.new(f_type, flags, stream_id, payload)
|
|
24
|
+
Frame.to_binary_s_header(length, @f_type, @flags, @stream_id) + @payload
|
|
39
25
|
end
|
|
40
26
|
end
|
|
41
27
|
end
|
|
@@ -25,14 +25,14 @@ module Biryani
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# @param s [String]
|
|
28
|
+
# @param _flags [Integer]
|
|
29
|
+
# @param stream_id [Integer]
|
|
28
30
|
#
|
|
29
31
|
# @return [WindowUpdate]
|
|
30
|
-
def self.read(s)
|
|
31
|
-
|
|
32
|
-
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if s[9..].bytesize != payload_length
|
|
33
|
-
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'WINDOW_UPDATE payload length MUST be 4') if s[9..].bytesize != 4
|
|
32
|
+
def self.read(s, _flags, stream_id)
|
|
33
|
+
return ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'WINDOW_UPDATE payload length MUST be 4') if s.bytesize != 4
|
|
34
34
|
|
|
35
|
-
window_size_increment = s
|
|
35
|
+
window_size_increment = s.unpack1('N')
|
|
36
36
|
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'WINDOW_UPDATE invalid window size increment 0') if window_size_increment.zero?
|
|
37
37
|
return ConnectionError.new(ErrorCode::FLOW_CONTROL_ERROR, 'WINDOW_UPDATE invalid window size increment greater than 2^31-1') if window_size_increment > 2**31 - 1
|
|
38
38
|
|
data/lib/biryani/frame.rb
CHANGED
|
@@ -10,6 +10,11 @@ module Biryani
|
|
|
10
10
|
GOAWAY = 0x07
|
|
11
11
|
WINDOW_UPDATE = 0x08
|
|
12
12
|
CONTINUATION = 0x09
|
|
13
|
+
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def self.unknown?(typ)
|
|
16
|
+
typ.negative? || typ > 0x09
|
|
17
|
+
end
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
module ErrorCode
|
|
@@ -37,11 +42,11 @@ module Biryani
|
|
|
37
42
|
# @return [Integer]
|
|
38
43
|
# @return [Integer]
|
|
39
44
|
def self.read_header(s)
|
|
40
|
-
b0, b1, b2, f_type,
|
|
45
|
+
b0, b1, b2, f_type, flags, stream_id = s.unpack('CCCCCN')
|
|
41
46
|
payload_length = (b0 << 16) | (b1 << 8) | b2
|
|
42
|
-
stream_id
|
|
47
|
+
stream_id %= 2**31 # Stream Identifier (31)
|
|
43
48
|
|
|
44
|
-
[payload_length, f_type,
|
|
49
|
+
[payload_length, f_type, flags, stream_id]
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
# @param uint8 [Integer]
|
|
@@ -136,9 +141,10 @@ module Biryani
|
|
|
136
141
|
|
|
137
142
|
payload_length, f_type, flags, stream_id = read_header(s)
|
|
138
143
|
payload = io.read(payload_length)
|
|
144
|
+
return ConnectionError.new(ErrorCode::PROTOCOL_ERROR, 'invalid frame') if payload.bytesize != payload_length
|
|
139
145
|
return Frame::Unknown.new(f_type, flags, stream_id, payload) unless FRAME_MAP.key?(f_type)
|
|
140
146
|
|
|
141
|
-
FRAME_MAP[f_type].read(
|
|
147
|
+
FRAME_MAP[f_type].read(payload, flags, stream_id)
|
|
142
148
|
rescue StandardError
|
|
143
149
|
ConnectionError.new(ErrorCode::FRAME_SIZE_ERROR, 'invalid frame')
|
|
144
150
|
end
|