http-2 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,22 +9,36 @@ describe HTTP2::Framer do
9
9
  {
10
10
  length: 4,
11
11
  type: :headers,
12
- flags: [:end_stream, :reserved, :end_headers],
12
+ flags: [:end_stream, :end_headers],
13
13
  stream: 15,
14
14
  }
15
15
  }
16
16
 
17
- let(:bytes) { [0x04, 0x01, 0x7, 0x0000000F].pack("nCCN") }
17
+ let(:bytes) { [0,0x04, 0x01, 0x5, 0x0000000F].pack("CnCCN") }
18
18
 
19
- it "should generate common 8 byte header" do
19
+ it "should generate common 9 byte header" do
20
20
  f.commonHeader(frame).should eq bytes
21
21
  end
22
22
 
23
- it "should parse common 8 byte header" do
23
+ it "should parse common 9 byte header" do
24
24
  f.readCommonHeader(Buffer.new(bytes)).should eq frame
25
25
  end
26
26
 
27
- it "should raise exception on invalid frame type" do
27
+ it "should generate a large frame" do
28
+ f = Framer.new
29
+ f.max_frame_size = 2**24-1
30
+ frame = {
31
+ length: 2**18 + 2**16 + 17,
32
+ type: :headers,
33
+ flags: [:end_stream, :end_headers],
34
+ stream: 15,
35
+ }
36
+ bytes = [5, 17, 0x01, 0x5, 0x0000000F].pack("CnCCN")
37
+ f.commonHeader(frame).should eq bytes
38
+ f.readCommonHeader(Buffer.new(bytes)).should eq frame
39
+ end
40
+
41
+ it "should raise exception on invalid frame type when sending" do
28
42
  expect {
29
43
  frame[:type] = :bogus
30
44
  f.commonHeader(frame)
@@ -47,7 +61,7 @@ describe HTTP2::Framer do
47
61
 
48
62
  it "should raise exception on invalid frame size" do
49
63
  expect {
50
- frame[:length] = 2**16
64
+ frame[:length] = 2**24
51
65
  f.commonHeader(frame)
52
66
  }.to raise_error(CompressionError, /too large/)
53
67
  end
@@ -58,13 +72,13 @@ describe HTTP2::Framer do
58
72
  frame = {
59
73
  length: 4,
60
74
  type: :data,
61
- flags: [:end_stream, :reserved],
75
+ flags: [:end_stream],
62
76
  stream: 1,
63
77
  payload: 'text'
64
78
  }
65
79
 
66
80
  bytes = f.generate(frame)
67
- bytes.should eq [0x4,0x0,0x3,0x1,*'text'.bytes].pack("nCCNC*")
81
+ bytes.should eq [0,0x4,0x0,0x1,0x1,*'text'.bytes].pack("CnCCNC*")
68
82
 
69
83
  f.parse(bytes).should eq frame
70
84
  end
@@ -75,13 +89,13 @@ describe HTTP2::Framer do
75
89
  frame = {
76
90
  length: 12,
77
91
  type: :headers,
78
- flags: [:end_stream, :reserved, :end_headers],
92
+ flags: [:end_stream, :end_headers],
79
93
  stream: 1,
80
94
  payload: 'header-block'
81
95
  }
82
96
 
83
97
  bytes = f.generate(frame)
84
- bytes.should eq [0xc,0x1,0x7,0x1,*'header-block'.bytes].pack("nCCNC*")
98
+ bytes.should eq [0,0xc,0x1,0x5,0x1,*'header-block'.bytes].pack("CnCCNC*")
85
99
  f.parse(bytes).should eq frame
86
100
  end
87
101
 
@@ -89,14 +103,16 @@ describe HTTP2::Framer do
89
103
  frame = {
90
104
  length: 16,
91
105
  type: :headers,
92
- flags: [:end_headers, :priority],
106
+ flags: [:end_headers],
93
107
  stream: 1,
94
- priority: 15,
108
+ stream_dependency: 15,
109
+ weight: 12,
110
+ exclusive: false,
95
111
  payload: 'header-block'
96
112
  }
97
113
 
98
114
  bytes = f.generate(frame)
99
- bytes.should eq [0x10,0x1,0xc,0x1,0xf,*'header-block'.bytes].pack("nCCNNC*")
115
+ bytes.should eq [0,0x11,0x1,0x24,0x1,0xf,0xb,*'header-block'.bytes].pack("CnCCNNCC*")
100
116
  f.parse(bytes).should eq frame
101
117
  end
102
118
  end
@@ -104,14 +120,16 @@ describe HTTP2::Framer do
104
120
  context "PRIORITY" do
105
121
  it "should generate and parse bytes" do
106
122
  frame = {
107
- length: 4,
123
+ length: 5,
108
124
  type: :priority,
109
125
  stream: 1,
110
- priority: 15
126
+ stream_dependency: 15,
127
+ weight: 12,
128
+ exclusive: true,
111
129
  }
112
130
 
113
131
  bytes = f.generate(frame)
114
- bytes.should eq [0x4,0x2,0x0,0x1,0xf].pack("nCCNN")
132
+ bytes.should eq [0,0x5,0x2,0x0,0x1,0x8000000f,0xb].pack("CnCCNNC")
115
133
  f.parse(bytes).should eq frame
116
134
  end
117
135
  end
@@ -126,7 +144,7 @@ describe HTTP2::Framer do
126
144
  }
