Skip to content

Commit

Permalink
cbor js runtime wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed May 16, 2024
1 parent b38fa63 commit f1673f0
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 54 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/submodules/cbor/cbor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { cbor } from "./cbor";

const toBytes = (string) => {
const toBytes = (str: string) => {
const bytes = [] as number[];
string.replace(/../g, function (pair) {
str.split(/../g).forEach((pair: string) => {
bytes.push(parseInt(pair, 16));
});
return new Uint8Array(bytes);
Expand Down
120 changes: 68 additions & 52 deletions packages/core/src/submodules/cbor/cbor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { fromUtf8, toUtf8 } from "@smithy/util-utf8";

/**
* This cbor serde implementation is derived from AWS SDK for Go's implementation.
*
* @see https://gist.github.com/lucix-aws/0b92a2d6fd80ec38ed49904e56621550
* @see https://github.com/aws/smithy-go/tree/main/encoding/cbor
*/

/**
*
*/
type CborListType<T = any> = Array<T>;
type CborMapType<T = any> = Record<string, T>;

Expand All @@ -25,9 +35,15 @@ type Float32 = number;

type CborSliceType = Uint8Array;

/**
* @author Luc Talatinian
*/
type CborMajorType =
| typeof majorUint64
| typeof majorNegativeInt64
| typeof majorUnstructuredByteString
| typeof majorUtf8String
| typeof majorList
| typeof majorMap
| typeof majorTag
| typeof majorSpecial;

const majorUint64 = 0;
const majorNegativeInt64 = 1;
Expand All @@ -38,31 +54,31 @@ const majorMap = 5;
const majorTag = 6;
const majorSpecial = 7;

const major7False = 20; // 0b10100
const major7True = 21; // 0b10101
const major7Null = 22; // 0b10110
const major7Undefined = 23; // 0b10111

const major7Float16 = 25; // 0b11001
const major7Float32 = 26; // 0b11010
const major7Float64 = 27; // 0b11011
const specialFalse = 20; // 0b10100
const specialTrue = 21; // 0b10101
const specialNull = 22; // 0b10110
const specialUndefined = 23; // 0b10111
const specialOneByte = 24;
const specialFloat16 = 25; // 0b11001
const specialFloat32 = 26; // 0b11010
const specialFloat64 = 27; // 0b11011

const minorIndefinite = 31;

/**
* Exponents of 2.
*/
const TWO = {
EIGHT: 0b0000_0000_0000_0000_0000_0000_0001_0000_0000,
SIXTEEN: 0b0000_0000_0000_0000_0001_0000_0000_0000_0000,
THIRTY_TWO: 0b0001_0000_0000_0000_0000_0000_0000_0000_0000,
EIGHT: 1 << 8,
SIXTEEN: 1 << 16,
THIRTY_TWO: 2 ** 32,
};

// decode

const float16ToUint32 = (float: Float16): Uint32 => {
const n = [
// sign: 2 ** 23 - 1 << 16
// sign: 2 ** 23 - 1
(float & 0b0111_1111_1111_1111_1111_1111) << 16,
// exponent
(float & 0b0000_0000_0111_1100_0000_0000) >> 10,
Expand All @@ -80,11 +96,11 @@ const float16ToUint32 = (float: Float16): Uint32 => {
if (mantissa === 0) return sign;

exponent = -14 + 127;
while ((mantissa & 0b100000000000000000000000) /* 2 ** 23 */ === 0) {
while ((mantissa & (1 << 23)) === 0) {
mantissa <<= 1;
--exponent;
}
mantissa &= 0b011111111111111111111111 /* 2 ** 23 - 1 */;
mantissa &= (1 << 23) - 1;
return sign | (exponent << 23) | mantissa;
}

Expand All @@ -107,9 +123,9 @@ const concat = (left: Uint8Array, right: Uint8Array): Uint8Array => {
const offsetDataView = (payload: Uint8Array): DataView =>
new DataView(payload.buffer, payload.buffer.byteLength - payload.length);

const peekMajor = (payload: Uint8Array): Uint8 => (payload[0] & 0b11100000) >> 5;
const peekMajor = (payload: Uint8Array): Uint8 => (payload[0] & 0b1110_0000) >> 5;

const peekMinor = (payload: Uint8Array): Uint8 => payload[0] & 0b00011111;
const peekMinor = (payload: Uint8Array): Uint8 => payload[0] & 0b0001_1111;

const decodeArgument = (payload: Uint8Array): [Uint64Imprecise, CborArgumentLengthOffset] => {
const minor = peekMinor(payload);
Expand All @@ -118,10 +134,10 @@ const decodeArgument = (payload: Uint8Array): [Uint64Imprecise, CborArgumentLeng
}

switch (minor) {
case 24:
case 25:
case 26:
case 27:
case specialOneByte:
case specialFloat16:
case specialFloat32:
case specialFloat64:
const argLen: CborArgumentLength = minorValueToArgumentLength(minor);
if (payload.length < argLen + 1) {
throw new Error(`arg len ${argLen} greater than remaining buf len.`);
Expand All @@ -134,9 +150,9 @@ const decodeArgument = (payload: Uint8Array): [Uint64Imprecise, CborArgumentLeng
};

const minorValueToArgumentLength = (minor: number): CborArgumentLength => {
if (minor === 24) return 1;
if (minor === 25) return 2;
if (minor === 26) return 4;
if (minor === specialOneByte) return 1;
if (minor === specialFloat16) return 2;
if (minor === specialFloat32) return 4;
return 8;
};

Expand Down Expand Up @@ -192,7 +208,7 @@ const decodeUnstructuredByteStringIndefinite = (payload: Uint8Array): [CborSlice

let unstructuredByteString = new Uint8Array();
for (let offset = 0; payload.length > 0; ) {
if (payload[0] === 255) {
if (payload[0] === 0b1111_1111) {
return [unstructuredByteString, offset + 2];
}

Expand Down Expand Up @@ -300,7 +316,7 @@ const decodeMapIndefinite = (payload: Uint8Array): [CborMapType, CborOffset] =>
throw new Error("unexpected end of map payload.");
}

if (payload[0] === 255) {
if (payload[0] === 0b1111_1111) {
return [map, off + 2];
}

Expand Down Expand Up @@ -333,25 +349,25 @@ const decodeTag = (payload: Uint8Array): [CborTagType, CborOffset] => {
const decodeSpecial = (payload: Uint8Array): [CborValueType, CborOffset] => {
const minor = peekMinor(payload);
switch (minor) {
case major7True:
case major7False:
return [minor === major7True, 1];
case major7Null:
case specialTrue:
case specialFalse:
return [minor === specialTrue, 1];
case specialNull:
return [null, 1];
case major7Undefined:
case specialUndefined:
return [undefined, 1];
case major7Float16:
case specialFloat16:
if (payload.length < 3) {
throw new Error("incomplete float16 at end of buf.");
}
const u16 = offsetDataView(payload.subarray(1)).getUint16(0);
return [uint32ToFloat32(float16ToUint32(u16)), 3];
case major7Float32:
case specialFloat32:
if (payload.length < 5) {
throw new Error("incomplete float32 at end of buf.");
}
return [offsetDataView(payload.subarray(1)).getFloat32(0), 5];
case major7Float64:
case specialFloat64:
if (payload.length < 9) {
throw new Error("incomplete float64 at end of buf.");
}
Expand Down Expand Up @@ -419,11 +435,11 @@ const getEncodeLength = (value: CborValueType, referenceTracker = new Set()): Cb
referenceTracker.delete(value);
return length;
} else if (typeof value === "object") {
const entries = Object.entries(value);
referenceTracker.add(value);
const entries = Object.entries(value);
const length =
getHeaderLength(entries.length) +
entries.reduce(
Object.entries(value).reduce(
(accumulatedLength, [key, mapMember]) =>
accumulatedLength + getEncodeLength(key) + getEncodeLength(mapMember, referenceTracker),
0
Expand All @@ -438,59 +454,59 @@ const getHeaderLength = (value: Uint64Imprecise): CborArgumentLengthOffset => {
return 1;
} else if (value < TWO.EIGHT) {
return 2;
} else if (value < TWO.SIXTEEN /* 2 ** 16 */) {
} else if (value < TWO.SIXTEEN) {
return 3;
} else if (value < TWO.THIRTY_TWO /* 2 ** 32 */) {
} else if (value < TWO.THIRTY_TWO) {
return 5;
}
return 9;
};

const encodeHeader = (major: Uint8, value: Uint64Imprecise, buffer: Uint8Array): CborArgumentLengthOffset => {
const encodeHeader = (major: CborMajorType, value: Uint64Imprecise, buffer: Uint8Array): CborArgumentLengthOffset => {
if (value < 24) {
buffer[0] = (major << 5) | value;
return 1;
} else if (value < 0x1_00) {
} else if (value < TWO.EIGHT) {
buffer[0] = (major << 5) | 24;
buffer[1] = value;
return 2;
} else if (value < 0x1_0000) {
buffer[0] = (major << 5) | 25;
} else if (value < TWO.SIXTEEN) {
buffer[0] = (major << 5) | specialFloat16;
offsetDataView(buffer.subarray(1)).setUint16(0, value);
return 3;
} else if (value < 0x1_0000_0000) {
buffer[0] = (major << 5) | 26;
} else if (value < TWO.THIRTY_TWO) {
buffer[0] = (major << 5) | specialFloat32;
offsetDataView(buffer.subarray(1)).setUint32(0, value);
return 5;
}
buffer[0] = (major << 5) | 27;
buffer[0] = (major << 5) | specialFloat64;
offsetDataView(buffer.subarray(1)).setBigUint64(0, BigInt(value));
return 9;
};

const compose = (major: Uint8, minor: Uint8): Uint8 => (major << 5) | minor;
const compose = (major: CborMajorType, minor: Uint8): Uint8 => (major << 5) | minor;

/**
* @param input - JS data object.
* @param buffer - mutated, not returned.
*/
const encode = (input: any, buffer: Uint8Array): CborOffset => {
if (input === null) {
buffer[0] = compose(majorSpecial, major7Null);
buffer[0] = compose(majorSpecial, specialNull);
return 1;
} else if (input === undefined) {
buffer[0] = compose(majorSpecial, major7Undefined);
buffer[0] = compose(majorSpecial, specialUndefined);
return 1;
} else if (typeof input === "boolean") {
buffer[0] = compose(majorSpecial, input ? major7True : major7False);
buffer[0] = compose(majorSpecial, input ? specialTrue : specialFalse);
return 1;
} else if (typeof input === "number") {
if (Number.isInteger(input))
return input >= 0
? encodeHeader(majorUint64, input, buffer)
: encodeHeader(majorNegativeInt64, -input - 1, buffer);

buffer[0] = compose(majorSpecial, major7Float64);
buffer[0] = compose(majorSpecial, specialFloat64);
offsetDataView(buffer.subarray(1)).setFloat64(0, input);
return float64EncodeLength;
} else if (typeof input === "string") {
Expand Down

0 comments on commit f1673f0

Please sign in to comment.