http-2-next 0.1.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.
@@ -0,0 +1,757 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP2Next
4
+ # Default connection and stream flow control window (64KB).
5
+ DEFAULT_FLOW_WINDOW = 65_535
6
+
7
+ # Default header table size
8
+ DEFAULT_HEADER_SIZE = 4096
9
+
10
+ # Default stream_limit
11
+ DEFAULT_MAX_CONCURRENT_STREAMS = 100
12
+
13
+ # Default values for SETTINGS frame, as defined by the spec.
14
+ SPEC_DEFAULT_CONNECTION_SETTINGS = {
15
+ settings_header_table_size: 4096,
16
+ settings_enable_push: 1, # enabled for servers
17
+ settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
18
+ settings_initial_window_size: 65_535,
19
+ settings_max_frame_size: 16_384,
20
+ settings_max_header_list_size: 2**31 - 1 # unlimited
21
+ }.freeze
22
+
23
+ DEFAULT_CONNECTION_SETTINGS = {
24
+ settings_header_table_size: 4096,
25
+ settings_enable_push: 1, # enabled for servers
26
+ settings_max_concurrent_streams: 100,
27
+ settings_initial_window_size: 65_535,
28
+ settings_max_frame_size: 16_384,
29
+ settings_max_header_list_size: 2**31 - 1 # unlimited
30
+ }.freeze
31
+
32
+ # Default stream priority (lower values are higher priority).
33
+ DEFAULT_WEIGHT = 16
34
+
35
+ # Default connection "fast-fail" preamble string as defined by the spec.
36
+ CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
37
+
38
+ # Connection encapsulates all of the connection, stream, flow-control,
39
+ # error management, and other processing logic required for a well-behaved
40
+ # HTTP 2.0 endpoint.
41
+ #
42
+ # Note that this class should not be used directly. Instead, you want to
43
+ # use either Client or Server class to drive the HTTP 2.0 exchange.
44
+ #
45
+ # rubocop:disable Metrics/ClassLength
46
+ class Connection
47
+ include FlowBuffer
48
+ include Emitter
49
+ include Error
50
+
51
+ # Connection state (:new, :closed).
52
+ attr_reader :state
53
+
54
+ # Size of current connection flow control window (by default, set to
55
+ # infinity, but is automatically updated on receipt of peer settings).
56
+ attr_reader :local_window
57
+ attr_reader :remote_window
58
+ alias window local_window
59
+
60
+ # Current settings value for local and peer
61
+ attr_reader :local_settings
62
+ attr_reader :remote_settings
63
+
64
+ # Pending settings value
65
+ # Sent but not ack'ed settings
66
+ attr_reader :pending_settings
67
+
68
+ # Number of active streams between client and server (reserved streams
69
+ # are not counted towards the stream limit).
70
+ attr_reader :active_stream_count
71
+
72
+ # Initializes new connection object.
73
+ #
74
+ def initialize(**settings)
75
+ @local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
76
+ @remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
77
+
78
+ @compressor = Header::Compressor.new(settings)
79
+ @decompressor = Header::Decompressor.new(settings)
80
+
81
+ @active_stream_count = 0
82
+ @last_activated_stream = 0
83
+ @last_stream_id = 0
84
+ @streams = {}
85
+ @streams_recently_closed = {}
86
+ @pending_settings = []
87
+
88
+ @framer = Framer.new(@local_settings[:settings_max_frame_size])
89
+
90
+ @local_window_limit = @local_settings[:settings_initial_window_size]
91
+ @local_window = @local_window_limit
92
+ @remote_window_limit = @remote_settings[:settings_initial_window_size]
93
+ @remote_window = @remote_window_limit
94
+
95
+ @recv_buffer = Buffer.new
96
+ @send_buffer = []
97
+ @continuation = []
98
+ @error = nil
99
+
100
+ @h2c_upgrade = nil
101
+ @closed_since = nil
102
+ end
103
+
104
+ def closed?
105
+ @state == :closed
106
+ end
107
+
108
+ # Allocates new stream for current connection.
109
+ #
110
+ # @param priority [Integer]
111
+ # @param window [Integer]
112
+ # @param parent [Stream]
113
+ def new_stream(**args)
114
+ raise ConnectionClosed if @state == :closed
115
+ raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
116
+
117
+ connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @last_activated_stream
118
+
119
+ stream = activate_stream(id: @stream_id, **args)
120
+ @last_activated_stream = stream.id
121
+
122
+ @stream_id += 2
123
+
124
+ stream
125
+ end
126
+
127
+ # Sends PING frame to the peer.
128
+ #
129
+ # @param payload [String] optional payload must be 8 bytes long
130
+ # @param blk [Proc] callback to execute when PONG is received
131
+ def ping(payload, &blk)
132
+ send(type: :ping, stream: 0, payload: payload)
133
+ once(:ack, &blk) if blk
134
+ end
135
+
136
+ # Sends a GOAWAY frame indicating that the peer should stop creating
137
+ # new streams for current connection.
138
+ #
139
+ # Endpoints MAY append opaque data to the payload of any GOAWAY frame.
140
+ # Additional debug data is intended for diagnostic purposes only and
141
+ # carries no semantic value. Debug data MUST NOT be persistently stored,
142
+ # since it could contain sensitive information.
143
+ #
144
+ # @param error [Symbol]
145
+ # @param payload [String]
146
+ def goaway(error = :no_error, payload = nil)
147
+ last_stream = if (max = @streams.max)
148
+ max.first
149
+ else
150
+ 0
151
+ end
152
+
153
+ send(type: :goaway, last_stream: last_stream,
154
+ error: error, payload: payload)
155
+ @state = :closed
156
+ @closed_since = Time.now
157
+ end
158
+
159
+ # Sends a WINDOW_UPDATE frame to the peer.
160
+ #
161
+ # @param increment [Integer]
162
+ def window_update(increment)
163
+ @local_window += increment
164
+ send(type: :window_update, stream: 0, increment: increment)
165
+ end
166
+
167
+ # Sends a connection SETTINGS frame to the peer.
168
+ # The values are reflected when the corresponding ACK is received.
169
+ #
170
+ # @param settings [Array or Hash]
171
+ def settings(payload)
172
+ payload = payload.to_a
173
+ connection_error if validate_settings(@local_role, payload)
174
+ @pending_settings << payload
175
+ send(type: :settings, stream: 0, payload: payload)
176
+ @pending_settings << payload
177
+ end
178
+
179
+ # Decodes incoming bytes into HTTP 2.0 frames and routes them to
180
+ # appropriate receivers: connection frames are handled directly, and
181
+ # stream frames are passed to appropriate stream objects.
182
+ #
183
+ # @param data [String] Binary encoded string
184
+ def receive(data)
185
+ @recv_buffer << data
186
+
187
+ # Upon establishment of a TCP connection and determination that
188
+ # HTTP/2.0 will be used by both peers, each endpoint MUST send a
189
+ # connection header as a final confirmation and to establish the
190
+ # initial settings for the HTTP/2.0 connection.
191
+ #
192
+ # Client connection header is 24 byte connection header followed by
193
+ # SETTINGS frame. Server connection header is SETTINGS frame only.
194
+ if @state == :waiting_magic
195
+ if @recv_buffer.size < 24
196
+ raise HandshakeError unless CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
197
+
198
+ return # maybe next time
199
+ elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
200
+ # MAGIC is OK. Send our settings
201
+ @state = :waiting_connection_preface
202
+ payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
203
+ settings(payload)
204
+ else
205
+ raise HandshakeError
206
+ end
207
+ end
208
+
209
+ while (frame = @framer.parse(@recv_buffer))
210
+ # Implementations MUST discard frames
211
+ # that have unknown or unsupported types.
212
+ if frame[:type].nil?
213
+ # However, extension frames that appear in
214
+ # the middle of a header block (Section 4.3) are not permitted; these
215
+ # MUST be treated as a connection error (Section 5.4.1) of type
216
+ # PROTOCOL_ERROR.
217
+ connection_error(:protocol_error) unless @continuation.empty?
218
+ next
219
+ end
220
+
221
+ emit(:frame_received, frame)
222
+
223
+ # Header blocks MUST be transmitted as a contiguous sequence of frames
224
+ # with no interleaved frames of any other type, or from any other stream.
225
+ unless @continuation.empty?
226
+ connection_error unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
227
+
228
+ @continuation << frame
229
+ next unless frame[:flags].include? :end_headers
230
+
231
+ payload = @continuation.map { |f| f[:payload] }.join
232
+
233
+ frame = @continuation.shift
234
+ @continuation.clear
235
+
236
+ frame.delete(:length)
237
+ frame[:payload] = Buffer.new(payload)
238
+ frame[:flags] << :end_headers
239
+ end
240
+
241
+ # SETTINGS frames always apply to a connection, never a single stream.
242
+ # The stream identifier for a settings frame MUST be zero. If an
243
+ # endpoint receives a SETTINGS frame whose stream identifier field is
244
+ # anything other than 0x0, the endpoint MUST respond with a connection
245
+ # error (Section 5.4.1) of type PROTOCOL_ERROR.
246
+ if connection_frame?(frame)
247
+ connection_error(:protocol_error) unless frame[:stream].zero?
248
+ connection_management(frame)
249
+ else
250
+ case frame[:type]
251
+ when :headers
252
+ # When server receives even-numbered stream identifier,
253
+ # the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
254
+ connection_error if frame[:stream].even? && is_a?(Server)
255
+
256
+ # The last frame in a sequence of HEADERS/CONTINUATION
257
+ # frames MUST have the END_HEADERS flag set.
258
+ unless frame[:flags].include? :end_headers
259
+ @continuation << frame
260
+ next
261
+ end
262
+
263
+ # After sending a GOAWAY frame, the sender can discard frames
264
+ # for new streams. However, any frames that alter connection
265
+ # state cannot be completely ignored. For instance, HEADERS,
266
+ # PUSH_PROMISE and CONTINUATION frames MUST be minimally
267
+ # processed to ensure a consistent compression state
268
+ decode_headers(frame)
269
+ return if @state == :closed
270
+
271
+ stream = @streams[frame[:stream]]
272
+ if stream.nil?
273
+ stream = activate_stream(
274
+ id: frame[:stream],
275
+ weight: frame[:weight] || DEFAULT_WEIGHT,
276
+ dependency: frame[:dependency] || 0,
277
+ exclusive: frame[:exclusive] || false
278
+ )
279
+ verify_stream_order(stream.id)
280
+ emit(:stream, stream)
281
+ end
282
+
283
+ stream << frame
284
+
285
+ when :push_promise
286
+ # The last frame in a sequence of PUSH_PROMISE/CONTINUATION
287
+ # frames MUST have the END_HEADERS flag set
288
+ unless frame[:flags].include? :end_headers
289
+ @continuation << frame
290
+ return
291
+ end
292
+
293
+ decode_headers(frame)
294
+ return if @state == :closed
295
+
296
+ # PUSH_PROMISE frames MUST be associated with an existing, peer-
297
+ # initiated stream... A receiver MUST treat the receipt of a
298
+ # PUSH_PROMISE on a stream that is neither "open" nor
299
+ # "half-closed (local)" as a connection error (Section 5.4.1) of
300
+ # type PROTOCOL_ERROR. Similarly, a receiver MUST treat the
301
+ # receipt of a PUSH_PROMISE that promises an illegal stream
302
+ # identifier (Section 5.1.1) (that is, an identifier for a stream
303
+ # that is not currently in the "idle" state) as a connection error
304
+ # (Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver
305
+ # recently sent a RST_STREAM frame to cancel the associated stream.
306
+ parent = @streams[frame[:stream]]
307
+ pid = frame[:promise_stream]
308
+
309
+ # if PUSH parent is recently closed, RST_STREAM the push
310
+ if @streams_recently_closed[frame[:stream]]
311
+ send(type: :rst_stream, stream: pid, error: :refused_stream)
312
+ return
313
+ end
314
+
315
+ connection_error(msg: "missing parent ID") if parent.nil?
316
+
317
+ unless parent.state == :open || parent.state == :half_closed_local
318
+ # An endpoint might receive a PUSH_PROMISE frame after it sends
319
+ # RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
320
+ # The RST_STREAM does not cancel any promised stream. Therefore, if
321
+ # promised streams are not desired, a RST_STREAM can be used to
322
+ # close any of those streams.
323
+ if parent.closed == :local_rst
324
+ # We can either (a) 'resurrect' the parent, or (b) RST_STREAM
325
+ # ... sticking with (b), might need to revisit later.
326
+ send(type: :rst_stream, stream: pid, error: :refused_stream)
327
+ else
328
+ connection_error
329
+ end
330
+ end
331
+
332
+ stream = activate_stream(id: pid, parent: parent)
333
+ verify_stream_order(stream.id)
334
+ emit(:promise, stream)
335
+ stream << frame
336
+ else
337
+ if (stream = @streams[frame[:stream]])
338
+ stream << frame
339
+ if frame[:type] == :data
340
+ update_local_window(frame)
341
+ calculate_window_update(@local_window_limit)
342
+ end
343
+ process_window_update(frame: frame, encode: true) if frame[:type] == :window_update
344
+ else
345
+ case frame[:type]
346
+ # The PRIORITY frame can be sent for a stream in the "idle" or
347
+ # "closed" state. This allows for the reprioritization of a
348
+ # group of dependent streams by altering the priority of an
349
+ # unused or closed parent stream.
350
+ when :priority
351
+ stream = activate_stream(
352
+ id: frame[:stream],
353
+ weight: frame[:weight] || DEFAULT_WEIGHT,
354
+ dependency: frame[:dependency] || 0,
355
+ exclusive: frame[:exclusive] || false
356
+ )
357
+
358
+ emit(:stream, stream)
359
+ stream << frame
360
+
361
+ # WINDOW_UPDATE can be sent by a peer that has sent a frame
362
+ # bearing the END_STREAM flag. This means that a receiver could
363
+ # receive a WINDOW_UPDATE frame on a "half-closed (remote)" or
364
+ # "closed" stream. A receiver MUST NOT treat this as an error
365
+ # (see Section 5.1).
366
+ when :window_update
367
+ stream = @streams_recently_closed[frame[:stream]]
368
+ connection_error(:protocol_error, "sent window update on idle stream") unless stream
369
+ process_window_update(frame: frame, encode: true)
370
+ else
371
+ # An endpoint that receives an unexpected stream identifier
372
+ # MUST respond with a connection error of type PROTOCOL_ERROR.
373
+ connection_error(msg: "stream does not exist")
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
379
+ rescue StandardError => e
380
+ raise if e.is_a?(Error::Error)
381
+
382
+ connection_error(e: e)
383
+ end
384
+
385
+ def <<(*args)
386
+ receive(*args)
387
+ end
388
+
389
+ private
390
+
391
+ # Send an outgoing frame. DATA frames are subject to connection flow
392
+ # control and may be split and / or buffered based on current window size.
393
+ # All other frames are sent immediately.
394
+ #
395
+ # @note all frames are currently delivered in FIFO order.
396
+ # @param frame [Hash]
397
+ def send(frame)
398
+ emit(:frame_sent, frame)
399
+ if frame[:type] == :data
400
+ send_data(frame, true)
401
+
402
+ elsif frame[:type] == :rst_stream && frame[:error] == :protocol_error
403
+ # An endpoint can end a connection at any time. In particular, an
404
+ # endpoint MAY choose to treat a stream error as a connection error.
405
+
406
+ goaway(frame[:error])
407
+ else
408
+ # HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
409
+ # RST_STREAM that are not protocol errors
410
+ frames = encode(frame)
411
+ frames.each { |f| emit(:frame, f) }
412
+ end
413
+ end
414
+
415
+ # Applies HTTP 2.0 binary encoding to the frame.
416
+ #
417
+ # @param frame [Hash]
418
+ # @return [Array of Buffer] encoded frame
419
+ def encode(frame)
420
+ frames = if frame[:type] == :headers || frame[:type] == :push_promise
421
+ encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
422
+ else
423
+ [frame] # otherwise one frame
424
+ end
425
+
426
+ frames.map { |f| @framer.generate(f) }
427
+ end
428
+
429
+ # Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
430
+ # frame addressed to stream ID = 0.
431
+ #
432
+ # @param frame [Hash]
433
+ # @return [Boolean]
434
+ def connection_frame?(frame)
435
+ (frame[:stream]).zero? ||
436
+ frame[:type] == :settings ||
437
+ frame[:type] == :ping ||
438
+ frame[:type] == :goaway
439
+ end
440
+
441
+ # Process received connection frame (stream ID = 0).
442
+ # - Handle SETTINGS updates
443
+ # - Connection flow control (WINDOW_UPDATE)
444
+ # - Emit PONG auto-reply to PING frames
445
+ # - Mark connection as closed on GOAWAY
446
+ #
447
+ # @param frame [Hash]
448
+ def connection_management(frame)
449
+ case @state
450
+ when :waiting_connection_preface
451
+ # The first frame MUST be a SETTINGS frame at the start of a connection.
452
+ @state = :connected
453
+ connection_settings(frame)
454
+
455
+ when :connected
456
+ case frame[:type]
457
+ when :settings
458
+ connection_settings(frame)
459
+ when :window_update
460
+ process_window_update(frame: frame, encode: true)
461
+ when :ping
462
+ ping_management(frame)
463
+ when :goaway
464
+ # Receivers of a GOAWAY frame MUST NOT open additional streams on
465
+ # the connection, although a new connection can be established
466
+ # for new streams.
467
+ @state = :closed
468
+ @closed_since = Time.now
469
+ emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
470
+ when :altsvc
471
+ # 4. The ALTSVC HTTP/2 Frame
472
+ # An ALTSVC frame on stream 0 with empty (length 0) "Origin"
473
+ # information is invalid and MUST be ignored.
474
+ emit(frame[:type], frame) if frame[:origin] && !frame[:origin].empty?
475
+ when :blocked
476
+ emit(frame[:type], frame)
477
+ else
478
+ connection_error
479
+ end
480
+ when :closed
481
+ case frame[:type]
482
+ when :goaway
483
+ connection_error
484
+ when :ping
485
+ ping_management(frame)
486
+ else
487
+ connection_error if (Time.now - @closed_since) > 15
488
+ end
489
+ else
490
+ connection_error
491
+ end
492
+ end
493
+
494
+ def ping_management(frame)
495
+ if frame[:flags].include? :ack
496
+ emit(:ack, frame[:payload])
497
+ else
498
+ send(type: :ping, stream: 0,
499
+ flags: [:ack], payload: frame[:payload])
500
+ end
501
+ end
502
+
503
+ # Validate settings parameters. See sepc Section 6.5.2.
504
+ #
505
+ # @param role [Symbol] The sender's role: :client or :server
506
+ # @return nil if no error. Exception object in case of any error.
507
+ def validate_settings(role, settings)
508
+ settings.each do |key, v|
509
+ case key
510
+ when :settings_header_table_size
511
+ # Any value is valid
512
+ when :settings_enable_push
513
+ case role
514
+ when :server
515
+ # Section 8.2
516
+ # Clients MUST reject any attempt to change the
517
+ # SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
518
+ # message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
519
+ return ProtocolError.new("invalid #{key} value") unless v.zero?
520
+ when :client
521
+ # Any value other than 0 or 1 MUST be treated as a
522
+ # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
523
+ return ProtocolError.new("invalid #{key} value") unless v.zero? || v == 1
524
+ end
525
+ when :settings_max_concurrent_streams
526
+ # Any value is valid
527
+ when :settings_initial_window_size
528
+ # Values above the maximum flow control window size of 2^31-1 MUST
529
+ # be treated as a connection error (Section 5.4.1) of type
530
+ # FLOW_CONTROL_ERROR.
531
+ return FlowControlError.new("invalid #{key} value") unless v <= 0x7fffffff
532
+ when :settings_max_frame_size
533
+ # The initial value is 2^14 (16,384) octets. The value advertised
534
+ # by an endpoint MUST be between this initial value and the maximum
535
+ # allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
536
+ # Values outside this range MUST be treated as a connection error
537
+ # (Section 5.4.1) of type PROTOCOL_ERROR.
538
+ return ProtocolError.new("invalid #{key} value") unless v >= 16_384 && v <= 16_777_215
539
+ when :settings_max_header_list_size
540
+ # Any value is valid
541
+ # else # ignore unknown settings
542
+ end
543
+ end
544
+ nil
545
+ end
546
+
547
+ # Update connection settings based on parameters set by the peer.
548
+ #
549
+ # @param frame [Hash]
550
+ def connection_settings(frame)
551
+ connection_error unless frame[:type] == :settings && (frame[:stream]).zero?
552
+
553
+ # Apply settings.
554
+ # side =
555
+ # local: previously sent and pended our settings should be effective
556
+ # remote: just received peer settings should immediately be effective
557
+ settings, side = if frame[:flags].include?(:ack)
558
+ # Process pending settings we have sent.
559
+ [@pending_settings.shift, :local]
560
+ else
561
+ check = validate_settings(@remote_role, frame[:payload])
562
+ connection_error(check) if check
563
+ [frame[:payload], :remote]
564
+ end
565
+
566
+ settings.each do |key, v|
567
+ case side
568
+ when :local
569
+ @local_settings[key] = v
570
+ when :remote
571
+ @remote_settings[key] = v
572
+ end
573
+
574
+ case key
575
+ when :settings_max_concurrent_streams
576
+ # Do nothing.
577
+ # The value controls at the next attempt of stream creation.
578
+
579
+ when :settings_initial_window_size
580
+ # A change to SETTINGS_INITIAL_WINDOW_SIZE could cause the available
581
+ # space in a flow control window to become negative. A sender MUST
582
+ # track the negative flow control window, and MUST NOT send new flow
583
+ # controlled frames until it receives WINDOW_UPDATE frames that cause
584
+ # the flow control window to become positive.
585
+ case side
586
+ when :local
587
+ @local_window = @local_window - @local_window_limit + v
588
+ @streams.each_value do |stream|
589
+ stream.emit(:local_window, stream.local_window - @local_window_limit + v)
590
+ end
591
+
592
+ @local_window_limit = v
593
+ when :remote
594
+ @remote_window = @remote_window - @remote_window_limit + v
595
+ @streams.each_value do |stream|
596
+ # Event name is :window, not :remote_window
597
+ stream.emit(:window, stream.remote_window - @remote_window_limit + v)
598
+ end
599
+
600
+ @remote_window_limit = v
601
+ end
602
+
603
+ when :settings_header_table_size
604
+ # Setting header table size might cause some headers evicted
605
+ case side
606
+ when :local
607
+ @compressor.table_size = v
608
+ when :remote
609
+ @decompressor.table_size = v
610
+ end
611
+
612
+ when :settings_enable_push
613
+ # nothing to do
614
+
615
+ when :settings_max_frame_size
616
+ @framer.remote_max_frame_size = v
617
+
618
+ # else # ignore unknown settings
619
+ end
620
+ end
621
+
622
+ case side
623
+ when :local
624
+ # Received a settings_ack. Notify application layer.
625
+ emit(:settings_ack, frame, @pending_settings.size)
626
+ when :remote
627
+ unless @state == :closed || @h2c_upgrade == :start
628
+ # Send ack to peer
629
+ send(type: :settings, stream: 0, payload: [], flags: [:ack])
630
+ # when initial window size changes, we try to flush any buffered
631
+ # data.
632
+ @streams.each_value(&:flush)
633
+ end
634
+ end
635
+ end
636
+
637
+ # Decode headers payload and update connection decompressor state.
638
+ #
639
+ # The receiver endpoint reassembles the header block by concatenating
640
+ # the individual fragments, then decompresses the block to reconstruct
641
+ # the header set - aka, header payloads are buffered until END_HEADERS,
642
+ # or an END_PROMISE flag is seen.
643
+ #
644
+ # @param frame [Hash]
645
+ def decode_headers(frame)
646
+ frame[:payload] = @decompressor.decode(frame[:payload], frame) if frame[:payload].is_a? Buffer
647
+ rescue CompressionError => e
648
+ connection_error(:compression_error, e: e)
649
+ rescue ProtocolError => e
650
+ connection_error(:protocol_error, e: e)
651
+ rescue StandardError => e
652
+ connection_error(:internal_error, e: e)
653
+ end
654
+
655
+ # Encode headers payload and update connection compressor state.
656
+ #
657
+ # @param frame [Hash]
658
+ # @return [Array of Frame]
659
+ def encode_headers(frame)
660
+ payload = frame[:payload]
661
+ payload = @compressor.encode(payload) unless payload.is_a? Buffer
662
+
663
+ frames = []
664
+
665
+ while payload.bytesize > 0
666
+ cont = frame.dup
667
+ cont[:type] = :continuation
668
+ cont[:flags] = []
669
+ cont[:payload] = payload.slice!(0, @remote_settings[:settings_max_frame_size])
670
+ frames << cont
671
+ end
672
+ if frames.empty?
673
+ frames = [frame]
674
+ else
675
+ frames.first[:type] = frame[:type]
676
+ frames.first[:flags] = frame[:flags] - [:end_headers]
677
+ frames.last[:flags] << :end_headers
678
+ end
679
+
680
+ frames
681
+ rescue StandardError => e
682
+ connection_error(:compression_error, e: e)
683
+ nil
684
+ end
685
+
686
+ # Activates new incoming or outgoing stream and registers appropriate
687
+ # connection managemet callbacks.
688
+ #
689
+ # @param id [Integer]
690
+ # @param priority [Integer]
691
+ # @param window [Integer]
692
+ # @param parent [Stream]
693
+ def activate_stream(id: nil, **args)
694
+ connection_error(msg: "Stream ID already exists") if @streams.key?(id)
695
+
696
+ raise StreamLimitExceeded if @active_stream_count >= @local_settings[:settings_max_concurrent_streams]
697
+
698
+ stream = Stream.new(connection: self, id: id, **args)
699
+
700
+ # Streams that are in the "open" state, or either of the "half closed"
701
+ # states count toward the maximum number of streams that an endpoint is
702
+ # permitted to open.
703
+ stream.once(:active) { @active_stream_count += 1 }
704
+ stream.once(:close) do
705
+ # Store a reference to the closed stream, such that we can respond
706
+ # to any in-flight frames while close is registered on both sides.
707
+ # References to such streams will be purged whenever another stream
708
+ # is closed, with a minimum of 15s RTT time window.
709
+ @streams_recently_closed[id] = Time.now
710
+ to_delete = @streams_recently_closed.select { |_, v| (Time.now - v) > 15 }
711
+ to_delete.each do |stream_id|
712
+ @streams.delete stream_id
713
+ @streams_recently_closed.delete stream_id
714
+ end
715
+ end
716
+
717
+ stream.on(:promise, &method(:promise)) if is_a? Server
718
+ stream.on(:frame, &method(:send))
719
+
720
+ @streams[id] = stream
721
+ end
722
+
723
+ def verify_stream_order(id)
724
+ return unless id.odd?
725
+
726
+ connection_error(msg: "Stream ID smaller than previous") if @last_stream_id > id
727
+ @last_stream_id = id
728
+ end
729
+
730
+ # Emit GOAWAY error indicating to peer that the connection is being
731
+ # aborted, and once sent, raise a local exception.
732
+ #
733
+ # @param error [Symbol]
734
+ # @option error [Symbol] :no_error
735
+ # @option error [Symbol] :internal_error
736
+ # @option error [Symbol] :flow_control_error
737
+ # @option error [Symbol] :stream_closed
738
+ # @option error [Symbol] :frame_too_large
739
+ # @option error [Symbol] :compression_error
740
+ # @param msg [String]
741
+ def connection_error(error = :protocol_error, msg: nil, e: nil)
742
+ goaway(error) unless @state == :closed || @state == :new
743
+
744
+ @state = :closed
745
+ @error = error
746
+ msg ||= e ? e.message : "protocol error"
747
+ backtrace = e ? e.backtrace : nil
748
+ raise Error.types[error], msg, backtrace
749
+ end
750
+ alias error connection_error
751
+
752
+ def manage_state(_)
753
+ yield
754
+ end
755
+ end
756
+ # rubocop:enable Metrics/ClassLength
757
+ end