lib/quic/src/transport.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const quic = @import("root.zig");
3
4 const cursor = quic.cursor;
5 const varint = quic.varint;
6
7 pub const parameter_count_max: u8 = 64;
8
9 pub const PreferredAddress = struct {
10 ipv4: [4]u8,
11 ipv4_port: u16,
12 ipv6: [16]u8,
13 ipv6_port: u16,
14 connection_id: quic.packet.ConnectionId,
15 reset_token: [16]u8,
16 };
17
18 pub const Parameters = struct {
19 original_destination_connection_id: ?quic.packet.ConnectionId = null,
20 max_idle_timeout: u62 = 0,
21 stateless_reset_token: ?[16]u8 = null,
22 max_udp_payload_size: u62 = 65_527,
23 initial_max_data: u62 = 0,
24 initial_max_stream_data_bidi_local: u62 = 0,
25 initial_max_stream_data_bidi_remote: u62 = 0,
26 initial_max_stream_data_uni: u62 = 0,
27 initial_max_streams_bidi: u62 = 0,
28 initial_max_streams_uni: u62 = 0,
29 ack_delay_exponent: u5 = 3,
30 max_ack_delay: u14 = 25,
31 disable_active_migration: bool = false,
32 preferred_address: ?PreferredAddress = null,
33 active_connection_id_limit: u62 = 2,
34 initial_source_connection_id: ?quic.packet.ConnectionId = null,
35 retry_source_connection_id: ?quic.packet.ConnectionId = null,
36 max_datagram_frame_size: u62 = 0,
37 };
38
39 const DecodeSpecific = error{
40 DuplicateParameter,
41 InvalidAckDelayExponent,
42 InvalidActiveConnectionIdLimit,
43 InvalidConnectionId,
44 InvalidDisableActiveMigration,
45 InvalidInteger,
46 InvalidLength,
47 InvalidMaxAckDelay,
48 InvalidMaxUdpPayloadSize,
49 InvalidPreferredAddress,
50 InvalidStatelessResetToken,
51 InvalidStreamLimit,
52 TooManyParameters,
53 };
54
55 const EncodeSpecific = error{
56 InvalidAckDelayExponent,
57 InvalidActiveConnectionIdLimit,
58 InvalidLength,
59 InvalidMaxAckDelay,
60 InvalidMaxUdpPayloadSize,
61 InvalidPreferredAddress,
62 InvalidStreamLimit,
63 };
64
65 pub const DecodeError = cursor.ReadError || varint.DecodeError || DecodeSpecific;
66 pub const EncodeError = cursor.WriteError || varint.EncodeError || EncodeSpecific;
67
68 fn valueLength(value: u62) DecodeError!usize {
69 if (comptime @bitSizeOf(usize) < 62) {
70 if (value > std.math.maxInt(usize)) return error.InvalidLength;
71 }
72 return @intCast(value);
73 }
74
75 fn sliceLength(length: usize) EncodeError!u62 {
76 if (@as(u64, length) > std.math.maxInt(u62)) return error.InvalidLength;
77 return @intCast(length);
78 }
79
80 fn readInteger(bytes: []const u8) DecodeError!u62 {
81 var input = cursor.Read.init(bytes);
82 const decoded = varint.read(&input) catch return error.InvalidInteger;
83 if (input.remaining() != 0) return error.InvalidInteger;
84 return decoded.value;
85 }
86
87 fn readConnectionId(bytes: []const u8) DecodeError!quic.packet.ConnectionId {
88 return quic.packet.ConnectionId.init(bytes) catch error.InvalidConnectionId;
89 }
90
91 fn readPreferredAddress(bytes: []const u8) DecodeError!PreferredAddress {
92 return parsePreferredAddress(bytes) catch error.InvalidPreferredAddress;
93 }
94
95 fn parsePreferredAddress(bytes: []const u8) DecodeError!PreferredAddress {
96 var input = cursor.Read.init(bytes);
97 var ipv4: [4]u8 = undefined;
98 @memcpy(&ipv4, try input.take(4));
99 const ipv4_port = try input.int(u16);
100 var ipv6: [16]u8 = undefined;
101 @memcpy(&ipv6, try input.take(16));
102 const ipv6_port = try input.int(u16);
103 const connection_id_length = try input.byte();
104 if (connection_id_length == 0) return error.InvalidPreferredAddress;
105 if (connection_id_length > quic.packet.connection_id_bytes_max) {
106 return error.InvalidPreferredAddress;
107 }
108 const connection_id = quic.packet.ConnectionId.init(
109 try input.take(connection_id_length),
110 ) catch return error.InvalidPreferredAddress;
111 var reset_token: [16]u8 = undefined;
112 @memcpy(&reset_token, try input.take(16));
113 if (input.remaining() != 0) return error.InvalidPreferredAddress;
114 return .{
115 .ipv4 = ipv4,
116 .ipv4_port = ipv4_port,
117 .ipv6 = ipv6,
118 .ipv6_port = ipv6_port,
119 .connection_id = connection_id,
120 .reset_token = reset_token,
121 };
122 }
123
124 fn assignInteger(parameters: *Parameters, id: u62, value: u62) DecodeError!void {
125 switch (id) {
126 0x01 => parameters.max_idle_timeout = value,
127 0x03 => {
128 if (value < 1200 or value > 65_527) return error.InvalidMaxUdpPayloadSize;
129 parameters.max_udp_payload_size = value;
130 },
131 0x04 => parameters.initial_max_data = value,
132 0x05 => parameters.initial_max_stream_data_bidi_local = value,
133 0x06 => parameters.initial_max_stream_data_bidi_remote = value,
134 0x07 => parameters.initial_max_stream_data_uni = value,
135 0x08 => {
136 if (value > @as(u62, 1) << 60) return error.InvalidStreamLimit;
137 parameters.initial_max_streams_bidi = value;
138 },
139 0x09 => {
140 if (value > @as(u62, 1) << 60) return error.InvalidStreamLimit;
141 parameters.initial_max_streams_uni = value;
142 },
143 0x0a => {
144 if (value > 20) return error.InvalidAckDelayExponent;
145 parameters.ack_delay_exponent = @intCast(value);
146 },
147 0x0b => {
148 if (value >= @as(u62, 1) << 14) return error.InvalidMaxAckDelay;
149 parameters.max_ack_delay = @intCast(value);
150 },
151 0x0e => {
152 if (value < 2) return error.InvalidActiveConnectionIdLimit;
153 parameters.active_connection_id_limit = value;
154 },
155 0x20 => parameters.max_datagram_frame_size = value,
156 else => unreachable,
157 }
158 }
159
160 fn assignParameter(
161 parameters: *Parameters,
162 id: u62,
163 value: []const u8,
164 ) DecodeError!void {
165 switch (id) {
166 0x00 => parameters.original_destination_connection_id = try readConnectionId(value),
167 0x01, 0x03...0x0b, 0x0e, 0x20 => {
168 try assignInteger(parameters, id, try readInteger(value));
169 },
170 0x02 => {
171 if (value.len != 16) return error.InvalidStatelessResetToken;
172 var token: [16]u8 = undefined;
173 @memcpy(&token, value);
174 parameters.stateless_reset_token = token;
175 },
176 0x0c => {
177 if (value.len != 0) return error.InvalidDisableActiveMigration;
178 parameters.disable_active_migration = true;
179 },
180 0x0d => parameters.preferred_address = try readPreferredAddress(value),
181 0x0f => parameters.initial_source_connection_id = try readConnectionId(value),
182 0x10 => parameters.retry_source_connection_id = try readConnectionId(value),
183 else => {},
184 }
185 }
186
187 fn duplicate(seen: *const [parameter_count_max]u62, count: u8, id: u62) bool {
188 for (0..parameter_count_max) |index| {
189 if (index >= count) break;
190 if (seen[index] == id) return true;
191 }
192 return false;
193 }
194
195 pub fn decode(bytes: []const u8) DecodeError!Parameters {
196 var input = cursor.Read.init(bytes);
197 var seen: [parameter_count_max]u62 = undefined;
198 var count: u8 = 0;
199 var parameters = Parameters{};
200 for (0..parameter_count_max + 1) |_| {
201 if (input.remaining() == 0) return parameters;
202 if (count == parameter_count_max) return error.TooManyParameters;
203 const id = (try varint.read(&input)).value;
204 const length = (try varint.read(&input)).value;
205 const value = try input.take(try valueLength(length));
206 if (duplicate(&seen, count, id)) return error.DuplicateParameter;
207 seen[count] = id;
208 count += 1;
209 try assignParameter(¶meters, id, value);
210 }
211 unreachable;
212 }
213
214 fn writeTuple(id: u62, value: []const u8, output: *cursor.Write) EncodeError!void {
215 _ = try varint.write(id, output);
216 _ = try varint.write(try sliceLength(value.len), output);
217 try output.put(value);
218 }
219
220 fn writeInteger(id: u62, value: u62, output: *cursor.Write) EncodeError!void {
221 var bytes: [8]u8 = undefined;
222 const length = try varint.encode(value, &bytes);
223 try writeTuple(id, bytes[0..length], output);
224 }
225
226 fn writePreferredAddress(
227 value: PreferredAddress,
228 output: *cursor.Write,
229 ) EncodeError!void {
230 if (value.connection_id.length == 0) return error.InvalidPreferredAddress;
231 var bytes: [61]u8 = undefined;
232 var payload = cursor.Write.init(&bytes);
233 try payload.put(&value.ipv4);
234 try payload.int(u16, value.ipv4_port);
235 try payload.put(&value.ipv6);
236 try payload.int(u16, value.ipv6_port);
237 try payload.byte(value.connection_id.length);
238 try payload.put(value.connection_id.slice());
239 try payload.put(&value.reset_token);
240 try writeTuple(0x0d, payload.written(), output);
241 }
242
243 fn validate(parameters: Parameters) EncodeError!void {
244 if (parameters.max_udp_payload_size < 1200 or parameters.max_udp_payload_size > 65_527) {
245 return error.InvalidMaxUdpPayloadSize;
246 }
247 if (parameters.ack_delay_exponent > 20) return error.InvalidAckDelayExponent;
248 if (parameters.active_connection_id_limit < 2) {
249 return error.InvalidActiveConnectionIdLimit;
250 }
251 if (parameters.initial_max_streams_bidi > @as(u62, 1) << 60) {
252 return error.InvalidStreamLimit;
253 }
254 if (parameters.initial_max_streams_uni > @as(u62, 1) << 60) {
255 return error.InvalidStreamLimit;
256 }
257 }
258
259 pub fn encode(parameters: Parameters, output: *cursor.Write) EncodeError!void {
260 try validate(parameters);
261 if (parameters.original_destination_connection_id) |id| {
262 try writeTuple(0x00, id.slice(), output);
263 }
264 if (parameters.max_idle_timeout != 0) {
265 try writeInteger(0x01, parameters.max_idle_timeout, output);
266 }
267 if (parameters.stateless_reset_token) |token| try writeTuple(0x02, &token, output);
268 if (parameters.max_udp_payload_size != 65_527) {
269 try writeInteger(0x03, parameters.max_udp_payload_size, output);
270 }
271 try writeFlowControl(parameters, output);
272 if (parameters.ack_delay_exponent != 3) {
273 try writeInteger(0x0a, parameters.ack_delay_exponent, output);
274 }
275 if (parameters.max_ack_delay != 25) {
276 try writeInteger(0x0b, parameters.max_ack_delay, output);
277 }
278 if (parameters.disable_active_migration) try writeTuple(0x0c, &.{}, output);
279 if (parameters.preferred_address) |address| try writePreferredAddress(address, output);
280 if (parameters.active_connection_id_limit != 2) {
281 try writeInteger(0x0e, parameters.active_connection_id_limit, output);
282 }
283 if (parameters.initial_source_connection_id) |id| {
284 try writeTuple(0x0f, id.slice(), output);
285 }
286 if (parameters.retry_source_connection_id) |id| {
287 try writeTuple(0x10, id.slice(), output);
288 }
289 if (parameters.max_datagram_frame_size != 0) {
290 try writeInteger(0x20, parameters.max_datagram_frame_size, output);
291 }
292 }
293
294 fn writeFlowControl(parameters: Parameters, output: *cursor.Write) EncodeError!void {
295 const values = [_]u62{
296 parameters.initial_max_data,
297 parameters.initial_max_stream_data_bidi_local,
298 parameters.initial_max_stream_data_bidi_remote,
299 parameters.initial_max_stream_data_uni,
300 parameters.initial_max_streams_bidi,
301 parameters.initial_max_streams_uni,
302 };
303 for (values, 0x04..) |value, id| {
304 if (value != 0) try writeInteger(@intCast(id), value, output);
305 }
306 }
307
308 test "RFC 9000 section 18.2 and RFC 9221 section 3 parameters round trip" {
309 const source = try quic.packet.ConnectionId.init(&.{ 1, 2, 3, 4 });
310 const original = try quic.packet.ConnectionId.init(&.{ 5, 6, 7 });
311 const preferred_id = try quic.packet.ConnectionId.init(&.{ 8, 9 });
312 const parameters = Parameters{
313 .original_destination_connection_id = original,
314 .max_idle_timeout = 30_000,
315 .stateless_reset_token = @splat(0x11),
316 .max_udp_payload_size = 1400,
317 .initial_max_data = 1000,
318 .initial_max_stream_data_bidi_local = 2000,
319 .initial_max_stream_data_bidi_remote = 3000,
320 .initial_max_stream_data_uni = 4000,
321 .initial_max_streams_bidi = 10,
322 .initial_max_streams_uni = 11,
323 .ack_delay_exponent = 4,
324 .max_ack_delay = 26,
325 .disable_active_migration = true,
326 .preferred_address = .{
327 .ipv4 = .{ 192, 0, 2, 1 },
328 .ipv4_port = 443,
329 .ipv6 = .{ 0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
330 .ipv6_port = 8443,
331 .connection_id = preferred_id,
332 .reset_token = @splat(0x22),
333 },
334 .active_connection_id_limit = 4,
335 .initial_source_connection_id = source,
336 .retry_source_connection_id = original,
337 .max_datagram_frame_size = 1200,
338 };
339 var bytes: [512]u8 = undefined;
340 var output = cursor.Write.init(&bytes);
341 try encode(parameters, &output);
342 const decoded = try decode(output.written());
343 try std.testing.expectEqualDeep(parameters, decoded);
344 }
345
346 test "RFC 9000 section 18.2 rejects invalid parameter values" {
347 var long_connection_id: [23]u8 = @splat(0xaa);
348 long_connection_id[0] = 0x0f;
349 long_connection_id[1] = 21;
350 const Case = struct { bytes: []const u8, expected: DecodeError };
351 const cases = [_]Case{
352 .{
353 .bytes = &.{ 0x03, 0x01, 0x25 },
354 .expected = error.InvalidMaxUdpPayloadSize,
355 },
356 .{
357 .bytes = &.{ 0x03, 0x04, 0x80, 0x00, 0xff, 0xf8 },
358 .expected = error.InvalidMaxUdpPayloadSize,
359 },
360 .{
361 .bytes = &.{ 0x0a, 0x01, 21 },
362 .expected = error.InvalidAckDelayExponent,
363 },
364 .{
365 .bytes = &.{ 0x0b, 0x04, 0x80, 0x00, 0x40, 0x00 },
366 .expected = error.InvalidMaxAckDelay,
367 },
368 .{
369 .bytes = &.{ 0x0e, 0x01, 1 },
370 .expected = error.InvalidActiveConnectionIdLimit,
371 },
372 .{
373 .bytes = &.{ 0x01, 0x02, 0x01, 0x00 },
374 .expected = error.InvalidInteger,
375 },
376 .{
377 .bytes = &.{ 0x01, 0x01, 0x40 },
378 .expected = error.InvalidInteger,
379 },
380 .{
381 .bytes = &long_connection_id,
382 .expected = error.InvalidConnectionId,
383 },
384 .{
385 .bytes = &.{ 0x0d, 0x01, 0x00 },
386 .expected = error.InvalidPreferredAddress,
387 },
388 };
389 for (cases) |case| try std.testing.expectError(case.expected, decode(case.bytes));
390 }
391
392 test "RFC 9000 section 18.2 max UDP payload size upper bound" {
393 const decoded = try decode(&.{ 0x03, 0x04, 0x80, 0x00, 0xff, 0xf7 });
394 try std.testing.expectEqual(@as(u62, 65_527), decoded.max_udp_payload_size);
395
396 var bytes: [16]u8 = undefined;
397 var output = cursor.Write.init(&bytes);
398 try encode(.{ .max_udp_payload_size = 65_527 }, &output);
399 try std.testing.expectEqualDeep(Parameters{}, try decode(output.written()));
400
401 output = cursor.Write.init(&bytes);
402 try std.testing.expectError(
403 error.InvalidMaxUdpPayloadSize,
404 encode(.{ .max_udp_payload_size = 65_528 }, &output),
405 );
406 }
407
408 test "RFC 9000 sections 7.4 and 18 reject duplicate parameters" {
409 try std.testing.expectError(
410 error.DuplicateParameter,
411 decode(&.{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x02 }),
412 );
413 }
414
415 test "RFC 9000 section 18.1 skips reserved and unknown parameters" {
416 const decoded = try decode(&.{ 27, 1, 0xaa, 0x21, 2, 0xbb, 0xcc });
417 try std.testing.expectEqualDeep(Parameters{}, decoded);
418 }
419
420 test "RFC 9000 section 18.2 parameter count maximum and maximum plus one" {
421 var bytes: [parameter_count_max * 3]u8 = undefined;
422 for (0..parameter_count_max) |index| {
423 bytes[index * 3] = 0x40;
424 bytes[index * 3 + 1] = @intCast(128 + index);
425 bytes[index * 3 + 2] = 0;
426 }
427 _ = try decode(&bytes);
428 var plus_one: [bytes.len + 3]u8 = undefined;
429 @memcpy(plus_one[0..bytes.len], &bytes);
430 plus_one[bytes.len] = 0x40;
431 plus_one[bytes.len + 1] = 0xc0;
432 plus_one[bytes.len + 2] = 0;
433 try std.testing.expectError(error.TooManyParameters, decode(&plus_one));
434 }