@yume-chan/adb 3.0.0-beta.1 → 3.0.0-beta.3

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 (99) hide show
  1. package/esm/adb.d.ts +7 -2
  2. package/esm/adb.d.ts.map +1 -1
  3. package/esm/adb.js +2 -2
  4. package/esm/adb.js.map +1 -1
  5. package/esm/banner.d.ts.map +1 -1
  6. package/esm/banner.js.map +1 -1
  7. package/esm/daemon/crypto.d.ts.map +1 -1
  8. package/esm/daemon/crypto.js +53 -18
  9. package/esm/daemon/crypto.js.map +1 -1
  10. package/esm/server/client.d.ts.map +1 -1
  11. package/esm/server/client.js +2 -1
  12. package/esm/server/client.js.map +1 -1
  13. package/esm/server/transport.d.ts +1 -1
  14. package/esm/server/transport.d.ts.map +1 -1
  15. package/esm/server/transport.js +2 -2
  16. package/esm/server/transport.js.map +1 -1
  17. package/esm/service/subprocess/shell/spawner.d.ts.map +1 -1
  18. package/esm/service/subprocess/shell/spawner.js.map +1 -1
  19. package/esm/service/sync/compression/adapters.d.ts +16 -0
  20. package/esm/service/sync/compression/adapters.d.ts.map +1 -0
  21. package/esm/service/sync/compression/adapters.js +84 -0
  22. package/esm/service/sync/compression/adapters.js.map +1 -0
  23. package/esm/service/sync/compression/format-ns.d.ts.map +1 -1
  24. package/esm/service/sync/compression/format-ns.js +1 -0
  25. package/esm/service/sync/compression/format-ns.js.map +1 -1
  26. package/esm/service/sync/compression/format.d.ts +1 -0
  27. package/esm/service/sync/compression/format.d.ts.map +1 -1
  28. package/esm/service/sync/compression/format.js +1 -0
  29. package/esm/service/sync/compression/format.js.map +1 -1
  30. package/esm/service/sync/compression/index.d.ts +1 -1
  31. package/esm/service/sync/compression/index.d.ts.map +1 -1
  32. package/esm/service/sync/compression/index.js +1 -1
  33. package/esm/service/sync/compression/index.js.map +1 -1
  34. package/esm/service/sync/compression/mode.d.ts +6 -0
  35. package/esm/service/sync/compression/mode.d.ts.map +1 -0
  36. package/esm/service/sync/compression/mode.js +5 -0
  37. package/esm/service/sync/compression/mode.js.map +1 -0
  38. package/esm/service/sync/compression/native.d.ts +8 -0
  39. package/esm/service/sync/compression/native.d.ts.map +1 -0
  40. package/esm/service/sync/compression/native.js +53 -0
  41. package/esm/service/sync/compression/native.js.map +1 -0
  42. package/esm/service/sync/id/request.d.ts +1 -0
  43. package/esm/service/sync/id/request.d.ts.map +1 -1
  44. package/esm/service/sync/id/request.js +1 -0
  45. package/esm/service/sync/id/request.js.map +1 -1
  46. package/esm/service/sync/request/flag.d.ts +9 -0
  47. package/esm/service/sync/request/flag.d.ts.map +1 -0
  48. package/esm/service/sync/request/flag.js +8 -0
  49. package/esm/service/sync/request/flag.js.map +1 -0
  50. package/esm/service/sync/request/pull.d.ts +25 -2
  51. package/esm/service/sync/request/pull.d.ts.map +1 -1
  52. package/esm/service/sync/request/pull.js +76 -16
  53. package/esm/service/sync/request/pull.js.map +1 -1
  54. package/esm/service/sync/request/push.d.ts +36 -14
  55. package/esm/service/sync/request/push.d.ts.map +1 -1
  56. package/esm/service/sync/request/push.js +162 -60
  57. package/esm/service/sync/request/push.js.map +1 -1
  58. package/esm/service/sync/service.d.ts +32 -16
  59. package/esm/service/sync/service.d.ts.map +1 -1
  60. package/esm/service/sync/service.js +58 -13
  61. package/esm/service/sync/service.js.map +1 -1
  62. package/esm/service/sync/socket-pool.d.ts +1 -0
  63. package/esm/service/sync/socket-pool.d.ts.map +1 -1
  64. package/esm/service/sync/socket-pool.js +45 -9
  65. package/esm/service/sync/socket-pool.js.map +1 -1
  66. package/esm/service/sync/socket.d.ts +4 -0
  67. package/esm/service/sync/socket.d.ts.map +1 -1
  68. package/esm/service/sync/socket.js +16 -0
  69. package/esm/service/sync/socket.js.map +1 -1
  70. package/esm/utils/ref.d.ts +3 -5
  71. package/esm/utils/ref.d.ts.map +1 -1
  72. package/esm/utils/ref.js +12 -6
  73. package/esm/utils/ref.js.map +1 -1
  74. package/package.json +6 -6
  75. package/src/adb.ts +15 -3
  76. package/src/banner.ts +1 -2
  77. package/src/daemon/crypto.ts +65 -23
  78. package/src/server/client.ts +5 -1
  79. package/src/server/transport.ts +5 -1
  80. package/src/service/subprocess/shell/spawner.ts +3 -1
  81. package/src/service/sync/compression/adapters.ts +137 -0
  82. package/src/service/sync/compression/format-ns.ts +2 -0
  83. package/src/service/sync/compression/format.ts +5 -0
  84. package/src/service/sync/compression/index.ts +1 -1
  85. package/src/service/sync/compression/mode.ts +6 -0
  86. package/src/service/sync/compression/native.ts +73 -0
  87. package/src/service/sync/id/request.ts +1 -0
  88. package/src/service/sync/request/flag.ts +9 -0
  89. package/src/service/sync/request/pull.ts +135 -27
  90. package/src/service/sync/request/push.ts +235 -88
  91. package/src/service/sync/service.ts +87 -27
  92. package/src/service/sync/socket-pool.ts +50 -9
  93. package/src/service/sync/socket.ts +23 -1
  94. package/src/utils/ref.ts +17 -7
  95. package/esm/service/sync/compression/compression.d.ts +0 -17
  96. package/esm/service/sync/compression/compression.d.ts.map +0 -1
  97. package/esm/service/sync/compression/compression.js +0 -92
  98. package/esm/service/sync/compression/compression.js.map +0 -1
  99. package/src/service/sync/compression/compression.ts +0 -126
