Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clever-shirts-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/core": patch
---

fix offset calculation when decoding bigInteger in CBOR
36 changes: 29 additions & 7 deletions packages/core/src/submodules/cbor/cbor-decode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NumericValue } from "@smithy/core/serde";
import { nv } from "@smithy/core/serde";
import { toUtf8 } from "@smithy/util-utf8";

import {
Expand Down Expand Up @@ -129,22 +129,44 @@ export function decode(at: Uint32, to: Uint32): CborValueType {
for (let i = start; i < start + length; ++i) {
b = (b << BigInt(8)) | BigInt(payload[i]);
}

_offset = offset + length;
// the new offset is the sum of:
// 1. the local major offset (1)
// 2. the offset of the decoded count of the bigInteger
// 3. the length of the data bytes of the bigInteger
_offset = offset + _offset + length;
return minor === 3 ? -b - BigInt(1) : b;
} else if (minor === 4) {
const decimalFraction = decode(at + offset, to);
const [exponent, mantissa] = decimalFraction;
const s = mantissa.toString();
const numericString = exponent === 0 ? s : s.slice(0, s.length + exponent) + "." + s.slice(exponent);
const normalizer = mantissa < 0 ? -1 : 1;
const mantissaStr = "0".repeat(Math.abs(exponent) + 1) + String(BigInt(normalizer) * BigInt(mantissa));

let numericString: string;
const sign = mantissa < 0 ? "-" : "";

numericString =
exponent === 0
? mantissaStr
: mantissaStr.slice(0, mantissaStr.length + exponent) + "." + mantissaStr.slice(exponent);
numericString = numericString.replace(/^0+/g, "");
if (numericString === "") {
numericString = "0";
}
if (numericString[0] === ".") {
numericString = "0" + numericString;
}
numericString = sign + numericString;

return new NumericValue(numericString, "bigDecimal");
// the new offset is the sum of:
// 1. the local major offset (1)
// 2. the offset of the decoded exponent mantissa pair
_offset = offset + _offset;
return nv(numericString);
} else {
const value = decode(at + offset, to);
const valueOffset = _offset;

_offset = offset + valueOffset;

return tag({ tag: castBigInt(unsignedInt), value });
}
}
Expand Down
92 changes: 88 additions & 4 deletions packages/core/src/submodules/cbor/cbor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NumericValue } from "@smithy/core/serde";
import { NumericValue, nv } from "@smithy/core/serde";
import * as fs from "fs";
// @ts-ignore
import JSONbig from "json-bigint";
Expand Down Expand Up @@ -219,12 +219,19 @@ describe("cbor", () => {
name: "object containing big numbers",
data: {
map: {
items: [BigInt(1e80)],
items: [BigInt(1e80), BigInt(1e80), nv("0.0000000001234000000001234"), nv("0.0000000001234000000001234")],
bigint: BigInt(1e80),
bigDecimal: nv("0.0000000001234000000001234"),
},
},
cbor: allocByteArray([
161, 99, 109, 97, 112, 161, 101, 105, 116, 101, 109, 115, 129, 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, 0, 0, 0, 0, 0, 0, 0, 0,
161, 99, 109, 97, 112, 163, 101, 105, 116, 101, 109, 115, 132, 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, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 196, 130, 56, 24,
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,
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,
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,
3, 167, 36, 210,
]),
},
];
Expand Down Expand Up @@ -305,6 +312,83 @@ describe("cbor", () => {
}
});

it("should round-trip sequences of big numbers", () => {
const sequence = {
map: {
items1: [
BigInt(1e20),
BigInt(2e30),
BigInt(3e40),
BigInt(4e50),
BigInt(5e60),
BigInt(6e70),
BigInt(7e80),
BigInt(8e90),
],
items2: [
nv("111.00000000000000000001"),
nv("0.000000000000000000002"),
nv("333.0000000000000000000003"),
nv("0.00000000000000000000004"),
nv("-555.000000000000000000000005"),
nv("-0.0000000000000000000000006"),
nv("-777.00000000000000000000000007"),
nv("-0.000000000000000000000000008"),
],
items3: [nv("0.0000000001234000000001234"), nv("0.00000000678678001234"), BigInt(1e20), BigInt(2e30)],
items4: [
BigInt(1e20),
BigInt(2e30),
nv("0.0000000001234000000001234"),
nv("0.0000067867867801234"),
BigInt(1e20),
BigInt(2e30),
nv("0.0000000001234000000001234"),
nv("1.000000000123678678678234"),
],
items5: [
nv("0.0000000001234000000001234"),
nv("0.00006786781234678678678"),
BigInt(1e20),
BigInt(2e30),
nv("0.0000000001234000000001234"),
nv("0.000000000123400000087678634"),
BigInt(1e20),
BigInt(2e30),
],
items6: [
nv("10469069930579305970359073950793057903597035970395069240692049609"),
nv("99130490139501395091035901395031950.4928053468045683958609485649280534680456839586094856"),
nv("1.000135135000103501305000000000004928053468045683958609485649280534680456839586094856"),
nv("1.00013513500010350130500000000000"),
nv("0.0000000001234000000001234"),
nv("0.0000001"),
nv("0.00001"),
nv("0.001"),
nv("0.000000"),
nv("0.0000"),
nv("0.00"),
nv("0.0"),
nv("0"),
nv("-0.1"),
nv("-0.01"),
nv("-0.000000000123400000087678634"),
nv("-0.0000000001234000000876786340000000000000000000000000000"),
nv("-1.000135135000103501305000000000004928053468045683958609485649280534680456839586094856"),
nv("-1.00013513500010350130500000000000"),
nv("-100305096350939057390735093.0000000001234000000001234"),
nv("-104695047960794069730590793057.0"),
nv("-104695047960794069730590793057"),
],
},
};

const serialized = cbor.serialize(sequence);
const deserialized = cbor.deserialize(serialized);

expect(deserialized).toEqual(sequence);
});

it("should throw an error if serializing a tag with missing properties", () => {
expect(() =>
cbor.serialize({
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/submodules/serde/value/NumericValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,19 @@ export class NumericValue {
return this.string;
}

public [Symbol.hasInstance](object: unknown) {
public static [Symbol.hasInstance](object: unknown) {
if (!object || typeof object !== "object") {
return false;
}
const _nv = object as NumericValue;
const prototypeMatch = NumericValue.prototype.isPrototypeOf(object.constructor?.prototype);
if (prototypeMatch) {
return prototypeMatch;
}
if (typeof _nv.string === "string" && typeof _nv.type === "string" && _nv.constructor?.name === "NumericValue") {
return true;
}
return false;
return prototypeMatch;
}
}

Expand Down
Loading