/**
* @file This file contains the definition of the class `DataContainer` which represents
* messages exchanged between the client and the server.
* @author Thorsten Kummerow <thorsten@textcontrol.com>
*/
'use strict';
const HeaderSize = 16; // Four 32 bit integers
/**
* Data container for messages
*/
class DataContainer {
/**
* Creates an instance of this class from a byte array.
* @param {Uint8Array} data The message's raw data as a byte array.
*/
constructor(data = null) {
// Command ID and parameters are integers:
/** @type {number} */
this.commandID = 0;
/** @type {number} */
this.param1 = 0;
/** @type {number} */
this.param2 = 0;
/** @type {number} */
this.param3 = 0;
/**
* Property 'data' contains the (optional) data content of the message
* @type {Uint8Array}
*/
this.data = null;
// Parse the raw data
if (data) this.fromByteArray(data);
}
/**
* Initializes this instance from a byte array.
* @param {Uint8Array} data The message's raw data as a byte array.
*/
fromByteArray(data) {
if (data.length < HeaderSize) throw new Error(`Buffer is too small to contain a command header. Bailing out. (Buffer size: ${data.length})`);
var view = new DataView(data.buffer);
this.commandID = view.getInt32(0, true);
this.param1 = view.getInt32(4, true);
this.param2 = view.getInt32(8, true);
this.param3 = view.getInt32(12, true);
if (data.length > HeaderSize) {
this.data = new Uint8Array(data.length - HeaderSize);
this.data.set(data.subarray(HeaderSize));
}
}
/**
* Converts this instance to a byte array.
* @returns {Uint8Array} A byte array representing this data container.
*/
toByteArray() {
let bufSize = HeaderSize;
if (this.data !== null) bufSize += this.data.length;
let result = new Uint8Array(bufSize);
let viewResult = new DataView(result.buffer);
viewResult.setInt32(0, this.commandID, true);
viewResult.setUint32(4, this.param1, true);
viewResult.setUint32(8, this.param2, true);
viewResult.setUint32(12, this.param3, true);
if (this.data !== null) result.set(this.data, 16);
return result;
}
/**
* Serializes a server message to JSON. The content of the "data" property is
* @returns {string} The message serialized to JSON
*/
serialize() {
let command = {
CommandID: this.commandID,
Param1: this.param1,
Param2: this.param2,
Param3: this.param3,
Data: this.dataAsObject
};
return JSON.stringify(command);
}
/**
* The data part of this message as an object.
* @readonly
* @type {Object}
*/
get dataAsObject() {
let strData = null;
try {
strData = this.dataAsString;
}
catch (err) {
// In case data does not contain a valid utf-8 string it is most likely
// raw binary data. Return that as base64 in those cases.
return Buffer.from(this.data).toString('base64');
}
if (strData === null) return null;
if (!strData.length) return {};
try {
return JSON.parse(strData);
}
catch (err) {
// In case the data does not contain a JSON string it is most likely
// raw binary data. Return that as base64 in those cases.
return Buffer.from(this.data).toString('base64');
}
}
/**
* The data part of this message as a string. Doesn't catch exceptions which arise
* when calling Buffer.from().
* @readonly
* @type {String}
*/
get dataAsString() {
if (this.data === null) return null;
if (!this.data.length) return '';
return Buffer.from(this.data, 'utf-8').toString();
}
}
/**
* Creates a DataContainer instance from a byte array.
* @param {Uint8Array} data The message's raw data as a byte array.
*/
function fromByteArray(data) {
return new DataContainer(data);
}
/**
* Creates a DataContainer instance from a JSON string.
* @param {String} strJSON The message as a JSON string.
*/
function fromJSON(strJSON) {
// Assume strJSON to be a JSON string. Caller is responsible for catching
// possible exceptions.
let obj = JSON.parse(strJSON);
if ((obj === null) || !obj.hasOwnProperty('CommandID')) {
console.error('Command string does not contain a command ID.');
return null;
}
let result = new DataContainer();
result.commandID = obj.CommandID;
try {
if (obj.hasOwnProperty('Param1')) result.param1 = obj.Param1;
if (obj.hasOwnProperty('Param2')) result.param2 = obj.Param2;
if (obj.hasOwnProperty('Param3')) result.param3 = obj.Param3;
if (obj.hasOwnProperty('Data')) {
let data = obj.Data;
if (typeof data === 'string') {
result.data = new Buffer.from(data, 'utf-8');
}
else {
// Just revert to JSON and see to it later
let strData = JSON.stringify(data);
result.data = new Buffer.from(strData, 'utf-8');
}
}
}
catch (err) { console.error(`Error in DataContainer.fromJSON: '${err.message}'`); }
return result;
}
module.exports.fromByteArray = fromByteArray;
module.exports.fromJSON = fromJSON;
module.exports.DataContainer = DataContainer;