http-2 0.9.0 → 0.11.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.
@@ -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