atproto utils for zig
0
fork

Configure Feed

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

refactor: decodeAt uses public readArg, delete internal readArgument

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

jcalabro 7388697d 24e6b1b4

+45 -90
+45 -90
src/internal/repo/cbor.zig
··· 265 265 266 266 fn decodeAt(allocator: Allocator, data: []const u8, pos: *usize, depth: usize) DecodeError!Value { 267 267 if (pos.* >= data.len) return error.UnexpectedEof; 268 - 269 268 const initial = data[pos.*]; 270 - pos.* += 1; 271 - 272 - const major: MajorType = @enumFromInt(@as(u3, @truncate(initial >> 5))); 269 + const major: u3 = @truncate(initial >> 5); 273 270 const additional: u5 = @truncate(initial); 274 271 275 - return switch (major) { 276 - .unsigned => { 277 - const val = try readArgument(data, pos, additional); 278 - return .{ .unsigned = val }; 279 - }, 280 - .negative => { 281 - const val = try readArgument(data, pos, additional); 272 + // simple values (major 7) are handled without readArg since floats 273 + // use additional 25/26/27 to mean float16/32/64, not integer arguments 274 + if (major == 7) { 275 + pos.* += 1; 276 + return switch (additional) { 277 + 20 => .{ .boolean = false }, 278 + 21 => .{ .boolean = true }, 279 + 22 => .null, 280 + 25, 26, 27 => error.UnsupportedFloat, // DAG-CBOR forbids floats in AT Protocol 281 + 31 => error.IndefiniteLength, // break code — DAG-CBOR forbids indefinite lengths 282 + else => error.UnsupportedSimpleValue, 283 + }; 284 + } 285 + 286 + const arg = try readArg(data, pos.*); 287 + pos.* = arg.end; 288 + 289 + return switch (@as(MajorType, @enumFromInt(major))) { 290 + .unsigned => .{ .unsigned = arg.val }, 291 + .negative => blk: { 282 292 // negative CBOR: value is -1 - val 283 - if (val > std.math.maxInt(i64)) return error.Overflow; 284 - return .{ .negative = -1 - @as(i64, @intCast(val)) }; 293 + if (arg.val > std.math.maxInt(i64)) return error.Overflow; 294 + break :blk .{ .negative = -1 - @as(i64, @intCast(arg.val)) }; 285 295 }, 286 - .byte_string => { 287 - const len = try readArgument(data, pos, additional); 288 - const end = pos.* + @as(usize, @intCast(len)); 296 + .byte_string => blk: { 297 + const end = pos.* + @as(usize, @intCast(arg.val)); 289 298 if (end > data.len) return error.UnexpectedEof; 290 299 const bytes = data[pos.*..end]; 291 300 pos.* = end; 292 - return .{ .bytes = bytes }; 301 + break :blk .{ .bytes = bytes }; 293 302 }, 294 - .text_string => { 295 - const len = try readArgument(data, pos, additional); 296 - const end = pos.* + @as(usize, @intCast(len)); 303 + .text_string => blk: { 304 + const end = pos.* + @as(usize, @intCast(arg.val)); 297 305 if (end > data.len) return error.UnexpectedEof; 298 306 const text = data[pos.*..end]; 299 307 if (!std.unicode.utf8ValidateSlice(text)) return error.InvalidUtf8; 300 308 pos.* = end; 301 - return .{ .text = text }; 309 + break :blk .{ .text = text }; 302 310 }, 303 - .array => { 311 + .array => blk: { 304 312 if (depth >= max_depth) return error.MaxDepthExceeded; 305 - const count = try readArgument(data, pos, additional); 306 313 // sanity check: each element is at least 1 byte 307 - if (count > data.len - pos.*) return error.UnexpectedEof; 308 - const items = try allocator.alloc(Value, @intCast(count)); 314 + if (arg.val > data.len - pos.*) return error.UnexpectedEof; 315 + const items = try allocator.alloc(Value, @intCast(arg.val)); 309 316 for (items) |*item| { 310 317 item.* = try decodeAt(allocator, data, pos, depth + 1); 311 318 } 312 - return .{ .array = items }; 319 + break :blk .{ .array = items }; 313 320 }, 314 - .map => { 321 + .map => blk: { 315 322 if (depth >= max_depth) return error.MaxDepthExceeded; 316 - const count = try readArgument(data, pos, additional); 317 323 // sanity check: each entry is at least 2 bytes (key + value) 318 - if (count > (data.len - pos.*) / 2) return error.UnexpectedEof; 319 - const entries = try allocator.alloc(Value.MapEntry, @intCast(count)); 324 + if (arg.val > (data.len - pos.*) / 2) return error.UnexpectedEof; 325 + const entries = try allocator.alloc(Value.MapEntry, @intCast(arg.val)); 320 326 for (entries, 0..) |*entry, i| { 321 327 // DAG-CBOR: map keys must be text strings — inline read to avoid 322 328 // a full decodeAt + Value union construction per key 323 - if (pos.* >= data.len) return error.UnexpectedEof; 324 - const key_byte = data[pos.*]; 325 - pos.* += 1; 326 - if (@as(u3, @truncate(key_byte >> 5)) != 3) return error.InvalidMapKey; 327 - const key_len = try readArgument(data, pos, @truncate(key_byte)); 328 - const key_end = pos.* + @as(usize, @intCast(key_len)); 329 + const key_arg = try readArg(data, pos.*); 330 + pos.* = key_arg.end; 331 + if (key_arg.major != 3) return error.InvalidMapKey; 332 + const key_end = pos.* + @as(usize, @intCast(key_arg.val)); 329 333 if (key_end > data.len) return error.UnexpectedEof; 330 334 entry.key = data[pos.*..key_end]; 331 335 if (!std.unicode.utf8ValidateSlice(entry.key)) return error.InvalidUtf8; ··· 349 353 350 354 entry.value = try decodeAt(allocator, data, pos, depth + 1); 351 355 } 352 - return .{ .map = entries }; 356 + break :blk .{ .map = entries }; 353 357 }, 354 - .tag => { 355 - const tag_num = try readArgument(data, pos, additional); 356 - if (tag_num != 42) return error.UnsupportedTag; // DAG-CBOR only allows tag 42 (CID) 358 + .tag => blk: { 359 + if (arg.val != 42) return error.UnsupportedTag; // DAG-CBOR only allows tag 42 (CID) 357 360 // CID link — content is a byte string with 0x00 prefix 358 361 const content = try decodeAt(allocator, data, pos, depth); 359 362 const cid_bytes = switch (content) { ··· 361 364 else => return error.InvalidCid, 362 365 }; 363 366 if (cid_bytes.len < 1 or cid_bytes[0] != 0x00) return error.InvalidCid; 364 - return .{ .cid = .{ .raw = cid_bytes[1..] } }; // zero-cost: just reference the bytes 365 - }, 366 - .simple => { 367 - return switch (additional) { 368 - 20 => .{ .boolean = false }, 369 - 21 => .{ .boolean = true }, 370 - 22 => .null, 371 - 25, 26, 27 => error.UnsupportedFloat, // DAG-CBOR forbids floats in AT Protocol 372 - 31 => error.IndefiniteLength, // break code — DAG-CBOR forbids indefinite lengths 373 - else => error.UnsupportedSimpleValue, 374 - }; 367 + break :blk .{ .cid = .{ .raw = cid_bytes[1..] } }; // zero-cost: just reference the bytes 375 368 }, 376 - }; 377 - } 378 - 379 - /// read the argument value from additional info + following bytes. 380 - /// enforces DAG-CBOR shortest-form encoding: rejects values that could 381 - /// have been encoded with fewer bytes. 382 - fn readArgument(data: []const u8, pos: *usize, additional: u5) DecodeError!u64 { 383 - return switch (additional) { 384 - 0...23 => @as(u64, additional), 385 - 24 => { // 1-byte 386 - if (pos.* >= data.len) return error.UnexpectedEof; 387 - const val = data[pos.*]; 388 - pos.* += 1; 389 - if (val < 24) return error.NonMinimalEncoding; 390 - return @as(u64, val); 391 - }, 392 - 25 => { // 2-byte big-endian 393 - if (pos.* + 2 > data.len) return error.UnexpectedEof; 394 - const val = std.mem.readInt(u16, data[pos.*..][0..2], .big); 395 - pos.* += 2; 396 - if (val <= 0xff) return error.NonMinimalEncoding; 397 - return @as(u64, val); 398 - }, 399 - 26 => { // 4-byte big-endian 400 - if (pos.* + 4 > data.len) return error.UnexpectedEof; 401 - const val = std.mem.readInt(u32, data[pos.*..][0..4], .big); 402 - pos.* += 4; 403 - if (val <= 0xffff) return error.NonMinimalEncoding; 404 - return @as(u64, val); 405 - }, 406 - 27 => { // 8-byte big-endian 407 - if (pos.* + 8 > data.len) return error.UnexpectedEof; 408 - const val = std.mem.readInt(u64, data[pos.*..][0..8], .big); 409 - pos.* += 8; 410 - if (val <= 0xffffffff) return error.NonMinimalEncoding; 411 - return val; 412 - }, 413 - 28, 29, 30 => error.ReservedAdditionalInfo, 414 - 31 => error.IndefiniteLength, 369 + .simple => unreachable, // handled above 415 370 }; 416 371 } 417 372