plum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +14 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE +21 -0
  7. data/README.md +14 -0
  8. data/Rakefile +12 -0
  9. data/bin/.gitkeep +0 -0
  10. data/examples/local_server.rb +206 -0
  11. data/examples/static_server.rb +157 -0
  12. data/lib/plum.rb +21 -0
  13. data/lib/plum/binary_string.rb +74 -0
  14. data/lib/plum/connection.rb +201 -0
  15. data/lib/plum/connection_utils.rb +38 -0
  16. data/lib/plum/errors.rb +35 -0
  17. data/lib/plum/event_emitter.rb +19 -0
  18. data/lib/plum/flow_control.rb +97 -0
  19. data/lib/plum/frame.rb +163 -0
  20. data/lib/plum/frame_factory.rb +53 -0
  21. data/lib/plum/frame_utils.rb +50 -0
  22. data/lib/plum/hpack/constants.rb +331 -0
  23. data/lib/plum/hpack/context.rb +55 -0
  24. data/lib/plum/hpack/decoder.rb +145 -0
  25. data/lib/plum/hpack/encoder.rb +105 -0
  26. data/lib/plum/hpack/huffman.rb +42 -0
  27. data/lib/plum/http_connection.rb +33 -0
  28. data/lib/plum/https_connection.rb +24 -0
  29. data/lib/plum/stream.rb +217 -0
  30. data/lib/plum/stream_utils.rb +58 -0
  31. data/lib/plum/version.rb +3 -0
  32. data/plum.gemspec +29 -0
  33. data/test/plum/connection/test_handle_frame.rb +70 -0
  34. data/test/plum/hpack/test_context.rb +63 -0
  35. data/test/plum/hpack/test_decoder.rb +291 -0
  36. data/test/plum/hpack/test_encoder.rb +49 -0
  37. data/test/plum/hpack/test_huffman.rb +36 -0
  38. data/test/plum/stream/test_handle_frame.rb +262 -0
  39. data/test/plum/test_binary_string.rb +64 -0
  40. data/test/plum/test_connection.rb +96 -0
  41. data/test/plum/test_connection_utils.rb +29 -0
  42. data/test/plum/test_error.rb +13 -0
  43. data/test/plum/test_flow_control.rb +167 -0
  44. data/test/plum/test_frame.rb +59 -0
  45. data/test/plum/test_frame_factory.rb +56 -0
  46. data/test/plum/test_frame_utils.rb +46 -0
  47. data/test/plum/test_https_connection.rb +37 -0
  48. data/test/plum/test_stream.rb +32 -0
  49. data/test/plum/test_stream_utils.rb +16 -0
  50. data/test/server.crt +19 -0
  51. data/test/server.csr +16 -0
  52. data/test/server.key +27 -0
  53. data/test/test_helper.rb +28 -0
  54. data/test/utils/assertions.rb +60 -0
  55. data/test/utils/server.rb +63 -0
  56. metadata +234 -0
