UNPKG

bali-component-framework

Version:

This library provides a JavaScript based implementation of the Bali Nebula™ Component Framework.

545 lines (480 loc) 17.2 kB
/************************************************************************ * Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. * ************************************************************************ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * * * This code is free software; you can redistribute it and/or modify it * * under the terms of The MIT License (MIT), as published by the Open * * Source Initiative. (See http://opensource.org/licenses/MIT) * ************************************************************************/ 'use strict'; /** * This class implements byte encoding and decoding methods for the following formats: * <pre> * * short * * integer * * base 2 * * base 16 * * base 32 * * base 64 * </pre> */ // This private constant sets the line width for formatting encoded byte strings. const LINE_WIDTH = 60; // This private constant sets the POSIX end of line character const EOL = '\n'; // The symbol lookup tables const base02LookupTable = '01'; const base16LookupTable = '0123456789ABCDEF'; const base32LookupTable = '0123456789ABCDFGHJKLMNPQRSTVWXYZ'; // missing 'E', 'I', 'O', and 'U' // PUBLIC FUNCTIONS /** * This function returns a decoder object that can perform byte encoding and decoding. * * An optional debug argument may be specified that controls the level of debugging that * should be applied during execution. The allowed levels are as follows: * <pre> * 0: no debugging is applied (this is the default value and has the best performance) * 1: log any exceptions to console.error before throwing them * 2: perform argument validation checks on each call (poor performance) * 3: log interesting arguments, states and results to console.log * </pre> * * @returns {Decoder} The new decoder. */ const Decoder = function(debug) { this.debug = debug || 0; return this; }; Decoder.prototype.constructor = Decoder; exports.Decoder = Decoder; /** * This method encodes the bytes in a data buffer into a base 2 string. * * @param {Buffer} buffer A data buffer containing the integer. * @param {Number} indentation The number of levels of indentation that should be prepended * to each formatted line. The default is zero. * @return {String} The base 2 encoded string. */ Decoder.prototype.base02Encode = function(buffer, indentation) { indentation = indentation || 0; // encode each byte var string = ''; buffer.forEach(function(byte) { // encode each bit for (var b = 7; b >= 0; b--) { const mask = 1 << b; const bit = (byte & mask) >>> b; string += base02LookupTable[bit]; } }, this); // break the string into formatted lines const base02 = formatLines(string, indentation); return base02; }; /** * This method decodes a base 2 encoded string into a data buffer containing the * decoded bytes. * * @param {String} base02 The base 2 encoded string. * @return {Buffer} A data buffer containing the decoded bytes. */ Decoder.prototype.base02Decode = function(base02) { // validate the base 2 encoded string base02 = base02.replace(/\s/g, ''); // strip out whitespace const length = base02.length; if (length % 8 !== 0) { const exception = Error('The number of characters in the base 2 binary string was not divisible by 8.'); if (this.debug > 0) console.error(exception); throw exception; } // decode each base 2 character const buffer = Buffer.alloc(length / 8); var index = 0; while (index < length - 7) { // decode one byte var byte = 0; for (var b = 7; b >= 0; b--) { const character = base02[index++]; const bit = base02LookupTable.indexOf(character); if (bit < 0) { const exception = Error('The binary string was not encoded using base 2.'); if (this.debug > 0) console.error(exception); throw exception; } byte |= (bit << b); } // append byte to binary string buffer[index / 8 - 1] = byte; } return buffer; }; /** * This method encodes the bytes in a data buffer into a base 16 string. * * @param {Buffer} buffer A data buffer containing the bytes to be encoded. * @param {Number} indentation The number of levels of indentation that should be prepended * to each formatted line. The default is zero. * @return {String} The base 16 encoded string. */ Decoder.prototype.base16Encode = function(buffer, indentation) { indentation = indentation || 0; // encode the bytes var string = ''; buffer.forEach(function(byte) { const highOrderNybble = (byte & 0xF0) >>> 4; string += base16LookupTable[highOrderNybble]; const lowOrderNybble = byte & 0x0F; string += base16LookupTable[lowOrderNybble]; }, this); // break the string into formatted lines const base16 = formatLines(string, indentation); return base16; }; /** * This method decodes a base 16 encoded string into a data buffer containing the * decoded bytes. * * @param {String} base16 The base 16 encoded string. * @return {Buffer} A data buffer containing the decoded bytes. */ Decoder.prototype.base16Decode = function(base16) { // validate the base 16 encoded string base16 = base16.replace(/\s/g, ''); // strip out whitespace base16 = base16.toUpperCase(); const length = base16.length; if (length % 2 !== 0) { const exception = Error('The number of characters in the base 16 binary string was not divisible by 2.'); if (this.debug > 0) console.error(exception); throw exception; } // decode each base 16 character const buffer = Buffer.alloc(length / 2); var index = 0; while (index < length - 1) { // decode the character for the high order nybble var character = base16[index++]; const highOrderNybble = base16LookupTable.indexOf(character); if (highOrderNybble < 0) { const exception = Error('The binary string was not encoded using base 16.'); if (this.debug > 0) console.error(exception); throw exception; } // decode the character for the low order nybble character = base16[index++]; const lowOrderNybble = base16LookupTable.indexOf(character); if (lowOrderNybble < 0) { const exception = Error('The binary string was not encoded using base 16.'); if (this.debug > 0) console.error(exception); throw exception; } // combine the nybbles to form the byte const charCode = (highOrderNybble << 4) | lowOrderNybble; buffer[index / 2 - 1] = charCode; } return buffer; }; /** * This method encodes the bytes in a data buffer into a base 32 string. * * @param {Buffer} buffer A data buffer containing the bytes to be encoded. * @param {Number} indentation The number of levels of indentation that should be prepended * to each formatted line. The default is zero. * @return {String} The base 32 encoded string. */ Decoder.prototype.base32Encode = function(buffer, indentation) { indentation = indentation || 0; // encode each byte var string = ''; const length = buffer.length; for (var i = 0; i < length; i++) { const previousByte = buffer[i - 1]; // ignored when i is zero const currentByte = buffer[i]; // encode next one or two 5 bit chunks string = base32EncodeBytes(previousByte, currentByte, i, string); } // encode the last 5 bit chunk const lastByte = buffer[length - 1]; string = base32EncodeLast(lastByte, length - 1, string); // break the string into formatted lines const base32 = formatLines(string, indentation); return base32; }; /** * This method decodes a base 32 encoded string into a data buffer containing the * decoded bytes. * * @param {String} base32 The base 32 encoded string. * @return {Buffer} A data buffer containing the decoded bytes. */ Decoder.prototype.base32Decode = function(base32) { // validate the base 32 encoded string base32 = base32.replace(/\s/g, ''); // strip out whitespace base32 = base32.toUpperCase(); const length = base32.length; var character; var chunk; // decode each base 32 character const buffer = Buffer.alloc(Math.floor(length * 5 / 8)); for (var i = 0; i < length; i++) { character = base32[i]; chunk = base32LookupTable.indexOf(character); if (chunk < 0) { const exception = Error('The binary string was not encoded using base 32.'); if (this.debug > 0) console.error(exception); throw exception; } if (i < length - 1) { base32DecodeBytes(chunk, i, buffer); } else { base32DecodeLast(chunk, i, buffer); } } return buffer; }; /** * This method encodes the bytes in a data buffer into a base 64 string. * * @param {Buffer} buffer A data buffer containing the bytes to be encoded. * @param {Number} indentation The number of levels of indentation that should be prepended * to each formatted line. The default is zero. * @return {String} The base 64 encoded string. */ Decoder.prototype.base64Encode = function(buffer, indentation) { indentation = indentation || 0; // format as indented 80 character blocks const string = buffer.toString('base64'); // break the string into formatted lines const base64 = formatLines(string, indentation); return base64; }; /** * This method decodes a base 64 encoded string into a data buffer containing the * decoded bytes. * * @param {String} base64 The base 64 encoded string. * @return {Buffer} A data buffer containing the decoded bytes. */ Decoder.prototype.base64Decode = function(base64) { return Buffer.from(base64, 'base64'); }; /** * This method converts a short into a data buffer containing its corresponding bytes * in 'big endian' order. * * @param {Number} short The short to be converted. * @return {Buffer} A data buffer containing the corresponding bytes. */ Decoder.prototype.shortToBytes = function(short) { const buffer = Buffer.alloc(2); for (var i = 0; i < 2; i++) { const byte = short >> (i * 8) & 0xFF; buffer[1 - i] = byte; } return buffer; }; /** * This method converts the bytes in a data buffer in 'big endian' order to its * corresponding short value. * * @param {Buffer} buffer A data buffer containing the bytes for the short. * @return {Number} The corresponding short value. */ Decoder.prototype.bytesToShort = function(buffer) { var short = 0; for (var i = 0; i < 2; i++) { const byte = buffer[1 - i]; short |= byte << (i * 8); } return short; }; /** * This method converts an integer into a data buffer containing its corresponding bytes * in 'big endian' order. * * @param {Number} integer The integer to be converted. * @return {Buffer} A data buffer containing the corresponding bytes. */ Decoder.prototype.integerToBytes = function(integer) { const buffer = Buffer.alloc(4); for (var i = 0; i < 4; i++) { const byte = integer >> (i * 8) & 0xFF; buffer[3 - i] = byte; } return buffer; }; /** * This method converts the bytes in a data buffer in 'big endian' order to its * corresponding integer value. * * @param {Buffer} buffer The buffer containing the bytes for the integer. * @return {Number} The corresponding integer value. */ Decoder.prototype.bytesToInteger = function(buffer) { var integer = 0; for (var i = 0; i < 4; i++) { const byte = buffer[3 - i]; integer |= byte << (i * 8); } return integer; }; // PRIVATE FUNCTIONS /* * offset: 0 1 2 3 4 0 * byte: 00000111|11222223|33334444|45555566|66677777|... * mask: F8 07 C0 3E 01 F0 0F 80 7C 03 E0 1F F8 07 */ const base32EncodeBytes = function(previous, current, byteIndex, base32) { var chunk; const offset = byteIndex % 5; switch (offset) { case 0: chunk = (current & 0xF8) >>> 3; base32 += base32LookupTable[chunk]; break; case 1: chunk = ((previous & 0x07) << 2) | ((current & 0xC0) >>> 6); base32 += base32LookupTable[chunk]; chunk = (current & 0x3E) >>> 1; base32 += base32LookupTable[chunk]; break; case 2: chunk = ((previous & 0x01) << 4) | ((current & 0xF0) >>> 4); base32 += base32LookupTable[chunk]; break; case 3: chunk = ((previous & 0x0F) << 1) | ((current & 0x80) >>> 7); base32 += base32LookupTable[chunk]; chunk = (current & 0x7C) >>> 2; base32 += base32LookupTable[chunk]; break; case 4: chunk = ((previous & 0x03) << 3) | ((current & 0xE0) >>> 5); base32 += base32LookupTable[chunk]; chunk = current & 0x1F; base32 += base32LookupTable[chunk]; break; } return base32; }; /* * Same as normal, but pad with 0's in "next" byte * case: 0 1 2 3 4 * byte: xxxxx111|00xxxxx3|00004444|0xxxxx66|000xxxxx|... * mask: F8 07 C0 3E 01 F0 0F 80 7C 03 E0 1F */ const base32EncodeLast = function(last, byteIndex, base32) { var chunk; const offset = byteIndex % 5; switch (offset) { case 0: chunk = (last & 0x07) << 2; base32 += base32LookupTable[chunk]; break; case 1: chunk = (last & 0x01) << 4; base32 += base32LookupTable[chunk]; break; case 2: chunk = (last & 0x0F) << 1; base32 += base32LookupTable[chunk]; break; case 3: chunk = (last & 0x03) << 3; base32 += base32LookupTable[chunk]; break; case 4: // nothing to do, was handled by previous call break; } return base32; }; /* * offset: 0 1 2 3 4 0 * byte: 00000111|11222223|33334444|45555566|66677777|... * mask: F8 07 C0 3E 01 F0 0F 80 7C 03 E0 1F F8 07 */ const base32DecodeBytes = function(chunk, characterIndex, buffer) { const byteIndex = Math.floor(characterIndex * 5 / 8); const offset = characterIndex % 8; switch (offset) { case 0: buffer[byteIndex] |= chunk << 3; break; case 1: buffer[byteIndex] |= chunk >>> 2; buffer[byteIndex + 1] |= chunk << 6; break; case 2: buffer[byteIndex] |= chunk << 1; break; case 3: buffer[byteIndex] |= chunk >>> 4; buffer[byteIndex + 1] |= chunk << 4; break; case 4: buffer[byteIndex] |= chunk >>> 1; buffer[byteIndex + 1] |= chunk << 7; break; case 5: buffer[byteIndex] |= chunk << 2; break; case 6: buffer[byteIndex] |= chunk >>> 3; buffer[byteIndex + 1] |= chunk << 5; break; case 7: buffer[byteIndex] |= chunk; break; } }; /* * Same as normal, but pad with 0's in "next" byte * case: 0 1 2 3 4 * byte: xxxxx111|00xxxxx3|00004444|0xxxxx66|00077777|... * mask: F8 07 C0 3E 01 F0 0F 80 7C 03 E0 1F */ const base32DecodeLast = function(chunk, characterIndex, buffer) { const byteIndex = Math.floor(characterIndex * 5 / 8); const offset = characterIndex % 8; switch (offset) { case 1: buffer[byteIndex] |= chunk >>> 2; break; case 3: buffer[byteIndex] |= chunk >>> 4; break; case 4: buffer[byteIndex] |= chunk >>> 1; break; case 6: buffer[byteIndex] |= chunk >>> 3; break; case 7: buffer[byteIndex] |= chunk; break; } }; /** * This function returns a formatted version of a string with LINE_WIDTH characters per line. * * @param {String} string The string to be formatted. * @param {Number} indentation The number of levels of indentation to be prepended to each line of the result. * @returns {String} The formatted string. */ const formatLines = function(string, indentation) { var prefix = ''; for (var i = 0; i < indentation; i++) prefix += ' '; var formatted = ''; const length = string.length; if (length > LINE_WIDTH) { for (var index = 0; index < length; index += LINE_WIDTH) { formatted += EOL + prefix; formatted += string.substring(index, index + LINE_WIDTH); } formatted += EOL; } else { formatted += string; } return formatted; };