diff --git a/libraries/adb/src/adb.ts b/libraries/adb/src/adb.ts index d39c954a..efd5afc1 100644 --- a/libraries/adb/src/adb.ts +++ b/libraries/adb/src/adb.ts @@ -6,7 +6,7 @@ import { AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, Ad import { AdbFeatures } from './features'; import { AdbCommand } from './packet'; import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket'; -import { ReadableStream, WritableStream } from "./stream"; +import { DecodeUtf8Stream, GatherStringStream, ReadableStream, WritableStream } from "./stream"; import { decodeUtf8 } from "./utils"; export enum AdbPropKey { @@ -52,8 +52,8 @@ export class Adb { public constructor( backend: AdbBackend, - readable: ReadableStream, - writable: WritableStream, + readable: ReadableStream, + writable: WritableStream, logger?: AdbLogger ) { this._backend = backend; @@ -225,11 +225,11 @@ export class Adb { public async createSocketAndWait(service: string): Promise { const socket = await this.createSocket(service); - let result = ''; - for await (const chunk of socket.readable) { - result += decodeUtf8(chunk); - } - return result; + const gatherStream = new GatherStringStream(); + await socket.readable + .pipeThrough(new DecodeUtf8Stream()) + .pipeTo(gatherStream.writable); + return gatherStream.result; } public async dispose(): Promise { diff --git a/libraries/adb/src/auth.ts b/libraries/adb/src/auth.ts index 96b2ff10..b6b2d858 100644 --- a/libraries/adb/src/auth.ts +++ b/libraries/adb/src/auth.ts @@ -5,15 +5,15 @@ import { calculatePublicKey, calculatePublicKeyLength, sign } from './crypto'; import { AdbCommand, AdbPacket, AdbPacketInit } from './packet'; import { calculateBase64EncodedLength, encodeBase64 } from './utils'; -export type AdbKeyIterable = Iterable | AsyncIterable; +export type AdbKeyIterable = Iterable | AsyncIterable; export interface AdbCredentialStore { /** * Generate and store a RSA private key with modulus length `2048` and public exponent `65537`. * - * The returned `ArrayBuffer` is the private key in PKCS #8 format. + * The returned `Uint8Array` is the private key in PKCS #8 format. */ - generateKey(): ValueOrPromise; + generateKey(): ValueOrPromise; /** * Synchronously or asynchronously iterate through all stored RSA private keys. @@ -63,7 +63,7 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* ( command: AdbCommand.Auth, arg0: AdbAuthType.Signature, arg1: 0, - payload: signature, + payload: new Uint8Array(signature), }; } }; @@ -78,12 +78,13 @@ export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* ( return; } - let privateKey: ArrayBuffer | undefined; + let privateKey: Uint8Array | undefined; for await (const key of credentialStore.iterateKeys()) { privateKey = key; break; } + if (!privateKey) { privateKey = await credentialStore.generateKey(); } @@ -93,7 +94,7 @@ export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* ( // The public key is null terminated, // So we allocate the buffer with one extra byte. - const publicKeyBuffer = new ArrayBuffer(publicKeyBase64Length + 1); + const publicKeyBuffer = new Uint8Array(publicKeyBase64Length + 1); calculatePublicKey(privateKey, publicKeyBuffer); encodeBase64(publicKeyBuffer, 0, publicKeyLength, publicKeyBuffer); diff --git a/libraries/adb/src/backend.ts b/libraries/adb/src/backend.ts index 082ef08c..42b3a636 100644 --- a/libraries/adb/src/backend.ts +++ b/libraries/adb/src/backend.ts @@ -6,5 +6,5 @@ export interface AdbBackend { readonly name: string | undefined; - connect(): ValueOrPromise>; + connect(): ValueOrPromise>; } diff --git a/libraries/adb/src/commands/framebuffer.ts b/libraries/adb/src/commands/framebuffer.ts index 64b4dd70..cca34b74 100644 --- a/libraries/adb/src/commands/framebuffer.ts +++ b/libraries/adb/src/commands/framebuffer.ts @@ -33,7 +33,7 @@ export const AdbFrameBufferV1 = .uint32('green_length') .uint32('alpha_offset') .uint32('alpha_length') - .uint8ClampedArray('data', { lengthField: 'size' }); + .uint8Array('data', { lengthField: 'size' }); export type AdbFrameBufferV1 = typeof AdbFrameBufferV1['TDeserializeResult']; @@ -52,7 +52,7 @@ export const AdbFrameBufferV2 = .uint32('green_length') .uint32('alpha_offset') .uint32('alpha_length') - .uint8ClampedArray('data', { lengthField: 'size' }); + .uint8Array('data', { lengthField: 'size' }); export type AdbFrameBufferV2 = typeof AdbFrameBufferV2['TDeserializeResult']; diff --git a/libraries/adb/src/commands/install.ts b/libraries/adb/src/commands/install.ts index cc032eb0..d843b133 100644 --- a/libraries/adb/src/commands/install.ts +++ b/libraries/adb/src/commands/install.ts @@ -5,10 +5,10 @@ import { AdbSync } from "./sync"; export function install( adb: Adb, -): WritableStream { +): WritableStream { const filename = `/data/local/tmp/${Math.random().toString().substring(2)}.apk`; - return new WrapWritableStream, AdbSync>({ + return new WrapWritableStream, AdbSync>({ async start() { // Upload apk file to tmp folder const sync = await adb.sync(); diff --git a/libraries/adb/src/commands/subprocess/legacy.ts b/libraries/adb/src/commands/subprocess/legacy.ts index 5971af11..073a8a4c 100644 --- a/libraries/adb/src/commands/subprocess/legacy.ts +++ b/libraries/adb/src/commands/subprocess/legacy.ts @@ -1,7 +1,7 @@ import { PromiseResolver } from "@yume-chan/async"; import type { Adb } from "../../adb"; import type { AdbSocket } from "../../socket"; -import { ReadableStream, TransformStream } from "../../stream"; +import { ReadableStream, ReadableStreamDefaultController, WrapReadableStream } from "../../stream"; import type { AdbSubprocessProtocol } from "./types"; /** @@ -24,26 +24,39 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol { // Legacy shell forwards all data to stdin. public get stdin() { return this.socket.writable; } - private _stdout: ReadableStream; + private _stdout: ReadableStream; // Legacy shell doesn't support splitting output streams. public get stdout() { return this._stdout; } // `stderr` of Legacy shell is always empty. - private _stderr = new TransformStream(); - public get stderr() { return this._stderr.readable; } + private _stderr: ReadableStream; + public get stderr() { return this._stderr; } private _exit = new PromiseResolver(); public get exit() { return this._exit.promise; } public constructor(socket: AdbSocket) { this.socket = socket; - this._stdout = this.socket.readable - .pipeThrough(new TransformStream({ - flush: () => { - this._stderr.writable.close(); - this._exit.resolve(0); - }, - })); + + let stderrController!: ReadableStreamDefaultController; + this._stderr = new ReadableStream({ + start(controller) { + stderrController = controller; + }, + }); + + this._stdout = new WrapReadableStream, undefined>({ + async start() { + return { + readable: socket.readable, + state: undefined, + }; + }, + async close() { + // Close `stderr` on exit. + stderrController.close(); + } + }); } public resize() { diff --git a/libraries/adb/src/commands/subprocess/protocol.ts b/libraries/adb/src/commands/subprocess/protocol.ts index 103c78a7..1fbb1b82 100644 --- a/libraries/adb/src/commands/subprocess/protocol.ts +++ b/libraries/adb/src/commands/subprocess/protocol.ts @@ -20,13 +20,13 @@ export enum AdbShellProtocolId { const AdbShellProtocolPacket = new Struct({ littleEndian: true }) .uint8('id', placeholder()) .uint32('length') - .arrayBuffer('data', { lengthField: 'length' }); + .uint8Array('data', { lengthField: 'length' }); type AdbShellProtocolPacketInit = typeof AdbShellProtocolPacket['TInit']; type AdbShellProtocolPacket = StructValueType; -class StdinSerializeStream extends TransformStream{ +class StdinSerializeStream extends TransformStream{ constructor() { super({ transform(chunk, controller) { @@ -42,7 +42,7 @@ class StdinSerializeStream extends TransformStream{ +class StdoutDeserializeStream extends TransformStream{ constructor(type: AdbShellProtocolId.Stdout | AdbShellProtocolId.Stderr) { super({ transform(chunk, controller) { @@ -106,13 +106,13 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol { private readonly _socket: AdbSocket; private _socketWriter: WritableStreamDefaultWriter; - private _stdin = new TransformStream(); - public get stdin() { return this._stdin.writable; } + private _stdin: WritableStream; + public get stdin() { return this._stdin; } - private _stdout: ReadableStream; + private _stdout: ReadableStream; public get stdout() { return this._stdout; } - private _stderr: ReadableStream; + private _stderr: ReadableStream; public get stderr() { return this._stderr; } private readonly _exit = new PromiseResolver(); @@ -125,9 +125,11 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol { // cspell: disable-next-line // https://www.plantuml.com/plantuml/png/bL91QiCm4Bpx5SAdv90lb1JISmiw5XzaQKf5PIkiLZIqzEyLSg8ks13gYtOykpFhiOw93N6UGjVDqK7rZsxKqNw0U_NTgVAy4empOy2mm4_olC0VEVEE47GUpnGjKdgXoD76q4GIEpyFhOwP_m28hW0NNzxNUig1_JdW0bA7muFIJDco1daJ_1SAX9bgvoPJPyIkSekhNYctvIGXrCH6tIsPL5fs-s6J5yc9BpWXhKtNdF2LgVYPGM_6GlMwfhWUsIt4lbScANrwlgVVUifPSVi__t44qStnwPvZwobdSmHHlL57p2vFuHS0 + // TODO: AdbShellSubprocessProtocol: Optimize stream graph + const [stdout, stderr] = socket.readable .pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket)) - .pipeThrough(new TransformStream({ + .pipeThrough(new TransformStream({ transform: (chunk, controller) => { if (chunk.id === AdbShellProtocolId.Exit) { this._exit.resolve(new Uint8Array(chunk.data)[0]!); @@ -151,9 +153,9 @@ export class AdbShellSubprocessProtocol implements AdbSubprocessProtocol { .pipeThrough(new StructSerializeStream(AdbShellProtocolPacket)) .pipeTo(socket.writable); - this._stdin.readable - .pipeThrough(new StdinSerializeStream()) - .pipeTo(multiplexer.createWriteable()); + const { readable, writable } = new StdinSerializeStream(); + this._stdin = writable; + readable.pipeTo(multiplexer.createWriteable()); this._socketWriter = multiplexer.createWriteable().getWriter(); } diff --git a/libraries/adb/src/commands/subprocess/types.ts b/libraries/adb/src/commands/subprocess/types.ts index e512b7e1..e163fec9 100644 --- a/libraries/adb/src/commands/subprocess/types.ts +++ b/libraries/adb/src/commands/subprocess/types.ts @@ -7,12 +7,12 @@ export interface AdbSubprocessProtocol { /** * A WritableStream that writes to the `stdin` pipe. */ - readonly stdin: WritableStream; + readonly stdin: WritableStream; /** * The `stdout` pipe of the process. */ - readonly stdout: ReadableStream; + readonly stdout: ReadableStream; /** * The `stderr` pipe of the process. @@ -20,7 +20,7 @@ export interface AdbSubprocessProtocol { * Note: Some `AdbShell` doesn't separate `stdout` and `stderr`, * All output will be sent to `stdout`. */ - readonly stderr: ReadableStream; + readonly stderr: ReadableStream; /** * A `Promise` that resolves to the exit code of the process. diff --git a/libraries/adb/src/commands/sync/list.ts b/libraries/adb/src/commands/sync/list.ts index 3a19d727..9f48dd48 100644 --- a/libraries/adb/src/commands/sync/list.ts +++ b/libraries/adb/src/commands/sync/list.ts @@ -20,7 +20,7 @@ const ResponseTypes = { export async function* adbSyncOpenDir( stream: AdbBufferedStream, - writer: WritableStreamDefaultWriter, + writer: WritableStreamDefaultWriter, path: string, ): AsyncGenerator { await adbSyncWriteRequest(writer, AdbSyncRequestId.List, path); diff --git a/libraries/adb/src/commands/sync/pull.ts b/libraries/adb/src/commands/sync/pull.ts index 2f190158..8e74ff31 100644 --- a/libraries/adb/src/commands/sync/pull.ts +++ b/libraries/adb/src/commands/sync/pull.ts @@ -6,7 +6,7 @@ import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './r export const AdbSyncDataResponse = new Struct({ littleEndian: true }) .uint32('dataLength') - .arrayBuffer('data', { lengthField: 'dataLength' }) + .uint8Array('data', { lengthField: 'dataLength' }) .extra({ id: AdbSyncResponseId.Data as const }); const ResponseTypes = { @@ -16,9 +16,9 @@ const ResponseTypes = { export function adbSyncPull( stream: AdbBufferedStream, - writer: WritableStreamDefaultWriter, + writer: WritableStreamDefaultWriter, path: string, -): ReadableStream { +): ReadableStream { return new ReadableStream({ async start() { await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path); diff --git a/libraries/adb/src/commands/sync/push.ts b/libraries/adb/src/commands/sync/push.ts index d4255a48..977bdf53 100644 --- a/libraries/adb/src/commands/sync/push.ts +++ b/libraries/adb/src/commands/sync/push.ts @@ -1,6 +1,5 @@ import Struct from '@yume-chan/struct'; -import { AdbBufferedStream, WritableStream, WritableStreamDefaultWriter } from '../../stream'; -import { chunkArrayLike } from '../../utils'; +import { AdbBufferedStream, ChunkStream, WritableStream, WritableStreamDefaultWriter } from '../../stream'; import { AdbSyncRequestId, adbSyncWriteRequest } from './request'; import { adbSyncReadResponse, AdbSyncResponseId } from './response'; import { LinuxFileType } from './stat'; @@ -17,21 +16,20 @@ export const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024; export function adbSyncPush( stream: AdbBufferedStream, - writer: WritableStreamDefaultWriter, + writer: WritableStreamDefaultWriter, filename: string, mode: number = (LinuxFileType.File << 12) | 0o666, mtime: number = (Date.now() / 1000) | 0, packetSize: number = ADB_SYNC_MAX_PACKET_SIZE, -): WritableStream { - return new WritableStream({ +): WritableStream { + const { readable, writable } = new ChunkStream(packetSize); + readable.pipeTo(new WritableStream({ async start() { const pathAndMode = `${filename},${mode.toString()}`; await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode); }, async write(chunk) { - for (const buffer of chunkArrayLike(chunk, packetSize)) { - await adbSyncWriteRequest(writer, AdbSyncRequestId.Data, buffer); - } + await adbSyncWriteRequest(writer, AdbSyncRequestId.Data, chunk); }, async close() { await adbSyncWriteRequest(writer, AdbSyncRequestId.Done, mtime); @@ -40,5 +38,6 @@ export function adbSyncPush( }, { highWaterMark: 16 * 1024, size(chunk) { return chunk.byteLength; } - }); + })); + return writable; } diff --git a/libraries/adb/src/commands/sync/request.ts b/libraries/adb/src/commands/sync/request.ts index 41e5ebcd..3d80748e 100644 --- a/libraries/adb/src/commands/sync/request.ts +++ b/libraries/adb/src/commands/sync/request.ts @@ -21,29 +21,29 @@ export const AdbSyncNumberRequest = export const AdbSyncDataRequest = new Struct({ littleEndian: true }) .fields(AdbSyncNumberRequest) - .arrayBuffer('data', { lengthField: 'arg' }); + .uint8Array('data', { lengthField: 'arg' }); export async function adbSyncWriteRequest( - writer: WritableStreamDefaultWriter, + writer: WritableStreamDefaultWriter, id: AdbSyncRequestId | string, - value: number | string | ArrayBuffer + value: number | string | Uint8Array ): Promise { - let buffer: ArrayBuffer; + let buffer: Uint8Array; if (typeof value === 'number') { - buffer = AdbSyncNumberRequest.serialize({ + buffer = new Uint8Array(AdbSyncNumberRequest.serialize({ id, arg: value, - }); + })); } else if (typeof value === 'string') { - buffer = AdbSyncDataRequest.serialize({ + buffer = new Uint8Array(AdbSyncDataRequest.serialize({ id, data: encodeUtf8(value), - }); + })); } else { - buffer = AdbSyncDataRequest.serialize({ + buffer = new Uint8Array(AdbSyncDataRequest.serialize({ id, data: value, - }); + })); } await writer.write(buffer); } diff --git a/libraries/adb/src/commands/sync/stat.ts b/libraries/adb/src/commands/sync/stat.ts index 92c175ae..b9dd8138 100644 --- a/libraries/adb/src/commands/sync/stat.ts +++ b/libraries/adb/src/commands/sync/stat.ts @@ -94,7 +94,7 @@ const Lstat2ResponseType = { export async function adbSyncLstat( stream: AdbBufferedStream, - writer: WritableStreamDefaultWriter, + writer: WritableStreamDefaultWriter, path: string, v2: boolean, ): Promise { @@ -115,7 +115,7 @@ export async function adbSyncLstat( export async function adbSyncStat( stream: AdbBufferedStream, - writer: WritableStreamDefaultWriter, + writer: WritableStreamDefaultWriter, path: string, ): Promise { await adbSyncWriteRequest(writer, AdbSyncRequestId.Stat, path); diff --git a/libraries/adb/src/commands/sync/sync.ts b/libraries/adb/src/commands/sync/sync.ts index 139a5061..a194d589 100644 --- a/libraries/adb/src/commands/sync/sync.ts +++ b/libraries/adb/src/commands/sync/sync.ts @@ -14,7 +14,7 @@ export class AdbSync extends AutoDisposable { protected stream: AdbBufferedStream; - protected writer: WritableStreamDefaultWriter; + protected writer: WritableStreamDefaultWriter; protected sendLock = this.addDisposable(new AutoResetEvent()); @@ -89,8 +89,8 @@ export class AdbSync extends AutoDisposable { * @param filename The full path of the file on device to read. * @returns A `ReadableStream` that reads from the file. */ - public read(filename: string): ReadableStream { - return new WrapReadableStream, undefined>({ + public read(filename: string): ReadableStream { + return new WrapReadableStream, undefined>({ start: async () => { await this.sendLock.wait(); return { @@ -116,7 +116,7 @@ export class AdbSync extends AutoDisposable { filename: string, mode?: number, mtime?: number, - ): WritableStream { + ): WritableStream { return new WrapWritableStream({ start: async () => { await this.sendLock.wait(); @@ -128,7 +128,7 @@ export class AdbSync extends AutoDisposable { mode, mtime, ), - state: {}, + state: undefined, }; }, close: async () => { diff --git a/libraries/adb/src/crypto.ts b/libraries/adb/src/crypto.ts index dc22dced..4818e7d5 100644 --- a/libraries/adb/src/crypto.ts +++ b/libraries/adb/src/crypto.ts @@ -6,15 +6,15 @@ const BigInt2 = BigInt(2); const BigInt64 = BigInt(64); export function getBig( - buffer: ArrayBuffer, + array: Uint8Array, offset = 0, - length = buffer.byteLength - offset + length = array.byteLength - offset ): bigint { - const view = new DataView(buffer); + const view = new DataView(array.buffer, array.byteOffset, array.byteLength); let result = BigInt0; - // Now `length` must be a multiplication of 8 + // Currently `length` must be a multiplication of 8 // Support for arbitrary length can be easily added for (let i = offset; i < offset + length; i += 8) { @@ -40,8 +40,8 @@ export function setBig(buffer: ArrayBuffer, value: bigint, offset: number = 0) { } } -export function setBigLE(buffer: ArrayBuffer, value: bigint, offset = 0) { - const view = new DataView(buffer); +export function setBigLE(array: Uint8Array, value: bigint, offset = 0) { + const view = new DataView(array.buffer, array.byteOffset, array.byteLength); while (value > BigInt0) { setBigUint64(view, offset, value, true); offset += 8; @@ -75,7 +75,7 @@ const RsaPrivateKeyNLength = 2048 / 8; const RsaPrivateKeyDOffset = 303; const RsaPrivateKeyDLength = 2048 / 8; -export function parsePrivateKey(key: ArrayBuffer): [n: bigint, d: bigint] { +export function parsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] { let n = getBig(key, RsaPrivateKeyNOffset, RsaPrivateKeyNLength); let d = getBig(key, RsaPrivateKeyDOffset, RsaPrivateKeyDLength); @@ -114,18 +114,18 @@ export function calculatePublicKeyLength() { } export function calculatePublicKey( - privateKey: ArrayBuffer -): ArrayBuffer; + privateKey: Uint8Array +): Uint8Array; export function calculatePublicKey( - privateKey: ArrayBuffer, - output: ArrayBuffer, + privateKey: Uint8Array, + output: Uint8Array, outputOffset?: number ): number; export function calculatePublicKey( - privateKey: ArrayBuffer, - output?: ArrayBuffer, + privateKey: Uint8Array, + output?: Uint8Array, outputOffset: number = 0 -): ArrayBuffer | number { +): Uint8Array | number { // Android has its own public key generation algorithm // See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#111 @@ -149,7 +149,7 @@ export function calculatePublicKey( let outputType: 'ArrayBuffer' | 'number'; const outputLength = calculatePublicKeyLength(); if (!output) { - output = new ArrayBuffer(outputLength); + output = new Uint8Array(outputLength); outputType = 'ArrayBuffer'; } else { if (output.byteLength - outputOffset < outputLength) { @@ -159,7 +159,7 @@ export function calculatePublicKey( outputType = 'number'; } - const outputView = new DataView(output); + const outputView = new DataView(output.buffer, output.byteOffset, output.byteLength); // modulusLengthInWords outputView.setUint32(outputOffset, 2048 / 8 / 4, true); @@ -227,14 +227,14 @@ export const Asn1Null = 0x05; export const Asn1Oid = 0x06; // PKCS#1 SHA-1 hash digest info -export const Sha1DigestInfo = [ +export const Sha1DigestInfo = new Uint8Array([ Asn1Sequence, 0x0d + Sha1DigestLength, Asn1Sequence, 0x09, // SHA-1 (1 3 14 3 2 26) Asn1Oid, 0x05, 1 * 40 + 3, 14, 3, 2, 26, Asn1Null, 0x00, Asn1OctetString, Sha1DigestLength -]; +]); // SubtleCrypto.sign() will hash the given data and sign the hash // But we don't need the hashing step @@ -242,7 +242,7 @@ export const Sha1DigestInfo = [ // encrypt the given data with its private key) // However SubtileCrypto.encrypt() doesn't accept 'RSASSA-PKCS1-v1_5' algorithm // So we need to implement the encryption by ourself -export function sign(privateKey: ArrayBuffer, data: ArrayBuffer): ArrayBuffer { +export function sign(privateKey: Uint8Array, data: Uint8Array): ArrayBuffer { const [n, d] = parsePrivateKey(privateKey); // PKCS#1 padding @@ -255,7 +255,7 @@ export function sign(privateKey: ArrayBuffer, data: ArrayBuffer): ArrayBuffer { padded[index] = 1; index += 1; - const fillLength = padded.length - Sha1DigestInfo.length - data.byteLength - 1; + const fillLength = padded.length - Sha1DigestInfo.length - data.length - 1; while (index < fillLength) { padded[index] = 0xff; index += 1; @@ -264,14 +264,14 @@ export function sign(privateKey: ArrayBuffer, data: ArrayBuffer): ArrayBuffer { padded[index] = 0; index += 1; - padded.set(new Uint8Array(Sha1DigestInfo), index); + padded.set(Sha1DigestInfo, index); index += Sha1DigestInfo.length; - padded.set(new Uint8Array(data), index); + padded.set(data, index); // Encryption // signature = padded ** d % n - let signature = powMod(getBig(padded.buffer), d, n); + let signature = powMod(getBig(padded), d, n); // Put into an ArrayBuffer const result = new ArrayBuffer(256); diff --git a/libraries/adb/src/index.ts b/libraries/adb/src/index.ts index 6b4a8fb3..ee87c40b 100644 --- a/libraries/adb/src/index.ts +++ b/libraries/adb/src/index.ts @@ -1,3 +1,10 @@ +declare global { + interface ArrayBuffer { + // Disallow assigning `Arraybuffer` to `Uint8Array` + __brand: never; + } +} + export * from './adb'; export * from './auth'; export * from './backend'; diff --git a/libraries/adb/src/packet.ts b/libraries/adb/src/packet.ts index 9721edc5..658c9840 100644 --- a/libraries/adb/src/packet.ts +++ b/libraries/adb/src/packet.ts @@ -22,13 +22,13 @@ const AdbPacketHeader = export const AdbPacket = new Struct({ littleEndian: true }) .fields(AdbPacketHeader) - .arrayBuffer('payload', { lengthField: 'payloadLength' }); + .uint8Array('payload', { lengthField: 'payloadLength' }); export type AdbPacket = typeof AdbPacket['TDeserializeResult']; export type AdbPacketInit = Omit; -export class AdbPacketSerializeStream extends TransformStream{ +export class AdbPacketSerializeStream extends TransformStream{ public calculateChecksum = true; public constructor() { @@ -49,7 +49,7 @@ export class AdbPacketSerializeStream extends TransformStream; - private readonly _readablePassthroughWriter: WritableStreamDefaultWriter; + private readonly _readablePassthrough: TransformStream; + private readonly _readablePassthroughWriter: WritableStreamDefaultWriter; public get readable() { return this._readablePassthrough.readable; } private _writePromise: PromiseResolver | undefined; - public readonly writable: WritableStream; + public readonly writable: WritableStream; private _writableClosed = false; @@ -61,7 +61,7 @@ export class AdbSocketController implements AdbSocketInfo { const { readable, writable } = new ChunkStream(this.dispatcher.maxPayloadSize); this.writable = writable; - readable.pipeTo(new WritableStream({ + readable.pipeTo(new WritableStream({ write: async (chunk) => { if (this._writableClosed) { throw new Error('Socket closed'); @@ -87,7 +87,7 @@ export class AdbSocketController implements AdbSocketInfo { })); } - public enqueue(packet: ArrayBuffer) { + public enqueue(packet: Uint8Array) { return this._readablePassthroughWriter.write(packet); } diff --git a/libraries/adb/src/socket/dispatcher.ts b/libraries/adb/src/socket/dispatcher.ts index 320ba75d..25fd32de 100644 --- a/libraries/adb/src/socket/dispatcher.ts +++ b/libraries/adb/src/socket/dispatcher.ts @@ -23,7 +23,7 @@ export interface AdbIncomingSocketEventArgs { socket: AdbSocket; } -const EmptyArrayBuffer = new ArrayBuffer(0); +const EmptyUint8Array = new Uint8Array(0); export class AdbPacketDispatcher extends AutoDisposable { // ADB socket id starts from 1 @@ -51,7 +51,7 @@ export class AdbPacketDispatcher extends AutoDisposable { private _abortController = new AbortController(); - public constructor(readable: ReadableStream, writable: WritableStream, logger?: AdbLogger) { + public constructor(readable: ReadableStream, writable: WritableStream, logger?: AdbLogger) { super(); this.logger = logger; @@ -221,23 +221,27 @@ export class AdbPacketDispatcher extends AutoDisposable { command: AdbCommand, arg0: number, arg1: number, - payload?: string | ArrayBuffer + payload?: string | Uint8Array ): Promise; public async sendPacket( packetOrCommand: AdbPacketInit | AdbCommand, arg0?: number, arg1?: number, - payload: string | ArrayBuffer = EmptyArrayBuffer, + payload: string | Uint8Array = EmptyUint8Array, ): Promise { let init: AdbPacketInit; if (arg0 === undefined) { init = packetOrCommand as AdbPacketInit; } else { + if (typeof payload === 'string') { + payload = encodeUtf8(payload); + } + init = { command: packetOrCommand as AdbCommand, arg0: arg0 as number, arg1: arg1 as number, - payload: typeof payload === 'string' ? encodeUtf8(payload) : payload, + payload, }; } diff --git a/libraries/adb/src/socket/socket.ts b/libraries/adb/src/socket/socket.ts index b99ba37b..3bf2bd86 100644 --- a/libraries/adb/src/socket/socket.ts +++ b/libraries/adb/src/socket/socket.ts @@ -9,8 +9,8 @@ export class AdbSocket implements AdbSocketInfo { public get localCreated() { return this.controller.localCreated; } public get serviceString() { return this.controller.serviceString; } - public get readable(): ReadableStream { return this.controller.readable; } - public get writable(): WritableStream { return this.controller.writable; } + public get readable(): ReadableStream { return this.controller.readable; } + public get writable(): WritableStream { return this.controller.writable; } public constructor(controller: AdbSocketController) { this.controller = controller; diff --git a/libraries/adb/src/stream/buffered.ts b/libraries/adb/src/stream/buffered.ts index 315f87e8..3aa9e490 100644 --- a/libraries/adb/src/stream/buffered.ts +++ b/libraries/adb/src/stream/buffered.ts @@ -14,11 +14,11 @@ export class BufferedStreamEndedError extends Error { export class BufferedStream { private buffer: Uint8Array | undefined; - protected readonly stream: ReadableStream; + protected readonly stream: ReadableStream; - protected readonly reader: ReadableStreamDefaultReader; + protected readonly reader: ReadableStreamDefaultReader; - public constructor(stream: ReadableStream) { + public constructor(stream: ReadableStream) { this.stream = stream; this.reader = stream.getReader(); } @@ -29,14 +29,14 @@ export class BufferedStream { * @param readToEnd When `true`, allow less data to be returned if the stream has reached its end. * @returns */ - public async read(length: number, readToEnd: boolean = false): Promise { + public async read(length: number, readToEnd: boolean = false): Promise { let array: Uint8Array; let index: number; if (this.buffer) { const buffer = this.buffer; if (buffer.byteLength > length) { this.buffer = buffer.subarray(length); - return buffer.slice(0, length).buffer; + return buffer.slice(0, length); } array = new Uint8Array(length); @@ -47,7 +47,7 @@ export class BufferedStream { const result = await this.reader.read(); if (result.done) { if (readToEnd) { - return new ArrayBuffer(0); + return new Uint8Array(0); } else { throw new Error('Unexpected end of stream'); } @@ -59,7 +59,7 @@ export class BufferedStream { } if (value.byteLength > length) { - this.buffer = new Uint8Array(value, length); + this.buffer = value.subarray(length); return value.slice(0, length); } @@ -74,7 +74,7 @@ export class BufferedStream { const result = await this.reader.read(); if (result.done) { if (readToEnd) { - return new ArrayBuffer(0); + return new Uint8Array(0); } else { throw new Error('Unexpected end of stream'); } @@ -82,16 +82,16 @@ export class BufferedStream { const { value } = result; if (value.byteLength > left) { - array.set(new Uint8Array(value, 0, left), index); - this.buffer = new Uint8Array(value, left); - return array.buffer; + array.set(value.subarray(0, left), index); + this.buffer = value.subarray(left); + return array; } - array.set(new Uint8Array(value), index); + array.set(value, index); index += value.byteLength; } - return array.buffer; + return array; } public close() { diff --git a/libraries/adb/src/stream/transform.ts b/libraries/adb/src/stream/transform.ts index c2af14ea..e756a683 100644 --- a/libraries/adb/src/stream/transform.ts +++ b/libraries/adb/src/stream/transform.ts @@ -1,9 +1,9 @@ -import Struct, { decodeUtf8, StructLike, StructValueType } from "@yume-chan/struct"; -import { chunkArrayLike } from "../utils/chunk"; +import Struct, { StructLike, StructValueType } from "@yume-chan/struct"; +import { decodeUtf8 } from "../utils"; import { BufferedStream, BufferedStreamEndedError } from "./buffered"; -import { TransformStream, WritableStream, WritableStreamDefaultWriter, ReadableStream, ReadableStreamDefaultReader } from "./detect"; +import { ReadableStream, ReadableStreamDefaultReader, TransformStream, WritableStream, WritableStreamDefaultWriter } from "./detect"; -export class DecodeUtf8Stream extends TransformStream{ +export class DecodeUtf8Stream extends TransformStream{ public constructor() { super({ transform(chunk, controller) { @@ -29,10 +29,10 @@ export class GatherStringStream extends TransformStream{ // TODO: Find other ways to implement `StructTransformStream` export class StructDeserializeStream> - extends TransformStream>{ + extends TransformStream>{ public constructor(struct: T) { // Convert incoming chunk to a `ReadableStream` - const passthrough = new TransformStream(); + const passthrough = new TransformStream(); const passthroughWriter = passthrough.writable.getWriter(); // Convert the `ReadableSteam` to a `BufferedStream` const bufferedStream = new BufferedStream(passthrough.readable); @@ -66,11 +66,11 @@ export class StructDeserializeStream> } export class StructSerializeStream> - extends TransformStream{ + extends TransformStream{ constructor(struct: T) { super({ transform(chunk, controller) { - controller.enqueue(struct.serialize(chunk)); + controller.enqueue(new Uint8Array(struct.serialize(chunk))); }, }); } @@ -149,12 +149,14 @@ export class WrapReadableStream, S> extends Reada } } -export class ChunkStream extends TransformStream{ +export class ChunkStream extends TransformStream{ public constructor(size: number) { super({ transform(chunk, controller) { - for (const piece of chunkArrayLike(chunk, size)) { - controller.enqueue(piece); + for (let start = 0; start < chunk.length; start += size) { + const end = start + size; + controller.enqueue(chunk.slice(start, end)); + start = end; } } }); diff --git a/libraries/adb/src/utils/base64.spec.ts b/libraries/adb/src/utils/base64.spec.ts index 79a9cc03..1e5f5bca 100644 --- a/libraries/adb/src/utils/base64.spec.ts +++ b/libraries/adb/src/utils/base64.spec.ts @@ -33,43 +33,41 @@ describe('base64', () => { describe('decodeBase64', () => { it("input length 0", () => { - expect(new Uint8Array(decodeBase64(''))).toEqual(new Uint8Array()); + expect(decodeBase64('')).toEqual(new Uint8Array()); }); it("input length 1", () => { - expect(new Uint8Array(decodeBase64('AA=='))).toEqual(new Uint8Array([0])); + expect(decodeBase64('AA==')).toEqual(new Uint8Array([0])); }); it("input length 2", () => { - expect(new Uint8Array(decodeBase64('AAE='))).toEqual(new Uint8Array([0, 1])); + expect(decodeBase64('AAE=')).toEqual(new Uint8Array([0, 1])); }); it("input length 3", () => { /* cspell: disable-next-line */ - expect(new Uint8Array(decodeBase64('AAEC'))).toEqual(new Uint8Array([0, 1, 2])); + expect(decodeBase64('AAEC')).toEqual(new Uint8Array([0, 1, 2])); }); it("input length 4", () => { /* cspell: disable-next-line */ - expect(new Uint8Array(decodeBase64('AAECAw=='))).toEqual(new Uint8Array([0, 1, 2, 3])); + expect(decodeBase64('AAECAw==')).toEqual(new Uint8Array([0, 1, 2, 3])); }); it("input length 5", () => { /* cspell: disable-next-line */ - expect(new Uint8Array(decodeBase64('AAECAwQ='))).toEqual(new Uint8Array([0, 1, 2, 3, 4])); + expect(decodeBase64('AAECAwQ=')).toEqual(new Uint8Array([0, 1, 2, 3, 4])); }); it("input length 6", () => { /* cspell: disable-next-line */ - expect(new Uint8Array(decodeBase64('AAECAwQF'))).toEqual(new Uint8Array([0, 1, 2, 3, 4, 5])); + expect(decodeBase64('AAECAwQF')).toEqual(new Uint8Array([0, 1, 2, 3, 4, 5])); }); it("all byte values", () => { expect( - new Uint8Array( - decodeBase64( - 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==' - ) + decodeBase64( + 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==' ) ).toEqual(new Uint8Array(Array.from({ length: 256 }, (_, index) => index))); }); @@ -78,21 +76,17 @@ describe('base64', () => { describe('encodeBase64', () => { it('input length 0', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array() - ) + encodeBase64( + new Uint8Array() ) ).toEqual(new Uint8Array()); }); it('input length 1', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - [0] - ) + encodeBase64( + new Uint8Array( + [0] ) ) ).toEqual(new Uint8Array([65, 65, 61, 61])); // AA== @@ -100,11 +94,9 @@ describe('base64', () => { it('input length 2', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - [0, 1] - ) + encodeBase64( + new Uint8Array( + [0, 1] ) ) ).toEqual(new Uint8Array([65, 65, 69, 61])); // AAE= @@ -112,11 +104,9 @@ describe('base64', () => { it('input length 3', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - [0, 1, 2] - ) + encodeBase64( + new Uint8Array( + [0, 1, 2] ) ) /* cspell: disable-next-line */ @@ -125,11 +115,9 @@ describe('base64', () => { it('input length 4', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - [0, 1, 2, 3] - ) + encodeBase64( + new Uint8Array( + [0, 1, 2, 3] ) ) /* cspell: disable-next-line */ @@ -138,11 +126,9 @@ describe('base64', () => { it('input length 5', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - [0, 1, 2, 3, 4] - ) + encodeBase64( + new Uint8Array( + [0, 1, 2, 3, 4] ) ) /* cspell: disable-next-line */ @@ -151,11 +137,9 @@ describe('base64', () => { it('input length 6', () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - [0, 1, 2, 3, 4, 5] - ) + encodeBase64( + new Uint8Array( + [0, 1, 2, 3, 4, 5] ) ) /* cspell: disable-next-line */ @@ -164,11 +148,9 @@ describe('base64', () => { it("all byte values", () => { expect( - new Uint8Array( - encodeBase64( - new Uint8Array( - Array.from({ length: 256 }, (_, index) => index) - ) + encodeBase64( + new Uint8Array( + Array.from({ length: 256 }, (_, index) => index) ) ) ).toEqual(new Uint8Array([65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81, 111, 76, 68, 65, 48, 79, 68, 120, 65, 82, 69, 104, 77, 85, 70, 82, 89, 88, 71, 66, 107, 97, 71, 120, 119, 100, 72, 104, 56, 103, 73, 83, 73, 106, 74, 67, 85, 109, 74, 121, 103, 112, 75, 105, 115, 115, 76, 83, 52, 118, 77, 68, 69, 121, 77, 122, 81, 49, 78, 106, 99, 52, 79, 84, 111, 55, 80, 68, 48, 43, 80, 48, 66, 66, 81, 107, 78, 69, 82, 85, 90, 72, 83, 69, 108, 75, 83, 48, 120, 78, 84, 107, 57, 81, 85, 86, 74, 84, 86, 70, 86, 87, 86, 49, 104, 90, 87, 108, 116, 99, 88, 86, 53, 102, 89, 71, 70, 105, 89, 50, 82, 108, 90, 109, 100, 111, 97, 87, 112, 114, 98, 71, 49, 117, 98, 51, 66, 120, 99, 110, 78, 48, 100, 88, 90, 51, 101, 72, 108, 54, 101, 51, 120, 57, 102, 110, 43, 65, 103, 89, 75, 68, 104, 73, 87, 71, 104, 52, 105, 74, 105, 111, 117, 77, 106, 89, 54, 80, 107, 74, 71, 83, 107, 53, 83, 86, 108, 112, 101, 89, 109, 90, 113, 98, 110, 74, 50, 101, 110, 54, 67, 104, 111, 113, 79, 107, 112, 97, 97, 110, 113, 75, 109, 113, 113, 54, 121, 116, 114, 113, 43, 119, 115, 98, 75, 122, 116, 76, 87, 50, 116, 55, 105, 53, 117, 114, 117, 56, 118, 98, 54, 47, 119, 77, 72, 67, 119, 56, 84, 70, 120, 115, 102, 73, 121, 99, 114, 76, 122, 77, 51, 79, 122, 57, 68, 82, 48, 116, 80, 85, 49, 100, 98, 88, 50, 78, 110, 97, 50, 57, 122, 100, 51, 116, 47, 103, 52, 101, 76, 106, 53, 79, 88, 109, 53, 43, 106, 112, 54, 117, 118, 115, 55, 101, 55, 118, 56, 80, 72, 121, 56, 47, 84, 49, 57, 118, 102, 52, 43, 102, 114, 55, 47, 80, 51, 43, 47, 119, 61, 61])); diff --git a/libraries/adb/src/utils/base64.ts b/libraries/adb/src/utils/base64.ts index d7c76c0b..fcd72cfa 100644 --- a/libraries/adb/src/utils/base64.ts +++ b/libraries/adb/src/utils/base64.ts @@ -26,46 +26,35 @@ export function calculateBase64EncodedLength(inputLength: number): [outputLength } export function encodeBase64( - input: ArrayBuffer | Uint8Array, + input: Uint8Array, inputOffset?: number, inputLength?: number, -): ArrayBuffer; // overload 1 +): Uint8Array; // overload 1 export function encodeBase64( - input: ArrayBuffer | Uint8Array, - output: ArrayBuffer | Uint8Array, + input: Uint8Array, + output: Uint8Array, outputOffset?: number ): number; // overload 2 export function encodeBase64( - input: ArrayBuffer | Uint8Array, + input: Uint8Array, inputOffset: number, - output: ArrayBuffer | Uint8Array, + output: Uint8Array, outputOffset?: number ): number; // overload 3 export function encodeBase64( - input: ArrayBuffer | Uint8Array, + input: Uint8Array, inputOffset: number, inputLength: number, - output: ArrayBuffer | Uint8Array, + output: Uint8Array, outputOffset?: number ): number; // overload 4 export function encodeBase64( - input: ArrayBuffer | Uint8Array, - arg1?: number | ArrayBuffer | Uint8Array, - arg2?: number | ArrayBuffer | Uint8Array, - _arg3?: number | ArrayBuffer | Uint8Array, + input: Uint8Array, + arg1?: number | Uint8Array, + arg2?: number | Uint8Array, + _arg3?: number | Uint8Array, _arg4?: number, -): ArrayBuffer | number { - if (input instanceof ArrayBuffer) { - input = new Uint8Array(input); - } - - // Because `Uint8Array` is type compatible with `ArrayBuffer`, - // TypeScript doesn't correctly narrow `input` to `Uint8Array` when assigning. - // Manually eliminate `ArrayBuffer` from `input` with a type guard. - if (input instanceof ArrayBuffer) { - return input; - } - +): Uint8Array | number { let inputOffset: number; let inputLength: number; let output: Uint8Array; @@ -82,46 +71,33 @@ export function encodeBase64( outputArgumentIndex = 3; } else { // overload 3 - inputLength = input.byteLength - inputOffset; + inputLength = input.length - inputOffset; outputArgumentIndex = 2; } } else { // overload 2 inputOffset = 0; - inputLength = input.byteLength; + inputLength = input.length; outputArgumentIndex = 1; } const [outputLength, paddingLength] = calculateBase64EncodedLength(inputLength); - let maybeOutput: ArrayBuffer | Uint8Array | undefined = arguments[outputArgumentIndex]; - let outputType: 'ArrayBuffer' | 'number'; + let maybeOutput: Uint8Array | undefined = arguments[outputArgumentIndex]; + let outputType: 'Uint8Array' | 'number'; if (maybeOutput) { outputOffset = arguments[outputArgumentIndex + 1] ?? 0; - if (maybeOutput.byteLength - outputOffset < outputLength) { + if (maybeOutput.length - outputOffset < outputLength) { throw new Error('output buffer is too small'); } - if (maybeOutput instanceof ArrayBuffer) { - output = new Uint8Array(maybeOutput); - } else { - output = maybeOutput; - } - + output = maybeOutput; outputType = 'number'; } else { - const buffer = new ArrayBuffer(outputLength); - output = new Uint8Array(buffer); + output = new Uint8Array(outputLength); outputOffset = 0; - outputType = 'ArrayBuffer'; - } - - // Because `Uint8Array` is type compatible with `ArrayBuffer`, - // TypeScript doesn't correctly narrow `output` to `Uint8Array` when assigning. - // Manually eliminate `ArrayBuffer` from `output` with a type guard. - if (output instanceof ArrayBuffer) { - return output; + outputType = 'Uint8Array'; } if (input.buffer === output.buffer) { @@ -209,14 +185,14 @@ export function encodeBase64( outputIndex -= 1; } - if (outputType === 'ArrayBuffer') { - return output.buffer; + if (outputType === 'Uint8Array') { + return output; } else { return outputLength; } } -export function decodeBase64(input: string): ArrayBuffer { +export function decodeBase64(input: string): Uint8Array { let padding: number; if (input[input.length - 2] === '=') { padding = 2; @@ -275,5 +251,5 @@ export function decodeBase64(input: string): ArrayBuffer { result[dIndex] = (a << 2) | ((b & 0b11_0000) >> 4); } - return result.buffer; + return result; } diff --git a/libraries/adb/src/utils/chunk.ts b/libraries/adb/src/utils/chunk.ts deleted file mode 100644 index a2dd6d6b..00000000 --- a/libraries/adb/src/utils/chunk.ts +++ /dev/null @@ -1,70 +0,0 @@ -export function* chunkArrayLike( - value: ArrayLike | ArrayBufferLike, - size: number -): Generator { - if ('length' in value) { - value = new Uint8Array(value).buffer; - } - - if (value.byteLength <= size) { - return yield value; - } - - for (let i = 0; i < value.byteLength; i += size) { - yield value.slice(i, i + size); - } -} - -export async function* chunkAsyncIterable( - value: AsyncIterable, - size: number -): AsyncGenerator { - let result = new Uint8Array(size); - let index = 0; - for await (let buffer of value) { - // `result` has some data, `result + buffer` is enough - if (index !== 0 && index + buffer.byteLength >= size) { - const remainder = size - index; - result.set(new Uint8Array(buffer, 0, remainder), index); - yield result.buffer; - - result = new Uint8Array(size); - index = 0; - - if (buffer.byteLength > remainder) { - // `buffer` still has some data - buffer = buffer.slice(remainder); - } else { - continue; - } - } - - // `result` is empty, `buffer` alone is enough - if (buffer.byteLength >= size) { - let remainder = false; - for (const chunk of chunkArrayLike(buffer, size)) { - if (chunk.byteLength === size) { - yield chunk; - continue; - } - - // `buffer` still has some data - remainder = true; - buffer = chunk; - } - - if (!remainder) { - continue; - } - } - - // `result` has some data but `result + buffer` is still not enough - // or after previous steps `buffer` still has some data - result.set(new Uint8Array(buffer), index); - index += buffer.byteLength; - } - - if (index !== 0) { - yield result.buffer.slice(0, index); - } -} diff --git a/libraries/adb/src/utils/encoding.ts b/libraries/adb/src/utils/encoding.ts index 18f1ecbf..3d8e25af 100644 --- a/libraries/adb/src/utils/encoding.ts +++ b/libraries/adb/src/utils/encoding.ts @@ -4,10 +4,10 @@ const Utf8Encoder = new TextEncoder(); // @ts-expect-error @types/node missing `TextDecoder` const Utf8Decoder = new TextDecoder(); -export function encodeUtf8(input: string): ArrayBuffer { - return Utf8Encoder.encode(input).buffer; +export function encodeUtf8(input: string): Uint8Array { + return Utf8Encoder.encode(input); } -export function decodeUtf8(buffer: ArrayBuffer): string { +export function decodeUtf8(buffer: Uint8Array): string { return Utf8Decoder.decode(buffer); } diff --git a/libraries/adb/src/utils/index.ts b/libraries/adb/src/utils/index.ts index 91043e6c..9f712889 100644 --- a/libraries/adb/src/utils/index.ts +++ b/libraries/adb/src/utils/index.ts @@ -1,4 +1,3 @@ export * from './auto-reset-event'; export * from './base64'; -export * from './chunk'; export * from './encoding'; diff --git a/libraries/struct/src/struct.spec.ts b/libraries/struct/src/struct.spec.ts index 3199a5f0..dd380d0e 100644 --- a/libraries/struct/src/struct.spec.ts +++ b/libraries/struct/src/struct.spec.ts @@ -1,6 +1,6 @@ import { StructAsyncDeserializeStream, StructDefaultOptions, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from './basic'; import { Struct } from './struct'; -import { ArrayBufferFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types'; +import { ArrayBufferFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, ArrayBufferViewFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types'; import { ValueOrPromise } from './utils'; class MockDeserializationStream implements StructDeserializeStream { @@ -181,7 +181,7 @@ describe('Struct', () => { const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition; expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition); - expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType); + expect(definition.type).toBeInstanceOf(ArrayBufferViewFieldType); expect(definition.options.length).toBe(10); }); @@ -220,7 +220,7 @@ describe('Struct', () => { const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition; expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition); - expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType); + expect(definition.type).toBeInstanceOf(ArrayBufferViewFieldType); expect(definition.options.lengthField).toBe('barLength'); }); diff --git a/libraries/struct/src/struct.ts b/libraries/struct/src/struct.ts index 392345ee..6459165f 100644 --- a/libraries/struct/src/struct.ts +++ b/libraries/struct/src/struct.ts @@ -3,7 +3,7 @@ import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic'; import { StructDefaultOptions, StructValue } from './basic'; import { Syncbird } from "./syncbird"; -import { ArrayBufferFieldType, ArrayBufferLikeFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types'; +import { ArrayBufferFieldType, ArrayBufferLikeFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, ArrayBufferViewFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types'; import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils"; export interface StructLike { @@ -450,17 +450,30 @@ export class Struct< return this.arrayBufferLike(name, ArrayBufferFieldType.instance, options); }; + public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator< + TFields, + TOmitInitKey, + TExtra, + TPostDeserialized, + ArrayBufferViewFieldType + > = ( + name: PropertyKey, + options: any + ): any => { + return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8Array, options); + }; + public uint8ClampedArray: BoundArrayBufferLikeFieldDefinitionCreator< TFields, TOmitInitKey, TExtra, TPostDeserialized, - Uint8ClampedArrayFieldType + ArrayBufferViewFieldType > = ( name: PropertyKey, options: any ): any => { - return this.arrayBufferLike(name, Uint8ClampedArrayFieldType.instance, options); + return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8ClampedArray, options); }; public string: BoundArrayBufferLikeFieldDefinitionCreator< diff --git a/libraries/struct/src/types/array-buffer.spec.ts b/libraries/struct/src/types/array-buffer.spec.ts index ca4aa822..8c4114e3 100644 --- a/libraries/struct/src/types/array-buffer.spec.ts +++ b/libraries/struct/src/types/array-buffer.spec.ts @@ -1,5 +1,5 @@ import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../basic'; -import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, Uint8ClampedArrayFieldType } from './array-buffer'; +import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, ArrayBufferViewFieldType } from './array-buffer'; class MockDeserializationStream implements StructDeserializeStream { public buffer = new ArrayBuffer(0); @@ -32,18 +32,18 @@ describe('Types', () => { describe('Uint8ClampedArrayFieldType', () => { it('should have a static instance', () => { - expect(Uint8ClampedArrayFieldType.instance).toBeInstanceOf(Uint8ClampedArrayFieldType); + expect(ArrayBufferViewFieldType.instance).toBeInstanceOf(ArrayBufferViewFieldType); }); it('`#toArrayBuffer` should return its `buffer`', () => { const array = new Uint8ClampedArray(10); const buffer = array.buffer; - expect(Uint8ClampedArrayFieldType.instance.toArrayBuffer(array)).toBe(buffer); + expect(ArrayBufferViewFieldType.instance.toArrayBuffer(array)).toBe(buffer); }); it('`#fromArrayBuffer` should return a view of the `ArrayBuffer`', () => { const arrayBuffer = new ArrayBuffer(10); - const array = Uint8ClampedArrayFieldType.instance.fromArrayBuffer(arrayBuffer); + const array = ArrayBufferViewFieldType.instance.fromArrayBuffer(arrayBuffer); expect(array).toHaveProperty('buffer', arrayBuffer); expect(array).toHaveProperty('byteOffset', 0); expect(array).toHaveProperty('byteLength', 10); @@ -51,7 +51,7 @@ describe('Types', () => { it('`#getSize` should return the `byteLength` of the `Uint8ClampedArray`', () => { const array = new Uint8ClampedArray(10); - expect(Uint8ClampedArrayFieldType.instance.getSize(array)).toBe(10); + expect(ArrayBufferViewFieldType.instance.getSize(array)).toBe(10); }); }); @@ -104,7 +104,7 @@ describe('Types', () => { it('should work with `Uint8ClampedArrayFieldType`', async () => { const size = 10; - const definition = new MockArrayBufferFieldDefinition(Uint8ClampedArrayFieldType.instance, size); + const definition = new MockArrayBufferFieldDefinition(ArrayBufferViewFieldType.instance, size); const context = new MockDeserializationStream(); const buffer = new ArrayBuffer(size); diff --git a/libraries/struct/src/types/array-buffer.ts b/libraries/struct/src/types/array-buffer.ts index 83022ce9..8a24040b 100644 --- a/libraries/struct/src/types/array-buffer.ts +++ b/libraries/struct/src/types/array-buffer.ts @@ -1,3 +1,5 @@ +// cspell: ignore syncbird + import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic'; import { Syncbird } from "../syncbird"; import { decodeUtf8, encodeUtf8, ValueOrPromise } from "../utils"; @@ -56,24 +58,30 @@ export class ArrayBufferFieldType extends ArrayBufferLikeFieldType } } -/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `Uint8ClampedArray` */ -export class Uint8ClampedArrayFieldType - extends ArrayBufferLikeFieldType { - public static readonly instance = new Uint8ClampedArrayFieldType(); +export type ArrayBufferViewConstructor = new (array: ArrayLike | ArrayBufferLike) => T; - protected constructor() { +/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `Uint8ClampedArray` */ +export class ArrayBufferViewFieldType + extends ArrayBufferLikeFieldType { + public static readonly uint8Array = new ArrayBufferViewFieldType(Uint8Array); + public static readonly uint8ClampedArray = new ArrayBufferViewFieldType(Uint8ClampedArray); + + protected type: ArrayBufferViewConstructor; + + public constructor(type: ArrayBufferViewConstructor) { super(); + this.type = type; } - public toArrayBuffer(value: Uint8ClampedArray): ArrayBuffer { + public toArrayBuffer(value: T): ArrayBuffer { return value.buffer; } - public fromArrayBuffer(arrayBuffer: ArrayBuffer): Uint8ClampedArray { - return new Uint8ClampedArray(arrayBuffer); + public fromArrayBuffer(arrayBuffer: ArrayBuffer): T { + return new this.type(arrayBuffer); } - public getSize(value: Uint8ClampedArray): number { + public getSize(value: T): number { return value.byteLength; } } diff --git a/libraries/struct/src/utils.ts b/libraries/struct/src/utils.ts index 91e97a39..df0d419f 100644 --- a/libraries/struct/src/utils.ts +++ b/libraries/struct/src/utils.ts @@ -53,10 +53,10 @@ const Utf8Encoder = new TextEncoder(); // @ts-expect-error @types/node missing `TextDecoder` const Utf8Decoder = new TextDecoder(); -export function encodeUtf8(input: string): ArrayBuffer { +export function encodeUtf8(input: string): Uint8Array { return Utf8Encoder.encode(input).buffer; } -export function decodeUtf8(buffer: ArrayBuffer): string { +export function decodeUtf8(buffer: Uint8Array): string { return Utf8Decoder.decode(buffer); }