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 }