127
145
 
128
146
  bytes = f.generate(frame)
129
- bytes.should eq [0x4,0x3,0x0,0x1,0x5].pack("nCCNN")
147
+ bytes.should eq [0,0x4,0x3,0x0,0x1,0x5].pack("CnCCNN")
130
148
  f.parse(bytes).should eq frame
131
149
  end
132
150
  end
@@ -134,47 +152,88 @@ describe HTTP2::Framer do
134
152
  context "SETTINGS" do
135
153
  let(:frame) {
136
154
  {
137
- length: 8,
138
155
  type: :settings,
139
156
  flags: [],
140
157
  stream: 0,
141
- payload: {
142
- settings_max_concurrent_streams: 10
143
- }
158
+ payload: [
159
+ [:settings_max_concurrent_streams, 10],
160
+ [:settings_header_table_size, 2048],
161
+ ]
144
162
  }
145
163
  }
146
164
 
147
165
  it "should generate and parse bytes" do
166
+ bytes = f.generate(frame)
167
+ bytes.should eq [0,12,0x4,0x0,0x0,3,10,1,2048].pack("CnCCNnNnN")
168
+ parsed = f.parse(bytes)
169
+ parsed.delete(:length)
170
+ frame.delete(:length)
171
+ parsed.should eq frame
172
+ end
148
173
 
174
+ it "should generate settings when id is given as an integer" do
175
+ frame[:payload][1][0] = 1
149
176
  bytes = f.generate(frame)
150
- bytes.should eq [0x8,0x4,0x0,0x0,0x4,0xa].pack("nCCNNN")
151
- f.parse(bytes).should eq frame
177
+ bytes.should eq [0,12,0x4,0x0,0x0,3,10,1,2048].pack("CnCCNnNnN")
152
178
  end
153
179
 
154
- it "should ignore custom settings" do
155
- frame[:length] = 8*2
156
- frame[:payload] = {
157
- settings_max_concurrent_streams: 10,
158
- settings_initial_window_size: 20
159
- }
180
+ it "should ignore custom settings when sending" do
181
+ frame[:payload] = [
182
+ [:settings_max_concurrent_streams, 10],
183
+ [:settings_initial_window_size, 20],
184
+ [55, 30],
185
+ ]
160
186
 
161
- buf = f.generate(frame.merge({55 => 30}))
187
+ buf = f.generate(frame)
188
+ frame[:payload].slice!(2) # cut off the extension
189
+ frame[:length] = 12 # frame length should be computed WITHOUT extensions
162
190
  f.parse(buf).should eq frame
