http-2 0.9.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,9 +20,9 @@ module HTTP2
20
20
 
21
21
  DEFAULT_CONNECTION_SETTINGS = {
22
22
  settings_header_table_size: 4096,
23
- settings_enable_push: 1, # enabled for servers
23
+ settings_enable_push: 1, # enabled for servers
24
24
  settings_max_concurrent_streams: 100,
25
- settings_initial_window_size: 65_535, #
25
+ settings_initial_window_size: 65_535,
26
26
  settings_max_frame_size: 16_384,
27
27
  settings_max_header_list_size: 2**31 - 1, # unlimited
28
28
  }.freeze
@@ -33,6 +33,9 @@ module HTTP2
33
33
  # Default connection "fast-fail" preamble string as defined by the spec.
34
34
  CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
35
35
 
36
+ # Time to hold recently closed streams until purge (seconds)
37
+ RECENTLY_CLOSED_STREAMS_TTL = 15
38
+
36
39
  # Connection encapsulates all of the connection, stream, flow-control,
37
40
  # error management, and other processing logic required for a well-behaved
38
41
  # HTTP 2.0 endpoint.
@@ -49,9 +52,6 @@ module HTTP2
49
52
  # Connection state (:new, :closed).
50
53
  attr_reader :state
51
54
 
52
- # Last connection error if connection is aborted.
53
- attr_reader :error
54
-
55
55
  # Size of current connection flow control window (by default, set to
56
56
  # infinity, but is automatically updated on receipt of peer settings).
57
57
  attr_reader :local_window
@@ -76,8 +76,8 @@ module HTTP2
76
76
  @local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
77
77
  @remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
78
78
 
79
- @compressor = Header::Compressor.new(settings)
80
- @decompressor = Header::Decompressor.new(settings)
79
+ @compressor = Header::Compressor.new(**settings)
80
+ @decompressor = Header::Decompressor.new(**settings)
81
81
 
82
82
  @active_stream_count = 0
83
83
  @streams = {}
@@ -95,6 +95,13 @@ module HTTP2
95
95
  @send_buffer = []
96
96
  @continuation = []
97
97
  @error = nil
98
+
99
+ @h2c_upgrade = nil
100
+ @closed_since = nil
101
+ end
102
+
103
+ def closed?
104
+ @state == :closed
98
105
  end
99
106
 
100
107
  # Allocates new stream for current connection.
@@ -141,6 +148,7 @@ module HTTP2
141
148
  send(type: :goaway, last_stream: last_stream,
142
149
  error: error, payload: payload)
143
150
  @state = :closed
151
+ @closed_since = Time.now
144
152
  end
145
153
 
146
154
  # Sends a WINDOW_UPDATE frame to the peer.
@@ -351,11 +359,14 @@ module HTTP2
351
359
  end
352
360
  end
353
361
 
354
- rescue => e
362
+ rescue StandardError => e
355
363
  raise if e.is_a?(Error::Error)
356
364
  connection_error(e: e)
357
365
  end
358
- alias << receive
366
+
367
+ def <<(*args)
368
+ receive(*args)
369
+ end
359
370
 
360
371
  private
361
372
 
@@ -443,12 +454,22 @@ module HTTP2
443
454
  # the connection, although a new connection can be established
444
455
  # for new streams.
445
456
  @state = :closed
457
+ @closed_since = Time.now
446
458
  emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
447
- when :altsvc, :blocked
459
+ when :altsvc
460
+ # 4. The ALTSVC HTTP/2 Frame
461
+ # An ALTSVC frame on stream 0 with empty (length 0) "Origin"
462
+ # information is invalid and MUST be ignored.
463
+ if frame[:origin] && !frame[:origin].empty?
464
+ emit(frame[:type], frame)
465
+ end
466
+ when :blocked
448
467
  emit(frame[:type], frame)
449
468
  else
450
469
  connection_error
451
470
  end
471
+ when :closed
472
+ connection_error if (Time.now - @closed_since) > 15
452
473
  else
453
474
  connection_error
454
475
  end
