UNPKG

@robotical/ricjs

Version:

Javascript/TS library for Robotical RIC

426 lines 14.9 kB
"use strict"; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // RICJS // Communications Library // // Rob Dobson & Chris Greening 2020-2022 // (C) 2020-2022 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const RICLog_1 = tslib_1.__importDefault(require("./RICLog")); const gt_1 = tslib_1.__importDefault(require("semver/functions/gt")); const eq_1 = tslib_1.__importDefault(require("semver/functions/eq")); class RICUtils { /** * * Add a string to a Uint8Array buffer * * @param buffer - buffer to add to * @param strToAdd - string to be added * @param startPos - start position in buffer (i.e. offset to place string at) */ static addStringToBuffer(buffer, strToAdd, startPos) { // Check valid if (buffer.length < startPos + strToAdd.length + 1) { RICLog_1.default.error("addStringToBuffer buffer too short"); return; } let curPos = startPos; for (let i = 0; i < strToAdd.length; i++) { let charAt = strToAdd.charCodeAt(i); if (charAt > 255) charAt = 255; buffer.set([charAt], curPos++); } buffer.set([0], buffer.length - 1); } /** * * Get a string from a Uint8Array buffer * * @param buffer - buffer to get from * @param startPos - start position in buffer (i.e. offset to start of string at) * @param strLen - length of string to get * @returns strGot - string got from buffer */ static getStringFromBuffer(buffer, startPos, strLen) { // Check valid if (buffer.length < startPos + strLen) { strLen = buffer.length - startPos; if (strLen <= 0) return ""; } let curPos = startPos; let outStr = ""; for (let i = 0; i < strLen; i++) { outStr += String.fromCharCode(buffer[curPos++]); } return outStr; } /** * * Debug code to format a Uint8Array to string for logging * * @param buffer - Uint8Array to be converted to hex string */ static bufferToHex(buffer) { if (buffer == null) return "null"; return Array.from(new Uint8Array(buffer)) .map((b) => RICUtils.padStartFn(b.toString(16), 2, "0")) .join(""); } /** * * Extract a big-endian float from a uint8array * * @param buf - Uint8Array containing float * @returns float */ static getBEFloatFromBuf(buf) { const revBuf = new Uint8Array(4); if (RICUtils.isLittleEndian()) { for (let i = 0; i < 4; i++) revBuf[3 - i] = buf[i]; } else { for (let i = 0; i < 4; i++) revBuf[i] = buf[i]; } const floatVal = new Float32Array(revBuf.buffer); // RICUtils.debug('revFloat ' + RICUtils.bufferToHex(revBuf) + floatVal[0]); return floatVal[0]; } /** * * Extract a big-endian uint16 from a uint8array * * @param buf - Uint8Array containing uint16 * @param pos - position (offset in buf) to get from * @returns int16 */ static getBEUint32FromBuf(buf, bufPos) { if (RICUtils.isLittleEndian()) return ((buf[bufPos] << 24) + (buf[bufPos + 1] << 16) + (buf[bufPos + 2] << 8) + buf[bufPos + 3]); return ((buf[bufPos + 3] << 24) + (buf[bufPos + 2] << 16) + (buf[bufPos + 1] << 8) + buf[bufPos]); } /** * * Extract a big-endian int16 from a uint8array * * @param buf - Uint8Array containing int16 * @param pos - position (offset in buf) to get from * @returns int16 */ static getBEInt16FromBuf(buf, bufPos) { if (RICUtils.isLittleEndian()) { const val = buf[bufPos] * 256 + buf[bufPos + 1]; return val > 32767 ? val - 65536 : val; } const val = buf[bufPos + 1] * 256 + buf[bufPos]; return val > 32767 ? val - 65536 : val; } /** * * Extract a big-endian uint16 from a uint8array * * @param buf - Uint8Array containing uint16 * @param pos - position (offset in buf) to get from * @returns int16 */ static getBEUint16FromBuf(buf, bufPos) { if (RICUtils.isLittleEndian()) return buf[bufPos] * 256 + buf[bufPos + 1]; return buf[bufPos + 1] * 256 + buf[bufPos]; } /** * * Extract a big-endian uint8 from a uint8array * * @param buf - Uint8Array containing uint8 * @param pos - position (offset in buf) to get from * @returns int16 */ static getBEUint8FromBuf(buf, pos) { return buf[pos]; } /** * * Extract a big-endian int8 from a uint8array * * @param buf - Uint8Array containing int8 * @param pos - position (offset in buf) to get from * @returns int16 */ static getBEInt8FromBuf(buf, pos) { return buf[pos] > 127 ? buf[pos] - 256 : buf[pos]; } static isLittleEndian() { // If already known just return if (this._isEndianSet) return this._isLittleEndian; // Figure out endian-ness const a = new Uint32Array([0x12345678]); const b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength); this._isEndianSet = true; this._isLittleEndian = b[0] != 0x12; return this._isLittleEndian; // RICUtils.debug("bigendian " + BigEndian); // let buf = new ArrayBuffer(2); // let shView = new Uint16Array(buf); // buf[0] = 1; // buf[1] = 0; // RICUtils.debug("Resulting short " + shView[0]); // return shView[0] == 1; } // The following code from https://github.com/jsdom/abab /** * Implementation of atob() according to the HTML and Infra specs, except that * instead of throwing INVALID_CHARACTER_ERR we return null. * * @param data - string containing base64 representation * @returns Uint8Array * */ static atob(data) { // Web IDL requires DOMStrings to just be converted using ECMAScript // ToString, which in our case amounts to using a template literal. data = `${data}`; // "Remove all ASCII whitespace from data." data = data.replace(/[ \t\n\f\r]/g, ""); // "If data's length divides by 4 leaving no remainder, then: if data ends // with one or two U+003D (=) code points, then remove them from data." if (data.length % 4 === 0) { data = data.replace(/==?$/, ""); } // "If data's length divides by 4 leaving a remainder of 1, then return // failure." // // "If data contains a code point that is not one of // // U+002B (+) // U+002F (/) // ASCII alphanumeric // // then return failure." if (data.length % 4 === 1 || /[^+/0-9A-Za-z]/.test(data)) { return null; } // "Let output be an empty byte sequence." const output = new Uint8Array((data.length * 3) / 4 + 1); // "Let buffer be an empty buffer that can have bits appended to it." // // We append bits via left-shift and or. accumulatedBits is used to track // when we've gotten to 24 bits. let buffer = 0; let accumulatedBits = 0; // "Let position be a position variable for data, initially pointing at the // start of data." // // "While position does not point past the end of data:" let bytePos = 0; for (let i = 0; i < data.length; i++) { // "Find the code point pointed to by position in the second column of // Table 1: The Base 64 Alphabet of RFC 4648. Let n be the number given in // the first cell of the same row. // // "Append to buffer the six bits corresponding to n, most significant bit // first." // // atobLookup() implements the table from RFC 4648. buffer <<= 6; const atobVal = RICUtils.atobLookup(data[i]); if (atobVal !== undefined) { buffer |= atobVal; } accumulatedBits += 6; // "If buffer has accumulated 24 bits, interpret them as three 8-bit // big-endian numbers. Append three bytes with values equal to those // numbers to output, in the same order, and then empty buffer." if (accumulatedBits === 24) { output[bytePos++] = (buffer & 0xff0000) >> 16; output[bytePos++] = (buffer & 0xff00) >> 8; output[bytePos++] = buffer & 0xff; buffer = accumulatedBits = 0; } // "Advance position by 1." } // "If buffer is not empty, it contains either 12 or 18 bits. If it contains // 12 bits, then discard the last four and interpret the remaining eight as // an 8-bit big-endian number. If it contains 18 bits, then discard the last // two and interpret the remaining 16 as two 8-bit big-endian numbers. Append // the one or two bytes with values equal to those one or two numbers to // output, in the same order." if (accumulatedBits === 12) { buffer >>= 4; output[bytePos++] = buffer & 0xff; } else if (accumulatedBits === 18) { buffer >>= 2; output[bytePos++] = (buffer & 0xff00) >> 8; output[bytePos++] = buffer & 0xff; } // "Return output." return output.slice(0, bytePos); } /** * A lookup table for atob(), which converts an ASCII character to the * corresponding six-bit number. */ static atobLookup(chr) { if (/[A-Z]/.test(chr)) { return chr.charCodeAt(0) - "A".charCodeAt(0); } if (/[a-z]/.test(chr)) { return chr.charCodeAt(0) - "a".charCodeAt(0) + 26; } if (/[0-9]/.test(chr)) { return chr.charCodeAt(0) - "0".charCodeAt(0) + 52; } if (chr === "+") { return 62; } if (chr === "/") { return 63; } // Throw exception; should not be hit in tests return undefined; } /** * btoa() as defined by the HTML and Infra specs, which mostly just references * RFC 4648. * * @param data - Uint8Array * @returns string containing base64 representation * */ static btoa(inBuf) { let i; // String conversion as required by Web IDL. // s = `${s}`; // "The btoa() method must throw an "InvalidCharacterError" DOMException if // data contains any character whose code point is greater than U+00FF." // for (i = 0; i < s.length; i++) { // if (s.charCodeAt(i) > 255) { // return null; // } // } let out = ""; for (i = 0; i < inBuf.length; i += 3) { const groupsOfSix = [ undefined, undefined, undefined, undefined, ]; groupsOfSix[0] = inBuf[i] >> 2; groupsOfSix[1] = (inBuf[i] & 0x03) << 4; if (inBuf.length > i + 1) { groupsOfSix[1] |= inBuf[i + 1] >> 4; groupsOfSix[2] = (inBuf[i + 1] & 0x0f) << 2; if (inBuf.length > i + 2) { groupsOfSix[2] |= inBuf[i + 2] >> 6; groupsOfSix[3] = inBuf[i + 2] & 0x3f; } } for (let j = 0; j < groupsOfSix.length; j++) { if (typeof groupsOfSix[j] === "undefined") { out += "="; } else { out += this.btoaLookup(groupsOfSix[j]); } } } return out; } /** * Lookup table for btoa(), which converts a six-bit number into the * corresponding ASCII character. */ static btoaLookup(idx) { if (idx === undefined) { return undefined; } if (idx < 26) { return String.fromCharCode(idx + "A".charCodeAt(0)); } if (idx < 52) { return String.fromCharCode(idx - 26 + "a".charCodeAt(0)); } if (idx < 62) { return String.fromCharCode(idx - 52 + "0".charCodeAt(0)); } if (idx === 62) { return "+"; } if (idx === 63) { return "/"; } // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. return undefined; } static buf2hex(buffer) { return Array.prototype.map .call(buffer, (x) => ("00" + x.toString(16)).slice(-2)) .join(""); } static padStartFn(inStr, targetLength, padString) { targetLength = targetLength >> 0; //truncate if number or convert non-number to 0; padString = String(typeof padString !== "undefined" ? padString : " "); if (inStr.length > targetLength) { return String(inStr); } else { targetLength = targetLength - inStr.length; if (targetLength > padString.length) { padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed } return padString.slice(0, targetLength) + String(inStr); } } static isVersionGreater(v1, v2) { try { return (0, gt_1.default)(v1, v2); } catch (e) { // one of the two versions is invalid, return true return true; } } static isVersionEqual(v1, v2) { try { return (0, eq_1.default)(v1, v2); } catch (e) { // one of the two versions is invalid, return false return false; } } static withTimeout(ms, promise) { let id; const timeout = new Promise((_, reject) => { id = setTimeout(() => { clearTimeout(id); reject('Timed out in ' + ms + 'ms.'); }, ms); }); return Promise.race([ promise, timeout ]); } } exports.default = RICUtils; RICUtils._isEndianSet = false; RICUtils._isLittleEndian = false; //# sourceMappingURL=RICUtils.js.map