kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
513 lines (498 loc) • 68.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.COLORMAP_TEXTURE_PARAMETERS = void 0;
exports.generateCategoricalColormapTexture = generateCategoricalColormapTexture;
exports.getCombineBandsModule = getCombineBandsModule;
exports.getImageMaskModule = getImageMaskModule;
exports.getModules = getModules;
exports.loadImage = loadImage;
exports.loadNpyArray = loadNpyArray;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _core = require("@loaders.gl/core");
var _images = require("@loaders.gl/images");
var _textures = require("@loaders.gl/textures");
var _constants = require("@luma.gl/constants");
var _commonUtils = require("@kepler.gl/common-utils");
var _constants2 = require("@kepler.gl/constants");
var _deckglLayers = require("@kepler.gl/deckgl-layers");
var _utils = require("@kepler.gl/utils");
var _rasterTileUtils = require("./raster-tile-utils");
var _requestThrottle = require("./request-throttle");
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
/**
* Functions and constants for handling webgl/luma.gl/deck.gl entities
*/ // @ts-ignore GL resolution depends on moduleResolution setting
/**
* Loose texture data descriptor passed around before actual luma.gl Texture creation.
* Not the same as luma.gl's strict TextureProps (which requires width/height).
*/
var combineBandsFloat = _deckglLayers.RasterWebGL.combineBandsFloat,
combineBandsInt = _deckglLayers.RasterWebGL.combineBandsInt,
combineBandsUint = _deckglLayers.RasterWebGL.combineBandsUint,
maskFloat = _deckglLayers.RasterWebGL.maskFloat,
maskInt = _deckglLayers.RasterWebGL.maskInt,
maskUint = _deckglLayers.RasterWebGL.maskUint,
linearRescale = _deckglLayers.RasterWebGL.linearRescale,
gammaContrast = _deckglLayers.RasterWebGL.gammaContrast,
sigmoidalContrast = _deckglLayers.RasterWebGL.sigmoidalContrast,
normalizedDifference = _deckglLayers.RasterWebGL.normalizedDifference,
enhancedVegetationIndex = _deckglLayers.RasterWebGL.enhancedVegetationIndex,
soilAdjustedVegetationIndex = _deckglLayers.RasterWebGL.soilAdjustedVegetationIndex,
modifiedSoilAdjustedVegetationIndex = _deckglLayers.RasterWebGL.modifiedSoilAdjustedVegetationIndex,
colormapModule = _deckglLayers.RasterWebGL.colormap,
filter = _deckglLayers.RasterWebGL.filter,
saturation = _deckglLayers.RasterWebGL.saturation,
reorderBands = _deckglLayers.RasterWebGL.reorderBands,
rgbaImage = _deckglLayers.RasterWebGL.rgbaImage;
/**
* Describe WebGL2 Texture parameters to use for given input data type
*/
/**
* Convert TypedArray to WebGL2 Texture Parameters
*/
function getWebGL2TextureParameters(data) {
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
return {
// Note: texture data has no auto-rescaling; pixel values stay as 0-255
format: _constants.GL.R8UI,
dataFormat: _constants.GL.RED_INTEGER,
type: _constants.GL.UNSIGNED_BYTE
};
}
if (data instanceof Uint16Array) {
return {
format: _constants.GL.R16UI,
dataFormat: _constants.GL.RED_INTEGER,
type: _constants.GL.UNSIGNED_SHORT
};
}
if (data instanceof Uint32Array) {
return {
format: _constants.GL.R32UI,
dataFormat: _constants.GL.RED_INTEGER,
type: _constants.GL.UNSIGNED_INT
};
}
if (data instanceof Int8Array) {
return {
format: _constants.GL.R8I,
dataFormat: _constants.GL.RED_INTEGER,
type: _constants.GL.BYTE
};
}
if (data instanceof Int16Array) {
return {
format: _constants.GL.R16I,
dataFormat: _constants.GL.RED_INTEGER,
type: _constants.GL.SHORT
};
}
if (data instanceof Int32Array) {
return {
format: _constants.GL.R32I,
dataFormat: _constants.GL.RED_INTEGER,
type: _constants.GL.INT
};
}
if (data instanceof Float32Array) {
return {
format: _constants.GL.R32F,
dataFormat: _constants.GL.RED,
type: _constants.GL.FLOAT
};
}
if (data instanceof Float64Array) {
return {
format: _constants.GL.R32F,
dataFormat: _constants.GL.RED,
type: _constants.GL.FLOAT
};
}
// For exhaustive check above; following should never occur
// https://stackoverflow.com/a/58009992
var unexpectedInput = data;
throw new Error(unexpectedInput);
}
/**
* Discrete-valued colormaps (e.g. from the output of
* classification algorithms) in the raster layer. Previously, the values passed to
* `TEXTURE_MIN_FILTER` and `TEXTURE_MAG_FILTER` were `GL.LINEAR`, which meant that the GPU would
* linearly interpolate values between two neighboring colormap pixel values. Setting these values
* to NEAREST means that the GPU will choose the nearest value on the texture2D lookup operation,
* which fixes precision issues for discrete-valued colormaps. This should be ok for continuous
* colormaps as long as the color difference between each pixel on the colormap is small.
*/
var COLORMAP_TEXTURE_PARAMETERS = exports.COLORMAP_TEXTURE_PARAMETERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.GL.TEXTURE_MIN_FILTER, _constants.GL.NEAREST), _constants.GL.TEXTURE_MAG_FILTER, _constants.GL.NEAREST), _constants.GL.TEXTURE_WRAP_S, _constants.GL.CLAMP_TO_EDGE), _constants.GL.TEXTURE_WRAP_T, _constants.GL.CLAMP_TO_EDGE);
var DEFAULT_8BIT_TEXTURE_PARAMETERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.GL.TEXTURE_MIN_FILTER, _constants.GL.LINEAR_MIPMAP_LINEAR), _constants.GL.TEXTURE_MAG_FILTER, _constants.GL.LINEAR), _constants.GL.TEXTURE_WRAP_S, _constants.GL.CLAMP_TO_EDGE), _constants.GL.TEXTURE_WRAP_T, _constants.GL.CLAMP_TO_EDGE);
var DEFAULT_HIGH_BIT_TEXTURE_PARAMETERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.GL.TEXTURE_MIN_FILTER, _constants.GL.NEAREST), _constants.GL.TEXTURE_MAG_FILTER, _constants.GL.NEAREST), _constants.GL.TEXTURE_WRAP_S, _constants.GL.CLAMP_TO_EDGE), _constants.GL.TEXTURE_WRAP_T, _constants.GL.CLAMP_TO_EDGE);
/**
* Select correct module type for "combineBands"
*
* combineBands joins up to four 2D arrays (contained in imageBands) into a single "rgba" image
* texture on the GPU. That shader code needs to have the same data type as the actual image data.
* E.g. for float data the texture needs to be `sampler2D`, for uint data the texture needs to be
* `usampler2D` and for int data the texture needs to be `isampler2D`.
*/
function getCombineBandsModule(imageBands) {
// Each image array is expected/required to be of the same data type
switch (imageBands[0].format) {
case _constants.GL.R8UI:
return combineBandsUint;
case _constants.GL.R16UI:
return combineBandsUint;
case _constants.GL.R32UI:
return combineBandsUint;
case _constants.GL.R8I:
return combineBandsInt;
case _constants.GL.R16I:
return combineBandsInt;
case _constants.GL.R32I:
return combineBandsInt;
case _constants.GL.R32F:
return combineBandsFloat;
default:
throw new Error('bad format');
}
}
/** Select correct image masking shader module for mask data type
* The imageMask could (at least in the future, theoretically) be of a different data format than
* the imageBands data itself.
*/
function getImageMaskModule(imageMask) {
switch (imageMask.format) {
case _constants.GL.R8UI:
return maskUint;
case _constants.GL.R16UI:
return maskUint;
case _constants.GL.R32UI:
return maskUint;
case _constants.GL.R8I:
return maskInt;
case _constants.GL.R16I:
return maskInt;
case _constants.GL.R32I:
return maskInt;
case _constants.GL.R32F:
return maskFloat;
default:
throw new Error('bad format');
}
}
/**
* Load image and wrap with default WebGL texture parameters
*
* @param url URL to load image
* @param textureParams parameters to pass to Texture2D
*
* @return image object to pass to Texture2D constructor
*/
function loadImage(_x) {
return _loadImage.apply(this, arguments);
}
function _loadImage() {
_loadImage = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(url) {
var textureParams,
requestOptions,
response,
image,
_args = arguments;
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
textureParams = _args.length > 1 && _args[1] !== undefined ? _args[1] : {};
requestOptions = _args.length > 2 && _args[2] !== undefined ? _args[2] : {};
_context.next = 4;
return (0, _core.fetchFile)(url, requestOptions);
case 4:
response = _context.sent;
_context.next = 7;
return (0, _core.parse)(response, _images.ImageLoader);
case 7:
image = _context.sent;
return _context.abrupt("return", _objectSpread({
data: image,
parameters: DEFAULT_8BIT_TEXTURE_PARAMETERS,
format: _constants.GL.RGB
}, textureParams));
case 9:
case "end":
return _context.stop();
}
}, _callee);
}));
return _loadImage.apply(this, arguments);
}
/**
* Load NPY Array
*
* The NPY format is described here: https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html.
* It's designed to be a very simple file format to hold an N-dimensional block of data. The header describes the data type, shape, and order (either C or Fortran) of the array.
*
* @param url URL to load NPY Array
* @param split Whether to split single typed array representing an N-dimensional array into an Array with each dimension as its own typed array
*
* @return image object to pass to Texture2D constructor
*/
function loadNpyArray(_x2, _x3, _x4) {
return _loadNpyArray.apply(this, arguments);
}
/**
* Create texture data for categorical colormap scale
* @param categoricalOptions - color map configuration and min-max values of categorical band
* @returns texture data
*/
function _loadNpyArray() {
_loadNpyArray = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(request, split, options) {
var _request$rasterServer;
var numAttempts, asset;
return _regenerator["default"].wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
numAttempts = 1 + ((_request$rasterServer = request.rasterServerMaxRetries) !== null && _request$rasterServer !== void 0 ? _request$rasterServer : (0, _utils.getApplicationConfig)().rasterServerMaxRetries);
_context3.next = 3;
return (0, _requestThrottle.getRequestThrottle)().throttleRequest(request.rasterServerUrl, /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() {
var attempt, _request$options$sign, _getLoaderOptions, npyOptions, response, data, header, shape, _getWebGL2TexturePara, format, dataFormat, type, _shape, z, height, width, mipmaps, parameters, channels, channelSize, i, _ref3, _request$rasterServer2, _error$response, _request$rasterServer3;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
attempt = 0;
case 1:
if (!(attempt < numAttempts)) {
_context2.next = 33;
break;
}
_context2.prev = 2;
_getLoaderOptions = (0, _constants2.getLoaderOptions)(), npyOptions = _getLoaderOptions.npy;
_context2.next = 6;
return (0, _core.load)(request.url, _textures.NPYLoader, {
npy: npyOptions,
fetch: options === null || options === void 0 ? void 0 : options.fetch
});
case 6:
response = _context2.sent;
if (!(!response || !response.data || (_request$options$sign = request.options.signal) !== null && _request$options$sign !== void 0 && _request$options$sign.aborted)) {
_context2.next = 9;
break;
}
return _context2.abrupt("return", null);
case 9:
// Float64 data needs to be coerced to Float32 for the GPU
if (response.data instanceof Float64Array) {
response.data = Float32Array.from(response.data);
}
data = response.data, header = response.header;
shape = header.shape;
_getWebGL2TexturePara = getWebGL2TextureParameters(data), format = _getWebGL2TexturePara.format, dataFormat = _getWebGL2TexturePara.dataFormat, type = _getWebGL2TexturePara.type; // TODO: check height-width or width-height
// Regardless, images usually square
// TODO: handle cases of 256x256x1 instead of 1x256x256
_shape = (0, _slicedToArray2["default"])(shape, 3), z = _shape[0], height = _shape[1], width = _shape[2]; // Since we now use WebGL2 data types for 8-bit textures, we set the following for all textures
mipmaps = false;
parameters = DEFAULT_HIGH_BIT_TEXTURE_PARAMETERS;
if (split) {
_context2.next = 18;
break;
}
return _context2.abrupt("return", {
data: data,
width: width,
height: height,
format: format,
dataFormat: dataFormat,
type: type,
parameters: parameters,
mipmaps: mipmaps
});
case 18:
// Split into individual arrays
channels = [];
channelSize = height * width;
for (i = 0; i < z; i++) {
channels.push({
data: data.subarray(i * channelSize, (i + 1) * channelSize),
width: width,
height: height,
format: format,
dataFormat: dataFormat,
type: type,
parameters: parameters,
mipmaps: mipmaps
});
}
return _context2.abrupt("return", channels);
case 24:
_context2.prev = 24;
_context2.t0 = _context2["catch"](2);
if (!(attempt < numAttempts && _context2.t0 instanceof _core.FetchError && (_ref3 = (_request$rasterServer2 = request.rasterServerServerErrorsToRetry) !== null && _request$rasterServer2 !== void 0 ? _request$rasterServer2 : (0, _utils.getApplicationConfig)().rasterServerServerErrorsToRetry) !== null && _ref3 !== void 0 && _ref3.includes((_error$response = _context2.t0.response) === null || _error$response === void 0 ? void 0 : _error$response.status))) {
_context2.next = 30;
break;
}
_context2.next = 29;
return (0, _commonUtils.sleep)((_request$rasterServer3 = request.rasterServerRetryDelay) !== null && _request$rasterServer3 !== void 0 ? _request$rasterServer3 : (0, _utils.getApplicationConfig)().rasterServerRetryDelay);
case 29:
return _context2.abrupt("continue", 30);
case 30:
attempt++;
_context2.next = 1;
break;
case 33:
return _context2.abrupt("return", null);
case 34:
case "end":
return _context2.stop();
}
}, _callee2, null, [[2, 24]]);
})), request.rasterServerMaxPerServerRequests);
case 3:
asset = _context3.sent;
return _context3.abrupt("return", asset);
case 5:
case "end":
return _context3.stop();
}
}, _callee3);
}));
return _loadNpyArray.apply(this, arguments);
}
function generateCategoricalColormapTexture(categoricalOptions) {
var data = (0, _rasterTileUtils.generateCategoricalBitmapArray)(categoricalOptions);
return {
data: data,
width: _rasterTileUtils.CATEGORICAL_TEXTURE_WIDTH,
height: 1,
format: _constants.GL.RGBA,
dataFormat: _constants.GL.RGBA,
type: _constants.GL.UNSIGNED_BYTE,
parameters: COLORMAP_TEXTURE_PARAMETERS,
mipmaps: false
};
}
// TODO: would probably be simpler to only pass in the props actually used by this function. That
// would mean a smaller object than RenderSubLayersProps
// eslint-disable-next-line max-statements, complexity
function getModules(_ref) {
var images = _ref.images,
props = _ref.props;
var moduleProps = {};
// Array of luma.gl WebGL modules to pass to the RasterLayer
var modules = [];
// use rgba image directly. Used for raster .pmtiles rendering
if (images.imageRgba) {
modules.push(rgbaImage);
// no support for other modules atm for direct rgba mode
return {
modules: modules,
moduleProps: moduleProps
};
}
if (!props) {
return {
modules: modules,
moduleProps: moduleProps
};
}
var renderBandIndexes = props.renderBandIndexes,
nonLinearRescaling = props.nonLinearRescaling,
linearRescalingFactor = props.linearRescalingFactor,
minPixelValue = props.minPixelValue,
maxPixelValue = props.maxPixelValue,
gammaContrastFactor = props.gammaContrastFactor,
sigmoidalContrastFactor = props.sigmoidalContrastFactor,
sigmoidalBiasFactor = props.sigmoidalBiasFactor,
saturationValue = props.saturationValue,
bandCombination = props.bandCombination,
filterEnabled = props.filterEnabled,
filterRange = props.filterRange,
dataType = props.dataType,
minCategoricalBandValue = props.minCategoricalBandValue,
maxCategoricalBandValue = props.maxCategoricalBandValue,
hasCategoricalColorMap = props.hasCategoricalColorMap;
if (Array.isArray(images.imageBands) && images.imageBands.length > 0) {
modules.push(getCombineBandsModule(images.imageBands));
}
if (images.imageMask) {
modules.push(getImageMaskModule(images.imageMask));
// In general, data masks are 0 for nodata and the maximum value for valid data, e.g. 255 or
// 65535 for uint8 or uint16 data, respectively
moduleProps.maskKeepMin = 1;
}
if (Array.isArray(renderBandIndexes)) {
modules.push(reorderBands);
moduleProps.ordering = renderBandIndexes;
}
var globalRange = maxPixelValue - minPixelValue;
// Fix rescaling if we are sure that dataset is categorical
if (hasCategoricalColorMap) {
modules.push(linearRescale);
moduleProps.linearRescaleScaler = 1 / maxPixelValue;
moduleProps.linearRescaleOffset = 0;
} else if ((0, _rasterTileUtils.isRescalingAllowed)(bandCombination)) {
if (!nonLinearRescaling) {
var _linearRescalingFacto = (0, _slicedToArray2["default"])(linearRescalingFactor, 2),
min = _linearRescalingFacto[0],
max = _linearRescalingFacto[1];
var localRange = max - min;
// Add linear rescaling module
modules.push(linearRescale);
// Divide by local range * global range
moduleProps.linearRescaleScaler = 1 / (localRange * globalRange);
// Subtract off the local min
moduleProps.linearRescaleOffset = -min;
// Clamp to [0, 1] done automatically?
} else {
modules.push(linearRescale);
moduleProps.linearRescaleScaler = 1 / maxPixelValue;
moduleProps.linearRescaleOffset = 0;
modules.push(gammaContrast);
moduleProps.gammaContrastValue = gammaContrastFactor;
modules.push(sigmoidalContrast);
moduleProps.sigmoidalContrast = sigmoidalContrastFactor;
moduleProps.sigmoidalBias = sigmoidalBiasFactor;
}
if (Number.isFinite(saturationValue) && saturationValue !== 1) {
modules.push(saturation);
moduleProps.saturationValue = saturationValue;
}
}
switch (bandCombination) {
case 'normalizedDifference':
modules.push(normalizedDifference);
break;
case 'enhancedVegetationIndex':
modules.push(enhancedVegetationIndex);
break;
case 'soilAdjustedVegetationIndex':
modules.push(soilAdjustedVegetationIndex);
break;
case 'modifiedSoilAdjustedVegetationIndex':
modules.push(modifiedSoilAdjustedVegetationIndex);
break;
default:
break;
}
if ((0, _rasterTileUtils.isFilterAllowed)(bandCombination) && filterEnabled) {
modules.push(filter);
moduleProps.filterMin1 = filterRange[0];
moduleProps.filterMax1 = filterRange[1];
}
// Apply colormap
if ((0, _rasterTileUtils.isColormapAllowed)(bandCombination) && images.imageColormap) {
modules.push(colormapModule);
moduleProps.minCategoricalBandValue = minCategoricalBandValue;
moduleProps.maxCategoricalBandValue = maxCategoricalBandValue;
moduleProps.dataTypeMaxValue = _rasterTileUtils.dtypeMaxValue[dataType];
moduleProps.maxPixelValue = maxPixelValue;
}
return {
modules: modules,
moduleProps: moduleProps
};
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfY29yZSIsInJlcXVpcmUiLCJfaW1hZ2VzIiwiX3RleHR1cmVzIiwiX2NvbnN0YW50cyIsIl9jb21tb25VdGlscyIsIl9jb25zdGFudHMyIiwiX2RlY2tnbExheWVycyIsIl91dGlscyIsIl9yYXN0ZXJUaWxlVXRpbHMiLCJfcmVxdWVzdFRocm90dGxlIiwib3duS2V5cyIsImUiLCJyIiwidCIsIk9iamVjdCIsImtleXMiLCJnZXRPd25Qcm9wZXJ0eVN5bWJvbHMiLCJvIiwiZmlsdGVyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJsZW5ndGgiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImdldE93blByb3BlcnR5RGVzY3JpcHRvcnMiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiZGVmaW5lUHJvcGVydHkiLCJjb21iaW5lQmFuZHNGbG9hdCIsIlJhc3RlcldlYkdMIiwiY29tYmluZUJhbmRzSW50IiwiY29tYmluZUJhbmRzVWludCIsIm1hc2tGbG9hdCIsIm1hc2tJbnQiLCJtYXNrVWludCIsImxpbmVhclJlc2NhbGUiLCJnYW1tYUNvbnRyYXN0Iiwic2lnbW9pZGFsQ29udHJhc3QiLCJub3JtYWxpemVkRGlmZmVyZW5jZSIsImVuaGFuY2VkVmVnZXRhdGlvbkluZGV4Iiwic29pbEFkanVzdGVkVmVnZXRhdGlvbkluZGV4IiwibW9kaWZpZWRTb2lsQWRqdXN0ZWRWZWdldGF0aW9uSW5kZXgiLCJjb2xvcm1hcE1vZHVsZSIsImNvbG9ybWFwIiwic2F0dXJhdGlvbiIsInJlb3JkZXJCYW5kcyIsInJnYmFJbWFnZSIsImdldFdlYkdMMlRleHR1cmVQYXJhbWV0ZXJzIiwiZGF0YSIsIlVpbnQ4QXJyYXkiLCJVaW50OENsYW1wZWRBcnJheSIsImZvcm1hdCIsIkdMIiwiUjhVSSIsImRhdGFGb3JtYXQiLCJSRURfSU5URUdFUiIsInR5cGUiLCJVTlNJR05FRF9CWVRFIiwiVWludDE2QXJyYXkiLCJSMTZVSSIsIlVOU0lHTkVEX1NIT1JUIiwiVWludDMyQXJyYXkiLCJSMzJVSSIsIlVOU0lHTkVEX0lOVCIsIkludDhBcnJheSIsIlI4SSIsIkJZVEUiLCJJbnQxNkFycmF5IiwiUjE2SSIsIlNIT1JUIiwiSW50MzJBcnJheSIsIlIzMkkiLCJJTlQiLCJGbG9hdDMyQXJyYXkiLCJSMzJGIiwiUkVEIiwiRkxPQVQiLCJGbG9hdDY0QXJyYXkiLCJ1bmV4cGVjdGVkSW5wdXQiLCJFcnJvciIsIkNPTE9STUFQX1RFWFRVUkVfUEFSQU1FVEVSUyIsImV4cG9ydHMiLCJURVhUVVJFX01JTl9GSUxURVIiLCJORUFSRVNUIiwiVEVYVFVSRV9NQUdfRklMVEVSIiwiVEVYVFVSRV9XUkFQX1MiLCJDTEFNUF9UT19FREdFIiwiVEVYVFVSRV9XUkFQX1QiLCJERUZBVUxUXzhCSVRfVEVYVFVSRV9QQVJBTUVURVJTIiwiTElORUFSX01JUE1BUF9MSU5FQVIiLCJMSU5FQVIiLCJERUZBVUxUX0hJR0hfQklUX1RFWFRVUkVfUEFSQU1FVEVSUyIsImdldENvbWJpbmVCYW5kc01vZHVsZSIsImltYWdlQmFuZHMiLCJnZXRJbWFnZU1hc2tNb2R1bGUiLCJpbWFnZU1hc2siLCJsb2FkSW1hZ2UiLCJfeCIsIl9sb2FkSW1hZ2UiLCJfYXN5bmNUb0dlbmVyYXRvcjIiLCJfcmVnZW5lcmF0b3IiLCJtYXJrIiwiX2NhbGxlZSIsInVybCIsInRleHR1cmVQYXJhbXMiLCJyZXF1ZXN0T3B0aW9ucyIsInJlc3BvbnNlIiwiaW1hZ2UiLCJfYXJncyIsIndyYXAiLCJfY2FsbGVlJCIsIl9jb250ZXh0IiwicHJldiIsIm5leHQiLCJ1bmRlZmluZWQiLCJmZXRjaEZpbGUiLCJzZW50IiwicGFyc2UiLCJJbWFnZUxvYWRlciIsImFicnVwdCIsInBhcmFtZXRlcnMiLCJSR0IiLCJzdG9wIiwibG9hZE5weUFycmF5IiwiX3gyIiwiX3gzIiwiX3g0IiwiX2xvYWROcHlBcnJheSIsIl9jYWxsZWUzIiwicmVxdWVzdCIsInNwbGl0Iiwib3B0aW9ucyIsIl9yZXF1ZXN0JHJhc3RlclNlcnZlciIsIm51bUF0dGVtcHRzIiwiYXNzZXQiLCJfY2FsbGVlMyQiLCJfY29udGV4dDMiLCJyYXN0ZXJTZXJ2ZXJNYXhSZXRyaWVzIiwiZ2V0QXBwbGljYXRpb25Db25maWciLCJnZXRSZXF1ZXN0VGhyb3R0bGUiLCJ0aHJvdHRsZVJlcXVlc3QiLCJyYXN0ZXJTZXJ2ZXJVcmwiLCJfY2FsbGVlMiIsImF0dGVtcHQiLCJfcmVxdWVzdCRvcHRpb25zJHNpZ24iLCJfZ2V0TG9hZGVyT3B0aW9ucyIsIm5weU9wdGlvbnMiLCJoZWFkZXIiLCJzaGFwZSIsIl9nZXRXZWJHTDJUZXh0dXJlUGFyYSIsIl9zaGFwZSIsInoiLCJoZWlnaHQiLCJ3aWR0aCIsIm1pcG1hcHMiLCJjaGFubmVscyIsImNoYW5uZWxTaXplIiwiaSIsIl9yZWYzIiwiX3JlcXVlc3QkcmFzdGVyU2VydmVyMiIsIl9lcnJvciRyZXNwb25zZSIsIl9yZXF1ZXN0JHJhc3RlclNlcnZlcjMiLCJfY2FsbGVlMiQiLCJfY29udGV4dDIiLCJnZXRMb2FkZXJPcHRpb25zIiwibnB5IiwibG9hZCIsIk5QWUxvYWRlciIsImZldGNoIiwic2lnbmFsIiwiYWJvcnRlZCIsImZyb20iLCJfc2xpY2VkVG9BcnJheTIiLCJzdWJhcnJheSIsInQwIiwiRmV0Y2hFcnJvciIsInJhc3RlclNlcnZlclNlcnZlckVycm9yc1RvUmV0cnkiLCJpbmNsdWRlcyIsInN0YXR1cyIsInNsZWVwIiwicmFzdGVyU2VydmVyUmV0cnlEZWxheSIsInJhc3RlclNlcnZlck1heFBlclNlcnZlclJlcXVlc3RzIiwiZ2VuZXJhdGVDYXRlZ29yaWNhbENvbG9ybWFwVGV4dHVyZSIsImNhdGVnb3JpY2FsT3B0aW9ucyIsImdlbmVyYXRlQ2F0ZWdvcmljYWxCaXRtYXBBcnJheSIsIkNBVEVHT1JJQ0FMX1RFWFRVUkVfV0lEVEgiLCJSR0JBIiwiZ2V0TW9kdWxlcyIsIl9yZWYiLCJpbWFnZXMiLCJwcm9wcyIsIm1vZHVsZVByb3BzIiwibW9kdWxlcyIsImltYWdlUmdiYSIsInJlbmRlckJhbmRJbmRleGVzIiwibm9uTGluZWFyUmVzY2FsaW5nIiwibGluZWFyUmVzY2FsaW5nRmFjdG9yIiwibWluUGl4ZWxWYWx1ZSIsIm1heFBpeGVsVmFsdWUiLCJnYW1tYUNvbnRyYXN0RmFjdG9yIiwic2lnbW9pZGFsQ29udHJhc3RGYWN0b3IiLCJzaWdtb2lkYWxCaWFzRmFjdG9yIiwic2F0dXJhdGlvblZhbHVlIiwiYmFuZENvbWJpbmF0aW9uIiwiZmlsdGVyRW5hYmxlZCIsImZpbHRlclJhbmdlIiwiZGF0YVR5cGUiLCJtaW5DYXRlZ29yaWNhbEJhbmRWYWx1ZSIsIm1heENhdGVnb3JpY2FsQmFuZFZhbHVlIiwiaGFzQ2F0ZWdvcmljYWxDb2xvck1hcCIsIkFycmF5IiwiaXNBcnJheSIsIm1hc2tLZWVwTWluIiwib3JkZXJpbmciLCJnbG9iYWxSYW5nZSIsImxpbmVhclJlc2NhbGVTY2FsZXIiLCJsaW5lYXJSZXNjYWxlT2Zmc2V0IiwiaXNSZXNjYWxpbmdBbGxvd2VkIiwiX2xpbmVhclJlc2NhbGluZ0ZhY3RvIiwibWluIiwibWF4IiwibG9jYWxSYW5nZSIsImdhbW1hQ29udHJhc3RWYWx1ZSIsInNpZ21vaWRhbEJpYXMiLCJOdW1iZXIiLCJpc0Zpbml0ZSIsImlzRmlsdGVyQWxsb3dlZCIsImZpbHRlck1pbjEiLCJmaWx0ZXJNYXgxIiwiaXNDb2xvcm1hcEFsbG93ZWQiLCJpbWFnZUNvbG9ybWFwIiwiZGF0YVR5cGVNYXhWYWx1ZSIsImR0eXBlTWF4VmFsdWUiXSwic291cmNlcyI6WyIuLi8uLi9zcmMvcmFzdGVyLXRpbGUvZ3B1LXV0aWxzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNSVRcbi8vIENvcHlyaWdodCBjb250cmlidXRvcnMgdG8gdGhlIGtlcGxlci5nbCBwcm9qZWN0XG5cbi8qKlxuICogRnVuY3Rpb25zIGFuZCBjb25zdGFudHMgZm9yIGhhbmRsaW5nIHdlYmdsL2x1bWEuZ2wvZGVjay5nbCBlbnRpdGllc1xuICovXG5cbmltcG9ydCB7cGFyc2UsIGZldGNoRmlsZSwgbG9hZCwgRmV0Y2hFcnJvciwgTG9hZGVyfSBmcm9tICdAbG9hZGVycy5nbC9jb3JlJztcbmltcG9ydCB7SW1hZ2VMb2FkZXJ9IGZyb20gJ0Bsb2FkZXJzLmdsL2ltYWdlcyc7XG5pbXBvcnQge05QWUxvYWRlcn0gZnJvbSAnQGxvYWRlcnMuZ2wvdGV4dHVyZXMnO1xuLy8gQHRzLWlnbm9yZSBHTCByZXNvbHV0aW9uIGRlcGVuZHMgb24gbW9kdWxlUmVzb2x1dGlvbiBzZXR0aW5nXG5pbXBvcnQge0dMfSBmcm9tICdAbHVtYS5nbC9jb25zdGFudHMnO1xuXG4vKipcbiAqIExvb3NlIHRleHR1cmUgZGF0YSBkZXNjcmlwdG9yIHBhc3NlZCBhcm91bmQgYmVmb3JlIGFjdHVhbCBsdW1hLmdsIFRleHR1cmUgY3JlYXRpb24uXG4gKiBOb3QgdGhlIHNhbWUgYXMgbHVtYS5nbCdzIHN0cmljdCBUZXh0dXJlUHJvcHMgKHdoaWNoIHJlcXVpcmVzIHdpZHRoL2hlaWdodCkuXG4gKi9cbnR5cGUgVGV4dHVyZTJEUHJvcHMgPSBSZWNvcmQ8c3RyaW5nLCBhbnk+O1xuXG5pbXBvcnQge3NsZWVwfSBmcm9tICdAa2VwbGVyLmdsL2NvbW1vbi11dGlscyc7XG5pbXBvcnQge2dldExvYWRlck9wdGlvbnN9IGZyb20gJ0BrZXBsZXIuZ2wvY29uc3RhbnRzJztcbmltcG9ydCB7UmFzdGVyV2ViR0x9IGZyb20gJ0BrZXBsZXIuZ2wvZGVja2dsLWxheWVycyc7XG5pbXBvcnQge2dldEFwcGxpY2F0aW9uQ29uZmlnfSBmcm9tICdAa2VwbGVyLmdsL3V0aWxzJztcblxudHlwZSBTaGFkZXJNb2R1bGUgPSBSYXN0ZXJXZWJHTC5TaGFkZXJNb2R1bGU7XG5jb25zdCB7XG4gIGNvbWJpbmVCYW5kc0Zsb2F0LFxuICBjb21iaW5lQmFuZHNJbnQsXG4gIGNvbWJpbmVCYW5kc1VpbnQsXG4gIG1hc2tGbG9hdCxcbiAgbWFza0ludCxcbiAgbWFza1VpbnQsXG4gIGxpbmVhclJlc2NhbGUsXG4gIGdhbW1hQ29udHJhc3QsXG4gIHNpZ21vaWRhbENvbnRyYXN0LFxuICBub3JtYWxpemVkRGlmZmVyZW5jZSxcbiAgZW5oYW5jZWRWZWdldGF0aW9uSW5kZXgsXG4gIHNvaWxBZGp1c3RlZFZlZ2V0YXRpb25JbmRleCxcbiAgbW9kaWZpZWRTb2lsQWRqdXN0ZWRWZWdldGF0aW9uSW5kZXgsXG4gIGNvbG9ybWFwOiBjb2xvcm1hcE1vZHVsZSxcbiAgZmlsdGVyLFxuICBzYXR1cmF0aW9uLFxuICByZW9yZGVyQmFuZHMsXG4gIHJnYmFJbWFnZVxufSA9IFJhc3RlcldlYkdMO1xuXG5pbXBvcnQge1xuICBDQVRFR09SSUNBTF9URVhUVVJFX1dJRFRILFxuICBkdHlwZU1heFZhbHVlLFxuICBnZW5lcmF0ZUNhdGVnb3JpY2FsQml0bWFwQXJyYXksXG4gIGlzQ29sb3JtYXBBbGxvd2VkLFxuICBpc0ZpbHRlckFsbG93ZWQsXG4gIGlzUmVzY2FsaW5nQWxsb3dlZFxufSBmcm9tICcuL3Jhc3Rlci10aWxlLXV0aWxzJztcbmltcG9ydCB7XG4gIENhdGVnb3JpY2FsQ29sb3JtYXBPcHRpb25zLFxuICBJbWFnZURhdGEsXG4gIE5QWUxvYWRlckRhdGFUeXBlcyxcbiAgTlBZTG9hZGVyUmVzcG9uc2UsXG4gIFJlbmRlclN1YkxheWVyc1Byb3BzXG59IGZyb20gJy4vdHlwZXMnO1xuaW1wb3J0IHtnZXRSZXF1ZXN0VGhyb3R0bGV9IGZyb20gJy4vcmVxdWVzdC10aHJvdHRsZSc7XG5cbi8qKlxuICogRGVzY3JpYmUgV2ViR0wyIFRleHR1cmUgcGFyYW1ldGVycyB0byB1c2UgZm9yIGdpdmVuIGlucHV0IGRhdGEgdHlwZVxuICovXG5pbnRlcmZhY2UgV2ViR0xUZXh0dXJlRm9ybWF0IHtcbiAgZm9ybWF0OiBudW1iZXI7XG4gIGRhdGFGb3JtYXQ6IG51bWJlcjtcbiAgdHlwZTogbnVtYmVyO1xufVxuXG4vKipcbiAqIENvbnZlcnQgVHlwZWRBcnJheSB0byBXZWJHTDIgVGV4dHVyZSBQYXJhbWV0ZXJzXG4gKi9cbmZ1bmN0aW9uIGdldFdlYkdMMlRleHR1cmVQYXJhbWV0ZXJzKGRhdGE6IE5QWUxvYWRlckRhdGFUeXBlcyk6IFdlYkdMVGV4dHVyZUZvcm1hdCB8IG5ldmVyIHtcbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBVaW50OEFycmF5IHx8IGRhdGEgaW5zdGFuY2VvZiBVaW50OENsYW1wZWRBcnJheSkge1xuICAgIHJldHVybiB7XG4gICAgICAvLyBOb3RlOiB0ZXh0dXJlIGRhdGEgaGFzIG5vIGF1dG8tcmVzY2FsaW5nOyBwaXhlbCB2YWx1ZXMgc3RheSBhcyAwLTI1NVxuICAgICAgZm9ybWF0OiBHTC5SOFVJLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVEX0lOVEVHRVIsXG4gICAgICB0eXBlOiBHTC5VTlNJR05FRF9CWVRFXG4gICAgfTtcbiAgfVxuXG4gIGlmIChkYXRhIGluc3RhbmNlb2YgVWludDE2QXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SMTZVSSxcbiAgICAgIGRhdGFGb3JtYXQ6IEdMLlJFRF9JTlRFR0VSLFxuICAgICAgdHlwZTogR0wuVU5TSUdORURfU0hPUlRcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBVaW50MzJBcnJheSkge1xuICAgIHJldHVybiB7XG4gICAgICBmb3JtYXQ6IEdMLlIzMlVJLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVEX0lOVEVHRVIsXG4gICAgICB0eXBlOiBHTC5VTlNJR05FRF9JTlRcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBJbnQ4QXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SOEksXG4gICAgICBkYXRhRm9ybWF0OiBHTC5SRURfSU5URUdFUixcbiAgICAgIHR5cGU6IEdMLkJZVEVcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBJbnQxNkFycmF5KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGZvcm1hdDogR0wuUjE2SSxcbiAgICAgIGRhdGFGb3JtYXQ6IEdMLlJFRF9JTlRFR0VSLFxuICAgICAgdHlwZTogR0wuU0hPUlRcbiAgICB9O1xuICB9XG4gIGlmIChkYXRhIGluc3RhbmNlb2YgSW50MzJBcnJheSkge1xuICAgIHJldHVybiB7XG4gICAgICBmb3JtYXQ6IEdMLlIzMkksXG4gICAgICBkYXRhRm9ybWF0OiBHTC5SRURfSU5URUdFUixcbiAgICAgIHR5cGU6IEdMLklOVFxuICAgIH07XG4gIH1cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBGbG9hdDMyQXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SMzJGLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVELFxuICAgICAgdHlwZTogR0wuRkxPQVRcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBGbG9hdDY0QXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SMzJGLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVELFxuICAgICAgdHlwZTogR0wuRkxPQVRcbiAgICB9O1xuICB9XG5cbiAgLy8gRm9yIGV4aGF1c3RpdmUgY2hlY2sgYWJvdmU7IGZvbGxvd2luZyBzaG91bGQgbmV2ZXIgb2NjdXJcbiAgLy8gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9hLzU4MDA5OTkyXG4gIGNvbnN0IHVuZXhwZWN0ZWRJbnB1dDogbmV2ZXIgPSBkYXRhO1xuICB0aHJvdyBuZXcgRXJyb3IodW5leHBlY3RlZElucHV0KTtcbn1cblxuLyoqXG4gKiBEaXNjcmV0ZS12YWx1ZWQgY29sb3JtYXBzIChlLmcuIGZyb20gdGhlIG91dHB1dCBvZlxuICogY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcykgaW4gdGhlIHJhc3RlciBsYXllci4gUHJldmlvdXNseSwgdGhlIHZhbHVlcyBwYXNzZWQgdG9cbiAqIGBURVhUVVJFX01JTl9GSUxURVJgIGFuZCBgVEVYVFVSRV9NQUdfRklMVEVSYCB3ZXJlIGBHTC5MSU5FQVJgLCB3aGljaCBtZWFudCB0aGF0IHRoZSBHUFUgd291bGRcbiAqIGxpbmVhcmx5IGludGVycG9sYXRlIHZhbHVlcyBiZXR3ZWVuIHR3byBuZWlnaGJvcmluZyBjb2xvcm1hcCBwaXhlbCB2YWx1ZXMuIFNldHRpbmcgdGhlc2UgdmFsdWVzXG4gKiB0byBORUFSRVNUIG1lYW5zIHRoYXQgdGhlIEdQVSB3aWxsIGNob29zZSB0aGUgbmVhcmVzdCB2YWx1ZSBvbiB0aGUgdGV4dHVyZTJEIGxvb2t1cCBvcGVyYXRpb24sXG4gKiB3aGljaCBmaXhlcyBwcmVjaXNpb24gaXNzdWVzIGZvciBkaXNjcmV0ZS12YWx1ZWQgY29sb3JtYXBzLiBUaGlzIHNob3VsZCBiZSBvayBmb3IgY29udGludW91c1xuICogY29sb3JtYXBzIGFzIGxvbmcgYXMgdGhlIGNvbG9yIGRpZmZlcmVuY2UgYmV0d2VlbiBlYWNoIHBpeGVsIG9uIHRoZSBjb2xvcm1hcCBpcyBzbWFsbC5cbiAqL1xuZXhwb3J0IGNvbnN0IENPTE9STUFQX1RFWFRVUkVfUEFSQU1FVEVSUyA9IHtcbiAgW0dMLlRFWFRVUkVfTUlOX0ZJTFRFUl06IEdMLk5FQVJFU1QsXG4gIFtHTC5URVhUVVJFX01BR19GSUxURVJdOiBHTC5ORUFSRVNULFxuICBbR0wuVEVYVFVSRV9XUkFQX1NdOiBHTC5DTEFNUF9UT19FREdFLFxuICBbR0wuVEVYVFVSRV9XUkFQX1RdOiBHTC5DTEFNUF9UT19FREdFXG59O1xuXG5jb25zdCBERUZBVUxUXzhCSVRfVEVYVFVSRV9QQVJBTUVURVJTID0ge1xuICBbR0wuVEVYVFVSRV9NSU5fRklMVEVSXTogR0wuTElORUFSX01JUE1BUF9MSU5FQVIsXG4gIFtHTC5URVhUVVJFX01BR19GSUxURVJdOiBHTC5MSU5FQVIsXG4gIFtHTC5URVhUVVJFX1dSQVBfU106IEdMLkNMQU1QX1RPX0VER0UsXG4gIFtHTC5URVhUVVJFX1dSQVBfVF06IEdMLkNMQU1QX1RPX0VER0Vcbn07XG5cbmNvbnN0IERFRkFVTFRfSElHSF9CSVRfVEVYVFVSRV9QQVJBTUVURVJTID0ge1xuICBbR0wuVEVYVFVSRV9NSU5fRklMVEVSXTogR0wuTkVBUkVTVCxcbiAgW0dMLlRFWFRVUkVfTUFHX0ZJTFRFUl06IEdMLk5FQVJFU1QsXG4gIFtHTC5URVhUVVJFX1dSQVBfU106IEdMLkNMQU1QX1RPX0VER0UsXG4gIFtHTC5URVhUVVJFX1dSQVBfVF06IEdMLkNMQU1QX1RPX0VER0Vcbn07XG5cbi8qKlxuICogU2VsZWN0IGNvcnJlY3QgbW9kdWxlIHR5cGUgZm9yIFwiY29tYmluZUJhbmRzXCJcbiAqXG4gKiBjb21iaW5lQmFuZHMgam9pbnMgdXAgdG8gZm91ciAyRCBhcnJheXMgKGNvbnRhaW5lZCBpbiBpbWFnZUJhbmRzKSBpbnRvIGEgc2luZ2xlIFwicmdiYVwiIGltYWdlXG4gKiB0ZXh0dXJlIG9uIHRoZSBHUFUuIFRoYXQgc2hhZGVyIGNvZGUgbmVlZHMgdG8gaGF2ZSB0aGUgc2FtZSBkYXRhIHR5cGUgYXMgdGhlIGFjdHVhbCBpbWFnZSBkYXRhLlxuICogRS5nLiBmb3IgZmxvYXQgZGF0YSB0aGUgdGV4dHVyZSBuZWVkcyB0byBiZSBgc2FtcGxlcjJEYCwgZm9yIHVpbnQgZGF0YSB0aGUgdGV4dHVyZSBuZWVkcyB0byBiZVxuICogYHVzYW1wbGVyMkRgIGFuZCBmb3IgaW50IGRhdGEgdGhlIHRleHR1cmUgbmVlZHMgdG8gYmUgYGlzYW1wbGVyMkRgLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0Q29tYmluZUJhbmRzTW9kdWxlKGltYWdlQmFuZHM6IFRleHR1cmUyRFByb3BzW10pOiBTaGFkZXJNb2R1bGUge1xuICAvLyBFYWNoIGltYWdlIGFycmF5IGlzIGV4cGVjdGVkL3JlcXVpcmVkIHRvIGJlIG9mIHRoZSBzYW1lIGRhdGEgdHlwZVxuICBzd2l0Y2ggKGltYWdlQmFuZHNbMF0uZm9ybWF0KSB7XG4gICAgY2FzZSBHTC5SOFVJOlxuICAgICAgcmV0dXJuIGNvbWJpbmVCYW5kc1VpbnQ7XG4gICAgY2FzZSBHTC5SMTZVSTpcbiAgICAgIHJldHVybiBjb21iaW5lQmFuZHNVaW50O1xuICAgIGNhc2UgR0wuUjMyVUk6XG4gICAgICByZXR1cm4gY29tYmluZUJhbmRzVWludDtcbiAgICBjYXNlIEdMLlI4STpcbiAgICAgIHJldHVybiBjb21iaW5lQmFuZHNJbnQ7XG4gICAgY2FzZSBHTC5SMTZJOlxuICAgICAgcmV0dXJuIGNvbWJpbmVCYW5kc0ludDtcbiAgICBjYXNlIEdMLlIzMkk6XG4gICAgICByZXR1cm4gY29tYmluZUJhbmRzSW50O1xuICAgIGNhc2UgR0wuUjMyRjpcbiAgICAgIHJldHVybiBjb21iaW5lQmFuZHNGbG9hdDtcbiAgICBkZWZhdWx0OlxuICAgICAgdGhyb3cgbmV3IEVycm9yKCdiYWQgZm9ybWF0Jyk7XG4gIH1cbn1cblxuLyoqIFNlbGVjdCBjb3JyZWN0IGltYWdlIG1hc2tpbmcgc2hhZGVyIG1vZHVsZSBmb3IgbWFzayBkYXRhIHR5cGVcbiAqIFRoZSBpbWFnZU1hc2sgY291bGQgKGF0IGxlYXN0IGluIHRoZSBmdXR1cmUsIHRoZW9yZXRpY2FsbHkpIGJlIG9mIGEgZGlmZmVyZW50IGRhdGEgZm9ybWF0IHRoYW5cbiAqIHRoZSBpbWFnZUJhbmRzIGRhdGEgaXRzZWxmLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0SW1hZ2VNYXNrTW9kdWxlKGltYWdlTWFzazogVGV4dHVyZTJEUHJvcHMpOiBTaGFkZXJNb2R1bGUge1xuICBzd2l0Y2ggKGltYWdlTWFzay5mb3JtYXQpIHtcbiAgICBjYXNlIEdMLlI4VUk6XG4gICAgICByZXR1cm4gbWFza1VpbnQ7XG4gICAgY2FzZSBHTC5SMTZVSTpcbiAgICAgIHJldHVybiBtYXNrVWludDtcbiAgICBjYXNlIEdMLlIzMlVJOlxuICAgICAgcmV0dXJuIG1hc2tVaW50O1xuICAgIGNhc2UgR0wuUjhJOlxuICAgICAgcmV0dXJuIG1hc2tJbnQ7XG4gICAgY2FzZSBHTC5SMTZJOlxuICAgICAgcmV0dXJuIG1hc2tJbnQ7XG4gICAgY2FzZSBHTC5SMzJJOlxuICAgICAgcmV0dXJuIG1hc2tJbnQ7XG4gICAgY2FzZSBHTC5SMzJGOlxuICAgICAgcmV0dXJuIG1hc2tGbG9hdDtcbiAgICBkZWZhdWx0OlxuICAgICAgdGhyb3cgbmV3IEVycm9yKCdiYWQgZm9ybWF0Jyk7XG4gIH1cbn1cblxuLyoqXG4gKiBMb2FkIGltYWdlIGFuZCB3cmFwIHdpdGggZGVmYXVsdCBXZWJHTCB0ZXh0dXJlIHBhcmFtZXRlcnNcbiAqXG4gKiBAcGFyYW0gdXJsIFVSTCB0byBsb2FkIGltYWdlXG4gKiBAcGFyYW0gdGV4dHVyZVBhcmFtcyBwYXJhbWV0ZXJzIHRvIHBhc3MgdG8gVGV4dHVyZTJEXG4gKlxuICogQHJldHVybiBpbWFnZSBvYmplY3QgdG8gcGFzcyB0byBUZXh0dXJlMkQgY29uc3RydWN0b3JcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRJbWFnZShcbiAgdXJsOiBzdHJpbmcsXG4gIHRleHR1cmVQYXJhbXM6IFRleHR1cmUyRFByb3BzID0ge30sXG4gIHJlcXVlc3RPcHRpb25zOiBSZXF1ZXN0SW5pdCA9IHt9XG4pOiBQcm9taXNlPFRleHR1cmUyRFByb3BzPiB7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2hGaWxlKHVybCwgcmVxdWVzdE9wdGlvbnMpO1xuICBjb25zdCBpbWFnZSA9IGF3YWl0IHBhcnNlKHJlc3BvbnNlLCBJbWFnZUxvYWRlcik7XG5cbiAgcmV0dXJuIHtcbiAgICBkYXRhOiBpbWFnZSxcbiAgICBwYXJhbWV0ZXJzOiBERUZBVUxUXzhCSVRfVEVYVFVSRV9QQVJBTUVURVJTLFxuICAgIGZvcm1hdDogR0wuUkdCLFxuICAgIC4uLnRleHR1cmVQYXJhbXNcbiAgfTtcbn1cblxudHlwZSBGZXRjaExpa2UgPSAodXJsOiBzdHJpbmcsIG9wdGlvbnM/OiBSZXF1ZXN0SW5pdCkgPT4gUHJvbWlzZTxSZXNwb25zZT47XG50eXBlIExvYWRpbmdPcHRpb25zID0ge1xuICBmZXRjaD86IHR5cGVvZiBmZXRjaCB8IEZldGNoTGlrZTtcbn07XG5cbnR5cGUgTnB5UmVxdWVzdCA9IHtcbiAgdXJsOiBzdHJpbmc7XG4gIHJhc3RlclNlcnZlclVybDogc3RyaW5nO1xuICBvcHRpb25zOiBSZXF1ZXN0SW5pdDtcbiAgcmFzdGVyU2VydmVyTWF4UmV0cmllcz86IG51bWJlcjtcbiAgcmFzdGVyU2VydmVyUmV0cnlEZWxheT86IG51bWJlcjtcbiAgcmFzdGVyU2VydmVyU2VydmVyRXJyb3JzVG9SZXRyeT86IG51bWJlcltdO1xuICByYXN0ZXJTZXJ2ZXJNYXhQZXJTZXJ2ZXJSZXF1ZXN0cz86IG51bWJlcjtcbn07XG5cbi8qKlxuICogTG9hZCBOUFkgQXJyYXlcbiAqXG4gKiBUaGUgTlBZIGZvcm1hdCBpcyBkZXNjcmliZWQgaGVyZTogaHR0cHM6Ly9udW1weS5vcmcvZG9jL3N0YWJsZS9yZWZlcmVuY2UvZ2VuZXJhdGVkL251bXB5LmxpYi5mb3JtYXQuaHRtbC5cbiAqIEl0J3MgZGVzaWduZWQgdG8gYmUgYSB2ZXJ5IHNpbXBsZSBmaWxlIGZvcm1hdCB0byBob2xkIGFuIE4tZGltZW5zaW9uYWwgYmxvY2sgb2YgZGF0YS4gVGhlIGhlYWRlciBkZXNjcmliZXMgdGhlIGRhdGEgdHlwZSwgc2hhcGUsIGFuZCBvcmRlciAoZWl0aGVyIEMgb3IgRm9ydHJhbikgb2YgdGhlIGFycmF5LlxuICpcbiAqIEBwYXJhbSB1cmwgVVJMIHRvIGxvYWQgTlBZIEFycmF5XG4gKiBAcGFyYW0gc3BsaXQgV2hldGhlciB0byBzcGxpdCBzaW5nbGUgdHlwZWQgYXJyYXkgcmVwcmVzZW50aW5nIGFuIE4tZGltZW5zaW9uYWwgYXJyYXkgaW50byBhbiBBcnJheSB3aXRoIGVhY2ggZGltZW5zaW9uIGFzIGl0cyBvd24gdHlwZWQgYXJyYXlcbiAqXG4gKiBAcmV0dXJuIGltYWdlIG9iamVjdCB0byBwYXNzIHRvIFRleHR1cmUyRCBjb25zdHJ1Y3RvclxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZE5weUFycmF5KFxuICByZXF1ZXN0OiBOcHlSZXF1ZXN0LFxuICBzcGxpdDogdHJ1ZSxcbiAgb3B0aW9ucz86IExvYWRpbmdPcHRpb25zXG4pOiBQcm9taXNlPFRleHR1cmUyRFByb3BzW10gfCBudWxsPjtcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkTnB5QXJyYXkoXG4gIHJlcXVlc3Q6IE5weVJlcXVlc3QsXG4gIHNwbGl0OiBmYWxzZSxcbiAgb3B0aW9ucz86IExvYWRpbmdPcHRpb25zXG4pOiBQcm9taXNlPFRleHR1cmUyRFByb3BzIHwgbnVsbD47XG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZE5weUFycmF5KFxuICByZXF1ZXN0OiBOcHlSZXF1ZXN0LFxuICBzcGxpdDogYm9vbGVhbixcbiAgb3B0aW9ucz86IExvYWRpbmdPcHRpb25zXG4pOiBQcm9taXNlPFRleHR1cmUyRFByb3BzIHwgVGV4dHVyZTJEUHJvcHNbXSB8IG51bGw+IHtcbiAgY29uc3QgbnVtQXR0ZW1wdHMgPVxuICAgIDEgKyAocmVxdWVzdC5yYXN0ZXJTZXJ2ZXJNYXhSZXRyaWVzID8/IGdldEFwcGxpY2F0aW9uQ29uZmlnKCkucmFzdGVyU2VydmVyTWF4UmV0cmllcyk7XG5cbiAgY29uc3QgYXNzZXQgPSBhd2FpdCBnZXRSZXF1ZXN0VGhyb3R0bGUoKS50aHJvdHRsZVJlcXVlc3QoXG4gICAgcmVxdWVzdC5yYXN0ZXJTZXJ2ZXJVcmwsXG4gICAgYXN5bmMgKCkgPT4ge1xuICAgICAgZm9yIChsZXQgYXR0ZW1wdCA9IDA7IGF0dGVtcHQgPCBudW1BdHRlbXB0czsgYXR0ZW1wdCsrKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3Qge25weTogbnB5T3B0aW9uc30gPSBnZXRMb2FkZXJPcHRpb25zKCk7XG5cbiAgICAgICAgICBjb25zdCByZXNwb25zZSA9IChhd2FpdCBsb2FkKHJlcXVlc3QudXJsLCBOUFlMb2FkZXIgYXMgTG9hZGVyPE5QWUxvYWRlclJlc3BvbnNlPiwge1xuICAgICAgICAgICAgbnB5OiBucHlPcHRpb25zLFxuICAgICAgICAgICAgZmV0Y2g6IG9wdGlvbnM/LmZldGNoXG4gICAgICAgICAgfSkpIGFzIE5QWUxvYWRlclJlc3BvbnNlO1xuXG4gICAgICAgICAgaWYgKCFyZXNwb25zZSB8fCAhcmVzcG9uc2UuZGF0YSB8fCByZXF1ZXN0Lm9wdGlvbnMuc2lnbmFsPy5hYm9ydGVkKSB7XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBGbG9hdDY0IGRhdGEgbmVlZHMgdG8gYmUgY29lcmNlZCB0byBGbG9hdDMyIGZvciB0aGUgR1BVXG4gICAgICAgICAgaWYgKHJlc3BvbnNlLmRhdGEgaW5zdGFuY2VvZiBGbG9hdDY0QXJyYXkpIHtcbiAgICAgICAgICAgIHJlc3BvbnNlLmRhdGEgPSBGbG9hdDMyQXJyYXkuZnJvbShyZXNwb25zZS5kYXRhKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBjb25zdCB7ZGF0YSwgaGVhZGVyfSA9IHJlc3BvbnNlO1xuICAgICAgICAgIGNvbnN0IHtzaGFwZX0gPSBoZWFkZXI7XG4gICAgICAgICAgY29uc3Qge2Zvcm1hdCwgZGF0YUZvcm1hdCwgdHlwZX0gPSBnZXRXZWJHTDJUZXh0dXJlUGFyYW1ldGVycyhkYXRhKTtcblxuICAgICAgICAgIC8vIFRPRE86IGNoZWNrIGhlaWdodC13aWR0aCBvciB3aWR0aC1oZWlnaHRcbiAgICAgICAgICAvLyBSZWdhcmRsZXNzLCBpbWFnZXMgdXN1YWxseSBzcXVhcmVcbiAgICAgICAgICAvLyBUT0RPOiBoYW5kbGUgY2FzZXMgb2YgMjU2eDI1NngxIGluc3RlYWQgb2YgMXgyNTZ4MjU2XG4gICAgICAgICAgY29uc3QgW3osIGhlaWdodCwgd2lkdGhdID0gc2hhcGU7XG5cbiAgICAgICAgICAvLyBTaW5jZSB3ZSBub3cgdXNlIFdlYkdMMiBkYXRhIHR5cGVzIGZvciA4LWJpdCB0ZXh0dXJlcywgd2Ugc2V0IHRoZSBmb2xsb3dpbmcgZm9yIGFsbCB0ZXh0dXJlc1xuICAgICAgICAgIGNvbnN0IG1pcG1hcHMgPSBmYWxzZTtcbiAgICAgICAgICBjb25zdCBwYXJhbWV0ZXJzID0gREVGQVVMVF9ISUdIX0JJVF9URVhUVVJFX1BBUkFNRVRFUlM7XG5cbiAgICAgICAgICBpZiAoIXNwbGl0KSB7XG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICBkYXRhLFxuICAgICAgICAgICAgICB3aWR0aCxcbiAgICAgICAgICAgICAgaGVpZ2h0LFxuICAgICAgICAgICAgICBmb3JtYXQsXG4gICAgICAgICAgICAgIGRhdGFGb3JtYXQsXG4gICAgICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgICAgIHBhcmFtZXRlcnMsXG4gICAgICAgICAgICAgIG1pcG1hcHNcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gU3BsaXQgaW50byBpbmRpdmlkdWFsIGFycmF5c1xuICAgICAgICAgIGNvbnN0IGNoYW5uZWxzOiBUZXh0dXJlMkRQcm9wc1tdID0gW107XG4gICAgICAgICAgY29uc3QgY2hhbm5lbFNpemUgPSBoZWlnaHQgKiB3aWR0aDtcbiAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHo7IGkrKykge1xuICAgICAgICAgICAgY2hhbm5lbHMucHVzaCh7XG4gICAgICAgICAgICAgIGRhdGE6IGRhdGEuc3ViYXJyYXkoaSAqIGNoYW5uZWxTaXplLCAoaSArIDEpICogY2hhbm5lbFNpemUpLFxuICAgICAgICAgICAgICB3aWR0aCxcbiAgICAgICAgICAgICAgaGVpZ2h0LFxuICAgICAgICAgICAgICBmb3JtYXQsXG4gICAgICAgICAgICAgIGRhdGFGb3JtYXQsXG4gICAgICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgICAgIHBhcmFtZXRlcnMsXG4gICAgICAgICAgICAgIG1pcG1hcHNcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gY2hhbm5lbHM7XG4gICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgLy8gUmV0cnkgaWYgU2VydmljZSBUZW1wb3JhcmlseSBVbmF2YWlsYWJsZSA1MDMgZXJyb3IgZXRjLlxuICAgICAgICAgIGlmIChcbiAgICAgICAgICAgIGF0dGVtcHQgPCBudW1BdHRlbXB0cyAmJlxuICAgICAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBGZXRjaEVycm9yICYmXG4gICAgICAgICAgICAoXG4gICAgICAgICAgICAgIHJlcXVlc3QucmFzdGVyU2VydmVyU2VydmVyRXJyb3JzVG9SZXRyeSA/P1xuICAgICAgICAgICAgICBnZXRBcHBsaWNhdGlvbkNvbmZpZygpLnJhc3RlclNlcnZlclNlcnZlckVycm9yc1RvUmV0cnlcbiAgICAgICAgICAgICk/LmluY2x1ZGVzKGVycm9yLnJlc3BvbnNlPy5zdGF0dXMgYXMgbnVtYmVyKVxuICAgICAgICAgICkge1xuICAgICAgICAgICAgYXdhaXQgc2xlZXAoXG4gICAgICAgICAgICAgIHJlcXVlc3QucmFzdGVyU2VydmVyUmV0cnlEZWxheSA/PyBnZXRBcHBsaWNhdGlvbkNvbmZpZygpLnJhc3RlclNlcnZlclJldHJ5RGVsYXlcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBudWxsO1xuICAgIH0sXG4gICAgcmVxdWVzdC5yYXN0ZXJTZXJ2ZXJNYXhQZXJTZXJ2ZXJSZXF1ZXN0c1xuICApO1xuXG4gIHJldHVybiBhc3NldDtcbn1cblxuLyoqXG4gKiBDcmVhdGUgdGV4dHVyZSBkYXRhIGZvciBjYXRlZ29yaWNhbCBjb2xvcm1hcCBzY2FsZVxuICogQHBhcmFtIGNhdGVnb3JpY2FsT3B0aW9ucyAtIGNvbG9yIG1hcCBjb25maWd1cmF0aW9uIGFuZCBtaW4tbWF4IHZhbHVlcyBvZiBjYXRlZ29yaWNhbCBiYW5kXG4gKiBAcmV0dXJucyB0ZXh0dXJlIGRhdGFcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdlbmVyYXRlQ2F0ZWdvcmljYWxDb2xvcm1hcFRleHR1cmUoXG4gIGNhdGVnb3JpY2FsT3B0aW9uczogQ2F0ZWdvcmljYWxDb2xvcm1hcE9wdGlvbnNcbik6IFRleHR1cmUyRFByb3BzIHtcbiAgY29uc3QgZGF0YSA9IGdlbmVyYXRlQ2F0ZWdvcmljYWxCaXRtYXBBcnJheShjYXRlZ29yaWNhbE9wdGlvbnMpO1xuICByZXR1cm4ge1xuICAgIGRhdGEsXG4gICAgd2lkdGg6IENBVEVHT1JJQ0FMX1RFWFRVUkVfV0lEVEgsXG4gICAgaGVpZ2h0OiAxLFxuICAgIGZvcm1hdDogR0wuUkdCQSxcbiAgICBkYXRhRm9ybWF0OiBHTC5SR0JBLFxuICAgIHR5cGU6IEdMLlVOU0lHTkVEX0JZVEUsXG4gICAgcGFyYW1ldGVyczogQ09MT1JNQVBfVEVYVFVSRV9QQVJBTUVURVJTLFxuICAgIG1pcG1hcHM6IGZhbHNlXG4gIH07XG59XG5cbi8vIFRPRE86IHdvdWxkIHByb2JhYmx5IGJlIHNpbXBsZXIgdG8gb25seSBwYXNzIGluIHRoZSBwcm9wcyBhY3R1YWxseSB1c2VkIGJ5IHRoaXMgZnVuY3Rpb24uIFRoYXRcbi8vIHdvdWxkIG1lYW4gYSBzbWFsbGVyIG9iamVjdCB0aGFuIFJlbmRlclN1YkxheWVyc1Byb3BzXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbWF4LXN0YXRlbWVudHMsIGNvbXBsZXhpdHlcbmV4cG9ydCBmdW5jdGlvbiBnZXRNb2R1bGVzKHtcbiAgaW1hZ2VzLFxuICBwcm9wc1xufToge1xuICBpbWFnZXM6IFBhcnRpYWw8SW1hZ2VEYXRhPjtcbiAgcHJvcHM/OiBSZW5kZXJTdWJMYXllcnNQcm9wcztcbn0pOiB7XG4gIG1vZHVsZXM6IFNoYWRlck1vZHVsZVtdO1xuICBtb2R1bGVQcm9wczogUmVjb3JkPHN0cmluZywgYW55Pjtcbn0ge1xuICBjb25zdCBtb2R1bGVQcm9wczogUmVjb3JkPHN0cmluZywgYW55PiA9IHt9O1xuICAvLyBBcnJheSBvZiBsdW1hLmdsIFdlYkdMIG1vZHVsZXMgdG8gcGFzcyB0byB0aGUgUmFzdGVyTGF5ZXJcbiAgY29uc3QgbW9kdWxlczogU2hhZGVyTW9kdWxlW10gPSBbXTtcblxuICAvLyB1c2UgcmdiYSBpbWFnZSBkaXJlY3RseS4gVXNlZCBmb3IgcmFzdGVyIC5wbXRpbGVzIHJlbmRlcmluZ1xuICBpZiAoaW1hZ2VzLmltYWdlUmdiYSkge1xuICAgIG1vZHVsZXMucHVzaChyZ2JhSW1hZ2UpO1xuXG4gICAgLy8gbm8gc3VwcG9ydCBmb3Igb3RoZXIgbW9kdWxlcyBhdG0gZm9yIGRpcmVjdCByZ2JhIG1vZGVcbiAgICByZXR1cm4ge21vZHVsZXMsIG1vZHVsZVByb3BzfTtcbiAgfVxuXG4gIGlmICghcHJvcHMpIHtcbiAgICByZXR1cm4ge21vZHVsZXMsIG1vZHVsZVByb3BzfTtcbiAgfVxuXG4gIGNvbnN0IHtcbiAgICByZW5kZXJCYW5kSW5kZXhlcyxcbiAgICBub25MaW5lYXJSZXNjYWxpbmcsXG4gICAgbGluZWFyUmVzY2FsaW5nRmFjdG9yLFxuICAgIG1pblBpeGVsVmFsdWUsXG4gICAgbWF4UGl4ZWxWYWx1ZSxcbiAgICBnYW1tYUNvbnRyYXN0RmFjdG9yLFxuICAgIHNpZ21vaWRhbENvbnRyYXN0RmFjdG9yLFxuICAgIHNpZ21vaWRhbEJpYXNGYWN0b3IsXG4gICAgc2F0dXJhdGlvblZhbHVlLFxuICAgIGJhbmRDb21iaW5hdGlvbixcbiAgICBmaWx0ZXJFbmFibGVkLFxuICAgIGZpbHRlclJhbmdlLFxuICAgIGRhdGFUeXBlLFxuICAgIG1pbkNhdGVnb3JpY2FsQmFuZFZhbHVlLFxuICAgIG1heENhdGVnb3JpY2FsQmFuZFZhbHVlLFxuICAgIGhhc0NhdGVnb3JpY2FsQ29sb3JNYXBcbiAgfSA9IHByb3BzO1xuXG4gIGlmIChBcnJheS5pc0FycmF5KGltYWdlcy5pbWFnZUJhbmRzKSAmJiBpbWFnZXMuaW1hZ2VCYW5kcy5sZW5ndGggPiAwKSB7XG4gICAgbW9kdWxlcy5wdXNoKGdldENvbWJpbmVCYW5kc01vZHVsZShpbWFnZXMuaW1hZ2VCYW5kcykpO1xuICB9XG5cbiAgaWYgKGltYWdlcy5pbWFnZU1hc2spIHtcbiAgICBtb2R1bGVzLnB1c2goZ2V0SW1hZ2VNYXNrTW9kdWxlKGltYWdlcy5pbWFnZU1hc2spKTtcbiAgICAvLyBJbiBnZW5lcmFsLCBkYXRhIG1hc2tzIGFyZSAwIGZvciBub2RhdGEgYW5kIHRoZSBtYXhpbXVtIHZhbHVlIGZvciB2YWxpZCBkYXRhLCBlLmcuIDI1NSBvclxuICAgIC8vIDY1NTM1IGZvciB1aW50OCBvciB1aW50MTYgZGF0YSwgcmVzcGVjdGl2ZWx5XG4gICAgbW9kdWxlUHJvcHMubWFza0tlZXBNaW4gPSAxO1xuICB9XG5cbiAgaWYgKEFycmF5LmlzQXJyYXkocmVuZGVyQmFuZEluZGV4ZXMpKSB7XG4gICAgbW9kdWxlcy5wdXNoKHJlb3JkZXJCYW5kcyk7XG4gICAgbW9kdWxlUHJvcHMub3JkZXJpbmcgPSByZW5kZXJCYW5kSW5kZXhlcztcbiAgfVxuXG4gIGNvbnN0IGdsb2JhbFJhbmdlID0gbWF4UGl4ZWxWYWx1ZSAtIG1pblBpeGVsVmFsdWU7XG4gIC8vIEZpeCByZXNjYWxpbmcgaWYgd2UgYXJlIHN1cmUgdGhhdCBkYXRhc2V0IGlzIGNhdGVnb3JpY2FsXG4gIGlmIChoYXNDYXRlZ29yaWNhbENvbG9yTWFwKSB7XG4gICAgbW9kdWxlcy5wdXNoKGxpbmVhclJlc2NhbGUpO1xuICAgIG1vZHVsZVByb3BzLmxpbmVhclJlc2NhbGVTY2FsZXIgPSAxIC8gbWF4UGl4ZWxWYWx1ZTtcbiAgICBtb2R1bGVQcm9wcy5saW5lYXJSZXNjYWxlT2Zmc2V0ID0gMDtcbiAgfSBlbHNlIGlmIChpc1Jlc2NhbGluZ0FsbG93ZWQoYmFuZENvbWJpbmF0aW9uKSkge1xuICAgIGlmICghbm9uTGluZWFyUmVzY2FsaW5nKSB7XG4gICAgICBjb25zdCBbbWluLCBtYXhdID0gbGluZWFyUmVzY2FsaW5nRmFjdG9yO1xuICAgICAgY29uc3QgbG9jYWxSYW5nZSA9IG1heCAtIG1pbjtcblxuICAgICAgLy8gQWRkIGxpbmVhciByZXNjYWxpbmcgbW9kdWxlXG4gICAgICBtb2R1bGVzLnB1c2gobGluZWFyUmVzY2FsZSk7XG5cbiAgICAgIC8vIERpdmlkZSBieSBsb2NhbCByYW5nZSAqIGdsb2JhbCByYW5nZVxuICAgICAgbW9kdWxlUHJvcHMubGluZWFyUmVzY2FsZVNjYWxlciA9IDEgLyAobG9jYWxSYW5nZSAqIGdsb2JhbFJhbmdlKTtcblxuICAgICAgLy8gU3VidHJhY3Qgb2ZmIHRoZSBsb2NhbCBtaW5cbiAgICAgIG1vZHVsZVByb3BzLmxpbmVhclJlc2NhbGVPZmZzZXQgPSAtbWluO1xuXG4gICAgICAvLyBDbGFtcCB0byBbMCwgMV0gZG9uZSBhdXRvbWF0aWNhbGx5P1xuICAgIH0gZWxzZSB7XG4gICAgICBtb2R1bGVzLnB1c2gobGluZWFyUmVzY2FsZSk7XG4gICAgICBtb2R1bGVQcm9wcy5saW5lYXJSZXNjYWxlU2NhbGVyID0gMSAvIG1heFBpeGVsVmFsdWU7XG4gICAgICBtb2R1bGVQcm9wcy5saW5lYXJSZXNjYWxlT2Zmc2V0ID0gMDtcblxuICAgICAgbW9kdWxlcy5wdXNoKGdhbW1hQ29udHJhc3QpO1xuICAgICAgbW9kdWxlUHJvcHMuZ2FtbWFDb250cmFzdFZhbHVlID0gZ2FtbWFDb250cmFzdEZhY3RvcjtcblxuICAgICAgbW9kdWxlcy5wdXNoKHNpZ21vaWRhbENvbnRyYXN0KTtcbiAgICAgIG1vZHVsZVByb3BzLnNpZ21vaWRhbENvbnRyYXN0ID0gc2lnbW9pZGFsQ29udHJhc3RGYWN0b3I7XG4gICAgICBtb2R1bGVQcm9wcy5zaWdtb2lkYWxCaWFzID0gc2lnbW9pZGFsQmlhc0ZhY3RvcjtcbiAgICB9XG5cbiAgICBpZiAoTnVtYmVyLmlzRmluaXRlKHNhdHVyYXRpb25WYWx1ZSkgJiYgc2F0dXJhdGlvblZhbHVlICE9PSAxKSB7XG4gICAgICBtb2R1bGVzLnB1c2goc2F0dXJhdGlvbik7XG4gICAgICBtb2R1bGVQcm9wcy5zYXR1cmF0aW9uVmFsdWUgPSBzYXR1cmF0aW9uVmFsdWU7XG4gICAgfVxuICB9XG5cbiAgc3dpdGNoIChiYW5kQ29tYmluYXRpb24pIHtcbiAgICBjYXNlICdub3JtYWxpemVkRGlmZmVyZW5jZSc6XG4gICAgICBtb2R1bGVzLnB1c2gobm9ybWFsaXplZERpZmZlcmVuY2UpO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSAnZW5oYW5jZWRWZWdldGF0aW9uSW5kZXgnOlxuICAgICAgbW9kdWxlcy5wdXNoKGVuaGFuY2VkVmVnZXRhdGlvbkluZGV4KTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ3NvaWxBZGp1c3RlZFZlZ2V0YXRpb25JbmRleCc6XG4gICAgICBtb2R1bGVzLnB1c2goc29pbEFkanVzdGVkVmVnZXRhdGlvbkluZGV4KTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ21vZGlmaWVkU29pbEFkanVzdGVkVmVnZXRhdGlvbkluZGV4JzpcbiAgICAgIG1vZHVsZXMucHVzaChtb2RpZmllZFNvaWxBZGp1c3RlZFZlZ2V0YXRpb25JbmRleCk7XG4gICAgICBicmVhaztcbiAgICBkZWZhdWx0OlxuICAgICAgYnJlYWs7XG4gIH1cblxuICBpZiAoaXNGaWx0ZXJBbGxvd2VkKGJhbmRDb21iaW5hdGlvbikgJiYgZmlsdGVyRW5hYmxlZCkge1xuICAgIG1vZHVsZXMucHVzaChmaWx0ZXIpO1xuICAgIG1vZHVsZVByb3BzLmZpbHRlck1pbjEgPSBmaWx0ZXJSYW5nZVswXTtcbiAgICBtb2R1bGVQcm9wcy5maWx0ZXJNYXgxID0gZmlsdGVyUmFuZ2VbMV07XG4gIH1cblxuICAvLyBBcHBseSBjb2xvcm1hcFxuICBpZiAoaXNDb2xvcm1hcEFsbG93ZWQoYmFuZENvbWJpbmF0aW9uKSAmJiBpbWFnZXMuaW1hZ2VDb2xvcm1hcCkge1xuICAgIG1vZHVsZXMucHVzaChjb2xvcm1hcE1vZHVsZSk7XG4gICAgbW9kdWxlUHJvcHMubWluQ2F0ZWdvcmljYWxCYW5kVmFsdWUgPSBtaW5DYXRlZ29yaWNhbEJhbmRWYWx1ZTtcbiAgICBtb2R1bGVQcm9wcy5tYXhDYXRlZ29yaWNhbEJhbmRWYWx1ZSA9IG1heENhdGVnb3JpY2FsQmFuZFZhbHVlO1xuICAgIG1vZHVsZVByb3BzLmRhdGFUeXBlTWF4VmFsdWUgPSBkdHlwZU1heFZhbHVlW2RhdGFUeXBlXTtcbiAgICBtb2R1bGVQcm9wcy5tYXhQaXhlbFZhbHVlID0gbWF4UGl4ZWxWYWx1ZTtcbiAgfVxuXG4gIHJldHVybiB7bW9kdWxlcywgbW9kdWxlUHJvcHN9O1xufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7OztBQU9BLElBQUFBLEtBQUEsR0FBQUMsT0FBQTtBQUNBLElBQUFDLE9BQUEsR0FBQUQsT0FBQTtBQUNBLElBQUFFLFNBQUEsR0FBQUYsT0FBQTtBQUVBLElBQUFHLFVBQUEsR0FBQUgsT0FBQTtBQVFBLElBQUFJLFlBQUEsR0FBQUosT0FBQTtBQUNBLElBQUFLLFdBQUEsR0FBQUwsT0FBQTtBQUNBLElBQUFNLGFBQUEsR0FBQU4sT0FBQTtBQUNBLElBQUFPLE1BQUEsR0FBQVAsT0FBQTtBQXdCQSxJQUFBUSxnQkFBQSxHQUFBUixPQUFBO0FBZUEsSUFBQVMsZ0JBQUEsR0FBQVQsT0FBQTtBQUFzRCxTQUFBVSxRQUFBQyxDQUFBLEVBQUFDLENBQUEsUUFBQUMsQ0FBQSxHQUFBQyxNQUFBLENBQUFDLElBQUEsQ0FBQUosQ0FBQSxPQUFBRyxNQUFBLE