@@ -0,0 +1,49 @@
1
+ require "test_helper"
2
+
3
+ class HPACKEncoderTest < Minitest::Test
4
+ # C.1.1
5
+ def test_hpack_encode_integer_small
6
+ result = new_encoder.__send__(:encode_integer, 10, 5)
7
+ assert_equal([0b00001010].pack("C*"), result)
8
+ end
9
+
10
+ # C.1.2
11
+ def test_hpack_encode_integer_big
12
+ result = new_encoder.__send__(:encode_integer, 1337, 5)
13
+ assert_equal([0b00011111, 0b10011010, 0b00001010].pack("C*"), result)
14
+ end
15
+
16
+ # C.1.3
17
+ def test_hpack_encode_integer_8prefix
18
+ result = new_encoder.__send__(:encode_integer, 42, 8)
19
+ assert_equal([0b00101010].pack("C*"), result)
20
+ end
21
+
22
+ def test_hpack_encode_single
23
+ headers = [["custom-key", "custom-header"]]
24
+ encoded = new_encoder.encode(headers)
25
+ decoded = new_decoder.decode(encoded)
26
+ assert_equal(headers, decoded)
27
+ end
28
+
29
+ def test_hpack_encode_multiple
30
+ headers = [
31
+ [":method", "GET"],
32
+ [":scheme", "http"],
33
+ [":path", "/"],
34
+ [":authority", "www.example.com"]
35
+ ]
36
+ encoded = new_encoder.encode(headers)
37
+ decoded = new_decoder.decode(encoded)
38
+ assert_equal(headers, decoded)
39
+ end
40
+
41
+ private
42
+ def new_decoder(settings_header_table_size = 1 << 31)
43
+ Plum::HPACK::Decoder.new(settings_header_table_size)
44
+ end
45
+
46
+ def new_encoder(settings_header_table_size = 1 << 31)
47
+ Plum::HPACK::Encoder.new(settings_header_table_size)
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ require "test_helper"
2
+
3
+ class HPACKHuffmanTest < Minitest::Test
4
+ def test_hpack_huffman_encode
5
+ text = "\x10\x60\x2a\x1d\x94\x47\x82\x2c\x3d\x19\xbf\x8e\xd9\x24\xba\xe6\xb4\x1a\xe1\x5c\x39\x0f\x61\xf4\x3b\x08\x62\x54\x15\x0c"
6
+ expected = "\xff\xff\xfe\xdf\xff\xbf\x3f\xff\xff\xf3\xff\xff\xdd\x8b\xff\xf9\xfe\xa0\xff\xff\xff\x5f\xff\xfe\x3f\xff\xfd\x7f\xff\xe5\xff\xcf\xff\xfc\x7f\xff\xe8\xff\xff\xdb\xff\xff\xfe\xdf\xff\xfe\x7f\xff\xc1\xff\xff\xff\xec\x1f\xff\xff\xe7\xfb\xff\xff\xfe\x88\xf7\xff\xff\xff\x97\xff\xff\xf5\x7f"
7
+ assert_equal(expected.force_encoding(Encoding::BINARY), Plum::HPACK::Huffman.encode(text).force_encoding(Encoding::BINARY))
8
+ end
9
+
10
+ def test_hpack_huffman_decode
11
+ encoded = "\xff\xff\xfe\xdf\xff\xbf\x3f\xff\xff\xf3\xff\xff\xdd\x8b\xff\xf9\xfe\xa0\xff\xff\xff\x5f\xff\xfe\x3f\xff\xfd\x7f\xff\xe5\xff\xcf\xff\xfc\x7f\xff\xe8\xff\xff\xdb\xff\xff\xfe\xdf\xff\xfe\x7f\xff\xc1\xff\xff\xff\xec\x1f\xff\xff\xe7\xfb\xff\xff\xfe\x88\xf7\xff\xff\xff\x97\xff\xff\xf5\x7f"
12
+ expected = "\x10\x60\x2a\x1d\x94\x47\x82\x2c\x3d\x19\xbf\x8e\xd9\x24\xba\xe6\xb4\x1a\xe1\x5c\x39\x0f\x61\xf4\x3b\x08\x62\x54\x15\x0c"
13
+ assert_equal(expected.force_encoding(Encoding::BINARY), Plum::HPACK::Huffman.decode(encoded).force_encoding(Encoding::BINARY))
14
+ end
15
+
16
+ def test_too_long_padding
17
+ encoded = "\x1c\x64\xff\xff\xff"
18
+ assert_raises(Plum::HPACKError) {
19
+ Plum::HPACK::Huffman.decode(encoded)
20
+ }
21
+ end
22
+
23
+ def test_padding_not_filled
24
+ encoded = "\x1c\x71\xfe"
25
+ assert_raises(Plum::HPACKError) {
26
+ Plum::HPACK::Huffman.decode(encoded)
27
+ }
28
+ end
29
+
30
+ def test_eos_in_encoded
31
+ encoded = "\xff\xff\xff\xff" # \xff\xff\xff\xfc + padding
32
+ assert_raises(Plum::HPACKError) {
33
+ Plum::HPACK::Huffman.decode(encoded)
34
+ }
35
+ end
36
+ end
@@ -0,0 +1,262 @@
1
+ require "test_helper"
2
+
3
+ using Plum::BinaryString
4
+
5
+ class StreamHandleFrameTest < Minitest::Test
6
+ ## DATA
7
+ def test_stream_handle_data
8
+ payload = "ABC" * 5
9
+ open_new_stream(state: :open) {|stream|
10
+ data = nil
11
+ stream.on(:data) {|_data| data = _data }
12
+ stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
13
+ flags: [], payload: payload))
14
+ assert_equal(payload, data)
15
+ }
16
+ end
17
+
18
+ def test_stream_handle_data_padded
19
+ payload = "ABC" * 5
20
+ open_new_stream(state: :open) {|stream|
21
+ data = nil
22
+ stream.on(:data) {|_data| data = _data }
23
+ stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
24
+ flags: [:padded], payload: "".push_uint8(6).push(payload).push("\x00"*6)))
25
+ assert_equal(payload, data)
26
+ }
27
+ end
28
+
29
+ def test_stream_handle_data_too_long_padding
30
+ payload = "ABC" * 5
31
+ open_new_stream(state: :open) {|stream|
32
+ assert_connection_error(:protocol_error) {
33
+ stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
34
+ flags: [:padded], payload: "".push_uint8(100).push(payload).push("\x00"*6)))
35
+ }
36
+ }
37
+ end
38
+
39
+ def test_stream_handle_data_end_stream
40
+ payload = "ABC" * 5
41
+ open_new_stream(state: :open) {|stream|
42
+ stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
43
+ flags: [:end_stream], payload: payload))
44
+ assert_equal(:half_closed_remote, stream.state)
45
+ }
46
+ end
47
+
48
+ def test_stream_handle_data_invalid_state
49
+ payload = "ABC" * 5
50
+ open_new_stream(state: :half_closed_remote) {|stream|
51
+ assert_stream_error(:stream_closed) {
52
+ stream.receive_frame(Frame.new(type: :data, stream_id: stream.id,
53
+ flags: [:end_stream], payload: payload))
54
+ }
55
+ }
56
+ end
57
+
58
+ ## HEADERS
59
+ def test_stream_handle_headers_single
60
+ open_new_stream {|stream|
61
+ headers = nil
62
+ stream.on(:headers) {|_headers|
63
+ headers = _headers
64
+ }
65
+ stream.receive_frame(Frame.new(type: :headers,
66
+ stream_id: stream.id,
67
+ flags: [:end_headers],
68
+ payload: HPACK::Encoder.new(0).encode([[":path", "/"]])))
69
+ assert_equal(:open, stream.state)
70
+ assert_equal([[":path", "/"]], headers)
71
+ }
72
+ end
73
+
74
+ def test_stream_handle_headers_continuation
75
+ open_new_stream {|stream|
76
+ payload = HPACK::Encoder.new(0).encode([[":path", "/"]])
77
+ headers = nil
78
+ stream.on(:headers) {|_headers|
79
+ headers = _headers
80
+ }
81
+ stream.receive_frame(Frame.new(type: :headers,
82
+ stream_id: stream.id,
83
+ flags: [:end_stream],
84
+ payload: payload[0..4]))
85
+ assert_equal(nil, headers) # wait CONTINUATION
86
+ stream.receive_frame(Frame.new(type: :continuation,
87
+ stream_id: stream.id,
88
+ flags: [:end_headers],
89
+ payload: payload[5..-1]))
90
+ assert_equal(:half_closed_remote, stream.state)
91
+ assert_equal([[":path", "/"]], headers)
92
+ }
93
+ end
94
+
95
+ def test_stream_handle_headers_padded
96
+ open_new_stream {|stream|
97
+ payload = HPACK::Encoder.new(0).encode([[":path", "/"]])
98
+ headers = nil
99
+ stream.on(:headers) {|_headers|
100
+ headers = _headers
101
+ }
102
+ stream.receive_frame(Frame.new(type: :headers,
103
+ stream_id: stream.id,
104
+ flags: [:end_headers, :padded],
105
+ payload: "".push_uint8(payload.bytesize).push(payload).push("\x00"*payload.bytesize)))
106
+ assert_equal([[":path", "/"]], headers)
107
+ }
108
+ end
109
+
110
+ def test_stream_handle_headers_too_long_padding
111
+ open_new_stream {|stream|
112
+ payload = HPACK::Encoder.new(0).encode([[":path", "/"]])
113
+ assert_connection_error(:protocol_error) {
114
+ stream.receive_frame(Frame.new(type: :headers,
115
+ stream_id: stream.id,
116
+ flags: [:end_headers, :padded],
117
+ payload: "".push_uint8(payload.bytesize+1).push(payload).push("\x00"*(payload.bytesize+1))))
118
+ }
119
+ }
120
+ end
121
+
122
+ def test_stream_handle_headers_broken
123
+ open_new_stream {|stream|
124
+ payload = "\x00\x01\x02"
125
+ assert_connection_error(:compression_error) {
126
+ stream.receive_frame(Frame.new(type: :headers,
127
+ stream_id: stream.id,
128
+ flags: [:end_headers],
129
+ payload: payload))
130
+ }
131
+ }
132
+ end
133
+
134
+ def test_stream_handle_headers_state
135
+ _payload = HPACK::Encoder.new(0).encode([[":path", "/"]])
136
+ open_new_stream(state: :reserved_local) {|stream|
137
+ assert_connection_error(:protocol_error) {
138
+ stream.receive_frame(Frame.new(type: :headers, stream_id: stream.id, flags: [:end_headers, :end_stream], payload: _payload))
139
+ }
140
+ }
141
+ open_new_stream(state: :closed) {|stream|
142
+ assert_connection_error(:stream_closed) {
143
+ stream.receive_frame(Frame.new(type: :headers, stream_id: stream.id, flags: [:end_headers, :end_stream], payload: _payload))
144
+ }
145
+ }
146
+ open_new_stream(state: :half_closed_remote) {|stream|
147
+ assert_stream_error(:stream_closed) {
148
+ stream.receive_frame(Frame.new(type: :headers, stream_id: stream.id, flags: [:end_headers, :end_stream], payload: _payload))
149
+ }
150
+ }
151
+ end
152
+
153
+ def test_stream_handle_headers_priority
154
+ open_server_connection {|con|
155
+ parent = open_new_stream(con)
156
+ stream = open_new_stream(con)
157
+
158
+ headers = nil
159
+ stream.on(:headers) {|_headers| headers = _headers }
160
+ header_block = HPACK::Encoder.new(0).encode([[":path", "/"]])
161
+ payload = "".push_uint32((1 << 31) | parent.id)
162
+ .push_uint8(50)
163
+ .push(header_block)
164
+ stream.receive_frame(Frame.new(type: :headers,
165
+ stream_id: stream.id,
166
+ flags: [:end_headers, :priority],
167
+ payload: payload))
168
+ assert_equal(true, stream.exclusive)
169
+ assert_equal(parent, stream.parent)
170
+ assert_equal(50, stream.weight)
171
+ assert_equal([[":path", "/"]], headers)
172
+ }
173
+ end
174
+
175
+ ## PRIORITY
176
+ def test_stream_handle_priority
177
+ open_server_connection {|con|
178
+ parent = open_new_stream(con)
179
+ stream = open_new_stream(con)
180
+
181
+ payload = "".push_uint32((1 << 31) | parent.id)
182
+ .push_uint8(50)
183
+ stream.receive_frame(Frame.new(type: :priority,
184
+ stream_id: stream.id,
185
+ payload: payload))
186
+ assert_equal(true, stream.exclusive)
187
+ assert_equal(parent, stream.parent)
188
+ assert_equal(50, stream.weight)
189
+ }
190
+ end
191
+
192
+ def test_stream_handle_priority_self_depend
193
+ open_server_connection {|con|
194
+ stream = open_new_stream(con)
195
+ payload = "".push_uint32((1 << 31) | stream.id).push_uint8(6)
196
+ stream.receive_frame(Frame.new(type: :priority,
197
+ stream_id: stream.id,
198
+ payload: payload))
199
+ last = sent_frames.last
200
+ assert_equal(:rst_stream, last.type)
201
+ assert_equal(HTTPError::ERROR_CODES[:protocol_error], last.payload.uint32)
202
+ }
203
+ end
204
+
205
+ def test_stream_handle_priority_exclusive
206
+ open_server_connection {|con|
207
+ parent = open_new_stream(con)
208
+ stream0 = open_new_stream(con, parent: parent)
209
+ stream1 = open_new_stream(con, parent: parent)
210
+ stream2 = open_new_stream(con, parent: parent)
211
+
212
+ payload = "".push_uint32((1 << 31) | parent.id).push_uint8(6)
213
+ stream0.receive_frame(Frame.new(type: :priority,
214
+ stream_id: stream0.id,
215
+ payload: payload))
216
+ assert_equal(parent, stream0.parent)
217
+ assert_equal(stream0, stream1.parent)
218
+ assert_equal(stream0, stream2.parent)
219
+ }
220
+ end
221
+
222
+ def test_stream_handle_frame_size_error
223
+ open_new_stream {|stream|
224
+ assert_stream_error(:frame_size_error) {
225
+ stream.receive_frame(Frame.new(type: :priority,
226
+ stream_id: stream.id,
227
+ payload: "\x00"))
228
+ }
229
+ }
230
+ end
231
+
232
+ ## RST_STREAM
233
+ def test_stream_handle_rst_stream
234
+ open_new_stream(state: :reserved_local) {|stream|
235
+ stream.receive_frame(Frame.new(type: :rst_stream,
236
+ stream_id: stream.id,
237
+ payload: "\x00\x00\x00\x00"))
238
+ assert_equal(:closed, stream.state)
239
+ }
240
+ end
241
+
242
+ def test_stream_handle_rst_stream_idle
243
+ open_new_stream(state: :idle) {|stream|
244
+ assert_connection_error(:protocol_error) {
245
+ stream.receive_frame(Frame.new(type: :rst_stream,
246
+ stream_id: stream.id,
247
+ payload: "\x00\x00\x00\x00"))
248
+ }
249
+ }
250
+ end
251
+
252
+ def test_stream_handle_rst_stream_frame_size
253
+ open_new_stream(state: :reserved_local) {|stream|
254
+ assert_connection_error(:frame_size_error) {
255
+ stream.receive_frame(Frame.new(type: :rst_stream,
256
+ stream_id: stream.id,
257
+ payload: "\x00\x00\x00"))
258
+ }
259
+ }
260
+ end
261
+
262
+ end
@@ -0,0 +1,64 @@
1
+ require "test_helper"
2
+
3
+ using BinaryString
4
+
5
+ class BinaryStringTest < Minitest::Test
6
+ def test_uint8
7
+ assert_equal(0x67, "\x67".uint8)
8
+ assert_equal(0x75, "\x67\x75".uint8(1))
9
+ end
10
+
11
+ def test_uint16
12
+ assert_equal(0x78ff, "\x78\xff".uint16)
13
+ assert_equal(0xee55, "\x78\xee\x55".uint16(1))
14
+ end
15
+
16
+ def test_uint24
17
+ assert_equal(0x005554, "\x00\x55\x54".uint24)
18
+ assert_equal(0x005554, "\x2f\xaa\x00\x55\x54".uint24(2))
19
+ end
20
+
21
+ def test_uint32
22
+ assert_equal(0x00555400, "\x00\x55\x54\x00".uint32)
23
+ assert_equal(0x00555400, "\x2f\xaa\x00\x55\x54\x00".uint32(2))
24
+ end
25
+
26
+ def test_push_uint8
27
+ assert_equal("\x24", "".push_uint8(0x24))
28
+ end
29
+
30
+ def test_push_uint16
31
+ assert_equal("\x24\x11", "".push_uint16(0x2411))
32
+ end
33
+
34
+ def test_push_uint24
35
+ assert_equal("\x11\x11\x24", "".push_uint24(0x111124))
36
+ end
37
+
38
+ def test_push_uint32
39
+ assert_equal("\x10\x00\x00\x24", "".push_uint32(0x10000024))
40
+ end
41
+
42
+ def test_push
43
+ assert_equal("adh", "ad".push("h"))
44
+ end
45
+
46
+ def test_byteshift
47
+ sushi = "\u{1f363}".encode(Encoding::UTF_8)
48
+ assert_equal("\xf0".b, sushi.byteshift(1).b)
49
+ assert_equal("\x9f\x8d\xa3".b, sushi.b)
50
+ end
51
+
52
+ def test_each_byteslice_block
53
+ ret = []
54
+ string = "12345678"
55
+ string.each_byteslice(3) {|part| ret << part }
56
+ assert_equal(["123", "456", "78"], ret)
57
+ end
58
+
59
+ def test_each_byteslice_enume
60
+ string = "12345678"
61
+ ret = string.each_byteslice(3)
62
+ assert_equal(["123", "456", "78"], ret.to_a)
63
+ end
64
+ end
@@ -0,0 +1,96 @@
1
+ require "test_helper"
2
+
3
+ using Plum::BinaryString
4
+
5
+ class ConnectionTest < Minitest::Test
6
+ def test_server_must_raise_frame_size_error_when_exeeeded_max_size
7
+ _settings = "".push_uint16(Frame::SETTINGS_TYPE[:max_frame_size]).push_uint32(2**14)
8
+ limit = 2 ** 14
9
+
10
+ new_con = -> (&blk) {
11
+ c = open_server_connection
12
+ c.settings(max_frame_size: limit)
13
+ blk.call c
14
+ }
15
+
16
+ new_con.call {|con|
17
+ assert_no_error {
18
+ con << Frame.new(type: :settings, stream_id: 0, payload: _settings * (limit / 6)).assemble
19
+ }
20
+ }
21
+ new_con.call {|con|
22
+ assert_connection_error(:frame_size_error) {
23
+ con << Frame.new(type: :settings, stream_id: 0, payload: _settings * (limit / 6 + 1)).assemble
24
+ }
25
+ }
26
+
27
+ new_con.call {|con|
28
+ assert_connection_error(:frame_size_error) {
29
+ con << Frame.new(type: :headers, stream_id: 3, payload: "\x00" * (limit + 1)).assemble
30
+ }
31
+ }
32
+ new_con.call {|con|
33
+ assert_stream_error(:frame_size_error) {
34
+ con << Frame.new(type: :headers, stream_id: 3, flags: [:end_headers], payload: "").assemble
35
+ con << Frame.new(type: :data, stream_id: 3, payload: "\x00" * (limit + 1)).assemble
36
+ }
37
+ }
38
+ end
39
+
40
+ def test_server_raise_cprotocol_error_illegal_control_stream
41
+ [:data, :headers, :priority, :rst_stream, :push_promise, :continuation].each do |type|
42
+ con = open_server_connection
43
+ assert_connection_error(:protocol_error) {
44
+ con << Frame.new(type: type, stream_id: 0).assemble
45
+ }
46
+ end
47
+ end
48
+
49
+ def test_server_ignore_unknown_frame_type
50
+ open_server_connection {|con|
51
+ assert_no_error {
52
+ con << "\x00\x00\x00\x0f\x00\x00\x00\x00\x00" # type: 0x0f, no flags, no payload, stream 0
53
+ }
54
+ }
55
+ end
56
+
57
+ def test_server_raise_cprotocol_error_client_start_even_stream_id
58
+ con = open_server_connection
59
+ assert_connection_error(:protocol_error) {
60
+ con << Frame.new(type: :headers, flags: [:end_headers], stream_id: 2).assemble
61
+ }
62
+ end
63
+
64
+ def test_server_raise_cprotocol_error_client_start_small_stream_id
65
+ con = open_server_connection
66
+ con << Frame.new(type: :headers, flags: [:end_headers], stream_id: 51).assemble
67
+ assert_connection_error(:protocol_error) {
68
+ con << Frame.new(type: :headers, flags: [:end_headers], stream_id: 31).assemble
69
+ }
70
+ end
71
+
72
+ def test_server_raise_cprotocol_error_invalid_continuation_state
73
+ prepare = -> &blk {
74
+ con = open_server_connection
75
+ con << Frame.new(type: :headers, flags: [:end_headers], stream_id: 1).assemble
76
+ con << Frame.new(type: :headers, flags: [:end_stream], stream_id: 3).assemble
77
+ blk.call(con)
78
+ }
79
+
80
+ prepare.call {|con|
81
+ assert_connection_error(:protocol_error) {
82
+ con << Frame.new(type: :data, stream_id: 1, payload: "hello").assemble
83
+ }
84
+ }
85
+ prepare.call {|con|
86
+ assert_connection_error(:protocol_error) {
87
+ con << Frame.new(type: :data, stream_id: 3, payload: "hello").assemble
88
+ }
89
+ }
90
+ prepare.call {|con|
91
+ assert_equal(:waiting_continuation, con.state)
92
+ con << Frame.new(type: :continuation, flags: [:end_headers], stream_id: 3, payload: "hello").assemble
93
+ assert_equal(:open, con.state)
94
+ }
95
+ end
96
+ end