tiny.quic.connection.assemble
Defined in connection.
API (6)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
sampled_bytes_min: Four bytes is the floor that a packet number and the payload behind it have to cover between them, so that header protection finds a whole sample to read.
Source
Source: lib/quic/src/connection/assemble.zig
zig
const std = @import("std");const quic = @import("../root.zig");const KeyStorage = [quic.crypto.Keys.storage_bytes_max]u8;pub const PacketType = enum { initial, handshake, one_rtt };pub const Spec = struct { packet_type: PacketType, destination: quic.packet.ConnectionId, source: quic.packet.ConnectionId, number: quic.connection.NumberEncoding, key_phase: bool = false, minimum_bytes: u16 = 0,};pub const Built = struct { length: u16, packet_number_offset: usize, packet_number_length: u3, payload_length: u16,};pub const Error = quic.packet.EncodeError || quic.crypto.packet.SealError || error{ NoSpace, PayloadTooLarge };/// Four bytes is the floor that a packet number and the payload behind it have to cover between/// them, so that header protection finds a whole sample to read. `packet` grows a short payload/// until the two together reach that floor, so a caller never has to pad by hand. One PING byte/// carried under a one-byte packet number therefore reaches the wire with three payload bytes.pub const sampled_bytes_min: usize = 4;pub fn packet( keys: *quic.crypto.Keys, spec: Spec, payload: []const u8, out: []u8,) Error!Built { const pn_len: u3 = @intCast(spec.number.bits / 8); std.debug.assert(pn_len >= 1); std.debug.assert(pn_len <= sampled_bytes_min); const payload_floor = @max(payload.len, sampled_bytes_min - pn_len); var payload_len = payload_floor; for (0..4) |_| { const total = try packetLength(spec, pn_len, payload_len); if (total == spec.minimum_bytes or spec.minimum_bytes == 0) break; if (total < spec.minimum_bytes) { payload_len = std.math.add(usize, payload_len, spec.minimum_bytes - total) catch return error.PayloadTooLarge; } else { const excess = total - spec.minimum_bytes; if (excess > payload_len - payload_floor) break; payload_len -= excess; } } const total = try packetLength(spec, pn_len, payload_len); if (total > out.len) return error.NoSpace; if (payload_len > std.math.maxInt(u16)) return error.PayloadTooLarge; var output = quic.cursor.Write.init(out[0..total]); const pn_offset = try writeHeader(spec, pn_len, payload_len, &output); try writePacketNumber(spec.number.packet_number, pn_len, &output); try output.put(payload); for (payload.len..payload_len) |_| try output.byte(0); const tag_end = std.math.add(usize, output.index, quic.crypto.packet.tag_bytes) catch return error.PayloadTooLarge; if (tag_end != total) return error.PayloadTooLarge; @memset(out[output.index..tag_end], 0); try quic.crypto.packet.seal( keys, spec.number.packet_number, out[0..total], pn_offset, pn_len, @intCast(payload_len), ); return .{ .length = @intCast(total), .packet_number_offset = pn_offset, .packet_number_length = pn_len, .payload_length = @intCast(payload_len), };}fn packetLength(spec: Spec, pn_len: u3, payload_len: usize) Error!usize { const protected_len = std.math.add( usize, payload_len, quic.crypto.packet.tag_bytes, ) catch return error.PayloadTooLarge; const body_len = std.math.add(usize, pn_len, protected_len) catch return error.PayloadTooLarge; const header_len = switch (spec.packet_type) { .initial, .handshake => try longHeaderLength(spec, body_len), .one_rtt => 1 + spec.destination.length, }; return std.math.add(usize, header_len, body_len) catch error.PayloadTooLarge;}fn longHeaderLength(spec: Spec, body_len: usize) Error!usize { if (body_len > std.math.maxInt(u62)) return error.PayloadTooLarge; const destination: usize = spec.destination.length; const source: usize = spec.source.length; const common = 1 + 4 + 1 + destination + 1 + source; const token: usize = if (spec.packet_type == .initial) 1 else 0; return common + token + quic.varint.encodedLength(@intCast(body_len));}fn writeHeader( spec: Spec, pn_len: u3, payload_len: usize, output: *quic.cursor.Write,) Error!usize { if (spec.packet_type == .one_rtt) { const first: u8 = 0x40 | (@as(u8, @intFromBool(spec.key_phase)) << 2) | (pn_len - 1); try quic.packet.encodeShort(.{ .first = first, .destination = spec.destination, .packet_number_offset = 0, }, output); return output.index; } const protected_len = payload_len + quic.crypto.packet.tag_bytes; const length: u62 = @intCast(@as(usize, pn_len) + protected_len); const first: u8 = switch (spec.packet_type) { .initial => 0xc0 | @as(u8, pn_len - 1), .handshake => 0xe0 | @as(u8, pn_len - 1), .one_rtt => unreachable, }; const common = quic.packet.Common{ .first = first, .version = 1, .destination = spec.destination, .source = spec.source, }; const value: quic.packet.Long = switch (spec.packet_type) { .initial => .{ .initial = .{ .common = common, .token = &.{}, .length = length, .packet_number_offset = 0, } }, .handshake => .{ .handshake = .{ .common = common, .length = length, .packet_number_offset = 0, } }, .one_rtt => unreachable, }; try quic.packet.encodeLong(value, output); return output.index;}fn writePacketNumber(value: u62, length: u3, output: *quic.cursor.Write) !void { var bytes: [8]u8 = undefined; std.mem.writeInt(u64, &bytes, value, .big); try output.put(bytes[bytes.len - length ..]);}test "RFC 9000 section 12.2 assembled Initial layout decodes through packet codec" { const secret: quic.crypto.Secret = @splat(0x42); var key_bytes: KeyStorage align(quic.crypto.Keys.storage_alignment) = undefined; var keys = try quic.crypto.Keys.derive(&key_bytes, .aes_128_gcm_sha256, secret); defer _ = keys.deinit(); const cid = try quic.packet.ConnectionId.init("12345678"); var out: [1200]u8 = undefined; const built = try packet(&keys, .{ .packet_type = .initial, .destination = cid, .source = cid, .number = .{ .packet_number = 0, .bits = 32, .value = 0 }, .minimum_bytes = 1200, }, &.{0x01}, &out); try std.testing.expectEqual(@as(u16, 1200), built.length); const decoded = try quic.packet.decodeLong(out[0..built.length]); try std.testing.expectEqual(built.packet_number_offset, decoded.initial.packet_number_offset); try std.testing.expectEqual( @as(u62, @intCast(1200 - built.packet_number_offset)), decoded.initial.length, );}test "RFC 9001 section 5.4.2 one-byte PING pads to a full header protection sample" { const secret: quic.crypto.Secret = @splat(0x42); var key_bytes: KeyStorage align(quic.crypto.Keys.storage_alignment) = undefined; var keys = try quic.crypto.Keys.derive(&key_bytes, .aes_128_gcm_sha256, secret); defer _ = keys.deinit(); const cid = try quic.packet.ConnectionId.init("12345678"); var out: [64]u8 = undefined; const built = try packet(&keys, .{ .packet_type = .one_rtt, .destination = cid, .source = cid, .number = .{ .packet_number = 7, .bits = 8, .value = 7 }, }, &.{0x01}, &out); try std.testing.expectEqual(@as(u16, sampled_bytes_min - 1), built.payload_length); try std.testing.expectEqual(@as(u16, 1 + 8 + sampled_bytes_min + 16), built.length);}Source: lib/quic/src/connection/root.zig:11
zig
pub const assemble = @import("assemble.zig");Audit
| Definitions | 7 |
|---|---|
| Public names | 7 |
| Members | 13 |
| Version | 26.7.0 |
| Revision | daab053ee433 |