@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.
Files changed (149) hide show
  1. package/CHANGELOG.json +51 -0
  2. package/CHANGELOG.md +24 -1
  3. package/LICENSE +1 -1
  4. package/esm/adb.d.ts +6 -3
  5. package/esm/adb.d.ts.map +1 -1
  6. package/esm/adb.js +11 -4
  7. package/esm/adb.js.map +1 -1
  8. package/esm/banner.d.ts.map +1 -1
  9. package/esm/banner.js +1 -0
  10. package/esm/banner.js.map +1 -1
  11. package/esm/commands/power.d.ts.map +1 -1
  12. package/esm/commands/power.js +6 -5
  13. package/esm/commands/power.js.map +1 -1
  14. package/esm/commands/reverse.d.ts.map +1 -1
  15. package/esm/commands/reverse.js +23 -11
  16. package/esm/commands/reverse.js.map +1 -1
  17. package/esm/commands/subprocess/command.js +3 -3
  18. package/esm/commands/subprocess/command.js.map +1 -1
  19. package/esm/commands/subprocess/protocols/none.d.ts +2 -2
  20. package/esm/commands/subprocess/protocols/none.d.ts.map +1 -1
  21. package/esm/commands/subprocess/protocols/shell.d.ts +3 -3
  22. package/esm/commands/subprocess/protocols/shell.d.ts.map +1 -1
  23. package/esm/commands/subprocess/protocols/shell.js +14 -69
  24. package/esm/commands/subprocess/protocols/shell.js.map +1 -1
  25. package/esm/commands/subprocess/protocols/types.d.ts +2 -2
  26. package/esm/commands/subprocess/protocols/types.d.ts.map +1 -1
  27. package/esm/commands/subprocess/utils.js +1 -1
  28. package/esm/commands/subprocess/utils.js.map +1 -1
  29. package/esm/commands/sync/list.d.ts +2 -5
  30. package/esm/commands/sync/list.d.ts.map +1 -1
  31. package/esm/commands/sync/list.js +2 -4
  32. package/esm/commands/sync/list.js.map +1 -1
  33. package/esm/commands/sync/pull.d.ts +1 -4
  34. package/esm/commands/sync/pull.d.ts.map +1 -1
  35. package/esm/commands/sync/pull.js +2 -2
  36. package/esm/commands/sync/pull.js.map +1 -1
  37. package/esm/commands/sync/push.d.ts +4 -7
  38. package/esm/commands/sync/push.d.ts.map +1 -1
  39. package/esm/commands/sync/push.js +3 -6
  40. package/esm/commands/sync/push.js.map +1 -1
  41. package/esm/commands/sync/request.d.ts +13 -18
  42. package/esm/commands/sync/request.d.ts.map +1 -1
  43. package/esm/commands/sync/request.js +23 -28
  44. package/esm/commands/sync/request.js.map +1 -1
  45. package/esm/commands/sync/response.d.ts +18 -12
  46. package/esm/commands/sync/response.d.ts.map +1 -1
  47. package/esm/commands/sync/response.js +39 -17
  48. package/esm/commands/sync/response.js.map +1 -1
  49. package/esm/commands/sync/socket.d.ts +2 -2
  50. package/esm/commands/sync/socket.d.ts.map +1 -1
  51. package/esm/commands/sync/socket.js +10 -5
  52. package/esm/commands/sync/socket.js.map +1 -1
  53. package/esm/commands/sync/stat.d.ts +0 -3
  54. package/esm/commands/sync/stat.d.ts.map +1 -1
  55. package/esm/commands/sync/stat.js +0 -2
  56. package/esm/commands/sync/stat.js.map +1 -1
  57. package/esm/commands/sync/sync.d.ts +4 -3
  58. package/esm/commands/sync/sync.d.ts.map +1 -1
  59. package/esm/commands/sync/sync.js +9 -7
  60. package/esm/commands/sync/sync.js.map +1 -1
  61. package/esm/commands/tcpip.js +1 -1
  62. package/esm/commands/tcpip.js.map +1 -1
  63. package/esm/daemon/auth.js +1 -1
  64. package/esm/daemon/auth.js.map +1 -1
  65. package/esm/daemon/crypto.d.ts +3 -2
  66. package/esm/daemon/crypto.d.ts.map +1 -1
  67. package/esm/daemon/crypto.js +56 -49
  68. package/esm/daemon/crypto.js.map +1 -1
  69. package/esm/daemon/dispatcher.d.ts +38 -6
  70. package/esm/daemon/dispatcher.d.ts.map +1 -1
  71. package/esm/daemon/dispatcher.js +86 -28
  72. package/esm/daemon/dispatcher.js.map +1 -1
  73. package/esm/daemon/packet.d.ts +2 -2
  74. package/esm/daemon/packet.d.ts.map +1 -1
  75. package/esm/daemon/packet.js +13 -12
  76. package/esm/daemon/packet.js.map +1 -1
  77. package/esm/daemon/socket.d.ts +11 -6
  78. package/esm/daemon/socket.d.ts.map +1 -1
  79. package/esm/daemon/socket.js +42 -12
  80. package/esm/daemon/socket.js.map +1 -1
  81. package/esm/daemon/transport.d.ts +75 -11
  82. package/esm/daemon/transport.d.ts.map +1 -1
  83. package/esm/daemon/transport.js +60 -33
  84. package/esm/daemon/transport.js.map +1 -1
  85. package/esm/features.d.ts +2 -1
  86. package/esm/features.d.ts.map +1 -1
  87. package/esm/features.js +2 -1
  88. package/esm/features.js.map +1 -1
  89. package/esm/server/client.d.ts +128 -48
  90. package/esm/server/client.d.ts.map +1 -1
  91. package/esm/server/client.js +378 -145
  92. package/esm/server/client.js.map +1 -1
  93. package/esm/server/transport.d.ts +3 -0
  94. package/esm/server/transport.d.ts.map +1 -1
  95. package/esm/server/transport.js +30 -4
  96. package/esm/server/transport.js.map +1 -1
  97. package/esm/utils/base64.d.ts.map +1 -1
  98. package/esm/utils/base64.js +33 -22
  99. package/esm/utils/base64.js.map +1 -1
  100. package/esm/utils/hex.d.ts +1 -1
  101. package/esm/utils/hex.d.ts.map +1 -1
  102. package/esm/utils/hex.js +12 -14
  103. package/esm/utils/hex.js.map +1 -1
  104. package/esm/utils/index.d.ts +1 -1
  105. package/esm/utils/index.d.ts.map +1 -1
  106. package/esm/utils/index.js +1 -1
  107. package/esm/utils/index.js.map +1 -1
  108. package/esm/utils/sequence-equal.d.ts +2 -0
  109. package/esm/utils/sequence-equal.d.ts.map +1 -0
  110. package/esm/utils/sequence-equal.js +12 -0
  111. package/esm/utils/sequence-equal.js.map +1 -0
  112. package/package.json +13 -15
  113. package/src/adb.ts +22 -6
  114. package/src/banner.ts +1 -0
  115. package/src/commands/power.ts +7 -5
  116. package/src/commands/reverse.ts +31 -18
  117. package/src/commands/subprocess/command.ts +3 -3
  118. package/src/commands/subprocess/protocols/none.ts +2 -2
  119. package/src/commands/subprocess/protocols/shell.ts +21 -100
  120. package/src/commands/subprocess/protocols/types.ts +2 -2
  121. package/src/commands/subprocess/utils.ts +1 -1
  122. package/src/commands/sync/list.ts +2 -4
  123. package/src/commands/sync/pull.ts +2 -2
  124. package/src/commands/sync/push.ts +7 -10
  125. package/src/commands/sync/request.ts +32 -32
  126. package/src/commands/sync/response.ts +47 -20
  127. package/src/commands/sync/socket.ts +13 -8
  128. package/src/commands/sync/stat.ts +0 -2
  129. package/src/commands/sync/sync.ts +13 -9
  130. package/src/commands/tcpip.ts +1 -1
  131. package/src/daemon/auth.ts +1 -1
  132. package/src/daemon/crypto.ts +64 -55
  133. package/src/daemon/dispatcher.ts +175 -37
  134. package/src/daemon/packet.ts +21 -14
  135. package/src/daemon/socket.ts +62 -28
  136. package/src/daemon/transport.ts +145 -40
  137. package/src/features.ts +2 -1
  138. package/src/server/client.ts +472 -224
  139. package/src/server/transport.ts +32 -4
  140. package/src/utils/base64.ts +33 -22
  141. package/src/utils/hex.ts +16 -14
  142. package/src/utils/index.ts +1 -1
  143. package/src/utils/sequence-equal.ts +13 -0
  144. package/tsconfig.build.tsbuildinfo +1 -1
  145. package/esm/utils/conditional-variable.d.ts +0 -8
  146. package/esm/utils/conditional-variable.d.ts.map +0 -1
  147. package/esm/utils/conditional-variable.js +0 -34
  148. package/esm/utils/conditional-variable.js.map +0 -1
  149. package/src/utils/conditional-variable.ts +0 -45
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { AutoDisposable } from "@yume-chan/event";
4
4
  import { BufferedReadableStream } from "@yume-chan/stream-extra";
