UNPKG

@openfisca/json-model

Version:

Library to handle informations extracted in JSON or YAML format from OpenFisca parameters, variables, etc

548 lines (539 loc) 105 kB
import { auditArray, auditBoolean, auditChain, auditCleanArray, auditFunction, auditHttpUrl, auditKeyValueDictionary, auditNonEmpty, auditNoop, auditNullish, auditNumber, auditOptions, auditRequire, auditSetNullish, auditString, auditSwitch, auditTest, auditTrimString, auditTuple, auditUnique } from "@auditors/core"; import { // BracketBase, // MaybeNumberValue, mergeReferences, ParameterClass, ScaleType, ValueType } from "../../parameters.js"; import { dateRegExp } from "../../periods.js"; import { auditDate } from "../periods.js"; export function auditRawBracketToEditablePhase1(audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); audit.attribute(data, "amount", true, errors, remainingKeys, auditKeyValueDictionary(auditDate, [auditRawValueOrExpectedToEditable(auditNumber), auditRequire])); audit.attribute(data, "average_rate", true, errors, remainingKeys, auditKeyValueDictionary(auditDate, [auditRawValueOrExpectedToEditable(auditNumber), auditRequire])); audit.attribute(data, "base", true, errors, remainingKeys, auditKeyValueDictionary(auditDate, [auditRawValueOrExpectedToEditable(auditNumber), auditRequire])); audit.attribute(data, "rate", true, errors, remainingKeys, auditKeyValueDictionary(auditDate, [auditRawValueOrExpectedToEditable(auditNumber), auditRequire])); audit.attribute(data, "threshold", true, errors, remainingKeys, auditKeyValueDictionary(auditDate, [auditRawValueOrExpectedToEditable(auditNumber), auditRequire]), auditRequire); return audit.reduceRemaining(data, errors, remainingKeys); } export function auditRawBracketToEditablePhase2Amount(audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); audit.attribute(data, "amount", true, errors, remainingKeys, auditRequire); audit.attribute(data, "average_rate", true, errors, remainingKeys, auditNullish); audit.attribute(data, "base", true, errors, remainingKeys, auditNullish); audit.attribute(data, "rate", true, errors, remainingKeys, auditNullish); audit.attribute(data, "threshold", true, errors, remainingKeys, auditRequire); return audit.reduceRemaining(data, errors, remainingKeys); } export function auditRawBracketPhase2AverageRate(audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); audit.attribute(data, "amount", true, errors, remainingKeys, auditNullish); audit.attribute(data, "average_rate", true, errors, remainingKeys, auditRequire); audit.attribute(data, "base", true, errors, remainingKeys); audit.attribute(data, "rate", true, errors, remainingKeys, auditNullish); audit.attribute(data, "threshold", true, errors, remainingKeys, auditRequire); if (errors.average_rate === undefined && errors.rate === undefined) { data.rate = data.average_rate; delete data.average_rate; } return audit.reduceRemaining(data, errors, remainingKeys); } export function auditRawBracketToEditablePhase2MarginalRate(audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); audit.attribute(data, "amount", true, errors, remainingKeys, auditNullish); audit.attribute(data, "base", true, errors, remainingKeys); audit.attribute(data, "average_rate", true, errors, remainingKeys, auditNullish); audit.attribute(data, "rate", true, errors, remainingKeys, auditRequire); audit.attribute(data, "threshold", true, errors, remainingKeys, auditRequire); return audit.reduceRemaining(data, errors, remainingKeys); } function auditRawMetadataBaseAttributesToEditable(audit, data, errors, remainingKeys) { for (const key of ["description", "label_en", "documentation", "short_label", "short_label_en"]) { audit.attribute(data, key, true, errors, remainingKeys, auditTrimString); } audit.attribute(data, "documentation_start", true, errors, remainingKeys, auditBoolean, auditFunction(value => value ? true : null)); audit.attribute(data, "last_value_still_valid_on", true, errors, remainingKeys, auditDate); audit.attribute(data, "notes", true, errors, remainingKeys, auditRawNotesToEditable); audit.attribute(data, "official_journal_date", true, errors, remainingKeys, auditKeyValueDictionary(auditDate, [auditSwitch(auditDate, [auditString, auditFunction(text => text.split(";")), auditArray(auditDate), // TODO: Return something other than a string? auditFunction(instants => instants.join("; "))]), auditRequire])); audit.attribute(data, "reference", true, errors, remainingKeys, auditRawReferencesToEditable); audit.attribute(data, "inflator", true, errors, remainingKeys, auditTrimString); audit.attribute(data, "inflator_reference", true, errors, remainingKeys, auditRawReferencesToEditable); } export function auditRawNodeParameterMetadataToEditable(parameterData, units, childrenId) { const children = parameterData.children; const testOrderItems = childrenId !== undefined || children != null && typeof children === "object"; if (childrenId === undefined) { childrenId = testOrderItems ? Object.keys(children) : []; } return function (audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); auditRawMetadataBaseAttributesToEditable(audit, data, errors, remainingKeys); // Used for compatibility with "barèmes IPP". audit.attribute(data, "order", true, errors, remainingKeys, auditCleanArray(auditString, testOrderItems ? auditOptions(childrenId) : auditNoop), auditUnique); audit.attribute(data, "unit", true, errors, remainingKeys, auditRawUnitNameToEditable(units)); return audit.reduceRemaining(data, errors, remainingKeys); }; } export function auditRawParameterToEditable(units, childrenId) { return function (audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); // Repair data before validation. // Move some attributes from data to metadata. // See function `_set_backward_compatibility_metadata` of // https://github.com/openfisca/openfisca-core/blob/master/openfisca_core/parameters/helpers.py for (const key of ["reference", "unit"]) { if (data[key] !== undefined) { let metadata = data.metadata; if (metadata === undefined) { metadata = data.metadata = {}; } metadata[key] = data[key]; delete data[key]; remainingKeys.delete(key); } } for (const key of ["description", "documentation", "file_path"]) { audit.attribute(data, key, true, errors, remainingKeys, auditTrimString); } audit.attribute(data, "documentation_start", true, errors, remainingKeys, auditBoolean, auditFunction(value => value ? true : null)); audit.attribute(data, "name", true, errors, remainingKeys, auditTrimString // auditTest((name) => { // const ids = name.split(".") // return ids[ids.length - 1].match(/^[\d]/) === null // }, "Last part of name must not start with a digit"), ); if (data["brackets"] !== undefined) { // Data is a Scale. data.class = ParameterClass.Scale; audit.attribute(data, "brackets", true, errors, remainingKeys, auditArray(auditRawBracketToEditablePhase1, auditNonEmpty, auditRequire), auditRequire); audit.attribute(data, "metadata", true, errors, remainingKeys, auditRawScaleMetadataToEditable(units)); } else { // Repair data before validation. // Create `values` attribute if it is omitted. const childrenKeys = [...remainingKeys].filter(key => !["metadata", "values"].includes(key)); if (data.values === undefined && childrenKeys.length > 0 && childrenKeys.every(key => dateRegExp.test(key))) { const values = data.values = {}; for (const instant of childrenKeys) { values[instant] = data[instant]; delete data[instant]; remainingKeys.delete(instant); } } if (data["values"] !== undefined) { // Data is a Parameter. data.class = ParameterClass.Value; audit.attribute(data, "metadata", true, errors, remainingKeys, auditRawValueParameterMetadataToEditable(units)); audit.attribute(data, "values", true, errors, remainingKeys, auditSwitch([auditKeyValueDictionary(auditDate, [auditRawValueOrValueOrExpectedToEditable(auditBoolean), auditRequire]), auditFunction(values => { data.type = ValueType.Boolean; return values; })], [auditKeyValueDictionary(auditDate, [auditRawValueOrValueOrExpectedToEditable(auditNumber), auditRequire]), auditFunction(values => { data.type = ValueType.Number; return values; })], [auditKeyValueDictionary(auditDate, [auditRawValueOrValueOrExpectedToEditable(auditArray(auditString)), auditRequire]), auditFunction(values => { data.type = ValueType.StringArray; return values; })], [auditKeyValueDictionary(auditDate, [auditRawValueOrValueOrExpectedToEditable(auditKeyValueDictionary(auditString, auditString)), auditRequire]), auditFunction(values => { data.type = ValueType.StringByString; return values; })]), auditRequire); } else { // Data is a NodeParameter. data.class = ParameterClass.Node; // Repair data before validation. // Create `children` attribute from children keys. const children = {}; for (const key of childrenKeys) { children[key] = data[key]; delete data[key]; remainingKeys.delete(key); } data.children = children; audit.attribute(data, "children", true, errors, remainingKeys, auditKeyValueDictionary(auditString, [auditRawParameterToEditable(units), auditRequire]) // auditRequire, // A node may have no child (especially unprocessed nodes). ); if (errors.children !== undefined && typeof errors.children === "object") { Object.assign(errors, errors.children); delete errors.children; } audit.attribute(data, "metadata", true, errors, remainingKeys, auditRawNodeParameterMetadataToEditable(data, units, childrenId)); } } if (errors.metadata === undefined && data.metadata != null) { // Merge metadata into parameter. const metadataErrors = {}; for (const [key, metadataValue] of Object.entries(data.metadata)) { const dataValue = data[key]; if (metadataValue !== dataValue) { if (dataValue === undefined) { data[key] = metadataValue; } else { metadataErrors[key] = "Field is present both in parameter and its metadata, with different values"; } } } if (Object.keys(metadataErrors).length > 0) { errors.metadata = metadataErrors; } else { delete data.metadata; } } if (data.class === ParameterClass.Scale && errors.brackets === undefined) { // For heuristic, see function `_get_at_instant` of // https://github.com/openfisca/openfisca-core/blob/master/openfisca_core/parameters/parameter_scale.py if (data.type === ScaleType.SingleAmount) { audit.attribute(data, "brackets", true, errors, remainingKeys, auditArray(auditRawBracketToEditablePhase2Amount, auditRequire), auditRequire); // An amount scale can only contain an amount_unit or threshold_unit. audit.attribute(data, "rate_unit", true, errors, remainingKeys, auditNullish); } else if (data.brackets.some(({ amount }) => amount != null)) { audit.attribute(data, "brackets", true, errors, remainingKeys, auditArray(auditRawBracketToEditablePhase2Amount, auditRequire), auditRequire); // An amount scale can only contain an amount_unit or threshold_unit. audit.attribute(data, "rate_unit", true, errors, remainingKeys, auditNullish); audit.attribute(data, "type", true, errors, remainingKeys, auditOptions([ScaleType.MarginalAmount]), auditSetNullish(ScaleType.MarginalAmount)); } else if (data.brackets.some(({ average_rate }) => average_rate != null)) { // A rate scale can only contain a rate_unit and a threshold_unit audit.attribute(data, "amount_unit", true, errors, remainingKeys, auditNullish); audit.attribute(data, "brackets", true, errors, remainingKeys, auditArray(auditRawBracketPhase2AverageRate, auditRequire), auditRequire); audit.attribute(data, "type", true, errors, remainingKeys, auditOptions([ScaleType.LinearAverageRate]), auditSetNullish(ScaleType.LinearAverageRate)); } else { // A rate scale can only contain a rate_unit and a threshold_unit audit.attribute(data, "amount_unit", true, errors, remainingKeys, auditNullish); audit.attribute(data, "brackets", true, errors, remainingKeys, auditArray(auditRawBracketToEditablePhase2MarginalRate, auditRequire), auditRequire); audit.attribute(data, "type", true, errors, remainingKeys, auditOptions([ScaleType.MarginalRate]), auditSetNullish(ScaleType.MarginalRate)); } // if (errors.brackets === undefined) { // // Sort brackets by thresholds // const brackets = data.brackets as BracketBase[] // // Si pour toutes les dates de threshold1, les values de threshold2 (qui ne sont pas expected ou null) sont inférieures // for (const [index, bracket2] of brackets.slice(1).entries()) { // const threshold1 = brackets[index].threshold // for (const [instant, value1] of Object.entries(threshold1)) { // if (value1 === "expected" || value1.value === null) { // continue // } // const value2 = bracket2.threshold[instant] // if ( // value2 === undefined || // value2 === "expected" || // value2.value === null || // Object.values(bracket2).every( // (valueAtInstant: { // [instant: string]: MaybeNumberValue | "expected" // }) => { // const value = valueAtInstant[instant] // return ( // value === undefined || // value === "expected" || // value.value === null || // value.value === 0 // ) // }, // ) // ) { // continue // } // if (value1.value > value2.value) { // errors.brackets = { // [index]: `At ${instant}, value ${value1.value} of this bracket is greater than the value ${value2.value} of the next bracket`, // } // break // } // } // } // } if (errors.brackets === undefined && errors.metadata?.ipp_csv_id === undefined) { const ippCsvId = data.metadata?.ipp_csv_id; if (ippCsvId !== undefined && typeof ippCsvId === "string") { const bracketsName = Object.keys(data.brackets); const ippCsvIdErrorByBracketName = {}; for (const bracketName of Object.keys(ippCsvId)) { if (!bracketsName.includes(bracketName)) { ippCsvIdErrorByBracketName[bracketName] = `Expected ${bracketsName.map(bracketName => `${JSON.stringify(bracketName)}`).join(" or ")} as key, got: ${JSON.stringify(bracketName)}`; } } if (Object.keys(ippCsvIdErrorByBracketName).length > 0) { if (errors.metadata === undefined) { errors.metadata = {}; } ; errors.metadata.ipp_csv_id = ippCsvIdErrorByBracketName; } } } // => Convert `unit` to `threshold unit` and delete it. if (errors.unit === undefined && data.unit != null) { if (errors.threshold_unit === undefined) { audit.attribute(data, "threshold_unit", true, errors, remainingKeys, auditNullish); if (errors.threshold_unit === undefined) { data.threshold_unit = data.unit; } } delete data.unit; } } // Merge the references of the parameter values (and brackets values) with // the references of the parameter. if (Object.keys(errors).length === 0) { const referencesByInstant = data.reference ?? {}; switch (data.class) { case ParameterClass.Node: { break; } case ParameterClass.Scale: { const brackets = data.brackets; for (const bracket of brackets) { for (const key of ["amount", "base", "rate", "threshold"]) { const values = bracket[key]; if (values === undefined) { continue; } for (const [instant, valueAtInstant] of Object.entries(values)) { if (valueAtInstant !== "expected" && valueAtInstant.reference != null) { const mergedReferences = mergeReferences(referencesByInstant[instant], valueAtInstant.reference); if (mergedReferences !== undefined) { referencesByInstant[instant] = mergedReferences; } delete valueAtInstant.reference; } } } } break; } case ParameterClass.Value: { for (const [instant, valueAtInstant] of Object.entries(data.values)) { if (valueAtInstant !== "expected" && valueAtInstant.reference != null) { const mergedReferences = mergeReferences(referencesByInstant[instant], valueAtInstant.reference); if (mergedReferences !== undefined) { referencesByInstant[instant] = mergedReferences; } delete valueAtInstant.reference; } } break; } } if (Object.keys(referencesByInstant).length > 0) { data.reference = referencesByInstant; } } return audit.reduceRemaining(data, errors, remainingKeys); }; } export function auditRawReferenceObjectToEditable(audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); audit.attribute(data, "href", true, errors, remainingKeys, auditHttpUrl, // Remove jsessionid from Légifrance URLs. auditFunction(url => url.replace(/;jsessionid=[^\/?]*/, ""))); audit.attribute(data, "note", true, errors, remainingKeys, auditTrimString); audit.attribute(data, "title", true, errors, remainingKeys, auditTrimString); if (errors.href === undefined && errors.note === undefined && errors.title === undefined && data.href == null && data.note == null && data.title == null) { errors.href = errors.note = errors.title = "A reference object must contain a href and/or a note and/or a title"; } return audit.reduceRemaining(data, errors, remainingKeys); } export function auditRawScaleMetadataToEditable(units) { return function (audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); auditRawMetadataBaseAttributesToEditable(audit, data, errors, remainingKeys); // Used only for compatibility with "barèmes IPP". audit.attribute(data, "ipp_csv_id", true, errors, remainingKeys, auditSwitch(auditTrimString, auditKeyValueDictionary(auditOptions(["amount", "average_rate", "base", "rate", "threshold"]), [auditTrimString, auditRequire]))); audit.attribute(data, "amount_unit", true, errors, remainingKeys, auditRawUnitNameToEditable(units)); audit.attribute( // UK data, "period", true, errors, remainingKeys, auditString, auditOptions(["hour", // UK "month", // UK "week", // UK "year" // UK ])); audit.attribute(data, "rate_unit", true, errors, remainingKeys, auditRawUnitNameToEditable(units)); audit.attribute(data, "threshold_unit", true, errors, remainingKeys, auditRawUnitNameToEditable(units)); audit.attribute(data, "type", true, errors, remainingKeys, auditString, auditOptions(["marginal_rate", "single_amount"])); audit.attribute(data, "unit", true, errors, remainingKeys, auditRawUnitNameToEditable(units)); return audit.reduceRemaining(data, errors, remainingKeys); }; } export function auditRawUnitNameToEditable(units) { const unitsName = units.map(({ name }) => name); return auditChain(auditString, auditTrimString, auditOptions(unitsName)); } export function auditRawValueParameterMetadataToEditable(units) { return function (audit, dataUnknown) { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); auditRawMetadataBaseAttributesToEditable(audit, data, errors, remainingKeys); // Used only for compatibility "barèmes IPP". audit.attribute(data, "ipp_csv_id", true, errors, remainingKeys, auditTrimString); audit.attribute( // UK data, "period", true, errors, remainingKeys, auditString, auditOptions(["hour", // UK "month", // UK "week", // UK "year" // UK ])); audit.attribute(data, "unit", true, errors, remainingKeys, // Remove US "bool" unit. auditFunction(unit => unit === "bool" ? undefined : unit), auditRawUnitNameToEditable(units)); return audit.reduceRemaining(data, errors, remainingKeys); }; } export function auditRawValueToEditable(...auditors) { return (audit, dataUnknown) => { if (dataUnknown == null) { return [dataUnknown, null]; } if (typeof dataUnknown !== "object") { return audit.unexpectedType(dataUnknown, "object"); } const data = { ...dataUnknown }; const errors = {}; const remainingKeys = new Set(Object.keys(data)); audit.attribute(data, "reference", true, errors, remainingKeys, auditSwitch([auditRawReferenceToEditable, auditFunction(reference => [reference])], auditCleanArray(auditRawReferenceToEditable))); audit.attribute(data, "value", false, // Keep null value. errors, remainingKeys, ...auditors); return audit.reduceRemaining(data, errors, remainingKeys); }; } export const auditRawReferenceToEditable = auditSwitch([auditString, auditSwitch([auditHttpUrl, // Remove jsessionid from Légifrance URLs. auditFunction(url => url.replace(/;jsessionid=[^\/?]*/, "")), auditFunction(href => ({ href }))], auditChain( // When string has the form "title: url", replace it with a reference object. auditTest(text => (text.match(/:/g) ?? []).length > 1), auditFunction(text => { const textSplit = text.split(":"); return [textSplit[0], textSplit.slice(1).join(":")]; }), auditTuple(auditTrimString, auditHttpUrl), auditFunction(([title, href]) => ({ href, title }))), auditFunction(title => ({ title })))], auditRawReferenceObjectToEditable); export const auditRawReferencesToEditable = auditSwitch([auditRawReferenceToEditable, auditFunction(reference => ({ "0001-01-01": [reference] }))], [auditArray(), auditCleanArray(auditRawReferenceToEditable), auditFunction(references => ({ "0001-01-01": references }))], auditKeyValueDictionary(auditDate, auditSwitch([auditRawReferenceToEditable, auditFunction(reference => [reference])], [auditArray(), auditCleanArray(auditRawReferenceToEditable)]))); // export const auditRawNotesToEditable = auditSwitch( // [auditTrimString, auditFunction((note) => ({ "0001-01-01": note }))], // [ // auditArray(), // auditCleanArray(auditTrimString), // auditFunction((notes: string[]) => ({ // "0001-01-01": notes.map((note) => `- ${note}`).join("\n"), // })), // ], // auditKeyValueDictionary(auditDate, auditTrimString), // ) export const auditRawNotesToEditable = auditRawReferencesToEditable; export const auditRawValueOrExpectedToEditable = (...auditors) => auditSwitch(auditTest(value => value === "expected", 'Value is not "expected"'), auditRawValueToEditable(...auditors)); export const auditRawValueOrValueOrExpectedToEditable = (...auditors) => auditSwitch(auditTest(value => value === "expected", 'Value is not "expected"'), auditRawValueToEditable(...auditors), [...auditors, auditFunction(value => ({ value }))]); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhdWRpdEFycmF5IiwiYXVkaXRCb29sZWFuIiwiYXVkaXRDaGFpbiIsImF1ZGl0Q2xlYW5BcnJheSIsImF1ZGl0RnVuY3Rpb24iLCJhdWRpdEh0dHBVcmwiLCJhdWRpdEtleVZhbHVlRGljdGlvbmFyeSIsImF1ZGl0Tm9uRW1wdHkiLCJhdWRpdE5vb3AiLCJhdWRpdE51bGxpc2giLCJhdWRpdE51bWJlciIsImF1ZGl0T3B0aW9ucyIsImF1ZGl0UmVxdWlyZSIsImF1ZGl0U2V0TnVsbGlzaCIsImF1ZGl0U3RyaW5nIiwiYXVkaXRTd2l0Y2giLCJhdWRpdFRlc3QiLCJhdWRpdFRyaW1TdHJpbmciLCJhdWRpdFR1cGxlIiwiYXVkaXRVbmlxdWUiLCJtZXJnZVJlZmVyZW5jZXMiLCJQYXJhbWV0ZXJDbGFzcyIsIlNjYWxlVHlwZSIsIlZhbHVlVHlwZSIsImRhdGVSZWdFeHAiLCJhdWRpdERhdGUiLCJhdWRpdFJhd0JyYWNrZXRUb0VkaXRhYmxlUGhhc2UxIiwiYXVkaXQiLCJkYXRhVW5rbm93biIsInVuZXhwZWN0ZWRUeXBlIiwiZGF0YSIsImVycm9ycyIsInJlbWFpbmluZ0tleXMiLCJTZXQiLCJPYmplY3QiLCJrZXlzIiwiYXR0cmlidXRlIiwiYXVkaXRSYXdWYWx1ZU9yRXhwZWN0ZWRUb0VkaXRhYmxlIiwicmVkdWNlUmVtYWluaW5nIiwiYXVkaXRSYXdCcmFja2V0VG9FZGl0YWJsZVBoYXNlMkFtb3VudCIsImF1ZGl0UmF3QnJhY2tldFBoYXNlMkF2ZXJhZ2VSYXRlIiwiYXZlcmFnZV9yYXRlIiwidW5kZWZpbmVkIiwicmF0ZSIsImF1ZGl0UmF3QnJhY2tldFRvRWRpdGFibGVQaGFzZTJNYXJnaW5hbFJhdGUiLCJhdWRpdFJhd01ldGFkYXRhQmFzZUF0dHJpYnV0ZXNUb0VkaXRhYmxlIiwia2V5IiwidmFsdWUiLCJhdWRpdFJhd05vdGVzVG9FZGl0YWJsZSIsInRleHQiLCJzcGxpdCIsImluc3RhbnRzIiwiam9pbiIsImF1ZGl0UmF3UmVmZXJlbmNlc1RvRWRpdGFibGUiLCJhdWRpdFJhd05vZGVQYXJhbWV0ZXJNZXRhZGF0YVRvRWRpdGFibGUiLCJwYXJhbWV0ZXJEYXRhIiwidW5pdHMiLCJjaGlsZHJlbklkIiwiY2hpbGRyZW4iLCJ0ZXN0T3JkZXJJdGVtcyIsImF1ZGl0UmF3VW5pdE5hbWVUb0VkaXRhYmxlIiwiYXVkaXRSYXdQYXJhbWV0ZXJUb0VkaXRhYmxlIiwibWV0YWRhdGEiLCJkZWxldGUiLCJjbGFzcyIsIlNjYWxlIiwiYXVkaXRSYXdTY2FsZU1ldGFkYXRhVG9FZGl0YWJsZSIsImNoaWxkcmVuS2V5cyIsImZpbHRlciIsImluY2x1ZGVzIiwidmFsdWVzIiwibGVuZ3RoIiwiZXZlcnkiLCJ0ZXN0IiwiaW5zdGFudCIsIlZhbHVlIiwiYXVkaXRSYXdWYWx1ZVBhcmFtZXRlck1ldGFkYXRhVG9FZGl0YWJsZSIsImF1ZGl0UmF3VmFsdWVPclZhbHVlT3JFeHBlY3RlZFRvRWRpdGFibGUiLCJ0eXBlIiwiQm9vbGVhbiIsIk51bWJlciIsIlN0cmluZ0FycmF5IiwiU3RyaW5nQnlTdHJpbmciLCJOb2RlIiwiYXNzaWduIiwibWV0YWRhdGFFcnJvcnMiLCJtZXRhZGF0YVZhbHVlIiwiZW50cmllcyIsImRhdGFWYWx1ZSIsImJyYWNrZXRzIiwiU2luZ2xlQW1vdW50Iiwic29tZSIsImFtb3VudCIsIk1hcmdpbmFsQW1vdW50IiwiTGluZWFyQXZlcmFnZVJhdGUiLCJNYXJnaW5hbFJhdGUiLCJpcHBfY3N2X2lkIiwiaXBwQ3N2SWQiLCJicmFja2V0c05hbWUiLCJpcHBDc3ZJZEVycm9yQnlCcmFja2V0TmFtZSIsImJyYWNrZXROYW1lIiwibWFwIiwiSlNPTiIsInN0cmluZ2lmeSIsInVuaXQiLCJ0aHJlc2hvbGRfdW5pdCIsInJlZmVyZW5jZXNCeUluc3RhbnQiLCJyZWZlcmVuY2UiLCJicmFja2V0IiwidmFsdWVBdEluc3RhbnQiLCJtZXJnZWRSZWZlcmVuY2VzIiwiYXVkaXRSYXdSZWZlcmVuY2VPYmplY3RUb0VkaXRhYmxlIiwidXJsIiwicmVwbGFjZSIsImhyZWYiLCJub3RlIiwidGl0bGUiLCJ1bml0c05hbWUiLCJuYW1lIiwiYXVkaXRSYXdWYWx1ZVRvRWRpdGFibGUiLCJhdWRpdG9ycyIsImF1ZGl0UmF3UmVmZXJlbmNlVG9FZGl0YWJsZSIsIm1hdGNoIiwidGV4dFNwbGl0Iiwic2xpY2UiLCJyZWZlcmVuY2VzIl0sInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2F1ZGl0b3JzL3BhcmFtZXRlcnMvcmF3X3RvX2VkaXRhYmxlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEF1ZGl0LFxuICBhdWRpdEFycmF5LFxuICBhdWRpdEJvb2xlYW4sXG4gIGF1ZGl0Q2hhaW4sXG4gIGF1ZGl0Q2xlYW5BcnJheSxcbiAgYXVkaXRGdW5jdGlvbixcbiAgYXVkaXRIdHRwVXJsLFxuICBhdWRpdEtleVZhbHVlRGljdGlvbmFyeSxcbiAgYXVkaXROb25FbXB0eSxcbiAgYXVkaXROb29wLFxuICBhdWRpdE51bGxpc2gsXG4gIGF1ZGl0TnVtYmVyLFxuICBhdWRpdE9wdGlvbnMsXG4gIGF1ZGl0UmVxdWlyZSxcbiAgYXVkaXRTZXROdWxsaXNoLFxuICBhdWRpdFN0cmluZyxcbiAgYXVkaXRTd2l0Y2gsXG4gIGF1ZGl0VGVzdCxcbiAgYXVkaXRUcmltU3RyaW5nLFxuICBhdWRpdFR1cGxlLFxuICBhdWRpdFVuaXF1ZSxcbiAgdHlwZSBBdWRpdG9yLFxufSBmcm9tIFwiQGF1ZGl0b3JzL2NvcmVcIlxuXG5pbXBvcnQge1xuICAvLyBCcmFja2V0QmFzZSxcbiAgLy8gTWF5YmVOdW1iZXJWYWx1ZSxcbiAgbWVyZ2VSZWZlcmVuY2VzLFxuICBQYXJhbWV0ZXJDbGFzcyxcbiAgU2NhbGVUeXBlLFxuICBWYWx1ZVR5cGUsXG4gIHR5cGUgQW1vdW50QnJhY2tldCxcbiAgdHlwZSBCcmFja2V0VmFsdWVBdEluc3RhbnQsXG4gIHR5cGUgUmF0ZUJyYWNrZXQsXG4gIHR5cGUgVmFsdWVBdEluc3RhbnQsXG59IGZyb20gXCIuLi8uLi9wYXJhbWV0ZXJzXCJcbmltcG9ydCB7IGRhdGVSZWdFeHAgfSBmcm9tIFwiLi4vLi4vcGVyaW9kc1wiXG5pbXBvcnQgdHlwZSB7IFJlZmVyZW5jZXNCeUluc3RhbnQgfSBmcm9tIFwiLi4vLi4vcmVmZXJlbmNlc1wiXG5pbXBvcnQgdHlwZSB7IFVuaXQgfSBmcm9tIFwiLi4vLi4vdW5pdHNcIlxuXG5pbXBvcnQgeyBhdWRpdERhdGUgfSBmcm9tIFwiLi4vcGVyaW9kc1wiXG5cbmV4cG9ydCBmdW5jdGlvbiBhdWRpdFJhd0JyYWNrZXRUb0VkaXRhYmxlUGhhc2UxKFxuICBhdWRpdDogQXVkaXQsXG4gIGRhdGFVbmtub3duOiB1bmtub3duLFxuKTogW3Vua25vd24sIHVua25vd25dIHtcbiAgaWYgKGRhdGFVbmtub3duID09IG51bGwpIHtcbiAgICByZXR1cm4gW2RhdGFVbmtub3duLCBudWxsXVxuICB9XG4gIGlmICh0eXBlb2YgZGF0YVVua25vd24gIT09IFwib2JqZWN0XCIpIHtcbiAgICByZXR1cm4gYXVkaXQudW5leHBlY3RlZFR5cGUoZGF0YVVua25vd24sIFwib2JqZWN0XCIpXG4gIH1cblxuICBjb25zdCBkYXRhOiB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSA9IHsgLi4uZGF0YVVua25vd24gfVxuICBjb25zdCBlcnJvcnM6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9ID0ge31cbiAgY29uc3QgcmVtYWluaW5nS2V5cyA9IG5ldyBTZXQoT2JqZWN0LmtleXMoZGF0YSkpXG5cbiAgYXVkaXQuYXR0cmlidXRlKFxuICAgIGRhdGEsXG4gICAgXCJhbW91bnRcIixcbiAgICB0cnVlLFxuICAgIGVycm9ycyxcbiAgICByZW1haW5pbmdLZXlzLFxuICAgIGF1ZGl0S2V5VmFsdWVEaWN0aW9uYXJ5KGF1ZGl0RGF0ZSwgW1xuICAgICAgYXVkaXRSYXdWYWx1ZU9yRXhwZWN0ZWRUb0VkaXRhYmxlKGF1ZGl0TnVtYmVyKSxcbiAgICAgIGF1ZGl0UmVxdWlyZSxcbiAgICBdKSxcbiAgKVxuICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgZGF0YSxcbiAgICBcImF2ZXJhZ2VfcmF0ZVwiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXRLZXlWYWx1ZURpY3Rpb25hcnkoYXVkaXREYXRlLCBbXG4gICAgICBhdWRpdFJhd1ZhbHVlT3JFeHBlY3RlZFRvRWRpdGFibGUoYXVkaXROdW1iZXIpLFxuICAgICAgYXVkaXRSZXF1aXJlLFxuICAgIF0pLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICBkYXRhLFxuICAgIFwiYmFzZVwiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXRLZXlWYWx1ZURpY3Rpb25hcnkoYXVkaXREYXRlLCBbXG4gICAgICBhdWRpdFJhd1ZhbHVlT3JFeHBlY3RlZFRvRWRpdGFibGUoYXVkaXROdW1iZXIpLFxuICAgICAgYXVkaXRSZXF1aXJlLFxuICAgIF0pLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICBkYXRhLFxuICAgIFwicmF0ZVwiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXRLZXlWYWx1ZURpY3Rpb25hcnkoYXVkaXREYXRlLCBbXG4gICAgICBhdWRpdFJhd1ZhbHVlT3JFeHBlY3RlZFRvRWRpdGFibGUoYXVkaXROdW1iZXIpLFxuICAgICAgYXVkaXRSZXF1aXJlLFxuICAgIF0pLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICBkYXRhLFxuICAgIFwidGhyZXNob2xkXCIsXG4gICAgdHJ1ZSxcbiAgICBlcnJvcnMsXG4gICAgcmVtYWluaW5nS2V5cyxcbiAgICBhdWRpdEtleVZhbHVlRGljdGlvbmFyeShhdWRpdERhdGUsIFtcbiAgICAgIGF1ZGl0UmF3VmFsdWVPckV4cGVjdGVkVG9FZGl0YWJsZShhdWRpdE51bWJlciksXG4gICAgICBhdWRpdFJlcXVpcmUsXG4gICAgXSksXG4gICAgYXVkaXRSZXF1aXJlLFxuICApXG5cbiAgcmV0dXJuIGF1ZGl0LnJlZHVjZVJlbWFpbmluZyhkYXRhLCBlcnJvcnMsIHJlbWFpbmluZ0tleXMpXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBhdWRpdFJhd0JyYWNrZXRUb0VkaXRhYmxlUGhhc2UyQW1vdW50KFxuICBhdWRpdDogQXVkaXQsXG4gIGRhdGFVbmtub3duOiB1bmtub3duLFxuKTogW3Vua25vd24sIHVua25vd25dIHtcbiAgaWYgKGRhdGFVbmtub3duID09IG51bGwpIHtcbiAgICByZXR1cm4gW2RhdGFVbmtub3duLCBudWxsXVxuICB9XG4gIGlmICh0eXBlb2YgZGF0YVVua25vd24gIT09IFwib2JqZWN0XCIpIHtcbiAgICByZXR1cm4gYXVkaXQudW5leHBlY3RlZFR5cGUoZGF0YVVua25vd24sIFwib2JqZWN0XCIpXG4gIH1cblxuICBjb25zdCBkYXRhOiB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSA9IHsgLi4uZGF0YVVua25vd24gfVxuICBjb25zdCBlcnJvcnM6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9ID0ge31cbiAgY29uc3QgcmVtYWluaW5nS2V5cyA9IG5ldyBTZXQoT2JqZWN0LmtleXMoZGF0YSkpXG5cbiAgYXVkaXQuYXR0cmlidXRlKGRhdGEsIFwiYW1vdW50XCIsIHRydWUsIGVycm9ycywgcmVtYWluaW5nS2V5cywgYXVkaXRSZXF1aXJlKVxuICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgZGF0YSxcbiAgICBcImF2ZXJhZ2VfcmF0ZVwiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXROdWxsaXNoLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShkYXRhLCBcImJhc2VcIiwgdHJ1ZSwgZXJyb3JzLCByZW1haW5pbmdLZXlzLCBhdWRpdE51bGxpc2gpXG4gIGF1ZGl0LmF0dHJpYnV0ZShkYXRhLCBcInJhdGVcIiwgdHJ1ZSwgZXJyb3JzLCByZW1haW5pbmdLZXlzLCBhdWRpdE51bGxpc2gpXG4gIGF1ZGl0LmF0dHJpYnV0ZShkYXRhLCBcInRocmVzaG9sZFwiLCB0cnVlLCBlcnJvcnMsIHJlbWFpbmluZ0tleXMsIGF1ZGl0UmVxdWlyZSlcblxuICByZXR1cm4gYXVkaXQucmVkdWNlUmVtYWluaW5nKGRhdGEsIGVycm9ycywgcmVtYWluaW5nS2V5cylcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0UmF3QnJhY2tldFBoYXNlMkF2ZXJhZ2VSYXRlKFxuICBhdWRpdDogQXVkaXQsXG4gIGRhdGFVbmtub3duOiB1bmtub3duLFxuKTogW3Vua25vd24sIHVua25vd25dIHtcbiAgaWYgKGRhdGFVbmtub3duID09IG51bGwpIHtcbiAgICByZXR1cm4gW2RhdGFVbmtub3duLCBudWxsXVxuICB9XG4gIGlmICh0eXBlb2YgZGF0YVVua25vd24gIT09IFwib2JqZWN0XCIpIHtcbiAgICByZXR1cm4gYXVkaXQudW5leHBlY3RlZFR5cGUoZGF0YVVua25vd24sIFwib2JqZWN0XCIpXG4gIH1cblxuICBjb25zdCBkYXRhOiB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSA9IHsgLi4uZGF0YVVua25vd24gfVxuICBjb25zdCBlcnJvcnM6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9ID0ge31cbiAgY29uc3QgcmVtYWluaW5nS2V5cyA9IG5ldyBTZXQoT2JqZWN0LmtleXMoZGF0YSkpXG5cbiAgYXVkaXQuYXR0cmlidXRlKGRhdGEsIFwiYW1vdW50XCIsIHRydWUsIGVycm9ycywgcmVtYWluaW5nS2V5cywgYXVkaXROdWxsaXNoKVxuICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgZGF0YSxcbiAgICBcImF2ZXJhZ2VfcmF0ZVwiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXRSZXF1aXJlLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShkYXRhLCBcImJhc2VcIiwgdHJ1ZSwgZXJyb3JzLCByZW1haW5pbmdLZXlzKVxuICBhdWRpdC5hdHRyaWJ1dGUoZGF0YSwgXCJyYXRlXCIsIHRydWUsIGVycm9ycywgcmVtYWluaW5nS2V5cywgYXVkaXROdWxsaXNoKVxuICBhdWRpdC5hdHRyaWJ1dGUoZGF0YSwgXCJ0aHJlc2hvbGRcIiwgdHJ1ZSwgZXJyb3JzLCByZW1haW5pbmdLZXlzLCBhdWRpdFJlcXVpcmUpXG5cbiAgaWYgKGVycm9ycy5hdmVyYWdlX3JhdGUgPT09IHVuZGVmaW5lZCAmJiBlcnJvcnMucmF0ZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgZGF0YS5yYXRlID0gZGF0YS5hdmVyYWdlX3JhdGVcbiAgICBkZWxldGUgZGF0YS5hdmVyYWdlX3JhdGVcbiAgfVxuXG4gIHJldHVybiBhdWRpdC5yZWR1Y2VSZW1haW5pbmcoZGF0YSwgZXJyb3JzLCByZW1haW5pbmdLZXlzKVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRSYXdCcmFja2V0VG9FZGl0YWJsZVBoYXNlMk1hcmdpbmFsUmF0ZShcbiAgYXVkaXQ6IEF1ZGl0LFxuICBkYXRhVW5rbm93bjogdW5rbm93bixcbik6IFt1bmtub3duLCB1bmtub3duXSB7XG4gIGlmIChkYXRhVW5rbm93biA9PSBudWxsKSB7XG4gICAgcmV0dXJuIFtkYXRhVW5rbm93biwgbnVsbF1cbiAgfVxuICBpZiAodHlwZW9mIGRhdGFVbmtub3duICE9PSBcIm9iamVjdFwiKSB7XG4gICAgcmV0dXJuIGF1ZGl0LnVuZXhwZWN0ZWRUeXBlKGRhdGFVbmtub3duLCBcIm9iamVjdFwiKVxuICB9XG5cbiAgY29uc3QgZGF0YTogeyBba2V5OiBzdHJpbmddOiB1bmtub3duIH0gPSB7IC4uLmRhdGFVbmtub3duIH1cbiAgY29uc3QgZXJyb3JzOiB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSA9IHt9XG4gIGNvbnN0IHJlbWFpbmluZ0tleXMgPSBuZXcgU2V0KE9iamVjdC5rZXlzKGRhdGEpKVxuXG4gIGF1ZGl0LmF0dHJpYnV0ZShkYXRhLCBcImFtb3VudFwiLCB0cnVlLCBlcnJvcnMsIHJlbWFpbmluZ0tleXMsIGF1ZGl0TnVsbGlzaClcbiAgYXVkaXQuYXR0cmlidXRlKGRhdGEsIFwiYmFzZVwiLCB0cnVlLCBlcnJvcnMsIHJlbWFpbmluZ0tleXMpXG4gIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICBkYXRhLFxuICAgIFwiYXZlcmFnZV9yYXRlXCIsXG4gICAgdHJ1ZSxcbiAgICBlcnJvcnMsXG4gICAgcmVtYWluaW5nS2V5cyxcbiAgICBhdWRpdE51bGxpc2gsXG4gIClcbiAgYXVkaXQuYXR0cmlidXRlKGRhdGEsIFwicmF0ZVwiLCB0cnVlLCBlcnJvcnMsIHJlbWFpbmluZ0tleXMsIGF1ZGl0UmVxdWlyZSlcbiAgYXVkaXQuYXR0cmlidXRlKGRhdGEsIFwidGhyZXNob2xkXCIsIHRydWUsIGVycm9ycywgcmVtYWluaW5nS2V5cywgYXVkaXRSZXF1aXJlKVxuXG4gIHJldHVybiBhdWRpdC5yZWR1Y2VSZW1haW5pbmcoZGF0YSwgZXJyb3JzLCByZW1haW5pbmdLZXlzKVxufVxuXG5mdW5jdGlvbiBhdWRpdFJhd01ldGFkYXRhQmFzZUF0dHJpYnV0ZXNUb0VkaXRhYmxlKFxuICBhdWRpdDogQXVkaXQsXG4gIGRhdGE6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9LFxuICBlcnJvcnM6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9LFxuICByZW1haW5pbmdLZXlzOiBTZXQ8c3RyaW5nPixcbik6IHZvaWQge1xuICBmb3IgKGNvbnN0IGtleSBvZiBbXG4gICAgXCJkZXNjcmlwdGlvblwiLFxuICAgIFwibGFiZWxfZW5cIixcbiAgICBcImRvY3VtZW50YXRpb25cIixcbiAgICBcInNob3J0X2xhYmVsXCIsXG4gICAgXCJzaG9ydF9sYWJlbF9lblwiLFxuICBdKSB7XG4gICAgYXVkaXQuYXR0cmlidXRlKGRhdGEsIGtleSwgdHJ1ZSwgZXJyb3JzLCByZW1haW5pbmdLZXlzLCBhdWRpdFRyaW1TdHJpbmcpXG4gIH1cbiAgYXVkaXQuYXR0cmlidXRlKFxuICAgIGRhdGEsXG4gICAgXCJkb2N1bWVudGF0aW9uX3N0YXJ0XCIsXG4gICAgdHJ1ZSxcbiAgICBlcnJvcnMsXG4gICAgcmVtYWluaW5nS2V5cyxcbiAgICBhdWRpdEJvb2xlYW4sXG4gICAgYXVkaXRGdW5jdGlvbigodmFsdWUpID0+ICh2YWx1ZSA/IHRydWUgOiBudWxsKSksXG4gIClcbiAgYXVkaXQuYXR0cmlidXRlKFxuICAgIGRhdGEsXG4gICAgXCJsYXN0X3ZhbHVlX3N0aWxsX3ZhbGlkX29uXCIsXG4gICAgdHJ1ZSxcbiAgICBlcnJvcnMsXG4gICAgcmVtYWluaW5nS2V5cyxcbiAgICBhdWRpdERhdGUsXG4gIClcbiAgYXVkaXQuYXR0cmlidXRlKFxuICAgIGRhdGEsXG4gICAgXCJub3Rlc1wiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXRSYXdOb3Rlc1RvRWRpdGFibGUsXG4gIClcbiAgYXVkaXQuYXR0cmlidXRlKFxuICAgIGRhdGEsXG4gICAgXCJvZmZpY2lhbF9qb3VybmFsX2RhdGVcIixcbiAgICB0cnVlLFxuICAgIGVycm9ycyxcbiAgICByZW1haW5pbmdLZXlzLFxuICAgIGF1ZGl0S2V5VmFsdWVEaWN0aW9uYXJ5KGF1ZGl0RGF0ZSwgW1xuICAgICAgYXVkaXRTd2l0Y2goYXVkaXREYXRlLCBbXG4gICAgICAgIGF1ZGl0U3RyaW5nLFxuICAgICAgICBhdWRpdEZ1bmN0aW9uKCh0ZXh0KSA9PiB0ZXh0LnNwbGl0KFwiO1wiKSksXG4gICAgICAgIGF1ZGl0QXJyYXkoYXVkaXREYXRlKSxcbiAgICAgICAgLy8gVE9ETzogUmV0dXJuIHNvbWV0aGluZyBvdGhlciB0aGFuIGEgc3RyaW5nP1xuICAgICAgICBhdWRpdEZ1bmN0aW9uKChpbnN0YW50cykgPT4gaW5zdGFudHMuam9pbihcIjsgXCIpKSxcbiAgICAgIF0pLFxuICAgICAgYXVkaXRSZXF1aXJlLFxuICAgIF0pLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICBkYXRhLFxuICAgIFwicmVmZXJlbmNlXCIsXG4gICAgdHJ1ZSxcbiAgICBlcnJvcnMsXG4gICAgcmVtYWluaW5nS2V5cyxcbiAgICBhdWRpdFJhd1JlZmVyZW5jZXNUb0VkaXRhYmxlLFxuICApXG4gIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICBkYXRhLFxuICAgIFwiaW5mbGF0b3JcIixcbiAgICB0cnVlLFxuICAgIGVycm9ycyxcbiAgICByZW1haW5pbmdLZXlzLFxuICAgIGF1ZGl0VHJpbVN0cmluZyxcbiAgKVxuICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgZGF0YSxcbiAgICBcImluZmxhdG9yX3JlZmVyZW5jZVwiLFxuICAgIHRydWUsXG4gICAgZXJyb3JzLFxuICAgIHJlbWFpbmluZ0tleXMsXG4gICAgYXVkaXRSYXdSZWZlcmVuY2VzVG9FZGl0YWJsZSxcbiAgKVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRSYXdOb2RlUGFyYW1ldGVyTWV0YWRhdGFUb0VkaXRhYmxlKFxuICBwYXJhbWV0ZXJEYXRhOiB7XG4gICAgW2tleTogc3RyaW5nXTogdW5rbm93blxuICB9LFxuICB1bml0czogVW5pdFtdLFxuICBjaGlsZHJlbklkOiBzdHJpbmdbXSB8IHVuZGVmaW5lZCxcbik6IEF1ZGl0b3Ige1xuICBjb25zdCBjaGlsZHJlbiA9IHBhcmFtZXRlckRhdGEuY2hpbGRyZW5cbiAgY29uc3QgdGVzdE9yZGVySXRlbXMgPVxuICAgIGNoaWxkcmVuSWQgIT09IHVuZGVmaW5lZCB8fFxuICAgIChjaGlsZHJlbiAhPSBudWxsICYmIHR5cGVvZiBjaGlsZHJlbiA9PT0gXCJvYmplY3RcIilcbiAgaWYgKGNoaWxkcmVuSWQgPT09IHVuZGVmaW5lZCkge1xuICAgIGNoaWxkcmVuSWQgPSB0ZXN0T3JkZXJJdGVtcyA/IE9iamVjdC5rZXlzKGNoaWxkcmVuISkgOiBbXVxuICB9XG5cbiAgcmV0dXJuIGZ1bmN0aW9uIChhdWRpdDogQXVkaXQsIGRhdGFVbmtub3duOiB1bmtub3duKTogW3Vua25vd24sIHVua25vd25dIHtcbiAgICBpZiAoZGF0YVVua25vd24gPT0gbnVsbCkge1xuICAgICAgcmV0dXJuIFtkYXRhVW5rbm93biwgbnVsbF1cbiAgICB9XG4gICAgaWYgKHR5cGVvZiBkYXRhVW5rbm93biAhPT0gXCJvYmplY3RcIikge1xuICAgICAgcmV0dXJuIGF1ZGl0LnVuZXhwZWN0ZWRUeXBlKGRhdGFVbmtub3duLCBcIm9iamVjdFwiKVxuICAgIH1cblxuICAgIGNvbnN0IGRhdGE6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9ID0geyAuLi5kYXRhVW5rbm93biB9XG4gICAgY29uc3QgZXJyb3JzOiB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSA9IHt9XG4gICAgY29uc3QgcmVtYWluaW5nS2V5cyA9IG5ldyBTZXQoT2JqZWN0LmtleXMoZGF0YSkpXG5cbiAgICBhdWRpdFJhd01ldGFkYXRhQmFzZUF0dHJpYnV0ZXNUb0VkaXRhYmxlKGF1ZGl0LCBkYXRhLCBlcnJvcnMsIHJlbWFpbmluZ0tleXMpXG4gICAgLy8gVXNlZCBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIFwiYmFyw6htZXMgSVBQXCIuXG4gICAgYXVkaXQuYXR0cmlidXRlKFxuICAgICAgZGF0YSxcbiAgICAgIFwib3JkZXJcIixcbiAgICAgIHRydWUsXG4gICAgICBlcnJvcnMsXG4gICAgICByZW1haW5pbmdLZXlzLFxuICAgICAgYXVkaXRDbGVhbkFycmF5KFxuICAgICAgICBhdWRpdFN0cmluZyxcbiAgICAgICAgdGVzdE9yZGVySXRlbXMgPyBhdWRpdE9wdGlvbnMoY2hpbGRyZW5JZCEpIDogYXVkaXROb29wLFxuICAgICAgKSxcbiAgICAgIGF1ZGl0VW5pcXVlLFxuICAgIClcbiAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICBkYXRhLFxuICAgICAgXCJ1bml0XCIsXG4gICAgICB0cnVlLFxuICAgICAgZXJyb3JzLFxuICAgICAgcmVtYWluaW5nS2V5cyxcbiAgICAgIGF1ZGl0UmF3VW5pdE5hbWVUb0VkaXRhYmxlKHVuaXRzKSxcbiAgICApXG5cbiAgICByZXR1cm4gYXVkaXQucmVkdWNlUmVtYWluaW5nKGRhdGEsIGVycm9ycywgcmVtYWluaW5nS2V5cylcbiAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gYXVkaXRSYXdQYXJhbWV0ZXJUb0VkaXRhYmxlKFxuICB1bml0czogVW5pdFtdLFxuICBjaGlsZHJlbklkPzogc3RyaW5nW10gfCB1bmRlZmluZWQsXG4pOiBBdWRpdG9yIHtcbiAgcmV0dXJuIGZ1bmN0aW9uIChhdWRpdDogQXVkaXQsIGRhdGFVbmtub3duOiB1bmtub3duKTogW3Vua25vd24sIHVua25vd25dIHtcbiAgICBpZiAoZGF0YVVua25vd24gPT0gbnVsbCkge1xuICAgICAgcmV0dXJuIFtkYXRhVW5rbm93biwgbnVsbF1cbiAgICB9XG4gICAgaWYgKHR5cGVvZiBkYXRhVW5rbm93biAhPT0gXCJvYmplY3RcIikge1xuICAgICAgcmV0dXJuIGF1ZGl0LnVuZXhwZWN0ZWRUeXBlKGRhdGFVbmtub3duLCBcIm9iamVjdFwiKVxuICAgIH1cblxuICAgIGNvbnN0IGRhdGE6IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9ID0geyAuLi5kYXRhVW5rbm93biB9XG4gICAgY29uc3QgZXJyb3JzOiB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSA9IHt9XG4gICAgY29uc3QgcmVtYWluaW5nS2V5cyA9IG5ldyBTZXQoT2JqZWN0LmtleXMoZGF0YSkpXG5cbiAgICAvLyBSZXBhaXIgZGF0YSBiZWZvcmUgdmFsaWRhdGlvbi5cbiAgICAvLyBNb3ZlIHNvbWUgYXR0cmlidXRlcyBmcm9tIGRhdGEgdG8gbWV0YWRhdGEuXG4gICAgLy8gU2VlIGZ1bmN0aW9uIGBfc2V0X2JhY2t3YXJkX2NvbXBhdGliaWxpdHlfbWV0YWRhdGFgIG9mXG4gICAgLy8gaHR0cHM6Ly9naXRodWIuY29tL29wZW5maXNjYS9vcGVuZmlzY2EtY29yZS9ibG9iL21hc3Rlci9vcGVuZmlzY2FfY29yZS9wYXJhbWV0ZXJzL2hlbHBlcnMucHlcbiAgICBmb3IgKGNvbnN0IGtleSBvZiBbXCJyZWZlcmVuY2VcIiwgXCJ1bml0XCJdKSB7XG4gICAgICBpZiAoZGF0YVtrZXldICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgbGV0IG1ldGFkYXRhID0gZGF0YS5tZXRhZGF0YSBhcyB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSB8IHVuZGVmaW5lZFxuICAgICAgICBpZiAobWV0YWRhdGEgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIG1ldGFkYXRhID0gZGF0YS5tZXRhZGF0YSA9IHt9XG4gICAgICAgIH1cbiAgICAgICAgbWV0YWRhdGFba2V5XSA9IGRhdGFba2V5XVxuICAgICAgICBkZWxldGUgZGF0YVtrZXldXG4gICAgICAgIHJlbWFpbmluZ0tleXMuZGVsZXRlKGtleSlcbiAgICAgIH1cbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IGtleSBvZiBbXCJkZXNjcmlwdGlvblwiLCBcImRvY3VtZW50YXRpb25cIiwgXCJmaWxlX3BhdGhcIl0pIHtcbiAgICAgIGF1ZGl0LmF0dHJpYnV0ZShkYXRhLCBrZXksIHRydWUsIGVycm9ycywgcmVtYWluaW5nS2V5cywgYXVkaXRUcmltU3RyaW5nKVxuICAgIH1cbiAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICBkYXRhLFxuICAgICAgXCJkb2N1bWVudGF0aW9uX3N0YXJ0XCIsXG4gICAgICB0cnVlLFxuICAgICAgZXJyb3JzLFxuICAgICAgcmVtYWluaW5nS2V5cyxcbiAgICAgIGF1ZGl0Qm9vbGVhbixcbiAgICAgIGF1ZGl0RnVuY3Rpb24oKHZhbHVlKSA9PiAodmFsdWUgPyB0cnVlIDogbnVsbCkpLFxuICAgIClcbiAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICBkYXRhLFxuICAgICAgXCJuYW1lXCIsXG4gICAgICB0cnVlLFxuICAgICAgZXJyb3JzLFxuICAgICAgcmVtYWluaW5nS2V5cyxcbiAgICAgIGF1ZGl0VHJpbVN0cmluZyxcbiAgICAgIC8vIGF1ZGl0VGVzdCgobmFtZSkgPT4ge1xuICAgICAgLy8gICBjb25zdCBpZHMgPSBuYW1lLnNwbGl0KFwiLlwiKVxuICAgICAgLy8gICByZXR1cm4gaWRzW2lkcy5sZW5ndGggLSAxXS5tYXRjaCgvXltcXGRdLykgPT09IG51bGxcbiAgICAgIC8vIH0sIFwiTGFzdCBwYXJ0IG9mIG5hbWUgbXVzdCBub3Qgc3RhcnQgd2l0aCBhIGRpZ2l0XCIpLFxuICAgIClcblxuICAgIGlmIChkYXRhW1wiYnJhY2tldHNcIl0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgLy8gRGF0YSBpcyBhIFNjYWxlLlxuICAgICAgZGF0YS5jbGFzcyA9IFBhcmFtZXRlckNsYXNzLlNjYWxlXG4gICAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICAgIGRhdGEsXG4gICAgICAgIFwiYnJhY2tldHNcIixcbiAgICAgICAgdHJ1ZSxcbiAgICAgICAgZXJyb3JzLFxuICAgICAgICByZW1haW5pbmdLZXlzLFxuICAgICAgICBhdWRpdEFycmF5KFxuICAgICAgICAgIGF1ZGl0UmF3QnJhY2tldFRvRWRpdGFibGVQaGFzZTEsXG4gICAgICAgICAgYXVkaXROb25FbXB0eSxcbiAgICAgICAgICBhdWRpdFJlcXVpcmUsXG4gICAgICAgICksXG4gICAgICAgIGF1ZGl0UmVxdWlyZSxcbiAgICAgIClcbiAgICAgIGF1ZGl0LmF0dHJpYnV0ZShcbiAgICAgICAgZGF0YSxcbiAgICAgICAgXCJtZXRhZGF0YVwiLFxuICAgICAgICB0cnVlLFxuICAgICAgICBlcnJvcnMsXG4gICAgICAgIHJlbWFpbmluZ0tleXMsXG4gICAgICAgIGF1ZGl0UmF3U2NhbGVNZXRhZGF0YVRvRWRpdGFibGUodW5pdHMpLFxuICAgICAgKVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBSZXBhaXIgZGF0YSBiZWZvcmUgdmFsaWRhdGlvbi5cbiAgICAgIC8vIENyZWF0ZSBgdmFsdWVzYCBhdHRyaWJ1dGUgaWYgaXQgaXMgb21pdHRlZC5cbiAgICAgIGNvbnN0IGNoaWxkcmVuS2V5cyA9IFsuLi5yZW1haW5pbmdLZXlzXS5maWx0ZXIoXG4gICAgICAgIChrZXkpID0+ICFbXCJtZXRhZGF0YVwiLCBcInZhbHVlc1wiXS5pbmNsdWRlcyhrZXkpLFxuICAgICAgKVxuICAgICAgaWYgKFxuICAgICAgICBkYXRhLnZhbHVlcyA9PT0gdW5kZWZpbmVkICYmXG4gICAgICAgIGNoaWxkcmVuS2V5cy5sZW5ndGggPiAwICYmXG4gICAgICAgIGNoaWxkcmVuS2V5cy5ldmVyeSgoa2V5KSA9PiBkYXRlUmVnRXhwLnRlc3Qoa2V5KSlcbiAgICAgICkge1xuICAgICAgICBjb25zdCB2YWx1ZXM6IHsgW2luc3RhbnQ6IHN0cmluZ106IHVua25vd24gfSA9IChkYXRhLnZhbHVlcyA9IHt9KVxuICAgICAgICBmb3IgKGNvbnN0IGluc3RhbnQgb2YgY2hpbGRyZW5LZXlzKSB7XG4gICAgICAgICAgdmFsdWVzW2luc3RhbnRdID0gZGF0YVtpbnN0YW50XVxuICAgICAgICAgIGRlbGV0ZSBkYXRhW2luc3RhbnRdXG4gICAgICAgICAgcmVtYWluaW5nS2V5cy5kZWxldGUoaW5zdGFudClcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBpZiAoZGF0YVtcInZhbHVlc1wiXSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIC8vIERhdGEgaXMgYSBQYXJhbWV0ZXIuXG4gICAgICAgIGRhdGEuY2xhc3MgPSBQYXJhbWV0ZXJDbGFzcy5WYWx1ZVxuICAgICAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICAgICAgZGF0YSxcbiAgICAgICAgICBcIm1ldGFkYXRhXCIsXG4gICAgICAgICAgdHJ1ZSxcbiAgICAgICAgICBlcnJvcnMsXG4gICAgICAgICAgcmVtYWluaW5nS2V5cyxcbiAgICAgICAgICBhdWRpdFJhd1ZhbHVlUGFyYW1ldGVyTWV0YWRhdGFUb0VkaXRhYmxlKHVuaXRzKSxcbiAgICAgICAgKVxuICAgICAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICAgICAgZGF0YSxcbiAgICAgICAgICBcInZhbHVlc1wiLFxuICAgICAgICAgIHRydWUsXG4gICAgICAgICAgZXJyb3JzLFxuICAgICAgICAgIHJlbWFpbmluZ0tleXMsXG4gICAgICAgICAgYXVkaXRTd2l0Y2goXG4gICAgICAgICAgICBbXG4gICAgICAgICAgICAgIGF1ZGl0S2V5VmFsdWVEaWN0aW9uYXJ5KGF1ZGl0RGF0ZSwgW1xuICAgICAgICAgICAgICAgIGF1ZGl0UmF3VmFsdWVPclZhbHVlT3JFeHBlY3RlZFRvRWRpdGFibGUoYXVkaXRCb29sZWFuKSxcbiAgICAgICAgICAgICAgICBhdWRpdFJlcXVpcmUsXG4gICAgICAgICAgICAgIF0pLFxuICAgICAgICAgICAgICBhdWRpdEZ1bmN0aW9uKCh2YWx1ZXMpID0+IHtcbiAgICAgICAgICAgICAgICBkYXRhLnR5cGUgPSBWYWx1ZVR5cGUuQm9vbGVhblxuICAgICAgICAgICAgICAgIHJldHVybiB2YWx1ZXNcbiAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICBdLFxuICAgICAgICAgICAgW1xuICAgICAgICAgICAgICBhdWRpdEtleVZhbHVlRGljdGlvbmFyeShhdWRpdERhdGUsIFtcbiAgICAgICAgICAgICAgICBhdWRpdFJhd1ZhbHVlT3JWYWx1ZU9yRXhwZWN0ZWRUb0VkaXRhYmxlKGF1ZGl0TnVtYmVyKSxcbiAgICAgICAgICAgICAgICBhdWRpdFJlcXVpcmUsXG4gICAgICAgICAgICAgIF0pLFxuICAgICAgICAgICAgICBhdWRpdEZ1bmN0aW9uKCh2YWx1ZXMpID0+IHtcbiAgICAgICAgICAgICAgICBkYXRhLnR5cGUgPSBWYWx1ZVR5cGUuTnVtYmVyXG4gICAgICAgICAgICAgICAgcmV0dXJuIHZhbHVlc1xuICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgIF0sXG4gICAgICAgICAgICBbXG4gICAgICAgICAgICAgIGF1ZGl0S2V5VmFsdWVEaWN0aW9uYXJ5KGF1ZGl0RGF0ZSwgW1xuICAgICAgICAgICAgICAgIGF1ZGl0UmF3VmFsdWVPclZhbHVlT3JFeHBlY3RlZFRvRWRpdGFibGUoXG4gICAgICAgICAgICAgICAgICBhdWRpdEFycmF5KGF1ZGl0U3RyaW5nKSxcbiAgICAgICAgICAgICAgICApLFxuICAgICAgICAgICAgICAgIGF1ZGl0UmVxdWlyZSxcbiAgICAgICAgICAgICAgXSksXG4gICAgICAgICAgICAgIGF1ZGl0RnVuY3Rpb24oKHZhbHVlcykgPT4ge1xuICAgICAgICAgICAgICAgIGRhdGEudHlwZSA9IFZhbHVlVHlwZS5TdHJpbmdBcnJheVxuICAgICAgICAgICAgICAgIHJldHVybiB2YWx1ZXNcbiAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICBdLFxuICAgICAgICAgICAgW1xuICAgICAgICAgICAgICBhdWRpdEtleVZhbHVlRGljdGlvbmFyeShhdWRpdERhdGUsIFtcbiAgICAgICAgICAgICAgICBhdWRpdFJhd1ZhbHVlT3JWYWx1ZU9yRXhwZWN0ZWRUb0VkaXRhYmxlKFxuICAgICAgICAgICAgICAgICAgYXVkaXRLZXlWYWx1ZURpY3Rpb25hcnkoYXVkaXRTdHJpbmcsIGF1ZGl0U3RyaW5nKSxcbiAgICAgICAgICAgICAgICApLFxuICAgICAgICAgICAgICAgIGF1ZGl0UmVxdWlyZSxcbiAgICAgICAgICAgICAgXSksXG4gICAgICAgICAgICAgIGF1ZGl0RnVuY3Rpb24oKHZhbHVlcykgPT4ge1xuICAgICAgICAgICAgICAgIGRhdGEudHlwZSA9IFZhbHVlVHlwZS5TdHJpbmdCeVN0cmluZ1xuICAgICAgICAgICAgICAgIHJldHVybiB2YWx1ZXNcbiAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICBdLFxuICAgICAgICAgICksXG4gICAgICAgICAgYXVkaXRSZXF1aXJlLFxuICAgICAgICApXG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBEYXRhIGlzIGEgTm9kZVBhcmFtZXRlci5cbiAgICAgICAgZGF0YS5jbGFzcyA9IFBhcmFtZXRlckNsYXNzLk5vZGVcblxuICAgICAgICAvLyBSZXBhaXIgZGF0YSBiZWZvcmUgdmFsaWRhdGlvbi5cbiAgICAgICAgLy8gQ3JlYXRlIGBjaGlsZHJlbmAgYXR0cmlidXRlIGZyb20gY2hpbGRyZW4ga2V5cy5cbiAgICAgICAgY29uc3QgY2hpbGRyZW46IHsgW2tleTogc3RyaW5nXTogdW5rbm93biB9ID0ge31cbiAgICAgICAgZm9yIChjb25zdCBrZXkgb2YgY2hpbGRyZW5LZXlzKSB7XG4gICAgICAgICAgY2hpbGRyZW5ba2V5XSA9IGRhdGFba2V5XVxuICAgICAgICAgIGRlbGV0ZSBkYXRhW2tleV1cbiAgICAgICAgICByZW1haW5pbmdLZXlzLmRlbGV0ZShrZXkpXG4gICAgICAgIH1cbiAgICAgICAgZGF0YS5jaGlsZHJlbiA9IGNoaWxkcmVuXG5cbiAgICAgICAgYXVkaXQuYXR0cmlidXRlKFxuICAgICAgICAgIGRhdGEsXG4gICAgICAgICAgXCJjaGlsZHJlblwiLFxuICAgICAgICAgIHRydWUsXG4gICAgICAgICAgZXJyb3JzLFxuICAgICAgICAgIHJlbWFpbmluZ0tleXMsXG4gICAgICAgICAgYXVkaXRLZXlWYWx1ZURpY3Rpb25hcnkoYXVkaXRTdHJpbmcsIFtcbiAgICAgICAgICAgIGF1ZGl0UmF3UGFyYW1ldGVyVG9FZGl0YWJsZSh1bml0cyksXG4gICAgICAgICAgICBhdWRpdFJlcXVpcmUsXG4gICAgICAgICAgXSksXG4gICAgICAgICAgLy8gYXVkaXRSZXF1aXJlLCAvLyBBIG5vZGUgbWF5IGhhdmUgbm8gY2hpbGQgKGVzcGVjaWFsbHkgdW5wcm9jZXNzZWQgbm9kZXMpLlxuICAgICAgICApXG4gICAgICAgIGlmIChcbiAgICAgICAgICBlcnJvcnMuY2hpbGRyZW4gIT09IHVuZGVmaW5lZCAmJlxuICAgICAgICAgIHR5cGVvZiBlcnJvcnMuY2hpbGRyZW4gPT09IFwib2JqZWN0XCJcbiAgICAgICAgKSB7XG4gICAgICAgICAgT2JqZWN0LmFzc2lnbihlcnJvcnMsIGVycm9ycy5jaGlsZHJlbilcbiAgICAgICAgICBkZWxldGUgZXJyb3JzLmNoaWxkcmVuXG4gICAgICAgIH1cblxuICAgICAgICBhdWRpdC5hdHRyaWJ1dGUoXG4gICAgICAgICAgZGF0YSxcbiAgICAgICAgICBcIm1ldGFkYXRhXCIsXG4gICAgICAgICAgdHJ1ZSxcbiAgICAgICAgICBlcnJvcnMsXG4gICAgICAgICAgcmVtYWluaW5nS2V5cyxcbiAgICAgICAgICBhdWRpdFJhd05vZGVQYXJhbWV0ZXJNZXRhZGF0YVRvRWRpdGFibGUoZGF0YSwgdW5pdHMsIGNoaWxkcmVuSWQpLFxuICAgICAgICApXG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGVycm9ycy5tZXRhZGF0YSA9PT0gdW5kZWZpbmVkICYmIGRhdGEubWV0YWRhdGEgIT0gbnVsbCkge1xuICAgICAgLy8gTWVyZ2UgbWV0YWRhdGEgaW50byBwYXJhbWV0ZXIuXG4gICAgICBjb25zdCBtZXRhZGF0YUVycm9yczogeyBba2V5OiBzdHJpbmddOiB1bmtub3duIH0gPSB7fVxuICAgICAgZm9yIChjb25zdCBba2V5LCBtZXRhZGF0YVZhbHVlXSBvZiBPYmplY3QuZW50cmllcyhcbiAgICAgICAgZGF0YS5tZXRhZGF0YSBhcyB7IFtrZXk6IHN0cmluZ106IHVua25vd24gfSxcbiAgICAgICkpIHtcbiAgICAgICAgY29uc3QgZGF0YVZhbHVlID0gZGF0YVtrZXldXG4gICAgICAgIGlmIChtZXRhZGF0YVZhbHVlICE9PSBkYXRhVmFsdWUpIHtcbiAgICAgICAgICBpZiAoZGF0YVZhbHVlID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGRhdGFba2V5XSA9IG1ldGFkYXRhVmFsdWVcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgbWV0YWRhdGFFcnJvcnNba2V5XSA9XG4gICAgICAgICAgICAgIFwiRmllbGQgaXMgcHJlc2VudCBib3RoIGluIHBhcmFtZXRlciBhbmQgaXRzIG1ldGFkYXRhLCB3aXRoIGRpZmZlcmVudCB2YWx1ZXNcIlxuICAgICAgICAgI