Skip to content

Commit

Permalink
feat(struct): new API full rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
yume-chan committed Oct 31, 2024
1 parent a292684 commit d50a170
Show file tree
Hide file tree
Showing 89 changed files with 1,481 additions and 5,506 deletions.
6 changes: 3 additions & 3 deletions libraries/adb-daemon-webusb/src/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
pipeFrom,
} from "@yume-chan/stream-extra";
import type { ExactReadable } from "@yume-chan/struct";
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
import { EmptyUint8Array } from "@yume-chan/struct";

import type { UsbInterfaceFilter } from "./utils.js";
import {
Expand Down Expand Up @@ -185,7 +185,7 @@ export class AdbDaemonWebUsbConnection
if (zeroMask && (chunk.length & zeroMask) === 0) {
await device.raw.transferOut(
outEndpoint.endpointNumber,
EMPTY_UINT8_ARRAY,
EmptyUint8Array,
);
}
} catch (e) {
Expand Down Expand Up @@ -234,7 +234,7 @@ export class AdbDaemonWebUsbConnection
);
packet.payload = new Uint8Array(result.data!.buffer);
} else {
packet.payload = EMPTY_UINT8_ARRAY;
packet.payload = EmptyUint8Array;
}

return packet;
Expand Down
6 changes: 3 additions & 3 deletions libraries/adb-scrcpy/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
BufferedReadableStream,
PushReadableStream,
} from "@yume-chan/stream-extra";
import type { ValueOrPromise } from "@yume-chan/struct";
import type { MaybePromiseLike } from "@yume-chan/struct";

export interface AdbScrcpyConnectionOptions {
scid: number;
Expand Down Expand Up @@ -54,7 +54,7 @@ export abstract class AdbScrcpyConnection implements Disposable {
this.socketName = this.getSocketName();
}

initialize(): ValueOrPromise<void> {
initialize(): MaybePromiseLike<void> {
// pure virtual method
}

Expand All @@ -66,7 +66,7 @@ export abstract class AdbScrcpyConnection implements Disposable {
return socketName;
}

abstract getStreams(): ValueOrPromise<AdbScrcpyConnectionStreams>;
abstract getStreams(): MaybePromiseLike<AdbScrcpyConnectionStreams>;

dispose(): void {
// pure virtual method
Expand Down
6 changes: 3 additions & 3 deletions libraries/adb-server-node-tcp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
PushReadableStream,
tryClose,
} from "@yume-chan/stream-extra";
import type { ValueOrPromise } from "@yume-chan/struct";
import type { MaybePromiseLike } from "@yume-chan/struct";

function nodeSocketToConnection(
socket: Socket,
Expand Down Expand Up @@ -138,7 +138,7 @@ export class AdbServerNodeTcpConnector
return address;
}

removeReverseTunnel(address: string): ValueOrPromise<void> {
removeReverseTunnel(address: string): MaybePromiseLike<void> {
const server = this.#listeners.get(address);
if (!server) {
return;
Expand All @@ -147,7 +147,7 @@ export class AdbServerNodeTcpConnector
this.#listeners.delete(address);
}

clearReverseTunnels(): ValueOrPromise<void> {
clearReverseTunnels(): MaybePromiseLike<void> {
for (const server of this.#listeners.values()) {
server.close();
}
Expand Down
14 changes: 7 additions & 7 deletions libraries/adb/src/adb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
ReadableWritablePair,
} from "@yume-chan/stream-extra";
import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
import type { ValueOrPromise } from "@yume-chan/struct";
import type { MaybePromiseLike } from "@yume-chan/struct";

import type { AdbBanner } from "./banner.js";
import type { AdbFrameBuffer } from "./commands/index.js";
Expand All @@ -19,7 +19,7 @@ import {
import type { AdbFeature } from "./features.js";

export interface Closeable {
close(): ValueOrPromise<void>;
close(): MaybePromiseLike<void>;
}

/**
Expand All @@ -37,7 +37,7 @@ export interface AdbSocket

export type AdbIncomingSocketHandler = (
socket: AdbSocket,
) => ValueOrPromise<void>;
) => MaybePromiseLike<void>;

export interface AdbTransport extends Closeable {
readonly serial: string;
Expand All @@ -50,16 +50,16 @@ export interface AdbTransport extends Closeable {

readonly clientFeatures: readonly AdbFeature[];

connect(service: string): ValueOrPromise<AdbSocket>;
connect(service: string): MaybePromiseLike<AdbSocket>;

addReverseTunnel(
handler: AdbIncomingSocketHandler,
address?: string,
): ValueOrPromise<string>;
): MaybePromiseLike<string>;

removeReverseTunnel(address: string): ValueOrPromise<void>;
removeReverseTunnel(address: string): MaybePromiseLike<void>;

clearReverseTunnels(): ValueOrPromise<void>;
clearReverseTunnels(): MaybePromiseLike<void>;
}

export class Adb implements Closeable {
Expand Down
85 changes: 44 additions & 41 deletions libraries/adb/src/commands/framebuffer.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,53 @@
import { BufferedReadableStream } from "@yume-chan/stream-extra";
import Struct, { StructEmptyError } from "@yume-chan/struct";
import type { StructValue } from "@yume-chan/struct";
import { buffer, Struct, StructEmptyError, u32 } from "@yume-chan/struct";

import type { Adb } from "../adb.js";

const Version =
/* #__PURE__ */
new Struct({ littleEndian: true }).uint32("version");
const Version = new Struct({ version: u32 }, { littleEndian: true });

export const AdbFrameBufferV1 =
/* #__PURE__ */
new Struct({ littleEndian: true })
.uint32("bpp")
.uint32("size")
.uint32("width")
.uint32("height")
.uint32("red_offset")
.uint32("red_length")
.uint32("blue_offset")
.uint32("blue_length")
.uint32("green_offset")
.uint32("green_length")
.uint32("alpha_offset")
.uint32("alpha_length")
.uint8Array("data", { lengthField: "size" });
export const AdbFrameBufferV1 = new Struct(
{
bpp: u32,
size: u32,
width: u32,
height: u32,
red_offset: u32,
red_length: u32,
blue_offset: u32,
blue_length: u32,
green_offset: u32,
green_length: u32,
alpha_offset: u32,
alpha_length: u32,
data: buffer("size"),
},
{ littleEndian: true },
);

export type AdbFrameBufferV1 = (typeof AdbFrameBufferV1)["TDeserializeResult"];
export type AdbFrameBufferV1 = StructValue<typeof AdbFrameBufferV1>;

export const AdbFrameBufferV2 =
/* #__PURE__ */
new Struct({ littleEndian: true })
.uint32("bpp")
.uint32("colorSpace")
.uint32("size")
.uint32("width")
.uint32("height")
.uint32("red_offset")
.uint32("red_length")
.uint32("blue_offset")
.uint32("blue_length")
.uint32("green_offset")
.uint32("green_length")
.uint32("alpha_offset")
.uint32("alpha_length")
.uint8Array("data", { lengthField: "size" });
export const AdbFrameBufferV2 = new Struct(
{
bpp: u32,
colorSpace: u32,
size: u32,
width: u32,
height: u32,
red_offset: u32,
red_length: u32,
blue_offset: u32,
blue_length: u32,
green_offset: u32,
green_length: u32,
alpha_offset: u32,
alpha_length: u32,
data: buffer("size"),
},
{ littleEndian: true },
);

export type AdbFrameBufferV2 = (typeof AdbFrameBufferV2)["TDeserializeResult"];
export type AdbFrameBufferV2 = StructValue<typeof AdbFrameBufferV2>;

/**
* ADB uses 8 int32 fields to describe bit depths
Expand Down Expand Up @@ -99,9 +102,9 @@ export async function framebuffer(adb: Adb): Promise<AdbFrameBuffer> {
switch (version) {
case 1:
// TODO: AdbFrameBuffer: does all v1 responses uses the same color space? Add it so the command returns same format for all versions.
return AdbFrameBufferV1.deserialize(stream);
return await AdbFrameBufferV1.deserialize(stream);
case 2:
return AdbFrameBufferV2.deserialize(stream);
return await AdbFrameBufferV2.deserialize(stream);
default:
throw new AdbFrameBufferUnsupportedVersionError(version);
}
Expand Down
36 changes: 26 additions & 10 deletions libraries/adb/src/commands/reverse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// cspell: ignore killforward

import { BufferedReadableStream } from "@yume-chan/stream-extra";
import Struct, { ExactReadableEndedError, encodeUtf8 } from "@yume-chan/struct";
import {
ExactReadableEndedError,
Struct,
encodeUtf8,
string,
} from "@yume-chan/struct";

import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
import { hexToNumber, sequenceEqual } from "../utils/index.js";
Expand All @@ -14,11 +19,21 @@ export interface AdbForwardListener {
remoteName: string;
}

const AdbReverseStringResponse =
/* #__PURE__ */
new Struct()
.string("length", { length: 4 })
.string("content", { lengthField: "length", lengthFieldRadix: 16 });
const AdbReverseStringResponse = new Struct(
{
length: string(4),
content: string({
field: "length",
convert(value: string) {
return Number.parseInt(value);
},
back(value) {
return value.toString(16).padStart(4, "0");
},
}),
},
{ littleEndian: true },
);

export class AdbReverseError extends Error {
constructor(message: string) {
Expand All @@ -35,9 +50,9 @@ export class AdbReverseNotSupportedError extends AdbReverseError {
}
}

const AdbReverseErrorResponse =
/* #__PURE__ */
new Struct().concat(AdbReverseStringResponse).postDeserialize((value) => {
const AdbReverseErrorResponse = new Struct(AdbReverseStringResponse.fields, {
littleEndian: true,
postDeserialize: (value) => {
// https://issuetracker.google.com/issues/37066218
// ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over Wi-Fi),
// and returns this confusing "more than one device/emulator" error.
Expand All @@ -46,7 +61,8 @@ const AdbReverseErrorResponse =
} else {
throw new AdbReverseError(value.content);
}
});
},
});

// Like `hexToNumber`, it's much faster than first converting `buffer` to a string
function decimalToNumber(buffer: Uint8Array) {
Expand Down
12 changes: 12 additions & 0 deletions libraries/adb/src/commands/subprocess/protocols/shell.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ async function assertResolves<T>(promise: Promise<T>, expected: T) {
return assert.deepStrictEqual(await promise, expected);
}

describe("AdbShellProtocolPacket", () => {
it("should serialize", () => {
assert.deepStrictEqual(
AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.Stdout,
data: new Uint8Array([1, 2, 3, 4]),
}),
new Uint8Array([1, 4, 0, 0, 0, 1, 2, 3, 4]),
);
});
});

describe("AdbSubprocessShellProtocol", () => {
describe("`stdout` and `stderr`", () => {
it("should parse data from `socket", () => {
Expand Down
21 changes: 11 additions & 10 deletions libraries/adb/src/commands/subprocess/protocols/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
StructDeserializeStream,
WritableStream,
} from "@yume-chan/stream-extra";
import type { StructValueType } from "@yume-chan/struct";
import Struct, { placeholder } from "@yume-chan/struct";
import type { StructValue } from "@yume-chan/struct";
import { Struct, buffer, u32, u8 } from "@yume-chan/struct";

import type { Adb, AdbSocket } from "../../../adb.js";
import { AdbFeature } from "../../../features.js";
Expand All @@ -32,14 +32,15 @@ export type AdbShellProtocolId =
(typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId];

// This packet format is used in both directions.
export const AdbShellProtocolPacket =
/* #__PURE__ */
new Struct({ littleEndian: true })
.uint8("id", placeholder<AdbShellProtocolId>())
.uint32("length")
.uint8Array("data", { lengthField: "length" });

type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>;
export const AdbShellProtocolPacket = new Struct(
{
id: u8.as<AdbShellProtocolId>(),
data: buffer(u32),
},
{ littleEndian: true },
);

type AdbShellProtocolPacket = StructValue<typeof AdbShellProtocolPacket>;

/**
* Shell v2 a.k.a Shell Protocol
Expand Down
12 changes: 6 additions & 6 deletions libraries/adb/src/commands/subprocess/protocols/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
ReadableStream,
WritableStream,
} from "@yume-chan/stream-extra";
import type { ValueOrPromise } from "@yume-chan/struct";
import type { MaybePromiseLike } from "@yume-chan/struct";

import type { Adb, AdbSocket } from "../../../adb.js";

Expand Down Expand Up @@ -40,23 +40,23 @@ export interface AdbSubprocessProtocol {
* Some `AdbSubprocessProtocol`s may not support resizing
* and will ignore calls to this method.
*/
resize(rows: number, cols: number): ValueOrPromise<void>;
resize(rows: number, cols: number): MaybePromiseLike<void>;

/**
* Kills the current process.
*/
kill(): ValueOrPromise<void>;
kill(): MaybePromiseLike<void>;
}

export interface AdbSubprocessProtocolConstructor {
/** Returns `true` if the `adb` instance supports this shell */
isSupported(adb: Adb): ValueOrPromise<boolean>;
isSupported(adb: Adb): MaybePromiseLike<boolean>;

/** Spawns an executable in PTY (interactive) mode. */
pty(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
pty(adb: Adb, command: string): MaybePromiseLike<AdbSubprocessProtocol>;

/** Spawns an executable and pipe the output. */
raw(adb: Adb, command: string): ValueOrPromise<AdbSubprocessProtocol>;
raw(adb: Adb, command: string): MaybePromiseLike<AdbSubprocessProtocol>;

/** Creates a new `AdbShell` by attaching to an exist `AdbSocket` */
new (socket: AdbSocket): AdbSubprocessProtocol;
Expand Down
Loading

0 comments on commit d50a170

Please sign in to comment.