appium-ios-remotexpc 0.11.0 → 0.13.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 (172) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +28 -0
  3. package/build/src/constants.d.ts +2 -0
  4. package/build/src/constants.d.ts.map +1 -0
  5. package/build/src/constants.js +3 -0
  6. package/build/src/index.d.ts +2 -1
  7. package/build/src/index.d.ts.map +1 -1
  8. package/build/src/index.js +2 -1
  9. package/build/src/lib/apple-tv/constants.d.ts +0 -1
  10. package/build/src/lib/apple-tv/constants.d.ts.map +1 -1
  11. package/build/src/lib/apple-tv/constants.js +0 -1
  12. package/build/src/lib/apple-tv/discovery/device-discovery.d.ts +10 -0
  13. package/build/src/lib/apple-tv/discovery/device-discovery.d.ts.map +1 -0
  14. package/build/src/lib/apple-tv/discovery/device-discovery.js +22 -0
  15. package/build/src/lib/apple-tv/discovery/index.d.ts +2 -0
  16. package/build/src/lib/apple-tv/discovery/index.d.ts.map +1 -0
  17. package/build/src/lib/apple-tv/discovery/index.js +1 -0
  18. package/build/src/lib/apple-tv/encryption/chacha20-poly1305.js +2 -2
  19. package/build/src/lib/apple-tv/encryption/ed25519.js +2 -2
  20. package/build/src/lib/apple-tv/encryption/hkdf.js +2 -2
  21. package/build/src/lib/apple-tv/index.d.ts +5 -0
  22. package/build/src/lib/apple-tv/index.d.ts.map +1 -1
  23. package/build/src/lib/apple-tv/index.js +5 -0
  24. package/build/src/lib/apple-tv/network/constants.d.ts +10 -0
  25. package/build/src/lib/apple-tv/network/constants.d.ts.map +1 -0
  26. package/build/src/lib/apple-tv/network/constants.js +9 -0
  27. package/build/src/lib/apple-tv/network/index.d.ts +4 -0
  28. package/build/src/lib/apple-tv/network/index.d.ts.map +1 -0
  29. package/build/src/lib/apple-tv/network/index.js +2 -0
  30. package/build/src/lib/apple-tv/network/network-client.d.ts +16 -0
  31. package/build/src/lib/apple-tv/network/network-client.d.ts.map +1 -0
  32. package/build/src/lib/apple-tv/network/network-client.js +169 -0
  33. package/build/src/lib/apple-tv/network/types.d.ts +8 -0
  34. package/build/src/lib/apple-tv/network/types.d.ts.map +1 -0
  35. package/build/src/lib/apple-tv/network/types.js +1 -0
  36. package/build/src/lib/apple-tv/pairing/index.d.ts +3 -0
  37. package/build/src/lib/apple-tv/pairing/index.d.ts.map +1 -0
  38. package/build/src/lib/apple-tv/pairing/index.js +2 -0
  39. package/build/src/lib/apple-tv/pairing/pairing-service.d.ts +15 -0
  40. package/build/src/lib/apple-tv/pairing/pairing-service.d.ts.map +1 -0
  41. package/build/src/lib/apple-tv/pairing/pairing-service.js +112 -0
  42. package/build/src/lib/apple-tv/pairing/user-input-service.d.ts +8 -0
  43. package/build/src/lib/apple-tv/pairing/user-input-service.d.ts.map +1 -0
  44. package/build/src/lib/apple-tv/pairing/user-input-service.js +61 -0
  45. package/build/src/lib/apple-tv/pairing-protocol/constants.d.ts +18 -0
  46. package/build/src/lib/apple-tv/pairing-protocol/constants.d.ts.map +1 -0
  47. package/build/src/lib/apple-tv/pairing-protocol/constants.js +17 -0
  48. package/build/src/lib/apple-tv/pairing-protocol/index.d.ts +4 -0
  49. package/build/src/lib/apple-tv/pairing-protocol/index.d.ts.map +1 -0
  50. package/build/src/lib/apple-tv/pairing-protocol/index.js +2 -0
  51. package/build/src/lib/apple-tv/pairing-protocol/pairing-protocol.d.ts +159 -0
  52. package/build/src/lib/apple-tv/pairing-protocol/pairing-protocol.d.ts.map +1 -0
  53. package/build/src/lib/apple-tv/pairing-protocol/pairing-protocol.js +494 -0
  54. package/build/src/lib/apple-tv/pairing-protocol/types.d.ts +57 -0
  55. package/build/src/lib/apple-tv/pairing-protocol/types.d.ts.map +1 -0
  56. package/build/src/lib/apple-tv/pairing-protocol/types.js +1 -0
  57. package/build/src/lib/apple-tv/srp/srp-client.js +2 -2
  58. package/build/src/lib/apple-tv/storage/index.d.ts +3 -0
  59. package/build/src/lib/apple-tv/storage/index.d.ts.map +1 -0
  60. package/build/src/lib/apple-tv/storage/index.js +1 -0
  61. package/build/src/lib/apple-tv/storage/pairing-storage.d.ts +12 -0
  62. package/build/src/lib/apple-tv/storage/pairing-storage.d.ts.map +1 -0
  63. package/build/src/lib/apple-tv/storage/pairing-storage.js +36 -0
  64. package/build/src/lib/apple-tv/storage/types.d.ts +5 -0
  65. package/build/src/lib/apple-tv/storage/types.d.ts.map +1 -0
  66. package/build/src/lib/apple-tv/storage/types.js +1 -0
  67. package/build/src/lib/apple-tv/types.d.ts +0 -1
  68. package/build/src/lib/apple-tv/types.d.ts.map +1 -1
  69. package/build/src/lib/bonjour/bonjour-discovery.d.ts.map +1 -1
  70. package/build/src/lib/bonjour/bonjour-discovery.js +4 -2
  71. package/build/src/lib/lockdown/index.d.ts.map +1 -1
  72. package/build/src/lib/lockdown/index.js +4 -4
  73. package/build/src/lib/logger.d.ts +2 -0
  74. package/build/src/lib/logger.d.ts.map +1 -0
  75. package/build/src/lib/logger.js +7 -0
  76. package/build/src/lib/pair-record/pair-record.d.ts.map +1 -1
  77. package/build/src/lib/pair-record/pair-record.js +2 -2
  78. package/build/src/lib/plist/binary-plist-parser.d.ts.map +1 -1
  79. package/build/src/lib/plist/binary-plist-parser.js +2 -2
  80. package/build/src/lib/plist/length-based-splitter.d.ts.map +1 -1
  81. package/build/src/lib/plist/length-based-splitter.js +2 -2
  82. package/build/src/lib/plist/plist-decoder.d.ts.map +1 -1
  83. package/build/src/lib/plist/plist-decoder.js +2 -2
  84. package/build/src/lib/plist/plist-parser.js +2 -2
  85. package/build/src/lib/plist/plist-service.d.ts.map +1 -1
  86. package/build/src/lib/plist/plist-service.js +3 -3
  87. package/build/src/lib/remote-xpc/remote-xpc-connection.js +2 -2
  88. package/build/src/lib/tss/index.js +2 -2
  89. package/build/src/lib/tunnel/index.d.ts.map +1 -1
  90. package/build/src/lib/tunnel/index.js +2 -2
  91. package/build/src/lib/tunnel/packet-stream-client.d.ts.map +1 -1
  92. package/build/src/lib/tunnel/packet-stream-client.js +2 -2
  93. package/build/src/lib/tunnel/packet-stream-server.d.ts.map +1 -1
  94. package/build/src/lib/tunnel/packet-stream-server.js +2 -2
  95. package/build/src/lib/tunnel/tunnel-api-client.d.ts.map +1 -1
  96. package/build/src/lib/tunnel/tunnel-api-client.js +2 -2
  97. package/build/src/lib/tunnel/tunnel-registry-server.js +2 -2
  98. package/build/src/lib/usbmux/index.d.ts.map +1 -1
  99. package/build/src/lib/usbmux/index.js +2 -2
  100. package/build/src/services/ios/afc/index.d.ts.map +1 -1
  101. package/build/src/services/ios/afc/index.js +2 -2
  102. package/build/src/services/ios/base-service.d.ts.map +1 -1
  103. package/build/src/services/ios/base-service.js +2 -2
  104. package/build/src/services/ios/diagnostic-service/index.d.ts.map +1 -1
  105. package/build/src/services/ios/diagnostic-service/index.js +2 -2
  106. package/build/src/services/ios/mobile-config/index.d.ts.map +1 -1
  107. package/build/src/services/ios/mobile-config/index.js +3 -2
  108. package/build/src/services/ios/mobile-image-mounter/index.js +2 -2
  109. package/build/src/services/ios/notification-proxy/index.d.ts.map +1 -1
  110. package/build/src/services/ios/notification-proxy/index.js +2 -2
  111. package/build/src/services/ios/power-assertion/index.d.ts.map +1 -1
  112. package/build/src/services/ios/power-assertion/index.js +2 -2
  113. package/build/src/services/ios/syslog-service/index.d.ts.map +1 -1
  114. package/build/src/services/ios/syslog-service/index.js +3 -3
  115. package/build/src/services/ios/tunnel-service/index.d.ts.map +1 -1
  116. package/build/src/services/ios/tunnel-service/index.js +2 -2
  117. package/build/src/services/ios/webinspector/index.js +2 -2
  118. package/package.json +4 -2
  119. package/scripts/pair-appletv.ts +79 -0
  120. package/scripts/test-tunnel-creation.ts +1 -1
  121. package/src/constants.ts +4 -0
  122. package/src/index.ts +2 -0
  123. package/src/lib/apple-tv/constants.ts +0 -1
  124. package/src/lib/apple-tv/discovery/device-discovery.ts +34 -0
  125. package/src/lib/apple-tv/discovery/index.ts +1 -0
  126. package/src/lib/apple-tv/encryption/chacha20-poly1305.ts +2 -2
  127. package/src/lib/apple-tv/encryption/ed25519.ts +2 -2
  128. package/src/lib/apple-tv/encryption/hkdf.ts +2 -2
  129. package/src/lib/apple-tv/index.ts +5 -0
  130. package/src/lib/apple-tv/network/constants.ts +9 -0
  131. package/src/lib/apple-tv/network/index.ts +3 -0
  132. package/src/lib/apple-tv/network/network-client.ts +214 -0
  133. package/src/lib/apple-tv/network/types.ts +7 -0
  134. package/src/lib/apple-tv/pairing/index.ts +2 -0
  135. package/src/lib/apple-tv/pairing/pairing-service.ts +175 -0
  136. package/src/lib/apple-tv/pairing/user-input-service.ts +71 -0
  137. package/src/lib/apple-tv/pairing-protocol/constants.ts +19 -0
  138. package/src/lib/apple-tv/pairing-protocol/index.ts +8 -0
  139. package/src/lib/apple-tv/pairing-protocol/pairing-protocol.ts +636 -0
  140. package/src/lib/apple-tv/pairing-protocol/types.ts +60 -0
  141. package/src/lib/apple-tv/srp/srp-client.ts +2 -2
  142. package/src/lib/apple-tv/storage/index.ts +2 -0
  143. package/src/lib/apple-tv/storage/pairing-storage.ts +60 -0
  144. package/src/lib/apple-tv/storage/types.ts +9 -0
  145. package/src/lib/apple-tv/types.ts +0 -1
  146. package/src/lib/bonjour/bonjour-discovery.ts +4 -2
  147. package/src/lib/lockdown/index.ts +4 -4
  148. package/src/lib/logger.ts +9 -0
  149. package/src/lib/pair-record/pair-record.ts +3 -2
  150. package/src/lib/plist/binary-plist-parser.ts +2 -3
  151. package/src/lib/plist/length-based-splitter.ts +2 -2
  152. package/src/lib/plist/plist-decoder.ts +2 -2
  153. package/src/lib/plist/plist-parser.ts +2 -2
  154. package/src/lib/plist/plist-service.ts +3 -3
  155. package/src/lib/remote-xpc/remote-xpc-connection.ts +2 -2
  156. package/src/lib/tss/index.ts +2 -2
  157. package/src/lib/tunnel/index.ts +2 -2
  158. package/src/lib/tunnel/packet-stream-client.ts +3 -2
  159. package/src/lib/tunnel/packet-stream-server.ts +3 -2
  160. package/src/lib/tunnel/tunnel-api-client.ts +2 -3
  161. package/src/lib/tunnel/tunnel-registry-server.ts +2 -2
  162. package/src/lib/usbmux/index.ts +2 -2
  163. package/src/services/ios/afc/index.ts +2 -2
  164. package/src/services/ios/base-service.ts +2 -3
  165. package/src/services/ios/diagnostic-service/index.ts +2 -3
  166. package/src/services/ios/mobile-config/index.ts +3 -2
  167. package/src/services/ios/mobile-image-mounter/index.ts +2 -2
  168. package/src/services/ios/notification-proxy/index.ts +2 -3
  169. package/src/services/ios/power-assertion/index.ts +2 -3
  170. package/src/services/ios/syslog-service/index.ts +3 -3
  171. package/src/services/ios/tunnel-service/index.ts +2 -2
  172. package/src/services/ios/webinspector/index.ts +2 -2