@@ -493,7 +514,7 @@ module HTTP2
493
514
  # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
494
515
  # Values outside this range MUST be treated as a connection error
495
516
  # (Section 5.4.1) of type PROTOCOL_ERROR.
496
- unless 16_384 <= v && v <= 16_777_215
517
+ unless v >= 16_384 && v <= 16_777_215
497
518
  return ProtocolError.new("invalid #{key} value")
498
519
  end
499
520
  when :settings_max_header_list_size
@@ -518,7 +539,7 @@ module HTTP2
518
539
  # Process pending settings we have sent.
519
540
  [@pending_settings.shift, :local]
520
541
  else
521
- connection_error(check) if validate_settings(@remote_role, frame[:payload])
542
+ connection_error if validate_settings(@remote_role, frame[:payload])
522
543
  [frame[:payload], :remote]
523
544
  end
524
545
 
@@ -572,7 +593,8 @@ module HTTP2
572
593
  # nothing to do
573
594
 
574
595
  when :settings_max_frame_size
575
- # nothing to do
596
+ # update framer max_frame_size
597
+ @framer.max_frame_size = v
576
598
 
577
599
  # else # ignore unknown settings
578
600
  end
@@ -603,8 +625,12 @@ module HTTP2
603
625
  frame[:payload] = @decompressor.decode(frame[:payload])
604
626
  end
605
627
 
606
- rescue => e
628
+ rescue CompressionError => e
607
629
  connection_error(:compression_error, e: e)
630
+ rescue ProtocolError => e
631
+ connection_error(:protocol_error, e: e)
632
+ rescue StandardError => e
633
+ connection_error(:internal_error, e: e)
608
634
  end
609
635
 
610
636
  # Encode headers payload and update connection compressor state.
@@ -634,7 +660,7 @@ module HTTP2
634
660
 
635
661
  frames
636
662
 
637
- rescue => e
663
+ rescue StandardError => e
638
664
  connection_error(:compression_error, e: e)
639
665
  nil
640
666
  end
@@ -649,22 +675,21 @@ module HTTP2
649
675
  def activate_stream(id: nil, **args)
650
676
  connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
651
677
 
652
- stream = Stream.new({ connection: self, id: id }.merge(args))
678
+ stream = Stream.new(**{ connection: self, id: id }.merge(args))
653
679
 
654
680
  # Streams that are in the "open" state, or either of the "half closed"
655
681
  # states count toward the maximum number of streams that an endpoint is
656
682
  # permitted to open.
657
683
  stream.once(:active) { @active_stream_count += 1 }
658
684
  stream.once(:close) do
659
- @streams.delete id
660
685
  @active_stream_count -= 1
661
686
 
662
687
  # Store a reference to the closed stream, such that we can respond
663
688
  # to any in-flight frames while close is registered on both sides.
664
689
  # References to such streams will be purged whenever another stream
665
- # is closed, with a minimum of 15s RTT time window.
666
- @streams_recently_closed.delete_if { |_, v| (Time.now - v) > 15 }
667
- @streams_recently_closed[id] = Time.now
690
+ # is closed, with a defined RTT time window.
691
+ @streams_recently_closed[id] = Time.now.to_i
692
+ cleanup_recently_closed
668
693
  end
669
694
 
670
695
  stream.on(:promise, &method(:promise)) if self.is_a? Server
@@ -673,6 +698,22 @@ module HTTP2
673
698
  @streams[id] = stream
674
699
  end
675
700
 
701
+ # Purge recently streams closed within defined RTT time window.
702
+ def cleanup_recently_closed
703
+ now_ts = Time.now.to_i
704
+ to_delete = []
705
+ @streams_recently_closed.each do |stream_id, ts|
706
+ # Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
707
+ break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
708
+ to_delete << stream_id
709
+ end
710
+
711
+ to_delete.each do |stream_id|
712
+ @streams.delete stream_id
713
+ @streams_recently_closed.delete stream_id
714
+ end
715
+ end
716
+
676
717
  # Emit GOAWAY error indicating to peer that the connection is being
677
718
  # aborted, and once sent, raise a local exception.