163
191
  end
164
192
 
165
- it "should raise exception on invalid stream ID" do
193
+ it "should ignore custom settings when receiving" do
194
+ frame[:payload] = [
195
+ [:settings_max_concurrent_streams, 10],
196
+ [:settings_initial_window_size, 20],
197
+ ]
198
+
199
+ buf = f.generate(frame)
200
+ buf.setbyte(2, 18) # add 6 to the frame length
201
+ buf << "\x00\x37\x00\x00\x00\x1e"
202
+ parsed = f.parse(buf)
203
+ parsed.delete(:length)
204
+ frame.delete(:length)
205
+ parsed.should eq frame
206
+ end
207
+
208
+ it "should raise exception on sending invalid stream ID" do
166
209
  expect {
167
210
  frame[:stream] = 1
168
211
  f.generate(frame)
169
212
  }.to raise_error(CompressionError, /Invalid stream ID/)
170
213
  end
171
214
 
172
- it "should raise exception on invalid setting" do
215
+ it "should raise exception on receiving invalid stream ID" do
216
+ expect {
217
+ buf = f.generate(frame)
218
+ buf.setbyte(8, 1)
219
+ f.parse(buf)
220
+ }.to raise_error(ProtocolError, /Invalid stream ID/)
221
+ end
222
+
223
+ it "should raise exception on sending invalid setting" do
173
224
  expect {
174
- frame[:payload] = {random: 23}
225
+ frame[:payload] = [[:random, 23]]
175
226
  f.generate(frame)
176
227
  }.to raise_error(CompressionError, /Unknown settings ID/)
177
228
  end
229
+
230
+ it "should raise exception on receiving invalid payload length" do
231
+ expect {
232
+ buf = f.generate(frame)
233
+ buf.setbyte(2, 11) # change payload length
234
+ f.parse(buf)
235
+ }.to raise_error(ProtocolError, /Invalid settings payload length/)
236
+ end
178
237
  end
179
238
 
180
239
  context "PUSH_PROMISE" do
@@ -182,14 +241,14 @@ describe HTTP2::Framer do
182
241
  frame = {
183
242
  length: 11,
184
243
  type: :push_promise,
185
- flags: [:end_push_promise],
244
+ flags: [:end_headers],
186
245
  stream: 1,
187
246
  promise_stream: 2,
188
247
  payload: 'headers'
189
248
  }
190
249
 
191
250
  bytes = f.generate(frame)
192
- bytes.should eq [0xb,0x5,0x1,0x1,0x2,*'headers'.bytes].pack("nCCNNC*")
251
+ bytes.should eq [0,0xb,0x5,0x4,0x1,0x2,*'headers'.bytes].pack("CnCCNNC*")
193
252
  f.parse(bytes).should eq frame
194
253
  end
195
254
  end
@@ -200,14 +259,14 @@ describe HTTP2::Framer do
200
259
  length: 8,
201
260
  stream: 1,
202
261
  type: :ping,
203
- flags: [:pong],
262
+ flags: [:ack],
204
263
  payload: '12345678'
205
264
  }
206
265
  }
207
266
 
208
267
  it "should generate and parse bytes" do
209
268
  bytes = f.generate(frame)
210
- bytes.should eq [0x8,0x6,0x1,0x1,*'12345678'.bytes].pack("nCCNC*")
269
+ bytes.should eq [0,0x8,0x6,0x1,0x1,*'12345678'.bytes].pack("CnCCNC*")
211
270
  f.parse(bytes).should eq frame
212
271
  end
213
272
 
@@ -233,7 +292,7 @@ describe HTTP2::Framer do
233
292
 
234
293
  it "should generate and parse bytes" do
235
294
  bytes = f.generate(frame)
236
- bytes.should eq [0xd,0x7,0x0,0x1,0x2,0x0,*'debug'.bytes].pack("nCCNNNC*")
295
+ bytes.should eq [0,0xd,0x7,0x0,0x1,0x2,0x0,*'debug'.bytes].pack("CnCCNNNC*")
237
296
  f.parse(bytes).should eq frame
238
297
  end
239
298
 
@@ -242,7 +301,7 @@ describe HTTP2::Framer do
242
301
  frame[:length] = 0x8
243
302
 
244
303
  bytes = f.generate(frame)
245
- bytes.should eq [0x8,0x7,0x0,0x1,0x2,0x0].pack("nCCNNN")
304
+ bytes.should eq [0,0x8,0x7,0x0,0x1,0x2,0x0].pack("CnCCNNN")
246
305
  f.parse(bytes).should eq frame
247
306
  end
248
307
  end
@@ -256,7 +315,7 @@ describe HTTP2::Framer do
256
315
  }
257
316
 
258
317
  bytes = f.generate(frame)
259
- bytes.should eq [0x4,0x9,0x0,0x0,0xa].pack("nCCNN")
318
+ bytes.should eq [0,0x4,0x8,0x0,0x0,0xa].pack("CnCCNN")
260
319
  f.parse(bytes).should eq frame
261
320
  end
262
321
  end
@@ -267,23 +326,120 @@ describe HTTP2::Framer do
267
326
  length: 12,
268
327
  type: :continuation,
269
328
  stream: 1,
270
- flags: [:end_stream, :end_headers],
329
+ flags: [:end_headers],
271
330
  payload: 'header-block'
272
331
  }
273
332
 
274
333
  bytes = f.generate(frame)
275
- bytes.should eq [0xc,0xa,0x3,0x1,*'header-block'.bytes].pack("nCCNC*")
334
+ bytes.should eq [0,0xc,0x9,0x4,0x1,*'header-block'.bytes].pack("CnCCNC*")
276
335
  f.parse(bytes).should eq frame
277
336
  end
278
337
  end
279
338
 
339
+ context "ALTSVC" do
340
+ it "should generate and parse bytes" do
341
+ frame = {
342
+ length: 44,
343
+ type: :altsvc,
344
+ stream: 1,
345
+ max_age: 1402290402, # 4
346
+ port: 8080, # 2
347
+ proto: 'h2-13', # 1 + 5
348
+ host: 'www.example.com', # 1 + 15
349
+ origin: 'www.example.com', # 15
350
+ }
351
+ bytes = f.generate(frame)
352
+ expected = [0, 43, 0xa, 0, 1, 1402290402, 8080].pack("CnCCNNn")
353
+ expected << [5, *'h2-13'.bytes].pack("CC*")
354
+ expected << [15, *'www.example.com'.bytes].pack("CC*")
355
+ expected << [*'www.example.com'.bytes].pack("C*")
356
+ bytes.should eq expected
357
+ f.parse(bytes).should eq frame
358
+ end
359
+ end
360
+
361
+ context "Padding" do
362
+ [:data, :headers, :push_promise].each do |type|
363
+ [1,256].each do |padlen|
364
+ context "generating #{type} frame padded #{padlen}" do
365
+ before do
366
+ @frame = {
367
+ length: 12,
368
+ type: type,
369
+ stream: 1,
370
+ payload: 'example data',
371
+ }
372
+ type == :push_promise and @frame[:promise_stream] = 2
373
+ @normal = f.generate(@frame)
374
+ @padded = f.generate(@frame.merge(:padding => padlen))
375
+ end
376
+ it "should generate a frame with padding" do
377
+ @padded.bytesize.should eq @normal.bytesize + padlen
378
+ end
379
+ it "should fill padded octets with zero" do
380
+ trailer_len = padlen - 1
381
+ @padded[-trailer_len, trailer_len].should match(/\A\0*\z/)
382
+ end
383
+ it "should parse a frame with padding" do
384
+ f.parse(Buffer.new(@padded)).should eq \
385
+ f.parse(Buffer.new(@normal)).merge(:padding => padlen)
386
+ end
387
+ it "should preserve payload" do
388
+ f.parse(Buffer.new(@padded))[:payload].should eq @frame[:payload]
389
+ end
390
+ end
391
+ end
392
+ end
393
+ context "generating with invalid padding length" do
394
+ before do
395
+ @frame = {
396
+ length: 12,
397
+ type: :data,
398
+ stream: 1,
399
+ payload: 'example data',
400
+ }
401
+ end
402
+ [0, 257,1334].each do |padlen|
403
+ it "should raise error on trying to generate data frame padded with invalid #{padlen}" do
404
+ expect {
405
+ f.generate(@frame.merge(:padding => padlen))
406
+ }.to raise_error(CompressionError, /padding/i)
407
+ end
408
+ end
409
+ it "should raise error when adding a padding would make frame too large" do
410
+ @frame[:payload] = 'q' * (f.max_frame_size - 200)
411
+ @frame[:length] = @frame[:payload].size
412
+ @frame[:padding] = 210 # would exceed 4096
413
+ expect {
414
+ f.generate(@frame)
415
+ }.to raise_error(CompressionError, /padding/i)
416
+ end
417
+ end
418
+ context "parsing frames with invalid paddings" do
419
+ before do
420
+ @frame = {
421
+ length: 12,
422
+ type: :data,
423
+ stream: 1,
424
+ payload: 'example data',
425
+ }
426
+ @padlen = 123
427
+ @padded = f.generate(@frame.merge(:padding => @padlen))
428
+ end
429
+ it "should raise exception when the given padding is longer than the payload" do
430
+ @padded.setbyte(9,240)
431
+ expect { f.parse(Buffer.new(@padded)) }.to raise_error(ProtocolError, /padding/)
432
+ end
433
+ end
434
+ end
435
+
280
436
  it "should determine frame length" do
