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(&parameters, 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 }