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 write API: buffer-direct CBOR encoding

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

jcalabro df6d1389 f1e62b57

+315
+102
src/internal/repo/cbor.zig
··· 853 853 return null; 854 854 } 855 855 856 + // === low-level write API === 857 + 858 + /// Write CBOR initial byte + argument using shortest encoding. 859 + /// Returns new position after written bytes. Caller must ensure buf is large enough. 860 + pub fn writeArg(buf: []u8, pos: usize, major: u3, val: u64) usize { 861 + const prefix: u8 = @as(u8, major) << 5; 862 + if (val < 24) { 863 + buf[pos] = prefix | @as(u8, @intCast(val)); 864 + return pos + 1; 865 + } else if (val <= 0xff) { 866 + buf[pos] = prefix | 24; 867 + buf[pos + 1] = @intCast(val); 868 + return pos + 2; 869 + } else if (val <= 0xffff) { 870 + buf[pos] = prefix | 25; 871 + const v: u16 = @intCast(val); 872 + buf[pos + 1] = @truncate(v >> 8); 873 + buf[pos + 2] = @truncate(v); 874 + return pos + 3; 875 + } else if (val <= 0xffffffff) { 876 + buf[pos] = prefix | 26; 877 + const v: u32 = @intCast(val); 878 + buf[pos + 1] = @truncate(v >> 24); 879 + buf[pos + 2] = @truncate(v >> 16); 880 + buf[pos + 3] = @truncate(v >> 8); 881 + buf[pos + 4] = @truncate(v); 882 + return pos + 5; 883 + } else { 884 + buf[pos] = prefix | 27; 885 + buf[pos + 1] = @truncate(val >> 56); 886 + buf[pos + 2] = @truncate(val >> 48); 887 + buf[pos + 3] = @truncate(val >> 40); 888 + buf[pos + 4] = @truncate(val >> 32); 889 + buf[pos + 5] = @truncate(val >> 24); 890 + buf[pos + 6] = @truncate(val >> 16); 891 + buf[pos + 7] = @truncate(val >> 8); 892 + buf[pos + 8] = @truncate(val); 893 + return pos + 9; 894 + } 895 + } 896 + 897 + /// Write CBOR text string header + payload. 898 + pub fn writeText(buf: []u8, pos: usize, text: []const u8) usize { 899 + const p = writeArg(buf, pos, 3, text.len); 900 + @memcpy(buf[p..][0..text.len], text); 901 + return p + text.len; 902 + } 903 + 904 + /// Write CBOR byte string header + payload. 905 + pub fn writeBytes(buf: []u8, pos: usize, bytes: []const u8) usize { 906 + const p = writeArg(buf, pos, 2, bytes.len); 907 + @memcpy(buf[p..][0..bytes.len], bytes); 908 + return p + bytes.len; 909 + } 910 + 911 + /// Write unsigned integer (major 0). 912 + pub fn writeUint(buf: []u8, pos: usize, val: u64) usize { 913 + return writeArg(buf, pos, 0, val); 914 + } 915 + 916 + /// Write signed integer. Positive values use major 0, negative values use major 1. 917 + pub fn writeInt(buf: []u8, pos: usize, val: i64) usize { 918 + if (val >= 0) { 919 + return writeArg(buf, pos, 0, @intCast(val)); 920 + } else { 921 + const raw: u64 = @intCast(-1 - val); 922 + return writeArg(buf, pos, 1, raw); 923 + } 924 + } 925 + 926 + /// Write map header (major 5). 927 + pub fn writeMapHeader(buf: []u8, pos: usize, count: usize) usize { 928 + return writeArg(buf, pos, 5, count); 929 + } 930 + 931 + /// Write array header (major 4). 932 + pub fn writeArrayHeader(buf: []u8, pos: usize, count: usize) usize { 933 + return writeArg(buf, pos, 4, count); 934 + } 935 + 936 + /// Write boolean: 0xf5 (true) or 0xf4 (false). 937 + pub fn writeBool(buf: []u8, pos: usize, val: bool) usize { 938 + buf[pos] = if (val) 0xf5 else 0xf4; 939 + return pos + 1; 940 + } 941 + 942 + /// Write null: 0xf6. 943 + pub fn writeNull(buf: []u8, pos: usize) usize { 944 + buf[pos] = 0xf6; 945 + return pos + 1; 946 + } 947 + 948 + /// Write tag(42) + byte string with 0x00 prefix + CID raw bytes. 949 + pub fn writeCidLink(buf: []u8, pos: usize, cid_raw: []const u8) usize { 950 + var p = writeArg(buf, pos, 6, 42); 951 + p = writeArg(buf, p, 2, 1 + cid_raw.len); 952 + buf[p] = 0x00; 953 + p += 1; 954 + @memcpy(buf[p..][0..cid_raw.len], cid_raw); 955 + return p + cid_raw.len; 956 + } 957 + 856 958 // === tests === 857 959 858 960 test "decode unsigned integers" {
+212
src/internal/repo/cbor_write_test.zig
··· 1 + const std = @import("std"); 2 + const cbor = @import("cbor.zig"); 3 + 4 + const writeArg = cbor.writeArg; 5 + const writeText = cbor.writeText; 6 + const writeBytes = cbor.writeBytes; 7 + const writeUint = cbor.writeUint; 8 + const writeInt = cbor.writeInt; 9 + const writeMapHeader = cbor.writeMapHeader; 10 + const writeArrayHeader = cbor.writeArrayHeader; 11 + const writeBool = cbor.writeBool; 12 + const writeNull = cbor.writeNull; 13 + const writeCidLink = cbor.writeCidLink; 14 + 15 + const readArg = cbor.readArg; 16 + const readText = cbor.readText; 17 + const readBytes = cbor.readBytes; 18 + const readUint = cbor.readUint; 19 + const readInt = cbor.readInt; 20 + const readBool = cbor.readBool; 21 + const readNull = cbor.readNull; 22 + const readMapHeader = cbor.readMapHeader; 23 + const readArrayHeader = cbor.readArrayHeader; 24 + const readCidLink = cbor.readCidLink; 25 + 26 + const decodeAll = cbor.decodeAll; 27 + 28 + // =========================================================================== 29 + // writeArg 30 + // =========================================================================== 31 + 32 + test "writeArg: value 0 (1 byte)" { 33 + var buf: [16]u8 = undefined; 34 + const end = writeArg(&buf, 0, 0, 0); 35 + try std.testing.expectEqual(@as(usize, 1), end); 36 + try std.testing.expectEqual(@as(u8, 0x00), buf[0]); 37 + } 38 + 39 + test "writeArg: value 23 (1 byte)" { 40 + var buf: [16]u8 = undefined; 41 + const end = writeArg(&buf, 0, 0, 23); 42 + try std.testing.expectEqual(@as(usize, 1), end); 43 + try std.testing.expectEqual(@as(u8, 0x17), buf[0]); 44 + } 45 + 46 + test "writeArg: value 24 (2 bytes)" { 47 + var buf: [16]u8 = undefined; 48 + const end = writeArg(&buf, 0, 0, 24); 49 + try std.testing.expectEqual(@as(usize, 2), end); 50 + try std.testing.expectEqual(@as(u8, 0x18), buf[0]); 51 + try std.testing.expectEqual(@as(u8, 24), buf[1]); 52 + } 53 + 54 + test "writeArg: value 1000 (3 bytes)" { 55 + var buf: [16]u8 = undefined; 56 + const end = writeArg(&buf, 0, 0, 1000); 57 + try std.testing.expectEqual(@as(usize, 3), end); 58 + try std.testing.expectEqual(@as(u8, 0x19), buf[0]); 59 + try std.testing.expectEqual(@as(u8, 0x03), buf[1]); 60 + try std.testing.expectEqual(@as(u8, 0xe8), buf[2]); 61 + } 62 + 63 + // =========================================================================== 64 + // writeText round-trip 65 + // =========================================================================== 66 + 67 + test "writeText: 'hello' round-trip" { 68 + var buf: [64]u8 = undefined; 69 + const end = writeText(&buf, 0, "hello"); 70 + const result = try readText(&buf, 0); 71 + try std.testing.expectEqualStrings("hello", result.val); 72 + try std.testing.expectEqual(end, result.end); 73 + } 74 + 75 + // =========================================================================== 76 + // writeBytes round-trip 77 + // =========================================================================== 78 + 79 + test "writeBytes: [1,2,3] round-trip" { 80 + var buf: [64]u8 = undefined; 81 + const input = [_]u8{ 1, 2, 3 }; 82 + const end = writeBytes(&buf, 0, &input); 83 + const result = try readBytes(&buf, 0); 84 + try std.testing.expectEqual(@as(usize, 3), result.val.len); 85 + try std.testing.expectEqual(@as(u8, 1), result.val[0]); 86 + try std.testing.expectEqual(@as(u8, 2), result.val[1]); 87 + try std.testing.expectEqual(@as(u8, 3), result.val[2]); 88 + try std.testing.expectEqual(end, result.end); 89 + } 90 + 91 + // =========================================================================== 92 + // writeUint round-trip 93 + // =========================================================================== 94 + 95 + test "writeUint: 42 round-trip" { 96 + var buf: [16]u8 = undefined; 97 + const end = writeUint(&buf, 0, 42); 98 + const result = try readUint(&buf, 0); 99 + try std.testing.expectEqual(@as(u64, 42), result.val); 100 + try std.testing.expectEqual(end, result.end); 101 + } 102 + 103 + // =========================================================================== 104 + // writeInt 105 + // =========================================================================== 106 + 107 + test "writeInt: -10 verify bytes" { 108 + var buf: [16]u8 = undefined; 109 + const end = writeInt(&buf, 0, -10); 110 + try std.testing.expectEqual(@as(usize, 1), end); 111 + try std.testing.expectEqual(@as(u8, 0x29), buf[0]); 112 + } 113 + 114 + test "writeInt: positive 42 round-trip" { 115 + var buf: [16]u8 = undefined; 116 + const end = writeInt(&buf, 0, 42); 117 + const result = try readInt(&buf, 0); 118 + try std.testing.expectEqual(@as(i64, 42), result.val); 119 + try std.testing.expectEqual(end, result.end); 120 + } 121 + 122 + // =========================================================================== 123 + // writeMapHeader 124 + // =========================================================================== 125 + 126 + test "writeMapHeader: count 3 verify byte" { 127 + var buf: [16]u8 = undefined; 128 + const end = writeMapHeader(&buf, 0, 3); 129 + try std.testing.expectEqual(@as(usize, 1), end); 130 + try std.testing.expectEqual(@as(u8, 0xa3), buf[0]); 131 + } 132 + 133 + // =========================================================================== 134 + // writeArrayHeader 135 + // =========================================================================== 136 + 137 + test "writeArrayHeader: count 2 verify byte" { 138 + var buf: [16]u8 = undefined; 139 + const end = writeArrayHeader(&buf, 0, 2); 140 + try std.testing.expectEqual(@as(usize, 1), end); 141 + try std.testing.expectEqual(@as(u8, 0x82), buf[0]); 142 + } 143 + 144 + // =========================================================================== 145 + // writeBool 146 + // =========================================================================== 147 + 148 + test "writeBool: true and false consecutive" { 149 + var buf: [16]u8 = undefined; 150 + var p = writeBool(&buf, 0, true); 151 + p = writeBool(&buf, p, false); 152 + try std.testing.expectEqual(@as(u8, 0xf5), buf[0]); 153 + try std.testing.expectEqual(@as(u8, 0xf4), buf[1]); 154 + try std.testing.expectEqual(@as(usize, 2), p); 155 + 156 + const r1 = try readBool(&buf, 0); 157 + try std.testing.expectEqual(true, r1.val); 158 + const r2 = try readBool(&buf, r1.end); 159 + try std.testing.expectEqual(false, r2.val); 160 + } 161 + 162 + // =========================================================================== 163 + // writeNull 164 + // =========================================================================== 165 + 166 + test "writeNull: verify byte" { 167 + var buf: [16]u8 = undefined; 168 + const end = writeNull(&buf, 0); 169 + try std.testing.expectEqual(@as(usize, 1), end); 170 + try std.testing.expectEqual(@as(u8, 0xf6), buf[0]); 171 + // round-trip 172 + const read_end = try readNull(&buf, 0); 173 + try std.testing.expectEqual(end, read_end); 174 + } 175 + 176 + // =========================================================================== 177 + // writeCidLink round-trip 178 + // =========================================================================== 179 + 180 + test "writeCidLink: round-trip" { 181 + var buf: [128]u8 = undefined; 182 + const cid_raw = [_]u8{ 0x01, 0x71, 0x12, 0x20 } ++ [_]u8{0xaa} ** 32; 183 + const end = writeCidLink(&buf, 0, &cid_raw); 184 + const result = try readCidLink(&buf, 0); 185 + try std.testing.expectEqual(@as(usize, 36), result.val.len); 186 + try std.testing.expectEqualSlices(u8, &cid_raw, result.val); 187 + try std.testing.expectEqual(end, result.end); 188 + } 189 + 190 + // =========================================================================== 191 + // Full record: manually write {"text": "hello", "value": 42} then decodeAll 192 + // =========================================================================== 193 + 194 + test "full record: write map then decodeAll" { 195 + var buf: [128]u8 = undefined; 196 + // DAG-CBOR sorts keys by length then lex: "text" (4) < "value" (5) 197 + var p: usize = 0; 198 + p = writeMapHeader(&buf, p, 2); 199 + p = writeText(&buf, p, "text"); 200 + p = writeText(&buf, p, "hello"); 201 + p = writeText(&buf, p, "value"); 202 + p = writeUint(&buf, p, 42); 203 + 204 + const encoded = buf[0..p]; 205 + 206 + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 207 + defer arena.deinit(); 208 + const decoded = try decodeAll(arena.allocator(), encoded); 209 + 210 + try std.testing.expectEqualStrings("hello", decoded.getString("text").?); 211 + try std.testing.expectEqual(@as(u64, 42), decoded.getUint("value").?); 212 + }
+1
src/root.zig
··· 75 75 _ = @import("internal/repo/repo_verifier.zig"); 76 76 _ = @import("internal/repo/cbor_test.zig"); 77 77 _ = @import("internal/repo/cbor_read_test.zig"); 78 + _ = @import("internal/repo/cbor_write_test.zig"); 78 79 _ = @import("internal/repo/car_test.zig"); 79 80 } 80 81 }