281
437
  frames = [
282
438
  [{type: :data, stream: 1, flags: [:end_stream], payload: "abc"}, 3],
283
439
  [{type: :headers, stream: 1, payload: "abc"}, 3],
284
- [{type: :priority, stream: 3, priority: 30}, 4],
440
+ [{type: :priority, stream: 3, stream_dependency: 30, exclusive: false, weight: 1}, 5],
285
441
  [{type: :rst_stream, stream: 3, error: 100}, 4],
286
- [{type: :settings, payload: {settings_max_concurrent_streams: 10}}, 8],
442
+ [{type: :settings, payload: [[:settings_max_concurrent_streams, 10]]}, 6],
287
443
  [{type: :push_promise, promise_stream: 5, payload: "abc"}, 7],
288
444
  [{type: :ping, payload: "blob"*2}, 8],
289
445
  [{type: :goaway, last_stream: 5, error: 20, payload: "blob"}, 12],
@@ -293,7 +449,8 @@ describe HTTP2::Framer do
293
449
 
294
450
  frames.each do |(frame, size)|
295
451
  bytes = f.generate(frame)
296
- bytes.slice(0,2).unpack("n").first.should eq size
452
+ bytes.slice(1,2).unpack("n").first.should eq size
453
+ bytes.readbyte(0).should eq 0
297
454
  end
298
455
  end
299
456
 
@@ -318,4 +475,15 @@ describe HTTP2::Framer do
318
475
  bytes.should be_empty
319
476
  end
320
477
 
478
+ it "should ignore unknown extension frames" do
479
+ frame = {type: :headers, stream: 1, payload: "headers"}
480
+ bytes = f.generate(frame)
481
+ bytes = Buffer.new(bytes + bytes) # Two HEADERS frames in bytes
482
+ bytes.setbyte(3, 42) # Make the first unknown type 42
483
+
484
+ f.parse(bytes).should be_nil # first frame should be ignored
485
+ f.parse(bytes).should eq frame # should generate only one HEADERS
486
+ bytes.should be_empty
487
+ end
488
+
321
489
  end
@@ -1,9 +1,25 @@
1
- require 'http/2'
1
+ begin
2
+ if RSpec::Core::Version::STRING.to_i >= 3
3
+ # Disable deprecation warnings for newer RSpec
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |c|
6
+ c.syntax = [:should, :expect]
7
+ end
8
+ config.mock_with :rspec do |c|
9
+ c.syntax = [:should, :expect]
10
+ end
11
+ end
12
+ end
13
+ rescue Exception
14
+ end
15
+
2
16
  require 'json'
3
17
  require 'coveralls'
4
18
 
5
19
  Coveralls.wear! if ENV["CI"]
6
20
 
21
+ require 'http/2'
22
+
7
23
  include HTTP2
8
24
  include HTTP2::Header
9
25
  include HTTP2::Error
@@ -19,20 +35,22 @@ HEADERS = {
19
35
  type: :headers,
20
36
  flags: [:end_headers],
21
37
  stream: 1,
22
- payload: 'header-block'
38
+ payload: Compressor.new.encode([['a','b']])
23
39
  }
24
40
 
25
41
  HEADERS_END_STREAM = {
26
42
  type: :headers,
27
43
  flags: [:end_headers, :end_stream],
28
44
  stream: 1,
29
- payload: 'header-block'
45
+ payload: Compressor.new.encode([['a','b']])
30
46
  }
31
47
 
32
48
  PRIORITY = {
33
49
  type: :priority,
34
50
  stream: 1,
35
- priority: 15
51
+ exclusive: false,
52
+ stream_dependency: 0,
53
+ weight: 20,
36
54
  }
37
55
 
38
56
  RST_STREAM = {
@@ -44,18 +62,18 @@ RST_STREAM = {
44
62
  SETTINGS = {
45
63
  type: :settings,
46
64
  stream: 0,
47
- payload: {
48
- settings_max_concurrent_streams: 10,
49
- settings_flow_control_options: 1
50
- }
65
+ payload: [
66
+ [:settings_max_concurrent_streams, 10],
67
+ [:settings_initial_window_size, 0x7fffffff],
68
+ ]
51
69
  }
52
70
 
53
71
  PUSH_PROMISE = {
54
72
  type: :push_promise,
55
- flags: [:end_push_promise],
73
+ flags: [:end_headers],
56
74
  stream: 1,
57
75
  promise_stream: 2,
58
- payload: 'headers'
76
+ payload: Compressor.new.encode([['a','b']])
59
77
  }
60
78
 
61
79
  PING = {
@@ -67,7 +85,7 @@ PING = {
67
85
  PONG = {
68
86
  stream: 0,
69
87
  type: :ping,
70
- flags: [:pong],
88
+ flags: [:ack],
71
89
  payload: '12345678'
72
90
  }
73
91
 
@@ -89,14 +107,23 @@ CONTINUATION = {
89
107
  payload: '-second-block'
90
108
  }
91
109
 
110
+ ALTSVC = {
111
+ type: :altsvc,
112
+ max_age: 1402290402, # 4
113
+ port: 8080, # 2 reserved 1
114
+ proto: 'h2-12', # 1 + 5
115
+ host: 'www.example.com', # 1 + 15
116
+ origin: 'www.example.com', # 15
117
+ }
118
+
92
119
  FRAME_TYPES = [
93
120
  DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PUSH_PROMISE,
94
- PING, GOAWAY, WINDOW_UPDATE, CONTINUATION
121
+ PING, GOAWAY, WINDOW_UPDATE, CONTINUATION, ALTSVC
95
122
  ]
96
123
 
97
124
  def set_stream_id(bytes, id)
98
- head = bytes.slice!(0,8).unpack('nCCN')
99
- head[3] = id
125
+ head = bytes.slice!(0,9).unpack('CnCCN')
126
+ head[4] = id
100
127
 
101
- head.pack('nCCN') + bytes
128
+ head.pack('CnCCN') + bytes
102
129
  end