package/src/banner.ts CHANGED
@@ -21,8 +21,7 @@ export class AdbBanner {
21
21
  const pieces = banner.split("::");
22
22
  if (pieces.length > 1) {
23
23
  state = (pieces[0]!.trim() || undefined) as
24
- | AdbServerClient.ConnectionState
25
- | undefined;
24
+ AdbServerClient.ConnectionState | undefined;
26
25
  const props = pieces[1]!;
27
26
  for (const prop of props.split(";")) {
28
27
  // istanbul ignore if
@@ -18,10 +18,12 @@ export function getBigUint(
18
18
  ): bigint {
19
19
  let result = 0n;
20
20
 
21
- // Currently `length` must be a multiplication of 8
22
- // Support for arbitrary length can be easily added
21
+ // The leading bytes that do not fill a whole 64-bit word.
22
+ for (let j = 0; j < length % 8; j += 1) {
23
+ result = (result << 8n) | BigInt(array[byteOffset + j]!);
24
+ }
23
25
 
24
- for (let i = byteOffset; i < byteOffset + length; i += 8) {
26
+ for (let i = byteOffset + (length % 8); i < byteOffset + length; i += 8) {
25
27
  result <<= 64n;
26
28
  const value = getUint64BigEndian(array, i);
27
29
  result |= value;
@@ -73,14 +75,6 @@ export interface SimpleRsaPrivateKey {
73
75
  d: bigint;
74
76
  }
75
77
 
76
- // These values are correct only if
77
- // modulus length is 2048 and
78
- // public exponent (e) is 65537
79
- // Anyway, that's how this library generates keys
80
-
81
- // To support other parameters,
82
- // a proper ASN.1 parser can be used
83
-
84
78
  // References:
85
79
  //
86
80
  // https://tools.ietf.org/html/rfc8017#appendix-A.1.2
@@ -94,20 +88,68 @@ export interface SimpleRsaPrivateKey {
94
88
  // https://www.itu.int/rec/T-REC-X.690-201508-I/en
95
89
  // X.690: Specification of Distinguished Encoding Rules (DER)
96
90
 
97
- const RsaPrivateKeyNOffset = 38;
98
- const RsaPrivateKeyNLength = 2048 / 8;
99
- const RsaPrivateKeyDOffset = 303;
100
- const RsaPrivateKeyDLength = 2048 / 8;
101
-
102
- export function rsaParsePrivateKey(key: Uint8Array): SimpleRsaPrivateKey {
103
- if (key.length < RsaPrivateKeyDOffset + RsaPrivateKeyDLength) {
104
- throw new Error(
105
- "RSA private key is too short. Expecting a PKCS#8 formatted RSA private key with modulus length 2048 bits and public exponent 65537.",
106
- );
91
+ /**
92
+ * One DER tag-length-value header: where its content starts and how long it is.
93
+ * @param data The DER-encoded data.
94
+ * @param offset The offset of the header in the data.
95
+ * @returns The content offset and length.
96
+ */
97
+ function derHeader(
98
+ data: Uint8Array,
99
+ offset: number,
100
+ ): { offset: number; length: number } {
101
+ offset = offset + 1; // Only low-tag-number forms occur in a PKCS #8 RSA key.
102
+
103
+ let length = data[offset]!;
104
+ offset += 1;
105
+
106
+ if (length & 0x80) {
107
+ const lengthBytes = length & 0x7f;
108
+ length = 0;
109
+ for (let i = 0; i < lengthBytes; i += 1) {
110
+ length = (length << 8) | data[offset]!;
111
+ offset += 1;
112
+ }
107
113
  }
108
114
 
109
- const n = getBigUint(key, RsaPrivateKeyNOffset, RsaPrivateKeyNLength);
110
- const d = getBigUint(key, RsaPrivateKeyDOffset, RsaPrivateKeyDLength);
115
+ return { offset, length };
116
+ }
117
+
118
+ export function rsaParsePrivateKey(key: Uint8Array): SimpleRsaPrivateKey {
119
+ // PrivateKeyInfo ::= SEQUENCE {
120
+ // version INTEGER,
121
+ // privateKeyAlgorithm AlgorithmIdentifier,
122
+ // privateKey OCTET STRING -- wrapping an RSAPrivateKey
123
+ // }
124
+ // RSAPrivateKey ::= SEQUENCE {
125
+ // version Version,
126
+ // modulus INTEGER, -- n
127
+ // publicExponent INTEGER, -- e
128
+ // privateExponent INTEGER -- d
129
+ // -- ... other fields omitted
130
+ // }
131
+
132
+ let offset = derHeader(key, 0).offset; // into PrivateKeyInfo
133
+
134
+ let header = derHeader(key, offset); // skip version
135
+ offset = header.offset + header.length;
136
+ header = derHeader(key, offset); // skip privateKeyAlgorithm
137
+ offset = header.offset + header.length;
138
+
139
+ offset = derHeader(key, offset).offset; // into OCTET STRING
140
+ offset = derHeader(key, offset).offset; // into RSAPrivateKey
141
+
142
+ header = derHeader(key, offset); // skip version
143
+ offset = header.offset + header.length;
144
+
145
+ const nHeader = derHeader(key, offset); // get modulus
146
+ offset = nHeader.offset + nHeader.length;
147
+ const eHeader = derHeader(key, offset); // skip publicExponent
148
+ offset = eHeader.offset + eHeader.length;
149
+ const dHeader = derHeader(key, offset); // get privateExponent
150
+
151
+ const n = getBigUint(key, nHeader.offset, nHeader.length);
152
+ const d = getBigUint(key, dHeader.offset, dHeader.length);
111
153
  return { n, d };
112
154
  }
113
155
 
@@ -510,7 +510,11 @@ export class AdbServerClient {
510
510
  disconnected,
511
511
  );
512
512
 
513
- void transport.disconnected.finally(() => waitAbortController.abort());
513
+ const abortDisconnectWait = () => waitAbortController.abort();
514
+ void transport.disconnected.then(
515
+ abortDisconnectWait,
516
+ abortDisconnectWait,
517
+ );
514
518
 
515
519
  return transport;
516
520
  }
@@ -52,10 +52,14 @@ export class AdbServerTransport implements AdbTransport {
52
52
  this.#disconnected = Promise.race([this.#closed.promise, disconnected]);
53
53
  }
54
54
 
55
- async connect(service: string): Promise<Adb.Socket> {
55
+ async connect(
56
+ service: string,
57
+ options?: AdbTransport.ConnectOptions,
58
+ ): Promise<Adb.Socket> {
56
59
  const socket = await this.#client.createDeviceConnection(
57
60
  { transportId: this.transportId },
58
61
  service,
62
+ options,
59
63
  );
60
64
  this.#sockets.push(socket);
61
65
  return socket;
@@ -78,7 +78,9 @@ export function adbShellProtocolSpawner(
78
78
  stdout,
79
79
  stderr,
80
80
  exitCode,
81
- } satisfies AdbShellProtocolSpawner.WaitResult<Uint8Array[]>;
81
+ } satisfies AdbShellProtocolSpawner.WaitResult<
82
+ Uint8Array[]
83
+ >;
82
84
  } finally {
83
85
  await process.kill();
84
86
  }
@@ -0,0 +1,137 @@
1
+ import type { TransformStream } from "@yume-chan/stream-extra";
2
+
3
+ import type { Adb } from "../../../adb.js";
4
+ import { AdbFeature } from "../../../features.js";
5
+
6
+ import { Format, FormatNameMap } from "./format.js";
7
+ import { Mode } from "./mode.js";
8
+ import {
9
+ createNativeCompressionStream,
10
+ createNativeDecompressionStream,
11
+ hasNativeSupport,
12
+ } from "./native.js";
13
+
14
+ export { Format, FormatNameMap, Mode };
15
+
16
+ export type Adapter = () => TransformStream<Uint8Array, Uint8Array>;
17
+
18
+ const CompressionRegistry: Partial<
19
+ Record<Exclude<Format, typeof Format.None>, Adapter>
20
+ > = {};
21
+
22
+ const DecompressionRegistry: Partial<
23
+ Record<Exclude<Format, typeof Format.None>, Adapter>
24
+ > = {};
25
+
26
+ export function registerCompressionAdapter(
27
+ format: Exclude<Format, typeof Format.None>,
28
+ adapter: Adapter,
29
+ ) {
30
+ CompressionRegistry[format] = adapter;
31
+ }
32
+
33
+ export function registerDecompressionAdapter(
34
+ format: Exclude<Format, typeof Format.None>,
35
+ adapter: Adapter,
36
+ ) {
37
+ DecompressionRegistry[format] = adapter;
38
+ }
39
+
40
+ function hasAdapterOrNativeSupport(format: Format, mode: Mode) {
41
+ if (format === Format.None) {
42
+ return true;
43
+ }
44
+
45
+ const Registry =
46
+ mode === Mode.Compress ? CompressionRegistry : DecompressionRegistry;
47
+ if (Registry[format]) {
48
+ return true;
49
+ }
50
+
51
+ return hasNativeSupport(format, mode);
52
+ }
53
+
54
+ export function canUseBrotli(adb: Adb, mode: Mode) {
55
+ return (
56
+ adb.canUseFeature(AdbFeature.SendReceive2Brotli) &&
57
+ hasAdapterOrNativeSupport(Format.Brotli, mode)
58
+ );
59
+ }
60
+
61
+ export function canUseLz4(adb: Adb, mode: Mode) {
62
+ return (
63
+ adb.canUseFeature(AdbFeature.SendReceive2Lz4) &&
64
+ hasAdapterOrNativeSupport(Format.Lz4, mode)
65
+ );
66
+ }
67
+
68
+ export function canUseZstd(adb: Adb, mode: Mode) {
69
+ return (
70
+ adb.canUseFeature(AdbFeature.SendReceive2Zstd) &&
71
+ hasAdapterOrNativeSupport(Format.Zstd, mode)
72
+ );
73
+ }
74
+
75
+ export function canUseFormat(adb: Adb, format: Format, mode: Mode) {
76
+ switch (format) {
77
+ case Format.None:
78
+ return true;
79
+ case Format.Brotli:
80
+ return canUseBrotli(adb, mode);
81
+ case Format.Lz4:
82
+ return canUseLz4(adb, mode);
83
+ case Format.Zstd:
84
+ return canUseZstd(adb, mode);
85
+ default:
86
+ return false;
87
+ }
88
+ }
89
+
90
+ export function chooseFormat(adb: Adb, mode: Mode) {
91
+ // The order follows
92
+ // https://android.googlesource.com/platform/packages/modules/adb/+/3da39565cad412a743a58b94b875a43ed3c640d3/client/file_sync_client.cpp#277
93
+
94
+ if (canUseZstd(adb, mode)) {
95
+ return Format.Zstd;
96
+ }
97
+
98
+ if (canUseLz4(adb, mode)) {
99
+ return Format.Lz4;
100
+ }
101
+
102
+ if (canUseBrotli(adb, mode)) {
103
+ return Format.Brotli;
104
+ }
105
+
106
+ return Format.None;
107
+ }
108
+
109
+ export function createCompressionStream(
110
+ format: Exclude<Format, typeof Format.None>,
111
+ ): TransformStream<Uint8Array, Uint8Array> {
112
+ const adapter = CompressionRegistry[format];
113
+ if (adapter) {
114
+ return adapter();
115
+ }
116
+ if (hasNativeSupport(format, Mode.Compress)) {
117
+ return createNativeCompressionStream(format);
118
+ }
119
+ throw new Error(
120
+ `No adapter registered for format ${FormatNameMap[format]}`,
121
+ );
122
+ }
123
+
124
+ export function createDecompressionStream(
125
+ format: Exclude<Format, typeof Format.None>,
126
+ ): TransformStream<Uint8Array, Uint8Array> {
127
+ const adapter = DecompressionRegistry[format];
128
+ if (adapter) {
129
+ return adapter();
130
+ }
131
+ if (hasNativeSupport(format, Mode.Decompress)) {
132
+ return createNativeDecompressionStream(format);
133
+ }
134
+ throw new Error(
135
+ `No adapter registered for format ${FormatNameMap[format]}`,
136
+ );
137
+ }
@@ -1,3 +1,5 @@
1
+ // https://android.googlesource.com/platform/packages/modules/adb/+/888a54dcbf954fdffacc8283a793290abcc589cd/file_sync_protocol.h#102
2
+
1
3
  export const None = 0;
2
4
  export const Brotli = 2;
3
5
  export const Lz4 = 3;
@@ -2,4 +2,9 @@ import * as Format from "./format-ns.js";
2
2
 
3
3
  type Format = (typeof Format)[keyof typeof Format];
4
4
 
5
+ export const FormatNameMap = /* #__PURE__ */ (() =>
6
+ Object.fromEntries(
7
+ Object.entries(Format).map(([key, value]) => [value, key]),
8
+ ))() as Record<Format, string>;
9
+
5
10
  export { Format };
@@ -1 +1 @@
1
- export * as Compression from "./compression.js";
1
+ export * as Compression from "./adapters.js";
@@ -0,0 +1,6 @@
1
+ export const Mode = {
2
+ Compress: 0,
3
+ Decompress: 1,
4
+ } as const;
5
+
6
+ export type Mode = (typeof Mode)[keyof typeof Mode];
@@ -0,0 +1,73 @@
1
+ import {
2
+ CompressionStream,
3
+ DecompressionStream,
4
+ TransformStream,
5
+ } from "@yume-chan/stream-extra";
6
+
7
+ import { NOOP } from "../../../utils/no-op.js";
8
+
9
+ import { Format } from "./format.js";
10
+ import { Mode } from "./mode.js";
11
+
12
+ export const FormatMap: Record<Format, string> = {
13
+ [Format.None]: "",
14
+ // https://github.com/whatwg/compression/issues/54
15
+ [Format.Zstd]: "zstd",
16
+ // placeholder
17
+ [Format.Lz4]: "lz4",
18
+ // https://github.com/whatwg/compression/pull/80
19
+ [Format.Brotli]: "brotli",
20
+ };
21
+
22
+ const CompressionFormatCache = new Map<Format, boolean>();
23
+ const DecompressionFormatCache = new Map<Format, boolean>();
24
+
25
+ export function hasNativeSupport(format: Format, mode: Mode) {
26
+ if (format === Format.None) {
27
+ return true;
28
+ }
29
+
30
+ const Cache =
31
+ mode === Mode.Compress
32
+ ? CompressionFormatCache
33
+ : DecompressionFormatCache;
34
+ if (Cache.has(format)) {
35
+ return Cache.get(format)!;
36
+ }
37
+
38
+ const Stream =
39
+ mode === Mode.Compress ? CompressionStream : DecompressionStream;
40
+ if (!Stream) {
41
+ return false;
42
+ }
43
+
44
+ try {
45
+ const stream = new Stream(FormatMap[format]);
46
+ void stream.writable.abort().catch(NOOP);
47
+ Cache.set(format, true);
48
+ return true;
49
+ } catch {
50
+ Cache.set(format, false);
51
+ return false;
52
+ }
53
+ }
54
+
55
+ export function createNativeCompressionStream(
56
+ format: Format,
57
+ ): CompressionStream {
58
+ if (format === Format.None) {
59
+ return new TransformStream();
60
+ }
61
+
62
+ return new CompressionStream(FormatMap[format]);
63
+ }
64
+
65
+ export function createNativeDecompressionStream(
66
+ format: Format,
67
+ ): DecompressionStream {
68
+ if (format === Format.None) {
69
+ return new TransformStream();
70
+ }
71
+
72
+ return new DecompressionStream(FormatMap[format]);
73
+ }
@@ -10,3 +10,4 @@ export const ListV2 = encodeId("LIS2");
10
10
  export const Send = encodeId("SEND");
11
11
  export const SendV2 = encodeId("SND2");
12
12
  export const Receive = encodeId("RECV");
13
+ export const ReceiveV2 = encodeId("RCV2");
@@ -0,0 +1,9 @@
1
+ export const SyncFlag = {
2
+ None: 0,
3
+ Brotli: 1,
4
+ Lz4: 2,
5
+ Zstd: 4,
6
+ DryRun: 0x80000000,
7
+ } as const;
8
+
9
+ export type SyncFlag = (typeof SyncFlag)[keyof typeof SyncFlag];
@@ -1,11 +1,15 @@
1
- import { ReadableStream } from "@yume-chan/stream-extra";
1
+ import type { TransformStream } from "@yume-chan/stream-extra";
2
+ import { InspectStream, ReadableStream } from "@yume-chan/stream-extra";
2
3
  import type { StructValue } from "@yume-chan/struct";
3
4
  import { buffer, struct, u32 } from "@yume-chan/struct";
4
5
 
5
6
  import { RequestId, ResponseId } from "../id/index.js";
7
+ import { Compression } from "../index-ns.js";
6
8
  import type { SocketPool } from "../socket-pool.js";
7
9
  import { Error as AdbSyncError } from "../socket.js";
8
10
 
11
+ import { SyncFlag } from "./flag.js";
12
+
9
13
  export const DataResponse = struct(
10
14
  { data: buffer(u32) },
11
15
  { littleEndian: true },
@@ -13,37 +17,141 @@ export const DataResponse = struct(
13
17
 
14
18
  export type DataResponse = StructValue<typeof DataResponse>;
15
19
 
16
- export async function* generator(
20
+ export interface ReceiveSession {
21
+ /**
22
+ * The readable stream to read the file content from.
23
+ */
24
+ readable: ReadableStream<Uint8Array>;
25
+ /**
26
+ * Gets the number of bytes read from {@link readable}.
27
+ */
28
+ bytesRead: number;
29
+ /**
30
+ * When using Pull v2, gets the compression format used (might be `None`).
31
+ *
32
+ * When using Pull v1, this will always be `undefined`.
33
+ */
34
+ compression?: Compression.Format | undefined;
35
+ /**
36
+ * When using Pull v2, gets the size of the compressed data received from the device.
37
+ * Might be same as {@link bytesRead} if no compression was applied.
38
+ */
39
+ bytesCompressed: number;
40
+ }
41
+
42
+ export function receiveV1(pool: SocketPool, path: string): ReceiveSession {
43
+ let bytesReceived = 0;
44
+ return {
45
+ readable: ReadableStream.from(
46
+ pool.withSocketGenerator(async function* (socket) {
47
+ await socket.writeRequest(RequestId.Receive, path);
48
+
49
+ for await (const packet of socket.readResponses(
50
+ ResponseId.Data,
51
+ DataResponse,
52
+ )) {
53
+ bytesReceived += packet.data.length;
54
+ yield packet.data;
55
+ }
56
+ }),
57
+ ),
58
+ get bytesRead() {
59
+ return bytesReceived;
60
+ },
61
+ compression: undefined,
62
+ get bytesCompressed() {
63
+ return bytesReceived;
64
+ },
65
+ };
66
+ }
67
+
68
+ export function receiveV2(
17
69
  pool: SocketPool,
18
70
  path: string,
19
- ): AsyncGenerator<Uint8Array, void, void> {
20
- const socket = await pool.acquire();
21
- let completed = false;
22
- let error: unknown;
23
-
24
- try {
25
- await socket.writeRequest(RequestId.Receive, path);
26
- for await (const packet of socket.readResponses(
27
- ResponseId.Data,
28
- DataResponse,
29
- )) {
30
- yield packet.data;
31
- }
32
- completed = true;
33
- } catch (e) {
34
- error = e;
35
- throw e;
36
- } finally {
37
- await pool.release(
38
- socket,
39
- !(completed || error instanceof AdbSyncError),
40
- );
71
+ compression?: Compression.Format,
72
+ ): ReceiveSession {
73
+ let flags: SyncFlag = SyncFlag.None;
74
+ let decompressStream: TransformStream<Uint8Array, Uint8Array> | undefined;
75
+ switch (compression) {
76
+ case Compression.Format.Brotli:
77
+ flags |= SyncFlag.Brotli;
78
+ decompressStream = Compression.createDecompressionStream(
79
+ Compression.Format.Brotli,
80
+ );
81
+ break;
82
+ case Compression.Format.Lz4:
83
+ flags |= SyncFlag.Lz4;
84
+ decompressStream = Compression.createDecompressionStream(
85
+ Compression.Format.Lz4,
86
+ );
87
+ break;
88
+ case Compression.Format.Zstd:
89
+ flags |= SyncFlag.Zstd;
90
+ decompressStream = Compression.createDecompressionStream(
91
+ Compression.Format.Zstd,
92
+ );
93
+ break;
41
94
  }
95
+
96
+ let bytesReceived = 0;
97
+ const raw = ReadableStream.from(
98
+ pool.withSocketGenerator(async function* (socket) {
99
+ await socket.writeRequest(RequestId.ReceiveV2, path);
100
+ await socket.writeRequest(RequestId.ReceiveV2, flags);
101
+
102
+ for await (const packet of socket.readResponses(
103
+ ResponseId.Data,
104
+ DataResponse,
105
+ )) {
106
+ bytesReceived += packet.data.length;
107
+ yield packet.data;
108
+ }
109
+ }),
110
+ );
111
+
112
+ if (decompressStream) {
113
+ let bytesRead = 0;
114
+ return {
115
+ readable: raw.pipeThrough(decompressStream).pipeThrough(
116
+ new InspectStream((chunk) => {
117
+ bytesRead += chunk.length;
118
+ }),
119
+ ),
120
+ get bytesRead() {
121
+ return bytesRead;
122
+ },
123
+ compression,
124
+ get bytesCompressed() {
125
+ return bytesReceived;
126
+ },
127
+ };
128
+ }
129
+
130
+ return {
131
+ readable: raw,
132
+ get bytesRead() {
133
+ return bytesReceived;
134
+ },
135
+ compression: Compression.Format.None,
136
+ get bytesCompressed() {
137
+ return bytesReceived;
138
+ },
139
+ };
42
140
  }
43
141
 
44
- export function stream(
142
+ export function receive(
143
+ version: 1 | 2,
45
144
  pool: SocketPool,
46
145
  path: string,
47
- ): ReadableStream<Uint8Array> {
48
- return ReadableStream.from(generator(pool, path));
146
+ compression?: Compression.Format,
147
+ ) {
148
+ if (version === 2) {
149
+ return receiveV2(pool, path, compression);
150
+ }
151
+
152
+ if (compression !== undefined && compression !== Compression.Format.None) {
153
+ throw new AdbSyncError("compression is not supported in v1");
154
+ }
155
+
156
+ return receiveV1(pool, path);
49
157
  }