biryani 0.0.1

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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +30 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +36 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +48 -0
  9. data/Rakefile +8 -0
  10. data/biryani.gemspec +21 -0
  11. data/example/echo.rb +27 -0
  12. data/example/hello_world.rb +22 -0
  13. data/lib/biryani/connection.rb +464 -0
  14. data/lib/biryani/connection_error.rb +17 -0
  15. data/lib/biryani/data_buffer.rb +42 -0
  16. data/lib/biryani/frame/continuation.rb +48 -0
  17. data/lib/biryani/frame/data.rb +70 -0
  18. data/lib/biryani/frame/goaway.rb +44 -0
  19. data/lib/biryani/frame/headers.rb +110 -0
  20. data/lib/biryani/frame/ping.rb +49 -0
  21. data/lib/biryani/frame/priority.rb +44 -0
  22. data/lib/biryani/frame/push_promise.rb +75 -0
  23. data/lib/biryani/frame/rst_stream.rb +40 -0
  24. data/lib/biryani/frame/settings.rb +66 -0
  25. data/lib/biryani/frame/unknown.rb +42 -0
  26. data/lib/biryani/frame/window_update.rb +43 -0
  27. data/lib/biryani/frame.rb +146 -0
  28. data/lib/biryani/hpack/decoder.rb +22 -0
  29. data/lib/biryani/hpack/dynamic_table.rb +65 -0
  30. data/lib/biryani/hpack/encoder.rb +22 -0
  31. data/lib/biryani/hpack/error.rb +12 -0
  32. data/lib/biryani/hpack/field.rb +357 -0
  33. data/lib/biryani/hpack/fields.rb +28 -0
  34. data/lib/biryani/hpack/huffman.rb +309 -0
  35. data/lib/biryani/hpack/integer.rb +66 -0
  36. data/lib/biryani/hpack/option.rb +24 -0
  37. data/lib/biryani/hpack/string.rb +40 -0
  38. data/lib/biryani/hpack.rb +84 -0
  39. data/lib/biryani/http_request.rb +83 -0
  40. data/lib/biryani/http_response.rb +61 -0
  41. data/lib/biryani/server.rb +19 -0
  42. data/lib/biryani/state.rb +224 -0
  43. data/lib/biryani/stream.rb +19 -0
  44. data/lib/biryani/stream_error.rb +17 -0
  45. data/lib/biryani/streams_context.rb +89 -0
  46. data/lib/biryani/version.rb +3 -0
  47. data/lib/biryani/window.rb +29 -0
  48. data/lib/biryani.rb +17 -0
  49. data/spec/connection/close_all_streams_spec.rb +17 -0
  50. data/spec/connection/handle_connection_window_update_spec.rb +16 -0
  51. data/spec/connection/handle_ping_spec.rb +21 -0
  52. data/spec/connection/handle_rst_stream_spec.rb +21 -0
  53. data/spec/connection/handle_settings_spec.rb +31 -0
  54. data/spec/connection/handle_stream_window_update_spec.rb +20 -0
  55. data/spec/connection/read_http2_magic_spec.rb +26 -0
  56. data/spec/connection/remove_closed_streams_spec.rb +51 -0
  57. data/spec/connection/send_spec.rb +114 -0
  58. data/spec/connection/transition_state_send_spec.rb +39 -0
  59. data/spec/connection/unwrap_spec.rb +28 -0
  60. data/spec/data_buffer_spec.rb +135 -0
  61. data/spec/frame/continuation_spec.rb +39 -0
  62. data/spec/frame/data_spec.rb +25 -0
  63. data/spec/frame/goaway_spec.rb +23 -0
  64. data/spec/frame/headers_spec.rb +52 -0
  65. data/spec/frame/ping_spec.rb +22 -0
  66. data/spec/frame/priority_spec.rb +22 -0
  67. data/spec/frame/push_promise_spec.rb +24 -0
  68. data/spec/frame/read_spec.rb +30 -0
  69. data/spec/frame/rst_stream_spec.rb +21 -0
  70. data/spec/frame/settings_spec.rb +23 -0
  71. data/spec/frame/window_update_spec.rb +21 -0
  72. data/spec/hpack/decoder_spec.rb +170 -0
  73. data/spec/hpack/encoder_spec.rb +48 -0
  74. data/spec/hpack/field_spec.rb +42 -0
  75. data/spec/hpack/fields_spec.rb +17 -0
  76. data/spec/hpack/huffman_spec.rb +20 -0
  77. data/spec/hpack/integer_spec.rb +27 -0
  78. data/spec/hpack/string_spec.rb +19 -0
  79. data/spec/http_request_builder_spec.rb +45 -0
  80. data/spec/spec_helper.rb +9 -0
  81. metadata +165 -0