@@ -0,0 +1,494 @@
1
+ import { logger } from '@appium/support';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { hostname } from 'node:os';
4
+ import { DEFAULT_PAIRING_CONFIG, PairingDataComponentType, } from '../constants.js';
5
+ import { encodeAppleTVDeviceInfo } from '../deviceInfo/index.js';
6
+ import { createEd25519Signature, decryptChaCha20Poly1305, encryptChaCha20Poly1305, generateEd25519KeyPair, hkdf, } from '../encryption/index.js';
7
+ import { PairingError } from '../errors.js';
8
+ import { NETWORK_CONSTANTS } from '../network/constants.js';
9
+ import { SRPClient } from '../srp/index.js';
10
+ import { PairingStorage } from '../storage/pairing-storage.js';
11
+ import { createPairVerificationData, createSetupManualPairingData, decodeTLV8ToDict, encodeTLV8, } from '../tlv/index.js';
12
+ import { generateHostId } from '../utils/uuid-generator.js';
13
+ import { INFO_TYPE, PAIRING_MESSAGES, PAIRING_STATES } from './constants.js';
14
+ /** Implements the Apple TV pairing protocol including SRP authentication and key exchange */
15
+ export class PairingProtocol {
16
+ networkClient;
17
+ userInput;
18
+ static log = logger.getLogger('PairingProtocol');
19
+ _sequenceNumber = 0;
20
+ constructor(networkClient, userInput) {
21
+ this.networkClient = networkClient;
22
+ this.userInput = userInput;
23
+ }
24
+ async executePairingFlow(device) {
25
+ this._sequenceNumber = 1;
26
+ try {
27
+ // Step 1: Handshake
28
+ await this.performHandshake();
29
+ // Step 2: Pair verification attempt
30
+ await this.attemptPairVerification();
31
+ // Step 3: Setup manual pairing
32
+ const setupResponse = await this.setupManualPairing();
33
+ const srpData = this.extractAndValidatePairingData(setupResponse);
34
+ // Step 4: SRP Authentication
35
+ const srpClient = await this.performSRPAuthentication(srpData);
36
+ // Step 5: Generate keys and send M5
37
+ const encryptionKeys = this.deriveEncryptionKeys(srpClient.sessionKey);
38
+ const { publicKey: ltpk, privateKey: ltsk } = generateEd25519KeyPair();
39
+ const devicePairingID = generateHostId(hostname());
40
+ await this.sendM5Message(encryptionKeys.encryptKey, devicePairingID, ltpk, ltsk, srpClient.sessionKey);
41
+ // Step 6: Receive M6 completion
42
+ await this.receiveM6Completion(encryptionKeys.decryptKey);
43
+ return this.createPairingResult(device, ltpk, ltsk);
44
+ }
45
+ catch (error) {
46
+ PairingProtocol.log.error('Pairing flow failed:', error);
47
+ throw error;
48
+ }
49
+ }
50
+ createRequest(content, sequenceNumber) {
51
+ return {
52
+ message: {
53
+ plain: {
54
+ _0: content,
55
+ },
56
+ },
57
+ originatedBy: 'host',
58
+ sequenceNumber: sequenceNumber ?? this._sequenceNumber++,
59
+ };
60
+ }
61
+ /**
62
+ * Fragments a buffer into TLV8 items of maximum fragment size
63
+ * @param buffer Buffer to fragment
64
+ * @param type TLV8 type identifier
65
+ * @returns Array of TLV8 items
66
+ */
67
+ fragmentBuffer(buffer, type) {
68
+ const fragments = [];
69
+ for (let i = 0; i < buffer.length; i += NETWORK_CONSTANTS.MAX_TLV_FRAGMENT_SIZE) {
70
+ fragments.push({
71
+ type,
72
+ data: buffer.subarray(i, i + NETWORK_CONSTANTS.MAX_TLV_FRAGMENT_SIZE),
73
+ });
74
+ }
75
+ return fragments;
76
+ }
77
+ /**
78
+ * Creates a nonce buffer with prefix padding
79
+ * @param nonceString The nonce string identifier
80
+ * @returns Padded nonce buffer
81
+ */
82
+ createNonce(nonceString) {
83
+ return Buffer.concat([Buffer.alloc(4), Buffer.from(nonceString)]);
84
+ }
85
+ /**
86
+ * Performs initial handshake with Apple TV to establish connection
87
+ * Sends handshake request with host options and wire protocol version
88
+ */
89
+ async performHandshake() {
90
+ const request = this.createHandshakeRequest();
91
+ await this.networkClient.sendPacket(request);
92
+ await this.networkClient.receiveResponse();
93
+ PairingProtocol.log.debug('Handshake completed');
94
+ }
95
+ /**
96
+ * Attempts to verify existing pairing credentials with Apple TV
97
+ * Creates pair verification request and handles expected failure for new pairing flow
98
+ */
99
+ async attemptPairVerification() {
100
+ const request = this.createPairVerificationRequest();
101
+ await this.networkClient.sendPacket(request);
102
+ await this.networkClient.receiveResponse();
103
+ const failedRequest = this.createPairVerifyFailedRequest();
104
+ await this.networkClient.sendPacket(failedRequest);
105
+ PairingProtocol.log.debug('Pair verification attempt completed');
106
+ }
107
+ /**
108
+ * Initiates manual pairing setup process with Apple TV
109
+ * Sends setup request and receives SRP challenge data
110
+ * @returns Response containing SRP salt and server public key
111
+ */
112
+ async setupManualPairing() {
113
+ const request = this.createSetupManualPairingRequest();
114
+ await this.networkClient.sendPacket(request);
115
+ const response = await this.networkClient.receiveResponse();
116
+ PairingProtocol.log.debug('Manual pairing setup completed');
117
+ return response;
118
+ }
119
+ /**
120
+ * Extracts and validates SRP pairing data from Apple TV response
121
+ * @param response Network response containing pairing data
122
+ * @returns Parsed TLV8 dictionary with SRP challenge components
123
+ * @throws PairingError if pairing data is missing or invalid
124
+ */
125
+ extractAndValidatePairingData(response) {
126
+ const srpData = response.message?.plain?._0?.event?._0?.pairingData?._0?.data;
127
+ if (!srpData) {
128
+ throw new PairingError('No pairing data received', 'NO_PAIRING_DATA');
129
+ }
130
+ const parsedSRP = this.parseTLV8Response(srpData);
131
+ this.validateSRPResponse(parsedSRP);
132
+ return parsedSRP;
133
+ }
134
+ /**
135
+ * Performs SRP authentication using user-provided PIN
136
+ * Prompts for PIN, creates SRP client, sends proof, and validates response
137
+ * @param parsedSRP SRP challenge data from Apple TV
138
+ * @returns Authenticated SRP client with session key
139
+ * @throws PairingError if PIN is incorrect or authentication fails
140
+ */
141
+ async performSRPAuthentication(parsedSRP) {
142
+ const pin = await this.userInput.promptForPIN();
143
+ const srpClient = this.createSRPClient(pin, parsedSRP);
144
+ await this.sendSRPProof(srpClient);
145
+ const response = await this.networkClient.receiveResponse();
146
+ this.validateSRPProofResponse(response);
147
+ PairingProtocol.log.debug('SRP authentication completed');
148
+ return srpClient;
149
+ }
150
+ /**
151
+ * Receives and processes M6 pairing completion message from Apple TV
152
+ * Attempts to decrypt and validate final pairing state
153
+ * @param decryptKey Decryption key for M6 encrypted data
154
+ */
155
+ async receiveM6Completion(decryptKey) {
156
+ const m6Response = await this.networkClient.receiveResponse();
157
+ PairingProtocol.log.info('M6 Response received');
158
+ try {
159
+ this.processM6Response(m6Response, decryptKey);
160
+ }
161
+ catch (error) {
162
+ PairingProtocol.log.warn('M6 decryption failed - but pairing may still be successful:', error.message);
163
+ }
164
+ }
165
+ /**
166
+ * Constructs initial handshake request packet
167
+ * Includes host options and wire protocol version for pairing session
168
+ * @returns Handshake request with sequence number 0
169
+ */
170
+ createHandshakeRequest() {
171
+ return {
172
+ message: {
173
+ plain: {
174
+ _0: {
175
+ request: {
176
+ _0: {
177
+ handshake: {
178
+ _0: {
179
+ hostOptions: { attemptPairVerify: true },
180
+ wireProtocolVersion: 19,
181
+ },
182
+ },
183
+ },
184
+ },
185
+ },
186
+ },
187
+ },
188
+ originatedBy: 'host',
189
+ sequenceNumber: 0,
190
+ };
191
+ }
192
+ /**
193
+ * Creates pair verification request with X25519 public key
194
+ * Used to attempt verification of existing pairing credentials
195
+ * @returns Pair verification request with random X25519 key
196
+ */
197
+ createPairVerificationRequest() {
198
+ const x25519PublicKey = randomBytes(32);
199
+ const pairingData = createPairVerificationData(x25519PublicKey);
200
+ return this.createRequest({
201
+ event: {
202
+ _0: {
203
+ pairingData: {
204
+ _0: {
205
+ data: pairingData,
206
+ kind: 'verifyManualPairing',
207
+ startNewSession: true,
208
+ },
209
+ },
210
+ },
211
+ },
212
+ });
213
+ }
214
+ /**
215
+ * Creates pair verification failed event request
216
+ * Sent after verification attempt to proceed with manual pairing setup
217
+ * @returns Pair verify failed event packet
218
+ */
219
+ createPairVerifyFailedRequest() {
220
+ return this.createRequest({
221
+ event: {
222
+ _0: {
223
+ pairVerifyFailed: {},
224
+ },
225
+ },
226
+ });
227
+ }
228
+ /**
229
+ * Constructs manual pairing setup request packet
230
+ * Initiates M1/M2 exchange with setup pairing data
231
+ * @returns Setup manual pairing request with host information
232
+ */
233
+ createSetupManualPairingRequest() {
234
+ const setupData = createSetupManualPairingData();
235
+ return this.createRequest({
236
+ event: {
237
+ _0: {
238
+ pairingData: {
239
+ _0: {
240
+ data: setupData,
241
+ kind: 'setupManualPairing',
242
+ sendingHost: hostname(),
243
+ startNewSession: true,
244
+ },
245
+ },
246
+ },
247
+ },
248
+ });
249
+ }
250
+ /**
251
+ * Parses base64-encoded TLV8 response into dictionary
252
+ * Decodes base64 string and converts TLV8 format to key-value pairs
253
+ * @param data Base64-encoded TLV8 data
254
+ * @returns Dictionary mapping TLV8 type numbers to buffer values
255
+ * @throws PairingError if TLV8 parsing fails
256
+ */
257
+ parseTLV8Response(data) {
258
+ try {
259
+ const buffer = Buffer.from(data, 'base64');
260
+ const decoded = decodeTLV8ToDict(buffer);
261
+ const result = {};
262
+ for (const [key, value] of Object.entries(decoded)) {
263
+ if (value !== undefined) {
264
+ result[Number(key)] = value;
265
+ }
266
+ }
267
+ return result;
268
+ }
269
+ catch (error) {
270
+ throw new PairingError('Failed to parse TLV8 response', 'TLV8_PARSE_ERROR', error);
271
+ }
272
+ }
273
+ /**
274
+ * Validates SRP response for errors and required challenge data
275
+ * Checks for error codes and verifies presence of salt and public key
276
+ * @param parsedSRP Parsed SRP response dictionary
277
+ * @throws PairingError if response contains errors or missing required data
278
+ */
279
+ validateSRPResponse(parsedSRP) {
280
+ const errorBuffer = parsedSRP[PairingDataComponentType.ERROR];
281
+ if (errorBuffer) {
282
+ if (errorBuffer.length === 0) {
283
+ throw new PairingError('Apple TV returned empty error buffer', 'INVALID_ERROR_RESPONSE');
284
+ }
285
+ const errorCode = errorBuffer[0];
286
+ throw new PairingError(`Apple TV rejected request with error ${errorCode}`, 'APPLE_TV_ERROR', { errorCode });
287
+ }
288
+ if (!parsedSRP[PairingDataComponentType.SALT] ||
289
+ !parsedSRP[PairingDataComponentType.PUBLIC_KEY]) {
290
+ throw new PairingError('Missing SRP challenge data', 'MISSING_SRP_DATA');
291
+ }
292
+ }
293
+ /**
294
+ * Initializes SRP client with PIN and server challenge data
295
+ * Sets up identity, salt, and server public key for proof computation
296
+ * @param pin User-provided pairing PIN
297
+ * @param parsedSRP SRP challenge data containing salt and server public key
298
+ * @returns Configured SRP client ready for proof generation
299
+ * @throws PairingError if required SRP data is missing
300
+ */
301
+ createSRPClient(pin, parsedSRP) {
302
+ try {
303
+ const salt = parsedSRP[PairingDataComponentType.SALT];
304
+ const serverPublicKey = parsedSRP[PairingDataComponentType.PUBLIC_KEY];
305
+ const srpClient = new SRPClient();
306
+ srpClient.setIdentity('Pair-Setup', pin);
307
+ srpClient.salt = salt;
308
+ srpClient.serverPublicKey = serverPublicKey;
309
+ return srpClient;
310
+ }
311
+ catch (error) {
312
+ throw new PairingError('Failed to create SRP client', 'SRP_CLIENT_ERROR', error);
313
+ }
314
+ }
315
+ /**
316
+ * Sends M3 message containing SRP proof to Apple TV
317
+ * Includes client public key and computed proof for authentication
318
+ * @param srpClient Configured SRP client with computed proof
319
+ */
320
+ async sendSRPProof(srpClient) {
321
+ const clientPublicKey = srpClient.publicKey;
322
+ const clientProof = srpClient.computeProof();
323
+ const tlvItems = [
324
+ {
325
+ type: PairingDataComponentType.STATE,
326
+ data: Buffer.from([PAIRING_STATES.M3]),
327
+ },
328
+ ...this.fragmentBuffer(clientPublicKey, PairingDataComponentType.PUBLIC_KEY),
329
+ { type: PairingDataComponentType.PROOF, data: clientProof },
330
+ ];
331
+ const tlv = encodeTLV8(tlvItems);
332
+ const request = this.createRequest({
333
+ event: {
334
+ _0: {
335
+ pairingData: {
336
+ _0: {
337
+ data: tlv.toString('base64'),
338
+ kind: 'setupManualPairing',
339
+ sendingHost: hostname(),
340
+ startNewSession: false,
341
+ },
342
+ },
343
+ },
344
+ },
345
+ });
346
+ await this.networkClient.sendPacket(request);
347
+ }
348
+ /**
349
+ * Validates M4 SRP proof response from Apple TV
350
+ * Checks for authentication errors indicating incorrect PIN
351
+ * @param response Network response containing SRP proof validation result
352
+ * @throws PairingError if PIN authentication failed
353
+ */
354
+ validateSRPProofResponse(response) {
355
+ if (response.message?.plain?._0?.event?._0?.pairingData?._0?.data) {
356
+ const proofData = Buffer.from(response.message.plain._0.event._0.pairingData._0.data, 'base64');
357
+ const parsedProof = decodeTLV8ToDict(proofData);
358
+ if (parsedProof[PairingDataComponentType.ERROR]) {
359
+ throw new PairingError('SRP authentication failed - wrong PIN', 'WRONG_PIN');
360
+ }
361
+ }
362
+ }
363
+ /**
364
+ * Sends M5 exchange message with encrypted pairing credentials
365
+ * Includes long-term public key, signature, and device information encrypted with session key
366
+ * @param encryptKey Encryption key derived from session key
367
+ * @param devicePairingID Host device pairing identifier
368
+ * @param ltpk Long-term public key (Ed25519)
369
+ * @param ltsk Long-term secret key (Ed25519)
370
+ * @param sessionKey SRP session key for signature derivation
371
+ * @throws PairingError if M5 message creation fails
372
+ */
373
+ async sendM5Message(encryptKey, devicePairingID, ltpk, ltsk, sessionKey) {
374
+ try {
375
+ const signingKey = hkdf({
376
+ ikm: sessionKey,
377
+ salt: Buffer.from(PAIRING_MESSAGES.SIGN_SALT, 'utf8'),
378
+ info: Buffer.from(PAIRING_MESSAGES.SIGN_INFO, 'utf8'),
379
+ length: 32,
380
+ });
381
+ const devicePairingIDBuffer = Buffer.from(devicePairingID, 'utf8');
382
+ const dataToSign = Buffer.concat([
383
+ signingKey,
384
+ devicePairingIDBuffer,
385
+ ltpk,
386
+ ]);
387
+ const signature = createEd25519Signature(dataToSign, ltsk);
388
+ const deviceInfo = encodeAppleTVDeviceInfo(devicePairingID);
389
+ const tlvItems = [
390
+ {
391
+ type: PairingDataComponentType.IDENTIFIER,
392
+ data: devicePairingIDBuffer,
393
+ },
394
+ { type: PairingDataComponentType.PUBLIC_KEY, data: ltpk },
395
+ { type: PairingDataComponentType.SIGNATURE, data: signature },
396
+ { type: INFO_TYPE, data: deviceInfo },
397
+ ];
398
+ const tlvData = encodeTLV8(tlvItems);
399
+ const nonce = this.createNonce(PAIRING_MESSAGES.M5_NONCE);
400
+ const encrypted = encryptChaCha20Poly1305({
401
+ plaintext: tlvData,
402
+ key: encryptKey,
403
+ nonce,
404
+ });
405
+ const encryptedTLVItems = [
406
+ ...this.fragmentBuffer(encrypted, PairingDataComponentType.ENCRYPTED_DATA),
407
+ {
408
+ type: PairingDataComponentType.STATE,
409
+ data: Buffer.from([PAIRING_STATES.M5]),
410
+ },
411
+ ];
412
+ const encryptedTLV = encodeTLV8(encryptedTLVItems);
413
+ const request = this.createRequest({
414
+ event: {
415
+ _0: {
416
+ pairingData: {
417
+ _0: {
418
+ data: encryptedTLV.toString('base64'),
419
+ kind: 'setupManualPairing',
420
+ sendingHost: hostname(),
421
+ startNewSession: false,
422
+ },
423
+ },
424
+ },
425
+ },
426
+ });
427
+ await this.networkClient.sendPacket(request);
428
+ }
429
+ catch (error) {
430
+ throw new PairingError('Failed to create M5 message', 'M5_ERROR', error);
431
+ }
432
+ }
433
+ /**
434
+ * Processes and decrypts M6 completion response from Apple TV
435
+ * Validates pairing completion state and decrypts final exchange data
436
+ * @param m6Response Network response containing M6 completion message
437
+ * @param decryptKey Decryption key for M6 encrypted data
438
+ */
439
+ processM6Response(m6Response, decryptKey) {
440
+ if (!m6Response.message?.plain?._0?.event?._0?.pairingData?._0?.data) {
441
+ return;
442
+ }
443
+ const m6DataBase64 = m6Response.message.plain._0.event._0.pairingData._0.data;
444
+ const m6TLVBuffer = Buffer.from(m6DataBase64, 'base64');
445
+ const m6Parsed = decodeTLV8ToDict(m6TLVBuffer);
446
+ PairingProtocol.log.debug('M6 TLV types received:', Object.keys(m6Parsed).map((k) => `0x${Number(k).toString(16)}`));
447
+ const stateData = m6Parsed[PairingDataComponentType.STATE];
448
+ if (stateData && stateData[0] === PAIRING_STATES.M6) {
449
+ PairingProtocol.log.info('✅ Pairing completed successfully (STATE=0x06)');
450
+ }
451
+ const encryptedData = m6Parsed[PairingDataComponentType.ENCRYPTED_DATA];
452
+ if (encryptedData) {
453
+ const nonce = this.createNonce(PAIRING_MESSAGES.M6_NONCE);
454
+ const decrypted = decryptChaCha20Poly1305({
455
+ ciphertext: encryptedData,
456
+ key: decryptKey,
457
+ nonce,
458
+ });
459
+ const decryptedTLV = decodeTLV8ToDict(decrypted);
460
+ PairingProtocol.log.debug('M6 decrypted content types:', Object.keys(decryptedTLV));
461
+ }
462
+ }
463
+ /**
464
+ * Derives encryption and decryption keys from SRP session key
465
+ * Uses HKDF with pairing-specific salt and info strings
466
+ * @param sessionKey SRP session key from authentication
467
+ * @returns Encryption keys for M5/M6 message exchange
468
+ */
469
+ deriveEncryptionKeys(sessionKey) {
470
+ const sharedKey = hkdf({
471
+ ikm: sessionKey,
472
+ salt: Buffer.from(PAIRING_MESSAGES.ENCRYPT_SALT, 'utf8'),
473
+ info: Buffer.from(PAIRING_MESSAGES.ENCRYPT_INFO, 'utf8'),
474
+ length: 32,
475
+ });
476
+ PairingProtocol.log.debug('Derived encryption keys');
477
+ return {
478
+ encryptKey: sharedKey,
479
+ decryptKey: sharedKey,
480
+ };
481
+ }
482
+ /**
483
+ * Saves pairing credentials to storage and returns credential path
484
+ * Persists long-term public and private keys for future connections
485
+ * @param device Apple TV device information
486
+ * @param ltpk Long-term public key to save
487
+ * @param ltsk Long-term secret key to save
488
+ * @returns Path to saved pairing credentials file
489
+ */
490
+ async createPairingResult(device, ltpk, ltsk) {
491
+ const storage = new PairingStorage(DEFAULT_PAIRING_CONFIG);
492
+ return await storage.save(device.identifier || device.name, ltpk, ltsk);
493
+ }
494
+ }
@@ -0,0 +1,57 @@
1
+ import type { AppleTVDevice } from '../../bonjour/bonjour-discovery.js';
2
+ /** Encryption keys derived from SRP session key for secure communication */
3
+ export interface EncryptionKeys {
4
+ encryptKey: Buffer;
5
+ decryptKey: Buffer;
6
+ }
7
+ /** Handshake message payload structure */
8
+ export interface HandshakePayload {
9
+ request: {
10
+ _0: {
11
+ handshake: {
12
+ _0: {
13
+ hostOptions: {
14
+ attemptPairVerify: boolean;
15
+ };
16
+ wireProtocolVersion: number;
17
+ };
18
+ };
19
+ };
20
+ };
21
+ }
22
+ /** Pairing event message payload structure */
23
+ export interface PairingDataPayload {
24
+ event: {
25
+ _0: {
26
+ pairingData?: {
27
+ _0: {
28
+ data: string;
29
+ kind: 'verifyManualPairing' | 'setupManualPairing';
30
+ startNewSession?: boolean;
31
+ sendingHost?: string;
32
+ };
33
+ };
34
+ pairVerifyFailed?: Record<string, never>;
35
+ };
36
+ };
37
+ }
38
+ /** Structure of a pairing request message sent to Apple TV */
39
+ export interface PairingRequest {
40
+ message: {
41
+ plain: {
42
+ _0: HandshakePayload | PairingDataPayload;
43
+ };
44
+ };
45
+ originatedBy: 'host' | 'accessory';
46
+ sequenceNumber: number;
47
+ }
48
+ /** Interface for handling user input during pairing process */
49
+ export interface UserInputInterface {
50
+ promptForPIN(): Promise<string>;
51
+ promptForInput(prompt: string): Promise<string>;
52
+ }
53
+ /** Interface for executing the Apple TV pairing protocol flow */
54
+ export interface PairingProtocolInterface {
55
+ executePairingFlow(device: AppleTVDevice): Promise<string>;
56
+ }
57
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/pairing-protocol/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAExE,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE;QACP,EAAE,EAAE;YACF,SAAS,EAAE;gBACT,EAAE,EAAE;oBACF,WAAW,EAAE;wBAAE,iBAAiB,EAAE,OAAO,CAAA;qBAAE,CAAC;oBAC5C,mBAAmB,EAAE,MAAM,CAAC;iBAC7B,CAAC;aACH,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,EAAE,EAAE;YACF,WAAW,CAAC,EAAE;gBACZ,EAAE,EAAE;oBACF,IAAI,EAAE,MAAM,CAAC;oBACb,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;oBACnD,eAAe,CAAC,EAAE,OAAO,CAAC;oBAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;iBACtB,CAAC;aACH,CAAC;YACF,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;SAC1C,CAAC;KACH,CAAC;CACH;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QACP,KAAK,EAAE;YACL,EAAE,EAAE,gBAAgB,GAAG,kBAAkB,CAAC;SAC3C,CAAC;KACH,CAAC;IACF,YAAY,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IACjC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACjD;AAED,iEAAiE;AACjE,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,10 @@
1
- import { logger } from '@appium/support';
2
1
  import { randomBytes } from 'node:crypto';
