From 94aa2774790d87ae3f0e5471eb76c4112d84e9dc Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 20 Jan 2026 12:01:26 +0100 Subject: [PATCH] Add util functions for uint8 array conversion --- libs/common/src/platform/misc/utils.spec.ts | 140 +++++++++++++++++++- libs/common/src/platform/misc/utils.ts | 66 ++++++++- 2 files changed, 203 insertions(+), 3 deletions(-) diff --git a/libs/common/src/platform/misc/utils.spec.ts b/libs/common/src/platform/misc/utils.spec.ts index 664c6e22b3a..032b03fc3e2 100644 --- a/libs/common/src/platform/misc/utils.spec.ts +++ b/libs/common/src/platform/misc/utils.spec.ts @@ -417,6 +417,142 @@ describe("Utils Service", () => { // }); }); + describe("fromArrayToHex(...)", () => { + const originalIsNode = Utils.isNode; + + afterEach(() => { + Utils.isNode = originalIsNode; + }); + + runInBothEnvironments("should convert a Uint8Array to a hex string", () => { + const arr = new Uint8Array([0x00, 0x01, 0x02, 0x0a, 0xff]); + const hexString = Utils.fromArrayToHex(arr); + expect(hexString).toBe("0001020aff"); + }); + + runInBothEnvironments("should return null for null input", () => { + const hexString = Utils.fromArrayToHex(null); + expect(hexString).toBeNull(); + }); + + runInBothEnvironments("should return empty string for an empty Uint8Array", () => { + const arr = new Uint8Array([]); + const hexString = Utils.fromArrayToHex(arr); + expect(hexString).toBe(""); + }); + }); + + describe("fromArrayToB64(...)", () => { + const originalIsNode = Utils.isNode; + + afterEach(() => { + Utils.isNode = originalIsNode; + }); + + runInBothEnvironments("should convert a Uint8Array to a b64 string", () => { + const arr = new Uint8Array(asciiHelloWorldArray); + const b64String = Utils.fromArrayToB64(arr); + expect(b64String).toBe(b64HelloWorldString); + }); + + runInBothEnvironments("should return null for null input", () => { + const b64String = Utils.fromArrayToB64(null); + expect(b64String).toBeNull(); + }); + + runInBothEnvironments("should return empty string for an empty Uint8Array", () => { + const arr = new Uint8Array([]); + const b64String = Utils.fromArrayToB64(arr); + expect(b64String).toBe(""); + }); + }); + + describe("fromArrayToUrlB64(...)", () => { + const originalIsNode = Utils.isNode; + + afterEach(() => { + Utils.isNode = originalIsNode; + }); + + runInBothEnvironments("should convert a Uint8Array to a URL-safe b64 string", () => { + // Input that produces +, /, and = in standard base64 + const arr = new Uint8Array([251, 255, 254]); + const urlB64String = Utils.fromArrayToUrlB64(arr); + // Standard b64 would be "+//+" with padding, URL-safe removes padding and replaces chars + expect(urlB64String).not.toContain("+"); + expect(urlB64String).not.toContain("/"); + expect(urlB64String).not.toContain("="); + }); + + runInBothEnvironments("should return null for null input", () => { + const urlB64String = Utils.fromArrayToUrlB64(null); + expect(urlB64String).toBeNull(); + }); + + runInBothEnvironments("should return empty string for an empty Uint8Array", () => { + const arr = new Uint8Array([]); + const urlB64String = Utils.fromArrayToUrlB64(arr); + expect(urlB64String).toBe(""); + }); + }); + + describe("fromArrayToByteString(...)", () => { + const originalIsNode = Utils.isNode; + + afterEach(() => { + Utils.isNode = originalIsNode; + }); + + runInBothEnvironments("should convert a Uint8Array to a byte string", () => { + const arr = new Uint8Array(asciiHelloWorldArray); + const byteString = Utils.fromArrayToByteString(arr); + expect(byteString).toBe(asciiHelloWorld); + }); + + runInBothEnvironments("should return null for null input", () => { + const byteString = Utils.fromArrayToByteString(null); + expect(byteString).toBeNull(); + }); + + runInBothEnvironments("should return empty string for an empty Uint8Array", () => { + const arr = new Uint8Array([]); + const byteString = Utils.fromArrayToByteString(arr); + expect(byteString).toBe(""); + }); + }); + + describe("fromArrayToUtf8(...)", () => { + const originalIsNode = Utils.isNode; + + afterEach(() => { + Utils.isNode = originalIsNode; + }); + + runInBothEnvironments("should convert a Uint8Array to a UTF-8 string", () => { + const arr = new Uint8Array(asciiHelloWorldArray); + const utf8String = Utils.fromArrayToUtf8(arr); + expect(utf8String).toBe(asciiHelloWorld); + }); + + runInBothEnvironments("should return null for null input", () => { + const utf8String = Utils.fromArrayToUtf8(null); + expect(utf8String).toBeNull(); + }); + + runInBothEnvironments("should return empty string for an empty Uint8Array", () => { + const arr = new Uint8Array([]); + const utf8String = Utils.fromArrayToUtf8(arr); + expect(utf8String).toBe(""); + }); + + runInBothEnvironments("should handle multi-byte UTF-8 characters", () => { + // "日本" in UTF-8 bytes + const arr = new Uint8Array([0xe6, 0x97, 0xa5, 0xe6, 0x9c, 0xac]); + const utf8String = Utils.fromArrayToUtf8(arr); + expect(utf8String).toBe("日本"); + }); + }); + describe("Base64 and ArrayBuffer round trip conversions", () => { const originalIsNode = Utils.isNode; @@ -447,10 +583,10 @@ describe("Utils Service", () => { "should correctly round trip convert from base64 to ArrayBuffer and back", () => { // Convert known base64 string to ArrayBuffer - const bufferFromB64 = Utils.fromB64ToArray(b64HelloWorldString).buffer; + const bufferFromB64 = Utils.fromB64ToArray(b64HelloWorldString); // Convert the ArrayBuffer back to a base64 string - const roundTrippedB64String = Utils.fromBufferToB64(bufferFromB64); + const roundTrippedB64String = Utils.fromArrayToB64(bufferFromB64); // Compare the original base64 string with the round-tripped base64 string expect(roundTrippedB64String).toBe(b64HelloWorldString); diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 136b0ac394f..de71aa375e9 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -128,6 +128,70 @@ export class Utils { return arr; } + /** + * Converts a Uint8Array to a hexadecimal string. + * @param arr - The Uint8Array to convert. + * @returns The hexadecimal string representation, or null if the input is null. + */ + static fromArrayToHex(arr: Uint8Array | null): string | null { + if (arr == null) { + return null; + } + return this.fromBufferToHex(arr.buffer as ArrayBuffer); + } + + /** + * Converts a Uint8Array to a Base64 encoded string. + * @param arr - The Uint8Array to convert. + * @returns The Base64 encoded string, or null if the input is null. + */ + static fromArrayToB64(arr: Uint8Array | null): string | null { + if (arr == null) { + return null; + } + + return this.fromBufferToB64(arr.buffer as ArrayBuffer); + } + + /** + * Converts a Uint8Array to a URL-safe Base64 encoded string. + * @param arr - The Uint8Array to convert. + * @returns The URL-safe Base64 encoded string, or null if the input is null. + */ + static fromArrayToUrlB64(arr: Uint8Array | null): string | null { + if (arr == null) { + return null; + } + + return this.fromBufferToUrlB64(arr.buffer as ArrayBuffer); + } + + /** + * Converts a Uint8Array to a byte string (each byte as a character). + * @param arr - The Uint8Array to convert. + * @returns The byte string representation, or null if the input is null. + */ + static fromArrayToByteString(arr: Uint8Array | null): string | null { + if (arr == null) { + return null; + } + + return this.fromBufferToByteString(arr.buffer as ArrayBuffer); + } + + /** + * Converts a Uint8Array to a UTF-8 decoded string. + * @param arr - The Uint8Array containing UTF-8 encoded bytes. + * @returns The decoded UTF-8 string, or null if the input is null. + */ + static fromArrayToUtf8(arr: Uint8Array | null): string | null { + if (arr == null) { + return null; + } + + return this.fromBufferToUtf8(arr.buffer as ArrayBuffer); + } + /** * Convert binary data into a Base64 string. * @@ -301,7 +365,7 @@ export class Utils { } static fromUtf8ToUrlB64(utfStr: string): string { - return Utils.fromBufferToUrlB64(Utils.fromUtf8ToArray(utfStr)); + return Utils.fromArrayToUrlB64(Utils.fromUtf8ToArray(utfStr)); } static fromB64ToUtf8(b64Str: string): string {