@leosprograms/vf-graphql-holochain
Version:
GraphQL schema bindings for the Holochain implementation of ValueFlows
400 lines • 57.8 kB
JavaScript
/**
* Connection wrapper for Holochain DNA method calls
*
* :TODO: :WARNING:
*
* This layer is currently unsuitable for mixing with DNAs that use dna-local identifier formats, and
* will cause encoding errors if 2-element lists of identifiers are passed.
*
* Such tuples are interpreted as [`DnaHash`, `AnyDhtHash`] pairs by the GraphQL <-> Holochain
* serialisation layer and transformed into compound IDs at I/O time. So, this adapter should
* *only* be used to wrap DNAs explicitly developed with multi-DNA references in mind.
*
* Also :TODO: - standardise a binary format for universally unique Holochain entry/header identifiers.
*
* @package: hREA
* @since: 2019-05-20
*/
import { AppWebsocket, AdminWebsocket, CellType } from '@holochain/client';
import deepForEach from 'deep-for-each';
import isObject from 'is-object';
import { Buffer } from 'buffer';
import { format, parse } from 'fecha';
import { fromByteArray, toByteArray } from 'base64-js';
//----------------------------------------------------------------------------------------------------------------------
// Connection persistence and multi-conductor / multi-agent handling
//----------------------------------------------------------------------------------------------------------------------
// :NOTE: when calling AppWebsocket.connect for the Launcher Context
// it just expects an empty string for the socketURI. Other environments require it.
let ENV_CONNECTION_URI = process.env.REACT_APP_HC_CONN_URL || '';
let ENV_ADMIN_CONNECTION_URI = process.env.REACT_APP_HC_ADMIN_CONN_URL || '';
let ENV_HOLOCHAIN_APP_ID = process.env.REACT_APP_HC_APP_ID || '';
const CONNECTION_CACHE = {};
const APP_AGENT_CONNECTION_CACHE = {};
/**
* If no `conductorUri` is provided or is otherwise empty or undefined,
* a connection is attempted via the `REACT_APP_HC_CONN_URL` environment variable.
* Only if running in a Holochain Launcher context, can both of the before-mentioned values
* be left undefined or empty, and the websocket connection can still be established.
*/
export async function autoConnect(weaveAppAgentClient, conductorUri, adminConductorUri, appID, traceAppSignals, origin) {
console.log(`Auto-connect to Holochain conductor: ${conductorUri}, admin: ${adminConductorUri}, appID: ${appID}, origin: ${origin}`);
conductorUri = conductorUri || ENV_CONNECTION_URI;
adminConductorUri = adminConductorUri || ENV_ADMIN_CONNECTION_URI;
if (weaveAppAgentClient) {
await openWeaveConnection(conductorUri, weaveAppAgentClient, traceAppSignals);
const { dnaConfig, appId: realAppId, } = await sniffHolochainAppCells(weaveAppAgentClient.appWebsocket, appID);
return {
conn: weaveAppAgentClient,
adminConn: null,
dnaConfig,
conductorUri,
adminConductorUri,
appId: appID
};
}
let adminConn = null;
let token;
if (adminConductorUri && appID) {
if (origin) {
adminConn = await AdminWebsocket.connect({ url: adminConductorUri, wsClientOptions: { origin: origin }, defaultTimeout: 999999999 });
}
else {
adminConn = await AdminWebsocket.connect({ url: adminConductorUri, defaultTimeout: 999999999 });
}
let tokenResp = await adminConn.issueAppAuthenticationToken({
installed_app_id: appID,
});
token = tokenResp.token;
}
let conn;
if (origin) {
console.log(`Holochain connection to ${conductorUri} with origin ${origin}`);
if (token) {
conn = await openConnection(conductorUri, traceAppSignals, token, origin);
}
else {
conn = await openConnection(conductorUri, traceAppSignals, null, origin);
}
}
else {
console.log(`Holochain connection to ${conductorUri} without origin`);
if (token) {
conn = await openConnection(conductorUri, traceAppSignals, token);
}
else {
conn = await openConnection(conductorUri, traceAppSignals);
}
}
const { dnaConfig, appId: realAppId, } = await sniffHolochainAppCells(conn, appID);
if (adminConn) {
for await (let cellId of Object.values(dnaConfig)) {
await adminConn.authorizeSigningCredentials(cellId);
}
}
return {
conn,
adminConn,
dnaConfig,
conductorUri,
adminConductorUri,
appId: realAppId
};
}
/**
* Inits a connection for the given weave client.
*/
export const openWeaveConnection = (appSocketURI, appAgentClient, traceAppSignals) => {
console.log(`Save Holochain connection from openWeaveConnection:`, appAgentClient);
APP_AGENT_CONNECTION_CACHE[appSocketURI] = Promise.resolve(appAgentClient);
console.log(`Holochain saved to ${APP_AGENT_CONNECTION_CACHE[appSocketURI]} OK from openWeaveConnection`);
return APP_AGENT_CONNECTION_CACHE[appSocketURI];
};
/**
* Inits a connection for the given websocket URI.
*
* This method gives calling code an opportunity to register globals for all future
* instances of a connection of the same `socketURI`. To ensure this is done reliably,
* a runtime error will be thrown by `getConnection` if no `openConnection` has
* been previously performed for the same `socketURI`.
*/
export const openConnection = (appSocketURI, traceAppSignals, token, origin) => {
console.log(`Init Holochain connection: ${appSocketURI}, origin: ${origin}`);
if (origin) {
console.log(`Holochain connection to ${appSocketURI} with origin ${origin}`);
CONNECTION_CACHE[appSocketURI] = AppWebsocket.connect({ url: appSocketURI, wsClientOptions: { origin: origin }, token: token })
.then((client) => {
console.log(`Holochain connection to ${appSocketURI} OK`);
if (traceAppSignals) {
client.on('signal', traceAppSignals);
}
return client;
});
return CONNECTION_CACHE[appSocketURI];
}
else {
console.log(`Holochain connection to ${appSocketURI} without origin`);
CONNECTION_CACHE[appSocketURI] = AppWebsocket.connect({ url: appSocketURI, token: token })
.then((client) => {
console.log(`Holochain connection to ${appSocketURI} OK`);
if (traceAppSignals) {
client.on('signal', traceAppSignals);
}
return client;
});
return CONNECTION_CACHE[appSocketURI];
}
};
const getConnection = (appSocketURI) => {
if (!CONNECTION_CACHE[appSocketURI]) {
throw new Error(`Connection for ${appSocketURI} not initialised! Please call openConnection() first.`);
}
return CONNECTION_CACHE[appSocketURI];
};
const getWeaveConnection = (appSocketURI) => {
if (!APP_AGENT_CONNECTION_CACHE[appSocketURI]) {
throw new Error(`Connection for ${appSocketURI} not initialised! Please call openConnection() first.`);
}
// console.log(`Holochain connection from getWeaveConnection:`, APP_AGENT_CONNECTION_CACHE[appSocketURI])
return APP_AGENT_CONNECTION_CACHE[appSocketURI];
};
/**
* Introspect an active Holochain connection's app cells to determine cell IDs
* for mapping to the schema resolvers.
* If no `appId` is provided or is otherwise empty or undefined,
* it will try to use the `REACT_APP_HC_APP_ID` environment variable.
* Only if running in a Holochain Launcher context, can both of the before-mentioned values
* be left undefined or empty, and the AppWebsocket will know which appId to introspect into.
*/
export async function sniffHolochainAppCells(conn, appId) {
// console.log("sniff holochain app cells", conn, appId)
// use the default set by the environment variable
// and furthermore, note that both of these will be ignored
// in the Holochain Launcher context
// which will override any given value to the AppWebsocket
// for installed_app_id
appId = appId || ENV_HOLOCHAIN_APP_ID;
const appInfo = await conn.appInfo();
if (!appInfo) {
throw new Error(`appInfo call failed for Holochain app '${appId}' - ensure the name is correct and that the app installation has succeeded`);
}
let dnaConfig = {};
Object.entries(appInfo.cell_info).forEach(([roleName, cellInfos]) => {
// this is the "magic pattern" of having for
// example the "agreement" DNA, it should have
// an assigned "role_name" in the happ of
// "hrea_agreement_1" or "hrea_observation_2"
// and the middle section should match the expected name
// for DNAIdMappings, which are also used during zome calls
const hrea_cell_match = roleName.match(/hrea_(\w+)_\d+/);
if (!hrea_cell_match)
return;
const hreaRole = hrea_cell_match[1];
if (cellInfos) {
const firstCell = cellInfos[0];
if (CellType.Provisioned in firstCell) {
dnaConfig[hreaRole] = firstCell[CellType.Provisioned].cell_id;
}
}
});
console.info('Connecting to detected Holochain cells:', dnaConfig);
return {
dnaConfig,
appId,
};
}
//----------------------------------------------------------------------------------------------------------------------
// Holochain / GraphQL type translation layer
//----------------------------------------------------------------------------------------------------------------------
// @see https://crates.io/crates/holo_hash
const HOLOCHAIN_IDENTIFIER_LEN = 39;
// @see holo_hash::hash_type::primitive
const HOLOHASH_PREFIX_DNA = [0x84, 0x2d, 0x24]; // uhC0k
const HOLOHASH_PREFIX_ENTRY = [0x84, 0x21, 0x24]; // uhCEk
const HOLOHASH_PREFIX_HEADER = [0x84, 0x29, 0x24]; // uhCkk
const HOLOHASH_PREFIX_AGENT = [0x84, 0x20, 0x24]; // uhCAk
const serializedHashMatchRegex = /^[A-Za-z0-9_+\-/]{53}={0,2}$/;
const idMatchRegex = /^[A-Za-z0-9_+\-/]{53}={0,2}:[A-Za-z0-9_+\-/]{53}={0,2}$/;
// something like
// $:uhC0k1mcUqQIbtT0mkdTldhBaAvR6KlKxIV2IYwJemHt-NO92uXG5
// or kg:uhC0k1mcUqQIbtT0mkdTldhBaAvR6KlKxIV2IYwJemHt-NO92uXG5
// but not 9:uhC0k1mcUqQIbtT0mkdTldhBaAvR6KlKxIV2IYwJemHt-NO92uXG5 (i.e. no digits in the id)
const stringIdRegex = /^\D+?:[A-Za-z0-9_+\-/]{53}={0,2}$/;
// @see https://github.com/holochain-open-dev/core-types/blob/main/src/utils.ts
export function deserializeHash(hash) {
// return Base64.toUint8Array(hash.slice(1))
return toByteArray(hash.slice(1));
}
export function deserializeId(field) {
const matches = field.split(':');
return [
Buffer.from(deserializeHash(matches[1])),
Buffer.from(deserializeHash(matches[0])),
];
}
function deserializeStringId(field) {
const matches = field.split(':');
return [
Buffer.from(deserializeHash(matches[1])),
matches[0],
];
}
// @see https://github.com/holochain-open-dev/core-types/blob/main/src/utils.ts
export function serializeHash(hash) {
// return `u${Base64.fromUint8Array(hash, true)}`
return `u${fromByteArray(hash)}`;
}
function serializeId(id) {
return `${serializeHash(id[1])}:${serializeHash(id[0])}`;
}
function seralizeStringId(id) {
return `${id[1]}:${serializeHash(id[0])}`;
}
// Construct appropriate IDs for records in associated DNAs by substituting
// the CellId portion of the ID with that of an appropriate destination record
export function remapCellId(originalId, newCellId) {
const [origId, _origCell] = originalId.split(':');
return `${origId}:${newCellId.split(':')[1]}`;
}
const LONG_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
const SHORT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
const isoDateRegex = /^\d{4}-\d\d-\d\d(T\d\d:\d\d:\d\d(\.\d\d\d)?)?([+-]\d\d:\d\d)?$/;
/**
* Decode raw data input coming from Holochain API websocket.
*
* Mutates in place- we have no need for the non-normalised primitive format and this saves memory.
*/
const decodeFields = (result) => {
deepForEach(result, (value, prop, subject) => {
// ActionHash or AgentPubKey
if ((value instanceof Buffer || value instanceof Uint8Array) && value.length === HOLOCHAIN_IDENTIFIER_LEN &&
(checkLeadingBytes(value, HOLOHASH_PREFIX_HEADER) || checkLeadingBytes(value, HOLOHASH_PREFIX_AGENT))) {
subject[prop] = serializeHash(value);
}
// RecordId | StringId (Agent, for now)
if (Array.isArray(value) && value.length == 2 &&
(value[0] instanceof Buffer || value[0] instanceof Uint8Array) &&
value[0].length === HOLOCHAIN_IDENTIFIER_LEN &&
checkLeadingBytes(value[0], HOLOHASH_PREFIX_DNA)) {
// Match 2-element arrays of Buffer objects as IDs.
// Since we check the hash prefixes, this should make it safe to mix with fields which reference arrays of plain EntryHash / ActionHash data.
if ((value[1] instanceof Buffer || value[1] instanceof Uint8Array) && value[1].length === HOLOCHAIN_IDENTIFIER_LEN &&
(checkLeadingBytes(value[1], HOLOHASH_PREFIX_ENTRY) || checkLeadingBytes(value[1], HOLOHASH_PREFIX_HEADER) || checkLeadingBytes(value[1], HOLOHASH_PREFIX_AGENT))) {
subject[prop] = serializeId(value);
// Match 2-element pairs of Buffer/String as a "DNA-scoped identifier" (eg. UnitId)
// :TODO: This one probably isn't safe for regular ID field mixing.
// Custom serde de/serializer would make bind this handling to the appropriate fields without duck-typing issues.
}
else {
subject[prop] = seralizeStringId(value);
}
}
// recursively check for Date strings and convert to JS date objects upon receiving
if (value && value.match && value.match(isoDateRegex)) {
subject[prop] = parse(value, LONG_DATETIME_FORMAT);
if (subject[prop] === null) {
subject[prop] = parse(value, SHORT_DATETIME_FORMAT);
}
}
});
};
function checkLeadingBytes(ofVar, against) {
return ofVar[0] === against[0] &&
ofVar[1] === against[1] &&
ofVar[2] === against[2];
}
/**
* Encode application runtime data into serialisable format for transmitting to API websocket.
*
* Clones data in order to keep input data pristine.
*/
const encodeFields = (args) => {
if (!args)
return args;
let res = args;
// encode dates as ISO8601 DateTime strings
if (args instanceof Date) {
return format(args, LONG_DATETIME_FORMAT);
}
// deserialise any identifiers back to their binary format
else if (args.match && args.match(serializedHashMatchRegex)) {
return deserializeHash(args);
}
else if (args.match && args.match(idMatchRegex)) {
return deserializeId(args);
}
else if (args.match && args.match(stringIdRegex)) {
return deserializeStringId(args);
}
// recurse into child fields
else if (Array.isArray(args)) {
res = [];
args.forEach((value, key) => {
res[key] = encodeFields(value);
});
}
else if (isObject(args)) {
res = {};
for (const key in args) {
res[key] = encodeFields(args[key]);
}
}
return res;
};
/**
* Higher-order function to generate async functions for calling zome RPC methods
*/
const zomeFunction = (socketURI, cell_id, zome_name, fn_name, skipEncodeDecode) => async (args) => {
// const startTime = new Date().getTime()
// console.log(`Calling zome function ${fn_name} at time ${startTime}`)
let noWeaveSocket = !APP_AGENT_CONNECTION_CACHE[socketURI];
if (!noWeaveSocket) {
const appAgentClient = await getWeaveConnection(socketURI);
const res = await appAgentClient.callZome({
cell_id,
zome_name,
fn_name,
payload: skipEncodeDecode ? args : encodeFields(args),
}, 1200000); //20 minute timeout
if (!skipEncodeDecode)
decodeFields(res);
return res;
}
else {
const midtime1 = new Date().getTime();
const appAgentWebsocket = await getConnection(socketURI);
const midTime2 = new Date().getTime();
const res = await appAgentWebsocket.callZome({
cell_id,
zome_name,
fn_name,
provenance: cell_id[1],
payload: skipEncodeDecode ? args : encodeFields(args),
}, 1200000); //20 minute timeout
if (!skipEncodeDecode)
decodeFields(res);
// const endTime = new Date().getTime()
// console.log('Done calling zome function at time', endTime)
// console.log(`Done calling zome function ${fn_name} in time ${(endTime - startTime) / 1000}`)
return res;
}
};
/**
* External API for accessing zome methods, passing them through an optional intermediary DNA ID mapping
*
* @param mappings DNAIdMappings to use for this collaboration space.
* `instance` must be present in the mapping, and the mapped CellId will be used instead of `instance` itself.
* @param socketURI If provided, connects to the Holochain conductor on a different URI.
*
* @return bound async zome function which can be called directly
*/
export const mapZomeFn = (mappings, socketURI, instance, zome, fn, skipEncodeDecode) => {
return zomeFunction(socketURI, (mappings && mappings[instance]), zome, fn, skipEncodeDecode);
};
export const extractEdges = (withEdges) => {
if (!withEdges.edges || !withEdges.edges.length) {
return [];
}
return withEdges.edges.map(({ node }) => node);
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29ubmVjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL2Nvbm5lY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7R0FnQkc7QUFFSCxPQUFPLEVBQVksWUFBWSxFQUFFLGNBQWMsRUFBVSxRQUFRLEVBQXVCLE1BQU0sbUJBQW1CLENBQUE7QUFDakgsT0FBTyxXQUFXLE1BQU0sZUFBZSxDQUFBO0FBQ3ZDLE9BQU8sUUFBUSxNQUFNLFdBQVcsQ0FBQTtBQUNoQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sUUFBUSxDQUFBO0FBQy9CLE9BQU8sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sT0FBTyxDQUFBO0FBRXJDLE9BQU8sRUFBRSxhQUFhLEVBQUUsV0FBVyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBSXZELHdIQUF3SDtBQUN4SCxvRUFBb0U7QUFDcEUsd0hBQXdIO0FBRXhILG9FQUFvRTtBQUNwRSxvRkFBb0Y7QUFDcEYsSUFBSSxrQkFBa0IsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUErQixJQUFJLEVBQUUsQ0FBQTtBQUMxRSxJQUFJLHdCQUF3QixHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQXFDLElBQUksRUFBRSxDQUFBO0FBQ3RGLElBQUksb0JBQW9CLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBNkIsSUFBSSxFQUFFLENBQUE7QUFFMUUsTUFBTSxnQkFBZ0IsR0FBMkMsRUFBRSxDQUFBO0FBQ25FLE1BQU0sMEJBQTBCLEdBQXdDLEVBQUUsQ0FBQTtBQUUxRTs7Ozs7R0FLRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsV0FBVyxDQUFDLG1CQUF5QixFQUFFLFlBQXFCLEVBQUUsaUJBQTBCLEVBQUUsS0FBYyxFQUFFLGVBQTBCLEVBQUUsTUFBZTtJQUN6SyxPQUFPLENBQUMsR0FBRyxDQUFDLHdDQUF3QyxZQUFZLFlBQVksaUJBQWlCLFlBQVksS0FBSyxh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QUFVLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztZQUN0QixPQUFPLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQztTQUN0RCxFQUFFLE9BQU8sQ0FBQyxDQUFBLENBQUMsbUJBQW1CO1FBQy9CLElBQUksQ0FBQyxnQkFBZ0I7WUFBRSxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUE7UUFFeEMsdUNBQXVDO1FBQ3ZDLDZEQUE2RDtRQUM3RCwrRkFBK0Y7UUFDL0YsT0FBTyxHQUFHLENBQUE7S0FDWDtBQUNILENBQUMsQ0FBQTtBQUVEOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFHLENBQXdCLFFBQXVCLEVBQUUsU0FBaUIsRUFBRSxRQUFnQixFQUFFLElBQVksRUFBRSxFQUFVLEVBQUUsZ0JBQTBCLEVBQUUsRUFBRTtJQUNySyxPQUFPLFlBQVksQ0FBd0IsU0FBUyxFQUFFLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQTtBQUNySCxDQUFDLENBQUE7QUFHRCxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsQ0FBSSxTQUFtQyxFQUFPLEVBQUU7SUFDMUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRTtRQUMvQyxPQUFPLEVBQUUsQ0FBQTtLQUNWO0lBQ0QsT0FBTyxTQUFTLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFBO0FBQ2hELENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29ubmVjdGlvbiB3cmFwcGVyIGZvciBIb2xvY2hhaW4gRE5BIG1ldGhvZCBjYWxsc1xuICpcbiAqIDpUT0RPOiA6V0FSTklORzpcbiAqXG4gKiBUaGlzIGxheWVyIGlzIGN1cnJlbnRseSB1bnN1aXRhYmxlIGZvciBtaXhpbmcgd2l0aCBETkFzIHRoYXQgdXNlIGRuYS1sb2NhbCBpZGVudGlmaWVyIGZvcm1hdHMsIGFuZFxuICogd2lsbCBjYXVzZSBlbmNvZGluZyBlcnJvcnMgaWYgMi1lbGVtZW50IGxpc3RzIG9mIGlkZW50aWZpZXJzIGFyZSBwYXNzZWQuXG4gKlxuICogU3VjaCB0dXBsZXMgYXJlIGludGVycHJldGVkIGFzIFtgRG5hSGFzaGAsIGBBbnlEaHRIYXNoYF0gcGFpcnMgYnkgdGhlIEdyYXBoUUwgPC0+IEhvbG9jaGFpblxuICogc2VyaWFsaXNhdGlvbiBsYXllciBhbmQgdHJhbnNmb3JtZWQgaW50byBjb21wb3VuZCBJRHMgYXQgSS9PIHRpbWUuIFNvLCB0aGlzIGFkYXB0ZXIgc2hvdWxkXG4gKiAqb25seSogYmUgdXNlZCB0byB3cmFwIEROQXMgZXhwbGljaXRseSBkZXZlbG9wZWQgd2l0aCBtdWx0aS1ETkEgcmVmZXJlbmNlcyBpbiBtaW5kLlxuICpcbiAqIEFsc28gOlRPRE86IC0gc3RhbmRhcmRpc2UgYSBiaW5hcnkgZm9ybWF0IGZvciB1bml2ZXJzYWxseSB1bmlxdWUgSG9sb2NoYWluIGVudHJ5L2hlYWRlciBpZGVudGlmaWVycy5cbiAqXG4gKiBAcGFja2FnZTogaFJFQVxuICogQHNpbmNlOiAgIDIwMTktMDUtMjBcbiAqL1xuXG5pbXBvcnQgeyBTaWduYWxDYiwgQXBwV2Vic29ja2V0LCBBZG1pbldlYnNvY2tldCwgQ2VsbElkLCBDZWxsVHlwZSwgSG9sb0hhc2gsIEFwcENsaWVudCB9IGZyb20gJ0Bob2xvY2hhaW4vY2xpZW50J1xuaW1wb3J0IGRlZXBGb3JFYWNoIGZyb20gJ2RlZXAtZm9yLWVhY2gnXG5pbXBvcnQgaXNPYmplY3QgZnJvbSAnaXMtb2JqZWN0J1xuaW1wb3J0IHsgQnVmZmVyIH0gZnJvbSAnYnVmZmVyJ1xuaW1wb3J0IHsgZm9ybWF0LCBwYXJzZSB9IGZyb20gJ2ZlY2hhJ1xuaW1wb3J0IHsgRE5BSWRNYXBwaW5ncyB9IGZyb20gJy4vdHlwZXMnXG5pbXBvcnQgeyBmcm9tQnl0ZUFycmF5LCB0b0J5dGVBcnJheSB9IGZyb20gJ2Jhc2U2NC1qcyc7XG5cbnR5cGUgUmVjb3JkSWQgPSBbSG9sb0hhc2gsIEhvbG9IYXNoXVxuXG4vLy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbi8vIENvbm5lY3Rpb24gcGVyc2lzdGVuY2UgYW5kIG11bHRpLWNvbmR1Y3RvciAvIG11bHRpLWFnZW50IGhhbmRsaW5nXG4vLy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLy8gOk5PVEU6IHdoZW4gY2FsbGluZyBBcHBXZWJzb2NrZXQuY29ubmVjdCBmb3IgdGhlIExhdW5jaGVyIENvbnRleHRcbi8vIGl0IGp1c3QgZXhwZWN0cyBhbiBlbXB0eSBzdHJpbmcgZm9yIHRoZSBzb2NrZXRVUkkuIE90aGVyIGVudmlyb25tZW50cyByZXF1aXJlIGl0LlxubGV0IEVOVl9DT05ORUNUSU9OX1VSSSA9IHByb2Nlc3MuZW52LlJFQUNUX0FQUF9IQ19DT05OX1VSTCBhcyBzdHJpbmcgfHwgJydcbmxldCBFTlZfQURNSU5fQ09OTkVDVElPTl9VUkkgPSBwcm9jZXNzLmVudi5SRUFDVF9BUFBfSENfQURNSU5fQ09OTl9VUkwgYXMgc3RyaW5nIHx8ICcnXG5sZXQgRU5WX0hPTE9DSEFJTl9BUFBfSUQgPSBwcm9jZXNzLmVudi5SRUFDVF9BUFBfSENfQVBQX0lEIGFzIHN0cmluZyB8fCAnJ1xuXG5jb25zdCBDT05ORUNUSU9OX0NBQ0hFOiB7IFtpOiBzdHJpbmddOiBQcm9taXNlPEFwcFdlYnNvY2tldD4gfSA9IHt9XG5jb25zdCBBUFBfQUdFTlRfQ09OTkVDVElPTl9DQUNIRTogeyBbaTogc3RyaW5nXTogUHJvbWlzZTxBcHBDbGllbnQ+IH0gPSB7fVxuXG4vKipcbiAqIElmIG5vIGBjb25kdWN0b3JVcmlgIGlzIHByb3ZpZGVkIG9yIGlzIG90aGVyd2lzZSBlbXB0eSBvciB1bmRlZmluZWQsXG4gKiBhIGNvbm5lY3Rpb24gaXMgYXR0ZW1wdGVkIHZpYSB0aGUgYFJFQUNUX0FQUF9IQ19DT05OX1VSTGAgZW52aXJvbm1lbnQgdmFyaWFibGUuXG4gKiBPbmx5IGlmIHJ1bm5pbmcgaW4gYSBIb2xvY2hhaW4gTGF1bmNoZXIgY29udGV4dCwgY2FuIGJvdGggb2YgdGhlIGJlZm9yZS1tZW50aW9uZWQgdmFsdWVzXG4gKiBiZSBsZWZ0IHVuZGVmaW5lZCBvciBlbXB0eSwgYW5kIHRoZSB3ZWJzb2NrZXQgY29ubmVjdGlvbiBjYW4gc3RpbGwgYmUgZXN0YWJsaXNoZWQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBhdXRvQ29ubmVjdCh3ZWF2ZUFwcEFnZW50Q2xpZW50PzogYW55LCBjb25kdWN0b3JVcmk/OiBzdHJpbmcsIGFkbWluQ29uZHVjdG9yVXJpPzogc3RyaW5nLCBhcHBJRD86IHN0cmluZywgdHJhY2VBcHBTaWduYWxzPzogU2lnbmFsQ2IsIG9yaWdpbj86IHN0cmluZykge1xuICBjb25zb2xlLmxvZyhgQXV0by1jb25uZWN0IHRvIEhvbG9jaGFpbiBjb25kdWN0b3I6ICR7Y29uZHVjdG9yVXJpfSwgYWRtaW46ICR7YWRtaW5Db25kdWN0b3JVcml9LCBhcHBJRDogJHthcHBJRH0sIG9yaWdpbjogJHtvcmlnaW59YClcbiAgY29uZHVjdG9yVXJpID0gY29uZHVjdG9yVXJpIHx8IEVOVl9DT05ORUNUSU9OX1VSSVxuICBhZG1pbkNvbmR1Y3RvclVyaSA9IGFkbWluQ29uZHVjdG9yVXJpIHx8IEVOVl9BRE1JTl9DT05ORUNUSU9OX1VSSVxuXG4gIGlmICh3ZWF2ZUFwcEFnZW50Q2xpZW50KSB7XG4gICBhd2FpdCBvcGVuV2VhdmVDb25uZWN0aW9uKGNvbmR1Y3RvclVyaSwgd2VhdmVBcHBBZ2VudENsaWVudCwgdHJhY2VBcHBTaWduYWxzKVxuICAgY29uc3Qge1xuICAgIGRuYUNvbmZpZyxcbiAgICBhcHBJZDogcmVhbEFwcElkLFxuICAgfSA9IGF3YWl0IHNuaWZmSG9sb2NoYWluQXBwQ2VsbHMod2VhdmVBcHBBZ2VudENsaWVudC5hcHBXZWJzb2NrZXQsIGFwcElEKVxuICAgXG4gICByZXR1cm4ge1xuICAgICBjb25uOiB3ZWF2ZUFwcEFnZW50Q2xpZW50LFxuICAgICBhZG1pbkNvbm46IG51bGwsXG4gICAgIGRuYUNvbmZpZyxcbiAgICAgY29uZHVjdG9yVXJpLFxuICAgICBhZG1pbkNvbmR1Y3RvclVyaSxcbiAgICAgYXBwSWQ6IGFwcElEXG4gICB9XG4gIH1cblxuICBsZXQgYWRtaW5Db25uOiBBZG1pbldlYnNvY2tldCB8IG51bGwgPSBudWxsXG4gIGxldCB0b2tlbjtcbiAgaWYgKGFkbWluQ29uZHVjdG9yVXJpICYmIGFwcElEKSB7XG4gICAgaWYgKG9yaWdpbikge1xuICAgICAgYWRtaW5Db25uID0gYXdhaXQgQWRtaW5XZWJzb2NrZXQuY29ubmVjdCh7dXJsOiBhZG1pbkNvbmR1Y3RvclVyaSwgd3NDbGllbnRPcHRpb25zOiB7IG9yaWdpbjogb3JpZ2lufSwgZGVmYXVsdFRpbWVvdXQ6IDk5OTk5OTk5OX0pXG4gICAgfSBlbHNlIHtcbiAgICAgIGFkbWluQ29ubiA9IGF3YWl0IEFkbWluV2Vic29ja2V0LmNvbm5lY3Qoe3VybDogYWRtaW5Db25kdWN0b3JVcmksIGRlZmF1bHRUaW1lb3V0OiA5OTk5OTk5OTl9KVxuICAgIH1cblxuICAgIGxldCB0b2tlblJlc3AgPSBhd2FpdCBhZG1pbkNvbm4uaXNzdWVBcHBBdXRoZW50aWNhdGlvblRva2VuKHtcbiAgICAgIGluc3RhbGxlZF9hcHBfaWQ6IGFwcElELFxuICAgIH0pO1xuICAgIHRva2VuID0gdG9rZW5SZXNwLnRva2VuO1xuICB9XG5cblxuICBsZXQgY29ubjtcbiAgaWYgKG9yaWdpbikge1xuICAgIGNvbnNvbGUubG9nKGBIb2xvY2hhaW4gY29ubmVjdGlvbiB0byAke2NvbmR1Y3RvclVyaX0gd2l0aCBvcmlnaW4gJHtvcmlnaW59YClcbiAgICBpZiAodG9rZW4pIHtcbiAgICAgIGNvbm4gPSBhd2FpdCBvcGVuQ29ubmVjdGlvbihjb25kdWN0b3JVcmksIHRyYWNlQXBwU2lnbmFscywgdG9rZW4sIG9yaWdpbik7XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbm4gPSBhd2FpdCBvcGVuQ29ubmVjdGlvbihjb25kdWN0b3JVcmksIHRyYWNlQXBwU2lnbmFscywgbnVsbCwgb3JpZ2luKTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgY29uc29sZS5sb2coYEhvbG9jaGFpbiBjb25uZWN0aW9uIHRvICR7Y29uZHVjdG9yVXJpfSB3aXRob3V0IG9yaWdpbmApXG4gICAgaWYgKHRva2VuKSB7XG4gICAgICBjb25uID0gYXdhaXQgb3BlbkNvbm5lY3Rpb24oY29uZHVjdG9yVXJpLCB0cmFjZUFwcFNpZ25hbHMsIHRva2VuKTtcbiAgICB9IGVsc2Uge1xuICAgICAgY29ubiA9IGF3YWl0IG9wZW5Db25uZWN0aW9uKGNvbmR1Y3RvclVyaSwgdHJhY2VBcHBTaWduYWxzKTtcbiAgICB9XG4gIH1cbiAgY29uc3Qge1xuICAgIGRuYUNvbmZpZyxcbiAgICBhcHBJZDogcmVhbEFwcElkLFxuICB9ID0gYXdhaXQgc25pZmZIb2xvY2hhaW5BcHBDZWxscyhjb25uLCBhcHBJRCk7XG5cbiAgaWYgKGFkbWluQ29ubikge1xuICAgIGZvciBhd2FpdCAobGV0IGNlbGxJZCBvZiBPYmplY3QudmFsdWVzKGRuYUNvbmZpZykpIHtcbiAgICAgIGF3YWl0IGFkbWluQ29ubi5hdXRob3JpemVTaWduaW5nQ3JlZGVudGlhbHMoY2VsbElkKVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiB7XG4gICAgY29ubixcbiAgICBhZG1pbkNvbm4sXG4gICAgZG5hQ29uZmlnLFxuICAgIGNvbmR1Y3RvclVyaSxcbiAgICBhZG1pbkNvbmR1Y3RvclVyaSxcbiAgICBhcHBJZDogcmVhbEFwcElkXG4gIH1cbn1cblxuLyoqXG4gKiBJbml0cyBhIGNvbm5lY3Rpb24gZm9yIHRoZSBnaXZlbiB3ZWF2ZSBjbGllbnQuXG4gKi9cbmV4cG9ydCBjb25zdCBvcGVuV2VhdmVDb25uZWN0aW9uID0gKGFwcFNvY2tldFVSSTogc3RyaW5nLCBhcHBBZ2VudENsaWVudDogQXBwQ2xpZW50LCB0cmFjZUFwcFNpZ25hbHM/OiBTaWduYWxDYikgPT4ge1xuICBjb25zb2xlLmxvZyhgU2F2ZSBIb2xvY2hhaW4gY29ubmVjdGlvbiBmcm9tIG9wZW5XZWF2ZUNvbm5lY3Rpb246YCwgYXBwQWdlbnRDbGllbnQpXG5cbiAgQVBQX0FHRU5UX0NPTk5FQ1RJT05fQ0FDSEVbYXBwU29ja2V0VVJJXSA9IFByb21pc2UucmVzb2x2ZShhcHBBZ2VudENsaWVudClcblxuICBjb25zb2xlLmxvZyhgSG9sb2NoYWluIHNhdmVkIHRvICR7QVBQX0FHRU5UX0NPTk5FQ1RJT05fQ0FDSEVbYXBwU29ja2V0VVJJXX0gT0sgZnJvbSBvcGVuV2VhdmVDb25uZWN0aW9uYClcblxuICByZXR1cm4gQVBQX0FHRU5UX0NPTk5FQ1RJT05fQ0FDSEVbYXBwU29ja2V0VVJJXVxufVxuXG5cbi8qKlxuICogSW5pdHMgYSBjb25uZWN0aW9uIGZvciB0aGUgZ2l2ZW4gd2Vic29ja2V0IFVSSS5cbiAqXG4gKiBUaGlzIG1ldGhvZCBnaXZlcyBjYWxsaW5nIGNvZGUgYW4gb3Bwb3J0dW5pdHkgdG8gcmVnaXN0ZXIgZ2xvYmFscyBmb3IgYWxsIGZ1dHVyZVxuICogaW5zdGFuY2VzIG9mIGEgY29ubmVjdGlvbiBvZiB0aGUgc2FtZSBgc29ja2V0VVJJYC4gVG8gZW5zdXJlIHRoaXMgaXMgZG9uZSByZWxpYWJseSxcbiAqIGEgcnVudGltZSBlcnJvciB3aWxsIGJlIHRocm93biBieSBgZ2V0Q29ubmVjdGlvbmAgaWYgbm8gYG9wZW5Db25uZWN0aW9uYCBoYXNcbiAqIGJlZW4gcHJldmlvdXNseSBwZXJmb3JtZWQgZm9yIHRoZSBzYW1lIGBzb2NrZXRVUklgLlxuICovXG5leHBvcnQgY29uc3Qgb3BlbkNvbm5lY3Rpb24gPSAoYXBwU29ja2V0VVJJOiBzdHJpbmcsIHRyYWNlQXBwU2lnbmFscz86IFNpZ25hbENiLCB0b2tlbj86IGFueSwgb3JpZ2luPzogc3RyaW5nKSA9PiB7XG4gIGNvbnNvbGUubG9nKGBJbml0IEhvbG9jaGFpbiBjb25uZWN0aW9uOiAke2FwcFNvY2tldFVSSX0sIG9yaWdpbjogJHtvcmlnaW59YClcblxuICBpZiAob3JpZ2luKSB7XG4gICAgY29uc29sZS5sb2coYEhvbG9jaGFpbiBjb25uZWN0aW9uIHRvICR7YXBwU29ja2V0VVJJfSB3aXRoIG9yaWdpbiAke29yaWdpbn1gKVxuICAgIENPTk5FQ1RJT05fQ0FDSEVbYXBwU29ja2V0VVJJXSA9IEFwcFdlYnNvY2tldC5jb25uZWN0KHt1cmw6IGFwcFNvY2tldFVSSSwgd3NDbGllbnRPcHRpb25zOiB7IG9yaWdpbjogb3JpZ2lufSwgdG9rZW46IHRva2VufSlcbiAgICAudGhlbigoY2xpZW50KSA9PiB7XG4gICAgICBjb25zb2xlLmxvZyhgSG9sb2NoYWluIGNvbm5lY3Rpb24gdG8gJHthcHBTb2NrZXRVUkl9IE9LYClcbiAgICAgIGlmICh0cmFjZUFwcFNpZ25hbHMpIHtcbiAgICAgICAgY2xpZW50Lm9uKCdzaWduYWwnLCB0cmFjZUFwcFNpZ25hbHMpXG4gICAgICB9XG4gICAgICByZXR1cm4gY2xpZW50XG4gICAgfSlcbiAgICByZXR1cm4gQ09OTkVDVElPTl9DQUNIRVthcHBTb2NrZXRVUkldXG4gIH0gZWxzZSB7XG4gICAgY29uc29sZS5sb2coYEhvbG9jaGFpbiBjb25uZWN0aW9uIHRvICR7YXBwU29ja2V0VVJJfSB3aXRob3V0IG9yaWdpbmApXG4gICAgQ09OTkVDVElPTl9DQUNIRVthcHBTb2NrZXRVUkldID0gQXBwV2Vic29ja2V0LmNvbm5lY3Qoe3VybDogYXBwU29ja2V0VVJJLCB0b2tlbjogdG9rZW59KVxuICAgIC50aGVuKChjbGllbnQpID0+IHtcbiAgICAgIGNvbnNvbGUubG9nKGBIb2xvY2hhaW4gY29ubmVjdGlvbiB0byAke2FwcFNvY2tldFVSSX0gT0tgKVxuICAgICAgaWYgKHRyYWNlQXBwU2lnbmFscykge1xuICAgICAgICBjbGllbnQub24oJ3NpZ25hbCcsIHRyYWNlQXBwU2lnbmFscylcbiAgICAgIH1cbiAgICAgIHJldHVybiBjbGllbnRcbiAgICB9KVxuICAgIHJldHVybiBDT05ORUNUSU9OX0NBQ0hFW2FwcFNvY2tldFVSSV1cbiAgfVxuXG59XG5cbmNvbnN0IGdldENvbm5lY3Rpb24gPSAoYXBwU29ja2V0VVJJOiBzdHJpbmcpID0+IHtcbiAgaWYgKCFDT05ORUNUSU9OX0NBQ0hFW2FwcFNvY2tldFVSSV0pIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYENvbm5lY3Rpb24gZm9yICR7YXBwU29ja2V0VVJJfSBub3QgaW5pdGlhbGlzZWQhIFBsZWFzZSBjYWxsIG9wZW5Db25uZWN0aW9uKCkgZmlyc3QuYClcbiAgfVxuXG4gIHJldHVybiBDT05ORUNUSU9OX0NBQ0hFW2FwcFNvY2tldFVSSV1cbn1cblxuY29uc3QgZ2V0V2VhdmVDb25uZWN0aW9uID0gKGFwcFNvY2tldFVSSTogc3RyaW5nKSA9PiB7XG4gIGlmICghQVBQX0FHRU5UX0NPTk5FQ1RJT05fQ0FDSEVbYXBwU29ja2V0VVJJXSkge1xuICAgIHRocm93IG5ldyBFcnJvcihgQ29ubmVjdGlvbiBmb3IgJHthcHBTb2NrZXRVUkl9IG5vdCBpbml0aWFsaXNlZCEgUGxlYXNlIGNhbGwgb3BlbkNvbm5lY3Rpb24oKSBmaXJzdC5gKVxuICB9XG5cbiAgLy8gY29uc29sZS5sb2coYEhvbG9jaGFpbiBjb25uZWN0aW9uIGZyb20gZ2V0V2VhdmVDb25uZWN0aW9uOmAsIEFQUF9BR0VOVF9DT05ORUNUSU9OX0NBQ0hFW2FwcFNvY2tldFVSSV0pXG5cbiAgcmV0dXJuIEFQUF9BR0VOVF9DT05ORUNUSU9OX0NBQ0hFW2FwcFNvY2tldFVSSV1cbn1cblxuLyoqXG4gKiBJbnRyb3NwZWN0IGFuIGFjdGl2ZSBIb2xvY2hhaW4gY29ubmVjdGlvbidzIGFwcCBjZWxscyB0byBkZXRlcm1pbmUgY2VsbCBJRHNcbiAqIGZvciBtYXBwaW5nIHRvIHRoZSBzY2hlbWEgcmVzb2x2ZXJzLlxuICogSWYgbm8gYGFwcElkYCBpcyBwcm92aWRlZCBvciBpcyBvdGhlcndpc2UgZW1wdHkgb3IgdW5kZWZpbmVkLFxuICogaXQgd2lsbCB0cnkgdG8gdXNlIHRoZSBgUkVBQ1RfQVBQX0hDX0FQUF9JRGAgZW52aXJvbm1lbnQgdmFyaWFibGUuXG4gKiBPbmx5IGlmIHJ1bm5pbmcgaW4gYSBIb2xvY2hhaW4gTGF1bmNoZXIgY29udGV4dCwgY2FuIGJvdGggb2YgdGhlIGJlZm9yZS1tZW50aW9uZWQgdmFsdWVzXG4gKiBiZSBsZWZ0IHVuZGVmaW5lZCBvciBlbXB0eSwgYW5kIHRoZSBBcHBXZWJzb2NrZXQgd2lsbCBrbm93IHdoaWNoIGFwcElkIHRvIGludHJvc3BlY3QgaW50by5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNuaWZmSG9sb2NoYWluQXBwQ2VsbHMoY29ubjogQXBwV2Vic29ja2V0LCBhcHBJZD86IHN0cmluZykge1xuICAvLyBjb25zb2xlLmxvZyhcInNuaWZmIGhvbG9jaGFpbiBhcHAgY2VsbHNcIiwgY29ubiwgYXBwSWQpXG4gIC8vIHVzZSB0aGUgZGVmYXVsdCBzZXQgYnkgdGhlIGVudmlyb25tZW50IHZhcmlhYmxlXG4gIC8vIGFuZCBmdXJ0aGVybW9yZSwgbm90ZSB0aGF0IGJvdGggb2YgdGhlc2Ugd2lsbCBiZSBpZ25vcmVkXG4gIC8vIGluIHRoZSBIb2xvY2hhaW4gTGF1bmNoZXIgY29udGV4dFxuICAvLyB3aGljaCB3aWxsIG92ZXJyaWRlIGFueSBnaXZlbiB2YWx1ZSB0byB0aGUgQXBwV2Vic29ja2V0XG4gIC8vIGZvciBpbnN0YWxsZWRfYXBwX2lkXG4gIGFwcElkID0gYXBwSWQgfHwgRU5WX0hPTE9DSEFJTl9BUFBfSURcbiAgY29uc3QgYXBwSW5mbyA9IGF3YWl0IGNvbm4uYXBwSW5mbygpXG4gIGlmICghYXBwSW5mbykge1xuICAgIHRocm93IG5ldyBFcnJvcihgYXBwSW5mbyBjYWxsIGZhaWxlZCBmb3IgSG9sb2NoYWluIGFwcCAnJHthcHBJZH0nIC0gZW5zdXJlIHRoZSBuYW1lIGlzIGNvcnJlY3QgYW5kIHRoYXQgdGhlIGFwcCBpbnN0YWxsYXRpb24gaGFzIHN1Y2NlZWRlZGApXG4gIH1cblxuICBsZXQgZG5hQ29uZmlnOiBETkFJZE1hcHBpbmdzID0ge31cbiAgT2JqZWN0LmVudHJpZXMoYXBwSW5mby5jZWxsX2luZm8pLmZvckVhY2goKFtyb2xlTmFtZSwgY2VsbEluZm9zXSkgPT4ge1xuICAgIC8vIHRoaXMgaXMgdGhlIFwibWFnaWMgcGF0dGVyblwiIG9mIGhhdmluZyBmb3JcbiAgICAvLyBleGFtcGxlIHRoZSBcImFncmVlbWVudFwiIEROQSwgaXQgc2hvdWxkIGhhdmVcbiAgICAvLyBhbiBhc3NpZ25lZCBcInJvbGVfbmFtZVwiIGluIHRoZSBoYXBwIG9mXG4gICAgLy8gXCJocmVhX2FncmVlbWVudF8xXCIgb3IgXCJocmVhX29ic2VydmF0aW9uXzJcIlxuICAgIC8vIGFuZCB0aGUgbWlkZGxlIHNlY3Rpb24gc2hvdWxkIG1hdGNoIHRoZSBleHBlY3RlZCBuYW1lXG4gICAgLy8gZm9yIEROQUlkTWFwcGluZ3MsIHdoaWNoIGFyZSBhbHNvIHVzZWQgZHVyaW5nIHpvbWUgY2FsbHNcbiAgICBjb25zdCBocmVhX2NlbGxfbWF0Y2ggPSByb2xlTmFtZS5tYXRjaCgvaHJlYV8oXFx3KylfXFxkKy8pXG4gICAgaWYgKCFocmVhX2NlbGxfbWF0Y2gpIHJldHVyblxuICAgIGNvbnN0IGhyZWFSb2xlID0gaHJlYV9jZWxsX21hdGNoWzFdIGFzIGtleW9mIEROQUlkTWFwcGluZ3NcbiAgICBpZiAoY2VsbEluZm9zKSB7XG4gICAgICBjb25zdCBmaXJzdENlbGwgPSBjZWxsSW5mb3NbMF1cbiAgICAgIGlmIChDZWxsVHlwZS5Qcm92aXNpb25lZCBpbiBmaXJzdENlbGwpIHtcbiAgICAgICAgZG5hQ29uZmlnW2hyZWFSb2xlXSA9IGZpcnN0Q2VsbFtDZWxsVHlwZS5Qcm92aXNpb25lZF0uY2VsbF9pZFxuICAgICAgfVxuICAgIH1cbiAgfSlcblxuICBjb25zb2xlLmluZm8oJ0Nvbm5lY3RpbmcgdG8gZGV0ZWN0ZWQgSG9sb2NoYWluIGNlbGxzOicsIGRuYUNvbmZpZylcblxuICByZXR1cm4ge1xuICAgIGRuYUNvbmZpZyxcbiAgICBhcHBJZCxcbiAgfVxufVxuXG5cbi8vLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gSG9sb2NoYWluIC8gR3JhcGhRTCB0eXBlIHRyYW5zbGF0aW9uIGxheWVyXG4vLy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLy8gQHNlZSBodHRwczovL2NyYXRlcy5pby9jcmF0ZXMvaG9sb19oYXNoXG5jb25zdCBIT0xPQ0hBSU5fSURFTlRJRklFUl9MRU4gPSAzOVxuLy8gQHNlZSBob2xvX2hhc2g6Omhhc2hfdHlwZTo6cHJpbWl0aXZlXG5jb25zdCBIT0xPSEFTSF9QUkVGSVhfRE5BID0gWzB4ODQsIDB4MmQsIDB4MjRdICAgICAvLyB1aEMwa1xuY29uc3QgSE9MT0hBU0hfUFJFRklYX0VOVFJZID0gWzB4ODQsIDB4MjEsIDB4MjRdICAgLy8gdWhDRWtcbmNvbnN0IEhPTE9IQVNIX1BSRUZJWF9IRUFERVIgPSBbMHg4NCwgMHgyOSwgMHgyNF0gIC8vIHVoQ2trXG5jb25zdCBIT0xPSEFTSF9QUkVGSVhfQUdFTlQgPSBbMHg4NCwgMHgyMCwgMHgyNF0gICAvLyB1aENBa1xuXG5jb25zdCBzZXJpYWxpemVkSGFzaE1hdGNoUmVnZXggPSAvXltBLVphLXowLTlfK1xcLS9dezUzfT17MCwyfSQvXG5jb25zdCBpZE1hdGNoUmVnZXggPSAvXltBLVphLXowLTlfK1xcLS9dezUzfT17MCwyfTpbQS1aYS16MC05XytcXC0vXXs1M309ezAsMn0kL1xuLy8gc29tZXRoaW5nIGxpa2Vcbi8vICQ6dWhDMGsxbWNVcVFJYnRUMG1rZFRsZGhCYUF2UjZLbEt4SVYySVl3SmVtSHQtTk85MnVYRzVcbi8vIG9yIGtnOnVoQzBrMW1jVXFRSWJ0VDBta2RUbGRoQmFBdlI2S2xLeElWMklZd0plbUh0LU5POTJ1WEc1XG4vLyBidXQgbm90IDk6dWhDMGsxbWNVcVFJYnRUMG1rZFRsZGhCYUF2UjZLbEt4SVYySVl3SmVtSHQtTk85MnVYRzUgKGkuZS4gbm8gZGlnaXRzIGluIHRoZSBpZClcbmNvbnN0IHN0cmluZ0lkUmVnZXggPSAvXlxcRCs/OltBLVphLXowLTlfK1xcLS9dezUzfT17MCwyfSQvXG5cbi8vIEBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2hvbG9jaGFpbi1vcGVuLWRldi9jb3JlLXR5cGVzL2Jsb2IvbWFpbi9zcmMvdXRpbHMudHNcbmV4cG9ydCBmdW5jdGlvbiBkZXNlcmlhbGl6ZUhhc2goaGFzaDogc3RyaW5nKTogVWludDhBcnJheSB7XG4gIC8vIHJldHVybiBCYXNlNjQudG9VaW50OEFycmF5KGhhc2guc2xpY2UoMSkpXG4gIHJldHVybiB0b0J5dGVBcnJheShoYXNoLnNsaWNlKDEpKVxufVxuXG5leHBvcnQgZnVuY3Rpb24gZGVzZXJpYWxpemVJZChmaWVsZDogc3RyaW5nKTogUmVjb3JkSWQge1xuICBjb25zdCBtYXRjaGVzID0gZmllbGQuc3BsaXQoJzonKVxuICByZXR1cm4gW1xuICAgIEJ1ZmZlci5mcm9tKGRlc2VyaWFsaXplSGFzaChtYXRjaGVzWzFdKSksXG4gICAgQnVmZmVyLmZyb20oZGVzZXJpYWxpemVIYXNoKG1hdGNoZXNbMF0pKSxcbiAgXVxufVxuXG5mdW5jdGlvbiBkZXNlcmlhbGl6ZVN0cmluZ0lkKGZpZWxkOiBzdHJpbmcpOiBbQnVmZmVyLHN0cmluZ10ge1xuICBjb25zdCBtYXRjaGVzID0gZmllbGQuc3BsaXQoJzonKVxuICByZXR1cm4gW1xuICAgIEJ1ZmZlci5mcm9tKGRlc2VyaWFsaXplSGFzaChtYXRjaGVzWzFdKSksXG4gICAgbWF0Y2hlc1swXSxcbiAgXVxufVxuXG4vLyBAc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9ob2xvY2hhaW4tb3Blbi1kZXYvY29yZS10eXBlcy9ibG9iL21haW4vc3JjL3V0aWxzLnRzXG5leHBvcnQgZnVuY3Rpb24gc2VyaWFsaXplSGFzaChoYXNoOiBVaW50OEFycmF5KTogc3RyaW5nIHtcbiAgLy8gcmV0dXJuIGB1JHtCYXNlNjQuZnJvbVVpbnQ4QXJyYXkoaGFzaCwgdHJ1ZSl9YFxuICByZXR1cm4gYHUke2Zyb21CeXRlQXJyYXkoaGFzaCl9YFxufVxuXG5mdW5jdGlvbiBzZXJpYWxpemVJZChpZDogUmVjb3JkSWQpOiBzdHJpbmcge1xuICByZXR1cm4gYCR7c2VyaWFsaXplSGFzaChpZFsxXSl9OiR7c2VyaWFsaXplSGFzaChpZFswXSl9YFxufVxuXG5mdW5jdGlvbiBzZXJhbGl6ZVN0cmluZ0lkKGlkOiBbQnVmZmVyLHN0cmluZ10pOiBzdHJpbmcge1xuICByZXR1cm4gYCR7aWRbMV19OiR7c2VyaWFsaXplSGFzaChpZFswXSl9YFxufVxuXG4vLyBDb25zdHJ1Y3QgYXBwcm9wcmlhdGUgSURzIGZvciByZWNvcmRzIGluIGFzc29jaWF0ZWQgRE5BcyBieSBzdWJzdGl0dXRpbmdcbi8vIHRoZSBDZWxsSWQgcG9ydGlvbiBvZiB0aGUgSUQgd2l0aCB0aGF0IG9mIGFuIGFwcHJvcHJpYXRlIGRlc3RpbmF0aW9uIHJlY29yZFxuZXhwb3J0IGZ1bmN0aW9uIHJlbWFwQ2VsbElkKG9yaWdpbmFsSWQsIG5ld0NlbGxJZCkge1xuICBjb25zdCBbb3JpZ0lkLCBfb3JpZ0NlbGxdID0gb3JpZ2luYWxJZC5zcGxpdCgnOicpXG4gIHJldHVybiBgJHtvcmlnSWR9OiR7bmV3Q2VsbElkLnNwbGl0KCc6JylbMV19YFxufVxuXG5jb25zdCBMT05HX0RBVEVUSU1FX0ZPUk1BVCA9ICdZWVlZLU1NLUREVEhIOm1tOnNzLlNTU1onXG5jb25zdCBTSE9SVF9EQVRFVElNRV9GT1JNQVQgPSAnWVlZWS1NTS1ERFRISDptbTpzc1onXG5jb25zdCBpc29EYXRlUmVnZXggPSAvXlxcZHs0fS1cXGRcXGQtXFxkXFxkKFRcXGRcXGQ6XFxkXFxkOlxcZFxcZChcXC5cXGRcXGRcXGQpPyk/KFsrLV1cXGRcXGQ6XFxkXFxkKT8kL1xuXG4vKipcbiAqIERlY29kZSByYXcgZGF0YSBpbnB1dCBjb21pbmcgZnJvbSBIb2xvY2hhaW4gQVBJIHdlYnNvY2tldC5cbiAqXG4gKiBNdXRhdGVzIGluIHBsYWNlLSB3ZSBoYXZlIG5vIG5lZWQgZm9yIHRoZSBub24tbm9ybWFsaXNlZCBwcmltaXRpdmUgZm9ybWF0IGFuZCB0aGlzIHNhdmVzIG1lbW9yeS5cbiAqL1xuY29uc3QgZGVjb2RlRmllbGRzID0gKHJlc3VsdDogYW55KTogdm9pZCA9PiB7XG4gIGRlZXBGb3JFYWNoKHJlc3VsdCwgKHZhbHVlLCBwcm9wLCBzdWJqZWN0KSA9PiB7XG5cbiAgICAvLyBBY3Rpb25IYXNoIG9yIEFnZW50UHViS2V5XG4gICAgaWYgKCh2YWx1ZSBpbnN0YW5jZW9mIEJ1ZmZlciB8fCB2YWx1ZSBpbnN0YW5jZW9mIFVpbnQ4QXJyYXkpICYmIHZhbHVlLmxlbmd0aCA9PT0gSE9MT0NIQUlOX0lERU5USUZJRVJfTEVOICYmXG4gICAgICAoY2hlY2tMZWFkaW5nQnl0ZXModmFsdWUsIEhPTE9IQVNIX1BSRUZJWF9IRUFERVIpIHx8IGNoZWNrTGVhZGluZ0J5dGVzKHZ