Skip to content

Commit

Permalink
chore(deser-lib): changes based on review
Browse files Browse the repository at this point in the history
TICKET: HSM-236
  • Loading branch information
johnoliverdriscoll committed Dec 4, 2023
1 parent 6216ddf commit 7324754
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 72 deletions.
156 changes: 102 additions & 54 deletions modules/deser-lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { decodeFirstSync, encodeCanonical } from 'cbor';

/** Return a string describing value as a type. */
function getType(value): string {
if (Array.isArray(value)) {
/**
* Return a string describing value as a type.
* @param value - Any javascript value to type.
* @returns String describing value type.
*/
function getType(value: unknown): string {
if (value === null || value === undefined) {
return 'null';
}
if (value instanceof Array) {
const types = value.map(getType);
if (!types.slice(1).every((value) => value === types[0])) {
throw new Error('Array elements are not of the same type');
}
return JSON.stringify([types[0]]);
}
if (typeof value === 'object') {
if (value instanceof Object) {
const properties = Object.getOwnPropertyNames(value);
properties.sort();
return JSON.stringify(
Expand All @@ -28,8 +35,13 @@ function getType(value): string {
return JSON.stringify(typeof value);
}

/** Compare two buffers for sorting. */
function bufferCompare(a: Buffer, b: Buffer) {
/**
* Compare two buffers for sorting.
* @param a - left buffer to compare to right buffer.
* @param b - right buffer to compare to left buffer.
* @returns Negative if a < b, positive if b > a, 0 if equal.
*/
function bufferCompare(a: Buffer, b: Buffer): number {
let i = 0;
while (i < a.length && i < b.length && a[i] == b[i]) {
i++;
Expand All @@ -43,44 +55,68 @@ function bufferCompare(a: Buffer, b: Buffer) {
return a[i] - b[i];
}

/** Compare two array elements for sorting. */
function elementCompare(a: any, b: any) {
if (!('weight' in a) || !('weight' in b)) {
throw new Error('Array elements lack weight property');
/** A sortable array element. */
interface Sortable {
weight: number;
value?: unknown;
}

/**
* Type check for sortable array element.
* @param value - Value to type check.
* @returns True if value is a sortable array element.
*/
function isSortable(value: unknown): value is Sortable {
return value instanceof Object && 'weight' in value;
}

/**
* Compare two array elements for sorting.
* @param a - left element to compare to right element.
* @param b - right element to compare to left element.
* @returns Negative if a < b, positive if b > a, 0 if equal.
*/
function elementCompare(a: unknown, b: unknown): number {
if (!isSortable(a) || !isSortable(b)) {
throw new Error('Array elements must be sortable');
}
if (a.weight === b.weight) {
if (!('value' in a) || !('value' in b)) {
throw new Error('Array elements lack value property');
}
const aVal = transform(a.value);
const bVal = transform(b.value);
if (!Buffer.isBuffer(aVal) && typeof aVal !== 'string' && typeof aVal !== 'number') {
throw new Error('Array element value cannot be compared');
}
if (!Buffer.isBuffer(bVal) && typeof bVal !== 'string' && typeof bVal !== 'number') {
throw new Error('Array element value cannot be compared');
}
if (typeof aVal === 'number' && typeof bVal === 'number') {
return aVal - bVal;
}
let aBuf, bBuf;
if (typeof aVal === 'number') {
aBuf = Buffer.from([aVal]);
} else {
aBuf = Buffer.from(aVal);
}
if (typeof bVal === 'number') {
bBuf = Buffer.from([bVal]);
} else {
bBuf = Buffer.from(bVal);
if ('value' in a && 'value' in b) {
const aVal = transform(a.value);
const bVal = transform(b.value);
if (
(!Buffer.isBuffer(aVal) && typeof aVal !== 'string' && typeof aVal !== 'number') ||
(!Buffer.isBuffer(bVal) && typeof bVal !== 'string' && typeof bVal !== 'number')
) {
throw new Error('Array element value cannot be compared');
}
let aBuf, bBuf;
if (typeof aVal === 'number') {
aBuf = Buffer.from([aVal]);
} else {
aBuf = Buffer.from(aVal);
}
if (typeof bVal === 'number') {
bBuf = Buffer.from([bVal]);
} else {
bBuf = Buffer.from(bVal);
}
return bufferCompare(aBuf, bBuf);
}
return bufferCompare(aBuf, bBuf);
throw new Error('Array elements must be sortable');
}
return a.weight - b.weight;
}

/** Transform value into its canonical, serializable form. */
export function transform(value: any) {
/**
* Transform value into its canonical, serializable form.
* @param value - Value to transform.
* @returns Canonical, serializable form of value.
*/
export function transform<T>(value: T): T | Buffer {
if (value === null || value === undefined) {
return value;
}
if (typeof value === 'string') {
// Transform hex strings to buffers.
if (value.startsWith('0x')) {
Expand All @@ -89,36 +125,40 @@ export function transform(value: any) {
}
return Buffer.from(value.slice(2), 'hex');
}
} else if (Array.isArray(value)) {
// Enforce array elemenst are same type.
} else if (value instanceof Array) {
// Enforce array elements are same type.
getType(value);
value = value.slice(0);
value.sort(elementCompare).map(transform);
return value.map(transform);
} else if (typeof value === 'object') {
value = [...value] as unknown as T;
(value as unknown as Array<unknown>).sort(elementCompare);
return (value as unknown as Array<unknown>).map(transform) as unknown as T;
} else if (value instanceof Object) {
const properties = Object.getOwnPropertyNames(value);
properties.sort();
return properties.reduce((acc, name) => {
acc[name] = transform(value[name]);
return acc;
}, {});
}, {}) as unknown as T;
}
return value;
}

/** Untransform value into its human readable form. */
export function untransform(value: any) {
/**
* Untransform value into its human readable form.
* @param value - Value to untransform.
* @returns Untransformed, human readable form of value.
*/
export function untransform<T>(value: T): T | string {
if (Buffer.isBuffer(value)) {
return '0x' + value.toString('hex');
}
if (Array.isArray(value) && value.length > 1) {
if (value instanceof Array && value.length > 1) {
for (let i = 1; i < value.length; i++) {
if (value[i - 1].weight > value[i].weight) {
throw new Error('Array elements are not in canonical order');
}
}
return value.map(untransform);
} else if (typeof value === 'object') {
return value.map(untransform) as unknown as T;
} else if (value instanceof Object) {
const properties = Object.getOwnPropertyNames(value);
for (let i = 1; i < properties.length; i++) {
if (properties[i - 1].localeCompare(properties[i]) > 0) {
Expand All @@ -128,17 +168,25 @@ export function untransform(value: any) {
return properties.reduce((acc, name) => {
acc[name] = untransform(value[name]);
return acc;
}, {});
}, {}) as unknown as T;
}
return value;
}

/** Serialize a value. */
export function serialize(value: any): Buffer {
/**
* Serialize a value.
* @param value - Value to serialize.
* @returns Buffer representing serialized value.
*/
export function serialize<T>(value: T): Buffer {
return encodeCanonical(transform(value));
}

/** Deserialize a value. */
export function deserialize(value: Buffer) {
/**
* Deserialize a value.
* @param value - Buffer to deserialize.
* @returns Deserialized value.
*/
export function deserialize(value: Buffer): unknown {
return untransform(decodeFirstSync(value));
}
Loading

0 comments on commit 7324754

Please sign in to comment.