const std = @import("std"); const zat = @import("zat"); pub const Error = error{ ClockIdOutOfRange, TidTooShort, TidTooLong, InvalidTidChar, }; pub const encoded_len = 13; const alphabet = "234567abcdefghijklmnopqrstuvwxyz"; /// ATProto TID generator. /// /// A TID is a 13-character sortable base32 string containing a microsecond /// timestamp plus a 10-bit clock id. This module does not own clock state; the /// caller provides time and clock id so tests and storage layers can control /// monotonicity. pub fn encode(timestamp_us: u64, clock_id: u16) Error![encoded_len]u8 { if (clock_id > 1023) return Error.ClockIdOutOfRange; return zat.Tid.fromTimestamp(timestamp_us, @intCast(clock_id)).raw; } pub fn decode(tid: []const u8) Error!u64 { if (tid.len < encoded_len) return Error.TidTooShort; if (tid.len > encoded_len) return Error.TidTooLong; const parsed = zat.Tid.parse(tid) orelse return Error.InvalidTidChar; return (parsed.timestamp() << 10) | parsed.clockId(); } pub fn timestampMicros(tid: []const u8) Error!u64 { if (tid.len < encoded_len) return Error.TidTooShort; if (tid.len > encoded_len) return Error.TidTooLong; return (zat.Tid.parse(tid) orelse return Error.InvalidTidChar).timestamp(); } pub fn clockId(tid: []const u8) Error!u16 { if (tid.len < encoded_len) return Error.TidTooShort; if (tid.len > encoded_len) return Error.TidTooLong; return (zat.Tid.parse(tid) orelse return Error.InvalidTidChar).clockId(); } fn decodeChar(c: u8) Error!u6 { for (alphabet, 0..) |candidate, i| { if (candidate == c) return @intCast(i); } return Error.InvalidTidChar; } test "encodes sortable tids" { const a = try encode(1_700_000_000_000_000, 0); const b = try encode(1_700_000_000_000_001, 0); try std.testing.expect(std.mem.lessThan(u8, &a, &b)); } test "round trips timestamp and clock id" { const tid = try encode(1_700_000_123_456_789, 42); try std.testing.expectEqual(@as(u64, 1_700_000_123_456_789), try timestampMicros(&tid)); try std.testing.expectEqual(@as(u16, 42), try clockId(&tid)); } test "rejects malformed tids" { try std.testing.expectError(Error.TidTooShort, decode("abc")); try std.testing.expectError(Error.TidTooLong, decode("abcdefghijklmn")); try std.testing.expectError(Error.InvalidTidChar, decode("0000000000000")); }