@@ -0,0 +1,224 @@
1
+ module Biryani
2
+ class State
3
+ def initialize
4
+ @state = :idle
5
+ end
6
+
7
+ # @param frame [Object]
8
+ # @param direction [:send, :recv]
9
+ def transition!(frame, direction)
10
+ obj = self.class.next(@state, frame, direction)
11
+ return obj if obj.is_a?(StreamError) || obj.is_a?(ConnectionError)
12
+
13
+ @state = obj
14
+ end
15
+
16
+ # +--------+
17
+ # send PP | | recv PP
18
+ # ,--------+ idle +--------.
19
+ # / | | \
20
+ # v +--------+ v
21
+ # +----------+ | +----------+
22
+ # | | | send H / | |
23
+ # ,------+ reserved | | recv H | reserved +------.
24
+ # | | (local) | | | (remote) | |
25
+ # | +---+------+ v +------+---+ |
26
+ # | | +--------+ | |
27
+ # | | recv ES | | send ES | |
28
+ # | send H | ,-------+ open +-------. | recv H |
29
+ # | | / | | \ | |
30
+ # | v v +---+----+ v v |
31
+ # | +----------+ | +----------+ |
32
+ # | | half- | | | half- | |
33
+ # | | closed | | send R / | closed | |
34
+ # | | (remote) | | recv R | (local) | |
35
+ # | +----+-----+ | +-----+----+ |
36
+ # | | | | |
37
+ # | | send ES / | recv ES / | |
38
+ # | | send R / v send R / | |
39
+ # | | recv R +--------+ recv R | |
40
+ # | send R / `----------->| |<-----------' send R / |
41
+ # | recv R | closed | recv R |
42
+ # `----------------------->| |<-----------------------'
43
+ # +--------+
44
+ # https://datatracker.ietf.org/doc/html/rfc9113#section-5.1
45
+ #
46
+ # @param state [Symbol]
47
+ # @param frame [Object]
48
+ # @param direction [:send, :recv]
49
+ #
50
+ # @raise [StandardError]
51
+ #
52
+ # @return [Symbol, ConnectionError]
53
+ # rubocop: disable Metrics/AbcSize
54
+ # rubocop: disable Metrics/CyclomaticComplexity
55
+ # rubocop: disable Metrics/MethodLength
56
+ # rubocop: disable Metrics/PerceivedComplexity
57
+ def self.next(state, frame, direction)
58
+ typ = frame.f_type
59
+ case [state, typ, direction]
60
+ # idle
61
+ in [:idle, FrameType::HEADERS, :recv] if frame.end_stream? && frame.end_headers?
62
+ :half_closed_remote
63
+ in [:idle, FrameType::HEADERS, :recv] if frame.end_headers?
64
+ :receiving_data
65
+ in [:idle, FrameType::HEADERS, :recv] if frame.end_stream?
66
+ :receiving_continuation
67
+ in [:idle, FrameType::HEADERS, :recv]
68
+ :receiving_continuation_data
69
+ in [:idle, FrameType::PRIORITY, :recv]
70
+ state
71
+ in [:idle, FrameType::PUSH_PROMISE, :send]
72
+ :reserved_local
73
+ in [:idle, _, _]
74
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
75
+
76
+ # receiving_continuation_data
77
+ in [:receiving_continuation_data, FrameType::RST_STREAM, _]
78
+ :closed
79
+ in [:receiving_continuation_data, FrameType::CONTINUATION, :recv] if frame.end_headers?
80
+ :receiving_data
81
+ in [:receiving_continuation_data, FrameType::CONTINUATION, :recv]
82
+ state
83
+ in [:receiving_continuation_data, _, _]
84
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
85
+
86
+ # receiving_continuation
87
+ in [:receiving_continuation, FrameType::RST_STREAM, _]
88
+ :closed
89
+ in [:receiving_continuation, FrameType::CONTINUATION, :recv] if frame.end_headers?
90
+ :half_closed_remote
91
+ in [:receiving_continuation, FrameType::CONTINUATION, :recv]
92
+ state
93
+ in [:receiving_continuation, _, _]
94
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
95
+
96
+ # receiving_data
97
+ in [:receiving_data, FrameType::DATA, :recv] if frame.end_stream?
98
+ :half_closed_remote
99
+ in [:receiving_data, FrameType::DATA, :recv]
100
+ state
101
+ in [:receiving_data, FrameType::RST_STREAM, _]
102
+ :closed
103
+ in [:receiving_data, _, _]
104
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
105
+
106
+ # reserved_remote
107
+ in [:reserved_remote, _, _]
108
+ # TODO
109
+ state
110
+
111
+ # reserved_local
112
+ in [:reserved_local, _, _]
113
+ # TODO
114
+ state
115
+
116
+ # half_closed_remote
117
+ in [:half_closed_remote, FrameType::HEADERS, :send] if frame.end_stream? && frame.end_headers?
118
+ :closed
119
+ in [:half_closed_remote, FrameType::HEADERS, :send] if frame.end_headers?
120
+ :sending_data
121
+ in [:half_closed_remote, FrameType::HEADERS, :send] if frame.end_stream?
122
+ :sending_continuation
123
+ in [:half_closed_remote, FrameType::HEADERS, :send]
124
+ :sending_continuation_data
125
+ in [:half_closed_remote, FrameType::PRIORITY, :recv]
126
+ state
127
+ in [:half_closed_remote, FrameType::RST_STREAM, _]
128
+ :closed
129
+ in [half_closed_remote, FrameType::WINDOW_UPDATE, :recv]
130
+ state
131
+ in [:half_closed_remote, _, :recv]
132
+ unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
133
+ in [:half_closed_local, _, :send]
134
+ unreachable(state, typ, direction)
135
+
136
+ # sending_continuation_data
137
+ in [:sending_continuation_data, FrameType::RST_STREAM, :send]
138
+ :closed
139
+ in [:sending_continuation_data, FrameType::CONTINUATION, :send] if frame.end_headers?
140
+ :sending_data
141
+ in [:sending_continuation_data, FrameType::CONTINUATION, :send]
142
+ state
143
+ in [:sending_continuation_data, _, :send]
144
+ unreachable(state, typ, direction)
145
+ in [:sending_continuation_data, _, :recv]
146
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
147
+
148
+ # sending_continuation
149
+ in [:sending_continuation, FrameType::RST_STREAM, :send]
150
+ :closed
151
+ in [:sending_continuation, FrameType::CONTINUATION, :send] if frame.end_headers?
152
+ :closed
153
+ in [:sending_continuation, FrameType::CONTINUATION, :send]
154
+ state
155
+ in [:sending_continuation, _, :send]
156
+ unreachable(state, typ, direction)
157
+ in [:sending_continuation, _, :recv]
158
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
159
+
160
+ # sending_data
161
+ in [:sending_data, FrameType::DATA, :send] if frame.end_stream?
162
+ :closed
163
+ in [:sending_data, FrameType::DATA, :send]
164
+ state
165
+ in [:sending_data, FrameType::RST_STREAM, :send]
166
+ :closed
167
+ in [:sending_continuation, _, :send]
168
+ unreachable(state, typ, direction)
169
+ in [:sending_continuation, _, :recv]
170
+ unexpected(ErrorCode::PROTOCOL_ERROR, state, typ, direction)
171
+
172
+ # closed
173
+ in [:closed, FrameType::RST_STREAM, :recv]
174
+ state
175
+ in [:closed, _, :send]
176
+ unreachable(state, typ, direction)
177
+ in [:closed, _, :recv]
178
+ unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
179
+
180
+ # other
181
+ in [_, _, :send]
182
+ unreachable(state, typ, direction)
183
+ in [_, _, :recv]
184
+ unexpected(ErrorCode::STREAM_CLOSED, state, typ, direction)
185
+ end
186
+ end
187
+ # rubocop: enable Metrics/AbcSize
188
+ # rubocop: enable Metrics/CyclomaticComplexity
189
+ # rubocop: enable Metrics/MethodLength
190
+ # rubocop: enable Metrics/PerceivedComplexity
191
+
192
+ def self.unexpected(error_code, state, typ, direction)
193
+ ConnectionError.new(error_code, "#{direction} unexpected #{format('0x%02x', typ)} frame in #{state}")
194
+ end
195
+
196
+ def self.unreachable(state, typ, direction)
197
+ raise "#{direction} unexpected #{format('0x%02x', typ)} frame in #{state}"
198
+ end
199
+
200
+ def close
201
+ @state = :closed
202
+ end
203
+
204
+ # @return [Boolean]
205
+ def closed?
206
+ @state == :closed
207
+ end
208
+
209
+ # @return [Boolean]
210
+ def idle?
211
+ @state == :idle
212
+ end
213
+
214
+ # @return [Boolean]
215
+ def active?
216
+ @state == :open || @state == :half_closed_local || @state == :half_closed_remote
217
+ end
218
+
219
+ # @return [Boolean]
220
+ def half_closed_remote?
221
+ @state == :half_closed_remote
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,19 @@
1
+ module Biryani
2
+ class Stream
3
+ attr_accessor :rx
4
+
5
+ # @param tx [Port]
6
+ # @param stream_id [Integer]
7
+ # @param proc [Proc]
8
+ def initialize(tx, stream_id, proc)
9
+ @rx = Ractor.new(tx, stream_id, proc) do |tx, stream_id, proc|
10
+ unless (req = Ractor.recv).nil?
11
+ res = HTTPResponse.new(0, {}, '')
12
+
13
+ proc.call(req, res)
14
+ tx << [res, stream_id]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Biryani
2
+ class StreamError
3
+ # @param code [Integer]
4
+ # @param stream_id [stream_id]
5
+ # @param debug [String]
6
+ def initialize(code, stream_id, debug)
7
+ @code = code
8
+ @stream_id = stream_id
9
+ @debug = debug
10
+ end
11
+
12
+ # @return [RstStream]
13
+ def rst_stream
14
+ Frame::RstStream.new(@stream_id, @code)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,89 @@
1
+ module Biryani
2
+ class StreamsContext
3
+ def initialize
4
+ @h = {}
5
+ end
6
+
7
+ # @param stream_id [Integer]
8
+ # @param proc [Proc]
9
+ #
10
+ # @return [StreamContext]
11
+ def new_context(stream_id, proc)
12
+ ctx = StreamContext.new(stream_id, proc)
13
+ @h[stream_id] = ctx
14
+ ctx
15
+ end
16
+
17
+ # @param stream_id [Integer]
18
+ def delete(stream_id)
19
+ @h.delete(stream_id)
20
+ end
21
+
22
+ # @param stream_id [Integer]
23
+ #
24
+ # @return [StreamContext, nil]
25
+ def [](stream_id)
26
+ @h[stream_id]
27
+ end
28
+
29
+ # @return [Integer]
30
+ def length
31
+ @h.length
32
+ end
33
+
34
+ def each(&block)
35
+ @h.each_value(&block)
36
+ end
37
+
38
+ # @return [Array<Port>]
39
+ def txs
40
+ @h.values.filter { |ctx| !ctx.closed? }.map(&:tx)
41
+ end
42
+
43
+ # @return [Integer]
44
+ def count_active
45
+ @h.values.filter(&:active?).length
46
+ end
47
+
48
+ # @return [Array<Integer>]
49
+ def closed_stream_ids
50
+ @h.filter { |_, ctx| ctx.closed? }.keys
51
+ end
52
+
53
+ # @return [Integer]
54
+ def last_stream_id
55
+ @h.reject { |_, ctx| ctx.idle? }.keys.max || 0
56
+ end
57
+ end
58
+
59
+ class StreamContext
60
+ attr_accessor :stream, :tx, :send_window, :recv_window, :fragment, :content, :state
61
+
62
+ # @param stream_id [Integer]
63
+ # @param proc [Proc]
64
+ def initialize(stream_id, proc)
65
+ @tx = Ractor::Port.new
66
+ @stream = Stream.new(@tx, stream_id, proc)
67
+ @send_window = Window.new
68
+ @recv_window = Window.new
69
+ @fragment = StringIO.new
70
+ @content = StringIO.new
71
+ @state = State.new
72
+ end
73
+
74
+ # @return [Boolean]
75
+ def closed?
76
+ @state.closed?
77
+ end
78
+
79
+ # @return [Boolean]
80
+ def idle?
81
+ @state.idle?
82
+ end
83
+
84
+ # @return [Boolean]
85
+ def active?
86
+ @state.active?
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module Biryani
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,29 @@
1
+ module Biryani
2
+ class Window
3
+ def initialize
4
+ @window = 2**16 - 1
5
+ end
6
+
7
+ # @param length [Integer]
8
+ #
9
+ # @return [Boolean]
10
+ def available?(length)
11
+ @window > length
12
+ end
13
+
14
+ # @param length [Integer]
15
+ def consume!(length)
16
+ @window -= length
17
+ end
18
+
19
+ # @param length [Integer]
20
+ def increase!(length)
21
+ @window += length
22
+ end
23
+
24
+ # @return [Integer]
25
+ def length
26
+ @window
27
+ end
28
+ end
29
+ end
data/lib/biryani.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'stringio'
2
+ require 'uri'
3
+
4
+ require_relative 'biryani/connection'
5
+ require_relative 'biryani/connection_error'
6
+ require_relative 'biryani/data_buffer'
7
+ require_relative 'biryani/frame'
8
+ require_relative 'biryani/hpack'
9
+ require_relative 'biryani/http_request'
10
+ require_relative 'biryani/http_response'
11
+ require_relative 'biryani/server'
12
+ require_relative 'biryani/state'
13
+ require_relative 'biryani/streams_context'
14
+ require_relative 'biryani/stream_error'
15
+ require_relative 'biryani/stream'
16
+ require_relative 'biryani/version'
17
+ require_relative 'biryani/window'
@@ -0,0 +1,17 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'close_all_streams' do
5
+ let(:streams_ctx) do
6
+ streams_ctx = StreamsContext.new
7
+ streams_ctx.new_context(1, do_nothing_proc)
8
+ streams_ctx.new_context(2, do_nothing_proc)
9
+ streams_ctx
10
+ end
11
+ it 'should close' do
12
+ Connection.close_all_streams(streams_ctx)
13
+ expect { streams_ctx[1].tx << nil }.to raise_error Ractor::ClosedError
14
+ expect { streams_ctx[2].tx << nil }.to raise_error Ractor::ClosedError
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'handle_connection_window_update' do
5
+ let(:window_update) do
6
+ Frame::WindowUpdate.new(0, 1000)
7
+ end
8
+ let(:send_window) do
9
+ Window.new
10
+ end
11
+ it 'should handle' do
12
+ expect { Connection.handle_connection_window_update(window_update, send_window) }.not_to raise_error
13
+ expect(send_window.length).to eq 2**16 - 1 + 1000
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'handle_ping' do
5
+ let(:ping1) do
6
+ Frame::Ping.new(true, 0, "\x00" * 8)
7
+ end
8
+ it 'should handle' do
9
+ expect(Connection.handle_ping(ping1)).to eq nil
10
+ end
11
+
12
+ let(:ping2) do
13
+ Frame::Ping.new(false, 0, "\x00" * 8)
14
+ end
15
+ it 'should handle' do
16
+ expect(Connection.handle_ping(ping2)).to_not eq nil
17
+ expect(Connection.handle_ping(ping2).ack?).to eq true
18
+ expect(Connection.handle_ping(ping2).opaque).to eq "\x00" * 8
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'handle_rst_stream' do
5
+ let(:rst_stream) do
6
+ Frame::RstStream.new(2, 0)
7
+ end
8
+ let(:streams_ctx) do
9
+ streams_ctx = StreamsContext.new
10
+ streams_ctx.new_context(1, do_nothing_proc)
11
+ streams_ctx.new_context(2, do_nothing_proc)
12
+ streams_ctx
13
+ end
14
+ it 'should handle' do
15
+ Connection.handle_rst_stream(rst_stream, streams_ctx)
16
+ expect(streams_ctx.length).to eq 2
17
+ expect(streams_ctx.count_active).to eq 0
18
+ expect(streams_ctx.closed_stream_ids.length).to eq 1
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'handle_settings' do
5
+ let(:send_settings) do
6
+ Connection.default_settings
7
+ end
8
+ let(:decoder) do
9
+ HPACK::Decoder.new(4_096)
10
+ end
11
+
12
+ let(:settings1) do
13
+ Frame::Settings.new(false, 0, { SettingsID::SETTINGS_HEADER_TABLE_SIZE => 8_192 })
14
+ end
15
+ it 'should handle' do
16
+ reply_settings = Connection.handle_settings(settings1, send_settings, decoder)
17
+ expect(reply_settings.ack?).to eq true
18
+ expect(reply_settings.setting.empty?).to eq true
19
+ expect(send_settings[SettingsID::SETTINGS_MAX_CONCURRENT_STREAMS]).to eq 0xffffffff
20
+ expect(send_settings[SettingsID::SETTINGS_MAX_FRAME_SIZE]).to eq 16_384
21
+ expect(send_settings[SettingsID::SETTINGS_HEADER_TABLE_SIZE]).to eq 8_192
22
+ end
23
+
24
+ let(:settings2) do
25
+ Frame::Settings.new(true, 0, {})
26
+ end
27
+ it 'should handle' do
28
+ expect(Connection.handle_settings(settings2, send_settings, decoder)).to eq nil
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'handle_stream_window_update' do
5
+ let(:window_update) do
6
+ Frame::WindowUpdate.new(1, 1000)
7
+ end
8
+ let(:streams_ctx) do
9
+ streams_ctx = StreamsContext.new
10
+ streams_ctx.new_context(1, do_nothing_proc)
11
+ streams_ctx.new_context(2, do_nothing_proc)
12
+ streams_ctx
13
+ end
14
+ it 'should handle' do
15
+ expect { Connection.handle_stream_window_update(window_update, streams_ctx) }.not_to raise_error
16
+ expect(streams_ctx[1].send_window.length).to eq 2**16 - 1 + 1000
17
+ expect(streams_ctx[2].send_window.length).to eq 2**16 - 1
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'read_http2_magic' do
5
+ let(:io1) do
6
+ StringIO.new("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
7
+ end
8
+ it 'should read' do
9
+ expect(Connection.read_http2_magic(io1)).to eq nil
10
+ end
11
+
12
+ let(:io2) do
13
+ StringIO.new("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n\x00xff")
14
+ end
15
+ it 'should read' do
16
+ expect(Connection.read_http2_magic(io2)).to eq nil
17
+ end
18
+
19
+ let(:io3) do
20
+ StringIO.new("\x00xffPRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
21
+ end
22
+ it 'should not read' do
23
+ expect(Connection.read_http2_magic(io3)).to be_kind_of ConnectionError
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe Connection do
4
+ context 'remove_closed_streams' do
5
+ let(:streams_ctx1) do
6
+ streams_ctx = StreamsContext.new
7
+ streams_ctx.new_context(1, do_nothing_proc)
8
+ streams_ctx.new_context(2, do_nothing_proc)
9
+ streams_ctx
10
+ end
11
+ let(:data_buffer1) do
12
+ DataBuffer.new
13
+ end
14
+ it 'should close' do
15
+ Connection.remove_closed_streams(streams_ctx1, data_buffer1)
16
+ expect(streams_ctx1.length).to eq 2
17
+ end
18
+
19
+ let(:streams_ctx2) do
20
+ streams_ctx = StreamsContext.new
21
+ streams_ctx.new_context(1, do_nothing_proc)
22
+ streams_ctx.new_context(2, do_nothing_proc)
23
+ streams_ctx[2].state.close
24
+ streams_ctx
25
+ end
26
+ let(:data_buffer2) do
27
+ DataBuffer.new
28
+ end
29
+ it 'should close' do
30
+ Connection.remove_closed_streams(streams_ctx2, data_buffer2)
31
+ expect(streams_ctx2.length).to eq 2 # remain stream_id
32
+ end
33
+
34
+ let(:streams_ctx3) do
35
+ streams_ctx = StreamsContext.new
36
+ streams_ctx.new_context(1, do_nothing_proc)
37
+ streams_ctx.new_context(2, do_nothing_proc)
38
+ streams_ctx[2].state.close
39
+ streams_ctx
40
+ end
41
+ let(:data_buffer3) do
42
+ data_buffer = DataBuffer.new
43
+ data_buffer << Frame::Data.new(false, 2, 'two', nil)
44
+ data_buffer
45
+ end
46
+ it 'should close' do
47
+ Connection.remove_closed_streams(streams_ctx3, data_buffer3)
48
+ expect(streams_ctx3.length).to eq 2
49
+ end
50
+ end
51
+ end