refactor(adb): further split sync module

This commit is contained in:
Simon Chan 2020-09-28 01:48:33 +08:00
parent 65e0ba042e
commit d887639efd
18 changed files with 482 additions and 419 deletions

View file

@ -2,12 +2,10 @@ import { PromiseResolver } from '@yume-chan/async-operation-manager';
import { DisposableList } from '@yume-chan/event';
import { AdbAuthenticationHandler, AdbDefaultAuthenticators } from './auth';
import { AdbBackend } from './backend';
import { AdbReverseCommand, AdbTcpIpCommand } from './commands';
import { AdbFrameBuffer, AdbReverseCommand, AdbSync, AdbTcpIpCommand } from './commands';
import { AdbFeatures } from './features';
import { FrameBuffer } from './framebuffer';
import { AdbCommand } from './packet';
import { AdbBufferedStream, AdbPacketDispatcher, AdbReadableStream, AdbStream } from './stream';
import { AdbSync } from './sync';
export enum AdbPropKey {
Product = 'ro.product.name',
@ -175,15 +173,20 @@ export class Adb {
}
}
public async sync(): Promise<AdbSync> {
const stream = await this.createStream('sync:');
return new AdbSync(stream, this);
public async getProp(key: string): Promise<string> {
const output = await this.shell('getprop', key);
return output.trim();
}
public async framebuffer(): Promise<FrameBuffer> {
public async sync(): Promise<AdbSync> {
const stream = await this.createStream('sync:');
return new AdbSync(this, stream);
}
public async framebuffer(): Promise<AdbFrameBuffer> {
const stream = await this.createStream('framebuffer:');
const buffered = new AdbBufferedStream(stream);
return FrameBuffer.deserialize(buffered);
return AdbFrameBuffer.deserialize(buffered);
}
public async createStream(service: string): Promise<AdbStream> {

View file

@ -1,6 +1,6 @@
import { Struct, StructValueType } from "@yume-chan/struct";
export const FrameBuffer =
export const AdbFrameBuffer =
new Struct({ littleEndian: true })
.uint32('version', undefined, 2 as const)
.uint32('bpp')
@ -18,4 +18,4 @@ export const FrameBuffer =
.uint32('alpha_length')
.arrayBuffer('data', { lengthField: 'size' });
export type FrameBuffer = StructValueType<typeof FrameBuffer>;
export type AdbFrameBuffer = StructValueType<typeof AdbFrameBuffer>;

View file

@ -1,3 +1,5 @@
export * from './base';
export * from './tcpip';
export * from './framebuffer';
export * from './reverse';
export * from './sync';
export * from './tcpip';

View file

@ -1,9 +1,7 @@
import { AutoDisposable } from '@yume-chan/event';
import { Struct } from '@yume-chan/struct';
import { Adb } from '../adb';
import { AdbPacket } from '../packet';
import { AdbBufferedStream, AdbPacketDispatcher, AdbStream } from '../stream';
import { AdbCommandBase } from './base';
import { AdbBufferedStream, AdbIncomingStreamEventArgs, AdbPacketDispatcher, AdbStream } from '../stream';
export interface AdbReverseHandler {
onStream(packet: AdbPacket, stream: AdbStream): void;
@ -29,38 +27,22 @@ const AdbReverseErrorResponse =
});
export class AdbReverseCommand extends AutoDisposable {
private portToHandlerMap = new Map<number, AdbReverseHandler>();
protected portToHandlerMap = new Map<number, AdbReverseHandler>();
private devicePortToPortMap = new Map<number, number>();
protected devicePortToPortMap = new Map<number, number>();
private dispatcher: AdbPacketDispatcher;
protected dispatcher: AdbPacketDispatcher;
private listening = false;
protected listening = false;
public constructor(dispatcher: AdbPacketDispatcher) {
super();
this.dispatcher = dispatcher;
this.addDisposable(this.dispatcher.onStream(this.handleStream, this));
}
public async list(): Promise<AdbForwardListener[]> {
const stream = await this.dispatcher.createStream('reverse:list-forward');
const buffered = new AdbBufferedStream(stream);
const response = await AdbReverseStringResponse.deserialize(buffered);
return response.content!.split('\n').map(line => {
const [deviceSerial, localName, remoteName] = line.split(' ');
return { deviceSerial, localName, remoteName };
});
}
public async add(
port: number,
handler: AdbReverseHandler,
devicePort: number = 0,
): Promise<number> {
if (!this.listening) {
this.addDisposable(this.dispatcher.onStream(e => {
protected handleStream(e: AdbIncomingStreamEventArgs): void {
if (e.handled) {
return;
}
@ -71,9 +53,13 @@ export class AdbReverseCommand extends AutoDisposable {
this.portToHandlerMap.get(port)!.onStream(e.packet, e.stream);
e.handled = true;
}
}));
}
public async add(
port: number,
handler: AdbReverseHandler,
devicePort: number = 0,
): Promise<number> {
const stream = await this.dispatcher.createStream(`reverse:forward:tcp:${devicePort};tcp:${port}`);
const buffered = new AdbBufferedStream(stream);
@ -92,6 +78,18 @@ export class AdbReverseCommand extends AutoDisposable {
}
}
public async list(): Promise<AdbForwardListener[]> {
const stream = await this.dispatcher.createStream('reverse:list-forward');
const buffered = new AdbBufferedStream(stream);
const response = await AdbReverseStringResponse.deserialize(buffered);
return response.content!.split('\n').map(line => {
const [deviceSerial, localName, remoteName] = line.split(' ');
return { deviceSerial, localName, remoteName };
});
}
public async remove(devicePort: number): Promise<void> {
const stream = await this.dispatcher.createStream(`reverse:killforward:tcp:${devicePort}`);
const buffered = new AdbBufferedStream(stream);

View file

@ -1,2 +1,7 @@
export * from './sync';
export * from './list';
export * from './receive';
export * from './request';
export * from './response';
export * from './send';
export * from './stat';
export * from './sync';

View file

@ -0,0 +1,39 @@
import { StructValueType } from '@yume-chan/struct';
import { AdbBufferedStream } from '../../stream';
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response';
import { AdbSyncLstatResponse } from './stat';
export const AdbSyncEntryResponse =
AdbSyncLstatResponse
.afterParsed()
.uint32('nameLength')
.string('name', { lengthField: 'nameLength' })
.extra({ id: AdbSyncResponseId.Entry as const });
export type AdbSyncEntryResponse = StructValueType<typeof AdbSyncEntryResponse>;
const ResponseTypes = {
[AdbSyncResponseId.Entry]: AdbSyncEntryResponse,
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncEntryResponse.size),
};
export async function* adbSyncOpenDir(
stream: AdbBufferedStream,
path: string
): AsyncGenerator<AdbSyncEntryResponse, void, void> {
await adbSyncWriteRequest(stream, AdbSyncRequestId.List, path);
while (true) {
const response = await adbSyncReadResponse(stream, ResponseTypes);
switch (response.id) {
case AdbSyncResponseId.Entry:
yield response;
break;
case AdbSyncResponseId.Done:
return;
default:
throw new Error('Unexpected response id');
}
}
}

View file

@ -0,0 +1,34 @@
import { Struct } from '@yume-chan/struct';
import { AdbBufferedStream } from '../../stream';
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
import { AdbSyncDoneResponse, adbSyncReadResponse, AdbSyncResponseId } from './response';
export const AdbSyncDataResponse =
new Struct({ littleEndian: true })
.uint32('dataLength')
.arrayBuffer('data', { lengthField: 'dataLength' })
.extra({ id: AdbSyncResponseId.Data as const });
const ResponseTypes = {
[AdbSyncResponseId.Data]: AdbSyncDataResponse,
[AdbSyncResponseId.Done]: new AdbSyncDoneResponse(AdbSyncDataResponse.size),
};
export async function* adbSyncPull(
stream: AdbBufferedStream,
path: string,
): AsyncGenerator<ArrayBuffer, void, void> {
await adbSyncWriteRequest(stream, AdbSyncRequestId.Receive, path);
while (true) {
const response = await adbSyncReadResponse(stream, ResponseTypes);
switch (response.id) {
case AdbSyncResponseId.Data:
yield response.data!;
break;
case AdbSyncResponseId.Done:
return;
default:
throw new Error('Unexpected response id');
}
}
}

View file

@ -0,0 +1,47 @@
import { Struct } from '@yume-chan/struct';
import { AdbBufferedStream } from '../../stream';
export enum AdbSyncRequestId {
List = 'LIST',
Send = 'SEND',
Lstat = 'STAT',
Stat = 'STA2',
Lstat2 = 'LST2',
Data = 'DATA',
Done = 'DONE',
Receive = 'RECV',
}
export const AdbSyncNumberRequest =
new Struct({ littleEndian: true })
.string('id', { length: 4 })
.uint32('arg');
export const AdbSyncDataRequest =
AdbSyncNumberRequest
.arrayBuffer('data', { lengthField: 'arg' });
export async function adbSyncWriteRequest(
stream: AdbBufferedStream,
id: AdbSyncRequestId | string,
value: number | string | ArrayBuffer
): Promise<void> {
let buffer: ArrayBuffer;
if (typeof value === 'number') {
buffer = AdbSyncNumberRequest.serialize({
id,
arg: value,
}, stream);
} else if (typeof value === 'string') {
buffer = AdbSyncDataRequest.serialize({
id,
data: stream.encodeUtf8(value),
}, stream);
} else {
buffer = AdbSyncDataRequest.serialize({
id,
data: value,
}, stream);
}
await stream.write(buffer);
}

View file

@ -0,0 +1,57 @@
import { Struct, StructDeserializationContext, StructValueType } from '@yume-chan/struct';
import { AdbBufferedStream } from '../../stream';
export enum AdbSyncResponseId {
Entry = 'DENT',
Lstat = 'STAT',
Stat = 'STA2',
Lstat2 = 'LST2',
Done = 'DONE',
Data = 'DATA',
Ok = 'OKAY',
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 {
private length: number;
public readonly id = AdbSyncResponseId.Done;
public constructor(length: number) {
this.length = length;
}
public async deserialize(context: StructDeserializationContext): Promise<this> {
await context.read(this.length);
return this;
}
}
export const AdbSyncFailResponse =
new Struct({ littleEndian: true })
.uint32('messageLength')
.string('message', { lengthField: 'messageLength' })
.afterParsed(object => {
throw new Error(object.message);
});
export async function adbSyncReadResponse<T extends Record<string, { deserialize(context: StructDeserializationContext): Promise<any>; }>>(
stream: AdbBufferedStream,
types: T,
): Promise<StructValueType<T[keyof T]>> {
const id = stream.backend.decodeUtf8(await stream.read(4));
if (id === AdbSyncResponseId.Fail) {
await AdbSyncFailResponse.deserialize(stream);
}
if (types[id]) {
return types[id].deserialize(stream);
}
throw new Error('Unexpected response id');
}

View file

@ -0,0 +1,107 @@
import { Struct } from '@yume-chan/struct';
import { AdbBufferedStream } from '../../stream';
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
import { adbSyncReadResponse, AdbSyncResponseId } from './response';
import { LinuxFileType } from './stat';
export const AdbSyncOkResponse =
new Struct({ littleEndian: true })
.uint32('unused');
const ResponseTypes = {
[AdbSyncResponseId.Ok]: AdbSyncOkResponse,
};
export function* chunkArrayLike(
value: ArrayLike<number> | ArrayBufferLike,
size: number
): Generator<ArrayBuffer, void, void> {
if ('length' in value) {
value = new Uint8Array(value).buffer;
}
if (value.byteLength <= size) {
return yield value;
}
for (let i = 0; i < value.byteLength; i += size) {
yield value.slice(i, i + size);
}
}
export async function* chunkAsyncIterable(
value: AsyncIterable<ArrayBuffer>,
size: number
): AsyncGenerator<ArrayBuffer, void, void> {
let result = new Uint8Array(size);
let index = 0;
for await (let buffer of value) {
// `result` has some data, `result + buffer` is enough
if (index !== 0 && index + buffer.byteLength >= size) {
const remainder = size - index;
result.set(new Uint8Array(buffer, 0, remainder), index);
yield result.buffer;
result = new Uint8Array(size);
index = 0;
if (buffer.byteLength > remainder) {
// `buffer` still has some data
buffer = buffer.slice(remainder);
} else {
continue;
}
}
// `result` is empty, `buffer` alone is enough
if (buffer.byteLength >= size) {
let remainder = false;
for (const chunk of chunkArrayLike(buffer, size)) {
if (chunk.byteLength === size) {
yield chunk;
}
// `buffer` still has some data
remainder = true;
buffer = chunk;
}
if (!remainder) {
continue;
}
}
// `result` has some data but `result + buffer` is still not enough
// or after previous steps `buffer` still has some data
result.set(new Uint8Array(buffer), index);
index += buffer.byteLength;
}
}
export const AdbSyncSendPacketSize = 64 * 1024;
export async function adbSyncPush(
stream: AdbBufferedStream,
path: string,
file: ArrayLike<number> | ArrayBufferLike | AsyncIterable<ArrayBuffer>,
mode: number = LinuxFileType.File | 0o777,
mtime: number = (Date.now() / 1000) | 0,
packetSize: number = AdbSyncSendPacketSize,
): Promise<void> {
const pathAndMode = `${path},${mode.toString(8)}`;
await adbSyncWriteRequest(stream, AdbSyncRequestId.Send, pathAndMode);
let chunkReader: Iterable<ArrayBuffer> | AsyncIterable<ArrayBuffer>;
if ('length' in file || 'byteLength' in file) {
chunkReader = chunkArrayLike(file, packetSize);
} else {
chunkReader = chunkAsyncIterable(file, packetSize);
}
for await (const buffer of chunkReader) {
await adbSyncWriteRequest(stream, AdbSyncRequestId.Data, buffer);
}
await adbSyncWriteRequest(stream, AdbSyncRequestId.Send, mtime);
await adbSyncReadResponse(stream, ResponseTypes);
}

View file

@ -0,0 +1,121 @@
import { placeholder, Struct, StructValueType } from '@yume-chan/struct';
import { AdbBufferedStream } from '../../stream';
import { AdbSyncRequestId, adbSyncWriteRequest } from './request';
import { adbSyncReadResponse, AdbSyncResponseId } from './response';
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
export enum LinuxFileType {
Directory = 0o04,
File = 0o10,
Link = 0o12,
}
export const AdbSyncLstatResponse =
new Struct({ littleEndian: true })
.int32('mode')
.int32('size')
.int32('mtime')
.extra({
id: AdbSyncResponseId.Lstat as const,
get type() { return this.mode >> 12 as LinuxFileType; },
get permission() { return this.mode & 0b00001111_11111111; },
})
.afterParsed((object) => {
if (object.mode === 0 &&
object.size === 0 &&
object.mtime === 0
) {
throw new Error('lstat failed');
}
});
export type AdbSyncLstatResponse = StructValueType<typeof AdbSyncLstatResponse>;
export enum AdbSyncStatErrorCode {
EACCES = 13,
EEXIST = 17,
EFAULT = 14,
EFBIG = 27,
EINTR = 4,
EINVAL = 22,
EIO = 5,
EISDIR = 21,
ELOOP = 40,
EMFILE = 24,
ENAMETOOLONG = 36,
ENFILE = 23,
ENOENT = 2,
ENOMEM = 12,
ENOSPC = 28,
ENOTDIR = 20,
EOVERFLOW = 75,
EPERM = 1,
EROFS = 30,
ETXTBSY = 26,
}
export const AdbSyncStatResponse =
new Struct({ littleEndian: true })
.uint32('error', undefined, placeholder<AdbSyncStatErrorCode>())
.uint64('dev')
.uint64('ino')
.uint32('mode')
.uint32('nlink')
.uint32('uid')
.uint32('gid')
.uint64('size')
.uint64('atime')
.uint64('mtime')
.uint64('ctime')
.extra({
id: AdbSyncResponseId.Stat as const,
get type() { return this.mode >> 12 as LinuxFileType; },
get permission() { return this.mode & 0b00001111_11111111; },
})
.afterParsed((object) => {
if (object.error) {
throw new Error(AdbSyncStatErrorCode[object.error]);
}
});
export type AdbSyncStatResponse = StructValueType<typeof AdbSyncStatResponse>;
const StatResponseType = {
[AdbSyncResponseId.Stat]: AdbSyncStatResponse,
};
const LstatResponseType = {
[AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
};
const Lstat2ResponseType = {
[AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
};
export async function adbSyncLstat(
stream: AdbBufferedStream,
path: string,
v2: boolean,
): Promise<AdbSyncLstatResponse | AdbSyncStatResponse> {
let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
let responseType: typeof LstatResponseType | typeof Lstat2ResponseType;
if (v2) {
requestId = AdbSyncRequestId.Lstat2;
responseType = Lstat2ResponseType;
} else {
requestId = AdbSyncRequestId.Lstat;
responseType = LstatResponseType;
}
await adbSyncWriteRequest(stream, requestId, path);
return adbSyncReadResponse(stream, responseType);
}
export async function adbSyncStat(
stream: AdbBufferedStream,
path: string,
): Promise<AdbSyncStatResponse> {
await adbSyncWriteRequest(stream, AdbSyncRequestId.Stat, path);
return adbSyncReadResponse(stream, StatResponseType);
}

View file

@ -1,260 +1,12 @@
import { AutoDisposable } from '@yume-chan/event';
import { placeholder, Struct, StructDeserializationContext, StructInitType, StructValueType } from '@yume-chan/struct';
import { Adb } from '../../adb';
import { AdbFeatures } from '../../features';
import { AdbBufferedStream, AdbStream } from '../../stream';
import { AutoResetEvent } from '../../utils';
export enum AdbSyncRequestId {
List = 'LIST',
Send = 'SEND',
Lstat = 'STAT',
Stat = 'STA2',
Lstat2 = 'LST2',
Data = 'DATA',
Done = 'DONE',
Receive = 'RECV',
}
export enum AdbSyncResponseId {
Entry = 'DENT',
Lstat = 'STAT',
Stat = 'STA2',
Lstat2 = 'LST2',
Done = 'DONE',
Data = 'DATA',
Ok = 'OKAY',
Fail = 'FAIL',
}
const AdbSyncNumberRequest =
new Struct({ littleEndian: true })
.string('id', { length: 4 })
.uint32('arg');
const AdbSyncStringRequest =
AdbSyncNumberRequest
.string('data', { lengthField: 'arg' });
const AdbSyncBufferRequest =
AdbSyncNumberRequest
.arrayBuffer('data', { lengthField: 'arg' });
// https://github.com/python/cpython/blob/4e581d64b8aff3e2eda99b12f080c877bb78dfca/Lib/stat.py#L36
export enum LinuxFileType {
Directory = 0o04,
File = 0o10,
Link = 0o12,
}
export const AdbSyncLstatResponse =
new Struct({ littleEndian: true })
.int32('mode')
.int32('size')
.int32('mtime')
.extra({
id: AdbSyncResponseId.Lstat as const,
get type() { return this.mode >> 12 as LinuxFileType; },
get permission() { return this.mode & 0b00001111_11111111; },
})
.afterParsed((object) => {
if (object.mode === 0 &&
object.size === 0 &&
object.mtime === 0
) {
throw new Error('lstat failed');
}
});
export type AdbSyncLstatResponse = StructValueType<typeof AdbSyncLstatResponse>;
export enum ErrorCode {
EACCES = 13,
EEXIST = 17,
EFAULT = 14,
EFBIG = 27,
EINTR = 4,
EINVAL = 22,
EIO = 5,
EISDIR = 21,
ELOOP = 40,
EMFILE = 24,
ENAMETOOLONG = 36,
ENFILE = 23,
ENOENT = 2,
ENOMEM = 12,
ENOSPC = 28,
ENOTDIR = 20,
EOVERFLOW = 75,
EPERM = 1,
EROFS = 30,
ETXTBSY = 26,
}
export const AdbSyncStatResponse =
new Struct({ littleEndian: true })
.uint32('error', undefined, placeholder<ErrorCode>())
.uint64('dev')
.uint64('ino')
.uint32('mode')
.uint32('nlink')
.uint32('uid')
.uint32('gid')
.uint64('size')
.uint64('atime')
.uint64('mtime')
.uint64('ctime')
.extra({
id: AdbSyncResponseId.Stat as const,
get type() { return this.mode >> 12 as LinuxFileType; },
get permission() { return this.mode & 0b00001111_11111111; },
})
.afterParsed((object) => {
if (object.error) {
throw new Error(ErrorCode[object.error]);
}
});
export type AdbSyncStatResponse = StructValueType<typeof AdbSyncStatResponse>;
export const AdbSyncEntryResponse =
AdbSyncLstatResponse
.afterParsed()
.uint32('nameLength')
.string('name', { lengthField: 'nameLength' })
.extra({ id: AdbSyncResponseId.Entry as const });
export type AdbSyncEntryResponse = StructValueType<typeof AdbSyncEntryResponse>;
export const AdbSyncDataResponse =
new Struct({ littleEndian: true })
.uint32('dataLength')
.arrayBuffer('data', { lengthField: 'dataLength' })
.extra({ id: AdbSyncResponseId.Data as const });
export interface AdbSyncDoneResponseDeserializeContext extends StructDeserializationContext {
size: number;
}
export class AdbSyncDoneResponse {
public static readonly instance = new AdbSyncDoneResponse();
public static async deserialize(
context: AdbSyncDoneResponseDeserializeContext
): Promise<AdbSyncDoneResponse> {
await context.read(context.size);
return AdbSyncDoneResponse.instance;
}
public readonly id = AdbSyncResponseId.Done;
}
export const AdbSyncFailResponse =
new Struct({ littleEndian: true })
.uint32('messageLength')
.string('message', { lengthField: 'messageLength' })
.afterParsed(object => {
throw new Error(object.message);
});
const ResponseTypeMap = {
[AdbSyncResponseId.Entry]: AdbSyncEntryResponse,
[AdbSyncResponseId.Lstat]: AdbSyncLstatResponse,
[AdbSyncResponseId.Stat]: AdbSyncStatResponse,
[AdbSyncResponseId.Lstat2]: AdbSyncStatResponse,
[AdbSyncResponseId.Data]: AdbSyncDataResponse,
[AdbSyncResponseId.Fail]: AdbSyncFailResponse,
[AdbSyncResponseId.Done]: AdbSyncDoneResponse,
} as const;
async function readResponse(stream: AdbBufferedStream, size: number) {
// 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.
const id = stream.backend.decodeUtf8(await stream.read(4)) as keyof typeof ResponseTypeMap;
if (ResponseTypeMap[id]) {
return ResponseTypeMap[id].deserialize({
size,
read: stream.read.bind(stream),
decodeUtf8: stream.backend.decodeUtf8.bind(stream.backend),
encodeUtf8: stream.backend.encodeUtf8.bind(stream.backend),
});
}
await stream.read(size);
throw new Error('Unexpected response id');
}
export function chunkArray(
value: ArrayLike<number>,
size: number
): Generator<ArrayBuffer, void, void> {
return chunkArrayBuffer(new Uint8Array(value).buffer, size);
}
export function* chunkArrayBuffer(
value: ArrayBufferLike,
size: number
): Generator<ArrayBuffer, void, void> {
if (value.byteLength <= size) {
return yield value;
}
for (let i = 0; i < value.byteLength; i += size) {
yield value.slice(i, i + size);
}
}
export async function* chunkAsyncIterable(
value: AsyncIterable<ArrayBuffer>,
size: number
): AsyncGenerator<ArrayBuffer, void, void> {
let result = new Uint8Array(size);
let index = 0;
for await (let buffer of value) {
// `result` has some data, `result + buffer` is enough
if (index !== 0 && index + buffer.byteLength >= size) {
const remainder = size - index;
result.set(new Uint8Array(buffer, 0, remainder), index);
yield result.buffer;
result = new Uint8Array(size);
index = 0;
if (buffer.byteLength > remainder) {
// `buffer` still has some data
buffer = buffer.slice(remainder);
} else {
continue;
}
}
// `result` is empty, `buffer` alone is enough
if (buffer.byteLength >= size) {
let remainder = false;
for (const chunk of chunkArrayBuffer(buffer, size)) {
if (chunk.byteLength === size) {
yield chunk;
}
// `buffer` still has some data
remainder = true;
buffer = chunk;
}
if (!remainder) {
continue;
}
}
// `result` has some data but `result + buffer` is still not enough
// or after previous steps `buffer` still has some data
result.set(new Uint8Array(buffer), index);
index += buffer.byteLength;
}
}
import { AdbSyncEntryResponse, adbSyncOpenDir } from './list';
import { adbSyncPull } from './receive';
import { adbSyncPush } from './send';
import { adbSyncLstat, adbSyncStat } from './stat';
export class AdbSync extends AutoDisposable {
protected adb: Adb;
@ -274,37 +26,11 @@ export class AdbSync extends AutoDisposable {
this.stream = new AdbBufferedStream(stream);
}
protected send<T extends Struct<object, object, object, unknown>>(
type: T,
value: StructInitType<T>
) {
return this.stream.write(type.serialize(value, this.stream.backend));
}
public async lstat(path: string): Promise<AdbSyncLstatResponse | AdbSyncStatResponse> {
public async lstat(path: string) {
await this.sendLock.wait();
try {
let requestId: AdbSyncRequestId.Lstat | AdbSyncRequestId.Lstat2;
let responseType: typeof AdbSyncLstatResponse | typeof AdbSyncStatResponse;
let responseId: AdbSyncResponseId.Lstat | AdbSyncResponseId.Stat;
if (this.supportStat) {
requestId = AdbSyncRequestId.Lstat2;
responseType = AdbSyncStatResponse;
responseId = AdbSyncResponseId.Stat;
} else {
requestId = AdbSyncRequestId.Lstat;
responseType = AdbSyncLstatResponse;
responseId = AdbSyncResponseId.Lstat;
}
await this.send(AdbSyncStringRequest, { id: requestId, data: path });
const response = await readResponse(this.stream, responseType.size);
if (response.id !== responseId) {
throw new Error('Unexpected response id');
}
return response;
return adbSyncLstat(this.stream, path, this.supportStat);
} finally {
this.sendLock.notify();
}
@ -318,48 +44,28 @@ export class AdbSync extends AutoDisposable {
await this.sendLock.wait();
try {
await this.send(AdbSyncStringRequest, { id: AdbSyncRequestId.Stat, data: path });
const response = await readResponse(this.stream, AdbSyncStatResponse.size);
if (response.id !== AdbSyncResponseId.Stat) {
throw new Error('Unexpected response id');
}
return response;
return adbSyncStat(this.stream, path);
} finally {
this.sendLock.notify();
}
}
public async isDirectory(path: string): Promise<boolean> {
await this.sendLock.wait();
try {
await this.stat(path + '/');
return true;
} catch (e) {
return false;
} finally {
this.sendLock.notify();
}
}
public async *opendir(path: string) {
public async *opendir(
path: string
): AsyncGenerator<AdbSyncEntryResponse, void, void> {
await this.sendLock.wait();
try {
await this.send(AdbSyncStringRequest, { id: AdbSyncRequestId.List, data: path });
while (true) {
const response = await readResponse(this.stream, AdbSyncEntryResponse.size);
switch (response.id) {
case AdbSyncResponseId.Entry:
yield response;
break;
case AdbSyncResponseId.Done:
return;
default:
throw new Error('Unexpected response id');
}
}
yield* adbSyncOpenDir(this.stream, path);
} finally {
this.sendLock.notify();
}
@ -377,36 +83,12 @@ export class AdbSync extends AutoDisposable {
await this.sendLock.wait();
try {
await this.send(AdbSyncStringRequest, { id: AdbSyncRequestId.Receive, data: path });
while (true) {
const response = await readResponse(this.stream, AdbSyncDataResponse.size);
switch (response.id) {
case AdbSyncResponseId.Data:
yield response.data!;
break;
case AdbSyncResponseId.Done:
return;
default:
throw new Error('Unexpected response id');
}
}
yield* adbSyncPull(this.stream, path);
} finally {
this.sendLock.notify();
}
}
public async write(
path: string,
file: AsyncIterable<ArrayBuffer>,
mode?: number,
mtime?: number,
): Promise<void>;
public async write(
path: string,
file: ArrayLike<number>,
mode?: number,
mtime?: number,
): Promise<void>;
public async write(
path: string,
file: AsyncIterable<ArrayBuffer> | ArrayLike<number>,
@ -414,35 +96,9 @@ export class AdbSync extends AutoDisposable {
mtime = Date.now(),
): Promise<void> {
await this.sendLock.wait();
const packetSize = 64 * 1024;
try {
const pathAndMode = `${path},${mode.toString(8)}`;
await this.send(AdbSyncStringRequest, {
id: AdbSyncRequestId.Send,
data: pathAndMode
});
let chunkReader: Iterable<ArrayBuffer> | AsyncIterable<ArrayBuffer>;
if ('length' in file) {
chunkReader = chunkArray(file, packetSize);
} else if ('byteLength' in file) {
chunkReader = chunkArrayBuffer(file, packetSize);
} else {
chunkReader = chunkAsyncIterable(file, packetSize);
}
for await (const buffer of chunkReader) {
await this.send(AdbSyncBufferRequest, {
id: AdbSyncRequestId.Data,
data: buffer,
});
}
await this.send(AdbSyncNumberRequest, {
id: AdbSyncRequestId.Send,
arg: mtime
});
adbSyncPush(this.stream, path, file, mode, mtime);
} finally {
this.sendLock.notify();
}

View file

@ -1,23 +1,18 @@
import { AdbCommandBase } from './base';
export class AdbTcpIpCommand extends AdbCommandBase {
private async getProp(key: string): Promise<string> {
const output = await this.adb.shell('getprop', key);
return output.trim();
}
public async getAddresses(): Promise<string[]> {
const propAddr = await this.getProp('service.adb.listen_addrs');
const propAddr = await this.adb.getProp('service.adb.listen_addrs');
if (propAddr) {
return propAddr.split(',');
}
let port = await this.getProp('service.adb.tcp.port');
let port = await this.adb.getProp('service.adb.tcp.port');
if (port) {
return [`0.0.0.0:${port}`];
}
port = await this.getProp('persist.adb.tcp.port');
port = await this.adb.getProp('persist.adb.tcp.port');
if (port) {
return [`0.0.0.0:${port}`];
}

View file

@ -257,7 +257,7 @@ export function sign(privateKey: ArrayBuffer, data: ArrayBuffer): ArrayBuffer {
const fillLength = padded.length - Sha1DigestInfo.length - data.byteLength - 1;
while (index < fillLength) {
padded[index] = 0xff;
index++;
index += 1;
}
padded[index] = 0;

View file

@ -1,10 +1,9 @@
export * from './adb';
export * from './auth';
export * from './backend';
export * from './commands';
export * from './crypto';
export * from './features';
export * from './framebuffer';
export * from './packet';
export * from './stream';
export * from './sync';
export * from './utils';

View file

@ -11,7 +11,7 @@ export interface AdbPacketReceivedEventArgs {
packet: AdbPacket;
}
export interface AdbStreamCreatedEventArgs {
export interface AdbIncomingStreamEventArgs {
handled: boolean;
packet: AdbPacket;
@ -30,7 +30,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
public get onPacket() { return this.packetEvent.event; }
private readonly streamEvent = this.addDisposable(new EventEmitter<AdbStreamCreatedEventArgs>());
private readonly streamEvent = this.addDisposable(new EventEmitter<AdbIncomingStreamEventArgs>());
public get onStream() { return this.streamEvent.event; }
private readonly errorEvent = this.addDisposable(new EventEmitter<Error>());
@ -132,7 +132,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
const controller = new AdbStreamController(localId, remoteId, this);
const stream = new AdbStream(controller);
const args: AdbStreamCreatedEventArgs = {
const args: AdbIncomingStreamEventArgs = {
handled: false,
packet,
stream,

View file

@ -18,7 +18,7 @@ function addRange(start: string, end: string) {
const endCharCode = end.charCodeAt(0);
const length = endCharCode - startCharCode + 1;
for (let i = startCharCode; i <= endCharCode; i++) {
for (let i = startCharCode; i <= endCharCode; i += 1) {
chars.push(i);
}

View file

@ -3,8 +3,8 @@ import { Array, FieldDescriptorBase, FieldDescriptorBaseOptions, FieldType, Fiel
import { StructDefaultOptions, StructDeserializationContext, StructOptions, StructSerializationContext } from './types';
import { Evaluate, Identity, OmitNever, Overwrite } from './utils';
export type StructValueType<T extends Struct<object, object, object, unknown>> =
T extends { deserialize(reader: StructDeserializationContext): Promise<infer R>; } ? R : never;
export type StructValueType<T> =
T extends { deserialize(context: StructDeserializationContext): Promise<infer R>; } ? R : never;
export type StructInitType<T extends Struct<object, object, object, unknown>> =
T extends { create(value: infer R, ...args: any): any; } ? Evaluate<R> : never;
@ -407,7 +407,7 @@ export default class Struct<
let size = this._size;
let fieldSize: number[] = [];
for (let i = 0; i < this.fields.length; i++) {
for (let i = 0; i < this.fields.length; i += 1) {
const field = this.fields[i];
const type = getFieldTypeDefinition(field.type);
if (type.getDynamicSize) {
@ -426,7 +426,7 @@ export default class Struct<
const buffer = new ArrayBuffer(size);
const dataView = new DataView(buffer);
let offset = 0;
for (let i = 0; i < this.fields.length; i++) {
for (let i = 0; i < this.fields.length; i += 1) {
const field = this.fields[i];
const type = getFieldTypeDefinition(field.type);
type.serialize({