@yume-chan/adb 0.0.9 → 0.0.10
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 +23 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -21
- package/README.md +227 -257
- package/dts/adb.d.ts.map +1 -1
- package/dts/auth.d.ts +11 -1
- package/dts/auth.d.ts.map +1 -1
- package/dts/backend.d.ts +3 -5
- package/dts/backend.d.ts.map +1 -1
- package/dts/commands/demo-mode.d.ts +3 -3
- package/dts/commands/demo-mode.d.ts.map +1 -1
- package/dts/commands/reverse.d.ts +2 -2
- package/dts/commands/reverse.d.ts.map +1 -1
- package/dts/commands/shell/index.d.ts +31 -5
- package/dts/commands/shell/index.d.ts.map +1 -1
- package/dts/commands/shell/legacy.d.ts +13 -2
- package/dts/commands/shell/legacy.d.ts.map +1 -1
- package/dts/commands/shell/protocol.d.ts +11 -3
- package/dts/commands/shell/protocol.d.ts.map +1 -1
- package/dts/commands/shell/types.d.ts +35 -3
- package/dts/commands/shell/types.d.ts.map +1 -1
- package/dts/commands/sync/request.d.ts +1 -1
- package/dts/commands/sync/request.d.ts.map +1 -1
- package/dts/commands/sync/response.d.ts +2 -2
- package/dts/commands/sync/response.d.ts.map +1 -1
- package/dts/commands/sync/sync.d.ts.map +1 -1
- package/dts/crypto.d.ts +7 -0
- package/dts/crypto.d.ts.map +1 -1
- package/dts/packet.d.ts.map +1 -1
- package/dts/socket/close-event-emitter.d.ts.map +1 -1
- package/dts/socket/controller.d.ts.map +1 -1
- package/dts/socket/data-event-emitter.d.ts.map +1 -1
- package/dts/socket/dispatcher.d.ts.map +1 -1
- package/dts/stream/buffered-stream.d.ts +2 -4
- package/dts/stream/buffered-stream.d.ts.map +1 -1
- package/dts/utils/encoding.d.ts +3 -0
- package/dts/utils/encoding.d.ts.map +1 -0
- package/dts/utils/index.d.ts +1 -0
- package/dts/utils/index.d.ts.map +1 -1
- package/esm/adb.js +3 -2
- package/esm/adb.js.map +1 -1
- package/esm/auth.js +2 -2
- package/esm/auth.js.map +1 -1
- package/esm/commands/demo-mode.js +9 -9
- package/esm/commands/demo-mode.js.map +1 -1
- package/esm/commands/reverse.js +3 -2
- package/esm/commands/reverse.js.map +1 -1
- package/esm/commands/shell/index.js +28 -18
- package/esm/commands/shell/index.js.map +1 -1
- package/esm/commands/shell/legacy.js +16 -0
- package/esm/commands/shell/legacy.js.map +1 -1
- package/esm/commands/shell/protocol.js +36 -9
- package/esm/commands/shell/protocol.js.map +1 -1
- package/esm/commands/sync/request.js +5 -4
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.js +4 -3
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/crypto.js +7 -2
- package/esm/crypto.js.map +1 -1
- package/esm/packet.js +12 -14
- package/esm/packet.js.map +1 -1
- package/esm/socket/close-event-emitter.js.map +1 -1
- package/esm/socket/controller.js.map +1 -1
- package/esm/socket/data-event-emitter.js.map +1 -1
- package/esm/socket/dispatcher.js +7 -7
- package/esm/socket/dispatcher.js.map +1 -1
- package/esm/stream/buffered-stream.js +0 -6
- package/esm/stream/buffered-stream.js.map +1 -1
- package/esm/utils/encoding.js +11 -0
- package/esm/utils/encoding.js.map +1 -0
- package/esm/utils/index.js +1 -0
- package/esm/utils/index.js.map +1 -1
- package/package.json +44 -51
- package/src/adb.ts +243 -242
- package/src/auth.ts +166 -156
- package/src/backend.ts +20 -24
- package/src/commands/base.ts +11 -11
- package/src/commands/demo-mode.ts +160 -160
- package/src/commands/framebuffer.ts +73 -73
- package/src/commands/index.ts +8 -8
- package/src/commands/install.ts +21 -21
- package/src/commands/reverse.ts +134 -133
- package/src/commands/shell/index.ts +87 -68
- package/src/commands/shell/legacy.ts +59 -42
- package/src/commands/shell/protocol.ts +121 -94
- package/src/commands/shell/types.ts +55 -21
- package/src/commands/shell/utils.ts +20 -20
- package/src/commands/sync/index.ts +7 -7
- package/src/commands/sync/list.ts +39 -39
- package/src/commands/sync/pull.ts +34 -34
- package/src/commands/sync/push.ts +46 -46
- package/src/commands/sync/request.ts +49 -48
- package/src/commands/sync/response.ts +58 -57
- package/src/commands/sync/stat.ts +121 -121
- package/src/commands/sync/sync.ts +113 -113
- package/src/commands/tcpip.ts +21 -21
- package/src/crypto.ts +285 -280
- package/src/features.ts +5 -5
- package/src/index.ts +10 -10
- package/src/packet.ts +84 -89
- package/src/socket/close-event-emitter.ts +28 -28
- package/src/socket/controller.ts +105 -105
- package/src/socket/data-event-emitter.ts +27 -27
- package/src/socket/dispatcher.ts +269 -269
- package/src/socket/index.ts +6 -6
- package/src/socket/logger.ts +12 -12
- package/src/socket/socket.ts +28 -28
- package/src/stream/buffered-stream.ts +92 -100
- package/src/stream/index.ts +2 -2
- package/src/stream/stream.ts +50 -50
- package/src/utils/auto-reset-event.ts +41 -41
- package/src/utils/base64.ts +274 -274
- package/src/utils/chunk.ts +70 -70
- package/src/utils/encoding.ts +13 -0
- package/src/utils/event-queue.ts +145 -145
- package/src/utils/index.ts +5 -4
- package/cjs/adb.js +0 -205
- package/cjs/adb.js.map +0 -1
- package/cjs/auth.js +0 -141
- package/cjs/auth.js.map +0 -1
- package/cjs/backend.js +0 -3
- package/cjs/backend.js.map +0 -1
- package/cjs/commands/base.js +0 -12
- package/cjs/commands/base.js.map +0 -1
- package/cjs/commands/demo-mode.js +0 -174
- package/cjs/commands/demo-mode.js.map +0 -1
- package/cjs/commands/framebuffer.js +0 -66
- package/cjs/commands/framebuffer.js.map +0 -1
- package/cjs/commands/index.js +0 -12
- package/cjs/commands/index.js.map +0 -1
- package/cjs/commands/install.js +0 -20
- package/cjs/commands/install.js.map +0 -1
- package/cjs/commands/reverse.js +0 -105
- package/cjs/commands/reverse.js.map +0 -1
- package/cjs/commands/shell/index.js +0 -56
- package/cjs/commands/shell/index.js.map +0 -1
- package/cjs/commands/shell/legacy.js +0 -37
- package/cjs/commands/shell/legacy.js.map +0 -1
- package/cjs/commands/shell/protocol.js +0 -83
- package/cjs/commands/shell/protocol.js.map +0 -1
- package/cjs/commands/shell/types.js +0 -3
- package/cjs/commands/shell/types.js.map +0 -1
- package/cjs/commands/shell/utils.js +0 -23
- package/cjs/commands/shell/utils.js.map +0 -1
- package/cjs/commands/sync/index.js +0 -11
- package/cjs/commands/sync/index.js.map +0 -1
- package/cjs/commands/sync/list.js +0 -36
- package/cjs/commands/sync/list.js.map +0 -1
- package/cjs/commands/sync/pull.js +0 -34
- package/cjs/commands/sync/pull.js.map +0 -1
- package/cjs/commands/sync/push.js +0 -49
- package/cjs/commands/sync/push.js.map +0 -1
- package/cjs/commands/sync/request.js +0 -48
- package/cjs/commands/sync/request.js.map +0 -1
- package/cjs/commands/sync/response.js +0 -53
- package/cjs/commands/sync/response.js.map +0 -1
- package/cjs/commands/sync/stat.js +0 -109
- package/cjs/commands/sync/stat.js.map +0 -1
- package/cjs/commands/sync/sync.js +0 -118
- package/cjs/commands/sync/sync.js.map +0 -1
- package/cjs/commands/tcpip.js +0 -28
- package/cjs/commands/tcpip.js.map +0 -1
- package/cjs/crypto.js +0 -231
- package/cjs/crypto.js.map +0 -1
- package/cjs/features.js +0 -10
- package/cjs/features.js.map +0 -1
- package/cjs/index.js +0 -14
- package/cjs/index.js.map +0 -1
- package/cjs/packet.js +0 -77
- package/cjs/packet.js.map +0 -1
- package/cjs/socket/close-event-emitter.js +0 -32
- package/cjs/socket/close-event-emitter.js.map +0 -1
- package/cjs/socket/controller.js +0 -84
- package/cjs/socket/controller.js.map +0 -1
- package/cjs/socket/data-event-emitter.js +0 -29
- package/cjs/socket/data-event-emitter.js.map +0 -1
- package/cjs/socket/dispatcher.js +0 -219
- package/cjs/socket/dispatcher.js.map +0 -1
- package/cjs/socket/index.js +0 -10
- package/cjs/socket/index.js.map +0 -1
- package/cjs/socket/logger.js +0 -3
- package/cjs/socket/logger.js.map +0 -1
- package/cjs/socket/socket.js +0 -24
- package/cjs/socket/socket.js.map +0 -1
- package/cjs/stream/buffered-stream.js +0 -78
- package/cjs/stream/buffered-stream.js.map +0 -1
- package/cjs/stream/index.js +0 -6
- package/cjs/stream/index.js.map +0 -1
- package/cjs/stream/stream.js +0 -45
- package/cjs/stream/stream.js.map +0 -1
- package/cjs/utils/auto-reset-event.js +0 -37
- package/cjs/utils/auto-reset-event.js.map +0 -1
- package/cjs/utils/base64.js +0 -210
- package/cjs/utils/base64.js.map +0 -1
- package/cjs/utils/chunk.js +0 -75
- package/cjs/utils/chunk.js.map +0 -1
- package/cjs/utils/event-queue.js +0 -108
- package/cjs/utils/event-queue.js.map +0 -1
- package/cjs/utils/index.js +0 -8
- package/cjs/utils/index.js.map +0 -1
- package/tsconfig.cjs.json +0 -11
- package/tsconfig.esm.json +0 -28
- package/tsconfig.esm.tsbuildinfo +0 -1305
- package/tsconfig.json +0 -18
package/src/socket/dispatcher.ts
CHANGED
|
@@ -1,269 +1,269 @@
|
|
|
1
|
-
import { AsyncOperationManager } from '@yume-chan/async';
|
|
2
|
-
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
|
3
|
-
import { AdbBackend } from '../backend';
|
|
4
|
-
import { AdbCommand, AdbPacket, AdbPacketInit } from '../packet';
|
|
5
|
-
import { AutoResetEvent } from '../utils';
|
|
6
|
-
import { AdbSocketController } from './controller';
|
|
7
|
-
import { AdbLogger } from './logger';
|
|
8
|
-
import { AdbSocket } from './socket';
|
|
9
|
-
|
|
10
|
-
export interface AdbPacketReceivedEventArgs {
|
|
11
|
-
handled: boolean;
|
|
12
|
-
|
|
13
|
-
packet: AdbPacket;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface AdbIncomingSocketEventArgs {
|
|
17
|
-
handled: boolean;
|
|
18
|
-
|
|
19
|
-
packet: AdbPacket;
|
|
20
|
-
|
|
21
|
-
serviceString: string;
|
|
22
|
-
|
|
23
|
-
socket: AdbSocket;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const EmptyArrayBuffer = new ArrayBuffer(0);
|
|
27
|
-
|
|
28
|
-
export class AdbPacketDispatcher extends AutoDisposable {
|
|
29
|
-
// ADB socket id starts from 1
|
|
30
|
-
// (0 means open failed)
|
|
31
|
-
private readonly initializers = new AsyncOperationManager(1);
|
|
32
|
-
private readonly sockets = new Map<number, AdbSocketController>();
|
|
33
|
-
private readonly sendLock = new AutoResetEvent();
|
|
34
|
-
private readonly logger: AdbLogger | undefined;
|
|
35
|
-
|
|
36
|
-
public readonly backend: AdbBackend;
|
|
37
|
-
|
|
38
|
-
public maxPayloadSize = 0;
|
|
39
|
-
public calculateChecksum = true;
|
|
40
|
-
public appendNullToServiceString = true;
|
|
41
|
-
|
|
42
|
-
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
|
43
|
-
public get onPacket() { return this.packetEvent.event; }
|
|
44
|
-
|
|
45
|
-
private readonly incomingSocketEvent = this.addDisposable(new EventEmitter<AdbIncomingSocketEventArgs>());
|
|
46
|
-
public get onIncomingSocket() { return this.incomingSocketEvent.event; }
|
|
47
|
-
|
|
48
|
-
private readonly errorEvent = this.addDisposable(new EventEmitter<Error>());
|
|
49
|
-
public get onError() { return this.errorEvent.event; }
|
|
50
|
-
|
|
51
|
-
private _running = false;
|
|
52
|
-
public get running() { return this._running; }
|
|
53
|
-
|
|
54
|
-
public constructor(backend: AdbBackend, logger?: AdbLogger) {
|
|
55
|
-
super();
|
|
56
|
-
|
|
57
|
-
this.backend = backend;
|
|
58
|
-
this.logger = logger;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private async receiveLoop() {
|
|
62
|
-
try {
|
|
63
|
-
while (this._running) {
|
|
64
|
-
const packet = await AdbPacket.read(this.backend);
|
|
65
|
-
this.logger?.onIncomingPacket?.(packet);
|
|
66
|
-
|
|
67
|
-
switch (packet.command) {
|
|
68
|
-
case AdbCommand.OK:
|
|
69
|
-
this.handleOk(packet);
|
|
70
|
-
continue;
|
|
71
|
-
case AdbCommand.Close:
|
|
72
|
-
await this.handleClose(packet);
|
|
73
|
-
continue;
|
|
74
|
-
case AdbCommand.Write:
|
|
75
|
-
if (this.sockets.has(packet.arg1)) {
|
|
76
|
-
await this.sockets.get(packet.arg1)!.dataEvent.fire(packet.payload!);
|
|
77
|
-
await this.sendPacket(AdbCommand.OK, packet.arg1, packet.arg0);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Maybe the device is responding to a packet of last connection
|
|
81
|
-
// Just ignore it
|
|
82
|
-
continue;
|
|
83
|
-
case AdbCommand.Open:
|
|
84
|
-
await this.handleOpen(packet);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const args: AdbPacketReceivedEventArgs = {
|
|
89
|
-
handled: false,
|
|
90
|
-
packet,
|
|
91
|
-
};
|
|
92
|
-
this.packetEvent.fire(args);
|
|
93
|
-
if (!args.handled) {
|
|
94
|
-
this.dispose();
|
|
95
|
-
throw new Error(`Unhandled packet with command '${packet.command}'`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
} catch (e) {
|
|
99
|
-
if (!this._running) {
|
|
100
|
-
// ignore error
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
this.errorEvent.fire(e);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private handleOk(packet: AdbPacket) {
|
|
109
|
-
if (this.initializers.resolve(packet.arg1, packet.arg0)) {
|
|
110
|
-
// Device successfully created the socket
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const socket = this.sockets.get(packet.arg1);
|
|
115
|
-
if (socket) {
|
|
116
|
-
// Device has received last `WRTE` to the socket
|
|
117
|
-
socket.ack();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Maybe the device is responding to a packet of last connection
|
|
122
|
-
// Tell the device to close the socket
|
|
123
|
-
this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private async handleClose(packet: AdbPacket) {
|
|
127
|
-
// From https://android.googlesource.com/platform/packages/modules/adb/+/65d18e2c1cc48b585811954892311b28a4c3d188/adb.cpp#459
|
|
128
|
-
/* According to protocol.txt, p->msg.arg0 might be 0 to indicate
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// So don't return if `reject` didn't find a pending socket
|
|
135
|
-
if (packet.arg0 === 0 &&
|
|
136
|
-
this.initializers.reject(packet.arg1, new Error('Socket open failed'))) {
|
|
137
|
-
// Device failed to create the socket
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const socket = this.sockets.get(packet.arg1);
|
|
142
|
-
if (socket) {
|
|
143
|
-
// The device want to close the socket
|
|
144
|
-
if (!socket.closed) {
|
|
145
|
-
await this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
146
|
-
}
|
|
147
|
-
socket.dispose();
|
|
148
|
-
this.sockets.delete(packet.arg1);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Maybe the device is responding to a packet of last connection
|
|
153
|
-
// Just ignore it
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private async handleOpen(packet: AdbPacket) {
|
|
157
|
-
// AsyncOperationManager doesn't support get and skip an ID
|
|
158
|
-
// Use `add` + `resolve` to simulate this behavior
|
|
159
|
-
const [localId] = this.initializers.add<number>();
|
|
160
|
-
this.initializers.resolve(localId, undefined);
|
|
161
|
-
|
|
162
|
-
const remoteId = packet.arg0;
|
|
163
|
-
const serviceString =
|
|
164
|
-
|
|
165
|
-
const controller = new AdbSocketController({
|
|
166
|
-
dispatcher: this,
|
|
167
|
-
localId,
|
|
168
|
-
remoteId,
|
|
169
|
-
localCreated: false,
|
|
170
|
-
serviceString,
|
|
171
|
-
});
|
|
172
|
-
const socket = new AdbSocket(controller);
|
|
173
|
-
|
|
174
|
-
const args: AdbIncomingSocketEventArgs = {
|
|
175
|
-
handled: false,
|
|
176
|
-
packet,
|
|
177
|
-
serviceString,
|
|
178
|
-
socket,
|
|
179
|
-
};
|
|
180
|
-
this.incomingSocketEvent.fire(args);
|
|
181
|
-
|
|
182
|
-
if (args.handled) {
|
|
183
|
-
this.sockets.set(localId, controller);
|
|
184
|
-
await this.sendPacket(AdbCommand.OK, localId, remoteId);
|
|
185
|
-
} else {
|
|
186
|
-
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
public start() {
|
|
191
|
-
this._running = true;
|
|
192
|
-
this.receiveLoop();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
public async createSocket(serviceString: string): Promise<AdbSocket> {
|
|
196
|
-
if (this.appendNullToServiceString) {
|
|
197
|
-
serviceString += '\0';
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const [localId, initializer] = this.initializers.add<number>();
|
|
201
|
-
await this.sendPacket(AdbCommand.Open, localId, 0, serviceString);
|
|
202
|
-
|
|
203
|
-
const remoteId = await initializer;
|
|
204
|
-
const controller = new AdbSocketController({
|
|
205
|
-
dispatcher: this,
|
|
206
|
-
localId,
|
|
207
|
-
remoteId,
|
|
208
|
-
localCreated: true,
|
|
209
|
-
serviceString,
|
|
210
|
-
});
|
|
211
|
-
this.sockets.set(controller.localId, controller);
|
|
212
|
-
|
|
213
|
-
return new AdbSocket(controller);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
public sendPacket(packet: AdbPacketInit): Promise<void>;
|
|
217
|
-
public sendPacket(
|
|
218
|
-
command: AdbCommand,
|
|
219
|
-
arg0: number,
|
|
220
|
-
arg1: number,
|
|
221
|
-
payload?: string | ArrayBuffer
|
|
222
|
-
): Promise<void>;
|
|
223
|
-
public async sendPacket(
|
|
224
|
-
packetOrCommand: AdbPacketInit | AdbCommand,
|
|
225
|
-
arg0?: number,
|
|
226
|
-
arg1?: number,
|
|
227
|
-
payload: string | ArrayBuffer = EmptyArrayBuffer,
|
|
228
|
-
): Promise<void> {
|
|
229
|
-
let init: AdbPacketInit;
|
|
230
|
-
if (arg0 === undefined) {
|
|
231
|
-
init = packetOrCommand as AdbPacketInit;
|
|
232
|
-
} else {
|
|
233
|
-
init = {
|
|
234
|
-
command: packetOrCommand as AdbCommand,
|
|
235
|
-
arg0: arg0 as number,
|
|
236
|
-
arg1: arg1 as number,
|
|
237
|
-
payload: typeof payload === 'string' ?
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (init.payload &&
|
|
242
|
-
init.payload.byteLength > this.maxPayloadSize) {
|
|
243
|
-
throw new Error('payload too large');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
// `AdbPacket.write` writes each packet in two parts
|
|
248
|
-
// Use a lock to prevent packets been interlaced
|
|
249
|
-
await this.sendLock.wait();
|
|
250
|
-
|
|
251
|
-
this.logger?.onOutgoingPacket?.(init);
|
|
252
|
-
|
|
253
|
-
await AdbPacket.write(init, this.calculateChecksum, this.backend);
|
|
254
|
-
} finally {
|
|
255
|
-
this.sendLock.notify();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public dispose() {
|
|
260
|
-
this._running = false;
|
|
261
|
-
|
|
262
|
-
for (const socket of this.sockets.values()) {
|
|
263
|
-
socket.dispose();
|
|
264
|
-
}
|
|
265
|
-
this.sockets.clear();
|
|
266
|
-
|
|
267
|
-
super.dispose();
|
|
268
|
-
}
|
|
269
|
-
}
|
|
1
|
+
import { AsyncOperationManager } from '@yume-chan/async';
|
|
2
|
+
import { AutoDisposable, EventEmitter } from '@yume-chan/event';
|
|
3
|
+
import { AdbBackend } from '../backend';
|
|
4
|
+
import { AdbCommand, AdbPacket, AdbPacketInit } from '../packet';
|
|
5
|
+
import { AutoResetEvent, decodeUtf8, encodeUtf8 } from '../utils';
|
|
6
|
+
import { AdbSocketController } from './controller';
|
|
7
|
+
import { AdbLogger } from './logger';
|
|
8
|
+
import { AdbSocket } from './socket';
|
|
9
|
+
|
|
10
|
+
export interface AdbPacketReceivedEventArgs {
|
|
11
|
+
handled: boolean;
|
|
12
|
+
|
|
13
|
+
packet: AdbPacket;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AdbIncomingSocketEventArgs {
|
|
17
|
+
handled: boolean;
|
|
18
|
+
|
|
19
|
+
packet: AdbPacket;
|
|
20
|
+
|
|
21
|
+
serviceString: string;
|
|
22
|
+
|
|
23
|
+
socket: AdbSocket;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const EmptyArrayBuffer = new ArrayBuffer(0);
|
|
27
|
+
|
|
28
|
+
export class AdbPacketDispatcher extends AutoDisposable {
|
|
29
|
+
// ADB socket id starts from 1
|
|
30
|
+
// (0 means open failed)
|
|
31
|
+
private readonly initializers = new AsyncOperationManager(1);
|
|
32
|
+
private readonly sockets = new Map<number, AdbSocketController>();
|
|
33
|
+
private readonly sendLock = new AutoResetEvent();
|
|
34
|
+
private readonly logger: AdbLogger | undefined;
|
|
35
|
+
|
|
36
|
+
public readonly backend: AdbBackend;
|
|
37
|
+
|
|
38
|
+
public maxPayloadSize = 0;
|
|
39
|
+
public calculateChecksum = true;
|
|
40
|
+
public appendNullToServiceString = true;
|
|
41
|
+
|
|
42
|
+
private readonly packetEvent = this.addDisposable(new EventEmitter<AdbPacketReceivedEventArgs>());
|
|
43
|
+
public get onPacket() { return this.packetEvent.event; }
|
|
44
|
+
|
|
45
|
+
private readonly incomingSocketEvent = this.addDisposable(new EventEmitter<AdbIncomingSocketEventArgs>());
|
|
46
|
+
public get onIncomingSocket() { return this.incomingSocketEvent.event; }
|
|
47
|
+
|
|
48
|
+
private readonly errorEvent = this.addDisposable(new EventEmitter<Error>());
|
|
49
|
+
public get onError() { return this.errorEvent.event; }
|
|
50
|
+
|
|
51
|
+
private _running = false;
|
|
52
|
+
public get running() { return this._running; }
|
|
53
|
+
|
|
54
|
+
public constructor(backend: AdbBackend, logger?: AdbLogger) {
|
|
55
|
+
super();
|
|
56
|
+
|
|
57
|
+
this.backend = backend;
|
|
58
|
+
this.logger = logger;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private async receiveLoop() {
|
|
62
|
+
try {
|
|
63
|
+
while (this._running) {
|
|
64
|
+
const packet = await AdbPacket.read(this.backend);
|
|
65
|
+
this.logger?.onIncomingPacket?.(packet);
|
|
66
|
+
|
|
67
|
+
switch (packet.command) {
|
|
68
|
+
case AdbCommand.OK:
|
|
69
|
+
this.handleOk(packet);
|
|
70
|
+
continue;
|
|
71
|
+
case AdbCommand.Close:
|
|
72
|
+
await this.handleClose(packet);
|
|
73
|
+
continue;
|
|
74
|
+
case AdbCommand.Write:
|
|
75
|
+
if (this.sockets.has(packet.arg1)) {
|
|
76
|
+
await this.sockets.get(packet.arg1)!.dataEvent.fire(packet.payload!);
|
|
77
|
+
await this.sendPacket(AdbCommand.OK, packet.arg1, packet.arg0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Maybe the device is responding to a packet of last connection
|
|
81
|
+
// Just ignore it
|
|
82
|
+
continue;
|
|
83
|
+
case AdbCommand.Open:
|
|
84
|
+
await this.handleOpen(packet);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const args: AdbPacketReceivedEventArgs = {
|
|
89
|
+
handled: false,
|
|
90
|
+
packet,
|
|
91
|
+
};
|
|
92
|
+
this.packetEvent.fire(args);
|
|
93
|
+
if (!args.handled) {
|
|
94
|
+
this.dispose();
|
|
95
|
+
throw new Error(`Unhandled packet with command '${packet.command}'`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
if (!this._running) {
|
|
100
|
+
// ignore error
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.errorEvent.fire(e as Error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private handleOk(packet: AdbPacket) {
|
|
109
|
+
if (this.initializers.resolve(packet.arg1, packet.arg0)) {
|
|
110
|
+
// Device successfully created the socket
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const socket = this.sockets.get(packet.arg1);
|
|
115
|
+
if (socket) {
|
|
116
|
+
// Device has received last `WRTE` to the socket
|
|
117
|
+
socket.ack();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Maybe the device is responding to a packet of last connection
|
|
122
|
+
// Tell the device to close the socket
|
|
123
|
+
this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async handleClose(packet: AdbPacket) {
|
|
127
|
+
// From https://android.googlesource.com/platform/packages/modules/adb/+/65d18e2c1cc48b585811954892311b28a4c3d188/adb.cpp#459
|
|
128
|
+
/* According to protocol.txt, p->msg.arg0 might be 0 to indicate
|
|
129
|
+
* a failed OPEN only. However, due to a bug in previous ADB
|
|
130
|
+
* versions, CLOSE(0, remote-id, "") was also used for normal
|
|
131
|
+
* CLOSE() operations.
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
// So don't return if `reject` didn't find a pending socket
|
|
135
|
+
if (packet.arg0 === 0 &&
|
|
136
|
+
this.initializers.reject(packet.arg1, new Error('Socket open failed'))) {
|
|
137
|
+
// Device failed to create the socket
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const socket = this.sockets.get(packet.arg1);
|
|
142
|
+
if (socket) {
|
|
143
|
+
// The device want to close the socket
|
|
144
|
+
if (!socket.closed) {
|
|
145
|
+
await this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
|
146
|
+
}
|
|
147
|
+
socket.dispose();
|
|
148
|
+
this.sockets.delete(packet.arg1);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Maybe the device is responding to a packet of last connection
|
|
153
|
+
// Just ignore it
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async handleOpen(packet: AdbPacket) {
|
|
157
|
+
// AsyncOperationManager doesn't support get and skip an ID
|
|
158
|
+
// Use `add` + `resolve` to simulate this behavior
|
|
159
|
+
const [localId] = this.initializers.add<number>();
|
|
160
|
+
this.initializers.resolve(localId, undefined);
|
|
161
|
+
|
|
162
|
+
const remoteId = packet.arg0;
|
|
163
|
+
const serviceString = decodeUtf8(packet.payload!);
|
|
164
|
+
|
|
165
|
+
const controller = new AdbSocketController({
|
|
166
|
+
dispatcher: this,
|
|
167
|
+
localId,
|
|
168
|
+
remoteId,
|
|
169
|
+
localCreated: false,
|
|
170
|
+
serviceString,
|
|
171
|
+
});
|
|
172
|
+
const socket = new AdbSocket(controller);
|
|
173
|
+
|
|
174
|
+
const args: AdbIncomingSocketEventArgs = {
|
|
175
|
+
handled: false,
|
|
176
|
+
packet,
|
|
177
|
+
serviceString,
|
|
178
|
+
socket,
|
|
179
|
+
};
|
|
180
|
+
this.incomingSocketEvent.fire(args);
|
|
181
|
+
|
|
182
|
+
if (args.handled) {
|
|
183
|
+
this.sockets.set(localId, controller);
|
|
184
|
+
await this.sendPacket(AdbCommand.OK, localId, remoteId);
|
|
185
|
+
} else {
|
|
186
|
+
await this.sendPacket(AdbCommand.Close, 0, remoteId);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public start() {
|
|
191
|
+
this._running = true;
|
|
192
|
+
this.receiveLoop();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public async createSocket(serviceString: string): Promise<AdbSocket> {
|
|
196
|
+
if (this.appendNullToServiceString) {
|
|
197
|
+
serviceString += '\0';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const [localId, initializer] = this.initializers.add<number>();
|
|
201
|
+
await this.sendPacket(AdbCommand.Open, localId, 0, serviceString);
|
|
202
|
+
|
|
203
|
+
const remoteId = await initializer;
|
|
204
|
+
const controller = new AdbSocketController({
|
|
205
|
+
dispatcher: this,
|
|
206
|
+
localId,
|
|
207
|
+
remoteId,
|
|
208
|
+
localCreated: true,
|
|
209
|
+
serviceString,
|
|
210
|
+
});
|
|
211
|
+
this.sockets.set(controller.localId, controller);
|
|
212
|
+
|
|
213
|
+
return new AdbSocket(controller);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public sendPacket(packet: AdbPacketInit): Promise<void>;
|
|
217
|
+
public sendPacket(
|
|
218
|
+
command: AdbCommand,
|
|
219
|
+
arg0: number,
|
|
220
|
+
arg1: number,
|
|
221
|
+
payload?: string | ArrayBuffer
|
|
222
|
+
): Promise<void>;
|
|
223
|
+
public async sendPacket(
|
|
224
|
+
packetOrCommand: AdbPacketInit | AdbCommand,
|
|
225
|
+
arg0?: number,
|
|
226
|
+
arg1?: number,
|
|
227
|
+
payload: string | ArrayBuffer = EmptyArrayBuffer,
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
let init: AdbPacketInit;
|
|
230
|
+
if (arg0 === undefined) {
|
|
231
|
+
init = packetOrCommand as AdbPacketInit;
|
|
232
|
+
} else {
|
|
233
|
+
init = {
|
|
234
|
+
command: packetOrCommand as AdbCommand,
|
|
235
|
+
arg0: arg0 as number,
|
|
236
|
+
arg1: arg1 as number,
|
|
237
|
+
payload: typeof payload === 'string' ? encodeUtf8(payload) : payload,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (init.payload &&
|
|
242
|
+
init.payload.byteLength > this.maxPayloadSize) {
|
|
243
|
+
throw new Error('payload too large');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
// `AdbPacket.write` writes each packet in two parts
|
|
248
|
+
// Use a lock to prevent packets been interlaced
|
|
249
|
+
await this.sendLock.wait();
|
|
250
|
+
|
|
251
|
+
this.logger?.onOutgoingPacket?.(init);
|
|
252
|
+
|
|
253
|
+
await AdbPacket.write(init, this.calculateChecksum, this.backend);
|
|
254
|
+
} finally {
|
|
255
|
+
this.sendLock.notify();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public override dispose() {
|
|
260
|
+
this._running = false;
|
|
261
|
+
|
|
262
|
+
for (const socket of this.sockets.values()) {
|
|
263
|
+
socket.dispose();
|
|
264
|
+
}
|
|
265
|
+
this.sockets.clear();
|
|
266
|
+
|
|
267
|
+
super.dispose();
|
|
268
|
+
}
|
|
269
|
+
}
|
package/src/socket/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export * from './close-event-emitter';
|
|
2
|
-
export * from './controller';
|
|
3
|
-
export * from './data-event-emitter';
|
|
4
|
-
export * from './dispatcher';
|
|
5
|
-
export * from './logger';
|
|
6
|
-
export * from './socket';
|
|
1
|
+
export * from './close-event-emitter';
|
|
2
|
+
export * from './controller';
|
|
3
|
+
export * from './data-event-emitter';
|
|
4
|
+
export * from './dispatcher';
|
|
5
|
+
export * from './logger';
|
|
6
|
+
export * from './socket';
|
package/src/socket/logger.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { AdbPacket, AdbPacketInit } from '../packet';
|
|
2
|
-
import { AdbSocket } from './socket';
|
|
3
|
-
|
|
4
|
-
export interface AdbLogger {
|
|
5
|
-
onIncomingPacket?(packet: AdbPacket): void;
|
|
6
|
-
|
|
7
|
-
onOutgoingPacket?(packet: AdbPacketInit): void;
|
|
8
|
-
|
|
9
|
-
onSocketOpened?(socket: AdbSocket): void;
|
|
10
|
-
|
|
11
|
-
onSocketClosed?(socket: AdbSocket): void;
|
|
12
|
-
}
|
|
1
|
+
import { AdbPacket, AdbPacketInit } from '../packet';
|
|
2
|
+
import { AdbSocket } from './socket';
|
|
3
|
+
|
|
4
|
+
export interface AdbLogger {
|
|
5
|
+
onIncomingPacket?(packet: AdbPacket): void;
|
|
6
|
+
|
|
7
|
+
onOutgoingPacket?(packet: AdbPacketInit): void;
|
|
8
|
+
|
|
9
|
+
onSocketOpened?(socket: AdbSocket): void;
|
|
10
|
+
|
|
11
|
+
onSocketClosed?(socket: AdbSocket): void;
|
|
12
|
+
}
|
package/src/socket/socket.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { AdbSocketController, AdbSocketInfo } from './controller';
|
|
2
|
-
|
|
3
|
-
export class AdbSocket implements AdbSocketInfo {
|
|
4
|
-
private readonly controller: AdbSocketController;
|
|
5
|
-
|
|
6
|
-
public get backend() { return this.controller.backend; }
|
|
7
|
-
public get localId() { return this.controller.localId; }
|
|
8
|
-
public get remoteId() { return this.controller.remoteId; }
|
|
9
|
-
public get localCreated() { return this.controller.localCreated; }
|
|
10
|
-
public get serviceString() { return this.controller.serviceString; }
|
|
11
|
-
|
|
12
|
-
public get closed() { return this.controller.closed; }
|
|
13
|
-
|
|
14
|
-
public get onData() { return this.controller.dataEvent.event; }
|
|
15
|
-
public get onClose() { return this.controller.onClose; }
|
|
16
|
-
|
|
17
|
-
public constructor(controller: AdbSocketController) {
|
|
18
|
-
this.controller = controller;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public write(data: ArrayBuffer): Promise<void> {
|
|
22
|
-
return this.controller.write(data);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public close(): Promise<void> {
|
|
26
|
-
return this.controller.close();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
import { AdbSocketController, AdbSocketInfo } from './controller';
|
|
2
|
+
|
|
3
|
+
export class AdbSocket implements AdbSocketInfo {
|
|
4
|
+
private readonly controller: AdbSocketController;
|
|
5
|
+
|
|
6
|
+
public get backend() { return this.controller.backend; }
|
|
7
|
+
public get localId() { return this.controller.localId; }
|
|
8
|
+
public get remoteId() { return this.controller.remoteId; }
|
|
9
|
+
public get localCreated() { return this.controller.localCreated; }
|
|
10
|
+
public get serviceString() { return this.controller.serviceString; }
|
|
11
|
+
|
|
12
|
+
public get closed() { return this.controller.closed; }
|
|
13
|
+
|
|
14
|
+
public get onData() { return this.controller.dataEvent.event; }
|
|
15
|
+
public get onClose() { return this.controller.onClose; }
|
|
16
|
+
|
|
17
|
+
public constructor(controller: AdbSocketController) {
|
|
18
|
+
this.controller = controller;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public write(data: ArrayBuffer): Promise<void> {
|
|
22
|
+
return this.controller.write(data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public close(): Promise<void> {
|
|
26
|
+
return this.controller.close();
|
|
27
|
+
}
|
|
28
|
+
}
|