atproto utils for zig zat.dev
atproto sdk zig
26
fork

Configure Feed

Select the types of activity you want to include in your feed.

add low-level readArg: zero-copy CBOR header reader

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

jcalabro 6e0d4068 129ec288

+226
+56
src/internal/repo/cbor.zig
··· 241 241 DuplicateMapKey, 242 242 InvalidUtf8, 243 243 MaxDepthExceeded, 244 + WrongType, 244 245 }; 245 246 246 247 /// maximum nesting depth for arrays/maps to prevent stack overflow ··· 589 590 v >>= 7; 590 591 } 591 592 try writer.writeByte(@as(u8, @truncate(v))); 593 + } 594 + 595 + /// Result of reading a CBOR initial byte and its argument. 596 + pub const Arg = struct { 597 + major: u3, 598 + val: u64, 599 + end: usize, 600 + }; 601 + 602 + /// Read a CBOR initial byte at `pos`, parse the argument value from 603 + /// additional info + following bytes, and return the major type (high 3 bits), 604 + /// argument value, and position after the header. 605 + /// 606 + /// Validates shortest-form encoding (DAG-CBOR requirement). 607 + /// This is the public, value-semantics equivalent of the internal `readArgument`. 608 + pub fn readArg(data: []const u8, pos: usize) DecodeError!Arg { 609 + if (pos >= data.len) return error.UnexpectedEof; 610 + const initial = data[pos]; 611 + const major: u3 = @truncate(initial >> 5); 612 + const additional: u5 = @truncate(initial); 613 + var cur = pos + 1; 614 + const val: u64 = switch (additional) { 615 + 0...23 => @as(u64, additional), 616 + 24 => blk: { // 1-byte 617 + if (cur >= data.len) return error.UnexpectedEof; 618 + const v = data[cur]; 619 + cur += 1; 620 + if (v < 24) return error.NonMinimalEncoding; 621 + break :blk @as(u64, v); 622 + }, 623 + 25 => blk: { // 2-byte big-endian 624 + if (cur + 2 > data.len) return error.UnexpectedEof; 625 + const v = std.mem.readInt(u16, data[cur..][0..2], .big); 626 + cur += 2; 627 + if (v <= 0xff) return error.NonMinimalEncoding; 628 + break :blk @as(u64, v); 629 + }, 630 + 26 => blk: { // 4-byte big-endian 631 + if (cur + 4 > data.len) return error.UnexpectedEof; 632 + const v = std.mem.readInt(u32, data[cur..][0..4], .big); 633 + cur += 4; 634 + if (v <= 0xffff) return error.NonMinimalEncoding; 635 + break :blk @as(u64, v); 636 + }, 637 + 27 => blk: { // 8-byte big-endian 638 + if (cur + 8 > data.len) return error.UnexpectedEof; 639 + const v = std.mem.readInt(u64, data[cur..][0..8], .big); 640 + cur += 8; 641 + if (v <= 0xffffffff) return error.NonMinimalEncoding; 642 + break :blk v; 643 + }, 644 + 28, 29, 30 => return error.ReservedAdditionalInfo, 645 + 31 => return error.IndefiniteLength, 646 + }; 647 + return .{ .major = major, .val = val, .end = cur }; 592 648 } 593 649 594 650 // === tests ===
+169
src/internal/repo/cbor_read_test.zig
··· 1 + const std = @import("std"); 2 + const cbor = @import("cbor.zig"); 3 + const readArg = cbor.readArg; 4 + const Arg = cbor.Arg; 5 + 6 + // --------------------------------------------------------------------------- 7 + // Inline values 0-23 (major type 0 = unsigned) 8 + // --------------------------------------------------------------------------- 9 + 10 + test "readArg: inline value 0" { 11 + const data = [_]u8{0x00}; // major 0, additional 0 12 + const arg = try readArg(&data, 0); 13 + try std.testing.expectEqual(@as(u3, 0), arg.major); 14 + try std.testing.expectEqual(@as(u64, 0), arg.val); 15 + try std.testing.expectEqual(@as(usize, 1), arg.end); 16 + } 17 + 18 + test "readArg: inline value 1" { 19 + const data = [_]u8{0x01}; 20 + const arg = try readArg(&data, 0); 21 + try std.testing.expectEqual(@as(u3, 0), arg.major); 22 + try std.testing.expectEqual(@as(u64, 1), arg.val); 23 + try std.testing.expectEqual(@as(usize, 1), arg.end); 24 + } 25 + 26 + test "readArg: inline value 23" { 27 + const data = [_]u8{0x17}; // major 0, additional 23 28 + const arg = try readArg(&data, 0); 29 + try std.testing.expectEqual(@as(u3, 0), arg.major); 30 + try std.testing.expectEqual(@as(u64, 23), arg.val); 31 + try std.testing.expectEqual(@as(usize, 1), arg.end); 32 + } 33 + 34 + // --------------------------------------------------------------------------- 35 + // 1-byte value (additional info = 24) 36 + // --------------------------------------------------------------------------- 37 + 38 + test "readArg: 1-byte value 24" { 39 + const data = [_]u8{ 0x18, 24 }; // major 0, additional 24, payload 24 40 + const arg = try readArg(&data, 0); 41 + try std.testing.expectEqual(@as(u3, 0), arg.major); 42 + try std.testing.expectEqual(@as(u64, 24), arg.val); 43 + try std.testing.expectEqual(@as(usize, 2), arg.end); 44 + } 45 + 46 + test "readArg: 1-byte value 255" { 47 + const data = [_]u8{ 0x18, 0xff }; // major 0, additional 24, payload 255 48 + const arg = try readArg(&data, 0); 49 + try std.testing.expectEqual(@as(u3, 0), arg.major); 50 + try std.testing.expectEqual(@as(u64, 255), arg.val); 51 + try std.testing.expectEqual(@as(usize, 2), arg.end); 52 + } 53 + 54 + // --------------------------------------------------------------------------- 55 + // 2-byte value (additional info = 25) 56 + // --------------------------------------------------------------------------- 57 + 58 + test "readArg: 2-byte value 256" { 59 + const data = [_]u8{ 0x19, 0x01, 0x00 }; // major 0, additional 25, payload 256 big-endian 60 + const arg = try readArg(&data, 0); 61 + try std.testing.expectEqual(@as(u3, 0), arg.major); 62 + try std.testing.expectEqual(@as(u64, 256), arg.val); 63 + try std.testing.expectEqual(@as(usize, 3), arg.end); 64 + } 65 + 66 + // --------------------------------------------------------------------------- 67 + // 4-byte value (additional info = 26) 68 + // --------------------------------------------------------------------------- 69 + 70 + test "readArg: 4-byte value 65536" { 71 + const data = [_]u8{ 0x1a, 0x00, 0x01, 0x00, 0x00 }; // major 0, additional 26, payload 65536 72 + const arg = try readArg(&data, 0); 73 + try std.testing.expectEqual(@as(u3, 0), arg.major); 74 + try std.testing.expectEqual(@as(u64, 65536), arg.val); 75 + try std.testing.expectEqual(@as(usize, 5), arg.end); 76 + } 77 + 78 + // --------------------------------------------------------------------------- 79 + // 8-byte value (additional info = 27) 80 + // --------------------------------------------------------------------------- 81 + 82 + test "readArg: 8-byte value 0x100000000" { 83 + // major 0, additional 27, payload 0x00_00_00_01_00_00_00_00 84 + const data = [_]u8{ 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }; 85 + const arg = try readArg(&data, 0); 86 + try std.testing.expectEqual(@as(u3, 0), arg.major); 87 + try std.testing.expectEqual(@as(u64, 0x100000000), arg.val); 88 + try std.testing.expectEqual(@as(usize, 9), arg.end); 89 + } 90 + 91 + // --------------------------------------------------------------------------- 92 + // Reject non-minimal encodings 93 + // --------------------------------------------------------------------------- 94 + 95 + test "readArg: reject non-minimal 0 encoded as 1-byte" { 96 + // Value 0 encoded with additional=24 payload=0x00 (should be inline 0) 97 + const data = [_]u8{ 0x18, 0x00 }; 98 + try std.testing.expectError(error.NonMinimalEncoding, readArg(&data, 0)); 99 + } 100 + 101 + test "readArg: reject non-minimal 255 encoded as 2-byte" { 102 + // Value 255 encoded with additional=25 payload=0x00ff (should be 1-byte) 103 + const data = [_]u8{ 0x19, 0x00, 0xff }; 104 + try std.testing.expectError(error.NonMinimalEncoding, readArg(&data, 0)); 105 + } 106 + 107 + // --------------------------------------------------------------------------- 108 + // Reject truncated data 109 + // --------------------------------------------------------------------------- 110 + 111 + test "readArg: reject truncated 2-byte" { 112 + // additional=25 needs 2 payload bytes, but only 1 provided 113 + const data = [_]u8{ 0x19, 0x01 }; 114 + try std.testing.expectError(error.UnexpectedEof, readArg(&data, 0)); 115 + } 116 + 117 + // --------------------------------------------------------------------------- 118 + // Reject reserved additional info (28-30) 119 + // --------------------------------------------------------------------------- 120 + 121 + test "readArg: reject reserved additional info 28" { 122 + const data = [_]u8{0x1c}; // major 0, additional 28 123 + try std.testing.expectError(error.ReservedAdditionalInfo, readArg(&data, 0)); 124 + } 125 + 126 + // --------------------------------------------------------------------------- 127 + // Reject indefinite length (additional info = 31) 128 + // --------------------------------------------------------------------------- 129 + 130 + test "readArg: reject indefinite length 31" { 131 + const data = [_]u8{0x5f}; // major 2 (byte string), additional 31 132 + try std.testing.expectError(error.IndefiniteLength, readArg(&data, 0)); 133 + } 134 + 135 + // --------------------------------------------------------------------------- 136 + // Non-zero start position 137 + // --------------------------------------------------------------------------- 138 + 139 + test "readArg: non-zero start position" { 140 + // prefix byte 0xAA, then a valid CBOR unsigned 24 at position 1 141 + const data = [_]u8{ 0xaa, 0x18, 24 }; 142 + const arg = try readArg(&data, 1); 143 + try std.testing.expectEqual(@as(u3, 0), arg.major); 144 + try std.testing.expectEqual(@as(u64, 24), arg.val); 145 + try std.testing.expectEqual(@as(usize, 3), arg.end); 146 + } 147 + 148 + test "readArg: non-zero start position with different major type" { 149 + // At position 2: 0x63 = major 3 (text string), additional 3 (inline length 3) 150 + const data = [_]u8{ 0x00, 0x00, 0x63, 0x66, 0x6f, 0x6f }; 151 + const arg = try readArg(&data, 2); 152 + try std.testing.expectEqual(@as(u3, 3), arg.major); 153 + try std.testing.expectEqual(@as(u64, 3), arg.val); 154 + try std.testing.expectEqual(@as(usize, 3), arg.end); 155 + } 156 + 157 + // --------------------------------------------------------------------------- 158 + // EOF at start position 159 + // --------------------------------------------------------------------------- 160 + 161 + test "readArg: empty data returns UnexpectedEof" { 162 + const data = [_]u8{}; 163 + try std.testing.expectError(error.UnexpectedEof, readArg(&data, 0)); 164 + } 165 + 166 + test "readArg: pos beyond data returns UnexpectedEof" { 167 + const data = [_]u8{0x00}; 168 + try std.testing.expectError(error.UnexpectedEof, readArg(&data, 1)); 169 + }
+1
src/root.zig
··· 74 74 _ = @import("internal/testing/interop_tests.zig"); 75 75 _ = @import("internal/repo/repo_verifier.zig"); 76 76 _ = @import("internal/repo/cbor_test.zig"); 77 + _ = @import("internal/repo/cbor_read_test.zig"); 77 78 _ = @import("internal/repo/car_test.zig"); 78 79 } 79 80 }