-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
21 changed files
with
3,099 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@smithy/core": minor | ||
--- | ||
|
||
cbor (de)serializer for JS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
/** | ||
* Do not edit: | ||
* This is a compatibility redirect for contexts that do not understand package.json exports field. | ||
*/ | ||
module.exports = require("./dist-cjs/submodules/cbor/index.js"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
import * as fs from "fs"; | ||
import JSONbig from "json-bigint"; | ||
import * as path from "path"; | ||
|
||
import { cbor } from "./cbor"; | ||
|
||
// syntax is ESM but the test target is CJS. | ||
const here = __dirname; | ||
|
||
const errorTests = JSONbig({ useNativeBigInt: true, alwaysParseAsBig: false }).parse( | ||
fs.readFileSync(path.join(here, "test-data", "decode-error-tests.json")) | ||
); | ||
const successTests = JSONbig({ useNativeBigInt: true, alwaysParseAsBig: false }).parse( | ||
fs.readFileSync(path.join(here, "test-data", "success-tests.json")) | ||
); | ||
|
||
describe("cbor", () => { | ||
const examples = [ | ||
{ | ||
name: "false", | ||
data: false, | ||
// special major 7 = 0b111 plus false(20) = 0b10100 | ||
cbor: new Uint8Array([0b111_10100]), | ||
}, | ||
{ | ||
name: "true", | ||
data: true, | ||
// increment from false | ||
cbor: new Uint8Array([0b111_10101]), | ||
}, | ||
{ | ||
name: "null", | ||
data: null, | ||
// increment from true | ||
cbor: new Uint8Array([0b111_10110]), | ||
}, | ||
{ | ||
name: "an unsigned zero integer", | ||
data: 0, | ||
// unsigned int major (0) plus 00's. | ||
cbor: new Uint8Array([0b000_00000]), | ||
}, | ||
{ | ||
name: "negative 1", | ||
data: -1, | ||
// negative major (1) plus 00's, since -1 is the first negative number. | ||
cbor: new Uint8Array([0b001_00000]), | ||
}, | ||
{ | ||
name: "Number.MIN_SAFE_INTEGER", | ||
data: -9007199254740991, | ||
cbor: new Uint8Array([0b001_11011, 0, 31, 255, 255, 255, 255, 255, 254]), | ||
}, | ||
{ | ||
name: "Number.MAX_SAFE_INTEGER", | ||
data: 9007199254740991, | ||
cbor: new Uint8Array([0b000_11011, 0, 31, 255, 255, 255, 255, 255, 255]), | ||
}, | ||
{ | ||
name: "int64 min", | ||
data: BigInt("-18446744073709551616"), | ||
cbor: new Uint8Array([0b001_11011, 255, 255, 255, 255, 255, 255, 255, 255]), | ||
}, | ||
{ | ||
name: "int64 max", | ||
data: BigInt("18446744073709551615"), | ||
cbor: new Uint8Array([0b000_11011, 255, 255, 255, 255, 255, 255, 255, 255]), | ||
}, | ||
{ | ||
name: "negative float", | ||
data: -3015135.135135135, | ||
cbor: new Uint8Array([0b111_11011, +193, +71, +0, +239, +145, +76, +27, +173]), | ||
}, | ||
{ | ||
name: "positive float", | ||
data: 3015135.135135135, | ||
cbor: new Uint8Array([0b111_11011, +65, +71, +0, +239, +145, +76, +27, +173]), | ||
}, | ||
{ | ||
name: "an empty string", | ||
data: "", | ||
// string major plus 00's | ||
cbor: new Uint8Array([0b011_00000]), | ||
}, | ||
{ | ||
name: "a short string", | ||
data: "hello, world", | ||
cbor: new Uint8Array([108, 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100]), | ||
}, | ||
{ | ||
name: "simple object", | ||
data: { | ||
message: "hello, world", | ||
}, | ||
cbor: new Uint8Array([ | ||
161, 103, 109, 101, 115, 115, 97, 103, 101, 108, 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, | ||
]), | ||
}, | ||
|
||
{ | ||
name: "complex object", | ||
data: { | ||
number: 135019305913059, | ||
message: "hello, world", | ||
list: [0, false, { a: "b" }], | ||
map: { | ||
a: "a", | ||
b: "b", | ||
items: [0, -1, true, false, null, "", "test", ["nested item A", "nested item B"]], | ||
}, | ||
}, | ||
cbor: new Uint8Array([ | ||
164, 102, 110, 117, 109, 98, 101, 114, 27, 0, 0, 122, 204, 161, 196, 74, 227, 103, 109, 101, 115, 115, 97, 103, | ||
101, 108, 104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 100, 108, 105, 115, 116, 131, 0, 244, 161, | ||
97, 97, 97, 98, 99, 109, 97, 112, 163, 97, 97, 97, 97, 97, 98, 97, 98, 101, 105, 116, 101, 109, 115, 136, 0, 32, | ||
245, 244, 246, 96, 100, 116, 101, 115, 116, 130, 109, 110, 101, 115, 116, 101, 100, 32, 105, 116, 101, 109, 32, | ||
65, 109, 110, 101, 115, 116, 101, 100, 32, 105, 116, 101, 109, 32, 66, | ||
]), | ||
}, | ||
]; | ||
|
||
const toBytes = (hex: string) => { | ||
const bytes = []; | ||
hex.replace(/../g, (substr: string): string => { | ||
bytes.push(parseInt(substr, 16)); | ||
return substr; | ||
}); | ||
return new Uint8Array(bytes); | ||
}; | ||
|
||
describe("locally curated scenarios", () => { | ||
for (const { name, data, cbor: cbor_representation } of examples) { | ||
it(`should encode for ${name}`, async () => { | ||
const serialized = cbor.serialize(data); | ||
expect(serialized).toEqual(cbor_representation); | ||
}); | ||
|
||
it(`should decode for ${name}`, async () => { | ||
const deserialized = cbor.deserialize(cbor_representation); | ||
expect(deserialized).toEqual(data); | ||
}); | ||
} | ||
}); | ||
|
||
describe("externally curated scenarios", () => { | ||
for (const { description, input, error } of errorTests) { | ||
it(description, () => { | ||
expect(error).toBe(true); | ||
const bytes = toBytes(input); | ||
expect(() => { | ||
cbor.deserialize(bytes); | ||
}).toThrow(); | ||
}); | ||
} | ||
|
||
function binaryToFloat32(b: number) { | ||
const dv = new DataView(new ArrayBuffer(4)); | ||
dv.setInt32(0, Number(b)); | ||
return dv.getFloat32(0); | ||
} | ||
|
||
function binaryToFloat64(b: number) { | ||
const binaryArray = b.toString(2).split("").map(Number); | ||
const pad = Array(64).fill(0); | ||
const binary64 = new Uint8Array(pad.concat(binaryArray).slice(-64)); | ||
|
||
const sign = binary64[0]; | ||
const exponent = Number("0b" + Array.from(binary64.subarray(1, 12)).join("")); | ||
const fraction = binary64.subarray(12); | ||
|
||
const scalar = (-1) ** sign; | ||
let sum = 1; | ||
for (let i = 1; i <= 52; ++i) { | ||
const position = i - 1; | ||
const bit = fraction[position]; | ||
sum += 2 ** -i * bit; | ||
} | ||
const exponentScalar = Math.pow(2, exponent - 1023); | ||
return scalar * sum * exponentScalar; | ||
} | ||
|
||
function translateTestData(data: any) { | ||
const [type, value] = Object.entries(data)[0] as [string, any]; | ||
switch (type) { | ||
case "null": | ||
return null; | ||
case "uint": | ||
case "negint": | ||
case "bool": | ||
case "string": | ||
return value; | ||
case "float32": | ||
return binaryToFloat32(value); | ||
case "float64": | ||
return binaryToFloat64(value); | ||
case "bytestring": | ||
return new Uint8Array(value.map(Number)); | ||
case "list": | ||
return value.map(translateTestData); | ||
case "map": | ||
const output = {} as Record<string, any>; | ||
for (const [k, v] of Object.entries(value)) { | ||
output[k] = translateTestData(v); | ||
} | ||
return output; | ||
case "tag": | ||
const { id, value: tagValue } = value; | ||
return { | ||
tag: id, | ||
value: translateTestData(tagValue), | ||
}; | ||
default: | ||
throw new Error(`Unrecognized test scenario <expect> type ${type}.`); | ||
} | ||
} | ||
|
||
for (const { description, input, expect: _expect } of successTests) { | ||
const bytes = toBytes(input); | ||
const jsObject = translateTestData(_expect); | ||
|
||
it(`serialization for ${description}`, () => { | ||
const serialized = cbor.serialize(jsObject); | ||
const redeserialized = cbor.deserialize(serialized); | ||
/** | ||
* We cannot assert that serialized == bytes, | ||
* because there are multiple serializations | ||
* that deserialize to the same object. | ||
*/ | ||
expect(redeserialized).toEqual(jsObject); | ||
}); | ||
it(`deserialization for ${description}`, () => { | ||
const deserialized = cbor.deserialize(bytes); | ||
expect(deserialized).toEqual(jsObject); | ||
}); | ||
} | ||
}); | ||
}); |
Oops, something went wrong.