Skip to content

Commit 84b7afb

Browse files
committed
fix(core): use unique symbol to indicate tags for cbor serialization
1 parent 7f0bf78 commit 84b7afb

File tree

7 files changed

+67
-5
lines changed

7 files changed

+67
-5
lines changed

.changeset/sweet-planets-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/core": patch
3+
---
4+
5+
make CBOR tags more distinct in JS

packages/core/src/submodules/cbor/cbor-decode.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
specialNull,
2727
specialTrue,
2828
specialUndefined,
29+
tagSymbol,
2930
Uint8,
3031
Uint32,
3132
Uint64,
@@ -122,7 +123,7 @@ export function decode(at: Uint32, to: Uint32): CborValueType {
122123
const valueOffset = _offset;
123124

124125
_offset = offset + valueOffset;
125-
return { tag: castBigInt(unsignedInt), value };
126+
return { tag: castBigInt(unsignedInt), value, [tagSymbol]: true };
126127
}
127128
case majorUtf8String:
128129
case majorMap:

packages/core/src/submodules/cbor/cbor-encode.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import {
99
majorMap,
1010
majorNegativeInt64,
1111
majorSpecial,
12+
majorTag,
1213
majorUint64,
1314
majorUnstructuredByteString,
1415
majorUtf8String,
1516
specialFalse,
1617
specialNull,
1718
specialTrue,
19+
tagSymbol,
1820
Uint64,
1921
} from "./cbor-types";
2022
import { alloc } from "./cbor-types";
@@ -179,6 +181,15 @@ export function encode(_input: any): void {
179181
cursor += input.byteLength;
180182
continue;
181183
} else if (typeof input === "object") {
184+
if (input[tagSymbol]) {
185+
if ("tag" in input && "value" in input) {
186+
encodeStack.push(input.value);
187+
encodeHeader(majorTag, input.tag);
188+
continue;
189+
} else {
190+
throw new Error("tag encountered with missing fields, need 'tag' and 'value', found: " + JSON.stringify(input));
191+
}
192+
}
182193
const keys = Object.keys(input);
183194
for (let i = keys.length - 1; i >= 0; --i) {
184195
const key = keys[i];

packages/core/src/submodules/cbor/cbor-types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ export const minorIndefinite = 31; // 0b11111
6666
export function alloc(size: number) {
6767
return typeof Buffer !== "undefined" ? Buffer.alloc(size) : new Uint8Array(size);
6868
}
69+
70+
/**
71+
* @public
72+
*
73+
* The presence of this symbol as an object key indicates it should be considered a tag
74+
* for CBOR serialization purposes.
75+
*
76+
* The object must also have the properties "tag" and "value".
77+
*/
78+
export const tagSymbol = Symbol("@smithy/core/cbor::tagSymbol");

packages/core/src/submodules/cbor/cbor.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { describe, expect, test as it } from "vitest";
55

66
import { cbor } from "./cbor";
77
import { bytesToFloat16 } from "./cbor-decode";
8+
import { tagSymbol } from "./cbor-types";
9+
import { dateToTag } from "./parseCborBody";
810

911
// syntax is ESM but the test target is CJS.
1012
const here = __dirname;
@@ -179,6 +181,18 @@ describe("cbor", () => {
179181
161, 103, 109, 101, 115, 115, 97, 103, 101, 108, 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100,
180182
]),
181183
},
184+
{
185+
name: "date=0",
186+
data: dateToTag(new Date(0)),
187+
// major tag (6 or 110), minor 1 (timestamp)
188+
cbor: allocByteArray([0b11000001, 0]),
189+
},
190+
{
191+
name: "date=turn of the millenium",
192+
data: dateToTag(new Date(946684799999)),
193+
// major tag (6 or 110), minor 1 (timestamp)
194+
cbor: allocByteArray([0b11000001, 251, 65, 204, 54, 161, 191, 255, 223, 59]),
195+
},
182196
{
183197
name: "complex object",
184198
data: {
@@ -202,7 +216,7 @@ describe("cbor", () => {
202216
];
203217

204218
const toBytes = (hex: string) => {
205-
const bytes = [];
219+
const bytes = [] as number[];
206220
hex.replace(/../g, (substr: string): string => {
207221
bytes.push(parseInt(substr, 16));
208222
return substr;
@@ -211,6 +225,19 @@ describe("cbor", () => {
211225
};
212226

213227
describe("locally curated scenarios", () => {
228+
it("should throw an error if serializing a tag with missing properties", () => {
229+
expect(() =>
230+
cbor.serialize({
231+
myTag: {
232+
[tagSymbol]: true,
233+
tag: 1,
234+
// value: undefined
235+
},
236+
})
237+
).toThrowError("tag encountered with missing fields, need 'tag' and 'value', found: {\"tag\":1}");
238+
cbor.resizeEncodingBuffer(0);
239+
});
240+
214241
for (const { name, data, cbor: cbor_representation } of examples) {
215242
it(`should encode for ${name}`, async () => {
216243
const serialized = cbor.serialize(data);
@@ -292,6 +319,7 @@ describe("cbor", () => {
292319
return {
293320
tag: id,
294321
value: translateTestData(tagValue),
322+
[tagSymbol]: true,
295323
};
296324
default:
297325
throw new Error(`Unrecognized test scenario <expect> type ${type}.`);

packages/core/src/submodules/cbor/cbor.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ export const cbor = {
1717
return decode(0, payload.length);
1818
},
1919
serialize(input: any) {
20-
encode(input);
21-
return toUint8Array();
20+
try {
21+
encode(input);
22+
return toUint8Array();
23+
} catch (e) {
24+
toUint8Array(); // resets cursor.
25+
throw e;
26+
}
2227
},
2328
/**
2429
* @public

packages/core/src/submodules/cbor/parseCborBody.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { HeaderBag as __HeaderBag, HttpResponse, SerdeContext as __SerdeContext,
44
import { calculateBodyLength } from "@smithy/util-body-length-browser";
55

66
import { cbor } from "./cbor";
7+
import { tagSymbol } from "./cbor-types";
78

89
/**
910
* @internal
@@ -27,8 +28,9 @@ export const parseCborBody = (streamBody: any, context: SerdeContext): any => {
2728
/**
2829
* @internal
2930
*/
30-
export const dateToTag = (date: Date): { tag: 1; value: number } => {
31+
export const dateToTag = (date: Date): { tag: 1; value: number; [tagSymbol]: true } => {
3132
return {
33+
[tagSymbol]: true,
3234
tag: 1,
3335
value: date.getTime() / 1000,
3436
};

0 commit comments

Comments
 (0)