UNPKG

matrix-react-sdk

Version:
392 lines (381 loc) 59.4 kB
"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