diff --git a/src/internal/serve/wire.zig b/src/internal/serve/wire.zig index 46841fd..599d1f9 100644 --- a/src/internal/serve/wire.zig +++ b/src/internal/serve/wire.zig @@ -513,8 +513,18 @@ pub fn encodeSyncV2(w: *std.Io.Writer, args: struct { try s.write(seq); try s.objectField("sync"); try s.beginObject(); + // lexicon "bytes" value: the {"$bytes":""} sentinel + // (atmos cbor.AppendJSONBytes) — a bare base64 string is rejected by + // conformant decoders (caught live by the d4dd2f0 differential oracle) try s.objectField("blocks"); - try writeBase64String(&s, args.blocks); + try s.beginObject(); + try s.objectField("$bytes"); + try s.beginWriteRaw(); + try s.writer.writeByte('"'); + try writeBase64NoPad(s.writer, args.blocks); + try s.writer.writeByte('"'); + s.endWriteRaw(); + try s.endObject(); try s.objectField("did"); try writeString(&s, args.did); try s.objectField("rev"); @@ -534,22 +544,6 @@ pub fn encodeSyncV2(w: *std.Io.Writer, args: struct { try w.writeByte('}'); } -/// base64 std WITH padding, matching Go's []byte JSON encoding -fn writeBase64String(s: *std.json.Stringify, data: []const u8) !void { - try s.beginWriteRaw(); - try s.writer.writeByte('"'); - const encoder = std.base64.standard.Encoder; - var chunk: [4]u8 = undefined; - var i: usize = 0; - while (i < data.len) : (i += 3) { - const n = @min(3, data.len - i); - const out = encoder.encode(&chunk, data[i..][0..n]); - try s.writer.writeAll(out); - } - try s.writer.writeByte('"'); - s.endWriteRaw(); -} - /// #info advisory (seq-less): {"$type":"message","payload":{"$type":"...#info","message"?,"name"}} pub fn encodeInfoV2(w: *std.Io.Writer, name: []const u8, message: ?[]const u8) !void { try w.writeAll(message_frame_prefix);