2
+ import { getLogger } from '../../logger.js';
3
3
  import { SRP_GENERATOR, SRP_KEY_LENGTH_BYTES, SRP_PRIME_3072, SRP_PRIVATE_KEY_BITS, SRP_USERNAME, } from '../constants.js';
4
4
  import { SRPError } from '../errors.js';
5
5
  import { bigIntToBuffer, bufferToBigInt, modPow, } from '../utils/buffer-utils.js';
6
6
  import { calculateK, calculateM1, calculateU, calculateX, hash, } from './crypto-utils.js';
7
- const log = logger.getLogger('SRPClient');
7
+ const log = getLogger('SRPClient');
8
8
  /**
9
9
  * SRP (Secure Remote Password) client implementation following RFC 5054.
10
10
  *
@@ -0,0 +1,3 @@
1
+ export { PairingStorage } from './pairing-storage.js';
2
+ export type { PairingStorageInterface } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export { PairingStorage } from './pairing-storage.js';
@@ -0,0 +1,12 @@
1
+ import type { PairingConfig } from '../types.js';
2
+ import type { PairingStorageInterface } from './types.js';
3
+ /** Manages persistent storage of pairing credentials as plist files */
4
+ export declare class PairingStorage implements PairingStorageInterface {
5
+ private readonly config;
6
+ private readonly log;
7
+ private readonly box;
8
+ constructor(config: PairingConfig);
9
+ save(deviceId: string, ltpk: Buffer, ltsk: Buffer, remoteUnlockHostKey?: string): Promise<string>;
10
+ private createPlistContent;
11
+ }
12
+ //# sourceMappingURL=pairing-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-storage.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/storage/pairing-storage.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,uEAAuE;AACvE,qBAAa,cAAe,YAAW,uBAAuB;IAIhD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAEQ,MAAM,EAAE,aAAa;IAI5C,IAAI,CACR,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,mBAAmB,SAAK,GACvB,OAAO,CAAC,MAAM,CAAC;IAyBlB,OAAO,CAAC,kBAAkB;CAW3B"}
@@ -0,0 +1,36 @@
1
+ import { strongbox } from '@appium/strongbox';
2
+ import { logger } from '@appium/support';
3
+ import { STRONGBOX_CONTAINER_NAME } from '../../../constants.js';
4
+ import { createXmlPlist } from '../../plist/index.js';
5
+ import { PairingError } from '../errors.js';
6
+ /** Manages persistent storage of pairing credentials as plist files */
7
+ export class PairingStorage {
8
+ config;
9
+ log = logger.getLogger('PairingStorage');
10
+ box;
11
+ constructor(config) {
12
+ this.config = config;
13
+ this.box = strongbox(STRONGBOX_CONTAINER_NAME);
14
+ }
15
+ async save(deviceId, ltpk, ltsk, remoteUnlockHostKey = '') {
16
+ try {
17
+ const itemName = `appletv_pairing_${deviceId}`;
18
+ const plistContent = this.createPlistContent(ltpk, ltsk, remoteUnlockHostKey);
19
+ const item = await this.box.createItemWithValue(itemName, plistContent);
20
+ const itemPath = item.id;
21
+ this.log.info(`Pairing record saved to: ${itemPath}`);
22
+ return itemPath;
23
+ }
24
+ catch (error) {
25
+ this.log.error('Save pairing record error:', error);
26
+ throw new PairingError('Failed to save pairing record', 'SAVE_ERROR', error);
27
+ }
28
+ }
29
+ createPlistContent(publicKey, privateKey, remoteUnlockHostKey) {
30
+ return createXmlPlist({
31
+ private_key: privateKey,
32
+ public_key: publicKey,
33
+ remote_unlock_host_key: remoteUnlockHostKey,
34
+ });
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ /** Interface for storing pairing credentials to disk */
2
+ export interface PairingStorageInterface {
3
+ save(deviceId: string, ltpk: Buffer, ltsk: Buffer, remoteUnlockHostKey?: string): Promise<string>;
4
+ }
5
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../src/lib/apple-tv/storage/types.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,IAAI,CACF,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,mBAAmB,CAAC,EAAE,MAAM,GAC3B,OAAO,CAAC,MAAM,CAAC,CAAC;CACpB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -21,7 +21,6 @@ export interface PairingConfig {
21
21
  timeout: number;
22
22
  discoveryTimeout: number;
23
23
  maxRetries: number;
24
- pairingDirectory: string;
25
24
  }
26
25
  export interface TLV8Item {
27
26
  type: PairingDataComponentTypeValue;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/apple-tv/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB,EAAE,MAAM,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAGD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,6BAA6B,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAGnD,MAAM,MAAM,WAAW,GACnB,IAAI,GACJ,SAAS,GACT,OAAO,GACP,MAAM,GACN,MAAM,GACN,MAAM,GACN,WAAW,GACX,gBAAgB,CAAC;AAGrB,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,WAAW,CAAC;CAAG;AAG1D,MAAM,WAAW,gBAAiB,SAAQ,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;CAAG"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/apple-tv/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB,EAAE,MAAM,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAGD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,6BAA6B,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAGD,MAAM,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAGnD,MAAM,MAAM,WAAW,GACnB,IAAI,GACJ,SAAS,GACT,OAAO,GACP,MAAM,GACN,MAAM,GACN,MAAM,GACN,WAAW,GACX,gBAAgB,CAAC;AAGrB,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,WAAW,CAAC;CAAG;AAG1D,MAAM,WAAW,gBAAiB,SAAQ,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;CAAG"}