matrix-react-sdk
Version:
SDK for matrix.org using React
486 lines (470 loc) • 94.5 kB
JavaScript
"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