5
- import Struct, { ExactReadableEndedError } from "@yume-chan/struct";
5
+ import Struct, { ExactReadableEndedError, encodeUtf8 } from "@yume-chan/struct";
6
6
 
7
7
  import type { Adb, AdbIncomingSocketHandler } from "../adb.js";
8
- import { decodeUtf8, hexToNumber } from "../utils/index.js";
8
+ import { hexToNumber, sequenceEqual } from "../utils/index.js";
9
9
 
10
10
  export interface AdbForwardListener {
11
11
  deviceSerial: string;
@@ -38,7 +38,7 @@ const AdbReverseErrorResponse = new Struct()
38
38
  .concat(AdbReverseStringResponse)
39
39
  .postDeserialize((value) => {
40
40
  // https://issuetracker.google.com/issues/37066218
41
- // ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over WiFi),
41
+ // ADB on Android <9 can't create reverse tunnels when connected wirelessly (ADB over Wi-Fi),
42
42
  // and returns this confusing "more than one device/emulator" error.
43
43
  if (value.content === "more than one device/emulator") {
44
44
  throw new AdbReverseNotSupportedError();
@@ -47,11 +47,21 @@ const AdbReverseErrorResponse = new Struct()
47
47
  }
48
48
  });
49
49
 
50
- async function readString(stream: BufferedReadableStream, length: number) {
51
- const buffer = await stream.readExactly(length);
52
- return decodeUtf8(buffer);
50
+ // Like `hexToNumber`, it's much faster than first converting `buffer` to a string
51
+ function decimalToNumber(buffer: Uint8Array) {
52
+ let value = 0;
53
+ for (const byte of buffer) {
54
+ // Like `parseInt`, return when it encounters a non-digit character
55
+ if (byte < 48 || byte > 57) {
56
+ return value;
57
+ }
58
+ value = value * 10 + byte - 48;
59
+ }
60
+ return value;
53
61
  }
54
62
 
63
+ const OKAY = encodeUtf8("OKAY");
64
+
55
65
  export class AdbReverseCommand extends AutoDisposable {
56
66
  protected adb: Adb;
57
67
 
@@ -70,10 +80,12 @@ export class AdbReverseCommand extends AutoDisposable {
70
80
 
71
81
  protected async sendRequest(service: string) {
72
82
  const stream = await this.createBufferedStream(service);
73
- const success = (await readString(stream, 4)) === "OKAY";
74
- if (!success) {
83
+
84
+ const response = await stream.readExactly(4);
85
+ if (!sequenceEqual(response, OKAY)) {
75
86
  await AdbReverseErrorResponse.deserialize(stream);
76
87
  }
88
+
77
89
  return stream;
78
90
  }
79
91
 
@@ -81,14 +93,15 @@ export class AdbReverseCommand extends AutoDisposable {
81
93
  const stream = await this.createBufferedStream("reverse:list-forward");
82
94
 
83
95
  const response = await AdbReverseStringResponse.deserialize(stream);
84
- return response.content!.split("\n").map((line) => {
85
- const [deviceSerial, localName, remoteName] = line.split(" ") as [
86
- string,
87
- string,
88
- string,
89
- ];
90
- return { deviceSerial, localName, remoteName };
91
- });
96
+ return response.content
97
+ .split("\n")
98
+ .filter((line) => !!line)
99
+ .map((line) => {
100
+ const [deviceSerial, localName, remoteName] = line.split(
101
+ " ",
102
+ ) as [string, string, string];
103
+ return { deviceSerial, localName, remoteName };
104
+ });
92
105
 
93
106
  // No need to close the stream, device will close it
94
107
  }
@@ -110,8 +123,8 @@ export class AdbReverseCommand extends AutoDisposable {
110
123
  const position = stream.position;
111
124
  try {
112
125
  const length = hexToNumber(await stream.readExactly(4));
113
- const port = await readString(stream, length);
114
- deviceAddress = `tcp:${Number.parseInt(port, 10)}`;
126
+ const port = decimalToNumber(await stream.readExactly(length));
127
+ deviceAddress = `tcp:${port}`;
115
128
  } catch (e) {
116
129
  if (
117
130
  e instanceof ExactReadableEndedError &&
@@ -1,4 +1,4 @@
1
- import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
1
+ import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
2
2
 
3
3
  import { AdbCommandBase } from "../base.js";
4
4
 
@@ -112,10 +112,10 @@ export class AdbSubprocess extends AdbCommandBase {
112
112
 
113
113
  const [stdout, stderr, exitCode] = await Promise.all([
114
114
  process.stdout
115
- .pipeThrough(new DecodeUtf8Stream())
115
+ .pipeThrough(new TextDecoderStream())
116
116
  .pipeThrough(new ConcatStringStream()),
117
117
  process.stderr
118
- .pipeThrough(new DecodeUtf8Stream())
118
+ .pipeThrough(new TextDecoderStream())
119
119
  .pipeThrough(new ConcatStringStream()),
120
120
  process.exit,
121
121
  ]);
@@ -1,4 +1,4 @@
1
- import type { Consumable, WritableStream } from "@yume-chan/stream-extra";
1
+ import type { MaybeConsumable, WritableStream } from "@yume-chan/stream-extra";
2
2
  import { ReadableStream } from "@yume-chan/stream-extra";
3
3
 
4
4
  import type { Adb, AdbSocket } from "../../../adb.js";
@@ -36,7 +36,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
36
36
  readonly #socket: AdbSocket;
37
37
 
38
38
  // Legacy shell forwards all data to stdin.
39
- get stdin(): WritableStream<Consumable<Uint8Array>> {
39
+ get stdin(): WritableStream<MaybeConsumable<Uint8Array>> {
40
40
  return this.#socket.writable;
41
41
  }
42
42
 
@@ -1,17 +1,14 @@
1
1
  import { PromiseResolver } from "@yume-chan/async";
2
2
  import type {
3
- Consumable,
4
3
  PushReadableStreamController,
5
4
  ReadableStream,
6
5
  WritableStreamDefaultWriter,
7
6
  } from "@yume-chan/stream-extra";
8
7
  import {
9
- ConsumableTransformStream,
10
- ConsumableWritableStream,
8
+ MaybeConsumable,
11
9
  PushReadableStream,
12
10
  StructDeserializeStream,
13
11
  WritableStream,
14
- pipeFrom,
15
12
  } from "@yume-chan/stream-extra";
16
13
  import type { StructValueType } from "@yume-chan/struct";
17
14
  import Struct, { placeholder } from "@yume-chan/struct";
@@ -37,68 +34,8 @@ const AdbShellProtocolPacket = new Struct({ littleEndian: true })
37
34
  .uint32("length")
38
35
  .uint8Array("data", { lengthField: "length" });
39
36
 
40
- type AdbShellProtocolPacketInit = (typeof AdbShellProtocolPacket)["TInit"];
41
-
42
37
  type AdbShellProtocolPacket = StructValueType<typeof AdbShellProtocolPacket>;
43
38
 
44
- class StdinSerializeStream extends ConsumableTransformStream<
45
- Uint8Array,
46
- AdbShellProtocolPacketInit
47
- > {
48
- constructor() {
49
- super({
50
- async transform(chunk, controller) {
51
- await controller.enqueue({
52
- id: AdbShellProtocolId.Stdin,
53
- data: chunk,
54
- });
55
- },
56
- flush() {
57
- // TODO: AdbShellSubprocessProtocol: support closing stdin
58
- },
59
- });
60
- }
61
- }
62
-
63
- class MultiplexStream<T> {
64
- #readable: PushReadableStream<T>;
65
- #readableController!: PushReadableStreamController<T>;
66
- get readable() {
67
- return this.#readable;
68
- }
69
-
70
- #activeCount = 0;
71
-
72
- constructor() {
73
- this.#readable = new PushReadableStream((controller) => {
74
- this.#readableController = controller;
75
- });
76
- }
77
-
78
- createWriteable() {
79
- return new WritableStream<T>({
80
- start: () => {
81
- this.#activeCount += 1;
82
- },
83
- write: async (chunk) => {
84
- await this.#readableController.enqueue(chunk);
85
- },
86
- abort: () => {
87
- this.#activeCount -= 1;
88
- if (this.#activeCount === 0) {
89
- this.#readableController.close();
90
- }
91
- },
92
- close: () => {
93
- this.#activeCount -= 1;
94
- if (this.#activeCount === 0) {
95
- this.#readableController.close();
96
- }
97
- },
98
- });
99
- }
100
- }
101
-
102
39
  /**
103
40
  * Shell v2 a.k.a Shell Protocol
104
41
  *
@@ -109,7 +46,7 @@ class MultiplexStream<T> {
109
46
  */
110
47
  export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
111
48
  static isSupported(adb: Adb) {
112
- return adb.supportsFeature(AdbFeature.ShellV2);
49
+ return adb.canUseFeature(AdbFeature.ShellV2);
113
50
  }
114
51
 
115
52
  static async pty(adb: Adb, command: string) {
@@ -126,11 +63,9 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
126
63
  }
127
64
 
128
65
  readonly #socket: AdbSocket;
129
- #socketWriter: WritableStreamDefaultWriter<
130
- Consumable<AdbShellProtocolPacketInit>
131
- >;
66
+ #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
132
67
 
133
- #stdin: WritableStream<Consumable<Uint8Array>>;
68
+ #stdin: WritableStream<MaybeConsumable<Uint8Array>>;
134
69
  get stdin() {
135
70
  return this.#stdin;
136
71
  }
@@ -153,13 +88,8 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
153
88
  constructor(socket: AdbSocket) {
154
89
  this.#socket = socket;
155
90
 
156
- // Check this image to help you understand the stream graph
157
- // cspell: disable-next-line
158
- // https://www.plantuml.com/plantuml/png/bL91QiCm4Bpx5SAdv90lb1JISmiw5XzaQKf5PIkiLZIqzEyLSg8ks13gYtOykpFhiOw93N6UGjVDqK7rZsxKqNw0U_NTgVAy4empOy2mm4_olC0VEVEE47GUpnGjKdgXoD76q4GIEpyFhOwP_m28hW0NNzxNUig1_JdW0bA7muFIJDco1daJ_1SAX9bgvoPJPyIkSekhNYctvIGXrCH6tIsPL5fs-s6J5yc9BpWXhKtNdF2LgVYPGM_6GlMwfhWUsIt4lbScANrwlgVVUifPSVi__t44qStnwPvZwobdSmHHlL57p2vFuHS0
159
-
160
91
  let stdoutController!: PushReadableStreamController<Uint8Array>;
161
92
  let stderrController!: PushReadableStreamController<Uint8Array>;
162
-
163
93
  this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
164
94
  stdoutController = controller;
165
95
  });
@@ -207,39 +137,30 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
207
137
  },
208
138
  );
209
139
 
210
- const multiplexer = new MultiplexStream<
211
- Consumable<AdbShellProtocolPacketInit>
212
- >();
213
- void multiplexer.readable
214
- .pipeThrough(
215
- new ConsumableTransformStream({
216
- async transform(chunk, controller) {
217
- await controller.enqueue(
218
- AdbShellProtocolPacket.serialize(chunk),
219
- );
220
- },
221
- }),
222
- )
223
- .pipeTo(socket.writable);
224
-
225
- this.#stdin = pipeFrom(
226
- multiplexer.createWriteable(),
227
- new StdinSerializeStream(),
228
- );
140
+ this.#writer = this.#socket.writable.getWriter();
229
141
 
230
- this.#socketWriter = multiplexer.createWriteable().getWriter();
142
+ this.#stdin = new MaybeConsumable.WritableStream<Uint8Array>({
143
+ write: async (chunk) => {
144
+ await this.#writer.write(
145
+ AdbShellProtocolPacket.serialize({
146
+ id: AdbShellProtocolId.Stdin,
147
+ data: chunk,
148
+ }),
149
+ );
150
+ },
151
+ });
231
152
  }
