From 659325c85a2957bfd08ee9d66c001aebac3a3eda Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 3 Feb 2023 17:10:33 +0000 Subject: [PATCH] Improved performance of str/array buffer conversions --- src/core/Utils.mjs | 29 ++++++++++++-------- src/core/lib/Base64.mjs | 6 ++-- src/core/operations/FromBCD.mjs | 2 +- src/core/operations/FromCharcode.mjs | 6 ++-- src/core/operations/HammingDistance.mjs | 4 +-- src/core/operations/ParseIPv4Header.mjs | 2 +- src/core/operations/ParseX509Certificate.mjs | 2 +- src/core/operations/PlayMedia.mjs | 2 +- 8 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 0ce95987..4f938d6a 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -474,6 +474,7 @@ class Utils { static strToArrayBuffer(str) { log.debug(`Converting string[${str?.length}] to array buffer`); if (!str) return new ArrayBuffer; + const arr = new Uint8Array(str.length); let i = str.length, b; while (i--) { @@ -502,9 +503,10 @@ class Utils { static strToUtf8ArrayBuffer(str) { log.debug(`Converting string[${str?.length}] to UTF8 array buffer`); if (!str) return new ArrayBuffer; - const utf8Str = utf8.encode(str); - if (str.length !== utf8Str.length) { + const buffer = new TextEncoder("utf-8").encode(str); + + if (str.length !== buffer.length) { if (isWorkerEnvironment() && self && typeof self.setOption === "function") { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { @@ -512,7 +514,7 @@ class Utils { } } - return Utils.strToArrayBuffer(utf8Str); + return buffer.buffer; } @@ -627,20 +629,24 @@ class Utils { static byteArrayToUtf8(byteArray) { log.debug(`Converting byte array[${byteArray?.length}] to UTF8`); if (!byteArray || !byteArray.length) return ""; - const str = Utils.byteArrayToChars(byteArray); + if (!(byteArray instanceof Uint8Array)) + byteArray = new Uint8Array(byteArray); + try { - const utf8Str = utf8.decode(str); - if (str.length !== utf8Str.length) { + const str = new TextDecoder("utf-8", {fatal: true}).decode(byteArray); + + if (str.length !== byteArray.length) { if (isWorkerEnvironment()) { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { window.app.options.attemptHighlight = false; } } - return utf8Str; + + return str; } catch (err) { // If it fails, treat it as ANSI - return str; + return Utils.byteArrayToChars(byteArray); } } @@ -662,9 +668,10 @@ class Utils { log.debug(`Converting byte array[${byteArray?.length}] to chars`); if (!byteArray || !byteArray.length) return ""; let str = ""; - // String concatenation appears to be faster than an array join - for (let i = 0; i < byteArray.length;) { - str += String.fromCharCode(byteArray[i++]); + // Maxiumum arg length for fromCharCode is 65535, but the stack may already be fairly deep, + // so don't get too near it. + for (let i = 0; i < byteArray.length; i += 20000) { + str += String.fromCharCode(...(byteArray.slice(i, i+20000))); } return str; } diff --git a/src/core/lib/Base64.mjs b/src/core/lib/Base64.mjs index c26630b9..aeda98cf 100644 --- a/src/core/lib/Base64.mjs +++ b/src/core/lib/Base64.mjs @@ -25,12 +25,12 @@ import OperationError from "../errors/OperationError.mjs"; */ export function toBase64(data, alphabet="A-Za-z0-9+/=") { if (!data) return ""; + if (typeof data == "string") { + data = Utils.strToArrayBuffer(data); + } if (data instanceof ArrayBuffer) { data = new Uint8Array(data); } - if (typeof data == "string") { - data = Utils.strToByteArray(data); - } alphabet = Utils.expandAlphRange(alphabet).join(""); if (alphabet.length !== 64 && alphabet.length !== 65) { // Allow for padding diff --git a/src/core/operations/FromBCD.mjs b/src/core/operations/FromBCD.mjs index e58116d3..8fa990a4 100644 --- a/src/core/operations/FromBCD.mjs +++ b/src/core/operations/FromBCD.mjs @@ -84,7 +84,7 @@ class FromBCD extends Operation { break; case "Raw": default: - byteArray = Utils.strToByteArray(input); + byteArray = new Uint8Array(Utils.strToArrayBuffer(input)); byteArray.forEach(b => { nibbles.push(b >>> 4); nibbles.push(b & 15); diff --git a/src/core/operations/FromCharcode.mjs b/src/core/operations/FromCharcode.mjs index 052b1162..7857802b 100644 --- a/src/core/operations/FromCharcode.mjs +++ b/src/core/operations/FromCharcode.mjs @@ -26,7 +26,7 @@ class FromCharcode extends Operation { this.description = "Converts unicode character codes back into text.

e.g. 0393 03b5 03b9 03ac 20 03c3 03bf 03c5 becomes Γειά σου"; this.infoURL = "https://wikipedia.org/wiki/Plane_(Unicode)"; this.inputType = "string"; - this.outputType = "byteArray"; + this.outputType = "ArrayBuffer"; this.args = [ { "name": "Delimiter", @@ -44,7 +44,7 @@ class FromCharcode extends Operation { /** * @param {string} input * @param {Object[]} args - * @returns {byteArray} + * @returns {ArrayBuffer} * * @throws {OperationError} if base out of range */ @@ -77,7 +77,7 @@ class FromCharcode extends Operation { for (i = 0; i < bites.length; i++) { latin1 += Utils.chr(parseInt(bites[i], base)); } - return Utils.strToByteArray(latin1); + return Utils.strToArrayBuffer(latin1); } } diff --git a/src/core/operations/HammingDistance.mjs b/src/core/operations/HammingDistance.mjs index 60121a75..7d5a9b1d 100644 --- a/src/core/operations/HammingDistance.mjs +++ b/src/core/operations/HammingDistance.mjs @@ -68,8 +68,8 @@ class HammingDistance extends Operation { samples[0] = fromHex(samples[0]); samples[1] = fromHex(samples[1]); } else { - samples[0] = Utils.strToByteArray(samples[0]); - samples[1] = Utils.strToByteArray(samples[1]); + samples[0] = new Uint8Array(Utils.strToArrayBuffer(samples[0])); + samples[1] = new Uint8Array(Utils.strToArrayBuffer(samples[1])); } let dist = 0; diff --git a/src/core/operations/ParseIPv4Header.mjs b/src/core/operations/ParseIPv4Header.mjs index 304ba0c0..84351cdc 100644 --- a/src/core/operations/ParseIPv4Header.mjs +++ b/src/core/operations/ParseIPv4Header.mjs @@ -49,7 +49,7 @@ class ParseIPv4Header extends Operation { if (format === "Hex") { input = fromHex(input); } else if (format === "Raw") { - input = Utils.strToByteArray(input); + input = new Uint8Array(Utils.strToArrayBuffer(input)); } else { throw new OperationError("Unrecognised input format."); } diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs index 88678de9..11e63424 100644 --- a/src/core/operations/ParseX509Certificate.mjs +++ b/src/core/operations/ParseX509Certificate.mjs @@ -71,7 +71,7 @@ class ParseX509Certificate extends Operation { cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); break; case "Raw": - cert.readCertHex(toHex(Utils.strToByteArray(input), "")); + cert.readCertHex(toHex(Utils.strToArrayBuffer(input), "")); break; default: undefinedInputFormat = true; diff --git a/src/core/operations/PlayMedia.mjs b/src/core/operations/PlayMedia.mjs index f16684c7..22b7d8a2 100644 --- a/src/core/operations/PlayMedia.mjs +++ b/src/core/operations/PlayMedia.mjs @@ -77,7 +77,7 @@ class PlayMedia extends Operation { * Displays an audio or video element that may be able to play the media * file. * - * @param data {byteArray} Data containing an audio or video file. + * @param {byteArray} data Data containing an audio or video file. * @returns {string} Markup to display a media player. */ async present(data) {