lib/quic/src/varint.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const cursor = @import("cursor.zig");
3
4 pub const EncodeError = cursor.WriteError;
5 pub const DecodeError = cursor.ReadError;
6
7 pub const Decoded = struct {
8 value: u62,
9 length: u4,
10 };
11
12 pub fn encodedLength(value: u62) u4 {
13 if (value <= 63) return 1;
14 if (value <= 16_383) return 2;
15 if (value <= 1_073_741_823) return 4;
16 return 8;
17 }
18
19 pub fn write(value: u62, output: *cursor.Write) EncodeError!u4 {
20 const length = encodedLength(value);
21 switch (length) {
22 1 => try output.byte(@intCast(value)),
23 2 => try output.int(u16, @as(u16, @intCast(value)) | 0x4000),
24 4 => try output.int(u32, @as(u32, @intCast(value)) | 0x8000_0000),
25 8 => try output.int(u64, @as(u64, value) | 0xc000_0000_0000_0000),
26 else => unreachable,
27 }
28 return length;
29 }
30
31 pub fn encode(value: u62, out: []u8) EncodeError!u4 {
32 var output = cursor.Write.init(out);
33 return write(value, &output);
34 }
35
36 /// Reads one varint from a read cursor and returns its value with the number of bytes it took, so a
37 /// codec takes the next varint out of a byte stream and leaves the cursor past it. The width comes
38 /// from the top two bits of the first byte. A value written in a wider form than it needs is
39 /// accepted and decoded to the same value, which RFC 9000 section 16 permits. The frame codec
40 /// rejects a non-minimal frame type. Too few bytes for the selected width give `Truncated`.
41 pub fn read(input: *cursor.Read) DecodeError!Decoded {
42 const first = try input.byte();
43 const length: u4 = @as(u4, 1) << @intCast(first >> 6);
44 const tail = try input.take(@as(usize, length) - 1);
45 var value: u64 = first & 0x3f;
46 for (0..7) |index| {
47 if (index >= tail.len) break;
48 value = (value << 8) | tail[index];
49 }
50 std.debug.assert(value <= std.math.maxInt(u62));
51 return .{ .value = @intCast(value), .length = length };
52 }
53
54 /// Reads one varint from the front of a byte slice and returns its value with the number of bytes
55 /// it took, so a caller holding a plain byte slice calls it without building a cursor first. It
56 /// wraps the cursor form, so it accepts the same non-minimal encodings RFC 9000 section 16 permits,
57 /// and the frame codec keeps the minimality rule for frame type fields. Bytes after the varint are
58 /// left alone, and too few bytes give `Truncated`.
59 pub fn decode(bytes: []const u8) DecodeError!Decoded {
60 var input = cursor.Read.init(bytes);
61 return read(&input);
62 }
63
64 test "RFC 9000 section 16 and Appendix A.1 varint known answers" {
65 const Vector = struct { bytes: []const u8, value: u62 };
66 const vectors = [_]Vector{
67 .{
68 .bytes = &.{ 0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c },
69 .value = 151_288_809_941_952_652,
70 },
71 .{ .bytes = &.{ 0x9d, 0x7f, 0x3e, 0x7d }, .value = 494_878_333 },
72 .{ .bytes = &.{ 0x7b, 0xbd }, .value = 15_293 },
73 .{ .bytes = &.{0x25}, .value = 37 },
74 };
75 for (vectors) |vector| {
76 const decoded = try decode(vector.bytes);
77 try std.testing.expectEqual(vector.value, decoded.value);
78 try std.testing.expectEqual(vector.bytes.len, decoded.length);
79 var encoded: [8]u8 = undefined;
80 const length = try encode(vector.value, &encoded);
81 try std.testing.expectEqualSlices(u8, vector.bytes, encoded[0..length]);
82 }
83 }
84
85 test "RFC 9000 section 16 accepts permitted non-minimal varints" {
86 const decoded = try decode(&.{ 0x40, 0x25 });
87 try std.testing.expectEqual(@as(u62, 37), decoded.value);
88 try std.testing.expectEqual(@as(u4, 2), decoded.length);
89 }