@yume-chan/adb 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +51 -0
- package/CHANGELOG.md +24 -1
- package/LICENSE +1 -1
- package/esm/adb.d.ts +6 -3
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +11 -4
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts.map +1 -1
- package/esm/banner.js +1 -0
- package/esm/banner.js.map +1 -1
- package/esm/commands/power.d.ts.map +1 -1
- package/esm/commands/power.js +6 -5
- package/esm/commands/power.js.map +1 -1
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +23 -11
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/command.js +3 -3
- package/esm/commands/subprocess/command.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +2 -2
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts +3 -3
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +14 -69
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/protocols/types.d.ts +2 -2
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
- package/esm/commands/subprocess/utils.js +1 -1
- package/esm/commands/subprocess/utils.js.map +1 -1
- package/esm/commands/sync/list.d.ts +2 -5
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +2 -4
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts +1 -4
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +2 -2
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.d.ts +4 -7
- package/esm/commands/sync/push.d.ts.map +1 -1
- package/esm/commands/sync/push.js +3 -6
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.d.ts +13 -18
- package/esm/commands/sync/request.d.ts.map +1 -1
- package/esm/commands/sync/request.js +23 -28
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +18 -12
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +39 -17
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +2 -2
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +10 -5
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.d.ts +0 -3
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +0 -2
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +4 -3
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +9 -7
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.js +1 -1
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/daemon/auth.js +1 -1
- package/esm/daemon/auth.js.map +1 -1
- package/esm/daemon/crypto.d.ts +3 -2
- package/esm/daemon/crypto.d.ts.map +1 -1
- package/esm/daemon/crypto.js +56 -49
- package/esm/daemon/crypto.js.map +1 -1
- package/esm/daemon/dispatcher.d.ts +38 -6
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +86 -28
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +2 -2
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +13 -12
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +11 -6
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +42 -12
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +75 -11
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +60 -33
- package/esm/daemon/transport.js.map +1 -1
- package/esm/features.d.ts +2 -1
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +2 -1
- package/esm/features.js.map +1 -1
- package/esm/server/client.d.ts +128 -48
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +378 -145
- package/esm/server/client.js.map +1 -1
- package/esm/server/transport.d.ts +3 -0
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +30 -4
- package/esm/server/transport.js.map +1 -1
- package/esm/utils/base64.d.ts.map +1 -1
- package/esm/utils/base64.js +33 -22
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/hex.d.ts +1 -1
- package/esm/utils/hex.d.ts.map +1 -1
- package/esm/utils/hex.js +12 -14
- package/esm/utils/hex.js.map +1 -1
- package/esm/utils/index.d.ts +1 -1
- package/esm/utils/index.d.ts.map +1 -1
- package/esm/utils/index.js +1 -1
- package/esm/utils/index.js.map +1 -1
- package/esm/utils/sequence-equal.d.ts +2 -0
- package/esm/utils/sequence-equal.d.ts.map +1 -0
- package/esm/utils/sequence-equal.js +12 -0
- package/esm/utils/sequence-equal.js.map +1 -0
- package/package.json +13 -15
- package/src/adb.ts +22 -6
- package/src/banner.ts +1 -0
- package/src/commands/power.ts +7 -5
- package/src/commands/reverse.ts +31 -18
- package/src/commands/subprocess/command.ts +3 -3
- package/src/commands/subprocess/protocols/none.ts +2 -2
- package/src/commands/subprocess/protocols/shell.ts +21 -100
- package/src/commands/subprocess/protocols/types.ts +2 -2
- package/src/commands/subprocess/utils.ts +1 -1
- package/src/commands/sync/list.ts +2 -4
- package/src/commands/sync/pull.ts +2 -2
- package/src/commands/sync/push.ts +7 -10
- package/src/commands/sync/request.ts +32 -32
- package/src/commands/sync/response.ts +47 -20
- package/src/commands/sync/socket.ts +13 -8
- package/src/commands/sync/stat.ts +0 -2
- package/src/commands/sync/sync.ts +13 -9
- package/src/commands/tcpip.ts +1 -1
- package/src/daemon/auth.ts +1 -1
- package/src/daemon/crypto.ts +64 -55
- package/src/daemon/dispatcher.ts +175 -37
- package/src/daemon/packet.ts +21 -14
- package/src/daemon/socket.ts +62 -28
- package/src/daemon/transport.ts +145 -40
- package/src/features.ts +2 -1
- package/src/server/client.ts +472 -224
- package/src/server/transport.ts +32 -4
- package/src/utils/base64.ts +33 -22
- package/src/utils/hex.ts +16 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/sequence-equal.ts +13 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/esm/utils/conditional-variable.d.ts +0 -8
- package/esm/utils/conditional-variable.d.ts.map +0 -1
- package/esm/utils/conditional-variable.js +0 -34
- package/esm/utils/conditional-variable.js.map +0 -1
- package/src/utils/conditional-variable.ts +0 -45
package/src/daemon/dispatcher.ts
CHANGED
|
@@ -3,40 +3,83 @@ import {
|
|
|
3
3
|
PromiseResolver,
|
|
4
4
|
delay,
|
|
5
5
|
} from "@yume-chan/async";
|
|
6
|
+
import {
|
|
7
|
+
getUint32LittleEndian,
|
|
8
|
+
setUint32LittleEndian,
|
|
9
|
+
} from "@yume-chan/no-data-view";
|
|
6
10
|
import type {
|
|
7
|
-
Consumable,
|
|
8
11
|
ReadableWritablePair,
|
|
9
12
|
WritableStreamDefaultWriter,
|
|
10
13
|
} from "@yume-chan/stream-extra";
|
|
11
14
|
import {
|
|
12
15
|
AbortController,
|
|
13
|
-
|
|
16
|
+
Consumable,
|
|
14
17
|
WritableStream,
|
|
15
18
|
} from "@yume-chan/stream-extra";
|
|
16
|
-
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
|
19
|
+
import { EMPTY_UINT8_ARRAY, decodeUtf8, encodeUtf8 } from "@yume-chan/struct";
|
|
17
20
|
|
|
18
21
|
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
|
|
19
|
-
import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
|
|
20
22
|
|
|
21
23
|
import type { AdbPacketData, AdbPacketInit } from "./packet.js";
|
|
22
24
|
import { AdbCommand, calculateChecksum } from "./packet.js";
|
|
23
25
|
import { AdbDaemonSocketController } from "./socket.js";
|
|
24
26
|
|
|
25
27
|
export interface AdbPacketDispatcherOptions {
|
|
28
|
+
/**
|
|
29
|
+
* From Android 9.0, ADB stopped checking the checksum in packet header to improve performance.
|
|
30
|
+
*
|
|
31
|
+
* The value should be inferred from the device's ADB protocol version.
|
|
32
|
+
*/
|
|
26
33
|
calculateChecksum: boolean;
|
|
34
|
+
|
|
27
35
|
/**
|
|
28
|
-
* Before Android 9.0, ADB uses `char*` to parse service
|
|
36
|
+
* Before Android 9.0, ADB uses `char*` to parse service strings,
|
|
29
37
|
* thus requires a null character to terminate.
|
|
30
38
|
*
|
|
31
|
-
*
|
|
39
|
+
* The value should be inferred from the device's ADB protocol version.
|
|
40
|
+
* Usually it should have the same value as `calculateChecksum`, since they both changed
|
|
41
|
+
* in Android 9.0.
|
|
32
42
|
*/
|
|
33
43
|
appendNullToServiceString: boolean;
|
|
44
|
+
|
|
34
45
|
maxPayloadSize: number;
|
|
46
|
+
|
|
35
47
|
/**
|
|
36
|
-
* Whether to
|
|
48
|
+
* Whether to keep the `connection` open (don't call `writable.close` and `readable.cancel`)
|
|
49
|
+
* when `AdbPacketDispatcher.close` is called.
|
|
50
|
+
*
|
|
51
|
+
* @default false
|
|
37
52
|
*/
|
|
38
53
|
preserveConnection?: boolean | undefined;
|
|
39
|
-
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The number of bytes the device can send before receiving an ack packet.
|
|
57
|
+
* Using delayed ack can improve the throughput,
|
|
58
|
+
* especially when the device is connected over Wi-Fi (so the latency is higher).
|
|
59
|
+
*
|
|
60
|
+
* This must be the negotiated value between the client and device. If the device enabled
|
|
61
|
+
* delayed ack but the client didn't, the device will throw an error when the client sends
|
|
62
|
+
* the first `WRTE` packet. And vice versa.
|
|
63
|
+
*/
|
|
64
|
+
initialDelayedAckBytes: number;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* When set, the dispatcher will throw an error when
|
|
68
|
+
* one of the socket readable stalls for this amount of milliseconds.
|
|
69
|
+
*
|
|
70
|
+
* Because ADB is a multiplexed protocol, blocking one socket will also block all other sockets.
|
|
71
|
+
* It's important to always read from all sockets to prevent stalling.
|
|
72
|
+
*
|
|
73
|
+
* This option is helpful to detect bugs in the client code.
|
|
74
|
+
*
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
readTimeLimit?: number | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface SocketOpenResult {
|
|
81
|
+
remoteId: number;
|
|
82
|
+
availableWriteBytes: number;
|
|
40
83
|
}
|
|
41
84
|
|
|
42
85
|
/**
|
|
@@ -79,6 +122,10 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
79
122
|
options: AdbPacketDispatcherOptions,
|
|
80
123
|
) {
|
|
81
124
|
this.options = options;
|
|
125
|
+
// Don't allow negative values in dispatcher
|
|
126
|
+
if (this.options.initialDelayedAckBytes < 0) {
|
|
127
|
+
this.options.initialDelayedAckBytes = 0;
|
|
128
|
+
}
|
|
82
129
|
|
|
83
130
|
connection.readable
|
|
84
131
|
.pipeTo(
|
|
@@ -169,38 +216,104 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
169
216
|
}
|
|
170
217
|
|
|
171
218
|
#handleOkay(packet: AdbPacketData) {
|
|
172
|
-
|
|
219
|
+
let ackBytes: number;
|
|
220
|
+
if (this.options.initialDelayedAckBytes !== 0) {
|
|
221
|
+
if (packet.payload.length !== 4) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"Invalid OKAY packet. Payload size should be 4",
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
ackBytes = getUint32LittleEndian(packet.payload, 0);
|
|
227
|
+
} else {
|
|
228
|
+
if (packet.payload.length !== 0) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
"Invalid OKAY packet. Payload size should be 0",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
ackBytes = Infinity;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (
|
|
237
|
+
this.#initializers.resolve(packet.arg1, {
|
|
238
|
+
remoteId: packet.arg0,
|
|
239
|
+
availableWriteBytes: ackBytes,
|
|
240
|
+
} satisfies SocketOpenResult)
|
|
241
|
+
) {
|
|
173
242
|
// Device successfully created the socket
|
|
174
243
|
return;
|
|
175
244
|
}
|
|
176
245
|
|
|
177
246
|
const socket = this.#sockets.get(packet.arg1);
|
|
178
247
|
if (socket) {
|
|
179
|
-
//
|
|
180
|
-
socket.
|
|
248
|
+
// When delayed ack is enabled, `ackBytes` is a positive number represents
|
|
249
|
+
// how many bytes the device has received from this socket.
|
|
250
|
+
// When delayed ack is disabled, `ackBytes` is always `Infinity` represents
|
|
251
|
+
// the device has received last `WRTE` packet from the socket.
|
|
252
|
+
socket.ack(ackBytes);
|
|
181
253
|
return;
|
|
182
254
|
}
|
|
183
255
|
|
|
184
256
|
// Maybe the device is responding to a packet of last connection
|
|
185
257
|
// Tell the device to close the socket
|
|
186
|
-
void this.sendPacket(
|
|
258
|
+
void this.sendPacket(
|
|
259
|
+
AdbCommand.Close,
|
|
260
|
+
packet.arg1,
|
|
261
|
+
packet.arg0,
|
|
262
|
+
EMPTY_UINT8_ARRAY,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#sendOkay(localId: number, remoteId: number, ackBytes: number) {
|
|
267
|
+
let payload: Uint8Array;
|
|
268
|
+
if (this.options.initialDelayedAckBytes !== 0) {
|
|
269
|
+
// TODO: try reusing this buffer to reduce memory allocation
|
|
270
|
+
// However, that requires blocking reentrance of `sendOkay`, which might be more expensive
|
|
271
|
+
payload = new Uint8Array(4);
|
|
272
|
+
setUint32LittleEndian(payload, 0, ackBytes);
|
|
273
|
+
} else {
|
|
274
|
+
payload = EMPTY_UINT8_ARRAY;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return this.sendPacket(AdbCommand.Okay, localId, remoteId, payload);
|
|
187
278
|
}
|
|
188
279
|
|
|
189
280
|
async #handleOpen(packet: AdbPacketData) {
|
|
190
|
-
//
|
|
191
|
-
//
|
|
281
|
+
// Allocate a local ID for the socket from `#initializers`.
|
|
282
|
+
// `AsyncOperationManager` doesn't directly support returning the next ID,
|
|
283
|
+
// so use `add` + `resolve` to simulate this
|
|
192
284
|
const [localId] = this.#initializers.add<number>();
|
|
193
285
|
this.#initializers.resolve(localId, undefined);
|
|
194
286
|
|
|
195
287
|
const remoteId = packet.arg0;
|
|
288
|
+
let availableWriteBytes = packet.arg1;
|
|
196
289
|
let service = decodeUtf8(packet.payload);
|
|
290
|
+
// ADB Daemon still adds a null character to the service string
|
|
197
291
|
if (service.endsWith("\0")) {
|
|
198
292
|
service = service.substring(0, service.length - 1);
|
|
199
293
|
}
|
|
200
294
|
|
|
295
|
+
// Check remote delayed ack enablement is consistent with local
|
|
296
|
+
if (this.options.initialDelayedAckBytes === 0) {
|
|
297
|
+
if (availableWriteBytes !== 0) {
|
|
298
|
+
throw new Error("Invalid OPEN packet. arg1 should be 0");
|
|
299
|
+
}
|
|
300
|
+
availableWriteBytes = Infinity;
|
|
301
|
+
} else {
|
|
302
|
+
if (availableWriteBytes === 0) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
"Invalid OPEN packet. arg1 should be greater than 0",
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
201
309
|
const handler = this.#incomingSocketHandlers.get(service);
|
|
202
310
|
if (!handler) {
|
|
203
|
-
await this.sendPacket(
|
|
311
|
+
await this.sendPacket(
|
|
312
|
+
AdbCommand.Close,
|
|
313
|
+
0,
|
|
314
|
+
remoteId,
|
|
315
|
+
EMPTY_UINT8_ARRAY,
|
|
316
|
+
);
|
|
204
317
|
return;
|
|
205
318
|
}
|
|
206
319
|
|
|
@@ -210,14 +323,24 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
210
323
|
remoteId,
|
|
211
324
|
localCreated: false,
|
|
212
325
|
service,
|
|
326
|
+
availableWriteBytes,
|
|
213
327
|
});
|
|
214
328
|
|
|
215
329
|
try {
|
|
216
330
|
await handler(controller.socket);
|
|
217
331
|
this.#sockets.set(localId, controller);
|
|
218
|
-
await this
|
|
332
|
+
await this.#sendOkay(
|
|
333
|
+
localId,
|
|
334
|
+
remoteId,
|
|
335
|
+
this.options.initialDelayedAckBytes,
|
|
336
|
+
);
|
|
219
337
|
} catch (e) {
|
|
220
|
-
await this.sendPacket(
|
|
338
|
+
await this.sendPacket(
|
|
339
|
+
AdbCommand.Close,
|
|
340
|
+
0,
|
|
341
|
+
remoteId,
|
|
342
|
+
EMPTY_UINT8_ARRAY,
|
|
343
|
+
);
|
|
221
344
|
}
|
|
222
345
|
}
|
|
223
346
|
|
|
@@ -228,26 +351,33 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
228
351
|
}
|
|
229
352
|
|
|
230
353
|
let handled = false;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (this.options.debugSlowRead && !handled) {
|
|
234
|
-
throw new Error(
|
|
235
|
-
`packet for \`${socket.service}\` not handled in 5 seconds`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
}),
|
|
354
|
+
|
|
355
|
+
const promises: Promise<void>[] = [
|
|
239
356
|
(async () => {
|
|
240
357
|
await socket.enqueue(packet.payload);
|
|
241
|
-
await this
|
|
242
|
-
AdbCommand.Okay,
|
|
358
|
+
await this.#sendOkay(
|
|
243
359
|
packet.arg1,
|
|
244
360
|
packet.arg0,
|
|
361
|
+
packet.payload.length,
|
|
245
362
|
);
|
|
246
363
|
handled = true;
|
|
247
364
|
})(),
|
|
248
|
-
]
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
if (this.options.readTimeLimit) {
|
|
368
|
+
promises.push(
|
|
369
|
+
(async () => {
|
|
370
|
+
await delay(this.options.readTimeLimit!);
|
|
371
|
+
if (!handled) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
`readable of \`${socket.service}\` has stalled for ${this.options.readTimeLimit} milliseconds`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
})(),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
249
379
|
|
|
250
|
-
|
|
380
|
+
await Promise.race(promises);
|
|
251
381
|
}
|
|
252
382
|
|
|
253
383
|
async createSocket(service: string): Promise<AdbSocket> {
|
|
@@ -255,17 +385,24 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
255
385
|
service += "\0";
|
|
256
386
|
}
|
|
257
387
|
|
|
258
|
-
const [localId, initializer] =
|
|
259
|
-
|
|
388
|
+
const [localId, initializer] =
|
|
389
|
+
this.#initializers.add<SocketOpenResult>();
|
|
390
|
+
await this.sendPacket(
|
|
391
|
+
AdbCommand.Open,
|
|
392
|
+
localId,
|
|
393
|
+
this.options.initialDelayedAckBytes,
|
|
394
|
+
service,
|
|
395
|
+
);
|
|
260
396
|
|
|
261
|
-
// Fulfilled by `
|
|
262
|
-
const remoteId = await initializer;
|
|
397
|
+
// Fulfilled by `handleOkay`
|
|
398
|
+
const { remoteId, availableWriteBytes } = await initializer;
|
|
263
399
|
const controller = new AdbDaemonSocketController({
|
|
264
400
|
dispatcher: this,
|
|
265
401
|
localId,
|
|
266
402
|
remoteId,
|
|
267
403
|
localCreated: true,
|
|
268
404
|
service,
|
|
405
|
+
availableWriteBytes,
|
|
269
406
|
});
|
|
270
407
|
this.#sockets.set(localId, controller);
|
|
271
408
|
|
|
@@ -288,17 +425,18 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
288
425
|
command: AdbCommand,
|
|
289
426
|
arg0: number,
|
|
290
427
|
arg1: number,
|
|
291
|
-
|
|
428
|
+
// PERF: It's slightly faster to not use default parameter values
|
|
429
|
+
payload: string | Uint8Array,
|
|
292
430
|
): Promise<void> {
|
|
293
431
|
if (typeof payload === "string") {
|
|
294
432
|
payload = encodeUtf8(payload);
|
|
295
433
|
}
|
|
296
434
|
|
|
297
|
-
if (payload.
|
|
298
|
-
throw new
|
|
435
|
+
if (payload.length > this.options.maxPayloadSize) {
|
|
436
|
+
throw new TypeError("payload too large");
|
|
299
437
|
}
|
|
300
438
|
|
|
301
|
-
await
|
|
439
|
+
await Consumable.WritableStream.write(this.#writer, {
|
|
302
440
|
command,
|
|
303
441
|
arg0,
|
|
304
442
|
arg1,
|
package/src/daemon/packet.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Consumable, TransformStream } from "@yume-chan/stream-extra";
|
|
2
2
|
import Struct from "@yume-chan/struct";
|
|
3
3
|
|
|
4
4
|
export enum AdbCommand {
|
|
@@ -49,26 +49,33 @@ export function calculateChecksum(payload: Uint8Array): number {
|
|
|
49
49
|
return payload.reduce((result, item) => result + item, 0);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export class AdbPacketSerializeStream extends
|
|
53
|
-
AdbPacketInit
|
|
54
|
-
Uint8Array
|
|
52
|
+
export class AdbPacketSerializeStream extends TransformStream<
|
|
53
|
+
Consumable<AdbPacketInit>,
|
|
54
|
+
Consumable<Uint8Array>
|
|
55
55
|
> {
|
|
56
56
|
constructor() {
|
|
57
57
|
const headerBuffer = new Uint8Array(AdbPacketHeader.size);
|
|
58
58
|
super({
|
|
59
59
|
transform: async (chunk, controller) => {
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
await chunk.tryConsume(async (chunk) => {
|
|
61
|
+
const init = chunk as AdbPacketInit & AdbPacketHeaderInit;
|
|
62
|
+
init.payloadLength = init.payload.length;
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
await Consumable.ReadableStream.enqueue(
|
|
65
|
+
controller,
|
|
66
|
+
AdbPacketHeader.serialize(init, headerBuffer),
|
|
67
|
+
);
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
if (init.payloadLength) {
|
|
70
|
+
// USB protocol preserves packet boundaries,
|
|
71
|
+
// so we must write payload separately as native ADB does,
|
|
72
|
+
// otherwise the read operation on device will fail.
|
|
73
|
+
await Consumable.ReadableStream.enqueue(
|
|
74
|
+
controller,
|
|
75
|
+
init.payload,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
72
79
|
},
|
|
73
80
|
});
|
|
74
81
|
}
|
package/src/daemon/socket.ts
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
2
|
import type { Disposable } from "@yume-chan/event";
|
|
3
3
|
import type {
|
|
4
|
-
Consumable,
|
|
5
4
|
PushReadableStreamController,
|
|
6
5
|
ReadableStream,
|
|
7
6
|
WritableStream,
|
|
8
7
|
WritableStreamDefaultController,
|
|
9
8
|
} from "@yume-chan/stream-extra";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
PushReadableStream,
|
|
13
|
-
} from "@yume-chan/stream-extra";
|
|
9
|
+
import { MaybeConsumable, PushReadableStream } from "@yume-chan/stream-extra";
|
|
10
|
+
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
|
14
11
|
|
|
15
12
|
import type { AdbSocket } from "../adb.js";
|
|
16
|
-
import { raceSignal } from "../server/index.js";
|
|
17
13
|
|
|
18
14
|
import type { AdbPacketDispatcher } from "./dispatcher.js";
|
|
19
15
|
import { AdbCommand } from "./packet.js";
|
|
@@ -26,11 +22,15 @@ export interface AdbDaemonSocketInfo {
|
|
|
26
22
|
service: string;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
export interface
|
|
30
|
-
extends AdbDaemonSocketInfo {
|
|
25
|
+
export interface AdbDaemonSocketInit extends AdbDaemonSocketInfo {
|
|
31
26
|
dispatcher: AdbPacketDispatcher;
|
|
32
27
|
|
|
33
28
|
highWaterMark?: number | undefined;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The initial delayed ack byte count, or `Infinity` if delayed ack is disabled.
|
|
32
|
+
*/
|
|
33
|
+
availableWriteBytes: number;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export class AdbDaemonSocketController
|
|
@@ -49,9 +49,8 @@ export class AdbDaemonSocketController
|
|
|
49
49
|
return this.#readable;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
#writePromise: PromiseResolver<void> | undefined;
|
|
53
52
|
#writableController!: WritableStreamDefaultController;
|
|
54
|
-
readonly writable: WritableStream<
|
|
53
|
+
readonly writable: WritableStream<MaybeConsumable<Uint8Array>>;
|
|
55
54
|
|
|
56
55
|
#closed = false;
|
|
57
56
|
|
|
@@ -65,7 +64,18 @@ export class AdbDaemonSocketController
|
|
|
65
64
|
return this.#socket;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
|
|
67
|
+
#availableWriteBytesChanged: PromiseResolver<void> | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* When delayed ack is disabled, returns `Infinity` if the socket is ready to write
|
|
70
|
+
* (exactly one packet can be written no matter how large it is), or `-1` if the socket
|
|
71
|
+
* is waiting for ack message.
|
|
72
|
+
*
|
|
73
|
+
* When delayed ack is enabled, returns a non-negative finite number indicates the number of
|
|
74
|
+
* bytes that can be written to the socket before waiting for ack message.
|
|
75
|
+
*/
|
|
76
|
+
#availableWriteBytes = 0;
|
|
77
|
+
|
|
78
|
+
constructor(options: AdbDaemonSocketInit) {
|
|
69
79
|
this.#dispatcher = options.dispatcher;
|
|
70
80
|
this.localId = options.localId;
|
|
71
81
|
this.remoteId = options.remoteId;
|
|
@@ -76,11 +86,16 @@ export class AdbDaemonSocketController
|
|
|
76
86
|
this.#readableController = controller;
|
|
77
87
|
});
|
|
78
88
|
|
|
79
|
-
this.writable = new
|
|
89
|
+
this.writable = new MaybeConsumable.WritableStream<Uint8Array>({
|
|
80
90
|
start: (controller) => {
|
|
81
91
|
this.#writableController = controller;
|
|
92
|
+
controller.signal.addEventListener("abort", () => {
|
|
93
|
+
this.#availableWriteBytesChanged?.reject(
|
|
94
|
+
controller.signal.reason,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
82
97
|
},
|
|
83
|
-
write: async (data
|
|
98
|
+
write: async (data) => {
|
|
84
99
|
const size = data.length;
|
|
85
100
|
const chunkSize = this.#dispatcher.options.maxPayloadSize;
|
|
86
101
|
for (
|
|
@@ -88,23 +103,38 @@ export class AdbDaemonSocketController
|
|
|
88
103
|
start < size;
|
|
89
104
|
start = end, end += chunkSize
|
|
90
105
|
) {
|
|
91
|
-
|
|
92
|
-
await this.#
|
|
93
|
-
AdbCommand.Write,
|
|
94
|
-
this.localId,
|
|
95
|
-
this.remoteId,
|
|
96
|
-
data.subarray(start, end),
|
|
97
|
-
);
|
|
98
|
-
// Wait for ack packet
|
|
99
|
-
await raceSignal(
|
|
100
|
-
() => this.#writePromise!.promise,
|
|
101
|
-
controller.signal,
|
|
102
|
-
);
|
|
106
|
+
const chunk = data.subarray(start, end);
|
|
107
|
+
await this.#writeChunk(chunk);
|
|
103
108
|
}
|
|
104
109
|
},
|
|
105
110
|
});
|
|
106
111
|
|
|
107
112
|
this.#socket = new AdbDaemonSocket(this);
|
|
113
|
+
this.#availableWriteBytes = options.availableWriteBytes;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async #writeChunk(data: Uint8Array) {
|
|
117
|
+
const length = data.length;
|
|
118
|
+
while (this.#availableWriteBytes < length) {
|
|
119
|
+
// Only one lock is required because Web Streams API guarantees
|
|
120
|
+
// that `write` is not reentrant.
|
|
121
|
+
const resolver = new PromiseResolver<void>();
|
|
122
|
+
this.#availableWriteBytesChanged = resolver;
|
|
123
|
+
await resolver.promise;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.#availableWriteBytes === Infinity) {
|
|
127
|
+
this.#availableWriteBytes = -1;
|
|
128
|
+
} else {
|
|
129
|
+
this.#availableWriteBytes -= length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await this.#dispatcher.sendPacket(
|
|
133
|
+
AdbCommand.Write,
|
|
134
|
+
this.localId,
|
|
135
|
+
this.remoteId,
|
|
136
|
+
data,
|
|
137
|
+
);
|
|
108
138
|
}
|
|
109
139
|
|
|
110
140
|
async enqueue(data: Uint8Array) {
|
|
@@ -124,8 +154,9 @@ export class AdbDaemonSocketController
|
|
|
124
154
|
}
|
|
125
155
|
}
|
|
126
156
|
|
|
127
|
-
ack() {
|
|
128
|
-
this.#
|
|
157
|
+
public ack(bytes: number) {
|
|
158
|
+
this.#availableWriteBytes += bytes;
|
|
159
|
+
this.#availableWriteBytesChanged?.resolve();
|
|
129
160
|
}
|
|
130
161
|
|
|
131
162
|
async close(): Promise<void> {
|
|
@@ -134,6 +165,8 @@ export class AdbDaemonSocketController
|
|
|
134
165
|
}
|
|
135
166
|
this.#closed = true;
|
|
136
167
|
|
|
168
|
+
this.#availableWriteBytesChanged?.reject(new Error("Socket closed"));
|
|
169
|
+
|
|
137
170
|
try {
|
|
138
171
|
this.#writableController.error(new Error("Socket closed"));
|
|
139
172
|
} catch {
|
|
@@ -144,6 +177,7 @@ export class AdbDaemonSocketController
|
|
|
144
177
|
AdbCommand.Close,
|
|
145
178
|
this.localId,
|
|
146
179
|
this.remoteId,
|
|
180
|
+
EMPTY_UINT8_ARRAY,
|
|
147
181
|
);
|
|
148
182
|
}
|
|
149
183
|
|
|
@@ -185,7 +219,7 @@ export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
|
|
|
185
219
|
get readable(): ReadableStream<Uint8Array> {
|
|
186
220
|
return this.#controller.readable;
|
|
187
221
|
}
|
|
188
|
-
get writable(): WritableStream<
|
|
222
|
+
get writable(): WritableStream<MaybeConsumable<Uint8Array>> {
|
|
189
223
|
return this.#controller.writable;
|
|
190
224
|
}
|
|
191
225
|
|