Module: Quicsilver::Protocol::ControlStreamParser

Included in:
Client, Transport::Connection
Defined in:
lib/quicsilver/protocol/control_stream_parser.rb

Overview

Shared control stream parsing for both server Connection and Client.

RFC 9114 §7.2.4: Both endpoints MUST send and process SETTINGS. RFC 9114 §7.2.6: Both endpoints MUST validate incoming GOAWAY.

Includer must provide:

@settings_received — boolean, initially false
@peer_goaway_id   — nil initially

Includer may override:

on_settings_received(settings_hash) — called after SETTINGS parsed
on_goaway_received(stream_id)       — called after GOAWAY parsed
handle_control_frame(type, payload) — called for non-SETTINGS/GOAWAY frames

Constant Summary collapse

HTTP2_SETTINGS =

RFC 9114 §7.2.4.1 / §11.2.2: HTTP/2 setting identifiers forbidden in HTTP/3 0x00 = SETTINGS_HEADER_TABLE_SIZE (reserved), 0x02-0x05 = various HTTP/2 settings Note: 0x08 (SETTINGS_ENABLE_CONNECT_PROTOCOL) is valid in HTTP/3 per RFC 9220

[0x00, 0x02, 0x03, 0x04, 0x05].freeze

Instance Method Summary collapse

Instance Method Details

#parse_control_frames(data) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/quicsilver/protocol/control_stream_parser.rb', line 24

def parse_control_frames(data)
  first_frame = !@settings_received

  Protocol::FrameReader.each(data) do |type, payload|
    if first_frame && type != Protocol::FRAME_SETTINGS
      raise Protocol::FrameError.new("First frame on control stream must be SETTINGS",
        error_code: Protocol::H3_MISSING_SETTINGS)
    end
    first_frame = false

    case type
    when Protocol::FRAME_SETTINGS
      raise Protocol::FrameError, "Duplicate SETTINGS frame on control stream" if @settings_received
      parse_peer_settings(payload)
      @settings_received = true
    when Protocol::FRAME_GOAWAY
      parse_peer_goaway(payload)
    else
      handle_control_frame(type, payload)
    end
  end
end

#parse_peer_goaway(payload) ⇒ Object

RFC 9114 §7.2.6: Validate incoming GOAWAY frame. Stream ID must be a client-initiated bidirectional stream ID (divisible by 4) and must not increase from a previous GOAWAY.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/quicsilver/protocol/control_stream_parser.rb', line 75

def parse_peer_goaway(payload)
  stream_id, _ = Protocol.decode_varint(payload.bytes, 0)

  unless stream_id % 4 == 0
    raise Protocol::FrameError.new(
      "GOAWAY stream ID #{stream_id} is not a client-initiated bidirectional stream ID",
      error_code: Protocol::H3_ID_ERROR)
  end

  if @peer_goaway_id && stream_id > @peer_goaway_id
    raise Protocol::FrameError.new(
      "GOAWAY stream ID #{stream_id} exceeds previous #{@peer_goaway_id}",
      error_code: Protocol::H3_ID_ERROR)
  end

  @peer_goaway_id = stream_id
end

#parse_peer_settings(payload) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/quicsilver/protocol/control_stream_parser.rb', line 47

def parse_peer_settings(payload)
  offset = 0
  seen = Set.new
  settings = {}

  while offset < payload.bytesize
    id, id_len = Protocol.decode_varint(payload.bytes, offset)
    value, value_len = Protocol.decode_varint(payload.bytes, offset + id_len)
    break if id_len == 0 || value_len == 0

    if HTTP2_SETTINGS.include?(id)
      raise Protocol::FrameError.new("HTTP/2 setting identifier 0x#{id.to_s(16)} not allowed in HTTP/3",
        error_code: Protocol::H3_SETTINGS_ERROR)
    end

    raise Protocol::FrameError, "Duplicate setting identifier 0x#{id.to_s(16)}" if seen.include?(id)
    seen.add(id)

    settings[id] = value
    offset += id_len + value_len
  end

  on_settings_received(settings)
end