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

Create function to traverse the object for encoding - Closes #5217 & #5228 & #5235 #5322

Merged
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fa91d0c
Implement initial version of addSchema and minimal test plus partial …
pablitovicente May 13, 2020
f2ef35d
Add initial encode() method
pablitovicente May 14, 2020
ff4a886
Add more types to GenericObject interface for lisk-codec
pablitovicente May 14, 2020
bf26693
Improve type and formatting issues
pablitovicente May 14, 2020
31a0d03
Merge branch 'development' into 5217-create_function_to_traverse_the_…
pablitovicente May 14, 2020
be64156
Move types to its own file for lisk-codec
pablitovicente May 14, 2020
1cb1cbf
Update encode() to generate buffers. Build map of data type writers. …
pablitovicente May 14, 2020
2697422
Generate binary keys correctly and cache the buffer for it in the com…
pablitovicente May 14, 2020
e76e2ca
Remove boolean writer before merge
pablitovicente May 14, 2020
8ea0726
Merge branch 'development' into 5217-create_function_to_traverse_the_…
pablitovicente May 14, 2020
0328946
Not encode missing object properties into binary messages
pablitovicente May 15, 2020
a3378f7
Concatenate buffers outside of the main encoding loop for encode() in…
pablitovicente May 15, 2020
7807e83
POC for decode(). Will be removed once we decide in final implementation
pablitovicente May 15, 2020
e99e525
Add alternative encode method
pablitovicente May 15, 2020
8b7a337
Compile recursive schemas and refactor decode() to support new compil…
pablitovicente May 18, 2020
3a20e7c
WIP support for arrays
pablitovicente May 18, 2020
9d88f38
Add support to compiled schema as needed by Array writer
pablitovicente May 19, 2020
b07055c
WIP test for encode
pablitovicente May 19, 2020
1d7e8d9
Add refactored version of compileSchema() supporting recursive structure
pablitovicente May 19, 2020
2fba57f
Remove stub writers for string and bytes before merging development
pablitovicente May 19, 2020
1a48bf7
Merge branch 'development' into 5217-create_function_to_traverse_the_…
pablitovicente May 19, 2020
f4d5ef7
Update key generator after changes to varint writters
pablitovicente May 19, 2020
5c7f023
Rewrite encode to use updated compiled schema
pablitovicente May 19, 2020
be2bb90
WIP encode() collection
pablitovicente May 20, 2020
57a1e30
First working version of encode() for lisk-codec
pablitovicente May 20, 2020
e816746
Remove POC for decode()
pablitovicente May 20, 2020
15cc2b7
Fix initial encode() tests
pablitovicente May 20, 2020
23b0ce1
Remove usages of any type casting from compileSchema()
pablitovicente May 20, 2020
13ca6da
Add all protocol-specs for lisk-codec as fixtures
pablitovicente May 20, 2020
4d592e8
Replace destructuring with regular for loop
pablitovicente May 20, 2020
8552633
Update schemas in lisk-codec proto-specs to include
pablitovicente May 20, 2020
1965856
Fix proto-spec schemas and rewrite encode() tests to use them
pablitovicente May 20, 2020
6d538a1
Fix test assertion rename private method as suggested in PR
pablitovicente May 20, 2020
12ba68f
Fix typo in error message
pablitovicente May 20, 2020
e69f2c0
Remove unnecessary code from lisk-codec/writeObject()
pablitovicente May 25, 2020
433d556
Fix formatting issue
pablitovicente May 25, 2020
729ee1b
Add complete tests for encode()
pablitovicente May 25, 2020
94b1d1e
Fix addSchema tests
pablitovicente May 25, 2020
728a91d
Merge branch 'development' into 5217-create_function_to_traverse_the_…
pablitovicente May 25, 2020
8752d15
Add lisk-codec snapshots
pablitovicente May 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions elements/lisk-codec/benchmark/encode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 obj = {
b: '1371893719313718937193137189371931371893719313718937193',
a: '1371893719313718937193137189371931371893719313718937193',
d: Buffer.from('1371893719313718937193137189371931371893719313718937193'),
e: 10000,
c: {
cc: '1371893719313718937193137189371931371893719313718937193',
ca: '1371893719313718937193137189371931371893719313718937193',
cb: {
cbb: '1371893719313718937193137189371931371893719313718937193',
cba: '1371893719313718937193137189371931371893719313718937193',
cbc: {
cbcb: '1371893719313718937193137189371931371893719313718937193',
cbca: '1371893719313718937193137189371931371893719313718937193',
},
cbd: '1371893719313718937193137189371931371893719313718937193',
},
},
};