678
719
  #
@@ -699,4 +740,5 @@ module HTTP2
699
740
  yield
700
741
  end
701
742
  end
743
+ # rubocop:enable ClassLength
702
744
  end
@@ -359,14 +359,14 @@ module HTTP2
359
359
  if frame[:flags].include? :priority
360
360
  e_sd = payload.read_uint32
361
361
  frame[:stream_dependency] = e_sd & RBIT
362
- frame[:exclusive] = (e_sd & EBIT) != 0 # rubocop:disable Style/NumericPredicate
362
+ frame[:exclusive] = (e_sd & EBIT) != 0
363
363
  frame[:weight] = payload.getbyte + 1
364
364
  end
365
365
  frame[:payload] = payload.read(frame[:length])
366
366
  when :priority
367
367
  e_sd = payload.read_uint32
368
368
  frame[:stream_dependency] = e_sd & RBIT
369
- frame[:exclusive] = (e_sd & EBIT) != 0 # rubocop:disable Style/NumericPredicate
369
+ frame[:exclusive] = (e_sd & EBIT) != 0
370
370
  frame[:weight] = payload.getbyte + 1
371
371
  when :rst_stream
372
372
  frame[:error] = unpack_error payload.read_uint32
@@ -442,4 +442,5 @@ module HTTP2
442
442
  name || error
443
443
  end
444
444
  end
445
+ # rubocop:enable ClassLength
445
446
  end
@@ -91,6 +91,10 @@ module HTTP2
91
91
  on(:local_window) { |v| @local_window_max_size = @local_window = v }
92
92
  end
93
93
 
94
+ def closed?
95
+ @state == :closed
96
+ end
97
+
94
98
  # Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
95
99
  #
96
100
  # @param frame [Hash]
@@ -111,7 +115,15 @@ module HTTP2
111
115
  process_priority(frame)
112
116
  when :window_update
113
117
  process_window_update(frame)
114
- when :altsvc, :blocked
118
+ when :altsvc
119
+ # 4. The ALTSVC HTTP/2 Frame
120
+ # An ALTSVC frame on a
121
+ # stream other than stream 0 containing non-empty "Origin" information
122
+ # is invalid and MUST be ignored.
123
+ if !frame[:origin] || frame[:origin].empty?
124
+ emit(frame[:type], frame)
125
+ end
126
+ when :blocked
115
127
  emit(frame[:type], frame)
116
128
  end
117
129
 
@@ -144,6 +156,7 @@ module HTTP2
144
156
  end
145
157
 
146
158
  # Sends a HEADERS frame containing HTTP response headers.
159
+ # All pseudo-header fields MUST appear in the header block before regular header fields.
147
160
  #
148
161
  # @param headers [Array or Hash] Array of key-value pairs or Hash
149
162
  # @param end_headers [Boolean] indicates that no more headers will be sent
@@ -1,3 +1,3 @@
1
1
  module HTTP2
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'helper'
2
2
 
3
3
  RSpec.describe HTTP2::Client do
4
+ include FrameHelpers
4
5
  before(:each) do
5
6
  @client = Client.new
6
7
  end
@@ -32,6 +33,18 @@ RSpec.describe HTTP2::Client do
32
33
  expect(frame[:type]).to eq :settings
33
34
  expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
34
35
  end
36
+
37
+ it 'should initialize client when receiving server settings before sending ack' do
38
+ frames = []
39
+ @client.on(:frame) { |bytes| frames << bytes }
40
+ @client << f.generate(settings_frame)
41
+
42
+ expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
43
+ expect(f.parse(frames[1])[:type]).to eq :settings
44
+ ack_frame = f.parse(frames[2])
45
+ expect(ack_frame[:type]).to eq :settings
46
+ expect(ack_frame[:flags]).to include(:ack)
47
+ end
35
48
  end
36
49
 
37
50
  context 'upgrade' do
@@ -60,33 +73,33 @@ RSpec.describe HTTP2::Client do
60
73
 
61
74
  it 'should raise error on PUSH_PROMISE against stream 0' do
