Skip to content

Commit 745715b

Browse files
2 parents 979d800 + 99b9878 commit 745715b

File tree

2 files changed

+10
-269
lines changed

2 files changed

+10
-269
lines changed

modules/deser-lib/src/cbor.ts

Lines changed: 0 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,5 @@
11
import { decodeFirstSync, encode } from 'cbor';
22

3-
/**
4-
* Return a string describing value as a type.
5-
* @param value - Any javascript value to type.
6-
* @returns String describing value type.
7-
*/
8-
function getType(value: unknown): string {
9-
if (value === null || value === undefined) {
10-
return 'null';
11-
}
12-
if (value instanceof Array) {
13-
const types = value.map(getType);
14-
if (!types.slice(1).every((value) => value === types[0])) {
15-
throw new Error('Array elements are not of the same type');
16-
}
17-
return JSON.stringify([types[0]]);
18-
}
19-
if (value instanceof Object) {
20-
const properties = Object.getOwnPropertyNames(value);
21-
properties.sort();
22-
return JSON.stringify(
23-
properties.reduce((acc, name) => {
24-
acc[name] = getType(value[name]);
25-
return acc;
26-
}, {})
27-
);
28-
}
29-
if (typeof value === 'string') {
30-
if ((value as string).startsWith('0x')) {
31-
return 'bytes';
32-
}
33-
return 'string';
34-
}
35-
return JSON.stringify(typeof value);
36-
}
37-
38-
/**
39-
* Compare two buffers for sorting.
40-
* @param a - left buffer to compare to right buffer.
41-
* @param b - right buffer to compare to left buffer.
42-
* @returns Negative if a < b, positive if b > a, 0 if equal.
43-
*/
44-
function bufferCompare(a: Buffer, b: Buffer): number {
45-
let i = 0;
46-
while (i < a.length && i < b.length && a[i] == b[i]) {
47-
i++;
48-
}
49-
if (i === a.length && i === b.length) {
50-
return 0;
51-
}
52-
if (i === a.length || i === b.length) {
53-
return a.length - b.length;
54-
}
55-
return a[i] - b[i];
56-
}
57-
58-
/** A sortable array element. */
59-
type Sortable = {
60-
weight: number;
61-
value?: unknown;
62-
};
63-
64-
/**
65-
* Type check for sortable array element.
66-
* @param value - Value to type check.
67-
* @returns True if value is a sortable array element.
68-
*/
69-
function isSortable(value: unknown): value is Sortable {
70-
return value instanceof Object && 'weight' in value && typeof (value as Sortable).weight === 'number';
71-
}
72-
73-
/**
74-
* Convert number to base 256 and return as a big-endian Buffer.
75-
* @param value - Value to convert.
76-
* @returns Buffer representation of the number.
77-
*/
78-
function numberToBufferBE(value: number): Buffer {
79-
// Normalize value so that negative numbers aren't compared higher
80-
// than positive numbers when accounting for two's complement.
81-
value += Math.pow(2, 52);
82-
const byteCount = Math.floor((value.toString(2).length + 7) / 8);
83-
const buffer = Buffer.alloc(byteCount);
84-
let i = 0;
85-
while (value) {
86-
buffer[i++] = value % 256;
87-
value = Math.floor(value / 256);
88-
}
89-
return buffer.reverse();
90-
}
91-
92-
/**
93-
* Compare two array elements for sorting.
94-
* @param a - left element to compare to right element.
95-
* @param b - right element to compare to left element.
96-
* @returns Negative if a < b, positive if b > a, 0 if equal.
97-
*/
98-
function elementCompare(a: unknown, b: unknown): number {
99-
if (!isSortable(a) || !isSortable(b)) {
100-
throw new Error('Array elements must be sortable');
101-
}
102-
if (a.weight === b.weight) {
103-
if (a.value === undefined && b.value === undefined) {
104-
throw new Error('Array elements must be sortable');
105-
}
106-
const aVal = transform(a.value);
107-
const bVal = transform(b.value);
108-
if (
109-
(!Buffer.isBuffer(aVal) && typeof aVal !== 'string' && typeof aVal !== 'number') ||
110-
(!Buffer.isBuffer(bVal) && typeof bVal !== 'string' && typeof bVal !== 'number')
111-
) {
112-
throw new Error('Array element value cannot be compared');
113-
}
114-
let aBuf, bBuf;
115-
if (typeof aVal === 'number') {
116-
aBuf = numberToBufferBE(aVal);
117-
} else {
118-
aBuf = Buffer.from(aVal);
119-
}
120-
if (typeof bVal === 'number') {
121-
bBuf = numberToBufferBE(bVal);
122-
} else {
123-
bBuf = Buffer.from(bVal);
124-
}
125-
return bufferCompare(aBuf, bBuf);
126-
}
127-
return a.weight - b.weight;
128-
}
129-
1303
/**
1314
* Transform value into its canonical, serializable form.
1325
* @param value - Value to transform.
@@ -147,14 +20,10 @@ export function transform<T>(value: T): T | Buffer {
14720
return value.slice(1) as unknown as T;
14821
}
14922
} else if (value instanceof Array) {
150-
// Enforce array elements are same type.
151-
getType(value);
15223
value = [...value] as unknown as T;
153-
(value as unknown as Array<unknown>).sort(elementCompare);
15424
return (value as unknown as Array<unknown>).map(transform) as unknown as T;
15525
} else if (value instanceof Object) {
15626
const properties = Object.getOwnPropertyNames(value);
157-
properties.sort();
15827
return properties.reduce((acc, name) => {
15928
acc[name] = transform(value[name]);
16029
return acc;
@@ -176,19 +45,9 @@ export function untransform<T>(value: T): T | string {
17645
return '\\' + value;
17746
}
17847
} else if (value instanceof Array && value.length > 1) {
179-
for (let i = 1; i < value.length; i++) {
180-
if (value[i - 1].weight > value[i].weight) {
181-
throw new Error('Array elements are not in canonical order');
182-
}
183-
}
18448
return value.map(untransform) as unknown as T;
18549
} else if (value instanceof Object) {
18650
const properties = Object.getOwnPropertyNames(value);
187-
for (let i = 1; i < properties.length; i++) {
188-
if (properties[i - 1].localeCompare(properties[i]) > 0) {
189-
throw new Error('Object properties are not in canonical order');
190-
}
191-
}
19251
return properties.reduce((acc, name) => {
19352
acc[name] = untransform(value[name]);
19453
return acc;

modules/deser-lib/test/unit/deser-lib.ts

Lines changed: 10 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -4,116 +4,6 @@ import * as cborFixtures from '../cbor/fixtures.json';
44
describe('deser-lib', function () {
55
describe('cbor', function () {
66
describe('transform', function () {
7-
it('orders object properties canonically', function () {
8-
const res = Cbor.transform({ b: 'second', a: 'first' }) as any;
9-
const properties = Object.getOwnPropertyNames(res);
10-
properties[0].should.equal('a');
11-
properties[1].should.equal('b');
12-
res.a.should.equal('first');
13-
res.b.should.equal('second');
14-
});
15-
16-
describe('canonical ordering', function () {
17-
it('orders by weight', function () {
18-
const res = Cbor.transform([
19-
{ weight: 2, value: null },
20-
{ weight: 1, value: null },
21-
]) as any;
22-
res[0].weight.should.equal(1);
23-
res[1].weight.should.equal(2);
24-
});
25-
26-
it('groups equal elements', function () {
27-
const res = Cbor.transform([
28-
{
29-
weight: 2,
30-
value: 'b',
31-
},
32-
{
33-
weight: 1,
34-
value: 'a',
35-
},
36-
{
37-
weight: 3,
38-
value: 'c',
39-
},
40-
{
41-
weight: 2,
42-
value: 'b',
43-
},
44-
]) as any;
45-
res[0].weight.should.equal(1);
46-
res[1].weight.should.equal(2);
47-
res[2].weight.should.equal(2);
48-
res[3].weight.should.equal(3);
49-
});
50-
51-
it('orders number values', function () {
52-
const res = Cbor.transform([
53-
{ weight: 1, value: 2 },
54-
{ weight: 1, value: 1 },
55-
]) as any;
56-
res[0].value.should.equal(1);
57-
res[1].value.should.equal(2);
58-
});
59-
60-
it('orders string values', function () {
61-
const res = Cbor.transform([
62-
{ weight: 1, value: 'ab' },
63-
{ weight: 1, value: 'aa' },
64-
]) as any;
65-
res[0].value.should.equal('aa');
66-
res[1].value.should.equal('ab');
67-
});
68-
69-
it('orders byte values', function () {
70-
const res = Cbor.transform([
71-
{ weight: 1, value: '0x0b' },
72-
{ weight: 1, value: '0x0a' },
73-
]) as any;
74-
res[0].value.equals(Buffer.from([0x0a])).should.equal(true);
75-
res[1].value.equals(Buffer.from([0x0b])).should.equal(true);
76-
});
77-
78-
it('orders string values of different lengths', function () {
79-
const res = Cbor.transform([
80-
{ weight: 1, value: 'ab' },
81-
{ weight: 1, value: 'a' },
82-
]) as any;
83-
res[0].value.should.equal('a');
84-
res[1].value.should.equal('ab');
85-
});
86-
87-
it('throws for elements without weight', function () {
88-
(() => Cbor.transform([{}, {}])).should.throw();
89-
});
90-
91-
it('throws for elements without value', function () {
92-
(() => Cbor.transform([{ weight: 1 }, { weight: 1 }])).should.throw();
93-
});
94-
95-
it('throws for values that cannot be compared', function () {
96-
(() =>
97-
Cbor.transform([
98-
{ weight: 1, value: {} },
99-
{ weight: 1, value: 1 },
100-
])).should.throw();
101-
(() =>
102-
Cbor.transform([
103-
{ weight: 1, value: undefined },
104-
{ weight: 1, value: null },
105-
])).should.throw();
106-
});
107-
108-
it('throws for elements of mixed type', function () {
109-
(() =>
110-
Cbor.transform([
111-
{ weight: 0, value: '0' },
112-
{ weight: 0, value: 0 },
113-
])).should.throw();
114-
});
115-
});
116-
1177
it('preserves null values', function () {
1188
const res = Cbor.transform({ value: null }) as any;
1199
res.should.have.property('value').which.is.null();
@@ -139,21 +29,21 @@ describe('deser-lib', function () {
13929
});
14030

14131
it('transforms object recursively', function () {
142-
const res = Cbor.transform({ value: { b: 'second', a: 'first' } }) as any;
32+
const res = Cbor.transform({ value: { b: 'first', a: 'second' } }) as any;
14333
const properties = Object.getOwnPropertyNames(res.value);
144-
properties[0].should.equal('a');
145-
properties[1].should.equal('b');
146-
res.value.a.should.equal('first');
147-
res.value.b.should.equal('second');
34+
properties[0].should.equal('b');
35+
properties[1].should.equal('a');
36+
res.value.b.should.equal('first');
37+
res.value.a.should.equal('second');
14838
});
14939

15040
it('transforms array recursively', function () {
151-
const res = Cbor.transform([{ weight: 0, value: { b: 'second', a: 'first' } }]) as any;
41+
const res = Cbor.transform([{ weight: 0, value: { b: 'first', a: 'second' } }]) as any;
15242
const properties = Object.getOwnPropertyNames(res[0].value);
153-
properties[0].should.equal('a');
154-
properties[1].should.equal('b');
155-
res[0].value.a.should.equal('first');
156-
res[0].value.b.should.equal('second');
43+
properties[0].should.equal('b');
44+
properties[1].should.equal('a');
45+
res[0].value.b.should.equal('first');
46+
res[0].value.a.should.equal('second');
15747
});
15848

15949
it('throws for invalid hex strings', function () {
@@ -171,14 +61,6 @@ describe('deser-lib', function () {
17161
res.b.should.equal('second');
17262
});
17363

174-
it('enforces canonical object property order', function () {
175-
(() => Cbor.untransform({ b: 'second', a: 'first' })).should.throw();
176-
});
177-
178-
it('enforces canonical array element order', function () {
179-
(() => Cbor.untransform([{ weight: 2 }, { weight: 1 }])).should.throw();
180-
});
181-
18264
it('replaces Buffers with prefixed hex strings', function () {
18365
const hex = '00010203';
18466
const res = Cbor.untransform({ value: Buffer.from(hex, 'hex') }) as any;

0 commit comments

Comments
 (0)