UNPKG

@sphereon/pex

Version:

A Typescript implementation of the v1 and v2 DIF Presentation Exchange specification

446 lines 45.2 kB
import { CredentialMapper, } from '@sphereon/ssi-types'; import { Status } from './ConstraintUtils'; import { EvaluationClientWrapper } from './evaluation'; import { PresentationSubmissionLocation, } from './signing'; import { PEVersion, SSITypesBuilder } from './types'; import { calculateSdHash, definitionVersionDiscovery, formatValidationErrors, getSubjectIdsAsString } from './utils'; import { PresentationDefinitionV1VB, PresentationDefinitionV2VB, PresentationSubmissionVB, ValidationEngine } from './validation'; /** * This is the main interfacing class to be used by developers using the PEX library. */ export class PEX { _evaluationClientWrapper; options; constructor(options) { // TODO: So we have state in the form of this property which is set in the constructor, but we are overwriting it elsewhere. We need to retrhink how to instantiate PEX this._evaluationClientWrapper = new EvaluationClientWrapper(); this.options = options; } /*** * The evaluatePresentation compares what is expected from one or more presentations with a presentationDefinition. * presentationDefinition: It can be either v1 or v2 of presentationDefinition * * @param presentationDefinition the definition of what is expected in the presentation. * @param presentations the presentation(s) which have to be evaluated in comparison of the definition. * @param opts - limitDisclosureSignatureSuites the credential signature suites that support limit disclosure * * @return the evaluation results specify what was expected and was fulfilled and also specifies which requirements described in the input descriptors * were not fulfilled by the presentation(s). */ evaluatePresentation(presentationDefinition, presentations, opts) { // We map it to an array for now to make processing on the presentations easier, but before checking against the submission // we will transform it to the original structure (array vs single) so the references in the submission stay correct const presentationsArray = Array.isArray(presentations) ? presentations : [presentations]; if (presentationsArray.length === 0) { throw new Error('At least one presentation must be provided'); } const generatePresentationSubmission = opts?.generatePresentationSubmission !== undefined ? opts.generatePresentationSubmission : opts?.presentationSubmission === undefined; const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition); const presentationsCopy = JSON.parse(JSON.stringify(presentationsArray)); const wrappedPresentations = presentationsCopy.map((p) => SSITypesBuilder.mapExternalVerifiablePresentationToWrappedVP(p, this.options?.hasher)); let presentationSubmission = opts?.presentationSubmission; let presentationSubmissionLocation = opts?.presentationSubmissionLocation ?? ((Array.isArray(presentations) && presentations.length > 1) || !CredentialMapper.isW3cPresentation(wrappedPresentations[0].presentation) ? PresentationSubmissionLocation.EXTERNAL : PresentationSubmissionLocation.PRESENTATION); // When only one presentation, we also allow it to be present in the VP if (!presentationSubmission && presentationsArray.length === 1 && CredentialMapper.isW3cPresentation(wrappedPresentations[0].presentation) && !generatePresentationSubmission) { const decoded = wrappedPresentations[0].decoded; if ('presentation_submission' in decoded) { presentationSubmission = decoded.presentation_submission; } if (!presentationSubmission) { throw Error(`Either a presentation submission as part of the VP or provided in options was expected`); } presentationSubmissionLocation = PresentationSubmissionLocation.PRESENTATION; if (opts?.presentationSubmissionLocation && opts.presentationSubmissionLocation !== PresentationSubmissionLocation.PRESENTATION) { throw new Error(`unexpected presentationSubmissionLocation ${opts.presentationSubmissionLocation} was provided. Expected ${PresentationSubmissionLocation.PRESENTATION} when no presentationSubmission passed and first verifiable presentation contains a presentation_submission and generatePresentationSubmission is false`); } } else if (!presentationSubmission && !generatePresentationSubmission) { throw new Error('Presentation submission in options was expected.'); } // TODO: we should probably add support for holder dids in the kb-jwt of an SD-JWT. We can extract this from the // `wrappedPresentation.original.compactKbJwt`, but as HAIP doesn't use dids, we'll leave it for now. const holderDIDs = wrappedPresentations .map((p) => { return CredentialMapper.isW3cPresentation(p.presentation) && p.presentation.holder ? p.presentation.holder : undefined; }) .filter((d) => d !== undefined); const updatedOpts = { ...opts, holderDIDs, presentationSubmission, presentationSubmissionLocation, generatePresentationSubmission, }; const allWvcs = wrappedPresentations.reduce((all, wvp) => [...all, ...wvp.vcs], []); const result = this._evaluationClientWrapper.evaluatePresentations(pd, Array.isArray(presentations) ? wrappedPresentations : wrappedPresentations[0], updatedOpts); if (result.areRequiredCredentialsPresent !== Status.ERROR) { const selectFromClientWrapper = new EvaluationClientWrapper(); const selectResults = selectFromClientWrapper.selectFrom(pd, allWvcs, updatedOpts); if (selectResults.areRequiredCredentialsPresent !== Status.ERROR) { result.errors = []; } } return result; } /*** * The evaluate compares what is expected from a verifiableCredentials with the presentationDefinition. * * @param presentationDefinition the v1 or v2 definition of what is expected in the presentation. * @param verifiableCredentials the verifiable credentials which are candidates to fulfill requirements defined in the presentationDefinition param. * @param opts - holderDIDs the list of the DIDs that the wallet holders controls. Optional, but needed by some input requirements that do a holderDID check. * @ - limitDisclosureSignatureSuites the credential signature suites that support limit disclosure * * @return the evaluation results specify what was expected and was fulfilled and also specifies which requirements described in the input descriptors * were not fulfilled by the verifiable credentials. */ evaluateCredentials(presentationDefinition, verifiableCredentials, opts) { const wrappedVerifiableCredentials = SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(verifiableCredentials, this.options?.hasher); // TODO: So we have state in the form of this property which is set in the constructor, but we are overwriting it here. We need to retrhink how to instantiate PEX this._evaluationClientWrapper = new EvaluationClientWrapper(); const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition); const result = this._evaluationClientWrapper.evaluate(pd, wrappedVerifiableCredentials, opts); if (result.value && result.value.descriptor_map.length) { const selectFromClientWrapper = new EvaluationClientWrapper(); const selectResults = selectFromClientWrapper.selectFrom(pd, wrappedVerifiableCredentials, opts); result.areRequiredCredentialsPresent = selectResults.areRequiredCredentialsPresent; result.errors = selectResults.errors; } else { result.areRequiredCredentialsPresent = Status.ERROR; } return result; } /** * The selectFrom method is a helper function that helps filter out the verifiable credentials which can not be selected and returns * the selectable credentials. * * @param presentationDefinition the v1 or v2 definition of what is expected in the presentation. * @param verifiableCredentials verifiable credentials are the credentials from wallet provided to the library to find selectable credentials. * @param opts - holderDIDs the decentralized identifier(s) of the wallet holderDID. This is used to identify the credentials issued to the holderDID of wallet in certain scenario's. * - limitDisclosureSignatureSuites the credential signature suites that support limit disclosure * * @return the selectable credentials. */ selectFrom(presentationDefinition, verifiableCredentials, opts) { const verifiableCredentialCopy = JSON.parse(JSON.stringify(verifiableCredentials)); const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition); // TODO: So we have state in the form of this property which is set in the constructor, but we are overwriting it here. We need to retrhink how to instantiate PEX this._evaluationClientWrapper = new EvaluationClientWrapper(); return this._evaluationClientWrapper.selectFrom(pd, SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(verifiableCredentialCopy, this.options?.hasher), opts); } presentationSubmissionFrom(presentationDefinition, selectedCredentials, opts) { const pd = SSITypesBuilder.toInternalPresentationDefinition(presentationDefinition); return this._evaluationClientWrapper.submissionFrom(pd, SSITypesBuilder.mapExternalVerifiableCredentialsToWrappedVcs(selectedCredentials, this.options?.hasher), opts); } /** * This method helps create an Unsigned Presentation. An Unsigned Presentation after signing becomes a Presentation. And can be sent to * the verifier after signing it. * * @param presentationDefinition the v1 or v2 definition of what is expected in the presentation. * @param selectedCredentials the credentials which were declared selectable by getSelectableCredentials and then chosen by the intelligent-user * (e.g. human). * @param opts - holderDID optional; the decentralized identity of the wallet holderDID. This is used to identify the holderDID of the presentation. * * @return the presentation. */ presentationFrom(presentationDefinition, selectedCredentials, opts) { const presentationSubmission = this.presentationSubmissionFrom(presentationDefinition, selectedCredentials, opts); const hasSdJwtCredentials = selectedCredentials.some((c) => CredentialMapper.isSdJwtDecodedCredential(c) || CredentialMapper.isSdJwtEncoded(c)); // We could include it in the KB-JWT? Not sure if we want that if (opts?.presentationSubmissionLocation === PresentationSubmissionLocation.PRESENTATION && hasSdJwtCredentials) { throw new Error('Presentation submission location cannot be set to presentation when creating a presentation with an SD-JWT VC'); } const presentationSubmissionLocation = opts?.presentationSubmissionLocation ?? (hasSdJwtCredentials ? PresentationSubmissionLocation.EXTERNAL : PresentationSubmissionLocation.PRESENTATION); const presentations = this.constructPresentations(selectedCredentials, { ...opts, // We only pass in the submission in case it needs to be included in the presentation presentationSubmission: presentationSubmissionLocation === PresentationSubmissionLocation.PRESENTATION ? presentationSubmission : undefined, hasher: this.options?.hasher, }); this.updateSdJwtCredentials(presentations); return { presentations, presentationSubmissionLocation, presentationSubmission, }; } constructPresentations(selectedCredentials, opts) { if (!selectedCredentials) { throw Error(`At least a verifiable credential needs to be passed in to create a presentation`); } const verifiableCredentials = (Array.isArray(selectedCredentials) ? selectedCredentials : [selectedCredentials]); if (verifiableCredentials.some((c) => CredentialMapper.isSdJwtDecodedCredential(c) || CredentialMapper.isSdJwtEncoded(c))) { if (!this.options?.hasher) { throw new Error('Hasher must be provided when creating a presentation with an SD-JWT VC'); } } const wVCs = verifiableCredentials.map((vc) => CredentialMapper.toWrappedVerifiableCredential(vc, { hasher: this.options?.hasher })); const holders = Array.from(new Set(wVCs.flatMap((wvc) => getSubjectIdsAsString(wvc.credential)))); if (holders.length !== 1 && !opts?.holderDID) { console.log(`We deduced ${holders.length} subject from ${wVCs.length} Verifiable Credentials, and no holder property was given. This might lead to undesired results`); } const holder = opts?.holderDID ?? (holders.length === 1 ? holders[0] : undefined); const type = opts?.basePresentationPayload?.type ? Array.isArray(opts.basePresentationPayload.type) ? opts.basePresentationPayload.type : [opts.basePresentationPayload.type] : []; if (!type.includes('VerifiablePresentation')) { type.push('VerifiablePresentation'); } const context = opts?.basePresentationPayload?.['@context'] ? Array.isArray(opts.basePresentationPayload['@context']) ? opts.basePresentationPayload['@context'] : [opts.basePresentationPayload['@context']] : []; if (!context.includes('https://www.w3.org/2018/credentials/v1')) { context.push('https://www.w3.org/2018/credentials/v1'); } if (opts?.presentationSubmission) { if (!type.includes('PresentationSubmission')) { type.push('PresentationSubmission'); } if (!context.includes('https://identity.foundation/presentation-exchange/submission/v1')) { context.push('https://identity.foundation/presentation-exchange/submission/v1'); } } const result = []; if (PEX.allowMultipleVCsPerPresentation(verifiableCredentials)) { result.push({ ...opts?.basePresentationPayload, '@context': context, type, holder, ...(!!opts?.presentationSubmission && { presentation_submission: opts.presentationSubmission }), verifiableCredential: verifiableCredentials, }); } else { verifiableCredentials.forEach((vc) => { if (CredentialMapper.isSdJwtDecodedCredential(vc)) { result.push(vc); } else if (CredentialMapper.isSdJwtEncoded(vc)) { const decoded = CredentialMapper.decodeVerifiableCredential(vc, opts?.hasher); result.push(decoded); } else { // This should be jwt or json-ld result.push({ ...opts?.basePresentationPayload, '@context': context, type, holder, ...(!!opts?.presentationSubmission && { presentation_submission: opts.presentationSubmission }), verifiableCredential: [vc], }); } }); } return result; } /* TODO SDK-37 refinement needed */ static allowMultipleVCsPerPresentation(verifiableCredentials) { const jwtCredentials = verifiableCredentials.filter((c) => CredentialMapper.isJwtEncoded(c) || CredentialMapper.isJwtDecodedCredential(c)); if (jwtCredentials.length > 0) { const subjects = new Set(); // const verificationMethods = new Set<string>(); for (const credential of jwtCredentials) { const decodedCredential = CredentialMapper.isJwtEncoded(credential) ? CredentialMapper.decodeVerifiableCredential(credential) : credential; const subject = decodedCredential.sub || (decodedCredential.vc && decodedCredential.sub); if (subject) { subjects.add(subject); } /* const vcProof = decodedCredential.proof ?? decodedCredential.vc.proof; const proofs = Array.isArray(vcProof) ? vcProof : [vcProof]; proofs.filter((proof: IProof) => proof.verificationMethod).forEach((proof: IProof) => verificationMethods.add(proof.verificationMethod)); */ } // If there's more than one unique subject or verification method, we can't allow multiple VCs in a single presentation if (subjects.size > 1) { return false; } } if (verifiableCredentials.some((c) => CredentialMapper.isSdJwtEncoded(c) || CredentialMapper.isSdJwtDecodedCredential(c))) { return false; } return true; } /** * This method validates whether an object is usable as a presentation definition or not. * * @param presentationDefinition presentationDefinition of V1 or v2 to be validated. * * @return the validation results to reveal what is acceptable/unacceptable about the passed object to be considered a valid presentation definition */ static validateDefinition(presentationDefinition) { const result = definitionVersionDiscovery(presentationDefinition); if (result.error) { const errorParts = [result.error]; const v1ErrorString = formatValidationErrors(result.v1Errors); if (v1ErrorString) { errorParts.push('\nVersion 1 validation errors:\n ' + v1ErrorString); } const v2ErrorString = formatValidationErrors(result.v2Errors); if (v2ErrorString) { errorParts.push('\nVersion 2 validation errors:\n ' + v2ErrorString); } throw new Error(errorParts.join('')); } const validators = []; result.version === PEVersion.v1 ? validators.push({ bundler: new PresentationDefinitionV1VB('root'), target: SSITypesBuilder.modelEntityToInternalPresentationDefinitionV1(presentationDefinition), }) : validators.push({ bundler: new PresentationDefinitionV2VB('root'), target: SSITypesBuilder.modelEntityInternalPresentationDefinitionV2(presentationDefinition), }); return new ValidationEngine().validate(validators); } /** * This method validates whether an object is usable as a presentation submission or not. * * @param presentationSubmission the object to be validated. * * @return the validation results to reveal what is acceptable/unacceptable about the passed object to be considered a valid presentation submission */ static validateSubmission(presentationSubmission) { return new ValidationEngine().validate([ { bundler: new PresentationSubmissionVB('root'), target: presentationSubmission, }, ]); } /** * This method can be used to combine a definition, selected Verifiable Credentials, together with * signing opts and a callback to sign a presentation, making it a Verifiable Presentation before sending. * * Please note that PEX has no signature support on purpose. We didn't want this library to depend on all kinds of signature suites. * The callback function next to the Signing Params also gets a Presentation which is evaluated against the definition. * It is up to you to decide whether you simply update the supplied partial proof and add it to the presentation in the callback, * or whether you will use the selected Credentials, Presentation definition, evaluation results and/or presentation submission together with the signature opts * * @param presentationDefinition the Presentation Definition V1 or V2 * @param selectedCredentials the PEX and/or User selected/filtered credentials that will become part of the Verifiable Presentation * @param signingCallBack the function which will be provided as a parameter. And this will be the method that will be able to perform actual * signing. One example of signing is available in the project named. pe-selective-disclosure. * @param opts Signing Params these are the signing params required to sign. * * @return the signed and thus Verifiable Presentation. */ async verifiablePresentationFrom(presentationDefinition, selectedCredentials, signingCallBack, opts) { const { holderDID, signatureOptions, proofOptions } = opts; function limitedDisclosureSuites() { let limitDisclosureSignatureSuites = []; if (proofOptions?.typeSupportsSelectiveDisclosure) { if (!proofOptions?.type) { throw Error('Please provide a proof type if you enable selective disclosure'); } limitDisclosureSignatureSuites = [proofOptions.type]; } return limitDisclosureSignatureSuites; } const holderDIDs = holderDID ? [holderDID] : []; const limitDisclosureSignatureSuites = limitedDisclosureSuites(); const evaluationResult = this.evaluateCredentials(presentationDefinition, selectedCredentials, { holderDIDs, limitDisclosureSignatureSuites, }); const presentationResult = this.presentationFrom(presentationDefinition, evaluationResult.verifiableCredential, opts); const presentations = presentationResult.presentations; const evaluationResults = this.evaluatePresentation(presentationDefinition, presentations, { limitDisclosureSignatureSuites, ...(presentationResult.presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL && { presentationSubmission: presentationResult.presentationSubmission, }), }); if (!evaluationResults.value && selectedCredentials.length === 0) { evaluationResults.value = presentationResult.presentationSubmission; } if (!evaluationResults.value) { throw new Error('Could not get evaluation results from presentationResult'); } const proof = { type: proofOptions?.type, verificationMethod: signatureOptions?.verificationMethod, created: proofOptions?.created ? proofOptions.created : new Date().toISOString(), proofPurpose: proofOptions?.proofPurpose, proofValue: signatureOptions?.proofValue, jws: signatureOptions?.jws, challenge: proofOptions?.challenge, nonce: proofOptions?.nonce, domain: proofOptions?.domain, }; this.updateSdJwtCredentials(presentations, proofOptions?.nonce); const verifiablePresentations = []; for (const presentation of presentations) { const callBackParams = { options: { ...opts, presentationSubmissionLocation: presentationResult.presentationSubmissionLocation, }, presentation, presentationDefinition, selectedCredentials, proof, presentationSubmission: evaluationResults.value, evaluationResults, }; verifiablePresentations.push(await signingCallBack(callBackParams)); } return { verifiablePresentations, presentationSubmissionLocation: presentationResult.presentationSubmissionLocation, presentationSubmission: evaluationResults.value, }; } updateSdJwtCredentials(presentations, nonce) { presentations.forEach((presentation, index) => { // Select type without kbJwt as isSdJwtDecodedCredential and won't accept the partial sdvc type if (CredentialMapper.isSdJwtDecodedCredential(presentation)) { const sdJwtCredential = presentation; if (!this.options?.hasher) { throw new Error('Hasher must be provided when creating a presentation with an SD-JWT VC'); } // extract sd_alg or default to sha-256 const hashAlg = sdJwtCredential.signedPayload._sd_alg ?? 'sha-256'; const sdHash = calculateSdHash(sdJwtCredential.compactSdJwtVc, hashAlg, this.options.hasher); const kbJwt = { // alg MUST be set by the signer header: { typ: 'kb+jwt', }, // aud MUST be set by the signer or provided by e.g. SIOP/OpenID4VP lib payload: { iat: Math.floor(new Date().getTime() / 1000), nonce: nonce, sd_hash: sdHash, }, }; presentations[index] = { ...sdJwtCredential, kbJwt, }; } }); } static definitionVersionDiscovery(presentationDefinition) { return definitionVersionDiscovery(presentationDefinition); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUEVYLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vbGliL1BFWC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBRUwsZ0JBQWdCLEdBY2pCLE1BQU0scUJBQXFCLENBQUM7QUFFN0IsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQzNDLE9BQU8sRUFBRSx1QkFBdUIsRUFBbUUsTUFBTSxjQUFjLENBQUM7QUFDeEgsT0FBTyxFQU1MLDhCQUE4QixHQUcvQixNQUFNLFdBQVcsQ0FBQztBQUNuQixPQUFPLEVBQXdGLFNBQVMsRUFBRSxlQUFlLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDM0ksT0FBTyxFQUFFLGVBQWUsRUFBRSwwQkFBMEIsRUFBRSxzQkFBc0IsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNySCxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsMEJBQTBCLEVBQUUsd0JBQXdCLEVBQWEsZ0JBQWdCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFjN0k7O0dBRUc7QUFDSCxNQUFNLE9BQU8sR0FBRztJQUNKLHdCQUF3QixDQUEwQjtJQUNsRCxPQUFPLENBQWM7SUFFL0IsWUFBWSxPQUFvQjtRQUM5Qix3S0FBd0s7UUFDeEssSUFBSSxDQUFDLHdCQUF3QixHQUFHLElBQUksdUJBQXVCLEVBQUUsQ0FBQztRQUU5RCxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUN6QixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNJLG9CQUFvQixDQUN6QixzQkFBK0MsRUFDL0MsYUFBZ0gsRUFDaEgsSUFZQztRQUVELDJIQUEySDtRQUMzSCxvSEFBb0g7UUFDcEgsTUFBTSxrQkFBa0IsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDMUYsSUFBSSxrQkFBa0IsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDcEMsTUFBTSxJQUFJLEtBQUssQ0FBQyw0Q0FBNEMsQ0FBQyxDQUFDO1FBQ2hFLENBQUM7UUFFRCxNQUFNLDhCQUE4QixHQUNsQyxJQUFJLEVBQUUsOEJBQThCLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsOEJBQThCLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxzQkFBc0IsS0FBSyxTQUFTLENBQUM7UUFDeEksTUFBTSxFQUFFLEdBQW9DLGVBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3JILE1BQU0saUJBQWlCLEdBQXFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUM7UUFFM0csTUFBTSxvQkFBb0IsR0FBb0MsaUJBQWlCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FDeEYsZUFBZSxDQUFDLDRDQUE0QyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUN0RixDQUFDO1FBRUYsSUFBSSxzQkFBc0IsR0FBRyxJQUFJLEVBQUUsc0JBQXNCLENBQUM7UUFDMUQsSUFBSSw4QkFBOEIsR0FDaEMsSUFBSSxFQUFFLDhCQUE4QjtZQUNwQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsSUFBSSxhQUFhLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsaUJBQWlCLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDO2dCQUN0SSxDQUFDLENBQUMsOEJBQThCLENBQUMsUUFBUTtnQkFDekMsQ0FBQyxDQUFDLDhCQUE4QixDQUFDLFlBQVksQ0FBQyxDQUFDO1FBRW5ELHVFQUF1RTtRQUN2RSxJQUNFLENBQUMsc0JBQXNCO1lBQ3ZCLGtCQUFrQixDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQy9CLGdCQUFnQixDQUFDLGlCQUFpQixDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQztZQUN4RSxDQUFDLDhCQUE4QixFQUMvQixDQUFDO1lBQ0QsTUFBTSxPQUFPLEdBQUcsb0JBQW9CLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDO1lBQ2hELElBQUkseUJBQXlCLElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQ3pDLHNCQUFzQixHQUFHLE9BQU8sQ0FBQyx1QkFBdUIsQ0FBQztZQUMzRCxDQUFDO1lBQ0QsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7Z0JBQzVCLE1BQU0sS0FBSyxDQUFDLHdGQUF3RixDQUFDLENBQUM7WUFDeEcsQ0FBQztZQUNELDhCQUE4QixHQUFHLDhCQUE4QixDQUFDLFlBQVksQ0FBQztZQUM3RSxJQUFJLElBQUksRUFBRSw4QkFBOEIsSUFBSSxJQUFJLENBQUMsOEJBQThCLEtBQUssOEJBQThCLENBQUMsWUFBWSxFQUFFLENBQUM7Z0JBQ2hJLE1BQU0sSUFBSSxLQUFLLENBQ2IsNkNBQTZDLElBQUksQ0FBQyw4QkFBOEIsMkJBQTJCLDhCQUE4QixDQUFDLFlBQVkseUpBQXlKLENBQ2hULENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQzthQUFNLElBQUksQ0FBQyxzQkFBc0IsSUFBSSxDQUFDLDhCQUE4QixFQUFFLENBQUM7WUFDdEUsTUFBTSxJQUFJLEtBQUssQ0FBQyxrREFBa0QsQ0FBQyxDQUFDO1FBQ3RFLENBQUM7UUFFRCxnSEFBZ0g7UUFDaEgscUdBQXFHO1FBQ3JHLE1BQU0sVUFBVSxHQUFHLG9CQUFvQjthQUNwQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtZQUNULE9BQU8sZ0JBQWdCLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO1FBQ3pILENBQUMsQ0FBQzthQUNELE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBZSxFQUFFLENBQUMsQ0FBQyxLQUFLLFNBQVMsQ0FBQyxDQUFDO1FBRS9DLE1BQU0sV0FBVyxHQUFHO1lBQ2xCLEdBQUcsSUFBSTtZQUNQLFVBQVU7WUFDVixzQkFBc0I7WUFDdEIsOEJBQThCO1lBQzlCLDhCQUE4QjtTQUMvQixDQUFDO1FBRUYsTUFBTSxPQUFPLEdBQUcsb0JBQW9CLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQyxHQUFHLEdBQUcsRUFBRSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRSxFQUFtQyxDQUFDLENBQUM7UUFDckgsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLHdCQUF3QixDQUFDLHFCQUFxQixDQUNoRSxFQUFFLEVBQ0YsS0FBSyxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQyxFQUM3RSxXQUFXLENBQ1osQ0FBQztRQUVGLElBQUksTUFBTSxDQUFDLDZCQUE2QixLQUFLLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUMxRCxNQUFNLHVCQUF1QixHQUFHLElBQUksdUJBQXVCLEVBQUUsQ0FBQztZQUM5RCxNQUFNLGFBQWEsR0FBa0IsdUJBQXVCLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxPQUFPLEVBQUUsV0FBVyxDQUFDLENBQUM7WUFDbEcsSUFBSSxhQUFhLENBQUMsNkJBQTZCLEtBQUssTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNqRSxNQUFNLENBQUMsTUFBTSxHQUFHLEVBQUUsQ0FBQztZQUNyQixDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0ksbUJBQW1CLENBQ3hCLHNCQUErQyxFQUMvQyxxQkFBcUQsRUFDckQsSUFLQztRQUVELE1BQU0sNEJBQTRCLEdBQWtDLGVBQWUsQ0FBQyw0Q0FBNEMsQ0FDOUgscUJBQXFCLEVBQ3JCLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUNyQixDQUFDO1FBRUYsbUtBQW1LO1FBQ25LLElBQUksQ0FBQyx3QkFBd0IsR0FBRyxJQUFJLHVCQUF1QixFQUFFLENBQUM7UUFDOUQsTUFBTSxFQUFFLEdBQW9DLGVBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3JILE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLDRCQUE0QixFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzlGLElBQUksTUFBTSxDQUFDLEtBQUssSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLGNBQWMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUN2RCxNQUFNLHVCQUF1QixHQUFHLElBQUksdUJBQXVCLEVBQUUsQ0FBQztZQUM5RCxNQUFNLGFBQWEsR0FBa0IsdUJBQXVCLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSw0QkFBNEIsRUFBRSxJQUFJLENBQUMsQ0FBQztZQUNoSCxNQUFNLENBQUMsNkJBQTZCLEdBQUcsYUFBYSxDQUFDLDZCQUE2QixDQUFDO1lBQ25GLE1BQU0sQ0FBQyxNQUFNLEdBQUcsYUFBYSxDQUFDLE1BQU0sQ0FBQztRQUN2QyxDQUFDO2FBQU0sQ0FBQztZQUNOLE1BQU0sQ0FBQyw2QkFBNkIsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDO1FBQ3RELENBQUM7UUFDRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNJLFVBQVUsQ0FDZixzQkFBK0MsRUFDL0MscUJBQXFELEVBQ3JELElBS0M7UUFFRCxNQUFNLHdCQUF3QixHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDLENBQUM7UUFDbkYsTUFBTSxFQUFFLEdBQW9DLGVBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3JILG1LQUFtSztRQUNuSyxJQUFJLENBQUMsd0JBQXdCLEdBQUcsSUFBSSx1QkFBdUIsRUFBRSxDQUFDO1FBQzlELE9BQU8sSUFBSSxDQUFDLHdCQUF3QixDQUFDLFVBQVUsQ0FDN0MsRUFBRSxFQUNGLGVBQWUsQ0FBQyw0Q0FBNEMsQ0FBQyx3QkFBd0IsRUFBRSxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxFQUM1RyxJQUFJLENBQ0wsQ0FBQztJQUNKLENBQUM7SUFFTSwwQkFBMEIsQ0FDL0Isc0JBQStDLEVBQy9DLG1CQUFtRCxFQUNuRCxJQVFDO1FBRUQsTUFBTSxFQUFFLEdBQW9DLGVBQWUsQ0FBQyxnQ0FBZ0MsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3JILE9BQU8sSUFBSSxDQUFDLHdCQUF3QixDQUFDLGNBQWMsQ0FDakQsRUFBRSxFQUNGLGVBQWUsQ0FBQyw0Q0FBNEMsQ0FBQyxtQkFBbUIsRUFBRSxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxFQUN2RyxJQUFJLENBQ0wsQ0FBQztJQUNKLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0ksZ0JBQWdCLENBQ3JCLHNCQUErQyxFQUMvQyxtQkFBbUQsRUFDbkQsSUFBMkI7UUFFM0IsTUFBTSxzQkFBc0IsR0FBRyxJQUFJLENBQUMsMEJBQTBCLENBQUMsc0JBQXNCLEVBQUUsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDbEgsTUFBTSxtQkFBbUIsR0FBRyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLGdCQUFnQixDQUFDLHdCQUF3QixDQUFDLENBQUMsQ0FBQyxJQUFJLGdCQUFnQixDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRWhKLDhEQUE4RDtRQUM5RCxJQUFJLElBQUksRUFBRSw4QkFBOEIsS0FBSyw4QkFBOEIsQ0FBQyxZQUFZLElBQUksbUJBQW1CLEVBQUUsQ0FBQztZQUNoSCxNQUFNLElBQUksS0FBSyxDQUFDLCtHQUErRyxDQUFDLENBQUM7UUFDbkksQ0FBQztRQUVELE1BQU0sOEJBQThCLEdBQ2xDLElBQUksRUFBRSw4QkFBOEI7WUFDcEMsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUMsOEJBQThCLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyw4QkFBOEIsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUVoSCxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsc0JBQXNCLENBQUMsbUJBQW1CLEVBQUU7WUFDckUsR0FBRyxJQUFJO1lBQ1AscUZBQXFGO1lBQ3JGLHNCQUFzQixFQUFFLDhCQUE4QixLQUFLLDhCQUE4QixDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsc0JBQXNCLENBQUMsQ0FBQyxDQUFDLFNBQVM7WUFDM0ksTUFBTSxFQUFFLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTTtTQUM3QixDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsc0JBQXNCLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDM0MsT0FBTztZQUNMLGFBQWE7WUFDYiw4QkFBOEI7WUFDOUIsc0JBQXNCO1NBQ3ZCLENBQUM7SUFDSixDQUFDO0lBRU0sc0JBQXNCLENBQzNCLG1CQUFrRixFQUNsRixJQVFDO1FBRUQsSUFBSSxDQUFDLG1CQUFtQixFQUFFLENBQUM7WUFDekIsTUFBTSxLQUFLLENBQUMsaUZBQWlGLENBQUMsQ0FBQztRQUNqRyxDQUFDO1FBQ0QsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLENBQUMsbUJBQW1CLENBQUMsQ0FBOEIsQ0FBQztRQUM5SSxJQUFJLHFCQUFxQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsd0JBQXdCLENBQUMsQ0FBQyxDQUFDLElBQUksZ0JBQWdCLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMxSCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztnQkFDMUIsTUFBTSxJQUFJLEtBQUssQ0FBQyx3RUFBd0UsQ0FBQyxDQUFDO1lBQzVGLENBQUM7UUFDSCxDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcscUJBQXFCLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyw2QkFBNkIsQ0FBQyxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDckksTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxxQkFBcUIsQ0FBQyxHQUFHLENBQUMsVUFBeUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2pILElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7WUFDN0MsT0FBTyxDQUFDLEdBQUcsQ0FDVCxjQUFjLE9BQU8sQ0FBQyxNQUFNLGlCQUFpQixJQUFJLENBQUMsTUFBTSxpR0FBaUcsQ0FDMUosQ0FBQztRQUNKLENBQUM7UUFDRCxNQUFNLE1BQU0sR0FBRyxJQUFJLEVBQUUsU0FBUyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUM7UUFFbEYsTUFBTSxJQUFJLEdBQUcsSUFBSSxFQUFFLHVCQUF1QixFQUFFLElBQUk7WUFDOUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQztnQkFDaEQsQ0FBQyxDQUFDLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxJQUFJO2dCQUNuQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsdUJBQXVCLENBQUMsSUFBSSxDQUFDO1lBQ3ZDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDUCxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyx3QkFBd0IsQ0FBQyxFQUFFLENBQUM7WUFDN0MsSUFBSSxDQUFDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO1FBQ3RDLENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFJLEVBQUUsdUJBQXVCLEVBQUUsQ0FBQyxVQUFVLENBQUM7WUFDekQsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFVBQVUsQ0FBQyxDQUFDO2dCQUN2RCxDQUFDLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFVBQVUsQ0FBQztnQkFDMUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQzlDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDUCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyx3Q0FBd0MsQ0FBQyxFQUFFLENBQUM7WUFDaEUsT0FBTyxDQUFDLElBQUksQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDO1FBQ3pELENBQUM7UUFFRCxJQUFJLElBQUksRUFBRSxzQkFBc0IsRUFBRSxDQUFDO1lBQ2pDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLHdCQUF3QixDQUFDLEVBQUUsQ0FBQztnQkFDN0MsSUFBSSxDQUFDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO1lBQ3RDLENBQUM7WUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxpRUFBaUUsQ0FBQyxFQUFFLENBQUM7Z0JBQ3pGLE9BQU8sQ0FBQyxJQUFJLENBQUMsaUVBQWlFLENBQUMsQ0FBQztZQUNsRixDQUFDO1FBQ0gsQ0FBQztRQUNELE1BQU0sTUFBTSxHQUFtRSxFQUFFLENBQUM7UUFDbEYsSUFBSSxHQUFHLENBQUMsK0JBQStCLENBQUMscUJBQXFCLENBQUMsRUFBRSxDQUFDO1lBQy9ELE1BQU0sQ0FBQyxJQUFJLENBQUM7Z0JBQ1YsR0FBRyxJQUFJLEVBQUUsdUJBQXVCO2dCQUNoQyxVQUFVLEVBQUUsT0FBTztnQkFDbkIsSUFBSTtnQkFDSixNQUFNO2dCQUNOLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLHNCQUFzQixJQUFJLEVBQUUsdUJBQXVCLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7Z0JBQy9GLG9CQUFvQixFQUFFLHFCQUFxQjthQUM1QyxDQUFDLENBQUM7UUFDTCxDQUFDO2FBQU0sQ0FBQztZQUNOLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFO2dCQUNuQyxJQUFJLGdCQUFnQixDQUFDLHdCQUF3QixDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUM7b0JBQ2xELE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBNkMsQ0FBQyxDQUFDO2dCQUM3RCxDQUFDO3FCQUFNLElBQUksZ0JBQWdCLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUM7b0JBQy9DLE1BQU0sT0FBTyxHQUFHLGdCQUFnQixDQUFDLDBCQUEwQixDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7b0JBQzlFLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBa0QsQ0FBQyxDQUFDO2dCQUNsRSxDQUFDO3FCQUFNLENBQUM7b0JBQ04sZ0NBQWdDO29CQUNoQyxNQUFNLENBQUMsSUFBSSxDQUFDO3dCQUNWLEdBQUcsSUFBSSxFQUFFLHVCQUF1Qjt3QkFDaEMsVUFBVSxFQUFFLE9BQU87d0JBQ25CLElBQUk7d0JBQ0osTUFBTTt3QkFDTixHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxzQkFBc0IsSUFBSSxFQUFFLHVCQUF1QixFQUFFLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO3dCQUMvRixvQkFBb0IsRUFBRSxDQUFDLEVBQUUsQ0FBQztxQkFDM0IsQ0FBQyxDQUFDO2dCQUNMLENBQUM7WUFDSCxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUM7UUFDRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxNQUFNLENBQUMsK0JBQStCLENBQUMscUJBQTBEO1FBQ3RHLE1BQU0sY0FBYyxHQUFHLHFCQUFxQixDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxJQUFJLGdCQUFnQixDQUFDLHNCQUFzQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFM0ksSUFBSSxjQUFjLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQzlCLE1BQU0sUUFBUSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7WUFDbkMsc0RBQXNEO1lBRXRELEtBQUssTUFBTSxVQUFVLElBQUksY0FBYyxFQUFFLENBQUM7Z0JBQ3hDLE1BQU0saUJBQWlCLEdBQUcsZ0JBQWdCLENBQUMsWUFBWSxDQUFDLFVBQVUsQ0FBQztvQkFDakUsQ0FBQyxDQUFFLGdCQUFnQixDQUFDLDBCQUEwQixDQUFDLFVBQVUsQ0FBb0M7b0JBQzdGLENBQUMsQ0FBRSxVQUE2QyxDQUFDO2dCQUVuRCxNQUFNLE9BQU8sR0FBRyxpQkFBaUIsQ0FBQyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxFQUFFLElBQUksaUJBQWlCLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQ3pGLElBQUksT0FBTyxFQUFFLENBQUM7b0JBQ1osUUFBUSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztnQkFDeEIsQ0FBQztnQkFFRDs7OztVQUlOO1lBQ0ksQ0FBQztZQUVELHVIQUF1SDtZQUN2SCxJQUFJLFFBQVEsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3RCLE9BQU8sS0FBSyxDQUFDO1lBQ2YsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLHFCQUFxQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsZ0JBQWdCLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxJQUFJLGdCQUFnQixDQUFDLHdCQUF3QixDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMxSCxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSSxNQUFNLENBQUMsa0JBQWtCLENBQUMsc0JBQStDO1FBQzlFLE1BQU0sTUFBTSxHQUFHLDBCQUEwQixDQUFDLHNCQUFzQixDQUFDLENBQUM7UUFDbEUsSUFBSSxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDakIsTUFBTSxVQUFVLEdBQUcsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7WUFFbEMsTUFBTSxhQUFhLEdBQUcsc0JBQXNCLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQzlELElBQUksYUFBYSxFQUFFLENBQUM7Z0JBQ2xCLFVBQVUsQ0FBQyxJQUFJLENBQUMsb0NBQW9DLEdBQUcsYUFBYSxDQUFDLENBQUM7WUFDeEUsQ0FBQztZQUVELE1BQU0sYUFBYSxHQUFHLHNCQUFzQixDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUM5RCxJQUFJLGFBQWEsRUFBRSxDQUFDO2dCQUNsQixVQUFVLENBQUMsSUFBSSxDQUFDLG9DQUFvQyxHQUFHLGFBQWEsQ0FBQyxDQUFDO1lBQ3hFLENBQUM7WUFFRCxNQUFNLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUN2QyxDQUFDO1FBRUQsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDO1FBQ3RCLE1BQU0sQ0FBQyxPQUFPLEtBQUssU0FBUyxDQUFDLEVBQUU7WUFDN0IsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUM7Z0JBQ2QsT0FBTyxFQUFFLElBQUksMEJBQTBCLENBQUMsTUFBTSxDQUFDO2dCQUMvQyxNQUFNLEVBQUUsZUFBZSxDQUFDLDZDQUE2QyxDQUFDLHNCQUFrRCxDQUFDO2FBQzFILENBQUM7WUFDSixDQUFDLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQztnQkFDZCxPQUFPLEVBQUUsSUFBSSwwQkFBMEIsQ0FBQyxNQUFNLENBQUM7Z0JBQy9DLE1BQU0sRUFBRSxlQUFlLENBQUMsMkNBQTJDLENBQUMsc0JBQWtELENBQUM7YUFDeEgsQ0FBQyxDQUFDO1FBQ1AsT0FBTyxJQUFJLGdCQUFnQixFQUFFLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ3JELENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSSxNQUFNLENBQUMsa0JBQWtCLENBQUMsc0JBQThDO1FBQzdFLE9BQU8sSUFBSSxnQkFBZ0IsRUFBRSxDQUFDLFFBQVEsQ0FBQztZQUNyQztnQkFDRSxPQUFPLEVBQUUsSUFBSSx3QkFBd0IsQ0FBQyxNQUFNLENBQUM7Z0JBQzdDLE1BQU0sRUFBRSxzQkFBc0I7YUFDL0I7U0FDRixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7T0FnQkc7SUFDSSxLQUFLLENBQUMsMEJBQTBCLENBQ3JDLHNCQUErQyxFQUMvQyxtQkFBbUQsRUFDbkQsZUFBMEgsRUFDMUgsSUFBb0M7UUFFcEMsTUFBTSxFQUFFLFNBQVMsRUFBRSxnQkFBZ0IsRUFBRSxZQUFZLEVBQUUsR0FBRyxJQUFJLENBQUM7UUFFM0QsU0FBUyx1QkFBdUI7WUFDOUIsSUFBSSw4QkFBOEIsR0FBYSxFQUFFLENBQUM7WUFDbEQsSUFBSSxZQUFZLEVBQUUsK0JBQStCLEVBQUUsQ0FBQztnQkFDbEQsSUFBSSxDQUFDLFlBQVksRUFBRSxJQUFJLEVBQUUsQ0FBQztvQkFDeEIsTUFBTSxLQUFLLENBQUMsZ0VBQWdFLENBQUMsQ0FBQztnQkFDaEYsQ0FBQztnQkFDRCw4QkFBOEIsR0FBRyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN2RCxDQUFDO1lBQ0QsT0FBTyw4QkFBOEIsQ0FBQztRQUN4QyxDQUFDO1FBRUQsTUFBTSxVQUFVLEdBQWEsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDMUQsTUFBTSw4QkFBOEIsR0FBRyx1QkFBdUIsRUFBRSxDQUFDO1FBQ2pFLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLG1CQUFtQixDQUFDLHNCQUFzQixFQUFFLG1CQUFtQixFQUFFO1lBQzdGLFVBQVU7WUFDViw4QkFBOEI7U0FDL0IsQ0FBQyxDQUFDO1FBRUgsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsc0JBQXNCLEVBQUUsZ0JBQWdCLENBQUMsb0JBQW9CLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDdEgsTUFBTSxhQUFhLEdBQUcsa0JBQWtCLENBQUMsYUFBYSxDQUFDO1FBQ3ZELE1BQU0saUJBQWlCLEdBQUcsSUFBSSxDQUFDLG9CQUFvQixDQUFDLHNCQUFzQixFQUFFLGFBQWEsRUFBRTtZQUN6Riw4QkFBOEI7WUFDOUIsR0FBRyxDQUFDLGtCQUFrQixDQUFDLDhCQUE4QixLQUFLLDhCQUE4QixDQUFDLFFBQVEsSUFBSTtnQkFDbkcsc0JBQXNCLEVBQUUsa0JBQWtCLENBQUMsc0JBQXNCO2FBQ2xFLENBQUM7U0FDSCxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsaUJBQWlCLENBQUMsS0FBSyxJQUFJLG1CQUFtQixDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNqRSxpQkFBaUIsQ0FBQyxLQUFLLEdBQUcsa0JBQWtCLENBQUMsc0JBQXNCLENBQUM7UUFDdEUsQ0FBQztRQUNELElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUM3QixNQUFNLElBQUksS0FBSyxDQUFDLDBEQUEwRCxDQUFDLENBQUM7UUFDOUUsQ0FBQztRQUVELE1BQU0sS0FBSyxHQUFvQjtZQUM3QixJQUFJLEVBQUUsWUFBWSxFQUFFLElBQUk7WUFDeEIsa0JBQWtCLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCO1lBQ3hELE9BQU8sRUFBRSxZQUFZLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRTtZQUNoRixZQUFZLEVBQUUsWUFBWSxFQUFFLFlBQVk7WUFDeEMsVUFBVSxFQUFFLGdCQUFnQixFQUFFLFVBQVU7WUFDeEMsR0FBRyxFQUFFLGdCQUFnQixFQUFFLEdBQUc7WUFDMUIsU0FBUyxFQUFFLFlBQVksRUFBRSxTQUFTO1lBQ2xDLEtBQUssRUFBRSxZQUFZLEVBQUUsS0FBSztZQUMxQixNQUFNLEVBQUUsWUFBWSxFQUFFLE1BQU07U0FDN0IsQ0FBQztRQUVGLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxhQUFhLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBRWhFLE1BQU0sdUJBQXVCLEdBQXNELEVBQUUsQ0FBQztRQUN0RixLQUFLLE1BQU0sWUFBWSxJQUFJLGFBQWEsRUFBRSxDQUFDO1lBQ3pDLE1BQU0sY0FBYyxHQUFtQztnQkFDckQsT0FBTyxFQUFFO29CQUNQLEdBQUcsSUFBSTtvQkFDUCw4QkFBOEIsRUFBRSxrQkFBa0IsQ0FBQyw4QkFBOEI7aUJBQ2xGO2dCQUNELFlBQVk7Z0JBQ1osc0JBQXNCO2dCQUN0QixtQkFBbUI7Z0JBQ25CLEtBQUs7Z0JBQ0wsc0JBQXNCLEVBQUUsaUJBQWlCLENBQUMsS0FBSztnQkFDL0MsaUJBQWlCO2FBQ2xCLENBQUM7WUFDRix1QkFBdUIsQ0FBQyxJQUFJLENBQUMsTUFBTSxlQUFlLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQztRQUN0RSxDQUFDO1FBQ0QsT0FBTztZQUNMLHVCQUF1QjtZQUN2Qiw4QkFBOEIsRUFBRSxrQkFBa0IsQ0FBQyw4QkFBOEI7WUFDakYsc0JBQXNCLEVBQUUsaUJBQWlCLENBQUMsS0FBSztTQUNoRCxDQUFDO0lBQ0osQ0FBQztJQUVPLHNCQUFzQixDQUM1QixhQUFnSCxFQUNoSCxLQUFjO1FBRWQsYUFBYSxDQUFDLE9BQU8sQ0FBQyxDQUFDLFlBQVksRUFBRSxLQUFLLEVBQUUsRUFBRTtZQUM1QywrRkFBK0Y7WUFDL0YsSUFBSSxnQkFBZ0IsQ0FBQyx3QkFBd0IsQ0FBQyxZQUFnRCxDQUFDLEVBQUUsQ0FBQztnQkFDaEcsTUFBTSxlQUFlLEdBQUcsWUFBZ0QsQ0FBQztnQkFDekUsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLENBQUM7b0JBQzFCLE1BQU0sSUFBSSxLQUFLLENBQUMsd0VBQXdFLENBQUMsQ0FBQztnQkFDNUYsQ0FBQztnQkFFRCx1Q0FBdUM7Z0JBQ3ZDLE1BQU0sT0FBTyxHQUFHLGVBQWUsQ0FBQyxhQUFhLENBQUMsT0FBTyxJQUFJLFNBQVMsQ0FBQztnQkFDbkUsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLGVBQWUsQ0FBQyxjQUFjLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7Z0JBRTdGLE1BQU0sS0FBSyxHQUFHO29CQUNaLGdDQUFnQztvQkFDaEMsTUFBTSxFQUFFO3dCQUNOLEdBQUcsRUFBRSxRQUFRO3FCQUNkO29CQUNELHVFQUF1RTtvQkFDdkUsT0FBTyxFQUFFO3dCQUNQLEdBQUcsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksSUFBSSxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsSUFBSSxDQUFDO3dCQUM1QyxLQUFLLEVBQUUsS0FBSzt3QkFDWixPQUFPLEVBQUUsTUFBTTtxQkFDaEI7aUJBQzBCLENBQUM7Z0JBRTlCLGFBQWEsQ0FBQyxLQUFLLENBQUMsR0FBRztvQkFDckIsR0FBRyxlQUFlO29CQUNsQixLQUFLO2lCQUM0QyxDQUFDO1lBQ3RELENBQUM7UUFDSCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTSxNQUFNLENBQUMsMEJBQTBCLENBQUMsc0JBQStDO1FBQ3RGLE9BQU8sMEJBQTBCLENBQUMsc0JBQXNCLENBQUMsQ0FBQztJQUM1RCxDQUFDO0NBQ0YifQ==