src/codec.js
// Copyright © 2020 Hoani Bryson
// License: MIT (https://mit-license.org/)
//
// Codec
//
// L3aP codec for encoding and decoding packets
//
const utf8 = require('utf8');
const packet = require('./packet');
const typeCodec = require('./typeCodec');
const mapGenerator = require('./mapGenerator');
const verify = require('./verify');
exports.Codec = class Codec {
constructor(input) {
this.is_valid = false;
this.encode_map = {};
this.decode_map = {};
this.start_to_category_map = {};
this._config = {};
if (verify.verify_quiet(input)) {
this._config = input;
this.is_valid = true;
}
if (this.is_valid) {
[this.encode_map, this.decode_map] = mapGenerator.generate_maps(
this._config
);
this._map_categories();
}
}
valid() {
return this.is_valid;
}
encode(packets){
if (this._is_valid == false) {
return utf8.encode("");
}
if ((packets instanceof packet.Packet) == true) {
packets = [packets];
}
else if (typeof packets != "object") {
return utf8.encode("");
}
let encoded = "";
for (let i in packets) {
if (encoded != "") {
encoded += this._config["end"];
}
const encoded_packet = this._encode_packet(packets[i]);
if (encoded_packet == null) {
return utf8.encode('');
}
else {
encoded += encoded_packet;
}
}
encoded += this._config["end"];
return utf8.encode(encoded);
}
_encode_packet(_packet) {
let internal = "";
const start = this._config["category"][_packet.category];
if ((_packet.category in this._config.category) === false) {
return null;
}
for (let j in _packet.paths) {
const path = _packet.paths[j];
const payload = _packet.payloads[j];
if (path != null) {
// Detect compound packet and add compound character
if (internal != "") {
internal += this._config["compound"];
}
const encoded_single = this._encode_single(path, payload);
if (encoded_single != null) {
internal += encoded_single;
}
else {
return null;
}
}
}
return start + internal;
}
_encode_single(path, payload) {
let encode_data;
let encoded = "";
if (path in this.encode_map) {
encode_data = this.encode_map[path];
}
else {
console.log(`Could not encode with invalid branch: ${path}`);
return null;
}
encoded += encode_data.addr;
if (payload != null) {
const count = Math.min(encode_data.types.length, payload.length);
for (let i = 0; i < count; i++) {
encoded += this._config["separator"];
encoded += typeCodec.encode_types(payload[i], encode_data.types[i]);
}
}
return encoded;
}
decode(encoded) {
if (this._is_valid == false) {
return [encoded, []];
}
let strings = encoded.split(
utf8.encode(this._config["end"])
);
const remainder = strings.slice(strings.length-1, strings.length);
const packets = [];
if (strings.length == 1) {
return [remainder, []];
}
strings = strings.slice(0, strings.length - 1);
for(let i in strings) {
const p = this._decode_packet(strings[i]);
if (p != null) {
packets.push(p);
}
}
return [remainder, packets];
}
_decode_packet(string) {
string = utf8.decode(string);
const start = string[0];
const category = this._category_from_start(start);
if (category == null) {
return null;
}
else {
let _packet = new packet.Packet(category);
string = string.slice(1, string.length);
const subpackets = string.split(this._config["compound"]);
for (let j in subpackets) {
const data = this._decode_subpacket(subpackets[j]);
if (data != null) {
const [path, payload] = data;
_packet.add(path, payload);
}
else {
_packet = null;
}
}
return _packet;
}
}
_decode_subpacket(subpacket) {
const parts = subpacket.split(this._config["separator"]);
if (parts != ['']) {
const payload = [];
const addr = parts[0];
if (addr in this.decode_map) {
const decode_data = this.decode_map[addr];
const encoded_payload = parts.slice(1, parts.length);
for (let k = 0; k < encoded_payload.length; k++) {
const item = encoded_payload[k];
const type = decode_data.types[k];
payload.push(typeCodec.decode_types(item, type));
}
return [decode_data.path, payload]
}
}
return null;
}
unpack(_packet) {
const result = {};
for (let i = 0; i < _packet.paths.length; i++) {
const path = _packet.paths[i];
const payload = _packet.payloads[i];
const unpack_data = this.encode_map[path];
const value_count = Math.min(
payload.length,
unpack_data.data_branches.length
);
for (let j = 0; j < value_count; j++) {
const branch = unpack_data.data_branches[j];
const value = payload[j];
result[branch] = value;
}
}
return result;
}
_map_categories() {
const categories = this._config['category'];
const keys = Object.keys(categories);
for (i in keys) {
const key = keys[i];
const start = categories[key];
this.start_to_category_map[start] = key;
}
}
_category_from_start(start) {
if (start in this.start_to_category_map) {
return this.start_to_category_map[start];
}
return null;
}
}