tiny.quic.connection
Defined in tiny.quic.
API (127)
Actions
Public operations.
Capacity.deriveConnection.acceptStream: Gives a server the identifier 0 once, after the client's first frame for that stream has arrived, so the server learns that the client has opened stream 0.Connection.bytesInFlight: Adds up what all three packet number spaces have outstanding, so a caller watching congestion reads the total bytes in flight.Connection.close: Moves the connection into closing and starts the closing period from the time the caller gave, so a close unable to reach the wire still ends and the caller receives the deadline to stop driving the connection.Connection.closeReasonConnection.deinitConnection.finish: Settles the final size at whatever has been written so far, so an application ends its side of the stream.Connection.handshakeKeysDiscardedConnection.initConnection.initialKeysDiscardedConnection.nextTimeoutConnection.openStream: Opens stream 0 for a client once the connection is established, and gives back the identifier 0, so the client obtains stream 0 before it writes.Connection.peerConnection.pingConnection.read: Copies waiting bytes in offset order and says whether they carry the reader to the FIN, enabling an application to take received stream bytes and release receive window room back to the peer.Connection.receiveConnection.receiveState: Hands back how far the receiving half of an open stream has traveled, so an application sees whether the peer has finished or reset the stream.Connection.requestKeyUpdateConnection.resetStream: Gives up on sending and puts RESET_STREAM in the queue under the caller's code, so an application abandons its side of the stream.Connection.roundTrip: Hands back what the connection has measured of the path, so a caller sees what the connection's timers rest on.Connection.sendConnection.sendState: Hands back how far the sending half of an open stream has traveled, so an application sees whether its data was acknowledged or its reset took effect.Connection.stateConnection.statsConnection.stopSending: Puts STOP_SENDING in the queue under the caller's code while data may still arrive, so an application tells the peer to stop sending.Connection.streamStats: Hands back how far an open stream's data has traveled and which codes each side sent, so an application inspects stream progress and exchanged error codes.Connection.write: Takes application bytes into the send buffer, as many as the free room allows, and reports how many it took, providing the entry point for an application to hand stream bytes to the connection.KeyState.deinitKeyState.discardHandshakeKeyState.discardInitialKeyState.initKeyState.installInitialKeyState.installTlsKeyState.observeAck: Frees the state that holds a further rotation back,update_awaiting_ack, once the peer has acknowledged something protected by the write keys now in use.KeyState.observeAckSent: Marks the peer's most recent phase change as covered by an acknowledgment this endpoint has now sent.KeyState.observeCurrentRead: Pulls the phase boundary down to the number of a packet that arrived behind its neighbors.KeyState.observePreviousRead: Keeps the highest number the older keys have opened since the last rotation.KeyState.prepareSendKeyState.promoteApplicationReadKeyState.readKeyState.requestUpdateKeyState.selectApplicationReadKeyState.writeRanges.containsRanges.encodeRanges.initRanges.insertReassembler.consumeReassembler.contiguousReassembler.initReassembler.receiveSent.acknowledge: Settles every held packet the ACK frame covers and hands each one to the caller-supplied sink'sonAcknowledgedmethod, so the arriving frame reaches the stream and CRYPTO state that settled packets carried.Sent.addSent.clear: Empties the ring and returns the outstanding byte count to zero when the space drops its keys, because subsequent arrivals lack keys to settle those packets.Sent.detectLost: Walks the held packets at or under the largest acknowledged number and settles each one that either rule reaches, moving the frames of each lost packet back into the send queues.Sent.hasCapacitySent.initSentRecord.emptySentRecord.inFlight: Reports whether this packet still counts against the space's outstanding bytes, so both passes can skip a record that stopped counting.Space.ackDelaySpace.ackDueSpace.bytesInFlight: Reports what this space has outstanding, in bytes, so the connection sums it over the three spaces and the timer reads it to choose which probe to arm.Space.clearAckSpace.detectLost: Walks this space's outstanding packets once, hands the sink each one that either rule reaches, and writes the space's deadline afresh.Space.discard: Throws away this space's outstanding packets and both of its timers, so no timer outlives the space it belongs to.Space.initSpace.markAckSentSpace.numberEncodingSpace.processAckSpace.recordReceivedSpace.recordSentSpaceKind.levelStorage.activateStorage.deinitStorage.initStorage.keysStorage.rangesStorage.recordsacknowledges
Types and contracts
Public types and contracts.
Acknowledged: Reports what one ACK frame settled for the first time, so the connection can take a round-trip sample that needs both of its facts.Capacity: Capacity works out, from the limits alone, where each buffer sits in the block and how many bytes the block needs in total.Capacity.DeriveErrorCloseReason:Connection.closeReasonhands this structure back to report what ended the connection, and whether this endpoint or the peer decided it.Config: Carries the identity, the parameter values, the connection ID, and the random source that one handshake draws on.Connection: Primary type for one QUIC endpoint, held by a caller to drive withreceive,send, andnextTimeout, reading and writing byte slices its caller owns, and opening no socket of its own.CryptoSummaryDropStatsFrameSummary: Keeps, for one sent packet, whatever a later loss must act on, so loss recovery finds what one lost packet can put back in the queue.InitErrorKeySlotKeyStateKeyState.InstallErrorLimits: Fixes, field by field, how much the connection may retain, so that a caller fills this structure once and hands the same value to the capacity, the storage, and the connection.Lost: Reports what one detection pass took out and when the next pass is due, so the space can store that pass's deadline.NumberEncodingOpenErrorRangeRangesReassemblerReassembler.ReceiveErrorSentSent.AddErrorSentRecord: Holds one sent packet until an acknowledgment or a loss settles it, so both the acknowledgment path and the loss path work over this record.SpaceSpace.AckErrorSpace.NumberErrorSpaceKindStats: Counts datagrams, packets, failed authentications, and drops over the life of one connection, returned byConnection.statsso that a caller sees what the connection did with each datagram.Status: Names the five conditions a caller can observe from outside as handshaking, established, closing, draining, and closed.Storage: Storage cuts the caller's block into every typed buffer one connection works out of.Storage.CapacityStorage.InitErrorStorage.LimitsStorage.StorageStreamFrameStats: Counts the stream and flow control frames that crossed in one direction, carried byStatsfor each direction so that a caller can see which frames moved.StreamStats: Reports what has happened to stream 0 in both directions, returned byConnection.streamStatsfor an open stream so that an application can see how far its data has traveled.StreamSummary: Records which stream bytes one STREAM frame put on the wire and whether that frame closed the stream, so the sending half of stream 0 receives this summary once the packet that carried that frame is resolved.TransportError
Namespaces
Public namespaces.
Values and defaults
Public values and defaults.
Storage.claimStorage.storage_alignmentStorage.work_limitsclose_reason_bytes_max: Maximum byte length of a close reason, published so a caller can size its reason string against this bound becausecloseanswersReasonTooLongfor anything longer.closing_period_ns: Three-second span that a closing or draining connection stands before it reports itself closed, so a caller waiting onnextTimeoutknows how long the connection stays before reporting closed.
Source
Source: lib/quic/src/connection/ack.zig:4
pub const Range = struct { smallest: u62, largest: u62,};Source: lib/quic/src/connection/ack.zig:9
pub const Ranges = struct { values: []Range, count: u16 = 0, pub fn init(values: []Range) Ranges { std.debug.assert(values.len <= std.math.maxInt(u16)); for (values) |*value| value.* = .{ .smallest = 0, .largest = 0 }; return .{ .values = values }; } pub fn contains(self: *const Ranges, packet_number: u62) bool { for (0..self.values.len) |index| { if (index >= self.count) break; const value = self.values[index]; if (packet_number < value.smallest) return false; if (packet_number <= value.largest) return true; } return false; } pub fn insert(self: *Ranges, packet_number: u62) bool { if (self.contains(packet_number)) return false; var at = self.insertionIndex(packet_number); const joins_left = self.joinsLeft(at, packet_number); const joins_right = self.joinsRight(at, packet_number); if (joins_left and joins_right) { self.values[at - 1].largest = self.values[at].largest; self.remove(at); return true; } if (joins_left) { self.values[at - 1].largest = packet_number; return true; } if (joins_right) { self.values[at].smallest = packet_number; return true; } if (self.count == self.values.len) { if (at == 0) return true; self.remove(0); at = self.insertionIndex(packet_number); } self.insertAt(at, .{ .smallest = packet_number, .largest = packet_number }); return true; } pub fn encode(self: *const Ranges, delay: u62, output: *quic.cursor.Write) !void { if (self.count == 0) return error.EmptyRanges; std.debug.assert(self.count <= self.values.len); std.debug.assert(self.count - 1 <= quic.frame.ack_ranges_max); const newest = self.values[self.count - 1]; try output.byte(0x02); _ = try quic.varint.write(newest.largest, output); _ = try quic.varint.write(delay, output); _ = try quic.varint.write(self.count - 1, output); _ = try quic.varint.write(newest.largest - newest.smallest, output); var previous = newest; for (0..self.values.len) |reverse| { if (reverse + 1 >= self.count) break; const index = self.count - 2 - reverse; const current = self.values[index]; _ = try quic.varint.write(previous.smallest - current.largest - 2, output); _ = try quic.varint.write(current.largest - current.smallest, output); previous = current; } } fn insertionIndex(self: *const Ranges, packet_number: u62) usize { for (0..self.values.len) |index| { if (index >= self.count) return index; if (packet_number < self.values[index].smallest) return index; } return self.count; } fn joinsLeft(self: *const Ranges, at: usize, packet_number: u62) bool { if (at == 0) return false; const largest = self.values[at - 1].largest; if (largest == std.math.maxInt(u62)) return false; return largest + 1 == packet_number; } fn joinsRight(self: *const Ranges, at: usize, packet_number: u62) bool { if (at >= self.count) return false; if (packet_number == std.math.maxInt(u62)) return false; return packet_number + 1 == self.values[at].smallest; } fn insertAt(self: *Ranges, at: usize, value: Range) void { std.debug.assert(self.count < self.values.len); var index: usize = self.count; for (0..self.values.len) |_| { if (index <= at) break; self.values[index] = self.values[index - 1]; index -= 1; } self.values[at] = value; self.count += 1; } fn remove(self: *Ranges, at: usize) void { std.debug.assert(at < self.count); for (0..self.values.len) |offset| { const index = at + offset; if (index + 1 >= self.count) break; self.values[index] = self.values[index + 1]; } self.count -= 1; }};Source: lib/quic/src/connection/crypto.zig:3
pub const Reassembler = struct { bytes: []u8, present: []u8, next_offset: u62 = 0, pub const ReceiveError = error{ BufferExceeded, ConflictingData }; pub fn init(bytes: []u8, present: []u8) Reassembler { std.debug.assert(bytes.len == present.len); @memset(bytes, 0); @memset(present, 0); return .{ .bytes = bytes, .present = present }; } pub fn receive( self: *Reassembler, offset: u62, data: []const u8, ) ReceiveError!void { const start = std.math.cast(usize, offset) orelse return error.BufferExceeded; if (start > self.bytes.len) return error.BufferExceeded; if (data.len > self.bytes.len - start) return error.BufferExceeded; for (data, 0..) |byte, index| { const target = start + index; if (self.present[target] != 0 and self.bytes[target] != byte) { return error.ConflictingData; } self.bytes[target] = byte; self.present[target] = 1; } } pub fn contiguous(self: *const Reassembler) []const u8 { const start = std.math.cast(usize, self.next_offset) orelse return &.{}; if (start >= self.bytes.len) return &.{}; var end = start; for (start..self.bytes.len) |index| { if (self.present[index] == 0) break; end += 1; } return self.bytes[start..end]; } pub fn consume(self: *Reassembler, length: usize) void { const available = self.contiguous(); std.debug.assert(length <= available.len); self.next_offset += @intCast(length); }};Source: lib/quic/src/connection/keys.zig:4
pub const State = struct { storage: *quic.connection.Storage, initial_write: ?quic.crypto.Keys = null, initial_read: ?quic.crypto.Keys = null, handshake_write: ?quic.crypto.Keys = null, handshake_read: ?quic.crypto.Keys = null, application_write: ?quic.crypto.Keys = null, application_read_previous: ?quic.crypto.Keys = null, application_read_current: ?quic.crypto.Keys = null, application_read_next: ?quic.crypto.Keys = null, application_write_secret: quic.crypto.Secret = @splat(0), application_read_secret: quic.crypto.Secret = @splat(0), application_read_next_secret: quic.crypto.Secret = @splat(0), send_phase: bool = false, receive_phase: bool = false, previous_read_valid: bool = false, first_current_read_packet: u62 = 0, receive_update_acknowledged: bool = true, previous_read_largest: ?u62 = null, update_requested: bool = false, update_awaiting_ack: bool = false, update_first_packet: ?u62 = null, pub const InstallError = quic.crypto.Keys.InitError || error{UnsupportedCipherSuite}; pub fn init(storage: *quic.connection.Storage) State { return .{ .storage = storage }; } pub fn installInitial( self: *State, role: quic.tls.Role, destination: []const u8, ) InstallError!void { std.debug.assert(self.initial_write == null); std.debug.assert(self.initial_read == null); const secrets = quic.crypto.initial.secrets(destination); const write_secret = if (role == .client) secrets.client else secrets.server; const read_secret = if (role == .client) secrets.server else secrets.client; self.initial_write = try quic.crypto.Keys.derive( self.storage.keys(.initial_write), .aes_128_gcm_sha256, write_secret, ); self.initial_read = try quic.crypto.Keys.derive( self.storage.keys(.initial_read), .aes_128_gcm_sha256, read_secret, ); } pub fn installTls(self: *State, engine: *const quic.tls.Engine) InstallError!void { const suite = try selectedSuite(engine); if (self.handshake_write == null) { if (engine.secret(.handshake, .write)) |secret| { self.handshake_write = try quic.crypto.Keys.derive( self.storage.keys(.handshake_write), suite, secret, ); } } if (self.handshake_read == null) { if (engine.secret(.handshake, .read)) |secret| { self.handshake_read = try quic.crypto.Keys.derive( self.storage.keys(.handshake_read), suite, secret, ); } } try self.installApplicationWrite(engine, suite); try self.installApplicationRead(engine, suite); } fn installApplicationWrite( self: *State, engine: *const quic.tls.Engine, suite: quic.crypto.Suite, ) InstallError!void { if (self.application_write != null) return; const secret = engine.secret(.one_rtt, .write) orelse return; self.application_write_secret = secret; self.application_write = try quic.crypto.Keys.derive( self.storage.keys(.application_write), suite, secret, ); } fn installApplicationRead( self: *State, engine: *const quic.tls.Engine, suite: quic.crypto.Suite, ) InstallError!void { if (self.application_read_current != null) return; const secret = engine.secret(.one_rtt, .read) orelse return; self.application_read_secret = secret; self.application_read_next_secret = quic.crypto.Keys.next(suite, secret); self.application_read_previous = try quic.crypto.Keys.derive( self.storage.keys(.application_read_previous), suite, secret, ); self.application_read_current = try quic.crypto.Keys.derive( self.storage.keys(.application_read_current), suite, secret, ); self.application_read_next = try quic.crypto.Keys.derive( self.storage.keys(.application_read_next), suite, secret, ); self.application_read_next.?.update(self.application_read_next_secret); } pub fn write(self: *State, kind: quic.connection.SpaceKind) ?*quic.crypto.Keys { return switch (kind) { .initial => if (self.initial_write) |*keys| keys else null, .handshake => if (self.handshake_write) |*keys| keys else null, .application => if (self.application_write) |*keys| keys else null, }; } pub fn read(self: *State, kind: quic.connection.SpaceKind) ?*quic.crypto.Keys { return switch (kind) { .initial => if (self.initial_read) |*keys| keys else null, .handshake => if (self.handshake_read) |*keys| keys else null, .application => if (self.application_read_current) |*keys| keys else null, }; } pub fn selectApplicationRead( self: *State, header: quic.crypto.packet.Header, ) ?*quic.crypto.Keys { const phase = header.key_phase orelse return null; if (phase == self.receive_phase) { return if (self.application_read_current) |*keys| keys else null; } if (self.previous_read_valid and header.packet_number < self.first_current_read_packet) { return if (self.application_read_previous) |*keys| keys else null; } return if (self.application_read_next) |*keys| keys else null; } pub fn promoteApplicationRead( self: *State, packet_number: u62, next_send: u62, ) error{ConsecutiveUpdate}!void { std.debug.assert(self.application_read_previous != null); std.debug.assert(self.application_read_current != null); std.debug.assert(self.application_read_next != null); const peer_initiated = self.send_phase == self.receive_phase; if (peer_initiated and !self.receive_update_acknowledged) return error.ConsecutiveUpdate; std.mem.swap( quic.crypto.Keys, &self.application_read_previous.?, &self.application_read_current.?, ); std.mem.swap( quic.crypto.Keys, &self.application_read_current.?, &self.application_read_next.?, ); self.application_read_secret = self.application_read_next_secret; const suite = self.application_read_current.?.selectedSuite(); self.application_read_next_secret = quic.crypto.Keys.next( suite, self.application_read_secret, ); self.application_read_next.?.update(self.application_read_next_secret); self.previous_read_valid = true; self.receive_phase = !self.receive_phase; self.first_current_read_packet = packet_number; self.previous_read_largest = null; self.receive_update_acknowledged = false; if (peer_initiated) self.updateWrite(next_send); std.debug.assert(self.send_phase == self.receive_phase); } pub fn requestUpdate(self: *State) void { self.update_requested = true; } pub fn prepareSend(self: *State, confirmed: bool, next_packet: u62) void { const keys = if (self.application_write) |*value| value else return; if (!confirmed) return; if (!self.update_requested and !keys.needsUpdate()) return; if (self.update_awaiting_ack) return; self.updateWrite(next_packet); self.update_requested = false; } fn updateWrite(self: *State, next_packet: u62) void { const keys = &self.application_write.?; self.application_write_secret = quic.crypto.Keys.next( keys.selectedSuite(), self.application_write_secret, ); keys.update(self.application_write_secret); self.send_phase = !self.send_phase; self.update_awaiting_ack = true; self.update_first_packet = next_packet; } /// Frees the state that holds a further rotation back, `update_awaiting_ack`, once the peer has /// acknowledged something protected by the write keys now in use. The connection hands every /// ACK frame that arrives in the application space to this function, so a second key update can /// follow the first, because `prepareSend` tests that field before it rotates the write keys /// again. An ACK whose largest number falls under the first packet protected by those keys /// leaves the state as it was. The call returns at once when write key rotation is already /// complete. pub fn observeAck(self: *State, value: quic.frame.Ack) void { const first = self.update_first_packet orelse return; std.debug.assert(self.update_awaiting_ack); if (value.largest < first) return; self.update_awaiting_ack = false; self.update_first_packet = null; } /// Marks the peer's most recent phase change as covered by an acknowledgment this endpoint has /// now sent. The connection calls this function after it puts an ACK into a 1-RTT packet, so a /// peer-driven key update can be followed by another, because `promoteApplicationRead` reads /// that mark and turns a second peer-driven rotation away when the mark is missing, answering /// `ConsecutiveUpdate`. The number handed in sits at or above the first packet of the phase /// being read. pub fn observeAckSent(self: *State, largest_received: u62) void { std.debug.assert(largest_received >= self.first_current_read_packet); self.receive_update_acknowledged = true; } /// Pulls the phase boundary down to the number of a packet that arrived behind its neighbors. /// That boundary is `first_current_read_packet`, the lowest number the current keys have been /// seen to open. The connection reports every 1-RTT packet the current read keys opened to this /// function, so the boundary between the two key generations follows the peer's numbering, /// which the order of arrival can disturb. The call answers `PreviousKeysAboveCurrent` when the /// older keys have opened something higher since the last rotation, because a newer generation /// covering a lower number would break the order the peer promised. A number the older keys /// handled already leaves the boundary in place, which holds `previous_read_largest` strictly /// under the boundary. pub fn observeCurrentRead( self: *State, packet_number: u62, ) error{PreviousKeysAboveCurrent}!void { std.debug.assert(self.application_read_current != null); if (self.previous_read_largest) |largest| { std.debug.assert(largest < self.first_current_read_packet); if (packet_number < largest) return error.PreviousKeysAboveCurrent; if (packet_number == largest) return; } if (packet_number >= self.first_current_read_packet) return; self.first_current_read_packet = packet_number; } /// Keeps the highest number the older keys have opened since the last rotation. The connection /// reports every 1-RTT packet the older read keys opened to this function, so /// `observeCurrentRead` compares a later packet against that number to separate a tolerable /// reordering from one it refuses. The number handed in falls under the phase boundary. pub fn observePreviousRead(self: *State, packet_number: u62) void { std.debug.assert(self.previous_read_valid); std.debug.assert(packet_number < self.first_current_read_packet); const largest = self.previous_read_largest orelse packet_number; self.previous_read_largest = @max(largest, packet_number); } pub fn discardInitial(self: *State) void { discard(&self.initial_write); discard(&self.initial_read); } pub fn discardHandshake(self: *State) void { discard(&self.handshake_write); discard(&self.handshake_read); } pub fn deinit(self: *State) void { discard(&self.initial_write); discard(&self.initial_read); discard(&self.handshake_write); discard(&self.handshake_read); discard(&self.application_write); discard(&self.application_read_previous); discard(&self.application_read_current); discard(&self.application_read_next); std.crypto.secureZero(u8, &self.application_write_secret); std.crypto.secureZero(u8, &self.application_read_secret); std.crypto.secureZero(u8, &self.application_read_next_secret); }};Source: lib/quic/src/connection/machine.zig:138
/// Primary type for one QUIC endpoint, held by a caller to drive with `receive`, `send`, and/// `nextTimeout`, reading and writing byte slices its caller owns, and opening no socket of its/// own. The connection holds a pointer into its storage, so it and that storage both stay at fixed/// addresses for as long as it lives. The `init` constructor wants a configuration, the limits, and/// storage already set up for those same limits, and answers `StorageMismatch` for storage set up/// for others. Inside the connection are one record per packet number space, the key state, the TLS/// engine, both halves of stream 0, and the send credit and receive window that cover the whole/// connection. The `deinit` method gives back the TLS storage and the keys, and marks the storage/// ready for the caller to take back.pub const Connection = struct { config: quic.connection.Config, limits: quic.connection.Limits, storage: *quic.connection.Storage, local_cid: quic.packet.ConnectionId, peer_cid: ?quic.packet.ConnectionId, original_dcid: ?quic.packet.ConnectionId, peer_source_cid: ?quic.packet.ConnectionId, engine: ?quic.tls.Engine, keys: quic.connection.KeyState, spaces: [3]quic.connection.Space, crypto_streams: [3]quic.connection.Reassembler, crypto_send_offsets: [3]u62 = .{ 0, 0, 0 }, /// Storage where each packet number space's lost handshake bytes wait for a packet, holding /// them until they go out again ahead of anything TLS has newly produced. The `emitCrypto` /// method clears them before it asks TLS for anything new. Dropping a space's keys empties its /// entry. crypto_lost: [3]quic.connection.recovery.LostRange = @splat(.{}), status_value: quic.connection.Status = .handshaking, close_reason_value: ?quic.connection.CloseReason = null, close_pending: bool = false, close_deadline_ns: ?u64 = null, close_packets_since_response: u16 = 0, close_response_packet_limit: u16 = 1, pending_ping: bool = false, pending_handshake_done: bool = false, handshake_done_received: bool = false, handshake_confirmed: bool = false, handshake_acknowledged: bool = false, rtt: quic.connection.recovery.Estimator = .{}, pto_count: u32 = 0, pto_deadline_ns: ?u64 = null, pto_space: quic.connection.SpaceKind = .initial, /// Timestamp recording when a client first armed its anti-deadlock probe with nothing /// outstanding, holding that deadline still across the arming that `send` and `receive` do on /// every call. Both `send` and `receive` arm the timer afresh on every call, and this anchor /// keeps those calls from pushing the probe further off. The anchor empties once packets go /// out, once the peer has proof of this endpoint's address, and once a probe deadline passes. pto_anchor_ns: ?u64 = null, probe_packets: [3]u8 = @splat(0), peer_parameters_authenticated: bool = false, peer_parameters: ?quic.transport.Parameters = null, stream_send: quic.connection.stream.Send, stream_receive: quic.connection.stream.Receive, connection_credit: quic.connection.stream.Credit = .{ .limit = 0 }, connection_window: quic.connection.stream.Window, stream_open: bool = false, stream_accepted: bool = false, peer_streams_bidi: u62 = 0, streams_blocked_pending: bool = false, streams_blocked_sent: bool = false, effective_idle_timeout_ms: u62, timer_started: bool = false, last_activity_ns: u64 = 0, client_address_validated: bool, received_before_validation: u64 = 0, sent_before_validation: u64 = 0, stats_value: quic.connection.Stats = .{}, deinitialized: bool = false, pub fn init( config: quic.connection.Config, limits: quic.connection.Limits, storage: *quic.connection.Storage, ) InitError!Connection { const expected = try quic.connection.Storage.Capacity.derive(limits); if (storage.phase != .initialization) return error.StorageMismatch; if (storage.capacity.storage_bytes != expected.storage_bytes) { return error.StorageMismatch; } if (storage.capacity.stream_receive_offset != expected.stream_receive_offset) { return error.StorageMismatch; } if (config.local_cid.len == 0) return error.InvalidConfig; if (config.local_cid.len > quic.packet.connection_id_bytes_max) { return error.InvalidConfig; } if (config.ack_delay_exponent > 20) return error.InvalidConfig; const local_cid = quic.packet.ConnectionId.init(config.local_cid) catch return error.InvalidConfig; var self = Connection{ .config = config, .limits = limits, .storage = storage, .local_cid = local_cid, .peer_cid = null, .original_dcid = null, .peer_source_cid = null, .engine = null, .keys = quic.connection.KeyState.init(storage), .spaces = initSpaces(storage), .crypto_streams = initCryptoStreams(storage), .stream_send = quic.connection.stream.Send.init( storage.stream_send_bytes, storage.stream_sent_ranges, 0, ), .stream_receive = quic.connection.stream.Receive.init( storage.stream_receive_bytes, storage.stream_receive_ranges, quic.connection.parameters.streamReceiveWindow(config, limits), ), .connection_window = quic.connection.stream.Window.init(config.initial_max_data), .effective_idle_timeout_ms = config.max_idle_timeout, .client_address_validated = config.role == .client, }; errdefer self.keys.deinit(); if (config.role == .client) try self.initializeClient(); storage.activate(); std.debug.assert(self.storage == storage); std.debug.assert(self.local_cid.length >= 1); return self; } pub fn deinit(self: *Connection) void { std.debug.assert(!self.deinitialized); if (self.engine) |*engine| { engine.deinit(); } else { self.storage.tls_storage.activate(); } _ = self.storage.tls_storage.deinit(); self.keys.deinit(); self.storage.nested_released = true; self.deinitialized = true; } pub fn state(self: *const Connection) quic.connection.Status { std.debug.assert(!self.deinitialized); return self.status_value; } pub fn closeReason(self: *const Connection) ?quic.connection.CloseReason { std.debug.assert(!self.deinitialized); return self.close_reason_value; } pub fn stats(self: *const Connection) quic.connection.Stats { std.debug.assert(!self.deinitialized); return self.stats_value; } /// Hands back what the connection has measured of the path, so a caller sees what the /// connection's timers rest on. pub fn roundTrip(self: *const Connection) quic.connection.recovery.Estimator { std.debug.assert(!self.deinitialized); return self.rtt; } /// Adds up what all three packet number spaces have outstanding, so a caller watching /// congestion reads the total bytes in flight. pub fn bytesInFlight(self: *const Connection) u64 { std.debug.assert(!self.deinitialized); var total: u64 = 0; for (&self.spaces) |*space_value| total += space_value.bytesInFlight(); return total; } pub fn peer(self: *const Connection) ?quic.tls.Peer { std.debug.assert(!self.deinitialized); const engine = if (self.engine) |*value| value else return null; return engine.peer(); } pub fn initialKeysDiscarded(self: *const Connection) bool { return self.keys.initial_write == null and self.keys.initial_read == null; } pub fn handshakeKeysDiscarded(self: *const Connection) bool { return self.keys.handshake_write == null and self.keys.handshake_read == null; } pub fn requestKeyUpdate(self: *Connection) error{NotEstablished}!void { if (self.status_value != .established) return error.NotEstablished; self.keys.requestUpdate(); self.pending_ping = true; } pub fn ping(self: *Connection) error{NotEstablished}!void { if (self.status_value != .established) return error.NotEstablished; self.pending_ping = true; } /// Opens stream 0 for a client once the connection is established, and gives back the /// identifier 0, so the client obtains stream 0 before it writes. A server gets `NotClient`, a /// call made ahead of that point gets `NotEstablished`, and a repeat gets `StreamExists`. A /// peer that allows no streams gets `StreamLimit`, and one STREAMS_BLOCKED frame goes in the /// queue. pub fn openStream(self: *Connection) OpenError!StreamId { std.debug.assert(!self.deinitialized); if (self.config.role != .client) return error.NotClient; if (self.status_value != .established) return error.NotEstablished; if (self.stream_open) return error.StreamExists; if (self.peer_streams_bidi == 0) { if (!self.streams_blocked_sent) self.streams_blocked_pending = true; return error.StreamLimit; } self.stream_open = true; return 0; } /// Gives a server the identifier 0 once, after the client's first frame for that stream has /// arrived, so the server learns that the client has opened stream 0. A client receives null, /// and a server asking ahead of that frame or asking a second time also receives null. pub fn acceptStream(self: *Connection) ?StreamId { std.debug.assert(!self.deinitialized); if (self.config.role != .server) return null; if (!self.stream_open) return null; if (self.stream_accepted) return null; self.stream_accepted = true; return 0; } /// Takes application bytes into the send buffer, as many as the free room allows, and reports /// how many it took, providing the entry point for an application to hand stream bytes to the /// connection. Bytes go in for identifier 0 on an open stream, while the connection is /// handshaking or established. pub fn write(self: *Connection, id: StreamId, bytes: []const u8) usize { if (!self.streamOpenForApplication(id)) return 0; return self.stream_send.write(bytes); } /// Settles the final size at whatever has been written so far, so an application ends its side /// of the stream. The call acts for identifier 0 on an open stream, while the connection is /// handshaking or established. pub fn finish(self: *Connection, id: StreamId) void { if (!self.streamOpenForApplication(id)) return; self.stream_send.finish(); } /// Copies waiting bytes in offset order and says whether they carry the reader to the FIN, /// enabling an application to take received stream bytes and release receive window room back /// to the peer. The method copies for identifier 0 on an open stream. pub fn read(self: *Connection, id: StreamId, out: []u8) ReadResult { if (!self.streamKnown(id)) return .{ .bytes = 0, .fin = false }; return self.stream_receive.read(&self.connection_window, out); } /// Gives up on sending and puts RESET_STREAM in the queue under the caller's code, so an /// application abandons its side of the stream. The call acts for identifier 0 on an open /// stream, while the connection is handshaking or established. pub fn resetStream(self: *Connection, id: StreamId, app_error_code: u62) void { if (!self.streamOpenForApplication(id)) return; _ = self.stream_send.reset(app_error_code); } /// Puts STOP_SENDING in the queue under the caller's code while data may still arrive, so an /// application tells the peer to stop sending. The call acts for identifier 0 on an open /// stream, while the connection is handshaking or established. pub fn stopSending(self: *Connection, id: StreamId, app_error_code: u62) void { if (!self.streamOpenForApplication(id)) return; _ = self.stream_receive.stop(app_error_code); } /// Hands back how far the sending half of an open stream has traveled, so an application sees /// whether its data was acknowledged or its reset took effect. The method answers for /// identifier 0 on an open stream, and offers null elsewhere. pub fn sendState(self: *const Connection, id: StreamId) ?SendState { if (!self.streamKnown(id)) return null; return self.stream_send.state; } /// Hands back how far the receiving half of an open stream has traveled, so an application /// sees whether the peer has finished or reset the stream. The method answers for identifier 0 /// on an open stream, and offers null elsewhere. pub fn receiveState(self: *const Connection, id: StreamId) ?ReceiveState { if (!self.streamKnown(id)) return null; return self.stream_receive.state; } /// Hands back how far an open stream's data has traveled and which codes each side sent, so an /// application inspects stream progress and exchanged error codes. The method answers for /// identifier 0 on an open stream, and offers null elsewhere. pub fn streamStats(self: *const Connection, id: StreamId) ?quic.connection.StreamStats { if (!self.streamKnown(id)) return null; const send_part = &self.stream_send; const receive_part = &self.stream_receive; return .{ .bytes_written = send_part.written, .bytes_sent = send_part.sent, .bytes_released = send_part.acknowledged, .bytes_received = receive_part.window.received, .bytes_read = receive_part.read_offset, .send_final_size = send_part.final_size, .receive_final_size = receive_part.final_size, .reset_sent_code = send_part.reset_code, .reset_received_code = receive_part.reset_code, .stop_sending_sent_code = receive_part.stop_code, }; } /// Moves the connection into closing and starts the closing period from the time the caller /// gave, so a close unable to reach the wire still ends and the caller receives the deadline to /// stop driving the connection. A reason past `close_reason_bytes_max` answers `ReasonTooLong` /// and changes nothing. The reason text is copied into the connection's own storage. When /// called on a connection that has already left handshaking or established, the call returns /// with the status untouched. pub fn close( self: *Connection, error_code: u62, application: bool, reason: []const u8, now_ns: u64, ) error{ReasonTooLong}!void { std.debug.assert(!self.deinitialized); std.debug.assert(close_reason_bytes_max <= self.storage.reason.len); if (reason.len > close_reason_bytes_max) return error.ReasonTooLong; self.advanceTime(now_ns); if (!isOpen(self.status_value)) return; std.debug.assert(self.close_deadline_ns == null); self.storeReason(error_code, application, null, reason, false); self.status_value = .closing; self.close_pending = true; self.close_deadline_ns = closeDeadline(now_ns); } pub fn receive(self: *Connection, datagram: []const u8, now_ns: u64) void { std.debug.assert(self.storage.phase == .steady); std.debug.assert(!self.deinitialized); self.advanceTime(now_ns); if (self.status_value == .closed or self.status_value == .draining) return; increment(&self.stats_value.datagrams_received); if (self.config.role == .server and !self.client_address_validated) { self.received_before_validation +|= datagram.len; } defer self.armProbeTimeout(now_ns); if (datagram.len > self.limits.datagram_bytes) { increment(&self.stats_value.drops.oversized); return; } if (self.status_value == .closing) self.noteClosingDatagram(); self.receivePackets(datagram, now_ns); } pub fn send(self: *Connection, out: []u8, now_ns: u64) usize { std.debug.assert(self.storage.phase == .steady); std.debug.assert(!self.deinitialized); self.advanceTime(now_ns); if (self.status_value == .closed or self.status_value == .draining) return 0; if (out.len == 0) return 0; if (self.status_value == .closing) return self.sendClosing(out, now_ns); if (self.status_value == .established) { self.keys.prepareSend(true, self.space(.application).next_packet_number); } var datagram_length: usize = 0; var carries_initial = false; const can_coalesce_handshake = self.keys.write(.handshake) != null; for ([_]quic.connection.SpaceKind{ .initial, .handshake, .application }) |kind| { const limit = self.sendLimit(out.len); if (datagram_length >= limit) break; const available = limit - datagram_length; const minimum = packetMinimum(kind, available, carries_initial, can_coalesce_handshake); const force_ping = (kind == .handshake and carries_initial) or self.probe_packets[spaceIndex(kind)] != 0; const count = self.sendSpace( kind, out[datagram_length..limit], available, minimum, force_ping, now_ns, ); if (kind == .initial and count != 0) carries_initial = true; datagram_length += count; } if (carries_initial) std.debug.assert(datagram_length >= 1200); if (datagram_length != 0) self.recordDatagramSent(datagram_length); self.armProbeTimeout(now_ns); return datagram_length; } pub fn nextTimeout(self: *const Connection, now_ns: u64) ?u64 { std.debug.assert(self.storage.phase == .steady); std.debug.assert(!self.deinitialized); const deadline = switch (self.status_value) { .handshaking, .established => self.activeDeadline() orelse return null, .closing, .draining => self.close_deadline_ns orelse return null, .closed => return null, }; return @max(deadline, now_ns); } fn activeDeadline(self: *const Connection) ?u64 { std.debug.assert(isOpen(self.status_value)); std.debug.assert(self.close_deadline_ns == null); var deadline: ?u64 = null; for (&self.spaces) |*space_value| { if (space_value.ack_due_at_ns) |due| deadline = earlier(deadline, due); if (space_value.loss_deadline_ns) |due| deadline = earlier(deadline, due); } if (self.pto_deadline_ns) |due| deadline = earlier(deadline, due); if (self.idleDeadline()) |idle| deadline = earlier(deadline, idle); return deadline; } fn initializeClient(self: *Connection) InitError!void { std.debug.assert(self.config.role == .client); std.debug.assert(self.engine == null); var random = self.config.random.values() catch return error.RandomFailed; defer std.crypto.secureZero(u8, std.mem.asBytes(&random)); const destination = quic.packet.ConnectionId.init(random.hello[0..8]) catch return error.InvalidConfig; self.peer_cid = destination; self.original_dcid = destination; try self.keys.installInitial(.client, destination.slice()); const encoded = try quic.connection.parameters.encode( self.config, self.limits, self.local_cid, null, self.storage.transport, ); self.engine = try quic.tls.Engine.init(.{ .role = .client, .identity = self.config.identity, .expected_peer = self.config.expected_peer, .alpn = self.config.alpn, .transport_parameters = encoded, .server_name = self.config.server_name, .random = self.config.random, .cipher_suite = preferredCipherSuite(self.config.preferred_suite), }, .{ .max_message = self.limits.tls_message_max }, &self.storage.tls_storage); } fn initializeServer(self: *Connection, view: PacketView, now_ns: u64) bool { std.debug.assert(self.config.role == .server); std.debug.assert(self.engine == null); std.debug.assert(view.kind == .initial); std.debug.assert(view.destination.length >= 8); const source = view.source orelse return false; self.original_dcid = view.destination; self.peer_source_cid = source; self.peer_cid = source; self.keys.installInitial(.server, view.destination.slice()) catch { self.fail(quic.connection.TransportError.internal_error, null, "initial keys", now_ns); return false; }; const encoded = quic.connection.parameters.encode( self.config, self.limits, self.local_cid, view.destination, self.storage.transport, ) catch { self.fail(quic.connection.TransportError.internal_error, null, "parameters", now_ns); return false; }; self.engine = quic.tls.Engine.init(.{ .role = .server, .identity = self.config.identity, .expected_peer = self.config.expected_peer, .alpn = self.config.alpn, .transport_parameters = encoded, .server_name = self.config.server_name, .random = self.config.random, .cipher_suite = preferredCipherSuite(self.config.preferred_suite), }, .{ .max_message = self.limits.tls_message_max }, &self.storage.tls_storage) catch { self.fail(quic.connection.TransportError.internal_error, null, "TLS init", now_ns); return false; }; return true; } fn receivePackets(self: *Connection, datagram: []const u8, now_ns: u64) void { std.debug.assert(datagram.len <= self.limits.datagram_bytes); std.debug.assert(self.status_value != .closed); var offset: usize = 0; var first_destination: ?quic.packet.ConnectionId = null; for (0..self.limits.datagram_bytes) |_| { if (offset >= datagram.len) break; const view = inspectPacket(datagram[offset..], self.local_cid.length) catch |failure| { if (failure == error.Malformed) increment(&self.stats_value.drops.malformed); if (failure == error.Unsupported) increment(&self.stats_value.drops.unsupported); return; }; if (first_destination == null) first_destination = view.destination; if (!sameCid(first_destination.?, view.destination)) { increment(&self.stats_value.drops.wrong_connection); offset += view.length; continue; } self.receivePacket(datagram[offset..][0..view.length], datagram.len, view, now_ns); if (self.status_value == .draining or self.status_value == .closed) return; offset += view.length; if (view.kind == .application) break; } } /// Admits one packet by opening the packet, working through the payload, and writing the packet /// number into the space, serving as the single admission path that keeps the key state honest /// through the order of its steps. The key state is written last, after the payload has been /// worked through and the number written. That order holds because a packet can decrypt and /// still be dropped without ending the connection, and the key state is meant to describe the /// packets this endpoint took. A number the space holds already counts as a repeat, and the /// packet goes no further. A server taking in a Handshake packet has its proof of the client's /// address, and drops its Initial keys. fn receivePacket( self: *Connection, packet_bytes: []const u8, datagram_length: usize, view: PacketView, now_ns: u64, ) void { std.debug.assert(packet_bytes.len <= datagram_length); std.debug.assert(datagram_length <= self.limits.datagram_bytes); if (self.config.role == .server and self.engine == null and view.kind == .initial and view.destination.length < 8) { increment(&self.stats_value.drops.malformed); return; } if (self.config.role == .server and view.kind == .initial and datagram_length < 1200) { increment(&self.stats_value.drops.undersized_initial); return; } if (self.config.role == .server and self.engine == null) { if (view.kind != .initial) { increment(&self.stats_value.drops.unavailable_keys); return; } if (!self.initializeServer(view, now_ns)) return; } if (!self.validateConnectionIds(view)) { increment(&self.stats_value.drops.wrong_connection); return; } @memcpy(self.storage.packet[0..packet_bytes.len], packet_bytes); const mutable = self.storage.packet[0..packet_bytes.len]; const result = self.openPacket(view, mutable, now_ns) orelse return; const opened = result.opened; const space_value = self.space(view.kind); if (space_value.received.contains(opened.packet_number)) { increment(&self.stats_value.drops.duplicate); return; } const ack_eliciting = self.processPayload(view.kind, opened.payload, now_ns) orelse return; const accepted = space_value.recordReceived( opened.packet_number, ack_eliciting, now_ns, maxAckDelayNs(self.config.max_ack_delay), ); std.debug.assert(accepted); if (view.kind == .application and isOpen(self.status_value)) { const observed = self.observeApplicationRead( opened.packet_number, result.phase_changed, now_ns, ); if (!observed) return; } if (self.keys.write(view.kind) == null) space_value.clearAck(); increment(&self.stats_value.packets_received); self.touchActivity(now_ns); if (self.config.role == .server and view.kind == .handshake) { self.client_address_validated = true; self.discardInitialKeys(); } } fn openPacket( self: *Connection, view: PacketView, bytes: []u8, now_ns: u64, ) ?OpenedPacket { std.debug.assert(bytes.len <= self.limits.datagram_bytes); const space_value = self.space(view.kind); if (view.kind != .application) { const read_keys = self.keys.read(view.kind) orelse { increment(&self.stats_value.drops.unavailable_keys); return null; }; const long = quic.crypto.packet.open( read_keys, bytes, self.storage.scratch, view.packet_number_offset, space_value.largest_received, ) catch |failure| return self.openFailure(read_keys, failure, now_ns); return .{ .opened = long, .phase_changed = false }; } const header_keys = self.keys.read(.application) orelse { increment(&self.stats_value.drops.unavailable_keys); return null; }; const header = quic.crypto.packet.unprotect( header_keys, bytes, view.packet_number_offset, space_value.largest_received, ) catch { increment(&self.stats_value.drops.malformed); return null; }; const phase_changed = header.key_phase.? != self.keys.receive_phase; const read_keys = self.keys.selectApplicationRead(header) orelse { increment(&self.stats_value.drops.unavailable_keys); return null; }; const opened = quic.crypto.packet.openPayload( read_keys, bytes, header, self.storage.scratch, ) catch |failure| return self.openFailure(read_keys, failure, now_ns); return .{ .opened = opened, .phase_changed = phase_changed }; } /// Writes one accepted 1-RTT packet into the key state, recognizing a key update and closing /// the connection on an illegal update. A packet of the phase now being read, arriving behind /// its neighbors, pulls the boundary down. A packet of the other phase sitting under that /// boundary is written against the older keys. A packet of the other phase in any other /// position moves the read keys on a generation. Either check failing ends the connection with /// KEY_UPDATE_ERROR and answers false. The caller has written the number down already, which /// holds the first packet of the phase now being read at or under the space's largest received /// number. fn observeApplicationRead( self: *Connection, packet_number: u62, phase_changed: bool, now_ns: u64, ) bool { std.debug.assert(self.keys.application_read_current != null); std.debug.assert(isOpen(self.status_value)); std.debug.assert(self.space(.application).largest_received != null); std.debug.assert(self.space(.application).largest_received.? >= packet_number); const key_update_error = quic.connection.TransportError.key_update_error; if (!phase_changed) { self.keys.observeCurrentRead(packet_number) catch { self.fail(key_update_error, null, "previous keys above current packet", now_ns); return false; }; return true; } const uses_previous = self.keys.previous_read_valid and packet_number < self.keys.first_current_read_packet; if (uses_previous) { self.keys.observePreviousRead(packet_number); return true; } const next_send = self.space(.application).next_packet_number; self.keys.promoteApplicationRead(packet_number, next_send) catch { self.fail(key_update_error, null, "consecutive key update", now_ns); return false; }; return true; } fn openFailure( self: *Connection, read_keys: *quic.crypto.Keys, failure: quic.crypto.packet.OpenError, now_ns: u64, ) ?OpenedPacket { if (failure == error.AuthenticationFailed) { increment(&self.stats_value.drops.unauthenticated); increment(&self.stats_value.failed_authentications); if (self.stats_value.failed_authentications >= read_keys.integrityLimit()) { self.fail( quic.connection.TransportError.aead_limit_reached, null, "AEAD integrity limit", now_ns, ); } return null; } if (failure == error.IntegrityLimitReached) { self.fail( quic.connection.TransportError.aead_limit_reached, null, "AEAD integrity limit", now_ns, ); return null; } if (failure == error.ReservedBits) { self.fail( quic.connection.TransportError.protocol_violation, null, "reserved packet bits", now_ns, ); return null; } increment(&self.stats_value.drops.malformed); return null; } fn processPayload( self: *Connection, kind: quic.connection.SpaceKind, payload: []u8, now_ns: u64, ) ?bool { std.debug.assert(payload.len <= self.limits.datagram_bytes); if (payload.len == 0) { self.fail( quic.connection.TransportError.protocol_violation, null, "empty packet", now_ns, ); return null; } var input = quic.cursor.Read.init(payload); var ack_eliciting = false; for (0..self.limits.datagram_bytes) |_| { if (input.remaining() == 0) return ack_eliciting; const value = quic.frame.decode(&input) catch { self.fail( quic.connection.TransportError.frame_encoding_error, null, "frame encoding", now_ns, ); return null; }; const eliciting = self.processFrame(kind, value, now_ns) orelse return null; if (eliciting) ack_eliciting = true; if (self.status_value == .draining) return ack_eliciting; } unreachable; } fn processFrame( self: *Connection, kind: quic.connection.SpaceKind, value: quic.frame.Frame, now_ns: u64, ) ?bool { std.debug.assert(self.status_value != .draining); std.debug.assert(self.status_value != .closed); if (self.status_value == .closing and value != .connection_close) return false; return switch (value) { .padding => false, .ping => true, .ack => |ack| self.processAck(kind, ack, now_ns), .crypto => |crypto_frame| self.processCrypto(kind, crypto_frame, now_ns), .handshake_done => self.processHandshakeDone(kind, now_ns), .connection_close => |close_frame| self.processClose(kind, close_frame, now_ns), .stream, .reset_stream, .stop_sending, .max_data, .max_stream_data, .max_streams, .data_blocked, .stream_data_blocked, .streams_blocked, => self.processStreamFrame(kind, value, now_ns), else => { self.fail( quic.connection.TransportError.protocol_violation, frameType(value), "frame not implemented", now_ns, ); return null; }, }; } fn processAck( self: *Connection, kind: quic.connection.SpaceKind, value: quic.frame.Ack, now_ns: u64, ) ?bool { const sink = AckSink{ .connection = self, .kind = kind }; const acknowledged = self.space(kind).processAck(value, sink) catch { self.fail( quic.connection.TransportError.protocol_violation, 0x02, "ACK for unsent packet", now_ns, ); return null; }; self.sampleRoundTrip(kind, value, acknowledged, now_ns); if (kind == .handshake) self.handshake_acknowledged = true; if (acknowledged.records != 0 and self.peerCompletedAddressValidation()) self.pto_count = 0; self.detectLoss(kind, now_ns); if (kind == .application) self.keys.observeAck(value); return false; } /// Runs one detection pass over a space at the delay in force, serving as the single function /// starting loss detection so every pass uses the same delay. That delay is worked out from the /// latest and the smoothed measurement of the path. Whatever the pass removed is added to the /// connection's counters. fn detectLoss(self: *Connection, kind: quic.connection.SpaceKind, now_ns: u64) void { const delay_ns = quic.connection.recovery.lossDelayNs( self.rtt.latest_ns, self.rtt.smoothed_ns, ); const sink = LossSink{ .connection = self, .kind = kind }; const lost = self.space(kind).detectLost(now_ns, delay_ns, sink); self.stats_value.bytes_lost +|= lost.bytes; } /// Arms the one recovery deadline the connection reports, deciding which single deadline /// `nextTimeout` will report for recovery. While any space is waiting on the time rule, that /// wait governs and no probe is armed at all. With packets outstanding the function arms the /// soonest probe, unless an unvalidated server could not afford to send it. With nothing /// outstanding the function arms a client's anti-deadlock probe. A connection that has left /// handshaking or established arms nothing. fn armProbeTimeout(self: *Connection, now_ns: u64) void { self.pto_deadline_ns = null; if (!isOpen(self.status_value)) { self.pto_anchor_ns = null; return; } for (&self.spaces) |*space_value| { if (space_value.loss_deadline_ns == null) continue; self.pto_anchor_ns = null; return; } if (self.bytesInFlight() != 0) { self.pto_anchor_ns = null; const probe = self.armedProbe() orelse return; if (self.amplificationBlocks(probe.kind)) return; self.pto_deadline_ns = probe.deadline_ns; self.pto_space = probe.kind; return; } self.armDeadlockProbe(now_ns); } /// Arms the anti-deadlock probe from the anchor, keeping a client sending while it waits for a /// server flight that never came. A client keeps it armed with nothing outstanding until a /// Handshake acknowledgment, or a confirmed handshake, gives the peer its proof of the address. /// It points at the Handshake space if Handshake keys exist, and at the Initial space if /// Handshake keys are absent. With no write keys in the space it picked, the function leaves /// the timer disarmed and empties the anchor. fn armDeadlockProbe(self: *Connection, now_ns: u64) void { std.debug.assert(self.bytesInFlight() == 0); if (self.peerCompletedAddressValidation()) { self.pto_anchor_ns = null; return; } const kind: quic.connection.SpaceKind = if (self.keys.write(.handshake) != null) .handshake else .initial; if (self.keys.write(kind) == null) { self.pto_anchor_ns = null; return; } const anchor_ns = self.pto_anchor_ns orelse now_ns; self.pto_anchor_ns = anchor_ns; self.pto_space = kind; self.pto_deadline_ns = quic.connection.recovery.probeDeadlineNs( anchor_ns, self.probePeriodNs(kind), ); } /// Picks the soonest probe deadline among the spaces holding packets the peer has yet to /// settle, selecting the single winning probe timer among the three spaces. Each space measures /// its deadline from the last packet it sent that the peer owes an answer to. The application /// space is left out until the handshake is confirmed. With all spaces settled, the function /// returns null. fn armedProbe(self: *const Connection) ?Probe { var earliest: ?Probe = null; for ([_]quic.connection.SpaceKind{ .initial, .handshake, .application }) |kind| { const space_value = &self.spaces[spaceIndex(kind)]; if (space_value.bytesInFlight() == 0) continue; if (kind == .application and !self.handshake_confirmed) break; const sent_ns = space_value.last_ack_eliciting_sent_ns orelse continue; const deadline_ns = quic.connection.recovery.probeDeadlineNs( sent_ns, self.probePeriodNs(kind), ); if (earliest) |current| { if (deadline_ns >= current.deadline_ns) continue; } earliest = .{ .deadline_ns = deadline_ns, .kind = kind }; } return earliest; } /// Works out how long one space waits for an answer, at the doubling now in force, defining the /// period added to a send time for every probe deadline. The wait rests on the connection's /// smoothed measurement and its spread. fn probePeriodNs(self: *const Connection, kind: quic.connection.SpaceKind) u64 { const delay_ns = self.probeAckDelayNs(kind); return quic.connection.recovery.probePeriodNs( self.rtt.smoothed_ns, self.rtt.variation_ns, delay_ns, self.pto_count, ); } /// Reads the peer's `max_ack_delay` parameter into nanoseconds, for the application space and /// only past handshake confirmation, isolating the rule that adds the peer's delay only in one /// space and only after one point. It yields zero for the other two spaces, ahead of that /// confirmation, and before the peer's parameters have arrived. fn probeAckDelayNs(self: *const Connection, kind: quic.connection.SpaceKind) u64 { if (kind != .application) return 0; if (!self.handshake_confirmed) return 0; const parameters = self.peer_parameters orelse return 0; return maxAckDelayNs(parameters.max_ack_delay); } /// Says whether the peer has proof of this endpoint's address, deciding both whether the /// anti-deadlock probe stays armed and whether an acknowledgment resets the backoff. A server /// always has it, because a client proves the server's address by finishing the handshake with /// it. A client has it once a Handshake acknowledgment arrives, or once the handshake is /// confirmed. fn peerCompletedAddressValidation(self: *const Connection) bool { if (self.config.role == .server) return true; return self.handshake_acknowledged or self.handshake_confirmed; } /// Says whether an unvalidated server is short of the budget one probe in the space would cost, /// so the timer asks before arming a probe that would spin. That budget is three times what the /// client has sent this server, less what the server has sent back. The timer then waits on the /// client's next datagram to lift the budget. A client, and a server whose peer is validated, /// are never short. fn amplificationBlocks(self: *const Connection, kind: quic.connection.SpaceKind) bool { if (self.config.role != .server) return false; if (self.client_address_validated) return false; return self.remainingAmplificationBudget() < sendRoomMin(kind); } /// Owes the armed space its probe packets once the deadline has passed, turning an expired /// deadline into packets the next `send` call emits. One packet is owed when nothing is /// outstanding, and two otherwise. It then lifts the doubling count, empties the anchor the /// anti-deadlock probe measured from, adds one to the timeout counter, and arms the timer /// afresh. `send` puts the owed packets out, forcing a PING into each. fn expireProbeTimeout(self: *Connection, now_ns: u64) void { std.debug.assert(isOpen(self.status_value)); const deadline = self.pto_deadline_ns orelse return; if (now_ns < deadline) return; const kind = self.pto_space; const owed: u8 = if (self.bytesInFlight() == 0) 1 else quic.connection.recovery.probe_packets; self.probe_packets[spaceIndex(kind)] = owed; self.pto_count +|= 1; self.pto_anchor_ns = null; increment(&self.stats_value.probe_timeouts); self.armProbeTimeout(now_ns); } /// Runs a detection pass over every space whose wait has run out, providing the path through /// which a caller calling back at the reported deadline reaches the loss pass. `receive`, /// `send`, and `close` all arrive here by way of `advanceTime`. fn detectExpiredLoss(self: *Connection, now_ns: u64) void { std.debug.assert(isOpen(self.status_value)); for ([_]quic.connection.SpaceKind{ .initial, .handshake, .application }) |kind| { const deadline = self.space(kind).loss_deadline_ns orelse continue; if (now_ns < deadline) continue; self.detectLoss(kind, now_ns); } } /// Measures the path from one ACK frame as the single source feeding the round-trip estimate, /// enforcing two conditions that determine whether an acknowledgment contributes a sample. A /// measurement wants the ACK frame's largest number to be one this ACK settled first. It also /// wants at least one packet the peer owed an answer to among what this ACK settled. What it /// measures is the time since that packet went out, less the delay the peer reported. fn sampleRoundTrip( self: *Connection, kind: quic.connection.SpaceKind, value: quic.frame.Ack, acknowledged: quic.connection.Acknowledged, now_ns: u64, ) void { if (!acknowledged.ack_eliciting) return; const sent_ns = acknowledged.largest_sent_ns orelse return; const latest_ns = quic.connection.recovery.sample(sent_ns, now_ns) orelse return; self.rtt.update(latest_ns, self.peerAckDelayNs(kind, value)); } /// Works out how much of the gap one ACK's sender says it spent before answering, centralizing /// the rule that counts delay only in one space and caps it only after one point. Initial and /// Handshake acknowledgments omit this delay figure. The peer's `max_ack_delay` holds that /// figure down once the handshake is confirmed. The figure is zero until the peer's parameters /// have arrived. fn peerAckDelayNs( self: *const Connection, kind: quic.connection.SpaceKind, value: quic.frame.Ack, ) u64 { const parameters = self.peer_parameters orelse return 0; const maximum_ns: ?u64 = if (self.handshake_confirmed) maxAckDelayNs(parameters.max_ack_delay) else null; return quic.connection.recovery.reportedDelayNs( kind == .application, value.delay, parameters.ack_delay_exponent, maximum_ns, ); } /// Applies one stream or flow control frame as the entry point where these frames enter the /// connection, which only a 1-RTT packet may carry. The same frame inside an Initial or /// Handshake packet ends the connection with PROTOCOL_VIOLATION. Answering with null leaves the /// packet unacknowledged. An arriving STOP_SENDING resets the sending half, a MAX_STREAM_DATA /// lifts the stream's allowance, and a MAX_DATA lifts the connection's. An arriving MAX_STREAMS /// lifts how many bidirectional streams the peer allows. fn processStreamFrame( self: *Connection, kind: quic.connection.SpaceKind, value: quic.frame.Frame, now_ns: u64, ) ?bool { std.debug.assert(isOpen(self.status_value)); if (kind != .application) { self.fail( quic.connection.TransportError.protocol_violation, frameType(value), "stream frame outside 1-RTT", now_ns, ); return null; } const received = &self.stats_value.stream_received; switch (value) { .stream => |data| return self.processStreamData(data, now_ns), .reset_stream => |reset| return self.processResetStream(reset, now_ns), .stop_sending => |stop| { if (!self.admitStream(stop.stream_id, 0x05, now_ns)) return null; increment(&received.stop_sending); _ = self.stream_send.reset(stop.error_code); }, .max_stream_data => |limit| { if (!self.admitStream(limit.stream_id, 0x11, now_ns)) return null; increment(&received.max_stream_data); self.stream_send.credit.raise(limit.maximum); }, .stream_data_blocked => |limit| { if (!self.admitStream(limit.stream_id, 0x15, now_ns)) return null; increment(&received.stream_data_blocked); }, .max_data => |maximum| { increment(&received.max_data); self.connection_credit.raise(maximum); }, .data_blocked => increment(&received.data_blocked), .max_streams => |limit| { increment(&received.max_streams); if (!limit.unidirectional) { self.peer_streams_bidi = @max(self.peer_streams_bidi, limit.maximum); } }, .streams_blocked => increment(&received.streams_blocked), else => unreachable, } return true; } /// Puts one STREAM frame's data in order on the receiving half, delivering arriving STREAM data /// to the receiving part of stream 0. With the reassembly table full, the packet goes /// unacknowledged and the `stream_ranges` counter climbs. Data past a window ends the /// connection with FLOW_CONTROL_ERROR, and a final size at odds with one already settled ends /// it with FINAL_SIZE_ERROR. fn processStreamData(self: *Connection, data: quic.frame.Stream, now_ns: u64) ?bool { const frame_type = frameType(.{ .stream = data }); if (!self.admitStream(data.stream_id, frame_type, now_ns)) return null; const window = &self.connection_window; self.stream_receive.receive(window, data.offset, data.data, data.fin) catch |failure| { if (failure == error.RangesFull) { increment(&self.stats_value.drops.stream_ranges); return null; } const code = if (failure == error.FlowControl) quic.connection.TransportError.flow_control_error else quic.connection.TransportError.final_size_error; self.fail(code, frame_type, "STREAM data", now_ns); return null; }; const received = &self.stats_value.stream_received; increment(&received.stream_frames); received.stream_bytes +|= data.data.len; return true; } fn processResetStream(self: *Connection, reset: quic.frame.ResetStream, now_ns: u64) ?bool { if (!self.admitStream(reset.stream_id, 0x04, now_ns)) return null; const window = &self.connection_window; const code = reset.error_code; const final = reset.final_size; self.stream_receive.resetReceived(window, code, final) catch |failure| { const transport_code = if (failure == error.FlowControl) quic.connection.TransportError.flow_control_error else quic.connection.TransportError.final_size_error; self.fail(transport_code, 0x04, "RESET_STREAM final size", now_ns); return null; }; increment(&self.stats_value.stream_received.reset_stream); return true; } /// Runs the identifier checks as every frame naming a stream passes through it, and opens /// stream 0 on a server so the server first learns stream 0 exists. A frame naming a stream /// this endpoint should have opened itself ends the connection with STREAM_STATE_ERROR, unless /// it names stream 0 and stream 0 is open. A unidirectional identifier, or one whose sequence /// reaches the advertised limit, ends the connection with STREAM_LIMIT_ERROR. The function /// answers false after ending the connection. fn admitStream(self: *Connection, stream_id: StreamId, frame_type: u62, now_ns: u64) bool { const local: quic.connection.stream.Initiator = switch (self.config.role) { .client => .client, .server => .server, }; if (quic.connection.stream.initiator(stream_id) == local) { if (stream_id == 0 and self.stream_open) return true; self.fail( quic.connection.TransportError.stream_state_error, frame_type, "stream not opened", now_ns, ); return false; } const limit = quic.connection.parameters.localStreamsBidi(self.config.role); const unidirectional = quic.connection.stream.direction(stream_id) == .unidirectional; if (unidirectional or quic.connection.stream.sequence(stream_id) >= limit) { self.fail( quic.connection.TransportError.stream_limit_error, frame_type, "stream limit", now_ns, ); return false; } std.debug.assert(stream_id == 0); self.stream_open = true; return true; } fn processCrypto( self: *Connection, kind: quic.connection.SpaceKind, value: quic.frame.Crypto, now_ns: u64, ) ?bool { const stream = &self.crypto_streams[spaceIndex(kind)]; stream.receive(value.offset, value.data) catch |failure| { const code = if (failure == error.BufferExceeded) quic.connection.TransportError.crypto_buffer_exceeded else quic.connection.TransportError.protocol_violation; self.fail(code, 0x06, "CRYPTO reassembly", now_ns); return null; }; if (!self.driveTls(kind, now_ns)) return null; return true; } fn driveTls(self: *Connection, kind: quic.connection.SpaceKind, now_ns: u64) bool { const engine = if (self.engine) |*value| value else return false; const stream = &self.crypto_streams[spaceIndex(kind)]; for (0..self.limits.crypto_buffer_bytes) |_| { const bytes = stream.contiguous(); if (bytes.len == 0) break; engine.receive(kind.level(), bytes) catch { const alert = engine.alert() orelse quic.tls.Alert.internal_error; self.fail(@intCast(alert.quicError()), 0x06, "TLS alert", now_ns); return false; }; stream.consume(bytes.len); } self.keys.installTls(engine) catch { self.fail(quic.connection.TransportError.internal_error, 0x06, "TLS keys", now_ns); return false; }; if (!self.authenticatePeer(now_ns)) return false; self.observeTlsState(); return self.status_value != .closing; } fn authenticatePeer(self: *Connection, now_ns: u64) bool { if (self.peer_parameters_authenticated) return true; const engine = if (self.engine) |*value| value else return true; const peer_info = engine.peer() orelse return true; const source = self.peer_source_cid orelse return true; const parameters = quic.connection.parameters.validatePeer( self.config.role, peer_info.transport_parameters, source, self.original_dcid, ) catch { self.fail( quic.connection.TransportError.transport_parameter_error, null, "transport parameters", now_ns, ); return false; }; self.peer_parameters = parameters; self.connection_credit.raise(parameters.initial_max_data); const send_limit = quic.connection.parameters.streamSendLimit( self.config.role, parameters, ); self.stream_send.credit.raise(send_limit); self.peer_streams_bidi = parameters.initial_max_streams_bidi; self.peer_parameters_authenticated = true; self.effective_idle_timeout_ms = quic.connection.parameters.effectiveIdle( self.config.max_idle_timeout, parameters.max_idle_timeout, ); return true; } fn observeTlsState(self: *Connection) void { std.debug.assert(isOpen(self.status_value)); const engine = if (self.engine) |*value| value else return; if (self.config.role != .server) return; if (engine.state() != .handshake_confirmed) return; if (self.status_value == .handshaking) { self.pending_handshake_done = true; self.status_value = .established; } self.handshake_confirmed = true; self.discardHandshakeKeys(); } fn processHandshakeDone( self: *Connection, kind: quic.connection.SpaceKind, now_ns: u64, ) ?bool { std.debug.assert(isOpen(self.status_value)); if (kind != .application or self.config.role != .client) { self.fail( quic.connection.TransportError.protocol_violation, 0x1e, "invalid HANDSHAKE_DONE", now_ns, ); return null; } const engine = if (self.engine) |*value| value else return null; engine.confirm(); self.handshake_confirmed = true; self.discardHandshakeKeys(); if (self.status_value == .handshaking) self.status_value = .established; self.handshake_done_received = true; increment(&self.stats_value.handshake_done_received); return true; } fn processClose( self: *Connection, kind: quic.connection.SpaceKind, value: quic.frame.ConnectionClose, now_ns: u64, ) ?bool { if (value.application and kind != .application) { self.fail( quic.connection.TransportError.protocol_violation, 0x1d, "application close level", now_ns, ); return null; } if (self.status_value != .closing) { self.storeReason( value.error_code, value.application, value.frame_type, value.reason, true, ); } self.close_deadline_ns = self.close_deadline_ns orelse closeDeadline(now_ns); self.status_value = .draining; self.close_pending = false; return false; } fn sendClosing(self: *Connection, out: []u8, now_ns: u64) usize { std.debug.assert(self.status_value == .closing); std.debug.assert(self.close_reason_value != null); if (!self.close_pending) return 0; const kind = self.closeSpace() orelse { self.status_value = .closed; return 0; }; const limit = self.sendLimit(out.len); var length: usize = 0; if (kind == .application and self.keys.write(.handshake) != null) { length = self.sendClosePacket(.handshake, out[0..limit], now_ns); } length += self.sendClosePacket(kind, out[length..limit], now_ns); if (length == 0) return 0; std.debug.assert(length <= limit); std.debug.assert(self.close_deadline_ns != null); self.close_pending = false; self.advanceCloseResponseLimit(); self.recordDatagramSent(length); return length; } fn sendClosePacket( self: *Connection, kind: quic.connection.SpaceKind, out: []u8, now_ns: u64, ) usize { std.debug.assert(out.len <= self.limits.datagram_bytes); std.debug.assert(self.keys.write(kind) != null); const packet_limit: usize = if (kind == .initial) 1200 else out.len; if (out.len < packet_limit) return 0; const plan = self.planPacket(kind, packet_limit, now_ns) orelse return 0; var output = quic.cursor.Write.init(self.storage.packet[0..plan.payload_capacity]); const reason = self.close_reason_value.?; const application = reason.application and kind == .application; const converted = reason.application and !application; quic.frame.encode(.{ .connection_close = .{ .application = application, .error_code = if (converted) quic.connection.TransportError.application_error else reason.error_code, .frame_type = if (application) null else reason.frame_type orelse 0, .reason = if (converted) &.{} else reason.reason, } }, &output) catch return 0; const payload = Payload{ .bytes = output.written(), .summary = .{ .connection_close = true }, .ack_eliciting = false, .sent_ack = false, .sent_ping = false, .sent_handshake_done = false, .sent_close = true, }; const minimum: u16 = if (kind == .initial) 1200 else 0; const count = self.assemblePayload(kind, plan.number, payload, minimum, out, now_ns); std.debug.assert(count <= out.len); return count; } fn sendSpace( self: *Connection, kind: quic.connection.SpaceKind, out: []u8, available: usize, minimum_bytes: u16, force_ping: bool, now_ns: u64, ) usize { std.debug.assert(available <= out.len); std.debug.assert(available <= self.limits.datagram_bytes); if (available < sendRoomMin(kind)) return 0; if (self.keys.write(kind) == null) return 0; const payload_limit = if (kind == .initial and minimum_bytes != 0) @min(available, @as(usize, minimum_bytes)) else available; const plan = self.planPacket(kind, payload_limit, now_ns) orelse return 0; const payload = self.buildPayload( kind, plan.payload_capacity, force_ping, now_ns, ) catch { self.fail( quic.connection.TransportError.internal_error, null, "packet assembly", now_ns, ); return 0; } orelse return 0; return self.assemblePayload(kind, plan.number, payload, minimum_bytes, out, now_ns); } fn buildPayload( self: *Connection, kind: quic.connection.SpaceKind, available: usize, force_ping: bool, now_ns: u64, ) PayloadError!?Payload { std.debug.assert(available <= self.storage.packet.len); var output = quic.cursor.Write.init(self.storage.packet[0..available]); var payload = Payload{ .bytes = &.{}, .summary = .{}, .ack_eliciting = false, .sent_ack = false, .sent_ping = false, .sent_handshake_done = false, .sent_close = false, }; const space_value = self.space(kind); const record_room = space_value.sent.hasCapacity(); const application = kind == .application; const eliciting = force_ping or (application and self.applicationFramesPending()); const send_ack = space_value.ack_pending and ((record_room and eliciting) or space_value.ackDue(now_ns)); if (send_ack) payload.sent_ack = try self.emitAck(space_value, &output, now_ns); payload.summary.ack = payload.sent_ack; if (record_room) { if (application) try self.emitApplicationControl(&output, &payload); if (force_ping and !payload.ack_eliciting) try emitPing(&output, &payload); if (try self.emitCrypto(kind, &output)) |crypto_summary| { payload.summary.crypto = crypto_summary; payload.ack_eliciting = true; } if (application) try self.emitStreamFrames(&output, &payload); } if (output.index == 0) return null; payload.bytes = output.written(); return payload; } fn emitAck( self: *Connection, space_value: *quic.connection.Space, output: *quic.cursor.Write, now_ns: u64, ) PayloadError!bool { const start = output.index; const delay = space_value.ackDelay(now_ns, self.config.ack_delay_exponent); space_value.received.encode(delay, output) catch |failure| switch (failure) { error.NoSpace => { output.index = start; return false; }, else => |other| return other, }; return true; } fn emitApplicationControl( self: *Connection, output: *quic.cursor.Write, payload: *Payload, ) PayloadError!void { if (self.pending_handshake_done) { if (try encodeDeferred(output, .{ .handshake_done = {} })) { payload.summary.handshake_done = true; payload.sent_handshake_done = true; payload.ack_eliciting = true; } } if (self.pending_ping) try emitPing(output, payload); } /// Packs the flow control frames first, and a single STREAM frame for stream 0 after them, into /// whatever room the packet has left, constructing the stream half of one 1-RTT payload. The /// function packs the stream frames once stream 0 is open. A packet that took any of them /// obliges the peer to answer. fn emitStreamFrames( self: *Connection, output: *quic.cursor.Write, payload: *Payload, ) PayloadError!void { const start = output.index; try self.emitConnectionFlow(output, &payload.summary); if (self.stream_open) { try self.emitStreamControl(output, &payload.summary); try self.emitStream(output, &payload.summary); } if (output.index > start) payload.ack_eliciting = true; } fn emitConnectionFlow( self: *Connection, output: *quic.cursor.Write, summary: *quic.connection.FrameSummary, ) PayloadError!void { if (self.connection_window.update()) |limit| { if (try encodeDeferred(output, .{ .max_data = limit })) { summary.max_data = limit; } } if (self.connectionBlocked()) |limit| { if (try encodeDeferred(output, .{ .data_blocked = limit })) { summary.data_blocked = limit; } } if (self.streams_blocked_pending) { const limit = quic.frame.StreamLimit{ .unidirectional = false, .maximum = self.peer_streams_bidi, }; if (try encodeDeferred(output, .{ .streams_blocked = limit })) { summary.streams_blocked = limit; } } } fn emitStreamControl( self: *Connection, output: *quic.cursor.Write, summary: *quic.connection.FrameSummary, ) PayloadError!void { std.debug.assert(self.stream_open); if (self.stream_send.resetFrame(0)) |reset| { if (try encodeDeferred(output, .{ .reset_stream = reset })) { summary.reset_stream = reset; } } if (self.stream_receive.stop_pending) { const code = self.stream_receive.stop_code.?; const stop = quic.frame.StopSending{ .stream_id = 0, .error_code = code }; if (try encodeDeferred(output, .{ .stop_sending = stop })) { summary.stop_sending = stop; } } if (self.stream_receive.windowUpdate()) |maximum| { const limit = quic.frame.StreamData{ .stream_id = 0, .maximum = maximum }; if (try encodeDeferred(output, .{ .max_stream_data = limit })) { summary.max_stream_data = limit; } } if (self.stream_send.blocked()) |maximum| { const limit = quic.frame.StreamData{ .stream_id = 0, .maximum = maximum }; if (try encodeDeferred(output, .{ .stream_data_blocked = limit })) { summary.stream_data_blocked = limit; } } } /// Packs a single STREAM frame for stream 0, last of all, sizing the frame to whatever room is /// left. Bytes going a second time travel ahead of fresh ones, since the pick starts at the /// sending half's next offset. The length field goes in when padding follows, which occurs when /// the frame would otherwise end short of a full header protection sample. The FIN travels on /// the last byte. A frame too large for the room left keeps its bytes back, for some later /// packet to carry. fn emitStream( self: *Connection, output: *quic.cursor.Write, summary: *quic.connection.FrameSummary, ) PayloadError!void { std.debug.assert(self.stream_open); const send_part = &self.stream_send; const credit = self.connection_credit.available(); const offset = send_part.nextOffset(); const remaining = output.remaining(); const plain_max = frameDataCapacity(0, offset, remaining, false) orelse return; const plain = send_part.nextChunk(plain_max, credit) orelse return; std.debug.assert(plain.offset == offset); const plain_header = frameHeaderBytes(0, offset, plain.data.len, false); const plain_end = output.index + plain_header + plain.data.len; const length_present = plain_end < quic.connection.assemble.sampled_bytes_min; var chunk = plain; if (length_present) { const framed_max = frameDataCapacity(0, offset, remaining, true) orelse return; chunk = send_part.nextChunk(framed_max, credit) orelse return; } const value = quic.connection.stream.frame(0, chunk, length_present); if (!try encodeDeferred(output, value)) return; summary.stream = .{ .stream_id = 0, .offset = chunk.offset, .length = @intCast(chunk.data.len), .fin = chunk.fin, }; } /// Packs one CRYPTO frame for the space so handshake bytes reach a packet. Handshake bytes /// waiting in that space go first, and TLS is asked for nothing new. Otherwise fresh TLS bytes /// move the send offset on. TLS has no way to take bytes back, so fresh bytes that will not fit /// the packet join the ones waiting. fn emitCrypto( self: *Connection, kind: quic.connection.SpaceKind, output: *quic.cursor.Write, ) PayloadError!?quic.connection.CryptoSummary { std.debug.assert(output.remaining() <= self.limits.datagram_bytes); const engine = if (self.engine) |*value| value else return null; const index = spaceIndex(kind); std.debug.assert(engine.emitted(kind.level()).len == self.crypto_send_offsets[index]); if (!self.crypto_lost[index].isEmpty()) return self.emitLostCrypto(kind, output); const offset = self.crypto_send_offsets[index]; const maximum = cryptoDataCapacity(offset, output.remaining(), self.storage.scratch.len); if (maximum == 0) return null; const count = engine.emit(kind.level(), self.storage.scratch[0..maximum]); std.debug.assert(count <= maximum); if (count == 0) return null; self.crypto_send_offsets[index] += @intCast(count); const data = self.storage.scratch[0..count]; if (!try encodeDeferred(output, .{ .crypto = .{ .offset = offset, .data = data } })) { self.crypto_lost[index].add(offset, @intCast(count)); return null; } return .{ .offset = offset, .length = @intCast(count) }; } /// Packs one CRYPTO frame from the first waiting offset, carrying as many waiting bytes as fit, /// to rebuild a lost handshake frame from bytes TLS already emitted. The bytes come back out of /// the TLS engine's own record of what it produced. The function takes the packed bytes out of /// the waiting stretch there and then. Doing so there and then is safe, because the packet is /// written down as sent immediately after packing, or the connection fails. fn emitLostCrypto( self: *Connection, kind: quic.connection.SpaceKind, output: *quic.cursor.Write, ) PayloadError!?quic.connection.CryptoSummary { const engine = if (self.engine) |*value| value else return null; const lost = &self.crypto_lost[spaceIndex(kind)]; std.debug.assert(!lost.isEmpty()); const flight = engine.emitted(kind.level()); std.debug.assert(lost.end <= flight.len); const offset = lost.start; const room = output.remaining(); const waiting: usize = @intCast(lost.length()); const count = cryptoDataCapacity(offset, room, @min(waiting, room)); if (count == 0) return null; const data = flight[@intCast(offset)..][0..count]; const value = quic.frame.Frame{ .crypto = .{ .offset = offset, .data = data } }; if (!try encodeDeferred(output, value)) return null; lost.advance(@intCast(count)); return .{ .offset = offset, .length = @intCast(count) }; } fn assemblePayload( self: *Connection, kind: quic.connection.SpaceKind, number: quic.connection.NumberEncoding, payload: Payload, minimum_bytes: u16, out: []u8, now_ns: u64, ) usize { std.debug.assert(out.len <= self.limits.datagram_bytes); std.debug.assert(payload.bytes.len <= self.limits.datagram_bytes); const space_value = self.space(kind); if (payload.ack_eliciting) std.debug.assert(space_value.sent.hasCapacity()); const write_keys = self.keys.write(kind) orelse { self.fail(quic.connection.TransportError.internal_error, null, "write keys", now_ns); return 0; }; const destination_cid = self.peer_cid orelse { self.fail(quic.connection.TransportError.internal_error, null, "peer CID", now_ns); return 0; }; const packet_type: quic.connection.assemble.PacketType = switch (kind) { .initial => .initial, .handshake => .handshake, .application => .one_rtt, }; const built = quic.connection.assemble.packet(write_keys, .{ .packet_type = packet_type, .destination = destination_cid, .source = self.local_cid, .number = number, .key_phase = self.keys.send_phase, .minimum_bytes = minimum_bytes, }, payload.bytes, out) catch |failure| { if (failure == error.ConfidentialityLimitReached) { self.fail( quic.connection.TransportError.aead_limit_reached, null, "AEAD confidentiality limit", now_ns, ); } else { self.fail( quic.connection.TransportError.internal_error, null, "packet protection", now_ns, ); } return 0; }; if (!self.recordPacketSent(space_value, kind, number, payload, built, now_ns)) return 0; self.commitPayload(kind, payload, now_ns); increment(&self.stats_value.packets_sent); self.touchActivityOnSend(now_ns, payload.ack_eliciting); return built.length; } fn recordPacketSent( self: *Connection, space_value: *quic.connection.Space, kind: quic.connection.SpaceKind, number: quic.connection.NumberEncoding, payload: Payload, built: quic.connection.assemble.Built, now_ns: u64, ) bool { std.debug.assert(built.length <= self.limits.datagram_bytes); std.debug.assert(payload.bytes.len <= built.payload_length); std.debug.assert(number.packet_number == space_value.next_packet_number); if (!payload.ack_eliciting) { space_value.next_packet_number += 1; return true; } space_value.recordSent(.{ .packet_number = number.packet_number, .time_sent_ns = now_ns, .ack_eliciting = true, .acknowledged = false, .lost = false, .in_flight_bytes = built.length, .key_phase = self.keys.send_phase, .key_phase_present = kind == .application, .frames = payload.summary, }) catch { self.fail( quic.connection.TransportError.internal_error, null, "sent records", now_ns, ); return false; }; return true; } fn planPacket( self: *Connection, kind: quic.connection.SpaceKind, packet_bytes: usize, now_ns: u64, ) ?PacketPlan { std.debug.assert(packet_bytes <= self.limits.datagram_bytes); const destination = self.peer_cid orelse { self.fail(quic.connection.TransportError.internal_error, null, "peer CID", now_ns); return null; }; const number = self.space(kind).numberEncoding() catch { self.fail(quic.connection.TransportError.internal_error, null, "packet number", now_ns); return null; }; const number_bytes: usize = number.bits / 8; const reserve = packetReserve( kind, packet_bytes, @intCast(number_bytes), destination.length, self.local_cid.length, ); const sampled = quic.connection.assemble.sampled_bytes_min - number_bytes; if (reserve + @max(sampled, 1) > packet_bytes) return null; std.debug.assert(reserve < packet_bytes); return .{ .number = number, .payload_capacity = packet_bytes - reserve }; } fn commitPayload( self: *Connection, kind: quic.connection.SpaceKind, payload: Payload, now_ns: u64, ) void { if (payload.sent_ack) { const space_value = self.space(kind); space_value.markAckSent(); increment(&self.stats_value.ack_frames_sent); if (kind == .application) self.keys.observeAckSent(space_value.largest_received.?); } if (payload.ack_eliciting) { const owed = &self.probe_packets[spaceIndex(kind)]; if (owed.* != 0) owed.* -= 1; } if (payload.sent_ping) self.pending_ping = false; if (payload.sent_handshake_done) { self.pending_handshake_done = false; increment(&self.stats_value.handshake_done_sent); } if (payload.sent_close) self.close_pending = false; if (kind == .application) self.commitStreamFrames(payload.summary); if (kind == .handshake and self.config.role == .client) self.discardInitialKeys(); if (kind == .application and self.keys.application_write.?.confidentialityExhausted()) { self.fail( quic.connection.TransportError.aead_limit_reached, null, "AEAD confidentiality limit", now_ns, ); } } fn commitStreamFrames(self: *Connection, summary: quic.connection.FrameSummary) void { const sent_stats = &self.stats_value.stream_sent; if (summary.max_data) |limit| { self.connection_window.advertise(limit); increment(&sent_stats.max_data); } if (summary.data_blocked) |limit| { self.connection_credit.markBlocked(limit); increment(&sent_stats.data_blocked); } if (summary.streams_blocked != null) { self.streams_blocked_pending = false; self.streams_blocked_sent = true; increment(&sent_stats.streams_blocked); } if (summary.reset_stream != null) { self.stream_send.commitReset(); increment(&sent_stats.reset_stream); } if (summary.stop_sending != null) { self.stream_receive.stop_pending = false; increment(&sent_stats.stop_sending); } if (summary.max_stream_data) |limit| { self.stream_receive.window.advertise(limit.maximum); increment(&sent_stats.max_stream_data); } if (summary.stream_data_blocked) |limit| { self.stream_send.commitBlocked(limit.maximum); increment(&sent_stats.stream_data_blocked); } const range = summary.stream orelse return; const new_bytes = self.stream_send.commitChunk(range.offset, range.length, range.fin); self.connection_credit.consume(new_bytes); increment(&sent_stats.stream_frames); sent_stats.stream_bytes +|= range.length; } /// Decides what one lost 1-RTT packet puts back in the queue, centralizing the whole /// retransmission policy for that packet in one place. Stream 0 decides for the STREAM bytes, /// the FIN, the RESET_STREAM, and the STOP_SENDING. The two MAX frames go again, each carrying /// the figure now in force. The three BLOCKED frames go again while the sender still stands at /// the figure the lost one reported. HANDSHAKE_DONE goes again. fn loseApplicationFrames(self: *Connection, frames: quic.connection.FrameSummary) void { std.debug.assert(isOpen(self.status_value)); if (frames.handshake_done) self.pending_handshake_done = true; if (frames.max_data) |limit| self.connection_window.loseLimit(limit); if (frames.data_blocked) |limit| self.connection_credit.loseBlocked(limit); if (frames.streams_blocked) |limit| self.loseStreamsBlocked(limit); const send_part = &self.stream_send; const receive_part = &self.stream_receive; if (frames.stream) |range| send_part.lose(range.offset, range.length, range.fin); if (frames.reset_stream != null) send_part.loseReset(); if (frames.stop_sending != null) receive_part.loseStop(); if (frames.max_stream_data) |limit| receive_part.window.loseLimit(limit.maximum); if (frames.stream_data_blocked) |limit| send_part.credit.loseBlocked(limit.maximum); } /// Puts STREAMS_BLOCKED back in the queue while stream 0 is still unopened and the peer still /// allows the same number of streams the lost frame reported, resending the blocked report /// while that condition holds. fn loseStreamsBlocked(self: *Connection, limit: quic.frame.StreamLimit) void { std.debug.assert(!limit.unidirectional); if (self.stream_open) return; if (self.peer_streams_bidi != limit.maximum) return; self.streams_blocked_pending = true; } fn validateConnectionIds(self: *Connection, view: PacketView) bool { if (self.peer_source_cid == null) { const source = view.source orelse return false; if (view.kind != .initial) return false; self.peer_source_cid = source; self.peer_cid = source; } else if (view.source) |source| { if (!sameCid(self.peer_source_cid.?, source)) return false; } if (self.config.role == .server and view.kind == .initial) { if (sameCid(view.destination, self.original_dcid.?)) return true; } return sameCid(view.destination, self.local_cid); } /// Drops the Initial keys, everything the Initial space held for recovery, and its waiting /// handshake bytes, so an abandoned space leaves no timer or record behind. A client arrives /// here as it sends each Handshake packet, and a server as it takes one in. Only the first call /// does anything, which keeps a repeat from putting the doubling count back to the start. fn discardInitialKeys(self: *Connection) void { if (self.initialKeysDiscarded()) return; self.keys.discardInitial(); self.space(.initial).discard(); self.crypto_lost[spaceIndex(.initial)] = .{}; self.resetProbeTimeout(.initial); std.debug.assert(self.keys.write(.initial) == null); std.debug.assert(self.keys.read(.initial) == null); } /// Drops the Handshake keys, everything the Handshake space held for recovery, and its waiting /// handshake bytes, so an abandoned space leaves no timer or record behind. A client arrives /// here on each HANDSHAKE_DONE frame it takes in, and a server wherever TLS says the handshake /// stands confirmed. Only the first call does anything, which keeps a repeat from putting the /// doubling count back to the start. fn discardHandshakeKeys(self: *Connection) void { if (self.handshakeKeysDiscarded()) return; self.keys.discardHandshake(); self.space(.handshake).discard(); self.crypto_lost[spaceIndex(.handshake)] = .{}; self.resetProbeTimeout(.handshake); std.debug.assert(self.keys.write(.handshake) == null); std.debug.assert(self.keys.read(.handshake) == null); } /// Puts the probe timeout of a space whose keys are gone back to the start, so a space that no /// longer exists stops contributing to consecutive backoff timeouts. The doubling count starts /// over, and no probe is owed to that space. The function empties the deadline and the /// anti-deadlock anchor, and every caller leaves through `send` or `receive`, which arm the /// timer afresh. fn resetProbeTimeout(self: *Connection, kind: quic.connection.SpaceKind) void { std.debug.assert(kind != .application); self.pto_count = 0; self.probe_packets[spaceIndex(kind)] = 0; self.pto_deadline_ns = null; self.pto_anchor_ns = null; } fn noteClosingDatagram(self: *Connection) void { std.debug.assert(self.status_value == .closing); std.debug.assert(self.close_response_packet_limit >= 1); self.close_packets_since_response +|= 1; if (self.close_packets_since_response >= self.close_response_packet_limit) { self.close_pending = true; } } fn advanceCloseResponseLimit(self: *Connection) void { std.debug.assert(self.status_value == .closing); std.debug.assert(self.close_response_packet_limit >= 1); self.close_packets_since_response = 0; self.close_response_packet_limit *|= 2; } fn fail( self: *Connection, error_code: u62, frame_type: ?u62, reason: []const u8, now_ns: u64, ) void { switch (self.status_value) { .handshaking, .established => {}, .closing => { std.debug.assert(self.close_deadline_ns != null); return; }, .draining, .closed => return, } std.debug.assert(self.close_deadline_ns == null); self.storeReason(error_code, false, frame_type, reason, false); self.status_value = .closing; self.close_pending = true; self.close_deadline_ns = closeDeadline(now_ns); } fn storeReason( self: *Connection, error_code: u62, application: bool, frame_type: ?u62, reason: []const u8, remote: bool, ) void { std.debug.assert(reason.len <= self.storage.reason.len); @memcpy(self.storage.reason[0..reason.len], reason); self.close_reason_value = .{ .error_code = error_code, .application = application, .frame_type = frame_type, .reason = self.storage.reason[0..reason.len], .remote = remote, }; } fn closeSpace(self: *Connection) ?quic.connection.SpaceKind { if (self.keys.write(.application) != null) return .application; if (self.keys.write(.handshake) != null) return .handshake; if (self.keys.write(.initial) != null) return .initial; return null; } /// Says whether an identifier names the one stream this connection has open, providing the /// single initial check for every public method that names an already-open stream. `write`, /// `finish`, `read`, `resetStream`, `stopSending`, `sendState`, `receiveState`, and /// `streamStats` all pass through it. The check asserts that the connection still holds its /// storage, so a call made after the caller took that storage back trips in a debug build. /// Identifier 0 is the one it answers true for. fn streamKnown(self: *const Connection, id: StreamId) bool { std.debug.assert(!self.deinitialized); if (id != 0) return false; return self.stream_open; } fn streamOpenForApplication(self: *const Connection, id: StreamId) bool { if (!self.streamKnown(id)) return false; return isOpen(self.status_value); } fn connectionBlocked(self: *const Connection) ?u62 { if (!self.stream_open) return null; const send_part = &self.stream_send; const waiting = send_part.reset_code == null and send_part.sent < send_part.written; return self.connection_credit.blocked(waiting); } /// Says whether any 1-RTT control, flow control, or stream frame is waiting, which lets an ACK /// travel alongside one because an ACK alone does not oblige the peer to answer. The function /// covers a waiting HANDSHAKE_DONE, PING, and STREAMS_BLOCKED, a connection window figure, and /// a connection blocked report. Once stream 0 is open, the function covers a waiting reset, a /// waiting stop, a stream window figure, a stream blocked report, and a pick ready to go. fn applicationFramesPending(self: *const Connection) bool { if (self.pending_handshake_done) return true; if (self.pending_ping) return true; if (self.streams_blocked_pending) return true; if (self.connection_window.update() != null) return true; if (!self.stream_open) return false; if (self.stream_send.reset_pending) return true; if (self.stream_receive.stop_pending) return true; if (self.stream_receive.windowUpdate() != null) return true; if (self.stream_send.blocked() != null) return true; if (self.connectionBlocked() != null) return true; const credit = self.connection_credit.available(); return self.stream_send.nextChunk(std.math.maxInt(u16), credit) != null; } fn space(self: *Connection, kind: quic.connection.SpaceKind) *quic.connection.Space { std.debug.assert(spaceIndex(kind) < self.spaces.len); return &self.spaces[spaceIndex(kind)]; } fn sendLimit(self: *const Connection, out_length: usize) usize { var limit = @min(out_length, self.limits.datagram_bytes); if (self.config.role == .server and !self.client_address_validated) { limit = @min(limit, self.remainingAmplificationBudget()); } return limit; } fn remainingAmplificationBudget(self: *const Connection) usize { const received = std.math.mul(u64, self.received_before_validation, 3) catch std.math.maxInt(u64); if (received <= self.sent_before_validation) return 0; return std.math.cast(usize, received - self.sent_before_validation) orelse std.math.maxInt(usize); } fn recordDatagramSent(self: *Connection, length: usize) void { increment(&self.stats_value.datagrams_sent); if (self.config.role == .server and !self.client_address_validated) { self.sent_before_validation +|= length; } } fn touchActivity(self: *Connection, now_ns: u64) void { self.timer_started = true; self.last_activity_ns = now_ns; } fn touchActivityOnSend(self: *Connection, now_ns: u64, ack_eliciting: bool) void { if (!ack_eliciting) return; self.touchActivity(now_ns); } fn idleDeadline(self: *const Connection) ?u64 { if (!self.timer_started) return null; if (self.effective_idle_timeout_ms == 0) return null; const duration = std.math.mul( u64, self.effective_idle_timeout_ms, std.time.ns_per_ms, ) catch std.math.maxInt(u64); return std.math.add(u64, self.last_activity_ns, duration) catch std.math.maxInt(u64); } fn advanceTime(self: *Connection, now_ns: u64) void { if (self.close_deadline_ns) |deadline| { if (now_ns >= deadline) { self.status_value = .closed; self.close_pending = false; return; } } if (!isOpen(self.status_value)) return; self.detectExpiredLoss(now_ns); if (!isOpen(self.status_value)) return; self.expireProbeTimeout(now_ns); if (!isOpen(self.status_value)) return; const idle = self.idleDeadline() orelse return; if (now_ns < idle) return; self.storeReason(0, false, null, "idle timeout", false); self.status_value = .closed; }};Source: lib/quic/src/connection/machine.zig:77
pub const OpenError = error{ NotClient, NotEstablished, StreamExists, StreamLimit };Source: lib/quic/src/connection/model.zig:78
/// `Connection.closeReason` hands this structure back to report what ended the connection, and/// whether this endpoint or the peer decided it. The structure carries the numeric code, whether/// that code came from the application, the frame type if one applies, and the reason text. The/// reason text points into the connection's own storage, so it lives as long as that storage does.pub const CloseReason = struct { error_code: u62, application: bool, frame_type: ?u62, reason: []const u8, remote: bool,};Source: lib/quic/src/connection/model.zig:44
/// Carries the identity, the parameter values, the connection ID, and the random source that one/// handshake draws on. A caller passes this structure as the second argument to `Connection.init`/// so that the handshake receives everything it needs. Its `role` field decides which side of the/// protocol this connection plays, and the whole state machine branches on it. `Connection.init`/// turns away an empty connection ID, one past 20 bytes, and an acknowledgment delay exponent/// above 20. The parameter fields travel to the peer inside the TLS handshake.pub const Config = struct { role: quic.tls.Role, identity: quic.tls.Identity, expected_peer: ?quic.tls.PublicKey, alpn: []const u8, server_name: ?[]const u8, random: quic.tls.Random, local_cid: []const u8, preferred_suite: quic.crypto.Suite = .aes_128_gcm_sha256, max_idle_timeout: u62, ack_delay_exponent: u5, max_ack_delay: u14, initial_max_data: u62, initial_max_stream_data_bidi_local: u62, initial_max_stream_data_bidi_remote: u62,};Source: lib/quic/src/connection/model.zig:86
pub const DropStats = struct { malformed: u64 = 0, unauthenticated: u64 = 0, unsupported: u64 = 0, unavailable_keys: u64 = 0, wrong_connection: u64 = 0, duplicate: u64 = 0, undersized_initial: u64 = 0, oversized: u64 = 0, /// Counts the packets that went unacknowledged because the reassembly table had no room left, /// so a caller learns that the table and the network cost a packet. stream_ranges: u64 = 0,};Source: lib/quic/src/connection/model.zig:10
/// Fixes, field by field, how much the connection may retain, so that a caller fills this structure/// once and hands the same value to the capacity, the storage, and the connection. The connection/// asks for nothing more once it is running. `Capacity.derive`, `Storage.init`, and/// `Connection.init` all take the same value, and the connection compares its storage against a/// capacity derived afresh, answering `StorageMismatch` when the two disagree. Derivation turns/// away a zero in any field, a datagram size under 1200 bytes, and a received range count past what/// one ACK frame can hold.pub const Limits = struct { tls_message_max: u16, crypto_buffer_bytes: u20, sent_records: u16, received_ranges: u16, datagram_bytes: u16, /// Decides how much an application can hand over before waiting for the peer by sizing the /// buffer that keeps stream 0 bytes while the peer has yet to acknowledge them. `write` takes /// only what fits the room left there, so this figure bounds one handover. Room returns when /// every earlier byte has been acknowledged, or when a reset abandons what is left. stream_send_bytes: u32, /// Decides both how much arriving data the connection can hold and how much it invites the peer /// to send by sizing the buffer that holds arriving stream 0 bytes until an application reads /// them. The window advertised for stream 0 is the lesser of that buffer and the configured /// stream data limit, so the peer is invited to send only what there is room for. stream_receive_bytes: u32, /// Decides how much reordering the receiving half absorbs before a packet costs the connection /// a round trip by sizing the table that tracks separate stretches of arrived data while the /// holes between them stay unfilled. One packet past that count goes unacknowledged and lifts /// the `drops.stream_ranges` counter, so the peer sends it a second time. stream_receive_ranges: u16, /// Decides how many STREAM frames can be outstanding at one time by sizing the table that keeps /// one entry per STREAM frame until an acknowledgment or a loss resolves it. With every entry /// taken, `nextChunk` offers nothing, so the connection packs no further STREAM frame until one /// entry comes free. stream_sent_ranges: u16,};Source: lib/quic/src/connection/model.zig:141
/// Counts datagrams, packets, failed authentications, and drops over the life of one connection,/// returned by `Connection.stats` so that a caller sees what the connection did with each datagram./// Every counter holds at its maximum once it reaches it. The structure nests one frame counter per/// direction and the drop counters.pub const Stats = struct { datagrams_sent: u64 = 0, datagrams_received: u64 = 0, packets_sent: u64 = 0, packets_received: u64 = 0, ack_frames_sent: u64 = 0, handshake_done_sent: u64 = 0, handshake_done_received: u64 = 0, failed_authentications: u64 = 0, /// Counts what loss detection removed, packet by packet and byte by byte, so that a caller /// learns how much loss detection has taken out of the flight. packets_lost: u64 = 0, bytes_lost: u64 = 0, /// Counts the probe deadlines that passed without one, so that a caller learns how often the /// connection had to prod the peer for an acknowledgment. probe_timeouts: u64 = 0, stream_sent: StreamFrameStats = .{}, stream_received: StreamFrameStats = .{}, drops: DropStats = .{},};Source: lib/quic/src/connection/model.zig:66
/// Names the five conditions a caller can observe from outside as handshaking, established,/// closing, draining, and closed. `Connection.state` hands this status back so that a caller knows/// whether to keep driving the connection. While a connection is closing or draining, `nextTimeout`/// offers only its close deadline, and a closed one offers nothing. A closing connection works/// through CONNECTION_CLOSE frames and passes over the rest.pub const Status = enum { handshaking, established, closing, draining, closed,};Source: lib/quic/src/connection/model.zig:104
/// Counts the stream and flow control frames that crossed in one direction, carried by `Stats` for/// each direction so that a caller can see which frames moved. The structure has a field for STREAM/// frames and their bytes, for RESET_STREAM and STOP_SENDING, for the three MAX frames, and for the/// three BLOCKED frames.pub const StreamFrameStats = struct { stream_frames: u64 = 0, stream_bytes: u64 = 0, reset_stream: u64 = 0, stop_sending: u64 = 0, max_data: u64 = 0, max_stream_data: u64 = 0, max_streams: u64 = 0, data_blocked: u64 = 0, stream_data_blocked: u64 = 0, streams_blocked: u64 = 0,};Source: lib/quic/src/connection/model.zig:121
/// Reports what has happened to stream 0 in both directions, returned by `Connection.streamStats`/// for an open stream so that an application can see how far its data has traveled. The structure/// carries the bytes written, sent, released, received, and read, the final size each side settled/// on, and the codes a reset or a stop carried.pub const StreamStats = struct { bytes_written: u62, bytes_sent: u62, /// Counts the bytes the send buffer has given up, whether an acknowledgment or a reset freed /// them, so an application learns how much of the send buffer has come free for the next /// handover. bytes_released: u62, bytes_received: u62, bytes_read: u62, send_final_size: ?u62, receive_final_size: ?u62, reset_sent_code: ?u62, reset_received_code: ?u62, stop_sending_sent_code: ?u62,};Source: lib/quic/src/connection/sent.zig:96
/// Reports what one ACK frame settled for the first time, so the connection can take a round-trip/// sample that needs both of its facts. The summary carries a packet count, whether any of those/// packets was one the peer owed an answer to, and when the largest of them went out.pub const Acknowledged = struct { records: u16 = 0, ack_eliciting: bool = false, /// Holds when the ACK frame's largest number went out so the connection can sample round-trip /// time, populated only when this ACK settles that packet for the first time. When an earlier /// ACK already settled that largest number, the field stays empty, and the connection skips /// round-trip sampling. largest_sent_ns: ?u64 = null,};Source: lib/quic/src/connection/sent.zig:6
pub const CryptoSummary = struct { offset: u62, length: u20,};Source: lib/quic/src/connection/sent.zig:26
/// Keeps, for one sent packet, whatever a later loss must act on, so loss recovery finds what one/// lost packet can put back in the queue. The stream and flow control fields hold the figures that/// went out, so a lost frame can be weighed against the state as it stands now before anything is/// sent again. The ACK, PING, HANDSHAKE_DONE, and CONNECTION_CLOSE fields need one bit each,/// because those frames lack recovery data for the sender.pub const FrameSummary = struct { crypto: ?CryptoSummary = null, stream: ?StreamSummary = null, reset_stream: ?quic.frame.ResetStream = null, stop_sending: ?quic.frame.StopSending = null, max_data: ?u62 = null, max_stream_data: ?quic.frame.StreamData = null, /// A connection in this package sends no MAX_STREAMS, because the one stream the server offers /// leaves the count nothing to grow into, so the loss path asserts this field stays empty. max_streams: ?quic.frame.StreamLimit = null, data_blocked: ?u62 = null, stream_data_blocked: ?quic.frame.StreamData = null, streams_blocked: ?quic.frame.StreamLimit = null, ack: bool = false, ping: bool = false, handshake_done: bool = false, connection_close: bool = false,};Source: lib/quic/src/connection/sent.zig:84
/// Reports what one detection pass took out and when the next pass is due, so the space can store/// that pass's deadline. The report carries a packet count, a byte total, and a deadline.pub const Lost = struct { records: u16 = 0, bytes: u64 = 0, /// Holds the soonest moment at which the time threshold will reach a packet the pass left /// alone, offered by `nextTimeout` so a caller knows when to call back. The field stays empty /// when the pass left nothing outstanding under the largest acknowledged number. deadline_ns: ?u64 = null,};Source: lib/quic/src/connection/sent.zig:106
pub const Sent = struct { records: []SentRecord, head: u16 = 0, count: u16 = 0, /// Holds what this space has outstanding, in bytes, to supply this space's share of what the /// connection has outstanding. The count climbs as packets go out and falls as each is settled. in_flight_bytes: u64 = 0, pub const AddError = error{Full}; pub fn init(records: []SentRecord) Sent { std.debug.assert(records.len <= std.math.maxInt(u16)); for (records) |*record| record.* = SentRecord.empty(); return .{ .records = records }; } pub fn hasCapacity(self: *Sent) bool { self.retirePrefix(); return self.count < self.records.len; } pub fn add(self: *Sent, record: SentRecord) AddError!void { std.debug.assert(record.inFlight()); self.retirePrefix(); if (self.count == self.records.len) return error.Full; const slot = self.slotIndex(self.count); self.records[slot] = record; self.count += 1; self.in_flight_bytes += record.in_flight_bytes; } /// Settles every held packet the ACK frame covers and hands each one to the caller-supplied /// sink's `onAcknowledged` method, so the arriving frame reaches the stream and CRYPTO state /// that settled packets carried. A packet the same ACK has already settled is passed over, so /// the sink sees each one once. The ring retires settled packets at its front, freeing their /// slots for later sends. The function reports back what this ACK settled for the first time. pub fn acknowledge(self: *Sent, value: quic.frame.Ack, sink: anytype) Acknowledged { var result = Acknowledged{}; for (0..self.records.len) |offset| { if (offset >= self.count) break; const record = &self.records[self.slotIndex(@intCast(offset))]; if (!record.inFlight()) continue; if (!acknowledges(value, record.packet_number)) continue; record.acknowledged = true; self.release(record.in_flight_bytes); result.records +|= 1; result.ack_eliciting = result.ack_eliciting or record.ack_eliciting; if (record.packet_number == value.largest) result.largest_sent_ns = record.time_sent_ns; sink.onAcknowledged(record.*); } self.retirePrefix(); return result; } /// Empties the ring and returns the outstanding byte count to zero when the space drops its /// keys, because subsequent arrivals lack keys to settle those packets. pub fn clear(self: *Sent) void { for (self.records) |*record| record.* = SentRecord.empty(); self.head = 0; self.count = 0; self.in_flight_bytes = 0; } /// Walks the held packets at or under the largest acknowledged number and settles each one that /// either rule reaches, moving the frames of each lost packet back into the send queues. The /// rule that declares a packet lost once three later packets are acknowledged, the packet /// threshold, reaches a packet three or more below that number, and the time threshold reaches /// one whose loss delay has run out. Each packet the pass settles goes to the caller-supplied /// sink's `onLost` method. The pass reports the soonest moment at which the time rule will /// reach one of the packets it left alone. pub fn detectLost( self: *Sent, largest_acknowledged: u62, now_ns: u64, delay_ns: u64, sink: anytype, ) Lost { var result = Lost{}; for (0..self.records.len) |offset| { if (offset >= self.count) break; const record = &self.records[self.slotIndex(@intCast(offset))]; if (!record.inFlight()) continue; if (record.packet_number > largest_acknowledged) continue; const deadline_ns = recovery.lossDeadlineNs(record.time_sent_ns, delay_ns); const threshold = recovery.reordered(record.packet_number, largest_acknowledged); if (!threshold and now_ns < deadline_ns) { result.deadline_ns = earlier(result.deadline_ns, deadline_ns); continue; } record.lost = true; self.release(record.in_flight_bytes); result.records +|= 1; result.bytes +|= record.in_flight_bytes; sink.onLost(record.*); } self.retirePrefix(); return result; } fn release(self: *Sent, bytes: u32) void { std.debug.assert(self.in_flight_bytes >= bytes); self.in_flight_bytes -= bytes; } fn retirePrefix(self: *Sent) void { for (0..self.records.len) |_| { if (self.count == 0) break; if (self.records[self.head].inFlight()) break; self.records[self.head] = SentRecord.empty(); self.head = @intCast((@as(usize, self.head) + 1) % self.records.len); self.count -= 1; } } fn slotIndex(self: *const Sent, offset: u16) usize { std.debug.assert(offset <= self.count); std.debug.assert(offset < self.records.len); return (@as(usize, self.head) + offset) % self.records.len; }};Source: lib/quic/src/connection/sent.zig:49
/// Holds one sent packet until an acknowledgment or a loss settles it, so both the acknowledgment/// path and the loss path work over this record. The record keeps the number, the send time,/// whether the peer owes an answer, the byte cost, the key phase, and what the packet carried. Once/// settled, the packet stops counting against the space's outstanding bytes.pub const SentRecord = struct { packet_number: u62, time_sent_ns: u64, ack_eliciting: bool, acknowledged: bool, lost: bool, in_flight_bytes: u32, key_phase: bool, key_phase_present: bool, frames: FrameSummary, pub fn empty() SentRecord { return .{ .packet_number = 0, .time_sent_ns = 0, .ack_eliciting = false, .acknowledged = false, .lost = false, .in_flight_bytes = 0, .key_phase = false, .key_phase_present = false, .frames = .{}, }; } /// Reports whether this packet still counts against the space's outstanding bytes, so both /// passes can skip a record that stopped counting. The packet counts while awaiting settlement /// from an acknowledgment or a loss. pub fn inFlight(self: SentRecord) bool { return !self.acknowledged and !self.lost; }};Source: lib/quic/src/connection/sent.zig:14
/// Records which stream bytes one STREAM frame put on the wire and whether that frame closed the/// stream, so the sending half of stream 0 receives this summary once the packet that carried that/// frame is resolved. The summary names the stream, the offset, the byte count, and the FIN.pub const StreamSummary = struct { stream_id: u62, offset: u62, length: u16, fin: bool,};Source: lib/quic/src/connection/space.zig:4
pub const Kind = enum(u2) { initial, handshake, application, pub fn level(self: Kind) quic.tls.Level { return switch (self) { .initial => .initial, .handshake => .handshake, .application => .one_rtt, }; }};Source: lib/quic/src/connection/space.zig:18
pub const NumberEncoding = struct { packet_number: u62, bits: u6, value: u32,};Source: lib/quic/src/connection/space.zig:24
pub const Space = struct { kind: Kind, next_packet_number: u62 = 0, largest_acknowledged: ?u62 = null, largest_received: ?u62 = null, received: quic.connection.Ranges, sent: quic.connection.Sent, ack_pending: bool = false, largest_received_at_ns: u64 = 0, ack_due_at_ns: ?u64 = null, /// Holds when this space last sent a packet the peer owes an answer to. The probe period is /// added to it to reach the probe deadline for this space. Discarding the space's keys wipes /// it. last_ack_eliciting_sent_ns: ?u64 = null, /// Holds when the oldest packet still outstanding under the largest acknowledged number will /// fall to the time rule, so `nextTimeout` offers this instant to the connection. Each /// detection pass writes it afresh. The deadline is empty until an acknowledgment has arrived, /// and empty again once every packet the rules can reach has gone. loss_deadline_ns: ?u64 = null, pub const NumberError = error{ PacketNumberExhausted, PacketNumberDistance }; pub const AckError = error{AcknowledgesUnsent}; pub fn init( kind: Kind, ranges: []quic.connection.Range, records: []quic.connection.SentRecord, ) Space { return .{ .kind = kind, .received = quic.connection.Ranges.init(ranges), .sent = quic.connection.Sent.init(records), }; } pub fn numberEncoding(self: *const Space) NumberError!NumberEncoding { const packet_number = self.next_packet_number; if (packet_number >= std.math.maxInt(u62)) return error.PacketNumberExhausted; if (self.largest_acknowledged) |largest| { if (packet_number <= largest) return error.PacketNumberDistance; const distance = packet_number - largest; if (distance >= @as(u62, 1) << 31) return error.PacketNumberDistance; const truncated = quic.packet.Number.truncate(packet_number, largest); return .{ .packet_number = packet_number, .bits = truncated.bits, .value = truncated.value, }; } return .{ .packet_number = packet_number, .bits = 32, .value = @truncate(packet_number) }; } pub fn recordSent(self: *Space, record: quic.connection.SentRecord) !void { std.debug.assert(record.packet_number == self.next_packet_number); try self.sent.add(record); self.next_packet_number += 1; if (record.ack_eliciting) self.last_ack_eliciting_sent_ns = record.time_sent_ns; } pub fn processAck( self: *Space, value: quic.frame.Ack, sink: anytype, ) AckError!quic.connection.Acknowledged { if (value.largest >= self.next_packet_number) return error.AcknowledgesUnsent; const result = self.sent.acknowledge(value, sink); if (self.largest_acknowledged == null) self.largest_acknowledged = value.largest; if (self.largest_acknowledged) |largest| { if (value.largest > largest) self.largest_acknowledged = value.largest; } return result; } /// Reports what this space has outstanding, in bytes, so the connection sums it over the three /// spaces and the timer reads it to choose which probe to arm. pub fn bytesInFlight(self: *const Space) u64 { return self.sent.in_flight_bytes; } /// Walks this space's outstanding packets once, hands the sink each one that either rule /// reaches, and writes the space's deadline afresh. The connection runs this pass after each /// ACK and again once a deadline passes, so the sink queues the frames of a lost packet. Before /// any acknowledgment has arrived the call removes nothing, because both rules are measured /// against the largest acknowledged number. In that case the call also empties the space's /// deadline. pub fn detectLost( self: *Space, now_ns: u64, delay_ns: u64, sink: anytype, ) quic.connection.Lost { const largest = self.largest_acknowledged orelse { self.loss_deadline_ns = null; return .{}; }; const result = self.sent.detectLost(largest, now_ns, delay_ns, sink); self.loss_deadline_ns = result.deadline_ns; return result; } /// Throws away this space's outstanding packets and both of its timers, so no timer outlives /// the space it belongs to. The call also clears the pending acknowledgment, so nothing is owed /// in a space that can no longer send. The received ranges are left alone, because nothing /// reads them once the keys are gone. pub fn discard(self: *Space) void { self.clearAck(); self.sent.clear(); self.loss_deadline_ns = null; self.last_ack_eliciting_sent_ns = null; } pub fn recordReceived( self: *Space, packet_number: u62, ack_eliciting: bool, now_ns: u64, maximum_delay_ns: u64, ) bool { if (!self.received.insert(packet_number)) return false; if (self.largest_received == null) self.largest_received = packet_number; if (self.largest_received) |largest| { if (packet_number >= largest) { self.largest_received = packet_number; self.largest_received_at_ns = now_ns; } } if (!ack_eliciting) return true; self.ack_pending = true; const delay = if (self.kind == .application) maximum_delay_ns else 0; self.ack_due_at_ns = std.math.add(u64, now_ns, delay) catch std.math.maxInt(u64); return true; } pub fn ackDue(self: *const Space, now_ns: u64) bool { if (!self.ack_pending) return false; const due = self.ack_due_at_ns orelse return false; return due <= now_ns; } pub fn ackDelay(self: *const Space, now_ns: u64, exponent: u5) u62 { if (now_ns <= self.largest_received_at_ns) return 0; const delay_us = (now_ns - self.largest_received_at_ns) / std.time.ns_per_us; const encoded = delay_us >> exponent; return @intCast(@min(encoded, std.math.maxInt(u62))); } pub fn markAckSent(self: *Space) void { std.debug.assert(self.ack_pending); self.ack_pending = false; self.ack_due_at_ns = null; } pub fn clearAck(self: *Space) void { self.ack_pending = false; self.ack_due_at_ns = null; }};Source: lib/quic/src/connection/storage.zig:36
/// Capacity works out, from the limits alone, where each buffer sits in the block and how many/// bytes the block needs in total. A caller derives one to learn how large a byte array to hand/// `Storage.init`. Its `storage_bytes` field is the figure a caller sizes its array by. Derivation/// fails with `LimitsEmpty`, `RangeLimitExceeded`, `DatagramTooSmall`, `DatagramTooLarge`, or/// `CapacityOverflow`, which puts invalid limits out before any storage is touched. The layout/// covers the sent records and received ranges of all three spaces, the TLS storage, the CRYPTO/// lanes, the packet, scratch, and reason bytes, the eight key slots, the encoded transport/// parameters, and the stream 0 buffers and tables.pub const Capacity = struct { sent_offset: usize, sent_count: usize, sent_bytes: usize, ranges_offset: usize, range_count: usize, range_bytes: usize, tls_offset: usize, tls_bytes: usize, crypto_offset: usize, crypto_lane_bytes: usize, packet_offset: usize, scratch_offset: usize, reason_offset: usize, keys_offset: usize, keys_bytes: usize, transport_offset: usize, transport_bytes: usize, stream_sent_offset: usize, stream_range_offset: usize, stream_send_offset: usize, stream_receive_offset: usize, storage_bytes: usize, pub const DeriveError: type = CapacityDeriveError; pub fn derive(limits: StorageLimits) DeriveError!Capacity { try validateLimits(limits); const sent_count = try multiplied(limits.sent_records, 3); const sent = try placed(quic.connection.SentRecord, 0, sent_count); const range_count = try multiplied(limits.received_ranges, 3); const ranges = try placed(quic.connection.Range, sent.end, range_count); const tls_offset = try aligned(ranges.end, quic.tls.Storage.storage_alignment); const tls_capacity = quic.tls.Capacity.derive(.{ .max_message = limits.tls_message_max, }) catch return error.CapacityOverflow; const crypto_offset = try added(tls_offset, tls_capacity.storage_bytes); const crypto_lane_bytes: usize = limits.crypto_buffer_bytes; const crypto_bytes = try multiplied(crypto_lane_bytes, 6); const packet_offset = try added(crypto_offset, crypto_bytes); const scratch_offset = try added(packet_offset, limits.datagram_bytes); const reason_offset = try added(scratch_offset, limits.datagram_bytes); const reason_end = try added(reason_offset, limits.datagram_bytes); const keys_offset = try aligned(reason_end, quic.crypto.Keys.storage_alignment); const keys_bytes = try multiplied(key_slot_count, quic.crypto.Keys.storage_bytes_max); const transport_offset = try added(keys_offset, keys_bytes); const transport_end = try added(transport_offset, limits.tls_message_max); const streams = try placeStreams(limits, transport_end); return .{ .sent_offset = sent.start, .sent_count = sent_count, .sent_bytes = sent.bytes, .ranges_offset = ranges.start, .range_count = range_count, .range_bytes = ranges.bytes, .tls_offset = tls_offset, .tls_bytes = tls_capacity.storage_bytes, .crypto_offset = crypto_offset, .crypto_lane_bytes = crypto_lane_bytes, .packet_offset = packet_offset, .scratch_offset = scratch_offset, .reason_offset = reason_offset, .keys_offset = keys_offset, .keys_bytes = keys_bytes, .transport_offset = transport_offset, .transport_bytes = limits.tls_message_max, .stream_sent_offset = streams.sent.start, .stream_range_offset = streams.ranges.start, .stream_send_offset = streams.send.start, .stream_receive_offset = streams.receive.start, .storage_bytes = streams.receive.end, }; }};Source: lib/quic/src/connection/storage.zig:7
pub const KeySlot = enum(u3) { initial_write, initial_read, handshake_write, handshake_read, application_write, application_read_previous, application_read_current, application_read_next,};Source: lib/quic/src/connection/storage.zig:175
/// Storage cuts the caller's block into every typed buffer one connection works out of. A caller/// sets one up over its own bytes and hands a pointer to `Connection.init`, which keeps that/// pointer. The block and the connection both have to stay put for the connection's life, because/// the connection and the key state each hold a pointer to it. A block shorter than the derived/// total is refused with `StorageTooShort`, and nothing else happens. `activate` moves it from/// setup into steady use, and `Connection.init` does that once the connection stands. `deinit`/// wipes every byte and hands the block back, and it requires that the connection has already/// released the TLS storage nested inside. `records`, `ranges`, and `keys` hand out the per-space/// and per-slot views of the block.pub const Storage = struct { phase: alloc_phase.capacity.Phase, capacity: StorageCapacity, storage: []align(quic.tls.Storage.storage_alignment) u8, sent_records: []quic.connection.SentRecord, received_ranges: []quic.connection.Range, tls_storage: quic.tls.Storage, crypto_bytes: [3][]u8, crypto_present: [3][]u8, packet: []u8, scratch: []u8, reason: []u8, key_bytes: []u8, transport: []u8, stream_sent_ranges: []quic.connection.stream.SentRange, stream_receive_ranges: []quic.connection.stream.Range, stream_send_bytes: []u8, stream_receive_bytes: []u8, nested_released: bool = false, pub const storage_alignment: usize = quic.tls.Storage.storage_alignment; pub const Storage = []align(storage_alignment) u8; pub const Limits: type = StorageLimits; pub const Capacity: type = StorageCapacity; pub const InitError = StorageCapacity.DeriveError || quic.tls.Storage.InitError || error{StorageTooShort}; pub const work_limits: alloc_phase.capacity.WorkLimits = .{ .transition_steps_max = 1, .cleanup_steps_per_call_max = 0, .cleanup_calls_at_capacity_max = 0, }; pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "quic.connection_storage", .kind = .startup_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "packet_spaces", .lifetime = .steady, .detail = "three bounded sent-record and received-range lanes", }, .{ .id = "crypto_and_tls", .lifetime = .steady, .detail = "TLS storage, CRYPTO reassembly, and packet protection keys", }, .{ .id = "datagram_work", .lifetime = .steady, .detail = "packet input, open scratch, and close reason bytes", }, .{ .id = "stream_buffers", .lifetime = .steady, .detail = "stream 0 send and receive rings with sent and received ranges", }, }, .excluded = &.{ "caller configuration slices and identity key storage", "caller datagram input and output slices", "standard cryptographic primitive stack storage", }, }, .capacity = .{ .inputs = &.{ alloc_phase.capacity.bindInput( StorageLimits, "tls_message_max", "tls_message_max", ), alloc_phase.capacity.bindInput( StorageLimits, "crypto_buffer_bytes", "crypto_buffer_bytes", ), alloc_phase.capacity.bindInput( StorageLimits, "sent_records", "sent_records", ), alloc_phase.capacity.bindInput( StorageLimits, "received_ranges", "received_ranges", ), alloc_phase.capacity.bindInput( StorageLimits, "datagram_bytes", "datagram_bytes", ), alloc_phase.capacity.bindInput( StorageLimits, "stream_send_bytes", "stream_send_bytes", ), alloc_phase.capacity.bindInput( StorageLimits, "stream_receive_bytes", "stream_receive_bytes", ), alloc_phase.capacity.bindInput( StorageLimits, "stream_receive_ranges", "stream_receive_ranges", ), alloc_phase.capacity.bindInput( StorageLimits, "stream_sent_ranges", "stream_sent_ranges", ), }, .type_selectors = &.{ alloc_phase.capacity.bindType( quic.connection.SentRecord, "sent_record", ), alloc_phase.capacity.bindType( quic.connection.Range, "received_range", ), alloc_phase.capacity.bindType( quic.connection.stream.SentRange, "stream_sent_range", ), alloc_phase.capacity.bindType( quic.connection.stream.Range, "stream_received_range", ), }, .nodes = &.{ .{ .input = 2 }, .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 3 } } }, .{ .scale = .{ .node = 1, .coefficient = .{ .size_of_concrete_type = 0 }, } }, .{ .alignment = .{ .node = 2, .alignment = .{ .concrete_type = 1 }, } }, .{ .input = 3 }, .{ .scale = .{ .node = 4, .coefficient = .{ .literal = 3 } } }, .{ .scale = .{ .node = 5, .coefficient = .{ .size_of_concrete_type = 1 }, } }, .{ .add = .{ .left = 3, .right = 6 } }, .{ .alignment = .{ .node = 7, .alignment = .{ .literal = 16 } } }, .{ .input = 0 }, .{ .scale = .{ .node = 9, .coefficient = .{ .literal = 8 } } }, .{ .constant = 28 }, .{ .add = .{ .left = 10, .right = 11 } }, .{ .add = .{ .left = 8, .right = 12 } }, .{ .input = 1 }, .{ .scale = .{ .node = 14, .coefficient = .{ .literal = 6 } } }, .{ .add = .{ .left = 13, .right = 15 } }, .{ .input = 4 }, .{ .scale = .{ .node = 17, .coefficient = .{ .literal = 3 } } }, .{ .add = .{ .left = 16, .right = 18 } }, .{ .constant = 608 }, .{ .add = .{ .left = 19, .right = 20 } }, .{ .add = .{ .left = 21, .right = 9 } }, .{ .alignment = .{ .node = 22, .alignment = .{ .concrete_type = 2 }, } }, .{ .input = 8 }, .{ .scale = .{ .node = 24, .coefficient = .{ .size_of_concrete_type = 2 }, } }, .{ .add = .{ .left = 23, .right = 25 } }, .{ .alignment = .{ .node = 26, .alignment = .{ .concrete_type = 3 }, } }, .{ .input = 7 }, .{ .scale = .{ .node = 28, .coefficient = .{ .size_of_concrete_type = 3 }, } }, .{ .add = .{ .left = 27, .right = 29 } }, .{ .input = 5 }, .{ .add = .{ .left = 30, .right = 31 } }, .{ .input = 6 }, .{ .add = .{ .left = 32, .right = 33 } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .exact, .expression = 34, }}, }, .overload = .{ .kind = .reject_before_seal, .detail = "invalid limits and short caller storage reject before activation", }, .risks = .{ .transitive = .{ .status = .witnessed, .detail = "packet, frame, TLS, and crypto paths retain only provisioned slices", }, .foreign = .{ .status = .excluded, .detail = "the sans-I/O connection crosses no socket or foreign boundary", }, }, .work = .{ .equation = "each operation scans caller limits or one datagram at most", }, .obligations = &.{ .{ .key = "quic_connection_capacity", .role = .capacity_model }, .{ .key = "quic_connection_overload", .role = .overload }, .{ .key = "quic_connection_transitive", .role = .transitive_risk }, .{ .key = "quic_connection_work", .role = .work_bound }, .{ .key = "quic_connection_root", .role = .custom }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker }, }, }, }; pub fn init(bytes: @This().Storage, limits: StorageLimits) InitError!@This() { const capacity = try StorageCapacity.derive(limits); if (bytes.len < capacity.storage_bytes) return error.StorageTooShort; const owned = bytes[0..capacity.storage_bytes]; const sent_slice = typedSlice( quic.connection.SentRecord, owned, capacity.sent_offset, capacity.sent_count, ); const range_slice = typedSlice( quic.connection.Range, owned, capacity.ranges_offset, capacity.range_count, ); const stream_sent_slice = typedSlice( quic.connection.stream.SentRange, owned, capacity.stream_sent_offset, limits.stream_sent_ranges, ); const stream_range_slice = typedSlice( quic.connection.stream.Range, owned, capacity.stream_range_offset, limits.stream_receive_ranges, ); const send_start = capacity.stream_send_offset; const receive_start = capacity.stream_receive_offset; const tls_bytes: []align(quic.tls.Storage.storage_alignment) u8 = @alignCast( owned[capacity.tls_offset..][0..capacity.tls_bytes], ); var result = @This(){ .phase = .initialization, .capacity = capacity, .storage = owned, .sent_records = sent_slice, .received_ranges = range_slice, .tls_storage = try quic.tls.Storage.init(tls_bytes, .{ .max_message = limits.tls_message_max, }), .crypto_bytes = undefined, .crypto_present = undefined, .packet = owned[capacity.packet_offset..][0..limits.datagram_bytes], .scratch = owned[capacity.scratch_offset..][0..limits.datagram_bytes], .reason = owned[capacity.reason_offset..][0..limits.datagram_bytes], .key_bytes = owned[capacity.keys_offset..][0..capacity.keys_bytes], .transport = owned[capacity.transport_offset..][0..capacity.transport_bytes], .stream_sent_ranges = stream_sent_slice, .stream_receive_ranges = stream_range_slice, .stream_send_bytes = owned[send_start..][0..limits.stream_send_bytes], .stream_receive_bytes = owned[receive_start..][0..limits.stream_receive_bytes], }; result.partitionCrypto(); return result; } pub fn activate(self: *@This()) void { std.debug.assert(self.phase == .initialization); self.phase = .steady; } pub fn deinit(self: *@This()) @This().Storage { std.debug.assert(self.phase == .steady); std.debug.assert(self.nested_released); std.crypto.secureZero(u8, self.storage); self.phase = .teardown; const bytes = self.storage; self.* = undefined; return bytes; } pub fn records(self: *@This(), index: usize) []quic.connection.SentRecord { std.debug.assert(index < 3); const width = self.capacity.sent_count / 3; return self.sent_records[index * width ..][0..width]; } pub fn ranges(self: *@This(), index: usize) []quic.connection.Range { std.debug.assert(index < 3); const width = self.capacity.range_count / 3; return self.received_ranges[index * width ..][0..width]; } pub fn keys(self: *@This(), slot: KeySlot) quic.crypto.Keys.Storage { const width = quic.crypto.Keys.storage_bytes_max; const start = @as(usize, @backingInt(slot)) * width; return @alignCast(self.key_bytes[start..][0..width]); } fn partitionCrypto(self: *@This()) void { const width = self.capacity.crypto_lane_bytes; for (0..3) |index| { const data_start = self.capacity.crypto_offset + index * width; const present_start = self.capacity.crypto_offset + (3 + index) * width; self.crypto_bytes[index] = self.storage[data_start..][0..width]; self.crypto_present[index] = self.storage[present_start..][0..width]; } }};Source: lib/quic/src/connection/machine.zig:124
pub const InitError = quic.connection.Storage.Capacity.DeriveError || quic.connection.KeyState.InstallError || quic.tls.InitError || quic.connection.parameters.Error || error{ InvalidConfig, RandomFailed, StorageMismatch };Source: lib/quic/src/connection/machine.zig:17
/// Maximum byte length of a close reason, published so a caller can size its reason string against/// this bound because `close` answers `ReasonTooLong` for anything longer. The figure defines how/// far a close reason may run while its packets still fit a 1200-byte datagram with 20-byte/// connection IDs at both ends. The figure is the lesser of what one Initial close packet leaves/// free and what a Handshake and a 1-RTT close packet leave free between them, because a 1-RTT/// close raised before the handshake is confirmed travels behind a Handshake copy.pub const close_reason_bytes_max: usize = closeReasonCapacity();Source: lib/quic/src/connection/machine.zig:9
/// Three-second span that a closing or draining connection stands before it reports itself closed,/// so a caller waiting on `nextTimeout` knows how long the connection stays before reporting/// closed. Both `close` and the path that fails a connection from inside set that deadline three/// seconds past the time the caller gave. The span is a constant, so it rests on no measurement of/// the path.pub const closing_period_ns: u64 = 3 * std.time.ns_per_s;Source: lib/quic/src/connection/sent.zig:231
pub fn acknowledges(value: quic.frame.Ack, packet_number: u62) bool { var largest = value.largest; var smallest = largest - value.first_range; if (packet_number >= smallest and packet_number <= largest) return true; var iterator = value.iterator(); for (0..quic.frame.ack_ranges_max) |index| { if (index >= value.range_count) break; const range = (iterator.next() catch return false) orelse return false; largest = smallest - range.gap - 2; smallest = largest - range.length; if (packet_number >= smallest and packet_number <= largest) return true; } return false;}Source: lib/quic/src/connection/root.zig
const ack = @import("ack.zig");const crypto = @import("crypto.zig");const keys = @import("keys.zig");const machine = @import("machine.zig");const model = @import("model.zig");const params = @import("params.zig");const sent = @import("sent.zig");const space = @import("space.zig");const storage = @import("storage.zig");pub const assemble = @import("assemble.zig");pub const recovery = @import("recovery/root.zig");pub const stream = @import("stream/root.zig");pub const Limits = model.Limits;pub const Config = model.Config;pub const Status = model.Status;pub const CloseReason = model.CloseReason;pub const DropStats = model.DropStats;pub const Stats = model.Stats;pub const StreamFrameStats = model.StreamFrameStats;pub const StreamStats = model.StreamStats;pub const TransportError = model.TransportError;pub const parameters = params;pub const Acknowledged = sent.Acknowledged;pub const Lost = sent.Lost;pub const CryptoSummary = sent.CryptoSummary;pub const FrameSummary = sent.FrameSummary;pub const StreamSummary = sent.StreamSummary;pub const SentRecord = sent.SentRecord;pub const Sent = sent.Sent;pub const acknowledges = sent.acknowledges;pub const Range = ack.Range;pub const Ranges = ack.Ranges;pub const Reassembler = crypto.Reassembler;pub const KeyState = keys.State;pub const SpaceKind = space.Kind;pub const NumberEncoding = space.NumberEncoding;pub const Space = space.Space;pub const Capacity = storage.Capacity;pub const KeySlot = storage.KeySlot;pub const Storage = storage.Storage;pub const Connection = machine.Connection;pub const InitError = machine.InitError;pub const OpenError = machine.OpenError;pub const closing_period_ns = machine.closing_period_ns;pub const close_reason_bytes_max = machine.close_reason_bytes_max;Source: lib/quic/src/root.zig:44
pub const connection = @import("connection/root.zig");Complete call list for connection.Connection.send
9 direct calls.
lib.quic.src.connection.machine.Connection.advanceTime[method] — private source atlib/quic/src/connection/machine.zig:2200in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.Connection.armProbeTimeout[method] — private source atlib/quic/src/connection/machine.zig:946in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.Connection.recordDatagramSent[method] — private source atlib/quic/src/connection/machine.zig:2172in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.Connection.sendClosing[method] — private source atlib/quic/src/connection/machine.zig:1420in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.Connection.sendLimit[method] — private source atlib/quic/src/connection/machine.zig:2156in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.Connection.sendSpace[method] — private source atlib/quic/src/connection/machine.zig:1482in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.Connection.space[method] — private source atlib/quic/src/connection/machine.zig:2151in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.packetMinimum[function] — private source atlib/quic/src/connection/machine.zig:2356in nearest public ownerlib.quic.src.connection.machinelib.quic.src.connection.machine.spaceIndex[function] — private source atlib/quic/src/connection/machine.zig:2429in nearest public ownerlib.quic.src.connection.machine
Complete caller list for connection.SentRecord.empty
7 direct callers.
tiny.quic.connection.Sent.clear[method] atlib/quic/src/connection/sent.zig:162tiny.quic.connection.Sent.init[function] atlib/quic/src/connection/sent.zig:116lib.quic.src.connection.sent.Sent.retirePrefix[method] — private source atlib/quic/src/connection/sent.zig:210in nearest public ownerlib.quic.src.connection.sentlib.quic.src.connection.sent.test_RFC_9000_section_13.3_acknowledgment_reports_each_record's_frames_once[function] — test source atlib/quic/src/connection/sent.zig:263in nearest public ownerlib.quic.src.connection.sentlib.quic.src.connection.sent.test_RFC_9001_section_4.9.1_key_discard_drops_sent_records[function] — test source atlib/quic/src/connection/sent.zig:318in nearest public ownerlib.quic.src.connection.sentlib.quic.src.connection.sent.test_RFC_9002_section_2_bytes_in_flight_follow_the_sent_records[function] — test source atlib/quic/src/connection/sent.zig:292in nearest public ownerlib.quic.src.connection.sentlib.quic.src.connection.sent.test_sent_record_maximum_and_maximum_plus_one[function] — test source atlib/quic/src/connection/sent.zig:246in nearest public ownerlib.quic.src.connection.sent
Audit
| Definitions | 123 |
|---|---|
| Public names | 123 |
| Members | 267 |
| Version | 26.7.0 |
| Revision | daab053ee433 |