matrix-react-sdk
Version:
SDK for matrix.org using React
424 lines (407 loc) • 71.9 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _matrix = require("matrix-js-sdk/src/matrix");
var _logger = require("matrix-js-sdk/src/logger");
var _crypto = require("matrix-js-sdk/src/crypto");
var _PosthogAnalytics = require("./PosthogAnalytics");
var _dispatcher = _interopRequireDefault(require("./dispatcher/dispatcher"));
var _BulkUnverifiedSessionsToast = require("./toasts/BulkUnverifiedSessionsToast");
var _SetupEncryptionToast = require("./toasts/SetupEncryptionToast");
var _UnverifiedSessionToast = require("./toasts/UnverifiedSessionToast");
var _SecurityManager = require("./SecurityManager");
var _WellKnownUtils = require("./utils/WellKnownUtils");
var _actions = require("./dispatcher/actions");
var _login = require("./utils/login");
var _SdkConfig = _interopRequireDefault(require("./SdkConfig"));
var _PlatformPeg = _interopRequireDefault(require("./PlatformPeg"));
var _clientInformation = require("./utils/device/clientInformation");
var _SettingsStore = _interopRequireDefault(require("./settings/SettingsStore"));
var _UIFeature = require("./settings/UIFeature");
var _snoozeBulkUnverifiedDeviceReminder = require("./utils/device/snoozeBulkUnverifiedDeviceReminder");
var _deviceInfo = require("./utils/crypto/deviceInfo");
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 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.
*/
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
class DeviceListener {
constructor() {
(0, _defineProperty2.default)(this, "dispatcherRef", void 0);
// device IDs for which the user has dismissed the verify toast ('Later')
(0, _defineProperty2.default)(this, "dismissed", new Set());
// has the user dismissed any of the various nag toasts to setup encryption on this device?
(0, _defineProperty2.default)(this, "dismissedThisDeviceToast", false);
/** Cache of the info about the current key backup on the server. */
(0, _defineProperty2.default)(this, "keyBackupInfo", null);
/** When `keyBackupInfo` was last updated */
(0, _defineProperty2.default)(this, "keyBackupFetchedAt", null);
// We keep a list of our own device IDs so we can batch ones that were already
// there the last time the app launched into a single toast, but display new
// ones in their own toasts.
(0, _defineProperty2.default)(this, "ourDeviceIdsAtStart", null);
// The set of device IDs we're currently displaying toasts for
(0, _defineProperty2.default)(this, "displayingToastsForDeviceIds", new Set());
(0, _defineProperty2.default)(this, "running", false);
// The client with which the instance is running. Only set if `running` is true, otherwise undefined.
(0, _defineProperty2.default)(this, "client", void 0);
(0, _defineProperty2.default)(this, "shouldRecordClientInformation", false);
(0, _defineProperty2.default)(this, "enableBulkUnverifiedSessionsReminder", true);
(0, _defineProperty2.default)(this, "deviceClientInformationSettingWatcherRef", void 0);
// Remember the current analytics state to avoid sending the same event multiple times.
(0, _defineProperty2.default)(this, "analyticsVerificationState", void 0);
(0, _defineProperty2.default)(this, "analyticsRecoveryState", void 0);
(0, _defineProperty2.default)(this, "onDevicesUpdated", async (users, initialFetch) => {
if (!this.client) return;
// If we didn't know about *any* devices before (ie. it's fresh login),
// then they are all pre-existing devices, so ignore this and set the
// devicesAtStart list to the devices that we see after the fetch.
if (initialFetch) return;
const myUserId = this.client.getSafeUserId();
if (users.includes(myUserId)) await this.ensureDeviceIdsAtStartPopulated();
this.recheck();
});
(0, _defineProperty2.default)(this, "onUserTrustStatusChanged", userId => {
if (!this.client) return;
if (userId !== this.client.getUserId()) return;
this.recheck();
});
(0, _defineProperty2.default)(this, "onCrossSingingKeysChanged", () => {
this.recheck();
});
(0, _defineProperty2.default)(this, "onAccountData", ev => {
// User may have:
// * migrated SSSS to symmetric
// * uploaded keys to secret storage
// * completed secret storage creation
// which result in account data changes affecting checks below.
if (ev.getType().startsWith("m.secret_storage.") || ev.getType().startsWith("m.cross_signing.") || ev.getType() === "m.megolm_backup.v1") {
this.recheck();
}
});
(0, _defineProperty2.default)(this, "onSync", (state, prevState) => {
if (state === "PREPARED" && prevState === null) {
this.recheck();
}
});
(0, _defineProperty2.default)(this, "onRoomStateEvents", ev => {
if (ev.getType() !== _matrix.EventType.RoomEncryption) return;
// If a room changes to encrypted, re-check as it may be our first
// encrypted room. This also catches encrypted room creation as well.
this.recheck();
});
(0, _defineProperty2.default)(this, "onAction", ({
action
}) => {
if (action !== _actions.Action.OnLoggedIn) return;
this.recheck();
this.updateClientInformation();
});
/**
* Check if key backup is enabled, and if not, raise an `Action.ReportKeyBackupNotEnabled` event (which will
* trigger an auto-rageshake).
*/
(0, _defineProperty2.default)(this, "checkKeyBackupStatus", async () => {
if (this.keyBackupStatusChecked || !this.client) {
return;
}
const activeKeyBackupVersion = await this.client.getCrypto()?.getActiveSessionBackupVersion();
// if key backup is enabled, no need to check this ever again (XXX: why only when it is enabled?)
this.keyBackupStatusChecked = !!activeKeyBackupVersion;
if (!activeKeyBackupVersion) {
_dispatcher.default.dispatch({
action: _actions.Action.ReportKeyBackupNotEnabled
});
}
});
(0, _defineProperty2.default)(this, "keyBackupStatusChecked", false);
(0, _defineProperty2.default)(this, "onRecordClientInformationSettingChange", (_originalSettingName, _roomId, _level, _newLevel, newValue) => {
const prevValue = this.shouldRecordClientInformation;
this.shouldRecordClientInformation = !!newValue;
if (this.shouldRecordClientInformation !== prevValue) {
this.updateClientInformation();
}
});
(0, _defineProperty2.default)(this, "updateClientInformation", async () => {
if (!this.client) return;
try {
if (this.shouldRecordClientInformation) {
await (0, _clientInformation.recordClientInformation)(this.client, _SdkConfig.default.get(), _PlatformPeg.default.get() ?? undefined);
} else {
await (0, _clientInformation.removeClientInformation)(this.client);
}
} catch (error) {
// this is a best effort operation
// log the error without rethrowing
_logger.logger.error("Failed to update client information", error);
}
});
}
static sharedInstance() {
if (!window.mxDeviceListener) window.mxDeviceListener = new DeviceListener();
return window.mxDeviceListener;
}
start(matrixClient) {
this.running = true;
this.client = matrixClient;
this.client.on(_crypto.CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
this.client.on(_crypto.CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
this.client.on(_crypto.CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged);
this.client.on(_matrix.ClientEvent.AccountData, this.onAccountData);
this.client.on(_matrix.ClientEvent.Sync, this.onSync);
this.client.on(_matrix.RoomStateEvent.Events, this.onRoomStateEvents);
this.shouldRecordClientInformation = _SettingsStore.default.getValue("deviceClientInformationOptIn");
// only configurable in config, so we don't need to watch the value
this.enableBulkUnverifiedSessionsReminder = _SettingsStore.default.getValue(_UIFeature.UIFeature.BulkUnverifiedSessionsReminder);
this.deviceClientInformationSettingWatcherRef = _SettingsStore.default.watchSetting("deviceClientInformationOptIn", null, this.onRecordClientInformationSettingChange);
this.dispatcherRef = _dispatcher.default.register(this.onAction);
this.recheck();
this.updateClientInformation();
}
stop() {
this.running = false;
if (this.client) {
this.client.removeListener(_crypto.CryptoEvent.DevicesUpdated, this.onDevicesUpdated);
this.client.removeListener(_crypto.CryptoEvent.UserTrustStatusChanged, this.onUserTrustStatusChanged);
this.client.removeListener(_crypto.CryptoEvent.KeysChanged, this.onCrossSingingKeysChanged);
this.client.removeListener(_matrix.ClientEvent.AccountData, this.onAccountData);
this.client.removeListener(_matrix.ClientEvent.Sync, this.onSync);
this.client.removeListener(_matrix.RoomStateEvent.Events, this.onRoomStateEvents);
}
if (this.deviceClientInformationSettingWatcherRef) {
_SettingsStore.default.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
}
if (this.dispatcherRef) {
_dispatcher.default.unregister(this.dispatcherRef);
this.dispatcherRef = undefined;
}
this.dismissed.clear();
this.dismissedThisDeviceToast = false;
this.keyBackupInfo = null;
this.keyBackupFetchedAt = null;
this.keyBackupStatusChecked = false;
this.ourDeviceIdsAtStart = null;
this.displayingToastsForDeviceIds = new Set();
this.client = undefined;
}
/**
* Dismiss notifications about our own unverified devices
*
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
*/
async dismissUnverifiedSessions(deviceIds) {
_logger.logger.log("Dismissing unverified sessions: " + Array.from(deviceIds).join(","));
for (const d of deviceIds) {
this.dismissed.add(d);
}
this.recheck();
}
dismissEncryptionSetup() {
this.dismissedThisDeviceToast = true;
this.recheck();
}
async ensureDeviceIdsAtStartPopulated() {
if (this.ourDeviceIdsAtStart === null) {
this.ourDeviceIdsAtStart = await this.getDeviceIds();
}
}
/** Get the device list for the current user
*
* @returns the set of device IDs
*/
async getDeviceIds() {
const cli = this.client;
if (!cli) return new Set();
return await (0, _deviceInfo.getUserDeviceIds)(cli, cli.getSafeUserId());
}
/**
* Fetch the key backup information from the server.
*
* The result is cached for `KEY_BACKUP_POLL_INTERVAL` ms to avoid repeated API calls.
*
* @returns The key backup info from the server, or `null` if there is no key backup.
*/
async getKeyBackupInfo() {
if (!this.client) return null;
const now = new Date().getTime();
if (!this.keyBackupInfo || !this.keyBackupFetchedAt || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
this.keyBackupInfo = await this.client.getKeyBackupVersion();
this.keyBackupFetchedAt = now;
}
return this.keyBackupInfo;
}
shouldShowSetupEncryptionToast() {
// If we're in the middle of a secret storage operation, we're likely
// modifying the state involved here, so don't add new toasts to setup.
if ((0, _SecurityManager.isSecretStorageBeingAccessed)()) return false;
// Show setup toasts once the user is in at least one encrypted room.
const cli = this.client;
return cli?.getRooms().some(r => cli.isRoomEncrypted(r.roomId)) ?? false;
}
recheck() {
this.doRecheck().catch(e => {
if (e instanceof _matrix.ClientStoppedError) {
// the client was stopped while recheck() was running. Nothing left to do.
} else {
_logger.logger.error("Error during `DeviceListener.recheck`", e);
}
});
}
async doRecheck() {
if (!this.running || !this.client) return; // we have been stopped
const cli = this.client;
// cross-signing support was added to Matrix in MSC1756, which landed in spec v1.1
if (!(await cli.isVersionSupported("v1.1"))) return;
const crypto = cli.getCrypto();
if (!crypto) return;
// don't recheck until the initial sync is complete: lots of account data events will fire
// while the initial sync is processing and we don't need to recheck on each one of them
// (we add a listener on sync to do once check after the initial sync is done)
if (!cli.isInitialSyncComplete()) return;
const crossSigningReady = await crypto.isCrossSigningReady();
const secretStorageReady = await crypto.isSecretStorageReady();
const allSystemsReady = crossSigningReady && secretStorageReady;
await this.reportCryptoSessionStateToAnalytics(cli);
if (this.dismissedThisDeviceToast || allSystemsReady) {
(0, _SetupEncryptionToast.hideToast)();
this.checkKeyBackupStatus();
} else if (this.shouldShowSetupEncryptionToast()) {
// make sure our keys are finished downloading
await crypto.getUserDeviceInfo([cli.getSafeUserId()]);
// cross signing isn't enabled - nag to enable it
// There are 3 different toasts for:
if (!(await crypto.getCrossSigningKeyId()) && (await crypto.userHasCrossSigningKeys())) {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
(0, _SetupEncryptionToast.showToast)(_SetupEncryptionToast.Kind.VERIFY_THIS_SESSION);
this.checkKeyBackupStatus();
} else {
const backupInfo = await this.getKeyBackupInfo();
if (backupInfo) {
// No cross-signing on account but key backup available (upgrade encryption)
(0, _SetupEncryptionToast.showToast)(_SetupEncryptionToast.Kind.UPGRADE_ENCRYPTION);
} else {
// No cross-signing or key backup on account (set up encryption)
await cli.waitForClientWellKnown();
if ((0, _WellKnownUtils.isSecureBackupRequired)(cli) && (0, _login.isLoggedIn)()) {
// If we're meant to set up, and Secure Backup is required,
// trigger the flow directly without a toast once logged in.
(0, _SetupEncryptionToast.hideToast)();
(0, _SecurityManager.accessSecretStorage)();
} else {
(0, _SetupEncryptionToast.showToast)(_SetupEncryptionToast.Kind.SET_UP_ENCRYPTION);
}
}
}
}
// This needs to be done after awaiting on getUserDeviceInfo() above, so
// we make sure we get the devices after the fetch is done.
await this.ensureDeviceIdsAtStartPopulated();
// Unverified devices that were there last time the app ran
// (technically could just be a boolean: we don't actually
// need to remember the device IDs, but for the sake of
// symmetry...).
const oldUnverifiedDeviceIds = new Set();
// Unverified devices that have appeared since then
const newUnverifiedDeviceIds = new Set();
const isCurrentDeviceTrusted = crossSigningReady && Boolean((await crypto.getDeviceVerificationStatus(cli.getSafeUserId(), cli.deviceId))?.crossSigningVerified);
// as long as cross-signing isn't ready,
// you can't see or dismiss any device toasts
if (crossSigningReady) {
const devices = await this.getDeviceIds();
for (const deviceId of devices) {
if (deviceId === cli.deviceId) continue;
const deviceTrust = await crypto.getDeviceVerificationStatus(cli.getSafeUserId(), deviceId);
if (!deviceTrust?.crossSigningVerified && !this.dismissed.has(deviceId)) {
if (this.ourDeviceIdsAtStart?.has(deviceId)) {
oldUnverifiedDeviceIds.add(deviceId);
} else {
newUnverifiedDeviceIds.add(deviceId);
}
}
}
}
_logger.logger.debug("Old unverified sessions: " + Array.from(oldUnverifiedDeviceIds).join(","));
_logger.logger.debug("New unverified sessions: " + Array.from(newUnverifiedDeviceIds).join(","));
_logger.logger.debug("Currently showing toasts for: " + Array.from(this.displayingToastsForDeviceIds).join(","));
const isBulkUnverifiedSessionsReminderSnoozed = (0, _snoozeBulkUnverifiedDeviceReminder.isBulkUnverifiedDeviceReminderSnoozed)();
// Display or hide the batch toast for old unverified sessions
// don't show the toast if the current device is unverified
if (oldUnverifiedDeviceIds.size > 0 && isCurrentDeviceTrusted && this.enableBulkUnverifiedSessionsReminder && !isBulkUnverifiedSessionsReminderSnoozed) {
(0, _BulkUnverifiedSessionsToast.showToast)(oldUnverifiedDeviceIds);
} else {
(0, _BulkUnverifiedSessionsToast.hideToast)();
}
// Show toasts for new unverified devices if they aren't already there
for (const deviceId of newUnverifiedDeviceIds) {
(0, _UnverifiedSessionToast.showToast)(deviceId);
}
// ...and hide any we don't need any more
for (const deviceId of this.displayingToastsForDeviceIds) {
if (!newUnverifiedDeviceIds.has(deviceId)) {
_logger.logger.debug("Hiding unverified session toast for " + deviceId);
(0, _UnverifiedSessionToast.hideToast)(deviceId);
}
}
this.displayingToastsForDeviceIds = newUnverifiedDeviceIds;
}
/**
* Reports current recovery state to analytics.
* Checks if the session is verified and if the recovery is correctly set up (i.e all secrets known locally and in 4S).
* @param cli - the matrix client
* @private
*/
async reportCryptoSessionStateToAnalytics(cli) {
const crypto = cli.getCrypto();
const secretStorageReady = await crypto.isSecretStorageReady();
const crossSigningStatus = await crypto.getCrossSigningStatus();
const backupInfo = await this.getKeyBackupInfo();
const is4SEnabled = (await cli.secretStorage.getDefaultKeyId()) != null;
const deviceVerificationStatus = await crypto.getDeviceVerificationStatus(cli.getUserId(), cli.getDeviceId());
const verificationState = deviceVerificationStatus?.signedByOwner && deviceVerificationStatus?.crossSigningVerified ? "Verified" : "NotVerified";
let recoveryState;
if (!is4SEnabled) {
recoveryState = "Disabled";
} else {
const allCrossSigningSecretsCached = crossSigningStatus.privateKeysCachedLocally.masterKey && crossSigningStatus.privateKeysCachedLocally.selfSigningKey && crossSigningStatus.privateKeysCachedLocally.userSigningKey;
if (backupInfo != null) {
// There is a backup. Check that all secrets are stored in 4S and known locally.
// If they are not, recovery is incomplete.
const backupPrivateKeyIsInCache = (await crypto.getSessionBackupPrivateKey()) != null;
if (secretStorageReady && allCrossSigningSecretsCached && backupPrivateKeyIsInCache) {
recoveryState = "Enabled";
} else {
recoveryState = "Incomplete";
}
} else {
// No backup. Just consider cross-signing secrets.
if (secretStorageReady && allCrossSigningSecretsCached) {
recoveryState = "Enabled";
} else {
recoveryState = "Incomplete";
}
}
}
if (this.analyticsVerificationState === verificationState && this.analyticsRecoveryState === recoveryState) {
// No changes, no need to send the event nor update the user properties
return;
}
this.analyticsRecoveryState = recoveryState;
this.analyticsVerificationState = verificationState;
// Update user properties
_PosthogAnalytics.PosthogAnalytics.instance.setProperty("recoveryState", recoveryState);
_PosthogAnalytics.PosthogAnalytics.instance.setProperty("verificationState", verificationState);
_PosthogAnalytics.PosthogAnalytics.instance.trackEvent({
eventName: "CryptoSessionState",
verificationState: verificationState,
recoveryState: recoveryState
});
}
}
exports.default = DeviceListener;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbWF0cml4IiwicmVxdWlyZSIsIl9sb2dnZXIiLCJfY3J5cHRvIiwiX1Bvc3Rob2dBbmFseXRpY3MiLCJfZGlzcGF0Y2hlciIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfQnVsa1VudmVyaWZpZWRTZXNzaW9uc1RvYXN0IiwiX1NldHVwRW5jcnlwdGlvblRvYXN0IiwiX1VudmVyaWZpZWRTZXNzaW9uVG9hc3QiLCJfU2VjdXJpdHlNYW5hZ2VyIiwiX1dlbGxLbm93blV0aWxzIiwiX2FjdGlvbnMiLCJfbG9naW4iLCJfU2RrQ29uZmlnIiwiX1BsYXRmb3JtUGVnIiwiX2NsaWVudEluZm9ybWF0aW9uIiwiX1NldHRpbmdzU3RvcmUiLCJfVUlGZWF0dXJlIiwiX3Nub296ZUJ1bGtVbnZlcmlmaWVkRGV2aWNlUmVtaW5kZXIiLCJfZGV2aWNlSW5mbyIsIktFWV9CQUNLVVBfUE9MTF9JTlRFUlZBTCIsIkRldmljZUxpc3RlbmVyIiwiY29uc3RydWN0b3IiLCJfZGVmaW5lUHJvcGVydHkyIiwiZGVmYXVsdCIsIlNldCIsInVzZXJzIiwiaW5pdGlhbEZldGNoIiwiY2xpZW50IiwibXlVc2VySWQiLCJnZXRTYWZlVXNlcklkIiwiaW5jbHVkZXMiLCJlbnN1cmVEZXZpY2VJZHNBdFN0YXJ0UG9wdWxhdGVkIiwicmVjaGVjayIsInVzZXJJZCIsImdldFVzZXJJZCIsImV2IiwiZ2V0VHlwZSIsInN0YXJ0c1dpdGgiLCJzdGF0ZSIsInByZXZTdGF0ZSIsIkV2ZW50VHlwZSIsIlJvb21FbmNyeXB0aW9uIiwiYWN0aW9uIiwiQWN0aW9uIiwiT25Mb2dnZWRJbiIsInVwZGF0ZUNsaWVudEluZm9ybWF0aW9uIiwia2V5QmFja3VwU3RhdHVzQ2hlY2tlZCIsImFjdGl2ZUtleUJhY2t1cFZlcnNpb24iLCJnZXRDcnlwdG8iLCJnZXRBY3RpdmVTZXNzaW9uQmFja3VwVmVyc2lvbiIsImRpcyIsImRpc3BhdGNoIiwiUmVwb3J0S2V5QmFja3VwTm90RW5hYmxlZCIsIl9vcmlnaW5hbFNldHRpbmdOYW1lIiwiX3Jvb21JZCIsIl9sZXZlbCIsIl9uZXdMZXZlbCIsIm5ld1ZhbHVlIiwicHJldlZhbHVlIiwic2hvdWxkUmVjb3JkQ2xpZW50SW5mb3JtYXRpb24iLCJyZWNvcmRDbGllbnRJbmZvcm1hdGlvbiIsIlNka0NvbmZpZyIsImdldCIsIlBsYXRmb3JtUGVnIiwidW5kZWZpbmVkIiwicmVtb3ZlQ2xpZW50SW5mb3JtYXRpb24iLCJlcnJvciIsImxvZ2dlciIsInNoYXJlZEluc3RhbmNlIiwid2luZG93IiwibXhEZXZpY2VMaXN0ZW5lciIsInN0YXJ0IiwibWF0cml4Q2xpZW50IiwicnVubmluZyIsIm9uIiwiQ3J5cHRvRXZlbnQiLCJEZXZpY2VzVXBkYXRlZCIsIm9uRGV2aWNlc1VwZGF0ZWQiLCJVc2VyVHJ1c3RTdGF0dXNDaGFuZ2VkIiwib25Vc2VyVHJ1c3RTdGF0dXNDaGFuZ2VkIiwiS2V5c0NoYW5nZWQiLCJvbkNyb3NzU2luZ2luZ0tleXNDaGFuZ2VkIiwiQ2xpZW50RXZlbnQiLCJBY2NvdW50RGF0YSIsIm9uQWNjb3VudERhdGEiLCJTeW5jIiwib25TeW5jIiwiUm9vbVN0YXRlRXZlbnQiLCJFdmVudHMiLCJvblJvb21TdGF0ZUV2ZW50cyIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsImVuYWJsZUJ1bGtVbnZlcmlmaWVkU2Vzc2lvbnNSZW1pbmRlciIsIlVJRmVhdHVyZSIsIkJ1bGtVbnZlcmlmaWVkU2Vzc2lvbnNSZW1pbmRlciIsImRldmljZUNsaWVudEluZm9ybWF0aW9uU2V0dGluZ1dhdGNoZXJSZWYiLCJ3YXRjaFNldHRpbmciLCJvblJlY29yZENsaWVudEluZm9ybWF0aW9uU2V0dGluZ0NoYW5nZSIsImRpc3BhdGNoZXJSZWYiLCJyZWdpc3RlciIsIm9uQWN0aW9uIiwic3RvcCIsInJlbW92ZUxpc3RlbmVyIiwidW53YXRjaFNldHRpbmciLCJ1bnJlZ2lzdGVyIiwiZGlzbWlzc2VkIiwiY2xlYXIiLCJkaXNtaXNzZWRUaGlzRGV2aWNlVG9hc3QiLCJrZXlCYWNrdXBJbmZvIiwia2V5QmFja3VwRmV0Y2hlZEF0Iiwib3VyRGV2aWNlSWRzQXRTdGFydCIsImRpc3BsYXlpbmdUb2FzdHNGb3JEZXZpY2VJZHMiLCJkaXNtaXNzVW52ZXJpZmllZFNlc3Npb25zIiwiZGV2aWNlSWRzIiwibG9nIiwiQXJyYXkiLCJmcm9tIiwiam9pbiIsImQiLCJhZGQiLCJkaXNtaXNzRW5jcnlwdGlvblNldHVwIiwiZ2V0RGV2aWNlSWRzIiwiY2xpIiwiZ2V0VXNlckRldmljZUlkcyIsImdldEtleUJhY2t1cEluZm8iLCJub3ciLCJEYXRlIiwiZ2V0VGltZSIsImdldEtleUJhY2t1cFZlcnNpb24iLCJzaG91bGRTaG93U2V0dXBFbmNyeXB0aW9uVG9hc3QiLCJpc1NlY3JldFN0b3JhZ2VCZWluZ0FjY2Vzc2VkIiwiZ2V0Um9vbXMiLCJzb21lIiwiciIsImlzUm9vbUVuY3J5cHRlZCIsInJvb21JZCIsImRvUmVjaGVjayIsImNhdGNoIiwiZSIsIkNsaWVudFN0b3BwZWRFcnJvciIsImlzVmVyc2lvblN1cHBvcnRlZCIsImNyeXB0byIsImlzSW5pdGlhbFN5bmNDb21wbGV0ZSIsImNyb3NzU2lnbmluZ1JlYWR5IiwiaXNDcm9zc1NpZ25pbmdSZWFkeSIsInNlY3JldFN0b3JhZ2VSZWFkeSIsImlzU2VjcmV0U3RvcmFnZVJlYWR5IiwiYWxsU3lzdGVtc1JlYWR5IiwicmVwb3J0Q3J5cHRvU2Vzc2lvblN0YXRlVG9BbmFseXRpY3MiLCJoaWRlU2V0dXBFbmNyeXB0aW9uVG9hc3QiLCJjaGVja0tleUJhY2t1cFN0YXR1cyIsImdldFVzZXJEZXZpY2VJbmZvIiwiZ2V0Q3Jvc3NTaWduaW5nS2V5SWQiLCJ1c2VySGFzQ3Jvc3NTaWduaW5nS2V5cyIsInNob3dTZXR1cEVuY3J5cHRpb25Ub2FzdCIsIlNldHVwS2luZCIsIlZFUklGWV9USElTX1NFU1NJT04iLCJiYWNrdXBJbmZvIiwiVVBHUkFERV9FTkNSWVBUSU9OIiwid2FpdEZvckNsaWVudFdlbGxLbm93biIsImlzU2VjdXJlQmFja3VwUmVxdWlyZWQiLCJpc0xvZ2dlZEluIiwiYWNjZXNzU2VjcmV0U3RvcmFnZSIsIlNFVF9VUF9FTkNSWVBUSU9OIiwib2xkVW52ZXJpZmllZERldmljZUlkcyIsIm5ld1VudmVyaWZpZWREZXZpY2VJZHMiLCJpc0N1cnJlbnREZXZpY2VUcnVzdGVkIiwiQm9vbGVhbiIsImdldERldmljZVZlcmlmaWNhdGlvblN0YXR1cyIsImRldmljZUlkIiwiY3Jvc3NTaWduaW5nVmVyaWZpZWQiLCJkZXZpY2VzIiwiZGV2aWNlVHJ1c3QiLCJoYXMiLCJkZWJ1ZyIsImlzQnVsa1VudmVyaWZpZWRTZXNzaW9uc1JlbWluZGVyU25vb3plZCIsImlzQnVsa1VudmVyaWZpZWREZXZpY2VSZW1pbmRlclNub296ZWQiLCJzaXplIiwic2hvd0J1bGtVbnZlcmlmaWVkU2Vzc2lvbnNUb2FzdCIsImhpZGVCdWxrVW52ZXJpZmllZFNlc3Npb25zVG9hc3QiLCJzaG93VW52ZXJpZmllZFNlc3Npb25zVG9hc3QiLCJoaWRlVW52ZXJpZmllZFNlc3Npb25zVG9hc3QiLCJjcm9zc1NpZ25pbmdTdGF0dXMiLCJnZXRDcm9zc1NpZ25pbmdTdGF0dXMiLCJpczRTRW5hYmxlZCIsInNlY3JldFN0b3JhZ2UiLCJnZXREZWZhdWx0S2V5SWQiLCJkZXZpY2VWZXJpZmljYXRpb25TdGF0dXMiLCJnZXREZXZpY2VJZCIsInZlcmlmaWNhdGlvblN0YXRlIiwic2lnbmVkQnlPd25lciIsInJlY292ZXJ5U3RhdGUiLCJhbGxDcm9zc1NpZ25pbmdTZWNyZXRzQ2FjaGVkIiwicHJpdmF0ZUtleXNDYWNoZWRMb2NhbGx5IiwibWFzdGVyS2V5Iiwic2VsZlNpZ25pbmdLZXkiLCJ1c2VyU2lnbmluZ0tleSIsImJhY2t1cFByaXZhdGVLZXlJc0luQ2FjaGUiLCJnZXRTZXNzaW9uQmFja3VwUHJpdmF0ZUtleSIsImFuYWx5dGljc1ZlcmlmaWNhdGlvblN0YXRlIiwiYW5hbHl0aWNzUmVjb3ZlcnlTdGF0ZSIsIlBvc3Rob2dBbmFseXRpY3MiLCJpbnN0YW5jZSIsInNldFByb3BlcnR5IiwidHJhY2tFdmVudCIsImV2ZW50TmFtZSIsImV4cG9ydHMiXSwic291cmNlcyI6WyIuLi9zcmMvRGV2aWNlTGlzdGVuZXIudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMjAgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IHtcbiAgICBNYXRyaXhFdmVudCxcbiAgICBDbGllbnRFdmVudCxcbiAgICBFdmVudFR5cGUsXG4gICAgTWF0cml4Q2xpZW50LFxuICAgIFJvb21TdGF0ZUV2ZW50LFxuICAgIFN5bmNTdGF0ZSxcbiAgICBDbGllbnRTdG9wcGVkRXJyb3IsXG59IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9sb2dnZXJcIjtcbmltcG9ydCB7IENyeXB0b0V2ZW50IH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL2NyeXB0b1wiO1xuaW1wb3J0IHsgS2V5QmFja3VwSW5mbyB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9jcnlwdG8tYXBpXCI7XG5pbXBvcnQgeyBDcnlwdG9TZXNzaW9uU3RhdGVDaGFuZ2UgfSBmcm9tIFwiQG1hdHJpeC1vcmcvYW5hbHl0aWNzLWV2ZW50cy90eXBlcy90eXBlc2NyaXB0L0NyeXB0b1Nlc3Npb25TdGF0ZUNoYW5nZVwiO1xuXG5pbXBvcnQgeyBQb3N0aG9nQW5hbHl0aWNzIH0gZnJvbSBcIi4vUG9zdGhvZ0FuYWx5dGljc1wiO1xuaW1wb3J0IGRpcyBmcm9tIFwiLi9kaXNwYXRjaGVyL2Rpc3BhdGNoZXJcIjtcbmltcG9ydCB7XG4gICAgaGlkZVRvYXN0IGFzIGhpZGVCdWxrVW52ZXJpZmllZFNlc3Npb25zVG9hc3QsXG4gICAgc2hvd1RvYXN0IGFzIHNob3dCdWxrVW52ZXJpZmllZFNlc3Npb25zVG9hc3QsXG59IGZyb20gXCIuL3RvYXN0cy9CdWxrVW52ZXJpZmllZFNlc3Npb25zVG9hc3RcIjtcbmltcG9ydCB7XG4gICAgaGlkZVRvYXN0IGFzIGhpZGVTZXR1cEVuY3J5cHRpb25Ub2FzdCxcbiAgICBLaW5kIGFzIFNldHVwS2luZCxcbiAgICBzaG93VG9hc3QgYXMgc2hvd1NldHVwRW5jcnlwdGlvblRvYXN0LFxufSBmcm9tIFwiLi90b2FzdHMvU2V0dXBFbmNyeXB0aW9uVG9hc3RcIjtcbmltcG9ydCB7XG4gICAgaGlkZVRvYXN0IGFzIGhpZGVVbnZlcmlmaWVkU2Vzc2lvbnNUb2FzdCxcbiAgICBzaG93VG9hc3QgYXMgc2hvd1VudmVyaWZpZWRTZXNzaW9uc1RvYXN0LFxufSBmcm9tIFwiLi90b2FzdHMvVW52ZXJpZmllZFNlc3Npb25Ub2FzdFwiO1xuaW1wb3J0IHsgYWNjZXNzU2VjcmV0U3RvcmFnZSwgaXNTZWNyZXRTdG9yYWdlQmVpbmdBY2Nlc3NlZCB9IGZyb20gXCIuL1NlY3VyaXR5TWFuYWdlclwiO1xuaW1wb3J0IHsgaXNTZWN1cmVCYWNrdXBSZXF1aXJlZCB9IGZyb20gXCIuL3V0aWxzL1dlbGxLbm93blV0aWxzXCI7XG5pbXBvcnQgeyBBY3Rpb25QYXlsb2FkIH0gZnJvbSBcIi4vZGlzcGF0Y2hlci9wYXlsb2Fkc1wiO1xuaW1wb3J0IHsgQWN0aW9uIH0gZnJvbSBcIi4vZGlzcGF0Y2hlci9hY3Rpb25zXCI7XG5pbXBvcnQgeyBpc0xvZ2dlZEluIH0gZnJvbSBcIi4vdXRpbHMvbG9naW5cIjtcbmltcG9ydCBTZGtDb25maWcgZnJvbSBcIi4vU2RrQ29uZmlnXCI7XG5pbXBvcnQgUGxhdGZvcm1QZWcgZnJvbSBcIi4vUGxhdGZvcm1QZWdcIjtcbmltcG9ydCB7IHJlY29yZENsaWVudEluZm9ybWF0aW9uLCByZW1vdmVDbGllbnRJbmZvcm1hdGlvbiB9IGZyb20gXCIuL3V0aWxzL2RldmljZS9jbGllbnRJbmZvcm1hdGlvblwiO1xuaW1wb3J0IFNldHRpbmdzU3RvcmUsIHsgQ2FsbGJhY2tGbiB9IGZyb20gXCIuL3NldHRpbmdzL1NldHRpbmdzU3RvcmVcIjtcbmltcG9ydCB7IFVJRmVhdHVyZSB9IGZyb20gXCIuL3NldHRpbmdzL1VJRmVhdHVyZVwiO1xuaW1wb3J0IHsgaXNCdWxrVW52ZXJpZmllZERldmljZVJlbWluZGVyU25vb3plZCB9IGZyb20gXCIuL3V0aWxzL2RldmljZS9zbm9vemVCdWxrVW52ZXJpZmllZERldmljZVJlbWluZGVyXCI7XG5pbXBvcnQgeyBnZXRVc2VyRGV2aWNlSWRzIH0gZnJvbSBcIi4vdXRpbHMvY3J5cHRvL2RldmljZUluZm9cIjtcblxuY29uc3QgS0VZX0JBQ0tVUF9QT0xMX0lOVEVSVkFMID0gNSAqIDYwICogMTAwMDtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgRGV2aWNlTGlzdGVuZXIge1xuICAgIHByaXZhdGUgZGlzcGF0Y2hlclJlZj86IHN0cmluZztcbiAgICAvLyBkZXZpY2UgSURzIGZvciB3aGljaCB0aGUgdXNlciBoYXMgZGlzbWlzc2VkIHRoZSB2ZXJpZnkgdG9hc3QgKCdMYXRlcicpXG4gICAgcHJpdmF0ZSBkaXNtaXNzZWQgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgICAvLyBoYXMgdGhlIHVzZXIgZGlzbWlzc2VkIGFueSBvZiB0aGUgdmFyaW91cyBuYWcgdG9hc3RzIHRvIHNldHVwIGVuY3J5cHRpb24gb24gdGhpcyBkZXZpY2U/XG4gICAgcHJpdmF0ZSBkaXNtaXNzZWRUaGlzRGV2aWNlVG9hc3QgPSBmYWxzZTtcbiAgICAvKiogQ2FjaGUgb2YgdGhlIGluZm8gYWJvdXQgdGhlIGN1cnJlbnQga2V5IGJhY2t1cCBvbiB0aGUgc2VydmVyLiAqL1xuICAgIHByaXZhdGUga2V5QmFja3VwSW5mbzogS2V5QmFja3VwSW5mbyB8IG51bGwgPSBudWxsO1xuICAgIC8qKiBXaGVuIGBrZXlCYWNrdXBJbmZvYCB3YXMgbGFzdCB1cGRhdGVkICovXG4gICAgcHJpdmF0ZSBrZXlCYWNrdXBGZXRjaGVkQXQ6IG51bWJlciB8IG51bGwgPSBudWxsO1xuICAgIC8vIFdlIGtlZXAgYSBsaXN0IG9mIG91ciBvd24gZGV2aWNlIElEcyBzbyB3ZSBjYW4gYmF0Y2ggb25lcyB0aGF0IHdlcmUgYWxyZWFkeVxuICAgIC8vIHRoZXJlIHRoZSBsYXN0IHRpbWUgdGhlIGFwcCBsYXVuY2hlZCBpbnRvIGEgc2luZ2xlIHRvYXN0LCBidXQgZGlzcGxheSBuZXdcbiAgICAvLyBvbmVzIGluIHRoZWlyIG93biB0b2FzdHMuXG4gICAgcHJpdmF0ZSBvdXJEZXZpY2VJZHNBdFN0YXJ0OiBTZXQ8c3RyaW5nPiB8IG51bGwgPSBudWxsO1xuICAgIC8vIFRoZSBzZXQgb2YgZGV2aWNlIElEcyB3ZSdyZSBjdXJyZW50bHkgZGlzcGxheWluZyB0b2FzdHMgZm9yXG4gICAgcHJpdmF0ZSBkaXNwbGF5aW5nVG9hc3RzRm9yRGV2aWNlSWRzID0gbmV3IFNldDxzdHJpbmc+KCk7XG4gICAgcHJpdmF0ZSBydW5uaW5nID0gZmFsc2U7XG4gICAgLy8gVGhlIGNsaWVudCB3aXRoIHdoaWNoIHRoZSBpbnN0YW5jZSBpcyBydW5uaW5nLiBPbmx5IHNldCBpZiBgcnVubmluZ2AgaXMgdHJ1ZSwgb3RoZXJ3aXNlIHVuZGVmaW5lZC5cbiAgICBwcml2YXRlIGNsaWVudD86IE1hdHJpeENsaWVudDtcbiAgICBwcml2YXRlIHNob3VsZFJlY29yZENsaWVudEluZm9ybWF0aW9uID0gZmFsc2U7XG4gICAgcHJpdmF0ZSBlbmFibGVCdWxrVW52ZXJpZmllZFNlc3Npb25zUmVtaW5kZXIgPSB0cnVlO1xuICAgIHByaXZhdGUgZGV2aWNlQ2xpZW50SW5mb3JtYXRpb25TZXR0aW5nV2F0Y2hlclJlZjogc3RyaW5nIHwgdW5kZWZpbmVkO1xuXG4gICAgLy8gUmVtZW1iZXIgdGhlIGN1cnJlbnQgYW5hbHl0aWNzIHN0YXRlIHRvIGF2b2lkIHNlbmRpbmcgdGhlIHNhbWUgZXZlbnQgbXVsdGlwbGUgdGltZXMuXG4gICAgcHJpdmF0ZSBhbmFseXRpY3NWZXJpZmljYXRpb25TdGF0ZT86IHN0cmluZztcbiAgICBwcml2YXRlIGFuYWx5dGljc1JlY292ZXJ5U3RhdGU/OiBzdHJpbmc7XG5cbiAgICBwdWJsaWMgc3RhdGljIHNoYXJlZEluc3RhbmNlKCk6IERldmljZUxpc3RlbmVyIHtcbiAgICAgICAgaWYgKCF3aW5kb3cubXhEZXZpY2VMaXN0ZW5lcikgd2luZG93Lm14RGV2aWNlTGlzdGVuZXIgPSBuZXcgRGV2aWNlTGlzdGVuZXIoKTtcbiAgICAgICAgcmV0dXJuIHdpbmRvdy5teERldmljZUxpc3RlbmVyO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdGFydChtYXRyaXhDbGllbnQ6IE1hdHJpeENsaWVudCk6IHZvaWQge1xuICAgICAgICB0aGlzLnJ1bm5pbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLmNsaWVudCA9IG1hdHJpeENsaWVudDtcbiAgICAgICAgdGhpcy5jbGllbnQub24oQ3J5cHRvRXZlbnQuRGV2aWNlc1VwZGF0ZWQsIHRoaXMub25EZXZpY2VzVXBkYXRlZCk7XG4gICAgICAgIHRoaXMuY2xpZW50Lm9uKENyeXB0b0V2ZW50LlVzZXJUcnVzdFN0YXR1c0NoYW5nZWQsIHRoaXMub25Vc2VyVHJ1c3RTdGF0dXNDaGFuZ2VkKTtcbiAgICAgICAgdGhpcy5jbGllbnQub24oQ3J5cHRvRXZlbnQuS2V5c0NoYW5nZWQsIHRoaXMub25Dcm9zc1NpbmdpbmdLZXlzQ2hhbmdlZCk7XG4gICAgICAgIHRoaXMuY2xpZW50Lm9uKENsaWVudEV2ZW50LkFjY291bnREYXRhLCB0aGlzLm9uQWNjb3VudERhdGEpO1xuICAgICAgICB0aGlzLmNsaWVudC5vbihDbGllbnRFdmVudC5TeW5jLCB0aGlzLm9uU3luYyk7XG4gICAgICAgIHRoaXMuY2xpZW50Lm9uKFJvb21TdGF0ZUV2ZW50LkV2ZW50cywgdGhpcy5vblJvb21TdGF0ZUV2ZW50cyk7XG4gICAgICAgIHRoaXMuc2hvdWxkUmVjb3JkQ2xpZW50SW5mb3JtYXRpb24gPSBTZXR0aW5nc1N0b3JlLmdldFZhbHVlKFwiZGV2aWNlQ2xpZW50SW5mb3JtYXRpb25PcHRJblwiKTtcbiAgICAgICAgLy8gb25seSBjb25maWd1cmFibGUgaW4gY29uZmlnLCBzbyB3ZSBkb24ndCBuZWVkIHRvIHdhdGNoIHRoZSB2YWx1ZVxuICAgICAgICB0aGlzLmVuYWJsZUJ1bGtVbnZlcmlmaWVkU2Vzc2lvbnNSZW1pbmRlciA9IFNldHRpbmdzU3RvcmUuZ2V0VmFsdWUoVUlGZWF0dXJlLkJ1bGtVbnZlcmlmaWVkU2Vzc2lvbnNSZW1pbmRlcik7XG4gICAgICAgIHRoaXMuZGV2aWNlQ2xpZW50SW5mb3JtYXRpb25TZXR0aW5nV2F0Y2hlclJlZiA9IFNldHRpbmdzU3RvcmUud2F0Y2hTZXR0aW5nKFxuICAgICAgICAgICAgXCJkZXZpY2VDbGllbnRJbmZvcm1hdGlvbk9wdEluXCIsXG4gICAgICAgICAgICBudWxsLFxuICAgICAgICAgICAgdGhpcy5vblJlY29yZENsaWVudEluZm9ybWF0aW9uU2V0dGluZ0NoYW5nZSxcbiAgICAgICAgKTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaGVyUmVmID0gZGlzLnJlZ2lzdGVyKHRoaXMub25BY3Rpb24pO1xuICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICAgICAgdGhpcy51cGRhdGVDbGllbnRJbmZvcm1hdGlvbigpO1xuICAgIH1cblxuICAgIHB1YmxpYyBzdG9wKCk6IHZvaWQge1xuICAgICAgICB0aGlzLnJ1bm5pbmcgPSBmYWxzZTtcbiAgICAgICAgaWYgKHRoaXMuY2xpZW50KSB7XG4gICAgICAgICAgICB0aGlzLmNsaWVudC5yZW1vdmVMaXN0ZW5lcihDcnlwdG9FdmVudC5EZXZpY2VzVXBkYXRlZCwgdGhpcy5vbkRldmljZXNVcGRhdGVkKTtcbiAgICAgICAgICAgIHRoaXMuY2xpZW50LnJlbW92ZUxpc3RlbmVyKENyeXB0b0V2ZW50LlVzZXJUcnVzdFN0YXR1c0NoYW5nZWQsIHRoaXMub25Vc2VyVHJ1c3RTdGF0dXNDaGFuZ2VkKTtcbiAgICAgICAgICAgIHRoaXMuY2xpZW50LnJlbW92ZUxpc3RlbmVyKENyeXB0b0V2ZW50LktleXNDaGFuZ2VkLCB0aGlzLm9uQ3Jvc3NTaW5naW5nS2V5c0NoYW5nZWQpO1xuICAgICAgICAgICAgdGhpcy5jbGllbnQucmVtb3ZlTGlzdGVuZXIoQ2xpZW50RXZlbnQuQWNjb3VudERhdGEsIHRoaXMub25BY2NvdW50RGF0YSk7XG4gICAgICAgICAgICB0aGlzLmNsaWVudC5yZW1vdmVMaXN0ZW5lcihDbGllbnRFdmVudC5TeW5jLCB0aGlzLm9uU3luYyk7XG4gICAgICAgICAgICB0aGlzLmNsaWVudC5yZW1vdmVMaXN0ZW5lcihSb29tU3RhdGVFdmVudC5FdmVudHMsIHRoaXMub25Sb29tU3RhdGVFdmVudHMpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLmRldmljZUNsaWVudEluZm9ybWF0aW9uU2V0dGluZ1dhdGNoZXJSZWYpIHtcbiAgICAgICAgICAgIFNldHRpbmdzU3RvcmUudW53YXRjaFNldHRpbmcodGhpcy5kZXZpY2VDbGllbnRJbmZvcm1hdGlvblNldHRpbmdXYXRjaGVyUmVmKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5kaXNwYXRjaGVyUmVmKSB7XG4gICAgICAgICAgICBkaXMudW5yZWdpc3Rlcih0aGlzLmRpc3BhdGNoZXJSZWYpO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaGVyUmVmID0gdW5kZWZpbmVkO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuZGlzbWlzc2VkLmNsZWFyKCk7XG4gICAgICAgIHRoaXMuZGlzbWlzc2VkVGhpc0RldmljZVRvYXN0ID0gZmFsc2U7XG4gICAgICAgIHRoaXMua2V5QmFja3VwSW5mbyA9IG51bGw7XG4gICAgICAgIHRoaXMua2V5QmFja3VwRmV0Y2hlZEF0ID0gbnVsbDtcbiAgICAgICAgdGhpcy5rZXlCYWNrdXBTdGF0dXNDaGVja2VkID0gZmFsc2U7XG4gICAgICAgIHRoaXMub3VyRGV2aWNlSWRzQXRTdGFydCA9IG51bGw7XG4gICAgICAgIHRoaXMuZGlzcGxheWluZ1RvYXN0c0ZvckRldmljZUlkcyA9IG5ldyBTZXQoKTtcbiAgICAgICAgdGhpcy5jbGllbnQgPSB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRGlzbWlzcyBub3RpZmljYXRpb25zIGFib3V0IG91ciBvd24gdW52ZXJpZmllZCBkZXZpY2VzXG4gICAgICpcbiAgICAgKiBAcGFyYW0ge1N0cmluZ1tdfSBkZXZpY2VJZHMgTGlzdCBvZiBkZXZpY2UgSURzIHRvIGRpc21pc3Mgbm90aWZpY2F0aW9ucyBmb3JcbiAgICAgKi9cbiAgICBwdWJsaWMgYXN5bmMgZGlzbWlzc1VudmVyaWZpZWRTZXNzaW9ucyhkZXZpY2VJZHM6IEl0ZXJhYmxlPHN0cmluZz4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgbG9nZ2VyLmxvZyhcIkRpc21pc3NpbmcgdW52ZXJpZmllZCBzZXNzaW9uczogXCIgKyBBcnJheS5mcm9tKGRldmljZUlkcykuam9pbihcIixcIikpO1xuICAgICAgICBmb3IgKGNvbnN0IGQgb2YgZGV2aWNlSWRzKSB7XG4gICAgICAgICAgICB0aGlzLmRpc21pc3NlZC5hZGQoZCk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZGlzbWlzc0VuY3J5cHRpb25TZXR1cCgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5kaXNtaXNzZWRUaGlzRGV2aWNlVG9hc3QgPSB0cnVlO1xuICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGFzeW5jIGVuc3VyZURldmljZUlkc0F0U3RhcnRQb3B1bGF0ZWQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGlmICh0aGlzLm91ckRldmljZUlkc0F0U3RhcnQgPT09IG51bGwpIHtcbiAgICAgICAgICAgIHRoaXMub3VyRGV2aWNlSWRzQXRTdGFydCA9IGF3YWl0IHRoaXMuZ2V0RGV2aWNlSWRzKCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKiogR2V0IHRoZSBkZXZpY2UgbGlzdCBmb3IgdGhlIGN1cnJlbnQgdXNlclxuICAgICAqXG4gICAgICogQHJldHVybnMgdGhlIHNldCBvZiBkZXZpY2UgSURzXG4gICAgICovXG4gICAgcHJpdmF0ZSBhc3luYyBnZXREZXZpY2VJZHMoKTogUHJvbWlzZTxTZXQ8c3RyaW5nPj4ge1xuICAgICAgICBjb25zdCBjbGkgPSB0aGlzLmNsaWVudDtcbiAgICAgICAgaWYgKCFjbGkpIHJldHVybiBuZXcgU2V0KCk7XG4gICAgICAgIHJldHVybiBhd2FpdCBnZXRVc2VyRGV2aWNlSWRzKGNsaSwgY2xpLmdldFNhZmVVc2VySWQoKSk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBvbkRldmljZXNVcGRhdGVkID0gYXN5bmMgKHVzZXJzOiBzdHJpbmdbXSwgaW5pdGlhbEZldGNoPzogYm9vbGVhbik6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICBpZiAoIXRoaXMuY2xpZW50KSByZXR1cm47XG4gICAgICAgIC8vIElmIHdlIGRpZG4ndCBrbm93IGFib3V0ICphbnkqIGRldmljZXMgYmVmb3JlIChpZS4gaXQncyBmcmVzaCBsb2dpbiksXG4gICAgICAgIC8vIHRoZW4gdGhleSBhcmUgYWxsIHByZS1leGlzdGluZyBkZXZpY2VzLCBzbyBpZ25vcmUgdGhpcyBhbmQgc2V0IHRoZVxuICAgICAgICAvLyBkZXZpY2VzQXRTdGFydCBsaXN0IHRvIHRoZSBkZXZpY2VzIHRoYXQgd2Ugc2VlIGFmdGVyIHRoZSBmZXRjaC5cbiAgICAgICAgaWYgKGluaXRpYWxGZXRjaCkgcmV0dXJuO1xuXG4gICAgICAgIGNvbnN0IG15VXNlcklkID0gdGhpcy5jbGllbnQuZ2V0U2FmZVVzZXJJZCgpO1xuICAgICAgICBpZiAodXNlcnMuaW5jbHVkZXMobXlVc2VySWQpKSBhd2FpdCB0aGlzLmVuc3VyZURldmljZUlkc0F0U3RhcnRQb3B1bGF0ZWQoKTtcblxuICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvblVzZXJUcnVzdFN0YXR1c0NoYW5nZWQgPSAodXNlcklkOiBzdHJpbmcpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKCF0aGlzLmNsaWVudCkgcmV0dXJuO1xuICAgICAgICBpZiAodXNlcklkICE9PSB0aGlzLmNsaWVudC5nZXRVc2VySWQoKSkgcmV0dXJuO1xuICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkNyb3NzU2luZ2luZ0tleXNDaGFuZ2VkID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkFjY291bnREYXRhID0gKGV2OiBNYXRyaXhFdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICAvLyBVc2VyIG1heSBoYXZlOlxuICAgICAgICAvLyAqIG1pZ3JhdGVkIFNTU1MgdG8gc3ltbWV0cmljXG4gICAgICAgIC8vICogdXBsb2FkZWQga2V5cyB0byBzZWNyZXQgc3RvcmFnZVxuICAgICAgICAvLyAqIGNvbXBsZXRlZCBzZWNyZXQgc3RvcmFnZSBjcmVhdGlvblxuICAgICAgICAvLyB3aGljaCByZXN1bHQgaW4gYWNjb3VudCBkYXRhIGNoYW5nZXMgYWZmZWN0aW5nIGNoZWNrcyBiZWxvdy5cbiAgICAgICAgaWYgKFxuICAgICAgICAgICAgZXYuZ2V0VHlwZSgpLnN0YXJ0c1dpdGgoXCJtLnNlY3JldF9zdG9yYWdlLlwiKSB8fFxuICAgICAgICAgICAgZXYuZ2V0VHlwZSgpLnN0YXJ0c1dpdGgoXCJtLmNyb3NzX3NpZ25pbmcuXCIpIHx8XG4gICAgICAgICAgICBldi5nZXRUeXBlKCkgPT09IFwibS5tZWdvbG1fYmFja3VwLnYxXCJcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcml2YXRlIG9uU3luYyA9IChzdGF0ZTogU3luY1N0YXRlLCBwcmV2U3RhdGU6IFN5bmNTdGF0ZSB8IG51bGwpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKHN0YXRlID09PSBcIlBSRVBBUkVEXCIgJiYgcHJldlN0YXRlID09PSBudWxsKSB7XG4gICAgICAgICAgICB0aGlzLnJlY2hlY2soKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwcml2YXRlIG9uUm9vbVN0YXRlRXZlbnRzID0gKGV2OiBNYXRyaXhFdmVudCk6IHZvaWQgPT4ge1xuICAgICAgICBpZiAoZXYuZ2V0VHlwZSgpICE9PSBFdmVudFR5cGUuUm9vbUVuY3J5cHRpb24pIHJldHVybjtcblxuICAgICAgICAvLyBJZiBhIHJvb20gY2hhbmdlcyB0byBlbmNyeXB0ZWQsIHJlLWNoZWNrIGFzIGl0IG1heSBiZSBvdXIgZmlyc3RcbiAgICAgICAgLy8gZW5jcnlwdGVkIHJvb20uIFRoaXMgYWxzbyBjYXRjaGVzIGVuY3J5cHRlZCByb29tIGNyZWF0aW9uIGFzIHdlbGwuXG4gICAgICAgIHRoaXMucmVjaGVjaygpO1xuICAgIH07XG5cbiAgICBwcml2YXRlIG9uQWN0aW9uID0gKHsgYWN0aW9uIH06IEFjdGlvblBheWxvYWQpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKGFjdGlvbiAhPT0gQWN0aW9uLk9uTG9nZ2VkSW4pIHJldHVybjtcbiAgICAgICAgdGhpcy5yZWNoZWNrKCk7XG4gICAgICAgIHRoaXMudXBkYXRlQ2xpZW50SW5mb3JtYXRpb24oKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogRmV0Y2ggdGhlIGtleSBiYWNrdXAgaW5mb3JtYXRpb24gZnJvbSB0aGUgc2VydmVyLlxuICAgICAqXG4gICAgICogVGhlIHJlc3VsdCBpcyBjYWNoZWQgZm9yIGBLRVlfQkFDS1VQX1BPTExfSU5URVJWQUxgIG1zIHRvIGF2b2lkIHJlcGVhdGVkIEFQSSBjYWxscy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBrZXkgYmFja3VwIGluZm8gZnJvbSB0aGUgc2VydmVyLCBvciBgbnVsbGAgaWYgdGhlcmUgaXMgbm8ga2V5IGJhY2t1cC5cbiAgICAgKi9cbiAgICBwcml2YXRlIGFzeW5jIGdldEtleUJhY2t1cEluZm8oKTogUHJvbWlzZTxLZXlCYWNrdXBJbmZvIHwgbnVsbD4ge1xuICAgICAgICBpZiAoIXRoaXMuY2xpZW50KSByZXR1cm4gbnVsbDtcbiAgICAgICAgY29uc3Qgbm93ID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgIGlmIChcbiAgICAgICAgICAgICF0aGlzLmtleUJhY2t1cEluZm8gfHxcbiAgICAgICAgICAgICF0aGlzLmtleUJhY2t1cEZldGNoZWRBdCB8fFxuICAgICAgICAgICAgdGhpcy5rZXlCYWNrdXBGZXRjaGVkQXQgPCBub3cgLSBLRVlfQkFDS1VQX1BPTExfSU5URVJWQUxcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aGlzLmtleUJhY2t1cEluZm8gPSBhd2FpdCB0aGlzLmNsaWVudC5nZXRLZXlCYWNrdXBWZXJzaW9uKCk7XG4gICAgICAgICAgICB0aGlzLmtleUJhY2t1cEZldGNoZWRBdCA9IG5vdztcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5rZXlCYWNrdXBJbmZvO1xuICAgIH1cblxuICAgIHByaXZhdGUgc2hvdWxkU2hvd1NldHVwRW5jcnlwdGlvblRvYXN0KCk6IGJvb2xlYW4ge1xuICAgICAgICAvLyBJZiB3ZSdyZSBpbiB0aGUgbWlkZGxlIG9mIGEgc2VjcmV0IHN0b3JhZ2Ugb3BlcmF0aW9uLCB3ZSdyZSBsaWtlbHlcbiAgICAgICAgLy8gbW9kaWZ5aW5nIHRoZSBzdGF0ZSBpbnZvbHZlZCBoZXJlLCBzbyBkb24ndCBhZGQgbmV3IHRvYXN0cyB0byBzZXR1cC5cbiAgICAgICAgaWYgKGlzU2VjcmV0U3RvcmFnZUJlaW5nQWNjZXNzZWQoKSkgcmV0dXJuIGZhbHNlO1xuICAgICAgICAvLyBTaG93IHNldHVwIHRvYXN0cyBvbmNlIHRoZSB1c2VyIGlzIGluIGF0IGxlYXN0IG9uZSBlbmNyeXB0ZWQgcm9vbS5cbiAgICAgICAgY29uc3QgY2xpID0gdGhpcy5jbGllbnQ7XG4gICAgICAgIHJldHVybiBjbGk/LmdldFJvb21zKCkuc29tZSgocikgPT4gY2xpLmlzUm9vbUVuY3J5cHRlZChyLnJvb21JZCkpID8/IGZhbHNlO1xuICAgIH1cblxuICAgIHByaXZhdGUgcmVjaGVjaygpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5kb1JlY2hlY2soKS5jYXRjaCgoZSkgPT4ge1xuICAgICAgICAgICAgaWYgKGUgaW5zdGFuY2VvZiBDbGllbnRTdG9wcGVkRXJyb3IpIHtcbiAgICAgICAgICAgICAgICAvLyB0aGUgY2xpZW50IHdhcyBzdG9wcGVkIHdoaWxlIHJlY2hlY2soKSB3YXMgcnVubmluZy4gTm90aGluZyBsZWZ0IHRvIGRvLlxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIuZXJyb3IoXCJFcnJvciBkdXJpbmcgYERldmljZUxpc3RlbmVyLnJlY2hlY2tgXCIsIGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGFzeW5jIGRvUmVjaGVjaygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgaWYgKCF0aGlzLnJ1bm5pbmcgfHwgIXRoaXMuY2xpZW50KSByZXR1cm47IC8vIHdlIGhhdmUgYmVlbiBzdG9wcGVkXG4gICAgICAgIGNvbnN0IGNsaSA9IHRoaXMuY2xpZW50O1xuXG4gICAgICAgIC8vIGNyb3NzLXNpZ25pbmcgc3VwcG9ydCB3YXMgYWRkZWQgdG8gTWF0cml4IGluIE1TQzE3NTYsIHdoaWNoIGxhbmRlZCBpbiBzcGVjIHYxLjFcbiAgICAgICAgaWYgKCEoYXdhaXQgY2xpLmlzVmVyc2lvblN1cHBvcnRlZChcInYxLjFcIikpKSByZXR1cm47XG5cbiAgICAgICAgY29uc3QgY3J5cHRvID0gY2xpLmdldENyeXB0bygpO1xuICAgICAgICBpZiAoIWNyeXB0bykgcmV0dXJuO1xuXG4gICAgICAgIC8vIGRvbid0IHJlY2hlY2sgdW50aWwgdGhlIGluaXRpYWwgc3luYyBpcyBjb21wbGV0ZTogbG90cyBvZiBhY2NvdW50IGRhdGEgZXZlbnRzIHdpbGwgZmlyZVxuICAgICAgICAvLyB3aGlsZSB0aGUgaW5pdGlhbCBzeW5jIGlzIHByb2Nlc3NpbmcgYW5kIHdlIGRvbid0IG5lZWQgdG8gcmVjaGVjayBvbiBlYWNoIG9uZSBvZiB0aGVtXG4gICAgICAgIC8vICh3ZSBhZGQgYSBsaXN0ZW5lciBvbiBzeW5jIHRvIGRvIG9uY2UgY2hlY2sgYWZ0ZXIgdGhlIGluaXRpYWwgc3luYyBpcyBkb25lKVxuICAgICAgICBpZiAoIWNsaS5pc0luaXRpYWxTeW5jQ29tcGxldGUoKSkgcmV0dXJuO1xuXG4gICAgICAgIGNvbnN0IGNyb3NzU2lnbmluZ1JlYWR5ID0gYXdhaXQgY3J5cHRvLmlzQ3Jvc3NTaWduaW5nUmVhZHkoKTtcbiAgICAgICAgY29uc3Qgc2VjcmV0U3RvcmFnZVJlYWR5ID0gYXdhaXQgY3J5cHRvLmlzU2VjcmV0U3RvcmFnZVJlYWR5KCk7XG4gICAgICAgIGNvbnN0IGFsbFN5c3RlbXNSZWFkeSA9IGNyb3NzU2lnbmluZ1JlYWR5ICYmIHNlY3JldFN0b3JhZ2VSZWFkeTtcbiAgICAgICAgYXdhaXQgdGhpcy5yZXBvcnRDcnlwdG9TZXNzaW9uU3RhdGVUb0FuYWx5dGljcyhjbGkpO1xuXG4gICAgICAgIGlmICh0aGlzLmRpc21pc3NlZFRoaXNEZXZpY2VUb2FzdCB8fCBhbGxTeXN0ZW1zUmVhZHkpIHtcbiAgICAgICAgICAgIGhpZGVTZXR1cEVuY3J5cHRpb25Ub2FzdCgpO1xuXG4gICAgICAgICAgICB0aGlzLmNoZWNrS2V5QmFja3VwU3RhdHVzKCk7XG4gICAgICAgIH0gZWxzZSBpZiAodGhpcy5zaG91bGRTaG93U2V0dXBFbmNyeXB0aW9uVG9hc3QoKSkge1xuICAgICAgICAgICAgLy8gbWFrZSBzdXJlIG91ciBrZXlzIGFyZSBmaW5pc2hlZCBkb3dubG9hZGluZ1xuICAgICAgICAgICAgYXdhaXQgY3J5cHRvLmdldFVzZXJEZXZpY2VJbmZvKFtjbGkuZ2V0U2FmZVVzZXJJZCgpXSk7XG5cbiAgICAgICAgICAgIC8vIGNyb3NzIHNpZ25pbmcgaXNuJ3QgZW5hYmxlZCAtIG5hZyB0byBlbmFibGUgaXRcbiAgICAgICAgICAgIC8vIFRoZXJlIGFyZSAzIGRpZmZlcmVudCB0b2FzdHMgZm9yOlxuICAgICAgICAgICAgaWYgKCEoYXdhaXQgY3J5cHRvLmdldENyb3NzU2lnbmluZ0tleUlkKCkpICYmIChhd2FpdCBjcnlwdG8udXNlckhhc0Nyb3NzU2lnbmluZ0tleXMoKSkpIHtcbiAgICAgICAgICAgICAgICAvLyBDcm9zcy1zaWduaW5nIG9uIGFjY291bnQgYnV0IHRoaXMgZGV2aWNlIGRvZXNuJ3QgdHJ1c3QgdGhlIG1hc3RlciBrZXkgKHZlcmlmeSB0aGlzIHNlc3Npb24pXG4gICAgICAgICAgICAgICAgc2hvd1NldHVwRW5jcnlwdGlvblRvYXN0KFNldHVwS2luZC5WRVJJRllfVEhJU19TRVNTSU9OKTtcbiAgICAgICAgICAgICAgICB0aGlzLmNoZWNrS2V5QmFja3VwU3RhdHVzKCk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnN0IGJhY2t1cEluZm8gPSBhd2FpdCB0aGlzLmdldEtleUJhY2t1cEluZm8oKTtcbiAgICAgICAgICAgICAgICBpZiAoYmFja3VwSW5mbykge1xuICAgICAgICAgICAgICAgICAgICAvLyBObyBjcm9zcy1zaWduaW5nIG9uIGFjY291bnQgYnV0IGtleSBiYWNrdXAgYXZhaWxhYmxlICh1cGdyYWRlIGVuY3J5cHRpb24pXG4gICAgICAgICAgICAgICAgICAgIHNob3dTZXR1cEVuY3J5cHRpb25Ub2FzdChTZXR1cEtpbmQuVVBHUkFERV9FTkNSWVBUSU9OKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAvLyBObyBjcm9zcy1zaWduaW5nIG9yIGtleSBiYWNrdXAgb24gYWNjb3VudCAoc2V0IHVwIGVuY3J5cHRpb24pXG4gICAgICAgICAgICAgICAgICAgIGF3YWl0IGNsaS53YWl0Rm9yQ2xpZW50V2VsbEtub3duKCk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChpc1NlY3VyZUJhY2t1cFJlcXVpcmVkKGNsaSkgJiYgaXNMb2dnZWRJbigpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBJZiB3ZSdyZSBtZWFudCB0byBzZXQgdXAsIGFuZCBTZWN1cmUgQmFja3VwIGlzIHJlcXVpcmVkLFxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gdHJpZ2dlciB0aGUgZmxvdyBkaXJlY3RseSB3aXRob3V0IGEgdG9hc3Qgb25jZSBsb2dnZWQgaW4uXG4gICAgICAgICAgICAgICAgICAgICAgICBoaWRlU2V0dXBFbmNyeXB0aW9uVG9hc3QoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGFjY2Vzc1NlY3JldFN0b3JhZ2UoKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNob3dTZXR1cEVuY3J5cHRpb25Ub2FzdChTZXR1cEtpbmQuU0VUX1VQX0VOQ1JZUFRJT04pO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gVGhpcyBuZWVkcyB0byBiZSBkb25lIGFmdGVyIGF3YWl0aW5nIG9uIGdldFVzZXJEZXZpY2VJbmZvKCkgYWJvdmUsIHNvXG4gICAgICAgIC8vIHdlIG1ha2Ugc3VyZSB3ZSBnZXQgdGhlIGRldmljZXMgYWZ0ZXIgdGhlIGZldGNoIGlzIGRvbmUuXG4gICAgICAgIGF3YWl0IHRoaXMuZW5zdXJlRGV2aWNlSWRzQXRTdGFydFBvcHVsYXRlZCgpO1xuXG4gICAgICAgIC8vIFVudmVyaWZpZWQgZGV2aWNlcyB0aGF0IHdlcmUgdGhlcmUgbGFzdCB0aW1lIHRoZSBhcHAgcmFuXG4gICAgICAgIC8vICh0ZWNobmljYWxseSBjb3VsZCBqdXN0IGJlIGEgYm9vbGVhbjogd2UgZG9uJ3QgYWN0dWFsbHlcbiAgICAgICAgLy8gbmVlZCB0byByZW1lbWJlciB0aGUgZGV2aWNlIElEcywgYnV0IGZvciB0aGUgc2FrZSBvZlxuICAgICAgICAvLyBzeW1tZXRyeS4uLikuXG4gICAgICAgIGNvbnN0IG9sZFVudmVyaWZpZWREZXZpY2VJZHMgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgICAgICAgLy8gVW52ZXJpZmllZCBkZXZpY2VzIHRoYXQgaGF2ZSBhcHBlYXJlZCBzaW5jZSB0aGVuXG4gICAgICAgIGNvbnN0IG5ld1VudmVyaWZpZWREZXZpY2VJZHMgPSBuZXcgU2V0PHN0cmluZz4oKTtcblxuICAgICAgICBjb25zdCBpc0N1cnJlbnREZXZpY2VUcnVzdGVkID1cbiAgICAgICAgICAgIGNyb3NzU2lnbmluZ1JlYWR5ICYmXG4gICAgICAgICAgICBCb29sZWFuKFxuICAgICAgICAgICAgICAgIChhd2FpdCBjcnlwdG8uZ2V0RGV2aWNlVmVyaWZpY2F0aW9uU3RhdHVzKGNsaS5nZXRTYWZlVXNlcklkKCksIGNsaS5kZXZpY2VJZCEpKT8uY3Jvc3NTaWduaW5nVmVyaWZpZWQsXG4gICAgICAgICAgICApO1xuXG4gICAgICAgIC8vIGFzIGxvbmcgYXMgY3Jvc3Mtc2lnbmluZyBpc24ndCByZWFkeSxcbiAgICAgICAgLy8geW91IGNhbid0IHNlZSBvciBkaXNtaXNzIGFueSBkZXZpY2UgdG9hc3RzXG4gICAgICAgIGlmIChjcm9zc1NpZ25pbmdSZWFkeSkge1xuICAgICAgICAgICAgY29uc3QgZGV2aWNlcyA9IGF3YWl0IHRoaXMuZ2V0RGV2aWNlSWRzKCk7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGRldmljZUlkIG9mIGRldmljZXMpIHtcbiAgICAgICAgICAgICAgICBpZiAoZGV2aWNlSWQgPT09IGNsaS5kZXZpY2VJZCkgY29udGludWU7XG5cbiAgICAgICAgICAgICAgICBjb25zdCBkZXZpY2VUcnVzdCA9IGF3YWl0IGNyeXB0by5nZXREZXZpY2VWZXJpZmljYXRpb25TdGF0dXMoY2xpLmdldFNhZmVVc2VySWQoKSwgZGV2aWNlSWQpO1xuICAgICAgICAgICAgICAgIGlmICghZGV2aWNlVHJ1c3Q/LmNyb3NzU2lnbmluZ1ZlcmlmaWVkICYmICF0aGlzLmRpc21pc3NlZC5oYXMoZGV2aWNlSWQpKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmICh0aGlzLm91ckRldmljZUlkc0F0U3RhcnQ/LmhhcyhkZXZpY2VJZCkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG9sZFVudmVyaWZpZWREZXZpY2VJZHMuYWRkKGRldmljZUlkKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG5ld1VudmVyaWZpZWREZXZpY2VJZHMuYWRkKGRldmljZUlkKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhcIk9sZCB1bnZlcmlmaWVkIHNlc3Npb25zOiBcIiArIEFycmF5LmZyb20ob2xkVW52ZXJpZmllZERldmljZUlkcykuam9pbihcIixcIikpO1xuICAgICAgICBsb2dnZXIuZGVidWcoXCJOZXcgdW52ZXJpZmllZCBzZXNzaW9uczogXCIgKyBBcnJheS5mcm9tKG5ld1VudmVyaWZpZWREZXZpY2VJZHMpLmpvaW4oXCIsXCIpKTtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKFwiQ3VycmVudGx5IHNob3dpbmcgdG9hc3RzIGZvcjogXCIgKyBBcnJheS5mcm9tKHRoaXMuZGlzcGxheWluZ1RvYXN0c0ZvckRldmljZUlkcykuam9pbihcIixcIikpO1xuXG4gICAgICAgIGNvbnN0IGlzQnVsa1VudmVyaWZpZWRTZXNzaW9uc1JlbWluZGVyU25vb3plZCA9IGlzQnVsa1VudmVyaWZpZWREZXZpY2VSZW1pbmRlclNub296ZWQoKTtcblxuICAgICAgICAvLyBEaXNwbGF5IG9yIGhpZGUgdGhlIGJhdGNoIHRvYXN0IGZvciBvbGQgdW52ZXJpZmllZCBzZXNzaW9uc1xuICAgICAgICAvLyBkb24ndCBzaG93IHRoZSB0b2FzdCBpZiB0aGUgY3VycmVudCBkZXZpY2UgaXMgdW52ZXJpZmllZFxuICAgICAgICBpZiAoXG4gICAgICAgICAgICBvbGRVbnZlcmlmaWVkRGV2aWNlSWRzLnNpemUgPiAwICYmXG4gICAgICAgICAgICBpc0N1cnJlbnREZXZpY2VUcnVzdGVkICYmXG4gICAgICAgICAgICB0aGlzLmVuYWJsZUJ1bGtVbnZlcmlmaWVkU2Vzc2lvbnNSZW1pbmRlciAmJlxuICAgICAgICAgICAgIWlzQnVsa1VudmVyaWZpZWRTZXNzaW9uc1JlbWluZGVyU25vb3plZFxuICAgICAgICApIHtcbiAgICAgICAgICAgIHNob3dCdWxrVW52ZXJpZmllZFNlc3Npb25zVG9hc3Qob2xkVW52ZXJpZmllZERldmljZUlkcyk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBoaWRlQnVsa1VudmVyaWZpZWRTZXNzaW9uc1RvYXN0KCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBTaG93IHRvYXN0cyBmb3IgbmV3IHVudmVyaWZpZWQgZGV2aWNlcyBpZiB0aGV5IGFyZW4ndCBhbHJlYWR5IHRoZXJlXG4gICAgICAgIGZvciAoY29uc3QgZGV2aWNlSWQgb2YgbmV3VW52ZXJpZmllZERldmljZUlkcykge1xuICAgICAgICAgICAgc2hvd1VudmVyaWZpZWRTZXNzaW9uc1RvYXN0KGRldmljZUlkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIC4uLmFuZCBoaWRlIGFueSB3ZSBkb24ndCBuZWVkIGFueSBtb3JlXG4gICAgICAgIGZvciAoY29uc3QgZGV2aWNlSWQgb2YgdGhpcy5kaXNwbGF5aW5nVG9hc3RzRm9yRGV2aWNlSWRzKSB7XG4gICAgICAgICAgICBpZiAoIW5ld1VudmVyaWZpZWREZXZpY2VJZHMuaGFzKGRldmljZUlkKSkge1xuICAgICAgICAgICAgICAgIGxvZ2dlci5kZWJ1ZyhcIkhpZGluZyB1bnZlcmlmaWVkIHNlc3Npb24gdG9hc3QgZm9yIFwiICsgZGV2aWNlSWQpO1xuICAgICAgICAgICAgICAgIGhpZGVVbnZlcmlmaWVkU2Vzc2lvbnNUb2FzdChkZXZpY2VJZCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLmRpc3BsYXlpbmdUb2FzdHNGb3JEZXZpY2VJZHMgPSBuZXdVbnZlcmlmaWVkRGV2aWNlSWRzO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlcG9ydHMgY3VycmVudCByZWNvdmVyeSBzdGF0ZSB0byBhbmFseXRpY3MuXG4gICAgICogQ2hlY2tzIGlmIHRoZSBzZXNzaW9uIGlzIHZlcmlmaWVkIGFuZCBpZiB0aGUgcmVjb3ZlcnkgaXMgY29ycmVjdGx5IHNldCB1cCAoaS5lIGFsbCBzZWNyZXRzIGtub3duIGxvY2FsbHkgYW5kIGluIDRTKS5cbiAgICAgKiBAcGFyYW0gY2xpIC0gdGhlIG1hdHJpeCBjbGllbnRcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIHByaXZhdGUgYXN5bmMgcmVwb3J0Q3J5cHRvU2Vzc2lvblN0YXRlVG9BbmFseXRpY3MoY2xpOiBNYXRyaXhDbGllbnQpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgY29uc3QgY3J5cHRvID0gY2xpLmdldENyeXB0bygpITtcbiAgICAgICAgY29uc3Qgc2VjcmV0U3RvcmFnZVJlYWR5ID0gYXdhaXQgY3J5cHRvLmlzU2VjcmV0U3RvcmFnZVJlYWR5KCk7XG4gICAgICAgIGNvbnN0IGNyb3NzU2lnbmluZ1N0YXR1cyA9IGF3YWl0IGNyeXB0by5nZXRDcm9zc1NpZ25pbmdTdGF0dXMoKTtcbiAgICAgICAgY29uc3QgYmFja3VwSW5mbyA9IGF3YWl0IHRoaXMuZ2V0S2V5QmFja3VwSW5mbygpO1xuICAgICAgICBjb25zdCBpczRTRW5hYmxlZCA9IChhd2FpdCBjbGkuc2VjcmV0U3RvcmFnZS5nZXREZWZhdWx0S2V5SWQoKSkgIT0gbnVsbDtcbiAgICAgICAgY29uc3QgZGV2aWNlVmVyaWZpY2F0aW9uU3RhdHVzID0gYXdhaXQgY3J5cHRvLmdldERldmljZVZlcmlmaWNhdGlvblN0YXR1cyhjbGkuZ2V0VXNlcklkKCkhLCBjbGkuZ2V0RGV2aWNlSWQoKSEpO1xuXG4gICAgICAgIGNvbnN0IHZlcmlmaWNhdGlvblN0YXRlID1cbiAgICAgICAgICAgIGRldmljZVZlcmlmaWNhdGlvblN0YXR1cz8uc2lnbmVkQnlPd25lciAmJiBkZXZpY2VWZXJpZmljYXRpb25TdGF0dXM/LmNyb3NzU2lnbmluZ1ZlcmlmaWVkXG4gICAgICAgICAgICAgICAgPyBcIlZlcmlmaWVkXCJcbiAgICAgICAgICAgICAgICA6IFwiTm90VmVyaWZpZWRcIjtcblxuICAgICAgICBsZXQgcmVjb3ZlcnlTdGF0ZTogXCJEaXNhYmxlZFwiIHwgXCJFbmFibGVkXCIgfCBcIkluY29tcGxldGVcIjtcbiAgICAgICAgaWYgKCFpczRTRW5hYmxlZCkge1xuICAgICAgICAgICAgcmVjb3ZlcnlTdGF0ZSA9IFwiRGlzYWJsZWRcIjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IGFsbENyb3NzU2lnbmluZ1NlY3JldHNDYWNoZWQgPVxuICAgICAgICAgICAgICAgIGNyb3NzU2lnbmluZ1N0YXR1cy5wcml2YXRlS2V5c0NhY2hlZExvY2FsbHkubWFzdGVyS2V5ICYmXG4gICAgICAgICAgICAgI