62
75
  expect do
63
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 0)
76
+ @client << set_stream_id(f.generate(push_promise_frame), 0)
64
77
  end.to raise_error(ProtocolError)
65
78
  end
66
79
 
67
80
  it 'should raise error on PUSH_PROMISE against bogus stream' do
68
81
  expect do
69
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 31_415)
82
+ @client << set_stream_id(f.generate(push_promise_frame), 31_415)
70
83
  end.to raise_error(ProtocolError)
71
84
  end
72
85
 
73
86
  it 'should raise error on PUSH_PROMISE against non-idle stream' do
74
87
  expect do
75
88
  s = @client.new_stream
76
- s.send HEADERS.dup
89
+ s.send headers_frame
77
90
 
78
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
79
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
91
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
92
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
80
93
  end.to raise_error(ProtocolError)
81
94
  end
82
95
 
83
96
  it 'should emit stream object for received PUSH_PROMISE' do
84
97
  s = @client.new_stream
85
- s.send HEADERS.deep_dup
98
+ s.send headers_frame
86
99
 
87
100
  promise = nil
88
101
  @client.on(:promise) { |stream| promise = stream }
89
- @client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
102
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
90
103
 
91
104
  expect(promise.id).to eq 2
92
105
  expect(promise.state).to eq :reserved_remote
@@ -95,14 +108,14 @@ RSpec.describe HTTP2::Client do
95
108
  it 'should emit promise headers for received PUSH_PROMISE' do
96
109
  header = nil
97
110
  s = @client.new_stream
98
- s.send HEADERS.deep_dup
111
+ s.send headers_frame
99
112
 
100
113
  @client.on(:promise) do |stream|
101
114
  stream.on(:promise_headers) do |h|
102
115
  header = h
103
116
  end
104
117
  end
105
- @client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
118
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
106
119
 
107
120
  expect(header).to be_a(Array)
108
121
  # expect(header).to eq([%w(a b)])
@@ -110,7 +123,7 @@ RSpec.describe HTTP2::Client do
110
123
 
111
124
  it 'should auto RST_STREAM promises against locally-RST stream' do
112
125
  s = @client.new_stream
113
- s.send HEADERS.deep_dup
126
+ s.send headers_frame
114
127
  s.close
115
128
 
116
129
  allow(@client).to receive(:send)
@@ -119,7 +132,57 @@ RSpec.describe HTTP2::Client do
119
132
  expect(frame[:stream]).to eq 2
120
133
  end
121
134
 
122
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
135
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
136
+ end
137
+ end
138
+
139
+ context 'alt-svc' do
140
+ context 'received in the connection' do
141
+ it 'should emit :altsvc when receiving one' do
142
+ @client << f.generate(settings_frame)
143
+ frame = nil
144
+ @client.on(:altsvc) do |f|
145
+ frame = f
146
+ end
147
+ @client << f.generate(altsvc_frame)
148
+ expect(frame).to be_a(Hash)
149
+ end
150
+ it 'should not emit :altsvc when the frame when contains no host' do
151
+ @client << f.generate(settings_frame)
152
+ frame = nil
153
+ @client.on(:altsvc) do |f|
154
+ frame = f
155
+ end
156
+
157
+ @client << f.generate(altsvc_frame.merge(origin: nil))
158
+ expect(frame).to be_nil
159
+ end
160
+ end
161
+ context 'received in a stream' do
162
+ it 'should emit :altsvc' do
163
+ s = @client.new_stream
164
+ s.send headers_frame
165
+ s.close
166
+
167
+ frame = nil
168
+ s.on(:altsvc) { |f| frame = f }
169
+
170
+ @client << set_stream_id(f.generate(altsvc_frame.merge(origin: nil)), s.id)
171
+
172
+ expect(frame).to be_a(Hash)
173
+ end
174
+ it 'should not emit :alt_svc when the frame when contains a origin' do
175
+ s = @client.new_stream
176
+ s.send headers_frame
177
+ s.close
178
+
179
+ frame = nil
180
+ s.on(:altsvc) { |f| frame = f }
181
+
182
+ @client << set_stream_id(f.generate(altsvc_frame), s.id)
183
+
184
+ expect(frame).to be_nil
185
+ end
123
186
  end
