matrix-react-sdk
Version:
SDK for matrix.org using React
392 lines (381 loc) • 59.4 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SlidingSyncManager = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _matrix = require("matrix-js-sdk/src/matrix");
var _slidingSync = require("matrix-js-sdk/src/sliding-sync");
var _logger = require("matrix-js-sdk/src/logger");
var _utils = require("matrix-js-sdk/src/utils");
var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore"));
var _SlidingSyncController = _interopRequireDefault(require("./settings/controllers/SlidingSyncController"));
var _SlidingSyncManager;
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
/*
* Sliding Sync Architecture - MSC https://github.com/matrix-org/matrix-spec-proposals/pull/3575
*
* This is a holistic summary of the changes made to Element-Web / React SDK / JS SDK to enable sliding sync.
* This summary will hopefully signpost where developers need to look if they want to make changes to this code.
*
* At the lowest level, the JS SDK contains an HTTP API wrapper function in client.ts. This is used by
* a SlidingSync class in JS SDK, which contains code to handle list operations (INSERT/DELETE/SYNC/etc)
* and contains the main request API bodies, but has no code to control updating JS SDK structures: it just
* exposes an EventEmitter to listen for updates. When MatrixClient.startClient is called, callers need to
* provide a SlidingSync instance as this contains the main request API params (timeline limit, required state,
* how many lists, etc).
*
* The SlidingSyncSdk INTERNAL class in JS SDK attaches listeners to SlidingSync to update JS SDK Room objects,
* and it conveniently exposes an identical public API to SyncApi (to allow it to be a drop-in replacement).
*
* At the highest level, SlidingSyncManager contains mechanisms to tell UI lists which rooms to show,
* and contains the core request API params used in Element-Web. It does this by listening for events
* emitted by the SlidingSync class and by modifying the request API params on the SlidingSync class.
*
* (entry point) (updates JS SDK)
* SlidingSyncManager SlidingSyncSdk
* | |
* +------------------.------------------+
* listens | listens
* SlidingSync
* (sync loop,
* list ops)
*/
// how long to long poll for
const SLIDING_SYNC_TIMEOUT_MS = 20 * 1000;
// the things to fetch when a user clicks on a room
const DEFAULT_ROOM_SUBSCRIPTION_INFO = {
timeline_limit: 50,
// missing required_state which will change depending on the kind of room
include_old_rooms: {
timeline_limit: 0,
required_state: [
// state needed to handle space navigation and tombstone chains
[_matrix.EventType.RoomCreate, ""], [_matrix.EventType.RoomTombstone, ""], [_matrix.EventType.SpaceChild, _slidingSync.MSC3575_WILDCARD], [_matrix.EventType.SpaceParent, _slidingSync.MSC3575_WILDCARD], [_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME]]
}
};
// lazy load room members so rooms like Matrix HQ don't take forever to load
const UNENCRYPTED_SUBSCRIPTION_NAME = "unencrypted";
const UNENCRYPTED_SUBSCRIPTION = Object.assign({
required_state: [[_slidingSync.MSC3575_WILDCARD, _slidingSync.MSC3575_WILDCARD],
// all events
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME],
// except for m.room.members, get our own membership
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_LAZY] // ...and lazy load the rest.
]
}, DEFAULT_ROOM_SUBSCRIPTION_INFO);
// we need all the room members in encrypted rooms because we need to know which users to encrypt
// messages for.
const ENCRYPTED_SUBSCRIPTION = Object.assign({
required_state: [[_slidingSync.MSC3575_WILDCARD, _slidingSync.MSC3575_WILDCARD] // all events
]
}, DEFAULT_ROOM_SUBSCRIPTION_INFO);
/**
* This class manages the entirety of sliding sync at a high UI/UX level. It controls the placement
* of placeholders in lists, controls updating sliding window ranges, and controls which events
* are pulled down when. The intention behind this manager is be the single place to look for sliding
* sync options and code.
*/
class SlidingSyncManager {
constructor() {
(0, _defineProperty2.default)(this, "slidingSync", void 0);
(0, _defineProperty2.default)(this, "client", void 0);
(0, _defineProperty2.default)(this, "configureDefer", (0, _utils.defer)());
}
static get instance() {
return SlidingSyncManager.internalInstance;
}
configure(client, proxyUrl) {
this.client = client;
// by default use the encrypted subscription as that gets everything, which is a safer
// default than potentially missing member events.
this.slidingSync = new _slidingSync.SlidingSync(proxyUrl, new Map(), ENCRYPTED_SUBSCRIPTION, client, SLIDING_SYNC_TIMEOUT_MS);
this.slidingSync.addCustomSubscription(UNENCRYPTED_SUBSCRIPTION_NAME, UNENCRYPTED_SUBSCRIPTION);
// set the space list
this.slidingSync.setList(SlidingSyncManager.ListSpaces, {
ranges: [[0, 20]],
sort: ["by_name"],
slow_get_all_rooms: true,
timeline_limit: 0,
required_state: [[_matrix.EventType.RoomJoinRules, ""],
// the public icon on the room list
[_matrix.EventType.RoomAvatar, ""],
// any room avatar
[_matrix.EventType.RoomTombstone, ""],
// lets JS SDK hide rooms which are dead
[_matrix.EventType.RoomEncryption, ""],
// lets rooms be configured for E2EE correctly
[_matrix.EventType.RoomCreate, ""],
// for isSpaceRoom checks
[_matrix.EventType.SpaceChild, _slidingSync.MSC3575_WILDCARD],
// all space children
[_matrix.EventType.SpaceParent, _slidingSync.MSC3575_WILDCARD],
// all space parents
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME] // lets the client calculate that we are in fact in the room
],
include_old_rooms: {
timeline_limit: 0,
required_state: [[_matrix.EventType.RoomCreate, ""], [_matrix.EventType.RoomTombstone, ""],
// lets JS SDK hide rooms which are dead
[_matrix.EventType.SpaceChild, _slidingSync.MSC3575_WILDCARD],
// all space children
[_matrix.EventType.SpaceParent, _slidingSync.MSC3575_WILDCARD],
// all space parents
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME] // lets the client calculate that we are in fact in the room
]
},
filters: {
room_types: ["m.space"]
}
});
this.configureDefer.resolve();
return this.slidingSync;
}
/**
* Ensure that this list is registered.
* @param listKey The list key to register
* @param updateArgs The fields to update on the list.
* @returns The complete list request params
*/
async ensureListRegistered(listKey, updateArgs) {
_logger.logger.debug("ensureListRegistered:::", listKey, updateArgs);
await this.configureDefer.promise;
let list = this.slidingSync.getListParams(listKey);
if (!list) {
list = {
ranges: [[0, 20]],
sort: ["by_notification_level", "by_recency"],
timeline_limit: 1,
// most recent message display: though this seems to only be needed for favourites?
required_state: [[_matrix.EventType.RoomJoinRules, ""],
// the public icon on the room list
[_matrix.EventType.RoomAvatar, ""],
// any room avatar
[_matrix.EventType.RoomTombstone, ""],
// lets JS SDK hide rooms which are dead
[_matrix.EventType.RoomEncryption, ""],
// lets rooms be configured for E2EE correctly
[_matrix.EventType.RoomCreate, ""],
// for isSpaceRoom checks
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME] // lets the client calculate that we are in fact in the room
],
include_old_rooms: {
timeline_limit: 0,
required_state: [[_matrix.EventType.RoomCreate, ""], [_matrix.EventType.RoomTombstone, ""],
// lets JS SDK hide rooms which are dead
[_matrix.EventType.SpaceChild, _slidingSync.MSC3575_WILDCARD],
// all space children
[_matrix.EventType.SpaceParent, _slidingSync.MSC3575_WILDCARD],
// all space parents
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME] // lets the client calculate that we are in fact in the room
]
}
};
list = Object.assign(list, updateArgs);
} else {
const updatedList = Object.assign({}, list, updateArgs);
// cannot use objectHasDiff as we need to do deep diff checking
if (JSON.stringify(list) === JSON.stringify(updatedList)) {
_logger.logger.debug("list matches, not sending, update => ", updateArgs);
return list;
}
list = updatedList;
}
try {
// if we only have range changes then call a different function so we don't nuke the list from before
if (updateArgs.ranges && Object.keys(updateArgs).length === 1) {
await this.slidingSync.setListRanges(listKey, updateArgs.ranges);
} else {
await this.slidingSync.setList(listKey, list);
}
} catch (err) {
_logger.logger.debug("ensureListRegistered: update failed txn_id=", err);
}
return this.slidingSync.getListParams(listKey);
}
async setRoomVisible(roomId, visible) {
await this.configureDefer.promise;
const subscriptions = this.slidingSync.getRoomSubscriptions();
if (visible) {
subscriptions.add(roomId);
} else {
subscriptions.delete(roomId);
}
const room = this.client?.getRoom(roomId);
let shouldLazyLoad = !this.client?.isRoomEncrypted(roomId);
if (!room) {
// default to safety: request all state if we can't work it out. This can happen if you
// refresh the app whilst viewing a room: we call setRoomVisible before we know anything
// about the room.
shouldLazyLoad = false;
}
_logger.logger.log("SlidingSync setRoomVisible:", roomId, visible, "shouldLazyLoad:", shouldLazyLoad);
if (shouldLazyLoad) {
// lazy load this room
this.slidingSync.useCustomSubscription(roomId, UNENCRYPTED_SUBSCRIPTION_NAME);
}
const p = this.slidingSync.modifyRoomSubscriptions(subscriptions);
if (room) {
return roomId; // we have data already for this room, show immediately e.g it's in a list
}
try {
// wait until the next sync before returning as RoomView may need to know the current state
await p;
} catch (err) {
_logger.logger.warn("SlidingSync setRoomVisible:", roomId, visible, "failed to confirm transaction");
}
return roomId;
}
/**
* Retrieve all rooms on the user's account. Used for pre-populating the local search cache.
* Retrieval is gradual over time.
* @param batchSize The number of rooms to return in each request.
* @param gapBetweenRequestsMs The number of milliseconds to wait between requests.
*/
async startSpidering(batchSize, gapBetweenRequestsMs) {
await (0, _utils.sleep)(gapBetweenRequestsMs); // wait a bit as this is called on first render so let's let things load
let startIndex = batchSize;
let hasMore = true;
let firstTime = true;
while (hasMore) {
const endIndex = startIndex + batchSize - 1;
try {
const ranges = [[0, batchSize - 1], [startIndex, endIndex]];
if (firstTime) {
await this.slidingSync.setList(SlidingSyncManager.ListSearch, {
// e.g [0,19] [20,39] then [0,19] [40,59]. We keep [0,20] constantly to ensure
// any changes to the list whilst spidering are caught.
ranges: ranges,
sort: ["by_recency" // this list isn't shown on the UI so just sorting by timestamp is enough
],
timeline_limit: 0,
// we only care about the room details, not messages in the room
required_state: [[_matrix.EventType.RoomJoinRules, ""],
// the public icon on the room list
[_matrix.EventType.RoomAvatar, ""],
// any room avatar
[_matrix.EventType.RoomTombstone, ""],
// lets JS SDK hide rooms which are dead
[_matrix.EventType.RoomEncryption, ""],
// lets rooms be configured for E2EE correctly
[_matrix.EventType.RoomCreate, ""],
// for isSpaceRoom checks
[_matrix.EventType.RoomMember, _slidingSync.MSC3575_STATE_KEY_ME] // lets the client calculate that we are in fact in the room
],
// we don't include_old_rooms here in an effort to reduce the impact of spidering all rooms
// on the user's account. This means some data in the search dialog results may be inaccurate
// e.g membership of space, but this will be corrected when the user clicks on the room
// as the direct room subscription does include old room iterations.
filters: {
// we get spaces via a different list, so filter them out
not_room_types: ["m.space"]
}
});
} else {
await this.slidingSync.setListRanges(SlidingSyncManager.ListSearch, ranges);
}
} catch (err) {
// do nothing, as we reject only when we get interrupted but that's fine as the next
// request will include our data
} finally {
// gradually request more over time, even on errors.
await (0, _utils.sleep)(gapBetweenRequestsMs);
}
const listData = this.slidingSync.getListData(SlidingSyncManager.ListSearch);
hasMore = endIndex + 1 < listData.joinedCount;
startIndex += batchSize;
firstTime = false;
}
}
/**
* Set up the Sliding Sync instance; configures the end point and starts spidering.
* The sliding sync endpoint is derived the following way:
* 1. The user-defined sliding sync proxy URL (legacy, for backwards compatibility)
* 2. The client `well-known` sliding sync proxy URL [declared at the unstable prefix](https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#unstable-prefix)
* 3. The homeserver base url (for native server support)
* @param client The MatrixClient to use
* @returns A working Sliding Sync or undefined
*/
async setup(client) {
const baseUrl = client.baseUrl;
const proxyUrl = _SettingsStore.default.getValue("feature_sliding_sync_proxy_url");
const wellKnownProxyUrl = await this.getProxyFromWellKnown(client);
const slidingSyncEndpoint = proxyUrl || wellKnownProxyUrl || baseUrl;
this.configure(client, slidingSyncEndpoint);
_logger.logger.info("Sliding sync activated at", slidingSyncEndpoint);
this.startSpidering(100, 50); // 100 rooms at a time, 50ms apart
return this.slidingSync;
}
/**
* Get the sliding sync proxy URL from the client well known
* @param client The MatrixClient to use
* @return The proxy url
*/
async getProxyFromWellKnown(client) {
let proxyUrl;
try {
const clientDomain = await client.getDomain();
if (clientDomain === null) {
throw new RangeError("Homeserver domain is null");
}
const clientWellKnown = await _matrix.AutoDiscovery.findClientConfig(clientDomain);
proxyUrl = clientWellKnown?.["org.matrix.msc3575.proxy"]?.url;
} catch (e) {
// Either client.getDomain() is null so we've shorted out, or is invalid so `AutoDiscovery.findClientConfig` has thrown
}
if (proxyUrl != undefined) {
_logger.logger.log("getProxyFromWellKnown: client well-known declares sliding sync proxy at", proxyUrl);
}
return proxyUrl;
}
/**
* Check if the server "natively" supports sliding sync (with an unstable endpoint).
* @param client The MatrixClient to use
* @return Whether the "native" (unstable) endpoint is supported
*/
async nativeSlidingSyncSupport(client) {
// Per https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1589542561
// `client` can be undefined/null in tests for some reason.
const support = await client?.doesServerSupportUnstableFeature("org.matrix.msc3575");
if (support) {
_logger.logger.log("nativeSlidingSyncSupport: sliding sync advertised as unstable");
}
return support;
}
/**
* Check whether our homeserver has sliding sync support, that the endpoint is up, and
* is a sliding sync endpoint.
*
* Sets static member `SlidingSyncController.serverSupportsSlidingSync`
* @param client The MatrixClient to use
*/
async checkSupport(client) {
if (await this.nativeSlidingSyncSupport(client)) {
_SlidingSyncController.default.serverSupportsSlidingSync = true;
return;
}
const proxyUrl = await this.getProxyFromWellKnown(client);
if (proxyUrl != undefined) {
const response = await fetch(new URL("/client/server.json", proxyUrl), {
method: _matrix.Method.Get,
signal: (0, _matrix.timeoutSignal)(10 * 1000) // 10s
});
if (response.status === 200) {
_logger.logger.log("checkSupport: well-known sliding sync proxy is up at", proxyUrl);
_SlidingSyncController.default.serverSupportsSlidingSync = true;
}
}
}
}
exports.SlidingSyncManager = SlidingSyncManager;
_SlidingSyncManager = SlidingSyncManager;
(0, _defineProperty2.default)(SlidingSyncManager, "ListSpaces", "space_list");
(0, _defineProperty2.default)(SlidingSyncManager, "ListSearch", "search_list");
(0, _defineProperty2.default)(SlidingSyncManager, "internalInstance", new _SlidingSyncManager());
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4IiwicmVxdWlyZSIsIl9zbGlkaW5nU3luYyIsIl9sb2dnZXIiLCJfdXRpbHMiLCJfU2V0dGluZ3NTdG9yZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfU2xpZGluZ1N5bmNDb250cm9sbGVyIiwiX1NsaWRpbmdTeW5jTWFuYWdlciIsIlNMSURJTkdfU1lOQ19USU1FT1VUX01TIiwiREVGQVVMVF9ST09NX1NVQlNDUklQVElPTl9JTkZPIiwidGltZWxpbmVfbGltaXQiLCJpbmNsdWRlX29sZF9yb29tcyIsInJlcXVpcmVkX3N0YXRlIiwiRXZlbnRUeXBlIiwiUm9vbUNyZWF0ZSIsIlJvb21Ub21ic3RvbmUiLCJTcGFjZUNoaWxkIiwiTVNDMzU3NV9XSUxEQ0FSRCIsIlNwYWNlUGFyZW50IiwiUm9vbU1lbWJlciIsIk1TQzM1NzVfU1RBVEVfS0VZX01FIiwiVU5FTkNSWVBURURfU1VCU0NSSVBUSU9OX05BTUUiLCJVTkVOQ1JZUFRFRF9TVUJTQ1JJUFRJT04iLCJPYmplY3QiLCJhc3NpZ24iLCJNU0MzNTc1X1NUQVRFX0tFWV9MQVpZIiwiRU5DUllQVEVEX1NVQlNDUklQVElPTiIsIlNsaWRpbmdTeW5jTWFuYWdlciIsImNvbnN0cnVjdG9yIiwiX2RlZmluZVByb3BlcnR5MiIsImRlZmF1bHQiLCJkZWZlciIsImluc3RhbmNlIiwiaW50ZXJuYWxJbnN0YW5jZSIsImNvbmZpZ3VyZSIsImNsaWVudCIsInByb3h5VXJsIiwic2xpZGluZ1N5bmMiLCJTbGlkaW5nU3luYyIsIk1hcCIsImFkZEN1c3RvbVN1YnNjcmlwdGlvbiIsInNldExpc3QiLCJMaXN0U3BhY2VzIiwicmFuZ2VzIiwic29ydCIsInNsb3dfZ2V0X2FsbF9yb29tcyIsIlJvb21Kb2luUnVsZXMiLCJSb29tQXZhdGFyIiwiUm9vbUVuY3J5cHRpb24iLCJmaWx0ZXJzIiwicm9vbV90eXBlcyIsImNvbmZpZ3VyZURlZmVyIiwicmVzb2x2ZSIsImVuc3VyZUxpc3RSZWdpc3RlcmVkIiwibGlzdEtleSIsInVwZGF0ZUFyZ3MiLCJsb2dnZXIiLCJkZWJ1ZyIsInByb21pc2UiLCJsaXN0IiwiZ2V0TGlzdFBhcmFtcyIsInVwZGF0ZWRMaXN0IiwiSlNPTiIsInN0cmluZ2lmeSIsImtleXMiLCJsZW5ndGgiLCJzZXRMaXN0UmFuZ2VzIiwiZXJyIiwic2V0Um9vbVZpc2libGUiLCJyb29tSWQiLCJ2aXNpYmxlIiwic3Vic2NyaXB0aW9ucyIsImdldFJvb21TdWJzY3JpcHRpb25zIiwiYWRkIiwiZGVsZXRlIiwicm9vbSIsImdldFJvb20iLCJzaG91bGRMYXp5TG9hZCIsImlzUm9vbUVuY3J5cHRlZCIsImxvZyIsInVzZUN1c3RvbVN1YnNjcmlwdGlvbiIsInAiLCJtb2RpZnlSb29tU3Vic2NyaXB0aW9ucyIsIndhcm4iLCJzdGFydFNwaWRlcmluZyIsImJhdGNoU2l6ZSIsImdhcEJldHdlZW5SZXF1ZXN0c01zIiwic2xlZXAiLCJzdGFydEluZGV4IiwiaGFzTW9yZSIsImZpcnN0VGltZSIsImVuZEluZGV4IiwiTGlzdFNlYXJjaCIsIm5vdF9yb29tX3R5cGVzIiwibGlzdERhdGEiLCJnZXRMaXN0RGF0YSIsImpvaW5lZENvdW50Iiwic2V0dXAiLCJiYXNlVXJsIiwiU2V0dGluZ3NTdG9yZSIsImdldFZhbHVlIiwid2VsbEtub3duUHJveHlVcmwiLCJnZXRQcm94eUZyb21XZWxsS25vd24iLCJzbGlkaW5nU3luY0VuZHBvaW50IiwiaW5mbyIsImNsaWVudERvbWFpbiIsImdldERvbWFpbiIsIlJhbmdlRXJyb3IiLCJjbGllbnRXZWxsS25vd24iLCJBdXRvRGlzY292ZXJ5IiwiZmluZENsaWVudENvbmZpZyIsInVybCIsImUiLCJ1bmRlZmluZWQiLCJuYXRpdmVTbGlkaW5nU3luY1N1cHBvcnQiLCJzdXBwb3J0IiwiZG9lc1NlcnZlclN1cHBvcnRVbnN0YWJsZUZlYXR1cmUiLCJjaGVja1N1cHBvcnQiLCJTbGlkaW5nU3luY0NvbnRyb2xsZXIiLCJzZXJ2ZXJTdXBwb3J0c1NsaWRpbmdTeW5jIiwicmVzcG9uc2UiLCJmZXRjaCIsIlVSTCIsIm1ldGhvZCIsIk1ldGhvZCIsIkdldCIsInNpZ25hbCIsInRpbWVvdXRTaWduYWwiLCJzdGF0dXMiLCJleHBvcnRzIl0sInNvdXJjZXMiOlsiLi4vc3JjL1NsaWRpbmdTeW5jTWFuYWdlci50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuQ29weXJpZ2h0IDIwMjQgTmV3IFZlY3RvciBMdGQuXG5Db3B5cmlnaHQgMjAyMiBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuXG5TUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcblBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4qL1xuXG4vKlxuICogU2xpZGluZyBTeW5jIEFyY2hpdGVjdHVyZSAtIE1TQyBodHRwczovL2dpdGh1Yi5jb20vbWF0cml4LW9yZy9tYXRyaXgtc3BlYy1wcm9wb3NhbHMvcHVsbC8zNTc1XG4gKlxuICogVGhpcyBpcyBhIGhvbGlzdGljIHN1bW1hcnkgb2YgdGhlIGNoYW5nZXMgbWFkZSB0byBFbGVtZW50LVdlYiAvIFJlYWN0IFNESyAvIEpTIFNESyB0byBlbmFibGUgc2xpZGluZyBzeW5jLlxuICogVGhpcyBzdW1tYXJ5IHdpbGwgaG9wZWZ1bGx5IHNpZ25wb3N0IHdoZXJlIGRldmVsb3BlcnMgbmVlZCB0byBsb29rIGlmIHRoZXkgd2FudCB0byBtYWtlIGNoYW5nZXMgdG8gdGhpcyBjb2RlLlxuICpcbiAqIEF0IHRoZSBsb3dlc3QgbGV2ZWwsIHRoZSBKUyBTREsgY29udGFpbnMgYW4gSFRUUCBBUEkgd3JhcHBlciBmdW5jdGlvbiBpbiBjbGllbnQudHMuIFRoaXMgaXMgdXNlZCBieVxuICogYSBTbGlkaW5nU3luYyBjbGFzcyBpbiBKUyBTREssIHdoaWNoIGNvbnRhaW5zIGNvZGUgdG8gaGFuZGxlIGxpc3Qgb3BlcmF0aW9ucyAoSU5TRVJUL0RFTEVURS9TWU5DL2V0YylcbiAqIGFuZCBjb250YWlucyB0aGUgbWFpbiByZXF1ZXN0IEFQSSBib2RpZXMsIGJ1dCBoYXMgbm8gY29kZSB0byBjb250cm9sIHVwZGF0aW5nIEpTIFNESyBzdHJ1Y3R1cmVzOiBpdCBqdXN0XG4gKiBleHBvc2VzIGFuIEV2ZW50RW1pdHRlciB0byBsaXN0ZW4gZm9yIHVwZGF0ZXMuIFdoZW4gTWF0cml4Q2xpZW50LnN0YXJ0Q2xpZW50IGlzIGNhbGxlZCwgY2FsbGVycyBuZWVkIHRvXG4gKiBwcm92aWRlIGEgU2xpZGluZ1N5bmMgaW5zdGFuY2UgYXMgdGhpcyBjb250YWlucyB0aGUgbWFpbiByZXF1ZXN0IEFQSSBwYXJhbXMgKHRpbWVsaW5lIGxpbWl0LCByZXF1aXJlZCBzdGF0ZSxcbiAqIGhvdyBtYW55IGxpc3RzLCBldGMpLlxuICpcbiAqIFRoZSBTbGlkaW5nU3luY1NkayBJTlRFUk5BTCBjbGFzcyBpbiBKUyBTREsgYXR0YWNoZXMgbGlzdGVuZXJzIHRvIFNsaWRpbmdTeW5jIHRvIHVwZGF0ZSBKUyBTREsgUm9vbSBvYmplY3RzLFxuICogYW5kIGl0IGNvbnZlbmllbnRseSBleHBvc2VzIGFuIGlkZW50aWNhbCBwdWJsaWMgQVBJIHRvIFN5bmNBcGkgKHRvIGFsbG93IGl0IHRvIGJlIGEgZHJvcC1pbiByZXBsYWNlbWVudCkuXG4gKlxuICogQXQgdGhlIGhpZ2hlc3QgbGV2ZWwsIFNsaWRpbmdTeW5jTWFuYWdlciBjb250YWlucyBtZWNoYW5pc21zIHRvIHRlbGwgVUkgbGlzdHMgd2hpY2ggcm9vbXMgdG8gc2hvdyxcbiAqIGFuZCBjb250YWlucyB0aGUgY29yZSByZXF1ZXN0IEFQSSBwYXJhbXMgdXNlZCBpbiBFbGVtZW50LVdlYi4gSXQgZG9lcyB0aGlzIGJ5IGxpc3RlbmluZyBmb3IgZXZlbnRzXG4gKiBlbWl0dGVkIGJ5IHRoZSBTbGlkaW5nU3luYyBjbGFzcyBhbmQgYnkgbW9kaWZ5aW5nIHRoZSByZXF1ZXN0IEFQSSBwYXJhbXMgb24gdGhlIFNsaWRpbmdTeW5jIGNsYXNzLlxuICpcbiAqICAgIChlbnRyeSBwb2ludCkgICAgICAgICAgICAgICAgICAgICAodXBkYXRlcyBKUyBTREspXG4gKiAgU2xpZGluZ1N5bmNNYW5hZ2VyICAgICAgICAgICAgICAgICAgIFNsaWRpbmdTeW5jU2RrXG4gKiAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHxcbiAqICAgICAgICstLS0tLS0tLS0tLS0tLS0tLS0uLS0tLS0tLS0tLS0tLS0tLS0tK1xuICogICAgICAgICBsaXN0ZW5zICAgICAgICAgIHwgICAgICAgICAgbGlzdGVuc1xuICogICAgICAgICAgICAgICAgICAgICBTbGlkaW5nU3luY1xuICogICAgICAgICAgICAgICAgICAgICAoc3luYyBsb29wLFxuICogICAgICAgICAgICAgICAgICAgICAgbGlzdCBvcHMpXG4gKi9cblxuaW1wb3J0IHsgTWF0cml4Q2xpZW50LCBFdmVudFR5cGUsIEF1dG9EaXNjb3ZlcnksIE1ldGhvZCwgdGltZW91dFNpZ25hbCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7XG4gICAgTVNDMzU3NUZpbHRlcixcbiAgICBNU0MzNTc1TGlzdCxcbiAgICBNU0MzNTc1X1NUQVRFX0tFWV9MQVpZLFxuICAgIE1TQzM1NzVfU1RBVEVfS0VZX01FLFxuICAgIE1TQzM1NzVfV0lMRENBUkQsXG4gICAgU2xpZGluZ1N5bmMsXG59IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9zbGlkaW5nLXN5bmNcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcbmltcG9ydCB7IGRlZmVyLCBzbGVlcCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy91dGlsc1wiO1xuXG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi9zZXR0aW5ncy9TZXR0aW5nc1N0b3JlXCI7XG5pbXBvcnQgU2xpZGluZ1N5bmNDb250cm9sbGVyIGZyb20gXCIuL3NldHRpbmdzL2NvbnRyb2xsZXJzL1NsaWRpbmdTeW5jQ29udHJvbGxlclwiO1xuXG4vLyBob3cgbG9uZyB0byBsb25nIHBvbGwgZm9yXG5jb25zdCBTTElESU5HX1NZTkNfVElNRU9VVF9NUyA9IDIwICogMTAwMDtcblxuLy8gdGhlIHRoaW5ncyB0byBmZXRjaCB3aGVuIGEgdXNlciBjbGlja3Mgb24gYSByb29tXG5jb25zdCBERUZBVUxUX1JPT01fU1VCU0NSSVBUSU9OX0lORk8gPSB7XG4gICAgdGltZWxpbmVfbGltaXQ6IDUwLFxuICAgIC8vIG1pc3NpbmcgcmVxdWlyZWRfc3RhdGUgd2hpY2ggd2lsbCBjaGFuZ2UgZGVwZW5kaW5nIG9uIHRoZSBraW5kIG9mIHJvb21cbiAgICBpbmNsdWRlX29sZF9yb29tczoge1xuICAgICAgICB0aW1lbGluZV9saW1pdDogMCxcbiAgICAgICAgcmVxdWlyZWRfc3RhdGU6IFtcbiAgICAgICAgICAgIC8vIHN0YXRlIG5lZWRlZCB0byBoYW5kbGUgc3BhY2UgbmF2aWdhdGlvbiBhbmQgdG9tYnN0b25lIGNoYWluc1xuICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tQ3JlYXRlLCBcIlwiXSxcbiAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbVRvbWJzdG9uZSwgXCJcIl0sXG4gICAgICAgICAgICBbRXZlbnRUeXBlLlNwYWNlQ2hpbGQsIE1TQzM1NzVfV0lMRENBUkRdLFxuICAgICAgICAgICAgW0V2ZW50VHlwZS5TcGFjZVBhcmVudCwgTVNDMzU3NV9XSUxEQ0FSRF0sXG4gICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21NZW1iZXIsIE1TQzM1NzVfU1RBVEVfS0VZX01FXSxcbiAgICAgICAgXSxcbiAgICB9LFxufTtcbi8vIGxhenkgbG9hZCByb29tIG1lbWJlcnMgc28gcm9vbXMgbGlrZSBNYXRyaXggSFEgZG9uJ3QgdGFrZSBmb3JldmVyIHRvIGxvYWRcbmNvbnN0IFVORU5DUllQVEVEX1NVQlNDUklQVElPTl9OQU1FID0gXCJ1bmVuY3J5cHRlZFwiO1xuY29uc3QgVU5FTkNSWVBURURfU1VCU0NSSVBUSU9OID0gT2JqZWN0LmFzc2lnbihcbiAgICB7XG4gICAgICAgIHJlcXVpcmVkX3N0YXRlOiBbXG4gICAgICAgICAgICBbTVNDMzU3NV9XSUxEQ0FSRCwgTVNDMzU3NV9XSUxEQ0FSRF0sIC8vIGFsbCBldmVudHNcbiAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbU1lbWJlciwgTVNDMzU3NV9TVEFURV9LRVlfTUVdLCAvLyBleGNlcHQgZm9yIG0ucm9vbS5tZW1iZXJzLCBnZXQgb3VyIG93biBtZW1iZXJzaGlwXG4gICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21NZW1iZXIsIE1TQzM1NzVfU1RBVEVfS0VZX0xBWlldLCAvLyAuLi5hbmQgbGF6eSBsb2FkIHRoZSByZXN0LlxuICAgICAgICBdLFxuICAgIH0sXG4gICAgREVGQVVMVF9ST09NX1NVQlNDUklQVElPTl9JTkZPLFxuKTtcblxuLy8gd2UgbmVlZCBhbGwgdGhlIHJvb20gbWVtYmVycyBpbiBlbmNyeXB0ZWQgcm9vbXMgYmVjYXVzZSB3ZSBuZWVkIHRvIGtub3cgd2hpY2ggdXNlcnMgdG8gZW5jcnlwdFxuLy8gbWVzc2FnZXMgZm9yLlxuY29uc3QgRU5DUllQVEVEX1NVQlNDUklQVElPTiA9IE9iamVjdC5hc3NpZ24oXG4gICAge1xuICAgICAgICByZXF1aXJlZF9zdGF0ZTogW1xuICAgICAgICAgICAgW01TQzM1NzVfV0lMRENBUkQsIE1TQzM1NzVfV0lMRENBUkRdLCAvLyBhbGwgZXZlbnRzXG4gICAgICAgIF0sXG4gICAgfSxcbiAgICBERUZBVUxUX1JPT01fU1VCU0NSSVBUSU9OX0lORk8sXG4pO1xuXG5leHBvcnQgdHlwZSBQYXJ0aWFsU2xpZGluZ1N5bmNSZXF1ZXN0ID0ge1xuICAgIGZpbHRlcnM/OiBNU0MzNTc1RmlsdGVyO1xuICAgIHNvcnQ/OiBzdHJpbmdbXTtcbiAgICByYW5nZXM/OiBbc3RhcnRJbmRleDogbnVtYmVyLCBlbmRJbmRleDogbnVtYmVyXVtdO1xufTtcblxuLyoqXG4gKiBUaGlzIGNsYXNzIG1hbmFnZXMgdGhlIGVudGlyZXR5IG9mIHNsaWRpbmcgc3luYyBhdCBhIGhpZ2ggVUkvVVggbGV2ZWwuIEl0IGNvbnRyb2xzIHRoZSBwbGFjZW1lbnRcbiAqIG9mIHBsYWNlaG9sZGVycyBpbiBsaXN0cywgY29udHJvbHMgdXBkYXRpbmcgc2xpZGluZyB3aW5kb3cgcmFuZ2VzLCBhbmQgY29udHJvbHMgd2hpY2ggZXZlbnRzXG4gKiBhcmUgcHVsbGVkIGRvd24gd2hlbi4gVGhlIGludGVudGlvbiBiZWhpbmQgdGhpcyBtYW5hZ2VyIGlzIGJlIHRoZSBzaW5nbGUgcGxhY2UgdG8gbG9vayBmb3Igc2xpZGluZ1xuICogc3luYyBvcHRpb25zIGFuZCBjb2RlLlxuICovXG5leHBvcnQgY2xhc3MgU2xpZGluZ1N5bmNNYW5hZ2VyIHtcbiAgICBwdWJsaWMgc3RhdGljIHJlYWRvbmx5IExpc3RTcGFjZXMgPSBcInNwYWNlX2xpc3RcIjtcbiAgICBwdWJsaWMgc3RhdGljIHJlYWRvbmx5IExpc3RTZWFyY2ggPSBcInNlYXJjaF9saXN0XCI7XG4gICAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgaW50ZXJuYWxJbnN0YW5jZSA9IG5ldyBTbGlkaW5nU3luY01hbmFnZXIoKTtcblxuICAgIHB1YmxpYyBzbGlkaW5nU3luYz86IFNsaWRpbmdTeW5jO1xuICAgIHByaXZhdGUgY2xpZW50PzogTWF0cml4Q2xpZW50O1xuXG4gICAgcHJpdmF0ZSBjb25maWd1cmVEZWZlciA9IGRlZmVyPHZvaWQ+KCk7XG5cbiAgICBwdWJsaWMgc3RhdGljIGdldCBpbnN0YW5jZSgpOiBTbGlkaW5nU3luY01hbmFnZXIge1xuICAgICAgICByZXR1cm4gU2xpZGluZ1N5bmNNYW5hZ2VyLmludGVybmFsSW5zdGFuY2U7XG4gICAgfVxuXG4gICAgcHVibGljIGNvbmZpZ3VyZShjbGllbnQ6IE1hdHJpeENsaWVudCwgcHJveHlVcmw6IHN0cmluZyk6IFNsaWRpbmdTeW5jIHtcbiAgICAgICAgdGhpcy5jbGllbnQgPSBjbGllbnQ7XG4gICAgICAgIC8vIGJ5IGRlZmF1bHQgdXNlIHRoZSBlbmNyeXB0ZWQgc3Vic2NyaXB0aW9uIGFzIHRoYXQgZ2V0cyBldmVyeXRoaW5nLCB3aGljaCBpcyBhIHNhZmVyXG4gICAgICAgIC8vIGRlZmF1bHQgdGhhbiBwb3RlbnRpYWxseSBtaXNzaW5nIG1lbWJlciBldmVudHMuXG4gICAgICAgIHRoaXMuc2xpZGluZ1N5bmMgPSBuZXcgU2xpZGluZ1N5bmMoXG4gICAgICAgICAgICBwcm94eVVybCxcbiAgICAgICAgICAgIG5ldyBNYXAoKSxcbiAgICAgICAgICAgIEVOQ1JZUFRFRF9TVUJTQ1JJUFRJT04sXG4gICAgICAgICAgICBjbGllbnQsXG4gICAgICAgICAgICBTTElESU5HX1NZTkNfVElNRU9VVF9NUyxcbiAgICAgICAgKTtcbiAgICAgICAgdGhpcy5zbGlkaW5nU3luYy5hZGRDdXN0b21TdWJzY3JpcHRpb24oVU5FTkNSWVBURURfU1VCU0NSSVBUSU9OX05BTUUsIFVORU5DUllQVEVEX1NVQlNDUklQVElPTik7XG4gICAgICAgIC8vIHNldCB0aGUgc3BhY2UgbGlzdFxuICAgICAgICB0aGlzLnNsaWRpbmdTeW5jLnNldExpc3QoU2xpZGluZ1N5bmNNYW5hZ2VyLkxpc3RTcGFjZXMsIHtcbiAgICAgICAgICAgIHJhbmdlczogW1swLCAyMF1dLFxuICAgICAgICAgICAgc29ydDogW1wiYnlfbmFtZVwiXSxcbiAgICAgICAgICAgIHNsb3dfZ2V0X2FsbF9yb29tczogdHJ1ZSxcbiAgICAgICAgICAgIHRpbWVsaW5lX2xpbWl0OiAwLFxuICAgICAgICAgICAgcmVxdWlyZWRfc3RhdGU6IFtcbiAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21Kb2luUnVsZXMsIFwiXCJdLCAvLyB0aGUgcHVibGljIGljb24gb24gdGhlIHJvb20gbGlzdFxuICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbUF2YXRhciwgXCJcIl0sIC8vIGFueSByb29tIGF2YXRhclxuICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbVRvbWJzdG9uZSwgXCJcIl0sIC8vIGxldHMgSlMgU0RLIGhpZGUgcm9vbXMgd2hpY2ggYXJlIGRlYWRcbiAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21FbmNyeXB0aW9uLCBcIlwiXSwgLy8gbGV0cyByb29tcyBiZSBjb25maWd1cmVkIGZvciBFMkVFIGNvcnJlY3RseVxuICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbUNyZWF0ZSwgXCJcIl0sIC8vIGZvciBpc1NwYWNlUm9vbSBjaGVja3NcbiAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlNwYWNlQ2hpbGQsIE1TQzM1NzVfV0lMRENBUkRdLCAvLyBhbGwgc3BhY2UgY2hpbGRyZW5cbiAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlNwYWNlUGFyZW50LCBNU0MzNTc1X1dJTERDQVJEXSwgLy8gYWxsIHNwYWNlIHBhcmVudHNcbiAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21NZW1iZXIsIE1TQzM1NzVfU1RBVEVfS0VZX01FXSwgLy8gbGV0cyB0aGUgY2xpZW50IGNhbGN1bGF0ZSB0aGF0IHdlIGFyZSBpbiBmYWN0IGluIHRoZSByb29tXG4gICAgICAgICAgICBdLFxuICAgICAgICAgICAgaW5jbHVkZV9vbGRfcm9vbXM6IHtcbiAgICAgICAgICAgICAgICB0aW1lbGluZV9saW1pdDogMCxcbiAgICAgICAgICAgICAgICByZXF1aXJlZF9zdGF0ZTogW1xuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21DcmVhdGUsIFwiXCJdLFxuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21Ub21ic3RvbmUsIFwiXCJdLCAvLyBsZXRzIEpTIFNESyBoaWRlIHJvb21zIHdoaWNoIGFyZSBkZWFkXG4gICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuU3BhY2VDaGlsZCwgTVNDMzU3NV9XSUxEQ0FSRF0sIC8vIGFsbCBzcGFjZSBjaGlsZHJlblxuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlNwYWNlUGFyZW50LCBNU0MzNTc1X1dJTERDQVJEXSwgLy8gYWxsIHNwYWNlIHBhcmVudHNcbiAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tTWVtYmVyLCBNU0MzNTc1X1NUQVRFX0tFWV9NRV0sIC8vIGxldHMgdGhlIGNsaWVudCBjYWxjdWxhdGUgdGhhdCB3ZSBhcmUgaW4gZmFjdCBpbiB0aGUgcm9vbVxuICAgICAgICAgICAgICAgIF0sXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgZmlsdGVyczoge1xuICAgICAgICAgICAgICAgIHJvb21fdHlwZXM6IFtcIm0uc3BhY2VcIl0sXG4gICAgICAgICAgICB9LFxuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5jb25maWd1cmVEZWZlci5yZXNvbHZlKCk7XG4gICAgICAgIHJldHVybiB0aGlzLnNsaWRpbmdTeW5jO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEVuc3VyZSB0aGF0IHRoaXMgbGlzdCBpcyByZWdpc3RlcmVkLlxuICAgICAqIEBwYXJhbSBsaXN0S2V5IFRoZSBsaXN0IGtleSB0byByZWdpc3RlclxuICAgICAqIEBwYXJhbSB1cGRhdGVBcmdzIFRoZSBmaWVsZHMgdG8gdXBkYXRlIG9uIHRoZSBsaXN0LlxuICAgICAqIEByZXR1cm5zIFRoZSBjb21wbGV0ZSBsaXN0IHJlcXVlc3QgcGFyYW1zXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIGVuc3VyZUxpc3RSZWdpc3RlcmVkKGxpc3RLZXk6IHN0cmluZywgdXBkYXRlQXJnczogUGFydGlhbFNsaWRpbmdTeW5jUmVxdWVzdCk6IFByb21pc2U8TVNDMzU3NUxpc3Q+IHtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKFwiZW5zdXJlTGlzdFJlZ2lzdGVyZWQ6OjpcIiwgbGlzdEtleSwgdXBkYXRlQXJncyk7XG4gICAgICAgIGF3YWl0IHRoaXMuY29uZmlndXJlRGVmZXIucHJvbWlzZTtcbiAgICAgICAgbGV0IGxpc3QgPSB0aGlzLnNsaWRpbmdTeW5jIS5nZXRMaXN0UGFyYW1zKGxpc3RLZXkpO1xuICAgICAgICBpZiAoIWxpc3QpIHtcbiAgICAgICAgICAgIGxpc3QgPSB7XG4gICAgICAgICAgICAgICAgcmFuZ2VzOiBbWzAsIDIwXV0sXG4gICAgICAgICAgICAgICAgc29ydDogW1wiYnlfbm90aWZpY2F0aW9uX2xldmVsXCIsIFwiYnlfcmVjZW5jeVwiXSxcbiAgICAgICAgICAgICAgICB0aW1lbGluZV9saW1pdDogMSwgLy8gbW9zdCByZWNlbnQgbWVzc2FnZSBkaXNwbGF5OiB0aG91Z2ggdGhpcyBzZWVtcyB0byBvbmx5IGJlIG5lZWRlZCBmb3IgZmF2b3VyaXRlcz9cbiAgICAgICAgICAgICAgICByZXF1aXJlZF9zdGF0ZTogW1xuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21Kb2luUnVsZXMsIFwiXCJdLCAvLyB0aGUgcHVibGljIGljb24gb24gdGhlIHJvb20gbGlzdFxuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21BdmF0YXIsIFwiXCJdLCAvLyBhbnkgcm9vbSBhdmF0YXJcbiAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tVG9tYnN0b25lLCBcIlwiXSwgLy8gbGV0cyBKUyBTREsgaGlkZSByb29tcyB3aGljaCBhcmUgZGVhZFxuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21FbmNyeXB0aW9uLCBcIlwiXSwgLy8gbGV0cyByb29tcyBiZSBjb25maWd1cmVkIGZvciBFMkVFIGNvcnJlY3RseVxuICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21DcmVhdGUsIFwiXCJdLCAvLyBmb3IgaXNTcGFjZVJvb20gY2hlY2tzXG4gICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbU1lbWJlciwgTVNDMzU3NV9TVEFURV9LRVlfTUVdLCAvLyBsZXRzIHRoZSBjbGllbnQgY2FsY3VsYXRlIHRoYXQgd2UgYXJlIGluIGZhY3QgaW4gdGhlIHJvb21cbiAgICAgICAgICAgICAgICBdLFxuICAgICAgICAgICAgICAgIGluY2x1ZGVfb2xkX3Jvb21zOiB7XG4gICAgICAgICAgICAgICAgICAgIHRpbWVsaW5lX2xpbWl0OiAwLFxuICAgICAgICAgICAgICAgICAgICByZXF1aXJlZF9zdGF0ZTogW1xuICAgICAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tQ3JlYXRlLCBcIlwiXSxcbiAgICAgICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbVRvbWJzdG9uZSwgXCJcIl0sIC8vIGxldHMgSlMgU0RLIGhpZGUgcm9vbXMgd2hpY2ggYXJlIGRlYWRcbiAgICAgICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuU3BhY2VDaGlsZCwgTVNDMzU3NV9XSUxEQ0FSRF0sIC8vIGFsbCBzcGFjZSBjaGlsZHJlblxuICAgICAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5TcGFjZVBhcmVudCwgTVNDMzU3NV9XSUxEQ0FSRF0sIC8vIGFsbCBzcGFjZSBwYXJlbnRzXG4gICAgICAgICAgICAgICAgICAgICAgICBbRXZlbnRUeXBlLlJvb21NZW1iZXIsIE1TQzM1NzVfU1RBVEVfS0VZX01FXSwgLy8gbGV0cyB0aGUgY2xpZW50IGNhbGN1bGF0ZSB0aGF0IHdlIGFyZSBpbiBmYWN0IGluIHRoZSByb29tXG4gICAgICAgICAgICAgICAgICAgIF0sXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBsaXN0ID0gT2JqZWN0LmFzc2lnbihsaXN0LCB1cGRhdGVBcmdzKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IHVwZGF0ZWRMaXN0ID0gT2JqZWN0LmFzc2lnbih7fSwgbGlzdCwgdXBkYXRlQXJncyk7XG4gICAgICAgICAgICAvLyBjYW5ub3QgdXNlIG9iamVjdEhhc0RpZmYgYXMgd2UgbmVlZCB0byBkbyBkZWVwIGRpZmYgY2hlY2tpbmdcbiAgICAgICAgICAgIGlmIChKU09OLnN0cmluZ2lmeShsaXN0KSA9PT0gSlNPTi5zdHJpbmdpZnkodXBkYXRlZExpc3QpKSB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyLmRlYnVnKFwibGlzdCBtYXRjaGVzLCBub3Qgc2VuZGluZywgdXBkYXRlID0+IFwiLCB1cGRhdGVBcmdzKTtcbiAgICAgICAgICAgICAgICByZXR1cm4gbGlzdDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxpc3QgPSB1cGRhdGVkTGlzdDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICAvLyBpZiB3ZSBvbmx5IGhhdmUgcmFuZ2UgY2hhbmdlcyB0aGVuIGNhbGwgYSBkaWZmZXJlbnQgZnVuY3Rpb24gc28gd2UgZG9uJ3QgbnVrZSB0aGUgbGlzdCBmcm9tIGJlZm9yZVxuICAgICAgICAgICAgaWYgKHVwZGF0ZUFyZ3MucmFuZ2VzICYmIE9iamVjdC5rZXlzKHVwZGF0ZUFyZ3MpLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMuc2xpZGluZ1N5bmMhLnNldExpc3RSYW5nZXMobGlzdEtleSwgdXBkYXRlQXJncy5yYW5nZXMpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBhd2FpdCB0aGlzLnNsaWRpbmdTeW5jIS5zZXRMaXN0KGxpc3RLZXksIGxpc3QpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIGxvZ2dlci5kZWJ1ZyhcImVuc3VyZUxpc3RSZWdpc3RlcmVkOiB1cGRhdGUgZmFpbGVkIHR4bl9pZD1cIiwgZXJyKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5zbGlkaW5nU3luYyEuZ2V0TGlzdFBhcmFtcyhsaXN0S2V5KSE7XG4gICAgfVxuXG4gICAgcHVibGljIGFzeW5jIHNldFJvb21WaXNpYmxlKHJvb21JZDogc3RyaW5nLCB2aXNpYmxlOiBib29sZWFuKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICAgICAgYXdhaXQgdGhpcy5jb25maWd1cmVEZWZlci5wcm9taXNlO1xuICAgICAgICBjb25zdCBzdWJzY3JpcHRpb25zID0gdGhpcy5zbGlkaW5nU3luYyEuZ2V0Um9vbVN1YnNjcmlwdGlvbnMoKTtcbiAgICAgICAgaWYgKHZpc2libGUpIHtcbiAgICAgICAgICAgIHN1YnNjcmlwdGlvbnMuYWRkKHJvb21JZCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzdWJzY3JpcHRpb25zLmRlbGV0ZShyb29tSWQpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHJvb20gPSB0aGlzLmNsaWVudD8uZ2V0Um9vbShyb29tSWQpO1xuICAgICAgICBsZXQgc2hvdWxkTGF6eUxvYWQgPSAhdGhpcy5jbGllbnQ/LmlzUm9vbUVuY3J5cHRlZChyb29tSWQpO1xuICAgICAgICBpZiAoIXJvb20pIHtcbiAgICAgICAgICAgIC8vIGRlZmF1bHQgdG8gc2FmZXR5OiByZXF1ZXN0IGFsbCBzdGF0ZSBpZiB3ZSBjYW4ndCB3b3JrIGl0IG91dC4gVGhpcyBjYW4gaGFwcGVuIGlmIHlvdVxuICAgICAgICAgICAgLy8gcmVmcmVzaCB0aGUgYXBwIHdoaWxzdCB2aWV3aW5nIGEgcm9vbTogd2UgY2FsbCBzZXRSb29tVmlzaWJsZSBiZWZvcmUgd2Uga25vdyBhbnl0aGluZ1xuICAgICAgICAgICAgLy8gYWJvdXQgdGhlIHJvb20uXG4gICAgICAgICAgICBzaG91bGRMYXp5TG9hZCA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgICAgIGxvZ2dlci5sb2coXCJTbGlkaW5nU3luYyBzZXRSb29tVmlzaWJsZTpcIiwgcm9vbUlkLCB2aXNpYmxlLCBcInNob3VsZExhenlMb2FkOlwiLCBzaG91bGRMYXp5TG9hZCk7XG4gICAgICAgIGlmIChzaG91bGRMYXp5TG9hZCkge1xuICAgICAgICAgICAgLy8gbGF6eSBsb2FkIHRoaXMgcm9vbVxuICAgICAgICAgICAgdGhpcy5zbGlkaW5nU3luYyEudXNlQ3VzdG9tU3Vic2NyaXB0aW9uKHJvb21JZCwgVU5FTkNSWVBURURfU1VCU0NSSVBUSU9OX05BTUUpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHAgPSB0aGlzLnNsaWRpbmdTeW5jIS5tb2RpZnlSb29tU3Vic2NyaXB0aW9ucyhzdWJzY3JpcHRpb25zKTtcbiAgICAgICAgaWYgKHJvb20pIHtcbiAgICAgICAgICAgIHJldHVybiByb29tSWQ7IC8vIHdlIGhhdmUgZGF0YSBhbHJlYWR5IGZvciB0aGlzIHJvb20sIHNob3cgaW1tZWRpYXRlbHkgZS5nIGl0J3MgaW4gYSBsaXN0XG4gICAgICAgIH1cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIC8vIHdhaXQgdW50aWwgdGhlIG5leHQgc3luYyBiZWZvcmUgcmV0dXJuaW5nIGFzIFJvb21WaWV3IG1heSBuZWVkIHRvIGtub3cgdGhlIGN1cnJlbnQgc3RhdGVcbiAgICAgICAgICAgIGF3YWl0IHA7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgbG9nZ2VyLndhcm4oXCJTbGlkaW5nU3luYyBzZXRSb29tVmlzaWJsZTpcIiwgcm9vbUlkLCB2aXNpYmxlLCBcImZhaWxlZCB0byBjb25maXJtIHRyYW5zYWN0aW9uXCIpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiByb29tSWQ7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0cmlldmUgYWxsIHJvb21zIG9uIHRoZSB1c2VyJ3MgYWNjb3VudC4gVXNlZCBmb3IgcHJlLXBvcHVsYXRpbmcgdGhlIGxvY2FsIHNlYXJjaCBjYWNoZS5cbiAgICAgKiBSZXRyaWV2YWwgaXMgZ3JhZHVhbCBvdmVyIHRpbWUuXG4gICAgICogQHBhcmFtIGJhdGNoU2l6ZSBUaGUgbnVtYmVyIG9mIHJvb21zIHRvIHJldHVybiBpbiBlYWNoIHJlcXVlc3QuXG4gICAgICogQHBhcmFtIGdhcEJldHdlZW5SZXF1ZXN0c01zIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHdhaXQgYmV0d2VlbiByZXF1ZXN0cy5cbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgc3RhcnRTcGlkZXJpbmcoYmF0Y2hTaXplOiBudW1iZXIsIGdhcEJldHdlZW5SZXF1ZXN0c01zOiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgYXdhaXQgc2xlZXAoZ2FwQmV0d2VlblJlcXVlc3RzTXMpOyAvLyB3YWl0IGEgYml0IGFzIHRoaXMgaXMgY2FsbGVkIG9uIGZpcnN0IHJlbmRlciBzbyBsZXQncyBsZXQgdGhpbmdzIGxvYWRcbiAgICAgICAgbGV0IHN0YXJ0SW5kZXggPSBiYXRjaFNpemU7XG4gICAgICAgIGxldCBoYXNNb3JlID0gdHJ1ZTtcbiAgICAgICAgbGV0IGZpcnN0VGltZSA9IHRydWU7XG4gICAgICAgIHdoaWxlIChoYXNNb3JlKSB7XG4gICAgICAgICAgICBjb25zdCBlbmRJbmRleCA9IHN0YXJ0SW5kZXggKyBiYXRjaFNpemUgLSAxO1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBjb25zdCByYW5nZXMgPSBbXG4gICAgICAgICAgICAgICAgICAgIFswLCBiYXRjaFNpemUgLSAxXSxcbiAgICAgICAgICAgICAgICAgICAgW3N0YXJ0SW5kZXgsIGVuZEluZGV4XSxcbiAgICAgICAgICAgICAgICBdO1xuICAgICAgICAgICAgICAgIGlmIChmaXJzdFRpbWUpIHtcbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgdGhpcy5zbGlkaW5nU3luYyEuc2V0TGlzdChTbGlkaW5nU3luY01hbmFnZXIuTGlzdFNlYXJjaCwge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gZS5nIFswLDE5XSBbMjAsMzldIHRoZW4gWzAsMTldIFs0MCw1OV0uIFdlIGtlZXAgWzAsMjBdIGNvbnN0YW50bHkgdG8gZW5zdXJlXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBhbnkgY2hhbmdlcyB0byB0aGUgbGlzdCB3aGlsc3Qgc3BpZGVyaW5nIGFyZSBjYXVnaHQuXG4gICAgICAgICAgICAgICAgICAgICAgICByYW5nZXM6IHJhbmdlcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNvcnQ6IFtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBcImJ5X3JlY2VuY3lcIiwgLy8gdGhpcyBsaXN0IGlzbid0IHNob3duIG9uIHRoZSBVSSBzbyBqdXN0IHNvcnRpbmcgYnkgdGltZXN0YW1wIGlzIGVub3VnaFxuICAgICAgICAgICAgICAgICAgICAgICAgXSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVsaW5lX2xpbWl0OiAwLCAvLyB3ZSBvbmx5IGNhcmUgYWJvdXQgdGhlIHJvb20gZGV0YWlscywgbm90IG1lc3NhZ2VzIGluIHRoZSByb29tXG4gICAgICAgICAgICAgICAgICAgICAgICByZXF1aXJlZF9zdGF0ZTogW1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbUpvaW5SdWxlcywgXCJcIl0sIC8vIHRoZSBwdWJsaWMgaWNvbiBvbiB0aGUgcm9vbSBsaXN0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tQXZhdGFyLCBcIlwiXSwgLy8gYW55IHJvb20gYXZhdGFyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tVG9tYnN0b25lLCBcIlwiXSwgLy8gbGV0cyBKUyBTREsgaGlkZSByb29tcyB3aGljaCBhcmUgZGVhZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbUVuY3J5cHRpb24sIFwiXCJdLCAvLyBsZXRzIHJvb21zIGJlIGNvbmZpZ3VyZWQgZm9yIEUyRUUgY29ycmVjdGx5XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW0V2ZW50VHlwZS5Sb29tQ3JlYXRlLCBcIlwiXSwgLy8gZm9yIGlzU3BhY2VSb29tIGNoZWNrc1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFtFdmVudFR5cGUuUm9vbU1lbWJlciwgTVNDMzU3NV9TVEFURV9LRVlfTUVdLCAvLyBsZXRzIHRoZSBjbGllbnQgY2FsY3VsYXRlIHRoYXQgd2UgYXJlIGluIGZhY3QgaW4gdGhlIHJvb21cbiAgICAgICAgICAgICAgICAgICAgICAgIF0sXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyB3ZSBkb24ndCBpbmNsdWRlX29sZF9yb29tcyBoZXJlIGluIGFuIGVmZm9ydCB0byByZWR1Y2UgdGhlIGltcGFjdCBvZiBzcGlkZXJpbmcgYWxsIHJvb21zXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBvbiB0aGUgdXNlcidzIGFjY291bnQuIFRoaXMgbWVhbnMgc29tZSBkYXRhIGluIHRoZSBzZWFyY2ggZGlhbG9nIHJlc3VsdHMgbWF5IGJlIGluYWNjdXJhdGVcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGUuZyBtZW1iZXJzaGlwIG9mIHNwYWNlLCBidXQgdGhpcyB3aWxsIGJlIGNvcnJlY3RlZCB3aGVuIHRoZSB1c2VyIGNsaWNrcyBvbiB0aGUgcm9vbVxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gYXMgdGhlIGRpcmVjdCByb29tIHN1YnNjcmlwdGlvbiBkb2VzIGluY2x1ZGUgb2xkIHJvb20gaXRlcmF0aW9ucy5cbiAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcnM6IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyB3ZSBnZXQgc3BhY2VzIHZpYSBhIGRpZmZlcmVudCBsaXN0LCBzbyBmaWx0ZXIgdGhlbSBvdXRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3Rfcm9vbV90eXBlczogW1wibS5zcGFjZVwiXSxcbiAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMuc2xpZGluZ1N5bmMhLnNldExpc3RSYW5nZXMoU2xpZGluZ1N5bmNNYW5hZ2VyLkxpc3RTZWFyY2gsIHJhbmdlcyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgICAgLy8gZG8gbm90aGluZywgYXMgd2UgcmVqZWN0IG9ubHkgd2hlbiB3ZSBnZXQgaW50ZXJydXB0ZWQgYnV0IHRoYXQncyBmaW5lIGFzIHRoZSBuZXh0XG4gICAgICAgICAgICAgICAgLy8gcmVxdWVzdCB3aWxsIGluY2x1ZGUgb3VyIGRhdGFcbiAgICAgICAgICAgIH0gZmluYWxseSB7XG4gICAgICAgICAgICAgICAgLy8gZ3JhZHVhbGx5IHJlcXVlc3QgbW9yZSBvdmVyIHRpbWUsIGV2ZW4gb24gZXJyb3JzLlxuICAgICAgICAgICAgICAgIGF3YWl0IHNsZWVwKGdhcEJldHdlZW5SZXF1ZXN0c01zKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGxpc3REYXRhID0gdGhpcy5zbGlkaW5nU3luYyEuZ2V0TGlzdERhdGEoU2xpZGluZ1N5bmNNYW5hZ2VyLkxpc3RTZWFyY2gpITtcbiAgICAgICAgICAgIGhhc01vcmUgPSBlbmRJbmRleCArIDEgPCBsaXN0RGF0YS5qb2luZWRDb3VudDtcbiAgICAgICAgICAgIHN0YXJ0SW5kZXggKz0gYmF0Y2hTaXplO1xuICAgICAgICAgICAgZmlyc3RUaW1lID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdXAgdGhlIFNsaWRpbmcgU3luYyBpbnN0YW5jZTsgY29uZmlndXJlcyB0aGUgZW5kIHBvaW50IGFuZCBzdGFydHMgc3BpZGVyaW5nLlxuICAgICAqIFRoZSBzbGlkaW5nIHN5bmMgZW5kcG9pbnQgaXMgZGVyaXZlZCB0aGUgZm9sbG93aW5nIHdheTpcbiAgICAgKiAgIDEuIFRoZSB1c2VyLWRlZmluZWQgc2xpZGluZyBzeW5jIHByb3h5IFVSTCAobGVnYWN5LCBmb3IgYmFja3dhcmRzIGNvbXBhdGliaWxpdHkpXG4gICAgICogICAyLiBUaGUgY2xpZW50IGB3ZWxsLWtub3duYCBzbGlkaW5nIHN5bmMgcHJveHkgVVJMIFtkZWNsYXJlZCBhdCB0aGUgdW5zdGFibGUgcHJlZml4XShodHRwczovL2dpdGh1Yi5jb20vbWF0cml4LW9yZy9tYXRyaXgtc3BlYy1wcm9wb3NhbHMvYmxvYi9rZWdhbi9zeW5jLXYzL3Byb3Bvc2Fscy8zNTc1LXN5bmMubWQjdW5zdGFibGUtcHJlZml4KVxuICAgICAqICAgMy4gVGhlIGhvbWVzZXJ2ZXIgYmFzZSB1cmwgKGZvciBuYXRpdmUgc2VydmVyIHN1cHBvcnQpXG4gICAgICogQHBhcmFtIGNsaWVudCBUaGUgTWF0cml4Q2xpZW50IHRvIHVzZVxuICAgICAqIEByZXR1cm5zIEEgd29ya2luZyBTbGlkaW5nIFN5bmMgb3IgdW5kZWZpbmVkXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIHNldHVwKGNsaWVudDogTWF0cml4Q2xpZW50KTogUHJvbWlzZTxTbGlkaW5nU3luYyB8IHVuZGVmaW5lZD4ge1xuICAgICAgICBjb25zdCBiYXNlVXJsID0gY2xpZW50LmJhc2VVcmw7XG4gICAgICAgIGNvbnN0IHByb3h5VXJsID0gU2V0dGluZ3NTdG9yZS5nZXRWYWx1ZShcImZlYXR1cmVfc2xpZGluZ19zeW5jX3Byb3h5X3VybFwiKTtcbiAgICAgICAgY29uc3Qgd2VsbEtub3duUHJveHlVcmwgPSBhd2FpdCB0aGlzLmdldFByb3h5RnJvbVdlbGxLbm93bihjbGllbnQpO1xuXG4gICAgICAgIGNvbnN0IHNsaWRpbmdTeW5jRW5kcG9pbnQgPSBwcm94eVVybCB8fCB3ZWxsS25vd25Qcm94eVVybCB8fCBiYXNlVXJsO1xuXG4gICAgICAgIHRoaXMuY29uZmlndXJlKGNsaWVudCwgc2xpZGluZ1N5bmNFbmRwb2ludCk7XG4gICAgICAgIGxvZ2dlci5pbmZvKFwiU2xpZGluZyBzeW5jIGFjdGl2YXRlZCBhdFwiLCBzbGlkaW5nU3luY0VuZHBvaW50KTtcbiAgICAgICAgdGhpcy5zdGFydFNwaWRlcmluZygxMDAsIDUwKTsgLy8gMTAwIHJvb21zIGF0IGEgdGltZSwgNTBtcyBhcGFydFxuXG4gICAgICAgIHJldHVybiB0aGlzLnNsaWRpbmdTeW5jO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEdldCB0aGUgc2xpZGluZyBzeW5jIHByb3h5IFVSTCBmcm9tIHRoZSBjbGllbnQgd2VsbCBrbm93blxuICAgICAqIEBwYXJhbSBjbGllbnQgVGhlIE1hdHJpeENsaWVudCB0byB1c2VcbiAgICAgKiBAcmV0dXJuIFRoZSBwcm94eSB1cmxcbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgZ2V0UHJveHlGcm9tV2VsbEtub3duKGNsaWVudDogTWF0cml4Q2xpZW50KTogUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+IHtcbiAgICAgICAgbGV0IHByb3h5VXJsOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IGNsaWVudERvbWFpbiA9IGF3YWl0IGNsaWVudC5nZXREb21haW4oKTtcbiAgICAgICAgICAgIGlmIChjbGllbnREb21haW4gPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgUmFuZ2VFcnJvcihcIkhvbWVzZXJ2ZXIgZG9tYWluIGlzIG51bGxcIik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBjbGllbnRXZWxsS25vd24gPSBhd2FpdCBBdXRvRGlzY292ZXJ5LmZpbmRDbGllbnRDb25maWcoY2xpZW50RG9tYWluKTtcbiAgICAgICAgICAgIHByb3h5VXJsID0gY2xpZW50V2VsbEtub3duPy5bXCJvcmcubWF0cml4Lm1zYzM1NzUucHJveHlcIl0/LnVybDtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgLy8gRWl0aGVyIGNsaWVudC5nZXREb21haW4oKSBpcyBudWxsIHNvIHdlJ3ZlIHNob3J0ZWQgb3V0LCBvciBpcyBpbnZhbGlkIHNvIGBBdXRvRGlzY292ZXJ5LmZpbmRDbGllbnRDb25maWdgIGhhcyB0aHJvd25cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChwcm94eVVybCAhPSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGxvZ2dlci5sb2coXCJnZXRQcm94eUZyb21XZWxsS25vd246IGNsaWVudCB3ZWxsLWtub3duIGRlY2xhcmVzIHNsaWRpbmcgc3luYyBwcm94eSBhdFwiLCBwcm94eVVybCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHByb3h5VXJsO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIHRoZSBzZXJ2ZXIgXCJuYXRpdmVseVwiIHN1cHBvcnRzIHNsaWRpbmcgc3luYyAod2l0aCBhbiB1bnN0YWJsZSBlbmRwb2ludCkuXG4gICAgICogQHBhcmFtIGNsaWVudCBUaGUgTWF0cml4Q2xpZW50IHRvIHVzZVxuICAgICAqIEByZXR1cm4gV2hldGhlciB0aGUgXCJuYXRpdmVcIiAodW5zdGFibGUpIGVuZHBvaW50IGlzIHN1cHBvcnRlZFxuICAgICAqL1xuICAgIHB1YmxpYyBhc3luYyBuYXRpdmVTbGlkaW5nU3luY1N1cHBvcnQoY2xpZW50OiBNYXRyaXhDbGllbnQpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgICAgICAgLy8gUGVyIGh0dHBzOi8vZ2l0aHViLmNvbS9tYXRyaXgtb3JnL21hdHJpeC1zcGVjLXByb3Bvc2Fscy9wdWxsLzM1NzUvZmlsZXMjcjE1ODk1NDI1NjFcbiAgICAgICAgLy8gYGNsaWVudGAgY2FuIGJlIHVuZGVmaW5lZC9udWxsIGluIHRlc3RzIGZvciBzb21lIHJlYXNvbi5cbiAgICAgICAgY29uc3Qgc3VwcG9ydCA9IGF3YWl0IGNsaWVudD8uZG9lc1NlcnZlclN1cHBvcnRVbnN0YWJsZUZlYXR1cmUoXCJvcmcubWF0cml4Lm1zYzM1NzVcIik7XG4gICAgICAgIGlmIChzdXBwb3J0KSB7XG4gICAgICAgICAgICBsb2dnZXIubG9nKFwibmF0aXZlU2xpZGluZ1N5bmNTdXBwb3J0OiBzbGlkaW5nIHN5bmMgYWR2ZXJ0aXNlZCBhcyB1bnN0YWJsZVwiKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc3VwcG9ydDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDaGVjayB3aGV0aGVyIG91ciBob21lc2VydmVyIGhhcyBzbGlkaW5nIHN5bmMgc3VwcG9ydCwgdGhhdCB0aGUgZW5kcG9pbnQgaXMgdXAsIGFuZFxuICAgICAqIGlzIGEgc2xpZGluZyBzeW5jIGVuZHBvaW50LlxuICAgICAqXG4gICAgICogU2V0cyBzdGF0aWMgbWVtYmVyIGBTbGlkaW5nU3luY0NvbnRyb2xsZXIuc2VydmVyU3VwcG9ydHNTbGlkaW5nU3luY2BcbiAgICAgKiBAcGFyYW0gY2xpZW50IFRoZSBNYXRyaXhDbGllbnQgdG8gdXNlXG4gICAgICovXG4gICAgcHVibGljIGFzeW5jIGNoZWNrU3VwcG9ydChjbGllbnQ6IE1hdHJpeENsaWVudCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBpZiAoYXdhaXQgdGhpcy5uYXRpdmVTbGlkaW5nU3luY1N1cHBvcnQoY2xpZW50KSkge1xuICAgICAgICAgICAgU2xpZGluZ1N5bmNDb250cm9sbGVyLnNlcnZlclN1cHBvcnRzU2xpZGluZ1N5bmMgPSB0cnVlO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcHJveHlVcmwgPSBhd2FpdCB0aGlzLmdldFByb3h5RnJvbVdlbGxLbm93bihjbGllbnQpO1xuICAgICAgICBpZiAocHJveHlVcmwgIT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKG5ldyBVUkwoXCIvY2xpZW50L3NlcnZlci5qc29uXCIsIHByb3h5VXJsKSwge1xuICAgICAgICAgICAgICAgIG1ldGhvZDogTWV0aG9kLkdldCxcbiAgICAgICAgICAgICAgICBzaWduYWw6IHRpbWVvdXRTaWduYWwoMTAgKiAxMDAwKSwgLy8gMTBzXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDIwMCkge1xuICAgICAgICAgICAgICAgIGxvZ2dlci5sb2coXCJjaGVja1N1cHBvcnQ6IHdlbGwta25vd24gc2xpZGluZyBzeW5jIHByb3h5IGlzIHVwIGF0XCIsIHByb3h5VXJsKTtcbiAgICAgICAgICAgICAgICBTbGlkaW5nU3luY0NvbnRyb2xsZXIuc2VydmVyU3VwcG9ydHNTbGlkaW5nU3luYyA9IHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG59XG4iXSwibWFwcGluZ3MiOiI7Ozs7Ozs7O0FBc0NBLElBQUFBLE9BQUEsR0FBQUMsT0FBQTtBQUNBLElBQUFDLFlBQUEsR0FBQUQsT0FBQTtBQVFBLElBQUFFLE9BQUEsR0FBQUYsT0FBQTtBQUNBLElBQUFHLE1BQUEsR0FBQUgsT0FBQTtBQUVBLElBQUFJLGNBQUEsR0FBQUMsc0JBQUEsQ0FBQUwsT0FBQTtBQUNBLElBQUFNLHNCQUFBLEdBQUFELHNCQUFBLENBQUFMLE9BQUE7QUFBaUYsSUFBQU8sbUJBQUE7QUFuRGpGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQWlCQTtBQUNBLE1BQU1DLHVCQUF1QixHQUFHLEVBQUUsR0FBRyxJQUFJOztBQUV6QztBQUNBLE1BQU1DLDhCQUE4QixHQUFHO0VBQ25DQyxjQUFjLEVBQUUsRUFBRTtFQUNsQjtFQUNBQyxpQkFBaUIsRUFBRTtJQUNmRCxjQUFjLEVBQUUsQ0FBQztJQUNqQkUsY0FBYyxFQUFFO0lBQ1o7SUFDQSxDQUFDQyxpQkFBUyxDQUFDQyxVQUFVLEVBQUUsRUFBRSxDQUFDLEVBQzFCLENBQUNELGlCQUFTLENBQUNFLGFBQWEsRUFBRSxFQUFFLENBQUMsRUFDN0IsQ0FBQ0YsaUJBQVMsQ0FBQ0csVUFBVSxFQUFFQyw2QkFBZ0IsQ0FBQyxFQUN4QyxDQUFDSixpQkFBUyxDQUFDSyxXQUFXLEVBQUVELDZCQUFnQixDQUFDLEVBQ3pDLENBQUNKLGlCQUFTLENBQUNNLFVBQVUsRUFBRUMsaUNBQW9CLENBQUM7RUFFcEQ7QUFDSixDQUFDO0FBQ0Q7QUFDQSxNQUFNQyw2QkFBNkIsR0FBRyxhQUFhO0FBQ25ELE1BQU1DLHdCQUF3QixHQUFHQyxNQUFNLENBQUNDLE1BQU0sQ0FDMUM7RUFDSVosY0FBYyxFQUFFLENBQ1osQ0FBQ0ssNkJBQWdCLEVBQUVBLDZCQUFnQixDQUFDO0VBQUU7RUFDdEMsQ0FBQ0osaUJBQVMsQ0FBQ00sVUFBVSxFQUFFQyxpQ0FBb0IsQ0FBQztFQUFFO0VBQzlDLENBQUNQLGlCQUFTLENBQUNNLFVBQVUsRUFBRU0sbUNBQXNCLENBQUMsQ0FBRTtFQUFBO0FBRXhELENBQUMsRUFDRGhCLDhCQUNKLENBQUM7O0FBRUQ7QUFDQTtBQUNBLE1BQU1pQixzQkFBc0IsR0FBR0gsTUFBTSxDQUFDQyxNQUFNLENBQ3hDO0VBQ0laLGNBQWMsRUFBRSxDQUNaLENBQUNLLDZCQUFnQixFQUFFQSw2QkFBZ0IsQ0FBQyxDQUFFO0VBQUE7QUFFOUMsQ0FBQyxFQUNEUiw4QkFDSixDQUFDO0FBUUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sTUFBTWtCLGtCQUFrQixDQUFDO0VBQUFDLFlBQUE7SUFBQSxJQUFBQyxnQkFBQSxDQUFBQyxPQUFBO0lBQUEsSUFBQUQsZ0JBQUEsQ0FBQUMsT0FBQTtJQUFBLElBQUFELGdCQUFBLENBQUFDLE9BQUEsMEJBUUgsSUFBQUMsWUFBSyxFQUFPLENBQUM7RUFBQTtFQUV0QyxXQUFrQkMsUUFBUUEsQ0FBQSxFQUF1QjtJQUM3QyxPQUFPTCxrQkFBa0IsQ0FBQ00sZ0JBQWdCO0VBQzlDO0VBRU9DLFNBQVNBLENBQUNDLE1BQW9CLEVBQUVDLFFBQWdCLEVBQWU7SUFDbEUsSUFBSSxDQUFDRCxNQUFNLEdBQUdBLE1BQU07SUFDcEI7SUFDQTtJQUNBLElBQUksQ0FBQ0UsV0FBVyxHQUFHLElBQUlDLHdCQUFXLENBQzlCRixRQUFRLEVBQ1IsSUFBSUcsR0FBRyxDQUFDLENBQUMsRUFDVGIsc0JBQXNCLEVBQ3RCUyxNQUFNLEVBQ04zQix1QkFDSixDQUFDO0lBQ0QsSUFBSSxDQUFDNkIsV0FBVyxDQUFDRyxxQkFBcUIsQ0FBQ25CLDZCQUE2QixFQUFFQyx3QkFBd0IsQ0FBQztJQUMvRjtJQUNBLElBQUksQ0FBQ2UsV0FBVyxDQUFDSSxPQUFPLENBQUNkLGtCQUFrQixDQUFDZSxVQUFVLEVBQUU7TUFDcERDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO01BQ2pCQyxJQUFJLEVBQUUsQ0FBQyxTQUFTLENBQUM7TUFDakJDLGtCQUFrQixFQUFFLElBQUk7TUFDeEJuQyxjQUFjLEVBQUUsQ0FBQztNQUNqQkUsY0FBYyxFQUFFLENBQ1osQ0FBQ0MsaUJBQVMsQ0FBQ2lDLGFBQWEsRUFBRSxFQUFFLENBQUM7TUFBRTtNQUMvQixDQUFDakMsaUJBQVMsQ0FBQ2tDLFVBQVUsRUFBRSxFQUFFLENBQUM7TUFBRTtNQUM1QixDQUFDbEMsaUJBQVMsQ0FBQ0UsYUFBYSxFQUFFLEVBQUUsQ0FBQztNQUFFO0