@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
JavaScript
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