gitoriaLog in with ident

tickets

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commit9bfba36a9bfba36aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre9bfba36a/plugins/mpackdb/msgpack.zig

22.6 KB

  1. // MessagePack codec for hl:mpackdb — byte-compatible with npm msgpackr 1.11.5
  2. // as mpackdb v1.0.7 uses it (decision D12).
  3. //
  4. // Measured msgpackr surface (probed against the real library, see
  5. // testdata/gen_corpus.mjs):
  6. // - NO record/shared-structure extensions: the standalone pack() export
  7. // serializes plain objects as ordinary MessagePack maps.
  8. // - Objects ALWAYS encode as map16 (0xde) — even empty — never fixmap/map32.
  9. // Maps with > 65535 keys throw in msgpackr; we mirror with error.MapTooLarge.
  10. // - Numbers: integer-valued f64 in [-2^31, 2^32) uses the smallest
  11. // uint/int family (fixint, cc, cd, ce / negfixint, d0, d1, d2). Everything
  12. // else (non-integers, |v| outside that range, NaN, ±Inf) is float64 (0xcb).
  13. // msgpackr never emits float32 or int64/uint64 for JS numbers.
  14. // - Strings: fixstr ≤31 bytes, str8 ≤255, str16 ≤65535, str32 beyond.
  15. // - Arrays: fixarray ≤15, array16 ≤65535, array32 beyond.
  16. // - Binary: bin8/16/32 by length.
  17. // - undefined: fixext1 type 0x00 data 0x00 (d4 00 00) — kept as a distinct
  18. // Value so re-encode is byte-exact; it surfaces as null in Hybriel.
  19. // - Date: timestamp ext -1 (d6ff / d7ff / c70cff). Kept as a raw ext Value
  20. // for byte-exact round-trips; timestampMs() interprets it.
  21. //
  22. // Decode accepts the FULL MessagePack spec (all int/str/bin/array/map families,
  23. // float32, int64/uint64, all ext forms); encode emits only the canonical
  24. // msgpackr subset above.
  25. const std = @import("std");
  26. pub const Error = error{
  27. Truncated,
  28. InvalidFormat, // 0xc1
  29. MapTooLarge,
  30. OutOfMemory,
  31. };
  32. pub const Ext = struct {
  33. type: i8,
  34. data: []const u8,
  35. };
  36. pub const Entry = struct {
  37. key: Value,
  38. value: Value,
  39. };
  40. pub const Value = union(enum) {
  41. nil,
  42. undef, // msgpackr `undefined` (fixext1 type 0)
  43. boolean: bool,
  44. number: f64,
  45. str: []const u8,
  46. bin: []const u8,
  47. array: []Value,
  48. map: []Entry,
  49. ext: Ext,
  50. /// Interpret a timestamp ext (-1) as milliseconds since epoch.
  51. pub fn timestampMs(self: Value) ?f64 {
  52. if (self != .ext or self.ext.type != -1) return null;
  53. const d = self.ext.data;
  54. switch (d.len) {
  55. 4 => {
  56. const sec = std.mem.readInt(u32, d[0..4], .big);
  57. return @as(f64, @floatFromInt(sec)) * 1000.0;
  58. },
  59. 8 => {
  60. const packed_v = std.mem.readInt(u64, d[0..8], .big);
  61. const ns: f64 = @floatFromInt(packed_v >> 34);
  62. const sec: f64 = @floatFromInt(packed_v & 0x3ffffffff);
  63. return sec * 1000.0 + ns / 1_000_000.0;
  64. },
  65. 12 => {
  66. const ns = std.mem.readInt(u32, d[0..4], .big);
  67. const sec = std.mem.readInt(i64, d[4..12], .big);
  68. return @as(f64, @floatFromInt(sec)) * 1000.0 + @as(f64, @floatFromInt(ns)) / 1_000_000.0;
  69. },
  70. else => return null,
  71. }
  72. }
  73. /// Map field lookup by string key (returns null for missing / non-map).
  74. pub fn get(self: Value, key: []const u8) ?Value {
  75. if (self != .map) return null;
  76. for (self.map) |e| {
  77. if (e.key == .str and std.mem.eql(u8, e.key.str, key)) return e.value;
  78. }
  79. return null;
  80. }
  81. };
  82. pub const Decoded = struct {
  83. value: Value,
  84. len: usize, // bytes consumed
  85. };
  86. /// Decode one MessagePack value from `bytes`. All slices in the returned Value
  87. /// reference memory allocated from `alloc` (str/bin/ext data are duplicated,
  88. /// so the Value outlives `bytes`). Use an arena and free it wholesale.
  89. pub fn decode(alloc: std.mem.Allocator, bytes: []const u8) Error!Decoded {
  90. var d = Decoder{ .alloc = alloc, .bytes = bytes, .pos = 0 };
  91. const v = try d.readValue();
  92. return .{ .value = v, .len = d.pos };
  93. }
  94. const Decoder = struct {
  95. alloc: std.mem.Allocator,
  96. bytes: []const u8,
  97. pos: usize,
  98. fn need(self: *Decoder, n: usize) Error!void {
  99. if (self.pos + n > self.bytes.len) return Error.Truncated;
  100. }
  101. fn take(self: *Decoder, n: usize) Error![]const u8 {
  102. try self.need(n);
  103. const s = self.bytes[self.pos .. self.pos + n];
  104. self.pos += n;
  105. return s;
  106. }
  107. fn readBigInt(self: *Decoder, comptime T: type) Error!T {
  108. const n = @divExact(@typeInfo(T).int.bits, 8);
  109. const s = try self.take(n);
  110. return std.mem.readInt(T, s[0..n], .big);
  111. }
  112. fn dupSlice(self: *Decoder, n: usize) Error![]const u8 {
  113. const s = try self.take(n);
  114. return self.alloc.dupe(u8, s) catch return Error.OutOfMemory;
  115. }
  116. fn readArray(self: *Decoder, n: usize) Error!Value {
  117. const items = self.alloc.alloc(Value, n) catch return Error.OutOfMemory;
  118. for (items) |*it| it.* = try self.readValue();
  119. return .{ .array = items };
  120. }
  121. fn readMap(self: *Decoder, n: usize) Error!Value {
  122. const entries = self.alloc.alloc(Entry, n) catch return Error.OutOfMemory;
  123. for (entries) |*e| {
  124. e.key = try self.readValue();
  125. e.value = try self.readValue();
  126. }
  127. return .{ .map = entries };
  128. }
  129. fn readExt(self: *Decoder, n: usize) Error!Value {
  130. try self.need(1 + n);
  131. const t: i8 = @bitCast(self.bytes[self.pos]);
  132. self.pos += 1;
  133. const data = try self.dupSlice(n);
  134. // msgpackr undefined: fixext1 type 0, data byte 0
  135. if (t == 0 and n == 1 and data[0] == 0) return .undef;
  136. return .{ .ext = .{ .type = t, .data = data } };
  137. }
  138. fn readValue(self: *Decoder) Error!Value {
  139. try self.need(1);
  140. const b = self.bytes[self.pos];
  141. self.pos += 1;
  142. switch (b) {
  143. 0x00...0x7f => return .{ .number = @floatFromInt(b) },
  144. 0x80...0x8f => return self.readMap(b & 0x0f),
  145. 0x90...0x9f => return self.readArray(b & 0x0f),
  146. 0xa0...0xbf => return .{ .str = try self.dupSlice(b & 0x1f) },
  147. 0xc0 => return .nil,
  148. 0xc1 => return Error.InvalidFormat,
  149. 0xc2 => return .{ .boolean = false },
  150. 0xc3 => return .{ .boolean = true },
  151. 0xc4 => return .{ .bin = try self.dupSlice(try self.readBigInt(u8)) },
  152. 0xc5 => return .{ .bin = try self.dupSlice(try self.readBigInt(u16)) },
  153. 0xc6 => return .{ .bin = try self.dupSlice(try self.readBigInt(u32)) },
  154. 0xc7 => return self.readExt(try self.readBigInt(u8)),
  155. 0xc8 => return self.readExt(try self.readBigInt(u16)),
  156. 0xc9 => return self.readExt(try self.readBigInt(u32)),
  157. 0xca => {
  158. const bits = try self.readBigInt(u32);
  159. return .{ .number = @as(f32, @bitCast(bits)) };
  160. },
  161. 0xcb => {
  162. const bits = try self.readBigInt(u64);
  163. return .{ .number = @bitCast(bits) };
  164. },
  165. 0xcc => return .{ .number = @floatFromInt(try self.readBigInt(u8)) },
  166. 0xcd => return .{ .number = @floatFromInt(try self.readBigInt(u16)) },
  167. 0xce => return .{ .number = @floatFromInt(try self.readBigInt(u32)) },
  168. 0xcf => return .{ .number = @floatFromInt(try self.readBigInt(u64)) },
  169. 0xd0 => return .{ .number = @floatFromInt(@as(i8, @bitCast(try self.readBigInt(u8)))) },
  170. 0xd1 => return .{ .number = @floatFromInt(@as(i16, @bitCast(try self.readBigInt(u16)))) },
  171. 0xd2 => return .{ .number = @floatFromInt(@as(i32, @bitCast(try self.readBigInt(u32)))) },
  172. 0xd3 => return .{ .number = @floatFromInt(@as(i64, @bitCast(try self.readBigInt(u64)))) },
  173. 0xd4 => return self.readExt(1),
  174. 0xd5 => return self.readExt(2),
  175. 0xd6 => return self.readExt(4),
  176. 0xd7 => return self.readExt(8),
  177. 0xd8 => return self.readExt(16),
  178. 0xd9 => return .{ .str = try self.dupSlice(try self.readBigInt(u8)) },
  179. 0xda => return .{ .str = try self.dupSlice(try self.readBigInt(u16)) },
  180. 0xdb => return .{ .str = try self.dupSlice(try self.readBigInt(u32)) },
  181. 0xdc => return self.readArray(try self.readBigInt(u16)),
  182. 0xdd => return self.readArray(try self.readBigInt(u32)),
  183. 0xde => return self.readMap(try self.readBigInt(u16)),
  184. 0xdf => return self.readMap(try self.readBigInt(u32)),
  185. 0xe0...0xff => return .{ .number = @floatFromInt(@as(i8, @bitCast(b))) },
  186. }
  187. }
  188. };
  189. /// Encode a Value in the canonical msgpackr form (see header comment).
  190. pub fn encode(alloc: std.mem.Allocator, value: Value) Error![]u8 {
  191. var buf: std.ArrayList(u8) = .empty;
  192. errdefer buf.deinit(alloc);
  193. try encodeInto(alloc, &buf, value);
  194. return buf.toOwnedSlice(alloc) catch Error.OutOfMemory;
  195. }
  196. pub fn encodeInto(alloc: std.mem.Allocator, buf: *std.ArrayList(u8), value: Value) Error!void {
  197. const w = Writer{ .alloc = alloc, .buf = buf };
  198. try w.writeValue(value);
  199. }
  200. const Writer = struct {
  201. alloc: std.mem.Allocator,
  202. buf: *std.ArrayList(u8),
  203. fn byte(self: Writer, b: u8) Error!void {
  204. self.buf.append(self.alloc, b) catch return Error.OutOfMemory;
  205. }
  206. fn raw(self: Writer, s: []const u8) Error!void {
  207. self.buf.appendSlice(self.alloc, s) catch return Error.OutOfMemory;
  208. }
  209. fn big(self: Writer, comptime T: type, v: T) Error!void {
  210. var tmp: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
  211. std.mem.writeInt(T, &tmp, v, .big);
  212. try self.raw(&tmp);
  213. }
  214. fn writeNumber(self: Writer, v: f64) Error!void {
  215. // msgpackr rule (measured): integer-valued and within [-2^31, 2^32) →
  216. // smallest int family; else float64. -0 encodes as fixint 0.
  217. if (v == @floor(v) and v >= -2147483648.0 and v <= 4294967295.0) {
  218. if (v >= 0) {
  219. const u: u32 = @intFromFloat(v);
  220. if (u < 0x80) return self.byte(@intCast(u));
  221. if (u <= 0xff) {
  222. try self.byte(0xcc);
  223. return self.byte(@intCast(u));
  224. }
  225. if (u <= 0xffff) {
  226. try self.byte(0xcd);
  227. return self.big(u16, @intCast(u));
  228. }
  229. try self.byte(0xce);
  230. return self.big(u32, u);
  231. } else {
  232. const i: i32 = @intFromFloat(v);
  233. if (i >= -32) return self.byte(@bitCast(@as(i8, @intCast(i))));
  234. if (i >= -128) {
  235. try self.byte(0xd0);
  236. return self.byte(@bitCast(@as(i8, @intCast(i))));
  237. }
  238. if (i >= -32768) {
  239. try self.byte(0xd1);
  240. return self.big(u16, @bitCast(@as(i16, @intCast(i))));
  241. }
  242. try self.byte(0xd2);
  243. return self.big(u32, @bitCast(i));
  244. }
  245. }
  246. try self.byte(0xcb);
  247. return self.big(u64, @bitCast(v));
  248. }
  249. fn writeStr(self: Writer, s: []const u8) Error!void {
  250. if (s.len <= 31) {
  251. try self.byte(0xa0 | @as(u8, @intCast(s.len)));
  252. } else if (s.len <= 0xff) {
  253. try self.byte(0xd9);
  254. try self.byte(@intCast(s.len));
  255. } else if (s.len <= 0xffff) {
  256. try self.byte(0xda);
  257. try self.big(u16, @intCast(s.len));
  258. } else {
  259. try self.byte(0xdb);
  260. try self.big(u32, @intCast(s.len));
  261. }
  262. return self.raw(s);
  263. }
  264. fn writeValue(self: Writer, value: Value) Error!void {
  265. switch (value) {
  266. .nil => try self.byte(0xc0),
  267. .undef => try self.raw(&[_]u8{ 0xd4, 0x00, 0x00 }),
  268. .boolean => |b| try self.byte(if (b) 0xc3 else 0xc2),
  269. .number => |n| try self.writeNumber(n),
  270. .str => |s| try self.writeStr(s),
  271. .bin => |s| {
  272. if (s.len <= 0xff) {
  273. try self.byte(0xc4);
  274. try self.byte(@intCast(s.len));
  275. } else if (s.len <= 0xffff) {
  276. try self.byte(0xc5);
  277. try self.big(u16, @intCast(s.len));
  278. } else {
  279. try self.byte(0xc6);
  280. try self.big(u32, @intCast(s.len));
  281. }
  282. try self.raw(s);
  283. },
  284. .array => |items| {
  285. if (items.len <= 15) {
  286. try self.byte(0x90 | @as(u8, @intCast(items.len)));
  287. } else if (items.len <= 0xffff) {
  288. try self.byte(0xdc);
  289. try self.big(u16, @intCast(items.len));
  290. } else {
  291. try self.byte(0xdd);
  292. try self.big(u32, @intCast(items.len));
  293. }
  294. for (items) |it| try self.writeValue(it);
  295. },
  296. .map => |entries| {
  297. // msgpackr: objects are ALWAYS map16, >65535 keys throws.
  298. if (entries.len > 0xffff) return Error.MapTooLarge;
  299. try self.byte(0xde);
  300. try self.big(u16, @intCast(entries.len));
  301. for (entries) |e| {
  302. try self.writeValue(e.key);
  303. try self.writeValue(e.value);
  304. }
  305. },
  306. .ext => |e| {
  307. switch (e.data.len) {
  308. 1 => try self.byte(0xd4),
  309. 2 => try self.byte(0xd5),
  310. 4 => try self.byte(0xd6),
  311. 8 => try self.byte(0xd7),
  312. 16 => try self.byte(0xd8),
  313. else => {
  314. if (e.data.len <= 0xff) {
  315. try self.byte(0xc7);
  316. try self.byte(@intCast(e.data.len));
  317. } else if (e.data.len <= 0xffff) {
  318. try self.byte(0xc8);
  319. try self.big(u16, @intCast(e.data.len));
  320. } else {
  321. try self.byte(0xc9);
  322. try self.big(u32, @intCast(e.data.len));
  323. }
  324. },
  325. }
  326. try self.byte(@bitCast(e.type));
  327. try self.raw(e.data);
  328. },
  329. }
  330. }
  331. };
  332. // =========================================================================
  333. // mpackdb record framing: [4-byte LE total size incl. prefix][msgpack]
  334. // =========================================================================
  335. /// Serialize a value with mpackdb's 4-byte little-endian length prefix.
  336. pub fn serialize(alloc: std.mem.Allocator, value: Value) Error![]u8 {
  337. var buf: std.ArrayList(u8) = .empty;
  338. errdefer buf.deinit(alloc);
  339. buf.appendSlice(alloc, &[_]u8{ 0, 0, 0, 0 }) catch return Error.OutOfMemory;
  340. try encodeInto(alloc, &buf, value);
  341. const total: u32 = @intCast(buf.items.len);
  342. std.mem.writeInt(u32, buf.items[0..4], total, .little);
  343. return buf.toOwnedSlice(alloc) catch Error.OutOfMemory;
  344. }
  345. /// Read the length prefix of a framed record (total size incl. the 4 bytes).
  346. pub fn readSize(bytes: []const u8) Error!u32 {
  347. if (bytes.len < 4) return Error.Truncated;
  348. return std.mem.readInt(u32, bytes[0..4], .little);
  349. }
  350. /// Deserialize a framed record ([4-byte LE size][msgpack]).
  351. pub fn deserializeFramed(alloc: std.mem.Allocator, bytes: []const u8) Error!Value {
  352. const total = try readSize(bytes);
  353. if (total < 5 or total > bytes.len) return Error.Truncated;
  354. const d = try decode(alloc, bytes[4..total]);
  355. return d.value;
  356. }
  357. // =========================================================================
  358. // Tests
  359. // =========================================================================
  360. const testing = std.testing;
  361. /// Linux-syscall file read (plugins avoid std.Io plumbing; same pattern as
  362. /// plugins/fs/fs.zig). Returns null when the file cannot be opened.
  363. fn readFileLinux(alloc: std.mem.Allocator, path: []const u8) ?[]u8 {
  364. const linux = std.os.linux;
  365. const path_z = alloc.dupeZ(u8, path) catch return null;
  366. defer alloc.free(path_z);
  367. const rc = linux.open(path_z, .{}, 0);
  368. const fd: i32 = @bitCast(@as(u32, @truncate(rc)));
  369. if (fd < 0) return null;
  370. defer _ = linux.close(fd);
  371. var content: std.ArrayList(u8) = .empty;
  372. var buf: [65536]u8 = undefined;
  373. while (true) {
  374. const n = linux.read(fd, &buf, buf.len);
  375. if (@as(isize, @bitCast(n)) <= 0) break;
  376. content.appendSlice(alloc, buf[0..n]) catch return null;
  377. }
  378. return content.toOwnedSlice(alloc) catch null;
  379. }
  380. test "corpus: decode + canonical re-encode is byte-identical (msgpackr 1.11.5)" {
  381. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  382. defer arena.deinit();
  383. const alloc = arena.allocator();
  384. // Corpus lives next to this file; zig build test runs with cwd native/.
  385. const candidates = [_][]const u8{
  386. "../plugins/mpackdb/testdata/corpus.json",
  387. "plugins/mpackdb/testdata/corpus.json",
  388. };
  389. var content: ?[]u8 = null;
  390. for (candidates) |p| {
  391. content = readFileLinux(alloc, p) orelse continue;
  392. break;
  393. }
  394. const json = content orelse return error.CorpusNotFound;
  395. const parsed = try std.json.parseFromSlice(std.json.Value, alloc, json, .{});
  396. const arr = parsed.value.array;
  397. try testing.expect(arr.items.len >= 70);
  398. var checked: usize = 0;
  399. for (arr.items) |item| {
  400. const obj = item.object;
  401. const hex = obj.get("hex").?.string;
  402. const framed = if (obj.get("framed")) |f| f.bool else false;
  403. const bytes = try alloc.alloc(u8, hex.len / 2);
  404. _ = try std.fmt.hexToBytes(bytes, hex);
  405. if (framed) {
  406. // Frame round-trip: parse prefix, decode, re-serialize, compare all.
  407. const total = try readSize(bytes);
  408. try testing.expectEqual(bytes.len, @as(usize, total));
  409. const v = try deserializeFramed(alloc, bytes);
  410. const re = try serialize(alloc, v);
  411. try testing.expectEqualSlices(u8, bytes, re);
  412. } else {
  413. const d = try decode(alloc, bytes);
  414. try testing.expectEqual(bytes.len, d.len);
  415. const re = try encode(alloc, d.value);
  416. try testing.expectEqualSlices(u8, bytes, re);
  417. }
  418. checked += 1;
  419. }
  420. try testing.expect(checked == arr.items.len);
  421. }
  422. test "decode spot checks" {
  423. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  424. defer arena.deinit();
  425. const alloc = arena.allocator();
  426. // {name:'Alice', age:30} as msgpackr encodes it (map16)
  427. var bytes: [19]u8 = undefined;
  428. _ = try std.fmt.hexToBytes(&bytes, "de0002a46e616d65a5416c696365a36167651e");
  429. const d = try decode(alloc, &bytes);
  430. const name = d.value.get("name").?;
  431. try testing.expectEqualStrings("Alice", name.str);
  432. try testing.expectEqual(@as(f64, 30), d.value.get("age").?.number);
  433. }
  434. test "decode non-canonical families (full spec)" {
  435. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  436. defer arena.deinit();
  437. const alloc = arena.allocator();
  438. // fixmap {a:1}
  439. var b1: [4]u8 = undefined;
  440. _ = try std.fmt.hexToBytes(&b1, "81a16101");
  441. const v1 = try decode(alloc, &b1);
  442. try testing.expectEqual(@as(f64, 1), v1.value.get("a").?.number);
  443. // map32 {a:1}
  444. var b2: [8]u8 = undefined;
  445. _ = try std.fmt.hexToBytes(&b2, "df00000001a16101");
  446. const v2 = try decode(alloc, &b2);
  447. try testing.expectEqual(@as(f64, 1), v2.value.get("a").?.number);
  448. // int64 -1 (d3 ffffffffffffffff)
  449. var b3: [9]u8 = undefined;
  450. _ = try std.fmt.hexToBytes(&b3, "d3ffffffffffffffff");
  451. const v3 = try decode(alloc, &b3);
  452. try testing.expectEqual(@as(f64, -1), v3.value.number);
  453. // uint64 2^32 (cf 0000000100000000)
  454. var b4: [9]u8 = undefined;
  455. _ = try std.fmt.hexToBytes(&b4, "cf0000000100000000");
  456. const v4 = try decode(alloc, &b4);
  457. try testing.expectEqual(@as(f64, 4294967296), v4.value.number);
  458. // float32 0.5 (ca 3f000000)
  459. var b5: [5]u8 = undefined;
  460. _ = try std.fmt.hexToBytes(&b5, "ca3f000000");
  461. const v5 = try decode(alloc, &b5);
  462. try testing.expectEqual(@as(f64, 0.5), v5.value.number);
  463. // 0xc1 is invalid
  464. const bad = [_]u8{0xc1};
  465. try testing.expectError(Error.InvalidFormat, decode(alloc, &bad));
  466. }
  467. test "undefined is distinct and byte-exact" {
  468. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  469. defer arena.deinit();
  470. const alloc = arena.allocator();
  471. const bytes = [_]u8{ 0xd4, 0x00, 0x00 };
  472. const d = try decode(alloc, &bytes);
  473. try testing.expect(d.value == .undef);
  474. const re = try encode(alloc, d.value);
  475. try testing.expectEqualSlices(u8, &bytes, re);
  476. }
  477. test "timestamp ext interpretation" {
  478. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  479. defer arena.deinit();
  480. const alloc = arena.allocator();
  481. // date whole sec: d6 ff 66a39486 → 1721996422000 ms
  482. var b: [6]u8 = undefined;
  483. _ = try std.fmt.hexToBytes(&b, "d6ff66a39486");
  484. const d = try decode(alloc, &b);
  485. try testing.expectEqual(@as(f64, 1721996422000), d.value.timestampMs().?);
  486. // date ms: d7 ff 1d53530066a39486 → 1721996422123 ms
  487. var b2: [10]u8 = undefined;
  488. _ = try std.fmt.hexToBytes(&b2, "d7ff1d53530066a39486");
  489. const d2 = try decode(alloc, &b2);
  490. try testing.expectApproxEqAbs(@as(f64, 1721996422123), d2.value.timestampMs().?, 0.001);
  491. }
  492. test "framing" {
  493. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  494. defer arena.deinit();
  495. const alloc = arena.allocator();
  496. var entries = [_]Entry{
  497. .{ .key = .{ .str = "id" }, .value = .{ .number = 1 } },
  498. };
  499. const v = Value{ .map = &entries };
  500. const framed = try serialize(alloc, v);
  501. try testing.expectEqual(@as(u32, @intCast(framed.len)), try readSize(framed));
  502. const back = try deserializeFramed(alloc, framed);
  503. try testing.expectEqual(@as(f64, 1), back.get("id").?.number);
  504. }
  505. test "encode number boundaries match msgpackr" {
  506. var arena = std.heap.ArenaAllocator.init(testing.allocator);
  507. defer arena.deinit();
  508. const alloc = arena.allocator();
  509. const cases = [_]struct { v: f64, hex: []const u8 }{
  510. .{ .v = 0, .hex = "00" },
  511. .{ .v = 127, .hex = "7f" },
  512. .{ .v = 128, .hex = "cc80" },
  513. .{ .v = 256, .hex = "cd0100" },
  514. .{ .v = 65536, .hex = "ce00010000" },
  515. .{ .v = 2147483648, .hex = "ce80000000" },
  516. .{ .v = 4294967296, .hex = "cb41f0000000000000" },
  517. .{ .v = -1, .hex = "ff" },
  518. .{ .v = -32, .hex = "e0" },
  519. .{ .v = -33, .hex = "d0df" },
  520. .{ .v = -129, .hex = "d1ff7f" },
  521. .{ .v = -32769, .hex = "d2ffff7fff" },
  522. .{ .v = -2147483648, .hex = "d280000000" },
  523. .{ .v = -2147483649, .hex = "cbc1e0000000200000" },
  524. .{ .v = 0.5, .hex = "cb3fe0000000000000" },
  525. };
  526. for (cases) |c| {
  527. const enc = try encode(alloc, Value{ .number = c.v });
  528. const expect_bytes = try alloc.alloc(u8, c.hex.len / 2);
  529. _ = try std.fmt.hexToBytes(expect_bytes, c.hex);
  530. try testing.expectEqualSlices(u8, expect_bytes, enc);
  531. }
  532. }

Branches

Latest commits

  • 9bfba36aantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
  • c7bd2645tickets: Hybriel master 73267707 (#122 fixed); compactNow workaround removed (#110 covered)mre
  • 2ab91ee9tickets: gate checks rows appear once (session sync); re-vendor to ff51cf46 stopped on hybriel#122, stays 837fe120mre
  • e01c2b1dtickets#24: installable app (manifest, service worker, offline list), own icon; gate waits for the hello's pongmre
  • 752fbb7fdeploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
  • 38bdd5e4deploy.sh: never send .git or .gitignore to Byrodinmre
  • f12fa1bcState of 2026-09-27, before the move to gitoriamre