Skip to content

Commit 11c5db0

Browse files
authored
V2: Relax validation in BinaryWriter (#877)
1 parent 2e4a5f0 commit 11c5db0

File tree

5 files changed

+111
-42
lines changed

5 files changed

+111
-42
lines changed

packages/bundle-size/README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ usually do. We repeat this for an increasing number of files.
1616
<!--- TABLE-START -->
1717
| code generator | files | bundle size | minified | compressed |
1818
|-----------------|----------|------------------------:|-----------------------:|-------------------:|
19-
| protobuf-es | 1 | 123,019 b | 63,933 b | 14,930 b |
20-
| protobuf-es | 4 | 125,214 b | 65,443 b | 15,592 b |
21-
| protobuf-es | 8 | 127,992 b | 67,214 b | 16,104 b |
22-
| protobuf-es | 16 | 138,500 b | 75,195 b | 18,432 b |
23-
| protobuf-es | 32 | 166,395 b | 97,210 b | 23,848 b |
19+
| protobuf-es | 1 | 123,350 b | 64,136 b | 14,970 b |
20+
| protobuf-es | 4 | 125,545 b | 65,646 b | 15,662 b |
21+
| protobuf-es | 8 | 128,323 b | 67,417 b | 16,196 b |
22+
| protobuf-es | 16 | 138,831 b | 75,398 b | 18,504 b |
23+
| protobuf-es | 32 | 166,726 b | 97,413 b | 23,953 b |
2424
| protobuf-javascript | 1 | 339,613 b | 255,820 b | 42,481 b |
2525
| protobuf-javascript | 4 | 366,281 b | 271,092 b | 43,912 b |
2626
| protobuf-javascript | 8 | 388,324 b | 283,409 b | 45,038 b |

packages/bundle-size/chart.svg

+6-6
Loading

packages/protobuf-test/src/wire/binary-encoding.test.ts

+61-18
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe("BinaryWriter", () => {
3131
expect(user.firstName).toBe("Homer");
3232
expect(user.active).toBe(true);
3333
});
34-
describe("float32", () => {
34+
describe("float32()", () => {
3535
it.each([
3636
1024,
3737
3.142,
@@ -41,13 +41,20 @@ describe("BinaryWriter", () => {
4141
Number.NEGATIVE_INFINITY,
4242
Number.NaN,
4343
])("should encode %s", (val) => {
44-
expect(new BinaryWriter().float(val).finish().length).toBeGreaterThan(0);
44+
const bytes = new BinaryWriter().float(val).finish();
45+
expect(bytes.length).toBeGreaterThan(0);
46+
// @ts-expect-error test string
47+
const bytesStr = new BinaryWriter().float(val.toString()).finish();
48+
expect(bytesStr.length).toBeGreaterThan(0);
49+
expect(bytesStr).toStrictEqual(bytes);
4550
});
4651
it.each([
47-
{ val: "123", err: /^invalid float32: string/ },
48-
{ val: true, err: /^invalid float32: bool/ },
52+
{ val: null, err: "invalid float32: object" },
53+
{ val: new Date(), err: "invalid float32: object" },
54+
{ val: undefined, err: "invalid float32: undefined" },
55+
{ val: true, err: "invalid float32: bool" },
4956
])("should error for wrong type $val", ({ val, err }) => {
50-
// @ts-expect-error TS2345
57+
// @ts-expect-error test wrong type
5158
expect(() => new BinaryWriter().float(val)).toThrow(err);
5259
});
5360
it.each([Number.MAX_VALUE, -Number.MAX_VALUE])(
@@ -56,40 +63,76 @@ describe("BinaryWriter", () => {
5663
expect(() => new BinaryWriter().float(val)).toThrow(
5764
/^invalid float32: .*/,
5865
);
66+
// @ts-expect-error test string
67+
expect(() => new BinaryWriter().float(val.toString())).toThrow(
68+
/^invalid float32: .*/,
69+
);
5970
},
6071
);
6172
});
62-
describe("int32", () => {
73+
// sfixed32, sint32, and int32 are signed 32-bit integers, just with different encoding
74+
describe.each(["sfixed32", "sint32", "int32"] as const)("%s()", (type) => {
6375
it.each([-0x80000000, 1024, 0x7fffffff])("should encode %s", (val) => {
64-
expect(new BinaryWriter().int32(val).finish().length).toBeGreaterThan(0);
76+
const bytes = new BinaryWriter()[type](val).finish();
77+
expect(bytes.length).toBeGreaterThan(0);
78+
// @ts-expect-error test string
79+
const bytesStr = new BinaryWriter()[type](val.toString()).finish();
80+
expect(bytesStr.length).toBeGreaterThan(0);
81+
expect(bytesStr).toStrictEqual(bytes);
6582
});
6683
it.each([
67-
{ val: "123", err: /^invalid int32: string/ },
68-
{ val: true, err: /^invalid int32: bool/ },
84+
{ val: null, err: "invalid int32: object" },
85+
{ val: new Date(), err: "invalid int32: object" },
86+
{ val: undefined, err: "invalid int32: undefined" },
87+
{ val: true, err: "invalid int32: bool" },
6988
])("should error for wrong type $val", ({ val, err }) => {
7089
// @ts-expect-error TS2345
71-
expect(() => new BinaryWriter().int32(val)).toThrow(err);
90+
expect(() => new BinaryWriter()[type](val)).toThrow(err);
7291
});
73-
it.each([0x7fffffff + 1, -0x80000000 - 1])(
92+
it.each([0x7fffffff + 1, -0x80000000 - 1, 3.142])(
7493
"should error for value out of range %s",
7594
(val) => {
76-
expect(() => new BinaryWriter().int32(val)).toThrow(
95+
expect(() => new BinaryWriter()[type](val)).toThrow(
96+
/^invalid int32: .*/,
97+
);
98+
// @ts-expect-error test string
99+
expect(() => new BinaryWriter()[type](val.toString())).toThrow(
77100
/^invalid int32: .*/,
78101
);
79102
},
80103
);
81104
});
82-
describe("uint32", () => {
105+
// fixed32 and uint32 are unsigned 32-bit integers, just with different encoding
106+
describe.each(["fixed32", "uint32"] as const)("%s()", (type) => {
83107
it.each([0, 1024, 0xffffffff])("should encode %s", (val) => {
84-
expect(new BinaryWriter().uint32(val).finish().length).toBeGreaterThan(0);
108+
const bytes = new BinaryWriter()[type](val).finish();
109+
expect(bytes.length).toBeGreaterThan(0);
110+
// @ts-expect-error test string
111+
const bytesStr = new BinaryWriter()[type](val.toString()).finish();
112+
expect(bytesStr.length).toBeGreaterThan(0);
113+
expect(bytesStr).toStrictEqual(bytes);
85114
});
86115
it.each([
87-
{ val: "123", err: /^invalid uint32: string/ },
88-
{ val: true, err: /^invalid uint32: bool/ },
89-
])("should error for wrong type$val", ({ val, err }) => {
116+
{ val: null, err: `invalid uint32: object` },
117+
{ val: new Date(), err: `invalid uint32: object` },
118+
{ val: undefined, err: `invalid uint32: undefined` },
119+
{ val: true, err: `invalid uint32: bool` },
120+
])("should error for wrong type $val", ({ val, err }) => {
90121
// @ts-expect-error TS2345
91-
expect(() => new BinaryWriter().uint32(val)).toThrow(err);
122+
expect(() => new BinaryWriter()[type](val)).toThrow(err);
92123
});
124+
it.each([0xffffffff + 1, -1, 3.142])(
125+
"should error for value out of range %s",
126+
(val) => {
127+
expect(() => new BinaryWriter()[type](val)).toThrow(
128+
/^invalid uint32: .*/,
129+
);
130+
// @ts-expect-error test string
131+
expect(() => new BinaryWriter()[type](val.toString())).toThrow(
132+
/^invalid uint32: .*/,
133+
);
134+
},
135+
);
93136
});
94137
});
95138

packages/protobuf/src/reflect/guard.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import type {
2121
} from "./reflect-types.js";
2222
import { unsafeLocal } from "./unsafe.js";
2323
import type { DescField, DescMessage } from "../descriptors.js";
24-
import { isMessage } from "../is-message.js";
2524

2625
export function isObject(arg: unknown): arg is Record<string, unknown> {
2726
return arg !== null && typeof arg == "object" && !Array.isArray(arg);
@@ -102,8 +101,9 @@ export function isReflectMessage(
102101
return (
103102
isObject(arg) &&
104103
unsafeLocal in arg &&
105-
"message" in arg &&
106-
isMessage(arg.message) &&
107-
(messageDesc === undefined || arg.message.$typeName == messageDesc.typeName)
104+
"desc" in arg &&
105+
isObject(arg.desc) &&
106+
arg.desc.kind === "message" &&
107+
(messageDesc === undefined || arg.desc.typeName == messageDesc.typeName)
108108
);
109109
}

packages/protobuf/src/wire/binary-encoding.ts

+35-9
Original file line numberDiff line numberDiff line change
@@ -568,29 +568,55 @@ export class BinaryReader {
568568
}
569569

570570
/**
571-
* Assert a valid signed protobuf 32-bit integer.
571+
* Assert a valid signed protobuf 32-bit integer as a number or string.
572572
*/
573573
function assertInt32(arg: unknown): asserts arg is number {
574-
if (typeof arg !== "number") throw new Error("invalid int32: " + typeof arg);
575-
if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN)
574+
if (typeof arg == "string") {
575+
arg = Number(arg);
576+
} else if (typeof arg != "number") {
577+
throw new Error("invalid int32: " + typeof arg);
578+
}
579+
if (
580+
!Number.isInteger(arg) ||
581+
(arg as number) > INT32_MAX ||
582+
(arg as number) < INT32_MIN
583+
)
576584
throw new Error("invalid int32: " + arg);
577585
}
578586

579587
/**
580-
* Assert a valid unsigned protobuf 32-bit integer.
588+
* Assert a valid unsigned protobuf 32-bit integer as a number or string.
581589
*/
582590
function assertUInt32(arg: unknown): asserts arg is number {
583-
if (typeof arg !== "number") throw new Error("invalid uint32: " + typeof arg);
584-
if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0)
591+
if (typeof arg == "string") {
592+
arg = Number(arg);
593+
} else if (typeof arg != "number") {
594+
throw new Error("invalid uint32: " + typeof arg);
595+
}
596+
if (
597+
!Number.isInteger(arg) ||
598+
(arg as number) > UINT32_MAX ||
599+
(arg as number) < 0
600+
)
585601
throw new Error("invalid uint32: " + arg);
586602
}
587603

588604
/**
589-
* Assert a valid protobuf float value.
605+
* Assert a valid protobuf float value as a number or string.
590606
*/
591607
function assertFloat32(arg: unknown): asserts arg is number {
592-
if (typeof arg !== "number")
608+
if (typeof arg == "string") {
609+
const o = arg;
610+
arg = Number(arg);
611+
if (isNaN(arg as number) && o !== "NaN") {
612+
throw new Error("invalid float32: " + o);
613+
}
614+
} else if (typeof arg != "number") {
593615
throw new Error("invalid float32: " + typeof arg);
594-
if (Number.isFinite(arg) && (arg > FLOAT32_MAX || arg < FLOAT32_MIN))
616+
}
617+
if (
618+
Number.isFinite(arg) &&
619+
((arg as number) > FLOAT32_MAX || (arg as number) < FLOAT32_MIN)
620+
)
595621
throw new Error("invalid float32: " + arg);
596622
}

0 commit comments

Comments
 (0)