Skip to content

Commit f4dcba0

Browse files
authored
fix(core/cbor): calculation of bigInteger offset (#1651)
1 parent d633bf1 commit f4dcba0

File tree

4 files changed

+128
-13
lines changed

4 files changed

+128
-13
lines changed

.changeset/clever-shirts-prove.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+
fix offset calculation when decoding bigInteger in CBOR

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NumericValue } from "@smithy/core/serde";
1+
import { nv } from "@smithy/core/serde";
22
import { toUtf8 } from "@smithy/util-utf8";
33

44
import {
@@ -129,22 +129,44 @@ export function decode(at: Uint32, to: Uint32): CborValueType {
129129
for (let i = start; i < start + length; ++i) {
130130
b = (b << BigInt(8)) | BigInt(payload[i]);
131131
}
132-
133-
_offset = offset + length;
132+
// the new offset is the sum of:
133+
// 1. the local major offset (1)
134+
// 2. the offset of the decoded count of the bigInteger
135+
// 3. the length of the data bytes of the bigInteger
136+
_offset = offset + _offset + length;
134137
return minor === 3 ? -b - BigInt(1) : b;
135138
} else if (minor === 4) {
136139
const decimalFraction = decode(at + offset, to);
137140
const [exponent, mantissa] = decimalFraction;
138-
const s = mantissa.toString();
139-
const numericString = exponent === 0 ? s : s.slice(0, s.length + exponent) + "." + s.slice(exponent);
141+
const normalizer = mantissa < 0 ? -1 : 1;
142+
const mantissaStr = "0".repeat(Math.abs(exponent) + 1) + String(BigInt(normalizer) * BigInt(mantissa));
143+
144+
let numericString: string;
145+
const sign = mantissa < 0 ? "-" : "";
146+
147+
numericString =
148+
exponent === 0
149+
? mantissaStr
150+
: mantissaStr.slice(0, mantissaStr.length + exponent) + "." + mantissaStr.slice(exponent);
151+
numericString = numericString.replace(/^0+/g, "");
152+
if (numericString === "") {
153+
numericString = "0";
154+
}
155+
if (numericString[0] === ".") {
156+
numericString = "0" + numericString;
157+
}
158+
numericString = sign + numericString;
140159

141-
return new NumericValue(numericString, "bigDecimal");
160+
// the new offset is the sum of:
161+
// 1. the local major offset (1)
162+
// 2. the offset of the decoded exponent mantissa pair
163+
_offset = offset + _offset;
164+
return nv(numericString);
142165
} else {
143166
const value = decode(at + offset, to);
144167
const valueOffset = _offset;
145168

146169
_offset = offset + valueOffset;
147-
148170
return tag({ tag: castBigInt(unsignedInt), value });
149171
}
150172
}

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

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NumericValue } from "@smithy/core/serde";
1+
import { NumericValue, nv } from "@smithy/core/serde";
22
import * as fs from "fs";
33
// @ts-ignore
44
import JSONbig from "json-bigint";
@@ -219,12 +219,19 @@ describe("cbor", () => {
219219
name: "object containing big numbers",
220220
data: {
221221
map: {
222-
items: [BigInt(1e80)],
222+
items: [BigInt(1e80), BigInt(1e80), nv("0.0000000001234000000001234"), nv("0.0000000001234000000001234")],
223+
bigint: BigInt(1e80),
224+
bigDecimal: nv("0.0000000001234000000001234"),
223225
},
224226
},
225227
cbor: allocByteArray([
226-
161, 99, 109, 97, 112, 161, 101, 105, 116, 101, 109, 115, 129, 194, 88, 34, 3, 95, 157, 234, 62, 31, 107, 224,
227-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
228+
161, 99, 109, 97, 112, 163, 101, 105, 116, 101, 109, 115, 132, 194, 88, 34, 3, 95, 157, 234, 62, 31, 107, 224,
229+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 88, 34, 3, 95, 157, 234, 62,
230+
31, 107, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 130, 56, 24,
231+
27, 0, 4, 98, 81, 3, 167, 36, 210, 196, 130, 56, 24, 27, 0, 4, 98, 81, 3, 167, 36, 210, 102, 98, 105, 103, 105,
232+
110, 116, 194, 88, 34, 3, 95, 157, 234, 62, 31, 107, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
233+
0, 0, 0, 0, 0, 0, 0, 0, 106, 98, 105, 103, 68, 101, 99, 105, 109, 97, 108, 196, 130, 56, 24, 27, 0, 4, 98, 81,
234+
3, 167, 36, 210,
228235
]),
229236
},
230237
];
@@ -305,6 +312,83 @@ describe("cbor", () => {
305312
}
306313
});
307314

