@phemium-costaisa/fingerprint-auth
Version:
Automatic plugin to work with FaceID and TouchID authentication
406 lines • 52.8 kB
JavaScript
import { __awaiter } from "tslib";
import { Inject, Injectable, Optional } from '@angular/core';
import { FingerprintAIO } from '@ionic-native/fingerprint-aio/ngx';
import { AndroidFingerprintAuth } from '@ionic-native/android-fingerprint-auth/ngx';
import { TouchID } from '@ionic-native/touch-id/ngx';
import { Keychain } from '@ionic-native/keychain/ngx';
import { Platform } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core';
import { forkJoin, from } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { StorageService } from './storage.service';
import { Config } from '../tokens';
export var Biometric;
(function (Biometric) {
Biometric["Face"] = "face";
Biometric["Fingerprint"] = "finger";
Biometric["Common"] = "biometric";
})(Biometric || (Biometric = {}));
export class FingerprintService {
constructor(platform, faio, androidFingerprintAuth, touchId, keychain, router, storage, translateService, config) {
this.platform = platform;
this.faio = faio;
this.androidFingerprintAuth = androidFingerprintAuth;
this.touchId = touchId;
this.keychain = keychain;
this.router = router;
this.storage = storage;
this.translateService = translateService;
this.config = config;
}
/**
* Use this method in Login page to check if user
* should be redirected to the Biometric Activator
*/
checkIfNeedsBiometric(user) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isPlatformMobile()) {
this.config.debug &&
console.log('[BiometricService]', 'isPlatformMobile():', this.isPlatformMobile());
return false;
}
// Check FaceID enabled
const biometricFaceActive = yield this.isBiometricActive(user, 'face');
this.config.debug &&
console.log('[BiometricService]', 'biometricFaceActive:', biometricFaceActive);
// Check TouchID enabled
const biometricTouchActive = yield this.isBiometricActive(user, 'touch');
this.config.debug &&
console.log('[BiometricService]', 'biometricTouchActive:', biometricTouchActive);
// Check if device has some biometric capability
const biometricCapabilities = yield this.retrieveDeviceBiometrics();
this.config.debug &&
console.log('[BiometricService]', 'biometricCapabilities:', biometricCapabilities);
const isBiometricCapable = biometricCapabilities.face || biometricCapabilities.touch;
// Redirect to Biometric Activator if conditions are met
return (biometricFaceActive === null &&
biometricTouchActive === null &&
isBiometricCapable);
});
}
/**
* Use in Login page to open Biometric prompt
* @param user string
*/
showBiometricPrompt(user) {
return __awaiter(this, void 0, void 0, function* () {
const face = yield this.showFingerprintId(user, 'face').toPromise();
const touch = yield this.showFingerprintId(user, 'touch').toPromise();
// Here we have a big dilemma
// In case the user device is capable of FaceID and TouchID,
// for the moment we will mark `face` as preferent,
// as iOS the mostly one with FaceID
if (face) {
// Login with FaceID
const password = yield this.launchFaceID(user);
if (password) {
return { user, password };
}
else {
throw 'Something went wrong in launchFaceID';
}
}
else if (touch) {
// Login with TouchID
const password = yield this.launchTouchID(user);
if (password) {
return { user, password };
}
else {
throw 'Something went wrong in launchTouchID';
}
}
else {
throw 'Looks like we have an error in our code.';
}
});
}
_iosShowTouchPrompt() {
return __awaiter(this, void 0, void 0, function* () {
// As found out, iOS devices with touch capability
// needs to use TouchID plugin with text fallback function
// otherwise the device will save the fingerprint without prompting the user
try {
return yield this.touchId.verifyFingerprintWithCustomPasswordFallback('Scan your fingerprint please');
}
catch (err) {
// -3 code means the user used the Use Password fallback, which is ok for us
if ((err === null || err === void 0 ? void 0 : err.code) !== -3) {
throw err;
}
return true;
}
});
}
_sanitizeUser(user) {
return `${user}`.toLowerCase();
}
_getStorageKey(user, biometricType) {
user = this._sanitizeUser(user);
return `biometricLoginActive_${biometricType}_${user}`;
}
_getStorageToken(user, biometricType) {
user = this._sanitizeUser(user);
return `token_${biometricType}_${user}`;
}
getLang() {
var _a;
return ((_a = this.translateService) === null || _a === void 0 ? void 0 : _a.currentLang) || 'es';
}
isBiometricActive(user, biometricType) {
return __awaiter(this, void 0, void 0, function* () {
return yield this.storage.get(this._getStorageKey(user, biometricType));
});
}
isPlatformMobile() {
return (this.platform.is('ios') ||
this.platform.is('ipad') ||
this.platform.is('iphone') ||
this.platform.is('android'));
}
clearBiometricData(user, biometricType) {
return __awaiter(this, void 0, void 0, function* () {
yield this.storage.remove(this._getStorageKey(user, biometricType));
yield this.storage.remove(this._getStorageToken(user, biometricType));
if (this._isIOS()) {
yield this.keychain.remove(this._getStorageToken(user, biometricType));
}
});
}
disableBiometricData(user, biometricType) {
return __awaiter(this, void 0, void 0, function* () {
yield this.storage.set(this._getStorageKey(user, biometricType), false);
yield this.storage.remove(this._getStorageToken(user, biometricType));
if (this._isIOS()) {
yield this.keychain.remove(this._getStorageToken(user, biometricType));
}
});
}
showFingerprintId(user, biometricType) {
this.config.debug &&
console.log('[BiometricService]', 'showFingerPrintId with type:', biometricType);
this.config.debug &&
console.log('[BiometricService]', 'Is IOS:', this._isIOS());
// Retrieve the getter for iOS or Android
const tokenPromise = this._isIOS()
? this.keychain.get(this._getStorageToken(user, biometricType))
: this.storage.get(this._getStorageToken(user, biometricType));
this.config.debug &&
console.log('[BiometricService]', 'Token Promise:', tokenPromise);
// User user to retrieve status of biometric and check if it has token
return forkJoin([
from(this.storage.get(this._getStorageKey(user, biometricType))),
from(tokenPromise),
]).pipe(tap(([active, token]) => this.config.debug &&
console.log('[BiometricService]', 'Biometric result |', 'Active:', active, 'Token:', token)), map(([active, token]) => !!active && !!token));
}
/**
* Use in Login page to show the Biometric Activator page
* @param user User
* @param password Password
* @param callbackUrl URL to return after success or fail
*/
showBiometricActivator(user, password, callbackUrl) {
this.router.navigate(['biometric-activator'], {
queryParams: { user, password, callbackUrl },
});
}
/**
* Checks and returns which biometrics methods are available
* for the current device
* @returns Promise<Biometric[]>
*/
retrieveDeviceBiometrics() {
return __awaiter(this, void 0, void 0, function* () {
yield this.platform.ready();
let iosResult = '';
let androidResult = null;
if (this._isIOS()) {
try {
iosResult = yield this.touchId.isAvailable();
}
catch (err) { }
}
else {
try {
androidResult = yield this.androidFingerprintAuth.isAvailable();
}
catch (err) { }
}
return {
face: iosResult === 'face',
touch: iosResult === 'touch' || (androidResult === null || androidResult === void 0 ? void 0 : androidResult.isAvailable) || false,
};
});
}
_isIOS() {
return (this.platform.is('ios') ||
this.platform.is('ipad') ||
this.platform.is('iphone'));
}
/**
* Stores the credentials used for biometrics
* @param user Client user
* @param password Client password
*/
_storePassword(user, password, biometricType) {
return __awaiter(this, void 0, void 0, function* () {
// Save user for later use in Login
if (this._isIOS()) {
// IOS - Use Keychain
const biometric = yield this.touchId.isAvailable();
// Set Biometric login in storage
yield this.storage.set(this._getStorageKey(user, biometricType), true);
// We check for `touch` or `face`, although is irrelevant
// as the encryption prompt as already been shown
if (biometric == 'touch' || biometric == 'face') {
if (biometric == 'touch') {
yield this._iosShowTouchPrompt();
}
// Although we define the prefix "token_", it doesn't actually contain a token,
// it will contain the password in the encrypted iOS storage
yield this.keychain.set(this._getStorageToken(user, biometricType), password, false);
}
}
else if (this.platform.is('android')) {
// Android - Use AndroidFingerprintAuth
const fingerprint = yield this.androidFingerprintAuth.isAvailable();
if (fingerprint.isAvailable) {
// Fingerprint is available, encrypt!
let fingerprintResult = yield this.androidFingerprintAuth.encrypt({
clientId: user,
username: user,
password: password,
locale: this.getLang(),
disableBackup: true,
});
if (fingerprintResult.withFingerprint) {
// User used fingerprint to decrypt password
this.storage.set(this._getStorageKey(user, biometricType), true);
this.storage.set(this._getStorageToken(user, biometricType), fingerprintResult.token);
this.config.debug &&
console.log(`Token (${user}): ${fingerprintResult.token}`);
}
else if (fingerprintResult.withBackup) {
// DEPRECATED: Disabled "Use backup" option from encrypt prompt as it is useless
// console.log('Successfully authenticated with backup password!');
}
else {
this.config.debug && console.log("Didn't authenticate!");
}
}
}
});
}
/**
* Shows the FaceID dialog to the user
* @param user Client user
* @returns Promise<void>
*/
activateFaceID(user, password) {
return __awaiter(this, void 0, void 0, function* () {
const biometricConfig = {
cancelButtonTitle: 'Cancelar',
description: 'Activar FaceID',
disableBackup: true,
title: 'Activate',
fallbackButtonTitle: 'Back Button',
subtitle: 'Act',
};
try {
// Show FaceID dialog to user
yield this.faio.show(biometricConfig);
}
catch (err) {
console.log('Match not found');
console.log(err);
}
// Save encrypted password
yield this._storePassword(user, password, 'face');
});
}
/**
* Shows the TouchID dialog to the user
* @param user Client user
* @param password Client password
*/
activateTouchID(user, password) {
return this._storePassword(user, password, 'touch');
}
/**
* Executes an attempt to check for the client fingerprint
* Used mainly in Login
* @returns Promise<void>
*/
launchTouchID(user) {
return __awaiter(this, void 0, void 0, function* () {
if (this._isIOS()) {
yield this.touchId.isAvailable(); // Will reject if not available
yield this._iosShowTouchPrompt(); // Will reject if fingerprint was failed
const password = (yield this.keychain.get(this._getStorageToken(user, 'touch')));
if (password) {
return password;
}
else {
console.log('Password not found in keychain');
}
}
else {
const fingerprintAvailable = (yield this.androidFingerprintAuth.isAvailable()).isAvailable;
if (fingerprintAvailable) {
const token = (yield this.storage.get(this._getStorageToken(user, 'touch')));
if (token) {
const fingerprintResult = yield this.androidFingerprintAuth.decrypt({
clientId: user,
username: user,
token: token,
locale: this.getLang(),
});
if (fingerprintResult.password) {
return fingerprintResult.password;
}
else {
console.log('Password not found in decrypted fingerprint');
}
}
else {
console.log('Token not found in storage');
}
}
}
});
}
/**
* Executes an attempt to check for the client face
* Used mainly in Login
* @returns Promise<void>
*/
launchFaceID(user) {
return __awaiter(this, void 0, void 0, function* () {
const faceAvailable = yield this.faio.isAvailable();
if (faceAvailable) {
try {
yield this.faio.show({
cancelButtonTitle: 'Cancel',
description: 'Acceder con FaceId',
disableBackup: true,
title: 'Scan',
fallbackButtonTitle: 'FB back Button',
subtitle: 'Subtitle',
});
}
catch (err) {
console.log('No match found');
console.log(err);
return null;
}
const password = (yield this.keychain.get(this._getStorageToken(user, 'face')));
if (password) {
return password;
}
else {
console.log('Password not found in keychain');
}
}
else {
console.log('FaceID is not available');
}
});
}
}
FingerprintService.decorators = [
{ type: Injectable }
];
FingerprintService.ctorParameters = () => [
{ type: Platform },
{ type: FingerprintAIO },
{ type: AndroidFingerprintAuth },
{ type: TouchID },
{ type: Keychain },
{ type: Router },
{ type: StorageService },
{ type: TranslateService, decorators: [{ type: Optional }] },
{ type: undefined, decorators: [{ type: Inject, args: [Config,] }] }
];
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmluZ2VycHJpbnQuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2ZpbmdlcnByaW50QXV0aC9zcmMvbGliL3NlcnZpY2UvZmluZ2VycHJpbnQuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzdELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNuRSxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSw0Q0FBNEMsQ0FBQztBQUNwRixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDckQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3RELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMxQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUN0QyxPQUFPLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDbkQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUduQyxNQUFNLENBQU4sSUFBWSxTQUlYO0FBSkQsV0FBWSxTQUFTO0lBQ25CLDBCQUFhLENBQUE7SUFDYixtQ0FBc0IsQ0FBQTtJQUN0QixpQ0FBb0IsQ0FBQTtBQUN0QixDQUFDLEVBSlcsU0FBUyxLQUFULFNBQVMsUUFJcEI7QUFVRCxNQUFNLE9BQU8sa0JBQWtCO0lBQzdCLFlBQ1UsUUFBa0Is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YztZQUNkLHNCQUFzQjtZQUN0QixPQUFPO1lBQ1AsUUFBUTtZQUtSLE1BQU07WUFDTixjQUFjO1lBSmQsZ0JBQWdCLHVCQStCcEIsUUFBUTs0Q0FDUixNQUFNLFNBQUMsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdCwgSW5qZWN0YWJsZSwgT3B0aW9uYWwgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgRmluZ2VycHJpbnRBSU8gfSBmcm9tICdAaW9uaWMtbmF0aXZlL2ZpbmdlcnByaW50LWFpby9uZ3gnO1xyXG5pbXBvcnQgeyBBbmRyb2lkRmluZ2VycHJpbnRBdXRoIH0gZnJvbSAnQGlvbmljLW5hdGl2ZS9hbmRyb2lkLWZpbmdlcnByaW50LWF1dGgvbmd4JztcclxuaW1wb3J0IHsgVG91Y2hJRCB9IGZyb20gJ0Bpb25pYy1uYXRpdmUvdG91Y2gtaWQvbmd4JztcclxuaW1wb3J0IHsgS2V5Y2hhaW4gfSBmcm9tICdAaW9uaWMtbmF0aXZlL2tleWNoYWluL25neCc7XHJcbmltcG9ydCB7IFBsYXRmb3JtIH0gZnJvbSAnQGlvbmljL2FuZ3VsYXInO1xyXG5pbXBvcnQgeyBUcmFuc2xhdGVTZXJ2aWNlIH0gZnJvbSAnQG5neC10cmFuc2xhdGUvY29yZSc7XHJcbmltcG9ydCB7IGZvcmtKb2luLCBmcm9tIH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7IG1hcCwgdGFwIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xyXG5pbXBvcnQgeyBSb3V0ZXIgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xyXG5pbXBvcnQgeyBTdG9yYWdlU2VydmljZSB9IGZyb20gJy4vc3RvcmFnZS5zZXJ2aWNlJztcclxuaW1wb3J0IHsgQ29uZmlnIH0gZnJvbSAnLi4vdG9rZW5zJztcclxuaW1wb3J0IHsgRmluZ2VycHJpbnRBdXRoQ29uZmlndXJhdGlvbiB9IGZyb20gJy4uL2ludGVyZmFjZXMnO1xyXG5cclxuZXhwb3J0IGVudW0gQmlvbWV0cmljIHtcclxuICBGYWNlID0gJ2ZhY2UnLCAvLyBXaWxsIHVzZSBGYWNlIHJlY29nbml0aW9uXHJcbiAgRmluZ2VycHJpbnQgPSAnZmluZ2VyJywgLy8gV2lsbCB1c2UgZmluZ2VycHJpbnRcclxuICBDb21tb24gPSAnYmlvbWV0cmljJywgLy8gRGV2aWNlIGhhcyBzb21lIGtpbmQgb2YgYmlvbWV0cmljIGVuY3J5cHRpb25cclxufVxyXG5cclxuZXhwb3J0IGludGVyZmFjZSBCaW9tZXRyaWNzQXZhaWxhYmxlIHtcclxuICBmYWNlOiBib29sZWFuO1xyXG4gIHRvdWNoOiBib29sZWFuO1xyXG59XHJcblxyXG5leHBvcnQgdHlwZSBCaW9tZXRyaWNzID0gJ2ZhY2UnIHwgJ2ZpbmdlcnByaW50JyB8ICdiaW9tZXRyaWMnO1xyXG5cclxuQEluamVjdGFibGUoKVxyXG5leHBvcnQgY2xhc3MgRmluZ2VycHJpbnRTZXJ2aWNlIHtcclxuICBjb25zdHJ1Y3RvcihcclxuICAgIHByaXZhdGUgcGxhdGZvcm06IFBsYXRmb3JtLFxyXG4gICAgcHJpdmF0ZSBmYWlvOiBGaW5nZXJwcmludEFJTyxcclxuICAgIHByaXZhdGUgYW5kcm9pZEZpbmdlcnByaW50QXV0aDogQW5kcm9pZEZpbmdlcnByaW50QXV0aCxcclxuICAgIHByaXZhdGUgdG91Y2hJZDogVG91Y2hJRCxcclxuICAgIHByaXZhdGUga2V5Y2hhaW46IEtleWNoYWluLFxyXG4gICAgcHJpdmF0ZSByb3V0ZXI6IFJvdXRlcixcclxuICAgIHByaXZhdGUgc3RvcmFnZTogU3RvcmFnZVNlcnZpY2UsXHJcbiAgICBAT3B0aW9uYWwoKSBwcml2YXRlIHRyYW5zbGF0ZVNlcnZpY2U6IFRyYW5zbGF0ZVNlcnZpY2UsXHJcbiAgICBASW5qZWN0KENvbmZpZykgcHJpdmF0ZSByZWFkb25seSBjb25maWc6IEZpbmdlcnByaW50QXV0aENvbmZpZ3VyYXRpb24sXHJcbiAgKSB7fVxyXG5cclxuICAvKipcclxuICAgKiBVc2UgdGhpcyBtZXRob2QgaW4gTG9naW4gcGFnZSB0byBjaGVjayBpZiB1c2VyXHJcbiAgICogc2hvdWxkIGJlIHJlZGlyZWN0ZWQgdG8gdGhlIEJpb21ldHJpYyBBY3RpdmF0b3JcclxuICAgKi9cclxuICBwdWJsaWMgYXN5bmMgY2hlY2tJZk5lZWRzQmlvbWV0cmljKHVzZXI6IHN0cmluZykge1xyXG4gICAgaWYgKCF0aGlzLmlzUGxhdGZvcm1Nb2JpbGUoKSkge1xyXG4gICAgICB0aGlzLmNvbmZpZy5kZWJ1ZyAmJlxyXG4gICAgICAgIGNvbnNvbGUubG9nKFxyXG4gICAgICAgICAgJ1tCaW9tZXRyaWNTZXJ2aWNlXScsXHJcbiAgICAgICAgICAnaXNQbGF0Zm9ybU1vYmlsZSgpOicsXHJcbiAgICAgICAgICB0aGlzLmlzUGxhdGZvcm1Nb2JpbGUoKSxcclxuICAgICAgICApO1xyXG4gICAgICByZXR1cm4gZmFsc2U7XHJcbiAgICB9XHJcbiAgICAvLyBDaGVjayBGYWNlSUQgZW5hYmxlZFxyXG4gICAgY29uc3QgYmlvbWV0cmljRmFjZUFjdGl2ZSA9IGF3YWl0IHRoaXMuaXNCaW9tZXRyaWNBY3RpdmUodXNlciwgJ2ZhY2UnKTtcclxuICAgIHRoaXMuY29uZmlnLmRlYnVnICYmXHJcbiAgICAgIGNvbnNvbGUubG9nKFxyXG4gICAgICAgICdbQmlvbWV0cmljU2VydmljZV0nLFxyXG4gICAgICAgICdiaW9tZXRyaWNGYWNlQWN0aXZlOicsXHJcbiAgICAgICAgYmlvbWV0cmljRmFjZUFjdGl2ZSxcclxuICAgICAgKTtcclxuICAgIC8vIENoZWNrIFRvdWNoSUQgZW5hYmxlZFxyXG4gICAgY29uc3QgYmlvbWV0cmljVG91Y2hBY3RpdmUgPSBhd2FpdCB0aGlzLmlzQmlvbWV0cmljQWN0aXZlKHVzZXIsICd0b3VjaCcpO1xyXG4gICAgdGhpcy5jb25maWcuZGVidWcgJiZcclxuICAgICAgY29uc29sZS5sb2coXHJcbiAgICAgICAgJ1tCaW9tZXRyaWNTZXJ2aWNlXScsXHJcbiAgICAgICAgJ2Jpb21ldHJpY1RvdWNoQWN0aXZlOicsXHJcbiAgICAgICAgYmlvbWV0cmljVG91Y2hBY3RpdmUsXHJcbiAgICAgICk7XHJcbiAgICAvLyBDaGVjayBpZiBkZXZpY2UgaGFzIHNvbWUgYmlvbWV0cmljIGNhcGFiaWxpdHlcclxuICAgIGNvbnN0IGJpb21ldHJpY0NhcGFiaWxpdGllcyA9IGF3YWl0IHRoaXMucmV0cmlldmVEZXZpY2VCaW9tZXRyaWNzKCk7XHJcbiAgICB0aGlzLmNvbmZpZy5kZWJ1ZyAmJlxyXG4gICAgICBjb25zb2xlLmxvZyhcclxuICAgICAgICAnW0Jpb21ldHJpY1NlcnZpY2VdJyxcclxuICAgICAgICAnYmlvbWV0cmljQ2FwYWJpbGl0aWVzOicsXHJcbiAgICAgICAgYmlvbWV0cmljQ2FwYWJpbGl0aWVzLFxyXG4gICAgICApO1xyXG4gICAgY29uc3QgaXNCaW9tZXRyaWNDYXBhYmxlID1cclxuICAgICAgYmlvbWV0cmljQ2FwYWJpbGl0aWVzLmZhY2UgfHwgYmlvbWV0cmljQ2FwYWJpbGl0aWVzLnRvdWNoO1xyXG4gICAgLy8gUmVkaXJlY3QgdG8gQmlvbWV0cmljIEFjdGl2YXRvciBpZiBjb25kaXRpb25zIGFyZSBtZXRcclxuICAgIHJldHVybiAoXHJcbiAgICAgIGJpb21ldHJpY0ZhY2VBY3RpdmUgPT09IG51bGwgJiZcclxuICAgICAgYmlvbWV0cmljVG91Y2hBY3RpdmUgPT09IG51bGwgJiZcclxuICAgICAgaXNCaW9tZXRyaWNDYXBhYmxlXHJcbiAgICApO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogVXNlIGluIExvZ2luIHBhZ2UgdG8gb3BlbiBCaW9tZXRyaWMgcHJvbXB0XHJcbiAgICogQHBhcmFtIHVzZXIgc3RyaW5nXHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIHNob3dCaW9tZXRyaWNQcm9tcHQodXNlcjogc3RyaW5nKSB7XHJcbiAgICBjb25zdCBmYWNlID0gYXdhaXQgdGhpcy5zaG93RmluZ2VycHJpbnRJZCh1c2VyLCAnZmFjZScpLnRvUHJvbWlzZSgpO1xyXG4gICAgY29uc3QgdG91Y2ggPSBhd2FpdCB0aGlzLnNob3dGaW5nZXJwcmludElkKHVzZXIsICd0b3VjaCcpLnRvUHJvbWlzZSgpO1xyXG4gICAgLy8gSGVyZSB3ZSBoYXZlIGEgYmlnIGRpbGVtbWFcclxuICAgIC8vIEluIGNhc2UgdGhlIHVzZXIgZGV2aWNlIGlzIGNhcGFibGUgb2YgRmFjZUlEIGFuZCBUb3VjaElELFxyXG4gICAgLy8gZm9yIHRoZSBtb21lbnQgd2Ugd2lsbCBtYXJrIGBmYWNlYCBhcyBwcmVmZXJlbnQsXHJcbiAgICAvLyBhcyBpT1MgdGhlIG1vc3RseSBvbmUgd2l0aCBGYWNlSURcclxuICAgIGlmIChmYWNlKSB7XHJcbiAgICAgIC8vIExvZ2luIHdpdGggRmFjZUlEXHJcbiAgICAgIGNvbnN0IHBhc3N3b3JkID0gYXdhaXQgdGhpcy5sYXVuY2hGYWNlSUQodXNlcik7XHJcbiAgICAgIGlmIChwYXNzd29yZCkge1xyXG4gICAgICAgIHJldHVybiB7IHVzZXIsIHBhc3N3b3JkIH07XHJcbiAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgdGhyb3cgJ1NvbWV0aGluZyB3ZW50IHdyb25nIGluIGxhdW5jaEZhY2VJRCc7XHJcbiAgICAgIH1cclxuICAgIH0gZWxzZSBpZiAodG91Y2gpIHtcclxuICAgICAgLy8gTG9naW4gd2l0aCBUb3VjaElEXHJcbiAgICAgIGNvbnN0IHBhc3N3b3JkID0gYXdhaXQgdGhpcy5sYXVuY2hUb3VjaElEKHVzZXIpO1xyXG4gICAgICBpZiAocGFzc3dvcmQpIHtcclxuICAgICAgICByZXR1cm4geyB1c2VyLCBwYXNzd29yZCB9O1xyXG4gICAgICB9IGVsc2Uge1xyXG4gICAgICAgIHRocm93ICdTb21ldGhpbmcgd2VudCB3cm9uZyBpbiBsYXVuY2hUb3VjaElEJztcclxuICAgICAgfVxyXG4gICAgfSBlbHNlIHtcclxuICAgICAgdGhyb3cgJ0xvb2tzIGxpa2Ugd2UgaGF2ZSBhbiBlcnJvciBpbiBvdXIgY29kZS4nO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBhc3luYyBfaW9zU2hvd1RvdWNoUHJvbXB0KCkge1xyXG4gICAgLy8gQXMgZm91bmQgb3V0LCBpT1MgZGV2aWNlcyB3aXRoIHRvdWNoIGNhcGFiaWxpdHlcclxuICAgIC8vIG5lZWRzIHRvIHVzZSBUb3VjaElEIHBsdWdpbiB3aXRoIHRleHQgZmFsbGJhY2sgZnVuY3Rpb25cclxuICAgIC8vIG90aGVyd2lzZSB0aGUgZGV2aWNlIHdpbGwgc2F2ZSB0aGUgZmluZ2VycHJpbnQgd2l0aG91dCBwcm9tcHRpbmcgdGhlIHVzZXJcclxuICAgIHRyeSB7XHJcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLnRvdWNoSWQudmVyaWZ5RmluZ2VycHJpbnRXaXRoQ3VzdG9tUGFzc3dvcmRGYWxsYmFjayhcclxuICAgICAgICAnU2NhbiB5b3VyIGZpbmdlcnByaW50IHBsZWFzZScsXHJcbiAgICAgICk7XHJcbiAgICB9IGNhdGNoIChlcnIpIHtcclxuICAgICAgLy8gLTMgY29kZSBtZWFucyB0aGUgdXNlciB1c2VkIHRoZSBVc2UgUGFzc3dvcmQgZmFsbGJhY2ssIHdoaWNoIGlzIG9rIGZvciB1c1xyXG4gICAgICBpZiAoZXJyPy5jb2RlICE9PSAtMykge1xyXG4gICAgICAgIHRocm93IGVycjtcclxuICAgICAgfVxyXG4gICAgICByZXR1cm4gdHJ1ZTtcclxuICAgIH1cclxuICB9XHJcblxyXG4gIHByaXZhdGUgX3Nhbml0aXplVXNlcih1c2VyOiBzdHJpbmcpIHtcclxuICAgIHJldHVybiBgJHt1c2VyfWAudG9Mb3dlckNhc2UoKTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgX2dldFN0b3JhZ2VLZXkoXHJcbiAgICB1c2VyOiBzdHJpbmcsXHJcbiAgICBiaW9tZXRyaWNUeXBlOiBrZXlvZiBCaW9tZXRyaWNzQXZhaWxhYmxlLFxyXG4gICkge1xyXG4gICAgdXNlciA9IHRoaXMuX3Nhbml0aXplVXNlcih1c2VyKTtcclxuICAgIHJldHVybiBgYmlvbWV0cmljTG9naW5BY3RpdmVfJHtiaW9tZXRyaWNUeXBlfV8ke3VzZXJ9YDtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgX2dldFN0b3JhZ2VUb2tlbihcclxuICAgIHVzZXI6IHN0cmluZyxcclxuICAgIGJpb21ldHJpY1R5cGU6IGtleW9mIEJpb21ldHJpY3NBdmFpbGFibGUsXHJcbiAgKSB7XHJcbiAgICB1c2VyID0gdGhpcy5fc2FuaXRpemVVc2VyKHVzZXIpO1xyXG4gICAgcmV0dXJuIGB0b2tlbl8ke2Jpb21ldHJpY1R5cGV9XyR7dXNlcn1gO1xyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBnZXRMYW5nKCkge1xyXG4gICAgcmV0dXJuIHRoaXMudHJhbnNsYXRlU2VydmljZT8uY3VycmVudExhbmcgfHwgJ2VzJztcclxuICB9XHJcblxyXG4gIHB1YmxpYyBhc3luYyBpc0Jpb21ldHJpY0FjdGl2ZShcclxuICAgIHVzZXI6IHN0cmluZyxcclxuICAgIGJpb21ldHJpY1R5cGU6IGtleW9mIEJpb21ldHJpY3NBdmFpbGFibGUsXHJcbiAgKSB7XHJcbiAgICByZXR1cm4gYXdhaXQgdGhpcy5zdG9yYWdlLmdldCh0aGlzLl9nZXRTdG9yYWdlS2V5KHVzZXIsIGJpb21ldHJpY1R5cGUpKTtcclxuICB9XHJcblxyXG4gIHB1YmxpYyBpc1BsYXRmb3JtTW9iaWxlKCkge1xyXG4gICAgcmV0dXJuIChcclxuICAgICAgdGhpcy5wbGF0Zm9ybS5pcygnaW9zJykgfHxcclxuICAgICAgdGhpcy5wbGF0Zm9ybS5pcygnaXBhZCcpIHx8XHJcbiAgICAgIHRoaXMucGxhdGZvcm0uaXMoJ2lwaG9uZScpIHx8XHJcbiAgICAgIHRoaXMucGxhdGZvcm0uaXMoJ2FuZHJvaWQnKVxyXG4gICAgKTtcclxuICB9XHJcblxyXG4gIHB1YmxpYyBhc3luYyBjbGVhckJpb21ldHJpY0RhdGEoXHJcbiAgICB1c2VyOiBzdHJpbmcsXHJcbiAgICBiaW9tZXRyaWNUeXBlOiBrZXlvZiBCaW9tZXRyaWNzQXZhaWxhYmxlLFxyXG4gICkge1xyXG4gICAgYXdhaXQgdGhpcy5zdG9yYWdlLnJlbW92ZSh0aGlzLl9nZXRTdG9yYWdlS2V5KHVzZXIsIGJpb21ldHJpY1R5cGUpKTtcclxuICAgIGF3YWl0IHRoaXMuc3RvcmFnZS5yZW1vdmUodGhpcy5fZ2V0U3RvcmFnZVRva2VuKHVzZXIsIGJpb21ldHJpY1R5cGUpKTtcclxuICAgIGlmICh0aGlzLl9pc0lPUygpKSB7XHJcbiAgICAgIGF3YWl0IHRoaXMua2V5Y2hhaW4ucmVtb3ZlKHRoaXMuX2dldFN0b3JhZ2VUb2tlbih1c2VyLCBiaW9tZXRyaWNUeXBlKSk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICBwdWJsaWMgYXN5bmMgZGlzYWJsZUJpb21ldHJpY0RhdGEoXHJcbiAgICB1c2VyOiBzdHJpbmcsXHJcbiAgICBiaW9tZXRyaWNUeXBlOiBrZXlvZiBCaW9tZXRyaWNzQXZhaWxhYmxlLFxyXG4gICkge1xyXG4gICAgYXdhaXQgdGhpcy5zdG9yYWdlLnNldCh0aGlzLl9nZXRTdG9yYWdlS2V5KHVzZXIsIGJpb21ldHJpY1R5cGUpLCBmYWxzZSk7XHJcbiAgICBhd2FpdCB0aGlzLnN0b3JhZ2UucmVtb3ZlKHRoaXMuX2dldFN0b3JhZ2VUb2tlbih1c2VyLCBiaW9tZXRyaWNUeXBlKSk7XHJcbiAgICBpZiAodGhpcy5faXNJT1MoKSkge1xyXG4gICAgICBhd2FpdCB0aGlzLmtleWNoYWluLnJlbW92ZSh0aGlzLl9nZXRTdG9yYWdlVG9rZW4odXNlciwgYmlvbWV0cmljVHlwZSkpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcHVibGljIHNob3dGaW5nZXJwcmludElkKFxyXG4gICAgdXNlcjogc3RyaW5nLFxyXG4gICAgYmlvbWV0cmljVHlwZToga2V5b2YgQmlvbWV0cmljc0F2YWlsYWJsZSxcclxuICApIHtcclxuICAgIHRoaXMuY29uZmlnLmRlYnVnICYmXHJcbiAgICAgIGNvbnNvbGUubG9nKFxyXG4gICAgICAgICdbQmlvbWV0cmljU2VydmljZV0nLFxyXG4gICAgICAgICdzaG93RmluZ2VyUHJpbnRJZCB3aXRoIHR5cGU6JyxcclxuICAgICAgICBiaW9tZXRyaWNUeXBlLFxyXG4gICAgICApO1xyXG4gICAgdGhpcy5jb25maWcuZGVidWcgJiZcclxuICAgICAgY29uc29sZS5sb2coJ1tCaW9tZXRyaWNTZXJ2aWNlXScsICdJcyBJT1M6JywgdGhpcy5faXNJT1MoKSk7XHJcbiAgICAvLyBSZXRyaWV2ZSB0aGUgZ2V0dGVyIGZvciBpT1Mgb3IgQW5kcm9pZFxyXG4gICAgY29uc3QgdG9rZW5Qcm9taXNlID0gdGhpcy5faXNJT1MoKVxyXG4gICAgICA/IHRoaXMua2V5Y2hhaW4uZ2V0KHRoaXMuX2dldFN0b3JhZ2VUb2tlbih1c2VyLCBiaW9tZXRyaWNUeXBlKSlcclxuICAgICAgOiB0aGlzLnN0b3JhZ2UuZ2V0KHRoaXMuX2dldFN0b3JhZ2VUb2tlbih1c2VyLCBiaW9tZXRyaWNUeXBlKSk7XHJcbiAgICB0aGlzLmNvbmZpZy5kZWJ1ZyAmJlxyXG4gICAgICBjb25zb2xlLmxvZygnW0Jpb21ldHJpY1NlcnZpY2VdJywgJ1Rva2VuIFByb21pc2U6JywgdG9rZW5Qcm9taXNlKTtcclxuICAgIC8vIFVzZXIgdXNlciB0byByZXRyaWV2ZSBzdGF0dXMgb2YgYmlvbWV0cmljIGFuZCBjaGVjayBpZiBpdCBoYXMgdG9rZW5cclxuICAgIHJldHVybiBmb3JrSm9pbihbXHJcbiAgICAgIGZyb20odGhpcy5zdG9yYWdlLmdldCh0aGlzLl9nZXRTdG9yYWdlS2V5KHVzZXIsIGJpb21ldHJpY1R5cGUpKSksXHJcbiAgICAgIGZyb20odG9rZW5Qcm9taXNlKSxcclxuICAgIF0pLnBpcGUoXHJcbiAgICAgIHRhcChcclxuICAgICAgICAoW2FjdGl2ZSwgdG9rZW5dKSA9PlxyXG4gICAgICAgICAgdGhpcy5jb25maWcuZGVidWcgJiZcclxuICAgICAgICAgIGNvbnNvbGUubG9nKFxyXG4gICAgICAgICAgICAnW0Jpb21ldHJpY1NlcnZpY2VdJyxcclxuICAgICAgICAgICAgJ0Jpb21ldHJpYyByZXN1bHQgfCcsXHJcbiAgICAgICAgICAgICdBY3RpdmU6JyxcclxuICAgICAgICAgICAgYWN0aXZlLFxyXG4gICAgICAgICAgICAnVG9rZW46JyxcclxuICAgICAgICAgICAgdG9rZW4sXHJcbiAgICAgICAgICApLFxyXG4gICAgICApLFxyXG4gICAgICBtYXAoKFthY3RpdmUsIHRva2VuXSkgPT4gISFhY3RpdmUgJiYgISF0b2tlbiksXHJcbiAgICApO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogVXNlIGluIExvZ2luIHBhZ2UgdG8gc2hvdyB0aGUgQmlvbWV0cmljIEFjdGl2YXRvciBwYWdlXHJcbiAgICogQHBhcmFtIHVzZXIgVXNlclxyXG4gICAqIEBwYXJhbSBwYXNzd29yZCBQYXNzd29yZFxyXG4gICAqIEBwYXJhbSBjYWxsYmFja1VybCBVUkwgdG8gcmV0dXJuIGFmdGVyIHN1Y2Nlc3Mgb3IgZmFpbFxyXG4gICAqL1xyXG4gIHB1YmxpYyBzaG93QmlvbWV0cmljQWN0aXZhdG9yKFxyXG4gICAgdXNlcjogc3RyaW5nLFxyXG4gICAgcGFzc3dvcmQ6IHN0cmluZyxcclxuICAgIGNhbGxiYWNrVXJsOiBzdHJpbmcsXHJcbiAgKSB7XHJcbiAgICB0aGlzLnJvdXRlci5uYXZpZ2F0ZShbJ2Jpb21ldHJpYy1hY3RpdmF0b3InXSwge1xyXG4gICAgICBxdWVyeVBhcmFtczogeyB1c2VyLCBwYXNzd29yZCwgY2FsbGJhY2tVcmwgfSxcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogQ2hlY2tzIGFuZCByZXR1cm5zIHdoaWNoIGJpb21ldHJpY3MgbWV0aG9kcyBhcmUgYXZhaWxhYmxlXHJcbiAgICogZm9yIHRoZSBjdXJyZW50IGRldmljZVxyXG4gICAqIEByZXR1cm5zIFByb21pc2U8QmlvbWV0cmljW10+XHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIHJldHJpZXZlRGV2aWNlQmlvbWV0cmljcygpOiBQcm9taXNlPEJpb21ldHJpY3NBdmFpbGFibGU+IHtcclxuICAgIGF3YWl0IHRoaXMucGxhdGZvcm0ucmVhZHkoKTtcclxuICAgIGxldCBpb3NSZXN1bHQgPSAnJztcclxuICAgIGxldCBhbmRyb2lkUmVzdWx0ID0gbnVsbDtcclxuICAgIGlmICh0aGlzLl9pc0lPUygpKSB7XHJcbiAgICAgIHRyeSB7XHJcbiAgICAgICAgaW9zUmVzdWx0ID0gYXdhaXQgdGhpcy50b3VjaElkLmlzQXZhaWxhYmxlKCk7XHJcbiAgICAgIH0gY2F0Y2ggKGVycikge31cclxuICAgIH0gZWxzZSB7XHJcbiAgICAgIHRyeSB7XHJcbiAgICAgICAgYW5kcm9pZFJlc3VsdCA9IGF3YWl0IHRoaXMuYW5kcm9pZEZpbmdlcnByaW50QXV0aC5pc0F2YWlsYWJsZSgpO1xyXG4gICAgICB9IGNhdGNoIChlcnIpIHt9XHJcbiAgICB9XHJcbiAgICByZXR1cm4ge1xyXG4gICAgICBmYWNlOiBpb3NSZXN1bHQgPT09ICdmYWNlJyxcclxuICAgICAgdG91Y2g6IGlvc1Jlc3VsdCA9PT0gJ3RvdWNoJyB8fCBhbmRyb2lkUmVzdWx0Py5pc0F2YWlsYWJsZSB8fCBmYWxzZSxcclxuICAgIH07XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIF9pc0lPUygpIHtcclxuICAgIHJldHVybiAoXHJcbiAgICAgIHRoaXMucGxhdGZvcm0uaXMoJ2lvcycpIHx8XHJcbiAgICAgIHRoaXMucGxhdGZvcm0uaXMoJ2lwYWQnKSB8fFxyXG4gICAgICB0aGlzLnBsYXRmb3JtLmlzKCdpcGhvbmUnKVxyXG4gICAgKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFN0b3JlcyB0aGUgY3JlZGVudGlhbHMgdXNlZCBmb3IgYmlvbWV0cmljc1xyXG4gICAqIEBwYXJhbSB1c2VyIENsaWVudCB1c2VyXHJcbiAgICogQHBhcmFtIHBhc3N3b3JkIENsaWVudCBwYXNzd29yZFxyXG4gICAqL1xyXG4gIHByaXZhdGUgYXN5bmMgX3N0b3JlUGFzc3dvcmQoXHJcbiAgICB1c2VyOiBzdHJpbmcsXHJcbiAgICBwYXNzd29yZDogc3RyaW5nLFxyXG4gICAgYmlvbWV0cmljVHlwZToga2V5b2YgQmlvbWV0cmljc0F2YWlsYWJsZSxcclxuICApIHtcclxuICAgIC8vIFNhdmUgdXNlciBmb3IgbGF0ZXIgdXNlIGluIExvZ2luXHJcbiAgICBpZiAodGhpcy5faXNJT1MoKSkge1xyXG4gICAgICAvLyBJT1MgLSBVc2UgS2V5Y2hhaW5cclxuICAgICAgY29uc3QgYmlvbWV0cmljID0gYXdhaXQgdGhpcy50b3VjaElkLmlzQXZhaWxhYmxlKCk7XHJcbiAgICAgIC8vIFNldCBCaW9tZXRyaWMgbG9naW4gaW4gc3RvcmFnZVxyXG4gICAgICBhd2FpdCB0aGlzLnN0b3JhZ2Uuc2V0KHRoaXMuX2dldFN0b3JhZ2VLZXkodXNlciwgYmlvbWV0cmljVHlwZSksIHRydWUpO1xyXG4gICAgICAvLyBXZSBjaGVjayBmb3IgYHRvdWNoYCBvciBgZmFjZWAsIGFsdGhvdWdoIGlzIGlycmVsZXZhbnRcclxuICAgICAgLy8gYXMgdGhlIGVuY3J5cHRpb24gcHJvbXB0IGFzIGFscmVhZHkgYmVlbiBzaG93blxyXG4gICAgICBpZiAoYmlvbWV0cmljID09ICd0b3VjaCcgfHwgYmlvbWV0cmljID09ICdmYWNlJykge1xyXG4gICAgICAgIGlmIChiaW9tZXRyaWMgPT0gJ3RvdWNoJykge1xyXG4gICAgICAgICAgYXdhaXQgdGhpcy5faW9zU2hvd1RvdWNoUHJvbXB0KCk7XHJcbiAgICAgICAgfVxyXG4gICAgICAgIC8vIEFsdGhvdWdoIHdlIGRlZmluZSB0aGUgcHJlZml4IFwidG9rZW5fXCIsIGl0IGRvZXNuJ3QgYWN0dWFsbHkgY29udGFpbiBhIHRva2VuLFxyXG4gICAgICAgIC8vIGl0IHdpbGwgY29udGFpbiB0aGUgcGFzc3dvcmQgaW4gdGhlIGVuY3J5cHRlZCBpT1Mgc3RvcmFnZVxyXG4gICAgICAgIGF3YWl0IHRoaXMua2V5Y2hhaW4uc2V0KFxyXG4gICAgICAgICAgdGhpcy5fZ2V0U3RvcmFnZVRva2VuKHVzZXIsIGJpb21ldHJpY1R5cGUpLFxyXG4gICAgICAgICAgcGFzc3dvcmQsXHJcbiAgICAgICAgICBmYWxzZSxcclxuICAgICAgICApO1xyXG4gICAgICB9XHJcbiAgICB9IGVsc2UgaWYgKHRoaXMucGxhdGZvcm0uaXMoJ2FuZHJvaWQnKSkge1xyXG4gICAgICAvLyBBbmRyb2lkIC0gVXNlIEFuZHJvaWRGaW5nZXJwcmludEF1dGhcclxuICAgICAgY29uc3QgZmluZ2VycHJpbnQgPSBhd2FpdCB0aGlzLmFuZHJvaWRGaW5nZXJwcmludEF1dGguaXNBdmFpbGFibGUoKTtcclxuICAgICAgaWYgKGZpbmdlcnByaW50LmlzQXZhaWxhYmxlKSB7XHJcbiAgICAgICAgLy8gRmluZ2VycHJpbnQgaXMgYXZhaWxhYmxlLCBlbmNyeXB0IVxyXG4gICAgICAgIGxldCBmaW5nZXJwcmludFJlc3VsdCA9IGF3YWl0IHRoaXMuYW5kcm9pZEZpbmdlcnByaW50QXV0aC5lbmNyeXB0KHtcclxuICAgICAgICAgIGNsaWVudElkOiB1c2VyLFxyXG4gICAgICAgICAgdXNlcm5hbWU6IHVzZXIsXHJcbiAgICAgICAgICBwYXNzd29yZDogcGFzc3dvcmQsXHJcbiAgICAgICAgICBsb2NhbGU6IHRoaXMuZ2V0TGFuZygpLFxyXG4gICAgICAgICAgZGlzYWJsZUJhY2t1cDogdHJ1ZSxcclxuICAgICAgICB9KTtcclxuICAgICAgICBpZiAoZmluZ2VycHJpbnRSZXN1bHQud2l0aEZpbmdlcnByaW50KSB7XHJcbiAgICAgICAgICAvLyBVc2VyIHVzZWQgZmluZ2VycHJpbnQgdG8gZGVjcnlwdCBwYXNzd29yZFxyXG4gICAgICAgICAgdGhpcy5zdG9yYWdlLnNldCh0aGlzLl9nZXRTdG9yYWdlS2V5KHVzZXIsIGJpb21ldHJpY1R5cGUpLCB0cnVlKTtcclxuICAgICAgICAgIHRoaXMuc3RvcmFnZS5zZXQoXHJcbiAgICAgICAgICAgIHRoaXMuX2dldFN0b3JhZ2VUb2tlbih1c2VyLCBiaW9tZXRyaWNUeXBlKSxcclxuICAgICAgICAgICAgZmluZ2VycHJpbnRSZXN1bHQudG9rZW4sXHJcbiAgICAgICAgICApO1xyXG4gICAgICAgICAgdGhpcy5jb25maWcuZGVidWcgJiZcclxuICAgICAgICAgICAgY29uc29sZS5sb2coYFRva2VuICgke3VzZXJ9KTogJHtmaW5nZXJwcmludFJlc3VsdC50b2tlbn1gKTtcclxuICAgICAgICB9IGVsc2UgaWYgKGZpbmdlcnByaW50UmVzdWx0LndpdGhCYWNrdXApIHtcclxuICAgICAgICAgIC8vIERFUFJFQ0FURUQ6IERpc2FibGVkIFwiVXNlIGJhY2t1cFwiIG9wdGlvbiBmcm9tIGVuY3J5cHQgcHJvbXB0IGFzIGl0IGlzIHVzZWxlc3NcclxuICAgICAgICAgIC8vIGNvbnNvbGUubG9nKCdTdWNjZXNzZnVsbHkgYXV0aGVudGljYXRlZCB3aXRoIGJhY2t1cCBwYXNzd29yZCEnKTtcclxuICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgdGhpcy5jb25maWcuZGVidWcgJiYgY29uc29sZS5sb2coXCJEaWRuJ3QgYXV0aGVudGljYXRlIVwiKTtcclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH1cclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFNob3dzIHRoZSBGYWNlSUQgZGlhbG9nIHRvIHRoZSB1c2VyXHJcbiAgICogQHBhcmFtIHVzZXIgQ2xpZW50IHVzZXJcclxuICAgKiBAcmV0dXJucyBQcm9taXNlPHZvaWQ+XHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIGFjdGl2YXRlRmFjZUlEKHVzZXI6IHN0cmluZywgcGFzc3dvcmQ6IHN0cmluZykge1xyXG4gICAgY29uc3QgYmlvbWV0cmljQ29uZmlnID0ge1xyXG4gICAgICBjYW5jZWxCdXR0b25UaXRsZTogJ0NhbmNlbGFyJyxcclxuICAgICAgZGVzY3JpcHRpb246ICdBY3RpdmFyIEZhY2VJRCcsXHJcbiAgICAgIGRpc2FibGVCYWNrdXA6IHRydWUsXHJcbiAgICAgIHRpdGxlOiAnQWN0aXZhdGUnLFxyXG4gICAgICBmYWxsYmFja0J1dHRvblRpdGxlOiAnQmFjayBCdXR0b24nLFxyXG4gICAgICBzdWJ0aXRsZTogJ0FjdCcsXHJcbiAgICB9O1xyXG4gICAgdHJ5IHtcclxuICAgICAgLy8gU2hvdyBGYWNlSUQgZGlhbG9nIHRvIHVzZXJcclxuICAgICAgYXdhaXQgdGhpcy5mYWlvLnNob3coYmlvbWV0cmljQ29uZmlnKTtcclxuICAgIH0gY2F0Y2ggKGVycikge1xyXG4gICAgICBjb25zb2xlLmxvZygnTWF0Y2ggbm90IGZvdW5kJyk7XHJcbiAgICAgIGNvbnNvbGUubG9nKGVycik7XHJcbiAgICB9XHJcbiAgICAvLyBTYXZlIGVuY3J5cHRlZCBwYXNzd29yZFxyXG4gICAgYXdhaXQgdGhpcy5fc3RvcmVQYXNzd29yZCh1c2VyLCBwYXNzd29yZCwgJ2ZhY2UnKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFNob3dzIHRoZSBUb3VjaElEIGRpYWxvZyB0byB0aGUgdXNlclxyXG4gICAqIEBwYXJhbSB1c2VyIENsaWVudCB1c2VyXHJcbiAgICogQHBhcmFtIHBhc3N3b3JkIENsaWVudCBwYXNzd29yZFxyXG4gICAqL1xyXG4gIHB1YmxpYyBhY3RpdmF0ZVRvdWNoSUQodXNlcjogc3RyaW5nLCBwYXNzd29yZDogc3RyaW5nKSB7XHJcbiAgICByZXR1cm4gdGhpcy5fc3RvcmVQYXNzd29yZCh1c2VyLCBwYXNzd29yZCwgJ3RvdWNoJyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBFeGVjdXRlcyBhbiBhdHRlbXB0IHRvIGNoZWNrIGZvciB0aGUgY2xpZW50IGZpbmdlcnByaW50XHJcbiAgICogVXNlZCBtYWlubHkgaW4gTG9naW5cclxuICAgKiBAcmV0dXJucyBQcm9taXNlPHZvaWQ+XHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIGxhdW5jaFRvdWNoSUQodXNlcjogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPiB7XHJcbiAgICBpZiAodGhpcy5faXNJT1MoKSkge1xyXG4gICAgICBhd2FpdCB0aGlzLnRvdWNoSWQuaXNBdmFpbGFibGUoKTsgLy8gV2lsbCByZWplY3QgaWYgbm90IGF2YWlsYWJsZVxyXG4gICAgICBhd2FpdCB0aGlzLl9pb3NTaG93VG91Y2hQcm9tcHQoKTsgLy8gV2lsbCByZWplY3QgaWYgZmluZ2VycHJpbnQgd2FzIGZhaWxlZFxyXG4gICAgICBjb25zdCBwYXNzd29yZCA9IDxzdHJpbmc+KFxyXG4gICAgICAgIGF3YWl0IHRoaXMua2V5Y2hhaW4uZ2V0KHRoaXMuX2dldFN0b3JhZ2VUb2tlbih1c2VyLCAndG91Y2gnKSlcclxuICAgICAgKTtcclxuICAgICAgaWYgK