Skip to content

Commit 97b3615

Browse files
committed
Fix deserialization of persisted byte maps
1 parent 3edfc50 commit 97b3615

3 files changed

Lines changed: 80 additions & 0 deletions

File tree

.changeset/blue-bytes-restore.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"capnweb": patch
3+
---
4+
5+
Restore valid numeric-key byte maps produced by JSON persistence.

__tests__/index.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4211,3 +4211,66 @@ describe("deserialization and transport correctness", () => {
42114211
expect(sentReason).toBe("a".repeat(MAX_CLOSE_REASON_BYTES - 1));
42124212
});
42134213
});
4214+
4215+
describe("restored numeric byte maps", () => {
4216+
it("restores contiguous bytes including both boundaries", () => {
4217+
expect(deserialize('["bytes",{"2":255,"0":0,"1":123}]'))
4218+
.toEqual(new Uint8Array([0, 123, 255]));
4219+
});
4220+
4221+
it("restores empty bytes", () => {
4222+
expect(deserialize('["bytes",{}]')).toEqual(new Uint8Array());
4223+
});
4224+
4225+
it.each([
4226+
{"0": 123, "2": 34},
4227+
{"1": 123},
4228+
{"00": 123},
4229+
{"0": -1},
4230+
{"0": 256},
4231+
{"0": 1.5},
4232+
{"0": "123"},
4233+
{"0": null},
4234+
{"data": 123},
4235+
{"0": 123, "length": 1},
4236+
[123],
4237+
null,
4238+
])("rejects malformed byte payload %j", (payload) => {
4239+
expect(() => deserialize(JSON.stringify(["bytes", payload])))
4240+
.toThrow("unknown special value");
4241+
});
4242+
4243+
it("restores a JSON-persisted byte tuple and reserializes it canonically", () => {
4244+
let original = new TextEncoder().encode('{"success":true}');
4245+
let persisted = JSON.stringify({body: ["bytes", original]});
4246+
expect(JSON.parse(persisted).body[1]).not.toBeInstanceOf(Uint8Array);
4247+
let restored = deserialize(persisted);
4248+
expect(restored).toEqual({body: original});
4249+
let continued = deserialize(serialize(restored)) as {body: Uint8Array};
4250+
expect(continued.body).toBeInstanceOf(Uint8Array);
4251+
expect(new Uint8Array(continued.body)).toEqual(original);
4252+
});
4253+
});
4254+
4255+
it("continues RPC calls after raw bytes cross a JSON boundary", async () => {
4256+
class JsonByteTransport extends ObjectTestTransport {
4257+
send(message: unknown): void {
4258+
super.send(JSON.parse(JSON.stringify(message)));
4259+
}
4260+
}
4261+
class EchoBytes extends RpcTarget {
4262+
echo(bytes: Uint8Array): Uint8Array {
4263+
expect(bytes).toBeInstanceOf(Uint8Array);
4264+
return bytes;
4265+
}
4266+
}
4267+
let clientTransport = new JsonByteTransport(undefined, "jsonCompatibleWithBytes");
4268+
let serverTransport = new JsonByteTransport(clientTransport, "jsonCompatibleWithBytes");
4269+
let client = new RpcSession<EchoBytes>(clientTransport);
4270+
new RpcSession(serverTransport, new EchoBytes());
4271+
using stub = client.getRemoteMain();
4272+
expect(await stub.echo(new Uint8Array([123, 34, 255])))
4273+
.toEqual(new Uint8Array([123, 34, 255]));
4274+
expect(await stub.echo(new Uint8Array([0, 1])))
4275+
.toEqual(new Uint8Array([0, 1]));
4276+
});

src/serialize.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,18 @@ export class Evaluator {
863863
bytes[i] = bs.charCodeAt(i);
864864
}
865865
}
866+
} else if (value[1] !== null && typeof value[1] === "object" &&
867+
(Object.getPrototypeOf(value[1]) === Object.prototype ||
868+
Object.getPrototypeOf(value[1]) === null)) {
869+
// JSON persistence can turn a Uint8Array into an object of indexed bytes.
870+
// Validate before conversion: Uint8Array.from() would truncate invalid values.
871+
let entries = Object.entries(value[1]);
872+
if (!entries.every(([key, byte], index) =>
873+
key === String(index) && typeof byte === "number" &&
874+
Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
875+
break;
876+
}
877+
bytes = Uint8Array.from(entries.map(([, byte]) => byte as number));
866878
} else {
867879
break;
868880
}

0 commit comments

Comments
 (0)