Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
🌱 Add decode
Browse files Browse the repository at this point in the history
✅ Add basic decode test

complete basic tests

benchmark
  • Loading branch information
shuse2 committed May 20, 2020
1 parent 12ba68f commit e6be103
Show file tree
Hide file tree
Showing 11 changed files with 747 additions and 199 deletions.
5 changes: 5 additions & 0 deletions elements/lisk-codec/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ module.exports = {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
rules: {
'no-continue': 'off',
'@typescript-eslint/prefer-for-of': 'off',
'@typescript-eslint/no-use-before-define': 'off',
},
};
69 changes: 69 additions & 0 deletions elements/lisk-codec/benchmark/decode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright © 2020 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/
// writeBoolean x 3,543,238 ops/sec ±1.59% (89 runs sampled)

const { Suite } = require('benchmark');
const {
codec,
} = require('../dist-node/codec');

const suite = new Suite();

const testSchema = {
$id: 'testSchema',
type: 'object',
properties: {
b: { fieldNumber: 2, dataType: 'string' },
a: { fieldNumber: 1, dataType: 'string' },
d: { fieldNumber: 4, dataType: 'bytes' },
e: { fieldNumber: 5, dataType: 'uint32' },
c: {
type: 'object',
fieldNumber: 3,
properties: {
cc: { fieldNumber: 3, dataType: 'string' },
ca: { fieldNumber: 1, dataType: 'string' },
cb: {
type: 'object',
fieldNumber: 2,
properties: {
cbb: { fieldNumber: 2, dataType: 'string' },
cba: { fieldNumber: 1, dataType: 'string' },
cbc: {
type: 'object',
fieldNumber: 3,
properties: {
cbcb: { fieldNumber: 3, dataType: 'string' },
cbca: { fieldNumber: 2, dataType: 'string' },
},
},
cbd: { fieldNumber: 4, dataType: 'string' },
},
},
},
},
},
};

const message = Buffer.from('0a37313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139331237313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139331a720a373133373138393337313933313337313839333731393331333731383933373139333133373138393337313933313337313839333731393312ab010a37313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139331237313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139331a721237313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139331a37313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139332237313337313839333731393331333731383933373139333133373138393337313933313337313839333731393331333731383933373139331a373133373138393337313933313337313839333731393331333731383933373139333133373138393337313933313337313839333731393322373133373138393337313933313337313839333731393331333731383933373139333133373138393337313933313337313839333731393328904e', 'hex');


suite
.add('decode', () => {
codec.decode(testSchema, message);
})
.on('cycle', function (event) {
console.log(String(event.target));
})
.run({ async: true });
13 changes: 9 additions & 4 deletions elements/lisk-codec/src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
SchemaProps,
} from './types';

import { writeObject } from './collection';
import { writeObject, readObject } from './collection';

