UNPKG

swarm-js

Version:
654 lines (560 loc) 59 kB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ // TODO: this is a temporary fix to hide those libraries from the browser. A // slightly better long-term solution would be to split this file into two, // separating the functions that are used on Node.js from the functions that // are used only on the browser. module.exports = function (_ref) { var fs = _ref.fs, files = _ref.files, os = _ref.os, path = _ref.path, child_process = _ref.child_process, mimetype = _ref.mimetype, defaultArchives = _ref.defaultArchives, request = _ref.request, downloadUrl = _ref.downloadUrl, bytes = _ref.bytes, hash = _ref.hash, pick = _ref.pick; // ∀ a . String -> JSON -> Map String a -o Map String a // Inserts a key/val pair in an object impurely. var impureInsert = function impureInsert(key) { return function (val) { return function (map) { return map[key] = val, map; }; }; }; // String -> JSON -> Map String JSON // Merges an array of keys and an array of vals into an object. var toMap = function toMap(keys) { return function (vals) { var map = {}; for (var i = 0, l = keys.length; i < l; ++i) { map[keys[i]] = vals[i]; } return map; }; }; // ∀ a . Map String a -> Map String a -> Map String a // Merges two maps into one. var merge = function merge(a) { return function (b) { var map = {}; for (var key in a) { map[key] = a[key]; } for (var _key in b) { map[_key] = b[_key]; } return map; }; }; // ∀ a . [a] -> [a] -> Bool var equals = function equals(a) { return function (b) { if (a.length !== b.length) { return false; } else { for (var i = 0, l = a.length; i < a; ++i) { if (a[i] !== b[i]) return false; } } return true; }; }; // String -> String -> String var rawUrl = function rawUrl(swarmUrl) { return function (hash) { return "".concat(swarmUrl, "/bzzr:/").concat(hash); }; }; // String -> String -> Promise Uint8Array // Gets the raw contents of a Swarm hash address. var downloadData = function downloadData(swarmUrl) { return function (hash) { return request(rawUrl(swarmUrl)(hash), { responseType: "arraybuffer" }).then(function (arrayBuffer) { var uint8Array = new Uint8Array(arrayBuffer); var error404 = [52, 48, 52, 32, 112, 97, 103, 101, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100, 10]; if (equals(uint8Array)(error404)) throw "Error 404."; return uint8Array; }); }; }; // type Entry = {"type": String, "hash": String} // type File = {"type": String, "data": Uint8Array} // String -> String -> Promise (Map String Entry) // Solves the manifest of a Swarm address recursively. // Returns a map from full paths to entries. var downloadEntries = function downloadEntries(swarmUrl) { return function (hash) { var search = function search(hash) { return function (path) { return function (routes) { // Formats an entry to the Swarm.js type. var format = function format(entry) { return { type: entry.contentType, hash: entry.hash }; }; // To download a single entry: // if type is bzz-manifest, go deeper // if not, add it to the routing table var downloadEntry = function downloadEntry(entry) { if (entry.path === undefined) { return Promise.resolve(); } else { return entry.contentType === "application/bzz-manifest+json" ? search(entry.hash)(path + entry.path)(routes) : Promise.resolve(impureInsert(path + entry.path)(format(entry))(routes)); } }; // Downloads the initial manifest and then each entry. return downloadData(swarmUrl)(hash).then(function (text) { return JSON.parse(toString(text)).entries; }).then(function (entries) { return Promise.all(entries.map(downloadEntry)); }).then(function () { return routes; }); }; }; }; return search(hash)("")({}); }; }; // String -> String -> Promise (Map String String) // Same as `downloadEntries`, but returns only hashes (no types). var downloadRoutes = function downloadRoutes(swarmUrl) { return function (hash) { return downloadEntries(swarmUrl)(hash).then(function (entries) { return toMap(Object.keys(entries))(Object.keys(entries).map(function (route) { return entries[route].hash; })); }); }; }; // String -> String -> Promise (Map String File) // Gets the entire directory tree in a Swarm address. // Returns a promise mapping paths to file contents. var downloadDirectory = function downloadDirectory(swarmUrl) { return function (hash) { return downloadEntries(swarmUrl)(hash).then(function (entries) { var paths = Object.keys(entries); var hashs = paths.map(function (path) { return entries[path].hash; }); var types = paths.map(function (path) { return entries[path].type; }); var datas = hashs.map(downloadData(swarmUrl)); var files = function files(datas) { return datas.map(function (data, i) { return { type: types[i], data: data }; }); }; return Promise.all(datas).then(function (datas) { return toMap(paths)(files(datas)); }); }); }; }; // String -> String -> String -> Promise String // Gets the raw contents of a Swarm hash address. // Returns a promise with the downloaded file path. var downloadDataToDisk = function downloadDataToDisk(swarmUrl) { return function (hash) { return function (filePath) { return files.download(rawUrl(swarmUrl)(hash))(filePath); }; }; }; // String -> String -> String -> Promise (Map String String) // Gets the entire directory tree in a Swarm address. // Returns a promise mapping paths to file contents. var downloadDirectoryToDisk = function downloadDirectoryToDisk(swarmUrl) { return function (hash) { return function (dirPath) { return downloadRoutes(swarmUrl)(hash).then(function (routingTable) { var downloads = []; for (var route in routingTable) { if (route.length > 0) { var filePath = path.join(dirPath, route); downloads.push(downloadDataToDisk(swarmUrl)(routingTable[route])(filePath)); } ; } ; return Promise.all(downloads).then(function () { return dirPath; }); }); }; }; }; // String -> Uint8Array -> Promise String // Uploads raw data to Swarm. // Returns a promise with the uploaded hash. var uploadData = function uploadData(swarmUrl) { return function (data) { return request("".concat(swarmUrl, "/bzzr:/"), { body: typeof data === "string" ? fromString(data) : data, method: "POST" }); }; }; // String -> String -> String -> File -> Promise String // Uploads a file to the Swarm manifest at a given hash, under a specific // route. Returns a promise containing the uploaded hash. // FIXME: for some reasons Swarm-Gateways is sometimes returning // error 404 (bad request), so we retry up to 3 times. Why? var uploadToManifest = function uploadToManifest(swarmUrl) { return function (hash) { return function (route) { return function (file) { var attempt = function attempt(n) { var slashRoute = route[0] === "/" ? route : "/" + route; var url = "".concat(swarmUrl, "/bzz:/").concat(hash).concat(slashRoute); var opt = { method: "PUT", headers: { "Content-Type": file.type }, body: file.data }; return request(url, opt).then(function (response) { if (response.indexOf("error") !== -1) { throw response; } return response; }).catch(function (e) { return n > 0 && attempt(n - 1); }); }; return attempt(3); }; }; }; }; // String -> {type: String, data: Uint8Array} -> Promise String var uploadFile = function uploadFile(swarmUrl) { return function (file) { return uploadDirectory(swarmUrl)({ "": file }); }; }; // String -> String -> Promise String var uploadFileFromDisk = function uploadFileFromDisk(swarmUrl) { return function (filePath) { return fs.readFile(filePath).then(function (data) { return uploadFile(swarmUrl)({ type: mimetype.lookup(filePath), data: data }); }); }; }; // String -> Map String File -> Promise String // Uploads a directory to Swarm. The directory is // represented as a map of routes and files. // A default path is encoded by having a "" route. var uploadDirectory = function uploadDirectory(swarmUrl) { return function (directory) { return uploadData(swarmUrl)("{}").then(function (hash) { var uploadRoute = function uploadRoute(route) { return function (hash) { return uploadToManifest(swarmUrl)(hash)(route)(directory[route]); }; }; var uploadToHash = function uploadToHash(hash, route) { return hash.then(uploadRoute(route)); }; return Object.keys(directory).reduce(uploadToHash, Promise.resolve(hash)); }); }; }; // String -> Promise String var uploadDataFromDisk = function uploadDataFromDisk(swarmUrl) { return function (filePath) { return fs.readFile(filePath).then(uploadData(swarmUrl)); }; }; // String -> Nullable String -> String -> Promise String var uploadDirectoryFromDisk = function uploadDirectoryFromDisk(swarmUrl) { return function (defaultPath) { return function (dirPath) { return files.directoryTree(dirPath).then(function (fullPaths) { return Promise.all(fullPaths.map(function (path) { return fs.readFile(path); })).then(function (datas) { var paths = fullPaths.map(function (path) { return path.slice(dirPath.length); }); var types = fullPaths.map(function (path) { return mimetype.lookup(path) || "text/plain"; }); return toMap(paths)(datas.map(function (data, i) { return { type: types[i], data: data }; })); }); }).then(function (directory) { return merge(defaultPath ? { "": directory[defaultPath] } : {})(directory); }).then(uploadDirectory(swarmUrl)); }; }; }; // String -> UploadInfo -> Promise String // Simplified multi-type upload which calls the correct // one based on the type of the argument given. var _upload = function upload(swarmUrl) { return function (arg) { // Upload raw data from browser if (arg.pick === "data") { return pick.data().then(uploadData(swarmUrl)); // Upload a file from browser } else if (arg.pick === "file") { return pick.file().then(uploadFile(swarmUrl)); // Upload a directory from browser } else if (arg.pick === "directory") { return pick.directory().then(uploadDirectory(swarmUrl)); // Upload directory/file from disk } else if (arg.path) { switch (arg.kind) { case "data": return uploadDataFromDisk(swarmUrl)(arg.path); case "file": return uploadFileFromDisk(swarmUrl)(arg.path); case "directory": return uploadDirectoryFromDisk(swarmUrl)(arg.defaultFile)(arg.path); } ; // Upload UTF-8 string or raw data (buffer) } else if (arg.length || typeof arg === "string") { return uploadData(swarmUrl)(arg); // Upload directory with JSON } else if (arg instanceof Object) { return uploadDirectory(swarmUrl)(arg); } return Promise.reject(new Error("Bad arguments")); }; }; // String -> String -> Nullable String -> Promise (String | Uint8Array | Map String Uint8Array) // Simplified multi-type download which calls the correct function based on // the type of the argument given, and on whether the Swwarm address has a // directory or a file. var _download = function download(swarmUrl) { return function (hash) { return function (path) { return isDirectory(swarmUrl)(hash).then(function (isDir) { if (isDir) { return path ? downloadDirectoryToDisk(swarmUrl)(hash)(path) : downloadDirectory(swarmUrl)(hash); } else { return path ? downloadDataToDisk(swarmUrl)(hash)(path) : downloadData(swarmUrl)(hash); } }); }; }; }; // String -> Promise String // Downloads the Swarm binaries into a path. Returns a promise that only // resolves when the exact Swarm file is there, and verified to be correct. // If it was already there to begin with, skips the download. var downloadBinary = function downloadBinary(path, archives) { var system = os.platform().replace("win32", "windows") + "-" + (os.arch() === "x64" ? "amd64" : "386"); var archive = (archives || defaultArchives)[system]; var archiveUrl = downloadUrl + archive.archive + ".tar.gz"; var archiveMD5 = archive.archiveMD5; var binaryMD5 = archive.binaryMD5; return files.safeDownloadArchived(archiveUrl)(archiveMD5)(binaryMD5)(path); }; // type SwarmSetup = { // account : String, // password : String, // dataDir : String, // binPath : String, // ensApi : String, // onDownloadProgress : Number ~> (), // archives : [{ // archive: String, // binaryMD5: String, // archiveMD5: String // }] // } // SwarmSetup ~> Promise Process // Starts the Swarm process. var startProcess = function startProcess(swarmSetup) { return new Promise(function (resolve, reject) { var spawn = child_process.spawn; var hasString = function hasString(str) { return function (buffer) { return ('' + buffer).indexOf(str) !== -1; }; }; var account = swarmSetup.account, password = swarmSetup.password, dataDir = swarmSetup.dataDir, ensApi = swarmSetup.ensApi, privateKey = swarmSetup.privateKey; var STARTUP_TIMEOUT_SECS = 3; var WAITING_PASSWORD = 0; var STARTING = 1; var LISTENING = 2; var PASSWORD_PROMPT_HOOK = "Passphrase"; var LISTENING_HOOK = "Swarm http proxy started"; var state = WAITING_PASSWORD; var swarmProcess = spawn(swarmSetup.binPath, ['--bzzaccount', account || privateKey, '--datadir', dataDir, '--ens-api', ensApi]); var handleProcessOutput = function handleProcessOutput(data) { if (state === WAITING_PASSWORD && hasString(PASSWORD_PROMPT_HOOK)(data)) { setTimeout(function () { state = STARTING; swarmProcess.stdin.write(password + '\n'); }, 500); } else if (hasString(LISTENING_HOOK)(data)) { state = LISTENING; clearTimeout(timeout); resolve(swarmProcess); } }; swarmProcess.stdout.on('data', handleProcessOutput); swarmProcess.stderr.on('data', handleProcessOutput); //swarmProcess.on('close', () => setTimeout(restart, 2000)); var restart = function restart() { return startProcess(swarmSetup).then(resolve).catch(reject); }; var error = function error() { return reject(new Error("Couldn't start swarm process.")); }; var timeout = setTimeout(error, 20000); }); }; // Process ~> Promise () // Stops the Swarm process. var stopProcess = function stopProcess(process) { return new Promise(function (resolve, reject) { process.stderr.removeAllListeners('data'); process.stdout.removeAllListeners('data'); process.stdin.removeAllListeners('error'); process.removeAllListeners('error'); process.removeAllListeners('exit'); process.kill('SIGINT'); var killTimeout = setTimeout(function () { return process.kill('SIGKILL'); }, 8000); process.once('close', function () { clearTimeout(killTimeout); resolve(); }); }); }; // SwarmSetup -> (SwarmAPI -> Promise ()) -> Promise () // Receives a Swarm configuration object and a callback function. It then // checks if a local Swarm node is running. If no local Swarm is found, it // downloads the Swarm binaries to the dataDir (if not there), checksums, // starts the Swarm process and calls the callback function with an API // object using the local node. That callback must return a promise which // will resolve when it is done using the API, so that this function can // close the Swarm process properly. Returns a promise that resolves when the // user is done with the API and the Swarm process is closed. // TODO: check if Swarm process is already running (improve `isAvailable`) var local = function local(swarmSetup) { return function (useAPI) { return _isAvailable("http://localhost:8500").then(function (isAvailable) { return isAvailable ? useAPI(at("http://localhost:8500")).then(function () {}) : downloadBinary(swarmSetup.binPath, swarmSetup.archives).onData(function (data) { return (swarmSetup.onProgress || function () {})(data.length); }).then(function () { return startProcess(swarmSetup); }).then(function (process) { return useAPI(at("http://localhost:8500")).then(function () { return process; }); }).then(stopProcess); }); }; }; // String ~> Promise Bool // Returns true if Swarm is available on `url`. // Perfoms a test upload to determine that. // TODO: improve this? var _isAvailable = function isAvailable(swarmUrl) { var testFile = "test"; var testHash = "c9a99c7d326dcc6316f32fe2625b311f6dc49a175e6877681ded93137d3569e7"; return uploadData(swarmUrl)(testFile).then(function (hash) { return hash === testHash; }).catch(function () { return false; }); }; // String -> String ~> Promise Bool // Returns a Promise which is true if that Swarm address is a directory. // Determines that by checking that it (i) is a JSON, (ii) has a .entries. // TODO: improve this? var isDirectory = function isDirectory(swarmUrl) { return function (hash) { return downloadData(swarmUrl)(hash).then(function (data) { try { return !!JSON.parse(toString(data)).entries; } catch (e) { return false; } }); }; }; // Uncurries a function; used to allow the f(x,y,z) style on exports. var uncurry = function uncurry(f) { return function (a, b, c, d, e) { var p; // Hardcoded because efficiency (`arguments` is very slow). if (typeof a !== "undefined") p = f(a); if (typeof b !== "undefined") p = f(b); if (typeof c !== "undefined") p = f(c); if (typeof d !== "undefined") p = f(d); if (typeof e !== "undefined") p = f(e); return p; }; }; // () -> Promise Bool // Not sure how to mock Swarm to test it properly. Ideas? var test = function test() { return Promise.resolve(true); }; // Uint8Array -> String var toString = function toString(uint8Array) { return bytes.toString(bytes.fromUint8Array(uint8Array)); }; // String -> Uint8Array var fromString = function fromString(string) { return bytes.toUint8Array(bytes.fromString(string)); }; // String -> SwarmAPI // Fixes the `swarmUrl`, returning an API where you don't have to pass it. var at = function at(swarmUrl) { return { download: function download(hash, path) { return _download(swarmUrl)(hash)(path); }, downloadData: uncurry(downloadData(swarmUrl)), downloadDataToDisk: uncurry(downloadDataToDisk(swarmUrl)), downloadDirectory: uncurry(downloadDirectory(swarmUrl)), downloadDirectoryToDisk: uncurry(downloadDirectoryToDisk(swarmUrl)), downloadEntries: uncurry(downloadEntries(swarmUrl)), downloadRoutes: uncurry(downloadRoutes(swarmUrl)), isAvailable: function isAvailable() { return _isAvailable(swarmUrl); }, upload: function upload(arg) { return _upload(swarmUrl)(arg); }, uploadData: uncurry(uploadData(swarmUrl)), uploadFile: uncurry(uploadFile(swarmUrl)), uploadFileFromDisk: uncurry(uploadFile(swarmUrl)), uploadDataFromDisk: uncurry(uploadDataFromDisk(swarmUrl)), uploadDirectory: uncurry(uploadDirectory(swarmUrl)), uploadDirectoryFromDisk: uncurry(uploadDirectoryFromDisk(swarmUrl)), uploadToManifest: uncurry(uploadToManifest(swarmUrl)), pick: pick, hash: hash, fromString: fromString, toString: toString }; }; return { at: at, local: local, download: _download, downloadBinary: downloadBinary, downloadData: downloadData, downloadDataToDisk: downloadDataToDisk, downloadDirectory: downloadDirectory, downloadDirectoryToDisk: downloadDirectoryToDisk, downloadEntries: downloadEntries, downloadRoutes: downloadRoutes, isAvailable: _isAvailable, startProcess: startProcess, stopProcess: stopProcess, upload: _upload, uploadData: uploadData, uploadDataFromDisk: uploadDataFromDisk, uploadFile: uploadFile, uploadFileFromDisk: uploadFileFromDisk, uploadDirectory: uploadDirectory, uploadDirectoryFromDisk: uploadDirectoryFromDisk, uploadToManifest: uploadToManifest, pick: pick, hash: hash, fromString: fromString, toString: toString }; }; },{}]},{},[1]) //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJsaWIvc3dhcm0uanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbigpe2Z1bmN0aW9uIHIoZSxuLHQpe2Z1bmN0aW9uIG8oaSxmKXtpZighbltpXSl7aWYoIWVbaV0pe3ZhciBjPVwiZnVuY3Rpb25cIj09dHlwZW9mIHJlcXVpcmUmJnJlcXVpcmU7aWYoIWYmJmMpcmV0dXJuIGMoaSwhMCk7aWYodSlyZXR1cm4gdShpLCEwKTt2YXIgYT1uZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK2krXCInXCIpO3Rocm93IGEuY29kZT1cIk1PRFVMRV9OT1RfRk9VTkRcIixhfXZhciBwPW5baV09e2V4cG9ydHM6e319O2VbaV1bMF0uY2FsbChwLmV4cG9ydHMsZnVuY3Rpb24ocil7dmFyIG49ZVtpXVsxXVtyXTtyZXR1cm4gbyhufHxyKX0scCxwLmV4cG9ydHMscixlLG4sdCl9cmV0dXJuIG5baV0uZXhwb3J0c31mb3IodmFyIHU9XCJmdW5jdGlvblwiPT10eXBlb2YgcmVxdWlyZSYmcmVxdWlyZSxpPTA7aTx0Lmxlbmd0aDtpKyspbyh0W2ldKTtyZXR1cm4gb31yZXR1cm4gcn0pKCkiLCIvLyBUT0RPOiB0aGlzIGlzIGEgdGVtcG9yYXJ5IGZpeCB0byBoaWRlIHRob3NlIGxpYnJhcmllcyBmcm9tIHRoZSBicm93c2VyLiBBXG4vLyBzbGlnaHRseSBiZXR0ZXIgbG9uZy10ZXJtIHNvbHV0aW9uIHdvdWxkIGJlIHRvIHNwbGl0IHRoaXMgZmlsZSBpbnRvIHR3byxcbi8vIHNlcGFyYXRpbmcgdGhlIGZ1bmN0aW9ucyB0aGF0IGFyZSB1c2VkIG9uIE5vZGUuanMgZnJvbSB0aGUgZnVuY3Rpb25zIHRoYXRcbi8vIGFyZSB1c2VkIG9ubHkgb24gdGhlIGJyb3dzZXIuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChfcmVmKSB7XG4gIHZhciBmcyA9IF9yZWYuZnMsXG4gICAgICBmaWxlcyA9IF9yZWYuZmlsZXMsXG4gICAgICBvcyA9IF9yZWYub3MsXG4gICAgICBwYXRoID0gX3JlZi5wYXRoLFxuICAgICAgY2hpbGRfcHJvY2VzcyA9IF9yZWYuY2hpbGRfcHJvY2VzcyxcbiAgICAgIG1pbWV0eXBlID0gX3JlZi5taW1ldHlwZSxcbiAgICAgIGRlZmF1bHRBcmNoaXZlcyA9IF9yZWYuZGVmYXVsdEFyY2hpdmVzLFxuICAgICAgcmVxdWVzdCA9IF9yZWYucmVxdWVzdCxcbiAgICAgIGRvd25sb2FkVXJsID0gX3JlZi5kb3dubG9hZFVybCxcbiAgICAgIGJ5dGVzID0gX3JlZi5ieXRlcyxcbiAgICAgIGhhc2ggPSBfcmVmLmhhc2gsXG4gICAgICBwaWNrID0gX3JlZi5waWNrO1xuXG4gIC8vIOKIgCBhIC4gU3RyaW5nIC0+IEpTT04gLT4gTWFwIFN0cmluZyBhIC1vIE1hcCBTdHJpbmcgYVxuICAvLyAgIEluc2VydHMgYSBrZXkvdmFsIHBhaXIgaW4gYW4gb2JqZWN0IGltcHVyZWx5LlxuICB2YXIgaW1wdXJlSW5zZXJ0ID0gZnVuY3Rpb24gaW1wdXJlSW5zZXJ0KGtleSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAodmFsKSB7XG4gICAgICByZXR1cm4gZnVuY3Rpb24gKG1hcCkge1xuICAgICAgICByZXR1cm4gbWFwW2tleV0gPSB2YWwsIG1hcDtcbiAgICAgIH07XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IEpTT04gLT4gTWFwIFN0cmluZyBKU09OXG4gIC8vICAgTWVyZ2VzIGFuIGFycmF5IG9mIGtleXMgYW5kIGFuIGFycmF5IG9mIHZhbHMgaW50byBhbiBvYmplY3QuXG5cblxuICB2YXIgdG9NYXAgPSBmdW5jdGlvbiB0b01hcChrZXlzKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICh2YWxzKSB7XG4gICAgICB2YXIgbWFwID0ge307XG5cbiAgICAgIGZvciAodmFyIGkgPSAwLCBsID0ga2V5cy5sZW5ndGg7IGkgPCBsOyArK2kpIHtcbiAgICAgICAgbWFwW2tleXNbaV1dID0gdmFsc1tpXTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG1hcDtcbiAgICB9O1xuICB9OyAvLyDiiIAgYSAuIE1hcCBTdHJpbmcgYSAtPiBNYXAgU3RyaW5nIGEgLT4gTWFwIFN0cmluZyBhXG4gIC8vICAgTWVyZ2VzIHR3byBtYXBzIGludG8gb25lLlxuXG5cbiAgdmFyIG1lcmdlID0gZnVuY3Rpb24gbWVyZ2UoYSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoYikge1xuICAgICAgdmFyIG1hcCA9IHt9O1xuXG4gICAgICBmb3IgKHZhciBrZXkgaW4gYSkge1xuICAgICAgICBtYXBba2V5XSA9IGFba2V5XTtcbiAgICAgIH1cblxuICAgICAgZm9yICh2YXIgX2tleSBpbiBiKSB7XG4gICAgICAgIG1hcFtfa2V5XSA9IGJbX2tleV07XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBtYXA7XG4gICAgfTtcbiAgfTsgLy8g4oiAIGEgLiBbYV0gLT4gW2FdIC0+IEJvb2xcblxuXG4gIHZhciBlcXVhbHMgPSBmdW5jdGlvbiBlcXVhbHMoYSkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoYikge1xuICAgICAgaWYgKGEubGVuZ3RoICE9PSBiLmxlbmd0aCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBmb3IgKHZhciBpID0gMCwgbCA9IGEubGVuZ3RoOyBpIDwgYTsgKytpKSB7XG4gICAgICAgICAgaWYgKGFbaV0gIT09IGJbaV0pIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9O1xuICB9OyAvLyBTdHJpbmcgLT4gU3RyaW5nIC0+IFN0cmluZ1xuXG5cbiAgdmFyIHJhd1VybCA9IGZ1bmN0aW9uIHJhd1VybChzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgcmV0dXJuIFwiXCIuY29uY2F0KHN3YXJtVXJsLCBcIi9ienpyOi9cIikuY29uY2F0KGhhc2gpO1xuICAgIH07XG4gIH07IC8vIFN0cmluZyAtPiBTdHJpbmcgLT4gUHJvbWlzZSBVaW50OEFycmF5XG4gIC8vICAgR2V0cyB0aGUgcmF3IGNvbnRlbnRzIG9mIGEgU3dhcm0gaGFzaCBhZGRyZXNzLlxuXG5cbiAgdmFyIGRvd25sb2FkRGF0YSA9IGZ1bmN0aW9uIGRvd25sb2FkRGF0YShzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgcmV0dXJuIHJlcXVlc3QocmF3VXJsKHN3YXJtVXJsKShoYXNoKSwge1xuICAgICAgICByZXNwb25zZVR5cGU6IFwiYXJyYXlidWZmZXJcIlxuICAgICAgfSkudGhlbihmdW5jdGlvbiAoYXJyYXlCdWZmZXIpIHtcbiAgICAgICAgdmFyIHVpbnQ4QXJyYXkgPSBuZXcgVWludDhBcnJheShhcnJheUJ1ZmZlcik7XG4gICAgICAgIHZhciBlcnJvcjQwNCA9IFs1MiwgNDgsIDUyLCAzMiwgMTEyLCA5NywgMTAzLCAxMDEsIDMyLCAxMTAsIDExMSwgMTE2LCAzMiwgMTAyLCAxMTEsIDExNywgMTEwLCAxMDAsIDEwXTtcbiAgICAgICAgaWYgKGVxdWFscyh1aW50OEFycmF5KShlcnJvcjQwNCkpIHRocm93IFwiRXJyb3IgNDA0LlwiO1xuICAgICAgICByZXR1cm4gdWludDhBcnJheTtcbiAgICAgIH0pO1xuICAgIH07XG4gIH07IC8vIHR5cGUgRW50cnkgPSB7XCJ0eXBlXCI6IFN0cmluZywgXCJoYXNoXCI6IFN0cmluZ31cbiAgLy8gdHlwZSBGaWxlID0ge1widHlwZVwiOiBTdHJpbmcsIFwiZGF0YVwiOiBVaW50OEFycmF5fVxuICAvLyBTdHJpbmcgLT4gU3RyaW5nIC0+IFByb21pc2UgKE1hcCBTdHJpbmcgRW50cnkpXG4gIC8vICAgU29sdmVzIHRoZSBtYW5pZmVzdCBvZiBhIFN3YXJtIGFkZHJlc3MgcmVjdXJzaXZlbHkuXG4gIC8vICAgUmV0dXJucyBhIG1hcCBmcm9tIGZ1bGwgcGF0aHMgdG8gZW50cmllcy5cblxuXG4gIHZhciBkb3dubG9hZEVudHJpZXMgPSBmdW5jdGlvbiBkb3dubG9hZEVudHJpZXMoc3dhcm1VcmwpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGhhc2gpIHtcbiAgICAgIHZhciBzZWFyY2ggPSBmdW5jdGlvbiBzZWFyY2goaGFzaCkge1xuICAgICAgICByZXR1cm4gZnVuY3Rpb24gKHBhdGgpIHtcbiAgICAgICAgICByZXR1cm4gZnVuY3Rpb24gKHJvdXRlcykge1xuICAgICAgICAgICAgLy8gRm9ybWF0cyBhbiBlbnRyeSB0byB0aGUgU3dhcm0uanMgdHlwZS5cbiAgICAgICAgICAgIHZhciBmb3JtYXQgPSBmdW5jdGlvbiBmb3JtYXQoZW50cnkpIHtcbiAgICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICB0eXBlOiBlbnRyeS5jb250ZW50VHlwZSxcbiAgICAgICAgICAgICAgICBoYXNoOiBlbnRyeS5oYXNoXG4gICAgICAgICAgICAgIH07XG4gICAgICAgICAgICB9OyAvLyBUbyBkb3dubG9hZCBhIHNpbmdsZSBlbnRyeTpcbiAgICAgICAgICAgIC8vICAgaWYgdHlwZSBpcyBienotbWFuaWZlc3QsIGdvIGRlZXBlclxuICAgICAgICAgICAgLy8gICBpZiBub3QsIGFkZCBpdCB0byB0aGUgcm91dGluZyB0YWJsZVxuXG5cbiAgICAgICAgICAgIHZhciBkb3dubG9hZEVudHJ5ID0gZnVuY3Rpb24gZG93bmxvYWRFbnRyeShlbnRyeSkge1xuICAgICAgICAgICAgICBpZiAoZW50cnkucGF0aCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSgpO1xuICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHJldHVybiBlbnRyeS5jb250ZW50VHlwZSA9PT0gXCJhcHBsaWNhdGlvbi9ienotbWFuaWZlc3QranNvblwiID8gc2VhcmNoKGVudHJ5Lmhhc2gpKHBhdGggKyBlbnRyeS5wYXRoKShyb3V0ZXMpIDogUHJvbWlzZS5yZXNvbHZlKGltcHVyZUluc2VydChwYXRoICsgZW50cnkucGF0aCkoZm9ybWF0KGVudHJ5KSkocm91dGVzKSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH07IC8vIERvd25sb2FkcyB0aGUgaW5pdGlhbCBtYW5pZmVzdCBhbmQgdGhlbiBlYWNoIGVudHJ5LlxuXG5cbiAgICAgICAgICAgIHJldHVybiBkb3dubG9hZERhdGEoc3dhcm1VcmwpKGhhc2gpLnRoZW4oZnVuY3Rpb24gKHRleHQpIHtcbiAgICAgICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UodG9TdHJpbmcodGV4dCkpLmVudHJpZXM7XG4gICAgICAgICAgICB9KS50aGVuKGZ1bmN0aW9uIChlbnRyaWVzKSB7XG4gICAgICAgICAgICAgIHJldHVybiBQcm9taXNlLmFsbChlbnRyaWVzLm1hcChkb3dubG9hZEVudHJ5KSk7XG4gICAgICAgICAgICB9KS50aGVuKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgcmV0dXJuIHJvdXRlcztcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH07XG4gICAgICAgIH07XG4gICAgICB9O1xuXG4gICAgICByZXR1cm4gc2VhcmNoKGhhc2gpKFwiXCIpKHt9KTtcbiAgICB9O1xuICB9OyAvLyBTdHJpbmcgLT4gU3RyaW5nIC0+IFByb21pc2UgKE1hcCBTdHJpbmcgU3RyaW5nKVxuICAvLyAgIFNhbWUgYXMgYGRvd25sb2FkRW50cmllc2AsIGJ1dCByZXR1cm5zIG9ubHkgaGFzaGVzIChubyB0eXBlcykuXG5cblxuICB2YXIgZG93bmxvYWRSb3V0ZXMgPSBmdW5jdGlvbiBkb3dubG9hZFJvdXRlcyhzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgcmV0dXJuIGRvd25sb2FkRW50cmllcyhzd2FybVVybCkoaGFzaCkudGhlbihmdW5jdGlvbiAoZW50cmllcykge1xuICAgICAgICByZXR1cm4gdG9NYXAoT2JqZWN0LmtleXMoZW50cmllcykpKE9iamVjdC5rZXlzKGVudHJpZXMpLm1hcChmdW5jdGlvbiAocm91dGUpIHtcbiAgICAgICAgICByZXR1cm4gZW50cmllc1tyb3V0ZV0uaGFzaDtcbiAgICAgICAgfSkpO1xuICAgICAgfSk7XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IFN0cmluZyAtPiBQcm9taXNlIChNYXAgU3RyaW5nIEZpbGUpXG4gIC8vICAgR2V0cyB0aGUgZW50aXJlIGRpcmVjdG9yeSB0cmVlIGluIGEgU3dhcm0gYWRkcmVzcy5cbiAgLy8gICBSZXR1cm5zIGEgcHJvbWlzZSBtYXBwaW5nIHBhdGhzIHRvIGZpbGUgY29udGVudHMuXG5cblxuICB2YXIgZG93bmxvYWREaXJlY3RvcnkgPSBmdW5jdGlvbiBkb3dubG9hZERpcmVjdG9yeShzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgcmV0dXJuIGRvd25sb2FkRW50cmllcyhzd2FybVVybCkoaGFzaCkudGhlbihmdW5jdGlvbiAoZW50cmllcykge1xuICAgICAgICB2YXIgcGF0aHMgPSBPYmplY3Qua2V5cyhlbnRyaWVzKTtcbiAgICAgICAgdmFyIGhhc2hzID0gcGF0aHMubWFwKGZ1bmN0aW9uIChwYXRoKSB7XG4gICAgICAgICAgcmV0dXJuIGVudHJpZXNbcGF0aF0uaGFzaDtcbiAgICAgICAgfSk7XG4gICAgICAgIHZhciB0eXBlcyA9IHBhdGhzLm1hcChmdW5jdGlvbiAocGF0aCkge1xuICAgICAgICAgIHJldHVybiBlbnRyaWVzW3BhdGhdLnR5cGU7XG4gICAgICAgIH0pO1xuICAgICAgICB2YXIgZGF0YXMgPSBoYXNocy5tYXAoZG93bmxvYWREYXRhKHN3YXJtVXJsKSk7XG5cbiAgICAgICAgdmFyIGZpbGVzID0gZnVuY3Rpb24gZmlsZXMoZGF0YXMpIHtcbiAgICAgICAgICByZXR1cm4gZGF0YXMubWFwKGZ1bmN0aW9uIChkYXRhLCBpKSB7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICB0eXBlOiB0eXBlc1tpXSxcbiAgICAgICAgICAgICAgZGF0YTogZGF0YVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgfTtcblxuICAgICAgICByZXR1cm4gUHJvbWlzZS5hbGwoZGF0YXMpLnRoZW4oZnVuY3Rpb24gKGRhdGFzKSB7XG4gICAgICAgICAgcmV0dXJuIHRvTWFwKHBhdGhzKShmaWxlcyhkYXRhcykpO1xuICAgICAgICB9KTtcbiAgICAgIH0pO1xuICAgIH07XG4gIH07IC8vIFN0cmluZyAtPiBTdHJpbmcgLT4gU3RyaW5nIC0+IFByb21pc2UgU3RyaW5nXG4gIC8vICAgR2V0cyB0aGUgcmF3IGNvbnRlbnRzIG9mIGEgU3dhcm0gaGFzaCBhZGRyZXNzLlxuICAvLyAgIFJldHVybnMgYSBwcm9taXNlIHdpdGggdGhlIGRvd25sb2FkZWQgZmlsZSBwYXRoLlxuXG5cbiAgdmFyIGRvd25sb2FkRGF0YVRvRGlzayA9IGZ1bmN0aW9uIGRvd25sb2FkRGF0YVRvRGlzayhzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uIChmaWxlUGF0aCkge1xuICAgICAgICByZXR1cm4gZmlsZXMuZG93bmxvYWQocmF3VXJsKHN3YXJtVXJsKShoYXNoKSkoZmlsZVBhdGgpO1xuICAgICAgfTtcbiAgICB9O1xuICB9OyAvLyBTdHJpbmcgLT4gU3RyaW5nIC0+IFN0cmluZyAtPiBQcm9taXNlIChNYXAgU3RyaW5nIFN0cmluZylcbiAgLy8gICBHZXRzIHRoZSBlbnRpcmUgZGlyZWN0b3J5IHRyZWUgaW4gYSBTd2FybSBhZGRyZXNzLlxuICAvLyAgIFJldHVybnMgYSBwcm9taXNlIG1hcHBpbmcgcGF0aHMgdG8gZmlsZSBjb250ZW50cy5cblxuXG4gIHZhciBkb3dubG9hZERpcmVjdG9yeVRvRGlzayA9IGZ1bmN0aW9uIGRvd25sb2FkRGlyZWN0b3J5VG9EaXNrKHN3YXJtVXJsKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChoYXNoKSB7XG4gICAgICByZXR1cm4gZnVuY3Rpb24gKGRpclBhdGgpIHtcbiAgICAgICAgcmV0dXJuIGRvd25sb2FkUm91dGVzKHN3YXJtVXJsKShoYXNoKS50aGVuKGZ1bmN0aW9uIChyb3V0aW5nVGFibGUpIHtcbiAgICAgICAgICB2YXIgZG93bmxvYWRzID0gW107XG5cbiAgICAgICAgICBmb3IgKHZhciByb3V0ZSBpbiByb3V0aW5nVGFibGUpIHtcbiAgICAgICAgICAgIGlmIChyb3V0ZS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgIHZhciBmaWxlUGF0aCA9IHBhdGguam9pbihkaXJQYXRoLCByb3V0ZSk7XG4gICAgICAgICAgICAgIGRvd25sb2Fkcy5wdXNoKGRvd25sb2FkRGF0YVRvRGlzayhzd2FybVVybCkocm91dGluZ1RhYmxlW3JvdXRlXSkoZmlsZVBhdGgpKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIDtcbiAgICAgICAgICByZXR1cm4gUHJvbWlzZS5hbGwoZG93bmxvYWRzKS50aGVuKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJldHVybiBkaXJQYXRoO1xuICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IFVpbnQ4QXJyYXkgLT4gUHJvbWlzZSBTdHJpbmdcbiAgLy8gICBVcGxvYWRzIHJhdyBkYXRhIHRvIFN3YXJtLlxuICAvLyAgIFJldHVybnMgYSBwcm9taXNlIHdpdGggdGhlIHVwbG9hZGVkIGhhc2guXG5cblxuICB2YXIgdXBsb2FkRGF0YSA9IGZ1bmN0aW9uIHVwbG9hZERhdGEoc3dhcm1VcmwpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGRhdGEpIHtcbiAgICAgIHJldHVybiByZXF1ZXN0KFwiXCIuY29uY2F0KHN3YXJtVXJsLCBcIi9ienpyOi9cIiksIHtcbiAgICAgICAgYm9keTogdHlwZW9mIGRhdGEgPT09IFwic3RyaW5nXCIgPyBmcm9tU3RyaW5nKGRhdGEpIDogZGF0YSxcbiAgICAgICAgbWV0aG9kOiBcIlBPU1RcIlxuICAgICAgfSk7XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IFN0cmluZyAtPiBTdHJpbmcgLT4gRmlsZSAtPiBQcm9taXNlIFN0cmluZ1xuICAvLyAgIFVwbG9hZHMgYSBmaWxlIHRvIHRoZSBTd2FybSBtYW5pZmVzdCBhdCBhIGdpdmVuIGhhc2gsIHVuZGVyIGEgc3BlY2lmaWNcbiAgLy8gICByb3V0ZS4gUmV0dXJucyBhIHByb21pc2UgY29udGFpbmluZyB0aGUgdXBsb2FkZWQgaGFzaC5cbiAgLy8gICBGSVhNRTogZm9yIHNvbWUgcmVhc29ucyBTd2FybS1HYXRld2F5cyBpcyBzb21ldGltZXMgcmV0dXJuaW5nXG4gIC8vICAgZXJyb3IgNDA0wqAoYmFkIHJlcXVlc3QpLCBzbyB3ZSByZXRyeSB1cCB0byAzIHRpbWVzLiBXaHk/XG5cblxuICB2YXIgdXBsb2FkVG9NYW5pZmVzdCA9IGZ1bmN0aW9uIHVwbG9hZFRvTWFuaWZlc3Qoc3dhcm1VcmwpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGhhc2gpIHtcbiAgICAgIHJldHVybiBmdW5jdGlvbiAocm91dGUpIHtcbiAgICAgICAgcmV0dXJuIGZ1bmN0aW9uIChmaWxlKSB7XG4gICAgICAgICAgdmFyIGF0dGVtcHQgPSBmdW5jdGlvbiBhdHRlbXB0KG4pIHtcbiAgICAgICAgICAgIHZhciBzbGFzaFJvdXRlID0gcm91dGVbMF0gPT09IFwiL1wiID8gcm91dGUgOiBcIi9cIiArIHJvdXRlO1xuICAgICAgICAgICAgdmFyIHVybCA9IFwiXCIuY29uY2F0KHN3YXJtVXJsLCBcIi9ieno6L1wiKS5jb25jYXQoaGFzaCkuY29uY2F0KHNsYXNoUm91dGUpO1xuICAgICAgICAgICAgdmFyIG9wdCA9IHtcbiAgICAgICAgICAgICAgbWV0aG9kOiBcIlBVVFwiLFxuICAgICAgICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgICAgICAgXCJDb250ZW50LVR5cGVcIjogZmlsZS50eXBlXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIGJvZHk6IGZpbGUuZGF0YVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHJldHVybiByZXF1ZXN0KHVybCwgb3B0KS50aGVuKGZ1bmN0aW9uIChyZXNwb25zZSkge1xuICAgICAgICAgICAgICBpZiAocmVzcG9uc2UuaW5kZXhPZihcImVycm9yXCIpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIHRocm93IHJlc3BvbnNlO1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgcmV0dXJuIHJlc3BvbnNlO1xuICAgICAgICAgICAgfSkuY2F0Y2goZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgICAgcmV0dXJuIG4gPiAwICYmIGF0dGVtcHQobiAtIDEpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfTtcblxuICAgICAgICAgIHJldHVybiBhdHRlbXB0KDMpO1xuICAgICAgICB9O1xuICAgICAgfTtcbiAgICB9O1xuICB9OyAvLyBTdHJpbmcgLT4ge3R5cGU6IFN0cmluZywgZGF0YTogVWludDhBcnJheX0gLT4gUHJvbWlzZSBTdHJpbmdcblxuXG4gIHZhciB1cGxvYWRGaWxlID0gZnVuY3Rpb24gdXBsb2FkRmlsZShzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoZmlsZSkge1xuICAgICAgcmV0dXJuIHVwbG9hZERpcmVjdG9yeShzd2FybVVybCkoe1xuICAgICAgICBcIlwiOiBmaWxlXG4gICAgICB9KTtcbiAgICB9O1xuICB9OyAvLyBTdHJpbmcgLT4gU3RyaW5nIC0+IFByb21pc2UgU3RyaW5nXG5cblxuICB2YXIgdXBsb2FkRmlsZUZyb21EaXNrID0gZnVuY3Rpb24gdXBsb2FkRmlsZUZyb21EaXNrKHN3YXJtVXJsKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChmaWxlUGF0aCkge1xuICAgICAgcmV0dXJuIGZzLnJlYWRGaWxlKGZpbGVQYXRoKS50aGVuKGZ1bmN0aW9uIChkYXRhKSB7XG4gICAgICAgIHJldHVybiB1cGxvYWRGaWxlKHN3YXJtVXJsKSh7XG4gICAgICAgICAgdHlwZTogbWltZXR5cGUubG9va3VwKGZpbGVQYXRoKSxcbiAgICAgICAgICBkYXRhOiBkYXRhXG4gICAgICAgIH0pO1xuICAgICAgfSk7XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IE1hcCBTdHJpbmcgRmlsZSAtPiBQcm9taXNlIFN0cmluZ1xuICAvLyAgIFVwbG9hZHMgYSBkaXJlY3RvcnkgdG8gU3dhcm0uIFRoZSBkaXJlY3RvcnkgaXNcbiAgLy8gICByZXByZXNlbnRlZCBhcyBhIG1hcCBvZiByb3V0ZXMgYW5kIGZpbGVzLlxuICAvLyAgIEEgZGVmYXVsdCBwYXRoIGlzIGVuY29kZWQgYnkgaGF2aW5nIGEgXCJcIiByb3V0ZS5cblxuXG4gIHZhciB1cGxvYWREaXJlY3RvcnkgPSBmdW5jdGlvbiB1cGxvYWREaXJlY3Rvcnkoc3dhcm1VcmwpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGRpcmVjdG9yeSkge1xuICAgICAgcmV0dXJuIHVwbG9hZERhdGEoc3dhcm1VcmwpKFwie31cIikudGhlbihmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgICB2YXIgdXBsb2FkUm91dGUgPSBmdW5jdGlvbiB1cGxvYWRSb3V0ZShyb3V0ZSkge1xuICAgICAgICAgIHJldHVybiBmdW5jdGlvbiAoaGFzaCkge1xuICAgICAgICAgICAgcmV0dXJuIHVwbG9hZFRvTWFuaWZlc3Qoc3dhcm1VcmwpKGhhc2gpKHJvdXRlKShkaXJlY3Rvcnlbcm91dGVdKTtcbiAgICAgICAgICB9O1xuICAgICAgICB9O1xuXG4gICAgICAgIHZhciB1cGxvYWRUb0hhc2ggPSBmdW5jdGlvbiB1cGxvYWRUb0hhc2goaGFzaCwgcm91dGUpIHtcbiAgICAgICAgICByZXR1cm4gaGFzaC50aGVuKHVwbG9hZFJvdXRlKHJvdXRlKSk7XG4gICAgICAgIH07XG5cbiAgICAgICAgcmV0dXJuIE9iamVjdC5rZXlzKGRpcmVjdG9yeSkucmVkdWNlKHVwbG9hZFRvSGFzaCwgUHJvbWlzZS5yZXNvbHZlKGhhc2gpKTtcbiAgICAgIH0pO1xuICAgIH07XG4gIH07IC8vIFN0cmluZyAtPiBQcm9taXNlIFN0cmluZ1xuXG5cbiAgdmFyIHVwbG9hZERhdGFGcm9tRGlzayA9IGZ1bmN0aW9uIHVwbG9hZERhdGFGcm9tRGlzayhzd2FybVVybCkge1xuICAgIHJldHVybiBmdW5jdGlvbiAoZmlsZVBhdGgpIHtcbiAgICAgIHJldHVybiBmcy5yZWFkRmlsZShmaWxlUGF0aCkudGhlbih1cGxvYWREYXRhKHN3YXJtVXJsKSk7XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IE51bGxhYmxlIFN0cmluZyAtPiBTdHJpbmcgLT4gUHJvbWlzZSBTdHJpbmdcblxuXG4gIHZhciB1cGxvYWREaXJlY3RvcnlGcm9tRGlzayA9IGZ1bmN0aW9uIHVwbG9hZERpcmVjdG9yeUZyb21EaXNrKHN3YXJtVXJsKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChkZWZhdWx0UGF0aCkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uIChkaXJQYXRoKSB7XG4gICAgICAgIHJldHVybiBmaWxlcy5kaXJlY3RvcnlUcmVlKGRpclBhdGgpLnRoZW4oZnVuY3Rpb24gKGZ1bGxQYXRocykge1xuICAgICAgICAgIHJldHVybiBQcm9taXNlLmFsbChmdWxsUGF0aHMubWFwKGZ1bmN0aW9uIChwYXRoKSB7XG4gICAgICAgICAgICByZXR1cm4gZnMucmVhZEZpbGUocGF0aCk7XG4gICAgICAgICAgfSkpLnRoZW4oZnVuY3Rpb24gKGRhdGFzKSB7XG4gICAgICAgICAgICB2YXIgcGF0aHMgPSBmdWxsUGF0aHMubWFwKGZ1bmN0aW9uIChwYXRoKSB7XG4gICAgICAgICAgICAgIHJldHVybiBwYXRoLnNsaWNlKGRpclBhdGgubGVuZ3RoKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgdmFyIHR5cGVzID0gZnVsbFBhdGhzLm1hcChmdW5jdGlvbiAocGF0aCkge1xuICAgICAgICAgICAgICByZXR1cm4gbWltZXR5cGUubG9va3VwKHBhdGgpIHx8IFwidGV4dC9wbGFpblwiO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICByZXR1cm4gdG9NYXAocGF0aHMpKGRhdGFzLm1hcChmdW5jdGlvbiAoZGF0YSwgaSkge1xuICAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHR5cGU6IHR5cGVzW2ldLFxuICAgICAgICAgICAgICAgIGRhdGE6IGRhdGFcbiAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIH0pKTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgfSkudGhlbihmdW5jdGlvbiAoZGlyZWN0b3J5KSB7XG4gICAgICAgICAgcmV0dXJuIG1lcmdlKGRlZmF1bHRQYXRoID8ge1xuICAgICAgICAgICAgXCJcIjogZGlyZWN0b3J5W2RlZmF1bHRQYXRoXVxuICAgICAgICAgIH0gOiB7fSkoZGlyZWN0b3J5KTtcbiAgICAgICAgfSkudGhlbih1cGxvYWREaXJlY3Rvcnkoc3dhcm1VcmwpKTtcbiAgICAgIH07XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IFVwbG9hZEluZm8gLT4gUHJvbWlzZSBTdHJpbmdcbiAgLy8gICBTaW1wbGlmaWVkIG11bHRpLXR5cGUgdXBsb2FkIHdoaWNoIGNhbGxzIHRoZSBjb3JyZWN0XG4gIC8vICAgb25lIGJhc2VkIG9uIHRoZSB0eXBlIG9mIHRoZSBhcmd1bWVudCBnaXZlbi5cblxuXG4gIHZhciBfdXBsb2FkID0gZnVuY3Rpb24gdXBsb2FkKHN3YXJtVXJsKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChhcmcpIHtcbiAgICAgIC8vIFVwbG9hZCByYXcgZGF0YSBmcm9tIGJyb3dzZXJcbiAgICAgIGlmIChhcmcucGljayA9PT0gXCJkYXRhXCIpIHtcbiAgICAgICAgcmV0dXJuIHBpY2suZGF0YSgpLnRoZW4odXBsb2FkRGF0YShzd2FybVVybCkpOyAvLyBVcGxvYWQgYSBmaWxlIGZyb20gYnJvd3NlclxuICAgICAgfSBlbHNlIGlmIChhcmcucGljayA9PT0gXCJmaWxlXCIpIHtcbiAgICAgICAgcmV0dXJuIHBpY2suZmlsZSgpLnRoZW4odXBsb2FkRmlsZShzd2FybVVybCkpOyAvLyBVcGxvYWQgYSBkaXJlY3RvcnkgZnJvbSBicm93c2VyXG4gICAgICB9IGVsc2UgaWYgKGFyZy5waWNrID09PSBcImRpcmVjdG9yeVwiKSB7XG4gICAgICAgIHJldHVybiBwaWNrLmRpcmVjdG9yeSgpLnRoZW4odXBsb2FkRGlyZWN0b3J5KHN3YXJtVXJsKSk7IC8vIFVwbG9hZCBkaXJlY3RvcnkvZmlsZSBmcm9tIGRpc2tcbiAgICAgIH0gZWxzZSBpZiAoYXJnLnBhdGgpIHtcbiAgICAgICAgc3dpdGNoIChhcmcua2luZCkge1xuICAgICAgICAgIGNhc2UgXCJkYXRhXCI6XG4gICAgICAgICAgICByZXR1cm4gdXBsb2FkRGF0YUZyb21EaXNrKHN3YXJtVXJsKShhcmcucGF0aCk7XG5cbiAgICAgICAgICBjYXNlIFwiZmlsZVwiOlxuICAgICAgICAgICAgcmV0dXJuIHVwbG9hZEZpbGVGcm9tRGlzayhzd2FybVVybCkoYXJnLnBhdGgpO1xuXG4gICAgICAgICAgY2FzZSBcImRpcmVjdG9yeVwiOlxuICAgICAgICAgICAgcmV0dXJuIHVwbG9hZERpcmVjdG9yeUZyb21EaXNrKHN3YXJtVXJsKShhcmcuZGVmYXVsdEZpbGUpKGFyZy5wYXRoKTtcbiAgICAgICAgfVxuXG4gICAgICAgIDsgLy8gVXBsb2FkIFVURi04IHN0cmluZyBvciByYXcgZGF0YSAoYnVmZmVyKVxuICAgICAgfSBlbHNlIGlmIChhcmcubGVuZ3RoIHx8IHR5cGVvZiBhcmcgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgICAgcmV0dXJuIHVwbG9hZERhdGEoc3dhcm1VcmwpKGFyZyk7IC8vIFVwbG9hZCBkaXJlY3Rvcnkgd2l0aCBKU09OXG4gICAgICB9IGVsc2UgaWYgKGFyZyBpbnN0YW5jZW9mIE9iamVjdCkge1xuICAgICAgICByZXR1cm4gdXBsb2FkRGlyZWN0b3J5KHN3YXJtVXJsKShhcmcpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKFwiQmFkIGFyZ3VtZW50c1wiKSk7XG4gICAgfTtcbiAgfTsgLy8gU3RyaW5nIC0+IFN0cmluZyAtPiBOdWxsYWJsZSBTdHJpbmcgLT4gUHJvbWlzZSAoU3RyaW5nIHwgVWludDhBcnJheSB8IE1hcCBTdHJpbmcgVWludDhBcnJheSlcbiAgLy8gICBTaW1wbGlmaWVkIG11bHRpLXR5cGUgZG93bmxvYWQgd2hpY2ggY2FsbHMgdGhlIGNvcnJlY3QgZnVuY3Rpb24gYmFzZWQgb25cbiAgLy8gICB0aGUgdHlwZSBvZiB0aGUgYXJndW1lbnQgZ2l2ZW4sIGFuZCBvbiB3aGV0aGVyIHRoZSBTd3dhcm0gYWRkcmVzcyBoYXMgYVxuICAvLyAgIGRpcmVjdG9yeSBvciBhIGZpbGUuXG5cblxuICB2YXIgX2Rvd25sb2FkID0gZnVuY3Rpb24gZG93bmxvYWQoc3dhcm1VcmwpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24gKGhhc2gpIHtcbiAgICAgIHJldHVybiBmdW5jdGlvbiAocGF0aCkge1xuICAgICAgICByZXR1cm4gaXNEaXJlY3Rvcnkoc3dhcm1VcmwpKGhhc2gpLnRoZW4oZnVuY3Rpb24gKGlzRGlyKSB7XG4gICAgICAgICAgaWYgKGlzRGlyKSB7XG4gICAgICAgICAgICByZXR1cm4gcGF0aCA/IGRvd25sb2FkRGlyZWN0b3J5VG9EaXNrKHN3YXJtVXJsKShoYXNoKShwYXRoKSA6IGRvd25sb2FkRGlyZWN0b3J5KHN3YXJtVXJsKShoYXNoKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcmV0dXJuIHBhdGggPyBkb3dubG9hZERhdGFUb0Rpc2soc3dhcm1VcmwpKGhhc2gpKHBhdGgpIDogZG93bmxvYWREYXRhKHN3YXJtVXJsKShoYXNoKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgfTtcbiAgICB9O1xuICB9OyAvLyBTdHJpbmcgLT4gUHJvbWlzZSBTdHJpbmdcbiAgLy8gICBEb3dubG9hZHMgdGhlIFN3YXJtIGJpbmFyaWVzIGludG8gYSBwYXRoLiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IG9ubHlcbiAgLy8gICByZXNvbHZlcyB3aGVuIHRoZSBleGFjdCBTd2FybSBmaWxlIGlzIHRoZXJlLCBhbmQgdmVyaWZpZWQgdG8gYmUgY29ycmVjdC5cbiAgLy8gICBJZiBpdCB3YXMgYWxyZWFkeSB0aGVyZSB0byBiZWdpbiB3aXRoLCBza2lwcyB0aGUgZG93bmxvYWQuXG5cblxuICB2YXIgZG93bmxvYWRCaW5hcnkgPSBmdW5jdGlvbiBkb3dubG9hZEJpbmFyeShwYXRoLCBhcmNoaXZlcykge1xuICAgIHZhciBzeXN0ZW0gPSBvcy5wbGF0Zm9ybSgpLnJlcGxhY2UoXCJ3aW4zMlwiLCBcIndpbmRvd3NcIikgKyBcIi1cIiArIChvcy5hcmNoKCkgPT09IFwieDY0XCIgPyBcImFtZDY0XCIgOiBcIjM4NlwiKTtcbiAgICB2YXIgYXJjaGl2ZSA9IChhcmNoaXZlcyB8fCBkZWZhdWx0QXJjaGl2ZXMpW3N5c3RlbV07XG4gICAgdmFyIGFyY2hpdmVVcmwgPSBkb3dubG9hZFVybCArIGFyY2hpdmUuYXJjaGl2ZSArIFwiLnRhci5nelwiO1xuICAgIHZhciBhcmNoaXZlTUQ1ID0gYXJjaGl2ZS5hcmNoaXZlTUQ1O1xuICAgIHZhciBiaW5hcnlNRDUgPSBhcmNoaXZlLmJpbmFyeU1ENTtcbiAgICByZXR1cm4gZmlsZXMuc2FmZURvd25sb2FkQXJjaGl2ZWQoYXJjaGl2ZVVybCkoYXJjaGl2ZU1ENSkoYmluYXJ5TUQ1KShwYXRoKTtcbiAgfTsgLy8gdHlwZSBTd2FybVNldHVwID0ge1xuICAvLyAgIGFjY291bnQgOiBTdHJpbmcsXG4gIC8vICAgcGFzc3dvcmQgOiBTdHJpbmcsXG4gIC8vICAgZGF0YURpciA6IFN0cmluZyxcbiAgLy8gICBiaW5QYXRoIDogU3RyaW5nLFxuICAvLyAgIGVuc0FwaSA6IFN0cmluZyxcbiAgLy8gICBvbkRvd25sb2FkUHJvZ3Jlc3MgOiBOdW1iZXIgfj4gKCksXG4gIC8vICAgYXJjaGl2ZXMgOiBbe1xuICAvLyAgICAgYXJjaGl2ZTogU3RyaW5nLFxuICAvLyAgICAgYmluYXJ5TUQ1OiBTdHJpbmcsXG4gIC8vICAgICBhcmNoaXZlTUQ1OiBTdHJpbmdcbiAgLy8gICB9XVxuICAvLyB9XG4gIC8vIFN3YXJtU2V0dXAgfj4gUHJvbWlzZSBQcm9jZXNzXG4gIC8vICAgU3RhcnRzIHRoZSBTd2FybSBwcm9jZXNzLlxuXG5cbiAgdmFyIHN0YXJ0UHJvY2VzcyA9IGZ1bmN0aW9uIHN0YXJ0UHJvY2Vzcyhzd2FybVNldHVwKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uIChyZXNvbHZlLCByZWplY3QpIHtcbiAgICAgIHZhciBzcGF3biA9IGNoaWxkX3Byb2Nlc3Muc3Bhd247XG5cbiAgICAgIHZhciBoYXNTdHJpbmcgPSBmdW5jdGlvbiBoYXNTdHJpbmcoc3RyKSB7XG4gICAgICAgIHJldHVybiBmdW5jdGlvbiAoYnVmZmVyKSB7XG4gICAgICAgICAgcmV0dXJuICgnJyArIGJ1ZmZlcikuaW5kZXhPZihzdHIpICE9PSAtMTtcbiAgICAgICAgfTtcbiAgICAgIH07XG5cbiAgICAgIHZhciBhY2NvdW50ID0gc3dhcm1TZXR1cC5hY2NvdW50LFxuICAgICAgICAgIHBhc3N3b3JkID0gc3dhcm1TZXR1cC5wYXNzd29yZCxcbiAgICAgICAgICBkYXRhRGlyID0gc3dhcm1TZXR1cC5kYXRhRGlyLFxuICAgICAgICAgIGVuc0FwaSA9IHN3YXJtU2V0dXAuZW5zQXBpLFxuICAgICAgICAgIHByaXZhdGVLZXkgPSBzd2FybVNldHVwLnByaXZhdGVLZXk7XG4gICAgICB2YXIgU1RBUlRVUF9USU1FT1VUX1NFQ1MgPSAzO1xuICAgICAgdmFyIFdBSVRJTkdfUEFTU1dPUkQgPSAwO1xuICAgICAgdmFyIFNUQVJUSU5HID0gMTtcbiAgICAgIHZhciBMSVNURU5JTkcgPSAyO1xuICAgICAgdmFyIFBBU1NXT1JEX1BST01QVF9IT09LID0gXCJQYXNzcGhyYXNlXCI7XG4gICAgICB2YXIgTElTVEVOSU5HX0hPT0sgPSBcIlN3YXJtIGh0dHAgcHJveHkgc3RhcnRlZFwiO1xuICAgICAgdmFyIHN0YXRlID0gV0FJVElOR19QQVNTV09SRDtcbiAgICAgIHZhciBzd2FybVByb2Nlc3MgPSBzcGF3bihzd2FybVNldHVwLmJpblBhdGgsIFsnLS1ienphY2NvdW50JywgYWNjb3VudCB8fCBwcml2YXRlS2V5LCAnLS1kYXRhZGlyJywgZGF0YURpciwgJy0tZW5zLWFwaScsIGVuc0FwaV0pO1xuXG4gICAgICB2YXIgaGFuZGxlUHJvY2Vzc091dHB1dCA9IGZ1bmN0aW9uIGhhbmRsZVByb2Nlc3NPdXRwdXQoZGF0YSkge1xuICAgICAgICBpZiAoc3RhdGUgPT09IFdBSVRJTkdfUEFTU1dPUkQgJiYgaGFzU3RyaW5nKFBBU1NXT1JEX1BST01QVF9IT09LKShkYXRhKSkge1xuICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgc3RhdGUgPSBTVEFSVElORztcbiAgICAgICAgICAgIHN3YXJtUHJvY2Vzcy5zdGRpbi53cml0ZShwYXNzd29yZCArICdcXG4nKTtcbiAgICAgICAgICB9LCA1MDApO1xuICAgICAgICB9IGVsc2UgaWYgKGhhc1N0cmluZyhMSVNURU5JTkdfSE9PSykoZGF0YSkpIHtcbiAgICAgICAgICBzdGF0ZSA9IExJU1RFTklORztcbiAgICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICAgICAgcmVzb2x2ZShzd2FybVByb2Nlc3MpO1xuICAgICAgICB9XG4gICAgICB9O1xuXG4gICAgICBzd2FybVByb2Nlc3Muc3Rkb3V0Lm9uKCdkYXRhJywgaGFuZGxlUHJvY2Vzc091dHB1dCk7XG4gICAgICBzd2Fy