UNPKG

angular-auth-oidc-client

Version:

An OpenID Connect Code Flow with PKCE,Implicit Flow client for Angular

449 lines 56.5 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import { Injectable } from '@angular/core'; import { hextob64u, KEYUTIL, KJUR } from 'jsrsasign'; import { EqualityHelperService } from './oidc-equality-helper.service'; import { TokenHelperService } from './oidc-token-helper.service'; import { LoggerService } from './oidc.logger.service'; // 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 OidcSecurityValidation { /** * @param {?} arrayHelperService * @param {?} tokenHelperService * @param {?} loggerService */ constructor(arrayHelperService, tokenHelperService, loggerService) { this.arrayHelperService = arrayHelperService; this.tokenHelperService = tokenHelperService; this.loggerService = loggerService; } // 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). /** * @param {?} token * @param {?=} offsetSeconds * @return {?} */ isTokenExpired(token, offsetSeconds) { /** @type {?} */ let decoded; decoded = this.tokenHelperService.getPayloadFromToken(token, false); return !this.validate_id_token_exp_not_expired(decoded, 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). /** * @param {?} decoded_id_token * @param {?=} offsetSeconds * @return {?} */ validate_id_token_exp_not_expired(decoded_id_token, offsetSeconds) { /** @type {?} */ const tokenExpirationDate = this.tokenHelperService.getTokenExpirationDate(decoded_id_token); offsetSeconds = offsetSeconds || 0; if (!tokenExpirationDate) { return false; } /** @type {?} */ const tokenExpirationValue = tokenExpirationDate.valueOf(); /** @type {?} */ const nowWithOffset = new Date().valueOf() + offsetSeconds * 1000; /** @type {?} */ const tokenNotExpired = tokenExpirationValue > nowWithOffset; this.loggerService.logDebug(`Token not expired?: ${tokenExpirationValue} > ${nowWithOffset} (${tokenNotExpired})`); // Token not expired? 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. /** * @param {?} dataIdToken * @return {?} */ validate_required_id_token(dataIdToken) { /** @type {?} */ let validated = true; if (!dataIdToken.hasOwnProperty('iss')) { validated = false; this.loggerService.logWarning('iss is missing, this is required in the id_token'); } if (!dataIdToken.hasOwnProperty('sub')) { validated = false; this.loggerService.logWarning('sub is missing, this is required in the id_token'); } if (!dataIdToken.hasOwnProperty('aud')) { validated = false; this.loggerService.logWarning('aud is missing, this is required in the id_token'); } if (!dataIdToken.hasOwnProperty('exp')) { validated = false; this.loggerService.logWarning('exp is missing, this is required in the id_token'); } if (!dataIdToken.hasOwnProperty('iat')) { validated = false; this.loggerService.logWarning('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. /** * @param {?} dataIdToken * @param {?} max_offset_allowed_in_seconds * @param {?} disable_iat_offset_validation * @return {?} */ validate_id_token_iat_max_offset(dataIdToken, max_offset_allowed_in_seconds, disable_iat_offset_validation) { if (disable_iat_offset_validation) { return true; } if (!dataIdToken.hasOwnProperty('iat')) { return false; } /** @type {?} */ const dateTime_iat_id_token = new Date(0); dateTime_iat_id_token.setUTCSeconds(dataIdToken.iat); max_offset_allowed_in_seconds = max_offset_allowed_in_seconds || 0; if (dateTime_iat_id_token == null) { return false; } this.loggerService.logDebug('validate_id_token_iat_max_offset: ' + (new Date().valueOf() - dateTime_iat_id_token.valueOf()) + ' < ' + max_offset_allowed_in_seconds * 1000); return new Date().valueOf() - dateTime_iat_id_token.valueOf() < max_offset_allowed_in_seconds * 1000; } // 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. /** * @param {?} dataIdToken * @param {?} local_nonce * @return {?} */ validate_id_token_nonce(dataIdToken, local_nonce) { if (dataIdToken.nonce !== local_nonce) { this.loggerService.logDebug('Validate_id_token_nonce failed, dataIdToken.nonce: ' + dataIdToken.nonce + ' local_nonce:' + local_nonce); 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. /** * @param {?} dataIdToken * @param {?} authWellKnownEndpoints_issuer * @return {?} */ validate_id_token_iss(dataIdToken, authWellKnownEndpoints_issuer) { if (((/** @type {?} */ (dataIdToken.iss))) !== ((/** @type {?} */ (authWellKnownEndpoints_issuer)))) { this.loggerService.logDebug('Validate_id_token_iss failed, dataIdToken.iss: ' + dataIdToken.iss + ' authWellKnownEndpoints issuer:' + authWellKnownEndpoints_issuer); 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. /** * @param {?} dataIdToken * @param {?} aud * @return {?} */ validate_id_token_aud(dataIdToken, aud) { if (dataIdToken.aud instanceof Array) { /** @type {?} */ const result = this.arrayHelperService.areEqual(dataIdToken.aud, aud); if (!result) { this.loggerService.logDebug('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('Validate_id_token_aud failed, dataIdToken.aud: ' + dataIdToken.aud + ' client_id:' + aud); return false; } return true; } /** * @param {?} state * @param {?} local_state * @return {?} */ validateStateFromHashCallback(state, local_state) { if (((/** @type {?} */ (state))) !== ((/** @type {?} */ (local_state)))) { this.loggerService.logDebug('ValidateStateFromHashCallback failed, state: ' + state + ' local_state:' + local_state); return false; } return true; } /** * @param {?} id_token_sub * @param {?} userdata_sub * @return {?} */ validate_userdata_sub_id_token(id_token_sub, userdata_sub) { if (((/** @type {?} */ (id_token_sub))) !== ((/** @type {?} */ (userdata_sub)))) { this.loggerService.logDebug('validate_userdata_sub_id_token failed, id_token_sub: ' + id_token_sub + ' userdata_sub:' + userdata_sub); 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. /** * @param {?} id_token * @param {?} jwtkeys * @return {?} */ validate_signature_id_token(id_token, jwtkeys) { if (!jwtkeys || !jwtkeys.keys) { return false; } /** @type {?} */ const header_data = this.tokenHelperService.getHeaderFromToken(id_token, false); if (Object.keys(header_data).length === 0 && header_data.constructor === Object) { this.loggerService.logWarning('id token has no header data'); return false; } /** @type {?} */ const kid = header_data.kid; /** @type {?} */ const alg = header_data.alg; if ('RS256' !== ((/** @type {?} */ (alg)))) { this.loggerService.logWarning('Only RS256 supported'); return false; } /** @type {?} */ let isValid = false; if (!header_data.hasOwnProperty('kid')) { // exactly 1 key in the jwtkeys and no kid in the Jose header // kty "RSA" use "sig" /** @type {?} */ let amountOfMatchingKeys = 0; for (const key of jwtkeys.keys) { if (((/** @type {?} */ (key.kty))) === 'RSA' && ((/** @type {?} */ (key.use))) === 'sig') { amountOfMatchingKeys = amountOfMatchingKeys + 1; } } if (amountOfMatchingKeys === 0) { this.loggerService.logWarning('no keys found, incorrect Signature, validation failed for id_token'); return false; } else if (amountOfMatchingKeys > 1) { this.loggerService.logWarning('no ID Token kid claim in JOSE header and multiple supplied in jwks_uri'); return false; } else { for (const key of jwtkeys.keys) { if (((/** @type {?} */ (key.kty))) === 'RSA' && ((/** @type {?} */ (key.use))) === 'sig') { /** @type {?} */ const publickey = KEYUTIL.getKey(key); isValid = KJUR.jws.JWS.verify(id_token, publickey, ['RS256']); if (!isValid) { this.loggerService.logWarning('incorrect Signature, validation failed for id_token'); } return isValid; } } } } else { // kid in the Jose header of id_token for (const key of jwtkeys.keys) { if (((/** @type {?} */ (key.kid))) === ((/** @type {?} */ (kid)))) { /** @type {?} */ const publickey = KEYUTIL.getKey(key); isValid = KJUR.jws.JWS.verify(id_token, publickey, ['RS256']); if (!isValid) { this.loggerService.logWarning('incorrect Signature, validation failed for id_token'); } return isValid; } } } return isValid; } /** * @param {?} response_type * @return {?} */ config_validate_response_type(response_type) { if (response_type === 'id_token token' || response_type === 'id_token') { return true; } if (response_type === 'code') { return true; } this.loggerService.logWarning('module configure incorrect, invalid response_type:' + response_type); return false; } // 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. /** * @param {?} access_token * @param {?} at_hash * @param {?} isCodeFlow * @return {?} */ validate_id_token_at_hash(access_token, at_hash, isCodeFlow) { this.loggerService.logDebug('at_hash from the server:' + at_hash); // The at_hash is optional for the code flow if (isCodeFlow) { if (!((/** @type {?} */ (at_hash)))) { this.loggerService.logDebug('Code Flow active, and no at_hash in the id_token, skipping check!'); return true; } } /** @type {?} */ const testdata = this.generate_at_hash('' + access_token); this.loggerService.logDebug('at_hash client validation not decoded:' + testdata); if (testdata === ((/** @type {?} */ (at_hash)))) { return true; // isValid; } else { /** @type {?} */ const testValue = this.generate_at_hash('' + decodeURIComponent(access_token)); this.loggerService.logDebug('-gen access--' + testValue); if (testValue === ((/** @type {?} */ (at_hash)))) { return true; // isValid } } return false; } /** * @private * @param {?} access_token * @return {?} */ generate_at_hash(access_token) { /** @type {?} */ const hash = KJUR.crypto.Util.hashString(access_token, 'sha256'); /** @type {?} */ const first128bits = hash.substr(0, hash.length / 2); /** @type {?} */ const testdata = hextob64u(first128bits); return testdata; } /** * @param {?} code_challenge * @return {?} */ generate_code_verifier(code_challenge) { /** @type {?} */ const hash = KJUR.crypto.Util.hashString(code_challenge, 'sha256'); /** @type {?} */ const testdata = hextob64u(hash); return testdata; } } OidcSecurityValidation.decorators = [ { type: Injectable } ]; /** @nocollapse */ OidcSecurityValidation.ctorParameters = () => [ { type: EqualityHelperService }, { type: TokenHelperService }, { type: LoggerService } ]; if (false) { /** * @type {?} * @private */ OidcSecurityValidation.prototype.arrayHelperService; /** * @type {?} * @private */ OidcSecurityValidation.prototype.tokenHelperService; /** * @type {?} * @private */ OidcSecurityValidation.prototype.loggerService; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2lkYy5zZWN1cml0eS52YWxpZGF0aW9uLmpzIiwic291cmNlUm9vdCI6Im5nOi8vYW5ndWxhci1hdXRoLW9pZGMtY2xpZW50LyIsInNvdXJjZXMiOlsibGliL3NlcnZpY2VzL29pZGMuc2VjdXJpdHkudmFsaWRhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDckQsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDdkUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDakUsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLHVCQUF1QixDQUFDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBNkN0RCxNQUFNLE9BQU8sc0JBQXNCOzs7Ozs7SUFDL0IsWUFDWSxrQkFBeUMsRUFDekMsa0JBQXNDLEVBQ3RDLGFBQTRCO1FBRjVCLHVCQUFrQixHQUFsQixrQkFBa0IsQ0FBdUI7UUFDekMsdUJBQWtCLEdBQWxCLGtCQUFrQixDQUFvQjtRQUN0QyxrQkFBYSxHQUFiLGFBQWEsQ0FBZTtJQUNyQyxDQUFDOzs7Ozs7O0lBR0osY0FBYyxDQUFDLEtBQWEsRUFBRSxhQUFzQjs7WUFDNUMsT0FBWTtRQUNoQixPQUFPLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLG1CQUFtQixDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztRQUVwRSxPQUFPLENBQUMsSUFBSSxDQUFDLGlDQUFpQyxDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsQ0FBQztJQUMzRSxDQUFDOzs7Ozs7O0lBR0QsaUNBQWlDLENBQUMsZ0JBQXdCLEVBQUUsYUFBc0I7O2NBQ3hFLG1CQUFtQixHQUFHLElBQUksQ0FBQyxrQkFBa0Is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QUFDLG1CQUFBLE9BQU8sRUFBVSxDQUFDLEVBQUU7Z0JBQ25DLE9BQU8sSUFBSSxDQUFDLENBQUMsVUFBVTthQUMxQjtTQUNKO1FBRUQsT0FBTyxLQUFLLENBQUM7SUFDakIsQ0FBQzs7Ozs7O0lBRU8sZ0JBQWdCLENBQUMsWUFBaUI7O2NBQ2hDLElBQUksR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsWUFBWSxFQUFFLFFBQVEsQ0FBQzs7Y0FDMUQsWUFBWSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDOztjQUM5QyxRQUFRLEdBQUcsU0FBUyxDQUFDLFlBQVksQ0FBQztRQUV4QyxPQUFPLFFBQVEsQ0FBQztJQUNwQixDQUFDOzs7OztJQUVELHNCQUFzQixDQUFDLGNBQW1COztjQUNoQyxJQUFJLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGNBQWMsRUFBRSxRQUFRLENBQUM7O2NBQzVELFFBQVEsR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDO1FBRWhDLE9BQU8sUUFBUSxDQUFDO0lBQ3BCLENBQUM7OztZQTdVSixVQUFVOzs7O1lBOUNGLHFCQUFxQjtZQUNyQixrQkFBa0I7WUFDbEIsYUFBYTs7Ozs7OztJQStDZCxvREFBaUQ7Ozs7O0lBQ2pELG9EQUE4Qzs7Ozs7SUFDOUMsK0NBQW9DIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgaGV4dG9iNjR1LCBLRVlVVElMLCBLSlVSIH0gZnJvbSAnanNyc2FzaWduJztcbmltcG9ydCB7IEVxdWFsaXR5SGVscGVyU2VydmljZSB9IGZyb20gJy4vb2lkYy1lcXVhbGl0eS1oZWxwZXIuc2VydmljZSc7XG5pbXBvcnQgeyBUb2tlbkhlbHBlclNlcnZpY2UgfSBmcm9tICcuL29pZGMtdG9rZW4taGVscGVyLnNlcnZpY2UnO1xuaW1wb3J0IHsgTG9nZ2VyU2VydmljZSB9IGZyb20gJy4vb2lkYy5sb2dnZXIuc2VydmljZSc7XG5cbi8vIGh0dHA6Ly9vcGVuaWQubmV0L3NwZWNzL29wZW5pZC1jb25uZWN0LWltcGxpY2l0LTFfMC5odG1sXG5cbi8vIGlkX3Rva2VuXG4vLyBpZF90b2tlbiBDMTogVGhlIElzc3VlciBJZGVudGlmaWVyIGZvciB0aGUgT3BlbklEIFByb3ZpZGVyICh3aGljaCBpcyB0eXBpY2FsbHkgb2J0YWluZWQgZHVyaW5nIERpc2NvdmVyeSlcbi8vIE1VU1QgZXhhY3RseSBtYXRjaCB0aGUgdmFsdWUgb2YgdGhlIGlzcyAoaXNzdWVyKSBDbGFpbS5cbi8vXG4vLyBpZF90b2tlbiBDMjogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoYXQgdGhlIGF1ZCAoYXVkaWVuY2UpIENsYWltIGNvbnRhaW5zIGl0cyBjbGllbnRfaWQgdmFsdWUgcmVnaXN0ZXJlZCBhdCB0aGUgSXNzdWVyIGlkZW50aWZpZWRcbi8vIGJ5IHRoZSBpc3MgKGlzc3VlcikgQ2xhaW0gYXMgYW4gYXVkaWVuY2UuVGhlIElEIFRva2VuIE1VU1QgYmUgcmVqZWN0ZWQgaWYgdGhlIElEIFRva2VuIGRvZXMgbm90IGxpc3QgdGhlIENsaWVudCBhcyBhIHZhbGlkIGF1ZGllbmNlLFxuLy8gb3IgaWYgaXQgY29udGFpbnMgYWRkaXRpb25hbCBhdWRpZW5jZXMgbm90IHRydXN0ZWQgYnkgdGhlIENsaWVudC5cbi8vXG4vLyBpZF90b2tlbiBDMzogSWYgdGhlIElEIFRva2VuIGNvbnRhaW5zIG11bHRpcGxlIGF1ZGllbmNlcywgdGhlIENsaWVudCBTSE9VTEQgdmVyaWZ5IHRoYXQgYW4gYXpwIENsYWltIGlzIHByZXNlbnQuXG4vL1xuLy8gaWRfdG9rZW4gQzQ6IElmIGFuIGF6cCAoYXV0aG9yaXplZCBwYXJ0eSkgQ2xhaW0gaXMgcHJlc2VudCwgdGhlIENsaWVudCBTSE9VTEQgdmVyaWZ5IHRoYXQgaXRzIGNsaWVudF9pZCBpcyB0aGUgQ2xhaW0gVmFsdWUuXG4vL1xuLy8gaWRfdG9rZW4gQzU6IFRoZSBDbGllbnQgTVVTVCB2YWxpZGF0ZSB0aGUgc2lnbmF0dXJlIG9mIHRoZSBJRCBUb2tlbiBhY2NvcmRpbmcgdG8gSldTIFtKV1NdIHVzaW5nIHRoZSBhbGdvcml0aG0gc3BlY2lmaWVkIGluIHRoZVxuLy8gYWxnIEhlYWRlciBQYXJhbWV0ZXIgb2YgdGhlIEpPU0UgSGVhZGVyLlRoZSBDbGllbnQgTVVTVCB1c2UgdGhlIGtleXMgcHJvdmlkZWQgYnkgdGhlIElzc3Vlci5cbi8vXG4vLyBpZF90b2tlbiBDNjogVGhlIGFsZyB2YWx1ZSBTSE9VTEQgYmUgUlMyNTYuIFZhbGlkYXRpb24gb2YgdG9rZW5zIHVzaW5nIG90aGVyIHNpZ25pbmcgYWxnb3JpdGhtcyBpcyBkZXNjcmliZWQgaW4gdGhlIE9wZW5JRCBDb25uZWN0IENvcmUgMS4wXG4vLyBbT3BlbklELkNvcmVdIHNwZWNpZmljYXRpb24uXG4vL1xuLy8gaWRfdG9rZW4gQzc6IFRoZSBjdXJyZW50IHRpbWUgTVVTVCBiZSBiZWZvcmUgdGhlIHRpbWUgcmVwcmVzZW50ZWQgYnkgdGhlIGV4cCBDbGFpbSAocG9zc2libHkgYWxsb3dpbmcgZm9yIHNvbWUgc21hbGwgbGVld2F5IHRvIGFjY291bnRcbi8vIGZvciBjbG9jayBza2V3KS5cbi8vXG4vLyBpZF90b2tlbiBDODogVGhlIGlhdCBDbGFpbSBjYW4gYmUgdXNlZCB0byByZWplY3QgdG9rZW5zIHRoYXQgd2VyZSBpc3N1ZWQgdG9vIGZhciBhd2F5IGZyb20gdGhlIGN1cnJlbnQgdGltZSxcbi8vIGxpbWl0aW5nIHRoZSBhbW91bnQgb2YgdGltZSB0aGF0IG5vbmNlcyBuZWVkIHRvIGJlIHN0b3JlZCB0byBwcmV2ZW50IGF0dGFja3MuVGhlIGFjY2VwdGFibGUgcmFuZ2UgaXMgQ2xpZW50IHNwZWNpZmljLlxuLy9cbi8vIGlkX3Rva2VuIEM5OiBUaGUgdmFsdWUgb2YgdGhlIG5vbmNlIENsYWltIE1VU1QgYmUgY2hlY2tlZCB0byB2ZXJpZnkgdGhhdCBpdCBpcyB0aGUgc2FtZSB2YWx1ZSBhcyB0aGUgb25lIHRoYXQgd2FzIHNlbnRcbi8vIGluIHRoZSBBdXRoZW50aWNhdGlvbiBSZXF1ZXN0LlRoZSBDbGllbnQgU0hPVUxEIGNoZWNrIHRoZSBub25jZSB2YWx1ZSBmb3IgcmVwbGF5IGF0dGFja3MuVGhlIHByZWNpc2UgbWV0aG9kIGZvciBkZXRlY3RpbmcgcmVwbGF5IGF0dGFja3Ncbi8vIGlzIENsaWVudCBzcGVjaWZpYy5cbi8vXG4vLyBpZF90b2tlbiBDMTA6IElmIHRoZSBhY3IgQ2xhaW0gd2FzIHJlcXVlc3RlZCwgdGhlIENsaWVudCBTSE9VTEQgY2hlY2sgdGhhdCB0aGUgYXNzZXJ0ZWQgQ2xhaW0gVmFsdWUgaXMgYXBwcm9wcmlhdGUuXG4vLyBUaGUgbWVhbmluZyBhbmQgcHJvY2Vzc2luZyBvZiBhY3IgQ2xhaW0gVmFsdWVzIGlzIG91dCBvZiBzY29wZSBmb3IgdGhpcyBkb2N1bWVudC5cbi8vXG4vLyBpZF90b2tlbiBDMTE6IFdoZW4gYSBtYXhfYWdlIHJlcXVlc3QgaXMgbWFkZSwgdGhlIENsaWVudCBTSE9VTEQgY2hlY2sgdGhlIGF1dGhfdGltZSBDbGFpbSB2YWx1ZSBhbmQgcmVxdWVzdCByZS0gYXV0aGVudGljYXRpb25cbi8vIGlmIGl0IGRldGVybWluZXMgdG9vIG11Y2ggdGltZSBoYXMgZWxhcHNlZCBzaW5jZSB0aGUgbGFzdCBFbmQtIFVzZXIgYXV0aGVudGljYXRpb24uXG5cbi8vIEFjY2VzcyBUb2tlbiBWYWxpZGF0aW9uXG4vLyBhY2Nlc3NfdG9rZW4gQzE6IEhhc2ggdGhlIG9jdGV0cyBvZiB0aGUgQVNDSUkgcmVwcmVzZW50YXRpb24gb2YgdGhlIGFjY2Vzc190b2tlbiB3aXRoIHRoZSBoYXNoIGFsZ29yaXRobSBzcGVjaWZpZWQgaW4gSldBW0pXQV1cbi8vIGZvciB0aGUgYWxnIEhlYWRlciBQYXJhbWV0ZXIgb2YgdGhlIElEIFRva2VuJ3MgSk9TRSBIZWFkZXIuIEZvciBpbnN0YW5jZSwgaWYgdGhlIGFsZyBpcyBSUzI1NiwgdGhlIGhhc2ggYWxnb3JpdGhtIHVzZWQgaXMgU0hBLTI1Ni5cbi8vIGFjY2Vzc190b2tlbiBDMjogVGFrZSB0aGUgbGVmdC0gbW9zdCBoYWxmIG9mIHRoZSBoYXNoIGFuZCBiYXNlNjR1cmwtIGVuY29kZSBpdC5cbi8vIGFjY2Vzc190b2tlbiBDMzogVGhlIHZhbHVlIG9mIGF0X2hhc2ggaW4gdGhlIElEIFRva2VuIE1VU1QgbWF0Y2ggdGhlIHZhbHVlIHByb2R1Y2VkIGluIHRoZSBwcmV2aW91cyBzdGVwIGlmIGF0X2hhc2ggaXMgcHJlc2VudCBpbiB0aGUgSUQgVG9rZW4uXG5cbkBJbmplY3RhYmxlKClcbmV4cG9ydCBjbGFzcyBPaWRjU2VjdXJpdHlWYWxpZGF0aW9uIHtcbiAgICBjb25zdHJ1Y3RvcihcbiAgICAgICAgcHJpdmF0ZSBhcnJheUhlbHBlclNlcnZpY2U6IEVxdWFsaXR5SGVscGVyU2VydmljZSxcbiAgICAgICAgcHJpdmF0ZSB0b2tlbkhlbHBlclNlcnZpY2U6IFRva2VuSGVscGVyU2VydmljZSxcbiAgICAgICAgcHJpdmF0ZSBsb2dnZXJTZXJ2aWNlOiBMb2dnZXJTZXJ2aWNlXG4gICAgKSB7fVxuXG4gICAgLy8gaWRfdG9rZW4gQzc6IFRoZSBjdXJyZW50IHRpbWUgTVVTVCBiZSBiZWZvcmUgdGhlIHRpbWUgcmVwcmVzZW50ZWQgYnkgdGhlIGV4cCBDbGFpbSAocG9zc2libHkgYWxsb3dpbmcgZm9yIHNvbWUgc21hbGwgbGVld2F5IHRvIGFjY291bnQgZm9yIGNsb2NrIHNrZXcpLlxuICAgIGlzVG9rZW5FeHBpcmVkKHRva2VuOiBzdHJpbmcsIG9mZnNldFNlY29uZHM/OiBudW1iZXIpOiBib29sZWFuIHtcbiAgICAgICAgbGV0IGRlY29kZWQ6IGFueTtcbiAgICAgICAgZGVjb2RlZCA9IHRoaXMudG9rZW5IZWxwZXJTZXJ2aWNlLmdldFBheWxvYWRGcm9tVG9rZW4odG9rZW4sIGZhbHNlKTtcblxuICAgICAgICByZXR1cm4gIXRoaXMudmFsaWRhdGVfaWRfdG9rZW5fZXhwX25vdF9leHBpcmVkKGRlY29kZWQsIG9mZnNldFNlY29uZHMpO1xuICAgIH1cblxuICAgIC8vIGlkX3Rva2VuIEM3OiBUaGUgY3VycmVudCB0aW1lIE1VU1QgYmUgYmVmb3JlIHRoZSB0aW1lIHJlcHJlc2VudGVkIGJ5IHRoZSBleHAgQ2xhaW0gKHBvc3NpYmx5IGFsbG93aW5nIGZvciBzb21lIHNtYWxsIGxlZXdheSB0byBhY2NvdW50IGZvciBjbG9jayBza2V3KS5cbiAgICB2YWxpZGF0ZV9pZF90b2tlbl9leHBfbm90X2V4cGlyZWQoZGVjb2RlZF9pZF90b2tlbjogc3RyaW5nLCBvZmZzZXRTZWNvbmRzPzogbnVtYmVyKTogYm9vbGVhbiB7XG4gICAgICAgIGNvbnN0IHRva2VuRXhwaXJhdGlvbkRhdGUgPSB0aGlzLnRva2VuSGVscGVyU2VydmljZS5nZXRUb2tlbkV4cGlyYXRpb25EYXRlKGRlY29kZWRfaWRfdG9rZW4pO1xuICAgICAgICBvZmZzZXRTZWNvbmRzID0gb2Zmc2V0U2Vjb25kcyB8fCAwO1xuXG4gICAgICAgIGlmICghdG9rZW5FeHBpcmF0aW9uRGF0ZSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgdG9rZW5FeHBpcmF0aW9uVmFsdWUgPSB0b2tlbkV4cGlyYXRpb25EYXRlLnZhbHVlT2YoKTtcbiAgICAgICAgY29uc3Qgbm93V2l0aE9mZnNldCA9IG5ldyBEYXRlKCkudmFsdWVPZigpICsgb2Zmc2V0U2Vjb25kcyAqIDEwMDA7XG4gICAgICAgIGNvbnN0IHRva2VuTm90RXhwaXJlZCA9IHRva2VuRXhwaXJhdGlvblZhbHVlID4gbm93V2l0aE9mZnNldDtcblxuICAgICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nRGVidWcoYFRva2VuIG5vdCBleHBpcmVkPzogJHt0b2tlbkV4cGlyYXRpb25WYWx1ZX0gPiAke25vd1dpdGhPZmZzZXR9ICAoJHt0b2tlbk5vdEV4cGlyZWR9KWApO1xuXG4gICAgICAgIC8vIFRva2VuIG5vdCBleHBpcmVkP1xuICAgICAgICByZXR1cm4gdG9rZW5Ob3RFeHBpcmVkO1xuICAgIH1cblxuICAgIC8vIGlzc1xuICAgIC8vIFJFUVVJUkVELiBJc3N1ZXIgSWRlbnRpZmllciBmb3IgdGhlIElzc3VlciBvZiB0aGUgcmVzcG9uc2UuVGhlIGlzcyB2YWx1ZSBpcyBhIGNhc2Utc2Vuc2l0aXZlIFVSTCB1c2luZyB0aGUgaHR0cHMgc2NoZW1lIHRoYXQgY29udGFpbnMgc2NoZW1lLCBob3N0LFxuICAgIC8vIGFuZCBvcHRpb25hbGx5LCBwb3J0IG51bWJlciBhbmQgcGF0aCBjb21wb25lbnRzIGFuZCBubyBxdWVyeSBvciBmcmFnbWVudCBjb21wb25lbnRzLlxuICAgIC8vXG4gICAgLy8gc3ViXG4gICAgLy8gUkVRVUlSRUQuIFN1YmplY3QgSWRlbnRpZmllci5Mb2NhbGx5IHVuaXF1ZSBhbmQgbmV2ZXIgcmVhc3NpZ25lZCBpZGVudGlmaWVyIHdpdGhpbiB0aGUgSXNzdWVyIGZvciB0aGUgRW5kLSBVc2VyLFxuICAgIC8vIHdoaWNoIGlzIGludGVuZGVkIHRvIGJlIGNvbnN1bWVkIGJ5IHRoZSBDbGllbnQsIGUuZy4sIDI0NDAwMzIwIG9yIEFJdE9hd213dFd3Y1QwazUxQmF5ZXdOdnV0ckpVcXN2bDZxczdBNC5cbiAgICAvLyBJdCBNVVNUIE5PVCBleGNlZWQgMjU1IEFTQ0lJIGNoYXJhY3RlcnMgaW4gbGVuZ3RoLlRoZSBzdWIgdmFsdWUgaXMgYSBjYXNlLXNlbnNpdGl2ZSBzdHJpbmcuXG4gICAgLy9cbiAgICAvLyBhdWRcbiAgICAvLyBSRVFVSVJFRC4gQXVkaWVuY2UocykgdGhhdCB0aGlzIElEIFRva2VuIGlzIGludGVuZGVkIGZvci4gSXQgTVVTVCBjb250YWluIHRoZSBPQXV0aCAyLjAgY2xpZW50X2lkIG9mIHRoZSBSZWx5aW5nIFBhcnR5IGFzIGFuIGF1ZGllbmNlIHZhbHVlLlxuICAgIC8vIEl0IE1BWSBhbHNvIGNvbnRhaW4gaWRlbnRpZmllcnMgZm9yIG90aGVyIGF1ZGllbmNlcy5JbiB0aGUgZ2VuZXJhbCBjYXNlLCB0aGUgYXVkIHZhbHVlIGlzIGFuIGFycmF5IG9mIGNhc2Utc2Vuc2l0aXZlIHN0cmluZ3MuXG4gICAgLy8gSW4gdGhlIGNvbW1vbiBzcGVjaWFsIGNhc2Ugd2hlbiB0aGVyZSBpcyBvbmUgYXVkaWVuY2UsIHRoZSBhdWQgdmFsdWUgTUFZIGJlIGEgc2luZ2xlIGNhc2Utc2Vuc2l0aXZlIHN0cmluZy5cbiAgICAvL1xuICAgIC8vIGV4cFxuICAgIC8vIFJFUVVJUkVELiBFeHBpcmF0aW9uIHRpbWUgb24gb3IgYWZ0ZXIgd2hpY2ggdGhlIElEIFRva2VuIE1VU1QgTk9UIGJlIGFjY2VwdGVkIGZvciBwcm9jZXNzaW5nLlxuICAgIC8vIFRoZSBwcm9jZXNzaW5nIG9mIHRoaXMgcGFyYW1ldGVyIHJlcXVpcmVzIHRoYXQgdGhlIGN1cnJlbnQgZGF0ZS8gdGltZSBNVVNUIGJlIGJlZm9yZSB0aGUgZXhwaXJhdGlvbiBkYXRlLyB0aW1lIGxpc3RlZCBpbiB0aGUgdmFsdWUuXG4gICAgLy8gSW1wbGVtZW50ZXJzIE1BWSBwcm92aWRlIGZvciBzb21lIHNtYWxsIGxlZXdheSwgdXN1YWxseSBubyBtb3JlIHRoYW4gYSBmZXcgbWludXRlcywgdG8gYWNjb3VudCBmb3IgY2xvY2sgc2tldy5cbiAgICAvLyBJdHMgdmFsdWUgaXMgYSBKU09OIFtSRkM3MTU5XSBudW1iZXIgcmVwcmVzZW50aW5nIHRoZSBudW1iZXIgb2Ygc2Vjb25kcyBmcm9tIDE5NzAtIDAxIC0gMDFUMDA6IDAwOjAwWiBhcyBtZWFzdXJlZCBpbiBVVEMgdW50aWwgdGhlIGRhdGUvIHRpbWUuXG4gICAgLy8gU2VlIFJGQyAzMzM5IFtSRkMzMzM5XSBmb3IgZGV0YWlscyByZWdhcmRpbmcgZGF0ZS8gdGltZXMgaW4gZ2VuZXJhbCBhbmQgVVRDIGluIHBhcnRpY3VsYXIuXG4gICAgLy9cbiAgICAvLyBpYXRcbiAgICAvLyBSRVFVSVJFRC4gVGltZSBhdCB3aGljaCB0aGUgSldUIHdhcyBpc3N1ZWQuIEl0cyB2YWx1ZSBpcyBhIEpTT04gbnVtYmVyIHJlcHJlc2VudGluZyB0aGUgbnVtYmVyIG9mIHNlY29uZHMgZnJvbSAxOTcwLSAwMSAtIDAxVDAwOiAwMDowMFogYXMgbWVhc3VyZWRcbiAgICAvLyBpbiBVVEMgdW50aWwgdGhlIGRhdGUvIHRpbWUuXG4gICAgdmFsaWRhdGVfcmVxdWlyZWRfaWRfdG9rZW4oZGF0YUlkVG9rZW46IGFueSk6IGJvb2xlYW4ge1xuICAgICAgICBsZXQgdmFsaWRhdGVkID0gdHJ1ZTtcbiAgICAgICAgaWYgKCFkYXRhSWRUb2tlbi5oYXNPd25Qcm9wZXJ0eSgnaXNzJykpIHtcbiAgICAgICAgICAgIHZhbGlkYXRlZCA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ2lzcyBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbicpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFkYXRhSWRUb2tlbi5oYXNPd25Qcm9wZXJ0eSgnc3ViJykpIHtcbiAgICAgICAgICAgIHZhbGlkYXRlZCA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ3N1YiBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbicpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFkYXRhSWRUb2tlbi5oYXNPd25Qcm9wZXJ0eSgnYXVkJykpIHtcbiAgICAgICAgICAgIHZhbGlkYXRlZCA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ2F1ZCBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbicpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFkYXRhSWRUb2tlbi5oYXNPd25Qcm9wZXJ0eSgnZXhwJykpIHtcbiAgICAgICAgICAgIHZhbGlkYXRlZCA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ2V4cCBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbicpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCFkYXRhSWRUb2tlbi5oYXNPd25Qcm9wZXJ0eSgnaWF0JykpIHtcbiAgICAgICAgICAgIHZhbGlkYXRlZCA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ2lhdCBpcyBtaXNzaW5nLCB0aGlzIGlzIHJlcXVpcmVkIGluIHRoZSBpZF90b2tlbicpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHZhbGlkYXRlZDtcbiAgICB9XG5cbiAgICAvLyBpZF90b2tlbiBDODogVGhlIGlhdCBDbGFpbSBjYW4gYmUgdXNlZCB0byByZWplY3QgdG9rZW5zIHRoYXQgd2VyZSBpc3N1ZWQgdG9vIGZhciBhd2F5IGZyb20gdGhlIGN1cnJlbnQgdGltZSxcbiAgICAvLyBsaW1pdGluZyB0aGUgYW1vdW50IG9mIHRpbWUgdGhhdCBub25jZXMgbmVlZCB0byBiZSBzdG9yZWQgdG8gcHJldmVudCBhdHRhY2tzLlRoZSBhY2NlcHRhYmxlIHJhbmdlIGlzIENsaWVudCBzcGVjaWZpYy5cbiAgICB2YWxpZGF0ZV9pZF90b2tlbl9pYXRfbWF4X29mZnNldChkYXRhSWRUb2tlbjogYW55LFxuICAgICAgICBtYXhfb2Zmc2V0X2FsbG93ZWRfaW5fc2Vjb25kczogbnVtYmVyLFxuICAgICAgICBkaXNhYmxlX2lhdF9vZmZzZXRfdmFsaWRhdGlvbjogYm9vbGVhbik6IGJvb2xlYW4ge1xuXG4gICAgICAgIGlmIChkaXNhYmxlX2lhdF9vZmZzZXRfdmFsaWRhdGlvbikge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWRhdGFJZFRva2VuLmhhc093blByb3BlcnR5KCdpYXQnKSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgZGF0ZVRpbWVfaWF0X2lkX3Rva2VuID0gbmV3IERhdGUoMCk7IC8vIFRoZSAwIGhlcmUgaXMgdGhlIGtleSwgd2hpY2ggc2V0cyB0aGUgZGF0ZSB0byB0aGUgZXBvY2hcbiAgICAgICAgZGF0ZVRpbWVfaWF0X2lkX3Rva2VuLnNldFVUQ1NlY29uZHMoZGF0YUlkVG9rZW4uaWF0KTtcblxuICAgICAgICBtYXhfb2Zmc2V0X2FsbG93ZWRfaW5fc2Vjb25kcyA9IG1heF9vZmZzZXRfYWxsb3dlZF9pbl9zZWNvbmRzIHx8IDA7XG5cbiAgICAgICAgaWYgKGRhdGVUaW1lX2lhdF9pZF90b2tlbiA9PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICAndmFsaWRhdGVfaWRfdG9rZW5faWF0X21heF9vZmZzZXQ6ICcgK1xuICAgICAgICAgICAgICAgIChuZXcgRGF0ZSgpLnZhbHVlT2YoKSAtIGRhdGVUaW1lX2lhdF9pZF90b2tlbi52YWx1ZU9mKCkpICtcbiAgICAgICAgICAgICAgICAnIDwgJyArXG4gICAgICAgICAgICAgICAgbWF4X29mZnNldF9hbGxvd2VkX2luX3NlY29uZHMgKiAxMDAwXG4gICAgICAgICk7XG4gICAgICAgIHJldHVybiBuZXcgRGF0ZSgpLnZhbHVlT2YoKSAtIGRhdGVUaW1lX2lhdF9pZF90b2tlbi52YWx1ZU9mKCkgPCBtYXhfb2Zmc2V0X2FsbG93ZWRfaW5fc2Vjb25kcyAqIDEwMDA7XG4gICAgfVxuXG4gICAgLy8gaWRfdG9rZW4gQzk6IFRoZSB2YWx1ZSBvZiB0aGUgbm9uY2UgQ2xhaW0gTVVTVCBiZSBjaGVja2VkIHRvIHZlcmlmeSB0aGF0IGl0IGlzIHRoZSBzYW1lIHZhbHVlIGFzIHRoZSBvbmVcbiAgICAvLyB0aGF0IHdhcyBzZW50IGluIHRoZSBBdXRoZW50aWNhdGlvbiBSZXF1ZXN0LlRoZSBDbGllbnQgU0hPVUxEIGNoZWNrIHRoZSBub25jZSB2YWx1ZSBmb3IgcmVwbGF5IGF0dGFja3MuXG4gICAgLy8gVGhlIHByZWNpc2UgbWV0aG9kIGZvciBkZXRlY3RpbmcgcmVwbGF5IGF0dGFja3MgaXMgQ2xpZW50IHNwZWNpZmljLlxuICAgIHZhbGlkYXRlX2lkX3Rva2VuX25vbmNlKGRhdGFJZFRva2VuOiBhbnksIGxvY2FsX25vbmNlOiBhbnkpOiBib29sZWFuIHtcbiAgICAgICAgaWYgKGRhdGFJZFRva2VuLm5vbmNlICE9PSBsb2NhbF9ub25jZSkge1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ0RlYnVnKCdWYWxpZGF0ZV9pZF90b2tlbl9ub25jZSBmYWlsZWQsIGRhdGFJZFRva2VuLm5vbmNlOiAnICsgZGF0YUlkVG9rZW4ubm9uY2UgKyAnIGxvY2FsX25vbmNlOicgKyBsb2NhbF9ub25jZSk7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICAvLyBpZF90b2tlbiBDMTogVGhlIElzc3VlciBJZGVudGlmaWVyIGZvciB0aGUgT3BlbklEIFByb3ZpZGVyICh3aGljaCBpcyB0eXBpY2FsbHkgb2J0YWluZWQgZHVyaW5nIERpc2NvdmVyeSlcbiAgICAvLyBNVVNUIGV4YWN0bHkgbWF0Y2ggdGhlIHZhbHVlIG9mIHRoZSBpc3MgKGlzc3VlcikgQ2xhaW0uXG4gICAgdmFsaWRhdGVfaWRfdG9rZW5faXNzKGRhdGFJZFRva2VuOiBhbnksIGF1dGhXZWxsS25vd25FbmRwb2ludHNfaXNzdWVyOiBhbnkpOiBib29sZWFuIHtcbiAgICAgICAgaWYgKChkYXRhSWRUb2tlbi5pc3MgYXMgc3RyaW5nKSAhPT0gKGF1dGhXZWxsS25vd25FbmRwb2ludHNfaXNzdWVyIGFzIHN0cmluZykpIHtcbiAgICAgICAgICAgIHRoaXMubG9nZ2VyU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgICAgICAnVmFsaWRhdGVfaWRfdG9rZW5faXNzIGZhaWxlZCwgZGF0YUlkVG9rZW4uaXNzOiAnICtcbiAgICAgICAgICAgICAgICAgICAgZGF0YUlkVG9rZW4uaXNzICtcbiAgICAgICAgICAgICAgICAgICAgJyBhdXRoV2VsbEtub3duRW5kcG9pbnRzIGlzc3VlcjonICtcbiAgICAgICAgICAgICAgICAgICAgYXV0aFdlbGxLbm93bkVuZHBvaW50c19pc3N1ZXJcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICAvLyBpZF90b2tlbiBDMjogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoYXQgdGhlIGF1ZCAoYXVkaWVuY2UpIENsYWltIGNvbnRhaW5zIGl0cyBjbGllbnRfaWQgdmFsdWUgcmVnaXN0ZXJlZCBhdCB0aGUgSXNzdWVyIGlkZW50aWZpZWRcbiAgICAvLyBieSB0aGUgaXNzIChpc3N1ZXIpIENsYWltIGFzIGFuIGF1ZGllbmNlLlxuICAgIC8vIFRoZSBJRCBUb2tlbiBNVVNUIGJlIHJlamVjdGVkIGlmIHRoZSBJRCBUb2tlbiBkb2VzIG5vdCBsaXN0IHRoZSBDbGllbnQgYXMgYSB2YWxpZCBhdWRpZW5jZSwgb3IgaWYgaXQgY29udGFpbnMgYWRkaXRpb25hbCBhdWRpZW5jZXNcbiAgICAvLyBub3QgdHJ1c3RlZCBieSB0aGUgQ2xpZW50LlxuICAgIHZhbGlkYXRlX2lkX3Rva2VuX2F1ZChkYXRhSWRUb2tlbjogYW55LCBhdWQ6IGFueSk6IGJvb2xlYW4ge1xuICAgICAgICBpZiAoZGF0YUlkVG9rZW4uYXVkIGluc3RhbmNlb2YgQXJyYXkpIHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3VsdCA9IHRoaXMuYXJyYXlIZWxwZXJTZXJ2aWNlLmFyZUVxdWFsKGRhdGFJZFRva2VuLmF1ZCwgYXVkKTtcblxuICAgICAgICAgICAgaWYgKCFyZXN1bHQpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nRGVidWcoJ1ZhbGlkYXRlX2lkX3Rva2VuX2F1ZCAgYXJyYXkgZmFpbGVkLCBkYXRhSWRUb2tlbi5hdWQ6ICcgKyBkYXRhSWRUb2tlbi5hdWQgKyAnIGNsaWVudF9pZDonICsgYXVkKTtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9IGVsc2UgaWYgKGRhdGFJZFRva2VuLmF1ZCAhPT0gYXVkKSB7XG4gICAgICAgICAgICB0aGlzLmxvZ2dlclNlcnZpY2UubG9nRGVidWcoJ1ZhbGlkYXRlX2lkX3Rva2VuX2F1ZCBmYWlsZWQsIGRhdGFJZFRva2VuLmF1ZDogJyArIGRhdGFJZFRva2VuLmF1ZCArICcgY2xpZW50X2lkOicgKyBhdWQpO1xuXG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICB2YWxpZGF0ZVN0YXRlRnJvbUhhc2hDYWxsYmFjayhzdGF0ZTogYW55LCBsb2NhbF9zdGF0ZTogYW55KTogYm9vbGVhbiB7XG4gICAgICAgIGlmICgoc3RhdGUgYXMgc3RyaW5nKSAhPT0gKGxvY2FsX3N0YXRlIGFzIHN0cmluZykpIHtcbiAgICAgICAgICAgIHRoaXMubG9nZ2VyU2VydmljZS5sb2dEZWJ1ZygnVmFsaWRhdGVTdGF0ZUZyb21IYXNoQ2FsbGJhY2sgZmFpbGVkLCBzdGF0ZTogJyArIHN0YXRlICsgJyBsb2NhbF9zdGF0ZTonICsgbG9jYWxfc3RhdGUpO1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgdmFsaWRhdGVfdXNlcmRhdGFfc3ViX2lkX3Rva2VuKGlkX3Rva2VuX3N1YjogYW55LCB1c2VyZGF0YV9zdWI6IGFueSk6IGJvb2xlYW4ge1xuICAgICAgICBpZiAoKGlkX3Rva2VuX3N1YiBhcyBzdHJpbmcpICE9PSAodXNlcmRhdGFfc3ViIGFzIHN0cmluZykpIHtcbiAgICAgICAgICAgIHRoaXMubG9nZ2VyU2VydmljZS5sb2dEZWJ1ZygndmFsaWRhdGVfdXNlcmRhdGFfc3ViX2lkX3Rva2VuIGZhaWxlZCwgaWRfdG9rZW5fc3ViOiAnICsgaWRfdG9rZW5fc3ViICsgJyB1c2VyZGF0YV9zdWI6JyArIHVzZXJkYXRhX3N1Yik7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICAvLyBpZF90b2tlbiBDNTogVGhlIENsaWVudCBNVVNUIHZhbGlkYXRlIHRoZSBzaWduYXR1cmUgb2YgdGhlIElEIFRva2VuIGFjY29yZGluZyB0byBKV1MgW0pXU10gdXNpbmcgdGhlIGFsZ29yaXRobSBzcGVjaWZpZWQgaW4gdGhlIGFsZ1xuICAgIC8vIEhlYWRlciBQYXJhbWV0ZXIgb2YgdGhlIEpPU0UgSGVhZGVyLlRoZSBDbGllbnQgTVVTVCB1c2UgdGhlIGtleXMgcHJvdmlkZWQgYnkgdGhlIElzc3Vlci5cbiAgICAvLyBpZF90b2tlbiBDNjogVGhlIGFsZyB2YWx1ZSBTSE9VTEQgYmUgUlMyNTYuIFZhbGlkYXRpb24gb2YgdG9rZW5zIHVzaW5nIG90aGVyIHNpZ25pbmcgYWxnb3JpdGhtcyBpcyBkZXNjcmliZWQgaW4gdGhlXG4gICAgLy8gT3BlbklEIENvbm5lY3QgQ29yZSAxLjAgW09wZW5JRC5Db3JlXSBzcGVjaWZpY2F0aW9uLlxuICAgIHZhbGlkYXRlX3NpZ25hdHVyZV9pZF90b2tlbihpZF90b2tlbjogYW55LCBqd3RrZXlzOiBhbnkpOiBib29sZWFuIHtcbiAgICAgICAgaWYgKCFqd3RrZXlzIHx8ICFqd3RrZXlzLmtleXMpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGhlYWRlcl9kYXRhID0gdGhpcy50b2tlbkhlbHBlclNlcnZpY2UuZ2V0SGVhZGVyRnJvbVRva2VuKGlkX3Rva2VuLCBmYWxzZSk7XG5cbiAgICAgICAgaWYgKE9iamVjdC5rZXlzKGhlYWRlcl9kYXRhKS5sZW5ndGggPT09IDAgJiYgaGVhZGVyX2RhdGEuY29uc3RydWN0b3IgPT09IE9iamVjdCkge1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ2lkIHRva2VuIGhhcyBubyBoZWFkZXIgZGF0YScpO1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3Qga2lkID0gaGVhZGVyX2RhdGEua2lkO1xuICAgICAgICBjb25zdCBhbGcgPSBoZWFkZXJfZGF0YS5hbGc7XG5cbiAgICAgICAgaWYgKCdSUzI1NicgIT09IChhbGcgYXMgc3RyaW5nKSkge1xuICAgICAgICAgICAgdGhpcy5sb2dnZXJTZXJ2aWNlLmxvZ1dhcm5pbmcoJ09ubHkgUlMyNTYgc3VwcG9ydGVkJyk7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICBsZXQgaXNWYWxpZCA9IGZhbHNlO1xuXG4gICAgICAgIGlmICghaGVhZGVyX2RhdGEuaGFzT3duUHJvcGVydHkoJ2tpZCcpKSB7XG4gICAgICAgICAgICAvLyBleGFjdGx5IDEga2V5IGluIHRoZSBqd3RrZXlzIGFuZCBubyBraWQgaW4gdGhlIEpvc2UgaGVhZGVyXG4gICAgICAgICAgICAvLyBrdHlcdFwiUlNBXCIgdXNlIFwic2lnXCJcbiAgICAgICAgICAgIGxldCBhbW91bnRPZk1hdGNoaW5nS2V5cyA9IDA7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGtleSBvZiBqd3RrZXlzLmtleXMpIHtcbiAgICAgICAgICAgICAgICBpZiAoKGtleS5rdHkgYXMgc3RyaW5nKSA9PT0gJ1JTQScgJiYgKGtleS51c2UgYXMgc3RyaW5nKSA9PT0gJ3NpZycpIHtcbiAgICAgICAgICAgICAgICAgICAgYW1vdW50T2ZNYXRjaGluZ0tleXMgPSBhbW91bnR