@dollhousemcp/mcp-server 2.0.10 → 2.0.11
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/dist/auto-dollhouse/portDiscovery.d.ts +23 -0
- package/dist/auto-dollhouse/portDiscovery.d.ts.map +1 -0
- package/dist/auto-dollhouse/portDiscovery.js +77 -0
- package/dist/cli/console-token.d.ts +18 -0
- package/dist/cli/console-token.d.ts.map +1 -0
- package/dist/cli/console-token.js +187 -0
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -5
- package/dist/web/console/consoleToken.d.ts +403 -0
- package/dist/web/console/consoleToken.d.ts.map +1 -0
- package/dist/web/console/consoleToken.js +930 -0
- package/dist/web/middleware/authMiddleware.d.ts +64 -0
- package/dist/web/middleware/authMiddleware.d.ts.map +1 -0
- package/dist/web/middleware/authMiddleware.js +174 -0
- package/dist/web/routes/consoleRouteHelpers.d.ts +33 -0
- package/dist/web/routes/consoleRouteHelpers.d.ts.map +1 -0
- package/dist/web/routes/consoleRouteHelpers.js +60 -0
- package/dist/web/routes/tokenRoutes.d.ts +37 -0
- package/dist/web/routes/tokenRoutes.d.ts.map +1 -0
- package/dist/web/routes/tokenRoutes.js +95 -0
- package/dist/web/routes/totpRoutes.d.ts +45 -0
- package/dist/web/routes/totpRoutes.d.ts.map +1 -0
- package/dist/web/routes/totpRoutes.js +187 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/constants/version.d.ts +0 -3
- package/dist/constants/version.d.ts.map +0 -1
- package/dist/constants/version.js +0 -4
- package/dist/logging/sinks/SSELogSink.d.ts +0 -35
- package/dist/logging/sinks/SSELogSink.d.ts.map +0 -1
- package/dist/logging/sinks/SSELogSink.js +0 -181
- package/dist/logging/viewer/viewerHtml.d.ts +0 -8
- package/dist/logging/viewer/viewerHtml.d.ts.map +0 -1
- package/dist/logging/viewer/viewerHtml.js +0 -204
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console session token storage and verification (#1780).
|
|
3
|
+
*
|
|
4
|
+
* Manages the file at `~/.dollhouse/run/console-token.auth.json` which
|
|
5
|
+
* holds the Bearer tokens that authenticate requests to the web console.
|
|
6
|
+
* The file is created on first leader election and persists across
|
|
7
|
+
* restarts — tokens only rotate when explicitly requested.
|
|
8
|
+
*
|
|
9
|
+
* The `.auth` filename suffix keeps this state isolated from any legacy
|
|
10
|
+
* no-authentication DollhouseMCP installation running on the same
|
|
11
|
+
* machine. The port, lock file, and token file all share the same
|
|
12
|
+
* isolation strategy — see `src/config/env.ts` for the port, and
|
|
13
|
+
* `LeaderElection.ts` for the lock file.
|
|
14
|
+
*
|
|
15
|
+
* Schema is forward-compatible with multi-device, multi-tenant, and
|
|
16
|
+
* scope-restricted tokens for Phase 2+. Phase 1 uses a single "console"
|
|
17
|
+
* kind token and stubs the scope/boundary checks.
|
|
18
|
+
*
|
|
19
|
+
* File format (version 1):
|
|
20
|
+
* ```json
|
|
21
|
+
* {
|
|
22
|
+
* "version": 1,
|
|
23
|
+
* "tokens": [
|
|
24
|
+
* {
|
|
25
|
+
* "id": "018e1a2b-...",
|
|
26
|
+
* "name": "Kermit on mick-MacBook-Pro",
|
|
27
|
+
* "kind": "console",
|
|
28
|
+
* "token": "<64-hex>",
|
|
29
|
+
* "scopes": ["admin"],
|
|
30
|
+
* "elementBoundaries": null,
|
|
31
|
+
* "tenant": null,
|
|
32
|
+
* "platform": "local",
|
|
33
|
+
* "labels": {},
|
|
34
|
+
* "createdAt": "2026-04-04T20:00:00.000Z",
|
|
35
|
+
* "lastUsedAt": null,
|
|
36
|
+
* "createdVia": "initial-setup"
|
|
37
|
+
* }
|
|
38
|
+
* ],
|
|
39
|
+
* "totp": { "enrolled": false, "secret": null, "backupCodes": [] }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @since v2.1.0 — Issue #1780
|
|
44
|
+
*/
|
|
45
|
+
import { homedir, hostname, platform } from 'node:os';
|
|
46
|
+
import { join } from 'node:path';
|
|
47
|
+
import { mkdir, readFile, rename, writeFile, chmod, unlink, copyFile } from 'node:fs/promises';
|
|
48
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
49
|
+
import { Secret, TOTP } from 'otpauth';
|
|
50
|
+
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
|
|
51
|
+
import { SecurityMonitor } from '../../security/securityMonitor.js';
|
|
52
|
+
import { logger } from '../../utils/logger.js';
|
|
53
|
+
/** Directory for runtime state files — same as LeaderElection. */
|
|
54
|
+
const RUN_DIR = join(homedir(), '.dollhouse', 'run');
|
|
55
|
+
/**
|
|
56
|
+
* Default path to the authenticated console's token file.
|
|
57
|
+
*
|
|
58
|
+
* The `.auth` suffix isolates this from any legacy no-authentication
|
|
59
|
+
* DollhouseMCP installation that may also be running on the same
|
|
60
|
+
* machine. Legacy installs did not write a token file at all; the
|
|
61
|
+
* authenticated console writes `console-token.auth.json`. Combined with
|
|
62
|
+
* the port and lock-file separation, this gives the two generations of
|
|
63
|
+
* the console fully independent state trees under `~/.dollhouse/run/`.
|
|
64
|
+
*
|
|
65
|
+
* Callers can override via `DOLLHOUSE_CONSOLE_TOKEN_FILE` in the env.
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_TOKEN_FILE = join(RUN_DIR, 'console-token.auth.json');
|
|
68
|
+
/** Current token file schema version. */
|
|
69
|
+
const TOKEN_FILE_VERSION = 1;
|
|
70
|
+
/** Token length in bytes (produces a 64-character hex string). */
|
|
71
|
+
const TOKEN_BYTES = 32;
|
|
72
|
+
/** File mode for the token file — owner read/write only. */
|
|
73
|
+
const TOKEN_FILE_MODE = 0o600;
|
|
74
|
+
/**
|
|
75
|
+
* TOTP configuration — RFC 6238 defaults. These are compiled-in so the
|
|
76
|
+
* otpauth URI is deterministic and compatible with every common authenticator
|
|
77
|
+
* app (Google Authenticator, 1Password, Authy, Bitwarden, etc.).
|
|
78
|
+
*/
|
|
79
|
+
const TOTP_ISSUER = 'DollhouseMCP';
|
|
80
|
+
const TOTP_ALGORITHM = 'SHA1';
|
|
81
|
+
const TOTP_DIGITS = 6;
|
|
82
|
+
const TOTP_PERIOD_SECONDS = 30;
|
|
83
|
+
const TOTP_SECRET_SIZE_BYTES = 20; // 160 bits — the RFC 6238 recommendation
|
|
84
|
+
/**
|
|
85
|
+
* ±2 time step tolerance (±60s drift) → 150 second total validity window.
|
|
86
|
+
*
|
|
87
|
+
* The naive choice would be `window: 1` (±30s, 90s total) which is the RFC
|
|
88
|
+
* 6238 default and matches most web login flows where the user types a code
|
|
89
|
+
* they just generated, with sub-second latency to the server. That doesn't
|
|
90
|
+
* cover our real deployment profile:
|
|
91
|
+
*
|
|
92
|
+
* 1. **Async bridge flows** — DollhouseBridge over Zulip (or similar chat
|
|
93
|
+
* transports) can add 10-60 seconds of latency between the user typing
|
|
94
|
+
* the code and the MCP server verifying it. A code typed with a few
|
|
95
|
+
* seconds of lifetime remaining can slide out of the validation window
|
|
96
|
+
* before reaching the server.
|
|
97
|
+
*
|
|
98
|
+
* 2. **Clock drift** — containers, VMs, and phones with lazy NTP can
|
|
99
|
+
* disagree with the server by tens of seconds. ±30s gives zero margin
|
|
100
|
+
* against any drift at all.
|
|
101
|
+
*
|
|
102
|
+
* `window: 2` gives 5 valid codes out of 10^6 per attempt (vs. 3 with
|
|
103
|
+
* window=1). Combined with the 10/min rate limiter and the always-on auth
|
|
104
|
+
* gate, brute-force success probability remains below 5e-5 per minute —
|
|
105
|
+
* well within the security envelope. `window: 3` (±90s, 210s total) is
|
|
106
|
+
* defensible but reserved for future deployment pressure; easier to widen
|
|
107
|
+
* later than to narrow later.
|
|
108
|
+
*
|
|
109
|
+
* Note: the window is anchored to the **server's verification clock**, not
|
|
110
|
+
* the user's code-generation clock. When a user complains that a code they
|
|
111
|
+
* "just typed" was rejected, the error message nudges them to submit codes
|
|
112
|
+
* with plenty of step-lifetime remaining.
|
|
113
|
+
*/
|
|
114
|
+
const TOTP_VALIDATE_WINDOW = 2;
|
|
115
|
+
/**
|
|
116
|
+
* Error message for a failed TOTP code check. Includes a hint about the
|
|
117
|
+
* common failure mode (code aged out in transit) so users on high-latency
|
|
118
|
+
* channels know what to try differently on retry.
|
|
119
|
+
*/
|
|
120
|
+
const INVALID_TOTP_MESSAGE = 'Invalid TOTP code. When copying from an authenticator app, use a code ' +
|
|
121
|
+
'with plenty of lifetime remaining — codes can age out in transit on ' +
|
|
122
|
+
'slower channels (e.g. chat bridges). Wait for a fresh code if unsure.';
|
|
123
|
+
/** Pending enrollments expire this long after begin() to limit in-memory secret lifetime. */
|
|
124
|
+
const TOTP_PENDING_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
125
|
+
/**
|
|
126
|
+
* Hard cap on concurrent pending enrollments. An authenticated caller who
|
|
127
|
+
* misbehaves could otherwise fill the in-memory Map with 10-minute-TTL
|
|
128
|
+
* secrets. Realistic users need at most 1 pending enrollment at a time;
|
|
129
|
+
* 10 is generous headroom for retry-during-enrollment scenarios.
|
|
130
|
+
*
|
|
131
|
+
* When the cap is hit, `beginTotpEnrollment` throws `TOO_MANY_PENDING`
|
|
132
|
+
* (mapped to HTTP 429). Rejecting loudly is preferred over silently
|
|
133
|
+
* evicting someone else's legitimate pending entry.
|
|
134
|
+
*/
|
|
135
|
+
const MAX_PENDING_ENROLLMENTS = 10;
|
|
136
|
+
/**
|
|
137
|
+
* Grace window for old tokens after rotation — 15 seconds.
|
|
138
|
+
*
|
|
139
|
+
* After a rotation, the previous token value stays valid for this period to
|
|
140
|
+
* cover in-flight requests from the rotating tab and any other local processes
|
|
141
|
+
* that read the token file but haven't picked up the new value yet. All
|
|
142
|
+
* consumers are loopback-only (127.0.0.1), so network latency is negligible
|
|
143
|
+
* and 15s is generous.
|
|
144
|
+
*
|
|
145
|
+
* Grace entries are per-rotation and in-memory only — they are never persisted
|
|
146
|
+
* to disk. A leader crash kills all grace entries, which is the safe failure
|
|
147
|
+
* mode (callers re-auth with the new token from the file).
|
|
148
|
+
*/
|
|
149
|
+
const ROTATION_GRACE_MS = 15_000;
|
|
150
|
+
/** Number of backup codes generated on enrollment. */
|
|
151
|
+
const BACKUP_CODE_COUNT = 10;
|
|
152
|
+
/** Characters per backup code — Crockford base32-ish, no ambiguous chars. */
|
|
153
|
+
const BACKUP_CODE_LENGTH = 8;
|
|
154
|
+
/**
|
|
155
|
+
* Backup code alphabet — Crockford base32 (32 chars, excludes I/L/O/U).
|
|
156
|
+
* 5 bits per char × 8 chars = 40 bits per code, plenty for one-shot use.
|
|
157
|
+
*/
|
|
158
|
+
const BACKUP_CODE_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
159
|
+
/**
|
|
160
|
+
* Strict format for console tokens — 64 lowercase hex characters.
|
|
161
|
+
* Used as a defense-in-depth check in verify(): even if the caller forgot
|
|
162
|
+
* to sanitize the presented value, we reject anything that isn't a legitimate
|
|
163
|
+
* 256-bit hex token before reaching the constant-time comparison.
|
|
164
|
+
* DMCP-SEC-004 mitigation.
|
|
165
|
+
*/
|
|
166
|
+
const TOKEN_FORMAT = /^[0-9a-f]{64}$/;
|
|
167
|
+
/**
|
|
168
|
+
* Typed error for TOTP store operations. Carries a machine-readable
|
|
169
|
+
* `code` field so the HTTP layer can map failures to consistent response
|
|
170
|
+
* codes without parsing free-form error messages. Callers (CLI, UI) can
|
|
171
|
+
* branch on `code` instead of the human-readable `message`.
|
|
172
|
+
*/
|
|
173
|
+
export class TotpError extends Error {
|
|
174
|
+
code;
|
|
175
|
+
constructor(message, code) {
|
|
176
|
+
super(message);
|
|
177
|
+
this.code = code;
|
|
178
|
+
this.name = 'TotpError';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Generate a cryptographically random token.
|
|
183
|
+
* Returns 64 hex characters (256 bits of entropy).
|
|
184
|
+
*/
|
|
185
|
+
function generateTokenValue() {
|
|
186
|
+
return randomBytes(TOKEN_BYTES).toString('hex');
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Mask a token for display — shows first 8 chars only.
|
|
190
|
+
*/
|
|
191
|
+
function maskToken(token) {
|
|
192
|
+
if (token.length <= 8)
|
|
193
|
+
return '••••••••';
|
|
194
|
+
return `${token.slice(0, 8)}${'•'.repeat(Math.min(56, token.length - 8))}`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Build a human-readable default name from a puppet name and the machine hostname.
|
|
198
|
+
* Example: "Kermit on mick-MacBook-Pro".
|
|
199
|
+
*
|
|
200
|
+
* The puppet name is passed in rather than imported to avoid a circular dependency
|
|
201
|
+
* with SessionNames (which only generates per-process names).
|
|
202
|
+
*/
|
|
203
|
+
function defaultTokenName(puppetName) {
|
|
204
|
+
const host = hostname() || 'localhost';
|
|
205
|
+
return `${puppetName} on ${host}`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Generate a batch of random backup codes. Each code is `BACKUP_CODE_LENGTH`
|
|
209
|
+
* characters drawn uniformly from `BACKUP_CODE_ALPHABET` (Crockford base32,
|
|
210
|
+
* 32 characters, 5 bits per char). Uses rejection sampling against 256-bit
|
|
211
|
+
* random bytes to avoid modulo bias.
|
|
212
|
+
*/
|
|
213
|
+
function generateBackupCodes() {
|
|
214
|
+
// 32-char alphabet divides 256 evenly (256 / 32 = 8), so the simple
|
|
215
|
+
// mod-32 mapping has no bias. Generate one byte per character.
|
|
216
|
+
const codes = [];
|
|
217
|
+
for (let i = 0; i < BACKUP_CODE_COUNT; i++) {
|
|
218
|
+
const bytes = randomBytes(BACKUP_CODE_LENGTH);
|
|
219
|
+
let code = '';
|
|
220
|
+
for (let j = 0; j < BACKUP_CODE_LENGTH; j++) {
|
|
221
|
+
code += BACKUP_CODE_ALPHABET[bytes[j] & 0x1f];
|
|
222
|
+
}
|
|
223
|
+
codes.push(code);
|
|
224
|
+
}
|
|
225
|
+
return codes;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Hash a backup code for storage. sha256 hex is plenty — these codes are
|
|
229
|
+
* high-entropy (40 bits) and we only need to detect a tamper, not resist
|
|
230
|
+
* password-cracking on a leaked hash.
|
|
231
|
+
*/
|
|
232
|
+
function hashBackupCode(code) {
|
|
233
|
+
return createHash('sha256').update(code, 'utf8').digest('hex');
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Normalize a user-entered backup code before hashing: uppercase, strip
|
|
237
|
+
* whitespace, strip dashes (users often type codes in groups like
|
|
238
|
+
* "XXXX-XXXX"). Returns the canonical form that matches what we stored.
|
|
239
|
+
*/
|
|
240
|
+
function normalizeBackupCode(raw) {
|
|
241
|
+
return raw.replaceAll(/[\s-]/g, '').toUpperCase();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Build the full otpauth:// URI for a given secret and display label.
|
|
245
|
+
* The label is URI-encoded by `otpauth` internally via URIComponent.
|
|
246
|
+
*/
|
|
247
|
+
function buildTotpUri(secret, label) {
|
|
248
|
+
const totp = new TOTP({
|
|
249
|
+
issuer: TOTP_ISSUER,
|
|
250
|
+
label,
|
|
251
|
+
algorithm: TOTP_ALGORITHM,
|
|
252
|
+
digits: TOTP_DIGITS,
|
|
253
|
+
period: TOTP_PERIOD_SECONDS,
|
|
254
|
+
secret,
|
|
255
|
+
});
|
|
256
|
+
return totp.toString();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Validate a single token entry object. Returns true if the entry has all
|
|
260
|
+
* required fields with the correct types. Extracted from validateTokenFile
|
|
261
|
+
* to keep the top-level validator's cognitive complexity manageable.
|
|
262
|
+
*/
|
|
263
|
+
function isValidTokenEntry(raw) {
|
|
264
|
+
if (!raw || typeof raw !== 'object')
|
|
265
|
+
return false;
|
|
266
|
+
const e = raw;
|
|
267
|
+
return (typeof e.id === 'string' && e.id.length > 0 &&
|
|
268
|
+
typeof e.name === 'string' &&
|
|
269
|
+
typeof e.token === 'string' && e.token.length > 0 &&
|
|
270
|
+
typeof e.kind === 'string' &&
|
|
271
|
+
Array.isArray(e.scopes) &&
|
|
272
|
+
typeof e.createdAt === 'string');
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Validate that a parsed JSON object conforms to the expected token file schema.
|
|
276
|
+
* Returns a typed ConsoleTokenFile or null if invalid.
|
|
277
|
+
*
|
|
278
|
+
* Strict validation — an unrecognized version or missing required fields
|
|
279
|
+
* causes the file to be treated as corrupt so a fresh one can be written.
|
|
280
|
+
*/
|
|
281
|
+
function validateTokenFile(raw) {
|
|
282
|
+
if (!raw || typeof raw !== 'object')
|
|
283
|
+
return null;
|
|
284
|
+
const obj = raw;
|
|
285
|
+
if (obj.version !== TOKEN_FILE_VERSION)
|
|
286
|
+
return null;
|
|
287
|
+
if (!Array.isArray(obj.tokens))
|
|
288
|
+
return null;
|
|
289
|
+
if (!obj.totp || typeof obj.totp !== 'object')
|
|
290
|
+
return null;
|
|
291
|
+
if (!obj.tokens.every(isValidTokenEntry))
|
|
292
|
+
return null;
|
|
293
|
+
return raw;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Stateful store that owns the console token file and verifies presented tokens.
|
|
297
|
+
*
|
|
298
|
+
* Designed to live on the leader process. Followers should not construct this —
|
|
299
|
+
* they read the file directly via `readTokenFileRaw()` for their own HTTP calls.
|
|
300
|
+
*/
|
|
301
|
+
export class ConsoleTokenStore {
|
|
302
|
+
filePath;
|
|
303
|
+
data = null;
|
|
304
|
+
/**
|
|
305
|
+
* Pre-converted Buffer cache keyed by entry id. Populated whenever `this.data`
|
|
306
|
+
* is assigned (load, create, future rotation). Verify() reuses the stored
|
|
307
|
+
* buffers so the hot path doesn't re-allocate per-token on every request.
|
|
308
|
+
* Negligible win with 1 token today; meaningful with Phase 2 multi-token
|
|
309
|
+
* lookups. Not serialized — buffers are never written to disk.
|
|
310
|
+
*/
|
|
311
|
+
tokenBuffers = new Map();
|
|
312
|
+
/**
|
|
313
|
+
* In-memory pending TOTP enrollments, keyed by opaque pendingId. Nothing
|
|
314
|
+
* lives on disk until confirmTotpEnrollment() succeeds, which limits the
|
|
315
|
+
* window in which a half-completed enrollment leaks a secret via file read.
|
|
316
|
+
* Entries expire after TOTP_PENDING_TTL_MS (#1794).
|
|
317
|
+
*/
|
|
318
|
+
pendingEnrollments = new Map();
|
|
319
|
+
/**
|
|
320
|
+
* In-memory grace buffer for recently-rotated tokens (#1795). After a
|
|
321
|
+
* rotation, the old token value is stashed here with a per-rotation expiry
|
|
322
|
+
* so in-flight requests that were sent before the rotation response arrived
|
|
323
|
+
* still authenticate. Entries are per-rotation (concurrent rotations each
|
|
324
|
+
* get their own grace slot) and never persisted to disk.
|
|
325
|
+
*/
|
|
326
|
+
graceEntries = [];
|
|
327
|
+
constructor(filePath = DEFAULT_TOKEN_FILE) {
|
|
328
|
+
this.filePath = filePath;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Rebuild the token buffer cache after a data load, create, or mutation.
|
|
332
|
+
* Keeps the hot verify() path allocation-free for the stored side.
|
|
333
|
+
*/
|
|
334
|
+
rebuildTokenBuffers() {
|
|
335
|
+
this.tokenBuffers.clear();
|
|
336
|
+
if (!this.data)
|
|
337
|
+
return;
|
|
338
|
+
for (const entry of this.data.tokens) {
|
|
339
|
+
this.tokenBuffers.set(entry.id, Buffer.from(entry.token, 'utf8'));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Read the existing token file, or create a new one with a single initial
|
|
344
|
+
* token if none exists. Idempotent — safe to call on every leader election.
|
|
345
|
+
*
|
|
346
|
+
* @param puppetName - A puppet name picked by the caller (e.g. from SessionNames)
|
|
347
|
+
* used to build the default display name on first run.
|
|
348
|
+
* @returns The primary (first) token entry — convenient for server startup
|
|
349
|
+
* to inject into HTML and stamp on followers.
|
|
350
|
+
*/
|
|
351
|
+
async ensureInitialized(puppetName) {
|
|
352
|
+
const readResult = await this.readWithStatus();
|
|
353
|
+
if (readResult.status === 'ok' && readResult.data.tokens.length > 0) {
|
|
354
|
+
this.data = readResult.data;
|
|
355
|
+
this.rebuildTokenBuffers();
|
|
356
|
+
logger.debug('[ConsoleToken] Loaded existing token file', {
|
|
357
|
+
path: this.filePath,
|
|
358
|
+
count: readResult.data.tokens.length,
|
|
359
|
+
});
|
|
360
|
+
return readResult.data.tokens[0];
|
|
361
|
+
}
|
|
362
|
+
// If the file existed but was corrupt, back it up before overwriting.
|
|
363
|
+
// Users may have hand-edited the file with custom names/labels — don't
|
|
364
|
+
// destroy their data silently. A timestamped copy lets them recover.
|
|
365
|
+
if (readResult.status === 'corrupt') {
|
|
366
|
+
await this.backupCorruptFile();
|
|
367
|
+
}
|
|
368
|
+
// Create a fresh file with one initial token
|
|
369
|
+
const now = new Date().toISOString();
|
|
370
|
+
const initial = {
|
|
371
|
+
id: randomUUID(),
|
|
372
|
+
name: defaultTokenName(puppetName),
|
|
373
|
+
kind: 'console',
|
|
374
|
+
token: generateTokenValue(),
|
|
375
|
+
scopes: ['admin'],
|
|
376
|
+
elementBoundaries: null,
|
|
377
|
+
tenant: null,
|
|
378
|
+
platform: 'local',
|
|
379
|
+
labels: {},
|
|
380
|
+
createdAt: now,
|
|
381
|
+
lastUsedAt: null,
|
|
382
|
+
createdVia: 'initial-setup',
|
|
383
|
+
};
|
|
384
|
+
const file = {
|
|
385
|
+
version: TOKEN_FILE_VERSION,
|
|
386
|
+
tokens: [initial],
|
|
387
|
+
totp: { enrolled: false, secret: null, backupCodes: [], enrolledAt: null },
|
|
388
|
+
};
|
|
389
|
+
await this.write(file);
|
|
390
|
+
this.data = file;
|
|
391
|
+
this.rebuildTokenBuffers();
|
|
392
|
+
logger.info('[ConsoleToken] Created new token file', {
|
|
393
|
+
path: this.filePath,
|
|
394
|
+
id: initial.id,
|
|
395
|
+
name: initial.name,
|
|
396
|
+
});
|
|
397
|
+
return initial;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Verify a presented Bearer token against the stored entries.
|
|
401
|
+
* Uses timing-safe comparison to prevent side-channel attacks.
|
|
402
|
+
*
|
|
403
|
+
* Updates `lastUsedAt` on the matched entry (in memory only; disk write
|
|
404
|
+
* is debounced to avoid disk thrash on every request — Phase 2 feature).
|
|
405
|
+
*
|
|
406
|
+
* @returns The matching entry, or null if no match.
|
|
407
|
+
*/
|
|
408
|
+
verify(presented) {
|
|
409
|
+
if (!this.data || !presented)
|
|
410
|
+
return null;
|
|
411
|
+
// DMCP-SEC-004: Normalize the presented token to NFC and validate the
|
|
412
|
+
// strict hex format before any comparison. This blocks Unicode abuse
|
|
413
|
+
// (homographs, zero-width, bidi overrides) from reaching timingSafeEqual.
|
|
414
|
+
// Defense-in-depth — the middleware already sanitizes, but verify() is a
|
|
415
|
+
// public API that any future caller could invoke directly.
|
|
416
|
+
const normalized = UnicodeValidator.normalize(presented).normalizedContent;
|
|
417
|
+
if (!TOKEN_FORMAT.test(normalized))
|
|
418
|
+
return null;
|
|
419
|
+
// Only the presented side is allocated per-request; stored buffers are
|
|
420
|
+
// pre-converted in the tokenBuffers cache so the hot loop is allocation-free.
|
|
421
|
+
const presentedBuf = Buffer.from(normalized, 'utf8');
|
|
422
|
+
for (const entry of this.data.tokens) {
|
|
423
|
+
const storedBuf = this.tokenBuffers.get(entry.id);
|
|
424
|
+
if (!storedBuf || storedBuf.length !== presentedBuf.length)
|
|
425
|
+
continue;
|
|
426
|
+
if (timingSafeEqual(presentedBuf, storedBuf)) {
|
|
427
|
+
entry.lastUsedAt = new Date().toISOString();
|
|
428
|
+
return entry;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Check grace buffer — recently-rotated tokens that haven't expired yet.
|
|
432
|
+
// Returns the primary entry on match so the caller gets the same admin
|
|
433
|
+
// shape; the grace window just extends the authentication period.
|
|
434
|
+
this.sweepExpiredGraceEntries();
|
|
435
|
+
for (const grace of this.graceEntries) {
|
|
436
|
+
if (grace.buf.length === presentedBuf.length && timingSafeEqual(presentedBuf, grace.buf)) {
|
|
437
|
+
// Touch the primary entry — the request is still "using" this token slot.
|
|
438
|
+
if (this.data.tokens.length > 0) {
|
|
439
|
+
this.data.tokens[0].lastUsedAt = new Date().toISOString();
|
|
440
|
+
}
|
|
441
|
+
return this.data.tokens[0] ?? null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get the primary token value for injection into HTML or forwarder config.
|
|
448
|
+
* Returns the first entry's token string, or null if uninitialized.
|
|
449
|
+
*/
|
|
450
|
+
getPrimaryTokenValue() {
|
|
451
|
+
if (!this.data || this.data.tokens.length === 0)
|
|
452
|
+
return null;
|
|
453
|
+
return this.data.tokens[0].token;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Return all tokens with the secret value masked — safe to serialize for
|
|
457
|
+
* the Security tab UI or `GET /api/console/token/info` responses.
|
|
458
|
+
*/
|
|
459
|
+
listMasked() {
|
|
460
|
+
if (!this.data)
|
|
461
|
+
return [];
|
|
462
|
+
return this.data.tokens.map(({ token, ...rest }) => ({
|
|
463
|
+
...rest,
|
|
464
|
+
tokenPreview: maskToken(token),
|
|
465
|
+
}));
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get the path to the token file on disk.
|
|
469
|
+
*/
|
|
470
|
+
getFilePath() {
|
|
471
|
+
return this.filePath;
|
|
472
|
+
}
|
|
473
|
+
// --------------------------------------------------------------------
|
|
474
|
+
// TOTP — Phase 2 (#1794)
|
|
475
|
+
// --------------------------------------------------------------------
|
|
476
|
+
/**
|
|
477
|
+
* Returns a safe-to-serialize view of TOTP enrollment state. Never leaks
|
|
478
|
+
* the secret or any backup code material.
|
|
479
|
+
*/
|
|
480
|
+
getTotpStatus() {
|
|
481
|
+
const totp = this.data?.totp;
|
|
482
|
+
if (!totp?.enrolled) {
|
|
483
|
+
return { enrolled: false, enrolledAt: null, backupCodesRemaining: 0 };
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
enrolled: true,
|
|
487
|
+
enrolledAt: totp.enrolledAt ?? null,
|
|
488
|
+
backupCodesRemaining: totp.backupCodes.length,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/** Convenience: true if the user has a confirmed TOTP secret. */
|
|
492
|
+
isTotpEnrolled() {
|
|
493
|
+
return Boolean(this.data?.totp?.enrolled && this.data.totp.secret);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Begin a TOTP enrollment. Generates a fresh secret, holds it in the
|
|
497
|
+
* in-memory pending map, and returns the data the UI needs to render a
|
|
498
|
+
* QR code and manual-entry fallback. Nothing is persisted until
|
|
499
|
+
* `confirmTotpEnrollment` succeeds.
|
|
500
|
+
*
|
|
501
|
+
* Callers may call this multiple times; each call produces a new pendingId
|
|
502
|
+
* and secret. Old pending entries expire after TOTP_PENDING_TTL_MS.
|
|
503
|
+
*
|
|
504
|
+
* @throws Error if TOTP is already enrolled — callers must disable first.
|
|
505
|
+
*/
|
|
506
|
+
beginTotpEnrollment(label) {
|
|
507
|
+
if (this.isTotpEnrolled()) {
|
|
508
|
+
throw new TotpError('TOTP is already enrolled — disable existing enrollment before enrolling again', 'ALREADY_ENROLLED');
|
|
509
|
+
}
|
|
510
|
+
this.sweepExpiredEnrollments();
|
|
511
|
+
// Reject if we're already at the per-process cap on concurrent pendings.
|
|
512
|
+
// Sweep ran above, so any slot still held is a live, unexpired enrollment
|
|
513
|
+
// belonging to somebody else — evicting it could silently kill their flow.
|
|
514
|
+
if (this.pendingEnrollments.size >= MAX_PENDING_ENROLLMENTS) {
|
|
515
|
+
throw new TotpError(`Too many pending enrollments (max ${MAX_PENDING_ENROLLMENTS}); wait for existing pendings to expire or be confirmed`, 'TOO_MANY_PENDING');
|
|
516
|
+
}
|
|
517
|
+
// Derive a display label from the primary token name if the caller
|
|
518
|
+
// didn't provide one. Authenticator apps show "<Issuer>:<label>", so
|
|
519
|
+
// including the token name gives multi-device users a way to tell
|
|
520
|
+
// enrollments apart.
|
|
521
|
+
const displayLabel = label
|
|
522
|
+
?? this.data?.tokens[0]?.name
|
|
523
|
+
?? 'console';
|
|
524
|
+
const secret = new Secret({ size: TOTP_SECRET_SIZE_BYTES });
|
|
525
|
+
const pendingId = randomUUID();
|
|
526
|
+
const expiresAt = Date.now() + TOTP_PENDING_TTL_MS;
|
|
527
|
+
this.pendingEnrollments.set(pendingId, {
|
|
528
|
+
secret: secret.base32,
|
|
529
|
+
label: displayLabel,
|
|
530
|
+
expiresAt,
|
|
531
|
+
});
|
|
532
|
+
logger.debug('[ConsoleToken] TOTP enrollment begun', { pendingId, label: displayLabel });
|
|
533
|
+
return {
|
|
534
|
+
pendingId,
|
|
535
|
+
secret: secret.base32,
|
|
536
|
+
otpauthUri: buildTotpUri(secret, displayLabel),
|
|
537
|
+
expiresAt,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Confirm a pending TOTP enrollment. Verifies the code against the pending
|
|
542
|
+
* secret; on success, generates 10 plaintext backup codes, hashes them for
|
|
543
|
+
* storage, and persists the enrollment. Returns the plaintext backup codes
|
|
544
|
+
* exactly once — the caller is responsible for showing them to the user
|
|
545
|
+
* and then discarding them.
|
|
546
|
+
*
|
|
547
|
+
* Wrong codes do NOT consume or invalidate the pending enrollment — the
|
|
548
|
+
* user can retry until it expires. This matches user expectations for
|
|
549
|
+
* "oops, typed the wrong code" and limits the damage from a fat-fingered
|
|
550
|
+
* first attempt.
|
|
551
|
+
*
|
|
552
|
+
* @throws Error if pendingId is unknown, expired, or code invalid.
|
|
553
|
+
*/
|
|
554
|
+
async confirmTotpEnrollment(pendingId, code) {
|
|
555
|
+
if (!this.data) {
|
|
556
|
+
throw new TotpError('Token store not initialized', 'STORE_NOT_INITIALIZED');
|
|
557
|
+
}
|
|
558
|
+
this.sweepExpiredEnrollments();
|
|
559
|
+
const pending = this.pendingEnrollments.get(pendingId);
|
|
560
|
+
if (!pending) {
|
|
561
|
+
throw new TotpError('Pending enrollment not found or expired', 'PENDING_NOT_FOUND');
|
|
562
|
+
}
|
|
563
|
+
// Verify the presented code against the pending secret. `validate` returns
|
|
564
|
+
// the time-step delta (a number, possibly 0) on match, or null on mismatch.
|
|
565
|
+
const totp = new TOTP({
|
|
566
|
+
issuer: TOTP_ISSUER,
|
|
567
|
+
label: pending.label,
|
|
568
|
+
algorithm: TOTP_ALGORITHM,
|
|
569
|
+
digits: TOTP_DIGITS,
|
|
570
|
+
period: TOTP_PERIOD_SECONDS,
|
|
571
|
+
secret: Secret.fromBase32(pending.secret),
|
|
572
|
+
});
|
|
573
|
+
const sanitized = code.replaceAll(/\s/g, '');
|
|
574
|
+
const delta = totp.validate({ token: sanitized, window: TOTP_VALIDATE_WINDOW });
|
|
575
|
+
if (delta === null) {
|
|
576
|
+
SecurityMonitor.logSecurityEvent({
|
|
577
|
+
type: 'TOTP_VERIFICATION_FAILED',
|
|
578
|
+
severity: 'MEDIUM',
|
|
579
|
+
source: 'ConsoleTokenStore.confirmTotpEnrollment',
|
|
580
|
+
details: 'Pending TOTP enrollment failed code verification',
|
|
581
|
+
});
|
|
582
|
+
throw new TotpError(INVALID_TOTP_MESSAGE, 'INVALID_TOTP_CODE');
|
|
583
|
+
}
|
|
584
|
+
// Code is valid — commit enrollment.
|
|
585
|
+
const plaintextCodes = generateBackupCodes();
|
|
586
|
+
const hashedCodes = plaintextCodes.map(hashBackupCode);
|
|
587
|
+
const enrolledAt = new Date().toISOString();
|
|
588
|
+
this.data.totp = {
|
|
589
|
+
enrolled: true,
|
|
590
|
+
secret: pending.secret,
|
|
591
|
+
backupCodes: hashedCodes,
|
|
592
|
+
enrolledAt,
|
|
593
|
+
};
|
|
594
|
+
await this.write(this.data);
|
|
595
|
+
this.pendingEnrollments.delete(pendingId);
|
|
596
|
+
logger.info('[ConsoleToken] TOTP enrollment confirmed', {
|
|
597
|
+
enrolledAt,
|
|
598
|
+
backupCodes: hashedCodes.length,
|
|
599
|
+
});
|
|
600
|
+
SecurityMonitor.logSecurityEvent({
|
|
601
|
+
type: 'TOTP_ENROLLED',
|
|
602
|
+
severity: 'MEDIUM',
|
|
603
|
+
source: 'ConsoleTokenStore.confirmTotpEnrollment',
|
|
604
|
+
details: 'Console TOTP second factor enrolled',
|
|
605
|
+
additionalData: { enrolledAt, backupCodes: hashedCodes.length },
|
|
606
|
+
});
|
|
607
|
+
return { backupCodes: plaintextCodes, enrolledAt };
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Verify a user-presented code. Accepts either a live TOTP code or a
|
|
611
|
+
* single-use backup code. On successful backup-code match, the consumed
|
|
612
|
+
* code's hash is removed from storage and the file is re-written.
|
|
613
|
+
*
|
|
614
|
+
* Returns a discriminated result so the caller can distinguish "consumed
|
|
615
|
+
* a backup code" (which the UI should surface with a warning about
|
|
616
|
+
* remaining count) from "valid TOTP code" (normal case). Returns
|
|
617
|
+
* `{ ok: false }` on any failure — the caller should not retry within
|
|
618
|
+
* the same request lifecycle.
|
|
619
|
+
*/
|
|
620
|
+
async verifyTotp(code) {
|
|
621
|
+
if (!this.data?.totp?.enrolled || !this.data.totp.secret) {
|
|
622
|
+
return { ok: false };
|
|
623
|
+
}
|
|
624
|
+
const sanitized = code.replaceAll(/\s/g, '');
|
|
625
|
+
if (!sanitized)
|
|
626
|
+
return { ok: false };
|
|
627
|
+
// Try live TOTP first — fast path, no disk write.
|
|
628
|
+
const totp = new TOTP({
|
|
629
|
+
issuer: TOTP_ISSUER,
|
|
630
|
+
label: this.data.tokens[0]?.name ?? 'console',
|
|
631
|
+
algorithm: TOTP_ALGORITHM,
|
|
632
|
+
digits: TOTP_DIGITS,
|
|
633
|
+
period: TOTP_PERIOD_SECONDS,
|
|
634
|
+
secret: Secret.fromBase32(this.data.totp.secret),
|
|
635
|
+
});
|
|
636
|
+
if (totp.validate({ token: sanitized, window: TOTP_VALIDATE_WINDOW }) !== null) {
|
|
637
|
+
return { ok: true, method: 'totp', backupCodesRemaining: this.data.totp.backupCodes.length };
|
|
638
|
+
}
|
|
639
|
+
// Fall back to backup code — normalize, hash, constant-time search.
|
|
640
|
+
const normalizedInput = normalizeBackupCode(sanitized);
|
|
641
|
+
const inputHash = hashBackupCode(normalizedInput);
|
|
642
|
+
const inputHashBuf = Buffer.from(inputHash, 'hex');
|
|
643
|
+
let matchIndex = -1;
|
|
644
|
+
for (let i = 0; i < this.data.totp.backupCodes.length; i++) {
|
|
645
|
+
const storedBuf = Buffer.from(this.data.totp.backupCodes[i], 'hex');
|
|
646
|
+
if (storedBuf.length === inputHashBuf.length && timingSafeEqual(inputHashBuf, storedBuf)) {
|
|
647
|
+
matchIndex = i;
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (matchIndex === -1) {
|
|
652
|
+
// Both TOTP and backup code paths exhausted without a match. Emit a
|
|
653
|
+
// single audit event so the security monitor can alert on aggregate
|
|
654
|
+
// failure rates (SecurityMonitor dedups the same type+source within
|
|
655
|
+
// a 60s window, so rapid-fire attacks are collapsed into one entry).
|
|
656
|
+
SecurityMonitor.logSecurityEvent({
|
|
657
|
+
type: 'TOTP_VERIFICATION_FAILED',
|
|
658
|
+
severity: 'MEDIUM',
|
|
659
|
+
source: 'ConsoleTokenStore.verifyTotp',
|
|
660
|
+
details: 'Presented TOTP code matched neither the live secret nor any backup code',
|
|
661
|
+
});
|
|
662
|
+
return { ok: false };
|
|
663
|
+
}
|
|
664
|
+
// Consume the matched backup code — remove from storage and persist.
|
|
665
|
+
this.data.totp.backupCodes.splice(matchIndex, 1);
|
|
666
|
+
await this.write(this.data);
|
|
667
|
+
logger.info('[ConsoleToken] Backup code consumed', {
|
|
668
|
+
remaining: this.data.totp.backupCodes.length,
|
|
669
|
+
});
|
|
670
|
+
SecurityMonitor.logSecurityEvent({
|
|
671
|
+
type: 'TOTP_BACKUP_CODE_CONSUMED',
|
|
672
|
+
severity: 'MEDIUM',
|
|
673
|
+
source: 'ConsoleTokenStore.verifyTotp',
|
|
674
|
+
details: 'TOTP backup code consumed for console authentication',
|
|
675
|
+
additionalData: { remaining: this.data.totp.backupCodes.length },
|
|
676
|
+
});
|
|
677
|
+
return { ok: true, method: 'backup', backupCodesRemaining: this.data.totp.backupCodes.length };
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Disable TOTP. Requires a valid code (TOTP or backup) as confirmation so
|
|
681
|
+
* an attacker who momentarily has access to a live session can't silently
|
|
682
|
+
* strip the second factor.
|
|
683
|
+
*
|
|
684
|
+
* @throws Error if not enrolled or code invalid.
|
|
685
|
+
*/
|
|
686
|
+
async disableTotp(code) {
|
|
687
|
+
if (!this.data) {
|
|
688
|
+
throw new TotpError('Token store not initialized', 'STORE_NOT_INITIALIZED');
|
|
689
|
+
}
|
|
690
|
+
if (!this.isTotpEnrolled()) {
|
|
691
|
+
throw new TotpError('TOTP is not currently enrolled', 'NOT_ENROLLED');
|
|
692
|
+
}
|
|
693
|
+
const result = await this.verifyTotp(code);
|
|
694
|
+
if (!result.ok) {
|
|
695
|
+
throw new TotpError(INVALID_TOTP_MESSAGE, 'INVALID_TOTP_CODE');
|
|
696
|
+
}
|
|
697
|
+
this.data.totp = {
|
|
698
|
+
enrolled: false,
|
|
699
|
+
secret: null,
|
|
700
|
+
backupCodes: [],
|
|
701
|
+
enrolledAt: null,
|
|
702
|
+
};
|
|
703
|
+
await this.write(this.data);
|
|
704
|
+
logger.info('[ConsoleToken] TOTP disabled');
|
|
705
|
+
SecurityMonitor.logSecurityEvent({
|
|
706
|
+
type: 'TOTP_DISABLED',
|
|
707
|
+
severity: 'HIGH',
|
|
708
|
+
source: 'ConsoleTokenStore.disableTotp',
|
|
709
|
+
details: 'Console TOTP second factor disabled — single-factor auth restored',
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
/** Remove any pending enrollments whose TTL has passed. */
|
|
713
|
+
sweepExpiredEnrollments() {
|
|
714
|
+
const now = Date.now();
|
|
715
|
+
for (const [id, pending] of this.pendingEnrollments) {
|
|
716
|
+
if (pending.expiresAt <= now) {
|
|
717
|
+
this.pendingEnrollments.delete(id);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// --------------------------------------------------------------------
|
|
722
|
+
// end TOTP
|
|
723
|
+
// --------------------------------------------------------------------
|
|
724
|
+
// --------------------------------------------------------------------
|
|
725
|
+
// Token rotation — #1795
|
|
726
|
+
// --------------------------------------------------------------------
|
|
727
|
+
/**
|
|
728
|
+
* Rotate the primary console token. Requires TOTP confirmation (Pattern B).
|
|
729
|
+
*
|
|
730
|
+
* Flow:
|
|
731
|
+
* 1. Verify the confirmation code via `verifyTotp()` (live TOTP or backup code).
|
|
732
|
+
* 2. Stash the old token value in the in-memory grace buffer so in-flight
|
|
733
|
+
* requests from the rotating tab (and any other local process that read
|
|
734
|
+
* the file before the rotation) still authenticate for ROTATION_GRACE_MS.
|
|
735
|
+
* 3. Generate a new 32-byte token, mutate the primary entry in place, write
|
|
736
|
+
* the file atomically, and rebuild the buffer cache.
|
|
737
|
+
* 4. Return the new token inline so the caller can update `DollhouseAuth.token`
|
|
738
|
+
* without a page reload.
|
|
739
|
+
*
|
|
740
|
+
* Pattern A (OS dialog fallback for users without TOTP) is deferred — the
|
|
741
|
+
* caller should gate the UI so the rotate action is only available when
|
|
742
|
+
* TOTP is enrolled.
|
|
743
|
+
*
|
|
744
|
+
* @throws TotpError STORE_NOT_INITIALIZED — store never loaded
|
|
745
|
+
* @throws TotpError TOTP_REQUIRED — TOTP not enrolled, rotation requires second-factor confirmation
|
|
746
|
+
* @throws TotpError INVALID_TOTP_CODE — wrong code
|
|
747
|
+
*/
|
|
748
|
+
async rotatePrimary(confirmationCode) {
|
|
749
|
+
if (!this.data) {
|
|
750
|
+
throw new TotpError('Token store not initialized', 'STORE_NOT_INITIALIZED');
|
|
751
|
+
}
|
|
752
|
+
if (!this.isTotpEnrolled()) {
|
|
753
|
+
throw new TotpError('Token rotation requires TOTP enrollment — enroll a second factor before rotating', 'TOTP_REQUIRED');
|
|
754
|
+
}
|
|
755
|
+
// Step 1: verify the confirmation code.
|
|
756
|
+
const verification = await this.verifyTotp(confirmationCode);
|
|
757
|
+
if (!verification.ok) {
|
|
758
|
+
throw new TotpError(INVALID_TOTP_MESSAGE, 'INVALID_TOTP_CODE');
|
|
759
|
+
}
|
|
760
|
+
// Step 2: stash the old token in the grace buffer.
|
|
761
|
+
const primary = this.data.tokens[0];
|
|
762
|
+
const graceDeadline = Date.now() + ROTATION_GRACE_MS;
|
|
763
|
+
this.sweepExpiredGraceEntries();
|
|
764
|
+
this.graceEntries.push({
|
|
765
|
+
buf: Buffer.from(primary.token, 'utf8'),
|
|
766
|
+
expiresAt: graceDeadline,
|
|
767
|
+
});
|
|
768
|
+
// Step 3: generate a new token, mutate the primary entry, persist.
|
|
769
|
+
const rotatedAt = new Date().toISOString();
|
|
770
|
+
primary.token = generateTokenValue();
|
|
771
|
+
primary.createdAt = rotatedAt;
|
|
772
|
+
primary.lastUsedAt = null;
|
|
773
|
+
primary.createdVia = 'rotation';
|
|
774
|
+
await this.write(this.data);
|
|
775
|
+
this.rebuildTokenBuffers();
|
|
776
|
+
logger.info('[ConsoleToken] Primary token rotated', {
|
|
777
|
+
id: primary.id,
|
|
778
|
+
rotatedAt,
|
|
779
|
+
graceMs: ROTATION_GRACE_MS,
|
|
780
|
+
});
|
|
781
|
+
SecurityMonitor.logSecurityEvent({
|
|
782
|
+
type: 'CONSOLE_TOKEN_ROTATED',
|
|
783
|
+
severity: 'HIGH',
|
|
784
|
+
source: 'ConsoleTokenStore.rotatePrimary',
|
|
785
|
+
details: 'Console primary token rotated via TOTP confirmation',
|
|
786
|
+
additionalData: {
|
|
787
|
+
tokenId: primary.id,
|
|
788
|
+
rotatedAt,
|
|
789
|
+
graceMs: ROTATION_GRACE_MS,
|
|
790
|
+
confirmationMethod: verification.method,
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
return {
|
|
794
|
+
token: primary.token,
|
|
795
|
+
rotatedAt,
|
|
796
|
+
graceUntil: graceDeadline,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/** Remove expired grace entries so they stop matching in verify(). */
|
|
800
|
+
sweepExpiredGraceEntries() {
|
|
801
|
+
const now = Date.now();
|
|
802
|
+
let i = 0;
|
|
803
|
+
while (i < this.graceEntries.length) {
|
|
804
|
+
if (this.graceEntries[i].expiresAt <= now) {
|
|
805
|
+
this.graceEntries.splice(i, 1);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
i++;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// --------------------------------------------------------------------
|
|
813
|
+
// end rotation
|
|
814
|
+
// --------------------------------------------------------------------
|
|
815
|
+
/**
|
|
816
|
+
* Read the token file and distinguish missing from corrupt.
|
|
817
|
+
*
|
|
818
|
+
* Returning a tagged union lets `ensureInitialized()` back up corrupt files
|
|
819
|
+
* before overwriting them — users who hand-edited their tokens with custom
|
|
820
|
+
* names or labels deserve a recovery path instead of a silent destroy.
|
|
821
|
+
*/
|
|
822
|
+
async readWithStatus() {
|
|
823
|
+
let content;
|
|
824
|
+
try {
|
|
825
|
+
content = await readFile(this.filePath, 'utf8');
|
|
826
|
+
}
|
|
827
|
+
catch (err) {
|
|
828
|
+
if (err.code === 'ENOENT')
|
|
829
|
+
return { status: 'missing' };
|
|
830
|
+
return { status: 'corrupt', reason: err instanceof Error ? err.message : String(err) };
|
|
831
|
+
}
|
|
832
|
+
try {
|
|
833
|
+
const parsed = JSON.parse(content);
|
|
834
|
+
const validated = validateTokenFile(parsed);
|
|
835
|
+
if (!validated)
|
|
836
|
+
return { status: 'corrupt', reason: 'schema validation failed' };
|
|
837
|
+
return { status: 'ok', data: validated };
|
|
838
|
+
}
|
|
839
|
+
catch (err) {
|
|
840
|
+
return { status: 'corrupt', reason: err instanceof Error ? err.message : String(err) };
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Copy the current (presumed corrupt) token file to a timestamped backup
|
|
845
|
+
* alongside it so the user can recover hand-edited data after an accidental
|
|
846
|
+
* syntax error. Best-effort — failure to back up does not block creating
|
|
847
|
+
* a fresh file, since the primary goal is keeping the console usable.
|
|
848
|
+
*/
|
|
849
|
+
async backupCorruptFile() {
|
|
850
|
+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-');
|
|
851
|
+
const backupPath = `${this.filePath}.corrupt-${timestamp}`;
|
|
852
|
+
try {
|
|
853
|
+
await copyFile(this.filePath, backupPath);
|
|
854
|
+
logger.warn(`[ConsoleToken] Corrupt token file backed up to ${backupPath} — a fresh token will be created`);
|
|
855
|
+
}
|
|
856
|
+
catch (err) {
|
|
857
|
+
logger.warn('[ConsoleToken] Could not back up corrupt token file, will overwrite in place', {
|
|
858
|
+
error: err instanceof Error ? err.message : String(err),
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Atomically write the token file with owner-only permissions.
|
|
864
|
+
* Uses temp+rename to avoid partial writes on crash.
|
|
865
|
+
*
|
|
866
|
+
* On Windows, `chmod(0o600)` is effectively a no-op because the file
|
|
867
|
+
* system uses ACLs instead of POSIX modes. We log a one-time warning so
|
|
868
|
+
* users on Windows know the token file does not have OS-enforced access
|
|
869
|
+
* control and can decide whether to use additional tooling (icacls, NTFS
|
|
870
|
+
* permissions, or a different storage location).
|
|
871
|
+
*/
|
|
872
|
+
async write(file) {
|
|
873
|
+
await mkdir(RUN_DIR, { recursive: true });
|
|
874
|
+
const tmpFile = `${this.filePath}.${process.pid}.tmp`;
|
|
875
|
+
try {
|
|
876
|
+
await writeFile(tmpFile, JSON.stringify(file, null, 2), 'utf8');
|
|
877
|
+
await chmod(tmpFile, TOKEN_FILE_MODE);
|
|
878
|
+
await rename(tmpFile, this.filePath);
|
|
879
|
+
this.warnIfWindowsPermissions();
|
|
880
|
+
}
|
|
881
|
+
catch (err) {
|
|
882
|
+
try {
|
|
883
|
+
await unlink(tmpFile);
|
|
884
|
+
}
|
|
885
|
+
catch { /* ignore */ }
|
|
886
|
+
throw err;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/** One-shot flag so the Windows permissions warning is logged at most once. */
|
|
890
|
+
windowsWarningLogged = false;
|
|
891
|
+
warnIfWindowsPermissions() {
|
|
892
|
+
if (this.windowsWarningLogged)
|
|
893
|
+
return;
|
|
894
|
+
if (platform() !== 'win32')
|
|
895
|
+
return;
|
|
896
|
+
this.windowsWarningLogged = true;
|
|
897
|
+
logger.warn(`[ConsoleToken] Token file at ${this.filePath} has no OS-enforced access control on Windows ` +
|
|
898
|
+
`(chmod 0o600 is a no-op on this platform). Any process running as the same user can read the file. ` +
|
|
899
|
+
`Consider using NTFS ACLs via 'icacls' for stronger isolation in multi-user environments.`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Read the raw token file from disk without constructing a store.
|
|
904
|
+
* Intended for follower processes that need the primary token to attach
|
|
905
|
+
* to their ingest POSTs. Returns null if the file does not exist or is invalid.
|
|
906
|
+
*
|
|
907
|
+
* @param filePath - Optional override for the token file location
|
|
908
|
+
*/
|
|
909
|
+
export async function readTokenFileRaw(filePath = DEFAULT_TOKEN_FILE) {
|
|
910
|
+
try {
|
|
911
|
+
const content = await readFile(filePath, 'utf8');
|
|
912
|
+
return validateTokenFile(JSON.parse(content));
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Get the primary token value from the token file on disk.
|
|
920
|
+
* Convenience helper for followers and external consumers.
|
|
921
|
+
*/
|
|
922
|
+
export async function getPrimaryTokenFromFile(filePath = DEFAULT_TOKEN_FILE) {
|
|
923
|
+
const file = await readTokenFileRaw(filePath);
|
|
924
|
+
if (!file || file.tokens.length === 0)
|
|
925
|
+
return null;
|
|
926
|
+
return file.tokens[0].token;
|
|
927
|
+
}
|
|
928
|
+
/** Export the default file path so callers can reference it in logs/docs. */
|
|
929
|
+
export { DEFAULT_TOKEN_FILE };
|
|
930
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc29sZVRva2VuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3dlYi9jb25zb2xlL2NvbnNvbGVUb2tlbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJDRztBQUVILE9BQU8sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUN0RCxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMvRixPQUFPLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsZUFBZSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ25GLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLCtDQUErQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFL0Msa0VBQWtFO0FBQ2xFLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUsRUFBRSxZQUFZLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFFckQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLGtCQUFrQixHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUseUJBQXlCLENBQUMsQ0FBQztBQUVwRSx5Q0FBeUM7QUFDekMsTUFBTSxrQkFBa0IsR0FBRyxDQUFVLENBQUM7QUFFdEMsa0VBQWtFO0FBQ2xFLE1BQU0sV0FBVyxHQUFHLEVBQUUsQ0FBQztBQUV2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Z2UgYW5kIHZlcmlmaWNhdGlvbiAoIzE3ODApLlxuICpcbiAqIE1hbmFnZXMgdGhlIGZpbGUgYXQgYH4vLmRvbGxob3VzZS9ydW4vY29uc29sZS10b2tlbi5hdXRoLmpzb25gIHdoaWNoXG4gKiBob2xkcyB0aGUgQmVhcmVyIHRva2VucyB0aGF0IGF1dGhlbnRpY2F0ZSByZXF1ZXN0cyB0byB0aGUgd2ViIGNvbnNvbGUuXG4gKiBUaGUgZmlsZSBpcyBjcmVhdGVkIG9uIGZpcnN0IGxlYWRlciBlbGVjdGlvbiBhbmQgcGVyc2lzdHMgYWNyb3NzXG4gKiByZXN0YXJ0cyDigJQgdG9rZW5zIG9ubHkgcm90YXRlIHdoZW4gZXhwbGljaXRseSByZXF1ZXN0ZWQuXG4gKlxuICogVGhlIGAuYXV0aGAgZmlsZW5hbWUgc3VmZml4IGtlZXBzIHRoaXMgc3RhdGUgaXNvbGF0ZWQgZnJvbSBhbnkgbGVnYWN5XG4gKiBuby1hdXRoZW50aWNhdGlvbiBEb2xsaG91c2VNQ1AgaW5zdGFsbGF0aW9uIHJ1bm5pbmcgb24gdGhlIHNhbWVcbiAqIG1hY2hpbmUuIFRoZSBwb3J0LCBsb2NrIGZpbGUsIGFuZCB0b2tlbiBmaWxlIGFsbCBzaGFyZSB0aGUgc2FtZVxuICogaXNvbGF0aW9uIHN0cmF0ZWd5IOKAlCBzZWUgYHNyYy9jb25maWcvZW52LnRzYCBmb3IgdGhlIHBvcnQsIGFuZFxuICogYExlYWRlckVsZWN0aW9uLnRzYCBmb3IgdGhlIGxvY2sgZmlsZS5cbiAqXG4gKiBTY2hlbWEgaXMgZm9yd2FyZC1jb21wYXRpYmxlIHdpdGggbXVsdGktZGV2aWNlLCBtdWx0aS10ZW5hbnQsIGFuZFxuICogc2NvcGUtcmVzdHJpY3RlZCB0b2tlbnMgZm9yIFBoYXNlIDIrLiBQaGFzZSAxIHVzZXMgYSBzaW5nbGUgXCJjb25zb2xlXCJcbiAqIGtpbmQgdG9rZW4gYW5kIHN0dWJzIHRoZSBzY29wZS9ib3VuZGFyeSBjaGVja3MuXG4gKlxuICogRmlsZSBmb3JtYXQgKHZlcnNpb24gMSk6XG4gKiBgYGBqc29uXG4gKiB7XG4gKiAgIFwidmVyc2lvblwiOiAxLFxuICogICBcInRva2Vuc1wiOiBbXG4gKiAgICAge1xuICogICAgICAgXCJpZFwiOiBcIjAxOGUxYTJiLS4uLlwiLFxuICogICAgICAgXCJuYW1lXCI6IFwiS2VybWl0IG9uIG1pY2stTWFjQm9vay1Qcm9cIixcbiAqICAgICAgIFwia2luZFwiOiBcImNvbnNvbGVcIixcbiAqICAgICAgIFwidG9rZW5cIjogXCI8NjQtaGV4PlwiLFxuICogICAgICAgXCJzY29wZXNcIjogW1wiYWRtaW5cIl0sXG4gKiAgICAgICBcImVsZW1lbnRCb3VuZGFyaWVzXCI6IG51bGwsXG4gKiAgICAgICBcInRlbmFudFwiOiBudWxsLFxuICogICAgICAgXCJwbGF0Zm9ybVwiOiBcImxvY2FsXCIsXG4gKiAgICAgICBcImxhYmVsc1wiOiB7fSxcbiAqICAgICAgIFwiY3JlYXRlZEF0XCI6IFwiMjAyNi0wNC0wNFQyMDowMDowMC4wMDBaXCIsXG4gKiAgICAgICBcImxhc3RVc2VkQXRcIjogbnVsbCxcbiAqICAgICAgIFwiY3JlYXRlZFZpYVwiOiBcImluaXRpYWwtc2V0dXBcIlxuICogICAgIH1cbiAqICAgXSxcbiAqICAgXCJ0b3RwXCI6IHsgXCJlbnJvbGxlZFwiOiBmYWxzZSwgXCJzZWNyZXRcIjogbnVsbCwgXCJiYWNrdXBDb2Rlc1wiOiBbXSB9XG4gKiB9XG4gKiBgYGBcbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTc4MFxuICovXG5cbmltcG9ydCB7IGhvbWVkaXIsIGhvc3RuYW1lLCBwbGF0Zm9ybSB9IGZyb20gJ25vZGU6b3MnO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgeyBta2RpciwgcmVhZEZpbGUsIHJlbmFtZSwgd3JpdGVGaWxlLCBjaG1vZCwgdW5saW5rLCBjb3B5RmlsZSB9IGZyb20gJ25vZGU6ZnMvcHJvbWlzZXMnO1xuaW1wb3J0IHsgY3JlYXRlSGFzaCwgcmFuZG9tQnl0ZXMsIHJhbmRvbVVVSUQsIHRpbWluZ1NhZmVFcXVhbCB9IGZyb20gJ25vZGU6Y3J5cHRvJztcbmltcG9ydCB7IFNlY3JldCwgVE9UUCB9IGZyb20gJ290cGF1dGgnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9zZWN1cml0eU1vbml0b3IuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuLyoqIERpcmVjdG9yeSBmb3IgcnVudGltZSBzdGF0ZSBmaWxlcyDigJQgc2FtZSBhcyBMZWFkZXJFbGVjdGlvbi4gKi9cbmNvbnN0IFJVTl9ESVIgPSBqb2luKGhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAncnVuJyk7XG5cbi8qKlxuICogRGVmYXVsdCBwYXRoIHRvIHRoZSBhdXRoZW50aWNhdGVkIGNvbnNvbGUncyB0b2tlbiBmaWxlLlxuICpcbiAqIFRoZSBgLmF1dGhgIHN1ZmZpeCBpc29sYXRlcyB0aGlzIGZyb20gYW55IGxlZ2FjeSBuby1hdXRoZW50aWNhdGlvblxuICogRG9sbGhvdXNlTUNQIGluc3RhbGxhdGlvbiB0aGF0IG1heSBhbHNvIGJlIHJ1bm5pbmcgb24gdGhlIHNhbWVcbiAqIG1hY2hpbmUuIExlZ2FjeSBpbnN0YWxscyBkaWQgbm90IHdyaXRlIGEgdG9rZW4gZmlsZSBhdCBhbGw7IHRoZVxuICogYXV0aGVudGljYXRlZCBjb25zb2xlIHdyaXRlcyBgY29uc29sZS10b2tlbi5hdXRoLmpzb25gLiBDb21iaW5lZCB3aXRoXG4gKiB0aGUgcG9ydCBhbmQgbG9jay1maWxlIHNlcGFyYXRpb24sIHRoaXMgZ2l2ZXMgdGhlIHR3byBnZW5lcmF0aW9ucyBvZlxuICogdGhlIGNvbnNvbGUgZnVsbHkgaW5kZXBlbmRlbnQgc3RhdGUgdHJlZXMgdW5kZXIgYH4vLmRvbGxob3VzZS9ydW4vYC5cbiAqXG4gKiBDYWxsZXJzIGNhbiBvdmVycmlkZSB2aWEgYERPTExIT1VTRV9DT05TT0xFX1RPS0VOX0ZJTEVgIGluIHRoZSBlbnYuXG4gKi9cbmNvbnN0IERFRkFVTFRfVE9LRU5fRklMRSA9IGpvaW4oUlVOX0RJUiwgJ2NvbnNvbGUtdG9rZW4uYXV0aC5qc29uJyk7XG5cbi8qKiBDdXJyZW50IHRva2VuIGZpbGUgc2NoZW1hIHZlcnNpb24uICovXG5jb25zdCBUT0tFTl9GSUxFX1ZFUlNJT04gPSAxIGFzIGNvbnN0O1xuXG4vKiogVG9rZW4gbGVuZ3RoIGluIGJ5dGVzIChwcm9kdWNlcyBhIDY0LWNoYXJhY3RlciBoZXggc3RyaW5nKS4gKi9cbmNvbnN0IFRPS0VOX0JZVEVTID0gMzI7XG5cbi8qKiBGaWxlIG1vZGUgZm9yIHRoZSB0b2tlbiBmaWxlIOKAlCBvd25lciByZWFkL3dyaXRlIG9ubHkuICovXG5jb25zdCBUT0tFTl9GSUxFX01PREUgPSAwbzYwMDtcblxuLyoqXG4gKiBUT1RQIGNvbmZpZ3VyYXRpb24g4oCUIFJGQyA2MjM4IGRlZmF1bHRzLiBUaGVzZSBhcmUgY29tcGlsZWQtaW4gc28gdGhlXG4gKiBvdHBhdXRoIFVSSSBpcyBkZXRlcm1pbmlzdGljIGFuZCBjb21wYXRpYmxlIHdpdGggZXZlcnkgY29tbW9uIGF1dGhlbnRpY2F0b3JcbiAqIGFwcCAoR29vZ2xlIEF1dGhlbnRpY2F0b3IsIDFQYXNzd29yZCwgQXV0aHksIEJpdHdhcmRlbiwgZXRjLikuXG4gKi9cbmNvbnN0IFRPVFBfSVNTVUVSID0gJ0RvbGxob3VzZU1DUCc7XG5jb25zdCBUT1RQX0FMR09SSVRITSA9ICdTSEExJyBhcyBjb25zdDtcbmNvbnN0IFRPVFBfRElHSVRTID0gNjtcbmNvbnN0IFRPVFBfUEVSSU9EX1NFQ09ORFMgPSAzMDtcbmNvbnN0IFRPVFBfU0VDUkVUX1NJWkVfQllURVMgPSAyMDsgLy8gMTYwIGJpdHMg4oCUIHRoZSBSRkMgNjIzOCByZWNvbW1lbmRhdGlvblxuLyoqXG4gKiDCsTIgdGltZSBzdGVwIHRvbGVyYW5jZSAowrE2MHMgZHJpZnQpIOKGkiAxNTAgc2Vjb25kIHRvdGFsIHZhbGlkaXR5IHdpbmRvdy5cbiAqXG4gKiBUaGUgbmFpdmUgY2hvaWNlIHdvdWxkIGJlIGB3aW5kb3c6IDFgICjCsTMwcywgOTBzIHRvdGFsKSB3aGljaCBpcyB0aGUgUkZDXG4gKiA2MjM4IGRlZmF1bHQgYW5kIG1hdGNoZXMgbW9zdCB3ZWIgbG9naW4gZmxvd3Mgd2hlcmUgdGhlIHVzZXIgdHlwZXMgYSBjb2RlXG4gKiB0aGV5IGp1c3QgZ2VuZXJhdGVkLCB3aXRoIHN1Yi1zZWNvbmQgbGF0ZW5jeSB0byB0aGUgc2VydmVyLiBUaGF0IGRvZXNuJ3RcbiAqIGNvdmVyIG91ciByZWFsIGRlcGxveW1lbnQgcHJvZmlsZTpcbiAqXG4gKiAxLiAqKkFzeW5jIGJyaWRnZSBmbG93cyoqIOKAlCBEb2xsaG91c2VCcmlkZ2Ugb3ZlciBadWxpcCAob3Igc2ltaWxhciBjaGF0XG4gKiAgICB0cmFuc3BvcnRzKSBjYW4gYWRkIDEwLTYwIHNlY29uZHMgb2YgbGF0ZW5jeSBiZXR3ZWVuIHRoZSB1c2VyIHR5cGluZ1xuICogICAgdGhlIGNvZGUgYW5kIHRoZSBNQ1Agc2VydmVyIHZlcmlmeWluZyBpdC4gQSBjb2RlIHR5cGVkIHdpdGggYSBmZXdcbiAqICAgIHNlY29uZHMgb2YgbGlmZXRpbWUgcmVtYWluaW5nIGNhbiBzbGlkZSBvdXQgb2YgdGhlIHZhbGlkYXRpb24gd2luZG93XG4gKiAgICBiZWZvcmUgcmVhY2hpbmcgdGhlIHNlcnZlci5cbiAqXG4gKiAyLiAqKkNsb2NrIGRyaWZ0Kiog4oCUIGNvbnRhaW5lcnMsIFZNcywgYW5kIHBob25lcyB3aXRoIGxhenkgTlRQIGNhblxuICogICAgZGlzYWdyZWUgd2l0aCB0aGUgc2VydmVyIGJ5IHRlbnMgb2Ygc2Vjb25kcy4gwrEzMHMgZ2l2ZXMgemVybyBtYXJnaW5cbiAqICAgIGFnYWluc3QgYW55IGRyaWZ0IGF0IGFsbC5cbiAqXG4gKiBgd2luZG93OiAyYCBnaXZlcyA1IHZhbGlkIGNvZGVzIG91dCBvZiAxMF42IHBlciBhdHRlbXB0ICh2cy4gMyB3aXRoXG4gKiB3aW5kb3c9MSkuIENvbWJpbmVkIHdpdGggdGhlIDEwL21pbiByYXRlIGxpbWl0ZXIgYW5kIHRoZSBhbHdheXMtb24gYXV0aFxuICogZ2F0ZSwgYnJ1dGUtZm9yY2Ugc3VjY2VzcyBwcm9iYWJpbGl0eSByZW1haW5zIGJlbG93IDVlLTUgcGVyIG1pbnV0ZSDigJRcbiAqIHdlbGwgd2l0aGluIHRoZSBzZWN1cml0eSBlbnZlbG9wZS4gYHdpbmRvdzogM2AgKMKxOTBzLCAyMTBzIHRvdGFsKSBpc1xuICogZGVmZW5zaWJsZSBidXQgcmVzZXJ2ZWQgZm9yIGZ1dHVyZSBkZXBsb3ltZW50IHByZXNzdXJlOyBlYXNpZXIgdG8gd2lkZW5cbiAqIGxhdGVyIHRoYW4gdG8gbmFycm93IGxhdGVyLlxuICpcbiAqIE5vdGU6IHRoZSB3aW5kb3cgaXMgYW5jaG9yZWQgdG8gdGhlICoqc2VydmVyJ3MgdmVyaWZpY2F0aW9uIGNsb2NrKiosIG5vdFxuICogdGhlIHVzZXIncyBjb2RlLWdlbmVyYXRpb24gY2xvY2suIFdoZW4gYSB1c2VyIGNvbXBsYWlucyB0aGF0IGEgY29kZSB0aGV5XG4gKiBcImp1c3QgdHlwZWRcIiB3YXMgcmVqZWN0ZWQsIHRoZSBlcnJvciBtZXNzYWdlIG51ZGdlcyB0aGVtIHRvIHN1Ym1pdCBjb2Rlc1xuICogd2l0aCBwbGVudHkgb2Ygc3RlcC1saWZldGltZSByZW1haW5pbmcuXG4gKi9cbmNvbnN0IFRPVFBfVkFMSURBVEVfV0lORE9XID0gMjtcblxuLyoqXG4gKiBFcnJvciBtZXNzYWdlIGZvciBhIGZhaWxlZCBUT1RQIGNvZGUgY2hlY2suIEluY2x1ZGVzIGEgaGludCBhYm91dCB0aGVcbiAqIGNvbW1vbiBmYWlsdXJlIG1vZGUgKGNvZGUgYWdlZCBvdXQgaW4gdHJhbnNpdCkgc28gdXNlcnMgb24gaGlnaC1sYXRlbmN5XG4gKiBjaGFubmVscyBrbm93IHdoYXQgdG8gdHJ5IGRpZmZlcmVudGx5IG9uIHJldHJ5LlxuICovXG5jb25zdCBJTlZBTElEX1RPVFBfTUVTU0FHRSA9XG4gICdJbnZhbGlkIFRPVFAgY29kZS4gV2hlbiBjb3B5aW5nIGZyb20gYW4gYXV0aGVudGljYXRvciBhcHAsIHVzZSBhIGNvZGUgJyArXG4gICd3aXRoIHBsZW50eSBvZiBsaWZldGltZSByZW1haW5pbmcg4oCUIGNvZGVzIGNhbiBhZ2Ugb3V0IGluIHRyYW5zaXQgb24gJyArXG4gICdzbG93ZXIgY2hhbm5lbHMgKGUuZy4gY2hhdCBicmlkZ2VzKS4gV2FpdCBmb3IgYSBmcmVzaCBjb2RlIGlmIHVuc3VyZS4nO1xuLyoqIFBlbmRpbmcgZW5yb2xsbWVudHMgZXhwaXJlIHRoaXMgbG9uZyBhZnRlciBiZWdpbigpIHRvIGxpbWl0IGluLW1lbW9yeSBzZWNyZXQgbGlmZXRpbWUuICovXG5jb25zdCBUT1RQX1BFTkRJTkdfVFRMX01TID0gMTAgKiA2MCAqIDEwMDA7IC8vIDEwIG1pbnV0ZXNcbi8qKlxuICogSGFyZCBjYXAgb24gY29uY3VycmVudCBwZW5kaW5nIGVucm9sbG1lbnRzLiBBbiBhdXRoZW50aWNhdGVkIGNhbGxlciB3aG9cbiAqIG1pc2JlaGF2ZXMgY291bGQgb3RoZXJ3aXNlIGZpbGwgdGhlIGluLW1lbW9yeSBNYXAgd2l0aCAxMC1taW51dGUtVFRMXG4gKiBzZWNyZXRzLiBSZWFsaXN0aWMgdXNlcnMgbmVlZCBhdCBtb3N0IDEgcGVuZGluZyBlbnJvbGxtZW50IGF0IGEgdGltZTtcbiAqIDEwIGlzIGdlbmVyb3VzIGhlYWRyb29tIGZvciByZXRyeS1kdXJpbmctZW5yb2xsbWVudCBzY2VuYXJpb3MuXG4gKlxuICogV2hlbiB0aGUgY2FwIGlzIGhpdCwgYGJlZ2luVG90cEVucm9sbG1lbnRgIHRocm93cyBgVE9PX01BTllfUEVORElOR2BcbiAqIChtYXBwZWQgdG8gSFRUUCA0MjkpLiBSZWplY3RpbmcgbG91ZGx5IGlzIHByZWZlcnJlZCBvdmVyIHNpbGVudGx5XG4gKiBldmljdGluZyBzb21lb25lIGVsc2UncyBsZWdpdGltYXRlIHBlbmRpbmcgZW50cnkuXG4gKi9cbmNvbnN0IE1BWF9QRU5ESU5HX0VOUk9MTE1FTlRTID0gMTA7XG5cbi8qKlxuICogR3JhY2Ugd2luZG93IGZvciBvbGQgdG9rZW5zIGFmdGVyIHJvdGF0aW9uIOKAlCAxNSBzZWNvbmRzLlxuICpcbiAqIEFmdGVyIGEgcm90YXRpb24sIHRoZSBwcmV2aW91cyB0b2tlbiB2YWx1ZSBzdGF5cyB2YWxpZCBmb3IgdGhpcyBwZXJpb2QgdG9cbiAqIGNvdmVyIGluLWZsaWdodCByZXF1ZXN0cyBmcm9tIHRoZSByb3RhdGluZyB0YWIgYW5kIGFueSBvdGhlciBsb2NhbCBwcm9jZXNzZXNcbiAqIHRoYXQgcmVhZCB0aGUgdG9rZW4gZmlsZSBidXQgaGF2ZW4ndCBwaWNrZWQgdXAgdGhlIG5ldyB2YWx1ZSB5ZXQuIEFsbFxuICogY29uc3VtZXJzIGFyZSBsb29wYmFjay1vbmx5ICgxMjcuMC4wLjEpLCBzbyBuZXR3b3JrIGxhdGVuY3kgaXMgbmVnbGlnaWJsZVxuICogYW5kIDE1cyBpcyBnZW5lcm91cy5cbiAqXG4gKiBHcmFjZSBlbnRyaWVzIGFyZSBwZXItcm90YXRpb24gYW5kIGluLW1lbW9yeSBvbmx5IOKAlCB0aGV5IGFyZSBuZXZlciBwZXJzaXN0ZWRcbiAqIHRvIGRpc2suIEEgbGVhZGVyIGNyYXNoIGtpbGxzIGFsbCBncmFjZSBlbnRyaWVzLCB3aGljaCBpcyB0aGUgc2FmZSBmYWlsdXJlXG4gKiBtb2RlIChjYWxsZXJzIHJlLWF1dGggd2l0aCB0aGUgbmV3IHRva2VuIGZyb20gdGhlIGZpbGUpLlxuICovXG5jb25zdCBST1RBVElPTl9HUkFDRV9NUyA9IDE1XzAwMDtcblxuLyoqIE51bWJlciBvZiBiYWNrdXAgY29kZXMgZ2VuZXJhdGVkIG9uIGVucm9sbG1lbnQuICovXG5jb25zdCBCQUNLVVBfQ09ERV9DT1VOVCA9IDEwO1xuLyoqIENoYXJhY3RlcnMgcGVyIGJhY2t1cCBjb2RlIOKAlCBDcm9ja2ZvcmQgYmFzZTMyLWlzaCwgbm8gYW1iaWd1b3VzIGNoYXJzLiAqL1xuY29uc3QgQkFDS1VQX0NPREVfTEVOR1RIID0gODtcbi8qKlxuICogQmFja3VwIGNvZGUgYWxwaGFiZXQg4oCUIENyb2NrZm9yZCBiYXNlMzIgKDMyIGNoYXJzLCBleGNsdWRlcyBJL0wvTy9VKS5cbiAqIDUgYml0cyBwZXIgY2hhciDDlyA4IGNoYXJzID0gNDAgYml0cyBwZXIgY29kZSwgcGxlbnR5IGZvciBvbmUtc2hvdCB1c2UuXG4gKi9cbmNvbnN0IEJBQ0tVUF9DT0RFX0FMUEhBQkVUID0gJzAxMjM0NTY3ODlBQkNERUZHSEpLTU5QUVJTVFZXWFlaJztcblxuLyoqXG4gKiBTdHJpY3QgZm9ybWF0IGZvciBjb25zb2xlIHRva2VucyDigJQgNjQgbG93ZXJjYXNlIGhleCBjaGFyYWN0ZXJzLlxuICogVXNlZCBhcyBhIGRlZmVuc2UtaW4tZGVwdGggY2hlY2sgaW4gdmVyaWZ5KCk6IGV2ZW4gaWYgdGhlIGNhbGxlciBmb3Jnb3RcbiAqIHRvIHNhbml0aXplIHRoZSBwcmVzZW50ZWQgdmFsdWUsIHdlIHJlamVjdCBhbnl0aGluZyB0aGF0IGlzbid0IGEgbGVnaXRpbWF0ZVxuICogMjU2LWJpdCBoZXggdG9rZW4gYmVmb3JlIHJlYWNoaW5nIHRoZSBjb25zdGFudC10aW1lIGNvbXBhcmlzb24uXG4gKiBETUNQLVNFQy0wMDQgbWl0aWdhdGlvbi5cbiAqL1xuY29uc3QgVE9LRU5fRk9STUFUID0gL15bMC05YS1mXXs2NH0kLztcblxuLyoqXG4gKiBFbGVtZW50IHZpc2liaWxpdHkgYm91bmRhcnkg4oCUIFBoYXNlIDMgZW50ZXJwcmlzZSBmZWF0dXJlLlxuICogUGhhc2UgMSBhbHdheXMgc3RvcmVzIGBudWxsYDsgdGhlIGZpZWxkIGV4aXN0cyBzbyB0aGUgc2NoZW1hIGlzIHN0YWJsZS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBFbGVtZW50Qm91bmRhcnkge1xuICBhbGxvd0NhdGVnb3JpZXM/OiBzdHJpbmdbXTtcbiAgZGVueUNhdGVnb3JpZXM/OiBzdHJpbmdbXTtcbiAgYWxsb3dUeXBlcz86IHN0cmluZ1tdO1xuICBkZW55VHlwZXM/OiBzdHJpbmdbXTtcbn1cblxuLyoqXG4gKiBBIHNpbmdsZSB0b2tlbiBlbnRyeSBpbiB0aGUgY29uc29sZSB0b2tlbiBmaWxlLlxuICpcbiAqIEVudGVycHJpc2UtcmVhZHkgZmllbGRzIChgc2NvcGVzYCwgYGVsZW1lbnRCb3VuZGFyaWVzYCwgYHRlbmFudGAsIGBwbGF0Zm9ybWAsXG4gKiBgbGFiZWxzYCkgYXJlIHByZXNlbnQgZnJvbSBQaGFzZSAxIGJ1dCBub3QgZW5mb3JjZWQg4oCUIHRoZSBtaWRkbGV3YXJlIHRyZWF0c1xuICogZXZlcnkgdmFsaWQgdG9rZW4gYXMgYWRtaW4tc2NvcGVkLCBzaW5nbGUtdGVuYW50LCBhbGwtZWxlbWVudHMgZm9yIG5vdy5cbiAqIFBoYXNlIDIrIGZsaXBzIGVuZm9yY2VtZW50IG9uIHdpdGhvdXQgcmVxdWlyaW5nIGEgc2NoZW1hIG1pZ3JhdGlvbi5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDb25zb2xlVG9rZW5FbnRyeSB7XG4gIC8qKiBTdGFibGUgdW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoaXMgdG9rZW4gKFVVSUQgdjQpLiAqL1xuICBpZDogc3RyaW5nO1xuICAvKiogSHVtYW4tcmVhZGFibGUgbmFtZSBzaG93biBpbiB0aGUgU2VjdXJpdHkgdGFiLiAqL1xuICBuYW1lOiBzdHJpbmc7XG4gIC8qKiBXaGF0IGtpbmQgb2YgY2xpZW50IHRoaXMgdG9rZW4gaXMgZm9yLiAqL1xuICBraW5kOiAnY29uc29sZScgfCAnZGV2aWNlJyB8ICdhdXRvbWF0aW9uJztcbiAgLyoqIFRoZSBzZWNyZXQgQmVhcmVyIHZhbHVlLiA2NCBoZXggY2hhcnMgKDI1NiBiaXRzIG9mIGVudHJvcHkpLiAqL1xuICB0b2tlbjogc3RyaW5nO1xuICAvKiogU2NvcGVzIGdyYW50ZWQgdG8gdGhpcyB0b2tlbi4gUGhhc2UgMSA9IGFsd2F5cyBgW1wiYWRtaW5cIl1gLiAqL1xuICBzY29wZXM6IHN0cmluZ1tdO1xuICAvKiogRWxlbWVudCB2aXNpYmlsaXR5IHJlc3RyaWN0aW9uLiBQaGFzZSAxID0gYWx3YXlzIGBudWxsYC4gKi9cbiAgZWxlbWVudEJvdW5kYXJpZXM6IEVsZW1lbnRCb3VuZGFyeSB8IG51bGw7XG4gIC8qKiBUZW5hbnQgaWRlbnRpZmllciBmb3IgbXVsdGktdGVuYW50IGRlcGxveW1lbnRzLiBQaGFzZSAxID0gYWx3YXlzIGBudWxsYC4gKi9cbiAgdGVuYW50OiBzdHJpbmcgfCBudWxsO1xuICAvKiogV2hlcmUgdGhpcyB0b2tlbiBpcyB1c2VkLiBQaGFzZSAxID0gYWx3YXlzIGBcImxvY2FsXCJgLiAqL1xuICBwbGF0Zm9ybTogc3RyaW5nO1xuICAvKiogT3BhcXVlIG1ldGFkYXRhIGZvciBlbnRlcnByaXNlIHRvb2xpbmcuIFBoYXNlIDEgPSBhbHdheXMgYHt9YC4gKi9cbiAgbGFiZWxzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+O1xuICAvKiogSVNPIHRpbWVzdGFtcCBvZiB0b2tlbiBjcmVhdGlvbi4gKi9cbiAgY3JlYXRlZEF0OiBzdHJpbmc7XG4gIC8qKiBJU08gdGltZXN0YW1wIG9mIG1vc3QgcmVjZW50IHVzZSwgb3IgbnVsbCBpZiBuZXZlciB1c2VkLiAqL1xuICBsYXN0VXNlZEF0OiBzdHJpbmcgfCBudWxsO1xuICAvKiogSG93IHRoaXMgdG9rZW4gd2FzIGNyZWF0ZWQg4oCUIFwiaW5pdGlhbC1zZXR1cFwiLCBcInBhaXJpbmdcIiwgXCJyb3RhdGlvblwiLCBldGMuICovXG4gIGNyZWF0ZWRWaWE6IHN0cmluZztcbn1cblxuLyoqXG4gKiBUT1RQIGVucm9sbG1lbnQgc3RhdGUuXG4gKlxuICogUG9wdWxhdGVkIGJ5IFBoYXNlIDIgKCMxNzk0KS4gVGhlIGBzZWNyZXRgIGZpZWxkIGhvbGRzIHRoZSBiYXNlMzItZW5jb2RlZFxuICogUkZDIDYyMzggc2VjcmV0IGluIHBsYWludGV4dCDigJQgdGhlIHN1cnJvdW5kaW5nIGZpbGUgaXMgMDYwMCwgd2hpY2ggbWF0Y2hlc1xuICogc3RhbmRhcmQgZmlsZS1iYXNlZCBUT1RQIHN0b3JhZ2UgcHJhY3RpY2UgKFNTSCBrZXlzLCBhZ2UgaWRlbnRpdHkgZmlsZXMsXG4gKiAxUGFzc3dvcmQgdmF1bHQgYmFja3VwcykuIEVuY3J5cHRpbmcgdGhlIHNlY3JldCBhdCByZXN0IHdpdGggYW4gT1Mga2V5Y2hhaW5cbiAqIGlzIGEgZnV0dXJlIGVuaGFuY2VtZW50LlxuICpcbiAqIEJhY2t1cCBjb2RlcyBhcmUgc3RvcmVkIGFzICoqc2hhMjU2IGhleCBoYXNoZXMqKiwgbmV2ZXIgcGxhaW50ZXh0LiBUaGVcbiAqIHBsYWludGV4dCBjb2RlcyBhcmUgc2hvd24gdG8gdGhlIHVzZXIgZXhhY3RseSBvbmNlIGF0IGVucm9sbG1lbnQgY29uZmlybVxuICogdGltZS4gRWFjaCBiYWNrdXAgY29kZSBpcyBzaW5nbGUtdXNlOiBjb25zdW1pbmcgb25lIHJlbW92ZXMgaXRzIGhhc2ggZnJvbVxuICogdGhpcyBhcnJheS5cbiAqXG4gKiBgZW5yb2xsZWRBdGAgaXMgb3B0aW9uYWwgZm9yIGJhY2t3YXJkIGNvbXBhdGliaWxpdHkgd2l0aCBQaGFzZSAxIGZpbGVzLFxuICogd2hpY2ggd2VyZSB3cml0dGVuIHdpdGggb25seSBge2Vucm9sbGVkLCBzZWNyZXQsIGJhY2t1cENvZGVzfWAuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVG90cFN0YXRlIHtcbiAgZW5yb2xsZWQ6IGJvb2xlYW47XG4gIHNlY3JldDogc3RyaW5nIHwgbnVsbDtcbiAgYmFja3VwQ29kZXM6IHN0cmluZ1tdO1xuICBlbnJvbGxlZEF0Pzogc3RyaW5nIHwgbnVsbDtcbn1cblxuLyoqXG4gKiBQdWJsaWMtc2FmZSB2aWV3IG9mIFRPVFAgc3RhdGUg4oCUIG5ldmVyIGxlYWtzIHNlY3JldCBtYXRlcmlhbC5cbiAqIFJldHVybmVkIGZyb20gdGhlIHN0YXR1cyBlbmRwb2ludCBhbmQgZnJvbSBzdG9yZSBtZXRob2RzIHRoYXQgbmVlZCB0b1xuICogcmVwb3J0IGVucm9sbG1lbnQgc3RhdGUgdG8gY2FsbGVycy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBUb3RwU3RhdHVzIHtcbiAgZW5yb2xsZWQ6IGJvb2xlYW47XG4gIGVucm9sbGVkQXQ6IHN0cmluZyB8IG51bGw7XG4gIGJhY2t1cENvZGVzUmVtYWluaW5nOiBudW1iZXI7XG59XG5cbi8qKlxuICogUmVzdWx0IG9mIGBiZWdpblRvdHBFbnJvbGxtZW50YCDigJQgdGhlIGNhbGxlciBuZWVkcyBhbGwgb2YgdGhlc2UgdG8gc2hvdyBhXG4gKiBRUiBjb2RlIGFuZCBtYW51YWwtZW50cnkgZmFsbGJhY2sgaW4gdGhlIFVJLiBOb25lIG9mIHRoaXMgaXMgcGVyc2lzdGVkO1xuICogdGhlIHBlbmRpbmcgc3RhdGUgb25seSBsaXZlcyBpbi1tZW1vcnkgdW50aWwgY29uZmlybWVkLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFRvdHBFbnJvbGxtZW50QmVnaW4ge1xuICBwZW5kaW5nSWQ6IHN0cmluZztcbiAgLyoqIEJhc2UzMi1lbmNvZGVkIFRPVFAgc2VjcmV0IGZvciBtYW51YWwgZW50cnkgKGdyb3VwZWQgZGlzcGxheSBpcyBjYWxsZXIncyBqb2IpLiAqL1xuICBzZWNyZXQ6IHN0cmluZztcbiAgLyoqIEZ1bGwgYG90cGF1dGg6Ly9gIFVSSSBmb3IgYXV0aGVudGljYXRvciBhcHBzIHRvIGltcG9ydC4gKi9cbiAgb3RwYXV0aFVyaTogc3RyaW5nO1xuICAvKiogVGltZXN0YW1wIChtcyBzaW5jZSBlcG9jaCkgd2hlbiB0aGlzIHBlbmRpbmcgZW5yb2xsbWVudCBleHBpcmVzLiAqL1xuICBleHBpcmVzQXQ6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBSZXN1bHQgb2YgYGNvbmZpcm1Ub3RwRW5yb2xsbWVudGAg4oCUIHRoZSBwbGFpbnRleHQgYmFja3VwIGNvZGVzIGFyZVxuICogcmV0dXJuZWQgdG8gdGhlIGNhbGxlciBleGFjdGx5IG9uY2UsIGF0IHRoaXMgcG9pbnQsIGFuZCB0aGVuIG9ubHkgdGhlaXJcbiAqIGhhc2hlcyBhcmUgcmV0YWluZWQuIFRoZSBjYWxsZXIgbXVzdCBkaXNwbGF5IHRoZW0gdG8gdGhlIHVzZXIgaW1tZWRpYXRlbHlcbiAqIGFuZCBuZXZlciBsb2cgdGhlbS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBUb3RwRW5yb2xsbWVudENvbmZpcm0ge1xuICBiYWNrdXBDb2Rlczogc3RyaW5nW107XG4gIGVucm9sbGVkQXQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBSZXN1bHQgb2YgYHJvdGF0ZVByaW1hcnlgIOKAlCByZXR1cm5lZCB0byB0aGUgY2FsbGVyIHNvIHRoZXkgY2FuIHVwZGF0ZVxuICogdGhlIGJyb3dzZXIgdG9rZW4gaW4tcGxhY2UgYW5kIGRpc3BsYXkgdGhlIGdyYWNlLXdpbmRvdyBkZWFkbGluZS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSb3RhdGlvblJlc3VsdCB7XG4gIC8qKiBUaGUgbmV3IDY0LWhleCB0b2tlbiB2YWx1ZS4gVGhlIGNhbGxlciBtdXN0IHRyZWF0IHRoaXMgYXMgYSBzZWNyZXQuICovXG4gIHRva2VuOiBzdHJpbmc7XG4gIC8qKiBJU08gdGltZXN0YW1wIG9mIHRoZSByb3RhdGlvbi4gKi9cbiAgcm90YXRlZEF0OiBzdHJpbmc7XG4gIC8qKiBtcy1zaW5jZS1lcG9jaCBkZWFkbGluZSBhZnRlciB3aGljaCB0aGUgb2xkIHRva2VuIHN0b3BzIHZlcmlmeWluZy4gKi9cbiAgZ3JhY2VVbnRpbDogbnVtYmVyO1xufVxuXG4vKiogSW4tbWVtb3J5IGdyYWNlIGVudHJ5IGZvciBhIHJlY2VudGx5LXJvdGF0ZWQgdG9rZW4uICovXG5pbnRlcmZhY2UgR3JhY2VFbnRyeSB7XG4gIGJ1ZjogQnVmZmVyO1xuICBleHBpcmVzQXQ6IG51bWJlcjtcbn1cblxuLyoqIEludGVybmFsIHBlbmRpbmctZW5yb2xsbWVudCBzdGF0ZSDigJQgc2VjcmV0IGhlbGQgaW4gbWVtb3J5IG9ubHkuICovXG5pbnRlcmZhY2UgUGVuZGluZ0Vucm9sbG1lbnQge1xuICBzZWNyZXQ6IHN0cmluZztcbiAgbGFiZWw6IHN0cmluZztcbiAgZXhwaXJlc0F0OiBudW1iZXI7XG59XG5cbi8qKlxuICogVHlwZWQgZXJyb3IgZm9yIFRPVFAgc3RvcmUgb3BlcmF0aW9ucy4gQ2FycmllcyBhIG1hY2hpbmUtcmVhZGFibGVcbiAqIGBjb2RlYCBmaWVsZCBzbyB0aGUgSFRUUCBsYXllciBjYW4gbWFwIGZhaWx1cmVzIHRvIGNvbnNpc3RlbnQgcmVzcG9uc2VcbiAqIGNvZGVzIHdpdGhvdXQgcGFyc2luZyBmcmVlLWZvcm0gZXJyb3IgbWVzc2FnZXMuIENhbGxlcnMgKENMSSwgVUkpIGNhblxuICogYnJhbmNoIG9uIGBjb2RlYCBpbnN0ZWFkIG9mIHRoZSBodW1hbi1yZWFkYWJsZSBgbWVzc2FnZWAuXG4gKi9cbmV4cG9ydCBjbGFzcyBUb3RwRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZywgcHVibGljIHJlYWRvbmx5IGNvZGU6IFRvdHBFcnJvckNvZGUpIHtcbiAgICBzdXBlcihtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnVG90cEVycm9yJztcbiAgfVxufVxuXG4vKiogRGlzY3JpbWluYXRlZCBzZXQgb2YgVE9UUCBmYWlsdXJlIHJlYXNvbnMgc3VyZmFjZWQgYnkgdGhlIHN0b3JlLiAqL1xuZXhwb3J0IHR5cGUgVG90cEVycm9yQ29kZSA9XG4gIHwgJ0FMUkVBRFlfRU5ST0xMRUQnXG4gIHwgJ05PVF9FTlJPTExFRCdcbiAgfCAnUEVORElOR19OT1RfRk9VTkQnXG4gIHwgJ0lOVkFMSURfVE9UUF9DT0RFJ1xuICB8ICdTVE9SRV9OT1RfSU5JVElBTElaRUQnXG4gIHwgJ1RPT19NQU5ZX1BFTkRJTkcnXG4gIHwgJ1RPVFBfUkVRVUlSRUQnO1xuXG4vKipcbiAqIFRoZSBmdWxsIG9uLWRpc2sgdG9rZW4gZmlsZSBzdHJ1Y3R1cmUuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ29uc29sZVRva2VuRmlsZSB7XG4gIHZlcnNpb246IHR5cGVvZiBUT0tFTl9GSUxFX1ZFUlNJT047XG4gIHRva2VuczogQ29uc29sZVRva2VuRW50cnlbXTtcbiAgdG90cDogVG90cFN0YXRlO1xufVxuXG4vKipcbiAqIEEgc2FmZS10by1sb2cgdmlldyBvZiBhIHRva2VuIGVudHJ5IOKAlCB0aGUgc2VjcmV0IGB0b2tlbmAgZmllbGQgaXNcbiAqIHJlcGxhY2VkIHdpdGggYSBtYXNrZWQgcHJldmlldyBzbyBpdCBuZXZlciBhcHBlYXJzIGluIGxvZ3Mgb3IgQVBJIHJlc3BvbnNlcy5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBNYXNrZWRUb2tlbkVudHJ5IGV4dGVuZHMgT21pdDxDb25zb2xlVG9rZW5FbnRyeSwgJ3Rva2VuJz4ge1xuICB0b2tlblByZXZpZXc6IHN0cmluZztcbn1cblxuLyoqXG4gKiBHZW5lcmF0ZSBhIGNyeXB0b2dyYXBoaWNhbGx5IHJhbmRvbSB0b2tlbi5cbiAqIFJldHVybnMgNjQgaGV4IGNoYXJhY3RlcnMgKDI1NiBiaXRzIG9mIGVudHJvcHkpLlxuICovXG5mdW5jdGlvbiBnZW5lcmF0ZVRva2VuVmFsdWUoKTogc3RyaW5nIHtcbiAgcmV0dXJuIHJhbmRvbUJ5dGVzKFRPS0VOX0JZVEVTKS50b1N0cmluZygnaGV4Jyk7XG59XG5cbi8qKlxuICogTWFzayBhIHRva2VuIGZvciBkaXNwbGF5IOKAlCBzaG93cyBmaXJzdCA4IGNoYXJzIG9ubHkuXG4gKi9cbmZ1bmN0aW9uIG1hc2tUb2tlbih0b2tlbjogc3RyaW5nKTogc3RyaW5nIHtcbiAgaWYgKHRva2VuLmxlbmd0aCA8PSA4KSByZXR1cm4gJ+KAouKAouKAouKAouKAouKAouKAouKAoic7XG4gIHJldHVybiBgJHt0b2tlbi5zbGljZSgwLCA4KX0keyfigKInLnJlcGVhdChNYXRoLm1pbig1NiwgdG9rZW4ubGVuZ3RoIC0gOCkpfWA7XG59XG5cbi8qKlxuICogQnVpbGQgYSBodW1hbi1yZWFkYWJsZSBkZWZhdWx0IG5hbWUgZnJvbSBhIHB1cHBldCBuYW1lIGFuZCB0aGUgbWFjaGluZSBob3N0bmFtZS5cbiAqIEV4YW1wbGU6IFwiS2VybWl0IG9uIG1pY2stTWFjQm9vay1Qcm9cIi5cbiAqXG4gKiBUaGUgcHVwcGV0IG5hbWUgaXMgcGFzc2VkIGluIHJhdGhlciB0aGFuIGltcG9ydGVkIHRvIGF2b2lkIGEgY2lyY3VsYXIgZGVwZW5kZW5jeVxuICogd2l0aCBTZXNzaW9uTmFtZXMgKHdoaWNoIG9ubHkgZ2VuZXJhdGVzIHBlci1wcm9jZXNzIG5hbWVzKS5cbiAqL1xuZnVuY3Rpb24gZGVmYXVsdFRva2VuTmFtZShwdXBwZXROYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCBob3N0ID0gaG9zdG5hbWUoKSB8fCAnbG9jYWxob3N0JztcbiAgcmV0dXJuIGAke3B1cHBldE5hbWV9IG9uICR7aG9zdH1gO1xufVxuXG4vKipcbiAqIEdlbmVyYXRlIGEgYmF0Y2ggb2YgcmFuZG9tIGJhY2t1cCBjb2Rlcy4gRWFjaCBjb2RlIGlzIGBCQUNLVVBfQ09ERV9MRU5HVEhgXG4gKiBjaGFyYWN0ZXJzIGRyYXduIHVuaWZvcm1seSBmcm9tIGBCQUNLVVBfQ09ERV9BTFBIQUJFVGAgKENyb2NrZm9yZCBiYXNlMzIsXG4gKiAzMiBjaGFyYWN0ZXJzLCA1IGJpdHMgcGVyIGNoYXIpLiBVc2VzIHJlamVjdGlvbiBzYW1wbGluZyBhZ2FpbnN0IDI1Ni1iaXRcbiAqIHJhbmRvbSBieXRlcyB0byBhdm9pZCBtb2R1bG8gYmlhcy5cbiAqL1xuZnVuY3Rpb24gZ2VuZXJhdGVCYWNrdXBDb2RlcygpOiBzdHJpbmdbXSB7XG4gIC8vIDMyLWNoYXIgYWxwaGFiZXQgZGl2aWRlcyAyNTYgZXZlbmx5ICgyNTYgLyAzMiA9IDgpLCBzbyB0aGUgc2ltcGxlXG4gIC8vIG1vZC0zMiBtYXBwaW5nIGhhcyBubyBiaWFzLiBHZW5lcmF0ZSBvbmUgYnl0ZSBwZXIgY2hhcmFjdGVyLlxuICBjb25zdCBjb2Rlczogc3RyaW5nW10gPSBbXTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBCQUNLVVBfQ09ERV9DT1VOVDsgaSsrKSB7XG4gICAgY29uc3QgYnl0ZXMgPSByYW5kb21CeXRlcyhCQUNLVVBfQ09ERV9MRU5HVEgpO1xuICAgIGxldCBjb2RlID0gJyc7XG4gICAgZm9yIChsZXQgaiA9IDA7IGogPCBCQUNLVVBfQ09ERV9MRU5HVEg7IGorKykge1xuICAgICAgY29kZSArPSBCQUNLVVBfQ09ERV9BTFBIQUJFVFtieXRlc1tqXSAmIDB4MWZdO1xuICAgIH1cbiAgICBjb2Rlcy5wdXNoKGNvZGUpO1xuICB9XG4gIHJldHVybiBjb2Rlcztcbn1cblxuLyoqXG4gKiBIYXNoIGEgYmFja3VwIGNvZGUgZm9yIHN0b3JhZ2UuIHNoYTI1NiBoZXggaXMgcGxlbnR5IOKAlCB0aGVzZSBjb2RlcyBhcmVcbiAqIGhpZ2gtZW50cm9weSAoNDAgYml0cykgYW5kIHdlIG9ubHkgbmVlZCB0byBkZXRlY3QgYSB0YW1wZXIsIG5vdCByZXNpc3RcbiAqIHBhc3N3b3JkLWNyYWNraW5nIG9uIGEgbGVha2VkIGhhc2guXG4gKi9cbmZ1bmN0aW9uIGhhc2hCYWNrdXBDb2RlKGNvZGU6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBjcmVhdGVIYXNoKCdzaGEyNTYnKS51cGRhdGUoY29kZSwgJ3V0ZjgnKS5kaWdlc3QoJ2hleCcpO1xufVxuXG4vKipcbiAqIE5vcm1hbGl6ZSBhIHVzZXItZW50ZXJlZCBiYWNrdXAgY29kZSBiZWZvcmUgaGFzaGluZzogdXBwZXJjYXNlLCBzdHJpcFxuICogd2hpdGVzcGFjZSwgc3RyaXAgZGFzaGVzICh1c2VycyBvZnRlbiB0eXBlIGNvZGVzIGluIGdyb3VwcyBsaWtlXG4gKiBcIlhYWFgtWFhYWFwiKS4gUmV0dXJucyB0aGUgY2Fub25pY2FsIGZvcm0gdGhhdCBtYXRjaGVzIHdoYXQgd2Ugc3RvcmVkLlxuICovXG5mdW5jdGlvbiBub3JtYWxpemVCYWNrdXBDb2RlKHJhdzogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIHJhdy5yZXBsYWNlQWxsKC9bXFxzLV0vZywgJycpLnRvVXBwZXJDYXNlKCk7XG59XG5cbi8qKlxuICogQnVpbGQgdGhlIGZ1bGwgb3RwYXV0aDovLyBVUkkgZm9yIGEgZ2l2ZW4gc2VjcmV0IGFuZCBkaXNwbGF5IGxhYmVsLlxuICogVGhlIGxhYmVsIGlzIFVSSS1lbmNvZGVkIGJ5IGBvdHBhdXRoYCBpbnRlcm5hbGx5IHZpYSBVUklDb21wb25lbnQuXG4gKi9cbmZ1bmN0aW9uIGJ1aWxkVG90cFVyaShzZWNyZXQ6IFNlY3JldCwgbGFiZWw6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IHRvdHAgPSBuZXcgVE9UUCh7XG4gICAgaXNzdWVyOiBUT1RQX0lTU1VFUixcbiAgICBsYWJlbCxcbiAgICBhbGdvcml0aG06IFRPVFBfQUxHT1JJVEhNLFxuICAgIGRpZ2l0czogVE9UUF9ESUdJVFMsXG4gICAgcGVyaW9kOiBUT1RQX1BFUklPRF9TRUNPTkRTLFxuICAgIHNlY3JldCxcbiAgfSk7XG4gIHJldHVybiB0b3RwLnRvU3RyaW5nKCk7XG59XG5cbi8qKlxuICogVmFsaWRhdGUgYSBzaW5nbGUgdG9rZW4gZW50cnkgb2JqZWN0LiBSZXR1cm5zIHRydWUgaWYgdGhlIGVudHJ5IGhhcyBhbGxcbiAqIHJlcXVpcmVkIGZpZWxkcyB3aXRoIHRoZSBjb3JyZWN0IHR5cGVzLiBFeHRyYWN0ZWQgZnJvbSB2YWxpZGF0ZVRva2VuRmlsZVxuICogdG8ga2VlcCB0aGUgdG9wLWxldmVsIHZhbGlkYXRvcidzIGNvZ25pdGl2ZSBjb21wbGV4aXR5IG1hbmFnZWFibGUuXG4gKi9cbmZ1bmN0aW9uIGlzVmFsaWRUb2tlbkVudHJ5KHJhdzogdW5rbm93bik6IGJvb2xlYW4ge1xuICBpZiAoIXJhdyB8fCB0eXBlb2YgcmF3ICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuICBjb25zdCBlID0gcmF3IGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICByZXR1cm4gKFxuICAgIHR5cGVvZiBlLmlkID09PSAnc3RyaW5nJyAmJiBlLmlkLmxlbmd0aCA+IDAgJiZcbiAgICB0eXBlb2YgZS5uYW1lID09PSAnc3RyaW5nJyAmJlxuICAgIHR5cGVvZiBlLnRva2VuID09PSAnc3RyaW5nJyAmJiBlLnRva2VuLmxlbmd0aCA+IDAgJiZcbiAgICB0eXBlb2YgZS5raW5kID09PSAnc3RyaW5nJyAmJlxuICAgIEFycmF5LmlzQXJyYXkoZS5zY29wZXMpICYmXG4gICAgdHlwZW9mIGUuY3JlYXRlZEF0ID09PSAnc3RyaW5nJ1xuICApO1xufVxuXG4vKipcbiAqIFZhbGlkYXRlIHRoYXQgYSBwYXJzZWQgSlNPTiBvYmplY3QgY29uZm9ybXMgdG8gdGhlIGV4cGVjdGVkIHRva2VuIGZpbGUgc2NoZW1hLlxuICogUmV0dXJucyBhIHR5cGVkIENvbnNvbGVUb2tlbkZpbGUgb3IgbnVsbCBpZiBpbnZhbGlkLlxuICpcbiAqIFN0cmljdCB2YWxpZGF0aW9uIOKAlCBhbiB1bnJlY29nbml6ZWQgdmVyc2lvbiBvciBtaXNzaW5nIHJlcXVpcmVkIGZpZWxkc1xuICogY2F1c2VzIHRoZSBmaWxlIHRvIGJlIHRyZWF0ZWQgYXMgY29ycnVwdCBzbyBhIGZyZXNoIG9uZSBjYW4gYmUgd3JpdHRlbi5cbiAqL1xuZnVuY3Rpb24gdmFsaWRhdGVUb2tlbkZpbGUocmF3OiB1bmtub3duKTogQ29uc29sZVRva2VuRmlsZSB8IG51bGwge1xuICBpZiAoIXJhdyB8fCB0eXBlb2YgcmF3ICE9PSAnb2JqZWN0JykgcmV0dXJuIG51bGw7XG4gIGNvbnN0IG9iaiA9IHJhdyBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcblxuICBpZiAob2JqLnZlcnNpb24gIT09IFRPS0VOX0ZJTEVfVkVSU0lPTikgcmV0dXJuIG51bGw7XG4gIGlmICghQXJyYXkuaXNBcnJheShvYmoudG9rZW5zKSkgcmV0dXJuIG51bGw7XG4gIGlmICghb2JqLnRvdHAgfHwgdHlwZW9mIG9iai50b3RwICE9PSAnb2JqZWN0JykgcmV0dXJuIG51bGw7XG4gIGlmICghb2JqLnRva2Vucy5ldmVyeShpc1ZhbGlkVG9rZW5FbnRyeSkpIHJldHVybiBudWxsO1xuXG4gIHJldHVybiByYXcgYXMgQ29uc29sZVRva2VuRmlsZTtcbn1cblxuLyoqXG4gKiBTdGF0ZWZ1bCBzdG9yZSB0aGF0IG93bnMgdGhlIGNvbnNvbGUgdG9rZW4gZmlsZSBhbmQgdmVyaWZpZXMgcHJlc2VudGVkIHRva2Vucy5cbiAqXG4gKiBEZXNpZ25lZCB0byBsaXZlIG9uIHRoZSBsZWFkZXIgcHJvY2Vzcy4gRm9sbG93ZXJzIHNob3VsZCBub3QgY29uc3RydWN0IHRoaXMg4oCUXG4gKiB0aGV5IHJlYWQgdGhlIGZpbGUgZGlyZWN0bHkgdmlhIGByZWFkVG9rZW5GaWxlUmF3KClgIGZvciB0aGVpciBvd24gSFRUUCBjYWxscy5cbiAqL1xuZXhwb3J0IGNsYXNzIENvbnNvbGVUb2tlblN0b3JlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBmaWxlUGF0aDogc3RyaW5nO1xuICBwcml2YXRlIGRhdGE6IENvbnNvbGVUb2tlbkZpbGUgfCBudWxsID0gbnVsbDtcbiAgLyoqXG4gICAqIFByZS1jb252ZXJ0ZWQgQnVmZmVyIGNhY2hlIGtleWVkIGJ5IGVudHJ5IGlkLiBQb3B1bGF0ZWQgd2hlbmV2ZXIgYHRoaXMuZGF0YWBcbiAgICogaXMgYXNzaWduZWQgKGxvYWQsIGNyZWF0ZSwgZnV0dXJlIHJvdGF0aW9uKS4gVmVyaWZ5KCkgcmV1c2VzIHRoZSBzdG9yZWRcbiAgICogYnVmZmVycyBzbyB0aGUgaG90IHBhdGggZG9lc24ndCByZS1hbGxvY2F0ZSBwZXItdG9rZW4gb24gZXZlcnkgcmVxdWVzdC5cbiAgICogTmVnbGlnaWJsZSB3aW4gd2l0aCAxIHRva2VuIHRvZGF5OyBtZWFuaW5nZnVsIHdpdGggUGhhc2UgMiBtdWx0aS10b2tlblxuICAgKiBsb29rdXBzLiBOb3Qgc2VyaWFsaXplZCDigJQgYnVmZmVycyBhcmUgbmV2ZXIgd3JpdHRlbiB0byBkaXNrLlxuICAgKi9cbiAgcHJpdmF0ZSByZWFkb25seSB0b2tlbkJ1ZmZlcnMgPSBuZXcgTWFwPHN0cmluZywgQnVmZmVyPigpO1xuICAvKipcbiAgICogSW4tbWVtb3J5IHBlbmRpbmcgVE9UUCBlbnJvbGxtZW50cywga2V5ZWQgYnkgb3BhcXVlIHBlbmRpbmdJZC4gTm90aGluZ1xuICAgKiBsaXZlcyBvbiBkaXNrIHVudGlsIGNvbmZpcm1Ub3RwRW5yb2xsbWVudCgpIHN1Y2NlZWRzLCB3aGljaCBsaW1pdHMgdGhlXG4gICAqIHdpbmRvdyBpbiB3aGljaCBhIGhhbGYtY29tcGxldGVkIGVucm9sbG1lbnQgbGVha3MgYSBzZWNyZXQgdmlhIGZpbGUgcmVhZC5cbiAgICogRW50cmllcyBleHBpcmUgYWZ0ZXIgVE9UUF9QRU5ESU5HX1RUTF9NUyAoIzE3OTQpLlxuICAgKi9cbiAgcHJpdmF0ZSByZWFkb25seSBwZW5kaW5nRW5yb2xsbWVudHMgPSBuZXcgTWFwPHN0cmluZywgUGVuZGluZ0Vucm9sbG1lbnQ+KCk7XG4gIC8qKlxuICAgKiBJbi1tZW1vcnkgZ3JhY2UgYnVmZmVyIGZvciByZWNlbnRseS1yb3RhdGVkIHRva2VucyAoIzE3OTUpLiBBZnRlciBhXG4gICAqIHJvdGF0aW9uLCB0aGUgb2xkIHRva2VuIHZhbHVlIGlzIHN0YXNoZWQgaGVyZSB3aXRoIGEgcGVyLXJvdGF0aW9uIGV4cGlyeVxuICAgKiBzbyBpbi1mbGlnaHQgcmVxdWVzdHMgdGhhdCB3ZXJlIHNlbnQgYmVmb3JlIHRoZSByb3RhdGlvbiByZXNwb25zZSBhcnJpdmVkXG4gICAqIHN0aWxsIGF1dGhlbnRpY2F0ZS4gRW50cmllcyBhcmUgcGVyLXJvdGF0aW9uIChjb25jdXJyZW50IHJvdGF0aW9ucyBlYWNoXG4gICAqIGdldCB0aGVpciBvd24gZ3JhY2Ugc2xvdCkgYW5kIG5ldmVyIHBlcnNpc3RlZCB0byBkaXNrLlxuICAgKi9cbiAgcHJpdmF0ZSByZWFkb25seSBncmFjZUVudHJpZXM6IEdyYWNlRW50cnlbXSA9IFtdO1xuXG4gIGNvbnN0cnVjdG9yKGZpbGVQYXRoOiBzdHJpbmcgPSBERUZBVUxUX1RPS0VOX0ZJTEUpIHtcbiAgICB0aGlzLmZpbGVQYXRoID0gZmlsZVBhdGg7XG4gIH1cblxuICAvKipcbiAgICogUmVidWlsZCB0aGUgdG9rZW4gYnVmZmVyIGNhY2hlIGFmdGVyIGEgZGF0YSBsb2FkLCBjcmVhdGUsIG9yIG11dGF0aW9uLlxuICAgKiBLZWVwcyB0aGUgaG90IHZlcmlmeSgpIHBhdGggYWxsb2NhdGlvbi1mcmVlIGZvciB0aGUgc3RvcmVkIHNpZGUuXG4gICAqL1xuICBwcml2YXRlIHJlYnVpbGRUb2tlbkJ1ZmZlcnMoKTogdm9pZCB7XG4gICAgdGhpcy50b2tlbkJ1ZmZlcnMuY2xlYXIoKTtcbiAgICBpZiAoIXRoaXMuZGF0YSkgcmV0dXJuO1xuICAgIGZvciAoY29uc3QgZW50cnkgb2YgdGhpcy5kYXRhLnRva2Vucykge1xuICAgICAgdGhpcy50b2tlbkJ1ZmZlcnMuc2V0KGVudHJ5LmlkLCBCdWZmZXIuZnJvbShlbnRyeS50b2tlbiwgJ3V0ZjgnKSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJlYWQgdGhlIGV4aXN0aW5nIHRva2VuIGZpbGUsIG9yIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCBhIHNpbmdsZSBpbml0aWFsXG4gICAqIHRva2VuIGlmIG5vbmUgZXhpc3RzLiBJZGVtcG90ZW50IOKAlCBzYWZlIHRvIGNhbGwgb24gZXZlcnkgbGVhZGVyIGVsZWN0aW9uLlxuICAgKlxuICAgKiBAcGFyYW0gcHVwcGV0TmFtZSAtIEEgcHVwcGV0IG5hbWUgcGlja2VkIGJ5IHRoZSBjYWxsZXIgKGUuZy4gZnJvbSBTZXNzaW9uTmFtZXMpXG4gICAqICAgICAgICAgICAgICAgICAgICAgdXNlZCB0byBidWlsZCB0aGUgZGVmYXVsdCBkaXNwbGF5IG5hbWUgb24gZmlyc3QgcnVuLlxuICAgKiBAcmV0dXJucyBUaGUgcHJpbWFyeSAoZmlyc3QpIHRva2VuIGVudHJ5IOKAlCBjb252ZW5pZW50IGZvciBzZXJ2ZXIgc3RhcnR1cFxuICAgKiAgICAgICAgICB0byBpbmplY3QgaW50byBIVE1MIGFuZCBzdGFtcCBvbiBmb2xsb3dlcnMuXG4gICAqL1xuICBhc3luYyBlbnN1cmVJbml0aWFsaXplZChwdXBwZXROYW1lOiBzdHJpbmcpOiBQcm9taXNlPENvbnNvbGVUb2tlbkVudHJ5PiB7XG4gICAgY29uc3QgcmVhZFJlc3VsdCA9IGF3YWl0IHRoaXMucmVhZFdpdGhTdGF0dXMoKTtcbiAgICBpZiAocmVhZFJlc3VsdC5zdGF0dXMgPT09ICdvaycgJiYgcmVhZFJlc3VsdC5kYXRhLnRva2Vucy5sZW5ndGggPiAwKSB7XG4gICAgICB0aGlzLmRhdGEgPSByZWFkUmVzdWx0LmRhdGE7XG4gICAgICB0aGlzLnJlYnVpbGRUb2tlbkJ1ZmZlcnMoKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnW0NvbnNvbGVUb2tlbl0gTG9hZGVkIGV4aXN0aW5nIHRva2VuIGZpbGUnLCB7XG4gICAgICAgIHBhdGg6IHRoaXMuZmlsZVBhdGgsXG4gICAgICAgIGNvdW50OiByZWFkUmVzdWx0LmRhdGEudG9rZW5zLmxlbmd0aCxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHJlYWRSZXN1bHQuZGF0YS50b2tlbnNbMF07XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlIGZpbGUgZXhpc3RlZCBidXQgd2FzIGNvcnJ1cHQsIGJhY2sgaXQgdXAgYmVmb3JlIG92ZXJ3cml0aW5nLlxuICAgIC8vIFVzZXJzIG1heSBoYXZlIGhhbmQtZWRpdGVkIHRoZSBmaWxlIHdpdGggY3VzdG9tIG5hbWVzL2xhYmVscyDigJQgZG9uJ3RcbiAgICAvLyBkZXN0cm95IHRoZWlyIGRhdGEgc2lsZW50bHkuIEEgdGltZXN0YW1wZWQgY29weSBsZXRzIHRoZW0gcmVjb3Zlci5cbiAgICBpZiAocmVhZFJlc3VsdC5zdGF0dXMgPT09ICdjb3JydXB0Jykge1xuICAgICAgYXdhaXQgdGhpcy5iYWNrdXBDb3JydXB0RmlsZSgpO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZSBhIGZyZXNoIGZpbGUgd2l0aCBvbmUgaW5pdGlhbCB0b2tlblxuICAgIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgICBjb25zdCBpbml0aWFsOiBDb25zb2xlVG9rZW5FbnRyeSA9IHtcbiAgICAgIGlkOiByYW5kb21VVUlEKCksXG4gICAgICBuYW1lOiBkZWZhdWx0VG9rZW5OYW1lKHB1cHBldE5hbWUpLFxuICAgICAga2luZDogJ2NvbnNvbGUnLFxuICAgICAgdG9rZW46IGdlbmVyYXRlVG9rZW5WYWx1ZSgpLFxuICAgICAgc2NvcGVzOiBbJ2FkbWluJ10sXG4gICAgICBlbGVtZW50Qm91bmRhcmllczogbnVsbCxcbiAgICAgIHRlbmFudDogbnVsbCxcbiAgICAgIHBsYXRmb3JtOiAnbG9jYWwnLFxuICAgICAgbGFiZWxzOiB7fSxcbiAgICAgIGNyZWF0ZWRBdDogbm93LFxuICAgICAgbGFzdFVzZWRBdDogbnVsbCxcbiAgICAgIGNyZWF0ZWRWaWE6ICdpbml0aWFsLXNldHVwJyxcbiAgICB9O1xuXG4gICAgY29uc3QgZmlsZTogQ29uc29sZVRva2VuRmlsZSA9IHtcbiAgICAgIHZlcnNpb246IFRPS0VOX0ZJTEVfVkVSU0lPTixcbiAgICAgIHRva2VuczogW2luaXRpYWxdLFxuICAgICAgdG90cDogeyBlbnJvbGxlZDogZmFsc2UsIHNlY3JldDogbnVsbCwgYmFja3VwQ29kZXM6IFtdLCBlbnJvbGxlZEF0OiBudWxsIH0sXG4gICAgfTtcblxuICAgIGF3YWl0IHRoaXMud3JpdGUoZmlsZSk7XG4gICAgdGhpcy5kYXRhID0gZmlsZTtcbiAgICB0aGlzLnJlYnVpbGRUb2tlbkJ1ZmZlcnMoKTtcbiAgICBsb2dnZXIuaW5mbygnW0NvbnNvbGVUb2tlbl0gQ3JlYXRlZCBuZXcgdG9rZW4gZmlsZScsIHtcbiAgICAgIHBhdGg6IHRoaXMuZmlsZVBhdGgsXG4gICAgICBpZDogaW5pdGlhbC5pZCxcbiAgICAgIG5hbWU6IGluaXRpYWwubmFtZSxcbiAgICB9KTtcbiAgICByZXR1cm4gaW5pdGlhbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBWZXJpZnkgYSBwcmVzZW50ZWQgQmVhcmVyIHRva2VuIGFnYWluc3QgdGhlIHN0b3JlZCBlbnRyaWVzLlxuICAgKiBVc2VzIHRpbWluZy1zYWZlIGNvbXBhcmlzb24gdG8gcHJldmVudCBzaWRlLWNoYW5uZWwgYXR0YWNrcy5cbiAgICpcbiAgICogVXBkYXRlcyBgbGFzdFVzZWRBdGAgb24gdGhlIG1hdGNoZWQgZW50cnkgKGluIG1lbW9yeSBvbmx5OyBkaXNrIHdyaXRlXG4gICAqIGlzIGRlYm91bmNlZCB0byBhdm9pZCBkaXNrIHRocmFzaCBvbiBldmVyeSByZXF1ZXN0IOKAlCBQaGFzZSAyIGZlYXR1cmUpLlxuICAgKlxuICAgKiBAcmV0dXJucyBUaGUgbWF0Y2hpbmcgZW50cnksIG9yIG51bGwgaWYgbm8gbWF0Y2guXG4gICAqL1xuICB2ZXJpZnkocHJlc2VudGVkOiBzdHJpbmcpOiBDb25zb2xlVG9rZW5FbnRyeSB8IG51bGwge1xuICAgIGlmICghdGhpcy5kYXRhIHx8ICFwcmVzZW50ZWQpIHJldHVybiBudWxsO1xuXG4gICAgLy8gRE1DUC1TRUMtMDA0OiBOb3JtYWxpemUgdGhlIHByZXNlbnRlZCB0b2tlbiB0byBORkMgYW5kIHZhbGlkYXRlIHRoZVxuICAgIC8vIHN0cmljdCBoZXggZm9ybWF0IGJlZm9yZSBhbnkgY29tcGFyaXNvbi4gVGhpcyBibG9ja3MgVW5pY29kZSBhYnVzZVxuICAgIC8vIChob21vZ3JhcGhzLCB6ZXJvLXdpZHRoLCBiaWRpIG92ZXJyaWRlcykgZnJvbSByZWFjaGluZyB0aW1pbmdTYWZlRXF1YWwuXG4gICAgLy8gRGVmZW5zZS1pbi1kZXB0aCDigJQgdGhlIG1pZGRsZXdhcmUgYWxyZWFkeSBzYW5pdGl6ZXMsIGJ1dCB2ZXJpZnkoKSBpcyBhXG4gICAgLy8gcHVibGljIEFQSSB0aGF0IGFueSBmdXR1cmUgY2FsbGVyIGNvdWxkIGludm9rZSBkaXJlY3RseS5cbiAgICBjb25zdCBub3JtYWxpemVkID0gVW5pY29kZVZhbGlkYXRvci5ub3JtYWxpemUocHJlc2VudGVkKS5ub3JtYWxpemVkQ29udGVudDtcbiAgICBpZiAoIVRPS0VOX0ZPUk1BVC50ZXN0KG5vcm1hbGl6ZWQpKSByZXR1cm4gbnVsbDtcblxuICAgIC8vIE9ubHkgdGhlIHByZXNlbnRlZCBzaWRlIGlzIGFsbG9jYXRlZCBwZXItcmVxdWVzdDsgc3RvcmVkIGJ1ZmZlcnMgYXJlXG4gICAgLy8gcHJlLWNvbnZlcnRlZCBpbiB0aGUgdG9rZW5CdWZmZXJzIGNhY2hlIHNvIHRoZSBob3QgbG9vcCBpcyBhbGxvY2F0aW9uLWZyZWUuXG4gICAgY29uc3QgcHJlc2VudGVkQnVmID0gQnVmZmVyLmZyb20obm9ybWFsaXplZCwgJ3V0ZjgnKTtcblxuICAgIGZvciAoY29uc3QgZW50cnkgb2YgdGhpcy5kYXRhLnRva2Vucykge1xuICAgICAgY29uc3Qgc3RvcmVkQnVmID0gdGhpcy50b2tlbkJ1ZmZlcnMuZ2V0KGVudHJ5LmlkKTtcbiAgICAgIGlmICghc3RvcmVkQnVmIHx8IHN0b3JlZEJ1Zi5sZW5ndGggIT09IHByZXNlbnRlZEJ1Zi5sZW5ndGgpIGNvbnRpbnVlO1xuICAgICAgaWYgKHRpbWluZ1NhZmVFcXVhbChwcmVzZW50ZWRCdWYsIHN0b3JlZEJ1ZikpIHtcbiAgICAgICAgZW50cnkubGFzdFVzZWRBdCA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgICAgICAgcmV0dXJuIGVudHJ5O1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIENoZWNrIGdyYWNlIGJ1ZmZlciDigJQgcmVjZW50bHktcm90YXRlZCB0b2tlbnMgdGhhdCBoYXZlbid0IGV4cGlyZWQgeWV0LlxuICAgIC8vIFJldHVybnMgdGhlIHByaW1hcnkgZW50cnkgb24gbWF0Y2ggc28gdGhlIGNhbGxlciBnZXRzIHRoZSBzYW1lIGFkbWluXG4gICAgLy8gc2hhcGU7IHRoZSBncmFjZSB3aW5kb3cganVzdCBleHRlbmRzIHRoZSBhdXRoZW50aWNhdGlvbiBwZXJpb2QuXG4gICAgdGhpcy5zd2VlcEV4cGlyZWRHcmFjZUVudHJpZXMoKTtcbiAgICBmb3IgKGNvbnN0IGdyYWNlIG9mIHRoaXMuZ3JhY2VFbnRyaWVzKSB7XG4gICAgICBpZiAoZ3JhY2UuYnVmLmxlbmd0aCA9PT0gcHJlc2VudGVkQnVmLmxlbmd0aCAmJiB0aW1pbmdTYWZlRXF1YWwocHJlc2VudGVkQnVmLCBncmFjZS5idWYpKSB7XG4gICAgICAgIC8vIFRvdWNoIHRoZSBwcmltYXJ5IGVudHJ5IOKAlCB0aGUgcmVxdWVzdCBpcyBzdGlsbCBcInVzaW5nXCIgdGhpcyB0b2tlbiBzbG90LlxuICAgICAgICBpZiAodGhpcy5kYXRhLnRva2Vucy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgdGhpcy5kYXRhLnRva2Vuc1swXS5sYXN0VXNlZEF0ID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLmRhdGEudG9rZW5zWzBdID8/IG51bGw7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBwcmltYXJ5IHRva2VuIHZhbHVlIGZvciBpbmplY3Rpb24gaW50byBIVE1MIG9yIGZvcndhcmRlciBjb25maWcuXG4gICAqIFJldHVybnMgdGhlIGZpcnN0IGVudHJ5J3MgdG9rZW4gc3RyaW5nLCBvciBudWxsIGlmIHVuaW5pdGlhbGl6ZWQuXG4gICAqL1xuICBnZXRQcmltYXJ5VG9rZW5WYWx1ZSgpOiBzdHJpbmcgfCBudWxsIHtcbiAgICBpZiAoIXRoaXMuZGF0YSB8fCB0aGlzLmRhdGEudG9rZW5zLmxlbmd0aCA9PT0gMCkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIHRoaXMuZGF0YS50b2tlbnNbMF0udG9rZW47XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJuIGFsbCB0b2tlbnMgd2l0aCB0aGUgc2VjcmV0IHZhbHVlIG1hc2tlZCDigJQgc2FmZSB0byBzZXJpYWxpemUgZm9yXG4gICAqIHRoZSBTZWN1cml0eSB0YWIgVUkgb3IgYEdFVCAvYXBpL2NvbnNvbGUvdG9rZW4vaW5mb2AgcmVzcG9uc2VzLlxuICAgKi9cbiAgbGlzdE1hc2tlZCgpOiBNYXNrZWRUb2tlbkVudHJ5W10ge1xuICAgIGlmICghdGhpcy5kYXRhKSByZXR1cm4gW107XG4gICAgcmV0dXJuIHRoaXMuZGF0YS50b2tlbnMubWFwKCh7IHRva2VuLCAuLi5yZXN0IH0pID0+ICh7XG4gICAgICAuLi5yZXN0LFxuICAgICAgdG9rZW5QcmV2aWV3OiBtYXNrVG9rZW4odG9rZW4pLFxuICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIHBhdGggdG8gdGhlIHRva2VuIGZpbGUgb24gZGlzay5cbiAgICovXG4gIGdldEZpbGVQYXRoKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuZmlsZVBhdGg7XG4gIH1cblxuICAvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAvLyBUT1RQIOKAlCBQaGFzZSAyICgjMTc5NClcbiAgLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuICAvKipcbiAgICogUmV0dXJucyBhIHNhZmUtdG8tc2VyaWFsaXplIHZpZXcgb2YgVE9UUCBlbnJvbGxtZW50IHN0YXRlLiBOZXZlciBsZWFrc1xuICAgKiB0aGUgc2VjcmV0IG9yIGFueSBiYWNrdXAgY29kZSBtYXRlcmlhbC5cbiAgICovXG4gIGdldFRvdHBTdGF0dXMoKTogVG90cFN0YXR1cyB7XG4gICAgY29uc3QgdG90cCA9IHRoaXMuZGF0YT8udG90cDtcbiAgICBpZiAoIXRvdHA/LmVucm9sbGVkKSB7XG4gICAgICByZXR1cm4geyBlbnJvbGxlZDogZmFsc2UsIGVucm9sbGVkQXQ6IG51bGwsIGJhY2t1cENvZGVzUmVtYWluaW5nOiAwIH07XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBlbnJvbGxlZDogdHJ1ZSxcbiAgICAgIGVucm9sbGVkQXQ6IHRvdHAuZW5yb2xsZWRBdCA/PyBudWxsLFxuICAgICAgYmFja3VwQ29kZXNSZW1haW5pbmc6IHRvdHAuYmFja3VwQ29kZXMubGVuZ3RoLFxuICAgIH07XG4gIH1cblxuICAvKiogQ29udmVuaWVuY2U6IHRydWUgaWYgdGhlIHVzZXIgaGFzIGEgY29uZmlybWVkIFRPVFAgc2VjcmV0LiAqL1xuICBpc1RvdHBFbnJvbGxlZCgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gQm9vbGVhbih0aGlzLmRhdGE/LnRvdHA/LmVucm9sbGVkICYmIHRoaXMuZGF0YS50b3RwLnNlY3JldCk7XG4gIH1cblxuICAvKipcbiAgICogQmVnaW4gYSBUT1RQIGVucm9sbG1lbnQuIEdlbmVyYXRlcyBhIGZyZXNoIHNlY3JldCwgaG9sZHMgaXQgaW4gdGhlXG4gICAqIGluLW1lbW9yeSBwZW5kaW5nIG1hcCwgYW5kIHJldHVybnMgdGhlIGRhdGEgdGhlIFVJIG5lZWRzIHRvIHJlbmRlciBhXG4gICAqIFFSIGNvZGUgYW5kIG1hbnVhbC1lbnRyeSBmYWxsYmFjay4gTm90aGluZyBpcyBwZXJzaXN0ZWQgdW50aWxcbiAgICogYGNvbmZpcm1Ub3RwRW5yb2xsbWVudGAgc3VjY2VlZHMuXG4gICAqXG4gICAqIENhbGxlcnMgbWF5IGNhbGwgdGhpcyBtdWx0aXBsZSB0aW1lczsgZWFjaCBjYWxsIHByb2R1Y2VzIGEgbmV3IHBlbmRpbmdJZFxuICAgKiBhbmQgc2VjcmV0LiBPbGQgcGVuZGluZyBlbnRyaWVzIGV4cGlyZSBhZnRlciBUT1RQX1BFTkRJTkdfVFRMX01TLlxuICAgKlxuICAgKiBAdGhyb3dzIEVycm9yIGlmIFRPVFAgaXMgYWxyZWFkeSBlbnJvbGxlZCDigJQgY2FsbGVycyBtdXN0IGRpc2FibGUgZmlyc3QuXG4gICAqL1xuICBiZWdpblRvdHBFbnJvbGxtZW50KGxhYmVsPzogc3RyaW5nKTogVG90cEVucm9sbG1lbnRCZWdpbiB7XG4gICAgaWYgKHRoaXMuaXNUb3RwRW5yb2xsZWQoKSkge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcihcbiAgICAgICAgJ1RPVFAgaXMgYWxyZWFkeSBlbnJvbGxlZCDigJQgZGlzYWJsZSBleGlzdGluZyBlbnJvbGxtZW50IGJlZm9yZSBlbnJvbGxpbmcgYWdhaW4nLFxuICAgICAgICAnQUxSRUFEWV9FTlJPTExFRCcsXG4gICAgICApO1xuICAgIH1cbiAgICB0aGlzLnN3ZWVwRXhwaXJlZEVucm9sbG1lbnRzKCk7XG4gICAgLy8gUmVqZWN0IGlmIHdlJ3JlIGFscmVhZHkgYXQgdGhlIHBlci1wcm9jZXNzIGNhcCBvbiBjb25jdXJyZW50IHBlbmRpbmdzLlxuICAgIC8vIFN3ZWVwIHJhbiBhYm92ZSwgc28gYW55IHNsb3Qgc3RpbGwgaGVsZCBpcyBhIGxpdmUsIHVuZXhwaXJlZCBlbnJvbGxtZW50XG4gICAgLy8gYmVsb25naW5nIHRvIHNvbWVib2R5IGVsc2Ug4oCUIGV2aWN0aW5nIGl0IGNvdWxkIHNpbGVudGx5IGtpbGwgdGhlaXIgZmxvdy5cbiAgICBpZiAodGhpcy5wZW5kaW5nRW5yb2xsbWVudHMuc2l6ZSA+PSBNQVhfUEVORElOR19FTlJPTExNRU5UUykge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcihcbiAgICAgICAgYFRvbyBtYW55IHBlbmRpbmcgZW5yb2xsbWVudHMgKG1heCAke01BWF9QRU5ESU5HX0VOUk9MTE1FTlRTfSk7IHdhaXQgZm9yIGV4aXN0aW5nIHBlbmRpbmdzIHRvIGV4cGlyZSBvciBiZSBjb25maXJtZWRgLFxuICAgICAgICAnVE9PX01BTllfUEVORElORycsXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIERlcml2ZSBhIGRpc3BsYXkgbGFiZWwgZnJvbSB0aGUgcHJpbWFyeSB0b2tlbiBuYW1lIGlmIHRoZSBjYWxsZXJcbiAgICAvLyBkaWRuJ3QgcHJvdmlkZSBvbmUuIEF1dGhlbnRpY2F0b3IgYXBwcyBzaG93IFwiPElzc3Vlcj46PGxhYmVsPlwiLCBzb1xuICAgIC8vIGluY2x1ZGluZyB0aGUgdG9rZW4gbmFtZSBnaXZlcyBtdWx0aS1kZXZpY2UgdXNlcnMgYSB3YXkgdG8gdGVsbFxuICAgIC8vIGVucm9sbG1lbnRzIGFwYXJ0LlxuICAgIGNvbnN0IGRpc3BsYXlMYWJlbCA9IGxhYmVsXG4gICAgICA/PyB0aGlzLmRhdGE/LnRva2Vuc1swXT8ubmFtZVxuICAgICAgPz8gJ2NvbnNvbGUnO1xuXG4gICAgY29uc3Qgc2VjcmV0ID0gbmV3IFNlY3JldCh7IHNpemU6IFRPVFBfU0VDUkVUX1NJWkVfQllURVMgfSk7XG4gICAgY29uc3QgcGVuZGluZ0lkID0gcmFuZG9tVVVJRCgpO1xuICAgIGNvbnN0IGV4cGlyZXNBdCA9IERhdGUubm93KCkgKyBUT1RQX1BFTkRJTkdfVFRMX01TO1xuICAgIHRoaXMucGVuZGluZ0Vucm9sbG1lbnRzLnNldChwZW5kaW5nSWQsIHtcbiAgICAgIHNlY3JldDogc2VjcmV0LmJhc2UzMixcbiAgICAgIGxhYmVsOiBkaXNwbGF5TGFiZWwsXG4gICAgICBleHBpcmVzQXQsXG4gICAgfSk7XG5cbiAgICBsb2dnZXIuZGVidWcoJ1tDb25zb2xlVG9rZW5dIFRPVFAgZW5yb2xsbWVudCBiZWd1bicsIHsgcGVuZGluZ0lkLCBsYWJlbDogZGlzcGxheUxhYmVsIH0pO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIHBlbmRpbmdJZCxcbiAgICAgIHNlY3JldDogc2VjcmV0LmJhc2UzMixcbiAgICAgIG90cGF1dGhVcmk6IGJ1aWxkVG90cFVyaShzZWNyZXQsIGRpc3BsYXlMYWJlbCksXG4gICAgICBleHBpcmVzQXQsXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb25maXJtIGEgcGVuZGluZyBUT1RQIGVucm9sbG1lbnQuIFZlcmlmaWVzIHRoZSBjb2RlIGFnYWluc3QgdGhlIHBlbmRpbmdcbiAgICogc2VjcmV0OyBvbiBzdWNjZXNzLCBnZW5lcmF0ZXMgMTAgcGxhaW50ZXh0IGJhY2t1cCBjb2RlcywgaGFzaGVzIHRoZW0gZm9yXG4gICAqIHN0b3JhZ2UsIGFuZCBwZXJzaXN0cyB0aGUgZW5yb2xsbWVudC4gUmV0dXJucyB0aGUgcGxhaW50ZXh0IGJhY2t1cCBjb2Rlc1xuICAgKiBleGFjdGx5IG9uY2Ug4oCUIHRoZSBjYWxsZXIgaXMgcmVzcG9uc2libGUgZm9yIHNob3dpbmcgdGhlbSB0byB0aGUgdXNlclxuICAgKiBhbmQgdGhlbiBkaXNjYXJkaW5nIHRoZW0uXG4gICAqXG4gICAqIFdyb25nIGNvZGVzIGRvIE5PVCBjb25zdW1lIG9yIGludmFsaWRhdGUgdGhlIHBlbmRpbmcgZW5yb2xsbWVudCDigJQgdGhlXG4gICAqIHVzZXIgY2FuIHJldHJ5IHVudGlsIGl0IGV4cGlyZXMuIFRoaXMgbWF0Y2hlcyB1c2VyIGV4cGVjdGF0aW9ucyBmb3JcbiAgICogXCJvb3BzLCB0eXBlZCB0aGUgd3JvbmcgY29kZVwiIGFuZCBsaW1pdHMgdGhlIGRhbWFnZSBmcm9tIGEgZmF0LWZpbmdlcmVkXG4gICAqIGZpcnN0IGF0dGVtcHQuXG4gICAqXG4gICAqIEB0aHJvd3MgRXJyb3IgaWYgcGVuZGluZ0lkIGlzIHVua25vd24sIGV4cGlyZWQsIG9yIGNvZGUgaW52YWxpZC5cbiAgICovXG4gIGFzeW5jIGNvbmZpcm1Ub3RwRW5yb2xsbWVudChwZW5kaW5nSWQ6IHN0cmluZywgY29kZTogc3RyaW5nKTogUHJvbWlzZTxUb3RwRW5yb2xsbWVudENvbmZpcm0+IHtcbiAgICBpZiAoIXRoaXMuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcignVG9rZW4gc3RvcmUgbm90IGluaXRpYWxpemVkJywgJ1NUT1JFX05PVF9JTklUSUFMSVpFRCcpO1xuICAgIH1cbiAgICB0aGlzLnN3ZWVwRXhwaXJlZEVucm9sbG1lbnRzKCk7XG4gICAgY29uc3QgcGVuZGluZyA9IHRoaXMucGVuZGluZ0Vucm9sbG1lbnRzLmdldChwZW5kaW5nSWQpO1xuICAgIGlmICghcGVuZGluZykge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcignUGVuZGluZyBlbnJvbGxtZW50IG5vdCBmb3VuZCBvciBleHBpcmVkJywgJ1BFTkRJTkdfTk9UX0ZPVU5EJyk7XG4gICAgfVxuXG4gICAgLy8gVmVyaWZ5IHRoZSBwcmVzZW50ZWQgY29kZSBhZ2FpbnN0IHRoZSBwZW5kaW5nIHNlY3JldC4gYHZhbGlkYXRlYCByZXR1cm5zXG4gICAgLy8gdGhlIHRpbWUtc3RlcCBkZWx0YSAoYSBudW1iZXIsIHBvc3NpYmx5IDApIG9uIG1hdGNoLCBvciBudWxsIG9uIG1pc21hdGNoLlxuICAgIGNvbnN0IHRvdHAgPSBuZXcgVE9UUCh7XG4gICAgICBpc3N1ZXI6IFRPVFBfSVNTVUVSLFxuICAgICAgbGFiZWw6IHBlbmRpbmcubGFiZWwsXG4gICAgICBhbGdvcml0aG06IFRPVFBfQUxHT1JJVEhNLFxuICAgICAgZGlnaXRzOiBUT1RQX0RJR0lUUyxcbiAgICAgIHBlcmlvZDogVE9UUF9QRVJJT0RfU0VDT05EUyxcbiAgICAgIHNlY3JldDogU2VjcmV0LmZyb21CYXNlMzIocGVuZGluZy5zZWNyZXQpLFxuICAgIH0pO1xuICAgIGNvbnN0IHNhbml0aXplZCA9IGNvZGUucmVwbGFjZUFsbCgvXFxzL2csICcnKTtcbiAgICBjb25zdCBkZWx0YSA9IHRvdHAudmFsaWRhdGUoeyB0b2tlbjogc2FuaXRpemVkLCB3aW5kb3c6IFRPVFBfVkFMSURBVEVfV0lORE9XIH0pO1xuICAgIGlmIChkZWx0YSA9PT0gbnVsbCkge1xuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnVE9UUF9WRVJJRklDQVRJT05fRkFJTEVEJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICBzb3VyY2U6ICdDb25zb2xlVG9rZW5TdG9yZS5jb25maXJtVG90cEVucm9sbG1lbnQnLFxuICAgICAgICBkZXRhaWxzOiAnUGVuZGluZyBUT1RQIGVucm9sbG1lbnQgZmFpbGVkIGNvZGUgdmVyaWZpY2F0aW9uJyxcbiAgICAgIH0pO1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcihJTlZBTElEX1RPVFBfTUVTU0FHRSwgJ0lOVkFMSURfVE9UUF9DT0RFJyk7XG4gICAgfVxuXG4gICAgLy8gQ29kZSBpcyB2YWxpZCDigJQgY29tbWl0IGVucm9sbG1lbnQuXG4gICAgY29uc3QgcGxhaW50ZXh0Q29kZXMgPSBnZW5lcmF0ZUJhY2t1cENvZGVzKCk7XG4gICAgY29uc3QgaGFzaGVkQ29kZXMgPSBwbGFpbnRleHRDb2Rlcy5tYXAoaGFzaEJhY2t1cENvZGUpO1xuICAgIGNvbnN0IGVucm9sbGVkQXQgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG5cbiAgICB0aGlzLmRhdGEudG90cCA9IHtcbiAgICAgIGVucm9sbGVkOiB0cnVlLFxuICAgICAgc2VjcmV0OiBwZW5kaW5nLnNlY3JldCxcbiAgICAgIGJhY2t1cENvZGVzOiBoYXNoZWRDb2RlcyxcbiAgICAgIGVucm9sbGVkQXQsXG4gICAgfTtcbiAgICBhd2FpdCB0aGlzLndyaXRlKHRoaXMuZGF0YSk7XG4gICAgdGhpcy5wZW5kaW5nRW5yb2xsbWVudHMuZGVsZXRlKHBlbmRpbmdJZCk7XG5cbiAgICBsb2dnZXIuaW5mbygnW0NvbnNvbGVUb2tlbl0gVE9UUCBlbnJvbGxtZW50IGNvbmZpcm1lZCcsIHtcbiAgICAgIGVucm9sbGVkQXQsXG4gICAgICBiYWNrdXBDb2RlczogaGFzaGVkQ29kZXMubGVuZ3RoLFxuICAgIH0pO1xuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdUT1RQX0VOUk9MTEVEJyxcbiAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgIHNvdXJjZTogJ0NvbnNvbGVUb2tlblN0b3JlLmNvbmZpcm1Ub3RwRW5yb2xsbWVudCcsXG4gICAgICBkZXRhaWxzOiAnQ29uc29sZSBUT1RQIHNlY29uZCBmYWN0b3IgZW5yb2xsZWQnLFxuICAgICAgYWRkaXRpb25hbERhdGE6IHsgZW5yb2xsZWRBdCwgYmFja3VwQ29kZXM6IGhhc2hlZENvZGVzLmxlbmd0aCB9LFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHsgYmFja3VwQ29kZXM6IHBsYWludGV4dENvZGVzLCBlbnJvbGxlZEF0IH07XG4gIH1cblxuICAvKipcbiAgICogVmVyaWZ5IGEgdXNlci1wcmVzZW50ZWQgY29kZS4gQWNjZXB0cyBlaXRoZXIgYSBsaXZlIFRPVFAgY29kZSBvciBhXG4gICAqIHNpbmdsZS11c2UgYmFja3VwIGNvZGUuIE9uIHN1Y2Nlc3NmdWwgYmFja3VwLWNvZGUgbWF0Y2gsIHRoZSBjb25zdW1lZFxuICAgKiBjb2RlJ3MgaGFzaCBpcyByZW1vdmVkIGZyb20gc3RvcmFnZSBhbmQgdGhlIGZpbGUgaXMgcmUtd3JpdHRlbi5cbiAgICpcbiAgICogUmV0dXJucyBhIGRpc2NyaW1pbmF0ZWQgcmVzdWx0IHNvIHRoZSBjYWxsZXIgY2FuIGRpc3Rpbmd1aXNoIFwiY29uc3VtZWRcbiAgICogYSBiYWNrdXAgY29kZVwiICh3aGljaCB0aGUgVUkgc2hvdWxkIHN1cmZhY2Ugd2l0aCBhIHdhcm5pbmcgYWJvdXRcbiAgICogcmVtYWluaW5nIGNvdW50KSBmcm9tIFwidmFsaWQgVE9UUCBjb2RlXCIgKG5vcm1hbCBjYXNlKS4gUmV0dXJuc1xuICAgKiBgeyBvazogZmFsc2UgfWAgb24gYW55IGZhaWx1cmUg4oCUIHRoZSBjYWxsZXIgc2hvdWxkIG5vdCByZXRyeSB3aXRoaW5cbiAgICogdGhlIHNhbWUgcmVxdWVzdCBsaWZlY3ljbGUuXG4gICAqL1xuICBhc3luYyB2ZXJpZnlUb3RwKGNvZGU6IHN0cmluZyk6IFByb21pc2U8eyBvazogdHJ1ZTsgbWV0aG9kOiAndG90cCcgfCAnYmFja3VwJzsgYmFja3VwQ29kZXNSZW1haW5pbmc6IG51bWJlciB9IHwgeyBvazogZmFsc2UgfT4ge1xuICAgIGlmICghdGhpcy5kYXRhPy50b3RwPy5lbnJvbGxlZCB8fCAhdGhpcy5kYXRhLnRvdHAuc2VjcmV0KSB7XG4gICAgICByZXR1cm4geyBvazogZmFsc2UgfTtcbiAgICB9XG4gICAgY29uc3Qgc2FuaXRpemVkID0gY29kZS5yZXBsYWNlQWxsKC9cXHMvZywgJycpO1xuICAgIGlmICghc2FuaXRpemVkKSByZXR1cm4geyBvazogZmFsc2UgfTtcblxuICAgIC8vIFRyeSBsaXZlIFRPVFAgZmlyc3Qg4oCUIGZhc3QgcGF0aCwgbm8gZGlzayB3cml0ZS5cbiAgICBjb25zdCB0b3RwID0gbmV3IFRPVFAoe1xuICAgICAgaXNzdWVyOiBUT1RQX0lTU1VFUixcbiAgICAgIGxhYmVsOiB0aGlzLmRhdGEudG9rZW5zWzBdPy5uYW1lID8/ICdjb25zb2xlJyxcbiAgICAgIGFsZ29yaXRobTogVE9UUF9BTEdPUklUSE0sXG4gICAgICBkaWdpdHM6IFRPVFBfRElHSVRTLFxuICAgICAgcGVyaW9kOiBUT1RQX1BFUklPRF9TRUNPTkRTLFxuICAgICAgc2VjcmV0OiBTZWNyZXQuZnJvbUJhc2UzMih0aGlzLmRhdGEudG90cC5zZWNyZXQpLFxuICAgIH0pO1xuICAgIGlmICh0b3RwLnZhbGlkYXRlKHsgdG9rZW46IHNhbml0aXplZCwgd2luZG93OiBUT1RQX1ZBTElEQVRFX1dJTkRPVyB9KSAhPT0gbnVsbCkge1xuICAgICAgcmV0dXJuIHsgb2s6IHRydWUsIG1ldGhvZDogJ3RvdHAnLCBiYWNrdXBDb2Rlc1JlbWFpbmluZzogdGhpcy5kYXRhLnRvdHAuYmFja3VwQ29kZXMubGVuZ3RoIH07XG4gICAgfVxuXG4gICAgLy8gRmFsbCBiYWNrIHRvIGJhY2t1cCBjb2RlIOKAlCBub3JtYWxpemUsIGhhc2gsIGNvbnN0YW50LXRpbWUgc2VhcmNoLlxuICAgIGNvbnN0IG5vcm1hbGl6ZWRJbnB1dCA9IG5vcm1hbGl6ZUJhY2t1cENvZGUoc2FuaXRpemVkKTtcbiAgICBjb25zdCBpbnB1dEhhc2ggPSBoYXNoQmFja3VwQ29kZShub3JtYWxpemVkSW5wdXQpO1xuICAgIGNvbnN0IGlucHV0SGFzaEJ1ZiA9IEJ1ZmZlci5mcm9tKGlucHV0SGFzaCwgJ2hleCcpO1xuICAgIGxldCBtYXRjaEluZGV4ID0gLTE7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLmRhdGEudG90cC5iYWNrdXBDb2Rlcy5sZW5ndGg7IGkrKykge1xuICAgICAgY29uc3Qgc3RvcmVkQnVmID0gQnVmZmVyLmZyb20odGhpcy5kYXRhLnRvdHAuYmFja3VwQ29kZXNbaV0sICdoZXgnKTtcbiAgICAgIGlmIChzdG9yZWRCdWYubGVuZ3RoID09PSBpbnB1dEhhc2hCdWYubGVuZ3RoICYmIHRpbWluZ1NhZmVFcXVhbChpbnB1dEhhc2hCdWYsIHN0b3JlZEJ1ZikpIHtcbiAgICAgICAgbWF0Y2hJbmRleCA9IGk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAobWF0Y2hJbmRleCA9PT0gLTEpIHtcbiAgICAgIC8vIEJvdGggVE9UUCBhbmQgYmFja3VwIGNvZGUgcGF0aHMgZXhoYXVzdGVkIHdpdGhvdXQgYSBtYXRjaC4gRW1pdCBhXG4gICAgICAvLyBzaW5nbGUgYXVkaXQgZXZlbnQgc28gdGhlIHNlY3VyaXR5IG1vbml0b3IgY2FuIGFsZXJ0IG9uIGFnZ3JlZ2F0ZVxuICAgICAgLy8gZmFpbHVyZSByYXRlcyAoU2VjdXJpdHlNb25pdG9yIGRlZHVwcyB0aGUgc2FtZSB0eXBlK3NvdXJjZSB3aXRoaW5cbiAgICAgIC8vIGEgNjBzIHdpbmRvdywgc28gcmFwaWQtZmlyZSBhdHRhY2tzIGFyZSBjb2xsYXBzZWQgaW50byBvbmUgZW50cnkpLlxuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnVE9UUF9WRVJJRklDQVRJT05fRkFJTEVEJyxcbiAgICAgICAgc2V2ZXJpdHk6ICdNRURJVU0nLFxuICAgICAgICBzb3VyY2U6ICdDb25zb2xlVG9rZW5TdG9yZS52ZXJpZnlUb3RwJyxcbiAgICAgICAgZGV0YWlsczogJ1ByZXNlbnRlZCBUT1RQIGNvZGUgbWF0Y2hlZCBuZWl0aGVyIHRoZSBsaXZlIHNlY3JldCBub3IgYW55IGJhY2t1cCBjb2RlJyxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHsgb2s6IGZhbHNlIH07XG4gICAgfVxuXG4gICAgLy8gQ29uc3VtZSB0aGUgbWF0Y2hlZCBiYWNrdXAgY29kZSDigJQgcmVtb3ZlIGZyb20gc3RvcmFnZSBhbmQgcGVyc2lzdC5cbiAgICB0aGlzLmRhdGEudG90cC5iYWNrdXBDb2Rlcy5zcGxpY2UobWF0Y2hJbmRleCwgMSk7XG4gICAgYXdhaXQgdGhpcy53cml0ZSh0aGlzLmRhdGEpO1xuICAgIGxvZ2dlci5pbmZvKCdbQ29uc29sZVRva2VuXSBCYWNrdXAgY29kZSBjb25zdW1lZCcsIHtcbiAgICAgIHJlbWFpbmluZzogdGhpcy5kYXRhLnRvdHAuYmFja3VwQ29kZXMubGVuZ3RoLFxuICAgIH0pO1xuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdUT1RQX0JBQ0tVUF9DT0RFX0NPTlNVTUVEJyxcbiAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgIHNvdXJjZTogJ0NvbnNvbGVUb2tlblN0b3JlLnZlcmlmeVRvdHAnLFxuICAgICAgZGV0YWlsczogJ1RPVFAgYmFja3VwIGNvZGUgY29uc3VtZWQgZm9yIGNvbnNvbGUgYXV0aGVudGljYXRpb24nLFxuICAgICAgYWRkaXRpb25hbERhdGE6IHsgcmVtYWluaW5nOiB0aGlzLmRhdGEudG90cC5iYWNrdXBDb2Rlcy5sZW5ndGggfSxcbiAgICB9KTtcbiAgICByZXR1cm4geyBvazogdHJ1ZSwgbWV0aG9kOiAnYmFja3VwJywgYmFja3VwQ29kZXNSZW1haW5pbmc6IHRoaXMuZGF0YS50b3RwLmJhY2t1cENvZGVzLmxlbmd0aCB9O1xuICB9XG5cbiAgLyoqXG4gICAqIERpc2FibGUgVE9UUC4gUmVxdWlyZXMgYSB2YWxpZCBjb2RlIChUT1RQIG9yIGJhY2t1cCkgYXMgY29uZmlybWF0aW9uIHNvXG4gICAqIGFuIGF0dGFja2VyIHdobyBtb21lbnRhcmlseSBoYXMgYWNjZXNzIHRvIGEgbGl2ZSBzZXNzaW9uIGNhbid0IHNpbGVudGx5XG4gICAqIHN0cmlwIHRoZSBzZWNvbmQgZmFjdG9yLlxuICAgKlxuICAgKiBAdGhyb3dzIEVycm9yIGlmIG5vdCBlbnJvbGxlZCBvciBjb2RlIGludmFsaWQuXG4gICAqL1xuICBhc3luYyBkaXNhYmxlVG90cChjb2RlOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAoIXRoaXMuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcignVG9rZW4gc3RvcmUgbm90IGluaXRpYWxpemVkJywgJ1NUT1JFX05PVF9JTklUSUFMSVpFRCcpO1xuICAgIH1cbiAgICBpZiAoIXRoaXMuaXNUb3RwRW5yb2xsZWQoKSkge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcignVE9UUCBpcyBub3QgY3VycmVudGx5IGVucm9sbGVkJywgJ05PVF9FTlJPTExFRCcpO1xuICAgIH1cbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB0aGlzLnZlcmlmeVRvdHAoY29kZSk7XG4gICAgaWYgKCFyZXN1bHQub2spIHtcbiAgICAgIHRocm93IG5ldyBUb3RwRXJyb3IoSU5WQUxJRF9UT1RQX01FU1NBR0UsICdJTlZBTElEX1RPVFBfQ09ERScpO1xuICAgIH1cbiAgICB0aGlzLmRhdGEudG90cCA9IHtcbiAgICAgIGVucm9sbGVkOiBmYWxzZSxcbiAgICAgIHNlY3JldDogbnVsbCxcbiAgICAgIGJhY2t1cENvZGVzOiBbXSxcbiAgICAgIGVucm9sbGVkQXQ6IG51bGwsXG4gICAgfTtcbiAgICBhd2FpdCB0aGlzLndyaXRlKHRoaXMuZGF0YSk7XG4gICAgbG9nZ2VyLmluZm8oJ1tDb25zb2xlVG9rZW5dIFRPVFAgZGlzYWJsZWQnKTtcbiAgICBTZWN1cml0eU1vbml0b3IubG9nU2VjdXJpdHlFdmVudCh7XG4gICAgICB0eXBlOiAnVE9UUF9ESVNBQkxFRCcsXG4gICAgICBzZXZlcml0eTogJ0hJR0gnLFxuICAgICAgc291cmNlOiAnQ29uc29sZVRva2VuU3RvcmUuZGlzYWJsZVRvdHAnLFxuICAgICAgZGV0YWlsczogJ0NvbnNvbGUgVE9UUCBzZWNvbmQgZmFjdG9yIGRpc2FibGVkIOKAlCBzaW5nbGUtZmFjdG9yIGF1dGggcmVzdG9yZWQnLFxuICAgIH0pO1xuICB9XG5cbiAgLyoqIFJlbW92ZSBhbnkgcGVuZGluZyBlbnJvbGxtZW50cyB3aG9zZSBUVEwgaGFzIHBhc3NlZC4gKi9cbiAgcHJpdmF0ZSBzd2VlcEV4cGlyZWRFbnJvbGxtZW50cygpOiB2b2lkIHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGZvciAoY29uc3QgW2lkLCBwZW5kaW5nXSBvZiB0aGlzLnBlbmRpbmdFbnJvbGxtZW50cykge1xuICAgICAgaWYgKHBlbmRpbmcuZXhwaXJlc0F0IDw9IG5vdykge1xuICAgICAgICB0aGlzLnBlbmRpbmdFbnJvbGxtZW50cy5kZWxldGUoaWQpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gIC8vIGVuZCBUT1RQXG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAgLy8gVG9rZW4gcm90YXRpb24g4oCUICMxNzk1XG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbiAgLyoqXG4gICAqIFJvdGF0ZSB0aGUgcHJpbWFyeSBjb25zb2xlIHRva2VuLiBSZXF1aXJlcyBUT1RQIGNvbmZpcm1hdGlvbiAoUGF0dGVybiBCKS5cbiAgICpcbiAgICogRmxvdzpcbiAgICogMS4gVmVyaWZ5IHRoZSBjb25maXJtYXRpb24gY29kZSB2aWEgYHZlcmlmeVRvdHAoKWAgKGxpdmUgVE9UUCBvciBiYWNrdXAgY29kZSkuXG4gICAqIDIuIFN0YXNoIHRoZSBvbGQgdG9rZW4gdmFsdWUgaW4gdGhlIGluLW1lbW9yeSBncmFjZSBidWZmZXIgc28gaW4tZmxpZ2h0XG4gICAqICAgIHJlcXVlc3RzIGZyb20gdGhlIHJvdGF0aW5nIHRhYiAoYW5kIGFueSBvdGhlciBsb2NhbCBwcm9jZXNzIHRoYXQgcmVhZFxuICAgKiAgICB0aGUgZmlsZSBiZWZvcmUgdGhlIHJvdGF0aW9uKSBzdGlsbCBhdXRoZW50aWNhdGUgZm9yIFJPVEFUSU9OX0dSQUNFX01TLlxuICAgKiAzLiBHZW5lcmF0ZSBhIG5ldyAzMi1ieXRlIHRva2VuLCBtdXRhdGUgdGhlIHByaW1hcnkgZW50cnkgaW4gcGxhY2UsIHdyaXRlXG4gICAqICAgIHRoZSBmaWxlIGF0b21pY2FsbHksIGFuZCByZWJ1aWxkIHRoZSBidWZmZXIgY2FjaGUuXG4gICAqIDQuIFJldHVybiB0aGUgbmV3IHRva2VuIGlubGluZSBzbyB0aGUgY2FsbGVyIGNhbiB1cGRhdGUgYERvbGxob3VzZUF1dGgudG9rZW5gXG4gICAqICAgIHdpdGhvdXQgYSBwYWdlIHJlbG9hZC5cbiAgICpcbiAgICogUGF0dGVybiBBIChPUyBkaWFsb2cgZmFsbGJhY2sgZm9yIHVzZXJzIHdpdGhvdXQgVE9UUCkgaXMgZGVmZXJyZWQg4oCUIHRoZVxuICAgKiBjYWxsZXIgc2hvdWxkIGdhdGUgdGhlIFVJIHNvIHRoZSByb3RhdGUgYWN0aW9uIGlzIG9ubHkgYXZhaWxhYmxlIHdoZW5cbiAgICogVE9UUCBpcyBlbnJvbGxlZC5cbiAgICpcbiAgICogQHRocm93cyBUb3RwRXJyb3IgU1RPUkVfTk9UX0lOSVRJQUxJWkVEIOKAlCBzdG9yZSBuZXZlciBsb2FkZWRcbiAgICogQHRocm93cyBUb3RwRXJyb3IgVE9UUF9SRVFVSVJFRCDigJQgVE9UUCBub3QgZW5yb2xsZWQsIHJvdGF0aW9uIHJlcXVpcmVzIHNlY29uZC1mYWN0b3IgY29uZmlybWF0aW9uXG4gICAqIEB0aHJvd3MgVG90cEVycm9yIElOVkFMSURfVE9UUF9DT0RFIOKAlCB3cm9uZyBjb2RlXG4gICAqL1xuICBhc3luYyByb3RhdGVQcmltYXJ5KGNvbmZpcm1hdGlvbkNvZGU6IHN0cmluZyk6IFByb21pc2U8Um90YXRpb25SZXN1bHQ+IHtcbiAgICBpZiAoIXRoaXMuZGF0YSkge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcignVG9rZW4gc3RvcmUgbm90IGluaXRpYWxpemVkJywgJ1NUT1JFX05PVF9JTklUSUFMSVpFRCcpO1xuICAgIH1cbiAgICBpZiAoIXRoaXMuaXNUb3RwRW5yb2xsZWQoKSkge1xuICAgICAgdGhyb3cgbmV3IFRvdHBFcnJvcihcbiAgICAgICAgJ1Rva2VuIHJvdGF0aW9uIHJlcXVpcmVzIFRPVFAgZW5yb2xsbWVudCDigJQgZW5yb2xsIGEgc2Vjb25kIGZhY3RvciBiZWZvcmUgcm90YXRpbmcnLFxuICAgICAgICAnVE9UUF9SRVFVSVJFRCcsXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIFN0ZXAgMTogdmVyaWZ5IHRoZSBjb25maXJtYXRpb24gY29kZS5cbiAgICBjb25zdCB2ZXJpZmljYXRpb24gPSBhd2FpdCB0aGlzLnZlcmlmeVRvdHAoY29uZmlybWF0aW9uQ29kZSk7XG4gICAgaWYgKCF2ZXJpZmljYXRpb24ub2spIHtcbiAgICAgIHRocm93IG5ldyBUb3RwRXJyb3IoSU5WQUxJRF9UT1RQX01FU1NBR0UsICdJTlZBTElEX1RPVFBfQ09ERScpO1xuICAgIH1cblxuICAgIC8vIFN0ZXAgMjogc3Rhc2ggdGhlIG9sZCB0b2tlbiBpbiB0aGUgZ3JhY2UgYnVmZmVyLlxuICAgIGNvbnN0IHByaW1hcnkgPSB0aGlzLmRhdGEudG9rZW5zWzBdO1xuICAgIGNvbnN0IGdyYWNlRGVhZGxpbmUgPSBEYXRlLm5vdygpICsgUk9UQVRJT05fR1JBQ0VfTVM7XG4gICAgdGhpcy5zd2VlcEV4cGlyZWRHcmFjZUVudHJpZXMoKTtcbiAgICB0aGlzLmdyYWNlRW50cmllcy5wdXNoKHtcbiAgICAgIGJ1ZjogQnVmZmVyLmZyb20ocHJpbWFyeS50b2tlbiwgJ3V0ZjgnKSxcbiAgICAgIGV4cGlyZXNBdDogZ3JhY2VEZWFkbGluZSxcbiAgICB9KTtcblxuICAgIC8vIFN0ZXAgMzogZ2VuZXJhdGUgYSBuZXcgdG9rZW4sIG11dGF0ZSB0aGUgcHJpbWFyeSBlbnRyeSwgcGVyc2lzdC5cbiAgICBjb25zdCByb3RhdGVkQXQgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCk7XG4gICAgcHJpbWFyeS50b2tlbiA9IGdlbmVyYXRlVG9rZW5WYWx1ZSgpO1xuICAgIHByaW1hcnkuY3JlYXRlZEF0ID0gcm90YXRlZEF0O1xuICAgIHByaW1hcnkubGFzdFVzZWRBdCA9IG51bGw7XG4gICAgcHJpbWFyeS5jcmVhdGVkVmlhID0gJ3JvdGF0aW9uJztcblxuICAgIGF3YWl0IHRoaXMud3JpdGUodGhpcy5kYXRhKTtcbiAgICB0aGlzLnJlYnVpbGRUb2tlbkJ1ZmZlcnMoKTtcblxuICAgIGxvZ2dlci5pbmZvKCdbQ29uc29sZVRva2VuXSBQcmltYXJ5IHRva2VuIHJvdGF0ZWQnLCB7XG4gICAgICBpZDogcHJpbWFyeS5pZCxcbiAgICAgIHJvdGF0ZWRBdCxcbiAgICAgIGdyYWNlTXM6IFJPVEFUSU9OX0dSQUNFX01TLFxuICAgIH0pO1xuICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgIHR5cGU6ICdDT05TT0xFX1RPS0VOX1JPVEFURUQnLFxuICAgICAgc2V2ZXJpdHk6ICdISUdIJyxcbiAgICAgIHNvdXJjZTogJ0NvbnNvbGVUb2tlblN0b3JlLnJvdGF0ZVByaW1hcnknLFxuICAgICAgZGV0YWlsczogJ0NvbnNvbGUgcHJpbWFyeSB0b2tlbiByb3RhdGVkIHZpYSBUT1RQIGNvbmZpcm1hdGlvbicsXG4gICAgICBhZGRpdGlvbmFsRGF0YToge1xuICAgICAgICB0b2tlbklkOiBwcmltYXJ5LmlkLFxuICAgICAgICByb3RhdGVkQXQsXG4gICAgICAgIGdyYWNlTXM6IFJPVEFUSU9OX0dSQUNFX01TLFxuICAgICAgICBjb25maXJtYXRpb25NZXRob2Q6IHZlcmlmaWNhdGlvbi5tZXRob2QsXG4gICAgICB9LFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIHRva2VuOiBwcmltYXJ5LnRva2VuLFxuICAgICAgcm90YXRlZEF0LFxuICAgICAgZ3JhY2VVbnRpbDogZ3JhY2VEZWFkbGluZSxcbiAgICB9O1xuICB9XG5cbiAgLyoqIFJlbW92ZSBleHBpcmVkIGdyYWNlIGVudHJpZXMgc28gdGhleSBzdG9wIG1hdGNoaW5nIGluIHZlcmlmeSgpLiAqL1xuICBwcml2YXRlIHN3ZWVwRXhwaXJlZEdyYWNlRW50cmllcygpOiB2b2lkIHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGxldCBpID0gMDtcbiAgICB3aGlsZSAoaSA8IHRoaXMuZ3JhY2VFbnRyaWVzLmxlbmd0aCkge1xuICAgICAgaWYgKHRoaXMuZ3JhY2VFbnRyaWVzW2ldLmV4cGlyZXNBdCA8PSBub3cpIHtcbiAgICAgICAgdGhpcy5ncmFjZUVudHJpZXMuc3BsaWNlKGksIDEpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaSsrO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gIC8vIGVuZCByb3RhdGlvblxuICAvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG4gIC8qKlxuICAgKiBSZWFkIHRoZSB0b2tlbiBmaWxlIGFuZCBkaXN0aW5ndWlzaCBtaXNzaW5nIGZyb20gY29ycnVwdC5cbiAgICpcbiAgICogUmV0dXJuaW5nIGEgdGFnZ2VkIHVuaW9uIGxldHMgYGVuc3VyZUluaXRpYWxpemVkKClgIGJhY2sgdXAgY29ycnVwdCBmaWxlc1xuICAgKiBiZWZvcmUgb3ZlcndyaXRpbmcgdGhlbSDigJQgdXNlcnMgd2hvIGhhbmQtZWRpdGVkIHRoZWlyIHRva2VucyB3aXRoIGN1c3RvbVxuICAgKiBuYW1lcyBvciBsYWJlbHMgZGVzZXJ2ZSBhIHJlY292ZXJ5IHBhdGggaW5zdGVhZCBvZiBhIHNpbGVudCBkZXN0cm95LlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyByZWFkV2l0aFN0YXR1cygpOiBQcm9taXNlPFxuICAgIHwgeyBzdGF0dXM6ICdvayc7IGRhdGE6IENvbnNvbGVUb2tlbkZpbGUgfVxuICAgIHwgeyBzdGF0dXM6ICdtaXNzaW5nJyB9XG4gICAgfCB7IHN0YXR1czogJ2NvcnJ1cHQnOyByZWFzb246IHN0cmluZyB9XG4gID4ge1xuICAgIGxldCBjb250ZW50OiBzdHJpbmc7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnRlbnQgPSBhd2FpdCByZWFkRmlsZSh0aGlzLmZpbGVQYXRoLCAndXRmOCcpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgaWYgKChlcnIgYXMgTm9kZUpTLkVycm5vRXhjZXB0aW9uKS5jb2RlID09PSAnRU5PRU5UJykgcmV0dXJuIHsgc3RhdHVzOiAnbWlzc2luZycgfTtcbiAgICAgIHJldHVybiB7IHN0YXR1czogJ2NvcnJ1cHQnLCByZWFzb246IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9O1xuICAgIH1cbiAgICB0cnkge1xuICAgICAgY29uc3QgcGFyc2VkID0gSlNPTi5wYXJzZShjb250ZW50KTtcbiAgICAgIGNvbnN0IHZhbGlkYXRlZCA9IHZhbGlkYXRlVG9rZW5GaWxlKHBhcnNlZCk7XG4gICAgICBpZiAoIXZhbGlkYXRlZCkgcmV0dXJuIHsgc3RhdHVzOiAnY29ycnVwdCcsIHJlYXNvbjogJ3NjaGVtYSB2YWxpZGF0aW9uIGZhaWxlZCcgfTtcbiAgICAgIHJldHVybiB7IHN0YXR1czogJ29rJywgZGF0YTogdmFsaWRhdGVkIH07XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICByZXR1cm4geyBzdGF0dXM6ICdjb3JydXB0JywgcmVhc29uOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycikgfTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ29weSB0aGUgY3VycmVudCAocHJlc3VtZWQgY29ycnVwdCkgdG9rZW4gZmlsZSB0byBhIHRpbWVzdGFtcGVkIGJhY2t1cFxuICAgKiBhbG9uZ3NpZGUgaXQgc28gdGhlIHVzZXIgY2FuIHJlY292ZXIgaGFuZC1lZGl0ZWQgZGF0YSBhZnRlciBhbiBhY2NpZGVudGFsXG4gICAqIHN5bnRheCBlcnJvci4gQmVzdC1lZmZvcnQg4oCUIGZhaWx1cmUgdG8gYmFjayB1cCBkb2VzIG5vdCBibG9jayBjcmVhdGluZ1xuICAgKiBhIGZyZXNoIGZpbGUsIHNpbmNlIHRoZSBwcmltYXJ5IGdvYWwgaXMga2VlcGluZyB0aGUgY29uc29sZSB1c2FibGUuXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGJhY2t1cENvcnJ1cHRGaWxlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHRpbWVzdGFtcCA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKS5yZXBsYWNlQWxsKC9bOi5dL2csICctJyk7XG4gICAgY29uc3QgYmFja3VwUGF0aCA9IGAke3RoaXMuZmlsZVBhdGh9LmNvcnJ1cHQtJHt0aW1lc3RhbXB9YDtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgY29weUZpbGUodGhpcy5maWxlUGF0aCwgYmFja3VwUGF0aCk7XG4gICAgICBsb2dnZXIud2FybihgW0NvbnNvbGVUb2tlbl0gQ29ycnVwdCB0b2tlbiBmaWxlIGJhY2tlZCB1cCB0byAke2JhY2t1cFBhdGh9IOKAlCBhIGZyZXNoIHRva2VuIHdpbGwgYmUgY3JlYXRlZGApO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgbG9nZ2VyLndhcm4oJ1tDb25zb2xlVG9rZW5dIENvdWxkIG5vdCBiYWNrIHVwIGNvcnJ1cHQgdG9rZW4gZmlsZSwgd2lsbCBvdmVyd3JpdGUgaW4gcGxhY2UnLCB7XG4gICAgICAgIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVyciksXG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQXRvbWljYWxseSB3cml0ZSB0aGUgdG9rZW4gZmlsZSB3aXRoIG93bmVyLW9ubHkgcGVybWlzc2lvbnMuXG4gICAqIFVzZXMgdGVtcCtyZW5hbWUgdG8gYXZvaWQgcGFydGlhbCB3cml0ZXMgb24gY3Jhc2guXG4gICAqXG4gICAqIE9uIFdpbmRvd3MsIGBjaG1vZCgwbzYwMClgIGlzIGVmZmVjdGl2ZWx5IGEgbm8tb3AgYmVjYXVzZSB0aGUgZmlsZVxuICAgKiBzeXN0ZW0gdXNlcyBBQ0xzIGluc3RlYWQgb2YgUE9TSVggbW9kZXMuIFdlIGxvZyBhIG9uZS10aW1lIHdhcm5pbmcgc29cbiAgICogdXNlcnMgb24gV2luZG93cyBrbm93IHRoZSB0b2tlbiBmaWxlIGRvZXMgbm90IGhhdmUgT1MtZW5mb3JjZWQgYWNjZXNzXG4gICAqIGNvbnRyb2wgYW5kIGNhbiBkZWNpZGUgd2hldGhlciB0byB1c2UgYWRkaXRpb25hbCB0b29saW5nIChpY2FjbHMsIE5URlNcbiAgICogcGVybWlzc2lvbnMsIG9yIGEgZGlmZmVyZW50IHN0b3JhZ2UgbG9jYXRpb24pLlxuICAgKi9cbiAgcHJpdmF0ZSBhc3luYyB3cml0ZShmaWxlOiBDb25zb2xlVG9rZW5GaWxlKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgbWtkaXIoUlVOX0RJUiwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgY29uc3QgdG1wRmlsZSA9IGAke3RoaXMuZmlsZVBhdGh9LiR7cHJvY2Vzcy5waWR9LnRtcGA7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHdyaXRlRmlsZSh0bXBGaWxlLCBKU09OLnN0cmluZ2lmeShmaWxlLCBudWxsLCAyKSwgJ3V0ZjgnKTtcbiAgICAgIGF3YWl0IGNobW9kKHRtcEZpbGUsIFRPS0VOX0ZJTEVfTU9ERSk7XG4gICAgICBhd2FpdCByZW5hbWUodG1wRmlsZSwgdGhpcy5maWxlUGF0aCk7XG4gICAgICB0aGlzLndhcm5JZldpbmRvd3NQZXJtaXNzaW9ucygpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdHJ5IHsgYXdhaXQgdW5saW5rKHRtcEZpbGUpOyB9IGNhdGNoIHsgLyogaWdub3JlICovIH1cbiAgICAgIHRocm93IGVycjtcbiAgICB9XG4gIH1cblxuICAvKiogT25lLXNob3QgZmxhZyBzbyB0aGUgV2luZG93cyBwZXJtaXNzaW9ucyB3YXJuaW5nIGlzIGxvZ2dlZCBhdCBtb3N0IG9uY2UuICovXG4gIHByaXZhdGUgd2luZG93c1dhcm5pbmdMb2dnZWQgPSBmYWxzZTtcblxuICBwcml2YXRlIHdhcm5JZldpbmRvd3NQZXJtaXNzaW9ucygpOiB2b2lkIHtcbiAgICBpZiAodGhpcy53aW5kb3dzV2FybmluZ0xvZ2dlZCkgcmV0dXJuO1xuICAgIGlmIChwbGF0Zm9ybSgpICE9PSAnd2luMzInKSByZXR1cm47XG4gICAgdGhpcy53aW5kb3dzV2FybmluZ0xvZ2dlZCA9IHRydWU7XG4gICAgbG9nZ2VyLndhcm4oXG4gICAgICBgW0NvbnNvbGVUb2tlbl0gVG9rZW4gZmlsZSBhdCAke3RoaXMuZmlsZVBhdGh9IGhhcyBubyBPUy1lbmZvcmNlZCBhY2Nlc3MgY29udHJvbCBvbiBXaW5kb3dzIGAgK1xuICAgICAgYChjaG1vZCAwbzYwMCBpcyBhIG5vLW9wIG9uIHRoaXMgcGxhdGZvcm0pLiBBbnkgcHJvY2VzcyBydW5uaW5nIGFzIHRoZSBzYW1lIHVzZXIgY2FuIHJlYWQgdGhlIGZpbGUuIGAgK1xuICAgICAgYENvbnNpZGVyIHVzaW5nIE5URlMgQUNMcyB2aWEgJ2ljYWNscycgZm9yIHN0cm9uZ2VyIGlzb2xhdGlvbiBpbiBtdWx0aS11c2VyIGVudmlyb25tZW50cy5gLFxuICAgICk7XG4gIH1cbn1cblxuLyoqXG4gKiBSZWFkIHRoZSByYXcgdG9rZW4gZmlsZSBmcm9tIGRpc2sgd2l0aG91dCBjb25zdHJ1Y3RpbmcgYSBzdG9yZS5cbiAqIEludGVuZGVkIGZvciBmb2xsb3dlciBwcm9jZXNzZXMgdGhhdCBuZWVkIHRoZSBwcmltYXJ5IHRva2VuIHRvIGF0dGFjaFxuICogdG8gdGhlaXIgaW5nZXN0IFBPU1RzLiBSZXR1cm5zIG51bGwgaWYgdGhlIGZpbGUgZG9lcyBub3QgZXhpc3Qgb3IgaXMgaW52YWxpZC5cbiAqXG4gKiBAcGFyYW0gZmlsZVBhdGggLSBPcHRpb25hbCBvdmVycmlkZSBmb3IgdGhlIHRva2VuIGZpbGUgbG9jYXRpb25cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJlYWRUb2tlbkZpbGVSYXcoZmlsZVBhdGg6IHN0cmluZyA9IERFRkFVTFRfVE9LRU5fRklMRSk6IFByb21pc2U8Q29uc29sZVRva2VuRmlsZSB8IG51bGw+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBjb250ZW50ID0gYXdhaXQgcmVhZEZpbGUoZmlsZVBhdGgsICd1dGY4Jyk7XG4gICAgcmV0dXJuIHZhbGlkYXRlVG9rZW5GaWxlKEpTT04ucGFyc2UoY29udGVudCkpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufVxuXG4vKipcbiAqIEdldCB0aGUgcHJpbWFyeSB0b2tlbiB2YWx1ZSBmcm9tIHRoZSB0b2tlbiBmaWxlIG9uIGRpc2suXG4gKiBDb252ZW5pZW5jZSBoZWxwZXIgZm9yIGZvbGxvd2VycyBhbmQgZXh0ZXJuYWwgY29uc3VtZXJzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0UHJpbWFyeVRva2VuRnJvbUZpbGUoZmlsZVBhdGg6IHN0cmluZyA9IERFRkFVTFRfVE9LRU5fRklMRSk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD4ge1xuICBjb25zdCBmaWxlID0gYXdhaXQgcmVhZFRva2VuRmlsZVJhdyhmaWxlUGF0aCk7XG4gIGlmICghZmlsZSB8fCBmaWxlLnRva2Vucy5sZW5ndGggPT09IDApIHJldHVybiBudWxsO1xuICByZXR1cm4gZmlsZS50b2tlbnNbMF0udG9rZW47XG59XG5cbi8qKiBFeHBvcnQgdGhlIGRlZmF1bHQgZmlsZSBwYXRoIHNvIGNhbGxlcnMgY2FuIHJlZmVyZW5jZSBpdCBpbiBsb2dzL2RvY3MuICovXG5leHBvcnQgeyBERUZBVUxUX1RPS0VOX0ZJTEUgfTtcbiJdfQ==
|