appium-ios-remotexpc 0.21.2 → 0.22.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/src/lib/apple-tv/constants.d.ts +4 -3
  3. package/build/src/lib/apple-tv/constants.d.ts.map +1 -1
  4. package/build/src/lib/apple-tv/constants.js +10 -3
  5. package/build/src/lib/apple-tv/discovery/device-discovery.d.ts.map +1 -1
  6. package/build/src/lib/apple-tv/discovery/device-discovery.js +2 -2
  7. package/build/src/lib/apple-tv/encryption/index.d.ts +1 -0
  8. package/build/src/lib/apple-tv/encryption/index.d.ts.map +1 -1
  9. package/build/src/lib/apple-tv/encryption/index.js +1 -0
  10. package/build/src/lib/apple-tv/encryption/x25519.d.ts +8 -0
  11. package/build/src/lib/apple-tv/encryption/x25519.d.ts.map +1 -0
  12. package/build/src/lib/apple-tv/encryption/x25519.js +52 -0
  13. package/build/src/lib/apple-tv/index.d.ts +1 -0
  14. package/build/src/lib/apple-tv/index.d.ts.map +1 -1
  15. package/build/src/lib/apple-tv/index.js +1 -0
  16. package/build/src/lib/apple-tv/network/network-client.js +2 -2
  17. package/build/src/lib/apple-tv/pairing/user-input-service.d.ts.map +1 -1
  18. package/build/src/lib/apple-tv/pairing/user-input-service.js +2 -2
  19. package/build/src/lib/apple-tv/pairing-protocol/constants.d.ts +17 -0
  20. package/build/src/lib/apple-tv/pairing-protocol/constants.d.ts.map +1 -1
  21. package/build/src/lib/apple-tv/pairing-protocol/constants.js +25 -0
  22. package/build/src/lib/apple-tv/pairing-protocol/index.d.ts +2 -1
  23. package/build/src/lib/apple-tv/pairing-protocol/index.d.ts.map +1 -1
  24. package/build/src/lib/apple-tv/pairing-protocol/index.js +2 -1
  25. package/build/src/lib/apple-tv/pairing-protocol/pair-verification-protocol.d.ts +66 -0
  26. package/build/src/lib/apple-tv/pairing-protocol/pair-verification-protocol.d.ts.map +1 -0
  27. package/build/src/lib/apple-tv/pairing-protocol/pair-verification-protocol.js +178 -0
  28. package/build/src/lib/apple-tv/pairing-protocol/pairing-protocol.d.ts +35 -2
  29. package/build/src/lib/apple-tv/pairing-protocol/pairing-protocol.d.ts.map +1 -1
  30. package/build/src/lib/apple-tv/pairing-protocol/pairing-protocol.js +48 -14
  31. package/build/src/lib/apple-tv/storage/index.d.ts +1 -1
  32. package/build/src/lib/apple-tv/storage/index.d.ts.map +1 -1
  33. package/build/src/lib/apple-tv/storage/pairing-storage.d.ts +4 -1
  34. package/build/src/lib/apple-tv/storage/pairing-storage.d.ts.map +1 -1
  35. package/build/src/lib/apple-tv/storage/pairing-storage.js +59 -4
  36. package/build/src/lib/apple-tv/storage/types.d.ts +7 -1
  37. package/build/src/lib/apple-tv/storage/types.d.ts.map +1 -1
  38. package/build/src/lib/apple-tv/tunnel/index.d.ts +3 -0
  39. package/build/src/lib/apple-tv/tunnel/index.d.ts.map +1 -0
  40. package/build/src/lib/apple-tv/tunnel/index.js +1 -0
  41. package/build/src/lib/apple-tv/tunnel/tunnel-service.d.ts +57 -0
  42. package/build/src/lib/apple-tv/tunnel/tunnel-service.d.ts.map +1 -0
  43. package/build/src/lib/apple-tv/tunnel/tunnel-service.js +357 -0
  44. package/build/src/lib/apple-tv/tunnel/types.d.ts +22 -0
  45. package/build/src/lib/apple-tv/tunnel/types.d.ts.map +1 -0
  46. package/build/src/lib/apple-tv/tunnel/types.js +1 -0
  47. package/build/src/lib/bonjour/bonjour-discovery.d.ts.map +1 -1
  48. package/build/src/lib/bonjour/bonjour-discovery.js +3 -3
  49. package/build/src/lib/lockdown/index.d.ts.map +1 -1
  50. package/build/src/lib/plist/length-based-splitter.d.ts.map +1 -1
  51. package/build/src/lib/plist/length-based-splitter.js +0 -7
  52. package/build/src/lib/tunnel/index.d.ts +1 -0
  53. package/build/src/lib/tunnel/index.d.ts.map +1 -1
  54. package/build/src/lib/tunnel/packet-stream-server.d.ts.map +1 -1
  55. package/build/src/lib/tunnel/tunnel-registry-server.d.ts +1 -0
  56. package/build/src/lib/tunnel/tunnel-registry-server.d.ts.map +1 -1
  57. package/build/src/lib/tunnel/tunnel-registry-server.js +1 -1
  58. package/build/src/services/ios/afc/index.d.ts.map +1 -1
  59. package/build/src/services/ios/afc/index.js +1 -1
  60. package/build/src/services/ios/afc/stream-utils.d.ts.map +1 -1
  61. package/build/src/services/ios/afc/stream-utils.js +0 -2
  62. package/build/src/services/ios/mobile-config/index.js +2 -2
  63. package/package.json +2 -1
  64. package/scripts/pair-appletv.ts +2 -2
  65. package/scripts/start-appletv-tunnel.ts +178 -0
  66. package/scripts/test-tunnel-creation.ts +32 -23
  67. package/src/lib/apple-tv/constants.ts +11 -3
  68. package/src/lib/apple-tv/discovery/device-discovery.ts +2 -3
  69. package/src/lib/apple-tv/encryption/index.ts +6 -0
  70. package/src/lib/apple-tv/encryption/x25519.ts +79 -0
  71. package/src/lib/apple-tv/index.ts +1 -0
  72. package/src/lib/apple-tv/network/network-client.ts +2 -2
  73. package/src/lib/apple-tv/pairing/user-input-service.ts +2 -2
  74. package/src/lib/apple-tv/pairing-protocol/constants.ts +29 -0
  75. package/src/lib/apple-tv/pairing-protocol/index.ts +12 -1
  76. package/src/lib/apple-tv/pairing-protocol/pair-verification-protocol.ts +329 -0
  77. package/src/lib/apple-tv/pairing-protocol/pairing-protocol.ts +49 -19
  78. package/src/lib/apple-tv/storage/index.ts +1 -1
  79. package/src/lib/apple-tv/storage/pairing-storage.ts +73 -5
  80. package/src/lib/apple-tv/storage/types.ts +8 -1
  81. package/src/lib/apple-tv/tunnel/index.ts +2 -0
  82. package/src/lib/apple-tv/tunnel/tunnel-service.ts +543 -0
  83. package/src/lib/apple-tv/tunnel/types.ts +23 -0
  84. package/src/lib/bonjour/bonjour-discovery.ts +3 -5
  85. package/src/lib/lockdown/index.ts +0 -7
  86. package/src/lib/plist/length-based-splitter.ts +0 -22
  87. package/src/lib/tunnel/index.ts +2 -8
  88. package/src/lib/tunnel/packet-stream-server.ts +0 -8
  89. package/src/lib/tunnel/tunnel-registry-server.ts +1 -1
  90. package/src/services/ios/afc/index.ts +0 -2
  91. package/src/services/ios/afc/stream-utils.ts +0 -2
  92. package/src/services/ios/mobile-config/index.ts +2 -2
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env tsx
2
+ import * as tls from 'node:tls';
3
+
4
+ import { PacketStreamServer, TunnelManager } from '../src/index.js';
5
+ import type { TunnelRegistry } from '../src/index.js';
6
+ import { AppleTVTunnelService } from '../src/lib/apple-tv/tunnel/index.js';
7
+ import type { AppleTVDevice } from '../src/lib/bonjour/bonjour-discovery.js';
8
+ import { getLogger } from '../src/lib/logger.js';
9
+ import type { TunnelConnection } from '../src/lib/tunnel/index.js';
10
+ import {
11
+ DEFAULT_TUNNEL_REGISTRY_PORT,
12
+ startTunnelRegistryServer,
13
+ } from '../src/lib/tunnel/tunnel-registry-server.js';
14
+
15
+ const log = getLogger('WiFiTunnel');
16
+ const PACKET_STREAM_PORT = 50100;
17
+
18
+ async function main(): Promise<void> {
19
+ const args = process.argv.slice(2);
20
+ const specificDeviceIdentifier = args.find((arg) => !arg.startsWith('-'));
21
+
22
+ if (specificDeviceIdentifier) {
23
+ log.info(
24
+ `Starting Apple TV tunnel for specific device identifier: ${specificDeviceIdentifier}`,
25
+ );
26
+ } else {
27
+ log.info('Starting Apple TV tunnel (will try all discovered devices)');
28
+ }
29
+
30
+ const tunnelService = new AppleTVTunnelService();
31
+ let tunnel: TunnelConnection | null = null;
32
+ let tlsSocket: tls.TLSSocket | null = null;
33
+ let deviceInfo: AppleTVDevice | null = null;
34
+ let packetStreamServer: PacketStreamServer | null = null;
35
+
36
+ const cleanup = async (signal: string): Promise<void> => {
37
+ log.warn(`\nCleaning up (${signal})...`);
38
+
39
+ try {
40
+ // Close packet stream server first
41
+ if (packetStreamServer) {
42
+ log.info('Closing packet stream server...');
43
+ await packetStreamServer.stop();
44
+ packetStreamServer = null;
45
+ }
46
+
47
+ if (tunnel && typeof tunnel.closer === 'function') {
48
+ log.info('Closing tunnel...');
49
+ await tunnel.closer();
50
+ }
51
+
52
+ if (tlsSocket && !tlsSocket.destroyed) {
53
+ log.info('Closing TLS-PSK connection...');
54
+ tlsSocket.destroy();
55
+ }
56
+
57
+ tunnelService.disconnect();
58
+
59
+ log.info('Cleanup completed.');
60
+ } catch (err) {
61
+ log.error('Error during cleanup:', err);
62
+ }
63
+ };
64
+
65
+ process.on('SIGINT', async () => {
66
+ await cleanup('SIGINT (Ctrl+C)');
67
+ process.exit(0);
68
+ });
69
+ process.on('SIGTERM', async () => {
70
+ await cleanup('SIGTERM');
71
+ process.exit(0);
72
+ });
73
+ process.on('SIGHUP', async () => {
74
+ await cleanup('SIGHUP');
75
+ process.exit(0);
76
+ });
77
+
78
+ process.on('uncaughtException', async (error) => {
79
+ log.error('Uncaught Exception:', error);
80
+ await cleanup('Uncaught Exception');
81
+ });
82
+
83
+ process.on('unhandledRejection', async (reason, promise) => {
84
+ log.error('Unhandled Rejection at:', promise, 'reason:', reason);
85
+ await cleanup('Unhandled Rejection');
86
+ });
87
+
88
+ try {
89
+ log.info('Starting Apple TV tunnel...');
90
+ const result = await tunnelService.startTunnel(
91
+ undefined,
92
+ specificDeviceIdentifier,
93
+ );
94
+ tlsSocket = result.socket;
95
+ deviceInfo = result.device;
96
+
97
+ if (!tlsSocket) {
98
+ throw new Error('TLS-PSK socket not established');
99
+ }
100
+
101
+ log.info('Creating tunnel with TunnelManager...');
102
+ tunnel = await TunnelManager.getTunnel(tlsSocket);
103
+
104
+ // Start packet stream server (same as iPhone tunnel)
105
+ let packetStreamPort = 0;
106
+ try {
107
+ packetStreamServer = new PacketStreamServer(PACKET_STREAM_PORT);
108
+ await packetStreamServer.start();
109
+
110
+ // Attach packet consumer to tunnel to receive packet data
111
+ const consumer = packetStreamServer.getPacketConsumer();
112
+ if (consumer && tunnel.addPacketConsumer) {
113
+ tunnel.addPacketConsumer(consumer);
114
+ log.info(`Packet stream server started on port ${PACKET_STREAM_PORT}`);
115
+ packetStreamPort = PACKET_STREAM_PORT;
116
+ } else {
117
+ log.warn('Failed to attach packet consumer to tunnel');
118
+ }
119
+ } catch (err) {
120
+ log.warn(`Failed to start packet stream server: ${err}`);
121
+ }
122
+
123
+ const now = Date.now();
124
+ const nowISOString = new Date().toISOString();
125
+
126
+ const registry: TunnelRegistry = {
127
+ tunnels: {
128
+ [deviceInfo.identifier]: {
129
+ udid: deviceInfo.identifier,
130
+ deviceId: 0,
131
+ address: tunnel.Address,
132
+ rsdPort: tunnel.RsdPort ?? 0,
133
+ packetStreamPort,
134
+ connectionType: 'WiFi',
135
+ productId: 0,
136
+ createdAt: now,
137
+ lastUpdated: now,
138
+ },
139
+ },
140
+ metadata: {
141
+ lastUpdated: nowISOString,
142
+ totalTunnels: 1,
143
+ activeTunnels: 1,
144
+ },
145
+ };
146
+
147
+ await startTunnelRegistryServer(registry);
148
+
149
+ log.info('=== TUNNEL ESTABLISHED ===');
150
+ log.info(`Tunnel Address: ${tunnel.Address}`);
151
+ log.info(`Tunnel RSD Port: ${tunnel.RsdPort}`);
152
+ log.info(`Packet Stream Port: ${packetStreamPort}`);
153
+ log.info('==========================');
154
+
155
+ log.info('\n📁 Tunnel registry API:');
156
+ log.info(
157
+ ` http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels`,
158
+ );
159
+ log.info(' - GET /remotexpc/tunnels - List all tunnels');
160
+ log.info(
161
+ ` - GET /remotexpc/tunnels/${deviceInfo.identifier} - Get tunnel by identifier`,
162
+ );
163
+ log.info(' - GET /remotexpc/tunnels/metadata - Get registry metadata');
164
+
165
+ log.info('\n✅ Apple TV tunnel is ready!');
166
+ log.info(`Use: --rsd ${tunnel.Address} ${tunnel.RsdPort}`);
167
+ log.info('\nPress Ctrl+C to close the tunnel and exit.');
168
+
169
+ process.stdin.resume();
170
+ } catch (error) {
171
+ log.error('Tunnel failed:', error);
172
+ throw error;
173
+ } finally {
174
+ await cleanup('Shutdown');
175
+ }
176
+ }
177
+
178
+ main();
@@ -8,14 +8,16 @@ import type { ConnectionOptions } from 'tls';
8
8
 
9
9
  import {
10
10
  PacketStreamServer,
11
- SocketInfo,
12
11
  TunnelManager,
13
12
  createLockdownServiceByUDID,
14
13
  createUsbmux,
15
14
  startCoreDeviceProxy,
16
- TunnelRegistry,
17
15
  } from '../src/index.js';
18
- import { startTunnelRegistryServer } from '../src/lib/tunnel/tunnel-registry-server.js';
16
+ import type { SocketInfo, TunnelRegistry } from '../src/index.js';
17
+ import {
18
+ DEFAULT_TUNNEL_REGISTRY_PORT,
19
+ startTunnelRegistryServer,
20
+ } from '../src/lib/tunnel/tunnel-registry-server.js';
19
21
  import type { Device } from '../src/lib/usbmux/index.js';
20
22
 
21
23
  const log = logger.getLogger('TunnelCreation');
@@ -27,7 +29,7 @@ async function updateTunnelRegistry(
27
29
  ): Promise<TunnelRegistry> {
28
30
  const now = Date.now();
29
31
  const nowISOString = new Date().toISOString();
30
-
32
+
31
33
  // Initialize registry if it doesn't exist
32
34
  const registry: TunnelRegistry = {
33
35
  tunnels: {},
@@ -35,7 +37,7 @@ async function updateTunnelRegistry(
35
37
  lastUpdated: nowISOString,
36
38
  totalTunnels: 0,
37
39
  activeTunnels: 0,
38
- }
40
+ },
39
41
  };
40
42
 
41
43
  // Update tunnels
@@ -47,7 +49,7 @@ async function updateTunnelRegistry(
47
49
  deviceId: result.device.DeviceID,
48
50
  address: result.tunnel.Address,
49
51
  rsdPort: result.tunnel.RsdPort ?? 0,
50
- packetStreamPort: result.packetStreamPort,
52
+ packetStreamPort: result.packetStreamPort ?? 0,
51
53
  connectionType: result.device.Properties.ConnectionType,
52
54
  productId: result.device.Properties.ProductID,
53
55
  createdAt: registry.tunnels[udid]?.createdAt ?? now,
@@ -86,7 +88,7 @@ let PACKET_STREAM_BASE_PORT = 50000;
86
88
  */
87
89
  function setupCleanupHandlers(): void {
88
90
  const cleanup = async (signal: string) => {
89
- log.warn(`\nReceived ${signal}. Cleaning up...`);
91
+ log.warn(`\nCleaning up (${signal})...`);
90
92
 
91
93
  // Close all packet stream servers
92
94
  if (packetStreamServers.size > 0) {
@@ -106,26 +108,32 @@ function setupCleanupHandlers(): void {
106
108
  packetStreamServers.clear();
107
109
  }
108
110
 
109
- log.info('Cleanup completed. Exiting...');
110
- process.exit(0);
111
+ log.info('Cleanup completed.');
111
112
  };
112
113
 
113
114
  // Handle various termination signals
114
- process.on('SIGINT', () => cleanup('SIGINT (Ctrl+C)'));
115
- process.on('SIGTERM', () => cleanup('SIGTERM'));
116
- process.on('SIGHUP', () => cleanup('SIGHUP'));
115
+ process.on('SIGINT', async () => {
116
+ await cleanup('SIGINT (Ctrl+C)');
117
+ process.exit(0);
118
+ });
119
+ process.on('SIGTERM', async () => {
120
+ await cleanup('SIGTERM');
121
+ process.exit(0);
122
+ });
123
+ process.on('SIGHUP', async () => {
124
+ await cleanup('SIGHUP');
125
+ process.exit(0);
126
+ });
117
127
 
118
128
  // Handle uncaught exceptions and unhandled rejections
119
129
  process.on('uncaughtException', async (error) => {
120
130
  log.error('Uncaught Exception:', error);
121
131
  await cleanup('Uncaught Exception');
122
- process.exit(1);
123
132
  });
124
133
 
125
134
  process.on('unhandledRejection', async (reason, promise) => {
126
135
  log.error('Unhandled Rejection at:', promise, 'reason:', reason);
127
136
  await cleanup('Unhandled Rejection');
128
- process.exit(1);
129
137
  });
130
138
  }
131
139
 
@@ -225,7 +233,7 @@ async function createTunnelForDevice(
225
233
  },
226
234
  packetStreamPort,
227
235
  success: true,
228
- socket
236
+ socket,
229
237
  };
230
238
  } catch (err) {
231
239
  log.warn(`Could not add device to info server: ${err}`);
@@ -356,23 +364,24 @@ async function main(): Promise<void> {
356
364
  log.info(' - GET /remotexpc/tunnels/metadata - Get registry metadata');
357
365
 
358
366
  log.info('\n💡 Example usage:');
359
- log.info(' curl http://localhost:4723/remotexpc/tunnels');
360
- log.info(' curl http://localhost:4723/remotexpc/tunnels/metadata');
367
+ log.info(
368
+ ` curl http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels`,
369
+ );
370
+ log.info(
371
+ ` curl http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels/metadata`,
372
+ );
361
373
  if (successful.length > 0) {
362
374
  const firstUdid = successful[0].device.Properties.SerialNumber;
363
375
  log.info(
364
- ` curl http://localhost:4723/remotexpc/tunnels/${firstUdid}`,
376
+ ` curl http://localhost:${DEFAULT_TUNNEL_REGISTRY_PORT}/remotexpc/tunnels/${firstUdid}`,
365
377
  );
366
378
  }
367
379
  }
368
380
  } catch (error) {
369
381
  log.error(`Error during tunnel creation test: ${error}`);
370
- process.exit(1);
382
+ throw error;
371
383
  }
372
384
  }
373
385
 
374
386
  // Run the main function
375
- main().catch(async (error) => {
376
- log.error(`Fatal error: ${error}`);
377
- process.exit(1);
378
- });
387
+ main();
@@ -1,10 +1,18 @@
1
1
  // Default configuration values for Apple TV pairing behavior
2
+ // These can be overridden via environment variables:
3
+ // - APPLETV_TIMEOUT: Network operation timeout in milliseconds (default: 30000)
4
+ // - APPLETV_DISCOVERY_TIMEOUT: Device discovery timeout in milliseconds (default: 10000)
5
+ // 10 seconds provides reliable Apple TV discovery via mDNS/Bonjour across various network conditions
6
+ // - APPLETV_MAX_RETRIES: Maximum retry attempts (default: 3)
2
7
  export const DEFAULT_PAIRING_CONFIG = {
3
- timeout: 30000,
4
- discoveryTimeout: 5000,
5
- maxRetries: 3,
8
+ timeout: Number(process.env.APPLETV_TIMEOUT) || 30000,
9
+ discoveryTimeout: Number(process.env.APPLETV_DISCOVERY_TIMEOUT) || 10000,
10
+ maxRetries: Number(process.env.APPLETV_MAX_RETRIES) || 3,
6
11
  } as const;
7
12
 
13
+ // Prefix used for Apple TV pairing record storage identifiers
14
+ export const APPLETV_PAIRING_PREFIX = 'appletv_pairing_';
15
+
8
16
  // TLV8 component type identifiers used in pairing data exchange
9
17
  export const PairingDataComponentType = {
10
18
  METHOD: 0x00,
@@ -1,15 +1,14 @@
1
- import { logger } from '@appium/support';
2
-
3
1
  import {
4
2
  type AppleTVDevice,
5
3
  BonjourDiscovery,
6
4
  } from '../../bonjour/bonjour-discovery.js';
5
+ import { getLogger } from '../../logger.js';
7
6
  import { PairingError } from '../errors.js';
8
7
  import type { PairingConfig } from '../types.js';
9
8
 
10
9
  /** Discovers Apple TV devices on the local network using Bonjour */
11
10
  export class DeviceDiscoveryService {
12
- private readonly log = logger.getLogger('DeviceDiscoveryService');
11
+ private readonly log = getLogger('DeviceDiscoveryService');
13
12
 
14
13
  constructor(private readonly config: PairingConfig) {}
15
14
 
@@ -8,4 +8,10 @@ export {
8
8
 
9
9
  export { generateEd25519KeyPair, createEd25519Signature } from './ed25519.js';
10
10
 
11
+ export {
12
+ generateX25519KeyPair,
13
+ performX25519DiffieHellman,
14
+ type X25519KeyPair,
15
+ } from './x25519.js';
16
+
11
17
  export { hkdf, type HKDFParams } from './hkdf.js';
@@ -0,0 +1,79 @@
1
+ import {
2
+ type KeyObject,
3
+ createPublicKey,
4
+ diffieHellman,
5
+ generateKeyPairSync,
6
+ } from 'node:crypto';
7
+
8
+ import { getLogger } from '../../logger.js';
9
+ import { CryptographyError } from '../errors.js';
10
+
11
+ const log = getLogger('X25519');
12
+
13
+ const X25519_PUBLIC_KEY_LENGTH = 32;
14
+ const X25519_SPKI_PREFIX = Buffer.from([
15
+ 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00,
16
+ ]);
17
+
18
+ export interface X25519KeyPair {
19
+ publicKey: Buffer;
20
+ privateKey: KeyObject;
21
+ }
22
+
23
+ export function generateX25519KeyPair(): X25519KeyPair {
24
+ try {
25
+ const { publicKey, privateKey } = generateKeyPairSync('x25519');
26
+
27
+ const publicKeyDer = publicKey.export({
28
+ type: 'spki',
29
+ format: 'der',
30
+ }) as Buffer;
31
+ const publicKeyBytes = publicKeyDer.subarray(-X25519_PUBLIC_KEY_LENGTH);
32
+
33
+ return {
34
+ publicKey: publicKeyBytes,
35
+ privateKey,
36
+ };
37
+ } catch (error) {
38
+ log.error('Failed to generate X25519 key pair:', error);
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ throw new CryptographyError(
41
+ `Failed to generate X25519 key pair: ${message}`,
42
+ );
43
+ }
44
+ }
45
+
46
+ export function performX25519DiffieHellman(
47
+ privateKey: KeyObject,
48
+ devicePublicKey: Buffer,
49
+ ): Buffer {
50
+ if (!devicePublicKey || devicePublicKey.length !== X25519_PUBLIC_KEY_LENGTH) {
51
+ throw new CryptographyError(
52
+ `Device public key must be ${X25519_PUBLIC_KEY_LENGTH} bytes`,
53
+ );
54
+ }
55
+
56
+ try {
57
+ const devicePublicKeySpki = Buffer.concat([
58
+ X25519_SPKI_PREFIX,
59
+ devicePublicKey,
60
+ ]);
61
+
62
+ const publicKeyObject = createPublicKey({
63
+ key: devicePublicKeySpki,
64
+ format: 'der',
65
+ type: 'spki',
66
+ });
67
+
68
+ return diffieHellman({
69
+ privateKey,
70
+ publicKey: publicKeyObject,
71
+ });
72
+ } catch (error) {
73
+ log.error('Failed to perform X25519 Diffie-Hellman:', error);
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ throw new CryptographyError(
76
+ `Failed to perform X25519 Diffie-Hellman: ${message}`,
77
+ );
78
+ }
79
+ }
@@ -20,5 +20,6 @@ export * from './srp/index.js';
20
20
  export * from './network/index.js';
21
21
  export * from './pairing-protocol/index.js';
22
22
  export * from './storage/index.js';
23
+ export * from './tunnel/index.js';
23
24
  export * from './discovery/index.js';
24
25
  export * from './pairing/index.js';
@@ -1,12 +1,12 @@
1
- import { logger } from '@appium/support';
2
1
  import * as net from 'node:net';
3
2
 
3
+ import { getLogger } from '../../logger.js';
4
4
  import { NetworkError } from '../errors.js';
5
5
  import type { PairingConfig } from '../types.js';
6
6
  import { NETWORK_CONSTANTS } from './constants.js';
7
7
  import type { NetworkClientInterface } from './types.js';
8
8
 
9
- const log = logger.getLogger('NetworkClient');
9
+ const log = getLogger('NetworkClient');
10
10
 
11
11
  /** Handles TCP socket communication with Apple TV devices */
12
12
  export class NetworkClient implements NetworkClientInterface {
@@ -1,13 +1,13 @@
1
- import { logger } from '@appium/support';
2
1
  import { createInterface } from 'node:readline';
3
2
 
3
+ import { getLogger } from '../../logger.js';
4
4
  import { PairingError } from '../errors.js';
5
5
  import { NETWORK_CONSTANTS } from '../network/constants.js';
6
6
  import type { UserInputInterface } from '../pairing-protocol/types.js';
7
7
 
8
8
  /** Handles user interaction for PIN input during pairing */
9
9
  export class UserInputService implements UserInputInterface {
10
- private readonly log = logger.getLogger('UserInputService');
10
+ private readonly log = getLogger('UserInputService');
11
11
 
12
12
  async promptForPIN(): Promise<string> {
13
13
  const rl = createInterface({
@@ -15,5 +15,34 @@ export const PAIRING_MESSAGES = {
15
15
  M6_NONCE: 'PS-Msg06',
16
16
  } as const;
17
17
 
18
+ export const PAIR_VERIFY_MESSAGES = {
19
+ ENCRYPT_SALT: 'Pair-Verify-Encrypt-Salt',
20
+ ENCRYPT_INFO: 'Pair-Verify-Encrypt-Info',
21
+ STATE_03_NONCE: 'PV-Msg03',
22
+ } as const;
23
+
24
+ export const PAIR_VERIFY_STATES = {
25
+ STATE_01: 0x01,
26
+ STATE_02: 0x02,
27
+ STATE_03: 0x03,
28
+ STATE_04: 0x04,
29
+ } as const;
30
+
31
+ export const ENCRYPTION_MESSAGES = {
32
+ CLIENT_ENCRYPT: 'ClientEncrypt-main',
33
+ SERVER_ENCRYPT: 'ServerEncrypt-main',
34
+ } as const;
35
+
36
+ /** Error descriptions for pair verification STATE=4 errors */
37
+ export const PAIR_VERIFY_ERROR_DESCRIPTIONS: Record<number, string> = {
38
+ 1: 'Unknown error',
39
+ 2: 'Authentication failed - invalid pair record',
40
+ 3: 'Backoff - too many attempts',
41
+ 4: 'Max peers - device has too many connections',
42
+ 5: 'Max tries exceeded',
43
+ 6: 'Service unavailable',
44
+ 7: 'Device busy',
45
+ } as const;
46
+
18
47
  /** TLV type for device info */
19
48
  export const INFO_TYPE = 0x11;
@@ -1,5 +1,16 @@
1
1
  export { PairingProtocol } from './pairing-protocol.js';
2
- export { PAIRING_STATES, PAIRING_MESSAGES, INFO_TYPE } from './constants.js';
2
+ export {
3
+ PairVerificationProtocol,
4
+ type VerificationKeys,
5
+ } from './pair-verification-protocol.js';
6
+ export {
7
+ PAIRING_STATES,
8
+ PAIRING_MESSAGES,
9
+ INFO_TYPE,
10
+ PAIR_VERIFY_MESSAGES,
11
+ PAIR_VERIFY_STATES,
12
+ ENCRYPTION_MESSAGES,
13
+ } from './constants.js';
3
14
  export type {
4
15
  EncryptionKeys,
5
16
  PairingRequest,