export class Codec {
private readonly _compileSchemas: CompiledSchemas = {};
Expand All @@ -42,9 +42,14 @@ export class Codec {
return Buffer.concat(res[0]);
}

// eslint-disable-next-line
public decode<T>(_schema: object, _message: Buffer): T {
return {} as T;
public decode<T>(schema: Schema, message: Buffer): T {
if (this._compileSchemas[schema.$id] === undefined) {
this.addSchema(schema);
}
const compiledSchema = this._compileSchemas[schema.$id];
const [res] = readObject(message, 0, compiledSchema);

return res as unknown as T;
}

private _compileSchema(
Expand Down
189 changes: 181 additions & 8 deletions elements/lisk-codec/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,39 @@
*
* Removal or modification of this copyright notice is prohibited.
*/
import { findObjectByPath, generateKey } from './utils';
import {
findObjectByPath,
generateKey,
} from './utils';
import {
GenericObject,
CompiledSchemasArray,
} from './types';
import {
writeSInt32,
writeSInt64,
writeUInt32,
writeUInt64,
readUInt32,
readSInt32,
readSInt64,
readUInt64,
} from './varint';
import { writeString, readString } from './string';
import { writeBytes, readBytes } from './bytes';
import { writeBoolean, readBoolean } from './boolean';
import { readKey } from './keys';

import { GenericObject, CompiledSchemasArray } from './types';

import { writeSInt32, writeSInt64, writeUInt32, writeUInt64 } from './varint';
import { writeString } from './string';
import { writeBytes } from './bytes';
import { writeBoolean } from './boolean';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _readers : { readonly [key: string]: (value: Buffer, offset: number) => any } = {
uint32: readUInt32,
sint32: readSInt32,
uint64: readUInt64,
sint64: readSInt64,
string: readString,
bytes: readBytes,
boolean: readBoolean,
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _writers: { readonly [key: string]: (value: any) => Buffer } = {
Expand Down Expand Up @@ -88,5 +113,153 @@ export const writeObject = (
simpleObjectSize += binaryKey.length + binaryValue.length;
}
}
return [chunks, simpleObjectSize];
return [chunks, simpleObjectSize]
}


export const readObject = (message: Buffer, offset: number, compiledSchema: CompiledSchemasArray): [GenericObject, number] => {
let index = offset;
const result: GenericObject = {};
for (let i = 0; i < compiledSchema.length; i += 1) {
const typeSchema = compiledSchema[i];
if (Array.isArray(typeSchema)) {
// Takeout the root wireType and field number
if (typeSchema[0].schemaProp.type === 'array') {
const [arr, nextOffset] = readArray(message, index, typeSchema);
result[typeSchema[0].propertyName] = arr;
index = nextOffset;
} else if (typeSchema[0].schemaProp.type === 'object') {
// It should be wire type 2 as it's object
const [, keySize] = readUInt32(message, index);
index += keySize;
// Takeout the length
const [, objectSize] = readUInt32(message, index);
index += objectSize;
const [obj, nextOffset] = readObject(message, index, typeSchema);
result[typeSchema[0].propertyName] = obj;
index = nextOffset;
} else {
throw new Error('Invalid container type');
}
continue;
}
if (typeSchema.schemaProp.type === 'object' || typeSchema.schemaProp.type === 'array') {
// typeSchema is header, and we ignroe this
continue;
}
// Takeout the root wireType and field number
const [key, keySize] = readUInt32(message, index);
const [fieldNumber] = readKey(key);
if (fieldNumber !== typeSchema.schemaProp.fieldNumber) {
continue;
}
// Index is only incremented when the key is actually used
index += keySize;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const [scalarValue, scalarSize] = _readers[typeSchema.schemaProp.dataType as string](message, index);
index += scalarSize;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
result[typeSchema.propertyName] = scalarValue;
}
return [result, index];
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const readArray = (message: Buffer, offset: number, compiledSchema: CompiledSchemasArray): [Array<any>, number] => {
// Takeout the root wireType and field number
let index = offset;
if (index >= message.length) {
return [[], index];
}
const startingByte = message[index];
// It should be wire type 2 as it's object
const [, keySize] = readUInt32(message, index);
index += keySize;
// Takeout the length
const [arrayLength, wireType2Size] = readUInt32(message, index);
index += wireType2Size;
const [, typeSchema] = compiledSchema;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: Array<any> = [];
// Case of object
if (Array.isArray(typeSchema)) {
// handle as object
const [nestedTypeSchema] = typeSchema;
if (nestedTypeSchema.schemaProp.type === 'object') {
// Since first key and length is read, we do read them directly
if (wireType2Size !== 0) {
// readObject returns Next offset, not index used
const [res, nextOffset] = readObject(message, index, typeSchema);
result.push(res);
index = nextOffset;
} else {
result.push({});
}
// If still the next bytes is the same key, it is still element of array
while (message[index] === startingByte) {
const [, wire2KeySize] = readUInt32(message, index);
index += wire2KeySize;
// Takeout the length
const [wireType2Length, wireType2LengthSize] = readUInt32(message, index);
index += wireType2LengthSize;
if (wireType2Length === 0) {
result.push({});
// Add default value
continue;
}
// readObject returns Next offset, not index used
const [res, nextOffset] = readObject(message, index, typeSchema);
result.push(res);
index += nextOffset;
}
return [result, index];
}
throw new Error('Invalid container type');
}
// Case for string and bytes
if (typeSchema.schemaProp.dataType === 'string' || typeSchema.schemaProp.dataType === 'bytes') {
// Since first key and length is read, we do read them directly
if (wireType2Size !== 0) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const [res, size] = _readers[typeSchema.schemaProp.dataType as string](message, index);
result.push(res);
index += size;
} else if (typeSchema.schemaProp.dataType === 'string') {
result.push('');
} else {
result.push(Buffer.alloc(0));
}
// If still the next bytes is the same key, it is still element of array
while (message[index] === startingByte) {
const [, wire2KeySize] = readUInt32(message, offset);
index += wire2KeySize;
// Takeout the length
const [wireType2Length, wireType2LengthSize] = readUInt32(message, index);
index += wireType2LengthSize;
if (wireType2Length === 0) {
if (typeSchema.schemaProp.dataType === 'string') {
result.push('');
} else {
result.push(Buffer.alloc(0));
}
// Add default value
continue;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const [res, wire2Size] = _readers[typeSchema.schemaProp.dataType as string](message, index);
result.push(res);
index += wire2Size;
}
return [result, index];
}
// Case for varint and boolean
const end = index + arrayLength;
while (index < end) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const [res, size] = _readers[typeSchema.schemaProp.dataType as string](message, index);
result.push(res);
index += size;
}

return [result, index];
};
2 changes: 1 addition & 1 deletion elements/lisk-codec/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface GenericObject {
[key: string]: GenericObject | string | number | Buffer;
[key: string]: GenericObject | string | number | Buffer | Array<string | number | bigint | Buffer>;
}
export interface SchemaPair {
readonly [key: string]: SchemaProps;
Expand Down
Loading

0 comments on commit e6be103

Please sign in to comment.