appium-ios-remotexpc 0.0.4 → 0.0.6
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 +12 -0
- package/build/src/index.d.ts +1 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/lib/apple-tv/constants.d.ts +28 -0
- package/build/src/lib/apple-tv/constants.d.ts.map +1 -1
- package/build/src/lib/apple-tv/constants.js +35 -0
- package/build/src/lib/apple-tv/encryption/chacha20-poly1305.d.ts +22 -0
- package/build/src/lib/apple-tv/encryption/chacha20-poly1305.d.ts.map +1 -0
- package/build/src/lib/apple-tv/encryption/chacha20-poly1305.js +97 -0
- package/build/src/lib/apple-tv/encryption/ed25519.d.ts +16 -0
- package/build/src/lib/apple-tv/encryption/ed25519.d.ts.map +1 -0
- package/build/src/lib/apple-tv/encryption/ed25519.js +93 -0
- package/build/src/lib/apple-tv/encryption/hkdf.d.ts +18 -0
- package/build/src/lib/apple-tv/encryption/hkdf.d.ts.map +1 -0
- package/build/src/lib/apple-tv/encryption/hkdf.js +73 -0
- package/build/src/lib/apple-tv/encryption/index.d.ts +5 -0
- package/build/src/lib/apple-tv/encryption/index.d.ts.map +1 -0
- package/build/src/lib/apple-tv/encryption/index.js +4 -0
- package/build/src/lib/apple-tv/encryption/opack2.d.ts +57 -0
- package/build/src/lib/apple-tv/encryption/opack2.d.ts.map +1 -0
- package/build/src/lib/apple-tv/encryption/opack2.js +203 -0
- package/build/src/lib/remote-xpc/remote-xpc-connection.d.ts +1 -2
- package/build/src/lib/remote-xpc/remote-xpc-connection.d.ts.map +1 -1
- package/build/src/lib/remote-xpc/remote-xpc-connection.js +1 -2
- package/build/src/lib/tunnel/index.d.ts +3 -2
- package/build/src/lib/tunnel/index.d.ts.map +1 -1
- package/build/src/lib/tunnel/index.js +6 -3
- package/build/src/lib/types.d.ts +11 -0
- package/build/src/lib/types.d.ts.map +1 -1
- package/build/src/services/ios/diagnostic-service/index.d.ts.map +1 -1
- package/build/src/services/ios/diagnostic-service/index.js +0 -1
- package/build/src/services.d.ts +3 -3
- package/build/src/services.d.ts.map +1 -1
- package/build/src/services.js +8 -5
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/lib/apple-tv/constants.ts +42 -0
- package/src/lib/apple-tv/encryption/chacha20-poly1305.ts +147 -0
- package/src/lib/apple-tv/encryption/ed25519.ts +126 -0
- package/src/lib/apple-tv/encryption/hkdf.ts +95 -0
- package/src/lib/apple-tv/encryption/index.ts +11 -0
- package/src/lib/apple-tv/encryption/opack2.ts +257 -0
- package/src/lib/remote-xpc/remote-xpc-connection.ts +1 -2
- package/src/lib/tunnel/index.ts +7 -5
- package/src/lib/types.ts +12 -0
- package/src/services/ios/diagnostic-service/index.ts +0 -1
- package/src/services.ts +10 -7
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { logger } from '@appium/support';
|
|
2
|
+
import {
|
|
3
|
+
type KeyPairKeyObjectResult,
|
|
4
|
+
generateKeyPairSync,
|
|
5
|
+
sign,
|
|
6
|
+
} from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
import { CryptographyError } from '../errors.js';
|
|
9
|
+
import type { PairingKeys } from '../types.js';
|
|
10
|
+
|
|
11
|
+
const log = logger.getLogger('Ed25519');
|
|
12
|
+
|
|
13
|
+
const ED25519_PUBLIC_KEY_LENGTH = 32;
|
|
14
|
+
const ED25519_PRIVATE_KEY_LENGTH = 32;
|
|
15
|
+
const ED25519_PKCS8_PREFIX = Buffer.from(
|
|
16
|
+
'302e020100300506032b657004220420',
|
|
17
|
+
'hex',
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generates a new Ed25519 key pair for cryptographic operations
|
|
22
|
+
* @returns PairingKeys object containing 32-byte public and private key buffers
|
|
23
|
+
* @throws CryptographyError if key generation fails
|
|
24
|
+
*/
|
|
25
|
+
export function generateEd25519KeyPair(): PairingKeys {
|
|
26
|
+
try {
|
|
27
|
+
const keyPair: KeyPairKeyObjectResult = generateKeyPairSync('ed25519');
|
|
28
|
+
|
|
29
|
+
const publicKeyDer = keyPair.publicKey.export({
|
|
30
|
+
type: 'spki',
|
|
31
|
+
format: 'der',
|
|
32
|
+
}) as Buffer;
|
|
33
|
+
|
|
34
|
+
const privateKeyDer = keyPair.privateKey.export({
|
|
35
|
+
type: 'pkcs8',
|
|
36
|
+
format: 'der',
|
|
37
|
+
}) as Buffer;
|
|
38
|
+
|
|
39
|
+
const publicKeyBuffer = extractEd25519PublicKey(publicKeyDer);
|
|
40
|
+
const privateKeyBuffer = extractEd25519PrivateKey(privateKeyDer);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
publicKey: publicKeyBuffer,
|
|
44
|
+
privateKey: privateKeyBuffer,
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
log.error('Failed to generate Ed25519 key pair:', error);
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
throw new CryptographyError(
|
|
50
|
+
`Failed to generate Ed25519 key pair: ${message}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates an Ed25519 digital signature for the provided data
|
|
57
|
+
* @param data - The data to sign
|
|
58
|
+
* @param privateKey - 32-byte Ed25519 private key
|
|
59
|
+
* @returns Buffer containing the 64-byte signature
|
|
60
|
+
* @throws CryptographyError if signing fails or private key is invalid
|
|
61
|
+
*/
|
|
62
|
+
export function createEd25519Signature(
|
|
63
|
+
data: Buffer,
|
|
64
|
+
privateKey: Buffer,
|
|
65
|
+
): Buffer {
|
|
66
|
+
if (!data || data.length === 0) {
|
|
67
|
+
throw new CryptographyError('Data to sign cannot be empty');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!privateKey || privateKey.length !== ED25519_PRIVATE_KEY_LENGTH) {
|
|
71
|
+
throw new CryptographyError(
|
|
72
|
+
`Private key must be ${ED25519_PRIVATE_KEY_LENGTH} bytes`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const privateKeyDer = Buffer.concat([ED25519_PKCS8_PREFIX, privateKey]);
|
|
78
|
+
|
|
79
|
+
return sign(null, data, {
|
|
80
|
+
key: privateKeyDer,
|
|
81
|
+
format: 'der',
|
|
82
|
+
type: 'pkcs8',
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
log.error('Failed to create Ed25519 signature:', error);
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
throw new CryptographyError(
|
|
88
|
+
`Failed to create Ed25519 signature: ${message}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extracts the raw 32-byte public key from DER-encoded SPKI format
|
|
95
|
+
* @param publicKeyDer - DER-encoded public key
|
|
96
|
+
* @returns 32-byte public key buffer
|
|
97
|
+
* @throws CryptographyError if extraction fails
|
|
98
|
+
*/
|
|
99
|
+
function extractEd25519PublicKey(publicKeyDer: Buffer): Buffer {
|
|
100
|
+
if (publicKeyDer.length < ED25519_PUBLIC_KEY_LENGTH) {
|
|
101
|
+
throw new CryptographyError('Invalid public key DER format');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return publicKeyDer.subarray(publicKeyDer.length - ED25519_PUBLIC_KEY_LENGTH);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Extracts the raw 32-byte private key from DER-encoded PKCS#8 format
|
|
109
|
+
* @param privateKeyDer - DER-encoded private key
|
|
110
|
+
* @returns 32-byte private key buffer
|
|
111
|
+
* @throws CryptographyError if extraction fails
|
|
112
|
+
*/
|
|
113
|
+
function extractEd25519PrivateKey(privateKeyDer: Buffer): Buffer {
|
|
114
|
+
const octetStringPattern = Buffer.from([0x04, 0x20]);
|
|
115
|
+
const index = privateKeyDer.indexOf(octetStringPattern);
|
|
116
|
+
|
|
117
|
+
if (index !== -1 && index + 34 <= privateKeyDer.length) {
|
|
118
|
+
return privateKeyDer.subarray(index + 2, index + 34);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (privateKeyDer.length >= 48) {
|
|
122
|
+
return privateKeyDer.subarray(16, 48);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new CryptographyError('Unable to extract private key from DER format');
|
|
126
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { logger } from '@appium/support';
|
|
2
|
+
import { createHmac } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
import { HKDF_HASH_ALGORITHM, HKDF_HASH_LENGTH } from '../constants.js';
|
|
5
|
+
import { CryptographyError } from '../errors.js';
|
|
6
|
+
|
|
7
|
+
const log = logger.getLogger('HKDF');
|
|
8
|
+
|
|
9
|
+
export interface HKDFParams {
|
|
10
|
+
ikm: Buffer;
|
|
11
|
+
salt: Buffer | null;
|
|
12
|
+
info: Buffer;
|
|
13
|
+
length: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MAX_OUTPUT_LENGTH = 255 * HKDF_HASH_LENGTH;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* HMAC-based Key Derivation Function (HKDF) as defined in RFC 5869
|
|
20
|
+
* Derives cryptographic keys from input key material using a two-step process:
|
|
21
|
+
* 1. Extract: Generate a pseudorandom key from the input key material
|
|
22
|
+
* 2. Expand: Expand the pseudorandom key to the desired output length
|
|
23
|
+
*
|
|
24
|
+
* @param params - HKDF parameters including input key material, salt, info, and desired output length
|
|
25
|
+
* @returns Buffer containing the derived key material of specified length
|
|
26
|
+
* @throws CryptographyError if derivation fails or parameters are invalid
|
|
27
|
+
*/
|
|
28
|
+
export function hkdf(params: HKDFParams): Buffer {
|
|
29
|
+
const { ikm, salt, info, length } = params;
|
|
30
|
+
|
|
31
|
+
if (!ikm || ikm.length === 0) {
|
|
32
|
+
throw new CryptographyError('Input key material (IKM) cannot be empty');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!info) {
|
|
36
|
+
throw new CryptographyError('Info parameter is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (length <= 0) {
|
|
40
|
+
throw new CryptographyError('Output length must be positive');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (length > MAX_OUTPUT_LENGTH) {
|
|
44
|
+
throw new CryptographyError(
|
|
45
|
+
`Output length cannot exceed ${MAX_OUTPUT_LENGTH} bytes`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const extractedKey = hkdfExtract(ikm, salt);
|
|
51
|
+
return hkdfExpand(extractedKey, info, length);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
log.error('HKDF derivation failed:', error);
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
throw new CryptographyError(`HKDF derivation failed: ${message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* HKDF Extract step: generates a pseudorandom key from input key material
|
|
61
|
+
* @param ikm - Input key material
|
|
62
|
+
* @param salt - Optional salt value (uses zero salt if null)
|
|
63
|
+
* @returns Pseudorandom key of hash length
|
|
64
|
+
*/
|
|
65
|
+
function hkdfExtract(ikm: Buffer, salt: Buffer | null): Buffer {
|
|
66
|
+
const actualSalt = salt || Buffer.alloc(HKDF_HASH_LENGTH);
|
|
67
|
+
return createHmac(HKDF_HASH_ALGORITHM, actualSalt).update(ikm).digest();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* HKDF Expand a step: expands a pseudorandom key to desired output length
|
|
72
|
+
* @param prk - Pseudorandom key from extract step
|
|
73
|
+
* @param info - Context and application specific information
|
|
74
|
+
* @param length - Desired output key material length
|
|
75
|
+
* @returns Output key material of specified length
|
|
76
|
+
*/
|
|
77
|
+
function hkdfExpand(prk: Buffer, info: Buffer, length: number): Buffer {
|
|
78
|
+
const numberOfBlocks = Math.ceil(length / HKDF_HASH_LENGTH);
|
|
79
|
+
const blocks: Buffer[] = [];
|
|
80
|
+
let previousBlock: Buffer = Buffer.alloc(0);
|
|
81
|
+
|
|
82
|
+
for (let blockIndex = 1; blockIndex <= numberOfBlocks; blockIndex++) {
|
|
83
|
+
const hmac = createHmac(HKDF_HASH_ALGORITHM, prk);
|
|
84
|
+
hmac.update(previousBlock);
|
|
85
|
+
hmac.update(info);
|
|
86
|
+
hmac.update(Buffer.from([blockIndex]));
|
|
87
|
+
|
|
88
|
+
const currentBlock = hmac.digest();
|
|
89
|
+
blocks.push(currentBlock);
|
|
90
|
+
previousBlock = currentBlock;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const outputKeyMaterial = Buffer.concat(blocks);
|
|
94
|
+
return outputKeyMaterial.subarray(0, length);
|
|
95
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Opack2 } from './opack2.js';
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
encryptChaCha20Poly1305,
|
|
5
|
+
decryptChaCha20Poly1305,
|
|
6
|
+
type ChaCha20Poly1305Params,
|
|
7
|
+
} from './chacha20-poly1305.js';
|
|
8
|
+
|
|
9
|
+
export { generateEd25519KeyPair, createEd25519Signature } from './ed25519.js';
|
|
10
|
+
|
|
11
|
+
export { hkdf, type HKDFParams } from './hkdf.js';
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as constants from '../constants.js';
|
|
2
|
+
import { AppleTVError } from '../errors.js';
|
|
3
|
+
|
|
4
|
+
interface SerializableArray extends Array<SerializableValue> {}
|
|
5
|
+
interface SerializableObject extends Record<string, SerializableValue> {}
|
|
6
|
+
|
|
7
|
+
type SerializableValue =
|
|
8
|
+
| null
|
|
9
|
+
| undefined
|
|
10
|
+
| boolean
|
|
11
|
+
| number
|
|
12
|
+
| string
|
|
13
|
+
| Buffer
|
|
14
|
+
| SerializableArray
|
|
15
|
+
| SerializableObject;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* OPACK2 binary serialization format encoder
|
|
19
|
+
* Implements Apple's OPACK2 protocol for efficient binary serialization of structured data
|
|
20
|
+
*/
|
|
21
|
+
export class Opack2 {
|
|
22
|
+
/**
|
|
23
|
+
* Serializes a JavaScript object to OPACK2 binary format
|
|
24
|
+
* @param obj - The object to serialize (supports primitives, arrays, objects, and Buffers)
|
|
25
|
+
* @returns Buffer containing the serialized data
|
|
26
|
+
* @throws AppleTVError if the object contains unsupported types
|
|
27
|
+
*/
|
|
28
|
+
static dumps(obj: SerializableValue): Buffer {
|
|
29
|
+
return this.encode(obj);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Main encoding dispatcher that routes values to appropriate type-specific encoders
|
|
34
|
+
* @param obj - Value to encode
|
|
35
|
+
* @returns Buffer containing encoded value
|
|
36
|
+
* @throws AppleTVError for unsupported types
|
|
37
|
+
*/
|
|
38
|
+
private static encode(obj: SerializableValue): Buffer {
|
|
39
|
+
if (obj === null || obj === undefined) {
|
|
40
|
+
return Buffer.from([constants.OPACK2_NULL]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof obj === 'boolean') {
|
|
44
|
+
return Buffer.from([
|
|
45
|
+
obj ? constants.OPACK2_TRUE : constants.OPACK2_FALSE,
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (typeof obj === 'number') {
|
|
50
|
+
return this.encodeNumber(obj);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof obj === 'string') {
|
|
54
|
+
return this.encodeString(obj);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Buffer.isBuffer(obj)) {
|
|
58
|
+
return this.encodeBytes(obj);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(obj)) {
|
|
62
|
+
return this.encodeArray(obj);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
typeof obj === 'object' &&
|
|
67
|
+
!Array.isArray(obj) &&
|
|
68
|
+
!Buffer.isBuffer(obj)
|
|
69
|
+
) {
|
|
70
|
+
return this.encodeDict(obj as Record<string, SerializableValue>);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new AppleTVError(
|
|
74
|
+
`Unsupported type for OPACK2 serialization: ${typeof obj}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Encodes numeric values with the appropriate size optimization
|
|
80
|
+
* @param num - Number to encode
|
|
81
|
+
* @returns Buffer containing encoded number
|
|
82
|
+
*/
|
|
83
|
+
private static encodeNumber(num: number): Buffer {
|
|
84
|
+
if (!Number.isInteger(num) || num < 0) {
|
|
85
|
+
const buffer = Buffer.allocUnsafe(5);
|
|
86
|
+
buffer[0] = constants.OPACK2_FLOAT_MARKER;
|
|
87
|
+
buffer.writeFloatLE(num, 1);
|
|
88
|
+
return buffer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (num <= constants.OPACK2_SMALL_INT_MAX) {
|
|
92
|
+
return Buffer.from([num + constants.OPACK2_SMALL_INT_OFFSET]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (num <= constants.OPACK2_UINT8_MAX) {
|
|
96
|
+
return Buffer.from([constants.OPACK2_INT8_MARKER, num]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (num <= constants.OPACK2_UINT32_MAX) {
|
|
100
|
+
const buffer = Buffer.allocUnsafe(5);
|
|
101
|
+
buffer[0] = constants.OPACK2_INT32_MARKER;
|
|
102
|
+
buffer.writeUInt32LE(num, 1);
|
|
103
|
+
return buffer;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (num <= Number.MAX_SAFE_INTEGER) {
|
|
107
|
+
const buffer = Buffer.allocUnsafe(9);
|
|
108
|
+
buffer[0] = constants.OPACK2_INT64_MARKER;
|
|
109
|
+
buffer.writeBigUInt64LE(BigInt(num), 1);
|
|
110
|
+
return buffer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new AppleTVError(`Number too large for OPACK2 encoding: ${num}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Encodes UTF-8 strings with length-optimized headers
|
|
118
|
+
* @param str - String to encode
|
|
119
|
+
* @returns Buffer containing encoded string
|
|
120
|
+
*/
|
|
121
|
+
private static encodeString(str: string): Buffer {
|
|
122
|
+
const encoded = Buffer.from(str, 'utf8');
|
|
123
|
+
const length = encoded.length;
|
|
124
|
+
|
|
125
|
+
if (length <= constants.OPACK2_SMALL_STRING_MAX) {
|
|
126
|
+
return Buffer.concat([
|
|
127
|
+
Buffer.from([constants.OPACK2_SMALL_STRING_BASE + length]),
|
|
128
|
+
encoded,
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (length <= constants.OPACK2_UINT8_MAX) {
|
|
133
|
+
return Buffer.concat([
|
|
134
|
+
Buffer.from([constants.OPACK2_STRING_8BIT_LEN_MARKER, length]),
|
|
135
|
+
encoded,
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (length <= constants.OPACK2_UINT16_MAX) {
|
|
140
|
+
const header = Buffer.allocUnsafe(3);
|
|
141
|
+
header[0] = constants.OPACK2_STRING_16BIT_LEN_MARKER;
|
|
142
|
+
header.writeUInt16BE(length, 1);
|
|
143
|
+
return Buffer.concat([header, encoded]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (length <= constants.OPACK2_UINT32_MAX) {
|
|
147
|
+
const header = Buffer.allocUnsafe(5);
|
|
148
|
+
header[0] = constants.OPACK2_STRING_32BIT_LEN_MARKER;
|
|
149
|
+
header.writeUInt32BE(length, 1);
|
|
150
|
+
return Buffer.concat([header, encoded]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new AppleTVError(
|
|
154
|
+
`String too long for OPACK2 encoding: ${length} bytes`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Encodes binary data with length-optimized headers
|
|
160
|
+
* @param bytes - Buffer to encode
|
|
161
|
+
* @returns Buffer containing encoded binary data
|
|
162
|
+
*/
|
|
163
|
+
private static encodeBytes(bytes: Buffer): Buffer {
|
|
164
|
+
const length = bytes.length;
|
|
165
|
+
|
|
166
|
+
if (length <= constants.OPACK2_SMALL_BYTES_MAX) {
|
|
167
|
+
return Buffer.concat([
|
|
168
|
+
Buffer.from([constants.OPACK2_SMALL_BYTES_BASE + length]),
|
|
169
|
+
bytes,
|
|
170
|
+
]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (length <= constants.OPACK2_UINT8_MAX) {
|
|
174
|
+
return Buffer.concat([
|
|
175
|
+
Buffer.from([constants.OPACK2_BYTES_8BIT_LEN_MARKER, length]),
|
|
176
|
+
bytes,
|
|
177
|
+
]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (length <= constants.OPACK2_UINT16_MAX) {
|
|
181
|
+
const header = Buffer.allocUnsafe(3);
|
|
182
|
+
header[0] = constants.OPACK2_BYTES_16BIT_LEN_MARKER;
|
|
183
|
+
header.writeUInt16BE(length, 1);
|
|
184
|
+
return Buffer.concat([header, bytes]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (length <= constants.OPACK2_UINT32_MAX) {
|
|
188
|
+
const header = Buffer.allocUnsafe(5);
|
|
189
|
+
header[0] = constants.OPACK2_BYTES_32BIT_LEN_MARKER;
|
|
190
|
+
header.writeUInt32BE(length, 1);
|
|
191
|
+
return Buffer.concat([header, bytes]);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new AppleTVError(
|
|
195
|
+
`Byte array too long for OPACK2 encoding: ${length} bytes`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Encodes arrays with count-optimized headers
|
|
201
|
+
* @param arr - Array to encode
|
|
202
|
+
* @returns Buffer containing encoded array
|
|
203
|
+
*/
|
|
204
|
+
private static encodeArray(arr: SerializableValue[]): Buffer {
|
|
205
|
+
const length = arr.length;
|
|
206
|
+
|
|
207
|
+
if (length <= constants.OPACK2_SMALL_ARRAY_MAX) {
|
|
208
|
+
const parts: Buffer[] = [
|
|
209
|
+
Buffer.from([constants.OPACK2_SMALL_ARRAY_BASE + length]),
|
|
210
|
+
];
|
|
211
|
+
for (const item of arr) {
|
|
212
|
+
parts.push(this.encode(item));
|
|
213
|
+
}
|
|
214
|
+
return Buffer.concat(parts);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const parts: Buffer[] = [
|
|
218
|
+
Buffer.from([constants.OPACK2_VARIABLE_ARRAY_MARKER]),
|
|
219
|
+
];
|
|
220
|
+
for (const item of arr) {
|
|
221
|
+
parts.push(this.encode(item));
|
|
222
|
+
}
|
|
223
|
+
parts.push(Buffer.from([constants.OPACK2_NULL]));
|
|
224
|
+
return Buffer.concat(parts);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Encodes objects/dictionaries with count-optimized headers
|
|
229
|
+
* @param dict - Object to encode
|
|
230
|
+
* @returns Buffer containing encoded dictionary
|
|
231
|
+
*/
|
|
232
|
+
private static encodeDict(dict: Record<string, SerializableValue>): Buffer {
|
|
233
|
+
const entries = Object.entries(dict);
|
|
234
|
+
const length = entries.length;
|
|
235
|
+
|
|
236
|
+
if (length < constants.OPACK2_SMALL_DICT_MAX) {
|
|
237
|
+
const parts: Buffer[] = [
|
|
238
|
+
Buffer.from([constants.OPACK2_SMALL_DICT_BASE + length]),
|
|
239
|
+
];
|
|
240
|
+
for (const [key, value] of entries) {
|
|
241
|
+
parts.push(this.encode(key));
|
|
242
|
+
parts.push(this.encode(value));
|
|
243
|
+
}
|
|
244
|
+
return Buffer.concat(parts);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const parts: Buffer[] = [
|
|
248
|
+
Buffer.from([constants.OPACK2_VARIABLE_DICT_MARKER]),
|
|
249
|
+
];
|
|
250
|
+
for (const [key, value] of entries) {
|
|
251
|
+
parts.push(this.encode(key));
|
|
252
|
+
parts.push(this.encode(value));
|
|
253
|
+
}
|
|
254
|
+
parts.push(Buffer.from([constants.OPACK2_NULL, constants.OPACK2_NULL]));
|
|
255
|
+
return Buffer.concat(parts);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -457,5 +457,4 @@ function extractServices(response: string): ServicesResponse {
|
|
|
457
457
|
return { services };
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
export
|
|
461
|
-
export { type Service, type ServicesResponse };
|
|
460
|
+
export { RemoteXpcConnection, type Service, type ServicesResponse };
|
package/src/lib/tunnel/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { logger } from '@appium/support';
|
|
|
2
2
|
import type { TLSSocket } from 'tls';
|
|
3
3
|
import { type TunnelConnection, connectToTunnelLockdown } from 'tuntap-bridge';
|
|
4
4
|
|
|
5
|
-
import RemoteXpcConnection from '../remote-xpc/remote-xpc-connection.js';
|
|
5
|
+
import { RemoteXpcConnection } from '../remote-xpc/remote-xpc-connection.js';
|
|
6
6
|
|
|
7
7
|
const log = logger.getLogger('TunnelManager');
|
|
8
8
|
|
|
@@ -64,9 +64,12 @@ class TunnelManagerService {
|
|
|
64
64
|
async createRemoteXPCConnection(
|
|
65
65
|
address: string,
|
|
66
66
|
rsdPort: number,
|
|
67
|
-
): Promise<
|
|
67
|
+
): Promise<RemoteXpcConnection> {
|
|
68
68
|
try {
|
|
69
|
-
const remoteXPC = new RemoteXpcConnection([
|
|
69
|
+
const remoteXPC: RemoteXpcConnection = new RemoteXpcConnection([
|
|
70
|
+
address,
|
|
71
|
+
rsdPort,
|
|
72
|
+
]);
|
|
70
73
|
|
|
71
74
|
// Connect to RemoteXPC with delay between retries
|
|
72
75
|
let retries = 3;
|
|
@@ -75,7 +78,6 @@ class TunnelManagerService {
|
|
|
75
78
|
while (retries > 0) {
|
|
76
79
|
try {
|
|
77
80
|
await remoteXPC.connect();
|
|
78
|
-
|
|
79
81
|
// Update the registry entry with the RemoteXPC connection
|
|
80
82
|
const entry = this.tunnelRegistry.get(address);
|
|
81
83
|
if (entry) {
|
|
@@ -249,5 +251,5 @@ class TunnelManagerService {
|
|
|
249
251
|
// Create and export the singleton instance
|
|
250
252
|
export const TunnelManager = new TunnelManagerService();
|
|
251
253
|
// Export packet streaming IPC functionality
|
|
252
|
-
export { PacketStreamServer } from './packet-stream-server.js';
|
|
253
254
|
export { PacketStreamClient } from './packet-stream-client.js';
|
|
255
|
+
export { PacketStreamServer } from './packet-stream-server.js';
|
package/src/lib/types.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { EventEmitter } from 'events';
|
|
|
5
5
|
import type { PacketData } from 'tuntap-bridge';
|
|
6
6
|
|
|
7
7
|
import type { BaseService, Service } from '../services/ios/base-service.js';
|
|
8
|
+
import type { RemoteXpcConnection } from './remote-xpc/remote-xpc-connection.js';
|
|
8
9
|
import type { Device } from './usbmux/index.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -190,6 +191,17 @@ export interface DiagnosticsServiceConstructor {
|
|
|
190
191
|
new (address: [string, number]): DiagnosticsService;
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Represents a DiagnosticsService instance with its associated RemoteXPC connection
|
|
196
|
+
* This allows callers to properly manage the connection lifecycle
|
|
197
|
+
*/
|
|
198
|
+
export interface DiagnosticsServiceWithConnection {
|
|
199
|
+
/** The DiagnosticsService instance */
|
|
200
|
+
diagnosticsService: DiagnosticsService;
|
|
201
|
+
/** The RemoteXPC connection that can be used to close the connection */
|
|
202
|
+
remoteXPC: RemoteXpcConnection;
|
|
203
|
+
}
|
|
204
|
+
|
|
193
205
|
/**
|
|
194
206
|
* Options for configuring syslog capture
|
|
195
207
|
*/
|
package/src/services.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { strongbox } from '@appium/strongbox';
|
|
2
2
|
|
|
3
|
-
import RemoteXpcConnection from './lib/remote-xpc/remote-xpc-connection.js';
|
|
3
|
+
import { RemoteXpcConnection } from './lib/remote-xpc/remote-xpc-connection.js';
|
|
4
4
|
import { TunnelManager } from './lib/tunnel/index.js';
|
|
5
5
|
import { TunnelApiClient } from './lib/tunnel/tunnel-api-client.js';
|
|
6
6
|
import type {
|
|
7
|
-
|
|
7
|
+
DiagnosticsServiceWithConnection,
|
|
8
8
|
SyslogService as SyslogServiceType,
|
|
9
9
|
} from './lib/types.js';
|
|
10
10
|
import DiagnosticsService from './services/ios/diagnostic-service/index.js';
|
|
@@ -15,15 +15,18 @@ const TUNNEL_REGISTRY_PORT = 'tunnelRegistryPort';
|
|
|
15
15
|
|
|
16
16
|
export async function startDiagnosticsService(
|
|
17
17
|
udid: string,
|
|
18
|
-
): Promise<
|
|
18
|
+
): Promise<DiagnosticsServiceWithConnection> {
|
|
19
19
|
const { remoteXPC, tunnelConnection } = await createRemoteXPCConnection(udid);
|
|
20
20
|
const diagnosticsService = remoteXPC.findService(
|
|
21
21
|
DiagnosticsService.RSD_SERVICE_NAME,
|
|
22
22
|
);
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
return {
|
|
24
|
+
remoteXPC: remoteXPC as RemoteXpcConnection,
|
|
25
|
+
diagnosticsService: new DiagnosticsService([
|
|
26
|
+
tunnelConnection.host,
|
|
27
|
+
parseInt(diagnosticsService.port, 10),
|
|
28
|
+
]),
|
|
29
|
+
};
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export async function startSyslogService(
|