UNPKG

matrix-react-sdk

Version:
486 lines (470 loc) 94.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.StopGapWidgetDriver = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _matrixWidgetApi = require("matrix-widget-api"); var _matrix = require("matrix-js-sdk/src/matrix"); var _logger = require("matrix-js-sdk/src/logger"); var _WidgetLifecycle = require("@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle"); var _SdkConfig = _interopRequireWildcard(require("../../SdkConfig")); var _iterables = require("../../utils/iterables"); var _MatrixClientPeg = require("../../MatrixClientPeg"); var _Modal = _interopRequireDefault(require("../../Modal")); var _WidgetOpenIDPermissionsDialog = _interopRequireDefault(require("../../components/views/dialogs/WidgetOpenIDPermissionsDialog")); var _WidgetCapabilitiesPromptDialog = _interopRequireDefault(require("../../components/views/dialogs/WidgetCapabilitiesPromptDialog")); var _WidgetPermissions = require("../../customisations/WidgetPermissions"); var _WidgetPermissionStore = require("./WidgetPermissionStore"); var _WidgetType = require("../../widgets/WidgetType"); var _effects = require("../../effects"); var _utils = require("../../effects/utils"); var _dispatcher = _interopRequireDefault(require("../../dispatcher/dispatcher")); var _ElementWidgetCapabilities = require("./ElementWidgetCapabilities"); var _navigator = require("../../utils/permalinks/navigator"); var _SDKContext = require("../../contexts/SDKContext"); var _ModuleRunner = require("../../modules/ModuleRunner"); var _SettingsStore = _interopRequireDefault(require("../../settings/SettingsStore")); var _Media = require("../../customisations/Media"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* * Copyright 2024 New Vector Ltd. * Copyright 2020-2023 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. */ // TODO: Purge this from the universe function getRememberedCapabilitiesForWidget(widget) { return JSON.parse(localStorage.getItem(`widget_${widget.id}_approved_caps`) || "[]"); } function setRememberedCapabilitiesForWidget(widget, caps) { localStorage.setItem(`widget_${widget.id}_approved_caps`, JSON.stringify(caps)); } const normalizeTurnServer = ({ urls, username, credential }) => ({ uris: urls, username, password: credential }); class StopGapWidgetDriver extends _matrixWidgetApi.WidgetDriver { // TODO: Refactor widgetKind into the Widget class constructor(allowedCapabilities, forWidget, forWidgetKind, virtual, inRoomId) { super(); // Always allow screenshots to be taken because it's a client-induced flow. The widget can't // spew screenshots at us and can't request screenshots of us, so it's up to us to provide the // button if the widget says it supports screenshots. (0, _defineProperty2.default)(this, "allowedCapabilities", void 0); this.forWidget = forWidget; this.forWidgetKind = forWidgetKind; this.inRoomId = inRoomId; this.allowedCapabilities = new Set([...allowedCapabilities, _matrixWidgetApi.MatrixCapabilities.Screenshots, _ElementWidgetCapabilities.ElementWidgetCapabilities.RequiresClient]); // Grant the permissions that are specific to given widget types if (_WidgetType.WidgetType.JITSI.matches(this.forWidget.type) && forWidgetKind === _matrixWidgetApi.WidgetKind.Room) { this.allowedCapabilities.add(_matrixWidgetApi.MatrixCapabilities.AlwaysOnScreen); } else if (_WidgetType.WidgetType.STICKERPICKER.matches(this.forWidget.type) && forWidgetKind === _matrixWidgetApi.WidgetKind.Account) { const stickerSendingCap = _matrixWidgetApi.WidgetEventCapability.forRoomEvent(_matrixWidgetApi.EventDirection.Send, _matrix.EventType.Sticker).raw; this.allowedCapabilities.add(_matrixWidgetApi.MatrixCapabilities.StickerSending); // legacy as far as MSC2762 is concerned this.allowedCapabilities.add(stickerSendingCap); // Auto-approve the legacy visibility capability. We send it regardless of capability. // Widgets don't technically need to request this capability, but Scalar still does. this.allowedCapabilities.add("visibility"); } else if (virtual && new URL(_SdkConfig.default.get("element_call").url ?? _SdkConfig.DEFAULTS.element_call.url).origin === this.forWidget.origin) { // This is a trusted Element Call widget that we control this.allowedCapabilities.add(_matrixWidgetApi.MatrixCapabilities.AlwaysOnScreen); this.allowedCapabilities.add(_matrixWidgetApi.MatrixCapabilities.MSC3846TurnServers); this.allowedCapabilities.add(`org.matrix.msc2762.timeline:${inRoomId}`); this.allowedCapabilities.add(_matrixWidgetApi.MatrixCapabilities.MSC4157SendDelayedEvent); this.allowedCapabilities.add(_matrixWidgetApi.MatrixCapabilities.MSC4157UpdateDelayedEvent); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forRoomEvent(_matrixWidgetApi.EventDirection.Send, "org.matrix.rageshake_request").raw); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forRoomEvent(_matrixWidgetApi.EventDirection.Receive, "org.matrix.rageshake_request").raw); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Receive, _matrix.EventType.RoomMember).raw); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Receive, "org.matrix.msc3401.call").raw); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Receive, _matrix.EventType.RoomEncryption).raw); const clientUserId = _MatrixClientPeg.MatrixClientPeg.safeGet().getSafeUserId(); // For the legacy membership type this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Send, "org.matrix.msc3401.call.member", clientUserId).raw); const clientDeviceId = _MatrixClientPeg.MatrixClientPeg.safeGet().getDeviceId(); if (clientDeviceId !== null) { // For the session membership type compliant with MSC4143 this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Send, "org.matrix.msc3401.call.member", `_${clientUserId}_${clientDeviceId}`).raw); // Version with no leading underscore, for room versions whose auth rules allow it this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Send, "org.matrix.msc3401.call.member", `${clientUserId}_${clientDeviceId}`).raw); } this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Receive, "org.matrix.msc3401.call.member").raw); // for determining auth rules specific to the room version this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forStateEvent(_matrixWidgetApi.EventDirection.Receive, _matrix.EventType.RoomCreate).raw); const sendRecvRoomEvents = ["io.element.call.encryption_keys", _matrix.EventType.Reaction, _matrix.EventType.RoomRedaction]; for (const eventType of sendRecvRoomEvents) { this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forRoomEvent(_matrixWidgetApi.EventDirection.Send, eventType).raw); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forRoomEvent(_matrixWidgetApi.EventDirection.Receive, eventType).raw); } const sendRecvToDevice = [_matrix.EventType.CallInvite, _matrix.EventType.CallCandidates, _matrix.EventType.CallAnswer, _matrix.EventType.CallHangup, _matrix.EventType.CallReject, _matrix.EventType.CallSelectAnswer, _matrix.EventType.CallNegotiate, _matrix.EventType.CallSDPStreamMetadataChanged, _matrix.EventType.CallSDPStreamMetadataChangedPrefix, _matrix.EventType.CallReplaces]; for (const eventType of sendRecvToDevice) { this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forToDeviceEvent(_matrixWidgetApi.EventDirection.Send, eventType).raw); this.allowedCapabilities.add(_matrixWidgetApi.WidgetEventCapability.forToDeviceEvent(_matrixWidgetApi.EventDirection.Receive, eventType).raw); } // To always allow OIDC requests for element call, the widgetPermissionStore is used: _SDKContext.SdkContextClass.instance.widgetPermissionStore.setOIDCState(forWidget, forWidgetKind, inRoomId, _WidgetPermissionStore.OIDCState.Allowed); } } async validateCapabilities(requested) { // Check to see if any capabilities aren't automatically accepted (such as sticker pickers // allowing stickers to be sent). If there are excess capabilities to be approved, the user // will be prompted to accept them. const diff = (0, _iterables.iterableDiff)(requested, this.allowedCapabilities); const missing = new Set(diff.removed); // "removed" is "in A (requested) but not in B (allowed)" const allowedSoFar = new Set(this.allowedCapabilities); getRememberedCapabilitiesForWidget(this.forWidget).forEach(cap => { allowedSoFar.add(cap); missing.delete(cap); }); let approved; if (_WidgetPermissions.WidgetPermissionCustomisations.preapproveCapabilities) { approved = await _WidgetPermissions.WidgetPermissionCustomisations.preapproveCapabilities(this.forWidget, requested); } else { const opts = { approvedCapabilities: undefined }; _ModuleRunner.ModuleRunner.instance.invoke(_WidgetLifecycle.WidgetLifecycle.CapabilitiesRequest, opts, this.forWidget, requested); approved = opts.approvedCapabilities; } if (approved) { approved.forEach(cap => { allowedSoFar.add(cap); missing.delete(cap); }); } // TODO: Do something when the widget requests new capabilities not yet asked for let rememberApproved = false; if (missing.size > 0) { try { const [result] = await _Modal.default.createDialog(_WidgetCapabilitiesPromptDialog.default, { requestedCapabilities: missing, widget: this.forWidget, widgetKind: this.forWidgetKind }).finished; result?.approved?.forEach(cap => allowedSoFar.add(cap)); rememberApproved = !!result?.remember; } catch (e) { _logger.logger.error("Non-fatal error getting capabilities: ", e); } } // discard all previously allowed capabilities if they are not requested // TODO: this results in an unexpected behavior when this function is called during the capabilities renegotiation of MSC2974 that will be resolved later. const allAllowed = new Set((0, _iterables.iterableIntersection)(allowedSoFar, requested)); if (rememberApproved) { setRememberedCapabilitiesForWidget(this.forWidget, Array.from(allAllowed)); } return allAllowed; } async sendEvent(eventType, content, stateKey = null, targetRoomId = null) { const client = _MatrixClientPeg.MatrixClientPeg.get(); const roomId = targetRoomId || _SDKContext.SdkContextClass.instance.roomViewStore.getRoomId(); if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); let r; if (stateKey !== null) { // state event r = await client.sendStateEvent(roomId, eventType, content, stateKey); } else if (eventType === _matrix.EventType.RoomRedaction) { // special case: extract the `redacts` property and call redact r = await client.redactEvent(roomId, content["redacts"]); } else { // message event r = await client.sendEvent(roomId, eventType, content); if (eventType === _matrix.EventType.RoomMessage) { _effects.CHAT_EFFECTS.forEach(effect => { if ((0, _utils.containsEmoji)(content, effect.emojis)) { // For initial threads launch, chat effects are disabled // see #19731 const isNotThread = content["m.relates_to"]?.rel_type !== _matrix.THREAD_RELATION_TYPE.name; if (isNotThread) { _dispatcher.default.dispatch({ action: `effects.${effect.command}` }); } } }); } } return { roomId, eventId: r.event_id }; } /** * @experimental Part of MSC4140 & MSC4157 * @see {@link WidgetDriver#sendDelayedEvent} */ /** * @experimental Part of MSC4140 & MSC4157 */ async sendDelayedEvent(delay, parentDelayId, eventType, content, stateKey = null, targetRoomId = null) { const client = _MatrixClientPeg.MatrixClientPeg.get(); const roomId = targetRoomId || _SDKContext.SdkContextClass.instance.roomViewStore.getRoomId(); if (!client || !roomId) throw new Error("Not in a room or not attached to a client"); let delayOpts; if (delay !== null) { delayOpts = _objectSpread({ delay }, parentDelayId !== null && { parent_delay_id: parentDelayId }); } else if (parentDelayId !== null) { delayOpts = { parent_delay_id: parentDelayId }; } else { throw new Error("Must provide at least one of delay or parentDelayId"); } let r; if (stateKey !== null) { // state event r = await client._unstable_sendDelayedStateEvent(roomId, delayOpts, eventType, content, stateKey); } else { // message event r = await client._unstable_sendDelayedEvent(roomId, delayOpts, null, eventType, content); } return { roomId, delayId: r.delay_id }; } /** * @experimental Part of MSC4140 & MSC4157 */ async updateDelayedEvent(delayId, action) { const client = _MatrixClientPeg.MatrixClientPeg.get(); if (!client) throw new Error("Not in a room or not attached to a client"); await client._unstable_updateDelayedEvent(delayId, action); } async sendToDevice(eventType, encrypted, contentMap) { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); if (encrypted) { const deviceInfoMap = await client.crypto.deviceList.downloadKeys(Object.keys(contentMap), false); await Promise.all(Object.entries(contentMap).flatMap(([userId, userContentMap]) => Object.entries(userContentMap).map(async ([deviceId, content]) => { const devices = deviceInfoMap.get(userId); if (!devices) return; if (deviceId === "*") { // Send the message to all devices we have keys for await client.encryptAndSendToDevices(Array.from(devices.values()).map(deviceInfo => ({ userId, deviceInfo })), content); } else if (devices.has(deviceId)) { // Send the message to a specific device await client.encryptAndSendToDevices([{ userId, deviceInfo: devices.get(deviceId) }], content); } }))); } else { await client.queueToDevice({ eventType, batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) => Object.entries(userContentMap).map(([deviceId, content]) => ({ userId, deviceId, payload: content }))) }); } } pickRooms(roomIds) { const client = _MatrixClientPeg.MatrixClientPeg.get(); if (!client) throw new Error("Not attached to a client"); const targetRooms = roomIds ? roomIds.includes(_matrixWidgetApi.Symbols.AnyRoom) ? client.getVisibleRooms(_SettingsStore.default.getValue("feature_dynamic_room_predecessors")) : roomIds.map(r => client.getRoom(r)) : [client.getRoom(_SDKContext.SdkContextClass.instance.roomViewStore.getRoomId())]; return targetRooms.filter(r => !!r); } async readRoomEvents(eventType, msgtype, limitPerRoom, roomIds) { limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary const rooms = this.pickRooms(roomIds); const allResults = []; for (const room of rooms) { const results = []; const events = room.getLiveTimeline().getEvents(); // timelines are most recent last for (let i = events.length - 1; i > 0; i--) { if (results.length >= limitPerRoom) break; const ev = events[i]; if (ev.getType() !== eventType || ev.isState()) continue; if (eventType === _matrix.EventType.RoomMessage && msgtype && msgtype !== ev.getContent()["msgtype"]) continue; results.push(ev); } results.forEach(e => allResults.push(e.getEffectiveEvent())); } return allResults; } async readStateEvents(eventType, stateKey, limitPerRoom, roomIds) { limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary const rooms = this.pickRooms(roomIds); const allResults = []; for (const room of rooms) { const results = []; const state = room.currentState.events.get(eventType); if (state) { if (stateKey === "" || !!stateKey) { const forKey = state.get(stateKey); if (forKey) results.push(forKey); } else { results.push(...Array.from(state.values())); } } results.slice(0, limitPerRoom).forEach(e => allResults.push(e.getEffectiveEvent())); } return allResults; } async askOpenID(observer) { const opts = { approved: undefined }; _ModuleRunner.ModuleRunner.instance.invoke(_WidgetLifecycle.WidgetLifecycle.IdentityRequest, opts, this.forWidget); if (opts.approved) { return observer.update({ state: _matrixWidgetApi.OpenIDRequestState.Allowed, token: await _MatrixClientPeg.MatrixClientPeg.safeGet().getOpenIdToken() }); } const oidcState = _SDKContext.SdkContextClass.instance.widgetPermissionStore.getOIDCState(this.forWidget, this.forWidgetKind, this.inRoomId); const getToken = () => { return _MatrixClientPeg.MatrixClientPeg.safeGet().getOpenIdToken(); }; if (oidcState === _WidgetPermissionStore.OIDCState.Denied) { return observer.update({ state: _matrixWidgetApi.OpenIDRequestState.Blocked }); } if (oidcState === _WidgetPermissionStore.OIDCState.Allowed) { return observer.update({ state: _matrixWidgetApi.OpenIDRequestState.Allowed, token: await getToken() }); } observer.update({ state: _matrixWidgetApi.OpenIDRequestState.PendingUserConfirmation }); _Modal.default.createDialog(_WidgetOpenIDPermissionsDialog.default, { widget: this.forWidget, widgetKind: this.forWidgetKind, inRoomId: this.inRoomId, onFinished: async confirm => { if (!confirm) { return observer.update({ state: _matrixWidgetApi.OpenIDRequestState.Blocked }); } return observer.update({ state: _matrixWidgetApi.OpenIDRequestState.Allowed, token: await getToken() }); } }); } async navigate(uri) { (0, _navigator.navigateToPermalink)(uri); } async *getTurnServers() { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); if (!client.pollingTurnServers || !client.getTurnServers().length) return; let setTurnServer; let setError; const onTurnServers = ([server]) => setTurnServer(normalizeTurnServer(server)); const onTurnServersError = (error, fatal) => { if (fatal) setError(error); }; client.on(_matrix.ClientEvent.TurnServers, onTurnServers); client.on(_matrix.ClientEvent.TurnServersError, onTurnServersError); try { const initialTurnServer = client.getTurnServers()[0]; yield normalizeTurnServer(initialTurnServer); // Repeatedly listen for new TURN servers until an error occurs or // the caller stops this generator while (true) { yield await new Promise((resolve, reject) => { setTurnServer = resolve; setError = reject; }); } } finally { // The loop was broken - clean up client.off(_matrix.ClientEvent.TurnServers, onTurnServers); client.off(_matrix.ClientEvent.TurnServersError, onTurnServersError); } } async readEventRelations(eventId, roomId, relationType, eventType, from, to, limit, direction) { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); const dir = direction; roomId = roomId ?? _SDKContext.SdkContextClass.instance.roomViewStore.getRoomId() ?? undefined; if (typeof roomId !== "string") { throw new Error("Error while reading the current room"); } const { events, nextBatch, prevBatch } = await client.relations(roomId, eventId, relationType ?? null, eventType ?? null, { from, to, limit, dir }); return { chunk: events.map(e => e.getEffectiveEvent()), nextBatch: nextBatch ?? undefined, prevBatch: prevBatch ?? undefined }; } async searchUserDirectory(searchTerm, limit) { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); const { limited, results } = await client.searchUserDirectory({ term: searchTerm, limit }); return { limited, results: results.map(r => ({ userId: r.user_id, displayName: r.display_name, avatarUrl: r.avatar_url })) }; } async getMediaConfig() { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); return await client.getMediaConfig(); } async uploadFile(file) { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); const uploadResult = await client.uploadContent(file); return { contentUri: uploadResult.content_uri }; } /** * Download a file from the media repository on the homeserver. * * @param contentUri - the MXC URI of the file to download * @returns an object with: file - response contents as Blob */ async downloadFile(contentUri) { const client = _MatrixClientPeg.MatrixClientPeg.safeGet(); const media = new _Media.Media({ mxc: contentUri }, client); const response = await media.downloadSource(); const blob = await response.blob(); return { file: blob }; } } exports.StopGapWidgetDriver = StopGapWidgetDriver; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4V2lkZ2V0QXBpIiwicmVxdWlyZSIsIl9tYXRyaXgiLCJfbG9nZ2VyIiwiX1dpZGdldExpZmVjeWNsZSIsIl9TZGtDb25maWciLCJfaW50ZXJvcFJlcXVpcmVXaWxkY2FyZCIsIl9pdGVyYWJsZXMiLCJfTWF0cml4Q2xpZW50UGVnIiwiX01vZGFsIiwiX2ludGVyb3BSZXF1aXJlRGVmYXVsdCIsIl9XaWRnZXRPcGVuSURQZXJtaXNzaW9uc0RpYWxvZyIsIl9XaWRnZXRDYXBhYmlsaXRpZXNQcm9tcHREaWFsb2ciLCJfV2lkZ2V0UGVybWlzc2lvbnMiLCJfV2lkZ2V0UGVybWlzc2lvblN0b3JlIiwiX1dpZGdldFR5cGUiLCJfZWZmZWN0cyIsIl91dGlscyIsIl9kaXNwYXRjaGVyIiwiX0VsZW1lbnRXaWRnZXRDYXBhYmlsaXRpZXMiLCJfbmF2aWdhdG9yIiwiX1NES0NvbnRleHQiLCJfTW9kdWxlUnVubmVyIiwiX1NldHRpbmdzU3RvcmUiLCJfTWVkaWEiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsImRlZmF1bHQiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJvd25LZXlzIiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJnZXRSZW1lbWJlcmVkQ2FwYWJpbGl0aWVzRm9yV2lkZ2V0Iiwid2lkZ2V0IiwiSlNPTiIsInBhcnNlIiwibG9jYWxTdG9yYWdlIiwiZ2V0SXRlbSIsImlkIiwic2V0UmVtZW1iZXJlZENhcGFiaWxpdGllc0ZvcldpZGdldCIsImNhcHMiLCJzZXRJdGVtIiwic3RyaW5naWZ5Iiwibm9ybWFsaXplVHVyblNlcnZlciIsInVybHMiLCJ1c2VybmFtZSIsImNyZWRlbnRpYWwiLCJ1cmlzIiwicGFzc3dvcmQiLCJTdG9wR2FwV2lkZ2V0RHJpdmVyIiwiV2lkZ2V0RHJpdmVyIiwiY29uc3RydWN0b3IiLCJhbGxvd2VkQ2FwYWJpbGl0aWVzIiwiZm9yV2lkZ2V0IiwiZm9yV2lkZ2V0S2luZCIsInZpcnR1YWwiLCJpblJvb21JZCIsIlNldCIsIk1hdHJpeENhcGFiaWxpdGllcyIsIlNjcmVlbnNob3RzIiwiRWxlbWVudFdpZGdldENhcGFiaWxpdGllcyIsIlJlcXVpcmVzQ2xpZW50IiwiV2lkZ2V0VHlwZSIsIkpJVFNJIiwibWF0Y2hlcyIsInR5cGUiLCJXaWRnZXRLaW5kIiwiUm9vbSIsImFkZCIsIkFsd2F5c09uU2NyZWVuIiwiU1RJQ0tFUlBJQ0tFUiIsIkFjY291bnQiLCJzdGlja2VyU2VuZGluZ0NhcCIsIldpZGdldEV2ZW50Q2FwYWJpbGl0eSIsImZvclJvb21FdmVudCIsIkV2ZW50RGlyZWN0aW9uIiwiU2VuZCIsIkV2ZW50VHlwZSIsIlN0aWNrZXIiLCJyYXciLCJTdGlja2VyU2VuZGluZyIsIlVSTCIsIlNka0NvbmZpZyIsInVybCIsIkRFRkFVTFRTIiwiZWxlbWVudF9jYWxsIiwib3JpZ2luIiwiTVNDMzg0NlR1cm5TZXJ2ZXJzIiwiTVNDNDE1N1NlbmREZWxheWVkRXZlbnQiLCJNU0M0MTU3VXBkYXRlRGVsYXllZEV2ZW50IiwiUmVjZWl2ZSIsImZvclN0YXRlRXZlbnQiLCJSb29tTWVtYmVyIiwiUm9vbUVuY3J5cHRpb24iLCJjbGllbnRVc2VySWQiLCJNYXRyaXhDbGllbnRQZWciLCJzYWZlR2V0IiwiZ2V0U2FmZVVzZXJJZCIsImNsaWVudERldmljZUlkIiwiZ2V0RGV2aWNlSWQiLCJSb29tQ3JlYXRlIiwic2VuZFJlY3ZSb29tRXZlbnRzIiwiUmVhY3Rpb24iLCJSb29tUmVkYWN0aW9uIiwiZXZlbnRUeXBlIiwic2VuZFJlY3ZUb0RldmljZSIsIkNhbGxJbnZpdGUiLCJDYWxsQ2FuZGlkYXRlcyIsIkNhbGxBbnN3ZXIiLCJDYWxsSGFuZ3VwIiwiQ2FsbFJlamVjdCIsIkNhbGxTZWxlY3RBbnN3ZXIiLCJDYWxsTmVnb3RpYXRlIiwiQ2FsbFNEUFN0cmVhbU1ldGFkYXRhQ2hhbmdlZCIsIkNhbGxTRFBTdHJlYW1NZXRhZGF0YUNoYW5nZWRQcmVmaXgiLCJDYWxsUmVwbGFjZXMiLCJmb3JUb0RldmljZUV2ZW50IiwiU2RrQ29udGV4dENsYXNzIiwiaW5zdGFuY2UiLCJ3aWRnZXRQZXJtaXNzaW9uU3RvcmUiLCJzZXRPSURDU3RhdGUiLCJPSURDU3RhdGUiLCJBbGxvd2VkIiwidmFsaWRhdGVDYXBhYmlsaXRpZXMiLCJyZXF1ZXN0ZWQiLCJkaWZmIiwiaXRlcmFibGVEaWZmIiwibWlzc2luZyIsInJlbW92ZWQiLCJhbGxvd2VkU29GYXIiLCJjYXAiLCJkZWxldGUiLCJhcHByb3ZlZCIsIldpZGdldFBlcm1pc3Npb25DdXN0b21pc2F0aW9ucyIsInByZWFwcHJvdmVDYXBhYmlsaXRpZXMiLCJvcHRzIiwiYXBwcm92ZWRDYXBhYmlsaXRpZXMiLCJ1bmRlZmluZWQiLCJNb2R1bGVSdW5uZXIiLCJpbnZva2UiLCJXaWRnZXRMaWZlY3ljbGUiLCJDYXBhYmlsaXRpZXNSZXF1ZXN0IiwicmVtZW1iZXJBcHByb3ZlZCIsInNpemUiLCJyZXN1bHQiLCJNb2RhbCIsImNyZWF0ZURpYWxvZyIsIldpZGdldENhcGFiaWxpdGllc1Byb21wdERpYWxvZyIsInJlcXVlc3RlZENhcGFiaWxpdGllcyIsIndpZGdldEtpbmQiLCJmaW5pc2hlZCIsInJlbWVtYmVyIiwibG9nZ2VyIiwiZXJyb3IiLCJhbGxBbGxvd2VkIiwiaXRlcmFibGVJbnRlcnNlY3Rpb24iLCJBcnJheSIsImZyb20iLCJzZW5kRXZlbnQiLCJjb250ZW50Iiwic3RhdGVLZXkiLCJ0YXJnZXRSb29tSWQiLCJjbGllbnQiLCJyb29tSWQiLCJyb29tVmlld1N0b3JlIiwiZ2V0Um9vbUlkIiwiRXJyb3IiLCJzZW5kU3RhdGVFdmVudCIsInJlZGFjdEV2ZW50IiwiUm9vbU1lc3NhZ2UiLCJDSEFUX0VGRkVDVFMiLCJlZmZlY3QiLCJjb250YWluc0Vtb2ppIiwiZW1vamlzIiwiaXNOb3RUaHJlYWQiLCJyZWxfdHlwZSIsIlRIUkVBRF9SRUxBVElPTl9UWVBFIiwibmFtZSIsImRpcyIsImRpc3BhdGNoIiwiYWN0aW9uIiwiY29tbWFuZCIsImV2ZW50SWQiLCJldmVudF9pZCIsInNlbmREZWxheWVkRXZlbnQiLCJkZWxheSIsInBhcmVudERlbGF5SWQiLCJkZWxheU9wdHMiLCJwYXJlbnRfZGVsYXlfaWQiLCJfdW5zdGFibGVfc2VuZERlbGF5ZWRTdGF0ZUV2ZW50IiwiX3Vuc3RhYmxlX3NlbmREZWxheWVkRXZlbnQiLCJkZWxheUlkIiwiZGVsYXlfaWQiLCJ1cGRhdGVEZWxheWVkRXZlbnQiLCJfdW5zdGFibGVfdXBkYXRlRGVsYXllZEV2ZW50Iiwic2VuZFRvRGV2aWNlIiwiZW5jcnlwdGVkIiwiY29udGVudE1hcCIsImRldmljZUluZm9NYXAiLCJjcnlwdG8iLCJkZXZpY2VMaXN0IiwiZG93bmxvYWRLZXlzIiwiUHJvbWlzZSIsImFsbCIsImVudHJpZXMiLCJmbGF0TWFwIiwidXNlcklkIiwidXNlckNvbnRlbnRNYXAiLCJtYXAiLCJkZXZpY2VJZCIsImRldmljZXMiLCJlbmNyeXB0QW5kU2VuZFRvRGV2aWNlcyIsInZhbHVlcyIsImRldmljZUluZm8iLCJxdWV1ZVRvRGV2aWNlIiwiYmF0Y2giLCJwYXlsb2FkIiwicGlja1Jvb21zIiwicm9vbUlkcyIsInRhcmdldFJvb21zIiwiaW5jbHVkZXMiLCJTeW1ib2xzIiwiQW55Um9vbSIsImdldFZpc2libGVSb29tcyIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsImdldFJvb20iLCJyZWFkUm9vbUV2ZW50cyIsIm1zZ3R5cGUiLCJsaW1pdFBlclJvb20iLCJNYXRoIiwibWluIiwiTnVtYmVyIiwiTUFYX1NBRkVfSU5URUdFUiIsInJvb21zIiwiYWxsUmVzdWx0cyIsInJvb20iLCJyZXN1bHRzIiwiZXZlbnRzIiwiZ2V0TGl2ZVRpbWVsaW5lIiwiZ2V0RXZlbnRzIiwiZXYiLCJnZXRUeXBlIiwiaXNTdGF0ZSIsImdldENvbnRlbnQiLCJnZXRFZmZlY3RpdmVFdmVudCIsInJlYWRTdGF0ZUV2ZW50cyIsInN0YXRlIiwiY3VycmVudFN0YXRlIiwiZm9yS2V5Iiwic2xpY2UiLCJhc2tPcGVuSUQiLCJvYnNlcnZlciIsIklkZW50aXR5UmVxdWVzdCIsInVwZGF0ZSIsIk9wZW5JRFJlcXVlc3RTdGF0ZSIsInRva2VuIiwiZ2V0T3BlbklkVG9rZW4iLCJvaWRjU3RhdGUiLCJnZXRPSURDU3RhdGUiLCJnZXRUb2tlbiIsIkRlbmllZCIsIkJsb2NrZWQiLCJQZW5kaW5nVXNlckNvbmZpcm1hdGlvbiIsIldpZGdldE9wZW5JRFBlcm1pc3Npb25zRGlhbG9nIiwib25GaW5pc2hlZCIsImNvbmZpcm0iLCJuYXZpZ2F0ZSIsInVyaSIsIm5hdmlnYXRlVG9QZXJtYWxpbmsiLCJnZXRUdXJuU2VydmVycyIsInBvbGxpbmdUdXJuU2VydmVycyIsInNldFR1cm5TZXJ2ZXIiLCJzZXRFcnJvciIsIm9uVHVyblNlcnZlcnMiLCJzZXJ2ZXIiLCJvblR1cm5TZXJ2ZXJzRXJyb3IiLCJmYXRhbCIsIm9uIiwiQ2xpZW50RXZlbnQiLCJUdXJuU2VydmVycyIsIlR1cm5TZXJ2ZXJzRXJyb3IiLCJpbml0aWFsVHVyblNlcnZlciIsInJlc29sdmUiLCJyZWplY3QiLCJvZmYiLCJyZWFkRXZlbnRSZWxhdGlvbnMiLCJyZWxhdGlvblR5cGUiLCJ0byIsImxpbWl0IiwiZGlyZWN0aW9uIiwiZGlyIiwibmV4dEJhdGNoIiwicHJldkJhdGNoIiwicmVsYXRpb25zIiwiY2h1bmsiLCJzZWFyY2hVc2VyRGlyZWN0b3J5Iiwic2VhcmNoVGVybSIsImxpbWl0ZWQiLCJ0ZXJtIiwidXNlcl9pZCIsImRpc3BsYXlOYW1lIiwiZGlzcGxheV9uYW1lIiwiYXZhdGFyVXJsIiwiYXZhdGFyX3VybCIsImdldE1lZGlhQ29uZmlnIiwidXBsb2FkRmlsZSIsImZpbGUiLCJ1cGxvYWRSZXN1bHQiLCJ1cGxvYWRDb250ZW50IiwiY29udGVudFVyaSIsImNvbnRlbnRfdXJpIiwiZG93bmxvYWRGaWxlIiwibWVkaWEiLCJNZWRpYSIsIm14YyIsInJlc3BvbnNlIiwiZG93bmxvYWRTb3VyY2UiLCJibG9iIiwiZXhwb3J0cyJdLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zdG9yZXMvd2lkZ2V0cy9TdG9wR2FwV2lkZ2V0RHJpdmVyLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiBDb3B5cmlnaHQgMjAyNCBOZXcgVmVjdG9yIEx0ZC5cbiAqIENvcHlyaWdodCAyMDIwLTIwMjMgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cbiAqXG4gKiBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQUdQTC0zLjAtb25seSBPUiBHUEwtMy4wLW9ubHlcbiAqIFBsZWFzZSBzZWUgTElDRU5TRSBmaWxlcyBpbiB0aGUgcmVwb3NpdG9yeSByb290IGZvciBmdWxsIGRldGFpbHMuXG4gKi9cblxuaW1wb3J0IHtcbiAgICBDYXBhYmlsaXR5LFxuICAgIEV2ZW50RGlyZWN0aW9uLFxuICAgIElPcGVuSURDcmVkZW50aWFscyxcbiAgICBJT3BlbklEVXBkYXRlLFxuICAgIElTZW5kRGVsYXllZEV2ZW50RGV0YWlscyxcbiAgICBJU2VuZEV2ZW50RGV0YWlscyxcbiAgICBJVHVyblNlcnZlcixcbiAgICBJUmVhZEV2ZW50UmVsYXRpb25zUmVzdWx0LFxuICAgIElSb29tRXZlbnQsXG4gICAgTWF0cml4Q2FwYWJpbGl0aWVzLFxuICAgIE9wZW5JRFJlcXVlc3RTdGF0ZSxcbiAgICBTaW1wbGVPYnNlcnZhYmxlLFxuICAgIFN5bWJvbHMsXG4gICAgV2lkZ2V0LFxuICAgIFdpZGdldERyaXZlcixcbiAgICBXaWRnZXRFdmVudENhcGFiaWxpdHksXG4gICAgV2lkZ2V0S2luZCxcbiAgICBJU2VhcmNoVXNlckRpcmVjdG9yeVJlc3VsdCxcbiAgICBJR2V0TWVkaWFDb25maWdSZXN1bHQsXG4gICAgVXBkYXRlRGVsYXllZEV2ZW50QWN0aW9uLFxufSBmcm9tIFwibWF0cml4LXdpZGdldC1hcGlcIjtcbmltcG9ydCB7XG4gICAgQ2xpZW50RXZlbnQsXG4gICAgSVR1cm5TZXJ2ZXIgYXMgSUNsaWVudFR1cm5TZXJ2ZXIsXG4gICAgRXZlbnRUeXBlLFxuICAgIElDb250ZW50LFxuICAgIE1hdHJpeEV2ZW50LFxuICAgIFJvb20sXG4gICAgRGlyZWN0aW9uLFxuICAgIFRIUkVBRF9SRUxBVElPTl9UWVBFLFxuICAgIFNlbmREZWxheWVkRXZlbnRSZXNwb25zZSxcbiAgICBTdGF0ZUV2ZW50cyxcbiAgICBUaW1lbGluZUV2ZW50cyxcbn0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL21hdHJpeFwiO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL2xvZ2dlclwiO1xuaW1wb3J0IHtcbiAgICBBcHByb3ZhbE9wdHMsXG4gICAgQ2FwYWJpbGl0aWVzT3B0cyxcbiAgICBXaWRnZXRMaWZlY3ljbGUsXG59IGZyb20gXCJAbWF0cml4LW9yZy9yZWFjdC1zZGstbW9kdWxlLWFwaS9saWIvbGlmZWN5Y2xlcy9XaWRnZXRMaWZlY3ljbGVcIjtcblxuaW1wb3J0IFNka0NvbmZpZywgeyBERUZBVUxUUyB9IGZyb20gXCIuLi8uLi9TZGtDb25maWdcIjtcbmltcG9ydCB7IGl0ZXJhYmxlRGlmZiwgaXRlcmFibGVJbnRlcnNlY3Rpb24gfSBmcm9tIFwiLi4vLi4vdXRpbHMvaXRlcmFibGVzXCI7XG5pbXBvcnQgeyBNYXRyaXhDbGllbnRQZWcgfSBmcm9tIFwiLi4vLi4vTWF0cml4Q2xpZW50UGVnXCI7XG5pbXBvcnQgTW9kYWwgZnJvbSBcIi4uLy4uL01vZGFsXCI7XG5pbXBvcnQgV2lkZ2V0T3BlbklEUGVybWlzc2lvbnNEaWFsb2cgZnJvbSBcIi4uLy4uL2NvbXBvbmVudHMvdmlld3MvZGlhbG9ncy9XaWRnZXRPcGVuSURQZXJtaXNzaW9uc0RpYWxvZ1wiO1xuaW1wb3J0IFdpZGdldENhcGFiaWxpdGllc1Byb21wdERpYWxvZyBmcm9tIFwiLi4vLi4vY29tcG9uZW50cy92aWV3cy9kaWFsb2dzL1dpZGdldENhcGFiaWxpdGllc1Byb21wdERpYWxvZ1wiO1xuaW1wb3J0IHsgV2lkZ2V0UGVybWlzc2lvbkN1c3RvbWlzYXRpb25zIH0gZnJvbSBcIi4uLy4uL2N1c3RvbWlzYXRpb25zL1dpZGdldFBlcm1pc3Npb25zXCI7XG5pbXBvcnQgeyBPSURDU3RhdGUgfSBmcm9tIFwiLi9XaWRnZXRQZXJtaXNzaW9uU3RvcmVcIjtcbmltcG9ydCB7IFdpZGdldFR5cGUgfSBmcm9tIFwiLi4vLi4vd2lkZ2V0cy9XaWRnZXRUeXBlXCI7XG5pbXBvcnQgeyBDSEFUX0VGRkVDVFMgfSBmcm9tIFwiLi4vLi4vZWZmZWN0c1wiO1xuaW1wb3J0IHsgY29udGFpbnNFbW9qaSB9IGZyb20gXCIuLi8uLi9lZmZlY3RzL3V0aWxzXCI7XG5pbXBvcnQgZGlzIGZyb20gXCIuLi8uLi9kaXNwYXRjaGVyL2Rpc3BhdGNoZXJcIjtcbmltcG9ydCB7IEVsZW1lbnRXaWRnZXRDYXBhYmlsaXRpZXMgfSBmcm9tIFwiLi9FbGVtZW50V2lkZ2V0Q2FwYWJpbGl0aWVzXCI7XG5pbXBvcnQgeyBuYXZpZ2F0ZVRvUGVybWFsaW5rIH0gZnJvbSBcIi4uLy4uL3V0aWxzL3Blcm1hbGlua3MvbmF2aWdhdG9yXCI7XG5pbXBvcnQgeyBTZGtDb250ZXh0Q2xhc3MgfSBmcm9tIFwiLi4vLi4vY29udGV4dHMvU0RLQ29udGV4dFwiO1xuaW1wb3J0IHsgTW9kdWxlUnVubmVyIH0gZnJvbSBcIi4uLy4uL21vZHVsZXMvTW9kdWxlUnVubmVyXCI7XG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi4vLi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuaW1wb3J0IHsgTWVkaWEgfSBmcm9tIFwiLi4vLi4vY3VzdG9taXNhdGlvbnMvTWVkaWFcIjtcblxuLy8gVE9ETzogUHVyZ2UgdGhpcyBmcm9tIHRoZSB1bml2ZXJzZVxuXG5mdW5jdGlvbiBnZXRSZW1lbWJlcmVkQ2FwYWJpbGl0aWVzRm9yV2lkZ2V0KHdpZGdldDogV2lkZ2V0KTogQ2FwYWJpbGl0eVtdIHtcbiAgICByZXR1cm4gSlNPTi5wYXJzZShsb2NhbFN0b3JhZ2UuZ2V0SXRlbShgd2lkZ2V0XyR7d2lkZ2V0LmlkfV9hcHByb3ZlZF9jYXBzYCkgfHwgXCJbXVwiKTtcbn1cblxuZnVuY3Rpb24gc2V0UmVtZW1iZXJlZENhcGFiaWxpdGllc0ZvcldpZGdldCh3aWRnZXQ6IFdpZGdldCwgY2FwczogQ2FwYWJpbGl0eVtdKTogdm9pZCB7XG4gICAgbG9jYWxTdG9yYWdlLnNldEl0ZW0oYHdpZGdldF8ke3dpZGdldC5pZH1fYXBwcm92ZWRfY2Fwc2AsIEpTT04uc3RyaW5naWZ5KGNhcHMpKTtcbn1cblxuY29uc3Qgbm9ybWFsaXplVHVyblNlcnZlciA9ICh7IHVybHMsIHVzZXJuYW1lLCBjcmVkZW50aWFsIH06IElDbGllbnRUdXJuU2VydmVyKTogSVR1cm5TZXJ2ZXIgPT4gKHtcbiAgICB1cmlzOiB1cmxzLFxuICAgIHVzZXJuYW1lLFxuICAgIHBhc3N3b3JkOiBjcmVkZW50aWFsLFxufSk7XG5cbmV4cG9ydCBjbGFzcyBTdG9wR2FwV2lkZ2V0RHJpdmVyIGV4dGVuZHMgV2lkZ2V0RHJpdmVyIHtcbiAgICBwcml2YXRlIGFsbG93ZWRDYXBhYmlsaXRpZXM6IFNldDxDYXBhYmlsaXR5PjtcblxuICAgIC8vIFRPRE86IFJlZmFjdG9yIHdpZGdldEtpbmQgaW50byB0aGUgV2lkZ2V0IGNsYXNzXG4gICAgcHVibGljIGNvbnN0cnVjdG9yKFxuICAgICAgICBhbGxvd2VkQ2FwYWJpbGl0aWVzOiBDYXBhYmlsaXR5W10sXG4gICAgICAgIHByaXZhdGUgZm9yV2lkZ2V0OiBXaWRnZXQsXG4gICAgICAgIHByaXZhdGUgZm9yV2lkZ2V0S2luZDogV2lkZ2V0S2luZCxcbiAgICAgICAgdmlydHVhbDogYm9vbGVhbixcbiAgICAgICAgcHJpdmF0ZSBpblJvb21JZD86IHN0cmluZyxcbiAgICApIHtcbiAgICAgICAgc3VwZXIoKTtcblxuICAgICAgICAvLyBBbHdheXMgYWxsb3cgc2NyZWVuc2hvdHMgdG8gYmUgdGFrZW4gYmVjYXVzZSBpdCdzIGEgY2xpZW50LWluZHVjZWQgZmxvdy4gVGhlIHdpZGdldCBjYW4ndFxuICAgICAgICAvLyBzcGV3IHNjcmVlbnNob3RzIGF0IHVzIGFuZCBjYW4ndCByZXF1ZXN0IHNjcmVlbnNob3RzIG9mIHVzLCBzbyBpdCdzIHVwIHRvIHVzIHRvIHByb3ZpZGUgdGhlXG4gICAgICAgIC8vIGJ1dHRvbiBpZiB0aGUgd2lkZ2V0IHNheXMgaXQgc3VwcG9ydHMgc2NyZWVuc2hvdHMuXG4gICAgICAgIHRoaXMuYWxsb3dlZENhcGFiaWxpdGllcyA9IG5ldyBTZXQoW1xuICAgICAgICAgICAgLi4uYWxsb3dlZENhcGFiaWxpdGllcyxcbiAgICAgICAgICAgIE1hdHJpeENhcGFiaWxpdGllcy5TY3JlZW5zaG90cyxcbiAgICAgICAgICAgIEVsZW1lbnRXaWRnZXRDYXBhYmlsaXRpZXMuUmVxdWlyZXNDbGllbnQsXG4gICAgICAgIF0pO1xuXG4gICAgICAgIC8vIEdyYW50IHRoZSBwZXJtaXNzaW9ucyB0aGF0IGFyZSBzcGVjaWZpYyB0byBnaXZlbiB3aWRnZXQgdHlwZXNcbiAgICAgICAgaWYgKFdpZGdldFR5cGUuSklUU0kubWF0Y2hlcyh0aGlzLmZvcldpZGdldC50eXBlKSAmJiBmb3JXaWRnZXRLaW5kID09PSBXaWRnZXRLaW5kLlJvb20pIHtcbiAgICAgICAgICAgIHRoaXMuYWxsb3dlZENhcGFiaWxpdGllcy5hZGQoTWF0cml4Q2FwYWJpbGl0aWVzLkFsd2F5c09uU2NyZWVuKTtcbiAgICAgICAgfSBlbHNlIGlmIChXaWRnZXRUeXBlLlNUSUNLRVJQSUNLRVIubWF0Y2hlcyh0aGlzLmZvcldpZGdldC50eXBlKSAmJiBmb3JXaWRnZXRLaW5kID09PSBXaWRnZXRLaW5kLkFjY291bnQpIHtcbiAgICAgICAgICAgIGNvbnN0IHN0aWNrZXJTZW5kaW5nQ2FwID0gV2lkZ2V0RXZlbnRDYXBhYmlsaXR5LmZvclJvb21FdmVudChFdmVudERpcmVjdGlvbi5TZW5kLCBFdmVudFR5cGUuU3RpY2tlcikucmF3O1xuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChNYXRyaXhDYXBhYmlsaXRpZXMuU3RpY2tlclNlbmRpbmcpOyAvLyBsZWdhY3kgYXMgZmFyIGFzIE1TQzI3NjIgaXMgY29uY2VybmVkXG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKHN0aWNrZXJTZW5kaW5nQ2FwKTtcblxuICAgICAgICAgICAgLy8gQXV0by1hcHByb3ZlIHRoZSBsZWdhY3kgdmlzaWJpbGl0eSBjYXBhYmlsaXR5LiBXZSBzZW5kIGl0IHJlZ2FyZGxlc3Mgb2YgY2FwYWJpbGl0eS5cbiAgICAgICAgICAgIC8vIFdpZGdldHMgZG9uJ3QgdGVjaG5pY2FsbHkgbmVlZCB0byByZXF1ZXN0IHRoaXMgY2FwYWJpbGl0eSwgYnV0IFNjYWxhciBzdGlsbCBkb2VzLlxuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChcInZpc2liaWxpdHlcIik7XG4gICAgICAgIH0gZWxzZSBpZiAoXG4gICAgICAgICAgICB2aXJ0dWFsICYmXG4gICAgICAgICAgICBuZXcgVVJMKFNka0NvbmZpZy5nZXQoXCJlbGVtZW50X2NhbGxcIikudXJsID8/IERFRkFVTFRTLmVsZW1lbnRfY2FsbC51cmwhKS5vcmlnaW4gPT09IHRoaXMuZm9yV2lkZ2V0Lm9yaWdpblxuICAgICAgICApIHtcbiAgICAgICAgICAgIC8vIFRoaXMgaXMgYSB0cnVzdGVkIEVsZW1lbnQgQ2FsbCB3aWRnZXQgdGhhdCB3ZSBjb250cm9sXG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKE1hdHJpeENhcGFiaWxpdGllcy5BbHdheXNPblNjcmVlbik7XG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKE1hdHJpeENhcGFiaWxpdGllcy5NU0MzODQ2VHVyblNlcnZlcnMpO1xuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChgb3JnLm1hdHJpeC5tc2MyNzYyLnRpbWVsaW5lOiR7aW5Sb29tSWR9YCk7XG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKE1hdHJpeENhcGFiaWxpdGllcy5NU0M0MTU3U2VuZERlbGF5ZWRFdmVudCk7XG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKE1hdHJpeENhcGFiaWxpdGllcy5NU0M0MTU3VXBkYXRlRGVsYXllZEV2ZW50KTtcblxuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChcbiAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yUm9vbUV2ZW50KEV2ZW50RGlyZWN0aW9uLlNlbmQsIFwib3JnLm1hdHJpeC5yYWdlc2hha2VfcmVxdWVzdFwiKS5yYXcsXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChcbiAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yUm9vbUV2ZW50KEV2ZW50RGlyZWN0aW9uLlJlY2VpdmUsIFwib3JnLm1hdHJpeC5yYWdlc2hha2VfcmVxdWVzdFwiKS5yYXcsXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChcbiAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yU3RhdGVFdmVudChFdmVudERpcmVjdGlvbi5SZWNlaXZlLCBFdmVudFR5cGUuUm9vbU1lbWJlcikucmF3LFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIHRoaXMuYWxsb3dlZENhcGFiaWxpdGllcy5hZGQoXG4gICAgICAgICAgICAgICAgV2lkZ2V0RXZlbnRDYXBhYmlsaXR5LmZvclN0YXRlRXZlbnQoRXZlbnREaXJlY3Rpb24uUmVjZWl2ZSwgXCJvcmcubWF0cml4Lm1zYzM0MDEuY2FsbFwiKS5yYXcsXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChcbiAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yU3RhdGVFdmVudChFdmVudERpcmVjdGlvbi5SZWNlaXZlLCBFdmVudFR5cGUuUm9vbUVuY3J5cHRpb24pLnJhdyxcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBjb25zdCBjbGllbnRVc2VySWQgPSBNYXRyaXhDbGllbnRQZWcuc2FmZUdldCgpLmdldFNhZmVVc2VySWQoKTtcbiAgICAgICAgICAgIC8vIEZvciB0aGUgbGVnYWN5IG1lbWJlcnNoaXAgdHlwZVxuICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChcbiAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yU3RhdGVFdmVudChFdmVudERpcmVjdGlvbi5TZW5kLCBcIm9yZy5tYXRyaXgubXNjMzQwMS5jYWxsLm1lbWJlclwiLCBjbGllbnRVc2VySWQpXG4gICAgICAgICAgICAgICAgICAgIC5yYXcsXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgY29uc3QgY2xpZW50RGV2aWNlSWQgPSBNYXRyaXhDbGllbnRQZWcuc2FmZUdldCgpLmdldERldmljZUlkKCk7XG4gICAgICAgICAgICBpZiAoY2xpZW50RGV2aWNlSWQgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAvLyBGb3IgdGhlIHNlc3Npb24gbWVtYmVyc2hpcCB0eXBlIGNvbXBsaWFudCB3aXRoIE1TQzQxNDNcbiAgICAgICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKFxuICAgICAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yU3RhdGVFdmVudChcbiAgICAgICAgICAgICAgICAgICAgICAgIEV2ZW50RGlyZWN0aW9uLlNlbmQsXG4gICAgICAgICAgICAgICAgICAgICAgICBcIm9yZy5tYXRyaXgubXNjMzQwMS5jYWxsLm1lbWJlclwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgYF8ke2NsaWVudFVzZXJJZH1fJHtjbGllbnREZXZpY2VJZH1gLFxuICAgICAgICAgICAgICAgICAgICApLnJhdyxcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIC8vIFZlcnNpb24gd2l0aCBubyBsZWFkaW5nIHVuZGVyc2NvcmUsIGZvciByb29tIHZlcnNpb25zIHdob3NlIGF1dGggcnVsZXMgYWxsb3cgaXRcbiAgICAgICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKFxuICAgICAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yU3RhdGVFdmVudChcbiAgICAgICAgICAgICAgICAgICAgICAgIEV2ZW50RGlyZWN0aW9uLlNlbmQsXG4gICAgICAgICAgICAgICAgICAgICAgICBcIm9yZy5tYXRyaXgubXNjMzQwMS5jYWxsLm1lbWJlclwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgYCR7Y2xpZW50VXNlcklkfV8ke2NsaWVudERldmljZUlkfWAsXG4gICAgICAgICAgICAgICAgICAgICkucmF3LFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKFxuICAgICAgICAgICAgICAgIFdpZGdldEV2ZW50Q2FwYWJpbGl0eS5mb3JTdGF0ZUV2ZW50KEV2ZW50RGlyZWN0aW9uLlJlY2VpdmUsIFwib3JnLm1hdHJpeC5tc2MzNDAxLmNhbGwubWVtYmVyXCIpLnJhdyxcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICAvLyBmb3IgZGV0ZXJtaW5pbmcgYXV0aCBydWxlcyBzcGVjaWZpYyB0byB0aGUgcm9vbSB2ZXJzaW9uXG4gICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKFxuICAgICAgICAgICAgICAgIFdpZGdldEV2ZW50Q2FwYWJpbGl0eS5mb3JTdGF0ZUV2ZW50KEV2ZW50RGlyZWN0aW9uLlJlY2VpdmUsIEV2ZW50VHlwZS5Sb29tQ3JlYXRlKS5yYXcsXG4gICAgICAgICAgICApO1xuXG4gICAgICAgICAgICBjb25zdCBzZW5kUmVjdlJvb21FdmVudHMgPSBbXCJpby5lbGVtZW50LmNhbGwuZW5jcnlwdGlvbl9rZXlzXCIsIEV2ZW50VHlwZS5SZWFjdGlvbiwgRXZlbnRUeXBlLlJvb21SZWRhY3Rpb25dO1xuICAgICAgICAgICAgZm9yIChjb25zdCBldmVudFR5cGUgb2Ygc2VuZFJlY3ZSb29tRXZlbnRzKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzLmFkZChXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yUm9vbUV2ZW50KEV2ZW50RGlyZWN0aW9uLlNlbmQsIGV2ZW50VHlwZSkucmF3KTtcbiAgICAgICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKFdpZGdldEV2ZW50Q2FwYWJpbGl0eS5mb3JSb29tRXZlbnQoRXZlbnREaXJlY3Rpb24uUmVjZWl2ZSwgZXZlbnRUeXBlKS5yYXcpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBzZW5kUmVjdlRvRGV2aWNlID0gW1xuICAgICAgICAgICAgICAgIEV2ZW50VHlwZS5DYWxsSW52aXRlLFxuICAgICAgICAgICAgICAgIEV2ZW50VHlwZS5DYWxsQ2FuZGlkYXRlcyxcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbEFuc3dlcixcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbEhhbmd1cCxcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbFJlamVjdCxcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbFNlbGVjdEFuc3dlcixcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbE5lZ290aWF0ZSxcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbFNEUFN0cmVhbU1ldGFkYXRhQ2hhbmdlZCxcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbFNEUFN0cmVhbU1ldGFkYXRhQ2hhbmdlZFByZWZpeCxcbiAgICAgICAgICAgICAgICBFdmVudFR5cGUuQ2FsbFJlcGxhY2VzLFxuICAgICAgICAgICAgXTtcbiAgICAgICAgICAgIGZvciAoY29uc3QgZXZlbnRUeXBlIG9mIHNlbmRSZWN2VG9EZXZpY2UpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMuYWRkKFxuICAgICAgICAgICAgICAgICAgICBXaWRnZXRFdmVudENhcGFiaWxpdHkuZm9yVG9EZXZpY2VFdmVudChFdmVudERpcmVjdGlvbi5TZW5kLCBldmVudFR5cGUpLnJhdyxcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIHRoaXMuYWxsb3dlZENhcGFiaWxpdGllcy5hZGQoXG4gICAgICAgICAgICAgICAgICAgIFdpZGdldEV2ZW50Q2FwYWJpbGl0eS5mb3JUb0RldmljZUV2ZW50KEV2ZW50RGlyZWN0aW9uLlJlY2VpdmUsIGV2ZW50VHlwZSkucmF3LFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIFRvIGFsd2F5cyBhbGxvdyBPSURDIHJlcXVlc3RzIGZvciBlbGVtZW50IGNhbGwsIHRoZSB3aWRnZXRQZXJtaXNzaW9uU3RvcmUgaXMgdXNlZDpcbiAgICAgICAgICAgIFNka0NvbnRleHRDbGFzcy5pbnN0YW5jZS53aWRnZXRQZXJtaXNzaW9uU3RvcmUuc2V0T0lEQ1N0YXRlKFxuICAgICAgICAgICAgICAgIGZvcldpZGdldCxcbiAgICAgICAgICAgICAgICBmb3JXaWRnZXRLaW5kLFxuICAgICAgICAgICAgICAgIGluUm9vbUlkLFxuICAgICAgICAgICAgICAgIE9JRENTdGF0ZS5BbGxvd2VkLFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHB1YmxpYyBhc3luYyB2YWxpZGF0ZUNhcGFiaWxpdGllcyhyZXF1ZXN0ZWQ6IFNldDxDYXBhYmlsaXR5Pik6IFByb21pc2U8U2V0PENhcGFiaWxpdHk+PiB7XG4gICAgICAgIC8vIENoZWNrIHRvIHNlZSBpZiBhbnkgY2FwYWJpbGl0aWVzIGFyZW4ndCBhdXRvbWF0aWNhbGx5IGFjY2VwdGVkIChzdWNoIGFzIHN0aWNrZXIgcGlja2Vyc1xuICAgICAgICAvLyBhbGxvd2luZyBzdGlja2VycyB0byBiZSBzZW50KS4gSWYgdGhlcmUgYXJlIGV4Y2VzcyBjYXBhYmlsaXRpZXMgdG8gYmUgYXBwcm92ZWQsIHRoZSB1c2VyXG4gICAgICAgIC8vIHdpbGwgYmUgcHJvbXB0ZWQgdG8gYWNjZXB0IHRoZW0uXG4gICAgICAgIGNvbnN0IGRpZmYgPSBpdGVyYWJsZURpZmYocmVxdWVzdGVkLCB0aGlzLmFsbG93ZWRDYXBhYmlsaXRpZXMpO1xuICAgICAgICBjb25zdCBtaXNzaW5nID0gbmV3IFNldChkaWZmLnJlbW92ZWQpOyAvLyBcInJlbW92ZWRcIiBpcyBcImluIEEgKHJlcXVlc3RlZCkgYnV0IG5vdCBpbiBCIChhbGxvd2VkKVwiXG4gICAgICAgIGNvbnN0IGFsbG93ZWRTb0ZhciA9IG5ldyBTZXQodGhpcy5hbGxvd2VkQ2FwYWJpbGl0aWVzKTtcbiAgICAgICAgZ2V0UmVtZW1iZXJlZENhcGFiaWxpdGllc0ZvcldpZGdldCh0aGlzLmZvcldpZGdldCkuZm9yRWFjaCgoY2FwKSA9PiB7XG4gICAgICAgICAgICBhbGxvd2VkU29GYXIuYWRkKGNhcCk7XG4gICAgICAgICAgICBtaXNzaW5nLmRlbGV0ZShjYXApO1xuICAgICAgICB9KTtcblxuICAgICAgICBsZXQgYXBwcm92ZWQ6IFNldDxzdHJpbmc+IHwgdW5kZWZpbmVkO1xuICAgICAgICBpZiAoV2lkZ2V0UGVybWlzc2lvbkN1c3RvbWlzYXRpb25zLnByZWFwcHJvdmVDYXBhYmlsaXRpZXMpIHtcbiAgICAgICAgICAgIGFwcHJvdmVkID0gYXdhaXQgV2lkZ2V0UGVybWlzc2lvbkN1c3RvbWlzYXRpb25zLnByZWFwcHJvdmVDYXBhYmlsaXRpZXModGhpcy5mb3JXaWRnZXQsIHJlcXVlc3RlZCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBjb25zdCBvcHRzOiBDYXBhYmlsaXRpZXNPcHRzID0geyBhcHByb3ZlZENhcGFiaWxpdGllczogdW5kZWZpbmVkIH07XG4gICAgICAgICAgICBNb2R1bGVSdW5uZXIuaW5zdGFuY2UuaW52b2tlKFdpZGdldExpZmVjeWNsZS5DYXBhYmlsaXRpZXNSZXF1ZXN0LCBvcHRzLCB0aGlzLmZvcldpZGdldCwgcmVxdWVzdGVkKTtcbiAgICAgICAgICAgIGFwcHJvdmVkID0gb3B0cy5hcHByb3ZlZENhcGFiaWxpdGllcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoYXBwcm92ZWQpIHtcbiAgICAgICAgICAgIGFwcHJvdmVkLmZvckVhY2goKGNhcCkgPT4ge1xuICAgICAgICAgICAgICAgIGFsbG93ZWRTb0Zhci5hZGQoY2FwKTtcbiAgICAgICAgICAgICAgICBtaXNzaW5nLmRlbGV0ZShjYXApO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUT0RPOiBEbyBzb21ldGhpbmcgd2hlbiB0aGUgd2lkZ2V0IHJlcXVlc3RzIG5ldyBjYXBhYmlsaXRpZXMgbm90IHlldCBhc2tlZCBmb3JcbiAgICAgICAgbGV0IHJlbWVtYmVyQXBwcm92ZWQgPSBmYWxzZTtcbiAgICAgICAgaWYgKG1pc3Npbmcuc2l6ZSA+IDApIHtcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgY29uc3QgW3Jlc3VsdF0gPSBhd2FpdCBNb2RhbC5jcmVhdGVEaWFsb2coV2lkZ2V0Q2FwYWJpbGl0aWVzUHJvbXB0RGlhbG9nLCB7XG4gICAgICAgICAgICAgICAgICAgIHJlcXVlc3RlZENhcGFiaWxpdGllczogbWlzc2luZyxcbiAgICAgICAgICAgICAgICAgICAgd2lkZ2V0OiB0aGlzLmZvcldpZGdldCxcbiAgICAgICAgICAgICAgICAgICAgd2lkZ2V0S2luZDogdGhpcy5mb3JXaWRnZXRLaW5kLFxuICAgICAgICAgICAgICAgIH0pLmZpbmlzaGVkO1xuICAgICAgICAgICAgICAgIHJlc3VsdD8uYXBwcm92ZWQ/LmZvckVhY2goKGNhcCkgPT4gYWxsb3dlZFNvRmFyLmFkZChjYXApKTtcbiAgICAgICAgICAgICAgICByZW1lbWJlckFwcHJvdmVkID0gISFyZXN1bHQ/LnJlbWVtYmVyO1xuICAgICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgICAgIGxvZ2dlci5lcnJvcihcIk5vbi1mYXRhbCBlcnJvciBnZXR0aW5nIGNhcGFiaWxpdGllczogXCIsIGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gZGlzY2FyZCBhbGwgcHJldmlvdXNseSBhbGxvd2VkIGNhcGFiaWxpdGllcyBpZiB0aGV5IGFyZSBub3QgcmVxdWVzdGVkXG4gICAgICAgIC8vIFRPRE86IHRoaXMgcmVzdWx0cyBpbiBhbiB1bmV4cGVjdGVkIGJlaGF2aW9yIHdoZW4gdGhpcyBmdW5jdGlvbiBpcyBjYWxsZWQgZHVyaW5nIHRoZSBjYXBhYmlsaXRpZXMgcmVuZWdvdGlhdGlvbiBvZiBNU0MyOTc0IHRoYXQgd2lsbCBiZSByZXNvbHZlZCBsYXRlci5cbiAgICAgICAgY29uc3QgYWxsQWxsb3dlZCA9IG5ldyBTZXQoaXRlcmFibGVJbnRlcnNlY3Rpb24oYWxsb3dlZFNvRmFyLCByZXF1ZXN0ZWQpKTtcblxuICAgICAgICBpZiAocmVtZW1iZXJBcHByb3ZlZCkge1xuICAgICAgICAgICAgc2V0UmVtZW1iZXJlZENhcGFiaWxpdGllc0ZvcldpZGdldCh0aGlzLmZvcldpZGdldCwgQXJyYXkuZnJvbShhbGxBbGxvd2VkKSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gYWxsQWxsb3dlZDtcbiAgICB9XG5cbiAgICBwdWJsaWMgYXN5bmMgc2VuZEV2ZW50PEsgZXh0ZW5kcyBrZXlvZiBTdGF0ZUV2ZW50cz4oXG4gICAgICAgIGV2ZW50VHlwZTogSyxcbiAgICAgICAgY29udGVudDogU3RhdGVFdmVudHNbS10sXG4gICAgICAgIHN0YXRlS2V5OiBzdHJpbmcgfCBudWxsLFxuICAgICAgICB0YXJnZXRSb29tSWQ6IHN0cmluZyB8IG51bGwsXG4gICAgKTogUHJvbWlzZTxJU2VuZEV2ZW50RGV0YWlscz47XG4gICAgcHVibGljIGFzeW5jIHNlbmRFdmVudDxLIGV4dGVuZHMga2V5b2YgVGltZWxpbmVFdmVudHM+KFxuICAgICAgICBldmVudFR5cGU6IEssXG4gICAgICAgIGNvbnRlbnQ6IFRpbWVsaW5lRXZlbnRzW0tdLFxuICAgICAgICBzdGF0ZUtleTogbnVsbCxcbiAgICAgICAgdGFyZ2V0Um9vbUlkOiBzdHJpbmcgfCBudWxsLFxuICAgICk6IFByb21pc2U8SVNlbmRFdmVudERldGFpbHM+O1xuICAgIHB1YmxpYyBhc3luYyBzZW5kRXZlbnQoXG4gICAgICAgIGV2ZW50VHlwZTogc3RyaW5nLFxuICAgICAgICBjb250ZW50OiBJQ29udGVudCxcbiAgICAgICAgc3RhdGVLZXk6IHN0cmluZyB8IG51bGwgPSBudWxsLFxuICAgICAgICB0YXJnZXRSb29tSWQ6IHN0cmluZyB8IG51bGwgPSBudWxsLFxuICAgICk6IFByb21pc2U8SVNlbmRFdmVudERldGFpbHM+IHtcbiAgICAgICAgY29uc3QgY2xpZW50ID0gTWF0cml4Q2xpZW50UGVnLmdldCgpO1xuICAgICAgICBjb25zdCByb29tSWQgPSB0YXJnZXRSb29tSWQgfHwgU2RrQ29udGV4dENsYXNzLmluc3RhbmNlLnJvb21WaWV3U3RvcmUuZ2V0Um9vbUlkKCk7XG5cbiAgICAgICAgaWYgKCFjbGllbnQgfHwgIXJvb21JZCkgdGhyb3cgbmV3IEVycm9yKFwiTm90IGluIGEgcm9vbSBvciBub3QgYXR0YWNoZWQgdG8gYSBjbGllbnRcIik7XG5cbiAgICAgICAgbGV0IHI6IHsgZXZlbnRfaWQ6IHN0cmluZyB9IHwgbnVsbDtcbiAgICAgICAgaWYgKHN0YXRlS2V5ICE9PSBudWxsKSB7XG4gICAgICAgICAgICAvLyBzdGF0ZSBldmVudFxuICAgICAgICAgICAgciA9IGF3YWl0IGNsaWVudC5zZW5kU3RhdGVFdmVudChcbiAgICAgICAgICAgICAgICByb29tSWQsXG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlIGFzIGtleW9mIFN0YXRlRXZlbnRzLFxuICAgICAgICAgICAgICAgIGNvbnRlbnQgYXMgU3RhdGVFdmVudHNba2V5b2YgU3RhdGVFdmVudHNdLFxuICAgICAgICAgICAgICAgIHN0YXRlS2V5LFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfSBlbHNlIGlmIChldmVudFR5cGUgPT09IEV2ZW50VHlwZS5Sb29tUmVkYWN0aW9uKSB7XG4gICAgICAgICAgICAvLyBzcGVjaWFsIGNhc2U6IGV4dHJhY3QgdGhlIGByZWRhY3RzYCBwcm9wZXJ0eSBhbmQgY2FsbCByZWRhY3RcbiAgICAgICAgICAgIHIgPSBhd2FpdCBjbGllbnQucmVkYWN0RXZlbnQocm9vbUlkLCBjb250ZW50W1wicmVkYWN0c1wiXSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBtZXNzYWdlIGV2ZW50XG4gICAgICAgICAgICByID0gYXdhaXQgY2xpZW50LnNlbmRFdmVudChcbiAgICAgICAgICAgICAgICByb29tSWQsXG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlIGFzIGtleW9mIFRpbWVsaW5lRXZlbnRzLFxuICAgICAgICAgICAgICAgIGNvbnRlbnQgYXMgVGltZWxpbmVFdmVudHNba2V5b2YgVGltZWxpbmVFdmVudHNdLFxuICAgICAgICAgICAgKTtcblxuICAgICAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gRXZlbnRUeXBlLlJvb21NZXNzYWdlKSB7XG4gICAgICAgICAgICAgICAgQ0hBVF9FRkZFQ1RTLmZvckVhY2goKGVmZmVjdCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpZiAoY29udGFpbnNFbW9qaShjb250ZW50LCBlZmZlY3QuZW1vamlzKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gRm9yIGluaXRpYWwgdGhyZWFkcyBsYXVuY2gsIGNoYXQgZWZmZWN0cyBhcmUgZGlzYWJsZWRcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHNlZSAjMTk3MzFcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGlzTm90VGhyZWFkID0gY29udGVudFtcIm0ucmVsYXRlc190b1wiXT8ucmVsX3R5cGUgIT09IFRIUkVBRF9SRUxBVElPTl9UWVBFLm5hbWU7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoaXNOb3RUaHJlYWQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXMuZGlzcGF0Y2goeyBhY3Rpb246IGBlZmZlY3RzLiR7ZWZmZWN0LmNvbW1hbmR9YCB9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgI