tiny.quic.transport
Defined in tiny.quic.
API (7)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: lib/quic/src/root.zig:51
zig
pub const transport = @import("transport.zig");Source: lib/quic/src/transport.zig
zig
const std = @import("std");const quic = @import("root.zig");const cursor = quic.cursor;const varint = quic.varint;pub const parameter_count_max: u8 = 64;pub const PreferredAddress = struct { ipv4: [4]u8, ipv4_port: u16, ipv6: [16]u8, ipv6_port: u16, connection_id: quic.packet.ConnectionId, reset_token: [16]u8,};pub const Parameters = struct { original_destination_connection_id: ?quic.packet.ConnectionId = null, max_idle_timeout: u62 = 0, stateless_reset_token: ?[16]u8 = null, max_udp_payload_size: u62 = 65_527, initial_max_data: u62 = 0, initial_max_stream_data_bidi_local: u62 = 0, initial_max_stream_data_bidi_remote: u62 = 0, initial_max_stream_data_uni: u62 = 0, initial_max_streams_bidi: u62 = 0, initial_max_streams_uni: u62 = 0, ack_delay_exponent: u5 = 3, max_ack_delay: u14 = 25, disable_active_migration: bool = false, preferred_address: ?PreferredAddress = null, active_connection_id_limit: u62 = 2, initial_source_connection_id: ?quic.packet.ConnectionId = null, retry_source_connection_id: ?quic.packet.ConnectionId = null, max_datagram_frame_size: u62 = 0,};const DecodeSpecific = error{ DuplicateParameter, InvalidAckDelayExponent, InvalidActiveConnectionIdLimit, InvalidConnectionId, InvalidDisableActiveMigration, InvalidInteger, InvalidLength, InvalidMaxAckDelay, InvalidMaxUdpPayloadSize, InvalidPreferredAddress, InvalidStatelessResetToken, InvalidStreamLimit, TooManyParameters,};const EncodeSpecific = error{ InvalidAckDelayExponent, InvalidActiveConnectionIdLimit, InvalidLength, InvalidMaxAckDelay, InvalidMaxUdpPayloadSize, InvalidPreferredAddress, InvalidStreamLimit,};pub const DecodeError = cursor.ReadError || varint.DecodeError || DecodeSpecific;pub const EncodeError = cursor.WriteError || varint.EncodeError || EncodeSpecific;fn valueLength(value: u62) DecodeError!usize { if (comptime @bitSizeOf(usize) < 62) { if (value > std.math.maxInt(usize)) return error.InvalidLength; } return @intCast(value);}fn sliceLength(length: usize) EncodeError!u62 { if (@as(u64, length) > std.math.maxInt(u62)) return error.InvalidLength; return @intCast(length);}fn readInteger(bytes: []const u8) DecodeError!u62 { var input = cursor.Read.init(bytes); const decoded = varint.read(&input) catch return error.InvalidInteger; if (input.remaining() != 0) return error.InvalidInteger; return decoded.value;}fn readConnectionId(bytes: []const u8) DecodeError!quic.packet.ConnectionId { return quic.packet.ConnectionId.init(bytes) catch error.InvalidConnectionId;}fn readPreferredAddress(bytes: []const u8) DecodeError!PreferredAddress { return parsePreferredAddress(bytes) catch error.InvalidPreferredAddress;}fn parsePreferredAddress(bytes: []const u8) DecodeError!PreferredAddress { var input = cursor.Read.init(bytes); var ipv4: [4]u8 = undefined; @memcpy(&ipv4, try input.take(4)); const ipv4_port = try input.int(u16); var ipv6: [16]u8 = undefined; @memcpy(&ipv6, try input.take(16)); const ipv6_port = try input.int(u16); const connection_id_length = try input.byte(); if (connection_id_length == 0) return error.InvalidPreferredAddress; if (connection_id_length > quic.packet.connection_id_bytes_max) { return error.InvalidPreferredAddress; } const connection_id = quic.packet.ConnectionId.init( try input.take(connection_id_length), ) catch return error.InvalidPreferredAddress; var reset_token: [16]u8 = undefined; @memcpy(&reset_token, try input.take(16)); if (input.remaining() != 0) return error.InvalidPreferredAddress; return .{ .ipv4 = ipv4, .ipv4_port = ipv4_port, .ipv6 = ipv6, .ipv6_port = ipv6_port, .connection_id = connection_id, .reset_token = reset_token, };}fn assignInteger(parameters: *Parameters, id: u62, value: u62) DecodeError!void { switch (id) { 0x01 => parameters.max_idle_timeout = value, 0x03 => { if (value < 1200 or value > 65_527) return error.InvalidMaxUdpPayloadSize; parameters.max_udp_payload_size = value; }, 0x04 => parameters.initial_max_data = value, 0x05 => parameters.initial_max_stream_data_bidi_local = value, 0x06 => parameters.initial_max_stream_data_bidi_remote = value, 0x07 => parameters.initial_max_stream_data_uni = value, 0x08 => { if (value > @as(u62, 1) << 60) return error.InvalidStreamLimit; parameters.initial_max_streams_bidi = value; }, 0x09 => { if (value > @as(u62, 1) << 60) return error.InvalidStreamLimit; parameters.initial_max_streams_uni = value; }, 0x0a => { if (value > 20) return error.InvalidAckDelayExponent; parameters.ack_delay_exponent = @intCast(value); }, 0x0b => { if (value >= @as(u62, 1) << 14) return error.InvalidMaxAckDelay; parameters.max_ack_delay = @intCast(value); }, 0x0e => { if (value < 2) return error.InvalidActiveConnectionIdLimit; parameters.active_connection_id_limit = value; }, 0x20 => parameters.max_datagram_frame_size = value, else => unreachable, }}fn assignParameter( parameters: *Parameters, id: u62, value: []const u8,) DecodeError!void { switch (id) { 0x00 => parameters.original_destination_connection_id = try readConnectionId(value), 0x01, 0x03...0x0b, 0x0e, 0x20 => { try assignInteger(parameters, id, try readInteger(value)); }, 0x02 => { if (value.len != 16) return error.InvalidStatelessResetToken; var token: [16]u8 = undefined; @memcpy(&token, value); parameters.stateless_reset_token = token; }, 0x0c => { if (value.len != 0) return error.InvalidDisableActiveMigration; parameters.disable_active_migration = true; }, 0x0d => parameters.preferred_address = try readPreferredAddress(value), 0x0f => parameters.initial_source_connection_id = try readConnectionId(value), 0x10 => parameters.retry_source_connection_id = try readConnectionId(value), else => {}, }}fn duplicate(seen: *const [parameter_count_max]u62, count: u8, id: u62) bool { for (0..parameter_count_max) |index| { if (index >= count) break; if (seen[index] == id) return true; } return false;}pub fn decode(bytes: []const u8) DecodeError!Parameters { var input = cursor.Read.init(bytes); var seen: [parameter_count_max]u62 = undefined; var count: u8 = 0; var parameters = Parameters{}; for (0..parameter_count_max + 1) |_| { if (input.remaining() == 0) return parameters; if (count == parameter_count_max) return error.TooManyParameters; const id = (try varint.read(&input)).value; const length = (try varint.read(&input)).value; const value = try input.take(try valueLength(length)); if (duplicate(&seen, count, id)) return error.DuplicateParameter; seen[count] = id; count += 1; try assignParameter(¶meters, id, value); } unreachable;}fn writeTuple(id: u62, value: []const u8, output: *cursor.Write) EncodeError!void { _ = try varint.write(id, output); _ = try varint.write(try sliceLength(value.len), output); try output.put(value);}fn writeInteger(id: u62, value: u62, output: *cursor.Write) EncodeError!void { var bytes: [8]u8 = undefined; const length = try varint.encode(value, &bytes); try writeTuple(id, bytes[0..length], output);}fn writePreferredAddress( value: PreferredAddress, output: *cursor.Write,) EncodeError!void { if (value.connection_id.length == 0) return error.InvalidPreferredAddress; var bytes: [61]u8 = undefined; var payload = cursor.Write.init(&bytes); try payload.put(&value.ipv4); try payload.int(u16, value.ipv4_port); try payload.put(&value.ipv6); try payload.int(u16, value.ipv6_port); try payload.byte(value.connection_id.length); try payload.put(value.connection_id.slice()); try payload.put(&value.reset_token); try writeTuple(0x0d, payload.written(), output);}fn validate(parameters: Parameters) EncodeError!void { if (parameters.max_udp_payload_size < 1200 or parameters.max_udp_payload_size > 65_527) { return error.InvalidMaxUdpPayloadSize; } if (parameters.ack_delay_exponent > 20) return error.InvalidAckDelayExponent; if (parameters.active_connection_id_limit < 2) { return error.InvalidActiveConnectionIdLimit; } if (parameters.initial_max_streams_bidi > @as(u62, 1) << 60) { return error.InvalidStreamLimit; } if (parameters.initial_max_streams_uni > @as(u62, 1) << 60) { return error.InvalidStreamLimit; }}pub fn encode(parameters: Parameters, output: *cursor.Write) EncodeError!void { try validate(parameters); if (parameters.original_destination_connection_id) |id| { try writeTuple(0x00, id.slice(), output); } if (parameters.max_idle_timeout != 0) { try writeInteger(0x01, parameters.max_idle_timeout, output); } if (parameters.stateless_reset_token) |token| try writeTuple(0x02, &token, output); if (parameters.max_udp_payload_size != 65_527) { try writeInteger(0x03, parameters.max_udp_payload_size, output); } try writeFlowControl(parameters, output); if (parameters.ack_delay_exponent != 3) { try writeInteger(0x0a, parameters.ack_delay_exponent, output); } if (parameters.max_ack_delay != 25) { try writeInteger(0x0b, parameters.max_ack_delay, output); } if (parameters.disable_active_migration) try writeTuple(0x0c, &.{}, output); if (parameters.preferred_address) |address| try writePreferredAddress(address, output); if (parameters.active_connection_id_limit != 2) { try writeInteger(0x0e, parameters.active_connection_id_limit, output); } if (parameters.initial_source_connection_id) |id| { try writeTuple(0x0f, id.slice(), output); } if (parameters.retry_source_connection_id) |id| { try writeTuple(0x10, id.slice(), output); } if (parameters.max_datagram_frame_size != 0) { try writeInteger(0x20, parameters.max_datagram_frame_size, output); }}fn writeFlowControl(parameters: Parameters, output: *cursor.Write) EncodeError!void { const values = [_]u62{ parameters.initial_max_data, parameters.initial_max_stream_data_bidi_local, parameters.initial_max_stream_data_bidi_remote, parameters.initial_max_stream_data_uni, parameters.initial_max_streams_bidi, parameters.initial_max_streams_uni, }; for (values, 0x04..) |value, id| { if (value != 0) try writeInteger(@intCast(id), value, output); }}test "RFC 9000 section 18.2 and RFC 9221 section 3 parameters round trip" { const source = try quic.packet.ConnectionId.init(&.{ 1, 2, 3, 4 }); const original = try quic.packet.ConnectionId.init(&.{ 5, 6, 7 }); const preferred_id = try quic.packet.ConnectionId.init(&.{ 8, 9 }); const parameters = Parameters{ .original_destination_connection_id = original, .max_idle_timeout = 30_000, .stateless_reset_token = @splat(0x11), .max_udp_payload_size = 1400, .initial_max_data = 1000, .initial_max_stream_data_bidi_local = 2000, .initial_max_stream_data_bidi_remote = 3000, .initial_max_stream_data_uni = 4000, .initial_max_streams_bidi = 10, .initial_max_streams_uni = 11, .ack_delay_exponent = 4, .max_ack_delay = 26, .disable_active_migration = true, .preferred_address = .{ .ipv4 = .{ 192, 0, 2, 1 }, .ipv4_port = 443, .ipv6 = .{ 0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .ipv6_port = 8443, .connection_id = preferred_id, .reset_token = @splat(0x22), }, .active_connection_id_limit = 4, .initial_source_connection_id = source, .retry_source_connection_id = original, .max_datagram_frame_size = 1200, }; var bytes: [512]u8 = undefined; var output = cursor.Write.init(&bytes); try encode(parameters, &output); const decoded = try decode(output.written()); try std.testing.expectEqualDeep(parameters, decoded);}test "RFC 9000 section 18.2 rejects invalid parameter values" { var long_connection_id: [23]u8 = @splat(0xaa); long_connection_id[0] = 0x0f; long_connection_id[1] = 21; const Case = struct { bytes: []const u8, expected: DecodeError }; const cases = [_]Case{ .{ .bytes = &.{ 0x03, 0x01, 0x25 }, .expected = error.InvalidMaxUdpPayloadSize, }, .{ .bytes = &.{ 0x03, 0x04, 0x80, 0x00, 0xff, 0xf8 }, .expected = error.InvalidMaxUdpPayloadSize, }, .{ .bytes = &.{ 0x0a, 0x01, 21 }, .expected = error.InvalidAckDelayExponent, }, .{ .bytes = &.{ 0x0b, 0x04, 0x80, 0x00, 0x40, 0x00 }, .expected = error.InvalidMaxAckDelay, }, .{ .bytes = &.{ 0x0e, 0x01, 1 }, .expected = error.InvalidActiveConnectionIdLimit, }, .{ .bytes = &.{ 0x01, 0x02, 0x01, 0x00 }, .expected = error.InvalidInteger, }, .{ .bytes = &.{ 0x01, 0x01, 0x40 }, .expected = error.InvalidInteger, }, .{ .bytes = &long_connection_id, .expected = error.InvalidConnectionId, }, .{ .bytes = &.{ 0x0d, 0x01, 0x00 }, .expected = error.InvalidPreferredAddress, }, }; for (cases) |case| try std.testing.expectError(case.expected, decode(case.bytes));}test "RFC 9000 section 18.2 max UDP payload size upper bound" { const decoded = try decode(&.{ 0x03, 0x04, 0x80, 0x00, 0xff, 0xf7 }); try std.testing.expectEqual(@as(u62, 65_527), decoded.max_udp_payload_size); var bytes: [16]u8 = undefined; var output = cursor.Write.init(&bytes); try encode(.{ .max_udp_payload_size = 65_527 }, &output); try std.testing.expectEqualDeep(Parameters{}, try decode(output.written())); output = cursor.Write.init(&bytes); try std.testing.expectError( error.InvalidMaxUdpPayloadSize, encode(.{ .max_udp_payload_size = 65_528 }, &output), );}test "RFC 9000 sections 7.4 and 18 reject duplicate parameters" { try std.testing.expectError( error.DuplicateParameter, decode(&.{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x02 }), );}test "RFC 9000 section 18.1 skips reserved and unknown parameters" { const decoded = try decode(&.{ 27, 1, 0xaa, 0x21, 2, 0xbb, 0xcc }); try std.testing.expectEqualDeep(Parameters{}, decoded);}test "RFC 9000 section 18.2 parameter count maximum and maximum plus one" { var bytes: [parameter_count_max * 3]u8 = undefined; for (0..parameter_count_max) |index| { bytes[index * 3] = 0x40; bytes[index * 3 + 1] = @intCast(128 + index); bytes[index * 3 + 2] = 0; } _ = try decode(&bytes); var plus_one: [bytes.len + 3]u8 = undefined; @memcpy(plus_one[0..bytes.len], &bytes); plus_one[bytes.len] = 0x40; plus_one[bytes.len + 1] = 0xc0; plus_one[bytes.len + 2] = 0; try std.testing.expectError(error.TooManyParameters, decode(&plus_one));}Complete call list for transport.decode
7 direct calls.
tiny.quic.cursor.Read.init[function] atlib/quic/src/cursor.zig:10tiny.quic.cursor.Read.remaining[method] atlib/quic/src/cursor.zig:14tiny.quic.cursor.Read.take[method] atlib/quic/src/cursor.zig:19lib.quic.src.transport.assignParameter[function] — private source atlib/quic/src/transport.zig:160in nearest public ownertiny.quic.transportlib.quic.src.transport.duplicate[function] — private source atlib/quic/src/transport.zig:187in nearest public ownertiny.quic.transportlib.quic.src.transport.valueLength[function] — private source atlib/quic/src/transport.zig:68in nearest public ownertiny.quic.transporttiny.quic.varint.read[function] atlib/quic/src/varint.zig:41
Audit
| Definitions | 8 |
|---|---|
| Public names | 8 |
| Members | 24 |
| Version | 26.7.0 |
| Revision | daab053ee433 |