315+
it("should round-trip sequences of big numbers", () => {
316+
const sequence = {
317+
map: {
318+
items1: [
319+
BigInt(1e20),
320+
BigInt(2e30),
321+
BigInt(3e40),
322+
BigInt(4e50),
323+
BigInt(5e60),
324+
BigInt(6e70),
325+
BigInt(7e80),
326+
BigInt(8e90),
327+
],
328+
items2: [
329+
nv("111.00000000000000000001"),
330+
nv("0.000000000000000000002"),
331+
nv("333.0000000000000000000003"),
332+
nv("0.00000000000000000000004"),
333+
nv("-555.000000000000000000000005"),
334+
nv("-0.0000000000000000000000006"),
335+
nv("-777.00000000000000000000000007"),
336+
nv("-0.000000000000000000000000008"),
337+
],
338+
items3: [nv("0.0000000001234000000001234"), nv("0.00000000678678001234"), BigInt(1e20), BigInt(2e30)],
339+
items4: [
340+
BigInt(1e20),
341+
BigInt(2e30),
342+
nv("0.0000000001234000000001234"),
343+
nv("0.0000067867867801234"),
344+
BigInt(1e20),
345+
BigInt(2e30),
346+
nv("0.0000000001234000000001234"),
347+
nv("1.000000000123678678678234"),
348+
],
349+
items5: [
350+
nv("0.0000000001234000000001234"),
351+
nv("0.00006786781234678678678"),
352+
BigInt(1e20),
353+
BigInt(2e30),
354+
nv("0.0000000001234000000001234"),
355+
nv("0.000000000123400000087678634"),
356+
BigInt(1e20),
357+
BigInt(2e30),
358+
],
359+
items6: [
360+
nv("10469069930579305970359073950793057903597035970395069240692049609"),
361+
nv("99130490139501395091035901395031950.4928053468045683958609485649280534680456839586094856"),
362+
nv("1.000135135000103501305000000000004928053468045683958609485649280534680456839586094856"),
363+
nv("1.00013513500010350130500000000000"),
364+
nv("0.0000000001234000000001234"),
365+
nv("0.0000001"),
366+
nv("0.00001"),
367+
nv("0.001"),
368+
nv("0.000000"),
369+
nv("0.0000"),
370+
nv("0.00"),
371+
nv("0.0"),
372+
nv("0"),
373+
nv("-0.1"),
374+
nv("-0.01"),
375+
nv("-0.000000000123400000087678634"),
376+
nv("-0.0000000001234000000876786340000000000000000000000000000"),
377+
nv("-1.000135135000103501305000000000004928053468045683958609485649280534680456839586094856"),
378+
nv("-1.00013513500010350130500000000000"),
379+
nv("-100305096350939057390735093.0000000001234000000001234"),
380+
nv("-104695047960794069730590793057.0"),
381+
nv("-104695047960794069730590793057"),
382+
],
383+
},
384+
};
385+
386+
const serialized = cbor.serialize(sequence);
387+
const deserialized = cbor.deserialize(serialized);
388+
389+
expect(deserialized).toEqual(sequence);
390+
});
391+
308392
it("should throw an error if serializing a tag with missing properties", () => {
309393
expect(() =>
310394
cbor.serialize({

packages/core/src/submodules/serde/value/NumericValue.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,19 @@ export class NumericValue {
5353
return this.string;
5454
}
5555

56-
public [Symbol.hasInstance](object: unknown) {
56+
public static [Symbol.hasInstance](object: unknown) {
5757
if (!object || typeof object !== "object") {
5858
return false;
5959
}
6060
const _nv = object as NumericValue;
61+
const prototypeMatch = NumericValue.prototype.isPrototypeOf(object.constructor?.prototype);
62+
if (prototypeMatch) {
63+
return prototypeMatch;
64+
}
6165
if (typeof _nv.string === "string" && typeof _nv.type === "string" && _nv.constructor?.name === "NumericValue") {
6266
return true;
6367
}
64-
return false;
68+
return prototypeMatch;
6569
}
6670
}
6771

0 commit comments

Comments
 (0)