lib/quic/src/cursor.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const ReadError = error{Truncated};
4 pub const WriteError = error{NoSpace};
5
6 pub const Read = struct {
7 bytes: []const u8,
8 index: usize,
9
10 pub fn init(bytes: []const u8) Read {
11 return .{ .bytes = bytes, .index = 0 };
12 }
13
14 pub fn remaining(self: *const Read) usize {
15 std.debug.assert(self.index <= self.bytes.len);
16 return self.bytes.len - self.index;
17 }
18
19 pub fn take(self: *Read, count: usize) ReadError![]const u8 {
20 std.debug.assert(self.index <= self.bytes.len);
21 if (count > self.bytes.len - self.index) return error.Truncated;
22 const start = self.index;
23 self.index += count;
24 std.debug.assert(self.index <= self.bytes.len);
25 return self.bytes[start..self.index];
26 }
27
28 pub fn byte(self: *Read) ReadError!u8 {
29 return (try self.take(1))[0];
30 }
31
32 pub fn int(self: *Read, comptime T: type) ReadError!T {
33 comptime std.debug.assert(@bitSizeOf(T) % 8 == 0);
34 const byte_count = @divExact(@bitSizeOf(T), 8);
35 const input = try self.take(byte_count);
36 var value: T = 0;
37 for (input) |part| value = (value << 8) | @as(T, part);
38 return value;
39 }
40
41 pub fn rest(self: *Read) []const u8 {
42 const input = self.bytes[self.index..];
43 self.index = self.bytes.len;
44 return input;
45 }
46 };
47
48 pub const Write = struct {
49 bytes: []u8,
50 index: usize,
51
52 pub fn init(bytes: []u8) Write {
53 return .{ .bytes = bytes, .index = 0 };
54 }
55
56 pub fn remaining(self: *const Write) usize {
57 std.debug.assert(self.index <= self.bytes.len);
58 return self.bytes.len - self.index;
59 }
60
61 pub fn put(self: *Write, input: []const u8) WriteError!void {
62 std.debug.assert(self.index <= self.bytes.len);
63 if (input.len > self.bytes.len - self.index) return error.NoSpace;
64 @memcpy(self.bytes[self.index..][0..input.len], input);
65 self.index += input.len;
66 std.debug.assert(self.index <= self.bytes.len);
67 }
68
69 pub fn byte(self: *Write, value: u8) WriteError!void {
70 try self.put(&.{value});
71 }
72
73 pub fn int(self: *Write, comptime T: type, value: T) WriteError!void {
74 comptime std.debug.assert(@bitSizeOf(T) % 8 == 0);
75 const byte_count = @divExact(@bitSizeOf(T), 8);
76 var output: [byte_count]u8 = undefined;
77 for (0..byte_count) |index| {
78 const shift = (byte_count - 1 - index) * 8;
79 output[index] = @truncate(value >> @intCast(shift));
80 }
81 try self.put(&output);
82 }
83
84 pub fn written(self: *const Write) []const u8 {
85 std.debug.assert(self.index <= self.bytes.len);
86 return self.bytes[0..self.index];
87 }
88 };
89
90 test "bounded cursors return operational exhaustion errors" {
91 var input = Read.init(&.{0xaa});
92 try std.testing.expectEqual(@as(u8, 0xaa), try input.byte());
93 try std.testing.expectError(error.Truncated, input.byte());
94
95 var bytes: [1]u8 = undefined;
96 var output = Write.init(&bytes);
97 try output.byte(0x55);
98 try std.testing.expectError(error.NoSpace, output.byte(0x66));
99 try std.testing.expectEqualSlices(u8, &.{0x55}, output.written());
100 }
101
102 test "bounded cursors use the wire width of sized integers" {
103 var bytes: [3]u8 = undefined;
104 var output = Write.init(&bytes);
105 try output.int(u24, 0xabcdef);
106 try std.testing.expectEqualSlices(u8, &.{ 0xab, 0xcd, 0xef }, output.written());
107 var input = Read.init(&bytes);
108 try std.testing.expectEqual(@as(u24, 0xabcdef), try input.int(u24));
109 }