Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 4fc3e81b authored by Simon Chan's avatar Simon Chan
Browse files

refactor: simplify shell protocol

parent b920c72e
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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();
+21 −91
Original line number Diff line number Diff line
@@ -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";
@@ -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
 *
@@ -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() {
@@ -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.#writer = this.#socket.writable.getWriter();

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

        this.#socketWriter = multiplexer.createWriteable().getWriter();
                chunk.consume();
            },
        });
    }

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

    kill() {
+1 −1
Original line number Diff line number Diff line
@@ -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;
    }