mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-06 03:50:18 +02:00
parent
2743a9088f
commit
2d1cf1fafa
3 changed files with 84 additions and 42 deletions
|
@ -99,6 +99,11 @@ class Uint8ArrayExactReadable implements ExactReadable {
|
||||||
export class AdbDaemonWebUsbConnection
|
export class AdbDaemonWebUsbConnection
|
||||||
implements ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
|
implements ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
|
||||||
{
|
{
|
||||||
|
#device: AdbDaemonWebUsbDevice;
|
||||||
|
get device() {
|
||||||
|
return this.#device;
|
||||||
|
}
|
||||||
|
|
||||||
#readable: ReadableStream<AdbPacketData>;
|
#readable: ReadableStream<AdbPacketData>;
|
||||||
get readable() {
|
get readable() {
|
||||||
return this.#readable;
|
return this.#readable;
|
||||||
|
@ -110,11 +115,13 @@ export class AdbDaemonWebUsbConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
device: USBDevice,
|
device: AdbDaemonWebUsbDevice,
|
||||||
inEndpoint: USBEndpoint,
|
inEndpoint: USBEndpoint,
|
||||||
outEndpoint: USBEndpoint,
|
outEndpoint: USBEndpoint,
|
||||||
usbManager: USB,
|
usbManager: USB,
|
||||||
) {
|
) {
|
||||||
|
this.#device = device;
|
||||||
|
|
||||||
let closed = false;
|
let closed = false;
|
||||||
|
|
||||||
const duplex = new DuplexStreamFactory<
|
const duplex = new DuplexStreamFactory<
|
||||||
|
@ -124,7 +131,7 @@ export class AdbDaemonWebUsbConnection
|
||||||
close: async () => {
|
close: async () => {
|
||||||
try {
|
try {
|
||||||
closed = true;
|
closed = true;
|
||||||
await device.close();
|
await device.raw.close();
|
||||||
} catch {
|
} catch {
|
||||||
/* device may have already disconnected */
|
/* device may have already disconnected */
|
||||||
}
|
}
|
||||||
|
@ -139,7 +146,7 @@ export class AdbDaemonWebUsbConnection
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleUsbDisconnect(e: USBConnectionEvent) {
|
function handleUsbDisconnect(e: USBConnectionEvent) {
|
||||||
if (e.device === device) {
|
if (e.device === device.raw) {
|
||||||
duplex.dispose().catch(unreachable);
|
duplex.dispose().catch(unreachable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,37 +157,63 @@ export class AdbDaemonWebUsbConnection
|
||||||
new ReadableStream<AdbPacketData>({
|
new ReadableStream<AdbPacketData>({
|
||||||
async pull(controller) {
|
async pull(controller) {
|
||||||
try {
|
try {
|
||||||
// The `length` argument in `transferIn` must not be smaller than what the device sent,
|
while (true) {
|
||||||
// otherwise it will return `babble` status without any data.
|
// The `length` argument in `transferIn` must not be smaller than what the device sent,
|
||||||
// ADB daemon sends each packet in two parts, the 24-byte header and the payload.
|
// otherwise it will return `babble` status without any data.
|
||||||
const result = await device.transferIn(
|
// ADB daemon sends each packet in two parts, the 24-byte header and the payload.
|
||||||
inEndpoint.endpointNumber,
|
const result = await device.raw.transferIn(
|
||||||
24,
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: webusb: handle `babble` by discarding the data and receive again
|
|
||||||
|
|
||||||
// Per spec, the `result.data` always covers the whole `buffer`.
|
|
||||||
const buffer = new Uint8Array(result.data!.buffer);
|
|
||||||
const stream = new Uint8ArrayExactReadable(buffer);
|
|
||||||
|
|
||||||
// Add `payload` field to its type, it's assigned below.
|
|
||||||
const packet = AdbPacketHeader.deserialize(
|
|
||||||
stream,
|
|
||||||
) as AdbPacketHeader & { payload: Uint8Array };
|
|
||||||
if (packet.payloadLength !== 0) {
|
|
||||||
const result = await device.transferIn(
|
|
||||||
inEndpoint.endpointNumber,
|
inEndpoint.endpointNumber,
|
||||||
packet.payloadLength,
|
24,
|
||||||
);
|
);
|
||||||
packet.payload = new Uint8Array(
|
|
||||||
result.data!.buffer,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
packet.payload = EMPTY_UINT8_ARRAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.enqueue(packet);
|
// Maximum payload size is 1MB, so reading 1MB data will always success,
|
||||||
|
// and always discards all lingering data.
|
||||||
|
// FIXME: Chrome on Windows doesn't support babble status. See the HACK below.
|
||||||
|
if (result.status === "babble") {
|
||||||
|
await device.raw.transferIn(
|
||||||
|
inEndpoint.endpointNumber,
|
||||||
|
1024 * 1024,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per spec, the `result.data` always covers the whole `buffer`.
|
||||||
|
const buffer = new Uint8Array(result.data!.buffer);
|
||||||
|
const stream = new Uint8ArrayExactReadable(buffer);
|
||||||
|
|
||||||
|
// Add `payload` field to its type, it's assigned below.
|
||||||
|
const packet = AdbPacketHeader.deserialize(
|
||||||
|
stream,
|
||||||
|
) as AdbPacketHeader & { payload: Uint8Array };
|
||||||
|
if (packet.payloadLength !== 0) {
|
||||||
|
// HACK: Chrome on Windows doesn't support babble status,
|
||||||
|
// so maybe we are not actually reading an ADB packet header.
|
||||||
|
// Currently the maximum payload size is 1MB,
|
||||||
|
// so if the payload length is larger than that,
|
||||||
|
// try to discard the data and receive again.
|
||||||
|
// https://crbug.com/1314358
|
||||||
|
if (packet.payloadLength > 1024 * 1024) {
|
||||||
|
await device.raw.transferIn(
|
||||||
|
inEndpoint.endpointNumber,
|
||||||
|
1024 * 1024,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await device.raw.transferIn(
|
||||||
|
inEndpoint.endpointNumber,
|
||||||
|
packet.payloadLength,
|
||||||
|
);
|
||||||
|
packet.payload = new Uint8Array(
|
||||||
|
result.data!.buffer,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
packet.payload = EMPTY_UINT8_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.enqueue(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// On Windows, disconnecting the device will cause `NetworkError` to be thrown,
|
// On Windows, disconnecting the device will cause `NetworkError` to be thrown,
|
||||||
// even before the `disconnect` event is fired.
|
// even before the `disconnect` event is fired.
|
||||||
|
@ -212,7 +245,7 @@ export class AdbDaemonWebUsbConnection
|
||||||
new ConsumableWritableStream({
|
new ConsumableWritableStream({
|
||||||
write: async (chunk) => {
|
write: async (chunk) => {
|
||||||
try {
|
try {
|
||||||
await device.transferOut(
|
await device.raw.transferOut(
|
||||||
outEndpoint.endpointNumber,
|
outEndpoint.endpointNumber,
|
||||||
chunk,
|
chunk,
|
||||||
);
|
);
|
||||||
|
@ -225,7 +258,7 @@ export class AdbDaemonWebUsbConnection
|
||||||
zeroMask &&
|
zeroMask &&
|
||||||
(chunk.byteLength & zeroMask) === 0
|
(chunk.byteLength & zeroMask) === 0
|
||||||
) {
|
) {
|
||||||
await device.transferOut(
|
await device.raw.transferOut(
|
||||||
outEndpoint.endpointNumber,
|
outEndpoint.endpointNumber,
|
||||||
EMPTY_UINT8_ARRAY,
|
EMPTY_UINT8_ARRAY,
|
||||||
);
|
);
|
||||||
|
@ -281,9 +314,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
||||||
* Claim the device and create a pair of `AdbPacket` streams to the ADB interface.
|
* Claim the device and create a pair of `AdbPacket` streams to the ADB interface.
|
||||||
* @returns The pair of `AdbPacket` streams.
|
* @returns The pair of `AdbPacket` streams.
|
||||||
*/
|
*/
|
||||||
async connect(): Promise<
|
async connect(): Promise<AdbDaemonWebUsbConnection> {
|
||||||
ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
|
|
||||||
> {
|
|
||||||
if (!this.#raw.opened) {
|
if (!this.#raw.opened) {
|
||||||
await this.#raw.open();
|
await this.#raw.open();
|
||||||
}
|
}
|
||||||
|
@ -319,7 +350,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
||||||
alternate.endpoints,
|
alternate.endpoints,
|
||||||
);
|
);
|
||||||
return new AdbDaemonWebUsbConnection(
|
return new AdbDaemonWebUsbConnection(
|
||||||
this.#raw,
|
this,
|
||||||
inEndpoint,
|
inEndpoint,
|
||||||
outEndpoint,
|
outEndpoint,
|
||||||
this.#usbManager,
|
this.#usbManager,
|
||||||
|
|
|
@ -27,9 +27,14 @@ import { AdbCommand, calculateChecksum } from "./packet.js";
|
||||||
|
|
||||||
export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001;
|
export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001;
|
||||||
|
|
||||||
|
export type AdbDaemonConnection = ReadableWritablePair<
|
||||||
|
AdbPacketData,
|
||||||
|
Consumable<AdbPacketInit>
|
||||||
|
>;
|
||||||
|
|
||||||
interface AdbDaemonAuthenticationOptions {
|
interface AdbDaemonAuthenticationOptions {
|
||||||
serial: string;
|
serial: string;
|
||||||
connection: ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>;
|
connection: AdbDaemonConnection;
|
||||||
credentialStore: AdbCredentialStore;
|
credentialStore: AdbCredentialStore;
|
||||||
authenticators?: AdbAuthenticator[];
|
authenticators?: AdbAuthenticator[];
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +45,7 @@ interface AdbDaemonAuthenticationOptions {
|
||||||
|
|
||||||
interface AdbDaemonSocketConnectorConstructionOptions {
|
interface AdbDaemonSocketConnectorConstructionOptions {
|
||||||
serial: string;
|
serial: string;
|
||||||
connection: ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>;
|
connection: AdbDaemonConnection;
|
||||||
version: number;
|
version: number;
|
||||||
maxPayloadSize: number;
|
maxPayloadSize: number;
|
||||||
banner: string;
|
banner: string;
|
||||||
|
@ -94,9 +99,8 @@ export class AdbDaemonTransport implements AdbTransport {
|
||||||
resolver.resolve(decodeUtf8(packet.payload));
|
resolver.resolve(decodeUtf8(packet.payload));
|
||||||
break;
|
break;
|
||||||
case AdbCommand.Auth: {
|
case AdbCommand.Auth: {
|
||||||
const response = await authProcessor.process(
|
const response =
|
||||||
packet,
|
await authProcessor.process(packet);
|
||||||
);
|
|
||||||
await sendPacket(response);
|
await sendPacket(response);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -193,6 +197,11 @@ export class AdbDaemonTransport implements AdbTransport {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#connection: AdbDaemonConnection;
|
||||||
|
get connection() {
|
||||||
|
return this.#connection;
|
||||||
|
}
|
||||||
|
|
||||||
readonly #dispatcher: AdbPacketDispatcher;
|
readonly #dispatcher: AdbPacketDispatcher;
|
||||||
|
|
||||||
#serial: string;
|
#serial: string;
|
||||||
|
@ -228,6 +237,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
||||||
preserveConnection,
|
preserveConnection,
|
||||||
}: AdbDaemonSocketConnectorConstructionOptions) {
|
}: AdbDaemonSocketConnectorConstructionOptions) {
|
||||||
this.#serial = serial;
|
this.#serial = serial;
|
||||||
|
this.#connection = connection;
|
||||||
this.#banner = AdbBanner.parse(banner);
|
this.#banner = AdbBanner.parse(banner);
|
||||||
|
|
||||||
let calculateChecksum: boolean;
|
let calculateChecksum: boolean;
|
||||||
|
|
|
@ -9,3 +9,4 @@ export * from "./logcat.js";
|
||||||
export * from "./overlay-display.js";
|
export * from "./overlay-display.js";
|
||||||
export * from "./pm.js";
|
export * from "./pm.js";
|
||||||
export * from "./settings.js";
|
export * from "./settings.js";
|
||||||
|
export * from "./string-format.js";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue