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 }