lib/quic/src/packet/header.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const quic = @import("../root.zig");
3 const model = @import("model.zig");
4
5 const cursor = quic.cursor;
6 const varint = quic.varint;
7
8 const DecodeSpecific = error{
9 ConnectionIdTooLong,
10 InvalidHeader,
11 InvalidLength,
12 InvalidRetry,
13 InvalidVersionList,
14 UnsupportedVersion,
15 };
16
17 const EncodeSpecific = error{
18 InvalidHeader,
19 InvalidLength,
20 InvalidRetry,
21 InvalidVersionList,
22 UnsupportedVersion,
23 };
24
25 pub const DecodeError = cursor.ReadError || varint.DecodeError || DecodeSpecific;
26 pub const EncodeError = cursor.WriteError || varint.EncodeError || EncodeSpecific;
27
28 fn readConnectionId(input: *cursor.Read) DecodeError!model.ConnectionId {
29 const length = try input.byte();
30 if (length > model.connection_id_bytes_max) return error.ConnectionIdTooLong;
31 return model.ConnectionId.init(try input.take(length)) catch
32 return error.ConnectionIdTooLong;
33 }
34
35 fn readCommon(input: *cursor.Read, first: u8, version: u32) DecodeError!model.Common {
36 return .{
37 .first = first,
38 .version = version,
39 .destination = try readConnectionId(input),
40 .source = try readConnectionId(input),
41 };
42 }
43
44 fn valueLength(value: u62) DecodeError!usize {
45 if (value > std.math.maxInt(usize)) return error.InvalidLength;
46 return @intCast(value);
47 }
48
49 fn decodeVersionNegotiation(
50 input: *cursor.Read,
51 common: model.Common,
52 ) DecodeError!model.Long {
53 const versions = input.rest();
54 if (versions.len == 0) return error.InvalidVersionList;
55 if (versions.len % 4 != 0) return error.InvalidVersionList;
56 return .{ .version_negotiation = .{ .common = common, .versions = versions } };
57 }
58
59 fn decodeInitial(input: *cursor.Read, common: model.Common) DecodeError!model.Long {
60 const token_length = try varint.read(input);
61 const token = try input.take(try valueLength(token_length.value));
62 const length = (try varint.read(input)).value;
63 if (length == 0) return error.InvalidLength;
64 return .{ .initial = .{
65 .common = common,
66 .token = token,
67 .length = length,
68 .packet_number_offset = input.index,
69 } };
70 }
71
72 fn decodeProtected(input: *cursor.Read, common: model.Common) DecodeError!model.Protected {
73 const length = (try varint.read(input)).value;
74 if (length == 0) return error.InvalidLength;
75 return .{
76 .common = common,
77 .length = length,
78 .packet_number_offset = input.index,
79 };
80 }
81
82 fn decodeRetry(input: *cursor.Read, common: model.Common) DecodeError!model.Long {
83 const remainder = input.rest();
84 if (remainder.len <= 16) return error.InvalidRetry;
85 var tag: [16]u8 = undefined;
86 @memcpy(&tag, remainder[remainder.len - 16 ..]);
87 return .{ .retry = .{
88 .common = common,
89 .token = remainder[0 .. remainder.len - 16],
90 .integrity_tag = tag,
91 } };
92 }
93
94 pub fn decodeLong(bytes: []const u8) DecodeError!model.Long {
95 var input = cursor.Read.init(bytes);
96 const first = try input.byte();
97 if (first & 0x80 == 0) return error.InvalidHeader;
98 const version = try input.int(u32);
99 const common = try readCommon(&input, first, version);
100 if (version == 0) return decodeVersionNegotiation(&input, common);
101 if (version != 1) return error.UnsupportedVersion;
102 if (first & 0x40 == 0) return error.InvalidHeader;
103 return switch ((first >> 4) & 0x03) {
104 0 => decodeInitial(&input, common),
105 1 => .{ .zero_rtt = try decodeProtected(&input, common) },
106 2 => .{ .handshake = try decodeProtected(&input, common) },
107 3 => decodeRetry(&input, common),
108 else => unreachable,
109 };
110 }
111
112 fn writeCommon(common: model.Common, output: *cursor.Write) EncodeError!void {
113 if (common.first & 0x80 == 0) return error.InvalidHeader;
114 try output.byte(common.first);
115 try output.int(u32, common.version);
116 try output.byte(common.destination.length);
117 try output.put(common.destination.slice());
118 try output.byte(common.source.length);
119 try output.put(common.source.slice());
120 }
121
122 fn validateVersionOne(common: model.Common, expected_type: u2) EncodeError!void {
123 if (common.version != 1) return error.UnsupportedVersion;
124 if (common.first & 0xc0 != 0xc0) return error.InvalidHeader;
125 if ((common.first >> 4) & 0x03 != expected_type) return error.InvalidHeader;
126 }
127
128 fn writeProtected(
129 value: model.Protected,
130 expected_type: u2,
131 output: *cursor.Write,
132 ) EncodeError!void {
133 try validateVersionOne(value.common, expected_type);
134 if (value.length == 0) return error.InvalidLength;
135 try writeCommon(value.common, output);
136 _ = try varint.write(value.length, output);
137 }
138
139 pub fn encodeLong(value: model.Long, output: *cursor.Write) EncodeError!void {
140 switch (value) {
141 .version_negotiation => |packet| {
142 if (packet.common.version != 0) return error.InvalidHeader;
143 if (packet.versions.len == 0) return error.InvalidVersionList;
144 if (packet.versions.len % 4 != 0) return error.InvalidVersionList;
145 try writeCommon(packet.common, output);
146 try output.put(packet.versions);
147 },
148 .initial => |packet| {
149 try validateVersionOne(packet.common, 0);
150 if (packet.length == 0) return error.InvalidLength;
151 try writeCommon(packet.common, output);
152 _ = try varint.write(@intCast(packet.token.len), output);
153 try output.put(packet.token);
154 _ = try varint.write(packet.length, output);
155 },
156 .zero_rtt => |packet| try writeProtected(packet, 1, output),
157 .handshake => |packet| try writeProtected(packet, 2, output),
158 .retry => |packet| {
159 try validateVersionOne(packet.common, 3);
160 if (packet.token.len == 0) return error.InvalidRetry;
161 try writeCommon(packet.common, output);
162 try output.put(packet.token);
163 try output.put(&packet.integrity_tag);
164 },
165 }
166 }
167
168 pub fn decodeShort(bytes: []const u8, destination_length: u5) DecodeError!model.Short {
169 if (destination_length > model.connection_id_bytes_max) {
170 return error.ConnectionIdTooLong;
171 }
172 var input = cursor.Read.init(bytes);
173 const first = try input.byte();
174 if (first & 0x80 != 0) return error.InvalidHeader;
175 if (first & 0x40 == 0) return error.InvalidHeader;
176 const destination = model.ConnectionId.init(try input.take(destination_length)) catch
177 return error.ConnectionIdTooLong;
178 return .{
179 .first = first,
180 .destination = destination,
181 .packet_number_offset = input.index,
182 };
183 }
184
185 pub fn encodeShort(value: model.Short, output: *cursor.Write) EncodeError!void {
186 if (value.first & 0x80 != 0) return error.InvalidHeader;
187 if (value.first & 0x40 == 0) return error.InvalidHeader;
188 try output.byte(value.first);
189 try output.put(value.destination.slice());
190 }
191
192 pub fn packetNumberLength(first: u8) u3 {
193 return @as(u3, @intCast(first & 0x03)) + 1;
194 }