@tensorflow/tfjs-backend-wasm
Version:
This package adds a WebAssembly backend to TensorFlow.js. It currently supports the following models from our [models](https://github.com/tensorflow/tfjs-models) repo: - BlazeFace - BodyPix - CocoSSD - Face landmarks detection - HandPose - KNN classifier
437 lines • 58.1 kB
JavaScript
/**
* @license
* Copyright 2019 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
*/
import './flags_wasm';
import { DataStorage, deprecationWarn, engine, env, KernelBackend, util } from '@tensorflow/tfjs-core';
import * as wasmFactoryThreadedSimd_import from '../wasm-out/tfjs-backend-wasm-threaded-simd.js';
// @ts-ignore
import { wasmWorkerContents } from '../wasm-out/tfjs-backend-wasm-threaded-simd.worker.js';
import * as wasmFactory_import from '../wasm-out/tfjs-backend-wasm.js';
// This workaround is required for importing in Node.js without using
// the node bundle (for testing). This would not be necessary if we
// flipped esModuleInterop to true, but we likely can't do that since
// google3 does not use it.
const wasmFactoryThreadedSimd = (wasmFactoryThreadedSimd_import.default ||
wasmFactoryThreadedSimd_import);
const wasmFactory = (wasmFactory_import.default || wasmFactory_import);
export class BackendWasm extends KernelBackend {
constructor(wasm) {
super();
this.wasm = wasm;
// 0 is reserved for null data ids.
this.dataIdNextNumber = 1;
this.wasm.tfjs.initWithThreadsCount(threadsCount);
actualThreadsCount = this.wasm.tfjs.getThreadsCount();
this.dataIdMap = new DataStorage(this, engine());
}
write(values, shape, dtype) {
const dataId = { id: this.dataIdNextNumber++ };
this.move(dataId, values, shape, dtype, 1);
return dataId;
}
numDataIds() {
return this.dataIdMap.numDataIds();
}
async time(f) {
const start = util.now();
f();
const kernelMs = util.now() - start;
return { kernelMs };
}
move(dataId, values, shape, dtype, refCount) {
const id = this.dataIdNextNumber++;
if (dtype === 'string') {
const stringBytes = values;
this.dataIdMap.set(dataId, { id, stringBytes, shape, dtype, memoryOffset: null, refCount });
return;
}
const size = util.sizeFromShape(shape);
const numBytes = size * util.bytesPerElement(dtype);
// `>>> 0` is needed for above 2GB allocations because wasm._malloc returns
// a signed int32 instead of an unsigned int32.
// https://v8.dev/blog/4gb-wasm-memory
const memoryOffset = this.wasm._malloc(numBytes) >>> 0;
this.dataIdMap.set(dataId, { id, memoryOffset, shape, dtype, refCount });
this.wasm.tfjs.registerTensor(id, size, memoryOffset);
if (values != null) {
this.wasm.HEAPU8.set(new Uint8Array(values.buffer, values.byteOffset, numBytes), memoryOffset);
}
}
async read(dataId) {
return this.readSync(dataId);
}
readSync(dataId, start, end) {
const { memoryOffset, dtype, shape, stringBytes } = this.dataIdMap.get(dataId);
if (dtype === 'string') {
// Slice all elements.
if ((start == null || start === 0) &&
(end == null || end >= stringBytes.length)) {
return stringBytes;
}
return stringBytes.slice(start, end);
}
start = start || 0;
end = end || util.sizeFromShape(shape);
const bytesPerElement = util.bytesPerElement(dtype);
const bytes = this.wasm.HEAPU8.slice(memoryOffset + start * bytesPerElement, memoryOffset + end * bytesPerElement);
return typedArrayFromBuffer(bytes.buffer, dtype);
}
/**
* Dispose the memory if the dataId has 0 refCount. Return true if the memory
* is released, false otherwise.
* @param dataId
* @oaram force Optional, remove the data regardless of refCount
*/
disposeData(dataId, force = false) {
if (this.dataIdMap.has(dataId)) {
const data = this.dataIdMap.get(dataId);
data.refCount--;
if (!force && data.refCount > 0) {
return false;
}
this.wasm._free(data.memoryOffset);
this.wasm.tfjs.disposeData(data.id);
this.dataIdMap.delete(dataId);
}
return true;
}
/** Return refCount of a `TensorData`. */
refCount(dataId) {
if (this.dataIdMap.has(dataId)) {
const tensorData = this.dataIdMap.get(dataId);
return tensorData.refCount;
}
return 0;
}
incRef(dataId) {
const data = this.dataIdMap.get(dataId);
if (data != null) {
data.refCount++;
}
}
floatPrecision() {
return 32;
}
// Returns the memory offset of a tensor. Useful for debugging and unit
// testing.
getMemoryOffset(dataId) {
return this.dataIdMap.get(dataId).memoryOffset;
}
dispose() {
this.wasm.tfjs.dispose();
if ('PThread' in this.wasm) {
this.wasm.PThread.terminateAllThreads();
}
this.wasm = null;
}
memory() {
return { unreliable: false };
}
/**
* Make a tensor info for the output of an op. If `memoryOffset` is not
* present, this method allocates memory on the WASM heap. If `memoryOffset`
* is present, the memory was allocated elsewhere (in c++) and we just record
* the pointer where that memory lives.
*/
makeOutput(shape, dtype, memoryOffset, values) {
let dataId;
if (memoryOffset == null) {
dataId = this.write(values !== null && values !== void 0 ? values : null, shape, dtype);
}
else {
const id = this.dataIdNextNumber++;
dataId = { id };
this.dataIdMap.set(dataId, { id, memoryOffset, shape, dtype, refCount: 1 });
const size = util.sizeFromShape(shape);
this.wasm.tfjs.registerTensor(id, size, memoryOffset);
}
return { dataId, shape, dtype };
}
typedArrayFromHeap({ shape, dtype, dataId }) {
const buffer = this.wasm.HEAPU8.buffer;
const { memoryOffset } = this.dataIdMap.get(dataId);
const size = util.sizeFromShape(shape);
switch (dtype) {
case 'float32':
return new Float32Array(buffer, memoryOffset, size);
case 'int32':
return new Int32Array(buffer, memoryOffset, size);
case 'bool':
return new Uint8Array(buffer, memoryOffset, size);
default:
throw new Error(`Unknown dtype ${dtype}`);
}
}
}
function createInstantiateWasmFunc(path) {
// this will be replace by rollup plugin patchWechatWebAssembly in
// minprogram's output.
// tslint:disable-next-line:no-any
return (imports, callback) => {
util.fetch(path, { credentials: 'same-origin' }).then((response) => {
if (!response['ok']) {
imports.env.a(`failed to load wasm binary file at '${path}'`);
}
response.arrayBuffer().then(binary => {
WebAssembly.instantiate(binary, imports).then(output => {
callback(output.instance, output.module);
});
});
});
return {};
};
}
/**
* Returns the path of the WASM binary.
* @param simdSupported whether SIMD is supported
* @param threadsSupported whether multithreading is supported
* @param wasmModuleFolder the directory containing the WASM binaries.
*/
function getPathToWasmBinary(simdSupported, threadsSupported, wasmModuleFolder) {
if (wasmPath != null) {
// If wasmPath is defined, the user has supplied a full path to
// the vanilla .wasm binary.
return wasmPath;
}
let path = 'tfjs-backend-wasm.wasm';
if (simdSupported && threadsSupported) {
path = 'tfjs-backend-wasm-threaded-simd.wasm';
}
else if (simdSupported) {
path = 'tfjs-backend-wasm-simd.wasm';
}
if (wasmFileMap != null) {
if (wasmFileMap[path] != null) {
return wasmFileMap[path];
}
}
return wasmModuleFolder + path;
}
/**
* Initializes the wasm module and creates the js <--> wasm bridge.
*
* NOTE: We wrap the wasm module in a object with property 'wasm' instead of
* returning Promise<BackendWasmModule> to avoid freezing Chrome (last tested
* in Chrome 76).
*/
export async function init() {
const [simdSupported, threadsSupported] = await Promise.all([
env().getAsync('WASM_HAS_SIMD_SUPPORT'),
env().getAsync('WASM_HAS_MULTITHREAD_SUPPORT')
]);
return new Promise((resolve, reject) => {
const factoryConfig = {};
/**
* This function overrides the Emscripten module locateFile utility.
* @param path The relative path to the file that needs to be loaded.
* @param prefix The path to the main JavaScript file's directory.
*/
factoryConfig.locateFile = (path, prefix) => {
if (path.endsWith('.worker.js')) {
// Escape '\n' because Blob will turn it into a newline.
// There should be a setting for this, but 'endings: "native"' does
// not seem to work.
const response = wasmWorkerContents.replace(/\n/g, '\\n');
const blob = new Blob([response], { type: 'application/javascript' });
return URL.createObjectURL(blob);
}
if (path.endsWith('.wasm')) {
return getPathToWasmBinary(simdSupported, threadsSupported, wasmPathPrefix != null ? wasmPathPrefix : prefix);
}
return prefix + path;
};
// Use the instantiateWasm override when system fetch is not available.
// Reference:
// https://github.com/emscripten-core/emscripten/blob/2bca083cbbd5a4133db61fbd74d04f7feecfa907/tests/manual_wasm_instantiate.html#L170
if (customFetch) {
factoryConfig.instantiateWasm =
createInstantiateWasmFunc(getPathToWasmBinary(simdSupported, threadsSupported, wasmPathPrefix != null ? wasmPathPrefix : ''));
}
let initialized = false;
factoryConfig.onAbort = () => {
if (initialized) {
// Emscripten already called console.warn so no need to double log.
return;
}
if (initAborted) {
// Emscripten calls `onAbort` twice, resulting in double error
// messages.
return;
}
initAborted = true;
const rejectMsg = 'Make sure the server can serve the `.wasm` file relative to the ' +
'bundled js file. For more details see https://github.com/tensorflow/tfjs/blob/master/tfjs-backend-wasm/README.md#using-bundlers';
reject({ message: rejectMsg });
};
let wasm;
// If `wasmPath` has been defined we must initialize the vanilla module.
if (threadsSupported && simdSupported && wasmPath == null) {
factoryConfig.mainScriptUrlOrBlob = new Blob([`var WasmBackendModuleThreadedSimd = ` +
wasmFactoryThreadedSimd.toString()], { type: 'text/javascript' });
wasm = wasmFactoryThreadedSimd(factoryConfig);
}
else {
// The wasmFactory works for both vanilla and SIMD binaries.
wasm = wasmFactory(factoryConfig);
}
// The `wasm` promise will resolve to the WASM module created by
// the factory, but it might have had errors during creation. Most
// errors are caught by the onAbort callback defined above.
// However, some errors, such as those occurring from a
// failed fetch, result in this promise being rejected. These are
// caught and re-rejected below.
wasm.then((module) => {
initialized = true;
initAborted = false;
const voidReturnType = null;
// Using the tfjs namespace to avoid conflict with emscripten's API.
module.tfjs = {
init: module.cwrap('init', null, []),
initWithThreadsCount: module.cwrap('init_with_threads_count', null, ['number']),
getThreadsCount: module.cwrap('get_threads_count', 'number', []),
registerTensor: module.cwrap('register_tensor', null, [
'number',
'number',
'number', // memoryOffset
]),
disposeData: module.cwrap('dispose_data', voidReturnType, ['number']),
dispose: module.cwrap('dispose', voidReturnType, []),
};
resolve({ wasm: module });
})
.catch(reject);
});
}
function typedArrayFromBuffer(buffer, dtype) {
switch (dtype) {
case 'float32':
return new Float32Array(buffer);
case 'int32':
return new Int32Array(buffer);
case 'bool':
return new Uint8Array(buffer);
default:
throw new Error(`Unknown dtype ${dtype}`);
}
}
const wasmBinaryNames = [
'tfjs-backend-wasm.wasm', 'tfjs-backend-wasm-simd.wasm',
'tfjs-backend-wasm-threaded-simd.wasm'
];
let wasmPath = null;
let wasmPathPrefix = null;
let wasmFileMap = {};
let initAborted = false;
let customFetch = false;
/**
* @deprecated Use `setWasmPaths` instead.
* Sets the path to the `.wasm` file which will be fetched when the wasm
* backend is initialized. See
* https://github.com/tensorflow/tfjs/blob/master/tfjs-backend-wasm/README.md#using-bundlers
* for more details.
* @param path wasm file path or url
* @param usePlatformFetch optional boolean to use platform fetch to download
* the wasm file, default to false.
*
* @doc {heading: 'Environment', namespace: 'wasm'}
*/
export function setWasmPath(path, usePlatformFetch = false) {
deprecationWarn('setWasmPath has been deprecated in favor of setWasmPaths and' +
' will be removed in a future release.');
if (initAborted) {
throw new Error('The WASM backend was already initialized. Make sure you call ' +
'`setWasmPath()` before you call `tf.setBackend()` or `tf.ready()`');
}
wasmPath = path;
customFetch = usePlatformFetch;
}
/**
* Configures the locations of the WASM binaries.
*
* ```js
* setWasmPaths({
* 'tfjs-backend-wasm.wasm': 'renamed.wasm',
* 'tfjs-backend-wasm-simd.wasm': 'renamed-simd.wasm',
* 'tfjs-backend-wasm-threaded-simd.wasm': 'renamed-threaded-simd.wasm'
* });
* tf.setBackend('wasm');
* ```
*
* @param prefixOrFileMap This can be either a string or object:
* - (string) The path to the directory where the WASM binaries are located.
* Note that this prefix will be used to load each binary (vanilla,
* SIMD-enabled, threading-enabled, etc.).
* - (object) Mapping from names of WASM binaries to custom
* full paths specifying the locations of those binaries. This is useful if
* your WASM binaries are not all located in the same directory, or if your
* WASM binaries have been renamed.
* @param usePlatformFetch optional boolean to use platform fetch to download
* the wasm file, default to false.
*
* @doc {heading: 'Environment', namespace: 'wasm'}
*/
export function setWasmPaths(prefixOrFileMap, usePlatformFetch = false) {
if (initAborted) {
throw new Error('The WASM backend was already initialized. Make sure you call ' +
'`setWasmPaths()` before you call `tf.setBackend()` or ' +
'`tf.ready()`');
}
if (typeof prefixOrFileMap === 'string') {
wasmPathPrefix = prefixOrFileMap;
}
else {
wasmFileMap = prefixOrFileMap;
const missingPaths = wasmBinaryNames.filter(name => wasmFileMap[name] == null);
if (missingPaths.length > 0) {
throw new Error(`There were no entries found for the following binaries: ` +
`${missingPaths.join(',')}. Please either call setWasmPaths with a ` +
`map providing a path for each binary, or with a string indicating ` +
`the directory where all the binaries can be found.`);
}
}
customFetch = usePlatformFetch;
}
/** Used in unit tests. */
export function resetWasmPath() {
wasmPath = null;
wasmPathPrefix = null;
wasmFileMap = {};
customFetch = false;
initAborted = false;
}
let threadsCount = -1;
let actualThreadsCount = -1;
/**
* Sets the number of threads that will be used by XNNPACK to create
* threadpool (default to the number of logical CPU cores).
*
* This must be called before calling `tf.setBackend('wasm')`.
*/
export function setThreadsCount(numThreads) {
threadsCount = numThreads;
}
/**
* Gets the actual threads count that is used by XNNPACK.
*
* It is set after the backend is intialized.
*/
export function getThreadsCount() {
if (actualThreadsCount === -1) {
throw new Error(`WASM backend not initialized.`);
}
return actualThreadsCount;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFja2VuZF93YXNtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vdGZqcy1iYWNrZW5kLXdhc20vc3JjL2JhY2tlbmRfd2FzbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxPQUFPLGNBQWMsQ0FBQztBQUV0QixPQUFPLEVBQWtDLFdBQVcsRUFBWSxlQUFlLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxhQUFhLEVBQWMsSUFBSSxFQUFDLE1BQU0sdUJBQXVCLENBQUM7QUFJNUosT0FBTyxLQUFLLDhCQUE4QixNQUFNLGdEQUFnRCxDQUFDO0FBQ2pHLGFBQWE7QUFDYixPQUFPLEVBQUMsa0JBQWtCLEVBQUMsTUFBTSx1REFBdUQsQ0FBQztBQUN6RixPQUFPLEtBQUssa0JBQWtCLE1BQU0sa0NBQWtDLENBQUM7QUFFdkUscUVBQXFFO0FBQ3JFLG1FQUFtRTtBQUNuRSxxRUFBcUU7QUFDckUsMkJBQTJCO0FBQzNCLE1BQU0sdUJBQXVCLEdBQUcsQ0FBQyw4QkFBOEIsQ0FBQyxPQUFPO0lBQ3RDLDhCQUE4QixDQUNkLENBQUM7QUFDbEQsTUFBTSxXQUFXLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLElBQUksa0JBQWtCLENBQ2hDLENBQUM7QUFjdEMsTUFBTSxPQUFPLFdBQVksU0FBUSxhQUFhO0lBSzVDLFlBQW1CLElBQXFEO1FBQ3RFLEtBQUssRUFBRSxDQUFDO1FBRFMsU0FBSSxHQUFKLElBQUksQ0FBaUQ7UUFKeEUsbUNBQW1DO1FBQzNCLHFCQUFnQixHQUFHLENBQUMsQ0FBQztRQUszQixJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUNsRCxrQkFBa0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztRQUN0RCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksV0FBVyxDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQ25ELENBQUM7SUFFUSxLQUFLLENBQ1YsTUFBdUMsRUFBRSxLQUFlLEVBQ3hELEtBQWU7UUFDakIsTUFBTSxNQUFNLEdBQUcsRUFBQyxFQUFFLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixFQUFFLEVBQUMsQ0FBQztRQUM3QyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQztRQUMzQyxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRVEsVUFBVTtRQUNqQixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFFLENBQUM7SUFDckMsQ0FBQztJQUVRLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBYTtRQUMvQixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDekIsQ0FBQyxFQUFFLENBQUM7UUFDSixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsS0FBSyxDQUFDO1FBQ3BDLE9BQU8sRUFBQyxRQUFRLEVBQUMsQ0FBQztJQUNwQixDQUFDO0lBRVEsSUFBSSxDQUNULE1BQWMsRUFBRSxNQUF1QyxFQUFFLEtBQWUsRUFDeEUsS0FBZSxFQUFFLFFBQWdCO1FBQ25DLE1BQU0sRUFBRSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ25DLElBQUksS0FBSyxLQUFLLFFBQVEsRUFBRTtZQUN0QixNQUFNLFdBQVcsR0FBRyxNQUFzQixDQUFDO1lBQzNDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUNkLE1BQU0sRUFDTixFQUFDLEVBQUUsRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBQyxDQUFDLENBQUM7WUFDbkUsT0FBTztTQUNSO1FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN2QyxNQUFNLFFBQVEsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUVwRCwyRUFBMkU7UUFDM0UsK0NBQStDO1FBQy9DLHNDQUFzQztRQUN0QyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFdkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLEVBQUMsRUFBRSxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBQyxDQUFDLENBQUM7UUFFdkUsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFFdEQsSUFBSSxNQUFNLElBQUksSUFBSSxFQUFFO1lBQ2xCLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FDaEIsSUFBSSxVQUFVLENBQ1QsTUFBa0MsQ0FBQyxNQUFNLEVBQ3pDLE1BQWtDLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQyxFQUM3RCxZQUFZLENBQUMsQ0FBQztTQUNuQjtJQUNILENBQUM7SUFFUSxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQWM7UUFDaEMsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9CLENBQUM7SUFFUSxRQUFRLENBQUMsTUFBYyxFQUFFLEtBQWMsRUFBRSxHQUFZO1FBRTVELE1BQU0sRUFBQyxZQUFZLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxXQUFXLEVBQUMsR0FDM0MsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDL0IsSUFBSSxLQUFLLEtBQUssUUFBUSxFQUFFO1lBQ3RCLHNCQUFzQjtZQUN0QixJQUFJLENBQUMsS0FBSyxJQUFJLElBQUksSUFBSSxLQUFLLEtBQUssQ0FBQyxDQUFDO2dCQUM5QixDQUFDLEdBQUcsSUFBSSxJQUFJLElBQUksR0FBRyxJQUFJLFdBQVcsQ0FBQyxNQUFNLENBQUMsRUFBRTtnQkFDOUMsT0FBTyxXQUFXLENBQUM7YUFDcEI7WUFDRCxPQUFPLFdBQVcsQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1NBQ3RDO1FBQ0QsS0FBSyxHQUFHLEtBQUssSUFBSSxDQUFDLENBQUM7UUFDbkIsR0FBRyxHQUFHLEdBQUcsSUFBSSxJQUFJLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZDLE1BQU0sZUFBZSxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDcEQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUNoQyxZQUFZLEdBQUcsS0FBSyxHQUFHLGVBQWUsRUFDdEMsWUFBWSxHQUFHLEdBQUcsR0FBRyxlQUFlLENBQUMsQ0FBQztRQUMxQyxPQUFPLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDbkQsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ00sV0FBVyxDQUFDLE1BQWMsRUFBRSxLQUFLLEdBQUcsS0FBSztRQUNoRCxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFO1lBQzlCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ3hDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNoQixJQUFJLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxRQUFRLEdBQUcsQ0FBQyxFQUFFO2dCQUMvQixPQUFPLEtBQUssQ0FBQzthQUNkO1lBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQ25DLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDcEMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7U0FDL0I7UUFDRCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRCx5Q0FBeUM7SUFDaEMsUUFBUSxDQUFDLE1BQWM7UUFDOUIsSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUM5QixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM5QyxPQUFPLFVBQVUsQ0FBQyxRQUFRLENBQUM7U0FDNUI7UUFDRCxPQUFPLENBQUMsQ0FBQztJQUNYLENBQUM7SUFFUSxNQUFNLENBQUMsTUFBYztRQUM1QixNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN4QyxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUU7WUFDaEIsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1NBQ2pCO0lBQ0gsQ0FBQztJQUVRLGNBQWM7UUFDckIsT0FBTyxFQUFFLENBQUM7SUFDWixDQUFDO0lBRUQsdUVBQXVFO0lBQ3ZFLFdBQVc7SUFDWCxlQUFlLENBQUMsTUFBYztRQUM1QixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLFlBQVksQ0FBQztJQUNqRCxDQUFDO0lBRVEsT0FBTztRQUNkLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3pCLElBQUksU0FBUyxJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUU7WUFDMUIsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztTQUN6QztRQUNELElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO0lBQ25CLENBQUM7SUFFUSxNQUFNO1FBQ2IsT0FBTyxFQUFDLFVBQVUsRUFBRSxLQUFLLEVBQUMsQ0FBQztJQUM3QixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxVQUFVLENBQ04sS0FBZSxFQUFFLEtBQWUsRUFBRSxZQUFxQixFQUN2RCxNQUFtQztRQUNyQyxJQUFJLE1BQVUsQ0FBQztRQUNmLElBQUksWUFBWSxJQUFJLElBQUksRUFBRTtZQUN4QixNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLGFBQU4sTUFBTSxjQUFOLE1BQU0sR0FBSSxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO1NBQ25EO2FBQU07WUFDTCxNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUNuQyxNQUFNLEdBQUcsRUFBQyxFQUFFLEVBQUMsQ0FBQztZQUNkLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxFQUFDLEVBQUUsRUFBRSxZQUFZLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsQ0FBQyxFQUFDLENBQUMsQ0FBQztZQUMxRSxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3ZDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxFQUFFLEVBQUUsSUFBSSxFQUFFLFlBQVksQ0FBQyxDQUFDO1NBQ3ZEO1FBQ0QsT0FBTyxFQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFDLENBQUM7SUFDaEMsQ0FBQztJQUVELGtCQUFrQixDQUFDLEVBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQWE7UUFFbkQsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO1FBQ3ZDLE1BQU0sRUFBQyxZQUFZLEVBQUMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNsRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3ZDLFFBQVEsS0FBSyxFQUFFO1lBQ2IsS0FBSyxTQUFTO2dCQUNaLE9BQU8sSUFBSSxZQUFZLENBQUMsTUFBTSxFQUFFLFlBQVksRUFBRSxJQUFJLENBQUMsQ0FBQztZQUN0RCxLQUFLLE9BQU87Z0JBQ1YsT0FBTyxJQUFJLFVBQVUsQ0FBQyxNQUFNLEVBQUUsWUFBWSxFQUFFLElBQUksQ0FBQyxDQUFDO1lBQ3BELEtBQUssTUFBTTtnQkFDVCxPQUFPLElBQUksVUFBVSxDQUFDLE1BQU0sRUFBRSxZQUFZLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDcEQ7Z0JBQ0UsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsS0FBSyxFQUFFLENBQUMsQ0FBQztTQUM3QztJQUNILENBQUM7Q0FDRjtBQUVELFNBQVMseUJBQXlCLENBQUMsSUFBWTtJQUM3QyxrRUFBa0U7SUFDbEUsdUJBQXVCO0lBQ3ZCLGtDQUFrQztJQUNsQyxPQUFPLENBQUMsT0FBWSxFQUFFLFFBQWEsRUFBRSxFQUFFO1FBQ3JDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLEVBQUMsV0FBVyxFQUFFLGFBQWEsRUFBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7WUFDL0QsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRTtnQkFDbkIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsdUNBQXVDLElBQUksR0FBRyxDQUFDLENBQUM7YUFDL0Q7WUFDRCxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFO2dCQUNuQyxXQUFXLENBQUMsV0FBVyxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUU7b0JBQ3JELFFBQVEsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDM0MsQ0FBQyxDQUFDLENBQUM7WUFDTCxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxFQUFFLENBQUM7SUFDWixDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLG1CQUFtQixDQUN4QixhQUFzQixFQUFFLGdCQUF5QixFQUNqRCxnQkFBd0I7SUFDMUIsSUFBSSxRQUFRLElBQUksSUFBSSxFQUFFO1FBQ3BCLCtEQUErRDtRQUMvRCw0QkFBNEI7UUFDNUIsT0FBTyxRQUFRLENBQUM7S0FDakI7SUFFRCxJQUFJLElBQUksR0FBbUIsd0JBQXdCLENBQUM7SUFDcEQsSUFBSSxhQUFhLElBQUksZ0JBQWdCLEVBQUU7UUFDckMsSUFBSSxHQUFHLHNDQUFzQyxDQUFDO0tBQy9DO1NBQU0sSUFBSSxhQUFhLEVBQUU7UUFDeEIsSUFBSSxHQUFHLDZCQUE2QixDQUFDO0tBQ3RDO0lBRUQsSUFBSSxXQUFXLElBQUksSUFBSSxFQUFFO1FBQ3ZCLElBQUksV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksRUFBRTtZQUM3QixPQUFPLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUMxQjtLQUNGO0lBRUQsT0FBTyxnQkFBZ0IsR0FBRyxJQUFJLENBQUM7QUFDakMsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsSUFBSTtJQUN4QixNQUFNLENBQUMsYUFBYSxFQUFFLGdCQUFnQixDQUFDLEdBQUcsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDO1FBQzFELEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyx1QkFBdUIsQ0FBQztRQUN2QyxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsOEJBQThCLENBQUM7S0FDL0MsQ0FBQyxDQUFDO0lBRUgsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtRQUNyQyxNQUFNLGFBQWEsR0FBc0IsRUFBRSxDQUFDO1FBRTVDOzs7O1dBSUc7UUFDSCxhQUFhLENBQUMsVUFBVSxHQUFHLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQzFDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsRUFBRTtnQkFDL0Isd0RBQXdEO2dCQUN4RCxtRUFBbUU7Z0JBQ25FLG9CQUFvQjtnQkFDcEIsTUFBTSxRQUFRLEdBQUksa0JBQTZCLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztnQkFDdEUsTUFBTSxJQUFJLEdBQUcsSUFBSSxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFDLElBQUksRUFBRSx3QkFBd0IsRUFBQyxDQUFDLENBQUM7Z0JBQ3BFLE9BQU8sR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNsQztZQUVELElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsRUFBRTtnQkFDMUIsT0FBTyxtQkFBbUIsQ0FDdEIsYUFBd0IsRUFBRSxnQkFBMkIsRUFDckQsY0FBYyxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQzthQUN2RDtZQUNELE9BQU8sTUFBTSxHQUFHLElBQUksQ0FBQztRQUN2QixDQUFDLENBQUM7UUFFRix1RUFBdUU7UUFDdkUsYUFBYTtRQUNiLHNJQUFzSTtRQUN0SSxJQUFJLFdBQVcsRUFBRTtZQUNmLGFBQWEsQ0FBQyxlQUFlO2dCQUN6Qix5QkFBeUIsQ0FBQyxtQkFBbUIsQ0FDekMsYUFBd0IsRUFBRSxnQkFBMkIsRUFDckQsY0FBYyxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1NBQ3hEO1FBRUQsSUFBSSxXQUFXLEdBQUcsS0FBSyxDQUFDO1FBQ3hCLGFBQWEsQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQzNCLElBQUksV0FBVyxFQUFFO2dCQUNmLG1FQUFtRTtnQkFDbkUsT0FBTzthQUNSO1lBQ0QsSUFBSSxXQUFXLEVBQUU7Z0JBQ2YsOERBQThEO2dCQUM5RCxZQUFZO2dCQUNaLE9BQU87YUFDUjtZQUNELFdBQVcsR0FBRyxJQUFJLENBQUM7WUFDbkIsTUFBTSxTQUFTLEdBQ1gsa0VBQWtFO2dCQUNsRSxpSUFBaUksQ0FBQztZQUN0SSxNQUFNLENBQUMsRUFBQyxPQUFPLEVBQUUsU0FBUyxFQUFDLENBQUMsQ0FBQztRQUMvQixDQUFDLENBQUM7UUFFRixJQUFJLElBQWdDLENBQUM7UUFDckMsd0VBQXdFO1FBQ3hFLElBQUksZ0JBQWdCLElBQUksYUFBYSxJQUFJLFFBQVEsSUFBSSxJQUFJLEVBQUU7WUFDekQsYUFBYSxDQUFDLG1CQUFtQixHQUFHLElBQUksSUFBSSxDQUN4QyxDQUFDLHNDQUFzQztvQkFDdEMsdUJBQXVCLENBQUMsUUFBUSxFQUFFLENBQUMsRUFDcEMsRUFBQyxJQUFJLEVBQUUsaUJBQWlCLEVBQUMsQ0FBQyxDQUFDO1lBQy9CLElBQUksR0FBRyx1QkFBdUIsQ0FBQyxhQUFhLENBQUMsQ0FBQztTQUMvQzthQUFNO1lBQ0wsNERBQTREO1lBQzVELElBQUksR0FBRyxXQUFXLENBQUMsYUFBYSxDQUFDLENBQUM7U0FDbkM7UUFFRCxnRUFBZ0U7UUFDaEUsa0VBQWtFO1FBQ2xFLDJEQUEyRDtRQUMzRCx1REFBdUQ7UUFDdkQsaUVBQWlFO1FBQ2pFLGdDQUFnQztRQUNoQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUU7WUFDZixXQUFXLEdBQUcsSUFBSSxDQUFDO1lBQ25CLFdBQVcsR0FBRyxLQUFLLENBQUM7WUFFcEIsTUFBTSxjQUFjLEdBQVcsSUFBSSxDQUFDO1lBQ3BDLG9FQUFvRTtZQUNwRSxNQUFNLENBQUMsSUFBSSxHQUFHO2dCQUNaLElBQUksRUFBRSxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsRUFBRSxDQUFDO2dCQUNwQyxvQkFBb0IsRUFDaEIsTUFBTSxDQUFDLEtBQUssQ0FBQyx5QkFBeUIsRUFBRSxJQUFJLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDN0QsZUFBZSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsbUJBQW1CLEVBQUUsUUFBUSxFQUFFLEVBQUUsQ0FBQztnQkFDaEUsY0FBYyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQ3hCLGlCQUFpQixFQUFFLElBQUksRUFDdkI7b0JBQ0UsUUFBUTtvQkFDUixRQUFRO29CQUNSLFFBQVEsRUFBRyxlQUFlO2lCQUMzQixDQUFDO2dCQUNOLFdBQVcsRUFDUCxNQUFNLENBQUMsS0FBSyxDQUFDLGNBQWMsRUFBRSxjQUFjLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDNUQsT0FBTyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFLGNBQWMsRUFBRSxFQUFFLENBQUM7YUFDckQsQ0FBQztZQUVGLE9BQU8sQ0FBQyxFQUFDLElBQUksRUFBRSxNQUFNLEVBQUMsQ0FBQyxDQUFDO1FBQzFCLENBQUMsQ0FBQzthQUNELEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNyQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxTQUFTLG9CQUFvQixDQUN6QixNQUFtQixFQUFFLEtBQWU7SUFDdEMsUUFBUSxLQUFLLEVBQUU7UUFDYixLQUFLLFNBQVM7WUFDWixPQUFPLElBQUksWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2xDLEtBQUssT0FBTztZQUNWLE9BQU8sSUFBSSxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDaEMsS0FBSyxNQUFNO1lBQ1QsT0FBTyxJQUFJLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQztZQUNFLE1BQU0sSUFBSSxLQUFLLENBQUMsaUJBQWlCLEtBQUssRUFBRSxDQUFDLENBQUM7S0FDN0M7QUFDSCxDQUFDO0FBRUQsTUFBTSxlQUFlLEdBQUc7SUFDdEIsd0JBQXdCLEVBQUUsNkJBQTZCO0lBQ3ZELHNDQUFzQztDQUM5QixDQUFFO0FBR1osSUFBSSxRQUFRLEdBQVcsSUFBSSxDQUFDO0FBQzVCLElBQUksY0FBYyxHQUFXLElBQUksQ0FBQztBQUNsQyxJQUFJLFdBQVcsR0FBdUMsRUFBRSxDQUFDO0FBQ3pELElBQUksV0FBVyxHQUFHLEtBQUssQ0FBQztBQUN4QixJQUFJLFdBQVcsR0FBRyxLQUFLLENBQUM7QUFFeEI7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUFDLElBQVksRUFBRSxnQkFBZ0IsR0FBRyxLQUFLO0lBQ2hFLGVBQWUsQ0FDWCw4REFBOEQ7UUFDOUQsdUNBQXVDLENBQUMsQ0FBQztJQUM3QyxJQUFJLFdBQVcsRUFBRTtRQUNmLE1BQU0sSUFBSSxLQUFLLENBQ1gsK0RBQStEO1lBQy9ELG1FQUFtRSxDQUFDLENBQUM7S0FDMUU7SUFDRCxRQUFRLEdBQUcsSUFBSSxDQUFDO0lBQ2hCLFdBQVcsR0FBRyxnQkFBZ0IsQ0FBQztBQUNqQyxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCRztBQUNILE1BQU0sVUFBVSxZQUFZLENBQ3hCLGVBQTBELEVBQzFELGdCQUFnQixHQUFHLEtBQUs7SUFDMUIsSUFBSSxXQUFXLEVBQUU7UUFDZixNQUFNLElBQUksS0FBSyxDQUNYLCtEQUErRDtZQUMvRCx3REFBd0Q7WUFDeEQsY0FBYyxDQUFDLENBQUM7S0FDckI7SUFFRCxJQUFJLE9BQU8sZUFBZSxLQUFLLFFBQVEsRUFBRTtRQUN2QyxjQUFjLEdBQUcsZUFBZSxDQUFDO0tBQ2xDO1NBQU07UUFDTCxXQUFXLEdBQUcsZUFBZSxDQUFDO1FBQzlCLE1BQU0sWUFBWSxHQUNkLGVBQWUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLENBQUM7UUFDOUQsSUFBSSxZQUFZLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtZQUMzQixNQUFNLElBQUksS0FBSyxDQUNYLDBEQUEwRDtnQkFDMUQsR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQywyQ0FBMkM7Z0JBQ3BFLG9FQUFvRTtnQkFDcEUsb0RBQW9ELENBQUMsQ0FBQztTQUMzRDtLQUNGO0lBRUQsV0FBVyxHQUFHLGdCQUFnQixDQUFDO0FBQ2pDLENBQUM7QUFFRCwwQkFBMEI7QUFDMUIsTUFBTSxVQUFVLGFBQWE7SUFDM0IsUUFBUSxHQUFHLElBQUksQ0FBQztJQUNoQixjQUFjLEdBQUcsSUFBSSxDQUFDO0lBQ3RCLFdBQVcsR0FBRyxFQUFFLENBQUM7SUFDakIsV0FBVyxHQUFHLEtBQUssQ0FBQztJQUNwQixXQUFXLEdBQUcsS0FBSyxDQUFDO0FBQ3RCLENBQUM7QUFFRCxJQUFJLFlBQVksR0FBRyxDQUFDLENBQUMsQ0FBQztBQUN0QixJQUFJLGtCQUFrQixHQUFHLENBQUMsQ0FBQyxDQUFDO0FBRTVCOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGVBQWUsQ0FBQyxVQUFrQjtJQUNoRCxZQUFZLEdBQUcsVUFBVSxDQUFDO0FBQzVCLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLGVBQWU7SUFDN0IsSUFBSSxrQkFBa0IsS0FBSyxDQUFDLENBQUMsRUFBRTtRQUM3QixNQUFNLElBQUksS0FBSyxDQUFDLCtCQUErQixDQUFDLENBQUM7S0FDbEQ7SUFDRCxPQUFPLGtCQUFrQixDQUFDO0FBQzVCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBsaWNlbnNlXG4gKiBDb3B5cmlnaHQgMjAxOSBHb29nbGUgTExDLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlIFwiTGljZW5zZVwiKTtcbiAqIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS5cbiAqIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdFxuICpcbiAqIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMFxuICpcbiAqIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmVcbiAqIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuIFwiQVMgSVNcIiBCQVNJUyxcbiAqIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuICogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZFxuICogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5pbXBvcnQgJy4vZmxhZ3Nfd2FzbSc7XG5cbmltcG9ydCB7YmFja2VuZF91dGlsLCBCYWNrZW5kVGltaW5nSW5mbywgRGF0YVN0b3JhZ2UsIERhdGFUeXBlLCBkZXByZWNhdGlvbldhcm4sIGVuZ2luZSwgZW52LCBLZXJuZWxCYWNrZW5kLCBUZW5zb3JJbmZvLCB1dGlsfSBmcm9tICdAdGVuc29yZmxvdy90ZmpzLWNvcmUnO1xuXG5pbXBvcnQge0JhY2tlbmRXYXNtTW9kdWxlLCBXYXNtRmFjdG9yeUNvbmZpZ30gZnJvbSAnLi4vd2FzbS1vdXQvdGZqcy1iYWNrZW5kLXdhc20nO1xuaW1wb3J0IHtCYWNrZW5kV2FzbVRocmVhZGVkU2ltZE1vZHVsZX0gZnJvbSAnLi4vd2FzbS1vdXQvdGZqcy1iYWNrZW5kLXdhc20tdGhyZWFkZWQtc2ltZCc7XG5pbXBvcnQgKiBhcyB3YXNtRmFjdG9yeVRocmVhZGVkU2ltZF9pbXBvcnQgZnJvbSAnLi4vd2FzbS1vdXQvdGZqcy1iYWNrZW5kLXdhc20tdGhyZWFkZWQtc2ltZC5qcyc7XG4vLyBAdHMtaWdub3JlXG5pbXBvcnQge3dhc21Xb3JrZXJDb250ZW50c30gZnJvbSAnLi4vd2FzbS1vdXQvdGZqcy1iYWNrZW5kLXdhc20tdGhyZWFkZWQtc2ltZC53b3JrZXIuanMnO1xuaW1wb3J0ICogYXMgd2FzbUZhY3RvcnlfaW1wb3J0IGZyb20gJy4uL3dhc20tb3V0L3RmanMtYmFja2VuZC13YXNtLmpzJztcblxuLy8gVGhpcyB3b3JrYXJvdW5kIGlzIHJlcXVpcmVkIGZvciBpbXBvcnRpbmcgaW4gTm9kZS5qcyB3aXRob3V0IHVzaW5nXG4vLyB0aGUgbm9kZSBidW5kbGUgKGZvciB0ZXN0aW5nKS4gVGhpcyB3b3VsZCBub3QgYmUgbmVjZXNzYXJ5IGlmIHdlXG4vLyBmbGlwcGVkIGVzTW9kdWxlSW50ZXJvcCB0byB0cnVlLCBidXQgd2UgbGlrZWx5IGNhbid0IGRvIHRoYXQgc2luY2Vcbi8vIGdvb2dsZTMgZG9lcyBub3QgdXNlIGl0LlxuY29uc3Qgd2FzbUZhY3RvcnlUaHJlYWRlZFNpbWQgPSAod2FzbUZhY3RvcnlUaHJlYWRlZFNpbWRfaW1wb3J0LmRlZmF1bHQgfHxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdhc21GYWN0b3J5VGhyZWFkZWRTaW1kX2ltcG9ydCkgYXNcbiAgICB0eXBlb2Ygd2FzbUZhY3RvcnlUaHJlYWRlZFNpbWRfaW1wb3J0LmRlZmF1bHQ7XG5jb25zdCB3YXNtRmFjdG9yeSA9ICh3YXNtRmFjdG9yeV9pbXBvcnQuZGVmYXVsdCB8fCB3YXNtRmFjdG9yeV9pbXBvcnQpIGFzXG4gICAgdHlwZW9mIHdhc21GYWN0b3J5X2ltcG9ydC5kZWZhdWx0O1xuXG5pbnRlcmZhY2UgVGVuc29yRGF0YSB7XG4gIGlkOiBudW1iZXI7XG4gIG1lbW9yeU9mZnNldDogbnVtYmVyO1xuICBzaGFwZTogbnVtYmVyW107XG4gIGR0eXBlOiBEYXRhVHlwZTtcbiAgcmVmQ291bnQ6IG51bWJlcjtcbiAgLyoqIE9ubHkgdXNlZCBmb3Igc3RyaW5nIHRlbnNvcnMsIHN0b3JpbmcgZW5jb2RlZCBieXRlcy4gKi9cbiAgc3RyaW5nQnl0ZXM/OiBVaW50OEFycmF5W107XG59XG5cbmV4cG9ydCB0eXBlIERhdGFJZCA9IG9iamVjdDsgIC8vIG9iamVjdCBpbnN0ZWFkIG9mIHt9IHRvIGZvcmNlIG5vbi1wcmltaXRpdmUuXG5cbmV4cG9ydCBjbGFzcyBCYWNrZW5kV2FzbSBleHRlbmRzIEtlcm5lbEJhY2tlbmQge1xuICAvLyAwIGlzIHJlc2VydmVkIGZvciBudWxsIGRhdGEgaWRzLlxuICBwcml2YXRlIGRhdGFJZE5leHROdW1iZXIgPSAxO1xuICBkYXRhSWRNYXA6IERhdGFTdG9yYWdlPFRlbnNvckRhdGE+O1xuXG4gIGNvbnN0cnVjdG9yKHB1YmxpYyB3YXNtOiBCYWNrZW5kV2FzbU1vZHVsZXxCYWNrZW5kV2FzbVRocmVhZGVkU2ltZE1vZHVsZSkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy53YXNtLnRmanMuaW5pdFdpdGhUaHJlYWRzQ291bnQodGhyZWFkc0NvdW50KTtcbiAgICBhY3R1YWxUaHJlYWRzQ291bnQgPSB0aGlzLndhc20udGZqcy5nZXRUaHJlYWRzQ291bnQoKTtcbiAgICB0aGlzLmRhdGFJZE1hcCA9IG5ldyBEYXRhU3RvcmFnZSh0aGlzLCBlbmdpbmUoKSk7XG4gIH1cblxuICBvdmVycmlkZSB3cml0ZShcbiAgICAgIHZhbHVlczogYmFja2VuZF91dGlsLkJhY2tlbmRWYWx1ZXN8bnVsbCwgc2hhcGU6IG51bWJlcltdLFxuICAgICAgZHR5cGU6IERhdGFUeXBlKTogRGF0YUlkIHtcbiAgICBjb25zdCBkYXRhSWQgPSB7aWQ6IHRoaXMuZGF0YUlkTmV4dE51bWJlcisrfTtcbiAgICB0aGlzLm1vdmUoZGF0YUlkLCB2YWx1ZXMsIHNoYXBlLCBkdHlwZSwgMSk7XG4gICAgcmV0dXJuIGRhdGFJZDtcbiAgfVxuXG4gIG92ZXJyaWRlIG51bURhdGFJZHMoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5kYXRhSWRNYXAubnVtRGF0YUlkcygpO1xuICB9XG5cbiAgb3ZlcnJpZGUgYXN5bmMgdGltZShmOiAoKSA9PiB2b2lkKTogUHJvbWlzZTxCYWNrZW5kVGltaW5nSW5mbz4ge1xuICAgIGNvbnN0IHN0YXJ0ID0gdXRpbC5ub3coKTtcbiAgICBmKCk7XG4gICAgY29uc3Qga2VybmVsTXMgPSB1dGlsLm5vdygpIC0gc3RhcnQ7XG4gICAgcmV0dXJuIHtrZXJuZWxNc307XG4gIH1cblxuICBvdmVycmlkZSBtb3ZlKFxuICAgICAgZGF0YUlkOiBEYXRhSWQsIHZhbHVlczogYmFja2VuZF91dGlsLkJhY2tlbmRWYWx1ZXN8bnVsbCwgc2hhcGU6IG51bWJlcltdLFxuICAgICAgZHR5cGU6IERhdGFUeXBlLCByZWZDb3VudDogbnVtYmVyKTogdm9pZCB7XG4gICAgY29uc3QgaWQgPSB0aGlzLmRhdGFJZE5leHROdW1iZXIrKztcbiAgICBpZiAoZHR5cGUgPT09ICdzdHJpbmcnKSB7XG4gICAgICBjb25zdCBzdHJpbmdCeXRlcyA9IHZhbHVlcyBhcyBVaW50OEFycmF5W107XG4gICAgICB0aGlzLmRhdGFJZE1hcC5zZXQoXG4gICAgICAgICAgZGF0YUlkLFxuICAgICAgICAgIHtpZCwgc3RyaW5nQnl0ZXMsIHNoYXBlLCBkdHlwZSwgbWVtb3J5T2Zmc2V0OiBudWxsLCByZWZDb3VudH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IHNpemUgPSB1dGlsLnNpemVGcm9tU2hhcGUoc2hhcGUpO1xuICAgIGNvbnN0IG51bUJ5dGVzID0gc2l6ZSAqIHV0aWwuYnl0ZXNQZXJFbGVtZW50KGR0eXBlKTtcblxuICAgIC8vIGA+Pj4gMGAgaXMgbmVlZGVkIGZvciBhYm92ZSAyR0IgYWxsb2NhdGlvbnMgYmVjYXVzZSB3YXNtLl9tYWxsb2MgcmV0dXJuc1xuICAgIC8vIGEgc2lnbmVkIGludDMyIGluc3RlYWQgb2YgYW4gdW5zaWduZWQgaW50MzIuXG4gICAgLy8gaHR0cHM6Ly92OC5kZXYvYmxvZy80Z2Itd2FzbS1tZW1vcnlcbiAgICBjb25zdCBtZW1vcnlPZmZzZXQgPSB0aGlzLndhc20uX21hbGxvYyhudW1CeXRlcykgPj4+IDA7XG5cbiAgICB0aGlzLmRhdGFJZE1hcC5zZXQoZGF0YUlkLCB7aWQsIG1lbW9yeU9mZnNldCwgc2hhcGUsIGR0eXBlLCByZWZDb3VudH0pO1xuXG4gICAgdGhpcy53YXNtLnRmanMucmVnaXN0ZXJUZW5zb3IoaWQsIHNpemUsIG1lbW9yeU9mZnNldCk7XG5cbiAgICBpZiAodmFsdWVzICE9IG51bGwpIHtcbiAgICAgIHRoaXMud2FzbS5IRUFQVTguc2V0KFxuICAgICAgICAgIG5ldyBVaW50OEFycmF5KFxuICAgICAgICAgICAgICAodmFsdWVzIGFzIGJhY2tlbmRfdXRpbC5UeXBlZEFycmF5KS5idWZmZXIsXG4gICAgICAgICAgICAgICh2YWx1ZXMgYXMgYmFja2VuZF91dGlsLlR5cGVkQXJyYXkpLmJ5dGVPZmZzZXQsIG51bUJ5dGVzKSxcbiAgICAgICAgICBtZW1vcnlPZmZzZXQpO1xuICAgIH1cbiAgfVxuXG4gIG92ZXJyaWRlIGFzeW5jIHJlYWQoZGF0YUlkOiBEYXRhSWQpOiBQcm9taXNlPGJhY2tlbmRfdXRpbC5CYWNrZW5kVmFsdWVzPiB7XG4gICAgcmV0dXJuIHRoaXMucmVhZFN5bmMoZGF0YUlkKTtcbiAgfVxuXG4gIG92ZXJyaWRlIHJlYWRTeW5jKGRhdGFJZDogRGF0YUlkLCBzdGFydD86IG51bWJlciwgZW5kPzogbnVtYmVyKTpcbiAgICAgIGJhY2tlbmRfdXRpbC5CYWNrZW5kVmFsdWVzIHtcbiAgICBjb25zdCB7bWVtb3J5T2Zmc2V0LCBkdHlwZSwgc2hhcGUsIHN0cmluZ0J5dGVzfSA9XG4gICAgICAgIHRoaXMuZGF0YUlkTWFwLmdldChkYXRhSWQpO1xuICAgIGlmIChkdHlwZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIC8vIFNsaWNlIGFsbCBlbGVtZW50cy5cbiAgICAgIGlmICgoc3RhcnQgPT0gbnVsbCB8fCBzdGFydCA9PT0gMCkgJiZcbiAgICAgICAgICAoZW5kID09IG51bGwgfHwgZW5kID49IHN0cmluZ0J5dGVzLmxlbmd0aCkpIHtcbiAgICAgICAgcmV0dXJuIHN0cmluZ0J5dGVzO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHN0cmluZ0J5dGVzLnNsaWNlKHN0YXJ0LCBlbmQpO1xuICAgIH1cbiAgICBzdGFydCA9IHN0YXJ0IHx8IDA7XG4gICAgZW5kID0gZW5kIHx8IHV0aWwuc2l6ZUZyb21TaGFwZShzaGFwZSk7XG4gICAgY29uc3QgYnl0ZXNQZXJFbGVtZW50ID0gdXRpbC5ieXRlc1BlckVsZW1lbnQoZHR5cGUpO1xuICAgIGNvbnN0IGJ5dGVzID0gdGhpcy53YXNtLkhFQVBVOC5zbGljZShcbiAgICAgICAgbWVtb3J5T2Zmc2V0ICsgc3RhcnQgKiBieXRlc1BlckVsZW1lbnQsXG4gICAgICAgIG1lbW9yeU9mZnNldCArIGVuZCAqIGJ5dGVzUGVyRWxlbWVudCk7XG4gICAgcmV0dXJuIHR5cGVkQXJyYXlGcm9tQnVmZmVyKGJ5dGVzLmJ1ZmZlciwgZHR5cGUpO1xuICB9XG5cbiAgLyoqXG4gICAqIERpc3Bvc2UgdGhlIG1lbW9yeSBpZiB0aGUgZGF0YUlkIGhhcyAwIHJlZkNvdW50LiBSZXR1cm4gdHJ1ZSBpZiB0aGUgbWVtb3J5XG4gICAqIGlzIHJlbGVhc2VkLCBmYWxzZSBvdGhlcndpc2UuXG4gICAqIEBwYXJhbSBkYXRhSWRcbiAgICogQG9hcmFtIGZvcmNlIE9wdGlvbmFsLCByZW1vdmUgdGhlIGRhdGEgcmVnYXJkbGVzcyBvZiByZWZDb3VudFxuICAgKi9cbiAgb3ZlcnJpZGUgZGlzcG9zZURhdGEoZGF0YUlkOiBEYXRhSWQsIGZvcmNlID0gZmFsc2UpOiBib29sZWFuIHtcbiAgICBpZiAodGhpcy5kYXRhSWRNYXAuaGFzKGRhdGFJZCkpIHtcbiAgICAgIGNvbnN0IGRhdGEgPSB0aGlzLmRhdGFJZE1hcC5nZXQoZGF0YUlkKTtcbiAgICAgIGRhdGEucmVmQ291bnQtLTtcbiAgICAgIGlmICghZm9yY2UgJiYgZGF0YS5yZWZDb3VudCA+IDApIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuXG4gICAgICB0aGlzLndhc20uX2ZyZWUoZGF0YS5tZW1vcnlPZmZzZXQpO1xuICAgICAgdGhpcy53YXNtLnRmanMuZGlzcG9zZURhdGEoZGF0YS5pZCk7XG4gICAgICB0aGlzLmRhdGFJZE1hcC5kZWxldGUoZGF0YUlkKTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICAvKiogUmV0dXJuIHJlZkNvdW50IG9mIGEgYFRlbnNvckRhdGFgLiAqL1xuICBvdmVycmlkZSByZWZDb3VudChkYXRhSWQ6IERhdGFJZCk6IG51bWJlciB7XG4gICAgaWYgKHRoaXMuZGF0YUlkTWFwLmhhcyhkYXRhSWQpKSB7XG4gICAgICBjb25zdCB0ZW5zb3JEYXRhID0gdGhpcy5kYXRhSWRNYXAuZ2V0KGRhdGFJZCk7XG4gICAgICByZXR1cm4gdGVuc29yRGF0YS5yZWZDb3VudDtcbiAgICB9XG4gICAgcmV0dXJuIDA7XG4gIH1cblxuICBvdmVycmlkZSBpbmNSZWYoZGF0YUlkOiBEYXRhSWQpIHtcbiAgICBjb25zdCBkYXRhID0gdGhpcy5kYXRhSWRNYXAuZ2V0KGRhdGFJZCk7XG4gICAgaWYgKGRhdGEgIT0gbnVsbCkge1xuICAgICAgZGF0YS5yZWZDb3VudCsrO1xuICAgIH1cbiAgfVxuXG4gIG92ZXJyaWRlIGZsb2F0UHJlY2lzaW9uKCk6IDMyIHtcbiAgICByZXR1cm4gMzI7XG4gIH1cblxuICAvLyBSZXR1cm5zIHRoZSBtZW1vcnkgb2Zmc2V0IG9mIGEgdGVuc29yLiBVc2VmdWwgZm9yIGRlYnVnZ2luZyBhbmQgdW5pdFxuICAvLyB0ZXN0aW5nLlxuICBnZXRNZW1vcnlPZmZzZXQoZGF0YUlkOiBEYXRhSWQpOiBudW1iZXIge1xuICAgIHJldHVybiB0aGlzLmRhdGFJZE1hcC5nZXQoZGF0YUlkKS5tZW1vcnlPZmZzZXQ7XG4gIH1cblxuICBvdmVycmlkZSBkaXNwb3NlKCkge1xuICAgIHRoaXMud2FzbS50ZmpzLmRpc3Bvc2UoKTtcbiAgICBpZiAoJ1BUaHJlYWQnIGluIHRoaXMud2FzbSkge1xuICAgICAgdGhpcy53YXNtLlBUaHJlYWQudGVybWluYXRlQWxsVGhyZWFkcygpO1xuICAgIH1cbiAgICB0aGlzLndhc20gPSBudWxsO1xuICB9XG5cbiAgb3ZlcnJpZGUgbWVtb3J5KCkge1xuICAgIHJldHVybiB7dW5yZWxpYWJsZTogZmFsc2V9O1xuICB9XG5cbiAgLyoqXG4gICAqIE1ha2UgYSB0ZW5zb3IgaW5mbyBmb3IgdGhlIG91dHB1dCBvZiBhbiBvcC4gSWYgYG1lbW9yeU9mZnNldGAgaXMgbm90XG4gICAqIHByZXNlbnQsIHRoaXMgbWV0aG9kIGFsbG9jYXRlcyBtZW1vcnkgb24gdGhlIFdBU00gaGVhcC4gSWYgYG1lbW9yeU9mZnNldGBcbiAgICogaXMgcHJlc2VudCwgdGhlIG1lbW9yeSB3YXMgYWxsb2NhdGVkIGVsc2V3aGVyZSAoaW4gYysrKSBhbmQgd2UganVzdCByZWNvcmRcbiAgICogdGhlIHBvaW50ZXIgd2hlcmUgdGhhdCBtZW1vcnkgbGl2ZXMuXG4gICAqL1xuICBtYWtlT3V0cHV0KFxuICAgICAgc2hhcGU6IG51bWJlcltdLCBkdHlwZTogRGF0YVR5cGUsIG1lbW9yeU9mZnNldD86IG51bWJlcixcbiAgICAgIHZhbHVlcz86IGJhY2tlbmRfdXRpbC5CYWNrZW5kVmFsdWVzKTogVGVuc29ySW5mbyB7XG4gICAgbGV0IGRhdGFJZDoge307XG4gICAgaWYgKG1lbW9yeU9mZnNldCA9PSBudWxsKSB7XG4gICAgICBkYXRhSWQgPSB0aGlzLndyaXRlKHZhbHVlcyA/PyBudWxsLCBzaGFwZSwgZHR5cGUpO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBpZCA9IHRoaXMuZGF0YUlkTmV4dE51bWJlcisrO1xuICAgICAgZGF0YUlkID0ge2lkfTtcbiAgICAgIHRoaXMuZGF0YUlkTWFwLnNldChkYXRhSWQsIHtpZCwgbWVtb3J5T2Zmc2V0LCBzaGFwZSwgZHR5cGUsIHJlZkNvdW50OiAxfSk7XG4gICAgICBjb25zdCBzaXplID0gdXRpbC5zaXplRnJvbVNoYXBlKHNoYXBlKTtcbiAgICAgIHRoaXMud2FzbS50ZmpzLnJlZ2lzdGVyVGVuc29yKGlkLCBzaXplLCBtZW1vcnlPZmZzZXQpO1xuICAgIH1cbiAgICByZXR1cm4ge2RhdGFJZCwgc2hhcGUsIGR0eXBlfTtcbiAgfVxuXG4gIHR5cGVkQXJyYXlGcm9tSGVhcCh7c2hhcGUsIGR0eXBlLCBkYXRhSWR9OiBUZW5zb3JJbmZvKTpcbiAgICAgIGJhY2tlbmRfdXRpbC5UeXBlZEFycmF5IHtcbiAgICBjb25zdCBidWZmZXIgPSB0aGlzLndhc20uSEVBUFU4LmJ1ZmZlcjtcbiAgICBjb25zdCB7bWVtb3J5T2Zmc2V0fSA9IHRoaXMuZGF0YUlkTWFwLmdldChkYXRhSWQpO1xuICAgIGNvbnN0IHNpemUgPSB1dGlsLnNpemVGcm9tU2hhcGUoc2hhcGUpO1xuICAgIHN3aXRjaCAoZHR5cGUpIHtcbiAgICAgIGNhc2UgJ2Zsb2F0MzInOlxuICAgICAgICByZXR1cm4gbmV3IEZsb2F0MzJBcnJheShidWZmZXIsIG1lbW9yeU9mZnNldCwgc2l6ZSk7XG4gICAgICBjYXNlICdpbnQzMic6XG4gICAgICAgIHJldHVybiBuZXcgSW50MzJBcnJheShidWZmZXIsIG1lbW9yeU9mZnNldCwgc2l6ZSk7XG4gICAgICBjYXNlICdib29sJzpcbiAgICAgICAgcmV0dXJuIG5ldyBVaW50OEFycmF5KGJ1ZmZlciwgbWVtb3J5T2Zmc2V0LCBzaXplKTtcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBkdHlwZSAke2R0eXBlfWApO1xuICAgIH1cbiAgfVxufVxuXG5mdW5jdGlvbiBjcmVhdGVJbnN0YW50aWF0ZVdhc21GdW5jKHBhdGg6IHN0cmluZykge1xuICAvLyB0aGlzIHdpbGwgYmUgcmVwbGFjZSBieSByb2xsdXAgcGx1Z2luIHBhdGNoV2VjaGF0V2ViQXNzZW1ibHkgaW5cbiAgLy8gbWlucHJvZ3JhbSdzIG91dHB1dC5cbiAgLy8gdHNsaW50OmRpc2FibGUtbmV4dC1saW5lOm5vLWFueVxuICByZXR1cm4gKGltcG9ydHM6IGFueSwgY2FsbGJhY2s6IGFueSkgPT4ge1xuICAgIHV0aWwuZmV0Y2gocGF0aCwge2NyZWRlbnRpYWxzOiAnc2FtZS1vcmlnaW4nfSkudGhlbigocmVzcG9uc2UpID0+IHtcbiAgICAgIGlmICghcmVzcG9uc2VbJ29rJ10pIHtcbiAgICAgICAgaW1wb3J0cy5lbnYuYShgZmFpbGVkIHRvIGxvYWQgd2FzbSBiaW5hcnkgZmlsZSBhdCAnJHtwYXRofSdgKTtcbiAgICAgIH1cbiAgICAgIHJlc3BvbnNlLmFycmF5QnVmZmVyKCkudGhlbihiaW5hcnkgPT4ge1xuICAgICAgICBXZWJBc3NlbWJseS5pbnN0YW50aWF0ZShiaW5hcnksIGltcG9ydHMpLnRoZW4ob3V0cHV0ID0+IHtcbiAgICAgICAgICBjYWxsYmFjayhvdXRwdXQuaW5zdGFuY2UsIG91dHB1dC5tb2R1bGUpO1xuICAgICAgICB9KTtcbiAgICAgIH0pO1xuICAgIH0pO1xuICAgIHJldHVybiB7fTtcbiAgfTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBwYXRoIG9mIHRoZSBXQVNNIGJpbmFyeS5cbiAqIEBwYXJhbSBzaW1kU3VwcG9ydGVkIHdoZXRoZXIgU0lNRCBpcyBzdXBwb3J0ZWRcbiAqIEBwYXJhbSB0aHJlYWRzU3VwcG9ydGVkIHdoZXRoZXIgbXVsdGl0aHJlYWRpbmcgaXMgc3VwcG9ydGVkXG4gKiBAcGFyYW0gd2FzbU1vZHVsZUZvbGRlciB0aGUgZGlyZWN0b3J5IGNvbnRhaW5pbmcgdGhlIFdBU00gYmluYXJpZXMuXG4gKi9cbmZ1bmN0aW9uIGdldFBhdGhUb1dhc21CaW5hcnkoXG4gICAgc2ltZFN1cHBvcnRlZDogYm9vbGVhbiwgdGhyZWFkc1N1cHBvcnRlZDogYm9vbGVhbixcbiAgICB3YXNtTW9kdWxlRm9sZGVyOiBzdHJpbmcpIHtcbiAgaWYgKHdhc21QYXRoICE9IG51bGwpIHtcbiAgICAvLyBJZiB3YXNtUGF0aCBpcyBkZWZpbmVkLCB0aGUgdXNlciBoYXMgc3VwcGxpZWQgYSBmdWxsIHBhdGggdG9cbiAgICAvLyB0aGUgdmFuaWxsYSAud2FzbSBiaW5hcnkuXG4gICAgcmV0dXJuIHdhc21QYXRoO1xuICB9XG5cbiAgbGV0IHBhdGg6IFdhc21CaW5hcnlOYW1lID0gJ3RmanMtYmFja2VuZC13YXNtLndhc20nO1xuICBpZiAoc2ltZFN1cHBvcnRlZCAmJiB0aHJlYWRzU3VwcG9ydGVkKSB7XG4gICAgcGF0aCA9ICd0ZmpzLWJhY2tlbmQtd2FzbS10aHJlYWRlZC1zaW1kLndhc20nO1xuICB9IGVsc2UgaWYgKHNpbWRTdXBwb3J0ZWQpIHtcbiAgICBwYXRoID0gJ3RmanMtYmFja2VuZC13YXNtLXNpbWQud2FzbSc7XG4gIH1cblxuICBpZiAod2FzbUZpbGVNYXAgIT0gbnVsbCkge1xuICAgIGlmICh3YXNtRmlsZU1hcFtwYXRoXSAhPSBudWxsKSB7XG4gICAgICByZXR1cm4gd2FzbUZpbGVNYXBbcGF0aF07XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHdhc21Nb2R1bGVGb2xkZXIgKyBwYXRoO1xufVxuXG4vKipcbiAqIEluaXRpYWxpemVzIHRoZSB3YXNtIG1vZHVsZSBhbmQgY3JlYXRlcyB0aGUganMgPC0tPiB3YXNtIGJyaWRnZS5cbiAqXG4gKiBOT1RFOiBXZSB3cmFwIHRoZSB3YXNtIG1vZHVsZSBpbiBhIG9iamVjdCB3aXRoIHByb3BlcnR5ICd3YXNtJyBpbnN0ZWFkIG9mXG4gKiByZXR1cm5pbmcgUHJvbWlzZTxCYWNrZW5kV2FzbU1vZHVsZT4gdG8gYXZvaWQgZnJlZXppbmcgQ2hyb21lIChsYXN0IHRlc3RlZFxuICogaW4gQ2hyb21lIDc2KS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGluaXQoKTogUHJvbWlzZTx7d2FzbTogQmFja2VuZFdhc21Nb2R1bGV9PiB7XG4gIGNvbnN0IFtzaW1kU3VwcG9ydGVkLCB0aHJlYWRzU3VwcG9ydGVkXSA9IGF3YWl0IFByb21pc2UuYWxsKFtcbiAgICBlbnYoKS5nZXRBc3luYygnV0FTTV9IQVNfU0lNRF9TVVBQT1JUJyksXG4gICAgZW52KCkuZ2V0QXN5bmMoJ1dBU01fSEFTX01VTFRJVEhSRUFEX1NVUFBPUlQnKVxuICBdKTtcblxuICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgIGNvbnN0IGZhY3RvcnlDb25maWc6IFdhc21GYWN0b3J5Q29uZmlnID0ge307XG5cbiAgICAvKipcbiAgICAgKiBUaGlzIGZ1bmN0aW9uIG92ZXJyaWRlcyB0aGUgRW1zY3JpcHRlbiBtb2R1bGUgbG9jYXRlRmlsZSB1dGlsaXR5LlxuICAgICAqIEBwYXJhbSBwYXRoIFRoZSByZWxhdGl2ZSBwYXRoIHRvIHRoZSBmaWxlIHRoYXQgbmVlZHMgdG8gYmUgbG9hZGVkLlxuICAgICAqIEBwYXJhbSBwcmVmaXggVGhlIHBhdGggdG8gdGhlIG1haW4gSmF2YVNjcmlwdCBmaWxlJ3MgZGlyZWN0b3J5LlxuICAgICAqL1xuICAgIGZhY3RvcnlDb25maWcubG9jYXRlRmlsZSA9IChwYXRoLCBwcmVmaXgpID0+IHtcbiAgICAgIGlmIChwYXRoLmVuZHNXaXRoKCcud29ya2VyLmpzJykpIHtcbiAgICAgICAgLy8gRXNjYXBlICdcXG4nIGJlY2F1c2UgQmxvYiB3aWxsIHR1cm4gaXQgaW50byBhIG5ld2xpbmUuXG4gICAgICAgIC8vIFRoZXJlIHNob3VsZCBiZSBhIHNldHRpbmcgZm9yIHRoaXMsIGJ1dCAnZW5kaW5nczogXCJuYXRpdmVcIicgZG9lc1xuICAgICAgICAvLyBub3Qgc2VlbSB0byB3b3JrLlxuICAgICAgICBjb25zdCByZXNwb25zZSA9ICh3YXNtV29ya2VyQ29udGVudHMgYXMgc3RyaW5nKS5yZXBsYWNlKC9cXG4vZywgJ1xcXFxuJyk7XG4gICAgICAgIGNvbnN0IGJsb2IgPSBuZXcgQmxvYihbcmVzcG9uc2VdLCB7dHlwZTogJ2FwcGxpY2F0aW9uL2phdmFzY3JpcHQnfSk7XG4gICAgICAgIHJldHVybiBVUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuICAgICAgfVxuXG4gICAgICBpZiAocGF0aC5lbmRzV2l0aCgnLndhc20nKSkge1xuICAgICAgICByZXR1cm4gZ2V0UGF0aFRvV2FzbUJpbmFyeShcbiAgICAgICAgICAgIHNpbWRTdXBwb3J0ZWQgYXMgYm9vbGVhbiwgdGhyZWFkc1N1cHBvcnRlZCBhcyBib29sZWFuLFxuICAgICAgICAgICAgd2FzbVBhdGhQcmVmaXggIT0gbnVsbCA/IHdhc21QYXRoUHJlZml4IDogcHJlZml4KTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBwcmVmaXggKyBwYXRoO1xuICAgIH07XG5cbiAgICAvLyBVc2UgdGhlIGluc3RhbnRpYXRlV2FzbSBvdmVycmlkZSB3aGVuIHN5c3RlbSBmZXRjaCBpcyBub3QgYXZhaWxhYmxlLlxuICAgIC8vIFJlZmVyZW5jZTpcbiAgICAvLyBodHRwczovL2dpdGh1Yi5jb20vZW1zY3JpcHRlbi1jb3JlL2Vtc2NyaXB0ZW4vYmxvYi8yYmNhMDgzY2JiZDVhNDEzM2RiNjFmYmQ3NGQwNGY3ZmVlY2ZhOTA3L3Rlc3RzL21hbnVhbF93YXNtX2luc3RhbnRpYXRlLmh0bWwjTDE3MFxuICAgIGlmIChjdXN0b21GZXRjaCkge1xuICAgICAgZmFjdG9yeUNvbmZpZy5pbnN0YW50aWF0ZVdhc20gPVxuICAgICAgICAgIGNyZWF0ZUluc3RhbnRpYXRlV2FzbUZ1bmMoZ2V0UGF0aFRvV2FzbUJpbmFyeShcbiAgICAgICAgICAgICAgc2ltZFN1cHBvcnRlZCBhcyBib29sZWFuLCB0aHJlYWRzU3VwcG9ydGVkIGFzIGJvb2xlYW4sXG4gICAgICAgICAgICAgIHdhc21QYXRoUHJlZml4ICE9IG51bGwgPyB3YXNtUGF0aFByZWZpeCA6ICcnKSk7XG4gICAgfVxuXG4gICAgbGV0IGluaXRpYWxpemVkID0gZmFsc2U7XG4gICAgZmFjdG9yeUNvbmZpZy5vbkFib3J0ID0gKCkgPT4ge1xuICAgICAgaWYgKGluaXRpYWxpemVkKSB7XG4gICAgICAgIC8vIEVtc2NyaXB0ZW4gYWxyZWFkeSBjYWxsZWQgY29uc29sZS53YXJuIHNvIG5vIG5lZWQgdG8gZG91YmxlIGxvZy5cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgaWYgKGluaXRBYm9ydGVkKSB7XG4gICAgICAgIC8vIEVtc2NyaXB0ZW4gY2FsbHMgYG9uQWJvcnRgIHR3aWNlLCByZXN1bHRpbmcgaW4gZG91YmxlIGVycm9yXG4gICAgICAgIC8vIG1lc3NhZ2VzLlxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBpbml0QWJvcnRlZCA9IHRydWU7XG4gICAgICBjb25zdCByZWplY3RNc2cgPVxuICAgICAgICAgICdNYWtlIHN1cmUgdGhlIHNlcnZlciBjYW4gc2VydmUgdGhlIGAud2FzbWAgZmlsZSByZWxhdGl2ZSB0byB0aGUgJyArXG4gICAgICAgICAgJ2J1bmRsZWQganMgZmlsZS4gRm9yIG1vcmUgZGV0YWlscyBzZWUgaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGZqcy9ibG9iL21hc3Rlci90ZmpzLWJhY2tlbmQtd2FzbS9SRUFETUUubWQjdXNpbmctYnVuZGxlcnMnO1xuICAgICAgcmVqZWN0KHttZXNzYWdlOiByZWplY3RNc2d9KTtcbiAgICB9O1xuXG4gICAgbGV0IHdhc206IFByb21pc2U8QmFja2VuZFdhc21Nb2R1bGU+O1xuICAgIC8vIElmIGB3YXNtUGF0aGAgaGFzIGJlZW4gZGVmaW5lZCB3ZSBtdXN0IGluaXRpYWxpemUgdGhlIHZhbmlsbGEgbW9kdWxlLlxuICAgIGlmICh0aHJlYWRzU3VwcG9ydGVkICYmIHNpbWRTdXBwb3J0ZWQgJiYgd2FzbVBhdGggPT0gbnVsbCkge1xuICAgICAgZmFjdG9yeUNvbmZpZy5tYWluU2NyaXB0VXJsT3JCbG9iID0gbmV3IEJsb2IoXG4gICAgICAgICAgW2B2YXIgV2FzbUJhY2tlbmRNb2R1bGVUaHJlYWRlZFNpbWQgPSBgICtcbiAgICAgICAgICAgd2FzbUZhY3RvcnlUaHJlYWRlZFNpbWQudG9TdHJpbmcoKV0sXG4gICAgICAgICAge3R5cGU6ICd0ZXh0L2phdmFzY3JpcHQnfSk7XG4gICAgICB3YXNtID0gd2FzbUZhY3RvcnlUaHJlYWRlZFNpbWQoZmFjdG9yeUNvbmZpZyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIFRoZSB3YXNtRmFjdG9yeSB3b3JrcyBmb3Ig