angular-auth-oidc-client
Version:
Angular Lib for OpenID Connect & OAuth2
378 lines • 65 kB
JavaScript
import { inject, Injectable } from '@angular/core';
import { base64url } from 'rfc4648';
import { from, of } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { JwkExtractor } from '../extractors/jwk.extractor';
import { LoggerService } from '../logging/logger.service';
import { TokenHelperService } from '../utils/tokenHelper/token-helper.service';
import { JwkWindowCryptoService } from './jwk-window-crypto.service';
import { JwtWindowCryptoService } from './jwt-window-crypto.service';
import { alg2kty, getImportAlg, getVerifyAlg } from './token-validation.helper';
import * as i0 from "@angular/core";
// http://openid.net/specs/openid-connect-implicit-1_0.html
// id_token
// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
//
// id_token C2: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified
// by the iss (issuer) Claim as an audience.The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
// or if it contains additional audiences not trusted by the Client.
//
// id_token C3: If the ID Token contains multiple audiences, the Client SHOULD verify that an azp Claim is present.
//
// id_token C4: If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
//
// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the
// alg Header Parameter of the JOSE Header.The Client MUST use the keys provided by the Issuer.
//
// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the OpenID Connect
// Core 1.0
// [OpenID.Core] specification.
//
// id_token C7: The current time MUST be before the time represented by the exp Claim (possibly allowing for some small leeway to account
// for clock skew).
//
// id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
//
// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one that was sent
// in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.The precise method for detecting replay attacks
// is Client specific.
//
// id_token C10: If the acr Claim was requested, the Client SHOULD check that the asserted Claim Value is appropriate.
// The meaning and processing of acr Claim Values is out of scope for this document.
//
// id_token C11: When a max_age request is made, the Client SHOULD check the auth_time Claim value and request re- authentication
// if it determines too much time has elapsed since the last End- User authentication.
// Access Token Validation
// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA]
// for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
// access_token C2: Take the left- most half of the hash and base64url- encode it.
// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash is present
// in the ID Token.
export class TokenValidationService {
constructor() {
this.keyAlgorithms = [
'HS256',
'HS384',
'HS512',
'RS256',
'RS384',
'RS512',
'ES256',
'ES384',
'PS256',
'PS384',
'PS512',
];
this.tokenHelperService = inject(TokenHelperService);
this.loggerService = inject(LoggerService);
this.jwkExtractor = inject(JwkExtractor);
this.jwkWindowCryptoService = inject(JwkWindowCryptoService);
this.jwtWindowCryptoService = inject(JwtWindowCryptoService);
}
static { this.refreshTokenNoncePlaceholder = '--RefreshToken--'; }
// id_token C7: The current time MUST be before the time represented by the exp Claim
// (possibly allowing for some small leeway to account for clock skew).
hasIdTokenExpired(token, configuration, offsetSeconds) {
const decoded = this.tokenHelperService.getPayloadFromToken(token, false, configuration);
return !this.validateIdTokenExpNotExpired(decoded, configuration, offsetSeconds);
}
// id_token C7: The current time MUST be before the time represented by the exp Claim
// (possibly allowing for some small leeway to account for clock skew).
validateIdTokenExpNotExpired(decodedIdToken, configuration, offsetSeconds) {
const tokenExpirationDate = this.tokenHelperService.getTokenExpirationDate(decodedIdToken);
offsetSeconds = offsetSeconds || 0;
if (!tokenExpirationDate) {
return false;
}
const tokenExpirationValue = tokenExpirationDate.valueOf();
const nowWithOffset = this.calculateNowWithOffset(offsetSeconds);
const tokenNotExpired = tokenExpirationValue > nowWithOffset;
this.loggerService.logDebug(configuration, `Has idToken expired: ${!tokenNotExpired} --> expires in ${this.millisToMinutesAndSeconds(tokenExpirationValue - nowWithOffset)} , ${new Date(tokenExpirationValue).toLocaleTimeString()} > ${new Date(nowWithOffset).toLocaleTimeString()}`);
return tokenNotExpired;
}
validateAccessTokenNotExpired(accessTokenExpiresAt, configuration, offsetSeconds) {
// value is optional, so if it does not exist, then it has not expired
if (!accessTokenExpiresAt) {
return true;
}
offsetSeconds = offsetSeconds || 0;
const accessTokenExpirationValue = accessTokenExpiresAt.valueOf();
const nowWithOffset = this.calculateNowWithOffset(offsetSeconds);
const tokenNotExpired = accessTokenExpirationValue > nowWithOffset;
this.loggerService.logDebug(configuration, `Has accessToken expired: ${!tokenNotExpired} --> expires in ${this.millisToMinutesAndSeconds(accessTokenExpirationValue - nowWithOffset)} , ${new Date(accessTokenExpirationValue).toLocaleTimeString()} > ${new Date(nowWithOffset).toLocaleTimeString()}`);
return tokenNotExpired;
}
// iss
// REQUIRED. Issuer Identifier for the Issuer of the response.The iss value is a case-sensitive URL using the
// https scheme that contains scheme, host,
// and optionally, port number and path components and no query or fragment components.
//
// sub
// REQUIRED. Subject Identifier.Locally unique and never reassigned identifier within the Issuer for the End- User,
// which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4.
// It MUST NOT exceed 255 ASCII characters in length.The sub value is a case-sensitive string.
//
// aud
// REQUIRED. Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an
// audience value.
// It MAY also contain identifiers for other audiences.In the general case, the aud value is an array of case-sensitive strings.
// In the common special case when there is one audience, the aud value MAY be a single case-sensitive string.
//
// exp
// REQUIRED. Expiration time on or after which the ID Token MUST NOT be accepted for processing.
// The processing of this parameter requires that the current date/ time MUST be before the expiration date/ time listed in the value.
// Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew.
// Its value is a JSON [RFC7159] number representing the number of seconds from 1970- 01 - 01T00: 00:00Z as measured in UTC until
// the date/ time.
// See RFC 3339 [RFC3339] for details regarding date/ times in general and UTC in particular.
//
// iat
// REQUIRED. Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from
// 1970- 01 - 01T00: 00: 00Z as measured
// in UTC until the date/ time.
validateRequiredIdToken(dataIdToken, configuration) {
let validated = true;
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'iss')) {
validated = false;
this.loggerService.logWarning(configuration, 'iss is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'sub')) {
validated = false;
this.loggerService.logWarning(configuration, 'sub is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'aud')) {
validated = false;
this.loggerService.logWarning(configuration, 'aud is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'exp')) {
validated = false;
this.loggerService.logWarning(configuration, 'exp is missing, this is required in the id_token');
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'iat')) {
validated = false;
this.loggerService.logWarning(configuration, 'iat is missing, this is required in the id_token');
}
return validated;
}
// id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
validateIdTokenIatMaxOffset(dataIdToken, maxOffsetAllowedInSeconds, disableIatOffsetValidation, configuration) {
if (disableIatOffsetValidation) {
return true;
}
if (!Object.prototype.hasOwnProperty.call(dataIdToken, 'iat')) {
return false;
}
const dateTimeIatIdToken = new Date(0); // The 0 here is the key, which sets the date to the epoch
dateTimeIatIdToken.setUTCSeconds(dataIdToken.iat);
maxOffsetAllowedInSeconds = maxOffsetAllowedInSeconds || 0;
const nowInUtc = new Date(new Date().toUTCString());
const diff = nowInUtc.valueOf() - dateTimeIatIdToken.valueOf();
const maxOffsetAllowedInMilliseconds = maxOffsetAllowedInSeconds * 1000;
this.loggerService.logDebug(configuration, `validate id token iat max offset ${diff} < ${maxOffsetAllowedInMilliseconds}`);
if (diff > 0) {
return diff < maxOffsetAllowedInMilliseconds;
}
return -diff < maxOffsetAllowedInMilliseconds;
}
// id_token C9: The value of the nonce Claim MUST be checked to verify that it is the same value as the one
// that was sent in the Authentication Request.The Client SHOULD check the nonce value for replay attacks.
// The precise method for detecting replay attacks is Client specific.
// However the nonce claim SHOULD not be present for the refresh_token grant type
// https://bitbucket.org/openid/connect/issues/1025/ambiguity-with-how-nonce-is-handled-on
// The current spec is ambiguous and KeyCloak does send it.
validateIdTokenNonce(dataIdToken, localNonce, ignoreNonceAfterRefresh, configuration) {
const isFromRefreshToken = (dataIdToken.nonce === undefined || ignoreNonceAfterRefresh) &&
localNonce === TokenValidationService.refreshTokenNoncePlaceholder;
if (!isFromRefreshToken && dataIdToken.nonce !== localNonce) {
this.loggerService.logDebug(configuration, 'Validate_id_token_nonce failed, dataIdToken.nonce: ' +
dataIdToken.nonce +
' local_nonce:' +
localNonce);
return false;
}
return true;
}
// id_token C1: The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
validateIdTokenIss(dataIdToken, authWellKnownEndpointsIssuer, configuration) {
if (dataIdToken.iss !== authWellKnownEndpointsIssuer) {
this.loggerService.logDebug(configuration, 'Validate_id_token_iss failed, dataIdToken.iss: ' +
dataIdToken.iss +
' authWellKnownEndpoints issuer:' +
authWellKnownEndpointsIssuer);
return false;
}
return true;
}
// id_token C2: The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified
// by the iss (issuer) Claim as an audience.
// The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences
// not trusted by the Client.
validateIdTokenAud(dataIdToken, aud, configuration) {
if (Array.isArray(dataIdToken.aud)) {
const result = dataIdToken.aud.includes(aud);
if (!result) {
this.loggerService.logDebug(configuration, 'Validate_id_token_aud array failed, dataIdToken.aud: ' +
dataIdToken.aud +
' client_id:' +
aud);
return false;
}
return true;
}
else if (dataIdToken.aud !== aud) {
this.loggerService.logDebug(configuration, 'Validate_id_token_aud failed, dataIdToken.aud: ' +
dataIdToken.aud +
' client_id:' +
aud);
return false;
}
return true;
}
validateIdTokenAzpExistsIfMoreThanOneAud(dataIdToken) {
if (!dataIdToken) {
return false;
}
return !(Array.isArray(dataIdToken.aud) &&
dataIdToken.aud.length > 1 &&
!dataIdToken.azp);
}
// If an azp (authorized party) Claim is present, the Client SHOULD verify that its client_id is the Claim Value.
validateIdTokenAzpValid(dataIdToken, clientId) {
if (!dataIdToken?.azp) {
return true;
}
return dataIdToken.azp === clientId;
}
validateStateFromHashCallback(state, localState, configuration) {
if (state !== localState) {
this.loggerService.logDebug(configuration, 'ValidateStateFromHashCallback failed, state: ' +
state +
' local_state:' +
localState);
return false;
}
return true;
}
// id_token C5: The Client MUST validate the signature of the ID Token according to JWS [JWS] using the algorithm specified in the alg
// Header Parameter of the JOSE Header.The Client MUST use the keys provided by the Issuer.
// id_token C6: The alg value SHOULD be RS256. Validation of tokens using other signing algorithms is described in the
// OpenID Connect Core 1.0 [OpenID.Core] specification.
validateSignatureIdToken(idToken, jwtkeys, configuration) {
if (!idToken) {
return of(true);
}
if (!jwtkeys || !jwtkeys.keys) {
return of(false);
}
const headerData = this.tokenHelperService.getHeaderFromToken(idToken, false, configuration);
if (Object.keys(headerData).length === 0 &&
headerData.constructor === Object) {
this.loggerService.logWarning(configuration, 'id token has no header data');
return of(false);
}
const kid = headerData.kid;
const alg = headerData.alg;
const keys = jwtkeys.keys;
let foundKeys;
let key;
if (!this.keyAlgorithms.includes(alg)) {
this.loggerService.logWarning(configuration, 'alg not supported', alg);
return of(false);
}
const kty = alg2kty(alg);
const use = 'sig';
try {
foundKeys = kid
? this.jwkExtractor.extractJwk(keys, { kid, kty, use }, false)
: this.jwkExtractor.extractJwk(keys, { kty, use }, false);
if (foundKeys.length === 0) {
foundKeys = kid
? this.jwkExtractor.extractJwk(keys, { kid, kty })
: this.jwkExtractor.extractJwk(keys, { kty });
}
key = foundKeys[0];
}
catch (e) {
this.loggerService.logError(configuration, e);
return of(false);
}
const algorithm = getImportAlg(alg);
const signingInput = this.tokenHelperService.getSigningInputFromToken(idToken, true, configuration);
const rawSignature = this.tokenHelperService.getSignatureFromToken(idToken, true, configuration);
return from(this.jwkWindowCryptoService.importVerificationKey(key, algorithm)).pipe(mergeMap((cryptoKey) => {
const signature = base64url.parse(rawSignature, {
loose: true,
});
const verifyAlgorithm = getVerifyAlg(alg);
return from(this.jwkWindowCryptoService.verifyKey(verifyAlgorithm, cryptoKey, signature, signingInput));
}), tap((isValid) => {
if (!isValid) {
this.loggerService.logWarning(configuration, 'incorrect Signature, validation failed for id_token');
}
}));
}
// Accepts ID Token without 'kid' claim in JOSE header if only one JWK supplied in 'jwks_url'
//// private validate_no_kid_in_header_only_one_allowed_in_jwtkeys(header_data: any, jwtkeys: any): boolean {
//// this.oidcSecurityCommon.logDebug('amount of jwtkeys.keys: ' + jwtkeys.keys.length);
//// if (!header_data.hasOwnProperty('kid')) {
//// // no kid defined in Jose header
//// if (jwtkeys.keys.length != 1) {
//// this.oidcSecurityCommon.logDebug('jwtkeys.keys.length != 1 and no kid in header');
//// return false;
//// }
//// }
//// return true;
//// }
// Access Token Validation
// access_token C1: Hash the octets of the ASCII representation of the access_token with the hash algorithm specified in JWA[JWA]
// for the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, the hash algorithm used is SHA-256.
// access_token C2: Take the left- most half of the hash and base64url- encode it.
// access_token C3: The value of at_hash in the ID Token MUST match the value produced in the previous step if at_hash
// is present in the ID Token.
validateIdTokenAtHash(accessToken, atHash, idTokenAlg, configuration) {
this.loggerService.logDebug(configuration, 'at_hash from the server:' + atHash);
// 'sha256' 'sha384' 'sha512'
let sha = 'SHA-256';
if (idTokenAlg.includes('384')) {
sha = 'SHA-384';
}
else if (idTokenAlg.includes('512')) {
sha = 'SHA-512';
}
return this.jwtWindowCryptoService
.generateAtHash('' + accessToken, sha)
.pipe(mergeMap((hash) => {
this.loggerService.logDebug(configuration, 'at_hash client validation not decoded:' + hash);
if (hash === atHash) {
return of(true); // isValid;
}
else {
return this.jwtWindowCryptoService
.generateAtHash('' + decodeURIComponent(accessToken), sha)
.pipe(map((newHash) => {
this.loggerService.logDebug(configuration, '-gen access--' + hash);
return newHash === atHash;
}));
}
}));
}
millisToMinutesAndSeconds(millis) {
const minutes = Math.floor(millis / 60000);
const seconds = ((millis % 60000) / 1000).toFixed(0);
return minutes + ':' + (+seconds < 10 ? '0' : '') + seconds;
}
calculateNowWithOffset(offsetSeconds) {
return new Date(new Date().toUTCString()).valueOf() + offsetSeconds * 1000;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: TokenValidationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: TokenValidationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: TokenValidationService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tdmFsaWRhdGlvbi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvYW5ndWxhci1hdXRoLW9pZGMtY2xpZW50L3NyYy9saWIvdmFsaWRhdGlvbi90b2tlbi12YWxpZGF0aW9uLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNwQyxPQUFPLEVBQUUsSUFBSSxFQUFjLEVBQUUsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUM1QyxPQUFPLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVwRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDM0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzFELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDJDQUEyQyxDQUFDO0FBQy9FLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3JFLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3JFLE9BQU8sRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxNQUFNLDJCQUEyQixDQUFDOztBQUVoRiwyREFBMkQ7QUFFM0QsV0FBVztBQUNYLDRHQUE0RztBQUM1RywwREFBMEQ7QUFDMUQsRUFBRTtBQUNGLHVJQUF1STtBQUN2SSx1SUFBdUk7QUFDdkksb0VBQW9FO0FBQ3BFLEVBQUU7QUFDRixtSEFBbUg7QUFDbkgsRUFBRTtBQUNGLDhIQUE4SDtBQUM5SCxFQUFFO0FBQ0Ysa0lBQWtJO0FBQ2xJLCtGQUErRjtBQUMvRixFQUFFO0FBQ0YscUlBQXFJO0FBQ3JJLFdB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QUMsQ0FBQyxPQUFlLEVBQUUsRUFBRTtvQkFDdEIsSUFBSSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQ3pCLGFBQWEsRUFDYixlQUFlLEdBQUcsSUFBSSxDQUN2QixDQUFDO29CQUVGLE9BQU8sT0FBTyxLQUFLLE1BQU0sQ0FBQztnQkFDNUIsQ0FBQyxDQUFDLENBQ0gsQ0FBQztZQUNOLENBQUM7UUFDSCxDQUFDLENBQUMsQ0FDSCxDQUFDO0lBQ04sQ0FBQztJQUVPLHlCQUF5QixDQUFDLE1BQWM7UUFDOUMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLENBQUM7UUFDM0MsTUFBTSxPQUFPLEdBQUcsQ0FBQyxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFckQsT0FBTyxPQUFPLEdBQUcsR0FBRyxHQUFHLENBQUMsQ0FBQyxPQUFPLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE9BQU8sQ0FBQztJQUM5RCxDQUFDO0lBRU8sc0JBQXNCLENBQUMsYUFBcUI7UUFDbEQsT0FBTyxJQUFJLElBQUksQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLEdBQUcsYUFBYSxHQUFHLElBQUksQ0FBQztJQUM3RSxDQUFDOzhHQXJpQlUsc0JBQXNCO2tIQUF0QixzQkFBc0IsY0FEVCxNQUFNOzsyRkFDbkIsc0JBQXNCO2tCQURsQyxVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGluamVjdCwgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgYmFzZTY0dXJsIH0gZnJvbSAncmZjNDY0OCc7XG5pbXBvcnQgeyBmcm9tLCBPYnNlcnZhYmxlLCBvZiB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgbWFwLCBtZXJnZU1hcCwgdGFwIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHsgT3BlbklkQ29uZmlndXJhdGlvbiB9IGZyb20gJy4uL2NvbmZpZy9vcGVuaWQtY29uZmlndXJhdGlvbic7XG5pbXBvcnQgeyBKd2tFeHRyYWN0b3IgfSBmcm9tICcuLi9leHRyYWN0b3JzL2p3ay5leHRyYWN0b3InO1xuaW1wb3J0IHsgTG9nZ2VyU2VydmljZSB9IGZyb20gJy4uL2xvZ2dpbmcvbG9nZ2VyLnNlcnZpY2UnO1xuaW1wb3J0IHsgVG9rZW5IZWxwZXJTZXJ2aWNlIH0gZnJvbSAnLi4vdXRpbHMvdG9rZW5IZWxwZXIvdG9rZW4taGVscGVyLnNlcnZpY2UnO1xuaW1wb3J0IHsgSndrV2luZG93Q3J5cHRvU2VydmljZSB9IGZyb20gJy4vandrLXdpbmRvdy1jcnlwdG8uc2VydmljZSc7XG5pbXBvcnQgeyBKd3RXaW5kb3dDcnlwdG9TZXJ2aWNlIH0gZnJvbSAnLi9qd3Qtd2luZG93LWNyeXB0by5zZXJ2aWNlJztcbmltcG9ydCB7IGFsZzJrdHksIGdldEltcG9ydEFsZywgZ2V0VmVyaWZ5QWxnIH0gZnJvbSAnLi90b2tlbi12YWxpZGF0aW9uLmhlbHBlcic7XG5cbi8vIGh0dHA6Ly9vcGVuaWQubmV0L3NwZWNzL29wZW5pZC1jb25uZWN0LWltcGxpY2l0LTFfMC5odG1sXG5cbi8vIGlkX3Rva2VuXG4vLyBpZF90b2tlbiBDMTogVGhlIElzc3VlciBJZGVudGlmaWVyIGZvciB0aGUgT3BlbklEIFByb3ZpZGVyICh3aGljaCBpcyB0eXBpY2FsbHkgb2J0YWluZWQgZHVyaW5nIERpc2NvdmVyeSlcbi8vIE1VU1QgZXhhY3RseSBtYXRjaCB0aGUgdmFsdWUgb2YgdGhlIGlzcyAoaXNzdWVyKSBDbGFpbS5cbi8vXG4vLyBpZF90b2tlbiBDMjogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoYXQgdGhlIGF1ZCAoYXVkaWVuY2UpIENsYWltIGNvbnRhaW5zIGl0cyBjbGllbnRfaWQgdmFsdWUgcmVnaXN0ZXJlZCBhdCB0aGUgSXNzdWVyIGlkZW50aWZpZWRcbi8vIGJ5IHRoZSBpc3MgKGlzc3VlcikgQ2xhaW0gYXMgYW4gYXVkaWVuY2UuVGhlIElEIFRva2VuIE1VU1QgYmUgcmVqZWN0ZWQgaWYgdGhlIElEIFRva2VuIGRvZXMgbm90IGxpc3QgdGhlIENsaWVudCBhcyBhIHZhbGlkIGF1ZGllbmNlLFxuLy8gb3IgaWYgaXQgY29udGFpbnMgYWRkaXRpb25hbCBhdWRpZW5jZXMgbm90IHRydXN0ZWQgYnkgdGhlIENsaWVudC5cbi8vXG4vLyBpZF90b2tlbiBDMzogSWYgdGhlIElEIFRva2VuIGNvbnRhaW5zIG11bHRpcGxlIGF1ZGllbmNlcywgdGhlIENsaWVudCBTSE9VTEQgdmVyaWZ5IHRoYXQgYW4gYXpwIENsYWltIGlzIHByZXNlbnQuXG4vL1xuLy8gaWRfdG9rZW4gQzQ6IElmIGFuIGF6cCAoYXV0aG9yaXplZCBwYXJ0eSkgQ2xhaW0gaXMgcHJlc2VudCwgdGhlIENsaWVudCBTSE9VTEQgdmVyaWZ5IHRoYXQgaXRzIGNsaWVudF9pZCBpcyB0aGUgQ2xhaW0gVmFsdWUuXG4vL1xuLy8gaWRfdG9rZW4gQzU6IFRoZSBDbGllbnQgTVVTVCB2YWxpZGF0ZSB0aGUgc2lnbmF0dXJlIG9mIHRoZSBJRCBUb2tlbiBhY2NvcmRpbmcgdG8gSldTIFtKV1NdIHVzaW5nIHRoZSBhbGdvcml0aG0gc3BlY2lmaWVkIGluIHRoZVxuLy8gYWxnIEhlYWRlciBQYXJhbWV0ZXIgb2YgdGhlIEpPU0UgSGVhZGVyLlRoZSBDbGllbnQgTVVTVCB1c2UgdGhlIGtleXMgcHJvdmlkZWQgYnkgdGhlIElzc3Vlci5cbi8vXG4vLyBpZF90b2tlbiBDNjogVGhlIGFsZyB2YWx1ZSBTSE9VTEQgYmUgUlMyNTYuIFZhbGlkYXRpb24gb2YgdG9rZW5zIHVzaW5nIG90aGVyIHNpZ25pbmcgYWxnb3JpdGhtcyBpcyBkZXNjcmliZWQgaW4gdGhlIE9wZW5JRCBDb25uZWN0XG4vLyBDb3JlIDEuMFxuLy8gW09wZW5JRC5Db3JlXSBzcGVjaWZpY2F0aW9uLlxuLy9cbi8vIGlkX3Rva2VuIEM3OiBUaGUgY3VycmVudCB0aW1lIE1VU1QgYmUgYmVmb3JlIHRoZSB0aW1lIHJlcHJlc2VudGVkIGJ5IHRoZSBleHAgQ2xhaW0gKHBvc3NpYmx5IGFsbG93aW5nIGZvciBzb21lIHNtYWxsIGxlZXdheSB0byBhY2NvdW50XG4vLyBmb3IgY2xvY2sgc2tldykuXG4vL1xuLy8gaWRfdG9rZW4gQzg6IFRoZSBpYXQgQ2xhaW0gY2FuIGJlIHVzZWQgdG8gcmVqZWN0IHRva2VucyB0aGF0IHdlcmUgaXNzdWVkIHRvbyBmYXIgYXdheSBmcm9tIHRoZSBjdXJyZW50IHRpbWUsXG4vLyBsaW1pdGluZyB0aGUgYW1vdW50IG9mIHRpbWUgdGhhdCBub25jZXMgbmVlZCB0byBiZSBzdG9yZWQgdG8gcHJldmVudCBhdHRhY2tzLlRoZSBhY2NlcHRhYmxlIHJhbmdlIGlzIENsaWVudCBzcGVjaWZpYy5cbi8vXG4vLyBpZF90b2tlbiBDOTogVGhlIHZhbHVlIG9mIHRoZSBub25jZSBDbGFpbSBNVVNUIGJlIGNoZWNrZWQgdG8gdmVyaWZ5IHRoYXQgaXQgaXMgdGhlIHNhbWUgdmFsdWUgYXMgdGhlIG9uZSB0aGF0IHdhcyBzZW50XG4vLyBpbiB0aGUgQXV0aGVudGljYXRpb24gUmVxdWVzdC5UaGUgQ2xpZW50IFNIT1VMRCBjaGVjayB0aGUgbm9uY2UgdmFsdWUgZm9yIHJlcGxheSBhdHRhY2tzLlRoZSBwcmVjaXNlIG1ldGhvZCBmb3IgZGV0ZWN0aW5nIHJlcGxheSBhdHRhY2tzXG4vLyBpcyBDbGllbnQgc3BlY2lmaWMuXG4vL1xuLy8gaWRfdG9rZW4gQzEwOiBJZiB0aGUgYWNyIENsYWltIHdhcyByZXF1ZXN0ZWQsIHRoZSBDbGllbnQgU0hPVUxEIGNoZWNrIHRoYXQgdGhlIGFzc2VydGVkIENsYWltIFZhbHVlIGlzIGFwcHJvcHJpYXRlLlxuLy8gVGhlIG1lYW5pbmcgYW5kIHByb2Nlc3Npbmcgb2YgYWNyIENsYWltIFZhbHVlcyBpcyBvdXQgb2Ygc2NvcGUgZm9yIHRoaXMgZG9jdW1lbnQuXG4vL1xuLy8gaWRfdG9rZW4gQzExOiBXaGVuIGEgbWF4X2FnZSByZXF1ZXN0IGlzIG1hZGUsIHRoZSBDbGllbnQgU0hPVUxEIGNoZWNrIHRoZSBhdXRoX3RpbWUgQ2xhaW0gdmFsdWUgYW5kIHJlcXVlc3QgcmUtIGF1dGhlbnRpY2F0aW9uXG4vLyBpZiBpdCBkZXRlcm1pbmVzIHRvbyBtdWNoIHRpbWUgaGFzIGVsYXBzZWQgc2luY2UgdGhlIGxhc3QgRW5kLSBVc2VyIGF1dGhlbnRpY2F0aW9uLlxuXG4vLyBBY2Nlc3MgVG9rZW4gVmFsaWRhdGlvblxuLy8gYWNjZXNzX3Rva2VuIEMxOiBIYXNoIHRoZSBvY3RldHMgb2YgdGhlIEFTQ0lJIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBhY2Nlc3NfdG9rZW4gd2l0aCB0aGUgaGFzaCBhbGdvcml0aG0gc3BlY2lmaWVkIGluIEpXQVtKV0FdXG4vLyBmb3IgdGhlIGFsZyBIZWFkZXIgUGFyYW1ldGVyIG9mIHRoZSBJRCBUb2tlbidzIEpPU0UgSGVhZGVyLiBGb3IgaW5zdGFuY2UsIGlmIHRoZSBhbGcgaXMgUlMyNTYsIHRoZSBoYXNoIGFsZ29yaXRobSB1c2VkIGlzIFNIQS0yNTYuXG4vLyBhY2Nlc3NfdG9rZW4gQzI6IFRha2UgdGhlIGxlZnQtIG1vc3QgaGFsZiBvZiB0aGUgaGFzaCBhbmQgYmFzZTY0dXJsLSBlbmNvZGUgaXQuXG4vLyBhY2Nlc3NfdG9rZW4gQzM6IFRoZSB2YWx1ZSBvZiBhdF9oYXNoIGluIHRoZSBJRCBUb2tlbiBNVVNUIG1hdGNoIHRoZSB2YWx1ZSBwcm9kdWNlZCBpbiB0aGUgcHJldmlvdXMgc3RlcCBpZiBhdF9oYXNoIGlzIHByZXNlbnRcbi8vIGluIHRoZSBJRCBUb2tlbi5cblxuQEluamVjdGFibGUoeyBwcm92aWRlZEluOiAncm9vdCcgfSlcbmV4cG9ydCBjbGFzcyBUb2tlblZhbGlkYXRpb25TZXJ2aWNlIHtcbiAgc3RhdGljIHJlZnJlc2hUb2tlbk5vbmNlUGxhY2Vob2xkZXIgPSAnLS1SZWZyZXNoVG9rZW4tLSc7XG5cbiAga2V5QWxnb3JpdGhtczogc3RyaW5nW10gPSBbXG4gICAgJ0hTMjU2JyxcbiAgICAnSFMzODQnLFxuICAgICdIUzUxMicsXG4gICAgJ1JTMjU2JyxcbiAgICAnUlMzODQnLFxuICAgICdSUzUxMicsXG4gICAgJ0VTMjU2JyxcbiAgICAnRVMzODQnLFxuICAgICdQUzI1NicsXG4gICAgJ1BTMzg0JyxcbiAgICAnUFM1MTInLFxuICBdO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgdG9rZW5IZWxwZXJTZXJ2aWNlID0gaW5qZWN0KFRva2VuSGVscGVyU2VydmljZSk7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBsb2dnZXJTZXJ2aWNlID0gaW5qZWN0KExvZ2dlclNlcnZpY2UpO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgandrRXh0cmFjdG9yID0gaW5qZWN0KEp3a0V4dHJhY3Rvcik7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBqd2tXaW5kb3dDcnlwdG9TZXJ2aWNlID0gaW5qZWN0KEp3a1dpbmRvd0NyeXB0b1NlcnZpY2UpO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgand0V2luZG93Q3J5cHRvU2VydmljZSA9IGluamVjdChKd3RXaW5kb3dDcnlwdG9TZXJ2aWNlKTtcblxuICAvLyBpZF90b2tlbiBDNzogVGhlIGN1cnJlbnQgdGltZSBNVVNUIGJlIGJlZm9yZSB0aGUgdGltZSByZXByZXNlbnRlZCBieSB0aGUgZXhwIENsYWltXG4gIC8vIChwb3NzaWJseSBhbGxvd2luZyBmb3Igc29tZSBzbWFsbCBsZWV3YXkgdG8gYWNjb3VudCBmb3IgY2xvY2sgc2tldykuXG4gIGhhc0lkVG9rZW5FeHBpcmVkKFxuICAgIHRva2VuOiBzdHJpbmcsXG4gICAgY29uZmlndXJhdGlvbjogT3BlbklkQ29uZmlndXJhdGlvbixcbiAgICBvZmZzZXRTZWNvbmRzPzogbnVtYmVyXG4gICk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IGRlY29kZWQgPSB0aGlzLnRva2VuSGVscGVyU2VydmljZS5nZXRQYXlsb2FkRnJvbVRva2VuKFxuICAgICAgdG9rZW4sXG4gICAgICBmYWxzZSxcbiAgICAgIGNvbmZpZ3VyYXRpb25cbiAgICApO1xuXG4gICAgcmV0dXJuICF0aGlzLnZhbGlkYXRlSWRUb2tlbkV4cE5vdEV4cGlyZWQoXG4gICAgICBkZWNvZGVkLFxuICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgIG9mZnNldFNlY29uZHNcbiAgICApO1xuICB9XG5cbiAgLy8gaWRfdG9rZW4gQzc6IFRoZSBjdXJyZW50IHRpbWUgTVVTVCBiZSBiZWZvcmUgdGhlIHRpbWUgcmVwcmVzZW50ZWQgYnkgdGhlIGV4cCBDbGFpbVxuICAvLyAocG9zc2libHkgYWxsb3dpbmcgZm9yIHNvbWUgc21hbGwgbGVld2F5IHRvIGFjY291bnQgZm9yIGNsb2NrIHNrZXcpLlxuICB2YWxpZGF0ZUlkVG9rZW5FeHBOb3RFeHBpcmVkKFxuICAgIGRlY29kZWRJZFRva2VuOiBzdHJpbmcsXG4gICAgY29uZmlndXJhdGlvbjogT3BlbklkQ29uZmlndXJhdGlvbixcbiAgICBvZmZzZXRTZWNvbmRzPzogbnVtYmVyXG4gICk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IHRva2VuRXhwaXJhdGlvbkRhdGUgPVxuICAgICAgdGhpcy50b2tlbkhlbHBlclNlcnZpY2UuZ2V0VG9rZW5FeHBpcmF0aW9uRGF0ZShkZWNvZGVkSWRUb2tlbik7XG5cbiAgICBvZmZzZXRTZWNvbmRzID0gb2Zmc2V0U2Vjb25kcyB8fCAwO1xuXG4gICAgaWYgKCF0b2tlbkV4cGlyYXRpb25EYXRlKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgY29uc3QgdG9rZW5FeHBpcmF0aW9uVmFsdWUgPSB0b2tlbkV4cGlyYXRpb25EYXRlLnZhbHVlT2YoKTtcbiAgICBjb25zdCBub3dXaXRoT2Zmc2V0ID0gdGhpcy5jYWxjdWxhdGVOb3dXaXRoT2Zmc2V0KG9mZnNldFNlY29uZHMpO1xuICAgIGNvbnN0IHRva2VuTm90RXhwaXJlZCA9IHRva2VuRXhwaXJhdGlvblZhbHVlID4gbm93V2l0aE9mZnNldDtcblxuICAgIHRoaXMubG9nZ2VyU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgIGNvbmZpZ3VyYXRpb24sXG4gICAgICBgSGFzIGlkVG9rZW4gZXhwaXJlZDogJHshdG9rZW5Ob3RFeHBpcmVkfSAtLT4gZXhwaXJlcyBpbiAke3RoaXMubWlsbGlzVG9NaW51dGVzQW5kU2Vjb25kcyhcbiAgICAgICAgdG9rZW5FeHBpcmF0aW9uVmFsdWUgLSBub3dXaXRoT2Zmc2V0XG4gICAgICApfSAsICR7bmV3IERhdGUodG9rZW5FeHBpcmF0aW9uVmFsdWUpLnRvTG9jYWxlVGltZVN0cmluZygpfSA+ICR7bmV3IERhdGUoXG4gICAgICAgIG5vd1dpdGhPZmZzZXRcbiAgICAgICkudG9Mb2NhbGVUaW1lU3RyaW5nKCl9YFxuICAgICk7XG5cbiAgICByZXR1cm4gdG9rZW5Ob3RFeHBpcmVkO1xuICB9XG5cbiAgdmFsaWRhdGVBY2Nlc3NUb2tlbk5vdEV4cGlyZWQoXG4gICAgYWNjZXNzVG9rZW5FeHBpcmVzQXQ6IERhdGUsXG4gICAgY29uZmlndXJhdGlvbjogT3BlbklkQ29uZmlndXJhdGlvbixcbiAgICBvZmZzZXRTZWNvbmRzPzogbnVtYmVyXG4gICk6IGJvb2xlYW4ge1xuICAgIC8vIHZhbHVlIGlzIG9wdGlvbmFsLCBzbyBpZiBpdCBkb2VzIG5vdCBleGlzdCwgdGhlbiBpdCBoYXMgbm90IGV4cGlyZWRcbiAgICBpZiAoIWFjY2Vzc1Rva2VuRXhwaXJlc0F0KSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICBvZmZzZXRTZWNvbmRzID0gb2Zmc2V0U2Vjb25kcyB8fCAwO1xuICAgIGNvbnN0IGFjY2Vzc1Rva2VuRXhwaXJhdGlvblZhbHVlID0gYWNjZXNzVG9rZW5FeHBpcmVzQXQudmFsdWVPZigpO1xuICAgIGNvbnN0IG5vd1dpdGhPZmZzZXQgPSB0aGlzLmNhbGN1bGF0ZU5vd1dpdGhPZmZzZXQob2Zmc2V0U2Vjb25kcyk7XG4gICAgY29uc3QgdG9rZW5Ob3RFeHBpcmVkID0gYWNjZXNzVG9rZW5FeHBpcmF0aW9uVmFsdWUgPiBub3dXaXRoT2Zmc2V0O1xuXG4gICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ0RlYnVnKFxuICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgIGBIYXMgYWNjZXNzVG9rZW4gZXhwaXJlZDogJHshdG9rZW5Ob3RFeHBpcmVkfSAtLT4gZXhwaXJlcyBpbiAke3RoaXMubWlsbGlzVG9NaW51dGVzQW5kU2Vjb25kcyhcbiAgICAgICAgYWNjZXNzVG9rZW5FeHBpcmF0aW9uVmFsdWUgLSBub3dXaXRoT2Zmc2V0XG4gICAgICApfSAsICR7bmV3IERhdGUoXG4gICAgICAgIGFjY2Vzc1Rva2VuRXhwaXJhdGlvblZhbHVlXG4gICAgICApLnRvTG9jYWxlVGltZVN0cmluZygpfSA+ICR7bmV3IERhdGUobm93V2l0aE9mZnNldCkudG9Mb2NhbGVUaW1lU3RyaW5nKCl9YFxuICAgICk7XG5cbiAgICByZXR1cm4gdG9rZW5Ob3RFeHBpcmVkO1xuICB9XG5cbiAgLy8gaXNzXG4gIC8vIFJFUVVJUkVELiBJc3N1ZXIgSWRlbnRpZmllciBmb3IgdGhlIElzc3VlciBvZiB0aGUgcmVzcG9uc2UuVGhlIGlzcyB2YWx1ZSBpcyBhIGNhc2Utc2Vuc2l0aXZlIFVSTCB1c2luZyB0aGVcbiAgLy8gaHR0cHMgc2NoZW1lIHRoYXQgY29udGFpbnMgc2NoZW1lLCBob3N0LFxuICAvLyBhbmQgb3B0aW9uYWxseSwgcG9ydCBudW1iZXIgYW5kIHBhdGggY29tcG9uZW50cyBhbmQgbm8gcXVlcnkgb3IgZnJhZ21lbnQgY29tcG9uZW50cy5cbiAgLy9cbiAgLy8gc3ViXG4gIC8vIFJFUVVJUkVELiBTdWJqZWN0IElkZW50aWZpZXIuTG9jYWxseSB1bmlxdWUgYW5kIG5ldmVyIHJlYXNzaWduZWQgaWRlbnRpZmllciB3aXRoaW4gdGhlIElzc3VlciBmb3IgdGhlIEVuZC0gVXNlcixcbiAgLy8gd2hpY2ggaXMgaW50ZW5kZWQgdG8gYmUgY29uc3VtZWQgYnkgdGhlIENsaWVudCwgZS5nLiwgMjQ0MDAzMjAgb3IgQUl0T2F3bXd0V3djVDBrNTFCYXlld052dXRySlVxc3ZsNnFzN0E0LlxuICAvLyBJdCBNVVNUIE5PVCBleGNlZWQgMjU1IEFTQ0lJIGNoYXJhY3RlcnMgaW4gbGVuZ3RoLlRoZSBzdWIgdmFsdWUgaXMgYSBjYXNlLXNlbnNpdGl2ZSBzdHJpbmcuXG4gIC8vXG4gIC8vIGF1ZFxuICAvLyBSRVFVSVJFRC4gQXVkaWVuY2UocykgdGhhdCB0aGlzIElEIFRva2VuIGlzIGludGVuZGVkIGZvci4gSXQgTVVTVCBjb250YWluIHRoZSBPQXV0aCAyLjAgY2xpZW50X2lkIG9mIHRoZSBSZWx5aW5nIFBhcnR5IGFzIGFuXG4gIC8vIGF1ZGllbmNlIHZhbHVlLlxuICAvLyBJdCBNQVkgYWxzbyBjb250YWluIGlkZW50aWZpZXJzIGZvciBvdGhlciBhdWRpZW5jZXMuSW4gdGhlIGdlbmVyYWwgY2FzZSwgdGhlIGF1ZCB2YWx1ZSBpcyBhbiBhcnJheSBvZiBjYXNlLXNlbnNpdGl2ZSBzdHJpbmdzLlxuICAvLyBJbiB0aGUgY29tbW9uIHNwZWNpYWwgY2FzZSB3aGVuIHRoZXJlIGlzIG9uZSBhdWRpZW5jZSwgdGhlIGF1ZCB2YWx1ZSBNQVkgYmUgYSBzaW5nbGUgY2FzZS1zZW5zaXRpdmUgc3RyaW5nLlxuICAvL1xuICAvLyBleHBcbiAgLy8gUkVRVUlSRUQuIEV4cGlyYXRpb24gdGltZSBvbiBvciBhZnRlciB3aGljaCB0aGUgSUQgVG9rZW4gTVVTVCBOT1QgYmUgYWNjZXB0ZWQgZm9yIHByb2Nlc3NpbmcuXG4gIC8vIFRoZSBwcm9jZXNzaW5nIG9mIHRoaXMgcGFyYW1ldGVyIHJlcXVpcmVzIHRoYXQgdGhlIGN1cnJlbnQgZGF0ZS8gdGltZSBNVVNUIGJlIGJlZm9yZSB0aGUgZXhwaXJhdGlvbiBkYXRlLyB0aW1lIGxpc3RlZCBpbiB0aGUgdmFsdWUuXG4gIC8vIEltcGxlbWVudGVycyBNQVkgcHJvdmlkZSBmb3Igc29tZSBzbWFsbCBsZWV3YXksIHVzdWFsbHkgbm8gbW9yZSB0aGFuIGEgZmV3IG1pbnV0ZXMsIHRvIGFjY291bnQgZm9yIGNsb2NrIHNrZXcuXG4gIC8vIEl0cyB2YWx1ZSBpcyBhIEpTT04gW1JGQzcxNTldIG51bWJlciByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiBzZWNvbmRzIGZyb20gMTk3MC0gMDEgLSAwMVQwMDogMDA6MDBaIGFzIG1lYXN1cmVkIGluIFVUQyB1bnRpbFxuICAvLyB0aGUgZGF0ZS8gdGltZS5cbiAgLy8gU2VlIFJGQyAzMzM5IFtSRkMzMzM5XSBmb3IgZGV0YWlscyByZWdhcmRpbmcgZGF0ZS8gdGltZXMgaW4gZ2VuZXJhbCBhbmQgVVRDIGluIHBhcnRpY3VsYXIuXG4gIC8vXG4gIC8vIGlhdFxuICAvLyBSRVFVSVJFRC4gVGltZSBhdCB3aGljaCB0aGUgSldUIHdhcyBpc3N1ZWQuIEl0cyB2YWx1ZSBpcyBhIEpTT04gbnVtYmVyIHJlcHJlc2VudGluZyB0aGUgbnVtYmVyIG9mIHNlY29uZHMgZnJvbVxuICAvLyAxOTcwLSAwMSAtIDAxVDAwOiAwMDogMDBaIGFzIG1lYXN1cmVkXG4gIC8vIGluIFVUQyB1bnRpbCB0aGUgZGF0ZS8gdGltZS5cbiAgdmFsaWRhdGVSZXF1aXJlZElkVG9rZW4oXG4gICAgZGF0YUlkVG9rZW46IGFueSxcbiAgICBjb25maWd1cmF0aW9uOiBPcGVuSWRDb25maWd1cmF0aW9uXG4gICk6IGJvb2xlYW4ge1xuICAgIGxldCB2YWxpZGF0ZWQgPSB0cnVlO1xuXG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZGF0YUlkVG9rZW4sICdpc3MnKSkge1xuICAgICAgdmFsaWRhdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nV2FybmluZyhcbiAgICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgICAgJ2lzcyBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbidcbiAgICAgICk7XG4gICAgfVxuXG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZGF0YUlkVG9rZW4sICdzdWInKSkge1xuICAgICAgdmFsaWRhdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nV2FybmluZyhcbiAgICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgICAgJ3N1YiBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbidcbiAgICAgICk7XG4gICAgfVxuXG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZGF0YUlkVG9rZW4sICdhdWQnKSkge1xuICAgICAgdmFsaWRhdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nV2FybmluZyhcbiAgICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgICAgJ2F1ZCBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbidcbiAgICAgICk7XG4gICAgfVxuXG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZGF0YUlkVG9rZW4sICdleHAnKSkge1xuICAgICAgdmFsaWRhdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nV2FybmluZyhcbiAgICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgICAgJ2V4cCBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbidcbiAgICAgICk7XG4gICAgfVxuXG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoZGF0YUlkVG9rZW4sICdpYXQnKSkge1xuICAgICAgdmFsaWRhdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nV2FybmluZyhcbiAgICAgICAgY29uZmlndXJhdGlvbixcbiAgICAgICAgJ2lhdCBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbidcbiAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHZhbGlkYXRlZDtcbiAgfVxuXG4gIC8vIGlkX3Rva2VuIEM4OiBUaGUgaWF0IENsYWltIGNhbiBiZSB1c2VkIHRvIHJlamVjdCB0b2tlbnMgdGhhdCB3ZXJlIGlzc3VlZCB0b28gZmFyIGF3YXkgZnJvbSB0aGUgY3VycmVudCB0aW1lLFxuICAvLyBsaW1pdGluZyB0aGUgYW1vdW50IG9mIHRpbWUgdGhhdCBub25jZXMgbmVlZCB0byBiZSBzdG9yZWQgdG8gcHJldmVudCBhdHRhY2tzLlRoZSBhY2NlcHR