suite
.add('encode', () => {
codec.encode(testSchema, obj);
})
.on('cycle', function (event) {
console.log(String(event.target));
})
.run({ async: true });
1 change: 1 addition & 0 deletions elements/lisk-codec/fixtures
1 change: 0 additions & 1 deletion elements/lisk-codec/fixtures/validBytesEncodings.json

This file was deleted.

1 change: 0 additions & 1 deletion elements/lisk-codec/fixtures/validStringEncodings.json

This file was deleted.

144 changes: 139 additions & 5 deletions elements/lisk-codec/src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,153 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { generateKey } from './utils';

import {
CompiledSchema,
CompiledSchemas,
CompiledSchemasArray,
GenericObject,
Schema,
SchemaProps,
} from './types';

import { writeObject } from './collection';

export class Codec {
// eslint-disable-next-line
public addSchema(_schema: object): void {}
private readonly _compileSchemas: CompiledSchemas = {};

// eslint-disable-next-line
public encode(_schema: object, _message: any): Buffer {
return Buffer.alloc(0);
public addSchema(schema: Schema): void {
const schemaName = schema.$id;
this._compileSchemas[schemaName] = this.compileSchema(schema, [], []);
}

public encode(schema: Schema, message: GenericObject): Buffer {
if (this._compileSchemas[schema.$id] === undefined) {
this.addSchema(schema);
}
const compiledSchema = this._compileSchemas[schema.$id];
const res = writeObject(compiledSchema, message, []);
return Buffer.concat(res[0]);
}

// eslint-disable-next-line
public decode<T>(_schema: object, _message: Buffer): T {
return {} as T;
}

private compileSchema(
pablitovicente marked this conversation as resolved.
Show resolved Hide resolved
schema: Schema | SchemaProps,
compiledSchema: CompiledSchemasArray,
dataPath: string[],
): CompiledSchemasArray {
if (schema.type === 'object') {
const { properties } = schema;
if (properties === undefined) {
ManuGowda marked this conversation as resolved.
Show resolved Hide resolved
throw new Error('Invalid schema. Missing "properties" property');
ManuGowda marked this conversation as resolved.
Show resolved Hide resolved
}
const currentDepthSchema = Object.entries(properties).sort(
ManuGowda marked this conversation as resolved.
Show resolved Hide resolved
(a, b) => a[1].fieldNumber - b[1].fieldNumber,
);

// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < currentDepthSchema.length; i += 1) {
const [schemaPropertyName, schemaPropertyValue] = currentDepthSchema[i];
if (schemaPropertyValue.type === 'object') {
// Object recursive case
dataPath.push(schemaPropertyName);
const nestedSchema = [
{
propertyName: schemaPropertyName,
schemaProp: {
type: schemaPropertyValue.type,
fieldNumber: schemaPropertyValue.fieldNumber,
},
dataPath: [...dataPath],
binaryKey: generateKey(schemaPropertyValue),
},
];
const res = this.compileSchema(
schemaPropertyValue,
nestedSchema,
dataPath,
);
compiledSchema.push(res as CompiledSchema[]);
dataPath.pop();
} else if (schemaPropertyValue.type === 'array') {
// Array recursive case
if (schemaPropertyValue.items === undefined) {
throw new Error(
'Invalid schema. Missing "items" property for Array schema',
);
}
dataPath.push(schemaPropertyName);
if (schemaPropertyValue.items.type === 'object') {
const nestedSchema = [
{
propertyName: schemaPropertyName,
schemaProp: {
type: 'object',
fieldNumber: schemaPropertyValue.fieldNumber,
},
dataPath: [...dataPath],
binaryKey: generateKey(schemaPropertyValue),
},
];
const res = this.compileSchema(
schemaPropertyValue.items,
nestedSchema,
dataPath,
);
compiledSchema.push([
ManuGowda marked this conversation as resolved.
Show resolved Hide resolved
{
propertyName: schemaPropertyName,
schemaProp: {
type: schemaPropertyValue.type,
fieldNumber: schemaPropertyValue.fieldNumber,
},
dataPath: [...dataPath],
binaryKey: generateKey(schemaPropertyValue),
},
(res as unknown) as CompiledSchema,
]);
dataPath.pop();
} else {
compiledSchema.push([
{
propertyName: schemaPropertyName,
schemaProp: {
type: schemaPropertyValue.type,
fieldNumber: schemaPropertyValue.fieldNumber,
},
dataPath: [...dataPath],
binaryKey: generateKey(schemaPropertyValue),
},
{
propertyName: schemaPropertyName,
schemaProp: {
dataType: schemaPropertyValue.items.dataType,
fieldNumber: schemaPropertyValue.fieldNumber,
},
dataPath: [...dataPath],
binaryKey: generateKey(schemaPropertyValue),
},
]);
dataPath.pop();
}
} else {
// Base case
compiledSchema.push({
propertyName: schemaPropertyName,
schemaProp: schemaPropertyValue,
dataPath: [...dataPath],
binaryKey: generateKey(schemaPropertyValue),
});
}
}
}
return compiledSchema;
}
}