124
187
  end
125
188
  end
@@ -236,6 +236,12 @@ RSpec.describe HTTP2::Header do
236
236
  expect(cc.table.size).to be 1
237
237
  expect(cc.table.first[0]).to eq 'test2'
238
238
  end
239
+
240
+ it 'should reject table size update if exceed limit' do
241
+ cc = EncodingContext.new(table_size: 4096)
242
+
243
+ expect { cc.process(type: :changetablesize, value: 150_000_000) }.to raise_error(CompressionError)
244
+ end
239
245
  end
240
246
 
241
247
  context 'encode' do
@@ -351,6 +357,78 @@ RSpec.describe HTTP2::Header do
351
357
  },
352
358
  ],
353
359
  },
360
+ { title: 'D.4.a. Request Examples with Huffman - Client Handling of Improperly Ordered Headers',
361
+ type: :request,
362
+ table_size: 4096,
363
+ huffman: :always,
364
+ streams: [
365
+ { wire: '8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff',
366
+ emitted: [
367
+ [':method', 'GET'],
368
+ [':scheme', 'http'],
369
+ [':path', '/'],
370
+ [':authority', 'www.example.com'],
371
+ ],
372
+ table: [
373
+ [':authority', 'www.example.com'],
374
+ ],
375
+ table_size: 57,
376
+ },
377
+ { wire: '8286 84be 5886 a8eb 1064 9cbf',
378
+ emitted: [
379
+ [':method', 'GET'],
380
+ [':scheme', 'http'],
381
+ ['cache-control', 'no-cache'],
382
+ [':path', '/'],
383
+ [':authority', 'www.example.com'],
384
+ ],
385
+ table: [
386
+ ['cache-control', 'no-cache'],
387
+ [':authority', 'www.example.com'],
388
+ ],
389
+ table_size: 110,
390
+ },
391
+ { wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
392
+ a849 e95b b8e8 b4bf",
393
+ emitted: [
394
+ [':method', 'GET'],
395
+ [':scheme', 'https'],
396
+ ['custom-key', 'custom-value'],
397
+ [':path', '/index.html'],
398
+ [':authority', 'www.example.com'],
399
+ ],
400
+ table: [
401
+ ['custom-key', 'custom-value'],
402
+ ['cache-control', 'no-cache'],
403
+ [':authority', 'www.example.com'],
404
+ ],
405
+ table_size: 164,
406
+ },
407
+ ],
408
+ },
409
+ { title: 'D.4.b. Request Examples with Huffman - Server Handling of Improperly Ordered Headers',
410
+ type: :request,
411
+ bypass_encoder: true,
412
+ table_size: 4096,
413
+ huffman: :always,
414
+ streams: [
415
+ { wire: '8286408825a849e95ba97d7f8925a849e95bb8e8b4bf84418cf1e3c2e5f23a6ba0ab90f4ff',
416
+ emitted: [
417
+ [':method', 'GET'],
418
+ [':scheme', 'http'],
419
+ ['custom-key', 'custom-value'],
420
+ [':path', '/'],
421
+ [':authority', 'www.example.com'],
422
+ ],
423
+ table: [
424
+ ['custom-key', 'custom-value'],
425
+ [':authority', 'www.example.com'],
426
+ ],
427
+ table_size: 111,
428
+ has_bad_headers: true,
429
+ },
430
+ ],
431
+ },
354
432
  { title: 'D.5. Response Examples without Huffman',
355
433
  type: :response,
356
434
  table_size: 256,
@@ -474,6 +552,22 @@ RSpec.describe HTTP2::Header do
474
552
  },
475
553
  ],
476
554
  },
