refactor: code cleanup

This commit is contained in:
Simon Chan 2024-06-02 01:55:26 +08:00
parent c440e83828
commit 721b6c0da6
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
68 changed files with 1161 additions and 1036 deletions

View file

@ -40,13 +40,13 @@
},
"devDependencies": {
"@jest/globals": "^30.0.0-alpha.4",
"@types/node": "^20.12.12",
"@types/node": "^20.13.0",
"@yume-chan/eslint-config": "workspace:^1.0.0",
"@yume-chan/tsconfig": "workspace:^1.0.0",
"cross-env": "^7.0.3",
"jest": "^30.0.0-alpha.4",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"prettier": "^3.3.0",
"ts-jest": "^29.1.4",
"typescript": "^5.4.5"
}
}

View file

@ -5,7 +5,7 @@ import { BufferedReadableStream } from "@yume-chan/stream-extra";
import Struct, { ExactReadableEndedError, encodeUtf8 } from "@yume-chan/struct";
import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
import { hexToNumber } from "../utils/index.js";
import { hexToNumber, sequenceEqual } from "../utils/index.js";
export interface AdbForwardListener {
deviceSerial: string;
@ -82,10 +82,8 @@ export class AdbReverseCommand extends AutoDisposable {
const stream = await this.createBufferedStream(service);
const response = await stream.readExactly(4);
for (let i = 0; i < 4; i += 1) {
if (response[i] !== OKAY[i]) {
await AdbReverseErrorResponse.deserialize(stream);
}
if (!sequenceEqual(response, OKAY)) {
await AdbReverseErrorResponse.deserialize(stream);
}
return stream;

View file

@ -48,7 +48,7 @@ export async function adbSyncWriteRequest(
// `writable` is buffered, it copies inputs to an internal buffer,
// so don't concatenate headers and data here, that will be an unnecessary copy.
await writable.write(
AdbSyncNumberRequest.serialize({ id, arg: value.byteLength }),
AdbSyncNumberRequest.serialize({ id, arg: value.length }),
);
await writable.write(value);
}

View file

@ -4,9 +4,7 @@ import type {
StructLike,
StructValueType,
} from "@yume-chan/struct";
import Struct from "@yume-chan/struct";
import { decodeUtf8 } from "../../utils/index.js";
import Struct, { decodeUtf8 } from "@yume-chan/struct";
function encodeAsciiUnchecked(value: string): Uint8Array {
const result = new Uint8Array(value.length);

View file

@ -0,0 +1,46 @@
import { describe, expect, it } from "@jest/globals";
import { ReadableStream, WritableStream } from "@yume-chan/stream-extra";
import { AdbSyncSocket } from "./socket.js";
describe("AdbSyncSocket", () => {
describe("lock", () => {
it("should wait for the previous lock to be released", async () => {
const result: number[] = [];
const socket = new AdbSyncSocket(
{
service: "",
close() {},
closed: Promise.resolve(),
readable: new ReadableStream(),
writable: new WritableStream(),
},
1024,
);
const locked = await socket.lock();
result.push(1);
void socket.lock().then((locked) => {
result.push(3);
locked.release();
});
// Queue some microtasks to allow the above `then` callback run (although it shouldn't)
for (let i = 0; i < 10; i += 1) {
await Promise.resolve();
}
locked.release();
result.push(2);
// Queue some microtasks to allow the above `then` callback run
for (let i = 0; i < 10; i += 1) {
await Promise.resolve();
}
expect(result).toEqual([1, 2, 3]);
});
});
});

View file

@ -47,7 +47,7 @@ export class AdbTcpIpCommand extends AdbCommandBase {
async setPort(port: number): Promise<string> {
if (port <= 0) {
throw new Error(`Invalid port ${port}`);
throw new TypeError(`Invalid port ${port}`);
}
const output = await this.adb.createSocketAndWait(`tcpip:${port}`);

View file

@ -175,8 +175,8 @@ export function adbGeneratePublicKey(
output = new Uint8Array(outputLength);
outputType = "Uint8Array";
} else {
if (output.byteLength < outputLength) {
throw new Error("output buffer is too small");
if (output.length < outputLength) {
throw new TypeError("output buffer is too small");
}
outputType = "number";
@ -185,7 +185,7 @@ export function adbGeneratePublicKey(
const outputView = new DataView(
output.buffer,
output.byteOffset,
output.byteLength,
output.length,
);
let outputOffset = 0;

View file

@ -16,10 +16,9 @@ import {
Consumable,
WritableStream,
} from "@yume-chan/stream-extra";
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
import { EMPTY_UINT8_ARRAY, decodeUtf8, encodeUtf8 } from "@yume-chan/struct";
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
import type { AdbPacketData, AdbPacketInit } from "./packet.js";
import { AdbCommand, calculateChecksum } from "./packet.js";
@ -219,14 +218,14 @@ export class AdbPacketDispatcher implements Closeable {
#handleOkay(packet: AdbPacketData) {
let ackBytes: number;
if (this.options.initialDelayedAckBytes !== 0) {
if (packet.payload.byteLength !== 4) {
if (packet.payload.length !== 4) {
throw new Error(
"Invalid OKAY packet. Payload size should be 4",
);
}
ackBytes = getUint32LittleEndian(packet.payload, 0);
} else {
if (packet.payload.byteLength !== 0) {
if (packet.payload.length !== 0) {
throw new Error(
"Invalid OKAY packet. Payload size should be 0",
);
@ -429,8 +428,8 @@ export class AdbPacketDispatcher implements Closeable {
payload = encodeUtf8(payload);
}
if (payload.byteLength > this.options.maxPayloadSize) {
throw new Error("payload too large");
if (payload.length > this.options.maxPayloadSize) {
throw new TypeError("payload too large");
}
await Consumable.WritableStream.write(this.#writer, {

View file

@ -59,14 +59,14 @@ export class AdbPacketSerializeStream extends TransformStream<
transform: async (chunk, controller) => {
await chunk.tryConsume(async (chunk) => {
const init = chunk as AdbPacketInit & AdbPacketHeaderInit;
init.payloadLength = init.payload.byteLength;
init.payloadLength = init.payload.length;
await Consumable.ReadableStream.enqueue(
controller,
AdbPacketHeader.serialize(init, headerBuffer),
);
if (init.payload.byteLength) {
if (init.payloadLength) {
// USB protocol preserves packet boundaries,
// so we must write payload separately as native ADB does,
// otherwise the read operation on device will fail.

View file

@ -110,7 +110,7 @@ export class AdbDaemonSocketController
}
async #writeChunk(data: Uint8Array, signal: AbortSignal) {
const length = data.byteLength;
const length = data.length;
while (this.#availableWriteBytes < length) {
// Only one lock is required because Web Streams API guarantees
// that `write` is not reentrant.

View file

@ -215,11 +215,10 @@ export class AdbDaemonTransport implements AdbTransport {
)
.then(
() => {
if (resolver.state === "running") {
resolver.reject(
new Error("Connection closed unexpectedly"),
);
}
// If `resolver` is already settled, call `reject` won't do anything.
resolver.reject(
new Error("Connection closed unexpectedly"),
);
},
(e) => {
resolver.reject(e);
@ -333,7 +332,7 @@ export class AdbDaemonTransport implements AdbTransport {
if (features.includes(AdbFeature.DelayedAck)) {
if (initialDelayedAckBytes <= 0) {
throw new Error(
throw new TypeError(
"`initialDelayedAckBytes` must be greater than 0 when DelayedAck feature is enabled.",
);
}

View file

@ -23,24 +23,15 @@ import {
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
import { AdbBanner } from "../banner.js";
import type { AdbFeature } from "../features.js";
import { NOOP, hexToNumber, write4HexDigits } from "../utils/index.js";
import {
NOOP,
hexToNumber,
sequenceEqual,
write4HexDigits,
} from "../utils/index.js";
import { AdbServerTransport } from "./transport.js";
function sequenceEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i += 1) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
const OKAY = encodeUtf8("OKAY");
const FAIL = encodeUtf8("FAIL");
@ -108,8 +99,8 @@ class AdbServerStream {
const response = await this.readExactly(4);
if (sequenceEqual(response, OKAY)) {
// `OKAY` is followed by data length and data
// But different services want to read the data differently
// So we don't read the data here
// But different services want to parse the data differently
// So don't read the data here
return;
}
@ -421,7 +412,7 @@ export class AdbServerClient {
if ("tcp" in device) {
return `host-local:${command}`;
}
throw new Error("Invalid device selector");
throw new TypeError("Invalid device selector");
}
/**
@ -507,7 +498,7 @@ export class AdbServerClient {
} else if ("tcp" in device) {
switchService = `host:tport:local`;
} else {
throw new Error("Invalid device selector");
throw new TypeError("Invalid device selector");
}
const connection = await this.createConnection(switchService);
@ -576,7 +567,7 @@ export class AdbServerClient {
} else if ("tcp" in device) {
type = "local";
} else {
throw new Error("Invalid device selector");
throw new TypeError("Invalid device selector");
}
// `waitFor` can't use `connectDevice`, because the device

View file

@ -66,7 +66,7 @@ export function encodeBase64(
return output;
} else {
if (output.length < outputLength) {
throw new Error("output buffer is too small");
throw new TypeError("output buffer is too small");
}
output = output.subarray(0, outputLength);
@ -124,7 +124,7 @@ export function encodeBase64(
// Input is in the middle of output,
// It's not possible to read either the first or the last three bytes
// before they are overwritten by the output.
throw new Error("input and output cannot overlap");
throw new TypeError("input and output cannot overlap");
}
return outputLength;

View file

@ -1,45 +0,0 @@
import { PromiseResolver } from "@yume-chan/async";
import type { Disposable } from "@yume-chan/event";
interface WaitEntry {
condition: () => boolean;
resolver: PromiseResolver<void>;
}
export class ConditionalVariable implements Disposable {
#locked = false;
readonly #queue: WaitEntry[] = [];
wait(condition: () => boolean): Promise<void> {
if (!this.#locked) {
this.#locked = true;
if (this.#queue.length === 0 && condition()) {
return Promise.resolve();
}
}
const resolver = new PromiseResolver<void>();
this.#queue.push({ condition, resolver });
return resolver.promise;
}
notifyOne() {
const entry = this.#queue.shift();
if (entry) {
if (entry.condition()) {
entry.resolver.resolve();
}
} else {
this.#locked = false;
}
}
dispose(): void {
for (const item of this.#queue) {
item.resolver.reject(
new Error("The ConditionalVariable has been disposed"),
);
}
this.#queue.length = 0;
}
}

View file

@ -1,6 +1,6 @@
function hexCharToNumber(char: number) {
if (char < 48) {
throw new Error(`Invalid hex char ${char}`);
throw new TypeError(`Invalid hex char ${char}`);
}
if (char < 58) {
// 0-9
@ -8,7 +8,7 @@ function hexCharToNumber(char: number) {
}
if (char < 65) {
throw new Error(`Invalid hex char ${char}`);
throw new TypeError(`Invalid hex char ${char}`);
}
if (char < 71) {
// A-F
@ -16,14 +16,14 @@ function hexCharToNumber(char: number) {
}
if (char < 97) {
throw new Error(`Invalid hex char ${char}`);
throw new TypeError(`Invalid hex char ${char}`);
}
if (char < 103) {
// a-f
return char - 87;
}
throw new Error(`Invalid hex char ${char}`);
throw new TypeError(`Invalid hex char ${char}`);
}
// It's 22x faster than converting `data` to string then `Number.parseInt`

View file

@ -1,6 +1,6 @@
export { decodeUtf8, encodeUtf8 } from "@yume-chan/struct";
export * from "./auto-reset-event.js";
export * from "./base64.js";
export * from "./conditional-variable.js";
export * from "./hex.js";
export * from "./no-op.js";
export * from "./sequence-equal.js";

View file

@ -0,0 +1,13 @@
export function sequenceEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i += 1) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}