UNPKG

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
"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