http-2 0.6.3 → 0.7.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.
@@ -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