export const codec = new Codec();
92 changes: 92 additions & 0 deletions elements/lisk-codec/src/collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* eslint-disable no-param-reassign */
pablitovicente marked this conversation as resolved.
Show resolved Hide resolved
/*
* 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.
*/
import { findObjectByPath, generateKey } from './utils';

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 _writers: { readonly [key: string]: (value: any) => Buffer } = {
uint32: writeUInt32,
sint32: writeSInt32,
uint64: writeUInt64,
sint64: writeSInt64,
string: writeString,
bytes: writeBytes,
boolean: writeBoolean,
};

export const writeObject = (
compiledSchema: CompiledSchemasArray,
message: GenericObject,
chunks: Buffer[],
): [Buffer[], number] => {
let simpleObjectSize = 0;
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < compiledSchema.length; i += 1) {
const property = compiledSchema[i];
if (Array.isArray(property)) {
const headerProp = property[0];
const nestedObject = findObjectByPath(message, [headerProp.propertyName]);
pablitovicente marked this conversation as resolved.
Show resolved Hide resolved
// Write the key for container object
const key = generateKey(headerProp.schemaProp);
pablitovicente marked this conversation as resolved.
Show resolved Hide resolved
chunks.push(key);
const [encodedValues, totalWrittenSize] = writeObject(
property,
nestedObject as GenericObject,
[],
);
// Add nested object size to total size
chunks.push(_writers.uint32(totalWrittenSize));
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let e = 0; e < encodedValues.length; e += 1) {
chunks.push(encodedValues[e]);
}
} else {
// This is the header object so it does not need to be written
if (property.schemaProp.type === 'object') {
// eslint-disable-next-line no-continue
continue;
}
const value = message[property.propertyName];
// Missing properties are not encoded as per LIP-0027
if (value === undefined) {
// eslint-disable-next-line no-continue
continue;
}

const {
schemaProp: { dataType },
binaryKey,
} = property;
if (dataType === undefined) {
throw new Error(
'Compiled Schema is corrutped as "dataType" can not be undefined.',
);
}

const binaryValue = _writers[dataType](value);

chunks.push(binaryKey);
chunks.push(binaryValue);
simpleObjectSize += binaryKey.length + binaryValue.length;
}
}
return [chunks, simpleObjectSize];
};
35 changes: 35 additions & 0 deletions elements/lisk-codec/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export interface GenericObject {
[key: string]: GenericObject | string | number | Buffer;
}
export interface SchemaPair {
readonly [key: string]: SchemaProps;
}
export interface Schema {
readonly $id: string;
readonly type: string;
properties: SchemaPair;
}
export interface SchemaProps {
readonly fieldNumber: number;
readonly type?: string;
readonly dataType?: string;
readonly properties?: SchemaPair;
readonly items?: SchemaProps;
}
export interface SchemaScalarType {
readonly dataType?: string;
readonly type?: string;
}

export interface CompiledSchema {
schemaProp: SchemaProps;
propertyName: string;
binaryKey: Buffer;
dataPath: string[];
}

export type CompiledSchemasArray = Array<CompiledSchema | CompiledSchema[]>;

export interface CompiledSchemas {
[key: string]: CompiledSchemasArray;
}
Loading