232
153
 
233
154
  async resize(rows: number, cols: number) {
234
- await ConsumableWritableStream.write(this.#socketWriter, {
235
- id: AdbShellProtocolId.WindowSizeChange,
236
- data: encodeUtf8(
155
+ await this.#writer.write(
156
+ AdbShellProtocolPacket.serialize({
157
+ id: AdbShellProtocolId.WindowSizeChange,
237
158
  // The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
238
159
  // However, according to https://linux.die.net/man/4/tty_ioctl
239
160
  // `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
240
- `${rows}x${cols},0x0\0`,
241
- ),
242
- });
161
+ data: encodeUtf8(`${rows}x${cols},0x0\0`),
162
+ }),
163
+ );
243
164
  }
244
165
 
245
166
  kill() {
@@ -1,5 +1,5 @@
1
1
  import type {
2
- Consumable,
2
+ MaybeConsumable,
3
3
  ReadableStream,
4
4
  WritableStream,
5
5
  } from "@yume-chan/stream-extra";
@@ -11,7 +11,7 @@ export interface AdbSubprocessProtocol {
11
11
  /**
12
12
  * A WritableStream that writes to the `stdin` stream.
13
13
  */
14
- readonly stdin: WritableStream<Consumable<Uint8Array>>;
14
+ readonly stdin: WritableStream<MaybeConsumable<Uint8Array>>;
15
15
 
16
16
  /**
17
17
  * The `stdout` stream of the process.
@@ -10,7 +10,7 @@ export function escapeArg(s: string) {
10
10
  break;
11
11
  }
12
12
  result += s.substring(base, found);
13
- // a'b becomes a'\'b
13
+ // a'b becomes a'\'b (the backslash is not a escape character)
14
14
  result += String.raw`'\''`;
15
15
  base = found + 1;
16
16
  }
@@ -17,8 +17,7 @@ export interface AdbSyncEntry extends AdbSyncStat {
17
17
  export const AdbSyncEntryResponse = new Struct({ littleEndian: true })
18
18
  .concat(AdbSyncLstatResponse)
19
19
  .uint32("nameLength")
20
- .string("name", { lengthField: "nameLength" })
21
- .extra({ id: AdbSyncResponseId.Entry as const });
20
+ .string("name", { lengthField: "nameLength" });
22
21
 
23
22
  export type AdbSyncEntryResponse =
24
23
  (typeof AdbSyncEntryResponse)["TDeserializeResult"];
@@ -26,8 +25,7 @@ export type AdbSyncEntryResponse =
26
25
  export const AdbSyncEntry2Response = new Struct({ littleEndian: true })
27
26
  .concat(AdbSyncStatResponse)
28
27
  .uint32("nameLength")
29
- .string("name", { lengthField: "nameLength" })
30
- .extra({ id: AdbSyncResponseId.Entry2 as const });
28
+ .string("name", { lengthField: "nameLength" });
31
29
 
32
30
  export type AdbSyncEntry2Response =
33
31
  (typeof AdbSyncEntry2Response)["TDeserializeResult"];
@@ -8,8 +8,7 @@ import type { AdbSyncSocket } from "./socket.js";
8
8
 
9
9
  export const AdbSyncDataResponse = new Struct({ littleEndian: true })
10
10
  .uint32("dataLength")
11
- .uint8Array("data", { lengthField: "dataLength" })
12
- .extra({ id: AdbSyncResponseId.Data as const });
11
+ .uint8Array("data", { lengthField: "dataLength" });
13
12
 
14
13
  export type AdbSyncDataResponse =
15
14
  (typeof AdbSyncDataResponse)["TDeserializeResult"];
@@ -52,6 +51,7 @@ export function adbSyncPull(
52
51
  socket: AdbSyncSocket,
53
52
  path: string,
54
53
  ): ReadableStream<Uint8Array> {
54
+ // TODO: use `ReadableStream.from` when it's supported
55
55
  return new PushReadableStream(async (controller) => {
56
56
  for await (const data of adbSyncPullGenerator(socket, path)) {
57
57
  await controller.enqueue(data);
@@ -1,8 +1,8 @@
1
- import type { Consumable, ReadableStream } from "@yume-chan/stream-extra";
1
+ import type { ReadableStream } from "@yume-chan/stream-extra";
2
2
  import {
3
3
  AbortController,
4
- ConsumableWritableStream,
5
4
  DistributionStream,
5
+ MaybeConsumable,
6
6
  } from "@yume-chan/stream-extra";
7
7
  import Struct, { placeholder } from "@yume-chan/struct";
8
8
 
@@ -18,7 +18,7 @@ export const ADB_SYNC_MAX_PACKET_SIZE = 64 * 1024;
18
18
  export interface AdbSyncPushV1Options {
19
19
  socket: AdbSyncSocket;
20
20
  filename: string;
21
- file: ReadableStream<Consumable<Uint8Array>>;
21
+ file: ReadableStream<MaybeConsumable<Uint8Array>>;
22
22
  type?: LinuxFileType;
23
23
  permission?: number;
24
24
  mtime?: number;
@@ -31,7 +31,7 @@ export const AdbSyncOkResponse = new Struct({ littleEndian: true }).uint32(
31
31
 
32
32
  async function pipeFileData(
33
33
  locked: AdbSyncSocketLocked,
34
- file: ReadableStream<Consumable<Uint8Array>>,
34
+ file: ReadableStream<MaybeConsumable<Uint8Array>>,
35
35
  packetSize: number,
36
36
  mtime: number,
37
37
  ) {
@@ -40,7 +40,7 @@ async function pipeFileData(
40
40
  const abortController = new AbortController();
41
41
  file.pipeThrough(new DistributionStream(packetSize, true))
42
42
  .pipeTo(
43
- new ConsumableWritableStream({
43
+ new MaybeConsumable.WritableStream({
44
44
  write: async (chunk) => {
45
45
  await adbSyncWriteRequest(
46
46
  locked,
@@ -97,10 +97,7 @@ export enum AdbSyncSendV2Flags {
97
97
  * 4
98
98
  */
99
99
  Zstd = 1 << 2,
100
- /**
101
- * 0x80000000
102
- */
103
- DryRun = (1 << 31) >>> 0,
100
+ DryRun = 0x80000000,
104
101
  }
105
102
 
106
103
  export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
@@ -114,7 +111,7 @@ export interface AdbSyncPushV2Options extends AdbSyncPushV1Options {
114
111
  }
115
112
 
116
113
  export const AdbSyncSendV2Request = new Struct({ littleEndian: true })
117
- .uint32("id", placeholder<AdbSyncRequestId>())
114
+ .uint32("id")
118
115
  .uint32("mode")
119
116
  .uint32("flags", placeholder<AdbSyncSendV2Flags>());
120
117
 
@@ -2,53 +2,53 @@ import Struct from "@yume-chan/struct";
2
2
 
3
3
  import { encodeUtf8 } from "../../utils/index.js";
4
4
 
5
- export enum AdbSyncRequestId {
6
- List = "LIST",
7
- ListV2 = "LIS2",
8
- Send = "SEND",
9
- SendV2 = "SND2",
10
- Lstat = "STAT",
11
- Stat = "STA2",
12
- LstatV2 = "LST2",
13
- Data = "DATA",
14
- Done = "DONE",
15
- Receive = "RECV",
5
+ import { adbSyncEncodeId } from "./response.js";
6
+
7
+ export namespace AdbSyncRequestId {
8
+ export const List = adbSyncEncodeId("LIST");
9
+ export const ListV2 = adbSyncEncodeId("LIS2");
10
+ export const Send = adbSyncEncodeId("SEND");
11
+ export const SendV2 = adbSyncEncodeId("SND2");
12
+ export const Lstat = adbSyncEncodeId("STAT");
13
+ export const Stat = adbSyncEncodeId("STA2");
14
+ export const LstatV2 = adbSyncEncodeId("LST2");
15
+ export const Data = adbSyncEncodeId("DATA");
16
+ export const Done = adbSyncEncodeId("DONE");
17
+ export const Receive = adbSyncEncodeId("RECV");
16
18
  }
17
19
 
18
20
  export const AdbSyncNumberRequest = new Struct({ littleEndian: true })
19
- .string("id", { length: 4 })
21
+ .uint32("id")
20
22
  .uint32("arg");
21
23
 
22
- export const AdbSyncDataRequest = new Struct({ littleEndian: true })
23
- .concat(AdbSyncNumberRequest)
24
- .uint8Array("data", { lengthField: "arg" });
25
-
26
24
  export interface AdbSyncWritable {
27
25
  write(buffer: Uint8Array): Promise<void>;
28
26
  }
29
27
 
30
28
  export async function adbSyncWriteRequest(
31
29
  writable: AdbSyncWritable,
32
- id: AdbSyncRequestId | string,
30
+ id: number | string,
33
31
  value: number | string | Uint8Array,
34
32
  ): Promise<void> {
33
+ if (typeof id === "string") {
34
+ id = adbSyncEncodeId(id);
35
+ }
36
+
35
37
  if (typeof value === "number") {
36
- const buffer = AdbSyncNumberRequest.serialize({
37
- id,
38
- arg: value,
39
- });
40
- await writable.write(buffer);
41
- } else if (typeof value === "string") {
42
- // Let `writable` buffer writes
43
- const buffer = encodeUtf8(value);
44
38
  await writable.write(
45
- AdbSyncNumberRequest.serialize({ id, arg: buffer.byteLength }),
39
+ AdbSyncNumberRequest.serialize({ id, arg: value }),
46
40
  );
47
- await writable.write(buffer);
48
- } else {
49
- await writable.write(
50
- AdbSyncNumberRequest.serialize({ id, arg: value.byteLength }),
51
- );
52
- await writable.write(value);
41
+ return;
42
+ }
43
+
44
+ if (typeof value === "string") {
45
+ value = encodeUtf8(value);
53
46
  }
47
+
48
+ // `writable` is buffered, it copies inputs to an internal buffer,
49
+ // so don't concatenate headers and data here, that will be an unnecessary copy.
50
+ await writable.write(
51
+ AdbSyncNumberRequest.serialize({ id, arg: value.length }),
52
+ );
53
+ await writable.write(value);
54
54
  }
@@ -1,22 +1,39 @@
1
+ import { getUint32LittleEndian } from "@yume-chan/no-data-view";
1
2
  import type {
2
3
  AsyncExactReadable,
3
4
  StructLike,
4
5
  StructValueType,
5
6
  } from "@yume-chan/struct";
6
- import Struct from "@yume-chan/struct";
7
+ import Struct, { decodeUtf8 } from "@yume-chan/struct";
7
8
 
8
- import { decodeUtf8 } from "../../utils/index.js";
9
+ function encodeAsciiUnchecked(value: string): Uint8Array {
10
+ const result = new Uint8Array(value.length);
11
+ for (let i = 0; i < value.length; i += 1) {
12
+ result[i] = value.charCodeAt(i);
13
+ }
14
+ return result;
15
+ }
16
+
17
+ /**
18
+ * Encode ID to numbers for faster comparison
19
+ * @param value A 4-character string
20
+ * @returns A 32-bit integer by encoding the string as little-endian
21
+ */
22
+ export function adbSyncEncodeId(value: string): number {
23
+ const buffer = encodeAsciiUnchecked(value);
24
+ return getUint32LittleEndian(buffer, 0);
25
+ }
9
26
 
10
- export enum AdbSyncResponseId {
11
- Entry = "DENT",
12
- Entry2 = "DNT2",
13
- Lstat = "STAT",
14
- Stat = "STA2",
15
- Lstat2 = "LST2",
16
- Done = "DONE",
17
- Data = "DATA",
18
- Ok = "OKAY",
19
- Fail = "FAIL",
27
+ export namespace AdbSyncResponseId {
28
+ export const Entry = adbSyncEncodeId("DENT");
29
+ export const Entry2 = adbSyncEncodeId("DNT2");
30
+ export const Lstat = adbSyncEncodeId("STAT");
31
+ export const Stat = adbSyncEncodeId("STA2");
32
+ export const Lstat2 = adbSyncEncodeId("LST2");
33
+ export const Done = adbSyncEncodeId("DONE");
34
+ export const Data = adbSyncEncodeId("DATA");
35
+ export const Ok = adbSyncEncodeId("OKAY");
36
+ export const Fail = adbSyncEncodeId("FAIL");
20
37
  }
21
38
 
22
39
  export class AdbSyncError extends Error {}
@@ -30,18 +47,24 @@ export const AdbSyncFailResponse = new Struct({ littleEndian: true })
30
47
 
31
48
  export async function adbSyncReadResponse<T>(
32
49
  stream: AsyncExactReadable,
33
- id: AdbSyncResponseId,
50
+ id: number | string,
34
51
  type: StructLike<T>,
35
52
  ): Promise<T> {
36
- const actualId = decodeUtf8(await stream.readExactly(4));
37
- switch (actualId) {
53
+ if (typeof id === "string") {
54
+ id = adbSyncEncodeId(id);
55
+ }
56
+
57
+ const buffer = await stream.readExactly(4);
58
+ switch (getUint32LittleEndian(buffer, 0)) {
38
59
  case AdbSyncResponseId.Fail:
39
60
  await AdbSyncFailResponse.deserialize(stream);
40
61
  throw new Error("Unreachable");
41
62
  case id:
42
63
  return await type.deserialize(stream);
43
64
  default:
44
- throw new Error(`Expected '${id}', but got '${actualId}'`);
65
+ throw new Error(
66
+ `Expected '${id}', but got '${decodeUtf8(buffer)}'`,
67
+ );
45
68
  }
46
69
  }
47
70
 
@@ -49,12 +72,16 @@ export async function* adbSyncReadResponses<
49
72
  T extends Struct<object, PropertyKey, object, unknown>,
50
73
  >(
51
74
  stream: AsyncExactReadable,
52
- id: AdbSyncResponseId,
75
+ id: number | string,
53
76
  type: T,
54
77
  ): AsyncGenerator<StructValueType<T>, void, void> {
78
+ if (typeof id === "string") {
79
+ id = adbSyncEncodeId(id);
80
+ }
81
+
55
82
  while (true) {
56
- const actualId = decodeUtf8(await stream.readExactly(4));
57
- switch (actualId) {
83
+ const buffer = await stream.readExactly(4);
84
+ switch (getUint32LittleEndian(buffer, 0)) {
58
85
  case AdbSyncResponseId.Fail:
59
86
  await AdbSyncFailResponse.deserialize(stream);
60
87
  throw new Error("Unreachable");
@@ -70,7 +97,7 @@ export async function* adbSyncReadResponses<
70
97
  break;
71
98
  default:
72
99
  throw new Error(
73
- `Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${actualId}'`,
100
+ `Expected '${id}' or '${AdbSyncResponseId.Done}', but got '${decodeUtf8(buffer)}'`,
74
101
  );
75
102
  }
76
103
  }