Skip to content

Commit 72e10ea

Browse files
committed
fix(core/cbor): calculation of bigInteger offset
1 parent d633bf1 commit 72e10ea

File tree

4 files changed

+111
-13
lines changed

4 files changed

+111
-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: 26 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,41 @@ 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.startsWith(".")) {
153+
numericString = "0" + numericString;
154+
}
155+
numericString = sign + numericString;
140156

141-
return new NumericValue(numericString, "bigDecimal");
157+
// the new offset is the sum of:
158+
// 1. the local major offset (1)
159+
// 2. the offset of the decoded exponent mantissa pair
160+
_offset = offset + _offset;
161+
return nv(numericString);
142162
} else {
143163
const value = decode(at + offset, to);
144164
const valueOffset = _offset;
145165

146166
_offset = offset + valueOffset;
147-
148167
return tag({ tag: castBigInt(unsignedInt), value });
149168
}
150169
}

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

Lines changed: 74 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,69 @@ 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("0.0000000001234000000001234"),
361+
nv("0.00006786781234678678678"),
362+
nv("0.0000000001234000000001234"),
363+
nv("0.000000000123400000087678634"),
364+
nv("0.0000000001234000000001234"),
365+
nv("0.00006786781234678678678"),
366+
nv("0.0000000001234000000001234"),
367+
nv("0.000000000123400000087678634"),
368+
],
369+
},
370+
};
371+
372+
const serialized = cbor.serialize(sequence);
373+
const deserialized = cbor.deserialize(serialized);
374+
375+
expect(deserialized).toEqual(sequence);
376+
});
377+
308378
it("should throw an error if serializing a tag with missing properties", () => {
309379
expect(() =>
310380
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)