mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 10:49:24 +02:00
refactor(adb): remove text encoding from backend
This commit is contained in:
parent
08767c7b71
commit
2b63aa630a
12 changed files with 50 additions and 66 deletions
|
@ -2,6 +2,8 @@
|
|||
|
||||
TypeScript implementation of Android Debug Bridge (ADB) protocol.
|
||||
|
||||
**WARNING:** The public API is UNSTABLE. If you have any questions, please open an issue.
|
||||
|
||||
- [Compatibility](#compatibility)
|
||||
- [Connection](#connection)
|
||||
- [Backend](#backend)
|
||||
|
@ -16,8 +18,6 @@ TypeScript implementation of Android Debug Bridge (ADB) protocol.
|
|||
- [AdbAuthenticator](#adbauthenticator)
|
||||
- [Stream multiplex](#stream-multiplex)
|
||||
- [Backend](#backend-1)
|
||||
- [`encodeUtf8`](#encodeutf8)
|
||||
- [`decodeUtf8`](#decodeutf8)
|
||||
- [Commands](#commands)
|
||||
- [childProcess](#childprocess)
|
||||
- [usb](#usb)
|
||||
|
@ -133,26 +133,10 @@ ADB commands are all based on streams. Multiple streams can send and receive at
|
|||
3. Client and server read/write on the stream.
|
||||
4. Client/server sends a `CLSE` to close the stream.
|
||||
|
||||
The `Backend` is responsible for encoding and decoding UTF-8 strings.
|
||||
The `Backend` is responsible for reading and writing data from underlying source.
|
||||
|
||||
### Backend
|
||||
|
||||
#### `encodeUtf8`
|
||||
|
||||
```ts
|
||||
encodeUtf8(input: string): ArrayBuffer
|
||||
```
|
||||
|
||||
Encode `input` into an `ArrayBuffer` with UTF-8 encoding.
|
||||
|
||||
#### `decodeUtf8`
|
||||
|
||||
```ts
|
||||
decodeUtf8(buffer: ArrayBuffer): string
|
||||
```
|
||||
|
||||
Decode `buffer` into a string with UTF-8 encoding.
|
||||
|
||||
## Commands
|
||||
|
||||
### childProcess
|
||||
|
|
|
@ -6,6 +6,7 @@ import { AdbChildProcess, AdbDemoMode, AdbFrameBuffer, AdbReverseCommand, AdbSyn
|
|||
import { AdbFeatures } from './features';
|
||||
import { AdbCommand } from './packet';
|
||||
import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket';
|
||||
import { decodeUtf8 } from "./utils";
|
||||
|
||||
export enum AdbPropKey {
|
||||
Product = 'ro.product.name',
|
||||
|
@ -118,7 +119,7 @@ export class Adb {
|
|||
this.packetDispatcher.appendNullToServiceString = false;
|
||||
}
|
||||
|
||||
this.parseBanner(this.backend.decodeUtf8(packet.payload!));
|
||||
this.parseBanner(decodeUtf8(packet.payload!));
|
||||
resolver.resolve();
|
||||
break;
|
||||
case AdbCommand.Auth:
|
||||
|
@ -229,7 +230,7 @@ export class Adb {
|
|||
const resolver = new PromiseResolver<string>();
|
||||
let result = '';
|
||||
socket.onData(buffer => {
|
||||
result += this.backend.decodeUtf8(buffer);
|
||||
result += decodeUtf8(buffer);
|
||||
});
|
||||
socket.onClose(() => resolver.resolve(result));
|
||||
return resolver.promise;
|
||||
|
|
|
@ -12,10 +12,6 @@ export interface AdbBackend {
|
|||
|
||||
connect?(): ValueOrPromise<void>;
|
||||
|
||||
encodeUtf8(input: string): ArrayBuffer;
|
||||
|
||||
decodeUtf8(buffer: ArrayBuffer): string;
|
||||
|
||||
read(length: number): ValueOrPromise<ArrayBuffer>;
|
||||
|
||||
write(buffer: ArrayBuffer): ValueOrPromise<void>;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { AutoDisposable } from '@yume-chan/event';
|
||||
import Struct from '@yume-chan/struct';
|
||||
import { AdbPacket } from '../packet';
|
||||
import { AdbIncomingSocketEventArgs, AdbPacketDispatcher, AdbSocket } from '../socket';
|
||||
import type { AdbPacket } from '../packet';
|
||||
import type { AdbIncomingSocketEventArgs, AdbPacketDispatcher, AdbSocket } from '../socket';
|
||||
import { AdbBufferedStream } from '../stream';
|
||||
import { decodeUtf8 } from "../utils";
|
||||
|
||||
export interface AdbReverseHandler {
|
||||
onSocket(packet: AdbPacket, socket: AdbSocket): void;
|
||||
|
@ -49,7 +50,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const address = this.dispatcher.backend.decodeUtf8(e.packet.payload!);
|
||||
const address = decodeUtf8(e.packet.payload!);
|
||||
// tcp:1234\0
|
||||
const port = Number.parseInt(address.substring(4));
|
||||
if (this.localPortToHandler.has(port)) {
|
||||
|
@ -65,7 +66,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
|
||||
private async sendRequest(service: string) {
|
||||
const stream = await this.createBufferedStream(service);
|
||||
const success = this.dispatcher.backend.decodeUtf8(await stream.read(4)) === 'OKAY';
|
||||
const success = decodeUtf8(await stream.read(4)) === 'OKAY';
|
||||
if (!success) {
|
||||
await AdbReverseErrorResponse.deserialize(stream);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { Adb } from "../../adb";
|
|||
import { AdbFeatures } from "../../features";
|
||||
import type { AdbSocket } from "../../socket";
|
||||
import { AdbBufferedStream } from "../../stream";
|
||||
import { encodeUtf8 } from "../../utils";
|
||||
import type { AdbShell } from "./types";
|
||||
|
||||
export enum AdbShellProtocolId {
|
||||
|
@ -93,8 +94,7 @@ export class AdbShellProtocol implements AdbShell {
|
|||
{
|
||||
id: AdbShellProtocolId.Stdin,
|
||||
data,
|
||||
},
|
||||
this.stream
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -104,14 +104,13 @@ export class AdbShellProtocol implements AdbShell {
|
|||
AdbShellProtocolPacket.serialize(
|
||||
{
|
||||
id: AdbShellProtocolId.WindowSizeChange,
|
||||
data: this.stream.encodeUtf8(
|
||||
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 pass `0` is fine.
|
||||
`${rows}x${cols},0x0\0`
|
||||
),
|
||||
},
|
||||
this.stream
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Struct from '@yume-chan/struct';
|
||||
import { AdbBufferedStream } from '../../stream';
|
||||
import type { AdbBufferedStream } from '../../stream';
|
||||
import { encodeUtf8 } from "../../utils";
|
||||
|
||||
export enum AdbSyncRequestId {
|
||||
List = 'LIST',
|
||||
|
@ -32,17 +33,17 @@ export async function adbSyncWriteRequest(
|
|||
buffer = AdbSyncNumberRequest.serialize({
|
||||
id,
|
||||
arg: value,
|
||||
}, stream);
|
||||
});
|
||||
} else if (typeof value === 'string') {
|
||||
buffer = AdbSyncDataRequest.serialize({
|
||||
id,
|
||||
data: stream.encodeUtf8(value),
|
||||
}, stream);
|
||||
data: encodeUtf8(value),
|
||||
});
|
||||
} else {
|
||||
buffer = AdbSyncDataRequest.serialize({
|
||||
id,
|
||||
data: value,
|
||||
}, stream);
|
||||
});
|
||||
}
|
||||
await stream.write(buffer);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Struct, { StructDeserializationContext, StructLike, StructValueType } from '@yume-chan/struct';
|
||||
import Struct, { StructAsyncDeserializeStream, StructLike, StructValueType } from '@yume-chan/struct';
|
||||
import { AdbBufferedStream } from '../../stream';
|
||||
import { decodeUtf8 } from "../../utils";
|
||||
|
||||
export enum AdbSyncResponseId {
|
||||
Entry = 'DENT',
|
||||
|
@ -25,8 +26,8 @@ export class AdbSyncDoneResponse implements StructLike<AdbSyncDoneResponse> {
|
|||
this.length = length;
|
||||
}
|
||||
|
||||
public async deserialize(context: StructDeserializationContext): Promise<this> {
|
||||
await context.read(this.length);
|
||||
public async deserialize(stream: StructAsyncDeserializeStream): Promise<this> {
|
||||
await stream.read(this.length);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ export async function adbSyncReadResponse<T extends Record<string, StructLike<an
|
|||
stream: AdbBufferedStream,
|
||||
types: T,
|
||||
): Promise<StructValueType<T[keyof T]>> {
|
||||
const id = stream.backend.decodeUtf8(await stream.read(4));
|
||||
const id = decodeUtf8(await stream.read(4));
|
||||
|
||||
if (id === AdbSyncResponseId.Fail) {
|
||||
await AdbSyncFailResponse.deserialize(stream);
|
||||
|
|
|
@ -31,15 +31,14 @@ export type AdbPacketInit = Omit<typeof AdbPacketStruct['TInit'], 'checksum' | '
|
|||
|
||||
export namespace AdbPacket {
|
||||
export async function read(backend: AdbBackend): Promise<AdbPacket> {
|
||||
let buffer = await backend.read(24);
|
||||
|
||||
// Detect boundary
|
||||
// Note that it relies on the backend to only return data from one write operation
|
||||
while (buffer.byteLength !== 24) {
|
||||
let buffer: ArrayBuffer;
|
||||
do {
|
||||
// Maybe it's a payload from last connection.
|
||||
// Ignore and try again
|
||||
buffer = await backend.read(24);
|
||||
}
|
||||
} while (buffer.byteLength !== 24);
|
||||
|
||||
let bufferUsed = false;
|
||||
const stream = new BufferedStream({
|
||||
|
@ -52,11 +51,7 @@ export namespace AdbPacket {
|
|||
}
|
||||
});
|
||||
|
||||
return AdbPacketStruct.deserialize({
|
||||
read: stream.read.bind(stream),
|
||||
decodeUtf8: backend.decodeUtf8.bind(backend),
|
||||
encodeUtf8: backend.encodeUtf8.bind(backend),
|
||||
});
|
||||
return AdbPacketStruct.deserialize(stream);
|
||||
}
|
||||
|
||||
export async function write(
|
||||
|
@ -80,7 +75,7 @@ export namespace AdbPacket {
|
|||
};
|
||||
|
||||
// Write payload separately to avoid an extra copy
|
||||
const header = AdbPacketHeader.serialize(packet, backend);
|
||||
const header = AdbPacketHeader.serialize(packet);
|
||||
await backend.write(header);
|
||||
if (packet.payload.byteLength) {
|
||||
await backend.write(packet.payload);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AsyncOperationManager } from '@yume-chan/async';
|
|||
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
||||
import { AdbBackend } from '../backend';
|
||||
import { AdbCommand, AdbPacket, AdbPacketInit } from '../packet';
|
||||
import { AutoResetEvent } from '../utils';
|
||||
import { AutoResetEvent, decodeUtf8, encodeUtf8 } from '../utils';
|
||||
import { AdbSocketController } from './controller';
|
||||
import { AdbLogger } from './logger';
|
||||
import { AdbSocket } from './socket';
|
||||
|
@ -160,7 +160,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
this.initializers.resolve(localId, undefined);
|
||||
|
||||
const remoteId = packet.arg0;
|
||||
const serviceString = this.backend.decodeUtf8(packet.payload!);
|
||||
const serviceString = decodeUtf8(packet.payload!);
|
||||
|
||||
const controller = new AdbSocketController({
|
||||
dispatcher: this,
|
||||
|
@ -234,7 +234,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
|
|||
command: packetOrCommand as AdbCommand,
|
||||
arg0: arg0 as number,
|
||||
arg1: arg1 as number,
|
||||
payload: typeof payload === 'string' ? this.backend.encodeUtf8(payload) : payload,
|
||||
payload: typeof payload === 'string' ? encodeUtf8(payload) : payload,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { StructDeserializationContext } from '@yume-chan/struct';
|
||||
import { StructAsyncDeserializeStream } from '@yume-chan/struct';
|
||||
import { AdbSocket, AdbSocketInfo } from '../socket';
|
||||
import { AdbSocketStream } from './stream';
|
||||
|
||||
|
@ -75,7 +75,7 @@ export class BufferedStream<T extends Stream> {
|
|||
|
||||
export class AdbBufferedStream
|
||||
extends BufferedStream<AdbSocketStream>
|
||||
implements AdbSocketInfo, StructDeserializationContext {
|
||||
implements AdbSocketInfo, StructAsyncDeserializeStream {
|
||||
public get backend() { return this.stream.backend; }
|
||||
public get localId() { return this.stream.localId; }
|
||||
public get remoteId() { return this.stream.remoteId; }
|
||||
|
@ -89,12 +89,4 @@ export class AdbBufferedStream
|
|||
public write(data: ArrayBuffer): Promise<void> {
|
||||
return this.stream.write(data);
|
||||
}
|
||||
|
||||
public decodeUtf8(buffer: ArrayBuffer): string {
|
||||
return this.backend.decodeUtf8(buffer);
|
||||
}
|
||||
|
||||
public encodeUtf8(input: string): ArrayBuffer {
|
||||
return this.backend.encodeUtf8(input);
|
||||
}
|
||||
}
|
||||
|
|
13
libraries/adb/src/utils/encoding.ts
Normal file
13
libraries/adb/src/utils/encoding.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
// @ts-expect-error @types/node missing `TextEncoder`
|
||||
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 decodeUtf8(buffer: ArrayBuffer): string {
|
||||
return Utf8Decoder.decode(buffer);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export * from './auto-reset-event';
|
||||
export * from './base64';
|
||||
export * from './chunk';
|
||||
export * from './encoding';
|
||||
export * from './event-queue';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue