@yume-chan/adb 0.0.21 → 0.0.23
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 +39 -0
- package/CHANGELOG.md +20 -1
- package/README.md +46 -366
- package/esm/adb.d.ts +9 -5
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +9 -2
- 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/framebuffer.d.ts +1 -1
- package/esm/commands/framebuffer.d.ts.map +1 -1
- package/esm/commands/framebuffer.js.map +1 -1
- package/esm/commands/reverse.js +1 -1
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/subprocess/command.js.map +1 -1
- package/esm/commands/subprocess/protocols/none.d.ts +2 -1
- package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/none.js +11 -14
- package/esm/commands/subprocess/protocols/none.js.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +21 -71
- package/esm/commands/subprocess/protocols/shell.js.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 -1
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +2 -2
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +4 -0
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +1 -1
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +1 -1
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +1 -0
- package/esm/commands/sync/socket.d.ts.map +1 -1
- package/esm/commands/sync/socket.js +4 -0
- package/esm/commands/sync/socket.js.map +1 -1
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +5 -6
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.d.ts +21 -0
- package/esm/commands/tcpip.d.ts.map +1 -1
- package/esm/commands/tcpip.js +18 -0
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/daemon/auth.js.map +1 -1
- package/esm/daemon/crypto.js.map +1 -1
- package/esm/daemon/dispatcher.d.ts +9 -1
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +107 -51
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +5 -5
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +1 -1
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +8 -13
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +80 -44
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +32 -4
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +61 -31
- 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 +14 -8
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +122 -70
- 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 +26 -0
- package/esm/server/transport.js.map +1 -1
- package/esm/utils/auto-reset-event.js.map +1 -1
- package/esm/utils/base64.js.map +1 -1
- package/esm/utils/conditional-variable.js.map +1 -1
- package/esm/utils/hex.js.map +1 -1
- package/package.json +14 -14
- package/src/adb.ts +22 -7
- package/src/banner.ts +1 -0
- package/src/commands/framebuffer.ts +1 -1
- package/src/commands/reverse.ts +2 -2
- package/src/commands/subprocess/protocols/none.ts +15 -19
- package/src/commands/subprocess/protocols/shell.ts +28 -98
- package/src/commands/subprocess/utils.ts +1 -1
- package/src/commands/sync/list.ts +6 -2
- package/src/commands/sync/pull.ts +3 -0
- package/src/commands/sync/response.ts +2 -2
- package/src/commands/sync/socket.ts +5 -0
- package/src/commands/sync/sync.ts +5 -6
- package/src/commands/tcpip.ts +44 -0
- package/src/daemon/dispatcher.ts +156 -65
- package/src/daemon/packet.ts +1 -1
- package/src/daemon/socket.ts +104 -86
- package/src/daemon/transport.ts +104 -38
- package/src/features.ts +2 -1
- package/src/server/client.ts +141 -91
- package/src/server/transport.ts +28 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/daemon/dispatcher.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AsyncOperationManager,
|
|
3
|
+
PromiseResolver,
|
|
4
|
+
delay,
|
|
5
|
+
} from "@yume-chan/async";
|
|
2
6
|
import type {
|
|
3
7
|
Consumable,
|
|
4
8
|
ReadableWritablePair,
|
|
@@ -9,10 +13,10 @@ import {
|
|
|
9
13
|
ConsumableWritableStream,
|
|
10
14
|
WritableStream,
|
|
11
15
|
} from "@yume-chan/stream-extra";
|
|
12
|
-
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
|
16
|
+
import { EMPTY_UINT8_ARRAY, NumberFieldType } from "@yume-chan/struct";
|
|
13
17
|
|
|
14
18
|
import type { AdbIncomingSocketHandler, AdbSocket, Closeable } from "../adb.js";
|
|
15
|
-
import { decodeUtf8, encodeUtf8
|
|
19
|
+
import { decodeUtf8, encodeUtf8 } from "../utils/index.js";
|
|
16
20
|
|
|
17
21
|
import type { AdbPacketData, AdbPacketInit } from "./packet.js";
|
|
18
22
|
import { AdbCommand, calculateChecksum } from "./packet.js";
|
|
@@ -28,10 +32,23 @@ export interface AdbPacketDispatcherOptions {
|
|
|
28
32
|
*/
|
|
29
33
|
appendNullToServiceString: boolean;
|
|
30
34
|
maxPayloadSize: number;
|
|
35
|
+
/**
|
|
36
|
+
* The number of bytes the device can send before receiving an ack packet.
|
|
37
|
+
|
|
38
|
+
* Set to 0 or any negative value to disable delayed ack.
|
|
39
|
+
* Otherwise the value must be in the range of unsigned 32-bit integer.
|
|
40
|
+
*/
|
|
41
|
+
initialDelayedAckBytes: number;
|
|
31
42
|
/**
|
|
32
43
|
* Whether to preserve the connection open after the `AdbPacketDispatcher` is closed.
|
|
33
44
|
*/
|
|
34
45
|
preserveConnection?: boolean | undefined;
|
|
46
|
+
debugSlowRead?: boolean | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SocketOpenResult {
|
|
50
|
+
remoteId: number;
|
|
51
|
+
availableWriteBytes: number;
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
/**
|
|
@@ -74,36 +91,28 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
74
91
|
options: AdbPacketDispatcherOptions,
|
|
75
92
|
) {
|
|
76
93
|
this.options = options;
|
|
94
|
+
// Don't allow negative values in dispatcher
|
|
95
|
+
if (this.options.initialDelayedAckBytes < 0) {
|
|
96
|
+
this.options.initialDelayedAckBytes = 0;
|
|
97
|
+
}
|
|
77
98
|
|
|
78
99
|
connection.readable
|
|
79
100
|
.pipeTo(
|
|
80
101
|
new WritableStream({
|
|
81
102
|
write: async (packet) => {
|
|
82
103
|
switch (packet.command) {
|
|
83
|
-
case AdbCommand.OK:
|
|
84
|
-
this.#handleOk(packet);
|
|
85
|
-
break;
|
|
86
104
|
case AdbCommand.Close:
|
|
87
105
|
await this.#handleClose(packet);
|
|
88
106
|
break;
|
|
89
|
-
case AdbCommand.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.get(packet.arg1)!
|
|
93
|
-
.enqueue(packet.payload);
|
|
94
|
-
await this.sendPacket(
|
|
95
|
-
AdbCommand.OK,
|
|
96
|
-
packet.arg1,
|
|
97
|
-
packet.arg0,
|
|
98
|
-
);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
throw new Error(
|
|
102
|
-
`Unknown local socket id: ${packet.arg1}`,
|
|
103
|
-
);
|
|
107
|
+
case AdbCommand.Okay:
|
|
108
|
+
this.#handleOkay(packet);
|
|
109
|
+
break;
|
|
104
110
|
case AdbCommand.Open:
|
|
105
111
|
await this.#handleOpen(packet);
|
|
106
112
|
break;
|
|
113
|
+
case AdbCommand.Write:
|
|
114
|
+
await this.#handleWrite(packet);
|
|
115
|
+
break;
|
|
107
116
|
default:
|
|
108
117
|
// Junk data may only appear in the authentication phase,
|
|
109
118
|
// since the dispatcher only works after authentication,
|
|
@@ -137,24 +146,6 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
137
146
|
this.#writer = connection.writable.getWriter();
|
|
138
147
|
}
|
|
139
148
|
|
|
140
|
-
#handleOk(packet: AdbPacketData) {
|
|
141
|
-
if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
|
|
142
|
-
// Device successfully created the socket
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const socket = this.#sockets.get(packet.arg1);
|
|
147
|
-
if (socket) {
|
|
148
|
-
// Device has received last `WRTE` to the socket
|
|
149
|
-
socket.ack();
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Maybe the device is responding to a packet of last connection
|
|
154
|
-
// Tell the device to close the socket
|
|
155
|
-
void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
149
|
async #handleClose(packet: AdbPacketData) {
|
|
159
150
|
// If the socket is still pending
|
|
160
151
|
if (
|
|
@@ -182,15 +173,8 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
182
173
|
// Ignore `arg0` and search for the socket
|
|
183
174
|
const socket = this.#sockets.get(packet.arg1);
|
|
184
175
|
if (socket) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
await this.sendPacket(
|
|
188
|
-
AdbCommand.Close,
|
|
189
|
-
packet.arg1,
|
|
190
|
-
packet.arg0,
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
await socket.dispose();
|
|
176
|
+
await socket.close();
|
|
177
|
+
socket.dispose();
|
|
194
178
|
this.#sockets.delete(packet.arg1);
|
|
195
179
|
return;
|
|
196
180
|
}
|
|
@@ -200,16 +184,59 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
200
184
|
// the device may also respond with two `CLSE` packets.
|
|
201
185
|
}
|
|
202
186
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
187
|
+
#handleOkay(packet: AdbPacketData) {
|
|
188
|
+
let ackBytes: number;
|
|
189
|
+
if (this.options.initialDelayedAckBytes !== 0) {
|
|
190
|
+
if (packet.payload.byteLength !== 4) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"Invalid OKAY packet. Payload size should be 4",
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
ackBytes = NumberFieldType.Uint32.deserialize(packet.payload, true);
|
|
196
|
+
} else {
|
|
197
|
+
if (packet.payload.byteLength !== 0) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"Invalid OKAY packet. Payload size should be 0",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
ackBytes = Infinity;
|
|
203
|
+
}
|
|
206
204
|
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
if (
|
|
206
|
+
this.#initializers.resolve(packet.arg1, {
|
|
207
|
+
remoteId: packet.arg0,
|
|
208
|
+
availableWriteBytes: ackBytes,
|
|
209
|
+
} satisfies SocketOpenResult)
|
|
210
|
+
) {
|
|
211
|
+
// Device successfully created the socket
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const socket = this.#sockets.get(packet.arg1);
|
|
216
|
+
if (socket) {
|
|
217
|
+
// When delayed ack is enabled, `ackBytes` is a positive number represents
|
|
218
|
+
// how many bytes the device has received from this socket.
|
|
219
|
+
// When delayed ack is disabled, `ackBytes` is always `Infinity` represents
|
|
220
|
+
// the device has received last `WRTE` packet from the socket.
|
|
221
|
+
socket.ack(ackBytes);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Maybe the device is responding to a packet of last connection
|
|
226
|
+
// Tell the device to close the socket
|
|
227
|
+
void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
209
228
|
}
|
|
210
229
|
|
|
211
|
-
|
|
212
|
-
|
|
230
|
+
#sendOkay(localId: number, remoteId: number, ackBytes: number) {
|
|
231
|
+
let payload: Uint8Array;
|
|
232
|
+
if (this.options.initialDelayedAckBytes !== 0) {
|
|
233
|
+
payload = new Uint8Array(4);
|
|
234
|
+
new DataView(payload.buffer).setUint32(0, ackBytes, true);
|
|
235
|
+
} else {
|
|
236
|
+
payload = EMPTY_UINT8_ARRAY;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return this.sendPacket(AdbCommand.Okay, localId, remoteId, payload);
|
|
213
240
|
}
|
|
214
241
|
|
|
215
242
|
async #handleOpen(packet: AdbPacketData) {
|
|
@@ -219,9 +246,20 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
219
246
|
this.#initializers.resolve(localId, undefined);
|
|
220
247
|
|
|
221
248
|
const remoteId = packet.arg0;
|
|
222
|
-
let
|
|
223
|
-
|
|
224
|
-
|
|
249
|
+
let initialDelayedAckBytes = packet.arg1;
|
|
250
|
+
const service = decodeUtf8(packet.payload);
|
|
251
|
+
|
|
252
|
+
if (this.options.initialDelayedAckBytes === 0) {
|
|
253
|
+
if (initialDelayedAckBytes !== 0) {
|
|
254
|
+
throw new Error("Invalid OPEN packet. arg1 should be 0");
|
|
255
|
+
}
|
|
256
|
+
initialDelayedAckBytes = Infinity;
|
|
257
|
+
} else {
|
|
258
|
+
if (initialDelayedAckBytes === 0) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
"Invalid OPEN packet. arg1 should be greater than 0",
|
|
261
|
+
);
|
|
262
|
+
}
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
const handler = this.#incomingSocketHandlers.get(service);
|
|
@@ -237,26 +275,66 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
237
275
|
localCreated: false,
|
|
238
276
|
service,
|
|
239
277
|
});
|
|
278
|
+
controller.ack(initialDelayedAckBytes);
|
|
240
279
|
|
|
241
280
|
try {
|
|
242
281
|
await handler(controller.socket);
|
|
243
282
|
this.#sockets.set(localId, controller);
|
|
244
|
-
await this
|
|
283
|
+
await this.#sendOkay(
|
|
284
|
+
localId,
|
|
285
|
+
remoteId,
|
|
286
|
+
this.options.initialDelayedAckBytes,
|
|
287
|
+
);
|
|
245
288
|
} catch (e) {
|
|
246
289
|
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
|
247
290
|
}
|
|
248
291
|
}
|
|
249
292
|
|
|
293
|
+
async #handleWrite(packet: AdbPacketData) {
|
|
294
|
+
const socket = this.#sockets.get(packet.arg1);
|
|
295
|
+
if (!socket) {
|
|
296
|
+
throw new Error(`Unknown local socket id: ${packet.arg1}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let handled = false;
|
|
300
|
+
await Promise.race([
|
|
301
|
+
delay(5000).then(() => {
|
|
302
|
+
if (this.options.debugSlowRead && !handled) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
`packet for \`${socket.service}\` not handled in 5 seconds`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}),
|
|
308
|
+
(async () => {
|
|
309
|
+
await socket.enqueue(packet.payload);
|
|
310
|
+
await this.#sendOkay(
|
|
311
|
+
packet.arg1,
|
|
312
|
+
packet.arg0,
|
|
313
|
+
packet.payload.length,
|
|
314
|
+
);
|
|
315
|
+
handled = true;
|
|
316
|
+
})(),
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
250
322
|
async createSocket(service: string): Promise<AdbSocket> {
|
|
251
323
|
if (this.options.appendNullToServiceString) {
|
|
252
324
|
service += "\0";
|
|
253
325
|
}
|
|
254
326
|
|
|
255
|
-
const [localId, initializer] =
|
|
256
|
-
|
|
327
|
+
const [localId, initializer] =
|
|
328
|
+
this.#initializers.add<SocketOpenResult>();
|
|
329
|
+
await this.sendPacket(
|
|
330
|
+
AdbCommand.Open,
|
|
331
|
+
localId,
|
|
332
|
+
this.options.initialDelayedAckBytes,
|
|
333
|
+
service,
|
|
334
|
+
);
|
|
257
335
|
|
|
258
|
-
// Fulfilled by `
|
|
259
|
-
const remoteId = await initializer;
|
|
336
|
+
// Fulfilled by `handleOkay`
|
|
337
|
+
const { remoteId, availableWriteBytes } = await initializer;
|
|
260
338
|
const controller = new AdbDaemonSocketController({
|
|
261
339
|
dispatcher: this,
|
|
262
340
|
localId,
|
|
@@ -264,11 +342,24 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
264
342
|
localCreated: true,
|
|
265
343
|
service,
|
|
266
344
|
});
|
|
345
|
+
controller.ack(availableWriteBytes);
|
|
267
346
|
this.#sockets.set(localId, controller);
|
|
268
347
|
|
|
269
348
|
return controller.socket;
|
|
270
349
|
}
|
|
271
350
|
|
|
351
|
+
addReverseTunnel(service: string, handler: AdbIncomingSocketHandler) {
|
|
352
|
+
this.#incomingSocketHandlers.set(service, handler);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
removeReverseTunnel(address: string) {
|
|
356
|
+
this.#incomingSocketHandlers.delete(address);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
clearReverseTunnels() {
|
|
360
|
+
this.#incomingSocketHandlers.clear();
|
|
361
|
+
}
|
|
362
|
+
|
|
272
363
|
async sendPacket(
|
|
273
364
|
command: AdbCommand,
|
|
274
365
|
arg0: number,
|
|
@@ -307,7 +398,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
307
398
|
this.#closed = true;
|
|
308
399
|
|
|
309
400
|
this.#readAbortController.abort();
|
|
310
|
-
if (this.options.preserveConnection
|
|
401
|
+
if (this.options.preserveConnection) {
|
|
311
402
|
this.#writer.releaseLock();
|
|
312
403
|
} else {
|
|
313
404
|
await this.#writer.close();
|
|
@@ -318,7 +409,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
318
409
|
|
|
319
410
|
#dispose() {
|
|
320
411
|
for (const socket of this.#sockets.values()) {
|
|
321
|
-
socket.dispose()
|
|
412
|
+
socket.dispose();
|
|
322
413
|
}
|
|
323
414
|
|
|
324
415
|
this.#disconnected.resolve();
|
package/src/daemon/packet.ts
CHANGED
package/src/daemon/socket.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
2
|
import type { Disposable } from "@yume-chan/event";
|
|
3
3
|
import type {
|
|
4
|
+
AbortSignal,
|
|
4
5
|
Consumable,
|
|
5
6
|
PushReadableStreamController,
|
|
6
7
|
ReadableStream,
|
|
7
|
-
ReadableWritablePair,
|
|
8
8
|
WritableStream,
|
|
9
|
+
WritableStreamDefaultController,
|
|
9
10
|
} from "@yume-chan/stream-extra";
|
|
10
11
|
import {
|
|
11
12
|
ConsumableWritableStream,
|
|
12
|
-
DistributionStream,
|
|
13
|
-
DuplexStreamFactory,
|
|
14
13
|
PushReadableStream,
|
|
15
|
-
pipeFrom,
|
|
16
14
|
} from "@yume-chan/stream-extra";
|
|
17
15
|
|
|
18
|
-
import type {
|
|
16
|
+
import type { AdbSocket } from "../adb.js";
|
|
19
17
|
|
|
20
18
|
import type { AdbPacketDispatcher } from "./dispatcher.js";
|
|
21
19
|
import { AdbCommand } from "./packet.js";
|
|
@@ -36,11 +34,7 @@ export interface AdbDaemonSocketConstructionOptions
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
export class AdbDaemonSocketController
|
|
39
|
-
implements
|
|
40
|
-
AdbDaemonSocketInfo,
|
|
41
|
-
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>,
|
|
42
|
-
Closeable,
|
|
43
|
-
Disposable
|
|
37
|
+
implements AdbDaemonSocketInfo, AdbSocket, Disposable
|
|
44
38
|
{
|
|
45
39
|
readonly #dispatcher!: AdbPacketDispatcher;
|
|
46
40
|
|
|
@@ -49,25 +43,20 @@ export class AdbDaemonSocketController
|
|
|
49
43
|
readonly localCreated!: boolean;
|
|
50
44
|
readonly service!: string;
|
|
51
45
|
|
|
52
|
-
#duplex: DuplexStreamFactory<Uint8Array, Consumable<Uint8Array>>;
|
|
53
|
-
|
|
54
46
|
#readable: ReadableStream<Uint8Array>;
|
|
55
47
|
#readableController!: PushReadableStreamController<Uint8Array>;
|
|
56
48
|
get readable() {
|
|
57
49
|
return this.#readable;
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
#
|
|
52
|
+
#writableController!: WritableStreamDefaultController;
|
|
61
53
|
readonly writable: WritableStream<Consumable<Uint8Array>>;
|
|
62
54
|
|
|
63
55
|
#closed = false;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
* It's only used by dispatcher to avoid sending another `CLSE` packet to remote.
|
|
68
|
-
*/
|
|
56
|
+
|
|
57
|
+
#closedPromise = new PromiseResolver<void>();
|
|
69
58
|
get closed() {
|
|
70
|
-
return this.#
|
|
59
|
+
return this.#closedPromise.promise;
|
|
71
60
|
}
|
|
72
61
|
|
|
73
62
|
#socket: AdbDaemonSocket;
|
|
@@ -75,6 +64,17 @@ export class AdbDaemonSocketController
|
|
|
75
64
|
return this.#socket;
|
|
76
65
|
}
|
|
77
66
|
|
|
67
|
+
#availableWriteBytesChanged: PromiseResolver<void> | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* When delayed ack is disabled, can be `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.
|
|
72
|
+
*
|
|
73
|
+
* When delayed ack is enabled, a non-negative finite number indicates the number of
|
|
74
|
+
* bytes that can be written to the socket before receiving an ack.
|
|
75
|
+
*/
|
|
76
|
+
#availableWriteBytes = 0;
|
|
77
|
+
|
|
78
78
|
constructor(options: AdbDaemonSocketConstructionOptions) {
|
|
79
79
|
this.#dispatcher = options.dispatcher;
|
|
80
80
|
this.localId = options.localId;
|
|
@@ -82,88 +82,110 @@ export class AdbDaemonSocketController
|
|
|
82
82
|
this.localCreated = options.localCreated;
|
|
83
83
|
this.service = options.service;
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
>({
|
|
93
|
-
close: async () => {
|
|
94
|
-
this.#closed = true;
|
|
95
|
-
|
|
96
|
-
await this.#dispatcher.sendPacket(
|
|
97
|
-
AdbCommand.Close,
|
|
98
|
-
this.localId,
|
|
99
|
-
this.remoteId
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Don't `dispose` here, we need to wait for `CLSE` response packet.
|
|
103
|
-
return false;
|
|
85
|
+
this.#readable = new PushReadableStream((controller) => {
|
|
86
|
+
this.#readableController = controller;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.writable = new ConsumableWritableStream<Uint8Array>({
|
|
90
|
+
start: (controller) => {
|
|
91
|
+
this.#writableController = controller;
|
|
104
92
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.#
|
|
93
|
+
write: async (data, controller) => {
|
|
94
|
+
const size = data.length;
|
|
95
|
+
const chunkSize = this.#dispatcher.options.maxPayloadSize;
|
|
96
|
+
for (
|
|
97
|
+
let start = 0, end = chunkSize;
|
|
98
|
+
start < size;
|
|
99
|
+
start = end, end += chunkSize
|
|
100
|
+
) {
|
|
101
|
+
const chunk = data.subarray(start, end);
|
|
102
|
+
await this.#writeChunk(chunk, controller.signal);
|
|
103
|
+
}
|
|
108
104
|
},
|
|
109
105
|
});
|
|
110
106
|
|
|
111
|
-
this.#
|
|
112
|
-
|
|
113
|
-
(controller) => {
|
|
114
|
-
this.#readableController = controller;
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
highWaterMark: options.highWaterMark ?? 16 * 1024,
|
|
118
|
-
size(chunk) {
|
|
119
|
-
return chunk.byteLength;
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
)
|
|
123
|
-
);
|
|
107
|
+
this.#socket = new AdbDaemonSocket(this);
|
|
108
|
+
}
|
|
124
109
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.remoteId,
|
|
135
|
-
chunk
|
|
136
|
-
);
|
|
137
|
-
await this.#writePromise.promise;
|
|
138
|
-
},
|
|
139
|
-
})
|
|
140
|
-
),
|
|
141
|
-
new DistributionStream(this.#dispatcher.options.maxPayloadSize)
|
|
142
|
-
);
|
|
110
|
+
async #writeChunk(data: Uint8Array, signal: AbortSignal) {
|
|
111
|
+
const length = data.byteLength;
|
|
112
|
+
while (this.#availableWriteBytes < length) {
|
|
113
|
+
// Only one lock is required because Web Streams API guarantees
|
|
114
|
+
// that `write` is not reentrant.
|
|
115
|
+
const resolver = new PromiseResolver<void>();
|
|
116
|
+
signal.addEventListener("abort", () => {
|
|
117
|
+
resolver.reject(signal.reason);
|
|
118
|
+
});
|
|
143
119
|
|
|
144
|
-
|
|
120
|
+
this.#availableWriteBytesChanged = resolver;
|
|
121
|
+
await resolver.promise;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (this.#availableWriteBytes === Infinity) {
|
|
125
|
+
this.#availableWriteBytes = -1;
|
|
126
|
+
} else {
|
|
127
|
+
this.#availableWriteBytes -= length;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await this.#dispatcher.sendPacket(
|
|
131
|
+
AdbCommand.Write,
|
|
132
|
+
this.localId,
|
|
133
|
+
this.remoteId,
|
|
134
|
+
data,
|
|
135
|
+
);
|
|
145
136
|
}
|
|
146
137
|
|
|
147
138
|
async enqueue(data: Uint8Array) {
|
|
148
|
-
//
|
|
149
|
-
//
|
|
139
|
+
// Consumers can `cancel` the `readable` if they are not interested in future data.
|
|
140
|
+
// Throw away the data if that happens.
|
|
150
141
|
if (this.#readableController.abortSignal.aborted) {
|
|
151
142
|
return;
|
|
152
143
|
}
|
|
153
144
|
|
|
154
|
-
|
|
145
|
+
try {
|
|
146
|
+
await this.#readableController.enqueue(data);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (this.#readableController.abortSignal.aborted) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
throw e;
|
|
152
|
+
}
|
|
155
153
|
}
|
|
156
154
|
|
|
157
|
-
ack() {
|
|
158
|
-
this.#
|
|
155
|
+
public ack(bytes: number) {
|
|
156
|
+
this.#availableWriteBytes += bytes;
|
|
157
|
+
this.#availableWriteBytesChanged?.resolve();
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
async close(): Promise<void> {
|
|
162
|
-
|
|
161
|
+
if (this.#closed) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.#closed = true;
|
|
165
|
+
|
|
166
|
+
this.#availableWriteBytesChanged?.reject(new Error("Socket closed"));
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
this.#writableController.error(new Error("Socket closed"));
|
|
170
|
+
} catch {
|
|
171
|
+
// ignore
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await this.#dispatcher.sendPacket(
|
|
175
|
+
AdbCommand.Close,
|
|
176
|
+
this.localId,
|
|
177
|
+
this.remoteId,
|
|
178
|
+
);
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
dispose() {
|
|
166
|
-
|
|
182
|
+
try {
|
|
183
|
+
this.#readableController.close();
|
|
184
|
+
} catch {
|
|
185
|
+
// ignore
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.#closedPromise.resolve();
|
|
167
189
|
}
|
|
168
190
|
}
|
|
169
191
|
|
|
@@ -175,11 +197,7 @@ export class AdbDaemonSocketController
|
|
|
175
197
|
* `socket.writable.abort()`, `socket.writable.getWriter().abort()`,
|
|
176
198
|
* `socket.writable.close()` or `socket.writable.getWriter().close()`.
|
|
177
199
|
*/
|
|
178
|
-
export class AdbDaemonSocket
|
|
179
|
-
implements
|
|
180
|
-
AdbDaemonSocketInfo,
|
|
181
|
-
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
|
|
182
|
-
{
|
|
200
|
+
export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
|
|
183
201
|
#controller: AdbDaemonSocketController;
|
|
184
202
|
|
|
185
203
|
get localId(): number {
|
|
@@ -202,7 +220,7 @@ export class AdbDaemonSocket
|
|
|
202
220
|
return this.#controller.writable;
|
|
203
221
|
}
|
|
204
222
|
|
|
205
|
-
get closed():
|
|
223
|
+
get closed(): Promise<void> {
|
|
206
224
|
return this.#controller.closed;
|
|
207
225
|
}
|
|
208
226
|
|