diff --git a/libraries/adb/src/commands/sync/id-common.ts b/libraries/adb/src/commands/sync/id-common.ts new file mode 100644 index 00000000..ba4769ca --- /dev/null +++ b/libraries/adb/src/commands/sync/id-common.ts @@ -0,0 +1,33 @@ +import { getUint32LittleEndian } from "@yume-chan/no-data-view"; + +function encodeAsciiUnchecked(value: string): Uint8Array { + const result = new Uint8Array(value.length); + for (let i = 0; i < value.length; i += 1) { + result[i] = value.charCodeAt(i); + } + return result; +} + +/** + * Encode ID to numbers for faster comparison. + * + * This function skips all checks. The caller must ensure the input is valid. + * + * @param value A 4 ASCII character string. + * @returns A 32-bit integer by encoding the string as little-endian + * + * #__NO_SIDE_EFFECTS__ + */ +export function adbSyncEncodeId(value: string): number { + const buffer = encodeAsciiUnchecked(value); + return getUint32LittleEndian(buffer, 0); +} + +// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/file_sync_protocol.h;l=23;drc=888a54dcbf954fdffacc8283a793290abcc589cd + +export const Lstat = adbSyncEncodeId("STAT"); +export const Stat = adbSyncEncodeId("STA2"); +export const LstatV2 = adbSyncEncodeId("LST2"); + +export const Done = adbSyncEncodeId("DONE"); +export const Data = adbSyncEncodeId("DATA"); diff --git a/libraries/adb/src/commands/sync/id-request.ts b/libraries/adb/src/commands/sync/id-request.ts new file mode 100644 index 00000000..76d31c5b --- /dev/null +++ b/libraries/adb/src/commands/sync/id-request.ts @@ -0,0 +1,12 @@ +import { adbSyncEncodeId } from "./id-common.js"; + +export { Data, Done, Lstat, LstatV2, Stat } from "./id-common.js"; + +// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/file_sync_protocol.h;l=23;drc=888a54dcbf954fdffacc8283a793290abcc589cd + +export const List = adbSyncEncodeId("LIST"); +export const ListV2 = adbSyncEncodeId("LIS2"); + +export const Send = adbSyncEncodeId("SEND"); +export const SendV2 = adbSyncEncodeId("SND2"); +export const Receive = adbSyncEncodeId("RECV"); diff --git a/libraries/adb/src/commands/sync/id-response.ts b/libraries/adb/src/commands/sync/id-response.ts new file mode 100644 index 00000000..03f85d91 --- /dev/null +++ b/libraries/adb/src/commands/sync/id-response.ts @@ -0,0 +1,11 @@ +import { adbSyncEncodeId } from "./id-common.js"; + +export { Data, Done, Lstat, LstatV2, Stat } from "./id-common.js"; + +// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/file_sync_protocol.h;l=23;drc=888a54dcbf954fdffacc8283a793290abcc589cd + +export const Entry = adbSyncEncodeId("DENT"); +export const EntryV2 = adbSyncEncodeId("DNT2"); + +export const Ok = adbSyncEncodeId("OKAY"); +export const Fail = adbSyncEncodeId("FAIL"); diff --git a/libraries/adb/src/commands/sync/id.ts b/libraries/adb/src/commands/sync/id.ts new file mode 100644 index 00000000..4778ec40 --- /dev/null +++ b/libraries/adb/src/commands/sync/id.ts @@ -0,0 +1,12 @@ +import * as AdbSyncRequestId from "./id-request.js"; +import * as AdbSyncResponseId from "./id-response.js"; + +// biome-ignore lint/suspicious/noRedeclare: TypeScript declaration merging for enum-like object +type AdbSyncRequestId = + (typeof AdbSyncRequestId)[keyof typeof AdbSyncRequestId]; + +// biome-ignore lint/suspicious/noRedeclare: TypeScript declaration merging for enum-like object +type AdbSyncResponseId = + (typeof AdbSyncResponseId)[keyof typeof AdbSyncResponseId]; + +export { AdbSyncRequestId, AdbSyncResponseId }; diff --git a/libraries/adb/src/commands/sync/index.ts b/libraries/adb/src/commands/sync/index.ts index 2ce53228..565440ec 100644 --- a/libraries/adb/src/commands/sync/index.ts +++ b/libraries/adb/src/commands/sync/index.ts @@ -1,3 +1,4 @@ +export * from "./id.js"; export * from "./list.js"; export * from "./pull.js"; export * from "./push.js"; diff --git a/libraries/adb/src/commands/sync/list.ts b/libraries/adb/src/commands/sync/list.ts index 531cdcb6..e769f4c8 100644 --- a/libraries/adb/src/commands/sync/list.ts +++ b/libraries/adb/src/commands/sync/list.ts @@ -1,8 +1,9 @@ import type { StructValue } from "@yume-chan/struct"; import { extend, string, u32 } from "@yume-chan/struct"; -import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js"; -import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js"; +import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js"; +import { adbSyncWriteRequest } from "./request.js"; +import { adbSyncReadResponses } from "./response.js"; import type { AdbSyncSocket } from "./socket.js"; import type { AdbSyncStat } from "./stat.js"; import { @@ -36,7 +37,7 @@ export async function* adbSyncOpenDirV2( await adbSyncWriteRequest(locked, AdbSyncRequestId.ListV2, path); for await (const item of adbSyncReadResponses( locked, - AdbSyncResponseId.Entry2, + AdbSyncResponseId.EntryV2, AdbSyncEntry2Response, )) { // `LST2` can return error codes for failed `lstat` calls. diff --git a/libraries/adb/src/commands/sync/pull.ts b/libraries/adb/src/commands/sync/pull.ts index df937288..7dcb0e1e 100644 --- a/libraries/adb/src/commands/sync/pull.ts +++ b/libraries/adb/src/commands/sync/pull.ts @@ -2,8 +2,9 @@ import { ReadableStream } from "@yume-chan/stream-extra"; import type { StructValue } from "@yume-chan/struct"; import { buffer, struct, u32 } from "@yume-chan/struct"; -import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js"; -import { adbSyncReadResponses, AdbSyncResponseId } from "./response.js"; +import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js"; +import { adbSyncWriteRequest } from "./request.js"; +import { adbSyncReadResponses } from "./response.js"; import type { AdbSyncSocket } from "./socket.js"; export const AdbSyncDataResponse = struct( diff --git a/libraries/adb/src/commands/sync/push.ts b/libraries/adb/src/commands/sync/push.ts index 1b93f5e6..7c53ed0c 100644 --- a/libraries/adb/src/commands/sync/push.ts +++ b/libraries/adb/src/commands/sync/push.ts @@ -8,8 +8,9 @@ import { struct, u32 } from "@yume-chan/struct"; import { NOOP } from "../../utils/index.js"; -import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js"; -import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js"; +import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js"; +import { adbSyncWriteRequest } from "./request.js"; +import { adbSyncReadResponse } from "./response.js"; import type { AdbSyncSocket, AdbSyncSocketLocked } from "./socket.js"; import { LinuxFileType } from "./stat.js"; diff --git a/libraries/adb/src/commands/sync/request.ts b/libraries/adb/src/commands/sync/request.ts index 0e39029c..09b714df 100644 --- a/libraries/adb/src/commands/sync/request.ts +++ b/libraries/adb/src/commands/sync/request.ts @@ -1,20 +1,5 @@ import { encodeUtf8, struct, u32 } from "@yume-chan/struct"; -import { adbSyncEncodeId } from "./response.js"; - -export const AdbSyncRequestId = { - List: adbSyncEncodeId("LIST"), - ListV2: adbSyncEncodeId("LIS2"), - Send: adbSyncEncodeId("SEND"), - SendV2: adbSyncEncodeId("SND2"), - Lstat: adbSyncEncodeId("STAT"), - Stat: adbSyncEncodeId("STA2"), - LstatV2: adbSyncEncodeId("LST2"), - Data: adbSyncEncodeId("DATA"), - Done: adbSyncEncodeId("DONE"), - Receive: adbSyncEncodeId("RECV"), -} as const; - export const AdbSyncNumberRequest = struct( { id: u32, arg: u32 }, { littleEndian: true }, @@ -26,13 +11,9 @@ export interface AdbSyncWritable { export async function adbSyncWriteRequest( writable: AdbSyncWritable, - id: number | string, + id: number, value: number | string | Uint8Array, ): Promise { - if (typeof id === "string") { - id = adbSyncEncodeId(id); - } - if (typeof value === "number") { await writable.write( AdbSyncNumberRequest.serialize({ id, arg: value }), diff --git a/libraries/adb/src/commands/sync/response.ts b/libraries/adb/src/commands/sync/response.ts index eba2ccf1..663b8c52 100644 --- a/libraries/adb/src/commands/sync/response.ts +++ b/libraries/adb/src/commands/sync/response.ts @@ -2,39 +2,9 @@ import { getUint32LittleEndian } from "@yume-chan/no-data-view"; import type { AsyncExactReadable, StructDeserializer } from "@yume-chan/struct"; import { decodeUtf8, string, struct, u32 } from "@yume-chan/struct"; -import { unreachable } from "../../utils/no-op.js"; +import { unreachable } from "../../utils/index.js"; -function encodeAsciiUnchecked(value: string): Uint8Array { - const result = new Uint8Array(value.length); - for (let i = 0; i < value.length; i += 1) { - result[i] = value.charCodeAt(i); - } - return result; -} - -/** - * Encode ID to numbers for faster comparison - * @param value A 4-character string - * @returns A 32-bit integer by encoding the string as little-endian - * - * #__NO_SIDE_EFFECTS__ - */ -export function adbSyncEncodeId(value: string): number { - const buffer = encodeAsciiUnchecked(value); - return getUint32LittleEndian(buffer, 0); -} - -export const AdbSyncResponseId = { - Entry: adbSyncEncodeId("DENT"), - Entry2: adbSyncEncodeId("DNT2"), - Lstat: adbSyncEncodeId("STAT"), - Stat: adbSyncEncodeId("STA2"), - Lstat2: adbSyncEncodeId("LST2"), - Done: adbSyncEncodeId("DONE"), - Data: adbSyncEncodeId("DATA"), - Ok: adbSyncEncodeId("OKAY"), - Fail: adbSyncEncodeId("FAIL"), -}; +import { AdbSyncResponseId } from "./id.js"; export class AdbSyncError extends Error {} @@ -50,18 +20,14 @@ export const AdbSyncFailResponse = struct( export async function adbSyncReadResponse( stream: AsyncExactReadable, - id: number | string, + id: number, type: StructDeserializer, ): Promise { - if (typeof id === "string") { - id = adbSyncEncodeId(id); - } - const buffer = await stream.readExactly(4); switch (getUint32LittleEndian(buffer, 0)) { case AdbSyncResponseId.Fail: await AdbSyncFailResponse.deserialize(stream); - throw new Error("Unreachable"); + unreachable(); case id: return await type.deserialize(stream); default: @@ -73,13 +39,9 @@ export async function adbSyncReadResponse( export async function* adbSyncReadResponses( stream: AsyncExactReadable, - id: number | string, + id: number, type: StructDeserializer, ): AsyncGenerator { - if (typeof id === "string") { - id = adbSyncEncodeId(id); - } - while (true) { const buffer = await stream.readExactly(4); switch (getUint32LittleEndian(buffer, 0)) { diff --git a/libraries/adb/src/commands/sync/stat.ts b/libraries/adb/src/commands/sync/stat.ts index 152a7e48..6aa1623b 100644 --- a/libraries/adb/src/commands/sync/stat.ts +++ b/libraries/adb/src/commands/sync/stat.ts @@ -1,8 +1,9 @@ import type { StructValue } from "@yume-chan/struct"; import { struct, u32, u64 } from "@yume-chan/struct"; -import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js"; -import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js"; +import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js"; +import { adbSyncWriteRequest } from "./request.js"; +import { adbSyncReadResponse } from "./response.js"; import type { AdbSyncSocket } from "./socket.js"; // https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36 @@ -131,7 +132,7 @@ export async function adbSyncLstat( await adbSyncWriteRequest(locked, AdbSyncRequestId.LstatV2, path); return await adbSyncReadResponse( locked, - AdbSyncResponseId.Lstat2, + AdbSyncResponseId.LstatV2, AdbSyncStatResponse, ); } else { diff --git a/libraries/adb/src/features.ts b/libraries/adb/src/features.ts index d2c9b8d5..d3591fec 100644 --- a/libraries/adb/src/features.ts +++ b/libraries/adb/src/features.ts @@ -1,6 +1,6 @@ import * as AdbFeature from "./features-value.js"; -// enum +// biome-ignore lint/suspicious/noRedeclare: TypeScript declaration merging for enum-like object type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature]; export { AdbFeature };