diff --git a/libraries/adb/src/adb.ts b/libraries/adb/src/adb.ts index 673f65eb..f781fb38 100644 --- a/libraries/adb/src/adb.ts +++ b/libraries/adb/src/adb.ts @@ -63,6 +63,7 @@ export class Adb implements Closeable { } } }), { + // Don't cancel the source ReadableStream on AbortSignal abort. preventCancel: true, signal: abortController.signal, }) @@ -146,7 +147,7 @@ export class Adb implements Closeable { private _device: string | undefined; public get device() { return this._device; } - private _features: AdbFeatures[] | undefined; + private _features: AdbFeatures[] = []; public get features() { return this._features; } public readonly subprocess: AdbSubprocess; @@ -190,8 +191,6 @@ export class Adb implements Closeable { } private parseBanner(banner: string): void { - this._features = []; - const pieces = banner.split('::'); if (pieces.length > 1) { const props = pieces[1]!; @@ -224,8 +223,13 @@ export class Adb implements Closeable { } } - public addIncomingSocketHandler(handler: AdbIncomingSocketHandler) { - return this.dispatcher.addIncomingSocketHandler(handler); + /** + * Add a handler for incoming socket. + * @param handler A function to call with new incoming sockets. It must return `true` if it accepts the socket. + * @returns A function to remove the handler. + */ + public onIncomingSocket(handler: AdbIncomingSocketHandler) { + return this.dispatcher.onIncomingSocket(handler); } public async createSocket(service: string): Promise { diff --git a/libraries/adb/src/commands/framebuffer.ts b/libraries/adb/src/commands/framebuffer.ts index ab312d36..b9a20b54 100644 --- a/libraries/adb/src/commands/framebuffer.ts +++ b/libraries/adb/src/commands/framebuffer.ts @@ -7,19 +7,6 @@ const Version = new Struct({ littleEndian: true }) .uint32('version'); -/* - * ADB uses 8 int32 fields to describe bit depths - * The only combination I have seen is RGBA8888, which is - * red_offset: 0 - * red_length: 8 - * blue_offset: 16 - * blue_length: 8 - * green_offset: 8 - * green_length: 8 - * alpha_offset: 24 - * alpha_length: 8 - */ - export const AdbFrameBufferV1 = new Struct({ littleEndian: true }) .uint32('bpp') @@ -57,6 +44,22 @@ export const AdbFrameBufferV2 = export type AdbFrameBufferV2 = typeof AdbFrameBufferV2['TDeserializeResult']; +/** + * ADB uses 8 int32 fields to describe bit depths + * + * The only combination I have seen is RGBA8888, which is + * + * red_offset: 0 + * red_length: 8 + * blue_offset: 16 + * blue_length: 8 + * green_offset: 8 + * green_length: 8 + * alpha_offset: 24 + * alpha_length: 8 + * + * But it doesn't mean that other combinations are not possible. + */ export type AdbFrameBuffer = AdbFrameBufferV1 | AdbFrameBufferV2; export async function framebuffer(adb: Adb): Promise { @@ -65,6 +68,7 @@ export async function framebuffer(adb: Adb): Promise { const { version } = await Version.deserialize(stream); switch (version) { case 1: + // TODO: AdbFrameBuffer: does all v1 responses uses the same color space? Add it so the command returns same format for all versions. return AdbFrameBufferV1.deserialize(stream); case 2: return AdbFrameBufferV2.deserialize(stream); diff --git a/libraries/adb/src/commands/install.ts b/libraries/adb/src/commands/install.ts index 1a4d43d7..d6f9b3ab 100644 --- a/libraries/adb/src/commands/install.ts +++ b/libraries/adb/src/commands/install.ts @@ -13,6 +13,7 @@ export function install( return new WrapWritableStream({ async start() { // TODO: install: support other install apk methods (streaming, etc.) + // TODO: install: support split apk formats (`adb install-multiple`) // Upload apk file to tmp folder sync = await adb.sync(); diff --git a/libraries/adb/src/commands/reverse.ts b/libraries/adb/src/commands/reverse.ts index d34e2675..4bef1434 100644 --- a/libraries/adb/src/commands/reverse.ts +++ b/libraries/adb/src/commands/reverse.ts @@ -41,7 +41,7 @@ export class AdbReverseCommand extends AutoDisposable { super(); this.adb = adb; - this.addDisposable(this.adb.addIncomingSocketHandler(this.handleIncomingSocket)); + this.addDisposable(this.adb.onIncomingSocket(this.handleIncomingSocket)); } protected handleIncomingSocket = async (socket: AdbSocket) => { @@ -80,8 +80,8 @@ export class AdbReverseCommand extends AutoDisposable { /** * @param deviceAddress The address adbd on device is listening on. Can be `tcp:0` to let adbd choose an available TCP port by itself. * @param localAddress Native ADB client will open a connection to this address when reverse connection received. In WebADB, it's only used to uniquely identify a reverse tunnel registry, `handler` will be called to handle the connection. - * @param handler A callback to handle incoming connections - * @returns If `deviceAddress` is `tcp:0`, return `tcp:{ACTUAL_LISTENING_PORT}`; otherwise, return `deviceAddress`. + * @param handler A callback to handle incoming connections. It must return `true` if it accepts the connection. + * @returns `tcp:{ACTUAL_LISTENING_PORT}`, If `deviceAddress` is `tcp:0`; otherwise, `deviceAddress`. */ public async add( deviceAddress: string, @@ -91,7 +91,7 @@ export class AdbReverseCommand extends AutoDisposable { const stream = await this.sendRequest(`reverse:forward:${deviceAddress};${localAddress}`); // `tcp:0` tells the device to pick an available port. - // Begin with Android 8, device will respond with the selected port for all `tcp:` requests. + // On Android >=8, device will respond with the selected port for all `tcp:` requests. if (deviceAddress.startsWith('tcp:')) { let length: number | undefined; try { @@ -101,7 +101,7 @@ export class AdbReverseCommand extends AutoDisposable { throw e; } - // Device before Android 8 doesn't have this response. + // Android <8 doesn't have this response. // (the stream is closed now) // Can be safely ignored. } diff --git a/libraries/adb/src/commands/subprocess/protocols/none.ts b/libraries/adb/src/commands/subprocess/protocols/none.ts index 10f70040..26eea50d 100644 --- a/libraries/adb/src/commands/subprocess/protocols/none.ts +++ b/libraries/adb/src/commands/subprocess/protocols/none.ts @@ -21,7 +21,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol { public static async raw(adb: Adb, command: string) { // `shell,raw:${command}` also triggers raw mode, - // But is not supported before Android 7. + // But is not supported on Android version <7. return new AdbSubprocessNoneProtocol(await adb.createSocket(`exec:${command}`)); } @@ -50,6 +50,8 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol { public constructor(socket: AdbSocket) { this.socket = socket; + // Link `stdout`, `stderr` and `stdin` together, + // so closing any of them will close the others. this.duplex = new DuplexStreamFactory({ close: async () => { await this.socket.close(); @@ -62,7 +64,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol { } public resize() { - // Not supported + // Not supported, but don't throw. } public kill() { diff --git a/libraries/adb/src/commands/subprocess/protocols/shell.ts b/libraries/adb/src/commands/subprocess/protocols/shell.ts index a3f7053f..15347d6c 100644 --- a/libraries/adb/src/commands/subprocess/protocols/shell.ts +++ b/libraries/adb/src/commands/subprocess/protocols/shell.ts @@ -1,5 +1,5 @@ import { PromiseResolver } from '@yume-chan/async'; -import { pipeFrom, PushReadableStream, StructDeserializeStream, StructSerializeStream, TransformStream, WritableStream, type WritableStreamDefaultWriter, type PushReadableStreamController, type ReadableStream } from '@yume-chan/stream-extra'; +import { pipeFrom, PushReadableStream, StructDeserializeStream, StructSerializeStream, TransformStream, WritableStream, type PushReadableStreamController, type ReadableStream, type WritableStreamDefaultWriter } from '@yume-chan/stream-extra'; import Struct, { placeholder, type StructValueType } from '@yume-chan/struct'; import type { Adb } from '../../../adb.js'; @@ -103,7 +103,7 @@ class MultiplexStream{ */ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol { public static isSupported(adb: Adb) { - return adb.features!.includes(AdbFeatures.ShellV2); + return adb.features.includes(AdbFeatures.ShellV2); } public static async pty(adb: Adb, command: string) { @@ -179,7 +179,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol { data: encodeUtf8( // The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}` // However, according to https://linux.die.net/man/4/tty_ioctl - // `x_pixels` and `y_pixels` are not used, so always passing `0` is fine. + // `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine. `${rows}x${cols},0x0\0` ), }); diff --git a/libraries/adb/src/commands/subprocess/protocols/types.ts b/libraries/adb/src/commands/subprocess/protocols/types.ts index 83a4a8e5..61c65691 100644 --- a/libraries/adb/src/commands/subprocess/protocols/types.ts +++ b/libraries/adb/src/commands/subprocess/protocols/types.ts @@ -6,17 +6,17 @@ import type { AdbSocket } from '../../../socket/index.js'; export interface AdbSubprocessProtocol { /** - * A WritableStream that writes to the `stdin` pipe. + * A WritableStream that writes to the `stdin` stream. */ readonly stdin: WritableStream; /** - * The `stdout` pipe of the process. + * The `stdout` stream of the process. */ readonly stdout: ReadableStream; /** - * The `stderr` pipe of the process. + * The `stderr` stream of the process. * * Note: Some `AdbSubprocessProtocol` doesn't separate `stdout` and `stderr`, * All output will be sent to `stdout`. diff --git a/libraries/adb/src/commands/sync/list.ts b/libraries/adb/src/commands/sync/list.ts index 244f1a0c..6061784d 100644 --- a/libraries/adb/src/commands/sync/list.ts +++ b/libraries/adb/src/commands/sync/list.ts @@ -2,7 +2,7 @@ import type { BufferedReadableStream, WritableStreamDefaultWriter } from '@yume- import Struct from '@yume-chan/struct'; import { AdbSyncRequestId, adbSyncWriteRequest } from './request.js'; -import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response.js'; +import { adbSyncReadResponses, AdbSyncResponseId } from './response.js'; import { AdbSyncLstatResponse, AdbSyncStatResponse, type AdbSyncStat } from './stat.js'; export interface AdbSyncEntry extends AdbSyncStat { @@ -27,61 +27,28 @@ export const AdbSyncEntry2Response = export type AdbSyncEntry2Response = typeof AdbSyncEntry2Response['TDeserializeResult']; -const LIST_V1_RESPONSE_TYPES = { - [AdbSyncResponseId.Entry]: AdbSyncEntryResponse, - [AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntryResponse.size), -}; - -const LIST_V2_RESPONSE_TYPES = { - [AdbSyncResponseId.Entry2]: AdbSyncEntry2Response, - [AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntry2Response.size), -}; - export async function* adbSyncOpenDir( stream: BufferedReadableStream, writer: WritableStreamDefaultWriter, path: string, v2: boolean, ): AsyncGenerator { - let requestId: AdbSyncRequestId.List | AdbSyncRequestId.List2; - let responseTypes: typeof LIST_V1_RESPONSE_TYPES | typeof LIST_V2_RESPONSE_TYPES; - if (v2) { - requestId = AdbSyncRequestId.List2; - responseTypes = LIST_V2_RESPONSE_TYPES; + await adbSyncWriteRequest(writer, AdbSyncRequestId.List2, path); + yield* adbSyncReadResponses(stream, AdbSyncResponseId.Entry2, AdbSyncEntry2Response); } else { - requestId = AdbSyncRequestId.List; - responseTypes = LIST_V1_RESPONSE_TYPES; - } - - await adbSyncWriteRequest(writer, requestId, path); - - while (true) { - const response = await adbSyncReadResponse(stream, responseTypes); - switch (response.id) { - case AdbSyncResponseId.Entry: - yield { - mode: response.mode, - size: BigInt(response.size), - mtime: BigInt(response.mtime), - get type() { return response.type; }, - get permission() { return response.permission; }, - name: response.name, - }; - break; - case AdbSyncResponseId.Entry2: - // `LST2` can return error codes for failed `lstat` calls. - // `LIST` just ignores them. - // But they only contain `name` so still pretty useless. - if (response.error !== 0) { - continue; - } - yield response; - break; - case AdbSyncResponseId.Done: - return; - default: - throw new Error('Unexpected response id'); + await adbSyncWriteRequest(writer, AdbSyncRequestId.List, path); + for await (const item of adbSyncReadResponses(stream, AdbSyncResponseId.Entry, AdbSyncEntryResponse)) { + // Convert to same format as `AdbSyncEntry2Response` for easier consumption. + // However it will add some overhead. + yield { + mode: item.mode, + size: BigInt(item.size), + mtime: BigInt(item.mtime), + get type() { return item.type; }, + get permission() { return item.permission; }, + name: item.name, + }; } } } diff --git a/libraries/adb/src/commands/sync/pull.ts b/libraries/adb/src/commands/sync/pull.ts index 3ac1a622..13c8bf1b 100644 --- a/libraries/adb/src/commands/sync/pull.ts +++ b/libraries/adb/src/commands/sync/pull.ts @@ -2,7 +2,7 @@ import { BufferedReadableStream, ReadableStream, WritableStreamDefaultWriter } f import Struct from '@yume-chan/struct'; import { AdbSyncRequestId, adbSyncWriteRequest } from './request.js'; -import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response.js'; +import { adbSyncReadResponses, AdbSyncResponseId } from './response.js'; export const AdbSyncDataResponse = new Struct({ littleEndian: true }) @@ -10,35 +10,33 @@ export const AdbSyncDataResponse = .uint8Array('data', { lengthField: 'dataLength' }) .extra({ id: AdbSyncResponseId.Data as const }); -const RESPONSE_TYPES = { - [AdbSyncResponseId.Data]: AdbSyncDataResponse, - [AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncDataResponse.size), -}; +export type AdbSyncDataResponse = typeof AdbSyncDataResponse['TDeserializeResult']; export function adbSyncPull( stream: BufferedReadableStream, writer: WritableStreamDefaultWriter, path: string, ): ReadableStream { + let generator!: AsyncGenerator; return new ReadableStream({ async start() { + // TODO: If `ReadableStream.from(AsyncGenerator)` is added to spec, use it instead. await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path); + generator = adbSyncReadResponses(stream, AdbSyncResponseId.Data, AdbSyncDataResponse); }, async pull(controller) { - const response = await adbSyncReadResponse(stream, RESPONSE_TYPES); - switch (response.id) { - case AdbSyncResponseId.Data: - controller.enqueue(response.data!); - break; - case AdbSyncResponseId.Done: - controller.close(); - break; - default: - throw new Error('Unexpected response id'); + const { done, value } = await generator.next(); + if (done) { + controller.close(); + return; } + controller.enqueue(value.data); }, cancel() { - throw new Error(`Sync commands don't support cancel.`); + try { + generator.return(); + } catch { } + throw new Error(`Sync commands can't be canceled.`); }, }, { highWaterMark: 16 * 1024, diff --git a/libraries/adb/src/commands/sync/push.ts b/libraries/adb/src/commands/sync/push.ts index aeeebe5c..335ad4da 100644 --- a/libraries/adb/src/commands/sync/push.ts +++ b/libraries/adb/src/commands/sync/push.ts @@ -9,10 +9,6 @@ export const AdbSyncOkResponse = new Struct({ littleEndian: true }) .uint32('unused'); -const ResponseTypes = { - [AdbSyncResponseId.Ok]: AdbSyncOkResponse, -}; - export const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024; export function adbSyncPush( @@ -34,7 +30,7 @@ export function adbSyncPush( }, async close() { await adbSyncWriteRequest(writer, AdbSyncRequestId.Done, mtime); - await adbSyncReadResponse(stream, ResponseTypes); + await adbSyncReadResponse(stream, AdbSyncResponseId.Ok, AdbSyncOkResponse); }, }), new ChunkStream(packetSize) diff --git a/libraries/adb/src/commands/sync/response.ts b/libraries/adb/src/commands/sync/response.ts index 52b4fec8..8e7b7900 100644 --- a/libraries/adb/src/commands/sync/response.ts +++ b/libraries/adb/src/commands/sync/response.ts @@ -1,5 +1,5 @@ import type { BufferedReadableStream } from '@yume-chan/stream-extra'; -import Struct, { type StructAsyncDeserializeStream, type StructLike, type StructValueType } from '@yume-chan/struct'; +import Struct, { StructValueType, type StructLike } from '@yume-chan/struct'; import { decodeUtf8 } from '../../utils/index.js'; @@ -15,25 +15,6 @@ export enum AdbSyncResponseId { Fail = 'FAIL', } -// DONE responses' size are always same as the request's normal response. -// For example DONE responses for LIST requests are 16 bytes (same as DENT responses), -// but DONE responses for STAT requests are 12 bytes (same as STAT responses) -// So we need to know responses' size in advance. -export class AdbSyncDoneResponse implements StructLike { - private length: number; - - public readonly id = AdbSyncResponseId.Done; - - public constructor(length: number) { - this.length = length; - } - - public async deserialize(stream: StructAsyncDeserializeStream): Promise { - await stream.read(this.length); - return this; - } -} - export const AdbSyncFailResponse = new Struct({ littleEndian: true }) .uint32('messageLength') @@ -42,24 +23,46 @@ export const AdbSyncFailResponse = throw new Error(object.message); }); -export async function adbSyncReadResponse>>( +export async function adbSyncReadResponse( stream: BufferedReadableStream, - types: T, - // When `T` is a union type, `T[keyof T]` only includes their common keys. - // For example, let `type T = { a: string, b: string } | { a: string, c: string}`, - // `keyof T` is `'a'`, not `'a' | 'b' | 'c'`. - // However, `T extends unknown ? keyof T : never` will distribute `T`, - // so returns all keys. -): Promise> { - const id = decodeUtf8(await stream.read(4)); - - if (id === AdbSyncResponseId.Fail) { - await AdbSyncFailResponse.deserialize(stream); + id: AdbSyncResponseId, + type: StructLike, +): Promise { + const actualId = decodeUtf8(await stream.read(4)); + switch (actualId) { + case AdbSyncResponseId.Fail: + await AdbSyncFailResponse.deserialize(stream); + throw new Error('Unreachable'); + case id: + return await type.deserialize(stream); + default: + throw new Error(`Expected '${id}', but got '${actualId}'`); + } +} + +export async function* adbSyncReadResponses>( + stream: BufferedReadableStream, + id: AdbSyncResponseId, + type: T, +): AsyncGenerator, void, void> { + while (true) { + const actualId = decodeUtf8(await stream.read(4)); + switch (actualId) { + case AdbSyncResponseId.Fail: + await AdbSyncFailResponse.deserialize(stream); + throw new Error('Unreachable'); + case AdbSyncResponseId.Done: + // `DONE` responses' size are always same as the request's normal response. + // + // For example, `DONE` responses for `LIST` requests are 16 bytes (same as `DENT` responses), + // but `DONE` responses for `STAT` requests are 12 bytes (same as `STAT` responses). + await stream.read(type.size); + return; + case id: + yield await type.deserialize(stream); + break; + default: + throw new Error(`Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${actualId}'`); + } } - - if (types[id]) { - return types[id]!.deserialize(stream); - } - - throw new Error(`Expected '${Object.keys(types).join(', ')}', but got '${id}'`); } diff --git a/libraries/adb/src/commands/sync/stat.ts b/libraries/adb/src/commands/sync/stat.ts index b0aef66b..88f93e62 100644 --- a/libraries/adb/src/commands/sync/stat.ts +++ b/libraries/adb/src/commands/sync/stat.ts @@ -96,50 +96,26 @@ export const AdbSyncStatResponse = export type AdbSyncStatResponse = typeof AdbSyncStatResponse['TDeserializeResult']; -const STAT_RESPONSE_TYPES = { - [AdbSyncResponseId.Stat]: AdbSyncStatResponse, -}; - -const LSTAT_RESPONSE_TYPES = { - [AdbSyncResponseId.Lstat]: AdbSyncLstatResponse, -}; - -const LSTAT_V2_RESPONSE_TYPES = { - [AdbSyncResponseId.Lstat2]: AdbSyncStatResponse, -}; - export async function adbSyncLstat( stream: BufferedReadableStream, writer: WritableStreamDefaultWriter, path: string, v2: boolean, ): Promise { - let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2; - let responseTypes: typeof LSTAT_RESPONSE_TYPES | typeof LSTAT_V2_RESPONSE_TYPES; - if (v2) { - requestId = AdbSyncRequestId.Lstat2; - responseTypes = LSTAT_V2_RESPONSE_TYPES; + await adbSyncWriteRequest(writer, AdbSyncRequestId.Lstat2, path); + return await adbSyncReadResponse(stream, AdbSyncResponseId.Lstat2, AdbSyncStatResponse); } else { - requestId = AdbSyncRequestId.Lstat; - responseTypes = LSTAT_RESPONSE_TYPES; - } - - await adbSyncWriteRequest(writer, requestId, path); - const response = await adbSyncReadResponse(stream, responseTypes); - - switch (response.id) { - case AdbSyncResponseId.Lstat: - return { - mode: response.mode, - // Convert to `BigInt` to make it compatible with `AdbSyncStatResponse` - size: BigInt(response.size), - mtime: BigInt(response.mtime), - get type() { return response.type; }, - get permission() { return response.permission; }, - }; - default: - return response; + await adbSyncWriteRequest(writer, AdbSyncRequestId.Lstat, path); + const response = await adbSyncReadResponse(stream, AdbSyncResponseId.Lstat, AdbSyncLstatResponse); + return { + mode: response.mode, + // Convert to `BigInt` to make it compatible with `AdbSyncStatResponse` + size: BigInt(response.size), + mtime: BigInt(response.mtime), + get type() { return response.type; }, + get permission() { return response.permission; }, + }; } } @@ -149,5 +125,5 @@ export async function adbSyncStat( path: string, ): Promise { await adbSyncWriteRequest(writer, AdbSyncRequestId.Stat, path); - return await adbSyncReadResponse(stream, STAT_RESPONSE_TYPES); + return await adbSyncReadResponse(stream, AdbSyncResponseId.Stat, AdbSyncStatResponse); } diff --git a/libraries/adb/src/commands/sync/sync.ts b/libraries/adb/src/commands/sync/sync.ts index 8979dab5..bf52810b 100644 --- a/libraries/adb/src/commands/sync/sync.ts +++ b/libraries/adb/src/commands/sync/sync.ts @@ -38,20 +38,20 @@ export class AdbSync extends AutoDisposable { protected sendLock = this.addDisposable(new AutoResetEvent()); public get supportsStat(): boolean { - return this.adb.features!.includes(AdbFeatures.StatV2); + return this.adb.features.includes(AdbFeatures.StatV2); } public get supportsList2(): boolean { - return this.adb.features!.includes(AdbFeatures.ListV2); + return this.adb.features.includes(AdbFeatures.ListV2); } public get fixedPushMkdir(): boolean { - return this.adb.features!.includes(AdbFeatures.FixedPushMkdir); + return this.adb.features.includes(AdbFeatures.FixedPushMkdir); } public get needPushMkdirWorkaround(): boolean { // https://android.googlesource.com/platform/packages/modules/adb/+/91768a57b7138166e0a3d11f79cd55909dda7014/client/file_sync_client.cpp#1361 - return this.adb.features!.includes(AdbFeatures.ShellV2) && !this.fixedPushMkdir; + return this.adb.features.includes(AdbFeatures.ShellV2) && !this.fixedPushMkdir; } public constructor(adb: Adb, socket: AdbSocket) { diff --git a/libraries/adb/src/crypto.ts b/libraries/adb/src/crypto.ts index 9b8d594e..f61a0d43 100644 --- a/libraries/adb/src/crypto.ts +++ b/libraries/adb/src/crypto.ts @@ -96,7 +96,7 @@ export function parsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] { // Taken from https://stackoverflow.com/a/51562038 // I can't understand, but it does work -// Only used with numbers less than 2^32 so doesn't need BigInt +// Only used with numbers smaller than 2^32 so doesn't need BigInt export function modInverse(a: number, m: number) { a = (a % m + m) % m; if (!a || m < 2) { diff --git a/libraries/adb/src/socket/dispatcher.ts b/libraries/adb/src/socket/dispatcher.ts index 6c615b60..bab6394f 100644 --- a/libraries/adb/src/socket/dispatcher.ts +++ b/libraries/adb/src/socket/dispatcher.ts @@ -168,7 +168,12 @@ export class AdbPacketDispatcher implements Closeable { // the device may also respond with two `CLSE` packets. } - public addIncomingSocketHandler(handler: AdbIncomingSocketHandler): RemoveEventListener { + /** + * Add a handler for incoming socket. + * @param handler A function to call with new incoming sockets. It must return `true` if it accepts the socket. + * @returns A function to remove the handler. + */ + public onIncomingSocket(handler: AdbIncomingSocketHandler): RemoveEventListener { this._incomingSocketHandlers.add(handler); const remove = () => { this._incomingSocketHandlers.delete(handler); @@ -178,7 +183,7 @@ export class AdbPacketDispatcher implements Closeable { } private async handleOpen(packet: AdbPacketData) { - // AsyncOperationManager doesn't support get and skip an ID + // `AsyncOperationManager` doesn't support skipping IDs // Use `add` + `resolve` to simulate this behavior const [localId] = this.initializers.add(); this.initializers.resolve(localId, undefined); diff --git a/libraries/adb/src/socket/socket.ts b/libraries/adb/src/socket/socket.ts index 79c2cce6..0f77af24 100644 --- a/libraries/adb/src/socket/socket.ts +++ b/libraries/adb/src/socket/socket.ts @@ -128,7 +128,7 @@ export class AdbSocketController implements AdbSocketInfo, ReadableWritablePair< } /** - * AdbSocket is a duplex stream. + * A duplex stream representing a socket to ADB daemon. * * To close it, call either `socket.close()`, * `socket.readable.cancel()`, `socket.readable.getReader().cancel()`, diff --git a/libraries/adb/src/utils/base64.ts b/libraries/adb/src/utils/base64.ts index e0de34f6..226b53c3 100644 --- a/libraries/adb/src/utils/base64.ts +++ b/libraries/adb/src/utils/base64.ts @@ -19,15 +19,38 @@ addRange('0', '9'); addRange('+', '+'); addRange('/', '/'); +/** + * Calculate the required length of the output buffer for the given input length. + * + * @param inputLength Length of the input in bytes + * @returns Length of the output in bytes + */ export function calculateBase64EncodedLength(inputLength: number): [outputLength: number, paddingLength: number] { const remainder = inputLength % 3; const paddingLength = remainder !== 0 ? 3 - remainder : 0; return [(inputLength + paddingLength) / 3 * 4, paddingLength]; } +/** + * Encode the given input buffer into base64. + * + * @param input The input buffer + * @returns The encoded output buffer + */ export function encodeBase64( input: Uint8Array, ): Uint8Array; +/** + * Encode the given input into base64 and write it to the output buffer. + * + * The output buffer must be at least as long as the value returned by `calculateBase64EncodedLength`. + * It can points to the same buffer as the input, as long as `output.offset <= input.offset - input.length / 3`, + * or `output.offset >= input.offset - 1` + * + * @param input The input buffer + * @param output The output buffer + * @returns The number of bytes written to the output buffer + */ export function encodeBase64( input: Uint8Array, output: Uint8Array, diff --git a/libraries/struct/src/basic/stream.ts b/libraries/struct/src/basic/stream.ts index dc66a7b0..4713bd5c 100644 --- a/libraries/struct/src/basic/stream.ts +++ b/libraries/struct/src/basic/stream.ts @@ -1,5 +1,7 @@ import type { ValueOrPromise } from "../utils.js"; +// TODO: allow over reading (returning a `Uint8Array`, an `offset` and a `length`) to avoid copying + export interface StructDeserializeStream { /** * Read data from the underlying data source.