http-2 0.7.0 → 0.8.0
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/.rspec +1 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +46 -0
- data/.travis.yml +10 -2
- data/Gemfile +7 -5
- data/README.md +35 -37
- data/Rakefile +9 -10
- data/example/README.md +0 -13
- data/example/client.rb +12 -15
- data/example/helper.rb +2 -2
- data/example/server.rb +19 -19
- data/example/upgrade_server.rb +191 -0
- data/http-2.gemspec +10 -9
- data/lib/http/2.rb +13 -13
- data/lib/http/2/buffer.rb +6 -10
- data/lib/http/2/client.rb +5 -10
- data/lib/http/2/compressor.rb +134 -146
- data/lib/http/2/connection.rb +104 -100
- data/lib/http/2/emitter.rb +2 -4
- data/lib/http/2/error.rb +7 -7
- data/lib/http/2/flow_buffer.rb +11 -10
- data/lib/http/2/framer.rb +78 -87
- data/lib/http/2/huffman.rb +265 -274
- data/lib/http/2/huffman_statemachine.rb +257 -257
- data/lib/http/2/server.rb +81 -6
- data/lib/http/2/stream.rb +195 -130
- data/lib/http/2/version.rb +1 -1
- data/lib/tasks/generate_huffman_table.rb +30 -24
- data/spec/buffer_spec.rb +11 -13
- data/spec/client_spec.rb +41 -42
- data/spec/compressor_spec.rb +243 -242
- data/spec/connection_spec.rb +252 -248
- data/spec/emitter_spec.rb +12 -12
- data/spec/framer_spec.rb +177 -179
- data/spec/helper.rb +56 -57
- data/spec/huffman_spec.rb +33 -33
- data/spec/server_spec.rb +15 -15
- data/spec/stream_spec.rb +356 -265
- metadata +7 -6
- data/spec/hpack_test_spec.rb +0 -83
data/spec/emitter_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require 'helper'
|
2
2
|
|
3
|
-
describe HTTP2::Emitter do
|
3
|
+
RSpec.describe HTTP2::Emitter do
|
4
4
|
class Worker
|
5
5
|
include Emitter
|
6
6
|
end
|
@@ -10,45 +10,45 @@ describe HTTP2::Emitter do
|
|
10
10
|
@cnt = 0
|
11
11
|
end
|
12
12
|
|
13
|
-
it
|
13
|
+
it 'should raise error on missing callback' do
|
14
14
|
expect { @w.on(:a) {} }.to_not raise_error
|
15
15
|
expect { @w.on(:a) }.to raise_error
|
16
16
|
end
|
17
17
|
|
18
|
-
it
|
18
|
+
it 'should allow multiple callbacks on single event' do
|
19
19
|
@w.on(:a) { @cnt += 1 }
|
20
20
|
@w.on(:a) { @cnt += 1 }
|
21
21
|
@w.emit(:a)
|
22
22
|
|
23
|
-
@cnt.
|
23
|
+
expect(@cnt).to eq 2
|
24
24
|
end
|
25
25
|
|
26
|
-
it
|
26
|
+
it 'should execute callback with optional args' do
|
27
27
|
args = nil
|
28
28
|
@w.on(:a) { |a| args = a }
|
29
29
|
@w.emit(:a, 123)
|
30
30
|
|
31
|
-
args.
|
31
|
+
expect(args).to eq 123
|
32
32
|
end
|
33
33
|
|
34
|
-
it
|
34
|
+
it 'should pass emitted callbacks to listeners' do
|
35
35
|
@w.on(:a) { |&block| block.call }
|
36
36
|
@w.once(:a) { |&block| block.call }
|
37
37
|
@w.emit(:a) { @cnt += 1 }
|
38
38
|
|
39
|
-
@cnt.
|
39
|
+
expect(@cnt).to eq 2
|
40
40
|
end
|
41
41
|
|
42
|
-
it
|
42
|
+
it 'should allow events with no callbacks' do
|
43
43
|
expect { @w.emit(:missing) }.to_not raise_error
|
44
44
|
end
|
45
45
|
|
46
|
-
it
|
46
|
+
it 'should execute callback exactly once' do
|
47
47
|
@w.on(:a) { @cnt += 1 }
|
48
48
|
@w.once(:a) { @cnt += 1 }
|
49
49
|
@w.emit(:a)
|
50
50
|
@w.emit(:a)
|
51
51
|
|
52
|
-
@cnt.
|
52
|
+
expect(@cnt).to eq 3
|
53
53
|
end
|
54
54
|
end
|
data/spec/framer_spec.rb
CHANGED
@@ -1,105 +1,104 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
describe HTTP2::Framer do
|
1
|
+
require 'helper'
|
4
2
|
|
3
|
+
RSpec.describe HTTP2::Framer do
|
5
4
|
let(:f) { Framer.new }
|
6
5
|
|
7
|
-
context
|
8
|
-
let(:frame)
|
6
|
+
context 'common header' do
|
7
|
+
let(:frame) do
|
9
8
|
{
|
10
9
|
length: 4,
|
11
10
|
type: :headers,
|
12
11
|
flags: [:end_stream, :end_headers],
|
13
12
|
stream: 15,
|
14
13
|
}
|
15
|
-
|
14
|
+
end
|
16
15
|
|
17
|
-
let(:bytes) { [0,0x04, 0x01, 0x5, 0x0000000F].pack(
|
16
|
+
let(:bytes) { [0, 0x04, 0x01, 0x5, 0x0000000F].pack('CnCCN') }
|
18
17
|
|
19
|
-
it
|
20
|
-
f.
|
18
|
+
it 'should generate common 9 byte header' do
|
19
|
+
expect(f.common_header(frame)).to eq bytes
|
21
20
|
end
|
22
21
|
|
23
|
-
it
|
24
|
-
f.
|
22
|
+
it 'should parse common 9 byte header' do
|
23
|
+
expect(f.read_common_header(Buffer.new(bytes))).to eq frame
|
25
24
|
end
|
26
25
|
|
27
|
-
it
|
26
|
+
it 'should generate a large frame' do
|
28
27
|
f = Framer.new
|
29
|
-
f.max_frame_size = 2**24-1
|
28
|
+
f.max_frame_size = 2**24 - 1
|
30
29
|
frame = {
|
31
30
|
length: 2**18 + 2**16 + 17,
|
32
31
|
type: :headers,
|
33
32
|
flags: [:end_stream, :end_headers],
|
34
33
|
stream: 15,
|
35
34
|
}
|
36
|
-
bytes = [5, 17, 0x01, 0x5, 0x0000000F].pack(
|
37
|
-
f.
|
38
|
-
f.
|
35
|
+
bytes = [5, 17, 0x01, 0x5, 0x0000000F].pack('CnCCN')
|
36
|
+
expect(f.common_header(frame)).to eq bytes
|
37
|
+
expect(f.read_common_header(Buffer.new(bytes))).to eq frame
|
39
38
|
end
|
40
39
|
|
41
|
-
it
|
42
|
-
expect
|
40
|
+
it 'should raise exception on invalid frame type when sending' do
|
41
|
+
expect do
|
43
42
|
frame[:type] = :bogus
|
44
|
-
f.
|
45
|
-
|
43
|
+
f.common_header(frame)
|
44
|
+
end.to raise_error(CompressionError, /invalid.*type/i)
|
46
45
|
end
|
47
46
|
|
48
|
-
it
|
49
|
-
expect
|
47
|
+
it 'should raise exception on invalid stream ID' do
|
48
|
+
expect do
|
50
49
|
frame[:stream] = Framer::MAX_STREAM_ID + 1
|
51
|
-
f.
|
52
|
-
|
50
|
+
f.common_header(frame)
|
51
|
+
end.to raise_error(CompressionError, /stream/i)
|
53
52
|
end
|
54
53
|
|
55
|
-
it
|
56
|
-
expect
|
54
|
+
it 'should raise exception on invalid frame flag' do
|
55
|
+
expect do
|
57
56
|
frame[:flags] = [:bogus]
|
58
|
-
f.
|
59
|
-
|
57
|
+
f.common_header(frame)
|
58
|
+
end.to raise_error(CompressionError, /frame flag/)
|
60
59
|
end
|
61
60
|
|
62
|
-
it
|
63
|
-
expect
|
61
|
+
it 'should raise exception on invalid frame size' do
|
62
|
+
expect do
|
64
63
|
frame[:length] = 2**24
|
65
|
-
f.
|
66
|
-
|
64
|
+
f.common_header(frame)
|
65
|
+
end.to raise_error(CompressionError, /too large/)
|
67
66
|
end
|
68
67
|
end
|
69
68
|
|
70
|
-
context
|
71
|
-
it
|
69
|
+
context 'DATA' do
|
70
|
+
it 'should generate and parse bytes' do
|
72
71
|
frame = {
|
73
72
|
length: 4,
|
74
73
|
type: :data,
|
75
74
|
flags: [:end_stream],
|
76
75
|
stream: 1,
|
77
|
-
payload: 'text'
|
76
|
+
payload: 'text',
|
78
77
|
}
|
79
78
|
|
80
79
|
bytes = f.generate(frame)
|
81
|
-
bytes.
|
80
|
+
expect(bytes).to eq [0, 0x4, 0x0, 0x1, 0x1, *'text'.bytes].pack('CnCCNC*')
|
82
81
|
|
83
|
-
f.parse(bytes).
|
82
|
+
expect(f.parse(bytes)).to eq frame
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
87
|
-
context
|
88
|
-
it
|
86
|
+
context 'HEADERS' do
|
87
|
+
it 'should generate and parse bytes' do
|
89
88
|
frame = {
|
90
89
|
length: 12,
|
91
90
|
type: :headers,
|
92
91
|
flags: [:end_stream, :end_headers],
|
93
92
|
stream: 1,
|
94
|
-
payload: 'header-block'
|
93
|
+
payload: 'header-block',
|
95
94
|
}
|
96
95
|
|
97
96
|
bytes = f.generate(frame)
|
98
|
-
bytes.
|
99
|
-
f.parse(bytes).
|
97
|
+
expect(bytes).to eq [0, 0xc, 0x1, 0x5, 0x1, *'header-block'.bytes].pack('CnCCNC*')
|
98
|
+
expect(f.parse(bytes)).to eq frame
|
100
99
|
end
|
101
100
|
|
102
|
-
it
|
101
|
+
it 'should carry an optional stream priority' do
|
103
102
|
frame = {
|
104
103
|
length: 16,
|
105
104
|
type: :headers,
|
@@ -108,17 +107,17 @@ describe HTTP2::Framer do
|
|
108
107
|
stream_dependency: 15,
|
109
108
|
weight: 12,
|
110
109
|
exclusive: false,
|
111
|
-
payload: 'header-block'
|
110
|
+
payload: 'header-block',
|
112
111
|
}
|
113
112
|
|
114
113
|
bytes = f.generate(frame)
|
115
|
-
bytes.
|
116
|
-
f.parse(bytes).
|
114
|
+
expect(bytes).to eq [0, 0x11, 0x1, 0x24, 0x1, 0xf, 0xb, *'header-block'.bytes].pack('CnCCNNCC*')
|
115
|
+
expect(f.parse(bytes)).to eq frame
|
117
116
|
end
|
118
117
|
end
|
119
118
|
|
120
|
-
context
|
121
|
-
it
|
119
|
+
context 'PRIORITY' do
|
120
|
+
it 'should generate and parse bytes' do
|
122
121
|
frame = {
|
123
122
|
length: 5,
|
124
123
|
type: :priority,
|
@@ -129,28 +128,28 @@ describe HTTP2::Framer do
|
|
129
128
|
}
|
130
129
|
|
131
130
|
bytes = f.generate(frame)
|
132
|
-
bytes.
|
133
|
-
f.parse(bytes).
|
131
|
+
expect(bytes).to eq [0, 0x5, 0x2, 0x0, 0x1, 0x8000000f, 0xb].pack('CnCCNNC')
|
132
|
+
expect(f.parse(bytes)).to eq frame
|
134
133
|
end
|
135
134
|
end
|
136
135
|
|
137
|
-
context
|
138
|
-
it
|
136
|
+
context 'RST_STREAM' do
|
137
|
+
it 'should generate and parse bytes' do
|
139
138
|
frame = {
|
140
139
|
length: 4,
|
141
140
|
type: :rst_stream,
|
142
141
|
stream: 1,
|
143
|
-
error: :stream_closed
|
142
|
+
error: :stream_closed,
|
144
143
|
}
|
145
144
|
|
146
145
|
bytes = f.generate(frame)
|
147
|
-
bytes.
|
148
|
-
f.parse(bytes).
|
146
|
+
expect(bytes).to eq [0, 0x4, 0x3, 0x0, 0x1, 0x5].pack('CnCCNN')
|
147
|
+
expect(f.parse(bytes)).to eq frame
|
149
148
|
end
|
150
149
|
end
|
151
150
|
|
152
|
-
context
|
153
|
-
let(:frame)
|
151
|
+
context 'SETTINGS' do
|
152
|
+
let(:frame) do
|
154
153
|
{
|
155
154
|
type: :settings,
|
156
155
|
flags: [],
|
@@ -158,26 +157,26 @@ describe HTTP2::Framer do
|
|
158
157
|
payload: [
|
159
158
|
[:settings_max_concurrent_streams, 10],
|
160
159
|
[:settings_header_table_size, 2048],
|
161
|
-
]
|
160
|
+
],
|
162
161
|
}
|
163
|
-
|
162
|
+
end
|
164
163
|
|
165
|
-
it
|
164
|
+
it 'should generate and parse bytes' do
|
166
165
|
bytes = f.generate(frame)
|
167
|
-
bytes.
|
166
|
+
expect(bytes).to eq [0, 12, 0x4, 0x0, 0x0, 3, 10, 1, 2048].pack('CnCCNnNnN')
|
168
167
|
parsed = f.parse(bytes)
|
169
168
|
parsed.delete(:length)
|
170
169
|
frame.delete(:length)
|
171
|
-
parsed.
|
170
|
+
expect(parsed).to eq frame
|
172
171
|
end
|
173
172
|
|
174
|
-
it
|
173
|
+
it 'should generate settings when id is given as an integer' do
|
175
174
|
frame[:payload][1][0] = 1
|
176
175
|
bytes = f.generate(frame)
|
177
|
-
bytes.
|
176
|
+
expect(bytes).to eq [0, 12, 0x4, 0x0, 0x0, 3, 10, 1, 2048].pack('CnCCNnNnN')
|
178
177
|
end
|
179
178
|
|
180
|
-
it
|
179
|
+
it 'should ignore custom settings when sending' do
|
181
180
|
frame[:payload] = [
|
182
181
|
[:settings_max_concurrent_streams, 10],
|
183
182
|
[:settings_initial_window_size, 20],
|
@@ -187,10 +186,10 @@ describe HTTP2::Framer do
|
|
187
186
|
buf = f.generate(frame)
|
188
187
|
frame[:payload].slice!(2) # cut off the extension
|
189
188
|
frame[:length] = 12 # frame length should be computed WITHOUT extensions
|
190
|
-
f.parse(buf).
|
189
|
+
expect(f.parse(buf)).to eq frame
|
191
190
|
end
|
192
191
|
|
193
|
-
it
|
192
|
+
it 'should ignore custom settings when receiving' do
|
194
193
|
frame[:payload] = [
|
195
194
|
[:settings_max_concurrent_streams, 10],
|
196
195
|
[:settings_initial_window_size, 20],
|
@@ -202,165 +201,165 @@ describe HTTP2::Framer do
|
|
202
201
|
parsed = f.parse(buf)
|
203
202
|
parsed.delete(:length)
|
204
203
|
frame.delete(:length)
|
205
|
-
parsed.
|
204
|
+
expect(parsed).to eq frame
|
206
205
|
end
|
207
206
|
|
208
|
-
it
|
209
|
-
expect
|
207
|
+
it 'should raise exception on sending invalid stream ID' do
|
208
|
+
expect do
|
210
209
|
frame[:stream] = 1
|
211
210
|
f.generate(frame)
|
212
|
-
|
211
|
+
end.to raise_error(CompressionError, /Invalid stream ID/)
|
213
212
|
end
|
214
213
|
|
215
|
-
it
|
216
|
-
expect
|
214
|
+
it 'should raise exception on receiving invalid stream ID' do
|
215
|
+
expect do
|
217
216
|
buf = f.generate(frame)
|
218
217
|
buf.setbyte(8, 1)
|
219
218
|
f.parse(buf)
|
220
|
-
|
219
|
+
end.to raise_error(ProtocolError, /Invalid stream ID/)
|
221
220
|
end
|
222
221
|
|
223
|
-
it
|
224
|
-
expect
|
222
|
+
it 'should raise exception on sending invalid setting' do
|
223
|
+
expect do
|
225
224
|
frame[:payload] = [[:random, 23]]
|
226
225
|
f.generate(frame)
|
227
|
-
|
226
|
+
end.to raise_error(CompressionError, /Unknown settings ID/)
|
228
227
|
end
|
229
228
|
|
230
|
-
it
|
231
|
-
expect
|
229
|
+
it 'should raise exception on receiving invalid payload length' do
|
230
|
+
expect do
|
232
231
|
buf = f.generate(frame)
|
233
232
|
buf.setbyte(2, 11) # change payload length
|
234
233
|
f.parse(buf)
|
235
|
-
|
234
|
+
end.to raise_error(ProtocolError, /Invalid settings payload length/)
|
236
235
|
end
|
237
236
|
end
|
238
237
|
|
239
|
-
context
|
240
|
-
it
|
238
|
+
context 'PUSH_PROMISE' do
|
239
|
+
it 'should generate and parse bytes' do
|
241
240
|
frame = {
|
242
241
|
length: 11,
|
243
242
|
type: :push_promise,
|
244
243
|
flags: [:end_headers],
|
245
244
|
stream: 1,
|
246
245
|
promise_stream: 2,
|
247
|
-
payload: 'headers'
|
246
|
+
payload: 'headers',
|
248
247
|
}
|
249
248
|
|
250
249
|
bytes = f.generate(frame)
|
251
|
-
bytes.
|
252
|
-
f.parse(bytes).
|
250
|
+
expect(bytes).to eq [0, 0xb, 0x5, 0x4, 0x1, 0x2, *'headers'.bytes].pack('CnCCNNC*')
|
251
|
+
expect(f.parse(bytes)).to eq frame
|
253
252
|
end
|
254
253
|
end
|
255
254
|
|
256
|
-
context
|
257
|
-
let(:frame)
|
255
|
+
context 'PING' do
|
256
|
+
let(:frame) do
|
258
257
|
{
|
259
258
|
length: 8,
|
260
259
|
stream: 1,
|
261
260
|
type: :ping,
|
262
261
|
flags: [:ack],
|
263
|
-
payload: '12345678'
|
262
|
+
payload: '12345678',
|
264
263
|
}
|
265
|
-
|
264
|
+
end
|
266
265
|
|
267
|
-
it
|
266
|
+
it 'should generate and parse bytes' do
|
268
267
|
bytes = f.generate(frame)
|
269
|
-
bytes.
|
270
|
-
f.parse(bytes).
|
268
|
+
expect(bytes).to eq [0, 0x8, 0x6, 0x1, 0x1, *'12345678'.bytes].pack('CnCCNC*')
|
269
|
+
expect(f.parse(bytes)).to eq frame
|
271
270
|
end
|
272
271
|
|
273
|
-
it
|
274
|
-
expect
|
275
|
-
frame[:payload] =
|
272
|
+
it 'should raise exception on invalid payload' do
|
273
|
+
expect do
|
274
|
+
frame[:payload] = '1234'
|
276
275
|
f.generate(frame)
|
277
|
-
|
276
|
+
end.to raise_error(CompressionError, /Invalid payload size/)
|
278
277
|
end
|
279
278
|
end
|
280
279
|
|
281
|
-
context
|
282
|
-
let(:frame)
|
280
|
+
context 'GOAWAY' do
|
281
|
+
let(:frame) do
|
283
282
|
{
|
284
283
|
length: 13,
|
285
284
|
stream: 1,
|
286
285
|
type: :goaway,
|
287
286
|
last_stream: 2,
|
288
287
|
error: :no_error,
|
289
|
-
payload: 'debug'
|
288
|
+
payload: 'debug',
|
290
289
|
}
|
291
|
-
|
290
|
+
end
|
292
291
|
|
293
|
-
it
|
292
|
+
it 'should generate and parse bytes' do
|
294
293
|
bytes = f.generate(frame)
|
295
|
-
bytes.
|
296
|
-
f.parse(bytes).
|
294
|
+
expect(bytes).to eq [0, 0xd, 0x7, 0x0, 0x1, 0x2, 0x0, *'debug'.bytes].pack('CnCCNNNC*')
|
295
|
+
expect(f.parse(bytes)).to eq frame
|
297
296
|
end
|
298
297
|
|
299
|
-
it
|
298
|
+
it 'should treat debug payload as optional' do
|
300
299
|
frame.delete :payload
|
301
300
|
frame[:length] = 0x8
|
302
301
|
|
303
302
|
bytes = f.generate(frame)
|
304
|
-
bytes.
|
305
|
-
f.parse(bytes).
|
303
|
+
expect(bytes).to eq [0, 0x8, 0x7, 0x0, 0x1, 0x2, 0x0].pack('CnCCNNN')
|
304
|
+
expect(f.parse(bytes)).to eq frame
|
306
305
|
end
|
307
306
|
end
|
308
307
|
|
309
|
-
context
|
310
|
-
it
|
308
|
+
context 'WINDOW_UPDATE' do
|
309
|
+
it 'should generate and parse bytes' do
|
311
310
|
frame = {
|
312
311
|
length: 4,
|
313
312
|
type: :window_update,
|
314
|
-
increment: 10
|
313
|
+
increment: 10,
|
315
314
|
}
|
316
315
|
|
317
316
|
bytes = f.generate(frame)
|
318
|
-
bytes.
|
319
|
-
f.parse(bytes).
|
317
|
+
expect(bytes).to eq [0, 0x4, 0x8, 0x0, 0x0, 0xa].pack('CnCCNN')
|
318
|
+
expect(f.parse(bytes)).to eq frame
|
320
319
|
end
|
321
320
|
end
|
322
321
|
|
323
|
-
context
|
324
|
-
it
|
322
|
+
context 'CONTINUATION' do
|
323
|
+
it 'should generate and parse bytes' do
|
325
324
|
frame = {
|
326
325
|
length: 12,
|
327
326
|
type: :continuation,
|
328
327
|
stream: 1,
|
329
328
|
flags: [:end_headers],
|
330
|
-
payload: 'header-block'
|
329
|
+
payload: 'header-block',
|
331
330
|
}
|
332
331
|
|
333
332
|
bytes = f.generate(frame)
|
334
|
-
bytes.
|
335
|
-
f.parse(bytes).
|
333
|
+
expect(bytes).to eq [0, 0xc, 0x9, 0x4, 0x1, *'header-block'.bytes].pack('CnCCNC*')
|
334
|
+
expect(f.parse(bytes)).to eq frame
|
336
335
|
end
|
337
336
|
end
|
338
337
|
|
339
|
-
context
|
340
|
-
it
|
338
|
+
context 'ALTSVC' do
|
339
|
+
it 'should generate and parse bytes' do
|
341
340
|
frame = {
|
342
341
|
length: 44,
|
343
342
|
type: :altsvc,
|
344
343
|
stream: 1,
|
345
|
-
max_age:
|
344
|
+
max_age: 1_402_290_402, # 4
|
346
345
|
port: 8080, # 2
|
347
346
|
proto: 'h2-13', # 1 + 5
|
348
347
|
host: 'www.example.com', # 1 + 15
|
349
348
|
origin: 'www.example.com', # 15
|
350
349
|
}
|
351
350
|
bytes = f.generate(frame)
|
352
|
-
expected = [0, 43, 0xa, 0, 1,
|
353
|
-
expected << [5, *'h2-13'.bytes].pack(
|
354
|
-
expected << [15, *'www.example.com'.bytes].pack(
|
355
|
-
expected << [*'www.example.com'.bytes].pack(
|
356
|
-
bytes.
|
357
|
-
f.parse(bytes).
|
351
|
+
expected = [0, 43, 0xa, 0, 1, 1_402_290_402, 8080].pack('CnCCNNn')
|
352
|
+
expected << [5, *'h2-13'.bytes].pack('CC*')
|
353
|
+
expected << [15, *'www.example.com'.bytes].pack('CC*')
|
354
|
+
expected << [*'www.example.com'.bytes].pack('C*')
|
355
|
+
expect(bytes).to eq expected
|
356
|
+
expect(f.parse(bytes)).to eq frame
|
358
357
|
end
|
359
358
|
end
|
360
359
|
|
361
|
-
context
|
360
|
+
context 'Padding' do
|
362
361
|
[:data, :headers, :push_promise].each do |type|
|
363
|
-
[1,256].each do |padlen|
|
362
|
+
[1, 256].each do |padlen|
|
364
363
|
context "generating #{type} frame padded #{padlen}" do
|
365
364
|
before do
|
366
365
|
@frame = {
|
@@ -369,28 +368,28 @@ describe HTTP2::Framer do
|
|
369
368
|
stream: 1,
|
370
369
|
payload: 'example data',
|
371
370
|
}
|
372
|
-
|
371
|
+
@frame[:promise_stream] = 2 if type == :push_promise
|
373
372
|
@normal = f.generate(@frame)
|
374
|
-
@padded = f.generate(@frame.merge(:
|
373
|
+
@padded = f.generate(@frame.merge(padding: padlen))
|
375
374
|
end
|
376
|
-
it
|
377
|
-
@padded.bytesize.
|
375
|
+
it 'should generate a frame with padding' do
|
376
|
+
expect(@padded.bytesize).to eq @normal.bytesize + padlen
|
378
377
|
end
|
379
|
-
it
|
378
|
+
it 'should fill padded octets with zero' do
|
380
379
|
trailer_len = padlen - 1
|
381
|
-
@padded[-trailer_len, trailer_len].
|
380
|
+
expect(@padded[-trailer_len, trailer_len]).to match(/\A\0*\z/)
|
382
381
|
end
|
383
|
-
it
|
384
|
-
f.parse(Buffer.new(@padded)).
|
385
|
-
|
382
|
+
it 'should parse a frame with padding' do
|
383
|
+
expect(f.parse(Buffer.new(@padded))).to eq \
|
384
|
+
f.parse(Buffer.new(@normal)).merge(padding: padlen)
|
386
385
|
end
|
387
|
-
it
|
388
|
-
f.parse(Buffer.new(@padded))[:payload].
|
386
|
+
it 'should preserve payload' do
|
387
|
+
expect(f.parse(Buffer.new(@padded))[:payload]).to eq @frame[:payload]
|
389
388
|
end
|
390
389
|
end
|
391
390
|
end
|
392
391
|
end
|
393
|
-
context
|
392
|
+
context 'generating with invalid padding length' do
|
394
393
|
before do
|
395
394
|
@frame = {
|
396
395
|
length: 12,
|
@@ -399,23 +398,23 @@ describe HTTP2::Framer do
|
|
399
398
|
payload: 'example data',
|
400
399
|
}
|
401
400
|
end
|
402
|
-
[0, 257,1334].each do |padlen|
|
401
|
+
[0, 257, 1334].each do |padlen|
|
403
402
|
it "should raise error on trying to generate data frame padded with invalid #{padlen}" do
|
404
|
-
expect
|
405
|
-
f.generate(@frame.merge(:
|
406
|
-
|
403
|
+
expect do
|
404
|
+
f.generate(@frame.merge(padding: padlen))
|
405
|
+
end.to raise_error(CompressionError, /padding/i)
|
407
406
|
end
|
408
407
|
end
|
409
|
-
it
|
408
|
+
it 'should raise error when adding a padding would make frame too large' do
|
410
409
|
@frame[:payload] = 'q' * (f.max_frame_size - 200)
|
411
410
|
@frame[:length] = @frame[:payload].size
|
412
411
|
@frame[:padding] = 210 # would exceed 4096
|
413
|
-
expect
|
412
|
+
expect do
|
414
413
|
f.generate(@frame)
|
415
|
-
|
414
|
+
end.to raise_error(CompressionError, /padding/i)
|
416
415
|
end
|
417
416
|
end
|
418
|
-
context
|
417
|
+
context 'parsing frames with invalid paddings' do
|
419
418
|
before do
|
420
419
|
@frame = {
|
421
420
|
length: 12,
|
@@ -424,66 +423,65 @@ describe HTTP2::Framer do
|
|
424
423
|
payload: 'example data',
|
425
424
|
}
|
426
425
|
@padlen = 123
|
427
|
-
@padded = f.generate(@frame.merge(:
|
426
|
+
@padded = f.generate(@frame.merge(padding: @padlen))
|
428
427
|
end
|
429
|
-
it
|
430
|
-
@padded.setbyte(9,240)
|
428
|
+
it 'should raise exception when the given padding is longer than the payload' do
|
429
|
+
@padded.setbyte(9, 240)
|
431
430
|
expect { f.parse(Buffer.new(@padded)) }.to raise_error(ProtocolError, /padding/)
|
432
431
|
end
|
433
432
|
end
|
434
433
|
end
|
435
434
|
|
436
|
-
it
|
435
|
+
it 'should determine frame length' do
|
437
436
|
frames = [
|
438
|
-
[{type: :data, stream: 1, flags: [:end_stream], payload:
|
439
|
-
[{type: :headers, stream: 1, payload:
|
440
|
-
[{type: :priority, stream: 3, stream_dependency: 30, exclusive: false, weight: 1}, 5],
|
441
|
-
[{type: :rst_stream, stream: 3, error: 100}, 4],
|
442
|
-
[{type: :settings, payload: [[:settings_max_concurrent_streams, 10]]}, 6],
|
443
|
-
[{type: :push_promise, promise_stream: 5, payload:
|
444
|
-
[{type: :ping, payload:
|
445
|
-
[{type: :goaway, last_stream: 5, error: 20, payload:
|
446
|
-
[{type: :window_update, stream: 1, increment: 1024}, 4],
|
447
|
-
[{type: :continuation, stream: 1, payload:
|
437
|
+
[{ type: :data, stream: 1, flags: [:end_stream], payload: 'abc' }, 3],
|
438
|
+
[{ type: :headers, stream: 1, payload: 'abc' }, 3],
|
439
|
+
[{ type: :priority, stream: 3, stream_dependency: 30, exclusive: false, weight: 1 }, 5],
|
440
|
+
[{ type: :rst_stream, stream: 3, error: 100 }, 4],
|
441
|
+
[{ type: :settings, payload: [[:settings_max_concurrent_streams, 10]] }, 6],
|
442
|
+
[{ type: :push_promise, promise_stream: 5, payload: 'abc' }, 7],
|
443
|
+
[{ type: :ping, payload: 'blob' * 2 }, 8],
|
444
|
+
[{ type: :goaway, last_stream: 5, error: 20, payload: 'blob' }, 12],
|
445
|
+
[{ type: :window_update, stream: 1, increment: 1024 }, 4],
|
446
|
+
[{ type: :continuation, stream: 1, payload: 'abc' }, 3],
|
448
447
|
]
|
449
448
|
|
450
449
|
frames.each do |(frame, size)|
|
451
450
|
bytes = f.generate(frame)
|
452
|
-
bytes.slice(1,2).unpack(
|
453
|
-
bytes.readbyte(0).
|
451
|
+
expect(bytes.slice(1, 2).unpack('n').first).to eq size
|
452
|
+
expect(bytes.readbyte(0)).to eq 0
|
454
453
|
end
|
455
454
|
end
|
456
455
|
|
457
|
-
it
|
456
|
+
it 'should parse single frame at a time' do
|
458
457
|
frames = [
|
459
|
-
{type: :headers, stream: 1, payload:
|
460
|
-
{type: :data, stream: 1, flags: [:end_stream], payload:
|
458
|
+
{ type: :headers, stream: 1, payload: 'headers' },
|
459
|
+
{ type: :data, stream: 1, flags: [:end_stream], payload: 'abc' },
|
461
460
|
]
|
462
461
|
|
463
462
|
buf = f.generate(frames[0]) << f.generate(frames[1])
|
464
463
|
|
465
|
-
f.parse(buf).
|
466
|
-
f.parse(buf).
|
464
|
+
expect(f.parse(buf)).to eq frames[0]
|
465
|
+
expect(f.parse(buf)).to eq frames[1]
|
467
466
|
end
|
468
467
|
|
469
|
-
it
|
470
|
-
frame = {type: :headers, stream: 1, payload:
|
468
|
+
it 'should process full frames only' do
|
469
|
+
frame = { type: :headers, stream: 1, payload: 'headers' }
|
471
470
|
bytes = f.generate(frame)
|
472
471
|
|
473
|
-
f.parse(bytes[0...-1]).
|
474
|
-
f.parse(bytes).
|
475
|
-
bytes.
|
472
|
+
expect(f.parse(bytes[0...-1])).to be_nil
|
473
|
+
expect(f.parse(bytes)).to eq frame
|
474
|
+
expect(bytes).to be_empty
|
476
475
|
end
|
477
476
|
|
478
|
-
it
|
479
|
-
frame = {type: :headers, stream: 1, payload:
|
477
|
+
it 'should ignore unknown extension frames' do
|
478
|
+
frame = { type: :headers, stream: 1, payload: 'headers' }
|
480
479
|
bytes = f.generate(frame)
|
481
480
|
bytes = Buffer.new(bytes + bytes) # Two HEADERS frames in bytes
|
482
481
|
bytes.setbyte(3, 42) # Make the first unknown type 42
|
483
482
|
|
484
|
-
f.parse(bytes).
|
485
|
-
f.parse(bytes).
|
486
|
-
bytes.
|
483
|
+
expect(f.parse(bytes)).to be_nil # first frame should be ignored
|
484
|
+
expect(f.parse(bytes)).to eq frame # should generate only one HEADERS
|
485
|
+
expect(bytes).to be_empty
|
487
486
|
end
|
488
|
-
|
489
487
|
end
|