matrix-react-sdk
Version:
SDK for matrix.org using React
579 lines (544 loc) • 71.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.IndexedDBLogStore = exports.ConsoleLogger = void 0;
exports.cleanup = cleanup;
exports.flush = flush;
exports.getLogsForReport = getLogsForReport;
exports.init = init;
exports.tryInitStorage = tryInitStorage;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _logger = require("matrix-js-sdk/src/logger");
var _randomstring = require("matrix-js-sdk/src/randomstring");
var _JSON = require("../utils/JSON");
/*
Copyright 2024 New Vector Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
Copyright 2017 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
// This module contains all the code needed to log the console, persist it to
// disk and submit bug reports. Rationale is as follows:
// - Monkey-patching the console is preferable to having a log library because
// we can catch logs by other libraries more easily, without having to all
// depend on the same log framework / pass the logger around.
// - We use IndexedDB to persists logs because it has generous disk space
// limits compared to local storage. IndexedDB does not work in incognito
// mode, in which case this module will not be able to write logs to disk.
// However, the logs will still be stored in-memory, so can still be
// submitted in a bug report should the user wish to: we can also store more
// logs in-memory than in local storage, which does work in incognito mode.
// We also need to handle the case where there are 2+ tabs. Each JS runtime
// generates a random string which serves as the "ID" for that tab/session.
// These IDs are stored along with the log lines.
// - Bug reports are sent as a POST over HTTPS: it purposefully does not use
// Matrix as bug reports may be made when Matrix is not responsive (which may
// be the cause of the bug). We send the most recent N MB of UTF-8 log data,
// starting with the most recent, which we know because the "ID"s are
// actually timestamps. We then purge the remaining logs. We also do this
// purge on startup to prevent logs from accumulating.
// the frequency with which we flush to indexeddb
const FLUSH_RATE_MS = 30 * 1000;
/** the length of log data we keep in indexeddb (and include in the reports), if there are more than 24 hours of logs */
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
/** the length of log data we keep in indexeddb (and include in the reports), if there are less than 24 hours of logs */
const MAX_LOG_SIZE_24H = 1024 * 1024 * 100; // 100 MB
const consoleFunctionsToLevels = {
log: "I",
info: "I",
warn: "W",
error: "E",
debug: "D"
};
// A class which monkey-patches the global console and stores log lines.
class ConsoleLogger {
constructor() {
(0, _defineProperty2.default)(this, "logs", "");
(0, _defineProperty2.default)(this, "originalFunctions", {});
}
monkeyPatch(consoleObj) {
// Monkey-patch console logging
Object.keys(consoleFunctionsToLevels).forEach(fnName => {
const level = consoleFunctionsToLevels[fnName];
const originalFn = consoleObj[fnName].bind(consoleObj);
this.originalFunctions[fnName] = originalFn;
consoleObj[fnName] = (...args) => {
this.log(level, ...args);
originalFn(...args);
};
});
}
bypassRageshake(fnName, ...args) {
this.originalFunctions[fnName]?.(...args);
}
log(level, ...args) {
// We don't know what locale the user may be running so use ISO strings
const ts = new Date().toISOString();
// Convert objects and errors to helpful things
args = args.map(arg => {
if (arg instanceof DOMException) {
return arg.message + ` (${arg.name} | ${arg.code})`;
} else if (arg instanceof Error) {
return arg.message + (arg.stack ? `\n${arg.stack}` : "");
} else if (typeof arg === "object") {
return JSON.stringify(arg, (0, _JSON.getCircularReplacer)());
} else {
return arg;
}
});
// Some browsers support string formatting which we're not doing here
// so the lines are a little more ugly but easy to implement / quick to
// run.
// Example line:
// 2017-01-18T11:23:53.214Z W Failed to set badge count
let line = `${ts} ${level} ${args.join(" ")}\n`;
// Do some cleanup
line = line.replace(/token=[a-zA-Z0-9-]+/gm, "token=xxxxx");
// Using + really is the quickest way in JS
// http://jsperf.com/concat-vs-plus-vs-join
this.logs += line;
}
/**
* Retrieve log lines to flush to disk.
* @param {boolean} keepLogs True to not delete logs after flushing.
* @return {string} \n delimited log lines to flush.
*/
flush(keepLogs) {
// The ConsoleLogger doesn't care how these end up on disk, it just
// flushes them to the caller.
if (keepLogs) {
return this.logs;
}
const logsToFlush = this.logs;
this.logs = "";
return logsToFlush;
}
}
// A class which stores log lines in an IndexedDB instance.
exports.ConsoleLogger = ConsoleLogger;
class IndexedDBLogStore {
constructor(indexedDB, logger) {
(0, _defineProperty2.default)(this, "id", void 0);
(0, _defineProperty2.default)(this, "index", 0);
(0, _defineProperty2.default)(this, "db", null);
(0, _defineProperty2.default)(this, "flushPromise", null);
(0, _defineProperty2.default)(this, "flushAgainPromise", null);
this.indexedDB = indexedDB;
this.logger = logger;
this.id = "instance-" + (0, _randomstring.randomString)(16);
}
/**
* @return {Promise} Resolves when the store is ready.
*/
connect() {
const req = this.indexedDB.open("logs");
return new Promise((resolve, reject) => {
req.onsuccess = () => {
this.db = req.result;
// Periodically flush logs to local storage / indexeddb
window.setInterval(this.flush.bind(this), FLUSH_RATE_MS);
resolve();
};
req.onerror = () => {
const err = "Failed to open log database: " + req.error?.name;
_logger.logger.error(err);
reject(new Error(err));
};
// First time: Setup the object store
req.onupgradeneeded = () => {
const db = req.result;
const logObjStore = db.createObjectStore("logs", {
keyPath: ["id", "index"]
});
// Keys in the database look like: [ "instance-148938490", 0 ]
// Later on we need to query everything based on an instance id.
// In order to do this, we need to set up indexes "id".
logObjStore.createIndex("id", "id", {
unique: false
});
logObjStore.add(this.generateLogEntry(new Date() + " ::: Log database was created."));
const lastModifiedStore = db.createObjectStore("logslastmod", {
keyPath: "id"
});
lastModifiedStore.add(this.generateLastModifiedTime());
};
});
}
/**
* Flush logs to disk.
*
* There are guards to protect against race conditions in order to ensure
* that all previous flushes have completed before the most recent flush.
* Consider without guards:
* - A calls flush() periodically.
* - B calls flush() and wants to send logs immediately afterwards.
* - If B doesn't wait for A's flush to complete, B will be missing the
* contents of A's flush.
* To protect against this, we set 'flushPromise' when a flush is ongoing.
* Subsequent calls to flush() during this period will chain another flush,
* then keep returning that same chained flush.
*
* This guarantees that we will always eventually do a flush when flush() is
* called.
*
* @return {Promise} Resolved when the logs have been flushed.
*/
flush() {
// check if a flush() operation is ongoing
if (this.flushPromise) {
if (this.flushAgainPromise) {
// this is the 3rd+ time we've called flush() : return the same promise.
return this.flushAgainPromise;
}
// queue up a flush to occur immediately after the pending one completes.
this.flushAgainPromise = this.flushPromise.then(() => {
return this.flush();
}).then(() => {
this.flushAgainPromise = null;
});
return this.flushAgainPromise;
}
// there is no flush promise or there was but it has finished, so do
// a brand new one, destroying the chain which may have been built up.
this.flushPromise = new Promise((resolve, reject) => {
if (!this.db) {
// not connected yet or user rejected access for us to r/w to the db.
reject(new Error("No connected database"));
return;
}
const lines = this.logger.flush();
if (lines.length === 0) {
resolve();
return;
}
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
const objStore = txn.objectStore("logs");
txn.oncomplete = event => {
resolve();
};
txn.onerror = () => {
_logger.logger.error("Failed to flush logs : ", txn.error);
reject(new Error("Failed to write logs: " + txn.error?.message));
};
objStore.add(this.generateLogEntry(lines));
const lastModStore = txn.objectStore("logslastmod");
lastModStore.put(this.generateLastModifiedTime());
}).then(() => {
this.flushPromise = null;
});
return this.flushPromise;
}
/**
* Consume the most recent logs and return them. Older logs which are not
* returned are deleted at the same time, so this can be called at startup
* to do house-keeping to keep the logs from growing too large.
*
* @return {Promise<Object[]>} Resolves to an array of objects. The array is
* sorted in time (oldest first) based on when the log file was created (the
* log ID). The objects have said log ID in an "id" field and "lines" which
* is a big string with all the new-line delimited logs.
*/
async consume() {
const allLogIds = await this.fetchLogIds();
let removeLogIds = [];
const logs = [];
let size = 0;
for (let i = 0; i < allLogIds.length; i++) {
const instanceId = allLogIds[i];
const {
lines,
truncated
} = await this.fetchLogs(instanceId, size);
// always add the returned logs: fetchLogs will truncate once it hits the size limit,
// so we'll go over the max but only by one fragment's worth.
logs.push({
lines,
id: instanceId
});
size += lines.length;
// If fetchLogs truncated we'll now be at or over the size limit,
// in which case we should stop and remove the rest of the log files.
if (truncated) {
_logger.logger.log(`rageshake: reached size limit while processing instance ${i + 1}/${allLogIds.length} (${instanceId}), with ${size} bytes of logs: will drop further instances`);
// the remaining log IDs should be removed. If we go out of
// bounds this is just []
removeLogIds = allLogIds.slice(i + 1);
break;
}
}
if (removeLogIds.length > 0) {
_logger.logger.log(`rageshake: removing logs: ${removeLogIds}`);
// Don't await this because it's non-fatal if we can't clean up
// logs.
Promise.all(removeLogIds.map(id => this.deleteLogs(id))).then(() => {
_logger.logger.log(`Removed ${removeLogIds.length} old logs.`);
}, err => {
_logger.logger.error(err);
});
}
return logs;
}
/**
* Fetch all the application instance names from the database.
*/
fetchLogIds() {
const db = this.db;
if (!db) return Promise.reject("DB unavailable");
// To gather all the log IDs, query for all records in logslastmod.
const o = db.transaction("logslastmod", "readonly").objectStore("logslastmod");
return selectQuery(o, undefined, cursor => {
return {
id: cursor.value.id,
ts: cursor.value.ts
};
}).then(res => {
// Sort IDs by timestamp (newest first)
return res.sort((a, b) => {
return b.ts - a.ts;
}).map(a => a.id);
});
}
/**
* Fetch logs for a given application instance from the database, stopping once we hit the size limit.
*
* @param id - Application instance to fetch logs for.
* @param sizeSoFar - Amount of logs we have already retrieved from other instances.
*
* @returns An object with the properties:
* * `lines`: the concatenated logs for this ID
* * `truncated`: whether the output was truncated due to hitting the size limit.
*/
fetchLogs(id, sizeSoFar) {
const db = this.db;
if (!db) return Promise.reject("DB unavailable");
const objectStore = db.transaction("logs", "readonly").objectStore("logs");
/** Determine whether we should stop collecting logs after this batch.
*
* @param sizeSoFar - The total amount of logs collected so far.
* @param logBatchTimestamp - The timestamp of the most recent batch of logs collected.
*
* @returns `true` if we should stop after this batch.
*/
function shouldTruncateAfterLogBatch(sizeSoFar, logBatchTimestamp) {
// First check if we have exceeded the absolute limit
if (sizeSoFar >= MAX_LOG_SIZE_24H) {
return true;
}
// Otherwise, check if the most recent batch is more than 24H old, and we have exceeded the limit for logs over 24H
if (Date.now() - logBatchTimestamp >= 24 * 3600 * 1000 && sizeSoFar >= MAX_LOG_SIZE) {
return true;
}
// Otherwise, we're good.
return false;
}
return new Promise((resolve, reject) => {
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), "prev");
let lines = "";
query.onerror = () => {
reject(new Error("Query failed: " + query.error?.message));
};
query.onsuccess = () => {
const cursor = query.result;
if (!cursor) {
// end of results
resolve({
lines,
truncated: false
});
return;
}
const newLines = cursor.value.lines;
// The query returns log chunks in reverse time order, so prepend this new chunk to the buffer.
lines = newLines + lines;
sizeSoFar += newLines.length;
// If we have now exceeded the size limit, stop.
if (shouldTruncateAfterLogBatch(sizeSoFar, cursor.value.ts ?? 0)) {
resolve({
lines,
truncated: true
});
} else {
cursor.continue();
}
};
});
}
/**
* Delete logs for a given application instance.
*
* @param id - Application instance to delete logs for.
*/
deleteLogs(id) {
const db = this.db;
if (!db) return Promise.reject("DB unavailable");
return new Promise((resolve, reject) => {
const txn = db.transaction(["logs", "logslastmod"], "readwrite");
const o = txn.objectStore("logs");
// only load the key path, not the data which may be huge
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
query.onsuccess = () => {
const cursor = query.result;
if (!cursor) {
return;
}
o.delete(cursor.primaryKey);
cursor.continue();
};
txn.oncomplete = () => {
resolve();
};
txn.onerror = () => {
reject(new Error("Failed to delete logs for " + `'${id}' : ${query.error?.message}`));
};
// delete last modified entries
const lastModStore = txn.objectStore("logslastmod");
lastModStore.delete(id);
});
}
/** Generate the object to be stored in the `logs` store */
generateLogEntry(lines) {
return {
id: this.id,
lines: lines,
index: this.index++,
/** The timestamp at which the line was *flushed* (not necessarily when it was written). */
ts: Date.now()
};
}
generateLastModifiedTime() {
return {
id: this.id,
ts: Date.now()
};
}
}
/**
* Helper method to collect results from a Cursor and promiseify it.
* @param {ObjectStore|Index} store The store to perform openCursor on.
* @param {IDBKeyRange=} keyRange Optional key range to apply on the cursor.
* @param {Function} resultMapper A function which is repeatedly called with a
* Cursor.
* Return the data you want to keep.
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
* resultMapper.
*/
exports.IndexedDBLogStore = IndexedDBLogStore;
function selectQuery(store, keyRange, resultMapper) {
const query = store.openCursor(keyRange);
return new Promise((resolve, reject) => {
const results = [];
query.onerror = () => {
reject(new Error("Query failed: " + query.error?.message));
};
// collect results
query.onsuccess = () => {
const cursor = query.result;
if (!cursor) {
resolve(results);
return; // end of results
}
results.push(resultMapper(cursor));
cursor.continue();
};
});
}
/**
* Configure rage shaking support for sending bug reports.
* Modifies globals.
* @param {boolean} setUpPersistence When true (default), the persistence will
* be set up immediately for the logs.
* @return {Promise} Resolves when set up.
*/
function init(setUpPersistence = true) {
if (global.mx_rage_initPromise) {
return global.mx_rage_initPromise;
}
global.mx_rage_logger = new ConsoleLogger();
global.mx_rage_logger.monkeyPatch(window.console);
// log unhandled rejections in the rageshake
window.addEventListener("unhandledrejection", event => {
global.mx_rage_logger.log("error", `Unhandled promise rejection: ${event.reason}`);
});
if (setUpPersistence) {
return tryInitStorage();
}
global.mx_rage_initPromise = Promise.resolve();
return global.mx_rage_initPromise;
}
/**
* Try to start up the rageshake storage for logs. If not possible (client unsupported)
* then this no-ops.
* @return {Promise} Resolves when complete.
*/
function tryInitStorage() {
if (global.mx_rage_initStoragePromise) {
return global.mx_rage_initStoragePromise;
}
_logger.logger.log("Configuring rageshake persistence...");
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
try {
indexedDB = window.indexedDB;
} catch (e) {}
if (indexedDB) {
global.mx_rage_store = new IndexedDBLogStore(indexedDB, global.mx_rage_logger);
global.mx_rage_initStoragePromise = global.mx_rage_store.connect();
// Fire off a task in the background which will clean up old logs in the store
global.mx_rage_initStoragePromise.then(() => {
global.mx_rage_store.consume().catch(e => {
_logger.logger.error("Error cleaning up rageshake store", e);
});
});
return global.mx_rage_initStoragePromise;
}
global.mx_rage_initStoragePromise = Promise.resolve();
return global.mx_rage_initStoragePromise;
}
function flush() {
if (!global.mx_rage_store) {
return;
}
global.mx_rage_store.flush();
}
/**
* Clean up old logs.
*
* @deprecated There is no need to call this explicitly: it will be done as a side-effect of {@link tryInitStorage},
* or {@link init} with `setUpPersistence: true`.
*
* @return {Promise} Resolves if cleaned logs.
*/
async function cleanup() {
if (!global.mx_rage_store) {
return;
}
await global.mx_rage_store.consume();
}
/**
* Get a recent snapshot of the logs, ready for attaching to a bug report
*
* @return {Array<{lines: string, id, string}>} list of log data
*/
async function getLogsForReport() {
if (!global.mx_rage_logger) {
throw new Error("No console logger, did you forget to call init()?");
}
// If in incognito mode, store is null, but we still want bug report
// sending to work going off the in-memory console logs.
if (global.mx_rage_store) {
// flush most recent logs
await global.mx_rage_store.flush();
return global.mx_rage_store.consume();
} else {
return [{
lines: global.mx_rage_logger.flush(true),
id: "-"
}];
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbG9nZ2VyIiwicmVxdWlyZSIsIl9yYW5kb21zdHJpbmciLCJfSlNPTiIsIkZMVVNIX1JBVEVfTVMiLCJNQVhfTE9HX1NJWkUiLCJNQVhfTE9HX1NJWkVfMjRIIiwiY29uc29sZUZ1bmN0aW9uc1RvTGV2ZWxzIiwibG9nIiwiaW5mbyIsIndhcm4iLCJlcnJvciIsImRlYnVnIiwiQ29uc29sZUxvZ2dlciIsImNvbnN0cnVjdG9yIiwiX2RlZmluZVByb3BlcnR5MiIsImRlZmF1bHQiLCJtb25rZXlQYXRjaCIsImNvbnNvbGVPYmoiLCJPYmplY3QiLCJrZXlzIiwiZm9yRWFjaCIsImZuTmFtZSIsImxldmVsIiwib3JpZ2luYWxGbiIsImJpbmQiLCJvcmlnaW5hbEZ1bmN0aW9ucyIsImFyZ3MiLCJieXBhc3NSYWdlc2hha2UiLCJ0cyIsIkRhdGUiLCJ0b0lTT1N0cmluZyIsIm1hcCIsImFyZyIsIkRPTUV4Y2VwdGlvbiIsIm1lc3NhZ2UiLCJuYW1lIiwiY29kZSIsIkVycm9yIiwic3RhY2siLCJKU09OIiwic3RyaW5naWZ5IiwiZ2V0Q2lyY3VsYXJSZXBsYWNlciIsImxpbmUiLCJqb2luIiwicmVwbGFjZSIsImxvZ3MiLCJmbHVzaCIsImtlZXBMb2dzIiwibG9nc1RvRmx1c2giLCJleHBvcnRzIiwiSW5kZXhlZERCTG9nU3RvcmUiLCJpbmRleGVkREIiLCJsb2dnZXIiLCJpZCIsInJhbmRvbVN0cmluZyIsImNvbm5lY3QiLCJyZXEiLCJvcGVuIiwiUHJvbWlzZSIsInJlc29sdmUiLCJyZWplY3QiLCJvbnN1Y2Nlc3MiLCJkYiIsInJlc3VsdCIsIndpbmRvdyIsInNldEludGVydmFsIiwib25lcnJvciIsImVyciIsIm9udXBncmFkZW5lZWRlZCIsImxvZ09ialN0b3JlIiwiY3JlYXRlT2JqZWN0U3RvcmUiLCJrZXlQYXRoIiwiY3JlYXRlSW5kZXgiLCJ1bmlxdWUiLCJhZGQiLCJnZW5lcmF0ZUxvZ0VudHJ5IiwibGFzdE1vZGlmaWVkU3RvcmUiLCJnZW5lcmF0ZUxhc3RNb2RpZmllZFRpbWUiLCJmbHVzaFByb21pc2UiLCJmbHVzaEFnYWluUHJvbWlzZSIsInRoZW4iLCJsaW5lcyIsImxlbmd0aCIsInR4biIsInRyYW5zYWN0aW9uIiwib2JqU3RvcmUiLCJvYmplY3RTdG9yZSIsIm9uY29tcGxldGUiLCJldmVudCIsImxhc3RNb2RTdG9yZSIsInB1dCIsImNvbnN1bWUiLCJhbGxMb2dJZHMiLCJmZXRjaExvZ0lkcyIsInJlbW92ZUxvZ0lkcyIsInNpemUiLCJpIiwiaW5zdGFuY2VJZCIsInRydW5jYXRlZCIsImZldGNoTG9ncyIsInB1c2giLCJzbGljZSIsImFsbCIsImRlbGV0ZUxvZ3MiLCJvIiwic2VsZWN0UXVlcnkiLCJ1bmRlZmluZWQiLCJjdXJzb3IiLCJ2YWx1ZSIsInJlcyIsInNvcnQiLCJhIiwiYiIsInNpemVTb0ZhciIsInNob3VsZFRydW5jYXRlQWZ0ZXJMb2dCYXRjaCIsImxvZ0JhdGNoVGltZXN0YW1wIiwibm93IiwicXVlcnkiLCJpbmRleCIsIm9wZW5DdXJzb3IiLCJJREJLZXlSYW5nZSIsIm9ubHkiLCJuZXdMaW5lcyIsImNvbnRpbnVlIiwib3BlbktleUN1cnNvciIsImRlbGV0ZSIsInByaW1hcnlLZXkiLCJzdG9yZSIsImtleVJhbmdlIiwicmVzdWx0TWFwcGVyIiwicmVzdWx0cyIsImluaXQiLCJzZXRVcFBlcnNpc3RlbmNlIiwiZ2xvYmFsIiwibXhfcmFnZV9pbml0UHJvbWlzZSIsIm14X3JhZ2VfbG9nZ2VyIiwiY29uc29sZSIsImFkZEV2ZW50TGlzdGVuZXIiLCJyZWFzb24iLCJ0cnlJbml0U3RvcmFnZSIsIm14X3JhZ2VfaW5pdFN0b3JhZ2VQcm9taXNlIiwiZSIsIm14X3JhZ2Vfc3RvcmUiLCJjYXRjaCIsImNsZWFudXAiLCJnZXRMb2dzRm9yUmVwb3J0Il0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL3JhZ2VzaGFrZS9yYWdlc2hha2UudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMTkgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cbkNvcHlyaWdodCAyMDE4IE5ldyBWZWN0b3IgTHRkXG5Db3B5cmlnaHQgMjAxNyBPcGVuTWFya2V0IEx0ZFxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG4vLyBUaGlzIG1vZHVsZSBjb250YWlucyBhbGwgdGhlIGNvZGUgbmVlZGVkIHRvIGxvZyB0aGUgY29uc29sZSwgcGVyc2lzdCBpdCB0b1xuLy8gZGlzayBhbmQgc3VibWl0IGJ1ZyByZXBvcnRzLiBSYXRpb25hbGUgaXMgYXMgZm9sbG93czpcbi8vICAtIE1vbmtleS1wYXRjaGluZyB0aGUgY29uc29sZSBpcyBwcmVmZXJhYmxlIHRvIGhhdmluZyBhIGxvZyBsaWJyYXJ5IGJlY2F1c2Vcbi8vICAgIHdlIGNhbiBjYXRjaCBsb2dzIGJ5IG90aGVyIGxpYnJhcmllcyBtb3JlIGVhc2lseSwgd2l0aG91dCBoYXZpbmcgdG8gYWxsXG4vLyAgICBkZXBlbmQgb24gdGhlIHNhbWUgbG9nIGZyYW1ld29yayAvIHBhc3MgdGhlIGxvZ2dlciBhcm91bmQuXG4vLyAgLSBXZSB1c2UgSW5kZXhlZERCIHRvIHBlcnNpc3RzIGxvZ3MgYmVjYXVzZSBpdCBoYXMgZ2VuZXJvdXMgZGlzayBzcGFjZVxuLy8gICAgbGltaXRzIGNvbXBhcmVkIHRvIGxvY2FsIHN0b3JhZ2UuIEluZGV4ZWREQiBkb2VzIG5vdCB3b3JrIGluIGluY29nbml0b1xuLy8gICAgbW9kZSwgaW4gd2hpY2ggY2FzZSB0aGlzIG1vZHVsZSB3aWxsIG5vdCBiZSBhYmxlIHRvIHdyaXRlIGxvZ3MgdG8gZGlzay5cbi8vICAgIEhvd2V2ZXIsIHRoZSBsb2dzIHdpbGwgc3RpbGwgYmUgc3RvcmVkIGluLW1lbW9yeSwgc28gY2FuIHN0aWxsIGJlXG4vLyAgICBzdWJtaXR0ZWQgaW4gYSBidWcgcmVwb3J0IHNob3VsZCB0aGUgdXNlciB3aXNoIHRvOiB3ZSBjYW4gYWxzbyBzdG9yZSBtb3JlXG4vLyAgICBsb2dzIGluLW1lbW9yeSB0aGFuIGluIGxvY2FsIHN0b3JhZ2UsIHdoaWNoIGRvZXMgd29yayBpbiBpbmNvZ25pdG8gbW9kZS5cbi8vICAgIFdlIGFsc28gbmVlZCB0byBoYW5kbGUgdGhlIGNhc2Ugd2hlcmUgdGhlcmUgYXJlIDIrIHRhYnMuIEVhY2ggSlMgcnVudGltZVxuLy8gICAgZ2VuZXJhdGVzIGEgcmFuZG9tIHN0cmluZyB3aGljaCBzZXJ2ZXMgYXMgdGhlIFwiSURcIiBmb3IgdGhhdCB0YWIvc2Vzc2lvbi5cbi8vICAgIFRoZXNlIElEcyBhcmUgc3RvcmVkIGFsb25nIHdpdGggdGhlIGxvZyBsaW5lcy5cbi8vICAtIEJ1ZyByZXBvcnRzIGFyZSBzZW50IGFzIGEgUE9TVCBvdmVyIEhUVFBTOiBpdCBwdXJwb3NlZnVsbHkgZG9lcyBub3QgdXNlXG4vLyAgICBNYXRyaXggYXMgYnVnIHJlcG9ydHMgbWF5IGJlIG1hZGUgd2hlbiBNYXRyaXggaXMgbm90IHJlc3BvbnNpdmUgKHdoaWNoIG1heVxuLy8gICAgYmUgdGhlIGNhdXNlIG9mIHRoZSBidWcpLiBXZSBzZW5kIHRoZSBtb3N0IHJlY2VudCBOIE1CIG9mIFVURi04IGxvZyBkYXRhLFxuLy8gICAgc3RhcnRpbmcgd2l0aCB0aGUgbW9zdCByZWNlbnQsIHdoaWNoIHdlIGtub3cgYmVjYXVzZSB0aGUgXCJJRFwicyBhcmVcbi8vICAgIGFjdHVhbGx5IHRpbWVzdGFtcHMuIFdlIHRoZW4gcHVyZ2UgdGhlIHJlbWFpbmluZyBsb2dzLiBXZSBhbHNvIGRvIHRoaXNcbi8vICAgIHB1cmdlIG9uIHN0YXJ0dXAgdG8gcHJldmVudCBsb2dzIGZyb20gYWNjdW11bGF0aW5nLlxuXG4vLyB0aGUgZnJlcXVlbmN5IHdpdGggd2hpY2ggd2UgZmx1c2ggdG8gaW5kZXhlZGRiXG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5pbXBvcnQgeyByYW5kb21TdHJpbmcgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvcmFuZG9tc3RyaW5nXCI7XG5cbmltcG9ydCB7IGdldENpcmN1bGFyUmVwbGFjZXIgfSBmcm9tIFwiLi4vdXRpbHMvSlNPTlwiO1xuXG5jb25zdCBGTFVTSF9SQVRFX01TID0gMzAgKiAxMDAwO1xuXG4vKiogdGhlIGxlbmd0aCBvZiBsb2cgZGF0YSB3ZSBrZWVwIGluIGluZGV4ZWRkYiAoYW5kIGluY2x1ZGUgaW4gdGhlIHJlcG9ydHMpLCBpZiB0aGVyZSBhcmUgbW9yZSB0aGFuIDI0IGhvdXJzIG9mIGxvZ3MgKi9cbmNvbnN0IE1BWF9MT0dfU0laRSA9IDEwMjQgKiAxMDI0ICogNTsgLy8gNSBNQlxuXG4vKiogdGhlIGxlbmd0aCBvZiBsb2cgZGF0YSB3ZSBrZWVwIGluIGluZGV4ZWRkYiAoYW5kIGluY2x1ZGUgaW4gdGhlIHJlcG9ydHMpLCBpZiB0aGVyZSBhcmUgbGVzcyB0aGFuIDI0IGhvdXJzIG9mIGxvZ3MgKi9cbmNvbnN0IE1BWF9MT0dfU0laRV8yNEggPSAxMDI0ICogMTAyNCAqIDEwMDsgLy8gMTAwIE1CXG5cbnR5cGUgTG9nRnVuY3Rpb24gPSAoLi4uYXJnczogKEVycm9yIHwgRE9NRXhjZXB0aW9uIHwgb2JqZWN0IHwgc3RyaW5nKVtdKSA9PiB2b2lkO1xuY29uc3QgY29uc29sZUZ1bmN0aW9uc1RvTGV2ZWxzID0ge1xuICAgIGxvZzogXCJJXCIsXG4gICAgaW5mbzogXCJJXCIsXG4gICAgd2FybjogXCJXXCIsXG4gICAgZXJyb3I6IFwiRVwiLFxuICAgIGRlYnVnOiBcIkRcIixcbn0gYXMgY29uc3Q7XG50eXBlIExvZ0Z1bmN0aW9uTmFtZSA9IGtleW9mIHR5cGVvZiBjb25zb2xlRnVuY3Rpb25zVG9MZXZlbHM7XG5cbi8vIEEgY2xhc3Mgd2hpY2ggbW9ua2V5LXBhdGNoZXMgdGhlIGdsb2JhbCBjb25zb2xlIGFuZCBzdG9yZXMgbG9nIGxpbmVzLlxuZXhwb3J0IGNsYXNzIENvbnNvbGVMb2dnZXIge1xuICAgIHByaXZhdGUgbG9ncyA9IFwiXCI7XG4gICAgcHJpdmF0ZSBvcmlnaW5hbEZ1bmN0aW9uczogeyBba2V5IGluIExvZ0Z1bmN0aW9uTmFtZV0/OiBMb2dGdW5jdGlvbiB9ID0ge307XG5cbiAgICBwdWJsaWMgbW9ua2V5UGF0Y2goY29uc29sZU9iajogQ29uc29sZSk6IHZvaWQge1xuICAgICAgICAvLyBNb25rZXktcGF0Y2ggY29uc29sZSBsb2dnaW5nXG4gICAgICAgIChPYmplY3Qua2V5cyhjb25zb2xlRnVuY3Rpb25zVG9MZXZlbHMpIGFzIExvZ0Z1bmN0aW9uTmFtZVtdKS5mb3JFYWNoKChmbk5hbWU6IExvZ0Z1bmN0aW9uTmFtZSkgPT4ge1xuICAgICAgICAgICAgY29uc3QgbGV2ZWwgPSBjb25zb2xlRnVuY3Rpb25zVG9MZXZlbHNbZm5OYW1lXTtcbiAgICAgICAgICAgIGNvbnN0IG9yaWdpbmFsRm4gPSBjb25zb2xlT2JqW2ZuTmFtZV0uYmluZChjb25zb2xlT2JqKTtcbiAgICAgICAgICAgIHRoaXMub3JpZ2luYWxGdW5jdGlvbnNbZm5OYW1lXSA9IG9yaWdpbmFsRm47XG4gICAgICAgICAgICBjb25zb2xlT2JqW2ZuTmFtZV0gPSAoLi4uYXJncykgPT4ge1xuICAgICAgICAgICAgICAgIHRoaXMubG9nKGxldmVsLCAuLi5hcmdzKTtcbiAgICAgICAgICAgICAgICBvcmlnaW5hbEZuKC4uLmFyZ3MpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcHVibGljIGJ5cGFzc1JhZ2VzaGFrZShmbk5hbWU6IExvZ0Z1bmN0aW9uTmFtZSwgLi4uYXJnczogKEVycm9yIHwgRE9NRXhjZXB0aW9uIHwgb2JqZWN0IHwgc3RyaW5nKVtdKTogdm9pZCB7XG4gICAgICAgIHRoaXMub3JpZ2luYWxGdW5jdGlvbnNbZm5OYW1lXT8uKC4uLmFyZ3MpO1xuICAgIH1cblxuICAgIHB1YmxpYyBsb2cobGV2ZWw6IHN0cmluZywgLi4uYXJnczogKEVycm9yIHwgRE9NRXhjZXB0aW9uIHwgb2JqZWN0IHwgc3RyaW5nKVtdKTogdm9pZCB7XG4gICAgICAgIC8vIFdlIGRvbid0IGtub3cgd2hhdCBsb2NhbGUgdGhlIHVzZXIgbWF5IGJlIHJ1bm5pbmcgc28gdXNlIElTTyBzdHJpbmdzXG4gICAgICAgIGNvbnN0IHRzID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpO1xuXG4gICAgICAgIC8vIENvbnZlcnQgb2JqZWN0cyBhbmQgZXJyb3JzIHRvIGhlbHBmdWwgdGhpbmdzXG4gICAgICAgIGFyZ3MgPSBhcmdzLm1hcCgoYXJnKSA9PiB7XG4gICAgICAgICAgICBpZiAoYXJnIGluc3RhbmNlb2YgRE9NRXhjZXB0aW9uKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGFyZy5tZXNzYWdlICsgYCAoJHthcmcubmFtZX0gfCAke2FyZy5jb2RlfSlgO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChhcmcgaW5zdGFuY2VvZiBFcnJvcikge1xuICAgICAgICAgICAgICAgIHJldHVybiBhcmcubWVzc2FnZSArIChhcmcuc3RhY2sgPyBgXFxuJHthcmcuc3RhY2t9YCA6IFwiXCIpO1xuICAgICAgICAgICAgfSBlbHNlIGlmICh0eXBlb2YgYXJnID09PSBcIm9iamVjdFwiKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KGFyZywgZ2V0Q2lyY3VsYXJSZXBsYWNlcigpKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGFyZztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gU29tZSBicm93c2VycyBzdXBwb3J0IHN0cmluZyBmb3JtYXR0aW5nIHdoaWNoIHdlJ3JlIG5vdCBkb2luZyBoZXJlXG4gICAgICAgIC8vIHNvIHRoZSBsaW5lcyBhcmUgYSBsaXR0bGUgbW9yZSB1Z2x5IGJ1dCBlYXN5IHRvIGltcGxlbWVudCAvIHF1aWNrIHRvXG4gICAgICAgIC8vIHJ1bi5cbiAgICAgICAgLy8gRXhhbXBsZSBsaW5lOlxuICAgICAgICAvLyAyMDE3LTAxLTE4VDExOjIzOjUzLjIxNFogVyBGYWlsZWQgdG8gc2V0IGJhZGdlIGNvdW50XG4gICAgICAgIGxldCBsaW5lID0gYCR7dHN9ICR7bGV2ZWx9ICR7YXJncy5qb2luKFwiIFwiKX1cXG5gO1xuICAgICAgICAvLyBEbyBzb21lIGNsZWFudXBcbiAgICAgICAgbGluZSA9IGxpbmUucmVwbGFjZSgvdG9rZW49W2EtekEtWjAtOS1dKy9nbSwgXCJ0b2tlbj14eHh4eFwiKTtcbiAgICAgICAgLy8gVXNpbmcgKyByZWFsbHkgaXMgdGhlIHF1aWNrZXN0IHdheSBpbiBKU1xuICAgICAgICAvLyBodHRwOi8vanNwZXJmLmNvbS9jb25jYXQtdnMtcGx1cy12cy1qb2luXG4gICAgICAgIHRoaXMubG9ncyArPSBsaW5lO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJldHJpZXZlIGxvZyBsaW5lcyB0byBmbHVzaCB0byBkaXNrLlxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0ga2VlcExvZ3MgVHJ1ZSB0byBub3QgZGVsZXRlIGxvZ3MgYWZ0ZXIgZmx1c2hpbmcuXG4gICAgICogQHJldHVybiB7c3RyaW5nfSBcXG4gZGVsaW1pdGVkIGxvZyBsaW5lcyB0byBmbHVzaC5cbiAgICAgKi9cbiAgICBwdWJsaWMgZmx1c2goa2VlcExvZ3M/OiBib29sZWFuKTogc3RyaW5nIHtcbiAgICAgICAgLy8gVGhlIENvbnNvbGVMb2dnZXIgZG9lc24ndCBjYXJlIGhvdyB0aGVzZSBlbmQgdXAgb24gZGlzaywgaXQganVzdFxuICAgICAgICAvLyBmbHVzaGVzIHRoZW0gdG8gdGhlIGNhbGxlci5cbiAgICAgICAgaWYgKGtlZXBMb2dzKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5sb2dzO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGxvZ3NUb0ZsdXNoID0gdGhpcy5sb2dzO1xuICAgICAgICB0aGlzLmxvZ3MgPSBcIlwiO1xuICAgICAgICByZXR1cm4gbG9nc1RvRmx1c2g7XG4gICAgfVxufVxuXG4vLyBBIGNsYXNzIHdoaWNoIHN0b3JlcyBsb2cgbGluZXMgaW4gYW4gSW5kZXhlZERCIGluc3RhbmNlLlxuZXhwb3J0IGNsYXNzIEluZGV4ZWREQkxvZ1N0b3JlIHtcbiAgICBwcml2YXRlIGlkOiBzdHJpbmc7XG4gICAgcHJpdmF0ZSBpbmRleCA9IDA7XG4gICAgcHJpdmF0ZSBkYjogSURCRGF0YWJhc2UgfCBudWxsID0gbnVsbDtcbiAgICBwcml2YXRlIGZsdXNoUHJvbWlzZTogUHJvbWlzZTx2b2lkPiB8IG51bGwgPSBudWxsO1xuICAgIHByaXZhdGUgZmx1c2hBZ2FpblByb21pc2U6IFByb21pc2U8dm9pZD4gfCBudWxsID0gbnVsbDtcblxuICAgIHB1YmxpYyBjb25zdHJ1Y3RvcihcbiAgICAgICAgcHJpdmF0ZSBpbmRleGVkREI6IElEQkZhY3RvcnksXG4gICAgICAgIHByaXZhdGUgbG9nZ2VyOiBDb25zb2xlTG9nZ2VyLFxuICAgICkge1xuICAgICAgICB0aGlzLmlkID0gXCJpbnN0YW5jZS1cIiArIHJhbmRvbVN0cmluZygxNik7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQHJldHVybiB7UHJvbWlzZX0gUmVzb2x2ZXMgd2hlbiB0aGUgc3RvcmUgaXMgcmVhZHkuXG4gICAgICovXG4gICAgcHVibGljIGNvbm5lY3QoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IHJlcSA9IHRoaXMuaW5kZXhlZERCLm9wZW4oXCJsb2dzXCIpO1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgcmVxLm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLmRiID0gcmVxLnJlc3VsdDtcbiAgICAgICAgICAgICAgICAvLyBQZXJpb2RpY2FsbHkgZmx1c2ggbG9ncyB0byBsb2NhbCBzdG9yYWdlIC8gaW5kZXhlZGRiXG4gICAgICAgICAgICAgICAgd2luZG93LnNldEludGVydmFsKHRoaXMuZmx1c2guYmluZCh0aGlzKSwgRkxVU0hfUkFURV9NUyk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmVxLm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgZXJyID0gXCJGYWlsZWQgdG8gb3BlbiBsb2cgZGF0YWJhc2U6IFwiICsgcmVxLmVycm9yPy5uYW1lO1xuICAgICAgICAgICAgICAgIGxvZ2dlci5lcnJvcihlcnIpO1xuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoZXJyKSk7XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAvLyBGaXJzdCB0aW1lOiBTZXR1cCB0aGUgb2JqZWN0IHN0b3JlXG4gICAgICAgICAgICByZXEub251cGdyYWRlbmVlZGVkID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIGNvbnN0IGRiID0gcmVxLnJlc3VsdDtcbiAgICAgICAgICAgICAgICBjb25zdCBsb2dPYmpTdG9yZSA9IGRiLmNyZWF0ZU9iamVjdFN0b3JlKFwibG9nc1wiLCB7XG4gICAgICAgICAgICAgICAgICAgIGtleVBhdGg6IFtcImlkXCIsIFwiaW5kZXhcIl0sXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgLy8gS2V5cyBpbiB0aGUgZGF0YWJhc2UgbG9vayBsaWtlOiBbIFwiaW5zdGFuY2UtMTQ4OTM4NDkwXCIsIDAgXVxuICAgICAgICAgICAgICAgIC8vIExhdGVyIG9uIHdlIG5lZWQgdG8gcXVlcnkgZXZlcnl0aGluZyBiYXNlZCBvbiBhbiBpbnN0YW5jZSBpZC5cbiAgICAgICAgICAgICAgICAvLyBJbiBvcmRlciB0byBkbyB0aGlzLCB3ZSBuZWVkIHRvIHNldCB1cCBpbmRleGVzIFwiaWRcIi5cbiAgICAgICAgICAgICAgICBsb2dPYmpTdG9yZS5jcmVhdGVJbmRleChcImlkXCIsIFwiaWRcIiwgeyB1bmlxdWU6IGZhbHNlIH0pO1xuXG4gICAgICAgICAgICAgICAgbG9nT2JqU3RvcmUuYWRkKHRoaXMuZ2VuZXJhdGVMb2dFbnRyeShuZXcgRGF0ZSgpICsgXCIgOjo6IExvZyBkYXRhYmFzZSB3YXMgY3JlYXRlZC5cIikpO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgbGFzdE1vZGlmaWVkU3RvcmUgPSBkYi5jcmVhdGVPYmplY3RTdG9yZShcImxvZ3NsYXN0bW9kXCIsIHtcbiAgICAgICAgICAgICAgICAgICAga2V5UGF0aDogXCJpZFwiLFxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIGxhc3RNb2RpZmllZFN0b3JlLmFkZCh0aGlzLmdlbmVyYXRlTGFzdE1vZGlmaWVkVGltZSgpKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZsdXNoIGxvZ3MgdG8gZGlzay5cbiAgICAgKlxuICAgICAqIFRoZXJlIGFyZSBndWFyZHMgdG8gcHJvdGVjdCBhZ2FpbnN0IHJhY2UgY29uZGl0aW9ucyBpbiBvcmRlciB0byBlbnN1cmVcbiAgICAgKiB0aGF0IGFsbCBwcmV2aW91cyBmbHVzaGVzIGhhdmUgY29tcGxldGVkIGJlZm9yZSB0aGUgbW9zdCByZWNlbnQgZmx1c2guXG4gICAgICogQ29uc2lkZXIgd2l0aG91dCBndWFyZHM6XG4gICAgICogIC0gQSBjYWxscyBmbHVzaCgpIHBlcmlvZGljYWxseS5cbiAgICAgKiAgLSBCIGNhbGxzIGZsdXNoKCkgYW5kIHdhbnRzIHRvIHNlbmQgbG9ncyBpbW1lZGlhdGVseSBhZnRlcndhcmRzLlxuICAgICAqICAtIElmIEIgZG9lc24ndCB3YWl0IGZvciBBJ3MgZmx1c2ggdG8gY29tcGxldGUsIEIgd2lsbCBiZSBtaXNzaW5nIHRoZVxuICAgICAqICAgIGNvbnRlbnRzIG9mIEEncyBmbHVzaC5cbiAgICAgKiBUbyBwcm90ZWN0IGFnYWluc3QgdGhpcywgd2Ugc2V0ICdmbHVzaFByb21pc2UnIHdoZW4gYSBmbHVzaCBpcyBvbmdvaW5nLlxuICAgICAqIFN1YnNlcXVlbnQgY2FsbHMgdG8gZmx1c2goKSBkdXJpbmcgdGhpcyBwZXJpb2Qgd2lsbCBjaGFpbiBhbm90aGVyIGZsdXNoLFxuICAgICAqIHRoZW4ga2VlcCByZXR1cm5pbmcgdGhhdCBzYW1lIGNoYWluZWQgZmx1c2guXG4gICAgICpcbiAgICAgKiBUaGlzIGd1YXJhbnRlZXMgdGhhdCB3ZSB3aWxsIGFsd2F5cyBldmVudHVhbGx5IGRvIGEgZmx1c2ggd2hlbiBmbHVzaCgpIGlzXG4gICAgICogY2FsbGVkLlxuICAgICAqXG4gICAgICogQHJldHVybiB7UHJvbWlzZX0gUmVzb2x2ZWQgd2hlbiB0aGUgbG9ncyBoYXZlIGJlZW4gZmx1c2hlZC5cbiAgICAgKi9cbiAgICBwdWJsaWMgZmx1c2goKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIC8vIGNoZWNrIGlmIGEgZmx1c2goKSBvcGVyYXRpb24gaXMgb25nb2luZ1xuICAgICAgICBpZiAodGhpcy5mbHVzaFByb21pc2UpIHtcbiAgICAgICAgICAgIGlmICh0aGlzLmZsdXNoQWdhaW5Qcm9taXNlKSB7XG4gICAgICAgICAgICAgICAgLy8gdGhpcyBpcyB0aGUgM3JkKyB0aW1lIHdlJ3ZlIGNhbGxlZCBmbHVzaCgpIDogcmV0dXJuIHRoZSBzYW1lIHByb21pc2UuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuZmx1c2hBZ2FpblByb21pc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBxdWV1ZSB1cCBhIGZsdXNoIHRvIG9jY3VyIGltbWVkaWF0ZWx5IGFmdGVyIHRoZSBwZW5kaW5nIG9uZSBjb21wbGV0ZXMuXG4gICAgICAgICAgICB0aGlzLmZsdXNoQWdhaW5Qcm9taXNlID0gdGhpcy5mbHVzaFByb21pc2VcbiAgICAgICAgICAgICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmZsdXNoKCk7XG4gICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZmx1c2hBZ2FpblByb21pc2UgPSBudWxsO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuZmx1c2hBZ2FpblByb21pc2U7XG4gICAgICAgIH1cbiAgICAgICAgLy8gdGhlcmUgaXMgbm8gZmx1c2ggcHJvbWlzZSBvciB0aGVyZSB3YXMgYnV0IGl0IGhhcyBmaW5pc2hlZCwgc28gZG9cbiAgICAgICAgLy8gYSBicmFuZCBuZXcgb25lLCBkZXN0cm95aW5nIHRoZSBjaGFpbiB3aGljaCBtYXkgaGF2ZSBiZWVuIGJ1aWx0IHVwLlxuICAgICAgICB0aGlzLmZsdXNoUHJvbWlzZSA9IG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy5kYikge1xuICAgICAgICAgICAgICAgIC8vIG5vdCBjb25uZWN0ZWQgeWV0IG9yIHVzZXIgcmVqZWN0ZWQgYWNjZXNzIGZvciB1cyB0byByL3cgdG8gdGhlIGRiLlxuICAgICAgICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoXCJObyBjb25uZWN0ZWQgZGF0YWJhc2VcIikpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGxpbmVzID0gdGhpcy5sb2dnZXIuZmx1c2goKTtcbiAgICAgICAgICAgIGlmIChsaW5lcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgdHhuID0gdGhpcy5kYi50cmFuc2FjdGlvbihbXCJsb2dzXCIsIFwibG9nc2xhc3Rtb2RcIl0sIFwicmVhZHdyaXRlXCIpO1xuICAgICAgICAgICAgY29uc3Qgb2JqU3RvcmUgPSB0eG4ub2JqZWN0U3RvcmUoXCJsb2dzXCIpO1xuICAgICAgICAgICAgdHhuLm9uY29tcGxldGUgPSAoZXZlbnQpID0+IHtcbiAgICAgICAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgdHhuLm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyLmVycm9yKFwiRmFpbGVkIHRvIGZsdXNoIGxvZ3MgOiBcIiwgdHhuLmVycm9yKTtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKFwiRmFpbGVkIHRvIHdyaXRlIGxvZ3M6IFwiICsgdHhuLmVycm9yPy5tZXNzYWdlKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgb2JqU3RvcmUuYWRkKHRoaXMuZ2VuZXJhdGVMb2dFbnRyeShsaW5lcykpO1xuICAgICAgICAgICAgY29uc3QgbGFzdE1vZFN0b3JlID0gdHhuLm9iamVjdFN0b3JlKFwibG9nc2xhc3Rtb2RcIik7XG4gICAgICAgICAgICBsYXN0TW9kU3RvcmUucHV0KHRoaXMuZ2VuZXJhdGVMYXN0TW9kaWZpZWRUaW1lKCkpO1xuICAgICAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgICAgICAgIHRoaXMuZmx1c2hQcm9taXNlID0gbnVsbDtcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiB0aGlzLmZsdXNoUHJvbWlzZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDb25zdW1lIHRoZSBtb3N0IHJlY2VudCBsb2dzIGFuZCByZXR1cm4gdGhlbS4gT2xkZXIgbG9ncyB3aGljaCBhcmUgbm90XG4gICAgICogcmV0dXJuZWQgYXJlIGRlbGV0ZWQgYXQgdGhlIHNhbWUgdGltZSwgc28gdGhpcyBjYW4gYmUgY2FsbGVkIGF0IHN0YXJ0dXBcbiAgICAgKiB0byBkbyBob3VzZS1rZWVwaW5nIHRvIGtlZXAgdGhlIGxvZ3MgZnJvbSBncm93aW5nIHRvbyBsYXJnZS5cbiAgICAgKlxuICAgICAqIEByZXR1cm4ge1Byb21pc2U8T2JqZWN0W10+fSBSZXNvbHZlcyB0byBhbiBhcnJheSBvZiBvYmplY3RzLiBUaGUgYXJyYXkgaXNcbiAgICAgKiBzb3J0ZWQgaW4gdGltZSAob2xkZXN0IGZpcnN0KSBiYXNlZCBvbiB3aGVuIHRoZSBsb2cgZmlsZSB3YXMgY3JlYXRlZCAodGhlXG4gICAgICogbG9nIElEKS4gVGhlIG9iamVjdHMgaGF2ZSBzYWlkIGxvZyBJRCBpbiBhbiBcImlkXCIgZmllbGQgYW5kIFwibGluZXNcIiB3aGljaFxuICAgICAqIGlzIGEgYmlnIHN0cmluZyB3aXRoIGFsbCB0aGUgbmV3LWxpbmUgZGVsaW1pdGVkIGxvZ3MuXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIGNvbnN1bWUoKTogUHJvbWlzZTx7IGxpbmVzOiBzdHJpbmc7IGlkOiBzdHJpbmcgfVtdPiB7XG4gICAgICAgIGNvbnN0IGFsbExvZ0lkcyA9IGF3YWl0IHRoaXMuZmV0Y2hMb2dJZHMoKTtcbiAgICAgICAgbGV0IHJlbW92ZUxvZ0lkczogc3RyaW5nW10gPSBbXTtcbiAgICAgICAgY29uc3QgbG9nczogeyBsaW5lczogc3RyaW5nOyBpZDogc3RyaW5nIH1bXSA9IFtdO1xuICAgICAgICBsZXQgc2l6ZSA9IDA7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYWxsTG9nSWRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBpbnN0YW5jZUlkID0gYWxsTG9nSWRzW2ldO1xuICAgICAgICAgICAgY29uc3QgeyBsaW5lcywgdHJ1bmNhdGVkIH0gPSBhd2FpdCB0aGlzLmZldGNoTG9ncyhpbnN0YW5jZUlkLCBzaXplKTtcblxuICAgICAgICAgICAgLy8gYWx3YXlzIGFkZCB0aGUgcmV0dXJuZWQgbG9nczogZmV0Y2hMb2dzIHdpbGwgdHJ1bmNhdGUgb25jZSBpdCBoaXRzIHRoZSBzaXplIGxpbWl0LFxuICAgICAgICAgICAgLy8gc28gd2UnbGwgZ28gb3ZlciB0aGUgbWF4IGJ1dCBvbmx5IGJ5IG9uZSBmcmFnbWVudCdzIHdvcnRoLlxuICAgICAgICAgICAgbG9ncy5wdXNoKHsgbGluZXMsIGlkOiBpbnN0YW5jZUlkIH0pO1xuICAgICAgICAgICAgc2l6ZSArPSBsaW5lcy5sZW5ndGg7XG5cbiAgICAgICAgICAgIC8vIElmIGZldGNoTG9ncyB0cnVuY2F0ZWQgd2UnbGwgbm93IGJlIGF0IG9yIG92ZXIgdGhlIHNpemUgbGltaXQsXG4gICAgICAgICAgICAvLyBpbiB3aGljaCBjYXNlIHdlIHNob3VsZCBzdG9wIGFuZCByZW1vdmUgdGhlIHJlc3Qgb2YgdGhlIGxvZyBmaWxlcy5cbiAgICAgICAgICAgIGlmICh0cnVuY2F0ZWQpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIubG9nKFxuICAgICAgICAgICAgICAgICAgICBgcmFnZXNoYWtlOiByZWFjaGVkIHNpemUgbGltaXQgd2hpbGUgcHJvY2Vzc2luZyBpbnN0YW5jZSAke2kgKyAxfS8ke1xuICAgICAgICAgICAgICAgICAgICAgICAgYWxsTG9nSWRzLmxlbmd0aFxuICAgICAgICAgICAgICAgICAgICB9ICgke2luc3RhbmNlSWR9KSwgd2l0aCAke3NpemV9IGJ5dGVzIG9mIGxvZ3M6IHdpbGwgZHJvcCBmdXJ0aGVyIGluc3RhbmNlc2AsXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAvLyB0aGUgcmVtYWluaW5nIGxvZyBJRHMgc2hvdWxkIGJlIHJlbW92ZWQuIElmIHdlIGdvIG91dCBvZlxuICAgICAgICAgICAgICAgIC8vIGJvdW5kcyB0aGlzIGlzIGp1c3QgW11cbiAgICAgICAgICAgICAgICByZW1vdmVMb2dJZHMgPSBhbGxMb2dJZHMuc2xpY2UoaSArIDEpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmIChyZW1vdmVMb2dJZHMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgbG9nZ2VyLmxvZyhgcmFnZXNoYWtlOiByZW1vdmluZyBsb2dzOiAke3JlbW92ZUxvZ0lkc31gKTtcbiAgICAgICAgICAgIC8vIERvbid0IGF3YWl0IHRoaXMgYmVjYXVzZSBpdCdzIG5vbi1mYXRhbCBpZiB3ZSBjYW4ndCBjbGVhbiB1cFxuICAgICAgICAgICAgLy8gbG9ncy5cbiAgICAgICAgICAgIFByb21pc2UuYWxsKHJlbW92ZUxvZ0lkcy5tYXAoKGlkKSA9PiB0aGlzLmRlbGV0ZUxvZ3MoaWQpKSkudGhlbihcbiAgICAgICAgICAgICAgICAoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGxvZ2dlci5sb2coYFJlbW92ZWQgJHtyZW1vdmVMb2dJZHMubGVuZ3RofSBvbGQgbG9ncy5gKTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIChlcnIpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgbG9nZ2VyLmVycm9yKGVycik7XG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGxvZ3M7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRmV0Y2ggYWxsIHRoZSBhcHBsaWNhdGlvbiBpbnN0YW5jZSBuYW1lcyBmcm9tIHRoZSBkYXRhYmFzZS5cbiAgICAgKi9cbiAgICBwcml2YXRlIGZldGNoTG9nSWRzKCk6IFByb21pc2U8c3RyaW5nW10+IHtcbiAgICAgICAgY29uc3QgZGIgPSB0aGlzLmRiO1xuICAgICAgICBpZiAoIWRiKSByZXR1cm4gUHJvbWlzZS5yZWplY3QoXCJEQiB1bmF2YWlsYWJsZVwiKTtcblxuICAgICAgICAvLyBUbyBnYXRoZXIgYWxsIHRoZSBsb2cgSURzLCBxdWVyeSBmb3IgYWxsIHJlY29yZHMgaW4gbG9nc2xhc3Rtb2QuXG4gICAgICAgIGNvbnN0IG8gPSBkYi50cmFuc2FjdGlvbihcImxvZ3NsYXN0bW9kXCIsIFwicmVhZG9ubHlcIikub2JqZWN0U3RvcmUoXCJsb2dzbGFzdG1vZFwiKTtcbiAgICAgICAgcmV0dXJuIHNlbGVjdFF1ZXJ5KG8sIHVuZGVmaW5lZCwgKGN1cnNvcikgPT4ge1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBpZDogY3Vyc29yLnZhbHVlLmlkLFxuICAgICAgICAgICAgICAgIHRzOiBjdXJzb3IudmFsdWUudHMsXG4gICAgICAgICAgICB9O1xuICAgICAgICB9KS50aGVuKChyZXMpID0+IHtcbiAgICAgICAgICAgIC8vIFNvcnQgSURzIGJ5IHRpbWVzdGFtcCAobmV3ZXN0IGZpcnN0KVxuICAgICAgICAgICAgcmV0dXJuIHJlc1xuICAgICAgICAgICAgICAgIC5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBiLnRzIC0gYS50cztcbiAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgIC5tYXAoKGEpID0+IGEuaWQpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGZXRjaCBsb2dzIGZvciBhIGdpdmVuIGFwcGxpY2F0aW9uIGluc3RhbmNlIGZyb20gdGhlIGRhdGFiYXNlLCBzdG9wcGluZyBvbmNlIHdlIGhpdCB0aGUgc2l6ZSBsaW1pdC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBpZCAtIEFwcGxpY2F0aW9uIGluc3RhbmNlIHRvIGZldGNoIGxvZ3MgZm9yLlxuICAgICAqIEBwYXJhbSBzaXplU29GYXIgLSBBbW91bnQgb2YgbG9ncyB3ZSBoYXZlIGFscmVhZHkgcmV0cmlldmVkIGZyb20gb3RoZXIgaW5zdGFuY2VzLlxuICAgICAqXG4gICAgICogQHJldHVybnMgQW4gb2JqZWN0IHdpdGggdGhlIHByb3BlcnRpZXM6XG4gICAgICogICogYGxpbmVzYDogdGhlIGNvbmNhdGVuYXRlZCBsb2dzIGZvciB0aGlzIElEXG4gICAgICogICogYHRydW5jYXRlZGA6IHdoZXRoZXIgdGhlIG91dHB1dCB3YXMgdHJ1bmNhdGVkIGR1ZSB0byBoaXR0aW5nIHRoZSBzaXplIGxpbWl0LlxuICAgICAqL1xuICAgIHByaXZhdGUgZmV0Y2hMb2dzKFxuICAgICAgICBpZDogc3RyaW5nLFxuICAgICAgICBzaXplU29GYXI6IG51bWJlcixcbiAgICApOiBQcm9taXNlPHtcbiAgICAgICAgbGluZXM6IHN0cmluZztcbiAgICAgICAgdHJ1bmNhdGVkOiBib29sZWFuO1xuICAgIH0+IHtcbiAgICAgICAgY29uc3QgZGIgPSB0aGlzLmRiO1xuICAgICAgICBpZiAoIWRiKSByZXR1cm4gUHJvbWlzZS5yZWplY3QoXCJEQiB1bmF2YWlsYWJsZVwiKTtcblxuICAgICAgICBjb25zdCBvYmplY3RTdG9yZSA9IGRiLnRyYW5zYWN0aW9uKFwibG9nc1wiLCBcInJlYWRvbmx5XCIpLm9iamVjdFN0b3JlKFwibG9nc1wiKTtcblxuICAgICAgICAvKiogRGV0ZXJtaW5lIHdoZXRoZXIgd2Ugc2hvdWxkIHN0b3AgY29sbGVjdGluZyBsb2dzIGFmdGVyIHRoaXMgYmF0Y2guXG4gICAgICAgICAqXG4gICAgICAgICAqIEBwYXJhbSBzaXplU29GYXIgLSBUaGUgdG90YWwgYW1vdW50IG9mIGxvZ3MgY29sbGVjdGVkIHNvIGZhci5cbiAgICAgICAgICogQHBhcmFtIGxvZ0JhdGNoVGltZXN0YW1wIC0gVGhlIHRpbWVzdGFtcCBvZiB0aGUgbW9zdCByZWNlbnQgYmF0Y2ggb2YgbG9ncyBjb2xsZWN0ZWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIEByZXR1cm5zIGB0cnVlYCBpZiB3ZSBzaG91bGQgc3RvcCBhZnRlciB0aGlzIGJhdGNoLlxuICAgICAgICAgKi9cbiAgICAgICAgZnVuY3Rpb24gc2hvdWxkVHJ1bmNhdGVBZnRlckxvZ0JhdGNoKHNpemVTb0ZhcjogbnVtYmVyLCBsb2dCYXRjaFRpbWVzdGFtcDogbnVtYmVyKTogYm9vbGVhbiB7XG4gICAgICAgICAgICAvLyBGaXJzdCBjaGVjayBpZiB3ZSBoYXZlIGV4Y2VlZGVkIHRoZSBhYnNvbHV0ZSBsaW1pdFxuICAgICAgICAgICAgaWYgKHNpemVTb0ZhciA+PSBNQVhfTE9HX1NJWkVfMjRIKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIE90aGVyd2lzZSwgY2hlY2sgaWYgdGhlIG1vc3QgcmVjZW50IGJhdGNoIGlzIG1vcmUgdGhhbiAyNEggb2xkLCBhbmQgd2UgaGF2ZSBleGNlZWRlZCB0aGUgbGltaXQgZm9yIGxvZ3Mgb3ZlciAyNEhcbiAgICAgICAgICAgIGlmIChEYXRlLm5vdygpIC0gbG9nQmF0Y2hUaW1lc3RhbXAgPj0gMjQgKiAzNjAwICogMTAwMCAmJiBzaXplU29GYXIgPj0gTUFYX0xPR19TSVpFKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIE90aGVyd2lzZSwgd2UncmUgZ29vZC5cbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBxdWVyeSA9IG9iamVjdFN0b3JlLmluZGV4KFwiaWRcIikub3BlbkN1cnNvcihJREJLZXlSYW5nZS5vbmx5KGlkKSwgXCJwcmV2XCIpO1xuICAgICAgICAgICAgbGV0IGxpbmVzID0gXCJcIjtcbiAgICAgICAgICAgIHF1ZXJ5Lm9uZXJyb3IgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihcIlF1ZXJ5IGZhaWxlZDogXCIgKyBxdWVyeS5lcnJvcj8ubWVzc2FnZSkpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHF1ZXJ5Lm9uc3VjY2VzcyA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBjdXJzb3IgPSBxdWVyeS5yZXN1bHQ7XG4gICAgICAgICAgICAgICAgaWYgKCFjdXJzb3IpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gZW5kIG9mIHJlc3VsdHNcbiAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZSh7IGxpbmVzLCB0cnVuY2F0ZWQ6IGZhbHNlIH0pO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNvbnN0IG5ld0xpbmVzID0gY3Vyc29yLnZhbHVlLmxpbmVzO1xuICAgICAgICAgICAgICAgIC8vIFRoZSBxdWVyeSByZXR1cm5zIGxvZyBjaHVua3MgaW4gcmV2ZXJzZSB0aW1lIG9yZGVyLCBzbyBwcmVwZW5kIHRoaXMgbmV3IGNodW5rIHRvIHRoZSBidWZmZXIuXG4gICAgICAgICAgICAgICAgbGluZXMgPSBuZXdMaW5lcyArIGxpbmVzO1xuICAgICAgICAgICAgICAgIHNpemVTb0ZhciArPSBuZXdMaW5lcy5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICAvLyBJZiB3ZSBoYXZlIG5vdyBleGNlZWRlZCB0aGUgc2l6ZSBsaW1pdCwgc3RvcC5cbiAgICAgICAgICAgICAgICBpZiAoc2hvdWxkVHJ1bmNhdGVBZnRlckxvZ0JhdGNoKHNpemVTb0ZhciwgY3Vyc29yLnZhbHVlLnRzID8/IDApKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc29sdmUoeyBsaW5lcywgdHJ1bmNhdGVkOiB0cnVlIH0pO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGN1cnNvci5jb250aW51ZSgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERlbGV0ZSBsb2dzIGZvciBhIGdpdmVuIGFwcGxpY2F0aW9uIGluc3RhbmNlLlxuICAgICAqXG4gICAgICogQHBhcmFtIGlkIC0gQXBwbGljYXRpb24gaW5zdGFuY2UgdG8gZGVsZXRlIGxvZ3MgZm9yLlxuICAgICAqL1xuICAgIHByaXZhdGUgZGVsZXRlTG9ncyhpZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IGRiID0gdGhpcy5kYjtcbiAgICAgICAgaWYgKCFkYikgcmV0dXJuIFByb21pc2UucmVqZWN0KFwiREIgdW5hdmFpbGFibGVcIik7XG5cbiAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlPHZvaWQ+KChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHR4biA9IGRiLnRyYW5zYWN0aW9uKFtcImxvZ3NcIiwgXCJsb2dzbGFzdG1vZFwiXSwgXCJyZWFkd3JpdGVcIik7XG4gICAgICAgICAgICBjb25zdCBvID0gdHhuLm9iamVjdFN0b3JlKFwibG9nc1wiKTtcbiAgICAgICAgICAgIC8vIG9ubHkgbG9hZCB0aGUga2V5IHBhdGgsIG5vdCB0aGUgZGF0YSB3aGljaCBtYXkgYmUgaHVnZVxuICAgICAgICAgICAgY29uc3QgcXVlcnkgPSBvLmluZGV4KFwiaWRcIikub3BlbktleUN1cnNvcihJREJLZXlSYW5nZS5vbmx5KGlkKSk7XG4gICAgICAgICAgICBxdWVyeS5vbnN1Y2Nlc3MgPSAoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgY3Vyc29yID0gcXVlcnkucmVzdWx0O1xuICAgICAgICAgICAgICAgIGlmICghY3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgby5kZWxldGUoY3Vyc29yLnByaW1hcnlLZXkpO1xuICAgICAgICAgICAgICAgIGN1cnNvci5jb250aW51ZSgpO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHR4bi5vbmNvbXBsZXRlID0gKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICB0eG4ub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWplY3QobmV3IEVycm9yKFwiRmFpbGVkIHRvIGRlbGV0ZSBsb2dzIGZvciBcIiArIGAnJHtpZH0nIDogJHtxdWVyeS5lcnJvcj8ubWVzc2FnZX1gKSk7XG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgLy8gZGVsZXRlIGxhc3QgbW9kaWZpZWQgZW50cmllc1xuICAgICAgICAgICAgY29uc3QgbGFzdE1vZFN0b3JlID0gdHhuLm9iamVjdFN0b3JlKFwibG9nc2xhc3Rtb2RcIik7XG4gICAgICAgICAgICBsYXN0TW9kU3RvcmUuZGVsZXRlKGlkKTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqIEdlbmVyYXRlIHRoZSBvYmplY3QgdG8gYmUgc3RvcmVkIGluIHRoZSBgbG9nc2Agc3RvcmUgKi9cbiAgICBwcml2YXRlIGdlbmVyYXRlTG9nRW50cnkobGluZXM6IHN0cmluZyk6IHsgaWQ6IHN0cmluZzsgbGluZXM6IHN0cmluZzsgaW5kZXg6IG51bWJlcjsgdHM6IG51bWJlciB9IHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGlkOiB0aGlzLmlkLFxuICAgICAgICAgICAgbGluZXM6IGxpbmVzLFxuICAgICAgICAgICAgaW5kZXg6IHRoaXMuaW5kZXgrKyxcbiAgICAgICAgICAgIC8qKiBUaGUgdGltZXN0YW1wIGF0IHdoaWNoIHRoZSBsaW5lIHdhcyAqZmx1c2hlZCogKG5vdCBuZWNlc3NhcmlseSB3aGVuIGl0IHdhcyB3cml0dGVuKS4gKi9cbiAgICAgICAgICAgIHRzOiBEYXRlLm5vdygpLFxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHByaXZhdGUgZ2VuZXJhdGVMYXN0TW9kaWZpZWRUaW1lKCk6IHsgaWQ6IHN0cmluZzsgdHM6IG51bWJlciB9IHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGlkOiB0aGlzLmlkLFxuICAgICAgICAgICAgdHM6IERhdGUubm93KCksXG4gICAgICAgIH07XG4gICAgfVxufVxuXG4vKipcbiAqIEhlbHBlciBtZXRob2QgdG8gY29sbGVjdCByZXN1bHRzIGZyb20gYSBDdXJzb3IgYW5kIHByb21pc2VpZnkgaXQuXG4gKiBAcGFyYW0ge09iamVjdFN0b3JlfEluZGV4fSBzdG9yZSBUaGUgc3RvcmUgdG8gcGVyZm9ybSBvcGVuQ3Vyc29yIG9uLlxuICogQHBhcmFtIHtJREJLZXlSYW5nZT19IGtleVJhbmdlIE9wdGlvbmFsIGtleSByYW5nZSB0byBhcHBseSBvbiB0aGUgY3Vyc29yLlxuICogQHBhcmFtIHtGdW5jdGlvbn0gcmVzdWx0TWFwcGVyIEEgZnVuY3Rpb24gd2hpY2ggaXMgcmVwZWF0ZWRseSBjYWxsZWQgd2l0aCBhXG4gKiBDdXJzb3IuXG4gKiBSZXR1cm4gdGhlIGRhdGEgeW91IHdhbnQgdG8ga2VlcC5cbiAqIEByZXR1cm4ge1Byb21pc2U8VFtdPn0gUmVzb2x2ZXMgdG8gYW4gYXJyYXkgb2Ygd2hhdGV2ZXIgeW91IHJldHVybmVkIGZyb21cbiAqIHJlc3VsdE1hcHBlci5cbiAqL1xuZnVuY3Rpb24gc2VsZWN0UXVlcnk8VD4oXG4gICAgc3RvcmU6IElEQkluZGV4IHwgSURCT2JqZWN0U3RvcmUsXG4gICAga2V5UmFuZ2U6IElEQktleVJhbmdlIHwgdW5kZWZpbmVkLFxuICAgIHJlc3VsdE1hcHBlcjogKGN1cnNvcjogSURCQ3Vyc29yV2l0aFZhbHVlKSA9PiBULFxuKTogUHJvbWlzZTxUW10+IHtcbiAgICBjb25zdCBxdWVyeSA9IHN0b3JlLm9wZW5DdXJzb3Ioa2V5UmFuZ2UpO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGNvbnN0IHJlc3VsdHM6IFRbXSA9IFtdO1xuICAgICAgICBxdWVyeS5vbmVycm9yID0gKCkgPT4ge1xuICAgICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihcIlF1ZXJ5IGZhaWxlZDogXCIgKyBxdWVyeS5lcnJvcj8ubWVzc2FnZSkpO1xuICAgICAgICB9O1xuICAgICAgICAvLyBjb2xsZWN0IHJlc3VsdHNcbiAgICAgICAgcXVlcnkub25zdWNjZXNzID0gKCkgPT4ge1xuICAgICAgICAgICAgY29uc3QgY3Vyc29yID0gcXVlcnkucmVzdWx0O1xuICAgICAgICAgICAgaWYgKCFjdXJzb3IpIHtcbiAgICAgICAgICAgICAgICByZXNvbHZlKHJlc3VsdHMpO1xuICAgICAgICAgICAgICAgIHJldHVybjsgLy8gZW5kIG9mIHJlc3VsdHNcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJlc3VsdHMucHVzaChyZXN1bHRNYXBwZXIoY3Vyc29yKSk7XG4gICAgICAgICAgICBjdXJzb3IuY29udGludWUoKTtcbiAgICAgICAgfTtcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBDb25maWd1cmUgcmFnZSBzaGFraW5nIHN1cHBvcnQgZm9yIHNlbmRpbmcgYnVnIHJlcG9ydHMuXG4gKiBNb2RpZmllcyBnbG9iYWxzLlxuICogQHBhcmFtIHtib29sZWFufSBzZXRVcFBlcnNpc3RlbmNlIFdoZW4gdHJ1ZSAoZGVmYXVsdCksIHRoZSBwZXJzaXN0ZW5jZSB3aWxsXG4gKiBiZSBzZXQgdXAgaW1tZWRpYXRlbHkgZm9yIHRoZSBsb2dzLlxuICogQHJldHVybiB7UHJvbWlzZX0gUmVzb2x2ZXMgd2hlbiBzZXQgdXAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbml0KHNldFVwUGVyc2lzdGVuY2UgPSB0cnVlKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKGdsb2JhbC5teF9yYWdlX2luaXRQcm9taXNlKSB7XG4gICAgICAgIHJldHVybiBnbG9iYWwubXhfcmFnZV9pbml0UHJvbWlzZTtcbiAgICB9XG4gICAgZ2xvYmFsLm14X3JhZ2VfbG9nZ2VyID0gbmV3IENvbnNvbG