UNPKG

angular-auth-oidc-client

Version:
378 lines 65 kB
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