Skip to content

Commit

Permalink
refactor: simplify shell protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
yume-chan committed Dec 17, 2023
1 parent b920c72 commit 4fc3e81
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 93 deletions.
2 changes: 1 addition & 1 deletion libraries/adb/src/commands/reverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const AdbReverseErrorResponse = new Struct()
.concat(AdbReverseStringResponse)
.postDeserialize((value) => {
// https://issuetracker.google.com/issues/37066218
// ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over WiFi),
// 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.
if (value.content === "more than one device/emulator") {
throw new AdbReverseNotSupportedError();
Expand Down
112 changes: 21 additions & 91 deletions libraries/adb/src/commands/subprocess/protocols/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import type {
WritableStreamDefaultWriter,
} from "@yume-chan/stream-extra";
import {
ConsumableTransformStream,
ConsumableWritableStream,
PushReadableStream,
StructDeserializeStream,
WritableStream,
pipeFrom,
} from "@yume-chan/stream-extra";
import type { StructValueType } from "@yume-chan/struct";
import Struct, { placeholder } from "@yume-chan/struct";
Expand All @@ -37,68 +35,8 @@ const AdbShellProtocolPacket = new Struct({ littleEndian: true })
.uint32("length")
.uint8Array("data", { lengthField: "length" });

type AdbShellProtocolPacketInit = (typeof AdbShellProtocolPacket)["TInit"];

type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>;

class StdinSerializeStream extends ConsumableTransformStream<
Uint8Array,
AdbShellProtocolPacketInit
> {
constructor() {
super({
async transform(chunk, controller) {
await controller.enqueue({
id: AdbShellProtocolId.Stdin,
data: chunk,
});
},
flush() {
// TODO: AdbShellSubprocessProtocol: support closing stdin
},
});
}
}

class MultiplexStream<T> {
#readable: PushReadableStream<T>;
#readableController!: PushReadableStreamController<T>;
get readable() {
return this.#readable;
}

#activeCount = 0;

constructor() {
this.#readable = new PushReadableStream((controller) => {
this.#readableController = controller;
});
}

createWriteable() {
return new WritableStream<T>({
start: () => {
this.#activeCount += 1;
},
write: async (chunk) => {
await this.#readableController.enqueue(chunk);
},
abort: () => {
this.#activeCount -= 1;
if (this.#activeCount === 0) {
this.#readableController.close();
}
},
close: () => {
this.#activeCount -= 1;
if (this.#activeCount === 0) {
this.#readableController.close();
}
},
});
}
}

/**
* Shell v2 a.k.a Shell Protocol
*
Expand Down Expand Up @@ -126,9 +64,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
}

readonly #socket: AdbSocket;
#socketWriter: WritableStreamDefaultWriter<
Consumable<AdbShellProtocolPacketInit>
>;
#writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;

#stdin: WritableStream<Consumable<Uint8Array>>;
get stdin() {
Expand Down Expand Up @@ -207,39 +143,33 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
},
);

const multiplexer = new MultiplexStream<
Consumable<AdbShellProtocolPacketInit>
>();
void multiplexer.readable
.pipeThrough(
new ConsumableTransformStream({
async transform(chunk, controller) {
await controller.enqueue(
AdbShellProtocolPacket.serialize(chunk),
);
},
}),
)
.pipeTo(socket.writable);

this.#stdin = pipeFrom(
multiplexer.createWriteable(),
new StdinSerializeStream(),
);
this.#writer = this.#socket.writable.getWriter();

this.#socketWriter = multiplexer.createWriteable().getWriter();
this.#stdin = new WritableStream<Consumable<Uint8Array>>({
write: async (chunk) => {
await ConsumableWritableStream.write(
this.#writer,
AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.Stdin,
data: chunk.value,
}),
);
chunk.consume();
},
});
}

async resize(rows: number, cols: number) {
await ConsumableWritableStream.write(this.#socketWriter, {
id: AdbShellProtocolId.WindowSizeChange,
data: encodeUtf8(
await ConsumableWritableStream.write(
this.#writer,
AdbShellProtocolPacket.serialize({
id: AdbShellProtocolId.WindowSizeChange,
// 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 unused, so always sending `0` should be fine.
`${rows}x${cols},0x0\0`,
),
});
data: encodeUtf8(`${rows}x${cols},0x0\0`),
}),
);
}

kill() {
Expand Down
2 changes: 1 addition & 1 deletion libraries/adb/src/commands/subprocess/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function escapeArg(s: string) {
break;
}
result += s.substring(base, found);
// a'b becomes a'\'b
// a'b becomes a'\'b (the backslash is not a escape character)
result += String.raw`'\''`;
base = found + 1;
}
Expand Down

0 comments on commit 4fc3e81

Please sign in to comment.