velocious 1.0.701 → 1.0.702
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/README.md +1 -0
- package/build/configuration-types.js +72 -2
- package/build/configuration.js +61 -3
- package/build/frontend-model-resource/base-resource.js +150 -2
- package/build/frontend-models/resource-definition.js +47 -0
- package/build/src/configuration-types.d.ts +166 -5
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +67 -3
- package/build/src/configuration.d.ts +11 -2
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +61 -4
- package/build/src/frontend-model-resource/base-resource.d.ts +76 -3
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +127 -3
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +43 -1
- package/build/src/sync/local-mutation-log.d.ts +6 -3
- package/build/src/sync/local-mutation-log.d.ts.map +1 -1
- package/build/src/sync/local-mutation-log.js +11 -4
- package/build/src/sync/peer-mutation-bundle.d.ts +26 -0
- package/build/src/sync/peer-mutation-bundle.d.ts.map +1 -1
- package/build/src/sync/peer-mutation-bundle.js +7 -1
- package/build/src/sync/sync-api-client.d.ts +3 -1
- package/build/src/sync/sync-api-client.d.ts.map +1 -1
- package/build/src/sync/sync-api-client.js +46 -8
- package/build/src/sync/sync-client-types.d.ts +24 -12
- package/build/src/sync/sync-client-types.d.ts.map +1 -1
- package/build/src/sync/sync-client-types.js +7 -5
- package/build/src/sync/sync-client.d.ts +101 -0
- package/build/src/sync/sync-client.d.ts.map +1 -1
- package/build/src/sync/sync-client.js +236 -24
- package/build/src/sync/sync-offline-session.d.ts +328 -0
- package/build/src/sync/sync-offline-session.d.ts.map +1 -0
- package/build/src/sync/sync-offline-session.js +617 -0
- package/build/src/sync/sync-resource-base.d.ts +26 -28
- package/build/src/sync/sync-resource-base.d.ts.map +1 -1
- package/build/src/sync/sync-resource-base.js +11 -45
- package/build/sync/local-mutation-log.js +10 -3
- package/build/sync/peer-mutation-bundle.js +6 -0
- package/build/sync/sync-api-client.js +53 -7
- package/build/sync/sync-client-types.js +6 -4
- package/build/sync/sync-client.js +264 -21
- package/build/sync/sync-offline-session.js +713 -0
- package/build/sync/sync-resource-base.js +10 -53
- package/package.json +1 -1
- package/src/configuration-types.js +72 -2
- package/src/configuration.js +61 -3
- package/src/frontend-model-resource/base-resource.js +150 -2
- package/src/frontend-models/resource-definition.js +47 -0
- package/src/sync/local-mutation-log.js +10 -3
- package/src/sync/peer-mutation-bundle.js +6 -0
- package/src/sync/sync-api-client.js +53 -7
- package/src/sync/sync-client-types.js +6 -4
- package/src/sync/sync-client.js +264 -21
- package/src/sync/sync-offline-session.js +713 -0
- package/src/sync/sync-resource-base.js +10 -53
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { createSignedMutation, generateSyncSigningKeyPair, mutationIdempotencyKey, verifyDeviceCertificate } from "./device-identity.js";
|
|
3
|
+
import { exportPeerMutationBundle, importPeerMutationBundle } from "./peer-mutation-bundle.js";
|
|
4
|
+
import { replayResultLocalStatus } from "./conflict-strategy.js";
|
|
5
|
+
/**
|
|
6
|
+
* Stored device signing identity: the backend-signed certificate plus the
|
|
7
|
+
* device private key matching the certificate's public key.
|
|
8
|
+
* @typedef {object} SyncOfflineDeviceIdentity
|
|
9
|
+
* @property {import("./device-identity.js").DeviceCertificate} certificate - Backend-signed device certificate.
|
|
10
|
+
* @property {import("./device-identity.js").SyncJsonWebKey} privateKey - Device private signing key (JWK).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Result of one offline session replay pass over the durable mutation logs.
|
|
14
|
+
* @typedef {object} SyncOfflineReplayResult
|
|
15
|
+
* @property {number} conflictCount - Records left in `conflict`.
|
|
16
|
+
* @property {number} pendingCount - Records still pending (blocked dependencies or unacknowledged).
|
|
17
|
+
* @property {number} rejectedCount - Records the backend rejected.
|
|
18
|
+
* @property {Array<{clientMutationId: string, idempotencyKey: string, mutation: import("./device-identity.js").SyncMutation, recordId: string, serverResult: Record<string, ReturnType<typeof JSON.parse>> | null, status: import("./local-mutation-log.js").LocalMutationStatus}>} results - Per-mutation mapped results in replay order.
|
|
19
|
+
* @property {number} syncedCount - Records acknowledged by the backend.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Session summary returned by start/refresh.
|
|
23
|
+
* @typedef {object} SyncOfflineSessionSummary
|
|
24
|
+
* @property {string} actorDeviceId - Certificate device identity.
|
|
25
|
+
* @property {string} actorUserId - Certificate user identity.
|
|
26
|
+
* @property {string} expiresAt - Live grant expiry.
|
|
27
|
+
* @property {string} grantId - Live grant id.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Framework-owned long-offline session behind SyncClient: device identity
|
|
31
|
+
* lifecycle, backend-issued offline grants, mutation signing, signed replay,
|
|
32
|
+
* and peer bundle transfer. SyncClient owns the public API; this class owns the
|
|
33
|
+
* session state. The app only provides durable storage adapters, the
|
|
34
|
+
* enrollment hook, and the grant scopes callback.
|
|
35
|
+
*/
|
|
36
|
+
export default class SyncOfflineSession {
|
|
37
|
+
/**
|
|
38
|
+
* Builds an offline session from the normalized `sync.client.offline` block.
|
|
39
|
+
* @param {object} args - Session args.
|
|
40
|
+
* @param {() => string | Promise<string>} args.authenticationToken - Resolves the auth token sent with bootstrap/replay requests.
|
|
41
|
+
* @param {import("../configuration-types.js").NormalizedVelociousSyncClientOfflineConfiguration} args.config - Normalized offline configuration.
|
|
42
|
+
* @param {Array<import("./local-mutation-log.js").default>} args.mutationLogs - Durable local mutation logs the session signs, replays, resigns, and exports.
|
|
43
|
+
* @param {(path: string, payload: Record<string, ReturnType<typeof JSON.parse>>, options?: {signal?: AbortSignal}) => Promise<ReturnType<typeof JSON.parse>>} args.post - Framework POSTer over the configured transport.
|
|
44
|
+
*/
|
|
45
|
+
constructor({ authenticationToken, config, mutationLogs, post }) {
|
|
46
|
+
if (!Array.isArray(mutationLogs) || mutationLogs.length === 0)
|
|
47
|
+
throw new Error("SyncOfflineSession requires at least one mutation log");
|
|
48
|
+
this.authenticationToken = authenticationToken;
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.mutationLogs = mutationLogs;
|
|
51
|
+
this.post = post;
|
|
52
|
+
/** @type {SyncOfflineDeviceIdentity | null} */
|
|
53
|
+
this._identity = null;
|
|
54
|
+
/** @type {Map<string, import("./offline-grant.js").SignedOfflineGrant>} */
|
|
55
|
+
this._grants = new Map();
|
|
56
|
+
this._grantsLoaded = false;
|
|
57
|
+
/** @type {import("./offline-grant.js").SignedOfflineGrant | null} */
|
|
58
|
+
this._activeGrant = null;
|
|
59
|
+
/** @type {Record<string, import("../configuration-types.js").NormalizedFrontendModelResourceSyncConfiguration> | null} */
|
|
60
|
+
this._syncManifest = null;
|
|
61
|
+
/** @type {Promise<SyncOfflineSessionSummary> | null} */
|
|
62
|
+
this._startPromise = null;
|
|
63
|
+
/** @type {Promise<SyncOfflineReplayResult> | null} */
|
|
64
|
+
this._replayPromise = null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether the session currently has a usable device identity and live grant.
|
|
68
|
+
* @returns {boolean} - Whether the session is active.
|
|
69
|
+
*/
|
|
70
|
+
isActive() {
|
|
71
|
+
return Boolean(this._identity && this._liveGrant());
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolves the device signing identity (generating and enrolling a key pair
|
|
75
|
+
* when the app-owned storage has none) and bootstraps a live offline grant.
|
|
76
|
+
* Idempotent and single-flighted: concurrent starts share one bootstrap.
|
|
77
|
+
* @returns {Promise<SyncOfflineSessionSummary>} - Session summary.
|
|
78
|
+
*/
|
|
79
|
+
async start() {
|
|
80
|
+
if (this._identity && this._liveGrant())
|
|
81
|
+
return this.sessionSummary();
|
|
82
|
+
if (!this._startPromise) {
|
|
83
|
+
this._startPromise = this._startSession().then((summary) => {
|
|
84
|
+
this._startPromise = null;
|
|
85
|
+
return summary;
|
|
86
|
+
}, (error) => {
|
|
87
|
+
this._startPromise = null;
|
|
88
|
+
throw error;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return await this._startPromise;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Drops the in-memory session (identity and grants stay in the app-owned
|
|
95
|
+
* durable storage; pending mutations stay in the durable mutation logs).
|
|
96
|
+
* @returns {Promise<void>} - Resolves after the session is dropped.
|
|
97
|
+
*/
|
|
98
|
+
async stop() {
|
|
99
|
+
if (this._replayPromise)
|
|
100
|
+
await this._replayPromise.catch(() => undefined);
|
|
101
|
+
this._identity = null;
|
|
102
|
+
this._activeGrant = null;
|
|
103
|
+
this._syncManifest = null;
|
|
104
|
+
this._grantsLoaded = false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Runs one start attempt: identity first, then grant bootstrap.
|
|
108
|
+
* @returns {Promise<SyncOfflineSessionSummary>} - Session summary.
|
|
109
|
+
*/
|
|
110
|
+
async _startSession() {
|
|
111
|
+
await this._ensureIdentity();
|
|
112
|
+
await this._loadRetainedGrants();
|
|
113
|
+
await this._ensureLiveGrant();
|
|
114
|
+
return this.sessionSummary();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns the current session summary.
|
|
118
|
+
* @returns {SyncOfflineSessionSummary} - Session summary.
|
|
119
|
+
*/
|
|
120
|
+
sessionSummary() {
|
|
121
|
+
const identity = this._requireIdentity();
|
|
122
|
+
const grant = this._requireLiveGrant();
|
|
123
|
+
return {
|
|
124
|
+
actorDeviceId: identity.certificate.certificate.actorDeviceId,
|
|
125
|
+
actorUserId: identity.certificate.certificate.actorUserId,
|
|
126
|
+
expiresAt: grant.grant.expiresAt,
|
|
127
|
+
grantId: grant.grant.grantId
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolves the device identity: load it from the app-owned storage, or
|
|
132
|
+
* generate a key pair, run the app-owned enrollment hook, verify the returned
|
|
133
|
+
* certificate against the configured backend public key, and store it.
|
|
134
|
+
* @returns {Promise<SyncOfflineDeviceIdentity>} - Device signing identity.
|
|
135
|
+
*/
|
|
136
|
+
async _ensureIdentity() {
|
|
137
|
+
if (this._identity)
|
|
138
|
+
return this._identity;
|
|
139
|
+
const stored = await this.config.deviceIdentityStorage.load();
|
|
140
|
+
if (stored) {
|
|
141
|
+
this._identity = await this._verifiedIdentity(stored);
|
|
142
|
+
return this._identity;
|
|
143
|
+
}
|
|
144
|
+
const keyPair = await generateSyncSigningKeyPair();
|
|
145
|
+
const certificate = await this.config.enrollDevice({
|
|
146
|
+
configuration: this.config.configuration,
|
|
147
|
+
publicKey: keyPair.publicKey
|
|
148
|
+
});
|
|
149
|
+
const identity = await this._verifiedIdentity({ certificate, privateKey: keyPair.privateKey });
|
|
150
|
+
await this.config.deviceIdentityStorage.save(/** @type {SyncOfflineDeviceIdentity} */ (cloneJson(identity, "device identity")));
|
|
151
|
+
this._identity = identity;
|
|
152
|
+
return identity;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Verifies a stored/enrolled certificate against the configured backend
|
|
156
|
+
* public key (when one is configured) and returns the identity.
|
|
157
|
+
* @param {SyncOfflineDeviceIdentity} identity - Candidate identity.
|
|
158
|
+
* @returns {Promise<SyncOfflineDeviceIdentity>} - Verified identity.
|
|
159
|
+
*/
|
|
160
|
+
async _verifiedIdentity(identity) {
|
|
161
|
+
if (!identity || typeof identity !== "object" || Array.isArray(identity)) {
|
|
162
|
+
throw new Error("sync.client.offline device identity storage must provide {certificate, privateKey} or null");
|
|
163
|
+
}
|
|
164
|
+
if (!identity.certificate || !identity.privateKey)
|
|
165
|
+
throw new Error("sync.client.offline device identity requires a certificate and a privateKey");
|
|
166
|
+
if (this.config.backendPublicKey) {
|
|
167
|
+
await verifyDeviceCertificate({
|
|
168
|
+
backendPublicKey: this.config.backendPublicKey,
|
|
169
|
+
certificate: identity.certificate
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return identity;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Loads app-retained grants once so a restarted device can still replay
|
|
176
|
+
* mutations that were signed under an older grant (peer-forwarded mutations
|
|
177
|
+
* keep the grant their original signer used).
|
|
178
|
+
* @returns {Promise<void>} - Resolves after retention is loaded.
|
|
179
|
+
*/
|
|
180
|
+
async _loadRetainedGrants() {
|
|
181
|
+
if (this._grantsLoaded)
|
|
182
|
+
return;
|
|
183
|
+
this._grantsLoaded = true;
|
|
184
|
+
if (!this.config.grantStorage)
|
|
185
|
+
return;
|
|
186
|
+
const retained = await this.config.grantStorage.load();
|
|
187
|
+
if (!Array.isArray(retained))
|
|
188
|
+
throw new Error("sync.client.offline grantStorage.load must resolve an array of signed offline grants");
|
|
189
|
+
for (const signedGrant of retained) {
|
|
190
|
+
if (!signedGrant || typeof signedGrant !== "object" || !signedGrant.grant || typeof signedGrant.grant.grantId !== "string")
|
|
191
|
+
continue;
|
|
192
|
+
this._grants.set(signedGrant.grant.grantId, signedGrant);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Returns the active grant when it is still live, otherwise null. Expiry is
|
|
197
|
+
* checked by timestamp only: the grant's HMAC signature is backend-only and
|
|
198
|
+
* can never be verified on the device.
|
|
199
|
+
* @param {Date} [now] - Check time.
|
|
200
|
+
* @returns {import("./offline-grant.js").SignedOfflineGrant | null} - Live grant or null.
|
|
201
|
+
*/
|
|
202
|
+
_liveGrant(now = this._now()) {
|
|
203
|
+
if (!this._activeGrant)
|
|
204
|
+
return null;
|
|
205
|
+
if (new Date(this._activeGrant.grant.expiresAt).getTime() <= now.getTime())
|
|
206
|
+
return null;
|
|
207
|
+
return this._activeGrant;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Ensures a live grant exists, bootstrapping a new one when the current grant
|
|
211
|
+
* is missing or expired. A refreshed grant re-signs this device's pending
|
|
212
|
+
* records so the backend always sees the grant an intent is replayed under.
|
|
213
|
+
* @returns {Promise<import("./offline-grant.js").SignedOfflineGrant>} - Live grant.
|
|
214
|
+
*/
|
|
215
|
+
async _ensureLiveGrant() {
|
|
216
|
+
const live = this._liveGrant();
|
|
217
|
+
if (live)
|
|
218
|
+
return live;
|
|
219
|
+
const granted = await this.bootstrapGrant();
|
|
220
|
+
await this._resignPendingRecords(granted);
|
|
221
|
+
return granted;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Requests a fresh offline grant from the built-in sync bootstrap endpoint
|
|
225
|
+
* and retains it under its grant id together with the sync manifest whose
|
|
226
|
+
* policy hashes mutations are signed against.
|
|
227
|
+
* @returns {Promise<import("./offline-grant.js").SignedOfflineGrant>} - Newly issued grant.
|
|
228
|
+
*/
|
|
229
|
+
async bootstrapGrant() {
|
|
230
|
+
const identity = await this._ensureIdentity();
|
|
231
|
+
const authenticationToken = await this.authenticationToken();
|
|
232
|
+
const scopes = await this.config.grantScopes({ configuration: this.config.configuration });
|
|
233
|
+
if (!scopes || typeof scopes !== "object" || Array.isArray(scopes))
|
|
234
|
+
throw new Error("sync.client.offline.grantScopes must resolve a plain object of grant scopes");
|
|
235
|
+
const response = await this.post(this.config.bootstrapPath, {
|
|
236
|
+
authenticationToken,
|
|
237
|
+
deviceId: identity.certificate.certificate.actorDeviceId,
|
|
238
|
+
scopes
|
|
239
|
+
});
|
|
240
|
+
if (!response || typeof response !== "object" || Array.isArray(response)) {
|
|
241
|
+
throw new Error(`Sync offline bootstrap did not return a response object (${this.config.bootstrapPath})`);
|
|
242
|
+
}
|
|
243
|
+
const responseRecord = /** @type {Record<string, unknown>} */ (response);
|
|
244
|
+
const signedGrant = /** @type {import("./offline-grant.js").SignedOfflineGrant | undefined} */ (responseRecord.offlineGrant);
|
|
245
|
+
if (!signedGrant || typeof signedGrant !== "object" || !signedGrant.grant || typeof signedGrant.grant.grantId !== "string") {
|
|
246
|
+
throw new Error(`Sync offline bootstrap did not return a signed offline grant (${this.config.bootstrapPath})`);
|
|
247
|
+
}
|
|
248
|
+
const manifest = /** @type {Record<string, import("../configuration-types.js").NormalizedFrontendModelResourceSyncConfiguration> | undefined} */ (responseRecord.syncManifest);
|
|
249
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
250
|
+
throw new Error(`Sync offline bootstrap did not return a sync manifest (${this.config.bootstrapPath})`);
|
|
251
|
+
}
|
|
252
|
+
this._grants.set(signedGrant.grant.grantId, signedGrant);
|
|
253
|
+
this._activeGrant = signedGrant;
|
|
254
|
+
this._syncManifest = manifest;
|
|
255
|
+
await this._persistGrants();
|
|
256
|
+
return signedGrant;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Forces a grant refresh: bootstrap a new grant and re-sign this device's
|
|
260
|
+
* pending records under it.
|
|
261
|
+
* @returns {Promise<SyncOfflineSessionSummary>} - Refreshed session summary.
|
|
262
|
+
*/
|
|
263
|
+
async refreshGrant() {
|
|
264
|
+
await this._ensureIdentity();
|
|
265
|
+
await this._loadRetainedGrants();
|
|
266
|
+
const granted = await this.bootstrapGrant();
|
|
267
|
+
await this._resignPendingRecords(granted);
|
|
268
|
+
return this.sessionSummary();
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Re-signs this device's still-pending records under a new grant. Safe
|
|
272
|
+
* because the replay idempotency key is
|
|
273
|
+
* `actorUserId:actorDeviceId:clientMutationId` and never carries the grant
|
|
274
|
+
* id. Records signed by another device (peer-forwarded mutations) keep their
|
|
275
|
+
* original signed envelope and original grant - they must always replay with
|
|
276
|
+
* the grant their signer used.
|
|
277
|
+
* @param {import("./offline-grant.js").SignedOfflineGrant} signedGrant - Grant the records are re-signed under.
|
|
278
|
+
* @returns {Promise<void>} - Resolves after pending records are re-signed.
|
|
279
|
+
*/
|
|
280
|
+
async _resignPendingRecords(signedGrant) {
|
|
281
|
+
this._requireIdentity();
|
|
282
|
+
for (const mutationLog of this.mutationLogs) {
|
|
283
|
+
for (const record of await mutationLog.pendingRecords()) {
|
|
284
|
+
if (!this._isOwnRecord(record))
|
|
285
|
+
continue;
|
|
286
|
+
if (record.mutation.offlineGrantId === signedGrant.grant.grantId && record.signedMutation)
|
|
287
|
+
continue;
|
|
288
|
+
const signedMutation = await this.signMutation(record.mutation);
|
|
289
|
+
await mutationLog.updateMutation({ id: record.id, mutation: signedMutation.mutation, signedMutation });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Checks whether a log record was signed/queued by this device's identity
|
|
295
|
+
* (or has no identity yet and can adopt it).
|
|
296
|
+
* @param {import("./local-mutation-log.js").LocalMutationLogRecord} record - Durable record.
|
|
297
|
+
* @returns {boolean} - Whether the record belongs to this device.
|
|
298
|
+
*/
|
|
299
|
+
_isOwnRecord(record) {
|
|
300
|
+
const identity = this._requireIdentity();
|
|
301
|
+
if (record.signedMutation) {
|
|
302
|
+
return record.signedMutation.mutation.actorDeviceId === identity.certificate.certificate.actorDeviceId &&
|
|
303
|
+
record.signedMutation.mutation.actorUserId === identity.certificate.certificate.actorUserId;
|
|
304
|
+
}
|
|
305
|
+
return !record.mutation.actorDeviceId || (record.mutation.actorDeviceId === identity.certificate.certificate.actorDeviceId &&
|
|
306
|
+
record.mutation.actorUserId === identity.certificate.certificate.actorUserId);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Signs one mutation payload with the device key under the live grant,
|
|
310
|
+
* filling the actor/grant/policy-hash identity from the session.
|
|
311
|
+
* @param {Omit<import("./device-identity.js").SyncMutation, "actorDeviceId" | "actorUserId" | "offlineGrantId" | "policyHash">} mutation - Mutation payload to sign (the session supplies the rotating identity fields).
|
|
312
|
+
* @returns {Promise<import("./device-identity.js").SignedSyncMutation>} - Signed mutation envelope.
|
|
313
|
+
*/
|
|
314
|
+
async signMutation(mutation) {
|
|
315
|
+
const identity = await this._ensureIdentity();
|
|
316
|
+
const grant = await this._grantForSigning();
|
|
317
|
+
return await createSignedMutation({
|
|
318
|
+
deviceCertificate: identity.certificate,
|
|
319
|
+
devicePrivateKey: identity.privateKey,
|
|
320
|
+
mutation: {
|
|
321
|
+
...mutation,
|
|
322
|
+
actorDeviceId: identity.certificate.certificate.actorDeviceId,
|
|
323
|
+
actorUserId: identity.certificate.certificate.actorUserId,
|
|
324
|
+
offlineGrantId: grant.grant.grantId,
|
|
325
|
+
policyHash: this.policyHashFor(mutation.model)
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Returns the grant a new mutation is signed under: the live grant when one
|
|
331
|
+
* exists, otherwise the newest retained grant (queueing stays possible while
|
|
332
|
+
* offline - the replay pass refreshes the grant and re-signs the pending
|
|
333
|
+
* records), and only bootstraps when no grant has ever been retained.
|
|
334
|
+
* @returns {Promise<import("./offline-grant.js").SignedOfflineGrant>} - Grant to sign under.
|
|
335
|
+
*/
|
|
336
|
+
async _grantForSigning() {
|
|
337
|
+
const live = this._liveGrant();
|
|
338
|
+
if (live)
|
|
339
|
+
return live;
|
|
340
|
+
if (this._activeGrant)
|
|
341
|
+
return this._activeGrant;
|
|
342
|
+
await this._loadRetainedGrants();
|
|
343
|
+
const retained = Array.from(this._grants.values()).sort((left, right) => new Date(right.grant.issuedAt).getTime() - new Date(left.grant.issuedAt).getTime());
|
|
344
|
+
if (retained.length > 0) {
|
|
345
|
+
this._activeGrant = retained[0];
|
|
346
|
+
return this._activeGrant;
|
|
347
|
+
}
|
|
348
|
+
return await this.bootstrapGrant();
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Returns the policy hash the live session's sync manifest carries for one model.
|
|
352
|
+
* @param {string} model - Model/resource name.
|
|
353
|
+
* @returns {string} - Policy hash.
|
|
354
|
+
*/
|
|
355
|
+
policyHashFor(model) {
|
|
356
|
+
const manifest = this._syncManifest || this._activeGrant?.grant.resources;
|
|
357
|
+
if (!manifest)
|
|
358
|
+
throw new Error("Sync offline session has no sync manifest - call startOfflineSession() first");
|
|
359
|
+
const entry = /** @type {Record<string, unknown> | undefined} */ (manifest[model]);
|
|
360
|
+
const policyHash = entry && typeof entry === "object" && !Array.isArray(entry) ? /** @type {Record<string, unknown>} */ (entry).policyHash : undefined;
|
|
361
|
+
if (typeof policyHash !== "string" || policyHash.length === 0) {
|
|
362
|
+
throw new Error(`Sync offline session has no policy hash for model '${model}' - is it sync-enabled on the backend?`);
|
|
363
|
+
}
|
|
364
|
+
return policyHash;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Drains the durable mutation logs through the built-in signed replay
|
|
368
|
+
* endpoint. Pending records are batched, each signed mutation travels with
|
|
369
|
+
* the signed grant it was signed under, and per-mutation results are mapped
|
|
370
|
+
* back onto the log records as synced/conflict/rejected.
|
|
371
|
+
* @param {object} [options] - Replay options.
|
|
372
|
+
* @param {number} [options.batchSize] - Max mutations per request.
|
|
373
|
+
* @param {AbortSignal} [options.signal] - Lifecycle cancellation signal.
|
|
374
|
+
* @returns {Promise<SyncOfflineReplayResult>} - Mapped replay result.
|
|
375
|
+
*/
|
|
376
|
+
async replayPending(options = {}) {
|
|
377
|
+
if (this._replayPromise)
|
|
378
|
+
return await this._replayPromise;
|
|
379
|
+
this._replayPromise = this._replayPass(options).finally(() => {
|
|
380
|
+
this._replayPromise = null;
|
|
381
|
+
});
|
|
382
|
+
return await this._replayPromise;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Runs one signed replay pass.
|
|
386
|
+
* @param {object} options - Pass options.
|
|
387
|
+
* @param {number} [options.batchSize] - Max mutations per request.
|
|
388
|
+
* @param {AbortSignal} [options.signal] - Lifecycle cancellation signal.
|
|
389
|
+
* @returns {Promise<SyncOfflineReplayResult>} - Mapped replay result.
|
|
390
|
+
*/
|
|
391
|
+
async _replayPass({ batchSize, signal } = {}) {
|
|
392
|
+
throwIfAborted(signal);
|
|
393
|
+
if (!this.isActive())
|
|
394
|
+
await this.start();
|
|
395
|
+
const maxBatchSize = typeof batchSize === "number" && batchSize >= 1 ? Math.floor(batchSize) : 100;
|
|
396
|
+
/** @type {SyncOfflineReplayResult["results"]} */
|
|
397
|
+
const results = [];
|
|
398
|
+
let syncedCount = 0;
|
|
399
|
+
let conflictCount = 0;
|
|
400
|
+
let rejectedCount = 0;
|
|
401
|
+
for (let pass = 0; pass < 10; pass += 1) {
|
|
402
|
+
throwIfAborted(signal);
|
|
403
|
+
await this._ensureLiveGrant();
|
|
404
|
+
/** @type {Array<{mutationLog: import("./local-mutation-log.js").default, record: import("./local-mutation-log.js").LocalMutationLogRecord}>} */
|
|
405
|
+
const entries = [];
|
|
406
|
+
for (const mutationLog of this.mutationLogs) {
|
|
407
|
+
for (const record of await mutationLog.pendingRecords()) {
|
|
408
|
+
if (!record.signedMutation)
|
|
409
|
+
continue;
|
|
410
|
+
// A record can only replay with the grant it was signed under. Records
|
|
411
|
+
// whose grant the session does not retain (e.g. peer imports without
|
|
412
|
+
// their signer's grant) stay pending instead of blocking the batch.
|
|
413
|
+
if (!this._grants.has(record.mutation.offlineGrantId))
|
|
414
|
+
continue;
|
|
415
|
+
entries.push({ mutationLog, record });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (entries.length === 0)
|
|
419
|
+
break;
|
|
420
|
+
const batch = entries.slice(0, maxBatchSize);
|
|
421
|
+
const authenticationToken = await this.authenticationToken();
|
|
422
|
+
const mutations = batch.map((entry) => this._replayMutation(entry.record));
|
|
423
|
+
const response = await this.post(this.config.replayPath, { authenticationToken, mutations }, { signal });
|
|
424
|
+
throwIfAborted(signal);
|
|
425
|
+
const resultsByIdempotencyKey = this._replayResultsById(response);
|
|
426
|
+
for (const { mutationLog, record } of batch) {
|
|
427
|
+
throwIfAborted(signal);
|
|
428
|
+
const idempotencyKey = mutationIdempotencyKey(/** @type {import("./device-identity.js").SignedSyncMutation} */ (record.signedMutation));
|
|
429
|
+
const result = resultsByIdempotencyKey.get(idempotencyKey);
|
|
430
|
+
if (!result)
|
|
431
|
+
throw new Error(`Signed sync replay response is missing a result for mutation ${record.mutation.clientMutationId}`);
|
|
432
|
+
const status = replayResultLocalStatus(/** @type {Record<string, import("./conflict-strategy.js").SyncJsonValue>} */ (result));
|
|
433
|
+
const serverResult = cloneJson(result, "sync replay result");
|
|
434
|
+
const updated = await mutationLog.updateStatus({
|
|
435
|
+
id: record.id,
|
|
436
|
+
status,
|
|
437
|
+
syncResult: /** @type {Record<string, import("../frontend-models/base.js").FrontendModelTransportValue>} */ (serverResult)
|
|
438
|
+
});
|
|
439
|
+
results.push({
|
|
440
|
+
clientMutationId: record.mutation.clientMutationId,
|
|
441
|
+
idempotencyKey,
|
|
442
|
+
mutation: /** @type {import("./device-identity.js").SyncMutation} */ (cloneJson(record.mutation, "mutation")),
|
|
443
|
+
recordId: updated.id,
|
|
444
|
+
serverResult,
|
|
445
|
+
status: updated.status
|
|
446
|
+
});
|
|
447
|
+
if (updated.status === "synced")
|
|
448
|
+
syncedCount += 1;
|
|
449
|
+
if (updated.status === "conflict")
|
|
450
|
+
conflictCount += 1;
|
|
451
|
+
if (updated.status === "rejected")
|
|
452
|
+
rejectedCount += 1;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
let pendingCount = 0;
|
|
456
|
+
for (const mutationLog of this.mutationLogs) {
|
|
457
|
+
pendingCount += (await mutationLog.pendingRecords()).length;
|
|
458
|
+
}
|
|
459
|
+
return { conflictCount, pendingCount, rejectedCount, results, syncedCount };
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Builds one signed replay mutation: the signed envelope plus the signed
|
|
463
|
+
* grant the mutation was signed under. Fails closed when the session does
|
|
464
|
+
* not retain that grant.
|
|
465
|
+
* @param {import("./local-mutation-log.js").LocalMutationLogRecord} record - Durable record.
|
|
466
|
+
* @returns {Record<string, ReturnType<typeof JSON.parse>>} - Replay mutation payload.
|
|
467
|
+
*/
|
|
468
|
+
_replayMutation(record) {
|
|
469
|
+
const signedMutation = /** @type {import("./device-identity.js").SignedSyncMutation} */ (record.signedMutation);
|
|
470
|
+
const signedGrant = this._grants.get(signedMutation.mutation.offlineGrantId);
|
|
471
|
+
if (!signedGrant)
|
|
472
|
+
throw new Error(`Sync offline session has no retained grant '${signedMutation.mutation.offlineGrantId}' for mutation ${signedMutation.mutation.clientMutationId}`);
|
|
473
|
+
return { ...cloneJson(signedMutation, "signed mutation"), signedOfflineGrant: cloneJson(signedGrant, "signed offline grant") };
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Normalizes a signed replay response into results keyed by idempotency key.
|
|
477
|
+
* @param {ReturnType<typeof JSON.parse>} response - Raw replay response.
|
|
478
|
+
* @returns {Map<string, Record<string, ReturnType<typeof JSON.parse>>>} - Results by idempotency key.
|
|
479
|
+
*/
|
|
480
|
+
_replayResultsById(response) {
|
|
481
|
+
if (!response || typeof response !== "object" || Array.isArray(response))
|
|
482
|
+
throw new Error("Signed sync replay response must be an object");
|
|
483
|
+
const responseRecord = /** @type {Record<string, unknown>} */ (response);
|
|
484
|
+
if (responseRecord.status === "error")
|
|
485
|
+
throw new Error(typeof responseRecord.errorMessage === "string" ? responseRecord.errorMessage : "Signed sync replay failed");
|
|
486
|
+
if (!Array.isArray(responseRecord.results))
|
|
487
|
+
throw new Error("Signed sync replay response is missing results");
|
|
488
|
+
/** @type {Map<string, Record<string, ReturnType<typeof JSON.parse>>>} */
|
|
489
|
+
const byKey = new Map();
|
|
490
|
+
for (const result of responseRecord.results) {
|
|
491
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
492
|
+
continue;
|
|
493
|
+
const resultRecord = /** @type {Record<string, unknown>} */ (result);
|
|
494
|
+
if (typeof resultRecord.idempotencyKey !== "string")
|
|
495
|
+
continue;
|
|
496
|
+
byKey.set(resultRecord.idempotencyKey, /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (resultRecord));
|
|
497
|
+
}
|
|
498
|
+
return byKey;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Exports the durable non-terminal mutations of every session log as one
|
|
502
|
+
* signed peer bundle.
|
|
503
|
+
* @param {object} [options] - Export options.
|
|
504
|
+
* @param {import("./local-mutation-log.js").LocalMutationStatus[]} [options.statuses] - Statuses to export.
|
|
505
|
+
* @param {() => Date} [options.now] - Export clock.
|
|
506
|
+
* @returns {Promise<import("./peer-mutation-bundle.js").PeerMutationBundle>} - Signed peer bundle.
|
|
507
|
+
*/
|
|
508
|
+
async exportBundle(options = {}) {
|
|
509
|
+
const identity = await this._ensureIdentity();
|
|
510
|
+
/** @type {import("./peer-mutation-bundle.js").PeerMutationBundle["mutations"]} */
|
|
511
|
+
const mutations = [];
|
|
512
|
+
let bundle = null;
|
|
513
|
+
for (const mutationLog of this.mutationLogs) {
|
|
514
|
+
const logBundle = await exportPeerMutationBundle({
|
|
515
|
+
deviceCertificate: identity.certificate,
|
|
516
|
+
devicePrivateKey: identity.privateKey,
|
|
517
|
+
mutationLog,
|
|
518
|
+
...options
|
|
519
|
+
});
|
|
520
|
+
if (!bundle)
|
|
521
|
+
bundle = logBundle;
|
|
522
|
+
else
|
|
523
|
+
mutations.push(...logBundle.mutations);
|
|
524
|
+
}
|
|
525
|
+
if (!bundle) {
|
|
526
|
+
throw new Error("Sync offline session export produced no bundle");
|
|
527
|
+
}
|
|
528
|
+
bundle.mutations = mutations.length > 0 ? [...bundle.mutations, ...mutations].sort((left, right) => (left.localSequence || 0) - (right.localSequence || 0)) : bundle.mutations;
|
|
529
|
+
return bundle;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Imports a peer bundle: every entry is verified against the configured
|
|
533
|
+
* backend public key before it is appended to a local mutation log. Entries
|
|
534
|
+
* already present by idempotency key are skipped as duplicates.
|
|
535
|
+
* @param {object} args - Import args.
|
|
536
|
+
* @param {import("./peer-mutation-bundle.js").PeerMutationBundle} args.bundle - Peer bundle to import.
|
|
537
|
+
* @param {Array<import("./offline-grant.js").SignedOfflineGrant>} [args.signedOfflineGrants] - Grants the peer signed its mutations under. They are retained so imported mutations replay with the grant their signer used.
|
|
538
|
+
* @param {import("./local-mutation-log.js").default} [args.mutationLog] - Target log. Defaults to the first session log.
|
|
539
|
+
* @returns {Promise<import("./peer-mutation-bundle.js").PeerMutationBundleImportResult>} - Import result.
|
|
540
|
+
*/
|
|
541
|
+
async importBundle({ bundle, mutationLog, signedOfflineGrants = [] }) {
|
|
542
|
+
if (!this.config.backendPublicKey)
|
|
543
|
+
throw new Error("sync.client.offline.backendPublicKey is required to import a peer mutation bundle");
|
|
544
|
+
if (!Array.isArray(signedOfflineGrants))
|
|
545
|
+
throw new Error("sync offline import signedOfflineGrants must be an array of signed offline grants");
|
|
546
|
+
for (const signedGrant of signedOfflineGrants) {
|
|
547
|
+
if (!signedGrant || typeof signedGrant !== "object" || !signedGrant.grant || typeof signedGrant.grant.grantId !== "string") {
|
|
548
|
+
throw new Error("sync offline import requires signed offline grant envelopes with grant payloads");
|
|
549
|
+
}
|
|
550
|
+
this._grants.set(signedGrant.grant.grantId, signedGrant);
|
|
551
|
+
}
|
|
552
|
+
if (signedOfflineGrants.length > 0)
|
|
553
|
+
await this._persistGrants();
|
|
554
|
+
return await importPeerMutationBundle({
|
|
555
|
+
backendPublicKey: this.config.backendPublicKey,
|
|
556
|
+
bundle,
|
|
557
|
+
mutationLog: mutationLog || this.mutationLogs[0]
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Persists retained grants through the optional app-owned grant storage.
|
|
562
|
+
* @returns {Promise<void>} - Resolves after retention is persisted.
|
|
563
|
+
*/
|
|
564
|
+
async _persistGrants() {
|
|
565
|
+
if (!this.config.grantStorage)
|
|
566
|
+
return;
|
|
567
|
+
await this.config.grantStorage.save(Array.from(this._grants.values()));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Returns the session clock.
|
|
571
|
+
* @returns {Date} - Current time.
|
|
572
|
+
*/
|
|
573
|
+
_now() {
|
|
574
|
+
return this.config.now();
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Returns the device identity or fails closed.
|
|
578
|
+
* @returns {SyncOfflineDeviceIdentity} - Device identity.
|
|
579
|
+
*/
|
|
580
|
+
_requireIdentity() {
|
|
581
|
+
if (!this._identity)
|
|
582
|
+
throw new Error("Sync offline session has no device identity - call startOfflineSession() first");
|
|
583
|
+
return this._identity;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Returns the live grant or fails closed.
|
|
587
|
+
* @returns {import("./offline-grant.js").SignedOfflineGrant} - Live grant.
|
|
588
|
+
*/
|
|
589
|
+
_requireLiveGrant() {
|
|
590
|
+
const grant = this._liveGrant();
|
|
591
|
+
if (!grant)
|
|
592
|
+
throw new Error("Sync offline session has no live offline grant - call startOfflineSession() or refreshOfflineGrant()");
|
|
593
|
+
return grant;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Clones a JSON object.
|
|
598
|
+
* @param {unknown} value - Value.
|
|
599
|
+
* @param {string} label - Diagnostic label.
|
|
600
|
+
* @returns {Record<string, ReturnType<typeof JSON.parse>>} - Cloned object.
|
|
601
|
+
*/
|
|
602
|
+
function cloneJson(value, label) {
|
|
603
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
604
|
+
throw new Error(`Expected sync offline ${label} object`);
|
|
605
|
+
return /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (JSON.parse(JSON.stringify(value)));
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Throws the exact abort reason at lifecycle boundaries.
|
|
609
|
+
* @param {AbortSignal | undefined} signal - Lifecycle signal.
|
|
610
|
+
* @returns {void} - No return value.
|
|
611
|
+
*/
|
|
612
|
+
function throwIfAborted(signal) {
|
|
613
|
+
if (!signal?.aborted)
|
|
614
|
+
return;
|
|
615
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Sync offline replay aborted");
|
|
616
|
+
}
|
|
617
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1vZmZsaW5lLXNlc3Npb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc3luYy9zeW5jLW9mZmxpbmUtc2Vzc2lvbi5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxZQUFZO0FBRVosT0FBTyxFQUNMLG9CQUFvQixFQUNwQiwwQkFBMEIsRUFDMUIsc0JBQXNCLEVBQ3RCLHVCQUF1QixFQUN4QixNQUFNLHNCQUFzQixDQUFBO0FBQzdCLE9BQU8sRUFBQyx3QkFBd0IsRUFBRSx3QkFBd0IsRUFBQyxNQUFNLDJCQUEyQixDQUFBO0FBQzVGLE9BQU8sRUFBQyx1QkFBdUIsRUFBQyxNQUFNLHdCQUF3QixDQUFBO0FBRTlEOzs7Ozs7R0FNRztBQUNIOzs7Ozs7OztHQVFHO0FBQ0g7Ozs7Ozs7R0FPRztBQUVIOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxPQUFPLE9BQU8sa0JBQWtCO0lBQ3JDOzs7Ozs7O09BT0c7SUFDSCxZQUFZLEVBQUMsbUJBQW1CLEVBQUUsTUFBTSxFQUFFLFlBQVksRUFBRSxJQUFJLEVBQUM7UUFDM0QsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLElBQUksWUFBWSxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyx1REFBdUQsQ0FBQyxDQUFBO1FBRXZJLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxtQkFBbUIsQ0FBQTtRQUM5QyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsWUFBWSxHQUFHLFlBQVksQ0FBQTtRQUNoQyxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUVoQiwrQ0FBK0M7UUFDL0MsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUE7UUFDckIsMkVBQTJFO1FBQzNFLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQTtRQUN4QixJQUFJLENBQUMsYUFB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SUFDZCxDQUFDO0NBQ0Y7QUFFRDs7Ozs7R0FLRztBQUNILFNBQVMsU0FBUyxDQUFDLEtBQUssRUFBRSxLQUFLO0lBQzdCLElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsS0FBSyxTQUFTLENBQUMsQ0FBQTtJQUV6SCxPQUFPLDREQUE0RCxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUN6RyxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQVMsY0FBYyxDQUFDLE1BQU07SUFDNUIsSUFBSSxDQUFDLE1BQU0sRUFBRSxPQUFPO1FBQUUsT0FBTTtJQUU1QixNQUFNLE1BQU0sQ0FBQyxNQUFNLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyw2QkFBNkIsQ0FBQyxDQUFBO0FBQ2pHLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IHtcbiAgY3JlYXRlU2lnbmVkTXV0YXRpb24sXG4gIGdlbmVyYXRlU3luY1NpZ25pbmdLZXlQYWlyLFxuICBtdXRhdGlvbklkZW1wb3RlbmN5S2V5LFxuICB2ZXJpZnlEZXZpY2VDZXJ0aWZpY2F0ZVxufSBmcm9tIFwiLi9kZXZpY2UtaWRlbnRpdHkuanNcIlxuaW1wb3J0IHtleHBvcnRQZWVyTXV0YXRpb25CdW5kbGUsIGltcG9ydFBlZXJNdXRhdGlvbkJ1bmRsZX0gZnJvbSBcIi4vcGVlci1tdXRhdGlvbi1idW5kbGUuanNcIlxuaW1wb3J0IHtyZXBsYXlSZXN1bHRMb2NhbFN0YXR1c30gZnJvbSBcIi4vY29uZmxpY3Qtc3RyYXRlZ3kuanNcIlxuXG4vKipcbiAqIFN0b3JlZCBkZXZpY2Ugc2lnbmluZyBpZGVudGl0eTogdGhlIGJhY2tlbmQtc2lnbmVkIGNlcnRpZmljYXRlIHBsdXMgdGhlXG4gKiBkZXZpY2UgcHJpdmF0ZSBrZXkgbWF0Y2hpbmcgdGhlIGNlcnRpZmljYXRlJ3MgcHVibGljIGtleS5cbiAqIEB0eXBlZGVmIHtvYmplY3R9IFN5bmNPZmZsaW5lRGV2aWNlSWRlbnRpdHlcbiAqIEBwcm9wZXJ0eSB7aW1wb3J0KFwiLi9kZXZpY2UtaWRlbnRpdHkuanNcIikuRGV2aWNlQ2VydGlmaWNhdGV9IGNlcnRpZmljYXRlIC0gQmFja2VuZC1zaWduZWQgZGV2aWNlIGNlcnRpZmljYXRlLlxuICogQHByb3BlcnR5IHtpbXBvcnQoXCIuL2RldmljZS1pZGVudGl0eS5qc1wiKS5TeW5jSnNvbldlYktleX0gcHJpdmF0ZUtleSAtIERldmljZSBwcml2YXRlIHNpZ25pbmcga2V5IChKV0spLlxuICovXG4vKipcbiAqIFJlc3VsdCBvZiBvbmUgb2ZmbGluZSBzZXNzaW9uIHJlcGxheSBwYXNzIG92ZXIgdGhlIGR1cmFibGUgbXV0YXRpb24gbG9ncy5cbiAqIEB0eXBlZGVmIHtvYmplY3R9IFN5bmNPZmZsaW5lUmVwbGF5UmVzdWx0XG4gKiBAcHJvcGVydHkge251bWJlcn0gY29uZmxpY3RDb3VudCAtIFJlY29yZHMgbGVmdCBpbiBgY29uZmxpY3RgLlxuICogQHByb3BlcnR5IHtudW1iZXJ9IHBlbmRpbmdDb3VudCAtIFJlY29yZHMgc3RpbGwgcGVuZGluZyAoYmxvY2tlZCBkZXBlbmRlbmNpZXMgb3IgdW5hY2tub3dsZWRnZWQpLlxuICogQHByb3BlcnR5IHtudW1iZXJ9IHJlamVjdGVkQ291bnQgLSBSZWNvcmRzIHRoZSBiYWNrZW5kIHJlamVjdGVkLlxuICogQHByb3BlcnR5IHtBcnJheTx7Y2xpZW50TXV0YXRpb25JZDogc3RyaW5nLCBpZGVtcG90ZW5jeUtleTogc3RyaW5nLCBtdXRhdGlvbjogaW1wb3J0KFwiLi9kZXZpY2UtaWRlbnRpdHkuanNcIikuU3luY011dGF0aW9uLCByZWNvcmRJZDogc3RyaW5nLCBzZXJ2ZXJSZXN1bHQ6IFJlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+PiB8IG51bGwsIHN0YXR1czogaW1wb3J0KFwiLi9sb2NhbC1tdXRhdGlvbi1sb2cuanNcIikuTG9jYWxNdXRhdGlvblN0YXR1c30+fSByZXN1bHRzIC0gUGVyLW11dGF0aW9uIG1hcHBlZCByZXN1bHRzIGluIHJlcGxheSBvcmRlci5cbiAqIEBwcm9wZXJ0eSB7bnVtYmVyfSBzeW5jZWRDb3VudCAtIFJlY29yZHMgYWNrbm93bGVkZ2VkIGJ5IHRoZSBiYWNrZW5kLlxuICovXG4vKipcbiAqIFNlc3Npb24gc3VtbWFyeSByZXR1cm5lZCBieSBzdGFydC9yZWZyZXNoLlxuICogQHR5cGVkZWYge29iamVjdH0gU3luY09mZmxpbmVTZXNzaW9uU3VtbWFyeVxuICogQHByb3BlcnR5IHtzdHJpbmd9IGFjdG9yRGV2aWNlSWQgLSBDZXJ0aWZpY2F0ZSBkZXZpY2UgaWRlbnRpdHkuXG4gKiBAcHJvcGVydHkge3N0cmluZ30gYWN0b3JVc2VySWQgLSBDZXJ0aWZpY2F0ZSB1c2VyIGlkZW50aXR5LlxuICogQHByb3BlcnR5IHtzdHJpbmd9IGV4cGlyZXNBdCAtIExpdmUgZ3JhbnQgZXhwaXJ5LlxuICogQHByb3BlcnR5IHtzdHJpbmd9IGdyYW50SWQgLSBMaXZlIGdyYW50IGlkLlxuICovXG5cbi8qKlxuICogRnJhbWV3b3JrLW93bmVkIGxvbmctb2ZmbGluZSBzZXNzaW9uIGJlaGluZCBTeW5jQ2xpZW50OiBkZXZpY2UgaWRlbnRpdHlcbiAqIGxpZmVjeWNsZSwgYmFja2VuZC1pc3N1ZWQgb2ZmbGluZSBncmFudHMsIG11dGF0aW9uIHNpZ25pbmcsIHNpZ25lZCByZXBsYXksXG4gKiBhbmQgcGVlciBidW5kbGUgdHJhbnNmZXIuIFN5bmNDbGllbnQgb3ducyB0aGUgcHVibGljIEFQSTsgdGhpcyBjbGFzcyBvd25zIHRoZVxuICogc2Vzc2lvbiBzdGF0ZS4gVGhlIGFwcCBvbmx5IHByb3ZpZGVzIGR1cmFibGUgc3RvcmFnZSBhZGFwdGVycywgdGhlXG4gKiBlbnJvbGxtZW50IGhvb2ssIGFuZCB0aGUgZ3JhbnQgc2NvcGVzIGNhbGxiYWNrLlxuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTeW5jT2ZmbGluZVNlc3Npb24ge1xuICAvKipcbiAgICogQnVpbGRzIGFuIG9mZmxpbmUgc2Vzc2lvbiBmcm9tIHRoZSBub3JtYWxpemVkIGBzeW5jLmNsaWVudC5vZmZsaW5lYCBibG9jay5cbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBTZXNzaW9uIGFyZ3MuXG4gICAqIEBwYXJhbSB7KCkgPT4gc3RyaW5nIHwgUHJvbWlzZTxzdHJpbmc+fSBhcmdzLmF1dGhlbnRpY2F0aW9uVG9rZW4gLSBSZXNvbHZlcyB0aGUgYXV0aCB0b2tlbiBzZW50IHdpdGggYm9vdHN0cmFwL3JlcGxheSByZXF1ZXN0cy5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLk5vcm1hbGl6ZWRWZWxvY2lvdXNTeW5jQ2xpZW50T2ZmbGluZUNvbmZpZ3VyYXRpb259IGFyZ3MuY29uZmlnIC0gTm9ybWFsaXplZCBvZmZsaW5lIGNvbmZpZ3VyYXRpb24uXG4gICAqIEBwYXJhbSB7QXJyYXk8aW1wb3J0KFwiLi9sb2NhbC1tdXRhdGlvbi1sb2cuanNcIikuZGVmYXVsdD59IGFyZ3MubXV0YXRpb25Mb2dzIC0gRHVyYWJsZSBsb2NhbCBtdXRhdGlvbiBsb2dzIHRoZSBzZXNzaW9uIHNpZ25zLCByZXBsYXlzLCByZXNpZ25zLCBhbmQgZXhwb3J0cy5cbiAgICogQHBhcmFtIHsocGF0aDogc3RyaW5nLCBwYXlsb2FkOiBSZWNvcmQ8c3RyaW5nLCBSZXR1cm5UeXBlPHR5cGVvZiBKU09OLnBhcnNlPj4sIG9wdGlvbnM/OiB7c2lnbmFsPzogQWJvcnRTaWduYWx9KSA9PiBQcm9taXNlPFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Pn0gYXJncy5wb3N0IC0gRnJhbWV3b3JrIFBPU1RlciBvdmVyIHRoZSBjb25maWd1cmVkIHRyYW5zcG9ydC5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHthdXRoZW50aWNhdGlvblRva2VuLCBjb25maWcsIG11dGF0aW9uTG9ncywgcG9zdH0pIHtcbiAgICBpZiAoIUFycmF5LmlzQXJyYXkobXV0YXRpb25Mb2dzKSB8fCBtdXRhdGlvbkxvZ3MubGVuZ3RoID09PSAwKSB0aHJvdyBuZXcgRXJyb3IoXCJTeW5jT2ZmbGluZVNlc3Npb24gcmVxdWlyZXMgYXQgbGVhc3Qgb25lIG11dGF0aW9uIGxvZ1wiKVxuXG4gICAgdGhpcy5hdXRoZW50aWNhdGlvblRva2VuID0gYXV0aGVudGljYXRpb25Ub2tlblxuICAgIHRoaXMuY29uZmlnID0gY29uZmlnXG4gICAgdGhpcy5tdXRhdGlvbkxvZ3MgPSBtdXRhdGlvbkxvZ3NcbiAgICB0aGlzLnBvc3QgPSBwb3N0XG5cbiAgICAvKiogQHR5cGUge1N5bmNPZmZsaW5lRGV2aWNlSWRlbnRpdHkgfCBudWxsfSAqL1xuICAgIHRoaXMuX2lkZW50aXR5ID0gbnVsbFxuICAgIC8qKiBAdHlwZSB7TWFwPHN0cmluZywgaW1wb3J0KFwiLi9vZmZsaW5lLWdyYW50LmpzXCIpLlNpZ25lZE9mZmxpbmVHcmFudD59ICovXG4gICAgdGhpcy5fZ3JhbnRzID0gbmV3IE1hcCgpXG4gICAgdGhpcy5fZ3JhbnRzTG9hZGVkID0gZmFsc2VcbiAgICAvKiogQHR5cGUge2ltcG9ydChcIi4vb2ZmbGluZS1ncmFudC5qc1wiKS5TaWduZWRPZmZsaW5lR3JhbnQgfCBudWxsfSAqL1xuICAgIHRoaXMuX2FjdGl2ZUdyYW50ID0gbnVsbFxuICAgIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgaW1wb3J0KFwiLi4vY29uZmlndXJhdGlvbi10eXBlcy5qc1wiKS5Ob3JtYWxpemVkRnJvbnRlbmRNb2RlbFJlc291cmNlU3luY0NvbmZpZ3VyYXRpb24+IHwgbnVsbH0gKi9cbiAgICB0aGlzLl9zeW5jTWFuaWZlc3QgPSBudWxsXG4gICAgLyoqIEB0eXBlIHtQcm9taXNlPFN5bmNPZmZsaW5lU2Vzc2lvblN1bW1hcnk+IHwgbnVsbH0gKi9cbiAgICB0aGlzLl9zdGFydFByb21pc2UgPSBudWxsXG4gICAgLyoqIEB0eXBlIHtQcm9taXNlPFN5bmNPZmZsaW5lUmVwbGF5UmVzdWx0PiB8IG51bGx9ICovXG4gICAgdGhpcy5fcmVwbGF5UHJvbWlzZSA9IG51bGxcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGV0aGVyIHRoZSBzZXNzaW9uIGN1cnJlbnRseSBoYXMgYSB1c2FibGUgZGV2aWNlIGlkZW50aXR5IGFuZCBsaXZlIGdyYW50LlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSBXaGV0aGVyIHRoZSBzZXNzaW9uIGlzIGFjdGl2ZS5cbiAgICovXG4gIGlzQWN0aXZlKCkge1xuICAgIHJldHVybiBCb29sZWFuKHRoaXMuX2lkZW50aXR5ICYmIHRoaXMuX2xpdmVHcmFudCgpKVxuICB9XG5cbiAgLyoqXG4gICAqIFJlc29sdmVzIHRoZSBkZXZpY2Ugc2lnbmluZyBpZGVudGl0eSAoZ2VuZXJhdGluZyBhbmQgZW5yb2xsaW5nIGEga2V5IHBhaXJcbiAgICogd2hlbiB0aGUgYXBwLW93bmVkIHN0b3JhZ2UgaGFzIG5vbmUpIGFuZCBib290c3RyYXBzIGEgbGl2ZSBvZmZsaW5lIGdyYW50LlxuICAgKiBJZGVtcG90ZW50IGFuZCBzaW5nbGUtZmxpZ2h0ZWQ6IGNvbmN1cnJlbnQgc3RhcnRzIHNoYXJlIG9uZSBib290c3RyYXAuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFN5bmNPZmZsaW5lU2Vzc2lvblN1bW1hcnk+fSAtIFNlc3Npb24gc3VtbWFyeS5cbiAgICovXG4gIGFzeW5jIHN0YXJ0KCkge1xuICAgIGlmICh0aGlzLl9pZGVudGl0eSAmJiB0aGlzLl9saXZlR3JhbnQoKSkgcmV0dXJuIHRoaXMuc2Vzc2lvblN1bW1hcnkoKVxuXG4gICAgaWYgKCF0aGlzLl9zdGFydFByb21pc2UpIHtcbiAgICAgIHRoaXMuX3N0YXJ0UHJvbWlzZSA9IHRoaXMuX3N0YXJ0U2Vzc2lvbigpLnRoZW4oKHN1bW1hcnkpID0+IHtcbiAgICAgICAgdGhpcy5fc3RhcnRQcm9taXNlID0gbnVsbFxuXG4gICAgICAgIHJldHVybiBzdW1tYXJ5XG4gICAgICB9LCAoZXJyb3IpID0+IHtcbiAgICAgICAgdGhpcy5fc3RhcnRQcm9taXNlID0gbnVsbFxuXG4gICAgICAgIHRocm93IGVycm9yXG4gICAgICB9KVxuICAgIH1cblxuICAgIHJldHVybiBhd2FpdCB0aGlzLl9zdGFydFByb21pc2VcbiAgfVxuXG4gIC8qKlxuICAgKiBEcm9wcyB0aGUgaW4tbWVtb3J5IHNlc3Npb24gKGlkZW50aXR5IGFuZCBncmFudHMgc3RheSBpbiB0aGUgYXBwLW93bmVkXG4gICAqIGR1cmFibGUgc3RvcmFnZTsgcGVuZGluZyBtdXRhdGlvbnMgc3RheSBpbiB0aGUgZHVyYWJsZSBtdXRhdGlvbiBsb2dzKS5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgYWZ0ZXIgdGhlIHNlc3Npb24gaXMgZHJvcHBlZC5cbiAgICovXG4gIGFzeW5jIHN0b3AoKSB7XG4gICAgaWYgKHRoaXMuX3JlcGxheVByb21pc2UpIGF3YWl0IHRoaXMuX3JlcGxheVByb21pc2UuY2F0Y2goKCkgPT4gdW5kZWZpbmVkKVxuXG4gICAgdGhpcy5faWRlbnRpdHkgPSBudWxsXG4gICAgdGhpcy5fYWN0aXZlR3JhbnQgPSBudWxsXG4gICAgdGhpcy5fc3luY01hbmlmZXN0ID0gbnVsbFxuICAgIHRoaXMuX2dyYW50c0xvYWRlZCA9IGZhbHNlXG4gIH1cblxuICAvKipcbiAgICogUnVucyBvbmUgc3RhcnQgYXR0ZW1wdDogaWRlbnRpdHkgZmlyc3QsIHRoZW4gZ3JhbnQgYm9vdHN0cmFwLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxTeW5jT2ZmbGluZVNlc3Npb25TdW1tYXJ5Pn0gLSBTZXNzaW9uIHN1bW1hcnkuXG4gICAqL1xuICBhc3luYyBfc3RhcnRTZXNzaW9uKCkge1xuICAgIGF3YWl0IHRoaXMuX2Vuc3VyZUlkZW50aXR5KClcbiAgICBhd2FpdCB0aGlzLl9sb2FkUmV0YWluZWRHcmFudHMoKVxuICAgIGF3YWl0IHRoaXMuX2Vuc3VyZUxpdmVHcmFudCgpXG5cbiAgICByZXR1cm4gdGhpcy5zZXNzaW9uU3VtbWFyeSgpXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgY3VycmVudCBzZXNzaW9uIHN1bW1hcnkuXG4gICAqIEByZXR1cm5zIHtTeW5jT2ZmbGluZVNlc3Npb25TdW1tYXJ5fSAtIFNlc3Npb24gc3VtbWFyeS5cbiAgICovXG4gIHNlc3Npb25TdW1tYXJ5KCkge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gdGhpcy5fcmVxdWlyZUlkZW50aXR5KClcbiAgICBjb25zdCBncmFudCA9IHRoaXMuX3JlcXVpcmVMaXZlR3JhbnQoKVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIGFjdG9yRGV2aWNlSWQ6IGlkZW50aXR5LmNlcnRpZmljYXRlLmNlcnRpZmljYXRlLmFjdG9yRGV2aWNlSWQsXG4gICAgICBhY3RvclVzZXJJZDogaWRlbnRpdHkuY2VydGlmaWNhdGUuY2VydGlmaWNhdGUuYWN0b3JVc2VySWQsXG4gICAgICBleHBpcmVzQXQ6IGdyYW50LmdyYW50LmV4cGlyZXNBdCxcbiAgICAgIGdyYW50SWQ6IGdyYW50LmdyYW50LmdyYW50SWRcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmVzb2x2ZXMgdGhlIGRldmljZSBpZGVudGl0eTogbG9hZCBpdCBmcm9tIHRoZSBhcHAtb3duZWQgc3RvcmFnZSwgb3JcbiAgICogZ2VuZXJhdGUgYSBrZXkgcGFpciwgcnVuIHRoZSBhcHAtb3duZWQgZW5yb2xsbWVudCBob29rLCB2ZXJpZnkgdGhlIHJldHVybmVkXG4gICAqIGNlcnRpZmljYXRlIGFnYWluc3QgdGhlIGNvbmZpZ3VyZWQgYmFja2VuZCBwdWJsaWMga2V5LCBhbmQgc3RvcmUgaXQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFN5bmNPZmZsaW5lRGV2aWNlSWRlbnRpdHk+fSAtIERldmljZSBzaWduaW5nIGlkZW50aXR5LlxuICAgKi9cbiAgYXN5bmMgX2Vuc3VyZUlkZW50aXR5KCkge1xuICAgIGlmICh0aGlzLl9pZGVudGl0eSkgcmV0dXJuIHRoaXMuX2lkZW50aXR5XG5cbiAgICBjb25zdCBzdG9yZWQgPSBhd2FpdCB0aGlzLmNvbmZpZy5kZXZpY2VJZGVudGl0eVN0b3JhZ2UubG9hZCgpXG5cbiAgICBpZiAoc3RvcmVkKSB7XG4gICAgICB0aGlzLl9pZGVudGl0eSA9IGF3YWl0IHRoaXMuX3ZlcmlmaWVkSWRlbnRpdHkoc3RvcmVkKVxuXG4gICAgICByZXR1cm4gdGhpcy5faWRlbnRpdHlcbiAgICB9XG5cbiAgICBjb25zdCBrZXlQYWlyID0gYXdhaXQgZ2VuZXJhdGVTeW5jU2lnbmluZ0tleVBhaXIoKVxuICAgIGNvbnN0IGNlcnRpZmljYXRlID0gYXdhaXQgdGhpcy5jb25maWcuZW5yb2xsRGV2aWNlKHtcbiAgICAgIGNvbmZpZ3VyYXRpb246IHRoaXMuY29uZmlnLmNvbmZpZ3VyYXRpb24sXG4gICAgICBwdWJsaWNLZXk6IGtleVBhaXIucHVibGljS2V5XG4gICAgfSlcbiAgICBjb25zdCBpZGVudGl0eSA9IGF3YWl0IHRoaXMuX3ZlcmlmaWVkSWRlbnRpdHkoe2NlcnRpZmljYXRlLCBwcml2YXRlS2V5OiBrZXlQYWlyLnByaXZhdGVLZXl9KVxuXG4gICAgYXdhaXQgdGhpcy5jb25maWcuZGV2aWNlSWRlbnRpdHlTdG9yYWdlLnNhdmUoLyoqIEB0eXBlIHtTeW5jT2ZmbGluZURldmljZUlkZW50aXR5fSAqLyAoY2xvbmVKc29uKGlkZW50aXR5LCBcImRldmljZSBpZGVudGl0eVwiKSkpXG5cbiAgICB0aGlzLl9pZGVudGl0eSA9IGlkZW50aXR5XG5cbiAgICByZXR1cm4gaWRlbnRpdHlcbiAgfVxuXG4gIC8qKlxuICAgKiBWZXJpZmllcyBhIHN0b3JlZC9lbnJvbGxlZCBjZXJ0aWZpY2F0ZSBhZ2FpbnN0IHRoZSBjb25maWd1cmVkIGJhY2tlbmRcbiAgICogcHVibGljIGtleSAod2hlbiBvbmUgaXMgY29uZmlndXJlZCkgYW5kIHJldHVybnMgdGhlIGlkZW50aXR5LlxuICAgKiBAcGFyYW0ge1N5bmNPZmZsaW5lRGV2aWNlSWRlbnRpdHl9IGlkZW50aXR5IC0gQ2FuZGlkYXRlIGlkZW50aXR5LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxTeW5jT2ZmbGluZURldmljZUlkZW50aXR5Pn0gLSBWZXJpZmllZCBpZGVudGl0eS5cbiAgICovXG4gIGFzeW5jIF92ZXJpZmllZElkZW50aXR5KGlkZW50aXR5KSB7XG4gICAgaWYgKCFpZGVudGl0eSB8fCB0eXBlb2YgaWRlbnRpdHkgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheShpZGVudGl0eSkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcInN5bmMuY2xpZW50Lm9mZmxpbmUgZGV2aWNlIGlkZW50aXR5IHN0b3JhZ2UgbXVzdCBwcm92aWRlIHtjZXJ0aWZpY2F0ZSwgcHJpdmF0ZUtleX0gb3IgbnVsbFwiKVxuICAgIH1cbiAgICBpZiAoIWlkZW50aXR5LmNlcnRpZmljYXRlIHx8ICFpZGVudGl0eS5wcml2YXRlS2V5KSB0aHJvdyBuZXcgRXJyb3IoXCJzeW5jLmNsaWVudC5vZmZsaW5lIGRldmljZSBpZGVudGl0eSByZXF1aXJlcyBhIGNlcnRpZmljYXRlIGFuZCBhIHByaXZhdGVLZXlcIilcblxuICAgIGlmICh0aGlzLmNvbmZpZy5iYWNrZW5kUHVibGljS2V5KSB7XG4gICAgICBhd2FpdCB2ZXJpZnlEZXZpY2VDZXJ0aWZpY2F0ZSh7XG4gICAgICAgIGJhY2tlbmRQdWJsaWNLZXk6IHRoaXMuY29uZmlnLmJhY2tlbmRQdWJsaWNLZXksXG4gICAgICAgIGNlcnRpZmljYXRlOiBpZGVudGl0eS5jZXJ0aWZpY2F0ZVxuICAgICAgfSlcbiAgICB9XG5cbiAgICByZXR1cm4gaWRlbnRpdHlcbiAgfVxuXG4gIC8qKlxuICAgKiBMb2FkcyBhcHAtcmV0YWluZWQgZ3JhbnRzIG9uY2Ugc28gYSByZXN0YXJ0ZWQgZGV2aWNlIGNhbiBzdGlsbCByZXBsYXlcbiAgICogbXV0YXRpb25zIHRoYXQgd2VyZSBzaWduZWQgdW5kZXIgYW4gb2xkZXIgZ3JhbnQgKHBlZXItZm9yd2FyZGVkIG11dGF0aW9uc1xuICAgKiBrZWVwIHRoZSBncmFudCB0aGVpciBvcmlnaW5hbCBzaWduZXIgdXNlZCkuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIHJldGVudGlvbiBpcyBsb2FkZWQuXG4gICAqL1xuICBhc3luYyBfbG9hZFJldGFpbmVkR3JhbnRzKCkge1xuICAgIGlmICh0aGlzLl9ncmFudHNMb2FkZWQpIHJldHVyblxuXG4gICAgdGhpcy5fZ3JhbnRzTG9hZGVkID0gdHJ1ZVxuICAgIGlmICghdGhpcy5jb25maWcuZ3JhbnRTdG9yYWdlKSByZXR1cm5cblxuICAgIGNvbnN0IHJldGFpbmVkID0gYXdhaXQgdGhpcy5jb25maWcuZ3JhbnRTdG9yYWdlLmxvYWQoKVxuXG4gICAgaWYgKCFBcnJheS5pc0FycmF5KHJldGFpbmVkKSkgdGhyb3cgbmV3IEVycm9yKFwic3luYy5jbGllbnQub2ZmbGluZSBncmFudFN0b3JhZ2UubG9hZCBtdXN0IHJlc29sdmUgYW4gYXJyYXkgb2Ygc2lnbmVkIG9mZmxpbmUgZ3JhbnRzXCIpXG5cbiAgICBmb3IgKGNvbnN0IHNpZ25lZEdyYW50IG9mIHJldGFpbmVkKSB7XG4gICAgICBpZiAoIXNpZ25lZEdyYW50IHx8IHR5cGVvZiBzaWduZWRHcmFudCAhPT0gXCJvYmplY3RcIiB8fCAhc2lnbmVkR3JhbnQuZ3JhbnQgfHwgdHlwZW9mIHNpZ25lZEdyYW50LmdyYW50LmdyYW50SWQgIT09IFwic3RyaW5nXCIpIGNvbnRpbnVlXG5cbiAgICAgIHRoaXMuX2dyYW50cy5zZXQoc2lnbmVkR3JhbnQuZ3JhbnQuZ3JhbnRJZCwgc2lnbmVkR3JhbnQpXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGFjdGl2ZSBncmFudCB3aGVuIGl0IGlzIHN0aWxsIGxpdmUsIG90aGVyd2lzZSBudWxsLiBFeHBpcnkgaXNcbiAgICogY2hlY2tlZCBieSB0aW1lc3RhbXAgb25seTogdGhlIGdyYW50J3MgSE1BQyBzaWduYXR1cmUgaXMgYmFja2VuZC1vbmx5IGFuZFxuICAgKiBjYW4gbmV2ZXIgYmUgdmVyaWZpZWQgb24gdGhlIGRldmljZS5cbiAgICogQHBhcmFtIHtEYXRlfSBbbm93XSAtIENoZWNrIHRpbWUuXG4gICAqIEByZXR1cm5zIHtpbXBvcnQoXCIuL29mZmxpbmUtZ3JhbnQuanNcIikuU2lnbmVkT2ZmbGluZUdyYW50IHwgbnVsbH0gLSBMaXZlIGdyYW50IG9yIG51bGwuXG4gICAqL1xuICBfbGl2ZUdyYW50KG5vdyA9IHRoaXMuX25vdygpKSB7XG4gICAgaWYgKCF0aGlzLl9hY3RpdmVHcmFudCkgcmV0dXJuIG51bGxcbiAgICBpZiAobmV3IERhdGUodGhpcy5fYWN0aXZlR3JhbnQuZ3JhbnQuZXhwaXJlc0F0KS5nZXRUaW1lKCkgPD0gbm93LmdldFRpbWUoKSkgcmV0dXJuIG51bGxcblxuICAgIHJldHVybiB0aGlzLl9hY3RpdmVHcmFudFxuICB9XG5cbiAgLyoqXG4gICAqIEVuc3VyZXMgYSBsaXZlIGdyYW50IGV4aXN0cywgYm9vdHN0cmFwcGluZyBhIG5ldyBvbmUgd2hlbiB0aGUgY3VycmVudCBncmFudFxuICAgKiBpcyBtaXNzaW5nIG9yIGV4cGlyZWQuIEEgcmVmcmVzaGVkIGdyYW50IHJlLXNpZ25zIHRoaXMgZGV2aWNlJ3MgcGVuZGluZ1xuICAgKiByZWNvcmRzIHNvIHRoZSBiYWNrZW5kIGFsd2F5cyBzZWVzIHRoZSBncmFudCBhbiBpbnRlbnQgaXMgcmVwbGF5ZWQgdW5kZXIuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPGltcG9ydChcIi4vb2ZmbGluZS1ncmFudC5qc1wiKS5TaWduZWRPZmZsaW5lR3JhbnQ+fSAtIExpdmUgZ3JhbnQuXG4gICAqL1xuICBhc3luYyBfZW5zdXJlTGl2ZUdyYW50KCkge1xuICAgIGNvbnN0IGxpdmUgPSB0aGlzLl9saXZlR3JhbnQoKVxuXG4gICAgaWYgKGxpdmUpIHJldHVybiBsaXZlXG5cbiAgICBjb25zdCBncmFudGVkID0gYXdhaXQgdGhpcy5ib290c3RyYXBHcmFudCgpXG5cbiAgICBhd2FpdCB0aGlzLl9yZXNpZ25QZW5kaW5nUmVjb3JkcyhncmFudGVkKVxuXG4gICAgcmV0dXJuIGdyYW50ZWRcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXF1ZXN0cyBhIGZyZXNoIG9mZmxpbmUgZ3JhbnQgZnJvbSB0aGUgYnVpbHQtaW4gc3luYyBib290c3RyYXAgZW5kcG9pbnRcbiAgICogYW5kIHJldGFpbnMgaXQgdW5kZXIgaXRzIGdyYW50IGlkIHRvZ2V0aGVyIHdpdGggdGhlIHN5bmMgbWFuaWZlc3Qgd2hvc2VcbiAgICogcG9saWN5IGhhc2hlcyBtdXRhdGlvbnMgYXJlIHNpZ25lZCBhZ2FpbnN0LlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCIuL29mZmxpbmUtZ3JhbnQuanNcIikuU2lnbmVkT2ZmbGluZUdyYW50Pn0gLSBOZXdseSBpc3N1ZWQgZ3JhbnQuXG4gICAqL1xuICBhc3luYyBib290c3RyYXBHcmFudCgpIHtcbiAgICBjb25zdCBpZGVudGl0eSA9IGF3YWl0IHRoaXMuX2Vuc3VyZUlkZW50aXR5KClcbiAgICBjb25zdCBhdXRoZW50aWNhdGlvblRva2VuID0gYXdhaXQgdGhpcy5hdXRoZW50aWNhdGlvblRva2VuKClcbiAgICBjb25zdCBzY29wZXMgPSBhd2FpdCB0aGlzLmNvbmZpZy5ncmFudFNjb3Blcyh7Y29uZmlndXJhdGlvbjogdGhpcy5jb25maWcuY29uZmlndXJhdGlvbn0pXG5cbiAgICBpZiAoIXNjb3BlcyB8fCB0eXBlb2Ygc2NvcGVzICE9PSBcIm9iamVjdFwiIHx8IEFycmF5LmlzQXJyYXkoc2NvcGVzKSkgdGhyb3cgbmV3IEVycm9yKFwic3luYy5jbGllbnQub2ZmbGluZS5ncmFudFNjb3BlcyBtdXN0IHJlc29sdmUgYSBwbGFpbiBvYmplY3Qgb2YgZ3JhbnQgc2NvcGVzXCIpXG5cbiAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRoaXMucG9zdCh0aGlzLmNvbmZpZy5ib290c3RyYXBQYXRoLCB7XG4gICAgICBhdXRoZW50aWNhdGlvblRva2VuLFxuICAgICAgZGV2aWNlSWQ6IGlkZW50aXR5LmNlcnRpZmljYXRlLmNlcnRpZmljYXRlLmFjdG9yRGV2aWNlSWQsXG4gICAgICBzY29wZXNcbiAgICB9KVxuXG4gICAgaWYgKCFyZXNwb25zZSB8fCB0eXBlb2YgcmVzcG9uc2UgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheShyZXNwb25zZSkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgU3luYyBvZmZsaW5lIGJvb3RzdHJhcCBkaWQgbm90IHJldHVybiBhIHJlc3BvbnNlIG9iamVjdCAoJHt0aGlzLmNvbmZpZy5ib290c3RyYXBQYXRofSlgKVxuICAgIH1cblxuICAgIGNvbnN0IHJlc3BvbnNlUmVjb3JkID0gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPn0gKi8gKHJlc3BvbnNlKVxuICAgIGNvbnN0IHNpZ25lZEdyYW50ID0gLyoqIEB0eXBlIHtpbXBvcnQoXCIuL29mZmxpbmUtZ3JhbnQuanNcIikuU2lnbmVkT2ZmbGluZUdyYW50IHwgdW5kZWZpbmVkfSAqLyAocmVzcG9uc2VSZWNvcmQub2ZmbGluZUdyYW50KVxuXG4gICAgaWYgKCFzaWduZWRHcmFudCB8fCB0eXBlb2Ygc2lnbmVkR3JhbnQgIT09IFwib2JqZWN0XCIgfHwgIXNpZ25lZEdyYW50LmdyYW50IHx8IHR5cGVvZiBzaWduZWRHcmFudC5ncmFudC5ncmFudElkICE9PSBcInN0cmluZ1wiKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFN5bmMgb2ZmbGluZSBib290c3RyYXAgZGlkIG5vdCByZXR1cm4gYSBzaWduZWQgb2ZmbGluZSBncmFudCAoJHt0aGlzLmNvbmZpZy5ib290c3RyYXBQYXRofSlgKVxuICAgIH1cblxuICAgIGNvbnN0IG1hbmlmZXN0ID0gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCBpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLXR5cGVzLmpzXCIpLk5vcm1hbGl6ZWRGcm9udGVuZE1vZGVsUmVzb3VyY2VTeW5jQ29uZmlndXJhdGlvbj4gfCB1bmRlZmluZWR9ICovIChyZXNwb25zZVJlY29yZC5zeW5jTWFuaWZlc3QpXG5cbiAgICBpZiAoIW1hbmlmZXN0IHx8IHR5cGVvZiBtYW5pZmVzdCAhPT0gXCJvYmplY3RcIiB8fCBBcnJheS5pc0FycmF5KG1hbmlmZXN0KSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBTeW5jIG9mZmxpbmUgYm9vdHN0cmFwIGRpZCBub3QgcmV0dXJuIGEgc3luYyBtYW5pZmVzdCAoJHt0aGlzLmNvbmZpZy5ib290c3RyYXBQYXRofSlgKVxuICAgIH1cblxuICAgIHRoaXMuX2dyYW50cy5zZXQoc2lnbmVkR3JhbnQuZ3JhbnQuZ3JhbnRJZCwgc2lnbmVkR3JhbnQpXG4gICAgdGhpcy5fYWN0aXZlR3JhbnQgPSBzaWduZWRHcmFudFxuICAgIHRoaXMuX3N5bmNNYW5pZmVzdCA9IG1hbmlmZXN0XG4gICAgYXdhaXQgdGhpcy5fcGVyc2lzdEdyYW50cygpXG5cbiAgICByZXR1cm4gc2lnbmVkR3JhbnRcbiAgfVxuXG4gIC8qKlxuICAgKiBGb3JjZXMgYSBncmFudCByZWZyZXNoOiBib290c3RyYXAgYSBuZXcgZ3JhbnQgYW5kIHJlLXNpZ24gdGhpcyBkZXZpY2Unc1xuICAgKiBwZW5kaW5nIHJlY29yZHMgdW5kZXIgaXQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFN5bmNPZmZsaW5lU2Vzc2lvblN1bW1hcnk+fSAtIFJlZnJlc2hlZCBzZXNzaW9uIHN1bW1hcnkuXG4gICAqL1xuICBhc3luYyByZWZyZXNoR3JhbnQoKSB7XG4gICAgYXdhaXQgdGhpcy5fZW5zdXJlSWRlbnRpdHkoKVxuICAgIGF3YWl0IHRoaXMuX2xvYWRSZXRhaW5lZEdyYW50cygpXG5cbiAgICBjb25zdCBncmFudGVkID0gYXdhaXQgdGhpcy5ib290c3RyYXBHcmFudCgpXG5cbiAgICBhd2FpdCB0aGlzLl9yZXNpZ25QZW5kaW5nUmVjb3JkcyhncmFudGVkKVxuXG4gICAgcmV0dXJuIHRoaXMuc2Vzc2lvblN1bW1hcnkoKVxuICB9XG5cbiAgLyoqXG4gICAqIFJlLXNpZ25zIHRoaXMgZGV2aWNlJ3Mgc3RpbGwtcGVuZGluZyByZWNvcmRzIHVuZGVyIGEgbmV3IGdyYW50LiBTYWZlXG4gICAqIGJlY2F1c2UgdGhlIHJlcGxheSBpZGVtcG90ZW5jeSBrZXkgaXNcbiAgICogYGFjdG9yVXNlcklkOmFjdG9yRGV2aWNlSWQ6Y2xpZW50TXV0YXRpb25JZGAgYW5kIG5ldmVyIGNhcnJpZXMgdGhlIGdyYW50XG4gICAqIGlkLiBSZWNvcmRzIHNpZ25lZCBieSBhbm90aGVyIGRldmljZSAocGVlci1mb3J3YXJkZWQgbXV0YXRpb25zKSBrZWVwIHRoZWlyXG4gICAqIG9yaWdpbmFsIHNpZ25lZCBlbnZlbG9wZSBhbmQgb3JpZ2luYWwgZ3JhbnQgLSB0aGV5IG11c3QgYWx3YXlzIHJlcGxheSB3aXRoXG4gICAqIHRoZSBncmFudCB0aGVpciBzaWduZXIgdXNlZC5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuL29mZmxpbmUtZ3JhbnQuanNcIikuU2lnbmVkT2ZmbGluZUdyYW50fSBzaWduZWRHcmFudCAtIEdyYW50IHRoZSByZWNvcmRzIGFyZSByZS1zaWduZWQgdW5kZXIuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIGFmdGVyIHBlbmRpbmcgcmVjb3JkcyBhcmUgcmUtc2lnbmVkLlxuICAgKi9cbiAgYXN5bmMgX3Jlc2lnblBlbmRpbmdSZWNvcmRzKHNpZ25lZEdyYW50KSB7XG4gICAgdGhpcy5fcmVxdWlyZUlkZW50aXR5KClcblxuICAgIGZvciAoY29uc3QgbXV0YXRpb25Mb2cgb2YgdGhpcy5tdXRhdGlvbkxvZ3MpIHtcbiAgICAgIGZvciAoY29uc3QgcmVjb3JkIG9mIGF3YWl0IG11dGF0aW9uTG9nLnBlbmRpbmdSZWNvcmRzKCkpIHtcbiAgICAgICAgaWYgKCF0aGlzLl9pc093blJlY29yZChyZWNvcmQpKSBjb250aW51ZVxuICAgICAgICBpZiAocmVjb3JkLm11dGF0aW9uLm9mZmxpbmVHcmFudElkID09PSBzaWduZWRHcmFudC5ncmFudC5ncmFudElkICYmIHJlY29yZC5zaWduZWRNdXRhdGlvbikgY29udGludWVcblxuICAgICAgICBjb25zdCBzaWduZWRNdXRhdGlvbiA9IGF3YWl0IHRoaXMuc2lnbk11dGF0aW9uKHJlY29yZC5tdXRhdGlvbilcblxuICAgICAgICBhd2FpdCBtdXRhdGlvbkxvZy51cGRhdGVNdXRhdGlvbih7aWQ6IHJlY29yZC5pZCwgbXV0YXRpb246IHNpZ25lZE11dGF0aW9uLm11dGF0aW9uLCBzaWduZWRNdXRhdGlvbn0pXG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrcyB3aGV0aGVyIGEgbG9nIHJlY29yZCB3YXMgc2lnbmVkL3F1ZXVlZCBieSB0aGlzIGRldmljZSdzIGlkZW50aXR5XG4gICAqIChvciBoYXMgbm8gaWRlbnRpdHkgeWV0IGFuZCBjYW4gYWRvcHQgaXQpLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vbG9jYWwtbXV0YXRpb24tbG9nLmpzXCIpLkxvY2FsTXV0YXRpb25Mb2dSZWNvcmR9IHJlY29yZCAtIER1cmFibGUgcmVjb3JkLlxuICAgKiBAcmV0dXJucyB7Ym9vbGVhbn0gLSBXaGV0aGVyIHRoZSByZWNvcmQgYmVsb25ncyB0byB0aGlzIGRldmljZS5cbiAgICovXG4gIF9pc093blJlY29yZChyZWNvcmQpIHtcbiAgICBjb25zdCBpZGVudGl0eSA9IHRoaXMuX3JlcXVpcmVJZGVudGl0eSgpXG5cbiAgICBpZiAocmVjb3JkLnNpZ25lZE11dGF0aW9uKSB7XG4gICAgICByZXR1cm4gcmVjb3JkLnNpZ25lZE11dGF0aW9uLm11dGF0aW9uLmFjdG9yRGV2aWNlSWQgPT09IGlkZW50aXR5LmNlcnRpZmljYXRlLmNlcnRpZmljYXRlLmFjdG9yRGV2aWNlSWQgJiZcbiAgICAgICAgcmVjb3JkLnNpZ25lZE11dGF0aW9uLm11dGF0aW9uLmFjdG9yVXNlcklkID09PSBpZGVudGl0eS5jZXJ0aWZpY2F0ZS5jZXJ0aWZpY2F0ZS5hY3RvclVzZXJJZFxuICAgIH1cblxuICAgIHJldHVybiAhcmVjb3JkLm11dGF0aW9uLmFjdG9yRGV2aWNlSWQgfHwgKFxuICAgICAgcmVjb3JkLm11dGF0aW9uLmFjdG9yRGV2aWNlSWQgPT09IGlkZW50aXR5LmNlcnRpZmljYXRlLmNlcnRpZmljYXRlLmFjdG9yRGV2aWNlSWQgJiZcbiAgICAgIHJlY29yZC5tdXRhdGlvbi5hY3RvclVzZXJJZCA9PT0gaWRlbnRpdHkuY2VydGlmaWNhdGUuY2VydGlmaWNhdGUuYWN0b3JVc2VySWRcbiAgICApXG4gIH1cblxuICAvKipcbiAgICogU2lnbnMgb25lIG11dGF0aW9uIHBheWxvYWQgd2l0aCB0aGUgZGV2aWNlIGtleSB1bmRlciB0aGUgbGl2ZSBncmFudCxcbiAgICogZmlsbGluZyB0aGUgYWN0b3IvZ3JhbnQvcG9saWN5LWhhc2ggaWRlbnRpdHkgZnJvbSB0aGUgc2Vzc2lvbi5cbiAgICogQHBhcmFtIHtPbWl0PGltcG9ydChcIi4vZGV2aWNlLWlkZW50aXR5LmpzXCIpLlN5bmNNdXRhdGlvbiwgXCJhY3RvckRldmljZUlkXCIgfCBcImFjdG9yVXNlcklkXCIgfCBcIm9mZmxpbmVHcmFudElkXCIgfCBcInBvbGljeUhhc2hcIj59IG11dGF0aW9uIC0gTXV0YXRpb24gcGF5bG9hZCB0byBzaWduICh0aGUgc2Vzc2lvbiBzdXBwbGllcyB0aGUgcm90YXRpbmcgaWRlbnRpdHkgZmllbGRzKS5cbiAgICogQHJldHVybnMge1Byb21pc2U8aW1wb3J0KFwiLi9kZXZpY2UtaWRlbnRpdHkuanNcIikuU2lnbmVkU3luY011dGF0aW9uPn0gLSBTaWduZWQgbXV0YXRpb24gZW52ZWxvcGUuXG4gICAqL1xuICBhc3luYyBzaWduTXV0YXRpb24obXV0YXRpb24pIHtcbiAgICBjb25zdCBpZGVudGl0eSA9IGF3YWl0IHRoaXMuX2Vuc3VyZUlkZW50aXR5KClcbiAgICBjb25zdCBncmFudCA9IGF3YWl0IHRoaXMuX2dyYW50Rm9yU2lnbmluZygpXG5cbiAgICByZXR1cm4gYXdhaXQgY3JlYXRlU2lnbmVkTXV0YXRpb24oe1xuICAgICAgZGV2aWNlQ2VydGlmaWNhdGU6IGlkZW50aXR5LmNlcnRpZmljYXRlLFxuICAgICAgZGV2aWNlUHJpdmF0ZUtleTogaWRlbnRpdHkucHJpdmF0ZUtleSxcbiAgICAgIG11dGF0aW9uOiB7XG4gICAgICAgIC4uLm11dGF0aW9uLFxuICAgICAgICBhY3RvckRldmljZUlkOiBpZGVudGl0eS5jZXJ0aWZpY2F0ZS5jZXJ0aWZpY2F0ZS5hY3RvckRldmljZUlkLFxuICAgICAgICBhY3RvclVzZXJJZDogaWRlbnRpdHkuY2VydGlmaWNhdGUuY2VydGlmaWNhdGUuYWN0b3JVc2VySWQsXG4gICAgICAgIG9mZmxpbmVHcmFudElkOiBncmFudC5ncmFudC5ncmFudElkLFxuICAgICAgICBwb2xpY3lIYXNoOiB0aGlzLnBvbGljeUhhc2hGb3IobXV0YXRpb24ubW9kZWwpXG4gICAgICB9XG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBncmFudCBhIG5ldyBtdXRhdGlvbiBpcyBzaWduZWQgdW5kZXI6IHRoZSBsaXZlIGdyYW50IHdoZW4gb25lXG4gICAqIGV4aXN0cywgb3RoZXJ3aXNlIHRoZSBuZXdlc3QgcmV0YWluZWQgZ3JhbnQgKHF1ZXVlaW5nIHN0YXlzIHBvc3NpYmxlIHdoaWxlXG4gICAqIG9mZmxpbmUgLSB0aGUgcmVwbGF5IHBhc3MgcmVmcmVzaGVzIHRoZSBncmFudCBhbmQgcmUtc2lnbnMgdGhlIHBlbmRpbmdcbiAgICogcmVjb3JkcyksIGFuZCBvbmx5IGJvb3RzdHJhcHMgd2hlbiBubyBncmFudCBoYXMgZXZlciBiZWVuIHJldGFpbmVkLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCIuL29mZmxpbmUtZ3JhbnQuanNcIikuU2lnbmVkT2ZmbGluZUdyYW50Pn0gLSBHcmFudCB0byBzaWduIHVuZGVyLlxuICAgKi9cbiAgYXN5bmMgX2dyYW50Rm9yU2lnbmluZygpIHtcbiAgICBjb25zdCBsaXZlID0gdGhpcy5fbGl2ZUdyYW50KClcblxuICAgIGlmIChsaXZlKSByZXR1cm4gbGl2ZVxuICAgIGlmICh0aGlzLl9hY3RpdmVHcmFudCkgcmV0dXJuIHRoaXMuX2FjdGl2ZUdyYW50XG5cbiAgICBhd2FpdCB0aGlzLl9sb2FkUmV0YWluZWRHcmFudHMoKVxuXG4gICAgY29uc3QgcmV0YWluZWQgPSBBcnJheS5mcm9tKHRoaXMuX2dyYW50cy52YWx1ZXMoKSkuc29ydCgobGVmdCwgcmlnaHQpID0+IG5ldyBEYXRlKHJpZ2h0LmdyYW50Lmlzc3VlZEF0KS5nZXRUaW1lKCkgLSBuZXcgRGF0ZShsZWZ0LmdyYW50Lmlzc3VlZEF0KS5nZXRUaW1lKCkpXG5cbiAgICBpZiAocmV0YWluZWQubGVuZ3RoID4gMCkge1xuICAgICAgdGhpcy5fYWN0aXZlR3JhbnQgPSByZXRhaW5lZFswXVxuXG4gICAgICByZXR1cm4gdGhpcy5fYWN0aXZlR3JhbnRcbiAgICB9XG5cbiAgICByZXR1cm4gYXdhaXQgdGhpcy5ib290c3RyYXBHcmFudCgpXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgcG9saWN5IGhhc2ggdGhlIGxpdmUgc2Vzc2lvbidzIHN5bmMgbWFuaWZlc3QgY2FycmllcyBmb3Igb25lIG1vZGVsLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gbW9kZWwgLSBNb2RlbC9yZXNvdXJjZSBuYW1lLlxuICAgKiBAcmV0dXJucyB7c3RyaW5nfSAtIFBvbGljeSBoYXNoLlxuICAgKi9cbiAgcG9saWN5SGFzaEZvcihtb2RlbCkge1xuICAgIGNvbnN0IG1hbmlmZXN0ID0gdGhpcy5fc3luY01hbmlmZXN0IHx8IHRoaXMuX2FjdGl2ZUdyYW50Py5ncmFudC5yZXNvdXJjZXNcblxuICAgIGlmICghbWFuaWZlc3QpIHRocm93IG5ldyBFcnJvcihcIlN5bmMgb2ZmbGluZSBzZXNzaW9uIGhhcyBubyBzeW5jIG1hbmlmZXN0IC0gY2FsbCBzdGFydE9mZmxpbmVTZXNzaW9uKCkgZmlyc3RcIilcblxuICAgIGNvbnN0IGVudHJ5ID0gLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IHVuZGVmaW5lZH0gKi8gKG1hbmlmZXN0W21vZGVsXSlcbiAgICBjb25zdCBwb2xpY3lIYXNoID0gZW50cnkgJiYgdHlwZW9mIGVudHJ5ID09PSBcIm9iamVjdFwiICYmICFBcnJheS5pc0FycmF5KGVudHJ5KSA/IC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgdW5rbm93bj59ICovIChlbnRyeSkucG9saWN5SGFzaCA6IHVuZGVmaW5lZFxuXG4gICAgaWYgKHR5cGVvZiBwb2xpY3lIYXNoICE9PSBcInN0cmluZ1wiIHx8IHBvbGljeUhhc2gubGVuZ3RoID09PSAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFN5bmMgb2ZmbGluZSBzZXNzaW9uIGhhcyBubyBwb2xpY3kgaGFzaCBmb3IgbW9kZWwgJyR7bW9kZWx9JyAtIGlzIGl0IHN5bmMtZW5hYmxlZCBvbiB0aGUgYmFja2VuZD9gKVxuICAgIH1cblxuICAgIHJldHVybiBwb2xpY3lIYXNoXG4gIH1cblxuICAvKipcbiAgICogRHJhaW5zIHRoZSBkdXJhYmxlIG11dGF0aW9uIGxvZ3MgdGhyb3VnaCB0aGUgYnVpbHQtaW4gc2lnbmVkIHJlcGxheVxuICAgKiBlbmRwb2ludC4gUGVuZGluZyByZWNvcmRzIGFyZSBiYXRjaGVkLCBlYWNoIHNpZ25lZCBtdXRhdGlvbiB0cmF2ZWxzIHdpdGhcbiAgICogdGhlIHNpZ25lZCBncmFudCBpdCB3YXMgc2lnbmVkIHVuZGVyLCBhbmQgcGVyLW11dGF0aW9uIHJlc3VsdHMgYXJlIG1hcHBlZFxuICAgKiBiYWNrIG9udG8gdGhlIGxvZyByZWNvcmRzIGFzIHN5bmNlZC9jb25mbGljdC9yZWplY3RlZC5cbiAgICogQHBhcmFtIHtvYmplY3R9IFtvcHRpb25zXSAtIFJlcGxheSBvcHRpb25zLlxuICAgKiBAcGFyYW0ge251bWJlcn0gW29wdGlvbnMuYmF0Y2hTaXplXSAtIE1heCBtdXRhdGlvbnMgcGVyIHJlcXVlc3QuXG4gICAqIEBwYXJhbSB7QWJvcnRTaWduYWx9IFtvcHRpb25zLnNpZ25hbF0gLSBMaWZlY3ljbGUgY2FuY2VsbGF0aW9uIHNpZ25hbC5cbiAgICogQHJldHVybnMge1Byb21pc2U8U3luY09mZmxpbmVSZXBsYXlSZXN1bHQ+fSAtIE1hcHBlZCByZXBsYXkgcmVzdWx0LlxuICAgKi9cbiAgYXN5bmMgcmVwbGF5UGVuZGluZyhvcHRpb25zID0ge30pIHtcbiAgICBpZiAodGhpcy5fcmVwbGF5UHJvbWlzZSkgcmV0dXJuIGF3YWl0IHRoaXMuX3JlcGxheVByb21pc2VcblxuICAgIHRoaXMuX3JlcGxheVByb21pc2UgPSB0aGlzLl9yZXBsYXlQYXNzKG9wdGlvbnMpLmZpbmFsbHkoKCkgPT4ge1xuICAgICAgdGhpcy5fcmVwbGF5UHJvbWlzZSA9IG51bGxcbiAgICB9KVxuXG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuX3JlcGxheVByb21pc2VcbiAgfVxuXG4gIC8qKlxuICAgKiBSdW5zIG9uZSBzaWduZWQgcmVwbGF5IHBhc3MuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBvcHRpb25zIC0gUGFzcyBvcHRpb25zLlxuICAgKiBAcGFyYW0ge251bWJlcn0gW29wdGlvbnMuYmF0Y2hTaXplXSAtIE1heCBtdXRhdGlvbnMgcGVyIHJlcXVlc3QuXG4gICAqIEBwYXJhbSB7QWJvcnRTaWduYWx9IFtvcHRpb25zLnNpZ25hbF0gLSBMaWZlY3ljbGUgY2FuY2VsbGF0aW9uIHNpZ25hbC5cbiAgICogQHJldHVybnMge1Byb21pc2U8U3luY09mZmxpbmVSZXBsYXlSZXN1bHQ+fSAtIE1hcHBlZCByZXBsYXkgcmVzdWx0LlxuICAgKi9cbiAgYXN5bmMgX3JlcGxheVBhc3Moe2JhdGNoU2l6ZSwgc2lnbmFsfSA9IHt9KSB7XG4gICAgdGhyb3dJZkFib3J0ZWQoc2lnbmFsKVxuXG4gICAgaWYgKCF0aGlzLmlzQWN0aXZlKCkpIGF3YWl0IHRoaXMuc3RhcnQoKVxuXG4gICAgY29uc3QgbWF4QmF0Y2hTaXplID0gdHlwZW9mIGJhdGNoU2l6ZSA9PT0gXCJudW1iZXJcIiAmJiBiYXRjaFNpemUgPj0gMSA/IE1hdGguZmxvb3IoYmF0Y2hTaXplKSA6IDEwMFxuICAgIC8qKiBAdHlwZSB7U3luY09mZmxpbmVSZXBsYXlSZXN1bHRbXCJyZXN1bHRzXCJdfSAqL1xuICAgIGNvbnN0IHJlc3VsdHMgPSBbXVxuICAgIGxldCBzeW5jZWRDb3VudCA9IDBcbiAgICBsZXQgY29uZmxpY3RDb3VudCA9IDBcbiAgICBsZXQgcmVqZWN0ZWRDb3VudCA9IDBcblxuICAgIGZvciAobGV0IHBhc3MgPSAwOyBwYXNzIDwgMTA7IHBhc3MgKz0gMSkge1xuICAgICAgdGhyb3dJZkFib3J0ZWQoc2lnbmFsKVxuICAgICAgYXdhaXQgdGhpcy5fZW5zdXJlTGl2ZUdyYW50KClcblxuICAgICAgLyoqIEB0eXBlIHtBcnJheTx7bXV0YXRpb25Mb2c6IGltcG9ydChcIi4vbG9jYWwtbXV0YXRpb24tbG9nLmpzXCIpLmRlZmF1bHQsIHJlY29yZDogaW1wb3J0KFwiLi9sb2NhbC1tdXRhdGlvbi1sb2cuanNcIikuTG9jYWxNdXRhdGlvbkxvZ1JlY29yZH0+fSAqL1xuICAgICAgY29uc3QgZW50cmllcyA9IFtdXG5cbiAgICAgIGZvciAoY29uc3QgbXV0YXRpb25Mb2cgb2YgdGhpcy5tdXRhdGlvbkxvZ3MpIHtcbiAgICAgICAgZm9yIChjb25zdCByZWNvcmQgb2YgYXdhaXQgbXV0YXRpb25Mb2cucGVuZGluZ1JlY29yZHMoKSkge1xuICAgICAgICAgIGlmICghcmVjb3JkLnNpZ25lZE11dGF0aW9uKSBjb250aW51ZVxuICAgICAgICAgIC8vIEEgcmVjb3JkIGNhbiBvbmx5IHJlcGxheSB3aXRoIHRoZSBncmFudCBpdCB3YXMgc2lnbmVkIHVuZGVyLiBSZWNvcmRzXG4gICAgICAgICAgLy8gd2hvc2UgZ3JhbnQgdGhlIHNlc3Npb24gZG9lcyBub3QgcmV0YWluIChlLmcuIHBlZXIgaW1wb3J0cyB3aXRob3V0XG4gICAgICAgICAgLy8gdGhlaXIgc2lnbmVyJ3MgZ3JhbnQpIHN0YXkgcGVuZGluZyBpbnN0ZWFkIG9mIGJsb2NraW5nIHRoZSBiYXRjaC5cbiAgICAgICAgICBpZiAoIXRoaXMuX2dyYW50cy5oYXMocmVjb3JkLm11dGF0aW9uLm9mZmxpbmVHcmFudElkKSkgY29udGludWVcblxuICAgICAgICAgIGVudHJpZXMucHVzaCh7bXV0YXRpb25Mb2csIHJlY29yZH0pXG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKGVudHJpZXMubGVuZ3RoID09PSAwKSBicmVha1xuXG4gICAgICBjb25zdCBiYXRjaCA9IGVudHJpZXMuc2xpY2UoMCwgbWF4QmF0Y2hTaXplKVxuICAgICAgY29uc3QgYXV0aGVudGljYXRpb25Ub2tlbiA9IGF3YWl0IHRoaXMuYXV0aGVudGljYXRpb25Ub2tlbigpXG4gICAgICBjb25zdCBtdXRhdGlvbnMgPSBiYXRjaC5tYXAoKGVudHJ5KSA9PiB0aGlzLl9yZXBsYXlNdXRhdGlvbihlbnRyeS5yZWNvcmQpKVxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0aGlzLnBvc3QodGhpcy5jb25maWcucmVwbGF5UGF0aCwge2F1dGhlbnRpY2F0aW9uVG9rZW4sIG11dGF0aW9uc30sIHtzaWduYWx9KVxuXG4gICAgICB0aHJvd0lmQWJvcnRlZChzaWduYWwpXG5cbiAgICAgIGNvbnN0IHJlc3VsdHNCeUlkZW1wb3RlbmN5S2V5ID0gdGhpcy5fcmVwbGF5UmVzdWx0c0J5SWQocmVzcG9uc2UpXG5cbiAgICAgIGZvciAoY29uc3Qge211dGF0aW9uTG9nLCByZWNvcmR9IG9mIGJhdGNoKSB7XG4gICAgICAgIHRocm93SWZBYm9ydGVkKHNpZ25hbClcbiAgICAgICAgY29uc3QgaWRlbXBvdGVuY3lLZXkgPSBtdXRhdGlvbklkZW1wb3RlbmN5S2V5KC8qKiBAdHlwZSB7aW1wb3J0KFwiLi9kZXZpY2UtaWRlbnRpdHkuanNcIikuU2lnbmVkU3luY011dGF0aW9ufSAqLyAocmVjb3JkLnNpZ25lZE11dGF0aW9uKSlcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gcmVzdWx0c0J5SWRlbXBvdGVuY3lLZXkuZ2V0KGlkZW1wb3RlbmN5S2V5KVxuXG4gICAgICAgIGlmICghcmVzdWx0KSB0aHJvdyBuZXcgRXJyb3IoYFNpZ25lZCBzeW5jIHJlcGxheSByZXNwb25zZSBpcyBtaXNzaW5nIGEgcmVzdWx0IGZvciBtdXRhdGlvbiAke3JlY29yZC5tdXRhdGlvbi5jbGllbnRNdXRhdGlvbklkfWApXG5cbiAgICAgICAgY29uc3Qgc3RhdHVzID0gcmVwbGF5UmVzdWx0TG9jYWxTdGF0dXMoLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCBpbXBvcnQoXCIuL2NvbmZsaWN0LXN0cmF0ZWd5LmpzXCIpLlN5bmNKc29uVmFsdWU+fSAqLyAocmVzdWx0KSlcbiAgICAgICAgY29uc3Qgc2VydmVyUmVzdWx0ID0gY2xvbmVKc29uKHJlc3VsdCwgXCJzeW5jIHJlcGxheSByZXN1bHRcIilcbiAgICAgICAgY29uc3QgdXBkYXRlZCA9IGF3YWl0IG11dGF0aW9uTG9nLnVwZGF0ZVN0YXR1cyh7XG4gICAgICAgICAgaWQ6IHJlY29yZC5pZCxcbiAgICAgICAgICBzdGF0dXMsXG4gICAgICAgICAgc3luY1Jlc3VsdDogLyoqIEB0eXBlIHtSZWNvcmQ8c3RyaW5nLCBpbXBvcnQoXCIuLi9mcm9udGVuZC1tb2RlbHMvYmFzZS5qc1wiKS5Gcm9udGVuZE1vZGVsVHJhbnNwb3J0VmFsdWU+fSAqLyAoc2VydmVyUmVzdWx0KVxuICAgICAgICB9KVxuXG4gICAgICAgIHJlc3VsdHMucHVzaCh7XG4gICAgICAgICAgY2xpZW50TXV0YXRpb25JZDogcmVjb3JkLm11dGF0aW9uLmNsaWVudE11dGF0aW9uSWQsXG4gICAgICAgICAgaWRlbXBvdGVuY3lLZXksXG4gICAgICAgICAgbXV0YXRpb246IC8qKiBAdHlwZSB7aW1wb3J0KFwiLi9kZXZpY2UtaWRlbnRpdHkuanNcIikuU3luY011dGF0aW9ufSAqLyAoY2xvbmVKc29uKHJlY29yZC5tdXRhdGlvbiwgXCJtdXRhdGlvblwiKSksXG4gICAgICAgICAgcmVjb3JkSWQ6IHVwZGF0ZWQuaWQsXG4gICAgICAgICAgc2VydmVyUmVzdWx0LFxuICAgICAgICAgIHN0YXR1czogdXBkYXRlZC5zdGF0dXNcbiAgICAgICAgfSlcblxuICAgICAgICBpZiAodXBkYXRlZC5zdGF0dXMgPT09IFwic3luY2VkXCIpIHN5bmNlZENvdW50ICs9IDFcbiAgICAgICAgaWYgKHVwZGF0ZWQuc3RhdHVzID09PSBcImNvbmZsaWN0XCIpIGNvbmZsaWN0Q291bnQgKz0gMVxuICAgICAgICBpZiAodXBkYXRlZC5zdGF0dXMgPT09IFwicmVqZWN0ZWRcIikgcmVqZWN0ZWRDb3VudCArPSAxXG4gICAgICB9XG4gICAgfVxuXG4gICAgbGV0IHBlbmRpbmdDb3VudCA9IDBcblxuICAgIGZvciAoY29uc3QgbXV0YXRpb25Mb2cgb2YgdGhpcy5tdXRhdGlvbkxvZ3MpIHtcbiAgICAgIHBlbmRpbmdDb3VudCArPSAoYXdhaXQgbXV0YXRpb25Mb2cucGVuZGluZ1JlY29yZHMoKSkubGVuZ3RoXG4gICAgfVxuXG4gICAgcmV0dXJuIHtjb25mbGljdENvdW50LCBwZW5kaW5nQ291bnQsIHJlamVjdGVkQ291bnQsIHJlc3VsdHMsIHN5bmNlZENvdW50fVxuICB9XG5cbiAgLyoqXG4gICAqIEJ1aWxkcyBvbmUgc2lnbmVkIHJlcGxheSBtdXRhdGlvbjogdGhlIHNpZ25lZCBlbnZlbG9wZSBwbHVzIHRoZSBzaWduZWRcbiAgICogZ3JhbnQgdGhlIG11dGF0aW9uIHdhcyBzaWduZWQgdW5kZXIuIEZhaWxzIGNsb3NlZCB3aGVuIHRoZSBzZXNzaW9uIGRvZXNcbiAgICogbm90IHJldGFpbiB0aGF0IGdyYW50LlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vbG9jYWwtbXV0YXRpb24tbG9nLmpzXCIpLkxvY2FsTXV0YXRpb25Mb2dSZWNvcmR9IHJlY29yZCAtIER1cmFibGUgcmVjb3JkLlxuICAgKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+fSAtIFJlcGxheSBtdXRhdGlvbiBwYXlsb2FkLlxuICAgKi9cbiAgX3JlcGxheU11dGF0aW9uKHJlY29yZCkge1xuICAgIGNvbnN0IHNpZ25lZE11dGF0aW9uID0gLyoqIEB0eXBlIHtpbXBvcnQoXCIuL2RldmljZS1pZGVudGl0eS5qc1wiKS5TaWduZWRTeW5jTXV0YXRpb259ICovIChyZWNvcmQuc2lnbmVkTXV0YXRpb24pXG4gICAgY29uc3Qgc2lnbmVkR3JhbnQgPSB0aGlzLl9ncmFudHMuZ2V0KHNpZ25lZE11dGF0aW9uLm11dGF0aW9uLm9mZmxpbmVHcmFudElkKVxuXG4gICAgaWYgKCFzaWduZWRHcmFudCkgdGhyb3cgbmV3IEVycm9yKGBTeW5jIG9mZmxpbmUgc2Vzc2lvbiBoYXMgbm8gcmV0YWluZWQgZ3JhbnQgJyR7c2lnbmVkTXV0YXRpb24ubXV0YXRpb24ub2ZmbGluZUdyYW50SWR9JyBmb3IgbXV0YXRpb24gJHtzaWduZWRNdXRhdGlvbi5tdXRhdGlvbi5jbGllbnRNdXRhdGlvbklkfWApXG5cbiAgICByZXR1cm4gey4uLmNsb25lSnNvbihzaWduZWRNdXRhdGlvbiwgXCJzaWduZWQgbXV0YXRpb25cIiksIHNpZ25lZE9mZmxpbmVHcmFudDogY2xvbmVKc29uKHNpZ25lZEdyYW50LCBcInNpZ25lZCBvZmZsaW5lIGdyYW50XCIpfVxuICB9XG5cbiAgLyoqXG4gICAqIE5vcm1hbGl6ZXMgYSBzaWduZWQgcmVwbGF5IHJlc3BvbnNlIGludG8gcmVzdWx0cyBrZXllZCBieSBpZGVtcG90ZW5jeSBrZXkuXG4gICAqIEBwYXJhbSB7UmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT59IHJlc3BvbnNlIC0gUmF3IHJlcGxheSByZXNwb25zZS5cbiAgICogQHJldHVybnMge01hcDxzdHJpbmcsIFJlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Pj59IC0gUmVzdWx0cyBieSBpZGVtcG90ZW5jeSBrZXkuXG4gICAqL1xuICBfcmVwbGF5UmVzdWx0c0J5SWQocmVzcG9uc2UpIHtcbiAgICBpZiAoIXJlc3BvbnNlIHx8IHR5cGVvZiByZXNwb25zZSAhPT0gXCJvYmplY3RcIiB8fCBBcnJheS5pc0FycmF5KHJlc3BvbnNlKSkgdGhyb3cgbmV3IEVycm9yKFwiU2lnbmVkIHN5bmMgcmVwbGF5IHJlc3BvbnNlIG11c3QgYmUgYW4gb2JqZWN0XCIpXG5cbiAgICBjb25zdCByZXNwb25zZVJlY29yZCA9IC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgdW5rbm93bj59ICovIChyZXNwb25zZSlcblxuICAgIGlmIChyZXNwb25zZVJlY29yZC5zdGF0dXMgPT09IFwiZXJyb3JcIikgdGhyb3cgbmV3IEVycm9yKHR5cGVvZiByZXNwb25zZVJlY29yZC5lcnJvck1lc3NhZ2UgPT09IFwic3RyaW5nXCIgPyByZXNwb25zZVJlY29yZC5lcnJvck1lc3NhZ2UgOiBcIlNpZ25lZCBzeW5jIHJlcGxheSBmYWlsZWRcIilcbiAgICBpZiAoIUFycmF5LmlzQXJyYXkocmVzcG9uc2VSZWNvcmQucmVzdWx0cykpIHRocm93IG5ldyBFcnJvcihcIlNpZ25lZCBzeW5jIHJlcGxheSByZXNwb25zZSBpcyBtaXNzaW5nIHJlc3VsdHNcIilcblxuICAgIC8qKiBAdHlwZSB7TWFwPHN0cmluZywgUmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+Pn0gKi9cbiAgICBjb25zdCBieUtleSA9IG5ldyBNYXAoKVxuXG4gICAgZm9yIChjb25zdCByZXN1bHQgb2YgcmVzcG9uc2VSZWNvcmQucmVzdWx0cykge1xuICAgICAgaWYgKCFyZXN1bHQgfHwgdHlwZW9mIHJlc3VsdCAhPT0gXCJvYmplY3RcIiB8fCBBcnJheS5pc0FycmF5KHJlc3VsdCkpIGNvbnRpbnVlXG5cbiAgICAgIGNvbnN0IHJlc3VsdFJlY29yZCA9IC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgdW5rbm93bj59ICovIChyZXN1bHQpXG5cbiAgICAgIGlmICh0eXBlb2YgcmVzdWx0UmVjb3JkLmlkZW1wb3RlbmN5S2V5ICE9PSBcInN0cmluZ1wiKSBjb250aW51ZVxuXG4gICAgICBieUtleS5zZXQocmVzdWx0UmVjb3JkLmlkZW1wb3RlbmN5S2V5LCAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIFJldHVyblR5cGU8dHlwZW9mIEpTT04ucGFyc2U+Pn0gKi8gKHJlc3VsdFJlY29yZCkpXG4gICAgfVxuXG4gICAgcmV0dXJuIGJ5S2V5XG4gIH1cblxuICAvKipcbiAgICogRXhwb3J0cyB0aGUgZHVyYWJsZSBub24tdGVybWluYWwgbXV0YXRpb25zIG9mIGV2ZXJ5IHNlc3Npb24gbG9nIGFzIG9uZVxuICAgKiBzaWduZWQgcGVlciBidW5kbGUuXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBbb3B0aW9uc10gLSBFeHBvcnQgb3B0aW9ucy5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuL2xvY2FsLW11dGF0aW9uLWxvZy5qc1wiKS5Mb2NhbE11dGF0aW9uU3RhdHVzW119IFtvcHRpb25zLnN0YXR1c2VzXSAtIFN0YXR1c2VzIHRvIGV4cG9ydC5cbiAgICogQHBhcmFtIHsoKSA9PiBEYXRlfSBbb3B0aW9ucy5ub3ddIC0gRXhwb3J0IGNsb2NrLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCIuL3BlZXItbXV0YXRpb24tYnVuZGxlLmpzXCIpLlBlZXJNdXRhdGlvbkJ1bmRsZT59IC0gU2lnbmVkIHBlZXIgYnVuZGxlLlxuICAgKi9cbiAgYXN5bmMgZXhwb3J0QnVuZGxlKG9wdGlvbnMgPSB7fSkge1xuICAgIGNvbnN0IGlkZW50aXR5ID0gYXdhaXQgdGhpcy5fZW5zdXJlSWRlbnRpdHkoKVxuICAgIC8qKiBAdHlwZSB7aW1wb3J0KFwiLi9wZWVyLW11dGF0aW9uLWJ1bmRsZS5qc1wiKS5QZWVyTXV0YXRpb25CdW5kbGVbXCJtdXRhdGlvbnNcIl19ICovXG4gICAgY29uc3QgbXV0YXRpb25zID0gW11cbiAgICBsZXQgYnVuZGxlID0gbnVsbFxuXG4gICAgZm9yIChjb25zdCBtdXRhdGlvbkxvZyBvZiB0aGlzLm11dGF0aW9uTG9ncykge1xuICAgICAgY29uc3QgbG9nQnVuZGxlID0gYXdhaXQgZXhwb3J0UGVlck11dGF0aW9uQnVuZGxlKHtcbiAgICAgICAgZGV2aWNlQ2VydGlmaWNhdGU6IGlkZW50aXR5LmNlcnRpZmljYXRlLFxuICAgICAgICBkZXZpY2VQcml2YXRlS2V5OiBpZGVudGl0eS5wcml2YXRlS2V5LFxuICAgICAgICBtdXRhdGlvbkxvZyxcbiAgICAgICAgLi4ub3B0aW9uc1xuICAgICAgfSlcblxuICAgICAgaWYgKCFidW5kbGUpIGJ1bmRsZSA9IGxvZ0J1bmRsZVxuICAgICAgZWxzZSBtdXRhdGlvbnMucHVzaCguLi5sb2dCdW5kbGUubXV0YXRpb25zKVxuICAgIH1cblxuICAgIGlmICghYnVuZGxlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTeW5jIG9mZmxpbmUgc2Vzc2lvbiBleHBvcnQgcHJvZHVjZWQgbm8gYnVuZGxlXCIpXG4gICAgfVxuXG4gICAgYnVuZGxlLm11dGF0aW9ucyA9IG11dGF0aW9ucy5sZW5ndGggPiAwID8gWy4uLmJ1bmRsZS5tdXRhdGlvbnMsIC4uLm11dGF0aW9uc10uc29ydCgobGVmdCwgcmlnaHQpID0+IChsZWZ0LmxvY2FsU2VxdWVuY2UgfHwgMCkgLSAocmlnaHQubG9jYWxTZXF1ZW5jZSB8fCAwKSkgOiBidW5kbGUubXV0YXRpb25zXG5cbiAgICByZXR1cm4gYnVuZGxlXG4gIH1cblxuICAvKipcbiAgICogSW1wb3J0cyBhIHBlZXIgYnVuZGxlOiBldmVyeSBlbnRyeSBpcyB2ZXJpZmllZCBhZ2FpbnN0IHRoZSBjb25maWd1cmVkXG4gICAqIGJhY2tlbmQgcHVibGljIGtleSBiZWZvcmUgaXQgaXMgYXBwZW5kZWQgdG8gYSBsb2NhbCBtdXRhdGlvbiBsb2cuIEVudHJpZXNcbiAgICogYWxyZWFkeSBwcmVzZW50IGJ5IGlkZW1wb3RlbmN5IGtleSBhcmUgc2tpcHBlZCBhcyBkdXBsaWNhdGVzLlxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIEltcG9ydCBhcmdzLlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4vcGVlci1tdXRhdGlvbi1idW5kbGUuanNcIikuUGVlck11dGF0aW9uQnVuZGxlfSBhcmdzLmJ1bmRsZSAtIFBlZXIgYnVuZGxlIHRvIGltcG9ydC5cbiAgICogQHBhcmFtIHtBcnJheTxpbXBvcnQoXCIuL29mZmxpbmUtZ3JhbnQuanNcIikuU2lnbmVkT2ZmbGluZUdyYW50Pn0gW2FyZ3Muc2lnbmVkT2ZmbGluZUdyYW50c10gLSBHcmFudHMgdGhlIHBlZXIgc2lnbmVkIGl0cyBtdXRhdGlvbnMgdW5kZXIuIFRoZXkgYXJlIHJldGFpbmVkIHNvIGltcG9ydGVkIG11dGF0aW9ucyByZXBsYXkgd2l0aCB0aGUgZ3JhbnQgdGhlaXIgc2lnbmVyIHVzZWQuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi9sb2NhbC1tdXRhdGlvbi1sb2cuanNcIikuZGVmYXVsdH0gW2FyZ3MubXV0YXRpb25Mb2ddIC0gVGFyZ2V0IGxvZy4gRGVmYXVsdHMgdG8gdGhlIGZpcnN0IHNlc3Npb24gbG9nLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxpbXBvcnQoXCIuL3BlZXItbXV0YXRpb24tYnVuZGxlLmpzXCIpLlBlZXJNdXRhdGlvbkJ1bmRsZUltcG9ydFJlc3VsdD59IC0gSW1wb3J0IHJlc3VsdC5cbiAgICovXG4gIGFzeW5jIGltcG9ydEJ1bmRsZSh7YnVuZGxlLCBtdXRhdGlvbkxvZywgc2lnbmVkT2ZmbGluZUdyYW50cyA9IFtdfSkge1xuICAgIGlmICghdGhpcy5jb25maWcuYmFja2VuZFB1YmxpY0tleSkgdGhyb3cgbmV3IEVycm9yKFwic3luYy5jbGllbnQub2ZmbGluZS5iYWNrZW5kUHVibGljS2V5IGlzIHJlcXVpcmVkIHRvIGltcG9ydCBhIHBlZXIgbXV0YXRpb24gYnVuZGxlXCIpXG4gICAgaWYgKCFBcnJheS5pc0FycmF5KHNpZ25lZE9mZmxpbmVHcmFudHMpKSB0aHJvdyBuZXcgRXJyb3IoXCJzeW5jIG9mZmxpbmUgaW1wb3J0IHNpZ25lZE9mZmxpbmVHcmFudHMgbXVzdCBiZSBhbiBhcnJheSBvZiBzaWduZWQgb2ZmbGluZSBncmFudHNcIilcblxuICAgIGZvciAoY29uc3Qgc2lnbmVkR3JhbnQgb2Ygc2lnbmVkT2ZmbGluZUdyYW50cykge1xuICAgICAgaWYgKCFzaWduZWRHcmFudCB8fCB0eXBlb2Ygc2lnbmVkR3JhbnQgIT09IFwib2JqZWN0XCIgfHwgIXNpZ25lZEdyYW50LmdyYW50IHx8IHR5cGVvZiBzaWduZWRHcmFudC5ncmFudC5ncmFudElkICE9PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcInN5bmMgb2ZmbGluZSBpbXBvcnQgcmVxdWlyZXMgc2lnbmVkIG9mZmxpbmUgZ3JhbnQgZW52ZWxvcGVzIHdpdGggZ3JhbnQgcGF5bG9hZHNcIilcbiAgICAgIH1cblxuICAgICAgdGhpcy5fZ3JhbnRzLnNldChzaWduZWRHcmFudC5ncmFudC5ncmFudElkLCBzaWduZWRHcmFudClcbiAgICB9XG5cbiAgICBpZiAoc2lnbmVkT2ZmbGluZUdyYW50cy5sZW5ndGggPiAwKSBhd2FpdCB0aGlzLl9wZXJzaXN0R3JhbnRzKClcblxuICAgIHJldHVybiBhd2FpdCBpbXBvcnRQZWVyTXV0YXRpb25CdW5kbGUoe1xuICAgICAgYmFja2VuZFB1YmxpY0tleTogdGhpcy5jb25maWcuYmFja2VuZFB1YmxpY0tleSxcbiAgICAgIGJ1bmRsZSxcbiAgICAgIG11dGF0aW9uTG9nOiBtdXRhdGlvbkxvZyB8fCB0aGlzLm11dGF0aW9uTG9nc1swXVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogUGVyc2lzdHMgcmV0YWluZWQgZ3JhbnRzIHRocm91Z2ggdGhlIG9wdGlvbmFsIGFwcC1vd25lZCBncmFudCBzdG9yYWdlLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyBhZnRlciByZXRlbnRpb24gaXMgcGVyc2lzdGVkLlxuICAgKi9cbiAgYXN5bmMgX3BlcnNpc3RHcmFudHMoKSB7XG4gICAgaWYgKCF0aGlzLmNvbmZpZy5ncmFudFN0b3JhZ2UpIHJldHVyblxuXG4gICAgYXdhaXQgdGhpcy5jb25maWcuZ3JhbnRTdG9yYWdlLnNhdmUoQXJyYXkuZnJvbSh0aGlzLl9ncmFudHMudmFsdWVzKCkpKVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIHNlc3Npb24gY2xvY2suXG4gICAqIEByZXR1cm5zIHtEYXRlfSAtIEN1cnJlbnQgdGltZS5cbiAgICovXG4gIF9ub3coKSB7XG4gICAgcmV0dXJuIHRoaXMuY29uZmlnLm5vdygpXG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZGV2aWNlIGlkZW50aXR5IG9yIGZhaWxzIGNsb3NlZC5cbiAgICogQHJldHVybnMge1N5bmNPZmZsaW5lRGV2aWNlSWRlbnRpdHl9IC0gRGV2aWNlIGlkZW50aXR5LlxuICAgKi9cbiAgX3JlcXVpcmVJZGVudGl0eSgpIHtcbiAgICBpZiAoIXRoaXMuX2lkZW50aXR5KSB0aHJvdyBuZXcgRXJyb3IoXCJTeW5jIG9mZmxpbmUgc2Vzc2lvbiBoYXMgbm8gZGV2aWNlIGlkZW50aXR5IC0gY2FsbCBzdGFydE9mZmxpbmVTZXNzaW9uKCkgZmlyc3RcIilcblxuICAgIHJldHVybiB0aGlzLl9pZGVudGl0eVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpdmUgZ3JhbnQgb3IgZmFpbHMgY2xvc2VkLlxuICAgKiBAcmV0dXJucyB7aW1wb3J0KFwiLi9vZmZsaW5lLWdyYW50LmpzXCIpLlNpZ25lZE9mZmxpbmVHcmFudH0gLSBMaXZlIGdyYW50LlxuICAgKi9cbiAgX3JlcXVpcmVMaXZlR3JhbnQoKSB7XG4gICAgY29uc3QgZ3JhbnQgPSB0aGlzLl9saXZlR3JhbnQoKVxuXG4gICAgaWYgKCFncmFudCkgdGhyb3cgbmV3IEVycm9yKFwiU3luYyBvZmZsaW5lIHNlc3Npb24gaGFzIG5vIGxpdmUgb2ZmbGluZSBncmFudCAtIGNhbGwgc3RhcnRPZmZsaW5lU2Vzc2lvbigpIG9yIHJlZnJlc2hPZmZsaW5lR3JhbnQoKVwiKVxuXG4gICAgcmV0dXJuIGdyYW50XG4gIH1cbn1cblxuLyoqXG4gKiBDbG9uZXMgYSBKU09OIG9iamVjdC5cbiAqIEBwYXJhbSB7dW5rbm93bn0gdmFsdWUgLSBWYWx1ZS5cbiAqIEBwYXJhbSB7c3RyaW5nfSBsYWJlbCAtIERpYWdub3N0aWMgbGFiZWwuXG4gKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+fSAtIENsb25lZCBvYmplY3QuXG4gKi9cbmZ1bmN0aW9uIGNsb25lSnNvbih2YWx1ZSwgbGFiZWwpIHtcbiAgaWYgKCF2YWx1ZSB8fCB0eXBlb2YgdmFsdWUgIT09IFwib2JqZWN0XCIgfHwgQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgc3luYyBvZmZsaW5lICR7bGFiZWx9IG9iamVjdGApXG5cbiAgcmV0dXJuIC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgUmV0dXJuVHlwZTx0eXBlb2YgSlNPTi5wYXJzZT4+fSAqLyAoSlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeSh2YWx1ZSkpKVxufVxuXG4vKipcbiAqIFRocm93cyB0aGUgZXhhY3QgYWJvcnQgcmVhc29uIGF0IGxpZmVjeWNsZSBib3VuZGFyaWVzLlxuICogQHBhcmFtIHtBYm9ydFNpZ25hbCB8IHVuZGVmaW5lZH0gc2lnbmFsIC0gTGlmZWN5Y2xlIHNpZ25hbC5cbiAqIEByZXR1cm5zIHt2b2lkfSAtIE5vIHJldHVybiB2YWx1ZS5cbiAqL1xuZnVuY3Rpb24gdGhyb3dJZkFib3J0ZWQoc2lnbmFsKSB7XG4gIGlmICghc2lnbmFsPy5hYm9ydGVkKSByZXR1cm5cblxuICB0aHJvdyBzaWduYWwucmVhc29uIGluc3RhbmNlb2YgRXJyb3IgPyBzaWduYWwucmVhc29uIDogbmV3IEVycm9yKFwiU3luYyBvZmZsaW5lIHJlcGxheSBhYm9ydGVkXCIpXG59XG4iXX0=
|