From aa2cb1611b00a416d3c22a0c79d6818d387c70d4 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 30 Apr 2024 11:23:36 +0100 Subject: [PATCH 1/5] Add `private` modifiers for method in web BufferUtils --- src/platform/web/lib/util/bufferutils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 279112ed95..0a30aa2bcd 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -15,7 +15,7 @@ class BufferUtils implements IBufferUtils { hexCharSet = '0123456789abcdef'; // // https://gist.githubusercontent.com/jonleighton/958841/raw/f200e30dfe95212c0165ccf1ae000ca51e9de803/gistfile1.js - uint8ViewToBase64(bytes: Uint8Array) { + private uint8ViewToBase64(bytes: Uint8Array) { let base64 = ''; const encodings = this.base64CharSet; @@ -66,7 +66,7 @@ class BufferUtils implements IBufferUtils { return base64; } - base64ToArrayBuffer(base64: string) { + private base64ToArrayBuffer(base64: string) { const binary_string = atob?.(base64) as string; // this will always be defined in browser so it's safe to cast const len = binary_string.length; const bytes = new Uint8Array(len); From f38ee2890885de31db6d52f06680fce1e2e99284 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 30 Apr 2024 11:26:12 +0100 Subject: [PATCH 2/5] Add return types to all methods in web BufferUtils --- src/platform/web/lib/util/bufferutils.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 0a30aa2bcd..b3a54cce61 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -15,7 +15,7 @@ class BufferUtils implements IBufferUtils { hexCharSet = '0123456789abcdef'; // // https://gist.githubusercontent.com/jonleighton/958841/raw/f200e30dfe95212c0165ccf1ae000ca51e9de803/gistfile1.js - private uint8ViewToBase64(bytes: Uint8Array) { + private uint8ViewToBase64(bytes: Uint8Array): string { let base64 = ''; const encodings = this.base64CharSet; @@ -66,7 +66,7 @@ class BufferUtils implements IBufferUtils { return base64; } - private base64ToArrayBuffer(base64: string) { + private base64ToArrayBuffer(base64: string): Output { const binary_string = atob?.(base64) as string; // this will always be defined in browser so it's safe to cast const len = binary_string.length; const bytes = new Uint8Array(len); @@ -105,7 +105,7 @@ class BufferUtils implements IBufferUtils { return this.toBuffer(buffer).buffer; } - base64Encode(buffer: Bufferlike) { + base64Encode(buffer: Bufferlike): string { return this.uint8ViewToBase64(this.toBuffer(buffer)); } @@ -117,7 +117,7 @@ class BufferUtils implements IBufferUtils { } } - hexEncode(buffer: Bufferlike) { + hexEncode(buffer: Bufferlike): string { const arrayBuffer = buffer instanceof ArrayBuffer ? buffer @@ -126,7 +126,7 @@ class BufferUtils implements IBufferUtils { return uint8Array.reduce((accum, byte) => accum + byte.toString(16).padStart(2, '0'), ''); } - hexDecode(hexEncodedBytes: string) { + hexDecode(hexEncodedBytes: string): Output { if (hexEncodedBytes.length % 2 !== 0) { throw new Error("Can't create a byte array from a hex string of odd length"); } @@ -140,7 +140,7 @@ class BufferUtils implements IBufferUtils { return uint8Array.buffer.slice(uint8Array.byteOffset, uint8Array.byteOffset + uint8Array.byteLength); } - utf8Encode(string: string) { + utf8Encode(string: string): Output { if (Platform.Config.TextEncoder) { return new Platform.Config.TextEncoder().encode(string).buffer; } else { @@ -153,7 +153,7 @@ class BufferUtils implements IBufferUtils { * can take (in particular allowing strings, which are just interpreted as * binary); here we ensure that the input is actually a buffer since trying * to utf8-decode a string to another string is almost certainly a mistake */ - utf8Decode(buffer: Bufferlike) { + utf8Decode(buffer: Bufferlike): string { if (!this.isBuffer(buffer)) { throw new Error('Expected input of utf8decode to be an arraybuffer or typed array'); } @@ -164,7 +164,7 @@ class BufferUtils implements IBufferUtils { } } - areBuffersEqual(buffer1: Bufferlike, buffer2: Bufferlike) { + areBuffersEqual(buffer1: Bufferlike, buffer2: Bufferlike): boolean { if (!buffer1 || !buffer2) return false; const arrayBuffer1 = this.toArrayBuffer(buffer1); const arrayBuffer2 = this.toArrayBuffer(buffer2); @@ -180,7 +180,7 @@ class BufferUtils implements IBufferUtils { return true; } - byteLength(buffer: Bufferlike) { + byteLength(buffer: Bufferlike): number { if (buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer)) { return buffer.byteLength; } @@ -188,7 +188,7 @@ class BufferUtils implements IBufferUtils { } /* Returns ArrayBuffer on browser and Buffer on Node.js */ - arrayBufferViewToBuffer(arrayBufferView: ArrayBufferView) { + arrayBufferViewToBuffer(arrayBufferView: ArrayBufferView): ArrayBuffer { return arrayBufferView.buffer; } From a20f20b6ba40d5abffb3c982fdae1ebcd449117c Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 30 Apr 2024 11:41:21 +0100 Subject: [PATCH 3/5] Update web BufferUtils to always use dedicated `toArrayBuffer` method when converting to ArrayBuffer This commit also ensures that `toArrayBuffer` method is using `.byteOffset` and `.byteLength` properties to retrieve correct section of the underlying ArrayBuffer used by the provided TypedArray. Resolves #1730, Resolves #1749 --- src/platform/web/lib/util/bufferutils.ts | 31 +++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index b3a54cce61..4168600f32 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -74,7 +74,7 @@ class BufferUtils implements IBufferUtils { const ascii = binary_string.charCodeAt(i); bytes[i] = ascii; } - return bytes.buffer; + return this.toArrayBuffer(bytes); } isBuffer(buffer: unknown): buffer is Bufferlike { @@ -92,17 +92,26 @@ class BufferUtils implements IBufferUtils { } if (ArrayBuffer.isView(buffer)) { - return new Uint8Array(buffer.buffer); + return new Uint8Array(this.toArrayBuffer(buffer)); } throw new Error('BufferUtils.toBuffer expected an ArrayBuffer or a view onto one'); } toArrayBuffer(buffer: Bufferlike): ArrayBuffer { + if (!ArrayBuffer) { + throw new Error("Can't convert to ArrayBuffer: browser does not support the necessary types"); + } + if (buffer instanceof ArrayBuffer) { return buffer; } - return this.toBuffer(buffer).buffer; + + if (ArrayBuffer.isView(buffer)) { + return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); + } + + throw new Error('BufferUtils.toArrayBuffer expected an ArrayBuffer or a view onto one'); } base64Encode(buffer: Bufferlike): string { @@ -118,11 +127,7 @@ class BufferUtils implements IBufferUtils { } hexEncode(buffer: Bufferlike): string { - const arrayBuffer = - buffer instanceof ArrayBuffer - ? buffer - : buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); - const uint8Array = new Uint8Array(arrayBuffer); + const uint8Array = this.toBuffer(buffer); return uint8Array.reduce((accum, byte) => accum + byte.toString(16).padStart(2, '0'), ''); } @@ -137,12 +142,13 @@ class BufferUtils implements IBufferUtils { uint8Array[i] = parseInt(hexEncodedBytes.slice(2 * i, 2 * (i + 1)), 16); } - return uint8Array.buffer.slice(uint8Array.byteOffset, uint8Array.byteOffset + uint8Array.byteLength); + return this.toArrayBuffer(uint8Array); } utf8Encode(string: string): Output { if (Platform.Config.TextEncoder) { - return new Platform.Config.TextEncoder().encode(string).buffer; + const encodedByteArray = new Platform.Config.TextEncoder().encode(string); + return this.toArrayBuffer(encodedByteArray); } else { throw new Error('Expected TextEncoder to be configured'); } @@ -189,11 +195,12 @@ class BufferUtils implements IBufferUtils { /* Returns ArrayBuffer on browser and Buffer on Node.js */ arrayBufferViewToBuffer(arrayBufferView: ArrayBufferView): ArrayBuffer { - return arrayBufferView.buffer; + return this.toArrayBuffer(arrayBufferView); } hmacSha256(message: Bufferlike, key: Bufferlike): Output { - return hmacSha256(this.toBuffer(key), this.toBuffer(message)); + const hash = hmacSha256(this.toBuffer(key), this.toBuffer(message)); + return this.toArrayBuffer(hash); } } From 3317106cd5b99a965ecd623f720b15430ea36788 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 30 Apr 2024 11:46:43 +0100 Subject: [PATCH 4/5] Remove unnecessary `.buffer` call in node.js BufferUtils --- src/platform/nodejs/lib/util/bufferutils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/nodejs/lib/util/bufferutils.ts b/src/platform/nodejs/lib/util/bufferutils.ts index e1c6fbcb67..52b46ba2b1 100644 --- a/src/platform/nodejs/lib/util/bufferutils.ts +++ b/src/platform/nodejs/lib/util/bufferutils.ts @@ -56,7 +56,7 @@ class BufferUtils implements IBufferUtils { } arrayBufferViewToBuffer(arrayBufferView: ArrayBufferView): Buffer { - return this.toBuffer(arrayBufferView.buffer); + return this.toBuffer(arrayBufferView); } utf8Decode(buffer: Bufferlike): string { From bebe0cdbc2c7bb9c8c79553668c616f7ac386777 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Thu, 2 May 2024 12:55:21 +0100 Subject: [PATCH 5/5] Improve comments in BufferUtils Also removes comment about old browsers support as it's not needed after https://github.com/ably/ably-js/pull/1633 --- src/common/types/IBufferUtils.ts | 7 ++++++- src/platform/web/lib/util/bufferutils.ts | 7 ++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/common/types/IBufferUtils.ts b/src/common/types/IBufferUtils.ts index efa1b51209..be573a44f8 100644 --- a/src/common/types/IBufferUtils.ts +++ b/src/common/types/IBufferUtils.ts @@ -2,7 +2,9 @@ export default interface IBufferUtils { base64CharSet: string; hexCharSet: string; isBuffer: (buffer: unknown) => buffer is Bufferlike; - // On browser this returns a Uint8Array, on node a Buffer + /** + * On browser this returns a Uint8Array, on node a Buffer + */ toBuffer: (buffer: Bufferlike) => ToBufferOutput; toArrayBuffer: (buffer: Bufferlike) => ArrayBuffer; base64Encode: (buffer: Bufferlike) => string; @@ -13,6 +15,9 @@ export default interface IBufferUtils { utf8Decode: (buffer: Bufferlike) => string; areBuffersEqual: (buffer1: Bufferlike, buffer2: Bufferlike) => boolean; byteLength: (buffer: Bufferlike) => number; + /** + * Returns ArrayBuffer on browser and Buffer on Node.js + */ arrayBufferViewToBuffer: (arrayBufferView: ArrayBufferView) => Bufferlike; hmacSha256(message: Bufferlike, key: Bufferlike): Output; } diff --git a/src/platform/web/lib/util/bufferutils.ts b/src/platform/web/lib/util/bufferutils.ts index 4168600f32..062e663515 100644 --- a/src/platform/web/lib/util/bufferutils.ts +++ b/src/platform/web/lib/util/bufferutils.ts @@ -3,8 +3,7 @@ import IBufferUtils from 'common/types/IBufferUtils'; import { hmac as hmacSha256 } from './hmac-sha256'; /* Most BufferUtils methods that return a binary object return an ArrayBuffer - * The exception is toBuffer, which returns a Uint8Array (and won't work on - * browsers too old to support it) */ + * The exception is toBuffer, which returns a Uint8Array */ export type Bufferlike = BufferSource; export type Output = Bufferlike; @@ -14,7 +13,7 @@ class BufferUtils implements IBufferUtils { base64CharSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; hexCharSet = '0123456789abcdef'; - // // https://gist.githubusercontent.com/jonleighton/958841/raw/f200e30dfe95212c0165ccf1ae000ca51e9de803/gistfile1.js + // https://gist.githubusercontent.com/jonleighton/958841/raw/f200e30dfe95212c0165ccf1ae000ca51e9de803/gistfile1.js private uint8ViewToBase64(bytes: Uint8Array): string { let base64 = ''; const encodings = this.base64CharSet; @@ -81,7 +80,6 @@ class BufferUtils implements IBufferUtils { return buffer instanceof ArrayBuffer || ArrayBuffer.isView(buffer); } - /* In browsers, returns a Uint8Array */ toBuffer(buffer: Bufferlike): ToBufferOutput { if (!ArrayBuffer) { throw new Error("Can't convert to Buffer: browser does not support the necessary types"); @@ -193,7 +191,6 @@ class BufferUtils implements IBufferUtils { return -1; } - /* Returns ArrayBuffer on browser and Buffer on Node.js */ arrayBufferViewToBuffer(arrayBufferView: ArrayBufferView): ArrayBuffer { return this.toArrayBuffer(arrayBufferView); }