UNPKG

@leosprograms/vf-graphql-holochain

Version:

GraphQL schema bindings for the Holochain implementation of ValueFlows

400 lines 57.8 kB
/** * 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