555
+ { title: 'D.6.a. Response Examples with Huffman - dynamic table size updates should not trigger exceptions',
556
+ type: :response,
557
+ table_size: 4096,
558
+ huffman: :always,
559
+ bypass_encoder: true,
560
+ streams: [
561
+ { wire: '2088 7689 aa63 55e5 80ae 16d7 17',
562
+ emitted: [
563
+ [':status', '200'],
564
+ ['server', 'nginx/1.15.2'],
565
+ ],
566
+ table: [],
567
+ table_size: 0,
568
+ },
569
+ ],
570
+ },
477
571
  ]
478
572
 
479
573
  context 'decode' do
@@ -485,25 +579,38 @@ RSpec.describe HTTP2::Header do
485
579
  before do
486
580
  (0...nth).each do |i|
487
581
  bytes = [ex[:streams][i][:wire].delete(" \n")].pack('H*')
488
- @dc.decode(HTTP2::Buffer.new(bytes))
582
+ if ex[:streams][i][:has_bad_headers]
583
+ expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
584
+ else
585
+ @dc.decode(HTTP2::Buffer.new(bytes))
586
+ end
489
587
  end
490
588
  end
491
- subject do
492
- bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
493
- @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
494
- end
495
- it 'should emit expected headers' do
496
- subject
497
- # order-perserving compare
498
- expect(@emitted).to eq ex[:streams][nth][:emitted]
499
- end
500
- it 'should update header table' do
501
- subject
502
- expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
503
- end
504
- it 'should compute header table size' do
505
- subject
506
- expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
589
+ if ex[:streams][nth][:has_bad_headers]
590
+ it 'should raise CompressionError' do
591
+ bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
592
+ expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
593
+ end
594
+ else
595
+ subject do
596
+ bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
597
+ @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
598
+ end
599
+ it 'should emit expected headers' do
600
+ subject
601
+ # partitioned compare
602
+ pseudo_headers, headers = ex[:streams][nth][:emitted].partition { |f, _| f.start_with? ':' }
603
+ partitioned_headers = pseudo_headers + headers
604
+ expect(@emitted).to eq partitioned_headers
605
+ end
606
+ it 'should update header table' do
607
+ subject
608
+ expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
609
+ end
610
+ it 'should compute header table size' do
611
+ subject
612
+ expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
613
+ end
507
614
  end
508
615
  end
509
616
  end
@@ -513,6 +620,7 @@ RSpec.describe HTTP2::Header do
513
620
 
514
621
  context 'encode' do
515
622
  spec_examples.each do |ex|
623
+ next if ex[:bypass_encoder]
516
624
  context "spec example #{ex[:title]}" do
517
625
  ex[:streams].size.times do |nth|
518
626
  context "request #{nth + 1}" do
@@ -522,22 +630,33 @@ RSpec.describe HTTP2::Header do
522
630
  end
523
631
  before do
524
632
  (0...nth).each do |i|
525
- @cc.encode(ex[:streams][i][:emitted])
633
+ if ex[:streams][i][:has_bad_headers]
634
+ @cc.encode(ex[:streams][i][:emitted], ensure_proper_ordering: false)
635
+ else
636
+ @cc.encode(ex[:streams][i][:emitted])
637
+ end
526
638
  end
527
639
  end
528
640
  subject do
529
- @cc.encode(ex[:streams][nth][:emitted])
641
+ if ex[:streams][nth][:has_bad_headers]
642
+ @cc.encode(ex[:streams][nth][:emitted], ensure_proper_ordering: false)
643
+ else
644
+ @cc.encode(ex[:streams][nth][:emitted])
645
+ end
530
646
  end
531
647
  it 'should emit expected bytes on wire' do
648
+ puts subject.unpack('H*').first
532
649
  expect(subject.unpack('H*').first).to eq ex[:streams][nth][:wire].delete(" \n")
533
650
  end
534
- it 'should update header table' do
535
- subject
536
- expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
537
- end
538
- it 'should compute header table size' do
539
- subject
540
- expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
651
+ unless ex[:streams][nth][:has_bad_headers]
652
+ it 'should update header table' do
653
+ subject
654
+ expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
655
+ end
656
+ it 'should compute header table size' do
657
+ subject
658
+ expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
659
+ end
541
660
  end
542
661
  end
543
662
  end