mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
refactor(adb): simplify sync request/response ID handling
This commit is contained in:
parent
c607d911e1
commit
ba956da6bd
12 changed files with 90 additions and 74 deletions
33
libraries/adb/src/commands/sync/id-common.ts
Normal file
33
libraries/adb/src/commands/sync/id-common.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
||||||
|
|
||||||
|
function encodeAsciiUnchecked(value: string): Uint8Array<ArrayBuffer> {
|
||||||
|
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");
|
12
libraries/adb/src/commands/sync/id-request.ts
Normal file
12
libraries/adb/src/commands/sync/id-request.ts
Normal file
|
@ -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");
|
11
libraries/adb/src/commands/sync/id-response.ts
Normal file
11
libraries/adb/src/commands/sync/id-response.ts
Normal file
|
@ -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");
|
12
libraries/adb/src/commands/sync/id.ts
Normal file
12
libraries/adb/src/commands/sync/id.ts
Normal file
|
@ -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 };
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from "./id.js";
|
||||||
export * from "./list.js";
|
export * from "./list.js";
|
||||||
export * from "./pull.js";
|
export * from "./pull.js";
|
||||||
export * from "./push.js";
|
export * from "./push.js";
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type { StructValue } from "@yume-chan/struct";
|
import type { StructValue } from "@yume-chan/struct";
|
||||||
import { extend, string, u32 } from "@yume-chan/struct";
|
import { extend, string, u32 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||||
import { AdbSyncResponseId, adbSyncReadResponses } from "./response.js";
|
import { adbSyncWriteRequest } from "./request.js";
|
||||||
|
import { adbSyncReadResponses } from "./response.js";
|
||||||
import type { AdbSyncSocket } from "./socket.js";
|
import type { AdbSyncSocket } from "./socket.js";
|
||||||
import type { AdbSyncStat } from "./stat.js";
|
import type { AdbSyncStat } from "./stat.js";
|
||||||
import {
|
import {
|
||||||
|
@ -36,7 +37,7 @@ export async function* adbSyncOpenDirV2(
|
||||||
await adbSyncWriteRequest(locked, AdbSyncRequestId.ListV2, path);
|
await adbSyncWriteRequest(locked, AdbSyncRequestId.ListV2, path);
|
||||||
for await (const item of adbSyncReadResponses(
|
for await (const item of adbSyncReadResponses(
|
||||||
locked,
|
locked,
|
||||||
AdbSyncResponseId.Entry2,
|
AdbSyncResponseId.EntryV2,
|
||||||
AdbSyncEntry2Response,
|
AdbSyncEntry2Response,
|
||||||
)) {
|
)) {
|
||||||
// `LST2` can return error codes for failed `lstat` calls.
|
// `LST2` can return error codes for failed `lstat` calls.
|
||||||
|
|
|
@ -2,8 +2,9 @@ import { ReadableStream } from "@yume-chan/stream-extra";
|
||||||
import type { StructValue } from "@yume-chan/struct";
|
import type { StructValue } from "@yume-chan/struct";
|
||||||
import { buffer, struct, u32 } from "@yume-chan/struct";
|
import { buffer, struct, u32 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||||
import { adbSyncReadResponses, AdbSyncResponseId } from "./response.js";
|
import { adbSyncWriteRequest } from "./request.js";
|
||||||
|
import { adbSyncReadResponses } from "./response.js";
|
||||||
import type { AdbSyncSocket } from "./socket.js";
|
import type { AdbSyncSocket } from "./socket.js";
|
||||||
|
|
||||||
export const AdbSyncDataResponse = struct(
|
export const AdbSyncDataResponse = struct(
|
||||||
|
|
|
@ -8,8 +8,9 @@ import { struct, u32 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import { NOOP } from "../../utils/index.js";
|
import { NOOP } from "../../utils/index.js";
|
||||||
|
|
||||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||||
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
|
import { adbSyncWriteRequest } from "./request.js";
|
||||||
|
import { adbSyncReadResponse } from "./response.js";
|
||||||
import type { AdbSyncSocket, AdbSyncSocketLocked } from "./socket.js";
|
import type { AdbSyncSocket, AdbSyncSocketLocked } from "./socket.js";
|
||||||
import { LinuxFileType } from "./stat.js";
|
import { LinuxFileType } from "./stat.js";
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,5 @@
|
||||||
import { encodeUtf8, struct, u32 } from "@yume-chan/struct";
|
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(
|
export const AdbSyncNumberRequest = struct(
|
||||||
{ id: u32, arg: u32 },
|
{ id: u32, arg: u32 },
|
||||||
{ littleEndian: true },
|
{ littleEndian: true },
|
||||||
|
@ -26,13 +11,9 @@ export interface AdbSyncWritable {
|
||||||
|
|
||||||
export async function adbSyncWriteRequest(
|
export async function adbSyncWriteRequest(
|
||||||
writable: AdbSyncWritable,
|
writable: AdbSyncWritable,
|
||||||
id: number | string,
|
id: number,
|
||||||
value: number | string | Uint8Array,
|
value: number | string | Uint8Array,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (typeof id === "string") {
|
|
||||||
id = adbSyncEncodeId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
await writable.write(
|
await writable.write(
|
||||||
AdbSyncNumberRequest.serialize({ id, arg: value }),
|
AdbSyncNumberRequest.serialize({ id, arg: value }),
|
||||||
|
|
|
@ -2,39 +2,9 @@ import { getUint32LittleEndian } from "@yume-chan/no-data-view";
|
||||||
import type { AsyncExactReadable, StructDeserializer } from "@yume-chan/struct";
|
import type { AsyncExactReadable, StructDeserializer } from "@yume-chan/struct";
|
||||||
import { decodeUtf8, string, struct, u32 } 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<ArrayBuffer> {
|
import { AdbSyncResponseId } from "./id.js";
|
||||||
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"),
|
|
||||||
};
|
|
||||||
|
|
||||||
export class AdbSyncError extends Error {}
|
export class AdbSyncError extends Error {}
|
||||||
|
|
||||||
|
@ -50,18 +20,14 @@ export const AdbSyncFailResponse = struct(
|
||||||
|
|
||||||
export async function adbSyncReadResponse<T>(
|
export async function adbSyncReadResponse<T>(
|
||||||
stream: AsyncExactReadable,
|
stream: AsyncExactReadable,
|
||||||
id: number | string,
|
id: number,
|
||||||
type: StructDeserializer<T>,
|
type: StructDeserializer<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
if (typeof id === "string") {
|
|
||||||
id = adbSyncEncodeId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = await stream.readExactly(4);
|
const buffer = await stream.readExactly(4);
|
||||||
switch (getUint32LittleEndian(buffer, 0)) {
|
switch (getUint32LittleEndian(buffer, 0)) {
|
||||||
case AdbSyncResponseId.Fail:
|
case AdbSyncResponseId.Fail:
|
||||||
await AdbSyncFailResponse.deserialize(stream);
|
await AdbSyncFailResponse.deserialize(stream);
|
||||||
throw new Error("Unreachable");
|
unreachable();
|
||||||
case id:
|
case id:
|
||||||
return await type.deserialize(stream);
|
return await type.deserialize(stream);
|
||||||
default:
|
default:
|
||||||
|
@ -73,13 +39,9 @@ export async function adbSyncReadResponse<T>(
|
||||||
|
|
||||||
export async function* adbSyncReadResponses<T>(
|
export async function* adbSyncReadResponses<T>(
|
||||||
stream: AsyncExactReadable,
|
stream: AsyncExactReadable,
|
||||||
id: number | string,
|
id: number,
|
||||||
type: StructDeserializer<T>,
|
type: StructDeserializer<T>,
|
||||||
): AsyncGenerator<T, void, void> {
|
): AsyncGenerator<T, void, void> {
|
||||||
if (typeof id === "string") {
|
|
||||||
id = adbSyncEncodeId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const buffer = await stream.readExactly(4);
|
const buffer = await stream.readExactly(4);
|
||||||
switch (getUint32LittleEndian(buffer, 0)) {
|
switch (getUint32LittleEndian(buffer, 0)) {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type { StructValue } from "@yume-chan/struct";
|
import type { StructValue } from "@yume-chan/struct";
|
||||||
import { struct, u32, u64 } from "@yume-chan/struct";
|
import { struct, u32, u64 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import { AdbSyncRequestId, adbSyncWriteRequest } from "./request.js";
|
import { AdbSyncRequestId, AdbSyncResponseId } from "./id.js";
|
||||||
import { AdbSyncResponseId, adbSyncReadResponse } from "./response.js";
|
import { adbSyncWriteRequest } from "./request.js";
|
||||||
|
import { adbSyncReadResponse } from "./response.js";
|
||||||
import type { AdbSyncSocket } from "./socket.js";
|
import type { AdbSyncSocket } from "./socket.js";
|
||||||
|
|
||||||
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
|
// 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);
|
await adbSyncWriteRequest(locked, AdbSyncRequestId.LstatV2, path);
|
||||||
return await adbSyncReadResponse(
|
return await adbSyncReadResponse(
|
||||||
locked,
|
locked,
|
||||||
AdbSyncResponseId.Lstat2,
|
AdbSyncResponseId.LstatV2,
|
||||||
AdbSyncStatResponse,
|
AdbSyncStatResponse,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as AdbFeature from "./features-value.js";
|
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];
|
type AdbFeature = (typeof AdbFeature)[keyof typeof AdbFeature];
|
||||||
|
|
||||||
export { AdbFeature };
|
export { AdbFeature };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue