mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 18:29:23 +02:00
refactor(adb): let backends deserialize packets by themselves for better optimization
This commit is contained in:
parent
38a76a2e0c
commit
8a521c8d93
11 changed files with 61 additions and 70 deletions
|
@ -158,25 +158,17 @@ function _Connect(): JSX.Element | null {
|
||||||
try {
|
try {
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
|
|
||||||
const dataStreamPair = await selectedBackend.connect();
|
const streams = await selectedBackend.connect();
|
||||||
|
|
||||||
const packetStreamPair = Adb.createConnection({
|
|
||||||
readable: dataStreamPair.readable
|
|
||||||
.pipeThrough(new InspectStream(chunk => {
|
|
||||||
byteInAcc.current += chunk.byteLength;
|
|
||||||
})),
|
|
||||||
writable: dataStreamPair.writable,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use `TransformStream` to intercept packets and log them
|
// Use `TransformStream` to intercept packets and log them
|
||||||
const readable = packetStreamPair.readable
|
const readable = streams.readable
|
||||||
.pipeThrough(
|
.pipeThrough(
|
||||||
new InspectStream(packet => {
|
new InspectStream(packet => {
|
||||||
globalState.appendLog('Incoming', packet);
|
globalState.appendLog('Incoming', packet);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const writable = pipeFrom(
|
const writable = pipeFrom(
|
||||||
packetStreamPair.writable,
|
streams.writable,
|
||||||
new InspectStream(packet => {
|
new InspectStream(packet => {
|
||||||
globalState.appendLog('Outgoing', packet);
|
globalState.appendLog('Outgoing', packet);
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AdbBackend, ReadableStream, WrapReadableStream, WrapWritableStream, WritableStream } from '@yume-chan/adb';
|
import { AdbBackend, AdbPacket, AdbPacketSerializeStream, pipeFrom, ReadableStream, StructDeserializeStream, WrapReadableStream, WrapWritableStream, WritableStream } from '@yume-chan/adb';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface TCPSocket {
|
interface TCPSocket {
|
||||||
|
@ -55,6 +55,7 @@ export default class AdbDirectSocketsBackend implements AdbBackend {
|
||||||
remotePort: this.port,
|
remotePort: this.port,
|
||||||
noDelay: true,
|
noDelay: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Native streams can't `pipeTo()` or `pipeThrough()` polyfilled streams, so we need to wrap them
|
// Native streams can't `pipeTo()` or `pipeThrough()` polyfilled streams, so we need to wrap them
|
||||||
return {
|
return {
|
||||||
readable: new WrapReadableStream<Uint8Array, ReadableStream<Uint8Array>, void>({
|
readable: new WrapReadableStream<Uint8Array, ReadableStream<Uint8Array>, void>({
|
||||||
|
@ -64,15 +65,15 @@ export default class AdbDirectSocketsBackend implements AdbBackend {
|
||||||
state: undefined,
|
state: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}),
|
}).pipeThrough(new StructDeserializeStream(AdbPacket)),
|
||||||
writable: new WrapWritableStream({
|
writable: pipeFrom(new WrapWritableStream({
|
||||||
async start() {
|
async start() {
|
||||||
return {
|
return {
|
||||||
writable,
|
writable,
|
||||||
state: undefined,
|
state: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}),
|
}), new AdbPacketSerializeStream()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/w3c-web-usb": "^1.0.4",
|
"@types/w3c-web-usb": "^1.0.4",
|
||||||
"@yume-chan/adb": "^0.0.10",
|
"@yume-chan/adb": "^0.0.10",
|
||||||
|
"@yume-chan/struct": "^0.0.10",
|
||||||
"tslib": "^2.3.1"
|
"tslib": "^2.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { DuplexStreamFactory, type AdbBackend, type ReadableStream, type ReadableWritablePair, type WritableStream } from '@yume-chan/adb';
|
import { AdbPacket, AdbPacketSerializeStream, DuplexStreamFactory, pipeFrom, ReadableStream, type AdbBackend, type AdbPacketCore, type AdbPacketInit, type ReadableWritablePair, type WritableStream } from '@yume-chan/adb';
|
||||||
|
import type { StructAsyncDeserializeStream } from "@yume-chan/struct";
|
||||||
|
|
||||||
export const WebUsbDeviceFilter: USBDeviceFilter = {
|
export const WebUsbDeviceFilter: USBDeviceFilter = {
|
||||||
classCode: 0xFF,
|
classCode: 0xFF,
|
||||||
|
@ -6,15 +7,15 @@ export const WebUsbDeviceFilter: USBDeviceFilter = {
|
||||||
protocolCode: 1,
|
protocolCode: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AdbWebUsbBackendStream implements ReadableWritablePair<Uint8Array, Uint8Array>{
|
export class AdbWebUsbBackendStream implements ReadableWritablePair<AdbPacketCore, AdbPacketInit>{
|
||||||
private _readable: ReadableStream<Uint8Array>;
|
private _readable: ReadableStream<AdbPacketCore>;
|
||||||
public get readable() { return this._readable; }
|
public get readable() { return this._readable; }
|
||||||
|
|
||||||
private _writable: WritableStream<Uint8Array>;
|
private _writable: WritableStream<AdbPacketInit>;
|
||||||
public get writable() { return this._writable; }
|
public get writable() { return this._writable; }
|
||||||
|
|
||||||
public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) {
|
public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) {
|
||||||
const factory = new DuplexStreamFactory<Uint8Array, Uint8Array>({
|
const factory = new DuplexStreamFactory<AdbPacketCore, Uint8Array>({
|
||||||
close: async () => {
|
close: async () => {
|
||||||
navigator.usb.removeEventListener('disconnect', handleUsbDisconnect);
|
navigator.usb.removeEventListener('disconnect', handleUsbDisconnect);
|
||||||
try {
|
try {
|
||||||
|
@ -33,9 +34,13 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<Uint8Array,
|
||||||
|
|
||||||
navigator.usb.addEventListener('disconnect', handleUsbDisconnect);
|
navigator.usb.addEventListener('disconnect', handleUsbDisconnect);
|
||||||
|
|
||||||
this._readable = factory.createReadable({
|
const incomingStream: StructAsyncDeserializeStream = {
|
||||||
pull: async (controller) => {
|
async read(length) {
|
||||||
const result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
|
// `ReadableStream<Uin8Array>` don't know how many bytes the consumer need in each `pull`,
|
||||||
|
// But `transferIn(endpointNumber, packetSize)` is much slower than `transferIn(endpointNumber, length)`
|
||||||
|
// So `AdbBackend` is refactored to use `ReadableStream<AdbPacketCore>` directly,
|
||||||
|
// (let each backend deserialize the packets in their own way)
|
||||||
|
const result = await device.transferIn(inEndpoint.endpointNumber, length);
|
||||||
|
|
||||||
// `USBTransferResult` has three states: "ok", "stall" and "babble",
|
// `USBTransferResult` has three states: "ok", "stall" and "babble",
|
||||||
// but ADBd on Android won't enter "stall" (halt) state,
|
// but ADBd on Android won't enter "stall" (halt) state,
|
||||||
|
@ -44,22 +49,25 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<Uint8Array,
|
||||||
// "babble" just means there is more data to be read.
|
// "babble" just means there is more data to be read.
|
||||||
|
|
||||||
// From spec, the `result.data` always covers the whole `buffer`.
|
// From spec, the `result.data` always covers the whole `buffer`.
|
||||||
const chunk = new Uint8Array(result.data!.buffer);
|
return new Uint8Array(result.data!.buffer);
|
||||||
controller.enqueue(chunk);
|
}
|
||||||
},
|
};
|
||||||
}, {
|
|
||||||
highWaterMark: 16 * 1024,
|
|
||||||
size(chunk) { return chunk.byteLength; },
|
|
||||||
});
|
|
||||||
|
|
||||||
this._writable = factory.createWritable({
|
this._readable = factory.createWrapReadable(new ReadableStream<AdbPacketCore>({
|
||||||
|
async pull(controller) {
|
||||||
|
const value = await AdbPacket.deserialize(incomingStream);
|
||||||
|
controller.enqueue(value);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._writable = pipeFrom(factory.createWritable({
|
||||||
write: async (chunk) => {
|
write: async (chunk) => {
|
||||||
await device.transferOut(outEndpoint.endpointNumber, chunk);
|
await device.transferOut(outEndpoint.endpointNumber, chunk);
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
highWaterMark: 16 * 1024,
|
highWaterMark: 16 * 1024,
|
||||||
size(chunk) { return chunk.byteLength; },
|
size(chunk) { return chunk.byteLength; },
|
||||||
});
|
}), new AdbPacketSerializeStream());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "../adb/tsconfig.build.json"
|
"path": "../adb/tsconfig.build.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../struct/tsconfig.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DuplexStreamFactory, type AdbBackend } from '@yume-chan/adb';
|
import { AdbPacket, AdbPacketSerializeStream, DuplexStreamFactory, pipeFrom, StructDeserializeStream, type AdbBackend } from '@yume-chan/adb';
|
||||||
|
|
||||||
export default class AdbWsBackend implements AdbBackend {
|
export default class AdbWsBackend implements AdbBackend {
|
||||||
public readonly serial: string;
|
public readonly serial: string;
|
||||||
|
@ -51,6 +51,9 @@ export default class AdbWsBackend implements AdbBackend {
|
||||||
size(chunk) { return chunk.byteLength; },
|
size(chunk) { return chunk.byteLength; },
|
||||||
});
|
});
|
||||||
|
|
||||||
return { readable, writable };
|
return {
|
||||||
|
readable: readable.pipeThrough(new StructDeserializeStream(AdbPacket)),
|
||||||
|
writable: pipeFrom(writable, new AdbPacketSerializeStream()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { PromiseResolver } from '@yume-chan/async';
|
||||||
import { AdbAuthenticationHandler, AdbDefaultAuthenticators, type AdbCredentialStore } from './auth.js';
|
import { AdbAuthenticationHandler, AdbDefaultAuthenticators, type AdbCredentialStore } from './auth.js';
|
||||||
import { AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install, type AdbFrameBuffer } from './commands/index.js';
|
import { AdbPower, AdbReverseCommand, AdbSubprocess, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install, type AdbFrameBuffer } from './commands/index.js';
|
||||||
import { AdbFeatures } from './features.js';
|
import { AdbFeatures } from './features.js';
|
||||||
import { AdbCommand, AdbPacket, AdbPacketSerializeStream, calculateChecksum, type AdbPacketCore, type AdbPacketInit } from './packet.js';
|
import { AdbCommand, AdbPacket, calculateChecksum, type AdbPacketCore, type AdbPacketInit } from './packet.js';
|
||||||
import { AdbPacketDispatcher, AdbSocket } from './socket/index.js';
|
import { AdbPacketDispatcher, AdbSocket } from './socket/index.js';
|
||||||
import { AbortController, DecodeUtf8Stream, GatherStringStream, pipeFrom, StructDeserializeStream, WritableStream, type ReadableWritablePair } from "./stream/index.js";
|
import { AbortController, DecodeUtf8Stream, GatherStringStream, WritableStream, type ReadableWritablePair } from "./stream/index.js";
|
||||||
import { decodeUtf8, encodeUtf8 } from "./utils/index.js";
|
import { decodeUtf8, encodeUtf8 } from "./utils/index.js";
|
||||||
|
|
||||||
export enum AdbPropKey {
|
export enum AdbPropKey {
|
||||||
|
@ -17,27 +17,13 @@ export enum AdbPropKey {
|
||||||
export const VERSION_OMIT_CHECKSUM = 0x01000001;
|
export const VERSION_OMIT_CHECKSUM = 0x01000001;
|
||||||
|
|
||||||
export class Adb {
|
export class Adb {
|
||||||
public static createConnection(
|
|
||||||
connection: ReadableWritablePair<Uint8Array, Uint8Array>
|
|
||||||
): ReadableWritablePair<AdbPacket, AdbPacketCore> {
|
|
||||||
return {
|
|
||||||
readable: connection.readable.pipeThrough(
|
|
||||||
new StructDeserializeStream(AdbPacket)
|
|
||||||
),
|
|
||||||
writable: pipeFrom(
|
|
||||||
connection.writable,
|
|
||||||
new AdbPacketSerializeStream()
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It's possible to call `authenticate` multiple times on a single connection,
|
* It's possible to call `authenticate` multiple times on a single connection,
|
||||||
* every time the device receives a `CNXN` packet it will reset its internal state,
|
* every time the device receives a `CNXN` packet it will reset its internal state,
|
||||||
* and begin authentication again.
|
* and begin authentication again.
|
||||||
*/
|
*/
|
||||||
public static async authenticate(
|
public static async authenticate(
|
||||||
connection: ReadableWritablePair<AdbPacket, AdbPacketCore>,
|
connection: ReadableWritablePair<AdbPacketCore, AdbPacketCore>,
|
||||||
credentialStore: AdbCredentialStore,
|
credentialStore: AdbCredentialStore,
|
||||||
authenticators = AdbDefaultAuthenticators,
|
authenticators = AdbDefaultAuthenticators,
|
||||||
) {
|
) {
|
||||||
|
@ -157,7 +143,7 @@ export class Adb {
|
||||||
public readonly tcpip: AdbTcpIpCommand;
|
public readonly tcpip: AdbTcpIpCommand;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
connection: ReadableWritablePair<AdbPacket, AdbPacketInit>,
|
connection: ReadableWritablePair<AdbPacketCore, AdbPacketInit>,
|
||||||
version: number,
|
version: number,
|
||||||
maxPayloadSize: number,
|
maxPayloadSize: number,
|
||||||
banner: string,
|
banner: string,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { ValueOrPromise } from '@yume-chan/struct';
|
import type { ValueOrPromise } from '@yume-chan/struct';
|
||||||
|
import type { AdbPacketCore, AdbPacketInit } from "./packet.js";
|
||||||
import type { ReadableWritablePair } from "./stream/index.js";
|
import type { ReadableWritablePair } from "./stream/index.js";
|
||||||
|
|
||||||
export interface AdbBackend {
|
export interface AdbBackend {
|
||||||
|
@ -6,5 +7,5 @@ export interface AdbBackend {
|
||||||
|
|
||||||
readonly name: string | undefined;
|
readonly name: string | undefined;
|
||||||
|
|
||||||
connect(): ValueOrPromise<ReadableWritablePair<Uint8Array, Uint8Array>>;
|
connect(): ValueOrPromise<ReadableWritablePair<AdbPacketCore, AdbPacketInit>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import { AutoDisposable } from '@yume-chan/event';
|
import { AutoDisposable } from '@yume-chan/event';
|
||||||
import Struct from '@yume-chan/struct';
|
import Struct from '@yume-chan/struct';
|
||||||
import type { AdbPacket } from '../packet.js';
|
import type { AdbPacketCore } from '../packet.js';
|
||||||
import type { AdbIncomingSocketEventArgs, AdbPacketDispatcher, AdbSocket } from '../socket/index.js';
|
import type { AdbIncomingSocketEventArgs, AdbPacketDispatcher, AdbSocket } from '../socket/index.js';
|
||||||
import { AdbBufferedStream } from '../stream/index.js';
|
import { AdbBufferedStream } from '../stream/index.js';
|
||||||
import { decodeUtf8 } from "../utils/index.js";
|
import { decodeUtf8 } from "../utils/index.js";
|
||||||
|
|
||||||
export interface AdbReverseHandler {
|
export interface AdbReverseHandler {
|
||||||
onSocket(packet: AdbPacket, socket: AdbSocket): void;
|
onSocket(packet: AdbPacketCore, socket: AdbSocket): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdbForwardListener {
|
export interface AdbForwardListener {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AsyncOperationManager, PromiseResolver } from '@yume-chan/async';
|
import { AsyncOperationManager, PromiseResolver } from '@yume-chan/async';
|
||||||
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
||||||
import { AdbCommand, AdbPacket, calculateChecksum, type AdbPacketCore, type AdbPacketInit } from '../packet.js';
|
import { AdbCommand, calculateChecksum, type AdbPacketCore, type AdbPacketInit } from '../packet.js';
|
||||||
import { AbortController, WritableStream, WritableStreamDefaultWriter, type ReadableWritablePair } from '../stream/index.js';
|
import { AbortController, WritableStream, WritableStreamDefaultWriter, type ReadableWritablePair } from '../stream/index.js';
|
||||||
import { decodeUtf8, encodeUtf8 } from '../utils/index.js';
|
import { decodeUtf8, encodeUtf8 } from '../utils/index.js';
|
||||||
import { AdbSocket } from './socket.js';
|
import { AdbSocket } from './socket.js';
|
||||||
|
@ -8,7 +8,7 @@ import { AdbSocket } from './socket.js';
|
||||||
export interface AdbIncomingSocketEventArgs {
|
export interface AdbIncomingSocketEventArgs {
|
||||||
handled: boolean;
|
handled: boolean;
|
||||||
|
|
||||||
packet: AdbPacket;
|
packet: AdbPacketCore;
|
||||||
|
|
||||||
serviceString: string;
|
serviceString: string;
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
private _abortController = new AbortController();
|
private _abortController = new AbortController();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
connection: ReadableWritablePair<AdbPacket, AdbPacketInit>,
|
connection: ReadableWritablePair<AdbPacketCore, AdbPacketInit>,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
this._writer = connection.writable.getWriter();
|
this._writer = connection.writable.getWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleOk(packet: AdbPacket) {
|
private handleOk(packet: AdbPacketCore) {
|
||||||
if (this.initializers.resolve(packet.arg1, packet.arg0)) {
|
if (this.initializers.resolve(packet.arg1, packet.arg0)) {
|
||||||
// Device successfully created the socket
|
// Device successfully created the socket
|
||||||
return;
|
return;
|
||||||
|
@ -109,7 +109,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleClose(packet: AdbPacket) {
|
private async handleClose(packet: AdbPacketCore) {
|
||||||
// From https://android.googlesource.com/platform/packages/modules/adb/+/65d18e2c1cc48b585811954892311b28a4c3d188/adb.cpp#459
|
// From https://android.googlesource.com/platform/packages/modules/adb/+/65d18e2c1cc48b585811954892311b28a4c3d188/adb.cpp#459
|
||||||
/* According to protocol.txt, p->msg.arg0 might be 0 to indicate
|
/* According to protocol.txt, p->msg.arg0 might be 0 to indicate
|
||||||
* a failed OPEN only. However, due to a bug in previous ADB
|
* a failed OPEN only. However, due to a bug in previous ADB
|
||||||
|
@ -139,7 +139,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
||||||
// Just ignore it
|
// Just ignore it
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleOpen(packet: AdbPacket) {
|
private async handleOpen(packet: AdbPacketCore) {
|
||||||
// AsyncOperationManager doesn't support get and skip an ID
|
// AsyncOperationManager doesn't support get and skip an ID
|
||||||
// Use `add` + `resolve` to simulate this behavior
|
// Use `add` + `resolve` to simulate this behavior
|
||||||
const [localId] = this.initializers.add<number>();
|
const [localId] = this.initializers.add<number>();
|
||||||
|
|
|
@ -179,15 +179,11 @@ export class StructDeserializeStream<T extends Struct<any, any, any, any>>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
this._readable = new PushReadableStream<StructValueType<T>>(
|
this._readable = new ReadableStream<StructValueType<T>>({
|
||||||
async controller => {
|
async pull(controller) {
|
||||||
try {
|
try {
|
||||||
// Unless we make `deserialize` be capable of pausing/resuming,
|
const value = await struct.deserialize(incomingStream);
|
||||||
// We always need at least one pull loop
|
controller.enqueue(value);
|
||||||
while (true) {
|
|
||||||
const value = await struct.deserialize(incomingStream);
|
|
||||||
await controller.enqueue(value);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof BufferedStreamEndedError) {
|
if (e instanceof BufferedStreamEndedError) {
|
||||||
controller.close();
|
controller.close();
|
||||||
|
@ -196,7 +192,7 @@ export class StructDeserializeStream<T extends Struct<any, any, any, any>>
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
this._writable = new WritableStream({
|
this._writable = new WritableStream({
|
||||||
async write(chunk) {
|
async write(chunk) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue