@yume-chan/adb 0.0.23 → 1.0.0
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.md +51 -2
- package/LICENSE +1 -1
- package/README.md +4 -1
- package/esm/adb.d.ts +15 -9
- package/esm/adb.d.ts.map +1 -1
- package/esm/adb.js +5 -2
- package/esm/adb.js.map +1 -1
- package/esm/banner.d.ts +7 -6
- package/esm/banner.d.ts.map +1 -1
- package/esm/banner.js +6 -7
- package/esm/banner.js.map +1 -1
- package/esm/commands/framebuffer.d.ts +63 -34
- package/esm/commands/framebuffer.d.ts.map +1 -1
- package/esm/commands/framebuffer.js +35 -33
- package/esm/commands/framebuffer.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 +12 -12
- package/esm/commands/reverse.d.ts.map +1 -1
- package/esm/commands/reverse.js +60 -39
- 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/none.js +3 -5
- package/esm/commands/subprocess/protocols/none.js.map +1 -1
- package/esm/commands/subprocess/protocols/shell.d.ts +22 -14
- package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
- package/esm/commands/subprocess/protocols/shell.js +21 -27
- package/esm/commands/subprocess/protocols/shell.js.map +1 -1
- package/esm/commands/subprocess/protocols/types.d.ts +8 -8
- package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
- package/esm/commands/sync/list.d.ts +49 -30
- package/esm/commands/sync/list.d.ts.map +1 -1
- package/esm/commands/sync/list.js +9 -11
- package/esm/commands/sync/list.js.map +1 -1
- package/esm/commands/sync/pull.d.ts +7 -9
- package/esm/commands/sync/pull.d.ts.map +1 -1
- package/esm/commands/sync/pull.js +4 -6
- package/esm/commands/sync/pull.js.map +1 -1
- package/esm/commands/sync/push.d.ts +25 -22
- package/esm/commands/sync/push.d.ts.map +1 -1
- package/esm/commands/sync/push.js +14 -21
- package/esm/commands/sync/push.js.map +1 -1
- package/esm/commands/sync/request.d.ts +20 -23
- package/esm/commands/sync/request.d.ts.map +1 -1
- package/esm/commands/sync/request.js +26 -35
- package/esm/commands/sync/request.js.map +1 -1
- package/esm/commands/sync/response.d.ts +25 -19
- package/esm/commands/sync/response.d.ts.map +1 -1
- package/esm/commands/sync/response.js +49 -26
- package/esm/commands/sync/response.js.map +1 -1
- package/esm/commands/sync/socket.d.ts +3 -3
- 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 +75 -54
- package/esm/commands/sync/stat.d.ts.map +1 -1
- package/esm/commands/sync/stat.js +73 -69
- package/esm/commands/sync/stat.js.map +1 -1
- package/esm/commands/sync/sync.d.ts +18 -17
- package/esm/commands/sync/sync.d.ts.map +1 -1
- package/esm/commands/sync/sync.js +8 -8
- package/esm/commands/sync/sync.js.map +1 -1
- package/esm/commands/tcpip.d.ts +0 -1
- package/esm/commands/tcpip.d.ts.map +1 -1
- package/esm/commands/tcpip.js +9 -9
- package/esm/commands/tcpip.js.map +1 -1
- package/esm/daemon/auth.d.ts +8 -7
- package/esm/daemon/auth.d.ts.map +1 -1
- package/esm/daemon/auth.js +8 -9
- package/esm/daemon/auth.js.map +1 -1
- package/esm/daemon/crypto.d.ts +4 -3
- package/esm/daemon/crypto.d.ts.map +1 -1
- package/esm/daemon/crypto.js +57 -49
- package/esm/daemon/crypto.js.map +1 -1
- package/esm/daemon/device.d.ts +2 -2
- package/esm/daemon/device.d.ts.map +1 -1
- package/esm/daemon/dispatcher.d.ts +35 -10
- package/esm/daemon/dispatcher.d.ts.map +1 -1
- package/esm/daemon/dispatcher.js +46 -33
- package/esm/daemon/dispatcher.js.map +1 -1
- package/esm/daemon/packet.d.ts +48 -32
- package/esm/daemon/packet.d.ts.map +1 -1
- package/esm/daemon/packet.js +36 -32
- package/esm/daemon/packet.js.map +1 -1
- package/esm/daemon/socket.d.ts +11 -11
- package/esm/daemon/socket.d.ts.map +1 -1
- package/esm/daemon/socket.js +19 -40
- package/esm/daemon/socket.js.map +1 -1
- package/esm/daemon/transport.d.ts +60 -20
- package/esm/daemon/transport.d.ts.map +1 -1
- package/esm/daemon/transport.js +12 -15
- package/esm/daemon/transport.js.map +1 -1
- package/esm/device-observer.d.ts +10 -0
- package/esm/device-observer.d.ts.map +1 -0
- package/esm/device-observer.js +2 -0
- package/esm/device-observer.js.map +1 -0
- package/esm/features.d.ts +12 -11
- package/esm/features.d.ts.map +1 -1
- package/esm/features.js +11 -12
- package/esm/features.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/server/client.d.ts +140 -50
- package/esm/server/client.d.ts.map +1 -1
- package/esm/server/client.js +396 -180
- package/esm/server/client.js.map +1 -1
- package/esm/server/transport.d.ts +2 -2
- package/esm/server/transport.d.ts.map +1 -1
- package/esm/server/transport.js +4 -6
- package/esm/server/transport.js.map +1 -1
- package/esm/utils/base64.d.ts.map +1 -1
- package/esm/utils/base64.js +52 -38
- 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/no-op.d.ts.map +1 -1
- package/esm/utils/no-op.js +1 -0
- package/esm/utils/no-op.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 +14 -19
- package/src/adb.ts +20 -11
- package/src/banner.ts +8 -6
- package/src/commands/framebuffer.ts +44 -35
- package/src/commands/power.ts +7 -5
- package/src/commands/reverse.ts +78 -47
- package/src/commands/subprocess/command.ts +3 -3
- package/src/commands/subprocess/protocols/none.ts +5 -7
- package/src/commands/subprocess/protocols/shell.ts +33 -35
- package/src/commands/subprocess/protocols/types.ts +8 -8
- package/src/commands/sync/list.ts +20 -15
- package/src/commands/sync/pull.ts +9 -8
- package/src/commands/sync/push.ts +25 -24
- package/src/commands/sync/request.ts +37 -38
- package/src/commands/sync/response.ts +63 -37
- package/src/commands/sync/socket.ts +13 -8
- package/src/commands/sync/stat.ts +93 -73
- package/src/commands/sync/sync.ts +12 -11
- package/src/commands/tcpip.ts +9 -9
- package/src/daemon/auth.ts +12 -10
- package/src/daemon/crypto.ts +66 -55
- package/src/daemon/device.ts +2 -2
- package/src/daemon/dispatcher.ts +106 -44
- package/src/daemon/packet.ts +57 -38
- package/src/daemon/socket.ts +30 -50
- package/src/daemon/transport.ts +98 -50
- package/src/device-observer.ts +10 -0
- package/src/features.ts +13 -11
- package/src/index.ts +1 -0
- package/src/server/client.ts +505 -268
- package/src/server/transport.ts +7 -9
- package/src/utils/base64.ts +59 -40
- package/src/utils/hex.ts +16 -14
- package/src/utils/index.ts +1 -1
- package/src/utils/no-op.ts +1 -0
- package/src/utils/sequence-equal.ts +13 -0
- package/tsconfig.build.json +1 -12
- package/tsconfig.build.tsbuildinfo +1 -1
- package/CHANGELOG.json +0 -265
- 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/commands/tcpip.ts
CHANGED
|
@@ -20,14 +20,14 @@ export interface AdbTcpIpListenAddresses {
|
|
|
20
20
|
persistPort: number | undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
return Number.parseInt(value, 10);
|
|
23
|
+
function parsePort(value: string): number | undefined {
|
|
24
|
+
if (!value || value === "0") {
|
|
25
|
+
return undefined;
|
|
29
26
|
}
|
|
27
|
+
return Number.parseInt(value, 10);
|
|
28
|
+
}
|
|
30
29
|
|
|
30
|
+
export class AdbTcpIpCommand extends AdbCommandBase {
|
|
31
31
|
async getListenAddresses(): Promise<AdbTcpIpListenAddresses> {
|
|
32
32
|
const serviceListenAddresses = await this.adb.getProp(
|
|
33
33
|
"service.adb.listen_addrs",
|
|
@@ -40,14 +40,14 @@ export class AdbTcpIpCommand extends AdbCommandBase {
|
|
|
40
40
|
serviceListenAddresses != ""
|
|
41
41
|
? serviceListenAddresses.split(",")
|
|
42
42
|
: [],
|
|
43
|
-
servicePort:
|
|
44
|
-
persistPort:
|
|
43
|
+
servicePort: parsePort(servicePort),
|
|
44
|
+
persistPort: parsePort(persistPort),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async setPort(port: number): Promise<string> {
|
|
49
49
|
if (port <= 0) {
|
|
50
|
-
throw new
|
|
50
|
+
throw new TypeError(`Invalid port ${port}`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const output = await this.adb.createSocketAndWait(`tcpip:${port}`);
|
package/src/daemon/auth.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
1
2
|
import { PromiseResolver } from "@yume-chan/async";
|
|
2
3
|
import type { Disposable } from "@yume-chan/event";
|
|
3
|
-
import
|
|
4
|
-
import { EMPTY_UINT8_ARRAY } from "@yume-chan/struct";
|
|
4
|
+
import { EmptyUint8Array } from "@yume-chan/struct";
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
calculateBase64EncodedLength,
|
|
@@ -33,7 +33,7 @@ export interface AdbCredentialStore {
|
|
|
33
33
|
/**
|
|
34
34
|
* Generates and stores a RSA private key with modulus length `2048` and public exponent `65537`.
|
|
35
35
|
*/
|
|
36
|
-
generateKey():
|
|
36
|
+
generateKey(): MaybePromiseLike<AdbPrivateKey>;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Synchronously or asynchronously iterates through all stored RSA private keys.
|
|
@@ -43,11 +43,13 @@ export interface AdbCredentialStore {
|
|
|
43
43
|
iterateKeys(): AdbKeyIterable;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export
|
|
47
|
-
Token
|
|
48
|
-
Signature
|
|
49
|
-
PublicKey
|
|
50
|
-
}
|
|
46
|
+
export const AdbAuthType = {
|
|
47
|
+
Token: 1,
|
|
48
|
+
Signature: 2,
|
|
49
|
+
PublicKey: 3,
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
export type AdbAuthType = (typeof AdbAuthType)[keyof typeof AdbAuthType];
|
|
51
53
|
|
|
52
54
|
export interface AdbAuthenticator {
|
|
53
55
|
/**
|
|
@@ -83,7 +85,7 @@ export const AdbSignatureAuthenticator: AdbAuthenticator = async function* (
|
|
|
83
85
|
command: AdbCommand.Auth,
|
|
84
86
|
arg0: AdbAuthType.Signature,
|
|
85
87
|
arg1: 0,
|
|
86
|
-
payload:
|
|
88
|
+
payload: signature,
|
|
87
89
|
};
|
|
88
90
|
}
|
|
89
91
|
};
|
|
@@ -114,7 +116,7 @@ export const AdbPublicKeyAuthenticator: AdbAuthenticator = async function* (
|
|
|
114
116
|
|
|
115
117
|
const nameBuffer = privateKey.name?.length
|
|
116
118
|
? encodeUtf8(privateKey.name)
|
|
117
|
-
:
|
|
119
|
+
: EmptyUint8Array;
|
|
118
120
|
const publicKeyBuffer = new Uint8Array(
|
|
119
121
|
publicKeyBase64Length +
|
|
120
122
|
(nameBuffer.length ? nameBuffer.length + 1 : 0) + // Space character + name
|
package/src/daemon/crypto.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
getUint64BigEndian,
|
|
3
|
+
setInt64BigEndian,
|
|
4
|
+
setInt64LittleEndian,
|
|
5
|
+
} from "@yume-chan/no-data-view";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Gets the `BigInt` value at the specified byte offset and length from the start of the view. There is
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
* @param byteOffset The place in the buffer at which the value should be retrieved.
|
|
12
13
|
*/
|
|
13
14
|
export function getBigUint(
|
|
14
|
-
|
|
15
|
+
array: Uint8Array,
|
|
15
16
|
byteOffset: number,
|
|
16
17
|
length: number,
|
|
17
18
|
): bigint {
|
|
@@ -22,8 +23,8 @@ export function getBigUint(
|
|
|
22
23
|
|
|
23
24
|
for (let i = byteOffset; i < byteOffset + length; i += 8) {
|
|
24
25
|
result <<= 64n;
|
|
25
|
-
const value =
|
|
26
|
-
result
|
|
26
|
+
const value = getUint64BigEndian(array, i);
|
|
27
|
+
result |= value;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
return result;
|
|
@@ -32,40 +33,34 @@ export function getBigUint(
|
|
|
32
33
|
/**
|
|
33
34
|
* Stores an arbitrary-precision positive `BigInt` value at the specified byte offset from the start of the view.
|
|
34
35
|
* @param byteOffset The place in the buffer at which the value should be set.
|
|
36
|
+
* @param length The number of bytes to set.
|
|
35
37
|
* @param value The value to set.
|
|
36
38
|
* @param littleEndian If `false` or `undefined`, a big-endian value should be written,
|
|
37
39
|
* otherwise a little-endian value should be written.
|
|
38
40
|
*/
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
39
43
|
export function setBigUint(
|
|
40
|
-
|
|
44
|
+
array: Uint8Array,
|
|
41
45
|
byteOffset: number,
|
|
46
|
+
length: number,
|
|
42
47
|
value: bigint,
|
|
43
48
|
littleEndian?: boolean,
|
|
44
49
|
) {
|
|
45
|
-
const start = byteOffset;
|
|
46
|
-
|
|
47
50
|
if (littleEndian) {
|
|
48
51
|
while (value > 0n) {
|
|
49
|
-
|
|
52
|
+
setInt64LittleEndian(array, byteOffset, value);
|
|
50
53
|
byteOffset += 8;
|
|
51
54
|
value >>= 64n;
|
|
52
55
|
}
|
|
53
56
|
} else {
|
|
54
|
-
|
|
55
|
-
// Convert it to an array of `uint64` first.
|
|
56
|
-
const uint64Array: bigint[] = [];
|
|
57
|
+
let position = byteOffset + length - 8;
|
|
57
58
|
while (value > 0n) {
|
|
58
|
-
|
|
59
|
+
setInt64BigEndian(array, position, value);
|
|
60
|
+
position -= 8;
|
|
59
61
|
value >>= 64n;
|
|
60
62
|
}
|
|
61
|
-
|
|
62
|
-
for (let i = uint64Array.length - 1; i >= 0; i -= 1) {
|
|
63
|
-
setBigUint64(dataView, byteOffset, uint64Array[i]!, false);
|
|
64
|
-
byteOffset += 8;
|
|
65
|
-
}
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
return byteOffset - start;
|
|
69
64
|
}
|
|
70
65
|
|
|
71
66
|
// These values are correct only if
|
|
@@ -95,17 +90,26 @@ const RsaPrivateKeyDOffset = 303;
|
|
|
95
90
|
const RsaPrivateKeyDLength = 2048 / 8;
|
|
96
91
|
|
|
97
92
|
export function rsaParsePrivateKey(key: Uint8Array): [n: bigint, d: bigint] {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const d = getBigUint(view, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
|
93
|
+
const n = getBigUint(key, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
|
|
94
|
+
const d = getBigUint(key, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
|
|
101
95
|
return [n, d];
|
|
102
96
|
}
|
|
103
97
|
|
|
98
|
+
function nonNegativeMod(m: number, d: number) {
|
|
99
|
+
const r = m % d;
|
|
100
|
+
if (r > 0) {
|
|
101
|
+
return r;
|
|
102
|
+
}
|
|
103
|
+
return r + (d > 0 ? d : -d);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
|
|
107
|
+
// Solve for the smallest positive `x` in the equation `a * x ≡ 1 (mod m)`,
|
|
108
|
+
// or in other words, `a * x % m = 1`
|
|
104
109
|
// Taken from https://stackoverflow.com/a/51562038
|
|
105
|
-
// I can't understand, but it does work
|
|
106
110
|
// Only used with numbers smaller than 2^32 so doesn't need BigInt
|
|
107
111
|
export function modInverse(a: number, m: number) {
|
|
108
|
-
a = (
|
|
112
|
+
a = nonNegativeMod(a, m);
|
|
109
113
|
if (!a || m < 2) {
|
|
110
114
|
return NaN; // invalid input
|
|
111
115
|
}
|
|
@@ -116,6 +120,7 @@ export function modInverse(a: number, m: number) {
|
|
|
116
120
|
[a, b] = [b, a % b];
|
|
117
121
|
s.push({ a, b });
|
|
118
122
|
}
|
|
123
|
+
/* istanbul ignore next */
|
|
119
124
|
if (a !== 1) {
|
|
120
125
|
return NaN; // inverse does not exists
|
|
121
126
|
}
|
|
@@ -125,11 +130,14 @@ export function modInverse(a: number, m: number) {
|
|
|
125
130
|
for (let i = s.length - 2; i >= 0; i -= 1) {
|
|
126
131
|
[x, y] = [y, x - y * Math.floor(s[i]!.a / s[i]!.b)];
|
|
127
132
|
}
|
|
128
|
-
return (
|
|
133
|
+
return nonNegativeMod(y, m);
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
const ModulusLengthInBytes = 2048 / 8;
|
|
137
|
+
const ModulusLengthInWords = ModulusLengthInBytes / 4;
|
|
138
|
+
|
|
131
139
|
export function adbGetPublicKeySize() {
|
|
132
|
-
return 4 + 4 +
|
|
140
|
+
return 4 + 4 + ModulusLengthInBytes + ModulusLengthInBytes + 4;
|
|
133
141
|
}
|
|
134
142
|
|
|
135
143
|
export function adbGeneratePublicKey(privateKey: Uint8Array): Uint8Array;
|
|
@@ -141,22 +149,27 @@ export function adbGeneratePublicKey(
|
|
|
141
149
|
privateKey: Uint8Array,
|
|
142
150
|
output?: Uint8Array,
|
|
143
151
|
): Uint8Array | number {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
//
|
|
152
|
+
// cspell: ignore: mincrypt
|
|
153
|
+
// Android 6 and earlier has its own encryption library called mincrypt
|
|
154
|
+
// This is the RSA public key format used by mincrypt:
|
|
155
|
+
// https://android.googlesource.com/platform/system/core/+/bb0c180e62703c2068a1b2c9f8ba6d634bf1553c/include/mincrypt/rsa.h#46
|
|
156
|
+
// `n0inv` and `rr` are pre-calculated to speed up RSA operations
|
|
157
|
+
|
|
158
|
+
// Android 7 switched its encryption library to BoringSSL, but still keeps the key format:
|
|
159
|
+
// https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
|
|
160
|
+
// Except when reading a key, `n0inv` and `rr` are ignored (they are still populated when generating a key):
|
|
161
|
+
// https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#55
|
|
162
|
+
|
|
163
|
+
// The public key is a struct (in little endian) of:
|
|
148
164
|
//
|
|
149
165
|
// [
|
|
150
166
|
// modulusLengthInWords, // 32-bit integer, a "word" is 32-bit so it must be 2048 / 8 / 4
|
|
151
|
-
// //
|
|
152
|
-
// n0inv, // 32-bit integer, the modular inverse of (
|
|
153
|
-
// modulus, // n
|
|
167
|
+
// // (the comment in Android source code is incorrect saying "This must be ANDROID_PUBKEY_MODULUS_SIZE")
|
|
168
|
+
// n0inv, // 32-bit integer, the modular inverse of (lower 32 bits of `n`)
|
|
169
|
+
// modulus, // `n`
|
|
154
170
|
// rr, // Montgomery parameter R^2
|
|
155
|
-
// exponent, // 32-bit integer, must be 65537
|
|
171
|
+
// exponent, // 32-bit integer, must be 3 or 65537
|
|
156
172
|
// ]
|
|
157
|
-
//
|
|
158
|
-
// (All in little endian)
|
|
159
|
-
// See https://android.googlesource.com/platform/system/core.git/+/91784040db2b9273687f88d8b95f729d4a61ecc2/libcrypto_utils/android_pubkey.cpp#38
|
|
160
173
|
|
|
161
174
|
let outputType: "Uint8Array" | "number";
|
|
162
175
|
const outputLength = adbGetPublicKeySize();
|
|
@@ -164,8 +177,8 @@ export function adbGeneratePublicKey(
|
|
|
164
177
|
output = new Uint8Array(outputLength);
|
|
165
178
|
outputType = "Uint8Array";
|
|
166
179
|
} else {
|
|
167
|
-
if (output.
|
|
168
|
-
throw new
|
|
180
|
+
if (output.length < outputLength) {
|
|
181
|
+
throw new TypeError("output buffer is too small");
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
outputType = "number";
|
|
@@ -174,31 +187,30 @@ export function adbGeneratePublicKey(
|
|
|
174
187
|
const outputView = new DataView(
|
|
175
188
|
output.buffer,
|
|
176
189
|
output.byteOffset,
|
|
177
|
-
output.
|
|
190
|
+
output.length,
|
|
178
191
|
);
|
|
179
192
|
let outputOffset = 0;
|
|
180
193
|
|
|
181
194
|
// modulusLengthInWords
|
|
182
|
-
outputView.setUint32(outputOffset,
|
|
195
|
+
outputView.setUint32(outputOffset, ModulusLengthInWords, true);
|
|
183
196
|
outputOffset += 4;
|
|
184
197
|
|
|
185
198
|
// extract `n` from private key
|
|
186
199
|
const [n] = rsaParsePrivateKey(privateKey);
|
|
187
200
|
|
|
188
201
|
// Calculate `n0inv`
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const n0inv = modInverse(-Number(BigInt.asUintN(32, n)), 2 ** 32);
|
|
192
|
-
outputView.setUint32(outputOffset, n0inv, true);
|
|
202
|
+
const n0inv = -modInverse(Number(n % 2n ** 32n), 2 ** 32);
|
|
203
|
+
outputView.setInt32(outputOffset, n0inv, true);
|
|
193
204
|
outputOffset += 4;
|
|
194
205
|
|
|
195
|
-
// Write n
|
|
196
|
-
setBigUint(
|
|
197
|
-
outputOffset +=
|
|
206
|
+
// Write `n` (a.k.a. `modulus`)
|
|
207
|
+
setBigUint(output, outputOffset, ModulusLengthInBytes, n, true);
|
|
208
|
+
outputOffset += ModulusLengthInBytes;
|
|
198
209
|
|
|
199
|
-
// Calculate rr = (2
|
|
210
|
+
// Calculate rr = (2 ** (rsa_size)) ** 2 % n
|
|
200
211
|
const rr = 2n ** 4096n % n;
|
|
201
|
-
|
|
212
|
+
setBigUint(output, outputOffset, ModulusLengthInBytes, rr, true);
|
|
213
|
+
outputOffset += ModulusLengthInBytes;
|
|
202
214
|
|
|
203
215
|
// exponent
|
|
204
216
|
outputView.setUint32(outputOffset, 65537, true);
|
|
@@ -305,12 +317,11 @@ export function rsaSign(privateKey: Uint8Array, data: Uint8Array): Uint8Array {
|
|
|
305
317
|
|
|
306
318
|
// Encryption
|
|
307
319
|
// signature = padded ** d % n
|
|
308
|
-
const
|
|
309
|
-
const signature = powMod(getBigUint(view, 0, view.byteLength), d, n);
|
|
320
|
+
const signature = powMod(getBigUint(padded, 0, padded.length), d, n);
|
|
310
321
|
|
|
311
322
|
// `padded` is not used anymore,
|
|
312
323
|
// re-use the buffer to store the result
|
|
313
|
-
setBigUint(
|
|
324
|
+
setBigUint(padded, 0, padded.length, signature, false);
|
|
314
325
|
|
|
315
326
|
return padded;
|
|
316
327
|
}
|
package/src/daemon/device.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
1
2
|
import type { Consumable, ReadableWritablePair } from "@yume-chan/stream-extra";
|
|
2
|
-
import type { ValueOrPromise } from "@yume-chan/struct";
|
|
3
3
|
|
|
4
4
|
import type { AdbPacketData, AdbPacketInit } from "./packet.js";
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ export interface AdbDaemonDevice {
|
|
|
8
8
|
|
|
9
9
|
readonly name: string | undefined;
|
|
10
10
|
|
|
11
|
-
connect():
|
|
11
|
+
connect(): MaybePromiseLike<
|
|
12
12
|
ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
|
|
13
13
|
>;
|
|
14
14
|
}
|
package/src/daemon/dispatcher.ts
CHANGED
|
@@ -3,47 +3,78 @@ 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 {
|
|
19
|
+
import { EmptyUint8Array, 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
|
-
*
|
|
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
|
|
52
|
+
*/
|
|
53
|
+
preserveConnection?: boolean | undefined;
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
*
|
|
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.
|
|
40
63
|
*/
|
|
41
64
|
initialDelayedAckBytes: number;
|
|
65
|
+
|
|
42
66
|
/**
|
|
43
|
-
*
|
|
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
|
|
44
76
|
*/
|
|
45
|
-
|
|
46
|
-
debugSlowRead?: boolean | undefined;
|
|
77
|
+
readTimeLimit?: number | undefined;
|
|
47
78
|
}
|
|
48
79
|
|
|
49
80
|
interface SocketOpenResult {
|
|
@@ -187,14 +218,14 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
187
218
|
#handleOkay(packet: AdbPacketData) {
|
|
188
219
|
let ackBytes: number;
|
|
189
220
|
if (this.options.initialDelayedAckBytes !== 0) {
|
|
190
|
-
if (packet.payload.
|
|
221
|
+
if (packet.payload.length !== 4) {
|
|
191
222
|
throw new Error(
|
|
192
223
|
"Invalid OKAY packet. Payload size should be 4",
|
|
193
224
|
);
|
|
194
225
|
}
|
|
195
|
-
ackBytes =
|
|
226
|
+
ackBytes = getUint32LittleEndian(packet.payload, 0);
|
|
196
227
|
} else {
|
|
197
|
-
if (packet.payload.
|
|
228
|
+
if (packet.payload.length !== 0) {
|
|
198
229
|
throw new Error(
|
|
199
230
|
"Invalid OKAY packet. Payload size should be 0",
|
|
200
231
|
);
|
|
@@ -224,38 +255,51 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
224
255
|
|
|
225
256
|
// Maybe the device is responding to a packet of last connection
|
|
226
257
|
// Tell the device to close the socket
|
|
227
|
-
void this.sendPacket(
|
|
258
|
+
void this.sendPacket(
|
|
259
|
+
AdbCommand.Close,
|
|
260
|
+
packet.arg1,
|
|
261
|
+
packet.arg0,
|
|
262
|
+
EmptyUint8Array,
|
|
263
|
+
);
|
|
228
264
|
}
|
|
229
265
|
|
|
230
266
|
#sendOkay(localId: number, remoteId: number, ackBytes: number) {
|
|
231
267
|
let payload: Uint8Array;
|
|
232
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
|
|
233
271
|
payload = new Uint8Array(4);
|
|
234
|
-
|
|
272
|
+
setUint32LittleEndian(payload, 0, ackBytes);
|
|
235
273
|
} else {
|
|
236
|
-
payload =
|
|
274
|
+
payload = EmptyUint8Array;
|
|
237
275
|
}
|
|
238
276
|
|
|
239
277
|
return this.sendPacket(AdbCommand.Okay, localId, remoteId, payload);
|
|
240
278
|
}
|
|
241
279
|
|
|
242
280
|
async #handleOpen(packet: AdbPacketData) {
|
|
243
|
-
//
|
|
244
|
-
//
|
|
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
|
|
245
284
|
const [localId] = this.#initializers.add<number>();
|
|
246
285
|
this.#initializers.resolve(localId, undefined);
|
|
247
286
|
|
|
248
287
|
const remoteId = packet.arg0;
|
|
249
|
-
let
|
|
250
|
-
|
|
288
|
+
let availableWriteBytes = packet.arg1;
|
|
289
|
+
let service = decodeUtf8(packet.payload);
|
|
290
|
+
// ADB Daemon still adds a null character to the service string
|
|
291
|
+
if (service.endsWith("\0")) {
|
|
292
|
+
service = service.substring(0, service.length - 1);
|
|
293
|
+
}
|
|
251
294
|
|
|
295
|
+
// Check remote delayed ack enablement is consistent with local
|
|
252
296
|
if (this.options.initialDelayedAckBytes === 0) {
|
|
253
|
-
if (
|
|
297
|
+
if (availableWriteBytes !== 0) {
|
|
254
298
|
throw new Error("Invalid OPEN packet. arg1 should be 0");
|
|
255
299
|
}
|
|
256
|
-
|
|
300
|
+
availableWriteBytes = Infinity;
|
|
257
301
|
} else {
|
|
258
|
-
if (
|
|
302
|
+
if (availableWriteBytes === 0) {
|
|
259
303
|
throw new Error(
|
|
260
304
|
"Invalid OPEN packet. arg1 should be greater than 0",
|
|
261
305
|
);
|
|
@@ -264,7 +308,12 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
264
308
|
|
|
265
309
|
const handler = this.#incomingSocketHandlers.get(service);
|
|
266
310
|
if (!handler) {
|
|
267
|
-
await this.sendPacket(
|
|
311
|
+
await this.sendPacket(
|
|
312
|
+
AdbCommand.Close,
|
|
313
|
+
0,
|
|
314
|
+
remoteId,
|
|
315
|
+
EmptyUint8Array,
|
|
316
|
+
);
|
|
268
317
|
return;
|
|
269
318
|
}
|
|
270
319
|
|
|
@@ -274,8 +323,8 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
274
323
|
remoteId,
|
|
275
324
|
localCreated: false,
|
|
276
325
|
service,
|
|
326
|
+
availableWriteBytes,
|
|
277
327
|
});
|
|
278
|
-
controller.ack(initialDelayedAckBytes);
|
|
279
328
|
|
|
280
329
|
try {
|
|
281
330
|
await handler(controller.socket);
|
|
@@ -285,8 +334,13 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
285
334
|
remoteId,
|
|
286
335
|
this.options.initialDelayedAckBytes,
|
|
287
336
|
);
|
|
288
|
-
} catch
|
|
289
|
-
await this.sendPacket(
|
|
337
|
+
} catch {
|
|
338
|
+
await this.sendPacket(
|
|
339
|
+
AdbCommand.Close,
|
|
340
|
+
0,
|
|
341
|
+
remoteId,
|
|
342
|
+
EmptyUint8Array,
|
|
343
|
+
);
|
|
290
344
|
}
|
|
291
345
|
}
|
|
292
346
|
|
|
@@ -297,14 +351,8 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
297
351
|
}
|
|
298
352
|
|
|
299
353
|
let handled = false;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (this.options.debugSlowRead && !handled) {
|
|
303
|
-
throw new Error(
|
|
304
|
-
`packet for \`${socket.service}\` not handled in 5 seconds`,
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
}),
|
|
354
|
+
|
|
355
|
+
const promises: Promise<void>[] = [
|
|
308
356
|
(async () => {
|
|
309
357
|
await socket.enqueue(packet.payload);
|
|
310
358
|
await this.#sendOkay(
|
|
@@ -314,9 +362,22 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
314
362
|
);
|
|
315
363
|
handled = true;
|
|
316
364
|
})(),
|
|
317
|
-
]
|
|
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
|
+
}
|
|
318
379
|
|
|
319
|
-
|
|
380
|
+
await Promise.race(promises);
|
|
320
381
|
}
|
|
321
382
|
|
|
322
383
|
async createSocket(service: string): Promise<AdbSocket> {
|
|
@@ -341,8 +402,8 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
341
402
|
remoteId,
|
|
342
403
|
localCreated: true,
|
|
343
404
|
service,
|
|
405
|
+
availableWriteBytes,
|
|
344
406
|
});
|
|
345
|
-
controller.ack(availableWriteBytes);
|
|
346
407
|
this.#sockets.set(localId, controller);
|
|
347
408
|
|
|
348
409
|
return controller.socket;
|
|
@@ -364,17 +425,18 @@ export class AdbPacketDispatcher implements Closeable {
|
|
|
364
425
|
command: AdbCommand,
|
|
365
426
|
arg0: number,
|
|
366
427
|
arg1: number,
|
|
367
|
-
|
|
428
|
+
// PERF: It's slightly faster to not use default parameter values
|
|
429
|
+
payload: string | Uint8Array,
|
|
368
430
|
): Promise<void> {
|
|
369
431
|
if (typeof payload === "string") {
|
|
370
432
|
payload = encodeUtf8(payload);
|
|
371
433
|
}
|
|
372
434
|
|
|
373
|
-
if (payload.
|
|
374
|
-
throw new
|
|
435
|
+
if (payload.length > this.options.maxPayloadSize) {
|
|
436
|
+
throw new TypeError("payload too large");
|
|
375
437
|
}
|
|
376
438
|
|
|
377
|
-
await
|
|
439
|
+
await Consumable.WritableStream.write(this.#writer, {
|
|
378
440
|
command,
|
|
379
441
|
arg0,
|
|
380
442
|
arg1,
|