"use strict"; (self["webpackChunkmy3d"] = self["webpackChunkmy3d"] || []).push([["index"],{ /***/ "./src/index.js": /*!**********************!*\ !*** ./src/index.js ***! \**********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ bridgeInvokeCallback: () => (/* binding */ bridgeInvokeCallback), /* harmony export */ playerInitialize: () => (/* binding */ playerInitialize), /* harmony export */ playerLoad: () => (/* binding */ playerLoad), /* harmony export */ playerPause: () => (/* binding */ playerPause), /* harmony export */ playerPlay: () => (/* binding */ playerPlay), /* harmony export */ playerSeek: () => (/* binding */ playerSeek), /* harmony export */ playerSetBaseRate: () => (/* binding */ playerSetBaseRate), /* harmony export */ playerSetIsMuted: () => (/* binding */ playerSetIsMuted), /* harmony export */ playerSetLevel: () => (/* binding */ playerSetLevel) /* harmony export */ }); /* harmony import */ var hls_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! hls.js */ "./node_modules/hls.js/dist/hls.mjs"); // TimeRangesStub class TimeRangesStub { constructor() { this._ranges = []; } get length() { return this._ranges.length; } start(index) { if (index < 0 || index >= this._ranges.length) { throw new DOMException('Invalid index', 'IndexSizeError'); } return this._ranges[index].start; } end(index) { if (index < 0 || index >= this._ranges.length) { throw new DOMException('Invalid index', 'IndexSizeError'); } return this._ranges[index].end; } // Helper method to add a range _addRange(start, end) { this._ranges.push({ start, end }); this._normalizeRanges(); } // Helper method to remove ranges that overlap with a given range _removeRange(start, end) { let updatedRanges = []; for (let range of this._ranges) { if (range.end <= start || range.start >= end) { // No overlap, keep the range as is updatedRanges.push(range); } else if (range.start < start && range.end > end) { // The range fully covers the removal range, split into two ranges updatedRanges.push({ start: range.start, end: start }); updatedRanges.push({ start: end, end: range.end }); } else if (range.start >= start && range.end <= end) { // The range is entirely within the removal range, remove it // Do not add to updatedRanges } else if (range.start < start && range.end > start && range.end <= end) { // The range overlaps with the removal range on the left updatedRanges.push({ start: range.start, end: start }); } else if (range.start >= start && range.start < end && range.end > end) { // The range overlaps with the removal range on the right updatedRanges.push({ start: end, end: range.end }); } } this._ranges = updatedRanges; } // Normalize and merge overlapping ranges _normalizeRanges() { this._ranges.sort((a, b) => a.start - b.start); let normalized = []; for (let range of this._ranges) { if (normalized.length === 0) { normalized.push(range); } else { let last = normalized[normalized.length - 1]; if (range.start <= last.end) { last.end = Math.max(last.end, range.end); } else { normalized.push(range); } } } this._ranges = normalized; } } // TextTrackStub class TextTrackStub extends EventTarget { constructor(kind = '', label = '', language = '') { super(); this.kind = kind; this.label = label; this.language = language; this.mode = 'disabled'; // 'disabled', 'hidden', or 'showing' this.cues = new TextTrackCueListStub(); this.activeCues = new TextTrackCueListStub(); } addCue(cue) { this.cues._add(cue); } removeCue(cue) { this.cues._remove(cue); } } // TextTrackCueListStub class TextTrackCueListStub { constructor() { this._cues = []; } get length() { return this._cues.length; } item(index) { return this._cues[index]; } getCueById(id) { return this._cues.find(cue => cue.id === id) || null; } _add(cue) { this._cues.push(cue); } _remove(cue) { const index = this._cues.indexOf(cue); if (index !== -1) { this._cues.splice(index, 1); } } [Symbol.iterator]() { return this._cues[Symbol.iterator](); } } class TextTrackListStub extends EventTarget { constructor() { super(); this._tracks = []; } get length() { return this._tracks.length; } item(index) { return this._tracks[index]; } _add(track) { this._tracks.push(track); this.dispatchEvent(new Event('addtrack')); } _remove(track) { const index = this._tracks.indexOf(track); if (index !== -1) { this._tracks.splice(index, 1); this.dispatchEvent(new Event('removetrack')); } } [Symbol.iterator]() { return this._tracks[Symbol.iterator](); } } // VideoElementStub class VideoElementStub extends EventTarget { constructor() { super(); this._currentTime = 0.0; this.duration = NaN; this.paused = true; this.playbackRate = 1.0; this.volume = 1.0; this.muted = false; this.readyState = 0; this.networkState = 0; this.buffered = new TimeRangesStub(); this.seeking = false; this.loop = false; this.autoplay = false; this.controls = false; this.error = null; this.src = ''; this.videoWidth = 0; this.videoHeight = 0; this.textTracks = new TextTrackListStub(); this._isPlaying = false; setTimeout(() => { this.readyState = 4; // HAVE_ENOUGH_DATA this.dispatchEvent(new Event('loadedmetadata')); this.dispatchEvent(new Event('loadeddata')); this.dispatchEvent(new Event('canplay')); this.dispatchEvent(new Event('canplaythrough')); }, 0); } get currentTime() { return this._currentTime; } set currentTime(value) { if (this._currentTime != value) { this._currentTime = value; this.dispatchEvent(new Event('seeked')); } } play() { if (this.paused) { this.paused = false; this._isPlaying = true; this.dispatchEvent(new Event('play')); this.dispatchEvent(new Event('playing')); // Simulate timeupdate events this._simulateTimeUpdate(); } return Promise.resolve(); } pause() { if (!this.paused) { this.paused = true; this._isPlaying = false; this.dispatchEvent(new Event('pause')); } } canPlayType(type) { // Assume all types are playable in this stub return 'probably'; } _getMedia() { return mediaSourceMap[this.src]; } // Simulate timeupdate events _simulateTimeUpdate() { if (this._isPlaying) { // Simulate time progression setTimeout(() => { var bufferedEnd = 0.0; const media = this._getMedia(); if (media) { if (media.sourceBuffers.length != 0) { this.buffered = media.sourceBuffers._buffers[0].buffered; bufferedEnd = this.buffered.length == 0 ? 0 : this.buffered.end(this.buffered.length - 1); } } // Consume buffered data if (this.currentTime < bufferedEnd) { // Advance currentTime this._currentTime += 0.1 * this.playbackRate; // Increment currentTime this.dispatchEvent(new Event('timeupdate')); // Continue simulation this._simulateTimeUpdate(); } else { console.log("Buffer underrun"); // Buffer underrun this._isPlaying = false; this.paused = true; this.dispatchEvent(new Event('waiting')); // The player should react by buffering more data } }, 100); } } addTextTrack(kind, label, language) { const textTrack = new TextTrackStub(kind, label, language); this.textTracks._add(textTrack); return textTrack; } } // MediaSourceStub class MediaSourceStub extends EventTarget { constructor() { super(); this.internalId = nextInternalId; nextInternalId += 1; this.sourceBuffers = new SourceBufferListStub(); this.activeSourceBuffers = new SourceBufferListStub(); this.readyState = 'closed'; this._duration = NaN; // Simulate asynchronous opening of MediaSource setTimeout(() => { this.readyState = 'open'; this.dispatchEvent(new Event('sourceopen')); }, 0); } static isTypeSupported(mimeType) { // Assume all MIME types are supported in this stub return true; } addSourceBuffer(mimeType) { if (this.readyState !== 'open') { throw new DOMException('MediaSource is not open', 'InvalidStateError'); } const sourceBuffer = new SourceBufferStub(this, mimeType); this.sourceBuffers._add(sourceBuffer); this.activeSourceBuffers._add(sourceBuffer); return sourceBuffer; } removeSourceBuffer(sourceBuffer) { if (!this.sourceBuffers._remove(sourceBuffer)) { throw new DOMException('SourceBuffer not found', 'NotFoundError'); } this.activeSourceBuffers._remove(sourceBuffer); } endOfStream(error) { if (this.readyState !== 'open') { throw new DOMException('MediaSource is not open', 'InvalidStateError'); } this.readyState = 'ended'; this.dispatchEvent(new Event('sourceended')); } set duration(value) { if (this.readyState === 'closed') { throw new DOMException('MediaSource is closed', 'InvalidStateError'); } this._duration = value; } get duration() { return this._duration; } } // SourceBufferList Stub class SourceBufferListStub extends EventTarget { constructor() { super(); this._buffers = []; } _add(buffer) { this._buffers.push(buffer); this.dispatchEvent(new Event('addsourcebuffer')); } _remove(buffer) { const index = this._buffers.indexOf(buffer); if (index === -1) { return false; } this._buffers.splice(index, 1); this.dispatchEvent(new Event('removesourcebuffer')); return true; } get length() { return this._buffers.length; } item(index) { return this._buffers[index]; } [Symbol.iterator]() { return this._buffers[Symbol.iterator](); } } window.bridgeObjectMap = {}; window.bridgeCallbackMap = {}; function bridgeInvokeAsync(bridgeId, className, methodName, params) { var promiseResolve; var promiseReject; var result = new Promise(function(resolve, reject) { promiseResolve = resolve; promiseReject = reject; }); const callbackId = nextInternalId; nextInternalId += 1; window.bridgeCallbackMap[callbackId] = promiseResolve; if (window.webkit.messageHandlers) { window.webkit.messageHandlers.performAction.postMessage({ 'event': 'bridgeInvoke', 'data': { 'bridgeId': bridgeId, 'className': className, 'methodName': methodName, 'params': params, 'callbackId': callbackId } }); } return result; } function bridgeInvokeCallback(callbackId, result) { const callback = window.bridgeCallbackMap[callbackId]; if (callback) { callback(result); } } function bytesToBase64(bytes) { const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte), ).join(""); return btoa(binString); } // SourceBufferStub class SourceBufferStub extends EventTarget { constructor(mediaSource, mimeType) { super(); this.mediaSource = mediaSource; this.mimeType = mimeType; this.updating = false; this.buffered = new TimeRangesStub(); this.timestampOffset = 0; this.appendWindowStart = 0; this.appendWindowEnd = Infinity; // Internal state to simulate buffering this._bufferedEnd = 0; this.bridgeId = nextInternalId; nextInternalId += 1; window.bridgeObjectMap[this.bridgeId] = this; bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "constructor", { "mimeType": mimeType }); } appendBuffer(data) { if (this.updating) { throw new DOMException('SourceBuffer is updating', 'InvalidStateError'); } this.updating = true; this.dispatchEvent(new Event('updatestart')); bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "appendBuffer", { "data": bytesToBase64(data) }).then((result) => { const rangeStart = result["rangeStart"]; const rangeEnd = result["rangeEnd"]; if (rangeStart && rangeEnd) { this.buffered._addRange(rangeStart, rangeEnd); this._bufferedEnd = rangeEnd; } this.updating = false; this.dispatchEvent(new Event('update')); this.dispatchEvent(new Event('updateend')); }); } abort() { if (this.updating) { this.updating = false; this.dispatchEvent(new Event('abort')); } } remove(start, end) { if (this.updating) { throw new DOMException('SourceBuffer is updating', 'InvalidStateError'); } this.updating = true; this.dispatchEvent(new Event('updatestart')); bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "remove", { "start": start, "end": end }).then((result) => { this.buffered._removeRange(start, end); this.updating = false; this.dispatchEvent(new Event('update')); this.dispatchEvent(new Event('updateend')); }); } } var useStubs = true; var nextInternalId = 0; var mediaSourceMap = {}; // Replace the global MediaSource with our stub if (useStubs && typeof window !== 'undefined') { window.MediaSource = MediaSourceStub; window.ManagedMediaSource = MediaSourceStub; window.SourceBuffer = SourceBufferStub; URL.createObjectURL = function(ms) { const url = "blob:mock-media-source:" + ms.internalId; mediaSourceMap[url] = ms; return url; }; } function postPlayerEvent(eventName, eventData) { if (window.webkit.messageHandlers) { window.webkit.messageHandlers.performAction.postMessage({'event': eventName, 'data': eventData}); } }; var video; var hls; var isManifestParsed = false; var isFirstFrameReady = false; var currentTimeUpdateTimeout = null; function playerInitialize(params) { video.muted = false; video.addEventListener('loadeddata', (event) => { if (!isFirstFrameReady) { isFirstFrameReady = true; refreshPlayerStatus(); } }); video.addEventListener("playing", function() { refreshPlayerStatus(); }); video.addEventListener("pause", function() { refreshPlayerStatus(); }); video.addEventListener("seeking", function() { refreshPlayerStatus(); }); video.addEventListener("waiting", function() { refreshPlayerStatus(); }); hls = new hls_js__WEBPACK_IMPORTED_MODULE_0__["default"]({ startLevel: 0, testBandwidth: false, debug: params['debug'] || true, autoStartLoad: false, backBufferLength: 30, maxBufferLength: 60, maxMaxBufferLength: 60 }); hls.on(hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events.MANIFEST_PARSED, function() { isManifestParsed = true; refreshPlayerStatus(); }); hls.on(hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events.LEVEL_SWITCHED, function() { refreshPlayerStatus(); }); hls.on(hls_js__WEBPACK_IMPORTED_MODULE_0__["default"].Events.LEVELS_UPDATED, function() { refreshPlayerStatus(); }); hls.loadSource('master.m3u8'); hls.attachMedia(video); } function playerLoad(initialLevelIndex) { hls.startLevel = initialLevelIndex; hls.startLoad(-1, false); } function playerPlay() { video.play(); } function playerPause() { video.pause(); } function playerSetBaseRate(value) { video.playbackRate = value; } function playerSetLevel(level) { if (level >= 0) { hls.currentLevel = level; } else { hls.currentLevel = -1; } } function playerSeek(value) { video.currentTime = value; } function playerSetIsMuted(value) { video.muted = value; } function getLevels() { var levels = []; for (var i = 0; i < hls.levels.length; i++) { var level = hls.levels[i]; levels.push({ 'index': i, 'bitrate': level.bitrate || 0, 'width': level.width || 0, 'height': level.height || 0 }); } return levels; } function refreshPlayerStatus() { var isPlaying = false; if (!video.paused && !video.ended && video.readyState > 2) { isPlaying = true; } postPlayerEvent('playerStatus', { 'isReady': isManifestParsed, 'isFirstFrameReady': isFirstFrameReady, 'isPlaying': !video.paused, 'rate': isPlaying ? video.playbackRate : 0.0, 'defaultRate': video.playbackRate, 'levels': getLevels(), 'currentLevel': hls.currentLevel }); refreshPlayerCurrentTime(); if (isPlaying) { if (currentTimeUpdateTimeout == null) { currentTimeUpdateTimeout = setTimeout(() => { refreshPlayerCurrentTime(); }, 200); } } else { if(currentTimeUpdateTimeout != null){ clearTimeout(currentTimeUpdateTimeout); currentTimeUpdateTimeout = null; } } } function refreshPlayerCurrentTime() { postPlayerEvent('playerCurrentTime', { 'value': video.currentTime }); currentTimeUpdateTimeout = setTimeout(() => { refreshPlayerCurrentTime() }, 200); } window.onload = () => { if (useStubs) { video = new VideoElementStub(); } else { video = document.createElement('video'); video.playsInline = true; video.controls = true; document.body.appendChild(video); } postPlayerEvent('windowOnLoad', { }); }; window.playerInitialize = playerInitialize; window.playerLoad = playerLoad; window.playerPlay = playerPlay; window.playerPause = playerPause; window.playerSetBaseRate = playerSetBaseRate; window.playerSetLevel = playerSetLevel; window.playerSeek = playerSeek; window.playerSetIsMuted = playerSetIsMuted; window.bridgeInvokeCallback = bridgeInvokeCallback; /***/ }), /***/ "./node_modules/hls.js/dist/hls.mjs": /*!******************************************!*\ !*** ./node_modules/hls.js/dist/hls.mjs ***! \******************************************/ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ AbrController: () => (/* binding */ AbrController), /* harmony export */ AttrList: () => (/* binding */ AttrList), /* harmony export */ AudioStreamController: () => (/* binding */ AudioStreamController), /* harmony export */ AudioTrackController: () => (/* binding */ AudioTrackController), /* harmony export */ BasePlaylistController: () => (/* binding */ BasePlaylistController), /* harmony export */ BaseSegment: () => (/* binding */ BaseSegment), /* harmony export */ BaseStreamController: () => (/* binding */ BaseStreamController), /* harmony export */ BufferController: () => (/* binding */ BufferController), /* harmony export */ CMCDController: () => (/* binding */ CMCDController), /* harmony export */ CapLevelController: () => (/* binding */ CapLevelController), /* harmony export */ ChunkMetadata: () => (/* binding */ ChunkMetadata), /* harmony export */ ContentSteeringController: () => (/* binding */ ContentSteeringController), /* harmony export */ DateRange: () => (/* binding */ DateRange), /* harmony export */ EMEController: () => (/* binding */ EMEController), /* harmony export */ ErrorActionFlags: () => (/* binding */ ErrorActionFlags), /* harmony export */ ErrorController: () => (/* binding */ ErrorController), /* harmony export */ ErrorDetails: () => (/* binding */ ErrorDetails), /* harmony export */ ErrorTypes: () => (/* binding */ ErrorTypes), /* harmony export */ Events: () => (/* binding */ Events), /* harmony export */ FPSController: () => (/* binding */ FPSController), /* harmony export */ Fragment: () => (/* binding */ Fragment), /* harmony export */ Hls: () => (/* binding */ Hls), /* harmony export */ HlsSkip: () => (/* binding */ HlsSkip), /* harmony export */ HlsUrlParameters: () => (/* binding */ HlsUrlParameters), /* harmony export */ KeySystemFormats: () => (/* binding */ KeySystemFormats), /* harmony export */ KeySystems: () => (/* binding */ KeySystems), /* harmony export */ Level: () => (/* binding */ Level), /* harmony export */ LevelDetails: () => (/* binding */ LevelDetails), /* harmony export */ LevelKey: () => (/* binding */ LevelKey), /* harmony export */ LoadStats: () => (/* binding */ LoadStats), /* harmony export */ MetadataSchema: () => (/* binding */ MetadataSchema), /* harmony export */ NetworkErrorAction: () => (/* binding */ NetworkErrorAction), /* harmony export */ Part: () => (/* binding */ Part), /* harmony export */ PlaylistLevelType: () => (/* binding */ PlaylistLevelType), /* harmony export */ SubtitleStreamController: () => (/* binding */ SubtitleStreamController), /* harmony export */ SubtitleTrackController: () => (/* binding */ SubtitleTrackController), /* harmony export */ TimelineController: () => (/* binding */ TimelineController), /* harmony export */ "default": () => (/* binding */ Hls), /* harmony export */ getMediaSource: () => (/* binding */ getMediaSource), /* harmony export */ isMSESupported: () => (/* binding */ isMSESupported), /* harmony export */ isSupported: () => (/* binding */ isSupported) /* harmony export */ }); function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var urlToolkit = {exports: {}}; (function (module, exports) { // see https://tools.ietf.org/html/rfc1808 (function (root) { var URL_REGEX = /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/; var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/; var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g; var URLToolkit = { // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // // E.g // With opts.alwaysNormalize = false (default, spec compliant) // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g // With opts.alwaysNormalize = true (not spec compliant) // http://a.com/b/cd + /e/f/../g => http://a.com/e/g buildAbsoluteURL: function (baseURL, relativeURL, opts) { opts = opts || {}; // remove any remaining space and CRLF baseURL = baseURL.trim(); relativeURL = relativeURL.trim(); if (!relativeURL) { // 2a) If the embedded URL is entirely empty, it inherits the // entire base URL (i.e., is set equal to the base URL) // and we are done. if (!opts.alwaysNormalize) { return baseURL; } var basePartsForNormalise = URLToolkit.parseURL(baseURL); if (!basePartsForNormalise) { throw new Error('Error trying to parse base URL.'); } basePartsForNormalise.path = URLToolkit.normalizePath( basePartsForNormalise.path ); return URLToolkit.buildURLFromParts(basePartsForNormalise); } var relativeParts = URLToolkit.parseURL(relativeURL); if (!relativeParts) { throw new Error('Error trying to parse relative URL.'); } if (relativeParts.scheme) { // 2b) If the embedded URL starts with a scheme name, it is // interpreted as an absolute URL and we are done. if (!opts.alwaysNormalize) { return relativeURL; } relativeParts.path = URLToolkit.normalizePath(relativeParts.path); return URLToolkit.buildURLFromParts(relativeParts); } var baseParts = URLToolkit.parseURL(baseURL); if (!baseParts) { throw new Error('Error trying to parse base URL.'); } if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); baseParts.netLoc = pathParts[1]; baseParts.path = pathParts[2]; } if (baseParts.netLoc && !baseParts.path) { baseParts.path = '/'; } var builtParts = { // 2c) Otherwise, the embedded URL inherits the scheme of // the base URL. scheme: baseParts.scheme, netLoc: relativeParts.netLoc, path: null, params: relativeParts.params, query: relativeParts.query, fragment: relativeParts.fragment, }; if (!relativeParts.netLoc) { // 3) If the embedded URL's is non-empty, we skip to // Step 7. Otherwise, the embedded URL inherits the // (if any) of the base URL. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the // path is not relative and we skip to Step 7. if (relativeParts.path[0] !== '/') { if (!relativeParts.path) { // 5) If the embedded URL path is empty (and not preceded by a // slash), then the embedded URL inherits the base URL path builtParts.path = baseParts.path; // 5a) if the embedded URL's is non-empty, we skip to // step 7; otherwise, it inherits the of the base // URL (if any) and if (!relativeParts.params) { builtParts.params = baseParts.params; // 5b) if the embedded URL's is non-empty, we skip to // step 7; otherwise, it inherits the of the base // URL (if any) and we skip to step 7. if (!relativeParts.query) { builtParts.query = baseParts.query; } } } else { // 6) The last segment of the base URL's path (anything // following the rightmost slash "/", or the entire path if no // slash is present) is removed and the embedded URL's path is // appended in its place. var baseURLPath = baseParts.path; var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; builtParts.path = URLToolkit.normalizePath(newPath); } } } if (builtParts.path === null) { builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; } return URLToolkit.buildURLFromParts(builtParts); }, parseURL: function (url) { var parts = URL_REGEX.exec(url); if (!parts) { return null; } return { scheme: parts[1] || '', netLoc: parts[2] || '', path: parts[3] || '', params: parts[4] || '', query: parts[5] || '', fragment: parts[6] || '', }; }, normalizePath: function (path) { // The following operations are // then applied, in order, to the new path: // 6a) All occurrences of "./", where "." is a complete path // segment, are removed. // 6b) If the path ends with "." as a complete path segment, // that "." is removed. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "/../", where is a // complete path segment not equal to "..", are removed. // Removal of these path segments is performed iteratively, // removing the leftmost matching pattern on each iteration, // until no matching pattern remains. // 6d) If the path ends with "/..", where is a // complete path segment not equal to "..", that // "/.." is removed. while ( path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length ) {} return path.split('').reverse().join(''); }, buildURLFromParts: function (parts) { return ( parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment ); }, }; module.exports = URLToolkit; })(); } (urlToolkit)); var urlToolkitExports = urlToolkit.exports; 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 _objectSpread2(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) { _defineProperty(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; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } // https://caniuse.com/mdn-javascript_builtins_number_isfinite const isFiniteNumber = Number.isFinite || function (value) { return typeof value === 'number' && isFinite(value); }; // https://caniuse.com/mdn-javascript_builtins_number_issafeinteger const isSafeInteger = Number.isSafeInteger || function (value) { return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER; }; const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; let Events = /*#__PURE__*/function (Events) { Events["MEDIA_ATTACHING"] = "hlsMediaAttaching"; Events["MEDIA_ATTACHED"] = "hlsMediaAttached"; Events["MEDIA_DETACHING"] = "hlsMediaDetaching"; Events["MEDIA_DETACHED"] = "hlsMediaDetached"; Events["BUFFER_RESET"] = "hlsBufferReset"; Events["BUFFER_CODECS"] = "hlsBufferCodecs"; Events["BUFFER_CREATED"] = "hlsBufferCreated"; Events["BUFFER_APPENDING"] = "hlsBufferAppending"; Events["BUFFER_APPENDED"] = "hlsBufferAppended"; Events["BUFFER_EOS"] = "hlsBufferEos"; Events["BUFFER_FLUSHING"] = "hlsBufferFlushing"; Events["BUFFER_FLUSHED"] = "hlsBufferFlushed"; Events["MANIFEST_LOADING"] = "hlsManifestLoading"; Events["MANIFEST_LOADED"] = "hlsManifestLoaded"; Events["MANIFEST_PARSED"] = "hlsManifestParsed"; Events["LEVEL_SWITCHING"] = "hlsLevelSwitching"; Events["LEVEL_SWITCHED"] = "hlsLevelSwitched"; Events["LEVEL_LOADING"] = "hlsLevelLoading"; Events["LEVEL_LOADED"] = "hlsLevelLoaded"; Events["LEVEL_UPDATED"] = "hlsLevelUpdated"; Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated"; Events["LEVELS_UPDATED"] = "hlsLevelsUpdated"; Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated"; Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching"; Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched"; Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading"; Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded"; Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated"; Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared"; Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch"; Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading"; Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded"; Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed"; Events["CUES_PARSED"] = "hlsCuesParsed"; Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound"; Events["INIT_PTS_FOUND"] = "hlsInitPtsFound"; Events["FRAG_LOADING"] = "hlsFragLoading"; Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted"; Events["FRAG_LOADED"] = "hlsFragLoaded"; Events["FRAG_DECRYPTED"] = "hlsFragDecrypted"; Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment"; Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata"; Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata"; Events["FRAG_PARSED"] = "hlsFragParsed"; Events["FRAG_BUFFERED"] = "hlsFragBuffered"; Events["FRAG_CHANGED"] = "hlsFragChanged"; Events["FPS_DROP"] = "hlsFpsDrop"; Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping"; Events["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated"; Events["ERROR"] = "hlsError"; Events["DESTROYING"] = "hlsDestroying"; Events["KEY_LOADING"] = "hlsKeyLoading"; Events["KEY_LOADED"] = "hlsKeyLoaded"; Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached"; Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached"; Events["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded"; return Events; }({}); /** * Defines each Event type and payload by Event name. Used in {@link hls.js#HlsEventEmitter} to strongly type the event listener API. */ let ErrorTypes = /*#__PURE__*/function (ErrorTypes) { ErrorTypes["NETWORK_ERROR"] = "networkError"; ErrorTypes["MEDIA_ERROR"] = "mediaError"; ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError"; ErrorTypes["MUX_ERROR"] = "muxError"; ErrorTypes["OTHER_ERROR"] = "otherError"; return ErrorTypes; }({}); let ErrorDetails = /*#__PURE__*/function (ErrorDetails) { ErrorDetails["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys"; ErrorDetails["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess"; ErrorDetails["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession"; ErrorDetails["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense"; ErrorDetails["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed"; ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed"; ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed"; ErrorDetails["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed"; ErrorDetails["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted"; ErrorDetails["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError"; ErrorDetails["MANIFEST_LOAD_ERROR"] = "manifestLoadError"; ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut"; ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError"; ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError"; ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError"; ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError"; ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut"; ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError"; ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError"; ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError"; ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut"; ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError"; ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut"; ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError"; ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut"; ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError"; ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError"; ErrorDetails["FRAG_GAP"] = "fragGap"; ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError"; ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError"; ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut"; ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError"; ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError"; ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError"; ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError"; ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError"; ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError"; ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole"; ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall"; ErrorDetails["INTERNAL_EXCEPTION"] = "internalException"; ErrorDetails["INTERNAL_ABORTED"] = "aborted"; ErrorDetails["UNKNOWN"] = "unknown"; return ErrorDetails; }({}); const noop = function noop() {}; const fakeLogger = { trace: noop, debug: noop, log: noop, warn: noop, info: noop, error: noop }; let exportedLogger = fakeLogger; // let lastCallTime; // function formatMsgWithTimeInfo(type, msg) { // const now = Date.now(); // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0'; // lastCallTime = now; // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )'; // return msg; // } function consolePrintFn(type) { const func = self.console[type]; if (func) { return func.bind(self.console, `[${type}] >`); } return noop; } function exportLoggerFunctions(debugConfig, ...functions) { functions.forEach(function (type) { exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type); }); } function enableLogs(debugConfig, id) { // check that console is available if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') { exportLoggerFunctions(debugConfig, // Remove out from list here to hard-disable a log-level // 'trace', 'debug', 'log', 'info', 'warn', 'error'); // Some browsers don't allow to use bind on console object anyway // fallback to default if needed try { exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.15"}`); } catch (e) { exportedLogger = fakeLogger; } } else { exportedLogger = fakeLogger; } } const logger = exportedLogger; const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g; // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js class AttrList { constructor(attrs) { if (typeof attrs === 'string') { attrs = AttrList.parseAttrList(attrs); } _extends(this, attrs); } get clientAttrs() { return Object.keys(this).filter(attr => attr.substring(0, 2) === 'X-'); } decimalInteger(attrName) { const intValue = parseInt(this[attrName], 10); if (intValue > Number.MAX_SAFE_INTEGER) { return Infinity; } return intValue; } hexadecimalInteger(attrName) { if (this[attrName]) { let stringValue = (this[attrName] || '0x').slice(2); stringValue = (stringValue.length & 1 ? '0' : '') + stringValue; const value = new Uint8Array(stringValue.length / 2); for (let i = 0; i < stringValue.length / 2; i++) { value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16); } return value; } else { return null; } } hexadecimalIntegerAsNumber(attrName) { const intValue = parseInt(this[attrName], 16); if (intValue > Number.MAX_SAFE_INTEGER) { return Infinity; } return intValue; } decimalFloatingPoint(attrName) { return parseFloat(this[attrName]); } optionalFloat(attrName, defaultValue) { const value = this[attrName]; return value ? parseFloat(value) : defaultValue; } enumeratedString(attrName) { return this[attrName]; } bool(attrName) { return this[attrName] === 'YES'; } decimalResolution(attrName) { const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]); if (res === null) { return undefined; } return { width: parseInt(res[1], 10), height: parseInt(res[2], 10) }; } static parseAttrList(input) { let match; const attrs = {}; const quote = '"'; ATTR_LIST_REGEX.lastIndex = 0; while ((match = ATTR_LIST_REGEX.exec(input)) !== null) { let value = match[2]; if (value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1) { value = value.slice(1, -1); } const name = match[1].trim(); attrs[name] = value; } return attrs; } } // Avoid exporting const enum so that these values can be inlined function isDateRangeCueAttribute(attrName) { return attrName !== "ID" && attrName !== "CLASS" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT"; } function isSCTE35Attribute(attrName) { return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN"; } class DateRange { constructor(dateRangeAttr, dateRangeWithSameId) { this.attr = void 0; this._startDate = void 0; this._endDate = void 0; this._badValueForSameId = void 0; if (dateRangeWithSameId) { const previousAttr = dateRangeWithSameId.attr; for (const key in previousAttr) { if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) { logger.warn(`DATERANGE tag attribute: "${key}" does not match for tags with ID: "${dateRangeAttr.ID}"`); this._badValueForSameId = key; break; } } // Merge DateRange tags with the same ID dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr); } this.attr = dateRangeAttr; this._startDate = new Date(dateRangeAttr["START-DATE"]); if ("END-DATE" in this.attr) { const endDate = new Date(this.attr["END-DATE"]); if (isFiniteNumber(endDate.getTime())) { this._endDate = endDate; } } } get id() { return this.attr.ID; } get class() { return this.attr.CLASS; } get startDate() { return this._startDate; } get endDate() { if (this._endDate) { return this._endDate; } const duration = this.duration; if (duration !== null) { return new Date(this._startDate.getTime() + duration * 1000); } return null; } get duration() { if ("DURATION" in this.attr) { const duration = this.attr.decimalFloatingPoint("DURATION"); if (isFiniteNumber(duration)) { return duration; } } else if (this._endDate) { return (this._endDate.getTime() - this._startDate.getTime()) / 1000; } return null; } get plannedDuration() { if ("PLANNED-DURATION" in this.attr) { return this.attr.decimalFloatingPoint("PLANNED-DURATION"); } return null; } get endOnNext() { return this.attr.bool("END-ON-NEXT"); } get isValid() { return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class); } } class LoadStats { constructor() { this.aborted = false; this.loaded = 0; this.retry = 0; this.total = 0; this.chunkCount = 0; this.bwEstimate = 0; this.loading = { start: 0, first: 0, end: 0 }; this.parsing = { start: 0, end: 0 }; this.buffering = { start: 0, first: 0, end: 0 }; } } var ElementaryStreamTypes = { AUDIO: "audio", VIDEO: "video", AUDIOVIDEO: "audiovideo" }; class BaseSegment { constructor(baseurl) { this._byteRange = null; this._url = null; // baseurl is the URL to the playlist this.baseurl = void 0; // relurl is the portion of the URL that comes from inside the playlist. this.relurl = void 0; // Holds the types of data this fragment supports this.elementaryStreams = { [ElementaryStreamTypes.AUDIO]: null, [ElementaryStreamTypes.VIDEO]: null, [ElementaryStreamTypes.AUDIOVIDEO]: null }; this.baseurl = baseurl; } // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array setByteRange(value, previous) { const params = value.split('@', 2); let start; if (params.length === 1) { start = (previous == null ? void 0 : previous.byteRangeEndOffset) || 0; } else { start = parseInt(params[1]); } this._byteRange = [start, parseInt(params[0]) + start]; } get byteRange() { if (!this._byteRange) { return []; } return this._byteRange; } get byteRangeStartOffset() { return this.byteRange[0]; } get byteRangeEndOffset() { return this.byteRange[1]; } get url() { if (!this._url && this.baseurl && this.relurl) { this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true }); } return this._url || ''; } set url(value) { this._url = value; } } /** * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}. */ class Fragment extends BaseSegment { constructor(type, baseurl) { super(baseurl); this._decryptdata = null; this.rawProgramDateTime = null; this.programDateTime = null; this.tagList = []; // EXTINF has to be present for a m3u8 to be considered valid this.duration = 0; // sn notates the sequence number for a segment, and if set to a string can be 'initSegment' this.sn = 0; // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption // core difference from the private field _decryptdata is the lack of the initialized IV // _decryptdata will set the IV for this segment based on the segment number in the fragment this.levelkeys = void 0; // A string representing the fragment type this.type = void 0; // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading this.loader = null; // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading this.keyLoader = null; // The level/track index to which the fragment belongs this.level = -1; // The continuity counter of the fragment this.cc = 0; // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. this.startPTS = void 0; // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. this.endPTS = void 0; // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete. this.startDTS = void 0; // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete. this.endDTS = void 0; // The start time of the fragment, as listed in the manifest. Updated after transmux complete. this.start = 0; // Set by `updateFragPTSDTS` in level-helper this.deltaPTS = void 0; // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. this.maxStartPTS = void 0; // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. this.minEndPTS = void 0; // Load/parse timing information this.stats = new LoadStats(); // Init Segment bytes (unset for media segments) this.data = void 0; // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered this.bitrateTest = false; // #EXTINF segment title this.title = null; // The Media Initialization Section for this segment this.initSegment = null; // Fragment is the last fragment in the media playlist this.endList = void 0; // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded this.gap = void 0; // Deprecated this.urlId = 0; this.type = type; } get decryptdata() { const { levelkeys } = this; if (!levelkeys && !this._decryptdata) { return null; } if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) { const key = this.levelkeys.identity; if (key) { this._decryptdata = key.getDecryptData(this.sn); } else { const keyFormats = Object.keys(this.levelkeys); if (keyFormats.length === 1) { return this._decryptdata = this.levelkeys[keyFormats[0]].getDecryptData(this.sn); } } } return this._decryptdata; } get end() { return this.start + this.duration; } get endProgramDateTime() { if (this.programDateTime === null) { return null; } if (!isFiniteNumber(this.programDateTime)) { return null; } const duration = !isFiniteNumber(this.duration) ? 0 : this.duration; return this.programDateTime + duration * 1000; } get encrypted() { var _this$_decryptdata; // At the m3u8-parser level we need to add support for manifest signalled keyformats // when we want the fragment to start reporting that it is encrypted. // Currently, keyFormat will only be set for identity keys if ((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.encrypted) { return true; } else if (this.levelkeys) { const keyFormats = Object.keys(this.levelkeys); const len = keyFormats.length; if (len > 1 || len === 1 && this.levelkeys[keyFormats[0]].encrypted) { return true; } } return false; } setKeyFormat(keyFormat) { if (this.levelkeys) { const key = this.levelkeys[keyFormat]; if (key && !this._decryptdata) { this._decryptdata = key.getDecryptData(this.sn); } } } abortRequests() { var _this$loader, _this$keyLoader; (_this$loader = this.loader) == null ? void 0 : _this$loader.abort(); (_this$keyLoader = this.keyLoader) == null ? void 0 : _this$keyLoader.abort(); } setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial = false) { const { elementaryStreams } = this; const info = elementaryStreams[type]; if (!info) { elementaryStreams[type] = { startPTS, endPTS, startDTS, endDTS, partial }; return; } info.startPTS = Math.min(info.startPTS, startPTS); info.endPTS = Math.max(info.endPTS, endPTS); info.startDTS = Math.min(info.startDTS, startDTS); info.endDTS = Math.max(info.endDTS, endDTS); } clearElementaryStreamInfo() { const { elementaryStreams } = this; elementaryStreams[ElementaryStreamTypes.AUDIO] = null; elementaryStreams[ElementaryStreamTypes.VIDEO] = null; elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null; } } /** * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}. */ class Part extends BaseSegment { constructor(partAttrs, frag, baseurl, index, previous) { super(baseurl); this.fragOffset = 0; this.duration = 0; this.gap = false; this.independent = false; this.relurl = void 0; this.fragment = void 0; this.index = void 0; this.stats = new LoadStats(); this.duration = partAttrs.decimalFloatingPoint('DURATION'); this.gap = partAttrs.bool('GAP'); this.independent = partAttrs.bool('INDEPENDENT'); this.relurl = partAttrs.enumeratedString('URI'); this.fragment = frag; this.index = index; const byteRange = partAttrs.enumeratedString('BYTERANGE'); if (byteRange) { this.setByteRange(byteRange, previous); } if (previous) { this.fragOffset = previous.fragOffset + previous.duration; } } get start() { return this.fragment.start + this.fragOffset; } get end() { return this.start + this.duration; } get loaded() { const { elementaryStreams } = this; return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo); } } const DEFAULT_TARGET_DURATION = 10; /** * Object representing parsed data from an HLS Media Playlist. Found in {@link hls.js#Level.details}. */ class LevelDetails { constructor(baseUrl) { this.PTSKnown = false; this.alignedSliding = false; this.averagetargetduration = void 0; this.endCC = 0; this.endSN = 0; this.fragments = void 0; this.fragmentHint = void 0; this.partList = null; this.dateRanges = void 0; this.live = true; this.ageHeader = 0; this.advancedDateTime = void 0; this.updated = true; this.advanced = true; this.availabilityDelay = void 0; // Manifest reload synchronization this.misses = 0; this.startCC = 0; this.startSN = 0; this.startTimeOffset = null; this.targetduration = 0; this.totalduration = 0; this.type = null; this.url = void 0; this.m3u8 = ''; this.version = null; this.canBlockReload = false; this.canSkipUntil = 0; this.canSkipDateRanges = false; this.skippedSegments = 0; this.recentlyRemovedDateranges = void 0; this.partHoldBack = 0; this.holdBack = 0; this.partTarget = 0; this.preloadHint = void 0; this.renditionReports = void 0; this.tuneInGoal = 0; this.deltaUpdateFailed = void 0; this.driftStartTime = 0; this.driftEndTime = 0; this.driftStart = 0; this.driftEnd = 0; this.encryptedFragments = void 0; this.playlistParsingError = null; this.variableList = null; this.hasVariableRefs = false; this.fragments = []; this.encryptedFragments = []; this.dateRanges = {}; this.url = baseUrl; } reloaded(previous) { if (!previous) { this.advanced = true; this.updated = true; return; } const partSnDiff = this.lastPartSn - previous.lastPartSn; const partIndexDiff = this.lastPartIndex - previous.lastPartIndex; this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live; this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0; if (this.updated || this.advanced) { this.misses = Math.floor(previous.misses * 0.6); } else { this.misses = previous.misses + 1; } this.availabilityDelay = previous.availabilityDelay; } get hasProgramDateTime() { if (this.fragments.length) { return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime); } return false; } get levelTargetDuration() { return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION; } get drift() { const runTime = this.driftEndTime - this.driftStartTime; if (runTime > 0) { const runDuration = this.driftEnd - this.driftStart; return runDuration * 1000 / runTime; } return 1; } get edge() { return this.partEnd || this.fragmentEnd; } get partEnd() { var _this$partList; if ((_this$partList = this.partList) != null && _this$partList.length) { return this.partList[this.partList.length - 1].end; } return this.fragmentEnd; } get fragmentEnd() { var _this$fragments; if ((_this$fragments = this.fragments) != null && _this$fragments.length) { return this.fragments[this.fragments.length - 1].end; } return 0; } get age() { if (this.advancedDateTime) { return Math.max(Date.now() - this.advancedDateTime, 0) / 1000; } return 0; } get lastPartIndex() { var _this$partList2; if ((_this$partList2 = this.partList) != null && _this$partList2.length) { return this.partList[this.partList.length - 1].index; } return -1; } get lastPartSn() { var _this$partList3; if ((_this$partList3 = this.partList) != null && _this$partList3.length) { return this.partList[this.partList.length - 1].fragment.sn; } return this.endSN; } } function base64Decode(base64encodedStr) { return Uint8Array.from(atob(base64encodedStr), c => c.charCodeAt(0)); } function getKeyIdBytes(str) { const keyIdbytes = strToUtf8array(str).subarray(0, 16); const paddedkeyIdbytes = new Uint8Array(16); paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length); return paddedkeyIdbytes; } function changeEndianness(keyId) { const swap = function swap(array, from, to) { const cur = array[from]; array[from] = array[to]; array[to] = cur; }; swap(keyId, 0, 3); swap(keyId, 1, 2); swap(keyId, 4, 5); swap(keyId, 6, 7); } function convertDataUriToArrayBytes(uri) { // data:[ const colonsplit = uri.split(':'); let keydata = null; if (colonsplit[0] === 'data' && colonsplit.length === 2) { const semicolonsplit = colonsplit[1].split(';'); const commasplit = semicolonsplit[semicolonsplit.length - 1].split(','); if (commasplit.length === 2) { const isbase64 = commasplit[0] === 'base64'; const data = commasplit[1]; if (isbase64) { semicolonsplit.splice(-1, 1); // remove from processing keydata = base64Decode(data); } else { keydata = getKeyIdBytes(data); } } } return keydata; } function strToUtf8array(str) { return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0)); } /** returns `undefined` is `self` is missing, e.g. in node */ const optionalSelf = typeof self !== 'undefined' ? self : undefined; /** * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess */ var KeySystems = { CLEARKEY: "org.w3.clearkey", FAIRPLAY: "com.apple.fps", PLAYREADY: "com.microsoft.playready", WIDEVINE: "com.widevine.alpha" }; // Playlist #EXT-X-KEY KEYFORMAT values var KeySystemFormats = { CLEARKEY: "org.w3.clearkey", FAIRPLAY: "com.apple.streamingkeydelivery", PLAYREADY: "com.microsoft.playready", WIDEVINE: "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" }; function keySystemFormatToKeySystemDomain(format) { switch (format) { case KeySystemFormats.FAIRPLAY: return KeySystems.FAIRPLAY; case KeySystemFormats.PLAYREADY: return KeySystems.PLAYREADY; case KeySystemFormats.WIDEVINE: return KeySystems.WIDEVINE; case KeySystemFormats.CLEARKEY: return KeySystems.CLEARKEY; } } // System IDs for which we can extract a key ID from "encrypted" event PSSH var KeySystemIds = { CENC: "1077efecc0b24d02ace33c1e52e2fb4b", CLEARKEY: "e2719d58a985b3c9781ab030af78d30e", FAIRPLAY: "94ce86fb07ff4f43adb893d2fa968ca2", PLAYREADY: "9a04f07998404286ab92e65be0885f95", WIDEVINE: "edef8ba979d64acea3c827dcd51d21ed" }; function keySystemIdToKeySystemDomain(systemId) { if (systemId === KeySystemIds.WIDEVINE) { return KeySystems.WIDEVINE; } else if (systemId === KeySystemIds.PLAYREADY) { return KeySystems.PLAYREADY; } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) { return KeySystems.CLEARKEY; } } function keySystemDomainToKeySystemFormat(keySystem) { switch (keySystem) { case KeySystems.FAIRPLAY: return KeySystemFormats.FAIRPLAY; case KeySystems.PLAYREADY: return KeySystemFormats.PLAYREADY; case KeySystems.WIDEVINE: return KeySystemFormats.WIDEVINE; case KeySystems.CLEARKEY: return KeySystemFormats.CLEARKEY; } } function getKeySystemsForConfig(config) { const { drmSystems, widevineLicenseUrl } = config; const keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(keySystem => !!drmSystems[keySystem]) : []; if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) { keySystemsToAttempt.push(KeySystems.WIDEVINE); } return keySystemsToAttempt; } const requestMediaKeySystemAccess = function (_optionalSelf$navigat) { if (optionalSelf != null && (_optionalSelf$navigat = optionalSelf.navigator) != null && _optionalSelf$navigat.requestMediaKeySystemAccess) { return self.navigator.requestMediaKeySystemAccess.bind(self.navigator); } else { return null; } }(); /** * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration */ function getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions) { let initDataTypes; switch (keySystem) { case KeySystems.FAIRPLAY: initDataTypes = ['cenc', 'sinf']; break; case KeySystems.WIDEVINE: case KeySystems.PLAYREADY: initDataTypes = ['cenc']; break; case KeySystems.CLEARKEY: initDataTypes = ['cenc', 'keyids']; break; default: throw new Error(`Unknown key-system: ${keySystem}`); } return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions); } function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions) { const baseConfig = { initDataTypes: initDataTypes, persistentState: drmSystemOptions.persistentState || 'optional', distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'optional', sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || 'temporary'], audioCapabilities: audioCodecs.map(codec => ({ contentType: `audio/mp4; codecs="${codec}"`, robustness: drmSystemOptions.audioRobustness || '', encryptionScheme: drmSystemOptions.audioEncryptionScheme || null })), videoCapabilities: videoCodecs.map(codec => ({ contentType: `video/mp4; codecs="${codec}"`, robustness: drmSystemOptions.videoRobustness || '', encryptionScheme: drmSystemOptions.videoEncryptionScheme || null })) }; return [baseConfig]; } function sliceUint8(array, start, end) { // @ts-expect-error This polyfills IE11 usage of Uint8Array slice. // It always exists in the TypeScript definition so fails, but it fails at runtime on IE11. return Uint8Array.prototype.slice ? array.slice(start, end) : new Uint8Array(Array.prototype.slice.call(array, start, end)); } // breaking up those two types in order to clarify what is happening in the decoding path. /** * Returns true if an ID3 header can be found at offset in data * @param data - The data to search * @param offset - The offset at which to start searching */ const isHeader$2 = (data, offset) => { /* * http://id3.org/id3v2.3.0 * [0] = 'I' * [1] = 'D' * [2] = '3' * [3,4] = {Version} * [5] = {Flags} * [6-9] = {ID3 Size} * * An ID3v2 tag can be detected with the following pattern: * $49 44 33 yy yy xx zz zz zz zz * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80 */ if (offset + 10 <= data.length) { // look for 'ID3' identifier if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) { // check version is within range if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { // check size is within range if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) { return true; } } } } return false; }; /** * Returns true if an ID3 footer can be found at offset in data * @param data - The data to search * @param offset - The offset at which to start searching */ const isFooter = (data, offset) => { /* * The footer is a copy of the header, but with a different identifier */ if (offset + 10 <= data.length) { // look for '3DI' identifier if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) { // check version is within range if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { // check size is within range if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) { return true; } } } } return false; }; /** * Returns any adjacent ID3 tags found in data starting at offset, as one block of data * @param data - The data to search in * @param offset - The offset at which to start searching * @returns the block of data containing any ID3 tags found * or *undefined* if no header is found at the starting offset */ const getID3Data = (data, offset) => { const front = offset; let length = 0; while (isHeader$2(data, offset)) { // ID3 header is 10 bytes length += 10; const size = readSize(data, offset + 6); length += size; if (isFooter(data, offset + 10)) { // ID3 footer is 10 bytes length += 10; } offset += length; } if (length > 0) { return data.subarray(front, front + length); } return undefined; }; const readSize = (data, offset) => { let size = 0; size = (data[offset] & 0x7f) << 21; size |= (data[offset + 1] & 0x7f) << 14; size |= (data[offset + 2] & 0x7f) << 7; size |= data[offset + 3] & 0x7f; return size; }; const canParse$2 = (data, offset) => { return isHeader$2(data, offset) && readSize(data, offset + 6) + 10 <= data.length - offset; }; /** * Searches for the Elementary Stream timestamp found in the ID3 data chunk * @param data - Block of data containing one or more ID3 tags */ const getTimeStamp = data => { const frames = getID3Frames(data); for (let i = 0; i < frames.length; i++) { const frame = frames[i]; if (isTimeStampFrame(frame)) { return readTimeStamp(frame); } } return undefined; }; /** * Returns true if the ID3 frame is an Elementary Stream timestamp frame */ const isTimeStampFrame = frame => { return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp'; }; const getFrameData = data => { /* Frame ID $xx xx xx xx (four characters) Size $xx xx xx xx Flags $xx xx */ const type = String.fromCharCode(data[0], data[1], data[2], data[3]); const size = readSize(data, 4); // skip frame id, size, and flags const offset = 10; return { type, size, data: data.subarray(offset, offset + size) }; }; /** * Returns an array of ID3 frames found in all the ID3 tags in the id3Data * @param id3Data - The ID3 data containing one or more ID3 tags */ const getID3Frames = id3Data => { let offset = 0; const frames = []; while (isHeader$2(id3Data, offset)) { const size = readSize(id3Data, offset + 6); // skip past ID3 header offset += 10; const end = offset + size; // loop through frames in the ID3 tag while (offset + 8 < end) { const frameData = getFrameData(id3Data.subarray(offset)); const frame = decodeFrame(frameData); if (frame) { frames.push(frame); } // skip frame header and frame data offset += frameData.size + 10; } if (isFooter(id3Data, offset)) { offset += 10; } } return frames; }; const decodeFrame = frame => { if (frame.type === 'PRIV') { return decodePrivFrame(frame); } else if (frame.type[0] === 'W') { return decodeURLFrame(frame); } return decodeTextFrame(frame); }; const decodePrivFrame = frame => { /* Format: \0 */ if (frame.size < 2) { return undefined; } const owner = utf8ArrayToStr(frame.data, true); const privateData = new Uint8Array(frame.data.subarray(owner.length + 1)); return { key: frame.type, info: owner, data: privateData.buffer }; }; const decodeTextFrame = frame => { if (frame.size < 2) { return undefined; } if (frame.type === 'TXXX') { /* Format: [0] = {Text Encoding} [1-?] = {Description}\0{Value} */ let index = 1; const description = utf8ArrayToStr(frame.data.subarray(index), true); index += description.length + 1; const value = utf8ArrayToStr(frame.data.subarray(index)); return { key: frame.type, info: description, data: value }; } /* Format: [0] = {Text Encoding} [1-?] = {Value} */ const text = utf8ArrayToStr(frame.data.subarray(1)); return { key: frame.type, data: text }; }; const decodeURLFrame = frame => { if (frame.type === 'WXXX') { /* Format: [0] = {Text Encoding} [1-?] = {Description}\0{URL} */ if (frame.size < 2) { return undefined; } let index = 1; const description = utf8ArrayToStr(frame.data.subarray(index), true); index += description.length + 1; const value = utf8ArrayToStr(frame.data.subarray(index)); return { key: frame.type, info: description, data: value }; } /* Format: [0-?] = {URL} */ const url = utf8ArrayToStr(frame.data); return { key: frame.type, data: url }; }; const readTimeStamp = timeStampFrame => { if (timeStampFrame.data.byteLength === 8) { const data = new Uint8Array(timeStampFrame.data); // timestamp is 33 bit expressed as a big-endian eight-octet number, // with the upper 31 bits set to zero. const pts33Bit = data[3] & 0x1; let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7]; timestamp /= 45; if (pts33Bit) { timestamp += 47721858.84; } // 2^32 / 90 return Math.round(timestamp); } return undefined; }; // http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197 // http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt /* utf.js - UTF-8 <=> UTF-16 convertion * * Copyright (C) 1999 Masanao Izumo * Version: 1.0 * LastModified: Dec 25 1999 * This library is free. You can redistribute it and/or modify it. */ const utf8ArrayToStr = (array, exitOnNull = false) => { const decoder = getTextDecoder(); if (decoder) { const decoded = decoder.decode(array); if (exitOnNull) { // grab up to the first null const idx = decoded.indexOf('\0'); return idx !== -1 ? decoded.substring(0, idx) : decoded; } // remove any null characters return decoded.replace(/\0/g, ''); } const len = array.length; let c; let char2; let char3; let out = ''; let i = 0; while (i < len) { c = array[i++]; if (c === 0x00 && exitOnNull) { return out; } else if (c === 0x00 || c === 0x03) { // If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it continue; } switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx out += String.fromCharCode(c); break; case 12: case 13: // 110x xxxx 10xx xxxx char2 = array[i++]; out += String.fromCharCode((c & 0x1f) << 6 | char2 & 0x3f); break; case 14: // 1110 xxxx 10xx xxxx 10xx xxxx char2 = array[i++]; char3 = array[i++]; out += String.fromCharCode((c & 0x0f) << 12 | (char2 & 0x3f) << 6 | (char3 & 0x3f) << 0); break; } } return out; }; let decoder; function getTextDecoder() { // On Play Station 4, TextDecoder is defined but partially implemented. // Manual decoding option is preferable if (navigator.userAgent.includes('PlayStation 4')) { return; } if (!decoder && typeof self.TextDecoder !== 'undefined') { decoder = new self.TextDecoder('utf-8'); } return decoder; } /** * hex dump helper class */ const Hex = { hexDump: function (array) { let str = ''; for (let i = 0; i < array.length; i++) { let h = array[i].toString(16); if (h.length < 2) { h = '0' + h; } str += h; } return str; } }; const UINT32_MAX$1 = Math.pow(2, 32) - 1; const push = [].push; // We are using fixed track IDs for driving the MP4 remuxer // instead of following the TS PIDs. // There is no reason not to do this and some browsers/SourceBuffer-demuxers // may not like if there are TrackID "switches" // See https://github.com/video-dev/hls.js/issues/1331 // Here we are mapping our internal track types to constant MP4 track IDs // With MSE currently one can only have one track of each, and we are muxing // whatever video/audio rendition in them. const RemuxerTrackIdConfig = { video: 1, audio: 2, id3: 3, text: 4 }; function bin2str(data) { return String.fromCharCode.apply(null, data); } function readUint16(buffer, offset) { const val = buffer[offset] << 8 | buffer[offset + 1]; return val < 0 ? 65536 + val : val; } function readUint32(buffer, offset) { const val = readSint32(buffer, offset); return val < 0 ? 4294967296 + val : val; } function readUint64(buffer, offset) { let result = readUint32(buffer, offset); result *= Math.pow(2, 32); result += readUint32(buffer, offset + 4); return result; } function readSint32(buffer, offset) { return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3]; } function writeUint32(buffer, offset, value) { buffer[offset] = value >> 24; buffer[offset + 1] = value >> 16 & 0xff; buffer[offset + 2] = value >> 8 & 0xff; buffer[offset + 3] = value & 0xff; } // Find "moof" box function hasMoofData(data) { const end = data.byteLength; for (let i = 0; i < end;) { const size = readUint32(data, i); if (size > 8 && data[i + 4] === 0x6d && data[i + 5] === 0x6f && data[i + 6] === 0x6f && data[i + 7] === 0x66) { return true; } i = size > 1 ? i + size : end; } return false; } // Find the data for a box specified by its path function findBox(data, path) { const results = []; if (!path.length) { // short-circuit the search for empty paths return results; } const end = data.byteLength; for (let i = 0; i < end;) { const size = readUint32(data, i); const type = bin2str(data.subarray(i + 4, i + 8)); const endbox = size > 1 ? i + size : end; if (type === path[0]) { if (path.length === 1) { // this is the end of the path and we've found the box we were // looking for results.push(data.subarray(i + 8, endbox)); } else { // recursively search for the next box along the path const subresults = findBox(data.subarray(i + 8, endbox), path.slice(1)); if (subresults.length) { push.apply(results, subresults); } } } i = endbox; } // we've finished searching all of data return results; } function parseSegmentIndex(sidx) { const references = []; const version = sidx[0]; // set initial offset, we skip the reference ID (not needed) let index = 8; const timescale = readUint32(sidx, index); index += 4; let earliestPresentationTime = 0; let firstOffset = 0; if (version === 0) { earliestPresentationTime = readUint32(sidx, index); firstOffset = readUint32(sidx, index + 4); index += 8; } else { earliestPresentationTime = readUint64(sidx, index); firstOffset = readUint64(sidx, index + 8); index += 16; } // skip reserved index += 2; let startByte = sidx.length + firstOffset; const referencesCount = readUint16(sidx, index); index += 2; for (let i = 0; i < referencesCount; i++) { let referenceIndex = index; const referenceInfo = readUint32(sidx, referenceIndex); referenceIndex += 4; const referenceSize = referenceInfo & 0x7fffffff; const referenceType = (referenceInfo & 0x80000000) >>> 31; if (referenceType === 1) { logger.warn('SIDX has hierarchical references (not supported)'); return null; } const subsegmentDuration = readUint32(sidx, referenceIndex); referenceIndex += 4; references.push({ referenceSize, subsegmentDuration, // unscaled info: { duration: subsegmentDuration / timescale, start: startByte, end: startByte + referenceSize - 1 } }); startByte += referenceSize; // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits // for |sapDelta|. referenceIndex += 4; // skip to next ref index = referenceIndex; } return { earliestPresentationTime, timescale, version, referencesCount, references }; } /** * Parses an MP4 initialization segment and extracts stream type and * timescale values for any declared tracks. Timescale values indicate the * number of clock ticks per second to assume for time-based values * elsewhere in the MP4. * * To determine the start time of an MP4, you need two pieces of * information: the timescale unit and the earliest base media decode * time. Multiple timescales can be specified within an MP4 but the * base media decode time is always expressed in the timescale from * the media header box for the track: * ``` * moov > trak > mdia > mdhd.timescale * moov > trak > mdia > hdlr * ``` * @param initSegment the bytes of the init segment * @returns a hash of track type to timescale values or null if * the init segment is malformed. */ function parseInitSegment(initSegment) { const result = []; const traks = findBox(initSegment, ['moov', 'trak']); for (let i = 0; i < traks.length; i++) { const trak = traks[i]; const tkhd = findBox(trak, ['tkhd'])[0]; if (tkhd) { let version = tkhd[0]; const trackId = readUint32(tkhd, version === 0 ? 12 : 20); const mdhd = findBox(trak, ['mdia', 'mdhd'])[0]; if (mdhd) { version = mdhd[0]; const timescale = readUint32(mdhd, version === 0 ? 12 : 20); const hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; if (hdlr) { const hdlrType = bin2str(hdlr.subarray(8, 12)); const type = { soun: ElementaryStreamTypes.AUDIO, vide: ElementaryStreamTypes.VIDEO }[hdlrType]; if (type) { // Parse codec details const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; const stsdData = parseStsd(stsd); result[trackId] = { timescale, type }; result[type] = _objectSpread2({ timescale, id: trackId }, stsdData); } } } } } const trex = findBox(initSegment, ['moov', 'mvex', 'trex']); trex.forEach(trex => { const trackId = readUint32(trex, 4); const track = result[trackId]; if (track) { track.default = { duration: readUint32(trex, 12), flags: readUint32(trex, 20) }; } }); return result; } function parseStsd(stsd) { const sampleEntries = stsd.subarray(8); const sampleEntriesEnd = sampleEntries.subarray(8 + 78); const fourCC = bin2str(sampleEntries.subarray(4, 8)); let codec = fourCC; const encrypted = fourCC === 'enca' || fourCC === 'encv'; if (encrypted) { const encBox = findBox(sampleEntries, [fourCC])[0]; const encBoxChildren = encBox.subarray(fourCC === 'enca' ? 28 : 78); const sinfs = findBox(encBoxChildren, ['sinf']); sinfs.forEach(sinf => { const schm = findBox(sinf, ['schm'])[0]; if (schm) { const scheme = bin2str(schm.subarray(4, 8)); if (scheme === 'cbcs' || scheme === 'cenc') { const frma = findBox(sinf, ['frma'])[0]; if (frma) { // for encrypted content codec fourCC will be in frma codec = bin2str(frma); } } } }); } switch (codec) { case 'avc1': case 'avc2': case 'avc3': case 'avc4': { // extract profile + compatibility + level out of avcC box const avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0]; codec += '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]); break; } case 'mp4a': { const codecBox = findBox(sampleEntries, [fourCC])[0]; const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0]; if (esdsBox && esdsBox.length > 12) { let i = 4; // ES Descriptor tag if (esdsBox[i++] !== 0x03) { break; } i = skipBERInteger(esdsBox, i); i += 2; // skip es_id; const flags = esdsBox[i++]; if (flags & 0x80) { i += 2; // skip dependency es_id } if (flags & 0x40) { i += esdsBox[i++]; // skip URL } // Decoder config descriptor if (esdsBox[i++] !== 0x04) { break; } i = skipBERInteger(esdsBox, i); const objectType = esdsBox[i++]; if (objectType === 0x40) { codec += '.' + toHex(objectType); } else { break; } i += 12; // Decoder specific info if (esdsBox[i++] !== 0x05) { break; } i = skipBERInteger(esdsBox, i); const firstByte = esdsBox[i++]; let audioObjectType = (firstByte & 0xf8) >> 3; if (audioObjectType === 31) { audioObjectType += 1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5); } codec += '.' + audioObjectType; } break; } case 'hvc1': case 'hev1': { const hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0]; const profileByte = hvcCBox[1]; const profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6]; const generalProfileIdc = profileByte & 0x1f; const profileCompat = readUint32(hvcCBox, 2); const tierFlag = (profileByte & 0x20) >> 5 ? 'H' : 'L'; const levelIDC = hvcCBox[12]; const constraintIndicator = hvcCBox.subarray(6, 12); codec += '.' + profileSpace + generalProfileIdc; codec += '.' + profileCompat.toString(16).toUpperCase(); codec += '.' + tierFlag + levelIDC; let constraintString = ''; for (let i = constraintIndicator.length; i--;) { const byte = constraintIndicator[i]; if (byte || constraintString) { const encodedByte = byte.toString(16).toUpperCase(); constraintString = '.' + encodedByte + constraintString; } } codec += constraintString; break; } case 'dvh1': case 'dvhe': { const dvcCBox = findBox(sampleEntriesEnd, ['dvcC'])[0]; const profile = dvcCBox[2] >> 1 & 0x7f; const level = dvcCBox[2] << 5 & 0x20 | dvcCBox[3] >> 3 & 0x1f; codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level); break; } case 'vp09': { const vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0]; const profile = vpcCBox[4]; const level = vpcCBox[5]; const bitDepth = vpcCBox[6] >> 4 & 0x0f; codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level) + '.' + addLeadingZero(bitDepth); break; } case 'av01': { const av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0]; const profile = av1CBox[1] >>> 5; const level = av1CBox[1] & 0x1f; const tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M'; const highBitDepth = (av1CBox[2] & 0x40) >> 6; const twelveBit = (av1CBox[2] & 0x20) >> 5; const bitDepth = profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8; const monochrome = (av1CBox[2] & 0x10) >> 4; const chromaSubsamplingX = (av1CBox[2] & 0x08) >> 3; const chromaSubsamplingY = (av1CBox[2] & 0x04) >> 2; const chromaSamplePosition = av1CBox[2] & 0x03; // TODO: parse color_description_present_flag // default it to BT.709/limited range for now // more info https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax const colorPrimaries = 1; const transferCharacteristics = 1; const matrixCoefficients = 1; const videoFullRangeFlag = 0; codec += '.' + profile + '.' + addLeadingZero(level) + tierFlag + '.' + addLeadingZero(bitDepth) + '.' + monochrome + '.' + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + '.' + addLeadingZero(colorPrimaries) + '.' + addLeadingZero(transferCharacteristics) + '.' + addLeadingZero(matrixCoefficients) + '.' + videoFullRangeFlag; break; } } return { codec, encrypted }; } function skipBERInteger(bytes, i) { const limit = i + 5; while (bytes[i++] & 0x80 && i < limit) {} return i; } function toHex(x) { return ('0' + x.toString(16).toUpperCase()).slice(-2); } function addLeadingZero(num) { return (num < 10 ? '0' : '') + num; } function patchEncyptionData(initSegment, decryptdata) { if (!initSegment || !decryptdata) { return initSegment; } const keyId = decryptdata.keyId; if (keyId && decryptdata.isCommonEncryption) { const traks = findBox(initSegment, ['moov', 'trak']); traks.forEach(trak => { const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; // skip the sample entry count const sampleEntries = stsd.subarray(8); let encBoxes = findBox(sampleEntries, ['enca']); const isAudio = encBoxes.length > 0; if (!isAudio) { encBoxes = findBox(sampleEntries, ['encv']); } encBoxes.forEach(enc => { const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78); const sinfBoxes = findBox(encBoxChildren, ['sinf']); sinfBoxes.forEach(sinf => { const tenc = parseSinf(sinf); if (tenc) { // Look for default key id (keyID offset is always 8 within the tenc box): const tencKeyId = tenc.subarray(8, 24); if (!tencKeyId.some(b => b !== 0)) { logger.log(`[eme] Patching keyId in 'enc${isAudio ? 'a' : 'v'}>sinf>>tenc' box: ${Hex.hexDump(tencKeyId)} -> ${Hex.hexDump(keyId)}`); tenc.set(keyId, 8); } } }); }); }); } return initSegment; } function parseSinf(sinf) { const schm = findBox(sinf, ['schm'])[0]; if (schm) { const scheme = bin2str(schm.subarray(4, 8)); if (scheme === 'cbcs' || scheme === 'cenc') { return findBox(sinf, ['schi', 'tenc'])[0]; } } return null; } /** * Determine the base media decode start time, in seconds, for an MP4 * fragment. If multiple fragments are specified, the earliest time is * returned. * * The base media decode time can be parsed from track fragment * metadata: * ``` * moof > traf > tfdt.baseMediaDecodeTime * ``` * It requires the timescale value from the mdhd to interpret. * * @param initData - a hash of track type to timescale values * @param fmp4 - the bytes of the mp4 fragment * @returns the earliest base media decode start time for the * fragment, in seconds */ function getStartDTS(initData, fmp4) { // we need info from two children of each track fragment box return findBox(fmp4, ['moof', 'traf']).reduce((result, traf) => { const tfdt = findBox(traf, ['tfdt'])[0]; const version = tfdt[0]; const start = findBox(traf, ['tfhd']).reduce((result, tfhd) => { // get the track id from the tfhd const id = readUint32(tfhd, 4); const track = initData[id]; if (track) { let baseTime = readUint32(tfdt, 4); if (version === 1) { // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild. // This prevents large values from being used for initPTS, which can cause playlist sync issues. // https://github.com/video-dev/hls.js/issues/5303 if (baseTime === UINT32_MAX$1) { logger.warn(`[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`); return result; } baseTime *= UINT32_MAX$1 + 1; baseTime += readUint32(tfdt, 8); } // assume a 90kHz clock if no timescale was specified const scale = track.timescale || 90e3; // convert base time to seconds const startTime = baseTime / scale; if (isFiniteNumber(startTime) && (result === null || startTime < result)) { return startTime; } } return result; }, null); if (start !== null && isFiniteNumber(start) && (result === null || start < result)) { return start; } return result; }, null); } /* For Reference: aligned(8) class TrackFragmentHeaderBox extends FullBox(‘tfhd’, 0, tf_flags){ unsigned int(32) track_ID; // all the following are optional fields unsigned int(64) base_data_offset; unsigned int(32) sample_description_index; unsigned int(32) default_sample_duration; unsigned int(32) default_sample_size; unsigned int(32) default_sample_flags } */ function getDuration(data, initData) { let rawDuration = 0; let videoDuration = 0; let audioDuration = 0; const trafs = findBox(data, ['moof', 'traf']); for (let i = 0; i < trafs.length; i++) { const traf = trafs[i]; // There is only one tfhd & trun per traf // This is true for CMAF style content, and we should perhaps check the ftyp // and only look for a single trun then, but for ISOBMFF we should check // for multiple track runs. const tfhd = findBox(traf, ['tfhd'])[0]; // get the track id from the tfhd const id = readUint32(tfhd, 4); const track = initData[id]; if (!track) { continue; } const trackDefault = track.default; const tfhdFlags = readUint32(tfhd, 0) | (trackDefault == null ? void 0 : trackDefault.flags); let sampleDuration = trackDefault == null ? void 0 : trackDefault.duration; if (tfhdFlags & 0x000008) { // 0x000008 indicates the presence of the default_sample_duration field if (tfhdFlags & 0x000002) { // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration // If present, the default_sample_duration exists at byte offset 12 sampleDuration = readUint32(tfhd, 12); } else { // Otherwise, the duration is at byte offset 8 sampleDuration = readUint32(tfhd, 8); } } // assume a 90kHz clock if no timescale was specified const timescale = track.timescale || 90e3; const truns = findBox(traf, ['trun']); for (let j = 0; j < truns.length; j++) { rawDuration = computeRawDurationFromSamples(truns[j]); if (!rawDuration && sampleDuration) { const sampleCount = readUint32(truns[j], 4); rawDuration = sampleDuration * sampleCount; } if (track.type === ElementaryStreamTypes.VIDEO) { videoDuration += rawDuration / timescale; } else if (track.type === ElementaryStreamTypes.AUDIO) { audioDuration += rawDuration / timescale; } } } if (videoDuration === 0 && audioDuration === 0) { // If duration samples are not available in the traf use sidx subsegment_duration let sidxMinStart = Infinity; let sidxMaxEnd = 0; let sidxDuration = 0; const sidxs = findBox(data, ['sidx']); for (let i = 0; i < sidxs.length; i++) { const sidx = parseSegmentIndex(sidxs[i]); if (sidx != null && sidx.references) { sidxMinStart = Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale); const subSegmentDuration = sidx.references.reduce((dur, ref) => dur + ref.info.duration || 0, 0); sidxMaxEnd = Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale); sidxDuration = sidxMaxEnd - sidxMinStart; } } if (sidxDuration && isFiniteNumber(sidxDuration)) { return sidxDuration; } } if (videoDuration) { return videoDuration; } return audioDuration; } /* For Reference: aligned(8) class TrackRunBox extends FullBox(‘trun’, version, tr_flags) { unsigned int(32) sample_count; // the following are optional fields signed int(32) data_offset; unsigned int(32) first_sample_flags; // all fields in the following array are optional { unsigned int(32) sample_duration; unsigned int(32) sample_size; unsigned int(32) sample_flags if (version == 0) { unsigned int(32) else { signed int(32) }[ sample_count ] } */ function computeRawDurationFromSamples(trun) { const flags = readUint32(trun, 0); // Flags are at offset 0, non-optional sample_count is at offset 4. Therefore we start 8 bytes in. // Each field is an int32, which is 4 bytes let offset = 8; // data-offset-present flag if (flags & 0x000001) { offset += 4; } // first-sample-flags-present flag if (flags & 0x000004) { offset += 4; } let duration = 0; const sampleCount = readUint32(trun, 4); for (let i = 0; i < sampleCount; i++) { // sample-duration-present flag if (flags & 0x000100) { const sampleDuration = readUint32(trun, offset); duration += sampleDuration; offset += 4; } // sample-size-present flag if (flags & 0x000200) { offset += 4; } // sample-flags-present flag if (flags & 0x000400) { offset += 4; } // sample-composition-time-offsets-present flag if (flags & 0x000800) { offset += 4; } } return duration; } function offsetStartDTS(initData, fmp4, timeOffset) { findBox(fmp4, ['moof', 'traf']).forEach(traf => { findBox(traf, ['tfhd']).forEach(tfhd => { // get the track id from the tfhd const id = readUint32(tfhd, 4); const track = initData[id]; if (!track) { return; } // assume a 90kHz clock if no timescale was specified const timescale = track.timescale || 90e3; // get the base media decode time from the tfdt findBox(traf, ['tfdt']).forEach(tfdt => { const version = tfdt[0]; const offset = timeOffset * timescale; if (offset) { let baseMediaDecodeTime = readUint32(tfdt, 4); if (version === 0) { baseMediaDecodeTime -= offset; baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0); writeUint32(tfdt, 4, baseMediaDecodeTime); } else { baseMediaDecodeTime *= Math.pow(2, 32); baseMediaDecodeTime += readUint32(tfdt, 8); baseMediaDecodeTime -= offset; baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0); const upper = Math.floor(baseMediaDecodeTime / (UINT32_MAX$1 + 1)); const lower = Math.floor(baseMediaDecodeTime % (UINT32_MAX$1 + 1)); writeUint32(tfdt, 4, upper); writeUint32(tfdt, 8, lower); } } }); }); }); } // TODO: Check if the last moof+mdat pair is part of the valid range function segmentValidRange(data) { const segmentedRange = { valid: null, remainder: null }; const moofs = findBox(data, ['moof']); if (moofs.length < 2) { segmentedRange.remainder = data; return segmentedRange; } const last = moofs[moofs.length - 1]; // Offset by 8 bytes; findBox offsets the start by as much segmentedRange.valid = sliceUint8(data, 0, last.byteOffset - 8); segmentedRange.remainder = sliceUint8(data, last.byteOffset - 8); return segmentedRange; } function appendUint8Array(data1, data2) { const temp = new Uint8Array(data1.length + data2.length); temp.set(data1); temp.set(data2, data1.length); return temp; } function parseSamples(timeOffset, track) { const seiSamples = []; const videoData = track.samples; const timescale = track.timescale; const trackId = track.id; let isHEVCFlavor = false; const moofs = findBox(videoData, ['moof']); moofs.map(moof => { const moofOffset = moof.byteOffset - 8; const trafs = findBox(moof, ['traf']); trafs.map(traf => { // get the base media decode time from the tfdt const baseTime = findBox(traf, ['tfdt']).map(tfdt => { const version = tfdt[0]; let result = readUint32(tfdt, 4); if (version === 1) { result *= Math.pow(2, 32); result += readUint32(tfdt, 8); } return result / timescale; })[0]; if (baseTime !== undefined) { timeOffset = baseTime; } return findBox(traf, ['tfhd']).map(tfhd => { const id = readUint32(tfhd, 4); const tfhdFlags = readUint32(tfhd, 0) & 0xffffff; const baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0; const sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0; const defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0; let defaultSampleDuration = 0; const defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0; let defaultSampleSize = 0; const defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0; let tfhdOffset = 8; if (id === trackId) { if (baseDataOffsetPresent) { tfhdOffset += 8; } if (sampleDescriptionIndexPresent) { tfhdOffset += 4; } if (defaultSampleDurationPresent) { defaultSampleDuration = readUint32(tfhd, tfhdOffset); tfhdOffset += 4; } if (defaultSampleSizePresent) { defaultSampleSize = readUint32(tfhd, tfhdOffset); tfhdOffset += 4; } if (defaultSampleFlagsPresent) { tfhdOffset += 4; } if (track.type === 'video') { isHEVCFlavor = isHEVC(track.codec); } findBox(traf, ['trun']).map(trun => { const version = trun[0]; const flags = readUint32(trun, 0) & 0xffffff; const dataOffsetPresent = (flags & 0x000001) !== 0; let dataOffset = 0; const firstSampleFlagsPresent = (flags & 0x000004) !== 0; const sampleDurationPresent = (flags & 0x000100) !== 0; let sampleDuration = 0; const sampleSizePresent = (flags & 0x000200) !== 0; let sampleSize = 0; const sampleFlagsPresent = (flags & 0x000400) !== 0; const sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0; let compositionOffset = 0; const sampleCount = readUint32(trun, 4); let trunOffset = 8; // past version, flags, and sample count if (dataOffsetPresent) { dataOffset = readUint32(trun, trunOffset); trunOffset += 4; } if (firstSampleFlagsPresent) { trunOffset += 4; } let sampleOffset = dataOffset + moofOffset; for (let ix = 0; ix < sampleCount; ix++) { if (sampleDurationPresent) { sampleDuration = readUint32(trun, trunOffset); trunOffset += 4; } else { sampleDuration = defaultSampleDuration; } if (sampleSizePresent) { sampleSize = readUint32(trun, trunOffset); trunOffset += 4; } else { sampleSize = defaultSampleSize; } if (sampleFlagsPresent) { trunOffset += 4; } if (sampleCompositionOffsetsPresent) { if (version === 0) { compositionOffset = readUint32(trun, trunOffset); } else { compositionOffset = readSint32(trun, trunOffset); } trunOffset += 4; } if (track.type === ElementaryStreamTypes.VIDEO) { let naluTotalSize = 0; while (naluTotalSize < sampleSize) { const naluSize = readUint32(videoData, sampleOffset); sampleOffset += 4; if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) { const data = videoData.subarray(sampleOffset, sampleOffset + naluSize); parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples); } sampleOffset += naluSize; naluTotalSize += naluSize + 4; } } timeOffset += sampleDuration / timescale; } }); } }); }); }); return seiSamples; } function isHEVC(codec) { if (!codec) { return false; } const delimit = codec.indexOf('.'); const baseCodec = delimit < 0 ? codec : codec.substring(0, delimit); return baseCodec === 'hvc1' || baseCodec === 'hev1' || // Dolby Vision baseCodec === 'dvh1' || baseCodec === 'dvhe'; } function isSEIMessage(isHEVCFlavor, naluHeader) { if (isHEVCFlavor) { const naluType = naluHeader >> 1 & 0x3f; return naluType === 39 || naluType === 40; } else { const naluType = naluHeader & 0x1f; return naluType === 6; } } function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) { const data = discardEPB(unescapedData); let seiPtr = 0; // skip nal header seiPtr += headerSize; let payloadType = 0; let payloadSize = 0; let b = 0; while (seiPtr < data.length) { payloadType = 0; do { if (seiPtr >= data.length) { break; } b = data[seiPtr++]; payloadType += b; } while (b === 0xff); // Parse payload size. payloadSize = 0; do { if (seiPtr >= data.length) { break; } b = data[seiPtr++]; payloadSize += b; } while (b === 0xff); const leftOver = data.length - seiPtr; // Create a variable to process the payload let payPtr = seiPtr; // Increment the seiPtr to the end of the payload if (payloadSize < leftOver) { seiPtr += payloadSize; } else if (payloadSize > leftOver) { // Some type of corruption has happened? logger.error(`Malformed SEI payload. ${payloadSize} is too small, only ${leftOver} bytes left to parse.`); // We might be able to parse some data, but let's be safe and ignore it. break; } if (payloadType === 4) { const countryCode = data[payPtr++]; if (countryCode === 181) { const providerCode = readUint16(data, payPtr); payPtr += 2; if (providerCode === 49) { const userStructure = readUint32(data, payPtr); payPtr += 4; if (userStructure === 0x47413934) { const userDataType = data[payPtr++]; // Raw CEA-608 bytes wrapped in CEA-708 packet if (userDataType === 3) { const firstByte = data[payPtr++]; const totalCCs = 0x1f & firstByte; const enabled = 0x40 & firstByte; const totalBytes = enabled ? 2 + totalCCs * 3 : 0; const byteArray = new Uint8Array(totalBytes); if (enabled) { byteArray[0] = firstByte; for (let i = 1; i < totalBytes; i++) { byteArray[i] = data[payPtr++]; } } samples.push({ type: userDataType, payloadType, pts, bytes: byteArray }); } } } } } else if (payloadType === 5) { if (payloadSize > 16) { const uuidStrArray = []; for (let i = 0; i < 16; i++) { const _b = data[payPtr++].toString(16); uuidStrArray.push(_b.length == 1 ? '0' + _b : _b); if (i === 3 || i === 5 || i === 7 || i === 9) { uuidStrArray.push('-'); } } const length = payloadSize - 16; const userDataBytes = new Uint8Array(length); for (let i = 0; i < length; i++) { userDataBytes[i] = data[payPtr++]; } samples.push({ payloadType, pts, uuid: uuidStrArray.join(''), userData: utf8ArrayToStr(userDataBytes), userDataBytes }); } } } } /** * remove Emulation Prevention bytes from a RBSP */ function discardEPB(data) { const length = data.byteLength; const EPBPositions = []; let i = 1; // Find all `Emulation Prevention Bytes` while (i < length - 2) { if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { EPBPositions.push(i + 2); i += 2; } else { i++; } } // If no Emulation Prevention Bytes were found just return the original // array if (EPBPositions.length === 0) { return data; } // Create a new array to hold the NAL unit data const newLength = length - EPBPositions.length; const newData = new Uint8Array(newLength); let sourceIndex = 0; for (i = 0; i < newLength; sourceIndex++, i++) { if (sourceIndex === EPBPositions[0]) { // Skip this byte sourceIndex++; // Remove this position index EPBPositions.shift(); } newData[i] = data[sourceIndex]; } return newData; } function parseEmsg(data) { const version = data[0]; let schemeIdUri = ''; let value = ''; let timeScale = 0; let presentationTimeDelta = 0; let presentationTime = 0; let eventDuration = 0; let id = 0; let offset = 0; if (version === 0) { while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; } schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { value += bin2str(data.subarray(offset, offset + 1)); offset += 1; } value += bin2str(data.subarray(offset, offset + 1)); offset += 1; timeScale = readUint32(data, 12); presentationTimeDelta = readUint32(data, 16); eventDuration = readUint32(data, 20); id = readUint32(data, 24); offset = 28; } else if (version === 1) { offset += 4; timeScale = readUint32(data, offset); offset += 4; const leftPresentationTime = readUint32(data, offset); offset += 4; const rightPresentationTime = readUint32(data, offset); offset += 4; presentationTime = 2 ** 32 * leftPresentationTime + rightPresentationTime; if (!isSafeInteger(presentationTime)) { presentationTime = Number.MAX_SAFE_INTEGER; logger.warn('Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box'); } eventDuration = readUint32(data, offset); offset += 4; id = readUint32(data, offset); offset += 4; while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; } schemeIdUri += bin2str(data.subarray(offset, offset + 1)); offset += 1; while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { value += bin2str(data.subarray(offset, offset + 1)); offset += 1; } value += bin2str(data.subarray(offset, offset + 1)); offset += 1; } const payload = data.subarray(offset, data.byteLength); return { schemeIdUri, value, timeScale, presentationTime, presentationTimeDelta, eventDuration, id, payload }; } function mp4Box(type, ...payload) { const len = payload.length; let size = 8; let i = len; while (i--) { size += payload[i].byteLength; } const result = new Uint8Array(size); result[0] = size >> 24 & 0xff; result[1] = size >> 16 & 0xff; result[2] = size >> 8 & 0xff; result[3] = size & 0xff; result.set(type, 4); for (i = 0, size = 8; i < len; i++) { result.set(payload[i], size); size += payload[i].byteLength; } return result; } function mp4pssh(systemId, keyids, data) { if (systemId.byteLength !== 16) { throw new RangeError('Invalid system id'); } let version; let kids; if (keyids) { version = 1; kids = new Uint8Array(keyids.length * 16); for (let ix = 0; ix < keyids.length; ix++) { const k = keyids[ix]; // uint8array if (k.byteLength !== 16) { throw new RangeError('Invalid key'); } kids.set(k, ix * 16); } } else { version = 0; kids = new Uint8Array(); } let kidCount; if (version > 0) { kidCount = new Uint8Array(4); if (keyids.length > 0) { new DataView(kidCount.buffer).setUint32(0, keyids.length, false); } } else { kidCount = new Uint8Array(); } const dataSize = new Uint8Array(4); if (data && data.byteLength > 0) { new DataView(dataSize.buffer).setUint32(0, data.byteLength, false); } return mp4Box([112, 115, 115, 104], new Uint8Array([version, 0x00, 0x00, 0x00 // Flags ]), systemId, // 16 bytes kidCount, kids, dataSize, data || new Uint8Array()); } function parseMultiPssh(initData) { const results = []; if (initData instanceof ArrayBuffer) { const length = initData.byteLength; let offset = 0; while (offset + 32 < length) { const view = new DataView(initData, offset); const pssh = parsePssh(view); results.push(pssh); offset += pssh.size; } } return results; } function parsePssh(view) { const size = view.getUint32(0); const offset = view.byteOffset; const length = view.byteLength; if (length < size) { return { offset, size: length }; } const type = view.getUint32(4); if (type !== 0x70737368) { return { offset, size }; } const version = view.getUint32(8) >>> 24; if (version !== 0 && version !== 1) { return { offset, size }; } const buffer = view.buffer; const systemId = Hex.hexDump(new Uint8Array(buffer, offset + 12, 16)); const dataSizeOrKidCount = view.getUint32(28); let kids = null; let data = null; if (version === 0) { if (size - 32 < dataSizeOrKidCount || dataSizeOrKidCount < 22) { return { offset, size }; } data = new Uint8Array(buffer, offset + 32, dataSizeOrKidCount); } else if (version === 1) { if (!dataSizeOrKidCount || length < offset + 32 + dataSizeOrKidCount * 16 + 16) { return { offset, size }; } kids = []; for (let i = 0; i < dataSizeOrKidCount; i++) { kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16)); } } return { version, systemId, kids, data, offset, size }; } let keyUriToKeyIdMap = {}; class LevelKey { static clearKeyUriToKeyIdMap() { keyUriToKeyIdMap = {}; } constructor(method, uri, format, formatversions = [1], iv = null) { this.uri = void 0; this.method = void 0; this.keyFormat = void 0; this.keyFormatVersions = void 0; this.encrypted = void 0; this.isCommonEncryption = void 0; this.iv = null; this.key = null; this.keyId = null; this.pssh = null; this.method = method; this.uri = uri; this.keyFormat = format; this.keyFormatVersions = formatversions; this.iv = iv; this.encrypted = method ? method !== 'NONE' : false; this.isCommonEncryption = this.encrypted && method !== 'AES-128'; } isSupported() { // If it's Segment encryption or No encryption, just select that key system if (this.method) { if (this.method === 'AES-128' || this.method === 'NONE') { return true; } if (this.keyFormat === 'identity') { // Maintain support for clear SAMPLE-AES with MPEG-3 TS return this.method === 'SAMPLE-AES'; } else { switch (this.keyFormat) { case KeySystemFormats.FAIRPLAY: case KeySystemFormats.WIDEVINE: case KeySystemFormats.PLAYREADY: case KeySystemFormats.CLEARKEY: return ['ISO-23001-7', 'SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) !== -1; } } } return false; } getDecryptData(sn) { if (!this.encrypted || !this.uri) { return null; } if (this.method === 'AES-128' && this.uri && !this.iv) { if (typeof sn !== 'number') { // We are fetching decryption data for a initialization segment // If the segment was encrypted with AES-128 // It must have an IV defined. We cannot substitute the Segment Number in. if (this.method === 'AES-128' && !this.iv) { logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`); } // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation. sn = 0; } const iv = createInitializationVector(sn); const decryptdata = new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv); return decryptdata; } // Initialize keyId if possible const keyBytes = convertDataUriToArrayBytes(this.uri); if (keyBytes) { switch (this.keyFormat) { case KeySystemFormats.WIDEVINE: this.pssh = keyBytes; // In case of widevine keyID is embedded in PSSH box. Read Key ID. if (keyBytes.length >= 22) { this.keyId = keyBytes.subarray(keyBytes.length - 22, keyBytes.length - 6); } break; case KeySystemFormats.PLAYREADY: { const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]); this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes); const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2); const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16)); // Parse Playready WRMHeader XML const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length); const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml'); const keyData = xmlDoc.getElementsByTagName('KID')[0]; if (keyData) { const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE'); if (keyId) { const keyIdArray = base64Decode(keyId).subarray(0, 16); // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID changeEndianness(keyIdArray); this.keyId = keyIdArray; } } break; } default: { let keydata = keyBytes.subarray(0, 16); if (keydata.length !== 16) { const padded = new Uint8Array(16); padded.set(keydata, 16 - keydata.length); keydata = padded; } this.keyId = keydata; break; } } } // Default behavior: assign a new keyId for each uri if (!this.keyId || this.keyId.byteLength !== 16) { let keyId = keyUriToKeyIdMap[this.uri]; if (!keyId) { const val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER; keyId = new Uint8Array(16); const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes dv.setUint32(0, val); keyUriToKeyIdMap[this.uri] = keyId; } this.keyId = keyId; } return this; } } function createInitializationVector(segmentNumber) { const uint8View = new Uint8Array(16); for (let i = 12; i < 16; i++) { uint8View[i] = segmentNumber >> 8 * (15 - i) & 0xff; } return uint8View; } const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g; function hasVariableReferences(str) { return VARIABLE_REPLACEMENT_REGEX.test(str); } function substituteVariablesInAttributes(parsed, attr, attributeNames) { if (parsed.variableList !== null || parsed.hasVariableRefs) { for (let i = attributeNames.length; i--;) { const name = attributeNames[i]; const value = attr[name]; if (value) { attr[name] = substituteVariables(parsed, value); } } } } function substituteVariables(parsed, value) { if (parsed.variableList !== null || parsed.hasVariableRefs) { const variableList = parsed.variableList; return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference => { const variableName = variableReference.substring(2, variableReference.length - 1); const variableValue = variableList == null ? void 0 : variableList[variableName]; if (variableValue === undefined) { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`)); return variableReference; } return variableValue; }); } return value; } function addVariableDefinition(parsed, attr, parentUrl) { let variableList = parsed.variableList; if (!variableList) { parsed.variableList = variableList = {}; } let NAME; let VALUE; if ('QUERYPARAM' in attr) { NAME = attr.QUERYPARAM; try { const searchParams = new self.URL(parentUrl).searchParams; if (searchParams.has(NAME)) { VALUE = searchParams.get(NAME); } else { throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`); } } catch (error) { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`)); } } else { NAME = attr.NAME; VALUE = attr.VALUE; } if (NAME in variableList) { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`)); } else { variableList[NAME] = VALUE || ''; } } function importVariableDefinition(parsed, attr, sourceVariableList) { const IMPORT = attr.IMPORT; if (sourceVariableList && IMPORT in sourceVariableList) { let variableList = parsed.variableList; if (!variableList) { parsed.variableList = variableList = {}; } variableList[IMPORT] = sourceVariableList[IMPORT]; } else { parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`)); } } /** * MediaSource helper */ function getMediaSource(preferManagedMediaSource = true) { if (typeof self === 'undefined') return undefined; const mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource; return mms || self.MediaSource || self.WebKitMediaSource; } function isManagedMediaSource(source) { return typeof self !== 'undefined' && source === self.ManagedMediaSource; } // from http://mp4ra.org/codecs.html // values indicate codec selection preference (lower is higher priority) const sampleEntryCodesISO = { audio: { a3ds: 1, 'ac-3': 0.95, 'ac-4': 1, alac: 0.9, alaw: 1, dra1: 1, 'dts+': 1, 'dts-': 1, dtsc: 1, dtse: 1, dtsh: 1, 'ec-3': 0.9, enca: 1, fLaC: 0.9, // MP4-RA listed codec entry for FLAC flac: 0.9, // legacy browser codec name for FLAC FLAC: 0.9, // some manifests may list "FLAC" with Apple's tools g719: 1, g726: 1, m4ae: 1, mha1: 1, mha2: 1, mhm1: 1, mhm2: 1, mlpa: 1, mp4a: 1, 'raw ': 1, Opus: 1, opus: 1, // browsers expect this to be lowercase despite MP4RA says 'Opus' samr: 1, sawb: 1, sawp: 1, sevc: 1, sqcp: 1, ssmv: 1, twos: 1, ulaw: 1 }, video: { avc1: 1, avc2: 1, avc3: 1, avc4: 1, avcp: 1, av01: 0.8, drac: 1, dva1: 1, dvav: 1, dvh1: 0.7, dvhe: 0.7, encv: 1, hev1: 0.75, hvc1: 0.75, mjp2: 1, mp4v: 1, mvc1: 1, mvc2: 1, mvc3: 1, mvc4: 1, resv: 1, rv60: 1, s263: 1, svc1: 1, svc2: 1, 'vc-1': 1, vp08: 1, vp09: 0.9 }, text: { stpp: 1, wvtt: 1 } }; function isCodecType(codec, type) { const typeCodes = sampleEntryCodesISO[type]; return !!typeCodes && !!typeCodes[codec.slice(0, 4)]; } function areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource = true) { return !codecs.split(',').some(codec => !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource)); } function isCodecMediaSourceSupported(codec, type, preferManagedMediaSource = true) { var _MediaSource$isTypeSu; const MediaSource = getMediaSource(preferManagedMediaSource); return (_MediaSource$isTypeSu = MediaSource == null ? void 0 : MediaSource.isTypeSupported(mimeTypeForCodec(codec, type))) != null ? _MediaSource$isTypeSu : false; } function mimeTypeForCodec(codec, type) { return `${type}/mp4;codecs="${codec}"`; } function videoCodecPreferenceValue(videoCodec) { if (videoCodec) { const fourCC = videoCodec.substring(0, 4); return sampleEntryCodesISO.video[fourCC]; } return 2; } function codecsSetSelectionPreferenceValue(codecSet) { return codecSet.split(',').reduce((num, fourCC) => { const preferenceValue = sampleEntryCodesISO.video[fourCC]; if (preferenceValue) { return (preferenceValue * 2 + num) / (num ? 3 : 2); } return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1); }, 0); } const CODEC_COMPATIBLE_NAMES = {}; function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource = true) { if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) { return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]; } // Idealy fLaC and Opus would be first (spec-compliant) but // some browsers will report that fLaC is supported then fail. // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728 const codecsToCheck = { flac: ['flac', 'fLaC', 'FLAC'], opus: ['opus', 'Opus'] }[lowerCaseCodec]; for (let i = 0; i < codecsToCheck.length; i++) { if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) { CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i]; return codecsToCheck[i]; } } return lowerCaseCodec; } const AUDIO_CODEC_REGEXP = /flac|opus/i; function getCodecCompatibleName(codec, preferManagedMediaSource = true) { return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource)); } function pickMostCompleteCodecName(parsedCodec, levelCodec) { // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a // so use level codec is parsed codec is unavailable or incomplete if (parsedCodec && parsedCodec !== 'mp4a') { return parsedCodec; } return levelCodec ? levelCodec.split(',')[0] : levelCodec; } function convertAVC1ToAVCOTI(codec) { // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e. const codecs = codec.split(','); for (let i = 0; i < codecs.length; i++) { const avcdata = codecs[i].split('.'); if (avcdata.length > 2) { let result = avcdata.shift() + '.'; result += parseInt(avcdata.shift()).toString(16); result += ('000' + parseInt(avcdata.shift()).toString(16)).slice(-4); codecs[i] = result; } } return codecs.join(','); } const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g; const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g; const IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m; // Handle empty Media Playlist (first EXTINF not signaled, but TARGETDURATION present) const LEVEL_PLAYLIST_REGEX_FAST = new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source, // duration (#EXTINF:,), group 1 => duration, group 2 => title /(?!#) *(\S[^\r\n]*)/.source, // segment URI, group 3 => the URI (note newline is not eaten) /#EXT-X-BYTERANGE:*(.+)/.source, // next segment's byterange, group 4 => range spec (x@y) /#EXT-X-PROGRAM-DATE-TIME:(.+)/.source, // next segment's program date/time group 5 => the datetime spec /#.*/.source // All other non-segment oriented tags will match with all groups empty ].join('|'), 'g'); const LEVEL_PLAYLIST_REGEX_SLOW = new RegExp([/#(EXTM3U)/.source, /#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source, /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source, /#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS)/.source, /(#)([^:]*):(.*)/.source, /(#)(.*)(?:.*)\r?\n?/.source].join('|')); class M3U8Parser { static findGroup(groups, mediaGroupId) { for (let i = 0; i < groups.length; i++) { const group = groups[i]; if (group.id === mediaGroupId) { return group; } } } static resolve(url, baseUrl) { return urlToolkitExports.buildAbsoluteURL(baseUrl, url, { alwaysNormalize: true }); } static isMediaPlaylist(str) { return IS_MEDIA_PLAYLIST.test(str); } static parseMasterPlaylist(string, baseurl) { const hasVariableRefs = hasVariableReferences(string) ; const parsed = { contentSteering: null, levels: [], playlistParsingError: null, sessionData: null, sessionKeys: null, startTimeOffset: null, variableList: null, hasVariableRefs }; const levelsWithKnownCodecs = []; MASTER_PLAYLIST_REGEX.lastIndex = 0; let result; while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) { if (result[1]) { var _level$unknownCodecs; // '#EXT-X-STREAM-INF' is found, parse level tag in group 1 const attrs = new AttrList(result[1]); { substituteVariablesInAttributes(parsed, attrs, ['CODECS', 'SUPPLEMENTAL-CODECS', 'ALLOWED-CPC', 'PATHWAY-ID', 'STABLE-VARIANT-ID', 'AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS', 'NAME']); } const uri = substituteVariables(parsed, result[2]) ; const level = { attrs, bitrate: attrs.decimalInteger('BANDWIDTH') || attrs.decimalInteger('AVERAGE-BANDWIDTH'), name: attrs.NAME, url: M3U8Parser.resolve(uri, baseurl) }; const resolution = attrs.decimalResolution('RESOLUTION'); if (resolution) { level.width = resolution.width; level.height = resolution.height; } setCodecs(attrs.CODECS, level); if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) { levelsWithKnownCodecs.push(level); } parsed.levels.push(level); } else if (result[3]) { const tag = result[3]; const attributes = result[4]; switch (tag) { case 'SESSION-DATA': { // #EXT-X-SESSION-DATA const sessionAttrs = new AttrList(attributes); { substituteVariablesInAttributes(parsed, sessionAttrs, ['DATA-ID', 'LANGUAGE', 'VALUE', 'URI']); } const dataId = sessionAttrs['DATA-ID']; if (dataId) { if (parsed.sessionData === null) { parsed.sessionData = {}; } parsed.sessionData[dataId] = sessionAttrs; } break; } case 'SESSION-KEY': { // #EXT-X-SESSION-KEY const sessionKey = parseKey(attributes, baseurl, parsed); if (sessionKey.encrypted && sessionKey.isSupported()) { if (parsed.sessionKeys === null) { parsed.sessionKeys = []; } parsed.sessionKeys.push(sessionKey); } else { logger.warn(`[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "${attributes}"`); } break; } case 'DEFINE': { // #EXT-X-DEFINE { const variableAttributes = new AttrList(attributes); substituteVariablesInAttributes(parsed, variableAttributes, ['NAME', 'VALUE', 'QUERYPARAM']); addVariableDefinition(parsed, variableAttributes, baseurl); } break; } case 'CONTENT-STEERING': { // #EXT-X-CONTENT-STEERING const contentSteeringAttributes = new AttrList(attributes); { substituteVariablesInAttributes(parsed, contentSteeringAttributes, ['SERVER-URI', 'PATHWAY-ID']); } parsed.contentSteering = { uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl), pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.' }; break; } case 'START': { // #EXT-X-START parsed.startTimeOffset = parseStartTimeOffset(attributes); break; } } } } // Filter out levels with unknown codecs if it does not remove all levels const stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length; parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels; if (parsed.levels.length === 0) { parsed.playlistParsingError = new Error('no levels found in manifest'); } return parsed; } static parseMasterPlaylistMedia(string, baseurl, parsed) { let result; const results = {}; const levels = parsed.levels; const groupsByType = { AUDIO: levels.map(level => ({ id: level.attrs.AUDIO, audioCodec: level.audioCodec })), SUBTITLES: levels.map(level => ({ id: level.attrs.SUBTITLES, textCodec: level.textCodec })), 'CLOSED-CAPTIONS': [] }; let id = 0; MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0; while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) { const attrs = new AttrList(result[1]); const type = attrs.TYPE; if (type) { const groups = groupsByType[type]; const medias = results[type] || []; results[type] = medias; { substituteVariablesInAttributes(parsed, attrs, ['URI', 'GROUP-ID', 'LANGUAGE', 'ASSOC-LANGUAGE', 'STABLE-RENDITION-ID', 'NAME', 'INSTREAM-ID', 'CHARACTERISTICS', 'CHANNELS']); } const lang = attrs.LANGUAGE; const assocLang = attrs['ASSOC-LANGUAGE']; const channels = attrs.CHANNELS; const characteristics = attrs.CHARACTERISTICS; const instreamId = attrs['INSTREAM-ID']; const media = { attrs, bitrate: 0, id: id++, groupId: attrs['GROUP-ID'] || '', name: attrs.NAME || lang || '', type, default: attrs.bool('DEFAULT'), autoselect: attrs.bool('AUTOSELECT'), forced: attrs.bool('FORCED'), lang, url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl) : '' }; if (assocLang) { media.assocLang = assocLang; } if (channels) { media.channels = channels; } if (characteristics) { media.characteristics = characteristics; } if (instreamId) { media.instreamId = instreamId; } if (groups != null && groups.length) { // If there are audio or text groups signalled in the manifest, let's look for a matching codec string for this track // If we don't find the track signalled, lets use the first audio groups codec we have // Acting as a best guess const groupCodec = M3U8Parser.findGroup(groups, media.groupId) || groups[0]; assignCodec(media, groupCodec, 'audioCodec'); assignCodec(media, groupCodec, 'textCodec'); } medias.push(media); } } return results; } static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) { const level = new LevelDetails(baseurl); const fragments = level.fragments; // The most recent init segment seen (applies to all subsequent segments) let currentInitSegment = null; let currentSN = 0; let currentPart = 0; let totalduration = 0; let discontinuityCounter = 0; let prevFrag = null; let frag = new Fragment(type, baseurl); let result; let i; let levelkeys; let firstPdtIndex = -1; let createNextFrag = false; let nextByteRange = null; LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0; level.m3u8 = string; level.hasVariableRefs = hasVariableReferences(string) ; while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) { if (createNextFrag) { createNextFrag = false; frag = new Fragment(type, baseurl); // setup the next fragment for part loading frag.start = totalduration; frag.sn = currentSN; frag.cc = discontinuityCounter; frag.level = id; if (currentInitSegment) { frag.initSegment = currentInitSegment; frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime; currentInitSegment.rawProgramDateTime = null; if (nextByteRange) { frag.setByteRange(nextByteRange); nextByteRange = null; } } } const duration = result[1]; if (duration) { // INF frag.duration = parseFloat(duration); // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 const title = (' ' + result[2]).slice(1); frag.title = title || null; frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]); } else if (result[3]) { // url if (isFiniteNumber(frag.duration)) { frag.start = totalduration; if (levelkeys) { setFragLevelKeys(frag, levelkeys, level); } frag.sn = currentSN; frag.level = id; frag.cc = discontinuityCounter; fragments.push(frag); // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 const uri = (' ' + result[3]).slice(1); frag.relurl = substituteVariables(level, uri) ; assignProgramDateTime(frag, prevFrag); prevFrag = frag; totalduration += frag.duration; currentSN++; currentPart = 0; createNextFrag = true; } } else if (result[4]) { // X-BYTERANGE const data = (' ' + result[4]).slice(1); if (prevFrag) { frag.setByteRange(data, prevFrag); } else { frag.setByteRange(data); } } else if (result[5]) { // PROGRAM-DATE-TIME // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 frag.rawProgramDateTime = (' ' + result[5]).slice(1); frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]); if (firstPdtIndex === -1) { firstPdtIndex = fragments.length; } } else { result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW); if (!result) { logger.warn('No matches on slow regex match for level playlist!'); continue; } for (i = 1; i < result.length; i++) { if (typeof result[i] !== 'undefined') { break; } } // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 const tag = (' ' + result[i]).slice(1); const value1 = (' ' + result[i + 1]).slice(1); const value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : ''; switch (tag) { case 'PLAYLIST-TYPE': level.type = value1.toUpperCase(); break; case 'MEDIA-SEQUENCE': currentSN = level.startSN = parseInt(value1); break; case 'SKIP': { const skipAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, skipAttrs, ['RECENTLY-REMOVED-DATERANGES']); } const skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS'); if (isFiniteNumber(skippedSegments)) { level.skippedSegments = skippedSegments; // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails` for (let _i = skippedSegments; _i--;) { fragments.unshift(null); } currentSN += skippedSegments; } const recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES'); if (recentlyRemovedDateranges) { level.recentlyRemovedDateranges = recentlyRemovedDateranges.split('\t'); } break; } case 'TARGETDURATION': level.targetduration = Math.max(parseInt(value1), 1); break; case 'VERSION': level.version = parseInt(value1); break; case 'INDEPENDENT-SEGMENTS': case 'EXTM3U': break; case 'ENDLIST': level.live = false; break; case '#': if (value1 || value2) { frag.tagList.push(value2 ? [value1, value2] : [value1]); } break; case 'DISCONTINUITY': discontinuityCounter++; frag.tagList.push(['DIS']); break; case 'GAP': frag.gap = true; frag.tagList.push([tag]); break; case 'BITRATE': frag.tagList.push([tag, value1]); break; case 'DATERANGE': { const dateRangeAttr = new AttrList(value1); { substituteVariablesInAttributes(level, dateRangeAttr, ['ID', 'CLASS', 'START-DATE', 'END-DATE', 'SCTE35-CMD', 'SCTE35-OUT', 'SCTE35-IN']); substituteVariablesInAttributes(level, dateRangeAttr, dateRangeAttr.clientAttrs); } const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]); if (dateRange.isValid || level.skippedSegments) { level.dateRanges[dateRange.id] = dateRange; } else { logger.warn(`Ignoring invalid DATERANGE tag: "${value1}"`); } // Add to fragment tag list for backwards compatibility (< v1.2.0) frag.tagList.push(['EXT-X-DATERANGE', value1]); break; } case 'DEFINE': { { const variableAttributes = new AttrList(value1); substituteVariablesInAttributes(level, variableAttributes, ['NAME', 'VALUE', 'IMPORT', 'QUERYPARAM']); if ('IMPORT' in variableAttributes) { importVariableDefinition(level, variableAttributes, multivariantVariableList); } else { addVariableDefinition(level, variableAttributes, baseurl); } } break; } case 'DISCONTINUITY-SEQUENCE': discontinuityCounter = parseInt(value1); break; case 'KEY': { const levelKey = parseKey(value1, baseurl, level); if (levelKey.isSupported()) { if (levelKey.method === 'NONE') { levelkeys = undefined; break; } if (!levelkeys) { levelkeys = {}; } if (levelkeys[levelKey.keyFormat]) { levelkeys = _extends({}, levelkeys); } levelkeys[levelKey.keyFormat] = levelKey; } else { logger.warn(`[Keys] Ignoring invalid EXT-X-KEY tag: "${value1}"`); } break; } case 'START': level.startTimeOffset = parseStartTimeOffset(value1); break; case 'MAP': { const mapAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, mapAttrs, ['BYTERANGE', 'URI']); } if (frag.duration) { // Initial segment tag is after segment duration tag. // #EXTINF: 6.0 // #EXT-X-MAP:URI="init.mp4 const init = new Fragment(type, baseurl); setInitSegment(init, mapAttrs, id, levelkeys); currentInitSegment = init; frag.initSegment = currentInitSegment; if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) { frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime; } } else { // Initial segment tag is before segment duration tag // Handle case where EXT-X-MAP is declared after EXT-X-BYTERANGE const end = frag.byteRangeEndOffset; if (end) { const start = frag.byteRangeStartOffset; nextByteRange = `${end - start}@${start}`; } else { nextByteRange = null; } setInitSegment(frag, mapAttrs, id, levelkeys); currentInitSegment = frag; createNextFrag = true; } break; } case 'SERVER-CONTROL': { const serverControlAttrs = new AttrList(value1); level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD'); level.canSkipUntil = serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0); level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool('CAN-SKIP-DATERANGES'); level.partHoldBack = serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0); level.holdBack = serverControlAttrs.optionalFloat('HOLD-BACK', 0); break; } case 'PART-INF': { const partInfAttrs = new AttrList(value1); level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET'); break; } case 'PART': { let partList = level.partList; if (!partList) { partList = level.partList = []; } const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined; const index = currentPart++; const partAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, partAttrs, ['BYTERANGE', 'URI']); } const part = new Part(partAttrs, frag, baseurl, index, previousFragmentPart); partList.push(part); frag.duration += part.duration; break; } case 'PRELOAD-HINT': { const preloadHintAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']); } level.preloadHint = preloadHintAttrs; break; } case 'RENDITION-REPORT': { const renditionReportAttrs = new AttrList(value1); { substituteVariablesInAttributes(level, renditionReportAttrs, ['URI']); } level.renditionReports = level.renditionReports || []; level.renditionReports.push(renditionReportAttrs); break; } default: logger.warn(`line parsed but not handled: ${result}`); break; } } } if (prevFrag && !prevFrag.relurl) { fragments.pop(); totalduration -= prevFrag.duration; if (level.partList) { level.fragmentHint = prevFrag; } } else if (level.partList) { assignProgramDateTime(frag, prevFrag); frag.cc = discontinuityCounter; level.fragmentHint = frag; if (levelkeys) { setFragLevelKeys(frag, levelkeys, level); } } const fragmentLength = fragments.length; const firstFragment = fragments[0]; const lastFragment = fragments[fragmentLength - 1]; totalduration += level.skippedSegments * level.targetduration; if (totalduration > 0 && fragmentLength && lastFragment) { level.averagetargetduration = totalduration / fragmentLength; const lastSn = lastFragment.sn; level.endSN = lastSn !== 'initSegment' ? lastSn : 0; if (!level.live) { lastFragment.endList = true; } if (firstFragment) { level.startCC = firstFragment.cc; } } else { level.endSN = 0; level.startCC = 0; } if (level.fragmentHint) { totalduration += level.fragmentHint.duration; } level.totalduration = totalduration; level.endCC = discontinuityCounter; /** * Backfill any missing PDT values * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after * one or more Media Segment URIs, the client SHOULD extrapolate * backward from that tag (using EXTINF durations and/or media * timestamps) to associate dates with those segments." * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs * computed. */ if (firstPdtIndex > 0) { backfillProgramDateTimes(fragments, firstPdtIndex); } return level; } } function parseKey(keyTagAttributes, baseurl, parsed) { var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT; // https://tools.ietf.org/html/rfc8216#section-4.3.2.4 const keyAttrs = new AttrList(keyTagAttributes); { substituteVariablesInAttributes(parsed, keyAttrs, ['KEYFORMAT', 'KEYFORMATVERSIONS', 'URI', 'IV', 'URI']); } const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : ''; const decrypturi = keyAttrs.URI; const decryptiv = keyAttrs.hexadecimalInteger('IV'); const decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS; // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity". const decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : 'identity'; if (decrypturi && keyAttrs.IV && !decryptiv) { logger.error(`Invalid IV: ${keyAttrs.IV}`); } // If decrypturi is a URI with a scheme, then baseurl will be ignored // No uri is allowed when METHOD is NONE const resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : ''; const keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : '1').split('/').map(Number).filter(Number.isFinite); return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv); } function parseStartTimeOffset(startAttributes) { const startAttrs = new AttrList(startAttributes); const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET'); if (isFiniteNumber(startTimeOffset)) { return startTimeOffset; } return null; } function setCodecs(codecsAttributeValue, level) { let codecs = (codecsAttributeValue || '').split(/[ ,]+/).filter(c => c); ['video', 'audio', 'text'].forEach(type => { const filtered = codecs.filter(codec => isCodecType(codec, type)); if (filtered.length) { // Comma separated list of all codecs for type level[`${type}Codec`] = filtered.join(','); // Remove known codecs so that only unknownCodecs are left after iterating through each type codecs = codecs.filter(codec => filtered.indexOf(codec) === -1); } }); level.unknownCodecs = codecs; } function assignCodec(media, groupItem, codecProperty) { const codecValue = groupItem[codecProperty]; if (codecValue) { media[codecProperty] = codecValue; } } function backfillProgramDateTimes(fragments, firstPdtIndex) { let fragPrev = fragments[firstPdtIndex]; for (let i = firstPdtIndex; i--;) { const frag = fragments[i]; // Exit on delta-playlist skipped segments if (!frag) { return; } frag.programDateTime = fragPrev.programDateTime - frag.duration * 1000; fragPrev = frag; } } function assignProgramDateTime(frag, prevFrag) { if (frag.rawProgramDateTime) { frag.programDateTime = Date.parse(frag.rawProgramDateTime); } else if (prevFrag != null && prevFrag.programDateTime) { frag.programDateTime = prevFrag.endProgramDateTime; } if (!isFiniteNumber(frag.programDateTime)) { frag.programDateTime = null; frag.rawProgramDateTime = null; } } function setInitSegment(frag, mapAttrs, id, levelkeys) { frag.relurl = mapAttrs.URI; if (mapAttrs.BYTERANGE) { frag.setByteRange(mapAttrs.BYTERANGE); } frag.level = id; frag.sn = 'initSegment'; if (levelkeys) { frag.levelkeys = levelkeys; } frag.initSegment = null; } function setFragLevelKeys(frag, levelkeys, level) { frag.levelkeys = levelkeys; const { encryptedFragments } = level; if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some(format => levelkeys[format].isCommonEncryption)) { encryptedFragments.push(frag); } } var PlaylistContextType = { MANIFEST: "manifest", LEVEL: "level", AUDIO_TRACK: "audioTrack", SUBTITLE_TRACK: "subtitleTrack" }; var PlaylistLevelType = { MAIN: "main", AUDIO: "audio", SUBTITLE: "subtitle" }; function mapContextToLevelType(context) { const { type } = context; switch (type) { case PlaylistContextType.AUDIO_TRACK: return PlaylistLevelType.AUDIO; case PlaylistContextType.SUBTITLE_TRACK: return PlaylistLevelType.SUBTITLE; default: return PlaylistLevelType.MAIN; } } function getResponseUrl(response, context) { let url = response.url; // responseURL not supported on some browsers (it is used to detect URL redirection) // data-uri mode also not supported (but no need to detect redirection) if (url === undefined || url.indexOf('data:') === 0) { // fallback to initial URL url = context.url; } return url; } class PlaylistLoader { constructor(hls) { this.hls = void 0; this.loaders = Object.create(null); this.variableList = null; this.hls = hls; this.registerListeners(); } startLoad(startPosition) {} stopLoad() { this.destroyInternalLoaders(); } registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this); hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this); hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this); } /** * Returns defaults or configured loader-type overloads (pLoader and loader config params) */ createInternalLoader(context) { const config = this.hls.config; const PLoader = config.pLoader; const Loader = config.loader; const InternalLoader = PLoader || Loader; const loader = new InternalLoader(config); this.loaders[context.type] = loader; return loader; } getInternalLoader(context) { return this.loaders[context.type]; } resetInternalLoader(contextType) { if (this.loaders[contextType]) { delete this.loaders[contextType]; } } /** * Call `destroy` on all internal loader instances mapped (one per context type) */ destroyInternalLoaders() { for (const contextType in this.loaders) { const loader = this.loaders[contextType]; if (loader) { loader.destroy(); } this.resetInternalLoader(contextType); } } destroy() { this.variableList = null; this.unregisterListeners(); this.destroyInternalLoaders(); } onManifestLoading(event, data) { const { url } = data; this.variableList = null; this.load({ id: null, level: 0, responseType: 'text', type: PlaylistContextType.MANIFEST, url, deliveryDirectives: null }); } onLevelLoading(event, data) { const { id, level, pathwayId, url, deliveryDirectives } = data; this.load({ id, level, pathwayId, responseType: 'text', type: PlaylistContextType.LEVEL, url, deliveryDirectives }); } onAudioTrackLoading(event, data) { const { id, groupId, url, deliveryDirectives } = data; this.load({ id, groupId, level: null, responseType: 'text', type: PlaylistContextType.AUDIO_TRACK, url, deliveryDirectives }); } onSubtitleTrackLoading(event, data) { const { id, groupId, url, deliveryDirectives } = data; this.load({ id, groupId, level: null, responseType: 'text', type: PlaylistContextType.SUBTITLE_TRACK, url, deliveryDirectives }); } load(context) { var _context$deliveryDire; const config = this.hls.config; // logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`); // Check if a loader for this context already exists let loader = this.getInternalLoader(context); if (loader) { const loaderContext = loader.context; if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) { // same URL can't overlap logger.trace('[playlist-loader]: playlist request ongoing'); return; } logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`); loader.abort(); } // apply different configs for retries depending on // context (manifest, level, audio/subs playlist) let loadPolicy; if (context.type === PlaylistContextType.MANIFEST) { loadPolicy = config.manifestLoadPolicy.default; } else { loadPolicy = _extends({}, config.playlistLoadPolicy.default, { timeoutRetry: null, errorRetry: null }); } loader = this.createInternalLoader(context); // Override level/track timeout for LL-HLS requests // (the default of 10000ms is counter productive to blocking playlist reload requests) if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) { let levelDetails; if (context.type === PlaylistContextType.LEVEL && context.level !== null) { levelDetails = this.hls.levels[context.level].details; } else if (context.type === PlaylistContextType.AUDIO_TRACK && context.id !== null) { levelDetails = this.hls.audioTracks[context.id].details; } else if (context.type === PlaylistContextType.SUBTITLE_TRACK && context.id !== null) { levelDetails = this.hls.subtitleTracks[context.id].details; } if (levelDetails) { const partTarget = levelDetails.partTarget; const targetDuration = levelDetails.targetduration; if (partTarget && targetDuration) { const maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1000; loadPolicy = _extends({}, loadPolicy, { maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs), maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs) }); } } } const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: legacyRetryCompatibility.maxNumRetry || 0, retryDelay: legacyRetryCompatibility.retryDelayMs || 0, maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { const loader = this.getInternalLoader(context); this.resetInternalLoader(context.type); const string = response.data; // Validate if it is an M3U8 at all if (string.indexOf('#EXTM3U') !== 0) { this.handleManifestParsingError(response, context, new Error('no EXTM3U delimiter'), networkDetails || null, stats); return; } stats.parsing.start = performance.now(); if (M3U8Parser.isMediaPlaylist(string)) { this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails || null, loader); } else { this.handleMasterPlaylist(response, stats, context, networkDetails); } }, onError: (response, context, networkDetails, stats) => { this.handleNetworkError(context, networkDetails, false, response, stats); }, onTimeout: (stats, context, networkDetails) => { this.handleNetworkError(context, networkDetails, true, undefined, stats); } }; // logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`); loader.load(context, loaderConfig, loaderCallbacks); } handleMasterPlaylist(response, stats, context, networkDetails) { const hls = this.hls; const string = response.data; const url = getResponseUrl(response, context); const parsedResult = M3U8Parser.parseMasterPlaylist(string, url); if (parsedResult.playlistParsingError) { this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats); return; } const { contentSteering, levels, sessionData, sessionKeys, startTimeOffset, variableList } = parsedResult; this.variableList = variableList; const { AUDIO: audioTracks = [], SUBTITLES: subtitles, 'CLOSED-CAPTIONS': captions } = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult); if (audioTracks.length) { // check if we have found an audio track embedded in main playlist (audio track without URI attribute) const embeddedAudioFound = audioTracks.some(audioTrack => !audioTrack.url); // if no embedded audio track defined, but audio codec signaled in quality level, // we need to signal this main audio track this could happen with playlists with // alt audio rendition in which quality levels (main) // contains both audio+video. but with mixed audio track not signaled if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) { logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one'); audioTracks.unshift({ type: 'main', name: 'main', groupId: 'main', default: false, autoselect: false, forced: false, id: -1, attrs: new AttrList({}), bitrate: 0, url: '' }); } } hls.trigger(Events.MANIFEST_LOADED, { levels, audioTracks, subtitles, captions, contentSteering, url, stats, networkDetails, sessionData, sessionKeys, startTimeOffset, variableList }); } handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) { const hls = this.hls; const { id, level, type } = context; const url = getResponseUrl(response, context); const levelUrlId = 0; const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0; const levelType = mapContextToLevelType(context); const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList); // We have done our first request (Manifest-type) and receive // not a master playlist but a chunk-list (track/level) // We fire the manifest-loaded event anyway with the parsed level-details // by creating a single-level structure for it. if (type === PlaylistContextType.MANIFEST) { const singleLevel = { attrs: new AttrList({}), bitrate: 0, details: levelDetails, name: '', url }; hls.trigger(Events.MANIFEST_LOADED, { levels: [singleLevel], audioTracks: [], url, stats, networkDetails, sessionData: null, sessionKeys: null, contentSteering: null, startTimeOffset: null, variableList: null }); } // save parsing time stats.parsing.end = performance.now(); // extend the context with the new levelDetails property context.levelDetails = levelDetails; this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader); } handleManifestParsingError(response, context, error, networkDetails, stats) { this.hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.MANIFEST_PARSING_ERROR, fatal: context.type === PlaylistContextType.MANIFEST, url: response.url, err: error, error, reason: error.message, response, context, networkDetails, stats }); } handleNetworkError(context, networkDetails, timeout = false, response, stats) { let message = `A network ${timeout ? 'timeout' : 'error' + (response ? ' (status ' + response.code + ')' : '')} occurred while loading ${context.type}`; if (context.type === PlaylistContextType.LEVEL) { message += `: ${context.level} id: ${context.id}`; } else if (context.type === PlaylistContextType.AUDIO_TRACK || context.type === PlaylistContextType.SUBTITLE_TRACK) { message += ` id: ${context.id} group-id: "${context.groupId}"`; } const error = new Error(message); logger.warn(`[playlist-loader]: ${message}`); let details = ErrorDetails.UNKNOWN; let fatal = false; const loader = this.getInternalLoader(context); switch (context.type) { case PlaylistContextType.MANIFEST: details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR; fatal = true; break; case PlaylistContextType.LEVEL: details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR; fatal = false; break; case PlaylistContextType.AUDIO_TRACK: details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR; fatal = false; break; case PlaylistContextType.SUBTITLE_TRACK: details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR; fatal = false; break; } if (loader) { this.resetInternalLoader(context.type); } const errorData = { type: ErrorTypes.NETWORK_ERROR, details, fatal, url: context.url, loader, context, error, networkDetails, stats }; if (response) { const url = (networkDetails == null ? void 0 : networkDetails.url) || context.url; errorData.response = _objectSpread2({ url, data: undefined }, response); } this.hls.trigger(Events.ERROR, errorData); } handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) { const hls = this.hls; const { type, level, id, groupId, deliveryDirectives } = context; const url = getResponseUrl(response, context); const parent = mapContextToLevelType(context); const levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? level : undefined; if (!levelDetails.fragments.length) { const _error = new Error('No Segments found in Playlist'); hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.LEVEL_EMPTY_ERROR, fatal: false, url, error: _error, reason: _error.message, response, context, level: levelIndex, parent, networkDetails, stats }); return; } if (!levelDetails.targetduration) { levelDetails.playlistParsingError = new Error('Missing Target Duration'); } const error = levelDetails.playlistParsingError; if (error) { hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.LEVEL_PARSING_ERROR, fatal: false, url, error, reason: error.message, response, context, level: levelIndex, parent, networkDetails, stats }); return; } if (levelDetails.live && loader) { if (loader.getCacheAge) { levelDetails.ageHeader = loader.getCacheAge() || 0; } if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) { levelDetails.ageHeader = 0; } } switch (type) { case PlaylistContextType.MANIFEST: case PlaylistContextType.LEVEL: hls.trigger(Events.LEVEL_LOADED, { details: levelDetails, level: levelIndex || 0, id: id || 0, stats, networkDetails, deliveryDirectives }); break; case PlaylistContextType.AUDIO_TRACK: hls.trigger(Events.AUDIO_TRACK_LOADED, { details: levelDetails, id: id || 0, groupId: groupId || '', stats, networkDetails, deliveryDirectives }); break; case PlaylistContextType.SUBTITLE_TRACK: hls.trigger(Events.SUBTITLE_TRACK_LOADED, { details: levelDetails, id: id || 0, groupId: groupId || '', stats, networkDetails, deliveryDirectives }); break; } } } function sendAddTrackEvent(track, videoEl) { let event; try { event = new Event('addtrack'); } catch (err) { // for IE11 event = document.createEvent('Event'); event.initEvent('addtrack', false, false); } event.track = track; videoEl.dispatchEvent(event); } function addCueToTrack(track, cue) { // Sometimes there are cue overlaps on segmented vtts so the same // cue can appear more than once in different vtt files. // This avoid showing duplicated cues with same timecode and text. const mode = track.mode; if (mode === 'disabled') { track.mode = 'hidden'; } if (track.cues && !track.cues.getCueById(cue.id)) { try { track.addCue(cue); if (!track.cues.getCueById(cue.id)) { throw new Error(`addCue is failed for: ${cue}`); } } catch (err) { logger.debug(`[texttrack-utils]: ${err}`); try { const textTrackCue = new self.TextTrackCue(cue.startTime, cue.endTime, cue.text); textTrackCue.id = cue.id; track.addCue(textTrackCue); } catch (err2) { logger.debug(`[texttrack-utils]: Legacy TextTrackCue fallback failed: ${err2}`); } } } if (mode === 'disabled') { track.mode = mode; } } function clearCurrentCues(track) { // When track.mode is disabled, track.cues will be null. // To guarantee the removal of cues, we need to temporarily // change the mode to hidden const mode = track.mode; if (mode === 'disabled') { track.mode = 'hidden'; } if (track.cues) { for (let i = track.cues.length; i--;) { track.removeCue(track.cues[i]); } } if (mode === 'disabled') { track.mode = mode; } } function removeCuesInRange(track, start, end, predicate) { const mode = track.mode; if (mode === 'disabled') { track.mode = 'hidden'; } if (track.cues && track.cues.length > 0) { const cues = getCuesInRange(track.cues, start, end); for (let i = 0; i < cues.length; i++) { if (!predicate || predicate(cues[i])) { track.removeCue(cues[i]); } } } if (mode === 'disabled') { track.mode = mode; } } // Find first cue starting after given time. // Modified version of binary search O(log(n)). function getFirstCueIndexAfterTime(cues, time) { // If first cue starts after time, start there if (time < cues[0].startTime) { return 0; } // If the last cue ends before time there is no overlap const len = cues.length - 1; if (time > cues[len].endTime) { return -1; } let left = 0; let right = len; while (left <= right) { const mid = Math.floor((right + left) / 2); if (time < cues[mid].startTime) { right = mid - 1; } else if (time > cues[mid].startTime && left < len) { left = mid + 1; } else { // If it's not lower or higher, it must be equal. return mid; } } // At this point, left and right have swapped. // No direct match was found, left or right element must be the closest. Check which one has the smallest diff. return cues[left].startTime - time < time - cues[right].startTime ? left : right; } function getCuesInRange(cues, start, end) { const cuesFound = []; const firstCueInRange = getFirstCueIndexAfterTime(cues, start); if (firstCueInRange > -1) { for (let i = firstCueInRange, len = cues.length; i < len; i++) { const cue = cues[i]; if (cue.startTime >= start && cue.endTime <= end) { cuesFound.push(cue); } else if (cue.startTime > end) { return cuesFound; } } } return cuesFound; } function filterSubtitleTracks(textTrackList) { const tracks = []; for (let i = 0; i < textTrackList.length; i++) { const track = textTrackList[i]; // Edge adds a track without a label; we don't want to use it if ((track.kind === 'subtitles' || track.kind === 'captions') && track.label) { tracks.push(textTrackList[i]); } } return tracks; } var MetadataSchema = { audioId3: "org.id3", dateRange: "com.apple.quicktime.HLS", emsg: "https://aomedia.org/emsg/ID3" }; const MIN_CUE_DURATION = 0.25; function getCueClass() { if (typeof self === 'undefined') return undefined; return self.VTTCue || self.TextTrackCue; } function createCueWithDataFields(Cue, startTime, endTime, data, type) { let cue = new Cue(startTime, endTime, ''); try { cue.value = data; if (type) { cue.type = type; } } catch (e) { cue = new Cue(startTime, endTime, JSON.stringify(type ? _objectSpread2({ type }, data) : data)); } return cue; } // VTTCue latest draft allows an infinite duration, fallback // to MAX_VALUE if necessary const MAX_CUE_ENDTIME = (() => { const Cue = getCueClass(); try { Cue && new Cue(0, Number.POSITIVE_INFINITY, ''); } catch (e) { return Number.MAX_VALUE; } return Number.POSITIVE_INFINITY; })(); function dateRangeDateToTimelineSeconds(date, offset) { return date.getTime() / 1000 - offset; } function hexToArrayBuffer(str) { return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer; } class ID3TrackController { constructor(hls) { this.hls = void 0; this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; this.hls = hls; this._registerListeners(); } destroy() { this._unregisterListeners(); this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; // @ts-ignore this.hls = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } // Add ID3 metatadata text track. onMediaAttached(event, data) { this.media = data.media; } onMediaDetaching() { if (!this.id3Track) { return; } clearCurrentCues(this.id3Track); this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; } onManifestLoading() { this.dateRangeCuesAppended = {}; } createTrack(media) { const track = this.getID3Track(media.textTracks); track.mode = 'hidden'; return track; } getID3Track(textTracks) { if (!this.media) { return; } for (let i = 0; i < textTracks.length; i++) { const textTrack = textTracks[i]; if (textTrack.kind === 'metadata' && textTrack.label === 'id3') { // send 'addtrack' when reusing the textTrack for metadata, // same as what we do for captions sendAddTrackEvent(textTrack, this.media); return textTrack; } } return this.media.addTextTrack('metadata', 'id3'); } onFragParsingMetadata(event, data) { if (!this.media) { return; } const { hls: { config: { enableEmsgMetadataCues, enableID3MetadataCues } } } = this; if (!enableEmsgMetadataCues && !enableID3MetadataCues) { return; } const { samples } = data; // create track dynamically if (!this.id3Track) { this.id3Track = this.createTrack(this.media); } const Cue = getCueClass(); if (!Cue) { return; } for (let i = 0; i < samples.length; i++) { const type = samples[i].type; if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) { continue; } const frames = getID3Frames(samples[i].data); if (frames) { const startTime = samples[i].pts; let endTime = startTime + samples[i].duration; if (endTime > MAX_CUE_ENDTIME) { endTime = MAX_CUE_ENDTIME; } const timeDiff = endTime - startTime; if (timeDiff <= 0) { endTime = startTime + MIN_CUE_DURATION; } for (let j = 0; j < frames.length; j++) { const frame = frames[j]; // Safari doesn't put the timestamp frame in the TextTrack if (!isTimeStampFrame(frame)) { // add a bounds to any unbounded cues this.updateId3CueEnds(startTime, type); const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type); if (cue) { this.id3Track.addCue(cue); } } } } } } updateId3CueEnds(startTime, type) { var _this$id3Track; const cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues; if (cues) { for (let i = cues.length; i--;) { const cue = cues[i]; if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) { cue.endTime = startTime; } } } } onBufferFlushing(event, { startOffset, endOffset, type }) { const { id3Track, hls } = this; if (!hls) { return; } const { config: { enableEmsgMetadataCues, enableID3MetadataCues } } = hls; if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) { let predicate; if (type === 'audio') { predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues; } else if (type === 'video') { predicate = cue => cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; } else { predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; } removeCuesInRange(id3Track, startOffset, endOffset, predicate); } } onLevelUpdated(event, { details }) { if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) { return; } const { dateRangeCuesAppended, id3Track } = this; const { dateRanges } = details; const ids = Object.keys(dateRanges); // Remove cues from track not found in details.dateRanges if (id3Track) { const idsToRemove = Object.keys(dateRangeCuesAppended).filter(id => !ids.includes(id)); for (let i = idsToRemove.length; i--;) { const id = idsToRemove[i]; Object.keys(dateRangeCuesAppended[id].cues).forEach(key => { id3Track.removeCue(dateRangeCuesAppended[id].cues[key]); }); delete dateRangeCuesAppended[id]; } } // Exit if the playlist does not have Date Ranges or does not have Program Date Time const lastFragment = details.fragments[details.fragments.length - 1]; if (ids.length === 0 || !isFiniteNumber(lastFragment == null ? void 0 : lastFragment.programDateTime)) { return; } if (!this.id3Track) { this.id3Track = this.createTrack(this.media); } const dateTimeOffset = lastFragment.programDateTime / 1000 - lastFragment.start; const Cue = getCueClass(); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const dateRange = dateRanges[id]; const startTime = dateRangeDateToTimelineSeconds(dateRange.startDate, dateTimeOffset); // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT) const appendedDateRangeCues = dateRangeCuesAppended[id]; const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {}; let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false; let endTime = MAX_CUE_ENDTIME; const endDate = dateRange.endDate; if (endDate) { endTime = dateRangeDateToTimelineSeconds(endDate, dateTimeOffset); durationKnown = true; } else if (dateRange.endOnNext && !durationKnown) { const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id) => { if (id !== dateRange.id) { const otherDateRange = dateRanges[id]; if (otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && (!candidateDateRange || dateRange.startDate < candidateDateRange.startDate)) { return otherDateRange; } } return candidateDateRange; }, null); if (nextDateRangeWithSameClass) { endTime = dateRangeDateToTimelineSeconds(nextDateRangeWithSameClass.startDate, dateTimeOffset); durationKnown = true; } } // Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute) // This is to emulate Safari HLS playback handling of DateRange tags const attributes = Object.keys(dateRange.attr); for (let j = 0; j < attributes.length; j++) { const key = attributes[j]; if (!isDateRangeCueAttribute(key)) { continue; } const cue = cues[key]; if (cue) { if (durationKnown && !appendedDateRangeCues.durationKnown) { cue.endTime = endTime; } } else if (Cue) { let data = dateRange.attr[key]; if (isSCTE35Attribute(key)) { data = hexToArrayBuffer(data); } const _cue = createCueWithDataFields(Cue, startTime, endTime, { key, data }, MetadataSchema.dateRange); if (_cue) { _cue.id = id; this.id3Track.addCue(_cue); cues[key] = _cue; } } } // Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes dateRangeCuesAppended[id] = { cues, dateRange, durationKnown }; } } } class LatencyController { constructor(hls) { this.hls = void 0; this.config = void 0; this.media = null; this.levelDetails = null; this.currentTime = 0; this.stallCount = 0; this._latency = null; this.timeupdateHandler = () => this.timeupdate(); this.hls = hls; this.config = hls.config; this.registerListeners(); } get latency() { return this._latency || 0; } get maxLatency() { const { config, levelDetails } = this; if (config.liveMaxLatencyDuration !== undefined) { return config.liveMaxLatencyDuration; } return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0; } get targetLatency() { const { levelDetails } = this; if (levelDetails === null) { return null; } const { holdBack, partHoldBack, targetduration } = levelDetails; const { liveSyncDuration, liveSyncDurationCount, lowLatencyMode } = this.config; const userConfig = this.hls.userConfig; let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack; if (userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) { targetLatency = liveSyncDuration !== undefined ? liveSyncDuration : liveSyncDurationCount * targetduration; } const maxLiveSyncOnStallIncrease = targetduration; const liveSyncOnStallIncrease = 1.0; return targetLatency + Math.min(this.stallCount * liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease); } get liveSyncPosition() { const liveEdge = this.estimateLiveEdge(); const targetLatency = this.targetLatency; const levelDetails = this.levelDetails; if (liveEdge === null || targetLatency === null || levelDetails === null) { return null; } const edge = levelDetails.edge; const syncPosition = liveEdge - targetLatency - this.edgeStalled; const min = edge - levelDetails.totalduration; const max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration); return Math.min(Math.max(min, syncPosition), max); } get drift() { const { levelDetails } = this; if (levelDetails === null) { return 1; } return levelDetails.drift; } get edgeStalled() { const { levelDetails } = this; if (levelDetails === null) { return 0; } const maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3; return Math.max(levelDetails.age - maxLevelUpdateAge, 0); } get forwardBufferLength() { const { media, levelDetails } = this; if (!media || !levelDetails) { return 0; } const bufferedRanges = media.buffered.length; return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime; } destroy() { this.unregisterListeners(); this.onMediaDetaching(); this.levelDetails = null; // @ts-ignore this.hls = this.timeupdateHandler = null; } registerListeners() { this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); this.hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); this.hls.off(Events.ERROR, this.onError, this); } onMediaAttached(event, data) { this.media = data.media; this.media.addEventListener('timeupdate', this.timeupdateHandler); } onMediaDetaching() { if (this.media) { this.media.removeEventListener('timeupdate', this.timeupdateHandler); this.media = null; } } onManifestLoading() { this.levelDetails = null; this._latency = null; this.stallCount = 0; } onLevelUpdated(event, { details }) { this.levelDetails = details; if (details.advanced) { this.timeupdate(); } if (!details.live && this.media) { this.media.removeEventListener('timeupdate', this.timeupdateHandler); } } onError(event, data) { var _this$levelDetails; if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) { return; } this.stallCount++; if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) { logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency'); } } timeupdate() { const { media, levelDetails } = this; if (!media || !levelDetails) { return; } this.currentTime = media.currentTime; const latency = this.computeLatency(); if (latency === null) { return; } this._latency = latency; // Adapt playbackRate to meet target latency in low-latency mode const { lowLatencyMode, maxLiveSyncPlaybackRate } = this.config; if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) { return; } const targetLatency = this.targetLatency; if (targetLatency === null) { return; } const distanceFromTarget = latency - targetLatency; // Only adjust playbackRate when within one target duration of targetLatency // and more than one second from under-buffering. // Playback further than one target duration from target can be considered DVR playback. const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration); const inLiveRange = distanceFromTarget < liveMinLatencyDuration; if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) { const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate)); const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20; media.playbackRate = Math.min(max, Math.max(1, rate)); } else if (media.playbackRate !== 1 && media.playbackRate !== 0) { media.playbackRate = 1; } } estimateLiveEdge() { const { levelDetails } = this; if (levelDetails === null) { return null; } return levelDetails.edge + levelDetails.age; } computeLatency() { const liveEdge = this.estimateLiveEdge(); if (liveEdge === null) { return null; } return liveEdge - this.currentTime; } } const HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', null]; function isHdcpLevel(value) { return HdcpLevels.indexOf(value) > -1; } const VideoRangeValues = ['SDR', 'PQ', 'HLG']; function isVideoRange(value) { return !!value && VideoRangeValues.indexOf(value) > -1; } var HlsSkip = { No: "", Yes: "YES", v2: "v2" }; function getSkipValue(details) { const { canSkipUntil, canSkipDateRanges, age } = details; // A Client SHOULD NOT request a Playlist Delta Update unless it already // has a version of the Playlist that is no older than one-half of the Skip Boundary. // @see: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.3.7 const playlistRecentEnough = age < canSkipUntil / 2; if (canSkipUntil && playlistRecentEnough) { if (canSkipDateRanges) { return HlsSkip.v2; } return HlsSkip.Yes; } return HlsSkip.No; } class HlsUrlParameters { constructor(msn, part, skip) { this.msn = void 0; this.part = void 0; this.skip = void 0; this.msn = msn; this.part = part; this.skip = skip; } addDirectives(uri) { const url = new self.URL(uri); if (this.msn !== undefined) { url.searchParams.set('_HLS_msn', this.msn.toString()); } if (this.part !== undefined) { url.searchParams.set('_HLS_part', this.part.toString()); } if (this.skip) { url.searchParams.set('_HLS_skip', this.skip); } return url.href; } } class Level { constructor(data) { this._attrs = void 0; this.audioCodec = void 0; this.bitrate = void 0; this.codecSet = void 0; this.url = void 0; this.frameRate = void 0; this.height = void 0; this.id = void 0; this.name = void 0; this.videoCodec = void 0; this.width = void 0; this.details = void 0; this.fragmentError = 0; this.loadError = 0; this.loaded = void 0; this.realBitrate = 0; this.supportedPromise = void 0; this.supportedResult = void 0; this._avgBitrate = 0; this._audioGroups = void 0; this._subtitleGroups = void 0; // Deprecated (retained for backwards compatibility) this._urlId = 0; this.url = [data.url]; this._attrs = [data.attrs]; this.bitrate = data.bitrate; if (data.details) { this.details = data.details; } this.id = data.id || 0; this.name = data.name; this.width = data.width || 0; this.height = data.height || 0; this.frameRate = data.attrs.optionalFloat('FRAME-RATE', 0); this._avgBitrate = data.attrs.decimalInteger('AVERAGE-BANDWIDTH'); this.audioCodec = data.audioCodec; this.videoCodec = data.videoCodec; this.codecSet = [data.videoCodec, data.audioCodec].filter(c => !!c).map(s => s.substring(0, 4)).join(','); this.addGroupId('audio', data.attrs.AUDIO); this.addGroupId('text', data.attrs.SUBTITLES); } get maxBitrate() { return Math.max(this.realBitrate, this.bitrate); } get averageBitrate() { return this._avgBitrate || this.realBitrate || this.bitrate; } get attrs() { return this._attrs[0]; } get codecs() { return this.attrs.CODECS || ''; } get pathwayId() { return this.attrs['PATHWAY-ID'] || '.'; } get videoRange() { return this.attrs['VIDEO-RANGE'] || 'SDR'; } get score() { return this.attrs.optionalFloat('SCORE', 0); } get uri() { return this.url[0] || ''; } hasAudioGroup(groupId) { return hasGroup(this._audioGroups, groupId); } hasSubtitleGroup(groupId) { return hasGroup(this._subtitleGroups, groupId); } get audioGroups() { return this._audioGroups; } get subtitleGroups() { return this._subtitleGroups; } addGroupId(type, groupId) { if (!groupId) { return; } if (type === 'audio') { let audioGroups = this._audioGroups; if (!audioGroups) { audioGroups = this._audioGroups = []; } if (audioGroups.indexOf(groupId) === -1) { audioGroups.push(groupId); } } else if (type === 'text') { let subtitleGroups = this._subtitleGroups; if (!subtitleGroups) { subtitleGroups = this._subtitleGroups = []; } if (subtitleGroups.indexOf(groupId) === -1) { subtitleGroups.push(groupId); } } } // Deprecated methods (retained for backwards compatibility) get urlId() { return 0; } set urlId(value) {} get audioGroupIds() { return this.audioGroups ? [this.audioGroupId] : undefined; } get textGroupIds() { return this.subtitleGroups ? [this.textGroupId] : undefined; } get audioGroupId() { var _this$audioGroups; return (_this$audioGroups = this.audioGroups) == null ? void 0 : _this$audioGroups[0]; } get textGroupId() { var _this$subtitleGroups; return (_this$subtitleGroups = this.subtitleGroups) == null ? void 0 : _this$subtitleGroups[0]; } addFallback() {} } function hasGroup(groups, groupId) { if (!groupId || !groups) { return false; } return groups.indexOf(groupId) !== -1; } function updateFromToPTS(fragFrom, fragTo) { const fragToPTS = fragTo.startPTS; // if we know startPTS[toIdx] if (isFiniteNumber(fragToPTS)) { // update fragment duration. // it helps to fix drifts between playlist reported duration and fragment real duration let duration = 0; let frag; if (fragTo.sn > fragFrom.sn) { duration = fragToPTS - fragFrom.start; frag = fragFrom; } else { duration = fragFrom.start - fragToPTS; frag = fragTo; } if (frag.duration !== duration) { frag.duration = duration; } // we dont know startPTS[toIdx] } else if (fragTo.sn > fragFrom.sn) { const contiguous = fragFrom.cc === fragTo.cc; // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS if (contiguous && fragFrom.minEndPTS) { fragTo.start = fragFrom.start + (fragFrom.minEndPTS - fragFrom.start); } else { fragTo.start = fragFrom.start + fragFrom.duration; } } else { fragTo.start = Math.max(fragFrom.start - fragTo.duration, 0); } } function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) { const parsedMediaDuration = endPTS - startPTS; if (parsedMediaDuration <= 0) { logger.warn('Fragment should have a positive duration', frag); endPTS = startPTS + frag.duration; endDTS = startDTS + frag.duration; } let maxStartPTS = startPTS; let minEndPTS = endPTS; const fragStartPts = frag.startPTS; const fragEndPts = frag.endPTS; if (isFiniteNumber(fragStartPts)) { // delta PTS between audio and video const deltaPTS = Math.abs(fragStartPts - startPTS); if (!isFiniteNumber(frag.deltaPTS)) { frag.deltaPTS = deltaPTS; } else { frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS); } maxStartPTS = Math.max(startPTS, fragStartPts); startPTS = Math.min(startPTS, fragStartPts); startDTS = Math.min(startDTS, frag.startDTS); minEndPTS = Math.min(endPTS, fragEndPts); endPTS = Math.max(endPTS, fragEndPts); endDTS = Math.max(endDTS, frag.endDTS); } const drift = startPTS - frag.start; if (frag.start !== 0) { frag.start = startPTS; } frag.duration = endPTS - frag.start; frag.startPTS = startPTS; frag.maxStartPTS = maxStartPTS; frag.startDTS = startDTS; frag.endPTS = endPTS; frag.minEndPTS = minEndPTS; frag.endDTS = endDTS; const sn = frag.sn; // 'initSegment' // exit if sn out of range if (!details || sn < details.startSN || sn > details.endSN) { return 0; } let i; const fragIdx = sn - details.startSN; const fragments = details.fragments; // update frag reference in fragments array // rationale is that fragments array might not contain this frag object. // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS() // if we don't update frag, we won't be able to propagate PTS info on the playlist // resulting in invalid sliding computation fragments[fragIdx] = frag; // adjust fragment PTS/duration from seqnum-1 to frag 0 for (i = fragIdx; i > 0; i--) { updateFromToPTS(fragments[i], fragments[i - 1]); } // adjust fragment PTS/duration from seqnum to last frag for (i = fragIdx; i < fragments.length - 1; i++) { updateFromToPTS(fragments[i], fragments[i + 1]); } if (details.fragmentHint) { updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint); } details.PTSKnown = details.alignedSliding = true; return drift; } function mergeDetails(oldDetails, newDetails) { // Track the last initSegment processed. Initialize it to the last one on the timeline. let currentInitSegment = null; const oldFragments = oldDetails.fragments; for (let i = oldFragments.length - 1; i >= 0; i--) { const oldInit = oldFragments[i].initSegment; if (oldInit) { currentInitSegment = oldInit; break; } } if (oldDetails.fragmentHint) { // prevent PTS and duration from being adjusted on the next hint delete oldDetails.fragmentHint.endPTS; } // check if old/new playlists have fragments in common // loop through overlapping SN and update startPTS , cc, and duration if any found let ccOffset = 0; let PTSFrag; mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => { if (oldFrag.relurl) { // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts. // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end // of the playlist. ccOffset = oldFrag.cc - newFrag.cc; } if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) { newFrag.start = newFrag.startPTS = oldFrag.startPTS; newFrag.startDTS = oldFrag.startDTS; newFrag.maxStartPTS = oldFrag.maxStartPTS; newFrag.endPTS = oldFrag.endPTS; newFrag.endDTS = oldFrag.endDTS; newFrag.minEndPTS = oldFrag.minEndPTS; newFrag.duration = oldFrag.endPTS - oldFrag.startPTS; if (newFrag.duration) { PTSFrag = newFrag; } // PTS is known when any segment has startPTS and endPTS newDetails.PTSKnown = newDetails.alignedSliding = true; } newFrag.elementaryStreams = oldFrag.elementaryStreams; newFrag.loader = oldFrag.loader; newFrag.stats = oldFrag.stats; if (oldFrag.initSegment) { newFrag.initSegment = oldFrag.initSegment; currentInitSegment = oldFrag.initSegment; } }); if (currentInitSegment) { const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments; fragmentsToCheck.forEach(frag => { var _currentInitSegment; if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) { frag.initSegment = currentInitSegment; } }); } if (newDetails.skippedSegments) { newDetails.deltaUpdateFailed = newDetails.fragments.some(frag => !frag); if (newDetails.deltaUpdateFailed) { logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist'); for (let i = newDetails.skippedSegments; i--;) { newDetails.fragments.shift(); } newDetails.startSN = newDetails.fragments[0].sn; newDetails.startCC = newDetails.fragments[0].cc; } else if (newDetails.canSkipDateRanges) { newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges); } } const newFragments = newDetails.fragments; if (ccOffset) { logger.warn('discontinuity sliding from playlist, take drift into account'); for (let i = 0; i < newFragments.length; i++) { newFragments[i].cc += ccOffset; } } if (newDetails.skippedSegments) { newDetails.startCC = newDetails.fragments[0].cc; } // Merge parts mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => { newPart.elementaryStreams = oldPart.elementaryStreams; newPart.stats = oldPart.stats; }); // if at least one fragment contains PTS info, recompute PTS information for all fragments if (PTSFrag) { updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS); } else { // ensure that delta is within oldFragments range // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61]) // in that case we also need to adjust start offset of all fragments adjustSliding(oldDetails, newDetails); } if (newFragments.length) { newDetails.totalduration = newDetails.edge - newFragments[0].start; } newDetails.driftStartTime = oldDetails.driftStartTime; newDetails.driftStart = oldDetails.driftStart; const advancedDateTime = newDetails.advancedDateTime; if (newDetails.advanced && advancedDateTime) { const edge = newDetails.edge; if (!newDetails.driftStart) { newDetails.driftStartTime = advancedDateTime; newDetails.driftStart = edge; } newDetails.driftEndTime = advancedDateTime; newDetails.driftEnd = edge; } else { newDetails.driftEndTime = oldDetails.driftEndTime; newDetails.driftEnd = oldDetails.driftEnd; newDetails.advancedDateTime = oldDetails.advancedDateTime; } } function mergeDateRanges(oldDateRanges, deltaDateRanges, recentlyRemovedDateranges) { const dateRanges = _extends({}, oldDateRanges); if (recentlyRemovedDateranges) { recentlyRemovedDateranges.forEach(id => { delete dateRanges[id]; }); } Object.keys(deltaDateRanges).forEach(id => { const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]); if (dateRange.isValid) { dateRanges[id] = dateRange; } else { logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(deltaDateRanges[id].attr)}"`); } }); return dateRanges; } function mapPartIntersection(oldParts, newParts, intersectionFn) { if (oldParts && newParts) { let delta = 0; for (let i = 0, len = oldParts.length; i <= len; i++) { const oldPart = oldParts[i]; const newPart = newParts[i + delta]; if (oldPart && newPart && oldPart.index === newPart.index && oldPart.fragment.sn === newPart.fragment.sn) { intersectionFn(oldPart, newPart); } else { delta--; } } } } function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) { const skippedSegments = newDetails.skippedSegments; const start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN; const end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN; const delta = newDetails.startSN - oldDetails.startSN; const newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments; const oldFrags = oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments; for (let i = start; i <= end; i++) { const oldFrag = oldFrags[delta + i]; let newFrag = newFrags[i]; if (skippedSegments && !newFrag && i < skippedSegments) { // Fill in skipped segments in delta playlist newFrag = newDetails.fragments[i] = oldFrag; } if (oldFrag && newFrag) { intersectionFn(oldFrag, newFrag); } } } function adjustSliding(oldDetails, newDetails) { const delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN; const oldFragments = oldDetails.fragments; if (delta < 0 || delta >= oldFragments.length) { return; } addSliding(newDetails, oldFragments[delta].start); } function addSliding(details, start) { if (start) { const fragments = details.fragments; for (let i = details.skippedSegments; i < fragments.length; i++) { fragments[i].start += start; } if (details.fragmentHint) { details.fragmentHint.start += start; } } } function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) { let reloadInterval = 1000 * newDetails.targetduration; if (newDetails.updated) { // Use last segment duration when shorter than target duration and near live edge const fragments = newDetails.fragments; const liveEdgeMaxTargetDurations = 4; if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) { const lastSegmentDuration = fragments[fragments.length - 1].duration * 1000; if (lastSegmentDuration < reloadInterval) { reloadInterval = lastSegmentDuration; } } } else { // estimate = 'miss half average'; // follow HLS Spec, If the client reloads a Playlist file and finds that it has not // changed then it MUST wait for a period of one-half the target // duration before retrying. reloadInterval /= 2; } return Math.round(reloadInterval); } function getFragmentWithSN(level, sn, fragCurrent) { if (!(level != null && level.details)) { return null; } const levelDetails = level.details; let fragment = levelDetails.fragments[sn - levelDetails.startSN]; if (fragment) { return fragment; } fragment = levelDetails.fragmentHint; if (fragment && fragment.sn === sn) { return fragment; } if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) { return fragCurrent; } return null; } function getPartWith(level, sn, partIndex) { var _level$details; if (!(level != null && level.details)) { return null; } return findPart((_level$details = level.details) == null ? void 0 : _level$details.partList, sn, partIndex); } function findPart(partList, sn, partIndex) { if (partList) { for (let i = partList.length; i--;) { const part = partList[i]; if (part.index === partIndex && part.fragment.sn === sn) { return part; } } } return null; } function reassignFragmentLevelIndexes(levels) { levels.forEach((level, index) => { const { details } = level; if (details != null && details.fragments) { details.fragments.forEach(fragment => { fragment.level = index; }); } }); } function isTimeoutError(error) { switch (error.details) { case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_TIMEOUT: case ErrorDetails.LEVEL_LOAD_TIMEOUT: case ErrorDetails.MANIFEST_LOAD_TIMEOUT: return true; } return false; } function getRetryConfig(loadPolicy, error) { const isTimeout = isTimeoutError(error); return loadPolicy.default[`${isTimeout ? 'timeout' : 'error'}Retry`]; } function getRetryDelay(retryConfig, retryCount) { // exponential backoff capped to max retry delay const backoffFactor = retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount); return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs); } function getLoaderConfigWithoutReties(loderConfig) { return _objectSpread2(_objectSpread2({}, loderConfig), { errorRetry: null, timeoutRetry: null }); } function shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse) { if (!retryConfig) { return false; } const httpStatus = loaderResponse == null ? void 0 : loaderResponse.code; const retry = retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout); return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry) : retry; } function retryForHttpStatus(httpStatus) { // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error) return httpStatus === 0 && navigator.onLine === false || !!httpStatus && (httpStatus < 400 || httpStatus > 499); } const BinarySearch = { /** * Searches for an item in an array which matches a certain condition. * This requires the condition to only match one item in the array, * and for the array to be ordered. * * @param list The array to search. * @param comparisonFn * Called and provided a candidate item as the first argument. * Should return: * > -1 if the item should be located at a lower index than the provided item. * > 1 if the item should be located at a higher index than the provided item. * > 0 if the item is the item you're looking for. * * @returns the object if found, otherwise returns null */ search: function (list, comparisonFn) { let minIndex = 0; let maxIndex = list.length - 1; let currentIndex = null; let currentElement = null; while (minIndex <= maxIndex) { currentIndex = (minIndex + maxIndex) / 2 | 0; currentElement = list[currentIndex]; const comparisonResult = comparisonFn(currentElement); if (comparisonResult > 0) { minIndex = currentIndex + 1; } else if (comparisonResult < 0) { maxIndex = currentIndex - 1; } else { return currentElement; } } return null; } }; /** * Returns first fragment whose endPdt value exceeds the given PDT, or null. * @param fragments - The array of candidate fragments * @param PDTValue - The PDT value which must be exceeded * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous */ function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) { if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) { return null; } // if less than start const startPDT = fragments[0].programDateTime; if (PDTValue < (startPDT || 0)) { return null; } const endPDT = fragments[fragments.length - 1].endProgramDateTime; if (PDTValue >= (endPDT || 0)) { return null; } maxFragLookUpTolerance = maxFragLookUpTolerance || 0; for (let seg = 0; seg < fragments.length; ++seg) { const frag = fragments[seg]; if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) { return frag; } } return null; } /** * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer. * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus * breaking any traps which would cause the same fragment to be continuously selected within a small range. * @param fragPrevious - The last frag successfully appended * @param fragments - The array of candidate fragments * @param bufferEnd - The end of the contiguous buffered range the playhead is currently within * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous * @returns a matching fragment or null */ function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0, nextFragLookupTolerance = 0.005) { let fragNext = null; if (fragPrevious) { fragNext = fragments[fragPrevious.sn - fragments[0].sn + 1] || null; // check for buffer-end rounding error const bufferEdgeError = fragPrevious.endDTS - bufferEnd; if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) { bufferEnd += 0.0000015; } } else if (bufferEnd === 0 && fragments[0].start === 0) { fragNext = fragments[0]; } // Prefer the next fragment if it's within tolerance if (fragNext && ((!fragPrevious || fragPrevious.level === fragNext.level) && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0 || fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))) { return fragNext; } // We might be seeking past the tolerance so find the best match const foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance)); if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) { return foundFragment; } // If no match was found return the next fragment after fragPrevious, or null return fragNext; } function fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance) { if (fragPrevious && fragPrevious.start === 0 && fragPrevious.level < fragNext.level && (fragPrevious.endPTS || 0) > 0) { const firstDuration = fragPrevious.tagList.reduce((duration, tag) => { if (tag[0] === 'INF') { duration += parseFloat(tag[1]); } return duration; }, nextFragLookupTolerance); return fragNext.start <= firstDuration; } return false; } /** * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions. * @param candidate - The fragment to test * @param bufferEnd - The end of the current buffered range the playhead is currently within * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous * @returns 0 if it matches, 1 if too low, -1 if too high */ function fragmentWithinToleranceTest(bufferEnd = 0, maxFragLookUpTolerance = 0, candidate) { // eagerly accept an accurate match (no tolerance) if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) { return 0; } // offset should be within fragment boundary - config.maxFragLookUpTolerance // this is to cope with situations like // bufferEnd = 9.991 // frag[Ø] : [0,10] // frag[1] : [10,20] // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here // frag start frag start+duration // |-----------------------------| // <---> <---> // ...--------><-----------------------------><---------.... // previous frag matching fragment next frag // return -1 return 0 return 1 // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`); // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)); if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) { return 1; } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) { // if maxFragLookUpTolerance will have negative value then don't return -1 for first element return -1; } return 0; } /** * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions. * This function tests the candidate's program date time values, as represented in Unix time * @param candidate - The fragment to test * @param pdtBufferEnd - The Unix time representing the end of the current buffered range * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous * @returns true if contiguous, false otherwise */ function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) { const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000; // endProgramDateTime can be null, default to zero const endProgramDateTime = candidate.endProgramDateTime || 0; return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd; } function findFragWithCC(fragments, cc) { return BinarySearch.search(fragments, candidate => { if (candidate.cc < cc) { return 1; } else if (candidate.cc > cc) { return -1; } else { return 0; } }); } var NetworkErrorAction = { DoNothing: 0, SendEndCallback: 1, SendAlternateToPenaltyBox: 2, RemoveAlternatePermanently: 3, InsertDiscontinuity: 4, RetryRequest: 5 }; var ErrorActionFlags = { None: 0, MoveAllAlternatesMatchingHost: 1, MoveAllAlternatesMatchingHDCP: 2, SwitchToSDR: 4 }; // Reserved for future use class ErrorController { constructor(hls) { this.hls = void 0; this.playlistError = 0; this.penalizedRenditions = {}; this.log = void 0; this.warn = void 0; this.error = void 0; this.hls = hls; this.log = logger.log.bind(logger, `[info]:`); this.warn = logger.warn.bind(logger, `[warning]:`); this.error = logger.error.bind(logger, `[error]:`); this.registerListeners(); } registerListeners() { const hls = this.hls; hls.on(Events.ERROR, this.onError, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } unregisterListeners() { const hls = this.hls; if (!hls) { return; } hls.off(Events.ERROR, this.onError, this); hls.off(Events.ERROR, this.onErrorOut, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); } destroy() { this.unregisterListeners(); // @ts-ignore this.hls = null; this.penalizedRenditions = {}; } startLoad(startPosition) {} stopLoad() { this.playlistError = 0; } getVariantLevelIndex(frag) { return (frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN ? frag.level : this.hls.loadLevel; } onManifestLoading() { this.playlistError = 0; this.penalizedRenditions = {}; } onLevelUpdated() { this.playlistError = 0; } onError(event, data) { var _data$frag, _data$level; if (data.fatal) { return; } const hls = this.hls; const context = data.context; switch (data.details) { case ErrorDetails.FRAG_LOAD_ERROR: case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_ERROR: case ErrorDetails.KEY_LOAD_TIMEOUT: data.errorAction = this.getFragRetryOrSwitchAction(data); return; case ErrorDetails.FRAG_PARSING_ERROR: // ignore empty segment errors marked as gap if ((_data$frag = data.frag) != null && _data$frag.gap) { data.errorAction = { action: NetworkErrorAction.DoNothing, flags: ErrorActionFlags.None }; return; } // falls through case ErrorDetails.FRAG_GAP: case ErrorDetails.FRAG_DECRYPT_ERROR: { // Switch level if possible, otherwise allow retry count to reach max error retries data.errorAction = this.getFragRetryOrSwitchAction(data); data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox; return; } case ErrorDetails.LEVEL_EMPTY_ERROR: case ErrorDetails.LEVEL_PARSING_ERROR: { var _data$context, _data$context$levelDe; // Only retry when empty and live const levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel; if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context$levelDe = _data$context.levelDetails) != null && _data$context$levelDe.live)) { data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex); } else { // Escalate to fatal if not retrying or switching data.levelRetry = false; data.errorAction = this.getLevelSwitchAction(data, levelIndex); } } return; case ErrorDetails.LEVEL_LOAD_ERROR: case ErrorDetails.LEVEL_LOAD_TIMEOUT: if (typeof (context == null ? void 0 : context.level) === 'number') { data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level); } return; case ErrorDetails.AUDIO_TRACK_LOAD_ERROR: case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: case ErrorDetails.SUBTITLE_LOAD_ERROR: case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT: if (context) { const level = hls.levels[hls.loadLevel]; if (level && (context.type === PlaylistContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === PlaylistContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) { // Perform Pathway switch or Redundant failover if possible for fastest recovery // otherwise allow playlist retry count to reach max error retries data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel); data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox; data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost; return; } } return; case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED: { const level = hls.levels[hls.loadLevel]; const restrictedHdcpLevel = level == null ? void 0 : level.attrs['HDCP-LEVEL']; if (restrictedHdcpLevel) { data.errorAction = { action: NetworkErrorAction.SendAlternateToPenaltyBox, flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP, hdcpLevel: restrictedHdcpLevel }; } else { this.keySystemError(data); } } return; case ErrorDetails.BUFFER_ADD_CODEC_ERROR: case ErrorDetails.REMUX_ALLOC_ERROR: case ErrorDetails.BUFFER_APPEND_ERROR: data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel); return; case ErrorDetails.INTERNAL_EXCEPTION: case ErrorDetails.BUFFER_APPENDING_ERROR: case ErrorDetails.BUFFER_FULL_ERROR: case ErrorDetails.LEVEL_SWITCH_ERROR: case ErrorDetails.BUFFER_STALLED_ERROR: case ErrorDetails.BUFFER_SEEK_OVER_HOLE: case ErrorDetails.BUFFER_NUDGE_ON_STALL: data.errorAction = { action: NetworkErrorAction.DoNothing, flags: ErrorActionFlags.None }; return; } if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) { this.keySystemError(data); } } keySystemError(data) { const levelIndex = this.getVariantLevelIndex(data.frag); // Do not retry level. Escalate to fatal if switching levels fails. data.levelRetry = false; data.errorAction = this.getLevelSwitchAction(data, levelIndex); } getPlaylistRetryOrSwitchAction(data, levelIndex) { const hls = this.hls; const retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data); const retryCount = this.playlistError++; const retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response); if (retry) { return { action: NetworkErrorAction.RetryRequest, flags: ErrorActionFlags.None, retryConfig, retryCount }; } const errorAction = this.getLevelSwitchAction(data, levelIndex); if (retryConfig) { errorAction.retryConfig = retryConfig; errorAction.retryCount = retryCount; } return errorAction; } getFragRetryOrSwitchAction(data) { const hls = this.hls; // Share fragment error count accross media options (main, audio, subs) // This allows for level based rendition switching when media option assets fail const variantLevelIndex = this.getVariantLevelIndex(data.frag); const level = hls.levels[variantLevelIndex]; const { fragLoadPolicy, keyLoadPolicy } = hls.config; const retryConfig = getRetryConfig(data.details.startsWith('key') ? keyLoadPolicy : fragLoadPolicy, data); const fragmentErrors = hls.levels.reduce((acc, level) => acc + level.fragmentError, 0); // Switch levels when out of retried or level index out of bounds if (level) { if (data.details !== ErrorDetails.FRAG_GAP) { level.fragmentError++; } const retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response); if (retry) { return { action: NetworkErrorAction.RetryRequest, flags: ErrorActionFlags.None, retryConfig, retryCount: fragmentErrors }; } } // Reach max retry count, or Missing level reference // Switch to valid index const errorAction = this.getLevelSwitchAction(data, variantLevelIndex); // Add retry details to allow skipping of FRAG_PARSING_ERROR if (retryConfig) { errorAction.retryConfig = retryConfig; errorAction.retryCount = fragmentErrors; } return errorAction; } getLevelSwitchAction(data, levelIndex) { const hls = this.hls; if (levelIndex === null || levelIndex === undefined) { levelIndex = hls.loadLevel; } const level = this.hls.levels[levelIndex]; if (level) { var _data$frag2, _data$context2; const errorDetails = data.details; level.loadError++; if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) { level.fragmentError++; } // Search for next level to retry let nextLevel = -1; const { levels, loadLevel, minAutoLevel, maxAutoLevel } = hls; if (!hls.autoLevelEnabled) { hls.loadLevel = -1; } const fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type; // Find alternate audio codec if available on audio codec error const isAudioCodecError = fragErrorType === PlaylistLevelType.AUDIO && errorDetails === ErrorDetails.FRAG_PARSING_ERROR || data.sourceBufferName === 'audio' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR); const findAudioCodecAlternate = isAudioCodecError && levels.some(({ audioCodec }) => level.audioCodec !== audioCodec); // Find alternate video codec if available on video codec error const isVideoCodecError = data.sourceBufferName === 'video' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR); const findVideoCodecAlternate = isVideoCodecError && levels.some(({ codecSet, audioCodec }) => level.codecSet !== codecSet && level.audioCodec === audioCodec); const { type: playlistErrorType, groupId: playlistErrorGroupId } = (_data$context2 = data.context) != null ? _data$context2 : {}; for (let i = levels.length; i--;) { const candidate = (i + loadLevel) % levels.length; if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && levels[candidate].loadError === 0) { var _level$audioGroups, _level$subtitleGroups; const levelCandidate = levels[candidate]; // Skip level switch if GAP tag is found in next level at same position if (errorDetails === ErrorDetails.FRAG_GAP && fragErrorType === PlaylistLevelType.MAIN && data.frag) { const levelDetails = levels[candidate].details; if (levelDetails) { const fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start); if (fragCandidate != null && fragCandidate.gap) { continue; } } } else if (playlistErrorType === PlaylistContextType.AUDIO_TRACK && levelCandidate.hasAudioGroup(playlistErrorGroupId) || playlistErrorType === PlaylistContextType.SUBTITLE_TRACK && levelCandidate.hasSubtitleGroup(playlistErrorGroupId)) { // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over continue; } else if (fragErrorType === PlaylistLevelType.AUDIO && (_level$audioGroups = level.audioGroups) != null && _level$audioGroups.some(groupId => levelCandidate.hasAudioGroup(groupId)) || fragErrorType === PlaylistLevelType.SUBTITLE && (_level$subtitleGroups = level.subtitleGroups) != null && _level$subtitleGroups.some(groupId => levelCandidate.hasSubtitleGroup(groupId)) || findAudioCodecAlternate && level.audioCodec === levelCandidate.audioCodec || !findAudioCodecAlternate && level.audioCodec !== levelCandidate.audioCodec || findVideoCodecAlternate && level.codecSet === levelCandidate.codecSet) { // For video/audio/subs frag errors find another group ID or fallthrough to redundant fail-over continue; } nextLevel = candidate; break; } } if (nextLevel > -1 && hls.loadLevel !== nextLevel) { data.levelRetry = true; this.playlistError = 0; return { action: NetworkErrorAction.SendAlternateToPenaltyBox, flags: ErrorActionFlags.None, nextAutoLevel: nextLevel }; } } // No levels to switch / Manual level selection / Level not found // Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level return { action: NetworkErrorAction.SendAlternateToPenaltyBox, flags: ErrorActionFlags.MoveAllAlternatesMatchingHost }; } onErrorOut(event, data) { var _data$errorAction; switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) { case NetworkErrorAction.DoNothing: break; case NetworkErrorAction.SendAlternateToPenaltyBox: this.sendAlternateToPenaltyBox(data); if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP) { data.fatal = true; } else if (/MediaSource readyState: ended/.test(data.error.message)) { this.warn(`MediaSource ended after "${data.sourceBufferName}" sourceBuffer append error. Attempting to recover from media error.`); this.hls.recoverMediaError(); } break; case NetworkErrorAction.RetryRequest: // handled by stream and playlist/level controllers break; } if (data.fatal) { this.hls.stopLoad(); return; } } sendAlternateToPenaltyBox(data) { const hls = this.hls; const errorAction = data.errorAction; if (!errorAction) { return; } const { flags, hdcpLevel, nextAutoLevel } = errorAction; switch (flags) { case ErrorActionFlags.None: this.switchLevel(data, nextAutoLevel); break; case ErrorActionFlags.MoveAllAlternatesMatchingHDCP: if (hdcpLevel) { hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(hdcpLevel) - 1]; errorAction.resolved = true; } this.warn(`Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`); break; } // If not resolved by previous actions try to switch to next level if (!errorAction.resolved) { this.switchLevel(data, nextAutoLevel); } } switchLevel(data, levelIndex) { if (levelIndex !== undefined && data.errorAction) { this.warn(`switching to level ${levelIndex} after ${data.details}`); this.hls.nextAutoLevel = levelIndex; data.errorAction.resolved = true; // Stream controller is responsible for this but won't switch on false start this.hls.nextLoadLevel = this.hls.nextAutoLevel; } } } class BasePlaylistController { constructor(hls, logPrefix) { this.hls = void 0; this.timer = -1; this.requestScheduled = -1; this.canLoad = false; this.log = void 0; this.warn = void 0; this.log = logger.log.bind(logger, `${logPrefix}:`); this.warn = logger.warn.bind(logger, `${logPrefix}:`); this.hls = hls; } destroy() { this.clearTimer(); // @ts-ignore this.hls = this.log = this.warn = null; } clearTimer() { if (this.timer !== -1) { self.clearTimeout(this.timer); this.timer = -1; } } startLoad() { this.canLoad = true; this.requestScheduled = -1; this.loadPlaylist(); } stopLoad() { this.canLoad = false; this.clearTimer(); } switchParams(playlistUri, previous, current) { const renditionReports = previous == null ? void 0 : previous.renditionReports; if (renditionReports) { let foundIndex = -1; for (let i = 0; i < renditionReports.length; i++) { const attr = renditionReports[i]; let uri; try { uri = new self.URL(attr.URI, previous.url).href; } catch (error) { logger.warn(`Could not construct new URL for Rendition Report: ${error}`); uri = attr.URI || ''; } // Use exact match. Otherwise, the last partial match, if any, will be used // (Playlist URI includes a query string that the Rendition Report does not) if (uri === playlistUri) { foundIndex = i; break; } else if (uri === playlistUri.substring(0, uri.length)) { foundIndex = i; } } if (foundIndex !== -1) { const attr = renditionReports[foundIndex]; const msn = parseInt(attr['LAST-MSN']) || (previous == null ? void 0 : previous.lastPartSn); let part = parseInt(attr['LAST-PART']) || (previous == null ? void 0 : previous.lastPartIndex); if (this.hls.config.lowLatencyMode) { const currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration); if (part >= 0 && currentGoal > previous.partTarget) { part += 1; } } const skip = current && getSkipValue(current); return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip); } } } loadPlaylist(hlsUrlParameters) { if (this.requestScheduled === -1) { this.requestScheduled = self.performance.now(); } // Loading is handled by the subclasses } shouldLoadPlaylist(playlist) { return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live); } shouldReloadPlaylist(playlist) { return this.timer === -1 && this.requestScheduled === -1 && this.shouldLoadPlaylist(playlist); } playlistLoaded(index, data, previousDetails) { const { details, stats } = data; // Set last updated date-time const now = self.performance.now(); const elapsed = stats.loading.first ? Math.max(0, now - stats.loading.first) : 0; details.advancedDateTime = Date.now() - elapsed; // if current playlist is a live playlist, arm a timer to reload it if (details.live || previousDetails != null && previousDetails.live) { details.reloaded(previousDetails); if (previousDetails) { this.log(`live playlist ${index} ${details.advanced ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex : details.updated ? 'UPDATED' : 'MISSED'}`); } // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments if (previousDetails && details.fragments.length > 0) { mergeDetails(previousDetails, details); } if (!this.canLoad || !details.live) { return; } let deliveryDirectives; let msn = undefined; let part = undefined; if (details.canBlockReload && details.endSN && details.advanced) { // Load level with LL-HLS delivery directives const lowLatencyMode = this.hls.config.lowLatencyMode; const lastPartSn = details.lastPartSn; const endSn = details.endSN; const lastPartIndex = details.lastPartIndex; const hasParts = lastPartIndex !== -1; const lastPart = lastPartSn === endSn; // When low latency mode is disabled, we'll skip part requests once the last part index is found const nextSnStartIndex = lowLatencyMode ? 0 : lastPartIndex; if (hasParts) { msn = lastPart ? endSn + 1 : lastPartSn; part = lastPart ? nextSnStartIndex : lastPartIndex + 1; } else { msn = endSn + 1; } // Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part // Update directives to obtain the Playlist that has the estimated additional duration of media const lastAdvanced = details.age; const cdnAge = lastAdvanced + details.ageHeader; let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5); if (currentGoal > 0) { if (previousDetails && currentGoal > previousDetails.tuneInGoal) { // If we attempted to get the next or latest playlist update, but currentGoal increased, // then we either can't catchup, or the "age" header cannot be trusted. this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`); currentGoal = 0; } else { const segments = Math.floor(currentGoal / details.targetduration); msn += segments; if (part !== undefined) { const parts = Math.round(currentGoal % details.targetduration / details.partTarget); part += parts; } this.log(`CDN Tune-in age: ${details.ageHeader}s last advanced ${lastAdvanced.toFixed(2)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`); } details.tuneInGoal = currentGoal; } deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part); if (lowLatencyMode || !lastPart) { this.loadPlaylist(deliveryDirectives); return; } } else if (details.canBlockReload || details.canSkipUntil) { deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part); } const bufferInfo = this.hls.mainForwardBufferInfo; const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0; const distanceToLiveEdgeMs = (details.edge - position) * 1000; const reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs); if (details.updated && now > this.requestScheduled + reloadInterval) { this.requestScheduled = stats.loading.start; } if (msn !== undefined && details.canBlockReload) { this.requestScheduled = stats.loading.first + reloadInterval - (details.partTarget * 1000 || 1000); } else if (this.requestScheduled === -1 || this.requestScheduled + reloadInterval < now) { this.requestScheduled = now; } else if (this.requestScheduled - now <= 0) { this.requestScheduled += reloadInterval; } let estimatedTimeUntilUpdate = this.requestScheduled - now; estimatedTimeUntilUpdate = Math.max(0, estimatedTimeUntilUpdate); this.log(`reload live playlist ${index} in ${Math.round(estimatedTimeUntilUpdate)} ms`); // this.log( // `live reload ${details.updated ? 'REFRESHED' : 'MISSED'} // reload in ${estimatedTimeUntilUpdate / 1000} // round trip ${(stats.loading.end - stats.loading.start) / 1000} // diff ${ // (reloadInterval - // (estimatedTimeUntilUpdate + // stats.loading.end - // stats.loading.start)) / // 1000 // } // reload interval ${reloadInterval / 1000} // target duration ${details.targetduration} // distance to edge ${distanceToLiveEdgeMs / 1000}` // ); this.timer = self.setTimeout(() => this.loadPlaylist(deliveryDirectives), estimatedTimeUntilUpdate); } else { this.clearTimer(); } } getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) { let skip = getSkipValue(details); if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) { msn = previousDeliveryDirectives.msn; part = previousDeliveryDirectives.part; skip = HlsSkip.No; } return new HlsUrlParameters(msn, part, skip); } checkRetry(errorEvent) { const errorDetails = errorEvent.details; const isTimeout = isTimeoutError(errorEvent); const errorAction = errorEvent.errorAction; const { action, retryCount = 0, retryConfig } = errorAction || {}; const retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox); if (retry) { var _errorEvent$context; this.requestScheduled = -1; if (retryCount >= retryConfig.maxNumRetry) { return false; } if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) { // The LL-HLS request already timed out so retry immediately this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" without delivery-directives`); this.loadPlaylist(); } else { const delay = getRetryDelay(retryConfig, retryCount); // Schedule level/track reload this.timer = self.setTimeout(() => this.loadPlaylist(), delay); this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" in ${delay}ms`); } // `levelRetry = true` used to inform other controllers that a retry is happening errorEvent.levelRetry = true; errorAction.resolved = true; } return retry; } } /* * compute an Exponential Weighted moving average * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average * - heavily inspired from shaka-player */ class EWMA { // About half of the estimated value will be from the last |halfLife| samples by weight. constructor(halfLife, estimate = 0, weight = 0) { this.halfLife = void 0; this.alpha_ = void 0; this.estimate_ = void 0; this.totalWeight_ = void 0; this.halfLife = halfLife; // Larger values of alpha expire historical data more slowly. this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0; this.estimate_ = estimate; this.totalWeight_ = weight; } sample(weight, value) { const adjAlpha = Math.pow(this.alpha_, weight); this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_; this.totalWeight_ += weight; } getTotalWeight() { return this.totalWeight_; } getEstimate() { if (this.alpha_) { const zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_); if (zeroFactor) { return this.estimate_ / zeroFactor; } } return this.estimate_; } } /* * EWMA Bandwidth Estimator * - heavily inspired from shaka-player * Tracks bandwidth samples and estimates available bandwidth. * Based on the minimum of two exponentially-weighted moving averages with * different half-lives. */ class EwmaBandWidthEstimator { constructor(slow, fast, defaultEstimate, defaultTTFB = 100) { this.defaultEstimate_ = void 0; this.minWeight_ = void 0; this.minDelayMs_ = void 0; this.slow_ = void 0; this.fast_ = void 0; this.defaultTTFB_ = void 0; this.ttfb_ = void 0; this.defaultEstimate_ = defaultEstimate; this.minWeight_ = 0.001; this.minDelayMs_ = 50; this.slow_ = new EWMA(slow); this.fast_ = new EWMA(fast); this.defaultTTFB_ = defaultTTFB; this.ttfb_ = new EWMA(slow); } update(slow, fast) { const { slow_, fast_, ttfb_ } = this; if (slow_.halfLife !== slow) { this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight()); } if (fast_.halfLife !== fast) { this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight()); } if (ttfb_.halfLife !== slow) { this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight()); } } sample(durationMs, numBytes) { durationMs = Math.max(durationMs, this.minDelayMs_); const numBits = 8 * numBytes; // weight is duration in seconds const durationS = durationMs / 1000; // value is bandwidth in bits/s const bandwidthInBps = numBits / durationS; this.fast_.sample(durationS, bandwidthInBps); this.slow_.sample(durationS, bandwidthInBps); } sampleTTFB(ttfb) { // weight is frequency curve applied to TTFB in seconds // (longer times have less weight with expected input under 1 second) const seconds = ttfb / 1000; const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2); this.ttfb_.sample(weight, Math.max(ttfb, 5)); } canEstimate() { return this.fast_.getTotalWeight() >= this.minWeight_; } getEstimate() { if (this.canEstimate()) { // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate())); // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate())); // Take the minimum of these two estimates. This should have the effect of // adapting down quickly, but up more slowly. return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate()); } else { return this.defaultEstimate_; } } getEstimateTTFB() { if (this.ttfb_.getTotalWeight() >= this.minWeight_) { return this.ttfb_.getEstimate(); } else { return this.defaultTTFB_; } } destroy() {} } const SUPPORTED_INFO_DEFAULT = { supported: true, configurations: [], decodingInfoResults: [{ supported: true, powerEfficient: true, smooth: true }] }; const SUPPORTED_INFO_CACHE = {}; function requiresMediaCapabilitiesDecodingInfo(level, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference) { // Only test support when configuration is exceeds minimum options const audioGroups = level.audioCodec ? level.audioGroups : null; const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec; const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels; const maxChannels = channelsPreference ? parseInt(channelsPreference) : audioCodecPreference ? Infinity : 2; let audioChannels = null; if (audioGroups != null && audioGroups.length) { try { if (audioGroups.length === 1 && audioGroups[0]) { audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels; } else { audioChannels = audioGroups.reduce((acc, groupId) => { if (groupId) { const audioTrackGroup = audioTracksByGroup.groups[groupId]; if (!audioTrackGroup) { throw new Error(`Audio track group ${groupId} not found`); } // Sum all channel key values Object.keys(audioTrackGroup.channels).forEach(key => { acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key]; }); } return acc; }, { 2: 0 }); } } catch (error) { return true; } } return level.videoCodec !== undefined && (level.width > 1920 && level.height > 1088 || level.height > 1920 && level.width > 1088 || level.frameRate > Math.max(currentFrameRate, 30) || level.videoRange !== 'SDR' && level.videoRange !== currentVideoRange || level.bitrate > Math.max(currentBw, 8e6)) || !!audioChannels && isFiniteNumber(maxChannels) && Object.keys(audioChannels).some(channels => parseInt(channels) > maxChannels); } function getMediaDecodingInfoPromise(level, audioTracksByGroup, mediaCapabilities) { const videoCodecs = level.videoCodec; const audioCodecs = level.audioCodec; if (!videoCodecs || !audioCodecs || !mediaCapabilities) { return Promise.resolve(SUPPORTED_INFO_DEFAULT); } const baseVideoConfiguration = { width: level.width, height: level.height, bitrate: Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate)), // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0. framerate: level.frameRate || 30 }; const videoRange = level.videoRange; if (videoRange !== 'SDR') { baseVideoConfiguration.transferFunction = videoRange.toLowerCase(); } const configurations = videoCodecs.split(',').map(videoCodec => ({ type: 'media-source', video: _objectSpread2(_objectSpread2({}, baseVideoConfiguration), {}, { contentType: mimeTypeForCodec(videoCodec, 'video') }) })); if (audioCodecs && level.audioGroups) { level.audioGroups.forEach(audioGroupId => { var _audioTracksByGroup$g; if (!audioGroupId) { return; } (_audioTracksByGroup$g = audioTracksByGroup.groups[audioGroupId]) == null ? void 0 : _audioTracksByGroup$g.tracks.forEach(audioTrack => { if (audioTrack.groupId === audioGroupId) { const channels = audioTrack.channels || ''; const channelsNumber = parseFloat(channels); if (isFiniteNumber(channelsNumber) && channelsNumber > 2) { configurations.push.apply(configurations, audioCodecs.split(',').map(audioCodec => ({ type: 'media-source', audio: { contentType: mimeTypeForCodec(audioCodec, 'audio'), channels: '' + channelsNumber // spatialRendering: // audioCodec === 'ec-3' && channels.indexOf('JOC'), } }))); } } }); }); } return Promise.all(configurations.map(configuration => { // Cache MediaCapabilities promises const decodingInfoKey = getMediaDecodingInfoKey(configuration); return SUPPORTED_INFO_CACHE[decodingInfoKey] || (SUPPORTED_INFO_CACHE[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration)); })).then(decodingInfoResults => ({ supported: !decodingInfoResults.some(info => !info.supported), configurations, decodingInfoResults })).catch(error => ({ supported: false, configurations, decodingInfoResults: [], error })); } function getMediaDecodingInfoKey(config) { const { audio, video } = config; const mediaConfig = video || audio; if (mediaConfig) { const codec = mediaConfig.contentType.split('"')[1]; if (video) { return `r${video.height}x${video.width}f${Math.ceil(video.framerate)}${video.transferFunction || 'sd'}_${codec}_${Math.ceil(video.bitrate / 1e5)}`; } if (audio) { return `c${audio.channels}${audio.spatialRendering ? 's' : 'n'}_${codec}`; } } return ''; } /** * @returns Whether we can detect and validate HDR capability within the window context */ function isHdrSupported() { if (typeof matchMedia === 'function') { const mediaQueryList = matchMedia('(dynamic-range: high)'); const badQuery = matchMedia('bad query'); if (mediaQueryList.media !== badQuery.media) { return mediaQueryList.matches === true; } } return false; } /** * Sanitizes inputs to return the active video selection options for HDR/SDR. * When both inputs are null: * * `{ preferHDR: false, allowedVideoRanges: [] }` * * When `currentVideoRange` non-null, maintain the active range: * * `{ preferHDR: currentVideoRange !== 'SDR', allowedVideoRanges: [currentVideoRange] }` * * When VideoSelectionOption non-null: * * - Allow all video ranges if `allowedVideoRanges` unspecified. * - If `preferHDR` is non-null use the value to filter `allowedVideoRanges`. * - Else check window for HDR support and set `preferHDR` to the result. * * @param currentVideoRange * @param videoPreference */ function getVideoSelectionOptions(currentVideoRange, videoPreference) { let preferHDR = false; let allowedVideoRanges = []; if (currentVideoRange) { preferHDR = currentVideoRange !== 'SDR'; allowedVideoRanges = [currentVideoRange]; } if (videoPreference) { allowedVideoRanges = videoPreference.allowedVideoRanges || VideoRangeValues.slice(0); preferHDR = videoPreference.preferHDR !== undefined ? videoPreference.preferHDR : isHdrSupported(); if (preferHDR) { allowedVideoRanges = allowedVideoRanges.filter(range => range !== 'SDR'); } else { allowedVideoRanges = ['SDR']; } } return { preferHDR, allowedVideoRanges }; } function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference) { const codecSets = Object.keys(codecTiers); const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels; const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec; const preferStereo = channelsPreference && parseInt(channelsPreference) === 2; // Use first level set to determine stereo, and minimum resolution and framerate let hasStereo = true; let hasCurrentVideoRange = false; let minHeight = Infinity; let minFramerate = Infinity; let minBitrate = Infinity; let selectedScore = 0; let videoRanges = []; const { preferHDR, allowedVideoRanges } = getVideoSelectionOptions(currentVideoRange, videoPreference); for (let i = codecSets.length; i--;) { const tier = codecTiers[codecSets[i]]; hasStereo = tier.channels[2] > 0; minHeight = Math.min(minHeight, tier.minHeight); minFramerate = Math.min(minFramerate, tier.minFramerate); minBitrate = Math.min(minBitrate, tier.minBitrate); const matchingVideoRanges = allowedVideoRanges.filter(range => tier.videoRanges[range] > 0); if (matchingVideoRanges.length > 0) { hasCurrentVideoRange = true; videoRanges = matchingVideoRanges; } } minHeight = isFiniteNumber(minHeight) ? minHeight : 0; minFramerate = isFiniteNumber(minFramerate) ? minFramerate : 0; const maxHeight = Math.max(1080, minHeight); const maxFramerate = Math.max(30, minFramerate); minBitrate = isFiniteNumber(minBitrate) ? minBitrate : currentBw; currentBw = Math.max(minBitrate, currentBw); // If there are no variants with matching preference, set currentVideoRange to undefined if (!hasCurrentVideoRange) { currentVideoRange = undefined; videoRanges = []; } const codecSet = codecSets.reduce((selected, candidate) => { // Remove candiates which do not meet bitrate, default audio, stereo or channels preference, 1080p or lower, 30fps or lower, or SDR/HDR selection if present const candidateTier = codecTiers[candidate]; if (candidate === selected) { return selected; } if (candidateTier.minBitrate > currentBw) { logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`); return selected; } if (!candidateTier.hasDefaultAudio) { logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`); return selected; } if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) { logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`); return selected; } if (channelsPreference && !preferStereo) { if (!candidateTier.channels[channelsPreference]) { logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`); return selected; } } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) { logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`); return selected; } if (candidateTier.minHeight > maxHeight) { logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`); return selected; } if (candidateTier.minFramerate > maxFramerate) { logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`); return selected; } if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) { logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`); return selected; } if (candidateTier.maxScore < selectedScore) { logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`); return selected; } // Remove candiates with less preferred codecs or more errors if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) { return selected; } selectedScore = candidateTier.maxScore; return candidate; }, undefined); return { codecSet, videoRanges, preferHDR, minFramerate, minBitrate }; } function logStartCodecCandidateIgnored(codeSet, reason) { logger.log(`[abr] start candidates with "${codeSet}" ignored because ${reason}`); } function getAudioTracksByGroup(allAudioTracks) { return allAudioTracks.reduce((audioTracksByGroup, track) => { let trackGroup = audioTracksByGroup.groups[track.groupId]; if (!trackGroup) { trackGroup = audioTracksByGroup.groups[track.groupId] = { tracks: [], channels: { 2: 0 }, hasDefault: false, hasAutoSelect: false }; } trackGroup.tracks.push(track); const channelsKey = track.channels || '2'; trackGroup.channels[channelsKey] = (trackGroup.channels[channelsKey] || 0) + 1; trackGroup.hasDefault = trackGroup.hasDefault || track.default; trackGroup.hasAutoSelect = trackGroup.hasAutoSelect || track.autoselect; if (trackGroup.hasDefault) { audioTracksByGroup.hasDefaultAudio = true; } if (trackGroup.hasAutoSelect) { audioTracksByGroup.hasAutoSelectAudio = true; } return audioTracksByGroup; }, { hasDefaultAudio: false, hasAutoSelectAudio: false, groups: {} }); } function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) { return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce((tiers, level) => { if (!level.codecSet) { return tiers; } const audioGroups = level.audioGroups; let tier = tiers[level.codecSet]; if (!tier) { tiers[level.codecSet] = tier = { minBitrate: Infinity, minHeight: Infinity, minFramerate: Infinity, maxScore: 0, videoRanges: { SDR: 0 }, channels: { '2': 0 }, hasDefaultAudio: !audioGroups, fragmentError: 0 }; } tier.minBitrate = Math.min(tier.minBitrate, level.bitrate); const lesserWidthOrHeight = Math.min(level.height, level.width); tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight); tier.minFramerate = Math.min(tier.minFramerate, level.frameRate); tier.maxScore = Math.max(tier.maxScore, level.score); tier.fragmentError += level.fragmentError; tier.videoRanges[level.videoRange] = (tier.videoRanges[level.videoRange] || 0) + 1; if (audioGroups) { audioGroups.forEach(audioGroupId => { if (!audioGroupId) { return; } const audioGroup = audioTracksByGroup.groups[audioGroupId]; if (!audioGroup) { return; } // Default audio is any group with DEFAULT=YES, or if missing then any group with AUTOSELECT=YES, or all variants tier.hasDefaultAudio = tier.hasDefaultAudio || audioTracksByGroup.hasDefaultAudio ? audioGroup.hasDefault : audioGroup.hasAutoSelect || !audioTracksByGroup.hasDefaultAudio && !audioTracksByGroup.hasAutoSelectAudio; Object.keys(audioGroup.channels).forEach(channels => { tier.channels[channels] = (tier.channels[channels] || 0) + audioGroup.channels[channels]; }); }); } return tiers; }, {}); } function findMatchingOption(option, tracks, matchPredicate) { if ('attrs' in option) { const index = tracks.indexOf(option); if (index !== -1) { return index; } } for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; if (matchesOption(option, track, matchPredicate)) { return i; } } return -1; } function matchesOption(option, track, matchPredicate) { const { groupId, name, lang, assocLang, characteristics, default: isDefault } = option; const forced = option.forced; return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || track.lang === lang) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (characteristics === undefined || characteristicsMatch(characteristics, track.characteristics)) && (matchPredicate === undefined || matchPredicate(option, track)); } function characteristicsMatch(characteristicsA, characteristicsB = '') { const arrA = characteristicsA.split(','); const arrB = characteristicsB.split(','); // Expects each item to be unique: return arrA.length === arrB.length && !arrA.some(el => arrB.indexOf(el) === -1); } function audioMatchPredicate(option, track) { const { audioCodec, channels } = option; return (audioCodec === undefined || (track.audioCodec || '').substring(0, 4) === audioCodec.substring(0, 4)) && (channels === undefined || channels === (track.channels || '2')); } function findClosestLevelWithAudioGroup(option, levels, allAudioTracks, searchIndex, matchPredicate) { const currentLevel = levels[searchIndex]; // Are there variants with same URI as current level? // If so, find a match that does not require any level URI change const variants = levels.reduce((variantMap, level, index) => { const uri = level.uri; const renditions = variantMap[uri] || (variantMap[uri] = []); renditions.push(index); return variantMap; }, {}); const renditions = variants[currentLevel.uri]; if (renditions.length > 1) { searchIndex = Math.max.apply(Math, renditions); } // Find best match const currentVideoRange = currentLevel.videoRange; const currentFrameRate = currentLevel.frameRate; const currentVideoCodec = currentLevel.codecSet.substring(0, 4); const matchingVideo = searchDownAndUpList(levels, searchIndex, level => { if (level.videoRange !== currentVideoRange || level.frameRate !== currentFrameRate || level.codecSet.substring(0, 4) !== currentVideoCodec) { return false; } const audioGroups = level.audioGroups; const tracks = allAudioTracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1); return findMatchingOption(option, tracks, matchPredicate) > -1; }); if (matchingVideo > -1) { return matchingVideo; } return searchDownAndUpList(levels, searchIndex, level => { const audioGroups = level.audioGroups; const tracks = allAudioTracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1); return findMatchingOption(option, tracks, matchPredicate) > -1; }); } function searchDownAndUpList(arr, searchIndex, predicate) { for (let i = searchIndex; i; i--) { if (predicate(arr[i])) { return i; } } for (let i = searchIndex + 1; i < arr.length; i++) { if (predicate(arr[i])) { return i; } } return -1; } class AbrController { constructor(_hls) { this.hls = void 0; this.lastLevelLoadSec = 0; this.lastLoadedFragLevel = -1; this.firstSelection = -1; this._nextAutoLevel = -1; this.nextAutoLevelKey = ''; this.audioTracksByGroup = null; this.codecTiers = null; this.timer = -1; this.fragCurrent = null; this.partCurrent = null; this.bitrateTestDelay = 0; this.bwEstimator = void 0; /* This method monitors the download rate of the current fragment, and will downswitch if that fragment will not load quickly enough to prevent underbuffering */ this._abandonRulesCheck = () => { const { fragCurrent: frag, partCurrent: part, hls } = this; const { autoLevelEnabled, media } = hls; if (!frag || !media) { return; } const now = performance.now(); const stats = part ? part.stats : frag.stats; const duration = part ? part.duration : frag.duration; const timeLoading = now - stats.loading.start; const minAutoLevel = hls.minAutoLevel; // If frag loading is aborted, complete, or from lowest level, stop timer and return if (stats.aborted || stats.loaded && stats.loaded === stats.total || frag.level <= minAutoLevel) { this.clearTimer(); // reset forced auto level value so that next level will be selected this._nextAutoLevel = -1; return; } // This check only runs if we're in ABR mode and actually playing if (!autoLevelEnabled || media.paused || !media.playbackRate || !media.readyState) { return; } const bufferInfo = hls.mainForwardBufferInfo; if (bufferInfo === null) { return; } const ttfbEstimate = this.bwEstimator.getEstimateTTFB(); const playbackRate = Math.abs(media.playbackRate); // To maintain stable adaptive playback, only begin monitoring frag loading after half or more of its playback duration has passed if (timeLoading <= Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))) { return; } // bufferStarvationDelay is an estimate of the amount time (in seconds) it will take to exhaust the buffer const bufferStarvationDelay = bufferInfo.len / playbackRate; const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1; const loadedFirstByte = stats.loaded && ttfb > -1; const bwEstimate = this.getBwEstimate(); const levels = hls.levels; const level = levels[frag.level]; const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8)); let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading; if (timeStreaming < 1 && loadedFirstByte) { timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate); } const loadRate = loadedFirstByte ? stats.loaded * 1000 / timeStreaming : 0; // fragLoadDelay is an estimate of the time (in seconds) it will take to buffer the remainder of the fragment const fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbEstimate / 1000; // Only downswitch if the time to finish loading the current fragment is greater than the amount of buffer left if (fragLoadedDelay <= bufferStarvationDelay) { return; } const bwe = loadRate ? loadRate * 8 : bwEstimate; let fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY; let nextLoadLevel; // Iterate through lower level and try to find the largest one that avoids rebuffering for (nextLoadLevel = frag.level - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) { // compute time to load next fragment at lower level // 8 = bits per byte (bps/Bps) const levelNextBitrate = levels[nextLoadLevel].maxBitrate; fragLevelNextLoadedDelay = this.getTimeToLoadFrag(ttfbEstimate / 1000, bwe, duration * levelNextBitrate, !levels[nextLoadLevel].details); if (fragLevelNextLoadedDelay < bufferStarvationDelay) { break; } } // Only emergency switch down if it takes less time to load a new fragment at lowest level instead of continuing // to load the current one if (fragLevelNextLoadedDelay >= fragLoadedDelay) { return; } // if estimated load time of new segment is completely unreasonable, ignore and do not emergency switch down if (fragLevelNextLoadedDelay > duration * 10) { return; } hls.nextLoadLevel = hls.nextAutoLevel = nextLoadLevel; if (loadedFirstByte) { // If there has been loading progress, sample bandwidth using loading time offset by minimum TTFB time this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded); } else { // If there has been no loading progress, sample TTFB this.bwEstimator.sampleTTFB(timeLoading); } const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate; if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) { this.resetEstimator(nextLoadLevelBitrate); } this.clearTimer(); logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly; Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s TTFB estimate: ${ttfb | 0} ms Current BW estimate: ${isFiniteNumber(bwEstimate) ? bwEstimate | 0 : 'Unknown'} bps New BW estimate: ${this.getBwEstimate() | 0} bps Switching to level ${nextLoadLevel} @ ${nextLoadLevelBitrate | 0} bps`); hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, { frag, part, stats }); }; this.hls = _hls; this.bwEstimator = this.initEstimator(); this.registerListeners(); } resetEstimator(abrEwmaDefaultEstimate) { if (abrEwmaDefaultEstimate) { logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`); this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate; } this.firstSelection = -1; this.bwEstimator = this.initEstimator(); } initEstimator() { const config = this.hls.config; return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate); } registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.FRAG_LOADING, this.onFragLoading, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const { hls } = this; if (!hls) { return; } hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.FRAG_LOADING, this.onFragLoading, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this); hls.off(Events.ERROR, this.onError, this); } destroy() { this.unregisterListeners(); this.clearTimer(); // @ts-ignore this.hls = this._abandonRulesCheck = null; this.fragCurrent = this.partCurrent = null; } onManifestLoading(event, data) { this.lastLoadedFragLevel = -1; this.firstSelection = -1; this.lastLevelLoadSec = 0; this.fragCurrent = this.partCurrent = null; this.onLevelsUpdated(); this.clearTimer(); } onLevelsUpdated() { if (this.lastLoadedFragLevel > -1 && this.fragCurrent) { this.lastLoadedFragLevel = this.fragCurrent.level; } this._nextAutoLevel = -1; this.onMaxAutoLevelUpdated(); this.codecTiers = null; this.audioTracksByGroup = null; } onMaxAutoLevelUpdated() { this.firstSelection = -1; this.nextAutoLevelKey = ''; } onFragLoading(event, data) { const frag = data.frag; if (this.ignoreFragment(frag)) { return; } if (!frag.bitrateTest) { var _data$part; this.fragCurrent = frag; this.partCurrent = (_data$part = data.part) != null ? _data$part : null; } this.clearTimer(); this.timer = self.setInterval(this._abandonRulesCheck, 100); } onLevelSwitching(event, data) { this.clearTimer(); } onError(event, data) { if (data.fatal) { return; } switch (data.details) { case ErrorDetails.BUFFER_ADD_CODEC_ERROR: case ErrorDetails.BUFFER_APPEND_ERROR: // Reset last loaded level so that a new selection can be made after calling recoverMediaError this.lastLoadedFragLevel = -1; this.firstSelection = -1; break; case ErrorDetails.FRAG_LOAD_TIMEOUT: { const frag = data.frag; const { fragCurrent, partCurrent: part } = this; if (frag && fragCurrent && frag.sn === fragCurrent.sn && frag.level === fragCurrent.level) { const now = performance.now(); const stats = part ? part.stats : frag.stats; const timeLoading = now - stats.loading.start; const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1; const loadedFirstByte = stats.loaded && ttfb > -1; if (loadedFirstByte) { const ttfbEstimate = this.bwEstimator.getEstimateTTFB(); this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded); } else { this.bwEstimator.sampleTTFB(timeLoading); } } break; } } } getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) { const fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth; const playlistLoadSec = isSwitch ? this.lastLevelLoadSec : 0; return fragLoadSec + playlistLoadSec; } onLevelLoaded(event, data) { const config = this.hls.config; const { loading } = data.stats; const timeLoadingMs = loading.end - loading.start; if (isFiniteNumber(timeLoadingMs)) { this.lastLevelLoadSec = timeLoadingMs / 1000; } if (data.details.live) { this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive); } else { this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD); } } onFragLoaded(event, { frag, part }) { const stats = part ? part.stats : frag.stats; if (frag.type === PlaylistLevelType.MAIN) { this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start); } if (this.ignoreFragment(frag)) { return; } // stop monitoring bw once frag loaded this.clearTimer(); // reset forced auto level value so that next level will be selected if (frag.level === this._nextAutoLevel) { this._nextAutoLevel = -1; } this.firstSelection = -1; // compute level average bitrate if (this.hls.config.abrMaxWithRealBitrate) { const duration = part ? part.duration : frag.duration; const level = this.hls.levels[frag.level]; const loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded; const loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration; level.loaded = { bytes: loadedBytes, duration: loadedDuration }; level.realBitrate = Math.round(8 * loadedBytes / loadedDuration); } if (frag.bitrateTest) { const fragBufferedData = { stats, frag, part, id: frag.type }; this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData); frag.bitrateTest = false; } else { // store level id after successful fragment load for playback this.lastLoadedFragLevel = frag.level; } } onFragBuffered(event, data) { const { frag, part } = data; const stats = part != null && part.stats.loaded ? part.stats : frag.stats; if (stats.aborted) { return; } if (this.ignoreFragment(frag)) { return; } // Use the difference between parsing and request instead of buffering and request to compute fragLoadingProcessing; // rationale is that buffer appending only happens once media is attached. This can happen when config.startFragPrefetch // is used. If we used buffering in that case, our BW estimate sample will be very large. const processingMs = stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB()); this.bwEstimator.sample(processingMs, stats.loaded); stats.bwEstimate = this.getBwEstimate(); if (frag.bitrateTest) { this.bitrateTestDelay = processingMs / 1000; } else { this.bitrateTestDelay = 0; } } ignoreFragment(frag) { // Only count non-alt-audio frags which were actually buffered in our BW calculations return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment'; } clearTimer() { if (this.timer > -1) { self.clearInterval(this.timer); this.timer = -1; } } get firstAutoLevel() { const { maxAutoLevel, minAutoLevel } = this.hls; const bwEstimate = this.getBwEstimate(); const maxStartDelay = this.hls.config.maxStarvationDelay; const abrAutoLevel = this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, 0, maxStartDelay, 1, 1); if (abrAutoLevel > -1) { return abrAutoLevel; } const firstLevel = this.hls.firstLevel; const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel); logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`); return clamped; } get forcedAutoLevel() { if (this.nextAutoLevelKey) { return -1; } return this._nextAutoLevel; } // return next auto level get nextAutoLevel() { const forcedAutoLevel = this.forcedAutoLevel; const bwEstimator = this.bwEstimator; const useEstimate = bwEstimator.canEstimate(); const loadedFirstFrag = this.lastLoadedFragLevel > -1; // in case next auto level has been forced, and bw not available or not reliable, return forced value if (forcedAutoLevel !== -1 && (!useEstimate || !loadedFirstFrag || this.nextAutoLevelKey === this.getAutoLevelKey())) { return forcedAutoLevel; } // compute next level using ABR logic const nextABRAutoLevel = useEstimate && loadedFirstFrag ? this.getNextABRAutoLevel() : this.firstAutoLevel; // use forced auto level while it hasn't errored more than ABR selection if (forcedAutoLevel !== -1) { const levels = this.hls.levels; if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) { return forcedAutoLevel; } } // save result until state has changed this._nextAutoLevel = nextABRAutoLevel; this.nextAutoLevelKey = this.getAutoLevelKey(); return nextABRAutoLevel; } getAutoLevelKey() { return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`; } getNextABRAutoLevel() { const { fragCurrent, partCurrent, hls } = this; const { maxAutoLevel, config, minAutoLevel } = hls; const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0; const avgbw = this.getBwEstimate(); // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted. const bufferStarvationDelay = this.getStarvationDelay(); let bwFactor = config.abrBandWidthFactor; let bwUpFactor = config.abrBandWidthUpFactor; // First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all if (bufferStarvationDelay) { const _bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor); if (_bestLevel >= 0) { return _bestLevel; } } // not possible to get rid of rebuffering... try to find level that will guarantee less than maxStarvationDelay of rebuffering let maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay; if (!bufferStarvationDelay) { // in case buffer is empty, let's check if previous fragment was loaded to perform a bitrate test const bitrateTestDelay = this.bitrateTestDelay; if (bitrateTestDelay) { // if it is the case, then we need to adjust our max starvation delay using maxLoadingDelay config value // max video loading delay used in automatic start level selection : // in that mode ABR controller will ensure that video loading time (ie the time to fetch the first fragment at lowest quality level + // the time to fetch the fragment at the appropriate quality level is less than ```maxLoadingDelay``` ) // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay; maxStarvationDelay = maxLoadingDelay - bitrateTestDelay; logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`); // don't use conservative factor on bitrate test bwFactor = bwUpFactor = 1; } } const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor); logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`); if (bestLevel > -1) { return bestLevel; } // If no matching level found, see if min auto level would be a better option const minLevel = hls.levels[minAutoLevel]; const autoLevel = hls.levels[hls.loadLevel]; if ((minLevel == null ? void 0 : minLevel.bitrate) < (autoLevel == null ? void 0 : autoLevel.bitrate)) { return minAutoLevel; } // or if bitrate is not lower, continue to use loadLevel return hls.loadLevel; } getStarvationDelay() { const hls = this.hls; const media = hls.media; if (!media) { return Infinity; } // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as // if we're playing back at the normal rate. const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0; const bufferInfo = hls.mainForwardBufferInfo; return (bufferInfo ? bufferInfo.len : 0) / playbackRate; } getBwEstimate() { return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate; } findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor) { var _level$details; const maxFetchDuration = bufferStarvationDelay + maxStarvationDelay; const lastLoadedFragLevel = this.lastLoadedFragLevel; const selectionBaseLevel = lastLoadedFragLevel === -1 ? this.hls.firstLevel : lastLoadedFragLevel; const { fragCurrent, partCurrent } = this; const { levels, allAudioTracks, loadLevel, config } = this.hls; if (levels.length === 1) { return 0; } const level = levels[selectionBaseLevel]; const live = !!(level != null && (_level$details = level.details) != null && _level$details.live); const firstSelection = loadLevel === -1 || lastLoadedFragLevel === -1; let currentCodecSet; let currentVideoRange = 'SDR'; let currentFrameRate = (level == null ? void 0 : level.frameRate) || 0; const { audioPreference, videoPreference } = config; const audioTracksByGroup = this.audioTracksByGroup || (this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks)); if (firstSelection) { if (this.firstSelection !== -1) { return this.firstSelection; } const codecTiers = this.codecTiers || (this.codecTiers = getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel)); const startTier = getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference); const { codecSet, videoRanges, minFramerate, minBitrate, preferHDR } = startTier; currentCodecSet = codecSet; currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0]; currentFrameRate = minFramerate; currentBw = Math.max(currentBw, minBitrate); logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`); } else { currentCodecSet = level == null ? void 0 : level.codecSet; currentVideoRange = level == null ? void 0 : level.videoRange; } const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0; const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000; const levelsSkipped = []; for (let i = maxAutoLevel; i >= minAutoLevel; i--) { var _levelInfo$supportedR; const levelInfo = levels[i]; const upSwitch = i > selectionBaseLevel; if (!levelInfo) { continue; } if (config.useMediaCapabilities && !levelInfo.supportedResult && !levelInfo.supportedPromise) { const mediaCapabilities = navigator.mediaCapabilities; if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === 'function' && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) { levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities); levelInfo.supportedPromise.then(decodingInfo => { if (!this.hls) { return; } levelInfo.supportedResult = decodingInfo; const levels = this.hls.levels; const index = levels.indexOf(levelInfo); if (decodingInfo.error) { logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`); } else if (!decodingInfo.supported) { logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`); if (index > -1 && levels.length > 1) { logger.log(`[abr] Removing unsupported level ${index}`); this.hls.removeLevel(index); } } }); } else { levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT; } } // skip candidates which change codec-family or video-range, // and which decrease or increase frame-rate for up and down-switch respectfully if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || levelInfo.supportedResult && !((_levelInfo$supportedR = levelInfo.supportedResult.decodingInfoResults) != null && _levelInfo$supportedR[0].smooth)) { levelsSkipped.push(i); continue; } const levelDetails = levelInfo.details; const avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration; let adjustedbw; // follow algorithm captured from stagefright : // https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp // Pick the highest bandwidth stream below or equal to estimated bandwidth. // consider only 80% of the available bandwidth, but if we are switching up, // be even more conservative (70%) to avoid overestimating and immediately // switching back. if (!upSwitch) { adjustedbw = bwFactor * currentBw; } else { adjustedbw = bwUpFactor * currentBw; } // Use average bitrate when starvation delay (buffer length) is gt or eq two segment durations and rebuffering is not expected (maxStarvationDelay > 0) const bitrate = currentFragDuration && bufferStarvationDelay >= currentFragDuration * 2 && maxStarvationDelay === 0 ? levels[i].averageBitrate : levels[i].maxBitrate; const fetchDuration = this.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails === undefined); const canSwitchWithinTolerance = // if adjusted bw is greater than level bitrate AND adjustedbw >= bitrate && ( // no level change, or new level has no error history i === lastLoadedFragLevel || levelInfo.loadError === 0 && levelInfo.fragmentError === 0) && ( // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches // we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ... // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1 fetchDuration <= ttfbEstimateSec || !isFiniteNumber(fetchDuration) || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration); if (canSwitchWithinTolerance) { const forcedAutoLevel = this.forcedAutoLevel; if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) { if (levelsSkipped.length) { logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`); } logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`); } if (firstSelection) { this.firstSelection = i; } // as we are looping from highest to lowest, this will return the best achievable quality level return i; } } // not enough time budget even with quality level 0 ... rebuffering might happen return -1; } set nextAutoLevel(nextLevel) { const { maxAutoLevel, minAutoLevel } = this.hls; const value = Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel); if (this._nextAutoLevel !== value) { this.nextAutoLevelKey = ''; this._nextAutoLevel = value; } } } /** * @ignore * Sub-class specialization of EventHandler base class. * * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop, * scheduled asynchroneously, avoiding recursive calls in the same tick. * * The task itself is implemented in `doTick`. It can be requested and called for single execution * using the `tick` method. * * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick", * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly. * * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`, * and cancelled with `clearNextTick`. * * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`). * * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine. * * Further explanations: * * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks. * * When the task execution (`tick` method) is called in re-entrant way this is detected and * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo). */ class TaskLoop { constructor() { this._boundTick = void 0; this._tickTimer = null; this._tickInterval = null; this._tickCallCount = 0; this._boundTick = this.tick.bind(this); } destroy() { this.onHandlerDestroying(); this.onHandlerDestroyed(); } onHandlerDestroying() { // clear all timers before unregistering from event bus this.clearNextTick(); this.clearInterval(); } onHandlerDestroyed() {} hasInterval() { return !!this._tickInterval; } hasNextTick() { return !!this._tickTimer; } /** * @param millis - Interval time (ms) * @eturns True when interval has been scheduled, false when already scheduled (no effect) */ setInterval(millis) { if (!this._tickInterval) { this._tickCallCount = 0; this._tickInterval = self.setInterval(this._boundTick, millis); return true; } return false; } /** * @returns True when interval was cleared, false when none was set (no effect) */ clearInterval() { if (this._tickInterval) { self.clearInterval(this._tickInterval); this._tickInterval = null; return true; } return false; } /** * @returns True when timeout was cleared, false when none was set (no effect) */ clearNextTick() { if (this._tickTimer) { self.clearTimeout(this._tickTimer); this._tickTimer = null; return true; } return false; } /** * Will call the subclass doTick implementation in this main loop tick * or in the next one (via setTimeout(,0)) in case it has already been called * in this tick (in case this is a re-entrant call). */ tick() { this._tickCallCount++; if (this._tickCallCount === 1) { this.doTick(); // re-entrant call to tick from previous doTick call stack // -> schedule a call on the next main loop iteration to process this task processing request if (this._tickCallCount > 1) { // make sure only one timer exists at any time at max this.tickImmediate(); } this._tickCallCount = 0; } } tickImmediate() { this.clearNextTick(); this._tickTimer = self.setTimeout(this._boundTick, 0); } /** * For subclass to implement task logic * @abstract */ doTick() {} } var FragmentState = { NOT_LOADED: "NOT_LOADED", APPENDING: "APPENDING", PARTIAL: "PARTIAL", OK: "OK" }; class FragmentTracker { constructor(hls) { this.activePartLists = Object.create(null); this.endListFragments = Object.create(null); this.fragments = Object.create(null); this.timeRanges = Object.create(null); this.bufferPadding = 0.2; this.hls = void 0; this.hasGaps = false; this.hls = hls; this._registerListeners(); } _registerListeners() { const { hls } = this; hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); } destroy() { this._unregisterListeners(); // @ts-ignore this.fragments = // @ts-ignore this.activePartLists = // @ts-ignore this.endListFragments = this.timeRanges = null; } /** * Return a Fragment or Part with an appended range that matches the position and levelType * Otherwise, return null */ getAppendedFrag(position, levelType) { const activeParts = this.activePartLists[levelType]; if (activeParts) { for (let i = activeParts.length; i--;) { const activePart = activeParts[i]; if (!activePart) { break; } const appendedPTS = activePart.end; if (activePart.start <= position && appendedPTS !== null && position <= appendedPTS) { return activePart; } } } return this.getBufferedFrag(position, levelType); } /** * Return a buffered Fragment that matches the position and levelType. * A buffered Fragment is one whose loading, parsing and appending is done (completed or "partial" meaning aborted). * If not found any Fragment, return null */ getBufferedFrag(position, levelType) { const { fragments } = this; const keys = Object.keys(fragments); for (let i = keys.length; i--;) { const fragmentEntity = fragments[keys[i]]; if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) { const frag = fragmentEntity.body; if (frag.start <= position && position <= frag.end) { return frag; } } } return null; } /** * Partial fragments effected by coded frame eviction will be removed * The browser will unload parts of the buffer to free up memory for new buffer data * Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable) */ detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart) { if (this.timeRanges) { this.timeRanges[elementaryStream] = timeRange; } // Check if any flagged fragments have been unloaded // excluding anything newer than appendedPartSn const appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1; Object.keys(this.fragments).forEach(key => { const fragmentEntity = this.fragments[key]; if (!fragmentEntity) { return; } if (appendedPartSn >= fragmentEntity.body.sn) { return; } if (!fragmentEntity.buffered && !fragmentEntity.loaded) { if (fragmentEntity.body.type === playlistType) { this.removeFragment(fragmentEntity.body); } return; } const esData = fragmentEntity.range[elementaryStream]; if (!esData) { return; } esData.time.some(time => { const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange); if (isNotBuffered) { // Unregister partial fragment as it needs to load again to be reused this.removeFragment(fragmentEntity.body); } return isNotBuffered; }); }); } /** * Checks if the fragment passed in is loaded in the buffer properly * Partially loaded fragments will be registered as a partial fragment */ detectPartialFragments(data) { const timeRanges = this.timeRanges; const { frag, part } = data; if (!timeRanges || frag.sn === 'initSegment') { return; } const fragKey = getFragmentKey(frag); const fragmentEntity = this.fragments[fragKey]; if (!fragmentEntity || fragmentEntity.buffered && frag.gap) { return; } const isFragHint = !frag.relurl; Object.keys(timeRanges).forEach(elementaryStream => { const streamInfo = frag.elementaryStreams[elementaryStream]; if (!streamInfo) { return; } const timeRange = timeRanges[elementaryStream]; const partial = isFragHint || streamInfo.partial === true; fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, part, partial, timeRange); }); fragmentEntity.loaded = null; if (Object.keys(fragmentEntity.range).length) { fragmentEntity.buffered = true; const endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList; if (endList) { this.endListFragments[fragmentEntity.body.type] = fragmentEntity; } if (!isPartial(fragmentEntity)) { // Remove older fragment parts from lookup after frag is tracked as buffered this.removeParts(frag.sn - 1, frag.type); } } else { // remove fragment if nothing was appended this.removeFragment(fragmentEntity.body); } } removeParts(snToKeep, levelType) { const activeParts = this.activePartLists[levelType]; if (!activeParts) { return; } this.activePartLists[levelType] = activeParts.filter(part => part.fragment.sn >= snToKeep); } fragBuffered(frag, force) { const fragKey = getFragmentKey(frag); let fragmentEntity = this.fragments[fragKey]; if (!fragmentEntity && force) { fragmentEntity = this.fragments[fragKey] = { body: frag, appendedPTS: null, loaded: null, buffered: false, range: Object.create(null) }; if (frag.gap) { this.hasGaps = true; } } if (fragmentEntity) { fragmentEntity.loaded = null; fragmentEntity.buffered = true; } } getBufferedTimes(fragment, part, partial, timeRange) { const buffered = { time: [], partial }; const startPTS = fragment.start; const endPTS = fragment.end; const minEndPTS = fragment.minEndPTS || endPTS; const maxStartPTS = fragment.maxStartPTS || startPTS; for (let i = 0; i < timeRange.length; i++) { const startTime = timeRange.start(i) - this.bufferPadding; const endTime = timeRange.end(i) + this.bufferPadding; if (maxStartPTS >= startTime && minEndPTS <= endTime) { // Fragment is entirely contained in buffer // No need to check the other timeRange times since it's completely playable buffered.time.push({ startPTS: Math.max(startPTS, timeRange.start(i)), endPTS: Math.min(endPTS, timeRange.end(i)) }); break; } else if (startPTS < endTime && endPTS > startTime) { const start = Math.max(startPTS, timeRange.start(i)); const end = Math.min(endPTS, timeRange.end(i)); if (end > start) { buffered.partial = true; // Check for intersection with buffer // Get playable sections of the fragment buffered.time.push({ startPTS: start, endPTS: end }); } } else if (endPTS <= startTime) { // No need to check the rest of the timeRange as it is in order break; } } return buffered; } /** * Gets the partial fragment for a certain time */ getPartialFragment(time) { let bestFragment = null; let timePadding; let startTime; let endTime; let bestOverlap = 0; const { bufferPadding, fragments } = this; Object.keys(fragments).forEach(key => { const fragmentEntity = fragments[key]; if (!fragmentEntity) { return; } if (isPartial(fragmentEntity)) { startTime = fragmentEntity.body.start - bufferPadding; endTime = fragmentEntity.body.end + bufferPadding; if (time >= startTime && time <= endTime) { // Use the fragment that has the most padding from start and end time timePadding = Math.min(time - startTime, endTime - time); if (bestOverlap <= timePadding) { bestFragment = fragmentEntity.body; bestOverlap = timePadding; } } } }); return bestFragment; } isEndListAppended(type) { const lastFragmentEntity = this.endListFragments[type]; return lastFragmentEntity !== undefined && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity)); } getState(fragment) { const fragKey = getFragmentKey(fragment); const fragmentEntity = this.fragments[fragKey]; if (fragmentEntity) { if (!fragmentEntity.buffered) { return FragmentState.APPENDING; } else if (isPartial(fragmentEntity)) { return FragmentState.PARTIAL; } else { return FragmentState.OK; } } return FragmentState.NOT_LOADED; } isTimeBuffered(startPTS, endPTS, timeRange) { let startTime; let endTime; for (let i = 0; i < timeRange.length; i++) { startTime = timeRange.start(i) - this.bufferPadding; endTime = timeRange.end(i) + this.bufferPadding; if (startPTS >= startTime && endPTS <= endTime) { return true; } if (endPTS <= startTime) { // No need to check the rest of the timeRange as it is in order return false; } } return false; } onFragLoaded(event, data) { const { frag, part } = data; // don't track initsegment (for which sn is not a number) // don't track frags used for bitrateTest, they're irrelevant. if (frag.sn === 'initSegment' || frag.bitrateTest) { return; } // Fragment entity `loaded` FragLoadedData is null when loading parts const loaded = part ? null : data; const fragKey = getFragmentKey(frag); this.fragments[fragKey] = { body: frag, appendedPTS: null, loaded, buffered: false, range: Object.create(null) }; } onBufferAppended(event, data) { const { frag, part, timeRanges } = data; if (frag.sn === 'initSegment') { return; } const playlistType = frag.type; if (part) { let activeParts = this.activePartLists[playlistType]; if (!activeParts) { this.activePartLists[playlistType] = activeParts = []; } activeParts.push(part); } // Store the latest timeRanges loaded in the buffer this.timeRanges = timeRanges; Object.keys(timeRanges).forEach(elementaryStream => { const timeRange = timeRanges[elementaryStream]; this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part); }); } onFragBuffered(event, data) { this.detectPartialFragments(data); } hasFragment(fragment) { const fragKey = getFragmentKey(fragment); return !!this.fragments[fragKey]; } hasParts(type) { var _this$activePartLists; return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length); } removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) { if (withGapOnly && !this.hasGaps) { return; } Object.keys(this.fragments).forEach(key => { const fragmentEntity = this.fragments[key]; if (!fragmentEntity) { return; } const frag = fragmentEntity.body; if (frag.type !== playlistType || withGapOnly && !frag.gap) { return; } if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) { this.removeFragment(frag); } }); } removeFragment(fragment) { const fragKey = getFragmentKey(fragment); fragment.stats.loaded = 0; fragment.clearElementaryStreamInfo(); const activeParts = this.activePartLists[fragment.type]; if (activeParts) { const snToRemove = fragment.sn; this.activePartLists[fragment.type] = activeParts.filter(part => part.fragment.sn !== snToRemove); } delete this.fragments[fragKey]; if (fragment.endList) { delete this.endListFragments[fragment.type]; } } removeAllFragments() { this.fragments = Object.create(null); this.endListFragments = Object.create(null); this.activePartLists = Object.create(null); this.hasGaps = false; } } function isPartial(fragmentEntity) { var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3; return fragmentEntity.buffered && (fragmentEntity.body.gap || ((_fragmentEntity$range = fragmentEntity.range.video) == null ? void 0 : _fragmentEntity$range.partial) || ((_fragmentEntity$range2 = fragmentEntity.range.audio) == null ? void 0 : _fragmentEntity$range2.partial) || ((_fragmentEntity$range3 = fragmentEntity.range.audiovideo) == null ? void 0 : _fragmentEntity$range3.partial)); } function getFragmentKey(fragment) { return `${fragment.type}_${fragment.level}_${fragment.sn}`; } /** * Provides methods dealing with buffer length retrieval for example. * * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property. * * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered */ const noopBuffered = { length: 0, start: () => 0, end: () => 0 }; class BufferHelper { /** * Return true if `media`'s buffered include `position` */ static isBuffered(media, position) { try { if (media) { const buffered = BufferHelper.getBuffered(media); for (let i = 0; i < buffered.length; i++) { if (position >= buffered.start(i) && position <= buffered.end(i)) { return true; } } } } catch (error) { // this is to catch // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer': // This SourceBuffer has been removed from the parent media source } return false; } static bufferInfo(media, pos, maxHoleDuration) { try { if (media) { const vbuffered = BufferHelper.getBuffered(media); const buffered = []; let i; for (i = 0; i < vbuffered.length; i++) { buffered.push({ start: vbuffered.start(i), end: vbuffered.end(i) }); } return this.bufferedInfo(buffered, pos, maxHoleDuration); } } catch (error) { // this is to catch // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer': // This SourceBuffer has been removed from the parent media source } return { len: 0, start: pos, end: pos, nextStart: undefined }; } static bufferedInfo(buffered, pos, maxHoleDuration) { pos = Math.max(0, pos); // sort on buffer.start/smaller end (IE does not always return sorted buffered range) buffered.sort(function (a, b) { const diff = a.start - b.start; if (diff) { return diff; } else { return b.end - a.end; } }); let buffered2 = []; if (maxHoleDuration) { // there might be some small holes between buffer time range // consider that holes smaller than maxHoleDuration are irrelevant and build another // buffer time range representations that discards those holes for (let i = 0; i < buffered.length; i++) { const buf2len = buffered2.length; if (buf2len) { const buf2end = buffered2[buf2len - 1].end; // if small hole (value between 0 or maxHoleDuration ) or overlapping (negative) if (buffered[i].start - buf2end < maxHoleDuration) { // merge overlapping time ranges // update lastRange.end only if smaller than item.end // e.g. [ 1, 15] with [ 2,8] => [ 1,15] (no need to modify lastRange.end) // whereas [ 1, 8] with [ 2,15] => [ 1,15] ( lastRange should switch from [1,8] to [1,15]) if (buffered[i].end > buf2end) { buffered2[buf2len - 1].end = buffered[i].end; } } else { // big hole buffered2.push(buffered[i]); } } else { // first value buffered2.push(buffered[i]); } } } else { buffered2 = buffered; } let bufferLen = 0; // bufferStartNext can possibly be undefined based on the conditional logic below let bufferStartNext; // bufferStart and bufferEnd are buffer boundaries around current video position let bufferStart = pos; let bufferEnd = pos; for (let i = 0; i < buffered2.length; i++) { const start = buffered2[i].start; const end = buffered2[i].end; // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i)); if (pos + maxHoleDuration >= start && pos < end) { // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length bufferStart = start; bufferEnd = end; bufferLen = bufferEnd - pos; } else if (pos + maxHoleDuration < start) { bufferStartNext = start; break; } } return { len: bufferLen, start: bufferStart || 0, end: bufferEnd || 0, nextStart: bufferStartNext }; } /** * Safe method to get buffered property. * SourceBuffer.buffered may throw if SourceBuffer is removed from it's MediaSource */ static getBuffered(media) { try { return media.buffered; } catch (e) { logger.log('failed to get media.buffered', e); return noopBuffered; } } } class ChunkMetadata { constructor(level, sn, id, size = 0, part = -1, partial = false) { this.level = void 0; this.sn = void 0; this.part = void 0; this.id = void 0; this.size = void 0; this.partial = void 0; this.transmuxing = getNewPerformanceTiming(); this.buffering = { audio: getNewPerformanceTiming(), video: getNewPerformanceTiming(), audiovideo: getNewPerformanceTiming() }; this.level = level; this.sn = sn; this.id = id; this.size = size; this.part = part; this.partial = partial; } } function getNewPerformanceTiming() { return { start: 0, executeStart: 0, executeEnd: 0, end: 0 }; } function findFirstFragWithCC(fragments, cc) { for (let i = 0, len = fragments.length; i < len; i++) { var _fragments$i; if (((_fragments$i = fragments[i]) == null ? void 0 : _fragments$i.cc) === cc) { return fragments[i]; } } return null; } function shouldAlignOnDiscontinuities(lastFrag, switchDetails, details) { if (switchDetails) { if (details.endCC > details.startCC || lastFrag && lastFrag.cc < details.startCC) { return true; } } return false; } // Find the first frag in the previous level which matches the CC of the first frag of the new level function findDiscontinuousReferenceFrag(prevDetails, curDetails) { const prevFrags = prevDetails.fragments; const curFrags = curDetails.fragments; if (!curFrags.length || !prevFrags.length) { logger.log('No fragments to align'); return; } const prevStartFrag = findFirstFragWithCC(prevFrags, curFrags[0].cc); if (!prevStartFrag || prevStartFrag && !prevStartFrag.startPTS) { logger.log('No frag in previous level to align on'); return; } return prevStartFrag; } function adjustFragmentStart(frag, sliding) { if (frag) { const start = frag.start + sliding; frag.start = frag.startPTS = start; frag.endPTS = start + frag.duration; } } function adjustSlidingStart(sliding, details) { // Update segments const fragments = details.fragments; for (let i = 0, len = fragments.length; i < len; i++) { adjustFragmentStart(fragments[i], sliding); } // Update LL-HLS parts at the end of the playlist if (details.fragmentHint) { adjustFragmentStart(details.fragmentHint, sliding); } details.alignedSliding = true; } /** * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a * contiguous stream with the last fragments. * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time * and an extra download. * @param lastFrag * @param lastLevel * @param details */ function alignStream(lastFrag, switchDetails, details) { if (!switchDetails) { return; } alignDiscontinuities(lastFrag, details, switchDetails); if (!details.alignedSliding && switchDetails) { // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level. // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same // discontinuity sequence. alignMediaPlaylistByPDT(details, switchDetails); } if (!details.alignedSliding && switchDetails && !details.skippedSegments) { // Try to align on sn so that we pick a better start fragment. // Do not perform this on playlists with delta updates as this is only to align levels on switch // and adjustSliding only adjusts fragments after skippedSegments. adjustSliding(switchDetails, details); } } /** * Computes the PTS if a new level's fragments using the PTS of a fragment in the last level which shares the same * discontinuity sequence. * @param lastFrag - The last Fragment which shares the same discontinuity sequence * @param lastLevel - The details of the last loaded level * @param details - The details of the new level */ function alignDiscontinuities(lastFrag, details, switchDetails) { if (shouldAlignOnDiscontinuities(lastFrag, switchDetails, details)) { const referenceFrag = findDiscontinuousReferenceFrag(switchDetails, details); if (referenceFrag && isFiniteNumber(referenceFrag.start)) { logger.log(`Adjusting PTS using last level due to CC increase within current level ${details.url}`); adjustSlidingStart(referenceFrag.start, details); } } } /** * Ensures appropriate time-alignment between renditions based on PDT. * This function assumes the timelines represented in `refDetails` are accurate, including the PDTs * for the last discontinuity sequence number shared by both playlists when present, * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation * times/timelines of `details` accordingly. * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks, * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should * be consistent across playlists, per the HLS spec. * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition). * @param refDetails - The details of the reference rendition with start and PDT times for alignment. */ function alignMediaPlaylistByPDT(details, refDetails) { if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) { return; } const fragments = details.fragments; const refFragments = refDetails.fragments; if (!fragments.length || !refFragments.length) { return; } // Calculate a delta to apply to all fragments according to the delta in PDT times and start times // of a fragment in the reference details, and a fragment in the target details of the same discontinuity. // If a fragment of the same discontinuity was not found use the middle fragment of both. let refFrag; let frag; const targetCC = Math.min(refDetails.endCC, details.endCC); if (refDetails.startCC < targetCC && details.startCC < targetCC) { refFrag = findFirstFragWithCC(refFragments, targetCC); frag = findFirstFragWithCC(fragments, targetCC); } if (!refFrag || !frag) { refFrag = refFragments[Math.floor(refFragments.length / 2)]; frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.floor(fragments.length / 2)]; } const refPDT = refFrag.programDateTime; const targetPDT = frag.programDateTime; if (!refPDT || !targetPDT) { return; } const delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start); adjustSlidingStart(delta, details); } const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb class FragmentLoader { constructor(config) { this.config = void 0; this.loader = null; this.partLoadTimeout = -1; this.config = config; } destroy() { if (this.loader) { this.loader.destroy(); this.loader = null; } } abort() { if (this.loader) { // Abort the loader for current fragment. Only one may load at any given time this.loader.abort(); } } load(frag, onProgress) { const url = frag.url; if (!url) { return Promise.reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, error: new Error(`Fragment does not have a ${url ? 'part list' : 'url'}`), networkDetails: null })); } this.abort(); const config = this.config; const FragmentILoader = config.fLoader; const DefaultILoader = config.loader; return new Promise((resolve, reject) => { if (this.loader) { this.loader.destroy(); } if (frag.gap) { if (frag.tagList.some(tags => tags[0] === 'GAP')) { reject(createGapLoadError(frag)); return; } else { // Reset temporary treatment as GAP tag frag.gap = false; } } const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config); const loaderContext = createLoaderContext(frag); const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default); const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0, highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE }; // Assign frag stats to the loader's stats reference frag.stats = loader.stats; loader.load(loaderContext, loaderConfig, { onSuccess: (response, stats, context, networkDetails) => { this.resetLoader(frag, loader); let payload = response.data; if (context.resetIV && frag.decryptdata) { frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16)); payload = payload.slice(16); } resolve({ frag, part: null, payload, networkDetails }); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, response: _objectSpread2({ url, data: undefined }, response), error: new Error(`HTTP Error ${response.code} ${response.text}`), networkDetails, stats })); }, onAbort: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.INTERNAL_ABORTED, fatal: false, frag, error: new Error('Aborted'), networkDetails, stats })); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag, error: new Error(`Timeout after ${loaderConfig.timeout}ms`), networkDetails, stats })); }, onProgress: (stats, context, data, networkDetails) => { if (onProgress) { onProgress({ frag, part: null, payload: data, networkDetails }); } } }); }); } loadPart(frag, part, onProgress) { this.abort(); const config = this.config; const FragmentILoader = config.fLoader; const DefaultILoader = config.loader; return new Promise((resolve, reject) => { if (this.loader) { this.loader.destroy(); } if (frag.gap || part.gap) { reject(createGapLoadError(frag, part)); return; } const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config); const loaderContext = createLoaderContext(frag, part); // Should we define another load policy for parts? const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default); const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0, highWaterMark: MIN_CHUNK_SIZE }; // Assign part stats to the loader's stats reference part.stats = loader.stats; loader.load(loaderContext, loaderConfig, { onSuccess: (response, stats, context, networkDetails) => { this.resetLoader(frag, loader); this.updateStatsFromPart(frag, part); const partLoadedData = { frag, part, payload: response.data, networkDetails }; onProgress(partLoadedData); resolve(partLoadedData); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_ERROR, fatal: false, frag, part, response: _objectSpread2({ url: loaderContext.url, data: undefined }, response), error: new Error(`HTTP Error ${response.code} ${response.text}`), networkDetails, stats })); }, onAbort: (stats, context, networkDetails) => { frag.stats.aborted = part.stats.aborted; this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.INTERNAL_ABORTED, fatal: false, frag, part, error: new Error('Aborted'), networkDetails, stats })); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(frag, loader); reject(new LoadError({ type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.FRAG_LOAD_TIMEOUT, fatal: false, frag, part, error: new Error(`Timeout after ${loaderConfig.timeout}ms`), networkDetails, stats })); } }); }); } updateStatsFromPart(frag, part) { const fragStats = frag.stats; const partStats = part.stats; const partTotal = partStats.total; fragStats.loaded += partStats.loaded; if (partTotal) { const estTotalParts = Math.round(frag.duration / part.duration); const estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts); const estRemainingParts = estTotalParts - estLoadedParts; const estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts); fragStats.total = fragStats.loaded + estRemainingBytes; } else { fragStats.total = Math.max(fragStats.loaded, fragStats.total); } const fragLoading = fragStats.loading; const partLoading = partStats.loading; if (fragLoading.start) { // add to fragment loader latency fragLoading.first += partLoading.first - partLoading.start; } else { fragLoading.start = partLoading.start; fragLoading.first = partLoading.first; } fragLoading.end = partLoading.end; } resetLoader(frag, loader) { frag.loader = null; if (this.loader === loader) { self.clearTimeout(this.partLoadTimeout); this.loader = null; } loader.destroy(); } } function createLoaderContext(frag, part = null) { const segment = part || frag; const loaderContext = { frag, part, responseType: 'arraybuffer', url: segment.url, headers: {}, rangeStart: 0, rangeEnd: 0 }; const start = segment.byteRangeStartOffset; const end = segment.byteRangeEndOffset; if (isFiniteNumber(start) && isFiniteNumber(end)) { var _frag$decryptdata; let byteRangeStart = start; let byteRangeEnd = end; if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') { // MAP segment encrypted with method 'AES-128', when served with HTTP Range, // has the unencrypted size specified in the range. // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6 const fragmentLen = end - start; if (fragmentLen % 16) { byteRangeEnd = end + (16 - fragmentLen % 16); } if (start !== 0) { loaderContext.resetIV = true; byteRangeStart = start - 16; } } loaderContext.rangeStart = byteRangeStart; loaderContext.rangeEnd = byteRangeEnd; } return loaderContext; } function createGapLoadError(frag, part) { const error = new Error(`GAP ${frag.gap ? 'tag' : 'attribute'} found`); const errorData = { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_GAP, fatal: false, frag, error, networkDetails: null }; if (part) { errorData.part = part; } (part ? part : frag).stats.aborted = true; return new LoadError(errorData); } class LoadError extends Error { constructor(data) { super(data.error.message); this.data = void 0; this.data = data; } } class AESCrypto { constructor(subtle, iv) { this.subtle = void 0; this.aesIV = void 0; this.subtle = subtle; this.aesIV = iv; } decrypt(data, key) { return this.subtle.decrypt({ name: 'AES-CBC', iv: this.aesIV }, key, data); } } class FastAESKey { constructor(subtle, key) { this.subtle = void 0; this.key = void 0; this.subtle = subtle; this.key = key; } expandKey() { return this.subtle.importKey('raw', this.key, { name: 'AES-CBC' }, false, ['encrypt', 'decrypt']); } } // PKCS7 function removePadding(array) { const outputBytes = array.byteLength; const paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1); if (paddingBytes) { return sliceUint8(array, 0, outputBytes - paddingBytes); } return array; } class AESDecryptor { constructor() { this.rcon = [0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)]; this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)]; this.sBox = new Uint32Array(256); this.invSBox = new Uint32Array(256); this.key = new Uint32Array(0); this.ksRows = 0; this.keySize = 0; this.keySchedule = void 0; this.invKeySchedule = void 0; this.initTable(); } // Using view.getUint32() also swaps the byte order. uint8ArrayToUint32Array_(arrayBuffer) { const view = new DataView(arrayBuffer); const newArray = new Uint32Array(4); for (let i = 0; i < 4; i++) { newArray[i] = view.getUint32(i * 4); } return newArray; } initTable() { const sBox = this.sBox; const invSBox = this.invSBox; const subMix = this.subMix; const subMix0 = subMix[0]; const subMix1 = subMix[1]; const subMix2 = subMix[2]; const subMix3 = subMix[3]; const invSubMix = this.invSubMix; const invSubMix0 = invSubMix[0]; const invSubMix1 = invSubMix[1]; const invSubMix2 = invSubMix[2]; const invSubMix3 = invSubMix[3]; const d = new Uint32Array(256); let x = 0; let xi = 0; let i = 0; for (i = 0; i < 256; i++) { if (i < 128) { d[i] = i << 1; } else { d[i] = i << 1 ^ 0x11b; } } for (i = 0; i < 256; i++) { let sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4; sx = sx >>> 8 ^ sx & 0xff ^ 0x63; sBox[x] = sx; invSBox[sx] = x; // Compute multiplication const x2 = d[x]; const x4 = d[x2]; const x8 = d[x4]; // Compute sub/invSub bytes, mix columns tables let t = d[sx] * 0x101 ^ sx * 0x1010100; subMix0[x] = t << 24 | t >>> 8; subMix1[x] = t << 16 | t >>> 16; subMix2[x] = t << 8 | t >>> 24; subMix3[x] = t; // Compute inv sub bytes, inv mix columns tables t = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; invSubMix0[sx] = t << 24 | t >>> 8; invSubMix1[sx] = t << 16 | t >>> 16; invSubMix2[sx] = t << 8 | t >>> 24; invSubMix3[sx] = t; // Compute next counter if (!x) { x = xi = 1; } else { x = x2 ^ d[d[d[x8 ^ x2]]]; xi ^= d[d[xi]]; } } } expandKey(keyBuffer) { // convert keyBuffer to Uint32Array const key = this.uint8ArrayToUint32Array_(keyBuffer); let sameKey = true; let offset = 0; while (offset < key.length && sameKey) { sameKey = key[offset] === this.key[offset]; offset++; } if (sameKey) { return; } this.key = key; const keySize = this.keySize = key.length; if (keySize !== 4 && keySize !== 6 && keySize !== 8) { throw new Error('Invalid aes key size=' + keySize); } const ksRows = this.ksRows = (keySize + 6 + 1) * 4; let ksRow; let invKsRow; const keySchedule = this.keySchedule = new Uint32Array(ksRows); const invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows); const sbox = this.sBox; const rcon = this.rcon; const invSubMix = this.invSubMix; const invSubMix0 = invSubMix[0]; const invSubMix1 = invSubMix[1]; const invSubMix2 = invSubMix[2]; const invSubMix3 = invSubMix[3]; let prev; let t; for (ksRow = 0; ksRow < ksRows; ksRow++) { if (ksRow < keySize) { prev = keySchedule[ksRow] = key[ksRow]; continue; } t = prev; if (ksRow % keySize === 0) { // Rot word t = t << 8 | t >>> 24; // Sub word t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff]; // Mix Rcon t ^= rcon[ksRow / keySize | 0] << 24; } else if (keySize > 6 && ksRow % keySize === 4) { // Sub word t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff]; } keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0; } for (invKsRow = 0; invKsRow < ksRows; invKsRow++) { ksRow = ksRows - invKsRow; if (invKsRow & 3) { t = keySchedule[ksRow]; } else { t = keySchedule[ksRow - 4]; } if (invKsRow < 4 || ksRow <= 4) { invKeySchedule[invKsRow] = t; } else { invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]]; } invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0; } } // Adding this as a method greatly improves performance. networkToHostOrderSwap(word) { return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; } decrypt(inputArrayBuffer, offset, aesIV) { const nRounds = this.keySize + 6; const invKeySchedule = this.invKeySchedule; const invSBOX = this.invSBox; const invSubMix = this.invSubMix; const invSubMix0 = invSubMix[0]; const invSubMix1 = invSubMix[1]; const invSubMix2 = invSubMix[2]; const invSubMix3 = invSubMix[3]; const initVector = this.uint8ArrayToUint32Array_(aesIV); let initVector0 = initVector[0]; let initVector1 = initVector[1]; let initVector2 = initVector[2]; let initVector3 = initVector[3]; const inputInt32 = new Int32Array(inputArrayBuffer); const outputInt32 = new Int32Array(inputInt32.length); let t0, t1, t2, t3; let s0, s1, s2, s3; let inputWords0, inputWords1, inputWords2, inputWords3; let ksRow, i; const swapWord = this.networkToHostOrderSwap; while (offset < inputInt32.length) { inputWords0 = swapWord(inputInt32[offset]); inputWords1 = swapWord(inputInt32[offset + 1]); inputWords2 = swapWord(inputInt32[offset + 2]); inputWords3 = swapWord(inputInt32[offset + 3]); s0 = inputWords0 ^ invKeySchedule[0]; s1 = inputWords3 ^ invKeySchedule[1]; s2 = inputWords2 ^ invKeySchedule[2]; s3 = inputWords1 ^ invKeySchedule[3]; ksRow = 4; // Iterate through the rounds of decryption for (i = 1; i < nRounds; i++) { t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow]; t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1]; t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2]; t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3]; // Update state s0 = t0; s1 = t1; s2 = t2; s3 = t3; ksRow = ksRow + 4; } // Shift rows, sub bytes, add round key t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow]; t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1]; t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2]; t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3]; // Write outputInt32[offset] = swapWord(t0 ^ initVector0); outputInt32[offset + 1] = swapWord(t3 ^ initVector1); outputInt32[offset + 2] = swapWord(t2 ^ initVector2); outputInt32[offset + 3] = swapWord(t1 ^ initVector3); // reset initVector to last 4 unsigned int initVector0 = inputWords0; initVector1 = inputWords1; initVector2 = inputWords2; initVector3 = inputWords3; offset = offset + 4; } return outputInt32.buffer; } } const CHUNK_SIZE = 16; // 16 bytes, 128 bits class Decrypter { constructor(config, { removePKCS7Padding = true } = {}) { this.logEnabled = true; this.removePKCS7Padding = void 0; this.subtle = null; this.softwareDecrypter = null; this.key = null; this.fastAesKey = null; this.remainderData = null; this.currentIV = null; this.currentResult = null; this.useSoftware = void 0; this.useSoftware = config.enableSoftwareAES; this.removePKCS7Padding = removePKCS7Padding; // built in decryptor expects PKCS7 padding if (removePKCS7Padding) { try { const browserCrypto = self.crypto; if (browserCrypto) { this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; } } catch (e) { /* no-op */ } } this.useSoftware = !this.subtle; } destroy() { this.subtle = null; this.softwareDecrypter = null; this.key = null; this.fastAesKey = null; this.remainderData = null; this.currentIV = null; this.currentResult = null; } isSync() { return this.useSoftware; } flush() { const { currentResult, remainderData } = this; if (!currentResult || remainderData) { this.reset(); return null; } const data = new Uint8Array(currentResult); this.reset(); if (this.removePKCS7Padding) { return removePadding(data); } return data; } reset() { this.currentResult = null; this.currentIV = null; this.remainderData = null; if (this.softwareDecrypter) { this.softwareDecrypter = null; } } decrypt(data, key, iv) { if (this.useSoftware) { return new Promise((resolve, reject) => { this.softwareDecrypt(new Uint8Array(data), key, iv); const decryptResult = this.flush(); if (decryptResult) { resolve(decryptResult.buffer); } else { reject(new Error('[softwareDecrypt] Failed to decrypt data')); } }); } return this.webCryptoDecrypt(new Uint8Array(data), key, iv); } // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached // data is handled in the flush() call softwareDecrypt(data, key, iv) { const { currentIV, currentResult, remainderData } = this; this.logOnce('JS AES decrypt'); // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached // the end on flush(), but by that time we have already received all bytes for the segment. // Progressive decryption does not work with WebCrypto if (remainderData) { data = appendUint8Array(remainderData, data); this.remainderData = null; } // Byte length must be a multiple of 16 (AES-128 = 128 bit blocks = 16 bytes) const currentChunk = this.getValidChunk(data); if (!currentChunk.length) { return null; } if (currentIV) { iv = currentIV; } let softwareDecrypter = this.softwareDecrypter; if (!softwareDecrypter) { softwareDecrypter = this.softwareDecrypter = new AESDecryptor(); } softwareDecrypter.expandKey(key); const result = currentResult; this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv); this.currentIV = sliceUint8(currentChunk, -16).buffer; if (!result) { return null; } return result; } webCryptoDecrypt(data, key, iv) { if (this.key !== key || !this.fastAesKey) { if (!this.subtle) { return Promise.resolve(this.onWebCryptoError(data, key, iv)); } this.key = key; this.fastAesKey = new FastAESKey(this.subtle, key); } return this.fastAesKey.expandKey().then(aesKey => { // decrypt using web crypto if (!this.subtle) { return Promise.reject(new Error('web crypto not initialized')); } this.logOnce('WebCrypto AES decrypt'); const crypto = new AESCrypto(this.subtle, new Uint8Array(iv)); return crypto.decrypt(data.buffer, aesKey); }).catch(err => { logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`); return this.onWebCryptoError(data, key, iv); }); } onWebCryptoError(data, key, iv) { this.useSoftware = true; this.logEnabled = true; this.softwareDecrypt(data, key, iv); const decryptResult = this.flush(); if (decryptResult) { return decryptResult.buffer; } throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data'); } getValidChunk(data) { let currentChunk = data; const splitPoint = data.length - data.length % CHUNK_SIZE; if (splitPoint !== data.length) { currentChunk = sliceUint8(data, 0, splitPoint); this.remainderData = sliceUint8(data, splitPoint); } return currentChunk; } logOnce(msg) { if (!this.logEnabled) { return; } logger.log(`[decrypter]: ${msg}`); this.logEnabled = false; } } /** * TimeRanges to string helper */ const TimeRanges = { toString: function (r) { let log = ''; const len = r.length; for (let i = 0; i < len; i++) { log += `[${r.start(i).toFixed(3)}-${r.end(i).toFixed(3)}]`; } return log; } }; const State = { STOPPED: 'STOPPED', IDLE: 'IDLE', KEY_LOADING: 'KEY_LOADING', FRAG_LOADING: 'FRAG_LOADING', FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY', WAITING_TRACK: 'WAITING_TRACK', PARSING: 'PARSING', PARSED: 'PARSED', ENDED: 'ENDED', ERROR: 'ERROR', WAITING_INIT_PTS: 'WAITING_INIT_PTS', WAITING_LEVEL: 'WAITING_LEVEL' }; class BaseStreamController extends TaskLoop { constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) { super(); this.hls = void 0; this.fragPrevious = null; this.fragCurrent = null; this.fragmentTracker = void 0; this.transmuxer = null; this._state = State.STOPPED; this.playlistType = void 0; this.media = null; this.mediaBuffer = null; this.config = void 0; this.bitrateTest = false; this.lastCurrentTime = 0; this.nextLoadPosition = 0; this.startPosition = 0; this.startTimeOffset = null; this.loadedmetadata = false; this.retryDate = 0; this.levels = null; this.fragmentLoader = void 0; this.keyLoader = void 0; this.levelLastLoaded = null; this.startFragRequested = false; this.decrypter = void 0; this.initPTS = []; this.onvseeking = null; this.onvended = null; this.logPrefix = ''; this.log = void 0; this.warn = void 0; this.playlistType = playlistType; this.logPrefix = logPrefix; this.log = logger.log.bind(logger, `${logPrefix}:`); this.warn = logger.warn.bind(logger, `${logPrefix}:`); this.hls = hls; this.fragmentLoader = new FragmentLoader(hls.config); this.keyLoader = keyLoader; this.fragmentTracker = fragmentTracker; this.config = hls.config; this.decrypter = new Decrypter(hls.config); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); } doTick() { this.onTickEnd(); } onTickEnd() {} // eslint-disable-next-line @typescript-eslint/no-unused-vars startLoad(startPosition) {} stopLoad() { this.fragmentLoader.abort(); this.keyLoader.abort(this.playlistType); const frag = this.fragCurrent; if (frag != null && frag.loader) { frag.abortRequests(); this.fragmentTracker.removeFragment(frag); } this.resetTransmuxer(); this.fragCurrent = null; this.fragPrevious = null; this.clearInterval(); this.clearNextTick(); this.state = State.STOPPED; } _streamEnded(bufferInfo, levelDetails) { // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached, // of nothing loading/loaded return false if (levelDetails.live || bufferInfo.nextStart || !bufferInfo.end || !this.media) { return false; } const partList = levelDetails.partList; // Since the last part isn't guaranteed to correspond to the last playlist segment for Low-Latency HLS, // check instead if the last part is buffered. if (partList != null && partList.length) { const lastPart = partList[partList.length - 1]; // Checking the midpoint of the part for potential margin of error and related issues. // NOTE: Technically I believe parts could yield content that is < the computed duration (including potential a duration of 0) // and still be spec-compliant, so there may still be edge cases here. Likewise, there could be issues in end of stream // part mismatches for independent audio and video playlists/segments. const lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2); return lastPartBuffered; } const playlistType = levelDetails.fragments[levelDetails.fragments.length - 1].type; return this.fragmentTracker.isEndListAppended(playlistType); } getLevelDetails() { if (this.levels && this.levelLastLoaded !== null) { var _this$levelLastLoaded; return (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details; } } onMediaAttached(event, data) { const media = this.media = this.mediaBuffer = data.media; this.onvseeking = this.onMediaSeeking.bind(this); this.onvended = this.onMediaEnded.bind(this); media.addEventListener('seeking', this.onvseeking); media.addEventListener('ended', this.onvended); const config = this.config; if (this.levels && config.autoStartLoad && this.state === State.STOPPED) { this.startLoad(config.startPosition); } } onMediaDetaching() { const media = this.media; if (media != null && media.ended) { this.log('MSE detaching and video ended, reset startPosition'); this.startPosition = this.lastCurrentTime = 0; } // remove video listeners if (media && this.onvseeking && this.onvended) { media.removeEventListener('seeking', this.onvseeking); media.removeEventListener('ended', this.onvended); this.onvseeking = this.onvended = null; } if (this.keyLoader) { this.keyLoader.detach(); } this.media = this.mediaBuffer = null; this.loadedmetadata = false; this.fragmentTracker.removeAllFragments(); this.stopLoad(); } onMediaSeeking() { const { config, fragCurrent, media, mediaBuffer, state } = this; const currentTime = media ? media.currentTime : 0; const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole); this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`); if (this.state === State.ENDED) { this.resetLoadingState(); } else if (fragCurrent) { // Seeking while frag load is in progress const tolerance = config.maxFragLookUpTolerance; const fragStartOffset = fragCurrent.start - tolerance; const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance; // if seeking out of buffered range or into new one if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) { const pastFragment = currentTime > fragEndOffset; // if the seek position is outside the current fragment range if (currentTime < fragStartOffset || pastFragment) { if (pastFragment && fragCurrent.loader) { this.log('seeking outside of buffer while fragment load in progress, cancel fragment load'); fragCurrent.abortRequests(); this.resetLoadingState(); } this.fragPrevious = null; } } } if (media) { // Remove gap fragments this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true); this.lastCurrentTime = currentTime; } // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target if (!this.loadedmetadata && !bufferInfo.len) { this.nextLoadPosition = this.startPosition = currentTime; } // Async tick to speed up processing this.tickImmediate(); } onMediaEnded() { // reset startPosition and lastCurrentTime to restart playback @ stream beginning this.startPosition = this.lastCurrentTime = 0; } onManifestLoaded(event, data) { this.startTimeOffset = data.startTimeOffset; this.initPTS = []; } onHandlerDestroying() { this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); this.stopLoad(); super.onHandlerDestroying(); // @ts-ignore this.hls = null; } onHandlerDestroyed() { this.state = State.STOPPED; if (this.fragmentLoader) { this.fragmentLoader.destroy(); } if (this.keyLoader) { this.keyLoader.destroy(); } if (this.decrypter) { this.decrypter.destroy(); } this.hls = this.log = this.warn = this.decrypter = this.keyLoader = this.fragmentLoader = this.fragmentTracker = null; super.onHandlerDestroyed(); } loadFragment(frag, level, targetBufferTime) { this._loadFragForPlayback(frag, level, targetBufferTime); } _loadFragForPlayback(frag, level, targetBufferTime) { const progressCallback = data => { if (this.fragContextChanged(frag)) { this.warn(`Fragment ${frag.sn}${data.part ? ' p: ' + data.part.index : ''} of level ${frag.level} was dropped during download.`); this.fragmentTracker.removeFragment(frag); return; } frag.stats.chunkCount++; this._handleFragmentLoadProgress(data); }; this._doFragLoad(frag, level, targetBufferTime, progressCallback).then(data => { if (!data) { // if we're here we probably needed to backtrack or are waiting for more parts return; } const state = this.state; if (this.fragContextChanged(frag)) { if (state === State.FRAG_LOADING || !this.fragCurrent && state === State.PARSING) { this.fragmentTracker.removeFragment(frag); this.state = State.IDLE; } return; } if ('payload' in data) { this.log(`Loaded fragment ${frag.sn} of level ${frag.level}`); this.hls.trigger(Events.FRAG_LOADED, data); } // Pass through the whole payload; controllers not implementing progressive loading receive data from this callback this._handleFragmentLoadComplete(data); }).catch(reason => { if (this.state === State.STOPPED || this.state === State.ERROR) { return; } this.warn(`Frag error: ${(reason == null ? void 0 : reason.message) || reason}`); this.resetFragmentLoading(frag); }); } clearTrackerIfNeeded(frag) { var _this$mediaBuffer; const { fragmentTracker } = this; const fragState = fragmentTracker.getState(frag); if (fragState === FragmentState.APPENDING) { // Lower the max buffer length and try again const playlistType = frag.type; const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType); const minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength); // If backtracking, always remove from the tracker without reducing max buffer length const backtrackFragment = this.backtrackFragment; const backtracked = backtrackFragment ? frag.sn - backtrackFragment.sn : 0; if (backtracked === 1 || this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)) { fragmentTracker.removeFragment(frag); } } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) { // Stop gap for bad tracker / buffer flush behavior fragmentTracker.removeAllFragments(); } else if (fragmentTracker.hasParts(frag.type)) { // In low latency mode, remove fragments for which only some parts were buffered fragmentTracker.detectPartialFragments({ frag, part: null, stats: frag.stats, id: frag.type }); if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) { fragmentTracker.removeFragment(frag); } } } checkLiveUpdate(details) { if (details.updated && !details.live) { // Live stream ended, update fragment tracker const lastFragment = details.fragments[details.fragments.length - 1]; this.fragmentTracker.detectPartialFragments({ frag: lastFragment, part: null, stats: lastFragment.stats, id: lastFragment.type }); } if (!details.fragments[0]) { details.deltaUpdateFailed = true; } } flushMainBuffer(startOffset, endOffset, type = null) { if (!(startOffset - endOffset)) { return; } // When alternate audio is playing, the audio-stream-controller is responsible for the audio buffer. Otherwise, // passing a null type flushes both buffers const flushScope = { startOffset, endOffset, type }; this.hls.trigger(Events.BUFFER_FLUSHING, flushScope); } _loadInitSegment(frag, level) { this._doFragLoad(frag, level).then(data => { if (!data || this.fragContextChanged(frag) || !this.levels) { throw new Error('init load aborted'); } return data; }).then(data => { const { hls } = this; const { payload } = data; const decryptData = frag.decryptdata; // check to see if the payload needs to be decrypted if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') { const startTime = self.performance.now(); // decrypt init segment data return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => { hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_DECRYPT_ERROR, fatal: false, error: err, reason: err.message, frag }); throw err; }).then(decryptedData => { const endTime = self.performance.now(); hls.trigger(Events.FRAG_DECRYPTED, { frag, payload: decryptedData, stats: { tstart: startTime, tdecrypt: endTime } }); data.payload = decryptedData; return this.completeInitSegmentLoad(data); }); } return this.completeInitSegmentLoad(data); }).catch(reason => { if (this.state === State.STOPPED || this.state === State.ERROR) { return; } this.warn(reason); this.resetFragmentLoading(frag); }); } completeInitSegmentLoad(data) { const { levels } = this; if (!levels) { throw new Error('init load aborted, missing levels'); } const stats = data.frag.stats; this.state = State.IDLE; data.frag.data = new Uint8Array(data.payload); stats.parsing.start = stats.buffering.start = self.performance.now(); stats.parsing.end = stats.buffering.end = self.performance.now(); this.tick(); } fragContextChanged(frag) { const { fragCurrent } = this; return !frag || !fragCurrent || frag.sn !== fragCurrent.sn || frag.level !== fragCurrent.level; } fragBufferedComplete(frag, part) { var _frag$startPTS, _frag$endPTS, _this$fragCurrent, _this$fragPrevious; const media = this.mediaBuffer ? this.mediaBuffer : this.media; this.log(`Buffered ${frag.type} sn: ${frag.sn}${part ? ' part: ' + part.index : ''} of ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level} (frag:[${((_frag$startPTS = frag.startPTS) != null ? _frag$startPTS : NaN).toFixed(3)}-${((_frag$endPTS = frag.endPTS) != null ? _frag$endPTS : NaN).toFixed(3)}] > buffer:${media ? TimeRanges.toString(BufferHelper.getBuffered(media)) : '(detached)'})`); if (frag.sn !== 'initSegment') { var _this$levels; if (frag.type !== PlaylistLevelType.SUBTITLE) { const el = frag.elementaryStreams; if (!Object.keys(el).some(type => !!el[type])) { // empty segment this.state = State.IDLE; return; } } const level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level]; if (level != null && level.fragmentError) { this.log(`Resetting level fragment error count of ${level.fragmentError} on frag buffered`); level.fragmentError = 0; } } this.state = State.IDLE; if (!media) { return; } if (!this.loadedmetadata && frag.type == PlaylistLevelType.MAIN && media.buffered.length && ((_this$fragCurrent = this.fragCurrent) == null ? void 0 : _this$fragCurrent.sn) === ((_this$fragPrevious = this.fragPrevious) == null ? void 0 : _this$fragPrevious.sn)) { this.loadedmetadata = true; this.seekToStartPos(); } this.tick(); } seekToStartPos() {} _handleFragmentLoadComplete(fragLoadedEndData) { const { transmuxer } = this; if (!transmuxer) { return; } const { frag, part, partsLoaded } = fragLoadedEndData; // If we did not load parts, or loaded all parts, we have complete (not partial) fragment data const complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some(fragLoaded => !fragLoaded); const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete); transmuxer.flush(chunkMeta); } // eslint-disable-next-line @typescript-eslint/no-unused-vars _handleFragmentLoadProgress(frag) {} _doFragLoad(frag, level, targetBufferTime = null, progressCallback) { var _frag$decryptdata; const details = level == null ? void 0 : level.details; if (!this.levels || !details) { throw new Error(`frag load aborted, missing level${details ? '' : ' detail'}s`); } let keyLoadingPromise = null; if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) { this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`); this.state = State.KEY_LOADING; this.fragCurrent = frag; keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => { if (!this.fragContextChanged(keyLoadedData.frag)) { this.hls.trigger(Events.KEY_LOADED, keyLoadedData); if (this.state === State.KEY_LOADING) { this.state = State.IDLE; } return keyLoadedData; } }); this.hls.trigger(Events.KEY_LOADING, { frag }); if (this.fragCurrent === null) { keyLoadingPromise = Promise.reject(new Error(`frag load aborted, context changed in KEY_LOADING`)); } } else if (!frag.encrypted && details.encryptedFragments.length) { this.keyLoader.loadClear(frag, details.encryptedFragments); } targetBufferTime = Math.max(frag.start, targetBufferTime || 0); if (this.config.lowLatencyMode && frag.sn !== 'initSegment') { const partList = details.partList; if (partList && progressCallback) { if (targetBufferTime > frag.end && details.fragmentHint) { frag = details.fragmentHint; } const partIndex = this.getNextPart(partList, frag, targetBufferTime); if (partIndex > -1) { const part = partList[partIndex]; this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`); this.nextLoadPosition = part.start + part.duration; this.state = State.FRAG_LOADING; let _result; if (keyLoadingPromise) { _result = keyLoadingPromise.then(keyLoadedData => { if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) { return null; } return this.doFragPartsLoad(frag, part, level, progressCallback); }).catch(error => this.handleFragLoadError(error)); } else { _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch(error => this.handleFragLoadError(error)); } this.hls.trigger(Events.FRAG_LOADING, { frag, part, targetBufferTime }); if (this.fragCurrent === null) { return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING parts`)); } return _result; } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) { // Fragment hint has no parts return Promise.resolve(null); } } } this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`); // Don't update nextLoadPosition for fragments which are not buffered if (isFiniteNumber(frag.sn) && !this.bitrateTest) { this.nextLoadPosition = frag.start + frag.duration; } this.state = State.FRAG_LOADING; // Load key before streaming fragment data const dataOnProgress = this.config.progressive; let result; if (dataOnProgress && keyLoadingPromise) { result = keyLoadingPromise.then(keyLoadedData => { if (!keyLoadedData || this.fragContextChanged(keyLoadedData == null ? void 0 : keyLoadedData.frag)) { return null; } return this.fragmentLoader.load(frag, progressCallback); }).catch(error => this.handleFragLoadError(error)); } else { // load unencrypted fragment data with progress event, // or handle fragment result after key and fragment are finished loading result = Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback : undefined), keyLoadingPromise]).then(([fragLoadedData]) => { if (!dataOnProgress && fragLoadedData && progressCallback) { progressCallback(fragLoadedData); } return fragLoadedData; }).catch(error => this.handleFragLoadError(error)); } this.hls.trigger(Events.FRAG_LOADING, { frag, targetBufferTime }); if (this.fragCurrent === null) { return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING`)); } return result; } doFragPartsLoad(frag, fromPart, level, progressCallback) { return new Promise((resolve, reject) => { var _level$details; const partsLoaded = []; const initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList; const loadPart = part => { this.fragmentLoader.loadPart(frag, part, progressCallback).then(partLoadedData => { partsLoaded[part.index] = partLoadedData; const loadedPart = partLoadedData.part; this.hls.trigger(Events.FRAG_LOADED, partLoadedData); const nextPart = getPartWith(level, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1); if (nextPart) { loadPart(nextPart); } else { return resolve({ frag, part: loadedPart, partsLoaded }); } }).catch(reject); }; loadPart(fromPart); }); } handleFragLoadError(error) { if ('data' in error) { const data = error.data; if (error.data && data.details === ErrorDetails.INTERNAL_ABORTED) { this.handleFragLoadAborted(data.frag, data.part); } else { this.hls.trigger(Events.ERROR, data); } } else { this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, err: error, error, fatal: true }); } return null; } _handleTransmuxerFlush(chunkMeta) { const context = this.getCurrentContext(chunkMeta); if (!context || this.state !== State.PARSING) { if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) { this.state = State.IDLE; } return; } const { frag, part, level } = context; const now = self.performance.now(); frag.stats.parsing.end = now; if (part) { part.stats.parsing.end = now; } this.updateLevelTiming(frag, part, level, chunkMeta.partial); } getCurrentContext(chunkMeta) { const { levels, fragCurrent } = this; const { level: levelIndex, sn, part: partIndex } = chunkMeta; if (!(levels != null && levels[levelIndex])) { this.warn(`Levels object was unset while buffering fragment ${sn} of level ${levelIndex}. The current chunk will not be buffered.`); return null; } const level = levels[levelIndex]; const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null; const frag = part ? part.fragment : getFragmentWithSN(level, sn, fragCurrent); if (!frag) { return null; } if (fragCurrent && fragCurrent !== frag) { frag.stats = fragCurrent.stats; } return { frag, part, level }; } bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) { var _buffer; if (!data || this.state !== State.PARSING) { return; } const { data1, data2 } = data; let buffer = data1; if (data1 && data2) { // Combine the moof + mdat so that we buffer with a single append buffer = appendUint8Array(data1, data2); } if (!((_buffer = buffer) != null && _buffer.length)) { return; } const segment = { type: data.type, frag, part, chunkMeta, parent: frag.type, data: buffer }; this.hls.trigger(Events.BUFFER_APPENDING, segment); if (data.dropped && data.independent && !part) { if (noBacktracking) { return; } // Clear buffer so that we reload previous segments sequentially if required this.flushBufferGap(frag); } } flushBufferGap(frag) { const media = this.media; if (!media) { return; } // If currentTime is not buffered, clear the back buffer so that we can backtrack as much as needed if (!BufferHelper.isBuffered(media, media.currentTime)) { this.flushMainBuffer(0, frag.start); return; } // Remove back-buffer without interrupting playback to allow back tracking const currentTime = media.currentTime; const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const fragDuration = frag.duration; const segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25); const start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction); if (frag.start - start > segmentFraction) { this.flushMainBuffer(start, frag.start); } } getFwdBufferInfo(bufferable, type) { const pos = this.getLoadPosition(); if (!isFiniteNumber(pos)) { return null; } return this.getFwdBufferInfoAtPos(bufferable, pos, type); } getFwdBufferInfoAtPos(bufferable, pos, type) { const { config: { maxBufferHole } } = this; const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole); // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) { const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type); if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) { return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole)); } } return bufferInfo; } getMaxBufferLength(levelBitrate) { const { config } = this; let maxBufLen; if (levelBitrate) { maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength); } else { maxBufLen = config.maxBufferLength; } return Math.min(maxBufLen, config.maxMaxBufferLength); } reduceMaxBufferLength(threshold, fragDuration) { const config = this.config; const minLength = Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration); const reducedLength = Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength); if (reducedLength >= minLength) { // reduce max buffer length as it might be too high. we do this to avoid loop flushing ... config.maxMaxBufferLength = reducedLength; this.warn(`Reduce max buffer length to ${reducedLength}s`); return true; } return false; } getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) { const fragOrPart = this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN); if (fragOrPart && 'fragment' in fragOrPart) { return fragOrPart.fragment; } return fragOrPart; } getNextFragment(pos, levelDetails) { const fragments = levelDetails.fragments; const fragLen = fragments.length; if (!fragLen) { return null; } // find fragment index, contiguous with end of buffer position const { config } = this; const start = fragments[0].start; let frag; if (levelDetails.live) { const initialLiveManifestSize = config.initialLiveManifestSize; if (fragLen < initialLiveManifestSize) { this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`); return null; } // The real fragment start times for a live stream are only known after the PTS range for that level is known. // In order to discover the range, we load the best matching fragment for that level and demux it. // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that // we get the fragment matching that start time if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) { frag = this.getInitialLiveFragment(levelDetails, fragments); this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos; } } else if (pos <= start) { // VoD playlist: if loadPosition before start of playlist, load first fragment frag = fragments[0]; } // If we haven't run into any special cases already, just load the fragment most closely matching the requested position if (!frag) { const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd; frag = this.getFragmentAtPosition(pos, end, levelDetails); } return this.mapToInitFragWhenRequired(frag); } isLoopLoading(frag, targetBufferTime) { const trackerState = this.fragmentTracker.getState(frag); return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime; } getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) { const gapStart = frag.gap; const nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails); if (nextFragment === null) { return nextFragment; } frag = nextFragment; if (gapStart && frag && !frag.gap && bufferInfo.nextStart) { // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType); if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) { // Returning here might result in not finding an audio and video candiate to skip to this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`); return null; } } return frag; } mapToInitFragWhenRequired(frag) { // If an initSegment is present, it must be buffered first if (frag != null && frag.initSegment && !(frag != null && frag.initSegment.data) && !this.bitrateTest) { return frag.initSegment; } return frag; } getNextPart(partList, frag, targetBufferTime) { let nextPart = -1; let contiguous = false; let independentAttrOmitted = true; for (let i = 0, len = partList.length; i < len; i++) { const part = partList[i]; independentAttrOmitted = independentAttrOmitted && !part.independent; if (nextPart > -1 && targetBufferTime < part.start) { break; } const loaded = part.loaded; if (loaded) { nextPart = -1; } else if ((contiguous || part.independent || independentAttrOmitted) && part.fragment === frag) { nextPart = i; } contiguous = loaded; } return nextPart; } loadedEndOfParts(partList, targetBufferTime) { const lastPart = partList[partList.length - 1]; return lastPart && targetBufferTime > lastPart.start && lastPart.loaded; } /* This method is used find the best matching first fragment for a live playlist. This fragment is used to calculate the "sliding" of the playlist, which is its offset from the start of playback. After sliding we can compute the real start and end times for each fragment in the playlist (after which this method will not need to be called). */ getInitialLiveFragment(levelDetails, fragments) { const fragPrevious = this.fragPrevious; let frag = null; if (fragPrevious) { if (levelDetails.hasProgramDateTime) { // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding this.log(`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`); frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance); } if (!frag) { // SN does not need to be accurate between renditions, but depending on the packaging it may be so. const targetSN = fragPrevious.sn + 1; if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) { const fragNext = fragments[targetSN - levelDetails.startSN]; // Ensure that we're staying within the continuity range, since PTS resets upon a new range if (fragPrevious.cc === fragNext.cc) { frag = fragNext; this.log(`Live playlist, switching playlist, load frag with next SN: ${frag.sn}`); } } // It's important to stay within the continuity range if available; otherwise the fragments in the playlist // will have the wrong start times if (!frag) { frag = findFragWithCC(fragments, fragPrevious.cc); if (frag) { this.log(`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`); } } } } else { // Find a new start fragment when fragPrevious is null const liveStart = this.hls.liveSyncPosition; if (liveStart !== null) { frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails); } } return frag; } /* This method finds the best matching fragment given the provided position. */ getFragmentAtPosition(bufferEnd, end, levelDetails) { const { config } = this; let { fragPrevious } = this; let { fragments, endSN } = levelDetails; const { fragmentHint } = levelDetails; const { maxFragLookUpTolerance } = config; const partList = levelDetails.partList; const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint); if (loadingParts && fragmentHint && !this.bitrateTest) { // Include incomplete fragment with parts at end fragments = fragments.concat(fragmentHint); endSN = fragmentHint.sn; } let frag; if (bufferEnd < end) { const lookupTolerance = bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance; // Remove the tolerance if it would put the bufferEnd past the actual end of stream // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE) frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance); } else { // reach end of playlist frag = fragments[fragments.length - 1]; } if (frag) { const curSNIdx = frag.sn - levelDetails.startSN; // Move fragPrevious forward to support forcing the next fragment to load // when the buffer catches up to a previously buffered range. const fragState = this.fragmentTracker.getState(frag); if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) { fragPrevious = frag; } if (fragPrevious && frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn)) { // Force the next fragment to load if the previous one was already selected. This can occasionally happen with // non-uniform fragment durations const sameLevel = fragPrevious && frag.level === fragPrevious.level; if (sameLevel) { const nextFrag = fragments[curSNIdx + 1]; if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) { frag = nextFrag; } else { frag = null; } } } } return frag; } synchronizeToLiveEdge(levelDetails) { const { config, media } = this; if (!media) { return; } const liveSyncPosition = this.hls.liveSyncPosition; const currentTime = media.currentTime; const start = levelDetails.fragments[0].start; const end = levelDetails.edge; const withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end; // Continue if we can seek forward to sync position or if current time is outside of sliding window if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) { // Continue if buffer is starving or if current time is behind max latency const maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) { if (!this.loadedmetadata) { this.nextLoadPosition = liveSyncPosition; } // Only seek if ready and there is not a significant forward buffer available for playback if (media.readyState) { this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to : ${liveSyncPosition.toFixed(3)}`); media.currentTime = liveSyncPosition; } } } } alignPlaylists(details, previousDetails, switchDetails) { // FIXME: If not for `shouldAlignOnDiscontinuities` requiring fragPrevious.cc, // this could all go in level-helper mergeDetails() const length = details.fragments.length; if (!length) { this.warn(`No fragments in live playlist`); return 0; } const slidingStart = details.fragments[0].start; const firstLevelLoad = !previousDetails; const aligned = details.alignedSliding && isFiniteNumber(slidingStart); if (firstLevelLoad || !aligned && !slidingStart) { const { fragPrevious } = this; alignStream(fragPrevious, switchDetails, details); const alignedSlidingStart = details.fragments[0].start; this.log(`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${previousDetails ? previousDetails.startSN : 'na'}->${details.startSN} prev-sn: ${fragPrevious ? fragPrevious.sn : 'na'} fragments: ${length}`); return alignedSlidingStart; } return slidingStart; } waitForCdnTuneIn(details) { // Wait for Low-Latency CDN Tune-in to get an updated playlist const advancePartLimit = 3; return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit); } setStartPosition(details, sliding) { // compute start position if set to -1. use it straight away if value is defined let startPosition = this.startPosition; if (startPosition < sliding) { startPosition = -1; } if (startPosition === -1 || this.lastCurrentTime === -1) { // Use Playlist EXT-X-START:TIME-OFFSET when set // Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match const offsetInMultivariantPlaylist = this.startTimeOffset !== null; const startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset; if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) { startPosition = sliding + startTimeOffset; if (startTimeOffset < 0) { startPosition += details.totalduration; } startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration); this.log(`Start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant' : 'media'} playlist, adjust startPosition to ${startPosition}`); this.startPosition = startPosition; } else if (details.live) { // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has // not been specified via the config or an as an argument to startLoad (#3736). startPosition = this.hls.liveSyncPosition || sliding; } else { this.startPosition = startPosition = 0; } this.lastCurrentTime = startPosition; } this.nextLoadPosition = startPosition; } getLoadPosition() { const { media } = this; // if we have not yet loaded any fragment, start loading from start position let pos = 0; if (this.loadedmetadata && media) { pos = media.currentTime; } else if (this.nextLoadPosition) { pos = this.nextLoadPosition; } return pos; } handleFragLoadAborted(frag, part) { if (this.transmuxer && frag.sn !== 'initSegment' && frag.stats.aborted) { this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} was aborted`); this.resetFragmentLoading(frag); } } resetFragmentLoading(frag) { if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) { this.state = State.IDLE; } } onFragmentOrKeyLoadError(filterType, data) { if (data.chunkMeta && !data.frag) { const context = this.getCurrentContext(data.chunkMeta); if (context) { data.frag = context.frag; } } const frag = data.frag; // Handle frag error related to caller's filterType if (!frag || frag.type !== filterType || !this.levels) { return; } if (this.fragContextChanged(frag)) { var _this$fragCurrent2; this.warn(`Frag load error must match current frag to retry ${frag.url} > ${(_this$fragCurrent2 = this.fragCurrent) == null ? void 0 : _this$fragCurrent2.url}`); return; } const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP; if (gapTagEncountered) { this.fragmentTracker.fragBuffered(frag, true); } // keep retrying until the limit will be reached const errorAction = data.errorAction; const { action, retryCount = 0, retryConfig } = errorAction || {}; if (errorAction && action === NetworkErrorAction.RetryRequest && retryConfig) { this.resetStartWhenNotLoaded(this.levelLastLoaded); const delay = getRetryDelay(retryConfig, retryCount); this.warn(`Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${data.details}, retrying loading ${retryCount + 1}/${retryConfig.maxNumRetry} in ${delay}ms`); errorAction.resolved = true; this.retryDate = self.performance.now() + delay; this.state = State.FRAG_LOADING_WAITING_RETRY; } else if (retryConfig && errorAction) { this.resetFragmentErrors(filterType); if (retryCount < retryConfig.maxNumRetry) { // Network retry is skipped when level switch is preferred if (!gapTagEncountered && action !== NetworkErrorAction.RemoveAlternatePermanently) { errorAction.resolved = true; } } else { logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`); return; } } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) { this.state = State.WAITING_LEVEL; } else { this.state = State.ERROR; } // Perform next async tick sooner to speed up error action resolution this.tickImmediate(); } reduceLengthAndFlushBuffer(data) { // if in appending state if (this.state === State.PARSING || this.state === State.PARSED) { const frag = data.frag; const playlistType = data.parent; const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType); // 0.5 : tolerance needed as some browsers stalls playback before reaching buffered end // reduce max buf len if current position is buffered const buffered = bufferedInfo && bufferedInfo.len > 0.5; if (buffered) { this.reduceMaxBufferLength(bufferedInfo.len, (frag == null ? void 0 : frag.duration) || 10); } const flushBuffer = !buffered; if (flushBuffer) { // current position is not buffered, but browser is still complaining about buffer full error // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708 // in that case flush the whole audio buffer to recover this.warn(`Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`); } if (frag) { this.fragmentTracker.removeFragment(frag); this.nextLoadPosition = frag.start; } this.resetLoadingState(); return flushBuffer; } return false; } resetFragmentErrors(filterType) { if (filterType === PlaylistLevelType.AUDIO) { // Reset current fragment since audio track audio is essential and may not have a fail-over track this.fragCurrent = null; } // Fragment errors that result in a level switch or redundant fail-over // should reset the stream controller state to idle if (!this.loadedmetadata) { this.startFragRequested = false; } if (this.state !== State.STOPPED) { this.state = State.IDLE; } } afterBufferFlushed(media, bufferType, playlistType) { if (!media) { return; } // After successful buffer flushing, filter flushed fragments from bufferedFrags use mediaBuffered instead of media // (so that we will check against video.buffered ranges in case of alt audio track) const bufferedTimeRanges = BufferHelper.getBuffered(media); this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType); if (this.state === State.ENDED) { this.resetLoadingState(); } } resetLoadingState() { this.log('Reset loading state'); this.fragCurrent = null; this.fragPrevious = null; this.state = State.IDLE; } resetStartWhenNotLoaded(level) { // if loadedmetadata is not set, it means that first frag request failed // in that case, reset startFragRequested flag if (!this.loadedmetadata) { this.startFragRequested = false; const details = level ? level.details : null; if (details != null && details.live) { // Update the start position and return to IDLE to recover live start this.startPosition = -1; this.setStartPosition(details, 0); this.resetLoadingState(); } else { this.nextLoadPosition = this.startPosition; } } } resetWhenMissingContext(chunkMeta) { this.warn(`The loading context changed while buffering fragment ${chunkMeta.sn} of level ${chunkMeta.level}. This chunk will not be buffered.`); this.removeUnbufferedFrags(); this.resetStartWhenNotLoaded(this.levelLastLoaded); this.resetLoadingState(); } removeUnbufferedFrags(start = 0) { this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true); } updateLevelTiming(frag, part, level, partial) { var _this$transmuxer; const details = level.details; if (!details) { this.warn('level.details undefined'); return; } const parsed = Object.keys(frag.elementaryStreams).reduce((result, type) => { const info = frag.elementaryStreams[type]; if (info) { const parsedDuration = info.endPTS - info.startPTS; if (parsedDuration <= 0) { // Destroy the transmuxer after it's next time offset failed to advance because duration was <= 0. // The new transmuxer will be configured with a time offset matching the next fragment start, // preventing the timeline from shifting. this.warn(`Could not parse fragment ${frag.sn} ${type} duration reliably (${parsedDuration})`); return result || false; } const drift = partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS); this.hls.trigger(Events.LEVEL_PTS_UPDATED, { details, level, drift, type, frag, start: info.startPTS, end: info.endPTS }); return true; } return result; }, false); if (!parsed && ((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null) { const error = new Error(`Found no media in fragment ${frag.sn} of level ${frag.level} resetting transmuxer to fallback to playlist timing`); if (level.fragmentError === 0) { // Mark and track the odd empty segment as a gap to avoid reloading level.fragmentError++; frag.gap = true; this.fragmentTracker.removeFragment(frag); this.fragmentTracker.fragBuffered(frag, true); } this.warn(error.message); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, error, frag, reason: `Found no media in msn ${frag.sn} of level "${level.url}"` }); if (!this.hls) { return; } this.resetTransmuxer(); // For this error fallthrough. Marking parsed will allow advancing to next fragment. } this.state = State.PARSED; this.hls.trigger(Events.FRAG_PARSED, { frag, part }); } resetTransmuxer() { if (this.transmuxer) { this.transmuxer.destroy(); this.transmuxer = null; } } recoverWorkerError(data) { if (data.event === 'demuxerWorker') { this.fragmentTracker.removeAllFragments(); this.resetTransmuxer(); this.resetStartWhenNotLoaded(this.levelLastLoaded); this.resetLoadingState(); } } set state(nextState) { const previousState = this._state; if (previousState !== nextState) { this._state = nextState; this.log(`${previousState}->${nextState}`); } } get state() { return this._state; } } class ChunkCache { constructor() { this.chunks = []; this.dataLength = 0; } push(chunk) { this.chunks.push(chunk); this.dataLength += chunk.length; } flush() { const { chunks, dataLength } = this; let result; if (!chunks.length) { return new Uint8Array(0); } else if (chunks.length === 1) { result = chunks[0]; } else { result = concatUint8Arrays(chunks, dataLength); } this.reset(); return result; } reset() { this.chunks.length = 0; this.dataLength = 0; } } function concatUint8Arrays(chunks, dataLength) { const result = new Uint8Array(dataLength); let offset = 0; for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; result.set(chunk, offset); offset += chunk.length; } return result; } // ensure the worker ends up in the bundle // If the worker should not be included this gets aliased to empty.js function hasUMDWorker() { return typeof __HLS_WORKER_BUNDLE__ === 'function'; } function injectWorker() { const blob = new self.Blob([`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`], { type: 'text/javascript' }); const objectURL = self.URL.createObjectURL(blob); const worker = new self.Worker(objectURL); return { worker, objectURL }; } function loadWorker(path) { const scriptURL = new self.URL(path, self.location.href).href; const worker = new self.Worker(scriptURL); return { worker, scriptURL }; } function dummyTrack(type = '', inputTimeScale = 90000) { return { type, id: -1, pid: -1, inputTimeScale, sequenceNumber: -1, samples: [], dropped: 0 }; } class BaseAudioDemuxer { constructor() { this._audioTrack = void 0; this._id3Track = void 0; this.frameIndex = 0; this.cachedData = null; this.basePTS = null; this.initPTS = null; this.lastPTS = null; } resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { this._id3Track = { type: 'id3', id: 3, pid: -1, inputTimeScale: 90000, sequenceNumber: 0, samples: [], dropped: 0 }; } resetTimeStamp(deaultTimestamp) { this.initPTS = deaultTimestamp; this.resetContiguity(); } resetContiguity() { this.basePTS = null; this.lastPTS = null; this.frameIndex = 0; } canParse(data, offset) { return false; } appendFrame(track, data, offset) {} // feed incoming data to the front of the parsing pipeline demux(data, timeOffset) { if (this.cachedData) { data = appendUint8Array(this.cachedData, data); this.cachedData = null; } let id3Data = getID3Data(data, 0); let offset = id3Data ? id3Data.length : 0; let lastDataIndex; const track = this._audioTrack; const id3Track = this._id3Track; const timestamp = id3Data ? getTimeStamp(id3Data) : undefined; const length = data.length; if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) { this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS); this.lastPTS = this.basePTS; } if (this.lastPTS === null) { this.lastPTS = this.basePTS; } // more expressive than alternative: id3Data?.length if (id3Data && id3Data.length > 0) { id3Track.samples.push({ pts: this.lastPTS, dts: this.lastPTS, data: id3Data, type: MetadataSchema.audioId3, duration: Number.POSITIVE_INFINITY }); } while (offset < length) { if (this.canParse(data, offset)) { const frame = this.appendFrame(track, data, offset); if (frame) { this.frameIndex++; this.lastPTS = frame.sample.pts; offset += frame.length; lastDataIndex = offset; } else { offset = length; } } else if (canParse$2(data, offset)) { // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data id3Data = getID3Data(data, offset); id3Track.samples.push({ pts: this.lastPTS, dts: this.lastPTS, data: id3Data, type: MetadataSchema.audioId3, duration: Number.POSITIVE_INFINITY }); offset += id3Data.length; lastDataIndex = offset; } else { offset++; } if (offset === length && lastDataIndex !== length) { const partialData = sliceUint8(data, lastDataIndex); if (this.cachedData) { this.cachedData = appendUint8Array(this.cachedData, partialData); } else { this.cachedData = partialData; } } } return { audioTrack: track, videoTrack: dummyTrack(), id3Track, textTrack: dummyTrack() }; } demuxSampleAes(data, keyData, timeOffset) { return Promise.reject(new Error(`[${this}] This demuxer does not support Sample-AES decryption`)); } flush(timeOffset) { // Parse cache in case of remaining frames. const cachedData = this.cachedData; if (cachedData) { this.cachedData = null; this.demux(cachedData, 0); } return { audioTrack: this._audioTrack, videoTrack: dummyTrack(), id3Track: this._id3Track, textTrack: dummyTrack() }; } destroy() {} } /** * Initialize PTS * <p> * use timestamp unless it is undefined, NaN or Infinity * </p> */ const initPTSFn = (timestamp, timeOffset, initPTS) => { if (isFiniteNumber(timestamp)) { return timestamp * 90; } const init90kHz = initPTS ? initPTS.baseTime * 90000 / initPTS.timescale : 0; return timeOffset * 90000 + init90kHz; }; /** * ADTS parser helper * @link https://wiki.multimedia.cx/index.php?title=ADTS */ function getAudioConfig(observer, data, offset, audioCodec) { let adtsObjectType; let adtsExtensionSamplingIndex; let adtsChannelConfig; let config; const userAgent = navigator.userAgent.toLowerCase(); const manifestCodec = audioCodec; const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; // byte 2 adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1; const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2; if (adtsSamplingIndex > adtsSamplingRates.length - 1) { const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`); observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: true, error, reason: error.message }); return; } adtsChannelConfig = (data[offset + 2] & 0x01) << 2; // byte 3 adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6; logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`); // firefox: freq less than 24kHz = AAC SBR (HE-AAC) if (/firefox/i.test(userAgent)) { if (adtsSamplingIndex >= 6) { adtsObjectType = 5; config = new Array(4); // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies // there is a factor 2 between frame sample rate and output sample rate // multiply frequency by 2 (see table below, equivalent to substract 3) adtsExtensionSamplingIndex = adtsSamplingIndex - 3; } else { adtsObjectType = 2; config = new Array(2); adtsExtensionSamplingIndex = adtsSamplingIndex; } // Android : always use AAC } else if (userAgent.indexOf('android') !== -1) { adtsObjectType = 2; config = new Array(2); adtsExtensionSamplingIndex = adtsSamplingIndex; } else { /* for other browsers (Chrome/Vivaldi/Opera ...) always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...) */ adtsObjectType = 5; config = new Array(4); // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz) if (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || audioCodec.indexOf('mp4a.40.5') !== -1) || !audioCodec && adtsSamplingIndex >= 6) { // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies // there is a factor 2 between frame sample rate and output sample rate // multiply frequency by 2 (see table below, equivalent to substract 3) adtsExtensionSamplingIndex = adtsSamplingIndex - 3; } else { // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio) // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo. if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSamplingIndex >= 6 && adtsChannelConfig === 1 || /vivaldi/i.test(userAgent)) || !audioCodec && adtsChannelConfig === 1) { adtsObjectType = 2; config = new Array(2); } adtsExtensionSamplingIndex = adtsSamplingIndex; } } /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig() Audio Profile / Audio Object Type 0: Null 1: AAC Main 2: AAC LC (Low Complexity) 3: AAC SSR (Scalable Sample Rate) 4: AAC LTP (Long Term Prediction) 5: SBR (Spectral Band Replication) 6: AAC Scalable sampling freq 0: 96000 Hz 1: 88200 Hz 2: 64000 Hz 3: 48000 Hz 4: 44100 Hz 5: 32000 Hz 6: 24000 Hz 7: 22050 Hz 8: 16000 Hz 9: 12000 Hz 10: 11025 Hz 11: 8000 Hz 12: 7350 Hz 13: Reserved 14: Reserved 15: frequency is written explictly Channel Configurations These are the channel configurations: 0: Defined in AOT Specifc Config 1: 1 channel: front-center 2: 2 channels: front-left, front-right */ // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1 config[0] = adtsObjectType << 3; // samplingFrequencyIndex config[0] |= (adtsSamplingIndex & 0x0e) >> 1; config[1] |= (adtsSamplingIndex & 0x01) << 7; // channelConfiguration config[1] |= adtsChannelConfig << 3; if (adtsObjectType === 5) { // adtsExtensionSamplingIndex config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1; config[2] = (adtsExtensionSamplingIndex & 0x01) << 7; // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ??? // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc config[2] |= 2 << 2; config[3] = 0; } return { config, samplerate: adtsSamplingRates[adtsSamplingIndex], channelCount: adtsChannelConfig, codec: 'mp4a.40.' + adtsObjectType, manifestCodec }; } function isHeaderPattern$1(data, offset) { return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0; } function getHeaderLength(data, offset) { return data[offset + 1] & 0x01 ? 7 : 9; } function getFullFrameLength(data, offset) { return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5; } function canGetFrameLength(data, offset) { return offset + 5 < data.length; } function isHeader$1(data, offset) { // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 // Layer bits (position 14 and 15) in header should be always 0 for ADTS // More info https://wiki.multimedia.cx/index.php?title=ADTS return offset + 1 < data.length && isHeaderPattern$1(data, offset); } function canParse$1(data, offset) { return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset; } function probe$1(data, offset) { // same as isHeader but we also check that ADTS frame follows last ADTS frame // or end of data is reached if (isHeader$1(data, offset)) { // ADTS header Length const headerLength = getHeaderLength(data, offset); if (offset + headerLength >= data.length) { return false; } // ADTS frame Length const frameLength = getFullFrameLength(data, offset); if (frameLength <= headerLength) { return false; } const newOffset = offset + frameLength; return newOffset === data.length || isHeader$1(data, newOffset); } return false; } function initTrackConfig(track, observer, data, offset, audioCodec) { if (!track.samplerate) { const config = getAudioConfig(observer, data, offset, audioCodec); if (!config) { return; } track.config = config.config; track.samplerate = config.samplerate; track.channelCount = config.channelCount; track.codec = config.codec; track.manifestCodec = config.manifestCodec; logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`); } } function getFrameDuration(samplerate) { return 1024 * 90000 / samplerate; } function parseFrameHeader(data, offset) { // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header const headerLength = getHeaderLength(data, offset); if (offset + headerLength <= data.length) { // retrieve frame size const frameLength = getFullFrameLength(data, offset) - headerLength; if (frameLength > 0) { // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`); return { headerLength, frameLength }; } } } function appendFrame$2(track, data, offset, pts, frameIndex) { const frameDuration = getFrameDuration(track.samplerate); const stamp = pts + frameIndex * frameDuration; const header = parseFrameHeader(data, offset); let unit; if (header) { const { frameLength, headerLength } = header; const _length = headerLength + frameLength; const missing = Math.max(0, offset + _length - data.length); // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`); if (missing) { unit = new Uint8Array(_length - headerLength); unit.set(data.subarray(offset + headerLength, data.length), 0); } else { unit = data.subarray(offset + headerLength, offset + _length); } const _sample = { unit, pts: stamp }; if (!missing) { track.samples.push(_sample); } return { sample: _sample, length: _length, missing }; } // overflow incomplete header const length = data.length - offset; unit = new Uint8Array(length); unit.set(data.subarray(offset, data.length), 0); const sample = { unit, pts: stamp }; return { sample, length, missing: -1 }; } /** * MPEG parser helper */ let chromeVersion$1 = null; const BitratesMap = [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]; const SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000]; const SamplesCoefficients = [ // MPEG 2.5 [0, // Reserved 72, // Layer3 144, // Layer2 12 // Layer1 ], // Reserved [0, // Reserved 0, // Layer3 0, // Layer2 0 // Layer1 ], // MPEG 2 [0, // Reserved 72, // Layer3 144, // Layer2 12 // Layer1 ], // MPEG 1 [0, // Reserved 144, // Layer3 144, // Layer2 12 // Layer1 ]]; const BytesInSlot = [0, // Reserved 1, // Layer3 1, // Layer2 4 // Layer1 ]; function appendFrame$1(track, data, offset, pts, frameIndex) { // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference if (offset + 24 > data.length) { return; } const header = parseHeader(data, offset); if (header && offset + header.frameLength <= data.length) { const frameDuration = header.samplesPerFrame * 90000 / header.sampleRate; const stamp = pts + frameIndex * frameDuration; const sample = { unit: data.subarray(offset, offset + header.frameLength), pts: stamp, dts: stamp }; track.config = []; track.channelCount = header.channelCount; track.samplerate = header.sampleRate; track.samples.push(sample); return { sample, length: header.frameLength, missing: 0 }; } } function parseHeader(data, offset) { const mpegVersion = data[offset + 1] >> 3 & 3; const mpegLayer = data[offset + 1] >> 1 & 3; const bitRateIndex = data[offset + 2] >> 4 & 15; const sampleRateIndex = data[offset + 2] >> 2 & 3; if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) { const paddingBit = data[offset + 2] >> 1 & 1; const channelMode = data[offset + 3] >> 6; const columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4; const bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000; const columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2; const sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex]; const channelCount = channelMode === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono) const sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer]; const bytesInSlot = BytesInSlot[mpegLayer]; const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot; const frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot; if (chromeVersion$1 === null) { const userAgent = navigator.userAgent || ''; const result = userAgent.match(/Chrome\/(\d+)/i); chromeVersion$1 = result ? parseInt(result[1]) : 0; } const needChromeFix = !!chromeVersion$1 && chromeVersion$1 <= 87; if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) { // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00) data[offset + 3] = data[offset + 3] | 0x80; } return { sampleRate, channelCount, frameLength, samplesPerFrame }; } } function isHeaderPattern(data, offset) { return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00; } function isHeader(data, offset) { // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1 // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III) // More info http://www.mp3-tech.org/programmer/frame_header.html return offset + 1 < data.length && isHeaderPattern(data, offset); } function canParse(data, offset) { const headerSize = 4; return isHeaderPattern(data, offset) && headerSize <= data.length - offset; } function probe(data, offset) { // same as isHeader but we also check that MPEG frame follows last MPEG frame // or end of data is reached if (offset + 1 < data.length && isHeaderPattern(data, offset)) { // MPEG header Length const headerLength = 4; // MPEG frame Length const header = parseHeader(data, offset); let frameLength = headerLength; if (header != null && header.frameLength) { frameLength = header.frameLength; } const newOffset = offset + frameLength; return newOffset === data.length || isHeader(data, newOffset); } return false; } /** * AAC demuxer */ class AACDemuxer extends BaseAudioDemuxer { constructor(observer, config) { super(); this.observer = void 0; this.config = void 0; this.observer = observer; this.config = config; } resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration); this._audioTrack = { container: 'audio/adts', type: 'audio', id: 2, pid: -1, sequenceNumber: 0, segmentCodec: 'aac', samples: [], manifestCodec: audioCodec, duration: trackDuration, inputTimeScale: 90000, dropped: 0 }; } // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS static probe(data) { if (!data) { return false; } // Check for the ADTS sync word // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 // Layer bits (position 14 and 15) in header should be always 0 for ADTS // More info https://wiki.multimedia.cx/index.php?title=ADTS const id3Data = getID3Data(data, 0); let offset = (id3Data == null ? void 0 : id3Data.length) || 0; if (probe(data, offset)) { return false; } for (let length = data.length; offset < length; offset++) { if (probe$1(data, offset)) { logger.log('ADTS sync word found !'); return true; } } return false; } canParse(data, offset) { return canParse$1(data, offset); } appendFrame(track, data, offset) { initTrackConfig(track, this.observer, data, offset, track.manifestCodec); const frame = appendFrame$2(track, data, offset, this.basePTS, this.frameIndex); if (frame && frame.missing === 0) { return frame; } } } const emsgSchemePattern = /\/emsg[-/]ID3/i; class MP4Demuxer { constructor(observer, config) { this.remainderData = null; this.timeOffset = 0; this.config = void 0; this.videoTrack = void 0; this.audioTrack = void 0; this.id3Track = void 0; this.txtTrack = void 0; this.config = config; } resetTimeStamp() {} resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { const videoTrack = this.videoTrack = dummyTrack('video', 1); const audioTrack = this.audioTrack = dummyTrack('audio', 1); const captionTrack = this.txtTrack = dummyTrack('text', 1); this.id3Track = dummyTrack('id3', 1); this.timeOffset = 0; if (!(initSegment != null && initSegment.byteLength)) { return; } const initData = parseInitSegment(initSegment); if (initData.video) { const { id, timescale, codec } = initData.video; videoTrack.id = id; videoTrack.timescale = captionTrack.timescale = timescale; videoTrack.codec = codec; } if (initData.audio) { const { id, timescale, codec } = initData.audio; audioTrack.id = id; audioTrack.timescale = timescale; audioTrack.codec = codec; } captionTrack.id = RemuxerTrackIdConfig.text; videoTrack.sampleDuration = 0; videoTrack.duration = audioTrack.duration = trackDuration; } resetContiguity() { this.remainderData = null; } static probe(data) { return hasMoofData(data); } demux(data, timeOffset) { this.timeOffset = timeOffset; // Load all data into the avc track. The CMAF remuxer will look for the data in the samples object; the rest of the fields do not matter let videoSamples = data; const videoTrack = this.videoTrack; const textTrack = this.txtTrack; if (this.config.progressive) { // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else. // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception. if (this.remainderData) { videoSamples = appendUint8Array(this.remainderData, data); } const segmentedData = segmentValidRange(videoSamples); this.remainderData = segmentedData.remainder; videoTrack.samples = segmentedData.valid || new Uint8Array(); } else { videoTrack.samples = videoSamples; } const id3Track = this.extractID3Track(videoTrack, timeOffset); textTrack.samples = parseSamples(timeOffset, videoTrack); return { videoTrack, audioTrack: this.audioTrack, id3Track, textTrack: this.txtTrack }; } flush() { const timeOffset = this.timeOffset; const videoTrack = this.videoTrack; const textTrack = this.txtTrack; videoTrack.samples = this.remainderData || new Uint8Array(); this.remainderData = null; const id3Track = this.extractID3Track(videoTrack, this.timeOffset); textTrack.samples = parseSamples(timeOffset, videoTrack); return { videoTrack, audioTrack: dummyTrack(), id3Track, textTrack: dummyTrack() }; } extractID3Track(videoTrack, timeOffset) { const id3Track = this.id3Track; if (videoTrack.samples.length) { const emsgs = findBox(videoTrack.samples, ['emsg']); if (emsgs) { emsgs.forEach(data => { const emsgInfo = parseEmsg(data); if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) { const pts = isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale; let duration = emsgInfo.eventDuration === 0xffffffff ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale; // Safari takes anything <= 0.001 seconds and maps it to Infinity if (duration <= 0.001) { duration = Number.POSITIVE_INFINITY; } const payload = emsgInfo.payload; id3Track.samples.push({ data: payload, len: payload.byteLength, dts: pts, pts: pts, type: MetadataSchema.emsg, duration: duration }); } }); } } return id3Track; } demuxSampleAes(data, keyData, timeOffset) { return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption')); } destroy() {} } const getAudioBSID = (data, offset) => { // check the bsid to confirm ac-3 | ec-3 let bsid = 0; let numBits = 5; offset += numBits; const temp = new Uint32Array(1); // unsigned 32 bit for temporary storage const mask = new Uint32Array(1); // unsigned 32 bit mask value const byte = new Uint8Array(1); // unsigned 8 bit for temporary storage while (numBits > 0) { byte[0] = data[offset]; // read remaining bits, upto 8 bits at a time const bits = Math.min(numBits, 8); const shift = 8 - bits; mask[0] = 0xff000000 >>> 24 + shift << shift; temp[0] = (byte[0] & mask[0]) >> shift; bsid = !bsid ? temp[0] : bsid << bits | temp[0]; offset += 1; numBits -= bits; } return bsid; }; class AC3Demuxer extends BaseAudioDemuxer { constructor(observer) { super(); this.observer = void 0; this.observer = observer; } resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration); this._audioTrack = { container: 'audio/ac-3', type: 'audio', id: 2, pid: -1, sequenceNumber: 0, segmentCodec: 'ac3', samples: [], manifestCodec: audioCodec, duration: trackDuration, inputTimeScale: 90000, dropped: 0 }; } canParse(data, offset) { return offset + 64 < data.length; } appendFrame(track, data, offset) { const frameLength = appendFrame(track, data, offset, this.basePTS, this.frameIndex); if (frameLength !== -1) { const sample = track.samples[track.samples.length - 1]; return { sample, length: frameLength, missing: 0 }; } } static probe(data) { if (!data) { return false; } const id3Data = getID3Data(data, 0); if (!id3Data) { return false; } // look for the ac-3 sync bytes const offset = id3Data.length; if (data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined && // check the bsid to confirm ac-3 getAudioBSID(data, offset) < 16) { return true; } return false; } } function appendFrame(track, data, start, pts, frameIndex) { if (start + 8 > data.length) { return -1; // not enough bytes left } if (data[start] !== 0x0b || data[start + 1] !== 0x77) { return -1; // invalid magic } // get sample rate const samplingRateCode = data[start + 4] >> 6; if (samplingRateCode >= 3) { return -1; // invalid sampling rate } const samplingRateMap = [48000, 44100, 32000]; const sampleRate = samplingRateMap[samplingRateCode]; // get frame size const frameSizeCode = data[start + 4] & 0x3f; const frameSizeMap = [64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975, 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728, 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920]; const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2; if (start + frameLength > data.length) { return -1; } // get channel count const channelMode = data[start + 6] >> 5; let skipCount = 0; if (channelMode === 2) { skipCount += 2; } else { if (channelMode & 1 && channelMode !== 1) { skipCount += 2; } if (channelMode & 4) { skipCount += 2; } } const lfeon = (data[start + 6] << 8 | data[start + 7]) >> 12 - skipCount & 1; const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5]; const channelCount = channelsMap[channelMode] + lfeon; // build dac3 box const bsid = data[start + 5] >> 3; const bsmod = data[start + 5] & 7; const config = new Uint8Array([samplingRateCode << 6 | bsid << 1 | bsmod >> 2, (bsmod & 3) << 6 | channelMode << 3 | lfeon << 2 | frameSizeCode >> 4, frameSizeCode << 4 & 0xe0]); const frameDuration = 1536 / sampleRate * 90000; const stamp = pts + frameIndex * frameDuration; const unit = data.subarray(start, start + frameLength); track.config = config; track.channelCount = channelCount; track.samplerate = sampleRate; track.samples.push({ unit, pts: stamp }); return frameLength; } class BaseVideoParser { constructor() { this.VideoSample = null; } createVideoSample(key, pts, dts, debug) { return { key, frame: false, pts, dts, units: [], debug, length: 0 }; } getLastNalUnit(samples) { var _VideoSample; let VideoSample = this.VideoSample; let lastUnit; // try to fallback to previous sample if current one is empty if (!VideoSample || VideoSample.units.length === 0) { VideoSample = samples[samples.length - 1]; } if ((_VideoSample = VideoSample) != null && _VideoSample.units) { const units = VideoSample.units; lastUnit = units[units.length - 1]; } return lastUnit; } pushAccessUnit(VideoSample, videoTrack) { if (VideoSample.units.length && VideoSample.frame) { // if sample does not have PTS/DTS, patch with last sample PTS/DTS if (VideoSample.pts === undefined) { const samples = videoTrack.samples; const nbSamples = samples.length; if (nbSamples) { const lastSample = samples[nbSamples - 1]; VideoSample.pts = lastSample.pts; VideoSample.dts = lastSample.dts; } else { // dropping samples, no timestamp found videoTrack.dropped++; return; } } videoTrack.samples.push(VideoSample); } if (VideoSample.debug.length) { logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug); } } } /** * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264. */ class ExpGolomb { constructor(data) { this.data = void 0; this.bytesAvailable = void 0; this.word = void 0; this.bitsAvailable = void 0; this.data = data; // the number of bytes left to examine in this.data this.bytesAvailable = data.byteLength; // the current word being examined this.word = 0; // :uint // the number of bits left to examine in the current word this.bitsAvailable = 0; // :uint } // ():void loadWord() { const data = this.data; const bytesAvailable = this.bytesAvailable; const position = data.byteLength - bytesAvailable; const workingBytes = new Uint8Array(4); const availableBytes = Math.min(4, bytesAvailable); if (availableBytes === 0) { throw new Error('no bytes available'); } workingBytes.set(data.subarray(position, position + availableBytes)); this.word = new DataView(workingBytes.buffer).getUint32(0); // track the amount of this.data that has been processed this.bitsAvailable = availableBytes * 8; this.bytesAvailable -= availableBytes; } // (count:int):void skipBits(count) { let skipBytes; // :int count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable); if (this.bitsAvailable > count) { this.word <<= count; this.bitsAvailable -= count; } else { count -= this.bitsAvailable; skipBytes = count >> 3; count -= skipBytes << 3; this.bytesAvailable -= skipBytes; this.loadWord(); this.word <<= count; this.bitsAvailable -= count; } } // (size:int):uint readBits(size) { let bits = Math.min(this.bitsAvailable, size); // :uint const valu = this.word >>> 32 - bits; // :uint if (size > 32) { logger.error('Cannot read more than 32 bits at a time'); } this.bitsAvailable -= bits; if (this.bitsAvailable > 0) { this.word <<= bits; } else if (this.bytesAvailable > 0) { this.loadWord(); } else { throw new Error('no bits available'); } bits = size - bits; if (bits > 0 && this.bitsAvailable) { return valu << bits | this.readBits(bits); } else { return valu; } } // ():uint skipLZ() { let leadingZeroCount; // :uint for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) { if ((this.word & 0x80000000 >>> leadingZeroCount) !== 0) { // the first bit of working word is 1 this.word <<= leadingZeroCount; this.bitsAvailable -= leadingZeroCount; return leadingZeroCount; } } // we exhausted word and still have not found a 1 this.loadWord(); return leadingZeroCount + this.skipLZ(); } // ():void skipUEG() { this.skipBits(1 + this.skipLZ()); } // ():void skipEG() { this.skipBits(1 + this.skipLZ()); } // ():uint readUEG() { const clz = this.skipLZ(); // :uint return this.readBits(clz + 1) - 1; } // ():int readEG() { const valu = this.readUEG(); // :int if (0x01 & valu) { // the number is odd if the low order bit is set return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 } else { return -1 * (valu >>> 1); // divide by two then make it negative } } // Some convenience functions // :Boolean readBoolean() { return this.readBits(1) === 1; } // ():int readUByte() { return this.readBits(8); } // ():int readUShort() { return this.readBits(16); } // ():int readUInt() { return this.readBits(32); } /** * Advance the ExpGolomb decoder past a scaling list. The scaling * list is optionally transmitted as part of a sequence parameter * set and is not relevant to transmuxing. * @param count the number of entries in this scaling list * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 */ skipScalingList(count) { let lastScale = 8; let nextScale = 8; let deltaScale; for (let j = 0; j < count; j++) { if (nextScale !== 0) { deltaScale = this.readEG(); nextScale = (lastScale + deltaScale + 256) % 256; } lastScale = nextScale === 0 ? lastScale : nextScale; } } /** * Read a sequence parameter set and return some interesting video * properties. A sequence parameter set is the H264 metadata that * describes the properties of upcoming video frames. * @returns an object with configuration parsed from the * sequence parameter set, including the dimensions of the * associated video frames. */ readSPS() { let frameCropLeftOffset = 0; let frameCropRightOffset = 0; let frameCropTopOffset = 0; let frameCropBottomOffset = 0; let numRefFramesInPicOrderCntCycle; let scalingListCount; let i; const readUByte = this.readUByte.bind(this); const readBits = this.readBits.bind(this); const readUEG = this.readUEG.bind(this); const readBoolean = this.readBoolean.bind(this); const skipBits = this.skipBits.bind(this); const skipEG = this.skipEG.bind(this); const skipUEG = this.skipUEG.bind(this); const skipScalingList = this.skipScalingList.bind(this); readUByte(); const profileIdc = readUByte(); // profile_idc readBits(5); // profileCompat constraint_set[0-4]_flag, u(5) skipBits(3); // reserved_zero_3bits u(3), readUByte(); // level_idc u(8) skipUEG(); // seq_parameter_set_id // some profiles have more optional data we don't need if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) { const chromaFormatIdc = readUEG(); if (chromaFormatIdc === 3) { skipBits(1); } // separate_colour_plane_flag skipUEG(); // bit_depth_luma_minus8 skipUEG(); // bit_depth_chroma_minus8 skipBits(1); // qpprime_y_zero_transform_bypass_flag if (readBoolean()) { // seq_scaling_matrix_present_flag scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; for (i = 0; i < scalingListCount; i++) { if (readBoolean()) { // seq_scaling_list_present_flag[ i ] if (i < 6) { skipScalingList(16); } else { skipScalingList(64); } } } } } skipUEG(); // log2_max_frame_num_minus4 const picOrderCntType = readUEG(); if (picOrderCntType === 0) { readUEG(); // log2_max_pic_order_cnt_lsb_minus4 } else if (picOrderCntType === 1) { skipBits(1); // delta_pic_order_always_zero_flag skipEG(); // offset_for_non_ref_pic skipEG(); // offset_for_top_to_bottom_field numRefFramesInPicOrderCntCycle = readUEG(); for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { skipEG(); } // offset_for_ref_frame[ i ] } skipUEG(); // max_num_ref_frames skipBits(1); // gaps_in_frame_num_value_allowed_flag const picWidthInMbsMinus1 = readUEG(); const picHeightInMapUnitsMinus1 = readUEG(); const frameMbsOnlyFlag = readBits(1); if (frameMbsOnlyFlag === 0) { skipBits(1); } // mb_adaptive_frame_field_flag skipBits(1); // direct_8x8_inference_flag if (readBoolean()) { // frame_cropping_flag frameCropLeftOffset = readUEG(); frameCropRightOffset = readUEG(); frameCropTopOffset = readUEG(); frameCropBottomOffset = readUEG(); } let pixelRatio = [1, 1]; if (readBoolean()) { // vui_parameters_present_flag if (readBoolean()) { // aspect_ratio_info_present_flag const aspectRatioIdc = readUByte(); switch (aspectRatioIdc) { case 1: pixelRatio = [1, 1]; break; case 2: pixelRatio = [12, 11]; break; case 3: pixelRatio = [10, 11]; break; case 4: pixelRatio = [16, 11]; break; case 5: pixelRatio = [40, 33]; break; case 6: pixelRatio = [24, 11]; break; case 7: pixelRatio = [20, 11]; break; case 8: pixelRatio = [32, 11]; break; case 9: pixelRatio = [80, 33]; break; case 10: pixelRatio = [18, 11]; break; case 11: pixelRatio = [15, 11]; break; case 12: pixelRatio = [64, 33]; break; case 13: pixelRatio = [160, 99]; break; case 14: pixelRatio = [4, 3]; break; case 15: pixelRatio = [3, 2]; break; case 16: pixelRatio = [2, 1]; break; case 255: { pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()]; break; } } } } return { width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2), height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset), pixelRatio: pixelRatio }; } readSliceType() { // skip NALu type this.readUByte(); // discard first_mb_in_slice this.readUEG(); // return slice_type return this.readUEG(); } } class AvcVideoParser extends BaseVideoParser { parseAVCPES(track, textTrack, pes, last, duration) { const units = this.parseAVCNALu(track, pes.data); let VideoSample = this.VideoSample; let push; let spsfound = false; // free pes.data to save up some memory pes.data = null; // if new NAL units found and last sample still there, let's push ... // this helps parsing streams with missing AUD (only do this if AUD never found) if (VideoSample && units.length && !track.audFound) { this.pushAccessUnit(VideoSample, track); VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, ''); } units.forEach(unit => { var _VideoSample2; switch (unit.type) { // NDR case 1: { let iskey = false; push = true; const data = unit.data; // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...) if (spsfound && data.length > 4) { // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR const sliceType = new ExpGolomb(data).readSliceType(); // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples. // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice. // I slice: A slice that is not an SI slice that is decoded using intra prediction only. // if (sliceType === 2 || sliceType === 7) { if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) { iskey = true; } } if (iskey) { var _VideoSample; // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) { this.pushAccessUnit(VideoSample, track); VideoSample = this.VideoSample = null; } } if (!VideoSample) { VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, ''); } VideoSample.frame = true; VideoSample.key = iskey; break; // IDR } case 5: push = true; // handle PES not starting with AUD // if we have frame data already, that cannot belong to the same frame, so force a push if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) { this.pushAccessUnit(VideoSample, track); VideoSample = this.VideoSample = null; } if (!VideoSample) { VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, ''); } VideoSample.key = true; VideoSample.frame = true; break; // SEI case 6: { push = true; parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples); break; // SPS } case 7: { var _track$pixelRatio, _track$pixelRatio2; push = true; spsfound = true; const sps = unit.data; const expGolombDecoder = new ExpGolomb(sps); const config = expGolombDecoder.readSPS(); if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) { track.width = config.width; track.height = config.height; track.pixelRatio = config.pixelRatio; track.sps = [sps]; track.duration = duration; const codecarray = sps.subarray(1, 4); let codecstring = 'avc1.'; for (let i = 0; i < 3; i++) { let h = codecarray[i].toString(16); if (h.length < 2) { h = '0' + h; } codecstring += h; } track.codec = codecstring; } break; } // PPS case 8: push = true; track.pps = [unit.data]; break; // AUD case 9: push = true; track.audFound = true; if (VideoSample) { this.pushAccessUnit(VideoSample, track); } VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, ''); break; // Filler Data case 12: push = true; break; default: push = false; if (VideoSample) { VideoSample.debug += 'unknown NAL ' + unit.type + ' '; } break; } if (VideoSample && push) { const units = VideoSample.units; units.push(unit); } }); // if last PES packet, push samples if (last && VideoSample) { this.pushAccessUnit(VideoSample, track); this.VideoSample = null; } } parseAVCNALu(track, array) { const len = array.byteLength; let state = track.naluState || 0; const lastState = state; const units = []; let i = 0; let value; let overflow; let unitType; let lastUnitStart = -1; let lastUnitType = 0; // logger.log('PES:' + Hex.hexDump(array)); if (state === -1) { // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet lastUnitStart = 0; // NALu type is value read from offset 0 lastUnitType = array[0] & 0x1f; state = 0; i = 1; } while (i < len) { value = array[i++]; // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case if (!state) { state = value ? 0 : 1; continue; } if (state === 1) { state = value ? 0 : 2; continue; } // here we have state either equal to 2 or 3 if (!value) { state = 3; } else if (value === 1) { overflow = i - state - 1; if (lastUnitStart >= 0) { const unit = { data: array.subarray(lastUnitStart, overflow), type: lastUnitType }; // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength); units.push(unit); } else { // lastUnitStart is undefined => this is the first start code found in this PES packet // first check if start code delimiter is overlapping between 2 PES packets, // ie it started in last packet (lastState not zero) // and ended at the beginning of this PES packet (i <= 4 - lastState) const lastUnit = this.getLastNalUnit(track.samples); if (lastUnit) { if (lastState && i <= 4 - lastState) { // start delimiter overlapping between PES packets // strip start delimiter bytes from the end of last NAL unit // check if lastUnit had a state different from zero if (lastUnit.state) { // strip last bytes lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState); } } // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit. if (overflow > 0) { // logger.log('first NALU found with overflow:' + overflow); lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow)); lastUnit.state = 0; } } } // check if we can read unit type if (i < len) { unitType = array[i] & 0x1f; // logger.log('find NALU @ offset:' + i + ',type:' + unitType); lastUnitStart = i; lastUnitType = unitType; state = 0; } else { // not enough byte to read unit type. let's read it on next PES parsing state = -1; } } else { state = 0; } } if (lastUnitStart >= 0 && state >= 0) { const unit = { data: array.subarray(lastUnitStart, len), type: lastUnitType, state: state }; units.push(unit); // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state); } // no NALu found if (units.length === 0) { // append pes.data to previous NAL unit const lastUnit = this.getLastNalUnit(track.samples); if (lastUnit) { lastUnit.data = appendUint8Array(lastUnit.data, array); } } track.naluState = state; return units; } } /** * SAMPLE-AES decrypter */ class SampleAesDecrypter { constructor(observer, config, keyData) { this.keyData = void 0; this.decrypter = void 0; this.keyData = keyData; this.decrypter = new Decrypter(config, { removePKCS7Padding: false }); } decryptBuffer(encryptedData) { return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer); } // AAC - encrypt all full 16 bytes blocks starting from offset 16 decryptAacSample(samples, sampleIndex, callback) { const curUnit = samples[sampleIndex].unit; if (curUnit.length <= 16) { // No encrypted portion in this sample (first 16 bytes is not // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html), return; } const encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16); const encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length); this.decryptBuffer(encryptedBuffer).then(decryptedBuffer => { const decryptedData = new Uint8Array(decryptedBuffer); curUnit.set(decryptedData, 16); if (!this.decrypter.isSync()) { this.decryptAacSamples(samples, sampleIndex + 1, callback); } }); } decryptAacSamples(samples, sampleIndex, callback) { for (;; sampleIndex++) { if (sampleIndex >= samples.length) { callback(); return; } if (samples[sampleIndex].unit.length < 32) { continue; } this.decryptAacSample(samples, sampleIndex, callback); if (!this.decrypter.isSync()) { return; } } } // AVC - encrypt one 16 bytes block out of ten, starting from offset 32 getAvcEncryptedData(decodedData) { const encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16; const encryptedData = new Int8Array(encryptedDataLen); let outputPos = 0; for (let inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) { encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos); } return encryptedData; } getAvcDecryptedUnit(decodedData, decryptedData) { const uint8DecryptedData = new Uint8Array(decryptedData); let inputPos = 0; for (let outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) { decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos); } return decodedData; } decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) { const decodedData = discardEPB(curUnit.data); const encryptedData = this.getAvcEncryptedData(decodedData); this.decryptBuffer(encryptedData.buffer).then(decryptedBuffer => { curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer); if (!this.decrypter.isSync()) { this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback); } }); } decryptAvcSamples(samples, sampleIndex, unitIndex, callback) { if (samples instanceof Uint8Array) { throw new Error('Cannot decrypt samples of type Uint8Array'); } for (;; sampleIndex++, unitIndex = 0) { if (sampleIndex >= samples.length) { callback(); return; } const curUnits = samples[sampleIndex].units; for (;; unitIndex++) { if (unitIndex >= curUnits.length) { break; } const curUnit = curUnits[unitIndex]; if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) { continue; } this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit); if (!this.decrypter.isSync()) { return; } } } } } const PACKET_LENGTH = 188; class TSDemuxer { constructor(observer, config, typeSupported) { this.observer = void 0; this.config = void 0; this.typeSupported = void 0; this.sampleAes = null; this.pmtParsed = false; this.audioCodec = void 0; this.videoCodec = void 0; this._duration = 0; this._pmtId = -1; this._videoTrack = void 0; this._audioTrack = void 0; this._id3Track = void 0; this._txtTrack = void 0; this.aacOverFlow = null; this.remainderData = null; this.videoParser = void 0; this.observer = observer; this.config = config; this.typeSupported = typeSupported; this.videoParser = new AvcVideoParser(); } static probe(data) { const syncOffset = TSDemuxer.syncOffset(data); if (syncOffset > 0) { logger.warn(`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`); } return syncOffset !== -1; } static syncOffset(data) { const length = data.length; let scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1; let i = 0; while (i < scanwindow) { // a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47 let foundPat = false; let packetStart = -1; let tsPackets = 0; for (let j = i; j < length; j += PACKET_LENGTH) { if (data[j] === 0x47 && (length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 0x47)) { tsPackets++; if (packetStart === -1) { packetStart = j; // First sync word found at offset, increase scan length (#5251) if (packetStart !== 0) { scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1; } } if (!foundPat) { foundPat = parsePID(data, j) === 0; } // Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501) if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) { return packetStart; } } else if (tsPackets) { // Exit if sync word found, but does not contain contiguous packets return -1; } else { break; } } i++; } return -1; } /** * Creates a track model internal to demuxer used to drive remuxing input */ static createTrack(type, duration) { return { container: type === 'video' || type === 'audio' ? 'video/mp2t' : undefined, type, id: RemuxerTrackIdConfig[type], pid: -1, inputTimeScale: 90000, sequenceNumber: 0, samples: [], dropped: 0, duration: type === 'audio' ? duration : undefined }; } /** * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start) * Resets all internal track instances of the demuxer. */ resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { this.pmtParsed = false; this._pmtId = -1; this._videoTrack = TSDemuxer.createTrack('video'); this._audioTrack = TSDemuxer.createTrack('audio', trackDuration); this._id3Track = TSDemuxer.createTrack('id3'); this._txtTrack = TSDemuxer.createTrack('text'); this._audioTrack.segmentCodec = 'aac'; // flush any partial content this.aacOverFlow = null; this.remainderData = null; this.audioCodec = audioCodec; this.videoCodec = videoCodec; this._duration = trackDuration; } resetTimeStamp() {} resetContiguity() { const { _audioTrack, _videoTrack, _id3Track } = this; if (_audioTrack) { _audioTrack.pesData = null; } if (_videoTrack) { _videoTrack.pesData = null; } if (_id3Track) { _id3Track.pesData = null; } this.aacOverFlow = null; this.remainderData = null; } demux(data, timeOffset, isSampleAes = false, flush = false) { if (!isSampleAes) { this.sampleAes = null; } let pes; const videoTrack = this._videoTrack; const audioTrack = this._audioTrack; const id3Track = this._id3Track; const textTrack = this._txtTrack; let videoPid = videoTrack.pid; let videoData = videoTrack.pesData; let audioPid = audioTrack.pid; let id3Pid = id3Track.pid; let audioData = audioTrack.pesData; let id3Data = id3Track.pesData; let unknownPID = null; let pmtParsed = this.pmtParsed; let pmtId = this._pmtId; let len = data.length; if (this.remainderData) { data = appendUint8Array(this.remainderData, data); len = data.length; this.remainderData = null; } if (len < PACKET_LENGTH && !flush) { this.remainderData = data; return { audioTrack, videoTrack, id3Track, textTrack }; } const syncOffset = Math.max(0, TSDemuxer.syncOffset(data)); len -= (len - syncOffset) % PACKET_LENGTH; if (len < data.byteLength && !flush) { this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len); } // loop through TS packets let tsPacketErrors = 0; for (let start = syncOffset; start < len; start += PACKET_LENGTH) { if (data[start] === 0x47) { const stt = !!(data[start + 1] & 0x40); const pid = parsePID(data, start); const atf = (data[start + 3] & 0x30) >> 4; // if an adaption field is present, its length is specified by the fifth byte of the TS packet header. let offset; if (atf > 1) { offset = start + 5 + data[start + 4]; // continue if there is only adaptation field if (offset === start + PACKET_LENGTH) { continue; } } else { offset = start + 4; } switch (pid) { case videoPid: if (stt) { if (videoData && (pes = parsePES(videoData))) { this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration); } videoData = { data: [], size: 0 }; } if (videoData) { videoData.data.push(data.subarray(offset, start + PACKET_LENGTH)); videoData.size += start + PACKET_LENGTH - offset; } break; case audioPid: if (stt) { if (audioData && (pes = parsePES(audioData))) { switch (audioTrack.segmentCodec) { case 'aac': this.parseAACPES(audioTrack, pes); break; case 'mp3': this.parseMPEGPES(audioTrack, pes); break; case 'ac3': { this.parseAC3PES(audioTrack, pes); } break; } } audioData = { data: [], size: 0 }; } if (audioData) { audioData.data.push(data.subarray(offset, start + PACKET_LENGTH)); audioData.size += start + PACKET_LENGTH - offset; } break; case id3Pid: if (stt) { if (id3Data && (pes = parsePES(id3Data))) { this.parseID3PES(id3Track, pes); } id3Data = { data: [], size: 0 }; } if (id3Data) { id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH)); id3Data.size += start + PACKET_LENGTH - offset; } break; case 0: if (stt) { offset += data[offset] + 1; } pmtId = this._pmtId = parsePAT(data, offset); // logger.log('PMT PID:' + this._pmtId); break; case pmtId: { if (stt) { offset += data[offset] + 1; } const parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer); // only update track id if track PID found while parsing PMT // this is to avoid resetting the PID to -1 in case // track PID transiently disappears from the stream // this could happen in case of transient missing audio samples for example // NOTE this is only the PID of the track as found in TS, // but we are not using this for MP4 track IDs. videoPid = parsedPIDs.videoPid; if (videoPid > 0) { videoTrack.pid = videoPid; videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec; } audioPid = parsedPIDs.audioPid; if (audioPid > 0) { audioTrack.pid = audioPid; audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec; } id3Pid = parsedPIDs.id3Pid; if (id3Pid > 0) { id3Track.pid = id3Pid; } if (unknownPID !== null && !pmtParsed) { logger.warn(`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`); unknownPID = null; // we set it to -188, the += 188 in the for loop will reset start to 0 start = syncOffset - 188; } pmtParsed = this.pmtParsed = true; break; } case 0x11: case 0x1fff: break; default: unknownPID = pid; break; } } else { tsPacketErrors++; } } if (tsPacketErrors > 0) { emitParsingError(this.observer, new Error(`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`)); } videoTrack.pesData = videoData; audioTrack.pesData = audioData; id3Track.pesData = id3Data; const demuxResult = { audioTrack, videoTrack, id3Track, textTrack }; if (flush) { this.extractRemainingSamples(demuxResult); } return demuxResult; } flush() { const { remainderData } = this; this.remainderData = null; let result; if (remainderData) { result = this.demux(remainderData, -1, false, true); } else { result = { videoTrack: this._videoTrack, audioTrack: this._audioTrack, id3Track: this._id3Track, textTrack: this._txtTrack }; } this.extractRemainingSamples(result); if (this.sampleAes) { return this.decrypt(result, this.sampleAes); } return result; } extractRemainingSamples(demuxResult) { const { audioTrack, videoTrack, id3Track, textTrack } = demuxResult; const videoData = videoTrack.pesData; const audioData = audioTrack.pesData; const id3Data = id3Track.pesData; // try to parse last PES packets let pes; if (videoData && (pes = parsePES(videoData))) { this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration); videoTrack.pesData = null; } else { // either avcData null or PES truncated, keep it for next frag parsing videoTrack.pesData = videoData; } if (audioData && (pes = parsePES(audioData))) { switch (audioTrack.segmentCodec) { case 'aac': this.parseAACPES(audioTrack, pes); break; case 'mp3': this.parseMPEGPES(audioTrack, pes); break; case 'ac3': { this.parseAC3PES(audioTrack, pes); } break; } audioTrack.pesData = null; } else { if (audioData != null && audioData.size) { logger.log('last AAC PES packet truncated,might overlap between fragments'); } // either audioData null or PES truncated, keep it for next frag parsing audioTrack.pesData = audioData; } if (id3Data && (pes = parsePES(id3Data))) { this.parseID3PES(id3Track, pes); id3Track.pesData = null; } else { // either id3Data null or PES truncated, keep it for next frag parsing id3Track.pesData = id3Data; } } demuxSampleAes(data, keyData, timeOffset) { const demuxResult = this.demux(data, timeOffset, true, !this.config.progressive); const sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData); return this.decrypt(demuxResult, sampleAes); } decrypt(demuxResult, sampleAes) { return new Promise(resolve => { const { audioTrack, videoTrack } = demuxResult; if (audioTrack.samples && audioTrack.segmentCodec === 'aac') { sampleAes.decryptAacSamples(audioTrack.samples, 0, () => { if (videoTrack.samples) { sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => { resolve(demuxResult); }); } else { resolve(demuxResult); } }); } else if (videoTrack.samples) { sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => { resolve(demuxResult); }); } }); } destroy() { this._duration = 0; } parseAACPES(track, pes) { let startOffset = 0; const aacOverFlow = this.aacOverFlow; let data = pes.data; if (aacOverFlow) { this.aacOverFlow = null; const frameMissingBytes = aacOverFlow.missing; const sampleLength = aacOverFlow.sample.unit.byteLength; // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`); if (frameMissingBytes === -1) { data = appendUint8Array(aacOverFlow.sample.unit, data); } else { const frameOverflowBytes = sampleLength - frameMissingBytes; aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes); track.samples.push(aacOverFlow.sample); startOffset = aacOverFlow.missing; } } // look for ADTS header (0xFFFx) let offset; let len; for (offset = startOffset, len = data.length; offset < len - 1; offset++) { if (isHeader$1(data, offset)) { break; } } // if ADTS header does not start straight from the beginning of the PES payload, raise an error if (offset !== startOffset) { let reason; const recoverable = offset < len - 1; if (recoverable) { reason = `AAC PES did not start with ADTS header,offset:${offset}`; } else { reason = 'No ADTS header found in AAC PES'; } emitParsingError(this.observer, new Error(reason), recoverable); if (!recoverable) { return; } } initTrackConfig(track, this.observer, data, offset, this.audioCodec); let pts; if (pes.pts !== undefined) { pts = pes.pts; } else if (aacOverFlow) { // if last AAC frame is overflowing, we should ensure timestamps are contiguous: // first sample PTS should be equal to last sample PTS + frameDuration const frameDuration = getFrameDuration(track.samplerate); pts = aacOverFlow.sample.pts + frameDuration; } else { logger.warn('[tsdemuxer]: AAC PES unknown PTS'); return; } // scan for aac samples let frameIndex = 0; let frame; while (offset < len) { frame = appendFrame$2(track, data, offset, pts, frameIndex); offset += frame.length; if (!frame.missing) { frameIndex++; for (; offset < len - 1; offset++) { if (isHeader$1(data, offset)) { break; } } } else { this.aacOverFlow = frame; break; } } } parseMPEGPES(track, pes) { const data = pes.data; const length = data.length; let frameIndex = 0; let offset = 0; const pts = pes.pts; if (pts === undefined) { logger.warn('[tsdemuxer]: MPEG PES unknown PTS'); return; } while (offset < length) { if (isHeader(data, offset)) { const frame = appendFrame$1(track, data, offset, pts, frameIndex); if (frame) { offset += frame.length; frameIndex++; } else { // logger.log('Unable to parse Mpeg audio frame'); break; } } else { // nothing found, keep looking offset++; } } } parseAC3PES(track, pes) { { const data = pes.data; const pts = pes.pts; if (pts === undefined) { logger.warn('[tsdemuxer]: AC3 PES unknown PTS'); return; } const length = data.length; let frameIndex = 0; let offset = 0; let parsed; while (offset < length && (parsed = appendFrame(track, data, offset, pts, frameIndex++)) > 0) { offset += parsed; } } } parseID3PES(id3Track, pes) { if (pes.pts === undefined) { logger.warn('[tsdemuxer]: ID3 PES unknown PTS'); return; } const id3Sample = _extends({}, pes, { type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3, duration: Number.POSITIVE_INFINITY }); id3Track.samples.push(id3Sample); } } function parsePID(data, offset) { // pid is a 13-bit field starting at the last bit of TS[1] return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2]; } function parsePAT(data, offset) { // skip the PSI header and parse the first PMT entry return (data[offset + 10] & 0x1f) << 8 | data[offset + 11]; } function parsePMT(data, offset, typeSupported, isSampleAes, observer) { const result = { audioPid: -1, videoPid: -1, id3Pid: -1, segmentVideoCodec: 'avc', segmentAudioCodec: 'aac' }; const sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2]; const tableEnd = offset + 3 + sectionLength - 4; // to determine where the table is, we have to figure out how // long the program info descriptors are const programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11]; // advance the offset to the first entry in the mapping table offset += 12 + programInfoLength; while (offset < tableEnd) { const pid = parsePID(data, offset); const esInfoLength = (data[offset + 3] & 0x0f) << 8 | data[offset + 4]; switch (data[offset]) { case 0xcf: // SAMPLE-AES AAC if (!isSampleAes) { logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC'); break; } /* falls through */ case 0x0f: // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio) // logger.log('AAC PID:' + pid); if (result.audioPid === -1) { result.audioPid = pid; } break; // Packetized metadata (ID3) case 0x15: // logger.log('ID3 PID:' + pid); if (result.id3Pid === -1) { result.id3Pid = pid; } break; case 0xdb: // SAMPLE-AES AVC if (!isSampleAes) { logEncryptedSamplesFoundInUnencryptedStream('H.264'); break; } /* falls through */ case 0x1b: // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video) // logger.log('AVC PID:' + pid); if (result.videoPid === -1) { result.videoPid = pid; result.segmentVideoCodec = 'avc'; } break; // ISO/IEC 11172-3 (MPEG-1 audio) // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio) case 0x03: case 0x04: // logger.log('MPEG PID:' + pid); if (!typeSupported.mpeg && !typeSupported.mp3) { logger.log('MPEG audio found, not supported in this browser'); } else if (result.audioPid === -1) { result.audioPid = pid; result.segmentAudioCodec = 'mp3'; } break; case 0xc1: // SAMPLE-AES AC3 if (!isSampleAes) { logEncryptedSamplesFoundInUnencryptedStream('AC-3'); break; } /* falls through */ case 0x81: { if (!typeSupported.ac3) { logger.log('AC-3 audio found, not supported in this browser'); } else if (result.audioPid === -1) { result.audioPid = pid; result.segmentAudioCodec = 'ac3'; } } break; case 0x06: // stream_type 6 can mean a lot of different things in case of DVB. // We need to look at the descriptors. Right now, we're only interested // in AC-3 audio, so we do the descriptor parsing only when we don't have // an audio PID yet. if (result.audioPid === -1 && esInfoLength > 0) { let parsePos = offset + 5; let remaining = esInfoLength; while (remaining > 2) { const descriptorId = data[parsePos]; switch (descriptorId) { case 0x6a: // DVB Descriptor for AC-3 { if (typeSupported.ac3 !== true) { logger.log('AC-3 audio found, not supported in this browser for now'); } else { result.audioPid = pid; result.segmentAudioCodec = 'ac3'; } } break; } const descriptorLen = data[parsePos + 1] + 2; parsePos += descriptorLen; remaining -= descriptorLen; } } break; case 0xc2: // SAMPLE-AES EC3 /* falls through */ case 0x87: emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found')); return result; case 0x24: emitParsingError(observer, new Error('Unsupported HEVC in M2TS found')); return result; } // move to the next table entry // skip past the elementary stream descriptors, if present offset += esInfoLength + 5; } return result; } function emitParsingError(observer, error, levelRetry) { logger.warn(`parsing error: ${error.message}`); observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, levelRetry, error, reason: error.message }); } function logEncryptedSamplesFoundInUnencryptedStream(type) { logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`); } function parsePES(stream) { let i = 0; let frag; let pesLen; let pesHdrLen; let pesPts; let pesDts; const data = stream.data; // safety check if (!stream || stream.size === 0) { return null; } // we might need up to 19 bytes to read PES header // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes // usually only one merge is needed (and this is rare ...) while (data[0].length < 19 && data.length > 1) { data[0] = appendUint8Array(data[0], data[1]); data.splice(1, 1); } // retrieve PTS/DTS from first fragment frag = data[0]; const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2]; if (pesPrefix === 1) { pesLen = (frag[4] << 8) + frag[5]; // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated // minus 6 : PES header size if (pesLen && pesLen > stream.size - 6) { return null; } const pesFlags = frag[7]; if (pesFlags & 0xc0) { /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html as PTS / DTS is 33 bit we cannot use bitwise operator in JS, as Bitwise operators treat their operands as a sequence of 32 bits */ pesPts = (frag[9] & 0x0e) * 536870912 + // 1 << 29 (frag[10] & 0xff) * 4194304 + // 1 << 22 (frag[11] & 0xfe) * 16384 + // 1 << 14 (frag[12] & 0xff) * 128 + // 1 << 7 (frag[13] & 0xfe) / 2; if (pesFlags & 0x40) { pesDts = (frag[14] & 0x0e) * 536870912 + // 1 << 29 (frag[15] & 0xff) * 4194304 + // 1 << 22 (frag[16] & 0xfe) * 16384 + // 1 << 14 (frag[17] & 0xff) * 128 + // 1 << 7 (frag[18] & 0xfe) / 2; if (pesPts - pesDts > 60 * 90000) { logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`); pesPts = pesDts; } } else { pesDts = pesPts; } } pesHdrLen = frag[8]; // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension let payloadStartOffset = pesHdrLen + 9; if (stream.size <= payloadStartOffset) { return null; } stream.size -= payloadStartOffset; // reassemble PES packet const pesData = new Uint8Array(stream.size); for (let j = 0, dataLen = data.length; j < dataLen; j++) { frag = data[j]; let len = frag.byteLength; if (payloadStartOffset) { if (payloadStartOffset > len) { // trim full frag if PES header bigger than frag payloadStartOffset -= len; continue; } else { // trim partial frag if PES header smaller than frag frag = frag.subarray(payloadStartOffset); len -= payloadStartOffset; payloadStartOffset = 0; } } pesData.set(frag, i); i += len; } if (pesLen) { // payload size : remove PES header + PES extension pesLen -= pesHdrLen + 3; } return { data: pesData, pts: pesPts, dts: pesDts, len: pesLen }; } return null; } /** * MP3 demuxer */ class MP3Demuxer extends BaseAudioDemuxer { resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration); this._audioTrack = { container: 'audio/mpeg', type: 'audio', id: 2, pid: -1, sequenceNumber: 0, segmentCodec: 'mp3', samples: [], manifestCodec: audioCodec, duration: trackDuration, inputTimeScale: 90000, dropped: 0 }; } static probe(data) { if (!data) { return false; } // check if data contains ID3 timestamp and MPEG sync word // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1 // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III) // More info http://www.mp3-tech.org/programmer/frame_header.html const id3Data = getID3Data(data, 0); let offset = (id3Data == null ? void 0 : id3Data.length) || 0; // Check for ac-3|ec-3 sync bytes and return false if present if (id3Data && data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined && // check the bsid to confirm ac-3 or ec-3 (not mp3) getAudioBSID(data, offset) <= 16) { return false; } for (let length = data.length; offset < length; offset++) { if (probe(data, offset)) { logger.log('MPEG Audio sync word found !'); return true; } } return false; } canParse(data, offset) { return canParse(data, offset); } appendFrame(track, data, offset) { if (this.basePTS === null) { return; } return appendFrame$1(track, data, offset, this.basePTS, this.frameIndex); } } /** * AAC helper */ class AAC { static getSilentFrame(codec, channelCount) { switch (codec) { case 'mp4a.40.2': if (channelCount === 1) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]); } else if (channelCount === 2) { return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]); } else if (channelCount === 3) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]); } else if (channelCount === 4) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]); } else if (channelCount === 5) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]); } else if (channelCount === 6) { return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]); } break; // handle HE-AAC below (mp4a.40.5 / mp4a.40.29) default: if (channelCount === 1) { // ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); } else if (channelCount === 2) { // ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); } else if (channelCount === 3) { // ffmpeg -y -f lavfi -i "aevalsrc=0|0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); } break; } return undefined; } } /** * Generate MP4 Box */ const UINT32_MAX = Math.pow(2, 32) - 1; class MP4 { static init() { MP4.types = { avc1: [], // codingname avcC: [], btrt: [], dinf: [], dref: [], esds: [], ftyp: [], hdlr: [], mdat: [], mdhd: [], mdia: [], mfhd: [], minf: [], moof: [], moov: [], mp4a: [], '.mp3': [], dac3: [], 'ac-3': [], mvex: [], mvhd: [], pasp: [], sdtp: [], stbl: [], stco: [], stsc: [], stsd: [], stsz: [], stts: [], tfdt: [], tfhd: [], traf: [], trak: [], trun: [], trex: [], tkhd: [], vmhd: [], smhd: [] }; let i; for (i in MP4.types) { if (MP4.types.hasOwnProperty(i)) { MP4.types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; } } const videoHdlr = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // pre_defined 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' ]); const audioHdlr = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // pre_defined 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' ]); MP4.HDLR_TYPES = { video: videoHdlr, audio: audioHdlr }; const dref = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x01, // entry_count 0x00, 0x00, 0x00, 0x0c, // entry_size 0x75, 0x72, 0x6c, 0x20, // 'url' type 0x00, // version 0 0x00, 0x00, 0x01 // entry_flags ]); const stco = new Uint8Array([0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00 // entry_count ]); MP4.STTS = MP4.STSC = MP4.STCO = stco; MP4.STSZ = new Uint8Array([0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, // sample_size 0x00, 0x00, 0x00, 0x00 // sample_count ]); MP4.VMHD = new Uint8Array([0x00, // version 0x00, 0x00, 0x01, // flags 0x00, 0x00, // graphicsmode 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor ]); MP4.SMHD = new Uint8Array([0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, // balance 0x00, 0x00 // reserved ]); MP4.STSD = new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x01]); // entry_count const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1 const minorVersion = new Uint8Array([0, 0, 0, 1]); MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand); MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref)); } static box(type, ...payload) { let size = 8; let i = payload.length; const len = i; // calculate the total size we need to allocate while (i--) { size += payload[i].byteLength; } const result = new Uint8Array(size); result[0] = size >> 24 & 0xff; result[1] = size >> 16 & 0xff; result[2] = size >> 8 & 0xff; result[3] = size & 0xff; result.set(type, 4); // copy the payload into the result for (i = 0, size = 8; i < len; i++) { // copy payload[i] array @ offset size result.set(payload[i], size); size += payload[i].byteLength; } return result; } static hdlr(type) { return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]); } static mdat(data) { return MP4.box(MP4.types.mdat, data); } static mdhd(timescale, duration) { duration *= timescale; const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); return MP4.box(MP4.types.mdhd, new Uint8Array([0x01, // version 1 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff, // timescale upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4, // 'und' language (undetermined) 0x00, 0x00])); } static mdia(track) { return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track)); } static mfhd(sequenceNumber) { return MP4.box(MP4.types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff // sequence_number ])); } static minf(track) { if (track.type === 'audio') { return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track)); } else { return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track)); } } static moof(sn, baseMediaDecodeTime, track) { return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime)); } static moov(tracks) { let i = tracks.length; const boxes = []; while (i--) { boxes[i] = MP4.trak(tracks[i]); } return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks))); } static mvex(tracks) { let i = tracks.length; const boxes = []; while (i--) { boxes[i] = MP4.trex(tracks[i]); } return MP4.box.apply(null, [MP4.types.mvex, ...boxes]); } static mvhd(timescale, duration) { duration *= timescale; const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); const bytes = new Uint8Array([0x01, // version 1 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff, // timescale upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00, // 1.0 rate 0x01, 0x00, // 1.0 volume 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 0xff, 0xff, 0xff, 0xff // next_track_ID ]); return MP4.box(MP4.types.mvhd, bytes); } static sdtp(track) { const samples = track.samples || []; const bytes = new Uint8Array(4 + samples.length); let i; let flags; // leave the full box header (4 bytes) all zero // write the sample table for (i = 0; i < samples.length; i++) { flags = samples[i].flags; bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; } return MP4.box(MP4.types.sdtp, bytes); } static stbl(track) { return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO)); } static avc1(track) { let sps = []; let pps = []; let i; let data; let len; // assemble the SPSs for (i = 0; i < track.sps.length; i++) { data = track.sps[i]; len = data.byteLength; sps.push(len >>> 8 & 0xff); sps.push(len & 0xff); // SPS sps = sps.concat(Array.prototype.slice.call(data)); } // assemble the PPSs for (i = 0; i < track.pps.length; i++) { data = track.pps[i]; len = data.byteLength; pps.push(len >>> 8 & 0xff); pps.push(len & 0xff); pps = pps.concat(Array.prototype.slice.call(data)); } const avcc = MP4.box(MP4.types.avcC, new Uint8Array([0x01, // version sps[3], // profile sps[4], // profile compat sps[5], // level 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes 0xe0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets ].concat(sps).concat([track.pps.length // numOfPictureParameterSets ]).concat(pps))); // "PPS" const width = track.width; const height = track.height; const hSpacing = track.pixelRatio[0]; const vSpacing = track.pixelRatio[1]; return MP4.box(MP4.types.avc1, new Uint8Array([0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // data_reference_index 0x00, 0x00, // pre_defined 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined width >> 8 & 0xff, width & 0xff, // width height >> 8 & 0xff, height & 0xff, // height 0x00, 0x48, 0x00, 0x00, // horizresolution 0x00, 0x48, 0x00, 0x00, // vertresolution 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // frame_count 0x12, 0x64, 0x61, 0x69, 0x6c, // dailymotion/hls.js 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname 0x00, 0x18, // depth = 24 0x11, 0x11]), // pre_defined = -1 avcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 0x00, 0x2d, 0xc6, 0xc0])), // avgBitrate MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24, // hSpacing hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24, // vSpacing vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff]))); } static esds(track) { const configlen = track.config.length; return new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags 0x03, // descriptor_type 0x17 + configlen, // length 0x00, 0x01, // es_id 0x00, // stream_priority 0x04, // descriptor_type 0x0f + configlen, // length 0x40, // codec : mpeg4_audio 0x15, // stream_type 0x00, 0x00, 0x00, // buffer_size 0x00, 0x00, 0x00, 0x00, // maxBitrate 0x00, 0x00, 0x00, 0x00, // avgBitrate 0x05 // descriptor_type ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor } static audioStsd(track) { const samplerate = track.samplerate; return new Uint8Array([0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, // reserved 0x00, 0x01, // data_reference_index 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 0x00, track.channelCount, // channelcount 0x00, 0x10, // sampleSize:16bits 0x00, 0x00, 0x00, 0x00, // reserved2 samplerate >> 8 & 0xff, samplerate & 0xff, // 0x00, 0x00]); } static mp4a(track) { return MP4.box(MP4.types.mp4a, MP4.audioStsd(track), MP4.box(MP4.types.esds, MP4.esds(track))); } static mp3(track) { return MP4.box(MP4.types['.mp3'], MP4.audioStsd(track)); } static ac3(track) { return MP4.box(MP4.types['ac-3'], MP4.audioStsd(track), MP4.box(MP4.types.dac3, track.config)); } static stsd(track) { if (track.type === 'audio') { if (track.segmentCodec === 'mp3' && track.codec === 'mp3') { return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track)); } if (track.segmentCodec === 'ac3') { return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track)); } return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track)); } else { return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track)); } } static tkhd(track) { const id = track.id; const duration = track.duration * track.timescale; const width = track.width; const height = track.height; const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); return MP4.box(MP4.types.tkhd, new Uint8Array([0x01, // version 1 0x00, 0x00, 0x07, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // creation_time 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, // modification_time id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff, // track_ID 0x00, 0x00, 0x00, 0x00, // reserved upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, // layer 0x00, 0x00, // alternate_group 0x00, 0x00, // non-audio track volume 0x00, 0x00, // reserved 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix width >> 8 & 0xff, width & 0xff, 0x00, 0x00, // width height >> 8 & 0xff, height & 0xff, 0x00, 0x00 // height ])); } static traf(track, baseMediaDecodeTime) { const sampleDependencyTable = MP4.sdtp(track); const id = track.id; const upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1)); const lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1)); return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff // track_ID ])), MP4.box(MP4.types.tfdt, new Uint8Array([0x01, // version 1 0x00, 0x00, 0x00, // flags upperWordBaseMediaDecodeTime >> 24, upperWordBaseMediaDecodeTime >> 16 & 0xff, upperWordBaseMediaDecodeTime >> 8 & 0xff, upperWordBaseMediaDecodeTime & 0xff, lowerWordBaseMediaDecodeTime >> 24, lowerWordBaseMediaDecodeTime >> 16 & 0xff, lowerWordBaseMediaDecodeTime >> 8 & 0xff, lowerWordBaseMediaDecodeTime & 0xff])), MP4.trun(track, sampleDependencyTable.length + 16 + // tfhd 20 + // tfdt 8 + // traf header 16 + // mfhd 8 + // moof header 8), // mdat header sampleDependencyTable); } /** * Generate a track box. * @param track a track definition */ static trak(track) { track.duration = track.duration || 0xffffffff; return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track)); } static trex(track) { const id = track.id; return MP4.box(MP4.types.trex, new Uint8Array([0x00, // version 0 0x00, 0x00, 0x00, // flags id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff, // track_ID 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 0x00, 0x00, 0x00, 0x00, // default_sample_duration 0x00, 0x00, 0x00, 0x00, // default_sample_size 0x00, 0x01, 0x00, 0x01 // default_sample_flags ])); } static trun(track, offset) { const samples = track.samples || []; const len = samples.length; const arraylen = 12 + 16 * len; const array = new Uint8Array(arraylen); let i; let sample; let duration; let size; let flags; let cts; offset += 8 + arraylen; array.set([track.type === 'video' ? 0x01 : 0x00, // version 1 for video with signed-int sample_composition_time_offset 0x00, 0x0f, 0x01, // flags len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff, // sample_count offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset ], 0); for (i = 0; i < len; i++) { sample = samples[i]; duration = sample.duration; size = sample.size; flags = sample.flags; cts = sample.cts; array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff, // sample_duration size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff, // sample_size flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f, // sample_flags cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset ], 12 + 16 * i); } return MP4.box(MP4.types.trun, array); } static initSegment(tracks) { if (!MP4.types) { MP4.init(); } const movie = MP4.moov(tracks); const result = appendUint8Array(MP4.FTYP, movie); return result; } } MP4.types = void 0; MP4.HDLR_TYPES = void 0; MP4.STTS = void 0; MP4.STSC = void 0; MP4.STCO = void 0; MP4.STSZ = void 0; MP4.VMHD = void 0; MP4.SMHD = void 0; MP4.STSD = void 0; MP4.FTYP = void 0; MP4.DINF = void 0; const MPEG_TS_CLOCK_FREQ_HZ = 90000; function toTimescaleFromBase(baseTime, destScale, srcBase = 1, round = false) { const result = baseTime * destScale * srcBase; // equivalent to `(value * scale) / (1 / base)` return round ? Math.round(result) : result; } function toTimescaleFromScale(baseTime, destScale, srcScale = 1, round = false) { return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round); } function toMsFromMpegTsClock(baseTime, round = false) { return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ, round); } function toMpegTsClockFromTimescale(baseTime, srcScale = 1) { return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale); } const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds const AAC_SAMPLES_PER_FRAME = 1024; const MPEG_AUDIO_SAMPLE_PER_FRAME = 1152; const AC3_SAMPLES_PER_FRAME = 1536; let chromeVersion = null; let safariWebkitVersion = null; class MP4Remuxer { constructor(observer, config, typeSupported, vendor = '') { this.observer = void 0; this.config = void 0; this.typeSupported = void 0; this.ISGenerated = false; this._initPTS = null; this._initDTS = null; this.nextAvcDts = null; this.nextAudioPts = null; this.videoSampleDuration = null; this.isAudioContiguous = false; this.isVideoContiguous = false; this.videoTrackConfig = void 0; this.observer = observer; this.config = config; this.typeSupported = typeSupported; this.ISGenerated = false; if (chromeVersion === null) { const userAgent = navigator.userAgent || ''; const result = userAgent.match(/Chrome\/(\d+)/i); chromeVersion = result ? parseInt(result[1]) : 0; } if (safariWebkitVersion === null) { const result = navigator.userAgent.match(/Safari\/(\d+)/i); safariWebkitVersion = result ? parseInt(result[1]) : 0; } } destroy() { // @ts-ignore this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null; } resetTimeStamp(defaultTimeStamp) { logger.log('[mp4-remuxer]: initPTS & initDTS reset'); this._initPTS = this._initDTS = defaultTimeStamp; } resetNextTimestamp() { logger.log('[mp4-remuxer]: reset next timestamp'); this.isVideoContiguous = false; this.isAudioContiguous = false; } resetInitSegment() { logger.log('[mp4-remuxer]: ISGenerated flag reset'); this.ISGenerated = false; this.videoTrackConfig = undefined; } getVideoStartPts(videoSamples) { let rolloverDetected = false; const startPTS = videoSamples.reduce((minPTS, sample) => { const delta = sample.pts - minPTS; if (delta < -4294967296) { // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation rolloverDetected = true; return normalizePts(minPTS, sample.pts); } else if (delta > 0) { return minPTS; } else { return sample.pts; } }, videoSamples[0].pts); if (rolloverDetected) { logger.debug('PTS rollover detected'); } return startPTS; } remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType) { let video; let audio; let initSegment; let text; let id3; let independent; let audioTimeOffset = timeOffset; let videoTimeOffset = timeOffset; // If we're remuxing audio and video progressively, wait until we've received enough samples for each track before proceeding. // This is done to synchronize the audio and video streams. We know if the current segment will have samples if the "pid" // parameter is greater than -1. The pid is set when the PMT is parsed, which contains the tracks list. // However, if the initSegment has already been generated, or we've reached the end of a segment (flush), // then we can remux one track without waiting for the other. const hasAudio = audioTrack.pid > -1; const hasVideo = videoTrack.pid > -1; const length = videoTrack.samples.length; const enoughAudioSamples = audioTrack.samples.length > 0; const enoughVideoSamples = flush && length > 0 || length > 1; const canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush; if (canRemuxAvc) { if (this.ISGenerated) { var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2; const config = this.videoTrackConfig; if (config && (videoTrack.width !== config.width || videoTrack.height !== config.height || ((_videoTrack$pixelRati = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati[0]) !== ((_config$pixelRatio = config.pixelRatio) == null ? void 0 : _config$pixelRatio[0]) || ((_videoTrack$pixelRati2 = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati2[1]) !== ((_config$pixelRatio2 = config.pixelRatio) == null ? void 0 : _config$pixelRatio2[1]))) { this.resetInitSegment(); } } else { initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); } const isVideoContiguous = this.isVideoContiguous; let firstKeyFrameIndex = -1; let firstKeyFramePTS; if (enoughVideoSamples) { firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples); if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) { independent = true; if (firstKeyFrameIndex > 0) { logger.warn(`[mp4-remuxer]: Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`); const startPTS = this.getVideoStartPts(videoTrack.samples); videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex); videoTrack.dropped += firstKeyFrameIndex; videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale; firstKeyFramePTS = videoTimeOffset; } else if (firstKeyFrameIndex === -1) { logger.warn(`[mp4-remuxer]: No keyframe found out of ${length} video samples`); independent = false; } } } if (this.ISGenerated) { if (enoughAudioSamples && enoughVideoSamples) { // timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS) // if first audio DTS is not aligned with first video DTS then we need to take that into account // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small // drift between audio and video streams const startPTS = this.getVideoStartPts(videoTrack.samples); const tsDelta = normalizePts(audioTrack.samples[0].pts, startPTS) - startPTS; const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale; audioTimeOffset += Math.max(0, audiovideoTimestampDelta); videoTimeOffset += Math.max(0, -audiovideoTimestampDelta); } // Purposefully remuxing audio before video, so that remuxVideo can use nextAudioPts, which is calculated in remuxAudio. if (enoughAudioSamples) { // if initSegment was generated without audio samples, regenerate it again if (!audioTrack.samplerate) { logger.warn('[mp4-remuxer]: regenerate InitSegment as audio detected'); initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); } audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : undefined); if (enoughVideoSamples) { const audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0; // if initSegment was generated without video samples, regenerate it again if (!videoTrack.inputTimeScale) { logger.warn('[mp4-remuxer]: regenerate InitSegment as video detected'); initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); } video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength); } } else if (enoughVideoSamples) { video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0); } if (video) { video.firstKeyFrame = firstKeyFrameIndex; video.independent = firstKeyFrameIndex !== -1; video.firstKeyFramePTS = firstKeyFramePTS; } } } // Allow ID3 and text to remux, even if more audio/video samples are required if (this.ISGenerated && this._initPTS && this._initDTS) { if (id3Track.samples.length) { id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS); } if (textTrack.samples.length) { text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS); } } return { audio, video, initSegment, independent, text, id3 }; } generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) { const audioSamples = audioTrack.samples; const videoSamples = videoTrack.samples; const typeSupported = this.typeSupported; const tracks = {}; const _initPTS = this._initPTS; let computePTSDTS = !_initPTS || accurateTimeOffset; let container = 'audio/mp4'; let initPTS; let initDTS; let timescale; if (computePTSDTS) { initPTS = initDTS = Infinity; } if (audioTrack.config && audioSamples.length) { // let's use audio sampling rate as MP4 time scale. // rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC) // using audio sampling rate here helps having an integer MP4 frame duration // this avoids potential rounding issue and AV sync issue audioTrack.timescale = audioTrack.samplerate; switch (audioTrack.segmentCodec) { case 'mp3': if (typeSupported.mpeg) { // Chrome and Safari container = 'audio/mpeg'; audioTrack.codec = ''; } else if (typeSupported.mp3) { // Firefox audioTrack.codec = 'mp3'; } break; case 'ac3': audioTrack.codec = 'ac-3'; break; } tracks.audio = { id: 'audio', container: container, codec: audioTrack.codec, initSegment: audioTrack.segmentCodec === 'mp3' && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]), metadata: { channelCount: audioTrack.channelCount } }; if (computePTSDTS) { timescale = audioTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { // remember first PTS of this demuxing context. for audio, PTS = DTS initPTS = initDTS = audioSamples[0].pts - Math.round(timescale * timeOffset); } else { computePTSDTS = false; } } } if (videoTrack.sps && videoTrack.pps && videoSamples.length) { // let's use input time scale as MP4 video timescale // we use input time scale straight away to avoid rounding issues on frame duration / cts computation videoTrack.timescale = videoTrack.inputTimeScale; tracks.video = { id: 'main', container: 'video/mp4', codec: videoTrack.codec, initSegment: MP4.initSegment([videoTrack]), metadata: { width: videoTrack.width, height: videoTrack.height } }; if (computePTSDTS) { timescale = videoTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { const startPTS = this.getVideoStartPts(videoSamples); const startOffset = Math.round(timescale * timeOffset); initDTS = Math.min(initDTS, normalizePts(videoSamples[0].dts, startPTS) - startOffset); initPTS = Math.min(initPTS, startPTS - startOffset); } else { computePTSDTS = false; } } this.videoTrackConfig = { width: videoTrack.width, height: videoTrack.height, pixelRatio: videoTrack.pixelRatio }; } if (Object.keys(tracks).length) { this.ISGenerated = true; if (computePTSDTS) { this._initPTS = { baseTime: initPTS, timescale: timescale }; this._initDTS = { baseTime: initDTS, timescale: timescale }; } else { initPTS = timescale = undefined; } return { tracks, initPTS, timescale }; } } remuxVideo(track, timeOffset, contiguous, audioTrackLength) { const timeScale = track.inputTimeScale; const inputSamples = track.samples; const outputSamples = []; const nbSamples = inputSamples.length; const initPTS = this._initPTS; let nextAvcDts = this.nextAvcDts; let offset = 8; let mp4SampleDuration = this.videoSampleDuration; let firstDTS; let lastDTS; let minPTS = Number.POSITIVE_INFINITY; let maxPTS = Number.NEGATIVE_INFINITY; let sortSamples = false; // if parsed fragment is contiguous with last one, let's use last DTS value as reference if (!contiguous || nextAvcDts === null) { const pts = timeOffset * timeScale; const cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts); if (chromeVersion && nextAvcDts !== null && Math.abs(pts - cts - nextAvcDts) < 15000) { // treat as contigous to adjust samples that would otherwise produce video buffer gaps in Chrome contiguous = true; } else { // if not contiguous, let's use target timeOffset nextAvcDts = pts - cts; } } // PTS is coded on 33bits, and can loop from -2^32 to 2^32 // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value const initTime = initPTS.baseTime * timeScale / initPTS.timescale; for (let i = 0; i < nbSamples; i++) { const sample = inputSamples[i]; sample.pts = normalizePts(sample.pts - initTime, nextAvcDts); sample.dts = normalizePts(sample.dts - initTime, nextAvcDts); if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) { sortSamples = true; } } // sort video samples by DTS then PTS then demux id order if (sortSamples) { inputSamples.sort(function (a, b) { const deltadts = a.dts - b.dts; const deltapts = a.pts - b.pts; return deltadts || deltapts; }); } // Get first/last DTS firstDTS = inputSamples[0].dts; lastDTS = inputSamples[inputSamples.length - 1].dts; // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS // set this constant duration as being the avg delta between consecutive DTS. const inputDuration = lastDTS - firstDTS; const averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30; // if fragment are contiguous, detect hole/overlapping between fragments if (contiguous) { // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole) const delta = firstDTS - nextAvcDts; const foundHole = delta > averageSampleDuration; const foundOverlap = delta < -1; if (foundHole || foundOverlap) { if (foundHole) { logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`); } else { logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`); } if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) { firstDTS = nextAvcDts; const firstPTS = inputSamples[0].pts - delta; if (foundHole) { inputSamples[0].dts = firstDTS; inputSamples[0].pts = firstPTS; } else { for (let i = 0; i < inputSamples.length; i++) { if (inputSamples[i].dts > firstPTS) { break; } inputSamples[i].dts -= delta; inputSamples[i].pts -= delta; } } logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`); } } } firstDTS = Math.max(0, firstDTS); let nbNalu = 0; let naluLen = 0; let dtsStep = firstDTS; for (let i = 0; i < nbSamples; i++) { // compute total/avc sample length and nb of NAL units const sample = inputSamples[i]; const units = sample.units; const nbUnits = units.length; let sampleLen = 0; for (let j = 0; j < nbUnits; j++) { sampleLen += units[j].data.length; } naluLen += sampleLen; nbNalu += nbUnits; sample.length = sampleLen; // ensure sample monotonic DTS if (sample.dts < dtsStep) { sample.dts = dtsStep; dtsStep += averageSampleDuration / 4 | 0 || 1; } else { dtsStep = sample.dts; } minPTS = Math.min(sample.pts, minPTS); maxPTS = Math.max(sample.pts, maxPTS); } lastDTS = inputSamples[nbSamples - 1].dts; /* concatenate the video data and construct the mdat in place (need 8 more bytes to fill length and mpdat type) */ const mdatSize = naluLen + 4 * nbNalu + 8; let mdat; try { mdat = new Uint8Array(mdatSize); } catch (err) { this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MUX_ERROR, details: ErrorDetails.REMUX_ALLOC_ERROR, fatal: false, error: err, bytes: mdatSize, reason: `fail allocating video mdat ${mdatSize}` }); return; } const view = new DataView(mdat.buffer); view.setUint32(0, mdatSize); mdat.set(MP4.types.mdat, 4); let stretchedLastFrame = false; let minDtsDelta = Number.POSITIVE_INFINITY; let minPtsDelta = Number.POSITIVE_INFINITY; let maxDtsDelta = Number.NEGATIVE_INFINITY; let maxPtsDelta = Number.NEGATIVE_INFINITY; for (let i = 0; i < nbSamples; i++) { const VideoSample = inputSamples[i]; const VideoSampleUnits = VideoSample.units; let mp4SampleLength = 0; // convert NALU bitstream to MP4 format (prepend NALU with size field) for (let j = 0, nbUnits = VideoSampleUnits.length; j < nbUnits; j++) { const unit = VideoSampleUnits[j]; const unitData = unit.data; const unitDataLen = unit.data.byteLength; view.setUint32(offset, unitDataLen); offset += 4; mdat.set(unitData, offset); offset += unitDataLen; mp4SampleLength += 4 + unitDataLen; } // expected sample duration is the Decoding Timestamp diff of consecutive samples let ptsDelta; if (i < nbSamples - 1) { mp4SampleDuration = inputSamples[i + 1].dts - VideoSample.dts; ptsDelta = inputSamples[i + 1].pts - VideoSample.pts; } else { const config = this.config; const lastFrameDuration = i > 0 ? VideoSample.dts - inputSamples[i - 1].dts : averageSampleDuration; ptsDelta = i > 0 ? VideoSample.pts - inputSamples[i - 1].pts : averageSampleDuration; if (config.stretchShortVideoTrack && this.nextAudioPts !== null) { // In some cases, a segment's audio track duration may exceed the video track duration. // Since we've already remuxed audio, and we know how long the audio track is, we look to // see if the delta to the next segment is longer than maxBufferHole. // If so, playback would potentially get stuck, so we artificially inflate // the duration of the last frame to minimize any potential gap between segments. const gapTolerance = Math.floor(config.maxBufferHole * timeScale); const deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioPts) - VideoSample.pts; if (deltaToFrameEnd > gapTolerance) { // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video // frame overlap. maxBufferHole should be >> lastFrameDuration anyway. mp4SampleDuration = deltaToFrameEnd - lastFrameDuration; if (mp4SampleDuration < 0) { mp4SampleDuration = lastFrameDuration; } else { stretchedLastFrame = true; } logger.log(`[mp4-remuxer]: It is approximately ${deltaToFrameEnd / 90} ms to the next segment; using duration ${mp4SampleDuration / 90} ms for the last video frame.`); } else { mp4SampleDuration = lastFrameDuration; } } else { mp4SampleDuration = lastFrameDuration; } } const compositionTimeOffset = Math.round(VideoSample.pts - VideoSample.dts); minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration); maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration); minPtsDelta = Math.min(minPtsDelta, ptsDelta); maxPtsDelta = Math.max(maxPtsDelta, ptsDelta); outputSamples.push(new Mp4Sample(VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset)); } if (outputSamples.length) { if (chromeVersion) { if (chromeVersion < 70) { // Chrome workaround, mark first sample as being a Random Access Point (keyframe) to avoid sourcebuffer append issue // https://code.google.com/p/chromium/issues/detail?id=229412 const flags = outputSamples[0].flags; flags.dependsOn = 2; flags.isNonSync = 0; } } else if (safariWebkitVersion) { // Fix for "CNN special report, with CC" in test-streams (Safari browser only) // Ignore DTS when frame durations are irregular. Safari MSE does not handle this leading to gaps. if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) { logger.warn('Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.'); let dts = firstDTS; for (let i = 0, len = outputSamples.length; i < len; i++) { const nextDts = dts + outputSamples[i].duration; const pts = dts + outputSamples[i].cts; if (i < len - 1) { const nextPts = nextDts + outputSamples[i + 1].cts; outputSamples[i].duration = nextPts - pts; } else { outputSamples[i].duration = i ? outputSamples[i - 1].duration : averageSampleDuration; } outputSamples[i].cts = 0; dts = nextDts; } } } } // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale) mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration; this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration; this.videoSampleDuration = mp4SampleDuration; this.isVideoContiguous = true; const moof = MP4.moof(track.sequenceNumber++, firstDTS, _extends({}, track, { samples: outputSamples })); const type = 'video'; const data = { data1: moof, data2: mdat, startPTS: minPTS / timeScale, endPTS: (maxPTS + mp4SampleDuration) / timeScale, startDTS: firstDTS / timeScale, endDTS: nextAvcDts / timeScale, type, hasAudio: false, hasVideo: true, nb: outputSamples.length, dropped: track.dropped }; track.samples = []; track.dropped = 0; return data; } getSamplesPerFrame(track) { switch (track.segmentCodec) { case 'mp3': return MPEG_AUDIO_SAMPLE_PER_FRAME; case 'ac3': return AC3_SAMPLES_PER_FRAME; default: return AAC_SAMPLES_PER_FRAME; } } remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset) { const inputTimeScale = track.inputTimeScale; const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale; const scaleFactor = inputTimeScale / mp4timeScale; const mp4SampleDuration = this.getSamplesPerFrame(track); const inputSampleDuration = mp4SampleDuration * scaleFactor; const initPTS = this._initPTS; const rawMPEG = track.segmentCodec === 'mp3' && this.typeSupported.mpeg; const outputSamples = []; const alignedWithVideo = videoTimeOffset !== undefined; let inputSamples = track.samples; let offset = rawMPEG ? 0 : 8; let nextAudioPts = this.nextAudioPts || -1; // window.audioSamples ? window.audioSamples.push(inputSamples.map(s => s.pts)) : (window.audioSamples = [inputSamples.map(s => s.pts)]); // for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs), // for sake of clarity: // consecutive fragments are frags with // - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR // - less than 20 audio frames distance // contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1) // this helps ensuring audio continuity // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame const timeOffsetMpegTS = timeOffset * inputTimeScale; const initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale; this.isAudioContiguous = contiguous = contiguous || inputSamples.length && nextAudioPts > 0 && (accurateTimeOffset && Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000 || Math.abs(normalizePts(inputSamples[0].pts - initTime, timeOffsetMpegTS) - nextAudioPts) < 20 * inputSampleDuration); // compute normalized PTS inputSamples.forEach(function (sample) { sample.pts = normalizePts(sample.pts - initTime, timeOffsetMpegTS); }); if (!contiguous || nextAudioPts < 0) { // filter out sample with negative PTS that are not playable anyway // if we don't remove these negative samples, they will shift all audio samples forward. // leading to audio overlap between current / next fragment inputSamples = inputSamples.filter(sample => sample.pts >= 0); // in case all samples have negative PTS, and have been filtered out, return now if (!inputSamples.length) { return; } if (videoTimeOffset === 0) { // Set the start to 0 to match video so that start gaps larger than inputSampleDuration are filled with silence nextAudioPts = 0; } else if (accurateTimeOffset && !alignedWithVideo) { // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS nextAudioPts = Math.max(0, timeOffsetMpegTS); } else { // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS nextAudioPts = inputSamples[0].pts; } } // If the audio track is missing samples, the frames seem to get "left-shifted" within the // resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment. // In an effort to prevent this from happening, we inject frames here where there are gaps. // When possible, we inject a silent frame; when that's not possible, we duplicate the last // frame. if (track.segmentCodec === 'aac') { const maxAudioFramesDrift = this.config.maxAudioFramesDrift; for (let i = 0, nextPts = nextAudioPts; i < inputSamples.length; i++) { // First, let's see how far off this frame is from where we expect it to be const sample = inputSamples[i]; const pts = sample.pts; const delta = pts - nextPts; const duration = Math.abs(1000 * delta / inputTimeScale); // When remuxing with video, if we're overlapping by more than a duration, drop this sample to stay in sync if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) { if (i === 0) { logger.warn(`Audio frame @ ${(pts / inputTimeScale).toFixed(3)}s overlaps nextAudioPts by ${Math.round(1000 * delta / inputTimeScale)} ms.`); this.nextAudioPts = nextAudioPts = nextPts = pts; } } // eslint-disable-line brace-style // Insert missing frames if: // 1: We're more than maxAudioFramesDrift frame away // 2: Not more than MAX_SILENT_FRAME_DURATION away // 3: currentTime (aka nextPtsNorm) is not 0 // 4: remuxing with video (videoTimeOffset !== undefined) else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) { let missing = Math.round(delta / inputSampleDuration); // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration. nextPts = pts - missing * inputSampleDuration; if (nextPts < 0) { missing--; nextPts += inputSampleDuration; } if (i === 0) { this.nextAudioPts = nextAudioPts = nextPts; } logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`); for (let j = 0; j < missing; j++) { const newStamp = Math.max(nextPts, 0); let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount); if (!fillFrame) { logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.'); fillFrame = sample.unit.subarray(); } inputSamples.splice(i, 0, { unit: fillFrame, pts: newStamp }); nextPts += inputSampleDuration; i++; } } sample.pts = nextPts; nextPts += inputSampleDuration; } } let firstPTS = null; let lastPTS = null; let mdat; let mdatSize = 0; let sampleLength = inputSamples.length; while (sampleLength--) { mdatSize += inputSamples[sampleLength].unit.byteLength; } for (let j = 0, _nbSamples = inputSamples.length; j < _nbSamples; j++) { const audioSample = inputSamples[j]; const unit = audioSample.unit; let pts = audioSample.pts; if (lastPTS !== null) { // If we have more than one sample, set the duration of the sample to the "real" duration; the PTS diff with // the previous sample const prevSample = outputSamples[j - 1]; prevSample.duration = Math.round((pts - lastPTS) / scaleFactor); } else { if (contiguous && track.segmentCodec === 'aac') { // set PTS/DTS to expected PTS/DTS pts = nextAudioPts; } // remember first PTS of our audioSamples firstPTS = pts; if (mdatSize > 0) { /* concatenate the audio data and construct the mdat in place (need 8 more bytes to fill length and mdat type) */ mdatSize += offset; try { mdat = new Uint8Array(mdatSize); } catch (err) { this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MUX_ERROR, details: ErrorDetails.REMUX_ALLOC_ERROR, fatal: false, error: err, bytes: mdatSize, reason: `fail allocating audio mdat ${mdatSize}` }); return; } if (!rawMPEG) { const view = new DataView(mdat.buffer); view.setUint32(0, mdatSize); mdat.set(MP4.types.mdat, 4); } } else { // no audio samples return; } } mdat.set(unit, offset); const unitLen = unit.byteLength; offset += unitLen; // Default the sample's duration to the computed mp4SampleDuration, which will either be 1024 for AAC or 1152 for MPEG // In the case that we have 1 sample, this will be the duration. If we have more than one sample, the duration // becomes the PTS diff with the previous sample outputSamples.push(new Mp4Sample(true, mp4SampleDuration, unitLen, 0)); lastPTS = pts; } // We could end up with no audio samples if all input samples were overlapping with the previously remuxed ones const nbSamples = outputSamples.length; if (!nbSamples) { return; } // The next audio sample PTS should be equal to last sample PTS + duration const lastSample = outputSamples[outputSamples.length - 1]; this.nextAudioPts = nextAudioPts = lastPTS + scaleFactor * lastSample.duration; // Set the track samples from inputSamples to outputSamples before remuxing const moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, { samples: outputSamples })); // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared track.samples = []; const start = firstPTS / inputTimeScale; const end = nextAudioPts / inputTimeScale; const type = 'audio'; const audioData = { data1: moof, data2: mdat, startPTS: start, endPTS: end, startDTS: start, endDTS: end, type, hasAudio: true, hasVideo: false, nb: nbSamples }; this.isAudioContiguous = true; return audioData; } remuxEmptyAudio(track, timeOffset, contiguous, videoData) { const inputTimeScale = track.inputTimeScale; const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale; const scaleFactor = inputTimeScale / mp4timeScale; const nextAudioPts = this.nextAudioPts; // sync with video's timestamp const initDTS = this._initDTS; const init90kHz = initDTS.baseTime * 90000 / initDTS.timescale; const startDTS = (nextAudioPts !== null ? nextAudioPts : videoData.startDTS * inputTimeScale) + init90kHz; const endDTS = videoData.endDTS * inputTimeScale + init90kHz; // one sample's duration value const frameDuration = scaleFactor * AAC_SAMPLES_PER_FRAME; // samples count of this segment's duration const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration); // silent frame const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount); logger.warn('[mp4-remuxer]: remux empty Audio'); // Can't remux if we can't generate a silent frame... if (!silentFrame) { logger.trace('[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec'); return; } const samples = []; for (let i = 0; i < nbSamples; i++) { const stamp = startDTS + i * frameDuration; samples.push({ unit: silentFrame, pts: stamp, dts: stamp }); } track.samples = samples; return this.remuxAudio(track, timeOffset, contiguous, false); } } function normalizePts(value, reference) { let offset; if (reference === null) { return value; } if (reference < value) { // - 2^33 offset = -8589934592; } else { // + 2^33 offset = 8589934592; } /* PTS is 33bit (from 0 to 2^33 -1) if diff between value and reference is bigger than half of the amplitude (2^32) then it means that PTS looping occured. fill the gap */ while (Math.abs(value - reference) > 4294967296) { value += offset; } return value; } function findKeyframeIndex(samples) { for (let i = 0; i < samples.length; i++) { if (samples[i].key) { return i; } } return -1; } function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) { const length = track.samples.length; if (!length) { return; } const inputTimeScale = track.inputTimeScale; for (let index = 0; index < length; index++) { const sample = track.samples[index]; // setting id3 pts, dts to relative time // using this._initPTS and this._initDTS to calculate relative time sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; } const samples = track.samples; track.samples = []; return { samples }; } function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) { const length = track.samples.length; if (!length) { return; } const inputTimeScale = track.inputTimeScale; for (let index = 0; index < length; index++) { const sample = track.samples[index]; // setting text pts, dts to relative time // using this._initPTS and this._initDTS to calculate relative time sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; } track.samples.sort((a, b) => a.pts - b.pts); const samples = track.samples; track.samples = []; return { samples }; } class Mp4Sample { constructor(isKeyframe, duration, size, cts) { this.size = void 0; this.duration = void 0; this.cts = void 0; this.flags = void 0; this.duration = duration; this.size = size; this.cts = cts; this.flags = { isLeading: 0, isDependedOn: 0, hasRedundancy: 0, degradPrio: 0, dependsOn: isKeyframe ? 2 : 1, isNonSync: isKeyframe ? 0 : 1 }; } } class PassThroughRemuxer { constructor() { this.emitInitSegment = false; this.audioCodec = void 0; this.videoCodec = void 0; this.initData = void 0; this.initPTS = null; this.initTracks = void 0; this.lastEndTime = null; } destroy() {} resetTimeStamp(defaultInitPTS) { this.initPTS = defaultInitPTS; this.lastEndTime = null; } resetNextTimestamp() { this.lastEndTime = null; } resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) { this.audioCodec = audioCodec; this.videoCodec = videoCodec; this.generateInitSegment(patchEncyptionData(initSegment, decryptdata)); this.emitInitSegment = true; } generateInitSegment(initSegment) { let { audioCodec, videoCodec } = this; if (!(initSegment != null && initSegment.byteLength)) { this.initTracks = undefined; this.initData = undefined; return; } const initData = this.initData = parseInitSegment(initSegment); // Get codec from initSegment or fallback to default if (initData.audio) { audioCodec = getParsedTrackCodec(initData.audio, ElementaryStreamTypes.AUDIO); } if (initData.video) { videoCodec = getParsedTrackCodec(initData.video, ElementaryStreamTypes.VIDEO); } const tracks = {}; if (initData.audio && initData.video) { tracks.audiovideo = { container: 'video/mp4', codec: audioCodec + ',' + videoCodec, initSegment, id: 'main' }; } else if (initData.audio) { tracks.audio = { container: 'audio/mp4', codec: audioCodec, initSegment, id: 'audio' }; } else if (initData.video) { tracks.video = { container: 'video/mp4', codec: videoCodec, initSegment, id: 'main' }; } else { logger.warn('[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.'); } this.initTracks = tracks; } remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset) { var _initData, _initData2; let { initPTS, lastEndTime } = this; const result = { audio: undefined, video: undefined, text: textTrack, id3: id3Track, initSegment: undefined }; // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update // the media duration (which is what timeOffset is provided as) before we need to process the next chunk. if (!isFiniteNumber(lastEndTime)) { lastEndTime = this.lastEndTime = timeOffset || 0; } // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only // audio or video (or both); adding it to video was an arbitrary choice. const data = videoTrack.samples; if (!(data != null && data.length)) { return result; } const initSegment = { initPTS: undefined, timescale: 1 }; let initData = this.initData; if (!((_initData = initData) != null && _initData.length)) { this.generateInitSegment(data); initData = this.initData; } if (!((_initData2 = initData) != null && _initData2.length)) { // We can't remux if the initSegment could not be generated logger.warn('[passthrough-remuxer.ts]: Failed to generate initSegment.'); return result; } if (this.emitInitSegment) { initSegment.tracks = this.initTracks; this.emitInitSegment = false; } const duration = getDuration(data, initData); const startDTS = getStartDTS(initData, data); const decodeTime = startDTS === null ? timeOffset : startDTS; if (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || initSegment.timescale !== initPTS.timescale && accurateTimeOffset) { initSegment.initPTS = decodeTime - timeOffset; if (initPTS && initPTS.timescale === 1) { logger.warn(`Adjusting initPTS by ${initSegment.initPTS - initPTS.baseTime}`); } this.initPTS = initPTS = { baseTime: initSegment.initPTS, timescale: 1 }; } const startTime = audioTrack ? decodeTime - initPTS.baseTime / initPTS.timescale : lastEndTime; const endTime = startTime + duration; offsetStartDTS(initData, data, initPTS.baseTime / initPTS.timescale); if (duration > 0) { this.lastEndTime = endTime; } else { logger.warn('Duration parsed from mp4 should be greater than zero'); this.resetNextTimestamp(); } const hasAudio = !!initData.audio; const hasVideo = !!initData.video; let type = ''; if (hasAudio) { type += 'audio'; } if (hasVideo) { type += 'video'; } const track = { data1: data, startPTS: startTime, startDTS: startTime, endPTS: endTime, endDTS: endTime, type, hasAudio, hasVideo, nb: 1, dropped: 0 }; result.audio = track.type === 'audio' ? track : undefined; result.video = track.type !== 'audio' ? track : undefined; result.initSegment = initSegment; result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS); if (textTrack.samples.length) { result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS); } return result; } } function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) { if (initPTS === null) { return true; } // InitPTS is invalid when distance from program would be more than segment duration or a minimum of one second const minDuration = Math.max(duration, 1); const startTime = startDTS - initPTS.baseTime / initPTS.timescale; return Math.abs(startTime - timeOffset) > minDuration; } function getParsedTrackCodec(track, type) { const parsedCodec = track == null ? void 0 : track.codec; if (parsedCodec && parsedCodec.length > 4) { return parsedCodec; } if (type === ElementaryStreamTypes.AUDIO) { if (parsedCodec === 'ec-3' || parsedCodec === 'ac-3' || parsedCodec === 'alac') { return parsedCodec; } if (parsedCodec === 'fLaC' || parsedCodec === 'Opus') { // Opting not to get `preferManagedMediaSource` from player config for isSupported() check for simplicity const preferManagedMediaSource = false; return getCodecCompatibleName(parsedCodec, preferManagedMediaSource); } const result = 'mp4a.40.5'; logger.info(`Parsed audio codec "${parsedCodec}" or audio object type not handled. Using "${result}"`); return result; } // Provide defaults based on codec type // This allows for some playback of some fmp4 playlists without CODECS defined in manifest logger.warn(`Unhandled video codec "${parsedCodec}"`); if (parsedCodec === 'hvc1' || parsedCodec === 'hev1') { return 'hvc1.1.6.L120.90'; } if (parsedCodec === 'av01') { return 'av01.0.04M.08'; } return 'avc1.42e01e'; } let now; // performance.now() not available on WebWorker, at least on Safari Desktop try { now = self.performance.now.bind(self.performance); } catch (err) { logger.debug('Unable to use Performance API on this environment'); now = optionalSelf == null ? void 0 : optionalSelf.Date.now; } const muxConfig = [{ demux: MP4Demuxer, remux: PassThroughRemuxer }, { demux: TSDemuxer, remux: MP4Remuxer }, { demux: AACDemuxer, remux: MP4Remuxer }, { demux: MP3Demuxer, remux: MP4Remuxer }]; { muxConfig.splice(2, 0, { demux: AC3Demuxer, remux: MP4Remuxer }); } class Transmuxer { constructor(observer, typeSupported, config, vendor, id) { this.async = false; this.observer = void 0; this.typeSupported = void 0; this.config = void 0; this.vendor = void 0; this.id = void 0; this.demuxer = void 0; this.remuxer = void 0; this.decrypter = void 0; this.probe = void 0; this.decryptionPromise = null; this.transmuxConfig = void 0; this.currentTransmuxState = void 0; this.observer = observer; this.typeSupported = typeSupported; this.config = config; this.vendor = vendor; this.id = id; } configure(transmuxConfig) { this.transmuxConfig = transmuxConfig; if (this.decrypter) { this.decrypter.reset(); } } push(data, decryptdata, chunkMeta, state) { const stats = chunkMeta.transmuxing; stats.executeStart = now(); let uintData = new Uint8Array(data); const { currentTransmuxState, transmuxConfig } = this; if (state) { this.currentTransmuxState = state; } const { contiguous, discontinuity, trackSwitch, accurateTimeOffset, timeOffset, initSegmentChange } = state || currentTransmuxState; const { audioCodec, videoCodec, defaultInitPts, duration, initSegmentData } = transmuxConfig; const keyData = getEncryptionType(uintData, decryptdata); if (keyData && keyData.method === 'AES-128') { const decrypter = this.getDecrypter(); // Software decryption is synchronous; webCrypto is not if (decrypter.isSync()) { // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached // data is handled in the flush() call let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer); // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress const loadingParts = chunkMeta.part > -1; if (loadingParts) { decryptedData = decrypter.flush(); } if (!decryptedData) { stats.executeEnd = now(); return emptyResult(chunkMeta); } uintData = new Uint8Array(decryptedData); } else { this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => { // Calling push here is important; if flush() is called while this is still resolving, this ensures that // the decrypted data has been transmuxed const result = this.push(decryptedData, null, chunkMeta); this.decryptionPromise = null; return result; }); return this.decryptionPromise; } } const resetMuxers = this.needsProbing(discontinuity, trackSwitch); if (resetMuxers) { const error = this.configureTransmuxer(uintData); if (error) { logger.warn(`[transmuxer] ${error.message}`); this.observer.emit(Events.ERROR, Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, fatal: false, error, reason: error.message }); stats.executeEnd = now(); return emptyResult(chunkMeta); } } if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) { this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata); } if (discontinuity || initSegmentChange || resetMuxers) { this.resetInitialTimestamp(defaultInitPts); } if (!contiguous) { this.resetContiguity(); } const result = this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta); const currentState = this.currentTransmuxState; currentState.contiguous = true; currentState.discontinuity = false; currentState.trackSwitch = false; stats.executeEnd = now(); return result; } // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type) flush(chunkMeta) { const stats = chunkMeta.transmuxing; stats.executeStart = now(); const { decrypter, currentTransmuxState, decryptionPromise } = this; if (decryptionPromise) { // Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore // only flushing is required for async decryption return decryptionPromise.then(() => { return this.flush(chunkMeta); }); } const transmuxResults = []; const { timeOffset } = currentTransmuxState; if (decrypter) { // The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults // This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads, // or for progressive downloads with small segments) const decryptedData = decrypter.flush(); if (decryptedData) { // Push always returns a TransmuxerResult if decryptdata is null transmuxResults.push(this.push(decryptedData, null, chunkMeta)); } } const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { // If probing failed, then Hls.js has been given content its not able to handle stats.executeEnd = now(); return [emptyResult(chunkMeta)]; } const demuxResultOrPromise = demuxer.flush(timeOffset); if (isPromise(demuxResultOrPromise)) { // Decrypt final SAMPLE-AES samples return demuxResultOrPromise.then(demuxResult => { this.flushRemux(transmuxResults, demuxResult, chunkMeta); return transmuxResults; }); } this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta); return transmuxResults; } flushRemux(transmuxResults, demuxResult, chunkMeta) { const { audioTrack, videoTrack, id3Track, textTrack } = demuxResult; const { accurateTimeOffset, timeOffset } = this.currentTransmuxState; logger.log(`[transmuxer.ts]: Flushed fragment ${chunkMeta.sn}${chunkMeta.part > -1 ? ' p: ' + chunkMeta.part : ''} of level ${chunkMeta.level}`); const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id); transmuxResults.push({ remuxResult, chunkMeta }); chunkMeta.transmuxing.executeEnd = now(); } resetInitialTimestamp(defaultInitPts) { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; } demuxer.resetTimeStamp(defaultInitPts); remuxer.resetTimeStamp(defaultInitPts); } resetContiguity() { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; } demuxer.resetContiguity(); remuxer.resetNextTimestamp(); } resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata) { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; } demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration); remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata); } destroy() { if (this.demuxer) { this.demuxer.destroy(); this.demuxer = undefined; } if (this.remuxer) { this.remuxer.destroy(); this.remuxer = undefined; } } transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) { let result; if (keyData && keyData.method === 'SAMPLE-AES') { result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta); } else { result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta); } return result; } transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) { const { audioTrack, videoTrack, id3Track, textTrack } = this.demuxer.demux(data, timeOffset, false, !this.config.progressive); const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id); return { remuxResult, chunkMeta }; } transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) { return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then(demuxResult => { const remuxResult = this.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, this.id); return { remuxResult, chunkMeta }; }); } configureTransmuxer(data) { const { config, observer, typeSupported, vendor } = this; // probe for content type let mux; for (let i = 0, len = muxConfig.length; i < len; i++) { var _muxConfig$i$demux; if ((_muxConfig$i$demux = muxConfig[i].demux) != null && _muxConfig$i$demux.probe(data)) { mux = muxConfig[i]; break; } } if (!mux) { return new Error('Failed to find demuxer by probing fragment data'); } // so let's check that current remuxer and demuxer are still valid const demuxer = this.demuxer; const remuxer = this.remuxer; const Remuxer = mux.remux; const Demuxer = mux.demux; if (!remuxer || !(remuxer instanceof Remuxer)) { this.remuxer = new Remuxer(observer, config, typeSupported, vendor); } if (!demuxer || !(demuxer instanceof Demuxer)) { this.demuxer = new Demuxer(observer, config, typeSupported); this.probe = Demuxer.probe; } } needsProbing(discontinuity, trackSwitch) { // in case of continuity change, or track switch // we might switch from content type (AAC container to TS container, or TS to fmp4 for example) return !this.demuxer || !this.remuxer || discontinuity || trackSwitch; } getDecrypter() { let decrypter = this.decrypter; if (!decrypter) { decrypter = this.decrypter = new Decrypter(this.config); } return decrypter; } } function getEncryptionType(data, decryptData) { let encryptionType = null; if (data.byteLength > 0 && (decryptData == null ? void 0 : decryptData.key) != null && decryptData.iv !== null && decryptData.method != null) { encryptionType = decryptData; } return encryptionType; } const emptyResult = chunkMeta => ({ remuxResult: {}, chunkMeta }); function isPromise(p) { return 'then' in p && p.then instanceof Function; } class TransmuxConfig { constructor(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) { this.audioCodec = void 0; this.videoCodec = void 0; this.initSegmentData = void 0; this.duration = void 0; this.defaultInitPts = void 0; this.audioCodec = audioCodec; this.videoCodec = videoCodec; this.initSegmentData = initSegmentData; this.duration = duration; this.defaultInitPts = defaultInitPts || null; } } class TransmuxState { constructor(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) { this.discontinuity = void 0; this.contiguous = void 0; this.accurateTimeOffset = void 0; this.trackSwitch = void 0; this.timeOffset = void 0; this.initSegmentChange = void 0; this.discontinuity = discontinuity; this.contiguous = contiguous; this.accurateTimeOffset = accurateTimeOffset; this.trackSwitch = trackSwitch; this.timeOffset = timeOffset; this.initSegmentChange = initSegmentChange; } } var eventemitter3 = {exports: {}}; (function (module) { var has = Object.prototype.hasOwnProperty , prefix = '~'; /** * Constructor to create a storage for our `EE` objects. * An `Events` instance is a plain object whose properties are event names. * * @constructor * @private */ function Events() {} // // We try to not inherit from `Object.prototype`. In some engines creating an // instance in this way is faster than calling `Object.create(null)` directly. // If `Object.create(null)` is not supported we prefix the event names with a // character to make sure that the built-in object properties are not // overridden or used as an attack vector. // if (Object.create) { Events.prototype = Object.create(null); // // This hack is needed because the `__proto__` property is still inherited in // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. // if (!new Events().__proto__) prefix = false; } /** * Representation of a single event listener. * * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} [once=false] Specify if the listener is a one-time listener. * @constructor * @private */ function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; } /** * Add a listener for a given event. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} once Specify if the listener is a one-time listener. * @returns {EventEmitter} * @private */ function addListener(emitter, event, fn, context, once) { if (typeof fn !== 'function') { throw new TypeError('The listener must be a function'); } var listener = new EE(fn, context || emitter, once) , evt = prefix ? prefix + event : event; if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); else emitter._events[evt] = [emitter._events[evt], listener]; return emitter; } /** * Clear event by name. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} evt The Event name. * @private */ function clearEvent(emitter, evt) { if (--emitter._eventsCount === 0) emitter._events = new Events(); else delete emitter._events[evt]; } /** * Minimal `EventEmitter` interface that is molded against the Node.js * `EventEmitter` interface. * * @constructor * @public */ function EventEmitter() { this._events = new Events(); this._eventsCount = 0; } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @public */ EventEmitter.prototype.eventNames = function eventNames() { var names = [] , events , name; if (this._eventsCount === 0) return names; for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }; /** * Return the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Array} The registered listeners. * @public */ EventEmitter.prototype.listeners = function listeners(event) { var evt = prefix ? prefix + event : event , handlers = this._events[evt]; if (!handlers) return []; if (handlers.fn) return [handlers.fn]; for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { ee[i] = handlers[i].fn; } return ee; }; /** * Return the number of listeners listening to a given event. * * @param {(String|Symbol)} event The event name. * @returns {Number} The number of listeners. * @public */ EventEmitter.prototype.listenerCount = function listenerCount(event) { var evt = prefix ? prefix + event : event , listeners = this._events[evt]; if (!listeners) return 0; if (listeners.fn) return 1; return listeners.length; }; /** * Calls each of the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Boolean} `true` if the event had listeners, else `false`. * @public */ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }; /** * Add a listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.on = function on(event, fn, context) { return addListener(this, event, fn, context, false); }; /** * Add a one-time listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.once = function once(event, fn, context) { return addListener(this, event, fn, context, true); }; /** * Remove the listeners of a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn Only remove the listeners that match this function. * @param {*} context Only remove the listeners that have this context. * @param {Boolean} once Only remove one-time listeners. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return this; if (!fn) { clearEvent(this, evt); return this; } var listeners = this._events[evt]; if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { clearEvent(this, evt); } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; else clearEvent(this, evt); } return this; }; /** * Remove all listeners, or those of the specified event. * * @param {(String|Symbol)} [event] The event name. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { var evt; if (event) { evt = prefix ? prefix + event : event; if (this._events[evt]) clearEvent(this, evt); } else { this._events = new Events(); this._eventsCount = 0; } return this; }; // // Alias methods names because people roll like that. // EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.addListener = EventEmitter.prototype.on; // // Expose the prefix. // EventEmitter.prefixed = prefix; // // Allow `EventEmitter` to be imported as module namespace. // EventEmitter.EventEmitter = EventEmitter; // // Expose the module. // { module.exports = EventEmitter; } } (eventemitter3)); var eventemitter3Exports = eventemitter3.exports; var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports); class TransmuxerInterface { constructor(hls, id, onTransmuxComplete, onFlush) { this.error = null; this.hls = void 0; this.id = void 0; this.observer = void 0; this.frag = null; this.part = null; this.useWorker = void 0; this.workerContext = null; this.onwmsg = void 0; this.transmuxer = null; this.onTransmuxComplete = void 0; this.onFlush = void 0; const config = hls.config; this.hls = hls; this.id = id; this.useWorker = !!config.enableWorker; this.onTransmuxComplete = onTransmuxComplete; this.onFlush = onFlush; const forwardMessage = (ev, data) => { data = data || {}; data.frag = this.frag; data.id = this.id; if (ev === Events.ERROR) { this.error = data.error; } this.hls.trigger(ev, data); }; // forward events to main thread this.observer = new EventEmitter(); this.observer.on(Events.FRAG_DECRYPTED, forwardMessage); this.observer.on(Events.ERROR, forwardMessage); const MediaSource = getMediaSource(config.preferManagedMediaSource) || { isTypeSupported: () => false }; const m2tsTypeSupported = { mpeg: MediaSource.isTypeSupported('audio/mpeg'), mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'), ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"') }; if (this.useWorker && typeof Worker !== 'undefined') { const canCreateWorker = config.workerPath || hasUMDWorker(); if (canCreateWorker) { try { if (config.workerPath) { logger.log(`loading Web Worker ${config.workerPath} for "${id}"`); this.workerContext = loadWorker(config.workerPath); } else { logger.log(`injecting Web Worker for "${id}"`); this.workerContext = injectWorker(); } this.onwmsg = event => this.onWorkerMessage(event); const { worker } = this.workerContext; worker.addEventListener('message', this.onwmsg); worker.onerror = event => { const error = new Error(`${event.message} (${event.filename}:${event.lineno})`); config.enableWorker = false; logger.warn(`Error in "${id}" Web Worker, fallback to inline`); this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal: false, event: 'demuxerWorker', error }); }; worker.postMessage({ cmd: 'init', typeSupported: m2tsTypeSupported, vendor: '', id: id, config: JSON.stringify(config) }); } catch (err) { logger.warn(`Error setting up "${id}" Web Worker, fallback to inline`, err); this.resetWorker(); this.error = null; this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id); } return; } } this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id); } resetWorker() { if (this.workerContext) { const { worker, objectURL } = this.workerContext; if (objectURL) { // revoke the Object URL that was used to create transmuxer worker, so as not to leak it self.URL.revokeObjectURL(objectURL); } worker.removeEventListener('message', this.onwmsg); worker.onerror = null; worker.terminate(); this.workerContext = null; } } destroy() { if (this.workerContext) { this.resetWorker(); this.onwmsg = undefined; } else { const transmuxer = this.transmuxer; if (transmuxer) { transmuxer.destroy(); this.transmuxer = null; } } const observer = this.observer; if (observer) { observer.removeAllListeners(); } this.frag = null; // @ts-ignore this.observer = null; // @ts-ignore this.hls = null; } push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) { var _frag$initSegment, _lastFrag$initSegment; chunkMeta.transmuxing.start = self.performance.now(); const { transmuxer } = this; const timeOffset = part ? part.start : frag.start; // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones const decryptdata = frag.decryptdata; const lastFrag = this.frag; const discontinuity = !(lastFrag && frag.cc === lastFrag.cc); const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level); const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1; const partDiff = this.part ? chunkMeta.part - this.part.index : -1; const progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount); const contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0)); const now = self.performance.now(); if (trackSwitch || snDiff || frag.stats.parsing.start === 0) { frag.stats.parsing.start = now; } if (part && (partDiff || !contiguous)) { part.stats.parsing.start = now; } const initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url)); const state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange); if (!contiguous || discontinuity || initSegmentChange) { logger.log(`[transmuxer-interface, ${frag.type}]: Starting new transmux session for sn: ${chunkMeta.sn} p: ${chunkMeta.part} level: ${chunkMeta.level} id: ${chunkMeta.id} discontinuity: ${discontinuity} trackSwitch: ${trackSwitch} contiguous: ${contiguous} accurateTimeOffset: ${accurateTimeOffset} timeOffset: ${timeOffset} initSegmentChange: ${initSegmentChange}`); const config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS); this.configureTransmuxer(config); } this.frag = frag; this.part = part; // Frags with sn of 'initSegment' are not transmuxed if (this.workerContext) { // post fragment payload as transferable objects for ArrayBuffer (no copy) this.workerContext.worker.postMessage({ cmd: 'demux', data, decryptdata, chunkMeta, state }, data instanceof ArrayBuffer ? [data] : []); } else if (transmuxer) { const transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state); if (isPromise(transmuxResult)) { transmuxer.async = true; transmuxResult.then(data => { this.handleTransmuxComplete(data); }).catch(error => { this.transmuxerError(error, chunkMeta, 'transmuxer-interface push error'); }); } else { transmuxer.async = false; this.handleTransmuxComplete(transmuxResult); } } } flush(chunkMeta) { chunkMeta.transmuxing.start = self.performance.now(); const { transmuxer } = this; if (this.workerContext) { this.workerContext.worker.postMessage({ cmd: 'flush', chunkMeta }); } else if (transmuxer) { let transmuxResult = transmuxer.flush(chunkMeta); const asyncFlush = isPromise(transmuxResult); if (asyncFlush || transmuxer.async) { if (!isPromise(transmuxResult)) { transmuxResult = Promise.resolve(transmuxResult); } transmuxResult.then(data => { this.handleFlushResult(data, chunkMeta); }).catch(error => { this.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error'); }); } else { this.handleFlushResult(transmuxResult, chunkMeta); } } } transmuxerError(error, chunkMeta, reason) { if (!this.hls) { return; } this.error = error; this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_PARSING_ERROR, chunkMeta, frag: this.frag || undefined, fatal: false, error, err: error, reason }); } handleFlushResult(results, chunkMeta) { results.forEach(result => { this.handleTransmuxComplete(result); }); this.onFlush(chunkMeta); } onWorkerMessage(event) { const data = event.data; if (!(data != null && data.event)) { logger.warn(`worker message received with no ${data ? 'event name' : 'data'}`); return; } const hls = this.hls; if (!this.hls) { return; } switch (data.event) { case 'init': { var _this$workerContext; const objectURL = (_this$workerContext = this.workerContext) == null ? void 0 : _this$workerContext.objectURL; if (objectURL) { // revoke the Object URL that was used to create transmuxer worker, so as not to leak it self.URL.revokeObjectURL(objectURL); } break; } case 'transmuxComplete': { this.handleTransmuxComplete(data.data); break; } case 'flush': { this.onFlush(data.data); break; } // pass logs from the worker thread to the main logger case 'workerLog': if (logger[data.data.logType]) { logger[data.data.logType](data.data.message); } break; default: { data.data = data.data || {}; data.data.frag = this.frag; data.data.id = this.id; hls.trigger(data.event, data.data); break; } } } configureTransmuxer(config) { const { transmuxer } = this; if (this.workerContext) { this.workerContext.worker.postMessage({ cmd: 'configure', config }); } else if (transmuxer) { transmuxer.configure(config); } } handleTransmuxComplete(result) { result.chunkMeta.transmuxing.end = self.performance.now(); this.onTransmuxComplete(result); } } function subtitleOptionsIdentical(trackList1, trackList2) { if (trackList1.length !== trackList2.length) { return false; } for (let i = 0; i < trackList1.length; i++) { if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) { return false; } } return true; } function mediaAttributesIdentical(attrs1, attrs2, customAttributes) { // Media options with the same rendition ID must be bit identical const stableRenditionId = attrs1['STABLE-RENDITION-ID']; if (stableRenditionId && !customAttributes) { return stableRenditionId === attrs2['STABLE-RENDITION-ID']; } // When rendition ID is not present, compare attributes return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]); } function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) { return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase()); } const TICK_INTERVAL$2 = 100; // how often to tick in ms class AudioStreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO); this.videoBuffer = null; this.videoTrackCC = -1; this.waitingVideoCC = -1; this.bufferedTrack = null; this.switchingTrack = null; this.trackId = -1; this.waitingData = null; this.mainDetails = null; this.flushing = false; this.bufferFlushed = false; this.cachedTrackLoadedData = null; this._registerListeners(); } onHandlerDestroying() { this._unregisterListeners(); super.onHandlerDestroying(); this.mainDetails = null; this.bufferedTrack = null; this.switchingTrack = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.BUFFER_RESET, this.onBufferReset, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.BUFFER_RESET, this.onBufferReset, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value onInitPtsFound(event, { frag, id, initPTS, timescale }) { // Always update the new INIT PTS // Can change due level switch if (id === 'main') { const cc = frag.cc; this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`); this.videoTrackCC = cc; // If we are waiting, tick immediately to unblock audio fragment transmuxing if (this.state === State.WAITING_INIT_PTS) { this.tick(); } } } startLoad(startPosition) { if (!this.levels) { this.startPosition = startPosition; this.state = State.STOPPED; return; } const lastCurrentTime = this.lastCurrentTime; this.stopLoad(); this.setInterval(TICK_INTERVAL$2); if (lastCurrentTime > 0 && startPosition === -1) { this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`); startPosition = lastCurrentTime; this.state = State.IDLE; } else { this.loadedmetadata = false; this.state = State.WAITING_TRACK; } this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; this.tick(); } doTick() { switch (this.state) { case State.IDLE: this.doTickIdle(); break; case State.WAITING_TRACK: { var _levels$trackId; const { levels, trackId } = this; const details = levels == null ? void 0 : (_levels$trackId = levels[trackId]) == null ? void 0 : _levels$trackId.details; if (details) { if (this.waitForCdnTuneIn(details)) { break; } this.state = State.WAITING_INIT_PTS; } break; } case State.FRAG_LOADING_WAITING_RETRY: { var _this$media; const now = performance.now(); const retryDate = this.retryDate; // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { const { levels, trackId } = this; this.log('RetryDate reached, switch back to IDLE state'); this.resetStartWhenNotLoaded((levels == null ? void 0 : levels[trackId]) || null); this.state = State.IDLE; } break; } case State.WAITING_INIT_PTS: { // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS const waitingData = this.waitingData; if (waitingData) { const { frag, part, cache, complete } = waitingData; if (this.initPTS[frag.cc] !== undefined) { this.waitingData = null; this.waitingVideoCC = -1; this.state = State.FRAG_LOADING; const payload = cache.flush(); const data = { frag, part, payload, networkDetails: null }; this._handleFragmentLoadProgress(data); if (complete) { super._handleFragmentLoadComplete(data); } } else if (this.videoTrackCC !== this.waitingVideoCC) { // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found this.log(`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${this.videoTrackCC}`); this.clearWaitingFragment(); } else { // Drop waiting fragment if an earlier fragment is needed const pos = this.getLoadPosition(); const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, pos, this.config.maxBufferHole); const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, this.config.maxFragLookUpTolerance, frag); if (waitingFragmentAtPosition < 0) { this.log(`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`); this.clearWaitingFragment(); } } } else { this.state = State.IDLE; } } } this.onTickEnd(); } clearWaitingFragment() { const waitingData = this.waitingData; if (waitingData) { this.fragmentTracker.removeFragment(waitingData.frag); this.waitingData = null; this.waitingVideoCC = -1; this.state = State.IDLE; } } resetLoadingState() { this.clearWaitingFragment(); super.resetLoadingState(); } onTickEnd() { const { media } = this; if (!(media != null && media.readyState)) { // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0) return; } this.lastCurrentTime = media.currentTime; } doTickIdle() { const { hls, levels, media, trackId } = this; const config = hls.config; // 1. if video not attached AND // start fragment already requested OR start frag prefetch not enabled // 2. if tracks or track not loaded and selected // then exit loop // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) { return; } const levelInfo = levels[trackId]; const trackDetails = levelInfo.details; if (!trackDetails || trackDetails.live && this.levelLastLoaded !== levelInfo || this.waitForCdnTuneIn(trackDetails)) { this.state = State.WAITING_TRACK; return; } const bufferable = this.mediaBuffer ? this.mediaBuffer : this.media; if (this.bufferFlushed && bufferable) { this.bufferFlushed = false; this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO); } const bufferInfo = this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO); if (bufferInfo === null) { return; } const { bufferedTrack, switchingTrack } = this; if (!switchingTrack && this._streamEnded(bufferInfo, trackDetails)) { hls.trigger(Events.BUFFER_EOS, { type: 'audio' }); this.state = State.ENDED; return; } const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN); const bufferLen = bufferInfo.len; const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len); const fragments = trackDetails.fragments; const start = fragments[0].start; let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end; if (switchingTrack && media) { const pos = this.getLoadPosition(); // STABLE if (bufferedTrack && !mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)) { targetBufferTime = pos; } // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime if (trackDetails.PTSKnown && pos < start) { // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start if (bufferInfo.end > start || bufferInfo.nextStart) { this.log('Alt audio track ahead of main track, seek to start of alt audio track'); media.currentTime = start + 0.05; } } } // if buffer length is less than maxBufLen, or near the end, find a fragment to load if (bufferLen >= maxBufLen && !switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) { return; } let frag = this.getNextFragment(targetBufferTime, trackDetails); let atGap = false; // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags if (frag && this.isLoopLoading(frag, targetBufferTime)) { atGap = !!frag.gap; frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen); } if (!frag) { this.bufferFlushed = true; return; } // Buffer audio up to one target duration ahead of main buffer const atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration; if (atBufferSyncLimit || // Or wait for main buffer after buffing some audio !(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) { // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN); if (mainFrag === null) { return; } // Bridge gaps in main buffer atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0); if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) { return; } } this.loadFragment(frag, levelInfo, targetBufferTime); } getMaxBufferLength(mainBufferLength) { const maxConfigBuffer = super.getMaxBufferLength(); if (!mainBufferLength) { return maxConfigBuffer; } return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength); } onMediaDetaching() { this.videoBuffer = null; this.bufferFlushed = this.flushing = false; super.onMediaDetaching(); } onAudioTracksUpdated(event, { audioTracks }) { // Reset tranxmuxer is essential for large context switches (Content Steering) this.resetTransmuxer(); this.levels = audioTracks.map(mediaPlaylist => new Level(mediaPlaylist)); } onAudioTrackSwitching(event, data) { // if any URL found on new audio track, it is an alternate audio track const altAudio = !!data.url; this.trackId = data.id; const { fragCurrent } = this; if (fragCurrent) { fragCurrent.abortRequests(); this.removeUnbufferedFrags(fragCurrent.start); } this.resetLoadingState(); // destroy useless transmuxer when switching audio to main if (!altAudio) { this.resetTransmuxer(); } else { // switching to audio track, start timer if not already started this.setInterval(TICK_INTERVAL$2); } // should we switch tracks ? if (altAudio) { this.switchingTrack = data; // main audio track are handled by stream-controller, just do something if switching to alt audio track this.state = State.IDLE; this.flushAudioIfNeeded(data); } else { this.switchingTrack = null; this.bufferedTrack = data; this.state = State.STOPPED; } this.tick(); } onManifestLoading() { this.fragmentTracker.removeAllFragments(); this.startPosition = this.lastCurrentTime = 0; this.bufferFlushed = this.flushing = false; this.levels = this.mainDetails = this.waitingData = this.bufferedTrack = this.cachedTrackLoadedData = this.switchingTrack = null; this.startFragRequested = false; this.trackId = this.videoTrackCC = this.waitingVideoCC = -1; } onLevelLoaded(event, data) { this.mainDetails = data.details; if (this.cachedTrackLoadedData !== null) { this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData); this.cachedTrackLoadedData = null; } } onAudioTrackLoaded(event, data) { var _track$details; if (this.mainDetails == null) { this.cachedTrackLoadedData = data; return; } const { levels } = this; const { details: newDetails, id: trackId } = data; if (!levels) { this.warn(`Audio tracks were reset while loading level ${trackId}`); return; } this.log(`Audio track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`); const track = levels[trackId]; let sliding = 0; if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { this.checkLiveUpdate(newDetails); const mainDetails = this.mainDetails; if (newDetails.deltaUpdateFailed || !mainDetails) { return; } if (!track.details && newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { // Make sure our audio rendition is aligned with the "main" rendition, using // pdt as our reference times. alignMediaPlaylistByPDT(newDetails, mainDetails); sliding = newDetails.fragments[0].start; } else { var _this$levelLastLoaded; sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details); } } track.details = newDetails; this.levelLastLoaded = track; // compute start position if we are aligned with the main playlist if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) { this.setStartPosition(this.mainDetails || newDetails, sliding); } // only switch back to IDLE state if we were waiting for track to start downloading a new fragment if (this.state === State.WAITING_TRACK && !this.waitForCdnTuneIn(newDetails)) { this.state = State.IDLE; } // trigger handler right now this.tick(); } _handleFragmentLoadProgress(data) { var _frag$initSegment; const { frag, part, payload } = data; const { config, trackId, levels } = this; if (!levels) { this.warn(`Audio tracks were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`); return; } const track = levels[trackId]; if (!track) { this.warn('Audio track is undefined on fragment load progress'); return; } const details = track.details; if (!details) { this.warn('Audio track details undefined on fragment load progress'); this.removeUnbufferedFrags(frag.start); return; } const audioCodec = config.defaultAudioCodec || track.audioCodec || 'mp4a.40.2'; let transmuxer = this.transmuxer; if (!transmuxer) { transmuxer = this.transmuxer = new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); } // Check if we have video initPTS // If not we need to wait for it const initPTS = this.initPTS[frag.cc]; const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; if (initPTS !== undefined) { // this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) const accurateTimeOffset = false; // details.PTSKnown || !details.live; const partIndex = part ? part.index : -1; const partial = partIndex !== -1; const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); transmuxer.push(payload, initSegmentData, audioCodec, '', frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); } else { this.log(`Unknown video PTS for cc ${frag.cc}, waiting for video PTS before demuxing audio frag ${frag.sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); const { cache } = this.waitingData = this.waitingData || { frag, part, cache: new ChunkCache(), complete: false }; cache.push(new Uint8Array(payload)); this.waitingVideoCC = this.videoTrackCC; this.state = State.WAITING_INIT_PTS; } } _handleFragmentLoadComplete(fragLoadedData) { if (this.waitingData) { this.waitingData.complete = true; return; } super._handleFragmentLoadComplete(fragLoadedData); } onBufferReset( /* event: Events.BUFFER_RESET */ ) { // reset reference to sourcebuffers this.mediaBuffer = this.videoBuffer = null; this.loadedmetadata = false; } onBufferCreated(event, data) { const audioTrack = data.tracks.audio; if (audioTrack) { this.mediaBuffer = audioTrack.buffer || null; } if (data.tracks.video) { this.videoBuffer = data.tracks.video.buffer || null; } } onFragBuffered(event, data) { const { frag, part } = data; if (frag.type !== PlaylistLevelType.AUDIO) { if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) { const bufferable = this.videoBuffer || this.media; if (bufferable) { const bufferedTimeRanges = BufferHelper.getBuffered(bufferable); if (bufferedTimeRanges.length) { this.loadedmetadata = true; } } } return; } if (this.fragContextChanged(frag)) { // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion // Avoid setting state back to IDLE or concluding the audio switch; otherwise, the switched-to track will not buffer this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index : ''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}, audioSwitch: ${this.switchingTrack ? this.switchingTrack.name : 'false'}`); return; } if (frag.sn !== 'initSegment') { this.fragPrevious = frag; const track = this.switchingTrack; if (track) { this.bufferedTrack = track; this.switchingTrack = null; this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track)); } } this.fragBufferedComplete(frag, part); } onError(event, data) { var _data$context; if (data.fatal) { this.state = State.ERROR; return; } switch (data.details) { case ErrorDetails.FRAG_GAP: case ErrorDetails.FRAG_PARSING_ERROR: case ErrorDetails.FRAG_DECRYPT_ERROR: case ErrorDetails.FRAG_LOAD_ERROR: case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_ERROR: case ErrorDetails.KEY_LOAD_TIMEOUT: this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data); break; case ErrorDetails.AUDIO_TRACK_LOAD_ERROR: case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: case ErrorDetails.LEVEL_PARSING_ERROR: // in case of non fatal error while loading track, if not retrying to load track, switch back to IDLE if (!data.levelRetry && this.state === State.WAITING_TRACK && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.AUDIO_TRACK) { this.state = State.IDLE; } break; case ErrorDetails.BUFFER_APPEND_ERROR: case ErrorDetails.BUFFER_FULL_ERROR: if (!data.parent || data.parent !== 'audio') { return; } if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) { this.resetLoadingState(); return; } if (this.reduceLengthAndFlushBuffer(data)) { this.bufferedTrack = null; super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio'); } break; case ErrorDetails.INTERNAL_EXCEPTION: this.recoverWorkerError(data); break; } } onBufferFlushing(event, { type }) { if (type !== ElementaryStreamTypes.VIDEO) { this.flushing = true; } } onBufferFlushed(event, { type }) { if (type !== ElementaryStreamTypes.VIDEO) { this.flushing = false; this.bufferFlushed = true; if (this.state === State.ENDED) { this.state = State.IDLE; } const mediaBuffer = this.mediaBuffer || this.media; if (mediaBuffer) { this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO); this.tick(); } } } _handleTransmuxComplete(transmuxResult) { var _id3$samples; const id = 'audio'; const { hls } = this; const { remuxResult, chunkMeta } = transmuxResult; const context = this.getCurrentContext(chunkMeta); if (!context) { this.resetWhenMissingContext(chunkMeta); return; } const { frag, part, level } = context; const { details } = level; const { audio, text, id3, initSegment } = remuxResult; // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level. // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed. if (this.fragContextChanged(frag) || !details) { this.fragmentTracker.removeFragment(frag); return; } this.state = State.PARSING; if (this.switchingTrack && audio) { this.completeAudioSwitch(this.switchingTrack); } if (initSegment != null && initSegment.tracks) { const mapFragment = frag.initSegment || frag; this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta); hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { frag: mapFragment, id, tracks: initSegment.tracks }); // Only flush audio from old audio tracks when PTS is known on new audio track } if (audio) { const { startPTS, endPTS, startDTS, endDTS } = audio; if (part) { part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { startPTS, endPTS, startDTS, endDTS }; } frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS); this.bufferFragmentData(audio, frag, part, chunkMeta); } if (id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) { const emittedID3 = _extends({ id, frag, details }, id3); hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); } if (text) { const emittedText = _extends({ id, frag, details }, text); hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); } } _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) { if (this.state !== State.PARSING) { return; } // delete any video track found on audio transmuxer if (tracks.video) { delete tracks.video; } // include levelCodec in audio and video tracks const track = tracks.audio; if (!track) { return; } track.id = 'audio'; const variantAudioCodecs = currentLevel.audioCodec; this.log(`Init audio buffer, container:${track.container}, codecs[level/parsed]=[${variantAudioCodecs}/${track.codec}]`); // SourceBuffer will use track.levelCodec if defined if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) { track.levelCodec = variantAudioCodecs; } this.hls.trigger(Events.BUFFER_CODECS, tracks); const initSegment = track.initSegment; if (initSegment != null && initSegment.byteLength) { const segment = { type: 'audio', frag, part: null, chunkMeta, parent: frag.type, data: initSegment }; this.hls.trigger(Events.BUFFER_APPENDING, segment); } // trigger handler right now this.tickImmediate(); } loadFragment(frag, track, targetBufferTime) { // only load if fragment is not loaded or if in audio switch const fragState = this.fragmentTracker.getState(frag); this.fragCurrent = frag; // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { var _track$details2; if (frag.sn === 'initSegment') { this._loadInitSegment(frag, track); } else if ((_track$details2 = track.details) != null && _track$details2.live && !this.initPTS[frag.cc]) { this.log(`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`); this.state = State.WAITING_INIT_PTS; const mainDetails = this.mainDetails; if (mainDetails && mainDetails.fragments[0].start !== track.details.fragments[0].start) { alignMediaPlaylistByPDT(track.details, mainDetails); } } else { this.startFragRequested = true; super.loadFragment(frag, track, targetBufferTime); } } else { this.clearTrackerIfNeeded(frag); } } flushAudioIfNeeded(switchingTrack) { const { media, bufferedTrack } = this; const bufferedAttributes = bufferedTrack == null ? void 0 : bufferedTrack.attrs; const switchAttributes = switchingTrack.attrs; if (media && bufferedAttributes && (bufferedAttributes.CHANNELS !== switchAttributes.CHANNELS || bufferedTrack.name !== switchingTrack.name || bufferedTrack.lang !== switchingTrack.lang)) { this.log('Switching audio track : flushing all audio'); super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio'); this.bufferedTrack = null; } } completeAudioSwitch(switchingTrack) { const { hls } = this; this.flushAudioIfNeeded(switchingTrack); this.bufferedTrack = switchingTrack; this.switchingTrack = null; hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack)); } } class AudioTrackController extends BasePlaylistController { constructor(hls) { super(hls, '[audio-track-controller]'); this.tracks = []; this.groupIds = null; this.tracksInGroup = []; this.trackId = -1; this.currentTrack = null; this.selectDefaultTrack = true; this.registerListeners(); } registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); hls.off(Events.ERROR, this.onError, this); } destroy() { this.unregisterListeners(); this.tracks.length = 0; this.tracksInGroup.length = 0; this.currentTrack = null; super.destroy(); } onManifestLoading() { this.tracks = []; this.tracksInGroup = []; this.groupIds = null; this.currentTrack = null; this.trackId = -1; this.selectDefaultTrack = true; } onManifestParsed(event, data) { this.tracks = data.audioTracks || []; } onAudioTrackLoaded(event, data) { const { id, groupId, details } = data; const trackInActiveGroup = this.tracksInGroup[id]; if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) { this.warn(`Audio track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`); return; } const curDetails = trackInActiveGroup.details; trackInActiveGroup.details = data.details; this.log(`Audio track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`); if (id === this.trackId) { this.playlistLoaded(id, data, curDetails); } } onLevelLoading(event, data) { this.switchLevel(data.level); } onLevelSwitching(event, data) { this.switchLevel(data.level); } switchLevel(levelIndex) { const levelInfo = this.hls.levels[levelIndex]; if (!levelInfo) { return; } const audioGroups = levelInfo.audioGroups || null; const currentGroups = this.groupIds; let currentTrack = this.currentTrack; if (!audioGroups || (currentGroups == null ? void 0 : currentGroups.length) !== (audioGroups == null ? void 0 : audioGroups.length) || audioGroups != null && audioGroups.some(groupId => (currentGroups == null ? void 0 : currentGroups.indexOf(groupId)) === -1)) { this.groupIds = audioGroups; this.trackId = -1; this.currentTrack = null; const audioTracks = this.tracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1); if (audioTracks.length) { // Disable selectDefaultTrack if there are no default tracks if (this.selectDefaultTrack && !audioTracks.some(track => track.default)) { this.selectDefaultTrack = false; } // track.id should match hls.audioTracks index audioTracks.forEach((track, i) => { track.id = i; }); } else if (!currentTrack && !this.tracksInGroup.length) { // Do not dispatch AUDIO_TRACKS_UPDATED when there were and are no tracks return; } this.tracksInGroup = audioTracks; // Find preferred track const audioPreference = this.hls.config.audioPreference; if (!currentTrack && audioPreference) { const groupIndex = findMatchingOption(audioPreference, audioTracks, audioMatchPredicate); if (groupIndex > -1) { currentTrack = audioTracks[groupIndex]; } else { const allIndex = findMatchingOption(audioPreference, this.tracks); currentTrack = this.tracks[allIndex]; } } // Select initial track let trackId = this.findTrackId(currentTrack); if (trackId === -1 && currentTrack) { trackId = this.findTrackId(null); } // Dispatch events and load track if needed const audioTracksUpdated = { audioTracks }; this.log(`Updating audio tracks, ${audioTracks.length} track(s) found in group(s): ${audioGroups == null ? void 0 : audioGroups.join(',')}`); this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated); const selectedTrackId = this.trackId; if (trackId !== -1 && selectedTrackId === -1) { this.setAudioTrack(trackId); } else if (audioTracks.length && selectedTrackId === -1) { var _this$groupIds; const error = new Error(`No audio track selected for current audio group-ID(s): ${(_this$groupIds = this.groupIds) == null ? void 0 : _this$groupIds.join(',')} track count: ${audioTracks.length}`); this.warn(error.message); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR, fatal: true, error }); } } else if (this.shouldReloadPlaylist(currentTrack)) { // Retry playlist loading if no playlist is or has been loaded yet this.setAudioTrack(this.trackId); } } onError(event, data) { if (data.fatal || !data.context) { return; } if (data.context.type === PlaylistContextType.AUDIO_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) { this.requestScheduled = -1; this.checkRetry(data); } } get allAudioTracks() { return this.tracks; } get audioTracks() { return this.tracksInGroup; } get audioTrack() { return this.trackId; } set audioTrack(newId) { // If audio track is selected from API then don't choose from the manifest default track this.selectDefaultTrack = false; this.setAudioTrack(newId); } setAudioOption(audioOption) { const hls = this.hls; hls.config.audioPreference = audioOption; if (audioOption) { const allAudioTracks = this.allAudioTracks; this.selectDefaultTrack = false; if (allAudioTracks.length) { // First see if current option matches (no switch op) const currentTrack = this.currentTrack; if (currentTrack && matchesOption(audioOption, currentTrack, audioMatchPredicate)) { return currentTrack; } // Find option in available tracks (tracksInGroup) const groupIndex = findMatchingOption(audioOption, this.tracksInGroup, audioMatchPredicate); if (groupIndex > -1) { const track = this.tracksInGroup[groupIndex]; this.setAudioTrack(groupIndex); return track; } else if (currentTrack) { // Find option in nearest level audio group let searchIndex = hls.loadLevel; if (searchIndex === -1) { searchIndex = hls.firstAutoLevel; } const switchIndex = findClosestLevelWithAudioGroup(audioOption, hls.levels, allAudioTracks, searchIndex, audioMatchPredicate); if (switchIndex === -1) { // could not find matching variant return null; } // and switch level to acheive the audio group switch hls.nextLoadLevel = switchIndex; } if (audioOption.channels || audioOption.audioCodec) { // Could not find a match with codec / channels predicate // Find a match without channels or codec const withoutCodecAndChannelsMatch = findMatchingOption(audioOption, allAudioTracks); if (withoutCodecAndChannelsMatch > -1) { return allAudioTracks[withoutCodecAndChannelsMatch]; } } } } return null; } setAudioTrack(newId) { const tracks = this.tracksInGroup; // check if level idx is valid if (newId < 0 || newId >= tracks.length) { this.warn(`Invalid audio track id: ${newId}`); return; } // stopping live reloading timer if any this.clearTimer(); this.selectDefaultTrack = false; const lastTrack = this.currentTrack; const track = tracks[newId]; const trackLoaded = track.details && !track.details.live; if (newId === this.trackId && track === lastTrack && trackLoaded) { return; } this.log(`Switching to audio-track ${newId} "${track.name}" lang:${track.lang} group:${track.groupId} channels:${track.channels}`); this.trackId = newId; this.currentTrack = track; this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track)); // Do not reload track unless live if (trackLoaded) { return; } const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details); this.loadPlaylist(hlsUrlParameters); } findTrackId(currentTrack) { const audioTracks = this.tracksInGroup; for (let i = 0; i < audioTracks.length; i++) { const track = audioTracks[i]; if (this.selectDefaultTrack && !track.default) { continue; } if (!currentTrack || matchesOption(currentTrack, track, audioMatchPredicate)) { return i; } } if (currentTrack) { const { name, lang, assocLang, characteristics, audioCodec, channels } = currentTrack; for (let i = 0; i < audioTracks.length; i++) { const track = audioTracks[i]; if (matchesOption({ name, lang, assocLang, characteristics, audioCodec, channels }, track, audioMatchPredicate)) { return i; } } for (let i = 0; i < audioTracks.length; i++) { const track = audioTracks[i]; if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) { return i; } } for (let i = 0; i < audioTracks.length; i++) { const track = audioTracks[i]; if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])) { return i; } } } return -1; } loadPlaylist(hlsUrlParameters) { const audioTrack = this.currentTrack; if (this.shouldLoadPlaylist(audioTrack) && audioTrack) { super.loadPlaylist(); const id = audioTrack.id; const groupId = audioTrack.groupId; let url = audioTrack.url; if (hlsUrlParameters) { try { url = hlsUrlParameters.addDirectives(url); } catch (error) { this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`); } } // track not retrieved yet, or live playlist we need to (re)load it this.log(`loading audio-track playlist ${id} "${audioTrack.name}" lang:${audioTrack.lang} group:${groupId}`); this.clearTimer(); this.hls.trigger(Events.AUDIO_TRACK_LOADING, { url, id, groupId, deliveryDirectives: hlsUrlParameters || null }); } } } const TICK_INTERVAL$1 = 500; // how often to tick in ms class SubtitleStreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE); this.currentTrackId = -1; this.tracksBuffered = []; this.mainDetails = null; this._registerListeners(); } onHandlerDestroying() { this._unregisterListeners(); super.onHandlerDestroying(); this.mainDetails = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } startLoad(startPosition) { this.stopLoad(); this.state = State.IDLE; this.setInterval(TICK_INTERVAL$1); this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; this.tick(); } onManifestLoading() { this.mainDetails = null; this.fragmentTracker.removeAllFragments(); } onMediaDetaching() { this.tracksBuffered = []; super.onMediaDetaching(); } onLevelLoaded(event, data) { this.mainDetails = data.details; } onSubtitleFragProcessed(event, data) { const { frag, success } = data; this.fragPrevious = frag; this.state = State.IDLE; if (!success) { return; } const buffered = this.tracksBuffered[this.currentTrackId]; if (!buffered) { return; } // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo // so we can re-use the logic used to detect how much has been buffered let timeRange; const fragStart = frag.start; for (let i = 0; i < buffered.length; i++) { if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) { timeRange = buffered[i]; break; } } const fragEnd = frag.start + frag.duration; if (timeRange) { timeRange.end = fragEnd; } else { timeRange = { start: fragStart, end: fragEnd }; buffered.push(timeRange); } this.fragmentTracker.fragBuffered(frag); this.fragBufferedComplete(frag, null); } onBufferFlushing(event, data) { const { startOffset, endOffset } = data; if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) { const endOffsetSubtitles = endOffset - 1; if (endOffsetSubtitles <= 0) { return; } data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles); this.tracksBuffered.forEach(buffered => { for (let i = 0; i < buffered.length;) { if (buffered[i].end <= endOffsetSubtitles) { buffered.shift(); continue; } else if (buffered[i].start < endOffsetSubtitles) { buffered[i].start = endOffsetSubtitles; } else { break; } i++; } }); this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE); } } onFragBuffered(event, data) { if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) { var _this$media; if ((_this$media = this.media) != null && _this$media.buffered.length) { this.loadedmetadata = true; } } } // If something goes wrong, proceed to next frag, if we were processing one. onError(event, data) { const frag = data.frag; if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.SUBTITLE) { if (data.details === ErrorDetails.FRAG_GAP) { this.fragmentTracker.fragBuffered(frag, true); } if (this.fragCurrent) { this.fragCurrent.abortRequests(); } if (this.state !== State.STOPPED) { this.state = State.IDLE; } } } // Got all new subtitle levels. onSubtitleTracksUpdated(event, { subtitleTracks }) { if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) { this.levels = subtitleTracks.map(mediaPlaylist => new Level(mediaPlaylist)); return; } this.tracksBuffered = []; this.levels = subtitleTracks.map(mediaPlaylist => { const level = new Level(mediaPlaylist); this.tracksBuffered[level.id] = []; return level; }); this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE); this.fragPrevious = null; this.mediaBuffer = null; } onSubtitleTrackSwitch(event, data) { var _this$levels; this.currentTrackId = data.id; if (!((_this$levels = this.levels) != null && _this$levels.length) || this.currentTrackId === -1) { this.clearInterval(); return; } // Check if track has the necessary details to load fragments const currentTrack = this.levels[this.currentTrackId]; if (currentTrack != null && currentTrack.details) { this.mediaBuffer = this.mediaBufferTimeRanges; } else { this.mediaBuffer = null; } if (currentTrack) { this.setInterval(TICK_INTERVAL$1); } } // Got a new set of subtitle fragments. onSubtitleTrackLoaded(event, data) { var _track$details; const { currentTrackId, levels } = this; const { details: newDetails, id: trackId } = data; if (!levels) { this.warn(`Subtitle tracks were reset while loading level ${trackId}`); return; } const track = levels[trackId]; if (trackId >= levels.length || !track) { return; } this.log(`Subtitle track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`); this.mediaBuffer = this.mediaBufferTimeRanges; let sliding = 0; if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { const mainDetails = this.mainDetails; if (newDetails.deltaUpdateFailed || !mainDetails) { return; } const mainSlidingStartFragment = mainDetails.fragments[0]; if (!track.details) { if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { alignMediaPlaylistByPDT(newDetails, mainDetails); sliding = newDetails.fragments[0].start; } else if (mainSlidingStartFragment) { // line up live playlist with main so that fragments in range are loaded sliding = mainSlidingStartFragment.start; addSliding(newDetails, sliding); } } else { var _this$levelLastLoaded; sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details); if (sliding === 0 && mainSlidingStartFragment) { // realign with main when there is no overlap with last refresh sliding = mainSlidingStartFragment.start; addSliding(newDetails, sliding); } } } track.details = newDetails; this.levelLastLoaded = track; if (trackId !== currentTrackId) { return; } if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) { this.setStartPosition(this.mainDetails || newDetails, sliding); } // trigger handler right now this.tick(); // If playlist is misaligned because of bad PDT or drift, delete details to resync with main on reload if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) { const foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0); if (!foundFrag) { this.warn('Subtitle playlist not aligned with playback'); track.details = undefined; } } } _handleFragmentLoadComplete(fragLoadedData) { const { frag, payload } = fragLoadedData; const decryptData = frag.decryptdata; const hls = this.hls; if (this.fragContextChanged(frag)) { return; } // check to see if the payload needs to be decrypted if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') { const startTime = performance.now(); // decrypt the subtitles this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => { hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_DECRYPT_ERROR, fatal: false, error: err, reason: err.message, frag }); throw err; }).then(decryptedData => { const endTime = performance.now(); hls.trigger(Events.FRAG_DECRYPTED, { frag, payload: decryptedData, stats: { tstart: startTime, tdecrypt: endTime } }); }).catch(err => { this.warn(`${err.name}: ${err.message}`); this.state = State.IDLE; }); } } doTick() { if (!this.media) { this.state = State.IDLE; return; } if (this.state === State.IDLE) { const { currentTrackId, levels } = this; const track = levels == null ? void 0 : levels[currentTrackId]; if (!track || !levels.length || !track.details) { return; } const { config } = this; const currentTime = this.getLoadPosition(); const bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole); const { end: targetBufferTime, len: bufferLen } = bufferedInfo; const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN); const trackDetails = track.details; const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration; if (bufferLen > maxBufLen) { return; } const fragments = trackDetails.fragments; const fragLen = fragments.length; const end = trackDetails.edge; let foundFrag = null; const fragPrevious = this.fragPrevious; if (targetBufferTime < end) { const tolerance = config.maxFragLookUpTolerance; const lookupTolerance = targetBufferTime > end - tolerance ? 0 : tolerance; foundFrag = findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance); if (!foundFrag && fragPrevious && fragPrevious.start < fragments[0].start) { foundFrag = fragments[0]; } } else { foundFrag = fragments[fragLen - 1]; } if (!foundFrag) { return; } foundFrag = this.mapToInitFragWhenRequired(foundFrag); if (foundFrag.sn !== 'initSegment') { // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment const curSNIdx = foundFrag.sn - trackDetails.startSN; const prevFrag = fragments[curSNIdx - 1]; if (prevFrag && prevFrag.cc === foundFrag.cc && this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED) { foundFrag = prevFrag; } } if (this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED) { // only load if fragment is not loaded this.loadFragment(foundFrag, track, targetBufferTime); } } } getMaxBufferLength(mainBufferLength) { const maxConfigBuffer = super.getMaxBufferLength(); if (!mainBufferLength) { return maxConfigBuffer; } return Math.max(maxConfigBuffer, mainBufferLength); } loadFragment(frag, level, targetBufferTime) { this.fragCurrent = frag; if (frag.sn === 'initSegment') { this._loadInitSegment(frag, level); } else { this.startFragRequested = true; super.loadFragment(frag, level, targetBufferTime); } } get mediaBufferTimeRanges() { return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []); } } class BufferableInstance { constructor(timeranges) { this.buffered = void 0; const getRange = (name, index, length) => { index = index >>> 0; if (index > length - 1) { throw new DOMException(`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`); } return timeranges[index][name]; }; this.buffered = { get length() { return timeranges.length; }, end(index) { return getRange('end', index, timeranges.length); }, start(index) { return getRange('start', index, timeranges.length); } }; } } class SubtitleTrackController extends BasePlaylistController { constructor(hls) { super(hls, '[subtitle-track-controller]'); this.media = null; this.tracks = []; this.groupIds = null; this.tracksInGroup = []; this.trackId = -1; this.currentTrack = null; this.selectDefaultTrack = true; this.queuedDefaultTrack = -1; this.asyncPollTrackChange = () => this.pollTrackChange(0); this.useTextTrackPolling = false; this.subtitlePollingInterval = -1; this._subtitleDisplay = true; this.onTextTracksChanged = () => { if (!this.useTextTrackPolling) { self.clearInterval(this.subtitlePollingInterval); } // Media is undefined when switching streams via loadSource() if (!this.media || !this.hls.config.renderTextTracksNatively) { return; } let textTrack = null; const tracks = filterSubtitleTracks(this.media.textTracks); for (let i = 0; i < tracks.length; i++) { if (tracks[i].mode === 'hidden') { // Do not break in case there is a following track with showing. textTrack = tracks[i]; } else if (tracks[i].mode === 'showing') { textTrack = tracks[i]; break; } } // Find internal track index for TextTrack const trackId = this.findTrackForTextTrack(textTrack); if (this.subtitleTrack !== trackId) { this.setSubtitleTrack(trackId); } }; this.registerListeners(); } destroy() { this.unregisterListeners(); this.tracks.length = 0; this.tracksInGroup.length = 0; this.currentTrack = null; this.onTextTracksChanged = this.asyncPollTrackChange = null; super.destroy(); } get subtitleDisplay() { return this._subtitleDisplay; } set subtitleDisplay(value) { this._subtitleDisplay = value; if (this.trackId > -1) { this.toggleTrackModes(); } } registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.off(Events.ERROR, this.onError, this); } // Listen for subtitle track change, then extract the current track ID. onMediaAttached(event, data) { this.media = data.media; if (!this.media) { return; } if (this.queuedDefaultTrack > -1) { this.subtitleTrack = this.queuedDefaultTrack; this.queuedDefaultTrack = -1; } this.useTextTrackPolling = !(this.media.textTracks && 'onchange' in this.media.textTracks); if (this.useTextTrackPolling) { this.pollTrackChange(500); } else { this.media.textTracks.addEventListener('change', this.asyncPollTrackChange); } } pollTrackChange(timeout) { self.clearInterval(this.subtitlePollingInterval); this.subtitlePollingInterval = self.setInterval(this.onTextTracksChanged, timeout); } onMediaDetaching() { if (!this.media) { return; } self.clearInterval(this.subtitlePollingInterval); if (!this.useTextTrackPolling) { this.media.textTracks.removeEventListener('change', this.asyncPollTrackChange); } if (this.trackId > -1) { this.queuedDefaultTrack = this.trackId; } const textTracks = filterSubtitleTracks(this.media.textTracks); // Clear loaded cues on media detachment from tracks textTracks.forEach(track => { clearCurrentCues(track); }); // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled. this.subtitleTrack = -1; this.media = null; } onManifestLoading() { this.tracks = []; this.groupIds = null; this.tracksInGroup = []; this.trackId = -1; this.currentTrack = null; this.selectDefaultTrack = true; } // Fired whenever a new manifest is loaded. onManifestParsed(event, data) { this.tracks = data.subtitleTracks; } onSubtitleTrackLoaded(event, data) { const { id, groupId, details } = data; const trackInActiveGroup = this.tracksInGroup[id]; if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) { this.warn(`Subtitle track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`); return; } const curDetails = trackInActiveGroup.details; trackInActiveGroup.details = data.details; this.log(`Subtitle track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`); if (id === this.trackId) { this.playlistLoaded(id, data, curDetails); } } onLevelLoading(event, data) { this.switchLevel(data.level); } onLevelSwitching(event, data) { this.switchLevel(data.level); } switchLevel(levelIndex) { const levelInfo = this.hls.levels[levelIndex]; if (!levelInfo) { return; } const subtitleGroups = levelInfo.subtitleGroups || null; const currentGroups = this.groupIds; let currentTrack = this.currentTrack; if (!subtitleGroups || (currentGroups == null ? void 0 : currentGroups.length) !== (subtitleGroups == null ? void 0 : subtitleGroups.length) || subtitleGroups != null && subtitleGroups.some(groupId => (currentGroups == null ? void 0 : currentGroups.indexOf(groupId)) === -1)) { this.groupIds = subtitleGroups; this.trackId = -1; this.currentTrack = null; const subtitleTracks = this.tracks.filter(track => !subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1); if (subtitleTracks.length) { // Disable selectDefaultTrack if there are no default tracks if (this.selectDefaultTrack && !subtitleTracks.some(track => track.default)) { this.selectDefaultTrack = false; } // track.id should match hls.audioTracks index subtitleTracks.forEach((track, i) => { track.id = i; }); } else if (!currentTrack && !this.tracksInGroup.length) { // Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks return; } this.tracksInGroup = subtitleTracks; // Find preferred track const subtitlePreference = this.hls.config.subtitlePreference; if (!currentTrack && subtitlePreference) { this.selectDefaultTrack = false; const groupIndex = findMatchingOption(subtitlePreference, subtitleTracks); if (groupIndex > -1) { currentTrack = subtitleTracks[groupIndex]; } else { const allIndex = findMatchingOption(subtitlePreference, this.tracks); currentTrack = this.tracks[allIndex]; } } // Select initial track let trackId = this.findTrackId(currentTrack); if (trackId === -1 && currentTrack) { trackId = this.findTrackId(null); } // Dispatch events and load track if needed const subtitleTracksUpdated = { subtitleTracks }; this.log(`Updating subtitle tracks, ${subtitleTracks.length} track(s) found in "${subtitleGroups == null ? void 0 : subtitleGroups.join(',')}" group-id`); this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated); if (trackId !== -1 && this.trackId === -1) { this.setSubtitleTrack(trackId); } } else if (this.shouldReloadPlaylist(currentTrack)) { // Retry playlist loading if no playlist is or has been loaded yet this.setSubtitleTrack(this.trackId); } } findTrackId(currentTrack) { const tracks = this.tracksInGroup; const selectDefault = this.selectDefaultTrack; for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; if (selectDefault && !track.default || !selectDefault && !currentTrack) { continue; } if (!currentTrack || matchesOption(track, currentTrack)) { return i; } } if (currentTrack) { for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) { return i; } } for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])) { return i; } } } return -1; } findTrackForTextTrack(textTrack) { if (textTrack) { const tracks = this.tracksInGroup; for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; if (subtitleTrackMatchesTextTrack(track, textTrack)) { return i; } } } return -1; } onError(event, data) { if (data.fatal || !data.context) { return; } if (data.context.type === PlaylistContextType.SUBTITLE_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) { this.checkRetry(data); } } get allSubtitleTracks() { return this.tracks; } /** get alternate subtitle tracks list from playlist **/ get subtitleTracks() { return this.tracksInGroup; } /** get/set index of the selected subtitle track (based on index in subtitle track lists) **/ get subtitleTrack() { return this.trackId; } set subtitleTrack(newId) { this.selectDefaultTrack = false; this.setSubtitleTrack(newId); } setSubtitleOption(subtitleOption) { this.hls.config.subtitlePreference = subtitleOption; if (subtitleOption) { const allSubtitleTracks = this.allSubtitleTracks; this.selectDefaultTrack = false; if (allSubtitleTracks.length) { // First see if current option matches (no switch op) const currentTrack = this.currentTrack; if (currentTrack && matchesOption(subtitleOption, currentTrack)) { return currentTrack; } // Find option in current group const groupIndex = findMatchingOption(subtitleOption, this.tracksInGroup); if (groupIndex > -1) { const track = this.tracksInGroup[groupIndex]; this.setSubtitleTrack(groupIndex); return track; } else if (currentTrack) { // If this is not the initial selection return null // option should have matched one in active group return null; } else { // Find the option in all tracks for initial selection const allIndex = findMatchingOption(subtitleOption, allSubtitleTracks); if (allIndex > -1) { return allSubtitleTracks[allIndex]; } } } } return null; } loadPlaylist(hlsUrlParameters) { super.loadPlaylist(); const currentTrack = this.currentTrack; if (this.shouldLoadPlaylist(currentTrack) && currentTrack) { const id = currentTrack.id; const groupId = currentTrack.groupId; let url = currentTrack.url; if (hlsUrlParameters) { try { url = hlsUrlParameters.addDirectives(url); } catch (error) { this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`); } } this.log(`Loading subtitle playlist for id ${id}`); this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, { url, id, groupId, deliveryDirectives: hlsUrlParameters || null }); } } /** * Disables the old subtitleTrack and sets current mode on the next subtitleTrack. * This operates on the DOM textTracks. * A value of -1 will disable all subtitle tracks. */ toggleTrackModes() { const { media } = this; if (!media) { return; } const textTracks = filterSubtitleTracks(media.textTracks); const currentTrack = this.currentTrack; let nextTrack; if (currentTrack) { nextTrack = textTracks.filter(textTrack => subtitleTrackMatchesTextTrack(currentTrack, textTrack))[0]; if (!nextTrack) { this.warn(`Unable to find subtitle TextTrack with name "${currentTrack.name}" and language "${currentTrack.lang}"`); } } [].slice.call(textTracks).forEach(track => { if (track.mode !== 'disabled' && track !== nextTrack) { track.mode = 'disabled'; } }); if (nextTrack) { const mode = this.subtitleDisplay ? 'showing' : 'hidden'; if (nextTrack.mode !== mode) { nextTrack.mode = mode; } } } /** * This method is responsible for validating the subtitle index and periodically reloading if live. * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track. */ setSubtitleTrack(newId) { const tracks = this.tracksInGroup; // setting this.subtitleTrack will trigger internal logic // if media has not been attached yet, it will fail // we keep a reference to the default track id // and we'll set subtitleTrack when onMediaAttached is triggered if (!this.media) { this.queuedDefaultTrack = newId; return; } // exit if track id as already set or invalid if (newId < -1 || newId >= tracks.length || !isFiniteNumber(newId)) { this.warn(`Invalid subtitle track id: ${newId}`); return; } // stopping live reloading timer if any this.clearTimer(); this.selectDefaultTrack = false; const lastTrack = this.currentTrack; const track = tracks[newId] || null; this.trackId = newId; this.currentTrack = track; this.toggleTrackModes(); if (!track) { // switch to -1 this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id: newId }); return; } const trackLoaded = !!track.details && !track.details.live; if (newId === this.trackId && track === lastTrack && trackLoaded) { return; } this.log(`Switching to subtitle-track ${newId}` + (track ? ` "${track.name}" lang:${track.lang} group:${track.groupId}` : '')); const { id, groupId = '', name, type, url } = track; this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id, groupId, name, type, url }); const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details); this.loadPlaylist(hlsUrlParameters); } } class BufferOperationQueue { constructor(sourceBufferReference) { this.buffers = void 0; this.queues = { video: [], audio: [], audiovideo: [] }; this.buffers = sourceBufferReference; } append(operation, type, pending) { const queue = this.queues[type]; queue.push(operation); if (queue.length === 1 && !pending) { this.executeNext(type); } } insertAbort(operation, type) { const queue = this.queues[type]; queue.unshift(operation); this.executeNext(type); } appendBlocker(type) { let execute; const promise = new Promise(resolve => { execute = resolve; }); const operation = { execute, onStart: () => {}, onComplete: () => {}, onError: () => {} }; this.append(operation, type); return promise; } executeNext(type) { const queue = this.queues[type]; if (queue.length) { const operation = queue[0]; try { // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations // which do not end with this event must call _onSBUpdateEnd manually operation.execute(); } catch (error) { logger.warn(`[buffer-operation-queue]: Exception executing "${type}" SourceBuffer operation: ${error}`); operation.onError(error); // Only shift the current operation off, otherwise the updateend handler will do this for us const sb = this.buffers[type]; if (!(sb != null && sb.updating)) { this.shiftAndExecuteNext(type); } } } } shiftAndExecuteNext(type) { this.queues[type].shift(); this.executeNext(type); } current(type) { return this.queues[type][0]; } } const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/; class BufferController { constructor(hls) { // The level details used to determine duration, target-duration and live this.details = null; // cache the self generated object url to detect hijack of video tag this._objectUrl = null; // A queue of buffer operations which require the SourceBuffer to not be updating upon execution this.operationQueue = void 0; // References to event listeners for each SourceBuffer, so that they can be referenced for event removal this.listeners = void 0; this.hls = void 0; // The number of BUFFER_CODEC events received before any sourceBuffers are created this.bufferCodecEventsExpected = 0; // The total number of BUFFER_CODEC events received this._bufferCodecEventsTotal = 0; // A reference to the attached media element this.media = null; // A reference to the active media source this.mediaSource = null; // Last MP3 audio chunk appended this.lastMpegAudioChunk = null; this.appendSource = void 0; // counters this.appendErrors = { audio: 0, video: 0, audiovideo: 0 }; this.tracks = {}; this.pendingTracks = {}; this.sourceBuffer = void 0; this.log = void 0; this.warn = void 0; this.error = void 0; this._onEndStreaming = event => { if (!this.hls) { return; } this.hls.pauseBuffering(); }; this._onStartStreaming = event => { if (!this.hls) { return; } this.hls.resumeBuffering(); }; // Keep as arrow functions so that we can directly reference these functions directly as event listeners this._onMediaSourceOpen = () => { const { media, mediaSource } = this; this.log('Media source opened'); if (media) { media.removeEventListener('emptied', this._onMediaEmptied); this.updateMediaElementDuration(); this.hls.trigger(Events.MEDIA_ATTACHED, { media, mediaSource: mediaSource }); } if (mediaSource) { // once received, don't listen anymore to sourceopen event mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen); } this.checkPendingTracks(); }; this._onMediaSourceClose = () => { this.log('Media source closed'); }; this._onMediaSourceEnded = () => { this.log('Media source ended'); }; this._onMediaEmptied = () => { const { mediaSrc, _objectUrl } = this; if (mediaSrc !== _objectUrl) { logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`); } }; this.hls = hls; const logPrefix = '[buffer-controller]'; this.appendSource = isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource)); this.log = logger.log.bind(logger, logPrefix); this.warn = logger.warn.bind(logger, logPrefix); this.error = logger.error.bind(logger, logPrefix); this._initSourceBuffer(); this.registerListeners(); } hasSourceTypes() { return this.getSourceBufferTypes().length > 0 || Object.keys(this.pendingTracks).length > 0; } destroy() { this.unregisterListeners(); this.details = null; this.lastMpegAudioChunk = null; // @ts-ignore this.hls = null; } registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.BUFFER_RESET, this.onBufferReset, this); hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this); hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.on(Events.BUFFER_EOS, this.onBufferEos, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); hls.on(Events.FRAG_PARSED, this.onFragParsed, this); hls.on(Events.FRAG_CHANGED, this.onFragChanged, this); } unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.BUFFER_RESET, this.onBufferReset, this); hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this); hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.off(Events.BUFFER_EOS, this.onBufferEos, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); hls.off(Events.FRAG_PARSED, this.onFragParsed, this); hls.off(Events.FRAG_CHANGED, this.onFragChanged, this); } _initSourceBuffer() { this.sourceBuffer = {}; this.operationQueue = new BufferOperationQueue(this.sourceBuffer); this.listeners = { audio: [], video: [], audiovideo: [] }; this.appendErrors = { audio: 0, video: 0, audiovideo: 0 }; this.lastMpegAudioChunk = null; } onManifestLoading() { this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0; this.details = null; } onManifestParsed(event, data) { // in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller // sourcebuffers will be created all at once when the expected nb of tracks will be reached // in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller // it will contain the expected nb of source buffers, no need to compute it let codecEvents = 2; if (data.audio && !data.video || !data.altAudio || !true) { codecEvents = 1; } this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = codecEvents; this.log(`${this.bufferCodecEventsExpected} bufferCodec event(s) expected`); } onMediaAttaching(event, data) { const media = this.media = data.media; const MediaSource = getMediaSource(this.appendSource); if (media && MediaSource) { var _ms$constructor; const ms = this.mediaSource = new MediaSource(); this.log(`created media source: ${(_ms$constructor = ms.constructor) == null ? void 0 : _ms$constructor.name}`); // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound ms.addEventListener('sourceopen', this._onMediaSourceOpen); ms.addEventListener('sourceended', this._onMediaSourceEnded); ms.addEventListener('sourceclose', this._onMediaSourceClose); if (this.appendSource) { ms.addEventListener('startstreaming', this._onStartStreaming); ms.addEventListener('endstreaming', this._onEndStreaming); } // cache the locally generated object url const objectUrl = this._objectUrl = self.URL.createObjectURL(ms); // link video and media Source if (this.appendSource) { try { media.removeAttribute('src'); // ManagedMediaSource will not open without disableRemotePlayback set to false or source alternatives const MMS = self.ManagedMediaSource; media.disableRemotePlayback = media.disableRemotePlayback || MMS && ms instanceof MMS; removeSourceChildren(media); addSource(media, objectUrl); media.load(); } catch (error) { media.src = objectUrl; } } else { media.src = objectUrl; } media.addEventListener('emptied', this._onMediaEmptied); } } onMediaDetaching() { const { media, mediaSource, _objectUrl } = this; if (mediaSource) { this.log('media source detaching'); if (mediaSource.readyState === 'open') { try { // endOfStream could trigger exception if any sourcebuffer is in updating state // we don't really care about checking sourcebuffer state here, // as we are anyway detaching the MediaSource // let's just avoid this exception to propagate mediaSource.endOfStream(); } catch (err) { this.warn(`onMediaDetaching: ${err.message} while calling endOfStream`); } } // Clean up the SourceBuffers by invoking onBufferReset this.onBufferReset(); mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen); mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded); mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose); if (this.appendSource) { mediaSource.removeEventListener('startstreaming', this._onStartStreaming); mediaSource.removeEventListener('endstreaming', this._onEndStreaming); } // Detach properly the MediaSource from the HTMLMediaElement as // suggested in https://github.com/w3c/media-source/issues/53. if (media) { media.removeEventListener('emptied', this._onMediaEmptied); if (_objectUrl) { self.URL.revokeObjectURL(_objectUrl); } // clean up video tag src only if it's our own url. some external libraries might // hijack the video tag and change its 'src' without destroying the Hls instance first if (this.mediaSrc === _objectUrl) { media.removeAttribute('src'); if (this.appendSource) { removeSourceChildren(media); } media.load(); } else { this.warn('media|source.src was changed by a third party - skip cleanup'); } } this.mediaSource = null; this.media = null; this._objectUrl = null; this.bufferCodecEventsExpected = this._bufferCodecEventsTotal; this.pendingTracks = {}; this.tracks = {}; } this.hls.trigger(Events.MEDIA_DETACHED, undefined); } onBufferReset() { this.getSourceBufferTypes().forEach(type => { this.resetBuffer(type); }); this._initSourceBuffer(); } resetBuffer(type) { const sb = this.sourceBuffer[type]; try { if (sb) { var _this$mediaSource; this.removeBufferListeners(type); // Synchronously remove the SB from the map before the next call in order to prevent an async function from // accessing it this.sourceBuffer[type] = undefined; if ((_this$mediaSource = this.mediaSource) != null && _this$mediaSource.sourceBuffers.length) { this.mediaSource.removeSourceBuffer(sb); } } } catch (err) { this.warn(`onBufferReset ${type}`, err); } } onBufferCodecs(event, data) { const sourceBufferCount = this.getSourceBufferTypes().length; const trackNames = Object.keys(data); trackNames.forEach(trackName => { if (sourceBufferCount) { // check if SourceBuffer codec needs to change const track = this.tracks[trackName]; if (track && typeof track.buffer.changeType === 'function') { var _trackCodec; const { id, codec, levelCodec, container, metadata } = data[trackName]; const currentCodecFull = pickMostCompleteCodecName(track.codec, track.levelCodec); const currentCodec = currentCodecFull == null ? void 0 : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1'); let trackCodec = pickMostCompleteCodecName(codec, levelCodec); const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1'); if (trackCodec && currentCodec !== nextCodec) { if (trackName.slice(0, 5) === 'audio') { trackCodec = getCodecCompatibleName(trackCodec, this.appendSource); } const mimeType = `${container};codecs=${trackCodec}`; this.appendChangeType(trackName, mimeType); this.log(`switching codec ${currentCodecFull} to ${trackCodec}`); this.tracks[trackName] = { buffer: track.buffer, codec, container, levelCodec, metadata, id }; } } } else { // if source buffer(s) not created yet, appended buffer tracks in this.pendingTracks this.pendingTracks[trackName] = data[trackName]; } }); // if sourcebuffers already created, do nothing ... if (sourceBufferCount) { return; } const bufferCodecEventsExpected = Math.max(this.bufferCodecEventsExpected - 1, 0); if (this.bufferCodecEventsExpected !== bufferCodecEventsExpected) { this.log(`${bufferCodecEventsExpected} bufferCodec event(s) expected ${trackNames.join(',')}`); this.bufferCodecEventsExpected = bufferCodecEventsExpected; } if (this.mediaSource && this.mediaSource.readyState === 'open') { this.checkPendingTracks(); } } appendChangeType(type, mimeType) { const { operationQueue } = this; const operation = { execute: () => { const sb = this.sourceBuffer[type]; if (sb) { this.log(`changing ${type} sourceBuffer type to ${mimeType}`); sb.changeType(mimeType); } operationQueue.shiftAndExecuteNext(type); }, onStart: () => {}, onComplete: () => {}, onError: error => { this.warn(`Failed to change ${type} SourceBuffer type`, error); } }; operationQueue.append(operation, type, !!this.pendingTracks[type]); } onBufferAppending(event, eventData) { const { hls, operationQueue, tracks } = this; const { data, type, frag, part, chunkMeta } = eventData; const chunkStats = chunkMeta.buffering[type]; const bufferAppendingStart = self.performance.now(); chunkStats.start = bufferAppendingStart; const fragBuffering = frag.stats.buffering; const partBuffering = part ? part.stats.buffering : null; if (fragBuffering.start === 0) { fragBuffering.start = bufferAppendingStart; } if (partBuffering && partBuffering.start === 0) { partBuffering.start = bufferAppendingStart; } // TODO: Only update timestampOffset when audio/mpeg fragment or part is not contiguous with previously appended // Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended) // in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset` // is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos). // More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486 const audioTrack = tracks.audio; let checkTimestampOffset = false; if (type === 'audio' && (audioTrack == null ? void 0 : audioTrack.container) === 'audio/mpeg') { checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn; this.lastMpegAudioChunk = chunkMeta; } const fragStart = frag.start; const operation = { execute: () => { chunkStats.executeStart = self.performance.now(); if (checkTimestampOffset) { const sb = this.sourceBuffer[type]; if (sb) { const delta = fragStart - sb.timestampOffset; if (Math.abs(delta) >= 0.1) { this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`); sb.timestampOffset = fragStart; } } } this.appendExecutor(data, type); }, onStart: () => { // logger.debug(`[buffer-controller]: ${type} SourceBuffer updatestart`); }, onComplete: () => { // logger.debug(`[buffer-controller]: ${type} SourceBuffer updateend`); const end = self.performance.now(); chunkStats.executeEnd = chunkStats.end = end; if (fragBuffering.first === 0) { fragBuffering.first = end; } if (partBuffering && partBuffering.first === 0) { partBuffering.first = end; } const { sourceBuffer } = this; const timeRanges = {}; for (const type in sourceBuffer) { timeRanges[type] = BufferHelper.getBuffered(sourceBuffer[type]); } this.appendErrors[type] = 0; if (type === 'audio' || type === 'video') { this.appendErrors.audiovideo = 0; } else { this.appendErrors.audio = 0; this.appendErrors.video = 0; } this.hls.trigger(Events.BUFFER_APPENDED, { type, frag, part, chunkMeta, parent: frag.type, timeRanges }); }, onError: error => { // in case any error occured while appending, put back segment in segments table const event = { type: ErrorTypes.MEDIA_ERROR, parent: frag.type, details: ErrorDetails.BUFFER_APPEND_ERROR, sourceBufferName: type, frag, part, chunkMeta, error, err: error, fatal: false }; if (error.code === DOMException.QUOTA_EXCEEDED_ERR) { // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror // let's stop appending any segments, and report BUFFER_FULL_ERROR error event.details = ErrorDetails.BUFFER_FULL_ERROR; } else { const appendErrorCount = ++this.appendErrors[type]; event.details = ErrorDetails.BUFFER_APPEND_ERROR; /* with UHD content, we could get loop of quota exceeded error until browser is able to evict some data from sourcebuffer. Retrying can help recover. */ this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`); if (appendErrorCount >= hls.config.appendErrorMaxRetry) { event.fatal = true; } } hls.trigger(Events.ERROR, event); } }; operationQueue.append(operation, type, !!this.pendingTracks[type]); } onBufferFlushing(event, data) { const { operationQueue } = this; const flushOperation = type => ({ execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset), onStart: () => { // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`); }, onComplete: () => { // logger.debug(`[buffer-controller]: Finished flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`); this.hls.trigger(Events.BUFFER_FLUSHED, { type }); }, onError: error => { this.warn(`Failed to remove from ${type} SourceBuffer`, error); } }); if (data.type) { operationQueue.append(flushOperation(data.type), data.type); } else { this.getSourceBufferTypes().forEach(type => { operationQueue.append(flushOperation(type), type); }); } } onFragParsed(event, data) { const { frag, part } = data; const buffersAppendedTo = []; const elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams; if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) { buffersAppendedTo.push('audiovideo'); } else { if (elementaryStreams[ElementaryStreamTypes.AUDIO]) { buffersAppendedTo.push('audio'); } if (elementaryStreams[ElementaryStreamTypes.VIDEO]) { buffersAppendedTo.push('video'); } } const onUnblocked = () => { const now = self.performance.now(); frag.stats.buffering.end = now; if (part) { part.stats.buffering.end = now; } const stats = part ? part.stats : frag.stats; this.hls.trigger(Events.FRAG_BUFFERED, { frag, part, stats, id: frag.type }); }; if (buffersAppendedTo.length === 0) { this.warn(`Fragments must have at least one ElementaryStreamType set. type: ${frag.type} level: ${frag.level} sn: ${frag.sn}`); } this.blockBuffers(onUnblocked, buffersAppendedTo); } onFragChanged(event, data) { this.trimBuffers(); } // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos() // an undefined data.type will mark all buffers as EOS. onBufferEos(event, data) { const ended = this.getSourceBufferTypes().reduce((acc, type) => { const sb = this.sourceBuffer[type]; if (sb && (!data.type || data.type === type)) { sb.ending = true; if (!sb.ended) { sb.ended = true; this.log(`${type} sourceBuffer now EOS`); } } return acc && !!(!sb || sb.ended); }, true); if (ended) { this.log(`Queueing mediaSource.endOfStream()`); this.blockBuffers(() => { this.getSourceBufferTypes().forEach(type => { const sb = this.sourceBuffer[type]; if (sb) { sb.ending = false; } }); const { mediaSource } = this; if (!mediaSource || mediaSource.readyState !== 'open') { if (mediaSource) { this.log(`Could not call mediaSource.endOfStream(). mediaSource.readyState: ${mediaSource.readyState}`); } return; } this.log(`Calling mediaSource.endOfStream()`); // Allow this to throw and be caught by the enqueueing function mediaSource.endOfStream(); }); } } onLevelUpdated(event, { details }) { if (!details.fragments.length) { return; } this.details = details; if (this.getSourceBufferTypes().length) { this.blockBuffers(this.updateMediaElementDuration.bind(this)); } else { this.updateMediaElementDuration(); } } trimBuffers() { const { hls, details, media } = this; if (!media || details === null) { return; } const sourceBufferTypes = this.getSourceBufferTypes(); if (!sourceBufferTypes.length) { return; } const config = hls.config; const currentTime = media.currentTime; const targetDuration = details.levelTargetDuration; // Support for deprecated liveBackBufferLength const backBufferLength = details.live && config.liveBackBufferLength !== null ? config.liveBackBufferLength : config.backBufferLength; if (isFiniteNumber(backBufferLength) && backBufferLength > 0) { const maxBackBufferLength = Math.max(backBufferLength, targetDuration); const targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength; this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition); } if (isFiniteNumber(config.frontBufferFlushThreshold) && config.frontBufferFlushThreshold > 0) { const frontBufferLength = Math.max(config.maxBufferLength, config.frontBufferFlushThreshold); const maxFrontBufferLength = Math.max(frontBufferLength, targetDuration); const targetFrontBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength; this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition); } } flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition) { const { details, sourceBuffer } = this; const sourceBufferTypes = this.getSourceBufferTypes(); sourceBufferTypes.forEach(type => { const sb = sourceBuffer[type]; if (sb) { const buffered = BufferHelper.getBuffered(sb); // when target buffer start exceeds actual buffer start if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) { this.hls.trigger(Events.BACK_BUFFER_REACHED, { bufferEnd: targetBackBufferPosition }); // Support for deprecated event: if (details != null && details.live) { this.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, { bufferEnd: targetBackBufferPosition }); } else if (sb.ended && buffered.end(buffered.length - 1) - currentTime < targetDuration * 2) { this.log(`Cannot flush ${type} back buffer while SourceBuffer is in ended state`); return; } this.hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, endOffset: targetBackBufferPosition, type }); } } }); } flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition) { const { sourceBuffer } = this; const sourceBufferTypes = this.getSourceBufferTypes(); sourceBufferTypes.forEach(type => { const sb = sourceBuffer[type]; if (sb) { const buffered = BufferHelper.getBuffered(sb); const numBufferedRanges = buffered.length; // The buffer is either empty or contiguous if (numBufferedRanges < 2) { return; } const bufferStart = buffered.start(numBufferedRanges - 1); const bufferEnd = buffered.end(numBufferedRanges - 1); // No flush if we can tolerate the current buffer length or the current buffer range we would flush is contiguous with current position if (targetFrontBufferPosition > bufferStart || currentTime >= bufferStart && currentTime <= bufferEnd) { return; } else if (sb.ended && currentTime - bufferEnd < 2 * targetDuration) { this.log(`Cannot flush ${type} front buffer while SourceBuffer is in ended state`); return; } this.hls.trigger(Events.BUFFER_FLUSHING, { startOffset: bufferStart, endOffset: Infinity, type }); } }); } /** * Update Media Source duration to current level duration or override to Infinity if configuration parameter * 'liveDurationInfinity` is set to `true` * More details: https://github.com/video-dev/hls.js/issues/355 */ updateMediaElementDuration() { if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') { return; } const { details, hls, media, mediaSource } = this; const levelDuration = details.fragments[0].start + details.totalduration; const mediaDuration = media.duration; const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0; if (details.live && hls.config.liveDurationInfinity) { // Override duration to Infinity mediaSource.duration = Infinity; this.updateSeekableRange(details); } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) { // levelDuration was the last value we set. // not using mediaSource.duration as the browser may tweak this value // only update Media Source duration if its value increase, this is to avoid // flushing already buffered portion when switching between quality level this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`); mediaSource.duration = levelDuration; } } updateSeekableRange(levelDetails) { const mediaSource = this.mediaSource; const fragments = levelDetails.fragments; const len = fragments.length; if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) { const start = Math.max(0, fragments[0].start); const end = Math.max(start, start + levelDetails.totalduration); this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`); mediaSource.setLiveSeekableRange(start, end); } } checkPendingTracks() { const { bufferCodecEventsExpected, operationQueue, pendingTracks } = this; // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once. // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after // data has been appended to existing ones. // 2 tracks is the max (one for audio, one for video). If we've reach this max go ahead and create the buffers. const pendingTracksCount = Object.keys(pendingTracks).length; if (pendingTracksCount && (!bufferCodecEventsExpected || pendingTracksCount === 2 || 'audiovideo' in pendingTracks)) { // ok, let's create them now ! this.createSourceBuffers(pendingTracks); this.pendingTracks = {}; // append any pending segments now ! const buffers = this.getSourceBufferTypes(); if (buffers.length) { this.hls.trigger(Events.BUFFER_CREATED, { tracks: this.tracks }); buffers.forEach(type => { operationQueue.executeNext(type); }); } else { const error = new Error('could not create source buffer for media codec(s)'); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR, fatal: true, error, reason: error.message }); } } } createSourceBuffers(tracks) { const { sourceBuffer, mediaSource } = this; if (!mediaSource) { throw Error('createSourceBuffers called when mediaSource was null'); } for (const trackName in tracks) { if (!sourceBuffer[trackName]) { var _track$levelCodec; const track = tracks[trackName]; if (!track) { throw Error(`source buffer exists for track ${trackName}, however track does not`); } // use levelCodec as first priority unless it contains multiple comma-separated codec values let codec = ((_track$levelCodec = track.levelCodec) == null ? void 0 : _track$levelCodec.indexOf(',')) === -1 ? track.levelCodec : track.codec; if (codec) { if (trackName.slice(0, 5) === 'audio') { codec = getCodecCompatibleName(codec, this.appendSource); } } const mimeType = `${track.container};codecs=${codec}`; this.log(`creating sourceBuffer(${mimeType})`); try { const sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType); const sbName = trackName; this.addBufferListener(sbName, 'updatestart', this._onSBUpdateStart); this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd); this.addBufferListener(sbName, 'error', this._onSBUpdateError); // ManagedSourceBuffer bufferedchange event if (this.appendSource) { this.addBufferListener(sbName, 'bufferedchange', (type, event) => { // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event. const removedRanges = event.removedRanges; if (removedRanges != null && removedRanges.length) { this.hls.trigger(Events.BUFFER_FLUSHED, { type: trackName }); } }); } this.tracks[trackName] = { buffer: sb, codec: codec, container: track.container, levelCodec: track.levelCodec, metadata: track.metadata, id: track.id }; } catch (err) { this.error(`error while trying to add sourceBuffer: ${err.message}`); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_ADD_CODEC_ERROR, fatal: false, error: err, sourceBufferName: trackName, mimeType: mimeType }); } } } } get mediaSrc() { var _this$media, _this$media$querySele; const media = ((_this$media = this.media) == null ? void 0 : (_this$media$querySele = _this$media.querySelector) == null ? void 0 : _this$media$querySele.call(_this$media, 'source')) || this.media; return media == null ? void 0 : media.src; } _onSBUpdateStart(type) { const { operationQueue } = this; const operation = operationQueue.current(type); operation.onStart(); } _onSBUpdateEnd(type) { var _this$mediaSource2; if (((_this$mediaSource2 = this.mediaSource) == null ? void 0 : _this$mediaSource2.readyState) === 'closed') { this.resetBuffer(type); return; } const { operationQueue } = this; const operation = operationQueue.current(type); operation.onComplete(); operationQueue.shiftAndExecuteNext(type); } _onSBUpdateError(type, event) { var _this$mediaSource3; const error = new Error(`${type} SourceBuffer error. MediaSource readyState: ${(_this$mediaSource3 = this.mediaSource) == null ? void 0 : _this$mediaSource3.readyState}`); this.error(`${error}`, event); // according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error // SourceBuffer errors are not necessarily fatal; if so, the HTMLMediaElement will fire an error event this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_APPENDING_ERROR, sourceBufferName: type, error, fatal: false }); // updateend is always fired after error, so we'll allow that to shift the current operation off of the queue const operation = this.operationQueue.current(type); if (operation) { operation.onError(error); } } // This method must result in an updateend event; if remove is not called, _onSBUpdateEnd must be called manually removeExecutor(type, startOffset, endOffset) { const { media, mediaSource, operationQueue, sourceBuffer } = this; const sb = sourceBuffer[type]; if (!media || !mediaSource || !sb) { this.warn(`Attempting to remove from the ${type} SourceBuffer, but it does not exist`); operationQueue.shiftAndExecuteNext(type); return; } const mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity; const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity; const removeStart = Math.max(0, startOffset); const removeEnd = Math.min(endOffset, mediaDuration, msDuration); if (removeEnd > removeStart && (!sb.ending || sb.ended)) { sb.ended = false; this.log(`Removing [${removeStart},${removeEnd}] from the ${type} SourceBuffer`); sb.remove(removeStart, removeEnd); } else { // Cycle the queue operationQueue.shiftAndExecuteNext(type); } } // This method must result in an updateend event; if append is not called, _onSBUpdateEnd must be called manually appendExecutor(data, type) { const sb = this.sourceBuffer[type]; if (!sb) { if (!this.pendingTracks[type]) { throw new Error(`Attempting to append to the ${type} SourceBuffer, but it does not exist`); } return; } sb.ended = false; sb.appendBuffer(data); } // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue // upon completion, since we already do it here blockBuffers(onUnblocked, buffers = this.getSourceBufferTypes()) { if (!buffers.length) { this.log('Blocking operation requested, but no SourceBuffers exist'); Promise.resolve().then(onUnblocked); return; } const { operationQueue } = this; // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`); const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type)); Promise.all(blockingOperations).then(() => { // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`); onUnblocked(); buffers.forEach(type => { const sb = this.sourceBuffer[type]; // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration) // While this is a workaround, it's probably useful to have around if (!(sb != null && sb.updating)) { operationQueue.shiftAndExecuteNext(type); } }); }); } getSourceBufferTypes() { return Object.keys(this.sourceBuffer); } addBufferListener(type, event, fn) { const buffer = this.sourceBuffer[type]; if (!buffer) { return; } const listener = fn.bind(this, type); this.listeners[type].push({ event, listener }); buffer.addEventListener(event, listener); } removeBufferListeners(type) { const buffer = this.sourceBuffer[type]; if (!buffer) { return; } this.listeners[type].forEach(l => { buffer.removeEventListener(l.event, l.listener); }); } } function removeSourceChildren(node) { const sourceChildren = node.querySelectorAll('source'); [].slice.call(sourceChildren).forEach(source => { node.removeChild(source); }); } function addSource(media, url) { const source = self.document.createElement('source'); source.type = 'video/mp4'; source.src = url; media.appendChild(source); } /** * * This code was ported from the dash.js project at: * https://github.com/Dash-Industry-Forum/dash.js/blob/development/externals/cea608-parser.js * https://github.com/Dash-Industry-Forum/dash.js/commit/8269b26a761e0853bb21d78780ed945144ecdd4d#diff-71bc295a2d6b6b7093a1d3290d53a4b2 * * The original copyright appears below: * * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2015-2016, DASH Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * 2. Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /** * Exceptions from regular ASCII. CodePoints are mapped to UTF-16 codes */ const specialCea608CharsCodes = { 0x2a: 0xe1, // lowercase a, acute accent 0x5c: 0xe9, // lowercase e, acute accent 0x5e: 0xed, // lowercase i, acute accent 0x5f: 0xf3, // lowercase o, acute accent 0x60: 0xfa, // lowercase u, acute accent 0x7b: 0xe7, // lowercase c with cedilla 0x7c: 0xf7, // division symbol 0x7d: 0xd1, // uppercase N tilde 0x7e: 0xf1, // lowercase n tilde 0x7f: 0x2588, // Full block // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES 0x80: 0xae, // Registered symbol (R) 0x81: 0xb0, // degree sign 0x82: 0xbd, // 1/2 symbol 0x83: 0xbf, // Inverted (open) question mark 0x84: 0x2122, // Trademark symbol (TM) 0x85: 0xa2, // Cents symbol 0x86: 0xa3, // Pounds sterling 0x87: 0x266a, // Music 8'th note 0x88: 0xe0, // lowercase a, grave accent 0x89: 0x20, // transparent space (regular) 0x8a: 0xe8, // lowercase e, grave accent 0x8b: 0xe2, // lowercase a, circumflex accent 0x8c: 0xea, // lowercase e, circumflex accent 0x8d: 0xee, // lowercase i, circumflex accent 0x8e: 0xf4, // lowercase o, circumflex accent 0x8f: 0xfb, // lowercase u, circumflex accent // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F 0x90: 0xc1, // capital letter A with acute 0x91: 0xc9, // capital letter E with acute 0x92: 0xd3, // capital letter O with acute 0x93: 0xda, // capital letter U with acute 0x94: 0xdc, // capital letter U with diaresis 0x95: 0xfc, // lowercase letter U with diaeresis 0x96: 0x2018, // opening single quote 0x97: 0xa1, // inverted exclamation mark 0x98: 0x2a, // asterisk 0x99: 0x2019, // closing single quote 0x9a: 0x2501, // box drawings heavy horizontal 0x9b: 0xa9, // copyright sign 0x9c: 0x2120, // Service mark 0x9d: 0x2022, // (round) bullet 0x9e: 0x201c, // Left double quotation mark 0x9f: 0x201d, // Right double quotation mark 0xa0: 0xc0, // uppercase A, grave accent 0xa1: 0xc2, // uppercase A, circumflex 0xa2: 0xc7, // uppercase C with cedilla 0xa3: 0xc8, // uppercase E, grave accent 0xa4: 0xca, // uppercase E, circumflex 0xa5: 0xcb, // capital letter E with diaresis 0xa6: 0xeb, // lowercase letter e with diaresis 0xa7: 0xce, // uppercase I, circumflex 0xa8: 0xcf, // uppercase I, with diaresis 0xa9: 0xef, // lowercase i, with diaresis 0xaa: 0xd4, // uppercase O, circumflex 0xab: 0xd9, // uppercase U, grave accent 0xac: 0xf9, // lowercase u, grave accent 0xad: 0xdb, // uppercase U, circumflex 0xae: 0xab, // left-pointing double angle quotation mark 0xaf: 0xbb, // right-pointing double angle quotation mark // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F 0xb0: 0xc3, // Uppercase A, tilde 0xb1: 0xe3, // Lowercase a, tilde 0xb2: 0xcd, // Uppercase I, acute accent 0xb3: 0xcc, // Uppercase I, grave accent 0xb4: 0xec, // Lowercase i, grave accent 0xb5: 0xd2, // Uppercase O, grave accent 0xb6: 0xf2, // Lowercase o, grave accent 0xb7: 0xd5, // Uppercase O, tilde 0xb8: 0xf5, // Lowercase o, tilde 0xb9: 0x7b, // Open curly brace 0xba: 0x7d, // Closing curly brace 0xbb: 0x5c, // Backslash 0xbc: 0x5e, // Caret 0xbd: 0x5f, // Underscore 0xbe: 0x7c, // Pipe (vertical line) 0xbf: 0x223c, // Tilde operator 0xc0: 0xc4, // Uppercase A, umlaut 0xc1: 0xe4, // Lowercase A, umlaut 0xc2: 0xd6, // Uppercase O, umlaut 0xc3: 0xf6, // Lowercase o, umlaut 0xc4: 0xdf, // Esszett (sharp S) 0xc5: 0xa5, // Yen symbol 0xc6: 0xa4, // Generic currency sign 0xc7: 0x2503, // Box drawings heavy vertical 0xc8: 0xc5, // Uppercase A, ring 0xc9: 0xe5, // Lowercase A, ring 0xca: 0xd8, // Uppercase O, stroke 0xcb: 0xf8, // Lowercase o, strok 0xcc: 0x250f, // Box drawings heavy down and right 0xcd: 0x2513, // Box drawings heavy down and left 0xce: 0x2517, // Box drawings heavy up and right 0xcf: 0x251b // Box drawings heavy up and left }; /** * Utils */ const getCharForByte = byte => String.fromCharCode(specialCea608CharsCodes[byte] || byte); const NR_ROWS = 15; const NR_COLS = 100; // Tables to look up row from PAC data const rowsLowCh1 = { 0x11: 1, 0x12: 3, 0x15: 5, 0x16: 7, 0x17: 9, 0x10: 11, 0x13: 12, 0x14: 14 }; const rowsHighCh1 = { 0x11: 2, 0x12: 4, 0x15: 6, 0x16: 8, 0x17: 10, 0x13: 13, 0x14: 15 }; const rowsLowCh2 = { 0x19: 1, 0x1a: 3, 0x1d: 5, 0x1e: 7, 0x1f: 9, 0x18: 11, 0x1b: 12, 0x1c: 14 }; const rowsHighCh2 = { 0x19: 2, 0x1a: 4, 0x1d: 6, 0x1e: 8, 0x1f: 10, 0x1b: 13, 0x1c: 15 }; const backgroundColors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent']; class CaptionsLogger { constructor() { this.time = null; this.verboseLevel = 0; } log(severity, msg) { if (this.verboseLevel >= severity) { const m = typeof msg === 'function' ? msg() : msg; logger.log(`${this.time} [${severity}] ${m}`); } } } const numArrayToHexArray = function numArrayToHexArray(numArray) { const hexArray = []; for (let j = 0; j < numArray.length; j++) { hexArray.push(numArray[j].toString(16)); } return hexArray; }; class PenState { constructor() { this.foreground = 'white'; this.underline = false; this.italics = false; this.background = 'black'; this.flash = false; } reset() { this.foreground = 'white'; this.underline = false; this.italics = false; this.background = 'black'; this.flash = false; } setStyles(styles) { const attribs = ['foreground', 'underline', 'italics', 'background', 'flash']; for (let i = 0; i < attribs.length; i++) { const style = attribs[i]; if (styles.hasOwnProperty(style)) { this[style] = styles[style]; } } } isDefault() { return this.foreground === 'white' && !this.underline && !this.italics && this.background === 'black' && !this.flash; } equals(other) { return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash; } copy(newPenState) { this.foreground = newPenState.foreground; this.underline = newPenState.underline; this.italics = newPenState.italics; this.background = newPenState.background; this.flash = newPenState.flash; } toString() { return 'color=' + this.foreground + ', underline=' + this.underline + ', italics=' + this.italics + ', background=' + this.background + ', flash=' + this.flash; } } /** * Unicode character with styling and background. * @constructor */ class StyledUnicodeChar { constructor() { this.uchar = ' '; this.penState = new PenState(); } reset() { this.uchar = ' '; this.penState.reset(); } setChar(uchar, newPenState) { this.uchar = uchar; this.penState.copy(newPenState); } setPenState(newPenState) { this.penState.copy(newPenState); } equals(other) { return this.uchar === other.uchar && this.penState.equals(other.penState); } copy(newChar) { this.uchar = newChar.uchar; this.penState.copy(newChar.penState); } isEmpty() { return this.uchar === ' ' && this.penState.isDefault(); } } /** * CEA-608 row consisting of NR_COLS instances of StyledUnicodeChar. * @constructor */ class Row { constructor(logger) { this.chars = []; this.pos = 0; this.currPenState = new PenState(); this.cueStartTime = null; this.logger = void 0; for (let i = 0; i < NR_COLS; i++) { this.chars.push(new StyledUnicodeChar()); } this.logger = logger; } equals(other) { for (let i = 0; i < NR_COLS; i++) { if (!this.chars[i].equals(other.chars[i])) { return false; } } return true; } copy(other) { for (let i = 0; i < NR_COLS; i++) { this.chars[i].copy(other.chars[i]); } } isEmpty() { let empty = true; for (let i = 0; i < NR_COLS; i++) { if (!this.chars[i].isEmpty()) { empty = false; break; } } return empty; } /** * Set the cursor to a valid column. */ setCursor(absPos) { if (this.pos !== absPos) { this.pos = absPos; } if (this.pos < 0) { this.logger.log(3, 'Negative cursor position ' + this.pos); this.pos = 0; } else if (this.pos > NR_COLS) { this.logger.log(3, 'Too large cursor position ' + this.pos); this.pos = NR_COLS; } } /** * Move the cursor relative to current position. */ moveCursor(relPos) { const newPos = this.pos + relPos; if (relPos > 1) { for (let i = this.pos + 1; i < newPos + 1; i++) { this.chars[i].setPenState(this.currPenState); } } this.setCursor(newPos); } /** * Backspace, move one step back and clear character. */ backSpace() { this.moveCursor(-1); this.chars[this.pos].setChar(' ', this.currPenState); } insertChar(byte) { if (byte >= 0x90) { // Extended char this.backSpace(); } const char = getCharForByte(byte); if (this.pos >= NR_COLS) { this.logger.log(0, () => 'Cannot insert ' + byte.toString(16) + ' (' + char + ') at position ' + this.pos + '. Skipping it!'); return; } this.chars[this.pos].setChar(char, this.currPenState); this.moveCursor(1); } clearFromPos(startPos) { let i; for (i = startPos; i < NR_COLS; i++) { this.chars[i].reset(); } } clear() { this.clearFromPos(0); this.pos = 0; this.currPenState.reset(); } clearToEndOfRow() { this.clearFromPos(this.pos); } getTextString() { const chars = []; let empty = true; for (let i = 0; i < NR_COLS; i++) { const char = this.chars[i].uchar; if (char !== ' ') { empty = false; } chars.push(char); } if (empty) { return ''; } else { return chars.join(''); } } setPenStyles(styles) { this.currPenState.setStyles(styles); const currChar = this.chars[this.pos]; currChar.setPenState(this.currPenState); } } /** * Keep a CEA-608 screen of 32x15 styled characters * @constructor */ class CaptionScreen { constructor(logger) { this.rows = []; this.currRow = NR_ROWS - 1; this.nrRollUpRows = null; this.lastOutputScreen = null; this.logger = void 0; for (let i = 0; i < NR_ROWS; i++) { this.rows.push(new Row(logger)); } this.logger = logger; } reset() { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].clear(); } this.currRow = NR_ROWS - 1; } equals(other) { let equal = true; for (let i = 0; i < NR_ROWS; i++) { if (!this.rows[i].equals(other.rows[i])) { equal = false; break; } } return equal; } copy(other) { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].copy(other.rows[i]); } } isEmpty() { let empty = true; for (let i = 0; i < NR_ROWS; i++) { if (!this.rows[i].isEmpty()) { empty = false; break; } } return empty; } backSpace() { const row = this.rows[this.currRow]; row.backSpace(); } clearToEndOfRow() { const row = this.rows[this.currRow]; row.clearToEndOfRow(); } /** * Insert a character (without styling) in the current row. */ insertChar(char) { const row = this.rows[this.currRow]; row.insertChar(char); } setPen(styles) { const row = this.rows[this.currRow]; row.setPenStyles(styles); } moveCursor(relPos) { const row = this.rows[this.currRow]; row.moveCursor(relPos); } setCursor(absPos) { this.logger.log(2, 'setCursor: ' + absPos); const row = this.rows[this.currRow]; row.setCursor(absPos); } setPAC(pacData) { this.logger.log(2, () => 'pacData = ' + JSON.stringify(pacData)); let newRow = pacData.row - 1; if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) { newRow = this.nrRollUpRows - 1; } // Make sure this only affects Roll-up Captions by checking this.nrRollUpRows if (this.nrRollUpRows && this.currRow !== newRow) { // clear all rows first for (let i = 0; i < NR_ROWS; i++) { this.rows[i].clear(); } // Copy this.nrRollUpRows rows from lastOutputScreen and place it in the newRow location // topRowIndex - the start of rows to copy (inclusive index) const topRowIndex = this.currRow + 1 - this.nrRollUpRows; // We only copy if the last position was already shown. // We use the cueStartTime value to check this. const lastOutputScreen = this.lastOutputScreen; if (lastOutputScreen) { const prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime; const time = this.logger.time; if (prevLineTime !== null && time !== null && prevLineTime < time) { for (let i = 0; i < this.nrRollUpRows; i++) { this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]); } } } } this.currRow = newRow; const row = this.rows[this.currRow]; if (pacData.indent !== null) { const indent = pacData.indent; const prevPos = Math.max(indent - 1, 0); row.setCursor(pacData.indent); pacData.color = row.chars[prevPos].penState.foreground; } const styles = { foreground: pacData.color, underline: pacData.underline, italics: pacData.italics, background: 'black', flash: false }; this.setPen(styles); } /** * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility). */ setBkgData(bkgData) { this.logger.log(2, () => 'bkgData = ' + JSON.stringify(bkgData)); this.backSpace(); this.setPen(bkgData); this.insertChar(0x20); // Space } setRollUpRows(nrRows) { this.nrRollUpRows = nrRows; } rollUp() { if (this.nrRollUpRows === null) { this.logger.log(3, 'roll_up but nrRollUpRows not set yet'); return; // Not properly setup } this.logger.log(1, () => this.getDisplayText()); const topRowIndex = this.currRow + 1 - this.nrRollUpRows; const topRow = this.rows.splice(topRowIndex, 1)[0]; topRow.clear(); this.rows.splice(this.currRow, 0, topRow); this.logger.log(2, 'Rolling up'); // this.logger.log(VerboseLevel.TEXT, this.get_display_text()) } /** * Get all non-empty rows with as unicode text. */ getDisplayText(asOneRow) { asOneRow = asOneRow || false; const displayText = []; let text = ''; let rowNr = -1; for (let i = 0; i < NR_ROWS; i++) { const rowText = this.rows[i].getTextString(); if (rowText) { rowNr = i + 1; if (asOneRow) { displayText.push('Row ' + rowNr + ": '" + rowText + "'"); } else { displayText.push(rowText.trim()); } } } if (displayText.length > 0) { if (asOneRow) { text = '[' + displayText.join(' | ') + ']'; } else { text = displayText.join('\n'); } } return text; } getTextAndFormat() { return this.rows; } } // var modes = ['MODE_ROLL-UP', 'MODE_POP-ON', 'MODE_PAINT-ON', 'MODE_TEXT']; class Cea608Channel { constructor(channelNumber, outputFilter, logger) { this.chNr = void 0; this.outputFilter = void 0; this.mode = void 0; this.verbose = void 0; this.displayedMemory = void 0; this.nonDisplayedMemory = void 0; this.lastOutputScreen = void 0; this.currRollUpRow = void 0; this.writeScreen = void 0; this.cueStartTime = void 0; this.logger = void 0; this.chNr = channelNumber; this.outputFilter = outputFilter; this.mode = null; this.verbose = 0; this.displayedMemory = new CaptionScreen(logger); this.nonDisplayedMemory = new CaptionScreen(logger); this.lastOutputScreen = new CaptionScreen(logger); this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; this.writeScreen = this.displayedMemory; this.mode = null; this.cueStartTime = null; // Keeps track of where a cue started. this.logger = logger; } reset() { this.mode = null; this.displayedMemory.reset(); this.nonDisplayedMemory.reset(); this.lastOutputScreen.reset(); this.outputFilter.reset(); this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; this.writeScreen = this.displayedMemory; this.mode = null; this.cueStartTime = null; } getHandler() { return this.outputFilter; } setHandler(newHandler) { this.outputFilter = newHandler; } setPAC(pacData) { this.writeScreen.setPAC(pacData); } setBkgData(bkgData) { this.writeScreen.setBkgData(bkgData); } setMode(newMode) { if (newMode === this.mode) { return; } this.mode = newMode; this.logger.log(2, () => 'MODE=' + newMode); if (this.mode === 'MODE_POP-ON') { this.writeScreen = this.nonDisplayedMemory; } else { this.writeScreen = this.displayedMemory; this.writeScreen.reset(); } if (this.mode !== 'MODE_ROLL-UP') { this.displayedMemory.nrRollUpRows = null; this.nonDisplayedMemory.nrRollUpRows = null; } this.mode = newMode; } insertChars(chars) { for (let i = 0; i < chars.length; i++) { this.writeScreen.insertChar(chars[i]); } const screen = this.writeScreen === this.displayedMemory ? 'DISP' : 'NON_DISP'; this.logger.log(2, () => screen + ': ' + this.writeScreen.getDisplayText(true)); if (this.mode === 'MODE_PAINT-ON' || this.mode === 'MODE_ROLL-UP') { this.logger.log(1, () => 'DISPLAYED: ' + this.displayedMemory.getDisplayText(true)); this.outputDataUpdate(); } } ccRCL() { // Resume Caption Loading (switch mode to Pop On) this.logger.log(2, 'RCL - Resume Caption Loading'); this.setMode('MODE_POP-ON'); } ccBS() { // BackSpace this.logger.log(2, 'BS - BackSpace'); if (this.mode === 'MODE_TEXT') { return; } this.writeScreen.backSpace(); if (this.writeScreen === this.displayedMemory) { this.outputDataUpdate(); } } ccAOF() { // Reserved (formerly Alarm Off) } ccAON() { // Reserved (formerly Alarm On) } ccDER() { // Delete to End of Row this.logger.log(2, 'DER- Delete to End of Row'); this.writeScreen.clearToEndOfRow(); this.outputDataUpdate(); } ccRU(nrRows) { // Roll-Up Captions-2,3,or 4 Rows this.logger.log(2, 'RU(' + nrRows + ') - Roll Up'); this.writeScreen = this.displayedMemory; this.setMode('MODE_ROLL-UP'); this.writeScreen.setRollUpRows(nrRows); } ccFON() { // Flash On this.logger.log(2, 'FON - Flash On'); this.writeScreen.setPen({ flash: true }); } ccRDC() { // Resume Direct Captioning (switch mode to PaintOn) this.logger.log(2, 'RDC - Resume Direct Captioning'); this.setMode('MODE_PAINT-ON'); } ccTR() { // Text Restart in text mode (not supported, however) this.logger.log(2, 'TR'); this.setMode('MODE_TEXT'); } ccRTD() { // Resume Text Display in Text mode (not supported, however) this.logger.log(2, 'RTD'); this.setMode('MODE_TEXT'); } ccEDM() { // Erase Displayed Memory this.logger.log(2, 'EDM - Erase Displayed Memory'); this.displayedMemory.reset(); this.outputDataUpdate(true); } ccCR() { // Carriage Return this.logger.log(2, 'CR - Carriage Return'); this.writeScreen.rollUp(); this.outputDataUpdate(true); } ccENM() { // Erase Non-Displayed Memory this.logger.log(2, 'ENM - Erase Non-displayed Memory'); this.nonDisplayedMemory.reset(); } ccEOC() { // End of Caption (Flip Memories) this.logger.log(2, 'EOC - End Of Caption'); if (this.mode === 'MODE_POP-ON') { const tmp = this.displayedMemory; this.displayedMemory = this.nonDisplayedMemory; this.nonDisplayedMemory = tmp; this.writeScreen = this.nonDisplayedMemory; this.logger.log(1, () => 'DISP: ' + this.displayedMemory.getDisplayText()); } this.outputDataUpdate(true); } ccTO(nrCols) { // Tab Offset 1,2, or 3 columns this.logger.log(2, 'TO(' + nrCols + ') - Tab Offset'); this.writeScreen.moveCursor(nrCols); } ccMIDROW(secondByte) { // Parse MIDROW command const styles = { flash: false }; styles.underline = secondByte % 2 === 1; styles.italics = secondByte >= 0x2e; if (!styles.italics) { const colorIndex = Math.floor(secondByte / 2) - 0x10; const colors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta']; styles.foreground = colors[colorIndex]; } else { styles.foreground = 'white'; } this.logger.log(2, 'MIDROW: ' + JSON.stringify(styles)); this.writeScreen.setPen(styles); } outputDataUpdate(dispatch = false) { const time = this.logger.time; if (time === null) { return; } if (this.outputFilter) { if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) { // Start of a new cue this.cueStartTime = time; } else { if (!this.displayedMemory.equals(this.lastOutputScreen)) { this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen); if (dispatch && this.outputFilter.dispatchCue) { this.outputFilter.dispatchCue(); } this.cueStartTime = this.displayedMemory.isEmpty() ? null : time; } } this.lastOutputScreen.copy(this.displayedMemory); } } cueSplitAtTime(t) { if (this.outputFilter) { if (!this.displayedMemory.isEmpty()) { if (this.outputFilter.newCue) { this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory); } this.cueStartTime = t; } } } } // Will be 1 or 2 when parsing captions class Cea608Parser { constructor(field, out1, out2) { this.channels = void 0; this.currentChannel = 0; this.cmdHistory = createCmdHistory(); this.logger = void 0; const logger = this.logger = new CaptionsLogger(); this.channels = [null, new Cea608Channel(field, out1, logger), new Cea608Channel(field + 1, out2, logger)]; } getHandler(channel) { return this.channels[channel].getHandler(); } setHandler(channel, newHandler) { this.channels[channel].setHandler(newHandler); } /** * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs. */ addData(time, byteList) { this.logger.time = time; for (let i = 0; i < byteList.length; i += 2) { const a = byteList[i] & 0x7f; const b = byteList[i + 1] & 0x7f; let cmdFound = false; let charsFound = null; if (a === 0 && b === 0) { continue; } else { this.logger.log(3, () => '[' + numArrayToHexArray([byteList[i], byteList[i + 1]]) + '] -> (' + numArrayToHexArray([a, b]) + ')'); } const cmdHistory = this.cmdHistory; const isControlCode = a >= 0x10 && a <= 0x1f; if (isControlCode) { // Skip redundant control codes if (hasCmdRepeated(a, b, cmdHistory)) { setLastCmd(null, null, cmdHistory); this.logger.log(3, () => 'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped'); continue; } setLastCmd(a, b, this.cmdHistory); cmdFound = this.parseCmd(a, b); if (!cmdFound) { cmdFound = this.parseMidrow(a, b); } if (!cmdFound) { cmdFound = this.parsePAC(a, b); } if (!cmdFound) { cmdFound = this.parseBackgroundAttributes(a, b); } } else { setLastCmd(null, null, cmdHistory); } if (!cmdFound) { charsFound = this.parseChars(a, b); if (charsFound) { const currChNr = this.currentChannel; if (currChNr && currChNr > 0) { const channel = this.channels[currChNr]; channel.insertChars(charsFound); } else { this.logger.log(2, 'No channel found yet. TEXT-MODE?'); } } } if (!cmdFound && !charsFound) { this.logger.log(2, () => "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + ' orig: ' + numArrayToHexArray([byteList[i], byteList[i + 1]])); } } } /** * Parse Command. * @returns True if a command was found */ parseCmd(a, b) { const cond1 = (a === 0x14 || a === 0x1c || a === 0x15 || a === 0x1d) && b >= 0x20 && b <= 0x2f; const cond2 = (a === 0x17 || a === 0x1f) && b >= 0x21 && b <= 0x23; if (!(cond1 || cond2)) { return false; } const chNr = a === 0x14 || a === 0x15 || a === 0x17 ? 1 : 2; const channel = this.channels[chNr]; if (a === 0x14 || a === 0x15 || a === 0x1c || a === 0x1d) { if (b === 0x20) { channel.ccRCL(); } else if (b === 0x21) { channel.ccBS(); } else if (b === 0x22) { channel.ccAOF(); } else if (b === 0x23) { channel.ccAON(); } else if (b === 0x24) { channel.ccDER(); } else if (b === 0x25) { channel.ccRU(2); } else if (b === 0x26) { channel.ccRU(3); } else if (b === 0x27) { channel.ccRU(4); } else if (b === 0x28) { channel.ccFON(); } else if (b === 0x29) { channel.ccRDC(); } else if (b === 0x2a) { channel.ccTR(); } else if (b === 0x2b) { channel.ccRTD(); } else if (b === 0x2c) { channel.ccEDM(); } else if (b === 0x2d) { channel.ccCR(); } else if (b === 0x2e) { channel.ccENM(); } else if (b === 0x2f) { channel.ccEOC(); } } else { // a == 0x17 || a == 0x1F channel.ccTO(b - 0x20); } this.currentChannel = chNr; return true; } /** * Parse midrow styling command */ parseMidrow(a, b) { let chNr = 0; if ((a === 0x11 || a === 0x19) && b >= 0x20 && b <= 0x2f) { if (a === 0x11) { chNr = 1; } else { chNr = 2; } if (chNr !== this.currentChannel) { this.logger.log(0, 'Mismatch channel in midrow parsing'); return false; } const channel = this.channels[chNr]; if (!channel) { return false; } channel.ccMIDROW(b); this.logger.log(3, () => 'MIDROW (' + numArrayToHexArray([a, b]) + ')'); return true; } return false; } /** * Parse Preable Access Codes (Table 53). * @returns {Boolean} Tells if PAC found */ parsePAC(a, b) { let row; const case1 = (a >= 0x11 && a <= 0x17 || a >= 0x19 && a <= 0x1f) && b >= 0x40 && b <= 0x7f; const case2 = (a === 0x10 || a === 0x18) && b >= 0x40 && b <= 0x5f; if (!(case1 || case2)) { return false; } const chNr = a <= 0x17 ? 1 : 2; if (b >= 0x40 && b <= 0x5f) { row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a]; } else { // 0x60 <= b <= 0x7F row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a]; } const channel = this.channels[chNr]; if (!channel) { return false; } channel.setPAC(this.interpretPAC(row, b)); this.currentChannel = chNr; return true; } /** * Interpret the second byte of the pac, and return the information. * @returns pacData with style parameters */ interpretPAC(row, byte) { let pacIndex; const pacData = { color: null, italics: false, indent: null, underline: false, row: row }; if (byte > 0x5f) { pacIndex = byte - 0x60; } else { pacIndex = byte - 0x40; } pacData.underline = (pacIndex & 1) === 1; if (pacIndex <= 0xd) { pacData.color = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)]; } else if (pacIndex <= 0xf) { pacData.italics = true; pacData.color = 'white'; } else { pacData.indent = Math.floor((pacIndex - 0x10) / 2) * 4; } return pacData; // Note that row has zero offset. The spec uses 1. } /** * Parse characters. * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise. */ parseChars(a, b) { let channelNr; let charCodes = null; let charCode1 = null; if (a >= 0x19) { channelNr = 2; charCode1 = a - 8; } else { channelNr = 1; charCode1 = a; } if (charCode1 >= 0x11 && charCode1 <= 0x13) { // Special character let oneCode; if (charCode1 === 0x11) { oneCode = b + 0x50; } else if (charCode1 === 0x12) { oneCode = b + 0x70; } else { oneCode = b + 0x90; } this.logger.log(2, () => "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr); charCodes = [oneCode]; } else if (a >= 0x20 && a <= 0x7f) { charCodes = b === 0 ? [a] : [a, b]; } if (charCodes) { this.logger.log(3, () => 'Char codes = ' + numArrayToHexArray(charCodes).join(',')); } return charCodes; } /** * Parse extended background attributes as well as new foreground color black. * @returns True if background attributes are found */ parseBackgroundAttributes(a, b) { const case1 = (a === 0x10 || a === 0x18) && b >= 0x20 && b <= 0x2f; const case2 = (a === 0x17 || a === 0x1f) && b >= 0x2d && b <= 0x2f; if (!(case1 || case2)) { return false; } let index; const bkgData = {}; if (a === 0x10 || a === 0x18) { index = Math.floor((b - 0x20) / 2); bkgData.background = backgroundColors[index]; if (b % 2 === 1) { bkgData.background = bkgData.background + '_semi'; } } else if (b === 0x2d) { bkgData.background = 'transparent'; } else { bkgData.foreground = 'black'; if (b === 0x2f) { bkgData.underline = true; } } const chNr = a <= 0x17 ? 1 : 2; const channel = this.channels[chNr]; channel.setBkgData(bkgData); return true; } /** * Reset state of parser and its channels. */ reset() { for (let i = 0; i < Object.keys(this.channels).length; i++) { const channel = this.channels[i]; if (channel) { channel.reset(); } } setLastCmd(null, null, this.cmdHistory); } /** * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty. */ cueSplitAtTime(t) { for (let i = 0; i < this.channels.length; i++) { const channel = this.channels[i]; if (channel) { channel.cueSplitAtTime(t); } } } } function setLastCmd(a, b, cmdHistory) { cmdHistory.a = a; cmdHistory.b = b; } function hasCmdRepeated(a, b, cmdHistory) { return cmdHistory.a === a && cmdHistory.b === b; } function createCmdHistory() { return { a: null, b: null }; } class OutputFilter { constructor(timelineController, trackName) { this.timelineController = void 0; this.cueRanges = []; this.trackName = void 0; this.startTime = null; this.endTime = null; this.screen = null; this.timelineController = timelineController; this.trackName = trackName; } dispatchCue() { if (this.startTime === null) { return; } this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges); this.startTime = null; } newCue(startTime, endTime, screen) { if (this.startTime === null || this.startTime > startTime) { this.startTime = startTime; } this.endTime = endTime; this.screen = screen; this.timelineController.createCaptionsTrack(this.trackName); } reset() { this.cueRanges = []; this.startTime = null; } } /** * Copyright 2013 vtt.js Contributors * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var VTTCue = (function () { if (optionalSelf != null && optionalSelf.VTTCue) { return self.VTTCue; } const AllowedDirections = ['', 'lr', 'rl']; const AllowedAlignments = ['start', 'middle', 'end', 'left', 'right']; function isAllowedValue(allowed, value) { if (typeof value !== 'string') { return false; } // necessary for assuring the generic conforms to the Array interface if (!Array.isArray(allowed)) { return false; } // reset the type so that the next narrowing works well const lcValue = value.toLowerCase(); // use the allow list to narrow the type to a specific subset of strings if (~allowed.indexOf(lcValue)) { return lcValue; } return false; } function findDirectionSetting(value) { return isAllowedValue(AllowedDirections, value); } function findAlignSetting(value) { return isAllowedValue(AllowedAlignments, value); } function extend(obj, ...rest) { let i = 1; for (; i < arguments.length; i++) { const cobj = arguments[i]; for (const p in cobj) { obj[p] = cobj[p]; } } return obj; } function VTTCue(startTime, endTime, text) { const cue = this; const baseObj = { enumerable: true }; /** * Shim implementation specific properties. These properties are not in * the spec. */ // Lets us know when the VTTCue's data has changed in such a way that we need // to recompute its display state. This lets us compute its display state // lazily. cue.hasBeenReset = false; /** * VTTCue and TextTrackCue properties * http://dev.w3.org/html5/webvtt/#vttcue-interface */ let _id = ''; let _pauseOnExit = false; let _startTime = startTime; let _endTime = endTime; let _text = text; let _region = null; let _vertical = ''; let _snapToLines = true; let _line = 'auto'; let _lineAlign = 'start'; let _position = 50; let _positionAlign = 'middle'; let _size = 50; let _align = 'middle'; Object.defineProperty(cue, 'id', extend({}, baseObj, { get: function () { return _id; }, set: function (value) { _id = '' + value; } })); Object.defineProperty(cue, 'pauseOnExit', extend({}, baseObj, { get: function () { return _pauseOnExit; }, set: function (value) { _pauseOnExit = !!value; } })); Object.defineProperty(cue, 'startTime', extend({}, baseObj, { get: function () { return _startTime; }, set: function (value) { if (typeof value !== 'number') { throw new TypeError('Start time must be set to a number.'); } _startTime = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'endTime', extend({}, baseObj, { get: function () { return _endTime; }, set: function (value) { if (typeof value !== 'number') { throw new TypeError('End time must be set to a number.'); } _endTime = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'text', extend({}, baseObj, { get: function () { return _text; }, set: function (value) { _text = '' + value; this.hasBeenReset = true; } })); // todo: implement VTTRegion polyfill? Object.defineProperty(cue, 'region', extend({}, baseObj, { get: function () { return _region; }, set: function (value) { _region = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'vertical', extend({}, baseObj, { get: function () { return _vertical; }, set: function (value) { const setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. if (setting === false) { throw new SyntaxError('An invalid or illegal string was specified.'); } _vertical = setting; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'snapToLines', extend({}, baseObj, { get: function () { return _snapToLines; }, set: function (value) { _snapToLines = !!value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'line', extend({}, baseObj, { get: function () { return _line; }, set: function (value) { if (typeof value !== 'number' && value !== 'auto') { throw new SyntaxError('An invalid number or illegal string was specified.'); } _line = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'lineAlign', extend({}, baseObj, { get: function () { return _lineAlign; }, set: function (value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError('An invalid or illegal string was specified.'); } _lineAlign = setting; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'position', extend({}, baseObj, { get: function () { return _position; }, set: function (value) { if (value < 0 || value > 100) { throw new Error('Position must be between 0 and 100.'); } _position = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'positionAlign', extend({}, baseObj, { get: function () { return _positionAlign; }, set: function (value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError('An invalid or illegal string was specified.'); } _positionAlign = setting; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'size', extend({}, baseObj, { get: function () { return _size; }, set: function (value) { if (value < 0 || value > 100) { throw new Error('Size must be between 0 and 100.'); } _size = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, 'align', extend({}, baseObj, { get: function () { return _align; }, set: function (value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError('An invalid or illegal string was specified.'); } _align = setting; this.hasBeenReset = true; } })); /** * Other <track> spec defined properties */ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state cue.displayState = undefined; } /** * VTTCue methods */ VTTCue.prototype.getCueAsHTML = function () { // Assume WebVTT.convertCueToDOMTree is on the global. const WebVTT = self.WebVTT; return WebVTT.convertCueToDOMTree(self, this.text); }; // this is a polyfill hack return VTTCue; })(); /* * Source: https://github.com/mozilla/vtt.js/blob/master/dist/vtt.js */ class StringDecoder { // eslint-disable-next-line @typescript-eslint/no-unused-vars decode(data, options) { if (!data) { return ''; } if (typeof data !== 'string') { throw new Error('Error - expected string data.'); } return decodeURIComponent(encodeURIComponent(data)); } } // Try to parse input as a time stamp. function parseTimeStamp(input) { function computeSeconds(h, m, s, f) { return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + parseFloat(f || 0); } const m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/); if (!m) { return null; } if (parseFloat(m[2]) > 59) { // Timestamp takes the form of [hours]:[minutes].[milliseconds] // First position is hours as it's over 59. return computeSeconds(m[2], m[3], 0, m[4]); } // Timestamp takes the form of [hours (optional)]:[minutes]:[seconds].[milliseconds] return computeSeconds(m[1], m[2], m[3], m[4]); } // A settings object holds key/value pairs and will ignore anything but the first // assignment to a specific key. class Settings { constructor() { this.values = Object.create(null); } // Only accept the first assignment to any key. set(k, v) { if (!this.get(k) && v !== '') { this.values[k] = v; } } // Return the value for a key, or a default value. // If 'defaultKey' is passed then 'dflt' is assumed to be an object with // a number of possible default values as properties where 'defaultKey' is // the key of the property that will be chosen; otherwise it's assumed to be // a single value. get(k, dflt, defaultKey) { if (defaultKey) { return this.has(k) ? this.values[k] : dflt[defaultKey]; } return this.has(k) ? this.values[k] : dflt; } // Check whether we have a value for a key. has(k) { return k in this.values; } // Accept a setting if its one of the given alternatives. alt(k, v, a) { for (let n = 0; n < a.length; ++n) { if (v === a[n]) { this.set(k, v); break; } } } // Accept a setting if its a valid (signed) integer. integer(k, v) { if (/^-?\d+$/.test(v)) { // integer this.set(k, parseInt(v, 10)); } } // Accept a setting if its a valid percentage. percent(k, v) { if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) { const percent = parseFloat(v); if (percent >= 0 && percent <= 100) { this.set(k, percent); return true; } } return false; } } // Helper function to parse input into groups separated by 'groupDelim', and // interpret each group as a key/value pair separated by 'keyValueDelim'. function parseOptions(input, callback, keyValueDelim, groupDelim) { const groups = groupDelim ? input.split(groupDelim) : [input]; for (const i in groups) { if (typeof groups[i] !== 'string') { continue; } const kv = groups[i].split(keyValueDelim); if (kv.length !== 2) { continue; } const k = kv[0]; const v = kv[1]; callback(k, v); } } const defaults = new VTTCue(0, 0, ''); // 'middle' was changed to 'center' in the spec: https://github.com/w3c/webvtt/pull/244 // Safari doesn't yet support this change, but FF and Chrome do. const center = defaults.align === 'middle' ? 'middle' : 'center'; function parseCue(input, cue, regionList) { // Remember the original input if we need to throw an error. const oInput = input; // 4.1 WebVTT timestamp function consumeTimeStamp() { const ts = parseTimeStamp(input); if (ts === null) { throw new Error('Malformed timestamp: ' + oInput); } // Remove time stamp from input. input = input.replace(/^[^\sa-zA-Z-]+/, ''); return ts; } // 4.4.2 WebVTT cue settings function consumeCueSettings(input, cue) { const settings = new Settings(); parseOptions(input, function (k, v) { let vals; switch (k) { case 'region': // Find the last region we parsed with the same region id. for (let i = regionList.length - 1; i >= 0; i--) { if (regionList[i].id === v) { settings.set(k, regionList[i].region); break; } } break; case 'vertical': settings.alt(k, v, ['rl', 'lr']); break; case 'line': vals = v.split(','); settings.integer(k, vals[0]); if (settings.percent(k, vals[0])) { settings.set('snapToLines', false); } settings.alt(k, vals[0], ['auto']); if (vals.length === 2) { settings.alt('lineAlign', vals[1], ['start', center, 'end']); } break; case 'position': vals = v.split(','); settings.percent(k, vals[0]); if (vals.length === 2) { settings.alt('positionAlign', vals[1], ['start', center, 'end', 'line-left', 'line-right', 'auto']); } break; case 'size': settings.percent(k, v); break; case 'align': settings.alt(k, v, ['start', center, 'end', 'left', 'right']); break; } }, /:/, /\s/); // Apply default values for any missing fields. cue.region = settings.get('region', null); cue.vertical = settings.get('vertical', ''); let line = settings.get('line', 'auto'); if (line === 'auto' && defaults.line === -1) { // set numeric line number for Safari line = -1; } cue.line = line; cue.lineAlign = settings.get('lineAlign', 'start'); cue.snapToLines = settings.get('snapToLines', true); cue.size = settings.get('size', 100); cue.align = settings.get('align', center); let position = settings.get('position', 'auto'); if (position === 'auto' && defaults.position === 50) { // set numeric position for Safari position = cue.align === 'start' || cue.align === 'left' ? 0 : cue.align === 'end' || cue.align === 'right' ? 100 : 50; } cue.position = position; } function skipWhitespace() { input = input.replace(/^\s+/, ''); } // 4.1 WebVTT cue timings. skipWhitespace(); cue.startTime = consumeTimeStamp(); // (1) collect cue start time skipWhitespace(); if (input.slice(0, 3) !== '-->') { // (3) next characters must match '-->' throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput); } input = input.slice(3); skipWhitespace(); cue.endTime = consumeTimeStamp(); // (5) collect cue end time // 4.1 WebVTT cue settings list. skipWhitespace(); consumeCueSettings(input, cue); } function fixLineBreaks(input) { return input.replace(/<br(?: \/)?>/gi, '\n'); } class VTTParser { constructor() { this.state = 'INITIAL'; this.buffer = ''; this.decoder = new StringDecoder(); this.regionList = []; this.cue = null; this.oncue = void 0; this.onparsingerror = void 0; this.onflush = void 0; } parse(data) { const _this = this; // If there is no data then we won't decode it, but will just try to parse // whatever is in buffer already. This may occur in circumstances, for // example when flush() is called. if (data) { // Try to decode the data that we received. _this.buffer += _this.decoder.decode(data, { stream: true }); } function collectNextLine() { let buffer = _this.buffer; let pos = 0; buffer = fixLineBreaks(buffer); while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { ++pos; } const line = buffer.slice(0, pos); // Advance the buffer early in case we fail below. if (buffer[pos] === '\r') { ++pos; } if (buffer[pos] === '\n') { ++pos; } _this.buffer = buffer.slice(pos); return line; } // 3.2 WebVTT metadata header syntax function parseHeader(input) { parseOptions(input, function (k, v) { // switch (k) { // case 'region': // 3.3 WebVTT region metadata header syntax // console.log('parse region', v); // parseRegion(v); // break; // } }, /:/); } // 5.1 WebVTT file parsing. try { let line = ''; if (_this.state === 'INITIAL') { // We can't start parsing until we have the first line. if (!/\r\n|\n/.test(_this.buffer)) { return this; } line = collectNextLine(); // strip of UTF-8 BOM if any // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 const m = line.match(/^()?WEBVTT([ \t].*)?$/); if (!(m != null && m[0])) { throw new Error('Malformed WebVTT signature.'); } _this.state = 'HEADER'; } let alreadyCollectedLine = false; while (_this.buffer) { // We can't parse a line until we have the full line. if (!/\r\n|\n/.test(_this.buffer)) { return this; } if (!alreadyCollectedLine) { line = collectNextLine(); } else { alreadyCollectedLine = false; } switch (_this.state) { case 'HEADER': // 13-18 - Allow a header (metadata) under the WEBVTT line. if (/:/.test(line)) { parseHeader(line); } else if (!line) { // An empty line terminates the header and starts the body (cues). _this.state = 'ID'; } continue; case 'NOTE': // Ignore NOTE blocks. if (!line) { _this.state = 'ID'; } continue; case 'ID': // Check for the start of NOTE blocks. if (/^NOTE($|[ \t])/.test(line)) { _this.state = 'NOTE'; break; } // 19-29 - Allow any number of line terminators, then initialize new cue values. if (!line) { continue; } _this.cue = new VTTCue(0, 0, ''); _this.state = 'CUE'; // 30-39 - Check if self line contains an optional identifier or timing data. if (line.indexOf('-->') === -1) { _this.cue.id = line; continue; } // Process line as start of a cue. /* falls through */ case 'CUE': // 40 - Collect cue timings and settings. if (!_this.cue) { _this.state = 'BADCUE'; continue; } try { parseCue(line, _this.cue, _this.regionList); } catch (e) { // In case of an error ignore rest of the cue. _this.cue = null; _this.state = 'BADCUE'; continue; } _this.state = 'CUETEXT'; continue; case 'CUETEXT': { const hasSubstring = line.indexOf('-->') !== -1; // 34 - If we have an empty line then report the cue. // 35 - If we have the special substring '-->' then report the cue, // but do not collect the line as we need to process the current // one as a new cue. if (!line || hasSubstring && (alreadyCollectedLine = true)) { // We are done parsing self cue. if (_this.oncue && _this.cue) { _this.oncue(_this.cue); } _this.cue = null; _this.state = 'ID'; continue; } if (_this.cue === null) { continue; } if (_this.cue.text) { _this.cue.text += '\n'; } _this.cue.text += line; } continue; case 'BADCUE': // 54-62 - Collect and discard the remaining cue. if (!line) { _this.state = 'ID'; } } } } catch (e) { // If we are currently parsing a cue, report what we have. if (_this.state === 'CUETEXT' && _this.cue && _this.oncue) { _this.oncue(_this.cue); } _this.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise // another exception occurred so enter BADCUE state. _this.state = _this.state === 'INITIAL' ? 'BADWEBVTT' : 'BADCUE'; } return this; } flush() { const _this = this; try { // Finish decoding the stream. // _this.buffer += _this.decoder.decode(); // Synthesize the end of the current cue or region. if (_this.cue || _this.state === 'HEADER') { _this.buffer += '\n\n'; _this.parse(); } // If we've flushed, parsed, and we're still on the INITIAL state then // that means we don't have enough of the stream to parse the first // line. if (_this.state === 'INITIAL' || _this.state === 'BADWEBVTT') { throw new Error('Malformed WebVTT signature.'); } } catch (e) { if (_this.onparsingerror) { _this.onparsingerror(e); } } if (_this.onflush) { _this.onflush(); } return this; } } const LINEBREAKS = /\r\n|\n\r|\n|\r/g; // String.prototype.startsWith is not supported in IE11 const startsWith = function startsWith(inputString, searchString, position = 0) { return inputString.slice(position, position + searchString.length) === searchString; }; const cueString2millis = function cueString2millis(timeString) { let ts = parseInt(timeString.slice(-3)); const secs = parseInt(timeString.slice(-6, -4)); const mins = parseInt(timeString.slice(-9, -7)); const hours = timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(':'))) : 0; if (!isFiniteNumber(ts) || !isFiniteNumber(secs) || !isFiniteNumber(mins) || !isFiniteNumber(hours)) { throw Error(`Malformed X-TIMESTAMP-MAP: Local:${timeString}`); } ts += 1000 * secs; ts += 60 * 1000 * mins; ts += 60 * 60 * 1000 * hours; return ts; }; // From https://github.com/darkskyapp/string-hash const hash = function hash(text) { let _hash = 5381; let i = text.length; while (i) { _hash = _hash * 33 ^ text.charCodeAt(--i); } return (_hash >>> 0).toString(); }; // Create a unique hash id for a cue based on start/end times and text. // This helps timeline-controller to avoid showing repeated captions. function generateCueId(startTime, endTime, text) { return hash(startTime.toString()) + hash(endTime.toString()) + hash(text); } const calculateOffset = function calculateOffset(vttCCs, cc, presentationTime) { let currCC = vttCCs[cc]; let prevCC = vttCCs[currCC.prevCC]; // This is the first discontinuity or cues have been processed since the last discontinuity // Offset = current discontinuity time if (!prevCC || !prevCC.new && currCC.new) { vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start; currCC.new = false; return; } // There have been discontinuities since cues were last parsed. // Offset = time elapsed while ((_prevCC = prevCC) != null && _prevCC.new) { var _prevCC; vttCCs.ccOffset += currCC.start - prevCC.start; currCC.new = false; currCC = prevCC; prevCC = vttCCs[currCC.prevCC]; } vttCCs.presentationOffset = presentationTime; }; function parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack) { const parser = new VTTParser(); // Convert byteArray into string, replacing any somewhat exotic linefeeds with "\n", then split on that character. // Uint8Array.prototype.reduce is not implemented in IE11 const vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, '\n').split('\n'); const cues = []; const init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0; let cueTime = '00:00.000'; let timestampMapMPEGTS = 0; let timestampMapLOCAL = 0; let parsingError; let inHeader = true; parser.oncue = function (cue) { // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline. const currCC = vttCCs[cc]; let cueOffset = vttCCs.ccOffset; // Calculate subtitle PTS offset const webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 90000; // Update offsets for new discontinuities if (currCC != null && currCC.new) { if (timestampMapLOCAL !== undefined) { // When local time is provided, offset = discontinuity start time - local time cueOffset = vttCCs.ccOffset = currCC.start; } else { calculateOffset(vttCCs, cc, webVttMpegTsMapOffset); } } if (webVttMpegTsMapOffset) { if (!initPTS) { parsingError = new Error('Missing initPTS for VTT MPEGTS'); return; } // If we have MPEGTS, offset = presentation time + discontinuity offset cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset; } const duration = cue.endTime - cue.startTime; const startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 90000, timeOffset * 90000) / 90000; cue.startTime = Math.max(startTime, 0); cue.endTime = Math.max(startTime + duration, 0); //trim trailing webvtt block whitespaces const text = cue.text.trim(); // Fix encoding of special characters cue.text = decodeURIComponent(encodeURIComponent(text)); // If the cue was not assigned an id from the VTT file (line above the content), create one. if (!cue.id) { cue.id = generateCueId(cue.startTime, cue.endTime, text); } if (cue.endTime > 0) { cues.push(cue); } }; parser.onparsingerror = function (error) { parsingError = error; }; parser.onflush = function () { if (parsingError) { errorCallBack(parsingError); return; } callBack(cues); }; // Go through contents line by line. vttLines.forEach(line => { if (inHeader) { // Look for X-TIMESTAMP-MAP in header. if (startsWith(line, 'X-TIMESTAMP-MAP=')) { // Once found, no more are allowed anyway, so stop searching. inHeader = false; // Extract LOCAL and MPEGTS. line.slice(16).split(',').forEach(timestamp => { if (startsWith(timestamp, 'LOCAL:')) { cueTime = timestamp.slice(6); } else if (startsWith(timestamp, 'MPEGTS:')) { timestampMapMPEGTS = parseInt(timestamp.slice(7)); } }); try { // Convert cue time to seconds timestampMapLOCAL = cueString2millis(cueTime) / 1000; } catch (error) { parsingError = error; } // Return without parsing X-TIMESTAMP-MAP line. return; } else if (line === '') { inHeader = false; } } // Parse line by default. parser.parse(line + '\n'); }); parser.flush(); } const IMSC1_CODEC = 'stpp.ttml.im1t'; // Time format: h:m:s:frames(.subframes) const HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/; // Time format: hours, minutes, seconds, milliseconds, frames, ticks const TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/; const textAlignToLineAlign = { left: 'start', center: 'center', right: 'end', start: 'start', end: 'end' }; function parseIMSC1(payload, initPTS, callBack, errorCallBack) { const results = findBox(new Uint8Array(payload), ['mdat']); if (results.length === 0) { errorCallBack(new Error('Could not parse IMSC1 mdat')); return; } const ttmlList = results.map(mdat => utf8ArrayToStr(mdat)); const syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale); try { ttmlList.forEach(ttml => callBack(parseTTML(ttml, syncTime))); } catch (error) { errorCallBack(error); } } function parseTTML(ttml, syncTime) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(ttml, 'text/xml'); const tt = xmlDoc.getElementsByTagName('tt')[0]; if (!tt) { throw new Error('Invalid ttml'); } const defaultRateInfo = { frameRate: 30, subFrameRate: 1, frameRateMultiplier: 0, tickRate: 0 }; const rateInfo = Object.keys(defaultRateInfo).reduce((result, key) => { result[key] = tt.getAttribute(`ttp:${key}`) || defaultRateInfo[key]; return result; }, {}); const trim = tt.getAttribute('xml:space') !== 'preserve'; const styleElements = collectionToDictionary(getElementCollection(tt, 'styling', 'style')); const regionElements = collectionToDictionary(getElementCollection(tt, 'layout', 'region')); const cueElements = getElementCollection(tt, 'body', '[begin]'); return [].map.call(cueElements, cueElement => { const cueText = getTextContent(cueElement, trim); if (!cueText || !cueElement.hasAttribute('begin')) { return null; } const startTime = parseTtmlTime(cueElement.getAttribute('begin'), rateInfo); const duration = parseTtmlTime(cueElement.getAttribute('dur'), rateInfo); let endTime = parseTtmlTime(cueElement.getAttribute('end'), rateInfo); if (startTime === null) { throw timestampParsingError(cueElement); } if (endTime === null) { if (duration === null) { throw timestampParsingError(cueElement); } endTime = startTime + duration; } const cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText); cue.id = generateCueId(cue.startTime, cue.endTime, cue.text); const region = regionElements[cueElement.getAttribute('region')]; const style = styleElements[cueElement.getAttribute('style')]; // Apply styles to cue const styles = getTtmlStyles(region, style, styleElements); const { textAlign } = styles; if (textAlign) { // cue.positionAlign not settable in FF~2016 const lineAlign = textAlignToLineAlign[textAlign]; if (lineAlign) { cue.lineAlign = lineAlign; } cue.align = textAlign; } _extends(cue, styles); return cue; }).filter(cue => cue !== null); } function getElementCollection(fromElement, parentName, childName) { const parent = fromElement.getElementsByTagName(parentName)[0]; if (parent) { return [].slice.call(parent.querySelectorAll(childName)); } return []; } function collectionToDictionary(elementsWithId) { return elementsWithId.reduce((dict, element) => { const id = element.getAttribute('xml:id'); if (id) { dict[id] = element; } return dict; }, {}); } function getTextContent(element, trim) { return [].slice.call(element.childNodes).reduce((str, node, i) => { var _node$childNodes; if (node.nodeName === 'br' && i) { return str + '\n'; } if ((_node$childNodes = node.childNodes) != null && _node$childNodes.length) { return getTextContent(node, trim); } else if (trim) { return str + node.textContent.trim().replace(/\s+/g, ' '); } return str + node.textContent; }, ''); } function getTtmlStyles(region, style, styleElements) { const ttsNs = 'http://www.w3.org/ns/ttml#styling'; let regionStyle = null; const styleAttributes = ['displayAlign', 'textAlign', 'color', 'backgroundColor', 'fontSize', 'fontFamily' // 'fontWeight', // 'lineHeight', // 'wrapOption', // 'fontStyle', // 'direction', // 'writingMode' ]; const regionStyleName = region != null && region.hasAttribute('style') ? region.getAttribute('style') : null; if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) { regionStyle = styleElements[regionStyleName]; } return styleAttributes.reduce((styles, name) => { const value = getAttributeNS(style, ttsNs, name) || getAttributeNS(region, ttsNs, name) || getAttributeNS(regionStyle, ttsNs, name); if (value) { styles[name] = value; } return styles; }, {}); } function getAttributeNS(element, ns, name) { if (!element) { return null; } return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name) : null; } function timestampParsingError(node) { return new Error(`Could not parse ttml timestamp ${node}`); } function parseTtmlTime(timeAttributeValue, rateInfo) { if (!timeAttributeValue) { return null; } let seconds = parseTimeStamp(timeAttributeValue); if (seconds === null) { if (HMSF_REGEX.test(timeAttributeValue)) { seconds = parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo); } else if (TIME_UNIT_REGEX.test(timeAttributeValue)) { seconds = parseTimeUnits(timeAttributeValue, rateInfo); } } return seconds; } function parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo) { const m = HMSF_REGEX.exec(timeAttributeValue); const frames = (m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate; return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate; } function parseTimeUnits(timeAttributeValue, rateInfo) { const m = TIME_UNIT_REGEX.exec(timeAttributeValue); const value = Number(m[1]); const unit = m[2]; switch (unit) { case 'h': return value * 3600; case 'm': return value * 60; case 'ms': return value * 1000; case 'f': return value / rateInfo.frameRate; case 't': return value / rateInfo.tickRate; } return value; } class TimelineController { constructor(hls) { this.hls = void 0; this.media = null; this.config = void 0; this.enabled = true; this.Cues = void 0; this.textTracks = []; this.tracks = []; this.initPTS = []; this.unparsedVttFrags = []; this.captionsTracks = {}; this.nonNativeCaptionsTracks = {}; this.cea608Parser1 = void 0; this.cea608Parser2 = void 0; this.lastCc = -1; // Last video (CEA-608) fragment CC this.lastSn = -1; // Last video (CEA-608) fragment MSN this.lastPartIndex = -1; // Last video (CEA-608) fragment Part Index this.prevCC = -1; // Last subtitle fragment CC this.vttCCs = newVTTCCs(); this.captionsProperties = void 0; this.hls = hls; this.config = hls.config; this.Cues = hls.config.cueHandler; this.captionsProperties = { textTrack1: { label: this.config.captionsTextTrack1Label, languageCode: this.config.captionsTextTrack1LanguageCode }, textTrack2: { label: this.config.captionsTextTrack2Label, languageCode: this.config.captionsTextTrack2LanguageCode }, textTrack3: { label: this.config.captionsTextTrack3Label, languageCode: this.config.captionsTextTrack3LanguageCode }, textTrack4: { label: this.config.captionsTextTrack4Label, languageCode: this.config.captionsTextTrack4LanguageCode } }; hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.on(Events.FRAG_LOADING, this.onFragLoading, this); hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this); hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this); hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); } destroy() { const { hls } = this; hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.off(Events.FRAG_LOADING, this.onFragLoading, this); hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this); hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this); hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); // @ts-ignore this.hls = this.config = null; this.cea608Parser1 = this.cea608Parser2 = undefined; } initCea608Parsers() { if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) { const channel1 = new OutputFilter(this, 'textTrack1'); const channel2 = new OutputFilter(this, 'textTrack2'); const channel3 = new OutputFilter(this, 'textTrack3'); const channel4 = new OutputFilter(this, 'textTrack4'); this.cea608Parser1 = new Cea608Parser(1, channel1, channel2); this.cea608Parser2 = new Cea608Parser(3, channel3, channel4); } } addCues(trackName, startTime, endTime, screen, cueRanges) { // skip cues which overlap more than 50% with previously parsed time ranges let merged = false; for (let i = cueRanges.length; i--;) { const cueRange = cueRanges[i]; const overlap = intersection(cueRange[0], cueRange[1], startTime, endTime); if (overlap >= 0) { cueRange[0] = Math.min(cueRange[0], startTime); cueRange[1] = Math.max(cueRange[1], endTime); merged = true; if (overlap / (endTime - startTime) > 0.5) { return; } } } if (!merged) { cueRanges.push([startTime, endTime]); } if (this.config.renderTextTracksNatively) { const track = this.captionsTracks[trackName]; this.Cues.newCue(track, startTime, endTime, screen); } else { const cues = this.Cues.newCue(null, startTime, endTime, screen); this.hls.trigger(Events.CUES_PARSED, { type: 'captions', cues, track: trackName }); } } // Triggered when an initial PTS is found; used for synchronisation of WebVTT. onInitPtsFound(event, { frag, id, initPTS, timescale }) { const { unparsedVttFrags } = this; if (id === 'main') { this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; } // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded. // Parse any unparsed fragments upon receiving the initial PTS. if (unparsedVttFrags.length) { this.unparsedVttFrags = []; unparsedVttFrags.forEach(frag => { this.onFragLoaded(Events.FRAG_LOADED, frag); }); } } getExistingTrack(label, language) { const { media } = this; if (media) { for (let i = 0; i < media.textTracks.length; i++) { const textTrack = media.textTracks[i]; if (canReuseVttTextTrack(textTrack, { name: label, lang: language, attrs: {} })) { return textTrack; } } } return null; } createCaptionsTrack(trackName) { if (this.config.renderTextTracksNatively) { this.createNativeTrack(trackName); } else { this.createNonNativeTrack(trackName); } } createNativeTrack(trackName) { if (this.captionsTracks[trackName]) { return; } const { captionsProperties, captionsTracks, media } = this; const { label, languageCode } = captionsProperties[trackName]; // Enable reuse of existing text track. const existingTrack = this.getExistingTrack(label, languageCode); if (!existingTrack) { const textTrack = this.createTextTrack('captions', label, languageCode); if (textTrack) { // Set a special property on the track so we know it's managed by Hls.js textTrack[trackName] = true; captionsTracks[trackName] = textTrack; } } else { captionsTracks[trackName] = existingTrack; clearCurrentCues(captionsTracks[trackName]); sendAddTrackEvent(captionsTracks[trackName], media); } } createNonNativeTrack(trackName) { if (this.nonNativeCaptionsTracks[trackName]) { return; } // Create a list of a single track for the provider to consume const trackProperties = this.captionsProperties[trackName]; if (!trackProperties) { return; } const label = trackProperties.label; const track = { _id: trackName, label, kind: 'captions', default: trackProperties.media ? !!trackProperties.media.default : false, closedCaptions: trackProperties.media }; this.nonNativeCaptionsTracks[trackName] = track; this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { tracks: [track] }); } createTextTrack(kind, label, lang) { const media = this.media; if (!media) { return; } return media.addTextTrack(kind, label, lang); } onMediaAttaching(event, data) { this.media = data.media; this._cleanTracks(); } onMediaDetaching() { const { captionsTracks } = this; Object.keys(captionsTracks).forEach(trackName => { clearCurrentCues(captionsTracks[trackName]); delete captionsTracks[trackName]; }); this.nonNativeCaptionsTracks = {}; } onManifestLoading() { // Detect discontinuity in video fragment (CEA-608) parsing this.lastCc = -1; this.lastSn = -1; this.lastPartIndex = -1; // Detect discontinuity in subtitle manifests this.prevCC = -1; this.vttCCs = newVTTCCs(); // Reset tracks this._cleanTracks(); this.tracks = []; this.captionsTracks = {}; this.nonNativeCaptionsTracks = {}; this.textTracks = []; this.unparsedVttFrags = []; this.initPTS = []; if (this.cea608Parser1 && this.cea608Parser2) { this.cea608Parser1.reset(); this.cea608Parser2.reset(); } } _cleanTracks() { // clear outdated subtitles const { media } = this; if (!media) { return; } const textTracks = media.textTracks; if (textTracks) { for (let i = 0; i < textTracks.length; i++) { clearCurrentCues(textTracks[i]); } } } onSubtitleTracksUpdated(event, data) { const tracks = data.subtitleTracks || []; const hasIMSC1 = tracks.some(track => track.textCodec === IMSC1_CODEC); if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) { const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks); if (listIsIdentical) { this.tracks = tracks; return; } this.textTracks = []; this.tracks = tracks; if (this.config.renderTextTracksNatively) { const media = this.media; const inUseTracks = media ? filterSubtitleTracks(media.textTracks) : null; this.tracks.forEach((track, index) => { // Reuse tracks with the same label and lang, but do not reuse 608/708 tracks let textTrack; if (inUseTracks) { let inUseTrack = null; for (let i = 0; i < inUseTracks.length; i++) { if (inUseTracks[i] && canReuseVttTextTrack(inUseTracks[i], track)) { inUseTrack = inUseTracks[i]; inUseTracks[i] = null; break; } } if (inUseTrack) { textTrack = inUseTrack; } } if (textTrack) { clearCurrentCues(textTrack); } else { const textTrackKind = captionsOrSubtitlesFromCharacteristics(track); textTrack = this.createTextTrack(textTrackKind, track.name, track.lang); if (textTrack) { textTrack.mode = 'disabled'; } } if (textTrack) { this.textTracks.push(textTrack); } }); // Warn when video element has captions or subtitle TextTracks carried over from another source if (inUseTracks != null && inUseTracks.length) { const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label); if (unusedTextTracks.length) { logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`); } } } else if (this.tracks.length) { // Create a list of tracks for the provider to consume const tracksList = this.tracks.map(track => { return { label: track.name, kind: track.type.toLowerCase(), default: track.default, subtitleTrack: track }; }); this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { tracks: tracksList }); } } } onManifestLoaded(event, data) { if (this.config.enableCEA708Captions && data.captions) { data.captions.forEach(captionsTrack => { const instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId); if (!instreamIdMatch) { return; } const trackName = `textTrack${instreamIdMatch[1]}`; const trackProperties = this.captionsProperties[trackName]; if (!trackProperties) { return; } trackProperties.label = captionsTrack.name; if (captionsTrack.lang) { // optional attribute trackProperties.languageCode = captionsTrack.lang; } trackProperties.media = captionsTrack; }); } } closedCaptionsForLevel(frag) { const level = this.hls.levels[frag.level]; return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS']; } onFragLoading(event, data) { // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) { var _data$part$index, _data$part; const { cea608Parser1, cea608Parser2, lastSn } = this; const { cc, sn } = data.frag; const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1; if (cea608Parser1 && cea608Parser2) { if (sn !== lastSn + 1 || sn === lastSn && partIndex !== this.lastPartIndex + 1 || cc !== this.lastCc) { cea608Parser1.reset(); cea608Parser2.reset(); } } this.lastCc = cc; this.lastSn = sn; this.lastPartIndex = partIndex; } } onFragLoaded(event, data) { const { frag, payload } = data; if (frag.type === PlaylistLevelType.SUBTITLE) { // If fragment is subtitle type, parse as WebVTT. if (payload.byteLength) { const decryptData = frag.decryptdata; // fragment after decryption has a stats object const decrypted = ('stats' in data); // If the subtitles are not encrypted, parse VTTs now. Otherwise, we need to wait. if (decryptData == null || !decryptData.encrypted || decrypted) { const trackPlaylistMedia = this.tracks[frag.level]; const vttCCs = this.vttCCs; if (!vttCCs[frag.cc]) { vttCCs[frag.cc] = { start: frag.start, prevCC: this.prevCC, new: true }; this.prevCC = frag.cc; } if (trackPlaylistMedia && trackPlaylistMedia.textCodec === IMSC1_CODEC) { this._parseIMSC1(frag, payload); } else { this._parseVTTs(data); } } } else { // In case there is no payload, finish unsuccessfully. this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag, error: new Error('Empty subtitle payload') }); } } } _parseIMSC1(frag, payload) { const hls = this.hls; parseIMSC1(payload, this.initPTS[frag.cc], cues => { this._appendCues(cues, frag.level); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: true, frag: frag }); }, error => { logger.log(`Failed to parse IMSC1: ${error}`); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag: frag, error }); }); } _parseVTTs(data) { var _frag$initSegment; const { frag, payload } = data; // We need an initial synchronisation PTS. Store fragments as long as none has arrived const { initPTS, unparsedVttFrags } = this; const maxAvCC = initPTS.length - 1; if (!initPTS[frag.cc] && maxAvCC === -1) { unparsedVttFrags.push(data); return; } const hls = this.hls; // Parse the WebVTT file contents. const payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)) : payload; parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, cues => { this._appendCues(cues, frag.level); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: true, frag: frag }); }, error => { const missingInitPTS = error.message === 'Missing initPTS for VTT MPEGTS'; if (missingInitPTS) { unparsedVttFrags.push(data); } else { this._fallbackToIMSC1(frag, payload); } // Something went wrong while parsing. Trigger event with success false. logger.log(`Failed to parse VTT cue: ${error}`); if (missingInitPTS && maxAvCC > frag.cc) { return; } hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag: frag, error }); }); } _fallbackToIMSC1(frag, payload) { // If textCodec is unknown, try parsing as IMSC1. Set textCodec based on the result const trackPlaylistMedia = this.tracks[frag.level]; if (!trackPlaylistMedia.textCodec) { parseIMSC1(payload, this.initPTS[frag.cc], () => { trackPlaylistMedia.textCodec = IMSC1_CODEC; this._parseIMSC1(frag, payload); }, () => { trackPlaylistMedia.textCodec = 'wvtt'; }); } } _appendCues(cues, fragLevel) { const hls = this.hls; if (this.config.renderTextTracksNatively) { const textTrack = this.textTracks[fragLevel]; // WebVTTParser.parse is an async method and if the currently selected text track mode is set to "disabled" // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null // and trying to access getCueById method of cues will throw an exception // Because we check if the mode is disabled, we can force check `cues` below. They can't be null. if (!textTrack || textTrack.mode === 'disabled') { return; } cues.forEach(cue => addCueToTrack(textTrack, cue)); } else { const currentTrack = this.tracks[fragLevel]; if (!currentTrack) { return; } const track = currentTrack.default ? 'default' : 'subtitles' + fragLevel; hls.trigger(Events.CUES_PARSED, { type: 'subtitles', cues, track }); } } onFragDecrypted(event, data) { const { frag } = data; if (frag.type === PlaylistLevelType.SUBTITLE) { this.onFragLoaded(Events.FRAG_LOADED, data); } } onSubtitleTracksCleared() { this.tracks = []; this.captionsTracks = {}; } onFragParsingUserdata(event, data) { this.initCea608Parsers(); const { cea608Parser1, cea608Parser2 } = this; if (!this.enabled || !cea608Parser1 || !cea608Parser2) { return; } const { frag, samples } = data; if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === 'NONE') { return; } // If the event contains captions (found in the bytes property), push all bytes into the parser immediately // It will create the proper timestamps based on the PTS value for (let i = 0; i < samples.length; i++) { const ccBytes = samples[i].bytes; if (ccBytes) { const ccdatas = this.extractCea608Data(ccBytes); cea608Parser1.addData(samples[i].pts, ccdatas[0]); cea608Parser2.addData(samples[i].pts, ccdatas[1]); } } } onBufferFlushing(event, { startOffset, endOffset, endOffsetSubtitles, type }) { const { media } = this; if (!media || media.currentTime < endOffset) { return; } // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed // Forward cues are never removed because we can loose streamed 608 content from recent fragments if (!type || type === 'video') { const { captionsTracks } = this; Object.keys(captionsTracks).forEach(trackName => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset)); } if (this.config.renderTextTracksNatively) { // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed if (startOffset === 0 && endOffsetSubtitles !== undefined) { const { textTracks } = this; Object.keys(textTracks).forEach(trackName => removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles)); } } } extractCea608Data(byteArray) { const actualCCBytes = [[], []]; const count = byteArray[0] & 0x1f; let position = 2; for (let j = 0; j < count; j++) { const tmpByte = byteArray[position++]; const ccbyte1 = 0x7f & byteArray[position++]; const ccbyte2 = 0x7f & byteArray[position++]; if (ccbyte1 === 0 && ccbyte2 === 0) { continue; } const ccValid = (0x04 & tmpByte) !== 0; // Support all four channels if (ccValid) { const ccType = 0x03 & tmpByte; if (0x00 /* CEA608 field1*/ === ccType || 0x01 /* CEA608 field2*/ === ccType) { // Exclude CEA708 CC data. actualCCBytes[ccType].push(ccbyte1); actualCCBytes[ccType].push(ccbyte2); } } } return actualCCBytes; } } function captionsOrSubtitlesFromCharacteristics(track) { if (track.characteristics) { if (/transcribes-spoken-dialog/gi.test(track.characteristics) && /describes-music-and-sound/gi.test(track.characteristics)) { return 'captions'; } } return 'subtitles'; } function canReuseVttTextTrack(inUseTrack, manifestTrack) { return !!inUseTrack && inUseTrack.kind === captionsOrSubtitlesFromCharacteristics(manifestTrack) && subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack); } function intersection(x1, x2, y1, y2) { return Math.min(x2, y2) - Math.max(x1, y1); } function newVTTCCs() { return { ccOffset: 0, presentationOffset: 0, 0: { start: 0, prevCC: -1, new: true } }; } class CapLevelController { constructor(hls) { this.hls = void 0; this.autoLevelCapping = void 0; this.firstLevel = void 0; this.media = void 0; this.restrictedLevels = void 0; this.timer = void 0; this.clientRect = void 0; this.streamController = void 0; this.hls = hls; this.autoLevelCapping = Number.POSITIVE_INFINITY; this.firstLevel = -1; this.media = null; this.restrictedLevels = []; this.timer = undefined; this.clientRect = null; this.registerListeners(); } setStreamController(streamController) { this.streamController = streamController; } destroy() { if (this.hls) { this.unregisterListener(); } if (this.timer) { this.stopCapping(); } this.media = null; this.clientRect = null; // @ts-ignore this.hls = this.streamController = null; } registerListeners() { const { hls } = this; hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); } unregisterListener() { const { hls } = this; hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); } onFpsDropLevelCapping(event, data) { // Don't add a restricted level more than once const level = this.hls.levels[data.droppedLevel]; if (this.isLevelAllowed(level)) { this.restrictedLevels.push({ bitrate: level.bitrate, height: level.height, width: level.width }); } } onMediaAttaching(event, data) { this.media = data.media instanceof HTMLVideoElement ? data.media : null; this.clientRect = null; if (this.timer && this.hls.levels.length) { this.detectPlayerSize(); } } onManifestParsed(event, data) { const hls = this.hls; this.restrictedLevels = []; this.firstLevel = data.firstLevel; if (hls.config.capLevelToPlayerSize && data.video) { // Start capping immediately if the manifest has signaled video codecs this.startCapping(); } } onLevelsUpdated(event, data) { if (this.timer && isFiniteNumber(this.autoLevelCapping)) { this.detectPlayerSize(); } } // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted // to the first level onBufferCodecs(event, data) { const hls = this.hls; if (hls.config.capLevelToPlayerSize && data.video) { // If the manifest did not signal a video codec capping has been deferred until we're certain video is present this.startCapping(); } } onMediaDetaching() { this.stopCapping(); } detectPlayerSize() { if (this.media) { if (this.mediaHeight <= 0 || this.mediaWidth <= 0) { this.clientRect = null; return; } const levels = this.hls.levels; if (levels.length) { const hls = this.hls; const maxLevel = this.getMaxLevel(levels.length - 1); if (maxLevel !== this.autoLevelCapping) { logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`); } hls.autoLevelCapping = maxLevel; if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) { // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch // usually happen when the user go to the fullscreen mode. this.streamController.nextLevelSwitch(); } this.autoLevelCapping = hls.autoLevelCapping; } } } /* * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled) */ getMaxLevel(capLevelIndex) { const levels = this.hls.levels; if (!levels.length) { return -1; } const validLevels = levels.filter((level, index) => this.isLevelAllowed(level) && index <= capLevelIndex); this.clientRect = null; return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight); } startCapping() { if (this.timer) { // Don't reset capping if started twice; this can happen if the manifest signals a video codec return; } this.autoLevelCapping = Number.POSITIVE_INFINITY; self.clearInterval(this.timer); this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000); this.detectPlayerSize(); } stopCapping() { this.restrictedLevels = []; this.firstLevel = -1; this.autoLevelCapping = Number.POSITIVE_INFINITY; if (this.timer) { self.clearInterval(this.timer); this.timer = undefined; } } getDimensions() { if (this.clientRect) { return this.clientRect; } const media = this.media; const boundsRect = { width: 0, height: 0 }; if (media) { const clientRect = media.getBoundingClientRect(); boundsRect.width = clientRect.width; boundsRect.height = clientRect.height; if (!boundsRect.width && !boundsRect.height) { // When the media element has no width or height (equivalent to not being in the DOM), // then use its width and height attributes (media.width, media.height) boundsRect.width = clientRect.right - clientRect.left || media.width || 0; boundsRect.height = clientRect.bottom - clientRect.top || media.height || 0; } } this.clientRect = boundsRect; return boundsRect; } get mediaWidth() { return this.getDimensions().width * this.contentScaleFactor; } get mediaHeight() { return this.getDimensions().height * this.contentScaleFactor; } get contentScaleFactor() { let pixelRatio = 1; if (!this.hls.config.ignoreDevicePixelRatio) { try { pixelRatio = self.devicePixelRatio; } catch (e) { /* no-op */ } } return pixelRatio; } isLevelAllowed(level) { const restrictedLevels = this.restrictedLevels; return !restrictedLevels.some(restrictedLevel => { return level.bitrate === restrictedLevel.bitrate && level.width === restrictedLevel.width && level.height === restrictedLevel.height; }); } static getMaxLevelByMediaSize(levels, width, height) { if (!(levels != null && levels.length)) { return -1; } // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next // to determine whether we've chosen the greatest bandwidth for the media's dimensions const atGreatestBandwidth = (curLevel, nextLevel) => { if (!nextLevel) { return true; } return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height; }; // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to // the max level let maxLevelIndex = levels.length - 1; // Prevent changes in aspect-ratio from causing capping to toggle back and forth const squareSize = Math.max(width, height); for (let i = 0; i < levels.length; i += 1) { const level = levels[i]; if ((level.width >= squareSize || level.height >= squareSize) && atGreatestBandwidth(level, levels[i + 1])) { maxLevelIndex = i; break; } } return maxLevelIndex; } } class FPSController { constructor(hls) { this.hls = void 0; this.isVideoPlaybackQualityAvailable = false; this.timer = void 0; this.media = null; this.lastTime = void 0; this.lastDroppedFrames = 0; this.lastDecodedFrames = 0; // stream controller must be provided as a dependency! this.streamController = void 0; this.hls = hls; this.registerListeners(); } setStreamController(streamController) { this.streamController = streamController; } registerListeners() { this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); } unregisterListeners() { this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); } destroy() { if (this.timer) { clearInterval(this.timer); } this.unregisterListeners(); this.isVideoPlaybackQualityAvailable = false; this.media = null; } onMediaAttaching(event, data) { const config = this.hls.config; if (config.capLevelOnFPSDrop) { const media = data.media instanceof self.HTMLVideoElement ? data.media : null; this.media = media; if (media && typeof media.getVideoPlaybackQuality === 'function') { this.isVideoPlaybackQualityAvailable = true; } self.clearInterval(this.timer); this.timer = self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod); } } checkFPS(video, decodedFrames, droppedFrames) { const currentTime = performance.now(); if (decodedFrames) { if (this.lastTime) { const currentPeriod = currentTime - this.lastTime; const currentDropped = droppedFrames - this.lastDroppedFrames; const currentDecoded = decodedFrames - this.lastDecodedFrames; const droppedFPS = 1000 * currentDropped / currentPeriod; const hls = this.hls; hls.trigger(Events.FPS_DROP, { currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames }); if (droppedFPS > 0) { // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod)); if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) { let currentLevel = hls.currentLevel; logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel); if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) { currentLevel = currentLevel - 1; hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, { level: currentLevel, droppedLevel: hls.currentLevel }); hls.autoLevelCapping = currentLevel; this.streamController.nextLevelSwitch(); } } } } this.lastTime = currentTime; this.lastDroppedFrames = droppedFrames; this.lastDecodedFrames = decodedFrames; } } checkFPSInterval() { const video = this.media; if (video) { if (this.isVideoPlaybackQualityAvailable) { const videoPlaybackQuality = video.getVideoPlaybackQuality(); this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames); } else { // HTMLVideoElement doesn't include the webkit types this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount); } } } } const LOGGER_PREFIX = '[eme]'; /** * Controller to deal with encrypted media extensions (EME) * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API * * @class * @constructor */ class EMEController { constructor(hls) { this.hls = void 0; this.config = void 0; this.media = null; this.keyFormatPromise = null; this.keySystemAccessPromises = {}; this._requestLicenseFailureCount = 0; this.mediaKeySessions = []; this.keyIdToKeySessionPromise = {}; this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : []; this.onMediaEncrypted = this._onMediaEncrypted.bind(this); this.onWaitingForKey = this._onWaitingForKey.bind(this); this.debug = logger.debug.bind(logger, LOGGER_PREFIX); this.log = logger.log.bind(logger, LOGGER_PREFIX); this.warn = logger.warn.bind(logger, LOGGER_PREFIX); this.error = logger.error.bind(logger, LOGGER_PREFIX); this.hls = hls; this.config = hls.config; this.registerListeners(); } destroy() { this.unregisterListeners(); this.onMediaDetached(); // Remove any references that could be held in config options or callbacks const config = this.config; config.requestMediaKeySystemAccessFunc = null; config.licenseXhrSetup = config.licenseResponseCallback = undefined; config.drmSystems = config.drmSystemOptions = {}; // @ts-ignore this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null; // @ts-ignore this.config = null; } registerListeners() { this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); } unregisterListeners() { this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); } getLicenseServerUrl(keySystem) { const { drmSystems, widevineLicenseUrl } = this.config; const keySystemConfiguration = drmSystems[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.licenseUrl; } // For backward compatibility if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) { return widevineLicenseUrl; } throw new Error(`no license server URL configured for key-system "${keySystem}"`); } getServerCertificateUrl(keySystem) { const { drmSystems } = this.config; const keySystemConfiguration = drmSystems[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.serverCertificateUrl; } else { this.log(`No Server Certificate in config.drmSystems["${keySystem}"]`); } } attemptKeySystemAccess(keySystemsToAttempt) { const levels = this.hls.levels; const uniqueCodec = (value, i, a) => !!value && a.indexOf(value) === i; const audioCodecs = levels.map(level => level.audioCodec).filter(uniqueCodec); const videoCodecs = levels.map(level => level.videoCodec).filter(uniqueCodec); if (audioCodecs.length + videoCodecs.length === 0) { videoCodecs.push('avc1.42e01e'); } return new Promise((resolve, reject) => { const attempt = keySystems => { const keySystem = keySystems.shift(); this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then(mediaKeys => resolve({ keySystem, mediaKeys })).catch(error => { if (keySystems.length) { attempt(keySystems); } else if (error instanceof EMEKeyError) { reject(error); } else { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_ACCESS, error, fatal: true }, error.message)); } }); }; attempt(keySystemsToAttempt); }); } requestMediaKeySystemAccess(keySystem, supportedConfigurations) { const { requestMediaKeySystemAccessFunc } = this.config; if (!(typeof requestMediaKeySystemAccessFunc === 'function')) { let errMessage = `Configured requestMediaKeySystemAccess is not a function ${requestMediaKeySystemAccessFunc}`; if (requestMediaKeySystemAccess === null && self.location.protocol === 'http:') { errMessage = `navigator.requestMediaKeySystemAccess is not available over insecure protocol ${location.protocol}`; } return Promise.reject(new Error(errMessage)); } return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations); } getMediaKeysPromise(keySystem, audioCodecs, videoCodecs) { // This can throw, but is caught in event handler callpath const mediaKeySystemConfigs = getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions); const keySystemAccessPromises = this.keySystemAccessPromises[keySystem]; let keySystemAccess = keySystemAccessPromises == null ? void 0 : keySystemAccessPromises.keySystemAccess; if (!keySystemAccess) { this.log(`Requesting encrypted media "${keySystem}" key-system access with config: ${JSON.stringify(mediaKeySystemConfigs)}`); keySystemAccess = this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs); const _keySystemAccessPromises = this.keySystemAccessPromises[keySystem] = { keySystemAccess }; keySystemAccess.catch(error => { this.log(`Failed to obtain access to key-system "${keySystem}": ${error}`); }); return keySystemAccess.then(mediaKeySystemAccess => { this.log(`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`); const certificateRequest = this.fetchServerCertificate(keySystem); this.log(`Create media-keys for "${keySystem}"`); _keySystemAccessPromises.mediaKeys = mediaKeySystemAccess.createMediaKeys().then(mediaKeys => { this.log(`Media-keys created for "${keySystem}"`); return certificateRequest.then(certificate => { if (certificate) { return this.setMediaKeysServerCertificate(mediaKeys, keySystem, certificate); } return mediaKeys; }); }); _keySystemAccessPromises.mediaKeys.catch(error => { this.error(`Failed to create media-keys for "${keySystem}"}: ${error}`); }); return _keySystemAccessPromises.mediaKeys; }); } return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys); } createMediaKeySessionContext({ decryptdata, keySystem, mediaKeys }) { this.log(`Creating key-system session "${keySystem}" keyId: ${Hex.hexDump(decryptdata.keyId || [])}`); const mediaKeysSession = mediaKeys.createSession(); const mediaKeySessionContext = { decryptdata, keySystem, mediaKeys, mediaKeysSession, keyStatus: 'status-pending' }; this.mediaKeySessions.push(mediaKeySessionContext); return mediaKeySessionContext; } renewKeySession(mediaKeySessionContext) { const decryptdata = mediaKeySessionContext.decryptdata; if (decryptdata.pssh) { const keySessionContext = this.createMediaKeySessionContext(mediaKeySessionContext); const keyId = this.getKeyIdString(decryptdata); const scheme = 'cenc'; this.keyIdToKeySessionPromise[keyId] = this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'expired'); } else { this.warn(`Could not renew expired session. Missing pssh initData.`); } this.removeSession(mediaKeySessionContext); } getKeyIdString(decryptdata) { if (!decryptdata) { throw new Error('Could not read keyId of undefined decryptdata'); } if (decryptdata.keyId === null) { throw new Error('keyId is null'); } return Hex.hexDump(decryptdata.keyId); } updateKeySession(mediaKeySessionContext, data) { var _mediaKeySessionConte; const keySession = mediaKeySessionContext.mediaKeysSession; this.log(`Updating key-session "${keySession.sessionId}" for keyID ${Hex.hexDump(((_mediaKeySessionConte = mediaKeySessionContext.decryptdata) == null ? void 0 : _mediaKeySessionConte.keyId) || [])} } (data length: ${data ? data.byteLength : data})`); return keySession.update(data); } selectKeySystemFormat(frag) { const keyFormats = Object.keys(frag.levelkeys || {}); if (!this.keyFormatPromise) { this.log(`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${frag.level}) key formats ${keyFormats.join(', ')}`); this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); } return this.keyFormatPromise; } getKeyFormatPromise(keyFormats) { return new Promise((resolve, reject) => { const keySystemsInConfig = getKeySystemsForConfig(this.config); const keySystemsToAttempt = keyFormats.map(keySystemFormatToKeySystemDomain).filter(value => !!value && keySystemsInConfig.indexOf(value) !== -1); return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({ keySystem }) => { const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem); if (keySystemFormat) { resolve(keySystemFormat); } else { reject(new Error(`Unable to find format for key-system "${keySystem}"`)); } }).catch(reject); }); } loadKey(data) { const decryptdata = data.keyInfo.decryptdata; const keyId = this.getKeyIdString(decryptdata); const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`; this.log(`Starting session for key ${keyDetails}`); let keySessionContextPromise = this.keyIdToKeySessionPromise[keyId]; if (!keySessionContextPromise) { keySessionContextPromise = this.keyIdToKeySessionPromise[keyId] = this.getKeySystemForKeyPromise(decryptdata).then(({ keySystem, mediaKeys }) => { this.throwIfDestroyed(); this.log(`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`); return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => { this.throwIfDestroyed(); const keySessionContext = this.createMediaKeySessionContext({ keySystem, mediaKeys, decryptdata }); const scheme = 'cenc'; return this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'playlist-key'); }); }); keySessionContextPromise.catch(error => this.handleError(error)); } return keySessionContextPromise; } throwIfDestroyed(message = 'Invalid state') { if (!this.hls) { throw new Error('invalid state'); } } handleError(error) { if (!this.hls) { return; } this.error(error.message); if (error instanceof EMEKeyError) { this.hls.trigger(Events.ERROR, error.data); } else { this.hls.trigger(Events.ERROR, { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_KEYS, error, fatal: true }); } } getKeySystemForKeyPromise(decryptdata) { const keyId = this.getKeyIdString(decryptdata); const mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId]; if (!mediaKeySessionContext) { const keySystem = keySystemFormatToKeySystemDomain(decryptdata.keyFormat); const keySystemsToAttempt = keySystem ? [keySystem] : getKeySystemsForConfig(this.config); return this.attemptKeySystemAccess(keySystemsToAttempt); } return mediaKeySessionContext; } getKeySystemSelectionPromise(keySystemsToAttempt) { if (!keySystemsToAttempt.length) { keySystemsToAttempt = getKeySystemsForConfig(this.config); } if (keySystemsToAttempt.length === 0) { throw new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE, fatal: true }, `Missing key-system license configuration options ${JSON.stringify({ drmSystems: this.config.drmSystems })}`); } return this.attemptKeySystemAccess(keySystemsToAttempt); } _onMediaEncrypted(event) { const { initDataType, initData } = event; const logMessage = `"${event.type}" event: init data type: "${initDataType}"`; this.debug(logMessage); // Ignore event when initData is null if (initData === null) { return; } let keyId; let keySystemDomain; if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) { // Match sinf keyId to playlist skd://keyId= const json = bin2str(new Uint8Array(initData)); try { const sinf = base64Decode(JSON.parse(json).sinf); const tenc = parseSinf(new Uint8Array(sinf)); if (!tenc) { throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`); } keyId = tenc.subarray(8, 24); keySystemDomain = KeySystems.FAIRPLAY; } catch (error) { this.warn(`${logMessage} Failed to parse sinf: ${error}`); return; } } else { // Support Widevine clear-lead key-session creation (otherwise depend on playlist keys) const psshResults = parseMultiPssh(initData); const psshInfo = psshResults.filter(pssh => pssh.systemId === KeySystemIds.WIDEVINE)[0]; if (!psshInfo) { if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) { this.warn(`${logMessage} contains incomplete or invalid pssh data`); } else { this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`); } return; } keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId); if (psshInfo.version === 0 && psshInfo.data) { const offset = psshInfo.data.length - 22; keyId = psshInfo.data.subarray(offset, offset + 16); } } if (!keySystemDomain || !keyId) { return; } const keyIdHex = Hex.hexDump(keyId); const { keyIdToKeySessionPromise, mediaKeySessions } = this; let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex]; for (let i = 0; i < mediaKeySessions.length; i++) { // Match playlist key const keyContext = mediaKeySessions[i]; const decryptdata = keyContext.decryptdata; if (!decryptdata.keyId) { continue; } const oldKeyIdHex = Hex.hexDump(decryptdata.keyId); if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) { keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; if (decryptdata.pssh) { break; } delete keyIdToKeySessionPromise[oldKeyIdHex]; decryptdata.pssh = new Uint8Array(initData); decryptdata.keyId = keyId; keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => { return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match'); }); break; } } if (!keySessionContextPromise) { // Clear-lead key (not encountered in playlist) keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({ keySystem, mediaKeys }) => { var _keySystemToKeySystem; this.throwIfDestroyed(); const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : ''); decryptdata.pssh = new Uint8Array(initData); decryptdata.keyId = keyId; return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => { this.throwIfDestroyed(); const keySessionContext = this.createMediaKeySessionContext({ decryptdata, keySystem, mediaKeys }); return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match'); }); }); } keySessionContextPromise.catch(error => this.handleError(error)); } _onWaitingForKey(event) { this.log(`"${event.type}" event`); } attemptSetMediaKeys(keySystem, mediaKeys) { const queue = this.setMediaKeysQueue.slice(); this.log(`Setting media-keys for "${keySystem}"`); // Only one setMediaKeys() can run at one time, and multiple setMediaKeys() operations // can be queued for execution for multiple key sessions. const setMediaKeysPromise = Promise.all(queue).then(() => { if (!this.media) { throw new Error('Attempted to set mediaKeys without media element attached'); } return this.media.setMediaKeys(mediaKeys); }); this.setMediaKeysQueue.push(setMediaKeysPromise); return setMediaKeysPromise.then(() => { this.log(`Media-keys set for "${keySystem}"`); queue.push(setMediaKeysPromise); this.setMediaKeysQueue = this.setMediaKeysQueue.filter(p => queue.indexOf(p) === -1); }); } generateRequestWithPreferredKeySession(context, initDataType, initData, reason) { var _this$config$drmSyste, _this$config$drmSyste2; const generateRequestFilter = (_this$config$drmSyste = this.config.drmSystems) == null ? void 0 : (_this$config$drmSyste2 = _this$config$drmSyste[context.keySystem]) == null ? void 0 : _this$config$drmSyste2.generateRequest; if (generateRequestFilter) { try { const mappedInitData = generateRequestFilter.call(this.hls, initDataType, initData, context); if (!mappedInitData) { throw new Error('Invalid response from configured generateRequest filter'); } initDataType = mappedInitData.initDataType; initData = context.decryptdata.pssh = mappedInitData.initData ? new Uint8Array(mappedInitData.initData) : null; } catch (error) { var _this$hls; this.warn(error.message); if ((_this$hls = this.hls) != null && _this$hls.config.debug) { throw error; } } } if (initData === null) { this.log(`Skipping key-session request for "${reason}" (no initData)`); return Promise.resolve(context); } const keyId = this.getKeyIdString(context.decryptdata); this.log(`Generating key-session request for "${reason}": ${keyId} (init data type: ${initDataType} length: ${initData ? initData.byteLength : null})`); const licenseStatus = new EventEmitter(); const onmessage = context._onmessage = event => { const keySession = context.mediaKeysSession; if (!keySession) { licenseStatus.emit('error', new Error('invalid state')); return; } const { messageType, message } = event; this.log(`"${messageType}" message event for session "${keySession.sessionId}" message size: ${message.byteLength}`); if (messageType === 'license-request' || messageType === 'license-renewal') { this.renewLicense(context, message).catch(error => { this.handleError(error); licenseStatus.emit('error', error); }); } else if (messageType === 'license-release') { if (context.keySystem === KeySystems.FAIRPLAY) { this.updateKeySession(context, strToUtf8array('acknowledged')); this.removeSession(context); } } else { this.warn(`unhandled media key message type "${messageType}"`); } }; const onkeystatuseschange = context._onkeystatuseschange = event => { const keySession = context.mediaKeysSession; if (!keySession) { licenseStatus.emit('error', new Error('invalid state')); return; } this.onKeyStatusChange(context); const keyStatus = context.keyStatus; licenseStatus.emit('keyStatus', keyStatus); if (keyStatus === 'expired') { this.warn(`${context.keySystem} expired for key ${keyId}`); this.renewKeySession(context); } }; context.mediaKeysSession.addEventListener('message', onmessage); context.mediaKeysSession.addEventListener('keystatuseschange', onkeystatuseschange); const keyUsablePromise = new Promise((resolve, reject) => { licenseStatus.on('error', reject); licenseStatus.on('keyStatus', keyStatus => { if (keyStatus.startsWith('usable')) { resolve(); } else if (keyStatus === 'output-restricted') { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED, fatal: false }, 'HDCP level output restricted')); } else if (keyStatus === 'internal-error') { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR, fatal: true }, `key status changed to "${keyStatus}"`)); } else if (keyStatus === 'expired') { reject(new Error('key expired while generating request')); } else { this.warn(`unhandled key status change "${keyStatus}"`); } }); }); return context.mediaKeysSession.generateRequest(initDataType, initData).then(() => { var _context$mediaKeysSes; this.log(`Request generated for key-session "${(_context$mediaKeysSes = context.mediaKeysSession) == null ? void 0 : _context$mediaKeysSes.sessionId}" keyId: ${keyId}`); }).catch(error => { throw new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_SESSION, error, fatal: false }, `Error generating key-session request: ${error}`); }).then(() => keyUsablePromise).catch(error => { licenseStatus.removeAllListeners(); this.removeSession(context); throw error; }).then(() => { licenseStatus.removeAllListeners(); return context; }); } onKeyStatusChange(mediaKeySessionContext) { mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach((status, keyId) => { this.log(`key status change "${status}" for keyStatuses keyId: ${Hex.hexDump('buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) : new Uint8Array(keyId))} session keyId: ${Hex.hexDump(new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []))} uri: ${mediaKeySessionContext.decryptdata.uri}`); mediaKeySessionContext.keyStatus = status; }); } fetchServerCertificate(keySystem) { const config = this.config; const Loader = config.loader; const certLoader = new Loader(config); const url = this.getServerCertificateUrl(keySystem); if (!url) { return Promise.resolve(); } this.log(`Fetching server certificate for "${keySystem}"`); return new Promise((resolve, reject) => { const loaderContext = { responseType: 'arraybuffer', url }; const loadPolicy = config.certLoadPolicy.default; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { resolve(response.data); }, onError: (response, contex, networkDetails, stats) => { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, fatal: true, networkDetails, response: _objectSpread2({ url: loaderContext.url, data: undefined }, response) }, `"${keySystem}" certificate request failed (${url}). Status: ${response.code} (${response.text})`)); }, onTimeout: (stats, context, networkDetails) => { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, fatal: true, networkDetails, response: { url: loaderContext.url, data: undefined } }, `"${keySystem}" certificate request timed out (${url})`)); }, onAbort: (stats, context, networkDetails) => { reject(new Error('aborted')); } }; certLoader.load(loaderContext, loaderConfig, loaderCallbacks); }); } setMediaKeysServerCertificate(mediaKeys, keySystem, cert) { return new Promise((resolve, reject) => { mediaKeys.setServerCertificate(cert).then(success => { this.log(`setServerCertificate ${success ? 'success' : 'not supported by CDM'} (${cert == null ? void 0 : cert.byteLength}) on "${keySystem}"`); resolve(mediaKeys); }).catch(error => { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED, error, fatal: true }, error.message)); }); }); } renewLicense(context, keyMessage) { return this.requestLicense(context, new Uint8Array(keyMessage)).then(data => { return this.updateKeySession(context, new Uint8Array(data)).catch(error => { throw new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED, error, fatal: true }, error.message); }); }); } unpackPlayReadyKeyMessage(xhr, licenseChallenge) { // On Edge, the raw license message is UTF-16-encoded XML. We need // to unpack the Challenge element (base64-encoded string containing the // actual license request) and any HttpHeader elements (sent as request // headers). // For PlayReady CDMs, we need to dig the Challenge out of the XML. const xmlString = String.fromCharCode.apply(null, new Uint16Array(licenseChallenge.buffer)); if (!xmlString.includes('PlayReadyKeyMessage')) { // This does not appear to be a wrapped message as on Edge. Some // clients do not need this unwrapping, so we will assume this is one of // them. Note that "xml" at this point probably looks like random // garbage, since we interpreted UTF-8 as UTF-16. xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8'); return licenseChallenge; } const keyMessageXml = new DOMParser().parseFromString(xmlString, 'application/xml'); // Set request headers. const headers = keyMessageXml.querySelectorAll('HttpHeader'); if (headers.length > 0) { let header; for (let i = 0, len = headers.length; i < len; i++) { var _header$querySelector, _header$querySelector2; header = headers[i]; const name = (_header$querySelector = header.querySelector('name')) == null ? void 0 : _header$querySelector.textContent; const value = (_header$querySelector2 = header.querySelector('value')) == null ? void 0 : _header$querySelector2.textContent; if (name && value) { xhr.setRequestHeader(name, value); } } } const challengeElement = keyMessageXml.querySelector('Challenge'); const challengeText = challengeElement == null ? void 0 : challengeElement.textContent; if (!challengeText) { throw new Error(`Cannot find <Challenge> in key message`); } return strToUtf8array(atob(challengeText)); } setupLicenseXHR(xhr, url, keysListItem, licenseChallenge) { const licenseXhrSetup = this.config.licenseXhrSetup; if (!licenseXhrSetup) { xhr.open('POST', url, true); return Promise.resolve({ xhr, licenseChallenge }); } return Promise.resolve().then(() => { if (!keysListItem.decryptdata) { throw new Error('Key removed'); } return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge); }).catch(error => { if (!keysListItem.decryptdata) { // Key session removed. Cancel license request. throw error; } // let's try to open before running setup xhr.open('POST', url, true); return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge); }).then(licenseXhrSetupResult => { // if licenseXhrSetup did not yet call open, let's do it now if (!xhr.readyState) { xhr.open('POST', url, true); } const finalLicenseChallenge = licenseXhrSetupResult ? licenseXhrSetupResult : licenseChallenge; return { xhr, licenseChallenge: finalLicenseChallenge }; }); } requestLicense(keySessionContext, licenseChallenge) { const keyLoadPolicy = this.config.keyLoadPolicy.default; return new Promise((resolve, reject) => { const url = this.getLicenseServerUrl(keySessionContext.keySystem); this.log(`Sending license request to URL: ${url}`); const xhr = new XMLHttpRequest(); xhr.responseType = 'arraybuffer'; xhr.onreadystatechange = () => { if (!this.hls || !keySessionContext.mediaKeysSession) { return reject(new Error('invalid state')); } if (xhr.readyState === 4) { if (xhr.status === 200) { this._requestLicenseFailureCount = 0; let data = xhr.response; this.log(`License received ${data instanceof ArrayBuffer ? data.byteLength : data}`); const licenseResponseCallback = this.config.licenseResponseCallback; if (licenseResponseCallback) { try { data = licenseResponseCallback.call(this.hls, xhr, url, keySessionContext); } catch (error) { this.error(error); } } resolve(data); } else { const retryConfig = keyLoadPolicy.errorRetry; const maxNumRetry = retryConfig ? retryConfig.maxNumRetry : 0; this._requestLicenseFailureCount++; if (this._requestLicenseFailureCount > maxNumRetry || xhr.status >= 400 && xhr.status < 500) { reject(new EMEKeyError({ type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED, fatal: true, networkDetails: xhr, response: { url, data: undefined, code: xhr.status, text: xhr.statusText } }, `License Request XHR failed (${url}). Status: ${xhr.status} (${xhr.statusText})`)); } else { const attemptsLeft = maxNumRetry - this._requestLicenseFailureCount + 1; this.warn(`Retrying license request, ${attemptsLeft} attempts left`); this.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject); } } } }; if (keySessionContext.licenseXhr && keySessionContext.licenseXhr.readyState !== XMLHttpRequest.DONE) { keySessionContext.licenseXhr.abort(); } keySessionContext.licenseXhr = xhr; this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(({ xhr, licenseChallenge }) => { if (keySessionContext.keySystem == KeySystems.PLAYREADY) { licenseChallenge = this.unpackPlayReadyKeyMessage(xhr, licenseChallenge); } xhr.send(licenseChallenge); }); }); } onMediaAttached(event, data) { if (!this.config.emeEnabled) { return; } const media = data.media; // keep reference of media this.media = media; media.addEventListener('encrypted', this.onMediaEncrypted); media.addEventListener('waitingforkey', this.onWaitingForKey); } onMediaDetached() { const media = this.media; const mediaKeysList = this.mediaKeySessions; if (media) { media.removeEventListener('encrypted', this.onMediaEncrypted); media.removeEventListener('waitingforkey', this.onWaitingForKey); this.media = null; } this._requestLicenseFailureCount = 0; this.setMediaKeysQueue = []; this.mediaKeySessions = []; this.keyIdToKeySessionPromise = {}; LevelKey.clearKeyUriToKeyIdMap(); // Close all sessions and remove media keys from the video element. const keySessionCount = mediaKeysList.length; EMEController.CDMCleanupPromise = Promise.all(mediaKeysList.map(mediaKeySessionContext => this.removeSession(mediaKeySessionContext)).concat(media == null ? void 0 : media.setMediaKeys(null).catch(error => { this.log(`Could not clear media keys: ${error}`); }))).then(() => { if (keySessionCount) { this.log('finished closing key sessions and clearing media keys'); mediaKeysList.length = 0; } }).catch(error => { this.log(`Could not close sessions and clear media keys: ${error}`); }); } onManifestLoading() { this.keyFormatPromise = null; } onManifestLoaded(event, { sessionKeys }) { if (!sessionKeys || !this.config.emeEnabled) { return; } if (!this.keyFormatPromise) { const keyFormats = sessionKeys.reduce((formats, sessionKey) => { if (formats.indexOf(sessionKey.keyFormat) === -1) { formats.push(sessionKey.keyFormat); } return formats; }, []); this.log(`Selecting key-system from session-keys ${keyFormats.join(', ')}`); this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); } } removeSession(mediaKeySessionContext) { const { mediaKeysSession, licenseXhr } = mediaKeySessionContext; if (mediaKeysSession) { this.log(`Remove licenses and keys and close session ${mediaKeysSession.sessionId}`); if (mediaKeySessionContext._onmessage) { mediaKeysSession.removeEventListener('message', mediaKeySessionContext._onmessage); mediaKeySessionContext._onmessage = undefined; } if (mediaKeySessionContext._onkeystatuseschange) { mediaKeysSession.removeEventListener('keystatuseschange', mediaKeySessionContext._onkeystatuseschange); mediaKeySessionContext._onkeystatuseschange = undefined; } if (licenseXhr && licenseXhr.readyState !== XMLHttpRequest.DONE) { licenseXhr.abort(); } mediaKeySessionContext.mediaKeysSession = mediaKeySessionContext.decryptdata = mediaKeySessionContext.licenseXhr = undefined; const index = this.mediaKeySessions.indexOf(mediaKeySessionContext); if (index > -1) { this.mediaKeySessions.splice(index, 1); } return mediaKeysSession.remove().catch(error => { this.log(`Could not remove session: ${error}`); }).then(() => { return mediaKeysSession.close(); }).catch(error => { this.log(`Could not close session: ${error}`); }); } } } EMEController.CDMCleanupPromise = void 0; class EMEKeyError extends Error { constructor(data, message) { super(message); this.data = void 0; data.error || (data.error = new Error(message)); this.data = data; data.err = data.error; } } /** * Common Media Object Type * * @group CMCD * @group CMSD * * @beta */ var CmObjectType; (function (CmObjectType) { /** * text file, such as a manifest or playlist */ CmObjectType["MANIFEST"] = "m"; /** * audio only */ CmObjectType["AUDIO"] = "a"; /** * video only */ CmObjectType["VIDEO"] = "v"; /** * muxed audio and video */ CmObjectType["MUXED"] = "av"; /** * init segment */ CmObjectType["INIT"] = "i"; /** * caption or subtitle */ CmObjectType["CAPTION"] = "c"; /** * ISOBMFF timed text track */ CmObjectType["TIMED_TEXT"] = "tt"; /** * cryptographic key, license or certificate. */ CmObjectType["KEY"] = "k"; /** * other */ CmObjectType["OTHER"] = "o"; })(CmObjectType || (CmObjectType = {})); /** * Common Media Streaming Format * * @group CMCD * @group CMSD * * @beta */ var CmStreamingFormat; (function (CmStreamingFormat) { /** * MPEG DASH */ CmStreamingFormat["DASH"] = "d"; /** * HTTP Live Streaming (HLS) */ CmStreamingFormat["HLS"] = "h"; /** * Smooth Streaming */ CmStreamingFormat["SMOOTH"] = "s"; /** * Other */ CmStreamingFormat["OTHER"] = "o"; })(CmStreamingFormat || (CmStreamingFormat = {})); /** * CMCD header fields. * * @group CMCD * * @beta */ var CmcdHeaderField; (function (CmcdHeaderField) { /** * keys whose values vary with the object being requested. */ CmcdHeaderField["OBJECT"] = "CMCD-Object"; /** * keys whose values vary with each request. */ CmcdHeaderField["REQUEST"] = "CMCD-Request"; /** * keys whose values are expected to be invariant over the life of the session. */ CmcdHeaderField["SESSION"] = "CMCD-Session"; /** * keys whose values do not vary with every request or object. */ CmcdHeaderField["STATUS"] = "CMCD-Status"; })(CmcdHeaderField || (CmcdHeaderField = {})); /** * The map of CMCD header fields to official CMCD keys. * * @internal * * @group CMCD */ const CmcdHeaderMap = { [CmcdHeaderField.OBJECT]: ['br', 'd', 'ot', 'tb'], [CmcdHeaderField.REQUEST]: ['bl', 'dl', 'mtp', 'nor', 'nrr', 'su'], [CmcdHeaderField.SESSION]: ['cid', 'pr', 'sf', 'sid', 'st', 'v'], [CmcdHeaderField.STATUS]: ['bs', 'rtp'] }; /** * Structured Field Item * * @group Structured Field * * @beta */ class SfItem { constructor(value, params) { this.value = void 0; this.params = void 0; if (Array.isArray(value)) { value = value.map(v => v instanceof SfItem ? v : new SfItem(v)); } this.value = value; this.params = params; } } /** * A class to represent structured field tokens when `Symbol` is not available. * * @group Structured Field * * @beta */ class SfToken { constructor(description) { this.description = void 0; this.description = description; } } const DICT = 'Dict'; function format(value) { if (Array.isArray(value)) { return JSON.stringify(value); } if (value instanceof Map) { return 'Map{}'; } if (value instanceof Set) { return 'Set{}'; } if (typeof value === 'object') { return JSON.stringify(value); } return String(value); } function throwError(action, src, type, cause) { return new Error(`failed to ${action} "${format(src)}" as ${type}`, { cause }); } const BARE_ITEM = 'Bare Item'; const BOOLEAN = 'Boolean'; const BYTES = 'Byte Sequence'; const DECIMAL = 'Decimal'; const INTEGER = 'Integer'; function isInvalidInt(value) { return value < -999999999999999 || 999999999999999 < value; } const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex const TOKEN = 'Token'; const KEY = 'Key'; function serializeError(src, type, cause) { return throwError('serialize', src, type, cause); } // 4.1.9. Serializing a Boolean // // Given a Boolean as input_boolean, return an ASCII string suitable for // use in a HTTP field value. // // 1. If input_boolean is not a boolean, fail serialization. // // 2. Let output be an empty string. // // 3. Append "?" to output. // // 4. If input_boolean is true, append "1" to output. // // 5. If input_boolean is false, append "0" to output. // // 6. Return output. function serializeBoolean(value) { if (typeof value !== 'boolean') { throw serializeError(value, BOOLEAN); } return value ? '?1' : '?0'; } /** * Encodes binary data to base64 * * @param binary - The binary data to encode * @returns The base64 encoded string * * @group Utils * * @beta */ function base64encode(binary) { return btoa(String.fromCharCode(...binary)); } // 4.1.8. Serializing a Byte Sequence // // Given a Byte Sequence as input_bytes, return an ASCII string suitable // for use in a HTTP field value. // // 1. If input_bytes is not a sequence of bytes, fail serialization. // // 2. Let output be an empty string. // // 3. Append ":" to output. // // 4. Append the result of base64-encoding input_bytes as per // [RFC4648], Section 4, taking account of the requirements below. // // 5. Append ":" to output. // // 6. Return output. // // The encoded data is required to be padded with "=", as per [RFC4648], // Section 3.2. // // Likewise, encoded data SHOULD have pad bits set to zero, as per // [RFC4648], Section 3.5, unless it is not possible to do so due to // implementation constraints. function serializeByteSequence(value) { if (ArrayBuffer.isView(value) === false) { throw serializeError(value, BYTES); } return `:${base64encode(value)}:`; } // 4.1.4. Serializing an Integer // // Given an Integer as input_integer, return an ASCII string suitable // for use in a HTTP field value. // // 1. If input_integer is not an integer in the range of // -999,999,999,999,999 to 999,999,999,999,999 inclusive, fail // serialization. // // 2. Let output be an empty string. // // 3. If input_integer is less than (but not equal to) 0, append "-" to // output. // // 4. Append input_integer's numeric value represented in base 10 using // only decimal digits to output. // // 5. Return output. function serializeInteger(value) { if (isInvalidInt(value)) { throw serializeError(value, INTEGER); } return value.toString(); } // 4.1.10. Serializing a Date // // Given a Date as input_integer, return an ASCII string suitable for // use in an HTTP field value. // 1. Let output be "@". // 2. Append to output the result of running Serializing an Integer // with input_date (Section 4.1.4). // 3. Return output. function serializeDate(value) { return `@${serializeInteger(value.getTime() / 1000)}`; } /** * This implements the rounding procedure described in step 2 of the "Serializing a Decimal" specification. * This rounding style is known as "even rounding", "banker's rounding", or "commercial rounding". * * @param value - The value to round * @param precision - The number of decimal places to round to * @returns The rounded value * * @group Utils * * @beta */ function roundToEven(value, precision) { if (value < 0) { return -roundToEven(-value, precision); } const decimalShift = Math.pow(10, precision); const isEquidistant = Math.abs(value * decimalShift % 1 - 0.5) < Number.EPSILON; if (isEquidistant) { // If the tail of the decimal place is 'equidistant' we round to the nearest even value const flooredValue = Math.floor(value * decimalShift); return (flooredValue % 2 === 0 ? flooredValue : flooredValue + 1) / decimalShift; } else { // Otherwise, proceed as normal return Math.round(value * decimalShift) / decimalShift; } } // 4.1.5. Serializing a Decimal // // Given a decimal number as input_decimal, return an ASCII string // suitable for use in a HTTP field value. // // 1. If input_decimal is not a decimal number, fail serialization. // // 2. If input_decimal has more than three significant digits to the // right of the decimal point, round it to three decimal places, // rounding the final digit to the nearest value, or to the even // value if it is equidistant. // // 3. If input_decimal has more than 12 significant digits to the left // of the decimal point after rounding, fail serialization. // // 4. Let output be an empty string. // // 5. If input_decimal is less than (but not equal to) 0, append "-" // to output. // // 6. Append input_decimal's integer component represented in base 10 // (using only decimal digits) to output; if it is zero, append // "0". // // 7. Append "." to output. // // 8. If input_decimal's fractional component is zero, append "0" to // output. // // 9. Otherwise, append the significant digits of input_decimal's // fractional component represented in base 10 (using only decimal // digits) to output. // // 10. Return output. function serializeDecimal(value) { const roundedValue = roundToEven(value, 3); // round to 3 decimal places if (Math.floor(Math.abs(roundedValue)).toString().length > 12) { throw serializeError(value, DECIMAL); } const stringValue = roundedValue.toString(); return stringValue.includes('.') ? stringValue : `${stringValue}.0`; } const STRING = 'String'; // 4.1.6. Serializing a String // // Given a String as input_string, return an ASCII string suitable for // use in a HTTP field value. // // 1. Convert input_string into a sequence of ASCII characters; if // conversion fails, fail serialization. // // 2. If input_string contains characters in the range %x00-1f or %x7f // (i.e., not in VCHAR or SP), fail serialization. // // 3. Let output be the string DQUOTE. // // 4. For each character char in input_string: // // 1. If char is "\" or DQUOTE: // // 1. Append "\" to output. // // 2. Append char to output. // // 5. Append DQUOTE to output. // // 6. Return output. function serializeString(value) { if (STRING_REGEX.test(value)) { throw serializeError(value, STRING); } return `"${value.replace(/\\/g, `\\\\`).replace(/"/g, `\\"`)}"`; } function symbolToStr(symbol) { return symbol.description || symbol.toString().slice(7, -1); } function serializeToken(token) { const value = symbolToStr(token); if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) { throw serializeError(value, TOKEN); } return value; } // 4.1.3.1. Serializing a Bare Item // // Given an Item as input_item, return an ASCII string suitable for use // in a HTTP field value. // // 1. If input_item is an Integer, return the result of running // Serializing an Integer (Section 4.1.4) with input_item. // // 2. If input_item is a Decimal, return the result of running // Serializing a Decimal (Section 4.1.5) with input_item. // // 3. If input_item is a String, return the result of running // Serializing a String (Section 4.1.6) with input_item. // // 4. If input_item is a Token, return the result of running // Serializing a Token (Section 4.1.7) with input_item. // // 5. If input_item is a Boolean, return the result of running // Serializing a Boolean (Section 4.1.9) with input_item. // // 6. If input_item is a Byte Sequence, return the result of running // Serializing a Byte Sequence (Section 4.1.8) with input_item. // // 7. If input_item is a Date, return the result of running Serializing // a Date (Section 4.1.10) with input_item. // // 8. Otherwise, fail serialization. function serializeBareItem(value) { switch (typeof value) { case 'number': if (!isFiniteNumber(value)) { throw serializeError(value, BARE_ITEM); } if (Number.isInteger(value)) { return serializeInteger(value); } return serializeDecimal(value); case 'string': return serializeString(value); case 'symbol': return serializeToken(value); case 'boolean': return serializeBoolean(value); case 'object': if (value instanceof Date) { return serializeDate(value); } if (value instanceof Uint8Array) { return serializeByteSequence(value); } if (value instanceof SfToken) { return serializeToken(value); } default: // fail throw serializeError(value, BARE_ITEM); } } // 4.1.1.3. Serializing a Key // // Given a key as input_key, return an ASCII string suitable for use in // a HTTP field value. // // 1. Convert input_key into a sequence of ASCII characters; if // conversion fails, fail serialization. // // 2. If input_key contains characters not in lcalpha, DIGIT, "_", "-", // ".", or "*" fail serialization. // // 3. If the first character of input_key is not lcalpha or "*", fail // serialization. // // 4. Let output be an empty string. // // 5. Append input_key to output. // // 6. Return output. function serializeKey(value) { if (/^[a-z*][a-z0-9\-_.*]*$/.test(value) === false) { throw serializeError(value, KEY); } return value; } // 4.1.1.2. Serializing Parameters // // Given an ordered Dictionary as input_parameters (each member having a // param_name and a param_value), return an ASCII string suitable for // use in a HTTP field value. // // 1. Let output be an empty string. // // 2. For each param_name with a value of param_value in // input_parameters: // // 1. Append ";" to output. // // 2. Append the result of running Serializing a Key // (Section 4.1.1.3) with param_name to output. // // 3. If param_value is not Boolean true: // // 1. Append "=" to output. // // 2. Append the result of running Serializing a bare Item // (Section 4.1.3.1) with param_value to output. // // 3. Return output. function serializeParams(params) { if (params == null) { return ''; } return Object.entries(params).map(([key, value]) => { if (value === true) { return `;${serializeKey(key)}`; // omit true } return `;${serializeKey(key)}=${serializeBareItem(value)}`; }).join(''); } // 4.1.3. Serializing an Item // // Given an Item as bare_item and Parameters as item_parameters, return // an ASCII string suitable for use in a HTTP field value. // // 1. Let output be an empty string. // // 2. Append the result of running Serializing a Bare Item // Section 4.1.3.1 with bare_item to output. // // 3. Append the result of running Serializing Parameters // Section 4.1.1.2 with item_parameters to output. // // 4. Return output. function serializeItem(value) { if (value instanceof SfItem) { return `${serializeBareItem(value.value)}${serializeParams(value.params)}`; } else { return serializeBareItem(value); } } // 4.1.1.1. Serializing an Inner List // // Given an array of (member_value, parameters) tuples as inner_list, // and parameters as list_parameters, return an ASCII string suitable // for use in a HTTP field value. // // 1. Let output be the string "(". // // 2. For each (member_value, parameters) of inner_list: // // 1. Append the result of running Serializing an Item // (Section 4.1.3) with (member_value, parameters) to output. // // 2. If more values remain in inner_list, append a single SP to // output. // // 3. Append ")" to output. // // 4. Append the result of running Serializing Parameters // (Section 4.1.1.2) with list_parameters to output. // // 5. Return output. function serializeInnerList(value) { return `(${value.value.map(serializeItem).join(' ')})${serializeParams(value.params)}`; } // 4.1.2. Serializing a Dictionary // // Given an ordered Dictionary as input_dictionary (each member having a // member_name and a tuple value of (member_value, parameters)), return // an ASCII string suitable for use in a HTTP field value. // // 1. Let output be an empty string. // // 2. For each member_name with a value of (member_value, parameters) // in input_dictionary: // // 1. Append the result of running Serializing a Key // (Section 4.1.1.3) with member's member_name to output. // // 2. If member_value is Boolean true: // // 1. Append the result of running Serializing Parameters // (Section 4.1.1.2) with parameters to output. // // 3. Otherwise: // // 1. Append "=" to output. // // 2. If member_value is an array, append the result of running // Serializing an Inner List (Section 4.1.1.1) with // (member_value, parameters) to output. // // 3. Otherwise, append the result of running Serializing an // Item (Section 4.1.3) with (member_value, parameters) to // output. // // 4. If more members remain in input_dictionary: // // 1. Append "," to output. // // 2. Append a single SP to output. // // 3. Return output. function serializeDict(dict, options = { whitespace: true }) { if (typeof dict !== 'object') { throw serializeError(dict, DICT); } const entries = dict instanceof Map ? dict.entries() : Object.entries(dict); const optionalWhiteSpace = options != null && options.whitespace ? ' ' : ''; return Array.from(entries).map(([key, item]) => { if (item instanceof SfItem === false) { item = new SfItem(item); } let output = serializeKey(key); if (item.value === true) { output += serializeParams(item.params); } else { output += '='; if (Array.isArray(item.value)) { output += serializeInnerList(item); } else { output += serializeItem(item); } } return output; }).join(`,${optionalWhiteSpace}`); } /** * Encode an object into a structured field dictionary * * @param input - The structured field dictionary to encode * @returns The structured field string * * @group Structured Field * * @beta */ function encodeSfDict(value, options) { return serializeDict(value, options); } /** * Checks if the given key is a token field. * * @param key - The key to check. * * @returns `true` if the key is a token field. * * @internal * * @group CMCD */ const isTokenField = key => key === 'ot' || key === 'sf' || key === 'st'; const isValid = value => { if (typeof value === 'number') { return isFiniteNumber(value); } return value != null && value !== '' && value !== false; }; /** * Constructs a relative path from a URL. * * @param url - The destination URL * @param base - The base URL * @returns The relative path * * @group Utils * * @beta */ function urlToRelativePath(url, base) { const to = new URL(url); const from = new URL(base); if (to.origin !== from.origin) { return url; } const toPath = to.pathname.split('/').slice(1); const fromPath = from.pathname.split('/').slice(1, -1); // remove common parents while (toPath[0] === fromPath[0]) { toPath.shift(); fromPath.shift(); } // add back paths while (fromPath.length) { fromPath.shift(); toPath.unshift('..'); } return toPath.join('/'); } /** * Generate a random v4 UUID * * @returns A random v4 UUID * * @group Utils * * @beta */ function uuid() { try { return crypto.randomUUID(); } catch (error) { try { const url = URL.createObjectURL(new Blob()); const uuid = url.toString(); URL.revokeObjectURL(url); return uuid.slice(uuid.lastIndexOf('/') + 1); } catch (error) { let dt = new Date().getTime(); const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = (dt + Math.random() * 16) % 16 | 0; dt = Math.floor(dt / 16); return (c == 'x' ? r : r & 0x3 | 0x8).toString(16); }); return uuid; } } } const toRounded = value => Math.round(value); const toUrlSafe = (value, options) => { if (options != null && options.baseUrl) { value = urlToRelativePath(value, options.baseUrl); } return encodeURIComponent(value); }; const toHundred = value => toRounded(value / 100) * 100; /** * The default formatters for CMCD values. * * @group CMCD * * @beta */ const CmcdFormatters = { /** * Bitrate (kbps) rounded integer */ br: toRounded, /** * Duration (milliseconds) rounded integer */ d: toRounded, /** * Buffer Length (milliseconds) rounded nearest 100ms */ bl: toHundred, /** * Deadline (milliseconds) rounded nearest 100ms */ dl: toHundred, /** * Measured Throughput (kbps) rounded nearest 100kbps */ mtp: toHundred, /** * Next Object Request URL encoded */ nor: toUrlSafe, /** * Requested maximum throughput (kbps) rounded nearest 100kbps */ rtp: toHundred, /** * Top Bitrate (kbps) rounded integer */ tb: toRounded }; /** * Internal CMCD processing function. * * @param obj - The CMCD object to process. * @param map - The mapping function to use. * @param options - Options for encoding. * * @internal * * @group CMCD */ function processCmcd(obj, options) { const results = {}; if (obj == null || typeof obj !== 'object') { return results; } const keys = Object.keys(obj).sort(); const formatters = _extends({}, CmcdFormatters, options == null ? void 0 : options.formatters); const filter = options == null ? void 0 : options.filter; keys.forEach(key => { if (filter != null && filter(key)) { return; } let value = obj[key]; const formatter = formatters[key]; if (formatter) { value = formatter(value, options); } // Version should only be reported if not equal to 1. if (key === 'v' && value === 1) { return; } // Playback rate should only be sent if not equal to 1. if (key == 'pr' && value === 1) { return; } // ignore invalid values if (!isValid(value)) { return; } if (isTokenField(key) && typeof value === 'string') { value = new SfToken(value); } results[key] = value; }); return results; } /** * Encode a CMCD object to a string. * * @param cmcd - The CMCD object to encode. * @param options - Options for encoding. * * @returns The encoded CMCD string. * * @group CMCD * * @beta */ function encodeCmcd(cmcd, options = {}) { if (!cmcd) { return ''; } return encodeSfDict(processCmcd(cmcd, options), _extends({ whitespace: false }, options)); } /** * Convert a CMCD data object to request headers * * @param cmcd - The CMCD data object to convert. * @param options - Options for encoding the CMCD object. * * @returns The CMCD header shards. * * @group CMCD * * @beta */ function toCmcdHeaders(cmcd, options = {}) { if (!cmcd) { return {}; } const entries = Object.entries(cmcd); const headerMap = Object.entries(CmcdHeaderMap).concat(Object.entries((options == null ? void 0 : options.customHeaderMap) || {})); const shards = entries.reduce((acc, entry) => { var _headerMap$find, _acc$field; const [key, value] = entry; const field = ((_headerMap$find = headerMap.find(entry => entry[1].includes(key))) == null ? void 0 : _headerMap$find[0]) || CmcdHeaderField.REQUEST; (_acc$field = acc[field]) != null ? _acc$field : acc[field] = {}; acc[field][key] = value; return acc; }, {}); return Object.entries(shards).reduce((acc, [field, value]) => { acc[field] = encodeCmcd(value, options); return acc; }, {}); } /** * Append CMCD query args to a header object. * * @param headers - The headers to append to. * @param cmcd - The CMCD object to append. * @param customHeaderMap - A map of custom CMCD keys to header fields. * * @returns The headers with the CMCD header shards appended. * * @group CMCD * * @beta */ function appendCmcdHeaders(headers, cmcd, options) { return _extends(headers, toCmcdHeaders(cmcd, options)); } /** * CMCD parameter name. * * @group CMCD * * @beta */ const CMCD_PARAM = 'CMCD'; /** * Convert a CMCD data object to a query arg. * * @param cmcd - The CMCD object to convert. * @param options - Options for encoding the CMCD object. * * @returns The CMCD query arg. * * @group CMCD * * @beta */ function toCmcdQuery(cmcd, options = {}) { if (!cmcd) { return ''; } const params = encodeCmcd(cmcd, options); return `${CMCD_PARAM}=${encodeURIComponent(params)}`; } const REGEX = /CMCD=[^&#]+/; /** * Append CMCD query args to a URL. * * @param url - The URL to append to. * @param cmcd - The CMCD object to append. * @param options - Options for encoding the CMCD object. * * @returns The URL with the CMCD query args appended. * * @group CMCD * * @beta */ function appendCmcdQuery(url, cmcd, options) { // TODO: Replace with URLSearchParams once we drop Safari < 10.1 & Chrome < 49 support. // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams const query = toCmcdQuery(cmcd, options); if (!query) { return url; } if (REGEX.test(url)) { return url.replace(REGEX, query); } const separator = url.includes('?') ? '&' : '?'; return `${url}${separator}${query}`; } /** * Controller to deal with Common Media Client Data (CMCD) * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf */ class CMCDController { // eslint-disable-line no-restricted-globals constructor(hls) { this.hls = void 0; this.config = void 0; this.media = void 0; this.sid = void 0; this.cid = void 0; this.useHeaders = false; this.includeKeys = void 0; this.initialized = false; this.starved = false; this.buffering = true; this.audioBuffer = void 0; // eslint-disable-line no-restricted-globals this.videoBuffer = void 0; this.onWaiting = () => { if (this.initialized) { this.starved = true; } this.buffering = true; }; this.onPlaying = () => { if (!this.initialized) { this.initialized = true; } this.buffering = false; }; /** * Apply CMCD data to a manifest request. */ this.applyPlaylistData = context => { try { this.apply(context, { ot: CmObjectType.MANIFEST, su: !this.initialized }); } catch (error) { logger.warn('Could not generate manifest CMCD data.', error); } }; /** * Apply CMCD data to a segment request */ this.applyFragmentData = context => { try { const fragment = context.frag; const level = this.hls.levels[fragment.level]; const ot = this.getObjectType(fragment); const data = { d: fragment.duration * 1000, ot }; if (ot === CmObjectType.VIDEO || ot === CmObjectType.AUDIO || ot == CmObjectType.MUXED) { data.br = level.bitrate / 1000; data.tb = this.getTopBandwidth(ot) / 1000; data.bl = this.getBufferLength(ot); } this.apply(context, data); } catch (error) { logger.warn('Could not generate segment CMCD data.', error); } }; this.hls = hls; const config = this.config = hls.config; const { cmcd } = config; if (cmcd != null) { config.pLoader = this.createPlaylistLoader(); config.fLoader = this.createFragmentLoader(); this.sid = cmcd.sessionId || uuid(); this.cid = cmcd.contentId; this.useHeaders = cmcd.useHeaders === true; this.includeKeys = cmcd.includeKeys; this.registerListeners(); } } registerListeners() { const hls = this.hls; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); } unregisterListeners() { const hls = this.hls; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); } destroy() { this.unregisterListeners(); this.onMediaDetached(); // @ts-ignore this.hls = this.config = this.audioBuffer = this.videoBuffer = null; // @ts-ignore this.onWaiting = this.onPlaying = null; } onMediaAttached(event, data) { this.media = data.media; this.media.addEventListener('waiting', this.onWaiting); this.media.addEventListener('playing', this.onPlaying); } onMediaDetached() { if (!this.media) { return; } this.media.removeEventListener('waiting', this.onWaiting); this.media.removeEventListener('playing', this.onPlaying); // @ts-ignore this.media = null; } onBufferCreated(event, data) { var _data$tracks$audio, _data$tracks$video; this.audioBuffer = (_data$tracks$audio = data.tracks.audio) == null ? void 0 : _data$tracks$audio.buffer; this.videoBuffer = (_data$tracks$video = data.tracks.video) == null ? void 0 : _data$tracks$video.buffer; } /** * Create baseline CMCD data */ createData() { var _this$media; return { v: 1, sf: CmStreamingFormat.HLS, sid: this.sid, cid: this.cid, pr: (_this$media = this.media) == null ? void 0 : _this$media.playbackRate, mtp: this.hls.bandwidthEstimate / 1000 }; } /** * Apply CMCD data to a request. */ apply(context, data = {}) { // apply baseline data _extends(data, this.createData()); const isVideo = data.ot === CmObjectType.INIT || data.ot === CmObjectType.VIDEO || data.ot === CmObjectType.MUXED; if (this.starved && isVideo) { data.bs = true; data.su = true; this.starved = false; } if (data.su == null) { data.su = this.buffering; } // TODO: Implement rtp, nrr, nor, dl const { includeKeys } = this; if (includeKeys) { data = Object.keys(data).reduce((acc, key) => { includeKeys.includes(key) && (acc[key] = data[key]); return acc; }, {}); } if (this.useHeaders) { if (!context.headers) { context.headers = {}; } appendCmcdHeaders(context.headers, data); } else { context.url = appendCmcdQuery(context.url, data); } } /** * The CMCD object type. */ getObjectType(fragment) { const { type } = fragment; if (type === 'subtitle') { return CmObjectType.TIMED_TEXT; } if (fragment.sn === 'initSegment') { return CmObjectType.INIT; } if (type === 'audio') { return CmObjectType.AUDIO; } if (type === 'main') { if (!this.hls.audioTracks.length) { return CmObjectType.MUXED; } return CmObjectType.VIDEO; } return undefined; } /** * Get the highest bitrate. */ getTopBandwidth(type) { let bitrate = 0; let levels; const hls = this.hls; if (type === CmObjectType.AUDIO) { levels = hls.audioTracks; } else { const max = hls.maxAutoLevel; const len = max > -1 ? max + 1 : hls.levels.length; levels = hls.levels.slice(0, len); } for (const level of levels) { if (level.bitrate > bitrate) { bitrate = level.bitrate; } } return bitrate > 0 ? bitrate : NaN; } /** * Get the buffer length for a media type in milliseconds */ getBufferLength(type) { const media = this.hls.media; const buffer = type === CmObjectType.AUDIO ? this.audioBuffer : this.videoBuffer; if (!buffer || !media) { return NaN; } const info = BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole); return info.len * 1000; } /** * Create a playlist loader */ createPlaylistLoader() { const { pLoader } = this.config; const apply = this.applyPlaylistData; const Ctor = pLoader || this.config.loader; return class CmcdPlaylistLoader { constructor(config) { this.loader = void 0; this.loader = new Ctor(config); } get stats() { return this.loader.stats; } get context() { return this.loader.context; } destroy() { this.loader.destroy(); } abort() { this.loader.abort(); } load(context, config, callbacks) { apply(context); this.loader.load(context, config, callbacks); } }; } /** * Create a playlist loader */ createFragmentLoader() { const { fLoader } = this.config; const apply = this.applyFragmentData; const Ctor = fLoader || this.config.loader; return class CmcdFragmentLoader { constructor(config) { this.loader = void 0; this.loader = new Ctor(config); } get stats() { return this.loader.stats; } get context() { return this.loader.context; } destroy() { this.loader.destroy(); } abort() { this.loader.abort(); } load(context, config, callbacks) { apply(context); this.loader.load(context, config, callbacks); } }; } } const PATHWAY_PENALTY_DURATION_MS = 300000; class ContentSteeringController { constructor(hls) { this.hls = void 0; this.log = void 0; this.loader = null; this.uri = null; this.pathwayId = '.'; this.pathwayPriority = null; this.timeToLoad = 300; this.reloadTimer = -1; this.updated = 0; this.started = false; this.enabled = true; this.levels = null; this.audioTracks = null; this.subtitleTracks = null; this.penalizedPathways = {}; this.hls = hls; this.log = logger.log.bind(logger, `[content-steering]:`); this.registerListeners(); } registerListeners() { const hls = this.hls; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const hls = this.hls; if (!hls) { return; } hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.ERROR, this.onError, this); } startLoad() { this.started = true; this.clearTimeout(); if (this.enabled && this.uri) { if (this.updated) { const ttl = this.timeToLoad * 1000 - (performance.now() - this.updated); if (ttl > 0) { this.scheduleRefresh(this.uri, ttl); return; } } this.loadSteeringManifest(this.uri); } } stopLoad() { this.started = false; if (this.loader) { this.loader.destroy(); this.loader = null; } this.clearTimeout(); } clearTimeout() { if (this.reloadTimer !== -1) { self.clearTimeout(this.reloadTimer); this.reloadTimer = -1; } } destroy() { this.unregisterListeners(); this.stopLoad(); // @ts-ignore this.hls = null; this.levels = this.audioTracks = this.subtitleTracks = null; } removeLevel(levelToRemove) { const levels = this.levels; if (levels) { this.levels = levels.filter(level => level !== levelToRemove); } } onManifestLoading() { this.stopLoad(); this.enabled = true; this.timeToLoad = 300; this.updated = 0; this.uri = null; this.pathwayId = '.'; this.levels = this.audioTracks = this.subtitleTracks = null; } onManifestLoaded(event, data) { const { contentSteering } = data; if (contentSteering === null) { return; } this.pathwayId = contentSteering.pathwayId; this.uri = contentSteering.uri; if (this.started) { this.startLoad(); } } onManifestParsed(event, data) { this.audioTracks = data.audioTracks; this.subtitleTracks = data.subtitleTracks; } onError(event, data) { const { errorAction } = data; if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox && errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost) { const levels = this.levels; let pathwayPriority = this.pathwayPriority; let errorPathway = this.pathwayId; if (data.context) { const { groupId, pathwayId, type } = data.context; if (groupId && levels) { errorPathway = this.getPathwayForGroupId(groupId, type, errorPathway); } else if (pathwayId) { errorPathway = pathwayId; } } if (!(errorPathway in this.penalizedPathways)) { this.penalizedPathways[errorPathway] = performance.now(); } if (!pathwayPriority && levels) { // If PATHWAY-PRIORITY was not provided, list pathways for error handling pathwayPriority = levels.reduce((pathways, level) => { if (pathways.indexOf(level.pathwayId) === -1) { pathways.push(level.pathwayId); } return pathways; }, []); } if (pathwayPriority && pathwayPriority.length > 1) { this.updatePathwayPriority(pathwayPriority); errorAction.resolved = this.pathwayId !== errorPathway; } if (!errorAction.resolved) { logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`); } } } filterParsedLevels(levels) { // Filter levels to only include those that are in the initial pathway this.levels = levels; let pathwayLevels = this.getLevelsForPathway(this.pathwayId); if (pathwayLevels.length === 0) { const pathwayId = levels[0].pathwayId; this.log(`No levels found in Pathway ${this.pathwayId}. Setting initial Pathway to "${pathwayId}"`); pathwayLevels = this.getLevelsForPathway(pathwayId); this.pathwayId = pathwayId; } if (pathwayLevels.length !== levels.length) { this.log(`Found ${pathwayLevels.length}/${levels.length} levels in Pathway "${this.pathwayId}"`); return pathwayLevels; } return levels; } getLevelsForPathway(pathwayId) { if (this.levels === null) { return []; } return this.levels.filter(level => pathwayId === level.pathwayId); } updatePathwayPriority(pathwayPriority) { this.pathwayPriority = pathwayPriority; let levels; // Evaluate if we should remove the pathway from the penalized list const penalizedPathways = this.penalizedPathways; const now = performance.now(); Object.keys(penalizedPathways).forEach(pathwayId => { if (now - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS) { delete penalizedPathways[pathwayId]; } }); for (let i = 0; i < pathwayPriority.length; i++) { const pathwayId = pathwayPriority[i]; if (pathwayId in penalizedPathways) { continue; } if (pathwayId === this.pathwayId) { return; } const selectedIndex = this.hls.nextLoadLevel; const selectedLevel = this.hls.levels[selectedIndex]; levels = this.getLevelsForPathway(pathwayId); if (levels.length > 0) { this.log(`Setting Pathway to "${pathwayId}"`); this.pathwayId = pathwayId; reassignFragmentLevelIndexes(levels); this.hls.trigger(Events.LEVELS_UPDATED, { levels }); // Set LevelController's level to trigger LEVEL_SWITCHING which loads playlist if needed const levelAfterChange = this.hls.levels[selectedIndex]; if (selectedLevel && levelAfterChange && this.levels) { if (levelAfterChange.attrs['STABLE-VARIANT-ID'] !== selectedLevel.attrs['STABLE-VARIANT-ID'] && levelAfterChange.bitrate !== selectedLevel.bitrate) { this.log(`Unstable Pathways change from bitrate ${selectedLevel.bitrate} to ${levelAfterChange.bitrate}`); } this.hls.nextLoadLevel = selectedIndex; } break; } } } getPathwayForGroupId(groupId, type, defaultPathway) { const levels = this.getLevelsForPathway(defaultPathway).concat(this.levels || []); for (let i = 0; i < levels.length; i++) { if (type === PlaylistContextType.AUDIO_TRACK && levels[i].hasAudioGroup(groupId) || type === PlaylistContextType.SUBTITLE_TRACK && levels[i].hasSubtitleGroup(groupId)) { return levels[i].pathwayId; } } return defaultPathway; } clonePathways(pathwayClones) { const levels = this.levels; if (!levels) { return; } const audioGroupCloneMap = {}; const subtitleGroupCloneMap = {}; pathwayClones.forEach(pathwayClone => { const { ID: cloneId, 'BASE-ID': baseId, 'URI-REPLACEMENT': uriReplacement } = pathwayClone; if (levels.some(level => level.pathwayId === cloneId)) { return; } const clonedVariants = this.getLevelsForPathway(baseId).map(baseLevel => { const attributes = new AttrList(baseLevel.attrs); attributes['PATHWAY-ID'] = cloneId; const clonedAudioGroupId = attributes.AUDIO && `${attributes.AUDIO}_clone_${cloneId}`; const clonedSubtitleGroupId = attributes.SUBTITLES && `${attributes.SUBTITLES}_clone_${cloneId}`; if (clonedAudioGroupId) { audioGroupCloneMap[attributes.AUDIO] = clonedAudioGroupId; attributes.AUDIO = clonedAudioGroupId; } if (clonedSubtitleGroupId) { subtitleGroupCloneMap[attributes.SUBTITLES] = clonedSubtitleGroupId; attributes.SUBTITLES = clonedSubtitleGroupId; } const url = performUriReplacement(baseLevel.uri, attributes['STABLE-VARIANT-ID'], 'PER-VARIANT-URIS', uriReplacement); const clonedLevel = new Level({ attrs: attributes, audioCodec: baseLevel.audioCodec, bitrate: baseLevel.bitrate, height: baseLevel.height, name: baseLevel.name, url, videoCodec: baseLevel.videoCodec, width: baseLevel.width }); if (baseLevel.audioGroups) { for (let i = 1; i < baseLevel.audioGroups.length; i++) { clonedLevel.addGroupId('audio', `${baseLevel.audioGroups[i]}_clone_${cloneId}`); } } if (baseLevel.subtitleGroups) { for (let i = 1; i < baseLevel.subtitleGroups.length; i++) { clonedLevel.addGroupId('text', `${baseLevel.subtitleGroups[i]}_clone_${cloneId}`); } } return clonedLevel; }); levels.push(...clonedVariants); cloneRenditionGroups(this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId); cloneRenditionGroups(this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId); }); } loadSteeringManifest(uri) { const config = this.hls.config; const Loader = config.loader; if (this.loader) { this.loader.destroy(); } this.loader = new Loader(config); let url; try { url = new self.URL(uri); } catch (error) { this.enabled = false; this.log(`Failed to parse Steering Manifest URI: ${uri}`); return; } if (url.protocol !== 'data:') { const throughput = (this.hls.bandwidthEstimate || config.abrEwmaDefaultEstimate) | 0; url.searchParams.set('_HLS_pathway', this.pathwayId); url.searchParams.set('_HLS_throughput', '' + throughput); } const context = { responseType: 'json', url: url.href }; const loadPolicy = config.steeringManifestLoadPolicy.default; const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: legacyRetryCompatibility.maxNumRetry || 0, retryDelay: legacyRetryCompatibility.retryDelayMs || 0, maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 }; const callbacks = { onSuccess: (response, stats, context, networkDetails) => { this.log(`Loaded steering manifest: "${url}"`); const steeringData = response.data; if (steeringData.VERSION !== 1) { this.log(`Steering VERSION ${steeringData.VERSION} not supported!`); return; } this.updated = performance.now(); this.timeToLoad = steeringData.TTL; const { 'RELOAD-URI': reloadUri, 'PATHWAY-CLONES': pathwayClones, 'PATHWAY-PRIORITY': pathwayPriority } = steeringData; if (reloadUri) { try { this.uri = new self.URL(reloadUri, url).href; } catch (error) { this.enabled = false; this.log(`Failed to parse Steering Manifest RELOAD-URI: ${reloadUri}`); return; } } this.scheduleRefresh(this.uri || context.url); if (pathwayClones) { this.clonePathways(pathwayClones); } const loadedSteeringData = { steeringManifest: steeringData, url: url.toString() }; this.hls.trigger(Events.STEERING_MANIFEST_LOADED, loadedSteeringData); if (pathwayPriority) { this.updatePathwayPriority(pathwayPriority); } }, onError: (error, context, networkDetails, stats) => { this.log(`Error loading steering manifest: ${error.code} ${error.text} (${context.url})`); this.stopLoad(); if (error.code === 410) { this.enabled = false; this.log(`Steering manifest ${context.url} no longer available`); return; } let ttl = this.timeToLoad * 1000; if (error.code === 429) { const loader = this.loader; if (typeof (loader == null ? void 0 : loader.getResponseHeader) === 'function') { const retryAfter = loader.getResponseHeader('Retry-After'); if (retryAfter) { ttl = parseFloat(retryAfter) * 1000; } } this.log(`Steering manifest ${context.url} rate limited`); return; } this.scheduleRefresh(this.uri || context.url, ttl); }, onTimeout: (stats, context, networkDetails) => { this.log(`Timeout loading steering manifest (${context.url})`); this.scheduleRefresh(this.uri || context.url); } }; this.log(`Requesting steering manifest: ${url}`); this.loader.load(context, loaderConfig, callbacks); } scheduleRefresh(uri, ttlMs = this.timeToLoad * 1000) { this.clearTimeout(); this.reloadTimer = self.setTimeout(() => { var _this$hls; const media = (_this$hls = this.hls) == null ? void 0 : _this$hls.media; if (media && !media.ended) { this.loadSteeringManifest(uri); return; } this.scheduleRefresh(uri, this.timeToLoad * 1000); }, ttlMs); } } function cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId) { if (!tracks) { return; } Object.keys(groupCloneMap).forEach(audioGroupId => { const clonedTracks = tracks.filter(track => track.groupId === audioGroupId).map(track => { const clonedTrack = _extends({}, track); clonedTrack.details = undefined; clonedTrack.attrs = new AttrList(clonedTrack.attrs); clonedTrack.url = clonedTrack.attrs.URI = performUriReplacement(track.url, track.attrs['STABLE-RENDITION-ID'], 'PER-RENDITION-URIS', uriReplacement); clonedTrack.groupId = clonedTrack.attrs['GROUP-ID'] = groupCloneMap[audioGroupId]; clonedTrack.attrs['PATHWAY-ID'] = cloneId; return clonedTrack; }); tracks.push(...clonedTracks); }); } function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) { const { HOST: host, PARAMS: params, [perOptionKey]: perOptionUris } = uriReplacement; let perVariantUri; if (stableId) { perVariantUri = perOptionUris == null ? void 0 : perOptionUris[stableId]; if (perVariantUri) { uri = perVariantUri; } } const url = new self.URL(uri); if (host && !perVariantUri) { url.host = host; } if (params) { Object.keys(params).sort().forEach(key => { if (key) { url.searchParams.set(key, params[key]); } }); } return url.href; } const AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im; class XhrLoader { constructor(config) { this.xhrSetup = void 0; this.requestTimeout = void 0; this.retryTimeout = void 0; this.retryDelay = void 0; this.config = null; this.callbacks = null; this.context = null; this.loader = null; this.stats = void 0; this.xhrSetup = config ? config.xhrSetup || null : null; this.stats = new LoadStats(); this.retryDelay = 0; } destroy() { this.callbacks = null; this.abortInternal(); this.loader = null; this.config = null; this.context = null; this.xhrSetup = null; } abortInternal() { const loader = this.loader; self.clearTimeout(this.requestTimeout); self.clearTimeout(this.retryTimeout); if (loader) { loader.onreadystatechange = null; loader.onprogress = null; if (loader.readyState !== 4) { this.stats.aborted = true; loader.abort(); } } } abort() { var _this$callbacks; this.abortInternal(); if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { this.callbacks.onAbort(this.stats, this.context, this.loader); } } load(context, config, callbacks) { if (this.stats.loading.start) { throw new Error('Loader can only be used once.'); } this.stats.loading.start = self.performance.now(); this.context = context; this.config = config; this.callbacks = callbacks; this.loadInternal(); } loadInternal() { const { config, context } = this; if (!config || !context) { return; } const xhr = this.loader = new self.XMLHttpRequest(); const stats = this.stats; stats.loading.first = 0; stats.loaded = 0; stats.aborted = false; const xhrSetup = this.xhrSetup; if (xhrSetup) { Promise.resolve().then(() => { if (this.loader !== xhr || this.stats.aborted) return; return xhrSetup(xhr, context.url); }).catch(error => { if (this.loader !== xhr || this.stats.aborted) return; xhr.open('GET', context.url, true); return xhrSetup(xhr, context.url); }).then(() => { if (this.loader !== xhr || this.stats.aborted) return; this.openAndSendXhr(xhr, context, config); }).catch(error => { // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS this.callbacks.onError({ code: xhr.status, text: error.message }, context, xhr, stats); return; }); } else { this.openAndSendXhr(xhr, context, config); } } openAndSendXhr(xhr, context, config) { if (!xhr.readyState) { xhr.open('GET', context.url, true); } const headers = context.headers; const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy; if (headers) { for (const header in headers) { xhr.setRequestHeader(header, headers[header]); } } if (context.rangeEnd) { xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1)); } xhr.onreadystatechange = this.readystatechange.bind(this); xhr.onprogress = this.loadprogress.bind(this); xhr.responseType = context.responseType; // setup timeout before we perform request self.clearTimeout(this.requestTimeout); config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout); xhr.send(); } readystatechange() { const { context, loader: xhr, stats } = this; if (!context || !xhr) { return; } const readyState = xhr.readyState; const config = this.config; // don't proceed if xhr has been aborted if (stats.aborted) { return; } // >= HEADERS_RECEIVED if (readyState >= 2) { if (stats.loading.first === 0) { stats.loading.first = Math.max(self.performance.now(), stats.loading.start); // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet if (config.timeout !== config.loadPolicy.maxLoadTimeMs) { self.clearTimeout(this.requestTimeout); config.timeout = config.loadPolicy.maxLoadTimeMs; this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start)); } } if (readyState === 4) { self.clearTimeout(this.requestTimeout); xhr.onreadystatechange = null; xhr.onprogress = null; const status = xhr.status; // http status between 200 to 299 are all successful const useResponse = xhr.responseType !== 'text'; if (status >= 200 && status < 300 && (useResponse && xhr.response || xhr.responseText !== null)) { stats.loading.end = Math.max(self.performance.now(), stats.loading.first); const data = useResponse ? xhr.response : xhr.responseText; const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length; stats.loaded = stats.total = len; stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first); if (!this.callbacks) { return; } const onProgress = this.callbacks.onProgress; if (onProgress) { onProgress(stats, context, data, xhr); } if (!this.callbacks) { return; } const response = { url: xhr.responseURL, data: data, code: status }; this.callbacks.onSuccess(response, stats, context, xhr); } else { const retryConfig = config.loadPolicy.errorRetry; const retryCount = stats.retry; // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error const response = { url: context.url, data: undefined, code: status }; if (shouldRetry(retryConfig, retryCount, false, response)) { this.retry(retryConfig); } else { logger.error(`${status} while loading ${context.url}`); this.callbacks.onError({ code: status, text: xhr.statusText }, context, xhr, stats); } } } } } loadtimeout() { if (!this.config) return; const retryConfig = this.config.loadPolicy.timeoutRetry; const retryCount = this.stats.retry; if (shouldRetry(retryConfig, retryCount, true)) { this.retry(retryConfig); } else { var _this$context; logger.warn(`timeout while loading ${(_this$context = this.context) == null ? void 0 : _this$context.url}`); const callbacks = this.callbacks; if (callbacks) { this.abortInternal(); callbacks.onTimeout(this.stats, this.context, this.loader); } } } retry(retryConfig) { const { context, stats } = this; this.retryDelay = getRetryDelay(retryConfig, stats.retry); stats.retry++; logger.warn(`${status ? 'HTTP Status ' + status : 'Timeout'} while loading ${context == null ? void 0 : context.url}, retrying ${stats.retry}/${retryConfig.maxNumRetry} in ${this.retryDelay}ms`); // abort and reset internal state this.abortInternal(); this.loader = null; // schedule retry self.clearTimeout(this.retryTimeout); this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay); } loadprogress(event) { const stats = this.stats; stats.loaded = event.loaded; if (event.lengthComputable) { stats.total = event.total; } } getCacheAge() { let result = null; if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) { const ageHeader = this.loader.getResponseHeader('age'); result = ageHeader ? parseFloat(ageHeader) : null; } return result; } getResponseHeader(name) { if (this.loader && new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, 'im').test(this.loader.getAllResponseHeaders())) { return this.loader.getResponseHeader(name); } return null; } } function fetchSupported() { if ( // @ts-ignore self.fetch && self.AbortController && self.ReadableStream && self.Request) { try { new self.ReadableStream({}); // eslint-disable-line no-new return true; } catch (e) { /* noop */ } } return false; } const BYTERANGE = /(\d+)-(\d+)\/(\d+)/; class FetchLoader { constructor(config /* HlsConfig */) { this.fetchSetup = void 0; this.requestTimeout = void 0; this.request = null; this.response = null; this.controller = void 0; this.context = null; this.config = null; this.callbacks = null; this.stats = void 0; this.loader = null; this.fetchSetup = config.fetchSetup || getRequest; this.controller = new self.AbortController(); this.stats = new LoadStats(); } destroy() { this.loader = this.callbacks = this.context = this.config = this.request = null; this.abortInternal(); this.response = null; // @ts-ignore this.fetchSetup = this.controller = this.stats = null; } abortInternal() { if (this.controller && !this.stats.loading.end) { this.stats.aborted = true; this.controller.abort(); } } abort() { var _this$callbacks; this.abortInternal(); if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { this.callbacks.onAbort(this.stats, this.context, this.response); } } load(context, config, callbacks) { const stats = this.stats; if (stats.loading.start) { throw new Error('Loader can only be used once.'); } stats.loading.start = self.performance.now(); const initParams = getRequestParameters(context, this.controller.signal); const onProgress = callbacks.onProgress; const isArrayBuffer = context.responseType === 'arraybuffer'; const LENGTH = isArrayBuffer ? 'byteLength' : 'length'; const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy; this.context = context; this.config = config; this.callbacks = callbacks; this.request = this.fetchSetup(context, initParams); self.clearTimeout(this.requestTimeout); config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; this.requestTimeout = self.setTimeout(() => { this.abortInternal(); callbacks.onTimeout(stats, context, this.response); }, config.timeout); self.fetch(this.request).then(response => { this.response = this.loader = response; const first = Math.max(self.performance.now(), stats.loading.start); self.clearTimeout(this.requestTimeout); config.timeout = maxLoadTimeMs; this.requestTimeout = self.setTimeout(() => { this.abortInternal(); callbacks.onTimeout(stats, context, this.response); }, maxLoadTimeMs - (first - stats.loading.start)); if (!response.ok) { const { status, statusText } = response; throw new FetchError(statusText || 'fetch, bad network response', status, response); } stats.loading.first = first; stats.total = getContentLength(response.headers) || stats.total; if (onProgress && isFiniteNumber(config.highWaterMark)) { return this.loadProgressively(response, stats, context, config.highWaterMark, onProgress); } if (isArrayBuffer) { return response.arrayBuffer(); } if (context.responseType === 'json') { return response.json(); } return response.text(); }).then(responseData => { const response = this.response; if (!response) { throw new Error('loader destroyed'); } self.clearTimeout(this.requestTimeout); stats.loading.end = Math.max(self.performance.now(), stats.loading.first); const total = responseData[LENGTH]; if (total) { stats.loaded = stats.total = total; } const loaderResponse = { url: response.url, data: responseData, code: response.status }; if (onProgress && !isFiniteNumber(config.highWaterMark)) { onProgress(stats, context, responseData, response); } callbacks.onSuccess(loaderResponse, stats, context, response); }).catch(error => { self.clearTimeout(this.requestTimeout); if (stats.aborted) { return; } // CORS errors result in an undefined code. Set it to 0 here to align with XHR's behavior // when destroying, 'error' itself can be undefined const code = !error ? 0 : error.code || 0; const text = !error ? null : error.message; callbacks.onError({ code, text }, context, error ? error.details : null, stats); }); } getCacheAge() { let result = null; if (this.response) { const ageHeader = this.response.headers.get('age'); result = ageHeader ? parseFloat(ageHeader) : null; } return result; } getResponseHeader(name) { return this.response ? this.response.headers.get(name) : null; } loadProgressively(response, stats, context, highWaterMark = 0, onProgress) { const chunkCache = new ChunkCache(); const reader = response.body.getReader(); const pump = () => { return reader.read().then(data => { if (data.done) { if (chunkCache.dataLength) { onProgress(stats, context, chunkCache.flush(), response); } return Promise.resolve(new ArrayBuffer(0)); } const chunk = data.value; const len = chunk.length; stats.loaded += len; if (len < highWaterMark || chunkCache.dataLength) { // The current chunk is too small to to be emitted or the cache already has data // Push it to the cache chunkCache.push(chunk); if (chunkCache.dataLength >= highWaterMark) { // flush in order to join the typed arrays onProgress(stats, context, chunkCache.flush(), response); } } else { // If there's nothing cached already, and the chache is large enough // just emit the progress event onProgress(stats, context, chunk, response); } return pump(); }).catch(() => { /* aborted */ return Promise.reject(); }); }; return pump(); } } function getRequestParameters(context, signal) { const initParams = { method: 'GET', mode: 'cors', credentials: 'same-origin', signal, headers: new self.Headers(_extends({}, context.headers)) }; if (context.rangeEnd) { initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1)); } return initParams; } function getByteRangeLength(byteRangeHeader) { const result = BYTERANGE.exec(byteRangeHeader); if (result) { return parseInt(result[2]) - parseInt(result[1]) + 1; } } function getContentLength(headers) { const contentRange = headers.get('Content-Range'); if (contentRange) { const byteRangeLength = getByteRangeLength(contentRange); if (isFiniteNumber(byteRangeLength)) { return byteRangeLength; } } const contentLength = headers.get('Content-Length'); if (contentLength) { return parseInt(contentLength); } } function getRequest(context, initParams) { return new self.Request(context.url, initParams); } class FetchError extends Error { constructor(message, code, details) { super(message); this.code = void 0; this.details = void 0; this.code = code; this.details = details; } } const WHITESPACE_CHAR = /\s/; const Cues = { newCue(track, startTime, endTime, captionScreen) { const result = []; let row; // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers let cue; let indenting; let indent; let text; const Cue = self.VTTCue || self.TextTrackCue; for (let r = 0; r < captionScreen.rows.length; r++) { row = captionScreen.rows[r]; indenting = true; indent = 0; text = ''; if (!row.isEmpty()) { var _track$cues; for (let c = 0; c < row.chars.length; c++) { if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) { indent++; } else { text += row.chars[c].uchar; indenting = false; } } // To be used for cleaning-up orphaned roll-up captions row.cueStartTime = startTime; // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE if (startTime === endTime) { endTime += 0.0001; } if (indent >= 16) { indent--; } else { indent++; } const cueText = fixLineBreaks(text.trim()); const id = generateCueId(startTime, endTime, cueText); // If this cue already exists in the track do not push it if (!(track != null && (_track$cues = track.cues) != null && _track$cues.getCueById(id))) { cue = new Cue(startTime, endTime, cueText); cue.id = id; cue.line = r + 1; cue.align = 'left'; // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code) // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608 // Firefox throws an exception and captions break with out of bounds 0-100 values cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10); result.push(cue); } } } if (track && result.length) { // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome result.sort((cueA, cueB) => { if (cueA.line === 'auto' || cueB.line === 'auto') { return 0; } if (cueA.line > 8 && cueB.line > 8) { return cueB.line - cueA.line; } return cueA.line - cueB.line; }); result.forEach(cue => addCueToTrack(track, cue)); } return result; } }; /** * @deprecated use fragLoadPolicy.default */ /** * @deprecated use manifestLoadPolicy.default and playlistLoadPolicy.default */ const defaultLoadPolicy = { maxTimeToFirstByteMs: 8000, maxLoadTimeMs: 20000, timeoutRetry: null, errorRetry: null }; /** * @ignore * If possible, keep hlsDefaultConfig shallow * It is cloned whenever a new Hls instance is created, by keeping the config * shallow the properties are cloned, and we don't end up manipulating the default */ const hlsDefaultConfig = _objectSpread2(_objectSpread2({ autoStartLoad: true, // used by stream-controller startPosition: -1, // used by stream-controller defaultAudioCodec: undefined, // used by stream-controller debug: false, // used by logger capLevelOnFPSDrop: false, // used by fps-controller capLevelToPlayerSize: false, // used by cap-level-controller ignoreDevicePixelRatio: false, // used by cap-level-controller preferManagedMediaSource: true, initialLiveManifestSize: 1, // used by stream-controller maxBufferLength: 30, // used by stream-controller backBufferLength: Infinity, // used by buffer-controller frontBufferFlushThreshold: Infinity, maxBufferSize: 60 * 1000 * 1000, // used by stream-controller maxBufferHole: 0.1, // used by stream-controller highBufferWatchdogPeriod: 2, // used by stream-controller nudgeOffset: 0.1, // used by stream-controller nudgeMaxRetry: 3, // used by stream-controller maxFragLookUpTolerance: 0.25, // used by stream-controller liveSyncDurationCount: 3, // used by latency-controller liveMaxLatencyDurationCount: Infinity, // used by latency-controller liveSyncDuration: undefined, // used by latency-controller liveMaxLatencyDuration: undefined, // used by latency-controller maxLiveSyncPlaybackRate: 1, // used by latency-controller liveDurationInfinity: false, // used by buffer-controller /** * @deprecated use backBufferLength */ liveBackBufferLength: null, // used by buffer-controller maxMaxBufferLength: 600, // used by stream-controller enableWorker: true, // used by transmuxer workerPath: null, // used by transmuxer enableSoftwareAES: true, // used by decrypter startLevel: undefined, // used by level-controller startFragPrefetch: false, // used by stream-controller fpsDroppedMonitoringPeriod: 5000, // used by fps-controller fpsDroppedMonitoringThreshold: 0.2, // used by fps-controller appendErrorMaxRetry: 3, // used by buffer-controller loader: XhrLoader, // loader: FetchLoader, fLoader: undefined, // used by fragment-loader pLoader: undefined, // used by playlist-loader xhrSetup: undefined, // used by xhr-loader licenseXhrSetup: undefined, // used by eme-controller licenseResponseCallback: undefined, // used by eme-controller abrController: AbrController, bufferController: BufferController, capLevelController: CapLevelController, errorController: ErrorController, fpsController: FPSController, stretchShortVideoTrack: false, // used by mp4-remuxer maxAudioFramesDrift: 1, // used by mp4-remuxer forceKeyFrameOnDiscontinuity: true, // used by ts-demuxer abrEwmaFastLive: 3, // used by abr-controller abrEwmaSlowLive: 9, // used by abr-controller abrEwmaFastVoD: 3, // used by abr-controller abrEwmaSlowVoD: 9, // used by abr-controller abrEwmaDefaultEstimate: 5e5, // 500 kbps // used by abr-controller abrEwmaDefaultEstimateMax: 5e6, // 5 mbps abrBandWidthFactor: 0.95, // used by abr-controller abrBandWidthUpFactor: 0.7, // used by abr-controller abrMaxWithRealBitrate: false, // used by abr-controller maxStarvationDelay: 4, // used by abr-controller maxLoadingDelay: 4, // used by abr-controller minAutoBitrate: 0, // used by hls emeEnabled: false, // used by eme-controller widevineLicenseUrl: undefined, // used by eme-controller drmSystems: {}, // used by eme-controller drmSystemOptions: {}, // used by eme-controller requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess , // used by eme-controller testBandwidth: true, progressive: false, lowLatencyMode: true, cmcd: undefined, enableDateRangeMetadataCues: true, enableEmsgMetadataCues: true, enableID3MetadataCues: true, useMediaCapabilities: true, certLoadPolicy: { default: defaultLoadPolicy }, keyLoadPolicy: { default: { maxTimeToFirstByteMs: 8000, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 1, retryDelayMs: 1000, maxRetryDelayMs: 20000, backoff: 'linear' }, errorRetry: { maxNumRetry: 8, retryDelayMs: 1000, maxRetryDelayMs: 20000, backoff: 'linear' } } }, manifestLoadPolicy: { default: { maxTimeToFirstByteMs: Infinity, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 1, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, playlistLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 2, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, fragLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 120000, timeoutRetry: { maxNumRetry: 4, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 6, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, steeringManifestLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 20000, timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 1, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, // These default settings are deprecated in favor of the above policies // and are maintained for backwards compatibility manifestLoadingTimeOut: 10000, manifestLoadingMaxRetry: 1, manifestLoadingRetryDelay: 1000, manifestLoadingMaxRetryTimeout: 64000, levelLoadingTimeOut: 10000, levelLoadingMaxRetry: 4, levelLoadingRetryDelay: 1000, levelLoadingMaxRetryTimeout: 64000, fragLoadingTimeOut: 20000, fragLoadingMaxRetry: 6, fragLoadingRetryDelay: 1000, fragLoadingMaxRetryTimeout: 64000 }, timelineConfig()), {}, { subtitleStreamController: SubtitleStreamController , subtitleTrackController: SubtitleTrackController , timelineController: TimelineController , audioStreamController: AudioStreamController , audioTrackController: AudioTrackController , emeController: EMEController , cmcdController: CMCDController , contentSteeringController: ContentSteeringController }); function timelineConfig() { return { cueHandler: Cues, // used by timeline-controller enableWebVTT: true, // used by timeline-controller enableIMSC1: true, // used by timeline-controller enableCEA708Captions: true, // used by timeline-controller captionsTextTrack1Label: 'English', // used by timeline-controller captionsTextTrack1LanguageCode: 'en', // used by timeline-controller captionsTextTrack2Label: 'Spanish', // used by timeline-controller captionsTextTrack2LanguageCode: 'es', // used by timeline-controller captionsTextTrack3Label: 'Unknown CC', // used by timeline-controller captionsTextTrack3LanguageCode: '', // used by timeline-controller captionsTextTrack4Label: 'Unknown CC', // used by timeline-controller captionsTextTrack4LanguageCode: '', // used by timeline-controller renderTextTracksNatively: true }; } /** * @ignore */ function mergeConfig(defaultConfig, userConfig) { if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) { throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration"); } if (userConfig.liveMaxLatencyDurationCount !== undefined && (userConfig.liveSyncDurationCount === undefined || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) { throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"'); } if (userConfig.liveMaxLatencyDuration !== undefined && (userConfig.liveSyncDuration === undefined || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) { throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"'); } const defaultsCopy = deepCpy(defaultConfig); // Backwards compatibility with deprecated config values const deprecatedSettingTypes = ['manifest', 'level', 'frag']; const deprecatedSettings = ['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout']; deprecatedSettingTypes.forEach(type => { const policyName = `${type === 'level' ? 'playlist' : type}LoadPolicy`; const policyNotSet = userConfig[policyName] === undefined; const report = []; deprecatedSettings.forEach(setting => { const deprecatedSetting = `${type}Loading${setting}`; const value = userConfig[deprecatedSetting]; if (value !== undefined && policyNotSet) { report.push(deprecatedSetting); const settings = defaultsCopy[policyName].default; userConfig[policyName] = { default: settings }; switch (setting) { case 'TimeOut': settings.maxLoadTimeMs = value; settings.maxTimeToFirstByteMs = value; break; case 'MaxRetry': settings.errorRetry.maxNumRetry = value; settings.timeoutRetry.maxNumRetry = value; break; case 'RetryDelay': settings.errorRetry.retryDelayMs = value; settings.timeoutRetry.retryDelayMs = value; break; case 'MaxRetryTimeout': settings.errorRetry.maxRetryDelayMs = value; settings.timeoutRetry.maxRetryDelayMs = value; break; } } }); if (report.length) { logger.warn(`hls.js config: "${report.join('", "')}" setting(s) are deprecated, use "${policyName}": ${JSON.stringify(userConfig[policyName])}`); } }); return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig); } function deepCpy(obj) { if (obj && typeof obj === 'object') { if (Array.isArray(obj)) { return obj.map(deepCpy); } return Object.keys(obj).reduce((result, key) => { result[key] = deepCpy(obj[key]); return result; }, {}); } return obj; } /** * @ignore */ function enableStreamingMode(config) { const currentLoader = config.loader; if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) { // If a developer has configured their own loader, respect that choice logger.log('[config]: Custom loader detected, cannot enable progressive streaming'); config.progressive = false; } else { const canStreamProgressively = fetchSupported(); if (canStreamProgressively) { config.loader = FetchLoader; config.progressive = true; config.enableSoftwareAES = true; logger.log('[config]: Progressive streaming enabled, using FetchLoader'); } } } let chromeOrFirefox; class LevelController extends BasePlaylistController { constructor(hls, contentSteeringController) { super(hls, '[level-controller]'); this._levels = []; this._firstLevel = -1; this._maxAutoLevel = -1; this._startLevel = void 0; this.currentLevel = null; this.currentLevelIndex = -1; this.manualLevelIndex = -1; this.steering = void 0; this.onParsedComplete = void 0; this.steering = contentSteeringController; this._registerListeners(); } _registerListeners() { const { hls } = this; hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.on(Events.ERROR, this.onError, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); hls.off(Events.ERROR, this.onError, this); } destroy() { this._unregisterListeners(); this.steering = null; this.resetLevels(); super.destroy(); } stopLoad() { const levels = this._levels; // clean up live level details to force reload them, and reset load errors levels.forEach(level => { level.loadError = 0; level.fragmentError = 0; }); super.stopLoad(); } resetLevels() { this._startLevel = undefined; this.manualLevelIndex = -1; this.currentLevelIndex = -1; this.currentLevel = null; this._levels = []; this._maxAutoLevel = -1; } onManifestLoading(event, data) { this.resetLevels(); } onManifestLoaded(event, data) { const preferManagedMediaSource = this.hls.config.preferManagedMediaSource; const levels = []; const redundantSet = {}; const generatePathwaySet = {}; let resolutionFound = false; let videoCodecFound = false; let audioCodecFound = false; data.levels.forEach(levelParsed => { var _audioCodec, _videoCodec; const attributes = levelParsed.attrs; // erase audio codec info if browser does not support mp4a.40.34. // demuxer will autodetect codec and fallback to mpeg/audio let { audioCodec, videoCodec } = levelParsed; if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) { chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent)); if (chromeOrFirefox) { levelParsed.audioCodec = audioCodec = undefined; } } if (audioCodec) { levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource); } if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) { videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec); } // only keep levels with supported audio/video codecs const { width, height, unknownCodecs } = levelParsed; resolutionFound || (resolutionFound = !!(width && height)); videoCodecFound || (videoCodecFound = !!videoCodec); audioCodecFound || (audioCodecFound = !!audioCodec); if (unknownCodecs != null && unknownCodecs.length || audioCodec && !areCodecsMediaSourceSupported(audioCodec, 'audio', preferManagedMediaSource) || videoCodec && !areCodecsMediaSourceSupported(videoCodec, 'video', preferManagedMediaSource)) { return; } const { CODECS, 'FRAME-RATE': FRAMERATE, 'HDCP-LEVEL': HDCP, 'PATHWAY-ID': PATHWAY, RESOLUTION, 'VIDEO-RANGE': VIDEO_RANGE } = attributes; const contentSteeringPrefix = `${PATHWAY || '.'}-`; const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`; if (!redundantSet[levelKey]) { const level = new Level(levelParsed); redundantSet[levelKey] = level; generatePathwaySet[levelKey] = 1; levels.push(level); } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs['PATHWAY-ID']) { // Assign Pathway IDs to Redundant Streams (default Pathways is ".". Redundant Streams "..", "...", and so on.) // Content Steering controller to handles Pathway fallback on error const pathwayCount = generatePathwaySet[levelKey] += 1; levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.'); const level = new Level(levelParsed); redundantSet[levelKey] = level; levels.push(level); } else { redundantSet[levelKey].addGroupId('audio', attributes.AUDIO); redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES); } }); this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound); } filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound) { let audioTracks = []; let subtitleTracks = []; let levels = filteredLevels; // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled if ((resolutionFound || videoCodecFound) && audioCodecFound) { levels = levels.filter(({ videoCodec, videoRange, width, height }) => (!!videoCodec || !!(width && height)) && isVideoRange(videoRange)); } if (levels.length === 0) { // Dispatch error after MANIFEST_LOADED is done propagating Promise.resolve().then(() => { if (this.hls) { if (data.levels.length) { this.warn(`One or more CODECS in variant not supported: ${JSON.stringify(data.levels[0].attrs)}`); } const error = new Error('no level with compatible codecs found in manifest'); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR, fatal: true, url: data.url, error, reason: error.message }); } }); return; } if (data.audioTracks) { const { preferManagedMediaSource } = this.hls.config; audioTracks = data.audioTracks.filter(track => !track.audioCodec || areCodecsMediaSourceSupported(track.audioCodec, 'audio', preferManagedMediaSource)); // Assign ids after filtering as array indices by group-id assignTrackIdsByGroup(audioTracks); } if (data.subtitles) { subtitleTracks = data.subtitles; assignTrackIdsByGroup(subtitleTracks); } // start bitrate is the first bitrate of the manifest const unsortedLevels = levels.slice(0); // sort levels from lowest to highest levels.sort((a, b) => { if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) { return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '') ? 1 : -1; } // sort on height before bitrate for cap-level-controller if (resolutionFound && a.height !== b.height) { return a.height - b.height; } if (a.frameRate !== b.frameRate) { return a.frameRate - b.frameRate; } if (a.videoRange !== b.videoRange) { return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange); } if (a.videoCodec !== b.videoCodec) { const valueA = videoCodecPreferenceValue(a.videoCodec); const valueB = videoCodecPreferenceValue(b.videoCodec); if (valueA !== valueB) { return valueB - valueA; } } if (a.uri === b.uri && a.codecSet !== b.codecSet) { const valueA = codecsSetSelectionPreferenceValue(a.codecSet); const valueB = codecsSetSelectionPreferenceValue(b.codecSet); if (valueA !== valueB) { return valueB - valueA; } } if (a.averageBitrate !== b.averageBitrate) { return a.averageBitrate - b.averageBitrate; } return 0; }); let firstLevelInPlaylist = unsortedLevels[0]; if (this.steering) { levels = this.steering.filterParsedLevels(levels); if (levels.length !== unsortedLevels.length) { for (let i = 0; i < unsortedLevels.length; i++) { if (unsortedLevels[i].pathwayId === levels[0].pathwayId) { firstLevelInPlaylist = unsortedLevels[i]; break; } } } } this._levels = levels; // find index of first level in sorted levels for (let i = 0; i < levels.length; i++) { if (levels[i] === firstLevelInPlaylist) { var _this$hls$userConfig; this._firstLevel = i; const firstLevelBitrate = firstLevelInPlaylist.bitrate; const bandwidthEstimate = this.hls.bandwidthEstimate; this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`); // Update default bwe to first variant bitrate as long it has not been configured or set if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === undefined) { const startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax); if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === hlsDefaultConfig.abrEwmaDefaultEstimate) { this.hls.bandwidthEstimate = startingBwEstimate; } } break; } } // Audio is only alternate if manifest include a URI along with the audio group tag, // and this is not an audio-only stream where levels contain audio-only const audioOnly = audioCodecFound && !videoCodecFound; const edata = { levels, audioTracks, subtitleTracks, sessionData: data.sessionData, sessionKeys: data.sessionKeys, firstLevel: this._firstLevel, stats: data.stats, audio: audioCodecFound, video: videoCodecFound, altAudio: !audioOnly && audioTracks.some(t => !!t.url) }; this.hls.trigger(Events.MANIFEST_PARSED, edata); // Initiate loading after all controllers have received MANIFEST_PARSED if (this.hls.config.autoStartLoad || this.hls.forceStartLoad) { this.hls.startLoad(this.hls.config.startPosition); } } get levels() { if (this._levels.length === 0) { return null; } return this._levels; } get level() { return this.currentLevelIndex; } set level(newLevel) { const levels = this._levels; if (levels.length === 0) { return; } // check if level idx is valid if (newLevel < 0 || newLevel >= levels.length) { // invalid level id given, trigger error const error = new Error('invalid level idx'); const fatal = newLevel < 0; this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.LEVEL_SWITCH_ERROR, level: newLevel, fatal, error, reason: error.message }); if (fatal) { return; } newLevel = Math.min(newLevel, levels.length - 1); } const lastLevelIndex = this.currentLevelIndex; const lastLevel = this.currentLevel; const lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined; const level = levels[newLevel]; const pathwayId = level.attrs['PATHWAY-ID']; this.currentLevelIndex = newLevel; this.currentLevel = level; if (lastLevelIndex === newLevel && level.details && lastLevel && lastPathwayId === pathwayId) { return; } this.log(`Switching to level ${newLevel} (${level.height ? level.height + 'p ' : ''}${level.videoRange ? level.videoRange + ' ' : ''}${level.codecSet ? level.codecSet + ' ' : ''}@${level.bitrate})${pathwayId ? ' with Pathway ' + pathwayId : ''} from level ${lastLevelIndex}${lastPathwayId ? ' with Pathway ' + lastPathwayId : ''}`); const levelSwitchingData = { level: newLevel, attrs: level.attrs, details: level.details, bitrate: level.bitrate, averageBitrate: level.averageBitrate, maxBitrate: level.maxBitrate, realBitrate: level.realBitrate, width: level.width, height: level.height, codecSet: level.codecSet, audioCodec: level.audioCodec, videoCodec: level.videoCodec, audioGroups: level.audioGroups, subtitleGroups: level.subtitleGroups, loaded: level.loaded, loadError: level.loadError, fragmentError: level.fragmentError, name: level.name, id: level.id, uri: level.uri, url: level.url, urlId: 0, audioGroupIds: level.audioGroupIds, textGroupIds: level.textGroupIds }; this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData); // check if we need to load playlist for this level const levelDetails = level.details; if (!levelDetails || levelDetails.live) { // level not retrieved yet, or live playlist we need to (re)load it const hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails); this.loadPlaylist(hlsUrlParameters); } } get manualLevel() { return this.manualLevelIndex; } set manualLevel(newLevel) { this.manualLevelIndex = newLevel; if (this._startLevel === undefined) { this._startLevel = newLevel; } if (newLevel !== -1) { this.level = newLevel; } } get firstLevel() { return this._firstLevel; } set firstLevel(newLevel) { this._firstLevel = newLevel; } get startLevel() { // Setting hls.startLevel (this._startLevel) overrides config.startLevel if (this._startLevel === undefined) { const configStartLevel = this.hls.config.startLevel; if (configStartLevel !== undefined) { return configStartLevel; } return this.hls.firstAutoLevel; } return this._startLevel; } set startLevel(newLevel) { this._startLevel = newLevel; } onError(event, data) { if (data.fatal || !data.context) { return; } if (data.context.type === PlaylistContextType.LEVEL && data.context.level === this.level) { this.checkRetry(data); } } // reset errors on the successful load of a fragment onFragBuffered(event, { frag }) { if (frag !== undefined && frag.type === PlaylistLevelType.MAIN) { const el = frag.elementaryStreams; if (!Object.keys(el).some(type => !!el[type])) { return; } const level = this._levels[frag.level]; if (level != null && level.loadError) { this.log(`Resetting level error count of ${level.loadError} on frag buffered`); level.loadError = 0; } } } onLevelLoaded(event, data) { var _data$deliveryDirecti2; const { level, details } = data; const curLevel = this._levels[level]; if (!curLevel) { var _data$deliveryDirecti; this.warn(`Invalid level index ${level}`); if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) { details.deltaUpdateFailed = true; } return; } // only process level loaded events matching with expected level if (level === this.currentLevelIndex) { // reset level load error counter on successful level loaded only if there is no issues with fragments if (curLevel.fragmentError === 0) { curLevel.loadError = 0; } this.playlistLoaded(level, data, curLevel.details); } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) { // received a delta playlist update that cannot be merged details.deltaUpdateFailed = true; } } loadPlaylist(hlsUrlParameters) { super.loadPlaylist(); const currentLevelIndex = this.currentLevelIndex; const currentLevel = this.currentLevel; if (currentLevel && this.shouldLoadPlaylist(currentLevel)) { let url = currentLevel.uri; if (hlsUrlParameters) { try { url = hlsUrlParameters.addDirectives(url); } catch (error) { this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`); } } const pathwayId = currentLevel.attrs['PATHWAY-ID']; this.log(`Loading level index ${currentLevelIndex}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part : ''} with${pathwayId ? ' Pathway ' + pathwayId : ''} ${url}`); // console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId); // console.log('New video quality level audio group id:', levelObject.attrs.AUDIO, level); this.clearTimer(); this.hls.trigger(Events.LEVEL_LOADING, { url, level: currentLevelIndex, pathwayId: currentLevel.attrs['PATHWAY-ID'], id: 0, // Deprecated Level urlId deliveryDirectives: hlsUrlParameters || null }); } } get nextLoadLevel() { if (this.manualLevelIndex !== -1) { return this.manualLevelIndex; } else { return this.hls.nextAutoLevel; } } set nextLoadLevel(nextLevel) { this.level = nextLevel; if (this.manualLevelIndex === -1) { this.hls.nextAutoLevel = nextLevel; } } removeLevel(levelIndex) { var _this$currentLevel; const levels = this._levels.filter((level, index) => { if (index !== levelIndex) { return true; } if (this.steering) { this.steering.removeLevel(level); } if (level === this.currentLevel) { this.currentLevel = null; this.currentLevelIndex = -1; if (level.details) { level.details.fragments.forEach(f => f.level = -1); } } return false; }); reassignFragmentLevelIndexes(levels); this._levels = levels; if (this.currentLevelIndex > -1 && (_this$currentLevel = this.currentLevel) != null && _this$currentLevel.details) { this.currentLevelIndex = this.currentLevel.details.fragments[0].level; } this.hls.trigger(Events.LEVELS_UPDATED, { levels }); } onLevelsUpdated(event, { levels }) { this._levels = levels; } checkMaxAutoUpdated() { const { autoLevelCapping, maxAutoLevel, maxHdcpLevel } = this.hls; if (this._maxAutoLevel !== maxAutoLevel) { this._maxAutoLevel = maxAutoLevel; this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, { autoLevelCapping, levels: this.levels, maxAutoLevel, minAutoLevel: this.hls.minAutoLevel, maxHdcpLevel }); } } } function assignTrackIdsByGroup(tracks) { const groups = {}; tracks.forEach(track => { const groupId = track.groupId || ''; track.id = groups[groupId] = groups[groupId] || 0; groups[groupId]++; }); } class KeyLoader { constructor(config) { this.config = void 0; this.keyUriToKeyInfo = {}; this.emeController = null; this.config = config; } abort(type) { for (const uri in this.keyUriToKeyInfo) { const loader = this.keyUriToKeyInfo[uri].loader; if (loader) { var _loader$context; if (type && type !== ((_loader$context = loader.context) == null ? void 0 : _loader$context.frag.type)) { return; } loader.abort(); } } } detach() { for (const uri in this.keyUriToKeyInfo) { const keyInfo = this.keyUriToKeyInfo[uri]; // Remove cached EME keys on detach if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) { delete this.keyUriToKeyInfo[uri]; } } } destroy() { this.detach(); for (const uri in this.keyUriToKeyInfo) { const loader = this.keyUriToKeyInfo[uri].loader; if (loader) { loader.destroy(); } } this.keyUriToKeyInfo = {}; } createKeyLoadError(frag, details = ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response) { return new LoadError({ type: ErrorTypes.NETWORK_ERROR, details, fatal: false, frag, response, error, networkDetails }); } loadClear(loadingFrag, encryptedFragments) { if (this.emeController && this.config.emeEnabled) { // access key-system with nearest key on start (loaidng frag is unencrypted) const { sn, cc } = loadingFrag; for (let i = 0; i < encryptedFragments.length; i++) { const frag = encryptedFragments[i]; if (cc <= frag.cc && (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)) { this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => { frag.setKeyFormat(keySystemFormat); }); break; } } } } load(frag) { if (!frag.decryptdata && frag.encrypted && this.emeController) { // Multiple keys, but none selected, resolve in eme-controller return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => { return this.loadInternal(frag, keySystemFormat); }); } return this.loadInternal(frag); } loadInternal(frag, keySystemFormat) { var _keyInfo, _keyInfo2; if (keySystemFormat) { frag.setKeyFormat(keySystemFormat); } const decryptdata = frag.decryptdata; if (!decryptdata) { const error = new Error(keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}` : 'Missing decryption data on fragment in onKeyLoading'); return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error)); } const uri = decryptdata.uri; if (!uri) { return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: "${uri}"`))); } let keyInfo = this.keyUriToKeyInfo[uri]; if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) { decryptdata.key = keyInfo.decryptdata.key; return Promise.resolve({ frag, keyInfo }); } // Return key load promise as long as it does not have a mediakey session with an unusable key status if ((_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) { var _keyInfo$mediaKeySess; switch ((_keyInfo$mediaKeySess = keyInfo.mediaKeySessionContext) == null ? void 0 : _keyInfo$mediaKeySess.keyStatus) { case undefined: case 'status-pending': case 'usable': case 'usable-in-future': return keyInfo.keyLoadPromise.then(keyLoadedData => { // Return the correct fragment with updated decryptdata key and loaded keyInfo decryptdata.key = keyLoadedData.keyInfo.decryptdata.key; return { frag, keyInfo }; }); } // If we have a key session and status and it is not pending or usable, continue // This will go back to the eme-controller for expired keys to get a new keyLoadPromise } // Load the key or return the loading promise keyInfo = this.keyUriToKeyInfo[uri] = { decryptdata, keyLoadPromise: null, loader: null, mediaKeySessionContext: null }; switch (decryptdata.method) { case 'ISO-23001-7': case 'SAMPLE-AES': case 'SAMPLE-AES-CENC': case 'SAMPLE-AES-CTR': if (decryptdata.keyFormat === 'identity') { // loadKeyHTTP handles http(s) and data URLs return this.loadKeyHTTP(keyInfo, frag); } return this.loadKeyEME(keyInfo, frag); case 'AES-128': return this.loadKeyHTTP(keyInfo, frag); default: return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`))); } } loadKeyEME(keyInfo, frag) { const keyLoadedData = { frag, keyInfo }; if (this.emeController && this.config.emeEnabled) { const keySessionContextPromise = this.emeController.loadKey(keyLoadedData); if (keySessionContextPromise) { return (keyInfo.keyLoadPromise = keySessionContextPromise.then(keySessionContext => { keyInfo.mediaKeySessionContext = keySessionContext; return keyLoadedData; })).catch(error => { // Remove promise for license renewal or retry keyInfo.keyLoadPromise = null; throw error; }); } } return Promise.resolve(keyLoadedData); } loadKeyHTTP(keyInfo, frag) { const config = this.config; const Loader = config.loader; const keyLoader = new Loader(config); frag.keyLoader = keyInfo.loader = keyLoader; return keyInfo.keyLoadPromise = new Promise((resolve, reject) => { const loaderContext = { keyInfo, frag, responseType: 'arraybuffer', url: keyInfo.decryptdata.uri }; // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times, // key-loader will trigger an error and rely on stream-controller to handle retry logic. // this will also align retry logic with fragment-loader const loadPolicy = config.keyLoadPolicy.default; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { const { frag, keyInfo, url: uri } = context; if (!frag.decryptdata || keyInfo !== this.keyUriToKeyInfo[uri]) { return reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails)); } keyInfo.decryptdata.key = frag.decryptdata.key = new Uint8Array(response.data); // detach fragment key loader on load success frag.keyLoader = null; keyInfo.loader = null; resolve({ frag, keyInfo }); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}`), networkDetails, _objectSpread2({ url: loaderContext.url, data: undefined }, response))); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails)); }, onAbort: (stats, context, networkDetails) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails)); } }; keyLoader.load(loaderContext, loaderConfig, loaderCallbacks); }); } resetLoader(context) { const { frag, keyInfo, url: uri } = context; const loader = keyInfo.loader; if (frag.keyLoader === loader) { frag.keyLoader = null; keyInfo.loader = null; } delete this.keyUriToKeyInfo[uri]; if (loader) { loader.destroy(); } } } function getSourceBuffer() { return self.SourceBuffer || self.WebKitSourceBuffer; } function isMSESupported() { const mediaSource = getMediaSource(); if (!mediaSource) { return false; } // if SourceBuffer is exposed ensure its API is valid // Older browsers do not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible const sourceBuffer = getSourceBuffer(); return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function'; } function isSupported() { if (!isMSESupported()) { return false; } const mediaSource = getMediaSource(); return typeof (mediaSource == null ? void 0 : mediaSource.isTypeSupported) === 'function' && (['avc1.42E01E,mp4a.40.2', 'av01.0.01M.08', 'vp09.00.50.08'].some(codecsForVideoContainer => mediaSource.isTypeSupported(mimeTypeForCodec(codecsForVideoContainer, 'video'))) || ['mp4a.40.2', 'fLaC'].some(codecForAudioContainer => mediaSource.isTypeSupported(mimeTypeForCodec(codecForAudioContainer, 'audio')))); } function changeTypeSupported() { var _sourceBuffer$prototy; const sourceBuffer = getSourceBuffer(); return typeof (sourceBuffer == null ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === 'function'; } const STALL_MINIMUM_DURATION_MS = 250; const MAX_START_GAP_JUMP = 2.0; const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1; const SKIP_BUFFER_RANGE_START = 0.05; class GapController { constructor(config, media, fragmentTracker, hls) { this.config = void 0; this.media = null; this.fragmentTracker = void 0; this.hls = void 0; this.nudgeRetry = 0; this.stallReported = false; this.stalled = null; this.moved = false; this.seeking = false; this.config = config; this.media = media; this.fragmentTracker = fragmentTracker; this.hls = hls; } destroy() { this.media = null; // @ts-ignore this.hls = this.fragmentTracker = null; } /** * Checks if the playhead is stuck within a gap, and if so, attempts to free it. * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range). * * @param lastCurrentTime - Previously read playhead position */ poll(lastCurrentTime, activeFrag) { const { config, media, stalled } = this; if (media === null) { return; } const { currentTime, seeking } = media; const seeked = this.seeking && !seeking; const beginSeek = !this.seeking && seeking; this.seeking = seeking; // The playhead is moving, no-op if (currentTime !== lastCurrentTime) { this.moved = true; if (!seeking) { this.nudgeRetry = 0; } if (stalled !== null) { // The playhead is now moving, but was previously stalled if (this.stallReported) { const _stalledDuration = self.performance.now() - stalled; logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`); this.stallReported = false; } this.stalled = null; } return; } // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek if (beginSeek || seeked) { this.stalled = null; return; } // The playhead should not be moving if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) { this.nudgeRetry = 0; return; } const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const nextStart = bufferInfo.nextStart || 0; if (seeking) { // Waiting for seeking in a buffered range to complete const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP; // Next buffered range is too far ahead to jump to while still seeking const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime); if (hasEnoughBuffer || noBufferGap) { return; } // Reset moved state when seeking to a point in or before a gap this.moved = false; } // Skip start gaps if we haven't played, but the last poll detected the start of a stall // The addition poll gives the browser a chance to jump the gap for us if (!this.moved && this.stalled !== null) { var _level$details; // There is no playable buffer (seeked, waiting for buffer) const isBuffered = bufferInfo.len > 0; if (!isBuffered && !nextStart) { return; } // Jump start gaps within jump threshold const startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime; // When joining a live stream with audio tracks, account for live playlist window sliding by allowing // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment // that begins over 1 target duration after the video start position. const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null; const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live; const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP; const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime); if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) { if (!media.paused) { this._trySkipBufferHole(partialOrGap); } return; } } // Start tracking stall time const tnow = self.performance.now(); if (stalled === null) { this.stalled = tnow; return; } const stalledDuration = tnow - stalled; if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) { // Report stalling after trying to fix this._reportStall(bufferInfo); if (!this.media) { return; } } const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole); this._tryFixBufferStall(bufferedWithHoles, stalledDuration); } /** * Detects and attempts to fix known buffer stalling issues. * @param bufferInfo - The properties of the current buffer. * @param stalledDurationMs - The amount of time Hls.js has been stalling for. * @private */ _tryFixBufferStall(bufferInfo, stalledDurationMs) { const { config, fragmentTracker, media } = this; if (media === null) { return; } const currentTime = media.currentTime; const partial = fragmentTracker.getPartialFragment(currentTime); if (partial) { // Try to skip over the buffer hole caused by a partial fragment // This method isn't limited by the size of the gap between buffered ranges const targetTime = this._trySkipBufferHole(partial); // we return here in this case, meaning // the branch below only executes when we haven't seeked to a new position if (targetTime || !this.media) { return; } } // if we haven't had to skip over a buffer hole of a partial fragment // we may just have to "nudge" the playlist as the browser decoding/rendering engine // needs to cross some sort of threshold covering all source-buffers content // to start playing properly. if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) { logger.warn('Trying to nudge playhead over buffer-hole'); // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds // We only try to jump the hole if it's under the configured size // Reset stalled so to rearm watchdog timer this.stalled = null; this._tryNudgeBuffer(); } } /** * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period. * @param bufferLen - The playhead distance from the end of the current buffer segment. * @private */ _reportStall(bufferInfo) { const { hls, media, stallReported } = this; if (!stallReported && media) { // Report stalled error once this.stallReported = true; const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`); logger.warn(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, fatal: false, error, buffer: bufferInfo.len }); } } /** * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments * @param partial - The partial fragment found at the current time (where playback is stalling). * @private */ _trySkipBufferHole(partial) { const { config, hls, media } = this; if (media === null) { return 0; } // Check if currentTime is between unbuffered regions of partial fragments const currentTime = media.currentTime; const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart; if (startTime) { const bufferStarved = bufferInfo.len <= config.maxBufferHole; const waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3; const gapLength = startTime - currentTime; if (gapLength > 0 && (bufferStarved || waiting)) { // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial if (gapLength > config.maxBufferHole) { const { fragmentTracker } = this; let startGap = false; if (currentTime === 0) { const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN); if (startFrag && startTime < startFrag.end) { startGap = true; } } if (!startGap) { const startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN); if (startProvisioned) { let moreToLoad = false; let pos = startProvisioned.end; while (pos < startTime) { const provisioned = fragmentTracker.getPartialFragment(pos); if (provisioned) { pos += provisioned.duration; } else { moreToLoad = true; break; } } if (moreToLoad) { return 0; } } } } const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS); logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`); this.moved = true; this.stalled = null; media.currentTime = targetTime; if (partial && !partial.gap) { const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, error, reason: error.message, frag: partial }); } return targetTime; } } return 0; } /** * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount. * @private */ _tryNudgeBuffer() { const { config, hls, media, nudgeRetry } = this; if (media === null) { return; } const currentTime = media.currentTime; this.nudgeRetry++; if (nudgeRetry < config.nudgeMaxRetry) { const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset; // playback stalled in buffered area ... let's nudge currentTime to try to overcome this const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`); logger.warn(error.message); media.currentTime = targetTime; hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_NUDGE_ON_STALL, error, fatal: false }); } else { const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`); logger.error(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, error, fatal: true }); } } } const TICK_INTERVAL = 100; // how often to tick in ms class StreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN); this.audioCodecSwap = false; this.gapController = null; this.level = -1; this._forceStartLoad = false; this.altAudio = false; this.audioOnly = false; this.fragPlaying = null; this.onvplaying = null; this.onvseeked = null; this.fragLastKbps = 0; this.couldBacktrack = false; this.backtrackFragment = null; this.audioCodecSwitch = false; this.videoBuffer = null; this._registerListeners(); } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); } onHandlerDestroying() { this._unregisterListeners(); super.onHandlerDestroying(); } startLoad(startPosition) { if (this.levels) { const { lastCurrentTime, hls } = this; this.stopLoad(); this.setInterval(TICK_INTERVAL); this.level = -1; if (!this.startFragRequested) { // determine load level let startLevel = hls.startLevel; if (startLevel === -1) { if (hls.config.testBandwidth && this.levels.length > 1) { // -1 : guess start Level by doing a bitrate test by loading first fragment of lowest quality level startLevel = 0; this.bitrateTest = true; } else { startLevel = hls.firstAutoLevel; } } // set new level to playlist loader : this will trigger start level load // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded hls.nextLoadLevel = startLevel; this.level = hls.loadLevel; this.loadedmetadata = false; } // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime if (lastCurrentTime > 0 && startPosition === -1) { this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`); startPosition = lastCurrentTime; } this.state = State.IDLE; this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; this.tick(); } else { this._forceStartLoad = true; this.state = State.STOPPED; } } stopLoad() { this._forceStartLoad = false; super.stopLoad(); } doTick() { switch (this.state) { case State.WAITING_LEVEL: { const { levels, level } = this; const currentLevel = levels == null ? void 0 : levels[level]; const details = currentLevel == null ? void 0 : currentLevel.details; if (details && (!details.live || this.levelLastLoaded === currentLevel)) { if (this.waitForCdnTuneIn(details)) { break; } this.state = State.IDLE; break; } else if (this.hls.nextLoadLevel !== this.level) { this.state = State.IDLE; break; } break; } case State.FRAG_LOADING_WAITING_RETRY: { var _this$media; const now = self.performance.now(); const retryDate = this.retryDate; // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { const { levels, level } = this; const currentLevel = levels == null ? void 0 : levels[level]; this.resetStartWhenNotLoaded(currentLevel || null); this.state = State.IDLE; } } break; } if (this.state === State.IDLE) { this.doTickIdle(); } this.onTickEnd(); } onTickEnd() { super.onTickEnd(); this.checkBuffer(); this.checkFragmentChanged(); } doTickIdle() { const { hls, levelLastLoaded, levels, media } = this; // if start level not parsed yet OR // if video not attached AND start fragment already requested OR start frag prefetch not enabled // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) { return; } // If the "main" level is audio-only but we are loading an alternate track in the same group, do not load anything if (this.altAudio && this.audioOnly) { return; } const level = hls.nextLoadLevel; if (!(levels != null && levels[level])) { return; } const levelInfo = levels[level]; // if buffer length is less than maxBufLen try to load a new fragment const bufferInfo = this.getMainFwdBufferInfo(); if (bufferInfo === null) { return; } const lastDetails = this.getLevelDetails(); if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) { const data = {}; if (this.altAudio) { data.type = 'video'; } this.hls.trigger(Events.BUFFER_EOS, data); this.state = State.ENDED; return; } // set next load level : this will trigger a playlist load if needed if (hls.loadLevel !== level && hls.manualLevel === -1) { this.log(`Adapting to level ${level} from level ${this.level}`); } this.level = hls.nextLoadLevel = level; const levelDetails = levelInfo.details; // if level info not retrieved yet, switch state and wait for level retrieval // if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load // a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist) if (!levelDetails || this.state === State.WAITING_LEVEL || levelDetails.live && this.levelLastLoaded !== levelInfo) { this.level = level; this.state = State.WAITING_LEVEL; return; } const bufferLen = bufferInfo.len; // compute max Buffer Length that we could get from this load level, based on level bitrate. don't buffer more than 60 MB and more than 30s const maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate); // Stay idle if we are still with buffer margins if (bufferLen >= maxBufLen) { return; } if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) { this.backtrackFragment = null; } const targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end; let frag = this.getNextFragment(targetBufferTime, levelDetails); // Avoid backtracking by loading an earlier segment in streams with segments that do not start with a key frame (flagged by `couldBacktrack`) if (this.couldBacktrack && !this.fragPrevious && frag && frag.sn !== 'initSegment' && this.fragmentTracker.getState(frag) !== FragmentState.OK) { var _this$backtrackFragme; const backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn; const fragIdx = backtrackSn - levelDetails.startSN; const backtrackFrag = levelDetails.fragments[fragIdx - 1]; if (backtrackFrag && frag.cc === backtrackFrag.cc) { frag = backtrackFrag; this.fragmentTracker.removeFragment(backtrackFrag); } } else if (this.backtrackFragment && bufferInfo.len) { this.backtrackFragment = null; } // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags if (frag && this.isLoopLoading(frag, targetBufferTime)) { const gapStart = frag.gap; if (!gapStart) { // Cleanup the fragment tracker before trying to find the next unbuffered fragment const type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO; const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; if (mediaBuffer) { this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); } } frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen); } if (!frag) { return; } if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) { frag = frag.initSegment; } this.loadFragment(frag, levelInfo, targetBufferTime); } loadFragment(frag, level, targetBufferTime) { // Check if fragment is not loaded const fragState = this.fragmentTracker.getState(frag); this.fragCurrent = frag; if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { if (frag.sn === 'initSegment') { this._loadInitSegment(frag, level); } else if (this.bitrateTest) { this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`); this._loadBitrateTestFrag(frag, level); } else { this.startFragRequested = true; super.loadFragment(frag, level, targetBufferTime); } } else { this.clearTrackerIfNeeded(frag); } } getBufferedFrag(position) { return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN); } followingBufferedFrag(frag) { if (frag) { // try to get range of next fragment (500ms after this range) return this.getBufferedFrag(frag.end + 0.5); } return null; } /* on immediate level switch : - pause playback if playing - cancel any pending load request - and trigger a buffer flush */ immediateLevelSwitch() { this.abortCurrentFrag(); this.flushMainBuffer(0, Number.POSITIVE_INFINITY); } /** * try to switch ASAP without breaking video playback: * in order to ensure smooth but quick level switching, * we need to find the next flushable buffer range * we should take into account new segment fetch time */ nextLevelSwitch() { const { levels, media } = this; // ensure that media is defined and that metadata are available (to retrieve currentTime) if (media != null && media.readyState) { let fetchdelay; const fragPlayingCurrent = this.getAppendedFrag(media.currentTime); if (fragPlayingCurrent && fragPlayingCurrent.start > 1) { // flush buffer preceding current fragment (flush until current fragment start offset) // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ... this.flushMainBuffer(0, fragPlayingCurrent.start - 1); } const levelDetails = this.getLevelDetails(); if (levelDetails != null && levelDetails.live) { const bufferInfo = this.getMainFwdBufferInfo(); // Do not flush in live stream with low buffer if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) { return; } } if (!media.paused && levels) { // add a safety delay of 1s const nextLevelId = this.hls.nextLoadLevel; const nextLevel = levels[nextLevelId]; const fragLastKbps = this.fragLastKbps; if (fragLastKbps && this.fragCurrent) { fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1000 * fragLastKbps) + 1; } else { fetchdelay = 0; } } else { fetchdelay = 0; } // this.log('fetchdelay:'+fetchdelay); // find buffer range that will be reached once new fragment will be fetched const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay); if (bufferedFrag) { // we can flush buffer range following this one without stalling playback const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag); if (nextBufferedFrag) { // if we are here, we can also cancel any loading/demuxing in progress, as they are useless this.abortCurrentFrag(); // start flush position is in next buffered frag. Leave some padding for non-independent segments and smoother playback. const maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start; const fragDuration = nextBufferedFrag.duration; const startPts = Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * (this.couldBacktrack ? 0.5 : 0.125)), fragDuration * (this.couldBacktrack ? 0.75 : 0.25))); this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY); } } } } abortCurrentFrag() { const fragCurrent = this.fragCurrent; this.fragCurrent = null; this.backtrackFragment = null; if (fragCurrent) { fragCurrent.abortRequests(); this.fragmentTracker.removeFragment(fragCurrent); } switch (this.state) { case State.KEY_LOADING: case State.FRAG_LOADING: case State.FRAG_LOADING_WAITING_RETRY: case State.PARSING: case State.PARSED: this.state = State.IDLE; break; } this.nextLoadPosition = this.getLoadPosition(); } flushMainBuffer(startOffset, endOffset) { super.flushMainBuffer(startOffset, endOffset, this.altAudio ? 'video' : null); } onMediaAttached(event, data) { super.onMediaAttached(event, data); const media = data.media; this.onvplaying = this.onMediaPlaying.bind(this); this.onvseeked = this.onMediaSeeked.bind(this); media.addEventListener('playing', this.onvplaying); media.addEventListener('seeked', this.onvseeked); this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls); } onMediaDetaching() { const { media } = this; if (media && this.onvplaying && this.onvseeked) { media.removeEventListener('playing', this.onvplaying); media.removeEventListener('seeked', this.onvseeked); this.onvplaying = this.onvseeked = null; this.videoBuffer = null; } this.fragPlaying = null; if (this.gapController) { this.gapController.destroy(); this.gapController = null; } super.onMediaDetaching(); } onMediaPlaying() { // tick to speed up FRAG_CHANGED triggering this.tick(); } onMediaSeeked() { const media = this.media; const currentTime = media ? media.currentTime : null; if (isFiniteNumber(currentTime)) { this.log(`Media seeked to ${currentTime.toFixed(3)}`); } // If seeked was issued before buffer was appended do not tick immediately const bufferInfo = this.getMainFwdBufferInfo(); if (bufferInfo === null || bufferInfo.len === 0) { this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`); return; } // tick to speed up FRAG_CHANGED triggering this.tick(); } onManifestLoading() { // reset buffer on manifest loading this.log('Trigger BUFFER_RESET'); this.hls.trigger(Events.BUFFER_RESET, undefined); this.fragmentTracker.removeAllFragments(); this.couldBacktrack = false; this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0; this.levels = this.fragPlaying = this.backtrackFragment = this.levelLastLoaded = null; this.altAudio = this.audioOnly = this.startFragRequested = false; } onManifestParsed(event, data) { // detect if we have different kind of audio codecs used amongst playlists let aac = false; let heaac = false; data.levels.forEach(level => { const codec = level.audioCodec; if (codec) { aac = aac || codec.indexOf('mp4a.40.2') !== -1; heaac = heaac || codec.indexOf('mp4a.40.5') !== -1; } }); this.audioCodecSwitch = aac && heaac && !changeTypeSupported(); if (this.audioCodecSwitch) { this.log('Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC'); } this.levels = data.levels; this.startFragRequested = false; } onLevelLoading(event, data) { const { levels } = this; if (!levels || this.state !== State.IDLE) { return; } const level = levels[data.level]; if (!level.details || level.details.live && this.levelLastLoaded !== level || this.waitForCdnTuneIn(level.details)) { this.state = State.WAITING_LEVEL; } } onLevelLoaded(event, data) { var _curLevel$details; const { levels } = this; const newLevelId = data.level; const newDetails = data.details; const duration = newDetails.totalduration; if (!levels) { this.warn(`Levels were reset while loading level ${newLevelId}`); return; } this.log(`Level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''}, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`); const curLevel = levels[newLevelId]; const fragCurrent = this.fragCurrent; if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) { if (fragCurrent.level !== data.level && fragCurrent.loader) { this.abortCurrentFrag(); } } let sliding = 0; if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) { var _this$levelLastLoaded; this.checkLiveUpdate(newDetails); if (newDetails.deltaUpdateFailed) { return; } sliding = this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details); } // override level info curLevel.details = newDetails; this.levelLastLoaded = curLevel; this.hls.trigger(Events.LEVEL_UPDATED, { details: newDetails, level: newLevelId }); // only switch back to IDLE state if we were waiting for level to start downloading a new fragment if (this.state === State.WAITING_LEVEL) { if (this.waitForCdnTuneIn(newDetails)) { // Wait for Low-Latency CDN Tune-in return; } this.state = State.IDLE; } if (!this.startFragRequested) { this.setStartPosition(newDetails, sliding); } else if (newDetails.live) { this.synchronizeToLiveEdge(newDetails); } // trigger handler right now this.tick(); } _handleFragmentLoadProgress(data) { var _frag$initSegment; const { frag, part, payload } = data; const { levels } = this; if (!levels) { this.warn(`Levels were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`); return; } const currentLevel = levels[frag.level]; const details = currentLevel.details; if (!details) { this.warn(`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`); this.fragmentTracker.removeFragment(frag); return; } const videoCodec = currentLevel.videoCodec; // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) const accurateTimeOffset = details.PTSKnown || !details.live; const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; const audioCodec = this._getAudioCodec(currentLevel); // transmux the MPEG-TS data to ISO-BMFF segments // this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`); const transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); const partIndex = part ? part.index : -1; const partial = partIndex !== -1; const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); const initPTS = this.initPTS[frag.cc]; transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); } onAudioTrackSwitching(event, data) { // if any URL found on new audio track, it is an alternate audio track const fromAltAudio = this.altAudio; const altAudio = !!data.url; // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered // don't do anything if we switch to alt audio: audio stream controller is handling it. // we will just have to change buffer scheduling on audioTrackSwitched if (!altAudio) { if (this.mediaBuffer !== this.media) { this.log('Switching on main audio, use media.buffered to schedule main fragment loading'); this.mediaBuffer = this.media; const fragCurrent = this.fragCurrent; // we need to refill audio buffer from main: cancel any frag loading to speed up audio switch if (fragCurrent) { this.log('Switching to main audio track, cancel main fragment load'); fragCurrent.abortRequests(); this.fragmentTracker.removeFragment(fragCurrent); } // destroy transmuxer to force init segment generation (following audio switch) this.resetTransmuxer(); // switch to IDLE state to load new fragment this.resetLoadingState(); } else if (this.audioOnly) { // Reset audio transmuxer so when switching back to main audio we're not still appending where we left off this.resetTransmuxer(); } const hls = this.hls; // If switching from alt to main audio, flush all audio and trigger track switched if (fromAltAudio) { hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: null }); this.fragmentTracker.removeAllFragments(); } hls.trigger(Events.AUDIO_TRACK_SWITCHED, data); } } onAudioTrackSwitched(event, data) { const trackId = data.id; const altAudio = !!this.hls.audioTracks[trackId].url; if (altAudio) { const videoBuffer = this.videoBuffer; // if we switched on alternate audio, ensure that main fragment scheduling is synced with video sourcebuffer buffered if (videoBuffer && this.mediaBuffer !== videoBuffer) { this.log('Switching on alternate audio, use video.buffered to schedule main fragment loading'); this.mediaBuffer = videoBuffer; } } this.altAudio = altAudio; this.tick(); } onBufferCreated(event, data) { const tracks = data.tracks; let mediaTrack; let name; let alternate = false; for (const type in tracks) { const track = tracks[type]; if (track.id === 'main') { name = type; mediaTrack = track; // keep video source buffer reference if (type === 'video') { const videoTrack = tracks[type]; if (videoTrack) { this.videoBuffer = videoTrack.buffer; } } } else { alternate = true; } } if (alternate && mediaTrack) { this.log(`Alternate track found, use ${name}.buffered to schedule main fragment loading`); this.mediaBuffer = mediaTrack.buffer; } else { this.mediaBuffer = this.media; } } onFragBuffered(event, data) { const { frag, part } = data; if (frag && frag.type !== PlaylistLevelType.MAIN) { return; } if (this.fragContextChanged(frag)) { // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion // Avoid setting state back to IDLE, since that will interfere with a level switch this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index : ''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}`); if (this.state === State.PARSED) { this.state = State.IDLE; } return; } const stats = part ? part.stats : frag.stats; this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first)); if (frag.sn !== 'initSegment') { this.fragPrevious = frag; } this.fragBufferedComplete(frag, part); } onError(event, data) { var _data$context; if (data.fatal) { this.state = State.ERROR; return; } switch (data.details) { case ErrorDetails.FRAG_GAP: case ErrorDetails.FRAG_PARSING_ERROR: case ErrorDetails.FRAG_DECRYPT_ERROR: case ErrorDetails.FRAG_LOAD_ERROR: case ErrorDetails.FRAG_LOAD_TIMEOUT: case ErrorDetails.KEY_LOAD_ERROR: case ErrorDetails.KEY_LOAD_TIMEOUT: this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data); break; case ErrorDetails.LEVEL_LOAD_ERROR: case ErrorDetails.LEVEL_LOAD_TIMEOUT: case ErrorDetails.LEVEL_PARSING_ERROR: // in case of non fatal error while loading level, if level controller is not retrying to load level, switch back to IDLE if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.LEVEL) { this.state = State.IDLE; } break; case ErrorDetails.BUFFER_APPEND_ERROR: case ErrorDetails.BUFFER_FULL_ERROR: if (!data.parent || data.parent !== 'main') { return; } if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) { this.resetLoadingState(); return; } if (this.reduceLengthAndFlushBuffer(data)) { this.flushMainBuffer(0, Number.POSITIVE_INFINITY); } break; case ErrorDetails.INTERNAL_EXCEPTION: this.recoverWorkerError(data); break; } } // Checks the health of the buffer and attempts to resolve playback stalls. checkBuffer() { const { media, gapController } = this; if (!media || !gapController || !media.readyState) { // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0) return; } if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) { // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null; gapController.poll(this.lastCurrentTime, activeFrag); } this.lastCurrentTime = media.currentTime; } onFragLoadEmergencyAborted() { this.state = State.IDLE; // if loadedmetadata is not set, it means that we are emergency switch down on first frag // in that case, reset startFragRequested flag if (!this.loadedmetadata) { this.startFragRequested = false; this.nextLoadPosition = this.startPosition; } this.tickImmediate(); } onBufferFlushed(event, { type }) { if (type !== ElementaryStreamTypes.AUDIO || this.audioOnly && !this.altAudio) { const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); this.tick(); } } onLevelsUpdated(event, data) { if (this.level > -1 && this.fragCurrent) { this.level = this.fragCurrent.level; } this.levels = data.levels; } swapAudioCodec() { this.audioCodecSwap = !this.audioCodecSwap; } /** * Seeks to the set startPosition if not equal to the mediaElement's current time. */ seekToStartPos() { const { media } = this; if (!media) { return; } const currentTime = media.currentTime; let startPosition = this.startPosition; // only adjust currentTime if different from startPosition or if startPosition not buffered // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered if (startPosition >= 0 && currentTime < startPosition) { if (media.seeking) { this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`); return; } const buffered = BufferHelper.getBuffered(media); const bufferStart = buffered.length ? buffered.start(0) : 0; const delta = bufferStart - startPosition; if (delta > 0 && (delta < this.config.maxBufferHole || delta < this.config.maxFragLookUpTolerance)) { this.log(`adjusting start position by ${delta} to match buffer start`); startPosition += delta; this.startPosition = startPosition; } this.log(`seek to target start position ${startPosition} from current time ${currentTime}`); media.currentTime = startPosition; } } _getAudioCodec(currentLevel) { let audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec; if (this.audioCodecSwap && audioCodec) { this.log('Swapping audio codec'); if (audioCodec.indexOf('mp4a.40.5') !== -1) { audioCodec = 'mp4a.40.2'; } else { audioCodec = 'mp4a.40.5'; } } return audioCodec; } _loadBitrateTestFrag(frag, level) { frag.bitrateTest = true; this._doFragLoad(frag, level).then(data => { const { hls } = this; if (!data || this.fragContextChanged(frag)) { return; } level.fragmentError = 0; this.state = State.IDLE; this.startFragRequested = false; this.bitrateTest = false; const stats = frag.stats; // Bitrate tests fragments are neither parsed nor buffered stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now(); hls.trigger(Events.FRAG_LOADED, data); frag.bitrateTest = false; }); } _handleTransmuxComplete(transmuxResult) { var _id3$samples; const id = 'main'; const { hls } = this; const { remuxResult, chunkMeta } = transmuxResult; const context = this.getCurrentContext(chunkMeta); if (!context) { this.resetWhenMissingContext(chunkMeta); return; } const { frag, part, level } = context; const { video, text, id3, initSegment } = remuxResult; const { details } = level; // The audio-stream-controller handles audio buffering if Hls.js is playing an alternate audio track const audio = this.altAudio ? undefined : remuxResult.audio; // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level. // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed. if (this.fragContextChanged(frag)) { this.fragmentTracker.removeFragment(frag); return; } this.state = State.PARSING; if (initSegment) { if (initSegment != null && initSegment.tracks) { const mapFragment = frag.initSegment || frag; this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta); hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { frag: mapFragment, id, tracks: initSegment.tracks }); } // This would be nice if Number.isFinite acted as a typeguard, but it doesn't. See: https://github.com/Microsoft/TypeScript/issues/10038 const initPTS = initSegment.initPTS; const timescale = initSegment.timescale; if (isFiniteNumber(initPTS)) { this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; hls.trigger(Events.INIT_PTS_FOUND, { frag, id, initPTS, timescale }); } } // Avoid buffering if backtracking this fragment if (video && details && frag.sn !== 'initSegment') { const prevFrag = details.fragments[frag.sn - 1 - details.startSN]; const isFirstFragment = frag.sn === details.startSN; const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc; if (remuxResult.independent !== false) { const { startPTS, endPTS, startDTS, endDTS } = video; if (part) { part.elementaryStreams[video.type] = { startPTS, endPTS, startDTS, endDTS }; } else { if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) { this.couldBacktrack = true; } if (video.dropped && video.independent) { // Backtrack if dropped frames create a gap after currentTime const bufferInfo = this.getMainFwdBufferInfo(); const targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole; const startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS; if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) { this.backtrack(frag); return; } else if (isFirstInDiscontinuity) { // Mark segment with a gap to avoid loop loading frag.gap = true; } // Set video stream start to fragment start so that truncated samples do not distort the timeline, and mark it partial frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true); } else if (isFirstFragment && startPTS > MAX_START_GAP_JUMP) { // Mark segment with a gap to skip large start gap frag.gap = true; } } frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS); if (this.backtrackFragment) { this.backtrackFragment = frag; } this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity); } else if (isFirstFragment || isFirstInDiscontinuity) { // Mark segment with a gap to avoid loop loading frag.gap = true; } else { this.backtrack(frag); return; } } if (audio) { const { startPTS, endPTS, startDTS, endDTS } = audio; if (part) { part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { startPTS, endPTS, startDTS, endDTS }; } frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS); this.bufferFragmentData(audio, frag, part, chunkMeta); } if (details && id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) { const emittedID3 = { id, frag, details, samples: id3.samples }; hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); } if (details && text) { const emittedText = { id, frag, details, samples: text.samples }; hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); } } _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) { if (this.state !== State.PARSING) { return; } this.audioOnly = !!tracks.audio && !tracks.video; // if audio track is expected to come from audio stream controller, discard any coming from main if (this.altAudio && !this.audioOnly) { delete tracks.audio; } // include levelCodec in audio and video tracks const { audio, video, audiovideo } = tracks; if (audio) { let audioCodec = currentLevel.audioCodec; const ua = navigator.userAgent.toLowerCase(); if (this.audioCodecSwitch) { if (audioCodec) { if (audioCodec.indexOf('mp4a.40.5') !== -1) { audioCodec = 'mp4a.40.2'; } else { audioCodec = 'mp4a.40.5'; } } // In the case that AAC and HE-AAC audio codecs are signalled in manifest, // force HE-AAC, as it seems that most browsers prefers it. // don't force HE-AAC if mono stream, or in Firefox const audioMetadata = audio.metadata; if (audioMetadata && 'channelCount' in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && ua.indexOf('firefox') === -1) { audioCodec = 'mp4a.40.5'; } } // HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise if (audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1 && ua.indexOf('android') !== -1 && audio.container !== 'audio/mpeg') { // Exclude mpeg audio audioCodec = 'mp4a.40.2'; this.log(`Android: force audio codec to ${audioCodec}`); } if (currentLevel.audioCodec && currentLevel.audioCodec !== audioCodec) { this.log(`Swapping manifest audio codec "${currentLevel.audioCodec}" for "${audioCodec}"`); } audio.levelCodec = audioCodec; audio.id = 'main'; this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ''}/${currentLevel.audioCodec || ''}/${audio.codec}]`); } if (video) { video.levelCodec = currentLevel.videoCodec; video.id = 'main'; this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ''}/${video.codec}]`); } if (audiovideo) { this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`); } this.hls.trigger(Events.BUFFER_CODECS, tracks); // loop through tracks that are going to be provided to bufferController Object.keys(tracks).forEach(trackName => { const track = tracks[trackName]; const initSegment = track.initSegment; if (initSegment != null && initSegment.byteLength) { this.hls.trigger(Events.BUFFER_APPENDING, { type: trackName, data: initSegment, frag, part: null, chunkMeta, parent: frag.type }); } }); // trigger handler right now this.tickImmediate(); } getMainFwdBufferInfo() { return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN); } backtrack(frag) { this.couldBacktrack = true; // Causes findFragments to backtrack through fragments to find the keyframe this.backtrackFragment = frag; this.resetTransmuxer(); this.flushBufferGap(frag); this.fragmentTracker.removeFragment(frag); this.fragPrevious = null; this.nextLoadPosition = frag.start; this.state = State.IDLE; } checkFragmentChanged() { const video = this.media; let fragPlayingCurrent = null; if (video && video.readyState > 1 && video.seeking === false) { const currentTime = video.currentTime; /* if video element is in seeked state, currentTime can only increase. (assuming that playback rate is positive ...) As sometimes currentTime jumps back to zero after a media decode error, check this, to avoid seeking back to wrong position after a media decode error */ if (BufferHelper.isBuffered(video, currentTime)) { fragPlayingCurrent = this.getAppendedFrag(currentTime); } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) { /* ensure that FRAG_CHANGED event is triggered at startup, when first video frame is displayed and playback is paused. add a tolerance of 100ms, in case current position is not buffered, check if current pos+100ms is buffered and use that buffer range for FRAG_CHANGED event reporting */ fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1); } if (fragPlayingCurrent) { this.backtrackFragment = null; const fragPlaying = this.fragPlaying; const fragCurrentLevel = fragPlayingCurrent.level; if (!fragPlaying || fragPlayingCurrent.sn !== fragPlaying.sn || fragPlaying.level !== fragCurrentLevel) { this.fragPlaying = fragPlayingCurrent; this.hls.trigger(Events.FRAG_CHANGED, { frag: fragPlayingCurrent }); if (!fragPlaying || fragPlaying.level !== fragCurrentLevel) { this.hls.trigger(Events.LEVEL_SWITCHED, { level: fragCurrentLevel }); } } } } } get nextLevel() { const frag = this.nextBufferedFrag; if (frag) { return frag.level; } return -1; } get currentFrag() { const media = this.media; if (media) { return this.fragPlaying || this.getAppendedFrag(media.currentTime); } return null; } get currentProgramDateTime() { const media = this.media; if (media) { const currentTime = media.currentTime; const frag = this.currentFrag; if (frag && isFiniteNumber(currentTime) && isFiniteNumber(frag.programDateTime)) { const epocMs = frag.programDateTime + (currentTime - frag.start) * 1000; return new Date(epocMs); } } return null; } get currentLevel() { const frag = this.currentFrag; if (frag) { return frag.level; } return -1; } get nextBufferedFrag() { const frag = this.currentFrag; if (frag) { return this.followingBufferedFrag(frag); } return null; } get forceStartLoad() { return this._forceStartLoad; } } /** * The `Hls` class is the core of the HLS.js library used to instantiate player instances. * @public */ class Hls { /** * Get the video-dev/hls.js package version. */ static get version() { return "1.5.15"; } /** * Check if the required MediaSource Extensions are available. */ static isMSESupported() { return isMSESupported(); } /** * Check if MediaSource Extensions are available and isTypeSupported checks pass for any baseline codecs. */ static isSupported() { return isSupported(); } /** * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource). */ static getMediaSource() { return getMediaSource(); } static get Events() { return Events; } static get ErrorTypes() { return ErrorTypes; } static get ErrorDetails() { return ErrorDetails; } /** * Get the default configuration applied to new instances. */ static get DefaultConfig() { if (!Hls.defaultConfig) { return hlsDefaultConfig; } return Hls.defaultConfig; } /** * Replace the default configuration applied to new instances. */ static set DefaultConfig(defaultConfig) { Hls.defaultConfig = defaultConfig; } /** * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`. * @param userConfig - Configuration options applied over `Hls.DefaultConfig` */ constructor(userConfig = {}) { /** * The runtime configuration used by the player. At instantiation this is combination of `hls.userConfig` merged over `Hls.DefaultConfig`. */ this.config = void 0; /** * The configuration object provided on player instantiation. */ this.userConfig = void 0; this.coreComponents = void 0; this.networkControllers = void 0; this.started = false; this._emitter = new EventEmitter(); this._autoLevelCapping = -1; this._maxHdcpLevel = null; this.abrController = void 0; this.bufferController = void 0; this.capLevelController = void 0; this.latencyController = void 0; this.levelController = void 0; this.streamController = void 0; this.audioTrackController = void 0; this.subtitleTrackController = void 0; this.emeController = void 0; this.cmcdController = void 0; this._media = null; this.url = null; this.triggeringException = void 0; enableLogs(userConfig.debug || false, 'Hls instance'); const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig); this.userConfig = userConfig; if (config.progressive) { enableStreamingMode(config); } // core controllers and network loaders const { abrController: ConfigAbrController, bufferController: ConfigBufferController, capLevelController: ConfigCapLevelController, errorController: ConfigErrorController, fpsController: ConfigFpsController } = config; const errorController = new ConfigErrorController(this); const abrController = this.abrController = new ConfigAbrController(this); const bufferController = this.bufferController = new ConfigBufferController(this); const capLevelController = this.capLevelController = new ConfigCapLevelController(this); const fpsController = new ConfigFpsController(this); const playListLoader = new PlaylistLoader(this); const id3TrackController = new ID3TrackController(this); const ConfigContentSteeringController = config.contentSteeringController; // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null; const levelController = this.levelController = new LevelController(this, contentSteering); // FragmentTracker must be defined before StreamController because the order of event handling is important const fragmentTracker = new FragmentTracker(this); const keyLoader = new KeyLoader(this.config); const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader); // Cap level controller uses streamController to flush the buffer capLevelController.setStreamController(streamController); // fpsController uses streamController to switch when frames are being dropped fpsController.setStreamController(streamController); const networkControllers = [playListLoader, levelController, streamController]; if (contentSteering) { networkControllers.splice(1, 0, contentSteering); } this.networkControllers = networkControllers; const coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker]; this.audioTrackController = this.createController(config.audioTrackController, networkControllers); const AudioStreamControllerClass = config.audioStreamController; if (AudioStreamControllerClass) { networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader)); } // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers); const SubtitleStreamControllerClass = config.subtitleStreamController; if (SubtitleStreamControllerClass) { networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader)); } this.createController(config.timelineController, coreComponents); keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents); this.cmcdController = this.createController(config.cmcdController, coreComponents); this.latencyController = this.createController(LatencyController, coreComponents); this.coreComponents = coreComponents; // Error controller handles errors before and after all other controllers // This listener will be invoked after all other controllers error listeners networkControllers.push(errorController); const onErrorOut = errorController.onErrorOut; if (typeof onErrorOut === 'function') { this.on(Events.ERROR, onErrorOut, errorController); } } createController(ControllerClass, components) { if (ControllerClass) { const controllerInstance = new ControllerClass(this); if (components) { components.push(controllerInstance); } return controllerInstance; } return null; } // Delegate the EventEmitter through the public API of Hls.js on(event, listener, context = this) { this._emitter.on(event, listener, context); } once(event, listener, context = this) { this._emitter.once(event, listener, context); } removeAllListeners(event) { this._emitter.removeAllListeners(event); } off(event, listener, context = this, once) { this._emitter.off(event, listener, context, once); } listeners(event) { return this._emitter.listeners(event); } emit(event, name, eventObject) { return this._emitter.emit(event, name, eventObject); } trigger(event, eventObject) { if (this.config.debug) { return this.emit(event, event, eventObject); } else { try { return this.emit(event, event, eventObject); } catch (error) { logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error); // Prevent recursion in error event handlers that throw #5497 if (!this.triggeringException) { this.triggeringException = true; const fatal = event === Events.ERROR; this.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal, event, error }); this.triggeringException = false; } } } return false; } listenerCount(event) { return this._emitter.listenerCount(event); } /** * Dispose of the instance */ destroy() { logger.log('destroy'); this.trigger(Events.DESTROYING, undefined); this.detachMedia(); this.removeAllListeners(); this._autoLevelCapping = -1; this.url = null; this.networkControllers.forEach(component => component.destroy()); this.networkControllers.length = 0; this.coreComponents.forEach(component => component.destroy()); this.coreComponents.length = 0; // Remove any references that could be held in config options or callbacks const config = this.config; config.xhrSetup = config.fetchSetup = undefined; // @ts-ignore this.userConfig = null; } /** * Attaches Hls.js to a media element */ attachMedia(media) { logger.log('attachMedia'); this._media = media; this.trigger(Events.MEDIA_ATTACHING, { media: media }); } /** * Detach Hls.js from the media */ detachMedia() { logger.log('detachMedia'); this.trigger(Events.MEDIA_DETACHING, undefined); this._media = null; } /** * Set the source URL. Can be relative or absolute. */ loadSource(url) { this.stopLoad(); const media = this.media; const loadedSource = this.url; const loadingSource = this.url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, { alwaysNormalize: true }); this._autoLevelCapping = -1; this._maxHdcpLevel = null; logger.log(`loadSource:${loadingSource}`); if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) { this.detachMedia(); this.attachMedia(media); } // when attaching to a source URL, trigger a playlist load this.trigger(Events.MANIFEST_LOADING, { url: url }); } /** * Start loading data from the stream source. * Depending on default config, client starts loading automatically when a source is set. * * @param startPosition - Set the start position to stream from. * Defaults to -1 (None: starts from earliest point) */ startLoad(startPosition = -1) { logger.log(`startLoad(${startPosition})`); this.started = true; this.networkControllers.forEach(controller => { controller.startLoad(startPosition); }); } /** * Stop loading of any stream data. */ stopLoad() { logger.log('stopLoad'); this.started = false; this.networkControllers.forEach(controller => { controller.stopLoad(); }); } /** * Resumes stream controller segment loading if previously started. */ resumeBuffering() { if (this.started) { this.networkControllers.forEach(controller => { if ('fragmentLoader' in controller) { controller.startLoad(-1); } }); } } /** * Stops stream controller segment loading without changing 'started' state like stopLoad(). * This allows for media buffering to be paused without interupting playlist loading. */ pauseBuffering() { this.networkControllers.forEach(controller => { if ('fragmentLoader' in controller) { controller.stopLoad(); } }); } /** * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1) */ swapAudioCodec() { logger.log('swapAudioCodec'); this.streamController.swapAudioCodec(); } /** * When the media-element fails, this allows to detach and then re-attach it * as one call (convenience method). * * Automatic recovery of media-errors by this process is configurable. */ recoverMediaError() { logger.log('recoverMediaError'); const media = this._media; this.detachMedia(); if (media) { this.attachMedia(media); } } removeLevel(levelIndex) { this.levelController.removeLevel(levelIndex); } /** * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH */ get levels() { const levels = this.levelController.levels; return levels ? levels : []; } /** * Index of quality level (variant) currently played */ get currentLevel() { return this.streamController.currentLevel; } /** * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection. */ set currentLevel(newLevel) { logger.log(`set currentLevel:${newLevel}`); this.levelController.manualLevel = newLevel; this.streamController.immediateLevelSwitch(); } /** * Index of next quality level loaded as scheduled by stream controller. */ get nextLevel() { return this.streamController.nextLevel; } /** * Set quality level index for next loaded data. * This will switch the video quality asap, without interrupting playback. * May abort current loading of data, and flush parts of buffer (outside currently played fragment region). * @param newLevel - Pass -1 for automatic level selection */ set nextLevel(newLevel) { logger.log(`set nextLevel:${newLevel}`); this.levelController.manualLevel = newLevel; this.streamController.nextLevelSwitch(); } /** * Return the quality level of the currently or last (of none is loaded currently) segment */ get loadLevel() { return this.levelController.level; } /** * Set quality level index for next loaded data in a conservative way. * This will switch the quality without flushing, but interrupt current loading. * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer. * @param newLevel - Pass -1 for automatic level selection */ set loadLevel(newLevel) { logger.log(`set loadLevel:${newLevel}`); this.levelController.manualLevel = newLevel; } /** * get next quality level loaded */ get nextLoadLevel() { return this.levelController.nextLoadLevel; } /** * Set quality level of next loaded segment in a fully "non-destructive" way. * Same as `loadLevel` but will wait for next switch (until current loading is done). */ set nextLoadLevel(level) { this.levelController.nextLoadLevel = level; } /** * Return "first level": like a default level, if not set, * falls back to index of first level referenced in manifest */ get firstLevel() { return Math.max(this.levelController.firstLevel, this.minAutoLevel); } /** * Sets "first-level", see getter. */ set firstLevel(newLevel) { logger.log(`set firstLevel:${newLevel}`); this.levelController.firstLevel = newLevel; } /** * Return the desired start level for the first fragment that will be loaded. * The default value of -1 indicates automatic start level selection. * Setting hls.nextAutoLevel without setting a startLevel will result in * the nextAutoLevel value being used for one fragment load. */ get startLevel() { const startLevel = this.levelController.startLevel; if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) { return this.abrController.forcedAutoLevel; } return startLevel; } /** * set start level (level of first fragment that will be played back) * if not overrided by user, first level appearing in manifest will be used as start level * if -1 : automatic start level selection, playback will start from level matching download bandwidth * (determined from download of first segment) */ set startLevel(newLevel) { logger.log(`set startLevel:${newLevel}`); // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel if (newLevel !== -1) { newLevel = Math.max(newLevel, this.minAutoLevel); } this.levelController.startLevel = newLevel; } /** * Whether level capping is enabled. * Default value is set via `config.capLevelToPlayerSize`. */ get capLevelToPlayerSize() { return this.config.capLevelToPlayerSize; } /** * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called. */ set capLevelToPlayerSize(shouldStartCapping) { const newCapLevelToPlayerSize = !!shouldStartCapping; if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) { if (newCapLevelToPlayerSize) { this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size. } else { this.capLevelController.stopCapping(); this.autoLevelCapping = -1; this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap. } this.config.capLevelToPlayerSize = newCapLevelToPlayerSize; } } /** * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) */ get autoLevelCapping() { return this._autoLevelCapping; } /** * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned. */ get bandwidthEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.getEstimate(); } set bandwidthEstimate(abrEwmaDefaultEstimate) { this.abrController.resetEstimator(abrEwmaDefaultEstimate); } /** * get time to first byte estimate * @type {number} */ get ttfbEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.getEstimateTTFB(); } /** * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) */ set autoLevelCapping(newLevel) { if (this._autoLevelCapping !== newLevel) { logger.log(`set autoLevelCapping:${newLevel}`); this._autoLevelCapping = newLevel; this.levelController.checkMaxAutoUpdated(); } } get maxHdcpLevel() { return this._maxHdcpLevel; } set maxHdcpLevel(value) { if (isHdcpLevel(value) && this._maxHdcpLevel !== value) { this._maxHdcpLevel = value; this.levelController.checkMaxAutoUpdated(); } } /** * True when automatic level selection enabled */ get autoLevelEnabled() { return this.levelController.manualLevel === -1; } /** * Level set manually (if any) */ get manualLevel() { return this.levelController.manualLevel; } /** * min level selectable in auto mode according to config.minAutoBitrate */ get minAutoLevel() { const { levels, config: { minAutoBitrate } } = this; if (!levels) return 0; const len = levels.length; for (let i = 0; i < len; i++) { if (levels[i].maxBitrate >= minAutoBitrate) { return i; } } return 0; } /** * max level selectable in auto mode according to autoLevelCapping */ get maxAutoLevel() { const { levels, autoLevelCapping, maxHdcpLevel } = this; let maxAutoLevel; if (autoLevelCapping === -1 && levels != null && levels.length) { maxAutoLevel = levels.length - 1; } else { maxAutoLevel = autoLevelCapping; } if (maxHdcpLevel) { for (let i = maxAutoLevel; i--;) { const hdcpLevel = levels[i].attrs['HDCP-LEVEL']; if (hdcpLevel && hdcpLevel <= maxHdcpLevel) { return i; } } } return maxAutoLevel; } get firstAutoLevel() { return this.abrController.firstAutoLevel; } /** * next automatically selected quality level */ get nextAutoLevel() { return this.abrController.nextAutoLevel; } /** * this setter is used to force next auto level. * this is useful to force a switch down in auto mode: * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example) * forced value is valid for one fragment. upon successful frag loading at forced level, * this value will be resetted to -1 by ABR controller. */ set nextAutoLevel(nextLevel) { this.abrController.nextAutoLevel = nextLevel; } /** * get the datetime value relative to media.currentTime for the active level Program Date Time if present */ get playingDate() { return this.streamController.currentProgramDateTime; } get mainForwardBufferInfo() { return this.streamController.getMainFwdBufferInfo(); } /** * Find and select the best matching audio track, making a level switch when a Group change is necessary. * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found. */ setAudioOption(audioOption) { var _this$audioTrackContr; return (_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption); } /** * Find and select the best matching subtitle track, making a level switch when a Group change is necessary. * Updates `hls.config.subtitlePreference`. Returns the selected track, or null when no matching track is found. */ setSubtitleOption(subtitleOption) { var _this$subtitleTrackCo; (_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption); return null; } /** * Get the complete list of audio tracks across all media groups */ get allAudioTracks() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.allAudioTracks : []; } /** * Get the list of selectable audio tracks */ get audioTracks() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.audioTracks : []; } /** * index of the selected audio track (index in audio track lists) */ get audioTrack() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.audioTrack : -1; } /** * selects an audio track, based on its index in audio track lists */ set audioTrack(audioTrackId) { const audioTrackController = this.audioTrackController; if (audioTrackController) { audioTrackController.audioTrack = audioTrackId; } } /** * get the complete list of subtitle tracks across all media groups */ get allSubtitleTracks() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : []; } /** * get alternate subtitle tracks list from playlist */ get subtitleTracks() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleTracks : []; } /** * index of the selected subtitle track (index in subtitle track lists) */ get subtitleTrack() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1; } get media() { return this._media; } /** * select an subtitle track, based on its index in subtitle track lists */ set subtitleTrack(subtitleTrackId) { const subtitleTrackController = this.subtitleTrackController; if (subtitleTrackController) { subtitleTrackController.subtitleTrack = subtitleTrackId; } } /** * Whether subtitle display is enabled or not */ get subtitleDisplay() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false; } /** * Enable/disable subtitle display rendering */ set subtitleDisplay(value) { const subtitleTrackController = this.subtitleTrackController; if (subtitleTrackController) { subtitleTrackController.subtitleDisplay = value; } } /** * get mode for Low-Latency HLS loading */ get lowLatencyMode() { return this.config.lowLatencyMode; } /** * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK. */ set lowLatencyMode(mode) { this.config.lowLatencyMode = mode; } /** * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```) * @returns null prior to loading live Playlist */ get liveSyncPosition() { return this.latencyController.liveSyncPosition; } /** * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced) * @returns 0 before first playlist is loaded */ get latency() { return this.latencyController.latency; } /** * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition``` * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration``` * @returns 0 before first playlist is loaded */ get maxLatency() { return this.latencyController.maxLatency; } /** * target distance from the edge as calculated by the latency controller */ get targetLatency() { return this.latencyController.targetLatency; } /** * the rate at which the edge of the current live playlist is advancing or 1 if there is none */ get drift() { return this.latencyController.drift; } /** * set to true when startLoad is called before MANIFEST_PARSED event */ get forceStartLoad() { return this.streamController.forceStartLoad; } } Hls.defaultConfig = void 0; //# sourceMappingURL=hls.mjs.map /***/ }) }, /******/ __webpack_require__ => { // webpackRuntimeModules /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) /******/ var __webpack_exports__ = (__webpack_exec__("./src/index.js")); /******/ } ]); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguYnVuZGxlLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBeUI7O0FBRXpCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLDRCQUE0QixZQUFZO0FBQ3hDO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQSxxQ0FBcUMsZ0NBQWdDO0FBQ3JFLHFDQUFxQyw0QkFBNEI7QUFDakUsY0FBYztBQUNkO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQSxxQ0FBcUMsZ0NBQWdDO0FBQ3JFLGNBQWM7QUFDZDtBQUNBLHFDQUFxQyw0QkFBNEI7QUFDakU7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCO0FBQ2xCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDO0FBQ2hDO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBLGlDQUFpQztBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0Esa0VBQWtFO0FBQ2xFOztBQUVBO0FBQ0E7QUFDQSxrQkFBa0I7QUFDbEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDs7QUFFQTtBQUNBOztBQUVPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsU0FBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTs7O0FBR0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0EsaUVBQWlFLHNDQUFzQztBQUN2RztBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBLEtBQUs7O0FBRUwsY0FBYyw4Q0FBRztBQUNqQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCxXQUFXLHFEQUFVO0FBQ3JCO0FBQ0E7QUFDQSxLQUFLOztBQUVMLFdBQVcscURBQVU7QUFDckI7QUFDQSxLQUFLO0FBQ0wsV0FBVyxxREFBVTtBQUNyQjtBQUNBLEtBQUs7O0FBRUw7QUFDQTtBQUNBOztBQUVPO0FBQ1A7QUFDQTtBQUNBOztBQUVPO0FBQ1A7QUFDQTs7QUFFTztBQUNQO0FBQ0E7O0FBRU87QUFDUDtBQUNBOztBQUVPO0FBQ1A7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRU87QUFDUDtBQUNBOztBQUVPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0Esb0JBQW9CLHVCQUF1QjtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7O0FBRUw7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0EsS0FBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsS0FBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FDN3FCQTtBQUNBO0FBQ0E7O0FBRUEsa0JBQWtCOztBQUVsQjtBQUNBOztBQUVBO0FBQ0E7QUFDQSx1RkFBdUYsaUJBQWlCO0FBQ3hHO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx3QkFBd0I7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEI7QUFDMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047O0FBRUE7QUFDQSxFQUFFO0FBQ0YsRUFBRTs7QUFFRjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0Isc0JBQXNCO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxDQUFDLEdBQUc7O0FBRUo7QUFDQSwrREFBK0QsOEJBQThCO0FBQzdGOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsQ0FBQyxHQUFHO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUMsR0FBRzs7QUFFSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHVDQUF1QyxLQUFLO0FBQzVDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELEdBQUcsc0JBQXNCLFNBQVM7QUFDdEYsTUFBTTtBQUNOO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLDRCQUE0QjtBQUNsRDtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsSUFBSSxzQ0FBc0MsaUJBQWlCO0FBQzlHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEM7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxrRUFBa0Usb0NBQW9DO0FBQ3RHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDBFQUEwRSxtQ0FBbUM7QUFDN0c7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQSx5RUFBeUUsMkJBQTJCO0FBQ3BHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5QkFBeUIsa0JBQWtCO0FBQzNDO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRDtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0EsQ0FBQzs7QUFFRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLFVBQVU7QUFDdkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQkFBK0IsVUFBVSxNQUFNO0FBQy9DO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQSwrQkFBK0IsVUFBVSxNQUFNO0FBQy9DO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQixnQkFBZ0I7QUFDaEIsZ0JBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLG1CQUFtQjtBQUNyQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2IsYUFBYSxZQUFZLEdBQUc7QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1gsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2IsYUFBYSxZQUFZLEdBQUc7QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isa0JBQWtCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixRQUFRO0FBQzFCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixRQUFRO0FBQzFCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLHFCQUFxQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixrQkFBa0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCO0FBQ2xCO0FBQ0E7QUFDQSxvQkFBb0I7QUFDcEI7QUFDQTtBQUNBLCtCQUErQjtBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRCxJQUFJO0FBQ3JEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0RBQXdELG9CQUFvQixvQkFBb0Isd0JBQXdCLEtBQUssbUJBQW1CO0FBQ2hKO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0Isa0JBQWtCO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixrQkFBa0I7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isa0JBQWtCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBLGFBQWE7QUFDYixNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixpQkFBaUI7QUFDbkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTCxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCO0FBQ3hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQzs7QUFFaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZCQUE2QixrQkFBa0I7QUFDL0M7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQjtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0wsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNOztBQUVOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLDZDQUE2QyxhQUFhLHFCQUFxQixVQUFVO0FBQ3pGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxnQkFBZ0I7QUFDaEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0Esd0JBQXdCLFFBQVE7QUFDaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixZQUFZO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWMsZUFBZTtBQUM3QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixTQUFTO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxQkFBcUIsb0JBQW9CO0FBQ3pDLDRCQUE0QjtBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHdCQUF3QjtBQUM1QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRFQUE0RSxZQUFZO0FBQ3hGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0RBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQW1CLFFBQVE7QUFDM0I7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsc0NBQXNDLG9CQUFvQjtBQUMxRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0NBQXdDLElBQUk7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0lBQStJLGFBQWE7QUFDNUo7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLDRCQUE0QixLQUFLLGdEQUFnRCxVQUFVO0FBQzNGO0FBQ0EsTUFBTTtBQUNOLDBHQUEwRyxjQUFjO0FBQ3hIO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUlBQW1JLEtBQUs7QUFDeEksSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0osa0pBQWtKLE9BQU87QUFDeko7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWSxLQUFLLEtBQUssVUFBVSxNQUFNO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNILGtCQUFrQiwwQkFBMEI7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixtQkFBbUI7QUFDckM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLDhEQUE4RDs7QUFFOUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsbUJBQW1CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCLCtFQUErRSxXQUFXO0FBQzFGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixtQkFBbUI7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDLEtBQUs7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCLGdFQUFnRSxPQUFPO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQjtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQSxnQkFBZ0I7QUFDaEIsdUVBQXVFLE9BQU87QUFDOUU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUNBQXFDLFlBQVksR0FBRyxNQUFNO0FBQzFELGtCQUFrQjtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdEQUF3RCxPQUFPO0FBQy9EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQ0FBMkM7QUFDM0M7QUFDQTtBQUNBLGdDQUFnQyxZQUFZO0FBQzVDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWUsS0FBSztBQUNwQjtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBOEIsSUFBSTtBQUNsQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxtRUFBbUUsYUFBYSxXQUFXLGNBQWMsUUFBUSxXQUFXOztBQUU1SDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwRUFBMEUsYUFBYTtBQUN2RjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ04sOEJBQThCO0FBQzlCO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQ0FBa0M7QUFDbEM7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxvRkFBb0YsWUFBWTs7QUFFaEc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQ0FBZ0M7QUFDaEM7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBOEI7QUFDOUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSwrQkFBK0IscUZBQXFGLHlCQUF5QixhQUFhO0FBQzFKO0FBQ0Esc0JBQXNCLGVBQWUsTUFBTSxXQUFXO0FBQ3RELE1BQU07QUFDTix5QkFBeUIsWUFBWSxhQUFhLGdCQUFnQjtBQUNsRTtBQUNBO0FBQ0Esc0NBQXNDLFFBQVE7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpREFBaUQsSUFBSTtBQUNyRDtBQUNBLE1BQU07QUFDTix5Q0FBeUMsSUFBSTtBQUM3QztBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUixnRkFBZ0YsS0FBSztBQUNyRjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9DQUFvQyxJQUFJO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsaUJBQWlCO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscURBQXFELFNBQVM7QUFDOUQ7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0IsMEJBQTBCO0FBQzVDO0FBQ0EsMENBQTBDO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUVBQXFFLEVBQUU7QUFDdkU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHVCQUF1QjtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixvQkFBb0I7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLG1CQUFtQjtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQ0FBZ0MsSUFBSTtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUNBQXVDLElBQUk7QUFDM0M7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGdCQUFnQjtBQUNwQztBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IsdUJBQXVCO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0I7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixPQUFPO0FBQzNCO0FBQ0E7O0FBRUE7QUFDQSxvQkFBb0IsMEJBQTBCO0FBQzlDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdDQUF3QyxRQUFRO0FBQ2hEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxJQUFJO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQix5QkFBeUI7QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDO0FBQ2hDO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOLDRFQUE0RSx5Q0FBeUM7QUFDckg7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDJDQUEyQyxVQUFVO0FBQ3JEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixVQUFVO0FBQ2hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMENBQTBDLHNCQUFzQjtBQUNoRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0NBQWtDLElBQUk7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQkFBK0IsZ0NBQWdDO0FBQy9EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQix3QkFBd0I7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw4REFBOEQ7QUFDOUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLE1BQU0sR0FBRyxhQUFhLEdBQUcsZ0JBQWdCLEdBQUcscUNBQXFDLEdBQUcsVUFBVTtBQUMzSTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1Isa0NBQWtDLElBQUk7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1YsZ0RBQWdELHNCQUFzQjtBQUN0RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0REFBNEQsaUJBQWlCO0FBQzdFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNDQUFzQyxZQUFZLFFBQVEsYUFBYTtBQUN2RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMENBQTBDLFVBQVU7QUFDcEQsNENBQTRDLFVBQVU7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQiw2QkFBNkI7QUFDbkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1YsMkVBQTJFLE1BQU07QUFDakY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNOztBQUVOO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0NBQWtDLE9BQU8sRUFBRSw0SEFBNEg7QUFDdks7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwREFBMEQsNEJBQTRCLE1BQU0sYUFBYSxxQkFBcUIsWUFBWTtBQUMxSTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBeUMsa0JBQWtCLGtCQUFrQix3QkFBd0IsVUFBVSxhQUFhLFVBQVUsVUFBVSxVQUFVLEtBQUs7QUFDL0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUNBQXVDLE9BQU8sS0FBSyxzQ0FBc0M7QUFDekY7QUFDQSwwQkFBMEI7QUFDMUIscUJBQXFCO0FBQ3JCLHNCQUFzQjtBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDJCQUEyQjtBQUMzQiwyQkFBMkI7QUFDM0IsNEJBQTRCLDRCQUE0QjtBQUN4RDs7QUFFQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxlQUFlLEdBQUcseUJBQXlCLFNBQVMsYUFBYTtBQUNoSDtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSwrQ0FBK0MsZUFBZSxHQUFHLHlCQUF5QixTQUFTLGFBQWEsT0FBTyxNQUFNO0FBQzdIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1EQUFtRCxTQUFTO0FBQzVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQSxTQUFTO0FBQ1Q7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkNBQTJDLDZCQUE2QjtBQUN4RTtBQUNBLEtBQUs7QUFDTCxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlCQUFpQixhQUFhLEdBQUcsWUFBWSxHQUFHLDJCQUEyQixFQUFFLCtCQUErQixHQUFHLE1BQU0sR0FBRywrQkFBK0I7QUFDdko7QUFDQTtBQUNBLGlCQUFpQixlQUFlLEVBQUUsbUNBQW1DLEdBQUcsTUFBTTtBQUM5RTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVMsMENBQTBDO0FBQ25EO0FBQ0E7QUFDQTtBQUNBLFNBQVMsaUZBQWlGO0FBQzFGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0osaUNBQWlDLElBQUk7QUFDckM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlFQUFpRSwwQkFBMEIsd0JBQXdCLFVBQVU7QUFDN0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwRUFBMEUscUJBQXFCO0FBQy9GO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUVBQXVFLG9CQUFvQix5Q0FBeUMsb0NBQW9DO0FBQ3hLO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxvRUFBb0UseUJBQXlCLGVBQWUsVUFBVTtBQUN0SDtBQUNBO0FBQ0E7QUFDQSxtRUFBbUUsNEJBQTRCLGVBQWUsYUFBYTtBQUMzSDtBQUNBO0FBQ0E7QUFDQSxrRkFBa0YsNkJBQTZCO0FBQy9HO0FBQ0E7QUFDQTtBQUNBLCtEQUErRCx3QkFBd0Isb0JBQW9CLGNBQWM7QUFDekg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw2Q0FBNkMsUUFBUSxvQkFBb0IsT0FBTztBQUNoRjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNULE9BQU87QUFDUDtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixtQkFBbUI7QUFDckM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHLElBQUk7QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBLDRCQUE0QixHQUFHO0FBQy9CO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDLGdCQUFnQjtBQUNoRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkNBQTJDLDhCQUE4QjtBQUN6RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQ0FBb0MsUUFBUSxFQUFFLG1DQUFtQyxXQUFXLFlBQVk7QUFDeEcsNkJBQTZCLGtDQUFrQztBQUMvRCxrREFBa0QsNEJBQTRCO0FBQzlFLHNEQUFzRCxxQ0FBcUM7QUFDM0YsdUJBQXVCLFVBQVU7QUFDakMsNkJBQTZCLHlEQUF5RDtBQUN0Rix5QkFBeUIsMEJBQTBCO0FBQ25ELDJCQUEyQixlQUFlLElBQUksMEJBQTBCO0FBQ3hFO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkNBQTJDLHVCQUF1QjtBQUNsRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrR0FBa0csWUFBWSxhQUFhLFFBQVE7QUFDbkk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWMscUJBQXFCLEdBQUcscUNBQXFDO0FBQzNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxvQ0FBb0MsOENBQThDLHVDQUF1QztBQUN4SztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUJBQXlCLG1FQUFtRSwwQkFBMEIsVUFBVTtBQUNoSTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnRUFBZ0U7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0Q0FBNEMsMEJBQTBCO0FBQ3RFLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQkFBK0IsbUJBQW1CO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBFQUEwRSxtQkFBbUIsY0FBYyxPQUFPLEVBQUUsNkJBQTZCO0FBQ2pKLGNBQWM7QUFDZCwrRkFBK0YsT0FBTyxFQUFFLDZCQUE2QjtBQUNySTtBQUNBLCtEQUErRCxNQUFNO0FBQ3JFO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWCxVQUFVO0FBQ1Y7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1EQUFtRCx5QkFBeUIsS0FBSyxjQUFjLG1DQUFtQyxnQ0FBZ0MsSUFBSSxzQ0FBc0MsdUJBQXVCLGFBQWEsSUFBSSxrQkFBa0I7QUFDdFE7QUFDQSxnREFBZ0QsbUJBQW1CLElBQUksR0FBRyxhQUFhLHVCQUF1QixZQUFZLGtDQUFrQyxPQUFPLDRCQUE0QixjQUFjLHdCQUF3QixtQkFBbUIsNkJBQTZCLGdCQUFnQiwwQkFBMEIsaUJBQWlCLGdCQUFnQixXQUFXLGlCQUFpQixhQUFhLG1CQUFtQixnQkFBZ0IsVUFBVTtBQUN0YjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUMsSUFBSTtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLDhCQUE4QixJQUFJO0FBQ2xDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isc0JBQXNCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWSxjQUFjLEdBQUcsZUFBZSxHQUFHLFlBQVk7QUFDM0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx3QkFBd0IscUJBQXFCO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IscUJBQXFCO0FBQzNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSwwQ0FBMEMsU0FBUztBQUNuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQ0FBMEMsU0FBUztBQUNuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyRkFBMkYsWUFBWTtBQUN2RztBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSx3Q0FBd0M7O0FBRXhDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFEQUFxRCwwQkFBMEI7QUFDL0U7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYiwyQ0FBMkMsZUFBZSxFQUFFLGNBQWM7QUFDMUU7QUFDQTtBQUNBLFdBQVc7QUFDWCxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1gsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDLHFCQUFxQjtBQUNuRTtBQUNBO0FBQ0EsV0FBVztBQUNYLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiLDJDQUEyQyxlQUFlLEVBQUUsY0FBYztBQUMxRTtBQUNBO0FBQ0EsV0FBVztBQUNYLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1gsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEMscUJBQXFCO0FBQ25FO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQSxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxlQUFlO0FBQ2Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQ0FBaUMsZ0NBQWdDO0FBQ2pFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLE9BQU87QUFDM0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCLFNBQVM7QUFDekI7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0IsU0FBUztBQUN6QjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsZ0JBQWdCO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1QkFBdUIsbUJBQW1CO0FBQzFDO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxrQkFBa0IsYUFBYTtBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSx1QkFBdUI7O0FBRXZCO0FBQ0E7QUFDQTtBQUNBLElBQUksSUFBSTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsMEVBQTBFLFNBQVMsSUFBSSxZQUFZO0FBQ25HO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtCQUErQixJQUFJO0FBQ25DO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsU0FBUztBQUM3QixpQkFBaUIsc0JBQXNCLEdBQUcsb0JBQW9CO0FBQzlEO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBDQUEwQyxVQUFVO0FBQ3BELDRDQUE0QyxVQUFVO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxpQ0FBaUMsbUVBQW1FLFdBQVcsTUFBTTtBQUNySDtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QixRQUFRLEVBQUUsMkNBQTJDLFdBQVcsWUFBWTtBQUMxRztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0NBQW9DLFNBQVMsV0FBVyxXQUFXO0FBQ25FO0FBQ0E7O0FBRUEseUNBQXlDO0FBQ3pDO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBLCtCQUErQixxREFBcUQ7QUFDcEY7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0EsUUFBUTtBQUNSOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5QkFBeUIsV0FBVyxNQUFNLFFBQVEsRUFBRSxvQ0FBb0MsS0FBSyxrRUFBa0UsRUFBRSxZQUFZLFNBQVMsNkVBQTZFLEdBQUcsdUVBQXVFLGFBQWEsNEVBQTRFO0FBQ3RhO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDREQUE0RCxxQkFBcUI7QUFDakY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF5RCx5QkFBeUI7QUFDbEY7QUFDQTtBQUNBO0FBQ0Esa0NBQWtDLFNBQVMsTUFBTSxnQkFBZ0IsR0FBRyxjQUFjLEtBQUssOERBQThELEVBQUUsV0FBVztBQUNsSztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUMsU0FBUyxLQUFLLFlBQVksTUFBTSxTQUFTLGVBQWUsZ0JBQWdCLEdBQUcsY0FBYyxhQUFhLFVBQVUsR0FBRyxvQkFBb0IsSUFBSSw2REFBNkQsSUFBSSxXQUFXLFlBQVksd0NBQXdDO0FBQ2xUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYixZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQ0FBaUMsU0FBUyxNQUFNLFNBQVMsRUFBRSxxRUFBcUUsRUFBRSw2REFBNkQsSUFBSSxXQUFXLFlBQVksd0NBQXdDO0FBQ2xRO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxvRUFBb0UsSUFBSSxXQUFXLFdBQVc7QUFDOUY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDLGNBQWM7QUFDN0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1FQUFtRSxRQUFRLFVBQVUsd0JBQXdCO0FBQzdHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDLGFBQWEsNkJBQTZCLFFBQVE7QUFDakc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQ0FBMkMsU0FBUztBQUNwRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnRkFBZ0YsNkJBQTZCO0FBQzdHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUZBQW1GLFFBQVE7QUFDM0Y7QUFDQTtBQUNBLDRFQUE0RTtBQUM1RTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1GQUFtRixRQUFRO0FBQzNGO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQ0FBaUMsd0JBQXdCLDREQUE0RCxJQUFJLDJCQUEyQiw0QkFBNEI7QUFDaEw7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBLHlDQUF5QyxnQ0FBZ0MsWUFBWSxpREFBaUQsSUFBSSxpQkFBaUIsV0FBVyx1Q0FBdUMsYUFBYSxPQUFPO0FBQ2pPO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQ0FBc0MsaUJBQWlCLFdBQVcseURBQXlELG9DQUFvQyxjQUFjO0FBQzdLO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNEJBQTRCLFFBQVEsRUFBRSxtQ0FBbUMsV0FBVyxZQUFZO0FBQ2hHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0VBQW9FLFVBQVUsSUFBSSxrRkFBa0Y7QUFDcEs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixTQUFTLEtBQUssWUFBWSxFQUFFLFlBQVksZUFBZSxhQUFhLHFCQUFxQixlQUFlLEdBQUcseUJBQXlCLEtBQUssTUFBTTtBQUMzSztBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLHVCQUF1QixjQUFjLGlDQUFpQyxXQUFXO0FBQ2pGO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzRkFBc0YsY0FBYztBQUNwRztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzRUFBc0UsY0FBYyxXQUFXLGdCQUFnQjtBQUMvRztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdEQUFnRCxTQUFTLEVBQUUsTUFBTSxxQkFBcUIsZUFBZTtBQUNyRztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBLDREQUE0RCxTQUFTLFdBQVcsWUFBWTtBQUM1RjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLFNBQVMsWUFBWSxVQUFVO0FBQ3hFLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixjQUFjLElBQUksVUFBVTtBQUM5QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLG1CQUFtQjtBQUNyQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDLFlBQVksaUJBQWlCLG1CQUFtQixLQUFLLGdCQUFnQixHQUFHLGlDQUFpQyxRQUFRO0FBQy9KO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdDQUF3QyxLQUFLO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyREFBMkQsa0JBQWtCO0FBQzdFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0JBQStCLFdBQVcsY0FBYyxlQUFlLGtCQUFrQixrQkFBa0I7QUFDM0c7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0JBQStCLFlBQVksU0FBUyxrQkFBa0IsYUFBYSxvQkFBb0I7QUFDdkc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMERBQTBELG9CQUFvQixHQUFHLFlBQVksR0FBRyxnQkFBZ0I7QUFDaEg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLCtCQUErQixXQUFXLFFBQVEsT0FBTyx1QkFBdUIsWUFBWSxHQUFHLG9CQUFvQixHQUFHLGlCQUFpQixXQUFXLFFBQVE7QUFDMUo7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9EO0FBQ3BEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQyxpQkFBaUI7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdHQUF3RztBQUN4RztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBbUM7QUFDbkMsbUNBQW1DO0FBQ25DLGtDQUFrQztBQUNsQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0EsZUFBZTtBQUNmOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQW1CO0FBQ25CO0FBQ0EsNEJBQTRCO0FBQzVCOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxtQkFBbUI7QUFDbkI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxtREFBbUQ7QUFDbkQsMENBQTBDO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsMEJBQTBCO0FBQzFCLCtCQUErQix1Q0FBdUM7QUFDdEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsK0JBQStCO0FBQy9CO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGlDQUFpQztBQUNqQztBQUNBO0FBQ0EsNkJBQTZCO0FBQzdCLE1BQU07QUFDTixnQ0FBZ0M7QUFDaEM7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsV0FBVztBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9DQUFvQztBQUNwQyxpQkFBaUI7QUFDakIsaUJBQWlCO0FBQ2pCLGlCQUFpQjtBQUNqQixlQUFlO0FBQ2Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7O0FBRVIsaUJBQWlCO0FBQ2pCLGlCQUFpQjtBQUNqQixtQkFBbUI7QUFDbkI7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxlQUFlO0FBQ2Y7QUFDQTtBQUNBLGlCQUFpQjtBQUNqQixNQUFNO0FBQ04sbUJBQW1CO0FBQ25CLGdCQUFnQjtBQUNoQixnQkFBZ0I7QUFDaEI7QUFDQSxrQkFBa0Isb0NBQW9DO0FBQ3REO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsZUFBZTtBQUNmLGlCQUFpQjtBQUNqQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTs7QUFFTixpQkFBaUI7QUFDakI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QixPQUFPO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixvQ0FBb0M7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw2QkFBNkIscUNBQXFDO0FBQ2xFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBFQUEwRSxXQUFXO0FBQ3JGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixZQUFZO0FBQ2xDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGlDQUFpQyxhQUFhO0FBQzlDO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELE9BQU8scUJBQXFCLFdBQVcsZ0NBQWdDLFlBQVk7QUFDdkk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF5RCxnQkFBZ0I7QUFDekU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiLFlBQVk7QUFDWjtBQUNBO0FBQ0EsU0FBUztBQUNULFFBQVE7QUFDUjtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxjQUFjO0FBQzdEO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtELGtCQUFrQjtBQUNwRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrRUFBa0UsT0FBTztBQUN6RSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWUsa0JBQWtCO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsaUNBQWlDO0FBQ2pDO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDLGNBQWM7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBLGdCQUFnQixNQUFNO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlCQUF5QixzQ0FBc0M7QUFDL0Q7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQ0FBMkMsYUFBYTtBQUN4RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQyxpQkFBaUI7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBOEI7O0FBRTlCLDZEQUE2RDtBQUM3RCx5REFBeUQ7QUFDekQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIsU0FBUztBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxnQkFBZ0Isc0JBQXNCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLGdCQUFnQixzQkFBc0I7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw0RUFBNEUsdUJBQXVCO0FBQ25HO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0IsU0FBUztBQUN6QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGlEQUFpRDtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLDZDQUE2QztBQUM3QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrREFBa0Qsb0JBQW9CLFNBQVMsUUFBUTtBQUN2RjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaLG1FQUFtRSxRQUFRO0FBQzNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixlQUFlO0FBQ25DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QixrQ0FBa0MsTUFBTSxNQUFNLDBDQUEwQyxzQkFBc0I7QUFDNUksVUFBVTtBQUNWLDhCQUE4QixtQ0FBbUMsTUFBTSxNQUFNLGlEQUFpRCxzQkFBc0I7QUFDcEo7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1osNEJBQTRCLHlCQUF5QjtBQUNyRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF5RCxvQ0FBb0MsR0FBRyxvQ0FBb0MsV0FBVyxrQ0FBa0M7QUFDakw7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsZUFBZTtBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLGFBQWE7QUFDbkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QyxTQUFTO0FBQ3ZELE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixlQUFlO0FBQ25DO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseURBQXlELGFBQWE7QUFDdEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0EsNkRBQTZELHNCQUFzQix3QkFBd0IsaUJBQWlCLHdCQUF3QjtBQUNwSixZQUFZO0FBQ1o7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0RBQXNELFNBQVM7QUFDL0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVFQUF1RTtBQUN2RTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxnREFBZ0Q7QUFDaEQ7O0FBRUE7QUFDQTtBQUNBLDhDQUE4Qyx5QkFBeUI7QUFDdkU7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBeUMsa0NBQWtDLDZCQUE2QiwyQ0FBMkM7QUFDbko7QUFDQTtBQUNBLFVBQVU7O0FBRVY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtEQUFrRCxTQUFTLGdCQUFnQixzQ0FBc0MsV0FBVywyQ0FBMkM7QUFDdkssMEJBQTBCLGFBQWE7QUFDdkM7QUFDQTtBQUNBO0FBQ0EsMkZBQTJGO0FBQzNGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzREFBc0QsZ0JBQWdCO0FBQ3RFO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUdBQW1HO0FBQ25HO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELFNBQVM7QUFDN0QsYUFBYTtBQUNiO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0EsbUhBQW1IO0FBQ25IO0FBQ0EsS0FBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixlQUFlO0FBQ25DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLG9CQUFvQjtBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IsZ0JBQWdCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixnQkFBZ0I7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLHFEQUFxRDtBQUNyRDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLGlDQUFpQztBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRDQUE0Qyx1Q0FBdUM7QUFDbkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVDQUF1QyxZQUFZLDZDQUE2QyxPQUFPO0FBQ3ZHO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0NBQXdDLFlBQVk7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEVBQUU7QUFDRjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxDQUFDO0FBQ0Q7QUFDQTtBQUNBLENBQUM7QUFDRDtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQSxDQUFDO0FBQ0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsNkNBQTZDO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSw2Q0FBNkM7QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQ0FBb0MsY0FBYztBQUNsRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTixvREFBb0QsYUFBYSxFQUFFLG9EQUFvRCxXQUFXLGdCQUFnQjtBQUNsSjtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsNENBQTRDLFNBQVM7QUFDckQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQSxDQUFDO0FBQ0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLHFCQUFxQjs7QUFFckI7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsWUFBWSxVQUFVO0FBQ3RCLFlBQVksR0FBRztBQUNmLFlBQVksU0FBUztBQUNyQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVksY0FBYztBQUMxQixZQUFZLGlCQUFpQjtBQUM3QixZQUFZLFVBQVU7QUFDdEIsWUFBWSxHQUFHO0FBQ2YsWUFBWSxTQUFTO0FBQ3JCLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGNBQWM7QUFDMUIsWUFBWSxpQkFBaUI7QUFDN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixjQUFjLE9BQU87QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBLDJEQUEyRCxPQUFPO0FBQ2xFO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixjQUFjLFFBQVE7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixjQUFjLFNBQVM7QUFDdkI7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLDJDQUEyQyxTQUFTO0FBQ3BEO0FBQ0E7O0FBRUE7QUFDQSxLQUFLO0FBQ0w7QUFDQTs7QUFFQSxpQkFBaUIsWUFBWTtBQUM3Qjs7QUFFQTtBQUNBLDZEQUE2RDtBQUM3RCxpRUFBaUU7QUFDakUscUVBQXFFO0FBQ3JFLHlFQUF5RTtBQUN6RTtBQUNBLDREQUE0RCxTQUFTO0FBQ3JFO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixZQUFZLFVBQVU7QUFDdEIsWUFBWSxHQUFHO0FBQ2YsY0FBYyxjQUFjO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsWUFBWSxpQkFBaUI7QUFDN0IsWUFBWSxVQUFVO0FBQ3RCLFlBQVksR0FBRztBQUNmLGNBQWMsY0FBYztBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVksaUJBQWlCO0FBQzdCLFlBQVksVUFBVTtBQUN0QixZQUFZLEdBQUc7QUFDZixZQUFZLFNBQVM7QUFDckIsY0FBYyxjQUFjO0FBQzVCO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCw2REFBNkQsWUFBWTtBQUN6RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVksaUJBQWlCO0FBQzdCLGNBQWMsY0FBYztBQUM1QjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxFQUFFOztBQUVGO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQ7QUFDbkQsbURBQW1EO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZDQUE2QyxtQkFBbUIsT0FBTyxHQUFHO0FBQzFFO0FBQ0EsWUFBWTtBQUNaLG9EQUFvRCxHQUFHO0FBQ3ZEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBLHVDQUF1QyxnQkFBZ0IsR0FBRyxlQUFlLEdBQUcsYUFBYTtBQUN6RjtBQUNBLHFDQUFxQyxHQUFHO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWCxVQUFVO0FBQ1YsMkNBQTJDLEdBQUc7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkNBQTJDLFVBQVUsMkNBQTJDLGNBQWMsS0FBSyxnQkFBZ0IsU0FBUyxpQkFBaUIsTUFBTTtBQUNuSyx5QkFBeUI7QUFDekIsdUJBQXVCO0FBQ3ZCLHNCQUFzQjtBQUN0Qiw4QkFBOEI7QUFDOUIsc0JBQXNCO0FBQ3RCLDZCQUE2QixrQkFBa0I7QUFDL0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1AsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQSxTQUFTO0FBQ1QsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLFNBQVM7QUFDVCxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxREFBcUQsNkJBQTZCO0FBQ2xGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQix1QkFBdUI7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLDZCQUE2Qjs7QUFFN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0NBQWtDLElBQUksbUJBQW1CLFFBQVE7QUFDakU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtEQUErRCwyQkFBMkI7QUFDMUY7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBLCtDQUErQyxRQUFRLHFDQUFxQyxrQkFBa0I7QUFDOUc7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRCxRQUFRLE1BQU0sWUFBWSx3Q0FBd0MsZ0JBQWdCO0FBQ25JO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLCtEQUErRCxRQUFRO0FBQ3ZFO0FBQ0E7QUFDQSw0QkFBNEIsU0FBUyxVQUFVLG1CQUFtQixHQUFHLGlCQUFpQixHQUFHLGlDQUFpQyxzQkFBc0IsR0FBRyx5QkFBeUIsUUFBUSxZQUFZLHlCQUF5QjtBQUN6TjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSx5RkFBeUYsU0FBUyxXQUFXLFlBQVk7QUFDekg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQ0FBaUMsSUFBSSxNQUFNLGlCQUFpQixHQUFHLGNBQWMsVUFBVSxRQUFRO0FBQy9GO0FBQ0Esd0NBQXdDO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOLDJDQUEyQyxRQUFRLHFEQUFxRCxTQUFTLE1BQU0saUJBQWlCLEdBQUcsY0FBYyxVQUFVLFFBQVE7QUFDM0s7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBFQUEwRTtBQUMxRSw0QkFBNEIsUUFBUSxFQUFFLGlDQUFpQyxXQUFXLFlBQVksOENBQThDLFdBQVcsaUJBQWlCLHlEQUF5RDtBQUNqTztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUVBQXVFO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNOztBQUVOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLGdCQUFnQiwwQkFBMEIsbUJBQW1CLEdBQUcsWUFBWTtBQUN6SDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUixnRUFBZ0UsU0FBUywrQ0FBK0MsU0FBUyxXQUFXLGFBQWE7QUFDeko7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSw4REFBOEQ7QUFDOUQ7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsdUNBQXVDLElBQUksWUFBWSxTQUFTLDRCQUE0QixpRUFBaUU7QUFDN0o7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsSUFBSSxHQUFHLHdCQUF3QixTQUFTLHlCQUF5QixRQUFRLFNBQVMsVUFBVSxnQkFBZ0IsR0FBRyxjQUFjO0FBQ3pKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNULFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF5QyxvQkFBb0IsOEJBQThCLHFEQUFxRDtBQUNoSjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBLDBGQUEwRiw4RUFBOEUsZUFBZSxtQkFBbUI7QUFDMU07QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLDJDQUEyQyxNQUFNO0FBQ2pEO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLE9BQU8sR0FBRyxXQUFXLFNBQVMsWUFBWSxRQUFRLGVBQWUsV0FBVyxlQUFlO0FBQ3BJO0FBQ0E7QUFDQSxvRUFBb0U7QUFDcEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHdCQUF3QjtBQUM1QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUixzQkFBc0Isd0JBQXdCO0FBQzlDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLHdCQUF3QjtBQUM5QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLHdCQUF3QjtBQUM5QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1YsaUZBQWlGLE1BQU07QUFDdkY7QUFDQTtBQUNBO0FBQ0EsK0NBQStDLElBQUksR0FBRyxnQkFBZ0IsU0FBUyxpQkFBaUIsUUFBUSxRQUFRO0FBQ2hIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7O0FBRUEsNkJBQTZCOztBQUU3QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHFCQUFxQjtBQUN6QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLG9CQUFvQjtBQUM1QztBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0Esa0VBQWtFLFFBQVE7QUFDMUU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0JBQStCLFNBQVMsVUFBVSxtQkFBbUIsR0FBRyxpQkFBaUIsR0FBRyxpQ0FBaUMsc0JBQXNCLEdBQUcseUJBQXlCLFFBQVEsWUFBWSx5QkFBeUI7QUFDNU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1QsT0FBTztBQUNQLHFCQUFxQixTQUFTLElBQUksWUFBWTtBQUM5QztBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscURBQXFELEtBQUsseUNBQXlDLE1BQU0sdUNBQXVDLE9BQU87QUFDdko7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLG1CQUFtQjtBQUN6QztBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSwwQ0FBMEMsSUFBSSxZQUFZLFNBQVMsNEJBQTRCLGlFQUFpRTtBQUNoSztBQUNBO0FBQ0E7QUFDQTtBQUNBLCtCQUErQixJQUFJLEdBQUcsd0JBQXdCLFNBQVMseUJBQXlCLFFBQVEsU0FBUyxVQUFVLGdCQUFnQixHQUFHLGNBQWM7QUFDNUo7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1QsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0Q0FBNEMsdUJBQXVCLHFCQUFxQiwyREFBMkQ7QUFDbko7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsbUJBQW1CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixtQkFBbUI7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixtQkFBbUI7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixtQkFBbUI7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1YsaUZBQWlGLE1BQU07QUFDdkY7QUFDQTtBQUNBLG1EQUFtRCxHQUFHO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtFQUFrRSxrQkFBa0Isa0JBQWtCLGtCQUFrQjtBQUN4SDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLDhDQUE4QyxNQUFNO0FBQ3BEO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRDQUE0QyxNQUFNLGtCQUFrQixXQUFXLFNBQVMsWUFBWSxRQUFRLGNBQWM7QUFDMUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSx1QkFBdUI7QUFDdkIsMEJBQTBCO0FBQzFCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLHNFQUFzRSxLQUFLLDRCQUE0QixNQUFNO0FBQzdHOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSwrRUFBK0UsWUFBWSxJQUFJLFNBQVM7QUFDeEc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFnQixnQ0FBZ0M7QUFDaEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx3Q0FBd0MsMkVBQTJFO0FBQ25IO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWLHlDQUF5QyxhQUFhO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOLGlDQUFpQyxLQUFLO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxXQUFXLFNBQVMsV0FBVztBQUMvRDtBQUNBLHdDQUF3QyxrQkFBa0IsS0FBSyxXQUFXO0FBQzFFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLEtBQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLDJCQUEyQixnQ0FBZ0MscUJBQXFCO0FBQ2xHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtCQUErQixNQUFNLHVCQUF1QixTQUFTO0FBQ3JFO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCx1QkFBdUI7QUFDdkIsMEJBQTBCO0FBQzFCO0FBQ0Esc0NBQXNDLE1BQU07QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5RUFBeUUsV0FBVyxVQUFVLE1BQU0sUUFBUSxRQUFRO0FBQ3BIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQSxnREFBZ0QsTUFBTTtBQUN0RCxPQUFPO0FBQ1A7QUFDQSxnREFBZ0QsTUFBTTtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBOEIsaUJBQWlCLEdBQUcsZ0NBQWdDLDhCQUE4QixLQUFLO0FBQ3JIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxpRUFBaUUsa0JBQWtCLEtBQUssZ0JBQWdCLE1BQU0sTUFBTTtBQUNwSCxPQUFPO0FBQ1A7QUFDQSxrRUFBa0Usa0JBQWtCLEtBQUssZ0JBQWdCLE1BQU0sTUFBTTtBQUNySDtBQUNBO0FBQ0EsU0FBUztBQUNULE9BQU87QUFDUDtBQUNBLDJDQUEyQyxNQUFNO0FBQ2pEO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBLG9GQUFvRixXQUFXLFNBQVMsWUFBWSxNQUFNLFFBQVE7QUFDbEk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixNQUFNO0FBQzVCO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBLDBGQUEwRix1QkFBdUI7QUFDakg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXOztBQUVYO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiLFlBQVk7QUFDWixxQ0FBcUMsTUFBTTtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1YsbUNBQW1DLE1BQU07QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsS0FBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxvREFBb0QseUJBQXlCO0FBQzdFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtELHFCQUFxQiw4QkFBOEIsTUFBTSxHQUFHLElBQUk7QUFDbEg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsU0FBUztBQUNULFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0RBQXdELFVBQVU7QUFDbEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixpQkFBaUIsU0FBUyxNQUFNO0FBQzVELDBDQUEwQyxTQUFTO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWLGdFQUFnRSxZQUFZO0FBQzVFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtCQUErQixNQUFNLDhDQUE4Qyx5RkFBeUY7QUFDNUssa0JBQWtCLE1BQU07QUFDeEI7QUFDQSxzREFBc0Q7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxvREFBb0Q7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxpREFBaUQsTUFBTTtBQUN2RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsWUFBWSxHQUFHLFVBQVUsYUFBYSxNQUFNO0FBQ3hFO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBOztBQUVBLG9EQUFvRDtBQUNwRDtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVEQUF1RCxNQUFNO0FBQzdEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTs7QUFFTixxREFBcUQsU0FBUztBQUM5RDtBQUNBO0FBQ0EseUVBQXlFLGFBQWEsU0FBUztBQUMvRjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpRUFBaUU7QUFDakUsYUFBYTtBQUNiO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLFdBQVcsR0FBRyxTQUFTLElBQUksRUFBRTtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLHFCQUFxQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isb0JBQW9CO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixhQUFhO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsYUFBYTtBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixhQUFhO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsYUFBYTtBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQ0FBaUMsZ0JBQWdCO0FBQ2pEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1QkFBdUIsYUFBYTtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGFBQWE7QUFDakM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixhQUFhO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsYUFBYTtBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsYUFBYTtBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGFBQWE7QUFDakM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixhQUFhO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixhQUFhO0FBQ25DO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIsdUJBQXVCO0FBQ2pEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkJBQTJCO0FBQzNCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGFBQWE7QUFDakM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QjtBQUM5QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isa0JBQWtCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IscUJBQXFCO0FBQ3pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxlQUFlLFNBQVM7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLG9CQUFvQjtBQUNwQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHVDQUF1QztBQUMzRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQiwwQkFBMEI7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxzQkFBc0I7QUFDakM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QztBQUM5QztBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCx1REFBdUQ7QUFDdkQ7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wscURBQXFEO0FBQ3JEO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsbURBQW1EO0FBQ25EO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsZ0RBQWdEO0FBQ2hEO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLOztBQUVMO0FBQ0Esa0RBQWtEO0FBQ2xEO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsb0RBQW9EO0FBQ3BEO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLHVEQUF1RDtBQUN2RDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLGdEQUFnRDtBQUNoRDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLHFEQUFxRDtBQUNyRDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsb0RBQW9EO0FBQ3BEO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wseURBQXlEO0FBQ3pEO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCxnREFBZ0Q7QUFDaEQ7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCxpREFBaUQ7QUFDakQ7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSzs7QUFFTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLEVBQUUsTUFBTSxFQUFFO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtEO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGNBQWM7QUFDbEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCLElBQUk7QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDLFFBQVE7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLHNDQUFzQztBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9DQUFvQzs7QUFFcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELFdBQVc7QUFDL0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUEwQjtBQUMxQjtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTs7QUFFQTs7QUFFQTtBQUNBLHlCQUF5QixHQUFHLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFOztBQUVwRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLElBQUk7QUFDN0M7QUFDQSxHQUFHLElBQUk7QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHLElBQUk7QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFEQUFxRCxLQUFLO0FBQzFEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBbUMsSUFBSTtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTs7QUFFQSw2Q0FBNkM7QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLHNCQUFzQiw2QkFBNkI7QUFDbkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IsdUJBQXVCO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsd0JBQXdCO0FBQ3BEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwRUFBMEUsNEJBQTRCO0FBQ3RHO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLG1CQUFtQjtBQUN6RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTCwyQ0FBMkMsTUFBTTtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLE1BQU07QUFDbkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixvQkFBb0I7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsV0FBVztBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEM7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsd0RBQXdEO0FBQ3hEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELFNBQVMsSUFBSSx3QkFBd0IsSUFBSSwwQkFBMEIsWUFBWSxnQkFBZ0IsR0FBRyxpQkFBaUI7QUFDdks7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDO0FBQy9DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLG1CQUFtQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdFQUF3RSxVQUFVO0FBQ2xGO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTiw4REFBOEQsVUFBVTtBQUN4RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLG1GQUFtRixnQ0FBZ0M7QUFDbkg7QUFDQSxzR0FBc0csa0JBQWtCO0FBQ3hIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEMsVUFBVSxtQ0FBbUMsc0NBQXNDO0FBQ2pJO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyREFBMkQsVUFBVSxLQUFLLE1BQU07QUFDaEYsT0FBTztBQUNQO0FBQ0EsMkNBQTJDLCtCQUErQjtBQUMxRTtBQUNBLDJDQUEyQyxVQUFVO0FBQ3JEO0FBQ0EsOENBQThDLFVBQVU7QUFDeEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWCxTQUFTO0FBQ1Q7QUFDQSx5REFBeUQsVUFBVSxFQUFFLElBQUksTUFBTTtBQUMvRSxTQUFTO0FBQ1Q7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0gsNkNBQTZDLFVBQVUsV0FBVyxxQ0FBcUM7QUFDdkc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLHFCQUFxQixjQUFjO0FBQ3pFLFFBQVEsZ0JBQWdCLDhCQUE4QjtBQUN0RDtBQUNBO0FBQ0E7QUFDQSx1REFBdUQ7QUFDdkQ7QUFDQSwwREFBMEQsU0FBUyxFQUFFLFVBQVUsSUFBSSxXQUFXLGdCQUFnQixzQkFBc0I7QUFDcEk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWLG9FQUFvRSxVQUFVO0FBQzlFO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtDQUFrQyxPQUFPLFdBQVcsc0JBQXNCLFlBQVksb0JBQW9CLE9BQU8sZ0JBQWdCO0FBQ2pJLHlDQUF5QyxXQUFXO0FBQ3BEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQSwrQ0FBK0MsY0FBYyxFQUFFLGVBQWUsSUFBSSxpQkFBaUIsWUFBWSxXQUFXO0FBQzFIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0EsU0FBUztBQUNULE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU8sc0RBQXNEO0FBQzdEO0FBQ0EsT0FBTyxFQUFFO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ04sMkJBQTJCLFdBQVcsNEJBQTRCLGFBQWE7QUFDL0U7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUixxQkFBcUIsWUFBWSx3QkFBd0IsTUFBTTtBQUMvRDtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1QkFBdUIsWUFBWTtBQUNuQyxVQUFVO0FBQ1YsK0JBQStCLFlBQVksTUFBTSxnRkFBZ0Y7QUFDakk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxvQkFBb0IsNkJBQTZCO0FBQ2pEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0EsU0FBUztBQUNULE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlCQUFpQixXQUFXO0FBQzVCO0FBQ0E7QUFDQTtBQUNBLHdDQUF3QyxVQUFVO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxzQ0FBc0MsVUFBVTtBQUNoRDtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELE9BQU87QUFDM0Q7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELE9BQU8sS0FBSyxPQUFPLG1CQUFtQixjQUFjLFVBQVUsc0NBQXNDO0FBQ3hKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLG1CQUFtQixZQUFZLCtCQUErQixxQkFBcUIsa0JBQWtCLG1CQUFtQjtBQUN4SDtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1IsdURBQXVELFlBQVk7QUFDbkU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUJBQXFCLG1CQUFtQixrQkFBa0IsTUFBTTtBQUNoRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWCxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLDRCQUE0QixVQUFVO0FBQ2pELFVBQVU7QUFDVjtBQUNBLFVBQVU7QUFDVixvREFBb0QsVUFBVTtBQUM5RDtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBLHFEQUFxRCxzR0FBc0csV0FBVyxNQUFNO0FBQzVLLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTywyQ0FBMkMsTUFBTTtBQUN4RCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0EscUNBQXFDLE9BQU8sMkJBQTJCLDJIQUEySCxpQkFBaUIsNkVBQTZFLE9BQU8sdUNBQXVDO0FBQzlVO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRCxVQUFVO0FBQzNEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYixXQUFXLE1BQU0sVUFBVSxnQ0FBZ0MsSUFBSSxhQUFhLGVBQWUsR0FBRyxjQUFjO0FBQzVHLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsTUFBTSxVQUFVLG1DQUFtQyxJQUFJO0FBQ2xFLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLDhDQUE4QyxHQUFHLHdDQUF3QyxRQUFRLFVBQVU7QUFDcEo7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0RBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNENBQTRDLFNBQVM7QUFDckQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrREFBa0QsSUFBSTtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF5QyxxREFBcUQ7QUFDOUY7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZSxpQ0FBaUMsSUFBSSxhQUFhLFlBQVksR0FBRyxlQUFlO0FBQy9GLGNBQWM7QUFDZDtBQUNBLHFEQUFxRCxjQUFjO0FBQ25FO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEMsTUFBTTtBQUNwRCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsaUVBQWlFLE1BQU07QUFDdkUsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1AseURBQXlELHNCQUFzQjtBQUMvRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLDZEQUE2RCwyQkFBMkI7QUFDeEY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QyxNQUFNO0FBQ3BELE9BQU87QUFDUDtBQUNBLE9BQU87QUFDUCw2Q0FBNkMsTUFBTTtBQUNuRCxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxDQUFDLG9DQUFvQzs7QUFFckM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUMsOENBQThDOztBQUUvQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUMsMENBQTBDOztBQUUzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlCQUFpQjtBQUNqQjtBQUNBO0FBQ0EsaUJBQWlCO0FBQ2pCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDLFFBQVEsR0FBRyxZQUFZLE9BQU8sS0FBSztBQUNuRTtBQUNBLEdBQUc7QUFDSDs7QUFFQTs7QUFFQTs7QUFFQTs7QUFFQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUEseUNBQXlDOztBQUV6Qzs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhLG9CQUFvQjtBQUNqQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSx5Q0FBeUM7QUFDdEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDO0FBQy9DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDO0FBQzlDO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0RBQXNELFlBQVk7QUFDbEU7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlFQUFpRTtBQUNqRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWEsa0RBQWtEO0FBQy9EOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOERBQThEO0FBQzlEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0I7QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZSxFQUFFLGtCQUFrQixHQUFHO0FBQ3RDO0FBQ0EsYUFBYSxFQUFFLGtCQUFrQixHQUFHLHlCQUF5QjtBQUM3RCxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjLCtCQUErQixFQUFFLDhCQUE4QjtBQUM3RSxJQUFJO0FBQ0o7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhLHlDQUF5QyxHQUFHLDhCQUE4QjtBQUN2Rjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUM7QUFDRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRyxXQUFXLG1CQUFtQjtBQUNqQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQztBQUNoQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0lBQWtJO0FBQ2xJO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7QUFDQTtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUM7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLFdBQVcsR0FBRywyQkFBMkI7QUFDckQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLElBQUksRUFBRSxVQUFVLEVBQUUsTUFBTTtBQUNwQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1QsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEI7QUFDMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTyxJQUFJO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBeUMsY0FBYyxJQUFJLG1CQUFtQix3Q0FBd0MsY0FBYyxVQUFVLGlDQUFpQyxjQUFjLGlDQUFpQyxhQUFhLHVDQUF1QztBQUNsUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw2Q0FBNkMsZUFBZSxnQ0FBZ0MsVUFBVTtBQUN0RztBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixxQkFBcUIsR0FBRyxlQUFlLHFCQUFxQixlQUFlO0FBQ25HO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLG9CQUFvQiw0QkFBNEI7QUFDaEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdDQUF3QyxVQUFVO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOERBQThELHVCQUF1QixLQUFLLHlCQUF5QjtBQUNuSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsbUJBQW1CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwREFBMEQsaUJBQWlCLFNBQVMsUUFBUTtBQUM1RixpRUFBaUUscUJBQXFCLFNBQVMsUUFBUTtBQUN2RztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQSwwQkFBMEIsa0NBQWtDO0FBQzVELCtDQUErQyx5QkFBeUIsU0FBUyxRQUFRO0FBQ3pGO0FBQ0E7QUFDQTtBQUNBLDBCQUEwQixxQ0FBcUM7QUFDL0QsOENBQThDLDRCQUE0QixTQUFTLFFBQVE7QUFDM0Y7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0EseURBQXlELElBQUk7QUFDN0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQ0FBK0MsSUFBSTtBQUNuRDtBQUNBO0FBQ0EsdUNBQXVDLHNCQUFzQjtBQUM3RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBLHNFQUFzRSxVQUFVO0FBQ2hGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBLHFEQUFxRCxZQUFZLEVBQUUsWUFBWSxHQUFHLFlBQVk7QUFDOUY7QUFDQTtBQUNBO0FBQ0Esd0NBQXdDLGFBQWE7QUFDckQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdDQUF3QyxhQUFhO0FBQ3JEO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBLHVEQUF1RCxZQUFZO0FBQ25FO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QyxJQUFJO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUNBQXFDO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsT0FBTztBQUNQLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1osNEJBQTRCLFFBQVEsZ0JBQWdCLFlBQVk7QUFDaEU7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSwyQ0FBMkMsb0VBQW9FO0FBQy9HO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLG1CQUFtQiw4Q0FBOEMsZ0JBQWdCLHVDQUF1QyxhQUFhLFlBQVksR0FBRyx5QkFBeUIsS0FBSyxnQkFBZ0I7QUFDbE07QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNDQUFzQyxLQUFLO0FBQzNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxHQUFHO0FBQ25DO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsK0JBQStCO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixzQkFBc0I7QUFDOUM7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCO0FBQ0Esc0JBQXNCO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsQ0FBQyx1QkFBdUI7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUM7QUFDRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIscUNBQXFDO0FBQy9EO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQyxLQUFLLFNBQVMsUUFBUTtBQUN6RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0EscUNBQXFDLG9CQUFvQixvQ0FBb0MsV0FBVyxLQUFLLHVDQUF1QztBQUNwSjtBQUNBLEdBQUc7QUFDSCx5Q0FBeUM7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSyxJQUFJO0FBQ1Q7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLHVDQUF1QyxlQUFlO0FBQ3RELDBCQUEwQixzQkFBc0IsRUFBRSxvQkFBb0IsR0FBRyxXQUFXLEdBQUcsVUFBVSxHQUFHLE9BQU8sR0FBRyxZQUFZLEdBQUcsS0FBSztBQUNsSTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0VBQXNFLHFDQUFxQztBQUMzRztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QiwyQkFBMkI7QUFDbkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLG9CQUFvQixtQkFBbUI7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFDQUFxQyxlQUFlLGlDQUFpQyxrQkFBa0I7QUFDdkc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUNBQW1DLFVBQVUsR0FBRyx3Q0FBd0MsRUFBRSwrQ0FBK0MsRUFBRSwyQ0FBMkMsR0FBRyxjQUFjLEdBQUcsK0NBQStDLGFBQWEsZUFBZSxFQUFFLHNEQUFzRDtBQUM3VTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbURBQW1ELGlCQUFpQjtBQUNwRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsdUNBQXVDLE1BQU07QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVixpRkFBaUYsTUFBTTtBQUN2RjtBQUNBO0FBQ0E7QUFDQSxzQ0FBc0Msa0JBQWtCLEVBQUUscUpBQXFKLE1BQU0sMENBQTBDLEVBQUUsSUFBSTs7QUFFclE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLHNCQUFzQiwrQkFBK0I7QUFDckQ7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnSEFBZ0gsZ0JBQWdCO0FBQ2hJO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0hBQXNILElBQUk7QUFDMUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZJQUE2SSxtQkFBbUI7QUFDaEs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYLFNBQVM7QUFDVDtBQUNBO0FBQ0Esb0dBQW9HLGVBQWUsY0FBYyxjQUFjO0FBQy9JO0FBQ0E7QUFDQSxXQUFXO0FBQ1gsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFEQUFxRCxZQUFZLFVBQVUsNkJBQTZCO0FBQ3hHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSx1REFBdUQsbUJBQW1CLHFCQUFxQiwyQkFBMkI7QUFDMUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0I7QUFDbEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlFQUFpRSxhQUFhLEtBQUssV0FBVztBQUM5RjtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFGQUFxRixhQUFhLEtBQUssV0FBVztBQUNsSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDREQUE0RCxhQUFhLEtBQUssV0FBVztBQUN6RjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxNQUFNO0FBQ04sdUZBQXVGLGFBQWEsUUFBUSxzQkFBc0I7QUFDbEk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTs7QUFFQSwyQkFBMkI7O0FBRTNCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlFQUFpRSwyQkFBMkI7QUFDNUY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxvQ0FBb0MsT0FBTyxhQUFhLFdBQVc7QUFDbkU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLDZCQUE2QixTQUFTLFdBQVcsWUFBWTtBQUM3RDtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtDQUFrQyx1QkFBdUI7QUFDekQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsZ0VBQWdFLHNDQUFzQztBQUN0RztBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBLHVEQUF1RDtBQUN2RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSx5REFBeUQsV0FBVztBQUNwRTtBQUNBO0FBQ0Esc0JBQXNCLFlBQVksVUFBVSxtQkFBbUIsR0FBRyxpQkFBaUIsR0FBRyxpQ0FBaUMsc0JBQXNCLEdBQUcseUJBQXlCLFFBQVEsUUFBUSxtQkFBbUIsSUFBSSxpQkFBaUIsYUFBYSxTQUFTO0FBQ3ZQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0EsbUZBQW1GLFNBQVMsV0FBVyxZQUFZO0FBQ25IO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxQ0FBcUMsU0FBUyxXQUFXLFlBQVk7QUFDckU7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSwrQkFBK0IsU0FBUyxNQUFNLGlCQUFpQixHQUFHLGNBQWMsVUFBVSxXQUFXLE9BQU8sUUFBUTtBQUNwSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZDQUE2QyxLQUFLO0FBQ2xEO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixRQUFRLEVBQUUsaUNBQWlDLFdBQVcsWUFBWSw4Q0FBOEMsV0FBVztBQUN2SjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLGNBQWMsdUJBQXVCLFlBQVk7QUFDdkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0RBQWdELE9BQU87QUFDdkQ7QUFDQTtBQUNBO0FBQ0EsZ0RBQWdELGVBQWUsb0JBQW9CLFlBQVk7QUFDL0Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtELFdBQVc7QUFDN0Q7QUFDQTtBQUNBLG1EQUFtRCx3QkFBd0IsU0FBUyxXQUFXO0FBQy9GO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxnQkFBZ0IsbUNBQW1DLGlCQUFpQixHQUFHLDhCQUE4QixHQUFHLFlBQVk7QUFDbks7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQ0FBK0MsZ0JBQWdCLDBCQUEwQiw4QkFBOEIsR0FBRyxZQUFZO0FBQ3RJO0FBQ0E7QUFDQSxvREFBb0QscUJBQXFCLDBCQUEwQixvQkFBb0IsR0FBRyxpQkFBaUI7QUFDM0k7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkJBQTZCO0FBQzdCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBLDZCQUE2QixjQUFjO0FBQzNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsY0FBYztBQUMxQztBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBbUMsU0FBUztBQUM1QztBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDLFNBQVM7QUFDekM7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxTQUFTO0FBQ3pDO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsaUNBQWlDLFNBQVM7QUFDMUM7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlDQUFpQyxTQUFTO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdEQUFnRDtBQUNoRCxRQUFRO0FBQ1I7QUFDQTtBQUNBLGlEQUFpRDtBQUNqRDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBeUMsU0FBUztBQUNsRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0Esb0JBQW9CLFNBQVM7QUFDN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLGlDQUFpQyxJQUFJO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRTRvQjtBQUM1b0IiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9teTNkLy4vc3JjL2luZGV4LmpzIiwid2VicGFjazovL215M2QvLi9ub2RlX21vZHVsZXMvaGxzLmpzL2Rpc3QvaGxzLm1qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgSGxzIGZyb20gXCJobHMuanNcIjtcblxuLy8gVGltZVJhbmdlc1N0dWJcbmNsYXNzIFRpbWVSYW5nZXNTdHViIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhpcy5fcmFuZ2VzID0gW107XG4gICAgfVxuXG4gICAgZ2V0IGxlbmd0aCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3Jhbmdlcy5sZW5ndGg7XG4gICAgfVxuXG4gICAgc3RhcnQoaW5kZXgpIHtcbiAgICAgICAgaWYgKGluZGV4IDwgMCB8fCBpbmRleCA+PSB0aGlzLl9yYW5nZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCdJbnZhbGlkIGluZGV4JywgJ0luZGV4U2l6ZUVycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX3Jhbmdlc1tpbmRleF0uc3RhcnQ7XG4gICAgfVxuXG4gICAgZW5kKGluZGV4KSB7XG4gICAgICAgIGlmIChpbmRleCA8IDAgfHwgaW5kZXggPj0gdGhpcy5fcmFuZ2VzLmxlbmd0aCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IERPTUV4Y2VwdGlvbignSW52YWxpZCBpbmRleCcsICdJbmRleFNpemVFcnJvcicpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9yYW5nZXNbaW5kZXhdLmVuZDtcbiAgICB9XG5cbiAgICAvLyBIZWxwZXIgbWV0aG9kIHRvIGFkZCBhIHJhbmdlXG4gICAgX2FkZFJhbmdlKHN0YXJ0LCBlbmQpIHtcbiAgICAgICAgdGhpcy5fcmFuZ2VzLnB1c2goeyBzdGFydCwgZW5kIH0pO1xuICAgICAgICB0aGlzLl9ub3JtYWxpemVSYW5nZXMoKTtcbiAgICB9XG5cbiAgICAvLyBIZWxwZXIgbWV0aG9kIHRvIHJlbW92ZSByYW5nZXMgdGhhdCBvdmVybGFwIHdpdGggYSBnaXZlbiByYW5nZVxuICAgIF9yZW1vdmVSYW5nZShzdGFydCwgZW5kKSB7XG4gICAgICAgIGxldCB1cGRhdGVkUmFuZ2VzID0gW107XG4gICAgICAgIGZvciAobGV0IHJhbmdlIG9mIHRoaXMuX3Jhbmdlcykge1xuICAgICAgICAgICAgaWYgKHJhbmdlLmVuZCA8PSBzdGFydCB8fCByYW5nZS5zdGFydCA+PSBlbmQpIHtcbiAgICAgICAgICAgICAgICAvLyBObyBvdmVybGFwLCBrZWVwIHRoZSByYW5nZSBhcyBpc1xuICAgICAgICAgICAgICAgIHVwZGF0ZWRSYW5nZXMucHVzaChyYW5nZSk7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHJhbmdlLnN0YXJ0IDwgc3RhcnQgJiYgcmFuZ2UuZW5kID4gZW5kKSB7XG4gICAgICAgICAgICAgICAgLy8gVGhlIHJhbmdlIGZ1bGx5IGNvdmVycyB0aGUgcmVtb3ZhbCByYW5nZSwgc3BsaXQgaW50byB0d28gcmFuZ2VzXG4gICAgICAgICAgICAgICAgdXBkYXRlZFJhbmdlcy5wdXNoKHsgc3RhcnQ6IHJhbmdlLnN0YXJ0LCBlbmQ6IHN0YXJ0IH0pO1xuICAgICAgICAgICAgICAgIHVwZGF0ZWRSYW5nZXMucHVzaCh7IHN0YXJ0OiBlbmQsIGVuZDogcmFuZ2UuZW5kIH0pO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChyYW5nZS5zdGFydCA+PSBzdGFydCAmJiByYW5nZS5lbmQgPD0gZW5kKSB7XG4gICAgICAgICAgICAgICAgLy8gVGhlIHJhbmdlIGlzIGVudGlyZWx5IHdpdGhpbiB0aGUgcmVtb3ZhbCByYW5nZSwgcmVtb3ZlIGl0XG4gICAgICAgICAgICAgICAgLy8gRG8gbm90IGFkZCB0byB1cGRhdGVkUmFuZ2VzXG4gICAgICAgICAgICB9IGVsc2UgaWYgKHJhbmdlLnN0YXJ0IDwgc3RhcnQgJiYgcmFuZ2UuZW5kID4gc3RhcnQgJiYgcmFuZ2UuZW5kIDw9IGVuZCkge1xuICAgICAgICAgICAgICAgIC8vIFRoZSByYW5nZSBvdmVybGFwcyB3aXRoIHRoZSByZW1vdmFsIHJhbmdlIG9uIHRoZSBsZWZ0XG4gICAgICAgICAgICAgICAgdXBkYXRlZFJhbmdlcy5wdXNoKHsgc3RhcnQ6IHJhbmdlLnN0YXJ0LCBlbmQ6IHN0YXJ0IH0pO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChyYW5nZS5zdGFydCA+PSBzdGFydCAmJiByYW5nZS5zdGFydCA8IGVuZCAmJiByYW5nZS5lbmQgPiBlbmQpIHtcbiAgICAgICAgICAgICAgICAvLyBUaGUgcmFuZ2Ugb3ZlcmxhcHMgd2l0aCB0aGUgcmVtb3ZhbCByYW5nZSBvbiB0aGUgcmlnaHRcbiAgICAgICAgICAgICAgICB1cGRhdGVkUmFuZ2VzLnB1c2goeyBzdGFydDogZW5kLCBlbmQ6IHJhbmdlLmVuZCB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9yYW5nZXMgPSB1cGRhdGVkUmFuZ2VzO1xuICAgIH1cblxuICAgIC8vIE5vcm1hbGl6ZSBhbmQgbWVyZ2Ugb3ZlcmxhcHBpbmcgcmFuZ2VzXG4gICAgX25vcm1hbGl6ZVJhbmdlcygpIHtcbiAgICAgICAgdGhpcy5fcmFuZ2VzLnNvcnQoKGEsIGIpID0+IGEuc3RhcnQgLSBiLnN0YXJ0KTtcbiAgICAgICAgbGV0IG5vcm1hbGl6ZWQgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgcmFuZ2Ugb2YgdGhpcy5fcmFuZ2VzKSB7XG4gICAgICAgICAgICBpZiAobm9ybWFsaXplZC5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgICAgICBub3JtYWxpemVkLnB1c2gocmFuZ2UpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBsZXQgbGFzdCA9IG5vcm1hbGl6ZWRbbm9ybWFsaXplZC5sZW5ndGggLSAxXTtcbiAgICAgICAgICAgICAgICBpZiAocmFuZ2Uuc3RhcnQgPD0gbGFzdC5lbmQpIHtcbiAgICAgICAgICAgICAgICAgICAgbGFzdC5lbmQgPSBNYXRoLm1heChsYXN0LmVuZCwgcmFuZ2UuZW5kKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVkLnB1c2gocmFuZ2UpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9yYW5nZXMgPSBub3JtYWxpemVkO1xuICAgIH1cbn1cblxuLy8gVGV4dFRyYWNrU3R1YlxuY2xhc3MgVGV4dFRyYWNrU3R1YiBleHRlbmRzIEV2ZW50VGFyZ2V0IHtcbiAgICBjb25zdHJ1Y3RvcihraW5kID0gJycsIGxhYmVsID0gJycsIGxhbmd1YWdlID0gJycpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5raW5kID0ga2luZDtcbiAgICAgICAgdGhpcy5sYWJlbCA9IGxhYmVsO1xuICAgICAgICB0aGlzLmxhbmd1YWdlID0gbGFuZ3VhZ2U7XG4gICAgICAgIHRoaXMubW9kZSA9ICdkaXNhYmxlZCc7IC8vICdkaXNhYmxlZCcsICdoaWRkZW4nLCBvciAnc2hvd2luZydcbiAgICAgICAgdGhpcy5jdWVzID0gbmV3IFRleHRUcmFja0N1ZUxpc3RTdHViKCk7XG4gICAgICAgIHRoaXMuYWN0aXZlQ3VlcyA9IG5ldyBUZXh0VHJhY2tDdWVMaXN0U3R1YigpO1xuICAgIH1cblxuICAgIGFkZEN1ZShjdWUpIHtcbiAgICAgICAgdGhpcy5jdWVzLl9hZGQoY3VlKTtcbiAgICB9XG5cbiAgICByZW1vdmVDdWUoY3VlKSB7XG4gICAgICAgIHRoaXMuY3Vlcy5fcmVtb3ZlKGN1ZSk7XG4gICAgfVxufVxuXG4vLyBUZXh0VHJhY2tDdWVMaXN0U3R1YlxuY2xhc3MgVGV4dFRyYWNrQ3VlTGlzdFN0dWIge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLl9jdWVzID0gW107XG4gICAgfVxuXG4gICAgZ2V0IGxlbmd0aCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2N1ZXMubGVuZ3RoO1xuICAgIH1cblxuICAgIGl0ZW0oaW5kZXgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2N1ZXNbaW5kZXhdO1xuICAgIH1cblxuICAgIGdldEN1ZUJ5SWQoaWQpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2N1ZXMuZmluZChjdWUgPT4gY3VlLmlkID09PSBpZCkgfHwgbnVsbDtcbiAgICB9XG5cbiAgICBfYWRkKGN1ZSkge1xuICAgICAgICB0aGlzLl9jdWVzLnB1c2goY3VlKTtcbiAgICB9XG5cbiAgICBfcmVtb3ZlKGN1ZSkge1xuICAgICAgICBjb25zdCBpbmRleCA9IHRoaXMuX2N1ZXMuaW5kZXhPZihjdWUpO1xuICAgICAgICBpZiAoaW5kZXggIT09IC0xKSB7XG4gICAgICAgIHRoaXMuX2N1ZXMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIFtTeW1ib2wuaXRlcmF0b3JdKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3Vlc1tTeW1ib2wuaXRlcmF0b3JdKCk7XG4gICAgfVxufVxuXG5jbGFzcyBUZXh0VHJhY2tMaXN0U3R1YiBleHRlbmRzIEV2ZW50VGFyZ2V0IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5fdHJhY2tzID0gW107XG4gICAgfVxuXG4gICAgZ2V0IGxlbmd0aCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3RyYWNrcy5sZW5ndGg7XG4gICAgfVxuXG4gICAgaXRlbShpbmRleCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fdHJhY2tzW2luZGV4XTtcbiAgICB9XG5cbiAgICBfYWRkKHRyYWNrKSB7XG4gICAgICAgIHRoaXMuX3RyYWNrcy5wdXNoKHRyYWNrKTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnYWRkdHJhY2snKSk7XG4gICAgfVxuXG4gICAgX3JlbW92ZSh0cmFjaykge1xuICAgICAgICBjb25zdCBpbmRleCA9IHRoaXMuX3RyYWNrcy5pbmRleE9mKHRyYWNrKTtcbiAgICAgICAgaWYgKGluZGV4ICE9PSAtMSkge1xuICAgICAgICB0aGlzLl90cmFja3Muc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgncmVtb3ZldHJhY2snKSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBbU3ltYm9sLml0ZXJhdG9yXSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3RyYWNrc1tTeW1ib2wuaXRlcmF0b3JdKCk7XG4gICAgfVxufVxuXG4vLyBWaWRlb0VsZW1lbnRTdHViXG5jbGFzcyBWaWRlb0VsZW1lbnRTdHViIGV4dGVuZHMgRXZlbnRUYXJnZXQge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLl9jdXJyZW50VGltZSA9IDAuMDtcbiAgICAgICAgdGhpcy5kdXJhdGlvbiA9IE5hTjtcbiAgICAgICAgdGhpcy5wYXVzZWQgPSB0cnVlO1xuICAgICAgICB0aGlzLnBsYXliYWNrUmF0ZSA9IDEuMDtcbiAgICAgICAgdGhpcy52b2x1bWUgPSAxLjA7XG4gICAgICAgIHRoaXMubXV0ZWQgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gMDtcbiAgICAgICAgdGhpcy5uZXR3b3JrU3RhdGUgPSAwO1xuICAgICAgICB0aGlzLmJ1ZmZlcmVkID0gbmV3IFRpbWVSYW5nZXNTdHViKCk7XG4gICAgICAgIHRoaXMuc2Vla2luZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmxvb3AgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5hdXRvcGxheSA9IGZhbHNlO1xuICAgICAgICB0aGlzLmNvbnRyb2xzID0gZmFsc2U7XG4gICAgICAgIHRoaXMuZXJyb3IgPSBudWxsO1xuICAgICAgICB0aGlzLnNyYyA9ICcnO1xuICAgICAgICB0aGlzLnZpZGVvV2lkdGggPSAwO1xuICAgICAgICB0aGlzLnZpZGVvSGVpZ2h0ID0gMDtcbiAgICAgICAgdGhpcy50ZXh0VHJhY2tzID0gbmV3IFRleHRUcmFja0xpc3RTdHViKCk7XG5cbiAgICAgICAgdGhpcy5faXNQbGF5aW5nID0gZmFsc2U7XG5cbiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSA0OyAvLyBIQVZFX0VOT1VHSF9EQVRBXG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdsb2FkZWRtZXRhZGF0YScpKTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ2xvYWRlZGRhdGEnKSk7XG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdjYW5wbGF5JykpO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnY2FucGxheXRocm91Z2gnKSk7XG4gICAgICAgIH0sIDApO1xuICAgIH1cblxuICAgIGdldCBjdXJyZW50VGltZSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2N1cnJlbnRUaW1lO1xuICAgIH1cblxuICAgIHNldCBjdXJyZW50VGltZSh2YWx1ZSkge1xuICAgICAgICBpZiAodGhpcy5fY3VycmVudFRpbWUgIT0gdmFsdWUpIHtcbiAgICAgICAgICAgIHRoaXMuX2N1cnJlbnRUaW1lID0gdmFsdWU7XG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdzZWVrZWQnKSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwbGF5KCkge1xuICAgICAgICBpZiAodGhpcy5wYXVzZWQpIHtcbiAgICAgICAgICAgIHRoaXMucGF1c2VkID0gZmFsc2U7XG4gICAgICAgICAgICB0aGlzLl9pc1BsYXlpbmcgPSB0cnVlO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgncGxheScpKTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ3BsYXlpbmcnKSk7XG4gICAgICAgICAgICAvLyBTaW11bGF0ZSB0aW1ldXBkYXRlIGV2ZW50c1xuICAgICAgICAgICAgdGhpcy5fc2ltdWxhdGVUaW1lVXBkYXRlKCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSgpO1xuICAgIH1cblxuICAgIHBhdXNlKCkge1xuICAgICAgICBpZiAoIXRoaXMucGF1c2VkKSB7XG4gICAgICAgICAgICB0aGlzLnBhdXNlZCA9IHRydWU7XG4gICAgICAgICAgICB0aGlzLl9pc1BsYXlpbmcgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ3BhdXNlJykpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgY2FuUGxheVR5cGUodHlwZSkge1xuICAgICAgICAvLyBBc3N1bWUgYWxsIHR5cGVzIGFyZSBwbGF5YWJsZSBpbiB0aGlzIHN0dWJcbiAgICAgICAgcmV0dXJuICdwcm9iYWJseSc7XG4gICAgfVxuXG4gICAgX2dldE1lZGlhKCkge1xuICAgICAgICByZXR1cm4gbWVkaWFTb3VyY2VNYXBbdGhpcy5zcmNdO1xuICAgIH1cblxuICAgIC8vIFNpbXVsYXRlIHRpbWV1cGRhdGUgZXZlbnRzXG4gICAgX3NpbXVsYXRlVGltZVVwZGF0ZSgpIHtcbiAgICAgICAgaWYgKHRoaXMuX2lzUGxheWluZykge1xuICAgICAgICAgICAgLy8gU2ltdWxhdGUgdGltZSBwcm9ncmVzc2lvblxuICAgICAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgdmFyIGJ1ZmZlcmVkRW5kID0gMC4wO1xuICAgICAgICAgICAgICAgIFxuICAgICAgICAgICAgICAgIGNvbnN0IG1lZGlhID0gdGhpcy5fZ2V0TWVkaWEoKTtcbiAgICAgICAgICAgICAgICBpZiAobWVkaWEpIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKG1lZGlhLnNvdXJjZUJ1ZmZlcnMubGVuZ3RoICE9IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuYnVmZmVyZWQgPSBtZWRpYS5zb3VyY2VCdWZmZXJzLl9idWZmZXJzWzBdLmJ1ZmZlcmVkO1xuICAgICAgICAgICAgICAgICAgICAgICAgYnVmZmVyZWRFbmQgPSB0aGlzLmJ1ZmZlcmVkLmxlbmd0aCA9PSAwID8gMCA6IHRoaXMuYnVmZmVyZWQuZW5kKHRoaXMuYnVmZmVyZWQubGVuZ3RoIC0gMSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAvLyBDb25zdW1lIGJ1ZmZlcmVkIGRhdGFcbiAgICAgICAgICAgICAgICBpZiAodGhpcy5jdXJyZW50VGltZSA8IGJ1ZmZlcmVkRW5kKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIEFkdmFuY2UgY3VycmVudFRpbWVcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5fY3VycmVudFRpbWUgKz0gMC4xICogdGhpcy5wbGF5YmFja1JhdGU7IC8vIEluY3JlbWVudCBjdXJyZW50VGltZVxuICAgICAgICAgICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCd0aW1ldXBkYXRlJykpO1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIENvbnRpbnVlIHNpbXVsYXRpb25cbiAgICAgICAgICAgICAgICAgICAgdGhpcy5fc2ltdWxhdGVUaW1lVXBkYXRlKCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coXCJCdWZmZXIgdW5kZXJydW5cIik7XG4gICAgICAgICAgICAgICAgICAgIC8vIEJ1ZmZlciB1bmRlcnJ1blxuICAgICAgICAgICAgICAgICAgICB0aGlzLl9pc1BsYXlpbmcgPSBmYWxzZTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wYXVzZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCd3YWl0aW5nJykpO1xuICAgICAgICAgICAgICAgICAgICAvLyBUaGUgcGxheWVyIHNob3VsZCByZWFjdCBieSBidWZmZXJpbmcgbW9yZSBkYXRhXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSwgMTAwKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGFkZFRleHRUcmFjayhraW5kLCBsYWJlbCwgbGFuZ3VhZ2UpIHtcbiAgICAgICAgY29uc3QgdGV4dFRyYWNrID0gbmV3IFRleHRUcmFja1N0dWIoa2luZCwgbGFiZWwsIGxhbmd1YWdlKTtcbiAgICAgICAgdGhpcy50ZXh0VHJhY2tzLl9hZGQodGV4dFRyYWNrKTtcbiAgICAgICAgcmV0dXJuIHRleHRUcmFjaztcbiAgICB9XG59XG5cbi8vIE1lZGlhU291cmNlU3R1YlxuY2xhc3MgTWVkaWFTb3VyY2VTdHViIGV4dGVuZHMgRXZlbnRUYXJnZXQge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICBzdXBlcigpO1xuXG4gICAgICAgIHRoaXMuaW50ZXJuYWxJZCA9IG5leHRJbnRlcm5hbElkO1xuICAgICAgICBuZXh0SW50ZXJuYWxJZCArPSAxO1xuXG4gICAgICAgIHRoaXMuc291cmNlQnVmZmVycyA9IG5ldyBTb3VyY2VCdWZmZXJMaXN0U3R1YigpO1xuICAgICAgICB0aGlzLmFjdGl2ZVNvdXJjZUJ1ZmZlcnMgPSBuZXcgU291cmNlQnVmZmVyTGlzdFN0dWIoKTtcbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ2Nsb3NlZCc7XG4gICAgICAgIHRoaXMuX2R1cmF0aW9uID0gTmFOO1xuXG4gICAgICAgIC8vIFNpbXVsYXRlIGFzeW5jaHJvbm91cyBvcGVuaW5nIG9mIE1lZGlhU291cmNlXG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ29wZW4nO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnc291cmNlb3BlbicpKTtcbiAgICAgICAgfSwgMCk7XG4gICAgfVxuXG4gICAgc3RhdGljIGlzVHlwZVN1cHBvcnRlZChtaW1lVHlwZSkge1xuICAgICAgICAvLyBBc3N1bWUgYWxsIE1JTUUgdHlwZXMgYXJlIHN1cHBvcnRlZCBpbiB0aGlzIHN0dWJcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgYWRkU291cmNlQnVmZmVyKG1pbWVUeXBlKSB7XG4gICAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgIT09ICdvcGVuJykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IERPTUV4Y2VwdGlvbignTWVkaWFTb3VyY2UgaXMgbm90IG9wZW4nLCAnSW52YWxpZFN0YXRlRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBzb3VyY2VCdWZmZXIgPSBuZXcgU291cmNlQnVmZmVyU3R1Yih0aGlzLCBtaW1lVHlwZSk7XG4gICAgICAgIHRoaXMuc291cmNlQnVmZmVycy5fYWRkKHNvdXJjZUJ1ZmZlcik7XG4gICAgICAgIHRoaXMuYWN0aXZlU291cmNlQnVmZmVycy5fYWRkKHNvdXJjZUJ1ZmZlcik7XG4gICAgICAgIHJldHVybiBzb3VyY2VCdWZmZXI7XG4gICAgfVxuXG4gICAgcmVtb3ZlU291cmNlQnVmZmVyKHNvdXJjZUJ1ZmZlcikge1xuICAgICAgICBpZiAoIXRoaXMuc291cmNlQnVmZmVycy5fcmVtb3ZlKHNvdXJjZUJ1ZmZlcikpIHtcbiAgICAgICAgdGhyb3cgbmV3IERPTUV4Y2VwdGlvbignU291cmNlQnVmZmVyIG5vdCBmb3VuZCcsICdOb3RGb3VuZEVycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5hY3RpdmVTb3VyY2VCdWZmZXJzLl9yZW1vdmUoc291cmNlQnVmZmVyKTtcbiAgICB9XG5cbiAgICBlbmRPZlN0cmVhbShlcnJvcikge1xuICAgICAgICBpZiAodGhpcy5yZWFkeVN0YXRlICE9PSAnb3BlbicpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBET01FeGNlcHRpb24oJ01lZGlhU291cmNlIGlzIG5vdCBvcGVuJywgJ0ludmFsaWRTdGF0ZUVycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ2VuZGVkJztcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnc291cmNlZW5kZWQnKSk7XG4gICAgfVxuXG4gICAgc2V0IGR1cmF0aW9uKHZhbHVlKSB7XG4gICAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgPT09ICdjbG9zZWQnKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCdNZWRpYVNvdXJjZSBpcyBjbG9zZWQnLCAnSW52YWxpZFN0YXRlRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9kdXJhdGlvbiA9IHZhbHVlO1xuICAgIH1cblxuICAgIGdldCBkdXJhdGlvbigpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2R1cmF0aW9uO1xuICAgIH1cbn1cblxuXG4vLyBTb3VyY2VCdWZmZXJMaXN0IFN0dWJcbmNsYXNzIFNvdXJjZUJ1ZmZlckxpc3RTdHViIGV4dGVuZHMgRXZlbnRUYXJnZXQge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLl9idWZmZXJzID0gW107XG4gICAgfVxuXG4gICAgX2FkZChidWZmZXIpIHtcbiAgICAgICAgdGhpcy5fYnVmZmVycy5wdXNoKGJ1ZmZlcik7XG4gICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ2FkZHNvdXJjZWJ1ZmZlcicpKTtcbiAgICB9XG5cbiAgICBfcmVtb3ZlKGJ1ZmZlcikge1xuICAgICAgICBjb25zdCBpbmRleCA9IHRoaXMuX2J1ZmZlcnMuaW5kZXhPZihidWZmZXIpO1xuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5fYnVmZmVycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdyZW1vdmVzb3VyY2VidWZmZXInKSk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIGdldCBsZW5ndGgoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9idWZmZXJzLmxlbmd0aDtcbiAgICB9XG5cbiAgICBpdGVtKGluZGV4KSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9idWZmZXJzW2luZGV4XTtcbiAgICB9XG5cbiAgICBbU3ltYm9sLml0ZXJhdG9yXSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2J1ZmZlcnNbU3ltYm9sLml0ZXJhdG9yXSgpO1xuICAgIH1cbn1cblxud2luZG93LmJyaWRnZU9iamVjdE1hcCA9IHt9O1xud2luZG93LmJyaWRnZUNhbGxiYWNrTWFwID0ge307XG5cbmZ1bmN0aW9uIGJyaWRnZUludm9rZUFzeW5jKGJyaWRnZUlkLCBjbGFzc05hbWUsIG1ldGhvZE5hbWUsIHBhcmFtcykge1xuICAgIHZhciBwcm9taXNlUmVzb2x2ZTtcbiAgICB2YXIgcHJvbWlzZVJlamVjdDtcbiAgICB2YXIgcmVzdWx0ID0gbmV3IFByb21pc2UoZnVuY3Rpb24ocmVzb2x2ZSwgcmVqZWN0KSB7XG4gICAgICAgIHByb21pc2VSZXNvbHZlID0gcmVzb2x2ZTtcbiAgICAgICAgcHJvbWlzZVJlamVjdCA9IHJlamVjdDtcbiAgICB9KTtcbiAgICBjb25zdCBjYWxsYmFja0lkID0gbmV4dEludGVybmFsSWQ7XG4gICAgbmV4dEludGVybmFsSWQgKz0gMTtcbiAgICB3aW5kb3cuYnJpZGdlQ2FsbGJhY2tNYXBbY2FsbGJhY2tJZF0gPSBwcm9taXNlUmVzb2x2ZTtcblxuICAgIGlmICh3aW5kb3cud2Via2l0Lm1lc3NhZ2VIYW5kbGVycykge1xuICAgICAgICB3aW5kb3cud2Via2l0Lm1lc3NhZ2VIYW5kbGVycy5wZXJmb3JtQWN0aW9uLnBvc3RNZXNzYWdlKHtcbiAgICAgICAgICAgICdldmVudCc6ICdicmlkZ2VJbnZva2UnLFxuICAgICAgICAgICAgJ2RhdGEnOiB7XG4gICAgICAgICAgICAgICAgJ2JyaWRnZUlkJzogYnJpZGdlSWQsXG4gICAgICAgICAgICAgICAgJ2NsYXNzTmFtZSc6IGNsYXNzTmFtZSxcbiAgICAgICAgICAgICAgICAnbWV0aG9kTmFtZSc6IG1ldGhvZE5hbWUsXG4gICAgICAgICAgICAgICAgJ3BhcmFtcyc6IHBhcmFtcyxcbiAgICAgICAgICAgICAgICAnY2FsbGJhY2tJZCc6IGNhbGxiYWNrSWRcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGJyaWRnZUludm9rZUNhbGxiYWNrKGNhbGxiYWNrSWQsIHJlc3VsdCkge1xuICAgIGNvbnN0IGNhbGxiYWNrID0gd2luZG93LmJyaWRnZUNhbGxiYWNrTWFwW2NhbGxiYWNrSWRdO1xuICAgIGlmIChjYWxsYmFjaykge1xuICAgICAgICBjYWxsYmFjayhyZXN1bHQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gYnl0ZXNUb0Jhc2U2NChieXRlcykge1xuICAgIGNvbnN0IGJpblN0cmluZyA9IEFycmF5LmZyb20oYnl0ZXMsIChieXRlKSA9PlxuICAgICAgICBTdHJpbmcuZnJvbUNvZGVQb2ludChieXRlKSxcbiAgICApLmpvaW4oXCJcIik7XG4gICAgcmV0dXJuIGJ0b2EoYmluU3RyaW5nKTtcbn1cblxuLy8gU291cmNlQnVmZmVyU3R1YlxuY2xhc3MgU291cmNlQnVmZmVyU3R1YiBleHRlbmRzIEV2ZW50VGFyZ2V0IHtcbiAgICBjb25zdHJ1Y3RvcihtZWRpYVNvdXJjZSwgbWltZVR5cGUpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5tZWRpYVNvdXJjZSA9IG1lZGlhU291cmNlO1xuICAgICAgICB0aGlzLm1pbWVUeXBlID0gbWltZVR5cGU7XG4gICAgICAgIHRoaXMudXBkYXRpbmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5idWZmZXJlZCA9IG5ldyBUaW1lUmFuZ2VzU3R1YigpO1xuICAgICAgICB0aGlzLnRpbWVzdGFtcE9mZnNldCA9IDA7XG4gICAgICAgIHRoaXMuYXBwZW5kV2luZG93U3RhcnQgPSAwO1xuICAgICAgICB0aGlzLmFwcGVuZFdpbmRvd0VuZCA9IEluZmluaXR5O1xuXG4gICAgICAgIC8vIEludGVybmFsIHN0YXRlIHRvIHNpbXVsYXRlIGJ1ZmZlcmluZ1xuICAgICAgICB0aGlzLl9idWZmZXJlZEVuZCA9IDA7XG5cbiAgICAgICAgdGhpcy5icmlkZ2VJZCA9IG5leHRJbnRlcm5hbElkO1xuICAgICAgICBuZXh0SW50ZXJuYWxJZCArPSAxO1xuICAgICAgICB3aW5kb3cuYnJpZGdlT2JqZWN0TWFwW3RoaXMuYnJpZGdlSWRdID0gdGhpcztcblxuICAgICAgICBicmlkZ2VJbnZva2VBc3luYyh0aGlzLmJyaWRnZUlkLCBcIlNvdXJjZUJ1ZmZlclwiLCBcImNvbnN0cnVjdG9yXCIsIHtcbiAgICAgICAgICAgIFwibWltZVR5cGVcIjogbWltZVR5cGVcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgYXBwZW5kQnVmZmVyKGRhdGEpIHtcbiAgICAgICAgaWYgKHRoaXMudXBkYXRpbmcpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBET01FeGNlcHRpb24oJ1NvdXJjZUJ1ZmZlciBpcyB1cGRhdGluZycsICdJbnZhbGlkU3RhdGVFcnJvcicpO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMudXBkYXRpbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCd1cGRhdGVzdGFydCcpKTtcblxuICAgICAgICBicmlkZ2VJbnZva2VBc3luYyh0aGlzLmJyaWRnZUlkLCBcIlNvdXJjZUJ1ZmZlclwiLCBcImFwcGVuZEJ1ZmZlclwiLCB7XG4gICAgICAgICAgICBcImRhdGFcIjogYnl0ZXNUb0Jhc2U2NChkYXRhKVxuICAgICAgICB9KS50aGVuKChyZXN1bHQpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHJhbmdlU3RhcnQgPSByZXN1bHRbXCJyYW5nZVN0YXJ0XCJdO1xuICAgICAgICAgICAgY29uc3QgcmFuZ2VFbmQgPSByZXN1bHRbXCJyYW5nZUVuZFwiXTtcbiAgICAgICAgICAgIGlmIChyYW5nZVN0YXJ0ICYmIHJhbmdlRW5kKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5idWZmZXJlZC5fYWRkUmFuZ2UocmFuZ2VTdGFydCwgcmFuZ2VFbmQpO1xuICAgICAgICAgICAgICAgIHRoaXMuX2J1ZmZlcmVkRW5kID0gcmFuZ2VFbmQ7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMudXBkYXRpbmcgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ3VwZGF0ZScpKTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ3VwZGF0ZWVuZCcpKTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgYWJvcnQoKSB7XG4gICAgICAgIGlmICh0aGlzLnVwZGF0aW5nKSB7XG4gICAgICAgICAgICB0aGlzLnVwZGF0aW5nID0gZmFsc2U7XG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdhYm9ydCcpKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJlbW92ZShzdGFydCwgZW5kKSB7XG4gICAgICAgIGlmICh0aGlzLnVwZGF0aW5nKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCdTb3VyY2VCdWZmZXIgaXMgdXBkYXRpbmcnLCAnSW52YWxpZFN0YXRlRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnVwZGF0aW5nID0gdHJ1ZTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlc3RhcnQnKSk7XG5cbiAgICAgICAgYnJpZGdlSW52b2tlQXN5bmModGhpcy5icmlkZ2VJZCwgXCJTb3VyY2VCdWZmZXJcIiwgXCJyZW1vdmVcIiwge1xuICAgICAgICAgICAgXCJzdGFydFwiOiBzdGFydCxcbiAgICAgICAgICAgIFwiZW5kXCI6IGVuZFxuICAgICAgICB9KS50aGVuKChyZXN1bHQpID0+IHtcbiAgICAgICAgICAgIHRoaXMuYnVmZmVyZWQuX3JlbW92ZVJhbmdlKHN0YXJ0LCBlbmQpO1xuICAgICAgICAgICAgdGhpcy51cGRhdGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlJykpO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlZW5kJykpO1xuICAgICAgICB9KTtcbiAgICB9XG59XG5cblxudmFyIHVzZVN0dWJzID0gdHJ1ZTtcblxudmFyIG5leHRJbnRlcm5hbElkID0gMDtcbnZhciBtZWRpYVNvdXJjZU1hcCA9IHt9O1xuXG4vLyBSZXBsYWNlIHRoZSBnbG9iYWwgTWVkaWFTb3VyY2Ugd2l0aCBvdXIgc3R1YlxuaWYgKHVzZVN0dWJzICYmIHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgd2luZG93Lk1lZGlhU291cmNlID0gTWVkaWFTb3VyY2VTdHViO1xuICAgIHdpbmRvdy5NYW5hZ2VkTWVkaWFTb3VyY2UgPSBNZWRpYVNvdXJjZVN0dWI7XG4gICAgd2luZG93LlNvdXJjZUJ1ZmZlciA9IFNvdXJjZUJ1ZmZlclN0dWI7XG4gICAgVVJMLmNyZWF0ZU9iamVjdFVSTCA9IGZ1bmN0aW9uKG1zKSB7XG4gICAgICAgIGNvbnN0IHVybCA9IFwiYmxvYjptb2NrLW1lZGlhLXNvdXJjZTpcIiArIG1zLmludGVybmFsSWQ7XG4gICAgICAgIG1lZGlhU291cmNlTWFwW3VybF0gPSBtcztcbiAgICAgICAgcmV0dXJuIHVybDtcbiAgICB9O1xufVxuXG5cbmZ1bmN0aW9uIHBvc3RQbGF5ZXJFdmVudChldmVudE5hbWUsIGV2ZW50RGF0YSkge1xuICAgIGlmICh3aW5kb3cud2Via2l0Lm1lc3NhZ2VIYW5kbGVycykge1xuICAgICAgICB3aW5kb3cud2Via2l0Lm1lc3NhZ2VIYW5kbGVycy5wZXJmb3JtQWN0aW9uLnBvc3RNZXNzYWdlKHsnZXZlbnQnOiBldmVudE5hbWUsICdkYXRhJzogZXZlbnREYXRhfSk7XG4gICAgfVxufTtcblxudmFyIHZpZGVvO1xudmFyIGhscztcblxudmFyIGlzTWFuaWZlc3RQYXJzZWQgPSBmYWxzZTtcbnZhciBpc0ZpcnN0RnJhbWVSZWFkeSA9IGZhbHNlO1xuXG52YXIgY3VycmVudFRpbWVVcGRhdGVUaW1lb3V0ID0gbnVsbDtcblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllckluaXRpYWxpemUocGFyYW1zKSB7XG4gICAgdmlkZW8ubXV0ZWQgPSBmYWxzZTtcblxuICAgIHZpZGVvLmFkZEV2ZW50TGlzdGVuZXIoJ2xvYWRlZGRhdGEnLCAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYgKCFpc0ZpcnN0RnJhbWVSZWFkeSkge1xuICAgICAgICAgICAgaXNGaXJzdEZyYW1lUmVhZHkgPSB0cnVlO1xuICAgICAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgICAgICB9XG4gICAgfSk7XG4gICAgdmlkZW8uYWRkRXZlbnRMaXN0ZW5lcihcInBsYXlpbmdcIiwgZnVuY3Rpb24oKSB7XG4gICAgICAgIHJlZnJlc2hQbGF5ZXJTdGF0dXMoKTtcbiAgICB9KTtcbiAgICB2aWRlby5hZGRFdmVudExpc3RlbmVyKFwicGF1c2VcIiwgZnVuY3Rpb24oKSB7IFxuICAgICAgICByZWZyZXNoUGxheWVyU3RhdHVzKCk7XG4gICAgfSk7XG4gICAgdmlkZW8uYWRkRXZlbnRMaXN0ZW5lcihcInNlZWtpbmdcIiwgZnVuY3Rpb24oKSB7IFxuICAgICAgICByZWZyZXNoUGxheWVyU3RhdHVzKCk7XG4gICAgfSk7XG4gICAgdmlkZW8uYWRkRXZlbnRMaXN0ZW5lcihcIndhaXRpbmdcIiwgZnVuY3Rpb24oKSB7IFxuICAgICAgICByZWZyZXNoUGxheWVyU3RhdHVzKCk7XG4gICAgfSk7XG5cbiAgICBobHMgPSBuZXcgSGxzKHtcbiAgICAgICAgc3RhcnRMZXZlbDogMCxcbiAgICAgICAgdGVzdEJhbmR3aWR0aDogZmFsc2UsXG4gICAgICAgIGRlYnVnOiBwYXJhbXNbJ2RlYnVnJ10gfHwgdHJ1ZSxcbiAgICAgICAgYXV0b1N0YXJ0TG9hZDogZmFsc2UsXG4gICAgICAgIGJhY2tCdWZmZXJMZW5ndGg6IDMwLFxuICAgICAgICBtYXhCdWZmZXJMZW5ndGg6IDYwLFxuICAgICAgICBtYXhNYXhCdWZmZXJMZW5ndGg6IDYwXG4gICAgfSk7XG4gICAgaGxzLm9uKEhscy5FdmVudHMuTUFOSUZFU1RfUEFSU0VELCBmdW5jdGlvbigpIHtcbiAgICAgICAgaXNNYW5pZmVzdFBhcnNlZCA9IHRydWU7XG4gICAgICAgIHJlZnJlc2hQbGF5ZXJTdGF0dXMoKTtcbiAgICB9KTtcblxuICAgIGhscy5vbihIbHMuRXZlbnRzLkxFVkVMX1NXSVRDSEVELCBmdW5jdGlvbigpIHtcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuICAgIGhscy5vbihIbHMuRXZlbnRzLkxFVkVMU19VUERBVEVELCBmdW5jdGlvbigpIHtcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuXG4gICAgaGxzLmxvYWRTb3VyY2UoJ21hc3Rlci5tM3U4Jyk7XG4gICAgaGxzLmF0dGFjaE1lZGlhKHZpZGVvKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllckxvYWQoaW5pdGlhbExldmVsSW5kZXgpIHtcbiAgICBobHMuc3RhcnRMZXZlbCA9IGluaXRpYWxMZXZlbEluZGV4O1xuICAgIGhscy5zdGFydExvYWQoLTEsIGZhbHNlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllclBsYXkoKSB7XG4gICAgdmlkZW8ucGxheSgpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcGxheWVyUGF1c2UoKSB7XG4gICAgdmlkZW8ucGF1c2UoKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllclNldEJhc2VSYXRlKHZhbHVlKSB7XG4gICAgdmlkZW8ucGxheWJhY2tSYXRlID0gdmFsdWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwbGF5ZXJTZXRMZXZlbChsZXZlbCkge1xuICAgIGlmIChsZXZlbCA+PSAwKSB7XG4gICAgICAgIGhscy5jdXJyZW50TGV2ZWwgPSBsZXZlbDtcbiAgICB9IGVsc2Uge1xuICAgICAgICBobHMuY3VycmVudExldmVsID0gLTE7XG4gICAgfVxufVxuXG5leHBvcnQgZnVuY3Rpb24gcGxheWVyU2Vlayh2YWx1ZSkge1xuICAgIHZpZGVvLmN1cnJlbnRUaW1lID0gdmFsdWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwbGF5ZXJTZXRJc011dGVkKHZhbHVlKSB7XG4gICAgdmlkZW8ubXV0ZWQgPSB2YWx1ZTtcbn1cblxuZnVuY3Rpb24gZ2V0TGV2ZWxzKCkge1xuICAgIHZhciBsZXZlbHMgPSBbXTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGhscy5sZXZlbHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIGxldmVsID0gaGxzLmxldmVsc1tpXTtcbiAgICAgICAgbGV2ZWxzLnB1c2goe1xuICAgICAgICAgICAgJ2luZGV4JzogaSxcbiAgICAgICAgICAgICdiaXRyYXRlJzogbGV2ZWwuYml0cmF0ZSB8fCAwLFxuICAgICAgICAgICAgJ3dpZHRoJzogbGV2ZWwud2lkdGggfHwgMCxcbiAgICAgICAgICAgICdoZWlnaHQnOiBsZXZlbC5oZWlnaHQgfHwgMFxuICAgICAgICB9KTtcbiAgICB9XG4gICAgcmV0dXJuIGxldmVscztcbn1cblxuZnVuY3Rpb24gcmVmcmVzaFBsYXllclN0YXR1cygpIHtcbiAgICB2YXIgaXNQbGF5aW5nID0gZmFsc2U7XG4gICAgaWYgKCF2aWRlby5wYXVzZWQgJiYgIXZpZGVvLmVuZGVkICYmIHZpZGVvLnJlYWR5U3RhdGUgPiAyKSB7XG4gICAgICAgIGlzUGxheWluZyA9IHRydWU7XG4gICAgfVxuXG4gICAgcG9zdFBsYXllckV2ZW50KCdwbGF5ZXJTdGF0dXMnLCB7XG4gICAgICAgICdpc1JlYWR5JzogaXNNYW5pZmVzdFBhcnNlZCxcbiAgICAgICAgJ2lzRmlyc3RGcmFtZVJlYWR5JzogaXNGaXJzdEZyYW1lUmVhZHksXG4gICAgICAgICdpc1BsYXlpbmcnOiAhdmlkZW8ucGF1c2VkLFxuICAgICAgICAncmF0ZSc6IGlzUGxheWluZyA/IHZpZGVvLnBsYXliYWNrUmF0ZSA6IDAuMCxcbiAgICAgICAgJ2RlZmF1bHRSYXRlJzogdmlkZW8ucGxheWJhY2tSYXRlLFxuICAgICAgICAnbGV2ZWxzJzogZ2V0TGV2ZWxzKCksXG4gICAgICAgICdjdXJyZW50TGV2ZWwnOiBobHMuY3VycmVudExldmVsXG4gICAgfSk7XG5cbiAgICByZWZyZXNoUGxheWVyQ3VycmVudFRpbWUoKTtcblxuICAgIGlmIChpc1BsYXlpbmcpIHtcbiAgICAgICAgaWYgKGN1cnJlbnRUaW1lVXBkYXRlVGltZW91dCA9PSBudWxsKSB7XG4gICAgICAgICAgICBjdXJyZW50VGltZVVwZGF0ZVRpbWVvdXQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgICAgICByZWZyZXNoUGxheWVyQ3VycmVudFRpbWUoKTtcbiAgICAgICAgICAgIH0sIDIwMCk7XG4gICAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgICBpZihjdXJyZW50VGltZVVwZGF0ZVRpbWVvdXQgIT0gbnVsbCl7XG4gICAgICAgICAgICBjbGVhclRpbWVvdXQoY3VycmVudFRpbWVVcGRhdGVUaW1lb3V0KTtcbiAgICAgICAgICAgIGN1cnJlbnRUaW1lVXBkYXRlVGltZW91dCA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9XG59XG5cbmZ1bmN0aW9uIHJlZnJlc2hQbGF5ZXJDdXJyZW50VGltZSgpIHtcbiAgICBwb3N0UGxheWVyRXZlbnQoJ3BsYXllckN1cnJlbnRUaW1lJywge1xuICAgICAgICAndmFsdWUnOiB2aWRlby5jdXJyZW50VGltZVxuICAgIH0pO1xuICAgIGN1cnJlbnRUaW1lVXBkYXRlVGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICByZWZyZXNoUGxheWVyQ3VycmVudFRpbWUoKVxuICAgIH0sIDIwMCk7XG59XG5cbndpbmRvdy5vbmxvYWQgPSAoKSA9PiB7XG4gICAgaWYgKHVzZVN0dWJzKSB7XG4gICAgICAgIHZpZGVvID0gbmV3IFZpZGVvRWxlbWVudFN0dWIoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB2aWRlbyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3ZpZGVvJyk7XG4gICAgICAgIHZpZGVvLnBsYXlzSW5saW5lID0gdHJ1ZTtcbiAgICAgICAgdmlkZW8uY29udHJvbHMgPSB0cnVlO1xuICAgICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHZpZGVvKTtcbiAgICB9XG5cbiAgICBwb3N0UGxheWVyRXZlbnQoJ3dpbmRvd09uTG9hZCcsIHtcbiAgICB9KTtcbn07XG5cbndpbmRvdy5wbGF5ZXJJbml0aWFsaXplID0gcGxheWVySW5pdGlhbGl6ZTtcbndpbmRvdy5wbGF5ZXJMb2FkID0gcGxheWVyTG9hZDtcbndpbmRvdy5wbGF5ZXJQbGF5ID0gcGxheWVyUGxheTtcbndpbmRvdy5wbGF5ZXJQYXVzZSA9IHBsYXllclBhdXNlO1xud2luZG93LnBsYXllclNldEJhc2VSYXRlID0gcGxheWVyU2V0QmFzZVJhdGU7XG53aW5kb3cucGxheWVyU2V0TGV2ZWwgPSBwbGF5ZXJTZXRMZXZlbDtcbndpbmRvdy5wbGF5ZXJTZWVrID0gcGxheWVyU2VlaztcbndpbmRvdy5wbGF5ZXJTZXRJc011dGVkID0gcGxheWVyU2V0SXNNdXRlZDtcbndpbmRvdy5icmlkZ2VJbnZva2VDYWxsYmFjayA9IGJyaWRnZUludm9rZUNhbGxiYWNrOyIsImZ1bmN0aW9uIGdldERlZmF1bHRFeHBvcnRGcm9tQ2pzICh4KSB7XG5cdHJldHVybiB4ICYmIHguX19lc01vZHVsZSAmJiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoeCwgJ2RlZmF1bHQnKSA/IHhbJ2RlZmF1bHQnXSA6IHg7XG59XG5cbnZhciB1cmxUb29sa2l0ID0ge2V4cG9ydHM6IHt9fTtcblxuKGZ1bmN0aW9uIChtb2R1bGUsIGV4cG9ydHMpIHtcblx0Ly8gc2VlIGh0dHBzOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMxODA4XG5cblx0KGZ1bmN0aW9uIChyb290KSB7XG5cdCAgdmFyIFVSTF9SRUdFWCA9XG5cdCAgICAvXig/PSgoPzpbYS16QS1aMC05K1xcLS5dKzopPykpXFwxKD89KCg/OlxcL1xcL1teXFwvPyNdKik/KSlcXDIoPz0oKD86KD86W14/I1xcL10qXFwvKSpbXjs/I1xcL10qKT8pKVxcMygoPzo7W14/I10qKT8pKFxcP1teI10qKT8oI1teXSopPyQvO1xuXHQgIHZhciBGSVJTVF9TRUdNRU5UX1JFR0VYID0gL14oPz0oW15cXC8/I10qKSlcXDEoW15dKikkLztcblx0ICB2YXIgU0xBU0hfRE9UX1JFR0VYID0gLyg/OlxcL3xeKVxcLig/PVxcLykvZztcblx0ICB2YXIgU0xBU0hfRE9UX0RPVF9SRUdFWCA9IC8oPzpcXC98XilcXC5cXC5cXC8oPyFcXC5cXC5cXC8pW15cXC9dKig/PVxcLykvZztcblxuXHQgIHZhciBVUkxUb29sa2l0ID0ge1xuXHQgICAgLy8gSWYgb3B0cy5hbHdheXNOb3JtYWxpemUgaXMgdHJ1ZSB0aGVuIHRoZSBwYXRoIHdpbGwgYWx3YXlzIGJlIG5vcm1hbGl6ZWQgZXZlbiB3aGVuIGl0IHN0YXJ0cyB3aXRoIC8gb3IgLy9cblx0ICAgIC8vIEUuZ1xuXHQgICAgLy8gV2l0aCBvcHRzLmFsd2F5c05vcm1hbGl6ZSA9IGZhbHNlIChkZWZhdWx0LCBzcGVjIGNvbXBsaWFudClcblx0ICAgIC8vIGh0dHA6Ly9hLmNvbS9iL2NkICsgL2UvZi8uLi9nID0+IGh0dHA6Ly9hLmNvbS9lL2YvLi4vZ1xuXHQgICAgLy8gV2l0aCBvcHRzLmFsd2F5c05vcm1hbGl6ZSA9IHRydWUgKG5vdCBzcGVjIGNvbXBsaWFudClcblx0ICAgIC8vIGh0dHA6Ly9hLmNvbS9iL2NkICsgL2UvZi8uLi9nID0+IGh0dHA6Ly9hLmNvbS9lL2dcblx0ICAgIGJ1aWxkQWJzb2x1dGVVUkw6IGZ1bmN0aW9uIChiYXNlVVJMLCByZWxhdGl2ZVVSTCwgb3B0cykge1xuXHQgICAgICBvcHRzID0gb3B0cyB8fCB7fTtcblx0ICAgICAgLy8gcmVtb3ZlIGFueSByZW1haW5pbmcgc3BhY2UgYW5kIENSTEZcblx0ICAgICAgYmFzZVVSTCA9IGJhc2VVUkwudHJpbSgpO1xuXHQgICAgICByZWxhdGl2ZVVSTCA9IHJlbGF0aXZlVVJMLnRyaW0oKTtcblx0ICAgICAgaWYgKCFyZWxhdGl2ZVVSTCkge1xuXHQgICAgICAgIC8vIDJhKSBJZiB0aGUgZW1iZWRkZWQgVVJMIGlzIGVudGlyZWx5IGVtcHR5LCBpdCBpbmhlcml0cyB0aGVcblx0ICAgICAgICAvLyBlbnRpcmUgYmFzZSBVUkwgKGkuZS4sIGlzIHNldCBlcXVhbCB0byB0aGUgYmFzZSBVUkwpXG5cdCAgICAgICAgLy8gYW5kIHdlIGFyZSBkb25lLlxuXHQgICAgICAgIGlmICghb3B0cy5hbHdheXNOb3JtYWxpemUpIHtcblx0ICAgICAgICAgIHJldHVybiBiYXNlVVJMO1xuXHQgICAgICAgIH1cblx0ICAgICAgICB2YXIgYmFzZVBhcnRzRm9yTm9ybWFsaXNlID0gVVJMVG9vbGtpdC5wYXJzZVVSTChiYXNlVVJMKTtcblx0ICAgICAgICBpZiAoIWJhc2VQYXJ0c0Zvck5vcm1hbGlzZSkge1xuXHQgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdFcnJvciB0cnlpbmcgdG8gcGFyc2UgYmFzZSBVUkwuJyk7XG5cdCAgICAgICAgfVxuXHQgICAgICAgIGJhc2VQYXJ0c0Zvck5vcm1hbGlzZS5wYXRoID0gVVJMVG9vbGtpdC5ub3JtYWxpemVQYXRoKFxuXHQgICAgICAgICAgYmFzZVBhcnRzRm9yTm9ybWFsaXNlLnBhdGhcblx0ICAgICAgICApO1xuXHQgICAgICAgIHJldHVybiBVUkxUb29sa2l0LmJ1aWxkVVJMRnJvbVBhcnRzKGJhc2VQYXJ0c0Zvck5vcm1hbGlzZSk7XG5cdCAgICAgIH1cblx0ICAgICAgdmFyIHJlbGF0aXZlUGFydHMgPSBVUkxUb29sa2l0LnBhcnNlVVJMKHJlbGF0aXZlVVJMKTtcblx0ICAgICAgaWYgKCFyZWxhdGl2ZVBhcnRzKSB7XG5cdCAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdFcnJvciB0cnlpbmcgdG8gcGFyc2UgcmVsYXRpdmUgVVJMLicpO1xuXHQgICAgICB9XG5cdCAgICAgIGlmIChyZWxhdGl2ZVBhcnRzLnNjaGVtZSkge1xuXHQgICAgICAgIC8vIDJiKSBJZiB0aGUgZW1iZWRkZWQgVVJMIHN0YXJ0cyB3aXRoIGEgc2NoZW1lIG5hbWUsIGl0IGlzXG5cdCAgICAgICAgLy8gaW50ZXJwcmV0ZWQgYXMgYW4gYWJzb2x1dGUgVVJMIGFuZCB3ZSBhcmUgZG9uZS5cblx0ICAgICAgICBpZiAoIW9wdHMuYWx3YXlzTm9ybWFsaXplKSB7XG5cdCAgICAgICAgICByZXR1cm4gcmVsYXRpdmVVUkw7XG5cdCAgICAgICAgfVxuXHQgICAgICAgIHJlbGF0aXZlUGFydHMucGF0aCA9IFVSTFRvb2xraXQubm9ybWFsaXplUGF0aChyZWxhdGl2ZVBhcnRzLnBhdGgpO1xuXHQgICAgICAgIHJldHVybiBVUkxUb29sa2l0LmJ1aWxkVVJMRnJvbVBhcnRzKHJlbGF0aXZlUGFydHMpO1xuXHQgICAgICB9XG5cdCAgICAgIHZhciBiYXNlUGFydHMgPSBVUkxUb29sa2l0LnBhcnNlVVJMKGJhc2VVUkwpO1xuXHQgICAgICBpZiAoIWJhc2VQYXJ0cykge1xuXHQgICAgICAgIHRocm93IG5ldyBFcnJvcignRXJyb3IgdHJ5aW5nIHRvIHBhcnNlIGJhc2UgVVJMLicpO1xuXHQgICAgICB9XG5cdCAgICAgIGlmICghYmFzZVBhcnRzLm5ldExvYyAmJiBiYXNlUGFydHMucGF0aCAmJiBiYXNlUGFydHMucGF0aFswXSAhPT0gJy8nKSB7XG5cdCAgICAgICAgLy8gSWYgbmV0TG9jIG1pc3NpbmcgYW5kIHBhdGggZG9lc24ndCBzdGFydCB3aXRoICcvJywgYXNzdW1lIGV2ZXJ0aGluZyBiZWZvcmUgdGhlIGZpcnN0ICcvJyBpcyB0aGUgbmV0TG9jXG5cdCAgICAgICAgLy8gVGhpcyBjYXVzZXMgJ2V4YW1wbGUuY29tL2EnIHRvIGJlIGhhbmRsZWQgYXMgJy8vZXhhbXBsZS5jb20vYScgaW5zdGVhZCBvZiAnL2V4YW1wbGUuY29tL2EnXG5cdCAgICAgICAgdmFyIHBhdGhQYXJ0cyA9IEZJUlNUX1NFR01FTlRfUkVHRVguZXhlYyhiYXNlUGFydHMucGF0aCk7XG5cdCAgICAgICAgYmFzZVBhcnRzLm5ldExvYyA9IHBhdGhQYXJ0c1sxXTtcblx0ICAgICAgICBiYXNlUGFydHMucGF0aCA9IHBhdGhQYXJ0c1syXTtcblx0ICAgICAgfVxuXHQgICAgICBpZiAoYmFzZVBhcnRzLm5ldExvYyAmJiAhYmFzZVBhcnRzLnBhdGgpIHtcblx0ICAgICAgICBiYXNlUGFydHMucGF0aCA9ICcvJztcblx0ICAgICAgfVxuXHQgICAgICB2YXIgYnVpbHRQYXJ0cyA9IHtcblx0ICAgICAgICAvLyAyYykgT3RoZXJ3aXNlLCB0aGUgZW1iZWRkZWQgVVJMIGluaGVyaXRzIHRoZSBzY2hlbWUgb2Zcblx0ICAgICAgICAvLyB0aGUgYmFzZSBVUkwuXG5cdCAgICAgICAgc2NoZW1lOiBiYXNlUGFydHMuc2NoZW1lLFxuXHQgICAgICAgIG5ldExvYzogcmVsYXRpdmVQYXJ0cy5uZXRMb2MsXG5cdCAgICAgICAgcGF0aDogbnVsbCxcblx0ICAgICAgICBwYXJhbXM6IHJlbGF0aXZlUGFydHMucGFyYW1zLFxuXHQgICAgICAgIHF1ZXJ5OiByZWxhdGl2ZVBhcnRzLnF1ZXJ5LFxuXHQgICAgICAgIGZyYWdtZW50OiByZWxhdGl2ZVBhcnRzLmZyYWdtZW50LFxuXHQgICAgICB9O1xuXHQgICAgICBpZiAoIXJlbGF0aXZlUGFydHMubmV0TG9jKSB7XG5cdCAgICAgICAgLy8gMykgSWYgdGhlIGVtYmVkZGVkIFVSTCdzIDxuZXRfbG9jPiBpcyBub24tZW1wdHksIHdlIHNraXAgdG9cblx0ICAgICAgICAvLyBTdGVwIDcuICBPdGhlcndpc2UsIHRoZSBlbWJlZGRlZCBVUkwgaW5oZXJpdHMgdGhlIDxuZXRfbG9jPlxuXHQgICAgICAgIC8vIChpZiBhbnkpIG9mIHRoZSBiYXNlIFVSTC5cblx0ICAgICAgICBidWlsdFBhcnRzLm5ldExvYyA9IGJhc2VQYXJ0cy5uZXRMb2M7XG5cdCAgICAgICAgLy8gNCkgSWYgdGhlIGVtYmVkZGVkIFVSTCBwYXRoIGlzIHByZWNlZGVkIGJ5IGEgc2xhc2ggXCIvXCIsIHRoZVxuXHQgICAgICAgIC8vIHBhdGggaXMgbm90IHJlbGF0aXZlIGFuZCB3ZSBza2lwIHRvIFN0ZXAgNy5cblx0ICAgICAgICBpZiAocmVsYXRpdmVQYXJ0cy5wYXRoWzBdICE9PSAnLycpIHtcblx0ICAgICAgICAgIGlmICghcmVsYXRpdmVQYXJ0cy5wYXRoKSB7XG5cdCAgICAgICAgICAgIC8vIDUpIElmIHRoZSBlbWJlZGRlZCBVUkwgcGF0aCBpcyBlbXB0eSAoYW5kIG5vdCBwcmVjZWRlZCBieSBhXG5cdCAgICAgICAgICAgIC8vIHNsYXNoKSwgdGhlbiB0aGUgZW1iZWRkZWQgVVJMIGluaGVyaXRzIHRoZSBiYXNlIFVSTCBwYXRoXG5cdCAgICAgICAgICAgIGJ1aWx0UGFydHMucGF0aCA9IGJhc2VQYXJ0cy5wYXRoO1xuXHQgICAgICAgICAgICAvLyA1YSkgaWYgdGhlIGVtYmVkZGVkIFVSTCdzIDxwYXJhbXM+IGlzIG5vbi1lbXB0eSwgd2Ugc2tpcCB0b1xuXHQgICAgICAgICAgICAvLyBzdGVwIDc7IG90aGVyd2lzZSwgaXQgaW5oZXJpdHMgdGhlIDxwYXJhbXM+IG9mIHRoZSBiYXNlXG5cdCAgICAgICAgICAgIC8vIFVSTCAoaWYgYW55KSBhbmRcblx0ICAgICAgICAgICAgaWYgKCFyZWxhdGl2ZVBhcnRzLnBhcmFtcykge1xuXHQgICAgICAgICAgICAgIGJ1aWx0UGFydHMucGFyYW1zID0gYmFzZVBhcnRzLnBhcmFtcztcblx0ICAgICAgICAgICAgICAvLyA1YikgaWYgdGhlIGVtYmVkZGVkIFVSTCdzIDxxdWVyeT4gaXMgbm9uLWVtcHR5LCB3ZSBza2lwIHRvXG5cdCAgICAgICAgICAgICAgLy8gc3RlcCA3OyBvdGhlcndpc2UsIGl0IGluaGVyaXRzIHRoZSA8cXVlcnk+IG9mIHRoZSBiYXNlXG5cdCAgICAgICAgICAgICAgLy8gVVJMIChpZiBhbnkpIGFuZCB3ZSBza2lwIHRvIHN0ZXAgNy5cblx0ICAgICAgICAgICAgICBpZiAoIXJlbGF0aXZlUGFydHMucXVlcnkpIHtcblx0ICAgICAgICAgICAgICAgIGJ1aWx0UGFydHMucXVlcnkgPSBiYXNlUGFydHMucXVlcnk7XG5cdCAgICAgICAgICAgICAgfVxuXHQgICAgICAgICAgICB9XG5cdCAgICAgICAgICB9IGVsc2Uge1xuXHQgICAgICAgICAgICAvLyA2KSBUaGUgbGFzdCBzZWdtZW50IG9mIHRoZSBiYXNlIFVSTCdzIHBhdGggKGFueXRoaW5nXG5cdCAgICAgICAgICAgIC8vIGZvbGxvd2luZyB0aGUgcmlnaHRtb3N0IHNsYXNoIFwiL1wiLCBvciB0aGUgZW50aXJlIHBhdGggaWYgbm9cblx0ICAgICAgICAgICAgLy8gc2xhc2ggaXMgcHJlc2VudCkgaXMgcmVtb3ZlZCBhbmQgdGhlIGVtYmVkZGVkIFVSTCdzIHBhdGggaXNcblx0ICAgICAgICAgICAgLy8gYXBwZW5kZWQgaW4gaXRzIHBsYWNlLlxuXHQgICAgICAgICAgICB2YXIgYmFzZVVSTFBhdGggPSBiYXNlUGFydHMucGF0aDtcblx0ICAgICAgICAgICAgdmFyIG5ld1BhdGggPVxuXHQgICAgICAgICAgICAgIGJhc2VVUkxQYXRoLnN1YnN0cmluZygwLCBiYXNlVVJMUGF0aC5sYXN0SW5kZXhPZignLycpICsgMSkgK1xuXHQgICAgICAgICAgICAgIHJlbGF0aXZlUGFydHMucGF0aDtcblx0ICAgICAgICAgICAgYnVpbHRQYXJ0cy5wYXRoID0gVVJMVG9vbGtpdC5ub3JtYWxpemVQYXRoKG5ld1BhdGgpO1xuXHQgICAgICAgICAgfVxuXHQgICAgICAgIH1cblx0ICAgICAgfVxuXHQgICAgICBpZiAoYnVpbHRQYXJ0cy5wYXRoID09PSBudWxsKSB7XG5cdCAgICAgICAgYnVpbHRQYXJ0cy5wYXRoID0gb3B0cy5hbHdheXNOb3JtYWxpemVcblx0ICAgICAgICAgID8gVVJMVG9vbGtpdC5ub3JtYWxpemVQYXRoKHJlbGF0aXZlUGFydHMucGF0aClcblx0ICAgICAgICAgIDogcmVsYXRpdmVQYXJ0cy5wYXRoO1xuXHQgICAgICB9XG5cdCAgICAgIHJldHVybiBVUkxUb29sa2l0LmJ1aWxkVVJMRnJvbVBhcnRzKGJ1aWx0UGFydHMpO1xuXHQgICAgfSxcblx0ICAgIHBhcnNlVVJMOiBmdW5jdGlvbiAodXJsKSB7XG5cdCAgICAgIHZhciBwYXJ0cyA9IFVSTF9SRUdFWC5leGVjKHVybCk7XG5cdCAgICAgIGlmICghcGFydHMpIHtcblx0ICAgICAgICByZXR1cm4gbnVsbDtcblx0ICAgICAgfVxuXHQgICAgICByZXR1cm4ge1xuXHQgICAgICAgIHNjaGVtZTogcGFydHNbMV0gfHwgJycsXG5cdCAgICAgICAgbmV0TG9jOiBwYXJ0c1syXSB8fCAnJyxcblx0ICAgICAgICBwYXRoOiBwYXJ0c1szXSB8fCAnJyxcblx0ICAgICAgICBwYXJhbXM6IHBhcnRzWzRdIHx8ICcnLFxuXHQgICAgICAgIHF1ZXJ5OiBwYXJ0c1s1XSB8fCAnJyxcblx0ICAgICAgICBmcmFnbWVudDogcGFydHNbNl0gfHwgJycsXG5cdCAgICAgIH07XG5cdCAgICB9LFxuXHQgICAgbm9ybWFsaXplUGF0aDogZnVuY3Rpb24gKHBhdGgpIHtcblx0ICAgICAgLy8gVGhlIGZvbGxvd2luZyBvcGVyYXRpb25zIGFyZVxuXHQgICAgICAvLyB0aGVuIGFwcGxpZWQsIGluIG9yZGVyLCB0byB0aGUgbmV3IHBhdGg6XG5cdCAgICAgIC8vIDZhKSBBbGwgb2NjdXJyZW5jZXMgb2YgXCIuL1wiLCB3aGVyZSBcIi5cIiBpcyBhIGNvbXBsZXRlIHBhdGhcblx0ICAgICAgLy8gc2VnbWVudCwgYXJlIHJlbW92ZWQuXG5cdCAgICAgIC8vIDZiKSBJZiB0aGUgcGF0aCBlbmRzIHdpdGggXCIuXCIgYXMgYSBjb21wbGV0ZSBwYXRoIHNlZ21lbnQsXG5cdCAgICAgIC8vIHRoYXQgXCIuXCIgaXMgcmVtb3ZlZC5cblx0ICAgICAgcGF0aCA9IHBhdGguc3BsaXQoJycpLnJldmVyc2UoKS5qb2luKCcnKS5yZXBsYWNlKFNMQVNIX0RPVF9SRUdFWCwgJycpO1xuXHQgICAgICAvLyA2YykgQWxsIG9jY3VycmVuY2VzIG9mIFwiPHNlZ21lbnQ+Ly4uL1wiLCB3aGVyZSA8c2VnbWVudD4gaXMgYVxuXHQgICAgICAvLyBjb21wbGV0ZSBwYXRoIHNlZ21lbnQgbm90IGVxdWFsIHRvIFwiLi5cIiwgYXJlIHJlbW92ZWQuXG5cdCAgICAgIC8vIFJlbW92YWwgb2YgdGhlc2UgcGF0aCBzZWdtZW50cyBpcyBwZXJmb3JtZWQgaXRlcmF0aXZlbHksXG5cdCAgICAgIC8vIHJlbW92aW5nIHRoZSBsZWZ0bW9zdCBtYXRjaGluZyBwYXR0ZXJuIG9uIGVhY2ggaXRlcmF0aW9uLFxuXHQgICAgICAvLyB1bnRpbCBubyBtYXRjaGluZyBwYXR0ZXJuIHJlbWFpbnMuXG5cdCAgICAgIC8vIDZkKSBJZiB0aGUgcGF0aCBlbmRzIHdpdGggXCI8c2VnbWVudD4vLi5cIiwgd2hlcmUgPHNlZ21lbnQ+IGlzIGFcblx0ICAgICAgLy8gY29tcGxldGUgcGF0aCBzZWdtZW50IG5vdCBlcXVhbCB0byBcIi4uXCIsIHRoYXRcblx0ICAgICAgLy8gXCI8c2VnbWVudD4vLi5cIiBpcyByZW1vdmVkLlxuXHQgICAgICB3aGlsZSAoXG5cdCAgICAgICAgcGF0aC5sZW5ndGggIT09IChwYXRoID0gcGF0aC5yZXBsYWNlKFNMQVNIX0RPVF9ET1RfUkVHRVgsICcnKSkubGVuZ3RoXG5cdCAgICAgICkge31cblx0ICAgICAgcmV0dXJuIHBhdGguc3BsaXQoJycpLnJldmVyc2UoKS5qb2luKCcnKTtcblx0ICAgIH0sXG5cdCAgICBidWlsZFVSTEZyb21QYXJ0czogZnVuY3Rpb24gKHBhcnRzKSB7XG5cdCAgICAgIHJldHVybiAoXG5cdCAgICAgICAgcGFydHMuc2NoZW1lICtcblx0ICAgICAgICBwYXJ0cy5uZXRMb2MgK1xuXHQgICAgICAgIHBhcnRzLnBhdGggK1xuXHQgICAgICAgIHBhcnRzLnBhcmFtcyArXG5cdCAgICAgICAgcGFydHMucXVlcnkgK1xuXHQgICAgICAgIHBhcnRzLmZyYWdtZW50XG5cdCAgICAgICk7XG5cdCAgICB9LFxuXHQgIH07XG5cblx0ICBtb2R1bGUuZXhwb3J0cyA9IFVSTFRvb2xraXQ7XG5cdH0pKCk7IFxufSAodXJsVG9vbGtpdCkpO1xuXG52YXIgdXJsVG9vbGtpdEV4cG9ydHMgPSB1cmxUb29sa2l0LmV4cG9ydHM7XG5cbmZ1bmN0aW9uIG93bktleXMoZSwgcikge1xuICB2YXIgdCA9IE9iamVjdC5rZXlzKGUpO1xuICBpZiAoT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scykge1xuICAgIHZhciBvID0gT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scyhlKTtcbiAgICByICYmIChvID0gby5maWx0ZXIoZnVuY3Rpb24gKHIpIHtcbiAgICAgIHJldHVybiBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKGUsIHIpLmVudW1lcmFibGU7XG4gICAgfSkpLCB0LnB1c2guYXBwbHkodCwgbyk7XG4gIH1cbiAgcmV0dXJuIHQ7XG59XG5mdW5jdGlvbiBfb2JqZWN0U3ByZWFkMihlKSB7XG4gIGZvciAodmFyIHIgPSAxOyByIDwgYXJndW1lbnRzLmxlbmd0aDsgcisrKSB7XG4gICAgdmFyIHQgPSBudWxsICE9IGFyZ3VtZW50c1tyXSA/IGFyZ3VtZW50c1tyXSA6IHt9O1xuICAgIHIgJSAyID8gb3duS2V5cyhPYmplY3QodCksICEwKS5mb3JFYWNoKGZ1bmN0aW9uIChyKSB7XG4gICAgICBfZGVmaW5lUHJvcGVydHkoZSwgciwgdFtyXSk7XG4gICAgfSkgOiBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyA/IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKGUsIE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3JzKHQpKSA6IG93bktleXMoT2JqZWN0KHQpKS5mb3JFYWNoKGZ1bmN0aW9uIChyKSB7XG4gICAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoZSwgciwgT2JqZWN0LmdldE93blByb3BlcnR5RGVzY3JpcHRvcih0LCByKSk7XG4gICAgfSk7XG4gIH1cbiAgcmV0dXJuIGU7XG59XG5mdW5jdGlvbiBfdG9QcmltaXRpdmUodCwgcikge1xuICBpZiAoXCJvYmplY3RcIiAhPSB0eXBlb2YgdCB8fCAhdCkgcmV0dXJuIHQ7XG4gIHZhciBlID0gdFtTeW1ib2wudG9QcmltaXRpdmVdO1xuICBpZiAodm9pZCAwICE9PSBlKSB7XG4gICAgdmFyIGkgPSBlLmNhbGwodCwgciB8fCBcImRlZmF1bHRcIik7XG4gICAgaWYgKFwib2JqZWN0XCIgIT0gdHlwZW9mIGkpIHJldHVybiBpO1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJAQHRvUHJpbWl0aXZlIG11c3QgcmV0dXJuIGEgcHJpbWl0aXZlIHZhbHVlLlwiKTtcbiAgfVxuICByZXR1cm4gKFwic3RyaW5nXCIgPT09IHIgPyBTdHJpbmcgOiBOdW1iZXIpKHQpO1xufVxuZnVuY3Rpb24gX3RvUHJvcGVydHlLZXkodCkge1xuICB2YXIgaSA9IF90b1ByaW1pdGl2ZSh0LCBcInN0cmluZ1wiKTtcbiAgcmV0dXJuIFwic3ltYm9sXCIgPT0gdHlwZW9mIGkgPyBpIDogU3RyaW5nKGkpO1xufVxuZnVuY3Rpb24gX2RlZmluZVByb3BlcnR5KG9iaiwga2V5LCB2YWx1ZSkge1xuICBrZXkgPSBfdG9Qcm9wZXJ0eUtleShrZXkpO1xuICBpZiAoa2V5IGluIG9iaikge1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShvYmosIGtleSwge1xuICAgICAgdmFsdWU6IHZhbHVlLFxuICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgIHdyaXRhYmxlOiB0cnVlXG4gICAgfSk7XG4gIH0gZWxzZSB7XG4gICAgb2JqW2tleV0gPSB2YWx1ZTtcbiAgfVxuICByZXR1cm4gb2JqO1xufVxuZnVuY3Rpb24gX2V4dGVuZHMoKSB7XG4gIF9leHRlbmRzID0gT2JqZWN0LmFzc2lnbiA/IE9iamVjdC5hc3NpZ24uYmluZCgpIDogZnVuY3Rpb24gKHRhcmdldCkge1xuICAgIGZvciAodmFyIGkgPSAxOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldO1xuICAgICAgZm9yICh2YXIga2V5IGluIHNvdXJjZSkge1xuICAgICAgICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHNvdXJjZSwga2V5KSkge1xuICAgICAgICAgIHRhcmdldFtrZXldID0gc291cmNlW2tleV07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHRhcmdldDtcbiAgfTtcbiAgcmV0dXJuIF9leHRlbmRzLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG59XG5cbi8vIGh0dHBzOi8vY2FuaXVzZS5jb20vbWRuLWphdmFzY3JpcHRfYnVpbHRpbnNfbnVtYmVyX2lzZmluaXRlXG5jb25zdCBpc0Zpbml0ZU51bWJlciA9IE51bWJlci5pc0Zpbml0ZSB8fCBmdW5jdGlvbiAodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gJ251bWJlcicgJiYgaXNGaW5pdGUodmFsdWUpO1xufTtcblxuLy8gaHR0cHM6Ly9jYW5pdXNlLmNvbS9tZG4tamF2YXNjcmlwdF9idWlsdGluc19udW1iZXJfaXNzYWZlaW50ZWdlclxuY29uc3QgaXNTYWZlSW50ZWdlciA9IE51bWJlci5pc1NhZmVJbnRlZ2VyIHx8IGZ1bmN0aW9uICh2YWx1ZSkge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09PSAnbnVtYmVyJyAmJiBNYXRoLmFicyh2YWx1ZSkgPD0gTUFYX1NBRkVfSU5URUdFUjtcbn07XG5jb25zdCBNQVhfU0FGRV9JTlRFR0VSID0gTnVtYmVyLk1BWF9TQUZFX0lOVEVHRVIgfHwgOTAwNzE5OTI1NDc0MDk5MTtcblxubGV0IEV2ZW50cyA9IC8qI19fUFVSRV9fKi9mdW5jdGlvbiAoRXZlbnRzKSB7XG4gIEV2ZW50c1tcIk1FRElBX0FUVEFDSElOR1wiXSA9IFwiaGxzTWVkaWFBdHRhY2hpbmdcIjtcbiAgRXZlbnRzW1wiTUVESUFfQVRUQUNIRURcIl0gPSBcImhsc01lZGlhQXR0YWNoZWRcIjtcbiAgRXZlbnRzW1wiTUVESUFfREVUQUNISU5HXCJdID0gXCJobHNNZWRpYURldGFjaGluZ1wiO1xuICBFdmVudHNbXCJNRURJQV9ERVRBQ0hFRFwiXSA9IFwiaGxzTWVkaWFEZXRhY2hlZFwiO1xuICBFdmVudHNbXCJCVUZGRVJfUkVTRVRcIl0gPSBcImhsc0J1ZmZlclJlc2V0XCI7XG4gIEV2ZW50c1tcIkJVRkZFUl9DT0RFQ1NcIl0gPSBcImhsc0J1ZmZlckNvZGVjc1wiO1xuICBFdmVudHNbXCJCVUZGRVJfQ1JFQVRFRFwiXSA9IFwiaGxzQnVmZmVyQ3JlYXRlZFwiO1xuICBFdmVudHNbXCJCVUZGRVJfQVBQRU5ESU5HXCJdID0gXCJobHNCdWZmZXJBcHBlbmRpbmdcIjtcbiAgRXZlbnRzW1wiQlVGRkVSX0FQUEVOREVEXCJdID0gXCJobHNCdWZmZXJBcHBlbmRlZFwiO1xuICBFdmVudHNbXCJCVUZGRVJfRU9TXCJdID0gXCJobHNCdWZmZXJFb3NcIjtcbiAgRXZlbnRzW1wiQlVGRkVSX0ZMVVNISU5HXCJdID0gXCJobHNCdWZmZXJGbHVzaGluZ1wiO1xuICBFdmVudHNbXCJCVUZGRVJfRkxVU0hFRFwiXSA9IFwiaGxzQnVmZmVyRmx1c2hlZFwiO1xuICBFdmVudHNbXCJNQU5JRkVTVF9MT0FESU5HXCJdID0gXCJobHNNYW5pZmVzdExvYWRpbmdcIjtcbiAgRXZlbnRzW1wiTUFOSUZFU1RfTE9BREVEXCJdID0gXCJobHNNYW5pZmVzdExvYWRlZFwiO1xuICBFdmVudHNbXCJNQU5JRkVTVF9QQVJTRURcIl0gPSBcImhsc01hbmlmZXN0UGFyc2VkXCI7XG4gIEV2ZW50c1tcIkxFVkVMX1NXSVRDSElOR1wiXSA9IFwiaGxzTGV2ZWxTd2l0Y2hpbmdcIjtcbiAgRXZlbnRzW1wiTEVWRUxfU1dJVENIRURcIl0gPSBcImhsc0xldmVsU3dpdGNoZWRcIjtcbiAgRXZlbnRzW1wiTEVWRUxfTE9BRElOR1wiXSA9IFwiaGxzTGV2ZWxMb2FkaW5nXCI7XG4gIEV2ZW50c1tcIkxFVkVMX0xPQURFRFwiXSA9IFwiaGxzTGV2ZWxMb2FkZWRcIjtcbiAgRXZlbnRzW1wiTEVWRUxfVVBEQVRFRFwiXSA9IFwiaGxzTGV2ZWxVcGRhdGVkXCI7XG4gIEV2ZW50c1tcIkxFVkVMX1BUU19VUERBVEVEXCJdID0gXCJobHNMZXZlbFB0c1VwZGF0ZWRcIjtcbiAgRXZlbnRzW1wiTEVWRUxTX1VQREFURURcIl0gPSBcImhsc0xldmVsc1VwZGF0ZWRcIjtcbiAgRXZlbnRzW1wiQVVESU9fVFJBQ0tTX1VQREFURURcIl0gPSBcImhsc0F1ZGlvVHJhY2tzVXBkYXRlZFwiO1xuICBFdmVudHNbXCJBVURJT19UUkFDS19TV0lUQ0hJTkdcIl0gPSBcImhsc0F1ZGlvVHJhY2tTd2l0Y2hpbmdcIjtcbiAgRXZlbnRzW1wiQVVESU9fVFJBQ0tfU1dJVENIRURcIl0gPSBcImhsc0F1ZGlvVHJhY2tTd2l0Y2hlZFwiO1xuICBFdmVudHNbXCJBVURJT19UUkFDS19MT0FESU5HXCJdID0gXCJobHNBdWRpb1RyYWNrTG9hZGluZ1wiO1xuICBFdmVudHNbXCJBVURJT19UUkFDS19MT0FERURcIl0gPSBcImhsc0F1ZGlvVHJhY2tMb2FkZWRcIjtcbiAgRXZlbnRzW1wiU1VCVElUTEVfVFJBQ0tTX1VQREFURURcIl0gPSBcImhsc1N1YnRpdGxlVHJhY2tzVXBkYXRlZFwiO1xuICBFdmVudHNbXCJTVUJUSVRMRV9UUkFDS1NfQ0xFQVJFRFwiXSA9IFwiaGxzU3VidGl0bGVUcmFja3NDbGVhcmVkXCI7XG4gIEV2ZW50c1tcIlNVQlRJVExFX1RSQUNLX1NXSVRDSFwiXSA9IFwiaGxzU3VidGl0bGVUcmFja1N3aXRjaFwiO1xuICBFdmVudHNbXCJTVUJUSVRMRV9UUkFDS19MT0FESU5HXCJdID0gXCJobHNTdWJ0aXRsZVRyYWNrTG9hZGluZ1wiO1xuICBFdmVudHNbXCJTVUJUSVRMRV9UUkFDS19MT0FERURcIl0gPSBcImhsc1N1YnRpdGxlVHJhY2tMb2FkZWRcIjtcbiAgRXZlbnRzW1wiU1VCVElUTEVfRlJBR19QUk9DRVNTRURcIl0gPSBcImhsc1N1YnRpdGxlRnJhZ1Byb2Nlc3NlZFwiO1xuICBFdmVudHNbXCJDVUVTX1BBUlNFRFwiXSA9IFwiaGxzQ3Vlc1BhcnNlZFwiO1xuICBFdmVudHNbXCJOT05fTkFUSVZFX1RFWFRfVFJBQ0tTX0ZPVU5EXCJdID0gXCJobHNOb25OYXRpdmVUZXh0VHJhY2tzRm91bmRcIjtcbiAgRXZlbnRzW1wiSU5JVF9QVFNfRk9VTkRcIl0gPSBcImhsc0luaXRQdHNGb3VuZFwiO1xuICBFdmVudHNbXCJGUkFHX0xPQURJTkdcIl0gPSBcImhsc0ZyYWdMb2FkaW5nXCI7XG4gIEV2ZW50c1tcIkZSQUdfTE9BRF9FTUVSR0VOQ1lfQUJPUlRFRFwiXSA9IFwiaGxzRnJhZ0xvYWRFbWVyZ2VuY3lBYm9ydGVkXCI7XG4gIEV2ZW50c1tcIkZSQUdfTE9BREVEXCJdID0gXCJobHNGcmFnTG9hZGVkXCI7XG4gIEV2ZW50c1tcIkZSQUdfREVDUllQVEVEXCJdID0gXCJobHNGcmFnRGVjcnlwdGVkXCI7XG4gIEV2ZW50c1tcIkZSQUdfUEFSU0lOR19JTklUX1NFR01FTlRcIl0gPSBcImhsc0ZyYWdQYXJzaW5nSW5pdFNlZ21lbnRcIjtcbiAgRXZlbnRzW1wiRlJBR19QQVJTSU5HX1VTRVJEQVRBXCJdID0gXCJobHNGcmFnUGFyc2luZ1VzZXJkYXRhXCI7XG4gIEV2ZW50c1tcIkZSQUdfUEFSU0lOR19NRVRBREFUQVwiXSA9IFwiaGxzRnJhZ1BhcnNpbmdNZXRhZGF0YVwiO1xuICBFdmVudHNbXCJGUkFHX1BBUlNFRFwiXSA9IFwiaGxzRnJhZ1BhcnNlZFwiO1xuICBFdmVudHNbXCJGUkFHX0JVRkZFUkVEXCJdID0gXCJobHNGcmFnQnVmZmVyZWRcIjtcbiAgRXZlbnRzW1wiRlJBR19DSEFOR0VEXCJdID0gXCJobHNGcmFnQ2hhbmdlZFwiO1xuICBFdmVudHNbXCJGUFNfRFJPUFwiXSA9IFwiaGxzRnBzRHJvcFwiO1xuICBFdmVudHNbXCJGUFNfRFJPUF9MRVZFTF9DQVBQSU5HXCJdID0gXCJobHNGcHNEcm9wTGV2ZWxDYXBwaW5nXCI7XG4gIEV2ZW50c1tcIk1BWF9BVVRPX0xFVkVMX1VQREFURURcIl0gPSBcImhsc01heEF1dG9MZXZlbFVwZGF0ZWRcIjtcbiAgRXZlbnRzW1wiRVJST1JcIl0gPSBcImhsc0Vycm9yXCI7XG4gIEV2ZW50c1tcIkRFU1RST1lJTkdcIl0gPSBcImhsc0Rlc3Ryb3lpbmdcIjtcbiAgRXZlbnRzW1wiS0VZX0xPQURJTkdcIl0gPSBcImhsc0tleUxvYWRpbmdcIjtcbiAgRXZlbnRzW1wiS0VZX0xPQURFRFwiXSA9IFwiaGxzS2V5TG9hZGVkXCI7XG4gIEV2ZW50c1tcIkxJVkVfQkFDS19CVUZGRVJfUkVBQ0hFRFwiXSA9IFwiaGxzTGl2ZUJhY2tCdWZmZXJSZWFjaGVkXCI7XG4gIEV2ZW50c1tcIkJBQ0tfQlVGRkVSX1JFQUNIRURcIl0gPSBcImhsc0JhY2tCdWZmZXJSZWFjaGVkXCI7XG4gIEV2ZW50c1tcIlNURUVSSU5HX01BTklGRVNUX0xPQURFRFwiXSA9IFwiaGxzU3RlZXJpbmdNYW5pZmVzdExvYWRlZFwiO1xuICByZXR1cm4gRXZlbnRzO1xufSh7fSk7XG5cbi8qKlxuICogRGVmaW5lcyBlYWNoIEV2ZW50IHR5cGUgYW5kIHBheWxvYWQgYnkgRXZlbnQgbmFtZS4gVXNlZCBpbiB7QGxpbmsgaGxzLmpzI0hsc0V2ZW50RW1pdHRlcn0gdG8gc3Ryb25nbHkgdHlwZSB0aGUgZXZlbnQgbGlzdGVuZXIgQVBJLlxuICovXG5cbmxldCBFcnJvclR5cGVzID0gLyojX19QVVJFX18qL2Z1bmN0aW9uIChFcnJvclR5cGVzKSB7XG4gIEVycm9yVHlwZXNbXCJORVRXT1JLX0VSUk9SXCJdID0gXCJuZXR3b3JrRXJyb3JcIjtcbiAgRXJyb3JUeXBlc1tcIk1FRElBX0VSUk9SXCJdID0gXCJtZWRpYUVycm9yXCI7XG4gIEVycm9yVHlwZXNbXCJLRVlfU1lTVEVNX0VSUk9SXCJdID0gXCJrZXlTeXN0ZW1FcnJvclwiO1xuICBFcnJvclR5cGVzW1wiTVVYX0VSUk9SXCJdID0gXCJtdXhFcnJvclwiO1xuICBFcnJvclR5cGVzW1wiT1RIRVJfRVJST1JcIl0gPSBcIm90aGVyRXJyb3JcIjtcbiAgcmV0dXJuIEVycm9yVHlwZXM7XG59KHt9KTtcbmxldCBFcnJvckRldGFpbHMgPSAvKiNfX1BVUkVfXyovZnVuY3Rpb24gKEVycm9yRGV0YWlscykge1xuICBFcnJvckRldGFpbHNbXCJLRVlfU1lTVEVNX05PX0tFWVNcIl0gPSBcImtleVN5c3RlbU5vS2V5c1wiO1xuICBFcnJvckRldGFpbHNbXCJLRVlfU1lTVEVNX05PX0FDQ0VTU1wiXSA9IFwia2V5U3lzdGVtTm9BY2Nlc3NcIjtcbiAgRXJyb3JEZXRhaWxzW1wiS0VZX1NZU1RFTV9OT19TRVNTSU9OXCJdID0gXCJrZXlTeXN0ZW1Ob1Nlc3Npb25cIjtcbiAgRXJyb3JEZXRhaWxzW1wiS0VZX1NZU1RFTV9OT19DT05GSUdVUkVEX0xJQ0VOU0VcIl0gPSBcImtleVN5c3RlbU5vQ29uZmlndXJlZExpY2Vuc2VcIjtcbiAgRXJyb3JEZXRhaWxzW1wiS0VZX1NZU1RFTV9MSUNFTlNFX1JFUVVFU1RfRkFJTEVEXCJdID0gXCJrZXlTeXN0ZW1MaWNlbnNlUmVxdWVzdEZhaWxlZFwiO1xuICBFcnJvckRldGFpbHNbXCJLRVlfU1lTVEVNX1NFUlZFUl9DRVJUSUZJQ0FURV9SRVFVRVNUX0ZBSUxFRFwiXSA9IFwia2V5U3lzdGVtU2VydmVyQ2VydGlmaWNhdGVSZXF1ZXN0RmFpbGVkXCI7XG4gIEVycm9yRGV0YWlsc1tcIktFWV9TWVNURU1fU0VSVkVSX0NFUlRJRklDQVRFX1VQREFURV9GQUlMRURcIl0gPSBcImtleVN5c3RlbVNlcnZlckNlcnRpZmljYXRlVXBkYXRlRmFpbGVkXCI7XG4gIEVycm9yRGV0YWlsc1tcIktFWV9TWVNURU1fU0VTU0lPTl9VUERBVEVfRkFJTEVEXCJdID0gXCJrZXlTeXN0ZW1TZXNzaW9uVXBkYXRlRmFpbGVkXCI7XG4gIEVycm9yRGV0YWlsc1tcIktFWV9TWVNURU1fU1RBVFVTX09VVFBVVF9SRVNUUklDVEVEXCJdID0gXCJrZXlTeXN0ZW1TdGF0dXNPdXRwdXRSZXN0cmljdGVkXCI7XG4gIEVycm9yRGV0YWlsc1tcIktFWV9TWVNURU1fU1RBVFVTX0lOVEVSTkFMX0VSUk9SXCJdID0gXCJrZXlTeXN0ZW1TdGF0dXNJbnRlcm5hbEVycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIk1BTklGRVNUX0xPQURfRVJST1JcIl0gPSBcIm1hbmlmZXN0TG9hZEVycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIk1BTklGRVNUX0xPQURfVElNRU9VVFwiXSA9IFwibWFuaWZlc3RMb2FkVGltZU91dFwiO1xuICBFcnJvckRldGFpbHNbXCJNQU5JRkVTVF9QQVJTSU5HX0VSUk9SXCJdID0gXCJtYW5pZmVzdFBhcnNpbmdFcnJvclwiO1xuICBFcnJvckRldGFpbHNbXCJNQU5JRkVTVF9JTkNPTVBBVElCTEVfQ09ERUNTX0VSUk9SXCJdID0gXCJtYW5pZmVzdEluY29tcGF0aWJsZUNvZGVjc0Vycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIkxFVkVMX0VNUFRZX0VSUk9SXCJdID0gXCJsZXZlbEVtcHR5RXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiTEVWRUxfTE9BRF9FUlJPUlwiXSA9IFwibGV2ZWxMb2FkRXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiTEVWRUxfTE9BRF9USU1FT1VUXCJdID0gXCJsZXZlbExvYWRUaW1lT3V0XCI7XG4gIEVycm9yRGV0YWlsc1tcIkxFVkVMX1BBUlNJTkdfRVJST1JcIl0gPSBcImxldmVsUGFyc2luZ0Vycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIkxFVkVMX1NXSVRDSF9FUlJPUlwiXSA9IFwibGV2ZWxTd2l0Y2hFcnJvclwiO1xuICBFcnJvckRldGFpbHNbXCJBVURJT19UUkFDS19MT0FEX0VSUk9SXCJdID0gXCJhdWRpb1RyYWNrTG9hZEVycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIkFVRElPX1RSQUNLX0xPQURfVElNRU9VVFwiXSA9IFwiYXVkaW9UcmFja0xvYWRUaW1lT3V0XCI7XG4gIEVycm9yRGV0YWlsc1tcIlNVQlRJVExFX0xPQURfRVJST1JcIl0gPSBcInN1YnRpdGxlVHJhY2tMb2FkRXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiU1VCVElUTEVfVFJBQ0tfTE9BRF9USU1FT1VUXCJdID0gXCJzdWJ0aXRsZVRyYWNrTG9hZFRpbWVPdXRcIjtcbiAgRXJyb3JEZXRhaWxzW1wiRlJBR19MT0FEX0VSUk9SXCJdID0gXCJmcmFnTG9hZEVycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIkZSQUdfTE9BRF9USU1FT1VUXCJdID0gXCJmcmFnTG9hZFRpbWVPdXRcIjtcbiAgRXJyb3JEZXRhaWxzW1wiRlJBR19ERUNSWVBUX0VSUk9SXCJdID0gXCJmcmFnRGVjcnlwdEVycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIkZSQUdfUEFSU0lOR19FUlJPUlwiXSA9IFwiZnJhZ1BhcnNpbmdFcnJvclwiO1xuICBFcnJvckRldGFpbHNbXCJGUkFHX0dBUFwiXSA9IFwiZnJhZ0dhcFwiO1xuICBFcnJvckRldGFpbHNbXCJSRU1VWF9BTExPQ19FUlJPUlwiXSA9IFwicmVtdXhBbGxvY0Vycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIktFWV9MT0FEX0VSUk9SXCJdID0gXCJrZXlMb2FkRXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiS0VZX0xPQURfVElNRU9VVFwiXSA9IFwia2V5TG9hZFRpbWVPdXRcIjtcbiAgRXJyb3JEZXRhaWxzW1wiQlVGRkVSX0FERF9DT0RFQ19FUlJPUlwiXSA9IFwiYnVmZmVyQWRkQ29kZWNFcnJvclwiO1xuICBFcnJvckRldGFpbHNbXCJCVUZGRVJfSU5DT01QQVRJQkxFX0NPREVDU19FUlJPUlwiXSA9IFwiYnVmZmVySW5jb21wYXRpYmxlQ29kZWNzRXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiQlVGRkVSX0FQUEVORF9FUlJPUlwiXSA9IFwiYnVmZmVyQXBwZW5kRXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiQlVGRkVSX0FQUEVORElOR19FUlJPUlwiXSA9IFwiYnVmZmVyQXBwZW5kaW5nRXJyb3JcIjtcbiAgRXJyb3JEZXRhaWxzW1wiQlVGRkVSX1NUQUxMRURfRVJST1JcIl0gPSBcImJ1ZmZlclN0YWxsZWRFcnJvclwiO1xuICBFcnJvckRldGFpbHNbXCJCVUZGRVJfRlVMTF9FUlJPUlwiXSA9IFwiYnVmZmVyRnVsbEVycm9yXCI7XG4gIEVycm9yRGV0YWlsc1tcIkJVRkZFUl9TRUVLX09WRVJfSE9MRVwiXSA9IFwiYnVmZmVyU2Vla092ZXJIb2xlXCI7XG4gIEVycm9yRGV0YWlsc1tcIkJVRkZFUl9OVURHRV9PTl9TVEFMTFwiXSA9IFwiYnVmZmVyTnVkZ2VPblN0YWxsXCI7XG4gIEVycm9yRGV0YWlsc1tcIklOVEVSTkFMX0VYQ0VQVElPTlwiXSA9IFwiaW50ZXJuYWxFeGNlcHRpb25cIjtcbiAgRXJyb3JEZXRhaWxzW1wiSU5URVJOQUxfQUJPUlRFRFwiXSA9IFwiYWJvcnRlZFwiO1xuICBFcnJvckRldGFpbHNbXCJVTktOT1dOXCJdID0gXCJ1bmtub3duXCI7XG4gIHJldHVybiBFcnJvckRldGFpbHM7XG59KHt9KTtcblxuY29uc3Qgbm9vcCA9IGZ1bmN0aW9uIG5vb3AoKSB7fTtcbmNvbnN0IGZha2VMb2dnZXIgPSB7XG4gIHRyYWNlOiBub29wLFxuICBkZWJ1Zzogbm9vcCxcbiAgbG9nOiBub29wLFxuICB3YXJuOiBub29wLFxuICBpbmZvOiBub29wLFxuICBlcnJvcjogbm9vcFxufTtcbmxldCBleHBvcnRlZExvZ2dlciA9IGZha2VMb2dnZXI7XG5cbi8vIGxldCBsYXN0Q2FsbFRpbWU7XG4vLyBmdW5jdGlvbiBmb3JtYXRNc2dXaXRoVGltZUluZm8odHlwZSwgbXNnKSB7XG4vLyAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4vLyAgIGNvbnN0IGRpZmYgPSBsYXN0Q2FsbFRpbWUgPyAnKycgKyAobm93IC0gbGFzdENhbGxUaW1lKSA6ICcwJztcbi8vICAgbGFzdENhbGxUaW1lID0gbm93O1xuLy8gICBtc2cgPSAobmV3IERhdGUobm93KSkudG9JU09TdHJpbmcoKSArICcgfCBbJyArICB0eXBlICsgJ10gPiAnICsgbXNnICsgJyAoICcgKyBkaWZmICsgJyBtcyApJztcbi8vICAgcmV0dXJuIG1zZztcbi8vIH1cblxuZnVuY3Rpb24gY29uc29sZVByaW50Rm4odHlwZSkge1xuICBjb25zdCBmdW5jID0gc2VsZi5jb25zb2xlW3R5cGVdO1xuICBpZiAoZnVuYykge1xuICAgIHJldHVybiBmdW5jLmJpbmQoc2VsZi5jb25zb2xlLCBgWyR7dHlwZX1dID5gKTtcbiAgfVxuICByZXR1cm4gbm9vcDtcbn1cbmZ1bmN0aW9uIGV4cG9ydExvZ2dlckZ1bmN0aW9ucyhkZWJ1Z0NvbmZpZywgLi4uZnVuY3Rpb25zKSB7XG4gIGZ1bmN0aW9ucy5mb3JFYWNoKGZ1bmN0aW9uICh0eXBlKSB7XG4gICAgZXhwb3J0ZWRMb2dnZXJbdHlwZV0gPSBkZWJ1Z0NvbmZpZ1t0eXBlXSA/IGRlYnVnQ29uZmlnW3R5cGVdLmJpbmQoZGVidWdDb25maWcpIDogY29uc29sZVByaW50Rm4odHlwZSk7XG4gIH0pO1xufVxuZnVuY3Rpb24gZW5hYmxlTG9ncyhkZWJ1Z0NvbmZpZywgaWQpIHtcbiAgLy8gY2hlY2sgdGhhdCBjb25zb2xlIGlzIGF2YWlsYWJsZVxuICBpZiAodHlwZW9mIGNvbnNvbGUgPT09ICdvYmplY3QnICYmIGRlYnVnQ29uZmlnID09PSB0cnVlIHx8IHR5cGVvZiBkZWJ1Z0NvbmZpZyA9PT0gJ29iamVjdCcpIHtcbiAgICBleHBvcnRMb2dnZXJGdW5jdGlvbnMoZGVidWdDb25maWcsXG4gICAgLy8gUmVtb3ZlIG91dCBmcm9tIGxpc3QgaGVyZSB0byBoYXJkLWRpc2FibGUgYSBsb2ctbGV2ZWxcbiAgICAvLyAndHJhY2UnLFxuICAgICdkZWJ1ZycsICdsb2cnLCAnaW5mbycsICd3YXJuJywgJ2Vycm9yJyk7XG4gICAgLy8gU29tZSBicm93c2VycyBkb24ndCBhbGxvdyB0byB1c2UgYmluZCBvbiBjb25zb2xlIG9iamVjdCBhbnl3YXlcbiAgICAvLyBmYWxsYmFjayB0byBkZWZhdWx0IGlmIG5lZWRlZFxuICAgIHRyeSB7XG4gICAgICBleHBvcnRlZExvZ2dlci5sb2coYERlYnVnIGxvZ3MgZW5hYmxlZCBmb3IgXCIke2lkfVwiIGluIGhscy5qcyB2ZXJzaW9uICR7XCIxLjUuMTVcIn1gKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBleHBvcnRlZExvZ2dlciA9IGZha2VMb2dnZXI7XG4gICAgfVxuICB9IGVsc2Uge1xuICAgIGV4cG9ydGVkTG9nZ2VyID0gZmFrZUxvZ2dlcjtcbiAgfVxufVxuY29uc3QgbG9nZ2VyID0gZXhwb3J0ZWRMb2dnZXI7XG5cbmNvbnN0IERFQ0lNQUxfUkVTT0xVVElPTl9SRUdFWCA9IC9eKFxcZCspeChcXGQrKSQvO1xuY29uc3QgQVRUUl9MSVNUX1JFR0VYID0gLyguKz8pPShcIi4qP1wifC4qPykoPzosfCQpL2c7XG5cbi8vIGFkYXB0ZWQgZnJvbSBodHRwczovL2dpdGh1Yi5jb20va2Fub25naWwvbm9kZS1tM3U4cGFyc2UvYmxvYi9tYXN0ZXIvYXR0cmxpc3QuanNcbmNsYXNzIEF0dHJMaXN0IHtcbiAgY29uc3RydWN0b3IoYXR0cnMpIHtcbiAgICBpZiAodHlwZW9mIGF0dHJzID09PSAnc3RyaW5nJykge1xuICAgICAgYXR0cnMgPSBBdHRyTGlzdC5wYXJzZUF0dHJMaXN0KGF0dHJzKTtcbiAgICB9XG4gICAgX2V4dGVuZHModGhpcywgYXR0cnMpO1xuICB9XG4gIGdldCBjbGllbnRBdHRycygpIHtcbiAgICByZXR1cm4gT2JqZWN0LmtleXModGhpcykuZmlsdGVyKGF0dHIgPT4gYXR0ci5zdWJzdHJpbmcoMCwgMikgPT09ICdYLScpO1xuICB9XG4gIGRlY2ltYWxJbnRlZ2VyKGF0dHJOYW1lKSB7XG4gICAgY29uc3QgaW50VmFsdWUgPSBwYXJzZUludCh0aGlzW2F0dHJOYW1lXSwgMTApO1xuICAgIGlmIChpbnRWYWx1ZSA+IE51bWJlci5NQVhfU0FGRV9JTlRFR0VSKSB7XG4gICAgICByZXR1cm4gSW5maW5pdHk7XG4gICAgfVxuICAgIHJldHVybiBpbnRWYWx1ZTtcbiAgfVxuICBoZXhhZGVjaW1hbEludGVnZXIoYXR0ck5hbWUpIHtcbiAgICBpZiAodGhpc1thdHRyTmFtZV0pIHtcbiAgICAgIGxldCBzdHJpbmdWYWx1ZSA9ICh0aGlzW2F0dHJOYW1lXSB8fCAnMHgnKS5zbGljZSgyKTtcbiAgICAgIHN0cmluZ1ZhbHVlID0gKHN0cmluZ1ZhbHVlLmxlbmd0aCAmIDEgPyAnMCcgOiAnJykgKyBzdHJpbmdWYWx1ZTtcbiAgICAgIGNvbnN0IHZhbHVlID0gbmV3IFVpbnQ4QXJyYXkoc3RyaW5nVmFsdWUubGVuZ3RoIC8gMik7XG4gICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHN0cmluZ1ZhbHVlLmxlbmd0aCAvIDI7IGkrKykge1xuICAgICAgICB2YWx1ZVtpXSA9IHBhcnNlSW50KHN0cmluZ1ZhbHVlLnNsaWNlKGkgKiAyLCBpICogMiArIDIpLCAxNik7XG4gICAgICB9XG4gICAgICByZXR1cm4gdmFsdWU7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuICBoZXhhZGVjaW1hbEludGVnZXJBc051bWJlcihhdHRyTmFtZSkge1xuICAgIGNvbnN0IGludFZhbHVlID0gcGFyc2VJbnQodGhpc1thdHRyTmFtZV0sIDE2KTtcbiAgICBpZiAoaW50VmFsdWUgPiBOdW1iZXIuTUFYX1NBRkVfSU5URUdFUikge1xuICAgICAgcmV0dXJuIEluZmluaXR5O1xuICAgIH1cbiAgICByZXR1cm4gaW50VmFsdWU7XG4gIH1cbiAgZGVjaW1hbEZsb2F0aW5nUG9pbnQoYXR0ck5hbWUpIHtcbiAgICByZXR1cm4gcGFyc2VGbG9hdCh0aGlzW2F0dHJOYW1lXSk7XG4gIH1cbiAgb3B0aW9uYWxGbG9hdChhdHRyTmFtZSwgZGVmYXVsdFZhbHVlKSB7XG4gICAgY29uc3QgdmFsdWUgPSB0aGlzW2F0dHJOYW1lXTtcbiAgICByZXR1cm4gdmFsdWUgPyBwYXJzZUZsb2F0KHZhbHVlKSA6IGRlZmF1bHRWYWx1ZTtcbiAgfVxuICBlbnVtZXJhdGVkU3RyaW5nKGF0dHJOYW1lKSB7XG4gICAgcmV0dXJuIHRoaXNbYXR0ck5hbWVdO1xuICB9XG4gIGJvb2woYXR0ck5hbWUpIHtcbiAgICByZXR1cm4gdGhpc1thdHRyTmFtZV0gPT09ICdZRVMnO1xuICB9XG4gIGRlY2ltYWxSZXNvbHV0aW9uKGF0dHJOYW1lKSB7XG4gICAgY29uc3QgcmVzID0gREVDSU1BTF9SRVNPTFVUSU9OX1JFR0VYLmV4ZWModGhpc1thdHRyTmFtZV0pO1xuICAgIGlmIChyZXMgPT09IG51bGwpIHtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICB3aWR0aDogcGFyc2VJbnQocmVzWzFdLCAxMCksXG4gICAgICBoZWlnaHQ6IHBhcnNlSW50KHJlc1syXSwgMTApXG4gICAgfTtcbiAgfVxuICBzdGF0aWMgcGFyc2VBdHRyTGlzdChpbnB1dCkge1xuICAgIGxldCBtYXRjaDtcbiAgICBjb25zdCBhdHRycyA9IHt9O1xuICAgIGNvbnN0IHF1b3RlID0gJ1wiJztcbiAgICBBVFRSX0xJU1RfUkVHRVgubGFzdEluZGV4ID0gMDtcbiAgICB3aGlsZSAoKG1hdGNoID0gQVRUUl9MSVNUX1JFR0VYLmV4ZWMoaW5wdXQpKSAhPT0gbnVsbCkge1xuICAgICAgbGV0IHZhbHVlID0gbWF0Y2hbMl07XG4gICAgICBpZiAodmFsdWUuaW5kZXhPZihxdW90ZSkgPT09IDAgJiYgdmFsdWUubGFzdEluZGV4T2YocXVvdGUpID09PSB2YWx1ZS5sZW5ndGggLSAxKSB7XG4gICAgICAgIHZhbHVlID0gdmFsdWUuc2xpY2UoMSwgLTEpO1xuICAgICAgfVxuICAgICAgY29uc3QgbmFtZSA9IG1hdGNoWzFdLnRyaW0oKTtcbiAgICAgIGF0dHJzW25hbWVdID0gdmFsdWU7XG4gICAgfVxuICAgIHJldHVybiBhdHRycztcbiAgfVxufVxuXG4vLyBBdm9pZCBleHBvcnRpbmcgY29uc3QgZW51bSBzbyB0aGF0IHRoZXNlIHZhbHVlcyBjYW4gYmUgaW5saW5lZFxuXG5mdW5jdGlvbiBpc0RhdGVSYW5nZUN1ZUF0dHJpYnV0ZShhdHRyTmFtZSkge1xuICByZXR1cm4gYXR0ck5hbWUgIT09IFwiSURcIiAmJiBhdHRyTmFtZSAhPT0gXCJDTEFTU1wiICYmIGF0dHJOYW1lICE9PSBcIlNUQVJULURBVEVcIiAmJiBhdHRyTmFtZSAhPT0gXCJEVVJBVElPTlwiICYmIGF0dHJOYW1lICE9PSBcIkVORC1EQVRFXCIgJiYgYXR0ck5hbWUgIT09IFwiRU5ELU9OLU5FWFRcIjtcbn1cbmZ1bmN0aW9uIGlzU0NURTM1QXR0cmlidXRlKGF0dHJOYW1lKSB7XG4gIHJldHVybiBhdHRyTmFtZSA9PT0gXCJTQ1RFMzUtT1VUXCIgfHwgYXR0ck5hbWUgPT09IFwiU0NURTM1LUlOXCI7XG59XG5jbGFzcyBEYXRlUmFuZ2Uge1xuICBjb25zdHJ1Y3RvcihkYXRlUmFuZ2VBdHRyLCBkYXRlUmFuZ2VXaXRoU2FtZUlkKSB7XG4gICAgdGhpcy5hdHRyID0gdm9pZCAwO1xuICAgIHRoaXMuX3N0YXJ0RGF0ZSA9IHZvaWQgMDtcbiAgICB0aGlzLl9lbmREYXRlID0gdm9pZCAwO1xuICAgIHRoaXMuX2JhZFZhbHVlRm9yU2FtZUlkID0gdm9pZCAwO1xuICAgIGlmIChkYXRlUmFuZ2VXaXRoU2FtZUlkKSB7XG4gICAgICBjb25zdCBwcmV2aW91c0F0dHIgPSBkYXRlUmFuZ2VXaXRoU2FtZUlkLmF0dHI7XG4gICAgICBmb3IgKGNvbnN0IGtleSBpbiBwcmV2aW91c0F0dHIpIHtcbiAgICAgICAgaWYgKE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChkYXRlUmFuZ2VBdHRyLCBrZXkpICYmIGRhdGVSYW5nZUF0dHJba2V5XSAhPT0gcHJldmlvdXNBdHRyW2tleV0pIHtcbiAgICAgICAgICBsb2dnZXIud2FybihgREFURVJBTkdFIHRhZyBhdHRyaWJ1dGU6IFwiJHtrZXl9XCIgZG9lcyBub3QgbWF0Y2ggZm9yIHRhZ3Mgd2l0aCBJRDogXCIke2RhdGVSYW5nZUF0dHIuSUR9XCJgKTtcbiAgICAgICAgICB0aGlzLl9iYWRWYWx1ZUZvclNhbWVJZCA9IGtleTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgLy8gTWVyZ2UgRGF0ZVJhbmdlIHRhZ3Mgd2l0aCB0aGUgc2FtZSBJRFxuICAgICAgZGF0ZVJhbmdlQXR0ciA9IF9leHRlbmRzKG5ldyBBdHRyTGlzdCh7fSksIHByZXZpb3VzQXR0ciwgZGF0ZVJhbmdlQXR0cik7XG4gICAgfVxuICAgIHRoaXMuYXR0ciA9IGRhdGVSYW5nZUF0dHI7XG4gICAgdGhpcy5fc3RhcnREYXRlID0gbmV3IERhdGUoZGF0ZVJhbmdlQXR0cltcIlNUQVJULURBVEVcIl0pO1xuICAgIGlmIChcIkVORC1EQVRFXCIgaW4gdGhpcy5hdHRyKSB7XG4gICAgICBjb25zdCBlbmREYXRlID0gbmV3IERhdGUodGhpcy5hdHRyW1wiRU5ELURBVEVcIl0pO1xuICAgICAgaWYgKGlzRmluaXRlTnVtYmVyKGVuZERhdGUuZ2V0VGltZSgpKSkge1xuICAgICAgICB0aGlzLl9lbmREYXRlID0gZW5kRGF0ZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgZ2V0IGlkKCkge1xuICAgIHJldHVybiB0aGlzLmF0dHIuSUQ7XG4gIH1cbiAgZ2V0IGNsYXNzKCkge1xuICAgIHJldHVybiB0aGlzLmF0dHIuQ0xBU1M7XG4gIH1cbiAgZ2V0IHN0YXJ0RGF0ZSgpIHtcbiAgICByZXR1cm4gdGhpcy5fc3RhcnREYXRlO1xuICB9XG4gIGdldCBlbmREYXRlKCkge1xuICAgIGlmICh0aGlzLl9lbmREYXRlKSB7XG4gICAgICByZXR1cm4gdGhpcy5fZW5kRGF0ZTtcbiAgICB9XG4gICAgY29uc3QgZHVyYXRpb24gPSB0aGlzLmR1cmF0aW9uO1xuICAgIGlmIChkdXJhdGlvbiAhPT0gbnVsbCkge1xuICAgICAgcmV0dXJuIG5ldyBEYXRlKHRoaXMuX3N0YXJ0RGF0ZS5nZXRUaW1lKCkgKyBkdXJhdGlvbiAqIDEwMDApO1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBnZXQgZHVyYXRpb24oKSB7XG4gICAgaWYgKFwiRFVSQVRJT05cIiBpbiB0aGlzLmF0dHIpIHtcbiAgICAgIGNvbnN0IGR1cmF0aW9uID0gdGhpcy5hdHRyLmRlY2ltYWxGbG9hdGluZ1BvaW50KFwiRFVSQVRJT05cIik7XG4gICAgICBpZiAoaXNGaW5pdGVOdW1iZXIoZHVyYXRpb24pKSB7XG4gICAgICAgIHJldHVybiBkdXJhdGlvbjtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHRoaXMuX2VuZERhdGUpIHtcbiAgICAgIHJldHVybiAodGhpcy5fZW5kRGF0ZS5nZXRUaW1lKCkgLSB0aGlzLl9zdGFydERhdGUuZ2V0VGltZSgpKSAvIDEwMDA7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGdldCBwbGFubmVkRHVyYXRpb24oKSB7XG4gICAgaWYgKFwiUExBTk5FRC1EVVJBVElPTlwiIGluIHRoaXMuYXR0cikge1xuICAgICAgcmV0dXJuIHRoaXMuYXR0ci5kZWNpbWFsRmxvYXRpbmdQb2ludChcIlBMQU5ORUQtRFVSQVRJT05cIik7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGdldCBlbmRPbk5leHQoKSB7XG4gICAgcmV0dXJuIHRoaXMuYXR0ci5ib29sKFwiRU5ELU9OLU5FWFRcIik7XG4gIH1cbiAgZ2V0IGlzVmFsaWQoKSB7XG4gICAgcmV0dXJuICEhdGhpcy5pZCAmJiAhdGhpcy5fYmFkVmFsdWVGb3JTYW1lSWQgJiYgaXNGaW5pdGVOdW1iZXIodGhpcy5zdGFydERhdGUuZ2V0VGltZSgpKSAmJiAodGhpcy5kdXJhdGlvbiA9PT0gbnVsbCB8fCB0aGlzLmR1cmF0aW9uID49IDApICYmICghdGhpcy5lbmRPbk5leHQgfHwgISF0aGlzLmNsYXNzKTtcbiAgfVxufVxuXG5jbGFzcyBMb2FkU3RhdHMge1xuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLmFib3J0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmxvYWRlZCA9IDA7XG4gICAgdGhpcy5yZXRyeSA9IDA7XG4gICAgdGhpcy50b3RhbCA9IDA7XG4gICAgdGhpcy5jaHVua0NvdW50ID0gMDtcbiAgICB0aGlzLmJ3RXN0aW1hdGUgPSAwO1xuICAgIHRoaXMubG9hZGluZyA9IHtcbiAgICAgIHN0YXJ0OiAwLFxuICAgICAgZmlyc3Q6IDAsXG4gICAgICBlbmQ6IDBcbiAgICB9O1xuICAgIHRoaXMucGFyc2luZyA9IHtcbiAgICAgIHN0YXJ0OiAwLFxuICAgICAgZW5kOiAwXG4gICAgfTtcbiAgICB0aGlzLmJ1ZmZlcmluZyA9IHtcbiAgICAgIHN0YXJ0OiAwLFxuICAgICAgZmlyc3Q6IDAsXG4gICAgICBlbmQ6IDBcbiAgICB9O1xuICB9XG59XG5cbnZhciBFbGVtZW50YXJ5U3RyZWFtVHlwZXMgPSB7XG4gIEFVRElPOiBcImF1ZGlvXCIsXG4gIFZJREVPOiBcInZpZGVvXCIsXG4gIEFVRElPVklERU86IFwiYXVkaW92aWRlb1wiXG59O1xuY2xhc3MgQmFzZVNlZ21lbnQge1xuICBjb25zdHJ1Y3RvcihiYXNldXJsKSB7XG4gICAgdGhpcy5fYnl0ZVJhbmdlID0gbnVsbDtcbiAgICB0aGlzLl91cmwgPSBudWxsO1xuICAgIC8vIGJhc2V1cmwgaXMgdGhlIFVSTCB0byB0aGUgcGxheWxpc3RcbiAgICB0aGlzLmJhc2V1cmwgPSB2b2lkIDA7XG4gICAgLy8gcmVsdXJsIGlzIHRoZSBwb3J0aW9uIG9mIHRoZSBVUkwgdGhhdCBjb21lcyBmcm9tIGluc2lkZSB0aGUgcGxheWxpc3QuXG4gICAgdGhpcy5yZWx1cmwgPSB2b2lkIDA7XG4gICAgLy8gSG9sZHMgdGhlIHR5cGVzIG9mIGRhdGEgdGhpcyBmcmFnbWVudCBzdXBwb3J0c1xuICAgIHRoaXMuZWxlbWVudGFyeVN0cmVhbXMgPSB7XG4gICAgICBbRWxlbWVudGFyeVN0cmVhbVR5cGVzLkFVRElPXTogbnVsbCxcbiAgICAgIFtFbGVtZW50YXJ5U3RyZWFtVHlwZXMuVklERU9dOiBudWxsLFxuICAgICAgW0VsZW1lbnRhcnlTdHJlYW1UeXBlcy5BVURJT1ZJREVPXTogbnVsbFxuICAgIH07XG4gICAgdGhpcy5iYXNldXJsID0gYmFzZXVybDtcbiAgfVxuXG4gIC8vIHNldEJ5dGVSYW5nZSBjb252ZXJ0cyBhIEVYVC1YLUJZVEVSQU5HRSBhdHRyaWJ1dGUgaW50byBhIHR3byBlbGVtZW50IGFycmF5XG4gIHNldEJ5dGVSYW5nZSh2YWx1ZSwgcHJldmlvdXMpIHtcbiAgICBjb25zdCBwYXJhbXMgPSB2YWx1ZS5zcGxpdCgnQCcsIDIpO1xuICAgIGxldCBzdGFydDtcbiAgICBpZiAocGFyYW1zLmxlbmd0aCA9PT0gMSkge1xuICAgICAgc3RhcnQgPSAocHJldmlvdXMgPT0gbnVsbCA/IHZvaWQgMCA6IHByZXZpb3VzLmJ5dGVSYW5nZUVuZE9mZnNldCkgfHwgMDtcbiAgICB9IGVsc2Uge1xuICAgICAgc3RhcnQgPSBwYXJzZUludChwYXJhbXNbMV0pO1xuICAgIH1cbiAgICB0aGlzLl9ieXRlUmFuZ2UgPSBbc3RhcnQsIHBhcnNlSW50KHBhcmFtc1swXSkgKyBzdGFydF07XG4gIH1cbiAgZ2V0IGJ5dGVSYW5nZSgpIHtcbiAgICBpZiAoIXRoaXMuX2J5dGVSYW5nZSkge1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fYnl0ZVJhbmdlO1xuICB9XG4gIGdldCBieXRlUmFuZ2VTdGFydE9mZnNldCgpIHtcbiAgICByZXR1cm4gdGhpcy5ieXRlUmFuZ2VbMF07XG4gIH1cbiAgZ2V0IGJ5dGVSYW5nZUVuZE9mZnNldCgpIHtcbiAgICByZXR1cm4gdGhpcy5ieXRlUmFuZ2VbMV07XG4gIH1cbiAgZ2V0IHVybCgpIHtcbiAgICBpZiAoIXRoaXMuX3VybCAmJiB0aGlzLmJhc2V1cmwgJiYgdGhpcy5yZWx1cmwpIHtcbiAgICAgIHRoaXMuX3VybCA9IHVybFRvb2xraXRFeHBvcnRzLmJ1aWxkQWJzb2x1dGVVUkwodGhpcy5iYXNldXJsLCB0aGlzLnJlbHVybCwge1xuICAgICAgICBhbHdheXNOb3JtYWxpemU6IHRydWVcbiAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fdXJsIHx8ICcnO1xuICB9XG4gIHNldCB1cmwodmFsdWUpIHtcbiAgICB0aGlzLl91cmwgPSB2YWx1ZTtcbiAgfVxufVxuXG4vKipcbiAqIE9iamVjdCByZXByZXNlbnRpbmcgcGFyc2VkIGRhdGEgZnJvbSBhbiBITFMgU2VnbWVudC4gRm91bmQgaW4ge0BsaW5rIGhscy5qcyNMZXZlbERldGFpbHMuZnJhZ21lbnRzfS5cbiAqL1xuY2xhc3MgRnJhZ21lbnQgZXh0ZW5kcyBCYXNlU2VnbWVudCB7XG4gIGNvbnN0cnVjdG9yKHR5cGUsIGJhc2V1cmwpIHtcbiAgICBzdXBlcihiYXNldXJsKTtcbiAgICB0aGlzLl9kZWNyeXB0ZGF0YSA9IG51bGw7XG4gICAgdGhpcy5yYXdQcm9ncmFtRGF0ZVRpbWUgPSBudWxsO1xuICAgIHRoaXMucHJvZ3JhbURhdGVUaW1lID0gbnVsbDtcbiAgICB0aGlzLnRhZ0xpc3QgPSBbXTtcbiAgICAvLyBFWFRJTkYgaGFzIHRvIGJlIHByZXNlbnQgZm9yIGEgbTN1OCB0byBiZSBjb25zaWRlcmVkIHZhbGlkXG4gICAgdGhpcy5kdXJhdGlvbiA9IDA7XG4gICAgLy8gc24gbm90YXRlcyB0aGUgc2VxdWVuY2UgbnVtYmVyIGZvciBhIHNlZ21lbnQsIGFuZCBpZiBzZXQgdG8gYSBzdHJpbmcgY2FuIGJlICdpbml0U2VnbWVudCdcbiAgICB0aGlzLnNuID0gMDtcbiAgICAvLyBsZXZlbGtleXMgYXJlIHRoZSBFWFQtWC1LRVkgdGFncyB0aGF0IGFwcGx5IHRvIHRoaXMgc2VnbWVudCBmb3IgZGVjcnlwdGlvblxuICAgIC8vIGNvcmUgZGlmZmVyZW5jZSBmcm9tIHRoZSBwcml2YXRlIGZpZWxkIF9kZWNyeXB0ZGF0YSBpcyB0aGUgbGFjayBvZiB0aGUgaW5pdGlhbGl6ZWQgSVZcbiAgICAvLyBfZGVjcnlwdGRhdGEgd2lsbCBzZXQgdGhlIElWIGZvciB0aGlzIHNlZ21lbnQgYmFzZWQgb24gdGhlIHNlZ21lbnQgbnVtYmVyIGluIHRoZSBmcmFnbWVudFxuICAgIHRoaXMubGV2ZWxrZXlzID0gdm9pZCAwO1xuICAgIC8vIEEgc3RyaW5nIHJlcHJlc2VudGluZyB0aGUgZnJhZ21lbnQgdHlwZVxuICAgIHRoaXMudHlwZSA9IHZvaWQgMDtcbiAgICAvLyBBIHJlZmVyZW5jZSB0byB0aGUgbG9hZGVyLiBTZXQgd2hpbGUgdGhlIGZyYWdtZW50IGlzIGxvYWRpbmcsIGFuZCByZW1vdmVkIGFmdGVyd2FyZHMuIFVzZWQgdG8gYWJvcnQgZnJhZ21lbnQgbG9hZGluZ1xuICAgIHRoaXMubG9hZGVyID0gbnVsbDtcbiAgICAvLyBBIHJlZmVyZW5jZSB0byB0aGUga2V5IGxvYWRlci4gU2V0IHdoaWxlIHRoZSBrZXkgaXMgbG9hZGluZywgYW5kIHJlbW92ZWQgYWZ0ZXJ3YXJkcy4gVXNlZCB0byBhYm9ydCBrZXkgbG9hZGluZ1xuICAgIHRoaXMua2V5TG9hZGVyID0gbnVsbDtcbiAgICAvLyBUaGUgbGV2ZWwvdHJhY2sgaW5kZXggdG8gd2hpY2ggdGhlIGZyYWdtZW50IGJlbG9uZ3NcbiAgICB0aGlzLmxldmVsID0gLTE7XG4gICAgLy8gVGhlIGNvbnRpbnVpdHkgY291bnRlciBvZiB0aGUgZnJhZ21lbnRcbiAgICB0aGlzLmNjID0gMDtcbiAgICAvLyBUaGUgc3RhcnRpbmcgUHJlc2VudGF0aW9uIFRpbWUgU3RhbXAgKFBUUykgb2YgdGhlIGZyYWdtZW50LiBTZXQgYWZ0ZXIgdHJhbnNtdXggY29tcGxldGUuXG4gICAgdGhpcy5zdGFydFBUUyA9IHZvaWQgMDtcbiAgICAvLyBUaGUgZW5kaW5nIFByZXNlbnRhdGlvbiBUaW1lIFN0YW1wIChQVFMpIG9mIHRoZSBmcmFnbWVudC4gU2V0IGFmdGVyIHRyYW5zbXV4IGNvbXBsZXRlLlxuICAgIHRoaXMuZW5kUFRTID0gdm9pZCAwO1xuICAgIC8vIFRoZSBzdGFydGluZyBEZWNvZGUgVGltZSBTdGFtcCAoRFRTKSBvZiB0aGUgZnJhZ21lbnQuIFNldCBhZnRlciB0cmFuc211eCBjb21wbGV0ZS5cbiAgICB0aGlzLnN0YXJ0RFRTID0gdm9pZCAwO1xuICAgIC8vIFRoZSBlbmRpbmcgRGVjb2RlIFRpbWUgU3RhbXAgKERUUykgb2YgdGhlIGZyYWdtZW50LiBTZXQgYWZ0ZXIgdHJhbnNtdXggY29tcGxldGUuXG4gICAgdGhpcy5lbmREVFMgPSB2b2lkIDA7XG4gICAgLy8gVGhlIHN0YXJ0IHRpbWUgb2YgdGhlIGZyYWdtZW50LCBhcyBsaXN0ZWQgaW4gdGhlIG1hbmlmZXN0LiBVcGRhdGVkIGFmdGVyIHRyYW5zbXV4IGNvbXBsZXRlLlxuICAgIHRoaXMuc3RhcnQgPSAwO1xuICAgIC8vIFNldCBieSBgdXBkYXRlRnJhZ1BUU0RUU2AgaW4gbGV2ZWwtaGVscGVyXG4gICAgdGhpcy5kZWx0YVBUUyA9IHZvaWQgMDtcbiAgICAvLyBUaGUgbWF4aW11bSBzdGFydGluZyBQcmVzZW50YXRpb24gVGltZSBTdGFtcCAoYXVkaW8vdmlkZW8gUFRTKSBvZiB0aGUgZnJhZ21lbnQuIFNldCBhZnRlciB0cmFuc211eCBjb21wbGV0ZS5cbiAgICB0aGlzLm1heFN0YXJ0UFRTID0gdm9pZCAwO1xuICAgIC8vIFRoZSBtaW5pbXVtIGVuZGluZyBQcmVzZW50YXRpb24gVGltZSBTdGFtcCAoYXVkaW8vdmlkZW8gUFRTKSBvZiB0aGUgZnJhZ21lbnQuIFNldCBhZnRlciB0cmFuc211eCBjb21wbGV0ZS5cbiAgICB0aGlzLm1pbkVuZFBUUyA9IHZvaWQgMDtcbiAgICAvLyBMb2FkL3BhcnNlIHRpbWluZyBpbmZvcm1hdGlvblxuICAgIHRoaXMuc3RhdHMgPSBuZXcgTG9hZFN0YXRzKCk7XG4gICAgLy8gSW5pdCBTZWdtZW50IGJ5dGVzICh1bnNldCBmb3IgbWVkaWEgc2VnbWVudHMpXG4gICAgdGhpcy5kYXRhID0gdm9pZCAwO1xuICAgIC8vIEEgZmxhZyBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIHNlZ21lbnQgd2FzIGRvd25sb2FkZWQgaW4gb3JkZXIgdG8gdGVzdCBiaXRyYXRlLCBhbmQgd2FzIG5vdCBidWZmZXJlZFxuICAgIHRoaXMuYml0cmF0ZVRlc3QgPSBmYWxzZTtcbiAgICAvLyAjRVhUSU5GICBzZWdtZW50IHRpdGxlXG4gICAgdGhpcy50aXRsZSA9IG51bGw7XG4gICAgLy8gVGhlIE1lZGlhIEluaXRpYWxpemF0aW9uIFNlY3Rpb24gZm9yIHRoaXMgc2VnbWVudFxuICAgIHRoaXMuaW5pdFNlZ21lbnQgPSBudWxsO1xuICAgIC8vIEZyYWdtZW50IGlzIHRoZSBsYXN0IGZyYWdtZW50IGluIHRoZSBtZWRpYSBwbGF5bGlzdFxuICAgIHRoaXMuZW5kTGlzdCA9IHZvaWQgMDtcbiAgICAvLyBGcmFnbWVudCBpcyBtYXJrZWQgYnkgYW4gRVhULVgtR0FQIHRhZyBpbmRpY2F0aW5nIHRoYXQgaXQgZG9lcyBub3QgY29udGFpbiBtZWRpYSBkYXRhIGFuZCBzaG91bGQgbm90IGJlIGxvYWRlZFxuICAgIHRoaXMuZ2FwID0gdm9pZCAwO1xuICAgIC8vIERlcHJlY2F0ZWRcbiAgICB0aGlzLnVybElkID0gMDtcbiAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICB9XG4gIGdldCBkZWNyeXB0ZGF0YSgpIHtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbGtleXNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIWxldmVsa2V5cyAmJiAhdGhpcy5fZGVjcnlwdGRhdGEpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBpZiAoIXRoaXMuX2RlY3J5cHRkYXRhICYmIHRoaXMubGV2ZWxrZXlzICYmICF0aGlzLmxldmVsa2V5cy5OT05FKSB7XG4gICAgICBjb25zdCBrZXkgPSB0aGlzLmxldmVsa2V5cy5pZGVudGl0eTtcbiAgICAgIGlmIChrZXkpIHtcbiAgICAgICAgdGhpcy5fZGVjcnlwdGRhdGEgPSBrZXkuZ2V0RGVjcnlwdERhdGEodGhpcy5zbik7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBjb25zdCBrZXlGb3JtYXRzID0gT2JqZWN0LmtleXModGhpcy5sZXZlbGtleXMpO1xuICAgICAgICBpZiAoa2V5Rm9ybWF0cy5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcy5fZGVjcnlwdGRhdGEgPSB0aGlzLmxldmVsa2V5c1trZXlGb3JtYXRzWzBdXS5nZXREZWNyeXB0RGF0YSh0aGlzLnNuKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fZGVjcnlwdGRhdGE7XG4gIH1cbiAgZ2V0IGVuZCgpIHtcbiAgICByZXR1cm4gdGhpcy5zdGFydCArIHRoaXMuZHVyYXRpb247XG4gIH1cbiAgZ2V0IGVuZFByb2dyYW1EYXRlVGltZSgpIHtcbiAgICBpZiAodGhpcy5wcm9ncmFtRGF0ZVRpbWUgPT09IG51bGwpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBpZiAoIWlzRmluaXRlTnVtYmVyKHRoaXMucHJvZ3JhbURhdGVUaW1lKSkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGNvbnN0IGR1cmF0aW9uID0gIWlzRmluaXRlTnVtYmVyKHRoaXMuZHVyYXRpb24pID8gMCA6IHRoaXMuZHVyYXRpb247XG4gICAgcmV0dXJuIHRoaXMucHJvZ3JhbURhdGVUaW1lICsgZHVyYXRpb24gKiAxMDAwO1xuICB9XG4gIGdldCBlbmNyeXB0ZWQoKSB7XG4gICAgdmFyIF90aGlzJF9kZWNyeXB0ZGF0YTtcbiAgICAvLyBBdCB0aGUgbTN1OC1wYXJzZXIgbGV2ZWwgd2UgbmVlZCB0byBhZGQgc3VwcG9ydCBmb3IgbWFuaWZlc3Qgc2lnbmFsbGVkIGtleWZvcm1hdHNcbiAgICAvLyB3aGVuIHdlIHdhbnQgdGhlIGZyYWdtZW50IHRvIHN0YXJ0IHJlcG9ydGluZyB0aGF0IGl0IGlzIGVuY3J5cHRlZC5cbiAgICAvLyBDdXJyZW50bHksIGtleUZvcm1hdCB3aWxsIG9ubHkgYmUgc2V0IGZvciBpZGVudGl0eSBrZXlzXG4gICAgaWYgKChfdGhpcyRfZGVjcnlwdGRhdGEgPSB0aGlzLl9kZWNyeXB0ZGF0YSkgIT0gbnVsbCAmJiBfdGhpcyRfZGVjcnlwdGRhdGEuZW5jcnlwdGVkKSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGVsc2UgaWYgKHRoaXMubGV2ZWxrZXlzKSB7XG4gICAgICBjb25zdCBrZXlGb3JtYXRzID0gT2JqZWN0LmtleXModGhpcy5sZXZlbGtleXMpO1xuICAgICAgY29uc3QgbGVuID0ga2V5Rm9ybWF0cy5sZW5ndGg7XG4gICAgICBpZiAobGVuID4gMSB8fCBsZW4gPT09IDEgJiYgdGhpcy5sZXZlbGtleXNba2V5Rm9ybWF0c1swXV0uZW5jcnlwdGVkKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgc2V0S2V5Rm9ybWF0KGtleUZvcm1hdCkge1xuICAgIGlmICh0aGlzLmxldmVsa2V5cykge1xuICAgICAgY29uc3Qga2V5ID0gdGhpcy5sZXZlbGtleXNba2V5Rm9ybWF0XTtcbiAgICAgIGlmIChrZXkgJiYgIXRoaXMuX2RlY3J5cHRkYXRhKSB7XG4gICAgICAgIHRoaXMuX2RlY3J5cHRkYXRhID0ga2V5LmdldERlY3J5cHREYXRhKHRoaXMuc24pO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBhYm9ydFJlcXVlc3RzKCkge1xuICAgIHZhciBfdGhpcyRsb2FkZXIsIF90aGlzJGtleUxvYWRlcjtcbiAgICAoX3RoaXMkbG9hZGVyID0gdGhpcy5sb2FkZXIpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRsb2FkZXIuYWJvcnQoKTtcbiAgICAoX3RoaXMka2V5TG9hZGVyID0gdGhpcy5rZXlMb2FkZXIpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRrZXlMb2FkZXIuYWJvcnQoKTtcbiAgfVxuICBzZXRFbGVtZW50YXJ5U3RyZWFtSW5mbyh0eXBlLCBzdGFydFBUUywgZW5kUFRTLCBzdGFydERUUywgZW5kRFRTLCBwYXJ0aWFsID0gZmFsc2UpIHtcbiAgICBjb25zdCB7XG4gICAgICBlbGVtZW50YXJ5U3RyZWFtc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IGluZm8gPSBlbGVtZW50YXJ5U3RyZWFtc1t0eXBlXTtcbiAgICBpZiAoIWluZm8pIHtcbiAgICAgIGVsZW1lbnRhcnlTdHJlYW1zW3R5cGVdID0ge1xuICAgICAgICBzdGFydFBUUyxcbiAgICAgICAgZW5kUFRTLFxuICAgICAgICBzdGFydERUUyxcbiAgICAgICAgZW5kRFRTLFxuICAgICAgICBwYXJ0aWFsXG4gICAgICB9O1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpbmZvLnN0YXJ0UFRTID0gTWF0aC5taW4oaW5mby5zdGFydFBUUywgc3RhcnRQVFMpO1xuICAgIGluZm8uZW5kUFRTID0gTWF0aC5tYXgoaW5mby5lbmRQVFMsIGVuZFBUUyk7XG4gICAgaW5mby5zdGFydERUUyA9IE1hdGgubWluKGluZm8uc3RhcnREVFMsIHN0YXJ0RFRTKTtcbiAgICBpbmZvLmVuZERUUyA9IE1hdGgubWF4KGluZm8uZW5kRFRTLCBlbmREVFMpO1xuICB9XG4gIGNsZWFyRWxlbWVudGFyeVN0cmVhbUluZm8oKSB7XG4gICAgY29uc3Qge1xuICAgICAgZWxlbWVudGFyeVN0cmVhbXNcbiAgICB9ID0gdGhpcztcbiAgICBlbGVtZW50YXJ5U3RyZWFtc1tFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU9dID0gbnVsbDtcbiAgICBlbGVtZW50YXJ5U3RyZWFtc1tFbGVtZW50YXJ5U3RyZWFtVHlwZXMuVklERU9dID0gbnVsbDtcbiAgICBlbGVtZW50YXJ5U3RyZWFtc1tFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU9WSURFT10gPSBudWxsO1xuICB9XG59XG5cbi8qKlxuICogT2JqZWN0IHJlcHJlc2VudGluZyBwYXJzZWQgZGF0YSBmcm9tIGFuIEhMUyBQYXJ0aWFsIFNlZ21lbnQuIEZvdW5kIGluIHtAbGluayBobHMuanMjTGV2ZWxEZXRhaWxzLnBhcnRMaXN0fS5cbiAqL1xuY2xhc3MgUGFydCBleHRlbmRzIEJhc2VTZWdtZW50IHtcbiAgY29uc3RydWN0b3IocGFydEF0dHJzLCBmcmFnLCBiYXNldXJsLCBpbmRleCwgcHJldmlvdXMpIHtcbiAgICBzdXBlcihiYXNldXJsKTtcbiAgICB0aGlzLmZyYWdPZmZzZXQgPSAwO1xuICAgIHRoaXMuZHVyYXRpb24gPSAwO1xuICAgIHRoaXMuZ2FwID0gZmFsc2U7XG4gICAgdGhpcy5pbmRlcGVuZGVudCA9IGZhbHNlO1xuICAgIHRoaXMucmVsdXJsID0gdm9pZCAwO1xuICAgIHRoaXMuZnJhZ21lbnQgPSB2b2lkIDA7XG4gICAgdGhpcy5pbmRleCA9IHZvaWQgMDtcbiAgICB0aGlzLnN0YXRzID0gbmV3IExvYWRTdGF0cygpO1xuICAgIHRoaXMuZHVyYXRpb24gPSBwYXJ0QXR0cnMuZGVjaW1hbEZsb2F0aW5nUG9pbnQoJ0RVUkFUSU9OJyk7XG4gICAgdGhpcy5nYXAgPSBwYXJ0QXR0cnMuYm9vbCgnR0FQJyk7XG4gICAgdGhpcy5pbmRlcGVuZGVudCA9IHBhcnRBdHRycy5ib29sKCdJTkRFUEVOREVOVCcpO1xuICAgIHRoaXMucmVsdXJsID0gcGFydEF0dHJzLmVudW1lcmF0ZWRTdHJpbmcoJ1VSSScpO1xuICAgIHRoaXMuZnJhZ21lbnQgPSBmcmFnO1xuICAgIHRoaXMuaW5kZXggPSBpbmRleDtcbiAgICBjb25zdCBieXRlUmFuZ2UgPSBwYXJ0QXR0cnMuZW51bWVyYXRlZFN0cmluZygnQllURVJBTkdFJyk7XG4gICAgaWYgKGJ5dGVSYW5nZSkge1xuICAgICAgdGhpcy5zZXRCeXRlUmFuZ2UoYnl0ZVJhbmdlLCBwcmV2aW91cyk7XG4gICAgfVxuICAgIGlmIChwcmV2aW91cykge1xuICAgICAgdGhpcy5mcmFnT2Zmc2V0ID0gcHJldmlvdXMuZnJhZ09mZnNldCArIHByZXZpb3VzLmR1cmF0aW9uO1xuICAgIH1cbiAgfVxuICBnZXQgc3RhcnQoKSB7XG4gICAgcmV0dXJuIHRoaXMuZnJhZ21lbnQuc3RhcnQgKyB0aGlzLmZyYWdPZmZzZXQ7XG4gIH1cbiAgZ2V0IGVuZCgpIHtcbiAgICByZXR1cm4gdGhpcy5zdGFydCArIHRoaXMuZHVyYXRpb247XG4gIH1cbiAgZ2V0IGxvYWRlZCgpIHtcbiAgICBjb25zdCB7XG4gICAgICBlbGVtZW50YXJ5U3RyZWFtc1xuICAgIH0gPSB0aGlzO1xuICAgIHJldHVybiAhIShlbGVtZW50YXJ5U3RyZWFtcy5hdWRpbyB8fCBlbGVtZW50YXJ5U3RyZWFtcy52aWRlbyB8fCBlbGVtZW50YXJ5U3RyZWFtcy5hdWRpb3ZpZGVvKTtcbiAgfVxufVxuXG5jb25zdCBERUZBVUxUX1RBUkdFVF9EVVJBVElPTiA9IDEwO1xuXG4vKipcbiAqIE9iamVjdCByZXByZXNlbnRpbmcgcGFyc2VkIGRhdGEgZnJvbSBhbiBITFMgTWVkaWEgUGxheWxpc3QuIEZvdW5kIGluIHtAbGluayBobHMuanMjTGV2ZWwuZGV0YWlsc30uXG4gKi9cbmNsYXNzIExldmVsRGV0YWlscyB7XG4gIGNvbnN0cnVjdG9yKGJhc2VVcmwpIHtcbiAgICB0aGlzLlBUU0tub3duID0gZmFsc2U7XG4gICAgdGhpcy5hbGlnbmVkU2xpZGluZyA9IGZhbHNlO1xuICAgIHRoaXMuYXZlcmFnZXRhcmdldGR1cmF0aW9uID0gdm9pZCAwO1xuICAgIHRoaXMuZW5kQ0MgPSAwO1xuICAgIHRoaXMuZW5kU04gPSAwO1xuICAgIHRoaXMuZnJhZ21lbnRzID0gdm9pZCAwO1xuICAgIHRoaXMuZnJhZ21lbnRIaW50ID0gdm9pZCAwO1xuICAgIHRoaXMucGFydExpc3QgPSBudWxsO1xuICAgIHRoaXMuZGF0ZVJhbmdlcyA9IHZvaWQgMDtcbiAgICB0aGlzLmxpdmUgPSB0cnVlO1xuICAgIHRoaXMuYWdlSGVhZGVyID0gMDtcbiAgICB0aGlzLmFkdmFuY2VkRGF0ZVRpbWUgPSB2b2lkIDA7XG4gICAgdGhpcy51cGRhdGVkID0gdHJ1ZTtcbiAgICB0aGlzLmFkdmFuY2VkID0gdHJ1ZTtcbiAgICB0aGlzLmF2YWlsYWJpbGl0eURlbGF5ID0gdm9pZCAwO1xuICAgIC8vIE1hbmlmZXN0IHJlbG9hZCBzeW5jaHJvbml6YXRpb25cbiAgICB0aGlzLm1pc3NlcyA9IDA7XG4gICAgdGhpcy5zdGFydENDID0gMDtcbiAgICB0aGlzLnN0YXJ0U04gPSAwO1xuICAgIHRoaXMuc3RhcnRUaW1lT2Zmc2V0ID0gbnVsbDtcbiAgICB0aGlzLnRhcmdldGR1cmF0aW9uID0gMDtcbiAgICB0aGlzLnRvdGFsZHVyYXRpb24gPSAwO1xuICAgIHRoaXMudHlwZSA9IG51bGw7XG4gICAgdGhpcy51cmwgPSB2b2lkIDA7XG4gICAgdGhpcy5tM3U4ID0gJyc7XG4gICAgdGhpcy52ZXJzaW9uID0gbnVsbDtcbiAgICB0aGlzLmNhbkJsb2NrUmVsb2FkID0gZmFsc2U7XG4gICAgdGhpcy5jYW5Ta2lwVW50aWwgPSAwO1xuICAgIHRoaXMuY2FuU2tpcERhdGVSYW5nZXMgPSBmYWxzZTtcbiAgICB0aGlzLnNraXBwZWRTZWdtZW50cyA9IDA7XG4gICAgdGhpcy5yZWNlbnRseVJlbW92ZWREYXRlcmFuZ2VzID0gdm9pZCAwO1xuICAgIHRoaXMucGFydEhvbGRCYWNrID0gMDtcbiAgICB0aGlzLmhvbGRCYWNrID0gMDtcbiAgICB0aGlzLnBhcnRUYXJnZXQgPSAwO1xuICAgIHRoaXMucHJlbG9hZEhpbnQgPSB2b2lkIDA7XG4gICAgdGhpcy5yZW5kaXRpb25SZXBvcnRzID0gdm9pZCAwO1xuICAgIHRoaXMudHVuZUluR29hbCA9IDA7XG4gICAgdGhpcy5kZWx0YVVwZGF0ZUZhaWxlZCA9IHZvaWQgMDtcbiAgICB0aGlzLmRyaWZ0U3RhcnRUaW1lID0gMDtcbiAgICB0aGlzLmRyaWZ0RW5kVGltZSA9IDA7XG4gICAgdGhpcy5kcmlmdFN0YXJ0ID0gMDtcbiAgICB0aGlzLmRyaWZ0RW5kID0gMDtcbiAgICB0aGlzLmVuY3J5cHRlZEZyYWdtZW50cyA9IHZvaWQgMDtcbiAgICB0aGlzLnBsYXlsaXN0UGFyc2luZ0Vycm9yID0gbnVsbDtcbiAgICB0aGlzLnZhcmlhYmxlTGlzdCA9IG51bGw7XG4gICAgdGhpcy5oYXNWYXJpYWJsZVJlZnMgPSBmYWxzZTtcbiAgICB0aGlzLmZyYWdtZW50cyA9IFtdO1xuICAgIHRoaXMuZW5jcnlwdGVkRnJhZ21lbnRzID0gW107XG4gICAgdGhpcy5kYXRlUmFuZ2VzID0ge307XG4gICAgdGhpcy51cmwgPSBiYXNlVXJsO1xuICB9XG4gIHJlbG9hZGVkKHByZXZpb3VzKSB7XG4gICAgaWYgKCFwcmV2aW91cykge1xuICAgICAgdGhpcy5hZHZhbmNlZCA9IHRydWU7XG4gICAgICB0aGlzLnVwZGF0ZWQgPSB0cnVlO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBwYXJ0U25EaWZmID0gdGhpcy5sYXN0UGFydFNuIC0gcHJldmlvdXMubGFzdFBhcnRTbjtcbiAgICBjb25zdCBwYXJ0SW5kZXhEaWZmID0gdGhpcy5sYXN0UGFydEluZGV4IC0gcHJldmlvdXMubGFzdFBhcnRJbmRleDtcbiAgICB0aGlzLnVwZGF0ZWQgPSB0aGlzLmVuZFNOICE9PSBwcmV2aW91cy5lbmRTTiB8fCAhIXBhcnRJbmRleERpZmYgfHwgISFwYXJ0U25EaWZmIHx8ICF0aGlzLmxpdmU7XG4gICAgdGhpcy5hZHZhbmNlZCA9IHRoaXMuZW5kU04gPiBwcmV2aW91cy5lbmRTTiB8fCBwYXJ0U25EaWZmID4gMCB8fCBwYXJ0U25EaWZmID09PSAwICYmIHBhcnRJbmRleERpZmYgPiAwO1xuICAgIGlmICh0aGlzLnVwZGF0ZWQgfHwgdGhpcy5hZHZhbmNlZCkge1xuICAgICAgdGhpcy5taXNzZXMgPSBNYXRoLmZsb29yKHByZXZpb3VzLm1pc3NlcyAqIDAuNik7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMubWlzc2VzID0gcHJldmlvdXMubWlzc2VzICsgMTtcbiAgICB9XG4gICAgdGhpcy5hdmFpbGFiaWxpdHlEZWxheSA9IHByZXZpb3VzLmF2YWlsYWJpbGl0eURlbGF5O1xuICB9XG4gIGdldCBoYXNQcm9ncmFtRGF0ZVRpbWUoKSB7XG4gICAgaWYgKHRoaXMuZnJhZ21lbnRzLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIGlzRmluaXRlTnVtYmVyKHRoaXMuZnJhZ21lbnRzW3RoaXMuZnJhZ21lbnRzLmxlbmd0aCAtIDFdLnByb2dyYW1EYXRlVGltZSk7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBnZXQgbGV2ZWxUYXJnZXREdXJhdGlvbigpIHtcbiAgICByZXR1cm4gdGhpcy5hdmVyYWdldGFyZ2V0ZHVyYXRpb24gfHwgdGhpcy50YXJnZXRkdXJhdGlvbiB8fCBERUZBVUxUX1RBUkdFVF9EVVJBVElPTjtcbiAgfVxuICBnZXQgZHJpZnQoKSB7XG4gICAgY29uc3QgcnVuVGltZSA9IHRoaXMuZHJpZnRFbmRUaW1lIC0gdGhpcy5kcmlmdFN0YXJ0VGltZTtcbiAgICBpZiAocnVuVGltZSA+IDApIHtcbiAgICAgIGNvbnN0IHJ1bkR1cmF0aW9uID0gdGhpcy5kcmlmdEVuZCAtIHRoaXMuZHJpZnRTdGFydDtcbiAgICAgIHJldHVybiBydW5EdXJhdGlvbiAqIDEwMDAgLyBydW5UaW1lO1xuICAgIH1cbiAgICByZXR1cm4gMTtcbiAgfVxuICBnZXQgZWRnZSgpIHtcbiAgICByZXR1cm4gdGhpcy5wYXJ0RW5kIHx8IHRoaXMuZnJhZ21lbnRFbmQ7XG4gIH1cbiAgZ2V0IHBhcnRFbmQoKSB7XG4gICAgdmFyIF90aGlzJHBhcnRMaXN0O1xuICAgIGlmICgoX3RoaXMkcGFydExpc3QgPSB0aGlzLnBhcnRMaXN0KSAhPSBudWxsICYmIF90aGlzJHBhcnRMaXN0Lmxlbmd0aCkge1xuICAgICAgcmV0dXJuIHRoaXMucGFydExpc3RbdGhpcy5wYXJ0TGlzdC5sZW5ndGggLSAxXS5lbmQ7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmZyYWdtZW50RW5kO1xuICB9XG4gIGdldCBmcmFnbWVudEVuZCgpIHtcbiAgICB2YXIgX3RoaXMkZnJhZ21lbnRzO1xuICAgIGlmICgoX3RoaXMkZnJhZ21lbnRzID0gdGhpcy5mcmFnbWVudHMpICE9IG51bGwgJiYgX3RoaXMkZnJhZ21lbnRzLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIHRoaXMuZnJhZ21lbnRzW3RoaXMuZnJhZ21lbnRzLmxlbmd0aCAtIDFdLmVuZDtcbiAgICB9XG4gICAgcmV0dXJuIDA7XG4gIH1cbiAgZ2V0IGFnZSgpIHtcbiAgICBpZiAodGhpcy5hZHZhbmNlZERhdGVUaW1lKSB7XG4gICAgICByZXR1cm4gTWF0aC5tYXgoRGF0ZS5ub3coKSAtIHRoaXMuYWR2YW5jZWREYXRlVGltZSwgMCkgLyAxMDAwO1xuICAgIH1cbiAgICByZXR1cm4gMDtcbiAgfVxuICBnZXQgbGFzdFBhcnRJbmRleCgpIHtcbiAgICB2YXIgX3RoaXMkcGFydExpc3QyO1xuICAgIGlmICgoX3RoaXMkcGFydExpc3QyID0gdGhpcy5wYXJ0TGlzdCkgIT0gbnVsbCAmJiBfdGhpcyRwYXJ0TGlzdDIubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gdGhpcy5wYXJ0TGlzdFt0aGlzLnBhcnRMaXN0Lmxlbmd0aCAtIDFdLmluZGV4O1xuICAgIH1cbiAgICByZXR1cm4gLTE7XG4gIH1cbiAgZ2V0IGxhc3RQYXJ0U24oKSB7XG4gICAgdmFyIF90aGlzJHBhcnRMaXN0MztcbiAgICBpZiAoKF90aGlzJHBhcnRMaXN0MyA9IHRoaXMucGFydExpc3QpICE9IG51bGwgJiYgX3RoaXMkcGFydExpc3QzLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIHRoaXMucGFydExpc3RbdGhpcy5wYXJ0TGlzdC5sZW5ndGggLSAxXS5mcmFnbWVudC5zbjtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuZW5kU047XG4gIH1cbn1cblxuZnVuY3Rpb24gYmFzZTY0RGVjb2RlKGJhc2U2NGVuY29kZWRTdHIpIHtcbiAgcmV0dXJuIFVpbnQ4QXJyYXkuZnJvbShhdG9iKGJhc2U2NGVuY29kZWRTdHIpLCBjID0+IGMuY2hhckNvZGVBdCgwKSk7XG59XG5cbmZ1bmN0aW9uIGdldEtleUlkQnl0ZXMoc3RyKSB7XG4gIGNvbnN0IGtleUlkYnl0ZXMgPSBzdHJUb1V0ZjhhcnJheShzdHIpLnN1YmFycmF5KDAsIDE2KTtcbiAgY29uc3QgcGFkZGVka2V5SWRieXRlcyA9IG5ldyBVaW50OEFycmF5KDE2KTtcbiAgcGFkZGVka2V5SWRieXRlcy5zZXQoa2V5SWRieXRlcywgMTYgLSBrZXlJZGJ5dGVzLmxlbmd0aCk7XG4gIHJldHVybiBwYWRkZWRrZXlJZGJ5dGVzO1xufVxuZnVuY3Rpb24gY2hhbmdlRW5kaWFubmVzcyhrZXlJZCkge1xuICBjb25zdCBzd2FwID0gZnVuY3Rpb24gc3dhcChhcnJheSwgZnJvbSwgdG8pIHtcbiAgICBjb25zdCBjdXIgPSBhcnJheVtmcm9tXTtcbiAgICBhcnJheVtmcm9tXSA9IGFycmF5W3RvXTtcbiAgICBhcnJheVt0b10gPSBjdXI7XG4gIH07XG4gIHN3YXAoa2V5SWQsIDAsIDMpO1xuICBzd2FwKGtleUlkLCAxLCAyKTtcbiAgc3dhcChrZXlJZCwgNCwgNSk7XG4gIHN3YXAoa2V5SWQsIDYsIDcpO1xufVxuZnVuY3Rpb24gY29udmVydERhdGFVcmlUb0FycmF5Qnl0ZXModXJpKSB7XG4gIC8vIGRhdGE6WzxtZWRpYSB0eXBlXVs7YXR0cmlidXRlPXZhbHVlXVs7YmFzZTY0XSw8ZGF0YT5cbiAgY29uc3QgY29sb25zcGxpdCA9IHVyaS5zcGxpdCgnOicpO1xuICBsZXQga2V5ZGF0YSA9IG51bGw7XG4gIGlmIChjb2xvbnNwbGl0WzBdID09PSAnZGF0YScgJiYgY29sb25zcGxpdC5sZW5ndGggPT09IDIpIHtcbiAgICBjb25zdCBzZW1pY29sb25zcGxpdCA9IGNvbG9uc3BsaXRbMV0uc3BsaXQoJzsnKTtcbiAgICBjb25zdCBjb21tYXNwbGl0ID0gc2VtaWNvbG9uc3BsaXRbc2VtaWNvbG9uc3BsaXQubGVuZ3RoIC0gMV0uc3BsaXQoJywnKTtcbiAgICBpZiAoY29tbWFzcGxpdC5sZW5ndGggPT09IDIpIHtcbiAgICAgIGNvbnN0IGlzYmFzZTY0ID0gY29tbWFzcGxpdFswXSA9PT0gJ2Jhc2U2NCc7XG4gICAgICBjb25zdCBkYXRhID0gY29tbWFzcGxpdFsxXTtcbiAgICAgIGlmIChpc2Jhc2U2NCkge1xuICAgICAgICBzZW1pY29sb25zcGxpdC5zcGxpY2UoLTEsIDEpOyAvLyByZW1vdmUgZnJvbSBwcm9jZXNzaW5nXG4gICAgICAgIGtleWRhdGEgPSBiYXNlNjREZWNvZGUoZGF0YSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBrZXlkYXRhID0gZ2V0S2V5SWRCeXRlcyhkYXRhKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIGtleWRhdGE7XG59XG5mdW5jdGlvbiBzdHJUb1V0ZjhhcnJheShzdHIpIHtcbiAgcmV0dXJuIFVpbnQ4QXJyYXkuZnJvbSh1bmVzY2FwZShlbmNvZGVVUklDb21wb25lbnQoc3RyKSksIGMgPT4gYy5jaGFyQ29kZUF0KDApKTtcbn1cblxuLyoqIHJldHVybnMgYHVuZGVmaW5lZGAgaXMgYHNlbGZgIGlzIG1pc3NpbmcsIGUuZy4gaW4gbm9kZSAqL1xuY29uc3Qgb3B0aW9uYWxTZWxmID0gdHlwZW9mIHNlbGYgIT09ICd1bmRlZmluZWQnID8gc2VsZiA6IHVuZGVmaW5lZDtcblxuLyoqXG4gKiBAc2VlIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9OYXZpZ2F0b3IvcmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzXG4gKi9cbnZhciBLZXlTeXN0ZW1zID0ge1xuICBDTEVBUktFWTogXCJvcmcudzMuY2xlYXJrZXlcIixcbiAgRkFJUlBMQVk6IFwiY29tLmFwcGxlLmZwc1wiLFxuICBQTEFZUkVBRFk6IFwiY29tLm1pY3Jvc29mdC5wbGF5cmVhZHlcIixcbiAgV0lERVZJTkU6IFwiY29tLndpZGV2aW5lLmFscGhhXCJcbn07XG5cbi8vIFBsYXlsaXN0ICNFWFQtWC1LRVkgS0VZRk9STUFUIHZhbHVlc1xudmFyIEtleVN5c3RlbUZvcm1hdHMgPSB7XG4gIENMRUFSS0VZOiBcIm9yZy53My5jbGVhcmtleVwiLFxuICBGQUlSUExBWTogXCJjb20uYXBwbGUuc3RyZWFtaW5na2V5ZGVsaXZlcnlcIixcbiAgUExBWVJFQURZOiBcImNvbS5taWNyb3NvZnQucGxheXJlYWR5XCIsXG4gIFdJREVWSU5FOiBcInVybjp1dWlkOmVkZWY4YmE5LTc5ZDYtNGFjZS1hM2M4LTI3ZGNkNTFkMjFlZFwiXG59O1xuZnVuY3Rpb24ga2V5U3lzdGVtRm9ybWF0VG9LZXlTeXN0ZW1Eb21haW4oZm9ybWF0KSB7XG4gIHN3aXRjaCAoZm9ybWF0KSB7XG4gICAgY2FzZSBLZXlTeXN0ZW1Gb3JtYXRzLkZBSVJQTEFZOlxuICAgICAgcmV0dXJuIEtleVN5c3RlbXMuRkFJUlBMQVk7XG4gICAgY2FzZSBLZXlTeXN0ZW1Gb3JtYXRzLlBMQVlSRUFEWTpcbiAgICAgIHJldHVybiBLZXlTeXN0ZW1zLlBMQVlSRUFEWTtcbiAgICBjYXNlIEtleVN5c3RlbUZvcm1hdHMuV0lERVZJTkU6XG4gICAgICByZXR1cm4gS2V5U3lzdGVtcy5XSURFVklORTtcbiAgICBjYXNlIEtleVN5c3RlbUZvcm1hdHMuQ0xFQVJLRVk6XG4gICAgICByZXR1cm4gS2V5U3lzdGVtcy5DTEVBUktFWTtcbiAgfVxufVxuXG4vLyBTeXN0ZW0gSURzIGZvciB3aGljaCB3ZSBjYW4gZXh0cmFjdCBhIGtleSBJRCBmcm9tIFwiZW5jcnlwdGVkXCIgZXZlbnQgUFNTSFxudmFyIEtleVN5c3RlbUlkcyA9IHtcbiAgQ0VOQzogXCIxMDc3ZWZlY2MwYjI0ZDAyYWNlMzNjMWU1MmUyZmI0YlwiLFxuICBDTEVBUktFWTogXCJlMjcxOWQ1OGE5ODViM2M5NzgxYWIwMzBhZjc4ZDMwZVwiLFxuICBGQUlSUExBWTogXCI5NGNlODZmYjA3ZmY0ZjQzYWRiODkzZDJmYTk2OGNhMlwiLFxuICBQTEFZUkVBRFk6IFwiOWEwNGYwNzk5ODQwNDI4NmFiOTJlNjViZTA4ODVmOTVcIixcbiAgV0lERVZJTkU6IFwiZWRlZjhiYTk3OWQ2NGFjZWEzYzgyN2RjZDUxZDIxZWRcIlxufTtcbmZ1bmN0aW9uIGtleVN5c3RlbUlkVG9LZXlTeXN0ZW1Eb21haW4oc3lzdGVtSWQpIHtcbiAgaWYgKHN5c3RlbUlkID09PSBLZXlTeXN0ZW1JZHMuV0lERVZJTkUpIHtcbiAgICByZXR1cm4gS2V5U3lzdGVtcy5XSURFVklORTtcbiAgfSBlbHNlIGlmIChzeXN0ZW1JZCA9PT0gS2V5U3lzdGVtSWRzLlBMQVlSRUFEWSkge1xuICAgIHJldHVybiBLZXlTeXN0ZW1zLlBMQVlSRUFEWTtcbiAgfSBlbHNlIGlmIChzeXN0ZW1JZCA9PT0gS2V5U3lzdGVtSWRzLkNFTkMgfHwgc3lzdGVtSWQgPT09IEtleVN5c3RlbUlkcy5DTEVBUktFWSkge1xuICAgIHJldHVybiBLZXlTeXN0ZW1zLkNMRUFSS0VZO1xuICB9XG59XG5mdW5jdGlvbiBrZXlTeXN0ZW1Eb21haW5Ub0tleVN5c3RlbUZvcm1hdChrZXlTeXN0ZW0pIHtcbiAgc3dpdGNoIChrZXlTeXN0ZW0pIHtcbiAgICBjYXNlIEtleVN5c3RlbXMuRkFJUlBMQVk6XG4gICAgICByZXR1cm4gS2V5U3lzdGVtRm9ybWF0cy5GQUlSUExBWTtcbiAgICBjYXNlIEtleVN5c3RlbXMuUExBWVJFQURZOlxuICAgICAgcmV0dXJuIEtleVN5c3RlbUZvcm1hdHMuUExBWVJFQURZO1xuICAgIGNhc2UgS2V5U3lzdGVtcy5XSURFVklORTpcbiAgICAgIHJldHVybiBLZXlTeXN0ZW1Gb3JtYXRzLldJREVWSU5FO1xuICAgIGNhc2UgS2V5U3lzdGVtcy5DTEVBUktFWTpcbiAgICAgIHJldHVybiBLZXlTeXN0ZW1Gb3JtYXRzLkNMRUFSS0VZO1xuICB9XG59XG5mdW5jdGlvbiBnZXRLZXlTeXN0ZW1zRm9yQ29uZmlnKGNvbmZpZykge1xuICBjb25zdCB7XG4gICAgZHJtU3lzdGVtcyxcbiAgICB3aWRldmluZUxpY2Vuc2VVcmxcbiAgfSA9IGNvbmZpZztcbiAgY29uc3Qga2V5U3lzdGVtc1RvQXR0ZW1wdCA9IGRybVN5c3RlbXMgPyBbS2V5U3lzdGVtcy5GQUlSUExBWSwgS2V5U3lzdGVtcy5XSURFVklORSwgS2V5U3lzdGVtcy5QTEFZUkVBRFksIEtleVN5c3RlbXMuQ0xFQVJLRVldLmZpbHRlcihrZXlTeXN0ZW0gPT4gISFkcm1TeXN0ZW1zW2tleVN5c3RlbV0pIDogW107XG4gIGlmICgha2V5U3lzdGVtc1RvQXR0ZW1wdFtLZXlTeXN0ZW1zLldJREVWSU5FXSAmJiB3aWRldmluZUxpY2Vuc2VVcmwpIHtcbiAgICBrZXlTeXN0ZW1zVG9BdHRlbXB0LnB1c2goS2V5U3lzdGVtcy5XSURFVklORSk7XG4gIH1cbiAgcmV0dXJuIGtleVN5c3RlbXNUb0F0dGVtcHQ7XG59XG5jb25zdCByZXF1ZXN0TWVkaWFLZXlTeXN0ZW1BY2Nlc3MgPSBmdW5jdGlvbiAoX29wdGlvbmFsU2VsZiRuYXZpZ2F0KSB7XG4gIGlmIChvcHRpb25hbFNlbGYgIT0gbnVsbCAmJiAoX29wdGlvbmFsU2VsZiRuYXZpZ2F0ID0gb3B0aW9uYWxTZWxmLm5hdmlnYXRvcikgIT0gbnVsbCAmJiBfb3B0aW9uYWxTZWxmJG5hdmlnYXQucmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzKSB7XG4gICAgcmV0dXJuIHNlbGYubmF2aWdhdG9yLnJlcXVlc3RNZWRpYUtleVN5c3RlbUFjY2Vzcy5iaW5kKHNlbGYubmF2aWdhdG9yKTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufSgpO1xuXG4vKipcbiAqIEBzZWUgaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvQVBJL01lZGlhS2V5U3lzdGVtQ29uZmlndXJhdGlvblxuICovXG5mdW5jdGlvbiBnZXRTdXBwb3J0ZWRNZWRpYUtleVN5c3RlbUNvbmZpZ3VyYXRpb25zKGtleVN5c3RlbSwgYXVkaW9Db2RlY3MsIHZpZGVvQ29kZWNzLCBkcm1TeXN0ZW1PcHRpb25zKSB7XG4gIGxldCBpbml0RGF0YVR5cGVzO1xuICBzd2l0Y2ggKGtleVN5c3RlbSkge1xuICAgIGNhc2UgS2V5U3lzdGVtcy5GQUlSUExBWTpcbiAgICAgIGluaXREYXRhVHlwZXMgPSBbJ2NlbmMnLCAnc2luZiddO1xuICAgICAgYnJlYWs7XG4gICAgY2FzZSBLZXlTeXN0ZW1zLldJREVWSU5FOlxuICAgIGNhc2UgS2V5U3lzdGVtcy5QTEFZUkVBRFk6XG4gICAgICBpbml0RGF0YVR5cGVzID0gWydjZW5jJ107XG4gICAgICBicmVhaztcbiAgICBjYXNlIEtleVN5c3RlbXMuQ0xFQVJLRVk6XG4gICAgICBpbml0RGF0YVR5cGVzID0gWydjZW5jJywgJ2tleWlkcyddO1xuICAgICAgYnJlYWs7XG4gICAgZGVmYXVsdDpcbiAgICAgIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBrZXktc3lzdGVtOiAke2tleVN5c3RlbX1gKTtcbiAgfVxuICByZXR1cm4gY3JlYXRlTWVkaWFLZXlTeXN0ZW1Db25maWd1cmF0aW9ucyhpbml0RGF0YVR5cGVzLCBhdWRpb0NvZGVjcywgdmlkZW9Db2RlY3MsIGRybVN5c3RlbU9wdGlvbnMpO1xufVxuZnVuY3Rpb24gY3JlYXRlTWVkaWFLZXlTeXN0ZW1Db25maWd1cmF0aW9ucyhpbml0RGF0YVR5cGVzLCBhdWRpb0NvZGVjcywgdmlkZW9Db2RlY3MsIGRybVN5c3RlbU9wdGlvbnMpIHtcbiAgY29uc3QgYmFzZUNvbmZpZyA9IHtcbiAgICBpbml0RGF0YVR5cGVzOiBpbml0RGF0YVR5cGVzLFxuICAgIHBlcnNpc3RlbnRTdGF0ZTogZHJtU3lzdGVtT3B0aW9ucy5wZXJzaXN0ZW50U3RhdGUgfHwgJ29wdGlvbmFsJyxcbiAgICBkaXN0aW5jdGl2ZUlkZW50aWZpZXI6IGRybVN5c3RlbU9wdGlvbnMuZGlzdGluY3RpdmVJZGVudGlmaWVyIHx8ICdvcHRpb25hbCcsXG4gICAgc2Vzc2lvblR5cGVzOiBkcm1TeXN0ZW1PcHRpb25zLnNlc3Npb25UeXBlcyB8fCBbZHJtU3lzdGVtT3B0aW9ucy5zZXNzaW9uVHlwZSB8fCAndGVtcG9yYXJ5J10sXG4gICAgYXVkaW9DYXBhYmlsaXRpZXM6IGF1ZGlvQ29kZWNzLm1hcChjb2RlYyA9PiAoe1xuICAgICAgY29udGVudFR5cGU6IGBhdWRpby9tcDQ7IGNvZGVjcz1cIiR7Y29kZWN9XCJgLFxuICAgICAgcm9idXN0bmVzczogZHJtU3lzdGVtT3B0aW9ucy5hdWRpb1JvYnVzdG5lc3MgfHwgJycsXG4gICAgICBlbmNyeXB0aW9uU2NoZW1lOiBkcm1TeXN0ZW1PcHRpb25zLmF1ZGlvRW5jcnlwdGlvblNjaGVtZSB8fCBudWxsXG4gICAgfSkpLFxuICAgIHZpZGVvQ2FwYWJpbGl0aWVzOiB2aWRlb0NvZGVjcy5tYXAoY29kZWMgPT4gKHtcbiAgICAgIGNvbnRlbnRUeXBlOiBgdmlkZW8vbXA0OyBjb2RlY3M9XCIke2NvZGVjfVwiYCxcbiAgICAgIHJvYnVzdG5lc3M6IGRybVN5c3RlbU9wdGlvbnMudmlkZW9Sb2J1c3RuZXNzIHx8ICcnLFxuICAgICAgZW5jcnlwdGlvblNjaGVtZTogZHJtU3lzdGVtT3B0aW9ucy52aWRlb0VuY3J5cHRpb25TY2hlbWUgfHwgbnVsbFxuICAgIH0pKVxuICB9O1xuICByZXR1cm4gW2Jhc2VDb25maWddO1xufVxuXG5mdW5jdGlvbiBzbGljZVVpbnQ4KGFycmF5LCBzdGFydCwgZW5kKSB7XG4gIC8vIEB0cy1leHBlY3QtZXJyb3IgVGhpcyBwb2x5ZmlsbHMgSUUxMSB1c2FnZSBvZiBVaW50OEFycmF5IHNsaWNlLlxuICAvLyBJdCBhbHdheXMgZXhpc3RzIGluIHRoZSBUeXBlU2NyaXB0IGRlZmluaXRpb24gc28gZmFpbHMsIGJ1dCBpdCBmYWlscyBhdCBydW50aW1lIG9uIElFMTEuXG4gIHJldHVybiBVaW50OEFycmF5LnByb3RvdHlwZS5zbGljZSA/IGFycmF5LnNsaWNlKHN0YXJ0LCBlbmQpIDogbmV3IFVpbnQ4QXJyYXkoQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoYXJyYXksIHN0YXJ0LCBlbmQpKTtcbn1cblxuLy8gYnJlYWtpbmcgdXAgdGhvc2UgdHdvIHR5cGVzIGluIG9yZGVyIHRvIGNsYXJpZnkgd2hhdCBpcyBoYXBwZW5pbmcgaW4gdGhlIGRlY29kaW5nIHBhdGguXG5cbi8qKlxuICogUmV0dXJucyB0cnVlIGlmIGFuIElEMyBoZWFkZXIgY2FuIGJlIGZvdW5kIGF0IG9mZnNldCBpbiBkYXRhXG4gKiBAcGFyYW0gZGF0YSAtIFRoZSBkYXRhIHRvIHNlYXJjaFxuICogQHBhcmFtIG9mZnNldCAtIFRoZSBvZmZzZXQgYXQgd2hpY2ggdG8gc3RhcnQgc2VhcmNoaW5nXG4gKi9cbmNvbnN0IGlzSGVhZGVyJDIgPSAoZGF0YSwgb2Zmc2V0KSA9PiB7XG4gIC8qXG4gICAqIGh0dHA6Ly9pZDMub3JnL2lkM3YyLjMuMFxuICAgKiBbMF0gICAgID0gJ0knXG4gICAqIFsxXSAgICAgPSAnRCdcbiAgICogWzJdICAgICA9ICczJ1xuICAgKiBbMyw0XSAgID0ge1ZlcnNpb259XG4gICAqIFs1XSAgICAgPSB7RmxhZ3N9XG4gICAqIFs2LTldICAgPSB7SUQzIFNpemV9XG4gICAqXG4gICAqIEFuIElEM3YyIHRhZyBjYW4gYmUgZGV0ZWN0ZWQgd2l0aCB0aGUgZm9sbG93aW5nIHBhdHRlcm46XG4gICAqICAkNDkgNDQgMzMgeXkgeXkgeHggenogenogenogenpcbiAgICogV2hlcmUgeXkgaXMgbGVzcyB0aGFuICRGRiwgeHggaXMgdGhlICdmbGFncycgYnl0ZSBhbmQgenogaXMgbGVzcyB0aGFuICQ4MFxuICAgKi9cbiAgaWYgKG9mZnNldCArIDEwIDw9IGRhdGEubGVuZ3RoKSB7XG4gICAgLy8gbG9vayBmb3IgJ0lEMycgaWRlbnRpZmllclxuICAgIGlmIChkYXRhW29mZnNldF0gPT09IDB4NDkgJiYgZGF0YVtvZmZzZXQgKyAxXSA9PT0gMHg0NCAmJiBkYXRhW29mZnNldCArIDJdID09PSAweDMzKSB7XG4gICAgICAvLyBjaGVjayB2ZXJzaW9uIGlzIHdpdGhpbiByYW5nZVxuICAgICAgaWYgKGRhdGFbb2Zmc2V0ICsgM10gPCAweGZmICYmIGRhdGFbb2Zmc2V0ICsgNF0gPCAweGZmKSB7XG4gICAgICAgIC8vIGNoZWNrIHNpemUgaXMgd2l0aGluIHJhbmdlXG4gICAgICAgIGlmIChkYXRhW29mZnNldCArIDZdIDwgMHg4MCAmJiBkYXRhW29mZnNldCArIDddIDwgMHg4MCAmJiBkYXRhW29mZnNldCArIDhdIDwgMHg4MCAmJiBkYXRhW29mZnNldCArIDldIDwgMHg4MCkge1xuICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn07XG5cbi8qKlxuICogUmV0dXJucyB0cnVlIGlmIGFuIElEMyBmb290ZXIgY2FuIGJlIGZvdW5kIGF0IG9mZnNldCBpbiBkYXRhXG4gKiBAcGFyYW0gZGF0YSAtIFRoZSBkYXRhIHRvIHNlYXJjaFxuICogQHBhcmFtIG9mZnNldCAtIFRoZSBvZmZzZXQgYXQgd2hpY2ggdG8gc3RhcnQgc2VhcmNoaW5nXG4gKi9cbmNvbnN0IGlzRm9vdGVyID0gKGRhdGEsIG9mZnNldCkgPT4ge1xuICAvKlxuICAgKiBUaGUgZm9vdGVyIGlzIGEgY29weSBvZiB0aGUgaGVhZGVyLCBidXQgd2l0aCBhIGRpZmZlcmVudCBpZGVudGlmaWVyXG4gICAqL1xuICBpZiAob2Zmc2V0ICsgMTAgPD0gZGF0YS5sZW5ndGgpIHtcbiAgICAvLyBsb29rIGZvciAnM0RJJyBpZGVudGlmaWVyXG4gICAgaWYgKGRhdGFbb2Zmc2V0XSA9PT0gMHgzMyAmJiBkYXRhW29mZnNldCArIDFdID09PSAweDQ0ICYmIGRhdGFbb2Zmc2V0ICsgMl0gPT09IDB4NDkpIHtcbiAgICAgIC8vIGNoZWNrIHZlcnNpb24gaXMgd2l0aGluIHJhbmdlXG4gICAgICBpZiAoZGF0YVtvZmZzZXQgKyAzXSA8IDB4ZmYgJiYgZGF0YVtvZmZzZXQgKyA0XSA8IDB4ZmYpIHtcbiAgICAgICAgLy8gY2hlY2sgc2l6ZSBpcyB3aXRoaW4gcmFuZ2VcbiAgICAgICAgaWYgKGRhdGFbb2Zmc2V0ICsgNl0gPCAweDgwICYmIGRhdGFbb2Zmc2V0ICsgN10gPCAweDgwICYmIGRhdGFbb2Zmc2V0ICsgOF0gPCAweDgwICYmIGRhdGFbb2Zmc2V0ICsgOV0gPCAweDgwKSB7XG4gICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufTtcblxuLyoqXG4gKiBSZXR1cm5zIGFueSBhZGphY2VudCBJRDMgdGFncyBmb3VuZCBpbiBkYXRhIHN0YXJ0aW5nIGF0IG9mZnNldCwgYXMgb25lIGJsb2NrIG9mIGRhdGFcbiAqIEBwYXJhbSBkYXRhIC0gVGhlIGRhdGEgdG8gc2VhcmNoIGluXG4gKiBAcGFyYW0gb2Zmc2V0IC0gVGhlIG9mZnNldCBhdCB3aGljaCB0byBzdGFydCBzZWFyY2hpbmdcbiAqIEByZXR1cm5zIHRoZSBibG9jayBvZiBkYXRhIGNvbnRhaW5pbmcgYW55IElEMyB0YWdzIGZvdW5kXG4gKiBvciAqdW5kZWZpbmVkKiBpZiBubyBoZWFkZXIgaXMgZm91bmQgYXQgdGhlIHN0YXJ0aW5nIG9mZnNldFxuICovXG5jb25zdCBnZXRJRDNEYXRhID0gKGRhdGEsIG9mZnNldCkgPT4ge1xuICBjb25zdCBmcm9udCA9IG9mZnNldDtcbiAgbGV0IGxlbmd0aCA9IDA7XG4gIHdoaWxlIChpc0hlYWRlciQyKGRhdGEsIG9mZnNldCkpIHtcbiAgICAvLyBJRDMgaGVhZGVyIGlzIDEwIGJ5dGVzXG4gICAgbGVuZ3RoICs9IDEwO1xuICAgIGNvbnN0IHNpemUgPSByZWFkU2l6ZShkYXRhLCBvZmZzZXQgKyA2KTtcbiAgICBsZW5ndGggKz0gc2l6ZTtcbiAgICBpZiAoaXNGb290ZXIoZGF0YSwgb2Zmc2V0ICsgMTApKSB7XG4gICAgICAvLyBJRDMgZm9vdGVyIGlzIDEwIGJ5dGVzXG4gICAgICBsZW5ndGggKz0gMTA7XG4gICAgfVxuICAgIG9mZnNldCArPSBsZW5ndGg7XG4gIH1cbiAgaWYgKGxlbmd0aCA+IDApIHtcbiAgICByZXR1cm4gZGF0YS5zdWJhcnJheShmcm9udCwgZnJvbnQgKyBsZW5ndGgpO1xuICB9XG4gIHJldHVybiB1bmRlZmluZWQ7XG59O1xuY29uc3QgcmVhZFNpemUgPSAoZGF0YSwgb2Zmc2V0KSA9PiB7XG4gIGxldCBzaXplID0gMDtcbiAgc2l6ZSA9IChkYXRhW29mZnNldF0gJiAweDdmKSA8PCAyMTtcbiAgc2l6ZSB8PSAoZGF0YVtvZmZzZXQgKyAxXSAmIDB4N2YpIDw8IDE0O1xuICBzaXplIHw9IChkYXRhW29mZnNldCArIDJdICYgMHg3ZikgPDwgNztcbiAgc2l6ZSB8PSBkYXRhW29mZnNldCArIDNdICYgMHg3ZjtcbiAgcmV0dXJuIHNpemU7XG59O1xuY29uc3QgY2FuUGFyc2UkMiA9IChkYXRhLCBvZmZzZXQpID0+IHtcbiAgcmV0dXJuIGlzSGVhZGVyJDIoZGF0YSwgb2Zmc2V0KSAmJiByZWFkU2l6ZShkYXRhLCBvZmZzZXQgKyA2KSArIDEwIDw9IGRhdGEubGVuZ3RoIC0gb2Zmc2V0O1xufTtcblxuLyoqXG4gKiBTZWFyY2hlcyBmb3IgdGhlIEVsZW1lbnRhcnkgU3RyZWFtIHRpbWVzdGFtcCBmb3VuZCBpbiB0aGUgSUQzIGRhdGEgY2h1bmtcbiAqIEBwYXJhbSBkYXRhIC0gQmxvY2sgb2YgZGF0YSBjb250YWluaW5nIG9uZSBvciBtb3JlIElEMyB0YWdzXG4gKi9cbmNvbnN0IGdldFRpbWVTdGFtcCA9IGRhdGEgPT4ge1xuICBjb25zdCBmcmFtZXMgPSBnZXRJRDNGcmFtZXMoZGF0YSk7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgZnJhbWVzLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3QgZnJhbWUgPSBmcmFtZXNbaV07XG4gICAgaWYgKGlzVGltZVN0YW1wRnJhbWUoZnJhbWUpKSB7XG4gICAgICByZXR1cm4gcmVhZFRpbWVTdGFtcChmcmFtZSk7XG4gICAgfVxuICB9XG4gIHJldHVybiB1bmRlZmluZWQ7XG59O1xuXG4vKipcbiAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgSUQzIGZyYW1lIGlzIGFuIEVsZW1lbnRhcnkgU3RyZWFtIHRpbWVzdGFtcCBmcmFtZVxuICovXG5jb25zdCBpc1RpbWVTdGFtcEZyYW1lID0gZnJhbWUgPT4ge1xuICByZXR1cm4gZnJhbWUgJiYgZnJhbWUua2V5ID09PSAnUFJJVicgJiYgZnJhbWUuaW5mbyA9PT0gJ2NvbS5hcHBsZS5zdHJlYW1pbmcudHJhbnNwb3J0U3RyZWFtVGltZXN0YW1wJztcbn07XG5jb25zdCBnZXRGcmFtZURhdGEgPSBkYXRhID0+IHtcbiAgLypcbiAgRnJhbWUgSUQgICAgICAgJHh4IHh4IHh4IHh4IChmb3VyIGNoYXJhY3RlcnMpXG4gIFNpemUgICAgICAgICAgICR4eCB4eCB4eCB4eFxuICBGbGFncyAgICAgICAgICAkeHggeHhcbiAgKi9cbiAgY29uc3QgdHlwZSA9IFN0cmluZy5mcm9tQ2hhckNvZGUoZGF0YVswXSwgZGF0YVsxXSwgZGF0YVsyXSwgZGF0YVszXSk7XG4gIGNvbnN0IHNpemUgPSByZWFkU2l6ZShkYXRhLCA0KTtcblxuICAvLyBza2lwIGZyYW1lIGlkLCBzaXplLCBhbmQgZmxhZ3NcbiAgY29uc3Qgb2Zmc2V0ID0gMTA7XG4gIHJldHVybiB7XG4gICAgdHlwZSxcbiAgICBzaXplLFxuICAgIGRhdGE6IGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBvZmZzZXQgKyBzaXplKVxuICB9O1xufTtcblxuLyoqXG4gKiBSZXR1cm5zIGFuIGFycmF5IG9mIElEMyBmcmFtZXMgZm91bmQgaW4gYWxsIHRoZSBJRDMgdGFncyBpbiB0aGUgaWQzRGF0YVxuICogQHBhcmFtIGlkM0RhdGEgLSBUaGUgSUQzIGRhdGEgY29udGFpbmluZyBvbmUgb3IgbW9yZSBJRDMgdGFnc1xuICovXG5jb25zdCBnZXRJRDNGcmFtZXMgPSBpZDNEYXRhID0+IHtcbiAgbGV0IG9mZnNldCA9IDA7XG4gIGNvbnN0IGZyYW1lcyA9IFtdO1xuICB3aGlsZSAoaXNIZWFkZXIkMihpZDNEYXRhLCBvZmZzZXQpKSB7XG4gICAgY29uc3Qgc2l6ZSA9IHJlYWRTaXplKGlkM0RhdGEsIG9mZnNldCArIDYpO1xuICAgIC8vIHNraXAgcGFzdCBJRDMgaGVhZGVyXG4gICAgb2Zmc2V0ICs9IDEwO1xuICAgIGNvbnN0IGVuZCA9IG9mZnNldCArIHNpemU7XG4gICAgLy8gbG9vcCB0aHJvdWdoIGZyYW1lcyBpbiB0aGUgSUQzIHRhZ1xuICAgIHdoaWxlIChvZmZzZXQgKyA4IDwgZW5kKSB7XG4gICAgICBjb25zdCBmcmFtZURhdGEgPSBnZXRGcmFtZURhdGEoaWQzRGF0YS5zdWJhcnJheShvZmZzZXQpKTtcbiAgICAgIGNvbnN0IGZyYW1lID0gZGVjb2RlRnJhbWUoZnJhbWVEYXRhKTtcbiAgICAgIGlmIChmcmFtZSkge1xuICAgICAgICBmcmFtZXMucHVzaChmcmFtZSk7XG4gICAgICB9XG5cbiAgICAgIC8vIHNraXAgZnJhbWUgaGVhZGVyIGFuZCBmcmFtZSBkYXRhXG4gICAgICBvZmZzZXQgKz0gZnJhbWVEYXRhLnNpemUgKyAxMDtcbiAgICB9XG4gICAgaWYgKGlzRm9vdGVyKGlkM0RhdGEsIG9mZnNldCkpIHtcbiAgICAgIG9mZnNldCArPSAxMDtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZyYW1lcztcbn07XG5jb25zdCBkZWNvZGVGcmFtZSA9IGZyYW1lID0+IHtcbiAgaWYgKGZyYW1lLnR5cGUgPT09ICdQUklWJykge1xuICAgIHJldHVybiBkZWNvZGVQcml2RnJhbWUoZnJhbWUpO1xuICB9IGVsc2UgaWYgKGZyYW1lLnR5cGVbMF0gPT09ICdXJykge1xuICAgIHJldHVybiBkZWNvZGVVUkxGcmFtZShmcmFtZSk7XG4gIH1cbiAgcmV0dXJuIGRlY29kZVRleHRGcmFtZShmcmFtZSk7XG59O1xuY29uc3QgZGVjb2RlUHJpdkZyYW1lID0gZnJhbWUgPT4ge1xuICAvKlxuICBGb3JtYXQ6IDx0ZXh0IHN0cmluZz5cXDA8YmluYXJ5IGRhdGE+XG4gICovXG4gIGlmIChmcmFtZS5zaXplIDwgMikge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbiAgY29uc3Qgb3duZXIgPSB1dGY4QXJyYXlUb1N0cihmcmFtZS5kYXRhLCB0cnVlKTtcbiAgY29uc3QgcHJpdmF0ZURhdGEgPSBuZXcgVWludDhBcnJheShmcmFtZS5kYXRhLnN1YmFycmF5KG93bmVyLmxlbmd0aCArIDEpKTtcbiAgcmV0dXJuIHtcbiAgICBrZXk6IGZyYW1lLnR5cGUsXG4gICAgaW5mbzogb3duZXIsXG4gICAgZGF0YTogcHJpdmF0ZURhdGEuYnVmZmVyXG4gIH07XG59O1xuY29uc3QgZGVjb2RlVGV4dEZyYW1lID0gZnJhbWUgPT4ge1xuICBpZiAoZnJhbWUuc2l6ZSA8IDIpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG4gIGlmIChmcmFtZS50eXBlID09PSAnVFhYWCcpIHtcbiAgICAvKlxuICAgIEZvcm1hdDpcbiAgICBbMF0gICA9IHtUZXh0IEVuY29kaW5nfVxuICAgIFsxLT9dID0ge0Rlc2NyaXB0aW9ufVxcMHtWYWx1ZX1cbiAgICAqL1xuICAgIGxldCBpbmRleCA9IDE7XG4gICAgY29uc3QgZGVzY3JpcHRpb24gPSB1dGY4QXJyYXlUb1N0cihmcmFtZS5kYXRhLnN1YmFycmF5KGluZGV4KSwgdHJ1ZSk7XG4gICAgaW5kZXggKz0gZGVzY3JpcHRpb24ubGVuZ3RoICsgMTtcbiAgICBjb25zdCB2YWx1ZSA9IHV0ZjhBcnJheVRvU3RyKGZyYW1lLmRhdGEuc3ViYXJyYXkoaW5kZXgpKTtcbiAgICByZXR1cm4ge1xuICAgICAga2V5OiBmcmFtZS50eXBlLFxuICAgICAgaW5mbzogZGVzY3JpcHRpb24sXG4gICAgICBkYXRhOiB2YWx1ZVxuICAgIH07XG4gIH1cbiAgLypcbiAgRm9ybWF0OlxuICBbMF0gICA9IHtUZXh0IEVuY29kaW5nfVxuICBbMS0/XSA9IHtWYWx1ZX1cbiAgKi9cbiAgY29uc3QgdGV4dCA9IHV0ZjhBcnJheVRvU3RyKGZyYW1lLmRhdGEuc3ViYXJyYXkoMSkpO1xuICByZXR1cm4ge1xuICAgIGtleTogZnJhbWUudHlwZSxcbiAgICBkYXRhOiB0ZXh0XG4gIH07XG59O1xuY29uc3QgZGVjb2RlVVJMRnJhbWUgPSBmcmFtZSA9PiB7XG4gIGlmIChmcmFtZS50eXBlID09PSAnV1hYWCcpIHtcbiAgICAvKlxuICAgIEZvcm1hdDpcbiAgICBbMF0gICA9IHtUZXh0IEVuY29kaW5nfVxuICAgIFsxLT9dID0ge0Rlc2NyaXB0aW9ufVxcMHtVUkx9XG4gICAgKi9cbiAgICBpZiAoZnJhbWUuc2l6ZSA8IDIpIHtcbiAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuICAgIGxldCBpbmRleCA9IDE7XG4gICAgY29uc3QgZGVzY3JpcHRpb24gPSB1dGY4QXJyYXlUb1N0cihmcmFtZS5kYXRhLnN1YmFycmF5KGluZGV4KSwgdHJ1ZSk7XG4gICAgaW5kZXggKz0gZGVzY3JpcHRpb24ubGVuZ3RoICsgMTtcbiAgICBjb25zdCB2YWx1ZSA9IHV0ZjhBcnJheVRvU3RyKGZyYW1lLmRhdGEuc3ViYXJyYXkoaW5kZXgpKTtcbiAgICByZXR1cm4ge1xuICAgICAga2V5OiBmcmFtZS50eXBlLFxuICAgICAgaW5mbzogZGVzY3JpcHRpb24sXG4gICAgICBkYXRhOiB2YWx1ZVxuICAgIH07XG4gIH1cbiAgLypcbiAgRm9ybWF0OlxuICBbMC0/XSA9IHtVUkx9XG4gICovXG4gIGNvbnN0IHVybCA9IHV0ZjhBcnJheVRvU3RyKGZyYW1lLmRhdGEpO1xuICByZXR1cm4ge1xuICAgIGtleTogZnJhbWUudHlwZSxcbiAgICBkYXRhOiB1cmxcbiAgfTtcbn07XG5jb25zdCByZWFkVGltZVN0YW1wID0gdGltZVN0YW1wRnJhbWUgPT4ge1xuICBpZiAodGltZVN0YW1wRnJhbWUuZGF0YS5ieXRlTGVuZ3RoID09PSA4KSB7XG4gICAgY29uc3QgZGF0YSA9IG5ldyBVaW50OEFycmF5KHRpbWVTdGFtcEZyYW1lLmRhdGEpO1xuICAgIC8vIHRpbWVzdGFtcCBpcyAzMyBiaXQgZXhwcmVzc2VkIGFzIGEgYmlnLWVuZGlhbiBlaWdodC1vY3RldCBudW1iZXIsXG4gICAgLy8gd2l0aCB0aGUgdXBwZXIgMzEgYml0cyBzZXQgdG8gemVyby5cbiAgICBjb25zdCBwdHMzM0JpdCA9IGRhdGFbM10gJiAweDE7XG4gICAgbGV0IHRpbWVzdGFtcCA9IChkYXRhWzRdIDw8IDIzKSArIChkYXRhWzVdIDw8IDE1KSArIChkYXRhWzZdIDw8IDcpICsgZGF0YVs3XTtcbiAgICB0aW1lc3RhbXAgLz0gNDU7XG4gICAgaWYgKHB0czMzQml0KSB7XG4gICAgICB0aW1lc3RhbXAgKz0gNDc3MjE4NTguODQ7XG4gICAgfSAvLyAyXjMyIC8gOTBcblxuICAgIHJldHVybiBNYXRoLnJvdW5kKHRpbWVzdGFtcCk7XG4gIH1cbiAgcmV0dXJuIHVuZGVmaW5lZDtcbn07XG5cbi8vIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvODkzNjk4NC91aW50OGFycmF5LXRvLXN0cmluZy1pbi1qYXZhc2NyaXB0LzIyMzczMTk3XG4vLyBodHRwOi8vd3d3Lm9uaWNvcy5jb20vc3RhZmYvaXovYW11c2UvamF2YXNjcmlwdC9leHBlcnQvdXRmLnR4dFxuLyogdXRmLmpzIC0gVVRGLTggPD0+IFVURi0xNiBjb252ZXJ0aW9uXG4gKlxuICogQ29weXJpZ2h0IChDKSAxOTk5IE1hc2FuYW8gSXp1bW8gPGl6QG9uaWNvcy5jby5qcD5cbiAqIFZlcnNpb246IDEuMFxuICogTGFzdE1vZGlmaWVkOiBEZWMgMjUgMTk5OVxuICogVGhpcyBsaWJyYXJ5IGlzIGZyZWUuICBZb3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5IGl0LlxuICovXG5jb25zdCB1dGY4QXJyYXlUb1N0ciA9IChhcnJheSwgZXhpdE9uTnVsbCA9IGZhbHNlKSA9PiB7XG4gIGNvbnN0IGRlY29kZXIgPSBnZXRUZXh0RGVjb2RlcigpO1xuICBpZiAoZGVjb2Rlcikge1xuICAgIGNvbnN0IGRlY29kZWQgPSBkZWNvZGVyLmRlY29kZShhcnJheSk7XG4gICAgaWYgKGV4aXRPbk51bGwpIHtcbiAgICAgIC8vIGdyYWIgdXAgdG8gdGhlIGZpcnN0IG51bGxcbiAgICAgIGNvbnN0IGlkeCA9IGRlY29kZWQuaW5kZXhPZignXFwwJyk7XG4gICAgICByZXR1cm4gaWR4ICE9PSAtMSA/IGRlY29kZWQuc3Vic3RyaW5nKDAsIGlkeCkgOiBkZWNvZGVkO1xuICAgIH1cblxuICAgIC8vIHJlbW92ZSBhbnkgbnVsbCBjaGFyYWN0ZXJzXG4gICAgcmV0dXJuIGRlY29kZWQucmVwbGFjZSgvXFwwL2csICcnKTtcbiAgfVxuICBjb25zdCBsZW4gPSBhcnJheS5sZW5ndGg7XG4gIGxldCBjO1xuICBsZXQgY2hhcjI7XG4gIGxldCBjaGFyMztcbiAgbGV0IG91dCA9ICcnO1xuICBsZXQgaSA9IDA7XG4gIHdoaWxlIChpIDwgbGVuKSB7XG4gICAgYyA9IGFycmF5W2krK107XG4gICAgaWYgKGMgPT09IDB4MDAgJiYgZXhpdE9uTnVsbCkge1xuICAgICAgcmV0dXJuIG91dDtcbiAgICB9IGVsc2UgaWYgKGMgPT09IDB4MDAgfHwgYyA9PT0gMHgwMykge1xuICAgICAgLy8gSWYgdGhlIGNoYXJhY3RlciBpcyAzIChFTkRfT0ZfVEVYVCkgb3IgMCAoTlVMTCkgdGhlbiBza2lwIGl0XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgc3dpdGNoIChjID4+IDQpIHtcbiAgICAgIGNhc2UgMDpcbiAgICAgIGNhc2UgMTpcbiAgICAgIGNhc2UgMjpcbiAgICAgIGNhc2UgMzpcbiAgICAgIGNhc2UgNDpcbiAgICAgIGNhc2UgNTpcbiAgICAgIGNhc2UgNjpcbiAgICAgIGNhc2UgNzpcbiAgICAgICAgLy8gMHh4eHh4eHhcbiAgICAgICAgb3V0ICs9IFN0cmluZy5mcm9tQ2hhckNvZGUoYyk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAxMjpcbiAgICAgIGNhc2UgMTM6XG4gICAgICAgIC8vIDExMHggeHh4eCAgIDEweHggeHh4eFxuICAgICAgICBjaGFyMiA9IGFycmF5W2krK107XG4gICAgICAgIG91dCArPSBTdHJpbmcuZnJvbUNoYXJDb2RlKChjICYgMHgxZikgPDwgNiB8IGNoYXIyICYgMHgzZik7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAxNDpcbiAgICAgICAgLy8gMTExMCB4eHh4ICAxMHh4IHh4eHggIDEweHggeHh4eFxuICAgICAgICBjaGFyMiA9IGFycmF5W2krK107XG4gICAgICAgIGNoYXIzID0gYXJyYXlbaSsrXTtcbiAgICAgICAgb3V0ICs9IFN0cmluZy5mcm9tQ2hhckNvZGUoKGMgJiAweDBmKSA8PCAxMiB8IChjaGFyMiAmIDB4M2YpIDw8IDYgfCAoY2hhcjMgJiAweDNmKSA8PCAwKTtcbiAgICAgICAgYnJlYWs7XG4gICAgfVxuICB9XG4gIHJldHVybiBvdXQ7XG59O1xubGV0IGRlY29kZXI7XG5mdW5jdGlvbiBnZXRUZXh0RGVjb2RlcigpIHtcbiAgLy8gT24gUGxheSBTdGF0aW9uIDQsIFRleHREZWNvZGVyIGlzIGRlZmluZWQgYnV0IHBhcnRpYWxseSBpbXBsZW1lbnRlZC5cbiAgLy8gTWFudWFsIGRlY29kaW5nIG9wdGlvbiBpcyBwcmVmZXJhYmxlXG4gIGlmIChuYXZpZ2F0b3IudXNlckFnZW50LmluY2x1ZGVzKCdQbGF5U3RhdGlvbiA0JykpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgaWYgKCFkZWNvZGVyICYmIHR5cGVvZiBzZWxmLlRleHREZWNvZGVyICE9PSAndW5kZWZpbmVkJykge1xuICAgIGRlY29kZXIgPSBuZXcgc2VsZi5UZXh0RGVjb2RlcigndXRmLTgnKTtcbiAgfVxuICByZXR1cm4gZGVjb2Rlcjtcbn1cblxuLyoqXG4gKiAgaGV4IGR1bXAgaGVscGVyIGNsYXNzXG4gKi9cblxuY29uc3QgSGV4ID0ge1xuICBoZXhEdW1wOiBmdW5jdGlvbiAoYXJyYXkpIHtcbiAgICBsZXQgc3RyID0gJyc7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhcnJheS5sZW5ndGg7IGkrKykge1xuICAgICAgbGV0IGggPSBhcnJheVtpXS50b1N0cmluZygxNik7XG4gICAgICBpZiAoaC5sZW5ndGggPCAyKSB7XG4gICAgICAgIGggPSAnMCcgKyBoO1xuICAgICAgfVxuICAgICAgc3RyICs9IGg7XG4gICAgfVxuICAgIHJldHVybiBzdHI7XG4gIH1cbn07XG5cbmNvbnN0IFVJTlQzMl9NQVgkMSA9IE1hdGgucG93KDIsIDMyKSAtIDE7XG5jb25zdCBwdXNoID0gW10ucHVzaDtcblxuLy8gV2UgYXJlIHVzaW5nIGZpeGVkIHRyYWNrIElEcyBmb3IgZHJpdmluZyB0aGUgTVA0IHJlbXV4ZXJcbi8vIGluc3RlYWQgb2YgZm9sbG93aW5nIHRoZSBUUyBQSURzLlxuLy8gVGhlcmUgaXMgbm8gcmVhc29uIG5vdCB0byBkbyB0aGlzIGFuZCBzb21lIGJyb3dzZXJzL1NvdXJjZUJ1ZmZlci1kZW11eGVyc1xuLy8gbWF5IG5vdCBsaWtlIGlmIHRoZXJlIGFyZSBUcmFja0lEIFwic3dpdGNoZXNcIlxuLy8gU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS92aWRlby1kZXYvaGxzLmpzL2lzc3Vlcy8xMzMxXG4vLyBIZXJlIHdlIGFyZSBtYXBwaW5nIG91ciBpbnRlcm5hbCB0cmFjayB0eXBlcyB0byBjb25zdGFudCBNUDQgdHJhY2sgSURzXG4vLyBXaXRoIE1TRSBjdXJyZW50bHkgb25lIGNhbiBvbmx5IGhhdmUgb25lIHRyYWNrIG9mIGVhY2gsIGFuZCB3ZSBhcmUgbXV4aW5nXG4vLyB3aGF0ZXZlciB2aWRlby9hdWRpbyByZW5kaXRpb24gaW4gdGhlbS5cbmNvbnN0IFJlbXV4ZXJUcmFja0lkQ29uZmlnID0ge1xuICB2aWRlbzogMSxcbiAgYXVkaW86IDIsXG4gIGlkMzogMyxcbiAgdGV4dDogNFxufTtcbmZ1bmN0aW9uIGJpbjJzdHIoZGF0YSkge1xuICByZXR1cm4gU3RyaW5nLmZyb21DaGFyQ29kZS5hcHBseShudWxsLCBkYXRhKTtcbn1cbmZ1bmN0aW9uIHJlYWRVaW50MTYoYnVmZmVyLCBvZmZzZXQpIHtcbiAgY29uc3QgdmFsID0gYnVmZmVyW29mZnNldF0gPDwgOCB8IGJ1ZmZlcltvZmZzZXQgKyAxXTtcbiAgcmV0dXJuIHZhbCA8IDAgPyA2NTUzNiArIHZhbCA6IHZhbDtcbn1cbmZ1bmN0aW9uIHJlYWRVaW50MzIoYnVmZmVyLCBvZmZzZXQpIHtcbiAgY29uc3QgdmFsID0gcmVhZFNpbnQzMihidWZmZXIsIG9mZnNldCk7XG4gIHJldHVybiB2YWwgPCAwID8gNDI5NDk2NzI5NiArIHZhbCA6IHZhbDtcbn1cbmZ1bmN0aW9uIHJlYWRVaW50NjQoYnVmZmVyLCBvZmZzZXQpIHtcbiAgbGV0IHJlc3VsdCA9IHJlYWRVaW50MzIoYnVmZmVyLCBvZmZzZXQpO1xuICByZXN1bHQgKj0gTWF0aC5wb3coMiwgMzIpO1xuICByZXN1bHQgKz0gcmVhZFVpbnQzMihidWZmZXIsIG9mZnNldCArIDQpO1xuICByZXR1cm4gcmVzdWx0O1xufVxuZnVuY3Rpb24gcmVhZFNpbnQzMihidWZmZXIsIG9mZnNldCkge1xuICByZXR1cm4gYnVmZmVyW29mZnNldF0gPDwgMjQgfCBidWZmZXJbb2Zmc2V0ICsgMV0gPDwgMTYgfCBidWZmZXJbb2Zmc2V0ICsgMl0gPDwgOCB8IGJ1ZmZlcltvZmZzZXQgKyAzXTtcbn1cbmZ1bmN0aW9uIHdyaXRlVWludDMyKGJ1ZmZlciwgb2Zmc2V0LCB2YWx1ZSkge1xuICBidWZmZXJbb2Zmc2V0XSA9IHZhbHVlID4+IDI0O1xuICBidWZmZXJbb2Zmc2V0ICsgMV0gPSB2YWx1ZSA+PiAxNiAmIDB4ZmY7XG4gIGJ1ZmZlcltvZmZzZXQgKyAyXSA9IHZhbHVlID4+IDggJiAweGZmO1xuICBidWZmZXJbb2Zmc2V0ICsgM10gPSB2YWx1ZSAmIDB4ZmY7XG59XG5cbi8vIEZpbmQgXCJtb29mXCIgYm94XG5mdW5jdGlvbiBoYXNNb29mRGF0YShkYXRhKSB7XG4gIGNvbnN0IGVuZCA9IGRhdGEuYnl0ZUxlbmd0aDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBlbmQ7KSB7XG4gICAgY29uc3Qgc2l6ZSA9IHJlYWRVaW50MzIoZGF0YSwgaSk7XG4gICAgaWYgKHNpemUgPiA4ICYmIGRhdGFbaSArIDRdID09PSAweDZkICYmIGRhdGFbaSArIDVdID09PSAweDZmICYmIGRhdGFbaSArIDZdID09PSAweDZmICYmIGRhdGFbaSArIDddID09PSAweDY2KSB7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgaSA9IHNpemUgPiAxID8gaSArIHNpemUgOiBlbmQ7XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG4vLyBGaW5kIHRoZSBkYXRhIGZvciBhIGJveCBzcGVjaWZpZWQgYnkgaXRzIHBhdGhcbmZ1bmN0aW9uIGZpbmRCb3goZGF0YSwgcGF0aCkge1xuICBjb25zdCByZXN1bHRzID0gW107XG4gIGlmICghcGF0aC5sZW5ndGgpIHtcbiAgICAvLyBzaG9ydC1jaXJjdWl0IHRoZSBzZWFyY2ggZm9yIGVtcHR5IHBhdGhzXG4gICAgcmV0dXJuIHJlc3VsdHM7XG4gIH1cbiAgY29uc3QgZW5kID0gZGF0YS5ieXRlTGVuZ3RoO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IGVuZDspIHtcbiAgICBjb25zdCBzaXplID0gcmVhZFVpbnQzMihkYXRhLCBpKTtcbiAgICBjb25zdCB0eXBlID0gYmluMnN0cihkYXRhLnN1YmFycmF5KGkgKyA0LCBpICsgOCkpO1xuICAgIGNvbnN0IGVuZGJveCA9IHNpemUgPiAxID8gaSArIHNpemUgOiBlbmQ7XG4gICAgaWYgKHR5cGUgPT09IHBhdGhbMF0pIHtcbiAgICAgIGlmIChwYXRoLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAvLyB0aGlzIGlzIHRoZSBlbmQgb2YgdGhlIHBhdGggYW5kIHdlJ3ZlIGZvdW5kIHRoZSBib3ggd2Ugd2VyZVxuICAgICAgICAvLyBsb29raW5nIGZvclxuICAgICAgICByZXN1bHRzLnB1c2goZGF0YS5zdWJhcnJheShpICsgOCwgZW5kYm94KSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyByZWN1cnNpdmVseSBzZWFyY2ggZm9yIHRoZSBuZXh0IGJveCBhbG9uZyB0aGUgcGF0aFxuICAgICAgICBjb25zdCBzdWJyZXN1bHRzID0gZmluZEJveChkYXRhLnN1YmFycmF5KGkgKyA4LCBlbmRib3gpLCBwYXRoLnNsaWNlKDEpKTtcbiAgICAgICAgaWYgKHN1YnJlc3VsdHMubGVuZ3RoKSB7XG4gICAgICAgICAgcHVzaC5hcHBseShyZXN1bHRzLCBzdWJyZXN1bHRzKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBpID0gZW5kYm94O1xuICB9XG5cbiAgLy8gd2UndmUgZmluaXNoZWQgc2VhcmNoaW5nIGFsbCBvZiBkYXRhXG4gIHJldHVybiByZXN1bHRzO1xufVxuZnVuY3Rpb24gcGFyc2VTZWdtZW50SW5kZXgoc2lkeCkge1xuICBjb25zdCByZWZlcmVuY2VzID0gW107XG4gIGNvbnN0IHZlcnNpb24gPSBzaWR4WzBdO1xuXG4gIC8vIHNldCBpbml0aWFsIG9mZnNldCwgd2Ugc2tpcCB0aGUgcmVmZXJlbmNlIElEIChub3QgbmVlZGVkKVxuICBsZXQgaW5kZXggPSA4O1xuICBjb25zdCB0aW1lc2NhbGUgPSByZWFkVWludDMyKHNpZHgsIGluZGV4KTtcbiAgaW5kZXggKz0gNDtcbiAgbGV0IGVhcmxpZXN0UHJlc2VudGF0aW9uVGltZSA9IDA7XG4gIGxldCBmaXJzdE9mZnNldCA9IDA7XG4gIGlmICh2ZXJzaW9uID09PSAwKSB7XG4gICAgZWFybGllc3RQcmVzZW50YXRpb25UaW1lID0gcmVhZFVpbnQzMihzaWR4LCBpbmRleCk7XG4gICAgZmlyc3RPZmZzZXQgPSByZWFkVWludDMyKHNpZHgsIGluZGV4ICsgNCk7XG4gICAgaW5kZXggKz0gODtcbiAgfSBlbHNlIHtcbiAgICBlYXJsaWVzdFByZXNlbnRhdGlvblRpbWUgPSByZWFkVWludDY0KHNpZHgsIGluZGV4KTtcbiAgICBmaXJzdE9mZnNldCA9IHJlYWRVaW50NjQoc2lkeCwgaW5kZXggKyA4KTtcbiAgICBpbmRleCArPSAxNjtcbiAgfVxuXG4gIC8vIHNraXAgcmVzZXJ2ZWRcbiAgaW5kZXggKz0gMjtcbiAgbGV0IHN0YXJ0Qnl0ZSA9IHNpZHgubGVuZ3RoICsgZmlyc3RPZmZzZXQ7XG4gIGNvbnN0IHJlZmVyZW5jZXNDb3VudCA9IHJlYWRVaW50MTYoc2lkeCwgaW5kZXgpO1xuICBpbmRleCArPSAyO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHJlZmVyZW5jZXNDb3VudDsgaSsrKSB7XG4gICAgbGV0IHJlZmVyZW5jZUluZGV4ID0gaW5kZXg7XG4gICAgY29uc3QgcmVmZXJlbmNlSW5mbyA9IHJlYWRVaW50MzIoc2lkeCwgcmVmZXJlbmNlSW5kZXgpO1xuICAgIHJlZmVyZW5jZUluZGV4ICs9IDQ7XG4gICAgY29uc3QgcmVmZXJlbmNlU2l6ZSA9IHJlZmVyZW5jZUluZm8gJiAweDdmZmZmZmZmO1xuICAgIGNvbnN0IHJlZmVyZW5jZVR5cGUgPSAocmVmZXJlbmNlSW5mbyAmIDB4ODAwMDAwMDApID4+PiAzMTtcbiAgICBpZiAocmVmZXJlbmNlVHlwZSA9PT0gMSkge1xuICAgICAgbG9nZ2VyLndhcm4oJ1NJRFggaGFzIGhpZXJhcmNoaWNhbCByZWZlcmVuY2VzIChub3Qgc3VwcG9ydGVkKScpO1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGNvbnN0IHN1YnNlZ21lbnREdXJhdGlvbiA9IHJlYWRVaW50MzIoc2lkeCwgcmVmZXJlbmNlSW5kZXgpO1xuICAgIHJlZmVyZW5jZUluZGV4ICs9IDQ7XG4gICAgcmVmZXJlbmNlcy5wdXNoKHtcbiAgICAgIHJlZmVyZW5jZVNpemUsXG4gICAgICBzdWJzZWdtZW50RHVyYXRpb24sXG4gICAgICAvLyB1bnNjYWxlZFxuICAgICAgaW5mbzoge1xuICAgICAgICBkdXJhdGlvbjogc3Vic2VnbWVudER1cmF0aW9uIC8gdGltZXNjYWxlLFxuICAgICAgICBzdGFydDogc3RhcnRCeXRlLFxuICAgICAgICBlbmQ6IHN0YXJ0Qnl0ZSArIHJlZmVyZW5jZVNpemUgLSAxXG4gICAgICB9XG4gICAgfSk7XG4gICAgc3RhcnRCeXRlICs9IHJlZmVyZW5jZVNpemU7XG5cbiAgICAvLyBTa2lwcGluZyAxIGJpdCBmb3IgfHN0YXJ0c1dpdGhTYXB8LCAzIGJpdHMgZm9yIHxzYXBUeXBlfCwgYW5kIDI4IGJpdHNcbiAgICAvLyBmb3IgfHNhcERlbHRhfC5cbiAgICByZWZlcmVuY2VJbmRleCArPSA0O1xuXG4gICAgLy8gc2tpcCB0byBuZXh0IHJlZlxuICAgIGluZGV4ID0gcmVmZXJlbmNlSW5kZXg7XG4gIH1cbiAgcmV0dXJuIHtcbiAgICBlYXJsaWVzdFByZXNlbnRhdGlvblRpbWUsXG4gICAgdGltZXNjYWxlLFxuICAgIHZlcnNpb24sXG4gICAgcmVmZXJlbmNlc0NvdW50LFxuICAgIHJlZmVyZW5jZXNcbiAgfTtcbn1cblxuLyoqXG4gKiBQYXJzZXMgYW4gTVA0IGluaXRpYWxpemF0aW9uIHNlZ21lbnQgYW5kIGV4dHJhY3RzIHN0cmVhbSB0eXBlIGFuZFxuICogdGltZXNjYWxlIHZhbHVlcyBmb3IgYW55IGRlY2xhcmVkIHRyYWNrcy4gVGltZXNjYWxlIHZhbHVlcyBpbmRpY2F0ZSB0aGVcbiAqIG51bWJlciBvZiBjbG9jayB0aWNrcyBwZXIgc2Vjb25kIHRvIGFzc3VtZSBmb3IgdGltZS1iYXNlZCB2YWx1ZXNcbiAqIGVsc2V3aGVyZSBpbiB0aGUgTVA0LlxuICpcbiAqIFRvIGRldGVybWluZSB0aGUgc3RhcnQgdGltZSBvZiBhbiBNUDQsIHlvdSBuZWVkIHR3byBwaWVjZXMgb2ZcbiAqIGluZm9ybWF0aW9uOiB0aGUgdGltZXNjYWxlIHVuaXQgYW5kIHRoZSBlYXJsaWVzdCBiYXNlIG1lZGlhIGRlY29kZVxuICogdGltZS4gTXVsdGlwbGUgdGltZXNjYWxlcyBjYW4gYmUgc3BlY2lmaWVkIHdpdGhpbiBhbiBNUDQgYnV0IHRoZVxuICogYmFzZSBtZWRpYSBkZWNvZGUgdGltZSBpcyBhbHdheXMgZXhwcmVzc2VkIGluIHRoZSB0aW1lc2NhbGUgZnJvbVxuICogdGhlIG1lZGlhIGhlYWRlciBib3ggZm9yIHRoZSB0cmFjazpcbiAqIGBgYFxuICogbW9vdiA+IHRyYWsgPiBtZGlhID4gbWRoZC50aW1lc2NhbGVcbiAqIG1vb3YgPiB0cmFrID4gbWRpYSA+IGhkbHJcbiAqIGBgYFxuICogQHBhcmFtIGluaXRTZWdtZW50IHRoZSBieXRlcyBvZiB0aGUgaW5pdCBzZWdtZW50XG4gKiBAcmV0dXJucyBhIGhhc2ggb2YgdHJhY2sgdHlwZSB0byB0aW1lc2NhbGUgdmFsdWVzIG9yIG51bGwgaWZcbiAqIHRoZSBpbml0IHNlZ21lbnQgaXMgbWFsZm9ybWVkLlxuICovXG5cbmZ1bmN0aW9uIHBhcnNlSW5pdFNlZ21lbnQoaW5pdFNlZ21lbnQpIHtcbiAgY29uc3QgcmVzdWx0ID0gW107XG4gIGNvbnN0IHRyYWtzID0gZmluZEJveChpbml0U2VnbWVudCwgWydtb292JywgJ3RyYWsnXSk7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgdHJha3MubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCB0cmFrID0gdHJha3NbaV07XG4gICAgY29uc3QgdGtoZCA9IGZpbmRCb3godHJhaywgWyd0a2hkJ10pWzBdO1xuICAgIGlmICh0a2hkKSB7XG4gICAgICBsZXQgdmVyc2lvbiA9IHRraGRbMF07XG4gICAgICBjb25zdCB0cmFja0lkID0gcmVhZFVpbnQzMih0a2hkLCB2ZXJzaW9uID09PSAwID8gMTIgOiAyMCk7XG4gICAgICBjb25zdCBtZGhkID0gZmluZEJveCh0cmFrLCBbJ21kaWEnLCAnbWRoZCddKVswXTtcbiAgICAgIGlmIChtZGhkKSB7XG4gICAgICAgIHZlcnNpb24gPSBtZGhkWzBdO1xuICAgICAgICBjb25zdCB0aW1lc2NhbGUgPSByZWFkVWludDMyKG1kaGQsIHZlcnNpb24gPT09IDAgPyAxMiA6IDIwKTtcbiAgICAgICAgY29uc3QgaGRsciA9IGZpbmRCb3godHJhaywgWydtZGlhJywgJ2hkbHInXSlbMF07XG4gICAgICAgIGlmIChoZGxyKSB7XG4gICAgICAgICAgY29uc3QgaGRsclR5cGUgPSBiaW4yc3RyKGhkbHIuc3ViYXJyYXkoOCwgMTIpKTtcbiAgICAgICAgICBjb25zdCB0eXBlID0ge1xuICAgICAgICAgICAgc291bjogRWxlbWVudGFyeVN0cmVhbVR5cGVzLkFVRElPLFxuICAgICAgICAgICAgdmlkZTogRWxlbWVudGFyeVN0cmVhbVR5cGVzLlZJREVPXG4gICAgICAgICAgfVtoZGxyVHlwZV07XG4gICAgICAgICAgaWYgKHR5cGUpIHtcbiAgICAgICAgICAgIC8vIFBhcnNlIGNvZGVjIGRldGFpbHNcbiAgICAgICAgICAgIGNvbnN0IHN0c2QgPSBmaW5kQm94KHRyYWssIFsnbWRpYScsICdtaW5mJywgJ3N0YmwnLCAnc3RzZCddKVswXTtcbiAgICAgICAgICAgIGNvbnN0IHN0c2REYXRhID0gcGFyc2VTdHNkKHN0c2QpO1xuICAgICAgICAgICAgcmVzdWx0W3RyYWNrSWRdID0ge1xuICAgICAgICAgICAgICB0aW1lc2NhbGUsXG4gICAgICAgICAgICAgIHR5cGVcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICByZXN1bHRbdHlwZV0gPSBfb2JqZWN0U3ByZWFkMih7XG4gICAgICAgICAgICAgIHRpbWVzY2FsZSxcbiAgICAgICAgICAgICAgaWQ6IHRyYWNrSWRcbiAgICAgICAgICAgIH0sIHN0c2REYXRhKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgY29uc3QgdHJleCA9IGZpbmRCb3goaW5pdFNlZ21lbnQsIFsnbW9vdicsICdtdmV4JywgJ3RyZXgnXSk7XG4gIHRyZXguZm9yRWFjaCh0cmV4ID0+IHtcbiAgICBjb25zdCB0cmFja0lkID0gcmVhZFVpbnQzMih0cmV4LCA0KTtcbiAgICBjb25zdCB0cmFjayA9IHJlc3VsdFt0cmFja0lkXTtcbiAgICBpZiAodHJhY2spIHtcbiAgICAgIHRyYWNrLmRlZmF1bHQgPSB7XG4gICAgICAgIGR1cmF0aW9uOiByZWFkVWludDMyKHRyZXgsIDEyKSxcbiAgICAgICAgZmxhZ3M6IHJlYWRVaW50MzIodHJleCwgMjApXG4gICAgICB9O1xuICAgIH1cbiAgfSk7XG4gIHJldHVybiByZXN1bHQ7XG59XG5mdW5jdGlvbiBwYXJzZVN0c2Qoc3RzZCkge1xuICBjb25zdCBzYW1wbGVFbnRyaWVzID0gc3RzZC5zdWJhcnJheSg4KTtcbiAgY29uc3Qgc2FtcGxlRW50cmllc0VuZCA9IHNhbXBsZUVudHJpZXMuc3ViYXJyYXkoOCArIDc4KTtcbiAgY29uc3QgZm91ckNDID0gYmluMnN0cihzYW1wbGVFbnRyaWVzLnN1YmFycmF5KDQsIDgpKTtcbiAgbGV0IGNvZGVjID0gZm91ckNDO1xuICBjb25zdCBlbmNyeXB0ZWQgPSBmb3VyQ0MgPT09ICdlbmNhJyB8fCBmb3VyQ0MgPT09ICdlbmN2JztcbiAgaWYgKGVuY3J5cHRlZCkge1xuICAgIGNvbnN0IGVuY0JveCA9IGZpbmRCb3goc2FtcGxlRW50cmllcywgW2ZvdXJDQ10pWzBdO1xuICAgIGNvbnN0IGVuY0JveENoaWxkcmVuID0gZW5jQm94LnN1YmFycmF5KGZvdXJDQyA9PT0gJ2VuY2EnID8gMjggOiA3OCk7XG4gICAgY29uc3Qgc2luZnMgPSBmaW5kQm94KGVuY0JveENoaWxkcmVuLCBbJ3NpbmYnXSk7XG4gICAgc2luZnMuZm9yRWFjaChzaW5mID0+IHtcbiAgICAgIGNvbnN0IHNjaG0gPSBmaW5kQm94KHNpbmYsIFsnc2NobSddKVswXTtcbiAgICAgIGlmIChzY2htKSB7XG4gICAgICAgIGNvbnN0IHNjaGVtZSA9IGJpbjJzdHIoc2NobS5zdWJhcnJheSg0LCA4KSk7XG4gICAgICAgIGlmIChzY2hlbWUgPT09ICdjYmNzJyB8fCBzY2hlbWUgPT09ICdjZW5jJykge1xuICAgICAgICAgIGNvbnN0IGZybWEgPSBmaW5kQm94KHNpbmYsIFsnZnJtYSddKVswXTtcbiAgICAgICAgICBpZiAoZnJtYSkge1xuICAgICAgICAgICAgLy8gZm9yIGVuY3J5cHRlZCBjb250ZW50IGNvZGVjIGZvdXJDQyB3aWxsIGJlIGluIGZybWFcbiAgICAgICAgICAgIGNvZGVjID0gYmluMnN0cihmcm1hKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICBzd2l0Y2ggKGNvZGVjKSB7XG4gICAgY2FzZSAnYXZjMSc6XG4gICAgY2FzZSAnYXZjMic6XG4gICAgY2FzZSAnYXZjMyc6XG4gICAgY2FzZSAnYXZjNCc6XG4gICAgICB7XG4gICAgICAgIC8vIGV4dHJhY3QgcHJvZmlsZSArIGNvbXBhdGliaWxpdHkgKyBsZXZlbCBvdXQgb2YgYXZjQyBib3hcbiAgICAgICAgY29uc3QgYXZjQ0JveCA9IGZpbmRCb3goc2FtcGxlRW50cmllc0VuZCwgWydhdmNDJ10pWzBdO1xuICAgICAgICBjb2RlYyArPSAnLicgKyB0b0hleChhdmNDQm94WzFdKSArIHRvSGV4KGF2Y0NCb3hbMl0pICsgdG9IZXgoYXZjQ0JveFszXSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIGNhc2UgJ21wNGEnOlxuICAgICAge1xuICAgICAgICBjb25zdCBjb2RlY0JveCA9IGZpbmRCb3goc2FtcGxlRW50cmllcywgW2ZvdXJDQ10pWzBdO1xuICAgICAgICBjb25zdCBlc2RzQm94ID0gZmluZEJveChjb2RlY0JveC5zdWJhcnJheSgyOCksIFsnZXNkcyddKVswXTtcbiAgICAgICAgaWYgKGVzZHNCb3ggJiYgZXNkc0JveC5sZW5ndGggPiAxMikge1xuICAgICAgICAgIGxldCBpID0gNDtcbiAgICAgICAgICAvLyBFUyBEZXNjcmlwdG9yIHRhZ1xuICAgICAgICAgIGlmIChlc2RzQm94W2krK10gIT09IDB4MDMpIHtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpID0gc2tpcEJFUkludGVnZXIoZXNkc0JveCwgaSk7XG4gICAgICAgICAgaSArPSAyOyAvLyBza2lwIGVzX2lkO1xuICAgICAgICAgIGNvbnN0IGZsYWdzID0gZXNkc0JveFtpKytdO1xuICAgICAgICAgIGlmIChmbGFncyAmIDB4ODApIHtcbiAgICAgICAgICAgIGkgKz0gMjsgLy8gc2tpcCBkZXBlbmRlbmN5IGVzX2lkXG4gICAgICAgICAgfVxuICAgICAgICAgIGlmIChmbGFncyAmIDB4NDApIHtcbiAgICAgICAgICAgIGkgKz0gZXNkc0JveFtpKytdOyAvLyBza2lwIFVSTFxuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBEZWNvZGVyIGNvbmZpZyBkZXNjcmlwdG9yXG4gICAgICAgICAgaWYgKGVzZHNCb3hbaSsrXSAhPT0gMHgwNCkge1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICAgIGkgPSBza2lwQkVSSW50ZWdlcihlc2RzQm94LCBpKTtcbiAgICAgICAgICBjb25zdCBvYmplY3RUeXBlID0gZXNkc0JveFtpKytdO1xuICAgICAgICAgIGlmIChvYmplY3RUeXBlID09PSAweDQwKSB7XG4gICAgICAgICAgICBjb2RlYyArPSAnLicgKyB0b0hleChvYmplY3RUeXBlKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICAgIGkgKz0gMTI7XG4gICAgICAgICAgLy8gRGVjb2RlciBzcGVjaWZpYyBpbmZvXG4gICAgICAgICAgaWYgKGVzZHNCb3hbaSsrXSAhPT0gMHgwNSkge1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICAgIGkgPSBza2lwQkVSSW50ZWdlcihlc2RzQm94LCBpKTtcbiAgICAgICAgICBjb25zdCBmaXJzdEJ5dGUgPSBlc2RzQm94W2krK107XG4gICAgICAgICAgbGV0IGF1ZGlvT2JqZWN0VHlwZSA9IChmaXJzdEJ5dGUgJiAweGY4KSA+PiAzO1xuICAgICAgICAgIGlmIChhdWRpb09iamVjdFR5cGUgPT09IDMxKSB7XG4gICAgICAgICAgICBhdWRpb09iamVjdFR5cGUgKz0gMSArICgoZmlyc3RCeXRlICYgMHg3KSA8PCAzKSArICgoZXNkc0JveFtpXSAmIDB4ZTApID4+IDUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBjb2RlYyArPSAnLicgKyBhdWRpb09iamVjdFR5cGU7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgY2FzZSAnaHZjMSc6XG4gICAgY2FzZSAnaGV2MSc6XG4gICAgICB7XG4gICAgICAgIGNvbnN0IGh2Y0NCb3ggPSBmaW5kQm94KHNhbXBsZUVudHJpZXNFbmQsIFsnaHZjQyddKVswXTtcbiAgICAgICAgY29uc3QgcHJvZmlsZUJ5dGUgPSBodmNDQm94WzFdO1xuICAgICAgICBjb25zdCBwcm9maWxlU3BhY2UgPSBbJycsICdBJywgJ0InLCAnQyddW3Byb2ZpbGVCeXRlID4+IDZdO1xuICAgICAgICBjb25zdCBnZW5lcmFsUHJvZmlsZUlkYyA9IHByb2ZpbGVCeXRlICYgMHgxZjtcbiAgICAgICAgY29uc3QgcHJvZmlsZUNvbXBhdCA9IHJlYWRVaW50MzIoaHZjQ0JveCwgMik7XG4gICAgICAgIGNvbnN0IHRpZXJGbGFnID0gKHByb2ZpbGVCeXRlICYgMHgyMCkgPj4gNSA/ICdIJyA6ICdMJztcbiAgICAgICAgY29uc3QgbGV2ZWxJREMgPSBodmNDQm94WzEyXTtcbiAgICAgICAgY29uc3QgY29uc3RyYWludEluZGljYXRvciA9IGh2Y0NCb3guc3ViYXJyYXkoNiwgMTIpO1xuICAgICAgICBjb2RlYyArPSAnLicgKyBwcm9maWxlU3BhY2UgKyBnZW5lcmFsUHJvZmlsZUlkYztcbiAgICAgICAgY29kZWMgKz0gJy4nICsgcHJvZmlsZUNvbXBhdC50b1N0cmluZygxNikudG9VcHBlckNhc2UoKTtcbiAgICAgICAgY29kZWMgKz0gJy4nICsgdGllckZsYWcgKyBsZXZlbElEQztcbiAgICAgICAgbGV0IGNvbnN0cmFpbnRTdHJpbmcgPSAnJztcbiAgICAgICAgZm9yIChsZXQgaSA9IGNvbnN0cmFpbnRJbmRpY2F0b3IubGVuZ3RoOyBpLS07KSB7XG4gICAgICAgICAgY29uc3QgYnl0ZSA9IGNvbnN0cmFpbnRJbmRpY2F0b3JbaV07XG4gICAgICAgICAgaWYgKGJ5dGUgfHwgY29uc3RyYWludFN0cmluZykge1xuICAgICAgICAgICAgY29uc3QgZW5jb2RlZEJ5dGUgPSBieXRlLnRvU3RyaW5nKDE2KS50b1VwcGVyQ2FzZSgpO1xuICAgICAgICAgICAgY29uc3RyYWludFN0cmluZyA9ICcuJyArIGVuY29kZWRCeXRlICsgY29uc3RyYWludFN0cmluZztcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY29kZWMgKz0gY29uc3RyYWludFN0cmluZztcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgY2FzZSAnZHZoMSc6XG4gICAgY2FzZSAnZHZoZSc6XG4gICAgICB7XG4gICAgICAgIGNvbnN0IGR2Y0NCb3ggPSBmaW5kQm94KHNhbXBsZUVudHJpZXNFbmQsIFsnZHZjQyddKVswXTtcbiAgICAgICAgY29uc3QgcHJvZmlsZSA9IGR2Y0NCb3hbMl0gPj4gMSAmIDB4N2Y7XG4gICAgICAgIGNvbnN0IGxldmVsID0gZHZjQ0JveFsyXSA8PCA1ICYgMHgyMCB8IGR2Y0NCb3hbM10gPj4gMyAmIDB4MWY7XG4gICAgICAgIGNvZGVjICs9ICcuJyArIGFkZExlYWRpbmdaZXJvKHByb2ZpbGUpICsgJy4nICsgYWRkTGVhZGluZ1plcm8obGV2ZWwpO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICBjYXNlICd2cDA5JzpcbiAgICAgIHtcbiAgICAgICAgY29uc3QgdnBjQ0JveCA9IGZpbmRCb3goc2FtcGxlRW50cmllc0VuZCwgWyd2cGNDJ10pWzBdO1xuICAgICAgICBjb25zdCBwcm9maWxlID0gdnBjQ0JveFs0XTtcbiAgICAgICAgY29uc3QgbGV2ZWwgPSB2cGNDQm94WzVdO1xuICAgICAgICBjb25zdCBiaXREZXB0aCA9IHZwY0NCb3hbNl0gPj4gNCAmIDB4MGY7XG4gICAgICAgIGNvZGVjICs9ICcuJyArIGFkZExlYWRpbmdaZXJvKHByb2ZpbGUpICsgJy4nICsgYWRkTGVhZGluZ1plcm8obGV2ZWwpICsgJy4nICsgYWRkTGVhZGluZ1plcm8oYml0RGVwdGgpO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICBjYXNlICdhdjAxJzpcbiAgICAgIHtcbiAgICAgICAgY29uc3QgYXYxQ0JveCA9IGZpbmRCb3goc2FtcGxlRW50cmllc0VuZCwgWydhdjFDJ10pWzBdO1xuICAgICAgICBjb25zdCBwcm9maWxlID0gYXYxQ0JveFsxXSA+Pj4gNTtcbiAgICAgICAgY29uc3QgbGV2ZWwgPSBhdjFDQm94WzFdICYgMHgxZjtcbiAgICAgICAgY29uc3QgdGllckZsYWcgPSBhdjFDQm94WzJdID4+PiA3ID8gJ0gnIDogJ00nO1xuICAgICAgICBjb25zdCBoaWdoQml0RGVwdGggPSAoYXYxQ0JveFsyXSAmIDB4NDApID4+IDY7XG4gICAgICAgIGNvbnN0IHR3ZWx2ZUJpdCA9IChhdjFDQm94WzJdICYgMHgyMCkgPj4gNTtcbiAgICAgICAgY29uc3QgYml0RGVwdGggPSBwcm9maWxlID09PSAyICYmIGhpZ2hCaXREZXB0aCA/IHR3ZWx2ZUJpdCA/IDEyIDogMTAgOiBoaWdoQml0RGVwdGggPyAxMCA6IDg7XG4gICAgICAgIGNvbnN0IG1vbm9jaHJvbWUgPSAoYXYxQ0JveFsyXSAmIDB4MTApID4+IDQ7XG4gICAgICAgIGNvbnN0IGNocm9tYVN1YnNhbXBsaW5nWCA9IChhdjFDQm94WzJdICYgMHgwOCkgPj4gMztcbiAgICAgICAgY29uc3QgY2hyb21hU3Vic2FtcGxpbmdZID0gKGF2MUNCb3hbMl0gJiAweDA0KSA+PiAyO1xuICAgICAgICBjb25zdCBjaHJvbWFTYW1wbGVQb3NpdGlvbiA9IGF2MUNCb3hbMl0gJiAweDAzO1xuICAgICAgICAvLyBUT0RPOiBwYXJzZSBjb2xvcl9kZXNjcmlwdGlvbl9wcmVzZW50X2ZsYWdcbiAgICAgICAgLy8gZGVmYXVsdCBpdCB0byBCVC43MDkvbGltaXRlZCByYW5nZSBmb3Igbm93XG4gICAgICAgIC8vIG1vcmUgaW5mbyBodHRwczovL2FvbWVkaWFjb2RlYy5naXRodWIuaW8vYXYxLWlzb2JtZmYvI2F2MWNvZGVjY29uZmlndXJhdGlvbmJveC1zeW50YXhcbiAgICAgICAgY29uc3QgY29sb3JQcmltYXJpZXMgPSAxO1xuICAgICAgICBjb25zdCB0cmFuc2ZlckNoYXJhY3RlcmlzdGljcyA9IDE7XG4gICAgICAgIGNvbnN0IG1hdHJpeENvZWZmaWNpZW50cyA9IDE7XG4gICAgICAgIGNvbnN0IHZpZGVvRnVsbFJhbmdlRmxhZyA9IDA7XG4gICAgICAgIGNvZGVjICs9ICcuJyArIHByb2ZpbGUgKyAnLicgKyBhZGRMZWFkaW5nWmVybyhsZXZlbCkgKyB0aWVyRmxhZyArICcuJyArIGFkZExlYWRpbmdaZXJvKGJpdERlcHRoKSArICcuJyArIG1vbm9jaHJvbWUgKyAnLicgKyBjaHJvbWFTdWJzYW1wbGluZ1ggKyBjaHJvbWFTdWJzYW1wbGluZ1kgKyBjaHJvbWFTYW1wbGVQb3NpdGlvbiArICcuJyArIGFkZExlYWRpbmdaZXJvKGNvbG9yUHJpbWFyaWVzKSArICcuJyArIGFkZExlYWRpbmdaZXJvKHRyYW5zZmVyQ2hhcmFjdGVyaXN0aWNzKSArICcuJyArIGFkZExlYWRpbmdaZXJvKG1hdHJpeENvZWZmaWNpZW50cykgKyAnLicgKyB2aWRlb0Z1bGxSYW5nZUZsYWc7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICB9XG4gIHJldHVybiB7XG4gICAgY29kZWMsXG4gICAgZW5jcnlwdGVkXG4gIH07XG59XG5mdW5jdGlvbiBza2lwQkVSSW50ZWdlcihieXRlcywgaSkge1xuICBjb25zdCBsaW1pdCA9IGkgKyA1O1xuICB3aGlsZSAoYnl0ZXNbaSsrXSAmIDB4ODAgJiYgaSA8IGxpbWl0KSB7fVxuICByZXR1cm4gaTtcbn1cbmZ1bmN0aW9uIHRvSGV4KHgpIHtcbiAgcmV0dXJuICgnMCcgKyB4LnRvU3RyaW5nKDE2KS50b1VwcGVyQ2FzZSgpKS5zbGljZSgtMik7XG59XG5mdW5jdGlvbiBhZGRMZWFkaW5nWmVybyhudW0pIHtcbiAgcmV0dXJuIChudW0gPCAxMCA/ICcwJyA6ICcnKSArIG51bTtcbn1cbmZ1bmN0aW9uIHBhdGNoRW5jeXB0aW9uRGF0YShpbml0U2VnbWVudCwgZGVjcnlwdGRhdGEpIHtcbiAgaWYgKCFpbml0U2VnbWVudCB8fCAhZGVjcnlwdGRhdGEpIHtcbiAgICByZXR1cm4gaW5pdFNlZ21lbnQ7XG4gIH1cbiAgY29uc3Qga2V5SWQgPSBkZWNyeXB0ZGF0YS5rZXlJZDtcbiAgaWYgKGtleUlkICYmIGRlY3J5cHRkYXRhLmlzQ29tbW9uRW5jcnlwdGlvbikge1xuICAgIGNvbnN0IHRyYWtzID0gZmluZEJveChpbml0U2VnbWVudCwgWydtb292JywgJ3RyYWsnXSk7XG4gICAgdHJha3MuZm9yRWFjaCh0cmFrID0+IHtcbiAgICAgIGNvbnN0IHN0c2QgPSBmaW5kQm94KHRyYWssIFsnbWRpYScsICdtaW5mJywgJ3N0YmwnLCAnc3RzZCddKVswXTtcblxuICAgICAgLy8gc2tpcCB0aGUgc2FtcGxlIGVudHJ5IGNvdW50XG4gICAgICBjb25zdCBzYW1wbGVFbnRyaWVzID0gc3RzZC5zdWJhcnJheSg4KTtcbiAgICAgIGxldCBlbmNCb3hlcyA9IGZpbmRCb3goc2FtcGxlRW50cmllcywgWydlbmNhJ10pO1xuICAgICAgY29uc3QgaXNBdWRpbyA9IGVuY0JveGVzLmxlbmd0aCA+IDA7XG4gICAgICBpZiAoIWlzQXVkaW8pIHtcbiAgICAgICAgZW5jQm94ZXMgPSBmaW5kQm94KHNhbXBsZUVudHJpZXMsIFsnZW5jdiddKTtcbiAgICAgIH1cbiAgICAgIGVuY0JveGVzLmZvckVhY2goZW5jID0+IHtcbiAgICAgICAgY29uc3QgZW5jQm94Q2hpbGRyZW4gPSBpc0F1ZGlvID8gZW5jLnN1YmFycmF5KDI4KSA6IGVuYy5zdWJhcnJheSg3OCk7XG4gICAgICAgIGNvbnN0IHNpbmZCb3hlcyA9IGZpbmRCb3goZW5jQm94Q2hpbGRyZW4sIFsnc2luZiddKTtcbiAgICAgICAgc2luZkJveGVzLmZvckVhY2goc2luZiA9PiB7XG4gICAgICAgICAgY29uc3QgdGVuYyA9IHBhcnNlU2luZihzaW5mKTtcbiAgICAgICAgICBpZiAodGVuYykge1xuICAgICAgICAgICAgLy8gTG9vayBmb3IgZGVmYXVsdCBrZXkgaWQgKGtleUlEIG9mZnNldCBpcyBhbHdheXMgOCB3aXRoaW4gdGhlIHRlbmMgYm94KTpcbiAgICAgICAgICAgIGNvbnN0IHRlbmNLZXlJZCA9IHRlbmMuc3ViYXJyYXkoOCwgMjQpO1xuICAgICAgICAgICAgaWYgKCF0ZW5jS2V5SWQuc29tZShiID0+IGIgIT09IDApKSB7XG4gICAgICAgICAgICAgIGxvZ2dlci5sb2coYFtlbWVdIFBhdGNoaW5nIGtleUlkIGluICdlbmMke2lzQXVkaW8gPyAnYScgOiAndid9PnNpbmY+PnRlbmMnIGJveDogJHtIZXguaGV4RHVtcCh0ZW5jS2V5SWQpfSAtPiAke0hleC5oZXhEdW1wKGtleUlkKX1gKTtcbiAgICAgICAgICAgICAgdGVuYy5zZXQoa2V5SWQsIDgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gaW5pdFNlZ21lbnQ7XG59XG5mdW5jdGlvbiBwYXJzZVNpbmYoc2luZikge1xuICBjb25zdCBzY2htID0gZmluZEJveChzaW5mLCBbJ3NjaG0nXSlbMF07XG4gIGlmIChzY2htKSB7XG4gICAgY29uc3Qgc2NoZW1lID0gYmluMnN0cihzY2htLnN1YmFycmF5KDQsIDgpKTtcbiAgICBpZiAoc2NoZW1lID09PSAnY2JjcycgfHwgc2NoZW1lID09PSAnY2VuYycpIHtcbiAgICAgIHJldHVybiBmaW5kQm94KHNpbmYsIFsnc2NoaScsICd0ZW5jJ10pWzBdO1xuICAgIH1cbiAgfVxuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqXG4gKiBEZXRlcm1pbmUgdGhlIGJhc2UgbWVkaWEgZGVjb2RlIHN0YXJ0IHRpbWUsIGluIHNlY29uZHMsIGZvciBhbiBNUDRcbiAqIGZyYWdtZW50LiBJZiBtdWx0aXBsZSBmcmFnbWVudHMgYXJlIHNwZWNpZmllZCwgdGhlIGVhcmxpZXN0IHRpbWUgaXNcbiAqIHJldHVybmVkLlxuICpcbiAqIFRoZSBiYXNlIG1lZGlhIGRlY29kZSB0aW1lIGNhbiBiZSBwYXJzZWQgZnJvbSB0cmFjayBmcmFnbWVudFxuICogbWV0YWRhdGE6XG4gKiBgYGBcbiAqIG1vb2YgPiB0cmFmID4gdGZkdC5iYXNlTWVkaWFEZWNvZGVUaW1lXG4gKiBgYGBcbiAqIEl0IHJlcXVpcmVzIHRoZSB0aW1lc2NhbGUgdmFsdWUgZnJvbSB0aGUgbWRoZCB0byBpbnRlcnByZXQuXG4gKlxuICogQHBhcmFtIGluaXREYXRhIC0gYSBoYXNoIG9mIHRyYWNrIHR5cGUgdG8gdGltZXNjYWxlIHZhbHVlc1xuICogQHBhcmFtIGZtcDQgLSB0aGUgYnl0ZXMgb2YgdGhlIG1wNCBmcmFnbWVudFxuICogQHJldHVybnMgdGhlIGVhcmxpZXN0IGJhc2UgbWVkaWEgZGVjb2RlIHN0YXJ0IHRpbWUgZm9yIHRoZVxuICogZnJhZ21lbnQsIGluIHNlY29uZHNcbiAqL1xuZnVuY3Rpb24gZ2V0U3RhcnREVFMoaW5pdERhdGEsIGZtcDQpIHtcbiAgLy8gd2UgbmVlZCBpbmZvIGZyb20gdHdvIGNoaWxkcmVuIG9mIGVhY2ggdHJhY2sgZnJhZ21lbnQgYm94XG4gIHJldHVybiBmaW5kQm94KGZtcDQsIFsnbW9vZicsICd0cmFmJ10pLnJlZHVjZSgocmVzdWx0LCB0cmFmKSA9PiB7XG4gICAgY29uc3QgdGZkdCA9IGZpbmRCb3godHJhZiwgWyd0ZmR0J10pWzBdO1xuICAgIGNvbnN0IHZlcnNpb24gPSB0ZmR0WzBdO1xuICAgIGNvbnN0IHN0YXJ0ID0gZmluZEJveCh0cmFmLCBbJ3RmaGQnXSkucmVkdWNlKChyZXN1bHQsIHRmaGQpID0+IHtcbiAgICAgIC8vIGdldCB0aGUgdHJhY2sgaWQgZnJvbSB0aGUgdGZoZFxuICAgICAgY29uc3QgaWQgPSByZWFkVWludDMyKHRmaGQsIDQpO1xuICAgICAgY29uc3QgdHJhY2sgPSBpbml0RGF0YVtpZF07XG4gICAgICBpZiAodHJhY2spIHtcbiAgICAgICAgbGV0IGJhc2VUaW1lID0gcmVhZFVpbnQzMih0ZmR0LCA0KTtcbiAgICAgICAgaWYgKHZlcnNpb24gPT09IDEpIHtcbiAgICAgICAgICAvLyBJZiB2YWx1ZSBpcyB0b28gbGFyZ2UsIGFzc3VtZSBzaWduZWQgNjQtYml0LiBOZWdhdGl2ZSB0cmFjayBmcmFnbWVudCBkZWNvZGUgdGltZXMgYXJlIGludmFsaWQsIGJ1dCB0aGV5IGV4aXN0IGluIHRoZSB3aWxkLlxuICAgICAgICAgIC8vIFRoaXMgcHJldmVudHMgbGFyZ2UgdmFsdWVzIGZyb20gYmVpbmcgdXNlZCBmb3IgaW5pdFBUUywgd2hpY2ggY2FuIGNhdXNlIHBsYXlsaXN0IHN5bmMgaXNzdWVzLlxuICAgICAgICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS92aWRlby1kZXYvaGxzLmpzL2lzc3Vlcy81MzAzXG4gICAgICAgICAgaWYgKGJhc2VUaW1lID09PSBVSU5UMzJfTUFYJDEpIHtcbiAgICAgICAgICAgIGxvZ2dlci53YXJuKGBbbXA0LWRlbXV4ZXJdOiBJZ25vcmluZyBhc3N1bWVkIGludmFsaWQgc2lnbmVkIDY0LWJpdCB0cmFjayBmcmFnbWVudCBkZWNvZGUgdGltZWApO1xuICAgICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgICB9XG4gICAgICAgICAgYmFzZVRpbWUgKj0gVUlOVDMyX01BWCQxICsgMTtcbiAgICAgICAgICBiYXNlVGltZSArPSByZWFkVWludDMyKHRmZHQsIDgpO1xuICAgICAgICB9XG4gICAgICAgIC8vIGFzc3VtZSBhIDkwa0h6IGNsb2NrIGlmIG5vIHRpbWVzY2FsZSB3YXMgc3BlY2lmaWVkXG4gICAgICAgIGNvbnN0IHNjYWxlID0gdHJhY2sudGltZXNjYWxlIHx8IDkwZTM7XG4gICAgICAgIC8vIGNvbnZlcnQgYmFzZSB0aW1lIHRvIHNlY29uZHNcbiAgICAgICAgY29uc3Qgc3RhcnRUaW1lID0gYmFzZVRpbWUgLyBzY2FsZTtcbiAgICAgICAgaWYgKGlzRmluaXRlTnVtYmVyKHN0YXJ0VGltZSkgJiYgKHJlc3VsdCA9PT0gbnVsbCB8fCBzdGFydFRpbWUgPCByZXN1bHQpKSB7XG4gICAgICAgICAgcmV0dXJuIHN0YXJ0VGltZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9LCBudWxsKTtcbiAgICBpZiAoc3RhcnQgIT09IG51bGwgJiYgaXNGaW5pdGVOdW1iZXIoc3RhcnQpICYmIChyZXN1bHQgPT09IG51bGwgfHwgc3RhcnQgPCByZXN1bHQpKSB7XG4gICAgICByZXR1cm4gc3RhcnQ7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH0sIG51bGwpO1xufVxuXG4vKlxuICBGb3IgUmVmZXJlbmNlOlxuICBhbGlnbmVkKDgpIGNsYXNzIFRyYWNrRnJhZ21lbnRIZWFkZXJCb3hcbiAgICAgICAgICAgZXh0ZW5kcyBGdWxsQm94KOKAmHRmaGTigJksIDAsIHRmX2ZsYWdzKXtcbiAgICAgdW5zaWduZWQgaW50KDMyKSAgdHJhY2tfSUQ7XG4gICAgIC8vIGFsbCB0aGUgZm9sbG93aW5nIGFyZSBvcHRpb25hbCBmaWVsZHNcbiAgICAgdW5zaWduZWQgaW50KDY0KSAgYmFzZV9kYXRhX29mZnNldDtcbiAgICAgdW5zaWduZWQgaW50KDMyKSAgc2FtcGxlX2Rlc2NyaXB0aW9uX2luZGV4O1xuICAgICB1bnNpZ25lZCBpbnQoMzIpICBkZWZhdWx0X3NhbXBsZV9kdXJhdGlvbjtcbiAgICAgdW5zaWduZWQgaW50KDMyKSAgZGVmYXVsdF9zYW1wbGVfc2l6ZTtcbiAgICAgdW5zaWduZWQgaW50KDMyKSAgZGVmYXVsdF9zYW1wbGVfZmxhZ3NcbiAgfVxuICovXG5mdW5jdGlvbiBnZXREdXJhdGlvbihkYXRhLCBpbml0RGF0YSkge1xuICBsZXQgcmF3RHVyYXRpb24gPSAwO1xuICBsZXQgdmlkZW9EdXJhdGlvbiA9IDA7XG4gIGxldCBhdWRpb0R1cmF0aW9uID0gMDtcbiAgY29uc3QgdHJhZnMgPSBmaW5kQm94KGRhdGEsIFsnbW9vZicsICd0cmFmJ10pO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHRyYWZzLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3QgdHJhZiA9IHRyYWZzW2ldO1xuICAgIC8vIFRoZXJlIGlzIG9ubHkgb25lIHRmaGQgJiB0cnVuIHBlciB0cmFmXG4gICAgLy8gVGhpcyBpcyB0cnVlIGZvciBDTUFGIHN0eWxlIGNvbnRlbnQsIGFuZCB3ZSBzaG91bGQgcGVyaGFwcyBjaGVjayB0aGUgZnR5cFxuICAgIC8vIGFuZCBvbmx5IGxvb2sgZm9yIGEgc2luZ2xlIHRydW4gdGhlbiwgYnV0IGZvciBJU09CTUZGIHdlIHNob3VsZCBjaGVja1xuICAgIC8vIGZvciBtdWx0aXBsZSB0cmFjayBydW5zLlxuICAgIGNvbnN0IHRmaGQgPSBmaW5kQm94KHRyYWYsIFsndGZoZCddKVswXTtcbiAgICAvLyBnZXQgdGhlIHRyYWNrIGlkIGZyb20gdGhlIHRmaGRcbiAgICBjb25zdCBpZCA9IHJlYWRVaW50MzIodGZoZCwgNCk7XG4gICAgY29uc3QgdHJhY2sgPSBpbml0RGF0YVtpZF07XG4gICAgaWYgKCF0cmFjaykge1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGNvbnN0IHRyYWNrRGVmYXVsdCA9IHRyYWNrLmRlZmF1bHQ7XG4gICAgY29uc3QgdGZoZEZsYWdzID0gcmVhZFVpbnQzMih0ZmhkLCAwKSB8ICh0cmFja0RlZmF1bHQgPT0gbnVsbCA/IHZvaWQgMCA6IHRyYWNrRGVmYXVsdC5mbGFncyk7XG4gICAgbGV0IHNhbXBsZUR1cmF0aW9uID0gdHJhY2tEZWZhdWx0ID09IG51bGwgPyB2b2lkIDAgOiB0cmFja0RlZmF1bHQuZHVyYXRpb247XG4gICAgaWYgKHRmaGRGbGFncyAmIDB4MDAwMDA4KSB7XG4gICAgICAvLyAweDAwMDAwOCBpbmRpY2F0ZXMgdGhlIHByZXNlbmNlIG9mIHRoZSBkZWZhdWx0X3NhbXBsZV9kdXJhdGlvbiBmaWVsZFxuICAgICAgaWYgKHRmaGRGbGFncyAmIDB4MDAwMDAyKSB7XG4gICAgICAgIC8vIDB4MDAwMDAyIGluZGljYXRlcyB0aGUgcHJlc2VuY2Ugb2YgdGhlIHNhbXBsZV9kZXNjcmlwdGlvbl9pbmRleCBmaWVsZCwgd2hpY2ggcHJlY2VkZXMgZGVmYXVsdF9zYW1wbGVfZHVyYXRpb25cbiAgICAgICAgLy8gSWYgcHJlc2VudCwgdGhlIGRlZmF1bHRfc2FtcGxlX2R1cmF0aW9uIGV4aXN0cyBhdCBieXRlIG9mZnNldCAxMlxuICAgICAgICBzYW1wbGVEdXJhdGlvbiA9IHJlYWRVaW50MzIodGZoZCwgMTIpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gT3RoZXJ3aXNlLCB0aGUgZHVyYXRpb24gaXMgYXQgYnl0ZSBvZmZzZXQgOFxuICAgICAgICBzYW1wbGVEdXJhdGlvbiA9IHJlYWRVaW50MzIodGZoZCwgOCk7XG4gICAgICB9XG4gICAgfVxuICAgIC8vIGFzc3VtZSBhIDkwa0h6IGNsb2NrIGlmIG5vIHRpbWVzY2FsZSB3YXMgc3BlY2lmaWVkXG4gICAgY29uc3QgdGltZXNjYWxlID0gdHJhY2sudGltZXNjYWxlIHx8IDkwZTM7XG4gICAgY29uc3QgdHJ1bnMgPSBmaW5kQm94KHRyYWYsIFsndHJ1biddKTtcbiAgICBmb3IgKGxldCBqID0gMDsgaiA8IHRydW5zLmxlbmd0aDsgaisrKSB7XG4gICAgICByYXdEdXJhdGlvbiA9IGNvbXB1dGVSYXdEdXJhdGlvbkZyb21TYW1wbGVzKHRydW5zW2pdKTtcbiAgICAgIGlmICghcmF3RHVyYXRpb24gJiYgc2FtcGxlRHVyYXRpb24pIHtcbiAgICAgICAgY29uc3Qgc2FtcGxlQ291bnQgPSByZWFkVWludDMyKHRydW5zW2pdLCA0KTtcbiAgICAgICAgcmF3RHVyYXRpb24gPSBzYW1wbGVEdXJhdGlvbiAqIHNhbXBsZUNvdW50O1xuICAgICAgfVxuICAgICAgaWYgKHRyYWNrLnR5cGUgPT09IEVsZW1lbnRhcnlTdHJlYW1UeXBlcy5WSURFTykge1xuICAgICAgICB2aWRlb0R1cmF0aW9uICs9IHJhd0R1cmF0aW9uIC8gdGltZXNjYWxlO1xuICAgICAgfSBlbHNlIGlmICh0cmFjay50eXBlID09PSBFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU8pIHtcbiAgICAgICAgYXVkaW9EdXJhdGlvbiArPSByYXdEdXJhdGlvbiAvIHRpbWVzY2FsZTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgaWYgKHZpZGVvRHVyYXRpb24gPT09IDAgJiYgYXVkaW9EdXJhdGlvbiA9PT0gMCkge1xuICAgIC8vIElmIGR1cmF0aW9uIHNhbXBsZXMgYXJlIG5vdCBhdmFpbGFibGUgaW4gdGhlIHRyYWYgdXNlIHNpZHggc3Vic2VnbWVudF9kdXJhdGlvblxuICAgIGxldCBzaWR4TWluU3RhcnQgPSBJbmZpbml0eTtcbiAgICBsZXQgc2lkeE1heEVuZCA9IDA7XG4gICAgbGV0IHNpZHhEdXJhdGlvbiA9IDA7XG4gICAgY29uc3Qgc2lkeHMgPSBmaW5kQm94KGRhdGEsIFsnc2lkeCddKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNpZHhzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBzaWR4ID0gcGFyc2VTZWdtZW50SW5kZXgoc2lkeHNbaV0pO1xuICAgICAgaWYgKHNpZHggIT0gbnVsbCAmJiBzaWR4LnJlZmVyZW5jZXMpIHtcbiAgICAgICAgc2lkeE1pblN0YXJ0ID0gTWF0aC5taW4oc2lkeE1pblN0YXJ0LCBzaWR4LmVhcmxpZXN0UHJlc2VudGF0aW9uVGltZSAvIHNpZHgudGltZXNjYWxlKTtcbiAgICAgICAgY29uc3Qgc3ViU2VnbWVudER1cmF0aW9uID0gc2lkeC5yZWZlcmVuY2VzLnJlZHVjZSgoZHVyLCByZWYpID0+IGR1ciArIHJlZi5pbmZvLmR1cmF0aW9uIHx8IDAsIDApO1xuICAgICAgICBzaWR4TWF4RW5kID0gTWF0aC5tYXgoc2lkeE1heEVuZCwgc3ViU2VnbWVudER1cmF0aW9uICsgc2lkeC5lYXJsaWVzdFByZXNlbnRhdGlvblRpbWUgLyBzaWR4LnRpbWVzY2FsZSk7XG4gICAgICAgIHNpZHhEdXJhdGlvbiA9IHNpZHhNYXhFbmQgLSBzaWR4TWluU3RhcnQ7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChzaWR4RHVyYXRpb24gJiYgaXNGaW5pdGVOdW1iZXIoc2lkeER1cmF0aW9uKSkge1xuICAgICAgcmV0dXJuIHNpZHhEdXJhdGlvbjtcbiAgICB9XG4gIH1cbiAgaWYgKHZpZGVvRHVyYXRpb24pIHtcbiAgICByZXR1cm4gdmlkZW9EdXJhdGlvbjtcbiAgfVxuICByZXR1cm4gYXVkaW9EdXJhdGlvbjtcbn1cblxuLypcbiAgRm9yIFJlZmVyZW5jZTpcbiAgYWxpZ25lZCg4KSBjbGFzcyBUcmFja1J1bkJveFxuICAgICAgICAgICBleHRlbmRzIEZ1bGxCb3go4oCYdHJ1buKAmSwgdmVyc2lvbiwgdHJfZmxhZ3MpIHtcbiAgICAgdW5zaWduZWQgaW50KDMyKSAgc2FtcGxlX2NvdW50O1xuICAgICAvLyB0aGUgZm9sbG93aW5nIGFyZSBvcHRpb25hbCBmaWVsZHNcbiAgICAgc2lnbmVkIGludCgzMikgZGF0YV9vZmZzZXQ7XG4gICAgIHVuc2lnbmVkIGludCgzMikgIGZpcnN0X3NhbXBsZV9mbGFncztcbiAgICAgLy8gYWxsIGZpZWxkcyBpbiB0aGUgZm9sbG93aW5nIGFycmF5IGFyZSBvcHRpb25hbFxuICAgICB7XG4gICAgICAgIHVuc2lnbmVkIGludCgzMikgIHNhbXBsZV9kdXJhdGlvbjtcbiAgICAgICAgdW5zaWduZWQgaW50KDMyKSAgc2FtcGxlX3NpemU7XG4gICAgICAgIHVuc2lnbmVkIGludCgzMikgIHNhbXBsZV9mbGFnc1xuICAgICAgICBpZiAodmVyc2lvbiA9PSAwKVxuICAgICAgICAgICB7IHVuc2lnbmVkIGludCgzMilcbiAgICAgICAgZWxzZVxuICAgICAgICAgICB7IHNpZ25lZCBpbnQoMzIpXG4gICAgIH1bIHNhbXBsZV9jb3VudCBdXG4gIH1cbiAqL1xuZnVuY3Rpb24gY29tcHV0ZVJhd0R1cmF0aW9uRnJvbVNhbXBsZXModHJ1bikge1xuICBjb25zdCBmbGFncyA9IHJlYWRVaW50MzIodHJ1biwgMCk7XG4gIC8vIEZsYWdzIGFyZSBhdCBvZmZzZXQgMCwgbm9uLW9wdGlvbmFsIHNhbXBsZV9jb3VudCBpcyBhdCBvZmZzZXQgNC4gVGhlcmVmb3JlIHdlIHN0YXJ0IDggYnl0ZXMgaW4uXG4gIC8vIEVhY2ggZmllbGQgaXMgYW4gaW50MzIsIHdoaWNoIGlzIDQgYnl0ZXNcbiAgbGV0IG9mZnNldCA9IDg7XG4gIC8vIGRhdGEtb2Zmc2V0LXByZXNlbnQgZmxhZ1xuICBpZiAoZmxhZ3MgJiAweDAwMDAwMSkge1xuICAgIG9mZnNldCArPSA0O1xuICB9XG4gIC8vIGZpcnN0LXNhbXBsZS1mbGFncy1wcmVzZW50IGZsYWdcbiAgaWYgKGZsYWdzICYgMHgwMDAwMDQpIHtcbiAgICBvZmZzZXQgKz0gNDtcbiAgfVxuICBsZXQgZHVyYXRpb24gPSAwO1xuICBjb25zdCBzYW1wbGVDb3VudCA9IHJlYWRVaW50MzIodHJ1biwgNCk7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgc2FtcGxlQ291bnQ7IGkrKykge1xuICAgIC8vIHNhbXBsZS1kdXJhdGlvbi1wcmVzZW50IGZsYWdcbiAgICBpZiAoZmxhZ3MgJiAweDAwMDEwMCkge1xuICAgICAgY29uc3Qgc2FtcGxlRHVyYXRpb24gPSByZWFkVWludDMyKHRydW4sIG9mZnNldCk7XG4gICAgICBkdXJhdGlvbiArPSBzYW1wbGVEdXJhdGlvbjtcbiAgICAgIG9mZnNldCArPSA0O1xuICAgIH1cbiAgICAvLyBzYW1wbGUtc2l6ZS1wcmVzZW50IGZsYWdcbiAgICBpZiAoZmxhZ3MgJiAweDAwMDIwMCkge1xuICAgICAgb2Zmc2V0ICs9IDQ7XG4gICAgfVxuICAgIC8vIHNhbXBsZS1mbGFncy1wcmVzZW50IGZsYWdcbiAgICBpZiAoZmxhZ3MgJiAweDAwMDQwMCkge1xuICAgICAgb2Zmc2V0ICs9IDQ7XG4gICAgfVxuICAgIC8vIHNhbXBsZS1jb21wb3NpdGlvbi10aW1lLW9mZnNldHMtcHJlc2VudCBmbGFnXG4gICAgaWYgKGZsYWdzICYgMHgwMDA4MDApIHtcbiAgICAgIG9mZnNldCArPSA0O1xuICAgIH1cbiAgfVxuICByZXR1cm4gZHVyYXRpb247XG59XG5mdW5jdGlvbiBvZmZzZXRTdGFydERUUyhpbml0RGF0YSwgZm1wNCwgdGltZU9mZnNldCkge1xuICBmaW5kQm94KGZtcDQsIFsnbW9vZicsICd0cmFmJ10pLmZvckVhY2godHJhZiA9PiB7XG4gICAgZmluZEJveCh0cmFmLCBbJ3RmaGQnXSkuZm9yRWFjaCh0ZmhkID0+IHtcbiAgICAgIC8vIGdldCB0aGUgdHJhY2sgaWQgZnJvbSB0aGUgdGZoZFxuICAgICAgY29uc3QgaWQgPSByZWFkVWludDMyKHRmaGQsIDQpO1xuICAgICAgY29uc3QgdHJhY2sgPSBpbml0RGF0YVtpZF07XG4gICAgICBpZiAoIXRyYWNrKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIC8vIGFzc3VtZSBhIDkwa0h6IGNsb2NrIGlmIG5vIHRpbWVzY2FsZSB3YXMgc3BlY2lmaWVkXG4gICAgICBjb25zdCB0aW1lc2NhbGUgPSB0cmFjay50aW1lc2NhbGUgfHwgOTBlMztcbiAgICAgIC8vIGdldCB0aGUgYmFzZSBtZWRpYSBkZWNvZGUgdGltZSBmcm9tIHRoZSB0ZmR0XG4gICAgICBmaW5kQm94KHRyYWYsIFsndGZkdCddKS5mb3JFYWNoKHRmZHQgPT4ge1xuICAgICAgICBjb25zdCB2ZXJzaW9uID0gdGZkdFswXTtcbiAgICAgICAgY29uc3Qgb2Zmc2V0ID0gdGltZU9mZnNldCAqIHRpbWVzY2FsZTtcbiAgICAgICAgaWYgKG9mZnNldCkge1xuICAgICAgICAgIGxldCBiYXNlTWVkaWFEZWNvZGVUaW1lID0gcmVhZFVpbnQzMih0ZmR0LCA0KTtcbiAgICAgICAgICBpZiAodmVyc2lvbiA9PT0gMCkge1xuICAgICAgICAgICAgYmFzZU1lZGlhRGVjb2RlVGltZSAtPSBvZmZzZXQ7XG4gICAgICAgICAgICBiYXNlTWVkaWFEZWNvZGVUaW1lID0gTWF0aC5tYXgoYmFzZU1lZGlhRGVjb2RlVGltZSwgMCk7XG4gICAgICAgICAgICB3cml0ZVVpbnQzMih0ZmR0LCA0LCBiYXNlTWVkaWFEZWNvZGVUaW1lKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgYmFzZU1lZGlhRGVjb2RlVGltZSAqPSBNYXRoLnBvdygyLCAzMik7XG4gICAgICAgICAgICBiYXNlTWVkaWFEZWNvZGVUaW1lICs9IHJlYWRVaW50MzIodGZkdCwgOCk7XG4gICAgICAgICAgICBiYXNlTWVkaWFEZWNvZGVUaW1lIC09IG9mZnNldDtcbiAgICAgICAgICAgIGJhc2VNZWRpYURlY29kZVRpbWUgPSBNYXRoLm1heChiYXNlTWVkaWFEZWNvZGVUaW1lLCAwKTtcbiAgICAgICAgICAgIGNvbnN0IHVwcGVyID0gTWF0aC5mbG9vcihiYXNlTWVkaWFEZWNvZGVUaW1lIC8gKFVJTlQzMl9NQVgkMSArIDEpKTtcbiAgICAgICAgICAgIGNvbnN0IGxvd2VyID0gTWF0aC5mbG9vcihiYXNlTWVkaWFEZWNvZGVUaW1lICUgKFVJTlQzMl9NQVgkMSArIDEpKTtcbiAgICAgICAgICAgIHdyaXRlVWludDMyKHRmZHQsIDQsIHVwcGVyKTtcbiAgICAgICAgICAgIHdyaXRlVWludDMyKHRmZHQsIDgsIGxvd2VyKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH0pO1xuICB9KTtcbn1cblxuLy8gVE9ETzogQ2hlY2sgaWYgdGhlIGxhc3QgbW9vZittZGF0IHBhaXIgaXMgcGFydCBvZiB0aGUgdmFsaWQgcmFuZ2VcbmZ1bmN0aW9uIHNlZ21lbnRWYWxpZFJhbmdlKGRhdGEpIHtcbiAgY29uc3Qgc2VnbWVudGVkUmFuZ2UgPSB7XG4gICAgdmFsaWQ6IG51bGwsXG4gICAgcmVtYWluZGVyOiBudWxsXG4gIH07XG4gIGNvbnN0IG1vb2ZzID0gZmluZEJveChkYXRhLCBbJ21vb2YnXSk7XG4gIGlmIChtb29mcy5sZW5ndGggPCAyKSB7XG4gICAgc2VnbWVudGVkUmFuZ2UucmVtYWluZGVyID0gZGF0YTtcbiAgICByZXR1cm4gc2VnbWVudGVkUmFuZ2U7XG4gIH1cbiAgY29uc3QgbGFzdCA9IG1vb2ZzW21vb2ZzLmxlbmd0aCAtIDFdO1xuICAvLyBPZmZzZXQgYnkgOCBieXRlczsgZmluZEJveCBvZmZzZXRzIHRoZSBzdGFydCBieSBhcyBtdWNoXG4gIHNlZ21lbnRlZFJhbmdlLnZhbGlkID0gc2xpY2VVaW50OChkYXRhLCAwLCBsYXN0LmJ5dGVPZmZzZXQgLSA4KTtcbiAgc2VnbWVudGVkUmFuZ2UucmVtYWluZGVyID0gc2xpY2VVaW50OChkYXRhLCBsYXN0LmJ5dGVPZmZzZXQgLSA4KTtcbiAgcmV0dXJuIHNlZ21lbnRlZFJhbmdlO1xufVxuZnVuY3Rpb24gYXBwZW5kVWludDhBcnJheShkYXRhMSwgZGF0YTIpIHtcbiAgY29uc3QgdGVtcCA9IG5ldyBVaW50OEFycmF5KGRhdGExLmxlbmd0aCArIGRhdGEyLmxlbmd0aCk7XG4gIHRlbXAuc2V0KGRhdGExKTtcbiAgdGVtcC5zZXQoZGF0YTIsIGRhdGExLmxlbmd0aCk7XG4gIHJldHVybiB0ZW1wO1xufVxuZnVuY3Rpb24gcGFyc2VTYW1wbGVzKHRpbWVPZmZzZXQsIHRyYWNrKSB7XG4gIGNvbnN0IHNlaVNhbXBsZXMgPSBbXTtcbiAgY29uc3QgdmlkZW9EYXRhID0gdHJhY2suc2FtcGxlcztcbiAgY29uc3QgdGltZXNjYWxlID0gdHJhY2sudGltZXNjYWxlO1xuICBjb25zdCB0cmFja0lkID0gdHJhY2suaWQ7XG4gIGxldCBpc0hFVkNGbGF2b3IgPSBmYWxzZTtcbiAgY29uc3QgbW9vZnMgPSBmaW5kQm94KHZpZGVvRGF0YSwgWydtb29mJ10pO1xuICBtb29mcy5tYXAobW9vZiA9PiB7XG4gICAgY29uc3QgbW9vZk9mZnNldCA9IG1vb2YuYnl0ZU9mZnNldCAtIDg7XG4gICAgY29uc3QgdHJhZnMgPSBmaW5kQm94KG1vb2YsIFsndHJhZiddKTtcbiAgICB0cmFmcy5tYXAodHJhZiA9PiB7XG4gICAgICAvLyBnZXQgdGhlIGJhc2UgbWVkaWEgZGVjb2RlIHRpbWUgZnJvbSB0aGUgdGZkdFxuICAgICAgY29uc3QgYmFzZVRpbWUgPSBmaW5kQm94KHRyYWYsIFsndGZkdCddKS5tYXAodGZkdCA9PiB7XG4gICAgICAgIGNvbnN0IHZlcnNpb24gPSB0ZmR0WzBdO1xuICAgICAgICBsZXQgcmVzdWx0ID0gcmVhZFVpbnQzMih0ZmR0LCA0KTtcbiAgICAgICAgaWYgKHZlcnNpb24gPT09IDEpIHtcbiAgICAgICAgICByZXN1bHQgKj0gTWF0aC5wb3coMiwgMzIpO1xuICAgICAgICAgIHJlc3VsdCArPSByZWFkVWludDMyKHRmZHQsIDgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiByZXN1bHQgLyB0aW1lc2NhbGU7XG4gICAgICB9KVswXTtcbiAgICAgIGlmIChiYXNlVGltZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHRpbWVPZmZzZXQgPSBiYXNlVGltZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmaW5kQm94KHRyYWYsIFsndGZoZCddKS5tYXAodGZoZCA9PiB7XG4gICAgICAgIGNvbnN0IGlkID0gcmVhZFVpbnQzMih0ZmhkLCA0KTtcbiAgICAgICAgY29uc3QgdGZoZEZsYWdzID0gcmVhZFVpbnQzMih0ZmhkLCAwKSAmIDB4ZmZmZmZmO1xuICAgICAgICBjb25zdCBiYXNlRGF0YU9mZnNldFByZXNlbnQgPSAodGZoZEZsYWdzICYgMHgwMDAwMDEpICE9PSAwO1xuICAgICAgICBjb25zdCBzYW1wbGVEZXNjcmlwdGlvbkluZGV4UHJlc2VudCA9ICh0ZmhkRmxhZ3MgJiAweDAwMDAwMikgIT09IDA7XG4gICAgICAgIGNvbnN0IGRlZmF1bHRTYW1wbGVEdXJhdGlvblByZXNlbnQgPSAodGZoZEZsYWdzICYgMHgwMDAwMDgpICE9PSAwO1xuICAgICAgICBsZXQgZGVmYXVsdFNhbXBsZUR1cmF0aW9uID0gMDtcbiAgICAgICAgY29uc3QgZGVmYXVsdFNhbXBsZVNpemVQcmVzZW50ID0gKHRmaGRGbGFncyAmIDB4MDAwMDEwKSAhPT0gMDtcbiAgICAgICAgbGV0IGRlZmF1bHRTYW1wbGVTaXplID0gMDtcbiAgICAgICAgY29uc3QgZGVmYXVsdFNhbXBsZUZsYWdzUHJlc2VudCA9ICh0ZmhkRmxhZ3MgJiAweDAwMDAyMCkgIT09IDA7XG4gICAgICAgIGxldCB0ZmhkT2Zmc2V0ID0gODtcbiAgICAgICAgaWYgKGlkID09PSB0cmFja0lkKSB7XG4gICAgICAgICAgaWYgKGJhc2VEYXRhT2Zmc2V0UHJlc2VudCkge1xuICAgICAgICAgICAgdGZoZE9mZnNldCArPSA4O1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoc2FtcGxlRGVzY3JpcHRpb25JbmRleFByZXNlbnQpIHtcbiAgICAgICAgICAgIHRmaGRPZmZzZXQgKz0gNDtcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKGRlZmF1bHRTYW1wbGVEdXJhdGlvblByZXNlbnQpIHtcbiAgICAgICAgICAgIGRlZmF1bHRTYW1wbGVEdXJhdGlvbiA9IHJlYWRVaW50MzIodGZoZCwgdGZoZE9mZnNldCk7XG4gICAgICAgICAgICB0ZmhkT2Zmc2V0ICs9IDQ7XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmIChkZWZhdWx0U2FtcGxlU2l6ZVByZXNlbnQpIHtcbiAgICAgICAgICAgIGRlZmF1bHRTYW1wbGVTaXplID0gcmVhZFVpbnQzMih0ZmhkLCB0ZmhkT2Zmc2V0KTtcbiAgICAgICAgICAgIHRmaGRPZmZzZXQgKz0gNDtcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKGRlZmF1bHRTYW1wbGVGbGFnc1ByZXNlbnQpIHtcbiAgICAgICAgICAgIHRmaGRPZmZzZXQgKz0gNDtcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKHRyYWNrLnR5cGUgPT09ICd2aWRlbycpIHtcbiAgICAgICAgICAgIGlzSEVWQ0ZsYXZvciA9IGlzSEVWQyh0cmFjay5jb2RlYyk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGZpbmRCb3godHJhZiwgWyd0cnVuJ10pLm1hcCh0cnVuID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHZlcnNpb24gPSB0cnVuWzBdO1xuICAgICAgICAgICAgY29uc3QgZmxhZ3MgPSByZWFkVWludDMyKHRydW4sIDApICYgMHhmZmZmZmY7XG4gICAgICAgICAgICBjb25zdCBkYXRhT2Zmc2V0UHJlc2VudCA9IChmbGFncyAmIDB4MDAwMDAxKSAhPT0gMDtcbiAgICAgICAgICAgIGxldCBkYXRhT2Zmc2V0ID0gMDtcbiAgICAgICAgICAgIGNvbnN0IGZpcnN0U2FtcGxlRmxhZ3NQcmVzZW50ID0gKGZsYWdzICYgMHgwMDAwMDQpICE9PSAwO1xuICAgICAgICAgICAgY29uc3Qgc2FtcGxlRHVyYXRpb25QcmVzZW50ID0gKGZsYWdzICYgMHgwMDAxMDApICE9PSAwO1xuICAgICAgICAgICAgbGV0IHNhbXBsZUR1cmF0aW9uID0gMDtcbiAgICAgICAgICAgIGNvbnN0IHNhbXBsZVNpemVQcmVzZW50ID0gKGZsYWdzICYgMHgwMDAyMDApICE9PSAwO1xuICAgICAgICAgICAgbGV0IHNhbXBsZVNpemUgPSAwO1xuICAgICAgICAgICAgY29uc3Qgc2FtcGxlRmxhZ3NQcmVzZW50ID0gKGZsYWdzICYgMHgwMDA0MDApICE9PSAwO1xuICAgICAgICAgICAgY29uc3Qgc2FtcGxlQ29tcG9zaXRpb25PZmZzZXRzUHJlc2VudCA9IChmbGFncyAmIDB4MDAwODAwKSAhPT0gMDtcbiAgICAgICAgICAgIGxldCBjb21wb3NpdGlvbk9mZnNldCA9IDA7XG4gICAgICAgICAgICBjb25zdCBzYW1wbGVDb3VudCA9IHJlYWRVaW50MzIodHJ1biwgNCk7XG4gICAgICAgICAgICBsZXQgdHJ1bk9mZnNldCA9IDg7IC8vIHBhc3QgdmVyc2lvbiwgZmxhZ3MsIGFuZCBzYW1wbGUgY291bnRcblxuICAgICAgICAgICAgaWYgKGRhdGFPZmZzZXRQcmVzZW50KSB7XG4gICAgICAgICAgICAgIGRhdGFPZmZzZXQgPSByZWFkVWludDMyKHRydW4sIHRydW5PZmZzZXQpO1xuICAgICAgICAgICAgICB0cnVuT2Zmc2V0ICs9IDQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoZmlyc3RTYW1wbGVGbGFnc1ByZXNlbnQpIHtcbiAgICAgICAgICAgICAgdHJ1bk9mZnNldCArPSA0O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbGV0IHNhbXBsZU9mZnNldCA9IGRhdGFPZmZzZXQgKyBtb29mT2Zmc2V0O1xuICAgICAgICAgICAgZm9yIChsZXQgaXggPSAwOyBpeCA8IHNhbXBsZUNvdW50OyBpeCsrKSB7XG4gICAgICAgICAgICAgIGlmIChzYW1wbGVEdXJhdGlvblByZXNlbnQpIHtcbiAgICAgICAgICAgICAgICBzYW1wbGVEdXJhdGlvbiA9IHJlYWRVaW50MzIodHJ1biwgdHJ1bk9mZnNldCk7XG4gICAgICAgICAgICAgICAgdHJ1bk9mZnNldCArPSA0O1xuICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHNhbXBsZUR1cmF0aW9uID0gZGVmYXVsdFNhbXBsZUR1cmF0aW9uO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChzYW1wbGVTaXplUHJlc2VudCkge1xuICAgICAgICAgICAgICAgIHNhbXBsZVNpemUgPSByZWFkVWludDMyKHRydW4sIHRydW5PZmZzZXQpO1xuICAgICAgICAgICAgICAgIHRydW5PZmZzZXQgKz0gNDtcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBzYW1wbGVTaXplID0gZGVmYXVsdFNhbXBsZVNpemU7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgaWYgKHNhbXBsZUZsYWdzUHJlc2VudCkge1xuICAgICAgICAgICAgICAgIHRydW5PZmZzZXQgKz0gNDtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZiAoc2FtcGxlQ29tcG9zaXRpb25PZmZzZXRzUHJlc2VudCkge1xuICAgICAgICAgICAgICAgIGlmICh2ZXJzaW9uID09PSAwKSB7XG4gICAgICAgICAgICAgICAgICBjb21wb3NpdGlvbk9mZnNldCA9IHJlYWRVaW50MzIodHJ1biwgdHJ1bk9mZnNldCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIGNvbXBvc2l0aW9uT2Zmc2V0ID0gcmVhZFNpbnQzMih0cnVuLCB0cnVuT2Zmc2V0KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgdHJ1bk9mZnNldCArPSA0O1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmICh0cmFjay50eXBlID09PSBFbGVtZW50YXJ5U3RyZWFtVHlwZXMuVklERU8pIHtcbiAgICAgICAgICAgICAgICBsZXQgbmFsdVRvdGFsU2l6ZSA9IDA7XG4gICAgICAgICAgICAgICAgd2hpbGUgKG5hbHVUb3RhbFNpemUgPCBzYW1wbGVTaXplKSB7XG4gICAgICAgICAgICAgICAgICBjb25zdCBuYWx1U2l6ZSA9IHJlYWRVaW50MzIodmlkZW9EYXRhLCBzYW1wbGVPZmZzZXQpO1xuICAgICAgICAgICAgICAgICAgc2FtcGxlT2Zmc2V0ICs9IDQ7XG4gICAgICAgICAgICAgICAgICBpZiAoaXNTRUlNZXNzYWdlKGlzSEVWQ0ZsYXZvciwgdmlkZW9EYXRhW3NhbXBsZU9mZnNldF0pKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSB2aWRlb0RhdGEuc3ViYXJyYXkoc2FtcGxlT2Zmc2V0LCBzYW1wbGVPZmZzZXQgKyBuYWx1U2l6ZSk7XG4gICAgICAgICAgICAgICAgICAgIHBhcnNlU0VJTWVzc2FnZUZyb21OQUx1KGRhdGEsIGlzSEVWQ0ZsYXZvciA/IDIgOiAxLCB0aW1lT2Zmc2V0ICsgY29tcG9zaXRpb25PZmZzZXQgLyB0aW1lc2NhbGUsIHNlaVNhbXBsZXMpO1xuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgc2FtcGxlT2Zmc2V0ICs9IG5hbHVTaXplO1xuICAgICAgICAgICAgICAgICAgbmFsdVRvdGFsU2l6ZSArPSBuYWx1U2l6ZSArIDQ7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHRpbWVPZmZzZXQgKz0gc2FtcGxlRHVyYXRpb24gLyB0aW1lc2NhbGU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH0pO1xuICB9KTtcbiAgcmV0dXJuIHNlaVNhbXBsZXM7XG59XG5mdW5jdGlvbiBpc0hFVkMoY29kZWMpIHtcbiAgaWYgKCFjb2RlYykge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBjb25zdCBkZWxpbWl0ID0gY29kZWMuaW5kZXhPZignLicpO1xuICBjb25zdCBiYXNlQ29kZWMgPSBkZWxpbWl0IDwgMCA/IGNvZGVjIDogY29kZWMuc3Vic3RyaW5nKDAsIGRlbGltaXQpO1xuICByZXR1cm4gYmFzZUNvZGVjID09PSAnaHZjMScgfHwgYmFzZUNvZGVjID09PSAnaGV2MScgfHxcbiAgLy8gRG9sYnkgVmlzaW9uXG4gIGJhc2VDb2RlYyA9PT0gJ2R2aDEnIHx8IGJhc2VDb2RlYyA9PT0gJ2R2aGUnO1xufVxuZnVuY3Rpb24gaXNTRUlNZXNzYWdlKGlzSEVWQ0ZsYXZvciwgbmFsdUhlYWRlcikge1xuICBpZiAoaXNIRVZDRmxhdm9yKSB7XG4gICAgY29uc3QgbmFsdVR5cGUgPSBuYWx1SGVhZGVyID4+IDEgJiAweDNmO1xuICAgIHJldHVybiBuYWx1VHlwZSA9PT0gMzkgfHwgbmFsdVR5cGUgPT09IDQwO1xuICB9IGVsc2Uge1xuICAgIGNvbnN0IG5hbHVUeXBlID0gbmFsdUhlYWRlciAmIDB4MWY7XG4gICAgcmV0dXJuIG5hbHVUeXBlID09PSA2O1xuICB9XG59XG5mdW5jdGlvbiBwYXJzZVNFSU1lc3NhZ2VGcm9tTkFMdSh1bmVzY2FwZWREYXRhLCBoZWFkZXJTaXplLCBwdHMsIHNhbXBsZXMpIHtcbiAgY29uc3QgZGF0YSA9IGRpc2NhcmRFUEIodW5lc2NhcGVkRGF0YSk7XG4gIGxldCBzZWlQdHIgPSAwO1xuICAvLyBza2lwIG5hbCBoZWFkZXJcbiAgc2VpUHRyICs9IGhlYWRlclNpemU7XG4gIGxldCBwYXlsb2FkVHlwZSA9IDA7XG4gIGxldCBwYXlsb2FkU2l6ZSA9IDA7XG4gIGxldCBiID0gMDtcbiAgd2hpbGUgKHNlaVB0ciA8IGRhdGEubGVuZ3RoKSB7XG4gICAgcGF5bG9hZFR5cGUgPSAwO1xuICAgIGRvIHtcbiAgICAgIGlmIChzZWlQdHIgPj0gZGF0YS5sZW5ndGgpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBiID0gZGF0YVtzZWlQdHIrK107XG4gICAgICBwYXlsb2FkVHlwZSArPSBiO1xuICAgIH0gd2hpbGUgKGIgPT09IDB4ZmYpO1xuXG4gICAgLy8gUGFyc2UgcGF5bG9hZCBzaXplLlxuICAgIHBheWxvYWRTaXplID0gMDtcbiAgICBkbyB7XG4gICAgICBpZiAoc2VpUHRyID49IGRhdGEubGVuZ3RoKSB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgICAgYiA9IGRhdGFbc2VpUHRyKytdO1xuICAgICAgcGF5bG9hZFNpemUgKz0gYjtcbiAgICB9IHdoaWxlIChiID09PSAweGZmKTtcbiAgICBjb25zdCBsZWZ0T3ZlciA9IGRhdGEubGVuZ3RoIC0gc2VpUHRyO1xuICAgIC8vIENyZWF0ZSBhIHZhcmlhYmxlIHRvIHByb2Nlc3MgdGhlIHBheWxvYWRcbiAgICBsZXQgcGF5UHRyID0gc2VpUHRyO1xuXG4gICAgLy8gSW5jcmVtZW50IHRoZSBzZWlQdHIgdG8gdGhlIGVuZCBvZiB0aGUgcGF5bG9hZFxuICAgIGlmIChwYXlsb2FkU2l6ZSA8IGxlZnRPdmVyKSB7XG4gICAgICBzZWlQdHIgKz0gcGF5bG9hZFNpemU7XG4gICAgfSBlbHNlIGlmIChwYXlsb2FkU2l6ZSA+IGxlZnRPdmVyKSB7XG4gICAgICAvLyBTb21lIHR5cGUgb2YgY29ycnVwdGlvbiBoYXMgaGFwcGVuZWQ/XG4gICAgICBsb2dnZXIuZXJyb3IoYE1hbGZvcm1lZCBTRUkgcGF5bG9hZC4gJHtwYXlsb2FkU2l6ZX0gaXMgdG9vIHNtYWxsLCBvbmx5ICR7bGVmdE92ZXJ9IGJ5dGVzIGxlZnQgdG8gcGFyc2UuYCk7XG4gICAgICAvLyBXZSBtaWdodCBiZSBhYmxlIHRvIHBhcnNlIHNvbWUgZGF0YSwgYnV0IGxldCdzIGJlIHNhZmUgYW5kIGlnbm9yZSBpdC5cbiAgICAgIGJyZWFrO1xuICAgIH1cbiAgICBpZiAocGF5bG9hZFR5cGUgPT09IDQpIHtcbiAgICAgIGNvbnN0IGNvdW50cnlDb2RlID0gZGF0YVtwYXlQdHIrK107XG4gICAgICBpZiAoY291bnRyeUNvZGUgPT09IDE4MSkge1xuICAgICAgICBjb25zdCBwcm92aWRlckNvZGUgPSByZWFkVWludDE2KGRhdGEsIHBheVB0cik7XG4gICAgICAgIHBheVB0ciArPSAyO1xuICAgICAgICBpZiAocHJvdmlkZXJDb2RlID09PSA0OSkge1xuICAgICAgICAgIGNvbnN0IHVzZXJTdHJ1Y3R1cmUgPSByZWFkVWludDMyKGRhdGEsIHBheVB0cik7XG4gICAgICAgICAgcGF5UHRyICs9IDQ7XG4gICAgICAgICAgaWYgKHVzZXJTdHJ1Y3R1cmUgPT09IDB4NDc0MTM5MzQpIHtcbiAgICAgICAgICAgIGNvbnN0IHVzZXJEYXRhVHlwZSA9IGRhdGFbcGF5UHRyKytdO1xuXG4gICAgICAgICAgICAvLyBSYXcgQ0VBLTYwOCBieXRlcyB3cmFwcGVkIGluIENFQS03MDggcGFja2V0XG4gICAgICAgICAgICBpZiAodXNlckRhdGFUeXBlID09PSAzKSB7XG4gICAgICAgICAgICAgIGNvbnN0IGZpcnN0Qnl0ZSA9IGRhdGFbcGF5UHRyKytdO1xuICAgICAgICAgICAgICBjb25zdCB0b3RhbENDcyA9IDB4MWYgJiBmaXJzdEJ5dGU7XG4gICAgICAgICAgICAgIGNvbnN0IGVuYWJsZWQgPSAweDQwICYgZmlyc3RCeXRlO1xuICAgICAgICAgICAgICBjb25zdCB0b3RhbEJ5dGVzID0gZW5hYmxlZCA/IDIgKyB0b3RhbENDcyAqIDMgOiAwO1xuICAgICAgICAgICAgICBjb25zdCBieXRlQXJyYXkgPSBuZXcgVWludDhBcnJheSh0b3RhbEJ5dGVzKTtcbiAgICAgICAgICAgICAgaWYgKGVuYWJsZWQpIHtcbiAgICAgICAgICAgICAgICBieXRlQXJyYXlbMF0gPSBmaXJzdEJ5dGU7XG4gICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDE7IGkgPCB0b3RhbEJ5dGVzOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgIGJ5dGVBcnJheVtpXSA9IGRhdGFbcGF5UHRyKytdO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBzYW1wbGVzLnB1c2goe1xuICAgICAgICAgICAgICAgIHR5cGU6IHVzZXJEYXRhVHlwZSxcbiAgICAgICAgICAgICAgICBwYXlsb2FkVHlwZSxcbiAgICAgICAgICAgICAgICBwdHMsXG4gICAgICAgICAgICAgICAgYnl0ZXM6IGJ5dGVBcnJheVxuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHBheWxvYWRUeXBlID09PSA1KSB7XG4gICAgICBpZiAocGF5bG9hZFNpemUgPiAxNikge1xuICAgICAgICBjb25zdCB1dWlkU3RyQXJyYXkgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCAxNjsgaSsrKSB7XG4gICAgICAgICAgY29uc3QgX2IgPSBkYXRhW3BheVB0cisrXS50b1N0cmluZygxNik7XG4gICAgICAgICAgdXVpZFN0ckFycmF5LnB1c2goX2IubGVuZ3RoID09IDEgPyAnMCcgKyBfYiA6IF9iKTtcbiAgICAgICAgICBpZiAoaSA9PT0gMyB8fCBpID09PSA1IHx8IGkgPT09IDcgfHwgaSA9PT0gOSkge1xuICAgICAgICAgICAgdXVpZFN0ckFycmF5LnB1c2goJy0nKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgbGVuZ3RoID0gcGF5bG9hZFNpemUgLSAxNjtcbiAgICAgICAgY29uc3QgdXNlckRhdGFCeXRlcyA9IG5ldyBVaW50OEFycmF5KGxlbmd0aCk7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICB1c2VyRGF0YUJ5dGVzW2ldID0gZGF0YVtwYXlQdHIrK107XG4gICAgICAgIH1cbiAgICAgICAgc2FtcGxlcy5wdXNoKHtcbiAgICAgICAgICBwYXlsb2FkVHlwZSxcbiAgICAgICAgICBwdHMsXG4gICAgICAgICAgdXVpZDogdXVpZFN0ckFycmF5LmpvaW4oJycpLFxuICAgICAgICAgIHVzZXJEYXRhOiB1dGY4QXJyYXlUb1N0cih1c2VyRGF0YUJ5dGVzKSxcbiAgICAgICAgICB1c2VyRGF0YUJ5dGVzXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIHJlbW92ZSBFbXVsYXRpb24gUHJldmVudGlvbiBieXRlcyBmcm9tIGEgUkJTUFxuICovXG5mdW5jdGlvbiBkaXNjYXJkRVBCKGRhdGEpIHtcbiAgY29uc3QgbGVuZ3RoID0gZGF0YS5ieXRlTGVuZ3RoO1xuICBjb25zdCBFUEJQb3NpdGlvbnMgPSBbXTtcbiAgbGV0IGkgPSAxO1xuXG4gIC8vIEZpbmQgYWxsIGBFbXVsYXRpb24gUHJldmVudGlvbiBCeXRlc2BcbiAgd2hpbGUgKGkgPCBsZW5ndGggLSAyKSB7XG4gICAgaWYgKGRhdGFbaV0gPT09IDAgJiYgZGF0YVtpICsgMV0gPT09IDAgJiYgZGF0YVtpICsgMl0gPT09IDB4MDMpIHtcbiAgICAgIEVQQlBvc2l0aW9ucy5wdXNoKGkgKyAyKTtcbiAgICAgIGkgKz0gMjtcbiAgICB9IGVsc2Uge1xuICAgICAgaSsrO1xuICAgIH1cbiAgfVxuXG4gIC8vIElmIG5vIEVtdWxhdGlvbiBQcmV2ZW50aW9uIEJ5dGVzIHdlcmUgZm91bmQganVzdCByZXR1cm4gdGhlIG9yaWdpbmFsXG4gIC8vIGFycmF5XG4gIGlmIChFUEJQb3NpdGlvbnMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIGRhdGE7XG4gIH1cblxuICAvLyBDcmVhdGUgYSBuZXcgYXJyYXkgdG8gaG9sZCB0aGUgTkFMIHVuaXQgZGF0YVxuICBjb25zdCBuZXdMZW5ndGggPSBsZW5ndGggLSBFUEJQb3NpdGlvbnMubGVuZ3RoO1xuICBjb25zdCBuZXdEYXRhID0gbmV3IFVpbnQ4QXJyYXkobmV3TGVuZ3RoKTtcbiAgbGV0IHNvdXJjZUluZGV4ID0gMDtcbiAgZm9yIChpID0gMDsgaSA8IG5ld0xlbmd0aDsgc291cmNlSW5kZXgrKywgaSsrKSB7XG4gICAgaWYgKHNvdXJjZUluZGV4ID09PSBFUEJQb3NpdGlvbnNbMF0pIHtcbiAgICAgIC8vIFNraXAgdGhpcyBieXRlXG4gICAgICBzb3VyY2VJbmRleCsrO1xuICAgICAgLy8gUmVtb3ZlIHRoaXMgcG9zaXRpb24gaW5kZXhcbiAgICAgIEVQQlBvc2l0aW9ucy5zaGlmdCgpO1xuICAgIH1cbiAgICBuZXdEYXRhW2ldID0gZGF0YVtzb3VyY2VJbmRleF07XG4gIH1cbiAgcmV0dXJuIG5ld0RhdGE7XG59XG5mdW5jdGlvbiBwYXJzZUVtc2coZGF0YSkge1xuICBjb25zdCB2ZXJzaW9uID0gZGF0YVswXTtcbiAgbGV0IHNjaGVtZUlkVXJpID0gJyc7XG4gIGxldCB2YWx1ZSA9ICcnO1xuICBsZXQgdGltZVNjYWxlID0gMDtcbiAgbGV0IHByZXNlbnRhdGlvblRpbWVEZWx0YSA9IDA7XG4gIGxldCBwcmVzZW50YXRpb25UaW1lID0gMDtcbiAgbGV0IGV2ZW50RHVyYXRpb24gPSAwO1xuICBsZXQgaWQgPSAwO1xuICBsZXQgb2Zmc2V0ID0gMDtcbiAgaWYgKHZlcnNpb24gPT09IDApIHtcbiAgICB3aGlsZSAoYmluMnN0cihkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMSkpICE9PSAnXFwwJykge1xuICAgICAgc2NoZW1lSWRVcmkgKz0gYmluMnN0cihkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMSkpO1xuICAgICAgb2Zmc2V0ICs9IDE7XG4gICAgfVxuICAgIHNjaGVtZUlkVXJpICs9IGJpbjJzdHIoZGF0YS5zdWJhcnJheShvZmZzZXQsIG9mZnNldCArIDEpKTtcbiAgICBvZmZzZXQgKz0gMTtcbiAgICB3aGlsZSAoYmluMnN0cihkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMSkpICE9PSAnXFwwJykge1xuICAgICAgdmFsdWUgKz0gYmluMnN0cihkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMSkpO1xuICAgICAgb2Zmc2V0ICs9IDE7XG4gICAgfVxuICAgIHZhbHVlICs9IGJpbjJzdHIoZGF0YS5zdWJhcnJheShvZmZzZXQsIG9mZnNldCArIDEpKTtcbiAgICBvZmZzZXQgKz0gMTtcbiAgICB0aW1lU2NhbGUgPSByZWFkVWludDMyKGRhdGEsIDEyKTtcbiAgICBwcmVzZW50YXRpb25UaW1lRGVsdGEgPSByZWFkVWludDMyKGRhdGEsIDE2KTtcbiAgICBldmVudER1cmF0aW9uID0gcmVhZFVpbnQzMihkYXRhLCAyMCk7XG4gICAgaWQgPSByZWFkVWludDMyKGRhdGEsIDI0KTtcbiAgICBvZmZzZXQgPSAyODtcbiAgfSBlbHNlIGlmICh2ZXJzaW9uID09PSAxKSB7XG4gICAgb2Zmc2V0ICs9IDQ7XG4gICAgdGltZVNjYWxlID0gcmVhZFVpbnQzMihkYXRhLCBvZmZzZXQpO1xuICAgIG9mZnNldCArPSA0O1xuICAgIGNvbnN0IGxlZnRQcmVzZW50YXRpb25UaW1lID0gcmVhZFVpbnQzMihkYXRhLCBvZmZzZXQpO1xuICAgIG9mZnNldCArPSA0O1xuICAgIGNvbnN0IHJpZ2h0UHJlc2VudGF0aW9uVGltZSA9IHJlYWRVaW50MzIoZGF0YSwgb2Zmc2V0KTtcbiAgICBvZmZzZXQgKz0gNDtcbiAgICBwcmVzZW50YXRpb25UaW1lID0gMiAqKiAzMiAqIGxlZnRQcmVzZW50YXRpb25UaW1lICsgcmlnaHRQcmVzZW50YXRpb25UaW1lO1xuICAgIGlmICghaXNTYWZlSW50ZWdlcihwcmVzZW50YXRpb25UaW1lKSkge1xuICAgICAgcHJlc2VudGF0aW9uVGltZSA9IE51bWJlci5NQVhfU0FGRV9JTlRFR0VSO1xuICAgICAgbG9nZ2VyLndhcm4oJ1ByZXNlbnRhdGlvbiB0aW1lIGV4Y2VlZHMgc2FmZSBpbnRlZ2VyIGxpbWl0IGFuZCB3cmFwcGVkIHRvIG1heCBzYWZlIGludGVnZXIgaW4gcGFyc2luZyBlbXNnIGJveCcpO1xuICAgIH1cbiAgICBldmVudER1cmF0aW9uID0gcmVhZFVpbnQzMihkYXRhLCBvZmZzZXQpO1xuICAgIG9mZnNldCArPSA0O1xuICAgIGlkID0gcmVhZFVpbnQzMihkYXRhLCBvZmZzZXQpO1xuICAgIG9mZnNldCArPSA0O1xuICAgIHdoaWxlIChiaW4yc3RyKGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBvZmZzZXQgKyAxKSkgIT09ICdcXDAnKSB7XG4gICAgICBzY2hlbWVJZFVyaSArPSBiaW4yc3RyKGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBvZmZzZXQgKyAxKSk7XG4gICAgICBvZmZzZXQgKz0gMTtcbiAgICB9XG4gICAgc2NoZW1lSWRVcmkgKz0gYmluMnN0cihkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMSkpO1xuICAgIG9mZnNldCArPSAxO1xuICAgIHdoaWxlIChiaW4yc3RyKGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBvZmZzZXQgKyAxKSkgIT09ICdcXDAnKSB7XG4gICAgICB2YWx1ZSArPSBiaW4yc3RyKGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBvZmZzZXQgKyAxKSk7XG4gICAgICBvZmZzZXQgKz0gMTtcbiAgICB9XG4gICAgdmFsdWUgKz0gYmluMnN0cihkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMSkpO1xuICAgIG9mZnNldCArPSAxO1xuICB9XG4gIGNvbnN0IHBheWxvYWQgPSBkYXRhLnN1YmFycmF5KG9mZnNldCwgZGF0YS5ieXRlTGVuZ3RoKTtcbiAgcmV0dXJuIHtcbiAgICBzY2hlbWVJZFVyaSxcbiAgICB2YWx1ZSxcbiAgICB0aW1lU2NhbGUsXG4gICAgcHJlc2VudGF0aW9uVGltZSxcbiAgICBwcmVzZW50YXRpb25UaW1lRGVsdGEsXG4gICAgZXZlbnREdXJhdGlvbixcbiAgICBpZCxcbiAgICBwYXlsb2FkXG4gIH07XG59XG5mdW5jdGlvbiBtcDRCb3godHlwZSwgLi4ucGF5bG9hZCkge1xuICBjb25zdCBsZW4gPSBwYXlsb2FkLmxlbmd0aDtcbiAgbGV0IHNpemUgPSA4O1xuICBsZXQgaSA9IGxlbjtcbiAgd2hpbGUgKGktLSkge1xuICAgIHNpemUgKz0gcGF5bG9hZFtpXS5ieXRlTGVuZ3RoO1xuICB9XG4gIGNvbnN0IHJlc3VsdCA9IG5ldyBVaW50OEFycmF5KHNpemUpO1xuICByZXN1bHRbMF0gPSBzaXplID4+IDI0ICYgMHhmZjtcbiAgcmVzdWx0WzFdID0gc2l6ZSA+PiAxNiAmIDB4ZmY7XG4gIHJlc3VsdFsyXSA9IHNpemUgPj4gOCAmIDB4ZmY7XG4gIHJlc3VsdFszXSA9IHNpemUgJiAweGZmO1xuICByZXN1bHQuc2V0KHR5cGUsIDQpO1xuICBmb3IgKGkgPSAwLCBzaXplID0gODsgaSA8IGxlbjsgaSsrKSB7XG4gICAgcmVzdWx0LnNldChwYXlsb2FkW2ldLCBzaXplKTtcbiAgICBzaXplICs9IHBheWxvYWRbaV0uYnl0ZUxlbmd0aDtcbiAgfVxuICByZXR1cm4gcmVzdWx0O1xufVxuZnVuY3Rpb24gbXA0cHNzaChzeXN0ZW1JZCwga2V5aWRzLCBkYXRhKSB7XG4gIGlmIChzeXN0ZW1JZC5ieXRlTGVuZ3RoICE9PSAxNikge1xuICAgIHRocm93IG5ldyBSYW5nZUVycm9yKCdJbnZhbGlkIHN5c3RlbSBpZCcpO1xuICB9XG4gIGxldCB2ZXJzaW9uO1xuICBsZXQga2lkcztcbiAgaWYgKGtleWlkcykge1xuICAgIHZlcnNpb24gPSAxO1xuICAgIGtpZHMgPSBuZXcgVWludDhBcnJheShrZXlpZHMubGVuZ3RoICogMTYpO1xuICAgIGZvciAobGV0IGl4ID0gMDsgaXggPCBrZXlpZHMubGVuZ3RoOyBpeCsrKSB7XG4gICAgICBjb25zdCBrID0ga2V5aWRzW2l4XTsgLy8gdWludDhhcnJheVxuICAgICAgaWYgKGsuYnl0ZUxlbmd0aCAhPT0gMTYpIHtcbiAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ0ludmFsaWQga2V5Jyk7XG4gICAgICB9XG4gICAgICBraWRzLnNldChrLCBpeCAqIDE2KTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgdmVyc2lvbiA9IDA7XG4gICAga2lkcyA9IG5ldyBVaW50OEFycmF5KCk7XG4gIH1cbiAgbGV0IGtpZENvdW50O1xuICBpZiAodmVyc2lvbiA+IDApIHtcbiAgICBraWRDb3VudCA9IG5ldyBVaW50OEFycmF5KDQpO1xuICAgIGlmIChrZXlpZHMubGVuZ3RoID4gMCkge1xuICAgICAgbmV3IERhdGFWaWV3KGtpZENvdW50LmJ1ZmZlcikuc2V0VWludDMyKDAsIGtleWlkcy5sZW5ndGgsIGZhbHNlKTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAga2lkQ291bnQgPSBuZXcgVWludDhBcnJheSgpO1xuICB9XG4gIGNvbnN0IGRhdGFTaXplID0gbmV3IFVpbnQ4QXJyYXkoNCk7XG4gIGlmIChkYXRhICYmIGRhdGEuYnl0ZUxlbmd0aCA+IDApIHtcbiAgICBuZXcgRGF0YVZpZXcoZGF0YVNpemUuYnVmZmVyKS5zZXRVaW50MzIoMCwgZGF0YS5ieXRlTGVuZ3RoLCBmYWxzZSk7XG4gIH1cbiAgcmV0dXJuIG1wNEJveChbMTEyLCAxMTUsIDExNSwgMTA0XSwgbmV3IFVpbnQ4QXJyYXkoW3ZlcnNpb24sIDB4MDAsIDB4MDAsIDB4MDAgLy8gRmxhZ3NcbiAgXSksIHN5c3RlbUlkLFxuICAvLyAxNiBieXRlc1xuICBraWRDb3VudCwga2lkcywgZGF0YVNpemUsIGRhdGEgfHwgbmV3IFVpbnQ4QXJyYXkoKSk7XG59XG5mdW5jdGlvbiBwYXJzZU11bHRpUHNzaChpbml0RGF0YSkge1xuICBjb25zdCByZXN1bHRzID0gW107XG4gIGlmIChpbml0RGF0YSBpbnN0YW5jZW9mIEFycmF5QnVmZmVyKSB7XG4gICAgY29uc3QgbGVuZ3RoID0gaW5pdERhdGEuYnl0ZUxlbmd0aDtcbiAgICBsZXQgb2Zmc2V0ID0gMDtcbiAgICB3aGlsZSAob2Zmc2V0ICsgMzIgPCBsZW5ndGgpIHtcbiAgICAgIGNvbnN0IHZpZXcgPSBuZXcgRGF0YVZpZXcoaW5pdERhdGEsIG9mZnNldCk7XG4gICAgICBjb25zdCBwc3NoID0gcGFyc2VQc3NoKHZpZXcpO1xuICAgICAgcmVzdWx0cy5wdXNoKHBzc2gpO1xuICAgICAgb2Zmc2V0ICs9IHBzc2guc2l6ZTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHJlc3VsdHM7XG59XG5mdW5jdGlvbiBwYXJzZVBzc2godmlldykge1xuICBjb25zdCBzaXplID0gdmlldy5nZXRVaW50MzIoMCk7XG4gIGNvbnN0IG9mZnNldCA9IHZpZXcuYnl0ZU9mZnNldDtcbiAgY29uc3QgbGVuZ3RoID0gdmlldy5ieXRlTGVuZ3RoO1xuICBpZiAobGVuZ3RoIDwgc2l6ZSkge1xuICAgIHJldHVybiB7XG4gICAgICBvZmZzZXQsXG4gICAgICBzaXplOiBsZW5ndGhcbiAgICB9O1xuICB9XG4gIGNvbnN0IHR5cGUgPSB2aWV3LmdldFVpbnQzMig0KTtcbiAgaWYgKHR5cGUgIT09IDB4NzA3MzczNjgpIHtcbiAgICByZXR1cm4ge1xuICAgICAgb2Zmc2V0LFxuICAgICAgc2l6ZVxuICAgIH07XG4gIH1cbiAgY29uc3QgdmVyc2lvbiA9IHZpZXcuZ2V0VWludDMyKDgpID4+PiAyNDtcbiAgaWYgKHZlcnNpb24gIT09IDAgJiYgdmVyc2lvbiAhPT0gMSkge1xuICAgIHJldHVybiB7XG4gICAgICBvZmZzZXQsXG4gICAgICBzaXplXG4gICAgfTtcbiAgfVxuICBjb25zdCBidWZmZXIgPSB2aWV3LmJ1ZmZlcjtcbiAgY29uc3Qgc3lzdGVtSWQgPSBIZXguaGV4RHVtcChuZXcgVWludDhBcnJheShidWZmZXIsIG9mZnNldCArIDEyLCAxNikpO1xuICBjb25zdCBkYXRhU2l6ZU9yS2lkQ291bnQgPSB2aWV3LmdldFVpbnQzMigyOCk7XG4gIGxldCBraWRzID0gbnVsbDtcbiAgbGV0IGRhdGEgPSBudWxsO1xuICBpZiAodmVyc2lvbiA9PT0gMCkge1xuICAgIGlmIChzaXplIC0gMzIgPCBkYXRhU2l6ZU9yS2lkQ291bnQgfHwgZGF0YVNpemVPcktpZENvdW50IDwgMjIpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIG9mZnNldCxcbiAgICAgICAgc2l6ZVxuICAgICAgfTtcbiAgICB9XG4gICAgZGF0YSA9IG5ldyBVaW50OEFycmF5KGJ1ZmZlciwgb2Zmc2V0ICsgMzIsIGRhdGFTaXplT3JLaWRDb3VudCk7XG4gIH0gZWxzZSBpZiAodmVyc2lvbiA9PT0gMSkge1xuICAgIGlmICghZGF0YVNpemVPcktpZENvdW50IHx8IGxlbmd0aCA8IG9mZnNldCArIDMyICsgZGF0YVNpemVPcktpZENvdW50ICogMTYgKyAxNikge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgb2Zmc2V0LFxuICAgICAgICBzaXplXG4gICAgICB9O1xuICAgIH1cbiAgICBraWRzID0gW107XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBkYXRhU2l6ZU9yS2lkQ291bnQ7IGkrKykge1xuICAgICAga2lkcy5wdXNoKG5ldyBVaW50OEFycmF5KGJ1ZmZlciwgb2Zmc2V0ICsgMzIgKyBpICogMTYsIDE2KSk7XG4gICAgfVxuICB9XG4gIHJldHVybiB7XG4gICAgdmVyc2lvbixcbiAgICBzeXN0ZW1JZCxcbiAgICBraWRzLFxuICAgIGRhdGEsXG4gICAgb2Zmc2V0LFxuICAgIHNpemVcbiAgfTtcbn1cblxubGV0IGtleVVyaVRvS2V5SWRNYXAgPSB7fTtcbmNsYXNzIExldmVsS2V5IHtcbiAgc3RhdGljIGNsZWFyS2V5VXJpVG9LZXlJZE1hcCgpIHtcbiAgICBrZXlVcmlUb0tleUlkTWFwID0ge307XG4gIH1cbiAgY29uc3RydWN0b3IobWV0aG9kLCB1cmksIGZvcm1hdCwgZm9ybWF0dmVyc2lvbnMgPSBbMV0sIGl2ID0gbnVsbCkge1xuICAgIHRoaXMudXJpID0gdm9pZCAwO1xuICAgIHRoaXMubWV0aG9kID0gdm9pZCAwO1xuICAgIHRoaXMua2V5Rm9ybWF0ID0gdm9pZCAwO1xuICAgIHRoaXMua2V5Rm9ybWF0VmVyc2lvbnMgPSB2b2lkIDA7XG4gICAgdGhpcy5lbmNyeXB0ZWQgPSB2b2lkIDA7XG4gICAgdGhpcy5pc0NvbW1vbkVuY3J5cHRpb24gPSB2b2lkIDA7XG4gICAgdGhpcy5pdiA9IG51bGw7XG4gICAgdGhpcy5rZXkgPSBudWxsO1xuICAgIHRoaXMua2V5SWQgPSBudWxsO1xuICAgIHRoaXMucHNzaCA9IG51bGw7XG4gICAgdGhpcy5tZXRob2QgPSBtZXRob2Q7XG4gICAgdGhpcy51cmkgPSB1cmk7XG4gICAgdGhpcy5rZXlGb3JtYXQgPSBmb3JtYXQ7XG4gICAgdGhpcy5rZXlGb3JtYXRWZXJzaW9ucyA9IGZvcm1hdHZlcnNpb25zO1xuICAgIHRoaXMuaXYgPSBpdjtcbiAgICB0aGlzLmVuY3J5cHRlZCA9IG1ldGhvZCA/IG1ldGhvZCAhPT0gJ05PTkUnIDogZmFsc2U7XG4gICAgdGhpcy5pc0NvbW1vbkVuY3J5cHRpb24gPSB0aGlzLmVuY3J5cHRlZCAmJiBtZXRob2QgIT09ICdBRVMtMTI4JztcbiAgfVxuICBpc1N1cHBvcnRlZCgpIHtcbiAgICAvLyBJZiBpdCdzIFNlZ21lbnQgZW5jcnlwdGlvbiBvciBObyBlbmNyeXB0aW9uLCBqdXN0IHNlbGVjdCB0aGF0IGtleSBzeXN0ZW1cbiAgICBpZiAodGhpcy5tZXRob2QpIHtcbiAgICAgIGlmICh0aGlzLm1ldGhvZCA9PT0gJ0FFUy0xMjgnIHx8IHRoaXMubWV0aG9kID09PSAnTk9ORScpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5rZXlGb3JtYXQgPT09ICdpZGVudGl0eScpIHtcbiAgICAgICAgLy8gTWFpbnRhaW4gc3VwcG9ydCBmb3IgY2xlYXIgU0FNUExFLUFFUyB3aXRoIE1QRUctMyBUU1xuICAgICAgICByZXR1cm4gdGhpcy5tZXRob2QgPT09ICdTQU1QTEUtQUVTJztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHN3aXRjaCAodGhpcy5rZXlGb3JtYXQpIHtcbiAgICAgICAgICBjYXNlIEtleVN5c3RlbUZvcm1hdHMuRkFJUlBMQVk6XG4gICAgICAgICAgY2FzZSBLZXlTeXN0ZW1Gb3JtYXRzLldJREVWSU5FOlxuICAgICAgICAgIGNhc2UgS2V5U3lzdGVtRm9ybWF0cy5QTEFZUkVBRFk6XG4gICAgICAgICAgY2FzZSBLZXlTeXN0ZW1Gb3JtYXRzLkNMRUFSS0VZOlxuICAgICAgICAgICAgcmV0dXJuIFsnSVNPLTIzMDAxLTcnLCAnU0FNUExFLUFFUycsICdTQU1QTEUtQUVTLUNFTkMnLCAnU0FNUExFLUFFUy1DVFInXS5pbmRleE9mKHRoaXMubWV0aG9kKSAhPT0gLTE7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIGdldERlY3J5cHREYXRhKHNuKSB7XG4gICAgaWYgKCF0aGlzLmVuY3J5cHRlZCB8fCAhdGhpcy51cmkpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBpZiAodGhpcy5tZXRob2QgPT09ICdBRVMtMTI4JyAmJiB0aGlzLnVyaSAmJiAhdGhpcy5pdikge1xuICAgICAgaWYgKHR5cGVvZiBzbiAhPT0gJ251bWJlcicpIHtcbiAgICAgICAgLy8gV2UgYXJlIGZldGNoaW5nIGRlY3J5cHRpb24gZGF0YSBmb3IgYSBpbml0aWFsaXphdGlvbiBzZWdtZW50XG4gICAgICAgIC8vIElmIHRoZSBzZWdtZW50IHdhcyBlbmNyeXB0ZWQgd2l0aCBBRVMtMTI4XG4gICAgICAgIC8vIEl0IG11c3QgaGF2ZSBhbiBJViBkZWZpbmVkLiBXZSBjYW5ub3Qgc3Vic3RpdHV0ZSB0aGUgU2VnbWVudCBOdW1iZXIgaW4uXG4gICAgICAgIGlmICh0aGlzLm1ldGhvZCA9PT0gJ0FFUy0xMjgnICYmICF0aGlzLml2KSB7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oYG1pc3NpbmcgSVYgZm9yIGluaXRpYWxpemF0aW9uIHNlZ21lbnQgd2l0aCBtZXRob2Q9XCIke3RoaXMubWV0aG9kfVwiIC0gY29tcGxpYW5jZSBpc3N1ZWApO1xuICAgICAgICB9XG4gICAgICAgIC8vIEV4cGxpY2l0bHkgc2V0IHNuIHRvIHJlc3VsdGluZyB2YWx1ZSBmcm9tIGltcGxpY2l0IGNvbnZlcnNpb25zICdpbml0U2VnbWVudCcgdmFsdWVzIGZvciBJViBnZW5lcmF0aW9uLlxuICAgICAgICBzbiA9IDA7XG4gICAgICB9XG4gICAgICBjb25zdCBpdiA9IGNyZWF0ZUluaXRpYWxpemF0aW9uVmVjdG9yKHNuKTtcbiAgICAgIGNvbnN0IGRlY3J5cHRkYXRhID0gbmV3IExldmVsS2V5KHRoaXMubWV0aG9kLCB0aGlzLnVyaSwgJ2lkZW50aXR5JywgdGhpcy5rZXlGb3JtYXRWZXJzaW9ucywgaXYpO1xuICAgICAgcmV0dXJuIGRlY3J5cHRkYXRhO1xuICAgIH1cblxuICAgIC8vIEluaXRpYWxpemUga2V5SWQgaWYgcG9zc2libGVcbiAgICBjb25zdCBrZXlCeXRlcyA9IGNvbnZlcnREYXRhVXJpVG9BcnJheUJ5dGVzKHRoaXMudXJpKTtcbiAgICBpZiAoa2V5Qnl0ZXMpIHtcbiAgICAgIHN3aXRjaCAodGhpcy5rZXlGb3JtYXQpIHtcbiAgICAgICAgY2FzZSBLZXlTeXN0ZW1Gb3JtYXRzLldJREVWSU5FOlxuICAgICAgICAgIHRoaXMucHNzaCA9IGtleUJ5dGVzO1xuICAgICAgICAgIC8vIEluIGNhc2Ugb2Ygd2lkZXZpbmUga2V5SUQgaXMgZW1iZWRkZWQgaW4gUFNTSCBib3guIFJlYWQgS2V5IElELlxuICAgICAgICAgIGlmIChrZXlCeXRlcy5sZW5ndGggPj0gMjIpIHtcbiAgICAgICAgICAgIHRoaXMua2V5SWQgPSBrZXlCeXRlcy5zdWJhcnJheShrZXlCeXRlcy5sZW5ndGggLSAyMiwga2V5Qnl0ZXMubGVuZ3RoIC0gNik7XG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIEtleVN5c3RlbUZvcm1hdHMuUExBWVJFQURZOlxuICAgICAgICAgIHtcbiAgICAgICAgICAgIGNvbnN0IFBsYXlSZWFkeUtleVN5c3RlbVVVSUQgPSBuZXcgVWludDhBcnJheShbMHg5YSwgMHgwNCwgMHhmMCwgMHg3OSwgMHg5OCwgMHg0MCwgMHg0MiwgMHg4NiwgMHhhYiwgMHg5MiwgMHhlNiwgMHg1YiwgMHhlMCwgMHg4OCwgMHg1ZiwgMHg5NV0pO1xuICAgICAgICAgICAgdGhpcy5wc3NoID0gbXA0cHNzaChQbGF5UmVhZHlLZXlTeXN0ZW1VVUlELCBudWxsLCBrZXlCeXRlcyk7XG4gICAgICAgICAgICBjb25zdCBrZXlCeXRlc1V0ZjE2ID0gbmV3IFVpbnQxNkFycmF5KGtleUJ5dGVzLmJ1ZmZlciwga2V5Qnl0ZXMuYnl0ZU9mZnNldCwga2V5Qnl0ZXMuYnl0ZUxlbmd0aCAvIDIpO1xuICAgICAgICAgICAgY29uc3Qga2V5Qnl0ZVN0ciA9IFN0cmluZy5mcm9tQ2hhckNvZGUuYXBwbHkobnVsbCwgQXJyYXkuZnJvbShrZXlCeXRlc1V0ZjE2KSk7XG5cbiAgICAgICAgICAgIC8vIFBhcnNlIFBsYXlyZWFkeSBXUk1IZWFkZXIgWE1MXG4gICAgICAgICAgICBjb25zdCB4bWxLZXlCeXRlcyA9IGtleUJ5dGVTdHIuc3Vic3RyaW5nKGtleUJ5dGVTdHIuaW5kZXhPZignPCcpLCBrZXlCeXRlU3RyLmxlbmd0aCk7XG4gICAgICAgICAgICBjb25zdCBwYXJzZXIgPSBuZXcgRE9NUGFyc2VyKCk7XG4gICAgICAgICAgICBjb25zdCB4bWxEb2MgPSBwYXJzZXIucGFyc2VGcm9tU3RyaW5nKHhtbEtleUJ5dGVzLCAndGV4dC94bWwnKTtcbiAgICAgICAgICAgIGNvbnN0IGtleURhdGEgPSB4bWxEb2MuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ0tJRCcpWzBdO1xuICAgICAgICAgICAgaWYgKGtleURhdGEpIHtcbiAgICAgICAgICAgICAgY29uc3Qga2V5SWQgPSBrZXlEYXRhLmNoaWxkTm9kZXNbMF0gPyBrZXlEYXRhLmNoaWxkTm9kZXNbMF0ubm9kZVZhbHVlIDoga2V5RGF0YS5nZXRBdHRyaWJ1dGUoJ1ZBTFVFJyk7XG4gICAgICAgICAgICAgIGlmIChrZXlJZCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGtleUlkQXJyYXkgPSBiYXNlNjREZWNvZGUoa2V5SWQpLnN1YmFycmF5KDAsIDE2KTtcbiAgICAgICAgICAgICAgICAvLyBLSUQgdmFsdWUgaW4gUFJPIGlzIGEgYmFzZTY0LWVuY29kZWQgbGl0dGxlIGVuZGlhbiBHVUlEIGludGVycHJldGF0aW9uIG9mIFVVSURcbiAgICAgICAgICAgICAgICAvLyBLSUQgdmFsdWUgaW4g4oCYdGVuY+KAmSBpcyBhIGJpZyBlbmRpYW4gVVVJRCBHVUlEIGludGVycHJldGF0aW9uIG9mIFVVSURcbiAgICAgICAgICAgICAgICBjaGFuZ2VFbmRpYW5uZXNzKGtleUlkQXJyYXkpO1xuICAgICAgICAgICAgICAgIHRoaXMua2V5SWQgPSBrZXlJZEFycmF5O1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICB9XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAge1xuICAgICAgICAgICAgbGV0IGtleWRhdGEgPSBrZXlCeXRlcy5zdWJhcnJheSgwLCAxNik7XG4gICAgICAgICAgICBpZiAoa2V5ZGF0YS5sZW5ndGggIT09IDE2KSB7XG4gICAgICAgICAgICAgIGNvbnN0IHBhZGRlZCA9IG5ldyBVaW50OEFycmF5KDE2KTtcbiAgICAgICAgICAgICAgcGFkZGVkLnNldChrZXlkYXRhLCAxNiAtIGtleWRhdGEubGVuZ3RoKTtcbiAgICAgICAgICAgICAga2V5ZGF0YSA9IHBhZGRlZDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMua2V5SWQgPSBrZXlkYXRhO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIERlZmF1bHQgYmVoYXZpb3I6IGFzc2lnbiBhIG5ldyBrZXlJZCBmb3IgZWFjaCB1cmlcbiAgICBpZiAoIXRoaXMua2V5SWQgfHwgdGhpcy5rZXlJZC5ieXRlTGVuZ3RoICE9PSAxNikge1xuICAgICAgbGV0IGtleUlkID0ga2V5VXJpVG9LZXlJZE1hcFt0aGlzLnVyaV07XG4gICAgICBpZiAoIWtleUlkKSB7XG4gICAgICAgIGNvbnN0IHZhbCA9IE9iamVjdC5rZXlzKGtleVVyaVRvS2V5SWRNYXApLmxlbmd0aCAlIE51bWJlci5NQVhfU0FGRV9JTlRFR0VSO1xuICAgICAgICBrZXlJZCA9IG5ldyBVaW50OEFycmF5KDE2KTtcbiAgICAgICAgY29uc3QgZHYgPSBuZXcgRGF0YVZpZXcoa2V5SWQuYnVmZmVyLCAxMiwgNCk7IC8vIEp1c3Qgc2V0IHRoZSBsYXN0IDQgYnl0ZXNcbiAgICAgICAgZHYuc2V0VWludDMyKDAsIHZhbCk7XG4gICAgICAgIGtleVVyaVRvS2V5SWRNYXBbdGhpcy51cmldID0ga2V5SWQ7XG4gICAgICB9XG4gICAgICB0aGlzLmtleUlkID0ga2V5SWQ7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9XG59XG5mdW5jdGlvbiBjcmVhdGVJbml0aWFsaXphdGlvblZlY3RvcihzZWdtZW50TnVtYmVyKSB7XG4gIGNvbnN0IHVpbnQ4VmlldyA9IG5ldyBVaW50OEFycmF5KDE2KTtcbiAgZm9yIChsZXQgaSA9IDEyOyBpIDwgMTY7IGkrKykge1xuICAgIHVpbnQ4Vmlld1tpXSA9IHNlZ21lbnROdW1iZXIgPj4gOCAqICgxNSAtIGkpICYgMHhmZjtcbiAgfVxuICByZXR1cm4gdWludDhWaWV3O1xufVxuXG5jb25zdCBWQVJJQUJMRV9SRVBMQUNFTUVOVF9SRUdFWCA9IC9cXHtcXCQoW2EtekEtWjAtOS1fXSspXFx9L2c7XG5mdW5jdGlvbiBoYXNWYXJpYWJsZVJlZmVyZW5jZXMoc3RyKSB7XG4gIHJldHVybiBWQVJJQUJMRV9SRVBMQUNFTUVOVF9SRUdFWC50ZXN0KHN0cik7XG59XG5mdW5jdGlvbiBzdWJzdGl0dXRlVmFyaWFibGVzSW5BdHRyaWJ1dGVzKHBhcnNlZCwgYXR0ciwgYXR0cmlidXRlTmFtZXMpIHtcbiAgaWYgKHBhcnNlZC52YXJpYWJsZUxpc3QgIT09IG51bGwgfHwgcGFyc2VkLmhhc1ZhcmlhYmxlUmVmcykge1xuICAgIGZvciAobGV0IGkgPSBhdHRyaWJ1dGVOYW1lcy5sZW5ndGg7IGktLTspIHtcbiAgICAgIGNvbnN0IG5hbWUgPSBhdHRyaWJ1dGVOYW1lc1tpXTtcbiAgICAgIGNvbnN0IHZhbHVlID0gYXR0cltuYW1lXTtcbiAgICAgIGlmICh2YWx1ZSkge1xuICAgICAgICBhdHRyW25hbWVdID0gc3Vic3RpdHV0ZVZhcmlhYmxlcyhwYXJzZWQsIHZhbHVlKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cbmZ1bmN0aW9uIHN1YnN0aXR1dGVWYXJpYWJsZXMocGFyc2VkLCB2YWx1ZSkge1xuICBpZiAocGFyc2VkLnZhcmlhYmxlTGlzdCAhPT0gbnVsbCB8fCBwYXJzZWQuaGFzVmFyaWFibGVSZWZzKSB7XG4gICAgY29uc3QgdmFyaWFibGVMaXN0ID0gcGFyc2VkLnZhcmlhYmxlTGlzdDtcbiAgICByZXR1cm4gdmFsdWUucmVwbGFjZShWQVJJQUJMRV9SRVBMQUNFTUVOVF9SRUdFWCwgdmFyaWFibGVSZWZlcmVuY2UgPT4ge1xuICAgICAgY29uc3QgdmFyaWFibGVOYW1lID0gdmFyaWFibGVSZWZlcmVuY2Uuc3Vic3RyaW5nKDIsIHZhcmlhYmxlUmVmZXJlbmNlLmxlbmd0aCAtIDEpO1xuICAgICAgY29uc3QgdmFyaWFibGVWYWx1ZSA9IHZhcmlhYmxlTGlzdCA9PSBudWxsID8gdm9pZCAwIDogdmFyaWFibGVMaXN0W3ZhcmlhYmxlTmFtZV07XG4gICAgICBpZiAodmFyaWFibGVWYWx1ZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHBhcnNlZC5wbGF5bGlzdFBhcnNpbmdFcnJvciB8fCAocGFyc2VkLnBsYXlsaXN0UGFyc2luZ0Vycm9yID0gbmV3IEVycm9yKGBNaXNzaW5nIHByZWNlZGluZyBFWFQtWC1ERUZJTkUgdGFnIGZvciBWYXJpYWJsZSBSZWZlcmVuY2U6IFwiJHt2YXJpYWJsZU5hbWV9XCJgKSk7XG4gICAgICAgIHJldHVybiB2YXJpYWJsZVJlZmVyZW5jZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB2YXJpYWJsZVZhbHVlO1xuICAgIH0pO1xuICB9XG4gIHJldHVybiB2YWx1ZTtcbn1cbmZ1bmN0aW9uIGFkZFZhcmlhYmxlRGVmaW5pdGlvbihwYXJzZWQsIGF0dHIsIHBhcmVudFVybCkge1xuICBsZXQgdmFyaWFibGVMaXN0ID0gcGFyc2VkLnZhcmlhYmxlTGlzdDtcbiAgaWYgKCF2YXJpYWJsZUxpc3QpIHtcbiAgICBwYXJzZWQudmFyaWFibGVMaXN0ID0gdmFyaWFibGVMaXN0ID0ge307XG4gIH1cbiAgbGV0IE5BTUU7XG4gIGxldCBWQUxVRTtcbiAgaWYgKCdRVUVSWVBBUkFNJyBpbiBhdHRyKSB7XG4gICAgTkFNRSA9IGF0dHIuUVVFUllQQVJBTTtcbiAgICB0cnkge1xuICAgICAgY29uc3Qgc2VhcmNoUGFyYW1zID0gbmV3IHNlbGYuVVJMKHBhcmVudFVybCkuc2VhcmNoUGFyYW1zO1xuICAgICAgaWYgKHNlYXJjaFBhcmFtcy5oYXMoTkFNRSkpIHtcbiAgICAgICAgVkFMVUUgPSBzZWFyY2hQYXJhbXMuZ2V0KE5BTUUpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBcIiR7TkFNRX1cIiBkb2VzIG5vdCBtYXRjaCBhbnkgcXVlcnkgcGFyYW1ldGVyIGluIFVSSTogXCIke3BhcmVudFVybH1cImApO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBwYXJzZWQucGxheWxpc3RQYXJzaW5nRXJyb3IgfHwgKHBhcnNlZC5wbGF5bGlzdFBhcnNpbmdFcnJvciA9IG5ldyBFcnJvcihgRVhULVgtREVGSU5FIFFVRVJZUEFSQU06ICR7ZXJyb3IubWVzc2FnZX1gKSk7XG4gICAgfVxuICB9IGVsc2Uge1xuICAgIE5BTUUgPSBhdHRyLk5BTUU7XG4gICAgVkFMVUUgPSBhdHRyLlZBTFVFO1xuICB9XG4gIGlmIChOQU1FIGluIHZhcmlhYmxlTGlzdCkge1xuICAgIHBhcnNlZC5wbGF5bGlzdFBhcnNpbmdFcnJvciB8fCAocGFyc2VkLnBsYXlsaXN0UGFyc2luZ0Vycm9yID0gbmV3IEVycm9yKGBFWFQtWC1ERUZJTkUgZHVwbGljYXRlIFZhcmlhYmxlIE5hbWUgZGVjbGFyYXRpb25zOiBcIiR7TkFNRX1cImApKTtcbiAgfSBlbHNlIHtcbiAgICB2YXJpYWJsZUxpc3RbTkFNRV0gPSBWQUxVRSB8fCAnJztcbiAgfVxufVxuZnVuY3Rpb24gaW1wb3J0VmFyaWFibGVEZWZpbml0aW9uKHBhcnNlZCwgYXR0ciwgc291cmNlVmFyaWFibGVMaXN0KSB7XG4gIGNvbnN0IElNUE9SVCA9IGF0dHIuSU1QT1JUO1xuICBpZiAoc291cmNlVmFyaWFibGVMaXN0ICYmIElNUE9SVCBpbiBzb3VyY2VWYXJpYWJsZUxpc3QpIHtcbiAgICBsZXQgdmFyaWFibGVMaXN0ID0gcGFyc2VkLnZhcmlhYmxlTGlzdDtcbiAgICBpZiAoIXZhcmlhYmxlTGlzdCkge1xuICAgICAgcGFyc2VkLnZhcmlhYmxlTGlzdCA9IHZhcmlhYmxlTGlzdCA9IHt9O1xuICAgIH1cbiAgICB2YXJpYWJsZUxpc3RbSU1QT1JUXSA9IHNvdXJjZVZhcmlhYmxlTGlzdFtJTVBPUlRdO1xuICB9IGVsc2Uge1xuICAgIHBhcnNlZC5wbGF5bGlzdFBhcnNpbmdFcnJvciB8fCAocGFyc2VkLnBsYXlsaXN0UGFyc2luZ0Vycm9yID0gbmV3IEVycm9yKGBFWFQtWC1ERUZJTkUgSU1QT1JUIGF0dHJpYnV0ZSBub3QgZm91bmQgaW4gTXVsdGl2YXJpYW50IFBsYXlsaXN0OiBcIiR7SU1QT1JUfVwiYCkpO1xuICB9XG59XG5cbi8qKlxuICogTWVkaWFTb3VyY2UgaGVscGVyXG4gKi9cblxuZnVuY3Rpb24gZ2V0TWVkaWFTb3VyY2UocHJlZmVyTWFuYWdlZE1lZGlhU291cmNlID0gdHJ1ZSkge1xuICBpZiAodHlwZW9mIHNlbGYgPT09ICd1bmRlZmluZWQnKSByZXR1cm4gdW5kZWZpbmVkO1xuICBjb25zdCBtbXMgPSAocHJlZmVyTWFuYWdlZE1lZGlhU291cmNlIHx8ICFzZWxmLk1lZGlhU291cmNlKSAmJiBzZWxmLk1hbmFnZWRNZWRpYVNvdXJjZTtcbiAgcmV0dXJuIG1tcyB8fCBzZWxmLk1lZGlhU291cmNlIHx8IHNlbGYuV2ViS2l0TWVkaWFTb3VyY2U7XG59XG5mdW5jdGlvbiBpc01hbmFnZWRNZWRpYVNvdXJjZShzb3VyY2UpIHtcbiAgcmV0dXJuIHR5cGVvZiBzZWxmICE9PSAndW5kZWZpbmVkJyAmJiBzb3VyY2UgPT09IHNlbGYuTWFuYWdlZE1lZGlhU291cmNlO1xufVxuXG4vLyBmcm9tIGh0dHA6Ly9tcDRyYS5vcmcvY29kZWNzLmh0bWxcbi8vIHZhbHVlcyBpbmRpY2F0ZSBjb2RlYyBzZWxlY3Rpb24gcHJlZmVyZW5jZSAobG93ZXIgaXMgaGlnaGVyIHByaW9yaXR5KVxuY29uc3Qgc2FtcGxlRW50cnlDb2Rlc0lTTyA9IHtcbiAgYXVkaW86IHtcbiAgICBhM2RzOiAxLFxuICAgICdhYy0zJzogMC45NSxcbiAgICAnYWMtNCc6IDEsXG4gICAgYWxhYzogMC45LFxuICAgIGFsYXc6IDEsXG4gICAgZHJhMTogMSxcbiAgICAnZHRzKyc6IDEsXG4gICAgJ2R0cy0nOiAxLFxuICAgIGR0c2M6IDEsXG4gICAgZHRzZTogMSxcbiAgICBkdHNoOiAxLFxuICAgICdlYy0zJzogMC45LFxuICAgIGVuY2E6IDEsXG4gICAgZkxhQzogMC45LFxuICAgIC8vIE1QNC1SQSBsaXN0ZWQgY29kZWMgZW50cnkgZm9yIEZMQUNcbiAgICBmbGFjOiAwLjksXG4gICAgLy8gbGVnYWN5IGJyb3dzZXIgY29kZWMgbmFtZSBmb3IgRkxBQ1xuICAgIEZMQUM6IDAuOSxcbiAgICAvLyBzb21lIG1hbmlmZXN0cyBtYXkgbGlzdCBcIkZMQUNcIiB3aXRoIEFwcGxlJ3MgdG9vbHNcbiAgICBnNzE5OiAxLFxuICAgIGc3MjY6IDEsXG4gICAgbTRhZTogMSxcbiAgICBtaGExOiAxLFxuICAgIG1oYTI6IDEsXG4gICAgbWhtMTogMSxcbiAgICBtaG0yOiAxLFxuICAgIG1scGE6IDEsXG4gICAgbXA0YTogMSxcbiAgICAncmF3ICc6IDEsXG4gICAgT3B1czogMSxcbiAgICBvcHVzOiAxLFxuICAgIC8vIGJyb3dzZXJzIGV4cGVjdCB0aGlzIHRvIGJlIGxvd2VyY2FzZSBkZXNwaXRlIE1QNFJBIHNheXMgJ09wdXMnXG4gICAgc2FtcjogMSxcbiAgICBzYXdiOiAxLFxuICAgIHNhd3A6IDEsXG4gICAgc2V2YzogMSxcbiAgICBzcWNwOiAxLFxuICAgIHNzbXY6IDEsXG4gICAgdHdvczogMSxcbiAgICB1bGF3OiAxXG4gIH0sXG4gIHZpZGVvOiB7XG4gICAgYXZjMTogMSxcbiAgICBhdmMyOiAxLFxuICAgIGF2YzM6IDEsXG4gICAgYXZjNDogMSxcbiAgICBhdmNwOiAxLFxuICAgIGF2MDE6IDAuOCxcbiAgICBkcmFjOiAxLFxuICAgIGR2YTE6IDEsXG4gICAgZHZhdjogMSxcbiAgICBkdmgxOiAwLjcsXG4gICAgZHZoZTogMC43LFxuICAgIGVuY3Y6IDEsXG4gICAgaGV2MTogMC43NSxcbiAgICBodmMxOiAwLjc1LFxuICAgIG1qcDI6IDEsXG4gICAgbXA0djogMSxcbiAgICBtdmMxOiAxLFxuICAgIG12YzI6IDEsXG4gICAgbXZjMzogMSxcbiAgICBtdmM0OiAxLFxuICAgIHJlc3Y6IDEsXG4gICAgcnY2MDogMSxcbiAgICBzMjYzOiAxLFxuICAgIHN2YzE6IDEsXG4gICAgc3ZjMjogMSxcbiAgICAndmMtMSc6IDEsXG4gICAgdnAwODogMSxcbiAgICB2cDA5OiAwLjlcbiAgfSxcbiAgdGV4dDoge1xuICAgIHN0cHA6IDEsXG4gICAgd3Z0dDogMVxuICB9XG59O1xuZnVuY3Rpb24gaXNDb2RlY1R5cGUoY29kZWMsIHR5cGUpIHtcbiAgY29uc3QgdHlwZUNvZGVzID0gc2FtcGxlRW50cnlDb2Rlc0lTT1t0eXBlXTtcbiAgcmV0dXJuICEhdHlwZUNvZGVzICYmICEhdHlwZUNvZGVzW2NvZGVjLnNsaWNlKDAsIDQpXTtcbn1cbmZ1bmN0aW9uIGFyZUNvZGVjc01lZGlhU291cmNlU3VwcG9ydGVkKGNvZGVjcywgdHlwZSwgcHJlZmVyTWFuYWdlZE1lZGlhU291cmNlID0gdHJ1ZSkge1xuICByZXR1cm4gIWNvZGVjcy5zcGxpdCgnLCcpLnNvbWUoY29kZWMgPT4gIWlzQ29kZWNNZWRpYVNvdXJjZVN1cHBvcnRlZChjb2RlYywgdHlwZSwgcHJlZmVyTWFuYWdlZE1lZGlhU291cmNlKSk7XG59XG5mdW5jdGlvbiBpc0NvZGVjTWVkaWFTb3VyY2VTdXBwb3J0ZWQoY29kZWMsIHR5cGUsIHByZWZlck1hbmFnZWRNZWRpYVNvdXJjZSA9IHRydWUpIHtcbiAgdmFyIF9NZWRpYVNvdXJjZSRpc1R5cGVTdTtcbiAgY29uc3QgTWVkaWFTb3VyY2UgPSBnZXRNZWRpYVNvdXJjZShwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UpO1xuICByZXR1cm4gKF9NZWRpYVNvdXJjZSRpc1R5cGVTdSA9IE1lZGlhU291cmNlID09IG51bGwgPyB2b2lkIDAgOiBNZWRpYVNvdXJjZS5pc1R5cGVTdXBwb3J0ZWQobWltZVR5cGVGb3JDb2RlYyhjb2RlYywgdHlwZSkpKSAhPSBudWxsID8gX01lZGlhU291cmNlJGlzVHlwZVN1IDogZmFsc2U7XG59XG5mdW5jdGlvbiBtaW1lVHlwZUZvckNvZGVjKGNvZGVjLCB0eXBlKSB7XG4gIHJldHVybiBgJHt0eXBlfS9tcDQ7Y29kZWNzPVwiJHtjb2RlY31cImA7XG59XG5mdW5jdGlvbiB2aWRlb0NvZGVjUHJlZmVyZW5jZVZhbHVlKHZpZGVvQ29kZWMpIHtcbiAgaWYgKHZpZGVvQ29kZWMpIHtcbiAgICBjb25zdCBmb3VyQ0MgPSB2aWRlb0NvZGVjLnN1YnN0cmluZygwLCA0KTtcbiAgICByZXR1cm4gc2FtcGxlRW50cnlDb2Rlc0lTTy52aWRlb1tmb3VyQ0NdO1xuICB9XG4gIHJldHVybiAyO1xufVxuZnVuY3Rpb24gY29kZWNzU2V0U2VsZWN0aW9uUHJlZmVyZW5jZVZhbHVlKGNvZGVjU2V0KSB7XG4gIHJldHVybiBjb2RlY1NldC5zcGxpdCgnLCcpLnJlZHVjZSgobnVtLCBmb3VyQ0MpID0+IHtcbiAgICBjb25zdCBwcmVmZXJlbmNlVmFsdWUgPSBzYW1wbGVFbnRyeUNvZGVzSVNPLnZpZGVvW2ZvdXJDQ107XG4gICAgaWYgKHByZWZlcmVuY2VWYWx1ZSkge1xuICAgICAgcmV0dXJuIChwcmVmZXJlbmNlVmFsdWUgKiAyICsgbnVtKSAvIChudW0gPyAzIDogMik7XG4gICAgfVxuICAgIHJldHVybiAoc2FtcGxlRW50cnlDb2Rlc0lTTy5hdWRpb1tmb3VyQ0NdICsgbnVtKSAvIChudW0gPyAyIDogMSk7XG4gIH0sIDApO1xufVxuY29uc3QgQ09ERUNfQ09NUEFUSUJMRV9OQU1FUyA9IHt9O1xuZnVuY3Rpb24gZ2V0Q29kZWNDb21wYXRpYmxlTmFtZUxvd2VyKGxvd2VyQ2FzZUNvZGVjLCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UgPSB0cnVlKSB7XG4gIGlmIChDT0RFQ19DT01QQVRJQkxFX05BTUVTW2xvd2VyQ2FzZUNvZGVjXSkge1xuICAgIHJldHVybiBDT0RFQ19DT01QQVRJQkxFX05BTUVTW2xvd2VyQ2FzZUNvZGVjXTtcbiAgfVxuXG4gIC8vIElkZWFseSBmTGFDIGFuZCBPcHVzIHdvdWxkIGJlIGZpcnN0IChzcGVjLWNvbXBsaWFudCkgYnV0XG4gIC8vIHNvbWUgYnJvd3NlcnMgd2lsbCByZXBvcnQgdGhhdCBmTGFDIGlzIHN1cHBvcnRlZCB0aGVuIGZhaWwuXG4gIC8vIHNlZTogaHR0cHM6Ly9idWdzLmNocm9taXVtLm9yZy9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MTQyMjcyOFxuICBjb25zdCBjb2RlY3NUb0NoZWNrID0ge1xuICAgIGZsYWM6IFsnZmxhYycsICdmTGFDJywgJ0ZMQUMnXSxcbiAgICBvcHVzOiBbJ29wdXMnLCAnT3B1cyddXG4gIH1bbG93ZXJDYXNlQ29kZWNdO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IGNvZGVjc1RvQ2hlY2subGVuZ3RoOyBpKyspIHtcbiAgICBpZiAoaXNDb2RlY01lZGlhU291cmNlU3VwcG9ydGVkKGNvZGVjc1RvQ2hlY2tbaV0sICdhdWRpbycsIHByZWZlck1hbmFnZWRNZWRpYVNvdXJjZSkpIHtcbiAgICAgIENPREVDX0NPTVBBVElCTEVfTkFNRVNbbG93ZXJDYXNlQ29kZWNdID0gY29kZWNzVG9DaGVja1tpXTtcbiAgICAgIHJldHVybiBjb2RlY3NUb0NoZWNrW2ldO1xuICAgIH1cbiAgfVxuICByZXR1cm4gbG93ZXJDYXNlQ29kZWM7XG59XG5jb25zdCBBVURJT19DT0RFQ19SRUdFWFAgPSAvZmxhY3xvcHVzL2k7XG5mdW5jdGlvbiBnZXRDb2RlY0NvbXBhdGlibGVOYW1lKGNvZGVjLCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UgPSB0cnVlKSB7XG4gIHJldHVybiBjb2RlYy5yZXBsYWNlKEFVRElPX0NPREVDX1JFR0VYUCwgbSA9PiBnZXRDb2RlY0NvbXBhdGlibGVOYW1lTG93ZXIobS50b0xvd2VyQ2FzZSgpLCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UpKTtcbn1cbmZ1bmN0aW9uIHBpY2tNb3N0Q29tcGxldGVDb2RlY05hbWUocGFyc2VkQ29kZWMsIGxldmVsQ29kZWMpIHtcbiAgLy8gUGFyc2luZyBvZiBtcDRhIGNvZGVjcyBzdHJpbmdzIGluIG1wNC10b29scyBmcm9tIG1lZGlhIGlzIGluY29tcGxldGUgYXMgb2YgZDhjNmM3YVxuICAvLyBzbyB1c2UgbGV2ZWwgY29kZWMgaXMgcGFyc2VkIGNvZGVjIGlzIHVuYXZhaWxhYmxlIG9yIGluY29tcGxldGVcbiAgaWYgKHBhcnNlZENvZGVjICYmIHBhcnNlZENvZGVjICE9PSAnbXA0YScpIHtcbiAgICByZXR1cm4gcGFyc2VkQ29kZWM7XG4gIH1cbiAgcmV0dXJuIGxldmVsQ29kZWMgPyBsZXZlbENvZGVjLnNwbGl0KCcsJylbMF0gOiBsZXZlbENvZGVjO1xufVxuZnVuY3Rpb24gY29udmVydEFWQzFUb0FWQ09USShjb2RlYykge1xuICAvLyBDb252ZXJ0IGF2YzEgY29kZWMgc3RyaW5nIGZyb20gUkZDLTQyODEgdG8gUkZDLTYzODEgZm9yIE1lZGlhU291cmNlLmlzVHlwZVN1cHBvcnRlZFxuICAvLyBFeGFtcGxlczogYXZjMS42Ni4zMCB0byBhdmMxLjQyMDAxZSBhbmQgYXZjMS43Ny4zMCxhdmMxLjY2LjMwIHRvIGF2YzEuNGQwMDFlLGF2YzEuNDIwMDFlLlxuICBjb25zdCBjb2RlY3MgPSBjb2RlYy5zcGxpdCgnLCcpO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IGNvZGVjcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGF2Y2RhdGEgPSBjb2RlY3NbaV0uc3BsaXQoJy4nKTtcbiAgICBpZiAoYXZjZGF0YS5sZW5ndGggPiAyKSB7XG4gICAgICBsZXQgcmVzdWx0ID0gYXZjZGF0YS5zaGlmdCgpICsgJy4nO1xuICAgICAgcmVzdWx0ICs9IHBhcnNlSW50KGF2Y2RhdGEuc2hpZnQoKSkudG9TdHJpbmcoMTYpO1xuICAgICAgcmVzdWx0ICs9ICgnMDAwJyArIHBhcnNlSW50KGF2Y2RhdGEuc2hpZnQoKSkudG9TdHJpbmcoMTYpKS5zbGljZSgtNCk7XG4gICAgICBjb2RlY3NbaV0gPSByZXN1bHQ7XG4gICAgfVxuICB9XG4gIHJldHVybiBjb2RlY3Muam9pbignLCcpO1xufVxuXG5jb25zdCBNQVNURVJfUExBWUxJU1RfUkVHRVggPSAvI0VYVC1YLVNUUkVBTS1JTkY6KFteXFxyXFxuXSopKD86W1xcclxcbl0oPzojW15cXHJcXG5dKik/KSooW15cXHJcXG5dKyl8I0VYVC1YLShTRVNTSU9OLURBVEF8U0VTU0lPTi1LRVl8REVGSU5FfENPTlRFTlQtU1RFRVJJTkd8U1RBUlQpOihbXlxcclxcbl0qKVtcXHJcXG5dKy9nO1xuY29uc3QgTUFTVEVSX1BMQVlMSVNUX01FRElBX1JFR0VYID0gLyNFWFQtWC1NRURJQTooLiopL2c7XG5jb25zdCBJU19NRURJQV9QTEFZTElTVCA9IC9eI0VYVCg/OklORnwtWC1UQVJHRVREVVJBVElPTik6L207IC8vIEhhbmRsZSBlbXB0eSBNZWRpYSBQbGF5bGlzdCAoZmlyc3QgRVhUSU5GIG5vdCBzaWduYWxlZCwgYnV0IFRBUkdFVERVUkFUSU9OIHByZXNlbnQpXG5cbmNvbnN0IExFVkVMX1BMQVlMSVNUX1JFR0VYX0ZBU1QgPSBuZXcgUmVnRXhwKFsvI0VYVElORjpcXHMqKFxcZCooPzpcXC5cXGQrKT8pKD86LCguKilcXHMrKT8vLnNvdXJjZSxcbi8vIGR1cmF0aW9uICgjRVhUSU5GOjxkdXJhdGlvbj4sPHRpdGxlPiksIGdyb3VwIDEgPT4gZHVyYXRpb24sIGdyb3VwIDIgPT4gdGl0bGVcbi8oPyEjKSAqKFxcU1teXFxyXFxuXSopLy5zb3VyY2UsXG4vLyBzZWdtZW50IFVSSSwgZ3JvdXAgMyA9PiB0aGUgVVJJIChub3RlIG5ld2xpbmUgaXMgbm90IGVhdGVuKVxuLyNFWFQtWC1CWVRFUkFOR0U6KiguKykvLnNvdXJjZSxcbi8vIG5leHQgc2VnbWVudCdzIGJ5dGVyYW5nZSwgZ3JvdXAgNCA9PiByYW5nZSBzcGVjICh4QHkpXG4vI0VYVC1YLVBST0dSQU0tREFURS1USU1FOiguKykvLnNvdXJjZSxcbi8vIG5leHQgc2VnbWVudCdzIHByb2dyYW0gZGF0ZS90aW1lIGdyb3VwIDUgPT4gdGhlIGRhdGV0aW1lIHNwZWNcbi8jLiovLnNvdXJjZSAvLyBBbGwgb3RoZXIgbm9uLXNlZ21lbnQgb3JpZW50ZWQgdGFncyB3aWxsIG1hdGNoIHdpdGggYWxsIGdyb3VwcyBlbXB0eVxuXS5qb2luKCd8JyksICdnJyk7XG5jb25zdCBMRVZFTF9QTEFZTElTVF9SRUdFWF9TTE9XID0gbmV3IFJlZ0V4cChbLyMoRVhUTTNVKS8uc291cmNlLCAvI0VYVC1YLShEQVRFUkFOR0V8REVGSU5FfEtFWXxNQVB8UEFSVHxQQVJULUlORnxQTEFZTElTVC1UWVBFfFBSRUxPQUQtSElOVHxSRU5ESVRJT04tUkVQT1JUfFNFUlZFUi1DT05UUk9MfFNLSVB8U1RBUlQpOiguKykvLnNvdXJjZSwgLyNFWFQtWC0oQklUUkFURXxESVNDT05USU5VSVRZLVNFUVVFTkNFfE1FRElBLVNFUVVFTkNFfFRBUkdFVERVUkFUSU9OfFZFUlNJT04pOiAqKFxcZCspLy5zb3VyY2UsIC8jRVhULVgtKERJU0NPTlRJTlVJVFl8RU5ETElTVHxHQVB8SU5ERVBFTkRFTlQtU0VHTUVOVFMpLy5zb3VyY2UsIC8oIykoW146XSopOiguKikvLnNvdXJjZSwgLygjKSguKikoPzouKilcXHI/XFxuPy8uc291cmNlXS5qb2luKCd8JykpO1xuY2xhc3MgTTNVOFBhcnNlciB7XG4gIHN0YXRpYyBmaW5kR3JvdXAoZ3JvdXBzLCBtZWRpYUdyb3VwSWQpIHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGdyb3Vwcy5sZW5ndGg7IGkrKykge1xuICAgICAgY29uc3QgZ3JvdXAgPSBncm91cHNbaV07XG4gICAgICBpZiAoZ3JvdXAuaWQgPT09IG1lZGlhR3JvdXBJZCkge1xuICAgICAgICByZXR1cm4gZ3JvdXA7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHN0YXRpYyByZXNvbHZlKHVybCwgYmFzZVVybCkge1xuICAgIHJldHVybiB1cmxUb29sa2l0RXhwb3J0cy5idWlsZEFic29sdXRlVVJMKGJhc2VVcmwsIHVybCwge1xuICAgICAgYWx3YXlzTm9ybWFsaXplOiB0cnVlXG4gICAgfSk7XG4gIH1cbiAgc3RhdGljIGlzTWVkaWFQbGF5bGlzdChzdHIpIHtcbiAgICByZXR1cm4gSVNfTUVESUFfUExBWUxJU1QudGVzdChzdHIpO1xuICB9XG4gIHN0YXRpYyBwYXJzZU1hc3RlclBsYXlsaXN0KHN0cmluZywgYmFzZXVybCkge1xuICAgIGNvbnN0IGhhc1ZhcmlhYmxlUmVmcyA9IGhhc1ZhcmlhYmxlUmVmZXJlbmNlcyhzdHJpbmcpIDtcbiAgICBjb25zdCBwYXJzZWQgPSB7XG4gICAgICBjb250ZW50U3RlZXJpbmc6IG51bGwsXG4gICAgICBsZXZlbHM6IFtdLFxuICAgICAgcGxheWxpc3RQYXJzaW5nRXJyb3I6IG51bGwsXG4gICAgICBzZXNzaW9uRGF0YTogbnVsbCxcbiAgICAgIHNlc3Npb25LZXlzOiBudWxsLFxuICAgICAgc3RhcnRUaW1lT2Zmc2V0OiBudWxsLFxuICAgICAgdmFyaWFibGVMaXN0OiBudWxsLFxuICAgICAgaGFzVmFyaWFibGVSZWZzXG4gICAgfTtcbiAgICBjb25zdCBsZXZlbHNXaXRoS25vd25Db2RlY3MgPSBbXTtcbiAgICBNQVNURVJfUExBWUxJU1RfUkVHRVgubGFzdEluZGV4ID0gMDtcbiAgICBsZXQgcmVzdWx0O1xuICAgIHdoaWxlICgocmVzdWx0ID0gTUFTVEVSX1BMQVlMSVNUX1JFR0VYLmV4ZWMoc3RyaW5nKSkgIT0gbnVsbCkge1xuICAgICAgaWYgKHJlc3VsdFsxXSkge1xuICAgICAgICB2YXIgX2xldmVsJHVua25vd25Db2RlY3M7XG4gICAgICAgIC8vICcjRVhULVgtU1RSRUFNLUlORicgaXMgZm91bmQsIHBhcnNlIGxldmVsIHRhZyAgaW4gZ3JvdXAgMVxuICAgICAgICBjb25zdCBhdHRycyA9IG5ldyBBdHRyTGlzdChyZXN1bHRbMV0pO1xuICAgICAgICB7XG4gICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhwYXJzZWQsIGF0dHJzLCBbJ0NPREVDUycsICdTVVBQTEVNRU5UQUwtQ09ERUNTJywgJ0FMTE9XRUQtQ1BDJywgJ1BBVEhXQVktSUQnLCAnU1RBQkxFLVZBUklBTlQtSUQnLCAnQVVESU8nLCAnVklERU8nLCAnU1VCVElUTEVTJywgJ0NMT1NFRC1DQVBUSU9OUycsICdOQU1FJ10pO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHVyaSA9IHN1YnN0aXR1dGVWYXJpYWJsZXMocGFyc2VkLCByZXN1bHRbMl0pIDtcbiAgICAgICAgY29uc3QgbGV2ZWwgPSB7XG4gICAgICAgICAgYXR0cnMsXG4gICAgICAgICAgYml0cmF0ZTogYXR0cnMuZGVjaW1hbEludGVnZXIoJ0JBTkRXSURUSCcpIHx8IGF0dHJzLmRlY2ltYWxJbnRlZ2VyKCdBVkVSQUdFLUJBTkRXSURUSCcpLFxuICAgICAgICAgIG5hbWU6IGF0dHJzLk5BTUUsXG4gICAgICAgICAgdXJsOiBNM1U4UGFyc2VyLnJlc29sdmUodXJpLCBiYXNldXJsKVxuICAgICAgICB9O1xuICAgICAgICBjb25zdCByZXNvbHV0aW9uID0gYXR0cnMuZGVjaW1hbFJlc29sdXRpb24oJ1JFU09MVVRJT04nKTtcbiAgICAgICAgaWYgKHJlc29sdXRpb24pIHtcbiAgICAgICAgICBsZXZlbC53aWR0aCA9IHJlc29sdXRpb24ud2lkdGg7XG4gICAgICAgICAgbGV2ZWwuaGVpZ2h0ID0gcmVzb2x1dGlvbi5oZWlnaHQ7XG4gICAgICAgIH1cbiAgICAgICAgc2V0Q29kZWNzKGF0dHJzLkNPREVDUywgbGV2ZWwpO1xuICAgICAgICBpZiAoISgoX2xldmVsJHVua25vd25Db2RlY3MgPSBsZXZlbC51bmtub3duQ29kZWNzKSAhPSBudWxsICYmIF9sZXZlbCR1bmtub3duQ29kZWNzLmxlbmd0aCkpIHtcbiAgICAgICAgICBsZXZlbHNXaXRoS25vd25Db2RlY3MucHVzaChsZXZlbCk7XG4gICAgICAgIH1cbiAgICAgICAgcGFyc2VkLmxldmVscy5wdXNoKGxldmVsKTtcbiAgICAgIH0gZWxzZSBpZiAocmVzdWx0WzNdKSB7XG4gICAgICAgIGNvbnN0IHRhZyA9IHJlc3VsdFszXTtcbiAgICAgICAgY29uc3QgYXR0cmlidXRlcyA9IHJlc3VsdFs0XTtcbiAgICAgICAgc3dpdGNoICh0YWcpIHtcbiAgICAgICAgICBjYXNlICdTRVNTSU9OLURBVEEnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAvLyAjRVhULVgtU0VTU0lPTi1EQVRBXG4gICAgICAgICAgICAgIGNvbnN0IHNlc3Npb25BdHRycyA9IG5ldyBBdHRyTGlzdChhdHRyaWJ1dGVzKTtcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHN1YnN0aXR1dGVWYXJpYWJsZXNJbkF0dHJpYnV0ZXMocGFyc2VkLCBzZXNzaW9uQXR0cnMsIFsnREFUQS1JRCcsICdMQU5HVUFHRScsICdWQUxVRScsICdVUkknXSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgY29uc3QgZGF0YUlkID0gc2Vzc2lvbkF0dHJzWydEQVRBLUlEJ107XG4gICAgICAgICAgICAgIGlmIChkYXRhSWQpIHtcbiAgICAgICAgICAgICAgICBpZiAocGFyc2VkLnNlc3Npb25EYXRhID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICBwYXJzZWQuc2Vzc2lvbkRhdGEgPSB7fTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcGFyc2VkLnNlc3Npb25EYXRhW2RhdGFJZF0gPSBzZXNzaW9uQXR0cnM7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgY2FzZSAnU0VTU0lPTi1LRVknOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAvLyAjRVhULVgtU0VTU0lPTi1LRVlcbiAgICAgICAgICAgICAgY29uc3Qgc2Vzc2lvbktleSA9IHBhcnNlS2V5KGF0dHJpYnV0ZXMsIGJhc2V1cmwsIHBhcnNlZCk7XG4gICAgICAgICAgICAgIGlmIChzZXNzaW9uS2V5LmVuY3J5cHRlZCAmJiBzZXNzaW9uS2V5LmlzU3VwcG9ydGVkKCkpIHtcbiAgICAgICAgICAgICAgICBpZiAocGFyc2VkLnNlc3Npb25LZXlzID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICBwYXJzZWQuc2Vzc2lvbktleXMgPSBbXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcGFyc2VkLnNlc3Npb25LZXlzLnB1c2goc2Vzc2lvbktleSk7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYFtLZXlzXSBJZ25vcmluZyBpbnZhbGlkIEVYVC1YLVNFU1NJT04tS0VZIHRhZzogXCIke2F0dHJpYnV0ZXN9XCJgKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICBjYXNlICdERUZJTkUnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAvLyAjRVhULVgtREVGSU5FXG4gICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBjb25zdCB2YXJpYWJsZUF0dHJpYnV0ZXMgPSBuZXcgQXR0ckxpc3QoYXR0cmlidXRlcyk7XG4gICAgICAgICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhwYXJzZWQsIHZhcmlhYmxlQXR0cmlidXRlcywgWydOQU1FJywgJ1ZBTFVFJywgJ1FVRVJZUEFSQU0nXSk7XG4gICAgICAgICAgICAgICAgYWRkVmFyaWFibGVEZWZpbml0aW9uKHBhcnNlZCwgdmFyaWFibGVBdHRyaWJ1dGVzLCBiYXNldXJsKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICBjYXNlICdDT05URU5ULVNURUVSSU5HJzpcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgLy8gI0VYVC1YLUNPTlRFTlQtU1RFRVJJTkdcbiAgICAgICAgICAgICAgY29uc3QgY29udGVudFN0ZWVyaW5nQXR0cmlidXRlcyA9IG5ldyBBdHRyTGlzdChhdHRyaWJ1dGVzKTtcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHN1YnN0aXR1dGVWYXJpYWJsZXNJbkF0dHJpYnV0ZXMocGFyc2VkLCBjb250ZW50U3RlZXJpbmdBdHRyaWJ1dGVzLCBbJ1NFUlZFUi1VUkknLCAnUEFUSFdBWS1JRCddKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBwYXJzZWQuY29udGVudFN0ZWVyaW5nID0ge1xuICAgICAgICAgICAgICAgIHVyaTogTTNVOFBhcnNlci5yZXNvbHZlKGNvbnRlbnRTdGVlcmluZ0F0dHJpYnV0ZXNbJ1NFUlZFUi1VUkknXSwgYmFzZXVybCksXG4gICAgICAgICAgICAgICAgcGF0aHdheUlkOiBjb250ZW50U3RlZXJpbmdBdHRyaWJ1dGVzWydQQVRIV0FZLUlEJ10gfHwgJy4nXG4gICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIGNhc2UgJ1NUQVJUJzpcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgLy8gI0VYVC1YLVNUQVJUXG4gICAgICAgICAgICAgIHBhcnNlZC5zdGFydFRpbWVPZmZzZXQgPSBwYXJzZVN0YXJ0VGltZU9mZnNldChhdHRyaWJ1dGVzKTtcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgLy8gRmlsdGVyIG91dCBsZXZlbHMgd2l0aCB1bmtub3duIGNvZGVjcyBpZiBpdCBkb2VzIG5vdCByZW1vdmUgYWxsIGxldmVsc1xuICAgIGNvbnN0IHN0cmlwVW5rbm93bkNvZGVjTGV2ZWxzID0gbGV2ZWxzV2l0aEtub3duQ29kZWNzLmxlbmd0aCA+IDAgJiYgbGV2ZWxzV2l0aEtub3duQ29kZWNzLmxlbmd0aCA8IHBhcnNlZC5sZXZlbHMubGVuZ3RoO1xuICAgIHBhcnNlZC5sZXZlbHMgPSBzdHJpcFVua25vd25Db2RlY0xldmVscyA/IGxldmVsc1dpdGhLbm93bkNvZGVjcyA6IHBhcnNlZC5sZXZlbHM7XG4gICAgaWYgKHBhcnNlZC5sZXZlbHMubGVuZ3RoID09PSAwKSB7XG4gICAgICBwYXJzZWQucGxheWxpc3RQYXJzaW5nRXJyb3IgPSBuZXcgRXJyb3IoJ25vIGxldmVscyBmb3VuZCBpbiBtYW5pZmVzdCcpO1xuICAgIH1cbiAgICByZXR1cm4gcGFyc2VkO1xuICB9XG4gIHN0YXRpYyBwYXJzZU1hc3RlclBsYXlsaXN0TWVkaWEoc3RyaW5nLCBiYXNldXJsLCBwYXJzZWQpIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGNvbnN0IHJlc3VsdHMgPSB7fTtcbiAgICBjb25zdCBsZXZlbHMgPSBwYXJzZWQubGV2ZWxzO1xuICAgIGNvbnN0IGdyb3Vwc0J5VHlwZSA9IHtcbiAgICAgIEFVRElPOiBsZXZlbHMubWFwKGxldmVsID0+ICh7XG4gICAgICAgIGlkOiBsZXZlbC5hdHRycy5BVURJTyxcbiAgICAgICAgYXVkaW9Db2RlYzogbGV2ZWwuYXVkaW9Db2RlY1xuICAgICAgfSkpLFxuICAgICAgU1VCVElUTEVTOiBsZXZlbHMubWFwKGxldmVsID0+ICh7XG4gICAgICAgIGlkOiBsZXZlbC5hdHRycy5TVUJUSVRMRVMsXG4gICAgICAgIHRleHRDb2RlYzogbGV2ZWwudGV4dENvZGVjXG4gICAgICB9KSksXG4gICAgICAnQ0xPU0VELUNBUFRJT05TJzogW11cbiAgICB9O1xuICAgIGxldCBpZCA9IDA7XG4gICAgTUFTVEVSX1BMQVlMSVNUX01FRElBX1JFR0VYLmxhc3RJbmRleCA9IDA7XG4gICAgd2hpbGUgKChyZXN1bHQgPSBNQVNURVJfUExBWUxJU1RfTUVESUFfUkVHRVguZXhlYyhzdHJpbmcpKSAhPT0gbnVsbCkge1xuICAgICAgY29uc3QgYXR0cnMgPSBuZXcgQXR0ckxpc3QocmVzdWx0WzFdKTtcbiAgICAgIGNvbnN0IHR5cGUgPSBhdHRycy5UWVBFO1xuICAgICAgaWYgKHR5cGUpIHtcbiAgICAgICAgY29uc3QgZ3JvdXBzID0gZ3JvdXBzQnlUeXBlW3R5cGVdO1xuICAgICAgICBjb25zdCBtZWRpYXMgPSByZXN1bHRzW3R5cGVdIHx8IFtdO1xuICAgICAgICByZXN1bHRzW3R5cGVdID0gbWVkaWFzO1xuICAgICAgICB7XG4gICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhwYXJzZWQsIGF0dHJzLCBbJ1VSSScsICdHUk9VUC1JRCcsICdMQU5HVUFHRScsICdBU1NPQy1MQU5HVUFHRScsICdTVEFCTEUtUkVORElUSU9OLUlEJywgJ05BTUUnLCAnSU5TVFJFQU0tSUQnLCAnQ0hBUkFDVEVSSVNUSUNTJywgJ0NIQU5ORUxTJ10pO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGxhbmcgPSBhdHRycy5MQU5HVUFHRTtcbiAgICAgICAgY29uc3QgYXNzb2NMYW5nID0gYXR0cnNbJ0FTU09DLUxBTkdVQUdFJ107XG4gICAgICAgIGNvbnN0IGNoYW5uZWxzID0gYXR0cnMuQ0hBTk5FTFM7XG4gICAgICAgIGNvbnN0IGNoYXJhY3RlcmlzdGljcyA9IGF0dHJzLkNIQVJBQ1RFUklTVElDUztcbiAgICAgICAgY29uc3QgaW5zdHJlYW1JZCA9IGF0dHJzWydJTlNUUkVBTS1JRCddO1xuICAgICAgICBjb25zdCBtZWRpYSA9IHtcbiAgICAgICAgICBhdHRycyxcbiAgICAgICAgICBiaXRyYXRlOiAwLFxuICAgICAgICAgIGlkOiBpZCsrLFxuICAgICAgICAgIGdyb3VwSWQ6IGF0dHJzWydHUk9VUC1JRCddIHx8ICcnLFxuICAgICAgICAgIG5hbWU6IGF0dHJzLk5BTUUgfHwgbGFuZyB8fCAnJyxcbiAgICAgICAgICB0eXBlLFxuICAgICAgICAgIGRlZmF1bHQ6IGF0dHJzLmJvb2woJ0RFRkFVTFQnKSxcbiAgICAgICAgICBhdXRvc2VsZWN0OiBhdHRycy5ib29sKCdBVVRPU0VMRUNUJyksXG4gICAgICAgICAgZm9yY2VkOiBhdHRycy5ib29sKCdGT1JDRUQnKSxcbiAgICAgICAgICBsYW5nLFxuICAgICAgICAgIHVybDogYXR0cnMuVVJJID8gTTNVOFBhcnNlci5yZXNvbHZlKGF0dHJzLlVSSSwgYmFzZXVybCkgOiAnJ1xuICAgICAgICB9O1xuICAgICAgICBpZiAoYXNzb2NMYW5nKSB7XG4gICAgICAgICAgbWVkaWEuYXNzb2NMYW5nID0gYXNzb2NMYW5nO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjaGFubmVscykge1xuICAgICAgICAgIG1lZGlhLmNoYW5uZWxzID0gY2hhbm5lbHM7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNoYXJhY3RlcmlzdGljcykge1xuICAgICAgICAgIG1lZGlhLmNoYXJhY3RlcmlzdGljcyA9IGNoYXJhY3RlcmlzdGljcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoaW5zdHJlYW1JZCkge1xuICAgICAgICAgIG1lZGlhLmluc3RyZWFtSWQgPSBpbnN0cmVhbUlkO1xuICAgICAgICB9XG4gICAgICAgIGlmIChncm91cHMgIT0gbnVsbCAmJiBncm91cHMubGVuZ3RoKSB7XG4gICAgICAgICAgLy8gSWYgdGhlcmUgYXJlIGF1ZGlvIG9yIHRleHQgZ3JvdXBzIHNpZ25hbGxlZCBpbiB0aGUgbWFuaWZlc3QsIGxldCdzIGxvb2sgZm9yIGEgbWF0Y2hpbmcgY29kZWMgc3RyaW5nIGZvciB0aGlzIHRyYWNrXG4gICAgICAgICAgLy8gSWYgd2UgZG9uJ3QgZmluZCB0aGUgdHJhY2sgc2lnbmFsbGVkLCBsZXRzIHVzZSB0aGUgZmlyc3QgYXVkaW8gZ3JvdXBzIGNvZGVjIHdlIGhhdmVcbiAgICAgICAgICAvLyBBY3RpbmcgYXMgYSBiZXN0IGd1ZXNzXG4gICAgICAgICAgY29uc3QgZ3JvdXBDb2RlYyA9IE0zVThQYXJzZXIuZmluZEdyb3VwKGdyb3VwcywgbWVkaWEuZ3JvdXBJZCkgfHwgZ3JvdXBzWzBdO1xuICAgICAgICAgIGFzc2lnbkNvZGVjKG1lZGlhLCBncm91cENvZGVjLCAnYXVkaW9Db2RlYycpO1xuICAgICAgICAgIGFzc2lnbkNvZGVjKG1lZGlhLCBncm91cENvZGVjLCAndGV4dENvZGVjJyk7XG4gICAgICAgIH1cbiAgICAgICAgbWVkaWFzLnB1c2gobWVkaWEpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfVxuICBzdGF0aWMgcGFyc2VMZXZlbFBsYXlsaXN0KHN0cmluZywgYmFzZXVybCwgaWQsIHR5cGUsIGxldmVsVXJsSWQsIG11bHRpdmFyaWFudFZhcmlhYmxlTGlzdCkge1xuICAgIGNvbnN0IGxldmVsID0gbmV3IExldmVsRGV0YWlscyhiYXNldXJsKTtcbiAgICBjb25zdCBmcmFnbWVudHMgPSBsZXZlbC5mcmFnbWVudHM7XG4gICAgLy8gVGhlIG1vc3QgcmVjZW50IGluaXQgc2VnbWVudCBzZWVuIChhcHBsaWVzIHRvIGFsbCBzdWJzZXF1ZW50IHNlZ21lbnRzKVxuICAgIGxldCBjdXJyZW50SW5pdFNlZ21lbnQgPSBudWxsO1xuICAgIGxldCBjdXJyZW50U04gPSAwO1xuICAgIGxldCBjdXJyZW50UGFydCA9IDA7XG4gICAgbGV0IHRvdGFsZHVyYXRpb24gPSAwO1xuICAgIGxldCBkaXNjb250aW51aXR5Q291bnRlciA9IDA7XG4gICAgbGV0IHByZXZGcmFnID0gbnVsbDtcbiAgICBsZXQgZnJhZyA9IG5ldyBGcmFnbWVudCh0eXBlLCBiYXNldXJsKTtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGxldCBpO1xuICAgIGxldCBsZXZlbGtleXM7XG4gICAgbGV0IGZpcnN0UGR0SW5kZXggPSAtMTtcbiAgICBsZXQgY3JlYXRlTmV4dEZyYWcgPSBmYWxzZTtcbiAgICBsZXQgbmV4dEJ5dGVSYW5nZSA9IG51bGw7XG4gICAgTEVWRUxfUExBWUxJU1RfUkVHRVhfRkFTVC5sYXN0SW5kZXggPSAwO1xuICAgIGxldmVsLm0zdTggPSBzdHJpbmc7XG4gICAgbGV2ZWwuaGFzVmFyaWFibGVSZWZzID0gaGFzVmFyaWFibGVSZWZlcmVuY2VzKHN0cmluZykgO1xuICAgIHdoaWxlICgocmVzdWx0ID0gTEVWRUxfUExBWUxJU1RfUkVHRVhfRkFTVC5leGVjKHN0cmluZykpICE9PSBudWxsKSB7XG4gICAgICBpZiAoY3JlYXRlTmV4dEZyYWcpIHtcbiAgICAgICAgY3JlYXRlTmV4dEZyYWcgPSBmYWxzZTtcbiAgICAgICAgZnJhZyA9IG5ldyBGcmFnbWVudCh0eXBlLCBiYXNldXJsKTtcbiAgICAgICAgLy8gc2V0dXAgdGhlIG5leHQgZnJhZ21lbnQgZm9yIHBhcnQgbG9hZGluZ1xuICAgICAgICBmcmFnLnN0YXJ0ID0gdG90YWxkdXJhdGlvbjtcbiAgICAgICAgZnJhZy5zbiA9IGN1cnJlbnRTTjtcbiAgICAgICAgZnJhZy5jYyA9IGRpc2NvbnRpbnVpdHlDb3VudGVyO1xuICAgICAgICBmcmFnLmxldmVsID0gaWQ7XG4gICAgICAgIGlmIChjdXJyZW50SW5pdFNlZ21lbnQpIHtcbiAgICAgICAgICBmcmFnLmluaXRTZWdtZW50ID0gY3VycmVudEluaXRTZWdtZW50O1xuICAgICAgICAgIGZyYWcucmF3UHJvZ3JhbURhdGVUaW1lID0gY3VycmVudEluaXRTZWdtZW50LnJhd1Byb2dyYW1EYXRlVGltZTtcbiAgICAgICAgICBjdXJyZW50SW5pdFNlZ21lbnQucmF3UHJvZ3JhbURhdGVUaW1lID0gbnVsbDtcbiAgICAgICAgICBpZiAobmV4dEJ5dGVSYW5nZSkge1xuICAgICAgICAgICAgZnJhZy5zZXRCeXRlUmFuZ2UobmV4dEJ5dGVSYW5nZSk7XG4gICAgICAgICAgICBuZXh0Qnl0ZVJhbmdlID0gbnVsbDtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGNvbnN0IGR1cmF0aW9uID0gcmVzdWx0WzFdO1xuICAgICAgaWYgKGR1cmF0aW9uKSB7XG4gICAgICAgIC8vIElORlxuICAgICAgICBmcmFnLmR1cmF0aW9uID0gcGFyc2VGbG9hdChkdXJhdGlvbik7XG4gICAgICAgIC8vIGF2b2lkIHNsaWNlZCBzdHJpbmdzICAgIGh0dHBzOi8vZ2l0aHViLmNvbS92aWRlby1kZXYvaGxzLmpzL2lzc3Vlcy85MzlcbiAgICAgICAgY29uc3QgdGl0bGUgPSAoJyAnICsgcmVzdWx0WzJdKS5zbGljZSgxKTtcbiAgICAgICAgZnJhZy50aXRsZSA9IHRpdGxlIHx8IG51bGw7XG4gICAgICAgIGZyYWcudGFnTGlzdC5wdXNoKHRpdGxlID8gWydJTkYnLCBkdXJhdGlvbiwgdGl0bGVdIDogWydJTkYnLCBkdXJhdGlvbl0pO1xuICAgICAgfSBlbHNlIGlmIChyZXN1bHRbM10pIHtcbiAgICAgICAgLy8gdXJsXG4gICAgICAgIGlmIChpc0Zpbml0ZU51bWJlcihmcmFnLmR1cmF0aW9uKSkge1xuICAgICAgICAgIGZyYWcuc3RhcnQgPSB0b3RhbGR1cmF0aW9uO1xuICAgICAgICAgIGlmIChsZXZlbGtleXMpIHtcbiAgICAgICAgICAgIHNldEZyYWdMZXZlbEtleXMoZnJhZywgbGV2ZWxrZXlzLCBsZXZlbCk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGZyYWcuc24gPSBjdXJyZW50U047XG4gICAgICAgICAgZnJhZy5sZXZlbCA9IGlkO1xuICAgICAgICAgIGZyYWcuY2MgPSBkaXNjb250aW51aXR5Q291bnRlcjtcbiAgICAgICAgICBmcmFnbWVudHMucHVzaChmcmFnKTtcbiAgICAgICAgICAvLyBhdm9pZCBzbGljZWQgc3RyaW5ncyAgICBodHRwczovL2dpdGh1Yi5jb20vdmlkZW8tZGV2L2hscy5qcy9pc3N1ZXMvOTM5XG4gICAgICAgICAgY29uc3QgdXJpID0gKCcgJyArIHJlc3VsdFszXSkuc2xpY2UoMSk7XG4gICAgICAgICAgZnJhZy5yZWx1cmwgPSBzdWJzdGl0dXRlVmFyaWFibGVzKGxldmVsLCB1cmkpIDtcbiAgICAgICAgICBhc3NpZ25Qcm9ncmFtRGF0ZVRpbWUoZnJhZywgcHJldkZyYWcpO1xuICAgICAgICAgIHByZXZGcmFnID0gZnJhZztcbiAgICAgICAgICB0b3RhbGR1cmF0aW9uICs9IGZyYWcuZHVyYXRpb247XG4gICAgICAgICAgY3VycmVudFNOKys7XG4gICAgICAgICAgY3VycmVudFBhcnQgPSAwO1xuICAgICAgICAgIGNyZWF0ZU5leHRGcmFnID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmIChyZXN1bHRbNF0pIHtcbiAgICAgICAgLy8gWC1CWVRFUkFOR0VcbiAgICAgICAgY29uc3QgZGF0YSA9ICgnICcgKyByZXN1bHRbNF0pLnNsaWNlKDEpO1xuICAgICAgICBpZiAocHJldkZyYWcpIHtcbiAgICAgICAgICBmcmFnLnNldEJ5dGVSYW5nZShkYXRhLCBwcmV2RnJhZyk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgZnJhZy5zZXRCeXRlUmFuZ2UoZGF0YSk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAocmVzdWx0WzVdKSB7XG4gICAgICAgIC8vIFBST0dSQU0tREFURS1USU1FXG4gICAgICAgIC8vIGF2b2lkIHNsaWNlZCBzdHJpbmdzICAgIGh0dHBzOi8vZ2l0aHViLmNvbS92aWRlby1kZXYvaGxzLmpzL2lzc3Vlcy85MzlcbiAgICAgICAgZnJhZy5yYXdQcm9ncmFtRGF0ZVRpbWUgPSAoJyAnICsgcmVzdWx0WzVdKS5zbGljZSgxKTtcbiAgICAgICAgZnJhZy50YWdMaXN0LnB1c2goWydQUk9HUkFNLURBVEUtVElNRScsIGZyYWcucmF3UHJvZ3JhbURhdGVUaW1lXSk7XG4gICAgICAgIGlmIChmaXJzdFBkdEluZGV4ID09PSAtMSkge1xuICAgICAgICAgIGZpcnN0UGR0SW5kZXggPSBmcmFnbWVudHMubGVuZ3RoO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXN1bHQgPSByZXN1bHRbMF0ubWF0Y2goTEVWRUxfUExBWUxJU1RfUkVHRVhfU0xPVyk7XG4gICAgICAgIGlmICghcmVzdWx0KSB7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oJ05vIG1hdGNoZXMgb24gc2xvdyByZWdleCBtYXRjaCBmb3IgbGV2ZWwgcGxheWxpc3QhJyk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgZm9yIChpID0gMTsgaSA8IHJlc3VsdC5sZW5ndGg7IGkrKykge1xuICAgICAgICAgIGlmICh0eXBlb2YgcmVzdWx0W2ldICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gYXZvaWQgc2xpY2VkIHN0cmluZ3MgICAgaHR0cHM6Ly9naXRodWIuY29tL3ZpZGVvLWRldi9obHMuanMvaXNzdWVzLzkzOVxuICAgICAgICBjb25zdCB0YWcgPSAoJyAnICsgcmVzdWx0W2ldKS5zbGljZSgxKTtcbiAgICAgICAgY29uc3QgdmFsdWUxID0gKCcgJyArIHJlc3VsdFtpICsgMV0pLnNsaWNlKDEpO1xuICAgICAgICBjb25zdCB2YWx1ZTIgPSByZXN1bHRbaSArIDJdID8gKCcgJyArIHJlc3VsdFtpICsgMl0pLnNsaWNlKDEpIDogJyc7XG4gICAgICAgIHN3aXRjaCAodGFnKSB7XG4gICAgICAgICAgY2FzZSAnUExBWUxJU1QtVFlQRSc6XG4gICAgICAgICAgICBsZXZlbC50eXBlID0gdmFsdWUxLnRvVXBwZXJDYXNlKCk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdNRURJQS1TRVFVRU5DRSc6XG4gICAgICAgICAgICBjdXJyZW50U04gPSBsZXZlbC5zdGFydFNOID0gcGFyc2VJbnQodmFsdWUxKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ1NLSVAnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBjb25zdCBza2lwQXR0cnMgPSBuZXcgQXR0ckxpc3QodmFsdWUxKTtcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHN1YnN0aXR1dGVWYXJpYWJsZXNJbkF0dHJpYnV0ZXMobGV2ZWwsIHNraXBBdHRycywgWydSRUNFTlRMWS1SRU1PVkVELURBVEVSQU5HRVMnXSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgY29uc3Qgc2tpcHBlZFNlZ21lbnRzID0gc2tpcEF0dHJzLmRlY2ltYWxJbnRlZ2VyKCdTS0lQUEVELVNFR01FTlRTJyk7XG4gICAgICAgICAgICAgIGlmIChpc0Zpbml0ZU51bWJlcihza2lwcGVkU2VnbWVudHMpKSB7XG4gICAgICAgICAgICAgICAgbGV2ZWwuc2tpcHBlZFNlZ21lbnRzID0gc2tpcHBlZFNlZ21lbnRzO1xuICAgICAgICAgICAgICAgIC8vIFRoaXMgd2lsbCByZXN1bHQgaW4gZnJhZ21lbnRzW10gY29udGFpbmluZyB1bmRlZmluZWQgdmFsdWVzLCB3aGljaCB3ZSB3aWxsIGZpbGwgaW4gd2l0aCBgbWVyZ2VEZXRhaWxzYFxuICAgICAgICAgICAgICAgIGZvciAobGV0IF9pID0gc2tpcHBlZFNlZ21lbnRzOyBfaS0tOykge1xuICAgICAgICAgICAgICAgICAgZnJhZ21lbnRzLnVuc2hpZnQobnVsbCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGN1cnJlbnRTTiArPSBza2lwcGVkU2VnbWVudHM7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgY29uc3QgcmVjZW50bHlSZW1vdmVkRGF0ZXJhbmdlcyA9IHNraXBBdHRycy5lbnVtZXJhdGVkU3RyaW5nKCdSRUNFTlRMWS1SRU1PVkVELURBVEVSQU5HRVMnKTtcbiAgICAgICAgICAgICAgaWYgKHJlY2VudGx5UmVtb3ZlZERhdGVyYW5nZXMpIHtcbiAgICAgICAgICAgICAgICBsZXZlbC5yZWNlbnRseVJlbW92ZWREYXRlcmFuZ2VzID0gcmVjZW50bHlSZW1vdmVkRGF0ZXJhbmdlcy5zcGxpdCgnXFx0Jyk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgY2FzZSAnVEFSR0VURFVSQVRJT04nOlxuICAgICAgICAgICAgbGV2ZWwudGFyZ2V0ZHVyYXRpb24gPSBNYXRoLm1heChwYXJzZUludCh2YWx1ZTEpLCAxKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ1ZFUlNJT04nOlxuICAgICAgICAgICAgbGV2ZWwudmVyc2lvbiA9IHBhcnNlSW50KHZhbHVlMSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdJTkRFUEVOREVOVC1TRUdNRU5UUyc6XG4gICAgICAgICAgY2FzZSAnRVhUTTNVJzpcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ0VORExJU1QnOlxuICAgICAgICAgICAgbGV2ZWwubGl2ZSA9IGZhbHNlO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnIyc6XG4gICAgICAgICAgICBpZiAodmFsdWUxIHx8IHZhbHVlMikge1xuICAgICAgICAgICAgICBmcmFnLnRhZ0xpc3QucHVzaCh2YWx1ZTIgPyBbdmFsdWUxLCB2YWx1ZTJdIDogW3ZhbHVlMV0pO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnRElTQ09OVElOVUlUWSc6XG4gICAgICAgICAgICBkaXNjb250aW51aXR5Q291bnRlcisrO1xuICAgICAgICAgICAgZnJhZy50YWdMaXN0LnB1c2goWydESVMnXSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdHQVAnOlxuICAgICAgICAgICAgZnJhZy5nYXAgPSB0cnVlO1xuICAgICAgICAgICAgZnJhZy50YWdMaXN0LnB1c2goW3RhZ10pO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAnQklUUkFURSc6XG4gICAgICAgICAgICBmcmFnLnRhZ0xpc3QucHVzaChbdGFnLCB2YWx1ZTFdKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ0RBVEVSQU5HRSc6XG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgIGNvbnN0IGRhdGVSYW5nZUF0dHIgPSBuZXcgQXR0ckxpc3QodmFsdWUxKTtcbiAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHN1YnN0aXR1dGVWYXJpYWJsZXNJbkF0dHJpYnV0ZXMobGV2ZWwsIGRhdGVSYW5nZUF0dHIsIFsnSUQnLCAnQ0xBU1MnLCAnU1RBUlQtREFURScsICdFTkQtREFURScsICdTQ1RFMzUtQ01EJywgJ1NDVEUzNS1PVVQnLCAnU0NURTM1LUlOJ10pO1xuICAgICAgICAgICAgICAgIHN1YnN0aXR1dGVWYXJpYWJsZXNJbkF0dHJpYnV0ZXMobGV2ZWwsIGRhdGVSYW5nZUF0dHIsIGRhdGVSYW5nZUF0dHIuY2xpZW50QXR0cnMpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGNvbnN0IGRhdGVSYW5nZSA9IG5ldyBEYXRlUmFuZ2UoZGF0ZVJhbmdlQXR0ciwgbGV2ZWwuZGF0ZVJhbmdlc1tkYXRlUmFuZ2VBdHRyLklEXSk7XG4gICAgICAgICAgICAgIGlmIChkYXRlUmFuZ2UuaXNWYWxpZCB8fCBsZXZlbC5za2lwcGVkU2VnbWVudHMpIHtcbiAgICAgICAgICAgICAgICBsZXZlbC5kYXRlUmFuZ2VzW2RhdGVSYW5nZS5pZF0gPSBkYXRlUmFuZ2U7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYElnbm9yaW5nIGludmFsaWQgREFURVJBTkdFIHRhZzogXCIke3ZhbHVlMX1cImApO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIC8vIEFkZCB0byBmcmFnbWVudCB0YWcgbGlzdCBmb3IgYmFja3dhcmRzIGNvbXBhdGliaWxpdHkgKDwgdjEuMi4wKVxuICAgICAgICAgICAgICBmcmFnLnRhZ0xpc3QucHVzaChbJ0VYVC1YLURBVEVSQU5HRScsIHZhbHVlMV0pO1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICBjYXNlICdERUZJTkUnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgY29uc3QgdmFyaWFibGVBdHRyaWJ1dGVzID0gbmV3IEF0dHJMaXN0KHZhbHVlMSk7XG4gICAgICAgICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhsZXZlbCwgdmFyaWFibGVBdHRyaWJ1dGVzLCBbJ05BTUUnLCAnVkFMVUUnLCAnSU1QT1JUJywgJ1FVRVJZUEFSQU0nXSk7XG4gICAgICAgICAgICAgICAgaWYgKCdJTVBPUlQnIGluIHZhcmlhYmxlQXR0cmlidXRlcykge1xuICAgICAgICAgICAgICAgICAgaW1wb3J0VmFyaWFibGVEZWZpbml0aW9uKGxldmVsLCB2YXJpYWJsZUF0dHJpYnV0ZXMsIG11bHRpdmFyaWFudFZhcmlhYmxlTGlzdCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIGFkZFZhcmlhYmxlRGVmaW5pdGlvbihsZXZlbCwgdmFyaWFibGVBdHRyaWJ1dGVzLCBiYXNldXJsKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgY2FzZSAnRElTQ09OVElOVUlUWS1TRVFVRU5DRSc6XG4gICAgICAgICAgICBkaXNjb250aW51aXR5Q291bnRlciA9IHBhcnNlSW50KHZhbHVlMSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdLRVknOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBjb25zdCBsZXZlbEtleSA9IHBhcnNlS2V5KHZhbHVlMSwgYmFzZXVybCwgbGV2ZWwpO1xuICAgICAgICAgICAgICBpZiAobGV2ZWxLZXkuaXNTdXBwb3J0ZWQoKSkge1xuICAgICAgICAgICAgICAgIGlmIChsZXZlbEtleS5tZXRob2QgPT09ICdOT05FJykge1xuICAgICAgICAgICAgICAgICAgbGV2ZWxrZXlzID0gdW5kZWZpbmVkO1xuICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmICghbGV2ZWxrZXlzKSB7XG4gICAgICAgICAgICAgICAgICBsZXZlbGtleXMgPSB7fTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKGxldmVsa2V5c1tsZXZlbEtleS5rZXlGb3JtYXRdKSB7XG4gICAgICAgICAgICAgICAgICBsZXZlbGtleXMgPSBfZXh0ZW5kcyh7fSwgbGV2ZWxrZXlzKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgbGV2ZWxrZXlzW2xldmVsS2V5LmtleUZvcm1hdF0gPSBsZXZlbEtleTtcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIud2FybihgW0tleXNdIElnbm9yaW5nIGludmFsaWQgRVhULVgtS0VZIHRhZzogXCIke3ZhbHVlMX1cImApO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIGNhc2UgJ1NUQVJUJzpcbiAgICAgICAgICAgIGxldmVsLnN0YXJ0VGltZU9mZnNldCA9IHBhcnNlU3RhcnRUaW1lT2Zmc2V0KHZhbHVlMSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdNQVAnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBjb25zdCBtYXBBdHRycyA9IG5ldyBBdHRyTGlzdCh2YWx1ZTEpO1xuICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhsZXZlbCwgbWFwQXR0cnMsIFsnQllURVJBTkdFJywgJ1VSSSddKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZiAoZnJhZy5kdXJhdGlvbikge1xuICAgICAgICAgICAgICAgIC8vIEluaXRpYWwgc2VnbWVudCB0YWcgaXMgYWZ0ZXIgc2VnbWVudCBkdXJhdGlvbiB0YWcuXG4gICAgICAgICAgICAgICAgLy8gICAjRVhUSU5GOiA2LjBcbiAgICAgICAgICAgICAgICAvLyAgICNFWFQtWC1NQVA6VVJJPVwiaW5pdC5tcDRcbiAgICAgICAgICAgICAgICBjb25zdCBpbml0ID0gbmV3IEZyYWdtZW50KHR5cGUsIGJhc2V1cmwpO1xuICAgICAgICAgICAgICAgIHNldEluaXRTZWdtZW50KGluaXQsIG1hcEF0dHJzLCBpZCwgbGV2ZWxrZXlzKTtcbiAgICAgICAgICAgICAgICBjdXJyZW50SW5pdFNlZ21lbnQgPSBpbml0O1xuICAgICAgICAgICAgICAgIGZyYWcuaW5pdFNlZ21lbnQgPSBjdXJyZW50SW5pdFNlZ21lbnQ7XG4gICAgICAgICAgICAgICAgaWYgKGN1cnJlbnRJbml0U2VnbWVudC5yYXdQcm9ncmFtRGF0ZVRpbWUgJiYgIWZyYWcucmF3UHJvZ3JhbURhdGVUaW1lKSB7XG4gICAgICAgICAgICAgICAgICBmcmFnLnJhd1Byb2dyYW1EYXRlVGltZSA9IGN1cnJlbnRJbml0U2VnbWVudC5yYXdQcm9ncmFtRGF0ZVRpbWU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIEluaXRpYWwgc2VnbWVudCB0YWcgaXMgYmVmb3JlIHNlZ21lbnQgZHVyYXRpb24gdGFnXG4gICAgICAgICAgICAgICAgLy8gSGFuZGxlIGNhc2Ugd2hlcmUgRVhULVgtTUFQIGlzIGRlY2xhcmVkIGFmdGVyIEVYVC1YLUJZVEVSQU5HRVxuICAgICAgICAgICAgICAgIGNvbnN0IGVuZCA9IGZyYWcuYnl0ZVJhbmdlRW5kT2Zmc2V0O1xuICAgICAgICAgICAgICAgIGlmIChlbmQpIHtcbiAgICAgICAgICAgICAgICAgIGNvbnN0IHN0YXJ0ID0gZnJhZy5ieXRlUmFuZ2VTdGFydE9mZnNldDtcbiAgICAgICAgICAgICAgICAgIG5leHRCeXRlUmFuZ2UgPSBgJHtlbmQgLSBzdGFydH1AJHtzdGFydH1gO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICBuZXh0Qnl0ZVJhbmdlID0gbnVsbDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgc2V0SW5pdFNlZ21lbnQoZnJhZywgbWFwQXR0cnMsIGlkLCBsZXZlbGtleXMpO1xuICAgICAgICAgICAgICAgIGN1cnJlbnRJbml0U2VnbWVudCA9IGZyYWc7XG4gICAgICAgICAgICAgICAgY3JlYXRlTmV4dEZyYWcgPSB0cnVlO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIGNhc2UgJ1NFUlZFUi1DT05UUk9MJzpcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgY29uc3Qgc2VydmVyQ29udHJvbEF0dHJzID0gbmV3IEF0dHJMaXN0KHZhbHVlMSk7XG4gICAgICAgICAgICAgIGxldmVsLmNhbkJsb2NrUmVsb2FkID0gc2VydmVyQ29udHJvbEF0dHJzLmJvb2woJ0NBTi1CTE9DSy1SRUxPQUQnKTtcbiAgICAgICAgICAgICAgbGV2ZWwuY2FuU2tpcFVudGlsID0gc2VydmVyQ29udHJvbEF0dHJzLm9wdGlvbmFsRmxvYXQoJ0NBTi1TS0lQLVVOVElMJywgMCk7XG4gICAgICAgICAgICAgIGxldmVsLmNhblNraXBEYXRlUmFuZ2VzID0gbGV2ZWwuY2FuU2tpcFVudGlsID4gMCAmJiBzZXJ2ZXJDb250cm9sQXR0cnMuYm9vbCgnQ0FOLVNLSVAtREFURVJBTkdFUycpO1xuICAgICAgICAgICAgICBsZXZlbC5wYXJ0SG9sZEJhY2sgPSBzZXJ2ZXJDb250cm9sQXR0cnMub3B0aW9uYWxGbG9hdCgnUEFSVC1IT0xELUJBQ0snLCAwKTtcbiAgICAgICAgICAgICAgbGV2ZWwuaG9sZEJhY2sgPSBzZXJ2ZXJDb250cm9sQXR0cnMub3B0aW9uYWxGbG9hdCgnSE9MRC1CQUNLJywgMCk7XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIGNhc2UgJ1BBUlQtSU5GJzpcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgY29uc3QgcGFydEluZkF0dHJzID0gbmV3IEF0dHJMaXN0KHZhbHVlMSk7XG4gICAgICAgICAgICAgIGxldmVsLnBhcnRUYXJnZXQgPSBwYXJ0SW5mQXR0cnMuZGVjaW1hbEZsb2F0aW5nUG9pbnQoJ1BBUlQtVEFSR0VUJyk7XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIGNhc2UgJ1BBUlQnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBsZXQgcGFydExpc3QgPSBsZXZlbC5wYXJ0TGlzdDtcbiAgICAgICAgICAgICAgaWYgKCFwYXJ0TGlzdCkge1xuICAgICAgICAgICAgICAgIHBhcnRMaXN0ID0gbGV2ZWwucGFydExpc3QgPSBbXTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBjb25zdCBwcmV2aW91c0ZyYWdtZW50UGFydCA9IGN1cnJlbnRQYXJ0ID4gMCA/IHBhcnRMaXN0W3BhcnRMaXN0Lmxlbmd0aCAtIDFdIDogdW5kZWZpbmVkO1xuICAgICAgICAgICAgICBjb25zdCBpbmRleCA9IGN1cnJlbnRQYXJ0Kys7XG4gICAgICAgICAgICAgIGNvbnN0IHBhcnRBdHRycyA9IG5ldyBBdHRyTGlzdCh2YWx1ZTEpO1xuICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhsZXZlbCwgcGFydEF0dHJzLCBbJ0JZVEVSQU5HRScsICdVUkknXSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgY29uc3QgcGFydCA9IG5ldyBQYXJ0KHBhcnRBdHRycywgZnJhZywgYmFzZXVybCwgaW5kZXgsIHByZXZpb3VzRnJhZ21lbnRQYXJ0KTtcbiAgICAgICAgICAgICAgcGFydExpc3QucHVzaChwYXJ0KTtcbiAgICAgICAgICAgICAgZnJhZy5kdXJhdGlvbiArPSBwYXJ0LmR1cmF0aW9uO1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICBjYXNlICdQUkVMT0FELUhJTlQnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBjb25zdCBwcmVsb2FkSGludEF0dHJzID0gbmV3IEF0dHJMaXN0KHZhbHVlMSk7XG4gICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBzdWJzdGl0dXRlVmFyaWFibGVzSW5BdHRyaWJ1dGVzKGxldmVsLCBwcmVsb2FkSGludEF0dHJzLCBbJ1VSSSddKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBsZXZlbC5wcmVsb2FkSGludCA9IHByZWxvYWRIaW50QXR0cnM7XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIGNhc2UgJ1JFTkRJVElPTi1SRVBPUlQnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBjb25zdCByZW5kaXRpb25SZXBvcnRBdHRycyA9IG5ldyBBdHRyTGlzdCh2YWx1ZTEpO1xuICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgc3Vic3RpdHV0ZVZhcmlhYmxlc0luQXR0cmlidXRlcyhsZXZlbCwgcmVuZGl0aW9uUmVwb3J0QXR0cnMsIFsnVVJJJ10pO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGxldmVsLnJlbmRpdGlvblJlcG9ydHMgPSBsZXZlbC5yZW5kaXRpb25SZXBvcnRzIHx8IFtdO1xuICAgICAgICAgICAgICBsZXZlbC5yZW5kaXRpb25SZXBvcnRzLnB1c2gocmVuZGl0aW9uUmVwb3J0QXR0cnMpO1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgbG9nZ2VyLndhcm4oYGxpbmUgcGFyc2VkIGJ1dCBub3QgaGFuZGxlZDogJHtyZXN1bHR9YCk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBpZiAocHJldkZyYWcgJiYgIXByZXZGcmFnLnJlbHVybCkge1xuICAgICAgZnJhZ21lbnRzLnBvcCgpO1xuICAgICAgdG90YWxkdXJhdGlvbiAtPSBwcmV2RnJhZy5kdXJhdGlvbjtcbiAgICAgIGlmIChsZXZlbC5wYXJ0TGlzdCkge1xuICAgICAgICBsZXZlbC5mcmFnbWVudEhpbnQgPSBwcmV2RnJhZztcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKGxldmVsLnBhcnRMaXN0KSB7XG4gICAgICBhc3NpZ25Qcm9ncmFtRGF0ZVRpbWUoZnJhZywgcHJldkZyYWcpO1xuICAgICAgZnJhZy5jYyA9IGRpc2NvbnRpbnVpdHlDb3VudGVyO1xuICAgICAgbGV2ZWwuZnJhZ21lbnRIaW50ID0gZnJhZztcbiAgICAgIGlmIChsZXZlbGtleXMpIHtcbiAgICAgICAgc2V0RnJhZ0xldmVsS2V5cyhmcmFnLCBsZXZlbGtleXMsIGxldmVsKTtcbiAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgZnJhZ21lbnRMZW5ndGggPSBmcmFnbWVudHMubGVuZ3RoO1xuICAgIGNvbnN0IGZpcnN0RnJhZ21lbnQgPSBmcmFnbWVudHNbMF07XG4gICAgY29uc3QgbGFzdEZyYWdtZW50ID0gZnJhZ21lbnRzW2ZyYWdtZW50TGVuZ3RoIC0gMV07XG4gICAgdG90YWxkdXJhdGlvbiArPSBsZXZlbC5za2lwcGVkU2VnbWVudHMgKiBsZXZlbC50YXJnZXRkdXJhdGlvbjtcbiAgICBpZiAodG90YWxkdXJhdGlvbiA+IDAgJiYgZnJhZ21lbnRMZW5ndGggJiYgbGFzdEZyYWdtZW50KSB7XG4gICAgICBsZXZlbC5hdmVyYWdldGFyZ2V0ZHVyYXRpb24gPSB0b3RhbGR1cmF0aW9uIC8gZnJhZ21lbnRMZW5ndGg7XG4gICAgICBjb25zdCBsYXN0U24gPSBsYXN0RnJhZ21lbnQuc247XG4gICAgICBsZXZlbC5lbmRTTiA9IGxhc3RTbiAhPT0gJ2luaXRTZWdtZW50JyA/IGxhc3RTbiA6IDA7XG4gICAgICBpZiAoIWxldmVsLmxpdmUpIHtcbiAgICAgICAgbGFzdEZyYWdtZW50LmVuZExpc3QgPSB0cnVlO1xuICAgICAgfVxuICAgICAgaWYgKGZpcnN0RnJhZ21lbnQpIHtcbiAgICAgICAgbGV2ZWwuc3RhcnRDQyA9IGZpcnN0RnJhZ21lbnQuY2M7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGxldmVsLmVuZFNOID0gMDtcbiAgICAgIGxldmVsLnN0YXJ0Q0MgPSAwO1xuICAgIH1cbiAgICBpZiAobGV2ZWwuZnJhZ21lbnRIaW50KSB7XG4gICAgICB0b3RhbGR1cmF0aW9uICs9IGxldmVsLmZyYWdtZW50SGludC5kdXJhdGlvbjtcbiAgICB9XG4gICAgbGV2ZWwudG90YWxkdXJhdGlvbiA9IHRvdGFsZHVyYXRpb247XG4gICAgbGV2ZWwuZW5kQ0MgPSBkaXNjb250aW51aXR5Q291bnRlcjtcblxuICAgIC8qKlxuICAgICAqIEJhY2tmaWxsIGFueSBtaXNzaW5nIFBEVCB2YWx1ZXNcbiAgICAgKiBcIklmIHRoZSBmaXJzdCBFWFQtWC1QUk9HUkFNLURBVEUtVElNRSB0YWcgaW4gYSBQbGF5bGlzdCBhcHBlYXJzIGFmdGVyXG4gICAgICogb25lIG9yIG1vcmUgTWVkaWEgU2VnbWVudCBVUklzLCB0aGUgY2xpZW50IFNIT1VMRCBleHRyYXBvbGF0ZVxuICAgICAqIGJhY2t3YXJkIGZyb20gdGhhdCB0YWcgKHVzaW5nIEVYVElORiBkdXJhdGlvbnMgYW5kL29yIG1lZGlhXG4gICAgICogdGltZXN0YW1wcykgdG8gYXNzb2NpYXRlIGRhdGVzIHdpdGggdGhvc2Ugc2VnbWVudHMuXCJcbiAgICAgKiBXZSBoYXZlIGFscmVhZHkgZXh0cmFwb2xhdGVkIGZvcndhcmQsIGJ1dCBhbGwgZnJhZ21lbnRzIHVwIHRvIHRoZSBmaXJzdCBpbnN0YW5jZSBvZiBQRFQgZG8gbm90IGhhdmUgdGhlaXIgUERUc1xuICAgICAqIGNvbXB1dGVkLlxuICAgICAqL1xuICAgIGlmIChmaXJzdFBkdEluZGV4ID4gMCkge1xuICAgICAgYmFja2ZpbGxQcm9ncmFtRGF0ZVRpbWVzKGZyYWdtZW50cywgZmlyc3RQZHRJbmRleCk7XG4gICAgfVxuICAgIHJldHVybiBsZXZlbDtcbiAgfVxufVxuZnVuY3Rpb24gcGFyc2VLZXkoa2V5VGFnQXR0cmlidXRlcywgYmFzZXVybCwgcGFyc2VkKSB7XG4gIHZhciBfa2V5QXR0cnMkTUVUSE9ELCBfa2V5QXR0cnMkS0VZRk9STUFUO1xuICAvLyBodHRwczovL3Rvb2xzLmlldGYub3JnL2h0bWwvcmZjODIxNiNzZWN0aW9uLTQuMy4yLjRcbiAgY29uc3Qga2V5QXR0cnMgPSBuZXcgQXR0ckxpc3Qoa2V5VGFnQXR0cmlidXRlcyk7XG4gIHtcbiAgICBzdWJzdGl0dXRlVmFyaWFibGVzSW5BdHRyaWJ1dGVzKHBhcnNlZCwga2V5QXR0cnMsIFsnS0VZRk9STUFUJywgJ0tFWUZPUk1BVFZFUlNJT05TJywgJ1VSSScsICdJVicsICdVUkknXSk7XG4gIH1cbiAgY29uc3QgZGVjcnlwdG1ldGhvZCA9IChfa2V5QXR0cnMkTUVUSE9EID0ga2V5QXR0cnMuTUVUSE9EKSAhPSBudWxsID8gX2tleUF0dHJzJE1FVEhPRCA6ICcnO1xuICBjb25zdCBkZWNyeXB0dXJpID0ga2V5QXR0cnMuVVJJO1xuICBjb25zdCBkZWNyeXB0aXYgPSBrZXlBdHRycy5oZXhhZGVjaW1hbEludGVnZXIoJ0lWJyk7XG4gIGNvbnN0IGRlY3J5cHRrZXlmb3JtYXR2ZXJzaW9ucyA9IGtleUF0dHJzLktFWUZPUk1BVFZFUlNJT05TO1xuICAvLyBGcm9tIFJGQzogVGhpcyBhdHRyaWJ1dGUgaXMgT1BUSU9OQUw7IGl0cyBhYnNlbmNlIGluZGljYXRlcyBhbiBpbXBsaWNpdCB2YWx1ZSBvZiBcImlkZW50aXR5XCIuXG4gIGNvbnN0IGRlY3J5cHRrZXlmb3JtYXQgPSAoX2tleUF0dHJzJEtFWUZPUk1BVCA9IGtleUF0dHJzLktFWUZPUk1BVCkgIT0gbnVsbCA/IF9rZXlBdHRycyRLRVlGT1JNQVQgOiAnaWRlbnRpdHknO1xuICBpZiAoZGVjcnlwdHVyaSAmJiBrZXlBdHRycy5JViAmJiAhZGVjcnlwdGl2KSB7XG4gICAgbG9nZ2VyLmVycm9yKGBJbnZhbGlkIElWOiAke2tleUF0dHJzLklWfWApO1xuICB9XG4gIC8vIElmIGRlY3J5cHR1cmkgaXMgYSBVUkkgd2l0aCBhIHNjaGVtZSwgdGhlbiBiYXNldXJsIHdpbGwgYmUgaWdub3JlZFxuICAvLyBObyB1cmkgaXMgYWxsb3dlZCB3aGVuIE1FVEhPRCBpcyBOT05FXG4gIGNvbnN0IHJlc29sdmVkVXJpID0gZGVjcnlwdHVyaSA/IE0zVThQYXJzZXIucmVzb2x2ZShkZWNyeXB0dXJpLCBiYXNldXJsKSA6ICcnO1xuICBjb25zdCBrZXlGb3JtYXRWZXJzaW9ucyA9IChkZWNyeXB0a2V5Zm9ybWF0dmVyc2lvbnMgPyBkZWNyeXB0a2V5Zm9ybWF0dmVyc2lvbnMgOiAnMScpLnNwbGl0KCcvJykubWFwKE51bWJlcikuZmlsdGVyKE51bWJlci5pc0Zpbml0ZSk7XG4gIHJldHVybiBuZXcgTGV2ZWxLZXkoZGVjcnlwdG1ldGhvZCwgcmVzb2x2ZWRVcmksIGRlY3J5cHRrZXlmb3JtYXQsIGtleUZvcm1hdFZlcnNpb25zLCBkZWNyeXB0aXYpO1xufVxuZnVuY3Rpb24gcGFyc2VTdGFydFRpbWVPZmZzZXQoc3RhcnRBdHRyaWJ1dGVzKSB7XG4gIGNvbnN0IHN0YXJ0QXR0cnMgPSBuZXcgQXR0ckxpc3Qoc3RhcnRBdHRyaWJ1dGVzKTtcbiAgY29uc3Qgc3RhcnRUaW1lT2Zmc2V0ID0gc3RhcnRBdHRycy5kZWNpbWFsRmxvYXRpbmdQb2ludCgnVElNRS1PRkZTRVQnKTtcbiAgaWYgKGlzRmluaXRlTnVtYmVyKHN0YXJ0VGltZU9mZnNldCkpIHtcbiAgICByZXR1cm4gc3RhcnRUaW1lT2Zmc2V0O1xuICB9XG4gIHJldHVybiBudWxsO1xufVxuZnVuY3Rpb24gc2V0Q29kZWNzKGNvZGVjc0F0dHJpYnV0ZVZhbHVlLCBsZXZlbCkge1xuICBsZXQgY29kZWNzID0gKGNvZGVjc0F0dHJpYnV0ZVZhbHVlIHx8ICcnKS5zcGxpdCgvWyAsXSsvKS5maWx0ZXIoYyA9PiBjKTtcbiAgWyd2aWRlbycsICdhdWRpbycsICd0ZXh0J10uZm9yRWFjaCh0eXBlID0+IHtcbiAgICBjb25zdCBmaWx0ZXJlZCA9IGNvZGVjcy5maWx0ZXIoY29kZWMgPT4gaXNDb2RlY1R5cGUoY29kZWMsIHR5cGUpKTtcbiAgICBpZiAoZmlsdGVyZWQubGVuZ3RoKSB7XG4gICAgICAvLyBDb21tYSBzZXBhcmF0ZWQgbGlzdCBvZiBhbGwgY29kZWNzIGZvciB0eXBlXG4gICAgICBsZXZlbFtgJHt0eXBlfUNvZGVjYF0gPSBmaWx0ZXJlZC5qb2luKCcsJyk7XG4gICAgICAvLyBSZW1vdmUga25vd24gY29kZWNzIHNvIHRoYXQgb25seSB1bmtub3duQ29kZWNzIGFyZSBsZWZ0IGFmdGVyIGl0ZXJhdGluZyB0aHJvdWdoIGVhY2ggdHlwZVxuICAgICAgY29kZWNzID0gY29kZWNzLmZpbHRlcihjb2RlYyA9PiBmaWx0ZXJlZC5pbmRleE9mKGNvZGVjKSA9PT0gLTEpO1xuICAgIH1cbiAgfSk7XG4gIGxldmVsLnVua25vd25Db2RlY3MgPSBjb2RlY3M7XG59XG5mdW5jdGlvbiBhc3NpZ25Db2RlYyhtZWRpYSwgZ3JvdXBJdGVtLCBjb2RlY1Byb3BlcnR5KSB7XG4gIGNvbnN0IGNvZGVjVmFsdWUgPSBncm91cEl0ZW1bY29kZWNQcm9wZXJ0eV07XG4gIGlmIChjb2RlY1ZhbHVlKSB7XG4gICAgbWVkaWFbY29kZWNQcm9wZXJ0eV0gPSBjb2RlY1ZhbHVlO1xuICB9XG59XG5mdW5jdGlvbiBiYWNrZmlsbFByb2dyYW1EYXRlVGltZXMoZnJhZ21lbnRzLCBmaXJzdFBkdEluZGV4KSB7XG4gIGxldCBmcmFnUHJldiA9IGZyYWdtZW50c1tmaXJzdFBkdEluZGV4XTtcbiAgZm9yIChsZXQgaSA9IGZpcnN0UGR0SW5kZXg7IGktLTspIHtcbiAgICBjb25zdCBmcmFnID0gZnJhZ21lbnRzW2ldO1xuICAgIC8vIEV4aXQgb24gZGVsdGEtcGxheWxpc3Qgc2tpcHBlZCBzZWdtZW50c1xuICAgIGlmICghZnJhZykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBmcmFnLnByb2dyYW1EYXRlVGltZSA9IGZyYWdQcmV2LnByb2dyYW1EYXRlVGltZSAtIGZyYWcuZHVyYXRpb24gKiAxMDAwO1xuICAgIGZyYWdQcmV2ID0gZnJhZztcbiAgfVxufVxuZnVuY3Rpb24gYXNzaWduUHJvZ3JhbURhdGVUaW1lKGZyYWcsIHByZXZGcmFnKSB7XG4gIGlmIChmcmFnLnJhd1Byb2dyYW1EYXRlVGltZSkge1xuICAgIGZyYWcucHJvZ3JhbURhdGVUaW1lID0gRGF0ZS5wYXJzZShmcmFnLnJhd1Byb2dyYW1EYXRlVGltZSk7XG4gIH0gZWxzZSBpZiAocHJldkZyYWcgIT0gbnVsbCAmJiBwcmV2RnJhZy5wcm9ncmFtRGF0ZVRpbWUpIHtcbiAgICBmcmFnLnByb2dyYW1EYXRlVGltZSA9IHByZXZGcmFnLmVuZFByb2dyYW1EYXRlVGltZTtcbiAgfVxuICBpZiAoIWlzRmluaXRlTnVtYmVyKGZyYWcucHJvZ3JhbURhdGVUaW1lKSkge1xuICAgIGZyYWcucHJvZ3JhbURhdGVUaW1lID0gbnVsbDtcbiAgICBmcmFnLnJhd1Byb2dyYW1EYXRlVGltZSA9IG51bGw7XG4gIH1cbn1cbmZ1bmN0aW9uIHNldEluaXRTZWdtZW50KGZyYWcsIG1hcEF0dHJzLCBpZCwgbGV2ZWxrZXlzKSB7XG4gIGZyYWcucmVsdXJsID0gbWFwQXR0cnMuVVJJO1xuICBpZiAobWFwQXR0cnMuQllURVJBTkdFKSB7XG4gICAgZnJhZy5zZXRCeXRlUmFuZ2UobWFwQXR0cnMuQllURVJBTkdFKTtcbiAgfVxuICBmcmFnLmxldmVsID0gaWQ7XG4gIGZyYWcuc24gPSAnaW5pdFNlZ21lbnQnO1xuICBpZiAobGV2ZWxrZXlzKSB7XG4gICAgZnJhZy5sZXZlbGtleXMgPSBsZXZlbGtleXM7XG4gIH1cbiAgZnJhZy5pbml0U2VnbWVudCA9IG51bGw7XG59XG5mdW5jdGlvbiBzZXRGcmFnTGV2ZWxLZXlzKGZyYWcsIGxldmVsa2V5cywgbGV2ZWwpIHtcbiAgZnJhZy5sZXZlbGtleXMgPSBsZXZlbGtleXM7XG4gIGNvbnN0IHtcbiAgICBlbmNyeXB0ZWRGcmFnbWVudHNcbiAgfSA9IGxldmVsO1xuICBpZiAoKCFlbmNyeXB0ZWRGcmFnbWVudHMubGVuZ3RoIHx8IGVuY3J5cHRlZEZyYWdtZW50c1tlbmNyeXB0ZWRGcmFnbWVudHMubGVuZ3RoIC0gMV0ubGV2ZWxrZXlzICE9PSBsZXZlbGtleXMpICYmIE9iamVjdC5rZXlzKGxldmVsa2V5cykuc29tZShmb3JtYXQgPT4gbGV2ZWxrZXlzW2Zvcm1hdF0uaXNDb21tb25FbmNyeXB0aW9uKSkge1xuICAgIGVuY3J5cHRlZEZyYWdtZW50cy5wdXNoKGZyYWcpO1xuICB9XG59XG5cbnZhciBQbGF5bGlzdENvbnRleHRUeXBlID0ge1xuICBNQU5JRkVTVDogXCJtYW5pZmVzdFwiLFxuICBMRVZFTDogXCJsZXZlbFwiLFxuICBBVURJT19UUkFDSzogXCJhdWRpb1RyYWNrXCIsXG4gIFNVQlRJVExFX1RSQUNLOiBcInN1YnRpdGxlVHJhY2tcIlxufTtcbnZhciBQbGF5bGlzdExldmVsVHlwZSA9IHtcbiAgTUFJTjogXCJtYWluXCIsXG4gIEFVRElPOiBcImF1ZGlvXCIsXG4gIFNVQlRJVExFOiBcInN1YnRpdGxlXCJcbn07XG5cbmZ1bmN0aW9uIG1hcENvbnRleHRUb0xldmVsVHlwZShjb250ZXh0KSB7XG4gIGNvbnN0IHtcbiAgICB0eXBlXG4gIH0gPSBjb250ZXh0O1xuICBzd2l0Y2ggKHR5cGUpIHtcbiAgICBjYXNlIFBsYXlsaXN0Q29udGV4dFR5cGUuQVVESU9fVFJBQ0s6XG4gICAgICByZXR1cm4gUGxheWxpc3RMZXZlbFR5cGUuQVVESU87XG4gICAgY2FzZSBQbGF5bGlzdENvbnRleHRUeXBlLlNVQlRJVExFX1RSQUNLOlxuICAgICAgcmV0dXJuIFBsYXlsaXN0TGV2ZWxUeXBlLlNVQlRJVExFO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gUGxheWxpc3RMZXZlbFR5cGUuTUFJTjtcbiAgfVxufVxuZnVuY3Rpb24gZ2V0UmVzcG9uc2VVcmwocmVzcG9uc2UsIGNvbnRleHQpIHtcbiAgbGV0IHVybCA9IHJlc3BvbnNlLnVybDtcbiAgLy8gcmVzcG9uc2VVUkwgbm90IHN1cHBvcnRlZCBvbiBzb21lIGJyb3dzZXJzIChpdCBpcyB1c2VkIHRvIGRldGVjdCBVUkwgcmVkaXJlY3Rpb24pXG4gIC8vIGRhdGEtdXJpIG1vZGUgYWxzbyBub3Qgc3VwcG9ydGVkIChidXQgbm8gbmVlZCB0byBkZXRlY3QgcmVkaXJlY3Rpb24pXG4gIGlmICh1cmwgPT09IHVuZGVmaW5lZCB8fCB1cmwuaW5kZXhPZignZGF0YTonKSA9PT0gMCkge1xuICAgIC8vIGZhbGxiYWNrIHRvIGluaXRpYWwgVVJMXG4gICAgdXJsID0gY29udGV4dC51cmw7XG4gIH1cbiAgcmV0dXJuIHVybDtcbn1cbmNsYXNzIFBsYXlsaXN0TG9hZGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5sb2FkZXJzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLnZhcmlhYmxlTGlzdCA9IG51bGw7XG4gICAgdGhpcy5obHMgPSBobHM7XG4gICAgdGhpcy5yZWdpc3Rlckxpc3RlbmVycygpO1xuICB9XG4gIHN0YXJ0TG9hZChzdGFydFBvc2l0aW9uKSB7fVxuICBzdG9wTG9hZCgpIHtcbiAgICB0aGlzLmRlc3Ryb3lJbnRlcm5hbExvYWRlcnMoKTtcbiAgfVxuICByZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTEVWRUxfTE9BRElORywgdGhpcy5vbkxldmVsTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5BVURJT19UUkFDS19MT0FESU5HLCB0aGlzLm9uQXVkaW9UcmFja0xvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuU1VCVElUTEVfVFJBQ0tfTE9BRElORywgdGhpcy5vblN1YnRpdGxlVHJhY2tMb2FkaW5nLCB0aGlzKTtcbiAgfVxuICB1bnJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX0xPQURJTkcsIHRoaXMub25MZXZlbExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkFVRElPX1RSQUNLX0xPQURJTkcsIHRoaXMub25BdWRpb1RyYWNrTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuU1VCVElUTEVfVFJBQ0tfTE9BRElORywgdGhpcy5vblN1YnRpdGxlVHJhY2tMb2FkaW5nLCB0aGlzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGRlZmF1bHRzIG9yIGNvbmZpZ3VyZWQgbG9hZGVyLXR5cGUgb3ZlcmxvYWRzIChwTG9hZGVyIGFuZCBsb2FkZXIgY29uZmlnIHBhcmFtcylcbiAgICovXG4gIGNyZWF0ZUludGVybmFsTG9hZGVyKGNvbnRleHQpIHtcbiAgICBjb25zdCBjb25maWcgPSB0aGlzLmhscy5jb25maWc7XG4gICAgY29uc3QgUExvYWRlciA9IGNvbmZpZy5wTG9hZGVyO1xuICAgIGNvbnN0IExvYWRlciA9IGNvbmZpZy5sb2FkZXI7XG4gICAgY29uc3QgSW50ZXJuYWxMb2FkZXIgPSBQTG9hZGVyIHx8IExvYWRlcjtcbiAgICBjb25zdCBsb2FkZXIgPSBuZXcgSW50ZXJuYWxMb2FkZXIoY29uZmlnKTtcbiAgICB0aGlzLmxvYWRlcnNbY29udGV4dC50eXBlXSA9IGxvYWRlcjtcbiAgICByZXR1cm4gbG9hZGVyO1xuICB9XG4gIGdldEludGVybmFsTG9hZGVyKGNvbnRleHQpIHtcbiAgICByZXR1cm4gdGhpcy5sb2FkZXJzW2NvbnRleHQudHlwZV07XG4gIH1cbiAgcmVzZXRJbnRlcm5hbExvYWRlcihjb250ZXh0VHlwZSkge1xuICAgIGlmICh0aGlzLmxvYWRlcnNbY29udGV4dFR5cGVdKSB7XG4gICAgICBkZWxldGUgdGhpcy5sb2FkZXJzW2NvbnRleHRUeXBlXTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ2FsbCBgZGVzdHJveWAgb24gYWxsIGludGVybmFsIGxvYWRlciBpbnN0YW5jZXMgbWFwcGVkIChvbmUgcGVyIGNvbnRleHQgdHlwZSlcbiAgICovXG4gIGRlc3Ryb3lJbnRlcm5hbExvYWRlcnMoKSB7XG4gICAgZm9yIChjb25zdCBjb250ZXh0VHlwZSBpbiB0aGlzLmxvYWRlcnMpIHtcbiAgICAgIGNvbnN0IGxvYWRlciA9IHRoaXMubG9hZGVyc1tjb250ZXh0VHlwZV07XG4gICAgICBpZiAobG9hZGVyKSB7XG4gICAgICAgIGxvYWRlci5kZXN0cm95KCk7XG4gICAgICB9XG4gICAgICB0aGlzLnJlc2V0SW50ZXJuYWxMb2FkZXIoY29udGV4dFR5cGUpO1xuICAgIH1cbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMudmFyaWFibGVMaXN0ID0gbnVsbDtcbiAgICB0aGlzLnVucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICB0aGlzLmRlc3Ryb3lJbnRlcm5hbExvYWRlcnMoKTtcbiAgfVxuICBvbk1hbmlmZXN0TG9hZGluZyhldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIHVybFxuICAgIH0gPSBkYXRhO1xuICAgIHRoaXMudmFyaWFibGVMaXN0ID0gbnVsbDtcbiAgICB0aGlzLmxvYWQoe1xuICAgICAgaWQ6IG51bGwsXG4gICAgICBsZXZlbDogMCxcbiAgICAgIHJlc3BvbnNlVHlwZTogJ3RleHQnLFxuICAgICAgdHlwZTogUGxheWxpc3RDb250ZXh0VHlwZS5NQU5JRkVTVCxcbiAgICAgIHVybCxcbiAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlczogbnVsbFxuICAgIH0pO1xuICB9XG4gIG9uTGV2ZWxMb2FkaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3Qge1xuICAgICAgaWQsXG4gICAgICBsZXZlbCxcbiAgICAgIHBhdGh3YXlJZCxcbiAgICAgIHVybCxcbiAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlc1xuICAgIH0gPSBkYXRhO1xuICAgIHRoaXMubG9hZCh7XG4gICAgICBpZCxcbiAgICAgIGxldmVsLFxuICAgICAgcGF0aHdheUlkLFxuICAgICAgcmVzcG9uc2VUeXBlOiAndGV4dCcsXG4gICAgICB0eXBlOiBQbGF5bGlzdENvbnRleHRUeXBlLkxFVkVMLFxuICAgICAgdXJsLFxuICAgICAgZGVsaXZlcnlEaXJlY3RpdmVzXG4gICAgfSk7XG4gIH1cbiAgb25BdWRpb1RyYWNrTG9hZGluZyhldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGlkLFxuICAgICAgZ3JvdXBJZCxcbiAgICAgIHVybCxcbiAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlc1xuICAgIH0gPSBkYXRhO1xuICAgIHRoaXMubG9hZCh7XG4gICAgICBpZCxcbiAgICAgIGdyb3VwSWQsXG4gICAgICBsZXZlbDogbnVsbCxcbiAgICAgIHJlc3BvbnNlVHlwZTogJ3RleHQnLFxuICAgICAgdHlwZTogUGxheWxpc3RDb250ZXh0VHlwZS5BVURJT19UUkFDSyxcbiAgICAgIHVybCxcbiAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlc1xuICAgIH0pO1xuICB9XG4gIG9uU3VidGl0bGVUcmFja0xvYWRpbmcoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBpZCxcbiAgICAgIGdyb3VwSWQsXG4gICAgICB1cmwsXG4gICAgICBkZWxpdmVyeURpcmVjdGl2ZXNcbiAgICB9ID0gZGF0YTtcbiAgICB0aGlzLmxvYWQoe1xuICAgICAgaWQsXG4gICAgICBncm91cElkLFxuICAgICAgbGV2ZWw6IG51bGwsXG4gICAgICByZXNwb25zZVR5cGU6ICd0ZXh0JyxcbiAgICAgIHR5cGU6IFBsYXlsaXN0Q29udGV4dFR5cGUuU1VCVElUTEVfVFJBQ0ssXG4gICAgICB1cmwsXG4gICAgICBkZWxpdmVyeURpcmVjdGl2ZXNcbiAgICB9KTtcbiAgfVxuICBsb2FkKGNvbnRleHQpIHtcbiAgICB2YXIgX2NvbnRleHQkZGVsaXZlcnlEaXJlO1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuaGxzLmNvbmZpZztcblxuICAgIC8vIGxvZ2dlci5kZWJ1ZyhgW3BsYXlsaXN0LWxvYWRlcl06IExvYWRpbmcgcGxheWxpc3Qgb2YgdHlwZSAke2NvbnRleHQudHlwZX0sIGxldmVsOiAke2NvbnRleHQubGV2ZWx9LCBpZDogJHtjb250ZXh0LmlkfWApO1xuXG4gICAgLy8gQ2hlY2sgaWYgYSBsb2FkZXIgZm9yIHRoaXMgY29udGV4dCBhbHJlYWR5IGV4aXN0c1xuICAgIGxldCBsb2FkZXIgPSB0aGlzLmdldEludGVybmFsTG9hZGVyKGNvbnRleHQpO1xuICAgIGlmIChsb2FkZXIpIHtcbiAgICAgIGNvbnN0IGxvYWRlckNvbnRleHQgPSBsb2FkZXIuY29udGV4dDtcbiAgICAgIGlmIChsb2FkZXJDb250ZXh0ICYmIGxvYWRlckNvbnRleHQudXJsID09PSBjb250ZXh0LnVybCAmJiBsb2FkZXJDb250ZXh0LmxldmVsID09PSBjb250ZXh0LmxldmVsKSB7XG4gICAgICAgIC8vIHNhbWUgVVJMIGNhbid0IG92ZXJsYXBcbiAgICAgICAgbG9nZ2VyLnRyYWNlKCdbcGxheWxpc3QtbG9hZGVyXTogcGxheWxpc3QgcmVxdWVzdCBvbmdvaW5nJyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGxvZ2dlci5sb2coYFtwbGF5bGlzdC1sb2FkZXJdOiBhYm9ydGluZyBwcmV2aW91cyBsb2FkZXIgZm9yIHR5cGU6ICR7Y29udGV4dC50eXBlfWApO1xuICAgICAgbG9hZGVyLmFib3J0KCk7XG4gICAgfVxuXG4gICAgLy8gYXBwbHkgZGlmZmVyZW50IGNvbmZpZ3MgZm9yIHJldHJpZXMgZGVwZW5kaW5nIG9uXG4gICAgLy8gY29udGV4dCAobWFuaWZlc3QsIGxldmVsLCBhdWRpby9zdWJzIHBsYXlsaXN0KVxuICAgIGxldCBsb2FkUG9saWN5O1xuICAgIGlmIChjb250ZXh0LnR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuTUFOSUZFU1QpIHtcbiAgICAgIGxvYWRQb2xpY3kgPSBjb25maWcubWFuaWZlc3RMb2FkUG9saWN5LmRlZmF1bHQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvYWRQb2xpY3kgPSBfZXh0ZW5kcyh7fSwgY29uZmlnLnBsYXlsaXN0TG9hZFBvbGljeS5kZWZhdWx0LCB7XG4gICAgICAgIHRpbWVvdXRSZXRyeTogbnVsbCxcbiAgICAgICAgZXJyb3JSZXRyeTogbnVsbFxuICAgICAgfSk7XG4gICAgfVxuICAgIGxvYWRlciA9IHRoaXMuY3JlYXRlSW50ZXJuYWxMb2FkZXIoY29udGV4dCk7XG5cbiAgICAvLyBPdmVycmlkZSBsZXZlbC90cmFjayB0aW1lb3V0IGZvciBMTC1ITFMgcmVxdWVzdHNcbiAgICAvLyAodGhlIGRlZmF1bHQgb2YgMTAwMDBtcyBpcyBjb3VudGVyIHByb2R1Y3RpdmUgdG8gYmxvY2tpbmcgcGxheWxpc3QgcmVsb2FkIHJlcXVlc3RzKVxuICAgIGlmIChpc0Zpbml0ZU51bWJlcigoX2NvbnRleHQkZGVsaXZlcnlEaXJlID0gY29udGV4dC5kZWxpdmVyeURpcmVjdGl2ZXMpID09IG51bGwgPyB2b2lkIDAgOiBfY29udGV4dCRkZWxpdmVyeURpcmUucGFydCkpIHtcbiAgICAgIGxldCBsZXZlbERldGFpbHM7XG4gICAgICBpZiAoY29udGV4dC50eXBlID09PSBQbGF5bGlzdENvbnRleHRUeXBlLkxFVkVMICYmIGNvbnRleHQubGV2ZWwgIT09IG51bGwpIHtcbiAgICAgICAgbGV2ZWxEZXRhaWxzID0gdGhpcy5obHMubGV2ZWxzW2NvbnRleHQubGV2ZWxdLmRldGFpbHM7XG4gICAgICB9IGVsc2UgaWYgKGNvbnRleHQudHlwZSA9PT0gUGxheWxpc3RDb250ZXh0VHlwZS5BVURJT19UUkFDSyAmJiBjb250ZXh0LmlkICE9PSBudWxsKSB7XG4gICAgICAgIGxldmVsRGV0YWlscyA9IHRoaXMuaGxzLmF1ZGlvVHJhY2tzW2NvbnRleHQuaWRdLmRldGFpbHM7XG4gICAgICB9IGVsc2UgaWYgKGNvbnRleHQudHlwZSA9PT0gUGxheWxpc3RDb250ZXh0VHlwZS5TVUJUSVRMRV9UUkFDSyAmJiBjb250ZXh0LmlkICE9PSBudWxsKSB7XG4gICAgICAgIGxldmVsRGV0YWlscyA9IHRoaXMuaGxzLnN1YnRpdGxlVHJhY2tzW2NvbnRleHQuaWRdLmRldGFpbHM7XG4gICAgICB9XG4gICAgICBpZiAobGV2ZWxEZXRhaWxzKSB7XG4gICAgICAgIGNvbnN0IHBhcnRUYXJnZXQgPSBsZXZlbERldGFpbHMucGFydFRhcmdldDtcbiAgICAgICAgY29uc3QgdGFyZ2V0RHVyYXRpb24gPSBsZXZlbERldGFpbHMudGFyZ2V0ZHVyYXRpb247XG4gICAgICAgIGlmIChwYXJ0VGFyZ2V0ICYmIHRhcmdldER1cmF0aW9uKSB7XG4gICAgICAgICAgY29uc3QgbWF4TG93TGF0ZW5jeVBsYXlsaXN0UmVmcmVzaCA9IE1hdGgubWF4KHBhcnRUYXJnZXQgKiAzLCB0YXJnZXREdXJhdGlvbiAqIDAuOCkgKiAxMDAwO1xuICAgICAgICAgIGxvYWRQb2xpY3kgPSBfZXh0ZW5kcyh7fSwgbG9hZFBvbGljeSwge1xuICAgICAgICAgICAgbWF4VGltZVRvRmlyc3RCeXRlTXM6IE1hdGgubWluKG1heExvd0xhdGVuY3lQbGF5bGlzdFJlZnJlc2gsIGxvYWRQb2xpY3kubWF4VGltZVRvRmlyc3RCeXRlTXMpLFxuICAgICAgICAgICAgbWF4TG9hZFRpbWVNczogTWF0aC5taW4obWF4TG93TGF0ZW5jeVBsYXlsaXN0UmVmcmVzaCwgbG9hZFBvbGljeS5tYXhUaW1lVG9GaXJzdEJ5dGVNcylcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBjb25zdCBsZWdhY3lSZXRyeUNvbXBhdGliaWxpdHkgPSBsb2FkUG9saWN5LmVycm9yUmV0cnkgfHwgbG9hZFBvbGljeS50aW1lb3V0UmV0cnkgfHwge307XG4gICAgY29uc3QgbG9hZGVyQ29uZmlnID0ge1xuICAgICAgbG9hZFBvbGljeSxcbiAgICAgIHRpbWVvdXQ6IGxvYWRQb2xpY3kubWF4TG9hZFRpbWVNcyxcbiAgICAgIG1heFJldHJ5OiBsZWdhY3lSZXRyeUNvbXBhdGliaWxpdHkubWF4TnVtUmV0cnkgfHwgMCxcbiAgICAgIHJldHJ5RGVsYXk6IGxlZ2FjeVJldHJ5Q29tcGF0aWJpbGl0eS5yZXRyeURlbGF5TXMgfHwgMCxcbiAgICAgIG1heFJldHJ5RGVsYXk6IGxlZ2FjeVJldHJ5Q29tcGF0aWJpbGl0eS5tYXhSZXRyeURlbGF5TXMgfHwgMFxuICAgIH07XG4gICAgY29uc3QgbG9hZGVyQ2FsbGJhY2tzID0ge1xuICAgICAgb25TdWNjZXNzOiAocmVzcG9uc2UsIHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICBjb25zdCBsb2FkZXIgPSB0aGlzLmdldEludGVybmFsTG9hZGVyKGNvbnRleHQpO1xuICAgICAgICB0aGlzLnJlc2V0SW50ZXJuYWxMb2FkZXIoY29udGV4dC50eXBlKTtcbiAgICAgICAgY29uc3Qgc3RyaW5nID0gcmVzcG9uc2UuZGF0YTtcblxuICAgICAgICAvLyBWYWxpZGF0ZSBpZiBpdCBpcyBhbiBNM1U4IGF0IGFsbFxuICAgICAgICBpZiAoc3RyaW5nLmluZGV4T2YoJyNFWFRNM1UnKSAhPT0gMCkge1xuICAgICAgICAgIHRoaXMuaGFuZGxlTWFuaWZlc3RQYXJzaW5nRXJyb3IocmVzcG9uc2UsIGNvbnRleHQsIG5ldyBFcnJvcignbm8gRVhUTTNVIGRlbGltaXRlcicpLCBuZXR3b3JrRGV0YWlscyB8fCBudWxsLCBzdGF0cyk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIHN0YXRzLnBhcnNpbmcuc3RhcnQgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgaWYgKE0zVThQYXJzZXIuaXNNZWRpYVBsYXlsaXN0KHN0cmluZykpIHtcbiAgICAgICAgICB0aGlzLmhhbmRsZVRyYWNrT3JMZXZlbFBsYXlsaXN0KHJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgbmV0d29ya0RldGFpbHMgfHwgbnVsbCwgbG9hZGVyKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLmhhbmRsZU1hc3RlclBsYXlsaXN0KHJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgbmV0d29ya0RldGFpbHMpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgb25FcnJvcjogKHJlc3BvbnNlLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscywgc3RhdHMpID0+IHtcbiAgICAgICAgdGhpcy5oYW5kbGVOZXR3b3JrRXJyb3IoY29udGV4dCwgbmV0d29ya0RldGFpbHMsIGZhbHNlLCByZXNwb25zZSwgc3RhdHMpO1xuICAgICAgfSxcbiAgICAgIG9uVGltZW91dDogKHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICB0aGlzLmhhbmRsZU5ldHdvcmtFcnJvcihjb250ZXh0LCBuZXR3b3JrRGV0YWlscywgdHJ1ZSwgdW5kZWZpbmVkLCBzdGF0cyk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIC8vIGxvZ2dlci5kZWJ1ZyhgW3BsYXlsaXN0LWxvYWRlcl06IENhbGxpbmcgaW50ZXJuYWwgbG9hZGVyIGRlbGVnYXRlIGZvciBVUkw6ICR7Y29udGV4dC51cmx9YCk7XG5cbiAgICBsb2FkZXIubG9hZChjb250ZXh0LCBsb2FkZXJDb25maWcsIGxvYWRlckNhbGxiYWNrcyk7XG4gIH1cbiAgaGFuZGxlTWFzdGVyUGxheWxpc3QocmVzcG9uc2UsIHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IHN0cmluZyA9IHJlc3BvbnNlLmRhdGE7XG4gICAgY29uc3QgdXJsID0gZ2V0UmVzcG9uc2VVcmwocmVzcG9uc2UsIGNvbnRleHQpO1xuICAgIGNvbnN0IHBhcnNlZFJlc3VsdCA9IE0zVThQYXJzZXIucGFyc2VNYXN0ZXJQbGF5bGlzdChzdHJpbmcsIHVybCk7XG4gICAgaWYgKHBhcnNlZFJlc3VsdC5wbGF5bGlzdFBhcnNpbmdFcnJvcikge1xuICAgICAgdGhpcy5oYW5kbGVNYW5pZmVzdFBhcnNpbmdFcnJvcihyZXNwb25zZSwgY29udGV4dCwgcGFyc2VkUmVzdWx0LnBsYXlsaXN0UGFyc2luZ0Vycm9yLCBuZXR3b3JrRGV0YWlscywgc3RhdHMpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7XG4gICAgICBjb250ZW50U3RlZXJpbmcsXG4gICAgICBsZXZlbHMsXG4gICAgICBzZXNzaW9uRGF0YSxcbiAgICAgIHNlc3Npb25LZXlzLFxuICAgICAgc3RhcnRUaW1lT2Zmc2V0LFxuICAgICAgdmFyaWFibGVMaXN0XG4gICAgfSA9IHBhcnNlZFJlc3VsdDtcbiAgICB0aGlzLnZhcmlhYmxlTGlzdCA9IHZhcmlhYmxlTGlzdDtcbiAgICBjb25zdCB7XG4gICAgICBBVURJTzogYXVkaW9UcmFja3MgPSBbXSxcbiAgICAgIFNVQlRJVExFUzogc3VidGl0bGVzLFxuICAgICAgJ0NMT1NFRC1DQVBUSU9OUyc6IGNhcHRpb25zXG4gICAgfSA9IE0zVThQYXJzZXIucGFyc2VNYXN0ZXJQbGF5bGlzdE1lZGlhKHN0cmluZywgdXJsLCBwYXJzZWRSZXN1bHQpO1xuICAgIGlmIChhdWRpb1RyYWNrcy5sZW5ndGgpIHtcbiAgICAgIC8vIGNoZWNrIGlmIHdlIGhhdmUgZm91bmQgYW4gYXVkaW8gdHJhY2sgZW1iZWRkZWQgaW4gbWFpbiBwbGF5bGlzdCAoYXVkaW8gdHJhY2sgd2l0aG91dCBVUkkgYXR0cmlidXRlKVxuICAgICAgY29uc3QgZW1iZWRkZWRBdWRpb0ZvdW5kID0gYXVkaW9UcmFja3Muc29tZShhdWRpb1RyYWNrID0+ICFhdWRpb1RyYWNrLnVybCk7XG5cbiAgICAgIC8vIGlmIG5vIGVtYmVkZGVkIGF1ZGlvIHRyYWNrIGRlZmluZWQsIGJ1dCBhdWRpbyBjb2RlYyBzaWduYWxlZCBpbiBxdWFsaXR5IGxldmVsLFxuICAgICAgLy8gd2UgbmVlZCB0byBzaWduYWwgdGhpcyBtYWluIGF1ZGlvIHRyYWNrIHRoaXMgY291bGQgaGFwcGVuIHdpdGggcGxheWxpc3RzIHdpdGhcbiAgICAgIC8vIGFsdCBhdWRpbyByZW5kaXRpb24gaW4gd2hpY2ggcXVhbGl0eSBsZXZlbHMgKG1haW4pXG4gICAgICAvLyBjb250YWlucyBib3RoIGF1ZGlvK3ZpZGVvLiBidXQgd2l0aCBtaXhlZCBhdWRpbyB0cmFjayBub3Qgc2lnbmFsZWRcbiAgICAgIGlmICghZW1iZWRkZWRBdWRpb0ZvdW5kICYmIGxldmVsc1swXS5hdWRpb0NvZGVjICYmICFsZXZlbHNbMF0uYXR0cnMuQVVESU8pIHtcbiAgICAgICAgbG9nZ2VyLmxvZygnW3BsYXlsaXN0LWxvYWRlcl06IGF1ZGlvIGNvZGVjIHNpZ25hbGVkIGluIHF1YWxpdHkgbGV2ZWwsIGJ1dCBubyBlbWJlZGRlZCBhdWRpbyB0cmFjayBzaWduYWxlZCwgY3JlYXRlIG9uZScpO1xuICAgICAgICBhdWRpb1RyYWNrcy51bnNoaWZ0KHtcbiAgICAgICAgICB0eXBlOiAnbWFpbicsXG4gICAgICAgICAgbmFtZTogJ21haW4nLFxuICAgICAgICAgIGdyb3VwSWQ6ICdtYWluJyxcbiAgICAgICAgICBkZWZhdWx0OiBmYWxzZSxcbiAgICAgICAgICBhdXRvc2VsZWN0OiBmYWxzZSxcbiAgICAgICAgICBmb3JjZWQ6IGZhbHNlLFxuICAgICAgICAgIGlkOiAtMSxcbiAgICAgICAgICBhdHRyczogbmV3IEF0dHJMaXN0KHt9KSxcbiAgICAgICAgICBiaXRyYXRlOiAwLFxuICAgICAgICAgIHVybDogJydcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICAgIGhscy50cmlnZ2VyKEV2ZW50cy5NQU5JRkVTVF9MT0FERUQsIHtcbiAgICAgIGxldmVscyxcbiAgICAgIGF1ZGlvVHJhY2tzLFxuICAgICAgc3VidGl0bGVzLFxuICAgICAgY2FwdGlvbnMsXG4gICAgICBjb250ZW50U3RlZXJpbmcsXG4gICAgICB1cmwsXG4gICAgICBzdGF0cyxcbiAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgc2Vzc2lvbkRhdGEsXG4gICAgICBzZXNzaW9uS2V5cyxcbiAgICAgIHN0YXJ0VGltZU9mZnNldCxcbiAgICAgIHZhcmlhYmxlTGlzdFxuICAgIH0pO1xuICB9XG4gIGhhbmRsZVRyYWNrT3JMZXZlbFBsYXlsaXN0KHJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgbmV0d29ya0RldGFpbHMsIGxvYWRlcikge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IHtcbiAgICAgIGlkLFxuICAgICAgbGV2ZWwsXG4gICAgICB0eXBlXG4gICAgfSA9IGNvbnRleHQ7XG4gICAgY29uc3QgdXJsID0gZ2V0UmVzcG9uc2VVcmwocmVzcG9uc2UsIGNvbnRleHQpO1xuICAgIGNvbnN0IGxldmVsVXJsSWQgPSAwO1xuICAgIGNvbnN0IGxldmVsSWQgPSBpc0Zpbml0ZU51bWJlcihsZXZlbCkgPyBsZXZlbCA6IGlzRmluaXRlTnVtYmVyKGlkKSA/IGlkIDogMDtcbiAgICBjb25zdCBsZXZlbFR5cGUgPSBtYXBDb250ZXh0VG9MZXZlbFR5cGUoY29udGV4dCk7XG4gICAgY29uc3QgbGV2ZWxEZXRhaWxzID0gTTNVOFBhcnNlci5wYXJzZUxldmVsUGxheWxpc3QocmVzcG9uc2UuZGF0YSwgdXJsLCBsZXZlbElkLCBsZXZlbFR5cGUsIGxldmVsVXJsSWQsIHRoaXMudmFyaWFibGVMaXN0KTtcblxuICAgIC8vIFdlIGhhdmUgZG9uZSBvdXIgZmlyc3QgcmVxdWVzdCAoTWFuaWZlc3QtdHlwZSkgYW5kIHJlY2VpdmVcbiAgICAvLyBub3QgYSBtYXN0ZXIgcGxheWxpc3QgYnV0IGEgY2h1bmstbGlzdCAodHJhY2svbGV2ZWwpXG4gICAgLy8gV2UgZmlyZSB0aGUgbWFuaWZlc3QtbG9hZGVkIGV2ZW50IGFueXdheSB3aXRoIHRoZSBwYXJzZWQgbGV2ZWwtZGV0YWlsc1xuICAgIC8vIGJ5IGNyZWF0aW5nIGEgc2luZ2xlLWxldmVsIHN0cnVjdHVyZSBmb3IgaXQuXG4gICAgaWYgKHR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuTUFOSUZFU1QpIHtcbiAgICAgIGNvbnN0IHNpbmdsZUxldmVsID0ge1xuICAgICAgICBhdHRyczogbmV3IEF0dHJMaXN0KHt9KSxcbiAgICAgICAgYml0cmF0ZTogMCxcbiAgICAgICAgZGV0YWlsczogbGV2ZWxEZXRhaWxzLFxuICAgICAgICBuYW1lOiAnJyxcbiAgICAgICAgdXJsXG4gICAgICB9O1xuICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLk1BTklGRVNUX0xPQURFRCwge1xuICAgICAgICBsZXZlbHM6IFtzaW5nbGVMZXZlbF0sXG4gICAgICAgIGF1ZGlvVHJhY2tzOiBbXSxcbiAgICAgICAgdXJsLFxuICAgICAgICBzdGF0cyxcbiAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgIHNlc3Npb25EYXRhOiBudWxsLFxuICAgICAgICBzZXNzaW9uS2V5czogbnVsbCxcbiAgICAgICAgY29udGVudFN0ZWVyaW5nOiBudWxsLFxuICAgICAgICBzdGFydFRpbWVPZmZzZXQ6IG51bGwsXG4gICAgICAgIHZhcmlhYmxlTGlzdDogbnVsbFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gc2F2ZSBwYXJzaW5nIHRpbWVcbiAgICBzdGF0cy5wYXJzaW5nLmVuZCA9IHBlcmZvcm1hbmNlLm5vdygpO1xuXG4gICAgLy8gZXh0ZW5kIHRoZSBjb250ZXh0IHdpdGggdGhlIG5ldyBsZXZlbERldGFpbHMgcHJvcGVydHlcbiAgICBjb250ZXh0LmxldmVsRGV0YWlscyA9IGxldmVsRGV0YWlscztcbiAgICB0aGlzLmhhbmRsZVBsYXlsaXN0TG9hZGVkKGxldmVsRGV0YWlscywgcmVzcG9uc2UsIHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscywgbG9hZGVyKTtcbiAgfVxuICBoYW5kbGVNYW5pZmVzdFBhcnNpbmdFcnJvcihyZXNwb25zZSwgY29udGV4dCwgZXJyb3IsIG5ldHdvcmtEZXRhaWxzLCBzdGF0cykge1xuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCB7XG4gICAgICB0eXBlOiBFcnJvclR5cGVzLk5FVFdPUktfRVJST1IsXG4gICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuTUFOSUZFU1RfUEFSU0lOR19FUlJPUixcbiAgICAgIGZhdGFsOiBjb250ZXh0LnR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuTUFOSUZFU1QsXG4gICAgICB1cmw6IHJlc3BvbnNlLnVybCxcbiAgICAgIGVycjogZXJyb3IsXG4gICAgICBlcnJvcixcbiAgICAgIHJlYXNvbjogZXJyb3IubWVzc2FnZSxcbiAgICAgIHJlc3BvbnNlLFxuICAgICAgY29udGV4dCxcbiAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgc3RhdHNcbiAgICB9KTtcbiAgfVxuICBoYW5kbGVOZXR3b3JrRXJyb3IoY29udGV4dCwgbmV0d29ya0RldGFpbHMsIHRpbWVvdXQgPSBmYWxzZSwgcmVzcG9uc2UsIHN0YXRzKSB7XG4gICAgbGV0IG1lc3NhZ2UgPSBgQSBuZXR3b3JrICR7dGltZW91dCA/ICd0aW1lb3V0JyA6ICdlcnJvcicgKyAocmVzcG9uc2UgPyAnIChzdGF0dXMgJyArIHJlc3BvbnNlLmNvZGUgKyAnKScgOiAnJyl9IG9jY3VycmVkIHdoaWxlIGxvYWRpbmcgJHtjb250ZXh0LnR5cGV9YDtcbiAgICBpZiAoY29udGV4dC50eXBlID09PSBQbGF5bGlzdENvbnRleHRUeXBlLkxFVkVMKSB7XG4gICAgICBtZXNzYWdlICs9IGA6ICR7Y29udGV4dC5sZXZlbH0gaWQ6ICR7Y29udGV4dC5pZH1gO1xuICAgIH0gZWxzZSBpZiAoY29udGV4dC50eXBlID09PSBQbGF5bGlzdENvbnRleHRUeXBlLkFVRElPX1RSQUNLIHx8IGNvbnRleHQudHlwZSA9PT0gUGxheWxpc3RDb250ZXh0VHlwZS5TVUJUSVRMRV9UUkFDSykge1xuICAgICAgbWVzc2FnZSArPSBgIGlkOiAke2NvbnRleHQuaWR9IGdyb3VwLWlkOiBcIiR7Y29udGV4dC5ncm91cElkfVwiYDtcbiAgICB9XG4gICAgY29uc3QgZXJyb3IgPSBuZXcgRXJyb3IobWVzc2FnZSk7XG4gICAgbG9nZ2VyLndhcm4oYFtwbGF5bGlzdC1sb2FkZXJdOiAke21lc3NhZ2V9YCk7XG4gICAgbGV0IGRldGFpbHMgPSBFcnJvckRldGFpbHMuVU5LTk9XTjtcbiAgICBsZXQgZmF0YWwgPSBmYWxzZTtcbiAgICBjb25zdCBsb2FkZXIgPSB0aGlzLmdldEludGVybmFsTG9hZGVyKGNvbnRleHQpO1xuICAgIHN3aXRjaCAoY29udGV4dC50eXBlKSB7XG4gICAgICBjYXNlIFBsYXlsaXN0Q29udGV4dFR5cGUuTUFOSUZFU1Q6XG4gICAgICAgIGRldGFpbHMgPSB0aW1lb3V0ID8gRXJyb3JEZXRhaWxzLk1BTklGRVNUX0xPQURfVElNRU9VVCA6IEVycm9yRGV0YWlscy5NQU5JRkVTVF9MT0FEX0VSUk9SO1xuICAgICAgICBmYXRhbCA9IHRydWU7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBQbGF5bGlzdENvbnRleHRUeXBlLkxFVkVMOlxuICAgICAgICBkZXRhaWxzID0gdGltZW91dCA/IEVycm9yRGV0YWlscy5MRVZFTF9MT0FEX1RJTUVPVVQgOiBFcnJvckRldGFpbHMuTEVWRUxfTE9BRF9FUlJPUjtcbiAgICAgICAgZmF0YWwgPSBmYWxzZTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIFBsYXlsaXN0Q29udGV4dFR5cGUuQVVESU9fVFJBQ0s6XG4gICAgICAgIGRldGFpbHMgPSB0aW1lb3V0ID8gRXJyb3JEZXRhaWxzLkFVRElPX1RSQUNLX0xPQURfVElNRU9VVCA6IEVycm9yRGV0YWlscy5BVURJT19UUkFDS19MT0FEX0VSUk9SO1xuICAgICAgICBmYXRhbCA9IGZhbHNlO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgUGxheWxpc3RDb250ZXh0VHlwZS5TVUJUSVRMRV9UUkFDSzpcbiAgICAgICAgZGV0YWlscyA9IHRpbWVvdXQgPyBFcnJvckRldGFpbHMuU1VCVElUTEVfVFJBQ0tfTE9BRF9USU1FT1VUIDogRXJyb3JEZXRhaWxzLlNVQlRJVExFX0xPQURfRVJST1I7XG4gICAgICAgIGZhdGFsID0gZmFsc2U7XG4gICAgICAgIGJyZWFrO1xuICAgIH1cbiAgICBpZiAobG9hZGVyKSB7XG4gICAgICB0aGlzLnJlc2V0SW50ZXJuYWxMb2FkZXIoY29udGV4dC50eXBlKTtcbiAgICB9XG4gICAgY29uc3QgZXJyb3JEYXRhID0ge1xuICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgZGV0YWlscyxcbiAgICAgIGZhdGFsLFxuICAgICAgdXJsOiBjb250ZXh0LnVybCxcbiAgICAgIGxvYWRlcixcbiAgICAgIGNvbnRleHQsXG4gICAgICBlcnJvcixcbiAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgc3RhdHNcbiAgICB9O1xuICAgIGlmIChyZXNwb25zZSkge1xuICAgICAgY29uc3QgdXJsID0gKG5ldHdvcmtEZXRhaWxzID09IG51bGwgPyB2b2lkIDAgOiBuZXR3b3JrRGV0YWlscy51cmwpIHx8IGNvbnRleHQudXJsO1xuICAgICAgZXJyb3JEYXRhLnJlc3BvbnNlID0gX29iamVjdFNwcmVhZDIoe1xuICAgICAgICB1cmwsXG4gICAgICAgIGRhdGE6IHVuZGVmaW5lZFxuICAgICAgfSwgcmVzcG9uc2UpO1xuICAgIH1cbiAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwgZXJyb3JEYXRhKTtcbiAgfVxuICBoYW5kbGVQbGF5bGlzdExvYWRlZChsZXZlbERldGFpbHMsIHJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgbmV0d29ya0RldGFpbHMsIGxvYWRlcikge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IHtcbiAgICAgIHR5cGUsXG4gICAgICBsZXZlbCxcbiAgICAgIGlkLFxuICAgICAgZ3JvdXBJZCxcbiAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlc1xuICAgIH0gPSBjb250ZXh0O1xuICAgIGNvbnN0IHVybCA9IGdldFJlc3BvbnNlVXJsKHJlc3BvbnNlLCBjb250ZXh0KTtcbiAgICBjb25zdCBwYXJlbnQgPSBtYXBDb250ZXh0VG9MZXZlbFR5cGUoY29udGV4dCk7XG4gICAgY29uc3QgbGV2ZWxJbmRleCA9IHR5cGVvZiBjb250ZXh0LmxldmVsID09PSAnbnVtYmVyJyAmJiBwYXJlbnQgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4gPyBsZXZlbCA6IHVuZGVmaW5lZDtcbiAgICBpZiAoIWxldmVsRGV0YWlscy5mcmFnbWVudHMubGVuZ3RoKSB7XG4gICAgICBjb25zdCBfZXJyb3IgPSBuZXcgRXJyb3IoJ05vIFNlZ21lbnRzIGZvdW5kIGluIFBsYXlsaXN0Jyk7XG4gICAgICBobHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuTEVWRUxfRU1QVFlfRVJST1IsXG4gICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgdXJsLFxuICAgICAgICBlcnJvcjogX2Vycm9yLFxuICAgICAgICByZWFzb246IF9lcnJvci5tZXNzYWdlLFxuICAgICAgICByZXNwb25zZSxcbiAgICAgICAgY29udGV4dCxcbiAgICAgICAgbGV2ZWw6IGxldmVsSW5kZXgsXG4gICAgICAgIHBhcmVudCxcbiAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgIHN0YXRzXG4gICAgICB9KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCFsZXZlbERldGFpbHMudGFyZ2V0ZHVyYXRpb24pIHtcbiAgICAgIGxldmVsRGV0YWlscy5wbGF5bGlzdFBhcnNpbmdFcnJvciA9IG5ldyBFcnJvcignTWlzc2luZyBUYXJnZXQgRHVyYXRpb24nKTtcbiAgICB9XG4gICAgY29uc3QgZXJyb3IgPSBsZXZlbERldGFpbHMucGxheWxpc3RQYXJzaW5nRXJyb3I7XG4gICAgaWYgKGVycm9yKSB7XG4gICAgICBobHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuTEVWRUxfUEFSU0lOR19FUlJPUixcbiAgICAgICAgZmF0YWw6IGZhbHNlLFxuICAgICAgICB1cmwsXG4gICAgICAgIGVycm9yLFxuICAgICAgICByZWFzb246IGVycm9yLm1lc3NhZ2UsXG4gICAgICAgIHJlc3BvbnNlLFxuICAgICAgICBjb250ZXh0LFxuICAgICAgICBsZXZlbDogbGV2ZWxJbmRleCxcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBuZXR3b3JrRGV0YWlscyxcbiAgICAgICAgc3RhdHNcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAobGV2ZWxEZXRhaWxzLmxpdmUgJiYgbG9hZGVyKSB7XG4gICAgICBpZiAobG9hZGVyLmdldENhY2hlQWdlKSB7XG4gICAgICAgIGxldmVsRGV0YWlscy5hZ2VIZWFkZXIgPSBsb2FkZXIuZ2V0Q2FjaGVBZ2UoKSB8fCAwO1xuICAgICAgfVxuICAgICAgaWYgKCFsb2FkZXIuZ2V0Q2FjaGVBZ2UgfHwgaXNOYU4obGV2ZWxEZXRhaWxzLmFnZUhlYWRlcikpIHtcbiAgICAgICAgbGV2ZWxEZXRhaWxzLmFnZUhlYWRlciA9IDA7XG4gICAgICB9XG4gICAgfVxuICAgIHN3aXRjaCAodHlwZSkge1xuICAgICAgY2FzZSBQbGF5bGlzdENvbnRleHRUeXBlLk1BTklGRVNUOlxuICAgICAgY2FzZSBQbGF5bGlzdENvbnRleHRUeXBlLkxFVkVMOlxuICAgICAgICBobHMudHJpZ2dlcihFdmVudHMuTEVWRUxfTE9BREVELCB7XG4gICAgICAgICAgZGV0YWlsczogbGV2ZWxEZXRhaWxzLFxuICAgICAgICAgIGxldmVsOiBsZXZlbEluZGV4IHx8IDAsXG4gICAgICAgICAgaWQ6IGlkIHx8IDAsXG4gICAgICAgICAgc3RhdHMsXG4gICAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgICAgZGVsaXZlcnlEaXJlY3RpdmVzXG4gICAgICAgIH0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgUGxheWxpc3RDb250ZXh0VHlwZS5BVURJT19UUkFDSzpcbiAgICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkFVRElPX1RSQUNLX0xPQURFRCwge1xuICAgICAgICAgIGRldGFpbHM6IGxldmVsRGV0YWlscyxcbiAgICAgICAgICBpZDogaWQgfHwgMCxcbiAgICAgICAgICBncm91cElkOiBncm91cElkIHx8ICcnLFxuICAgICAgICAgIHN0YXRzLFxuICAgICAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlc1xuICAgICAgICB9KTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIFBsYXlsaXN0Q29udGV4dFR5cGUuU1VCVElUTEVfVFJBQ0s6XG4gICAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5TVUJUSVRMRV9UUkFDS19MT0FERUQsIHtcbiAgICAgICAgICBkZXRhaWxzOiBsZXZlbERldGFpbHMsXG4gICAgICAgICAgaWQ6IGlkIHx8IDAsXG4gICAgICAgICAgZ3JvdXBJZDogZ3JvdXBJZCB8fCAnJyxcbiAgICAgICAgICBzdGF0cyxcbiAgICAgICAgICBuZXR3b3JrRGV0YWlscyxcbiAgICAgICAgICBkZWxpdmVyeURpcmVjdGl2ZXNcbiAgICAgICAgfSk7XG4gICAgICAgIGJyZWFrO1xuICAgIH1cbiAgfVxufVxuXG5mdW5jdGlvbiBzZW5kQWRkVHJhY2tFdmVudCh0cmFjaywgdmlkZW9FbCkge1xuICBsZXQgZXZlbnQ7XG4gIHRyeSB7XG4gICAgZXZlbnQgPSBuZXcgRXZlbnQoJ2FkZHRyYWNrJyk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIC8vIGZvciBJRTExXG4gICAgZXZlbnQgPSBkb2N1bWVudC5jcmVhdGVFdmVudCgnRXZlbnQnKTtcbiAgICBldmVudC5pbml0RXZlbnQoJ2FkZHRyYWNrJywgZmFsc2UsIGZhbHNlKTtcbiAgfVxuICBldmVudC50cmFjayA9IHRyYWNrO1xuICB2aWRlb0VsLmRpc3BhdGNoRXZlbnQoZXZlbnQpO1xufVxuZnVuY3Rpb24gYWRkQ3VlVG9UcmFjayh0cmFjaywgY3VlKSB7XG4gIC8vIFNvbWV0aW1lcyB0aGVyZSBhcmUgY3VlIG92ZXJsYXBzIG9uIHNlZ21lbnRlZCB2dHRzIHNvIHRoZSBzYW1lXG4gIC8vIGN1ZSBjYW4gYXBwZWFyIG1vcmUgdGhhbiBvbmNlIGluIGRpZmZlcmVudCB2dHQgZmlsZXMuXG4gIC8vIFRoaXMgYXZvaWQgc2hvd2luZyBkdXBsaWNhdGVkIGN1ZXMgd2l0aCBzYW1lIHRpbWVjb2RlIGFuZCB0ZXh0LlxuICBjb25zdCBtb2RlID0gdHJhY2subW9kZTtcbiAgaWYgKG1vZGUgPT09ICdkaXNhYmxlZCcpIHtcbiAgICB0cmFjay5tb2RlID0gJ2hpZGRlbic7XG4gIH1cbiAgaWYgKHRyYWNrLmN1ZXMgJiYgIXRyYWNrLmN1ZXMuZ2V0Q3VlQnlJZChjdWUuaWQpKSB7XG4gICAgdHJ5IHtcbiAgICAgIHRyYWNrLmFkZEN1ZShjdWUpO1xuICAgICAgaWYgKCF0cmFjay5jdWVzLmdldEN1ZUJ5SWQoY3VlLmlkKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYGFkZEN1ZSBpcyBmYWlsZWQgZm9yOiAke2N1ZX1gKTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgW3RleHR0cmFjay11dGlsc106ICR7ZXJyfWApO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgdGV4dFRyYWNrQ3VlID0gbmV3IHNlbGYuVGV4dFRyYWNrQ3VlKGN1ZS5zdGFydFRpbWUsIGN1ZS5lbmRUaW1lLCBjdWUudGV4dCk7XG4gICAgICAgIHRleHRUcmFja0N1ZS5pZCA9IGN1ZS5pZDtcbiAgICAgICAgdHJhY2suYWRkQ3VlKHRleHRUcmFja0N1ZSk7XG4gICAgICB9IGNhdGNoIChlcnIyKSB7XG4gICAgICAgIGxvZ2dlci5kZWJ1ZyhgW3RleHR0cmFjay11dGlsc106IExlZ2FjeSBUZXh0VHJhY2tDdWUgZmFsbGJhY2sgZmFpbGVkOiAke2VycjJ9YCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGlmIChtb2RlID09PSAnZGlzYWJsZWQnKSB7XG4gICAgdHJhY2subW9kZSA9IG1vZGU7XG4gIH1cbn1cbmZ1bmN0aW9uIGNsZWFyQ3VycmVudEN1ZXModHJhY2spIHtcbiAgLy8gV2hlbiB0cmFjay5tb2RlIGlzIGRpc2FibGVkLCB0cmFjay5jdWVzIHdpbGwgYmUgbnVsbC5cbiAgLy8gVG8gZ3VhcmFudGVlIHRoZSByZW1vdmFsIG9mIGN1ZXMsIHdlIG5lZWQgdG8gdGVtcG9yYXJpbHlcbiAgLy8gY2hhbmdlIHRoZSBtb2RlIHRvIGhpZGRlblxuICBjb25zdCBtb2RlID0gdHJhY2subW9kZTtcbiAgaWYgKG1vZGUgPT09ICdkaXNhYmxlZCcpIHtcbiAgICB0cmFjay5tb2RlID0gJ2hpZGRlbic7XG4gIH1cbiAgaWYgKHRyYWNrLmN1ZXMpIHtcbiAgICBmb3IgKGxldCBpID0gdHJhY2suY3Vlcy5sZW5ndGg7IGktLTspIHtcbiAgICAgIHRyYWNrLnJlbW92ZUN1ZSh0cmFjay5jdWVzW2ldKTtcbiAgICB9XG4gIH1cbiAgaWYgKG1vZGUgPT09ICdkaXNhYmxlZCcpIHtcbiAgICB0cmFjay5tb2RlID0gbW9kZTtcbiAgfVxufVxuZnVuY3Rpb24gcmVtb3ZlQ3Vlc0luUmFuZ2UodHJhY2ssIHN0YXJ0LCBlbmQsIHByZWRpY2F0ZSkge1xuICBjb25zdCBtb2RlID0gdHJhY2subW9kZTtcbiAgaWYgKG1vZGUgPT09ICdkaXNhYmxlZCcpIHtcbiAgICB0cmFjay5tb2RlID0gJ2hpZGRlbic7XG4gIH1cbiAgaWYgKHRyYWNrLmN1ZXMgJiYgdHJhY2suY3Vlcy5sZW5ndGggPiAwKSB7XG4gICAgY29uc3QgY3VlcyA9IGdldEN1ZXNJblJhbmdlKHRyYWNrLmN1ZXMsIHN0YXJ0LCBlbmQpO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgY3Vlcy5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKCFwcmVkaWNhdGUgfHwgcHJlZGljYXRlKGN1ZXNbaV0pKSB7XG4gICAgICAgIHRyYWNrLnJlbW92ZUN1ZShjdWVzW2ldKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgaWYgKG1vZGUgPT09ICdkaXNhYmxlZCcpIHtcbiAgICB0cmFjay5tb2RlID0gbW9kZTtcbiAgfVxufVxuXG4vLyBGaW5kIGZpcnN0IGN1ZSBzdGFydGluZyBhZnRlciBnaXZlbiB0aW1lLlxuLy8gTW9kaWZpZWQgdmVyc2lvbiBvZiBiaW5hcnkgc2VhcmNoIE8obG9nKG4pKS5cbmZ1bmN0aW9uIGdldEZpcnN0Q3VlSW5kZXhBZnRlclRpbWUoY3VlcywgdGltZSkge1xuICAvLyBJZiBmaXJzdCBjdWUgc3RhcnRzIGFmdGVyIHRpbWUsIHN0YXJ0IHRoZXJlXG4gIGlmICh0aW1lIDwgY3Vlc1swXS5zdGFydFRpbWUpIHtcbiAgICByZXR1cm4gMDtcbiAgfVxuICAvLyBJZiB0aGUgbGFzdCBjdWUgZW5kcyBiZWZvcmUgdGltZSB0aGVyZSBpcyBubyBvdmVybGFwXG4gIGNvbnN0IGxlbiA9IGN1ZXMubGVuZ3RoIC0gMTtcbiAgaWYgKHRpbWUgPiBjdWVzW2xlbl0uZW5kVGltZSkge1xuICAgIHJldHVybiAtMTtcbiAgfVxuICBsZXQgbGVmdCA9IDA7XG4gIGxldCByaWdodCA9IGxlbjtcbiAgd2hpbGUgKGxlZnQgPD0gcmlnaHQpIHtcbiAgICBjb25zdCBtaWQgPSBNYXRoLmZsb29yKChyaWdodCArIGxlZnQpIC8gMik7XG4gICAgaWYgKHRpbWUgPCBjdWVzW21pZF0uc3RhcnRUaW1lKSB7XG4gICAgICByaWdodCA9IG1pZCAtIDE7XG4gICAgfSBlbHNlIGlmICh0aW1lID4gY3Vlc1ttaWRdLnN0YXJ0VGltZSAmJiBsZWZ0IDwgbGVuKSB7XG4gICAgICBsZWZ0ID0gbWlkICsgMTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gSWYgaXQncyBub3QgbG93ZXIgb3IgaGlnaGVyLCBpdCBtdXN0IGJlIGVxdWFsLlxuICAgICAgcmV0dXJuIG1pZDtcbiAgICB9XG4gIH1cbiAgLy8gQXQgdGhpcyBwb2ludCwgbGVmdCBhbmQgcmlnaHQgaGF2ZSBzd2FwcGVkLlxuICAvLyBObyBkaXJlY3QgbWF0Y2ggd2FzIGZvdW5kLCBsZWZ0IG9yIHJpZ2h0IGVsZW1lbnQgbXVzdCBiZSB0aGUgY2xvc2VzdC4gQ2hlY2sgd2hpY2ggb25lIGhhcyB0aGUgc21hbGxlc3QgZGlmZi5cbiAgcmV0dXJuIGN1ZXNbbGVmdF0uc3RhcnRUaW1lIC0gdGltZSA8IHRpbWUgLSBjdWVzW3JpZ2h0XS5zdGFydFRpbWUgPyBsZWZ0IDogcmlnaHQ7XG59XG5mdW5jdGlvbiBnZXRDdWVzSW5SYW5nZShjdWVzLCBzdGFydCwgZW5kKSB7XG4gIGNvbnN0IGN1ZXNGb3VuZCA9IFtdO1xuICBjb25zdCBmaXJzdEN1ZUluUmFuZ2UgPSBnZXRGaXJzdEN1ZUluZGV4QWZ0ZXJUaW1lKGN1ZXMsIHN0YXJ0KTtcbiAgaWYgKGZpcnN0Q3VlSW5SYW5nZSA+IC0xKSB7XG4gICAgZm9yIChsZXQgaSA9IGZpcnN0Q3VlSW5SYW5nZSwgbGVuID0gY3Vlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgY29uc3QgY3VlID0gY3Vlc1tpXTtcbiAgICAgIGlmIChjdWUuc3RhcnRUaW1lID49IHN0YXJ0ICYmIGN1ZS5lbmRUaW1lIDw9IGVuZCkge1xuICAgICAgICBjdWVzRm91bmQucHVzaChjdWUpO1xuICAgICAgfSBlbHNlIGlmIChjdWUuc3RhcnRUaW1lID4gZW5kKSB7XG4gICAgICAgIHJldHVybiBjdWVzRm91bmQ7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBjdWVzRm91bmQ7XG59XG5mdW5jdGlvbiBmaWx0ZXJTdWJ0aXRsZVRyYWNrcyh0ZXh0VHJhY2tMaXN0KSB7XG4gIGNvbnN0IHRyYWNrcyA9IFtdO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHRleHRUcmFja0xpc3QubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCB0cmFjayA9IHRleHRUcmFja0xpc3RbaV07XG4gICAgLy8gRWRnZSBhZGRzIGEgdHJhY2sgd2l0aG91dCBhIGxhYmVsOyB3ZSBkb24ndCB3YW50IHRvIHVzZSBpdFxuICAgIGlmICgodHJhY2sua2luZCA9PT0gJ3N1YnRpdGxlcycgfHwgdHJhY2sua2luZCA9PT0gJ2NhcHRpb25zJykgJiYgdHJhY2subGFiZWwpIHtcbiAgICAgIHRyYWNrcy5wdXNoKHRleHRUcmFja0xpc3RbaV0pO1xuICAgIH1cbiAgfVxuICByZXR1cm4gdHJhY2tzO1xufVxuXG52YXIgTWV0YWRhdGFTY2hlbWEgPSB7XG4gIGF1ZGlvSWQzOiBcIm9yZy5pZDNcIixcbiAgZGF0ZVJhbmdlOiBcImNvbS5hcHBsZS5xdWlja3RpbWUuSExTXCIsXG4gIGVtc2c6IFwiaHR0cHM6Ly9hb21lZGlhLm9yZy9lbXNnL0lEM1wiXG59O1xuXG5jb25zdCBNSU5fQ1VFX0RVUkFUSU9OID0gMC4yNTtcbmZ1bmN0aW9uIGdldEN1ZUNsYXNzKCkge1xuICBpZiAodHlwZW9mIHNlbGYgPT09ICd1bmRlZmluZWQnKSByZXR1cm4gdW5kZWZpbmVkO1xuICByZXR1cm4gc2VsZi5WVFRDdWUgfHwgc2VsZi5UZXh0VHJhY2tDdWU7XG59XG5mdW5jdGlvbiBjcmVhdGVDdWVXaXRoRGF0YUZpZWxkcyhDdWUsIHN0YXJ0VGltZSwgZW5kVGltZSwgZGF0YSwgdHlwZSkge1xuICBsZXQgY3VlID0gbmV3IEN1ZShzdGFydFRpbWUsIGVuZFRpbWUsICcnKTtcbiAgdHJ5IHtcbiAgICBjdWUudmFsdWUgPSBkYXRhO1xuICAgIGlmICh0eXBlKSB7XG4gICAgICBjdWUudHlwZSA9IHR5cGU7XG4gICAgfVxuICB9IGNhdGNoIChlKSB7XG4gICAgY3VlID0gbmV3IEN1ZShzdGFydFRpbWUsIGVuZFRpbWUsIEpTT04uc3RyaW5naWZ5KHR5cGUgPyBfb2JqZWN0U3ByZWFkMih7XG4gICAgICB0eXBlXG4gICAgfSwgZGF0YSkgOiBkYXRhKSk7XG4gIH1cbiAgcmV0dXJuIGN1ZTtcbn1cblxuLy8gVlRUQ3VlIGxhdGVzdCBkcmFmdCBhbGxvd3MgYW4gaW5maW5pdGUgZHVyYXRpb24sIGZhbGxiYWNrXG4vLyB0byBNQVhfVkFMVUUgaWYgbmVjZXNzYXJ5XG5jb25zdCBNQVhfQ1VFX0VORFRJTUUgPSAoKCkgPT4ge1xuICBjb25zdCBDdWUgPSBnZXRDdWVDbGFzcygpO1xuICB0cnkge1xuICAgIEN1ZSAmJiBuZXcgQ3VlKDAsIE51bWJlci5QT1NJVElWRV9JTkZJTklUWSwgJycpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgcmV0dXJuIE51bWJlci5NQVhfVkFMVUU7XG4gIH1cbiAgcmV0dXJuIE51bWJlci5QT1NJVElWRV9JTkZJTklUWTtcbn0pKCk7XG5mdW5jdGlvbiBkYXRlUmFuZ2VEYXRlVG9UaW1lbGluZVNlY29uZHMoZGF0ZSwgb2Zmc2V0KSB7XG4gIHJldHVybiBkYXRlLmdldFRpbWUoKSAvIDEwMDAgLSBvZmZzZXQ7XG59XG5mdW5jdGlvbiBoZXhUb0FycmF5QnVmZmVyKHN0cikge1xuICByZXR1cm4gVWludDhBcnJheS5mcm9tKHN0ci5yZXBsYWNlKC9eMHgvLCAnJykucmVwbGFjZSgvKFtcXGRhLWZBLUZdezJ9KSA/L2csICcweCQxICcpLnJlcGxhY2UoLyArJC8sICcnKS5zcGxpdCgnICcpKS5idWZmZXI7XG59XG5jbGFzcyBJRDNUcmFja0NvbnRyb2xsZXIge1xuICBjb25zdHJ1Y3RvcihobHMpIHtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLmlkM1RyYWNrID0gbnVsbDtcbiAgICB0aGlzLm1lZGlhID0gbnVsbDtcbiAgICB0aGlzLmRhdGVSYW5nZUN1ZXNBcHBlbmRlZCA9IHt9O1xuICAgIHRoaXMuaGxzID0gaGxzO1xuICAgIHRoaXMuX3JlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLl91bnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgdGhpcy5pZDNUcmFjayA9IG51bGw7XG4gICAgdGhpcy5tZWRpYSA9IG51bGw7XG4gICAgdGhpcy5kYXRlUmFuZ2VDdWVzQXBwZW5kZWQgPSB7fTtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdGhpcy5obHMgPSBudWxsO1xuICB9XG4gIF9yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub24oRXZlbnRzLk1FRElBX0FUVEFDSEVELCB0aGlzLm9uTWVkaWFBdHRhY2hlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkZSQUdfUEFSU0lOR19NRVRBREFUQSwgdGhpcy5vbkZyYWdQYXJzaW5nTWV0YWRhdGEsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQlVGRkVSX0ZMVVNISU5HLCB0aGlzLm9uQnVmZmVyRmx1c2hpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTEVWRUxfVVBEQVRFRCwgdGhpcy5vbkxldmVsVXBkYXRlZCwgdGhpcyk7XG4gIH1cbiAgX3VucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRlJBR19QQVJTSU5HX01FVEFEQVRBLCB0aGlzLm9uRnJhZ1BhcnNpbmdNZXRhZGF0YSwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuQlVGRkVSX0ZMVVNISU5HLCB0aGlzLm9uQnVmZmVyRmx1c2hpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX1VQREFURUQsIHRoaXMub25MZXZlbFVwZGF0ZWQsIHRoaXMpO1xuICB9XG5cbiAgLy8gQWRkIElEMyBtZXRhdGFkYXRhIHRleHQgdHJhY2suXG4gIG9uTWVkaWFBdHRhY2hlZChldmVudCwgZGF0YSkge1xuICAgIHRoaXMubWVkaWEgPSBkYXRhLm1lZGlhO1xuICB9XG4gIG9uTWVkaWFEZXRhY2hpbmcoKSB7XG4gICAgaWYgKCF0aGlzLmlkM1RyYWNrKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNsZWFyQ3VycmVudEN1ZXModGhpcy5pZDNUcmFjayk7XG4gICAgdGhpcy5pZDNUcmFjayA9IG51bGw7XG4gICAgdGhpcy5tZWRpYSA9IG51bGw7XG4gICAgdGhpcy5kYXRlUmFuZ2VDdWVzQXBwZW5kZWQgPSB7fTtcbiAgfVxuICBvbk1hbmlmZXN0TG9hZGluZygpIHtcbiAgICB0aGlzLmRhdGVSYW5nZUN1ZXNBcHBlbmRlZCA9IHt9O1xuICB9XG4gIGNyZWF0ZVRyYWNrKG1lZGlhKSB7XG4gICAgY29uc3QgdHJhY2sgPSB0aGlzLmdldElEM1RyYWNrKG1lZGlhLnRleHRUcmFja3MpO1xuICAgIHRyYWNrLm1vZGUgPSAnaGlkZGVuJztcbiAgICByZXR1cm4gdHJhY2s7XG4gIH1cbiAgZ2V0SUQzVHJhY2sodGV4dFRyYWNrcykge1xuICAgIGlmICghdGhpcy5tZWRpYSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRleHRUcmFja3MubGVuZ3RoOyBpKyspIHtcbiAgICAgIGNvbnN0IHRleHRUcmFjayA9IHRleHRUcmFja3NbaV07XG4gICAgICBpZiAodGV4dFRyYWNrLmtpbmQgPT09ICdtZXRhZGF0YScgJiYgdGV4dFRyYWNrLmxhYmVsID09PSAnaWQzJykge1xuICAgICAgICAvLyBzZW5kICdhZGR0cmFjaycgd2hlbiByZXVzaW5nIHRoZSB0ZXh0VHJhY2sgZm9yIG1ldGFkYXRhLFxuICAgICAgICAvLyBzYW1lIGFzIHdoYXQgd2UgZG8gZm9yIGNhcHRpb25zXG4gICAgICAgIHNlbmRBZGRUcmFja0V2ZW50KHRleHRUcmFjaywgdGhpcy5tZWRpYSk7XG4gICAgICAgIHJldHVybiB0ZXh0VHJhY2s7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB0aGlzLm1lZGlhLmFkZFRleHRUcmFjaygnbWV0YWRhdGEnLCAnaWQzJyk7XG4gIH1cbiAgb25GcmFnUGFyc2luZ01ldGFkYXRhKGV2ZW50LCBkYXRhKSB7XG4gICAgaWYgKCF0aGlzLm1lZGlhKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGhsczoge1xuICAgICAgICBjb25maWc6IHtcbiAgICAgICAgICBlbmFibGVFbXNnTWV0YWRhdGFDdWVzLFxuICAgICAgICAgIGVuYWJsZUlEM01ldGFkYXRhQ3Vlc1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFlbmFibGVFbXNnTWV0YWRhdGFDdWVzICYmICFlbmFibGVJRDNNZXRhZGF0YUN1ZXMpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qge1xuICAgICAgc2FtcGxlc1xuICAgIH0gPSBkYXRhO1xuXG4gICAgLy8gY3JlYXRlIHRyYWNrIGR5bmFtaWNhbGx5XG4gICAgaWYgKCF0aGlzLmlkM1RyYWNrKSB7XG4gICAgICB0aGlzLmlkM1RyYWNrID0gdGhpcy5jcmVhdGVUcmFjayh0aGlzLm1lZGlhKTtcbiAgICB9XG4gICAgY29uc3QgQ3VlID0gZ2V0Q3VlQ2xhc3MoKTtcbiAgICBpZiAoIUN1ZSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNhbXBsZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGNvbnN0IHR5cGUgPSBzYW1wbGVzW2ldLnR5cGU7XG4gICAgICBpZiAodHlwZSA9PT0gTWV0YWRhdGFTY2hlbWEuZW1zZyAmJiAhZW5hYmxlRW1zZ01ldGFkYXRhQ3VlcyB8fCAhZW5hYmxlSUQzTWV0YWRhdGFDdWVzKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgY29uc3QgZnJhbWVzID0gZ2V0SUQzRnJhbWVzKHNhbXBsZXNbaV0uZGF0YSk7XG4gICAgICBpZiAoZnJhbWVzKSB7XG4gICAgICAgIGNvbnN0IHN0YXJ0VGltZSA9IHNhbXBsZXNbaV0ucHRzO1xuICAgICAgICBsZXQgZW5kVGltZSA9IHN0YXJ0VGltZSArIHNhbXBsZXNbaV0uZHVyYXRpb247XG4gICAgICAgIGlmIChlbmRUaW1lID4gTUFYX0NVRV9FTkRUSU1FKSB7XG4gICAgICAgICAgZW5kVGltZSA9IE1BWF9DVUVfRU5EVElNRTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCB0aW1lRGlmZiA9IGVuZFRpbWUgLSBzdGFydFRpbWU7XG4gICAgICAgIGlmICh0aW1lRGlmZiA8PSAwKSB7XG4gICAgICAgICAgZW5kVGltZSA9IHN0YXJ0VGltZSArIE1JTl9DVUVfRFVSQVRJT047XG4gICAgICAgIH1cbiAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBmcmFtZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICBjb25zdCBmcmFtZSA9IGZyYW1lc1tqXTtcbiAgICAgICAgICAvLyBTYWZhcmkgZG9lc24ndCBwdXQgdGhlIHRpbWVzdGFtcCBmcmFtZSBpbiB0aGUgVGV4dFRyYWNrXG4gICAgICAgICAgaWYgKCFpc1RpbWVTdGFtcEZyYW1lKGZyYW1lKSkge1xuICAgICAgICAgICAgLy8gYWRkIGEgYm91bmRzIHRvIGFueSB1bmJvdW5kZWQgY3Vlc1xuICAgICAgICAgICAgdGhpcy51cGRhdGVJZDNDdWVFbmRzKHN0YXJ0VGltZSwgdHlwZSk7XG4gICAgICAgICAgICBjb25zdCBjdWUgPSBjcmVhdGVDdWVXaXRoRGF0YUZpZWxkcyhDdWUsIHN0YXJ0VGltZSwgZW5kVGltZSwgZnJhbWUsIHR5cGUpO1xuICAgICAgICAgICAgaWYgKGN1ZSkge1xuICAgICAgICAgICAgICB0aGlzLmlkM1RyYWNrLmFkZEN1ZShjdWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICB1cGRhdGVJZDNDdWVFbmRzKHN0YXJ0VGltZSwgdHlwZSkge1xuICAgIHZhciBfdGhpcyRpZDNUcmFjaztcbiAgICBjb25zdCBjdWVzID0gKF90aGlzJGlkM1RyYWNrID0gdGhpcy5pZDNUcmFjaykgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGlkM1RyYWNrLmN1ZXM7XG4gICAgaWYgKGN1ZXMpIHtcbiAgICAgIGZvciAobGV0IGkgPSBjdWVzLmxlbmd0aDsgaS0tOykge1xuICAgICAgICBjb25zdCBjdWUgPSBjdWVzW2ldO1xuICAgICAgICBpZiAoY3VlLnR5cGUgPT09IHR5cGUgJiYgY3VlLnN0YXJ0VGltZSA8IHN0YXJ0VGltZSAmJiBjdWUuZW5kVGltZSA9PT0gTUFYX0NVRV9FTkRUSU1FKSB7XG4gICAgICAgICAgY3VlLmVuZFRpbWUgPSBzdGFydFRpbWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgb25CdWZmZXJGbHVzaGluZyhldmVudCwge1xuICAgIHN0YXJ0T2Zmc2V0LFxuICAgIGVuZE9mZnNldCxcbiAgICB0eXBlXG4gIH0pIHtcbiAgICBjb25zdCB7XG4gICAgICBpZDNUcmFjayxcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGlmICghaGxzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZzoge1xuICAgICAgICBlbmFibGVFbXNnTWV0YWRhdGFDdWVzLFxuICAgICAgICBlbmFibGVJRDNNZXRhZGF0YUN1ZXNcbiAgICAgIH1cbiAgICB9ID0gaGxzO1xuICAgIGlmIChpZDNUcmFjayAmJiAoZW5hYmxlRW1zZ01ldGFkYXRhQ3VlcyB8fCBlbmFibGVJRDNNZXRhZGF0YUN1ZXMpKSB7XG4gICAgICBsZXQgcHJlZGljYXRlO1xuICAgICAgaWYgKHR5cGUgPT09ICdhdWRpbycpIHtcbiAgICAgICAgcHJlZGljYXRlID0gY3VlID0+IGN1ZS50eXBlID09PSBNZXRhZGF0YVNjaGVtYS5hdWRpb0lkMyAmJiBlbmFibGVJRDNNZXRhZGF0YUN1ZXM7XG4gICAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICd2aWRlbycpIHtcbiAgICAgICAgcHJlZGljYXRlID0gY3VlID0+IGN1ZS50eXBlID09PSBNZXRhZGF0YVNjaGVtYS5lbXNnICYmIGVuYWJsZUVtc2dNZXRhZGF0YUN1ZXM7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBwcmVkaWNhdGUgPSBjdWUgPT4gY3VlLnR5cGUgPT09IE1ldGFkYXRhU2NoZW1hLmF1ZGlvSWQzICYmIGVuYWJsZUlEM01ldGFkYXRhQ3VlcyB8fCBjdWUudHlwZSA9PT0gTWV0YWRhdGFTY2hlbWEuZW1zZyAmJiBlbmFibGVFbXNnTWV0YWRhdGFDdWVzO1xuICAgICAgfVxuICAgICAgcmVtb3ZlQ3Vlc0luUmFuZ2UoaWQzVHJhY2ssIHN0YXJ0T2Zmc2V0LCBlbmRPZmZzZXQsIHByZWRpY2F0ZSk7XG4gICAgfVxuICB9XG4gIG9uTGV2ZWxVcGRhdGVkKGV2ZW50LCB7XG4gICAgZGV0YWlsc1xuICB9KSB7XG4gICAgaWYgKCF0aGlzLm1lZGlhIHx8ICFkZXRhaWxzLmhhc1Byb2dyYW1EYXRlVGltZSB8fCAhdGhpcy5obHMuY29uZmlnLmVuYWJsZURhdGVSYW5nZU1ldGFkYXRhQ3Vlcykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7XG4gICAgICBkYXRlUmFuZ2VDdWVzQXBwZW5kZWQsXG4gICAgICBpZDNUcmFja1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIGRhdGVSYW5nZXNcbiAgICB9ID0gZGV0YWlscztcbiAgICBjb25zdCBpZHMgPSBPYmplY3Qua2V5cyhkYXRlUmFuZ2VzKTtcbiAgICAvLyBSZW1vdmUgY3VlcyBmcm9tIHRyYWNrIG5vdCBmb3VuZCBpbiBkZXRhaWxzLmRhdGVSYW5nZXNcbiAgICBpZiAoaWQzVHJhY2spIHtcbiAgICAgIGNvbnN0IGlkc1RvUmVtb3ZlID0gT2JqZWN0LmtleXMoZGF0ZVJhbmdlQ3Vlc0FwcGVuZGVkKS5maWx0ZXIoaWQgPT4gIWlkcy5pbmNsdWRlcyhpZCkpO1xuICAgICAgZm9yIChsZXQgaSA9IGlkc1RvUmVtb3ZlLmxlbmd0aDsgaS0tOykge1xuICAgICAgICBjb25zdCBpZCA9IGlkc1RvUmVtb3ZlW2ldO1xuICAgICAgICBPYmplY3Qua2V5cyhkYXRlUmFuZ2VDdWVzQXBwZW5kZWRbaWRdLmN1ZXMpLmZvckVhY2goa2V5ID0+IHtcbiAgICAgICAgICBpZDNUcmFjay5yZW1vdmVDdWUoZGF0ZVJhbmdlQ3Vlc0FwcGVuZGVkW2lkXS5jdWVzW2tleV0pO1xuICAgICAgICB9KTtcbiAgICAgICAgZGVsZXRlIGRhdGVSYW5nZUN1ZXNBcHBlbmRlZFtpZF07XG4gICAgICB9XG4gICAgfVxuICAgIC8vIEV4aXQgaWYgdGhlIHBsYXlsaXN0IGRvZXMgbm90IGhhdmUgRGF0ZSBSYW5nZXMgb3IgZG9lcyBub3QgaGF2ZSBQcm9ncmFtIERhdGUgVGltZVxuICAgIGNvbnN0IGxhc3RGcmFnbWVudCA9IGRldGFpbHMuZnJhZ21lbnRzW2RldGFpbHMuZnJhZ21lbnRzLmxlbmd0aCAtIDFdO1xuICAgIGlmIChpZHMubGVuZ3RoID09PSAwIHx8ICFpc0Zpbml0ZU51bWJlcihsYXN0RnJhZ21lbnQgPT0gbnVsbCA/IHZvaWQgMCA6IGxhc3RGcmFnbWVudC5wcm9ncmFtRGF0ZVRpbWUpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghdGhpcy5pZDNUcmFjaykge1xuICAgICAgdGhpcy5pZDNUcmFjayA9IHRoaXMuY3JlYXRlVHJhY2sodGhpcy5tZWRpYSk7XG4gICAgfVxuICAgIGNvbnN0IGRhdGVUaW1lT2Zmc2V0ID0gbGFzdEZyYWdtZW50LnByb2dyYW1EYXRlVGltZSAvIDEwMDAgLSBsYXN0RnJhZ21lbnQuc3RhcnQ7XG4gICAgY29uc3QgQ3VlID0gZ2V0Q3VlQ2xhc3MoKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGlkcy5sZW5ndGg7IGkrKykge1xuICAgICAgY29uc3QgaWQgPSBpZHNbaV07XG4gICAgICBjb25zdCBkYXRlUmFuZ2UgPSBkYXRlUmFuZ2VzW2lkXTtcbiAgICAgIGNvbnN0IHN0YXJ0VGltZSA9IGRhdGVSYW5nZURhdGVUb1RpbWVsaW5lU2Vjb25kcyhkYXRlUmFuZ2Uuc3RhcnREYXRlLCBkYXRlVGltZU9mZnNldCk7XG5cbiAgICAgIC8vIFByb2Nlc3MgRGF0ZVJhbmdlcyB0byBkZXRlcm1pbmUgZW5kLXRpbWUgKGtub3duIERVUkFUSU9OLCBFTkQtREFURSwgb3IgRU5ELU9OLU5FWFQpXG4gICAgICBjb25zdCBhcHBlbmRlZERhdGVSYW5nZUN1ZXMgPSBkYXRlUmFuZ2VDdWVzQXBwZW5kZWRbaWRdO1xuICAgICAgY29uc3QgY3VlcyA9IChhcHBlbmRlZERhdGVSYW5nZUN1ZXMgPT0gbnVsbCA/IHZvaWQgMCA6IGFwcGVuZGVkRGF0ZVJhbmdlQ3Vlcy5jdWVzKSB8fCB7fTtcbiAgICAgIGxldCBkdXJhdGlvbktub3duID0gKGFwcGVuZGVkRGF0ZVJhbmdlQ3VlcyA9PSBudWxsID8gdm9pZCAwIDogYXBwZW5kZWREYXRlUmFuZ2VDdWVzLmR1cmF0aW9uS25vd24pIHx8IGZhbHNlO1xuICAgICAgbGV0IGVuZFRpbWUgPSBNQVhfQ1VFX0VORFRJTUU7XG4gICAgICBjb25zdCBlbmREYXRlID0gZGF0ZVJhbmdlLmVuZERhdGU7XG4gICAgICBpZiAoZW5kRGF0ZSkge1xuICAgICAgICBlbmRUaW1lID0gZGF0ZVJhbmdlRGF0ZVRvVGltZWxpbmVTZWNvbmRzKGVuZERhdGUsIGRhdGVUaW1lT2Zmc2V0KTtcbiAgICAgICAgZHVyYXRpb25Lbm93biA9IHRydWU7XG4gICAgICB9IGVsc2UgaWYgKGRhdGVSYW5nZS5lbmRPbk5leHQgJiYgIWR1cmF0aW9uS25vd24pIHtcbiAgICAgICAgY29uc3QgbmV4dERhdGVSYW5nZVdpdGhTYW1lQ2xhc3MgPSBpZHMucmVkdWNlKChjYW5kaWRhdGVEYXRlUmFuZ2UsIGlkKSA9PiB7XG4gICAgICAgICAgaWYgKGlkICE9PSBkYXRlUmFuZ2UuaWQpIHtcbiAgICAgICAgICAgIGNvbnN0IG90aGVyRGF0ZVJhbmdlID0gZGF0ZVJhbmdlc1tpZF07XG4gICAgICAgICAgICBpZiAob3RoZXJEYXRlUmFuZ2UuY2xhc3MgPT09IGRhdGVSYW5nZS5jbGFzcyAmJiBvdGhlckRhdGVSYW5nZS5zdGFydERhdGUgPiBkYXRlUmFuZ2Uuc3RhcnREYXRlICYmICghY2FuZGlkYXRlRGF0ZVJhbmdlIHx8IGRhdGVSYW5nZS5zdGFydERhdGUgPCBjYW5kaWRhdGVEYXRlUmFuZ2Uuc3RhcnREYXRlKSkge1xuICAgICAgICAgICAgICByZXR1cm4gb3RoZXJEYXRlUmFuZ2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHJldHVybiBjYW5kaWRhdGVEYXRlUmFuZ2U7XG4gICAgICAgIH0sIG51bGwpO1xuICAgICAgICBpZiAobmV4dERhdGVSYW5nZVdpdGhTYW1lQ2xhc3MpIHtcbiAgICAgICAgICBlbmRUaW1lID0gZGF0ZVJhbmdlRGF0ZVRvVGltZWxpbmVTZWNvbmRzKG5leHREYXRlUmFuZ2VXaXRoU2FtZUNsYXNzLnN0YXJ0RGF0ZSwgZGF0ZVRpbWVPZmZzZXQpO1xuICAgICAgICAgIGR1cmF0aW9uS25vd24gPSB0cnVlO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIENyZWF0ZSBUZXh0VHJhY2sgQ3VlcyBmb3IgZWFjaCBNZXRhZGF0YUdyb3VwIEl0ZW0gKHNlbGVjdCBEYXRlUmFuZ2UgYXR0cmlidXRlKVxuICAgICAgLy8gVGhpcyBpcyB0byBlbXVsYXRlIFNhZmFyaSBITFMgcGxheWJhY2sgaGFuZGxpbmcgb2YgRGF0ZVJhbmdlIHRhZ3NcbiAgICAgIGNvbnN0IGF0dHJpYnV0ZXMgPSBPYmplY3Qua2V5cyhkYXRlUmFuZ2UuYXR0cik7XG4gICAgICBmb3IgKGxldCBqID0gMDsgaiA8IGF0dHJpYnV0ZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgY29uc3Qga2V5ID0gYXR0cmlidXRlc1tqXTtcbiAgICAgICAgaWYgKCFpc0RhdGVSYW5nZUN1ZUF0dHJpYnV0ZShrZXkpKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgY3VlID0gY3Vlc1trZXldO1xuICAgICAgICBpZiAoY3VlKSB7XG4gICAgICAgICAgaWYgKGR1cmF0aW9uS25vd24gJiYgIWFwcGVuZGVkRGF0ZVJhbmdlQ3Vlcy5kdXJhdGlvbktub3duKSB7XG4gICAgICAgICAgICBjdWUuZW5kVGltZSA9IGVuZFRpbWU7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKEN1ZSkge1xuICAgICAgICAgIGxldCBkYXRhID0gZGF0ZVJhbmdlLmF0dHJba2V5XTtcbiAgICAgICAgICBpZiAoaXNTQ1RFMzVBdHRyaWJ1dGUoa2V5KSkge1xuICAgICAgICAgICAgZGF0YSA9IGhleFRvQXJyYXlCdWZmZXIoZGF0YSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IF9jdWUgPSBjcmVhdGVDdWVXaXRoRGF0YUZpZWxkcyhDdWUsIHN0YXJ0VGltZSwgZW5kVGltZSwge1xuICAgICAgICAgICAga2V5LFxuICAgICAgICAgICAgZGF0YVxuICAgICAgICAgIH0sIE1ldGFkYXRhU2NoZW1hLmRhdGVSYW5nZSk7XG4gICAgICAgICAgaWYgKF9jdWUpIHtcbiAgICAgICAgICAgIF9jdWUuaWQgPSBpZDtcbiAgICAgICAgICAgIHRoaXMuaWQzVHJhY2suYWRkQ3VlKF9jdWUpO1xuICAgICAgICAgICAgY3Vlc1trZXldID0gX2N1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gS2VlcCB0cmFjayBvZiBwcm9jZXNzZWQgRGF0ZVJhbmdlcyBieSBJRCBmb3IgdXBkYXRpbmcgY3VlcyB3aXRoIG5ldyBEYXRlUmFuZ2UgdGFnIGF0dHJpYnV0ZXNcbiAgICAgIGRhdGVSYW5nZUN1ZXNBcHBlbmRlZFtpZF0gPSB7XG4gICAgICAgIGN1ZXMsXG4gICAgICAgIGRhdGVSYW5nZSxcbiAgICAgICAgZHVyYXRpb25Lbm93blxuICAgICAgfTtcbiAgICB9XG4gIH1cbn1cblxuY2xhc3MgTGF0ZW5jeUNvbnRyb2xsZXIge1xuICBjb25zdHJ1Y3RvcihobHMpIHtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLmNvbmZpZyA9IHZvaWQgMDtcbiAgICB0aGlzLm1lZGlhID0gbnVsbDtcbiAgICB0aGlzLmxldmVsRGV0YWlscyA9IG51bGw7XG4gICAgdGhpcy5jdXJyZW50VGltZSA9IDA7XG4gICAgdGhpcy5zdGFsbENvdW50ID0gMDtcbiAgICB0aGlzLl9sYXRlbmN5ID0gbnVsbDtcbiAgICB0aGlzLnRpbWV1cGRhdGVIYW5kbGVyID0gKCkgPT4gdGhpcy50aW1ldXBkYXRlKCk7XG4gICAgdGhpcy5obHMgPSBobHM7XG4gICAgdGhpcy5jb25maWcgPSBobHMuY29uZmlnO1xuICAgIHRoaXMucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICBnZXQgbGF0ZW5jeSgpIHtcbiAgICByZXR1cm4gdGhpcy5fbGF0ZW5jeSB8fCAwO1xuICB9XG4gIGdldCBtYXhMYXRlbmN5KCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZyxcbiAgICAgIGxldmVsRGV0YWlsc1xuICAgIH0gPSB0aGlzO1xuICAgIGlmIChjb25maWcubGl2ZU1heExhdGVuY3lEdXJhdGlvbiAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gY29uZmlnLmxpdmVNYXhMYXRlbmN5RHVyYXRpb247XG4gICAgfVxuICAgIHJldHVybiBsZXZlbERldGFpbHMgPyBjb25maWcubGl2ZU1heExhdGVuY3lEdXJhdGlvbkNvdW50ICogbGV2ZWxEZXRhaWxzLnRhcmdldGR1cmF0aW9uIDogMDtcbiAgfVxuICBnZXQgdGFyZ2V0TGF0ZW5jeSgpIHtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbERldGFpbHNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAobGV2ZWxEZXRhaWxzID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgY29uc3Qge1xuICAgICAgaG9sZEJhY2ssXG4gICAgICBwYXJ0SG9sZEJhY2ssXG4gICAgICB0YXJnZXRkdXJhdGlvblxuICAgIH0gPSBsZXZlbERldGFpbHM7XG4gICAgY29uc3Qge1xuICAgICAgbGl2ZVN5bmNEdXJhdGlvbixcbiAgICAgIGxpdmVTeW5jRHVyYXRpb25Db3VudCxcbiAgICAgIGxvd0xhdGVuY3lNb2RlXG4gICAgfSA9IHRoaXMuY29uZmlnO1xuICAgIGNvbnN0IHVzZXJDb25maWcgPSB0aGlzLmhscy51c2VyQ29uZmlnO1xuICAgIGxldCB0YXJnZXRMYXRlbmN5ID0gbG93TGF0ZW5jeU1vZGUgPyBwYXJ0SG9sZEJhY2sgfHwgaG9sZEJhY2sgOiBob2xkQmFjaztcbiAgICBpZiAodXNlckNvbmZpZy5saXZlU3luY0R1cmF0aW9uIHx8IHVzZXJDb25maWcubGl2ZVN5bmNEdXJhdGlvbkNvdW50IHx8IHRhcmdldExhdGVuY3kgPT09IDApIHtcbiAgICAgIHRhcmdldExhdGVuY3kgPSBsaXZlU3luY0R1cmF0aW9uICE9PSB1bmRlZmluZWQgPyBsaXZlU3luY0R1cmF0aW9uIDogbGl2ZVN5bmNEdXJhdGlvbkNvdW50ICogdGFyZ2V0ZHVyYXRpb247XG4gICAgfVxuICAgIGNvbnN0IG1heExpdmVTeW5jT25TdGFsbEluY3JlYXNlID0gdGFyZ2V0ZHVyYXRpb247XG4gICAgY29uc3QgbGl2ZVN5bmNPblN0YWxsSW5jcmVhc2UgPSAxLjA7XG4gICAgcmV0dXJuIHRhcmdldExhdGVuY3kgKyBNYXRoLm1pbih0aGlzLnN0YWxsQ291bnQgKiBsaXZlU3luY09uU3RhbGxJbmNyZWFzZSwgbWF4TGl2ZVN5bmNPblN0YWxsSW5jcmVhc2UpO1xuICB9XG4gIGdldCBsaXZlU3luY1Bvc2l0aW9uKCkge1xuICAgIGNvbnN0IGxpdmVFZGdlID0gdGhpcy5lc3RpbWF0ZUxpdmVFZGdlKCk7XG4gICAgY29uc3QgdGFyZ2V0TGF0ZW5jeSA9IHRoaXMudGFyZ2V0TGF0ZW5jeTtcbiAgICBjb25zdCBsZXZlbERldGFpbHMgPSB0aGlzLmxldmVsRGV0YWlscztcbiAgICBpZiAobGl2ZUVkZ2UgPT09IG51bGwgfHwgdGFyZ2V0TGF0ZW5jeSA9PT0gbnVsbCB8fCBsZXZlbERldGFpbHMgPT09IG51bGwpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBjb25zdCBlZGdlID0gbGV2ZWxEZXRhaWxzLmVkZ2U7XG4gICAgY29uc3Qgc3luY1Bvc2l0aW9uID0gbGl2ZUVkZ2UgLSB0YXJnZXRMYXRlbmN5IC0gdGhpcy5lZGdlU3RhbGxlZDtcbiAgICBjb25zdCBtaW4gPSBlZGdlIC0gbGV2ZWxEZXRhaWxzLnRvdGFsZHVyYXRpb247XG4gICAgY29uc3QgbWF4ID0gZWRnZSAtICh0aGlzLmNvbmZpZy5sb3dMYXRlbmN5TW9kZSAmJiBsZXZlbERldGFpbHMucGFydFRhcmdldCB8fCBsZXZlbERldGFpbHMudGFyZ2V0ZHVyYXRpb24pO1xuICAgIHJldHVybiBNYXRoLm1pbihNYXRoLm1heChtaW4sIHN5bmNQb3NpdGlvbiksIG1heCk7XG4gIH1cbiAgZ2V0IGRyaWZ0KCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGxldmVsRGV0YWlsc1xuICAgIH0gPSB0aGlzO1xuICAgIGlmIChsZXZlbERldGFpbHMgPT09IG51bGwpIHtcbiAgICAgIHJldHVybiAxO1xuICAgIH1cbiAgICByZXR1cm4gbGV2ZWxEZXRhaWxzLmRyaWZ0O1xuICB9XG4gIGdldCBlZGdlU3RhbGxlZCgpIHtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbERldGFpbHNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAobGV2ZWxEZXRhaWxzID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG4gICAgY29uc3QgbWF4TGV2ZWxVcGRhdGVBZ2UgPSAodGhpcy5jb25maWcubG93TGF0ZW5jeU1vZGUgJiYgbGV2ZWxEZXRhaWxzLnBhcnRUYXJnZXQgfHwgbGV2ZWxEZXRhaWxzLnRhcmdldGR1cmF0aW9uKSAqIDM7XG4gICAgcmV0dXJuIE1hdGgubWF4KGxldmVsRGV0YWlscy5hZ2UgLSBtYXhMZXZlbFVwZGF0ZUFnZSwgMCk7XG4gIH1cbiAgZ2V0IGZvcndhcmRCdWZmZXJMZW5ndGgoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbWVkaWEsXG4gICAgICBsZXZlbERldGFpbHNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIW1lZGlhIHx8ICFsZXZlbERldGFpbHMpIHtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cbiAgICBjb25zdCBidWZmZXJlZFJhbmdlcyA9IG1lZGlhLmJ1ZmZlcmVkLmxlbmd0aDtcbiAgICByZXR1cm4gKGJ1ZmZlcmVkUmFuZ2VzID8gbWVkaWEuYnVmZmVyZWQuZW5kKGJ1ZmZlcmVkUmFuZ2VzIC0gMSkgOiBsZXZlbERldGFpbHMuZWRnZSkgLSB0aGlzLmN1cnJlbnRUaW1lO1xuICB9XG4gIGRlc3Ryb3koKSB7XG4gICAgdGhpcy51bnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgdGhpcy5vbk1lZGlhRGV0YWNoaW5nKCk7XG4gICAgdGhpcy5sZXZlbERldGFpbHMgPSBudWxsO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmhscyA9IHRoaXMudGltZXVwZGF0ZUhhbmRsZXIgPSBudWxsO1xuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIHRoaXMuaGxzLm9uKEV2ZW50cy5NRURJQV9BVFRBQ0hFRCwgdGhpcy5vbk1lZGlhQXR0YWNoZWQsIHRoaXMpO1xuICAgIHRoaXMuaGxzLm9uKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgdGhpcy5obHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIHRoaXMuaGxzLm9uKEV2ZW50cy5MRVZFTF9VUERBVEVELCB0aGlzLm9uTGV2ZWxVcGRhdGVkLCB0aGlzKTtcbiAgICB0aGlzLmhscy5vbihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvciwgdGhpcyk7XG4gIH1cbiAgdW5yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICB0aGlzLmhscy5vZmYoRXZlbnRzLk1FRElBX0FUVEFDSEVELCB0aGlzLm9uTWVkaWFBdHRhY2hlZCwgdGhpcyk7XG4gICAgdGhpcy5obHMub2ZmKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgdGhpcy5obHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICB0aGlzLmhscy5vZmYoRXZlbnRzLkxFVkVMX1VQREFURUQsIHRoaXMub25MZXZlbFVwZGF0ZWQsIHRoaXMpO1xuICAgIHRoaXMuaGxzLm9mZihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvciwgdGhpcyk7XG4gIH1cbiAgb25NZWRpYUF0dGFjaGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5tZWRpYSA9IGRhdGEubWVkaWE7XG4gICAgdGhpcy5tZWRpYS5hZGRFdmVudExpc3RlbmVyKCd0aW1ldXBkYXRlJywgdGhpcy50aW1ldXBkYXRlSGFuZGxlcik7XG4gIH1cbiAgb25NZWRpYURldGFjaGluZygpIHtcbiAgICBpZiAodGhpcy5tZWRpYSkge1xuICAgICAgdGhpcy5tZWRpYS5yZW1vdmVFdmVudExpc3RlbmVyKCd0aW1ldXBkYXRlJywgdGhpcy50aW1ldXBkYXRlSGFuZGxlcik7XG4gICAgICB0aGlzLm1lZGlhID0gbnVsbDtcbiAgICB9XG4gIH1cbiAgb25NYW5pZmVzdExvYWRpbmcoKSB7XG4gICAgdGhpcy5sZXZlbERldGFpbHMgPSBudWxsO1xuICAgIHRoaXMuX2xhdGVuY3kgPSBudWxsO1xuICAgIHRoaXMuc3RhbGxDb3VudCA9IDA7XG4gIH1cbiAgb25MZXZlbFVwZGF0ZWQoZXZlbnQsIHtcbiAgICBkZXRhaWxzXG4gIH0pIHtcbiAgICB0aGlzLmxldmVsRGV0YWlscyA9IGRldGFpbHM7XG4gICAgaWYgKGRldGFpbHMuYWR2YW5jZWQpIHtcbiAgICAgIHRoaXMudGltZXVwZGF0ZSgpO1xuICAgIH1cbiAgICBpZiAoIWRldGFpbHMubGl2ZSAmJiB0aGlzLm1lZGlhKSB7XG4gICAgICB0aGlzLm1lZGlhLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3RpbWV1cGRhdGUnLCB0aGlzLnRpbWV1cGRhdGVIYW5kbGVyKTtcbiAgICB9XG4gIH1cbiAgb25FcnJvcihldmVudCwgZGF0YSkge1xuICAgIHZhciBfdGhpcyRsZXZlbERldGFpbHM7XG4gICAgaWYgKGRhdGEuZGV0YWlscyAhPT0gRXJyb3JEZXRhaWxzLkJVRkZFUl9TVEFMTEVEX0VSUk9SKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuc3RhbGxDb3VudCsrO1xuICAgIGlmICgoX3RoaXMkbGV2ZWxEZXRhaWxzID0gdGhpcy5sZXZlbERldGFpbHMpICE9IG51bGwgJiYgX3RoaXMkbGV2ZWxEZXRhaWxzLmxpdmUpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbcGxheWJhY2stcmF0ZS1jb250cm9sbGVyXTogU3RhbGwgZGV0ZWN0ZWQsIGFkanVzdGluZyB0YXJnZXQgbGF0ZW5jeScpO1xuICAgIH1cbiAgfVxuICB0aW1ldXBkYXRlKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIG1lZGlhLFxuICAgICAgbGV2ZWxEZXRhaWxzXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFtZWRpYSB8fCAhbGV2ZWxEZXRhaWxzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuY3VycmVudFRpbWUgPSBtZWRpYS5jdXJyZW50VGltZTtcbiAgICBjb25zdCBsYXRlbmN5ID0gdGhpcy5jb21wdXRlTGF0ZW5jeSgpO1xuICAgIGlmIChsYXRlbmN5ID09PSBudWxsKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuX2xhdGVuY3kgPSBsYXRlbmN5O1xuXG4gICAgLy8gQWRhcHQgcGxheWJhY2tSYXRlIHRvIG1lZXQgdGFyZ2V0IGxhdGVuY3kgaW4gbG93LWxhdGVuY3kgbW9kZVxuICAgIGNvbnN0IHtcbiAgICAgIGxvd0xhdGVuY3lNb2RlLFxuICAgICAgbWF4TGl2ZVN5bmNQbGF5YmFja1JhdGVcbiAgICB9ID0gdGhpcy5jb25maWc7XG4gICAgaWYgKCFsb3dMYXRlbmN5TW9kZSB8fCBtYXhMaXZlU3luY1BsYXliYWNrUmF0ZSA9PT0gMSB8fCAhbGV2ZWxEZXRhaWxzLmxpdmUpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgdGFyZ2V0TGF0ZW5jeSA9IHRoaXMudGFyZ2V0TGF0ZW5jeTtcbiAgICBpZiAodGFyZ2V0TGF0ZW5jeSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBkaXN0YW5jZUZyb21UYXJnZXQgPSBsYXRlbmN5IC0gdGFyZ2V0TGF0ZW5jeTtcbiAgICAvLyBPbmx5IGFkanVzdCBwbGF5YmFja1JhdGUgd2hlbiB3aXRoaW4gb25lIHRhcmdldCBkdXJhdGlvbiBvZiB0YXJnZXRMYXRlbmN5XG4gICAgLy8gYW5kIG1vcmUgdGhhbiBvbmUgc2Vjb25kIGZyb20gdW5kZXItYnVmZmVyaW5nLlxuICAgIC8vIFBsYXliYWNrIGZ1cnRoZXIgdGhhbiBvbmUgdGFyZ2V0IGR1cmF0aW9uIGZyb20gdGFyZ2V0IGNhbiBiZSBjb25zaWRlcmVkIERWUiBwbGF5YmFjay5cbiAgICBjb25zdCBsaXZlTWluTGF0ZW5jeUR1cmF0aW9uID0gTWF0aC5taW4odGhpcy5tYXhMYXRlbmN5LCB0YXJnZXRMYXRlbmN5ICsgbGV2ZWxEZXRhaWxzLnRhcmdldGR1cmF0aW9uKTtcbiAgICBjb25zdCBpbkxpdmVSYW5nZSA9IGRpc3RhbmNlRnJvbVRhcmdldCA8IGxpdmVNaW5MYXRlbmN5RHVyYXRpb247XG4gICAgaWYgKGluTGl2ZVJhbmdlICYmIGRpc3RhbmNlRnJvbVRhcmdldCA+IDAuMDUgJiYgdGhpcy5mb3J3YXJkQnVmZmVyTGVuZ3RoID4gMSkge1xuICAgICAgY29uc3QgbWF4ID0gTWF0aC5taW4oMiwgTWF0aC5tYXgoMS4wLCBtYXhMaXZlU3luY1BsYXliYWNrUmF0ZSkpO1xuICAgICAgY29uc3QgcmF0ZSA9IE1hdGgucm91bmQoMiAvICgxICsgTWF0aC5leHAoLTAuNzUgKiBkaXN0YW5jZUZyb21UYXJnZXQgLSB0aGlzLmVkZ2VTdGFsbGVkKSkgKiAyMCkgLyAyMDtcbiAgICAgIG1lZGlhLnBsYXliYWNrUmF0ZSA9IE1hdGgubWluKG1heCwgTWF0aC5tYXgoMSwgcmF0ZSkpO1xuICAgIH0gZWxzZSBpZiAobWVkaWEucGxheWJhY2tSYXRlICE9PSAxICYmIG1lZGlhLnBsYXliYWNrUmF0ZSAhPT0gMCkge1xuICAgICAgbWVkaWEucGxheWJhY2tSYXRlID0gMTtcbiAgICB9XG4gIH1cbiAgZXN0aW1hdGVMaXZlRWRnZSgpIHtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbERldGFpbHNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAobGV2ZWxEZXRhaWxzID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIGxldmVsRGV0YWlscy5lZGdlICsgbGV2ZWxEZXRhaWxzLmFnZTtcbiAgfVxuICBjb21wdXRlTGF0ZW5jeSgpIHtcbiAgICBjb25zdCBsaXZlRWRnZSA9IHRoaXMuZXN0aW1hdGVMaXZlRWRnZSgpO1xuICAgIGlmIChsaXZlRWRnZSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiBsaXZlRWRnZSAtIHRoaXMuY3VycmVudFRpbWU7XG4gIH1cbn1cblxuY29uc3QgSGRjcExldmVscyA9IFsnTk9ORScsICdUWVBFLTAnLCAnVFlQRS0xJywgbnVsbF07XG5mdW5jdGlvbiBpc0hkY3BMZXZlbCh2YWx1ZSkge1xuICByZXR1cm4gSGRjcExldmVscy5pbmRleE9mKHZhbHVlKSA+IC0xO1xufVxuY29uc3QgVmlkZW9SYW5nZVZhbHVlcyA9IFsnU0RSJywgJ1BRJywgJ0hMRyddO1xuZnVuY3Rpb24gaXNWaWRlb1JhbmdlKHZhbHVlKSB7XG4gIHJldHVybiAhIXZhbHVlICYmIFZpZGVvUmFuZ2VWYWx1ZXMuaW5kZXhPZih2YWx1ZSkgPiAtMTtcbn1cbnZhciBIbHNTa2lwID0ge1xuICBObzogXCJcIixcbiAgWWVzOiBcIllFU1wiLFxuICB2MjogXCJ2MlwiXG59O1xuZnVuY3Rpb24gZ2V0U2tpcFZhbHVlKGRldGFpbHMpIHtcbiAgY29uc3Qge1xuICAgIGNhblNraXBVbnRpbCxcbiAgICBjYW5Ta2lwRGF0ZVJhbmdlcyxcbiAgICBhZ2VcbiAgfSA9IGRldGFpbHM7XG4gIC8vIEEgQ2xpZW50IFNIT1VMRCBOT1QgcmVxdWVzdCBhIFBsYXlsaXN0IERlbHRhIFVwZGF0ZSB1bmxlc3MgaXQgYWxyZWFkeVxuICAvLyBoYXMgYSB2ZXJzaW9uIG9mIHRoZSBQbGF5bGlzdCB0aGF0IGlzIG5vIG9sZGVyIHRoYW4gb25lLWhhbGYgb2YgdGhlIFNraXAgQm91bmRhcnkuXG4gIC8vIEBzZWU6IGh0dHBzOi8vZGF0YXRyYWNrZXIuaWV0Zi5vcmcvZG9jL2h0bWwvZHJhZnQtcGFudG9zLWhscy1yZmM4MjE2YmlzI3NlY3Rpb24tNi4zLjdcbiAgY29uc3QgcGxheWxpc3RSZWNlbnRFbm91Z2ggPSBhZ2UgPCBjYW5Ta2lwVW50aWwgLyAyO1xuICBpZiAoY2FuU2tpcFVudGlsICYmIHBsYXlsaXN0UmVjZW50RW5vdWdoKSB7XG4gICAgaWYgKGNhblNraXBEYXRlUmFuZ2VzKSB7XG4gICAgICByZXR1cm4gSGxzU2tpcC52MjtcbiAgICB9XG4gICAgcmV0dXJuIEhsc1NraXAuWWVzO1xuICB9XG4gIHJldHVybiBIbHNTa2lwLk5vO1xufVxuY2xhc3MgSGxzVXJsUGFyYW1ldGVycyB7XG4gIGNvbnN0cnVjdG9yKG1zbiwgcGFydCwgc2tpcCkge1xuICAgIHRoaXMubXNuID0gdm9pZCAwO1xuICAgIHRoaXMucGFydCA9IHZvaWQgMDtcbiAgICB0aGlzLnNraXAgPSB2b2lkIDA7XG4gICAgdGhpcy5tc24gPSBtc247XG4gICAgdGhpcy5wYXJ0ID0gcGFydDtcbiAgICB0aGlzLnNraXAgPSBza2lwO1xuICB9XG4gIGFkZERpcmVjdGl2ZXModXJpKSB7XG4gICAgY29uc3QgdXJsID0gbmV3IHNlbGYuVVJMKHVyaSk7XG4gICAgaWYgKHRoaXMubXNuICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIHVybC5zZWFyY2hQYXJhbXMuc2V0KCdfSExTX21zbicsIHRoaXMubXNuLnRvU3RyaW5nKCkpO1xuICAgIH1cbiAgICBpZiAodGhpcy5wYXJ0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIHVybC5zZWFyY2hQYXJhbXMuc2V0KCdfSExTX3BhcnQnLCB0aGlzLnBhcnQudG9TdHJpbmcoKSk7XG4gICAgfVxuICAgIGlmICh0aGlzLnNraXApIHtcbiAgICAgIHVybC5zZWFyY2hQYXJhbXMuc2V0KCdfSExTX3NraXAnLCB0aGlzLnNraXApO1xuICAgIH1cbiAgICByZXR1cm4gdXJsLmhyZWY7XG4gIH1cbn1cbmNsYXNzIExldmVsIHtcbiAgY29uc3RydWN0b3IoZGF0YSkge1xuICAgIHRoaXMuX2F0dHJzID0gdm9pZCAwO1xuICAgIHRoaXMuYXVkaW9Db2RlYyA9IHZvaWQgMDtcbiAgICB0aGlzLmJpdHJhdGUgPSB2b2lkIDA7XG4gICAgdGhpcy5jb2RlY1NldCA9IHZvaWQgMDtcbiAgICB0aGlzLnVybCA9IHZvaWQgMDtcbiAgICB0aGlzLmZyYW1lUmF0ZSA9IHZvaWQgMDtcbiAgICB0aGlzLmhlaWdodCA9IHZvaWQgMDtcbiAgICB0aGlzLmlkID0gdm9pZCAwO1xuICAgIHRoaXMubmFtZSA9IHZvaWQgMDtcbiAgICB0aGlzLnZpZGVvQ29kZWMgPSB2b2lkIDA7XG4gICAgdGhpcy53aWR0aCA9IHZvaWQgMDtcbiAgICB0aGlzLmRldGFpbHMgPSB2b2lkIDA7XG4gICAgdGhpcy5mcmFnbWVudEVycm9yID0gMDtcbiAgICB0aGlzLmxvYWRFcnJvciA9IDA7XG4gICAgdGhpcy5sb2FkZWQgPSB2b2lkIDA7XG4gICAgdGhpcy5yZWFsQml0cmF0ZSA9IDA7XG4gICAgdGhpcy5zdXBwb3J0ZWRQcm9taXNlID0gdm9pZCAwO1xuICAgIHRoaXMuc3VwcG9ydGVkUmVzdWx0ID0gdm9pZCAwO1xuICAgIHRoaXMuX2F2Z0JpdHJhdGUgPSAwO1xuICAgIHRoaXMuX2F1ZGlvR3JvdXBzID0gdm9pZCAwO1xuICAgIHRoaXMuX3N1YnRpdGxlR3JvdXBzID0gdm9pZCAwO1xuICAgIC8vIERlcHJlY2F0ZWQgKHJldGFpbmVkIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eSlcbiAgICB0aGlzLl91cmxJZCA9IDA7XG4gICAgdGhpcy51cmwgPSBbZGF0YS51cmxdO1xuICAgIHRoaXMuX2F0dHJzID0gW2RhdGEuYXR0cnNdO1xuICAgIHRoaXMuYml0cmF0ZSA9IGRhdGEuYml0cmF0ZTtcbiAgICBpZiAoZGF0YS5kZXRhaWxzKSB7XG4gICAgICB0aGlzLmRldGFpbHMgPSBkYXRhLmRldGFpbHM7XG4gICAgfVxuICAgIHRoaXMuaWQgPSBkYXRhLmlkIHx8IDA7XG4gICAgdGhpcy5uYW1lID0gZGF0YS5uYW1lO1xuICAgIHRoaXMud2lkdGggPSBkYXRhLndpZHRoIHx8IDA7XG4gICAgdGhpcy5oZWlnaHQgPSBkYXRhLmhlaWdodCB8fCAwO1xuICAgIHRoaXMuZnJhbWVSYXRlID0gZGF0YS5hdHRycy5vcHRpb25hbEZsb2F0KCdGUkFNRS1SQVRFJywgMCk7XG4gICAgdGhpcy5fYXZnQml0cmF0ZSA9IGRhdGEuYXR0cnMuZGVjaW1hbEludGVnZXIoJ0FWRVJBR0UtQkFORFdJRFRIJyk7XG4gICAgdGhpcy5hdWRpb0NvZGVjID0gZGF0YS5hdWRpb0NvZGVjO1xuICAgIHRoaXMudmlkZW9Db2RlYyA9IGRhdGEudmlkZW9Db2RlYztcbiAgICB0aGlzLmNvZGVjU2V0ID0gW2RhdGEudmlkZW9Db2RlYywgZGF0YS5hdWRpb0NvZGVjXS5maWx0ZXIoYyA9PiAhIWMpLm1hcChzID0+IHMuc3Vic3RyaW5nKDAsIDQpKS5qb2luKCcsJyk7XG4gICAgdGhpcy5hZGRHcm91cElkKCdhdWRpbycsIGRhdGEuYXR0cnMuQVVESU8pO1xuICAgIHRoaXMuYWRkR3JvdXBJZCgndGV4dCcsIGRhdGEuYXR0cnMuU1VCVElUTEVTKTtcbiAgfVxuICBnZXQgbWF4Qml0cmF0ZSgpIHtcbiAgICByZXR1cm4gTWF0aC5tYXgodGhpcy5yZWFsQml0cmF0ZSwgdGhpcy5iaXRyYXRlKTtcbiAgfVxuICBnZXQgYXZlcmFnZUJpdHJhdGUoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2F2Z0JpdHJhdGUgfHwgdGhpcy5yZWFsQml0cmF0ZSB8fCB0aGlzLmJpdHJhdGU7XG4gIH1cbiAgZ2V0IGF0dHJzKCkge1xuICAgIHJldHVybiB0aGlzLl9hdHRyc1swXTtcbiAgfVxuICBnZXQgY29kZWNzKCkge1xuICAgIHJldHVybiB0aGlzLmF0dHJzLkNPREVDUyB8fCAnJztcbiAgfVxuICBnZXQgcGF0aHdheUlkKCkge1xuICAgIHJldHVybiB0aGlzLmF0dHJzWydQQVRIV0FZLUlEJ10gfHwgJy4nO1xuICB9XG4gIGdldCB2aWRlb1JhbmdlKCkge1xuICAgIHJldHVybiB0aGlzLmF0dHJzWydWSURFTy1SQU5HRSddIHx8ICdTRFInO1xuICB9XG4gIGdldCBzY29yZSgpIHtcbiAgICByZXR1cm4gdGhpcy5hdHRycy5vcHRpb25hbEZsb2F0KCdTQ09SRScsIDApO1xuICB9XG4gIGdldCB1cmkoKSB7XG4gICAgcmV0dXJuIHRoaXMudXJsWzBdIHx8ICcnO1xuICB9XG4gIGhhc0F1ZGlvR3JvdXAoZ3JvdXBJZCkge1xuICAgIHJldHVybiBoYXNHcm91cCh0aGlzLl9hdWRpb0dyb3VwcywgZ3JvdXBJZCk7XG4gIH1cbiAgaGFzU3VidGl0bGVHcm91cChncm91cElkKSB7XG4gICAgcmV0dXJuIGhhc0dyb3VwKHRoaXMuX3N1YnRpdGxlR3JvdXBzLCBncm91cElkKTtcbiAgfVxuICBnZXQgYXVkaW9Hcm91cHMoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2F1ZGlvR3JvdXBzO1xuICB9XG4gIGdldCBzdWJ0aXRsZUdyb3VwcygpIHtcbiAgICByZXR1cm4gdGhpcy5fc3VidGl0bGVHcm91cHM7XG4gIH1cbiAgYWRkR3JvdXBJZCh0eXBlLCBncm91cElkKSB7XG4gICAgaWYgKCFncm91cElkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh0eXBlID09PSAnYXVkaW8nKSB7XG4gICAgICBsZXQgYXVkaW9Hcm91cHMgPSB0aGlzLl9hdWRpb0dyb3VwcztcbiAgICAgIGlmICghYXVkaW9Hcm91cHMpIHtcbiAgICAgICAgYXVkaW9Hcm91cHMgPSB0aGlzLl9hdWRpb0dyb3VwcyA9IFtdO1xuICAgICAgfVxuICAgICAgaWYgKGF1ZGlvR3JvdXBzLmluZGV4T2YoZ3JvdXBJZCkgPT09IC0xKSB7XG4gICAgICAgIGF1ZGlvR3JvdXBzLnB1c2goZ3JvdXBJZCk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0eXBlID09PSAndGV4dCcpIHtcbiAgICAgIGxldCBzdWJ0aXRsZUdyb3VwcyA9IHRoaXMuX3N1YnRpdGxlR3JvdXBzO1xuICAgICAgaWYgKCFzdWJ0aXRsZUdyb3Vwcykge1xuICAgICAgICBzdWJ0aXRsZUdyb3VwcyA9IHRoaXMuX3N1YnRpdGxlR3JvdXBzID0gW107XG4gICAgICB9XG4gICAgICBpZiAoc3VidGl0bGVHcm91cHMuaW5kZXhPZihncm91cElkKSA9PT0gLTEpIHtcbiAgICAgICAgc3VidGl0bGVHcm91cHMucHVzaChncm91cElkKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvLyBEZXByZWNhdGVkIG1ldGhvZHMgKHJldGFpbmVkIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eSlcbiAgZ2V0IHVybElkKCkge1xuICAgIHJldHVybiAwO1xuICB9XG4gIHNldCB1cmxJZCh2YWx1ZSkge31cbiAgZ2V0IGF1ZGlvR3JvdXBJZHMoKSB7XG4gICAgcmV0dXJuIHRoaXMuYXVkaW9Hcm91cHMgPyBbdGhpcy5hdWRpb0dyb3VwSWRdIDogdW5kZWZpbmVkO1xuICB9XG4gIGdldCB0ZXh0R3JvdXBJZHMoKSB7XG4gICAgcmV0dXJuIHRoaXMuc3VidGl0bGVHcm91cHMgPyBbdGhpcy50ZXh0R3JvdXBJZF0gOiB1bmRlZmluZWQ7XG4gIH1cbiAgZ2V0IGF1ZGlvR3JvdXBJZCgpIHtcbiAgICB2YXIgX3RoaXMkYXVkaW9Hcm91cHM7XG4gICAgcmV0dXJuIChfdGhpcyRhdWRpb0dyb3VwcyA9IHRoaXMuYXVkaW9Hcm91cHMpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRhdWRpb0dyb3Vwc1swXTtcbiAgfVxuICBnZXQgdGV4dEdyb3VwSWQoKSB7XG4gICAgdmFyIF90aGlzJHN1YnRpdGxlR3JvdXBzO1xuICAgIHJldHVybiAoX3RoaXMkc3VidGl0bGVHcm91cHMgPSB0aGlzLnN1YnRpdGxlR3JvdXBzKSA9PSBudWxsID8gdm9pZCAwIDogX3RoaXMkc3VidGl0bGVHcm91cHNbMF07XG4gIH1cbiAgYWRkRmFsbGJhY2soKSB7fVxufVxuZnVuY3Rpb24gaGFzR3JvdXAoZ3JvdXBzLCBncm91cElkKSB7XG4gIGlmICghZ3JvdXBJZCB8fCAhZ3JvdXBzKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIHJldHVybiBncm91cHMuaW5kZXhPZihncm91cElkKSAhPT0gLTE7XG59XG5cbmZ1bmN0aW9uIHVwZGF0ZUZyb21Ub1BUUyhmcmFnRnJvbSwgZnJhZ1RvKSB7XG4gIGNvbnN0IGZyYWdUb1BUUyA9IGZyYWdUby5zdGFydFBUUztcbiAgLy8gaWYgd2Uga25vdyBzdGFydFBUU1t0b0lkeF1cbiAgaWYgKGlzRmluaXRlTnVtYmVyKGZyYWdUb1BUUykpIHtcbiAgICAvLyB1cGRhdGUgZnJhZ21lbnQgZHVyYXRpb24uXG4gICAgLy8gaXQgaGVscHMgdG8gZml4IGRyaWZ0cyBiZXR3ZWVuIHBsYXlsaXN0IHJlcG9ydGVkIGR1cmF0aW9uIGFuZCBmcmFnbWVudCByZWFsIGR1cmF0aW9uXG4gICAgbGV0IGR1cmF0aW9uID0gMDtcbiAgICBsZXQgZnJhZztcbiAgICBpZiAoZnJhZ1RvLnNuID4gZnJhZ0Zyb20uc24pIHtcbiAgICAgIGR1cmF0aW9uID0gZnJhZ1RvUFRTIC0gZnJhZ0Zyb20uc3RhcnQ7XG4gICAgICBmcmFnID0gZnJhZ0Zyb207XG4gICAgfSBlbHNlIHtcbiAgICAgIGR1cmF0aW9uID0gZnJhZ0Zyb20uc3RhcnQgLSBmcmFnVG9QVFM7XG4gICAgICBmcmFnID0gZnJhZ1RvO1xuICAgIH1cbiAgICBpZiAoZnJhZy5kdXJhdGlvbiAhPT0gZHVyYXRpb24pIHtcbiAgICAgIGZyYWcuZHVyYXRpb24gPSBkdXJhdGlvbjtcbiAgICB9XG4gICAgLy8gd2UgZG9udCBrbm93IHN0YXJ0UFRTW3RvSWR4XVxuICB9IGVsc2UgaWYgKGZyYWdUby5zbiA+IGZyYWdGcm9tLnNuKSB7XG4gICAgY29uc3QgY29udGlndW91cyA9IGZyYWdGcm9tLmNjID09PSBmcmFnVG8uY2M7XG4gICAgLy8gVE9ETzogV2l0aCBwYXJ0LWxvYWRpbmcgZW5kL2R1cmF0aW9ucyB3ZSBuZWVkIHRvIGNvbmZpcm0gdGhlIHdob2xlIGZyYWdtZW50IGlzIGxvYWRlZCBiZWZvcmUgdXNpbmcgKG9yIHNldHRpbmcpIG1pbkVuZFBUU1xuICAgIGlmIChjb250aWd1b3VzICYmIGZyYWdGcm9tLm1pbkVuZFBUUykge1xuICAgICAgZnJhZ1RvLnN0YXJ0ID0gZnJhZ0Zyb20uc3RhcnQgKyAoZnJhZ0Zyb20ubWluRW5kUFRTIC0gZnJhZ0Zyb20uc3RhcnQpO1xuICAgIH0gZWxzZSB7XG4gICAgICBmcmFnVG8uc3RhcnQgPSBmcmFnRnJvbS5zdGFydCArIGZyYWdGcm9tLmR1cmF0aW9uO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICBmcmFnVG8uc3RhcnQgPSBNYXRoLm1heChmcmFnRnJvbS5zdGFydCAtIGZyYWdUby5kdXJhdGlvbiwgMCk7XG4gIH1cbn1cbmZ1bmN0aW9uIHVwZGF0ZUZyYWdQVFNEVFMoZGV0YWlscywgZnJhZywgc3RhcnRQVFMsIGVuZFBUUywgc3RhcnREVFMsIGVuZERUUykge1xuICBjb25zdCBwYXJzZWRNZWRpYUR1cmF0aW9uID0gZW5kUFRTIC0gc3RhcnRQVFM7XG4gIGlmIChwYXJzZWRNZWRpYUR1cmF0aW9uIDw9IDApIHtcbiAgICBsb2dnZXIud2FybignRnJhZ21lbnQgc2hvdWxkIGhhdmUgYSBwb3NpdGl2ZSBkdXJhdGlvbicsIGZyYWcpO1xuICAgIGVuZFBUUyA9IHN0YXJ0UFRTICsgZnJhZy5kdXJhdGlvbjtcbiAgICBlbmREVFMgPSBzdGFydERUUyArIGZyYWcuZHVyYXRpb247XG4gIH1cbiAgbGV0IG1heFN0YXJ0UFRTID0gc3RhcnRQVFM7XG4gIGxldCBtaW5FbmRQVFMgPSBlbmRQVFM7XG4gIGNvbnN0IGZyYWdTdGFydFB0cyA9IGZyYWcuc3RhcnRQVFM7XG4gIGNvbnN0IGZyYWdFbmRQdHMgPSBmcmFnLmVuZFBUUztcbiAgaWYgKGlzRmluaXRlTnVtYmVyKGZyYWdTdGFydFB0cykpIHtcbiAgICAvLyBkZWx0YSBQVFMgYmV0d2VlbiBhdWRpbyBhbmQgdmlkZW9cbiAgICBjb25zdCBkZWx0YVBUUyA9IE1hdGguYWJzKGZyYWdTdGFydFB0cyAtIHN0YXJ0UFRTKTtcbiAgICBpZiAoIWlzRmluaXRlTnVtYmVyKGZyYWcuZGVsdGFQVFMpKSB7XG4gICAgICBmcmFnLmRlbHRhUFRTID0gZGVsdGFQVFM7XG4gICAgfSBlbHNlIHtcbiAgICAgIGZyYWcuZGVsdGFQVFMgPSBNYXRoLm1heChkZWx0YVBUUywgZnJhZy5kZWx0YVBUUyk7XG4gICAgfVxuICAgIG1heFN0YXJ0UFRTID0gTWF0aC5tYXgoc3RhcnRQVFMsIGZyYWdTdGFydFB0cyk7XG4gICAgc3RhcnRQVFMgPSBNYXRoLm1pbihzdGFydFBUUywgZnJhZ1N0YXJ0UHRzKTtcbiAgICBzdGFydERUUyA9IE1hdGgubWluKHN0YXJ0RFRTLCBmcmFnLnN0YXJ0RFRTKTtcbiAgICBtaW5FbmRQVFMgPSBNYXRoLm1pbihlbmRQVFMsIGZyYWdFbmRQdHMpO1xuICAgIGVuZFBUUyA9IE1hdGgubWF4KGVuZFBUUywgZnJhZ0VuZFB0cyk7XG4gICAgZW5kRFRTID0gTWF0aC5tYXgoZW5kRFRTLCBmcmFnLmVuZERUUyk7XG4gIH1cbiAgY29uc3QgZHJpZnQgPSBzdGFydFBUUyAtIGZyYWcuc3RhcnQ7XG4gIGlmIChmcmFnLnN0YXJ0ICE9PSAwKSB7XG4gICAgZnJhZy5zdGFydCA9IHN0YXJ0UFRTO1xuICB9XG4gIGZyYWcuZHVyYXRpb24gPSBlbmRQVFMgLSBmcmFnLnN0YXJ0O1xuICBmcmFnLnN0YXJ0UFRTID0gc3RhcnRQVFM7XG4gIGZyYWcubWF4U3RhcnRQVFMgPSBtYXhTdGFydFBUUztcbiAgZnJhZy5zdGFydERUUyA9IHN0YXJ0RFRTO1xuICBmcmFnLmVuZFBUUyA9IGVuZFBUUztcbiAgZnJhZy5taW5FbmRQVFMgPSBtaW5FbmRQVFM7XG4gIGZyYWcuZW5kRFRTID0gZW5kRFRTO1xuICBjb25zdCBzbiA9IGZyYWcuc247IC8vICdpbml0U2VnbWVudCdcbiAgLy8gZXhpdCBpZiBzbiBvdXQgb2YgcmFuZ2VcbiAgaWYgKCFkZXRhaWxzIHx8IHNuIDwgZGV0YWlscy5zdGFydFNOIHx8IHNuID4gZGV0YWlscy5lbmRTTikge1xuICAgIHJldHVybiAwO1xuICB9XG4gIGxldCBpO1xuICBjb25zdCBmcmFnSWR4ID0gc24gLSBkZXRhaWxzLnN0YXJ0U047XG4gIGNvbnN0IGZyYWdtZW50cyA9IGRldGFpbHMuZnJhZ21lbnRzO1xuICAvLyB1cGRhdGUgZnJhZyByZWZlcmVuY2UgaW4gZnJhZ21lbnRzIGFycmF5XG4gIC8vIHJhdGlvbmFsZSBpcyB0aGF0IGZyYWdtZW50cyBhcnJheSBtaWdodCBub3QgY29udGFpbiB0aGlzIGZyYWcgb2JqZWN0LlxuICAvLyB0aGlzIHdpbGwgaGFwcGVuIGlmIHBsYXlsaXN0IGhhcyBiZWVuIHJlZnJlc2hlZCBiZXR3ZWVuIGZyYWcgbG9hZGluZyBhbmQgY2FsbCB0byB1cGRhdGVGcmFnUFRTRFRTKClcbiAgLy8gaWYgd2UgZG9uJ3QgdXBkYXRlIGZyYWcsIHdlIHdvbid0IGJlIGFibGUgdG8gcHJvcGFnYXRlIFBUUyBpbmZvIG9uIHRoZSBwbGF5bGlzdFxuICAvLyByZXN1bHRpbmcgaW4gaW52YWxpZCBzbGlkaW5nIGNvbXB1dGF0aW9uXG4gIGZyYWdtZW50c1tmcmFnSWR4XSA9IGZyYWc7XG4gIC8vIGFkanVzdCBmcmFnbWVudCBQVFMvZHVyYXRpb24gZnJvbSBzZXFudW0tMSB0byBmcmFnIDBcbiAgZm9yIChpID0gZnJhZ0lkeDsgaSA+IDA7IGktLSkge1xuICAgIHVwZGF0ZUZyb21Ub1BUUyhmcmFnbWVudHNbaV0sIGZyYWdtZW50c1tpIC0gMV0pO1xuICB9XG5cbiAgLy8gYWRqdXN0IGZyYWdtZW50IFBUUy9kdXJhdGlvbiBmcm9tIHNlcW51bSB0byBsYXN0IGZyYWdcbiAgZm9yIChpID0gZnJhZ0lkeDsgaSA8IGZyYWdtZW50cy5sZW5ndGggLSAxOyBpKyspIHtcbiAgICB1cGRhdGVGcm9tVG9QVFMoZnJhZ21lbnRzW2ldLCBmcmFnbWVudHNbaSArIDFdKTtcbiAgfVxuICBpZiAoZGV0YWlscy5mcmFnbWVudEhpbnQpIHtcbiAgICB1cGRhdGVGcm9tVG9QVFMoZnJhZ21lbnRzW2ZyYWdtZW50cy5sZW5ndGggLSAxXSwgZGV0YWlscy5mcmFnbWVudEhpbnQpO1xuICB9XG4gIGRldGFpbHMuUFRTS25vd24gPSBkZXRhaWxzLmFsaWduZWRTbGlkaW5nID0gdHJ1ZTtcbiAgcmV0dXJuIGRyaWZ0O1xufVxuZnVuY3Rpb24gbWVyZ2VEZXRhaWxzKG9sZERldGFpbHMsIG5ld0RldGFpbHMpIHtcbiAgLy8gVHJhY2sgdGhlIGxhc3QgaW5pdFNlZ21lbnQgcHJvY2Vzc2VkLiBJbml0aWFsaXplIGl0IHRvIHRoZSBsYXN0IG9uZSBvbiB0aGUgdGltZWxpbmUuXG4gIGxldCBjdXJyZW50SW5pdFNlZ21lbnQgPSBudWxsO1xuICBjb25zdCBvbGRGcmFnbWVudHMgPSBvbGREZXRhaWxzLmZyYWdtZW50cztcbiAgZm9yIChsZXQgaSA9IG9sZEZyYWdtZW50cy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgIGNvbnN0IG9sZEluaXQgPSBvbGRGcmFnbWVudHNbaV0uaW5pdFNlZ21lbnQ7XG4gICAgaWYgKG9sZEluaXQpIHtcbiAgICAgIGN1cnJlbnRJbml0U2VnbWVudCA9IG9sZEluaXQ7XG4gICAgICBicmVhaztcbiAgICB9XG4gIH1cbiAgaWYgKG9sZERldGFpbHMuZnJhZ21lbnRIaW50KSB7XG4gICAgLy8gcHJldmVudCBQVFMgYW5kIGR1cmF0aW9uIGZyb20gYmVpbmcgYWRqdXN0ZWQgb24gdGhlIG5leHQgaGludFxuICAgIGRlbGV0ZSBvbGREZXRhaWxzLmZyYWdtZW50SGludC5lbmRQVFM7XG4gIH1cbiAgLy8gY2hlY2sgaWYgb2xkL25ldyBwbGF5bGlzdHMgaGF2ZSBmcmFnbWVudHMgaW4gY29tbW9uXG4gIC8vIGxvb3AgdGhyb3VnaCBvdmVybGFwcGluZyBTTiBhbmQgdXBkYXRlIHN0YXJ0UFRTICwgY2MsIGFuZCBkdXJhdGlvbiBpZiBhbnkgZm91bmRcbiAgbGV0IGNjT2Zmc2V0ID0gMDtcbiAgbGV0IFBUU0ZyYWc7XG4gIG1hcEZyYWdtZW50SW50ZXJzZWN0aW9uKG9sZERldGFpbHMsIG5ld0RldGFpbHMsIChvbGRGcmFnLCBuZXdGcmFnKSA9PiB7XG4gICAgaWYgKG9sZEZyYWcucmVsdXJsKSB7XG4gICAgICAvLyBEbyBub3QgY29tcGFyZSBDQyBpZiB0aGUgb2xkIGZyYWdtZW50IGhhcyBubyB1cmwuIFRoaXMgaXMgYSBsZXZlbC5mcmFnbWVudEhpbnQgdXNlZCBieSBMTC1ITFMgcGFydHMuXG4gICAgICAvLyBJdCBtYXliZSBiZSBvZmYgYnkgMSBpZiBpdCB3YXMgY3JlYXRlZCBiZWZvcmUgYW55IHBhcnRzIG9yIGRpc2NvbnRpbnVpdHkgdGFncyB3ZXJlIGFwcGVuZGVkIHRvIHRoZSBlbmRcbiAgICAgIC8vIG9mIHRoZSBwbGF5bGlzdC5cbiAgICAgIGNjT2Zmc2V0ID0gb2xkRnJhZy5jYyAtIG5ld0ZyYWcuY2M7XG4gICAgfVxuICAgIGlmIChpc0Zpbml0ZU51bWJlcihvbGRGcmFnLnN0YXJ0UFRTKSAmJiBpc0Zpbml0ZU51bWJlcihvbGRGcmFnLmVuZFBUUykpIHtcbiAgICAgIG5ld0ZyYWcuc3RhcnQgPSBuZXdGcmFnLnN0YXJ0UFRTID0gb2xkRnJhZy5zdGFydFBUUztcbiAgICAgIG5ld0ZyYWcuc3RhcnREVFMgPSBvbGRGcmFnLnN0YXJ0RFRTO1xuICAgICAgbmV3RnJhZy5tYXhTdGFydFBUUyA9IG9sZEZyYWcubWF4U3RhcnRQVFM7XG4gICAgICBuZXdGcmFnLmVuZFBUUyA9IG9sZEZyYWcuZW5kUFRTO1xuICAgICAgbmV3RnJhZy5lbmREVFMgPSBvbGRGcmFnLmVuZERUUztcbiAgICAgIG5ld0ZyYWcubWluRW5kUFRTID0gb2xkRnJhZy5taW5FbmRQVFM7XG4gICAgICBuZXdGcmFnLmR1cmF0aW9uID0gb2xkRnJhZy5lbmRQVFMgLSBvbGRGcmFnLnN0YXJ0UFRTO1xuICAgICAgaWYgKG5ld0ZyYWcuZHVyYXRpb24pIHtcbiAgICAgICAgUFRTRnJhZyA9IG5ld0ZyYWc7XG4gICAgICB9XG5cbiAgICAgIC8vIFBUUyBpcyBrbm93biB3aGVuIGFueSBzZWdtZW50IGhhcyBzdGFydFBUUyBhbmQgZW5kUFRTXG4gICAgICBuZXdEZXRhaWxzLlBUU0tub3duID0gbmV3RGV0YWlscy5hbGlnbmVkU2xpZGluZyA9IHRydWU7XG4gICAgfVxuICAgIG5ld0ZyYWcuZWxlbWVudGFyeVN0cmVhbXMgPSBvbGRGcmFnLmVsZW1lbnRhcnlTdHJlYW1zO1xuICAgIG5ld0ZyYWcubG9hZGVyID0gb2xkRnJhZy5sb2FkZXI7XG4gICAgbmV3RnJhZy5zdGF0cyA9IG9sZEZyYWcuc3RhdHM7XG4gICAgaWYgKG9sZEZyYWcuaW5pdFNlZ21lbnQpIHtcbiAgICAgIG5ld0ZyYWcuaW5pdFNlZ21lbnQgPSBvbGRGcmFnLmluaXRTZWdtZW50O1xuICAgICAgY3VycmVudEluaXRTZWdtZW50ID0gb2xkRnJhZy5pbml0U2VnbWVudDtcbiAgICB9XG4gIH0pO1xuICBpZiAoY3VycmVudEluaXRTZWdtZW50KSB7XG4gICAgY29uc3QgZnJhZ21lbnRzVG9DaGVjayA9IG5ld0RldGFpbHMuZnJhZ21lbnRIaW50ID8gbmV3RGV0YWlscy5mcmFnbWVudHMuY29uY2F0KG5ld0RldGFpbHMuZnJhZ21lbnRIaW50KSA6IG5ld0RldGFpbHMuZnJhZ21lbnRzO1xuICAgIGZyYWdtZW50c1RvQ2hlY2suZm9yRWFjaChmcmFnID0+IHtcbiAgICAgIHZhciBfY3VycmVudEluaXRTZWdtZW50O1xuICAgICAgaWYgKGZyYWcgJiYgKCFmcmFnLmluaXRTZWdtZW50IHx8IGZyYWcuaW5pdFNlZ21lbnQucmVsdXJsID09PSAoKF9jdXJyZW50SW5pdFNlZ21lbnQgPSBjdXJyZW50SW5pdFNlZ21lbnQpID09IG51bGwgPyB2b2lkIDAgOiBfY3VycmVudEluaXRTZWdtZW50LnJlbHVybCkpKSB7XG4gICAgICAgIGZyYWcuaW5pdFNlZ21lbnQgPSBjdXJyZW50SW5pdFNlZ21lbnQ7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbiAgaWYgKG5ld0RldGFpbHMuc2tpcHBlZFNlZ21lbnRzKSB7XG4gICAgbmV3RGV0YWlscy5kZWx0YVVwZGF0ZUZhaWxlZCA9IG5ld0RldGFpbHMuZnJhZ21lbnRzLnNvbWUoZnJhZyA9PiAhZnJhZyk7XG4gICAgaWYgKG5ld0RldGFpbHMuZGVsdGFVcGRhdGVGYWlsZWQpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbbGV2ZWwtaGVscGVyXSBQcmV2aW91cyBwbGF5bGlzdCBtaXNzaW5nIHNlZ21lbnRzIHNraXBwZWQgaW4gZGVsdGEgcGxheWxpc3QnKTtcbiAgICAgIGZvciAobGV0IGkgPSBuZXdEZXRhaWxzLnNraXBwZWRTZWdtZW50czsgaS0tOykge1xuICAgICAgICBuZXdEZXRhaWxzLmZyYWdtZW50cy5zaGlmdCgpO1xuICAgICAgfVxuICAgICAgbmV3RGV0YWlscy5zdGFydFNOID0gbmV3RGV0YWlscy5mcmFnbWVudHNbMF0uc247XG4gICAgICBuZXdEZXRhaWxzLnN0YXJ0Q0MgPSBuZXdEZXRhaWxzLmZyYWdtZW50c1swXS5jYztcbiAgICB9IGVsc2UgaWYgKG5ld0RldGFpbHMuY2FuU2tpcERhdGVSYW5nZXMpIHtcbiAgICAgIG5ld0RldGFpbHMuZGF0ZVJhbmdlcyA9IG1lcmdlRGF0ZVJhbmdlcyhvbGREZXRhaWxzLmRhdGVSYW5nZXMsIG5ld0RldGFpbHMuZGF0ZVJhbmdlcywgbmV3RGV0YWlscy5yZWNlbnRseVJlbW92ZWREYXRlcmFuZ2VzKTtcbiAgICB9XG4gIH1cbiAgY29uc3QgbmV3RnJhZ21lbnRzID0gbmV3RGV0YWlscy5mcmFnbWVudHM7XG4gIGlmIChjY09mZnNldCkge1xuICAgIGxvZ2dlci53YXJuKCdkaXNjb250aW51aXR5IHNsaWRpbmcgZnJvbSBwbGF5bGlzdCwgdGFrZSBkcmlmdCBpbnRvIGFjY291bnQnKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IG5ld0ZyYWdtZW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgbmV3RnJhZ21lbnRzW2ldLmNjICs9IGNjT2Zmc2V0O1xuICAgIH1cbiAgfVxuICBpZiAobmV3RGV0YWlscy5za2lwcGVkU2VnbWVudHMpIHtcbiAgICBuZXdEZXRhaWxzLnN0YXJ0Q0MgPSBuZXdEZXRhaWxzLmZyYWdtZW50c1swXS5jYztcbiAgfVxuXG4gIC8vIE1lcmdlIHBhcnRzXG4gIG1hcFBhcnRJbnRlcnNlY3Rpb24ob2xkRGV0YWlscy5wYXJ0TGlzdCwgbmV3RGV0YWlscy5wYXJ0TGlzdCwgKG9sZFBhcnQsIG5ld1BhcnQpID0+IHtcbiAgICBuZXdQYXJ0LmVsZW1lbnRhcnlTdHJlYW1zID0gb2xkUGFydC5lbGVtZW50YXJ5U3RyZWFtcztcbiAgICBuZXdQYXJ0LnN0YXRzID0gb2xkUGFydC5zdGF0cztcbiAgfSk7XG5cbiAgLy8gaWYgYXQgbGVhc3Qgb25lIGZyYWdtZW50IGNvbnRhaW5zIFBUUyBpbmZvLCByZWNvbXB1dGUgUFRTIGluZm9ybWF0aW9uIGZvciBhbGwgZnJhZ21lbnRzXG4gIGlmIChQVFNGcmFnKSB7XG4gICAgdXBkYXRlRnJhZ1BUU0RUUyhuZXdEZXRhaWxzLCBQVFNGcmFnLCBQVFNGcmFnLnN0YXJ0UFRTLCBQVFNGcmFnLmVuZFBUUywgUFRTRnJhZy5zdGFydERUUywgUFRTRnJhZy5lbmREVFMpO1xuICB9IGVsc2Uge1xuICAgIC8vIGVuc3VyZSB0aGF0IGRlbHRhIGlzIHdpdGhpbiBvbGRGcmFnbWVudHMgcmFuZ2VcbiAgICAvLyBhbHNvIGFkanVzdCBzbGlkaW5nIGluIGNhc2UgZGVsdGEgaXMgMCAod2UgY291bGQgaGF2ZSBvbGQ9WzUwLTYwXSBhbmQgbmV3PW9sZD1bNTAtNjFdKVxuICAgIC8vIGluIHRoYXQgY2FzZSB3ZSBhbHNvIG5lZWQgdG8gYWRqdXN0IHN0YXJ0IG9mZnNldCBvZiBhbGwgZnJhZ21lbnRzXG4gICAgYWRqdXN0U2xpZGluZyhvbGREZXRhaWxzLCBuZXdEZXRhaWxzKTtcbiAgfVxuICBpZiAobmV3RnJhZ21lbnRzLmxlbmd0aCkge1xuICAgIG5ld0RldGFpbHMudG90YWxkdXJhdGlvbiA9IG5ld0RldGFpbHMuZWRnZSAtIG5ld0ZyYWdtZW50c1swXS5zdGFydDtcbiAgfVxuICBuZXdEZXRhaWxzLmRyaWZ0U3RhcnRUaW1lID0gb2xkRGV0YWlscy5kcmlmdFN0YXJ0VGltZTtcbiAgbmV3RGV0YWlscy5kcmlmdFN0YXJ0ID0gb2xkRGV0YWlscy5kcmlmdFN0YXJ0O1xuICBjb25zdCBhZHZhbmNlZERhdGVUaW1lID0gbmV3RGV0YWlscy5hZHZhbmNlZERhdGVUaW1lO1xuICBpZiAobmV3RGV0YWlscy5hZHZhbmNlZCAmJiBhZHZhbmNlZERhdGVUaW1lKSB7XG4gICAgY29uc3QgZWRnZSA9IG5ld0RldGFpbHMuZWRnZTtcbiAgICBpZiAoIW5ld0RldGFpbHMuZHJpZnRTdGFydCkge1xuICAgICAgbmV3RGV0YWlscy5kcmlmdFN0YXJ0VGltZSA9IGFkdmFuY2VkRGF0ZVRpbWU7XG4gICAgICBuZXdEZXRhaWxzLmRyaWZ0U3RhcnQgPSBlZGdlO1xuICAgIH1cbiAgICBuZXdEZXRhaWxzLmRyaWZ0RW5kVGltZSA9IGFkdmFuY2VkRGF0ZVRpbWU7XG4gICAgbmV3RGV0YWlscy5kcmlmdEVuZCA9IGVkZ2U7XG4gIH0gZWxzZSB7XG4gICAgbmV3RGV0YWlscy5kcmlmdEVuZFRpbWUgPSBvbGREZXRhaWxzLmRyaWZ0RW5kVGltZTtcbiAgICBuZXdEZXRhaWxzLmRyaWZ0RW5kID0gb2xkRGV0YWlscy5kcmlmdEVuZDtcbiAgICBuZXdEZXRhaWxzLmFkdmFuY2VkRGF0ZVRpbWUgPSBvbGREZXRhaWxzLmFkdmFuY2VkRGF0ZVRpbWU7XG4gIH1cbn1cbmZ1bmN0aW9uIG1lcmdlRGF0ZVJhbmdlcyhvbGREYXRlUmFuZ2VzLCBkZWx0YURhdGVSYW5nZXMsIHJlY2VudGx5UmVtb3ZlZERhdGVyYW5nZXMpIHtcbiAgY29uc3QgZGF0ZVJhbmdlcyA9IF9leHRlbmRzKHt9LCBvbGREYXRlUmFuZ2VzKTtcbiAgaWYgKHJlY2VudGx5UmVtb3ZlZERhdGVyYW5nZXMpIHtcbiAgICByZWNlbnRseVJlbW92ZWREYXRlcmFuZ2VzLmZvckVhY2goaWQgPT4ge1xuICAgICAgZGVsZXRlIGRhdGVSYW5nZXNbaWRdO1xuICAgIH0pO1xuICB9XG4gIE9iamVjdC5rZXlzKGRlbHRhRGF0ZVJhbmdlcykuZm9yRWFjaChpZCA9PiB7XG4gICAgY29uc3QgZGF0ZVJhbmdlID0gbmV3IERhdGVSYW5nZShkZWx0YURhdGVSYW5nZXNbaWRdLmF0dHIsIGRhdGVSYW5nZXNbaWRdKTtcbiAgICBpZiAoZGF0ZVJhbmdlLmlzVmFsaWQpIHtcbiAgICAgIGRhdGVSYW5nZXNbaWRdID0gZGF0ZVJhbmdlO1xuICAgIH0gZWxzZSB7XG4gICAgICBsb2dnZXIud2FybihgSWdub3JpbmcgaW52YWxpZCBQbGF5bGlzdCBEZWx0YSBVcGRhdGUgREFURVJBTkdFIHRhZzogXCIke0pTT04uc3RyaW5naWZ5KGRlbHRhRGF0ZVJhbmdlc1tpZF0uYXR0cil9XCJgKTtcbiAgICB9XG4gIH0pO1xuICByZXR1cm4gZGF0ZVJhbmdlcztcbn1cbmZ1bmN0aW9uIG1hcFBhcnRJbnRlcnNlY3Rpb24ob2xkUGFydHMsIG5ld1BhcnRzLCBpbnRlcnNlY3Rpb25Gbikge1xuICBpZiAob2xkUGFydHMgJiYgbmV3UGFydHMpIHtcbiAgICBsZXQgZGVsdGEgPSAwO1xuICAgIGZvciAobGV0IGkgPSAwLCBsZW4gPSBvbGRQYXJ0cy5sZW5ndGg7IGkgPD0gbGVuOyBpKyspIHtcbiAgICAgIGNvbnN0IG9sZFBhcnQgPSBvbGRQYXJ0c1tpXTtcbiAgICAgIGNvbnN0IG5ld1BhcnQgPSBuZXdQYXJ0c1tpICsgZGVsdGFdO1xuICAgICAgaWYgKG9sZFBhcnQgJiYgbmV3UGFydCAmJiBvbGRQYXJ0LmluZGV4ID09PSBuZXdQYXJ0LmluZGV4ICYmIG9sZFBhcnQuZnJhZ21lbnQuc24gPT09IG5ld1BhcnQuZnJhZ21lbnQuc24pIHtcbiAgICAgICAgaW50ZXJzZWN0aW9uRm4ob2xkUGFydCwgbmV3UGFydCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkZWx0YS0tO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuZnVuY3Rpb24gbWFwRnJhZ21lbnRJbnRlcnNlY3Rpb24ob2xkRGV0YWlscywgbmV3RGV0YWlscywgaW50ZXJzZWN0aW9uRm4pIHtcbiAgY29uc3Qgc2tpcHBlZFNlZ21lbnRzID0gbmV3RGV0YWlscy5za2lwcGVkU2VnbWVudHM7XG4gIGNvbnN0IHN0YXJ0ID0gTWF0aC5tYXgob2xkRGV0YWlscy5zdGFydFNOLCBuZXdEZXRhaWxzLnN0YXJ0U04pIC0gbmV3RGV0YWlscy5zdGFydFNOO1xuICBjb25zdCBlbmQgPSAob2xkRGV0YWlscy5mcmFnbWVudEhpbnQgPyAxIDogMCkgKyAoc2tpcHBlZFNlZ21lbnRzID8gbmV3RGV0YWlscy5lbmRTTiA6IE1hdGgubWluKG9sZERldGFpbHMuZW5kU04sIG5ld0RldGFpbHMuZW5kU04pKSAtIG5ld0RldGFpbHMuc3RhcnRTTjtcbiAgY29uc3QgZGVsdGEgPSBuZXdEZXRhaWxzLnN0YXJ0U04gLSBvbGREZXRhaWxzLnN0YXJ0U047XG4gIGNvbnN0IG5ld0ZyYWdzID0gbmV3RGV0YWlscy5mcmFnbWVudEhpbnQgPyBuZXdEZXRhaWxzLmZyYWdtZW50cy5jb25jYXQobmV3RGV0YWlscy5mcmFnbWVudEhpbnQpIDogbmV3RGV0YWlscy5mcmFnbWVudHM7XG4gIGNvbnN0IG9sZEZyYWdzID0gb2xkRGV0YWlscy5mcmFnbWVudEhpbnQgPyBvbGREZXRhaWxzLmZyYWdtZW50cy5jb25jYXQob2xkRGV0YWlscy5mcmFnbWVudEhpbnQpIDogb2xkRGV0YWlscy5mcmFnbWVudHM7XG4gIGZvciAobGV0IGkgPSBzdGFydDsgaSA8PSBlbmQ7IGkrKykge1xuICAgIGNvbnN0IG9sZEZyYWcgPSBvbGRGcmFnc1tkZWx0YSArIGldO1xuICAgIGxldCBuZXdGcmFnID0gbmV3RnJhZ3NbaV07XG4gICAgaWYgKHNraXBwZWRTZWdtZW50cyAmJiAhbmV3RnJhZyAmJiBpIDwgc2tpcHBlZFNlZ21lbnRzKSB7XG4gICAgICAvLyBGaWxsIGluIHNraXBwZWQgc2VnbWVudHMgaW4gZGVsdGEgcGxheWxpc3RcbiAgICAgIG5ld0ZyYWcgPSBuZXdEZXRhaWxzLmZyYWdtZW50c1tpXSA9IG9sZEZyYWc7XG4gICAgfVxuICAgIGlmIChvbGRGcmFnICYmIG5ld0ZyYWcpIHtcbiAgICAgIGludGVyc2VjdGlvbkZuKG9sZEZyYWcsIG5ld0ZyYWcpO1xuICAgIH1cbiAgfVxufVxuZnVuY3Rpb24gYWRqdXN0U2xpZGluZyhvbGREZXRhaWxzLCBuZXdEZXRhaWxzKSB7XG4gIGNvbnN0IGRlbHRhID0gbmV3RGV0YWlscy5zdGFydFNOICsgbmV3RGV0YWlscy5za2lwcGVkU2VnbWVudHMgLSBvbGREZXRhaWxzLnN0YXJ0U047XG4gIGNvbnN0IG9sZEZyYWdtZW50cyA9IG9sZERldGFpbHMuZnJhZ21lbnRzO1xuICBpZiAoZGVsdGEgPCAwIHx8IGRlbHRhID49IG9sZEZyYWdtZW50cy5sZW5ndGgpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgYWRkU2xpZGluZyhuZXdEZXRhaWxzLCBvbGRGcmFnbWVudHNbZGVsdGFdLnN0YXJ0KTtcbn1cbmZ1bmN0aW9uIGFkZFNsaWRpbmcoZGV0YWlscywgc3RhcnQpIHtcbiAgaWYgKHN0YXJ0KSB7XG4gICAgY29uc3QgZnJhZ21lbnRzID0gZGV0YWlscy5mcmFnbWVudHM7XG4gICAgZm9yIChsZXQgaSA9IGRldGFpbHMuc2tpcHBlZFNlZ21lbnRzOyBpIDwgZnJhZ21lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBmcmFnbWVudHNbaV0uc3RhcnQgKz0gc3RhcnQ7XG4gICAgfVxuICAgIGlmIChkZXRhaWxzLmZyYWdtZW50SGludCkge1xuICAgICAgZGV0YWlscy5mcmFnbWVudEhpbnQuc3RhcnQgKz0gc3RhcnQ7XG4gICAgfVxuICB9XG59XG5mdW5jdGlvbiBjb21wdXRlUmVsb2FkSW50ZXJ2YWwobmV3RGV0YWlscywgZGlzdGFuY2VUb0xpdmVFZGdlTXMgPSBJbmZpbml0eSkge1xuICBsZXQgcmVsb2FkSW50ZXJ2YWwgPSAxMDAwICogbmV3RGV0YWlscy50YXJnZXRkdXJhdGlvbjtcbiAgaWYgKG5ld0RldGFpbHMudXBkYXRlZCkge1xuICAgIC8vIFVzZSBsYXN0IHNlZ21lbnQgZHVyYXRpb24gd2hlbiBzaG9ydGVyIHRoYW4gdGFyZ2V0IGR1cmF0aW9uIGFuZCBuZWFyIGxpdmUgZWRnZVxuICAgIGNvbnN0IGZyYWdtZW50cyA9IG5ld0RldGFpbHMuZnJhZ21lbnRzO1xuICAgIGNvbnN0IGxpdmVFZGdlTWF4VGFyZ2V0RHVyYXRpb25zID0gNDtcbiAgICBpZiAoZnJhZ21lbnRzLmxlbmd0aCAmJiByZWxvYWRJbnRlcnZhbCAqIGxpdmVFZGdlTWF4VGFyZ2V0RHVyYXRpb25zID4gZGlzdGFuY2VUb0xpdmVFZGdlTXMpIHtcbiAgICAgIGNvbnN0IGxhc3RTZWdtZW50RHVyYXRpb24gPSBmcmFnbWVudHNbZnJhZ21lbnRzLmxlbmd0aCAtIDFdLmR1cmF0aW9uICogMTAwMDtcbiAgICAgIGlmIChsYXN0U2VnbWVudER1cmF0aW9uIDwgcmVsb2FkSW50ZXJ2YWwpIHtcbiAgICAgICAgcmVsb2FkSW50ZXJ2YWwgPSBsYXN0U2VnbWVudER1cmF0aW9uO1xuICAgICAgfVxuICAgIH1cbiAgfSBlbHNlIHtcbiAgICAvLyBlc3RpbWF0ZSA9ICdtaXNzIGhhbGYgYXZlcmFnZSc7XG4gICAgLy8gZm9sbG93IEhMUyBTcGVjLCBJZiB0aGUgY2xpZW50IHJlbG9hZHMgYSBQbGF5bGlzdCBmaWxlIGFuZCBmaW5kcyB0aGF0IGl0IGhhcyBub3RcbiAgICAvLyBjaGFuZ2VkIHRoZW4gaXQgTVVTVCB3YWl0IGZvciBhIHBlcmlvZCBvZiBvbmUtaGFsZiB0aGUgdGFyZ2V0XG4gICAgLy8gZHVyYXRpb24gYmVmb3JlIHJldHJ5aW5nLlxuICAgIHJlbG9hZEludGVydmFsIC89IDI7XG4gIH1cbiAgcmV0dXJuIE1hdGgucm91bmQocmVsb2FkSW50ZXJ2YWwpO1xufVxuZnVuY3Rpb24gZ2V0RnJhZ21lbnRXaXRoU04obGV2ZWwsIHNuLCBmcmFnQ3VycmVudCkge1xuICBpZiAoIShsZXZlbCAhPSBudWxsICYmIGxldmVsLmRldGFpbHMpKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3QgbGV2ZWxEZXRhaWxzID0gbGV2ZWwuZGV0YWlscztcbiAgbGV0IGZyYWdtZW50ID0gbGV2ZWxEZXRhaWxzLmZyYWdtZW50c1tzbiAtIGxldmVsRGV0YWlscy5zdGFydFNOXTtcbiAgaWYgKGZyYWdtZW50KSB7XG4gICAgcmV0dXJuIGZyYWdtZW50O1xuICB9XG4gIGZyYWdtZW50ID0gbGV2ZWxEZXRhaWxzLmZyYWdtZW50SGludDtcbiAgaWYgKGZyYWdtZW50ICYmIGZyYWdtZW50LnNuID09PSBzbikge1xuICAgIHJldHVybiBmcmFnbWVudDtcbiAgfVxuICBpZiAoc24gPCBsZXZlbERldGFpbHMuc3RhcnRTTiAmJiBmcmFnQ3VycmVudCAmJiBmcmFnQ3VycmVudC5zbiA9PT0gc24pIHtcbiAgICByZXR1cm4gZnJhZ0N1cnJlbnQ7XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5mdW5jdGlvbiBnZXRQYXJ0V2l0aChsZXZlbCwgc24sIHBhcnRJbmRleCkge1xuICB2YXIgX2xldmVsJGRldGFpbHM7XG4gIGlmICghKGxldmVsICE9IG51bGwgJiYgbGV2ZWwuZGV0YWlscykpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICByZXR1cm4gZmluZFBhcnQoKF9sZXZlbCRkZXRhaWxzID0gbGV2ZWwuZGV0YWlscykgPT0gbnVsbCA/IHZvaWQgMCA6IF9sZXZlbCRkZXRhaWxzLnBhcnRMaXN0LCBzbiwgcGFydEluZGV4KTtcbn1cbmZ1bmN0aW9uIGZpbmRQYXJ0KHBhcnRMaXN0LCBzbiwgcGFydEluZGV4KSB7XG4gIGlmIChwYXJ0TGlzdCkge1xuICAgIGZvciAobGV0IGkgPSBwYXJ0TGlzdC5sZW5ndGg7IGktLTspIHtcbiAgICAgIGNvbnN0IHBhcnQgPSBwYXJ0TGlzdFtpXTtcbiAgICAgIGlmIChwYXJ0LmluZGV4ID09PSBwYXJ0SW5kZXggJiYgcGFydC5mcmFnbWVudC5zbiA9PT0gc24pIHtcbiAgICAgICAgcmV0dXJuIHBhcnQ7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBudWxsO1xufVxuZnVuY3Rpb24gcmVhc3NpZ25GcmFnbWVudExldmVsSW5kZXhlcyhsZXZlbHMpIHtcbiAgbGV2ZWxzLmZvckVhY2goKGxldmVsLCBpbmRleCkgPT4ge1xuICAgIGNvbnN0IHtcbiAgICAgIGRldGFpbHNcbiAgICB9ID0gbGV2ZWw7XG4gICAgaWYgKGRldGFpbHMgIT0gbnVsbCAmJiBkZXRhaWxzLmZyYWdtZW50cykge1xuICAgICAgZGV0YWlscy5mcmFnbWVudHMuZm9yRWFjaChmcmFnbWVudCA9PiB7XG4gICAgICAgIGZyYWdtZW50LmxldmVsID0gaW5kZXg7XG4gICAgICB9KTtcbiAgICB9XG4gIH0pO1xufVxuXG5mdW5jdGlvbiBpc1RpbWVvdXRFcnJvcihlcnJvcikge1xuICBzd2l0Y2ggKGVycm9yLmRldGFpbHMpIHtcbiAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX0xPQURfVElNRU9VVDpcbiAgICBjYXNlIEVycm9yRGV0YWlscy5LRVlfTE9BRF9USU1FT1VUOlxuICAgIGNhc2UgRXJyb3JEZXRhaWxzLkxFVkVMX0xPQURfVElNRU9VVDpcbiAgICBjYXNlIEVycm9yRGV0YWlscy5NQU5JRkVTVF9MT0FEX1RJTUVPVVQ6XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG5mdW5jdGlvbiBnZXRSZXRyeUNvbmZpZyhsb2FkUG9saWN5LCBlcnJvcikge1xuICBjb25zdCBpc1RpbWVvdXQgPSBpc1RpbWVvdXRFcnJvcihlcnJvcik7XG4gIHJldHVybiBsb2FkUG9saWN5LmRlZmF1bHRbYCR7aXNUaW1lb3V0ID8gJ3RpbWVvdXQnIDogJ2Vycm9yJ31SZXRyeWBdO1xufVxuZnVuY3Rpb24gZ2V0UmV0cnlEZWxheShyZXRyeUNvbmZpZywgcmV0cnlDb3VudCkge1xuICAvLyBleHBvbmVudGlhbCBiYWNrb2ZmIGNhcHBlZCB0byBtYXggcmV0cnkgZGVsYXlcbiAgY29uc3QgYmFja29mZkZhY3RvciA9IHJldHJ5Q29uZmlnLmJhY2tvZmYgPT09ICdsaW5lYXInID8gMSA6IE1hdGgucG93KDIsIHJldHJ5Q291bnQpO1xuICByZXR1cm4gTWF0aC5taW4oYmFja29mZkZhY3RvciAqIHJldHJ5Q29uZmlnLnJldHJ5RGVsYXlNcywgcmV0cnlDb25maWcubWF4UmV0cnlEZWxheU1zKTtcbn1cbmZ1bmN0aW9uIGdldExvYWRlckNvbmZpZ1dpdGhvdXRSZXRpZXMobG9kZXJDb25maWcpIHtcbiAgcmV0dXJuIF9vYmplY3RTcHJlYWQyKF9vYmplY3RTcHJlYWQyKHt9LCBsb2RlckNvbmZpZyksIHtcbiAgICBlcnJvclJldHJ5OiBudWxsLFxuICAgIHRpbWVvdXRSZXRyeTogbnVsbFxuICB9KTtcbn1cbmZ1bmN0aW9uIHNob3VsZFJldHJ5KHJldHJ5Q29uZmlnLCByZXRyeUNvdW50LCBpc1RpbWVvdXQsIGxvYWRlclJlc3BvbnNlKSB7XG4gIGlmICghcmV0cnlDb25maWcpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgY29uc3QgaHR0cFN0YXR1cyA9IGxvYWRlclJlc3BvbnNlID09IG51bGwgPyB2b2lkIDAgOiBsb2FkZXJSZXNwb25zZS5jb2RlO1xuICBjb25zdCByZXRyeSA9IHJldHJ5Q291bnQgPCByZXRyeUNvbmZpZy5tYXhOdW1SZXRyeSAmJiAocmV0cnlGb3JIdHRwU3RhdHVzKGh0dHBTdGF0dXMpIHx8ICEhaXNUaW1lb3V0KTtcbiAgcmV0dXJuIHJldHJ5Q29uZmlnLnNob3VsZFJldHJ5ID8gcmV0cnlDb25maWcuc2hvdWxkUmV0cnkocmV0cnlDb25maWcsIHJldHJ5Q291bnQsIGlzVGltZW91dCwgbG9hZGVyUmVzcG9uc2UsIHJldHJ5KSA6IHJldHJ5O1xufVxuZnVuY3Rpb24gcmV0cnlGb3JIdHRwU3RhdHVzKGh0dHBTdGF0dXMpIHtcbiAgLy8gRG8gbm90IHJldHJ5IG9uIHN0YXR1cyA0eHgsIHN0YXR1cyAwIChDT1JTIGVycm9yKSwgb3IgdW5kZWZpbmVkIChkZWNyeXB0L2dhcC9wYXJzZSBlcnJvcilcbiAgcmV0dXJuIGh0dHBTdGF0dXMgPT09IDAgJiYgbmF2aWdhdG9yLm9uTGluZSA9PT0gZmFsc2UgfHwgISFodHRwU3RhdHVzICYmIChodHRwU3RhdHVzIDwgNDAwIHx8IGh0dHBTdGF0dXMgPiA0OTkpO1xufVxuXG5jb25zdCBCaW5hcnlTZWFyY2ggPSB7XG4gIC8qKlxuICAgKiBTZWFyY2hlcyBmb3IgYW4gaXRlbSBpbiBhbiBhcnJheSB3aGljaCBtYXRjaGVzIGEgY2VydGFpbiBjb25kaXRpb24uXG4gICAqIFRoaXMgcmVxdWlyZXMgdGhlIGNvbmRpdGlvbiB0byBvbmx5IG1hdGNoIG9uZSBpdGVtIGluIHRoZSBhcnJheSxcbiAgICogYW5kIGZvciB0aGUgYXJyYXkgdG8gYmUgb3JkZXJlZC5cbiAgICpcbiAgICogQHBhcmFtIGxpc3QgVGhlIGFycmF5IHRvIHNlYXJjaC5cbiAgICogQHBhcmFtIGNvbXBhcmlzb25GblxuICAgKiAgICAgIENhbGxlZCBhbmQgcHJvdmlkZWQgYSBjYW5kaWRhdGUgaXRlbSBhcyB0aGUgZmlyc3QgYXJndW1lbnQuXG4gICAqICAgICAgU2hvdWxkIHJldHVybjpcbiAgICogICAgICAgICAgPiAtMSBpZiB0aGUgaXRlbSBzaG91bGQgYmUgbG9jYXRlZCBhdCBhIGxvd2VyIGluZGV4IHRoYW4gdGhlIHByb3ZpZGVkIGl0ZW0uXG4gICAqICAgICAgICAgID4gMSBpZiB0aGUgaXRlbSBzaG91bGQgYmUgbG9jYXRlZCBhdCBhIGhpZ2hlciBpbmRleCB0aGFuIHRoZSBwcm92aWRlZCBpdGVtLlxuICAgKiAgICAgICAgICA+IDAgaWYgdGhlIGl0ZW0gaXMgdGhlIGl0ZW0geW91J3JlIGxvb2tpbmcgZm9yLlxuICAgKlxuICAgKiBAcmV0dXJucyB0aGUgb2JqZWN0IGlmIGZvdW5kLCBvdGhlcndpc2UgcmV0dXJucyBudWxsXG4gICAqL1xuICBzZWFyY2g6IGZ1bmN0aW9uIChsaXN0LCBjb21wYXJpc29uRm4pIHtcbiAgICBsZXQgbWluSW5kZXggPSAwO1xuICAgIGxldCBtYXhJbmRleCA9IGxpc3QubGVuZ3RoIC0gMTtcbiAgICBsZXQgY3VycmVudEluZGV4ID0gbnVsbDtcbiAgICBsZXQgY3VycmVudEVsZW1lbnQgPSBudWxsO1xuICAgIHdoaWxlIChtaW5JbmRleCA8PSBtYXhJbmRleCkge1xuICAgICAgY3VycmVudEluZGV4ID0gKG1pbkluZGV4ICsgbWF4SW5kZXgpIC8gMiB8IDA7XG4gICAgICBjdXJyZW50RWxlbWVudCA9IGxpc3RbY3VycmVudEluZGV4XTtcbiAgICAgIGNvbnN0IGNvbXBhcmlzb25SZXN1bHQgPSBjb21wYXJpc29uRm4oY3VycmVudEVsZW1lbnQpO1xuICAgICAgaWYgKGNvbXBhcmlzb25SZXN1bHQgPiAwKSB7XG4gICAgICAgIG1pbkluZGV4ID0gY3VycmVudEluZGV4ICsgMTtcbiAgICAgIH0gZWxzZSBpZiAoY29tcGFyaXNvblJlc3VsdCA8IDApIHtcbiAgICAgICAgbWF4SW5kZXggPSBjdXJyZW50SW5kZXggLSAxO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGN1cnJlbnRFbGVtZW50O1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufTtcblxuLyoqXG4gKiBSZXR1cm5zIGZpcnN0IGZyYWdtZW50IHdob3NlIGVuZFBkdCB2YWx1ZSBleGNlZWRzIHRoZSBnaXZlbiBQRFQsIG9yIG51bGwuXG4gKiBAcGFyYW0gZnJhZ21lbnRzIC0gVGhlIGFycmF5IG9mIGNhbmRpZGF0ZSBmcmFnbWVudHNcbiAqIEBwYXJhbSBQRFRWYWx1ZSAtIFRoZSBQRFQgdmFsdWUgd2hpY2ggbXVzdCBiZSBleGNlZWRlZFxuICogQHBhcmFtIG1heEZyYWdMb29rVXBUb2xlcmFuY2UgLSBUaGUgYW1vdW50IG9mIHRpbWUgdGhhdCBhIGZyYWdtZW50J3Mgc3RhcnQvZW5kIGNhbiBiZSB3aXRoaW4gaW4gb3JkZXIgdG8gYmUgY29uc2lkZXJlZCBjb250aWd1b3VzXG4gKi9cbmZ1bmN0aW9uIGZpbmRGcmFnbWVudEJ5UERUKGZyYWdtZW50cywgUERUVmFsdWUsIG1heEZyYWdMb29rVXBUb2xlcmFuY2UpIHtcbiAgaWYgKFBEVFZhbHVlID09PSBudWxsIHx8ICFBcnJheS5pc0FycmF5KGZyYWdtZW50cykgfHwgIWZyYWdtZW50cy5sZW5ndGggfHwgIWlzRmluaXRlTnVtYmVyKFBEVFZhbHVlKSkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gaWYgbGVzcyB0aGFuIHN0YXJ0XG4gIGNvbnN0IHN0YXJ0UERUID0gZnJhZ21lbnRzWzBdLnByb2dyYW1EYXRlVGltZTtcbiAgaWYgKFBEVFZhbHVlIDwgKHN0YXJ0UERUIHx8IDApKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3QgZW5kUERUID0gZnJhZ21lbnRzW2ZyYWdtZW50cy5sZW5ndGggLSAxXS5lbmRQcm9ncmFtRGF0ZVRpbWU7XG4gIGlmIChQRFRWYWx1ZSA+PSAoZW5kUERUIHx8IDApKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSA9IG1heEZyYWdMb29rVXBUb2xlcmFuY2UgfHwgMDtcbiAgZm9yIChsZXQgc2VnID0gMDsgc2VnIDwgZnJhZ21lbnRzLmxlbmd0aDsgKytzZWcpIHtcbiAgICBjb25zdCBmcmFnID0gZnJhZ21lbnRzW3NlZ107XG4gICAgaWYgKHBkdFdpdGhpblRvbGVyYW5jZVRlc3QoUERUVmFsdWUsIG1heEZyYWdMb29rVXBUb2xlcmFuY2UsIGZyYWcpKSB7XG4gICAgICByZXR1cm4gZnJhZztcbiAgICB9XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5cbi8qKlxuICogRmluZHMgYSBmcmFnbWVudCBiYXNlZCBvbiB0aGUgU04gb2YgdGhlIHByZXZpb3VzIGZyYWdtZW50OyBvciBiYXNlZCBvbiB0aGUgbmVlZHMgb2YgdGhlIGN1cnJlbnQgYnVmZmVyLlxuICogVGhpcyBtZXRob2QgY29tcGVuc2F0ZXMgZm9yIHNtYWxsIGJ1ZmZlciBnYXBzIGJ5IGFwcGx5aW5nIGEgdG9sZXJhbmNlIHRvIHRoZSBzdGFydCBvZiBhbnkgY2FuZGlkYXRlIGZyYWdtZW50LCB0aHVzXG4gKiBicmVha2luZyBhbnkgdHJhcHMgd2hpY2ggd291bGQgY2F1c2UgdGhlIHNhbWUgZnJhZ21lbnQgdG8gYmUgY29udGludW91c2x5IHNlbGVjdGVkIHdpdGhpbiBhIHNtYWxsIHJhbmdlLlxuICogQHBhcmFtIGZyYWdQcmV2aW91cyAtIFRoZSBsYXN0IGZyYWcgc3VjY2Vzc2Z1bGx5IGFwcGVuZGVkXG4gKiBAcGFyYW0gZnJhZ21lbnRzIC0gVGhlIGFycmF5IG9mIGNhbmRpZGF0ZSBmcmFnbWVudHNcbiAqIEBwYXJhbSBidWZmZXJFbmQgLSBUaGUgZW5kIG9mIHRoZSBjb250aWd1b3VzIGJ1ZmZlcmVkIHJhbmdlIHRoZSBwbGF5aGVhZCBpcyBjdXJyZW50bHkgd2l0aGluXG4gKiBAcGFyYW0gbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSAtIFRoZSBhbW91bnQgb2YgdGltZSB0aGF0IGEgZnJhZ21lbnQncyBzdGFydC9lbmQgY2FuIGJlIHdpdGhpbiBpbiBvcmRlciB0byBiZSBjb25zaWRlcmVkIGNvbnRpZ3VvdXNcbiAqIEByZXR1cm5zIGEgbWF0Y2hpbmcgZnJhZ21lbnQgb3IgbnVsbFxuICovXG5mdW5jdGlvbiBmaW5kRnJhZ21lbnRCeVBUUyhmcmFnUHJldmlvdXMsIGZyYWdtZW50cywgYnVmZmVyRW5kID0gMCwgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSA9IDAsIG5leHRGcmFnTG9va3VwVG9sZXJhbmNlID0gMC4wMDUpIHtcbiAgbGV0IGZyYWdOZXh0ID0gbnVsbDtcbiAgaWYgKGZyYWdQcmV2aW91cykge1xuICAgIGZyYWdOZXh0ID0gZnJhZ21lbnRzW2ZyYWdQcmV2aW91cy5zbiAtIGZyYWdtZW50c1swXS5zbiArIDFdIHx8IG51bGw7XG4gICAgLy8gY2hlY2sgZm9yIGJ1ZmZlci1lbmQgcm91bmRpbmcgZXJyb3JcbiAgICBjb25zdCBidWZmZXJFZGdlRXJyb3IgPSBmcmFnUHJldmlvdXMuZW5kRFRTIC0gYnVmZmVyRW5kO1xuICAgIGlmIChidWZmZXJFZGdlRXJyb3IgPiAwICYmIGJ1ZmZlckVkZ2VFcnJvciA8IDAuMDAwMDAxNSkge1xuICAgICAgYnVmZmVyRW5kICs9IDAuMDAwMDAxNTtcbiAgICB9XG4gIH0gZWxzZSBpZiAoYnVmZmVyRW5kID09PSAwICYmIGZyYWdtZW50c1swXS5zdGFydCA9PT0gMCkge1xuICAgIGZyYWdOZXh0ID0gZnJhZ21lbnRzWzBdO1xuICB9XG4gIC8vIFByZWZlciB0aGUgbmV4dCBmcmFnbWVudCBpZiBpdCdzIHdpdGhpbiB0b2xlcmFuY2VcbiAgaWYgKGZyYWdOZXh0ICYmICgoIWZyYWdQcmV2aW91cyB8fCBmcmFnUHJldmlvdXMubGV2ZWwgPT09IGZyYWdOZXh0LmxldmVsKSAmJiBmcmFnbWVudFdpdGhpblRvbGVyYW5jZVRlc3QoYnVmZmVyRW5kLCBtYXhGcmFnTG9va1VwVG9sZXJhbmNlLCBmcmFnTmV4dCkgPT09IDAgfHwgZnJhZ21lbnRXaXRoaW5GYXN0U3RhcnRTd2l0Y2goZnJhZ05leHQsIGZyYWdQcmV2aW91cywgTWF0aC5taW4obmV4dEZyYWdMb29rdXBUb2xlcmFuY2UsIG1heEZyYWdMb29rVXBUb2xlcmFuY2UpKSkpIHtcbiAgICByZXR1cm4gZnJhZ05leHQ7XG4gIH1cbiAgLy8gV2UgbWlnaHQgYmUgc2Vla2luZyBwYXN0IHRoZSB0b2xlcmFuY2Ugc28gZmluZCB0aGUgYmVzdCBtYXRjaFxuICBjb25zdCBmb3VuZEZyYWdtZW50ID0gQmluYXJ5U2VhcmNoLnNlYXJjaChmcmFnbWVudHMsIGZyYWdtZW50V2l0aGluVG9sZXJhbmNlVGVzdC5iaW5kKG51bGwsIGJ1ZmZlckVuZCwgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSkpO1xuICBpZiAoZm91bmRGcmFnbWVudCAmJiAoZm91bmRGcmFnbWVudCAhPT0gZnJhZ1ByZXZpb3VzIHx8ICFmcmFnTmV4dCkpIHtcbiAgICByZXR1cm4gZm91bmRGcmFnbWVudDtcbiAgfVxuICAvLyBJZiBubyBtYXRjaCB3YXMgZm91bmQgcmV0dXJuIHRoZSBuZXh0IGZyYWdtZW50IGFmdGVyIGZyYWdQcmV2aW91cywgb3IgbnVsbFxuICByZXR1cm4gZnJhZ05leHQ7XG59XG5mdW5jdGlvbiBmcmFnbWVudFdpdGhpbkZhc3RTdGFydFN3aXRjaChmcmFnTmV4dCwgZnJhZ1ByZXZpb3VzLCBuZXh0RnJhZ0xvb2t1cFRvbGVyYW5jZSkge1xuICBpZiAoZnJhZ1ByZXZpb3VzICYmIGZyYWdQcmV2aW91cy5zdGFydCA9PT0gMCAmJiBmcmFnUHJldmlvdXMubGV2ZWwgPCBmcmFnTmV4dC5sZXZlbCAmJiAoZnJhZ1ByZXZpb3VzLmVuZFBUUyB8fCAwKSA+IDApIHtcbiAgICBjb25zdCBmaXJzdER1cmF0aW9uID0gZnJhZ1ByZXZpb3VzLnRhZ0xpc3QucmVkdWNlKChkdXJhdGlvbiwgdGFnKSA9PiB7XG4gICAgICBpZiAodGFnWzBdID09PSAnSU5GJykge1xuICAgICAgICBkdXJhdGlvbiArPSBwYXJzZUZsb2F0KHRhZ1sxXSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gZHVyYXRpb247XG4gICAgfSwgbmV4dEZyYWdMb29rdXBUb2xlcmFuY2UpO1xuICAgIHJldHVybiBmcmFnTmV4dC5zdGFydCA8PSBmaXJzdER1cmF0aW9uO1xuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cblxuLyoqXG4gKiBUaGUgdGVzdCBmdW5jdGlvbiB1c2VkIGJ5IHRoZSBmaW5kRnJhZ21lbnRCeVNuJ3MgQmluYXJ5U2VhcmNoIHRvIGxvb2sgZm9yIHRoZSBiZXN0IG1hdGNoIHRvIHRoZSBjdXJyZW50IGJ1ZmZlciBjb25kaXRpb25zLlxuICogQHBhcmFtIGNhbmRpZGF0ZSAtIFRoZSBmcmFnbWVudCB0byB0ZXN0XG4gKiBAcGFyYW0gYnVmZmVyRW5kIC0gVGhlIGVuZCBvZiB0aGUgY3VycmVudCBidWZmZXJlZCByYW5nZSB0aGUgcGxheWhlYWQgaXMgY3VycmVudGx5IHdpdGhpblxuICogQHBhcmFtIG1heEZyYWdMb29rVXBUb2xlcmFuY2UgLSBUaGUgYW1vdW50IG9mIHRpbWUgdGhhdCBhIGZyYWdtZW50J3Mgc3RhcnQgY2FuIGJlIHdpdGhpbiBpbiBvcmRlciB0byBiZSBjb25zaWRlcmVkIGNvbnRpZ3VvdXNcbiAqIEByZXR1cm5zIDAgaWYgaXQgbWF0Y2hlcywgMSBpZiB0b28gbG93LCAtMSBpZiB0b28gaGlnaFxuICovXG5mdW5jdGlvbiBmcmFnbWVudFdpdGhpblRvbGVyYW5jZVRlc3QoYnVmZmVyRW5kID0gMCwgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSA9IDAsIGNhbmRpZGF0ZSkge1xuICAvLyBlYWdlcmx5IGFjY2VwdCBhbiBhY2N1cmF0ZSBtYXRjaCAobm8gdG9sZXJhbmNlKVxuICBpZiAoY2FuZGlkYXRlLnN0YXJ0IDw9IGJ1ZmZlckVuZCAmJiBjYW5kaWRhdGUuc3RhcnQgKyBjYW5kaWRhdGUuZHVyYXRpb24gPiBidWZmZXJFbmQpIHtcbiAgICByZXR1cm4gMDtcbiAgfVxuICAvLyBvZmZzZXQgc2hvdWxkIGJlIHdpdGhpbiBmcmFnbWVudCBib3VuZGFyeSAtIGNvbmZpZy5tYXhGcmFnTG9va1VwVG9sZXJhbmNlXG4gIC8vIHRoaXMgaXMgdG8gY29wZSB3aXRoIHNpdHVhdGlvbnMgbGlrZVxuICAvLyBidWZmZXJFbmQgPSA5Ljk5MVxuICAvLyBmcmFnW8OYXSA6IFswLDEwXVxuICAvLyBmcmFnWzFdIDogWzEwLDIwXVxuICAvLyBidWZmZXJFbmQgaXMgd2l0aGluIGZyYWdbMF0gcmFuZ2UgLi4uIGFsdGhvdWdoIHdoYXQgd2UgYXJlIGV4cGVjdGluZyBpcyB0byByZXR1cm4gZnJhZ1sxXSBoZXJlXG4gIC8vICAgICAgICAgICAgICBmcmFnIHN0YXJ0ICAgICAgICAgICAgICAgZnJhZyBzdGFydCtkdXJhdGlvblxuICAvLyAgICAgICAgICAgICAgICAgIHwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXxcbiAgLy8gICAgICAgICAgICAgIDwtLS0+ICAgICAgICAgICAgICAgICAgICAgICAgIDwtLS0+XG4gIC8vICAuLi4tLS0tLS0tLT48LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0+PC0tLS0tLS0tLS4uLi5cbiAgLy8gcHJldmlvdXMgZnJhZyAgICAgICAgIG1hdGNoaW5nIGZyYWdtZW50ICAgICAgICAgbmV4dCBmcmFnXG4gIC8vICByZXR1cm4gLTEgICAgICAgICAgICAgcmV0dXJuIDAgICAgICAgICAgICAgICAgIHJldHVybiAxXG4gIC8vIGxvZ2dlci5sb2coYGxldmVsL3NuL3N0YXJ0L2VuZC9idWZFbmQ6JHtsZXZlbH0vJHtjYW5kaWRhdGUuc259LyR7Y2FuZGlkYXRlLnN0YXJ0fS8keyhjYW5kaWRhdGUuc3RhcnQrY2FuZGlkYXRlLmR1cmF0aW9uKX0vJHtidWZmZXJFbmR9YCk7XG4gIC8vIFNldCB0aGUgbG9va3VwIHRvbGVyYW5jZSB0byBiZSBzbWFsbCBlbm91Z2ggdG8gZGV0ZWN0IHRoZSBjdXJyZW50IHNlZ21lbnQgLSBlbnN1cmVzIHdlIGRvbid0IHNraXAgb3ZlciB2ZXJ5IHNtYWxsIHNlZ21lbnRzXG4gIGNvbnN0IGNhbmRpZGF0ZUxvb2t1cFRvbGVyYW5jZSA9IE1hdGgubWluKG1heEZyYWdMb29rVXBUb2xlcmFuY2UsIGNhbmRpZGF0ZS5kdXJhdGlvbiArIChjYW5kaWRhdGUuZGVsdGFQVFMgPyBjYW5kaWRhdGUuZGVsdGFQVFMgOiAwKSk7XG4gIGlmIChjYW5kaWRhdGUuc3RhcnQgKyBjYW5kaWRhdGUuZHVyYXRpb24gLSBjYW5kaWRhdGVMb29rdXBUb2xlcmFuY2UgPD0gYnVmZmVyRW5kKSB7XG4gICAgcmV0dXJuIDE7XG4gIH0gZWxzZSBpZiAoY2FuZGlkYXRlLnN0YXJ0IC0gY2FuZGlkYXRlTG9va3VwVG9sZXJhbmNlID4gYnVmZmVyRW5kICYmIGNhbmRpZGF0ZS5zdGFydCkge1xuICAgIC8vIGlmIG1heEZyYWdMb29rVXBUb2xlcmFuY2Ugd2lsbCBoYXZlIG5lZ2F0aXZlIHZhbHVlIHRoZW4gZG9uJ3QgcmV0dXJuIC0xIGZvciBmaXJzdCBlbGVtZW50XG4gICAgcmV0dXJuIC0xO1xuICB9XG4gIHJldHVybiAwO1xufVxuXG4vKipcbiAqIFRoZSB0ZXN0IGZ1bmN0aW9uIHVzZWQgYnkgdGhlIGZpbmRGcmFnbWVudEJ5UGR0J3MgQmluYXJ5U2VhcmNoIHRvIGxvb2sgZm9yIHRoZSBiZXN0IG1hdGNoIHRvIHRoZSBjdXJyZW50IGJ1ZmZlciBjb25kaXRpb25zLlxuICogVGhpcyBmdW5jdGlvbiB0ZXN0cyB0aGUgY2FuZGlkYXRlJ3MgcHJvZ3JhbSBkYXRlIHRpbWUgdmFsdWVzLCBhcyByZXByZXNlbnRlZCBpbiBVbml4IHRpbWVcbiAqIEBwYXJhbSBjYW5kaWRhdGUgLSBUaGUgZnJhZ21lbnQgdG8gdGVzdFxuICogQHBhcmFtIHBkdEJ1ZmZlckVuZCAtIFRoZSBVbml4IHRpbWUgcmVwcmVzZW50aW5nIHRoZSBlbmQgb2YgdGhlIGN1cnJlbnQgYnVmZmVyZWQgcmFuZ2VcbiAqIEBwYXJhbSBtYXhGcmFnTG9va1VwVG9sZXJhbmNlIC0gVGhlIGFtb3VudCBvZiB0aW1lIHRoYXQgYSBmcmFnbWVudCdzIHN0YXJ0IGNhbiBiZSB3aXRoaW4gaW4gb3JkZXIgdG8gYmUgY29uc2lkZXJlZCBjb250aWd1b3VzXG4gKiBAcmV0dXJucyB0cnVlIGlmIGNvbnRpZ3VvdXMsIGZhbHNlIG90aGVyd2lzZVxuICovXG5mdW5jdGlvbiBwZHRXaXRoaW5Ub2xlcmFuY2VUZXN0KHBkdEJ1ZmZlckVuZCwgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSwgY2FuZGlkYXRlKSB7XG4gIGNvbnN0IGNhbmRpZGF0ZUxvb2t1cFRvbGVyYW5jZSA9IE1hdGgubWluKG1heEZyYWdMb29rVXBUb2xlcmFuY2UsIGNhbmRpZGF0ZS5kdXJhdGlvbiArIChjYW5kaWRhdGUuZGVsdGFQVFMgPyBjYW5kaWRhdGUuZGVsdGFQVFMgOiAwKSkgKiAxMDAwO1xuXG4gIC8vIGVuZFByb2dyYW1EYXRlVGltZSBjYW4gYmUgbnVsbCwgZGVmYXVsdCB0byB6ZXJvXG4gIGNvbnN0IGVuZFByb2dyYW1EYXRlVGltZSA9IGNhbmRpZGF0ZS5lbmRQcm9ncmFtRGF0ZVRpbWUgfHwgMDtcbiAgcmV0dXJuIGVuZFByb2dyYW1EYXRlVGltZSAtIGNhbmRpZGF0ZUxvb2t1cFRvbGVyYW5jZSA+IHBkdEJ1ZmZlckVuZDtcbn1cbmZ1bmN0aW9uIGZpbmRGcmFnV2l0aENDKGZyYWdtZW50cywgY2MpIHtcbiAgcmV0dXJuIEJpbmFyeVNlYXJjaC5zZWFyY2goZnJhZ21lbnRzLCBjYW5kaWRhdGUgPT4ge1xuICAgIGlmIChjYW5kaWRhdGUuY2MgPCBjYykge1xuICAgICAgcmV0dXJuIDE7XG4gICAgfSBlbHNlIGlmIChjYW5kaWRhdGUuY2MgPiBjYykge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG4gIH0pO1xufVxuXG52YXIgTmV0d29ya0Vycm9yQWN0aW9uID0ge1xuICBEb05vdGhpbmc6IDAsXG4gIFNlbmRFbmRDYWxsYmFjazogMSxcbiAgU2VuZEFsdGVybmF0ZVRvUGVuYWx0eUJveDogMixcbiAgUmVtb3ZlQWx0ZXJuYXRlUGVybWFuZW50bHk6IDMsXG4gIEluc2VydERpc2NvbnRpbnVpdHk6IDQsXG4gIFJldHJ5UmVxdWVzdDogNVxufTtcbnZhciBFcnJvckFjdGlvbkZsYWdzID0ge1xuICBOb25lOiAwLFxuICBNb3ZlQWxsQWx0ZXJuYXRlc01hdGNoaW5nSG9zdDogMSxcbiAgTW92ZUFsbEFsdGVybmF0ZXNNYXRjaGluZ0hEQ1A6IDIsXG4gIFN3aXRjaFRvU0RSOiA0XG59OyAvLyBSZXNlcnZlZCBmb3IgZnV0dXJlIHVzZVxuY2xhc3MgRXJyb3JDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5wbGF5bGlzdEVycm9yID0gMDtcbiAgICB0aGlzLnBlbmFsaXplZFJlbmRpdGlvbnMgPSB7fTtcbiAgICB0aGlzLmxvZyA9IHZvaWQgMDtcbiAgICB0aGlzLndhcm4gPSB2b2lkIDA7XG4gICAgdGhpcy5lcnJvciA9IHZvaWQgMDtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgICB0aGlzLmxvZyA9IGxvZ2dlci5sb2cuYmluZChsb2dnZXIsIGBbaW5mb106YCk7XG4gICAgdGhpcy53YXJuID0gbG9nZ2VyLndhcm4uYmluZChsb2dnZXIsIGBbd2FybmluZ106YCk7XG4gICAgdGhpcy5lcnJvciA9IGxvZ2dlci5lcnJvci5iaW5kKGxvZ2dlciwgYFtlcnJvcl06YCk7XG4gICAgdGhpcy5yZWdpc3Rlckxpc3RlbmVycygpO1xuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGhscy5vbihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvciwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkxFVkVMX1VQREFURUQsIHRoaXMub25MZXZlbFVwZGF0ZWQsIHRoaXMpO1xuICB9XG4gIHVucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgaWYgKCFobHMpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaGxzLm9mZihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvciwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvck91dCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTEVWRUxfVVBEQVRFRCwgdGhpcy5vbkxldmVsVXBkYXRlZCwgdGhpcyk7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLnVucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdGhpcy5obHMgPSBudWxsO1xuICAgIHRoaXMucGVuYWxpemVkUmVuZGl0aW9ucyA9IHt9O1xuICB9XG4gIHN0YXJ0TG9hZChzdGFydFBvc2l0aW9uKSB7fVxuICBzdG9wTG9hZCgpIHtcbiAgICB0aGlzLnBsYXlsaXN0RXJyb3IgPSAwO1xuICB9XG4gIGdldFZhcmlhbnRMZXZlbEluZGV4KGZyYWcpIHtcbiAgICByZXR1cm4gKGZyYWcgPT0gbnVsbCA/IHZvaWQgMCA6IGZyYWcudHlwZSkgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4gPyBmcmFnLmxldmVsIDogdGhpcy5obHMubG9hZExldmVsO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKCkge1xuICAgIHRoaXMucGxheWxpc3RFcnJvciA9IDA7XG4gICAgdGhpcy5wZW5hbGl6ZWRSZW5kaXRpb25zID0ge307XG4gIH1cbiAgb25MZXZlbFVwZGF0ZWQoKSB7XG4gICAgdGhpcy5wbGF5bGlzdEVycm9yID0gMDtcbiAgfVxuICBvbkVycm9yKGV2ZW50LCBkYXRhKSB7XG4gICAgdmFyIF9kYXRhJGZyYWcsIF9kYXRhJGxldmVsO1xuICAgIGlmIChkYXRhLmZhdGFsKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IGNvbnRleHQgPSBkYXRhLmNvbnRleHQ7XG4gICAgc3dpdGNoIChkYXRhLmRldGFpbHMpIHtcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkZSQUdfTE9BRF9FUlJPUjpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkZSQUdfTE9BRF9USU1FT1VUOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuS0VZX0xPQURfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5LRVlfTE9BRF9USU1FT1VUOlxuICAgICAgICBkYXRhLmVycm9yQWN0aW9uID0gdGhpcy5nZXRGcmFnUmV0cnlPclN3aXRjaEFjdGlvbihkYXRhKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuRlJBR19QQVJTSU5HX0VSUk9SOlxuICAgICAgICAvLyBpZ25vcmUgZW1wdHkgc2VnbWVudCBlcnJvcnMgbWFya2VkIGFzIGdhcFxuICAgICAgICBpZiAoKF9kYXRhJGZyYWcgPSBkYXRhLmZyYWcpICE9IG51bGwgJiYgX2RhdGEkZnJhZy5nYXApIHtcbiAgICAgICAgICBkYXRhLmVycm9yQWN0aW9uID0ge1xuICAgICAgICAgICAgYWN0aW9uOiBOZXR3b3JrRXJyb3JBY3Rpb24uRG9Ob3RoaW5nLFxuICAgICAgICAgICAgZmxhZ3M6IEVycm9yQWN0aW9uRmxhZ3MuTm9uZVxuICAgICAgICAgIH07XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAvLyBmYWxscyB0aHJvdWdoXG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX0dBUDpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkZSQUdfREVDUllQVF9FUlJPUjpcbiAgICAgICAge1xuICAgICAgICAgIC8vIFN3aXRjaCBsZXZlbCBpZiBwb3NzaWJsZSwgb3RoZXJ3aXNlIGFsbG93IHJldHJ5IGNvdW50IHRvIHJlYWNoIG1heCBlcnJvciByZXRyaWVzXG4gICAgICAgICAgZGF0YS5lcnJvckFjdGlvbiA9IHRoaXMuZ2V0RnJhZ1JldHJ5T3JTd2l0Y2hBY3Rpb24oZGF0YSk7XG4gICAgICAgICAgZGF0YS5lcnJvckFjdGlvbi5hY3Rpb24gPSBOZXR3b3JrRXJyb3JBY3Rpb24uU2VuZEFsdGVybmF0ZVRvUGVuYWx0eUJveDtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkxFVkVMX0VNUFRZX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuTEVWRUxfUEFSU0lOR19FUlJPUjpcbiAgICAgICAge1xuICAgICAgICAgIHZhciBfZGF0YSRjb250ZXh0LCBfZGF0YSRjb250ZXh0JGxldmVsRGU7XG4gICAgICAgICAgLy8gT25seSByZXRyeSB3aGVuIGVtcHR5IGFuZCBsaXZlXG4gICAgICAgICAgY29uc3QgbGV2ZWxJbmRleCA9IGRhdGEucGFyZW50ID09PSBQbGF5bGlzdExldmVsVHlwZS5NQUlOID8gZGF0YS5sZXZlbCA6IGhscy5sb2FkTGV2ZWw7XG4gICAgICAgICAgaWYgKGRhdGEuZGV0YWlscyA9PT0gRXJyb3JEZXRhaWxzLkxFVkVMX0VNUFRZX0VSUk9SICYmICEhKChfZGF0YSRjb250ZXh0ID0gZGF0YS5jb250ZXh0KSAhPSBudWxsICYmIChfZGF0YSRjb250ZXh0JGxldmVsRGUgPSBfZGF0YSRjb250ZXh0LmxldmVsRGV0YWlscykgIT0gbnVsbCAmJiBfZGF0YSRjb250ZXh0JGxldmVsRGUubGl2ZSkpIHtcbiAgICAgICAgICAgIGRhdGEuZXJyb3JBY3Rpb24gPSB0aGlzLmdldFBsYXlsaXN0UmV0cnlPclN3aXRjaEFjdGlvbihkYXRhLCBsZXZlbEluZGV4KTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gRXNjYWxhdGUgdG8gZmF0YWwgaWYgbm90IHJldHJ5aW5nIG9yIHN3aXRjaGluZ1xuICAgICAgICAgICAgZGF0YS5sZXZlbFJldHJ5ID0gZmFsc2U7XG4gICAgICAgICAgICBkYXRhLmVycm9yQWN0aW9uID0gdGhpcy5nZXRMZXZlbFN3aXRjaEFjdGlvbihkYXRhLCBsZXZlbEluZGV4KTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuTEVWRUxfTE9BRF9FUlJPUjpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkxFVkVMX0xPQURfVElNRU9VVDpcbiAgICAgICAgaWYgKHR5cGVvZiAoY29udGV4dCA9PSBudWxsID8gdm9pZCAwIDogY29udGV4dC5sZXZlbCkgPT09ICdudW1iZXInKSB7XG4gICAgICAgICAgZGF0YS5lcnJvckFjdGlvbiA9IHRoaXMuZ2V0UGxheWxpc3RSZXRyeU9yU3dpdGNoQWN0aW9uKGRhdGEsIGNvbnRleHQubGV2ZWwpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybjtcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkFVRElPX1RSQUNLX0xPQURfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5BVURJT19UUkFDS19MT0FEX1RJTUVPVVQ6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5TVUJUSVRMRV9MT0FEX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuU1VCVElUTEVfVFJBQ0tfTE9BRF9USU1FT1VUOlxuICAgICAgICBpZiAoY29udGV4dCkge1xuICAgICAgICAgIGNvbnN0IGxldmVsID0gaGxzLmxldmVsc1tobHMubG9hZExldmVsXTtcbiAgICAgICAgICBpZiAobGV2ZWwgJiYgKGNvbnRleHQudHlwZSA9PT0gUGxheWxpc3RDb250ZXh0VHlwZS5BVURJT19UUkFDSyAmJiBsZXZlbC5oYXNBdWRpb0dyb3VwKGNvbnRleHQuZ3JvdXBJZCkgfHwgY29udGV4dC50eXBlID09PSBQbGF5bGlzdENvbnRleHRUeXBlLlNVQlRJVExFX1RSQUNLICYmIGxldmVsLmhhc1N1YnRpdGxlR3JvdXAoY29udGV4dC5ncm91cElkKSkpIHtcbiAgICAgICAgICAgIC8vIFBlcmZvcm0gUGF0aHdheSBzd2l0Y2ggb3IgUmVkdW5kYW50IGZhaWxvdmVyIGlmIHBvc3NpYmxlIGZvciBmYXN0ZXN0IHJlY292ZXJ5XG4gICAgICAgICAgICAvLyBvdGhlcndpc2UgYWxsb3cgcGxheWxpc3QgcmV0cnkgY291bnQgdG8gcmVhY2ggbWF4IGVycm9yIHJldHJpZXNcbiAgICAgICAgICAgIGRhdGEuZXJyb3JBY3Rpb24gPSB0aGlzLmdldFBsYXlsaXN0UmV0cnlPclN3aXRjaEFjdGlvbihkYXRhLCBobHMubG9hZExldmVsKTtcbiAgICAgICAgICAgIGRhdGEuZXJyb3JBY3Rpb24uYWN0aW9uID0gTmV0d29ya0Vycm9yQWN0aW9uLlNlbmRBbHRlcm5hdGVUb1BlbmFsdHlCb3g7XG4gICAgICAgICAgICBkYXRhLmVycm9yQWN0aW9uLmZsYWdzID0gRXJyb3JBY3Rpb25GbGFncy5Nb3ZlQWxsQWx0ZXJuYXRlc01hdGNoaW5nSG9zdDtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9TVEFUVVNfT1VUUFVUX1JFU1RSSUNURUQ6XG4gICAgICAgIHtcbiAgICAgICAgICBjb25zdCBsZXZlbCA9IGhscy5sZXZlbHNbaGxzLmxvYWRMZXZlbF07XG4gICAgICAgICAgY29uc3QgcmVzdHJpY3RlZEhkY3BMZXZlbCA9IGxldmVsID09IG51bGwgPyB2b2lkIDAgOiBsZXZlbC5hdHRyc1snSERDUC1MRVZFTCddO1xuICAgICAgICAgIGlmIChyZXN0cmljdGVkSGRjcExldmVsKSB7XG4gICAgICAgICAgICBkYXRhLmVycm9yQWN0aW9uID0ge1xuICAgICAgICAgICAgICBhY3Rpb246IE5ldHdvcmtFcnJvckFjdGlvbi5TZW5kQWx0ZXJuYXRlVG9QZW5hbHR5Qm94LFxuICAgICAgICAgICAgICBmbGFnczogRXJyb3JBY3Rpb25GbGFncy5Nb3ZlQWxsQWx0ZXJuYXRlc01hdGNoaW5nSERDUCxcbiAgICAgICAgICAgICAgaGRjcExldmVsOiByZXN0cmljdGVkSGRjcExldmVsXG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmtleVN5c3RlbUVycm9yKGRhdGEpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5CVUZGRVJfQUREX0NPREVDX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuUkVNVVhfQUxMT0NfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5CVUZGRVJfQVBQRU5EX0VSUk9SOlxuICAgICAgICBkYXRhLmVycm9yQWN0aW9uID0gdGhpcy5nZXRMZXZlbFN3aXRjaEFjdGlvbihkYXRhLCAoX2RhdGEkbGV2ZWwgPSBkYXRhLmxldmVsKSAhPSBudWxsID8gX2RhdGEkbGV2ZWwgOiBobHMubG9hZExldmVsKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuSU5URVJOQUxfRVhDRVBUSU9OOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuQlVGRkVSX0FQUEVORElOR19FUlJPUjpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkJVRkZFUl9GVUxMX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuTEVWRUxfU1dJVENIX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuQlVGRkVSX1NUQUxMRURfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5CVUZGRVJfU0VFS19PVkVSX0hPTEU6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5CVUZGRVJfTlVER0VfT05fU1RBTEw6XG4gICAgICAgIGRhdGEuZXJyb3JBY3Rpb24gPSB7XG4gICAgICAgICAgYWN0aW9uOiBOZXR3b3JrRXJyb3JBY3Rpb24uRG9Ob3RoaW5nLFxuICAgICAgICAgIGZsYWdzOiBFcnJvckFjdGlvbkZsYWdzLk5vbmVcbiAgICAgICAgfTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoZGF0YS50eXBlID09PSBFcnJvclR5cGVzLktFWV9TWVNURU1fRVJST1IpIHtcbiAgICAgIHRoaXMua2V5U3lzdGVtRXJyb3IoZGF0YSk7XG4gICAgfVxuICB9XG4gIGtleVN5c3RlbUVycm9yKGRhdGEpIHtcbiAgICBjb25zdCBsZXZlbEluZGV4ID0gdGhpcy5nZXRWYXJpYW50TGV2ZWxJbmRleChkYXRhLmZyYWcpO1xuICAgIC8vIERvIG5vdCByZXRyeSBsZXZlbC4gRXNjYWxhdGUgdG8gZmF0YWwgaWYgc3dpdGNoaW5nIGxldmVscyBmYWlscy5cbiAgICBkYXRhLmxldmVsUmV0cnkgPSBmYWxzZTtcbiAgICBkYXRhLmVycm9yQWN0aW9uID0gdGhpcy5nZXRMZXZlbFN3aXRjaEFjdGlvbihkYXRhLCBsZXZlbEluZGV4KTtcbiAgfVxuICBnZXRQbGF5bGlzdFJldHJ5T3JTd2l0Y2hBY3Rpb24oZGF0YSwgbGV2ZWxJbmRleCkge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IHJldHJ5Q29uZmlnID0gZ2V0UmV0cnlDb25maWcoaGxzLmNvbmZpZy5wbGF5bGlzdExvYWRQb2xpY3ksIGRhdGEpO1xuICAgIGNvbnN0IHJldHJ5Q291bnQgPSB0aGlzLnBsYXlsaXN0RXJyb3IrKztcbiAgICBjb25zdCByZXRyeSA9IHNob3VsZFJldHJ5KHJldHJ5Q29uZmlnLCByZXRyeUNvdW50LCBpc1RpbWVvdXRFcnJvcihkYXRhKSwgZGF0YS5yZXNwb25zZSk7XG4gICAgaWYgKHJldHJ5KSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBhY3Rpb246IE5ldHdvcmtFcnJvckFjdGlvbi5SZXRyeVJlcXVlc3QsXG4gICAgICAgIGZsYWdzOiBFcnJvckFjdGlvbkZsYWdzLk5vbmUsXG4gICAgICAgIHJldHJ5Q29uZmlnLFxuICAgICAgICByZXRyeUNvdW50XG4gICAgICB9O1xuICAgIH1cbiAgICBjb25zdCBlcnJvckFjdGlvbiA9IHRoaXMuZ2V0TGV2ZWxTd2l0Y2hBY3Rpb24oZGF0YSwgbGV2ZWxJbmRleCk7XG4gICAgaWYgKHJldHJ5Q29uZmlnKSB7XG4gICAgICBlcnJvckFjdGlvbi5yZXRyeUNvbmZpZyA9IHJldHJ5Q29uZmlnO1xuICAgICAgZXJyb3JBY3Rpb24ucmV0cnlDb3VudCA9IHJldHJ5Q291bnQ7XG4gICAgfVxuICAgIHJldHVybiBlcnJvckFjdGlvbjtcbiAgfVxuICBnZXRGcmFnUmV0cnlPclN3aXRjaEFjdGlvbihkYXRhKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgLy8gU2hhcmUgZnJhZ21lbnQgZXJyb3IgY291bnQgYWNjcm9zcyBtZWRpYSBvcHRpb25zIChtYWluLCBhdWRpbywgc3VicylcbiAgICAvLyBUaGlzIGFsbG93cyBmb3IgbGV2ZWwgYmFzZWQgcmVuZGl0aW9uIHN3aXRjaGluZyB3aGVuIG1lZGlhIG9wdGlvbiBhc3NldHMgZmFpbFxuICAgIGNvbnN0IHZhcmlhbnRMZXZlbEluZGV4ID0gdGhpcy5nZXRWYXJpYW50TGV2ZWxJbmRleChkYXRhLmZyYWcpO1xuICAgIGNvbnN0IGxldmVsID0gaGxzLmxldmVsc1t2YXJpYW50TGV2ZWxJbmRleF07XG4gICAgY29uc3Qge1xuICAgICAgZnJhZ0xvYWRQb2xpY3ksXG4gICAgICBrZXlMb2FkUG9saWN5XG4gICAgfSA9IGhscy5jb25maWc7XG4gICAgY29uc3QgcmV0cnlDb25maWcgPSBnZXRSZXRyeUNvbmZpZyhkYXRhLmRldGFpbHMuc3RhcnRzV2l0aCgna2V5JykgPyBrZXlMb2FkUG9saWN5IDogZnJhZ0xvYWRQb2xpY3ksIGRhdGEpO1xuICAgIGNvbnN0IGZyYWdtZW50RXJyb3JzID0gaGxzLmxldmVscy5yZWR1Y2UoKGFjYywgbGV2ZWwpID0+IGFjYyArIGxldmVsLmZyYWdtZW50RXJyb3IsIDApO1xuICAgIC8vIFN3aXRjaCBsZXZlbHMgd2hlbiBvdXQgb2YgcmV0cmllZCBvciBsZXZlbCBpbmRleCBvdXQgb2YgYm91bmRzXG4gICAgaWYgKGxldmVsKSB7XG4gICAgICBpZiAoZGF0YS5kZXRhaWxzICE9PSBFcnJvckRldGFpbHMuRlJBR19HQVApIHtcbiAgICAgICAgbGV2ZWwuZnJhZ21lbnRFcnJvcisrO1xuICAgICAgfVxuICAgICAgY29uc3QgcmV0cnkgPSBzaG91bGRSZXRyeShyZXRyeUNvbmZpZywgZnJhZ21lbnRFcnJvcnMsIGlzVGltZW91dEVycm9yKGRhdGEpLCBkYXRhLnJlc3BvbnNlKTtcbiAgICAgIGlmIChyZXRyeSkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGFjdGlvbjogTmV0d29ya0Vycm9yQWN0aW9uLlJldHJ5UmVxdWVzdCxcbiAgICAgICAgICBmbGFnczogRXJyb3JBY3Rpb25GbGFncy5Ob25lLFxuICAgICAgICAgIHJldHJ5Q29uZmlnLFxuICAgICAgICAgIHJldHJ5Q291bnQ6IGZyYWdtZW50RXJyb3JzXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgfVxuICAgIC8vIFJlYWNoIG1heCByZXRyeSBjb3VudCwgb3IgTWlzc2luZyBsZXZlbCByZWZlcmVuY2VcbiAgICAvLyBTd2l0Y2ggdG8gdmFsaWQgaW5kZXhcbiAgICBjb25zdCBlcnJvckFjdGlvbiA9IHRoaXMuZ2V0TGV2ZWxTd2l0Y2hBY3Rpb24oZGF0YSwgdmFyaWFudExldmVsSW5kZXgpO1xuICAgIC8vIEFkZCByZXRyeSBkZXRhaWxzIHRvIGFsbG93IHNraXBwaW5nIG9mIEZSQUdfUEFSU0lOR19FUlJPUlxuICAgIGlmIChyZXRyeUNvbmZpZykge1xuICAgICAgZXJyb3JBY3Rpb24ucmV0cnlDb25maWcgPSByZXRyeUNvbmZpZztcbiAgICAgIGVycm9yQWN0aW9uLnJldHJ5Q291bnQgPSBmcmFnbWVudEVycm9ycztcbiAgICB9XG4gICAgcmV0dXJuIGVycm9yQWN0aW9uO1xuICB9XG4gIGdldExldmVsU3dpdGNoQWN0aW9uKGRhdGEsIGxldmVsSW5kZXgpIHtcbiAgICBjb25zdCBobHMgPSB0aGlzLmhscztcbiAgICBpZiAobGV2ZWxJbmRleCA9PT0gbnVsbCB8fCBsZXZlbEluZGV4ID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGxldmVsSW5kZXggPSBobHMubG9hZExldmVsO1xuICAgIH1cbiAgICBjb25zdCBsZXZlbCA9IHRoaXMuaGxzLmxldmVsc1tsZXZlbEluZGV4XTtcbiAgICBpZiAobGV2ZWwpIHtcbiAgICAgIHZhciBfZGF0YSRmcmFnMiwgX2RhdGEkY29udGV4dDI7XG4gICAgICBjb25zdCBlcnJvckRldGFpbHMgPSBkYXRhLmRldGFpbHM7XG4gICAgICBsZXZlbC5sb2FkRXJyb3IrKztcbiAgICAgIGlmIChlcnJvckRldGFpbHMgPT09IEVycm9yRGV0YWlscy5CVUZGRVJfQVBQRU5EX0VSUk9SKSB7XG4gICAgICAgIGxldmVsLmZyYWdtZW50RXJyb3IrKztcbiAgICAgIH1cbiAgICAgIC8vIFNlYXJjaCBmb3IgbmV4dCBsZXZlbCB0byByZXRyeVxuICAgICAgbGV0IG5leHRMZXZlbCA9IC0xO1xuICAgICAgY29uc3Qge1xuICAgICAgICBsZXZlbHMsXG4gICAgICAgIGxvYWRMZXZlbCxcbiAgICAgICAgbWluQXV0b0xldmVsLFxuICAgICAgICBtYXhBdXRvTGV2ZWxcbiAgICAgIH0gPSBobHM7XG4gICAgICBpZiAoIWhscy5hdXRvTGV2ZWxFbmFibGVkKSB7XG4gICAgICAgIGhscy5sb2FkTGV2ZWwgPSAtMTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGZyYWdFcnJvclR5cGUgPSAoX2RhdGEkZnJhZzIgPSBkYXRhLmZyYWcpID09IG51bGwgPyB2b2lkIDAgOiBfZGF0YSRmcmFnMi50eXBlO1xuICAgICAgLy8gRmluZCBhbHRlcm5hdGUgYXVkaW8gY29kZWMgaWYgYXZhaWxhYmxlIG9uIGF1ZGlvIGNvZGVjIGVycm9yXG4gICAgICBjb25zdCBpc0F1ZGlvQ29kZWNFcnJvciA9IGZyYWdFcnJvclR5cGUgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLkFVRElPICYmIGVycm9yRGV0YWlscyA9PT0gRXJyb3JEZXRhaWxzLkZSQUdfUEFSU0lOR19FUlJPUiB8fCBkYXRhLnNvdXJjZUJ1ZmZlck5hbWUgPT09ICdhdWRpbycgJiYgKGVycm9yRGV0YWlscyA9PT0gRXJyb3JEZXRhaWxzLkJVRkZFUl9BRERfQ09ERUNfRVJST1IgfHwgZXJyb3JEZXRhaWxzID09PSBFcnJvckRldGFpbHMuQlVGRkVSX0FQUEVORF9FUlJPUik7XG4gICAgICBjb25zdCBmaW5kQXVkaW9Db2RlY0FsdGVybmF0ZSA9IGlzQXVkaW9Db2RlY0Vycm9yICYmIGxldmVscy5zb21lKCh7XG4gICAgICAgIGF1ZGlvQ29kZWNcbiAgICAgIH0pID0+IGxldmVsLmF1ZGlvQ29kZWMgIT09IGF1ZGlvQ29kZWMpO1xuICAgICAgLy8gRmluZCBhbHRlcm5hdGUgdmlkZW8gY29kZWMgaWYgYXZhaWxhYmxlIG9uIHZpZGVvIGNvZGVjIGVycm9yXG4gICAgICBjb25zdCBpc1ZpZGVvQ29kZWNFcnJvciA9IGRhdGEuc291cmNlQnVmZmVyTmFtZSA9PT0gJ3ZpZGVvJyAmJiAoZXJyb3JEZXRhaWxzID09PSBFcnJvckRldGFpbHMuQlVGRkVSX0FERF9DT0RFQ19FUlJPUiB8fCBlcnJvckRldGFpbHMgPT09IEVycm9yRGV0YWlscy5CVUZGRVJfQVBQRU5EX0VSUk9SKTtcbiAgICAgIGNvbnN0IGZpbmRWaWRlb0NvZGVjQWx0ZXJuYXRlID0gaXNWaWRlb0NvZGVjRXJyb3IgJiYgbGV2ZWxzLnNvbWUoKHtcbiAgICAgICAgY29kZWNTZXQsXG4gICAgICAgIGF1ZGlvQ29kZWNcbiAgICAgIH0pID0+IGxldmVsLmNvZGVjU2V0ICE9PSBjb2RlY1NldCAmJiBsZXZlbC5hdWRpb0NvZGVjID09PSBhdWRpb0NvZGVjKTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgdHlwZTogcGxheWxpc3RFcnJvclR5cGUsXG4gICAgICAgIGdyb3VwSWQ6IHBsYXlsaXN0RXJyb3JHcm91cElkXG4gICAgICB9ID0gKF9kYXRhJGNvbnRleHQyID0gZGF0YS5jb250ZXh0KSAhPSBudWxsID8gX2RhdGEkY29udGV4dDIgOiB7fTtcbiAgICAgIGZvciAobGV0IGkgPSBsZXZlbHMubGVuZ3RoOyBpLS07KSB7XG4gICAgICAgIGNvbnN0IGNhbmRpZGF0ZSA9IChpICsgbG9hZExldmVsKSAlIGxldmVscy5sZW5ndGg7XG4gICAgICAgIGlmIChjYW5kaWRhdGUgIT09IGxvYWRMZXZlbCAmJiBjYW5kaWRhdGUgPj0gbWluQXV0b0xldmVsICYmIGNhbmRpZGF0ZSA8PSBtYXhBdXRvTGV2ZWwgJiYgbGV2ZWxzW2NhbmRpZGF0ZV0ubG9hZEVycm9yID09PSAwKSB7XG4gICAgICAgICAgdmFyIF9sZXZlbCRhdWRpb0dyb3VwcywgX2xldmVsJHN1YnRpdGxlR3JvdXBzO1xuICAgICAgICAgIGNvbnN0IGxldmVsQ2FuZGlkYXRlID0gbGV2ZWxzW2NhbmRpZGF0ZV07XG4gICAgICAgICAgLy8gU2tpcCBsZXZlbCBzd2l0Y2ggaWYgR0FQIHRhZyBpcyBmb3VuZCBpbiBuZXh0IGxldmVsIGF0IHNhbWUgcG9zaXRpb25cbiAgICAgICAgICBpZiAoZXJyb3JEZXRhaWxzID09PSBFcnJvckRldGFpbHMuRlJBR19HQVAgJiYgZnJhZ0Vycm9yVHlwZSA9PT0gUGxheWxpc3RMZXZlbFR5cGUuTUFJTiAmJiBkYXRhLmZyYWcpIHtcbiAgICAgICAgICAgIGNvbnN0IGxldmVsRGV0YWlscyA9IGxldmVsc1tjYW5kaWRhdGVdLmRldGFpbHM7XG4gICAgICAgICAgICBpZiAobGV2ZWxEZXRhaWxzKSB7XG4gICAgICAgICAgICAgIGNvbnN0IGZyYWdDYW5kaWRhdGUgPSBmaW5kRnJhZ21lbnRCeVBUUyhkYXRhLmZyYWcsIGxldmVsRGV0YWlscy5mcmFnbWVudHMsIGRhdGEuZnJhZy5zdGFydCk7XG4gICAgICAgICAgICAgIGlmIChmcmFnQ2FuZGlkYXRlICE9IG51bGwgJiYgZnJhZ0NhbmRpZGF0ZS5nYXApIHtcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSBpZiAocGxheWxpc3RFcnJvclR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuQVVESU9fVFJBQ0sgJiYgbGV2ZWxDYW5kaWRhdGUuaGFzQXVkaW9Hcm91cChwbGF5bGlzdEVycm9yR3JvdXBJZCkgfHwgcGxheWxpc3RFcnJvclR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuU1VCVElUTEVfVFJBQ0sgJiYgbGV2ZWxDYW5kaWRhdGUuaGFzU3VidGl0bGVHcm91cChwbGF5bGlzdEVycm9yR3JvdXBJZCkpIHtcbiAgICAgICAgICAgIC8vIEZvciBhdWRpby9zdWJzIHBsYXlsaXN0IGVycm9ycyBmaW5kIGFub3RoZXIgZ3JvdXAgSUQgb3IgZmFsbHRocm91Z2ggdG8gcmVkdW5kYW50IGZhaWwtb3ZlclxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfSBlbHNlIGlmIChmcmFnRXJyb3JUeXBlID09PSBQbGF5bGlzdExldmVsVHlwZS5BVURJTyAmJiAoX2xldmVsJGF1ZGlvR3JvdXBzID0gbGV2ZWwuYXVkaW9Hcm91cHMpICE9IG51bGwgJiYgX2xldmVsJGF1ZGlvR3JvdXBzLnNvbWUoZ3JvdXBJZCA9PiBsZXZlbENhbmRpZGF0ZS5oYXNBdWRpb0dyb3VwKGdyb3VwSWQpKSB8fCBmcmFnRXJyb3JUeXBlID09PSBQbGF5bGlzdExldmVsVHlwZS5TVUJUSVRMRSAmJiAoX2xldmVsJHN1YnRpdGxlR3JvdXBzID0gbGV2ZWwuc3VidGl0bGVHcm91cHMpICE9IG51bGwgJiYgX2xldmVsJHN1YnRpdGxlR3JvdXBzLnNvbWUoZ3JvdXBJZCA9PiBsZXZlbENhbmRpZGF0ZS5oYXNTdWJ0aXRsZUdyb3VwKGdyb3VwSWQpKSB8fCBmaW5kQXVkaW9Db2RlY0FsdGVybmF0ZSAmJiBsZXZlbC5hdWRpb0NvZGVjID09PSBsZXZlbENhbmRpZGF0ZS5hdWRpb0NvZGVjIHx8ICFmaW5kQXVkaW9Db2RlY0FsdGVybmF0ZSAmJiBsZXZlbC5hdWRpb0NvZGVjICE9PSBsZXZlbENhbmRpZGF0ZS5hdWRpb0NvZGVjIHx8IGZpbmRWaWRlb0NvZGVjQWx0ZXJuYXRlICYmIGxldmVsLmNvZGVjU2V0ID09PSBsZXZlbENhbmRpZGF0ZS5jb2RlY1NldCkge1xuICAgICAgICAgICAgLy8gRm9yIHZpZGVvL2F1ZGlvL3N1YnMgZnJhZyBlcnJvcnMgZmluZCBhbm90aGVyIGdyb3VwIElEIG9yIGZhbGx0aHJvdWdoIHRvIHJlZHVuZGFudCBmYWlsLW92ZXJcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgIH1cbiAgICAgICAgICBuZXh0TGV2ZWwgPSBjYW5kaWRhdGU7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChuZXh0TGV2ZWwgPiAtMSAmJiBobHMubG9hZExldmVsICE9PSBuZXh0TGV2ZWwpIHtcbiAgICAgICAgZGF0YS5sZXZlbFJldHJ5ID0gdHJ1ZTtcbiAgICAgICAgdGhpcy5wbGF5bGlzdEVycm9yID0gMDtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBhY3Rpb246IE5ldHdvcmtFcnJvckFjdGlvbi5TZW5kQWx0ZXJuYXRlVG9QZW5hbHR5Qm94LFxuICAgICAgICAgIGZsYWdzOiBFcnJvckFjdGlvbkZsYWdzLk5vbmUsXG4gICAgICAgICAgbmV4dEF1dG9MZXZlbDogbmV4dExldmVsXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgfVxuICAgIC8vIE5vIGxldmVscyB0byBzd2l0Y2ggLyBNYW51YWwgbGV2ZWwgc2VsZWN0aW9uIC8gTGV2ZWwgbm90IGZvdW5kXG4gICAgLy8gUmVzb2x2ZSB3aXRoIFBhdGh3YXkgc3dpdGNoLCBSZWR1bmRhbnQgZmFpbC1vdmVyLCBvciBzdGF5IG9uIGxvd2VzdCBMZXZlbFxuICAgIHJldHVybiB7XG4gICAgICBhY3Rpb246IE5ldHdvcmtFcnJvckFjdGlvbi5TZW5kQWx0ZXJuYXRlVG9QZW5hbHR5Qm94LFxuICAgICAgZmxhZ3M6IEVycm9yQWN0aW9uRmxhZ3MuTW92ZUFsbEFsdGVybmF0ZXNNYXRjaGluZ0hvc3RcbiAgICB9O1xuICB9XG4gIG9uRXJyb3JPdXQoZXZlbnQsIGRhdGEpIHtcbiAgICB2YXIgX2RhdGEkZXJyb3JBY3Rpb247XG4gICAgc3dpdGNoICgoX2RhdGEkZXJyb3JBY3Rpb24gPSBkYXRhLmVycm9yQWN0aW9uKSA9PSBudWxsID8gdm9pZCAwIDogX2RhdGEkZXJyb3JBY3Rpb24uYWN0aW9uKSB7XG4gICAgICBjYXNlIE5ldHdvcmtFcnJvckFjdGlvbi5Eb05vdGhpbmc6XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBOZXR3b3JrRXJyb3JBY3Rpb24uU2VuZEFsdGVybmF0ZVRvUGVuYWx0eUJveDpcbiAgICAgICAgdGhpcy5zZW5kQWx0ZXJuYXRlVG9QZW5hbHR5Qm94KGRhdGEpO1xuICAgICAgICBpZiAoIWRhdGEuZXJyb3JBY3Rpb24ucmVzb2x2ZWQgJiYgZGF0YS5kZXRhaWxzICE9PSBFcnJvckRldGFpbHMuRlJBR19HQVApIHtcbiAgICAgICAgICBkYXRhLmZhdGFsID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIGlmICgvTWVkaWFTb3VyY2UgcmVhZHlTdGF0ZTogZW5kZWQvLnRlc3QoZGF0YS5lcnJvci5tZXNzYWdlKSkge1xuICAgICAgICAgIHRoaXMud2FybihgTWVkaWFTb3VyY2UgZW5kZWQgYWZ0ZXIgXCIke2RhdGEuc291cmNlQnVmZmVyTmFtZX1cIiBzb3VyY2VCdWZmZXIgYXBwZW5kIGVycm9yLiBBdHRlbXB0aW5nIHRvIHJlY292ZXIgZnJvbSBtZWRpYSBlcnJvci5gKTtcbiAgICAgICAgICB0aGlzLmhscy5yZWNvdmVyTWVkaWFFcnJvcigpO1xuICAgICAgICB9XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBOZXR3b3JrRXJyb3JBY3Rpb24uUmV0cnlSZXF1ZXN0OlxuICAgICAgICAvLyBoYW5kbGVkIGJ5IHN0cmVhbSBhbmQgcGxheWxpc3QvbGV2ZWwgY29udHJvbGxlcnNcbiAgICAgICAgYnJlYWs7XG4gICAgfVxuICAgIGlmIChkYXRhLmZhdGFsKSB7XG4gICAgICB0aGlzLmhscy5zdG9wTG9hZCgpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgfVxuICBzZW5kQWx0ZXJuYXRlVG9QZW5hbHR5Qm94KGRhdGEpIHtcbiAgICBjb25zdCBobHMgPSB0aGlzLmhscztcbiAgICBjb25zdCBlcnJvckFjdGlvbiA9IGRhdGEuZXJyb3JBY3Rpb247XG4gICAgaWYgKCFlcnJvckFjdGlvbikge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7XG4gICAgICBmbGFncyxcbiAgICAgIGhkY3BMZXZlbCxcbiAgICAgIG5leHRBdXRvTGV2ZWxcbiAgICB9ID0gZXJyb3JBY3Rpb247XG4gICAgc3dpdGNoIChmbGFncykge1xuICAgICAgY2FzZSBFcnJvckFjdGlvbkZsYWdzLk5vbmU6XG4gICAgICAgIHRoaXMuc3dpdGNoTGV2ZWwoZGF0YSwgbmV4dEF1dG9MZXZlbCk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBFcnJvckFjdGlvbkZsYWdzLk1vdmVBbGxBbHRlcm5hdGVzTWF0Y2hpbmdIRENQOlxuICAgICAgICBpZiAoaGRjcExldmVsKSB7XG4gICAgICAgICAgaGxzLm1heEhkY3BMZXZlbCA9IEhkY3BMZXZlbHNbSGRjcExldmVscy5pbmRleE9mKGhkY3BMZXZlbCkgLSAxXTtcbiAgICAgICAgICBlcnJvckFjdGlvbi5yZXNvbHZlZCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy53YXJuKGBSZXN0cmljdGluZyBwbGF5YmFjayB0byBIRENQLUxFVkVMIG9mIFwiJHtobHMubWF4SGRjcExldmVsfVwiIG9yIGxvd2VyYCk7XG4gICAgICAgIGJyZWFrO1xuICAgIH1cbiAgICAvLyBJZiBub3QgcmVzb2x2ZWQgYnkgcHJldmlvdXMgYWN0aW9ucyB0cnkgdG8gc3dpdGNoIHRvIG5leHQgbGV2ZWxcbiAgICBpZiAoIWVycm9yQWN0aW9uLnJlc29sdmVkKSB7XG4gICAgICB0aGlzLnN3aXRjaExldmVsKGRhdGEsIG5leHRBdXRvTGV2ZWwpO1xuICAgIH1cbiAgfVxuICBzd2l0Y2hMZXZlbChkYXRhLCBsZXZlbEluZGV4KSB7XG4gICAgaWYgKGxldmVsSW5kZXggIT09IHVuZGVmaW5lZCAmJiBkYXRhLmVycm9yQWN0aW9uKSB7XG4gICAgICB0aGlzLndhcm4oYHN3aXRjaGluZyB0byBsZXZlbCAke2xldmVsSW5kZXh9IGFmdGVyICR7ZGF0YS5kZXRhaWxzfWApO1xuICAgICAgdGhpcy5obHMubmV4dEF1dG9MZXZlbCA9IGxldmVsSW5kZXg7XG4gICAgICBkYXRhLmVycm9yQWN0aW9uLnJlc29sdmVkID0gdHJ1ZTtcbiAgICAgIC8vIFN0cmVhbSBjb250cm9sbGVyIGlzIHJlc3BvbnNpYmxlIGZvciB0aGlzIGJ1dCB3b24ndCBzd2l0Y2ggb24gZmFsc2Ugc3RhcnRcbiAgICAgIHRoaXMuaGxzLm5leHRMb2FkTGV2ZWwgPSB0aGlzLmhscy5uZXh0QXV0b0xldmVsO1xuICAgIH1cbiAgfVxufVxuXG5jbGFzcyBCYXNlUGxheWxpc3RDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzLCBsb2dQcmVmaXgpIHtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLnRpbWVyID0gLTE7XG4gICAgdGhpcy5yZXF1ZXN0U2NoZWR1bGVkID0gLTE7XG4gICAgdGhpcy5jYW5Mb2FkID0gZmFsc2U7XG4gICAgdGhpcy5sb2cgPSB2b2lkIDA7XG4gICAgdGhpcy53YXJuID0gdm9pZCAwO1xuICAgIHRoaXMubG9nID0gbG9nZ2VyLmxvZy5iaW5kKGxvZ2dlciwgYCR7bG9nUHJlZml4fTpgKTtcbiAgICB0aGlzLndhcm4gPSBsb2dnZXIud2Fybi5iaW5kKGxvZ2dlciwgYCR7bG9nUHJlZml4fTpgKTtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMuY2xlYXJUaW1lcigpO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmhscyA9IHRoaXMubG9nID0gdGhpcy53YXJuID0gbnVsbDtcbiAgfVxuICBjbGVhclRpbWVyKCkge1xuICAgIGlmICh0aGlzLnRpbWVyICE9PSAtMSkge1xuICAgICAgc2VsZi5jbGVhclRpbWVvdXQodGhpcy50aW1lcik7XG4gICAgICB0aGlzLnRpbWVyID0gLTE7XG4gICAgfVxuICB9XG4gIHN0YXJ0TG9hZCgpIHtcbiAgICB0aGlzLmNhbkxvYWQgPSB0cnVlO1xuICAgIHRoaXMucmVxdWVzdFNjaGVkdWxlZCA9IC0xO1xuICAgIHRoaXMubG9hZFBsYXlsaXN0KCk7XG4gIH1cbiAgc3RvcExvYWQoKSB7XG4gICAgdGhpcy5jYW5Mb2FkID0gZmFsc2U7XG4gICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gIH1cbiAgc3dpdGNoUGFyYW1zKHBsYXlsaXN0VXJpLCBwcmV2aW91cywgY3VycmVudCkge1xuICAgIGNvbnN0IHJlbmRpdGlvblJlcG9ydHMgPSBwcmV2aW91cyA9PSBudWxsID8gdm9pZCAwIDogcHJldmlvdXMucmVuZGl0aW9uUmVwb3J0cztcbiAgICBpZiAocmVuZGl0aW9uUmVwb3J0cykge1xuICAgICAgbGV0IGZvdW5kSW5kZXggPSAtMTtcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgcmVuZGl0aW9uUmVwb3J0cy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCBhdHRyID0gcmVuZGl0aW9uUmVwb3J0c1tpXTtcbiAgICAgICAgbGV0IHVyaTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICB1cmkgPSBuZXcgc2VsZi5VUkwoYXR0ci5VUkksIHByZXZpb3VzLnVybCkuaHJlZjtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICBsb2dnZXIud2FybihgQ291bGQgbm90IGNvbnN0cnVjdCBuZXcgVVJMIGZvciBSZW5kaXRpb24gUmVwb3J0OiAke2Vycm9yfWApO1xuICAgICAgICAgIHVyaSA9IGF0dHIuVVJJIHx8ICcnO1xuICAgICAgICB9XG4gICAgICAgIC8vIFVzZSBleGFjdCBtYXRjaC4gT3RoZXJ3aXNlLCB0aGUgbGFzdCBwYXJ0aWFsIG1hdGNoLCBpZiBhbnksIHdpbGwgYmUgdXNlZFxuICAgICAgICAvLyAoUGxheWxpc3QgVVJJIGluY2x1ZGVzIGEgcXVlcnkgc3RyaW5nIHRoYXQgdGhlIFJlbmRpdGlvbiBSZXBvcnQgZG9lcyBub3QpXG4gICAgICAgIGlmICh1cmkgPT09IHBsYXlsaXN0VXJpKSB7XG4gICAgICAgICAgZm91bmRJbmRleCA9IGk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH0gZWxzZSBpZiAodXJpID09PSBwbGF5bGlzdFVyaS5zdWJzdHJpbmcoMCwgdXJpLmxlbmd0aCkpIHtcbiAgICAgICAgICBmb3VuZEluZGV4ID0gaTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKGZvdW5kSW5kZXggIT09IC0xKSB7XG4gICAgICAgIGNvbnN0IGF0dHIgPSByZW5kaXRpb25SZXBvcnRzW2ZvdW5kSW5kZXhdO1xuICAgICAgICBjb25zdCBtc24gPSBwYXJzZUludChhdHRyWydMQVNULU1TTiddKSB8fCAocHJldmlvdXMgPT0gbnVsbCA/IHZvaWQgMCA6IHByZXZpb3VzLmxhc3RQYXJ0U24pO1xuICAgICAgICBsZXQgcGFydCA9IHBhcnNlSW50KGF0dHJbJ0xBU1QtUEFSVCddKSB8fCAocHJldmlvdXMgPT0gbnVsbCA/IHZvaWQgMCA6IHByZXZpb3VzLmxhc3RQYXJ0SW5kZXgpO1xuICAgICAgICBpZiAodGhpcy5obHMuY29uZmlnLmxvd0xhdGVuY3lNb2RlKSB7XG4gICAgICAgICAgY29uc3QgY3VycmVudEdvYWwgPSBNYXRoLm1pbihwcmV2aW91cy5hZ2UgLSBwcmV2aW91cy5wYXJ0VGFyZ2V0LCBwcmV2aW91cy50YXJnZXRkdXJhdGlvbik7XG4gICAgICAgICAgaWYgKHBhcnQgPj0gMCAmJiBjdXJyZW50R29hbCA+IHByZXZpb3VzLnBhcnRUYXJnZXQpIHtcbiAgICAgICAgICAgIHBhcnQgKz0gMTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc2tpcCA9IGN1cnJlbnQgJiYgZ2V0U2tpcFZhbHVlKGN1cnJlbnQpO1xuICAgICAgICByZXR1cm4gbmV3IEhsc1VybFBhcmFtZXRlcnMobXNuLCBwYXJ0ID49IDAgPyBwYXJ0IDogdW5kZWZpbmVkLCBza2lwKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgbG9hZFBsYXlsaXN0KGhsc1VybFBhcmFtZXRlcnMpIHtcbiAgICBpZiAodGhpcy5yZXF1ZXN0U2NoZWR1bGVkID09PSAtMSkge1xuICAgICAgdGhpcy5yZXF1ZXN0U2NoZWR1bGVkID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICB9XG4gICAgLy8gTG9hZGluZyBpcyBoYW5kbGVkIGJ5IHRoZSBzdWJjbGFzc2VzXG4gIH1cbiAgc2hvdWxkTG9hZFBsYXlsaXN0KHBsYXlsaXN0KSB7XG4gICAgcmV0dXJuIHRoaXMuY2FuTG9hZCAmJiAhIXBsYXlsaXN0ICYmICEhcGxheWxpc3QudXJsICYmICghcGxheWxpc3QuZGV0YWlscyB8fCBwbGF5bGlzdC5kZXRhaWxzLmxpdmUpO1xuICB9XG4gIHNob3VsZFJlbG9hZFBsYXlsaXN0KHBsYXlsaXN0KSB7XG4gICAgcmV0dXJuIHRoaXMudGltZXIgPT09IC0xICYmIHRoaXMucmVxdWVzdFNjaGVkdWxlZCA9PT0gLTEgJiYgdGhpcy5zaG91bGRMb2FkUGxheWxpc3QocGxheWxpc3QpO1xuICB9XG4gIHBsYXlsaXN0TG9hZGVkKGluZGV4LCBkYXRhLCBwcmV2aW91c0RldGFpbHMpIHtcbiAgICBjb25zdCB7XG4gICAgICBkZXRhaWxzLFxuICAgICAgc3RhdHNcbiAgICB9ID0gZGF0YTtcblxuICAgIC8vIFNldCBsYXN0IHVwZGF0ZWQgZGF0ZS10aW1lXG4gICAgY29uc3Qgbm93ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICBjb25zdCBlbGFwc2VkID0gc3RhdHMubG9hZGluZy5maXJzdCA/IE1hdGgubWF4KDAsIG5vdyAtIHN0YXRzLmxvYWRpbmcuZmlyc3QpIDogMDtcbiAgICBkZXRhaWxzLmFkdmFuY2VkRGF0ZVRpbWUgPSBEYXRlLm5vdygpIC0gZWxhcHNlZDtcblxuICAgIC8vIGlmIGN1cnJlbnQgcGxheWxpc3QgaXMgYSBsaXZlIHBsYXlsaXN0LCBhcm0gYSB0aW1lciB0byByZWxvYWQgaXRcbiAgICBpZiAoZGV0YWlscy5saXZlIHx8IHByZXZpb3VzRGV0YWlscyAhPSBudWxsICYmIHByZXZpb3VzRGV0YWlscy5saXZlKSB7XG4gICAgICBkZXRhaWxzLnJlbG9hZGVkKHByZXZpb3VzRGV0YWlscyk7XG4gICAgICBpZiAocHJldmlvdXNEZXRhaWxzKSB7XG4gICAgICAgIHRoaXMubG9nKGBsaXZlIHBsYXlsaXN0ICR7aW5kZXh9ICR7ZGV0YWlscy5hZHZhbmNlZCA/ICdSRUZSRVNIRUQgJyArIGRldGFpbHMubGFzdFBhcnRTbiArICctJyArIGRldGFpbHMubGFzdFBhcnRJbmRleCA6IGRldGFpbHMudXBkYXRlZCA/ICdVUERBVEVEJyA6ICdNSVNTRUQnfWApO1xuICAgICAgfVxuICAgICAgLy8gTWVyZ2UgbGl2ZSBwbGF5bGlzdHMgdG8gYWRqdXN0IGZyYWdtZW50IHN0YXJ0cyBhbmQgZmlsbCBpbiBkZWx0YSBwbGF5bGlzdCBza2lwcGVkIHNlZ21lbnRzXG4gICAgICBpZiAocHJldmlvdXNEZXRhaWxzICYmIGRldGFpbHMuZnJhZ21lbnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgbWVyZ2VEZXRhaWxzKHByZXZpb3VzRGV0YWlscywgZGV0YWlscyk7XG4gICAgICB9XG4gICAgICBpZiAoIXRoaXMuY2FuTG9hZCB8fCAhZGV0YWlscy5saXZlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGxldCBkZWxpdmVyeURpcmVjdGl2ZXM7XG4gICAgICBsZXQgbXNuID0gdW5kZWZpbmVkO1xuICAgICAgbGV0IHBhcnQgPSB1bmRlZmluZWQ7XG4gICAgICBpZiAoZGV0YWlscy5jYW5CbG9ja1JlbG9hZCAmJiBkZXRhaWxzLmVuZFNOICYmIGRldGFpbHMuYWR2YW5jZWQpIHtcbiAgICAgICAgLy8gTG9hZCBsZXZlbCB3aXRoIExMLUhMUyBkZWxpdmVyeSBkaXJlY3RpdmVzXG4gICAgICAgIGNvbnN0IGxvd0xhdGVuY3lNb2RlID0gdGhpcy5obHMuY29uZmlnLmxvd0xhdGVuY3lNb2RlO1xuICAgICAgICBjb25zdCBsYXN0UGFydFNuID0gZGV0YWlscy5sYXN0UGFydFNuO1xuICAgICAgICBjb25zdCBlbmRTbiA9IGRldGFpbHMuZW5kU047XG4gICAgICAgIGNvbnN0IGxhc3RQYXJ0SW5kZXggPSBkZXRhaWxzLmxhc3RQYXJ0SW5kZXg7XG4gICAgICAgIGNvbnN0IGhhc1BhcnRzID0gbGFzdFBhcnRJbmRleCAhPT0gLTE7XG4gICAgICAgIGNvbnN0IGxhc3RQYXJ0ID0gbGFzdFBhcnRTbiA9PT0gZW5kU247XG4gICAgICAgIC8vIFdoZW4gbG93IGxhdGVuY3kgbW9kZSBpcyBkaXNhYmxlZCwgd2UnbGwgc2tpcCBwYXJ0IHJlcXVlc3RzIG9uY2UgdGhlIGxhc3QgcGFydCBpbmRleCBpcyBmb3VuZFxuICAgICAgICBjb25zdCBuZXh0U25TdGFydEluZGV4ID0gbG93TGF0ZW5jeU1vZGUgPyAwIDogbGFzdFBhcnRJbmRleDtcbiAgICAgICAgaWYgKGhhc1BhcnRzKSB7XG4gICAgICAgICAgbXNuID0gbGFzdFBhcnQgPyBlbmRTbiArIDEgOiBsYXN0UGFydFNuO1xuICAgICAgICAgIHBhcnQgPSBsYXN0UGFydCA/IG5leHRTblN0YXJ0SW5kZXggOiBsYXN0UGFydEluZGV4ICsgMTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBtc24gPSBlbmRTbiArIDE7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTG93LUxhdGVuY3kgQ0ROIFR1bmUtaW46IFwiYWdlXCIgaGVhZGVyIGFuZCB0aW1lIHNpbmNlIGxvYWQgaW5kaWNhdGVzIHdlJ3JlIGJlaGluZCBieSBtb3JlIHRoYW4gb25lIHBhcnRcbiAgICAgICAgLy8gVXBkYXRlIGRpcmVjdGl2ZXMgdG8gb2J0YWluIHRoZSBQbGF5bGlzdCB0aGF0IGhhcyB0aGUgZXN0aW1hdGVkIGFkZGl0aW9uYWwgZHVyYXRpb24gb2YgbWVkaWFcbiAgICAgICAgY29uc3QgbGFzdEFkdmFuY2VkID0gZGV0YWlscy5hZ2U7XG4gICAgICAgIGNvbnN0IGNkbkFnZSA9IGxhc3RBZHZhbmNlZCArIGRldGFpbHMuYWdlSGVhZGVyO1xuICAgICAgICBsZXQgY3VycmVudEdvYWwgPSBNYXRoLm1pbihjZG5BZ2UgLSBkZXRhaWxzLnBhcnRUYXJnZXQsIGRldGFpbHMudGFyZ2V0ZHVyYXRpb24gKiAxLjUpO1xuICAgICAgICBpZiAoY3VycmVudEdvYWwgPiAwKSB7XG4gICAgICAgICAgaWYgKHByZXZpb3VzRGV0YWlscyAmJiBjdXJyZW50R29hbCA+IHByZXZpb3VzRGV0YWlscy50dW5lSW5Hb2FsKSB7XG4gICAgICAgICAgICAvLyBJZiB3ZSBhdHRlbXB0ZWQgdG8gZ2V0IHRoZSBuZXh0IG9yIGxhdGVzdCBwbGF5bGlzdCB1cGRhdGUsIGJ1dCBjdXJyZW50R29hbCBpbmNyZWFzZWQsXG4gICAgICAgICAgICAvLyB0aGVuIHdlIGVpdGhlciBjYW4ndCBjYXRjaHVwLCBvciB0aGUgXCJhZ2VcIiBoZWFkZXIgY2Fubm90IGJlIHRydXN0ZWQuXG4gICAgICAgICAgICB0aGlzLndhcm4oYENETiBUdW5lLWluIGdvYWwgaW5jcmVhc2VkIGZyb206ICR7cHJldmlvdXNEZXRhaWxzLnR1bmVJbkdvYWx9IHRvOiAke2N1cnJlbnRHb2FsfSB3aXRoIHBsYXlsaXN0IGFnZTogJHtkZXRhaWxzLmFnZX1gKTtcbiAgICAgICAgICAgIGN1cnJlbnRHb2FsID0gMDtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY29uc3Qgc2VnbWVudHMgPSBNYXRoLmZsb29yKGN1cnJlbnRHb2FsIC8gZGV0YWlscy50YXJnZXRkdXJhdGlvbik7XG4gICAgICAgICAgICBtc24gKz0gc2VnbWVudHM7XG4gICAgICAgICAgICBpZiAocGFydCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgIGNvbnN0IHBhcnRzID0gTWF0aC5yb3VuZChjdXJyZW50R29hbCAlIGRldGFpbHMudGFyZ2V0ZHVyYXRpb24gLyBkZXRhaWxzLnBhcnRUYXJnZXQpO1xuICAgICAgICAgICAgICBwYXJ0ICs9IHBhcnRzO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5sb2coYENETiBUdW5lLWluIGFnZTogJHtkZXRhaWxzLmFnZUhlYWRlcn1zIGxhc3QgYWR2YW5jZWQgJHtsYXN0QWR2YW5jZWQudG9GaXhlZCgyKX1zIGdvYWw6ICR7Y3VycmVudEdvYWx9IHNraXAgc24gJHtzZWdtZW50c30gdG8gcGFydCAke3BhcnR9YCk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGRldGFpbHMudHVuZUluR29hbCA9IGN1cnJlbnRHb2FsO1xuICAgICAgICB9XG4gICAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlcyA9IHRoaXMuZ2V0RGVsaXZlcnlEaXJlY3RpdmVzKGRldGFpbHMsIGRhdGEuZGVsaXZlcnlEaXJlY3RpdmVzLCBtc24sIHBhcnQpO1xuICAgICAgICBpZiAobG93TGF0ZW5jeU1vZGUgfHwgIWxhc3RQYXJ0KSB7XG4gICAgICAgICAgdGhpcy5sb2FkUGxheWxpc3QoZGVsaXZlcnlEaXJlY3RpdmVzKTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAoZGV0YWlscy5jYW5CbG9ja1JlbG9hZCB8fCBkZXRhaWxzLmNhblNraXBVbnRpbCkge1xuICAgICAgICBkZWxpdmVyeURpcmVjdGl2ZXMgPSB0aGlzLmdldERlbGl2ZXJ5RGlyZWN0aXZlcyhkZXRhaWxzLCBkYXRhLmRlbGl2ZXJ5RGlyZWN0aXZlcywgbXNuLCBwYXJ0KTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGJ1ZmZlckluZm8gPSB0aGlzLmhscy5tYWluRm9yd2FyZEJ1ZmZlckluZm87XG4gICAgICBjb25zdCBwb3NpdGlvbiA9IGJ1ZmZlckluZm8gPyBidWZmZXJJbmZvLmVuZCAtIGJ1ZmZlckluZm8ubGVuIDogMDtcbiAgICAgIGNvbnN0IGRpc3RhbmNlVG9MaXZlRWRnZU1zID0gKGRldGFpbHMuZWRnZSAtIHBvc2l0aW9uKSAqIDEwMDA7XG4gICAgICBjb25zdCByZWxvYWRJbnRlcnZhbCA9IGNvbXB1dGVSZWxvYWRJbnRlcnZhbChkZXRhaWxzLCBkaXN0YW5jZVRvTGl2ZUVkZ2VNcyk7XG4gICAgICBpZiAoZGV0YWlscy51cGRhdGVkICYmIG5vdyA+IHRoaXMucmVxdWVzdFNjaGVkdWxlZCArIHJlbG9hZEludGVydmFsKSB7XG4gICAgICAgIHRoaXMucmVxdWVzdFNjaGVkdWxlZCA9IHN0YXRzLmxvYWRpbmcuc3RhcnQ7XG4gICAgICB9XG4gICAgICBpZiAobXNuICE9PSB1bmRlZmluZWQgJiYgZGV0YWlscy5jYW5CbG9ja1JlbG9hZCkge1xuICAgICAgICB0aGlzLnJlcXVlc3RTY2hlZHVsZWQgPSBzdGF0cy5sb2FkaW5nLmZpcnN0ICsgcmVsb2FkSW50ZXJ2YWwgLSAoZGV0YWlscy5wYXJ0VGFyZ2V0ICogMTAwMCB8fCAxMDAwKTtcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5yZXF1ZXN0U2NoZWR1bGVkID09PSAtMSB8fCB0aGlzLnJlcXVlc3RTY2hlZHVsZWQgKyByZWxvYWRJbnRlcnZhbCA8IG5vdykge1xuICAgICAgICB0aGlzLnJlcXVlc3RTY2hlZHVsZWQgPSBub3c7XG4gICAgICB9IGVsc2UgaWYgKHRoaXMucmVxdWVzdFNjaGVkdWxlZCAtIG5vdyA8PSAwKSB7XG4gICAgICAgIHRoaXMucmVxdWVzdFNjaGVkdWxlZCArPSByZWxvYWRJbnRlcnZhbDtcbiAgICAgIH1cbiAgICAgIGxldCBlc3RpbWF0ZWRUaW1lVW50aWxVcGRhdGUgPSB0aGlzLnJlcXVlc3RTY2hlZHVsZWQgLSBub3c7XG4gICAgICBlc3RpbWF0ZWRUaW1lVW50aWxVcGRhdGUgPSBNYXRoLm1heCgwLCBlc3RpbWF0ZWRUaW1lVW50aWxVcGRhdGUpO1xuICAgICAgdGhpcy5sb2coYHJlbG9hZCBsaXZlIHBsYXlsaXN0ICR7aW5kZXh9IGluICR7TWF0aC5yb3VuZChlc3RpbWF0ZWRUaW1lVW50aWxVcGRhdGUpfSBtc2ApO1xuICAgICAgLy8gdGhpcy5sb2coXG4gICAgICAvLyAgIGBsaXZlIHJlbG9hZCAke2RldGFpbHMudXBkYXRlZCA/ICdSRUZSRVNIRUQnIDogJ01JU1NFRCd9XG4gICAgICAvLyByZWxvYWQgaW4gJHtlc3RpbWF0ZWRUaW1lVW50aWxVcGRhdGUgLyAxMDAwfVxuICAgICAgLy8gcm91bmQgdHJpcCAkeyhzdGF0cy5sb2FkaW5nLmVuZCAtIHN0YXRzLmxvYWRpbmcuc3RhcnQpIC8gMTAwMH1cbiAgICAgIC8vIGRpZmYgJHtcbiAgICAgIC8vICAgKHJlbG9hZEludGVydmFsIC1cbiAgICAgIC8vICAgICAoZXN0aW1hdGVkVGltZVVudGlsVXBkYXRlICtcbiAgICAgIC8vICAgICAgIHN0YXRzLmxvYWRpbmcuZW5kIC1cbiAgICAgIC8vICAgICAgIHN0YXRzLmxvYWRpbmcuc3RhcnQpKSAvXG4gICAgICAvLyAgIDEwMDBcbiAgICAgIC8vIH1cbiAgICAgIC8vIHJlbG9hZCBpbnRlcnZhbCAke3JlbG9hZEludGVydmFsIC8gMTAwMH1cbiAgICAgIC8vIHRhcmdldCBkdXJhdGlvbiAke2RldGFpbHMudGFyZ2V0ZHVyYXRpb259XG4gICAgICAvLyBkaXN0YW5jZSB0byBlZGdlICR7ZGlzdGFuY2VUb0xpdmVFZGdlTXMgLyAxMDAwfWBcbiAgICAgIC8vICk7XG5cbiAgICAgIHRoaXMudGltZXIgPSBzZWxmLnNldFRpbWVvdXQoKCkgPT4gdGhpcy5sb2FkUGxheWxpc3QoZGVsaXZlcnlEaXJlY3RpdmVzKSwgZXN0aW1hdGVkVGltZVVudGlsVXBkYXRlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgfVxuICB9XG4gIGdldERlbGl2ZXJ5RGlyZWN0aXZlcyhkZXRhaWxzLCBwcmV2aW91c0RlbGl2ZXJ5RGlyZWN0aXZlcywgbXNuLCBwYXJ0KSB7XG4gICAgbGV0IHNraXAgPSBnZXRTa2lwVmFsdWUoZGV0YWlscyk7XG4gICAgaWYgKHByZXZpb3VzRGVsaXZlcnlEaXJlY3RpdmVzICE9IG51bGwgJiYgcHJldmlvdXNEZWxpdmVyeURpcmVjdGl2ZXMuc2tpcCAmJiBkZXRhaWxzLmRlbHRhVXBkYXRlRmFpbGVkKSB7XG4gICAgICBtc24gPSBwcmV2aW91c0RlbGl2ZXJ5RGlyZWN0aXZlcy5tc247XG4gICAgICBwYXJ0ID0gcHJldmlvdXNEZWxpdmVyeURpcmVjdGl2ZXMucGFydDtcbiAgICAgIHNraXAgPSBIbHNTa2lwLk5vO1xuICAgIH1cbiAgICByZXR1cm4gbmV3IEhsc1VybFBhcmFtZXRlcnMobXNuLCBwYXJ0LCBza2lwKTtcbiAgfVxuICBjaGVja1JldHJ5KGVycm9yRXZlbnQpIHtcbiAgICBjb25zdCBlcnJvckRldGFpbHMgPSBlcnJvckV2ZW50LmRldGFpbHM7XG4gICAgY29uc3QgaXNUaW1lb3V0ID0gaXNUaW1lb3V0RXJyb3IoZXJyb3JFdmVudCk7XG4gICAgY29uc3QgZXJyb3JBY3Rpb24gPSBlcnJvckV2ZW50LmVycm9yQWN0aW9uO1xuICAgIGNvbnN0IHtcbiAgICAgIGFjdGlvbixcbiAgICAgIHJldHJ5Q291bnQgPSAwLFxuICAgICAgcmV0cnlDb25maWdcbiAgICB9ID0gZXJyb3JBY3Rpb24gfHwge307XG4gICAgY29uc3QgcmV0cnkgPSAhIWVycm9yQWN0aW9uICYmICEhcmV0cnlDb25maWcgJiYgKGFjdGlvbiA9PT0gTmV0d29ya0Vycm9yQWN0aW9uLlJldHJ5UmVxdWVzdCB8fCAhZXJyb3JBY3Rpb24ucmVzb2x2ZWQgJiYgYWN0aW9uID09PSBOZXR3b3JrRXJyb3JBY3Rpb24uU2VuZEFsdGVybmF0ZVRvUGVuYWx0eUJveCk7XG4gICAgaWYgKHJldHJ5KSB7XG4gICAgICB2YXIgX2Vycm9yRXZlbnQkY29udGV4dDtcbiAgICAgIHRoaXMucmVxdWVzdFNjaGVkdWxlZCA9IC0xO1xuICAgICAgaWYgKHJldHJ5Q291bnQgPj0gcmV0cnlDb25maWcubWF4TnVtUmV0cnkpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgaWYgKGlzVGltZW91dCAmJiAoX2Vycm9yRXZlbnQkY29udGV4dCA9IGVycm9yRXZlbnQuY29udGV4dCkgIT0gbnVsbCAmJiBfZXJyb3JFdmVudCRjb250ZXh0LmRlbGl2ZXJ5RGlyZWN0aXZlcykge1xuICAgICAgICAvLyBUaGUgTEwtSExTIHJlcXVlc3QgYWxyZWFkeSB0aW1lZCBvdXQgc28gcmV0cnkgaW1tZWRpYXRlbHlcbiAgICAgICAgdGhpcy53YXJuKGBSZXRyeWluZyBwbGF5bGlzdCBsb2FkaW5nICR7cmV0cnlDb3VudCArIDF9LyR7cmV0cnlDb25maWcubWF4TnVtUmV0cnl9IGFmdGVyIFwiJHtlcnJvckRldGFpbHN9XCIgd2l0aG91dCBkZWxpdmVyeS1kaXJlY3RpdmVzYCk7XG4gICAgICAgIHRoaXMubG9hZFBsYXlsaXN0KCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBjb25zdCBkZWxheSA9IGdldFJldHJ5RGVsYXkocmV0cnlDb25maWcsIHJldHJ5Q291bnQpO1xuICAgICAgICAvLyBTY2hlZHVsZSBsZXZlbC90cmFjayByZWxvYWRcbiAgICAgICAgdGhpcy50aW1lciA9IHNlbGYuc2V0VGltZW91dCgoKSA9PiB0aGlzLmxvYWRQbGF5bGlzdCgpLCBkZWxheSk7XG4gICAgICAgIHRoaXMud2FybihgUmV0cnlpbmcgcGxheWxpc3QgbG9hZGluZyAke3JldHJ5Q291bnQgKyAxfS8ke3JldHJ5Q29uZmlnLm1heE51bVJldHJ5fSBhZnRlciBcIiR7ZXJyb3JEZXRhaWxzfVwiIGluICR7ZGVsYXl9bXNgKTtcbiAgICAgIH1cbiAgICAgIC8vIGBsZXZlbFJldHJ5ID0gdHJ1ZWAgdXNlZCB0byBpbmZvcm0gb3RoZXIgY29udHJvbGxlcnMgdGhhdCBhIHJldHJ5IGlzIGhhcHBlbmluZ1xuICAgICAgZXJyb3JFdmVudC5sZXZlbFJldHJ5ID0gdHJ1ZTtcbiAgICAgIGVycm9yQWN0aW9uLnJlc29sdmVkID0gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIHJldHJ5O1xuICB9XG59XG5cbi8qXG4gKiBjb21wdXRlIGFuIEV4cG9uZW50aWFsIFdlaWdodGVkIG1vdmluZyBhdmVyYWdlXG4gKiAtIGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01vdmluZ19hdmVyYWdlI0V4cG9uZW50aWFsX21vdmluZ19hdmVyYWdlXG4gKiAgLSBoZWF2aWx5IGluc3BpcmVkIGZyb20gc2hha2EtcGxheWVyXG4gKi9cblxuY2xhc3MgRVdNQSB7XG4gIC8vICBBYm91dCBoYWxmIG9mIHRoZSBlc3RpbWF0ZWQgdmFsdWUgd2lsbCBiZSBmcm9tIHRoZSBsYXN0IHxoYWxmTGlmZXwgc2FtcGxlcyBieSB3ZWlnaHQuXG4gIGNvbnN0cnVjdG9yKGhhbGZMaWZlLCBlc3RpbWF0ZSA9IDAsIHdlaWdodCA9IDApIHtcbiAgICB0aGlzLmhhbGZMaWZlID0gdm9pZCAwO1xuICAgIHRoaXMuYWxwaGFfID0gdm9pZCAwO1xuICAgIHRoaXMuZXN0aW1hdGVfID0gdm9pZCAwO1xuICAgIHRoaXMudG90YWxXZWlnaHRfID0gdm9pZCAwO1xuICAgIHRoaXMuaGFsZkxpZmUgPSBoYWxmTGlmZTtcbiAgICAvLyBMYXJnZXIgdmFsdWVzIG9mIGFscGhhIGV4cGlyZSBoaXN0b3JpY2FsIGRhdGEgbW9yZSBzbG93bHkuXG4gICAgdGhpcy5hbHBoYV8gPSBoYWxmTGlmZSA/IE1hdGguZXhwKE1hdGgubG9nKDAuNSkgLyBoYWxmTGlmZSkgOiAwO1xuICAgIHRoaXMuZXN0aW1hdGVfID0gZXN0aW1hdGU7XG4gICAgdGhpcy50b3RhbFdlaWdodF8gPSB3ZWlnaHQ7XG4gIH1cbiAgc2FtcGxlKHdlaWdodCwgdmFsdWUpIHtcbiAgICBjb25zdCBhZGpBbHBoYSA9IE1hdGgucG93KHRoaXMuYWxwaGFfLCB3ZWlnaHQpO1xuICAgIHRoaXMuZXN0aW1hdGVfID0gdmFsdWUgKiAoMSAtIGFkakFscGhhKSArIGFkakFscGhhICogdGhpcy5lc3RpbWF0ZV87XG4gICAgdGhpcy50b3RhbFdlaWdodF8gKz0gd2VpZ2h0O1xuICB9XG4gIGdldFRvdGFsV2VpZ2h0KCkge1xuICAgIHJldHVybiB0aGlzLnRvdGFsV2VpZ2h0XztcbiAgfVxuICBnZXRFc3RpbWF0ZSgpIHtcbiAgICBpZiAodGhpcy5hbHBoYV8pIHtcbiAgICAgIGNvbnN0IHplcm9GYWN0b3IgPSAxIC0gTWF0aC5wb3codGhpcy5hbHBoYV8sIHRoaXMudG90YWxXZWlnaHRfKTtcbiAgICAgIGlmICh6ZXJvRmFjdG9yKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmVzdGltYXRlXyAvIHplcm9GYWN0b3I7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmVzdGltYXRlXztcbiAgfVxufVxuXG4vKlxuICogRVdNQSBCYW5kd2lkdGggRXN0aW1hdG9yXG4gKiAgLSBoZWF2aWx5IGluc3BpcmVkIGZyb20gc2hha2EtcGxheWVyXG4gKiBUcmFja3MgYmFuZHdpZHRoIHNhbXBsZXMgYW5kIGVzdGltYXRlcyBhdmFpbGFibGUgYmFuZHdpZHRoLlxuICogQmFzZWQgb24gdGhlIG1pbmltdW0gb2YgdHdvIGV4cG9uZW50aWFsbHktd2VpZ2h0ZWQgbW92aW5nIGF2ZXJhZ2VzIHdpdGhcbiAqIGRpZmZlcmVudCBoYWxmLWxpdmVzLlxuICovXG5cbmNsYXNzIEV3bWFCYW5kV2lkdGhFc3RpbWF0b3Ige1xuICBjb25zdHJ1Y3RvcihzbG93LCBmYXN0LCBkZWZhdWx0RXN0aW1hdGUsIGRlZmF1bHRUVEZCID0gMTAwKSB7XG4gICAgdGhpcy5kZWZhdWx0RXN0aW1hdGVfID0gdm9pZCAwO1xuICAgIHRoaXMubWluV2VpZ2h0XyA9IHZvaWQgMDtcbiAgICB0aGlzLm1pbkRlbGF5TXNfID0gdm9pZCAwO1xuICAgIHRoaXMuc2xvd18gPSB2b2lkIDA7XG4gICAgdGhpcy5mYXN0XyA9IHZvaWQgMDtcbiAgICB0aGlzLmRlZmF1bHRUVEZCXyA9IHZvaWQgMDtcbiAgICB0aGlzLnR0ZmJfID0gdm9pZCAwO1xuICAgIHRoaXMuZGVmYXVsdEVzdGltYXRlXyA9IGRlZmF1bHRFc3RpbWF0ZTtcbiAgICB0aGlzLm1pbldlaWdodF8gPSAwLjAwMTtcbiAgICB0aGlzLm1pbkRlbGF5TXNfID0gNTA7XG4gICAgdGhpcy5zbG93XyA9IG5ldyBFV01BKHNsb3cpO1xuICAgIHRoaXMuZmFzdF8gPSBuZXcgRVdNQShmYXN0KTtcbiAgICB0aGlzLmRlZmF1bHRUVEZCXyA9IGRlZmF1bHRUVEZCO1xuICAgIHRoaXMudHRmYl8gPSBuZXcgRVdNQShzbG93KTtcbiAgfVxuICB1cGRhdGUoc2xvdywgZmFzdCkge1xuICAgIGNvbnN0IHtcbiAgICAgIHNsb3dfLFxuICAgICAgZmFzdF8sXG4gICAgICB0dGZiX1xuICAgIH0gPSB0aGlzO1xuICAgIGlmIChzbG93Xy5oYWxmTGlmZSAhPT0gc2xvdykge1xuICAgICAgdGhpcy5zbG93XyA9IG5ldyBFV01BKHNsb3csIHNsb3dfLmdldEVzdGltYXRlKCksIHNsb3dfLmdldFRvdGFsV2VpZ2h0KCkpO1xuICAgIH1cbiAgICBpZiAoZmFzdF8uaGFsZkxpZmUgIT09IGZhc3QpIHtcbiAgICAgIHRoaXMuZmFzdF8gPSBuZXcgRVdNQShmYXN0LCBmYXN0Xy5nZXRFc3RpbWF0ZSgpLCBmYXN0Xy5nZXRUb3RhbFdlaWdodCgpKTtcbiAgICB9XG4gICAgaWYgKHR0ZmJfLmhhbGZMaWZlICE9PSBzbG93KSB7XG4gICAgICB0aGlzLnR0ZmJfID0gbmV3IEVXTUEoc2xvdywgdHRmYl8uZ2V0RXN0aW1hdGUoKSwgdHRmYl8uZ2V0VG90YWxXZWlnaHQoKSk7XG4gICAgfVxuICB9XG4gIHNhbXBsZShkdXJhdGlvbk1zLCBudW1CeXRlcykge1xuICAgIGR1cmF0aW9uTXMgPSBNYXRoLm1heChkdXJhdGlvbk1zLCB0aGlzLm1pbkRlbGF5TXNfKTtcbiAgICBjb25zdCBudW1CaXRzID0gOCAqIG51bUJ5dGVzO1xuICAgIC8vIHdlaWdodCBpcyBkdXJhdGlvbiBpbiBzZWNvbmRzXG4gICAgY29uc3QgZHVyYXRpb25TID0gZHVyYXRpb25NcyAvIDEwMDA7XG4gICAgLy8gdmFsdWUgaXMgYmFuZHdpZHRoIGluIGJpdHMvc1xuICAgIGNvbnN0IGJhbmR3aWR0aEluQnBzID0gbnVtQml0cyAvIGR1cmF0aW9uUztcbiAgICB0aGlzLmZhc3RfLnNhbXBsZShkdXJhdGlvblMsIGJhbmR3aWR0aEluQnBzKTtcbiAgICB0aGlzLnNsb3dfLnNhbXBsZShkdXJhdGlvblMsIGJhbmR3aWR0aEluQnBzKTtcbiAgfVxuICBzYW1wbGVUVEZCKHR0ZmIpIHtcbiAgICAvLyB3ZWlnaHQgaXMgZnJlcXVlbmN5IGN1cnZlIGFwcGxpZWQgdG8gVFRGQiBpbiBzZWNvbmRzXG4gICAgLy8gKGxvbmdlciB0aW1lcyBoYXZlIGxlc3Mgd2VpZ2h0IHdpdGggZXhwZWN0ZWQgaW5wdXQgdW5kZXIgMSBzZWNvbmQpXG4gICAgY29uc3Qgc2Vjb25kcyA9IHR0ZmIgLyAxMDAwO1xuICAgIGNvbnN0IHdlaWdodCA9IE1hdGguc3FydCgyKSAqIE1hdGguZXhwKC1NYXRoLnBvdyhzZWNvbmRzLCAyKSAvIDIpO1xuICAgIHRoaXMudHRmYl8uc2FtcGxlKHdlaWdodCwgTWF0aC5tYXgodHRmYiwgNSkpO1xuICB9XG4gIGNhbkVzdGltYXRlKCkge1xuICAgIHJldHVybiB0aGlzLmZhc3RfLmdldFRvdGFsV2VpZ2h0KCkgPj0gdGhpcy5taW5XZWlnaHRfO1xuICB9XG4gIGdldEVzdGltYXRlKCkge1xuICAgIGlmICh0aGlzLmNhbkVzdGltYXRlKCkpIHtcbiAgICAgIC8vIGNvbnNvbGUubG9nKCdzbG93IGVzdGltYXRlOicrIE1hdGgucm91bmQodGhpcy5zbG93Xy5nZXRFc3RpbWF0ZSgpKSk7XG4gICAgICAvLyBjb25zb2xlLmxvZygnZmFzdCBlc3RpbWF0ZTonKyBNYXRoLnJvdW5kKHRoaXMuZmFzdF8uZ2V0RXN0aW1hdGUoKSkpO1xuICAgICAgLy8gVGFrZSB0aGUgbWluaW11bSBvZiB0aGVzZSB0d28gZXN0aW1hdGVzLiAgVGhpcyBzaG91bGQgaGF2ZSB0aGUgZWZmZWN0IG9mXG4gICAgICAvLyBhZGFwdGluZyBkb3duIHF1aWNrbHksIGJ1dCB1cCBtb3JlIHNsb3dseS5cbiAgICAgIHJldHVybiBNYXRoLm1pbih0aGlzLmZhc3RfLmdldEVzdGltYXRlKCksIHRoaXMuc2xvd18uZ2V0RXN0aW1hdGUoKSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJldHVybiB0aGlzLmRlZmF1bHRFc3RpbWF0ZV87XG4gICAgfVxuICB9XG4gIGdldEVzdGltYXRlVFRGQigpIHtcbiAgICBpZiAodGhpcy50dGZiXy5nZXRUb3RhbFdlaWdodCgpID49IHRoaXMubWluV2VpZ2h0Xykge1xuICAgICAgcmV0dXJuIHRoaXMudHRmYl8uZ2V0RXN0aW1hdGUoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHRoaXMuZGVmYXVsdFRURkJfO1xuICAgIH1cbiAgfVxuICBkZXN0cm95KCkge31cbn1cblxuY29uc3QgU1VQUE9SVEVEX0lORk9fREVGQVVMVCA9IHtcbiAgc3VwcG9ydGVkOiB0cnVlLFxuICBjb25maWd1cmF0aW9uczogW10sXG4gIGRlY29kaW5nSW5mb1Jlc3VsdHM6IFt7XG4gICAgc3VwcG9ydGVkOiB0cnVlLFxuICAgIHBvd2VyRWZmaWNpZW50OiB0cnVlLFxuICAgIHNtb290aDogdHJ1ZVxuICB9XVxufTtcbmNvbnN0IFNVUFBPUlRFRF9JTkZPX0NBQ0hFID0ge307XG5mdW5jdGlvbiByZXF1aXJlc01lZGlhQ2FwYWJpbGl0aWVzRGVjb2RpbmdJbmZvKGxldmVsLCBhdWRpb1RyYWNrc0J5R3JvdXAsIGN1cnJlbnRWaWRlb1JhbmdlLCBjdXJyZW50RnJhbWVSYXRlLCBjdXJyZW50QncsIGF1ZGlvUHJlZmVyZW5jZSkge1xuICAvLyBPbmx5IHRlc3Qgc3VwcG9ydCB3aGVuIGNvbmZpZ3VyYXRpb24gaXMgZXhjZWVkcyBtaW5pbXVtIG9wdGlvbnNcbiAgY29uc3QgYXVkaW9Hcm91cHMgPSBsZXZlbC5hdWRpb0NvZGVjID8gbGV2ZWwuYXVkaW9Hcm91cHMgOiBudWxsO1xuICBjb25zdCBhdWRpb0NvZGVjUHJlZmVyZW5jZSA9IGF1ZGlvUHJlZmVyZW5jZSA9PSBudWxsID8gdm9pZCAwIDogYXVkaW9QcmVmZXJlbmNlLmF1ZGlvQ29kZWM7XG4gIGNvbnN0IGNoYW5uZWxzUHJlZmVyZW5jZSA9IGF1ZGlvUHJlZmVyZW5jZSA9PSBudWxsID8gdm9pZCAwIDogYXVkaW9QcmVmZXJlbmNlLmNoYW5uZWxzO1xuICBjb25zdCBtYXhDaGFubmVscyA9IGNoYW5uZWxzUHJlZmVyZW5jZSA/IHBhcnNlSW50KGNoYW5uZWxzUHJlZmVyZW5jZSkgOiBhdWRpb0NvZGVjUHJlZmVyZW5jZSA/IEluZmluaXR5IDogMjtcbiAgbGV0IGF1ZGlvQ2hhbm5lbHMgPSBudWxsO1xuICBpZiAoYXVkaW9Hcm91cHMgIT0gbnVsbCAmJiBhdWRpb0dyb3Vwcy5sZW5ndGgpIHtcbiAgICB0cnkge1xuICAgICAgaWYgKGF1ZGlvR3JvdXBzLmxlbmd0aCA9PT0gMSAmJiBhdWRpb0dyb3Vwc1swXSkge1xuICAgICAgICBhdWRpb0NoYW5uZWxzID0gYXVkaW9UcmFja3NCeUdyb3VwLmdyb3Vwc1thdWRpb0dyb3Vwc1swXV0uY2hhbm5lbHM7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhdWRpb0NoYW5uZWxzID0gYXVkaW9Hcm91cHMucmVkdWNlKChhY2MsIGdyb3VwSWQpID0+IHtcbiAgICAgICAgICBpZiAoZ3JvdXBJZCkge1xuICAgICAgICAgICAgY29uc3QgYXVkaW9UcmFja0dyb3VwID0gYXVkaW9UcmFja3NCeUdyb3VwLmdyb3Vwc1tncm91cElkXTtcbiAgICAgICAgICAgIGlmICghYXVkaW9UcmFja0dyb3VwKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihgQXVkaW8gdHJhY2sgZ3JvdXAgJHtncm91cElkfSBub3QgZm91bmRgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIFN1bSBhbGwgY2hhbm5lbCBrZXkgdmFsdWVzXG4gICAgICAgICAgICBPYmplY3Qua2V5cyhhdWRpb1RyYWNrR3JvdXAuY2hhbm5lbHMpLmZvckVhY2goa2V5ID0+IHtcbiAgICAgICAgICAgICAgYWNjW2tleV0gPSAoYWNjW2tleV0gfHwgMCkgKyBhdWRpb1RyYWNrR3JvdXAuY2hhbm5lbHNba2V5XTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gYWNjO1xuICAgICAgICB9LCB7XG4gICAgICAgICAgMjogMFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICB9XG4gIHJldHVybiBsZXZlbC52aWRlb0NvZGVjICE9PSB1bmRlZmluZWQgJiYgKGxldmVsLndpZHRoID4gMTkyMCAmJiBsZXZlbC5oZWlnaHQgPiAxMDg4IHx8IGxldmVsLmhlaWdodCA+IDE5MjAgJiYgbGV2ZWwud2lkdGggPiAxMDg4IHx8IGxldmVsLmZyYW1lUmF0ZSA+IE1hdGgubWF4KGN1cnJlbnRGcmFtZVJhdGUsIDMwKSB8fCBsZXZlbC52aWRlb1JhbmdlICE9PSAnU0RSJyAmJiBsZXZlbC52aWRlb1JhbmdlICE9PSBjdXJyZW50VmlkZW9SYW5nZSB8fCBsZXZlbC5iaXRyYXRlID4gTWF0aC5tYXgoY3VycmVudEJ3LCA4ZTYpKSB8fCAhIWF1ZGlvQ2hhbm5lbHMgJiYgaXNGaW5pdGVOdW1iZXIobWF4Q2hhbm5lbHMpICYmIE9iamVjdC5rZXlzKGF1ZGlvQ2hhbm5lbHMpLnNvbWUoY2hhbm5lbHMgPT4gcGFyc2VJbnQoY2hhbm5lbHMpID4gbWF4Q2hhbm5lbHMpO1xufVxuZnVuY3Rpb24gZ2V0TWVkaWFEZWNvZGluZ0luZm9Qcm9taXNlKGxldmVsLCBhdWRpb1RyYWNrc0J5R3JvdXAsIG1lZGlhQ2FwYWJpbGl0aWVzKSB7XG4gIGNvbnN0IHZpZGVvQ29kZWNzID0gbGV2ZWwudmlkZW9Db2RlYztcbiAgY29uc3QgYXVkaW9Db2RlY3MgPSBsZXZlbC5hdWRpb0NvZGVjO1xuICBpZiAoIXZpZGVvQ29kZWNzIHx8ICFhdWRpb0NvZGVjcyB8fCAhbWVkaWFDYXBhYmlsaXRpZXMpIHtcbiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKFNVUFBPUlRFRF9JTkZPX0RFRkFVTFQpO1xuICB9XG4gIGNvbnN0IGJhc2VWaWRlb0NvbmZpZ3VyYXRpb24gPSB7XG4gICAgd2lkdGg6IGxldmVsLndpZHRoLFxuICAgIGhlaWdodDogbGV2ZWwuaGVpZ2h0LFxuICAgIGJpdHJhdGU6IE1hdGguY2VpbChNYXRoLm1heChsZXZlbC5iaXRyYXRlICogMC45LCBsZXZlbC5hdmVyYWdlQml0cmF0ZSkpLFxuICAgIC8vIEFzc3VtZSBhIGZyYW1lcmF0ZSBvZiAzMGZwcyBzaW5jZSBNZWRpYUNhcGFiaWxpdGllcyB3aWxsIG5vdCBhY2NlcHQgTGV2ZWwgZGVmYXVsdCBvZiAwLlxuICAgIGZyYW1lcmF0ZTogbGV2ZWwuZnJhbWVSYXRlIHx8IDMwXG4gIH07XG4gIGNvbnN0IHZpZGVvUmFuZ2UgPSBsZXZlbC52aWRlb1JhbmdlO1xuICBpZiAodmlkZW9SYW5nZSAhPT0gJ1NEUicpIHtcbiAgICBiYXNlVmlkZW9Db25maWd1cmF0aW9uLnRyYW5zZmVyRnVuY3Rpb24gPSB2aWRlb1JhbmdlLnRvTG93ZXJDYXNlKCk7XG4gIH1cbiAgY29uc3QgY29uZmlndXJhdGlvbnMgPSB2aWRlb0NvZGVjcy5zcGxpdCgnLCcpLm1hcCh2aWRlb0NvZGVjID0+ICh7XG4gICAgdHlwZTogJ21lZGlhLXNvdXJjZScsXG4gICAgdmlkZW86IF9vYmplY3RTcHJlYWQyKF9vYmplY3RTcHJlYWQyKHt9LCBiYXNlVmlkZW9Db25maWd1cmF0aW9uKSwge30sIHtcbiAgICAgIGNvbnRlbnRUeXBlOiBtaW1lVHlwZUZvckNvZGVjKHZpZGVvQ29kZWMsICd2aWRlbycpXG4gICAgfSlcbiAgfSkpO1xuICBpZiAoYXVkaW9Db2RlY3MgJiYgbGV2ZWwuYXVkaW9Hcm91cHMpIHtcbiAgICBsZXZlbC5hdWRpb0dyb3Vwcy5mb3JFYWNoKGF1ZGlvR3JvdXBJZCA9PiB7XG4gICAgICB2YXIgX2F1ZGlvVHJhY2tzQnlHcm91cCRnO1xuICAgICAgaWYgKCFhdWRpb0dyb3VwSWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgKF9hdWRpb1RyYWNrc0J5R3JvdXAkZyA9IGF1ZGlvVHJhY2tzQnlHcm91cC5ncm91cHNbYXVkaW9Hcm91cElkXSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9hdWRpb1RyYWNrc0J5R3JvdXAkZy50cmFja3MuZm9yRWFjaChhdWRpb1RyYWNrID0+IHtcbiAgICAgICAgaWYgKGF1ZGlvVHJhY2suZ3JvdXBJZCA9PT0gYXVkaW9Hcm91cElkKSB7XG4gICAgICAgICAgY29uc3QgY2hhbm5lbHMgPSBhdWRpb1RyYWNrLmNoYW5uZWxzIHx8ICcnO1xuICAgICAgICAgIGNvbnN0IGNoYW5uZWxzTnVtYmVyID0gcGFyc2VGbG9hdChjaGFubmVscyk7XG4gICAgICAgICAgaWYgKGlzRmluaXRlTnVtYmVyKGNoYW5uZWxzTnVtYmVyKSAmJiBjaGFubmVsc051bWJlciA+IDIpIHtcbiAgICAgICAgICAgIGNvbmZpZ3VyYXRpb25zLnB1c2guYXBwbHkoY29uZmlndXJhdGlvbnMsIGF1ZGlvQ29kZWNzLnNwbGl0KCcsJykubWFwKGF1ZGlvQ29kZWMgPT4gKHtcbiAgICAgICAgICAgICAgdHlwZTogJ21lZGlhLXNvdXJjZScsXG4gICAgICAgICAgICAgIGF1ZGlvOiB7XG4gICAgICAgICAgICAgICAgY29udGVudFR5cGU6IG1pbWVUeXBlRm9yQ29kZWMoYXVkaW9Db2RlYywgJ2F1ZGlvJyksXG4gICAgICAgICAgICAgICAgY2hhbm5lbHM6ICcnICsgY2hhbm5lbHNOdW1iZXJcbiAgICAgICAgICAgICAgICAvLyBzcGF0aWFsUmVuZGVyaW5nOlxuICAgICAgICAgICAgICAgIC8vICAgYXVkaW9Db2RlYyA9PT0gJ2VjLTMnICYmIGNoYW5uZWxzLmluZGV4T2YoJ0pPQycpLFxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KSkpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cbiAgcmV0dXJuIFByb21pc2UuYWxsKGNvbmZpZ3VyYXRpb25zLm1hcChjb25maWd1cmF0aW9uID0+IHtcbiAgICAvLyBDYWNoZSBNZWRpYUNhcGFiaWxpdGllcyBwcm9taXNlc1xuICAgIGNvbnN0IGRlY29kaW5nSW5mb0tleSA9IGdldE1lZGlhRGVjb2RpbmdJbmZvS2V5KGNvbmZpZ3VyYXRpb24pO1xuICAgIHJldHVybiBTVVBQT1JURURfSU5GT19DQUNIRVtkZWNvZGluZ0luZm9LZXldIHx8IChTVVBQT1JURURfSU5GT19DQUNIRVtkZWNvZGluZ0luZm9LZXldID0gbWVkaWFDYXBhYmlsaXRpZXMuZGVjb2RpbmdJbmZvKGNvbmZpZ3VyYXRpb24pKTtcbiAgfSkpLnRoZW4oZGVjb2RpbmdJbmZvUmVzdWx0cyA9PiAoe1xuICAgIHN1cHBvcnRlZDogIWRlY29kaW5nSW5mb1Jlc3VsdHMuc29tZShpbmZvID0+ICFpbmZvLnN1cHBvcnRlZCksXG4gICAgY29uZmlndXJhdGlvbnMsXG4gICAgZGVjb2RpbmdJbmZvUmVzdWx0c1xuICB9KSkuY2F0Y2goZXJyb3IgPT4gKHtcbiAgICBzdXBwb3J0ZWQ6IGZhbHNlLFxuICAgIGNvbmZpZ3VyYXRpb25zLFxuICAgIGRlY29kaW5nSW5mb1Jlc3VsdHM6IFtdLFxuICAgIGVycm9yXG4gIH0pKTtcbn1cbmZ1bmN0aW9uIGdldE1lZGlhRGVjb2RpbmdJbmZvS2V5KGNvbmZpZykge1xuICBjb25zdCB7XG4gICAgYXVkaW8sXG4gICAgdmlkZW9cbiAgfSA9IGNvbmZpZztcbiAgY29uc3QgbWVkaWFDb25maWcgPSB2aWRlbyB8fCBhdWRpbztcbiAgaWYgKG1lZGlhQ29uZmlnKSB7XG4gICAgY29uc3QgY29kZWMgPSBtZWRpYUNvbmZpZy5jb250ZW50VHlwZS5zcGxpdCgnXCInKVsxXTtcbiAgICBpZiAodmlkZW8pIHtcbiAgICAgIHJldHVybiBgciR7dmlkZW8uaGVpZ2h0fXgke3ZpZGVvLndpZHRofWYke01hdGguY2VpbCh2aWRlby5mcmFtZXJhdGUpfSR7dmlkZW8udHJhbnNmZXJGdW5jdGlvbiB8fCAnc2QnfV8ke2NvZGVjfV8ke01hdGguY2VpbCh2aWRlby5iaXRyYXRlIC8gMWU1KX1gO1xuICAgIH1cbiAgICBpZiAoYXVkaW8pIHtcbiAgICAgIHJldHVybiBgYyR7YXVkaW8uY2hhbm5lbHN9JHthdWRpby5zcGF0aWFsUmVuZGVyaW5nID8gJ3MnIDogJ24nfV8ke2NvZGVjfWA7XG4gICAgfVxuICB9XG4gIHJldHVybiAnJztcbn1cblxuLyoqXG4gKiBAcmV0dXJucyBXaGV0aGVyIHdlIGNhbiBkZXRlY3QgYW5kIHZhbGlkYXRlIEhEUiBjYXBhYmlsaXR5IHdpdGhpbiB0aGUgd2luZG93IGNvbnRleHRcbiAqL1xuZnVuY3Rpb24gaXNIZHJTdXBwb3J0ZWQoKSB7XG4gIGlmICh0eXBlb2YgbWF0Y2hNZWRpYSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgIGNvbnN0IG1lZGlhUXVlcnlMaXN0ID0gbWF0Y2hNZWRpYSgnKGR5bmFtaWMtcmFuZ2U6IGhpZ2gpJyk7XG4gICAgY29uc3QgYmFkUXVlcnkgPSBtYXRjaE1lZGlhKCdiYWQgcXVlcnknKTtcbiAgICBpZiAobWVkaWFRdWVyeUxpc3QubWVkaWEgIT09IGJhZFF1ZXJ5Lm1lZGlhKSB7XG4gICAgICByZXR1cm4gbWVkaWFRdWVyeUxpc3QubWF0Y2hlcyA9PT0gdHJ1ZTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG4vKipcbiAqIFNhbml0aXplcyBpbnB1dHMgdG8gcmV0dXJuIHRoZSBhY3RpdmUgdmlkZW8gc2VsZWN0aW9uIG9wdGlvbnMgZm9yIEhEUi9TRFIuXG4gKiBXaGVuIGJvdGggaW5wdXRzIGFyZSBudWxsOlxuICpcbiAqICAgIGB7IHByZWZlckhEUjogZmFsc2UsIGFsbG93ZWRWaWRlb1JhbmdlczogW10gfWBcbiAqXG4gKiBXaGVuIGBjdXJyZW50VmlkZW9SYW5nZWAgbm9uLW51bGwsIG1haW50YWluIHRoZSBhY3RpdmUgcmFuZ2U6XG4gKlxuICogICAgYHsgcHJlZmVySERSOiBjdXJyZW50VmlkZW9SYW5nZSAhPT0gJ1NEUicsIGFsbG93ZWRWaWRlb1JhbmdlczogW2N1cnJlbnRWaWRlb1JhbmdlXSB9YFxuICpcbiAqIFdoZW4gVmlkZW9TZWxlY3Rpb25PcHRpb24gbm9uLW51bGw6XG4gKlxuICogIC0gQWxsb3cgYWxsIHZpZGVvIHJhbmdlcyBpZiBgYWxsb3dlZFZpZGVvUmFuZ2VzYCB1bnNwZWNpZmllZC5cbiAqICAtIElmIGBwcmVmZXJIRFJgIGlzIG5vbi1udWxsIHVzZSB0aGUgdmFsdWUgdG8gZmlsdGVyIGBhbGxvd2VkVmlkZW9SYW5nZXNgLlxuICogIC0gRWxzZSBjaGVjayB3aW5kb3cgZm9yIEhEUiBzdXBwb3J0IGFuZCBzZXQgYHByZWZlckhEUmAgdG8gdGhlIHJlc3VsdC5cbiAqXG4gKiBAcGFyYW0gY3VycmVudFZpZGVvUmFuZ2VcbiAqIEBwYXJhbSB2aWRlb1ByZWZlcmVuY2VcbiAqL1xuZnVuY3Rpb24gZ2V0VmlkZW9TZWxlY3Rpb25PcHRpb25zKGN1cnJlbnRWaWRlb1JhbmdlLCB2aWRlb1ByZWZlcmVuY2UpIHtcbiAgbGV0IHByZWZlckhEUiA9IGZhbHNlO1xuICBsZXQgYWxsb3dlZFZpZGVvUmFuZ2VzID0gW107XG4gIGlmIChjdXJyZW50VmlkZW9SYW5nZSkge1xuICAgIHByZWZlckhEUiA9IGN1cnJlbnRWaWRlb1JhbmdlICE9PSAnU0RSJztcbiAgICBhbGxvd2VkVmlkZW9SYW5nZXMgPSBbY3VycmVudFZpZGVvUmFuZ2VdO1xuICB9XG4gIGlmICh2aWRlb1ByZWZlcmVuY2UpIHtcbiAgICBhbGxvd2VkVmlkZW9SYW5nZXMgPSB2aWRlb1ByZWZlcmVuY2UuYWxsb3dlZFZpZGVvUmFuZ2VzIHx8IFZpZGVvUmFuZ2VWYWx1ZXMuc2xpY2UoMCk7XG4gICAgcHJlZmVySERSID0gdmlkZW9QcmVmZXJlbmNlLnByZWZlckhEUiAhPT0gdW5kZWZpbmVkID8gdmlkZW9QcmVmZXJlbmNlLnByZWZlckhEUiA6IGlzSGRyU3VwcG9ydGVkKCk7XG4gICAgaWYgKHByZWZlckhEUikge1xuICAgICAgYWxsb3dlZFZpZGVvUmFuZ2VzID0gYWxsb3dlZFZpZGVvUmFuZ2VzLmZpbHRlcihyYW5nZSA9PiByYW5nZSAhPT0gJ1NEUicpO1xuICAgIH0gZWxzZSB7XG4gICAgICBhbGxvd2VkVmlkZW9SYW5nZXMgPSBbJ1NEUiddO1xuICAgIH1cbiAgfVxuICByZXR1cm4ge1xuICAgIHByZWZlckhEUixcbiAgICBhbGxvd2VkVmlkZW9SYW5nZXNcbiAgfTtcbn1cblxuZnVuY3Rpb24gZ2V0U3RhcnRDb2RlY1RpZXIoY29kZWNUaWVycywgY3VycmVudFZpZGVvUmFuZ2UsIGN1cnJlbnRCdywgYXVkaW9QcmVmZXJlbmNlLCB2aWRlb1ByZWZlcmVuY2UpIHtcbiAgY29uc3QgY29kZWNTZXRzID0gT2JqZWN0LmtleXMoY29kZWNUaWVycyk7XG4gIGNvbnN0IGNoYW5uZWxzUHJlZmVyZW5jZSA9IGF1ZGlvUHJlZmVyZW5jZSA9PSBudWxsID8gdm9pZCAwIDogYXVkaW9QcmVmZXJlbmNlLmNoYW5uZWxzO1xuICBjb25zdCBhdWRpb0NvZGVjUHJlZmVyZW5jZSA9IGF1ZGlvUHJlZmVyZW5jZSA9PSBudWxsID8gdm9pZCAwIDogYXVkaW9QcmVmZXJlbmNlLmF1ZGlvQ29kZWM7XG4gIGNvbnN0IHByZWZlclN0ZXJlbyA9IGNoYW5uZWxzUHJlZmVyZW5jZSAmJiBwYXJzZUludChjaGFubmVsc1ByZWZlcmVuY2UpID09PSAyO1xuICAvLyBVc2UgZmlyc3QgbGV2ZWwgc2V0IHRvIGRldGVybWluZSBzdGVyZW8sIGFuZCBtaW5pbXVtIHJlc29sdXRpb24gYW5kIGZyYW1lcmF0ZVxuICBsZXQgaGFzU3RlcmVvID0gdHJ1ZTtcbiAgbGV0IGhhc0N1cnJlbnRWaWRlb1JhbmdlID0gZmFsc2U7XG4gIGxldCBtaW5IZWlnaHQgPSBJbmZpbml0eTtcbiAgbGV0IG1pbkZyYW1lcmF0ZSA9IEluZmluaXR5O1xuICBsZXQgbWluQml0cmF0ZSA9IEluZmluaXR5O1xuICBsZXQgc2VsZWN0ZWRTY29yZSA9IDA7XG4gIGxldCB2aWRlb1JhbmdlcyA9IFtdO1xuICBjb25zdCB7XG4gICAgcHJlZmVySERSLFxuICAgIGFsbG93ZWRWaWRlb1Jhbmdlc1xuICB9ID0gZ2V0VmlkZW9TZWxlY3Rpb25PcHRpb25zKGN1cnJlbnRWaWRlb1JhbmdlLCB2aWRlb1ByZWZlcmVuY2UpO1xuICBmb3IgKGxldCBpID0gY29kZWNTZXRzLmxlbmd0aDsgaS0tOykge1xuICAgIGNvbnN0IHRpZXIgPSBjb2RlY1RpZXJzW2NvZGVjU2V0c1tpXV07XG4gICAgaGFzU3RlcmVvID0gdGllci5jaGFubmVsc1syXSA+IDA7XG4gICAgbWluSGVpZ2h0ID0gTWF0aC5taW4obWluSGVpZ2h0LCB0aWVyLm1pbkhlaWdodCk7XG4gICAgbWluRnJhbWVyYXRlID0gTWF0aC5taW4obWluRnJhbWVyYXRlLCB0aWVyLm1pbkZyYW1lcmF0ZSk7XG4gICAgbWluQml0cmF0ZSA9IE1hdGgubWluKG1pbkJpdHJhdGUsIHRpZXIubWluQml0cmF0ZSk7XG4gICAgY29uc3QgbWF0Y2hpbmdWaWRlb1JhbmdlcyA9IGFsbG93ZWRWaWRlb1Jhbmdlcy5maWx0ZXIocmFuZ2UgPT4gdGllci52aWRlb1Jhbmdlc1tyYW5nZV0gPiAwKTtcbiAgICBpZiAobWF0Y2hpbmdWaWRlb1Jhbmdlcy5sZW5ndGggPiAwKSB7XG4gICAgICBoYXNDdXJyZW50VmlkZW9SYW5nZSA9IHRydWU7XG4gICAgICB2aWRlb1JhbmdlcyA9IG1hdGNoaW5nVmlkZW9SYW5nZXM7XG4gICAgfVxuICB9XG4gIG1pbkhlaWdodCA9IGlzRmluaXRlTnVtYmVyKG1pbkhlaWdodCkgPyBtaW5IZWlnaHQgOiAwO1xuICBtaW5GcmFtZXJhdGUgPSBpc0Zpbml0ZU51bWJlcihtaW5GcmFtZXJhdGUpID8gbWluRnJhbWVyYXRlIDogMDtcbiAgY29uc3QgbWF4SGVpZ2h0ID0gTWF0aC5tYXgoMTA4MCwgbWluSGVpZ2h0KTtcbiAgY29uc3QgbWF4RnJhbWVyYXRlID0gTWF0aC5tYXgoMzAsIG1pbkZyYW1lcmF0ZSk7XG4gIG1pbkJpdHJhdGUgPSBpc0Zpbml0ZU51bWJlcihtaW5CaXRyYXRlKSA/IG1pbkJpdHJhdGUgOiBjdXJyZW50Qnc7XG4gIGN1cnJlbnRCdyA9IE1hdGgubWF4KG1pbkJpdHJhdGUsIGN1cnJlbnRCdyk7XG4gIC8vIElmIHRoZXJlIGFyZSBubyB2YXJpYW50cyB3aXRoIG1hdGNoaW5nIHByZWZlcmVuY2UsIHNldCBjdXJyZW50VmlkZW9SYW5nZSB0byB1bmRlZmluZWRcbiAgaWYgKCFoYXNDdXJyZW50VmlkZW9SYW5nZSkge1xuICAgIGN1cnJlbnRWaWRlb1JhbmdlID0gdW5kZWZpbmVkO1xuICAgIHZpZGVvUmFuZ2VzID0gW107XG4gIH1cbiAgY29uc3QgY29kZWNTZXQgPSBjb2RlY1NldHMucmVkdWNlKChzZWxlY3RlZCwgY2FuZGlkYXRlKSA9PiB7XG4gICAgLy8gUmVtb3ZlIGNhbmRpYXRlcyB3aGljaCBkbyBub3QgbWVldCBiaXRyYXRlLCBkZWZhdWx0IGF1ZGlvLCBzdGVyZW8gb3IgY2hhbm5lbHMgcHJlZmVyZW5jZSwgMTA4MHAgb3IgbG93ZXIsIDMwZnBzIG9yIGxvd2VyLCBvciBTRFIvSERSIHNlbGVjdGlvbiBpZiBwcmVzZW50XG4gICAgY29uc3QgY2FuZGlkYXRlVGllciA9IGNvZGVjVGllcnNbY2FuZGlkYXRlXTtcbiAgICBpZiAoY2FuZGlkYXRlID09PSBzZWxlY3RlZCkge1xuICAgICAgcmV0dXJuIHNlbGVjdGVkO1xuICAgIH1cbiAgICBpZiAoY2FuZGlkYXRlVGllci5taW5CaXRyYXRlID4gY3VycmVudEJ3KSB7XG4gICAgICBsb2dTdGFydENvZGVjQ2FuZGlkYXRlSWdub3JlZChjYW5kaWRhdGUsIGBtaW4gYml0cmF0ZSBvZiAke2NhbmRpZGF0ZVRpZXIubWluQml0cmF0ZX0gPiBjdXJyZW50IGVzdGltYXRlIG9mICR7Y3VycmVudEJ3fWApO1xuICAgICAgcmV0dXJuIHNlbGVjdGVkO1xuICAgIH1cbiAgICBpZiAoIWNhbmRpZGF0ZVRpZXIuaGFzRGVmYXVsdEF1ZGlvKSB7XG4gICAgICBsb2dTdGFydENvZGVjQ2FuZGlkYXRlSWdub3JlZChjYW5kaWRhdGUsIGBubyByZW5kaXRpb25zIHdpdGggZGVmYXVsdCBvciBhdXRvLXNlbGVjdCBzb3VuZCBmb3VuZGApO1xuICAgICAgcmV0dXJuIHNlbGVjdGVkO1xuICAgIH1cbiAgICBpZiAoYXVkaW9Db2RlY1ByZWZlcmVuY2UgJiYgY2FuZGlkYXRlLmluZGV4T2YoYXVkaW9Db2RlY1ByZWZlcmVuY2Uuc3Vic3RyaW5nKDAsIDQpKSAlIDUgIT09IDApIHtcbiAgICAgIGxvZ1N0YXJ0Q29kZWNDYW5kaWRhdGVJZ25vcmVkKGNhbmRpZGF0ZSwgYGF1ZGlvIGNvZGVjIHByZWZlcmVuY2UgXCIke2F1ZGlvQ29kZWNQcmVmZXJlbmNlfVwiIG5vdCBmb3VuZGApO1xuICAgICAgcmV0dXJuIHNlbGVjdGVkO1xuICAgIH1cbiAgICBpZiAoY2hhbm5lbHNQcmVmZXJlbmNlICYmICFwcmVmZXJTdGVyZW8pIHtcbiAgICAgIGlmICghY2FuZGlkYXRlVGllci5jaGFubmVsc1tjaGFubmVsc1ByZWZlcmVuY2VdKSB7XG4gICAgICAgIGxvZ1N0YXJ0Q29kZWNDYW5kaWRhdGVJZ25vcmVkKGNhbmRpZGF0ZSwgYG5vIHJlbmRpdGlvbnMgd2l0aCAke2NoYW5uZWxzUHJlZmVyZW5jZX0gY2hhbm5lbCBzb3VuZCBmb3VuZCAoY2hhbm5lbHMgb3B0aW9uczogJHtPYmplY3Qua2V5cyhjYW5kaWRhdGVUaWVyLmNoYW5uZWxzKX0pYCk7XG4gICAgICAgIHJldHVybiBzZWxlY3RlZDtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKCghYXVkaW9Db2RlY1ByZWZlcmVuY2UgfHwgcHJlZmVyU3RlcmVvKSAmJiBoYXNTdGVyZW8gJiYgY2FuZGlkYXRlVGllci5jaGFubmVsc1snMiddID09PSAwKSB7XG4gICAgICBsb2dTdGFydENvZGVjQ2FuZGlkYXRlSWdub3JlZChjYW5kaWRhdGUsIGBubyByZW5kaXRpb25zIHdpdGggc3RlcmVvIHNvdW5kIGZvdW5kYCk7XG4gICAgICByZXR1cm4gc2VsZWN0ZWQ7XG4gICAgfVxuICAgIGlmIChjYW5kaWRhdGVUaWVyLm1pbkhlaWdodCA+IG1heEhlaWdodCkge1xuICAgICAgbG9nU3RhcnRDb2RlY0NhbmRpZGF0ZUlnbm9yZWQoY2FuZGlkYXRlLCBgbWluIHJlc29sdXRpb24gb2YgJHtjYW5kaWRhdGVUaWVyLm1pbkhlaWdodH0gPiBtYXhpbXVtIG9mICR7bWF4SGVpZ2h0fWApO1xuICAgICAgcmV0dXJuIHNlbGVjdGVkO1xuICAgIH1cbiAgICBpZiAoY2FuZGlkYXRlVGllci5taW5GcmFtZXJhdGUgPiBtYXhGcmFtZXJhdGUpIHtcbiAgICAgIGxvZ1N0YXJ0Q29kZWNDYW5kaWRhdGVJZ25vcmVkKGNhbmRpZGF0ZSwgYG1pbiBmcmFtZXJhdGUgb2YgJHtjYW5kaWRhdGVUaWVyLm1pbkZyYW1lcmF0ZX0gPiBtYXhpbXVtIG9mICR7bWF4RnJhbWVyYXRlfWApO1xuICAgICAgcmV0dXJuIHNlbGVjdGVkO1xuICAgIH1cbiAgICBpZiAoIXZpZGVvUmFuZ2VzLnNvbWUocmFuZ2UgPT4gY2FuZGlkYXRlVGllci52aWRlb1Jhbmdlc1tyYW5nZV0gPiAwKSkge1xuICAgICAgbG9nU3RhcnRDb2RlY0NhbmRpZGF0ZUlnbm9yZWQoY2FuZGlkYXRlLCBgbm8gdmFyaWFudHMgd2l0aCBWSURFTy1SQU5HRSBvZiAke0pTT04uc3RyaW5naWZ5KHZpZGVvUmFuZ2VzKX0gZm91bmRgKTtcbiAgICAgIHJldHVybiBzZWxlY3RlZDtcbiAgICB9XG4gICAgaWYgKGNhbmRpZGF0ZVRpZXIubWF4U2NvcmUgPCBzZWxlY3RlZFNjb3JlKSB7XG4gICAgICBsb2dTdGFydENvZGVjQ2FuZGlkYXRlSWdub3JlZChjYW5kaWRhdGUsIGBtYXggc2NvcmUgb2YgJHtjYW5kaWRhdGVUaWVyLm1heFNjb3JlfSA8IHNlbGVjdGVkIG1heCBvZiAke3NlbGVjdGVkU2NvcmV9YCk7XG4gICAgICByZXR1cm4gc2VsZWN0ZWQ7XG4gICAgfVxuICAgIC8vIFJlbW92ZSBjYW5kaWF0ZXMgd2l0aCBsZXNzIHByZWZlcnJlZCBjb2RlY3Mgb3IgbW9yZSBlcnJvcnNcbiAgICBpZiAoc2VsZWN0ZWQgJiYgKGNvZGVjc1NldFNlbGVjdGlvblByZWZlcmVuY2VWYWx1ZShjYW5kaWRhdGUpID49IGNvZGVjc1NldFNlbGVjdGlvblByZWZlcmVuY2VWYWx1ZShzZWxlY3RlZCkgfHwgY2FuZGlkYXRlVGllci5mcmFnbWVudEVycm9yID4gY29kZWNUaWVyc1tzZWxlY3RlZF0uZnJhZ21lbnRFcnJvcikpIHtcbiAgICAgIHJldHVybiBzZWxlY3RlZDtcbiAgICB9XG4gICAgc2VsZWN0ZWRTY29yZSA9IGNhbmRpZGF0ZVRpZXIubWF4U2NvcmU7XG4gICAgcmV0dXJuIGNhbmRpZGF0ZTtcbiAgfSwgdW5kZWZpbmVkKTtcbiAgcmV0dXJuIHtcbiAgICBjb2RlY1NldCxcbiAgICB2aWRlb1JhbmdlcyxcbiAgICBwcmVmZXJIRFIsXG4gICAgbWluRnJhbWVyYXRlLFxuICAgIG1pbkJpdHJhdGVcbiAgfTtcbn1cbmZ1bmN0aW9uIGxvZ1N0YXJ0Q29kZWNDYW5kaWRhdGVJZ25vcmVkKGNvZGVTZXQsIHJlYXNvbikge1xuICBsb2dnZXIubG9nKGBbYWJyXSBzdGFydCBjYW5kaWRhdGVzIHdpdGggXCIke2NvZGVTZXR9XCIgaWdub3JlZCBiZWNhdXNlICR7cmVhc29ufWApO1xufVxuZnVuY3Rpb24gZ2V0QXVkaW9UcmFja3NCeUdyb3VwKGFsbEF1ZGlvVHJhY2tzKSB7XG4gIHJldHVybiBhbGxBdWRpb1RyYWNrcy5yZWR1Y2UoKGF1ZGlvVHJhY2tzQnlHcm91cCwgdHJhY2spID0+IHtcbiAgICBsZXQgdHJhY2tHcm91cCA9IGF1ZGlvVHJhY2tzQnlHcm91cC5ncm91cHNbdHJhY2suZ3JvdXBJZF07XG4gICAgaWYgKCF0cmFja0dyb3VwKSB7XG4gICAgICB0cmFja0dyb3VwID0gYXVkaW9UcmFja3NCeUdyb3VwLmdyb3Vwc1t0cmFjay5ncm91cElkXSA9IHtcbiAgICAgICAgdHJhY2tzOiBbXSxcbiAgICAgICAgY2hhbm5lbHM6IHtcbiAgICAgICAgICAyOiAwXG4gICAgICAgIH0sXG4gICAgICAgIGhhc0RlZmF1bHQ6IGZhbHNlLFxuICAgICAgICBoYXNBdXRvU2VsZWN0OiBmYWxzZVxuICAgICAgfTtcbiAgICB9XG4gICAgdHJhY2tHcm91cC50cmFja3MucHVzaCh0cmFjayk7XG4gICAgY29uc3QgY2hhbm5lbHNLZXkgPSB0cmFjay5jaGFubmVscyB8fCAnMic7XG4gICAgdHJhY2tHcm91cC5jaGFubmVsc1tjaGFubmVsc0tleV0gPSAodHJhY2tHcm91cC5jaGFubmVsc1tjaGFubmVsc0tleV0gfHwgMCkgKyAxO1xuICAgIHRyYWNrR3JvdXAuaGFzRGVmYXVsdCA9IHRyYWNrR3JvdXAuaGFzRGVmYXVsdCB8fCB0cmFjay5kZWZhdWx0O1xuICAgIHRyYWNrR3JvdXAuaGFzQXV0b1NlbGVjdCA9IHRyYWNrR3JvdXAuaGFzQXV0b1NlbGVjdCB8fCB0cmFjay5hdXRvc2VsZWN0O1xuICAgIGlmICh0cmFja0dyb3VwLmhhc0RlZmF1bHQpIHtcbiAgICAgIGF1ZGlvVHJhY2tzQnlHcm91cC5oYXNEZWZhdWx0QXVkaW8gPSB0cnVlO1xuICAgIH1cbiAgICBpZiAodHJhY2tHcm91cC5oYXNBdXRvU2VsZWN0KSB7XG4gICAgICBhdWRpb1RyYWNrc0J5R3JvdXAuaGFzQXV0b1NlbGVjdEF1ZGlvID0gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGF1ZGlvVHJhY2tzQnlHcm91cDtcbiAgfSwge1xuICAgIGhhc0RlZmF1bHRBdWRpbzogZmFsc2UsXG4gICAgaGFzQXV0b1NlbGVjdEF1ZGlvOiBmYWxzZSxcbiAgICBncm91cHM6IHt9XG4gIH0pO1xufVxuZnVuY3Rpb24gZ2V0Q29kZWNUaWVycyhsZXZlbHMsIGF1ZGlvVHJhY2tzQnlHcm91cCwgbWluQXV0b0xldmVsLCBtYXhBdXRvTGV2ZWwpIHtcbiAgcmV0dXJuIGxldmVscy5zbGljZShtaW5BdXRvTGV2ZWwsIG1heEF1dG9MZXZlbCArIDEpLnJlZHVjZSgodGllcnMsIGxldmVsKSA9PiB7XG4gICAgaWYgKCFsZXZlbC5jb2RlY1NldCkge1xuICAgICAgcmV0dXJuIHRpZXJzO1xuICAgIH1cbiAgICBjb25zdCBhdWRpb0dyb3VwcyA9IGxldmVsLmF1ZGlvR3JvdXBzO1xuICAgIGxldCB0aWVyID0gdGllcnNbbGV2ZWwuY29kZWNTZXRdO1xuICAgIGlmICghdGllcikge1xuICAgICAgdGllcnNbbGV2ZWwuY29kZWNTZXRdID0gdGllciA9IHtcbiAgICAgICAgbWluQml0cmF0ZTogSW5maW5pdHksXG4gICAgICAgIG1pbkhlaWdodDogSW5maW5pdHksXG4gICAgICAgIG1pbkZyYW1lcmF0ZTogSW5maW5pdHksXG4gICAgICAgIG1heFNjb3JlOiAwLFxuICAgICAgICB2aWRlb1Jhbmdlczoge1xuICAgICAgICAgIFNEUjogMFxuICAgICAgICB9LFxuICAgICAgICBjaGFubmVsczoge1xuICAgICAgICAgICcyJzogMFxuICAgICAgICB9LFxuICAgICAgICBoYXNEZWZhdWx0QXVkaW86ICFhdWRpb0dyb3VwcyxcbiAgICAgICAgZnJhZ21lbnRFcnJvcjogMFxuICAgICAgfTtcbiAgICB9XG4gICAgdGllci5taW5CaXRyYXRlID0gTWF0aC5taW4odGllci5taW5CaXRyYXRlLCBsZXZlbC5iaXRyYXRlKTtcbiAgICBjb25zdCBsZXNzZXJXaWR0aE9ySGVpZ2h0ID0gTWF0aC5taW4obGV2ZWwuaGVpZ2h0LCBsZXZlbC53aWR0aCk7XG4gICAgdGllci5taW5IZWlnaHQgPSBNYXRoLm1pbih0aWVyLm1pbkhlaWdodCwgbGVzc2VyV2lkdGhPckhlaWdodCk7XG4gICAgdGllci5taW5GcmFtZXJhdGUgPSBNYXRoLm1pbih0aWVyLm1pbkZyYW1lcmF0ZSwgbGV2ZWwuZnJhbWVSYXRlKTtcbiAgICB0aWVyLm1heFNjb3JlID0gTWF0aC5tYXgodGllci5tYXhTY29yZSwgbGV2ZWwuc2NvcmUpO1xuICAgIHRpZXIuZnJhZ21lbnRFcnJvciArPSBsZXZlbC5mcmFnbWVudEVycm9yO1xuICAgIHRpZXIudmlkZW9SYW5nZXNbbGV2ZWwudmlkZW9SYW5nZV0gPSAodGllci52aWRlb1Jhbmdlc1tsZXZlbC52aWRlb1JhbmdlXSB8fCAwKSArIDE7XG4gICAgaWYgKGF1ZGlvR3JvdXBzKSB7XG4gICAgICBhdWRpb0dyb3Vwcy5mb3JFYWNoKGF1ZGlvR3JvdXBJZCA9PiB7XG4gICAgICAgIGlmICghYXVkaW9Hcm91cElkKSB7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGF1ZGlvR3JvdXAgPSBhdWRpb1RyYWNrc0J5R3JvdXAuZ3JvdXBzW2F1ZGlvR3JvdXBJZF07XG4gICAgICAgIGlmICghYXVkaW9Hcm91cCkge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICAvLyBEZWZhdWx0IGF1ZGlvIGlzIGFueSBncm91cCB3aXRoIERFRkFVTFQ9WUVTLCBvciBpZiBtaXNzaW5nIHRoZW4gYW55IGdyb3VwIHdpdGggQVVUT1NFTEVDVD1ZRVMsIG9yIGFsbCB2YXJpYW50c1xuICAgICAgICB0aWVyLmhhc0RlZmF1bHRBdWRpbyA9IHRpZXIuaGFzRGVmYXVsdEF1ZGlvIHx8IGF1ZGlvVHJhY2tzQnlHcm91cC5oYXNEZWZhdWx0QXVkaW8gPyBhdWRpb0dyb3VwLmhhc0RlZmF1bHQgOiBhdWRpb0dyb3VwLmhhc0F1dG9TZWxlY3QgfHwgIWF1ZGlvVHJhY2tzQnlHcm91cC5oYXNEZWZhdWx0QXVkaW8gJiYgIWF1ZGlvVHJhY2tzQnlHcm91cC5oYXNBdXRvU2VsZWN0QXVkaW87XG4gICAgICAgIE9iamVjdC5rZXlzKGF1ZGlvR3JvdXAuY2hhbm5lbHMpLmZvckVhY2goY2hhbm5lbHMgPT4ge1xuICAgICAgICAgIHRpZXIuY2hhbm5lbHNbY2hhbm5lbHNdID0gKHRpZXIuY2hhbm5lbHNbY2hhbm5lbHNdIHx8IDApICsgYXVkaW9Hcm91cC5jaGFubmVsc1tjaGFubmVsc107XG4gICAgICAgIH0pO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiB0aWVycztcbiAgfSwge30pO1xufVxuZnVuY3Rpb24gZmluZE1hdGNoaW5nT3B0aW9uKG9wdGlvbiwgdHJhY2tzLCBtYXRjaFByZWRpY2F0ZSkge1xuICBpZiAoJ2F0dHJzJyBpbiBvcHRpb24pIHtcbiAgICBjb25zdCBpbmRleCA9IHRyYWNrcy5pbmRleE9mKG9wdGlvbik7XG4gICAgaWYgKGluZGV4ICE9PSAtMSkge1xuICAgICAgcmV0dXJuIGluZGV4O1xuICAgIH1cbiAgfVxuICBmb3IgKGxldCBpID0gMDsgaSA8IHRyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IHRyYWNrID0gdHJhY2tzW2ldO1xuICAgIGlmIChtYXRjaGVzT3B0aW9uKG9wdGlvbiwgdHJhY2ssIG1hdGNoUHJlZGljYXRlKSkge1xuICAgICAgcmV0dXJuIGk7XG4gICAgfVxuICB9XG4gIHJldHVybiAtMTtcbn1cbmZ1bmN0aW9uIG1hdGNoZXNPcHRpb24ob3B0aW9uLCB0cmFjaywgbWF0Y2hQcmVkaWNhdGUpIHtcbiAgY29uc3Qge1xuICAgIGdyb3VwSWQsXG4gICAgbmFtZSxcbiAgICBsYW5nLFxuICAgIGFzc29jTGFuZyxcbiAgICBjaGFyYWN0ZXJpc3RpY3MsXG4gICAgZGVmYXVsdDogaXNEZWZhdWx0XG4gIH0gPSBvcHRpb247XG4gIGNvbnN0IGZvcmNlZCA9IG9wdGlvbi5mb3JjZWQ7XG4gIHJldHVybiAoZ3JvdXBJZCA9PT0gdW5kZWZpbmVkIHx8IHRyYWNrLmdyb3VwSWQgPT09IGdyb3VwSWQpICYmIChuYW1lID09PSB1bmRlZmluZWQgfHwgdHJhY2submFtZSA9PT0gbmFtZSkgJiYgKGxhbmcgPT09IHVuZGVmaW5lZCB8fCB0cmFjay5sYW5nID09PSBsYW5nKSAmJiAobGFuZyA9PT0gdW5kZWZpbmVkIHx8IHRyYWNrLmFzc29jTGFuZyA9PT0gYXNzb2NMYW5nKSAmJiAoaXNEZWZhdWx0ID09PSB1bmRlZmluZWQgfHwgdHJhY2suZGVmYXVsdCA9PT0gaXNEZWZhdWx0KSAmJiAoZm9yY2VkID09PSB1bmRlZmluZWQgfHwgdHJhY2suZm9yY2VkID09PSBmb3JjZWQpICYmIChjaGFyYWN0ZXJpc3RpY3MgPT09IHVuZGVmaW5lZCB8fCBjaGFyYWN0ZXJpc3RpY3NNYXRjaChjaGFyYWN0ZXJpc3RpY3MsIHRyYWNrLmNoYXJhY3RlcmlzdGljcykpICYmIChtYXRjaFByZWRpY2F0ZSA9PT0gdW5kZWZpbmVkIHx8IG1hdGNoUHJlZGljYXRlKG9wdGlvbiwgdHJhY2spKTtcbn1cbmZ1bmN0aW9uIGNoYXJhY3RlcmlzdGljc01hdGNoKGNoYXJhY3RlcmlzdGljc0EsIGNoYXJhY3RlcmlzdGljc0IgPSAnJykge1xuICBjb25zdCBhcnJBID0gY2hhcmFjdGVyaXN0aWNzQS5zcGxpdCgnLCcpO1xuICBjb25zdCBhcnJCID0gY2hhcmFjdGVyaXN0aWNzQi5zcGxpdCgnLCcpO1xuICAvLyBFeHBlY3RzIGVhY2ggaXRlbSB0byBiZSB1bmlxdWU6XG4gIHJldHVybiBhcnJBLmxlbmd0aCA9PT0gYXJyQi5sZW5ndGggJiYgIWFyckEuc29tZShlbCA9PiBhcnJCLmluZGV4T2YoZWwpID09PSAtMSk7XG59XG5mdW5jdGlvbiBhdWRpb01hdGNoUHJlZGljYXRlKG9wdGlvbiwgdHJhY2spIHtcbiAgY29uc3Qge1xuICAgIGF1ZGlvQ29kZWMsXG4gICAgY2hhbm5lbHNcbiAgfSA9IG9wdGlvbjtcbiAgcmV0dXJuIChhdWRpb0NvZGVjID09PSB1bmRlZmluZWQgfHwgKHRyYWNrLmF1ZGlvQ29kZWMgfHwgJycpLnN1YnN0cmluZygwLCA0KSA9PT0gYXVkaW9Db2RlYy5zdWJzdHJpbmcoMCwgNCkpICYmIChjaGFubmVscyA9PT0gdW5kZWZpbmVkIHx8IGNoYW5uZWxzID09PSAodHJhY2suY2hhbm5lbHMgfHwgJzInKSk7XG59XG5mdW5jdGlvbiBmaW5kQ2xvc2VzdExldmVsV2l0aEF1ZGlvR3JvdXAob3B0aW9uLCBsZXZlbHMsIGFsbEF1ZGlvVHJhY2tzLCBzZWFyY2hJbmRleCwgbWF0Y2hQcmVkaWNhdGUpIHtcbiAgY29uc3QgY3VycmVudExldmVsID0gbGV2ZWxzW3NlYXJjaEluZGV4XTtcbiAgLy8gQXJlIHRoZXJlIHZhcmlhbnRzIHdpdGggc2FtZSBVUkkgYXMgY3VycmVudCBsZXZlbD9cbiAgLy8gSWYgc28sIGZpbmQgYSBtYXRjaCB0aGF0IGRvZXMgbm90IHJlcXVpcmUgYW55IGxldmVsIFVSSSBjaGFuZ2VcbiAgY29uc3QgdmFyaWFudHMgPSBsZXZlbHMucmVkdWNlKCh2YXJpYW50TWFwLCBsZXZlbCwgaW5kZXgpID0+IHtcbiAgICBjb25zdCB1cmkgPSBsZXZlbC51cmk7XG4gICAgY29uc3QgcmVuZGl0aW9ucyA9IHZhcmlhbnRNYXBbdXJpXSB8fCAodmFyaWFudE1hcFt1cmldID0gW10pO1xuICAgIHJlbmRpdGlvbnMucHVzaChpbmRleCk7XG4gICAgcmV0dXJuIHZhcmlhbnRNYXA7XG4gIH0sIHt9KTtcbiAgY29uc3QgcmVuZGl0aW9ucyA9IHZhcmlhbnRzW2N1cnJlbnRMZXZlbC51cmldO1xuICBpZiAocmVuZGl0aW9ucy5sZW5ndGggPiAxKSB7XG4gICAgc2VhcmNoSW5kZXggPSBNYXRoLm1heC5hcHBseShNYXRoLCByZW5kaXRpb25zKTtcbiAgfVxuICAvLyBGaW5kIGJlc3QgbWF0Y2hcbiAgY29uc3QgY3VycmVudFZpZGVvUmFuZ2UgPSBjdXJyZW50TGV2ZWwudmlkZW9SYW5nZTtcbiAgY29uc3QgY3VycmVudEZyYW1lUmF0ZSA9IGN1cnJlbnRMZXZlbC5mcmFtZVJhdGU7XG4gIGNvbnN0IGN1cnJlbnRWaWRlb0NvZGVjID0gY3VycmVudExldmVsLmNvZGVjU2V0LnN1YnN0cmluZygwLCA0KTtcbiAgY29uc3QgbWF0Y2hpbmdWaWRlbyA9IHNlYXJjaERvd25BbmRVcExpc3QobGV2ZWxzLCBzZWFyY2hJbmRleCwgbGV2ZWwgPT4ge1xuICAgIGlmIChsZXZlbC52aWRlb1JhbmdlICE9PSBjdXJyZW50VmlkZW9SYW5nZSB8fCBsZXZlbC5mcmFtZVJhdGUgIT09IGN1cnJlbnRGcmFtZVJhdGUgfHwgbGV2ZWwuY29kZWNTZXQuc3Vic3RyaW5nKDAsIDQpICE9PSBjdXJyZW50VmlkZW9Db2RlYykge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBjb25zdCBhdWRpb0dyb3VwcyA9IGxldmVsLmF1ZGlvR3JvdXBzO1xuICAgIGNvbnN0IHRyYWNrcyA9IGFsbEF1ZGlvVHJhY2tzLmZpbHRlcih0cmFjayA9PiAhYXVkaW9Hcm91cHMgfHwgYXVkaW9Hcm91cHMuaW5kZXhPZih0cmFjay5ncm91cElkKSAhPT0gLTEpO1xuICAgIHJldHVybiBmaW5kTWF0Y2hpbmdPcHRpb24ob3B0aW9uLCB0cmFja3MsIG1hdGNoUHJlZGljYXRlKSA+IC0xO1xuICB9KTtcbiAgaWYgKG1hdGNoaW5nVmlkZW8gPiAtMSkge1xuICAgIHJldHVybiBtYXRjaGluZ1ZpZGVvO1xuICB9XG4gIHJldHVybiBzZWFyY2hEb3duQW5kVXBMaXN0KGxldmVscywgc2VhcmNoSW5kZXgsIGxldmVsID0+IHtcbiAgICBjb25zdCBhdWRpb0dyb3VwcyA9IGxldmVsLmF1ZGlvR3JvdXBzO1xuICAgIGNvbnN0IHRyYWNrcyA9IGFsbEF1ZGlvVHJhY2tzLmZpbHRlcih0cmFjayA9PiAhYXVkaW9Hcm91cHMgfHwgYXVkaW9Hcm91cHMuaW5kZXhPZih0cmFjay5ncm91cElkKSAhPT0gLTEpO1xuICAgIHJldHVybiBmaW5kTWF0Y2hpbmdPcHRpb24ob3B0aW9uLCB0cmFja3MsIG1hdGNoUHJlZGljYXRlKSA+IC0xO1xuICB9KTtcbn1cbmZ1bmN0aW9uIHNlYXJjaERvd25BbmRVcExpc3QoYXJyLCBzZWFyY2hJbmRleCwgcHJlZGljYXRlKSB7XG4gIGZvciAobGV0IGkgPSBzZWFyY2hJbmRleDsgaTsgaS0tKSB7XG4gICAgaWYgKHByZWRpY2F0ZShhcnJbaV0pKSB7XG4gICAgICByZXR1cm4gaTtcbiAgICB9XG4gIH1cbiAgZm9yIChsZXQgaSA9IHNlYXJjaEluZGV4ICsgMTsgaSA8IGFyci5sZW5ndGg7IGkrKykge1xuICAgIGlmIChwcmVkaWNhdGUoYXJyW2ldKSkge1xuICAgICAgcmV0dXJuIGk7XG4gICAgfVxuICB9XG4gIHJldHVybiAtMTtcbn1cblxuY2xhc3MgQWJyQ29udHJvbGxlciB7XG4gIGNvbnN0cnVjdG9yKF9obHMpIHtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLmxhc3RMZXZlbExvYWRTZWMgPSAwO1xuICAgIHRoaXMubGFzdExvYWRlZEZyYWdMZXZlbCA9IC0xO1xuICAgIHRoaXMuZmlyc3RTZWxlY3Rpb24gPSAtMTtcbiAgICB0aGlzLl9uZXh0QXV0b0xldmVsID0gLTE7XG4gICAgdGhpcy5uZXh0QXV0b0xldmVsS2V5ID0gJyc7XG4gICAgdGhpcy5hdWRpb1RyYWNrc0J5R3JvdXAgPSBudWxsO1xuICAgIHRoaXMuY29kZWNUaWVycyA9IG51bGw7XG4gICAgdGhpcy50aW1lciA9IC0xO1xuICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSBudWxsO1xuICAgIHRoaXMucGFydEN1cnJlbnQgPSBudWxsO1xuICAgIHRoaXMuYml0cmF0ZVRlc3REZWxheSA9IDA7XG4gICAgdGhpcy5id0VzdGltYXRvciA9IHZvaWQgMDtcbiAgICAvKlxuICAgICAgICBUaGlzIG1ldGhvZCBtb25pdG9ycyB0aGUgZG93bmxvYWQgcmF0ZSBvZiB0aGUgY3VycmVudCBmcmFnbWVudCwgYW5kIHdpbGwgZG93bnN3aXRjaCBpZiB0aGF0IGZyYWdtZW50IHdpbGwgbm90IGxvYWRcbiAgICAgICAgcXVpY2tseSBlbm91Z2ggdG8gcHJldmVudCB1bmRlcmJ1ZmZlcmluZ1xuICAgICAgKi9cbiAgICB0aGlzLl9hYmFuZG9uUnVsZXNDaGVjayA9ICgpID0+IHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgZnJhZ0N1cnJlbnQ6IGZyYWcsXG4gICAgICAgIHBhcnRDdXJyZW50OiBwYXJ0LFxuICAgICAgICBobHNcbiAgICAgIH0gPSB0aGlzO1xuICAgICAgY29uc3Qge1xuICAgICAgICBhdXRvTGV2ZWxFbmFibGVkLFxuICAgICAgICBtZWRpYVxuICAgICAgfSA9IGhscztcbiAgICAgIGlmICghZnJhZyB8fCAhbWVkaWEpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3Qgbm93ID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICBjb25zdCBzdGF0cyA9IHBhcnQgPyBwYXJ0LnN0YXRzIDogZnJhZy5zdGF0cztcbiAgICAgIGNvbnN0IGR1cmF0aW9uID0gcGFydCA/IHBhcnQuZHVyYXRpb24gOiBmcmFnLmR1cmF0aW9uO1xuICAgICAgY29uc3QgdGltZUxvYWRpbmcgPSBub3cgLSBzdGF0cy5sb2FkaW5nLnN0YXJ0O1xuICAgICAgY29uc3QgbWluQXV0b0xldmVsID0gaGxzLm1pbkF1dG9MZXZlbDtcbiAgICAgIC8vIElmIGZyYWcgbG9hZGluZyBpcyBhYm9ydGVkLCBjb21wbGV0ZSwgb3IgZnJvbSBsb3dlc3QgbGV2ZWwsIHN0b3AgdGltZXIgYW5kIHJldHVyblxuICAgICAgaWYgKHN0YXRzLmFib3J0ZWQgfHwgc3RhdHMubG9hZGVkICYmIHN0YXRzLmxvYWRlZCA9PT0gc3RhdHMudG90YWwgfHwgZnJhZy5sZXZlbCA8PSBtaW5BdXRvTGV2ZWwpIHtcbiAgICAgICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgICAgIC8vIHJlc2V0IGZvcmNlZCBhdXRvIGxldmVsIHZhbHVlIHNvIHRoYXQgbmV4dCBsZXZlbCB3aWxsIGJlIHNlbGVjdGVkXG4gICAgICAgIHRoaXMuX25leHRBdXRvTGV2ZWwgPSAtMTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBUaGlzIGNoZWNrIG9ubHkgcnVucyBpZiB3ZSdyZSBpbiBBQlIgbW9kZSBhbmQgYWN0dWFsbHkgcGxheWluZ1xuICAgICAgaWYgKCFhdXRvTGV2ZWxFbmFibGVkIHx8IG1lZGlhLnBhdXNlZCB8fCAhbWVkaWEucGxheWJhY2tSYXRlIHx8ICFtZWRpYS5yZWFkeVN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGJ1ZmZlckluZm8gPSBobHMubWFpbkZvcndhcmRCdWZmZXJJbmZvO1xuICAgICAgaWYgKGJ1ZmZlckluZm8gPT09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3QgdHRmYkVzdGltYXRlID0gdGhpcy5id0VzdGltYXRvci5nZXRFc3RpbWF0ZVRURkIoKTtcbiAgICAgIGNvbnN0IHBsYXliYWNrUmF0ZSA9IE1hdGguYWJzKG1lZGlhLnBsYXliYWNrUmF0ZSk7XG4gICAgICAvLyBUbyBtYWludGFpbiBzdGFibGUgYWRhcHRpdmUgcGxheWJhY2ssIG9ubHkgYmVnaW4gbW9uaXRvcmluZyBmcmFnIGxvYWRpbmcgYWZ0ZXIgaGFsZiBvciBtb3JlIG9mIGl0cyBwbGF5YmFjayBkdXJhdGlvbiBoYXMgcGFzc2VkXG4gICAgICBpZiAodGltZUxvYWRpbmcgPD0gTWF0aC5tYXgodHRmYkVzdGltYXRlLCAxMDAwICogKGR1cmF0aW9uIC8gKHBsYXliYWNrUmF0ZSAqIDIpKSkpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBidWZmZXJTdGFydmF0aW9uRGVsYXkgaXMgYW4gZXN0aW1hdGUgb2YgdGhlIGFtb3VudCB0aW1lIChpbiBzZWNvbmRzKSBpdCB3aWxsIHRha2UgdG8gZXhoYXVzdCB0aGUgYnVmZmVyXG4gICAgICBjb25zdCBidWZmZXJTdGFydmF0aW9uRGVsYXkgPSBidWZmZXJJbmZvLmxlbiAvIHBsYXliYWNrUmF0ZTtcbiAgICAgIGNvbnN0IHR0ZmIgPSBzdGF0cy5sb2FkaW5nLmZpcnN0ID8gc3RhdHMubG9hZGluZy5maXJzdCAtIHN0YXRzLmxvYWRpbmcuc3RhcnQgOiAtMTtcbiAgICAgIGNvbnN0IGxvYWRlZEZpcnN0Qnl0ZSA9IHN0YXRzLmxvYWRlZCAmJiB0dGZiID4gLTE7XG4gICAgICBjb25zdCBid0VzdGltYXRlID0gdGhpcy5nZXRCd0VzdGltYXRlKCk7XG4gICAgICBjb25zdCBsZXZlbHMgPSBobHMubGV2ZWxzO1xuICAgICAgY29uc3QgbGV2ZWwgPSBsZXZlbHNbZnJhZy5sZXZlbF07XG4gICAgICBjb25zdCBleHBlY3RlZExlbiA9IHN0YXRzLnRvdGFsIHx8IE1hdGgubWF4KHN0YXRzLmxvYWRlZCwgTWF0aC5yb3VuZChkdXJhdGlvbiAqIGxldmVsLmF2ZXJhZ2VCaXRyYXRlIC8gOCkpO1xuICAgICAgbGV0IHRpbWVTdHJlYW1pbmcgPSBsb2FkZWRGaXJzdEJ5dGUgPyB0aW1lTG9hZGluZyAtIHR0ZmIgOiB0aW1lTG9hZGluZztcbiAgICAgIGlmICh0aW1lU3RyZWFtaW5nIDwgMSAmJiBsb2FkZWRGaXJzdEJ5dGUpIHtcbiAgICAgICAgdGltZVN0cmVhbWluZyA9IE1hdGgubWluKHRpbWVMb2FkaW5nLCBzdGF0cy5sb2FkZWQgKiA4IC8gYndFc3RpbWF0ZSk7XG4gICAgICB9XG4gICAgICBjb25zdCBsb2FkUmF0ZSA9IGxvYWRlZEZpcnN0Qnl0ZSA/IHN0YXRzLmxvYWRlZCAqIDEwMDAgLyB0aW1lU3RyZWFtaW5nIDogMDtcbiAgICAgIC8vIGZyYWdMb2FkRGVsYXkgaXMgYW4gZXN0aW1hdGUgb2YgdGhlIHRpbWUgKGluIHNlY29uZHMpIGl0IHdpbGwgdGFrZSB0byBidWZmZXIgdGhlIHJlbWFpbmRlciBvZiB0aGUgZnJhZ21lbnRcbiAgICAgIGNvbnN0IGZyYWdMb2FkZWREZWxheSA9IGxvYWRSYXRlID8gKGV4cGVjdGVkTGVuIC0gc3RhdHMubG9hZGVkKSAvIGxvYWRSYXRlIDogZXhwZWN0ZWRMZW4gKiA4IC8gYndFc3RpbWF0ZSArIHR0ZmJFc3RpbWF0ZSAvIDEwMDA7XG4gICAgICAvLyBPbmx5IGRvd25zd2l0Y2ggaWYgdGhlIHRpbWUgdG8gZmluaXNoIGxvYWRpbmcgdGhlIGN1cnJlbnQgZnJhZ21lbnQgaXMgZ3JlYXRlciB0aGFuIHRoZSBhbW91bnQgb2YgYnVmZmVyIGxlZnRcbiAgICAgIGlmIChmcmFnTG9hZGVkRGVsYXkgPD0gYnVmZmVyU3RhcnZhdGlvbkRlbGF5KSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGJ3ZSA9IGxvYWRSYXRlID8gbG9hZFJhdGUgKiA4IDogYndFc3RpbWF0ZTtcbiAgICAgIGxldCBmcmFnTGV2ZWxOZXh0TG9hZGVkRGVsYXkgPSBOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFk7XG4gICAgICBsZXQgbmV4dExvYWRMZXZlbDtcbiAgICAgIC8vIEl0ZXJhdGUgdGhyb3VnaCBsb3dlciBsZXZlbCBhbmQgdHJ5IHRvIGZpbmQgdGhlIGxhcmdlc3Qgb25lIHRoYXQgYXZvaWRzIHJlYnVmZmVyaW5nXG4gICAgICBmb3IgKG5leHRMb2FkTGV2ZWwgPSBmcmFnLmxldmVsIC0gMTsgbmV4dExvYWRMZXZlbCA+IG1pbkF1dG9MZXZlbDsgbmV4dExvYWRMZXZlbC0tKSB7XG4gICAgICAgIC8vIGNvbXB1dGUgdGltZSB0byBsb2FkIG5leHQgZnJhZ21lbnQgYXQgbG93ZXIgbGV2ZWxcbiAgICAgICAgLy8gOCA9IGJpdHMgcGVyIGJ5dGUgKGJwcy9CcHMpXG4gICAgICAgIGNvbnN0IGxldmVsTmV4dEJpdHJhdGUgPSBsZXZlbHNbbmV4dExvYWRMZXZlbF0ubWF4Qml0cmF0ZTtcbiAgICAgICAgZnJhZ0xldmVsTmV4dExvYWRlZERlbGF5ID0gdGhpcy5nZXRUaW1lVG9Mb2FkRnJhZyh0dGZiRXN0aW1hdGUgLyAxMDAwLCBid2UsIGR1cmF0aW9uICogbGV2ZWxOZXh0Qml0cmF0ZSwgIWxldmVsc1tuZXh0TG9hZExldmVsXS5kZXRhaWxzKTtcbiAgICAgICAgaWYgKGZyYWdMZXZlbE5leHRMb2FkZWREZWxheSA8IGJ1ZmZlclN0YXJ2YXRpb25EZWxheSkge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICAvLyBPbmx5IGVtZXJnZW5jeSBzd2l0Y2ggZG93biBpZiBpdCB0YWtlcyBsZXNzIHRpbWUgdG8gbG9hZCBhIG5ldyBmcmFnbWVudCBhdCBsb3dlc3QgbGV2ZWwgaW5zdGVhZCBvZiBjb250aW51aW5nXG4gICAgICAvLyB0byBsb2FkIHRoZSBjdXJyZW50IG9uZVxuICAgICAgaWYgKGZyYWdMZXZlbE5leHRMb2FkZWREZWxheSA+PSBmcmFnTG9hZGVkRGVsYXkpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBpZiBlc3RpbWF0ZWQgbG9hZCB0aW1lIG9mIG5ldyBzZWdtZW50IGlzIGNvbXBsZXRlbHkgdW5yZWFzb25hYmxlLCBpZ25vcmUgYW5kIGRvIG5vdCBlbWVyZ2VuY3kgc3dpdGNoIGRvd25cbiAgICAgIGlmIChmcmFnTGV2ZWxOZXh0TG9hZGVkRGVsYXkgPiBkdXJhdGlvbiAqIDEwKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGhscy5uZXh0TG9hZExldmVsID0gaGxzLm5leHRBdXRvTGV2ZWwgPSBuZXh0TG9hZExldmVsO1xuICAgICAgaWYgKGxvYWRlZEZpcnN0Qnl0ZSkge1xuICAgICAgICAvLyBJZiB0aGVyZSBoYXMgYmVlbiBsb2FkaW5nIHByb2dyZXNzLCBzYW1wbGUgYmFuZHdpZHRoIHVzaW5nIGxvYWRpbmcgdGltZSBvZmZzZXQgYnkgbWluaW11bSBUVEZCIHRpbWVcbiAgICAgICAgdGhpcy5id0VzdGltYXRvci5zYW1wbGUodGltZUxvYWRpbmcgLSBNYXRoLm1pbih0dGZiRXN0aW1hdGUsIHR0ZmIpLCBzdGF0cy5sb2FkZWQpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gSWYgdGhlcmUgaGFzIGJlZW4gbm8gbG9hZGluZyBwcm9ncmVzcywgc2FtcGxlIFRURkJcbiAgICAgICAgdGhpcy5id0VzdGltYXRvci5zYW1wbGVUVEZCKHRpbWVMb2FkaW5nKTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IG5leHRMb2FkTGV2ZWxCaXRyYXRlID0gbGV2ZWxzW25leHRMb2FkTGV2ZWxdLm1heEJpdHJhdGU7XG4gICAgICBpZiAodGhpcy5nZXRCd0VzdGltYXRlKCkgKiB0aGlzLmhscy5jb25maWcuYWJyQmFuZFdpZHRoVXBGYWN0b3IgPiBuZXh0TG9hZExldmVsQml0cmF0ZSkge1xuICAgICAgICB0aGlzLnJlc2V0RXN0aW1hdG9yKG5leHRMb2FkTGV2ZWxCaXRyYXRlKTtcbiAgICAgIH1cbiAgICAgIHRoaXMuY2xlYXJUaW1lcigpO1xuICAgICAgbG9nZ2VyLndhcm4oYFthYnJdIEZyYWdtZW50ICR7ZnJhZy5zbn0ke3BhcnQgPyAnIHBhcnQgJyArIHBhcnQuaW5kZXggOiAnJ30gb2YgbGV2ZWwgJHtmcmFnLmxldmVsfSBpcyBsb2FkaW5nIHRvbyBzbG93bHk7XG4gICAgICBUaW1lIHRvIHVuZGVyYnVmZmVyOiAke2J1ZmZlclN0YXJ2YXRpb25EZWxheS50b0ZpeGVkKDMpfSBzXG4gICAgICBFc3RpbWF0ZWQgbG9hZCB0aW1lIGZvciBjdXJyZW50IGZyYWdtZW50OiAke2ZyYWdMb2FkZWREZWxheS50b0ZpeGVkKDMpfSBzXG4gICAgICBFc3RpbWF0ZWQgbG9hZCB0aW1lIGZvciBkb3duIHN3aXRjaCBmcmFnbWVudDogJHtmcmFnTGV2ZWxOZXh0TG9hZGVkRGVsYXkudG9GaXhlZCgzKX0gc1xuICAgICAgVFRGQiBlc3RpbWF0ZTogJHt0dGZiIHwgMH0gbXNcbiAgICAgIEN1cnJlbnQgQlcgZXN0aW1hdGU6ICR7aXNGaW5pdGVOdW1iZXIoYndFc3RpbWF0ZSkgPyBid0VzdGltYXRlIHwgMCA6ICdVbmtub3duJ30gYnBzXG4gICAgICBOZXcgQlcgZXN0aW1hdGU6ICR7dGhpcy5nZXRCd0VzdGltYXRlKCkgfCAwfSBicHNcbiAgICAgIFN3aXRjaGluZyB0byBsZXZlbCAke25leHRMb2FkTGV2ZWx9IEAgJHtuZXh0TG9hZExldmVsQml0cmF0ZSB8IDB9IGJwc2ApO1xuICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkZSQUdfTE9BRF9FTUVSR0VOQ1lfQUJPUlRFRCwge1xuICAgICAgICBmcmFnLFxuICAgICAgICBwYXJ0LFxuICAgICAgICBzdGF0c1xuICAgICAgfSk7XG4gICAgfTtcbiAgICB0aGlzLmhscyA9IF9obHM7XG4gICAgdGhpcy5id0VzdGltYXRvciA9IHRoaXMuaW5pdEVzdGltYXRvcigpO1xuICAgIHRoaXMucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICByZXNldEVzdGltYXRvcihhYnJFd21hRGVmYXVsdEVzdGltYXRlKSB7XG4gICAgaWYgKGFickV3bWFEZWZhdWx0RXN0aW1hdGUpIHtcbiAgICAgIGxvZ2dlci5sb2coYHNldHRpbmcgaW5pdGlhbCBid2UgdG8gJHthYnJFd21hRGVmYXVsdEVzdGltYXRlfWApO1xuICAgICAgdGhpcy5obHMuY29uZmlnLmFickV3bWFEZWZhdWx0RXN0aW1hdGUgPSBhYnJFd21hRGVmYXVsdEVzdGltYXRlO1xuICAgIH1cbiAgICB0aGlzLmZpcnN0U2VsZWN0aW9uID0gLTE7XG4gICAgdGhpcy5id0VzdGltYXRvciA9IHRoaXMuaW5pdEVzdGltYXRvcigpO1xuICB9XG4gIGluaXRFc3RpbWF0b3IoKSB7XG4gICAgY29uc3QgY29uZmlnID0gdGhpcy5obHMuY29uZmlnO1xuICAgIHJldHVybiBuZXcgRXdtYUJhbmRXaWR0aEVzdGltYXRvcihjb25maWcuYWJyRXdtYVNsb3dWb0QsIGNvbmZpZy5hYnJFd21hRmFzdFZvRCwgY29uZmlnLmFickV3bWFEZWZhdWx0RXN0aW1hdGUpO1xuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0xPQURJTkcsIHRoaXMub25GcmFnTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0xPQURFRCwgdGhpcy5vbkZyYWdMb2FkZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuRlJBR19CVUZGRVJFRCwgdGhpcy5vbkZyYWdCdWZmZXJlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTF9TV0lUQ0hJTkcsIHRoaXMub25MZXZlbFN3aXRjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTF9MT0FERUQsIHRoaXMub25MZXZlbExvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTFNfVVBEQVRFRCwgdGhpcy5vbkxldmVsc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUFYX0FVVE9fTEVWRUxfVVBEQVRFRCwgdGhpcy5vbk1heEF1dG9MZXZlbFVwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvciwgdGhpcyk7XG4gIH1cbiAgdW5yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIWhscykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBobHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0xPQURJTkcsIHRoaXMub25GcmFnTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRlJBR19MT0FERUQsIHRoaXMub25GcmFnTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5MRVZFTF9TV0lUQ0hJTkcsIHRoaXMub25MZXZlbFN3aXRjaGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTEVWRUxfTE9BREVELCB0aGlzLm9uTGV2ZWxMb2FkZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMU19VUERBVEVELCB0aGlzLm9uTGV2ZWxzVXBkYXRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFYX0FVVE9fTEVWRUxfVVBEQVRFRCwgdGhpcy5vbk1heEF1dG9MZXZlbFVwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICB9XG4gIGRlc3Ryb3koKSB7XG4gICAgdGhpcy51bnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuaGxzID0gdGhpcy5fYWJhbmRvblJ1bGVzQ2hlY2sgPSBudWxsO1xuICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSB0aGlzLnBhcnRDdXJyZW50ID0gbnVsbDtcbiAgfVxuICBvbk1hbmlmZXN0TG9hZGluZyhldmVudCwgZGF0YSkge1xuICAgIHRoaXMubGFzdExvYWRlZEZyYWdMZXZlbCA9IC0xO1xuICAgIHRoaXMuZmlyc3RTZWxlY3Rpb24gPSAtMTtcbiAgICB0aGlzLmxhc3RMZXZlbExvYWRTZWMgPSAwO1xuICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSB0aGlzLnBhcnRDdXJyZW50ID0gbnVsbDtcbiAgICB0aGlzLm9uTGV2ZWxzVXBkYXRlZCgpO1xuICAgIHRoaXMuY2xlYXJUaW1lcigpO1xuICB9XG4gIG9uTGV2ZWxzVXBkYXRlZCgpIHtcbiAgICBpZiAodGhpcy5sYXN0TG9hZGVkRnJhZ0xldmVsID4gLTEgJiYgdGhpcy5mcmFnQ3VycmVudCkge1xuICAgICAgdGhpcy5sYXN0TG9hZGVkRnJhZ0xldmVsID0gdGhpcy5mcmFnQ3VycmVudC5sZXZlbDtcbiAgICB9XG4gICAgdGhpcy5fbmV4dEF1dG9MZXZlbCA9IC0xO1xuICAgIHRoaXMub25NYXhBdXRvTGV2ZWxVcGRhdGVkKCk7XG4gICAgdGhpcy5jb2RlY1RpZXJzID0gbnVsbDtcbiAgICB0aGlzLmF1ZGlvVHJhY2tzQnlHcm91cCA9IG51bGw7XG4gIH1cbiAgb25NYXhBdXRvTGV2ZWxVcGRhdGVkKCkge1xuICAgIHRoaXMuZmlyc3RTZWxlY3Rpb24gPSAtMTtcbiAgICB0aGlzLm5leHRBdXRvTGV2ZWxLZXkgPSAnJztcbiAgfVxuICBvbkZyYWdMb2FkaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgZnJhZyA9IGRhdGEuZnJhZztcbiAgICBpZiAodGhpcy5pZ25vcmVGcmFnbWVudChmcmFnKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoIWZyYWcuYml0cmF0ZVRlc3QpIHtcbiAgICAgIHZhciBfZGF0YSRwYXJ0O1xuICAgICAgdGhpcy5mcmFnQ3VycmVudCA9IGZyYWc7XG4gICAgICB0aGlzLnBhcnRDdXJyZW50ID0gKF9kYXRhJHBhcnQgPSBkYXRhLnBhcnQpICE9IG51bGwgPyBfZGF0YSRwYXJ0IDogbnVsbDtcbiAgICB9XG4gICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgdGhpcy50aW1lciA9IHNlbGYuc2V0SW50ZXJ2YWwodGhpcy5fYWJhbmRvblJ1bGVzQ2hlY2ssIDEwMCk7XG4gIH1cbiAgb25MZXZlbFN3aXRjaGluZyhldmVudCwgZGF0YSkge1xuICAgIHRoaXMuY2xlYXJUaW1lcigpO1xuICB9XG4gIG9uRXJyb3IoZXZlbnQsIGRhdGEpIHtcbiAgICBpZiAoZGF0YS5mYXRhbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBzd2l0Y2ggKGRhdGEuZGV0YWlscykge1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuQlVGRkVSX0FERF9DT0RFQ19FUlJPUjpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkJVRkZFUl9BUFBFTkRfRVJST1I6XG4gICAgICAgIC8vIFJlc2V0IGxhc3QgbG9hZGVkIGxldmVsIHNvIHRoYXQgYSBuZXcgc2VsZWN0aW9uIGNhbiBiZSBtYWRlIGFmdGVyIGNhbGxpbmcgcmVjb3Zlck1lZGlhRXJyb3JcbiAgICAgICAgdGhpcy5sYXN0TG9hZGVkRnJhZ0xldmVsID0gLTE7XG4gICAgICAgIHRoaXMuZmlyc3RTZWxlY3Rpb24gPSAtMTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX0xPQURfVElNRU9VVDpcbiAgICAgICAge1xuICAgICAgICAgIGNvbnN0IGZyYWcgPSBkYXRhLmZyYWc7XG4gICAgICAgICAgY29uc3Qge1xuICAgICAgICAgICAgZnJhZ0N1cnJlbnQsXG4gICAgICAgICAgICBwYXJ0Q3VycmVudDogcGFydFxuICAgICAgICAgIH0gPSB0aGlzO1xuICAgICAgICAgIGlmIChmcmFnICYmIGZyYWdDdXJyZW50ICYmIGZyYWcuc24gPT09IGZyYWdDdXJyZW50LnNuICYmIGZyYWcubGV2ZWwgPT09IGZyYWdDdXJyZW50LmxldmVsKSB7XG4gICAgICAgICAgICBjb25zdCBub3cgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgICAgIGNvbnN0IHN0YXRzID0gcGFydCA/IHBhcnQuc3RhdHMgOiBmcmFnLnN0YXRzO1xuICAgICAgICAgICAgY29uc3QgdGltZUxvYWRpbmcgPSBub3cgLSBzdGF0cy5sb2FkaW5nLnN0YXJ0O1xuICAgICAgICAgICAgY29uc3QgdHRmYiA9IHN0YXRzLmxvYWRpbmcuZmlyc3QgPyBzdGF0cy5sb2FkaW5nLmZpcnN0IC0gc3RhdHMubG9hZGluZy5zdGFydCA6IC0xO1xuICAgICAgICAgICAgY29uc3QgbG9hZGVkRmlyc3RCeXRlID0gc3RhdHMubG9hZGVkICYmIHR0ZmIgPiAtMTtcbiAgICAgICAgICAgIGlmIChsb2FkZWRGaXJzdEJ5dGUpIHtcbiAgICAgICAgICAgICAgY29uc3QgdHRmYkVzdGltYXRlID0gdGhpcy5id0VzdGltYXRvci5nZXRFc3RpbWF0ZVRURkIoKTtcbiAgICAgICAgICAgICAgdGhpcy5id0VzdGltYXRvci5zYW1wbGUodGltZUxvYWRpbmcgLSBNYXRoLm1pbih0dGZiRXN0aW1hdGUsIHR0ZmIpLCBzdGF0cy5sb2FkZWQpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgdGhpcy5id0VzdGltYXRvci5zYW1wbGVUVEZCKHRpbWVMb2FkaW5nKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICB9XG4gIH1cbiAgZ2V0VGltZVRvTG9hZEZyYWcodGltZVRvRmlyc3RCeXRlU2VjLCBiYW5kd2lkdGgsIGZyYWdTaXplQml0cywgaXNTd2l0Y2gpIHtcbiAgICBjb25zdCBmcmFnTG9hZFNlYyA9IHRpbWVUb0ZpcnN0Qnl0ZVNlYyArIGZyYWdTaXplQml0cyAvIGJhbmR3aWR0aDtcbiAgICBjb25zdCBwbGF5bGlzdExvYWRTZWMgPSBpc1N3aXRjaCA/IHRoaXMubGFzdExldmVsTG9hZFNlYyA6IDA7XG4gICAgcmV0dXJuIGZyYWdMb2FkU2VjICsgcGxheWxpc3RMb2FkU2VjO1xuICB9XG4gIG9uTGV2ZWxMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCBjb25maWcgPSB0aGlzLmhscy5jb25maWc7XG4gICAgY29uc3Qge1xuICAgICAgbG9hZGluZ1xuICAgIH0gPSBkYXRhLnN0YXRzO1xuICAgIGNvbnN0IHRpbWVMb2FkaW5nTXMgPSBsb2FkaW5nLmVuZCAtIGxvYWRpbmcuc3RhcnQ7XG4gICAgaWYgKGlzRmluaXRlTnVtYmVyKHRpbWVMb2FkaW5nTXMpKSB7XG4gICAgICB0aGlzLmxhc3RMZXZlbExvYWRTZWMgPSB0aW1lTG9hZGluZ01zIC8gMTAwMDtcbiAgICB9XG4gICAgaWYgKGRhdGEuZGV0YWlscy5saXZlKSB7XG4gICAgICB0aGlzLmJ3RXN0aW1hdG9yLnVwZGF0ZShjb25maWcuYWJyRXdtYVNsb3dMaXZlLCBjb25maWcuYWJyRXdtYUZhc3RMaXZlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5id0VzdGltYXRvci51cGRhdGUoY29uZmlnLmFickV3bWFTbG93Vm9ELCBjb25maWcuYWJyRXdtYUZhc3RWb0QpO1xuICAgIH1cbiAgfVxuICBvbkZyYWdMb2FkZWQoZXZlbnQsIHtcbiAgICBmcmFnLFxuICAgIHBhcnRcbiAgfSkge1xuICAgIGNvbnN0IHN0YXRzID0gcGFydCA/IHBhcnQuc3RhdHMgOiBmcmFnLnN0YXRzO1xuICAgIGlmIChmcmFnLnR5cGUgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4pIHtcbiAgICAgIHRoaXMuYndFc3RpbWF0b3Iuc2FtcGxlVFRGQihzdGF0cy5sb2FkaW5nLmZpcnN0IC0gc3RhdHMubG9hZGluZy5zdGFydCk7XG4gICAgfVxuICAgIGlmICh0aGlzLmlnbm9yZUZyYWdtZW50KGZyYWcpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIHN0b3AgbW9uaXRvcmluZyBidyBvbmNlIGZyYWcgbG9hZGVkXG4gICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgLy8gcmVzZXQgZm9yY2VkIGF1dG8gbGV2ZWwgdmFsdWUgc28gdGhhdCBuZXh0IGxldmVsIHdpbGwgYmUgc2VsZWN0ZWRcbiAgICBpZiAoZnJhZy5sZXZlbCA9PT0gdGhpcy5fbmV4dEF1dG9MZXZlbCkge1xuICAgICAgdGhpcy5fbmV4dEF1dG9MZXZlbCA9IC0xO1xuICAgIH1cbiAgICB0aGlzLmZpcnN0U2VsZWN0aW9uID0gLTE7XG5cbiAgICAvLyBjb21wdXRlIGxldmVsIGF2ZXJhZ2UgYml0cmF0ZVxuICAgIGlmICh0aGlzLmhscy5jb25maWcuYWJyTWF4V2l0aFJlYWxCaXRyYXRlKSB7XG4gICAgICBjb25zdCBkdXJhdGlvbiA9IHBhcnQgPyBwYXJ0LmR1cmF0aW9uIDogZnJhZy5kdXJhdGlvbjtcbiAgICAgIGNvbnN0IGxldmVsID0gdGhpcy5obHMubGV2ZWxzW2ZyYWcubGV2ZWxdO1xuICAgICAgY29uc3QgbG9hZGVkQnl0ZXMgPSAobGV2ZWwubG9hZGVkID8gbGV2ZWwubG9hZGVkLmJ5dGVzIDogMCkgKyBzdGF0cy5sb2FkZWQ7XG4gICAgICBjb25zdCBsb2FkZWREdXJhdGlvbiA9IChsZXZlbC5sb2FkZWQgPyBsZXZlbC5sb2FkZWQuZHVyYXRpb24gOiAwKSArIGR1cmF0aW9uO1xuICAgICAgbGV2ZWwubG9hZGVkID0ge1xuICAgICAgICBieXRlczogbG9hZGVkQnl0ZXMsXG4gICAgICAgIGR1cmF0aW9uOiBsb2FkZWREdXJhdGlvblxuICAgICAgfTtcbiAgICAgIGxldmVsLnJlYWxCaXRyYXRlID0gTWF0aC5yb3VuZCg4ICogbG9hZGVkQnl0ZXMgLyBsb2FkZWREdXJhdGlvbik7XG4gICAgfVxuICAgIGlmIChmcmFnLmJpdHJhdGVUZXN0KSB7XG4gICAgICBjb25zdCBmcmFnQnVmZmVyZWREYXRhID0ge1xuICAgICAgICBzdGF0cyxcbiAgICAgICAgZnJhZyxcbiAgICAgICAgcGFydCxcbiAgICAgICAgaWQ6IGZyYWcudHlwZVxuICAgICAgfTtcbiAgICAgIHRoaXMub25GcmFnQnVmZmVyZWQoRXZlbnRzLkZSQUdfQlVGRkVSRUQsIGZyYWdCdWZmZXJlZERhdGEpO1xuICAgICAgZnJhZy5iaXRyYXRlVGVzdCA9IGZhbHNlO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBzdG9yZSBsZXZlbCBpZCBhZnRlciBzdWNjZXNzZnVsIGZyYWdtZW50IGxvYWQgZm9yIHBsYXliYWNrXG4gICAgICB0aGlzLmxhc3RMb2FkZWRGcmFnTGV2ZWwgPSBmcmFnLmxldmVsO1xuICAgIH1cbiAgfVxuICBvbkZyYWdCdWZmZXJlZChldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBwYXJ0XG4gICAgfSA9IGRhdGE7XG4gICAgY29uc3Qgc3RhdHMgPSBwYXJ0ICE9IG51bGwgJiYgcGFydC5zdGF0cy5sb2FkZWQgPyBwYXJ0LnN0YXRzIDogZnJhZy5zdGF0cztcbiAgICBpZiAoc3RhdHMuYWJvcnRlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAodGhpcy5pZ25vcmVGcmFnbWVudChmcmFnKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBVc2UgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwYXJzaW5nIGFuZCByZXF1ZXN0IGluc3RlYWQgb2YgYnVmZmVyaW5nIGFuZCByZXF1ZXN0IHRvIGNvbXB1dGUgZnJhZ0xvYWRpbmdQcm9jZXNzaW5nO1xuICAgIC8vIHJhdGlvbmFsZSBpcyB0aGF0IGJ1ZmZlciBhcHBlbmRpbmcgb25seSBoYXBwZW5zIG9uY2UgbWVkaWEgaXMgYXR0YWNoZWQuIFRoaXMgY2FuIGhhcHBlbiB3aGVuIGNvbmZpZy5zdGFydEZyYWdQcmVmZXRjaFxuICAgIC8vIGlzIHVzZWQuIElmIHdlIHVzZWQgYnVmZmVyaW5nIGluIHRoYXQgY2FzZSwgb3VyIEJXIGVzdGltYXRlIHNhbXBsZSB3aWxsIGJlIHZlcnkgbGFyZ2UuXG4gICAgY29uc3QgcHJvY2Vzc2luZ01zID0gc3RhdHMucGFyc2luZy5lbmQgLSBzdGF0cy5sb2FkaW5nLnN0YXJ0IC0gTWF0aC5taW4oc3RhdHMubG9hZGluZy5maXJzdCAtIHN0YXRzLmxvYWRpbmcuc3RhcnQsIHRoaXMuYndFc3RpbWF0b3IuZ2V0RXN0aW1hdGVUVEZCKCkpO1xuICAgIHRoaXMuYndFc3RpbWF0b3Iuc2FtcGxlKHByb2Nlc3NpbmdNcywgc3RhdHMubG9hZGVkKTtcbiAgICBzdGF0cy5id0VzdGltYXRlID0gdGhpcy5nZXRCd0VzdGltYXRlKCk7XG4gICAgaWYgKGZyYWcuYml0cmF0ZVRlc3QpIHtcbiAgICAgIHRoaXMuYml0cmF0ZVRlc3REZWxheSA9IHByb2Nlc3NpbmdNcyAvIDEwMDA7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuYml0cmF0ZVRlc3REZWxheSA9IDA7XG4gICAgfVxuICB9XG4gIGlnbm9yZUZyYWdtZW50KGZyYWcpIHtcbiAgICAvLyBPbmx5IGNvdW50IG5vbi1hbHQtYXVkaW8gZnJhZ3Mgd2hpY2ggd2VyZSBhY3R1YWxseSBidWZmZXJlZCBpbiBvdXIgQlcgY2FsY3VsYXRpb25zXG4gICAgcmV0dXJuIGZyYWcudHlwZSAhPT0gUGxheWxpc3RMZXZlbFR5cGUuTUFJTiB8fCBmcmFnLnNuID09PSAnaW5pdFNlZ21lbnQnO1xuICB9XG4gIGNsZWFyVGltZXIoKSB7XG4gICAgaWYgKHRoaXMudGltZXIgPiAtMSkge1xuICAgICAgc2VsZi5jbGVhckludGVydmFsKHRoaXMudGltZXIpO1xuICAgICAgdGhpcy50aW1lciA9IC0xO1xuICAgIH1cbiAgfVxuICBnZXQgZmlyc3RBdXRvTGV2ZWwoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbWF4QXV0b0xldmVsLFxuICAgICAgbWluQXV0b0xldmVsXG4gICAgfSA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IGJ3RXN0aW1hdGUgPSB0aGlzLmdldEJ3RXN0aW1hdGUoKTtcbiAgICBjb25zdCBtYXhTdGFydERlbGF5ID0gdGhpcy5obHMuY29uZmlnLm1heFN0YXJ2YXRpb25EZWxheTtcbiAgICBjb25zdCBhYnJBdXRvTGV2ZWwgPSB0aGlzLmZpbmRCZXN0TGV2ZWwoYndFc3RpbWF0ZSwgbWluQXV0b0xldmVsLCBtYXhBdXRvTGV2ZWwsIDAsIG1heFN0YXJ0RGVsYXksIDEsIDEpO1xuICAgIGlmIChhYnJBdXRvTGV2ZWwgPiAtMSkge1xuICAgICAgcmV0dXJuIGFickF1dG9MZXZlbDtcbiAgICB9XG4gICAgY29uc3QgZmlyc3RMZXZlbCA9IHRoaXMuaGxzLmZpcnN0TGV2ZWw7XG4gICAgY29uc3QgY2xhbXBlZCA9IE1hdGgubWluKE1hdGgubWF4KGZpcnN0TGV2ZWwsIG1pbkF1dG9MZXZlbCksIG1heEF1dG9MZXZlbCk7XG4gICAgbG9nZ2VyLndhcm4oYFthYnJdIENvdWxkIG5vdCBmaW5kIGJlc3Qgc3RhcnRpbmcgYXV0byBsZXZlbC4gRGVmYXVsdGluZyB0byBmaXJzdCBpbiBwbGF5bGlzdCAke2ZpcnN0TGV2ZWx9IGNsYW1wZWQgdG8gJHtjbGFtcGVkfWApO1xuICAgIHJldHVybiBjbGFtcGVkO1xuICB9XG4gIGdldCBmb3JjZWRBdXRvTGV2ZWwoKSB7XG4gICAgaWYgKHRoaXMubmV4dEF1dG9MZXZlbEtleSkge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fbmV4dEF1dG9MZXZlbDtcbiAgfVxuXG4gIC8vIHJldHVybiBuZXh0IGF1dG8gbGV2ZWxcbiAgZ2V0IG5leHRBdXRvTGV2ZWwoKSB7XG4gICAgY29uc3QgZm9yY2VkQXV0b0xldmVsID0gdGhpcy5mb3JjZWRBdXRvTGV2ZWw7XG4gICAgY29uc3QgYndFc3RpbWF0b3IgPSB0aGlzLmJ3RXN0aW1hdG9yO1xuICAgIGNvbnN0IHVzZUVzdGltYXRlID0gYndFc3RpbWF0b3IuY2FuRXN0aW1hdGUoKTtcbiAgICBjb25zdCBsb2FkZWRGaXJzdEZyYWcgPSB0aGlzLmxhc3RMb2FkZWRGcmFnTGV2ZWwgPiAtMTtcbiAgICAvLyBpbiBjYXNlIG5leHQgYXV0byBsZXZlbCBoYXMgYmVlbiBmb3JjZWQsIGFuZCBidyBub3QgYXZhaWxhYmxlIG9yIG5vdCByZWxpYWJsZSwgcmV0dXJuIGZvcmNlZCB2YWx1ZVxuICAgIGlmIChmb3JjZWRBdXRvTGV2ZWwgIT09IC0xICYmICghdXNlRXN0aW1hdGUgfHwgIWxvYWRlZEZpcnN0RnJhZyB8fCB0aGlzLm5leHRBdXRvTGV2ZWxLZXkgPT09IHRoaXMuZ2V0QXV0b0xldmVsS2V5KCkpKSB7XG4gICAgICByZXR1cm4gZm9yY2VkQXV0b0xldmVsO1xuICAgIH1cblxuICAgIC8vIGNvbXB1dGUgbmV4dCBsZXZlbCB1c2luZyBBQlIgbG9naWNcbiAgICBjb25zdCBuZXh0QUJSQXV0b0xldmVsID0gdXNlRXN0aW1hdGUgJiYgbG9hZGVkRmlyc3RGcmFnID8gdGhpcy5nZXROZXh0QUJSQXV0b0xldmVsKCkgOiB0aGlzLmZpcnN0QXV0b0xldmVsO1xuXG4gICAgLy8gdXNlIGZvcmNlZCBhdXRvIGxldmVsIHdoaWxlIGl0IGhhc24ndCBlcnJvcmVkIG1vcmUgdGhhbiBBQlIgc2VsZWN0aW9uXG4gICAgaWYgKGZvcmNlZEF1dG9MZXZlbCAhPT0gLTEpIHtcbiAgICAgIGNvbnN0IGxldmVscyA9IHRoaXMuaGxzLmxldmVscztcbiAgICAgIGlmIChsZXZlbHMubGVuZ3RoID4gTWF0aC5tYXgoZm9yY2VkQXV0b0xldmVsLCBuZXh0QUJSQXV0b0xldmVsKSAmJiBsZXZlbHNbZm9yY2VkQXV0b0xldmVsXS5sb2FkRXJyb3IgPD0gbGV2ZWxzW25leHRBQlJBdXRvTGV2ZWxdLmxvYWRFcnJvcikge1xuICAgICAgICByZXR1cm4gZm9yY2VkQXV0b0xldmVsO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHNhdmUgcmVzdWx0IHVudGlsIHN0YXRlIGhhcyBjaGFuZ2VkXG4gICAgdGhpcy5fbmV4dEF1dG9MZXZlbCA9IG5leHRBQlJBdXRvTGV2ZWw7XG4gICAgdGhpcy5uZXh0QXV0b0xldmVsS2V5ID0gdGhpcy5nZXRBdXRvTGV2ZWxLZXkoKTtcbiAgICByZXR1cm4gbmV4dEFCUkF1dG9MZXZlbDtcbiAgfVxuICBnZXRBdXRvTGV2ZWxLZXkoKSB7XG4gICAgcmV0dXJuIGAke3RoaXMuZ2V0QndFc3RpbWF0ZSgpfV8ke3RoaXMuZ2V0U3RhcnZhdGlvbkRlbGF5KCkudG9GaXhlZCgyKX1gO1xuICB9XG4gIGdldE5leHRBQlJBdXRvTGV2ZWwoKSB7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZ0N1cnJlbnQsXG4gICAgICBwYXJ0Q3VycmVudCxcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIG1heEF1dG9MZXZlbCxcbiAgICAgIGNvbmZpZyxcbiAgICAgIG1pbkF1dG9MZXZlbFxuICAgIH0gPSBobHM7XG4gICAgY29uc3QgY3VycmVudEZyYWdEdXJhdGlvbiA9IHBhcnRDdXJyZW50ID8gcGFydEN1cnJlbnQuZHVyYXRpb24gOiBmcmFnQ3VycmVudCA/IGZyYWdDdXJyZW50LmR1cmF0aW9uIDogMDtcbiAgICBjb25zdCBhdmdidyA9IHRoaXMuZ2V0QndFc3RpbWF0ZSgpO1xuICAgIC8vIGJ1ZmZlclN0YXJ2YXRpb25EZWxheSBpcyB0aGUgd2FsbC1jbG9jayB0aW1lIGxlZnQgdW50aWwgdGhlIHBsYXliYWNrIGJ1ZmZlciBpcyBleGhhdXN0ZWQuXG4gICAgY29uc3QgYnVmZmVyU3RhcnZhdGlvbkRlbGF5ID0gdGhpcy5nZXRTdGFydmF0aW9uRGVsYXkoKTtcbiAgICBsZXQgYndGYWN0b3IgPSBjb25maWcuYWJyQmFuZFdpZHRoRmFjdG9yO1xuICAgIGxldCBid1VwRmFjdG9yID0gY29uZmlnLmFickJhbmRXaWR0aFVwRmFjdG9yO1xuXG4gICAgLy8gRmlyc3QsIGxvb2sgdG8gc2VlIGlmIHdlIGNhbiBmaW5kIGEgbGV2ZWwgbWF0Y2hpbmcgd2l0aCBvdXIgYXZnIGJhbmR3aWR0aCBBTkQgdGhhdCBjb3VsZCBhbHNvIGd1YXJhbnRlZSBubyByZWJ1ZmZlcmluZyBhdCBhbGxcbiAgICBpZiAoYnVmZmVyU3RhcnZhdGlvbkRlbGF5KSB7XG4gICAgICBjb25zdCBfYmVzdExldmVsID0gdGhpcy5maW5kQmVzdExldmVsKGF2Z2J3LCBtaW5BdXRvTGV2ZWwsIG1heEF1dG9MZXZlbCwgYnVmZmVyU3RhcnZhdGlvbkRlbGF5LCAwLCBid0ZhY3RvciwgYndVcEZhY3Rvcik7XG4gICAgICBpZiAoX2Jlc3RMZXZlbCA+PSAwKSB7XG4gICAgICAgIHJldHVybiBfYmVzdExldmVsO1xuICAgICAgfVxuICAgIH1cbiAgICAvLyBub3QgcG9zc2libGUgdG8gZ2V0IHJpZCBvZiByZWJ1ZmZlcmluZy4uLiB0cnkgdG8gZmluZCBsZXZlbCB0aGF0IHdpbGwgZ3VhcmFudGVlIGxlc3MgdGhhbiBtYXhTdGFydmF0aW9uRGVsYXkgb2YgcmVidWZmZXJpbmdcbiAgICBsZXQgbWF4U3RhcnZhdGlvbkRlbGF5ID0gY3VycmVudEZyYWdEdXJhdGlvbiA/IE1hdGgubWluKGN1cnJlbnRGcmFnRHVyYXRpb24sIGNvbmZpZy5tYXhTdGFydmF0aW9uRGVsYXkpIDogY29uZmlnLm1heFN0YXJ2YXRpb25EZWxheTtcbiAgICBpZiAoIWJ1ZmZlclN0YXJ2YXRpb25EZWxheSkge1xuICAgICAgLy8gaW4gY2FzZSBidWZmZXIgaXMgZW1wdHksIGxldCdzIGNoZWNrIGlmIHByZXZpb3VzIGZyYWdtZW50IHdhcyBsb2FkZWQgdG8gcGVyZm9ybSBhIGJpdHJhdGUgdGVzdFxuICAgICAgY29uc3QgYml0cmF0ZVRlc3REZWxheSA9IHRoaXMuYml0cmF0ZVRlc3REZWxheTtcbiAgICAgIGlmIChiaXRyYXRlVGVzdERlbGF5KSB7XG4gICAgICAgIC8vIGlmIGl0IGlzIHRoZSBjYXNlLCB0aGVuIHdlIG5lZWQgdG8gYWRqdXN0IG91ciBtYXggc3RhcnZhdGlvbiBkZWxheSB1c2luZyBtYXhMb2FkaW5nRGVsYXkgY29uZmlnIHZhbHVlXG4gICAgICAgIC8vIG1heCB2aWRlbyBsb2FkaW5nIGRlbGF5IHVzZWQgaW4gIGF1dG9tYXRpYyBzdGFydCBsZXZlbCBzZWxlY3Rpb24gOlxuICAgICAgICAvLyBpbiB0aGF0IG1vZGUgQUJSIGNvbnRyb2xsZXIgd2lsbCBlbnN1cmUgdGhhdCB2aWRlbyBsb2FkaW5nIHRpbWUgKGllIHRoZSB0aW1lIHRvIGZldGNoIHRoZSBmaXJzdCBmcmFnbWVudCBhdCBsb3dlc3QgcXVhbGl0eSBsZXZlbCArXG4gICAgICAgIC8vIHRoZSB0aW1lIHRvIGZldGNoIHRoZSBmcmFnbWVudCBhdCB0aGUgYXBwcm9wcmlhdGUgcXVhbGl0eSBsZXZlbCBpcyBsZXNzIHRoYW4gYGBgbWF4TG9hZGluZ0RlbGF5YGBgIClcbiAgICAgICAgLy8gY2FwIG1heExvYWRpbmdEZWxheSBhbmQgZW5zdXJlIGl0IGlzIG5vdCBiaWdnZXIgJ3RoYW4gYml0cmF0ZSB0ZXN0JyBmcmFnIGR1cmF0aW9uXG4gICAgICAgIGNvbnN0IG1heExvYWRpbmdEZWxheSA9IGN1cnJlbnRGcmFnRHVyYXRpb24gPyBNYXRoLm1pbihjdXJyZW50RnJhZ0R1cmF0aW9uLCBjb25maWcubWF4TG9hZGluZ0RlbGF5KSA6IGNvbmZpZy5tYXhMb2FkaW5nRGVsYXk7XG4gICAgICAgIG1heFN0YXJ2YXRpb25EZWxheSA9IG1heExvYWRpbmdEZWxheSAtIGJpdHJhdGVUZXN0RGVsYXk7XG4gICAgICAgIGxvZ2dlci5pbmZvKGBbYWJyXSBiaXRyYXRlIHRlc3QgdG9vayAke01hdGgucm91bmQoMTAwMCAqIGJpdHJhdGVUZXN0RGVsYXkpfW1zLCBzZXQgZmlyc3QgZnJhZ21lbnQgbWF4IGZldGNoRHVyYXRpb24gdG8gJHtNYXRoLnJvdW5kKDEwMDAgKiBtYXhTdGFydmF0aW9uRGVsYXkpfSBtc2ApO1xuICAgICAgICAvLyBkb24ndCB1c2UgY29uc2VydmF0aXZlIGZhY3RvciBvbiBiaXRyYXRlIHRlc3RcbiAgICAgICAgYndGYWN0b3IgPSBid1VwRmFjdG9yID0gMTtcbiAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgYmVzdExldmVsID0gdGhpcy5maW5kQmVzdExldmVsKGF2Z2J3LCBtaW5BdXRvTGV2ZWwsIG1heEF1dG9MZXZlbCwgYnVmZmVyU3RhcnZhdGlvbkRlbGF5LCBtYXhTdGFydmF0aW9uRGVsYXksIGJ3RmFjdG9yLCBid1VwRmFjdG9yKTtcbiAgICBsb2dnZXIuaW5mbyhgW2Ficl0gJHtidWZmZXJTdGFydmF0aW9uRGVsYXkgPyAncmVidWZmZXJpbmcgZXhwZWN0ZWQnIDogJ2J1ZmZlciBpcyBlbXB0eSd9LCBvcHRpbWFsIHF1YWxpdHkgbGV2ZWwgJHtiZXN0TGV2ZWx9YCk7XG4gICAgaWYgKGJlc3RMZXZlbCA+IC0xKSB7XG4gICAgICByZXR1cm4gYmVzdExldmVsO1xuICAgIH1cbiAgICAvLyBJZiBubyBtYXRjaGluZyBsZXZlbCBmb3VuZCwgc2VlIGlmIG1pbiBhdXRvIGxldmVsIHdvdWxkIGJlIGEgYmV0dGVyIG9wdGlvblxuICAgIGNvbnN0IG1pbkxldmVsID0gaGxzLmxldmVsc1ttaW5BdXRvTGV2ZWxdO1xuICAgIGNvbnN0IGF1dG9MZXZlbCA9IGhscy5sZXZlbHNbaGxzLmxvYWRMZXZlbF07XG4gICAgaWYgKChtaW5MZXZlbCA9PSBudWxsID8gdm9pZCAwIDogbWluTGV2ZWwuYml0cmF0ZSkgPCAoYXV0b0xldmVsID09IG51bGwgPyB2b2lkIDAgOiBhdXRvTGV2ZWwuYml0cmF0ZSkpIHtcbiAgICAgIHJldHVybiBtaW5BdXRvTGV2ZWw7XG4gICAgfVxuICAgIC8vIG9yIGlmIGJpdHJhdGUgaXMgbm90IGxvd2VyLCBjb250aW51ZSB0byB1c2UgbG9hZExldmVsXG4gICAgcmV0dXJuIGhscy5sb2FkTGV2ZWw7XG4gIH1cbiAgZ2V0U3RhcnZhdGlvbkRlbGF5KCkge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IG1lZGlhID0gaGxzLm1lZGlhO1xuICAgIGlmICghbWVkaWEpIHtcbiAgICAgIHJldHVybiBJbmZpbml0eTtcbiAgICB9XG4gICAgLy8gcGxheWJhY2tSYXRlIGlzIHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgcGxheWJhY2sgcmF0ZTsgaWYgbWVkaWEucGxheWJhY2tSYXRlIGlzIDAsIHdlIHVzZSAxIHRvIGxvYWQgYXNcbiAgICAvLyBpZiB3ZSdyZSBwbGF5aW5nIGJhY2sgYXQgdGhlIG5vcm1hbCByYXRlLlxuICAgIGNvbnN0IHBsYXliYWNrUmF0ZSA9IG1lZGlhICYmIG1lZGlhLnBsYXliYWNrUmF0ZSAhPT0gMCA/IE1hdGguYWJzKG1lZGlhLnBsYXliYWNrUmF0ZSkgOiAxLjA7XG4gICAgY29uc3QgYnVmZmVySW5mbyA9IGhscy5tYWluRm9yd2FyZEJ1ZmZlckluZm87XG4gICAgcmV0dXJuIChidWZmZXJJbmZvID8gYnVmZmVySW5mby5sZW4gOiAwKSAvIHBsYXliYWNrUmF0ZTtcbiAgfVxuICBnZXRCd0VzdGltYXRlKCkge1xuICAgIHJldHVybiB0aGlzLmJ3RXN0aW1hdG9yLmNhbkVzdGltYXRlKCkgPyB0aGlzLmJ3RXN0aW1hdG9yLmdldEVzdGltYXRlKCkgOiB0aGlzLmhscy5jb25maWcuYWJyRXdtYURlZmF1bHRFc3RpbWF0ZTtcbiAgfVxuICBmaW5kQmVzdExldmVsKGN1cnJlbnRCdywgbWluQXV0b0xldmVsLCBtYXhBdXRvTGV2ZWwsIGJ1ZmZlclN0YXJ2YXRpb25EZWxheSwgbWF4U3RhcnZhdGlvbkRlbGF5LCBid0ZhY3RvciwgYndVcEZhY3Rvcikge1xuICAgIHZhciBfbGV2ZWwkZGV0YWlscztcbiAgICBjb25zdCBtYXhGZXRjaER1cmF0aW9uID0gYnVmZmVyU3RhcnZhdGlvbkRlbGF5ICsgbWF4U3RhcnZhdGlvbkRlbGF5O1xuICAgIGNvbnN0IGxhc3RMb2FkZWRGcmFnTGV2ZWwgPSB0aGlzLmxhc3RMb2FkZWRGcmFnTGV2ZWw7XG4gICAgY29uc3Qgc2VsZWN0aW9uQmFzZUxldmVsID0gbGFzdExvYWRlZEZyYWdMZXZlbCA9PT0gLTEgPyB0aGlzLmhscy5maXJzdExldmVsIDogbGFzdExvYWRlZEZyYWdMZXZlbDtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnQ3VycmVudCxcbiAgICAgIHBhcnRDdXJyZW50XG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3Qge1xuICAgICAgbGV2ZWxzLFxuICAgICAgYWxsQXVkaW9UcmFja3MsXG4gICAgICBsb2FkTGV2ZWwsXG4gICAgICBjb25maWdcbiAgICB9ID0gdGhpcy5obHM7XG4gICAgaWYgKGxldmVscy5sZW5ndGggPT09IDEpIHtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cbiAgICBjb25zdCBsZXZlbCA9IGxldmVsc1tzZWxlY3Rpb25CYXNlTGV2ZWxdO1xuICAgIGNvbnN0IGxpdmUgPSAhIShsZXZlbCAhPSBudWxsICYmIChfbGV2ZWwkZGV0YWlscyA9IGxldmVsLmRldGFpbHMpICE9IG51bGwgJiYgX2xldmVsJGRldGFpbHMubGl2ZSk7XG4gICAgY29uc3QgZmlyc3RTZWxlY3Rpb24gPSBsb2FkTGV2ZWwgPT09IC0xIHx8IGxhc3RMb2FkZWRGcmFnTGV2ZWwgPT09IC0xO1xuICAgIGxldCBjdXJyZW50Q29kZWNTZXQ7XG4gICAgbGV0IGN1cnJlbnRWaWRlb1JhbmdlID0gJ1NEUic7XG4gICAgbGV0IGN1cnJlbnRGcmFtZVJhdGUgPSAobGV2ZWwgPT0gbnVsbCA/IHZvaWQgMCA6IGxldmVsLmZyYW1lUmF0ZSkgfHwgMDtcbiAgICBjb25zdCB7XG4gICAgICBhdWRpb1ByZWZlcmVuY2UsXG4gICAgICB2aWRlb1ByZWZlcmVuY2VcbiAgICB9ID0gY29uZmlnO1xuICAgIGNvbnN0IGF1ZGlvVHJhY2tzQnlHcm91cCA9IHRoaXMuYXVkaW9UcmFja3NCeUdyb3VwIHx8ICh0aGlzLmF1ZGlvVHJhY2tzQnlHcm91cCA9IGdldEF1ZGlvVHJhY2tzQnlHcm91cChhbGxBdWRpb1RyYWNrcykpO1xuICAgIGlmIChmaXJzdFNlbGVjdGlvbikge1xuICAgICAgaWYgKHRoaXMuZmlyc3RTZWxlY3Rpb24gIT09IC0xKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmZpcnN0U2VsZWN0aW9uO1xuICAgICAgfVxuICAgICAgY29uc3QgY29kZWNUaWVycyA9IHRoaXMuY29kZWNUaWVycyB8fCAodGhpcy5jb2RlY1RpZXJzID0gZ2V0Q29kZWNUaWVycyhsZXZlbHMsIGF1ZGlvVHJhY2tzQnlHcm91cCwgbWluQXV0b0xldmVsLCBtYXhBdXRvTGV2ZWwpKTtcbiAgICAgIGNvbnN0IHN0YXJ0VGllciA9IGdldFN0YXJ0Q29kZWNUaWVyKGNvZGVjVGllcnMsIGN1cnJlbnRWaWRlb1JhbmdlLCBjdXJyZW50QncsIGF1ZGlvUHJlZmVyZW5jZSwgdmlkZW9QcmVmZXJlbmNlKTtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgY29kZWNTZXQsXG4gICAgICAgIHZpZGVvUmFuZ2VzLFxuICAgICAgICBtaW5GcmFtZXJhdGUsXG4gICAgICAgIG1pbkJpdHJhdGUsXG4gICAgICAgIHByZWZlckhEUlxuICAgICAgfSA9IHN0YXJ0VGllcjtcbiAgICAgIGN1cnJlbnRDb2RlY1NldCA9IGNvZGVjU2V0O1xuICAgICAgY3VycmVudFZpZGVvUmFuZ2UgPSBwcmVmZXJIRFIgPyB2aWRlb1Jhbmdlc1t2aWRlb1Jhbmdlcy5sZW5ndGggLSAxXSA6IHZpZGVvUmFuZ2VzWzBdO1xuICAgICAgY3VycmVudEZyYW1lUmF0ZSA9IG1pbkZyYW1lcmF0ZTtcbiAgICAgIGN1cnJlbnRCdyA9IE1hdGgubWF4KGN1cnJlbnRCdywgbWluQml0cmF0ZSk7XG4gICAgICBsb2dnZXIubG9nKGBbYWJyXSBwaWNrZWQgc3RhcnQgdGllciAke0pTT04uc3RyaW5naWZ5KHN0YXJ0VGllcil9YCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGN1cnJlbnRDb2RlY1NldCA9IGxldmVsID09IG51bGwgPyB2b2lkIDAgOiBsZXZlbC5jb2RlY1NldDtcbiAgICAgIGN1cnJlbnRWaWRlb1JhbmdlID0gbGV2ZWwgPT0gbnVsbCA/IHZvaWQgMCA6IGxldmVsLnZpZGVvUmFuZ2U7XG4gICAgfVxuICAgIGNvbnN0IGN1cnJlbnRGcmFnRHVyYXRpb24gPSBwYXJ0Q3VycmVudCA/IHBhcnRDdXJyZW50LmR1cmF0aW9uIDogZnJhZ0N1cnJlbnQgPyBmcmFnQ3VycmVudC5kdXJhdGlvbiA6IDA7XG4gICAgY29uc3QgdHRmYkVzdGltYXRlU2VjID0gdGhpcy5id0VzdGltYXRvci5nZXRFc3RpbWF0ZVRURkIoKSAvIDEwMDA7XG4gICAgY29uc3QgbGV2ZWxzU2tpcHBlZCA9IFtdO1xuICAgIGZvciAobGV0IGkgPSBtYXhBdXRvTGV2ZWw7IGkgPj0gbWluQXV0b0xldmVsOyBpLS0pIHtcbiAgICAgIHZhciBfbGV2ZWxJbmZvJHN1cHBvcnRlZFI7XG4gICAgICBjb25zdCBsZXZlbEluZm8gPSBsZXZlbHNbaV07XG4gICAgICBjb25zdCB1cFN3aXRjaCA9IGkgPiBzZWxlY3Rpb25CYXNlTGV2ZWw7XG4gICAgICBpZiAoIWxldmVsSW5mbykge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmIChjb25maWcudXNlTWVkaWFDYXBhYmlsaXRpZXMgJiYgIWxldmVsSW5mby5zdXBwb3J0ZWRSZXN1bHQgJiYgIWxldmVsSW5mby5zdXBwb3J0ZWRQcm9taXNlKSB7XG4gICAgICAgIGNvbnN0IG1lZGlhQ2FwYWJpbGl0aWVzID0gbmF2aWdhdG9yLm1lZGlhQ2FwYWJpbGl0aWVzO1xuICAgICAgICBpZiAodHlwZW9mIChtZWRpYUNhcGFiaWxpdGllcyA9PSBudWxsID8gdm9pZCAwIDogbWVkaWFDYXBhYmlsaXRpZXMuZGVjb2RpbmdJbmZvKSA9PT0gJ2Z1bmN0aW9uJyAmJiByZXF1aXJlc01lZGlhQ2FwYWJpbGl0aWVzRGVjb2RpbmdJbmZvKGxldmVsSW5mbywgYXVkaW9UcmFja3NCeUdyb3VwLCBjdXJyZW50VmlkZW9SYW5nZSwgY3VycmVudEZyYW1lUmF0ZSwgY3VycmVudEJ3LCBhdWRpb1ByZWZlcmVuY2UpKSB7XG4gICAgICAgICAgbGV2ZWxJbmZvLnN1cHBvcnRlZFByb21pc2UgPSBnZXRNZWRpYURlY29kaW5nSW5mb1Byb21pc2UobGV2ZWxJbmZvLCBhdWRpb1RyYWNrc0J5R3JvdXAsIG1lZGlhQ2FwYWJpbGl0aWVzKTtcbiAgICAgICAgICBsZXZlbEluZm8uc3VwcG9ydGVkUHJvbWlzZS50aGVuKGRlY29kaW5nSW5mbyA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMuaGxzKSB7XG4gICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxldmVsSW5mby5zdXBwb3J0ZWRSZXN1bHQgPSBkZWNvZGluZ0luZm87XG4gICAgICAgICAgICBjb25zdCBsZXZlbHMgPSB0aGlzLmhscy5sZXZlbHM7XG4gICAgICAgICAgICBjb25zdCBpbmRleCA9IGxldmVscy5pbmRleE9mKGxldmVsSW5mbyk7XG4gICAgICAgICAgICBpZiAoZGVjb2RpbmdJbmZvLmVycm9yKSB7XG4gICAgICAgICAgICAgIGxvZ2dlci53YXJuKGBbYWJyXSBNZWRpYUNhcGFiaWxpdGllcyBkZWNvZGluZ0luZm8gZXJyb3I6IFwiJHtkZWNvZGluZ0luZm8uZXJyb3J9XCIgZm9yIGxldmVsICR7aW5kZXh9ICR7SlNPTi5zdHJpbmdpZnkoZGVjb2RpbmdJbmZvKX1gKTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoIWRlY29kaW5nSW5mby5zdXBwb3J0ZWQpIHtcbiAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYFthYnJdIFVuc3VwcG9ydGVkIE1lZGlhQ2FwYWJpbGl0aWVzIGRlY29kaW5nSW5mbyByZXN1bHQgZm9yIGxldmVsICR7aW5kZXh9ICR7SlNPTi5zdHJpbmdpZnkoZGVjb2RpbmdJbmZvKX1gKTtcbiAgICAgICAgICAgICAgaWYgKGluZGV4ID4gLTEgJiYgbGV2ZWxzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICAgICAgICBsb2dnZXIubG9nKGBbYWJyXSBSZW1vdmluZyB1bnN1cHBvcnRlZCBsZXZlbCAke2luZGV4fWApO1xuICAgICAgICAgICAgICAgIHRoaXMuaGxzLnJlbW92ZUxldmVsKGluZGV4KTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGxldmVsSW5mby5zdXBwb3J0ZWRSZXN1bHQgPSBTVVBQT1JURURfSU5GT19ERUZBVUxUO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIHNraXAgY2FuZGlkYXRlcyB3aGljaCBjaGFuZ2UgY29kZWMtZmFtaWx5IG9yIHZpZGVvLXJhbmdlLFxuICAgICAgLy8gYW5kIHdoaWNoIGRlY3JlYXNlIG9yIGluY3JlYXNlIGZyYW1lLXJhdGUgZm9yIHVwIGFuZCBkb3duLXN3aXRjaCByZXNwZWN0ZnVsbHlcbiAgICAgIGlmIChjdXJyZW50Q29kZWNTZXQgJiYgbGV2ZWxJbmZvLmNvZGVjU2V0ICE9PSBjdXJyZW50Q29kZWNTZXQgfHwgY3VycmVudFZpZGVvUmFuZ2UgJiYgbGV2ZWxJbmZvLnZpZGVvUmFuZ2UgIT09IGN1cnJlbnRWaWRlb1JhbmdlIHx8IHVwU3dpdGNoICYmIGN1cnJlbnRGcmFtZVJhdGUgPiBsZXZlbEluZm8uZnJhbWVSYXRlIHx8ICF1cFN3aXRjaCAmJiBjdXJyZW50RnJhbWVSYXRlID4gMCAmJiBjdXJyZW50RnJhbWVSYXRlIDwgbGV2ZWxJbmZvLmZyYW1lUmF0ZSB8fCBsZXZlbEluZm8uc3VwcG9ydGVkUmVzdWx0ICYmICEoKF9sZXZlbEluZm8kc3VwcG9ydGVkUiA9IGxldmVsSW5mby5zdXBwb3J0ZWRSZXN1bHQuZGVjb2RpbmdJbmZvUmVzdWx0cykgIT0gbnVsbCAmJiBfbGV2ZWxJbmZvJHN1cHBvcnRlZFJbMF0uc21vb3RoKSkge1xuICAgICAgICBsZXZlbHNTa2lwcGVkLnB1c2goaSk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgY29uc3QgbGV2ZWxEZXRhaWxzID0gbGV2ZWxJbmZvLmRldGFpbHM7XG4gICAgICBjb25zdCBhdmdEdXJhdGlvbiA9IChwYXJ0Q3VycmVudCA/IGxldmVsRGV0YWlscyA9PSBudWxsID8gdm9pZCAwIDogbGV2ZWxEZXRhaWxzLnBhcnRUYXJnZXQgOiBsZXZlbERldGFpbHMgPT0gbnVsbCA/IHZvaWQgMCA6IGxldmVsRGV0YWlscy5hdmVyYWdldGFyZ2V0ZHVyYXRpb24pIHx8IGN1cnJlbnRGcmFnRHVyYXRpb247XG4gICAgICBsZXQgYWRqdXN0ZWRidztcbiAgICAgIC8vIGZvbGxvdyBhbGdvcml0aG0gY2FwdHVyZWQgZnJvbSBzdGFnZWZyaWdodCA6XG4gICAgICAvLyBodHRwczovL2FuZHJvaWQuZ29vZ2xlc291cmNlLmNvbS9wbGF0Zm9ybS9mcmFtZXdvcmtzL2F2LysvbWFzdGVyL21lZGlhL2xpYnN0YWdlZnJpZ2h0L2h0dHBsaXZlL0xpdmVTZXNzaW9uLmNwcFxuICAgICAgLy8gUGljayB0aGUgaGlnaGVzdCBiYW5kd2lkdGggc3RyZWFtIGJlbG93IG9yIGVxdWFsIHRvIGVzdGltYXRlZCBiYW5kd2lkdGguXG4gICAgICAvLyBjb25zaWRlciBvbmx5IDgwJSBvZiB0aGUgYXZhaWxhYmxlIGJhbmR3aWR0aCwgYnV0IGlmIHdlIGFyZSBzd2l0Y2hpbmcgdXAsXG4gICAgICAvLyBiZSBldmVuIG1vcmUgY29uc2VydmF0aXZlICg3MCUpIHRvIGF2b2lkIG92ZXJlc3RpbWF0aW5nIGFuZCBpbW1lZGlhdGVseVxuICAgICAgLy8gc3dpdGNoaW5nIGJhY2suXG4gICAgICBpZiAoIXVwU3dpdGNoKSB7XG4gICAgICAgIGFkanVzdGVkYncgPSBid0ZhY3RvciAqIGN1cnJlbnRCdztcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGFkanVzdGVkYncgPSBid1VwRmFjdG9yICogY3VycmVudEJ3O1xuICAgICAgfVxuXG4gICAgICAvLyBVc2UgYXZlcmFnZSBiaXRyYXRlIHdoZW4gc3RhcnZhdGlvbiBkZWxheSAoYnVmZmVyIGxlbmd0aCkgaXMgZ3Qgb3IgZXEgdHdvIHNlZ21lbnQgZHVyYXRpb25zIGFuZCByZWJ1ZmZlcmluZyBpcyBub3QgZXhwZWN0ZWQgKG1heFN0YXJ2YXRpb25EZWxheSA+IDApXG4gICAgICBjb25zdCBiaXRyYXRlID0gY3VycmVudEZyYWdEdXJhdGlvbiAmJiBidWZmZXJTdGFydmF0aW9uRGVsYXkgPj0gY3VycmVudEZyYWdEdXJhdGlvbiAqIDIgJiYgbWF4U3RhcnZhdGlvbkRlbGF5ID09PSAwID8gbGV2ZWxzW2ldLmF2ZXJhZ2VCaXRyYXRlIDogbGV2ZWxzW2ldLm1heEJpdHJhdGU7XG4gICAgICBjb25zdCBmZXRjaER1cmF0aW9uID0gdGhpcy5nZXRUaW1lVG9Mb2FkRnJhZyh0dGZiRXN0aW1hdGVTZWMsIGFkanVzdGVkYncsIGJpdHJhdGUgKiBhdmdEdXJhdGlvbiwgbGV2ZWxEZXRhaWxzID09PSB1bmRlZmluZWQpO1xuICAgICAgY29uc3QgY2FuU3dpdGNoV2l0aGluVG9sZXJhbmNlID1cbiAgICAgIC8vIGlmIGFkanVzdGVkIGJ3IGlzIGdyZWF0ZXIgdGhhbiBsZXZlbCBiaXRyYXRlIEFORFxuICAgICAgYWRqdXN0ZWRidyA+PSBiaXRyYXRlICYmIChcbiAgICAgIC8vIG5vIGxldmVsIGNoYW5nZSwgb3IgbmV3IGxldmVsIGhhcyBubyBlcnJvciBoaXN0b3J5XG4gICAgICBpID09PSBsYXN0TG9hZGVkRnJhZ0xldmVsIHx8IGxldmVsSW5mby5sb2FkRXJyb3IgPT09IDAgJiYgbGV2ZWxJbmZvLmZyYWdtZW50RXJyb3IgPT09IDApICYmIChcbiAgICAgIC8vIGZyYWdtZW50IGZldGNoRHVyYXRpb24gdW5rbm93biBPUiBsaXZlIHN0cmVhbSBPUiBmcmFnbWVudCBmZXRjaER1cmF0aW9uIGxlc3MgdGhhbiBtYXggYWxsb3dlZCBmZXRjaCBkdXJhdGlvbiwgdGhlbiB0aGlzIGxldmVsIG1hdGNoZXNcbiAgICAgIC8vIHdlIGRvbid0IGFjY291bnQgZm9yIG1heCBGZXRjaCBEdXJhdGlvbiBmb3IgbGl2ZSBzdHJlYW1zLCB0aGlzIGlzIHRvIGF2b2lkIHN3aXRjaGluZyBkb3duIHdoZW4gbmVhciB0aGUgZWRnZSBvZiBsaXZlIHNsaWRpbmcgd2luZG93IC4uLlxuICAgICAgLy8gc3BlY2lhbCBjYXNlIHRvIHN1cHBvcnQgc3RhcnRMZXZlbCA9IC0xIChiaXRyYXRlVGVzdCkgb24gbGl2ZSBzdHJlYW1zIDogaW4gdGhhdCBjYXNlIHdlIHNob3VsZCBub3QgZXhpdCBsb29wIHNvIHRoYXQgZmluZEJlc3RMZXZlbCB3aWxsIHJldHVybiAtMVxuICAgICAgZmV0Y2hEdXJhdGlvbiA8PSB0dGZiRXN0aW1hdGVTZWMgfHwgIWlzRmluaXRlTnVtYmVyKGZldGNoRHVyYXRpb24pIHx8IGxpdmUgJiYgIXRoaXMuYml0cmF0ZVRlc3REZWxheSB8fCBmZXRjaER1cmF0aW9uIDwgbWF4RmV0Y2hEdXJhdGlvbik7XG4gICAgICBpZiAoY2FuU3dpdGNoV2l0aGluVG9sZXJhbmNlKSB7XG4gICAgICAgIGNvbnN0IGZvcmNlZEF1dG9MZXZlbCA9IHRoaXMuZm9yY2VkQXV0b0xldmVsO1xuICAgICAgICBpZiAoaSAhPT0gbG9hZExldmVsICYmIChmb3JjZWRBdXRvTGV2ZWwgPT09IC0xIHx8IGZvcmNlZEF1dG9MZXZlbCAhPT0gbG9hZExldmVsKSkge1xuICAgICAgICAgIGlmIChsZXZlbHNTa2lwcGVkLmxlbmd0aCkge1xuICAgICAgICAgICAgbG9nZ2VyLnRyYWNlKGBbYWJyXSBTa2lwcGVkIGxldmVsKHMpICR7bGV2ZWxzU2tpcHBlZC5qb2luKCcsJyl9IG9mICR7bWF4QXV0b0xldmVsfSBtYXggd2l0aCBDT0RFQ1MgYW5kIFZJREVPLVJBTkdFOlwiJHtsZXZlbHNbbGV2ZWxzU2tpcHBlZFswXV0uY29kZWNzfVwiICR7bGV2ZWxzW2xldmVsc1NraXBwZWRbMF1dLnZpZGVvUmFuZ2V9OyBub3QgY29tcGF0aWJsZSB3aXRoIFwiJHtsZXZlbC5jb2RlY3N9XCIgJHtjdXJyZW50VmlkZW9SYW5nZX1gKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgbG9nZ2VyLmluZm8oYFthYnJdIHN3aXRjaCBjYW5kaWRhdGU6JHtzZWxlY3Rpb25CYXNlTGV2ZWx9LT4ke2l9IGFkanVzdGVkYncoJHtNYXRoLnJvdW5kKGFkanVzdGVkYncpfSktYml0cmF0ZT0ke01hdGgucm91bmQoYWRqdXN0ZWRidyAtIGJpdHJhdGUpfSB0dGZiOiR7dHRmYkVzdGltYXRlU2VjLnRvRml4ZWQoMSl9IGF2Z0R1cmF0aW9uOiR7YXZnRHVyYXRpb24udG9GaXhlZCgxKX0gbWF4RmV0Y2hEdXJhdGlvbjoke21heEZldGNoRHVyYXRpb24udG9GaXhlZCgxKX0gZmV0Y2hEdXJhdGlvbjoke2ZldGNoRHVyYXRpb24udG9GaXhlZCgxKX0gZmlyc3RTZWxlY3Rpb246JHtmaXJzdFNlbGVjdGlvbn0gY29kZWNTZXQ6JHtjdXJyZW50Q29kZWNTZXR9IHZpZGVvUmFuZ2U6JHtjdXJyZW50VmlkZW9SYW5nZX0gaGxzLmxvYWRMZXZlbDoke2xvYWRMZXZlbH1gKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoZmlyc3RTZWxlY3Rpb24pIHtcbiAgICAgICAgICB0aGlzLmZpcnN0U2VsZWN0aW9uID0gaTtcbiAgICAgICAgfVxuICAgICAgICAvLyBhcyB3ZSBhcmUgbG9vcGluZyBmcm9tIGhpZ2hlc3QgdG8gbG93ZXN0LCB0aGlzIHdpbGwgcmV0dXJuIHRoZSBiZXN0IGFjaGlldmFibGUgcXVhbGl0eSBsZXZlbFxuICAgICAgICByZXR1cm4gaTtcbiAgICAgIH1cbiAgICB9XG4gICAgLy8gbm90IGVub3VnaCB0aW1lIGJ1ZGdldCBldmVuIHdpdGggcXVhbGl0eSBsZXZlbCAwIC4uLiByZWJ1ZmZlcmluZyBtaWdodCBoYXBwZW5cbiAgICByZXR1cm4gLTE7XG4gIH1cbiAgc2V0IG5leHRBdXRvTGV2ZWwobmV4dExldmVsKSB7XG4gICAgY29uc3Qge1xuICAgICAgbWF4QXV0b0xldmVsLFxuICAgICAgbWluQXV0b0xldmVsXG4gICAgfSA9IHRoaXMuaGxzO1xuICAgIGNvbnN0IHZhbHVlID0gTWF0aC5taW4oTWF0aC5tYXgobmV4dExldmVsLCBtaW5BdXRvTGV2ZWwpLCBtYXhBdXRvTGV2ZWwpO1xuICAgIGlmICh0aGlzLl9uZXh0QXV0b0xldmVsICE9PSB2YWx1ZSkge1xuICAgICAgdGhpcy5uZXh0QXV0b0xldmVsS2V5ID0gJyc7XG4gICAgICB0aGlzLl9uZXh0QXV0b0xldmVsID0gdmFsdWU7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogQGlnbm9yZVxuICogU3ViLWNsYXNzIHNwZWNpYWxpemF0aW9uIG9mIEV2ZW50SGFuZGxlciBiYXNlIGNsYXNzLlxuICpcbiAqIFRhc2tMb29wIGFsbG93cyB0byBzY2hlZHVsZSBhIHRhc2sgZnVuY3Rpb24gYmVpbmcgY2FsbGVkIChvcHRpb25uYWx5IHJlcGVhdGVkbHkpIG9uIHRoZSBtYWluIGxvb3AsXG4gKiBzY2hlZHVsZWQgYXN5bmNocm9uZW91c2x5LCBhdm9pZGluZyByZWN1cnNpdmUgY2FsbHMgaW4gdGhlIHNhbWUgdGljay5cbiAqXG4gKiBUaGUgdGFzayBpdHNlbGYgaXMgaW1wbGVtZW50ZWQgaW4gYGRvVGlja2AuIEl0IGNhbiBiZSByZXF1ZXN0ZWQgYW5kIGNhbGxlZCBmb3Igc2luZ2xlIGV4ZWN1dGlvblxuICogdXNpbmcgdGhlIGB0aWNrYCBtZXRob2QuXG4gKlxuICogSXQgd2lsbCBiZSBhc3N1cmVkIHRoYXQgdGhlIHRhc2sgZXhlY3V0aW9uIG1ldGhvZCAoYHRpY2tgKSBvbmx5IGdldHMgY2FsbGVkIG9uY2UgcGVyIG1haW4gbG9vcCBcInRpY2tcIixcbiAqIG5vIG1hdHRlciBob3cgb2Z0ZW4gaXQgZ2V0cyByZXF1ZXN0ZWQgZm9yIGV4ZWN1dGlvbi4gRXhlY3V0aW9uIGluIGZ1cnRoZXIgdGlja3Mgd2lsbCBiZSBzY2hlZHVsZWQgYWNjb3JkaW5nbHkuXG4gKlxuICogSWYgZnVydGhlciBleGVjdXRpb24gcmVxdWVzdHMgaGF2ZSBhbHJlYWR5IGJlZW4gc2NoZWR1bGVkIG9uIHRoZSBuZXh0IHRpY2ssIGl0IGNhbiBiZSBjaGVja2VkIHdpdGggYGhhc05leHRUaWNrYCxcbiAqIGFuZCBjYW5jZWxsZWQgd2l0aCBgY2xlYXJOZXh0VGlja2AuXG4gKlxuICogVGhlIHRhc2sgY2FuIGJlIHNjaGVkdWxlZCBhcyBhbiBpbnRlcnZhbCByZXBlYXRlZGx5IHdpdGggYSBwZXJpb2QgYXMgcGFyYW1ldGVyIChzZWUgYHNldEludGVydmFsYCwgYGNsZWFySW50ZXJ2YWxgKS5cbiAqXG4gKiBTdWItY2xhc3NlcyBuZWVkIHRvIGltcGxlbWVudCB0aGUgYGRvVGlja2AgbWV0aG9kIHdoaWNoIHdpbGwgZWZmZWN0aXZlbHkgaGF2ZSB0aGUgdGFzayBleGVjdXRpb24gcm91dGluZS5cbiAqXG4gKiBGdXJ0aGVyIGV4cGxhbmF0aW9uczpcbiAqXG4gKiBUaGUgYmFzZWNsYXNzIGhhcyBhIGB0aWNrYCBtZXRob2QgdGhhdCB3aWxsIHNjaGVkdWxlIHRoZSBkb1RpY2sgY2FsbC4gSXQgbWF5IGJlIGNhbGxlZCBzeW5jaHJvbmVvdXNseVxuICogb25seSBmb3IgYSBzdGFjay1kZXB0aCBvZiBvbmUuIE9uIHJlLWVudHJhbnQgY2FsbHMsIHN1Yi1zZXF1ZW50IGNhbGxzIGFyZSBzY2hlZHVsZWQgZm9yIG5leHQgbWFpbiBsb29wIHRpY2tzLlxuICpcbiAqIFdoZW4gdGhlIHRhc2sgZXhlY3V0aW9uIChgdGlja2AgbWV0aG9kKSBpcyBjYWxsZWQgaW4gcmUtZW50cmFudCB3YXkgdGhpcyBpcyBkZXRlY3RlZCBhbmRcbiAqIHdlIGFyZSBsaW1pdGluZyB0aGUgdGFzayBleGVjdXRpb24gcGVyIGNhbGwgc3RhY2sgdG8gZXhhY3RseSBvbmUsIGJ1dCBzY2hlZHVsaW5nL3Bvc3QtcG9uaW5nIGZ1cnRoZXJcbiAqIHRhc2sgcHJvY2Vzc2luZyBvbiB0aGUgbmV4dCBtYWluIGxvb3AgaXRlcmF0aW9uIChhbHNvIGtub3duIGFzIFwibmV4dCB0aWNrXCIgaW4gdGhlIE5vZGUvSlMgcnVudGltZSBsaW5nbykuXG4gKi9cbmNsYXNzIFRhc2tMb29wIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5fYm91bmRUaWNrID0gdm9pZCAwO1xuICAgIHRoaXMuX3RpY2tUaW1lciA9IG51bGw7XG4gICAgdGhpcy5fdGlja0ludGVydmFsID0gbnVsbDtcbiAgICB0aGlzLl90aWNrQ2FsbENvdW50ID0gMDtcbiAgICB0aGlzLl9ib3VuZFRpY2sgPSB0aGlzLnRpY2suYmluZCh0aGlzKTtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMub25IYW5kbGVyRGVzdHJveWluZygpO1xuICAgIHRoaXMub25IYW5kbGVyRGVzdHJveWVkKCk7XG4gIH1cbiAgb25IYW5kbGVyRGVzdHJveWluZygpIHtcbiAgICAvLyBjbGVhciBhbGwgdGltZXJzIGJlZm9yZSB1bnJlZ2lzdGVyaW5nIGZyb20gZXZlbnQgYnVzXG4gICAgdGhpcy5jbGVhck5leHRUaWNrKCk7XG4gICAgdGhpcy5jbGVhckludGVydmFsKCk7XG4gIH1cbiAgb25IYW5kbGVyRGVzdHJveWVkKCkge31cbiAgaGFzSW50ZXJ2YWwoKSB7XG4gICAgcmV0dXJuICEhdGhpcy5fdGlja0ludGVydmFsO1xuICB9XG4gIGhhc05leHRUaWNrKCkge1xuICAgIHJldHVybiAhIXRoaXMuX3RpY2tUaW1lcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0gbWlsbGlzIC0gSW50ZXJ2YWwgdGltZSAobXMpXG4gICAqIEBldHVybnMgVHJ1ZSB3aGVuIGludGVydmFsIGhhcyBiZWVuIHNjaGVkdWxlZCwgZmFsc2Ugd2hlbiBhbHJlYWR5IHNjaGVkdWxlZCAobm8gZWZmZWN0KVxuICAgKi9cbiAgc2V0SW50ZXJ2YWwobWlsbGlzKSB7XG4gICAgaWYgKCF0aGlzLl90aWNrSW50ZXJ2YWwpIHtcbiAgICAgIHRoaXMuX3RpY2tDYWxsQ291bnQgPSAwO1xuICAgICAgdGhpcy5fdGlja0ludGVydmFsID0gc2VsZi5zZXRJbnRlcnZhbCh0aGlzLl9ib3VuZFRpY2ssIG1pbGxpcyk7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgLyoqXG4gICAqIEByZXR1cm5zIFRydWUgd2hlbiBpbnRlcnZhbCB3YXMgY2xlYXJlZCwgZmFsc2Ugd2hlbiBub25lIHdhcyBzZXQgKG5vIGVmZmVjdClcbiAgICovXG4gIGNsZWFySW50ZXJ2YWwoKSB7XG4gICAgaWYgKHRoaXMuX3RpY2tJbnRlcnZhbCkge1xuICAgICAgc2VsZi5jbGVhckludGVydmFsKHRoaXMuX3RpY2tJbnRlcnZhbCk7XG4gICAgICB0aGlzLl90aWNrSW50ZXJ2YWwgPSBudWxsO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAcmV0dXJucyBUcnVlIHdoZW4gdGltZW91dCB3YXMgY2xlYXJlZCwgZmFsc2Ugd2hlbiBub25lIHdhcyBzZXQgKG5vIGVmZmVjdClcbiAgICovXG4gIGNsZWFyTmV4dFRpY2soKSB7XG4gICAgaWYgKHRoaXMuX3RpY2tUaW1lcikge1xuICAgICAgc2VsZi5jbGVhclRpbWVvdXQodGhpcy5fdGlja1RpbWVyKTtcbiAgICAgIHRoaXMuX3RpY2tUaW1lciA9IG51bGw7XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgLyoqXG4gICAqIFdpbGwgY2FsbCB0aGUgc3ViY2xhc3MgZG9UaWNrIGltcGxlbWVudGF0aW9uIGluIHRoaXMgbWFpbiBsb29wIHRpY2tcbiAgICogb3IgaW4gdGhlIG5leHQgb25lICh2aWEgc2V0VGltZW91dCgsMCkpIGluIGNhc2UgaXQgaGFzIGFscmVhZHkgYmVlbiBjYWxsZWRcbiAgICogaW4gdGhpcyB0aWNrIChpbiBjYXNlIHRoaXMgaXMgYSByZS1lbnRyYW50IGNhbGwpLlxuICAgKi9cbiAgdGljaygpIHtcbiAgICB0aGlzLl90aWNrQ2FsbENvdW50Kys7XG4gICAgaWYgKHRoaXMuX3RpY2tDYWxsQ291bnQgPT09IDEpIHtcbiAgICAgIHRoaXMuZG9UaWNrKCk7XG4gICAgICAvLyByZS1lbnRyYW50IGNhbGwgdG8gdGljayBmcm9tIHByZXZpb3VzIGRvVGljayBjYWxsIHN0YWNrXG4gICAgICAvLyAtPiBzY2hlZHVsZSBhIGNhbGwgb24gdGhlIG5leHQgbWFpbiBsb29wIGl0ZXJhdGlvbiB0byBwcm9jZXNzIHRoaXMgdGFzayBwcm9jZXNzaW5nIHJlcXVlc3RcbiAgICAgIGlmICh0aGlzLl90aWNrQ2FsbENvdW50ID4gMSkge1xuICAgICAgICAvLyBtYWtlIHN1cmUgb25seSBvbmUgdGltZXIgZXhpc3RzIGF0IGFueSB0aW1lIGF0IG1heFxuICAgICAgICB0aGlzLnRpY2tJbW1lZGlhdGUoKTtcbiAgICAgIH1cbiAgICAgIHRoaXMuX3RpY2tDYWxsQ291bnQgPSAwO1xuICAgIH1cbiAgfVxuICB0aWNrSW1tZWRpYXRlKCkge1xuICAgIHRoaXMuY2xlYXJOZXh0VGljaygpO1xuICAgIHRoaXMuX3RpY2tUaW1lciA9IHNlbGYuc2V0VGltZW91dCh0aGlzLl9ib3VuZFRpY2ssIDApO1xuICB9XG5cbiAgLyoqXG4gICAqIEZvciBzdWJjbGFzcyB0byBpbXBsZW1lbnQgdGFzayBsb2dpY1xuICAgKiBAYWJzdHJhY3RcbiAgICovXG4gIGRvVGljaygpIHt9XG59XG5cbnZhciBGcmFnbWVudFN0YXRlID0ge1xuICBOT1RfTE9BREVEOiBcIk5PVF9MT0FERURcIixcbiAgQVBQRU5ESU5HOiBcIkFQUEVORElOR1wiLFxuICBQQVJUSUFMOiBcIlBBUlRJQUxcIixcbiAgT0s6IFwiT0tcIlxufTtcbmNsYXNzIEZyYWdtZW50VHJhY2tlciB7XG4gIGNvbnN0cnVjdG9yKGhscykge1xuICAgIHRoaXMuYWN0aXZlUGFydExpc3RzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmVuZExpc3RGcmFnbWVudHMgPSBPYmplY3QuY3JlYXRlKG51bGwpO1xuICAgIHRoaXMuZnJhZ21lbnRzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLnRpbWVSYW5nZXMgPSBPYmplY3QuY3JlYXRlKG51bGwpO1xuICAgIHRoaXMuYnVmZmVyUGFkZGluZyA9IDAuMjtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLmhhc0dhcHMgPSBmYWxzZTtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgICB0aGlzLl9yZWdpc3Rlckxpc3RlbmVycygpO1xuICB9XG4gIF9yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9BUFBFTkRFRCwgdGhpcy5vbkJ1ZmZlckFwcGVuZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkZSQUdfQlVGRkVSRUQsIHRoaXMub25GcmFnQnVmZmVyZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuRlJBR19MT0FERUQsIHRoaXMub25GcmFnTG9hZGVkLCB0aGlzKTtcbiAgfVxuICBfdW5yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfQVBQRU5ERUQsIHRoaXMub25CdWZmZXJBcHBlbmRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRlJBR19CVUZGRVJFRCwgdGhpcy5vbkZyYWdCdWZmZXJlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRlJBR19MT0FERUQsIHRoaXMub25GcmFnTG9hZGVkLCB0aGlzKTtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMuX3VucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdGhpcy5mcmFnbWVudHMgPVxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmFjdGl2ZVBhcnRMaXN0cyA9XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuZW5kTGlzdEZyYWdtZW50cyA9IHRoaXMudGltZVJhbmdlcyA9IG51bGw7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJuIGEgRnJhZ21lbnQgb3IgUGFydCB3aXRoIGFuIGFwcGVuZGVkIHJhbmdlIHRoYXQgbWF0Y2hlcyB0aGUgcG9zaXRpb24gYW5kIGxldmVsVHlwZVxuICAgKiBPdGhlcndpc2UsIHJldHVybiBudWxsXG4gICAqL1xuICBnZXRBcHBlbmRlZEZyYWcocG9zaXRpb24sIGxldmVsVHlwZSkge1xuICAgIGNvbnN0IGFjdGl2ZVBhcnRzID0gdGhpcy5hY3RpdmVQYXJ0TGlzdHNbbGV2ZWxUeXBlXTtcbiAgICBpZiAoYWN0aXZlUGFydHMpIHtcbiAgICAgIGZvciAobGV0IGkgPSBhY3RpdmVQYXJ0cy5sZW5ndGg7IGktLTspIHtcbiAgICAgICAgY29uc3QgYWN0aXZlUGFydCA9IGFjdGl2ZVBhcnRzW2ldO1xuICAgICAgICBpZiAoIWFjdGl2ZVBhcnQpIHtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBhcHBlbmRlZFBUUyA9IGFjdGl2ZVBhcnQuZW5kO1xuICAgICAgICBpZiAoYWN0aXZlUGFydC5zdGFydCA8PSBwb3NpdGlvbiAmJiBhcHBlbmRlZFBUUyAhPT0gbnVsbCAmJiBwb3NpdGlvbiA8PSBhcHBlbmRlZFBUUykge1xuICAgICAgICAgIHJldHVybiBhY3RpdmVQYXJ0O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmdldEJ1ZmZlcmVkRnJhZyhwb3NpdGlvbiwgbGV2ZWxUeXBlKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm4gYSBidWZmZXJlZCBGcmFnbWVudCB0aGF0IG1hdGNoZXMgdGhlIHBvc2l0aW9uIGFuZCBsZXZlbFR5cGUuXG4gICAqIEEgYnVmZmVyZWQgRnJhZ21lbnQgaXMgb25lIHdob3NlIGxvYWRpbmcsIHBhcnNpbmcgYW5kIGFwcGVuZGluZyBpcyBkb25lIChjb21wbGV0ZWQgb3IgXCJwYXJ0aWFsXCIgbWVhbmluZyBhYm9ydGVkKS5cbiAgICogSWYgbm90IGZvdW5kIGFueSBGcmFnbWVudCwgcmV0dXJuIG51bGxcbiAgICovXG4gIGdldEJ1ZmZlcmVkRnJhZyhwb3NpdGlvbiwgbGV2ZWxUeXBlKSB7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZ21lbnRzXG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3Qga2V5cyA9IE9iamVjdC5rZXlzKGZyYWdtZW50cyk7XG4gICAgZm9yIChsZXQgaSA9IGtleXMubGVuZ3RoOyBpLS07KSB7XG4gICAgICBjb25zdCBmcmFnbWVudEVudGl0eSA9IGZyYWdtZW50c1trZXlzW2ldXTtcbiAgICAgIGlmICgoZnJhZ21lbnRFbnRpdHkgPT0gbnVsbCA/IHZvaWQgMCA6IGZyYWdtZW50RW50aXR5LmJvZHkudHlwZSkgPT09IGxldmVsVHlwZSAmJiBmcmFnbWVudEVudGl0eS5idWZmZXJlZCkge1xuICAgICAgICBjb25zdCBmcmFnID0gZnJhZ21lbnRFbnRpdHkuYm9keTtcbiAgICAgICAgaWYgKGZyYWcuc3RhcnQgPD0gcG9zaXRpb24gJiYgcG9zaXRpb24gPD0gZnJhZy5lbmQpIHtcbiAgICAgICAgICByZXR1cm4gZnJhZztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBQYXJ0aWFsIGZyYWdtZW50cyBlZmZlY3RlZCBieSBjb2RlZCBmcmFtZSBldmljdGlvbiB3aWxsIGJlIHJlbW92ZWRcbiAgICogVGhlIGJyb3dzZXIgd2lsbCB1bmxvYWQgcGFydHMgb2YgdGhlIGJ1ZmZlciB0byBmcmVlIHVwIG1lbW9yeSBmb3IgbmV3IGJ1ZmZlciBkYXRhXG4gICAqIEZyYWdtZW50cyB3aWxsIG5lZWQgdG8gYmUgcmVsb2FkZWQgd2hlbiB0aGUgYnVmZmVyIGlzIGZyZWVkIHVwLCByZW1vdmluZyBwYXJ0aWFsIGZyYWdtZW50cyB3aWxsIGFsbG93IHRoZW0gdG8gcmVsb2FkKHNpbmNlIHRoZXJlIG1pZ2h0IGJlIHBhcnRzIHRoYXQgYXJlIHN0aWxsIHBsYXlhYmxlKVxuICAgKi9cbiAgZGV0ZWN0RXZpY3RlZEZyYWdtZW50cyhlbGVtZW50YXJ5U3RyZWFtLCB0aW1lUmFuZ2UsIHBsYXlsaXN0VHlwZSwgYXBwZW5kZWRQYXJ0KSB7XG4gICAgaWYgKHRoaXMudGltZVJhbmdlcykge1xuICAgICAgdGhpcy50aW1lUmFuZ2VzW2VsZW1lbnRhcnlTdHJlYW1dID0gdGltZVJhbmdlO1xuICAgIH1cbiAgICAvLyBDaGVjayBpZiBhbnkgZmxhZ2dlZCBmcmFnbWVudHMgaGF2ZSBiZWVuIHVubG9hZGVkXG4gICAgLy8gZXhjbHVkaW5nIGFueXRoaW5nIG5ld2VyIHRoYW4gYXBwZW5kZWRQYXJ0U25cbiAgICBjb25zdCBhcHBlbmRlZFBhcnRTbiA9IChhcHBlbmRlZFBhcnQgPT0gbnVsbCA/IHZvaWQgMCA6IGFwcGVuZGVkUGFydC5mcmFnbWVudC5zbikgfHwgLTE7XG4gICAgT2JqZWN0LmtleXModGhpcy5mcmFnbWVudHMpLmZvckVhY2goa2V5ID0+IHtcbiAgICAgIGNvbnN0IGZyYWdtZW50RW50aXR5ID0gdGhpcy5mcmFnbWVudHNba2V5XTtcbiAgICAgIGlmICghZnJhZ21lbnRFbnRpdHkpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgaWYgKGFwcGVuZGVkUGFydFNuID49IGZyYWdtZW50RW50aXR5LmJvZHkuc24pIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgaWYgKCFmcmFnbWVudEVudGl0eS5idWZmZXJlZCAmJiAhZnJhZ21lbnRFbnRpdHkubG9hZGVkKSB7XG4gICAgICAgIGlmIChmcmFnbWVudEVudGl0eS5ib2R5LnR5cGUgPT09IHBsYXlsaXN0VHlwZSkge1xuICAgICAgICAgIHRoaXMucmVtb3ZlRnJhZ21lbnQoZnJhZ21lbnRFbnRpdHkuYm9keSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3QgZXNEYXRhID0gZnJhZ21lbnRFbnRpdHkucmFuZ2VbZWxlbWVudGFyeVN0cmVhbV07XG4gICAgICBpZiAoIWVzRGF0YSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBlc0RhdGEudGltZS5zb21lKHRpbWUgPT4ge1xuICAgICAgICBjb25zdCBpc05vdEJ1ZmZlcmVkID0gIXRoaXMuaXNUaW1lQnVmZmVyZWQodGltZS5zdGFydFBUUywgdGltZS5lbmRQVFMsIHRpbWVSYW5nZSk7XG4gICAgICAgIGlmIChpc05vdEJ1ZmZlcmVkKSB7XG4gICAgICAgICAgLy8gVW5yZWdpc3RlciBwYXJ0aWFsIGZyYWdtZW50IGFzIGl0IG5lZWRzIHRvIGxvYWQgYWdhaW4gdG8gYmUgcmV1c2VkXG4gICAgICAgICAgdGhpcy5yZW1vdmVGcmFnbWVudChmcmFnbWVudEVudGl0eS5ib2R5KTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gaXNOb3RCdWZmZXJlZDtcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrcyBpZiB0aGUgZnJhZ21lbnQgcGFzc2VkIGluIGlzIGxvYWRlZCBpbiB0aGUgYnVmZmVyIHByb3Blcmx5XG4gICAqIFBhcnRpYWxseSBsb2FkZWQgZnJhZ21lbnRzIHdpbGwgYmUgcmVnaXN0ZXJlZCBhcyBhIHBhcnRpYWwgZnJhZ21lbnRcbiAgICovXG4gIGRldGVjdFBhcnRpYWxGcmFnbWVudHMoZGF0YSkge1xuICAgIGNvbnN0IHRpbWVSYW5nZXMgPSB0aGlzLnRpbWVSYW5nZXM7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZyxcbiAgICAgIHBhcnRcbiAgICB9ID0gZGF0YTtcbiAgICBpZiAoIXRpbWVSYW5nZXMgfHwgZnJhZy5zbiA9PT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBmcmFnS2V5ID0gZ2V0RnJhZ21lbnRLZXkoZnJhZyk7XG4gICAgY29uc3QgZnJhZ21lbnRFbnRpdHkgPSB0aGlzLmZyYWdtZW50c1tmcmFnS2V5XTtcbiAgICBpZiAoIWZyYWdtZW50RW50aXR5IHx8IGZyYWdtZW50RW50aXR5LmJ1ZmZlcmVkICYmIGZyYWcuZ2FwKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGlzRnJhZ0hpbnQgPSAhZnJhZy5yZWx1cmw7XG4gICAgT2JqZWN0LmtleXModGltZVJhbmdlcykuZm9yRWFjaChlbGVtZW50YXJ5U3RyZWFtID0+IHtcbiAgICAgIGNvbnN0IHN0cmVhbUluZm8gPSBmcmFnLmVsZW1lbnRhcnlTdHJlYW1zW2VsZW1lbnRhcnlTdHJlYW1dO1xuICAgICAgaWYgKCFzdHJlYW1JbmZvKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IHRpbWVSYW5nZSA9IHRpbWVSYW5nZXNbZWxlbWVudGFyeVN0cmVhbV07XG4gICAgICBjb25zdCBwYXJ0aWFsID0gaXNGcmFnSGludCB8fCBzdHJlYW1JbmZvLnBhcnRpYWwgPT09IHRydWU7XG4gICAgICBmcmFnbWVudEVudGl0eS5yYW5nZVtlbGVtZW50YXJ5U3RyZWFtXSA9IHRoaXMuZ2V0QnVmZmVyZWRUaW1lcyhmcmFnLCBwYXJ0LCBwYXJ0aWFsLCB0aW1lUmFuZ2UpO1xuICAgIH0pO1xuICAgIGZyYWdtZW50RW50aXR5LmxvYWRlZCA9IG51bGw7XG4gICAgaWYgKE9iamVjdC5rZXlzKGZyYWdtZW50RW50aXR5LnJhbmdlKS5sZW5ndGgpIHtcbiAgICAgIGZyYWdtZW50RW50aXR5LmJ1ZmZlcmVkID0gdHJ1ZTtcbiAgICAgIGNvbnN0IGVuZExpc3QgPSBmcmFnbWVudEVudGl0eS5ib2R5LmVuZExpc3QgPSBmcmFnLmVuZExpc3QgfHwgZnJhZ21lbnRFbnRpdHkuYm9keS5lbmRMaXN0O1xuICAgICAgaWYgKGVuZExpc3QpIHtcbiAgICAgICAgdGhpcy5lbmRMaXN0RnJhZ21lbnRzW2ZyYWdtZW50RW50aXR5LmJvZHkudHlwZV0gPSBmcmFnbWVudEVudGl0eTtcbiAgICAgIH1cbiAgICAgIGlmICghaXNQYXJ0aWFsKGZyYWdtZW50RW50aXR5KSkge1xuICAgICAgICAvLyBSZW1vdmUgb2xkZXIgZnJhZ21lbnQgcGFydHMgZnJvbSBsb29rdXAgYWZ0ZXIgZnJhZyBpcyB0cmFja2VkIGFzIGJ1ZmZlcmVkXG4gICAgICAgIHRoaXMucmVtb3ZlUGFydHMoZnJhZy5zbiAtIDEsIGZyYWcudHlwZSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIHJlbW92ZSBmcmFnbWVudCBpZiBub3RoaW5nIHdhcyBhcHBlbmRlZFxuICAgICAgdGhpcy5yZW1vdmVGcmFnbWVudChmcmFnbWVudEVudGl0eS5ib2R5KTtcbiAgICB9XG4gIH1cbiAgcmVtb3ZlUGFydHMoc25Ub0tlZXAsIGxldmVsVHlwZSkge1xuICAgIGNvbnN0IGFjdGl2ZVBhcnRzID0gdGhpcy5hY3RpdmVQYXJ0TGlzdHNbbGV2ZWxUeXBlXTtcbiAgICBpZiAoIWFjdGl2ZVBhcnRzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuYWN0aXZlUGFydExpc3RzW2xldmVsVHlwZV0gPSBhY3RpdmVQYXJ0cy5maWx0ZXIocGFydCA9PiBwYXJ0LmZyYWdtZW50LnNuID49IHNuVG9LZWVwKTtcbiAgfVxuICBmcmFnQnVmZmVyZWQoZnJhZywgZm9yY2UpIHtcbiAgICBjb25zdCBmcmFnS2V5ID0gZ2V0RnJhZ21lbnRLZXkoZnJhZyk7XG4gICAgbGV0IGZyYWdtZW50RW50aXR5ID0gdGhpcy5mcmFnbWVudHNbZnJhZ0tleV07XG4gICAgaWYgKCFmcmFnbWVudEVudGl0eSAmJiBmb3JjZSkge1xuICAgICAgZnJhZ21lbnRFbnRpdHkgPSB0aGlzLmZyYWdtZW50c1tmcmFnS2V5XSA9IHtcbiAgICAgICAgYm9keTogZnJhZyxcbiAgICAgICAgYXBwZW5kZWRQVFM6IG51bGwsXG4gICAgICAgIGxvYWRlZDogbnVsbCxcbiAgICAgICAgYnVmZmVyZWQ6IGZhbHNlLFxuICAgICAgICByYW5nZTogT2JqZWN0LmNyZWF0ZShudWxsKVxuICAgICAgfTtcbiAgICAgIGlmIChmcmFnLmdhcCkge1xuICAgICAgICB0aGlzLmhhc0dhcHMgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoZnJhZ21lbnRFbnRpdHkpIHtcbiAgICAgIGZyYWdtZW50RW50aXR5LmxvYWRlZCA9IG51bGw7XG4gICAgICBmcmFnbWVudEVudGl0eS5idWZmZXJlZCA9IHRydWU7XG4gICAgfVxuICB9XG4gIGdldEJ1ZmZlcmVkVGltZXMoZnJhZ21lbnQsIHBhcnQsIHBhcnRpYWwsIHRpbWVSYW5nZSkge1xuICAgIGNvbnN0IGJ1ZmZlcmVkID0ge1xuICAgICAgdGltZTogW10sXG4gICAgICBwYXJ0aWFsXG4gICAgfTtcbiAgICBjb25zdCBzdGFydFBUUyA9IGZyYWdtZW50LnN0YXJ0O1xuICAgIGNvbnN0IGVuZFBUUyA9IGZyYWdtZW50LmVuZDtcbiAgICBjb25zdCBtaW5FbmRQVFMgPSBmcmFnbWVudC5taW5FbmRQVFMgfHwgZW5kUFRTO1xuICAgIGNvbnN0IG1heFN0YXJ0UFRTID0gZnJhZ21lbnQubWF4U3RhcnRQVFMgfHwgc3RhcnRQVFM7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aW1lUmFuZ2UubGVuZ3RoOyBpKyspIHtcbiAgICAgIGNvbnN0IHN0YXJ0VGltZSA9IHRpbWVSYW5nZS5zdGFydChpKSAtIHRoaXMuYnVmZmVyUGFkZGluZztcbiAgICAgIGNvbnN0IGVuZFRpbWUgPSB0aW1lUmFuZ2UuZW5kKGkpICsgdGhpcy5idWZmZXJQYWRkaW5nO1xuICAgICAgaWYgKG1heFN0YXJ0UFRTID49IHN0YXJ0VGltZSAmJiBtaW5FbmRQVFMgPD0gZW5kVGltZSkge1xuICAgICAgICAvLyBGcmFnbWVudCBpcyBlbnRpcmVseSBjb250YWluZWQgaW4gYnVmZmVyXG4gICAgICAgIC8vIE5vIG5lZWQgdG8gY2hlY2sgdGhlIG90aGVyIHRpbWVSYW5nZSB0aW1lcyBzaW5jZSBpdCdzIGNvbXBsZXRlbHkgcGxheWFibGVcbiAgICAgICAgYnVmZmVyZWQudGltZS5wdXNoKHtcbiAgICAgICAgICBzdGFydFBUUzogTWF0aC5tYXgoc3RhcnRQVFMsIHRpbWVSYW5nZS5zdGFydChpKSksXG4gICAgICAgICAgZW5kUFRTOiBNYXRoLm1pbihlbmRQVFMsIHRpbWVSYW5nZS5lbmQoaSkpXG4gICAgICAgIH0pO1xuICAgICAgICBicmVhaztcbiAgICAgIH0gZWxzZSBpZiAoc3RhcnRQVFMgPCBlbmRUaW1lICYmIGVuZFBUUyA+IHN0YXJ0VGltZSkge1xuICAgICAgICBjb25zdCBzdGFydCA9IE1hdGgubWF4KHN0YXJ0UFRTLCB0aW1lUmFuZ2Uuc3RhcnQoaSkpO1xuICAgICAgICBjb25zdCBlbmQgPSBNYXRoLm1pbihlbmRQVFMsIHRpbWVSYW5nZS5lbmQoaSkpO1xuICAgICAgICBpZiAoZW5kID4gc3RhcnQpIHtcbiAgICAgICAgICBidWZmZXJlZC5wYXJ0aWFsID0gdHJ1ZTtcbiAgICAgICAgICAvLyBDaGVjayBmb3IgaW50ZXJzZWN0aW9uIHdpdGggYnVmZmVyXG4gICAgICAgICAgLy8gR2V0IHBsYXlhYmxlIHNlY3Rpb25zIG9mIHRoZSBmcmFnbWVudFxuICAgICAgICAgIGJ1ZmZlcmVkLnRpbWUucHVzaCh7XG4gICAgICAgICAgICBzdGFydFBUUzogc3RhcnQsXG4gICAgICAgICAgICBlbmRQVFM6IGVuZFxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICB9IGVsc2UgaWYgKGVuZFBUUyA8PSBzdGFydFRpbWUpIHtcbiAgICAgICAgLy8gTm8gbmVlZCB0byBjaGVjayB0aGUgcmVzdCBvZiB0aGUgdGltZVJhbmdlIGFzIGl0IGlzIGluIG9yZGVyXG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gYnVmZmVyZWQ7XG4gIH1cblxuICAvKipcbiAgICogR2V0cyB0aGUgcGFydGlhbCBmcmFnbWVudCBmb3IgYSBjZXJ0YWluIHRpbWVcbiAgICovXG4gIGdldFBhcnRpYWxGcmFnbWVudCh0aW1lKSB7XG4gICAgbGV0IGJlc3RGcmFnbWVudCA9IG51bGw7XG4gICAgbGV0IHRpbWVQYWRkaW5nO1xuICAgIGxldCBzdGFydFRpbWU7XG4gICAgbGV0IGVuZFRpbWU7XG4gICAgbGV0IGJlc3RPdmVybGFwID0gMDtcbiAgICBjb25zdCB7XG4gICAgICBidWZmZXJQYWRkaW5nLFxuICAgICAgZnJhZ21lbnRzXG4gICAgfSA9IHRoaXM7XG4gICAgT2JqZWN0LmtleXMoZnJhZ21lbnRzKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBjb25zdCBmcmFnbWVudEVudGl0eSA9IGZyYWdtZW50c1trZXldO1xuICAgICAgaWYgKCFmcmFnbWVudEVudGl0eSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBpZiAoaXNQYXJ0aWFsKGZyYWdtZW50RW50aXR5KSkge1xuICAgICAgICBzdGFydFRpbWUgPSBmcmFnbWVudEVudGl0eS5ib2R5LnN0YXJ0IC0gYnVmZmVyUGFkZGluZztcbiAgICAgICAgZW5kVGltZSA9IGZyYWdtZW50RW50aXR5LmJvZHkuZW5kICsgYnVmZmVyUGFkZGluZztcbiAgICAgICAgaWYgKHRpbWUgPj0gc3RhcnRUaW1lICYmIHRpbWUgPD0gZW5kVGltZSkge1xuICAgICAgICAgIC8vIFVzZSB0aGUgZnJhZ21lbnQgdGhhdCBoYXMgdGhlIG1vc3QgcGFkZGluZyBmcm9tIHN0YXJ0IGFuZCBlbmQgdGltZVxuICAgICAgICAgIHRpbWVQYWRkaW5nID0gTWF0aC5taW4odGltZSAtIHN0YXJ0VGltZSwgZW5kVGltZSAtIHRpbWUpO1xuICAgICAgICAgIGlmIChiZXN0T3ZlcmxhcCA8PSB0aW1lUGFkZGluZykge1xuICAgICAgICAgICAgYmVzdEZyYWdtZW50ID0gZnJhZ21lbnRFbnRpdHkuYm9keTtcbiAgICAgICAgICAgIGJlc3RPdmVybGFwID0gdGltZVBhZGRpbmc7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIGJlc3RGcmFnbWVudDtcbiAgfVxuICBpc0VuZExpc3RBcHBlbmRlZCh0eXBlKSB7XG4gICAgY29uc3QgbGFzdEZyYWdtZW50RW50aXR5ID0gdGhpcy5lbmRMaXN0RnJhZ21lbnRzW3R5cGVdO1xuICAgIHJldHVybiBsYXN0RnJhZ21lbnRFbnRpdHkgIT09IHVuZGVmaW5lZCAmJiAobGFzdEZyYWdtZW50RW50aXR5LmJ1ZmZlcmVkIHx8IGlzUGFydGlhbChsYXN0RnJhZ21lbnRFbnRpdHkpKTtcbiAgfVxuICBnZXRTdGF0ZShmcmFnbWVudCkge1xuICAgIGNvbnN0IGZyYWdLZXkgPSBnZXRGcmFnbWVudEtleShmcmFnbWVudCk7XG4gICAgY29uc3QgZnJhZ21lbnRFbnRpdHkgPSB0aGlzLmZyYWdtZW50c1tmcmFnS2V5XTtcbiAgICBpZiAoZnJhZ21lbnRFbnRpdHkpIHtcbiAgICAgIGlmICghZnJhZ21lbnRFbnRpdHkuYnVmZmVyZWQpIHtcbiAgICAgICAgcmV0dXJuIEZyYWdtZW50U3RhdGUuQVBQRU5ESU5HO1xuICAgICAgfSBlbHNlIGlmIChpc1BhcnRpYWwoZnJhZ21lbnRFbnRpdHkpKSB7XG4gICAgICAgIHJldHVybiBGcmFnbWVudFN0YXRlLlBBUlRJQUw7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gRnJhZ21lbnRTdGF0ZS5PSztcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIEZyYWdtZW50U3RhdGUuTk9UX0xPQURFRDtcbiAgfVxuICBpc1RpbWVCdWZmZXJlZChzdGFydFBUUywgZW5kUFRTLCB0aW1lUmFuZ2UpIHtcbiAgICBsZXQgc3RhcnRUaW1lO1xuICAgIGxldCBlbmRUaW1lO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGltZVJhbmdlLmxlbmd0aDsgaSsrKSB7XG4gICAgICBzdGFydFRpbWUgPSB0aW1lUmFuZ2Uuc3RhcnQoaSkgLSB0aGlzLmJ1ZmZlclBhZGRpbmc7XG4gICAgICBlbmRUaW1lID0gdGltZVJhbmdlLmVuZChpKSArIHRoaXMuYnVmZmVyUGFkZGluZztcbiAgICAgIGlmIChzdGFydFBUUyA+PSBzdGFydFRpbWUgJiYgZW5kUFRTIDw9IGVuZFRpbWUpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgICBpZiAoZW5kUFRTIDw9IHN0YXJ0VGltZSkge1xuICAgICAgICAvLyBObyBuZWVkIHRvIGNoZWNrIHRoZSByZXN0IG9mIHRoZSB0aW1lUmFuZ2UgYXMgaXQgaXMgaW4gb3JkZXJcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgb25GcmFnTG9hZGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZyxcbiAgICAgIHBhcnRcbiAgICB9ID0gZGF0YTtcbiAgICAvLyBkb24ndCB0cmFjayBpbml0c2VnbWVudCAoZm9yIHdoaWNoIHNuIGlzIG5vdCBhIG51bWJlcilcbiAgICAvLyBkb24ndCB0cmFjayBmcmFncyB1c2VkIGZvciBiaXRyYXRlVGVzdCwgdGhleSdyZSBpcnJlbGV2YW50LlxuICAgIGlmIChmcmFnLnNuID09PSAnaW5pdFNlZ21lbnQnIHx8IGZyYWcuYml0cmF0ZVRlc3QpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBGcmFnbWVudCBlbnRpdHkgYGxvYWRlZGAgRnJhZ0xvYWRlZERhdGEgaXMgbnVsbCB3aGVuIGxvYWRpbmcgcGFydHNcbiAgICBjb25zdCBsb2FkZWQgPSBwYXJ0ID8gbnVsbCA6IGRhdGE7XG4gICAgY29uc3QgZnJhZ0tleSA9IGdldEZyYWdtZW50S2V5KGZyYWcpO1xuICAgIHRoaXMuZnJhZ21lbnRzW2ZyYWdLZXldID0ge1xuICAgICAgYm9keTogZnJhZyxcbiAgICAgIGFwcGVuZGVkUFRTOiBudWxsLFxuICAgICAgbG9hZGVkLFxuICAgICAgYnVmZmVyZWQ6IGZhbHNlLFxuICAgICAgcmFuZ2U6IE9iamVjdC5jcmVhdGUobnVsbClcbiAgICB9O1xuICB9XG4gIG9uQnVmZmVyQXBwZW5kZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydCxcbiAgICAgIHRpbWVSYW5nZXNcbiAgICB9ID0gZGF0YTtcbiAgICBpZiAoZnJhZy5zbiA9PT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBwbGF5bGlzdFR5cGUgPSBmcmFnLnR5cGU7XG4gICAgaWYgKHBhcnQpIHtcbiAgICAgIGxldCBhY3RpdmVQYXJ0cyA9IHRoaXMuYWN0aXZlUGFydExpc3RzW3BsYXlsaXN0VHlwZV07XG4gICAgICBpZiAoIWFjdGl2ZVBhcnRzKSB7XG4gICAgICAgIHRoaXMuYWN0aXZlUGFydExpc3RzW3BsYXlsaXN0VHlwZV0gPSBhY3RpdmVQYXJ0cyA9IFtdO1xuICAgICAgfVxuICAgICAgYWN0aXZlUGFydHMucHVzaChwYXJ0KTtcbiAgICB9XG4gICAgLy8gU3RvcmUgdGhlIGxhdGVzdCB0aW1lUmFuZ2VzIGxvYWRlZCBpbiB0aGUgYnVmZmVyXG4gICAgdGhpcy50aW1lUmFuZ2VzID0gdGltZVJhbmdlcztcbiAgICBPYmplY3Qua2V5cyh0aW1lUmFuZ2VzKS5mb3JFYWNoKGVsZW1lbnRhcnlTdHJlYW0gPT4ge1xuICAgICAgY29uc3QgdGltZVJhbmdlID0gdGltZVJhbmdlc1tlbGVtZW50YXJ5U3RyZWFtXTtcbiAgICAgIHRoaXMuZGV0ZWN0RXZpY3RlZEZyYWdtZW50cyhlbGVtZW50YXJ5U3RyZWFtLCB0aW1lUmFuZ2UsIHBsYXlsaXN0VHlwZSwgcGFydCk7XG4gICAgfSk7XG4gIH1cbiAgb25GcmFnQnVmZmVyZWQoZXZlbnQsIGRhdGEpIHtcbiAgICB0aGlzLmRldGVjdFBhcnRpYWxGcmFnbWVudHMoZGF0YSk7XG4gIH1cbiAgaGFzRnJhZ21lbnQoZnJhZ21lbnQpIHtcbiAgICBjb25zdCBmcmFnS2V5ID0gZ2V0RnJhZ21lbnRLZXkoZnJhZ21lbnQpO1xuICAgIHJldHVybiAhIXRoaXMuZnJhZ21lbnRzW2ZyYWdLZXldO1xuICB9XG4gIGhhc1BhcnRzKHR5cGUpIHtcbiAgICB2YXIgX3RoaXMkYWN0aXZlUGFydExpc3RzO1xuICAgIHJldHVybiAhISgoX3RoaXMkYWN0aXZlUGFydExpc3RzID0gdGhpcy5hY3RpdmVQYXJ0TGlzdHNbdHlwZV0pICE9IG51bGwgJiYgX3RoaXMkYWN0aXZlUGFydExpc3RzLmxlbmd0aCk7XG4gIH1cbiAgcmVtb3ZlRnJhZ21lbnRzSW5SYW5nZShzdGFydCwgZW5kLCBwbGF5bGlzdFR5cGUsIHdpdGhHYXBPbmx5LCB1bmJ1ZmZlcmVkT25seSkge1xuICAgIGlmICh3aXRoR2FwT25seSAmJiAhdGhpcy5oYXNHYXBzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIE9iamVjdC5rZXlzKHRoaXMuZnJhZ21lbnRzKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBjb25zdCBmcmFnbWVudEVudGl0eSA9IHRoaXMuZnJhZ21lbnRzW2tleV07XG4gICAgICBpZiAoIWZyYWdtZW50RW50aXR5KSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGZyYWcgPSBmcmFnbWVudEVudGl0eS5ib2R5O1xuICAgICAgaWYgKGZyYWcudHlwZSAhPT0gcGxheWxpc3RUeXBlIHx8IHdpdGhHYXBPbmx5ICYmICFmcmFnLmdhcCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBpZiAoZnJhZy5zdGFydCA8IGVuZCAmJiBmcmFnLmVuZCA+IHN0YXJ0ICYmIChmcmFnbWVudEVudGl0eS5idWZmZXJlZCB8fCB1bmJ1ZmZlcmVkT25seSkpIHtcbiAgICAgICAgdGhpcy5yZW1vdmVGcmFnbWVudChmcmFnKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICByZW1vdmVGcmFnbWVudChmcmFnbWVudCkge1xuICAgIGNvbnN0IGZyYWdLZXkgPSBnZXRGcmFnbWVudEtleShmcmFnbWVudCk7XG4gICAgZnJhZ21lbnQuc3RhdHMubG9hZGVkID0gMDtcbiAgICBmcmFnbWVudC5jbGVhckVsZW1lbnRhcnlTdHJlYW1JbmZvKCk7XG4gICAgY29uc3QgYWN0aXZlUGFydHMgPSB0aGlzLmFjdGl2ZVBhcnRMaXN0c1tmcmFnbWVudC50eXBlXTtcbiAgICBpZiAoYWN0aXZlUGFydHMpIHtcbiAgICAgIGNvbnN0IHNuVG9SZW1vdmUgPSBmcmFnbWVudC5zbjtcbiAgICAgIHRoaXMuYWN0aXZlUGFydExpc3RzW2ZyYWdtZW50LnR5cGVdID0gYWN0aXZlUGFydHMuZmlsdGVyKHBhcnQgPT4gcGFydC5mcmFnbWVudC5zbiAhPT0gc25Ub1JlbW92ZSk7XG4gICAgfVxuICAgIGRlbGV0ZSB0aGlzLmZyYWdtZW50c1tmcmFnS2V5XTtcbiAgICBpZiAoZnJhZ21lbnQuZW5kTGlzdCkge1xuICAgICAgZGVsZXRlIHRoaXMuZW5kTGlzdEZyYWdtZW50c1tmcmFnbWVudC50eXBlXTtcbiAgICB9XG4gIH1cbiAgcmVtb3ZlQWxsRnJhZ21lbnRzKCkge1xuICAgIHRoaXMuZnJhZ21lbnRzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmVuZExpc3RGcmFnbWVudHMgPSBPYmplY3QuY3JlYXRlKG51bGwpO1xuICAgIHRoaXMuYWN0aXZlUGFydExpc3RzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmhhc0dhcHMgPSBmYWxzZTtcbiAgfVxufVxuZnVuY3Rpb24gaXNQYXJ0aWFsKGZyYWdtZW50RW50aXR5KSB7XG4gIHZhciBfZnJhZ21lbnRFbnRpdHkkcmFuZ2UsIF9mcmFnbWVudEVudGl0eSRyYW5nZTIsIF9mcmFnbWVudEVudGl0eSRyYW5nZTM7XG4gIHJldHVybiBmcmFnbWVudEVudGl0eS5idWZmZXJlZCAmJiAoZnJhZ21lbnRFbnRpdHkuYm9keS5nYXAgfHwgKChfZnJhZ21lbnRFbnRpdHkkcmFuZ2UgPSBmcmFnbWVudEVudGl0eS5yYW5nZS52aWRlbykgPT0gbnVsbCA/IHZvaWQgMCA6IF9mcmFnbWVudEVudGl0eSRyYW5nZS5wYXJ0aWFsKSB8fCAoKF9mcmFnbWVudEVudGl0eSRyYW5nZTIgPSBmcmFnbWVudEVudGl0eS5yYW5nZS5hdWRpbykgPT0gbnVsbCA/IHZvaWQgMCA6IF9mcmFnbWVudEVudGl0eSRyYW5nZTIucGFydGlhbCkgfHwgKChfZnJhZ21lbnRFbnRpdHkkcmFuZ2UzID0gZnJhZ21lbnRFbnRpdHkucmFuZ2UuYXVkaW92aWRlbykgPT0gbnVsbCA/IHZvaWQgMCA6IF9mcmFnbWVudEVudGl0eSRyYW5nZTMucGFydGlhbCkpO1xufVxuZnVuY3Rpb24gZ2V0RnJhZ21lbnRLZXkoZnJhZ21lbnQpIHtcbiAgcmV0dXJuIGAke2ZyYWdtZW50LnR5cGV9XyR7ZnJhZ21lbnQubGV2ZWx9XyR7ZnJhZ21lbnQuc259YDtcbn1cblxuLyoqXG4gKiBQcm92aWRlcyBtZXRob2RzIGRlYWxpbmcgd2l0aCBidWZmZXIgbGVuZ3RoIHJldHJpZXZhbCBmb3IgZXhhbXBsZS5cbiAqXG4gKiBJbiBnZW5lcmFsLCBhIGhlbHBlciBhcm91bmQgSFRNTDUgTWVkaWFFbGVtZW50IFRpbWVSYW5nZXMgZ2F0aGVyZWQgZnJvbSBgYnVmZmVyZWRgIHByb3BlcnR5LlxuICpcbiAqIEFsc28gQHNlZSBodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvSFRNTE1lZGlhRWxlbWVudC9idWZmZXJlZFxuICovXG5cbmNvbnN0IG5vb3BCdWZmZXJlZCA9IHtcbiAgbGVuZ3RoOiAwLFxuICBzdGFydDogKCkgPT4gMCxcbiAgZW5kOiAoKSA9PiAwXG59O1xuY2xhc3MgQnVmZmVySGVscGVyIHtcbiAgLyoqXG4gICAqIFJldHVybiB0cnVlIGlmIGBtZWRpYWAncyBidWZmZXJlZCBpbmNsdWRlIGBwb3NpdGlvbmBcbiAgICovXG4gIHN0YXRpYyBpc0J1ZmZlcmVkKG1lZGlhLCBwb3NpdGlvbikge1xuICAgIHRyeSB7XG4gICAgICBpZiAobWVkaWEpIHtcbiAgICAgICAgY29uc3QgYnVmZmVyZWQgPSBCdWZmZXJIZWxwZXIuZ2V0QnVmZmVyZWQobWVkaWEpO1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGJ1ZmZlcmVkLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgaWYgKHBvc2l0aW9uID49IGJ1ZmZlcmVkLnN0YXJ0KGkpICYmIHBvc2l0aW9uIDw9IGJ1ZmZlcmVkLmVuZChpKSkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIHRoaXMgaXMgdG8gY2F0Y2hcbiAgICAgIC8vIEludmFsaWRTdGF0ZUVycm9yOiBGYWlsZWQgdG8gcmVhZCB0aGUgJ2J1ZmZlcmVkJyBwcm9wZXJ0eSBmcm9tICdTb3VyY2VCdWZmZXInOlxuICAgICAgLy8gVGhpcyBTb3VyY2VCdWZmZXIgaGFzIGJlZW4gcmVtb3ZlZCBmcm9tIHRoZSBwYXJlbnQgbWVkaWEgc291cmNlXG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBzdGF0aWMgYnVmZmVySW5mbyhtZWRpYSwgcG9zLCBtYXhIb2xlRHVyYXRpb24pIHtcbiAgICB0cnkge1xuICAgICAgaWYgKG1lZGlhKSB7XG4gICAgICAgIGNvbnN0IHZidWZmZXJlZCA9IEJ1ZmZlckhlbHBlci5nZXRCdWZmZXJlZChtZWRpYSk7XG4gICAgICAgIGNvbnN0IGJ1ZmZlcmVkID0gW107XG4gICAgICAgIGxldCBpO1xuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgdmJ1ZmZlcmVkLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgYnVmZmVyZWQucHVzaCh7XG4gICAgICAgICAgICBzdGFydDogdmJ1ZmZlcmVkLnN0YXJ0KGkpLFxuICAgICAgICAgICAgZW5kOiB2YnVmZmVyZWQuZW5kKGkpXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuYnVmZmVyZWRJbmZvKGJ1ZmZlcmVkLCBwb3MsIG1heEhvbGVEdXJhdGlvbik7XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIHRoaXMgaXMgdG8gY2F0Y2hcbiAgICAgIC8vIEludmFsaWRTdGF0ZUVycm9yOiBGYWlsZWQgdG8gcmVhZCB0aGUgJ2J1ZmZlcmVkJyBwcm9wZXJ0eSBmcm9tICdTb3VyY2VCdWZmZXInOlxuICAgICAgLy8gVGhpcyBTb3VyY2VCdWZmZXIgaGFzIGJlZW4gcmVtb3ZlZCBmcm9tIHRoZSBwYXJlbnQgbWVkaWEgc291cmNlXG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBsZW46IDAsXG4gICAgICBzdGFydDogcG9zLFxuICAgICAgZW5kOiBwb3MsXG4gICAgICBuZXh0U3RhcnQ6IHVuZGVmaW5lZFxuICAgIH07XG4gIH1cbiAgc3RhdGljIGJ1ZmZlcmVkSW5mbyhidWZmZXJlZCwgcG9zLCBtYXhIb2xlRHVyYXRpb24pIHtcbiAgICBwb3MgPSBNYXRoLm1heCgwLCBwb3MpO1xuICAgIC8vIHNvcnQgb24gYnVmZmVyLnN0YXJ0L3NtYWxsZXIgZW5kIChJRSBkb2VzIG5vdCBhbHdheXMgcmV0dXJuIHNvcnRlZCBidWZmZXJlZCByYW5nZSlcbiAgICBidWZmZXJlZC5zb3J0KGZ1bmN0aW9uIChhLCBiKSB7XG4gICAgICBjb25zdCBkaWZmID0gYS5zdGFydCAtIGIuc3RhcnQ7XG4gICAgICBpZiAoZGlmZikge1xuICAgICAgICByZXR1cm4gZGlmZjtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBiLmVuZCAtIGEuZW5kO1xuICAgICAgfVxuICAgIH0pO1xuICAgIGxldCBidWZmZXJlZDIgPSBbXTtcbiAgICBpZiAobWF4SG9sZUR1cmF0aW9uKSB7XG4gICAgICAvLyB0aGVyZSBtaWdodCBiZSBzb21lIHNtYWxsIGhvbGVzIGJldHdlZW4gYnVmZmVyIHRpbWUgcmFuZ2VcbiAgICAgIC8vIGNvbnNpZGVyIHRoYXQgaG9sZXMgc21hbGxlciB0aGFuIG1heEhvbGVEdXJhdGlvbiBhcmUgaXJyZWxldmFudCBhbmQgYnVpbGQgYW5vdGhlclxuICAgICAgLy8gYnVmZmVyIHRpbWUgcmFuZ2UgcmVwcmVzZW50YXRpb25zIHRoYXQgZGlzY2FyZHMgdGhvc2UgaG9sZXNcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYnVmZmVyZWQubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3QgYnVmMmxlbiA9IGJ1ZmZlcmVkMi5sZW5ndGg7XG4gICAgICAgIGlmIChidWYybGVuKSB7XG4gICAgICAgICAgY29uc3QgYnVmMmVuZCA9IGJ1ZmZlcmVkMltidWYybGVuIC0gMV0uZW5kO1xuICAgICAgICAgIC8vIGlmIHNtYWxsIGhvbGUgKHZhbHVlIGJldHdlZW4gMCBvciBtYXhIb2xlRHVyYXRpb24gKSBvciBvdmVybGFwcGluZyAobmVnYXRpdmUpXG4gICAgICAgICAgaWYgKGJ1ZmZlcmVkW2ldLnN0YXJ0IC0gYnVmMmVuZCA8IG1heEhvbGVEdXJhdGlvbikge1xuICAgICAgICAgICAgLy8gbWVyZ2Ugb3ZlcmxhcHBpbmcgdGltZSByYW5nZXNcbiAgICAgICAgICAgIC8vIHVwZGF0ZSBsYXN0UmFuZ2UuZW5kIG9ubHkgaWYgc21hbGxlciB0aGFuIGl0ZW0uZW5kXG4gICAgICAgICAgICAvLyBlLmcuICBbIDEsIDE1XSB3aXRoICBbIDIsOF0gPT4gWyAxLDE1XSAobm8gbmVlZCB0byBtb2RpZnkgbGFzdFJhbmdlLmVuZClcbiAgICAgICAgICAgIC8vIHdoZXJlYXMgWyAxLCA4XSB3aXRoICBbIDIsMTVdID0+IFsgMSwxNV0gKCBsYXN0UmFuZ2Ugc2hvdWxkIHN3aXRjaCBmcm9tIFsxLDhdIHRvIFsxLDE1XSlcbiAgICAgICAgICAgIGlmIChidWZmZXJlZFtpXS5lbmQgPiBidWYyZW5kKSB7XG4gICAgICAgICAgICAgIGJ1ZmZlcmVkMltidWYybGVuIC0gMV0uZW5kID0gYnVmZmVyZWRbaV0uZW5kO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBiaWcgaG9sZVxuICAgICAgICAgICAgYnVmZmVyZWQyLnB1c2goYnVmZmVyZWRbaV0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBmaXJzdCB2YWx1ZVxuICAgICAgICAgIGJ1ZmZlcmVkMi5wdXNoKGJ1ZmZlcmVkW2ldKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBidWZmZXJlZDIgPSBidWZmZXJlZDtcbiAgICB9XG4gICAgbGV0IGJ1ZmZlckxlbiA9IDA7XG5cbiAgICAvLyBidWZmZXJTdGFydE5leHQgY2FuIHBvc3NpYmx5IGJlIHVuZGVmaW5lZCBiYXNlZCBvbiB0aGUgY29uZGl0aW9uYWwgbG9naWMgYmVsb3dcbiAgICBsZXQgYnVmZmVyU3RhcnROZXh0O1xuXG4gICAgLy8gYnVmZmVyU3RhcnQgYW5kIGJ1ZmZlckVuZCBhcmUgYnVmZmVyIGJvdW5kYXJpZXMgYXJvdW5kIGN1cnJlbnQgdmlkZW8gcG9zaXRpb25cbiAgICBsZXQgYnVmZmVyU3RhcnQgPSBwb3M7XG4gICAgbGV0IGJ1ZmZlckVuZCA9IHBvcztcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGJ1ZmZlcmVkMi5sZW5ndGg7IGkrKykge1xuICAgICAgY29uc3Qgc3RhcnQgPSBidWZmZXJlZDJbaV0uc3RhcnQ7XG4gICAgICBjb25zdCBlbmQgPSBidWZmZXJlZDJbaV0uZW5kO1xuICAgICAgLy8gbG9nZ2VyLmxvZygnYnVmIHN0YXJ0L2VuZDonICsgYnVmZmVyZWQuc3RhcnQoaSkgKyAnLycgKyBidWZmZXJlZC5lbmQoaSkpO1xuICAgICAgaWYgKHBvcyArIG1heEhvbGVEdXJhdGlvbiA+PSBzdGFydCAmJiBwb3MgPCBlbmQpIHtcbiAgICAgICAgLy8gcGxheSBwb3NpdGlvbiBpcyBpbnNpZGUgdGhpcyBidWZmZXIgVGltZVJhbmdlLCByZXRyaWV2ZSBlbmQgb2YgYnVmZmVyIHBvc2l0aW9uIGFuZCBidWZmZXIgbGVuZ3RoXG4gICAgICAgIGJ1ZmZlclN0YXJ0ID0gc3RhcnQ7XG4gICAgICAgIGJ1ZmZlckVuZCA9IGVuZDtcbiAgICAgICAgYnVmZmVyTGVuID0gYnVmZmVyRW5kIC0gcG9zO1xuICAgICAgfSBlbHNlIGlmIChwb3MgKyBtYXhIb2xlRHVyYXRpb24gPCBzdGFydCkge1xuICAgICAgICBidWZmZXJTdGFydE5leHQgPSBzdGFydDtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBsZW46IGJ1ZmZlckxlbixcbiAgICAgIHN0YXJ0OiBidWZmZXJTdGFydCB8fCAwLFxuICAgICAgZW5kOiBidWZmZXJFbmQgfHwgMCxcbiAgICAgIG5leHRTdGFydDogYnVmZmVyU3RhcnROZXh0XG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTYWZlIG1ldGhvZCB0byBnZXQgYnVmZmVyZWQgcHJvcGVydHkuXG4gICAqIFNvdXJjZUJ1ZmZlci5idWZmZXJlZCBtYXkgdGhyb3cgaWYgU291cmNlQnVmZmVyIGlzIHJlbW92ZWQgZnJvbSBpdCdzIE1lZGlhU291cmNlXG4gICAqL1xuICBzdGF0aWMgZ2V0QnVmZmVyZWQobWVkaWEpIHtcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIG1lZGlhLmJ1ZmZlcmVkO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGxvZ2dlci5sb2coJ2ZhaWxlZCB0byBnZXQgbWVkaWEuYnVmZmVyZWQnLCBlKTtcbiAgICAgIHJldHVybiBub29wQnVmZmVyZWQ7XG4gICAgfVxuICB9XG59XG5cbmNsYXNzIENodW5rTWV0YWRhdGEge1xuICBjb25zdHJ1Y3RvcihsZXZlbCwgc24sIGlkLCBzaXplID0gMCwgcGFydCA9IC0xLCBwYXJ0aWFsID0gZmFsc2UpIHtcbiAgICB0aGlzLmxldmVsID0gdm9pZCAwO1xuICAgIHRoaXMuc24gPSB2b2lkIDA7XG4gICAgdGhpcy5wYXJ0ID0gdm9pZCAwO1xuICAgIHRoaXMuaWQgPSB2b2lkIDA7XG4gICAgdGhpcy5zaXplID0gdm9pZCAwO1xuICAgIHRoaXMucGFydGlhbCA9IHZvaWQgMDtcbiAgICB0aGlzLnRyYW5zbXV4aW5nID0gZ2V0TmV3UGVyZm9ybWFuY2VUaW1pbmcoKTtcbiAgICB0aGlzLmJ1ZmZlcmluZyA9IHtcbiAgICAgIGF1ZGlvOiBnZXROZXdQZXJmb3JtYW5jZVRpbWluZygpLFxuICAgICAgdmlkZW86IGdldE5ld1BlcmZvcm1hbmNlVGltaW5nKCksXG4gICAgICBhdWRpb3ZpZGVvOiBnZXROZXdQZXJmb3JtYW5jZVRpbWluZygpXG4gICAgfTtcbiAgICB0aGlzLmxldmVsID0gbGV2ZWw7XG4gICAgdGhpcy5zbiA9IHNuO1xuICAgIHRoaXMuaWQgPSBpZDtcbiAgICB0aGlzLnNpemUgPSBzaXplO1xuICAgIHRoaXMucGFydCA9IHBhcnQ7XG4gICAgdGhpcy5wYXJ0aWFsID0gcGFydGlhbDtcbiAgfVxufVxuZnVuY3Rpb24gZ2V0TmV3UGVyZm9ybWFuY2VUaW1pbmcoKSB7XG4gIHJldHVybiB7XG4gICAgc3RhcnQ6IDAsXG4gICAgZXhlY3V0ZVN0YXJ0OiAwLFxuICAgIGV4ZWN1dGVFbmQ6IDAsXG4gICAgZW5kOiAwXG4gIH07XG59XG5cbmZ1bmN0aW9uIGZpbmRGaXJzdEZyYWdXaXRoQ0MoZnJhZ21lbnRzLCBjYykge1xuICBmb3IgKGxldCBpID0gMCwgbGVuID0gZnJhZ21lbnRzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgdmFyIF9mcmFnbWVudHMkaTtcbiAgICBpZiAoKChfZnJhZ21lbnRzJGkgPSBmcmFnbWVudHNbaV0pID09IG51bGwgPyB2b2lkIDAgOiBfZnJhZ21lbnRzJGkuY2MpID09PSBjYykge1xuICAgICAgcmV0dXJuIGZyYWdtZW50c1tpXTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5mdW5jdGlvbiBzaG91bGRBbGlnbk9uRGlzY29udGludWl0aWVzKGxhc3RGcmFnLCBzd2l0Y2hEZXRhaWxzLCBkZXRhaWxzKSB7XG4gIGlmIChzd2l0Y2hEZXRhaWxzKSB7XG4gICAgaWYgKGRldGFpbHMuZW5kQ0MgPiBkZXRhaWxzLnN0YXJ0Q0MgfHwgbGFzdEZyYWcgJiYgbGFzdEZyYWcuY2MgPCBkZXRhaWxzLnN0YXJ0Q0MpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG5cbi8vIEZpbmQgdGhlIGZpcnN0IGZyYWcgaW4gdGhlIHByZXZpb3VzIGxldmVsIHdoaWNoIG1hdGNoZXMgdGhlIENDIG9mIHRoZSBmaXJzdCBmcmFnIG9mIHRoZSBuZXcgbGV2ZWxcbmZ1bmN0aW9uIGZpbmREaXNjb250aW51b3VzUmVmZXJlbmNlRnJhZyhwcmV2RGV0YWlscywgY3VyRGV0YWlscykge1xuICBjb25zdCBwcmV2RnJhZ3MgPSBwcmV2RGV0YWlscy5mcmFnbWVudHM7XG4gIGNvbnN0IGN1ckZyYWdzID0gY3VyRGV0YWlscy5mcmFnbWVudHM7XG4gIGlmICghY3VyRnJhZ3MubGVuZ3RoIHx8ICFwcmV2RnJhZ3MubGVuZ3RoKSB7XG4gICAgbG9nZ2VyLmxvZygnTm8gZnJhZ21lbnRzIHRvIGFsaWduJyk7XG4gICAgcmV0dXJuO1xuICB9XG4gIGNvbnN0IHByZXZTdGFydEZyYWcgPSBmaW5kRmlyc3RGcmFnV2l0aENDKHByZXZGcmFncywgY3VyRnJhZ3NbMF0uY2MpO1xuICBpZiAoIXByZXZTdGFydEZyYWcgfHwgcHJldlN0YXJ0RnJhZyAmJiAhcHJldlN0YXJ0RnJhZy5zdGFydFBUUykge1xuICAgIGxvZ2dlci5sb2coJ05vIGZyYWcgaW4gcHJldmlvdXMgbGV2ZWwgdG8gYWxpZ24gb24nKTtcbiAgICByZXR1cm47XG4gIH1cbiAgcmV0dXJuIHByZXZTdGFydEZyYWc7XG59XG5mdW5jdGlvbiBhZGp1c3RGcmFnbWVudFN0YXJ0KGZyYWcsIHNsaWRpbmcpIHtcbiAgaWYgKGZyYWcpIHtcbiAgICBjb25zdCBzdGFydCA9IGZyYWcuc3RhcnQgKyBzbGlkaW5nO1xuICAgIGZyYWcuc3RhcnQgPSBmcmFnLnN0YXJ0UFRTID0gc3RhcnQ7XG4gICAgZnJhZy5lbmRQVFMgPSBzdGFydCArIGZyYWcuZHVyYXRpb247XG4gIH1cbn1cbmZ1bmN0aW9uIGFkanVzdFNsaWRpbmdTdGFydChzbGlkaW5nLCBkZXRhaWxzKSB7XG4gIC8vIFVwZGF0ZSBzZWdtZW50c1xuICBjb25zdCBmcmFnbWVudHMgPSBkZXRhaWxzLmZyYWdtZW50cztcbiAgZm9yIChsZXQgaSA9IDAsIGxlbiA9IGZyYWdtZW50cy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgIGFkanVzdEZyYWdtZW50U3RhcnQoZnJhZ21lbnRzW2ldLCBzbGlkaW5nKTtcbiAgfVxuICAvLyBVcGRhdGUgTEwtSExTIHBhcnRzIGF0IHRoZSBlbmQgb2YgdGhlIHBsYXlsaXN0XG4gIGlmIChkZXRhaWxzLmZyYWdtZW50SGludCkge1xuICAgIGFkanVzdEZyYWdtZW50U3RhcnQoZGV0YWlscy5mcmFnbWVudEhpbnQsIHNsaWRpbmcpO1xuICB9XG4gIGRldGFpbHMuYWxpZ25lZFNsaWRpbmcgPSB0cnVlO1xufVxuXG4vKipcbiAqIFVzaW5nIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSBsYXN0IGxldmVsLCB0aGlzIGZ1bmN0aW9uIGNvbXB1dGVzIFBUUycgb2YgdGhlIG5ldyBmcmFnbWVudHMgc28gdGhhdCB0aGV5IGZvcm0gYVxuICogY29udGlndW91cyBzdHJlYW0gd2l0aCB0aGUgbGFzdCBmcmFnbWVudHMuXG4gKiBUaGUgUFRTIG9mIGEgZnJhZ21lbnQgbGV0cyBIbHMuanMga25vdyB3aGVyZSBpdCBmaXRzIGludG8gYSBzdHJlYW0gLSBieSBrbm93aW5nIGV2ZXJ5IFBUUywgd2Uga25vdyB3aGljaCBmcmFnbWVudCB0b1xuICogZG93bmxvYWQgYXQgYW55IGdpdmVuIHRpbWUuIFBUUyBpcyBub3JtYWxseSBjb21wdXRlZCB3aGVuIHRoZSBmcmFnbWVudCBpcyBkZW11eGVkLCBzbyB0YWtpbmcgdGhpcyBzdGVwIHNhdmVzIHVzIHRpbWVcbiAqIGFuZCBhbiBleHRyYSBkb3dubG9hZC5cbiAqIEBwYXJhbSBsYXN0RnJhZ1xuICogQHBhcmFtIGxhc3RMZXZlbFxuICogQHBhcmFtIGRldGFpbHNcbiAqL1xuZnVuY3Rpb24gYWxpZ25TdHJlYW0obGFzdEZyYWcsIHN3aXRjaERldGFpbHMsIGRldGFpbHMpIHtcbiAgaWYgKCFzd2l0Y2hEZXRhaWxzKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIGFsaWduRGlzY29udGludWl0aWVzKGxhc3RGcmFnLCBkZXRhaWxzLCBzd2l0Y2hEZXRhaWxzKTtcbiAgaWYgKCFkZXRhaWxzLmFsaWduZWRTbGlkaW5nICYmIHN3aXRjaERldGFpbHMpIHtcbiAgICAvLyBJZiB0aGUgUFRTIHdhc24ndCBmaWd1cmVkIG91dCB2aWEgZGlzY29udGludWl0eSBzZXF1ZW5jZSB0aGF0IG1lYW5zIHRoZXJlIHdhcyBubyBDQyBpbmNyZWFzZSB3aXRoaW4gdGhlIGxldmVsLlxuICAgIC8vIEFsaWduaW5nIHZpYSBQcm9ncmFtIERhdGUgVGltZSBzaG91bGQgdGhlcmVmb3JlIGJlIHJlbGlhYmxlLCBzaW5jZSBQRFQgc2hvdWxkIGJlIHRoZSBzYW1lIHdpdGhpbiB0aGUgc2FtZVxuICAgIC8vIGRpc2NvbnRpbnVpdHkgc2VxdWVuY2UuXG4gICAgYWxpZ25NZWRpYVBsYXlsaXN0QnlQRFQoZGV0YWlscywgc3dpdGNoRGV0YWlscyk7XG4gIH1cbiAgaWYgKCFkZXRhaWxzLmFsaWduZWRTbGlkaW5nICYmIHN3aXRjaERldGFpbHMgJiYgIWRldGFpbHMuc2tpcHBlZFNlZ21lbnRzKSB7XG4gICAgLy8gVHJ5IHRvIGFsaWduIG9uIHNuIHNvIHRoYXQgd2UgcGljayBhIGJldHRlciBzdGFydCBmcmFnbWVudC5cbiAgICAvLyBEbyBub3QgcGVyZm9ybSB0aGlzIG9uIHBsYXlsaXN0cyB3aXRoIGRlbHRhIHVwZGF0ZXMgYXMgdGhpcyBpcyBvbmx5IHRvIGFsaWduIGxldmVscyBvbiBzd2l0Y2hcbiAgICAvLyBhbmQgYWRqdXN0U2xpZGluZyBvbmx5IGFkanVzdHMgZnJhZ21lbnRzIGFmdGVyIHNraXBwZWRTZWdtZW50cy5cbiAgICBhZGp1c3RTbGlkaW5nKHN3aXRjaERldGFpbHMsIGRldGFpbHMpO1xuICB9XG59XG5cbi8qKlxuICogQ29tcHV0ZXMgdGhlIFBUUyBpZiBhIG5ldyBsZXZlbCdzIGZyYWdtZW50cyB1c2luZyB0aGUgUFRTIG9mIGEgZnJhZ21lbnQgaW4gdGhlIGxhc3QgbGV2ZWwgd2hpY2ggc2hhcmVzIHRoZSBzYW1lXG4gKiBkaXNjb250aW51aXR5IHNlcXVlbmNlLlxuICogQHBhcmFtIGxhc3RGcmFnIC0gVGhlIGxhc3QgRnJhZ21lbnQgd2hpY2ggc2hhcmVzIHRoZSBzYW1lIGRpc2NvbnRpbnVpdHkgc2VxdWVuY2VcbiAqIEBwYXJhbSBsYXN0TGV2ZWwgLSBUaGUgZGV0YWlscyBvZiB0aGUgbGFzdCBsb2FkZWQgbGV2ZWxcbiAqIEBwYXJhbSBkZXRhaWxzIC0gVGhlIGRldGFpbHMgb2YgdGhlIG5ldyBsZXZlbFxuICovXG5mdW5jdGlvbiBhbGlnbkRpc2NvbnRpbnVpdGllcyhsYXN0RnJhZywgZGV0YWlscywgc3dpdGNoRGV0YWlscykge1xuICBpZiAoc2hvdWxkQWxpZ25PbkRpc2NvbnRpbnVpdGllcyhsYXN0RnJhZywgc3dpdGNoRGV0YWlscywgZGV0YWlscykpIHtcbiAgICBjb25zdCByZWZlcmVuY2VGcmFnID0gZmluZERpc2NvbnRpbnVvdXNSZWZlcmVuY2VGcmFnKHN3aXRjaERldGFpbHMsIGRldGFpbHMpO1xuICAgIGlmIChyZWZlcmVuY2VGcmFnICYmIGlzRmluaXRlTnVtYmVyKHJlZmVyZW5jZUZyYWcuc3RhcnQpKSB7XG4gICAgICBsb2dnZXIubG9nKGBBZGp1c3RpbmcgUFRTIHVzaW5nIGxhc3QgbGV2ZWwgZHVlIHRvIENDIGluY3JlYXNlIHdpdGhpbiBjdXJyZW50IGxldmVsICR7ZGV0YWlscy51cmx9YCk7XG4gICAgICBhZGp1c3RTbGlkaW5nU3RhcnQocmVmZXJlbmNlRnJhZy5zdGFydCwgZGV0YWlscyk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogRW5zdXJlcyBhcHByb3ByaWF0ZSB0aW1lLWFsaWdubWVudCBiZXR3ZWVuIHJlbmRpdGlvbnMgYmFzZWQgb24gUERULlxuICogVGhpcyBmdW5jdGlvbiBhc3N1bWVzIHRoZSB0aW1lbGluZXMgcmVwcmVzZW50ZWQgaW4gYHJlZkRldGFpbHNgIGFyZSBhY2N1cmF0ZSwgaW5jbHVkaW5nIHRoZSBQRFRzXG4gKiBmb3IgdGhlIGxhc3QgZGlzY29udGludWl0eSBzZXF1ZW5jZSBudW1iZXIgc2hhcmVkIGJ5IGJvdGggcGxheWxpc3RzIHdoZW4gcHJlc2VudCxcbiAqIGFuZCB1c2VzIHRoZSBcIndhbGxjbG9ja1wiL1BEVCB0aW1lbGluZSBhcyBhIGNyb3NzLXJlZmVyZW5jZSB0byBgZGV0YWlsc2AsIGFkanVzdGluZyB0aGUgcHJlc2VudGF0aW9uXG4gKiB0aW1lcy90aW1lbGluZXMgb2YgYGRldGFpbHNgIGFjY29yZGluZ2x5LlxuICogR2l2ZW4gdGhlIGFzeW5jaHJvbm91cyBuYXR1cmUgb2YgZmV0Y2hlcyBhbmQgaW5pdGlhbCBsb2FkcyBvZiBsaXZlIGBtYWluYCBhbmQgYXVkaW8vc3VidGl0bGUgdHJhY2tzLFxuICogdGhlIHByaW1hcnkgcHVycG9zZSBvZiB0aGlzIGZ1bmN0aW9uIGlzIHRvIGVuc3VyZSB0aGUgXCJsb2NhbCB0aW1lbGluZXNcIiBvZiBhdWRpby9zdWJ0aXRsZSB0cmFja3NcbiAqIGFyZSBhbGlnbmVkIHRvIHRoZSBtYWluL3ZpZGVvIHRpbWVsaW5lLCB1c2luZyBQRFQgYXMgdGhlIGNyb3NzLXJlZmVyZW5jZS9cImFuY2hvclwiIHRoYXQgc2hvdWxkXG4gKiBiZSBjb25zaXN0ZW50IGFjcm9zcyBwbGF5bGlzdHMsIHBlciB0aGUgSExTIHNwZWMuXG4gKiBAcGFyYW0gZGV0YWlscyAtIFRoZSBkZXRhaWxzIG9mIHRoZSByZW5kaXRpb24geW91J2QgbGlrZSB0byB0aW1lLWFsaWduIChlLmcuIGFuIGF1ZGlvIHJlbmRpdGlvbikuXG4gKiBAcGFyYW0gcmVmRGV0YWlscyAtIFRoZSBkZXRhaWxzIG9mIHRoZSByZWZlcmVuY2UgcmVuZGl0aW9uIHdpdGggc3RhcnQgYW5kIFBEVCB0aW1lcyBmb3IgYWxpZ25tZW50LlxuICovXG5mdW5jdGlvbiBhbGlnbk1lZGlhUGxheWxpc3RCeVBEVChkZXRhaWxzLCByZWZEZXRhaWxzKSB7XG4gIGlmICghZGV0YWlscy5oYXNQcm9ncmFtRGF0ZVRpbWUgfHwgIXJlZkRldGFpbHMuaGFzUHJvZ3JhbURhdGVUaW1lKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIGNvbnN0IGZyYWdtZW50cyA9IGRldGFpbHMuZnJhZ21lbnRzO1xuICBjb25zdCByZWZGcmFnbWVudHMgPSByZWZEZXRhaWxzLmZyYWdtZW50cztcbiAgaWYgKCFmcmFnbWVudHMubGVuZ3RoIHx8ICFyZWZGcmFnbWVudHMubGVuZ3RoKSB7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgLy8gQ2FsY3VsYXRlIGEgZGVsdGEgdG8gYXBwbHkgdG8gYWxsIGZyYWdtZW50cyBhY2NvcmRpbmcgdG8gdGhlIGRlbHRhIGluIFBEVCB0aW1lcyBhbmQgc3RhcnQgdGltZXNcbiAgLy8gb2YgYSBmcmFnbWVudCBpbiB0aGUgcmVmZXJlbmNlIGRldGFpbHMsIGFuZCBhIGZyYWdtZW50IGluIHRoZSB0YXJnZXQgZGV0YWlscyBvZiB0aGUgc2FtZSBkaXNjb250aW51aXR5LlxuICAvLyBJZiBhIGZyYWdtZW50IG9mIHRoZSBzYW1lIGRpc2NvbnRpbnVpdHkgd2FzIG5vdCBmb3VuZCB1c2UgdGhlIG1pZGRsZSBmcmFnbWVudCBvZiBib3RoLlxuICBsZXQgcmVmRnJhZztcbiAgbGV0IGZyYWc7XG4gIGNvbnN0IHRhcmdldENDID0gTWF0aC5taW4ocmVmRGV0YWlscy5lbmRDQywgZGV0YWlscy5lbmRDQyk7XG4gIGlmIChyZWZEZXRhaWxzLnN0YXJ0Q0MgPCB0YXJnZXRDQyAmJiBkZXRhaWxzLnN0YXJ0Q0MgPCB0YXJnZXRDQykge1xuICAgIHJlZkZyYWcgPSBmaW5kRmlyc3RGcmFnV2l0aENDKHJlZkZyYWdtZW50cywgdGFyZ2V0Q0MpO1xuICAgIGZyYWcgPSBmaW5kRmlyc3RGcmFnV2l0aENDKGZyYWdtZW50cywgdGFyZ2V0Q0MpO1xuICB9XG4gIGlmICghcmVmRnJhZyB8fCAhZnJhZykge1xuICAgIHJlZkZyYWcgPSByZWZGcmFnbWVudHNbTWF0aC5mbG9vcihyZWZGcmFnbWVudHMubGVuZ3RoIC8gMildO1xuICAgIGZyYWcgPSBmaW5kRmlyc3RGcmFnV2l0aENDKGZyYWdtZW50cywgcmVmRnJhZy5jYykgfHwgZnJhZ21lbnRzW01hdGguZmxvb3IoZnJhZ21lbnRzLmxlbmd0aCAvIDIpXTtcbiAgfVxuICBjb25zdCByZWZQRFQgPSByZWZGcmFnLnByb2dyYW1EYXRlVGltZTtcbiAgY29uc3QgdGFyZ2V0UERUID0gZnJhZy5wcm9ncmFtRGF0ZVRpbWU7XG4gIGlmICghcmVmUERUIHx8ICF0YXJnZXRQRFQpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgY29uc3QgZGVsdGEgPSAodGFyZ2V0UERUIC0gcmVmUERUKSAvIDEwMDAgLSAoZnJhZy5zdGFydCAtIHJlZkZyYWcuc3RhcnQpO1xuICBhZGp1c3RTbGlkaW5nU3RhcnQoZGVsdGEsIGRldGFpbHMpO1xufVxuXG5jb25zdCBNSU5fQ0hVTktfU0laRSA9IE1hdGgucG93KDIsIDE3KTsgLy8gMTI4a2JcblxuY2xhc3MgRnJhZ21lbnRMb2FkZXIge1xuICBjb25zdHJ1Y3Rvcihjb25maWcpIHtcbiAgICB0aGlzLmNvbmZpZyA9IHZvaWQgMDtcbiAgICB0aGlzLmxvYWRlciA9IG51bGw7XG4gICAgdGhpcy5wYXJ0TG9hZFRpbWVvdXQgPSAtMTtcbiAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIGlmICh0aGlzLmxvYWRlcikge1xuICAgICAgdGhpcy5sb2FkZXIuZGVzdHJveSgpO1xuICAgICAgdGhpcy5sb2FkZXIgPSBudWxsO1xuICAgIH1cbiAgfVxuICBhYm9ydCgpIHtcbiAgICBpZiAodGhpcy5sb2FkZXIpIHtcbiAgICAgIC8vIEFib3J0IHRoZSBsb2FkZXIgZm9yIGN1cnJlbnQgZnJhZ21lbnQuIE9ubHkgb25lIG1heSBsb2FkIGF0IGFueSBnaXZlbiB0aW1lXG4gICAgICB0aGlzLmxvYWRlci5hYm9ydCgpO1xuICAgIH1cbiAgfVxuICBsb2FkKGZyYWcsIG9uUHJvZ3Jlc3MpIHtcbiAgICBjb25zdCB1cmwgPSBmcmFnLnVybDtcbiAgICBpZiAoIXVybCkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KG5ldyBMb2FkRXJyb3Ioe1xuICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk5FVFdPUktfRVJST1IsXG4gICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX0xPQURfRVJST1IsXG4gICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgZnJhZyxcbiAgICAgICAgZXJyb3I6IG5ldyBFcnJvcihgRnJhZ21lbnQgZG9lcyBub3QgaGF2ZSBhICR7dXJsID8gJ3BhcnQgbGlzdCcgOiAndXJsJ31gKSxcbiAgICAgICAgbmV0d29ya0RldGFpbHM6IG51bGxcbiAgICAgIH0pKTtcbiAgICB9XG4gICAgdGhpcy5hYm9ydCgpO1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuY29uZmlnO1xuICAgIGNvbnN0IEZyYWdtZW50SUxvYWRlciA9IGNvbmZpZy5mTG9hZGVyO1xuICAgIGNvbnN0IERlZmF1bHRJTG9hZGVyID0gY29uZmlnLmxvYWRlcjtcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgaWYgKHRoaXMubG9hZGVyKSB7XG4gICAgICAgIHRoaXMubG9hZGVyLmRlc3Ryb3koKTtcbiAgICAgIH1cbiAgICAgIGlmIChmcmFnLmdhcCkge1xuICAgICAgICBpZiAoZnJhZy50YWdMaXN0LnNvbWUodGFncyA9PiB0YWdzWzBdID09PSAnR0FQJykpIHtcbiAgICAgICAgICByZWplY3QoY3JlYXRlR2FwTG9hZEVycm9yKGZyYWcpKTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gUmVzZXQgdGVtcG9yYXJ5IHRyZWF0bWVudCBhcyBHQVAgdGFnXG4gICAgICAgICAgZnJhZy5nYXAgPSBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgY29uc3QgbG9hZGVyID0gdGhpcy5sb2FkZXIgPSBmcmFnLmxvYWRlciA9IEZyYWdtZW50SUxvYWRlciA/IG5ldyBGcmFnbWVudElMb2FkZXIoY29uZmlnKSA6IG5ldyBEZWZhdWx0SUxvYWRlcihjb25maWcpO1xuICAgICAgY29uc3QgbG9hZGVyQ29udGV4dCA9IGNyZWF0ZUxvYWRlckNvbnRleHQoZnJhZyk7XG4gICAgICBjb25zdCBsb2FkUG9saWN5ID0gZ2V0TG9hZGVyQ29uZmlnV2l0aG91dFJldGllcyhjb25maWcuZnJhZ0xvYWRQb2xpY3kuZGVmYXVsdCk7XG4gICAgICBjb25zdCBsb2FkZXJDb25maWcgPSB7XG4gICAgICAgIGxvYWRQb2xpY3ksXG4gICAgICAgIHRpbWVvdXQ6IGxvYWRQb2xpY3kubWF4TG9hZFRpbWVNcyxcbiAgICAgICAgbWF4UmV0cnk6IDAsXG4gICAgICAgIHJldHJ5RGVsYXk6IDAsXG4gICAgICAgIG1heFJldHJ5RGVsYXk6IDAsXG4gICAgICAgIGhpZ2hXYXRlck1hcms6IGZyYWcuc24gPT09ICdpbml0U2VnbWVudCcgPyBJbmZpbml0eSA6IE1JTl9DSFVOS19TSVpFXG4gICAgICB9O1xuICAgICAgLy8gQXNzaWduIGZyYWcgc3RhdHMgdG8gdGhlIGxvYWRlcidzIHN0YXRzIHJlZmVyZW5jZVxuICAgICAgZnJhZy5zdGF0cyA9IGxvYWRlci5zdGF0cztcbiAgICAgIGxvYWRlci5sb2FkKGxvYWRlckNvbnRleHQsIGxvYWRlckNvbmZpZywge1xuICAgICAgICBvblN1Y2Nlc3M6IChyZXNwb25zZSwgc3RhdHMsIGNvbnRleHQsIG5ldHdvcmtEZXRhaWxzKSA9PiB7XG4gICAgICAgICAgdGhpcy5yZXNldExvYWRlcihmcmFnLCBsb2FkZXIpO1xuICAgICAgICAgIGxldCBwYXlsb2FkID0gcmVzcG9uc2UuZGF0YTtcbiAgICAgICAgICBpZiAoY29udGV4dC5yZXNldElWICYmIGZyYWcuZGVjcnlwdGRhdGEpIHtcbiAgICAgICAgICAgIGZyYWcuZGVjcnlwdGRhdGEuaXYgPSBuZXcgVWludDhBcnJheShwYXlsb2FkLnNsaWNlKDAsIDE2KSk7XG4gICAgICAgICAgICBwYXlsb2FkID0gcGF5bG9hZC5zbGljZSgxNik7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJlc29sdmUoe1xuICAgICAgICAgICAgZnJhZyxcbiAgICAgICAgICAgIHBhcnQ6IG51bGwsXG4gICAgICAgICAgICBwYXlsb2FkLFxuICAgICAgICAgICAgbmV0d29ya0RldGFpbHNcbiAgICAgICAgICB9KTtcbiAgICAgICAgfSxcbiAgICAgICAgb25FcnJvcjogKHJlc3BvbnNlLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscywgc3RhdHMpID0+IHtcbiAgICAgICAgICB0aGlzLnJlc2V0TG9hZGVyKGZyYWcsIGxvYWRlcik7XG4gICAgICAgICAgcmVqZWN0KG5ldyBMb2FkRXJyb3Ioe1xuICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLkZSQUdfTE9BRF9FUlJPUixcbiAgICAgICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICByZXNwb25zZTogX29iamVjdFNwcmVhZDIoe1xuICAgICAgICAgICAgICB1cmwsXG4gICAgICAgICAgICAgIGRhdGE6IHVuZGVmaW5lZFxuICAgICAgICAgICAgfSwgcmVzcG9uc2UpLFxuICAgICAgICAgICAgZXJyb3I6IG5ldyBFcnJvcihgSFRUUCBFcnJvciAke3Jlc3BvbnNlLmNvZGV9ICR7cmVzcG9uc2UudGV4dH1gKSxcbiAgICAgICAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgICAgICAgc3RhdHNcbiAgICAgICAgICB9KSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uQWJvcnQ6IChzdGF0cywgY29udGV4dCwgbmV0d29ya0RldGFpbHMpID0+IHtcbiAgICAgICAgICB0aGlzLnJlc2V0TG9hZGVyKGZyYWcsIGxvYWRlcik7XG4gICAgICAgICAgcmVqZWN0KG5ldyBMb2FkRXJyb3Ioe1xuICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLklOVEVSTkFMX0FCT1JURUQsXG4gICAgICAgICAgICBmYXRhbDogZmFsc2UsXG4gICAgICAgICAgICBmcmFnLFxuICAgICAgICAgICAgZXJyb3I6IG5ldyBFcnJvcignQWJvcnRlZCcpLFxuICAgICAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgICAgICBzdGF0c1xuICAgICAgICAgIH0pKTtcbiAgICAgICAgfSxcbiAgICAgICAgb25UaW1lb3V0OiAoc3RhdHMsIGNvbnRleHQsIG5ldHdvcmtEZXRhaWxzKSA9PiB7XG4gICAgICAgICAgdGhpcy5yZXNldExvYWRlcihmcmFnLCBsb2FkZXIpO1xuICAgICAgICAgIHJlamVjdChuZXcgTG9hZEVycm9yKHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTkVUV09SS19FUlJPUixcbiAgICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX0xPQURfVElNRU9VVCxcbiAgICAgICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICBlcnJvcjogbmV3IEVycm9yKGBUaW1lb3V0IGFmdGVyICR7bG9hZGVyQ29uZmlnLnRpbWVvdXR9bXNgKSxcbiAgICAgICAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgICAgICAgc3RhdHNcbiAgICAgICAgICB9KSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uUHJvZ3Jlc3M6IChzdGF0cywgY29udGV4dCwgZGF0YSwgbmV0d29ya0RldGFpbHMpID0+IHtcbiAgICAgICAgICBpZiAob25Qcm9ncmVzcykge1xuICAgICAgICAgICAgb25Qcm9ncmVzcyh7XG4gICAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICAgIHBhcnQ6IG51bGwsXG4gICAgICAgICAgICAgIHBheWxvYWQ6IGRhdGEsXG4gICAgICAgICAgICAgIG5ldHdvcmtEZXRhaWxzXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG4gIGxvYWRQYXJ0KGZyYWcsIHBhcnQsIG9uUHJvZ3Jlc3MpIHtcbiAgICB0aGlzLmFib3J0KCk7XG4gICAgY29uc3QgY29uZmlnID0gdGhpcy5jb25maWc7XG4gICAgY29uc3QgRnJhZ21lbnRJTG9hZGVyID0gY29uZmlnLmZMb2FkZXI7XG4gICAgY29uc3QgRGVmYXVsdElMb2FkZXIgPSBjb25maWcubG9hZGVyO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBpZiAodGhpcy5sb2FkZXIpIHtcbiAgICAgICAgdGhpcy5sb2FkZXIuZGVzdHJveSgpO1xuICAgICAgfVxuICAgICAgaWYgKGZyYWcuZ2FwIHx8IHBhcnQuZ2FwKSB7XG4gICAgICAgIHJlamVjdChjcmVhdGVHYXBMb2FkRXJyb3IoZnJhZywgcGFydCkpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBsb2FkZXIgPSB0aGlzLmxvYWRlciA9IGZyYWcubG9hZGVyID0gRnJhZ21lbnRJTG9hZGVyID8gbmV3IEZyYWdtZW50SUxvYWRlcihjb25maWcpIDogbmV3IERlZmF1bHRJTG9hZGVyKGNvbmZpZyk7XG4gICAgICBjb25zdCBsb2FkZXJDb250ZXh0ID0gY3JlYXRlTG9hZGVyQ29udGV4dChmcmFnLCBwYXJ0KTtcbiAgICAgIC8vIFNob3VsZCB3ZSBkZWZpbmUgYW5vdGhlciBsb2FkIHBvbGljeSBmb3IgcGFydHM/XG4gICAgICBjb25zdCBsb2FkUG9saWN5ID0gZ2V0TG9hZGVyQ29uZmlnV2l0aG91dFJldGllcyhjb25maWcuZnJhZ0xvYWRQb2xpY3kuZGVmYXVsdCk7XG4gICAgICBjb25zdCBsb2FkZXJDb25maWcgPSB7XG4gICAgICAgIGxvYWRQb2xpY3ksXG4gICAgICAgIHRpbWVvdXQ6IGxvYWRQb2xpY3kubWF4TG9hZFRpbWVNcyxcbiAgICAgICAgbWF4UmV0cnk6IDAsXG4gICAgICAgIHJldHJ5RGVsYXk6IDAsXG4gICAgICAgIG1heFJldHJ5RGVsYXk6IDAsXG4gICAgICAgIGhpZ2hXYXRlck1hcms6IE1JTl9DSFVOS19TSVpFXG4gICAgICB9O1xuICAgICAgLy8gQXNzaWduIHBhcnQgc3RhdHMgdG8gdGhlIGxvYWRlcidzIHN0YXRzIHJlZmVyZW5jZVxuICAgICAgcGFydC5zdGF0cyA9IGxvYWRlci5zdGF0cztcbiAgICAgIGxvYWRlci5sb2FkKGxvYWRlckNvbnRleHQsIGxvYWRlckNvbmZpZywge1xuICAgICAgICBvblN1Y2Nlc3M6IChyZXNwb25zZSwgc3RhdHMsIGNvbnRleHQsIG5ldHdvcmtEZXRhaWxzKSA9PiB7XG4gICAgICAgICAgdGhpcy5yZXNldExvYWRlcihmcmFnLCBsb2FkZXIpO1xuICAgICAgICAgIHRoaXMudXBkYXRlU3RhdHNGcm9tUGFydChmcmFnLCBwYXJ0KTtcbiAgICAgICAgICBjb25zdCBwYXJ0TG9hZGVkRGF0YSA9IHtcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICBwYXJ0LFxuICAgICAgICAgICAgcGF5bG9hZDogcmVzcG9uc2UuZGF0YSxcbiAgICAgICAgICAgIG5ldHdvcmtEZXRhaWxzXG4gICAgICAgICAgfTtcbiAgICAgICAgICBvblByb2dyZXNzKHBhcnRMb2FkZWREYXRhKTtcbiAgICAgICAgICByZXNvbHZlKHBhcnRMb2FkZWREYXRhKTtcbiAgICAgICAgfSxcbiAgICAgICAgb25FcnJvcjogKHJlc3BvbnNlLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscywgc3RhdHMpID0+IHtcbiAgICAgICAgICB0aGlzLnJlc2V0TG9hZGVyKGZyYWcsIGxvYWRlcik7XG4gICAgICAgICAgcmVqZWN0KG5ldyBMb2FkRXJyb3Ioe1xuICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLkZSQUdfTE9BRF9FUlJPUixcbiAgICAgICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICBwYXJ0LFxuICAgICAgICAgICAgcmVzcG9uc2U6IF9vYmplY3RTcHJlYWQyKHtcbiAgICAgICAgICAgICAgdXJsOiBsb2FkZXJDb250ZXh0LnVybCxcbiAgICAgICAgICAgICAgZGF0YTogdW5kZWZpbmVkXG4gICAgICAgICAgICB9LCByZXNwb25zZSksXG4gICAgICAgICAgICBlcnJvcjogbmV3IEVycm9yKGBIVFRQIEVycm9yICR7cmVzcG9uc2UuY29kZX0gJHtyZXNwb25zZS50ZXh0fWApLFxuICAgICAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgICAgICBzdGF0c1xuICAgICAgICAgIH0pKTtcbiAgICAgICAgfSxcbiAgICAgICAgb25BYm9ydDogKHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICAgIGZyYWcuc3RhdHMuYWJvcnRlZCA9IHBhcnQuc3RhdHMuYWJvcnRlZDtcbiAgICAgICAgICB0aGlzLnJlc2V0TG9hZGVyKGZyYWcsIGxvYWRlcik7XG4gICAgICAgICAgcmVqZWN0KG5ldyBMb2FkRXJyb3Ioe1xuICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5ORVRXT1JLX0VSUk9SLFxuICAgICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLklOVEVSTkFMX0FCT1JURUQsXG4gICAgICAgICAgICBmYXRhbDogZmFsc2UsXG4gICAgICAgICAgICBmcmFnLFxuICAgICAgICAgICAgcGFydCxcbiAgICAgICAgICAgIGVycm9yOiBuZXcgRXJyb3IoJ0Fib3J0ZWQnKSxcbiAgICAgICAgICAgIG5ldHdvcmtEZXRhaWxzLFxuICAgICAgICAgICAgc3RhdHNcbiAgICAgICAgICB9KSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uVGltZW91dDogKHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICAgIHRoaXMucmVzZXRMb2FkZXIoZnJhZywgbG9hZGVyKTtcbiAgICAgICAgICByZWplY3QobmV3IExvYWRFcnJvcih7XG4gICAgICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk5FVFdPUktfRVJST1IsXG4gICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuRlJBR19MT0FEX1RJTUVPVVQsXG4gICAgICAgICAgICBmYXRhbDogZmFsc2UsXG4gICAgICAgICAgICBmcmFnLFxuICAgICAgICAgICAgcGFydCxcbiAgICAgICAgICAgIGVycm9yOiBuZXcgRXJyb3IoYFRpbWVvdXQgYWZ0ZXIgJHtsb2FkZXJDb25maWcudGltZW91dH1tc2ApLFxuICAgICAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgICAgICBzdGF0c1xuICAgICAgICAgIH0pKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cbiAgdXBkYXRlU3RhdHNGcm9tUGFydChmcmFnLCBwYXJ0KSB7XG4gICAgY29uc3QgZnJhZ1N0YXRzID0gZnJhZy5zdGF0cztcbiAgICBjb25zdCBwYXJ0U3RhdHMgPSBwYXJ0LnN0YXRzO1xuICAgIGNvbnN0IHBhcnRUb3RhbCA9IHBhcnRTdGF0cy50b3RhbDtcbiAgICBmcmFnU3RhdHMubG9hZGVkICs9IHBhcnRTdGF0cy5sb2FkZWQ7XG4gICAgaWYgKHBhcnRUb3RhbCkge1xuICAgICAgY29uc3QgZXN0VG90YWxQYXJ0cyA9IE1hdGgucm91bmQoZnJhZy5kdXJhdGlvbiAvIHBhcnQuZHVyYXRpb24pO1xuICAgICAgY29uc3QgZXN0TG9hZGVkUGFydHMgPSBNYXRoLm1pbihNYXRoLnJvdW5kKGZyYWdTdGF0cy5sb2FkZWQgLyBwYXJ0VG90YWwpLCBlc3RUb3RhbFBhcnRzKTtcbiAgICAgIGNvbnN0IGVzdFJlbWFpbmluZ1BhcnRzID0gZXN0VG90YWxQYXJ0cyAtIGVzdExvYWRlZFBhcnRzO1xuICAgICAgY29uc3QgZXN0UmVtYWluaW5nQnl0ZXMgPSBlc3RSZW1haW5pbmdQYXJ0cyAqIE1hdGgucm91bmQoZnJhZ1N0YXRzLmxvYWRlZCAvIGVzdExvYWRlZFBhcnRzKTtcbiAgICAgIGZyYWdTdGF0cy50b3RhbCA9IGZyYWdTdGF0cy5sb2FkZWQgKyBlc3RSZW1haW5pbmdCeXRlcztcbiAgICB9IGVsc2Uge1xuICAgICAgZnJhZ1N0YXRzLnRvdGFsID0gTWF0aC5tYXgoZnJhZ1N0YXRzLmxvYWRlZCwgZnJhZ1N0YXRzLnRvdGFsKTtcbiAgICB9XG4gICAgY29uc3QgZnJhZ0xvYWRpbmcgPSBmcmFnU3RhdHMubG9hZGluZztcbiAgICBjb25zdCBwYXJ0TG9hZGluZyA9IHBhcnRTdGF0cy5sb2FkaW5nO1xuICAgIGlmIChmcmFnTG9hZGluZy5zdGFydCkge1xuICAgICAgLy8gYWRkIHRvIGZyYWdtZW50IGxvYWRlciBsYXRlbmN5XG4gICAgICBmcmFnTG9hZGluZy5maXJzdCArPSBwYXJ0TG9hZGluZy5maXJzdCAtIHBhcnRMb2FkaW5nLnN0YXJ0O1xuICAgIH0gZWxzZSB7XG4gICAgICBmcmFnTG9hZGluZy5zdGFydCA9IHBhcnRMb2FkaW5nLnN0YXJ0O1xuICAgICAgZnJhZ0xvYWRpbmcuZmlyc3QgPSBwYXJ0TG9hZGluZy5maXJzdDtcbiAgICB9XG4gICAgZnJhZ0xvYWRpbmcuZW5kID0gcGFydExvYWRpbmcuZW5kO1xuICB9XG4gIHJlc2V0TG9hZGVyKGZyYWcsIGxvYWRlcikge1xuICAgIGZyYWcubG9hZGVyID0gbnVsbDtcbiAgICBpZiAodGhpcy5sb2FkZXIgPT09IGxvYWRlcikge1xuICAgICAgc2VsZi5jbGVhclRpbWVvdXQodGhpcy5wYXJ0TG9hZFRpbWVvdXQpO1xuICAgICAgdGhpcy5sb2FkZXIgPSBudWxsO1xuICAgIH1cbiAgICBsb2FkZXIuZGVzdHJveSgpO1xuICB9XG59XG5mdW5jdGlvbiBjcmVhdGVMb2FkZXJDb250ZXh0KGZyYWcsIHBhcnQgPSBudWxsKSB7XG4gIGNvbnN0IHNlZ21lbnQgPSBwYXJ0IHx8IGZyYWc7XG4gIGNvbnN0IGxvYWRlckNvbnRleHQgPSB7XG4gICAgZnJhZyxcbiAgICBwYXJ0LFxuICAgIHJlc3BvbnNlVHlwZTogJ2FycmF5YnVmZmVyJyxcbiAgICB1cmw6IHNlZ21lbnQudXJsLFxuICAgIGhlYWRlcnM6IHt9LFxuICAgIHJhbmdlU3RhcnQ6IDAsXG4gICAgcmFuZ2VFbmQ6IDBcbiAgfTtcbiAgY29uc3Qgc3RhcnQgPSBzZWdtZW50LmJ5dGVSYW5nZVN0YXJ0T2Zmc2V0O1xuICBjb25zdCBlbmQgPSBzZWdtZW50LmJ5dGVSYW5nZUVuZE9mZnNldDtcbiAgaWYgKGlzRmluaXRlTnVtYmVyKHN0YXJ0KSAmJiBpc0Zpbml0ZU51bWJlcihlbmQpKSB7XG4gICAgdmFyIF9mcmFnJGRlY3J5cHRkYXRhO1xuICAgIGxldCBieXRlUmFuZ2VTdGFydCA9IHN0YXJ0O1xuICAgIGxldCBieXRlUmFuZ2VFbmQgPSBlbmQ7XG4gICAgaWYgKGZyYWcuc24gPT09ICdpbml0U2VnbWVudCcgJiYgKChfZnJhZyRkZWNyeXB0ZGF0YSA9IGZyYWcuZGVjcnlwdGRhdGEpID09IG51bGwgPyB2b2lkIDAgOiBfZnJhZyRkZWNyeXB0ZGF0YS5tZXRob2QpID09PSAnQUVTLTEyOCcpIHtcbiAgICAgIC8vIE1BUCBzZWdtZW50IGVuY3J5cHRlZCB3aXRoIG1ldGhvZCAnQUVTLTEyOCcsIHdoZW4gc2VydmVkIHdpdGggSFRUUCBSYW5nZSxcbiAgICAgIC8vIGhhcyB0aGUgdW5lbmNyeXB0ZWQgc2l6ZSBzcGVjaWZpZWQgaW4gdGhlIHJhbmdlLlxuICAgICAgLy8gUmVmOiBodHRwczovL3Rvb2xzLmlldGYub3JnL2h0bWwvZHJhZnQtcGFudG9zLWhscy1yZmM4MjE2YmlzLTA4I3NlY3Rpb24tNi4zLjZcbiAgICAgIGNvbnN0IGZyYWdtZW50TGVuID0gZW5kIC0gc3RhcnQ7XG4gICAgICBpZiAoZnJhZ21lbnRMZW4gJSAxNikge1xuICAgICAgICBieXRlUmFuZ2VFbmQgPSBlbmQgKyAoMTYgLSBmcmFnbWVudExlbiAlIDE2KTtcbiAgICAgIH1cbiAgICAgIGlmIChzdGFydCAhPT0gMCkge1xuICAgICAgICBsb2FkZXJDb250ZXh0LnJlc2V0SVYgPSB0cnVlO1xuICAgICAgICBieXRlUmFuZ2VTdGFydCA9IHN0YXJ0IC0gMTY7XG4gICAgICB9XG4gICAgfVxuICAgIGxvYWRlckNvbnRleHQucmFuZ2VTdGFydCA9IGJ5dGVSYW5nZVN0YXJ0O1xuICAgIGxvYWRlckNvbnRleHQucmFuZ2VFbmQgPSBieXRlUmFuZ2VFbmQ7XG4gIH1cbiAgcmV0dXJuIGxvYWRlckNvbnRleHQ7XG59XG5mdW5jdGlvbiBjcmVhdGVHYXBMb2FkRXJyb3IoZnJhZywgcGFydCkge1xuICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcihgR0FQICR7ZnJhZy5nYXAgPyAndGFnJyA6ICdhdHRyaWJ1dGUnfSBmb3VuZGApO1xuICBjb25zdCBlcnJvckRhdGEgPSB7XG4gICAgdHlwZTogRXJyb3JUeXBlcy5NRURJQV9FUlJPUixcbiAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuRlJBR19HQVAsXG4gICAgZmF0YWw6IGZhbHNlLFxuICAgIGZyYWcsXG4gICAgZXJyb3IsXG4gICAgbmV0d29ya0RldGFpbHM6IG51bGxcbiAgfTtcbiAgaWYgKHBhcnQpIHtcbiAgICBlcnJvckRhdGEucGFydCA9IHBhcnQ7XG4gIH1cbiAgKHBhcnQgPyBwYXJ0IDogZnJhZykuc3RhdHMuYWJvcnRlZCA9IHRydWU7XG4gIHJldHVybiBuZXcgTG9hZEVycm9yKGVycm9yRGF0YSk7XG59XG5jbGFzcyBMb2FkRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKGRhdGEpIHtcbiAgICBzdXBlcihkYXRhLmVycm9yLm1lc3NhZ2UpO1xuICAgIHRoaXMuZGF0YSA9IHZvaWQgMDtcbiAgICB0aGlzLmRhdGEgPSBkYXRhO1xuICB9XG59XG5cbmNsYXNzIEFFU0NyeXB0byB7XG4gIGNvbnN0cnVjdG9yKHN1YnRsZSwgaXYpIHtcbiAgICB0aGlzLnN1YnRsZSA9IHZvaWQgMDtcbiAgICB0aGlzLmFlc0lWID0gdm9pZCAwO1xuICAgIHRoaXMuc3VidGxlID0gc3VidGxlO1xuICAgIHRoaXMuYWVzSVYgPSBpdjtcbiAgfVxuICBkZWNyeXB0KGRhdGEsIGtleSkge1xuICAgIHJldHVybiB0aGlzLnN1YnRsZS5kZWNyeXB0KHtcbiAgICAgIG5hbWU6ICdBRVMtQ0JDJyxcbiAgICAgIGl2OiB0aGlzLmFlc0lWXG4gICAgfSwga2V5LCBkYXRhKTtcbiAgfVxufVxuXG5jbGFzcyBGYXN0QUVTS2V5IHtcbiAgY29uc3RydWN0b3Ioc3VidGxlLCBrZXkpIHtcbiAgICB0aGlzLnN1YnRsZSA9IHZvaWQgMDtcbiAgICB0aGlzLmtleSA9IHZvaWQgMDtcbiAgICB0aGlzLnN1YnRsZSA9IHN1YnRsZTtcbiAgICB0aGlzLmtleSA9IGtleTtcbiAgfVxuICBleHBhbmRLZXkoKSB7XG4gICAgcmV0dXJuIHRoaXMuc3VidGxlLmltcG9ydEtleSgncmF3JywgdGhpcy5rZXksIHtcbiAgICAgIG5hbWU6ICdBRVMtQ0JDJ1xuICAgIH0sIGZhbHNlLCBbJ2VuY3J5cHQnLCAnZGVjcnlwdCddKTtcbiAgfVxufVxuXG4vLyBQS0NTN1xuZnVuY3Rpb24gcmVtb3ZlUGFkZGluZyhhcnJheSkge1xuICBjb25zdCBvdXRwdXRCeXRlcyA9IGFycmF5LmJ5dGVMZW5ndGg7XG4gIGNvbnN0IHBhZGRpbmdCeXRlcyA9IG91dHB1dEJ5dGVzICYmIG5ldyBEYXRhVmlldyhhcnJheS5idWZmZXIpLmdldFVpbnQ4KG91dHB1dEJ5dGVzIC0gMSk7XG4gIGlmIChwYWRkaW5nQnl0ZXMpIHtcbiAgICByZXR1cm4gc2xpY2VVaW50OChhcnJheSwgMCwgb3V0cHV0Qnl0ZXMgLSBwYWRkaW5nQnl0ZXMpO1xuICB9XG4gIHJldHVybiBhcnJheTtcbn1cbmNsYXNzIEFFU0RlY3J5cHRvciB7XG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMucmNvbiA9IFsweDAsIDB4MSwgMHgyLCAweDQsIDB4OCwgMHgxMCwgMHgyMCwgMHg0MCwgMHg4MCwgMHgxYiwgMHgzNl07XG4gICAgdGhpcy5zdWJNaXggPSBbbmV3IFVpbnQzMkFycmF5KDI1NiksIG5ldyBVaW50MzJBcnJheSgyNTYpLCBuZXcgVWludDMyQXJyYXkoMjU2KSwgbmV3IFVpbnQzMkFycmF5KDI1NildO1xuICAgIHRoaXMuaW52U3ViTWl4ID0gW25ldyBVaW50MzJBcnJheSgyNTYpLCBuZXcgVWludDMyQXJyYXkoMjU2KSwgbmV3IFVpbnQzMkFycmF5KDI1NiksIG5ldyBVaW50MzJBcnJheSgyNTYpXTtcbiAgICB0aGlzLnNCb3ggPSBuZXcgVWludDMyQXJyYXkoMjU2KTtcbiAgICB0aGlzLmludlNCb3ggPSBuZXcgVWludDMyQXJyYXkoMjU2KTtcbiAgICB0aGlzLmtleSA9IG5ldyBVaW50MzJBcnJheSgwKTtcbiAgICB0aGlzLmtzUm93cyA9IDA7XG4gICAgdGhpcy5rZXlTaXplID0gMDtcbiAgICB0aGlzLmtleVNjaGVkdWxlID0gdm9pZCAwO1xuICAgIHRoaXMuaW52S2V5U2NoZWR1bGUgPSB2b2lkIDA7XG4gICAgdGhpcy5pbml0VGFibGUoKTtcbiAgfVxuXG4gIC8vIFVzaW5nIHZpZXcuZ2V0VWludDMyKCkgYWxzbyBzd2FwcyB0aGUgYnl0ZSBvcmRlci5cbiAgdWludDhBcnJheVRvVWludDMyQXJyYXlfKGFycmF5QnVmZmVyKSB7XG4gICAgY29uc3QgdmlldyA9IG5ldyBEYXRhVmlldyhhcnJheUJ1ZmZlcik7XG4gICAgY29uc3QgbmV3QXJyYXkgPSBuZXcgVWludDMyQXJyYXkoNCk7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCA0OyBpKyspIHtcbiAgICAgIG5ld0FycmF5W2ldID0gdmlldy5nZXRVaW50MzIoaSAqIDQpO1xuICAgIH1cbiAgICByZXR1cm4gbmV3QXJyYXk7XG4gIH1cbiAgaW5pdFRhYmxlKCkge1xuICAgIGNvbnN0IHNCb3ggPSB0aGlzLnNCb3g7XG4gICAgY29uc3QgaW52U0JveCA9IHRoaXMuaW52U0JveDtcbiAgICBjb25zdCBzdWJNaXggPSB0aGlzLnN1Yk1peDtcbiAgICBjb25zdCBzdWJNaXgwID0gc3ViTWl4WzBdO1xuICAgIGNvbnN0IHN1Yk1peDEgPSBzdWJNaXhbMV07XG4gICAgY29uc3Qgc3ViTWl4MiA9IHN1Yk1peFsyXTtcbiAgICBjb25zdCBzdWJNaXgzID0gc3ViTWl4WzNdO1xuICAgIGNvbnN0IGludlN1Yk1peCA9IHRoaXMuaW52U3ViTWl4O1xuICAgIGNvbnN0IGludlN1Yk1peDAgPSBpbnZTdWJNaXhbMF07XG4gICAgY29uc3QgaW52U3ViTWl4MSA9IGludlN1Yk1peFsxXTtcbiAgICBjb25zdCBpbnZTdWJNaXgyID0gaW52U3ViTWl4WzJdO1xuICAgIGNvbnN0IGludlN1Yk1peDMgPSBpbnZTdWJNaXhbM107XG4gICAgY29uc3QgZCA9IG5ldyBVaW50MzJBcnJheSgyNTYpO1xuICAgIGxldCB4ID0gMDtcbiAgICBsZXQgeGkgPSAwO1xuICAgIGxldCBpID0gMDtcbiAgICBmb3IgKGkgPSAwOyBpIDwgMjU2OyBpKyspIHtcbiAgICAgIGlmIChpIDwgMTI4KSB7XG4gICAgICAgIGRbaV0gPSBpIDw8IDE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkW2ldID0gaSA8PCAxIF4gMHgxMWI7XG4gICAgICB9XG4gICAgfVxuICAgIGZvciAoaSA9IDA7IGkgPCAyNTY7IGkrKykge1xuICAgICAgbGV0IHN4ID0geGkgXiB4aSA8PCAxIF4geGkgPDwgMiBeIHhpIDw8IDMgXiB4aSA8PCA0O1xuICAgICAgc3ggPSBzeCA+Pj4gOCBeIHN4ICYgMHhmZiBeIDB4NjM7XG4gICAgICBzQm94W3hdID0gc3g7XG4gICAgICBpbnZTQm94W3N4XSA9IHg7XG5cbiAgICAgIC8vIENvbXB1dGUgbXVsdGlwbGljYXRpb25cbiAgICAgIGNvbnN0IHgyID0gZFt4XTtcbiAgICAgIGNvbnN0IHg0ID0gZFt4Ml07XG4gICAgICBjb25zdCB4OCA9IGRbeDRdO1xuXG4gICAgICAvLyBDb21wdXRlIHN1Yi9pbnZTdWIgYnl0ZXMsIG1peCBjb2x1bW5zIHRhYmxlc1xuICAgICAgbGV0IHQgPSBkW3N4XSAqIDB4MTAxIF4gc3ggKiAweDEwMTAxMDA7XG4gICAgICBzdWJNaXgwW3hdID0gdCA8PCAyNCB8IHQgPj4+IDg7XG4gICAgICBzdWJNaXgxW3hdID0gdCA8PCAxNiB8IHQgPj4+IDE2O1xuICAgICAgc3ViTWl4Mlt4XSA9IHQgPDwgOCB8IHQgPj4+IDI0O1xuICAgICAgc3ViTWl4M1t4XSA9IHQ7XG5cbiAgICAgIC8vIENvbXB1dGUgaW52IHN1YiBieXRlcywgaW52IG1peCBjb2x1bW5zIHRhYmxlc1xuICAgICAgdCA9IHg4ICogMHgxMDEwMTAxIF4geDQgKiAweDEwMDAxIF4geDIgKiAweDEwMSBeIHggKiAweDEwMTAxMDA7XG4gICAgICBpbnZTdWJNaXgwW3N4XSA9IHQgPDwgMjQgfCB0ID4+PiA4O1xuICAgICAgaW52U3ViTWl4MVtzeF0gPSB0IDw8IDE2IHwgdCA+Pj4gMTY7XG4gICAgICBpbnZTdWJNaXgyW3N4XSA9IHQgPDwgOCB8IHQgPj4+IDI0O1xuICAgICAgaW52U3ViTWl4M1tzeF0gPSB0O1xuXG4gICAgICAvLyBDb21wdXRlIG5leHQgY291bnRlclxuICAgICAgaWYgKCF4KSB7XG4gICAgICAgIHggPSB4aSA9IDE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB4ID0geDIgXiBkW2RbZFt4OCBeIHgyXV1dO1xuICAgICAgICB4aSBePSBkW2RbeGldXTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgZXhwYW5kS2V5KGtleUJ1ZmZlcikge1xuICAgIC8vIGNvbnZlcnQga2V5QnVmZmVyIHRvIFVpbnQzMkFycmF5XG4gICAgY29uc3Qga2V5ID0gdGhpcy51aW50OEFycmF5VG9VaW50MzJBcnJheV8oa2V5QnVmZmVyKTtcbiAgICBsZXQgc2FtZUtleSA9IHRydWU7XG4gICAgbGV0IG9mZnNldCA9IDA7XG4gICAgd2hpbGUgKG9mZnNldCA8IGtleS5sZW5ndGggJiYgc2FtZUtleSkge1xuICAgICAgc2FtZUtleSA9IGtleVtvZmZzZXRdID09PSB0aGlzLmtleVtvZmZzZXRdO1xuICAgICAgb2Zmc2V0Kys7XG4gICAgfVxuICAgIGlmIChzYW1lS2V5KSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMua2V5ID0ga2V5O1xuICAgIGNvbnN0IGtleVNpemUgPSB0aGlzLmtleVNpemUgPSBrZXkubGVuZ3RoO1xuICAgIGlmIChrZXlTaXplICE9PSA0ICYmIGtleVNpemUgIT09IDYgJiYga2V5U2l6ZSAhPT0gOCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdJbnZhbGlkIGFlcyBrZXkgc2l6ZT0nICsga2V5U2l6ZSk7XG4gICAgfVxuICAgIGNvbnN0IGtzUm93cyA9IHRoaXMua3NSb3dzID0gKGtleVNpemUgKyA2ICsgMSkgKiA0O1xuICAgIGxldCBrc1JvdztcbiAgICBsZXQgaW52S3NSb3c7XG4gICAgY29uc3Qga2V5U2NoZWR1bGUgPSB0aGlzLmtleVNjaGVkdWxlID0gbmV3IFVpbnQzMkFycmF5KGtzUm93cyk7XG4gICAgY29uc3QgaW52S2V5U2NoZWR1bGUgPSB0aGlzLmludktleVNjaGVkdWxlID0gbmV3IFVpbnQzMkFycmF5KGtzUm93cyk7XG4gICAgY29uc3Qgc2JveCA9IHRoaXMuc0JveDtcbiAgICBjb25zdCByY29uID0gdGhpcy5yY29uO1xuICAgIGNvbnN0IGludlN1Yk1peCA9IHRoaXMuaW52U3ViTWl4O1xuICAgIGNvbnN0IGludlN1Yk1peDAgPSBpbnZTdWJNaXhbMF07XG4gICAgY29uc3QgaW52U3ViTWl4MSA9IGludlN1Yk1peFsxXTtcbiAgICBjb25zdCBpbnZTdWJNaXgyID0gaW52U3ViTWl4WzJdO1xuICAgIGNvbnN0IGludlN1Yk1peDMgPSBpbnZTdWJNaXhbM107XG4gICAgbGV0IHByZXY7XG4gICAgbGV0IHQ7XG4gICAgZm9yIChrc1JvdyA9IDA7IGtzUm93IDwga3NSb3dzOyBrc1JvdysrKSB7XG4gICAgICBpZiAoa3NSb3cgPCBrZXlTaXplKSB7XG4gICAgICAgIHByZXYgPSBrZXlTY2hlZHVsZVtrc1Jvd10gPSBrZXlba3NSb3ddO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIHQgPSBwcmV2O1xuICAgICAgaWYgKGtzUm93ICUga2V5U2l6ZSA9PT0gMCkge1xuICAgICAgICAvLyBSb3Qgd29yZFxuICAgICAgICB0ID0gdCA8PCA4IHwgdCA+Pj4gMjQ7XG5cbiAgICAgICAgLy8gU3ViIHdvcmRcbiAgICAgICAgdCA9IHNib3hbdCA+Pj4gMjRdIDw8IDI0IHwgc2JveFt0ID4+PiAxNiAmIDB4ZmZdIDw8IDE2IHwgc2JveFt0ID4+PiA4ICYgMHhmZl0gPDwgOCB8IHNib3hbdCAmIDB4ZmZdO1xuXG4gICAgICAgIC8vIE1peCBSY29uXG4gICAgICAgIHQgXj0gcmNvbltrc1JvdyAvIGtleVNpemUgfCAwXSA8PCAyNDtcbiAgICAgIH0gZWxzZSBpZiAoa2V5U2l6ZSA+IDYgJiYga3NSb3cgJSBrZXlTaXplID09PSA0KSB7XG4gICAgICAgIC8vIFN1YiB3b3JkXG4gICAgICAgIHQgPSBzYm94W3QgPj4+IDI0XSA8PCAyNCB8IHNib3hbdCA+Pj4gMTYgJiAweGZmXSA8PCAxNiB8IHNib3hbdCA+Pj4gOCAmIDB4ZmZdIDw8IDggfCBzYm94W3QgJiAweGZmXTtcbiAgICAgIH1cbiAgICAgIGtleVNjaGVkdWxlW2tzUm93XSA9IHByZXYgPSAoa2V5U2NoZWR1bGVba3NSb3cgLSBrZXlTaXplXSBeIHQpID4+PiAwO1xuICAgIH1cbiAgICBmb3IgKGludktzUm93ID0gMDsgaW52S3NSb3cgPCBrc1Jvd3M7IGludktzUm93KyspIHtcbiAgICAgIGtzUm93ID0ga3NSb3dzIC0gaW52S3NSb3c7XG4gICAgICBpZiAoaW52S3NSb3cgJiAzKSB7XG4gICAgICAgIHQgPSBrZXlTY2hlZHVsZVtrc1Jvd107XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0ID0ga2V5U2NoZWR1bGVba3NSb3cgLSA0XTtcbiAgICAgIH1cbiAgICAgIGlmIChpbnZLc1JvdyA8IDQgfHwga3NSb3cgPD0gNCkge1xuICAgICAgICBpbnZLZXlTY2hlZHVsZVtpbnZLc1Jvd10gPSB0O1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaW52S2V5U2NoZWR1bGVbaW52S3NSb3ddID0gaW52U3ViTWl4MFtzYm94W3QgPj4+IDI0XV0gXiBpbnZTdWJNaXgxW3Nib3hbdCA+Pj4gMTYgJiAweGZmXV0gXiBpbnZTdWJNaXgyW3Nib3hbdCA+Pj4gOCAmIDB4ZmZdXSBeIGludlN1Yk1peDNbc2JveFt0ICYgMHhmZl1dO1xuICAgICAgfVxuICAgICAgaW52S2V5U2NoZWR1bGVbaW52S3NSb3ddID0gaW52S2V5U2NoZWR1bGVbaW52S3NSb3ddID4+PiAwO1xuICAgIH1cbiAgfVxuXG4gIC8vIEFkZGluZyB0aGlzIGFzIGEgbWV0aG9kIGdyZWF0bHkgaW1wcm92ZXMgcGVyZm9ybWFuY2UuXG4gIG5ldHdvcmtUb0hvc3RPcmRlclN3YXAod29yZCkge1xuICAgIHJldHVybiB3b3JkIDw8IDI0IHwgKHdvcmQgJiAweGZmMDApIDw8IDggfCAod29yZCAmIDB4ZmYwMDAwKSA+PiA4IHwgd29yZCA+Pj4gMjQ7XG4gIH1cbiAgZGVjcnlwdChpbnB1dEFycmF5QnVmZmVyLCBvZmZzZXQsIGFlc0lWKSB7XG4gICAgY29uc3QgblJvdW5kcyA9IHRoaXMua2V5U2l6ZSArIDY7XG4gICAgY29uc3QgaW52S2V5U2NoZWR1bGUgPSB0aGlzLmludktleVNjaGVkdWxlO1xuICAgIGNvbnN0IGludlNCT1ggPSB0aGlzLmludlNCb3g7XG4gICAgY29uc3QgaW52U3ViTWl4ID0gdGhpcy5pbnZTdWJNaXg7XG4gICAgY29uc3QgaW52U3ViTWl4MCA9IGludlN1Yk1peFswXTtcbiAgICBjb25zdCBpbnZTdWJNaXgxID0gaW52U3ViTWl4WzFdO1xuICAgIGNvbnN0IGludlN1Yk1peDIgPSBpbnZTdWJNaXhbMl07XG4gICAgY29uc3QgaW52U3ViTWl4MyA9IGludlN1Yk1peFszXTtcbiAgICBjb25zdCBpbml0VmVjdG9yID0gdGhpcy51aW50OEFycmF5VG9VaW50MzJBcnJheV8oYWVzSVYpO1xuICAgIGxldCBpbml0VmVjdG9yMCA9IGluaXRWZWN0b3JbMF07XG4gICAgbGV0IGluaXRWZWN0b3IxID0gaW5pdFZlY3RvclsxXTtcbiAgICBsZXQgaW5pdFZlY3RvcjIgPSBpbml0VmVjdG9yWzJdO1xuICAgIGxldCBpbml0VmVjdG9yMyA9IGluaXRWZWN0b3JbM107XG4gICAgY29uc3QgaW5wdXRJbnQzMiA9IG5ldyBJbnQzMkFycmF5KGlucHV0QXJyYXlCdWZmZXIpO1xuICAgIGNvbnN0IG91dHB1dEludDMyID0gbmV3IEludDMyQXJyYXkoaW5wdXRJbnQzMi5sZW5ndGgpO1xuICAgIGxldCB0MCwgdDEsIHQyLCB0MztcbiAgICBsZXQgczAsIHMxLCBzMiwgczM7XG4gICAgbGV0IGlucHV0V29yZHMwLCBpbnB1dFdvcmRzMSwgaW5wdXRXb3JkczIsIGlucHV0V29yZHMzO1xuICAgIGxldCBrc1JvdywgaTtcbiAgICBjb25zdCBzd2FwV29yZCA9IHRoaXMubmV0d29ya1RvSG9zdE9yZGVyU3dhcDtcbiAgICB3aGlsZSAob2Zmc2V0IDwgaW5wdXRJbnQzMi5sZW5ndGgpIHtcbiAgICAgIGlucHV0V29yZHMwID0gc3dhcFdvcmQoaW5wdXRJbnQzMltvZmZzZXRdKTtcbiAgICAgIGlucHV0V29yZHMxID0gc3dhcFdvcmQoaW5wdXRJbnQzMltvZmZzZXQgKyAxXSk7XG4gICAgICBpbnB1dFdvcmRzMiA9IHN3YXBXb3JkKGlucHV0SW50MzJbb2Zmc2V0ICsgMl0pO1xuICAgICAgaW5wdXRXb3JkczMgPSBzd2FwV29yZChpbnB1dEludDMyW29mZnNldCArIDNdKTtcbiAgICAgIHMwID0gaW5wdXRXb3JkczAgXiBpbnZLZXlTY2hlZHVsZVswXTtcbiAgICAgIHMxID0gaW5wdXRXb3JkczMgXiBpbnZLZXlTY2hlZHVsZVsxXTtcbiAgICAgIHMyID0gaW5wdXRXb3JkczIgXiBpbnZLZXlTY2hlZHVsZVsyXTtcbiAgICAgIHMzID0gaW5wdXRXb3JkczEgXiBpbnZLZXlTY2hlZHVsZVszXTtcbiAgICAgIGtzUm93ID0gNDtcblxuICAgICAgLy8gSXRlcmF0ZSB0aHJvdWdoIHRoZSByb3VuZHMgb2YgZGVjcnlwdGlvblxuICAgICAgZm9yIChpID0gMTsgaSA8IG5Sb3VuZHM7IGkrKykge1xuICAgICAgICB0MCA9IGludlN1Yk1peDBbczAgPj4+IDI0XSBeIGludlN1Yk1peDFbczEgPj4gMTYgJiAweGZmXSBeIGludlN1Yk1peDJbczIgPj4gOCAmIDB4ZmZdIF4gaW52U3ViTWl4M1tzMyAmIDB4ZmZdIF4gaW52S2V5U2NoZWR1bGVba3NSb3ddO1xuICAgICAgICB0MSA9IGludlN1Yk1peDBbczEgPj4+IDI0XSBeIGludlN1Yk1peDFbczIgPj4gMTYgJiAweGZmXSBeIGludlN1Yk1peDJbczMgPj4gOCAmIDB4ZmZdIF4gaW52U3ViTWl4M1tzMCAmIDB4ZmZdIF4gaW52S2V5U2NoZWR1bGVba3NSb3cgKyAxXTtcbiAgICAgICAgdDIgPSBpbnZTdWJNaXgwW3MyID4+PiAyNF0gXiBpbnZTdWJNaXgxW3MzID4+IDE2ICYgMHhmZl0gXiBpbnZTdWJNaXgyW3MwID4+IDggJiAweGZmXSBeIGludlN1Yk1peDNbczEgJiAweGZmXSBeIGludktleVNjaGVkdWxlW2tzUm93ICsgMl07XG4gICAgICAgIHQzID0gaW52U3ViTWl4MFtzMyA+Pj4gMjRdIF4gaW52U3ViTWl4MVtzMCA+PiAxNiAmIDB4ZmZdIF4gaW52U3ViTWl4MltzMSA+PiA4ICYgMHhmZl0gXiBpbnZTdWJNaXgzW3MyICYgMHhmZl0gXiBpbnZLZXlTY2hlZHVsZVtrc1JvdyArIDNdO1xuICAgICAgICAvLyBVcGRhdGUgc3RhdGVcbiAgICAgICAgczAgPSB0MDtcbiAgICAgICAgczEgPSB0MTtcbiAgICAgICAgczIgPSB0MjtcbiAgICAgICAgczMgPSB0MztcbiAgICAgICAga3NSb3cgPSBrc1JvdyArIDQ7XG4gICAgICB9XG5cbiAgICAgIC8vIFNoaWZ0IHJvd3MsIHN1YiBieXRlcywgYWRkIHJvdW5kIGtleVxuICAgICAgdDAgPSBpbnZTQk9YW3MwID4+PiAyNF0gPDwgMjQgXiBpbnZTQk9YW3MxID4+IDE2ICYgMHhmZl0gPDwgMTYgXiBpbnZTQk9YW3MyID4+IDggJiAweGZmXSA8PCA4IF4gaW52U0JPWFtzMyAmIDB4ZmZdIF4gaW52S2V5U2NoZWR1bGVba3NSb3ddO1xuICAgICAgdDEgPSBpbnZTQk9YW3MxID4+PiAyNF0gPDwgMjQgXiBpbnZTQk9YW3MyID4+IDE2ICYgMHhmZl0gPDwgMTYgXiBpbnZTQk9YW3MzID4+IDggJiAweGZmXSA8PCA4IF4gaW52U0JPWFtzMCAmIDB4ZmZdIF4gaW52S2V5U2NoZWR1bGVba3NSb3cgKyAxXTtcbiAgICAgIHQyID0gaW52U0JPWFtzMiA+Pj4gMjRdIDw8IDI0IF4gaW52U0JPWFtzMyA+PiAxNiAmIDB4ZmZdIDw8IDE2IF4gaW52U0JPWFtzMCA+PiA4ICYgMHhmZl0gPDwgOCBeIGludlNCT1hbczEgJiAweGZmXSBeIGludktleVNjaGVkdWxlW2tzUm93ICsgMl07XG4gICAgICB0MyA9IGludlNCT1hbczMgPj4+IDI0XSA8PCAyNCBeIGludlNCT1hbczAgPj4gMTYgJiAweGZmXSA8PCAxNiBeIGludlNCT1hbczEgPj4gOCAmIDB4ZmZdIDw8IDggXiBpbnZTQk9YW3MyICYgMHhmZl0gXiBpbnZLZXlTY2hlZHVsZVtrc1JvdyArIDNdO1xuXG4gICAgICAvLyBXcml0ZVxuICAgICAgb3V0cHV0SW50MzJbb2Zmc2V0XSA9IHN3YXBXb3JkKHQwIF4gaW5pdFZlY3RvcjApO1xuICAgICAgb3V0cHV0SW50MzJbb2Zmc2V0ICsgMV0gPSBzd2FwV29yZCh0MyBeIGluaXRWZWN0b3IxKTtcbiAgICAgIG91dHB1dEludDMyW29mZnNldCArIDJdID0gc3dhcFdvcmQodDIgXiBpbml0VmVjdG9yMik7XG4gICAgICBvdXRwdXRJbnQzMltvZmZzZXQgKyAzXSA9IHN3YXBXb3JkKHQxIF4gaW5pdFZlY3RvcjMpO1xuXG4gICAgICAvLyByZXNldCBpbml0VmVjdG9yIHRvIGxhc3QgNCB1bnNpZ25lZCBpbnRcbiAgICAgIGluaXRWZWN0b3IwID0gaW5wdXRXb3JkczA7XG4gICAgICBpbml0VmVjdG9yMSA9IGlucHV0V29yZHMxO1xuICAgICAgaW5pdFZlY3RvcjIgPSBpbnB1dFdvcmRzMjtcbiAgICAgIGluaXRWZWN0b3IzID0gaW5wdXRXb3JkczM7XG4gICAgICBvZmZzZXQgPSBvZmZzZXQgKyA0O1xuICAgIH1cbiAgICByZXR1cm4gb3V0cHV0SW50MzIuYnVmZmVyO1xuICB9XG59XG5cbmNvbnN0IENIVU5LX1NJWkUgPSAxNjsgLy8gMTYgYnl0ZXMsIDEyOCBiaXRzXG5cbmNsYXNzIERlY3J5cHRlciB7XG4gIGNvbnN0cnVjdG9yKGNvbmZpZywge1xuICAgIHJlbW92ZVBLQ1M3UGFkZGluZyA9IHRydWVcbiAgfSA9IHt9KSB7XG4gICAgdGhpcy5sb2dFbmFibGVkID0gdHJ1ZTtcbiAgICB0aGlzLnJlbW92ZVBLQ1M3UGFkZGluZyA9IHZvaWQgMDtcbiAgICB0aGlzLnN1YnRsZSA9IG51bGw7XG4gICAgdGhpcy5zb2Z0d2FyZURlY3J5cHRlciA9IG51bGw7XG4gICAgdGhpcy5rZXkgPSBudWxsO1xuICAgIHRoaXMuZmFzdEFlc0tleSA9IG51bGw7XG4gICAgdGhpcy5yZW1haW5kZXJEYXRhID0gbnVsbDtcbiAgICB0aGlzLmN1cnJlbnRJViA9IG51bGw7XG4gICAgdGhpcy5jdXJyZW50UmVzdWx0ID0gbnVsbDtcbiAgICB0aGlzLnVzZVNvZnR3YXJlID0gdm9pZCAwO1xuICAgIHRoaXMudXNlU29mdHdhcmUgPSBjb25maWcuZW5hYmxlU29mdHdhcmVBRVM7XG4gICAgdGhpcy5yZW1vdmVQS0NTN1BhZGRpbmcgPSByZW1vdmVQS0NTN1BhZGRpbmc7XG4gICAgLy8gYnVpbHQgaW4gZGVjcnlwdG9yIGV4cGVjdHMgUEtDUzcgcGFkZGluZ1xuICAgIGlmIChyZW1vdmVQS0NTN1BhZGRpbmcpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGJyb3dzZXJDcnlwdG8gPSBzZWxmLmNyeXB0bztcbiAgICAgICAgaWYgKGJyb3dzZXJDcnlwdG8pIHtcbiAgICAgICAgICB0aGlzLnN1YnRsZSA9IGJyb3dzZXJDcnlwdG8uc3VidGxlIHx8IGJyb3dzZXJDcnlwdG8ud2Via2l0U3VidGxlO1xuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIC8qIG5vLW9wICovXG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMudXNlU29mdHdhcmUgPSAhdGhpcy5zdWJ0bGU7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLnN1YnRsZSA9IG51bGw7XG4gICAgdGhpcy5zb2Z0d2FyZURlY3J5cHRlciA9IG51bGw7XG4gICAgdGhpcy5rZXkgPSBudWxsO1xuICAgIHRoaXMuZmFzdEFlc0tleSA9IG51bGw7XG4gICAgdGhpcy5yZW1haW5kZXJEYXRhID0gbnVsbDtcbiAgICB0aGlzLmN1cnJlbnRJViA9IG51bGw7XG4gICAgdGhpcy5jdXJyZW50UmVzdWx0ID0gbnVsbDtcbiAgfVxuICBpc1N5bmMoKSB7XG4gICAgcmV0dXJuIHRoaXMudXNlU29mdHdhcmU7XG4gIH1cbiAgZmx1c2goKSB7XG4gICAgY29uc3Qge1xuICAgICAgY3VycmVudFJlc3VsdCxcbiAgICAgIHJlbWFpbmRlckRhdGFcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIWN1cnJlbnRSZXN1bHQgfHwgcmVtYWluZGVyRGF0YSkge1xuICAgICAgdGhpcy5yZXNldCgpO1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGNvbnN0IGRhdGEgPSBuZXcgVWludDhBcnJheShjdXJyZW50UmVzdWx0KTtcbiAgICB0aGlzLnJlc2V0KCk7XG4gICAgaWYgKHRoaXMucmVtb3ZlUEtDUzdQYWRkaW5nKSB7XG4gICAgICByZXR1cm4gcmVtb3ZlUGFkZGluZyhkYXRhKTtcbiAgICB9XG4gICAgcmV0dXJuIGRhdGE7XG4gIH1cbiAgcmVzZXQoKSB7XG4gICAgdGhpcy5jdXJyZW50UmVzdWx0ID0gbnVsbDtcbiAgICB0aGlzLmN1cnJlbnRJViA9IG51bGw7XG4gICAgdGhpcy5yZW1haW5kZXJEYXRhID0gbnVsbDtcbiAgICBpZiAodGhpcy5zb2Z0d2FyZURlY3J5cHRlcikge1xuICAgICAgdGhpcy5zb2Z0d2FyZURlY3J5cHRlciA9IG51bGw7XG4gICAgfVxuICB9XG4gIGRlY3J5cHQoZGF0YSwga2V5LCBpdikge1xuICAgIGlmICh0aGlzLnVzZVNvZnR3YXJlKSB7XG4gICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICB0aGlzLnNvZnR3YXJlRGVjcnlwdChuZXcgVWludDhBcnJheShkYXRhKSwga2V5LCBpdik7XG4gICAgICAgIGNvbnN0IGRlY3J5cHRSZXN1bHQgPSB0aGlzLmZsdXNoKCk7XG4gICAgICAgIGlmIChkZWNyeXB0UmVzdWx0KSB7XG4gICAgICAgICAgcmVzb2x2ZShkZWNyeXB0UmVzdWx0LmJ1ZmZlcik7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcignW3NvZnR3YXJlRGVjcnlwdF0gRmFpbGVkIHRvIGRlY3J5cHQgZGF0YScpKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLndlYkNyeXB0b0RlY3J5cHQobmV3IFVpbnQ4QXJyYXkoZGF0YSksIGtleSwgaXYpO1xuICB9XG5cbiAgLy8gU29mdHdhcmUgZGVjcnlwdGlvbiBpcyBwcm9ncmVzc2l2ZS4gUHJvZ3Jlc3NpdmUgZGVjcnlwdGlvbiBtYXkgbm90IHJldHVybiBhIHJlc3VsdCBvbiBlYWNoIGNhbGwuIEFueSBjYWNoZWRcbiAgLy8gZGF0YSBpcyBoYW5kbGVkIGluIHRoZSBmbHVzaCgpIGNhbGxcbiAgc29mdHdhcmVEZWNyeXB0KGRhdGEsIGtleSwgaXYpIHtcbiAgICBjb25zdCB7XG4gICAgICBjdXJyZW50SVYsXG4gICAgICBjdXJyZW50UmVzdWx0LFxuICAgICAgcmVtYWluZGVyRGF0YVxuICAgIH0gPSB0aGlzO1xuICAgIHRoaXMubG9nT25jZSgnSlMgQUVTIGRlY3J5cHQnKTtcbiAgICAvLyBUaGUgb3V0cHV0IGlzIHN0YWdnZXJlZCBkdXJpbmcgcHJvZ3Jlc3NpdmUgcGFyc2luZyAtIHRoZSBjdXJyZW50IHJlc3VsdCBpcyBjYWNoZWQsIGFuZCBlbWl0dGVkIG9uIHRoZSBuZXh0IGNhbGxcbiAgICAvLyBUaGlzIGlzIGRvbmUgaW4gb3JkZXIgdG8gc3RyaXAgUEtDUzcgcGFkZGluZywgd2hpY2ggaXMgZm91bmQgYXQgdGhlIGVuZCBvZiBlYWNoIHNlZ21lbnQuIFdlIG9ubHkga25vdyB3ZSd2ZSByZWFjaGVkXG4gICAgLy8gdGhlIGVuZCBvbiBmbHVzaCgpLCBidXQgYnkgdGhhdCB0aW1lIHdlIGhhdmUgYWxyZWFkeSByZWNlaXZlZCBhbGwgYnl0ZXMgZm9yIHRoZSBzZWdtZW50LlxuICAgIC8vIFByb2dyZXNzaXZlIGRlY3J5cHRpb24gZG9lcyBub3Qgd29yayB3aXRoIFdlYkNyeXB0b1xuXG4gICAgaWYgKHJlbWFpbmRlckRhdGEpIHtcbiAgICAgIGRhdGEgPSBhcHBlbmRVaW50OEFycmF5KHJlbWFpbmRlckRhdGEsIGRhdGEpO1xuICAgICAgdGhpcy5yZW1haW5kZXJEYXRhID0gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBCeXRlIGxlbmd0aCBtdXN0IGJlIGEgbXVsdGlwbGUgb2YgMTYgKEFFUy0xMjggPSAxMjggYml0IGJsb2NrcyA9IDE2IGJ5dGVzKVxuICAgIGNvbnN0IGN1cnJlbnRDaHVuayA9IHRoaXMuZ2V0VmFsaWRDaHVuayhkYXRhKTtcbiAgICBpZiAoIWN1cnJlbnRDaHVuay5sZW5ndGgpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBpZiAoY3VycmVudElWKSB7XG4gICAgICBpdiA9IGN1cnJlbnRJVjtcbiAgICB9XG4gICAgbGV0IHNvZnR3YXJlRGVjcnlwdGVyID0gdGhpcy5zb2Z0d2FyZURlY3J5cHRlcjtcbiAgICBpZiAoIXNvZnR3YXJlRGVjcnlwdGVyKSB7XG4gICAgICBzb2Z0d2FyZURlY3J5cHRlciA9IHRoaXMuc29mdHdhcmVEZWNyeXB0ZXIgPSBuZXcgQUVTRGVjcnlwdG9yKCk7XG4gICAgfVxuICAgIHNvZnR3YXJlRGVjcnlwdGVyLmV4cGFuZEtleShrZXkpO1xuICAgIGNvbnN0IHJlc3VsdCA9IGN1cnJlbnRSZXN1bHQ7XG4gICAgdGhpcy5jdXJyZW50UmVzdWx0ID0gc29mdHdhcmVEZWNyeXB0ZXIuZGVjcnlwdChjdXJyZW50Q2h1bmsuYnVmZmVyLCAwLCBpdik7XG4gICAgdGhpcy5jdXJyZW50SVYgPSBzbGljZVVpbnQ4KGN1cnJlbnRDaHVuaywgLTE2KS5idWZmZXI7XG4gICAgaWYgKCFyZXN1bHQpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIHdlYkNyeXB0b0RlY3J5cHQoZGF0YSwga2V5LCBpdikge1xuICAgIGlmICh0aGlzLmtleSAhPT0ga2V5IHx8ICF0aGlzLmZhc3RBZXNLZXkpIHtcbiAgICAgIGlmICghdGhpcy5zdWJ0bGUpIHtcbiAgICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSh0aGlzLm9uV2ViQ3J5cHRvRXJyb3IoZGF0YSwga2V5LCBpdikpO1xuICAgICAgfVxuICAgICAgdGhpcy5rZXkgPSBrZXk7XG4gICAgICB0aGlzLmZhc3RBZXNLZXkgPSBuZXcgRmFzdEFFU0tleSh0aGlzLnN1YnRsZSwga2V5KTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuZmFzdEFlc0tleS5leHBhbmRLZXkoKS50aGVuKGFlc0tleSA9PiB7XG4gICAgICAvLyBkZWNyeXB0IHVzaW5nIHdlYiBjcnlwdG9cbiAgICAgIGlmICghdGhpcy5zdWJ0bGUpIHtcbiAgICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KG5ldyBFcnJvcignd2ViIGNyeXB0byBub3QgaW5pdGlhbGl6ZWQnKSk7XG4gICAgICB9XG4gICAgICB0aGlzLmxvZ09uY2UoJ1dlYkNyeXB0byBBRVMgZGVjcnlwdCcpO1xuICAgICAgY29uc3QgY3J5cHRvID0gbmV3IEFFU0NyeXB0byh0aGlzLnN1YnRsZSwgbmV3IFVpbnQ4QXJyYXkoaXYpKTtcbiAgICAgIHJldHVybiBjcnlwdG8uZGVjcnlwdChkYXRhLmJ1ZmZlciwgYWVzS2V5KTtcbiAgICB9KS5jYXRjaChlcnIgPT4ge1xuICAgICAgbG9nZ2VyLndhcm4oYFtkZWNyeXB0ZXJdOiBXZWJDcnlwdG8gRXJyb3IsIGRpc2FibGUgV2ViQ3J5cHRvIEFQSSwgJHtlcnIubmFtZX06ICR7ZXJyLm1lc3NhZ2V9YCk7XG4gICAgICByZXR1cm4gdGhpcy5vbldlYkNyeXB0b0Vycm9yKGRhdGEsIGtleSwgaXYpO1xuICAgIH0pO1xuICB9XG4gIG9uV2ViQ3J5cHRvRXJyb3IoZGF0YSwga2V5LCBpdikge1xuICAgIHRoaXMudXNlU29mdHdhcmUgPSB0cnVlO1xuICAgIHRoaXMubG9nRW5hYmxlZCA9IHRydWU7XG4gICAgdGhpcy5zb2Z0d2FyZURlY3J5cHQoZGF0YSwga2V5LCBpdik7XG4gICAgY29uc3QgZGVjcnlwdFJlc3VsdCA9IHRoaXMuZmx1c2goKTtcbiAgICBpZiAoZGVjcnlwdFJlc3VsdCkge1xuICAgICAgcmV0dXJuIGRlY3J5cHRSZXN1bHQuYnVmZmVyO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1dlYkNyeXB0byBhbmQgc29mdHdhcmVEZWNyeXB0OiBmYWlsZWQgdG8gZGVjcnlwdCBkYXRhJyk7XG4gIH1cbiAgZ2V0VmFsaWRDaHVuayhkYXRhKSB7XG4gICAgbGV0IGN1cnJlbnRDaHVuayA9IGRhdGE7XG4gICAgY29uc3Qgc3BsaXRQb2ludCA9IGRhdGEubGVuZ3RoIC0gZGF0YS5sZW5ndGggJSBDSFVOS19TSVpFO1xuICAgIGlmIChzcGxpdFBvaW50ICE9PSBkYXRhLmxlbmd0aCkge1xuICAgICAgY3VycmVudENodW5rID0gc2xpY2VVaW50OChkYXRhLCAwLCBzcGxpdFBvaW50KTtcbiAgICAgIHRoaXMucmVtYWluZGVyRGF0YSA9IHNsaWNlVWludDgoZGF0YSwgc3BsaXRQb2ludCk7XG4gICAgfVxuICAgIHJldHVybiBjdXJyZW50Q2h1bms7XG4gIH1cbiAgbG9nT25jZShtc2cpIHtcbiAgICBpZiAoIXRoaXMubG9nRW5hYmxlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsb2dnZXIubG9nKGBbZGVjcnlwdGVyXTogJHttc2d9YCk7XG4gICAgdGhpcy5sb2dFbmFibGVkID0gZmFsc2U7XG4gIH1cbn1cblxuLyoqXG4gKiAgVGltZVJhbmdlcyB0byBzdHJpbmcgaGVscGVyXG4gKi9cblxuY29uc3QgVGltZVJhbmdlcyA9IHtcbiAgdG9TdHJpbmc6IGZ1bmN0aW9uIChyKSB7XG4gICAgbGV0IGxvZyA9ICcnO1xuICAgIGNvbnN0IGxlbiA9IHIubGVuZ3RoO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIGxvZyArPSBgWyR7ci5zdGFydChpKS50b0ZpeGVkKDMpfS0ke3IuZW5kKGkpLnRvRml4ZWQoMyl9XWA7XG4gICAgfVxuICAgIHJldHVybiBsb2c7XG4gIH1cbn07XG5cbmNvbnN0IFN0YXRlID0ge1xuICBTVE9QUEVEOiAnU1RPUFBFRCcsXG4gIElETEU6ICdJRExFJyxcbiAgS0VZX0xPQURJTkc6ICdLRVlfTE9BRElORycsXG4gIEZSQUdfTE9BRElORzogJ0ZSQUdfTE9BRElORycsXG4gIEZSQUdfTE9BRElOR19XQUlUSU5HX1JFVFJZOiAnRlJBR19MT0FESU5HX1dBSVRJTkdfUkVUUlknLFxuICBXQUlUSU5HX1RSQUNLOiAnV0FJVElOR19UUkFDSycsXG4gIFBBUlNJTkc6ICdQQVJTSU5HJyxcbiAgUEFSU0VEOiAnUEFSU0VEJyxcbiAgRU5ERUQ6ICdFTkRFRCcsXG4gIEVSUk9SOiAnRVJST1InLFxuICBXQUlUSU5HX0lOSVRfUFRTOiAnV0FJVElOR19JTklUX1BUUycsXG4gIFdBSVRJTkdfTEVWRUw6ICdXQUlUSU5HX0xFVkVMJ1xufTtcbmNsYXNzIEJhc2VTdHJlYW1Db250cm9sbGVyIGV4dGVuZHMgVGFza0xvb3Age1xuICBjb25zdHJ1Y3RvcihobHMsIGZyYWdtZW50VHJhY2tlciwga2V5TG9hZGVyLCBsb2dQcmVmaXgsIHBsYXlsaXN0VHlwZSkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5mcmFnUHJldmlvdXMgPSBudWxsO1xuICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSBudWxsO1xuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyID0gdm9pZCAwO1xuICAgIHRoaXMudHJhbnNtdXhlciA9IG51bGw7XG4gICAgdGhpcy5fc3RhdGUgPSBTdGF0ZS5TVE9QUEVEO1xuICAgIHRoaXMucGxheWxpc3RUeXBlID0gdm9pZCAwO1xuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIHRoaXMubWVkaWFCdWZmZXIgPSBudWxsO1xuICAgIHRoaXMuY29uZmlnID0gdm9pZCAwO1xuICAgIHRoaXMuYml0cmF0ZVRlc3QgPSBmYWxzZTtcbiAgICB0aGlzLmxhc3RDdXJyZW50VGltZSA9IDA7XG4gICAgdGhpcy5uZXh0TG9hZFBvc2l0aW9uID0gMDtcbiAgICB0aGlzLnN0YXJ0UG9zaXRpb24gPSAwO1xuICAgIHRoaXMuc3RhcnRUaW1lT2Zmc2V0ID0gbnVsbDtcbiAgICB0aGlzLmxvYWRlZG1ldGFkYXRhID0gZmFsc2U7XG4gICAgdGhpcy5yZXRyeURhdGUgPSAwO1xuICAgIHRoaXMubGV2ZWxzID0gbnVsbDtcbiAgICB0aGlzLmZyYWdtZW50TG9hZGVyID0gdm9pZCAwO1xuICAgIHRoaXMua2V5TG9hZGVyID0gdm9pZCAwO1xuICAgIHRoaXMubGV2ZWxMYXN0TG9hZGVkID0gbnVsbDtcbiAgICB0aGlzLnN0YXJ0RnJhZ1JlcXVlc3RlZCA9IGZhbHNlO1xuICAgIHRoaXMuZGVjcnlwdGVyID0gdm9pZCAwO1xuICAgIHRoaXMuaW5pdFBUUyA9IFtdO1xuICAgIHRoaXMub252c2Vla2luZyA9IG51bGw7XG4gICAgdGhpcy5vbnZlbmRlZCA9IG51bGw7XG4gICAgdGhpcy5sb2dQcmVmaXggPSAnJztcbiAgICB0aGlzLmxvZyA9IHZvaWQgMDtcbiAgICB0aGlzLndhcm4gPSB2b2lkIDA7XG4gICAgdGhpcy5wbGF5bGlzdFR5cGUgPSBwbGF5bGlzdFR5cGU7XG4gICAgdGhpcy5sb2dQcmVmaXggPSBsb2dQcmVmaXg7XG4gICAgdGhpcy5sb2cgPSBsb2dnZXIubG9nLmJpbmQobG9nZ2VyLCBgJHtsb2dQcmVmaXh9OmApO1xuICAgIHRoaXMud2FybiA9IGxvZ2dlci53YXJuLmJpbmQobG9nZ2VyLCBgJHtsb2dQcmVmaXh9OmApO1xuICAgIHRoaXMuaGxzID0gaGxzO1xuICAgIHRoaXMuZnJhZ21lbnRMb2FkZXIgPSBuZXcgRnJhZ21lbnRMb2FkZXIoaGxzLmNvbmZpZyk7XG4gICAgdGhpcy5rZXlMb2FkZXIgPSBrZXlMb2FkZXI7XG4gICAgdGhpcy5mcmFnbWVudFRyYWNrZXIgPSBmcmFnbWVudFRyYWNrZXI7XG4gICAgdGhpcy5jb25maWcgPSBobHMuY29uZmlnO1xuICAgIHRoaXMuZGVjcnlwdGVyID0gbmV3IERlY3J5cHRlcihobHMuY29uZmlnKTtcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURFRCwgdGhpcy5vbk1hbmlmZXN0TG9hZGVkLCB0aGlzKTtcbiAgfVxuICBkb1RpY2soKSB7XG4gICAgdGhpcy5vblRpY2tFbmQoKTtcbiAgfVxuICBvblRpY2tFbmQoKSB7fVxuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW51c2VkLXZhcnNcbiAgc3RhcnRMb2FkKHN0YXJ0UG9zaXRpb24pIHt9XG4gIHN0b3BMb2FkKCkge1xuICAgIHRoaXMuZnJhZ21lbnRMb2FkZXIuYWJvcnQoKTtcbiAgICB0aGlzLmtleUxvYWRlci5hYm9ydCh0aGlzLnBsYXlsaXN0VHlwZSk7XG4gICAgY29uc3QgZnJhZyA9IHRoaXMuZnJhZ0N1cnJlbnQ7XG4gICAgaWYgKGZyYWcgIT0gbnVsbCAmJiBmcmFnLmxvYWRlcikge1xuICAgICAgZnJhZy5hYm9ydFJlcXVlc3RzKCk7XG4gICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5yZW1vdmVGcmFnbWVudChmcmFnKTtcbiAgICB9XG4gICAgdGhpcy5yZXNldFRyYW5zbXV4ZXIoKTtcbiAgICB0aGlzLmZyYWdDdXJyZW50ID0gbnVsbDtcbiAgICB0aGlzLmZyYWdQcmV2aW91cyA9IG51bGw7XG4gICAgdGhpcy5jbGVhckludGVydmFsKCk7XG4gICAgdGhpcy5jbGVhck5leHRUaWNrKCk7XG4gICAgdGhpcy5zdGF0ZSA9IFN0YXRlLlNUT1BQRUQ7XG4gIH1cbiAgX3N0cmVhbUVuZGVkKGJ1ZmZlckluZm8sIGxldmVsRGV0YWlscykge1xuICAgIC8vIElmIHBsYXlsaXN0IGlzIGxpdmUsIHRoZXJlIGlzIGFub3RoZXIgYnVmZmVyZWQgcmFuZ2UgYWZ0ZXIgdGhlIGN1cnJlbnQgcmFuZ2UsIG5vdGhpbmcgYnVmZmVyZWQsIG1lZGlhIGlzIGRldGFjaGVkLFxuICAgIC8vIG9mIG5vdGhpbmcgbG9hZGluZy9sb2FkZWQgcmV0dXJuIGZhbHNlXG4gICAgaWYgKGxldmVsRGV0YWlscy5saXZlIHx8IGJ1ZmZlckluZm8ubmV4dFN0YXJ0IHx8ICFidWZmZXJJbmZvLmVuZCB8fCAhdGhpcy5tZWRpYSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBjb25zdCBwYXJ0TGlzdCA9IGxldmVsRGV0YWlscy5wYXJ0TGlzdDtcbiAgICAvLyBTaW5jZSB0aGUgbGFzdCBwYXJ0IGlzbid0IGd1YXJhbnRlZWQgdG8gY29ycmVzcG9uZCB0byB0aGUgbGFzdCBwbGF5bGlzdCBzZWdtZW50IGZvciBMb3ctTGF0ZW5jeSBITFMsXG4gICAgLy8gY2hlY2sgaW5zdGVhZCBpZiB0aGUgbGFzdCBwYXJ0IGlzIGJ1ZmZlcmVkLlxuICAgIGlmIChwYXJ0TGlzdCAhPSBudWxsICYmIHBhcnRMaXN0Lmxlbmd0aCkge1xuICAgICAgY29uc3QgbGFzdFBhcnQgPSBwYXJ0TGlzdFtwYXJ0TGlzdC5sZW5ndGggLSAxXTtcblxuICAgICAgLy8gQ2hlY2tpbmcgdGhlIG1pZHBvaW50IG9mIHRoZSBwYXJ0IGZvciBwb3RlbnRpYWwgbWFyZ2luIG9mIGVycm9yIGFuZCByZWxhdGVkIGlzc3Vlcy5cbiAgICAgIC8vIE5PVEU6IFRlY2huaWNhbGx5IEkgYmVsaWV2ZSBwYXJ0cyBjb3VsZCB5aWVsZCBjb250ZW50IHRoYXQgaXMgPCB0aGUgY29tcHV0ZWQgZHVyYXRpb24gKGluY2x1ZGluZyBwb3RlbnRpYWwgYSBkdXJhdGlvbiBvZiAwKVxuICAgICAgLy8gYW5kIHN0aWxsIGJlIHNwZWMtY29tcGxpYW50LCBzbyB0aGVyZSBtYXkgc3RpbGwgYmUgZWRnZSBjYXNlcyBoZXJlLiBMaWtld2lzZSwgdGhlcmUgY291bGQgYmUgaXNzdWVzIGluIGVuZCBvZiBzdHJlYW1cbiAgICAgIC8vIHBhcnQgbWlzbWF0Y2hlcyBmb3IgaW5kZXBlbmRlbnQgYXVkaW8gYW5kIHZpZGVvIHBsYXlsaXN0cy9zZWdtZW50cy5cbiAgICAgIGNvbnN0IGxhc3RQYXJ0QnVmZmVyZWQgPSBCdWZmZXJIZWxwZXIuaXNCdWZmZXJlZCh0aGlzLm1lZGlhLCBsYXN0UGFydC5zdGFydCArIGxhc3RQYXJ0LmR1cmF0aW9uIC8gMik7XG4gICAgICByZXR1cm4gbGFzdFBhcnRCdWZmZXJlZDtcbiAgICB9XG4gICAgY29uc3QgcGxheWxpc3RUeXBlID0gbGV2ZWxEZXRhaWxzLmZyYWdtZW50c1tsZXZlbERldGFpbHMuZnJhZ21lbnRzLmxlbmd0aCAtIDFdLnR5cGU7XG4gICAgcmV0dXJuIHRoaXMuZnJhZ21lbnRUcmFja2VyLmlzRW5kTGlzdEFwcGVuZGVkKHBsYXlsaXN0VHlwZSk7XG4gIH1cbiAgZ2V0TGV2ZWxEZXRhaWxzKCkge1xuICAgIGlmICh0aGlzLmxldmVscyAmJiB0aGlzLmxldmVsTGFzdExvYWRlZCAhPT0gbnVsbCkge1xuICAgICAgdmFyIF90aGlzJGxldmVsTGFzdExvYWRlZDtcbiAgICAgIHJldHVybiAoX3RoaXMkbGV2ZWxMYXN0TG9hZGVkID0gdGhpcy5sZXZlbExhc3RMb2FkZWQpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRsZXZlbExhc3RMb2FkZWQuZGV0YWlscztcbiAgICB9XG4gIH1cbiAgb25NZWRpYUF0dGFjaGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgbWVkaWEgPSB0aGlzLm1lZGlhID0gdGhpcy5tZWRpYUJ1ZmZlciA9IGRhdGEubWVkaWE7XG4gICAgdGhpcy5vbnZzZWVraW5nID0gdGhpcy5vbk1lZGlhU2Vla2luZy5iaW5kKHRoaXMpO1xuICAgIHRoaXMub252ZW5kZWQgPSB0aGlzLm9uTWVkaWFFbmRlZC5iaW5kKHRoaXMpO1xuICAgIG1lZGlhLmFkZEV2ZW50TGlzdGVuZXIoJ3NlZWtpbmcnLCB0aGlzLm9udnNlZWtpbmcpO1xuICAgIG1lZGlhLmFkZEV2ZW50TGlzdGVuZXIoJ2VuZGVkJywgdGhpcy5vbnZlbmRlZCk7XG4gICAgY29uc3QgY29uZmlnID0gdGhpcy5jb25maWc7XG4gICAgaWYgKHRoaXMubGV2ZWxzICYmIGNvbmZpZy5hdXRvU3RhcnRMb2FkICYmIHRoaXMuc3RhdGUgPT09IFN0YXRlLlNUT1BQRUQpIHtcbiAgICAgIHRoaXMuc3RhcnRMb2FkKGNvbmZpZy5zdGFydFBvc2l0aW9uKTtcbiAgICB9XG4gIH1cbiAgb25NZWRpYURldGFjaGluZygpIHtcbiAgICBjb25zdCBtZWRpYSA9IHRoaXMubWVkaWE7XG4gICAgaWYgKG1lZGlhICE9IG51bGwgJiYgbWVkaWEuZW5kZWQpIHtcbiAgICAgIHRoaXMubG9nKCdNU0UgZGV0YWNoaW5nIGFuZCB2aWRlbyBlbmRlZCwgcmVzZXQgc3RhcnRQb3NpdGlvbicpO1xuICAgICAgdGhpcy5zdGFydFBvc2l0aW9uID0gdGhpcy5sYXN0Q3VycmVudFRpbWUgPSAwO1xuICAgIH1cblxuICAgIC8vIHJlbW92ZSB2aWRlbyBsaXN0ZW5lcnNcbiAgICBpZiAobWVkaWEgJiYgdGhpcy5vbnZzZWVraW5nICYmIHRoaXMub252ZW5kZWQpIHtcbiAgICAgIG1lZGlhLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3NlZWtpbmcnLCB0aGlzLm9udnNlZWtpbmcpO1xuICAgICAgbWVkaWEucmVtb3ZlRXZlbnRMaXN0ZW5lcignZW5kZWQnLCB0aGlzLm9udmVuZGVkKTtcbiAgICAgIHRoaXMub252c2Vla2luZyA9IHRoaXMub252ZW5kZWQgPSBudWxsO1xuICAgIH1cbiAgICBpZiAodGhpcy5rZXlMb2FkZXIpIHtcbiAgICAgIHRoaXMua2V5TG9hZGVyLmRldGFjaCgpO1xuICAgIH1cbiAgICB0aGlzLm1lZGlhID0gdGhpcy5tZWRpYUJ1ZmZlciA9IG51bGw7XG4gICAgdGhpcy5sb2FkZWRtZXRhZGF0YSA9IGZhbHNlO1xuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUFsbEZyYWdtZW50cygpO1xuICAgIHRoaXMuc3RvcExvYWQoKTtcbiAgfVxuICBvbk1lZGlhU2Vla2luZygpIHtcbiAgICBjb25zdCB7XG4gICAgICBjb25maWcsXG4gICAgICBmcmFnQ3VycmVudCxcbiAgICAgIG1lZGlhLFxuICAgICAgbWVkaWFCdWZmZXIsXG4gICAgICBzdGF0ZVxuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gbWVkaWEgPyBtZWRpYS5jdXJyZW50VGltZSA6IDA7XG4gICAgY29uc3QgYnVmZmVySW5mbyA9IEJ1ZmZlckhlbHBlci5idWZmZXJJbmZvKG1lZGlhQnVmZmVyID8gbWVkaWFCdWZmZXIgOiBtZWRpYSwgY3VycmVudFRpbWUsIGNvbmZpZy5tYXhCdWZmZXJIb2xlKTtcbiAgICB0aGlzLmxvZyhgbWVkaWEgc2Vla2luZyB0byAke2lzRmluaXRlTnVtYmVyKGN1cnJlbnRUaW1lKSA/IGN1cnJlbnRUaW1lLnRvRml4ZWQoMykgOiBjdXJyZW50VGltZX0sIHN0YXRlOiAke3N0YXRlfWApO1xuICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5FTkRFRCkge1xuICAgICAgdGhpcy5yZXNldExvYWRpbmdTdGF0ZSgpO1xuICAgIH0gZWxzZSBpZiAoZnJhZ0N1cnJlbnQpIHtcbiAgICAgIC8vIFNlZWtpbmcgd2hpbGUgZnJhZyBsb2FkIGlzIGluIHByb2dyZXNzXG4gICAgICBjb25zdCB0b2xlcmFuY2UgPSBjb25maWcubWF4RnJhZ0xvb2tVcFRvbGVyYW5jZTtcbiAgICAgIGNvbnN0IGZyYWdTdGFydE9mZnNldCA9IGZyYWdDdXJyZW50LnN0YXJ0IC0gdG9sZXJhbmNlO1xuICAgICAgY29uc3QgZnJhZ0VuZE9mZnNldCA9IGZyYWdDdXJyZW50LnN0YXJ0ICsgZnJhZ0N1cnJlbnQuZHVyYXRpb24gKyB0b2xlcmFuY2U7XG4gICAgICAvLyBpZiBzZWVraW5nIG91dCBvZiBidWZmZXJlZCByYW5nZSBvciBpbnRvIG5ldyBvbmVcbiAgICAgIGlmICghYnVmZmVySW5mby5sZW4gfHwgZnJhZ0VuZE9mZnNldCA8IGJ1ZmZlckluZm8uc3RhcnQgfHwgZnJhZ1N0YXJ0T2Zmc2V0ID4gYnVmZmVySW5mby5lbmQpIHtcbiAgICAgICAgY29uc3QgcGFzdEZyYWdtZW50ID0gY3VycmVudFRpbWUgPiBmcmFnRW5kT2Zmc2V0O1xuICAgICAgICAvLyBpZiB0aGUgc2VlayBwb3NpdGlvbiBpcyBvdXRzaWRlIHRoZSBjdXJyZW50IGZyYWdtZW50IHJhbmdlXG4gICAgICAgIGlmIChjdXJyZW50VGltZSA8IGZyYWdTdGFydE9mZnNldCB8fCBwYXN0RnJhZ21lbnQpIHtcbiAgICAgICAgICBpZiAocGFzdEZyYWdtZW50ICYmIGZyYWdDdXJyZW50LmxvYWRlcikge1xuICAgICAgICAgICAgdGhpcy5sb2coJ3NlZWtpbmcgb3V0c2lkZSBvZiBidWZmZXIgd2hpbGUgZnJhZ21lbnQgbG9hZCBpbiBwcm9ncmVzcywgY2FuY2VsIGZyYWdtZW50IGxvYWQnKTtcbiAgICAgICAgICAgIGZyYWdDdXJyZW50LmFib3J0UmVxdWVzdHMoKTtcbiAgICAgICAgICAgIHRoaXMucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5mcmFnUHJldmlvdXMgPSBudWxsO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChtZWRpYSkge1xuICAgICAgLy8gUmVtb3ZlIGdhcCBmcmFnbWVudHNcbiAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50c0luUmFuZ2UoY3VycmVudFRpbWUsIEluZmluaXR5LCB0aGlzLnBsYXlsaXN0VHlwZSwgdHJ1ZSk7XG4gICAgICB0aGlzLmxhc3RDdXJyZW50VGltZSA9IGN1cnJlbnRUaW1lO1xuICAgIH1cblxuICAgIC8vIGluIGNhc2Ugc2Vla2luZyBvY2N1cnMgYWx0aG91Z2ggbm8gbWVkaWEgYnVmZmVyZWQsIGFkanVzdCBzdGFydFBvc2l0aW9uIGFuZCBuZXh0TG9hZFBvc2l0aW9uIHRvIHNlZWsgdGFyZ2V0XG4gICAgaWYgKCF0aGlzLmxvYWRlZG1ldGFkYXRhICYmICFidWZmZXJJbmZvLmxlbikge1xuICAgICAgdGhpcy5uZXh0TG9hZFBvc2l0aW9uID0gdGhpcy5zdGFydFBvc2l0aW9uID0gY3VycmVudFRpbWU7XG4gICAgfVxuXG4gICAgLy8gQXN5bmMgdGljayB0byBzcGVlZCB1cCBwcm9jZXNzaW5nXG4gICAgdGhpcy50aWNrSW1tZWRpYXRlKCk7XG4gIH1cbiAgb25NZWRpYUVuZGVkKCkge1xuICAgIC8vIHJlc2V0IHN0YXJ0UG9zaXRpb24gYW5kIGxhc3RDdXJyZW50VGltZSB0byByZXN0YXJ0IHBsYXliYWNrIEAgc3RyZWFtIGJlZ2lubmluZ1xuICAgIHRoaXMuc3RhcnRQb3NpdGlvbiA9IHRoaXMubGFzdEN1cnJlbnRUaW1lID0gMDtcbiAgfVxuICBvbk1hbmlmZXN0TG9hZGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5zdGFydFRpbWVPZmZzZXQgPSBkYXRhLnN0YXJ0VGltZU9mZnNldDtcbiAgICB0aGlzLmluaXRQVFMgPSBbXTtcbiAgfVxuICBvbkhhbmRsZXJEZXN0cm95aW5nKCkge1xuICAgIHRoaXMuaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BREVELCB0aGlzLm9uTWFuaWZlc3RMb2FkZWQsIHRoaXMpO1xuICAgIHRoaXMuc3RvcExvYWQoKTtcbiAgICBzdXBlci5vbkhhbmRsZXJEZXN0cm95aW5nKCk7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuaGxzID0gbnVsbDtcbiAgfVxuICBvbkhhbmRsZXJEZXN0cm95ZWQoKSB7XG4gICAgdGhpcy5zdGF0ZSA9IFN0YXRlLlNUT1BQRUQ7XG4gICAgaWYgKHRoaXMuZnJhZ21lbnRMb2FkZXIpIHtcbiAgICAgIHRoaXMuZnJhZ21lbnRMb2FkZXIuZGVzdHJveSgpO1xuICAgIH1cbiAgICBpZiAodGhpcy5rZXlMb2FkZXIpIHtcbiAgICAgIHRoaXMua2V5TG9hZGVyLmRlc3Ryb3koKTtcbiAgICB9XG4gICAgaWYgKHRoaXMuZGVjcnlwdGVyKSB7XG4gICAgICB0aGlzLmRlY3J5cHRlci5kZXN0cm95KCk7XG4gICAgfVxuICAgIHRoaXMuaGxzID0gdGhpcy5sb2cgPSB0aGlzLndhcm4gPSB0aGlzLmRlY3J5cHRlciA9IHRoaXMua2V5TG9hZGVyID0gdGhpcy5mcmFnbWVudExvYWRlciA9IHRoaXMuZnJhZ21lbnRUcmFja2VyID0gbnVsbDtcbiAgICBzdXBlci5vbkhhbmRsZXJEZXN0cm95ZWQoKTtcbiAgfVxuICBsb2FkRnJhZ21lbnQoZnJhZywgbGV2ZWwsIHRhcmdldEJ1ZmZlclRpbWUpIHtcbiAgICB0aGlzLl9sb2FkRnJhZ0ZvclBsYXliYWNrKGZyYWcsIGxldmVsLCB0YXJnZXRCdWZmZXJUaW1lKTtcbiAgfVxuICBfbG9hZEZyYWdGb3JQbGF5YmFjayhmcmFnLCBsZXZlbCwgdGFyZ2V0QnVmZmVyVGltZSkge1xuICAgIGNvbnN0IHByb2dyZXNzQ2FsbGJhY2sgPSBkYXRhID0+IHtcbiAgICAgIGlmICh0aGlzLmZyYWdDb250ZXh0Q2hhbmdlZChmcmFnKSkge1xuICAgICAgICB0aGlzLndhcm4oYEZyYWdtZW50ICR7ZnJhZy5zbn0ke2RhdGEucGFydCA/ICcgcDogJyArIGRhdGEucGFydC5pbmRleCA6ICcnfSBvZiBsZXZlbCAke2ZyYWcubGV2ZWx9IHdhcyBkcm9wcGVkIGR1cmluZyBkb3dubG9hZC5gKTtcbiAgICAgICAgdGhpcy5mcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQoZnJhZyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGZyYWcuc3RhdHMuY2h1bmtDb3VudCsrO1xuICAgICAgdGhpcy5faGFuZGxlRnJhZ21lbnRMb2FkUHJvZ3Jlc3MoZGF0YSk7XG4gICAgfTtcbiAgICB0aGlzLl9kb0ZyYWdMb2FkKGZyYWcsIGxldmVsLCB0YXJnZXRCdWZmZXJUaW1lLCBwcm9ncmVzc0NhbGxiYWNrKS50aGVuKGRhdGEgPT4ge1xuICAgICAgaWYgKCFkYXRhKSB7XG4gICAgICAgIC8vIGlmIHdlJ3JlIGhlcmUgd2UgcHJvYmFibHkgbmVlZGVkIHRvIGJhY2t0cmFjayBvciBhcmUgd2FpdGluZyBmb3IgbW9yZSBwYXJ0c1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBzdGF0ZSA9IHRoaXMuc3RhdGU7XG4gICAgICBpZiAodGhpcy5mcmFnQ29udGV4dENoYW5nZWQoZnJhZykpIHtcbiAgICAgICAgaWYgKHN0YXRlID09PSBTdGF0ZS5GUkFHX0xPQURJTkcgfHwgIXRoaXMuZnJhZ0N1cnJlbnQgJiYgc3RhdGUgPT09IFN0YXRlLlBBUlNJTkcpIHtcbiAgICAgICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5yZW1vdmVGcmFnbWVudChmcmFnKTtcbiAgICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBpZiAoJ3BheWxvYWQnIGluIGRhdGEpIHtcbiAgICAgICAgdGhpcy5sb2coYExvYWRlZCBmcmFnbWVudCAke2ZyYWcuc259IG9mIGxldmVsICR7ZnJhZy5sZXZlbH1gKTtcbiAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRlJBR19MT0FERUQsIGRhdGEpO1xuICAgICAgfVxuXG4gICAgICAvLyBQYXNzIHRocm91Z2ggdGhlIHdob2xlIHBheWxvYWQ7IGNvbnRyb2xsZXJzIG5vdCBpbXBsZW1lbnRpbmcgcHJvZ3Jlc3NpdmUgbG9hZGluZyByZWNlaXZlIGRhdGEgZnJvbSB0aGlzIGNhbGxiYWNrXG4gICAgICB0aGlzLl9oYW5kbGVGcmFnbWVudExvYWRDb21wbGV0ZShkYXRhKTtcbiAgICB9KS5jYXRjaChyZWFzb24gPT4ge1xuICAgICAgaWYgKHRoaXMuc3RhdGUgPT09IFN0YXRlLlNUT1BQRUQgfHwgdGhpcy5zdGF0ZSA9PT0gU3RhdGUuRVJST1IpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy53YXJuKGBGcmFnIGVycm9yOiAkeyhyZWFzb24gPT0gbnVsbCA/IHZvaWQgMCA6IHJlYXNvbi5tZXNzYWdlKSB8fCByZWFzb259YCk7XG4gICAgICB0aGlzLnJlc2V0RnJhZ21lbnRMb2FkaW5nKGZyYWcpO1xuICAgIH0pO1xuICB9XG4gIGNsZWFyVHJhY2tlcklmTmVlZGVkKGZyYWcpIHtcbiAgICB2YXIgX3RoaXMkbWVkaWFCdWZmZXI7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZ21lbnRUcmFja2VyXG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3QgZnJhZ1N0YXRlID0gZnJhZ21lbnRUcmFja2VyLmdldFN0YXRlKGZyYWcpO1xuICAgIGlmIChmcmFnU3RhdGUgPT09IEZyYWdtZW50U3RhdGUuQVBQRU5ESU5HKSB7XG4gICAgICAvLyBMb3dlciB0aGUgbWF4IGJ1ZmZlciBsZW5ndGggYW5kIHRyeSBhZ2FpblxuICAgICAgY29uc3QgcGxheWxpc3RUeXBlID0gZnJhZy50eXBlO1xuICAgICAgY29uc3QgYnVmZmVyZWRJbmZvID0gdGhpcy5nZXRGd2RCdWZmZXJJbmZvKHRoaXMubWVkaWFCdWZmZXIsIHBsYXlsaXN0VHlwZSk7XG4gICAgICBjb25zdCBtaW5Gb3J3YXJkQnVmZmVyTGVuZ3RoID0gTWF0aC5tYXgoZnJhZy5kdXJhdGlvbiwgYnVmZmVyZWRJbmZvID8gYnVmZmVyZWRJbmZvLmxlbiA6IHRoaXMuY29uZmlnLm1heEJ1ZmZlckxlbmd0aCk7XG4gICAgICAvLyBJZiBiYWNrdHJhY2tpbmcsIGFsd2F5cyByZW1vdmUgZnJvbSB0aGUgdHJhY2tlciB3aXRob3V0IHJlZHVjaW5nIG1heCBidWZmZXIgbGVuZ3RoXG4gICAgICBjb25zdCBiYWNrdHJhY2tGcmFnbWVudCA9IHRoaXMuYmFja3RyYWNrRnJhZ21lbnQ7XG4gICAgICBjb25zdCBiYWNrdHJhY2tlZCA9IGJhY2t0cmFja0ZyYWdtZW50ID8gZnJhZy5zbiAtIGJhY2t0cmFja0ZyYWdtZW50LnNuIDogMDtcbiAgICAgIGlmIChiYWNrdHJhY2tlZCA9PT0gMSB8fCB0aGlzLnJlZHVjZU1heEJ1ZmZlckxlbmd0aChtaW5Gb3J3YXJkQnVmZmVyTGVuZ3RoLCBmcmFnLmR1cmF0aW9uKSkge1xuICAgICAgICBmcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQoZnJhZyk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICgoKF90aGlzJG1lZGlhQnVmZmVyID0gdGhpcy5tZWRpYUJ1ZmZlcikgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJG1lZGlhQnVmZmVyLmJ1ZmZlcmVkLmxlbmd0aCkgPT09IDApIHtcbiAgICAgIC8vIFN0b3AgZ2FwIGZvciBiYWQgdHJhY2tlciAvIGJ1ZmZlciBmbHVzaCBiZWhhdmlvclxuICAgICAgZnJhZ21lbnRUcmFja2VyLnJlbW92ZUFsbEZyYWdtZW50cygpO1xuICAgIH0gZWxzZSBpZiAoZnJhZ21lbnRUcmFja2VyLmhhc1BhcnRzKGZyYWcudHlwZSkpIHtcbiAgICAgIC8vIEluIGxvdyBsYXRlbmN5IG1vZGUsIHJlbW92ZSBmcmFnbWVudHMgZm9yIHdoaWNoIG9ubHkgc29tZSBwYXJ0cyB3ZXJlIGJ1ZmZlcmVkXG4gICAgICBmcmFnbWVudFRyYWNrZXIuZGV0ZWN0UGFydGlhbEZyYWdtZW50cyh7XG4gICAgICAgIGZyYWcsXG4gICAgICAgIHBhcnQ6IG51bGwsXG4gICAgICAgIHN0YXRzOiBmcmFnLnN0YXRzLFxuICAgICAgICBpZDogZnJhZy50eXBlXG4gICAgICB9KTtcbiAgICAgIGlmIChmcmFnbWVudFRyYWNrZXIuZ2V0U3RhdGUoZnJhZykgPT09IEZyYWdtZW50U3RhdGUuUEFSVElBTCkge1xuICAgICAgICBmcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQoZnJhZyk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGNoZWNrTGl2ZVVwZGF0ZShkZXRhaWxzKSB7XG4gICAgaWYgKGRldGFpbHMudXBkYXRlZCAmJiAhZGV0YWlscy5saXZlKSB7XG4gICAgICAvLyBMaXZlIHN0cmVhbSBlbmRlZCwgdXBkYXRlIGZyYWdtZW50IHRyYWNrZXJcbiAgICAgIGNvbnN0IGxhc3RGcmFnbWVudCA9IGRldGFpbHMuZnJhZ21lbnRzW2RldGFpbHMuZnJhZ21lbnRzLmxlbmd0aCAtIDFdO1xuICAgICAgdGhpcy5mcmFnbWVudFRyYWNrZXIuZGV0ZWN0UGFydGlhbEZyYWdtZW50cyh7XG4gICAgICAgIGZyYWc6IGxhc3RGcmFnbWVudCxcbiAgICAgICAgcGFydDogbnVsbCxcbiAgICAgICAgc3RhdHM6IGxhc3RGcmFnbWVudC5zdGF0cyxcbiAgICAgICAgaWQ6IGxhc3RGcmFnbWVudC50eXBlXG4gICAgICB9KTtcbiAgICB9XG4gICAgaWYgKCFkZXRhaWxzLmZyYWdtZW50c1swXSkge1xuICAgICAgZGV0YWlscy5kZWx0YVVwZGF0ZUZhaWxlZCA9IHRydWU7XG4gICAgfVxuICB9XG4gIGZsdXNoTWFpbkJ1ZmZlcihzdGFydE9mZnNldCwgZW5kT2Zmc2V0LCB0eXBlID0gbnVsbCkge1xuICAgIGlmICghKHN0YXJ0T2Zmc2V0IC0gZW5kT2Zmc2V0KSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBXaGVuIGFsdGVybmF0ZSBhdWRpbyBpcyBwbGF5aW5nLCB0aGUgYXVkaW8tc3RyZWFtLWNvbnRyb2xsZXIgaXMgcmVzcG9uc2libGUgZm9yIHRoZSBhdWRpbyBidWZmZXIuIE90aGVyd2lzZSxcbiAgICAvLyBwYXNzaW5nIGEgbnVsbCB0eXBlIGZsdXNoZXMgYm90aCBidWZmZXJzXG4gICAgY29uc3QgZmx1c2hTY29wZSA9IHtcbiAgICAgIHN0YXJ0T2Zmc2V0LFxuICAgICAgZW5kT2Zmc2V0LFxuICAgICAgdHlwZVxuICAgIH07XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuQlVGRkVSX0ZMVVNISU5HLCBmbHVzaFNjb3BlKTtcbiAgfVxuICBfbG9hZEluaXRTZWdtZW50KGZyYWcsIGxldmVsKSB7XG4gICAgdGhpcy5fZG9GcmFnTG9hZChmcmFnLCBsZXZlbCkudGhlbihkYXRhID0+IHtcbiAgICAgIGlmICghZGF0YSB8fCB0aGlzLmZyYWdDb250ZXh0Q2hhbmdlZChmcmFnKSB8fCAhdGhpcy5sZXZlbHMpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdpbml0IGxvYWQgYWJvcnRlZCcpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfSkudGhlbihkYXRhID0+IHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgaGxzXG4gICAgICB9ID0gdGhpcztcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgcGF5bG9hZFxuICAgICAgfSA9IGRhdGE7XG4gICAgICBjb25zdCBkZWNyeXB0RGF0YSA9IGZyYWcuZGVjcnlwdGRhdGE7XG5cbiAgICAgIC8vIGNoZWNrIHRvIHNlZSBpZiB0aGUgcGF5bG9hZCBuZWVkcyB0byBiZSBkZWNyeXB0ZWRcbiAgICAgIGlmIChwYXlsb2FkICYmIHBheWxvYWQuYnl0ZUxlbmd0aCA+IDAgJiYgZGVjcnlwdERhdGEgIT0gbnVsbCAmJiBkZWNyeXB0RGF0YS5rZXkgJiYgZGVjcnlwdERhdGEuaXYgJiYgZGVjcnlwdERhdGEubWV0aG9kID09PSAnQUVTLTEyOCcpIHtcbiAgICAgICAgY29uc3Qgc3RhcnRUaW1lID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgLy8gZGVjcnlwdCBpbml0IHNlZ21lbnQgZGF0YVxuICAgICAgICByZXR1cm4gdGhpcy5kZWNyeXB0ZXIuZGVjcnlwdChuZXcgVWludDhBcnJheShwYXlsb2FkKSwgZGVjcnlwdERhdGEua2V5LmJ1ZmZlciwgZGVjcnlwdERhdGEuaXYuYnVmZmVyKS5jYXRjaChlcnIgPT4ge1xuICAgICAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwge1xuICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5NRURJQV9FUlJPUixcbiAgICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX0RFQ1JZUFRfRVJST1IsXG4gICAgICAgICAgICBmYXRhbDogZmFsc2UsXG4gICAgICAgICAgICBlcnJvcjogZXJyLFxuICAgICAgICAgICAgcmVhc29uOiBlcnIubWVzc2FnZSxcbiAgICAgICAgICAgIGZyYWdcbiAgICAgICAgICB9KTtcbiAgICAgICAgICB0aHJvdyBlcnI7XG4gICAgICAgIH0pLnRoZW4oZGVjcnlwdGVkRGF0YSA9PiB7XG4gICAgICAgICAgY29uc3QgZW5kVGltZSA9IHNlbGYucGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkZSQUdfREVDUllQVEVELCB7XG4gICAgICAgICAgICBmcmFnLFxuICAgICAgICAgICAgcGF5bG9hZDogZGVjcnlwdGVkRGF0YSxcbiAgICAgICAgICAgIHN0YXRzOiB7XG4gICAgICAgICAgICAgIHRzdGFydDogc3RhcnRUaW1lLFxuICAgICAgICAgICAgICB0ZGVjcnlwdDogZW5kVGltZVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pO1xuICAgICAgICAgIGRhdGEucGF5bG9hZCA9IGRlY3J5cHRlZERhdGE7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuY29tcGxldGVJbml0U2VnbWVudExvYWQoZGF0YSk7XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRoaXMuY29tcGxldGVJbml0U2VnbWVudExvYWQoZGF0YSk7XG4gICAgfSkuY2F0Y2gocmVhc29uID0+IHtcbiAgICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5TVE9QUEVEIHx8IHRoaXMuc3RhdGUgPT09IFN0YXRlLkVSUk9SKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHRoaXMud2FybihyZWFzb24pO1xuICAgICAgdGhpcy5yZXNldEZyYWdtZW50TG9hZGluZyhmcmFnKTtcbiAgICB9KTtcbiAgfVxuICBjb21wbGV0ZUluaXRTZWdtZW50TG9hZChkYXRhKSB7XG4gICAgY29uc3Qge1xuICAgICAgbGV2ZWxzXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFsZXZlbHMpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignaW5pdCBsb2FkIGFib3J0ZWQsIG1pc3NpbmcgbGV2ZWxzJyk7XG4gICAgfVxuICAgIGNvbnN0IHN0YXRzID0gZGF0YS5mcmFnLnN0YXRzO1xuICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIGRhdGEuZnJhZy5kYXRhID0gbmV3IFVpbnQ4QXJyYXkoZGF0YS5wYXlsb2FkKTtcbiAgICBzdGF0cy5wYXJzaW5nLnN0YXJ0ID0gc3RhdHMuYnVmZmVyaW5nLnN0YXJ0ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICBzdGF0cy5wYXJzaW5nLmVuZCA9IHN0YXRzLmJ1ZmZlcmluZy5lbmQgPSBzZWxmLnBlcmZvcm1hbmNlLm5vdygpO1xuICAgIHRoaXMudGljaygpO1xuICB9XG4gIGZyYWdDb250ZXh0Q2hhbmdlZChmcmFnKSB7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZ0N1cnJlbnRcbiAgICB9ID0gdGhpcztcbiAgICByZXR1cm4gIWZyYWcgfHwgIWZyYWdDdXJyZW50IHx8IGZyYWcuc24gIT09IGZyYWdDdXJyZW50LnNuIHx8IGZyYWcubGV2ZWwgIT09IGZyYWdDdXJyZW50LmxldmVsO1xuICB9XG4gIGZyYWdCdWZmZXJlZENvbXBsZXRlKGZyYWcsIHBhcnQpIHtcbiAgICB2YXIgX2ZyYWckc3RhcnRQVFMsIF9mcmFnJGVuZFBUUywgX3RoaXMkZnJhZ0N1cnJlbnQsIF90aGlzJGZyYWdQcmV2aW91cztcbiAgICBjb25zdCBtZWRpYSA9IHRoaXMubWVkaWFCdWZmZXIgPyB0aGlzLm1lZGlhQnVmZmVyIDogdGhpcy5tZWRpYTtcbiAgICB0aGlzLmxvZyhgQnVmZmVyZWQgJHtmcmFnLnR5cGV9IHNuOiAke2ZyYWcuc259JHtwYXJ0ID8gJyBwYXJ0OiAnICsgcGFydC5pbmRleCA6ICcnfSBvZiAke3RoaXMucGxheWxpc3RUeXBlID09PSBQbGF5bGlzdExldmVsVHlwZS5NQUlOID8gJ2xldmVsJyA6ICd0cmFjayd9ICR7ZnJhZy5sZXZlbH0gKGZyYWc6WyR7KChfZnJhZyRzdGFydFBUUyA9IGZyYWcuc3RhcnRQVFMpICE9IG51bGwgPyBfZnJhZyRzdGFydFBUUyA6IE5hTikudG9GaXhlZCgzKX0tJHsoKF9mcmFnJGVuZFBUUyA9IGZyYWcuZW5kUFRTKSAhPSBudWxsID8gX2ZyYWckZW5kUFRTIDogTmFOKS50b0ZpeGVkKDMpfV0gPiBidWZmZXI6JHttZWRpYSA/IFRpbWVSYW5nZXMudG9TdHJpbmcoQnVmZmVySGVscGVyLmdldEJ1ZmZlcmVkKG1lZGlhKSkgOiAnKGRldGFjaGVkKSd9KWApO1xuICAgIGlmIChmcmFnLnNuICE9PSAnaW5pdFNlZ21lbnQnKSB7XG4gICAgICB2YXIgX3RoaXMkbGV2ZWxzO1xuICAgICAgaWYgKGZyYWcudHlwZSAhPT0gUGxheWxpc3RMZXZlbFR5cGUuU1VCVElUTEUpIHtcbiAgICAgICAgY29uc3QgZWwgPSBmcmFnLmVsZW1lbnRhcnlTdHJlYW1zO1xuICAgICAgICBpZiAoIU9iamVjdC5rZXlzKGVsKS5zb21lKHR5cGUgPT4gISFlbFt0eXBlXSkpIHtcbiAgICAgICAgICAvLyBlbXB0eSBzZWdtZW50XG4gICAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBjb25zdCBsZXZlbCA9IChfdGhpcyRsZXZlbHMgPSB0aGlzLmxldmVscykgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGxldmVsc1tmcmFnLmxldmVsXTtcbiAgICAgIGlmIChsZXZlbCAhPSBudWxsICYmIGxldmVsLmZyYWdtZW50RXJyb3IpIHtcbiAgICAgICAgdGhpcy5sb2coYFJlc2V0dGluZyBsZXZlbCBmcmFnbWVudCBlcnJvciBjb3VudCBvZiAke2xldmVsLmZyYWdtZW50RXJyb3J9IG9uIGZyYWcgYnVmZmVyZWRgKTtcbiAgICAgICAgbGV2ZWwuZnJhZ21lbnRFcnJvciA9IDA7XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIGlmICghbWVkaWEpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCF0aGlzLmxvYWRlZG1ldGFkYXRhICYmIGZyYWcudHlwZSA9PSBQbGF5bGlzdExldmVsVHlwZS5NQUlOICYmIG1lZGlhLmJ1ZmZlcmVkLmxlbmd0aCAmJiAoKF90aGlzJGZyYWdDdXJyZW50ID0gdGhpcy5mcmFnQ3VycmVudCkgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGZyYWdDdXJyZW50LnNuKSA9PT0gKChfdGhpcyRmcmFnUHJldmlvdXMgPSB0aGlzLmZyYWdQcmV2aW91cykgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGZyYWdQcmV2aW91cy5zbikpIHtcbiAgICAgIHRoaXMubG9hZGVkbWV0YWRhdGEgPSB0cnVlO1xuICAgICAgdGhpcy5zZWVrVG9TdGFydFBvcygpO1xuICAgIH1cbiAgICB0aGlzLnRpY2soKTtcbiAgfVxuICBzZWVrVG9TdGFydFBvcygpIHt9XG4gIF9oYW5kbGVGcmFnbWVudExvYWRDb21wbGV0ZShmcmFnTG9hZGVkRW5kRGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIHRyYW5zbXV4ZXJcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIXRyYW5zbXV4ZXIpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qge1xuICAgICAgZnJhZyxcbiAgICAgIHBhcnQsXG4gICAgICBwYXJ0c0xvYWRlZFxuICAgIH0gPSBmcmFnTG9hZGVkRW5kRGF0YTtcbiAgICAvLyBJZiB3ZSBkaWQgbm90IGxvYWQgcGFydHMsIG9yIGxvYWRlZCBhbGwgcGFydHMsIHdlIGhhdmUgY29tcGxldGUgKG5vdCBwYXJ0aWFsKSBmcmFnbWVudCBkYXRhXG4gICAgY29uc3QgY29tcGxldGUgPSAhcGFydHNMb2FkZWQgfHwgcGFydHNMb2FkZWQubGVuZ3RoID09PSAwIHx8IHBhcnRzTG9hZGVkLnNvbWUoZnJhZ0xvYWRlZCA9PiAhZnJhZ0xvYWRlZCk7XG4gICAgY29uc3QgY2h1bmtNZXRhID0gbmV3IENodW5rTWV0YWRhdGEoZnJhZy5sZXZlbCwgZnJhZy5zbiwgZnJhZy5zdGF0cy5jaHVua0NvdW50ICsgMSwgMCwgcGFydCA/IHBhcnQuaW5kZXggOiAtMSwgIWNvbXBsZXRlKTtcbiAgICB0cmFuc211eGVyLmZsdXNoKGNodW5rTWV0YSk7XG4gIH1cblxuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gIF9oYW5kbGVGcmFnbWVudExvYWRQcm9ncmVzcyhmcmFnKSB7fVxuICBfZG9GcmFnTG9hZChmcmFnLCBsZXZlbCwgdGFyZ2V0QnVmZmVyVGltZSA9IG51bGwsIHByb2dyZXNzQ2FsbGJhY2spIHtcbiAgICB2YXIgX2ZyYWckZGVjcnlwdGRhdGE7XG4gICAgY29uc3QgZGV0YWlscyA9IGxldmVsID09IG51bGwgPyB2b2lkIDAgOiBsZXZlbC5kZXRhaWxzO1xuICAgIGlmICghdGhpcy5sZXZlbHMgfHwgIWRldGFpbHMpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgZnJhZyBsb2FkIGFib3J0ZWQsIG1pc3NpbmcgbGV2ZWwke2RldGFpbHMgPyAnJyA6ICcgZGV0YWlsJ31zYCk7XG4gICAgfVxuICAgIGxldCBrZXlMb2FkaW5nUHJvbWlzZSA9IG51bGw7XG4gICAgaWYgKGZyYWcuZW5jcnlwdGVkICYmICEoKF9mcmFnJGRlY3J5cHRkYXRhID0gZnJhZy5kZWNyeXB0ZGF0YSkgIT0gbnVsbCAmJiBfZnJhZyRkZWNyeXB0ZGF0YS5rZXkpKSB7XG4gICAgICB0aGlzLmxvZyhgTG9hZGluZyBrZXkgZm9yICR7ZnJhZy5zbn0gb2YgWyR7ZGV0YWlscy5zdGFydFNOfS0ke2RldGFpbHMuZW5kU059XSwgJHt0aGlzLmxvZ1ByZWZpeCA9PT0gJ1tzdHJlYW0tY29udHJvbGxlcl0nID8gJ2xldmVsJyA6ICd0cmFjayd9ICR7ZnJhZy5sZXZlbH1gKTtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5LRVlfTE9BRElORztcbiAgICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSBmcmFnO1xuICAgICAga2V5TG9hZGluZ1Byb21pc2UgPSB0aGlzLmtleUxvYWRlci5sb2FkKGZyYWcpLnRoZW4oa2V5TG9hZGVkRGF0YSA9PiB7XG4gICAgICAgIGlmICghdGhpcy5mcmFnQ29udGV4dENoYW5nZWQoa2V5TG9hZGVkRGF0YS5mcmFnKSkge1xuICAgICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLktFWV9MT0FERUQsIGtleUxvYWRlZERhdGEpO1xuICAgICAgICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5LRVlfTE9BRElORykge1xuICAgICAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJldHVybiBrZXlMb2FkZWREYXRhO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLktFWV9MT0FESU5HLCB7XG4gICAgICAgIGZyYWdcbiAgICAgIH0pO1xuICAgICAgaWYgKHRoaXMuZnJhZ0N1cnJlbnQgPT09IG51bGwpIHtcbiAgICAgICAga2V5TG9hZGluZ1Byb21pc2UgPSBQcm9taXNlLnJlamVjdChuZXcgRXJyb3IoYGZyYWcgbG9hZCBhYm9ydGVkLCBjb250ZXh0IGNoYW5nZWQgaW4gS0VZX0xPQURJTkdgKSk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICghZnJhZy5lbmNyeXB0ZWQgJiYgZGV0YWlscy5lbmNyeXB0ZWRGcmFnbWVudHMubGVuZ3RoKSB7XG4gICAgICB0aGlzLmtleUxvYWRlci5sb2FkQ2xlYXIoZnJhZywgZGV0YWlscy5lbmNyeXB0ZWRGcmFnbWVudHMpO1xuICAgIH1cbiAgICB0YXJnZXRCdWZmZXJUaW1lID0gTWF0aC5tYXgoZnJhZy5zdGFydCwgdGFyZ2V0QnVmZmVyVGltZSB8fCAwKTtcbiAgICBpZiAodGhpcy5jb25maWcubG93TGF0ZW5jeU1vZGUgJiYgZnJhZy5zbiAhPT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgY29uc3QgcGFydExpc3QgPSBkZXRhaWxzLnBhcnRMaXN0O1xuICAgICAgaWYgKHBhcnRMaXN0ICYmIHByb2dyZXNzQ2FsbGJhY2spIHtcbiAgICAgICAgaWYgKHRhcmdldEJ1ZmZlclRpbWUgPiBmcmFnLmVuZCAmJiBkZXRhaWxzLmZyYWdtZW50SGludCkge1xuICAgICAgICAgIGZyYWcgPSBkZXRhaWxzLmZyYWdtZW50SGludDtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBwYXJ0SW5kZXggPSB0aGlzLmdldE5leHRQYXJ0KHBhcnRMaXN0LCBmcmFnLCB0YXJnZXRCdWZmZXJUaW1lKTtcbiAgICAgICAgaWYgKHBhcnRJbmRleCA+IC0xKSB7XG4gICAgICAgICAgY29uc3QgcGFydCA9IHBhcnRMaXN0W3BhcnRJbmRleF07XG4gICAgICAgICAgdGhpcy5sb2coYExvYWRpbmcgcGFydCBzbjogJHtmcmFnLnNufSBwOiAke3BhcnQuaW5kZXh9IGNjOiAke2ZyYWcuY2N9IG9mIHBsYXlsaXN0IFske2RldGFpbHMuc3RhcnRTTn0tJHtkZXRhaWxzLmVuZFNOfV0gcGFydHMgWzAtJHtwYXJ0SW5kZXh9LSR7cGFydExpc3QubGVuZ3RoIC0gMX1dICR7dGhpcy5sb2dQcmVmaXggPT09ICdbc3RyZWFtLWNvbnRyb2xsZXJdJyA/ICdsZXZlbCcgOiAndHJhY2snfTogJHtmcmFnLmxldmVsfSwgdGFyZ2V0OiAke3BhcnNlRmxvYXQodGFyZ2V0QnVmZmVyVGltZS50b0ZpeGVkKDMpKX1gKTtcbiAgICAgICAgICB0aGlzLm5leHRMb2FkUG9zaXRpb24gPSBwYXJ0LnN0YXJ0ICsgcGFydC5kdXJhdGlvbjtcbiAgICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuRlJBR19MT0FESU5HO1xuICAgICAgICAgIGxldCBfcmVzdWx0O1xuICAgICAgICAgIGlmIChrZXlMb2FkaW5nUHJvbWlzZSkge1xuICAgICAgICAgICAgX3Jlc3VsdCA9IGtleUxvYWRpbmdQcm9taXNlLnRoZW4oa2V5TG9hZGVkRGF0YSA9PiB7XG4gICAgICAgICAgICAgIGlmICgha2V5TG9hZGVkRGF0YSB8fCB0aGlzLmZyYWdDb250ZXh0Q2hhbmdlZChrZXlMb2FkZWREYXRhLmZyYWcpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuZG9GcmFnUGFydHNMb2FkKGZyYWcsIHBhcnQsIGxldmVsLCBwcm9ncmVzc0NhbGxiYWNrKTtcbiAgICAgICAgICAgIH0pLmNhdGNoKGVycm9yID0+IHRoaXMuaGFuZGxlRnJhZ0xvYWRFcnJvcihlcnJvcikpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBfcmVzdWx0ID0gdGhpcy5kb0ZyYWdQYXJ0c0xvYWQoZnJhZywgcGFydCwgbGV2ZWwsIHByb2dyZXNzQ2FsbGJhY2spLmNhdGNoKGVycm9yID0+IHRoaXMuaGFuZGxlRnJhZ0xvYWRFcnJvcihlcnJvcikpO1xuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5GUkFHX0xPQURJTkcsIHtcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICBwYXJ0LFxuICAgICAgICAgICAgdGFyZ2V0QnVmZmVyVGltZVxuICAgICAgICAgIH0pO1xuICAgICAgICAgIGlmICh0aGlzLmZyYWdDdXJyZW50ID09PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKGBmcmFnIGxvYWQgYWJvcnRlZCwgY29udGV4dCBjaGFuZ2VkIGluIEZSQUdfTE9BRElORyBwYXJ0c2ApKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV0dXJuIF9yZXN1bHQ7XG4gICAgICAgIH0gZWxzZSBpZiAoIWZyYWcudXJsIHx8IHRoaXMubG9hZGVkRW5kT2ZQYXJ0cyhwYXJ0TGlzdCwgdGFyZ2V0QnVmZmVyVGltZSkpIHtcbiAgICAgICAgICAvLyBGcmFnbWVudCBoaW50IGhhcyBubyBwYXJ0c1xuICAgICAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUobnVsbCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5sb2coYExvYWRpbmcgZnJhZ21lbnQgJHtmcmFnLnNufSBjYzogJHtmcmFnLmNjfSAke2RldGFpbHMgPyAnb2YgWycgKyBkZXRhaWxzLnN0YXJ0U04gKyAnLScgKyBkZXRhaWxzLmVuZFNOICsgJ10gJyA6ICcnfSR7dGhpcy5sb2dQcmVmaXggPT09ICdbc3RyZWFtLWNvbnRyb2xsZXJdJyA/ICdsZXZlbCcgOiAndHJhY2snfTogJHtmcmFnLmxldmVsfSwgdGFyZ2V0OiAke3BhcnNlRmxvYXQodGFyZ2V0QnVmZmVyVGltZS50b0ZpeGVkKDMpKX1gKTtcbiAgICAvLyBEb24ndCB1cGRhdGUgbmV4dExvYWRQb3NpdGlvbiBmb3IgZnJhZ21lbnRzIHdoaWNoIGFyZSBub3QgYnVmZmVyZWRcbiAgICBpZiAoaXNGaW5pdGVOdW1iZXIoZnJhZy5zbikgJiYgIXRoaXMuYml0cmF0ZVRlc3QpIHtcbiAgICAgIHRoaXMubmV4dExvYWRQb3NpdGlvbiA9IGZyYWcuc3RhcnQgKyBmcmFnLmR1cmF0aW9uO1xuICAgIH1cbiAgICB0aGlzLnN0YXRlID0gU3RhdGUuRlJBR19MT0FESU5HO1xuXG4gICAgLy8gTG9hZCBrZXkgYmVmb3JlIHN0cmVhbWluZyBmcmFnbWVudCBkYXRhXG4gICAgY29uc3QgZGF0YU9uUHJvZ3Jlc3MgPSB0aGlzLmNvbmZpZy5wcm9ncmVzc2l2ZTtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGlmIChkYXRhT25Qcm9ncmVzcyAmJiBrZXlMb2FkaW5nUHJvbWlzZSkge1xuICAgICAgcmVzdWx0ID0ga2V5TG9hZGluZ1Byb21pc2UudGhlbihrZXlMb2FkZWREYXRhID0+IHtcbiAgICAgICAgaWYgKCFrZXlMb2FkZWREYXRhIHx8IHRoaXMuZnJhZ0NvbnRleHRDaGFuZ2VkKGtleUxvYWRlZERhdGEgPT0gbnVsbCA/IHZvaWQgMCA6IGtleUxvYWRlZERhdGEuZnJhZykpIHtcbiAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5mcmFnbWVudExvYWRlci5sb2FkKGZyYWcsIHByb2dyZXNzQ2FsbGJhY2spO1xuICAgICAgfSkuY2F0Y2goZXJyb3IgPT4gdGhpcy5oYW5kbGVGcmFnTG9hZEVycm9yKGVycm9yKSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIGxvYWQgdW5lbmNyeXB0ZWQgZnJhZ21lbnQgZGF0YSB3aXRoIHByb2dyZXNzIGV2ZW50LFxuICAgICAgLy8gb3IgaGFuZGxlIGZyYWdtZW50IHJlc3VsdCBhZnRlciBrZXkgYW5kIGZyYWdtZW50IGFyZSBmaW5pc2hlZCBsb2FkaW5nXG4gICAgICByZXN1bHQgPSBQcm9taXNlLmFsbChbdGhpcy5mcmFnbWVudExvYWRlci5sb2FkKGZyYWcsIGRhdGFPblByb2dyZXNzID8gcHJvZ3Jlc3NDYWxsYmFjayA6IHVuZGVmaW5lZCksIGtleUxvYWRpbmdQcm9taXNlXSkudGhlbigoW2ZyYWdMb2FkZWREYXRhXSkgPT4ge1xuICAgICAgICBpZiAoIWRhdGFPblByb2dyZXNzICYmIGZyYWdMb2FkZWREYXRhICYmIHByb2dyZXNzQ2FsbGJhY2spIHtcbiAgICAgICAgICBwcm9ncmVzc0NhbGxiYWNrKGZyYWdMb2FkZWREYXRhKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZnJhZ0xvYWRlZERhdGE7XG4gICAgICB9KS5jYXRjaChlcnJvciA9PiB0aGlzLmhhbmRsZUZyYWdMb2FkRXJyb3IoZXJyb3IpKTtcbiAgICB9XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRlJBR19MT0FESU5HLCB7XG4gICAgICBmcmFnLFxuICAgICAgdGFyZ2V0QnVmZmVyVGltZVxuICAgIH0pO1xuICAgIGlmICh0aGlzLmZyYWdDdXJyZW50ID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKGBmcmFnIGxvYWQgYWJvcnRlZCwgY29udGV4dCBjaGFuZ2VkIGluIEZSQUdfTE9BRElOR2ApKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuICBkb0ZyYWdQYXJ0c0xvYWQoZnJhZywgZnJvbVBhcnQsIGxldmVsLCBwcm9ncmVzc0NhbGxiYWNrKSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIHZhciBfbGV2ZWwkZGV0YWlscztcbiAgICAgIGNvbnN0IHBhcnRzTG9hZGVkID0gW107XG4gICAgICBjb25zdCBpbml0aWFsUGFydExpc3QgPSAoX2xldmVsJGRldGFpbHMgPSBsZXZlbC5kZXRhaWxzKSA9PSBudWxsID8gdm9pZCAwIDogX2xldmVsJGRldGFpbHMucGFydExpc3Q7XG4gICAgICBjb25zdCBsb2FkUGFydCA9IHBhcnQgPT4ge1xuICAgICAgICB0aGlzLmZyYWdtZW50TG9hZGVyLmxvYWRQYXJ0KGZyYWcsIHBhcnQsIHByb2dyZXNzQ2FsbGJhY2spLnRoZW4ocGFydExvYWRlZERhdGEgPT4ge1xuICAgICAgICAgIHBhcnRzTG9hZGVkW3BhcnQuaW5kZXhdID0gcGFydExvYWRlZERhdGE7XG4gICAgICAgICAgY29uc3QgbG9hZGVkUGFydCA9IHBhcnRMb2FkZWREYXRhLnBhcnQ7XG4gICAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRlJBR19MT0FERUQsIHBhcnRMb2FkZWREYXRhKTtcbiAgICAgICAgICBjb25zdCBuZXh0UGFydCA9IGdldFBhcnRXaXRoKGxldmVsLCBmcmFnLnNuLCBwYXJ0LmluZGV4ICsgMSkgfHwgZmluZFBhcnQoaW5pdGlhbFBhcnRMaXN0LCBmcmFnLnNuLCBwYXJ0LmluZGV4ICsgMSk7XG4gICAgICAgICAgaWYgKG5leHRQYXJ0KSB7XG4gICAgICAgICAgICBsb2FkUGFydChuZXh0UGFydCk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiByZXNvbHZlKHtcbiAgICAgICAgICAgICAgZnJhZyxcbiAgICAgICAgICAgICAgcGFydDogbG9hZGVkUGFydCxcbiAgICAgICAgICAgICAgcGFydHNMb2FkZWRcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSkuY2F0Y2gocmVqZWN0KTtcbiAgICAgIH07XG4gICAgICBsb2FkUGFydChmcm9tUGFydCk7XG4gICAgfSk7XG4gIH1cbiAgaGFuZGxlRnJhZ0xvYWRFcnJvcihlcnJvcikge1xuICAgIGlmICgnZGF0YScgaW4gZXJyb3IpIHtcbiAgICAgIGNvbnN0IGRhdGEgPSBlcnJvci5kYXRhO1xuICAgICAgaWYgKGVycm9yLmRhdGEgJiYgZGF0YS5kZXRhaWxzID09PSBFcnJvckRldGFpbHMuSU5URVJOQUxfQUJPUlRFRCkge1xuICAgICAgICB0aGlzLmhhbmRsZUZyYWdMb2FkQWJvcnRlZChkYXRhLmZyYWcsIGRhdGEucGFydCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwgZGF0YSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCB7XG4gICAgICAgIHR5cGU6IEVycm9yVHlwZXMuT1RIRVJfRVJST1IsXG4gICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5JTlRFUk5BTF9FWENFUFRJT04sXG4gICAgICAgIGVycjogZXJyb3IsXG4gICAgICAgIGVycm9yLFxuICAgICAgICBmYXRhbDogdHJ1ZVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIF9oYW5kbGVUcmFuc211eGVyRmx1c2goY2h1bmtNZXRhKSB7XG4gICAgY29uc3QgY29udGV4dCA9IHRoaXMuZ2V0Q3VycmVudENvbnRleHQoY2h1bmtNZXRhKTtcbiAgICBpZiAoIWNvbnRleHQgfHwgdGhpcy5zdGF0ZSAhPT0gU3RhdGUuUEFSU0lORykge1xuICAgICAgaWYgKCF0aGlzLmZyYWdDdXJyZW50ICYmIHRoaXMuc3RhdGUgIT09IFN0YXRlLlNUT1BQRUQgJiYgdGhpcy5zdGF0ZSAhPT0gU3RhdGUuRVJST1IpIHtcbiAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICB9XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBwYXJ0LFxuICAgICAgbGV2ZWxcbiAgICB9ID0gY29udGV4dDtcbiAgICBjb25zdCBub3cgPSBzZWxmLnBlcmZvcm1hbmNlLm5vdygpO1xuICAgIGZyYWcuc3RhdHMucGFyc2luZy5lbmQgPSBub3c7XG4gICAgaWYgKHBhcnQpIHtcbiAgICAgIHBhcnQuc3RhdHMucGFyc2luZy5lbmQgPSBub3c7XG4gICAgfVxuICAgIHRoaXMudXBkYXRlTGV2ZWxUaW1pbmcoZnJhZywgcGFydCwgbGV2ZWwsIGNodW5rTWV0YS5wYXJ0aWFsKTtcbiAgfVxuICBnZXRDdXJyZW50Q29udGV4dChjaHVua01ldGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbHMsXG4gICAgICBmcmFnQ3VycmVudFxuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIGxldmVsOiBsZXZlbEluZGV4LFxuICAgICAgc24sXG4gICAgICBwYXJ0OiBwYXJ0SW5kZXhcbiAgICB9ID0gY2h1bmtNZXRhO1xuICAgIGlmICghKGxldmVscyAhPSBudWxsICYmIGxldmVsc1tsZXZlbEluZGV4XSkpIHtcbiAgICAgIHRoaXMud2FybihgTGV2ZWxzIG9iamVjdCB3YXMgdW5zZXQgd2hpbGUgYnVmZmVyaW5nIGZyYWdtZW50ICR7c259IG9mIGxldmVsICR7bGV2ZWxJbmRleH0uIFRoZSBjdXJyZW50IGNodW5rIHdpbGwgbm90IGJlIGJ1ZmZlcmVkLmApO1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGNvbnN0IGxldmVsID0gbGV2ZWxzW2xldmVsSW5kZXhdO1xuICAgIGNvbnN0IHBhcnQgPSBwYXJ0SW5kZXggPiAtMSA/IGdldFBhcnRXaXRoKGxldmVsLCBzbiwgcGFydEluZGV4KSA6IG51bGw7XG4gICAgY29uc3QgZnJhZyA9IHBhcnQgPyBwYXJ0LmZyYWdtZW50IDogZ2V0RnJhZ21lbnRXaXRoU04obGV2ZWwsIHNuLCBmcmFnQ3VycmVudCk7XG4gICAgaWYgKCFmcmFnKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgaWYgKGZyYWdDdXJyZW50ICYmIGZyYWdDdXJyZW50ICE9PSBmcmFnKSB7XG4gICAgICBmcmFnLnN0YXRzID0gZnJhZ0N1cnJlbnQuc3RhdHM7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydCxcbiAgICAgIGxldmVsXG4gICAgfTtcbiAgfVxuICBidWZmZXJGcmFnbWVudERhdGEoZGF0YSwgZnJhZywgcGFydCwgY2h1bmtNZXRhLCBub0JhY2t0cmFja2luZykge1xuICAgIHZhciBfYnVmZmVyO1xuICAgIGlmICghZGF0YSB8fCB0aGlzLnN0YXRlICE9PSBTdGF0ZS5QQVJTSU5HKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGRhdGExLFxuICAgICAgZGF0YTJcbiAgICB9ID0gZGF0YTtcbiAgICBsZXQgYnVmZmVyID0gZGF0YTE7XG4gICAgaWYgKGRhdGExICYmIGRhdGEyKSB7XG4gICAgICAvLyBDb21iaW5lIHRoZSBtb29mICsgbWRhdCBzbyB0aGF0IHdlIGJ1ZmZlciB3aXRoIGEgc2luZ2xlIGFwcGVuZFxuICAgICAgYnVmZmVyID0gYXBwZW5kVWludDhBcnJheShkYXRhMSwgZGF0YTIpO1xuICAgIH1cbiAgICBpZiAoISgoX2J1ZmZlciA9IGJ1ZmZlcikgIT0gbnVsbCAmJiBfYnVmZmVyLmxlbmd0aCkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qgc2VnbWVudCA9IHtcbiAgICAgIHR5cGU6IGRhdGEudHlwZSxcbiAgICAgIGZyYWcsXG4gICAgICBwYXJ0LFxuICAgICAgY2h1bmtNZXRhLFxuICAgICAgcGFyZW50OiBmcmFnLnR5cGUsXG4gICAgICBkYXRhOiBidWZmZXJcbiAgICB9O1xuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkJVRkZFUl9BUFBFTkRJTkcsIHNlZ21lbnQpO1xuICAgIGlmIChkYXRhLmRyb3BwZWQgJiYgZGF0YS5pbmRlcGVuZGVudCAmJiAhcGFydCkge1xuICAgICAgaWYgKG5vQmFja3RyYWNraW5nKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIC8vIENsZWFyIGJ1ZmZlciBzbyB0aGF0IHdlIHJlbG9hZCBwcmV2aW91cyBzZWdtZW50cyBzZXF1ZW50aWFsbHkgaWYgcmVxdWlyZWRcbiAgICAgIHRoaXMuZmx1c2hCdWZmZXJHYXAoZnJhZyk7XG4gICAgfVxuICB9XG4gIGZsdXNoQnVmZmVyR2FwKGZyYWcpIHtcbiAgICBjb25zdCBtZWRpYSA9IHRoaXMubWVkaWE7XG4gICAgaWYgKCFtZWRpYSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBJZiBjdXJyZW50VGltZSBpcyBub3QgYnVmZmVyZWQsIGNsZWFyIHRoZSBiYWNrIGJ1ZmZlciBzbyB0aGF0IHdlIGNhbiBiYWNrdHJhY2sgYXMgbXVjaCBhcyBuZWVkZWRcbiAgICBpZiAoIUJ1ZmZlckhlbHBlci5pc0J1ZmZlcmVkKG1lZGlhLCBtZWRpYS5jdXJyZW50VGltZSkpIHtcbiAgICAgIHRoaXMuZmx1c2hNYWluQnVmZmVyKDAsIGZyYWcuc3RhcnQpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBSZW1vdmUgYmFjay1idWZmZXIgd2l0aG91dCBpbnRlcnJ1cHRpbmcgcGxheWJhY2sgdG8gYWxsb3cgYmFjayB0cmFja2luZ1xuICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gbWVkaWEuY3VycmVudFRpbWU7XG4gICAgY29uc3QgYnVmZmVySW5mbyA9IEJ1ZmZlckhlbHBlci5idWZmZXJJbmZvKG1lZGlhLCBjdXJyZW50VGltZSwgMCk7XG4gICAgY29uc3QgZnJhZ0R1cmF0aW9uID0gZnJhZy5kdXJhdGlvbjtcbiAgICBjb25zdCBzZWdtZW50RnJhY3Rpb24gPSBNYXRoLm1pbih0aGlzLmNvbmZpZy5tYXhGcmFnTG9va1VwVG9sZXJhbmNlICogMiwgZnJhZ0R1cmF0aW9uICogMC4yNSk7XG4gICAgY29uc3Qgc3RhcnQgPSBNYXRoLm1heChNYXRoLm1pbihmcmFnLnN0YXJ0IC0gc2VnbWVudEZyYWN0aW9uLCBidWZmZXJJbmZvLmVuZCAtIHNlZ21lbnRGcmFjdGlvbiksIGN1cnJlbnRUaW1lICsgc2VnbWVudEZyYWN0aW9uKTtcbiAgICBpZiAoZnJhZy5zdGFydCAtIHN0YXJ0ID4gc2VnbWVudEZyYWN0aW9uKSB7XG4gICAgICB0aGlzLmZsdXNoTWFpbkJ1ZmZlcihzdGFydCwgZnJhZy5zdGFydCk7XG4gICAgfVxuICB9XG4gIGdldEZ3ZEJ1ZmZlckluZm8oYnVmZmVyYWJsZSwgdHlwZSkge1xuICAgIGNvbnN0IHBvcyA9IHRoaXMuZ2V0TG9hZFBvc2l0aW9uKCk7XG4gICAgaWYgKCFpc0Zpbml0ZU51bWJlcihwb3MpKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuZ2V0RndkQnVmZmVySW5mb0F0UG9zKGJ1ZmZlcmFibGUsIHBvcywgdHlwZSk7XG4gIH1cbiAgZ2V0RndkQnVmZmVySW5mb0F0UG9zKGJ1ZmZlcmFibGUsIHBvcywgdHlwZSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZzoge1xuICAgICAgICBtYXhCdWZmZXJIb2xlXG4gICAgICB9XG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3QgYnVmZmVySW5mbyA9IEJ1ZmZlckhlbHBlci5idWZmZXJJbmZvKGJ1ZmZlcmFibGUsIHBvcywgbWF4QnVmZmVySG9sZSk7XG4gICAgLy8gV29ya2Fyb3VuZCBmbGF3IGluIGdldHRpbmcgZm9yd2FyZCBidWZmZXIgd2hlbiBtYXhCdWZmZXJIb2xlIGlzIHNtYWxsZXIgdGhhbiBnYXAgYXQgY3VycmVudCBwb3NcbiAgICBpZiAoYnVmZmVySW5mby5sZW4gPT09IDAgJiYgYnVmZmVySW5mby5uZXh0U3RhcnQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgY29uc3QgYnVmZmVyZWRGcmFnQXRQb3MgPSB0aGlzLmZyYWdtZW50VHJhY2tlci5nZXRCdWZmZXJlZEZyYWcocG9zLCB0eXBlKTtcbiAgICAgIGlmIChidWZmZXJlZEZyYWdBdFBvcyAmJiBidWZmZXJJbmZvLm5leHRTdGFydCA8IGJ1ZmZlcmVkRnJhZ0F0UG9zLmVuZCkge1xuICAgICAgICByZXR1cm4gQnVmZmVySGVscGVyLmJ1ZmZlckluZm8oYnVmZmVyYWJsZSwgcG9zLCBNYXRoLm1heChidWZmZXJJbmZvLm5leHRTdGFydCwgbWF4QnVmZmVySG9sZSkpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gYnVmZmVySW5mbztcbiAgfVxuICBnZXRNYXhCdWZmZXJMZW5ndGgobGV2ZWxCaXRyYXRlKSB7XG4gICAgY29uc3Qge1xuICAgICAgY29uZmlnXG4gICAgfSA9IHRoaXM7XG4gICAgbGV0IG1heEJ1ZkxlbjtcbiAgICBpZiAobGV2ZWxCaXRyYXRlKSB7XG4gICAgICBtYXhCdWZMZW4gPSBNYXRoLm1heCg4ICogY29uZmlnLm1heEJ1ZmZlclNpemUgLyBsZXZlbEJpdHJhdGUsIGNvbmZpZy5tYXhCdWZmZXJMZW5ndGgpO1xuICAgIH0gZWxzZSB7XG4gICAgICBtYXhCdWZMZW4gPSBjb25maWcubWF4QnVmZmVyTGVuZ3RoO1xuICAgIH1cbiAgICByZXR1cm4gTWF0aC5taW4obWF4QnVmTGVuLCBjb25maWcubWF4TWF4QnVmZmVyTGVuZ3RoKTtcbiAgfVxuICByZWR1Y2VNYXhCdWZmZXJMZW5ndGgodGhyZXNob2xkLCBmcmFnRHVyYXRpb24pIHtcbiAgICBjb25zdCBjb25maWcgPSB0aGlzLmNvbmZpZztcbiAgICBjb25zdCBtaW5MZW5ndGggPSBNYXRoLm1heChNYXRoLm1pbih0aHJlc2hvbGQgLSBmcmFnRHVyYXRpb24sIGNvbmZpZy5tYXhCdWZmZXJMZW5ndGgpLCBmcmFnRHVyYXRpb24pO1xuICAgIGNvbnN0IHJlZHVjZWRMZW5ndGggPSBNYXRoLm1heCh0aHJlc2hvbGQgLSBmcmFnRHVyYXRpb24gKiAzLCBjb25maWcubWF4TWF4QnVmZmVyTGVuZ3RoIC8gMiwgbWluTGVuZ3RoKTtcbiAgICBpZiAocmVkdWNlZExlbmd0aCA+PSBtaW5MZW5ndGgpIHtcbiAgICAgIC8vIHJlZHVjZSBtYXggYnVmZmVyIGxlbmd0aCBhcyBpdCBtaWdodCBiZSB0b28gaGlnaC4gd2UgZG8gdGhpcyB0byBhdm9pZCBsb29wIGZsdXNoaW5nIC4uLlxuICAgICAgY29uZmlnLm1heE1heEJ1ZmZlckxlbmd0aCA9IHJlZHVjZWRMZW5ndGg7XG4gICAgICB0aGlzLndhcm4oYFJlZHVjZSBtYXggYnVmZmVyIGxlbmd0aCB0byAke3JlZHVjZWRMZW5ndGh9c2ApO1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBnZXRBcHBlbmRlZEZyYWcocG9zaXRpb24sIHBsYXlsaXN0VHlwZSA9IFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4pIHtcbiAgICBjb25zdCBmcmFnT3JQYXJ0ID0gdGhpcy5mcmFnbWVudFRyYWNrZXIuZ2V0QXBwZW5kZWRGcmFnKHBvc2l0aW9uLCBQbGF5bGlzdExldmVsVHlwZS5NQUlOKTtcbiAgICBpZiAoZnJhZ09yUGFydCAmJiAnZnJhZ21lbnQnIGluIGZyYWdPclBhcnQpIHtcbiAgICAgIHJldHVybiBmcmFnT3JQYXJ0LmZyYWdtZW50O1xuICAgIH1cbiAgICByZXR1cm4gZnJhZ09yUGFydDtcbiAgfVxuICBnZXROZXh0RnJhZ21lbnQocG9zLCBsZXZlbERldGFpbHMpIHtcbiAgICBjb25zdCBmcmFnbWVudHMgPSBsZXZlbERldGFpbHMuZnJhZ21lbnRzO1xuICAgIGNvbnN0IGZyYWdMZW4gPSBmcmFnbWVudHMubGVuZ3RoO1xuICAgIGlmICghZnJhZ0xlbikge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuXG4gICAgLy8gZmluZCBmcmFnbWVudCBpbmRleCwgY29udGlndW91cyB3aXRoIGVuZCBvZiBidWZmZXIgcG9zaXRpb25cbiAgICBjb25zdCB7XG4gICAgICBjb25maWdcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCBzdGFydCA9IGZyYWdtZW50c1swXS5zdGFydDtcbiAgICBsZXQgZnJhZztcbiAgICBpZiAobGV2ZWxEZXRhaWxzLmxpdmUpIHtcbiAgICAgIGNvbnN0IGluaXRpYWxMaXZlTWFuaWZlc3RTaXplID0gY29uZmlnLmluaXRpYWxMaXZlTWFuaWZlc3RTaXplO1xuICAgICAgaWYgKGZyYWdMZW4gPCBpbml0aWFsTGl2ZU1hbmlmZXN0U2l6ZSkge1xuICAgICAgICB0aGlzLndhcm4oYE5vdCBlbm91Z2ggZnJhZ21lbnRzIHRvIHN0YXJ0IHBsYXliYWNrIChoYXZlOiAke2ZyYWdMZW59LCBuZWVkOiAke2luaXRpYWxMaXZlTWFuaWZlc3RTaXplfSlgKTtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICAvLyBUaGUgcmVhbCBmcmFnbWVudCBzdGFydCB0aW1lcyBmb3IgYSBsaXZlIHN0cmVhbSBhcmUgb25seSBrbm93biBhZnRlciB0aGUgUFRTIHJhbmdlIGZvciB0aGF0IGxldmVsIGlzIGtub3duLlxuICAgICAgLy8gSW4gb3JkZXIgdG8gZGlzY292ZXIgdGhlIHJhbmdlLCB3ZSBsb2FkIHRoZSBiZXN0IG1hdGNoaW5nIGZyYWdtZW50IGZvciB0aGF0IGxldmVsIGFuZCBkZW11eCBpdC5cbiAgICAgIC8vIERvIG5vdCBsb2FkIHVzaW5nIGxpdmUgbG9naWMgaWYgdGhlIHN0YXJ0aW5nIGZyYWcgaXMgcmVxdWVzdGVkIC0gd2Ugd2FudCB0byB1c2UgZ2V0RnJhZ21lbnRBdFBvc2l0aW9uKCkgc28gdGhhdFxuICAgICAgLy8gd2UgZ2V0IHRoZSBmcmFnbWVudCBtYXRjaGluZyB0aGF0IHN0YXJ0IHRpbWVcbiAgICAgIGlmICghbGV2ZWxEZXRhaWxzLlBUU0tub3duICYmICF0aGlzLnN0YXJ0RnJhZ1JlcXVlc3RlZCAmJiB0aGlzLnN0YXJ0UG9zaXRpb24gPT09IC0xIHx8IHBvcyA8IHN0YXJ0KSB7XG4gICAgICAgIGZyYWcgPSB0aGlzLmdldEluaXRpYWxMaXZlRnJhZ21lbnQobGV2ZWxEZXRhaWxzLCBmcmFnbWVudHMpO1xuICAgICAgICB0aGlzLnN0YXJ0UG9zaXRpb24gPSB0aGlzLm5leHRMb2FkUG9zaXRpb24gPSBmcmFnID8gdGhpcy5obHMubGl2ZVN5bmNQb3NpdGlvbiB8fCBmcmFnLnN0YXJ0IDogcG9zO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAocG9zIDw9IHN0YXJ0KSB7XG4gICAgICAvLyBWb0QgcGxheWxpc3Q6IGlmIGxvYWRQb3NpdGlvbiBiZWZvcmUgc3RhcnQgb2YgcGxheWxpc3QsIGxvYWQgZmlyc3QgZnJhZ21lbnRcbiAgICAgIGZyYWcgPSBmcmFnbWVudHNbMF07XG4gICAgfVxuXG4gICAgLy8gSWYgd2UgaGF2ZW4ndCBydW4gaW50byBhbnkgc3BlY2lhbCBjYXNlcyBhbHJlYWR5LCBqdXN0IGxvYWQgdGhlIGZyYWdtZW50IG1vc3QgY2xvc2VseSBtYXRjaGluZyB0aGUgcmVxdWVzdGVkIHBvc2l0aW9uXG4gICAgaWYgKCFmcmFnKSB7XG4gICAgICBjb25zdCBlbmQgPSBjb25maWcubG93TGF0ZW5jeU1vZGUgPyBsZXZlbERldGFpbHMucGFydEVuZCA6IGxldmVsRGV0YWlscy5mcmFnbWVudEVuZDtcbiAgICAgIGZyYWcgPSB0aGlzLmdldEZyYWdtZW50QXRQb3NpdGlvbihwb3MsIGVuZCwgbGV2ZWxEZXRhaWxzKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMubWFwVG9Jbml0RnJhZ1doZW5SZXF1aXJlZChmcmFnKTtcbiAgfVxuICBpc0xvb3BMb2FkaW5nKGZyYWcsIHRhcmdldEJ1ZmZlclRpbWUpIHtcbiAgICBjb25zdCB0cmFja2VyU3RhdGUgPSB0aGlzLmZyYWdtZW50VHJhY2tlci5nZXRTdGF0ZShmcmFnKTtcbiAgICByZXR1cm4gKHRyYWNrZXJTdGF0ZSA9PT0gRnJhZ21lbnRTdGF0ZS5PSyB8fCB0cmFja2VyU3RhdGUgPT09IEZyYWdtZW50U3RhdGUuUEFSVElBTCAmJiAhIWZyYWcuZ2FwKSAmJiB0aGlzLm5leHRMb2FkUG9zaXRpb24gPiB0YXJnZXRCdWZmZXJUaW1lO1xuICB9XG4gIGdldE5leHRGcmFnbWVudExvb3BMb2FkaW5nKGZyYWcsIGxldmVsRGV0YWlscywgYnVmZmVySW5mbywgcGxheWxpc3RUeXBlLCBtYXhCdWZMZW4pIHtcbiAgICBjb25zdCBnYXBTdGFydCA9IGZyYWcuZ2FwO1xuICAgIGNvbnN0IG5leHRGcmFnbWVudCA9IHRoaXMuZ2V0TmV4dEZyYWdtZW50KHRoaXMubmV4dExvYWRQb3NpdGlvbiwgbGV2ZWxEZXRhaWxzKTtcbiAgICBpZiAobmV4dEZyYWdtZW50ID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gbmV4dEZyYWdtZW50O1xuICAgIH1cbiAgICBmcmFnID0gbmV4dEZyYWdtZW50O1xuICAgIGlmIChnYXBTdGFydCAmJiBmcmFnICYmICFmcmFnLmdhcCAmJiBidWZmZXJJbmZvLm5leHRTdGFydCkge1xuICAgICAgLy8gTWVkaWEgYnVmZmVyZWQgYWZ0ZXIgR0FQIHRhZ3Mgc2hvdWxkIG5vdCBtYWtlIHRoZSBuZXh0IGJ1ZmZlciB0aW1lcmFuZ2UgZXhjZWVkIGZvcndhcmQgYnVmZmVyIGxlbmd0aFxuICAgICAgY29uc3QgbmV4dGJ1ZmZlckluZm8gPSB0aGlzLmdldEZ3ZEJ1ZmZlckluZm9BdFBvcyh0aGlzLm1lZGlhQnVmZmVyID8gdGhpcy5tZWRpYUJ1ZmZlciA6IHRoaXMubWVkaWEsIGJ1ZmZlckluZm8ubmV4dFN0YXJ0LCBwbGF5bGlzdFR5cGUpO1xuICAgICAgaWYgKG5leHRidWZmZXJJbmZvICE9PSBudWxsICYmIGJ1ZmZlckluZm8ubGVuICsgbmV4dGJ1ZmZlckluZm8ubGVuID49IG1heEJ1Zkxlbikge1xuICAgICAgICAvLyBSZXR1cm5pbmcgaGVyZSBtaWdodCByZXN1bHQgaW4gbm90IGZpbmRpbmcgYW4gYXVkaW8gYW5kIHZpZGVvIGNhbmRpYXRlIHRvIHNraXAgdG9cbiAgICAgICAgdGhpcy5sb2coYGJ1ZmZlciBmdWxsIGFmdGVyIGdhcHMgaW4gXCIke3BsYXlsaXN0VHlwZX1cIiBwbGF5bGlzdCBzdGFydGluZyBhdCBzbjogJHtmcmFnLnNufWApO1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGZyYWc7XG4gIH1cbiAgbWFwVG9Jbml0RnJhZ1doZW5SZXF1aXJlZChmcmFnKSB7XG4gICAgLy8gSWYgYW4gaW5pdFNlZ21lbnQgaXMgcHJlc2VudCwgaXQgbXVzdCBiZSBidWZmZXJlZCBmaXJzdFxuICAgIGlmIChmcmFnICE9IG51bGwgJiYgZnJhZy5pbml0U2VnbWVudCAmJiAhKGZyYWcgIT0gbnVsbCAmJiBmcmFnLmluaXRTZWdtZW50LmRhdGEpICYmICF0aGlzLmJpdHJhdGVUZXN0KSB7XG4gICAgICByZXR1cm4gZnJhZy5pbml0U2VnbWVudDtcbiAgICB9XG4gICAgcmV0dXJuIGZyYWc7XG4gIH1cbiAgZ2V0TmV4dFBhcnQocGFydExpc3QsIGZyYWcsIHRhcmdldEJ1ZmZlclRpbWUpIHtcbiAgICBsZXQgbmV4dFBhcnQgPSAtMTtcbiAgICBsZXQgY29udGlndW91cyA9IGZhbHNlO1xuICAgIGxldCBpbmRlcGVuZGVudEF0dHJPbWl0dGVkID0gdHJ1ZTtcbiAgICBmb3IgKGxldCBpID0gMCwgbGVuID0gcGFydExpc3QubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIGNvbnN0IHBhcnQgPSBwYXJ0TGlzdFtpXTtcbiAgICAgIGluZGVwZW5kZW50QXR0ck9taXR0ZWQgPSBpbmRlcGVuZGVudEF0dHJPbWl0dGVkICYmICFwYXJ0LmluZGVwZW5kZW50O1xuICAgICAgaWYgKG5leHRQYXJ0ID4gLTEgJiYgdGFyZ2V0QnVmZmVyVGltZSA8IHBhcnQuc3RhcnQpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBjb25zdCBsb2FkZWQgPSBwYXJ0LmxvYWRlZDtcbiAgICAgIGlmIChsb2FkZWQpIHtcbiAgICAgICAgbmV4dFBhcnQgPSAtMTtcbiAgICAgIH0gZWxzZSBpZiAoKGNvbnRpZ3VvdXMgfHwgcGFydC5pbmRlcGVuZGVudCB8fCBpbmRlcGVuZGVudEF0dHJPbWl0dGVkKSAmJiBwYXJ0LmZyYWdtZW50ID09PSBmcmFnKSB7XG4gICAgICAgIG5leHRQYXJ0ID0gaTtcbiAgICAgIH1cbiAgICAgIGNvbnRpZ3VvdXMgPSBsb2FkZWQ7XG4gICAgfVxuICAgIHJldHVybiBuZXh0UGFydDtcbiAgfVxuICBsb2FkZWRFbmRPZlBhcnRzKHBhcnRMaXN0LCB0YXJnZXRCdWZmZXJUaW1lKSB7XG4gICAgY29uc3QgbGFzdFBhcnQgPSBwYXJ0TGlzdFtwYXJ0TGlzdC5sZW5ndGggLSAxXTtcbiAgICByZXR1cm4gbGFzdFBhcnQgJiYgdGFyZ2V0QnVmZmVyVGltZSA+IGxhc3RQYXJ0LnN0YXJ0ICYmIGxhc3RQYXJ0LmxvYWRlZDtcbiAgfVxuXG4gIC8qXG4gICBUaGlzIG1ldGhvZCBpcyB1c2VkIGZpbmQgdGhlIGJlc3QgbWF0Y2hpbmcgZmlyc3QgZnJhZ21lbnQgZm9yIGEgbGl2ZSBwbGF5bGlzdC4gVGhpcyBmcmFnbWVudCBpcyB1c2VkIHRvIGNhbGN1bGF0ZSB0aGVcbiAgIFwic2xpZGluZ1wiIG9mIHRoZSBwbGF5bGlzdCwgd2hpY2ggaXMgaXRzIG9mZnNldCBmcm9tIHRoZSBzdGFydCBvZiBwbGF5YmFjay4gQWZ0ZXIgc2xpZGluZyB3ZSBjYW4gY29tcHV0ZSB0aGUgcmVhbFxuICAgc3RhcnQgYW5kIGVuZCB0aW1lcyBmb3IgZWFjaCBmcmFnbWVudCBpbiB0aGUgcGxheWxpc3QgKGFmdGVyIHdoaWNoIHRoaXMgbWV0aG9kIHdpbGwgbm90IG5lZWQgdG8gYmUgY2FsbGVkKS5cbiAgKi9cbiAgZ2V0SW5pdGlhbExpdmVGcmFnbWVudChsZXZlbERldGFpbHMsIGZyYWdtZW50cykge1xuICAgIGNvbnN0IGZyYWdQcmV2aW91cyA9IHRoaXMuZnJhZ1ByZXZpb3VzO1xuICAgIGxldCBmcmFnID0gbnVsbDtcbiAgICBpZiAoZnJhZ1ByZXZpb3VzKSB7XG4gICAgICBpZiAobGV2ZWxEZXRhaWxzLmhhc1Byb2dyYW1EYXRlVGltZSkge1xuICAgICAgICAvLyBQcmVmZXIgdXNpbmcgUERULCBiZWNhdXNlIGl0IGNhbiBiZSBhY2N1cmF0ZSBlbm91Z2ggdG8gY2hvb3NlIHRoZSBjb3JyZWN0IGZyYWdtZW50IHdpdGhvdXQga25vd2luZyB0aGUgbGV2ZWwgc2xpZGluZ1xuICAgICAgICB0aGlzLmxvZyhgTGl2ZSBwbGF5bGlzdCwgc3dpdGNoaW5nIHBsYXlsaXN0LCBsb2FkIGZyYWcgd2l0aCBzYW1lIFBEVDogJHtmcmFnUHJldmlvdXMucHJvZ3JhbURhdGVUaW1lfWApO1xuICAgICAgICBmcmFnID0gZmluZEZyYWdtZW50QnlQRFQoZnJhZ21lbnRzLCBmcmFnUHJldmlvdXMuZW5kUHJvZ3JhbURhdGVUaW1lLCB0aGlzLmNvbmZpZy5tYXhGcmFnTG9va1VwVG9sZXJhbmNlKTtcbiAgICAgIH1cbiAgICAgIGlmICghZnJhZykge1xuICAgICAgICAvLyBTTiBkb2VzIG5vdCBuZWVkIHRvIGJlIGFjY3VyYXRlIGJldHdlZW4gcmVuZGl0aW9ucywgYnV0IGRlcGVuZGluZyBvbiB0aGUgcGFja2FnaW5nIGl0IG1heSBiZSBzby5cbiAgICAgICAgY29uc3QgdGFyZ2V0U04gPSBmcmFnUHJldmlvdXMuc24gKyAxO1xuICAgICAgICBpZiAodGFyZ2V0U04gPj0gbGV2ZWxEZXRhaWxzLnN0YXJ0U04gJiYgdGFyZ2V0U04gPD0gbGV2ZWxEZXRhaWxzLmVuZFNOKSB7XG4gICAgICAgICAgY29uc3QgZnJhZ05leHQgPSBmcmFnbWVudHNbdGFyZ2V0U04gLSBsZXZlbERldGFpbHMuc3RhcnRTTl07XG4gICAgICAgICAgLy8gRW5zdXJlIHRoYXQgd2UncmUgc3RheWluZyB3aXRoaW4gdGhlIGNvbnRpbnVpdHkgcmFuZ2UsIHNpbmNlIFBUUyByZXNldHMgdXBvbiBhIG5ldyByYW5nZVxuICAgICAgICAgIGlmIChmcmFnUHJldmlvdXMuY2MgPT09IGZyYWdOZXh0LmNjKSB7XG4gICAgICAgICAgICBmcmFnID0gZnJhZ05leHQ7XG4gICAgICAgICAgICB0aGlzLmxvZyhgTGl2ZSBwbGF5bGlzdCwgc3dpdGNoaW5nIHBsYXlsaXN0LCBsb2FkIGZyYWcgd2l0aCBuZXh0IFNOOiAke2ZyYWcuc259YCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIEl0J3MgaW1wb3J0YW50IHRvIHN0YXkgd2l0aGluIHRoZSBjb250aW51aXR5IHJhbmdlIGlmIGF2YWlsYWJsZTsgb3RoZXJ3aXNlIHRoZSBmcmFnbWVudHMgaW4gdGhlIHBsYXlsaXN0XG4gICAgICAgIC8vIHdpbGwgaGF2ZSB0aGUgd3Jvbmcgc3RhcnQgdGltZXNcbiAgICAgICAgaWYgKCFmcmFnKSB7XG4gICAgICAgICAgZnJhZyA9IGZpbmRGcmFnV2l0aENDKGZyYWdtZW50cywgZnJhZ1ByZXZpb3VzLmNjKTtcbiAgICAgICAgICBpZiAoZnJhZykge1xuICAgICAgICAgICAgdGhpcy5sb2coYExpdmUgcGxheWxpc3QsIHN3aXRjaGluZyBwbGF5bGlzdCwgbG9hZCBmcmFnIHdpdGggc2FtZSBDQzogJHtmcmFnLnNufWApO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBGaW5kIGEgbmV3IHN0YXJ0IGZyYWdtZW50IHdoZW4gZnJhZ1ByZXZpb3VzIGlzIG51bGxcbiAgICAgIGNvbnN0IGxpdmVTdGFydCA9IHRoaXMuaGxzLmxpdmVTeW5jUG9zaXRpb247XG4gICAgICBpZiAobGl2ZVN0YXJ0ICE9PSBudWxsKSB7XG4gICAgICAgIGZyYWcgPSB0aGlzLmdldEZyYWdtZW50QXRQb3NpdGlvbihsaXZlU3RhcnQsIHRoaXMuYml0cmF0ZVRlc3QgPyBsZXZlbERldGFpbHMuZnJhZ21lbnRFbmQgOiBsZXZlbERldGFpbHMuZWRnZSwgbGV2ZWxEZXRhaWxzKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGZyYWc7XG4gIH1cblxuICAvKlxuICBUaGlzIG1ldGhvZCBmaW5kcyB0aGUgYmVzdCBtYXRjaGluZyBmcmFnbWVudCBnaXZlbiB0aGUgcHJvdmlkZWQgcG9zaXRpb24uXG4gICAqL1xuICBnZXRGcmFnbWVudEF0UG9zaXRpb24oYnVmZmVyRW5kLCBlbmQsIGxldmVsRGV0YWlscykge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZ1xuICAgIH0gPSB0aGlzO1xuICAgIGxldCB7XG4gICAgICBmcmFnUHJldmlvdXNcbiAgICB9ID0gdGhpcztcbiAgICBsZXQge1xuICAgICAgZnJhZ21lbnRzLFxuICAgICAgZW5kU05cbiAgICB9ID0gbGV2ZWxEZXRhaWxzO1xuICAgIGNvbnN0IHtcbiAgICAgIGZyYWdtZW50SGludFxuICAgIH0gPSBsZXZlbERldGFpbHM7XG4gICAgY29uc3Qge1xuICAgICAgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZVxuICAgIH0gPSBjb25maWc7XG4gICAgY29uc3QgcGFydExpc3QgPSBsZXZlbERldGFpbHMucGFydExpc3Q7XG4gICAgY29uc3QgbG9hZGluZ1BhcnRzID0gISEoY29uZmlnLmxvd0xhdGVuY3lNb2RlICYmIHBhcnRMaXN0ICE9IG51bGwgJiYgcGFydExpc3QubGVuZ3RoICYmIGZyYWdtZW50SGludCk7XG4gICAgaWYgKGxvYWRpbmdQYXJ0cyAmJiBmcmFnbWVudEhpbnQgJiYgIXRoaXMuYml0cmF0ZVRlc3QpIHtcbiAgICAgIC8vIEluY2x1ZGUgaW5jb21wbGV0ZSBmcmFnbWVudCB3aXRoIHBhcnRzIGF0IGVuZFxuICAgICAgZnJhZ21lbnRzID0gZnJhZ21lbnRzLmNvbmNhdChmcmFnbWVudEhpbnQpO1xuICAgICAgZW5kU04gPSBmcmFnbWVudEhpbnQuc247XG4gICAgfVxuICAgIGxldCBmcmFnO1xuICAgIGlmIChidWZmZXJFbmQgPCBlbmQpIHtcbiAgICAgIGNvbnN0IGxvb2t1cFRvbGVyYW5jZSA9IGJ1ZmZlckVuZCA+IGVuZCAtIG1heEZyYWdMb29rVXBUb2xlcmFuY2UgPyAwIDogbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZTtcbiAgICAgIC8vIFJlbW92ZSB0aGUgdG9sZXJhbmNlIGlmIGl0IHdvdWxkIHB1dCB0aGUgYnVmZmVyRW5kIHBhc3QgdGhlIGFjdHVhbCBlbmQgb2Ygc3RyZWFtXG4gICAgICAvLyBVc2VzIGJ1ZmZlciBhbmQgc2VxdWVuY2UgbnVtYmVyIHRvIGNhbGN1bGF0ZSBzd2l0Y2ggc2VnbWVudCAocmVxdWlyZWQgaWYgdXNpbmcgRVhULVgtRElTQ09OVElOVUlUWS1TRVFVRU5DRSlcbiAgICAgIGZyYWcgPSBmaW5kRnJhZ21lbnRCeVBUUyhmcmFnUHJldmlvdXMsIGZyYWdtZW50cywgYnVmZmVyRW5kLCBsb29rdXBUb2xlcmFuY2UpO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyByZWFjaCBlbmQgb2YgcGxheWxpc3RcbiAgICAgIGZyYWcgPSBmcmFnbWVudHNbZnJhZ21lbnRzLmxlbmd0aCAtIDFdO1xuICAgIH1cbiAgICBpZiAoZnJhZykge1xuICAgICAgY29uc3QgY3VyU05JZHggPSBmcmFnLnNuIC0gbGV2ZWxEZXRhaWxzLnN0YXJ0U047XG4gICAgICAvLyBNb3ZlIGZyYWdQcmV2aW91cyBmb3J3YXJkIHRvIHN1cHBvcnQgZm9yY2luZyB0aGUgbmV4dCBmcmFnbWVudCB0byBsb2FkXG4gICAgICAvLyB3aGVuIHRoZSBidWZmZXIgY2F0Y2hlcyB1cCB0byBhIHByZXZpb3VzbHkgYnVmZmVyZWQgcmFuZ2UuXG4gICAgICBjb25zdCBmcmFnU3RhdGUgPSB0aGlzLmZyYWdtZW50VHJhY2tlci5nZXRTdGF0ZShmcmFnKTtcbiAgICAgIGlmIChmcmFnU3RhdGUgPT09IEZyYWdtZW50U3RhdGUuT0sgfHwgZnJhZ1N0YXRlID09PSBGcmFnbWVudFN0YXRlLlBBUlRJQUwgJiYgZnJhZy5nYXApIHtcbiAgICAgICAgZnJhZ1ByZXZpb3VzID0gZnJhZztcbiAgICAgIH1cbiAgICAgIGlmIChmcmFnUHJldmlvdXMgJiYgZnJhZy5zbiA9PT0gZnJhZ1ByZXZpb3VzLnNuICYmICghbG9hZGluZ1BhcnRzIHx8IHBhcnRMaXN0WzBdLmZyYWdtZW50LnNuID4gZnJhZy5zbikpIHtcbiAgICAgICAgLy8gRm9yY2UgdGhlIG5leHQgZnJhZ21lbnQgdG8gbG9hZCBpZiB0aGUgcHJldmlvdXMgb25lIHdhcyBhbHJlYWR5IHNlbGVjdGVkLiBUaGlzIGNhbiBvY2Nhc2lvbmFsbHkgaGFwcGVuIHdpdGhcbiAgICAgICAgLy8gbm9uLXVuaWZvcm0gZnJhZ21lbnQgZHVyYXRpb25zXG4gICAgICAgIGNvbnN0IHNhbWVMZXZlbCA9IGZyYWdQcmV2aW91cyAmJiBmcmFnLmxldmVsID09PSBmcmFnUHJldmlvdXMubGV2ZWw7XG4gICAgICAgIGlmIChzYW1lTGV2ZWwpIHtcbiAgICAgICAgICBjb25zdCBuZXh0RnJhZyA9IGZyYWdtZW50c1tjdXJTTklkeCArIDFdO1xuICAgICAgICAgIGlmIChmcmFnLnNuIDwgZW5kU04gJiYgdGhpcy5mcmFnbWVudFRyYWNrZXIuZ2V0U3RhdGUobmV4dEZyYWcpICE9PSBGcmFnbWVudFN0YXRlLk9LKSB7XG4gICAgICAgICAgICBmcmFnID0gbmV4dEZyYWc7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGZyYWcgPSBudWxsO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZnJhZztcbiAgfVxuICBzeW5jaHJvbml6ZVRvTGl2ZUVkZ2UobGV2ZWxEZXRhaWxzKSB7XG4gICAgY29uc3Qge1xuICAgICAgY29uZmlnLFxuICAgICAgbWVkaWFcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIW1lZGlhKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGxpdmVTeW5jUG9zaXRpb24gPSB0aGlzLmhscy5saXZlU3luY1Bvc2l0aW9uO1xuICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gbWVkaWEuY3VycmVudFRpbWU7XG4gICAgY29uc3Qgc3RhcnQgPSBsZXZlbERldGFpbHMuZnJhZ21lbnRzWzBdLnN0YXJ0O1xuICAgIGNvbnN0IGVuZCA9IGxldmVsRGV0YWlscy5lZGdlO1xuICAgIGNvbnN0IHdpdGhpblNsaWRpbmdXaW5kb3cgPSBjdXJyZW50VGltZSA+PSBzdGFydCAtIGNvbmZpZy5tYXhGcmFnTG9va1VwVG9sZXJhbmNlICYmIGN1cnJlbnRUaW1lIDw9IGVuZDtcbiAgICAvLyBDb250aW51ZSBpZiB3ZSBjYW4gc2VlayBmb3J3YXJkIHRvIHN5bmMgcG9zaXRpb24gb3IgaWYgY3VycmVudCB0aW1lIGlzIG91dHNpZGUgb2Ygc2xpZGluZyB3aW5kb3dcbiAgICBpZiAobGl2ZVN5bmNQb3NpdGlvbiAhPT0gbnVsbCAmJiBtZWRpYS5kdXJhdGlvbiA+IGxpdmVTeW5jUG9zaXRpb24gJiYgKGN1cnJlbnRUaW1lIDwgbGl2ZVN5bmNQb3NpdGlvbiB8fCAhd2l0aGluU2xpZGluZ1dpbmRvdykpIHtcbiAgICAgIC8vIENvbnRpbnVlIGlmIGJ1ZmZlciBpcyBzdGFydmluZyBvciBpZiBjdXJyZW50IHRpbWUgaXMgYmVoaW5kIG1heCBsYXRlbmN5XG4gICAgICBjb25zdCBtYXhMYXRlbmN5ID0gY29uZmlnLmxpdmVNYXhMYXRlbmN5RHVyYXRpb24gIT09IHVuZGVmaW5lZCA/IGNvbmZpZy5saXZlTWF4TGF0ZW5jeUR1cmF0aW9uIDogY29uZmlnLmxpdmVNYXhMYXRlbmN5RHVyYXRpb25Db3VudCAqIGxldmVsRGV0YWlscy50YXJnZXRkdXJhdGlvbjtcbiAgICAgIGlmICghd2l0aGluU2xpZGluZ1dpbmRvdyAmJiBtZWRpYS5yZWFkeVN0YXRlIDwgNCB8fCBjdXJyZW50VGltZSA8IGVuZCAtIG1heExhdGVuY3kpIHtcbiAgICAgICAgaWYgKCF0aGlzLmxvYWRlZG1ldGFkYXRhKSB7XG4gICAgICAgICAgdGhpcy5uZXh0TG9hZFBvc2l0aW9uID0gbGl2ZVN5bmNQb3NpdGlvbjtcbiAgICAgICAgfVxuICAgICAgICAvLyBPbmx5IHNlZWsgaWYgcmVhZHkgYW5kIHRoZXJlIGlzIG5vdCBhIHNpZ25pZmljYW50IGZvcndhcmQgYnVmZmVyIGF2YWlsYWJsZSBmb3IgcGxheWJhY2tcbiAgICAgICAgaWYgKG1lZGlhLnJlYWR5U3RhdGUpIHtcbiAgICAgICAgICB0aGlzLndhcm4oYFBsYXliYWNrOiAke2N1cnJlbnRUaW1lLnRvRml4ZWQoMyl9IGlzIGxvY2F0ZWQgdG9vIGZhciBmcm9tIHRoZSBlbmQgb2YgbGl2ZSBzbGlkaW5nIHBsYXlsaXN0OiAke2VuZH0sIHJlc2V0IGN1cnJlbnRUaW1lIHRvIDogJHtsaXZlU3luY1Bvc2l0aW9uLnRvRml4ZWQoMyl9YCk7XG4gICAgICAgICAgbWVkaWEuY3VycmVudFRpbWUgPSBsaXZlU3luY1Bvc2l0aW9uO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGFsaWduUGxheWxpc3RzKGRldGFpbHMsIHByZXZpb3VzRGV0YWlscywgc3dpdGNoRGV0YWlscykge1xuICAgIC8vIEZJWE1FOiBJZiBub3QgZm9yIGBzaG91bGRBbGlnbk9uRGlzY29udGludWl0aWVzYCByZXF1aXJpbmcgZnJhZ1ByZXZpb3VzLmNjLFxuICAgIC8vICB0aGlzIGNvdWxkIGFsbCBnbyBpbiBsZXZlbC1oZWxwZXIgbWVyZ2VEZXRhaWxzKClcbiAgICBjb25zdCBsZW5ndGggPSBkZXRhaWxzLmZyYWdtZW50cy5sZW5ndGg7XG4gICAgaWYgKCFsZW5ndGgpIHtcbiAgICAgIHRoaXMud2FybihgTm8gZnJhZ21lbnRzIGluIGxpdmUgcGxheWxpc3RgKTtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cbiAgICBjb25zdCBzbGlkaW5nU3RhcnQgPSBkZXRhaWxzLmZyYWdtZW50c1swXS5zdGFydDtcbiAgICBjb25zdCBmaXJzdExldmVsTG9hZCA9ICFwcmV2aW91c0RldGFpbHM7XG4gICAgY29uc3QgYWxpZ25lZCA9IGRldGFpbHMuYWxpZ25lZFNsaWRpbmcgJiYgaXNGaW5pdGVOdW1iZXIoc2xpZGluZ1N0YXJ0KTtcbiAgICBpZiAoZmlyc3RMZXZlbExvYWQgfHwgIWFsaWduZWQgJiYgIXNsaWRpbmdTdGFydCkge1xuICAgICAgY29uc3Qge1xuICAgICAgICBmcmFnUHJldmlvdXNcbiAgICAgIH0gPSB0aGlzO1xuICAgICAgYWxpZ25TdHJlYW0oZnJhZ1ByZXZpb3VzLCBzd2l0Y2hEZXRhaWxzLCBkZXRhaWxzKTtcbiAgICAgIGNvbnN0IGFsaWduZWRTbGlkaW5nU3RhcnQgPSBkZXRhaWxzLmZyYWdtZW50c1swXS5zdGFydDtcbiAgICAgIHRoaXMubG9nKGBMaXZlIHBsYXlsaXN0IHNsaWRpbmc6ICR7YWxpZ25lZFNsaWRpbmdTdGFydC50b0ZpeGVkKDIpfSBzdGFydC1zbjogJHtwcmV2aW91c0RldGFpbHMgPyBwcmV2aW91c0RldGFpbHMuc3RhcnRTTiA6ICduYSd9LT4ke2RldGFpbHMuc3RhcnRTTn0gcHJldi1zbjogJHtmcmFnUHJldmlvdXMgPyBmcmFnUHJldmlvdXMuc24gOiAnbmEnfSBmcmFnbWVudHM6ICR7bGVuZ3RofWApO1xuICAgICAgcmV0dXJuIGFsaWduZWRTbGlkaW5nU3RhcnQ7XG4gICAgfVxuICAgIHJldHVybiBzbGlkaW5nU3RhcnQ7XG4gIH1cbiAgd2FpdEZvckNkblR1bmVJbihkZXRhaWxzKSB7XG4gICAgLy8gV2FpdCBmb3IgTG93LUxhdGVuY3kgQ0ROIFR1bmUtaW4gdG8gZ2V0IGFuIHVwZGF0ZWQgcGxheWxpc3RcbiAgICBjb25zdCBhZHZhbmNlUGFydExpbWl0ID0gMztcbiAgICByZXR1cm4gZGV0YWlscy5saXZlICYmIGRldGFpbHMuY2FuQmxvY2tSZWxvYWQgJiYgZGV0YWlscy5wYXJ0VGFyZ2V0ICYmIGRldGFpbHMudHVuZUluR29hbCA+IE1hdGgubWF4KGRldGFpbHMucGFydEhvbGRCYWNrLCBkZXRhaWxzLnBhcnRUYXJnZXQgKiBhZHZhbmNlUGFydExpbWl0KTtcbiAgfVxuICBzZXRTdGFydFBvc2l0aW9uKGRldGFpbHMsIHNsaWRpbmcpIHtcbiAgICAvLyBjb21wdXRlIHN0YXJ0IHBvc2l0aW9uIGlmIHNldCB0byAtMS4gdXNlIGl0IHN0cmFpZ2h0IGF3YXkgaWYgdmFsdWUgaXMgZGVmaW5lZFxuICAgIGxldCBzdGFydFBvc2l0aW9uID0gdGhpcy5zdGFydFBvc2l0aW9uO1xuICAgIGlmIChzdGFydFBvc2l0aW9uIDwgc2xpZGluZykge1xuICAgICAgc3RhcnRQb3NpdGlvbiA9IC0xO1xuICAgIH1cbiAgICBpZiAoc3RhcnRQb3NpdGlvbiA9PT0gLTEgfHwgdGhpcy5sYXN0Q3VycmVudFRpbWUgPT09IC0xKSB7XG4gICAgICAvLyBVc2UgUGxheWxpc3QgRVhULVgtU1RBUlQ6VElNRS1PRkZTRVQgd2hlbiBzZXRcbiAgICAgIC8vIFByaW9yaXRpemUgTXVsdGl2YXJpYW50IFBsYXlsaXN0IG9mZnNldCBzbyB0aGF0IG1haW4sIGF1ZGlvLCBhbmQgc3VidGl0bGUgc3RyZWFtLWNvbnRyb2xsZXIgc3RhcnQgdGltZXMgbWF0Y2hcbiAgICAgIGNvbnN0IG9mZnNldEluTXVsdGl2YXJpYW50UGxheWxpc3QgPSB0aGlzLnN0YXJ0VGltZU9mZnNldCAhPT0gbnVsbDtcbiAgICAgIGNvbnN0IHN0YXJ0VGltZU9mZnNldCA9IG9mZnNldEluTXVsdGl2YXJpYW50UGxheWxpc3QgPyB0aGlzLnN0YXJ0VGltZU9mZnNldCA6IGRldGFpbHMuc3RhcnRUaW1lT2Zmc2V0O1xuICAgICAgaWYgKHN0YXJ0VGltZU9mZnNldCAhPT0gbnVsbCAmJiBpc0Zpbml0ZU51bWJlcihzdGFydFRpbWVPZmZzZXQpKSB7XG4gICAgICAgIHN0YXJ0UG9zaXRpb24gPSBzbGlkaW5nICsgc3RhcnRUaW1lT2Zmc2V0O1xuICAgICAgICBpZiAoc3RhcnRUaW1lT2Zmc2V0IDwgMCkge1xuICAgICAgICAgIHN0YXJ0UG9zaXRpb24gKz0gZGV0YWlscy50b3RhbGR1cmF0aW9uO1xuICAgICAgICB9XG4gICAgICAgIHN0YXJ0UG9zaXRpb24gPSBNYXRoLm1pbihNYXRoLm1heChzbGlkaW5nLCBzdGFydFBvc2l0aW9uKSwgc2xpZGluZyArIGRldGFpbHMudG90YWxkdXJhdGlvbik7XG4gICAgICAgIHRoaXMubG9nKGBTdGFydCB0aW1lIG9mZnNldCAke3N0YXJ0VGltZU9mZnNldH0gZm91bmQgaW4gJHtvZmZzZXRJbk11bHRpdmFyaWFudFBsYXlsaXN0ID8gJ211bHRpdmFyaWFudCcgOiAnbWVkaWEnfSBwbGF5bGlzdCwgYWRqdXN0IHN0YXJ0UG9zaXRpb24gdG8gJHtzdGFydFBvc2l0aW9ufWApO1xuICAgICAgICB0aGlzLnN0YXJ0UG9zaXRpb24gPSBzdGFydFBvc2l0aW9uO1xuICAgICAgfSBlbHNlIGlmIChkZXRhaWxzLmxpdmUpIHtcbiAgICAgICAgLy8gTGVhdmUgdGhpcy5zdGFydFBvc2l0aW9uIGF0IC0xLCBzbyB0aGF0IHdlIGNhbiB1c2UgYGdldEluaXRpYWxMaXZlRnJhZ21lbnRgIGxvZ2ljIHdoZW4gc3RhcnRQb3NpdGlvbiBoYXNcbiAgICAgICAgLy8gbm90IGJlZW4gc3BlY2lmaWVkIHZpYSB0aGUgY29uZmlnIG9yIGFuIGFzIGFuIGFyZ3VtZW50IHRvIHN0YXJ0TG9hZCAoIzM3MzYpLlxuICAgICAgICBzdGFydFBvc2l0aW9uID0gdGhpcy5obHMubGl2ZVN5bmNQb3NpdGlvbiB8fCBzbGlkaW5nO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5zdGFydFBvc2l0aW9uID0gc3RhcnRQb3NpdGlvbiA9IDA7XG4gICAgICB9XG4gICAgICB0aGlzLmxhc3RDdXJyZW50VGltZSA9IHN0YXJ0UG9zaXRpb247XG4gICAgfVxuICAgIHRoaXMubmV4dExvYWRQb3NpdGlvbiA9IHN0YXJ0UG9zaXRpb247XG4gIH1cbiAgZ2V0TG9hZFBvc2l0aW9uKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIG1lZGlhXG4gICAgfSA9IHRoaXM7XG4gICAgLy8gaWYgd2UgaGF2ZSBub3QgeWV0IGxvYWRlZCBhbnkgZnJhZ21lbnQsIHN0YXJ0IGxvYWRpbmcgZnJvbSBzdGFydCBwb3NpdGlvblxuICAgIGxldCBwb3MgPSAwO1xuICAgIGlmICh0aGlzLmxvYWRlZG1ldGFkYXRhICYmIG1lZGlhKSB7XG4gICAgICBwb3MgPSBtZWRpYS5jdXJyZW50VGltZTtcbiAgICB9IGVsc2UgaWYgKHRoaXMubmV4dExvYWRQb3NpdGlvbikge1xuICAgICAgcG9zID0gdGhpcy5uZXh0TG9hZFBvc2l0aW9uO1xuICAgIH1cbiAgICByZXR1cm4gcG9zO1xuICB9XG4gIGhhbmRsZUZyYWdMb2FkQWJvcnRlZChmcmFnLCBwYXJ0KSB7XG4gICAgaWYgKHRoaXMudHJhbnNtdXhlciAmJiBmcmFnLnNuICE9PSAnaW5pdFNlZ21lbnQnICYmIGZyYWcuc3RhdHMuYWJvcnRlZCkge1xuICAgICAgdGhpcy53YXJuKGBGcmFnbWVudCAke2ZyYWcuc259JHtwYXJ0ID8gJyBwYXJ0ICcgKyBwYXJ0LmluZGV4IDogJyd9IG9mIGxldmVsICR7ZnJhZy5sZXZlbH0gd2FzIGFib3J0ZWRgKTtcbiAgICAgIHRoaXMucmVzZXRGcmFnbWVudExvYWRpbmcoZnJhZyk7XG4gICAgfVxuICB9XG4gIHJlc2V0RnJhZ21lbnRMb2FkaW5nKGZyYWcpIHtcbiAgICBpZiAoIXRoaXMuZnJhZ0N1cnJlbnQgfHwgIXRoaXMuZnJhZ0NvbnRleHRDaGFuZ2VkKGZyYWcpICYmIHRoaXMuc3RhdGUgIT09IFN0YXRlLkZSQUdfTE9BRElOR19XQUlUSU5HX1JFVFJZKSB7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICB9XG4gIH1cbiAgb25GcmFnbWVudE9yS2V5TG9hZEVycm9yKGZpbHRlclR5cGUsIGRhdGEpIHtcbiAgICBpZiAoZGF0YS5jaHVua01ldGEgJiYgIWRhdGEuZnJhZykge1xuICAgICAgY29uc3QgY29udGV4dCA9IHRoaXMuZ2V0Q3VycmVudENvbnRleHQoZGF0YS5jaHVua01ldGEpO1xuICAgICAgaWYgKGNvbnRleHQpIHtcbiAgICAgICAgZGF0YS5mcmFnID0gY29udGV4dC5mcmFnO1xuICAgICAgfVxuICAgIH1cbiAgICBjb25zdCBmcmFnID0gZGF0YS5mcmFnO1xuICAgIC8vIEhhbmRsZSBmcmFnIGVycm9yIHJlbGF0ZWQgdG8gY2FsbGVyJ3MgZmlsdGVyVHlwZVxuICAgIGlmICghZnJhZyB8fCBmcmFnLnR5cGUgIT09IGZpbHRlclR5cGUgfHwgIXRoaXMubGV2ZWxzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh0aGlzLmZyYWdDb250ZXh0Q2hhbmdlZChmcmFnKSkge1xuICAgICAgdmFyIF90aGlzJGZyYWdDdXJyZW50MjtcbiAgICAgIHRoaXMud2FybihgRnJhZyBsb2FkIGVycm9yIG11c3QgbWF0Y2ggY3VycmVudCBmcmFnIHRvIHJldHJ5ICR7ZnJhZy51cmx9ID4gJHsoX3RoaXMkZnJhZ0N1cnJlbnQyID0gdGhpcy5mcmFnQ3VycmVudCkgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGZyYWdDdXJyZW50Mi51cmx9YCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGdhcFRhZ0VuY291bnRlcmVkID0gZGF0YS5kZXRhaWxzID09PSBFcnJvckRldGFpbHMuRlJBR19HQVA7XG4gICAgaWYgKGdhcFRhZ0VuY291bnRlcmVkKSB7XG4gICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5mcmFnQnVmZmVyZWQoZnJhZywgdHJ1ZSk7XG4gICAgfVxuICAgIC8vIGtlZXAgcmV0cnlpbmcgdW50aWwgdGhlIGxpbWl0IHdpbGwgYmUgcmVhY2hlZFxuICAgIGNvbnN0IGVycm9yQWN0aW9uID0gZGF0YS5lcnJvckFjdGlvbjtcbiAgICBjb25zdCB7XG4gICAgICBhY3Rpb24sXG4gICAgICByZXRyeUNvdW50ID0gMCxcbiAgICAgIHJldHJ5Q29uZmlnXG4gICAgfSA9IGVycm9yQWN0aW9uIHx8IHt9O1xuICAgIGlmIChlcnJvckFjdGlvbiAmJiBhY3Rpb24gPT09IE5ldHdvcmtFcnJvckFjdGlvbi5SZXRyeVJlcXVlc3QgJiYgcmV0cnlDb25maWcpIHtcbiAgICAgIHRoaXMucmVzZXRTdGFydFdoZW5Ob3RMb2FkZWQodGhpcy5sZXZlbExhc3RMb2FkZWQpO1xuICAgICAgY29uc3QgZGVsYXkgPSBnZXRSZXRyeURlbGF5KHJldHJ5Q29uZmlnLCByZXRyeUNvdW50KTtcbiAgICAgIHRoaXMud2FybihgRnJhZ21lbnQgJHtmcmFnLnNufSBvZiAke2ZpbHRlclR5cGV9ICR7ZnJhZy5sZXZlbH0gZXJyb3JlZCB3aXRoICR7ZGF0YS5kZXRhaWxzfSwgcmV0cnlpbmcgbG9hZGluZyAke3JldHJ5Q291bnQgKyAxfS8ke3JldHJ5Q29uZmlnLm1heE51bVJldHJ5fSBpbiAke2RlbGF5fW1zYCk7XG4gICAgICBlcnJvckFjdGlvbi5yZXNvbHZlZCA9IHRydWU7XG4gICAgICB0aGlzLnJldHJ5RGF0ZSA9IHNlbGYucGVyZm9ybWFuY2Uubm93KCkgKyBkZWxheTtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5GUkFHX0xPQURJTkdfV0FJVElOR19SRVRSWTtcbiAgICB9IGVsc2UgaWYgKHJldHJ5Q29uZmlnICYmIGVycm9yQWN0aW9uKSB7XG4gICAgICB0aGlzLnJlc2V0RnJhZ21lbnRFcnJvcnMoZmlsdGVyVHlwZSk7XG4gICAgICBpZiAocmV0cnlDb3VudCA8IHJldHJ5Q29uZmlnLm1heE51bVJldHJ5KSB7XG4gICAgICAgIC8vIE5ldHdvcmsgcmV0cnkgaXMgc2tpcHBlZCB3aGVuIGxldmVsIHN3aXRjaCBpcyBwcmVmZXJyZWRcbiAgICAgICAgaWYgKCFnYXBUYWdFbmNvdW50ZXJlZCAmJiBhY3Rpb24gIT09IE5ldHdvcmtFcnJvckFjdGlvbi5SZW1vdmVBbHRlcm5hdGVQZXJtYW5lbnRseSkge1xuICAgICAgICAgIGVycm9yQWN0aW9uLnJlc29sdmVkID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oYCR7ZGF0YS5kZXRhaWxzfSByZWFjaGVkIG9yIGV4Y2VlZGVkIG1heCByZXRyeSAoJHtyZXRyeUNvdW50fSlgKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoKGVycm9yQWN0aW9uID09IG51bGwgPyB2b2lkIDAgOiBlcnJvckFjdGlvbi5hY3Rpb24pID09PSBOZXR3b3JrRXJyb3JBY3Rpb24uU2VuZEFsdGVybmF0ZVRvUGVuYWx0eUJveCkge1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLldBSVRJTkdfTEVWRUw7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5FUlJPUjtcbiAgICB9XG4gICAgLy8gUGVyZm9ybSBuZXh0IGFzeW5jIHRpY2sgc29vbmVyIHRvIHNwZWVkIHVwIGVycm9yIGFjdGlvbiByZXNvbHV0aW9uXG4gICAgdGhpcy50aWNrSW1tZWRpYXRlKCk7XG4gIH1cbiAgcmVkdWNlTGVuZ3RoQW5kRmx1c2hCdWZmZXIoZGF0YSkge1xuICAgIC8vIGlmIGluIGFwcGVuZGluZyBzdGF0ZVxuICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5QQVJTSU5HIHx8IHRoaXMuc3RhdGUgPT09IFN0YXRlLlBBUlNFRCkge1xuICAgICAgY29uc3QgZnJhZyA9IGRhdGEuZnJhZztcbiAgICAgIGNvbnN0IHBsYXlsaXN0VHlwZSA9IGRhdGEucGFyZW50O1xuICAgICAgY29uc3QgYnVmZmVyZWRJbmZvID0gdGhpcy5nZXRGd2RCdWZmZXJJbmZvKHRoaXMubWVkaWFCdWZmZXIsIHBsYXlsaXN0VHlwZSk7XG4gICAgICAvLyAwLjUgOiB0b2xlcmFuY2UgbmVlZGVkIGFzIHNvbWUgYnJvd3NlcnMgc3RhbGxzIHBsYXliYWNrIGJlZm9yZSByZWFjaGluZyBidWZmZXJlZCBlbmRcbiAgICAgIC8vIHJlZHVjZSBtYXggYnVmIGxlbiBpZiBjdXJyZW50IHBvc2l0aW9uIGlzIGJ1ZmZlcmVkXG4gICAgICBjb25zdCBidWZmZXJlZCA9IGJ1ZmZlcmVkSW5mbyAmJiBidWZmZXJlZEluZm8ubGVuID4gMC41O1xuICAgICAgaWYgKGJ1ZmZlcmVkKSB7XG4gICAgICAgIHRoaXMucmVkdWNlTWF4QnVmZmVyTGVuZ3RoKGJ1ZmZlcmVkSW5mby5sZW4sIChmcmFnID09IG51bGwgPyB2b2lkIDAgOiBmcmFnLmR1cmF0aW9uKSB8fCAxMCk7XG4gICAgICB9XG4gICAgICBjb25zdCBmbHVzaEJ1ZmZlciA9ICFidWZmZXJlZDtcbiAgICAgIGlmIChmbHVzaEJ1ZmZlcikge1xuICAgICAgICAvLyBjdXJyZW50IHBvc2l0aW9uIGlzIG5vdCBidWZmZXJlZCwgYnV0IGJyb3dzZXIgaXMgc3RpbGwgY29tcGxhaW5pbmcgYWJvdXQgYnVmZmVyIGZ1bGwgZXJyb3JcbiAgICAgICAgLy8gdGhpcyBoYXBwZW5zIG9uIElFL0VkZ2UsIHJlZmVyIHRvIGh0dHBzOi8vZ2l0aHViLmNvbS92aWRlby1kZXYvaGxzLmpzL3B1bGwvNzA4XG4gICAgICAgIC8vIGluIHRoYXQgY2FzZSBmbHVzaCB0aGUgd2hvbGUgYXVkaW8gYnVmZmVyIHRvIHJlY292ZXJcbiAgICAgICAgdGhpcy53YXJuKGBCdWZmZXIgZnVsbCBlcnJvciB3aGlsZSBtZWRpYS5jdXJyZW50VGltZSBpcyBub3QgYnVmZmVyZWQsIGZsdXNoICR7cGxheWxpc3RUeXBlfSBidWZmZXJgKTtcbiAgICAgIH1cbiAgICAgIGlmIChmcmFnKSB7XG4gICAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50KGZyYWcpO1xuICAgICAgICB0aGlzLm5leHRMb2FkUG9zaXRpb24gPSBmcmFnLnN0YXJ0O1xuICAgICAgfVxuICAgICAgdGhpcy5yZXNldExvYWRpbmdTdGF0ZSgpO1xuICAgICAgcmV0dXJuIGZsdXNoQnVmZmVyO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgcmVzZXRGcmFnbWVudEVycm9ycyhmaWx0ZXJUeXBlKSB7XG4gICAgaWYgKGZpbHRlclR5cGUgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLkFVRElPKSB7XG4gICAgICAvLyBSZXNldCBjdXJyZW50IGZyYWdtZW50IHNpbmNlIGF1ZGlvIHRyYWNrIGF1ZGlvIGlzIGVzc2VudGlhbCBhbmQgbWF5IG5vdCBoYXZlIGEgZmFpbC1vdmVyIHRyYWNrXG4gICAgICB0aGlzLmZyYWdDdXJyZW50ID0gbnVsbDtcbiAgICB9XG4gICAgLy8gRnJhZ21lbnQgZXJyb3JzIHRoYXQgcmVzdWx0IGluIGEgbGV2ZWwgc3dpdGNoIG9yIHJlZHVuZGFudCBmYWlsLW92ZXJcbiAgICAvLyBzaG91bGQgcmVzZXQgdGhlIHN0cmVhbSBjb250cm9sbGVyIHN0YXRlIHRvIGlkbGVcbiAgICBpZiAoIXRoaXMubG9hZGVkbWV0YWRhdGEpIHtcbiAgICAgIHRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkID0gZmFsc2U7XG4gICAgfVxuICAgIGlmICh0aGlzLnN0YXRlICE9PSBTdGF0ZS5TVE9QUEVEKSB7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICB9XG4gIH1cbiAgYWZ0ZXJCdWZmZXJGbHVzaGVkKG1lZGlhLCBidWZmZXJUeXBlLCBwbGF5bGlzdFR5cGUpIHtcbiAgICBpZiAoIW1lZGlhKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIEFmdGVyIHN1Y2Nlc3NmdWwgYnVmZmVyIGZsdXNoaW5nLCBmaWx0ZXIgZmx1c2hlZCBmcmFnbWVudHMgZnJvbSBidWZmZXJlZEZyYWdzIHVzZSBtZWRpYUJ1ZmZlcmVkIGluc3RlYWQgb2YgbWVkaWFcbiAgICAvLyAoc28gdGhhdCB3ZSB3aWxsIGNoZWNrIGFnYWluc3QgdmlkZW8uYnVmZmVyZWQgcmFuZ2VzIGluIGNhc2Ugb2YgYWx0IGF1ZGlvIHRyYWNrKVxuICAgIGNvbnN0IGJ1ZmZlcmVkVGltZVJhbmdlcyA9IEJ1ZmZlckhlbHBlci5nZXRCdWZmZXJlZChtZWRpYSk7XG4gICAgdGhpcy5mcmFnbWVudFRyYWNrZXIuZGV0ZWN0RXZpY3RlZEZyYWdtZW50cyhidWZmZXJUeXBlLCBidWZmZXJlZFRpbWVSYW5nZXMsIHBsYXlsaXN0VHlwZSk7XG4gICAgaWYgKHRoaXMuc3RhdGUgPT09IFN0YXRlLkVOREVEKSB7XG4gICAgICB0aGlzLnJlc2V0TG9hZGluZ1N0YXRlKCk7XG4gICAgfVxuICB9XG4gIHJlc2V0TG9hZGluZ1N0YXRlKCkge1xuICAgIHRoaXMubG9nKCdSZXNldCBsb2FkaW5nIHN0YXRlJyk7XG4gICAgdGhpcy5mcmFnQ3VycmVudCA9IG51bGw7XG4gICAgdGhpcy5mcmFnUHJldmlvdXMgPSBudWxsO1xuICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICB9XG4gIHJlc2V0U3RhcnRXaGVuTm90TG9hZGVkKGxldmVsKSB7XG4gICAgLy8gaWYgbG9hZGVkbWV0YWRhdGEgaXMgbm90IHNldCwgaXQgbWVhbnMgdGhhdCBmaXJzdCBmcmFnIHJlcXVlc3QgZmFpbGVkXG4gICAgLy8gaW4gdGhhdCBjYXNlLCByZXNldCBzdGFydEZyYWdSZXF1ZXN0ZWQgZmxhZ1xuICAgIGlmICghdGhpcy5sb2FkZWRtZXRhZGF0YSkge1xuICAgICAgdGhpcy5zdGFydEZyYWdSZXF1ZXN0ZWQgPSBmYWxzZTtcbiAgICAgIGNvbnN0IGRldGFpbHMgPSBsZXZlbCA/IGxldmVsLmRldGFpbHMgOiBudWxsO1xuICAgICAgaWYgKGRldGFpbHMgIT0gbnVsbCAmJiBkZXRhaWxzLmxpdmUpIHtcbiAgICAgICAgLy8gVXBkYXRlIHRoZSBzdGFydCBwb3NpdGlvbiBhbmQgcmV0dXJuIHRvIElETEUgdG8gcmVjb3ZlciBsaXZlIHN0YXJ0XG4gICAgICAgIHRoaXMuc3RhcnRQb3NpdGlvbiA9IC0xO1xuICAgICAgICB0aGlzLnNldFN0YXJ0UG9zaXRpb24oZGV0YWlscywgMCk7XG4gICAgICAgIHRoaXMucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMubmV4dExvYWRQb3NpdGlvbiA9IHRoaXMuc3RhcnRQb3NpdGlvbjtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmVzZXRXaGVuTWlzc2luZ0NvbnRleHQoY2h1bmtNZXRhKSB7XG4gICAgdGhpcy53YXJuKGBUaGUgbG9hZGluZyBjb250ZXh0IGNoYW5nZWQgd2hpbGUgYnVmZmVyaW5nIGZyYWdtZW50ICR7Y2h1bmtNZXRhLnNufSBvZiBsZXZlbCAke2NodW5rTWV0YS5sZXZlbH0uIFRoaXMgY2h1bmsgd2lsbCBub3QgYmUgYnVmZmVyZWQuYCk7XG4gICAgdGhpcy5yZW1vdmVVbmJ1ZmZlcmVkRnJhZ3MoKTtcbiAgICB0aGlzLnJlc2V0U3RhcnRXaGVuTm90TG9hZGVkKHRoaXMubGV2ZWxMYXN0TG9hZGVkKTtcbiAgICB0aGlzLnJlc2V0TG9hZGluZ1N0YXRlKCk7XG4gIH1cbiAgcmVtb3ZlVW5idWZmZXJlZEZyYWdzKHN0YXJ0ID0gMCkge1xuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50c0luUmFuZ2Uoc3RhcnQsIEluZmluaXR5LCB0aGlzLnBsYXlsaXN0VHlwZSwgZmFsc2UsIHRydWUpO1xuICB9XG4gIHVwZGF0ZUxldmVsVGltaW5nKGZyYWcsIHBhcnQsIGxldmVsLCBwYXJ0aWFsKSB7XG4gICAgdmFyIF90aGlzJHRyYW5zbXV4ZXI7XG4gICAgY29uc3QgZGV0YWlscyA9IGxldmVsLmRldGFpbHM7XG4gICAgaWYgKCFkZXRhaWxzKSB7XG4gICAgICB0aGlzLndhcm4oJ2xldmVsLmRldGFpbHMgdW5kZWZpbmVkJyk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHBhcnNlZCA9IE9iamVjdC5rZXlzKGZyYWcuZWxlbWVudGFyeVN0cmVhbXMpLnJlZHVjZSgocmVzdWx0LCB0eXBlKSA9PiB7XG4gICAgICBjb25zdCBpbmZvID0gZnJhZy5lbGVtZW50YXJ5U3RyZWFtc1t0eXBlXTtcbiAgICAgIGlmIChpbmZvKSB7XG4gICAgICAgIGNvbnN0IHBhcnNlZER1cmF0aW9uID0gaW5mby5lbmRQVFMgLSBpbmZvLnN0YXJ0UFRTO1xuICAgICAgICBpZiAocGFyc2VkRHVyYXRpb24gPD0gMCkge1xuICAgICAgICAgIC8vIERlc3Ryb3kgdGhlIHRyYW5zbXV4ZXIgYWZ0ZXIgaXQncyBuZXh0IHRpbWUgb2Zmc2V0IGZhaWxlZCB0byBhZHZhbmNlIGJlY2F1c2UgZHVyYXRpb24gd2FzIDw9IDAuXG4gICAgICAgICAgLy8gVGhlIG5ldyB0cmFuc211eGVyIHdpbGwgYmUgY29uZmlndXJlZCB3aXRoIGEgdGltZSBvZmZzZXQgbWF0Y2hpbmcgdGhlIG5leHQgZnJhZ21lbnQgc3RhcnQsXG4gICAgICAgICAgLy8gcHJldmVudGluZyB0aGUgdGltZWxpbmUgZnJvbSBzaGlmdGluZy5cbiAgICAgICAgICB0aGlzLndhcm4oYENvdWxkIG5vdCBwYXJzZSBmcmFnbWVudCAke2ZyYWcuc259ICR7dHlwZX0gZHVyYXRpb24gcmVsaWFibHkgKCR7cGFyc2VkRHVyYXRpb259KWApO1xuICAgICAgICAgIHJldHVybiByZXN1bHQgfHwgZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgZHJpZnQgPSBwYXJ0aWFsID8gMCA6IHVwZGF0ZUZyYWdQVFNEVFMoZGV0YWlscywgZnJhZywgaW5mby5zdGFydFBUUywgaW5mby5lbmRQVFMsIGluZm8uc3RhcnREVFMsIGluZm8uZW5kRFRTKTtcbiAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuTEVWRUxfUFRTX1VQREFURUQsIHtcbiAgICAgICAgICBkZXRhaWxzLFxuICAgICAgICAgIGxldmVsLFxuICAgICAgICAgIGRyaWZ0LFxuICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgZnJhZyxcbiAgICAgICAgICBzdGFydDogaW5mby5zdGFydFBUUyxcbiAgICAgICAgICBlbmQ6IGluZm8uZW5kUFRTXG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfSwgZmFsc2UpO1xuICAgIGlmICghcGFyc2VkICYmICgoX3RoaXMkdHJhbnNtdXhlciA9IHRoaXMudHJhbnNtdXhlcikgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJHRyYW5zbXV4ZXIuZXJyb3IpID09PSBudWxsKSB7XG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcihgRm91bmQgbm8gbWVkaWEgaW4gZnJhZ21lbnQgJHtmcmFnLnNufSBvZiBsZXZlbCAke2ZyYWcubGV2ZWx9IHJlc2V0dGluZyB0cmFuc211eGVyIHRvIGZhbGxiYWNrIHRvIHBsYXlsaXN0IHRpbWluZ2ApO1xuICAgICAgaWYgKGxldmVsLmZyYWdtZW50RXJyb3IgPT09IDApIHtcbiAgICAgICAgLy8gTWFyayBhbmQgdHJhY2sgdGhlIG9kZCBlbXB0eSBzZWdtZW50IGFzIGEgZ2FwIHRvIGF2b2lkIHJlbG9hZGluZ1xuICAgICAgICBsZXZlbC5mcmFnbWVudEVycm9yKys7XG4gICAgICAgIGZyYWcuZ2FwID0gdHJ1ZTtcbiAgICAgICAgdGhpcy5mcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQoZnJhZyk7XG4gICAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLmZyYWdCdWZmZXJlZChmcmFnLCB0cnVlKTtcbiAgICAgIH1cbiAgICAgIHRoaXMud2FybihlcnJvci5tZXNzYWdlKTtcbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCB7XG4gICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX1BBUlNJTkdfRVJST1IsXG4gICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgZXJyb3IsXG4gICAgICAgIGZyYWcsXG4gICAgICAgIHJlYXNvbjogYEZvdW5kIG5vIG1lZGlhIGluIG1zbiAke2ZyYWcuc259IG9mIGxldmVsIFwiJHtsZXZlbC51cmx9XCJgXG4gICAgICB9KTtcbiAgICAgIGlmICghdGhpcy5obHMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5yZXNldFRyYW5zbXV4ZXIoKTtcbiAgICAgIC8vIEZvciB0aGlzIGVycm9yIGZhbGx0aHJvdWdoLiBNYXJraW5nIHBhcnNlZCB3aWxsIGFsbG93IGFkdmFuY2luZyB0byBuZXh0IGZyYWdtZW50LlxuICAgIH1cbiAgICB0aGlzLnN0YXRlID0gU3RhdGUuUEFSU0VEO1xuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkZSQUdfUEFSU0VELCB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydFxuICAgIH0pO1xuICB9XG4gIHJlc2V0VHJhbnNtdXhlcigpIHtcbiAgICBpZiAodGhpcy50cmFuc211eGVyKSB7XG4gICAgICB0aGlzLnRyYW5zbXV4ZXIuZGVzdHJveSgpO1xuICAgICAgdGhpcy50cmFuc211eGVyID0gbnVsbDtcbiAgICB9XG4gIH1cbiAgcmVjb3ZlcldvcmtlckVycm9yKGRhdGEpIHtcbiAgICBpZiAoZGF0YS5ldmVudCA9PT0gJ2RlbXV4ZXJXb3JrZXInKSB7XG4gICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5yZW1vdmVBbGxGcmFnbWVudHMoKTtcbiAgICAgIHRoaXMucmVzZXRUcmFuc211eGVyKCk7XG4gICAgICB0aGlzLnJlc2V0U3RhcnRXaGVuTm90TG9hZGVkKHRoaXMubGV2ZWxMYXN0TG9hZGVkKTtcbiAgICAgIHRoaXMucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgICB9XG4gIH1cbiAgc2V0IHN0YXRlKG5leHRTdGF0ZSkge1xuICAgIGNvbnN0IHByZXZpb3VzU3RhdGUgPSB0aGlzLl9zdGF0ZTtcbiAgICBpZiAocHJldmlvdXNTdGF0ZSAhPT0gbmV4dFN0YXRlKSB7XG4gICAgICB0aGlzLl9zdGF0ZSA9IG5leHRTdGF0ZTtcbiAgICAgIHRoaXMubG9nKGAke3ByZXZpb3VzU3RhdGV9LT4ke25leHRTdGF0ZX1gKTtcbiAgICB9XG4gIH1cbiAgZ2V0IHN0YXRlKCkge1xuICAgIHJldHVybiB0aGlzLl9zdGF0ZTtcbiAgfVxufVxuXG5jbGFzcyBDaHVua0NhY2hlIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5jaHVua3MgPSBbXTtcbiAgICB0aGlzLmRhdGFMZW5ndGggPSAwO1xuICB9XG4gIHB1c2goY2h1bmspIHtcbiAgICB0aGlzLmNodW5rcy5wdXNoKGNodW5rKTtcbiAgICB0aGlzLmRhdGFMZW5ndGggKz0gY2h1bmsubGVuZ3RoO1xuICB9XG4gIGZsdXNoKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNodW5rcyxcbiAgICAgIGRhdGFMZW5ndGhcbiAgICB9ID0gdGhpcztcbiAgICBsZXQgcmVzdWx0O1xuICAgIGlmICghY2h1bmtzLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIG5ldyBVaW50OEFycmF5KDApO1xuICAgIH0gZWxzZSBpZiAoY2h1bmtzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgcmVzdWx0ID0gY2h1bmtzWzBdO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXN1bHQgPSBjb25jYXRVaW50OEFycmF5cyhjaHVua3MsIGRhdGFMZW5ndGgpO1xuICAgIH1cbiAgICB0aGlzLnJlc2V0KCk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuICByZXNldCgpIHtcbiAgICB0aGlzLmNodW5rcy5sZW5ndGggPSAwO1xuICAgIHRoaXMuZGF0YUxlbmd0aCA9IDA7XG4gIH1cbn1cbmZ1bmN0aW9uIGNvbmNhdFVpbnQ4QXJyYXlzKGNodW5rcywgZGF0YUxlbmd0aCkge1xuICBjb25zdCByZXN1bHQgPSBuZXcgVWludDhBcnJheShkYXRhTGVuZ3RoKTtcbiAgbGV0IG9mZnNldCA9IDA7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgY2h1bmtzLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3QgY2h1bmsgPSBjaHVua3NbaV07XG4gICAgcmVzdWx0LnNldChjaHVuaywgb2Zmc2V0KTtcbiAgICBvZmZzZXQgKz0gY2h1bmsubGVuZ3RoO1xuICB9XG4gIHJldHVybiByZXN1bHQ7XG59XG5cbi8vIGVuc3VyZSB0aGUgd29ya2VyIGVuZHMgdXAgaW4gdGhlIGJ1bmRsZVxuLy8gSWYgdGhlIHdvcmtlciBzaG91bGQgbm90IGJlIGluY2x1ZGVkIHRoaXMgZ2V0cyBhbGlhc2VkIHRvIGVtcHR5LmpzXG5mdW5jdGlvbiBoYXNVTURXb3JrZXIoKSB7XG4gIHJldHVybiB0eXBlb2YgX19ITFNfV09SS0VSX0JVTkRMRV9fID09PSAnZnVuY3Rpb24nO1xufVxuZnVuY3Rpb24gaW5qZWN0V29ya2VyKCkge1xuICBjb25zdCBibG9iID0gbmV3IHNlbGYuQmxvYihbYHZhciBleHBvcnRzPXt9O3ZhciBtb2R1bGU9e2V4cG9ydHM6ZXhwb3J0c307ZnVuY3Rpb24gZGVmaW5lKGYpe2YoKX07ZGVmaW5lLmFtZD10cnVlOygke19fSExTX1dPUktFUl9CVU5ETEVfXy50b1N0cmluZygpfSkodHJ1ZSk7YF0sIHtcbiAgICB0eXBlOiAndGV4dC9qYXZhc2NyaXB0J1xuICB9KTtcbiAgY29uc3Qgb2JqZWN0VVJMID0gc2VsZi5VUkwuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuICBjb25zdCB3b3JrZXIgPSBuZXcgc2VsZi5Xb3JrZXIob2JqZWN0VVJMKTtcbiAgcmV0dXJuIHtcbiAgICB3b3JrZXIsXG4gICAgb2JqZWN0VVJMXG4gIH07XG59XG5mdW5jdGlvbiBsb2FkV29ya2VyKHBhdGgpIHtcbiAgY29uc3Qgc2NyaXB0VVJMID0gbmV3IHNlbGYuVVJMKHBhdGgsIHNlbGYubG9jYXRpb24uaHJlZikuaHJlZjtcbiAgY29uc3Qgd29ya2VyID0gbmV3IHNlbGYuV29ya2VyKHNjcmlwdFVSTCk7XG4gIHJldHVybiB7XG4gICAgd29ya2VyLFxuICAgIHNjcmlwdFVSTFxuICB9O1xufVxuXG5mdW5jdGlvbiBkdW1teVRyYWNrKHR5cGUgPSAnJywgaW5wdXRUaW1lU2NhbGUgPSA5MDAwMCkge1xuICByZXR1cm4ge1xuICAgIHR5cGUsXG4gICAgaWQ6IC0xLFxuICAgIHBpZDogLTEsXG4gICAgaW5wdXRUaW1lU2NhbGUsXG4gICAgc2VxdWVuY2VOdW1iZXI6IC0xLFxuICAgIHNhbXBsZXM6IFtdLFxuICAgIGRyb3BwZWQ6IDBcbiAgfTtcbn1cblxuY2xhc3MgQmFzZUF1ZGlvRGVtdXhlciB7XG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMuX2F1ZGlvVHJhY2sgPSB2b2lkIDA7XG4gICAgdGhpcy5faWQzVHJhY2sgPSB2b2lkIDA7XG4gICAgdGhpcy5mcmFtZUluZGV4ID0gMDtcbiAgICB0aGlzLmNhY2hlZERhdGEgPSBudWxsO1xuICAgIHRoaXMuYmFzZVBUUyA9IG51bGw7XG4gICAgdGhpcy5pbml0UFRTID0gbnVsbDtcbiAgICB0aGlzLmxhc3RQVFMgPSBudWxsO1xuICB9XG4gIHJlc2V0SW5pdFNlZ21lbnQoaW5pdFNlZ21lbnQsIGF1ZGlvQ29kZWMsIHZpZGVvQ29kZWMsIHRyYWNrRHVyYXRpb24pIHtcbiAgICB0aGlzLl9pZDNUcmFjayA9IHtcbiAgICAgIHR5cGU6ICdpZDMnLFxuICAgICAgaWQ6IDMsXG4gICAgICBwaWQ6IC0xLFxuICAgICAgaW5wdXRUaW1lU2NhbGU6IDkwMDAwLFxuICAgICAgc2VxdWVuY2VOdW1iZXI6IDAsXG4gICAgICBzYW1wbGVzOiBbXSxcbiAgICAgIGRyb3BwZWQ6IDBcbiAgICB9O1xuICB9XG4gIHJlc2V0VGltZVN0YW1wKGRlYXVsdFRpbWVzdGFtcCkge1xuICAgIHRoaXMuaW5pdFBUUyA9IGRlYXVsdFRpbWVzdGFtcDtcbiAgICB0aGlzLnJlc2V0Q29udGlndWl0eSgpO1xuICB9XG4gIHJlc2V0Q29udGlndWl0eSgpIHtcbiAgICB0aGlzLmJhc2VQVFMgPSBudWxsO1xuICAgIHRoaXMubGFzdFBUUyA9IG51bGw7XG4gICAgdGhpcy5mcmFtZUluZGV4ID0gMDtcbiAgfVxuICBjYW5QYXJzZShkYXRhLCBvZmZzZXQpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgYXBwZW5kRnJhbWUodHJhY2ssIGRhdGEsIG9mZnNldCkge31cblxuICAvLyBmZWVkIGluY29taW5nIGRhdGEgdG8gdGhlIGZyb250IG9mIHRoZSBwYXJzaW5nIHBpcGVsaW5lXG4gIGRlbXV4KGRhdGEsIHRpbWVPZmZzZXQpIHtcbiAgICBpZiAodGhpcy5jYWNoZWREYXRhKSB7XG4gICAgICBkYXRhID0gYXBwZW5kVWludDhBcnJheSh0aGlzLmNhY2hlZERhdGEsIGRhdGEpO1xuICAgICAgdGhpcy5jYWNoZWREYXRhID0gbnVsbDtcbiAgICB9XG4gICAgbGV0IGlkM0RhdGEgPSBnZXRJRDNEYXRhKGRhdGEsIDApO1xuICAgIGxldCBvZmZzZXQgPSBpZDNEYXRhID8gaWQzRGF0YS5sZW5ndGggOiAwO1xuICAgIGxldCBsYXN0RGF0YUluZGV4O1xuICAgIGNvbnN0IHRyYWNrID0gdGhpcy5fYXVkaW9UcmFjaztcbiAgICBjb25zdCBpZDNUcmFjayA9IHRoaXMuX2lkM1RyYWNrO1xuICAgIGNvbnN0IHRpbWVzdGFtcCA9IGlkM0RhdGEgPyBnZXRUaW1lU3RhbXAoaWQzRGF0YSkgOiB1bmRlZmluZWQ7XG4gICAgY29uc3QgbGVuZ3RoID0gZGF0YS5sZW5ndGg7XG4gICAgaWYgKHRoaXMuYmFzZVBUUyA9PT0gbnVsbCB8fCB0aGlzLmZyYW1lSW5kZXggPT09IDAgJiYgaXNGaW5pdGVOdW1iZXIodGltZXN0YW1wKSkge1xuICAgICAgdGhpcy5iYXNlUFRTID0gaW5pdFBUU0ZuKHRpbWVzdGFtcCwgdGltZU9mZnNldCwgdGhpcy5pbml0UFRTKTtcbiAgICAgIHRoaXMubGFzdFBUUyA9IHRoaXMuYmFzZVBUUztcbiAgICB9XG4gICAgaWYgKHRoaXMubGFzdFBUUyA9PT0gbnVsbCkge1xuICAgICAgdGhpcy5sYXN0UFRTID0gdGhpcy5iYXNlUFRTO1xuICAgIH1cblxuICAgIC8vIG1vcmUgZXhwcmVzc2l2ZSB0aGFuIGFsdGVybmF0aXZlOiBpZDNEYXRhPy5sZW5ndGhcbiAgICBpZiAoaWQzRGF0YSAmJiBpZDNEYXRhLmxlbmd0aCA+IDApIHtcbiAgICAgIGlkM1RyYWNrLnNhbXBsZXMucHVzaCh7XG4gICAgICAgIHB0czogdGhpcy5sYXN0UFRTLFxuICAgICAgICBkdHM6IHRoaXMubGFzdFBUUyxcbiAgICAgICAgZGF0YTogaWQzRGF0YSxcbiAgICAgICAgdHlwZTogTWV0YWRhdGFTY2hlbWEuYXVkaW9JZDMsXG4gICAgICAgIGR1cmF0aW9uOiBOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFlcbiAgICAgIH0pO1xuICAgIH1cbiAgICB3aGlsZSAob2Zmc2V0IDwgbGVuZ3RoKSB7XG4gICAgICBpZiAodGhpcy5jYW5QYXJzZShkYXRhLCBvZmZzZXQpKSB7XG4gICAgICAgIGNvbnN0IGZyYW1lID0gdGhpcy5hcHBlbmRGcmFtZSh0cmFjaywgZGF0YSwgb2Zmc2V0KTtcbiAgICAgICAgaWYgKGZyYW1lKSB7XG4gICAgICAgICAgdGhpcy5mcmFtZUluZGV4Kys7XG4gICAgICAgICAgdGhpcy5sYXN0UFRTID0gZnJhbWUuc2FtcGxlLnB0cztcbiAgICAgICAgICBvZmZzZXQgKz0gZnJhbWUubGVuZ3RoO1xuICAgICAgICAgIGxhc3REYXRhSW5kZXggPSBvZmZzZXQ7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgb2Zmc2V0ID0gbGVuZ3RoO1xuICAgICAgICB9XG4gICAgICB9IGVsc2UgaWYgKGNhblBhcnNlJDIoZGF0YSwgb2Zmc2V0KSkge1xuICAgICAgICAvLyBhZnRlciBhIElEMy5jYW5QYXJzZSwgYSBjYWxsIHRvIElEMy5nZXRJRDNEYXRhICpzaG91bGQqIGFsd2F5cyByZXR1cm5zIHNvbWUgZGF0YVxuICAgICAgICBpZDNEYXRhID0gZ2V0SUQzRGF0YShkYXRhLCBvZmZzZXQpO1xuICAgICAgICBpZDNUcmFjay5zYW1wbGVzLnB1c2goe1xuICAgICAgICAgIHB0czogdGhpcy5sYXN0UFRTLFxuICAgICAgICAgIGR0czogdGhpcy5sYXN0UFRTLFxuICAgICAgICAgIGRhdGE6IGlkM0RhdGEsXG4gICAgICAgICAgdHlwZTogTWV0YWRhdGFTY2hlbWEuYXVkaW9JZDMsXG4gICAgICAgICAgZHVyYXRpb246IE51bWJlci5QT1NJVElWRV9JTkZJTklUWVxuICAgICAgICB9KTtcbiAgICAgICAgb2Zmc2V0ICs9IGlkM0RhdGEubGVuZ3RoO1xuICAgICAgICBsYXN0RGF0YUluZGV4ID0gb2Zmc2V0O1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgb2Zmc2V0Kys7XG4gICAgICB9XG4gICAgICBpZiAob2Zmc2V0ID09PSBsZW5ndGggJiYgbGFzdERhdGFJbmRleCAhPT0gbGVuZ3RoKSB7XG4gICAgICAgIGNvbnN0IHBhcnRpYWxEYXRhID0gc2xpY2VVaW50OChkYXRhLCBsYXN0RGF0YUluZGV4KTtcbiAgICAgICAgaWYgKHRoaXMuY2FjaGVkRGF0YSkge1xuICAgICAgICAgIHRoaXMuY2FjaGVkRGF0YSA9IGFwcGVuZFVpbnQ4QXJyYXkodGhpcy5jYWNoZWREYXRhLCBwYXJ0aWFsRGF0YSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdGhpcy5jYWNoZWREYXRhID0gcGFydGlhbERhdGE7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgIGF1ZGlvVHJhY2s6IHRyYWNrLFxuICAgICAgdmlkZW9UcmFjazogZHVtbXlUcmFjaygpLFxuICAgICAgaWQzVHJhY2ssXG4gICAgICB0ZXh0VHJhY2s6IGR1bW15VHJhY2soKVxuICAgIH07XG4gIH1cbiAgZGVtdXhTYW1wbGVBZXMoZGF0YSwga2V5RGF0YSwgdGltZU9mZnNldCkge1xuICAgIHJldHVybiBQcm9taXNlLnJlamVjdChuZXcgRXJyb3IoYFske3RoaXN9XSBUaGlzIGRlbXV4ZXIgZG9lcyBub3Qgc3VwcG9ydCBTYW1wbGUtQUVTIGRlY3J5cHRpb25gKSk7XG4gIH1cbiAgZmx1c2godGltZU9mZnNldCkge1xuICAgIC8vIFBhcnNlIGNhY2hlIGluIGNhc2Ugb2YgcmVtYWluaW5nIGZyYW1lcy5cbiAgICBjb25zdCBjYWNoZWREYXRhID0gdGhpcy5jYWNoZWREYXRhO1xuICAgIGlmIChjYWNoZWREYXRhKSB7XG4gICAgICB0aGlzLmNhY2hlZERhdGEgPSBudWxsO1xuICAgICAgdGhpcy5kZW11eChjYWNoZWREYXRhLCAwKTtcbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgIGF1ZGlvVHJhY2s6IHRoaXMuX2F1ZGlvVHJhY2ssXG4gICAgICB2aWRlb1RyYWNrOiBkdW1teVRyYWNrKCksXG4gICAgICBpZDNUcmFjazogdGhpcy5faWQzVHJhY2ssXG4gICAgICB0ZXh0VHJhY2s6IGR1bW15VHJhY2soKVxuICAgIH07XG4gIH1cbiAgZGVzdHJveSgpIHt9XG59XG5cbi8qKlxuICogSW5pdGlhbGl6ZSBQVFNcbiAqIDxwPlxuICogICAgdXNlIHRpbWVzdGFtcCB1bmxlc3MgaXQgaXMgdW5kZWZpbmVkLCBOYU4gb3IgSW5maW5pdHlcbiAqIDwvcD5cbiAqL1xuY29uc3QgaW5pdFBUU0ZuID0gKHRpbWVzdGFtcCwgdGltZU9mZnNldCwgaW5pdFBUUykgPT4ge1xuICBpZiAoaXNGaW5pdGVOdW1iZXIodGltZXN0YW1wKSkge1xuICAgIHJldHVybiB0aW1lc3RhbXAgKiA5MDtcbiAgfVxuICBjb25zdCBpbml0OTBrSHogPSBpbml0UFRTID8gaW5pdFBUUy5iYXNlVGltZSAqIDkwMDAwIC8gaW5pdFBUUy50aW1lc2NhbGUgOiAwO1xuICByZXR1cm4gdGltZU9mZnNldCAqIDkwMDAwICsgaW5pdDkwa0h6O1xufTtcblxuLyoqXG4gKiBBRFRTIHBhcnNlciBoZWxwZXJcbiAqIEBsaW5rIGh0dHBzOi8vd2lraS5tdWx0aW1lZGlhLmN4L2luZGV4LnBocD90aXRsZT1BRFRTXG4gKi9cbmZ1bmN0aW9uIGdldEF1ZGlvQ29uZmlnKG9ic2VydmVyLCBkYXRhLCBvZmZzZXQsIGF1ZGlvQ29kZWMpIHtcbiAgbGV0IGFkdHNPYmplY3RUeXBlO1xuICBsZXQgYWR0c0V4dGVuc2lvblNhbXBsaW5nSW5kZXg7XG4gIGxldCBhZHRzQ2hhbm5lbENvbmZpZztcbiAgbGV0IGNvbmZpZztcbiAgY29uc3QgdXNlckFnZW50ID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpO1xuICBjb25zdCBtYW5pZmVzdENvZGVjID0gYXVkaW9Db2RlYztcbiAgY29uc3QgYWR0c1NhbXBsaW5nUmF0ZXMgPSBbOTYwMDAsIDg4MjAwLCA2NDAwMCwgNDgwMDAsIDQ0MTAwLCAzMjAwMCwgMjQwMDAsIDIyMDUwLCAxNjAwMCwgMTIwMDAsIDExMDI1LCA4MDAwLCA3MzUwXTtcbiAgLy8gYnl0ZSAyXG4gIGFkdHNPYmplY3RUeXBlID0gKChkYXRhW29mZnNldCArIDJdICYgMHhjMCkgPj4+IDYpICsgMTtcbiAgY29uc3QgYWR0c1NhbXBsaW5nSW5kZXggPSAoZGF0YVtvZmZzZXQgKyAyXSAmIDB4M2MpID4+PiAyO1xuICBpZiAoYWR0c1NhbXBsaW5nSW5kZXggPiBhZHRzU2FtcGxpbmdSYXRlcy5sZW5ndGggLSAxKSB7XG4gICAgY29uc3QgZXJyb3IgPSBuZXcgRXJyb3IoYGludmFsaWQgQURUUyBzYW1wbGluZyBpbmRleDoke2FkdHNTYW1wbGluZ0luZGV4fWApO1xuICAgIG9ic2VydmVyLmVtaXQoRXZlbnRzLkVSUk9SLCBFdmVudHMuRVJST1IsIHtcbiAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuRlJBR19QQVJTSU5HX0VSUk9SLFxuICAgICAgZmF0YWw6IHRydWUsXG4gICAgICBlcnJvcixcbiAgICAgIHJlYXNvbjogZXJyb3IubWVzc2FnZVxuICAgIH0pO1xuICAgIHJldHVybjtcbiAgfVxuICBhZHRzQ2hhbm5lbENvbmZpZyA9IChkYXRhW29mZnNldCArIDJdICYgMHgwMSkgPDwgMjtcbiAgLy8gYnl0ZSAzXG4gIGFkdHNDaGFubmVsQ29uZmlnIHw9IChkYXRhW29mZnNldCArIDNdICYgMHhjMCkgPj4+IDY7XG4gIGxvZ2dlci5sb2coYG1hbmlmZXN0IGNvZGVjOiR7YXVkaW9Db2RlY30sIEFEVFMgdHlwZToke2FkdHNPYmplY3RUeXBlfSwgc2FtcGxpbmdJbmRleDoke2FkdHNTYW1wbGluZ0luZGV4fWApO1xuICAvLyBmaXJlZm94OiBmcmVxIGxlc3MgdGhhbiAyNGtIeiA9IEFBQyBTQlIgKEhFLUFBQylcbiAgaWYgKC9maXJlZm94L2kudGVzdCh1c2VyQWdlbnQpKSB7XG4gICAgaWYgKGFkdHNTYW1wbGluZ0luZGV4ID49IDYpIHtcbiAgICAgIGFkdHNPYmplY3RUeXBlID0gNTtcbiAgICAgIGNvbmZpZyA9IG5ldyBBcnJheSg0KTtcbiAgICAgIC8vIEhFLUFBQyB1c2VzIFNCUiAoU3BlY3RyYWwgQmFuZCBSZXBsaWNhdGlvbikgLCBoaWdoIGZyZXF1ZW5jaWVzIGFyZSBjb25zdHJ1Y3RlZCBmcm9tIGxvdyBmcmVxdWVuY2llc1xuICAgICAgLy8gdGhlcmUgaXMgYSBmYWN0b3IgMiBiZXR3ZWVuIGZyYW1lIHNhbXBsZSByYXRlIGFuZCBvdXRwdXQgc2FtcGxlIHJhdGVcbiAgICAgIC8vIG11bHRpcGx5IGZyZXF1ZW5jeSBieSAyIChzZWUgdGFibGUgYmVsb3csIGVxdWl2YWxlbnQgdG8gc3Vic3RyYWN0IDMpXG4gICAgICBhZHRzRXh0ZW5zaW9uU2FtcGxpbmdJbmRleCA9IGFkdHNTYW1wbGluZ0luZGV4IC0gMztcbiAgICB9IGVsc2Uge1xuICAgICAgYWR0c09iamVjdFR5cGUgPSAyO1xuICAgICAgY29uZmlnID0gbmV3IEFycmF5KDIpO1xuICAgICAgYWR0c0V4dGVuc2lvblNhbXBsaW5nSW5kZXggPSBhZHRzU2FtcGxpbmdJbmRleDtcbiAgICB9XG4gICAgLy8gQW5kcm9pZCA6IGFsd2F5cyB1c2UgQUFDXG4gIH0gZWxzZSBpZiAodXNlckFnZW50LmluZGV4T2YoJ2FuZHJvaWQnKSAhPT0gLTEpIHtcbiAgICBhZHRzT2JqZWN0VHlwZSA9IDI7XG4gICAgY29uZmlnID0gbmV3IEFycmF5KDIpO1xuICAgIGFkdHNFeHRlbnNpb25TYW1wbGluZ0luZGV4ID0gYWR0c1NhbXBsaW5nSW5kZXg7XG4gIH0gZWxzZSB7XG4gICAgLyogIGZvciBvdGhlciBicm93c2VycyAoQ2hyb21lL1ZpdmFsZGkvT3BlcmEgLi4uKVxuICAgICAgICBhbHdheXMgZm9yY2UgYXVkaW8gdHlwZSB0byBiZSBIRS1BQUMgU0JSLCBhcyBzb21lIGJyb3dzZXJzIGRvIG5vdCBzdXBwb3J0IGF1ZGlvIGNvZGVjIHN3aXRjaCBwcm9wZXJseSAobGlrZSBDaHJvbWUgLi4uKVxuICAgICovXG4gICAgYWR0c09iamVjdFR5cGUgPSA1O1xuICAgIGNvbmZpZyA9IG5ldyBBcnJheSg0KTtcbiAgICAvLyBpZiAobWFuaWZlc3QgY29kZWMgaXMgSEUtQUFDIG9yIEhFLUFBQ3YyKSBPUiAobWFuaWZlc3QgY29kZWMgbm90IHNwZWNpZmllZCBBTkQgZnJlcXVlbmN5IGxlc3MgdGhhbiAyNGtIeilcbiAgICBpZiAoYXVkaW9Db2RlYyAmJiAoYXVkaW9Db2RlYy5pbmRleE9mKCdtcDRhLjQwLjI5JykgIT09IC0xIHx8IGF1ZGlvQ29kZWMuaW5kZXhPZignbXA0YS40MC41JykgIT09IC0xKSB8fCAhYXVkaW9Db2RlYyAmJiBhZHRzU2FtcGxpbmdJbmRleCA+PSA2KSB7XG4gICAgICAvLyBIRS1BQUMgdXNlcyBTQlIgKFNwZWN0cmFsIEJhbmQgUmVwbGljYXRpb24pICwgaGlnaCBmcmVxdWVuY2llcyBhcmUgY29uc3RydWN0ZWQgZnJvbSBsb3cgZnJlcXVlbmNpZXNcbiAgICAgIC8vIHRoZXJlIGlzIGEgZmFjdG9yIDIgYmV0d2VlbiBmcmFtZSBzYW1wbGUgcmF0ZSBhbmQgb3V0cHV0IHNhbXBsZSByYXRlXG4gICAgICAvLyBtdWx0aXBseSBmcmVxdWVuY3kgYnkgMiAoc2VlIHRhYmxlIGJlbG93LCBlcXVpdmFsZW50IHRvIHN1YnN0cmFjdCAzKVxuICAgICAgYWR0c0V4dGVuc2lvblNhbXBsaW5nSW5kZXggPSBhZHRzU2FtcGxpbmdJbmRleCAtIDM7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIGlmIChtYW5pZmVzdCBjb2RlYyBpcyBBQUMpIEFORCAoZnJlcXVlbmN5IGxlc3MgdGhhbiAyNGtIeiBBTkQgbmIgY2hhbm5lbCBpcyAxKSBPUiAobWFuaWZlc3QgY29kZWMgbm90IHNwZWNpZmllZCBhbmQgbW9ubyBhdWRpbylcbiAgICAgIC8vIENocm9tZSBmYWlscyB0byBwbGF5IGJhY2sgd2l0aCBsb3cgZnJlcXVlbmN5IEFBQyBMQyBtb25vIHdoZW4gaW5pdGlhbGl6ZWQgd2l0aCBIRS1BQUMuICBUaGlzIGlzIG5vdCBhIHByb2JsZW0gd2l0aCBzdGVyZW8uXG4gICAgICBpZiAoYXVkaW9Db2RlYyAmJiBhdWRpb0NvZGVjLmluZGV4T2YoJ21wNGEuNDAuMicpICE9PSAtMSAmJiAoYWR0c1NhbXBsaW5nSW5kZXggPj0gNiAmJiBhZHRzQ2hhbm5lbENvbmZpZyA9PT0gMSB8fCAvdml2YWxkaS9pLnRlc3QodXNlckFnZW50KSkgfHwgIWF1ZGlvQ29kZWMgJiYgYWR0c0NoYW5uZWxDb25maWcgPT09IDEpIHtcbiAgICAgICAgYWR0c09iamVjdFR5cGUgPSAyO1xuICAgICAgICBjb25maWcgPSBuZXcgQXJyYXkoMik7XG4gICAgICB9XG4gICAgICBhZHRzRXh0ZW5zaW9uU2FtcGxpbmdJbmRleCA9IGFkdHNTYW1wbGluZ0luZGV4O1xuICAgIH1cbiAgfVxuICAvKiByZWZlciB0byBodHRwOi8vd2lraS5tdWx0aW1lZGlhLmN4L2luZGV4LnBocD90aXRsZT1NUEVHLTRfQXVkaW8jQXVkaW9fU3BlY2lmaWNfQ29uZmlnXG4gICAgICBJU08gMTQ0OTYtMyAoQUFDKS5wZGYgLSBUYWJsZSAxLjEzIOKAlCBTeW50YXggb2YgQXVkaW9TcGVjaWZpY0NvbmZpZygpXG4gICAgQXVkaW8gUHJvZmlsZSAvIEF1ZGlvIE9iamVjdCBUeXBlXG4gICAgMDogTnVsbFxuICAgIDE6IEFBQyBNYWluXG4gICAgMjogQUFDIExDIChMb3cgQ29tcGxleGl0eSlcbiAgICAzOiBBQUMgU1NSIChTY2FsYWJsZSBTYW1wbGUgUmF0ZSlcbiAgICA0OiBBQUMgTFRQIChMb25nIFRlcm0gUHJlZGljdGlvbilcbiAgICA1OiBTQlIgKFNwZWN0cmFsIEJhbmQgUmVwbGljYXRpb24pXG4gICAgNjogQUFDIFNjYWxhYmxlXG4gICBzYW1wbGluZyBmcmVxXG4gICAgMDogOTYwMDAgSHpcbiAgICAxOiA4ODIwMCBIelxuICAgIDI6IDY0MDAwIEh6XG4gICAgMzogNDgwMDAgSHpcbiAgICA0OiA0NDEwMCBIelxuICAgIDU6IDMyMDAwIEh6XG4gICAgNjogMjQwMDAgSHpcbiAgICA3OiAyMjA1MCBIelxuICAgIDg6IDE2MDAwIEh6XG4gICAgOTogMTIwMDAgSHpcbiAgICAxMDogMTEwMjUgSHpcbiAgICAxMTogODAwMCBIelxuICAgIDEyOiA3MzUwIEh6XG4gICAgMTM6IFJlc2VydmVkXG4gICAgMTQ6IFJlc2VydmVkXG4gICAgMTU6IGZyZXF1ZW5jeSBpcyB3cml0dGVuIGV4cGxpY3RseVxuICAgIENoYW5uZWwgQ29uZmlndXJhdGlvbnNcbiAgICBUaGVzZSBhcmUgdGhlIGNoYW5uZWwgY29uZmlndXJhdGlvbnM6XG4gICAgMDogRGVmaW5lZCBpbiBBT1QgU3BlY2lmYyBDb25maWdcbiAgICAxOiAxIGNoYW5uZWw6IGZyb250LWNlbnRlclxuICAgIDI6IDIgY2hhbm5lbHM6IGZyb250LWxlZnQsIGZyb250LXJpZ2h0XG4gICovXG4gIC8vIGF1ZGlvT2JqZWN0VHlwZSA9IHByb2ZpbGUgPT4gcHJvZmlsZSwgdGhlIE1QRUctNCBBdWRpbyBPYmplY3QgVHlwZSBtaW51cyAxXG4gIGNvbmZpZ1swXSA9IGFkdHNPYmplY3RUeXBlIDw8IDM7XG4gIC8vIHNhbXBsaW5nRnJlcXVlbmN5SW5kZXhcbiAgY29uZmlnWzBdIHw9IChhZHRzU2FtcGxpbmdJbmRleCAmIDB4MGUpID4+IDE7XG4gIGNvbmZpZ1sxXSB8PSAoYWR0c1NhbXBsaW5nSW5kZXggJiAweDAxKSA8PCA3O1xuICAvLyBjaGFubmVsQ29uZmlndXJhdGlvblxuICBjb25maWdbMV0gfD0gYWR0c0NoYW5uZWxDb25maWcgPDwgMztcbiAgaWYgKGFkdHNPYmplY3RUeXBlID09PSA1KSB7XG4gICAgLy8gYWR0c0V4dGVuc2lvblNhbXBsaW5nSW5kZXhcbiAgICBjb25maWdbMV0gfD0gKGFkdHNFeHRlbnNpb25TYW1wbGluZ0luZGV4ICYgMHgwZSkgPj4gMTtcbiAgICBjb25maWdbMl0gPSAoYWR0c0V4dGVuc2lvblNhbXBsaW5nSW5kZXggJiAweDAxKSA8PCA3O1xuICAgIC8vIGFkdHNPYmplY3RUeXBlIChmb3JjZSB0byAyLCBjaHJvbWUgaXMgY2hlY2tpbmcgdGhhdCBvYmplY3QgdHlwZSBpcyBsZXNzIHRoYW4gNSA/Pz9cbiAgICAvLyAgICBodHRwczovL2Nocm9taXVtLmdvb2dsZXNvdXJjZS5jb20vY2hyb21pdW0vc3JjLmdpdC8rL21hc3Rlci9tZWRpYS9mb3JtYXRzL21wNC9hYWMuY2NcbiAgICBjb25maWdbMl0gfD0gMiA8PCAyO1xuICAgIGNvbmZpZ1szXSA9IDA7XG4gIH1cbiAgcmV0dXJuIHtcbiAgICBjb25maWcsXG4gICAgc2FtcGxlcmF0ZTogYWR0c1NhbXBsaW5nUmF0ZXNbYWR0c1NhbXBsaW5nSW5kZXhdLFxuICAgIGNoYW5uZWxDb3VudDogYWR0c0NoYW5uZWxDb25maWcsXG4gICAgY29kZWM6ICdtcDRhLjQwLicgKyBhZHRzT2JqZWN0VHlwZSxcbiAgICBtYW5pZmVzdENvZGVjXG4gIH07XG59XG5mdW5jdGlvbiBpc0hlYWRlclBhdHRlcm4kMShkYXRhLCBvZmZzZXQpIHtcbiAgcmV0dXJuIGRhdGFbb2Zmc2V0XSA9PT0gMHhmZiAmJiAoZGF0YVtvZmZzZXQgKyAxXSAmIDB4ZjYpID09PSAweGYwO1xufVxuZnVuY3Rpb24gZ2V0SGVhZGVyTGVuZ3RoKGRhdGEsIG9mZnNldCkge1xuICByZXR1cm4gZGF0YVtvZmZzZXQgKyAxXSAmIDB4MDEgPyA3IDogOTtcbn1cbmZ1bmN0aW9uIGdldEZ1bGxGcmFtZUxlbmd0aChkYXRhLCBvZmZzZXQpIHtcbiAgcmV0dXJuIChkYXRhW29mZnNldCArIDNdICYgMHgwMykgPDwgMTEgfCBkYXRhW29mZnNldCArIDRdIDw8IDMgfCAoZGF0YVtvZmZzZXQgKyA1XSAmIDB4ZTApID4+PiA1O1xufVxuZnVuY3Rpb24gY2FuR2V0RnJhbWVMZW5ndGgoZGF0YSwgb2Zmc2V0KSB7XG4gIHJldHVybiBvZmZzZXQgKyA1IDwgZGF0YS5sZW5ndGg7XG59XG5mdW5jdGlvbiBpc0hlYWRlciQxKGRhdGEsIG9mZnNldCkge1xuICAvLyBMb29rIGZvciBBRFRTIGhlYWRlciB8IDExMTEgMTExMSB8IDExMTEgWDAwWCB8IHdoZXJlIFggY2FuIGJlIGVpdGhlciAwIG9yIDFcbiAgLy8gTGF5ZXIgYml0cyAocG9zaXRpb24gMTQgYW5kIDE1KSBpbiBoZWFkZXIgc2hvdWxkIGJlIGFsd2F5cyAwIGZvciBBRFRTXG4gIC8vIE1vcmUgaW5mbyBodHRwczovL3dpa2kubXVsdGltZWRpYS5jeC9pbmRleC5waHA/dGl0bGU9QURUU1xuICByZXR1cm4gb2Zmc2V0ICsgMSA8IGRhdGEubGVuZ3RoICYmIGlzSGVhZGVyUGF0dGVybiQxKGRhdGEsIG9mZnNldCk7XG59XG5mdW5jdGlvbiBjYW5QYXJzZSQxKGRhdGEsIG9mZnNldCkge1xuICByZXR1cm4gY2FuR2V0RnJhbWVMZW5ndGgoZGF0YSwgb2Zmc2V0KSAmJiBpc0hlYWRlclBhdHRlcm4kMShkYXRhLCBvZmZzZXQpICYmIGdldEZ1bGxGcmFtZUxlbmd0aChkYXRhLCBvZmZzZXQpIDw9IGRhdGEubGVuZ3RoIC0gb2Zmc2V0O1xufVxuZnVuY3Rpb24gcHJvYmUkMShkYXRhLCBvZmZzZXQpIHtcbiAgLy8gc2FtZSBhcyBpc0hlYWRlciBidXQgd2UgYWxzbyBjaGVjayB0aGF0IEFEVFMgZnJhbWUgZm9sbG93cyBsYXN0IEFEVFMgZnJhbWVcbiAgLy8gb3IgZW5kIG9mIGRhdGEgaXMgcmVhY2hlZFxuICBpZiAoaXNIZWFkZXIkMShkYXRhLCBvZmZzZXQpKSB7XG4gICAgLy8gQURUUyBoZWFkZXIgTGVuZ3RoXG4gICAgY29uc3QgaGVhZGVyTGVuZ3RoID0gZ2V0SGVhZGVyTGVuZ3RoKGRhdGEsIG9mZnNldCk7XG4gICAgaWYgKG9mZnNldCArIGhlYWRlckxlbmd0aCA+PSBkYXRhLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICAvLyBBRFRTIGZyYW1lIExlbmd0aFxuICAgIGNvbnN0IGZyYW1lTGVuZ3RoID0gZ2V0RnVsbEZyYW1lTGVuZ3RoKGRhdGEsIG9mZnNldCk7XG4gICAgaWYgKGZyYW1lTGVuZ3RoIDw9IGhlYWRlckxlbmd0aCkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBjb25zdCBuZXdPZmZzZXQgPSBvZmZzZXQgKyBmcmFtZUxlbmd0aDtcbiAgICByZXR1cm4gbmV3T2Zmc2V0ID09PSBkYXRhLmxlbmd0aCB8fCBpc0hlYWRlciQxKGRhdGEsIG5ld09mZnNldCk7XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuZnVuY3Rpb24gaW5pdFRyYWNrQ29uZmlnKHRyYWNrLCBvYnNlcnZlciwgZGF0YSwgb2Zmc2V0LCBhdWRpb0NvZGVjKSB7XG4gIGlmICghdHJhY2suc2FtcGxlcmF0ZSkge1xuICAgIGNvbnN0IGNvbmZpZyA9IGdldEF1ZGlvQ29uZmlnKG9ic2VydmVyLCBkYXRhLCBvZmZzZXQsIGF1ZGlvQ29kZWMpO1xuICAgIGlmICghY29uZmlnKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRyYWNrLmNvbmZpZyA9IGNvbmZpZy5jb25maWc7XG4gICAgdHJhY2suc2FtcGxlcmF0ZSA9IGNvbmZpZy5zYW1wbGVyYXRlO1xuICAgIHRyYWNrLmNoYW5uZWxDb3VudCA9IGNvbmZpZy5jaGFubmVsQ291bnQ7XG4gICAgdHJhY2suY29kZWMgPSBjb25maWcuY29kZWM7XG4gICAgdHJhY2subWFuaWZlc3RDb2RlYyA9IGNvbmZpZy5tYW5pZmVzdENvZGVjO1xuICAgIGxvZ2dlci5sb2coYHBhcnNlZCBjb2RlYzoke3RyYWNrLmNvZGVjfSwgcmF0ZToke2NvbmZpZy5zYW1wbGVyYXRlfSwgY2hhbm5lbHM6JHtjb25maWcuY2hhbm5lbENvdW50fWApO1xuICB9XG59XG5mdW5jdGlvbiBnZXRGcmFtZUR1cmF0aW9uKHNhbXBsZXJhdGUpIHtcbiAgcmV0dXJuIDEwMjQgKiA5MDAwMCAvIHNhbXBsZXJhdGU7XG59XG5mdW5jdGlvbiBwYXJzZUZyYW1lSGVhZGVyKGRhdGEsIG9mZnNldCkge1xuICAvLyBUaGUgcHJvdGVjdGlvbiBza2lwIGJpdCB0ZWxscyB1cyBpZiB3ZSBoYXZlIDIgYnl0ZXMgb2YgQ1JDIGRhdGEgYXQgdGhlIGVuZCBvZiB0aGUgQURUUyBoZWFkZXJcbiAgY29uc3QgaGVhZGVyTGVuZ3RoID0gZ2V0SGVhZGVyTGVuZ3RoKGRhdGEsIG9mZnNldCk7XG4gIGlmIChvZmZzZXQgKyBoZWFkZXJMZW5ndGggPD0gZGF0YS5sZW5ndGgpIHtcbiAgICAvLyByZXRyaWV2ZSBmcmFtZSBzaXplXG4gICAgY29uc3QgZnJhbWVMZW5ndGggPSBnZXRGdWxsRnJhbWVMZW5ndGgoZGF0YSwgb2Zmc2V0KSAtIGhlYWRlckxlbmd0aDtcbiAgICBpZiAoZnJhbWVMZW5ndGggPiAwKSB7XG4gICAgICAvLyBsb2dnZXIubG9nKGBBQUMgZnJhbWUsIG9mZnNldC9sZW5ndGgvdG90YWwvcHRzOiR7b2Zmc2V0K2hlYWRlckxlbmd0aH0vJHtmcmFtZUxlbmd0aH0vJHtkYXRhLmJ5dGVMZW5ndGh9YCk7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBoZWFkZXJMZW5ndGgsXG4gICAgICAgIGZyYW1lTGVuZ3RoXG4gICAgICB9O1xuICAgIH1cbiAgfVxufVxuZnVuY3Rpb24gYXBwZW5kRnJhbWUkMih0cmFjaywgZGF0YSwgb2Zmc2V0LCBwdHMsIGZyYW1lSW5kZXgpIHtcbiAgY29uc3QgZnJhbWVEdXJhdGlvbiA9IGdldEZyYW1lRHVyYXRpb24odHJhY2suc2FtcGxlcmF0ZSk7XG4gIGNvbnN0IHN0YW1wID0gcHRzICsgZnJhbWVJbmRleCAqIGZyYW1lRHVyYXRpb247XG4gIGNvbnN0IGhlYWRlciA9IHBhcnNlRnJhbWVIZWFkZXIoZGF0YSwgb2Zmc2V0KTtcbiAgbGV0IHVuaXQ7XG4gIGlmIChoZWFkZXIpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFtZUxlbmd0aCxcbiAgICAgIGhlYWRlckxlbmd0aFxuICAgIH0gPSBoZWFkZXI7XG4gICAgY29uc3QgX2xlbmd0aCA9IGhlYWRlckxlbmd0aCArIGZyYW1lTGVuZ3RoO1xuICAgIGNvbnN0IG1pc3NpbmcgPSBNYXRoLm1heCgwLCBvZmZzZXQgKyBfbGVuZ3RoIC0gZGF0YS5sZW5ndGgpO1xuICAgIC8vIGxvZ2dlci5sb2coYEFBQyBmcmFtZSAke2ZyYW1lSW5kZXh9LCBwdHM6JHtzdGFtcH0gbGVuZ3RoQG9mZnNldC90b3RhbDogJHtmcmFtZUxlbmd0aH1AJHtvZmZzZXQraGVhZGVyTGVuZ3RofS8ke2RhdGEuYnl0ZUxlbmd0aH0gbWlzc2luZzogJHttaXNzaW5nfWApO1xuICAgIGlmIChtaXNzaW5nKSB7XG4gICAgICB1bml0ID0gbmV3IFVpbnQ4QXJyYXkoX2xlbmd0aCAtIGhlYWRlckxlbmd0aCk7XG4gICAgICB1bml0LnNldChkYXRhLnN1YmFycmF5KG9mZnNldCArIGhlYWRlckxlbmd0aCwgZGF0YS5sZW5ndGgpLCAwKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdW5pdCA9IGRhdGEuc3ViYXJyYXkob2Zmc2V0ICsgaGVhZGVyTGVuZ3RoLCBvZmZzZXQgKyBfbGVuZ3RoKTtcbiAgICB9XG4gICAgY29uc3QgX3NhbXBsZSA9IHtcbiAgICAgIHVuaXQsXG4gICAgICBwdHM6IHN0YW1wXG4gICAgfTtcbiAgICBpZiAoIW1pc3NpbmcpIHtcbiAgICAgIHRyYWNrLnNhbXBsZXMucHVzaChfc2FtcGxlKTtcbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgIHNhbXBsZTogX3NhbXBsZSxcbiAgICAgIGxlbmd0aDogX2xlbmd0aCxcbiAgICAgIG1pc3NpbmdcbiAgICB9O1xuICB9XG4gIC8vIG92ZXJmbG93IGluY29tcGxldGUgaGVhZGVyXG4gIGNvbnN0IGxlbmd0aCA9IGRhdGEubGVuZ3RoIC0gb2Zmc2V0O1xuICB1bml0ID0gbmV3IFVpbnQ4QXJyYXkobGVuZ3RoKTtcbiAgdW5pdC5zZXQoZGF0YS5zdWJhcnJheShvZmZzZXQsIGRhdGEubGVuZ3RoKSwgMCk7XG4gIGNvbnN0IHNhbXBsZSA9IHtcbiAgICB1bml0LFxuICAgIHB0czogc3RhbXBcbiAgfTtcbiAgcmV0dXJuIHtcbiAgICBzYW1wbGUsXG4gICAgbGVuZ3RoLFxuICAgIG1pc3Npbmc6IC0xXG4gIH07XG59XG5cbi8qKlxuICogIE1QRUcgcGFyc2VyIGhlbHBlclxuICovXG5cbmxldCBjaHJvbWVWZXJzaW9uJDEgPSBudWxsO1xuY29uc3QgQml0cmF0ZXNNYXAgPSBbMzIsIDY0LCA5NiwgMTI4LCAxNjAsIDE5MiwgMjI0LCAyNTYsIDI4OCwgMzIwLCAzNTIsIDM4NCwgNDE2LCA0NDgsIDMyLCA0OCwgNTYsIDY0LCA4MCwgOTYsIDExMiwgMTI4LCAxNjAsIDE5MiwgMjI0LCAyNTYsIDMyMCwgMzg0LCAzMiwgNDAsIDQ4LCA1NiwgNjQsIDgwLCA5NiwgMTEyLCAxMjgsIDE2MCwgMTkyLCAyMjQsIDI1NiwgMzIwLCAzMiwgNDgsIDU2LCA2NCwgODAsIDk2LCAxMTIsIDEyOCwgMTQ0LCAxNjAsIDE3NiwgMTkyLCAyMjQsIDI1NiwgOCwgMTYsIDI0LCAzMiwgNDAsIDQ4LCA1NiwgNjQsIDgwLCA5NiwgMTEyLCAxMjgsIDE0NCwgMTYwXTtcbmNvbnN0IFNhbXBsaW5nUmF0ZU1hcCA9IFs0NDEwMCwgNDgwMDAsIDMyMDAwLCAyMjA1MCwgMjQwMDAsIDE2MDAwLCAxMTAyNSwgMTIwMDAsIDgwMDBdO1xuY29uc3QgU2FtcGxlc0NvZWZmaWNpZW50cyA9IFtcbi8vIE1QRUcgMi41XG5bMCxcbi8vIFJlc2VydmVkXG43Mixcbi8vIExheWVyM1xuMTQ0LFxuLy8gTGF5ZXIyXG4xMiAvLyBMYXllcjFcbl0sXG4vLyBSZXNlcnZlZFxuWzAsXG4vLyBSZXNlcnZlZFxuMCxcbi8vIExheWVyM1xuMCxcbi8vIExheWVyMlxuMCAvLyBMYXllcjFcbl0sXG4vLyBNUEVHIDJcblswLFxuLy8gUmVzZXJ2ZWRcbjcyLFxuLy8gTGF5ZXIzXG4xNDQsXG4vLyBMYXllcjJcbjEyIC8vIExheWVyMVxuXSxcbi8vIE1QRUcgMVxuWzAsXG4vLyBSZXNlcnZlZFxuMTQ0LFxuLy8gTGF5ZXIzXG4xNDQsXG4vLyBMYXllcjJcbjEyIC8vIExheWVyMVxuXV07XG5jb25zdCBCeXRlc0luU2xvdCA9IFswLFxuLy8gUmVzZXJ2ZWRcbjEsXG4vLyBMYXllcjNcbjEsXG4vLyBMYXllcjJcbjQgLy8gTGF5ZXIxXG5dO1xuZnVuY3Rpb24gYXBwZW5kRnJhbWUkMSh0cmFjaywgZGF0YSwgb2Zmc2V0LCBwdHMsIGZyYW1lSW5kZXgpIHtcbiAgLy8gVXNpbmcgaHR0cDovL3d3dy5kYXRhdm95YWdlLmNvbS9tcGdzY3JpcHQvbXBlZ2hkci5odG0gYXMgYSByZWZlcmVuY2VcbiAgaWYgKG9mZnNldCArIDI0ID4gZGF0YS5sZW5ndGgpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgY29uc3QgaGVhZGVyID0gcGFyc2VIZWFkZXIoZGF0YSwgb2Zmc2V0KTtcbiAgaWYgKGhlYWRlciAmJiBvZmZzZXQgKyBoZWFkZXIuZnJhbWVMZW5ndGggPD0gZGF0YS5sZW5ndGgpIHtcbiAgICBjb25zdCBmcmFtZUR1cmF0aW9uID0gaGVhZGVyLnNhbXBsZXNQZXJGcmFtZSAqIDkwMDAwIC8gaGVhZGVyLnNhbXBsZVJhdGU7XG4gICAgY29uc3Qgc3RhbXAgPSBwdHMgKyBmcmFtZUluZGV4ICogZnJhbWVEdXJhdGlvbjtcbiAgICBjb25zdCBzYW1wbGUgPSB7XG4gICAgICB1bml0OiBkYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgaGVhZGVyLmZyYW1lTGVuZ3RoKSxcbiAgICAgIHB0czogc3RhbXAsXG4gICAgICBkdHM6IHN0YW1wXG4gICAgfTtcbiAgICB0cmFjay5jb25maWcgPSBbXTtcbiAgICB0cmFjay5jaGFubmVsQ291bnQgPSBoZWFkZXIuY2hhbm5lbENvdW50O1xuICAgIHRyYWNrLnNhbXBsZXJhdGUgPSBoZWFkZXIuc2FtcGxlUmF0ZTtcbiAgICB0cmFjay5zYW1wbGVzLnB1c2goc2FtcGxlKTtcbiAgICByZXR1cm4ge1xuICAgICAgc2FtcGxlLFxuICAgICAgbGVuZ3RoOiBoZWFkZXIuZnJhbWVMZW5ndGgsXG4gICAgICBtaXNzaW5nOiAwXG4gICAgfTtcbiAgfVxufVxuZnVuY3Rpb24gcGFyc2VIZWFkZXIoZGF0YSwgb2Zmc2V0KSB7XG4gIGNvbnN0IG1wZWdWZXJzaW9uID0gZGF0YVtvZmZzZXQgKyAxXSA+PiAzICYgMztcbiAgY29uc3QgbXBlZ0xheWVyID0gZGF0YVtvZmZzZXQgKyAxXSA+PiAxICYgMztcbiAgY29uc3QgYml0UmF0ZUluZGV4ID0gZGF0YVtvZmZzZXQgKyAyXSA+PiA0ICYgMTU7XG4gIGNvbnN0IHNhbXBsZVJhdGVJbmRleCA9IGRhdGFbb2Zmc2V0ICsgMl0gPj4gMiAmIDM7XG4gIGlmIChtcGVnVmVyc2lvbiAhPT0gMSAmJiBiaXRSYXRlSW5kZXggIT09IDAgJiYgYml0UmF0ZUluZGV4ICE9PSAxNSAmJiBzYW1wbGVSYXRlSW5kZXggIT09IDMpIHtcbiAgICBjb25zdCBwYWRkaW5nQml0ID0gZGF0YVtvZmZzZXQgKyAyXSA+PiAxICYgMTtcbiAgICBjb25zdCBjaGFubmVsTW9kZSA9IGRhdGFbb2Zmc2V0ICsgM10gPj4gNjtcbiAgICBjb25zdCBjb2x1bW5JbkJpdHJhdGVzID0gbXBlZ1ZlcnNpb24gPT09IDMgPyAzIC0gbXBlZ0xheWVyIDogbXBlZ0xheWVyID09PSAzID8gMyA6IDQ7XG4gICAgY29uc3QgYml0UmF0ZSA9IEJpdHJhdGVzTWFwW2NvbHVtbkluQml0cmF0ZXMgKiAxNCArIGJpdFJhdGVJbmRleCAtIDFdICogMTAwMDtcbiAgICBjb25zdCBjb2x1bW5JblNhbXBsZVJhdGVzID0gbXBlZ1ZlcnNpb24gPT09IDMgPyAwIDogbXBlZ1ZlcnNpb24gPT09IDIgPyAxIDogMjtcbiAgICBjb25zdCBzYW1wbGVSYXRlID0gU2FtcGxpbmdSYXRlTWFwW2NvbHVtbkluU2FtcGxlUmF0ZXMgKiAzICsgc2FtcGxlUmF0ZUluZGV4XTtcbiAgICBjb25zdCBjaGFubmVsQ291bnQgPSBjaGFubmVsTW9kZSA9PT0gMyA/IDEgOiAyOyAvLyBJZiBiaXRzIG9mIGNoYW5uZWwgbW9kZSBhcmUgYDExYCB0aGVuIGl0IGlzIGEgc2luZ2xlIGNoYW5uZWwgKE1vbm8pXG4gICAgY29uc3Qgc2FtcGxlQ29lZmZpY2llbnQgPSBTYW1wbGVzQ29lZmZpY2llbnRzW21wZWdWZXJzaW9uXVttcGVnTGF5ZXJdO1xuICAgIGNvbnN0IGJ5dGVzSW5TbG90ID0gQnl0ZXNJblNsb3RbbXBlZ0xheWVyXTtcbiAgICBjb25zdCBzYW1wbGVzUGVyRnJhbWUgPSBzYW1wbGVDb2VmZmljaWVudCAqIDggKiBieXRlc0luU2xvdDtcbiAgICBjb25zdCBmcmFtZUxlbmd0aCA9IE1hdGguZmxvb3Ioc2FtcGxlQ29lZmZpY2llbnQgKiBiaXRSYXRlIC8gc2FtcGxlUmF0ZSArIHBhZGRpbmdCaXQpICogYnl0ZXNJblNsb3Q7XG4gICAgaWYgKGNocm9tZVZlcnNpb24kMSA9PT0gbnVsbCkge1xuICAgICAgY29uc3QgdXNlckFnZW50ID0gbmF2aWdhdG9yLnVzZXJBZ2VudCB8fCAnJztcbiAgICAgIGNvbnN0IHJlc3VsdCA9IHVzZXJBZ2VudC5tYXRjaCgvQ2hyb21lXFwvKFxcZCspL2kpO1xuICAgICAgY2hyb21lVmVyc2lvbiQxID0gcmVzdWx0ID8gcGFyc2VJbnQocmVzdWx0WzFdKSA6IDA7XG4gICAgfVxuICAgIGNvbnN0IG5lZWRDaHJvbWVGaXggPSAhIWNocm9tZVZlcnNpb24kMSAmJiBjaHJvbWVWZXJzaW9uJDEgPD0gODc7XG4gICAgaWYgKG5lZWRDaHJvbWVGaXggJiYgbXBlZ0xheWVyID09PSAyICYmIGJpdFJhdGUgPj0gMjI0MDAwICYmIGNoYW5uZWxNb2RlID09PSAwKSB7XG4gICAgICAvLyBXb3JrIGFyb3VuZCBidWcgaW4gQ2hyb21pdW0gYnkgc2V0dGluZyBjaGFubmVsTW9kZSB0byBkdWFsLWNoYW5uZWwgKDAxKSBpbnN0ZWFkIG9mIHN0ZXJlbyAoMDApXG4gICAgICBkYXRhW29mZnNldCArIDNdID0gZGF0YVtvZmZzZXQgKyAzXSB8IDB4ODA7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBzYW1wbGVSYXRlLFxuICAgICAgY2hhbm5lbENvdW50LFxuICAgICAgZnJhbWVMZW5ndGgsXG4gICAgICBzYW1wbGVzUGVyRnJhbWVcbiAgICB9O1xuICB9XG59XG5mdW5jdGlvbiBpc0hlYWRlclBhdHRlcm4oZGF0YSwgb2Zmc2V0KSB7XG4gIHJldHVybiBkYXRhW29mZnNldF0gPT09IDB4ZmYgJiYgKGRhdGFbb2Zmc2V0ICsgMV0gJiAweGUwKSA9PT0gMHhlMCAmJiAoZGF0YVtvZmZzZXQgKyAxXSAmIDB4MDYpICE9PSAweDAwO1xufVxuZnVuY3Rpb24gaXNIZWFkZXIoZGF0YSwgb2Zmc2V0KSB7XG4gIC8vIExvb2sgZm9yIE1QRUcgaGVhZGVyIHwgMTExMSAxMTExIHwgMTExWCBYWVpYIHwgd2hlcmUgWCBjYW4gYmUgZWl0aGVyIDAgb3IgMSBhbmQgWSBvciBaIHNob3VsZCBiZSAxXG4gIC8vIExheWVyIGJpdHMgKHBvc2l0aW9uIDE0IGFuZCAxNSkgaW4gaGVhZGVyIHNob3VsZCBiZSBhbHdheXMgZGlmZmVyZW50IGZyb20gMCAoTGF5ZXIgSSBvciBMYXllciBJSSBvciBMYXllciBJSUkpXG4gIC8vIE1vcmUgaW5mbyBodHRwOi8vd3d3Lm1wMy10ZWNoLm9yZy9wcm9ncmFtbWVyL2ZyYW1lX2hlYWRlci5odG1sXG4gIHJldHVybiBvZmZzZXQgKyAxIDwgZGF0YS5sZW5ndGggJiYgaXNIZWFkZXJQYXR0ZXJuKGRhdGEsIG9mZnNldCk7XG59XG5mdW5jdGlvbiBjYW5QYXJzZShkYXRhLCBvZmZzZXQpIHtcbiAgY29uc3QgaGVhZGVyU2l6ZSA9IDQ7XG4gIHJldHVybiBpc0hlYWRlclBhdHRlcm4oZGF0YSwgb2Zmc2V0KSAmJiBoZWFkZXJTaXplIDw9IGRhdGEubGVuZ3RoIC0gb2Zmc2V0O1xufVxuZnVuY3Rpb24gcHJvYmUoZGF0YSwgb2Zmc2V0KSB7XG4gIC8vIHNhbWUgYXMgaXNIZWFkZXIgYnV0IHdlIGFsc28gY2hlY2sgdGhhdCBNUEVHIGZyYW1lIGZvbGxvd3MgbGFzdCBNUEVHIGZyYW1lXG4gIC8vIG9yIGVuZCBvZiBkYXRhIGlzIHJlYWNoZWRcbiAgaWYgKG9mZnNldCArIDEgPCBkYXRhLmxlbmd0aCAmJiBpc0hlYWRlclBhdHRlcm4oZGF0YSwgb2Zmc2V0KSkge1xuICAgIC8vIE1QRUcgaGVhZGVyIExlbmd0aFxuICAgIGNvbnN0IGhlYWRlckxlbmd0aCA9IDQ7XG4gICAgLy8gTVBFRyBmcmFtZSBMZW5ndGhcbiAgICBjb25zdCBoZWFkZXIgPSBwYXJzZUhlYWRlcihkYXRhLCBvZmZzZXQpO1xuICAgIGxldCBmcmFtZUxlbmd0aCA9IGhlYWRlckxlbmd0aDtcbiAgICBpZiAoaGVhZGVyICE9IG51bGwgJiYgaGVhZGVyLmZyYW1lTGVuZ3RoKSB7XG4gICAgICBmcmFtZUxlbmd0aCA9IGhlYWRlci5mcmFtZUxlbmd0aDtcbiAgICB9XG4gICAgY29uc3QgbmV3T2Zmc2V0ID0gb2Zmc2V0ICsgZnJhbWVMZW5ndGg7XG4gICAgcmV0dXJuIG5ld09mZnNldCA9PT0gZGF0YS5sZW5ndGggfHwgaXNIZWFkZXIoZGF0YSwgbmV3T2Zmc2V0KTtcbiAgfVxuICByZXR1cm4gZmFsc2U7XG59XG5cbi8qKlxuICogQUFDIGRlbXV4ZXJcbiAqL1xuY2xhc3MgQUFDRGVtdXhlciBleHRlbmRzIEJhc2VBdWRpb0RlbXV4ZXIge1xuICBjb25zdHJ1Y3RvcihvYnNlcnZlciwgY29uZmlnKSB7XG4gICAgc3VwZXIoKTtcbiAgICB0aGlzLm9ic2VydmVyID0gdm9pZCAwO1xuICAgIHRoaXMuY29uZmlnID0gdm9pZCAwO1xuICAgIHRoaXMub2JzZXJ2ZXIgPSBvYnNlcnZlcjtcbiAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgfVxuICByZXNldEluaXRTZWdtZW50KGluaXRTZWdtZW50LCBhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCB0cmFja0R1cmF0aW9uKSB7XG4gICAgc3VwZXIucmVzZXRJbml0U2VnbWVudChpbml0U2VnbWVudCwgYXVkaW9Db2RlYywgdmlkZW9Db2RlYywgdHJhY2tEdXJhdGlvbik7XG4gICAgdGhpcy5fYXVkaW9UcmFjayA9IHtcbiAgICAgIGNvbnRhaW5lcjogJ2F1ZGlvL2FkdHMnLFxuICAgICAgdHlwZTogJ2F1ZGlvJyxcbiAgICAgIGlkOiAyLFxuICAgICAgcGlkOiAtMSxcbiAgICAgIHNlcXVlbmNlTnVtYmVyOiAwLFxuICAgICAgc2VnbWVudENvZGVjOiAnYWFjJyxcbiAgICAgIHNhbXBsZXM6IFtdLFxuICAgICAgbWFuaWZlc3RDb2RlYzogYXVkaW9Db2RlYyxcbiAgICAgIGR1cmF0aW9uOiB0cmFja0R1cmF0aW9uLFxuICAgICAgaW5wdXRUaW1lU2NhbGU6IDkwMDAwLFxuICAgICAgZHJvcHBlZDogMFxuICAgIH07XG4gIH1cblxuICAvLyBTb3VyY2UgZm9yIHByb2JlIGluZm8gLSBodHRwczovL3dpa2kubXVsdGltZWRpYS5jeC9pbmRleC5waHA/dGl0bGU9QURUU1xuICBzdGF0aWMgcHJvYmUoZGF0YSkge1xuICAgIGlmICghZGF0YSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGZvciB0aGUgQURUUyBzeW5jIHdvcmRcbiAgICAvLyBMb29rIGZvciBBRFRTIGhlYWRlciB8IDExMTEgMTExMSB8IDExMTEgWDAwWCB8IHdoZXJlIFggY2FuIGJlIGVpdGhlciAwIG9yIDFcbiAgICAvLyBMYXllciBiaXRzIChwb3NpdGlvbiAxNCBhbmQgMTUpIGluIGhlYWRlciBzaG91bGQgYmUgYWx3YXlzIDAgZm9yIEFEVFNcbiAgICAvLyBNb3JlIGluZm8gaHR0cHM6Ly93aWtpLm11bHRpbWVkaWEuY3gvaW5kZXgucGhwP3RpdGxlPUFEVFNcbiAgICBjb25zdCBpZDNEYXRhID0gZ2V0SUQzRGF0YShkYXRhLCAwKTtcbiAgICBsZXQgb2Zmc2V0ID0gKGlkM0RhdGEgPT0gbnVsbCA/IHZvaWQgMCA6IGlkM0RhdGEubGVuZ3RoKSB8fCAwO1xuICAgIGlmIChwcm9iZShkYXRhLCBvZmZzZXQpKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGZvciAobGV0IGxlbmd0aCA9IGRhdGEubGVuZ3RoOyBvZmZzZXQgPCBsZW5ndGg7IG9mZnNldCsrKSB7XG4gICAgICBpZiAocHJvYmUkMShkYXRhLCBvZmZzZXQpKSB7XG4gICAgICAgIGxvZ2dlci5sb2coJ0FEVFMgc3luYyB3b3JkIGZvdW5kICEnKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBjYW5QYXJzZShkYXRhLCBvZmZzZXQpIHtcbiAgICByZXR1cm4gY2FuUGFyc2UkMShkYXRhLCBvZmZzZXQpO1xuICB9XG4gIGFwcGVuZEZyYW1lKHRyYWNrLCBkYXRhLCBvZmZzZXQpIHtcbiAgICBpbml0VHJhY2tDb25maWcodHJhY2ssIHRoaXMub2JzZXJ2ZXIsIGRhdGEsIG9mZnNldCwgdHJhY2subWFuaWZlc3RDb2RlYyk7XG4gICAgY29uc3QgZnJhbWUgPSBhcHBlbmRGcmFtZSQyKHRyYWNrLCBkYXRhLCBvZmZzZXQsIHRoaXMuYmFzZVBUUywgdGhpcy5mcmFtZUluZGV4KTtcbiAgICBpZiAoZnJhbWUgJiYgZnJhbWUubWlzc2luZyA9PT0gMCkge1xuICAgICAgcmV0dXJuIGZyYW1lO1xuICAgIH1cbiAgfVxufVxuXG5jb25zdCBlbXNnU2NoZW1lUGF0dGVybiA9IC9cXC9lbXNnWy0vXUlEMy9pO1xuY2xhc3MgTVA0RGVtdXhlciB7XG4gIGNvbnN0cnVjdG9yKG9ic2VydmVyLCBjb25maWcpIHtcbiAgICB0aGlzLnJlbWFpbmRlckRhdGEgPSBudWxsO1xuICAgIHRoaXMudGltZU9mZnNldCA9IDA7XG4gICAgdGhpcy5jb25maWcgPSB2b2lkIDA7XG4gICAgdGhpcy52aWRlb1RyYWNrID0gdm9pZCAwO1xuICAgIHRoaXMuYXVkaW9UcmFjayA9IHZvaWQgMDtcbiAgICB0aGlzLmlkM1RyYWNrID0gdm9pZCAwO1xuICAgIHRoaXMudHh0VHJhY2sgPSB2b2lkIDA7XG4gICAgdGhpcy5jb25maWcgPSBjb25maWc7XG4gIH1cbiAgcmVzZXRUaW1lU3RhbXAoKSB7fVxuICByZXNldEluaXRTZWdtZW50KGluaXRTZWdtZW50LCBhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCB0cmFja0R1cmF0aW9uKSB7XG4gICAgY29uc3QgdmlkZW9UcmFjayA9IHRoaXMudmlkZW9UcmFjayA9IGR1bW15VHJhY2soJ3ZpZGVvJywgMSk7XG4gICAgY29uc3QgYXVkaW9UcmFjayA9IHRoaXMuYXVkaW9UcmFjayA9IGR1bW15VHJhY2soJ2F1ZGlvJywgMSk7XG4gICAgY29uc3QgY2FwdGlvblRyYWNrID0gdGhpcy50eHRUcmFjayA9IGR1bW15VHJhY2soJ3RleHQnLCAxKTtcbiAgICB0aGlzLmlkM1RyYWNrID0gZHVtbXlUcmFjaygnaWQzJywgMSk7XG4gICAgdGhpcy50aW1lT2Zmc2V0ID0gMDtcbiAgICBpZiAoIShpbml0U2VnbWVudCAhPSBudWxsICYmIGluaXRTZWdtZW50LmJ5dGVMZW5ndGgpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGluaXREYXRhID0gcGFyc2VJbml0U2VnbWVudChpbml0U2VnbWVudCk7XG4gICAgaWYgKGluaXREYXRhLnZpZGVvKSB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGlkLFxuICAgICAgICB0aW1lc2NhbGUsXG4gICAgICAgIGNvZGVjXG4gICAgICB9ID0gaW5pdERhdGEudmlkZW87XG4gICAgICB2aWRlb1RyYWNrLmlkID0gaWQ7XG4gICAgICB2aWRlb1RyYWNrLnRpbWVzY2FsZSA9IGNhcHRpb25UcmFjay50aW1lc2NhbGUgPSB0aW1lc2NhbGU7XG4gICAgICB2aWRlb1RyYWNrLmNvZGVjID0gY29kZWM7XG4gICAgfVxuICAgIGlmIChpbml0RGF0YS5hdWRpbykge1xuICAgICAgY29uc3Qge1xuICAgICAgICBpZCxcbiAgICAgICAgdGltZXNjYWxlLFxuICAgICAgICBjb2RlY1xuICAgICAgfSA9IGluaXREYXRhLmF1ZGlvO1xuICAgICAgYXVkaW9UcmFjay5pZCA9IGlkO1xuICAgICAgYXVkaW9UcmFjay50aW1lc2NhbGUgPSB0aW1lc2NhbGU7XG4gICAgICBhdWRpb1RyYWNrLmNvZGVjID0gY29kZWM7XG4gICAgfVxuICAgIGNhcHRpb25UcmFjay5pZCA9IFJlbXV4ZXJUcmFja0lkQ29uZmlnLnRleHQ7XG4gICAgdmlkZW9UcmFjay5zYW1wbGVEdXJhdGlvbiA9IDA7XG4gICAgdmlkZW9UcmFjay5kdXJhdGlvbiA9IGF1ZGlvVHJhY2suZHVyYXRpb24gPSB0cmFja0R1cmF0aW9uO1xuICB9XG4gIHJlc2V0Q29udGlndWl0eSgpIHtcbiAgICB0aGlzLnJlbWFpbmRlckRhdGEgPSBudWxsO1xuICB9XG4gIHN0YXRpYyBwcm9iZShkYXRhKSB7XG4gICAgcmV0dXJuIGhhc01vb2ZEYXRhKGRhdGEpO1xuICB9XG4gIGRlbXV4KGRhdGEsIHRpbWVPZmZzZXQpIHtcbiAgICB0aGlzLnRpbWVPZmZzZXQgPSB0aW1lT2Zmc2V0O1xuICAgIC8vIExvYWQgYWxsIGRhdGEgaW50byB0aGUgYXZjIHRyYWNrLiBUaGUgQ01BRiByZW11eGVyIHdpbGwgbG9vayBmb3IgdGhlIGRhdGEgaW4gdGhlIHNhbXBsZXMgb2JqZWN0OyB0aGUgcmVzdCBvZiB0aGUgZmllbGRzIGRvIG5vdCBtYXR0ZXJcbiAgICBsZXQgdmlkZW9TYW1wbGVzID0gZGF0YTtcbiAgICBjb25zdCB2aWRlb1RyYWNrID0gdGhpcy52aWRlb1RyYWNrO1xuICAgIGNvbnN0IHRleHRUcmFjayA9IHRoaXMudHh0VHJhY2s7XG4gICAgaWYgKHRoaXMuY29uZmlnLnByb2dyZXNzaXZlKSB7XG4gICAgICAvLyBTcGxpdCB0aGUgYnl0ZXN0cmVhbSBpbnRvIHR3byByYW5nZXM6IG9uZSBlbmNvbXBhc3NpbmcgYWxsIGRhdGEgdXAgdW50aWwgdGhlIHN0YXJ0IG9mIHRoZSBsYXN0IG1vb2YsIGFuZCBldmVyeXRoaW5nIGVsc2UuXG4gICAgICAvLyBUaGlzIGlzIGRvbmUgdG8gZ3VhcmFudGVlIHRoYXQgd2UncmUgc2VuZGluZyB2YWxpZCBkYXRhIHRvIE1TRSAtIHdoZW4gZGVtdXhpbmcgcHJvZ3Jlc3NpdmVseSwgd2UgaGF2ZSBubyBndWFyYW50ZWVcbiAgICAgIC8vIHRoYXQgdGhlIGZldGNoIGxvYWRlciBnaXZlcyB1cyBmbHVzaCBtb29mK21kYXQgcGFpcnMuIElmIHdlIHB1c2ggamFnZ2VkIGRhdGEgdG8gTVNFLCBpdCB3aWxsIHRocm93IGFuIGV4Y2VwdGlvbi5cbiAgICAgIGlmICh0aGlzLnJlbWFpbmRlckRhdGEpIHtcbiAgICAgICAgdmlkZW9TYW1wbGVzID0gYXBwZW5kVWludDhBcnJheSh0aGlzLnJlbWFpbmRlckRhdGEsIGRhdGEpO1xuICAgICAgfVxuICAgICAgY29uc3Qgc2VnbWVudGVkRGF0YSA9IHNlZ21lbnRWYWxpZFJhbmdlKHZpZGVvU2FtcGxlcyk7XG4gICAgICB0aGlzLnJlbWFpbmRlckRhdGEgPSBzZWdtZW50ZWREYXRhLnJlbWFpbmRlcjtcbiAgICAgIHZpZGVvVHJhY2suc2FtcGxlcyA9IHNlZ21lbnRlZERhdGEudmFsaWQgfHwgbmV3IFVpbnQ4QXJyYXkoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmlkZW9UcmFjay5zYW1wbGVzID0gdmlkZW9TYW1wbGVzO1xuICAgIH1cbiAgICBjb25zdCBpZDNUcmFjayA9IHRoaXMuZXh0cmFjdElEM1RyYWNrKHZpZGVvVHJhY2ssIHRpbWVPZmZzZXQpO1xuICAgIHRleHRUcmFjay5zYW1wbGVzID0gcGFyc2VTYW1wbGVzKHRpbWVPZmZzZXQsIHZpZGVvVHJhY2spO1xuICAgIHJldHVybiB7XG4gICAgICB2aWRlb1RyYWNrLFxuICAgICAgYXVkaW9UcmFjazogdGhpcy5hdWRpb1RyYWNrLFxuICAgICAgaWQzVHJhY2ssXG4gICAgICB0ZXh0VHJhY2s6IHRoaXMudHh0VHJhY2tcbiAgICB9O1xuICB9XG4gIGZsdXNoKCkge1xuICAgIGNvbnN0IHRpbWVPZmZzZXQgPSB0aGlzLnRpbWVPZmZzZXQ7XG4gICAgY29uc3QgdmlkZW9UcmFjayA9IHRoaXMudmlkZW9UcmFjaztcbiAgICBjb25zdCB0ZXh0VHJhY2sgPSB0aGlzLnR4dFRyYWNrO1xuICAgIHZpZGVvVHJhY2suc2FtcGxlcyA9IHRoaXMucmVtYWluZGVyRGF0YSB8fCBuZXcgVWludDhBcnJheSgpO1xuICAgIHRoaXMucmVtYWluZGVyRGF0YSA9IG51bGw7XG4gICAgY29uc3QgaWQzVHJhY2sgPSB0aGlzLmV4dHJhY3RJRDNUcmFjayh2aWRlb1RyYWNrLCB0aGlzLnRpbWVPZmZzZXQpO1xuICAgIHRleHRUcmFjay5zYW1wbGVzID0gcGFyc2VTYW1wbGVzKHRpbWVPZmZzZXQsIHZpZGVvVHJhY2spO1xuICAgIHJldHVybiB7XG4gICAgICB2aWRlb1RyYWNrLFxuICAgICAgYXVkaW9UcmFjazogZHVtbXlUcmFjaygpLFxuICAgICAgaWQzVHJhY2ssXG4gICAgICB0ZXh0VHJhY2s6IGR1bW15VHJhY2soKVxuICAgIH07XG4gIH1cbiAgZXh0cmFjdElEM1RyYWNrKHZpZGVvVHJhY2ssIHRpbWVPZmZzZXQpIHtcbiAgICBjb25zdCBpZDNUcmFjayA9IHRoaXMuaWQzVHJhY2s7XG4gICAgaWYgKHZpZGVvVHJhY2suc2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgIGNvbnN0IGVtc2dzID0gZmluZEJveCh2aWRlb1RyYWNrLnNhbXBsZXMsIFsnZW1zZyddKTtcbiAgICAgIGlmIChlbXNncykge1xuICAgICAgICBlbXNncy5mb3JFYWNoKGRhdGEgPT4ge1xuICAgICAgICAgIGNvbnN0IGVtc2dJbmZvID0gcGFyc2VFbXNnKGRhdGEpO1xuICAgICAgICAgIGlmIChlbXNnU2NoZW1lUGF0dGVybi50ZXN0KGVtc2dJbmZvLnNjaGVtZUlkVXJpKSkge1xuICAgICAgICAgICAgY29uc3QgcHRzID0gaXNGaW5pdGVOdW1iZXIoZW1zZ0luZm8ucHJlc2VudGF0aW9uVGltZSkgPyBlbXNnSW5mby5wcmVzZW50YXRpb25UaW1lIC8gZW1zZ0luZm8udGltZVNjYWxlIDogdGltZU9mZnNldCArIGVtc2dJbmZvLnByZXNlbnRhdGlvblRpbWVEZWx0YSAvIGVtc2dJbmZvLnRpbWVTY2FsZTtcbiAgICAgICAgICAgIGxldCBkdXJhdGlvbiA9IGVtc2dJbmZvLmV2ZW50RHVyYXRpb24gPT09IDB4ZmZmZmZmZmYgPyBOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFkgOiBlbXNnSW5mby5ldmVudER1cmF0aW9uIC8gZW1zZ0luZm8udGltZVNjYWxlO1xuICAgICAgICAgICAgLy8gU2FmYXJpIHRha2VzIGFueXRoaW5nIDw9IDAuMDAxIHNlY29uZHMgYW5kIG1hcHMgaXQgdG8gSW5maW5pdHlcbiAgICAgICAgICAgIGlmIChkdXJhdGlvbiA8PSAwLjAwMSkge1xuICAgICAgICAgICAgICBkdXJhdGlvbiA9IE51bWJlci5QT1NJVElWRV9JTkZJTklUWTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IHBheWxvYWQgPSBlbXNnSW5mby5wYXlsb2FkO1xuICAgICAgICAgICAgaWQzVHJhY2suc2FtcGxlcy5wdXNoKHtcbiAgICAgICAgICAgICAgZGF0YTogcGF5bG9hZCxcbiAgICAgICAgICAgICAgbGVuOiBwYXlsb2FkLmJ5dGVMZW5ndGgsXG4gICAgICAgICAgICAgIGR0czogcHRzLFxuICAgICAgICAgICAgICBwdHM6IHB0cyxcbiAgICAgICAgICAgICAgdHlwZTogTWV0YWRhdGFTY2hlbWEuZW1zZyxcbiAgICAgICAgICAgICAgZHVyYXRpb246IGR1cmF0aW9uXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gaWQzVHJhY2s7XG4gIH1cbiAgZGVtdXhTYW1wbGVBZXMoZGF0YSwga2V5RGF0YSwgdGltZU9mZnNldCkge1xuICAgIHJldHVybiBQcm9taXNlLnJlamVjdChuZXcgRXJyb3IoJ1RoZSBNUDQgZGVtdXhlciBkb2VzIG5vdCBzdXBwb3J0IFNBTVBMRS1BRVMgZGVjcnlwdGlvbicpKTtcbiAgfVxuICBkZXN0cm95KCkge31cbn1cblxuY29uc3QgZ2V0QXVkaW9CU0lEID0gKGRhdGEsIG9mZnNldCkgPT4ge1xuICAvLyBjaGVjayB0aGUgYnNpZCB0byBjb25maXJtIGFjLTMgfCBlYy0zXG4gIGxldCBic2lkID0gMDtcbiAgbGV0IG51bUJpdHMgPSA1O1xuICBvZmZzZXQgKz0gbnVtQml0cztcbiAgY29uc3QgdGVtcCA9IG5ldyBVaW50MzJBcnJheSgxKTsgLy8gdW5zaWduZWQgMzIgYml0IGZvciB0ZW1wb3Jhcnkgc3RvcmFnZVxuICBjb25zdCBtYXNrID0gbmV3IFVpbnQzMkFycmF5KDEpOyAvLyB1bnNpZ25lZCAzMiBiaXQgbWFzayB2YWx1ZVxuICBjb25zdCBieXRlID0gbmV3IFVpbnQ4QXJyYXkoMSk7IC8vIHVuc2lnbmVkIDggYml0IGZvciB0ZW1wb3Jhcnkgc3RvcmFnZVxuICB3aGlsZSAobnVtQml0cyA+IDApIHtcbiAgICBieXRlWzBdID0gZGF0YVtvZmZzZXRdO1xuICAgIC8vIHJlYWQgcmVtYWluaW5nIGJpdHMsIHVwdG8gOCBiaXRzIGF0IGEgdGltZVxuICAgIGNvbnN0IGJpdHMgPSBNYXRoLm1pbihudW1CaXRzLCA4KTtcbiAgICBjb25zdCBzaGlmdCA9IDggLSBiaXRzO1xuICAgIG1hc2tbMF0gPSAweGZmMDAwMDAwID4+PiAyNCArIHNoaWZ0IDw8IHNoaWZ0O1xuICAgIHRlbXBbMF0gPSAoYnl0ZVswXSAmIG1hc2tbMF0pID4+IHNoaWZ0O1xuICAgIGJzaWQgPSAhYnNpZCA/IHRlbXBbMF0gOiBic2lkIDw8IGJpdHMgfCB0ZW1wWzBdO1xuICAgIG9mZnNldCArPSAxO1xuICAgIG51bUJpdHMgLT0gYml0cztcbiAgfVxuICByZXR1cm4gYnNpZDtcbn07XG5cbmNsYXNzIEFDM0RlbXV4ZXIgZXh0ZW5kcyBCYXNlQXVkaW9EZW11eGVyIHtcbiAgY29uc3RydWN0b3Iob2JzZXJ2ZXIpIHtcbiAgICBzdXBlcigpO1xuICAgIHRoaXMub2JzZXJ2ZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5vYnNlcnZlciA9IG9ic2VydmVyO1xuICB9XG4gIHJlc2V0SW5pdFNlZ21lbnQoaW5pdFNlZ21lbnQsIGF1ZGlvQ29kZWMsIHZpZGVvQ29kZWMsIHRyYWNrRHVyYXRpb24pIHtcbiAgICBzdXBlci5yZXNldEluaXRTZWdtZW50KGluaXRTZWdtZW50LCBhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCB0cmFja0R1cmF0aW9uKTtcbiAgICB0aGlzLl9hdWRpb1RyYWNrID0ge1xuICAgICAgY29udGFpbmVyOiAnYXVkaW8vYWMtMycsXG4gICAgICB0eXBlOiAnYXVkaW8nLFxuICAgICAgaWQ6IDIsXG4gICAgICBwaWQ6IC0xLFxuICAgICAgc2VxdWVuY2VOdW1iZXI6IDAsXG4gICAgICBzZWdtZW50Q29kZWM6ICdhYzMnLFxuICAgICAgc2FtcGxlczogW10sXG4gICAgICBtYW5pZmVzdENvZGVjOiBhdWRpb0NvZGVjLFxuICAgICAgZHVyYXRpb246IHRyYWNrRHVyYXRpb24sXG4gICAgICBpbnB1dFRpbWVTY2FsZTogOTAwMDAsXG4gICAgICBkcm9wcGVkOiAwXG4gICAgfTtcbiAgfVxuICBjYW5QYXJzZShkYXRhLCBvZmZzZXQpIHtcbiAgICByZXR1cm4gb2Zmc2V0ICsgNjQgPCBkYXRhLmxlbmd0aDtcbiAgfVxuICBhcHBlbmRGcmFtZSh0cmFjaywgZGF0YSwgb2Zmc2V0KSB7XG4gICAgY29uc3QgZnJhbWVMZW5ndGggPSBhcHBlbmRGcmFtZSh0cmFjaywgZGF0YSwgb2Zmc2V0LCB0aGlzLmJhc2VQVFMsIHRoaXMuZnJhbWVJbmRleCk7XG4gICAgaWYgKGZyYW1lTGVuZ3RoICE9PSAtMSkge1xuICAgICAgY29uc3Qgc2FtcGxlID0gdHJhY2suc2FtcGxlc1t0cmFjay5zYW1wbGVzLmxlbmd0aCAtIDFdO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgc2FtcGxlLFxuICAgICAgICBsZW5ndGg6IGZyYW1lTGVuZ3RoLFxuICAgICAgICBtaXNzaW5nOiAwXG4gICAgICB9O1xuICAgIH1cbiAgfVxuICBzdGF0aWMgcHJvYmUoZGF0YSkge1xuICAgIGlmICghZGF0YSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBjb25zdCBpZDNEYXRhID0gZ2V0SUQzRGF0YShkYXRhLCAwKTtcbiAgICBpZiAoIWlkM0RhdGEpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBsb29rIGZvciB0aGUgYWMtMyBzeW5jIGJ5dGVzXG4gICAgY29uc3Qgb2Zmc2V0ID0gaWQzRGF0YS5sZW5ndGg7XG4gICAgaWYgKGRhdGFbb2Zmc2V0XSA9PT0gMHgwYiAmJiBkYXRhW29mZnNldCArIDFdID09PSAweDc3ICYmIGdldFRpbWVTdGFtcChpZDNEYXRhKSAhPT0gdW5kZWZpbmVkICYmXG4gICAgLy8gY2hlY2sgdGhlIGJzaWQgdG8gY29uZmlybSBhYy0zXG4gICAgZ2V0QXVkaW9CU0lEKGRhdGEsIG9mZnNldCkgPCAxNikge1xuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxufVxuZnVuY3Rpb24gYXBwZW5kRnJhbWUodHJhY2ssIGRhdGEsIHN0YXJ0LCBwdHMsIGZyYW1lSW5kZXgpIHtcbiAgaWYgKHN0YXJ0ICsgOCA+IGRhdGEubGVuZ3RoKSB7XG4gICAgcmV0dXJuIC0xOyAvLyBub3QgZW5vdWdoIGJ5dGVzIGxlZnRcbiAgfVxuICBpZiAoZGF0YVtzdGFydF0gIT09IDB4MGIgfHwgZGF0YVtzdGFydCArIDFdICE9PSAweDc3KSB7XG4gICAgcmV0dXJuIC0xOyAvLyBpbnZhbGlkIG1hZ2ljXG4gIH1cblxuICAvLyBnZXQgc2FtcGxlIHJhdGVcbiAgY29uc3Qgc2FtcGxpbmdSYXRlQ29kZSA9IGRhdGFbc3RhcnQgKyA0XSA+PiA2O1xuICBpZiAoc2FtcGxpbmdSYXRlQ29kZSA+PSAzKSB7XG4gICAgcmV0dXJuIC0xOyAvLyBpbnZhbGlkIHNhbXBsaW5nIHJhdGVcbiAgfVxuICBjb25zdCBzYW1wbGluZ1JhdGVNYXAgPSBbNDgwMDAsIDQ0MTAwLCAzMjAwMF07XG4gIGNvbnN0IHNhbXBsZVJhdGUgPSBzYW1wbGluZ1JhdGVNYXBbc2FtcGxpbmdSYXRlQ29kZV07XG5cbiAgLy8gZ2V0IGZyYW1lIHNpemVcbiAgY29uc3QgZnJhbWVTaXplQ29kZSA9IGRhdGFbc3RhcnQgKyA0XSAmIDB4M2Y7XG4gIGNvbnN0IGZyYW1lU2l6ZU1hcCA9IFs2NCwgNjksIDk2LCA2NCwgNzAsIDk2LCA4MCwgODcsIDEyMCwgODAsIDg4LCAxMjAsIDk2LCAxMDQsIDE0NCwgOTYsIDEwNSwgMTQ0LCAxMTIsIDEyMSwgMTY4LCAxMTIsIDEyMiwgMTY4LCAxMjgsIDEzOSwgMTkyLCAxMjgsIDE0MCwgMTkyLCAxNjAsIDE3NCwgMjQwLCAxNjAsIDE3NSwgMjQwLCAxOTIsIDIwOCwgMjg4LCAxOTIsIDIwOSwgMjg4LCAyMjQsIDI0MywgMzM2LCAyMjQsIDI0NCwgMzM2LCAyNTYsIDI3OCwgMzg0LCAyNTYsIDI3OSwgMzg0LCAzMjAsIDM0OCwgNDgwLCAzMjAsIDM0OSwgNDgwLCAzODQsIDQxNywgNTc2LCAzODQsIDQxOCwgNTc2LCA0NDgsIDQ4NywgNjcyLCA0NDgsIDQ4OCwgNjcyLCA1MTIsIDU1NywgNzY4LCA1MTIsIDU1OCwgNzY4LCA2NDAsIDY5NiwgOTYwLCA2NDAsIDY5NywgOTYwLCA3NjgsIDgzNSwgMTE1MiwgNzY4LCA4MzYsIDExNTIsIDg5NiwgOTc1LCAxMzQ0LCA4OTYsIDk3NiwgMTM0NCwgMTAyNCwgMTExNCwgMTUzNiwgMTAyNCwgMTExNSwgMTUzNiwgMTE1MiwgMTI1MywgMTcyOCwgMTE1MiwgMTI1NCwgMTcyOCwgMTI4MCwgMTM5MywgMTkyMCwgMTI4MCwgMTM5NCwgMTkyMF07XG4gIGNvbnN0IGZyYW1lTGVuZ3RoID0gZnJhbWVTaXplTWFwW2ZyYW1lU2l6ZUNvZGUgKiAzICsgc2FtcGxpbmdSYXRlQ29kZV0gKiAyO1xuICBpZiAoc3RhcnQgKyBmcmFtZUxlbmd0aCA+IGRhdGEubGVuZ3RoKSB7XG4gICAgcmV0dXJuIC0xO1xuICB9XG5cbiAgLy8gZ2V0IGNoYW5uZWwgY291bnRcbiAgY29uc3QgY2hhbm5lbE1vZGUgPSBkYXRhW3N0YXJ0ICsgNl0gPj4gNTtcbiAgbGV0IHNraXBDb3VudCA9IDA7XG4gIGlmIChjaGFubmVsTW9kZSA9PT0gMikge1xuICAgIHNraXBDb3VudCArPSAyO1xuICB9IGVsc2Uge1xuICAgIGlmIChjaGFubmVsTW9kZSAmIDEgJiYgY2hhbm5lbE1vZGUgIT09IDEpIHtcbiAgICAgIHNraXBDb3VudCArPSAyO1xuICAgIH1cbiAgICBpZiAoY2hhbm5lbE1vZGUgJiA0KSB7XG4gICAgICBza2lwQ291bnQgKz0gMjtcbiAgICB9XG4gIH1cbiAgY29uc3QgbGZlb24gPSAoZGF0YVtzdGFydCArIDZdIDw8IDggfCBkYXRhW3N0YXJ0ICsgN10pID4+IDEyIC0gc2tpcENvdW50ICYgMTtcbiAgY29uc3QgY2hhbm5lbHNNYXAgPSBbMiwgMSwgMiwgMywgMywgNCwgNCwgNV07XG4gIGNvbnN0IGNoYW5uZWxDb3VudCA9IGNoYW5uZWxzTWFwW2NoYW5uZWxNb2RlXSArIGxmZW9uO1xuXG4gIC8vIGJ1aWxkIGRhYzMgYm94XG4gIGNvbnN0IGJzaWQgPSBkYXRhW3N0YXJ0ICsgNV0gPj4gMztcbiAgY29uc3QgYnNtb2QgPSBkYXRhW3N0YXJ0ICsgNV0gJiA3O1xuICBjb25zdCBjb25maWcgPSBuZXcgVWludDhBcnJheShbc2FtcGxpbmdSYXRlQ29kZSA8PCA2IHwgYnNpZCA8PCAxIHwgYnNtb2QgPj4gMiwgKGJzbW9kICYgMykgPDwgNiB8IGNoYW5uZWxNb2RlIDw8IDMgfCBsZmVvbiA8PCAyIHwgZnJhbWVTaXplQ29kZSA+PiA0LCBmcmFtZVNpemVDb2RlIDw8IDQgJiAweGUwXSk7XG4gIGNvbnN0IGZyYW1lRHVyYXRpb24gPSAxNTM2IC8gc2FtcGxlUmF0ZSAqIDkwMDAwO1xuICBjb25zdCBzdGFtcCA9IHB0cyArIGZyYW1lSW5kZXggKiBmcmFtZUR1cmF0aW9uO1xuICBjb25zdCB1bml0ID0gZGF0YS5zdWJhcnJheShzdGFydCwgc3RhcnQgKyBmcmFtZUxlbmd0aCk7XG4gIHRyYWNrLmNvbmZpZyA9IGNvbmZpZztcbiAgdHJhY2suY2hhbm5lbENvdW50ID0gY2hhbm5lbENvdW50O1xuICB0cmFjay5zYW1wbGVyYXRlID0gc2FtcGxlUmF0ZTtcbiAgdHJhY2suc2FtcGxlcy5wdXNoKHtcbiAgICB1bml0LFxuICAgIHB0czogc3RhbXBcbiAgfSk7XG4gIHJldHVybiBmcmFtZUxlbmd0aDtcbn1cblxuY2xhc3MgQmFzZVZpZGVvUGFyc2VyIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5WaWRlb1NhbXBsZSA9IG51bGw7XG4gIH1cbiAgY3JlYXRlVmlkZW9TYW1wbGUoa2V5LCBwdHMsIGR0cywgZGVidWcpIHtcbiAgICByZXR1cm4ge1xuICAgICAga2V5LFxuICAgICAgZnJhbWU6IGZhbHNlLFxuICAgICAgcHRzLFxuICAgICAgZHRzLFxuICAgICAgdW5pdHM6IFtdLFxuICAgICAgZGVidWcsXG4gICAgICBsZW5ndGg6IDBcbiAgICB9O1xuICB9XG4gIGdldExhc3ROYWxVbml0KHNhbXBsZXMpIHtcbiAgICB2YXIgX1ZpZGVvU2FtcGxlO1xuICAgIGxldCBWaWRlb1NhbXBsZSA9IHRoaXMuVmlkZW9TYW1wbGU7XG4gICAgbGV0IGxhc3RVbml0O1xuICAgIC8vIHRyeSB0byBmYWxsYmFjayB0byBwcmV2aW91cyBzYW1wbGUgaWYgY3VycmVudCBvbmUgaXMgZW1wdHlcbiAgICBpZiAoIVZpZGVvU2FtcGxlIHx8IFZpZGVvU2FtcGxlLnVuaXRzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgVmlkZW9TYW1wbGUgPSBzYW1wbGVzW3NhbXBsZXMubGVuZ3RoIC0gMV07XG4gICAgfVxuICAgIGlmICgoX1ZpZGVvU2FtcGxlID0gVmlkZW9TYW1wbGUpICE9IG51bGwgJiYgX1ZpZGVvU2FtcGxlLnVuaXRzKSB7XG4gICAgICBjb25zdCB1bml0cyA9IFZpZGVvU2FtcGxlLnVuaXRzO1xuICAgICAgbGFzdFVuaXQgPSB1bml0c1t1bml0cy5sZW5ndGggLSAxXTtcbiAgICB9XG4gICAgcmV0dXJuIGxhc3RVbml0O1xuICB9XG4gIHB1c2hBY2Nlc3NVbml0KFZpZGVvU2FtcGxlLCB2aWRlb1RyYWNrKSB7XG4gICAgaWYgKFZpZGVvU2FtcGxlLnVuaXRzLmxlbmd0aCAmJiBWaWRlb1NhbXBsZS5mcmFtZSkge1xuICAgICAgLy8gaWYgc2FtcGxlIGRvZXMgbm90IGhhdmUgUFRTL0RUUywgcGF0Y2ggd2l0aCBsYXN0IHNhbXBsZSBQVFMvRFRTXG4gICAgICBpZiAoVmlkZW9TYW1wbGUucHRzID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgY29uc3Qgc2FtcGxlcyA9IHZpZGVvVHJhY2suc2FtcGxlcztcbiAgICAgICAgY29uc3QgbmJTYW1wbGVzID0gc2FtcGxlcy5sZW5ndGg7XG4gICAgICAgIGlmIChuYlNhbXBsZXMpIHtcbiAgICAgICAgICBjb25zdCBsYXN0U2FtcGxlID0gc2FtcGxlc1tuYlNhbXBsZXMgLSAxXTtcbiAgICAgICAgICBWaWRlb1NhbXBsZS5wdHMgPSBsYXN0U2FtcGxlLnB0cztcbiAgICAgICAgICBWaWRlb1NhbXBsZS5kdHMgPSBsYXN0U2FtcGxlLmR0cztcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBkcm9wcGluZyBzYW1wbGVzLCBubyB0aW1lc3RhbXAgZm91bmRcbiAgICAgICAgICB2aWRlb1RyYWNrLmRyb3BwZWQrKztcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHZpZGVvVHJhY2suc2FtcGxlcy5wdXNoKFZpZGVvU2FtcGxlKTtcbiAgICB9XG4gICAgaWYgKFZpZGVvU2FtcGxlLmRlYnVnLmxlbmd0aCkge1xuICAgICAgbG9nZ2VyLmxvZyhWaWRlb1NhbXBsZS5wdHMgKyAnLycgKyBWaWRlb1NhbXBsZS5kdHMgKyAnOicgKyBWaWRlb1NhbXBsZS5kZWJ1Zyk7XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogUGFyc2VyIGZvciBleHBvbmVudGlhbCBHb2xvbWIgY29kZXMsIGEgdmFyaWFibGUtYml0d2lkdGggbnVtYmVyIGVuY29kaW5nIHNjaGVtZSB1c2VkIGJ5IGgyNjQuXG4gKi9cblxuY2xhc3MgRXhwR29sb21iIHtcbiAgY29uc3RydWN0b3IoZGF0YSkge1xuICAgIHRoaXMuZGF0YSA9IHZvaWQgMDtcbiAgICB0aGlzLmJ5dGVzQXZhaWxhYmxlID0gdm9pZCAwO1xuICAgIHRoaXMud29yZCA9IHZvaWQgMDtcbiAgICB0aGlzLmJpdHNBdmFpbGFibGUgPSB2b2lkIDA7XG4gICAgdGhpcy5kYXRhID0gZGF0YTtcbiAgICAvLyB0aGUgbnVtYmVyIG9mIGJ5dGVzIGxlZnQgdG8gZXhhbWluZSBpbiB0aGlzLmRhdGFcbiAgICB0aGlzLmJ5dGVzQXZhaWxhYmxlID0gZGF0YS5ieXRlTGVuZ3RoO1xuICAgIC8vIHRoZSBjdXJyZW50IHdvcmQgYmVpbmcgZXhhbWluZWRcbiAgICB0aGlzLndvcmQgPSAwOyAvLyA6dWludFxuICAgIC8vIHRoZSBudW1iZXIgb2YgYml0cyBsZWZ0IHRvIGV4YW1pbmUgaW4gdGhlIGN1cnJlbnQgd29yZFxuICAgIHRoaXMuYml0c0F2YWlsYWJsZSA9IDA7IC8vIDp1aW50XG4gIH1cblxuICAvLyAoKTp2b2lkXG4gIGxvYWRXb3JkKCkge1xuICAgIGNvbnN0IGRhdGEgPSB0aGlzLmRhdGE7XG4gICAgY29uc3QgYnl0ZXNBdmFpbGFibGUgPSB0aGlzLmJ5dGVzQXZhaWxhYmxlO1xuICAgIGNvbnN0IHBvc2l0aW9uID0gZGF0YS5ieXRlTGVuZ3RoIC0gYnl0ZXNBdmFpbGFibGU7XG4gICAgY29uc3Qgd29ya2luZ0J5dGVzID0gbmV3IFVpbnQ4QXJyYXkoNCk7XG4gICAgY29uc3QgYXZhaWxhYmxlQnl0ZXMgPSBNYXRoLm1pbig0LCBieXRlc0F2YWlsYWJsZSk7XG4gICAgaWYgKGF2YWlsYWJsZUJ5dGVzID09PSAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ25vIGJ5dGVzIGF2YWlsYWJsZScpO1xuICAgIH1cbiAgICB3b3JraW5nQnl0ZXMuc2V0KGRhdGEuc3ViYXJyYXkocG9zaXRpb24sIHBvc2l0aW9uICsgYXZhaWxhYmxlQnl0ZXMpKTtcbiAgICB0aGlzLndvcmQgPSBuZXcgRGF0YVZpZXcod29ya2luZ0J5dGVzLmJ1ZmZlcikuZ2V0VWludDMyKDApO1xuICAgIC8vIHRyYWNrIHRoZSBhbW91bnQgb2YgdGhpcy5kYXRhIHRoYXQgaGFzIGJlZW4gcHJvY2Vzc2VkXG4gICAgdGhpcy5iaXRzQXZhaWxhYmxlID0gYXZhaWxhYmxlQnl0ZXMgKiA4O1xuICAgIHRoaXMuYnl0ZXNBdmFpbGFibGUgLT0gYXZhaWxhYmxlQnl0ZXM7XG4gIH1cblxuICAvLyAoY291bnQ6aW50KTp2b2lkXG4gIHNraXBCaXRzKGNvdW50KSB7XG4gICAgbGV0IHNraXBCeXRlczsgLy8gOmludFxuICAgIGNvdW50ID0gTWF0aC5taW4oY291bnQsIHRoaXMuYnl0ZXNBdmFpbGFibGUgKiA4ICsgdGhpcy5iaXRzQXZhaWxhYmxlKTtcbiAgICBpZiAodGhpcy5iaXRzQXZhaWxhYmxlID4gY291bnQpIHtcbiAgICAgIHRoaXMud29yZCA8PD0gY291bnQ7XG4gICAgICB0aGlzLmJpdHNBdmFpbGFibGUgLT0gY291bnQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvdW50IC09IHRoaXMuYml0c0F2YWlsYWJsZTtcbiAgICAgIHNraXBCeXRlcyA9IGNvdW50ID4+IDM7XG4gICAgICBjb3VudCAtPSBza2lwQnl0ZXMgPDwgMztcbiAgICAgIHRoaXMuYnl0ZXNBdmFpbGFibGUgLT0gc2tpcEJ5dGVzO1xuICAgICAgdGhpcy5sb2FkV29yZCgpO1xuICAgICAgdGhpcy53b3JkIDw8PSBjb3VudDtcbiAgICAgIHRoaXMuYml0c0F2YWlsYWJsZSAtPSBjb3VudDtcbiAgICB9XG4gIH1cblxuICAvLyAoc2l6ZTppbnQpOnVpbnRcbiAgcmVhZEJpdHMoc2l6ZSkge1xuICAgIGxldCBiaXRzID0gTWF0aC5taW4odGhpcy5iaXRzQXZhaWxhYmxlLCBzaXplKTsgLy8gOnVpbnRcbiAgICBjb25zdCB2YWx1ID0gdGhpcy53b3JkID4+PiAzMiAtIGJpdHM7IC8vIDp1aW50XG4gICAgaWYgKHNpemUgPiAzMikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdDYW5ub3QgcmVhZCBtb3JlIHRoYW4gMzIgYml0cyBhdCBhIHRpbWUnKTtcbiAgICB9XG4gICAgdGhpcy5iaXRzQXZhaWxhYmxlIC09IGJpdHM7XG4gICAgaWYgKHRoaXMuYml0c0F2YWlsYWJsZSA+IDApIHtcbiAgICAgIHRoaXMud29yZCA8PD0gYml0cztcbiAgICB9IGVsc2UgaWYgKHRoaXMuYnl0ZXNBdmFpbGFibGUgPiAwKSB7XG4gICAgICB0aGlzLmxvYWRXb3JkKCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignbm8gYml0cyBhdmFpbGFibGUnKTtcbiAgICB9XG4gICAgYml0cyA9IHNpemUgLSBiaXRzO1xuICAgIGlmIChiaXRzID4gMCAmJiB0aGlzLmJpdHNBdmFpbGFibGUpIHtcbiAgICAgIHJldHVybiB2YWx1IDw8IGJpdHMgfCB0aGlzLnJlYWRCaXRzKGJpdHMpO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gdmFsdTtcbiAgICB9XG4gIH1cblxuICAvLyAoKTp1aW50XG4gIHNraXBMWigpIHtcbiAgICBsZXQgbGVhZGluZ1plcm9Db3VudDsgLy8gOnVpbnRcbiAgICBmb3IgKGxlYWRpbmdaZXJvQ291bnQgPSAwOyBsZWFkaW5nWmVyb0NvdW50IDwgdGhpcy5iaXRzQXZhaWxhYmxlOyArK2xlYWRpbmdaZXJvQ291bnQpIHtcbiAgICAgIGlmICgodGhpcy53b3JkICYgMHg4MDAwMDAwMCA+Pj4gbGVhZGluZ1plcm9Db3VudCkgIT09IDApIHtcbiAgICAgICAgLy8gdGhlIGZpcnN0IGJpdCBvZiB3b3JraW5nIHdvcmQgaXMgMVxuICAgICAgICB0aGlzLndvcmQgPDw9IGxlYWRpbmdaZXJvQ291bnQ7XG4gICAgICAgIHRoaXMuYml0c0F2YWlsYWJsZSAtPSBsZWFkaW5nWmVyb0NvdW50O1xuICAgICAgICByZXR1cm4gbGVhZGluZ1plcm9Db3VudDtcbiAgICAgIH1cbiAgICB9XG4gICAgLy8gd2UgZXhoYXVzdGVkIHdvcmQgYW5kIHN0aWxsIGhhdmUgbm90IGZvdW5kIGEgMVxuICAgIHRoaXMubG9hZFdvcmQoKTtcbiAgICByZXR1cm4gbGVhZGluZ1plcm9Db3VudCArIHRoaXMuc2tpcExaKCk7XG4gIH1cblxuICAvLyAoKTp2b2lkXG4gIHNraXBVRUcoKSB7XG4gICAgdGhpcy5za2lwQml0cygxICsgdGhpcy5za2lwTFooKSk7XG4gIH1cblxuICAvLyAoKTp2b2lkXG4gIHNraXBFRygpIHtcbiAgICB0aGlzLnNraXBCaXRzKDEgKyB0aGlzLnNraXBMWigpKTtcbiAgfVxuXG4gIC8vICgpOnVpbnRcbiAgcmVhZFVFRygpIHtcbiAgICBjb25zdCBjbHogPSB0aGlzLnNraXBMWigpOyAvLyA6dWludFxuICAgIHJldHVybiB0aGlzLnJlYWRCaXRzKGNseiArIDEpIC0gMTtcbiAgfVxuXG4gIC8vICgpOmludFxuICByZWFkRUcoKSB7XG4gICAgY29uc3QgdmFsdSA9IHRoaXMucmVhZFVFRygpOyAvLyA6aW50XG4gICAgaWYgKDB4MDEgJiB2YWx1KSB7XG4gICAgICAvLyB0aGUgbnVtYmVyIGlzIG9kZCBpZiB0aGUgbG93IG9yZGVyIGJpdCBpcyBzZXRcbiAgICAgIHJldHVybiAxICsgdmFsdSA+Pj4gMTsgLy8gYWRkIDEgdG8gbWFrZSBpdCBldmVuLCBhbmQgZGl2aWRlIGJ5IDJcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIC0xICogKHZhbHUgPj4+IDEpOyAvLyBkaXZpZGUgYnkgdHdvIHRoZW4gbWFrZSBpdCBuZWdhdGl2ZVxuICAgIH1cbiAgfVxuXG4gIC8vIFNvbWUgY29udmVuaWVuY2UgZnVuY3Rpb25zXG4gIC8vIDpCb29sZWFuXG4gIHJlYWRCb29sZWFuKCkge1xuICAgIHJldHVybiB0aGlzLnJlYWRCaXRzKDEpID09PSAxO1xuICB9XG5cbiAgLy8gKCk6aW50XG4gIHJlYWRVQnl0ZSgpIHtcbiAgICByZXR1cm4gdGhpcy5yZWFkQml0cyg4KTtcbiAgfVxuXG4gIC8vICgpOmludFxuICByZWFkVVNob3J0KCkge1xuICAgIHJldHVybiB0aGlzLnJlYWRCaXRzKDE2KTtcbiAgfVxuXG4gIC8vICgpOmludFxuICByZWFkVUludCgpIHtcbiAgICByZXR1cm4gdGhpcy5yZWFkQml0cygzMik7XG4gIH1cblxuICAvKipcbiAgICogQWR2YW5jZSB0aGUgRXhwR29sb21iIGRlY29kZXIgcGFzdCBhIHNjYWxpbmcgbGlzdC4gVGhlIHNjYWxpbmdcbiAgICogbGlzdCBpcyBvcHRpb25hbGx5IHRyYW5zbWl0dGVkIGFzIHBhcnQgb2YgYSBzZXF1ZW5jZSBwYXJhbWV0ZXJcbiAgICogc2V0IGFuZCBpcyBub3QgcmVsZXZhbnQgdG8gdHJhbnNtdXhpbmcuXG4gICAqIEBwYXJhbSBjb3VudCB0aGUgbnVtYmVyIG9mIGVudHJpZXMgaW4gdGhpcyBzY2FsaW5nIGxpc3RcbiAgICogQHNlZSBSZWNvbW1lbmRhdGlvbiBJVFUtVCBILjI2NCwgU2VjdGlvbiA3LjMuMi4xLjEuMVxuICAgKi9cbiAgc2tpcFNjYWxpbmdMaXN0KGNvdW50KSB7XG4gICAgbGV0IGxhc3RTY2FsZSA9IDg7XG4gICAgbGV0IG5leHRTY2FsZSA9IDg7XG4gICAgbGV0IGRlbHRhU2NhbGU7XG4gICAgZm9yIChsZXQgaiA9IDA7IGogPCBjb3VudDsgaisrKSB7XG4gICAgICBpZiAobmV4dFNjYWxlICE9PSAwKSB7XG4gICAgICAgIGRlbHRhU2NhbGUgPSB0aGlzLnJlYWRFRygpO1xuICAgICAgICBuZXh0U2NhbGUgPSAobGFzdFNjYWxlICsgZGVsdGFTY2FsZSArIDI1NikgJSAyNTY7XG4gICAgICB9XG4gICAgICBsYXN0U2NhbGUgPSBuZXh0U2NhbGUgPT09IDAgPyBsYXN0U2NhbGUgOiBuZXh0U2NhbGU7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJlYWQgYSBzZXF1ZW5jZSBwYXJhbWV0ZXIgc2V0IGFuZCByZXR1cm4gc29tZSBpbnRlcmVzdGluZyB2aWRlb1xuICAgKiBwcm9wZXJ0aWVzLiBBIHNlcXVlbmNlIHBhcmFtZXRlciBzZXQgaXMgdGhlIEgyNjQgbWV0YWRhdGEgdGhhdFxuICAgKiBkZXNjcmliZXMgdGhlIHByb3BlcnRpZXMgb2YgdXBjb21pbmcgdmlkZW8gZnJhbWVzLlxuICAgKiBAcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBjb25maWd1cmF0aW9uIHBhcnNlZCBmcm9tIHRoZVxuICAgKiBzZXF1ZW5jZSBwYXJhbWV0ZXIgc2V0LCBpbmNsdWRpbmcgdGhlIGRpbWVuc2lvbnMgb2YgdGhlXG4gICAqIGFzc29jaWF0ZWQgdmlkZW8gZnJhbWVzLlxuICAgKi9cbiAgcmVhZFNQUygpIHtcbiAgICBsZXQgZnJhbWVDcm9wTGVmdE9mZnNldCA9IDA7XG4gICAgbGV0IGZyYW1lQ3JvcFJpZ2h0T2Zmc2V0ID0gMDtcbiAgICBsZXQgZnJhbWVDcm9wVG9wT2Zmc2V0ID0gMDtcbiAgICBsZXQgZnJhbWVDcm9wQm90dG9tT2Zmc2V0ID0gMDtcbiAgICBsZXQgbnVtUmVmRnJhbWVzSW5QaWNPcmRlckNudEN5Y2xlO1xuICAgIGxldCBzY2FsaW5nTGlzdENvdW50O1xuICAgIGxldCBpO1xuICAgIGNvbnN0IHJlYWRVQnl0ZSA9IHRoaXMucmVhZFVCeXRlLmJpbmQodGhpcyk7XG4gICAgY29uc3QgcmVhZEJpdHMgPSB0aGlzLnJlYWRCaXRzLmJpbmQodGhpcyk7XG4gICAgY29uc3QgcmVhZFVFRyA9IHRoaXMucmVhZFVFRy5iaW5kKHRoaXMpO1xuICAgIGNvbnN0IHJlYWRCb29sZWFuID0gdGhpcy5yZWFkQm9vbGVhbi5iaW5kKHRoaXMpO1xuICAgIGNvbnN0IHNraXBCaXRzID0gdGhpcy5za2lwQml0cy5iaW5kKHRoaXMpO1xuICAgIGNvbnN0IHNraXBFRyA9IHRoaXMuc2tpcEVHLmJpbmQodGhpcyk7XG4gICAgY29uc3Qgc2tpcFVFRyA9IHRoaXMuc2tpcFVFRy5iaW5kKHRoaXMpO1xuICAgIGNvbnN0IHNraXBTY2FsaW5nTGlzdCA9IHRoaXMuc2tpcFNjYWxpbmdMaXN0LmJpbmQodGhpcyk7XG4gICAgcmVhZFVCeXRlKCk7XG4gICAgY29uc3QgcHJvZmlsZUlkYyA9IHJlYWRVQnl0ZSgpOyAvLyBwcm9maWxlX2lkY1xuICAgIHJlYWRCaXRzKDUpOyAvLyBwcm9maWxlQ29tcGF0IGNvbnN0cmFpbnRfc2V0WzAtNF1fZmxhZywgdSg1KVxuICAgIHNraXBCaXRzKDMpOyAvLyByZXNlcnZlZF96ZXJvXzNiaXRzIHUoMyksXG4gICAgcmVhZFVCeXRlKCk7IC8vIGxldmVsX2lkYyB1KDgpXG4gICAgc2tpcFVFRygpOyAvLyBzZXFfcGFyYW1ldGVyX3NldF9pZFxuICAgIC8vIHNvbWUgcHJvZmlsZXMgaGF2ZSBtb3JlIG9wdGlvbmFsIGRhdGEgd2UgZG9uJ3QgbmVlZFxuICAgIGlmIChwcm9maWxlSWRjID09PSAxMDAgfHwgcHJvZmlsZUlkYyA9PT0gMTEwIHx8IHByb2ZpbGVJZGMgPT09IDEyMiB8fCBwcm9maWxlSWRjID09PSAyNDQgfHwgcHJvZmlsZUlkYyA9PT0gNDQgfHwgcHJvZmlsZUlkYyA9PT0gODMgfHwgcHJvZmlsZUlkYyA9PT0gODYgfHwgcHJvZmlsZUlkYyA9PT0gMTE4IHx8IHByb2ZpbGVJZGMgPT09IDEyOCkge1xuICAgICAgY29uc3QgY2hyb21hRm9ybWF0SWRjID0gcmVhZFVFRygpO1xuICAgICAgaWYgKGNocm9tYUZvcm1hdElkYyA9PT0gMykge1xuICAgICAgICBza2lwQml0cygxKTtcbiAgICAgIH0gLy8gc2VwYXJhdGVfY29sb3VyX3BsYW5lX2ZsYWdcblxuICAgICAgc2tpcFVFRygpOyAvLyBiaXRfZGVwdGhfbHVtYV9taW51czhcbiAgICAgIHNraXBVRUcoKTsgLy8gYml0X2RlcHRoX2Nocm9tYV9taW51czhcbiAgICAgIHNraXBCaXRzKDEpOyAvLyBxcHByaW1lX3lfemVyb190cmFuc2Zvcm1fYnlwYXNzX2ZsYWdcbiAgICAgIGlmIChyZWFkQm9vbGVhbigpKSB7XG4gICAgICAgIC8vIHNlcV9zY2FsaW5nX21hdHJpeF9wcmVzZW50X2ZsYWdcbiAgICAgICAgc2NhbGluZ0xpc3RDb3VudCA9IGNocm9tYUZvcm1hdElkYyAhPT0gMyA/IDggOiAxMjtcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IHNjYWxpbmdMaXN0Q291bnQ7IGkrKykge1xuICAgICAgICAgIGlmIChyZWFkQm9vbGVhbigpKSB7XG4gICAgICAgICAgICAvLyBzZXFfc2NhbGluZ19saXN0X3ByZXNlbnRfZmxhZ1sgaSBdXG4gICAgICAgICAgICBpZiAoaSA8IDYpIHtcbiAgICAgICAgICAgICAgc2tpcFNjYWxpbmdMaXN0KDE2KTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIHNraXBTY2FsaW5nTGlzdCg2NCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHNraXBVRUcoKTsgLy8gbG9nMl9tYXhfZnJhbWVfbnVtX21pbnVzNFxuICAgIGNvbnN0IHBpY09yZGVyQ250VHlwZSA9IHJlYWRVRUcoKTtcbiAgICBpZiAocGljT3JkZXJDbnRUeXBlID09PSAwKSB7XG4gICAgICByZWFkVUVHKCk7IC8vIGxvZzJfbWF4X3BpY19vcmRlcl9jbnRfbHNiX21pbnVzNFxuICAgIH0gZWxzZSBpZiAocGljT3JkZXJDbnRUeXBlID09PSAxKSB7XG4gICAgICBza2lwQml0cygxKTsgLy8gZGVsdGFfcGljX29yZGVyX2Fsd2F5c196ZXJvX2ZsYWdcbiAgICAgIHNraXBFRygpOyAvLyBvZmZzZXRfZm9yX25vbl9yZWZfcGljXG4gICAgICBza2lwRUcoKTsgLy8gb2Zmc2V0X2Zvcl90b3BfdG9fYm90dG9tX2ZpZWxkXG4gICAgICBudW1SZWZGcmFtZXNJblBpY09yZGVyQ250Q3ljbGUgPSByZWFkVUVHKCk7XG4gICAgICBmb3IgKGkgPSAwOyBpIDwgbnVtUmVmRnJhbWVzSW5QaWNPcmRlckNudEN5Y2xlOyBpKyspIHtcbiAgICAgICAgc2tpcEVHKCk7XG4gICAgICB9IC8vIG9mZnNldF9mb3JfcmVmX2ZyYW1lWyBpIF1cbiAgICB9XG4gICAgc2tpcFVFRygpOyAvLyBtYXhfbnVtX3JlZl9mcmFtZXNcbiAgICBza2lwQml0cygxKTsgLy8gZ2Fwc19pbl9mcmFtZV9udW1fdmFsdWVfYWxsb3dlZF9mbGFnXG4gICAgY29uc3QgcGljV2lkdGhJbk1ic01pbnVzMSA9IHJlYWRVRUcoKTtcbiAgICBjb25zdCBwaWNIZWlnaHRJbk1hcFVuaXRzTWludXMxID0gcmVhZFVFRygpO1xuICAgIGNvbnN0IGZyYW1lTWJzT25seUZsYWcgPSByZWFkQml0cygxKTtcbiAgICBpZiAoZnJhbWVNYnNPbmx5RmxhZyA9PT0gMCkge1xuICAgICAgc2tpcEJpdHMoMSk7XG4gICAgfSAvLyBtYl9hZGFwdGl2ZV9mcmFtZV9maWVsZF9mbGFnXG5cbiAgICBza2lwQml0cygxKTsgLy8gZGlyZWN0Xzh4OF9pbmZlcmVuY2VfZmxhZ1xuICAgIGlmIChyZWFkQm9vbGVhbigpKSB7XG4gICAgICAvLyBmcmFtZV9jcm9wcGluZ19mbGFnXG4gICAgICBmcmFtZUNyb3BMZWZ0T2Zmc2V0ID0gcmVhZFVFRygpO1xuICAgICAgZnJhbWVDcm9wUmlnaHRPZmZzZXQgPSByZWFkVUVHKCk7XG4gICAgICBmcmFtZUNyb3BUb3BPZmZzZXQgPSByZWFkVUVHKCk7XG4gICAgICBmcmFtZUNyb3BCb3R0b21PZmZzZXQgPSByZWFkVUVHKCk7XG4gICAgfVxuICAgIGxldCBwaXhlbFJhdGlvID0gWzEsIDFdO1xuICAgIGlmIChyZWFkQm9vbGVhbigpKSB7XG4gICAgICAvLyB2dWlfcGFyYW1ldGVyc19wcmVzZW50X2ZsYWdcbiAgICAgIGlmIChyZWFkQm9vbGVhbigpKSB7XG4gICAgICAgIC8vIGFzcGVjdF9yYXRpb19pbmZvX3ByZXNlbnRfZmxhZ1xuICAgICAgICBjb25zdCBhc3BlY3RSYXRpb0lkYyA9IHJlYWRVQnl0ZSgpO1xuICAgICAgICBzd2l0Y2ggKGFzcGVjdFJhdGlvSWRjKSB7XG4gICAgICAgICAgY2FzZSAxOlxuICAgICAgICAgICAgcGl4ZWxSYXRpbyA9IFsxLCAxXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgMjpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbMTIsIDExXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgMzpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbMTAsIDExXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgNDpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbMTYsIDExXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgNTpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbNDAsIDMzXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgNjpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbMjQsIDExXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgNzpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbMjAsIDExXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgODpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbMzIsIDExXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgOTpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbODAsIDMzXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgMTA6XG4gICAgICAgICAgICBwaXhlbFJhdGlvID0gWzE4LCAxMV07XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlIDExOlxuICAgICAgICAgICAgcGl4ZWxSYXRpbyA9IFsxNSwgMTFdO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAxMjpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbNjQsIDMzXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgMTM6XG4gICAgICAgICAgICBwaXhlbFJhdGlvID0gWzE2MCwgOTldO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAxNDpcbiAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbNCwgM107XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlIDE1OlxuICAgICAgICAgICAgcGl4ZWxSYXRpbyA9IFszLCAyXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgMTY6XG4gICAgICAgICAgICBwaXhlbFJhdGlvID0gWzIsIDFdO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAyNTU6XG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgIHBpeGVsUmF0aW8gPSBbcmVhZFVCeXRlKCkgPDwgOCB8IHJlYWRVQnl0ZSgpLCByZWFkVUJ5dGUoKSA8PCA4IHwgcmVhZFVCeXRlKCldO1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgd2lkdGg6IE1hdGguY2VpbCgocGljV2lkdGhJbk1ic01pbnVzMSArIDEpICogMTYgLSBmcmFtZUNyb3BMZWZ0T2Zmc2V0ICogMiAtIGZyYW1lQ3JvcFJpZ2h0T2Zmc2V0ICogMiksXG4gICAgICBoZWlnaHQ6ICgyIC0gZnJhbWVNYnNPbmx5RmxhZykgKiAocGljSGVpZ2h0SW5NYXBVbml0c01pbnVzMSArIDEpICogMTYgLSAoZnJhbWVNYnNPbmx5RmxhZyA/IDIgOiA0KSAqIChmcmFtZUNyb3BUb3BPZmZzZXQgKyBmcmFtZUNyb3BCb3R0b21PZmZzZXQpLFxuICAgICAgcGl4ZWxSYXRpbzogcGl4ZWxSYXRpb1xuICAgIH07XG4gIH1cbiAgcmVhZFNsaWNlVHlwZSgpIHtcbiAgICAvLyBza2lwIE5BTHUgdHlwZVxuICAgIHRoaXMucmVhZFVCeXRlKCk7XG4gICAgLy8gZGlzY2FyZCBmaXJzdF9tYl9pbl9zbGljZVxuICAgIHRoaXMucmVhZFVFRygpO1xuICAgIC8vIHJldHVybiBzbGljZV90eXBlXG4gICAgcmV0dXJuIHRoaXMucmVhZFVFRygpO1xuICB9XG59XG5cbmNsYXNzIEF2Y1ZpZGVvUGFyc2VyIGV4dGVuZHMgQmFzZVZpZGVvUGFyc2VyIHtcbiAgcGFyc2VBVkNQRVModHJhY2ssIHRleHRUcmFjaywgcGVzLCBsYXN0LCBkdXJhdGlvbikge1xuICAgIGNvbnN0IHVuaXRzID0gdGhpcy5wYXJzZUFWQ05BTHUodHJhY2ssIHBlcy5kYXRhKTtcbiAgICBsZXQgVmlkZW9TYW1wbGUgPSB0aGlzLlZpZGVvU2FtcGxlO1xuICAgIGxldCBwdXNoO1xuICAgIGxldCBzcHNmb3VuZCA9IGZhbHNlO1xuICAgIC8vIGZyZWUgcGVzLmRhdGEgdG8gc2F2ZSB1cCBzb21lIG1lbW9yeVxuICAgIHBlcy5kYXRhID0gbnVsbDtcblxuICAgIC8vIGlmIG5ldyBOQUwgdW5pdHMgZm91bmQgYW5kIGxhc3Qgc2FtcGxlIHN0aWxsIHRoZXJlLCBsZXQncyBwdXNoIC4uLlxuICAgIC8vIHRoaXMgaGVscHMgcGFyc2luZyBzdHJlYW1zIHdpdGggbWlzc2luZyBBVUQgKG9ubHkgZG8gdGhpcyBpZiBBVUQgbmV2ZXIgZm91bmQpXG4gICAgaWYgKFZpZGVvU2FtcGxlICYmIHVuaXRzLmxlbmd0aCAmJiAhdHJhY2suYXVkRm91bmQpIHtcbiAgICAgIHRoaXMucHVzaEFjY2Vzc1VuaXQoVmlkZW9TYW1wbGUsIHRyYWNrKTtcbiAgICAgIFZpZGVvU2FtcGxlID0gdGhpcy5WaWRlb1NhbXBsZSA9IHRoaXMuY3JlYXRlVmlkZW9TYW1wbGUoZmFsc2UsIHBlcy5wdHMsIHBlcy5kdHMsICcnKTtcbiAgICB9XG4gICAgdW5pdHMuZm9yRWFjaCh1bml0ID0+IHtcbiAgICAgIHZhciBfVmlkZW9TYW1wbGUyO1xuICAgICAgc3dpdGNoICh1bml0LnR5cGUpIHtcbiAgICAgICAgLy8gTkRSXG4gICAgICAgIGNhc2UgMTpcbiAgICAgICAgICB7XG4gICAgICAgICAgICBsZXQgaXNrZXkgPSBmYWxzZTtcbiAgICAgICAgICAgIHB1c2ggPSB0cnVlO1xuICAgICAgICAgICAgY29uc3QgZGF0YSA9IHVuaXQuZGF0YTtcbiAgICAgICAgICAgIC8vIG9ubHkgY2hlY2sgc2xpY2UgdHlwZSB0byBkZXRlY3QgS0YgaW4gY2FzZSBTUFMgZm91bmQgaW4gc2FtZSBwYWNrZXQgKGFueSBrZXlmcmFtZSBpcyBwcmVjZWRlZCBieSBTUFMgLi4uKVxuICAgICAgICAgICAgaWYgKHNwc2ZvdW5kICYmIGRhdGEubGVuZ3RoID4gNCkge1xuICAgICAgICAgICAgICAvLyByZXRyaWV2ZSBzbGljZSB0eXBlIGJ5IHBhcnNpbmcgYmVnaW5uaW5nIG9mIE5BTCB1bml0IChmb2xsb3cgSDI2NCBzcGVjLCBzbGljZV9oZWFkZXIgZGVmaW5pdGlvbikgdG8gZGV0ZWN0IGtleWZyYW1lIGVtYmVkZGVkIGluIE5EUlxuICAgICAgICAgICAgICBjb25zdCBzbGljZVR5cGUgPSBuZXcgRXhwR29sb21iKGRhdGEpLnJlYWRTbGljZVR5cGUoKTtcbiAgICAgICAgICAgICAgLy8gMiA6IEkgc2xpY2UsIDQgOiBTSSBzbGljZSwgNyA6IEkgc2xpY2UsIDk6IFNJIHNsaWNlXG4gICAgICAgICAgICAgIC8vIFNJIHNsaWNlIDogQSBzbGljZSB0aGF0IGlzIGNvZGVkIHVzaW5nIGludHJhIHByZWRpY3Rpb24gb25seSBhbmQgdXNpbmcgcXVhbnRpc2F0aW9uIG9mIHRoZSBwcmVkaWN0aW9uIHNhbXBsZXMuXG4gICAgICAgICAgICAgIC8vIEFuIFNJIHNsaWNlIGNhbiBiZSBjb2RlZCBzdWNoIHRoYXQgaXRzIGRlY29kZWQgc2FtcGxlcyBjYW4gYmUgY29uc3RydWN0ZWQgaWRlbnRpY2FsbHkgdG8gYW4gU1Agc2xpY2UuXG4gICAgICAgICAgICAgIC8vIEkgc2xpY2U6IEEgc2xpY2UgdGhhdCBpcyBub3QgYW4gU0kgc2xpY2UgdGhhdCBpcyBkZWNvZGVkIHVzaW5nIGludHJhIHByZWRpY3Rpb24gb25seS5cbiAgICAgICAgICAgICAgLy8gaWYgKHNsaWNlVHlwZSA9PT0gMiB8fCBzbGljZVR5cGUgPT09IDcpIHtcbiAgICAgICAgICAgICAgaWYgKHNsaWNlVHlwZSA9PT0gMiB8fCBzbGljZVR5cGUgPT09IDQgfHwgc2xpY2VUeXBlID09PSA3IHx8IHNsaWNlVHlwZSA9PT0gOSkge1xuICAgICAgICAgICAgICAgIGlza2V5ID0gdHJ1ZTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGlza2V5KSB7XG4gICAgICAgICAgICAgIHZhciBfVmlkZW9TYW1wbGU7XG4gICAgICAgICAgICAgIC8vIGlmIHdlIGhhdmUgbm9uLWtleWZyYW1lIGRhdGEgYWxyZWFkeSwgdGhhdCBjYW5ub3QgYmVsb25nIHRvIHRoZSBzYW1lIGZyYW1lIGFzIGEga2V5ZnJhbWUsIHNvIGZvcmNlIGEgcHVzaFxuICAgICAgICAgICAgICBpZiAoKF9WaWRlb1NhbXBsZSA9IFZpZGVvU2FtcGxlKSAhPSBudWxsICYmIF9WaWRlb1NhbXBsZS5mcmFtZSAmJiAhVmlkZW9TYW1wbGUua2V5KSB7XG4gICAgICAgICAgICAgICAgdGhpcy5wdXNoQWNjZXNzVW5pdChWaWRlb1NhbXBsZSwgdHJhY2spO1xuICAgICAgICAgICAgICAgIFZpZGVvU2FtcGxlID0gdGhpcy5WaWRlb1NhbXBsZSA9IG51bGw7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmICghVmlkZW9TYW1wbGUpIHtcbiAgICAgICAgICAgICAgVmlkZW9TYW1wbGUgPSB0aGlzLlZpZGVvU2FtcGxlID0gdGhpcy5jcmVhdGVWaWRlb1NhbXBsZSh0cnVlLCBwZXMucHRzLCBwZXMuZHRzLCAnJyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBWaWRlb1NhbXBsZS5mcmFtZSA9IHRydWU7XG4gICAgICAgICAgICBWaWRlb1NhbXBsZS5rZXkgPSBpc2tleTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgLy8gSURSXG4gICAgICAgICAgfVxuICAgICAgICBjYXNlIDU6XG4gICAgICAgICAgcHVzaCA9IHRydWU7XG4gICAgICAgICAgLy8gaGFuZGxlIFBFUyBub3Qgc3RhcnRpbmcgd2l0aCBBVURcbiAgICAgICAgICAvLyBpZiB3ZSBoYXZlIGZyYW1lIGRhdGEgYWxyZWFkeSwgdGhhdCBjYW5ub3QgYmVsb25nIHRvIHRoZSBzYW1lIGZyYW1lLCBzbyBmb3JjZSBhIHB1c2hcbiAgICAgICAgICBpZiAoKF9WaWRlb1NhbXBsZTIgPSBWaWRlb1NhbXBsZSkgIT0gbnVsbCAmJiBfVmlkZW9TYW1wbGUyLmZyYW1lICYmICFWaWRlb1NhbXBsZS5rZXkpIHtcbiAgICAgICAgICAgIHRoaXMucHVzaEFjY2Vzc1VuaXQoVmlkZW9TYW1wbGUsIHRyYWNrKTtcbiAgICAgICAgICAgIFZpZGVvU2FtcGxlID0gdGhpcy5WaWRlb1NhbXBsZSA9IG51bGw7XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmICghVmlkZW9TYW1wbGUpIHtcbiAgICAgICAgICAgIFZpZGVvU2FtcGxlID0gdGhpcy5WaWRlb1NhbXBsZSA9IHRoaXMuY3JlYXRlVmlkZW9TYW1wbGUodHJ1ZSwgcGVzLnB0cywgcGVzLmR0cywgJycpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBWaWRlb1NhbXBsZS5rZXkgPSB0cnVlO1xuICAgICAgICAgIFZpZGVvU2FtcGxlLmZyYW1lID0gdHJ1ZTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgLy8gU0VJXG4gICAgICAgIGNhc2UgNjpcbiAgICAgICAgICB7XG4gICAgICAgICAgICBwdXNoID0gdHJ1ZTtcbiAgICAgICAgICAgIHBhcnNlU0VJTWVzc2FnZUZyb21OQUx1KHVuaXQuZGF0YSwgMSwgcGVzLnB0cywgdGV4dFRyYWNrLnNhbXBsZXMpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAvLyBTUFNcbiAgICAgICAgICB9XG4gICAgICAgIGNhc2UgNzpcbiAgICAgICAgICB7XG4gICAgICAgICAgICB2YXIgX3RyYWNrJHBpeGVsUmF0aW8sIF90cmFjayRwaXhlbFJhdGlvMjtcbiAgICAgICAgICAgIHB1c2ggPSB0cnVlO1xuICAgICAgICAgICAgc3BzZm91bmQgPSB0cnVlO1xuICAgICAgICAgICAgY29uc3Qgc3BzID0gdW5pdC5kYXRhO1xuICAgICAgICAgICAgY29uc3QgZXhwR29sb21iRGVjb2RlciA9IG5ldyBFeHBHb2xvbWIoc3BzKTtcbiAgICAgICAgICAgIGNvbnN0IGNvbmZpZyA9IGV4cEdvbG9tYkRlY29kZXIucmVhZFNQUygpO1xuICAgICAgICAgICAgaWYgKCF0cmFjay5zcHMgfHwgdHJhY2sud2lkdGggIT09IGNvbmZpZy53aWR0aCB8fCB0cmFjay5oZWlnaHQgIT09IGNvbmZpZy5oZWlnaHQgfHwgKChfdHJhY2skcGl4ZWxSYXRpbyA9IHRyYWNrLnBpeGVsUmF0aW8pID09IG51bGwgPyB2b2lkIDAgOiBfdHJhY2skcGl4ZWxSYXRpb1swXSkgIT09IGNvbmZpZy5waXhlbFJhdGlvWzBdIHx8ICgoX3RyYWNrJHBpeGVsUmF0aW8yID0gdHJhY2sucGl4ZWxSYXRpbykgPT0gbnVsbCA/IHZvaWQgMCA6IF90cmFjayRwaXhlbFJhdGlvMlsxXSkgIT09IGNvbmZpZy5waXhlbFJhdGlvWzFdKSB7XG4gICAgICAgICAgICAgIHRyYWNrLndpZHRoID0gY29uZmlnLndpZHRoO1xuICAgICAgICAgICAgICB0cmFjay5oZWlnaHQgPSBjb25maWcuaGVpZ2h0O1xuICAgICAgICAgICAgICB0cmFjay5waXhlbFJhdGlvID0gY29uZmlnLnBpeGVsUmF0aW87XG4gICAgICAgICAgICAgIHRyYWNrLnNwcyA9IFtzcHNdO1xuICAgICAgICAgICAgICB0cmFjay5kdXJhdGlvbiA9IGR1cmF0aW9uO1xuICAgICAgICAgICAgICBjb25zdCBjb2RlY2FycmF5ID0gc3BzLnN1YmFycmF5KDEsIDQpO1xuICAgICAgICAgICAgICBsZXQgY29kZWNzdHJpbmcgPSAnYXZjMS4nO1xuICAgICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IDM7IGkrKykge1xuICAgICAgICAgICAgICAgIGxldCBoID0gY29kZWNhcnJheVtpXS50b1N0cmluZygxNik7XG4gICAgICAgICAgICAgICAgaWYgKGgubGVuZ3RoIDwgMikge1xuICAgICAgICAgICAgICAgICAgaCA9ICcwJyArIGg7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGNvZGVjc3RyaW5nICs9IGg7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgdHJhY2suY29kZWMgPSBjb2RlY3N0cmluZztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgLy8gUFBTXG4gICAgICAgIGNhc2UgODpcbiAgICAgICAgICBwdXNoID0gdHJ1ZTtcbiAgICAgICAgICB0cmFjay5wcHMgPSBbdW5pdC5kYXRhXTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgLy8gQVVEXG4gICAgICAgIGNhc2UgOTpcbiAgICAgICAgICBwdXNoID0gdHJ1ZTtcbiAgICAgICAgICB0cmFjay5hdWRGb3VuZCA9IHRydWU7XG4gICAgICAgICAgaWYgKFZpZGVvU2FtcGxlKSB7XG4gICAgICAgICAgICB0aGlzLnB1c2hBY2Nlc3NVbml0KFZpZGVvU2FtcGxlLCB0cmFjayk7XG4gICAgICAgICAgfVxuICAgICAgICAgIFZpZGVvU2FtcGxlID0gdGhpcy5WaWRlb1NhbXBsZSA9IHRoaXMuY3JlYXRlVmlkZW9TYW1wbGUoZmFsc2UsIHBlcy5wdHMsIHBlcy5kdHMsICcnKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgLy8gRmlsbGVyIERhdGFcbiAgICAgICAgY2FzZSAxMjpcbiAgICAgICAgICBwdXNoID0gdHJ1ZTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICBwdXNoID0gZmFsc2U7XG4gICAgICAgICAgaWYgKFZpZGVvU2FtcGxlKSB7XG4gICAgICAgICAgICBWaWRlb1NhbXBsZS5kZWJ1ZyArPSAndW5rbm93biBOQUwgJyArIHVuaXQudHlwZSArICcgJztcbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBpZiAoVmlkZW9TYW1wbGUgJiYgcHVzaCkge1xuICAgICAgICBjb25zdCB1bml0cyA9IFZpZGVvU2FtcGxlLnVuaXRzO1xuICAgICAgICB1bml0cy5wdXNoKHVuaXQpO1xuICAgICAgfVxuICAgIH0pO1xuICAgIC8vIGlmIGxhc3QgUEVTIHBhY2tldCwgcHVzaCBzYW1wbGVzXG4gICAgaWYgKGxhc3QgJiYgVmlkZW9TYW1wbGUpIHtcbiAgICAgIHRoaXMucHVzaEFjY2Vzc1VuaXQoVmlkZW9TYW1wbGUsIHRyYWNrKTtcbiAgICAgIHRoaXMuVmlkZW9TYW1wbGUgPSBudWxsO1xuICAgIH1cbiAgfVxuICBwYXJzZUFWQ05BTHUodHJhY2ssIGFycmF5KSB7XG4gICAgY29uc3QgbGVuID0gYXJyYXkuYnl0ZUxlbmd0aDtcbiAgICBsZXQgc3RhdGUgPSB0cmFjay5uYWx1U3RhdGUgfHwgMDtcbiAgICBjb25zdCBsYXN0U3RhdGUgPSBzdGF0ZTtcbiAgICBjb25zdCB1bml0cyA9IFtdO1xuICAgIGxldCBpID0gMDtcbiAgICBsZXQgdmFsdWU7XG4gICAgbGV0IG92ZXJmbG93O1xuICAgIGxldCB1bml0VHlwZTtcbiAgICBsZXQgbGFzdFVuaXRTdGFydCA9IC0xO1xuICAgIGxldCBsYXN0VW5pdFR5cGUgPSAwO1xuICAgIC8vIGxvZ2dlci5sb2coJ1BFUzonICsgSGV4LmhleER1bXAoYXJyYXkpKTtcblxuICAgIGlmIChzdGF0ZSA9PT0gLTEpIHtcbiAgICAgIC8vIHNwZWNpYWwgdXNlIGNhc2Ugd2hlcmUgd2UgZm91bmQgMyBvciA0LWJ5dGUgc3RhcnQgY29kZXMgZXhhY3RseSBhdCB0aGUgZW5kIG9mIHByZXZpb3VzIFBFUyBwYWNrZXRcbiAgICAgIGxhc3RVbml0U3RhcnQgPSAwO1xuICAgICAgLy8gTkFMdSB0eXBlIGlzIHZhbHVlIHJlYWQgZnJvbSBvZmZzZXQgMFxuICAgICAgbGFzdFVuaXRUeXBlID0gYXJyYXlbMF0gJiAweDFmO1xuICAgICAgc3RhdGUgPSAwO1xuICAgICAgaSA9IDE7XG4gICAgfVxuICAgIHdoaWxlIChpIDwgbGVuKSB7XG4gICAgICB2YWx1ZSA9IGFycmF5W2krK107XG4gICAgICAvLyBvcHRpbWl6YXRpb24uIHN0YXRlIDAgYW5kIDEgYXJlIHRoZSBwcmVkb21pbmFudCBjYXNlLiBsZXQncyBoYW5kbGUgdGhlbSBvdXRzaWRlIG9mIHRoZSBzd2l0Y2gvY2FzZVxuICAgICAgaWYgKCFzdGF0ZSkge1xuICAgICAgICBzdGF0ZSA9IHZhbHVlID8gMCA6IDE7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgaWYgKHN0YXRlID09PSAxKSB7XG4gICAgICAgIHN0YXRlID0gdmFsdWUgPyAwIDogMjtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG4gICAgICAvLyBoZXJlIHdlIGhhdmUgc3RhdGUgZWl0aGVyIGVxdWFsIHRvIDIgb3IgM1xuICAgICAgaWYgKCF2YWx1ZSkge1xuICAgICAgICBzdGF0ZSA9IDM7XG4gICAgICB9IGVsc2UgaWYgKHZhbHVlID09PSAxKSB7XG4gICAgICAgIG92ZXJmbG93ID0gaSAtIHN0YXRlIC0gMTtcbiAgICAgICAgaWYgKGxhc3RVbml0U3RhcnQgPj0gMCkge1xuICAgICAgICAgIGNvbnN0IHVuaXQgPSB7XG4gICAgICAgICAgICBkYXRhOiBhcnJheS5zdWJhcnJheShsYXN0VW5pdFN0YXJ0LCBvdmVyZmxvdyksXG4gICAgICAgICAgICB0eXBlOiBsYXN0VW5pdFR5cGVcbiAgICAgICAgICB9O1xuICAgICAgICAgIC8vIGxvZ2dlci5sb2coJ3B1c2hpbmcgTkFMVSwgdHlwZS9zaXplOicgKyB1bml0LnR5cGUgKyAnLycgKyB1bml0LmRhdGEuYnl0ZUxlbmd0aCk7XG4gICAgICAgICAgdW5pdHMucHVzaCh1bml0KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBsYXN0VW5pdFN0YXJ0IGlzIHVuZGVmaW5lZCA9PiB0aGlzIGlzIHRoZSBmaXJzdCBzdGFydCBjb2RlIGZvdW5kIGluIHRoaXMgUEVTIHBhY2tldFxuICAgICAgICAgIC8vIGZpcnN0IGNoZWNrIGlmIHN0YXJ0IGNvZGUgZGVsaW1pdGVyIGlzIG92ZXJsYXBwaW5nIGJldHdlZW4gMiBQRVMgcGFja2V0cyxcbiAgICAgICAgICAvLyBpZSBpdCBzdGFydGVkIGluIGxhc3QgcGFja2V0IChsYXN0U3RhdGUgbm90IHplcm8pXG4gICAgICAgICAgLy8gYW5kIGVuZGVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhpcyBQRVMgcGFja2V0IChpIDw9IDQgLSBsYXN0U3RhdGUpXG4gICAgICAgICAgY29uc3QgbGFzdFVuaXQgPSB0aGlzLmdldExhc3ROYWxVbml0KHRyYWNrLnNhbXBsZXMpO1xuICAgICAgICAgIGlmIChsYXN0VW5pdCkge1xuICAgICAgICAgICAgaWYgKGxhc3RTdGF0ZSAmJiBpIDw9IDQgLSBsYXN0U3RhdGUpIHtcbiAgICAgICAgICAgICAgLy8gc3RhcnQgZGVsaW1pdGVyIG92ZXJsYXBwaW5nIGJldHdlZW4gUEVTIHBhY2tldHNcbiAgICAgICAgICAgICAgLy8gc3RyaXAgc3RhcnQgZGVsaW1pdGVyIGJ5dGVzIGZyb20gdGhlIGVuZCBvZiBsYXN0IE5BTCB1bml0XG4gICAgICAgICAgICAgIC8vIGNoZWNrIGlmIGxhc3RVbml0IGhhZCBhIHN0YXRlIGRpZmZlcmVudCBmcm9tIHplcm9cbiAgICAgICAgICAgICAgaWYgKGxhc3RVbml0LnN0YXRlKSB7XG4gICAgICAgICAgICAgICAgLy8gc3RyaXAgbGFzdCBieXRlc1xuICAgICAgICAgICAgICAgIGxhc3RVbml0LmRhdGEgPSBsYXN0VW5pdC5kYXRhLnN1YmFycmF5KDAsIGxhc3RVbml0LmRhdGEuYnl0ZUxlbmd0aCAtIGxhc3RTdGF0ZSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIElmIE5BTCB1bml0cyBhcmUgbm90IHN0YXJ0aW5nIHJpZ2h0IGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhlIFBFUyBwYWNrZXQsIHB1c2ggcHJlY2VkaW5nIGRhdGEgaW50byBwcmV2aW91cyBOQUwgdW5pdC5cblxuICAgICAgICAgICAgaWYgKG92ZXJmbG93ID4gMCkge1xuICAgICAgICAgICAgICAvLyBsb2dnZXIubG9nKCdmaXJzdCBOQUxVIGZvdW5kIHdpdGggb3ZlcmZsb3c6JyArIG92ZXJmbG93KTtcbiAgICAgICAgICAgICAgbGFzdFVuaXQuZGF0YSA9IGFwcGVuZFVpbnQ4QXJyYXkobGFzdFVuaXQuZGF0YSwgYXJyYXkuc3ViYXJyYXkoMCwgb3ZlcmZsb3cpKTtcbiAgICAgICAgICAgICAgbGFzdFVuaXQuc3RhdGUgPSAwO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBjaGVjayBpZiB3ZSBjYW4gcmVhZCB1bml0IHR5cGVcbiAgICAgICAgaWYgKGkgPCBsZW4pIHtcbiAgICAgICAgICB1bml0VHlwZSA9IGFycmF5W2ldICYgMHgxZjtcbiAgICAgICAgICAvLyBsb2dnZXIubG9nKCdmaW5kIE5BTFUgQCBvZmZzZXQ6JyArIGkgKyAnLHR5cGU6JyArIHVuaXRUeXBlKTtcbiAgICAgICAgICBsYXN0VW5pdFN0YXJ0ID0gaTtcbiAgICAgICAgICBsYXN0VW5pdFR5cGUgPSB1bml0VHlwZTtcbiAgICAgICAgICBzdGF0ZSA9IDA7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gbm90IGVub3VnaCBieXRlIHRvIHJlYWQgdW5pdCB0eXBlLiBsZXQncyByZWFkIGl0IG9uIG5leHQgUEVTIHBhcnNpbmdcbiAgICAgICAgICBzdGF0ZSA9IC0xO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzdGF0ZSA9IDA7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChsYXN0VW5pdFN0YXJ0ID49IDAgJiYgc3RhdGUgPj0gMCkge1xuICAgICAgY29uc3QgdW5pdCA9IHtcbiAgICAgICAgZGF0YTogYXJyYXkuc3ViYXJyYXkobGFzdFVuaXRTdGFydCwgbGVuKSxcbiAgICAgICAgdHlwZTogbGFzdFVuaXRUeXBlLFxuICAgICAgICBzdGF0ZTogc3RhdGVcbiAgICAgIH07XG4gICAgICB1bml0cy5wdXNoKHVuaXQpO1xuICAgICAgLy8gbG9nZ2VyLmxvZygncHVzaGluZyBOQUxVLCB0eXBlL3NpemUvc3RhdGU6JyArIHVuaXQudHlwZSArICcvJyArIHVuaXQuZGF0YS5ieXRlTGVuZ3RoICsgJy8nICsgc3RhdGUpO1xuICAgIH1cbiAgICAvLyBubyBOQUx1IGZvdW5kXG4gICAgaWYgKHVuaXRzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgLy8gYXBwZW5kIHBlcy5kYXRhIHRvIHByZXZpb3VzIE5BTCB1bml0XG4gICAgICBjb25zdCBsYXN0VW5pdCA9IHRoaXMuZ2V0TGFzdE5hbFVuaXQodHJhY2suc2FtcGxlcyk7XG4gICAgICBpZiAobGFzdFVuaXQpIHtcbiAgICAgICAgbGFzdFVuaXQuZGF0YSA9IGFwcGVuZFVpbnQ4QXJyYXkobGFzdFVuaXQuZGF0YSwgYXJyYXkpO1xuICAgICAgfVxuICAgIH1cbiAgICB0cmFjay5uYWx1U3RhdGUgPSBzdGF0ZTtcbiAgICByZXR1cm4gdW5pdHM7XG4gIH1cbn1cblxuLyoqXG4gKiBTQU1QTEUtQUVTIGRlY3J5cHRlclxuICovXG5cbmNsYXNzIFNhbXBsZUFlc0RlY3J5cHRlciB7XG4gIGNvbnN0cnVjdG9yKG9ic2VydmVyLCBjb25maWcsIGtleURhdGEpIHtcbiAgICB0aGlzLmtleURhdGEgPSB2b2lkIDA7XG4gICAgdGhpcy5kZWNyeXB0ZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5rZXlEYXRhID0ga2V5RGF0YTtcbiAgICB0aGlzLmRlY3J5cHRlciA9IG5ldyBEZWNyeXB0ZXIoY29uZmlnLCB7XG4gICAgICByZW1vdmVQS0NTN1BhZGRpbmc6IGZhbHNlXG4gICAgfSk7XG4gIH1cbiAgZGVjcnlwdEJ1ZmZlcihlbmNyeXB0ZWREYXRhKSB7XG4gICAgcmV0dXJuIHRoaXMuZGVjcnlwdGVyLmRlY3J5cHQoZW5jcnlwdGVkRGF0YSwgdGhpcy5rZXlEYXRhLmtleS5idWZmZXIsIHRoaXMua2V5RGF0YS5pdi5idWZmZXIpO1xuICB9XG5cbiAgLy8gQUFDIC0gZW5jcnlwdCBhbGwgZnVsbCAxNiBieXRlcyBibG9ja3Mgc3RhcnRpbmcgZnJvbSBvZmZzZXQgMTZcbiAgZGVjcnlwdEFhY1NhbXBsZShzYW1wbGVzLCBzYW1wbGVJbmRleCwgY2FsbGJhY2spIHtcbiAgICBjb25zdCBjdXJVbml0ID0gc2FtcGxlc1tzYW1wbGVJbmRleF0udW5pdDtcbiAgICBpZiAoY3VyVW5pdC5sZW5ndGggPD0gMTYpIHtcbiAgICAgIC8vIE5vIGVuY3J5cHRlZCBwb3J0aW9uIGluIHRoaXMgc2FtcGxlIChmaXJzdCAxNiBieXRlcyBpcyBub3RcbiAgICAgIC8vIGVuY3J5cHRlZCwgc2VlIGh0dHBzOi8vZGV2ZWxvcGVyLmFwcGxlLmNvbS9saWJyYXJ5L2FyY2hpdmUvZG9jdW1lbnRhdGlvbi9BdWRpb1ZpZGVvL0NvbmNlcHR1YWwvSExTX1NhbXBsZV9FbmNyeXB0aW9uL0VuY3J5cHRpb24vRW5jcnlwdGlvbi5odG1sKSxcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgZW5jcnlwdGVkRGF0YSA9IGN1clVuaXQuc3ViYXJyYXkoMTYsIGN1clVuaXQubGVuZ3RoIC0gY3VyVW5pdC5sZW5ndGggJSAxNik7XG4gICAgY29uc3QgZW5jcnlwdGVkQnVmZmVyID0gZW5jcnlwdGVkRGF0YS5idWZmZXIuc2xpY2UoZW5jcnlwdGVkRGF0YS5ieXRlT2Zmc2V0LCBlbmNyeXB0ZWREYXRhLmJ5dGVPZmZzZXQgKyBlbmNyeXB0ZWREYXRhLmxlbmd0aCk7XG4gICAgdGhpcy5kZWNyeXB0QnVmZmVyKGVuY3J5cHRlZEJ1ZmZlcikudGhlbihkZWNyeXB0ZWRCdWZmZXIgPT4ge1xuICAgICAgY29uc3QgZGVjcnlwdGVkRGF0YSA9IG5ldyBVaW50OEFycmF5KGRlY3J5cHRlZEJ1ZmZlcik7XG4gICAgICBjdXJVbml0LnNldChkZWNyeXB0ZWREYXRhLCAxNik7XG4gICAgICBpZiAoIXRoaXMuZGVjcnlwdGVyLmlzU3luYygpKSB7XG4gICAgICAgIHRoaXMuZGVjcnlwdEFhY1NhbXBsZXMoc2FtcGxlcywgc2FtcGxlSW5kZXggKyAxLCBjYWxsYmFjayk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbiAgZGVjcnlwdEFhY1NhbXBsZXMoc2FtcGxlcywgc2FtcGxlSW5kZXgsIGNhbGxiYWNrKSB7XG4gICAgZm9yICg7OyBzYW1wbGVJbmRleCsrKSB7XG4gICAgICBpZiAoc2FtcGxlSW5kZXggPj0gc2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgICAgY2FsbGJhY2soKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgaWYgKHNhbXBsZXNbc2FtcGxlSW5kZXhdLnVuaXQubGVuZ3RoIDwgMzIpIHtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG4gICAgICB0aGlzLmRlY3J5cHRBYWNTYW1wbGUoc2FtcGxlcywgc2FtcGxlSW5kZXgsIGNhbGxiYWNrKTtcbiAgICAgIGlmICghdGhpcy5kZWNyeXB0ZXIuaXNTeW5jKCkpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIEFWQyAtIGVuY3J5cHQgb25lIDE2IGJ5dGVzIGJsb2NrIG91dCBvZiB0ZW4sIHN0YXJ0aW5nIGZyb20gb2Zmc2V0IDMyXG4gIGdldEF2Y0VuY3J5cHRlZERhdGEoZGVjb2RlZERhdGEpIHtcbiAgICBjb25zdCBlbmNyeXB0ZWREYXRhTGVuID0gTWF0aC5mbG9vcigoZGVjb2RlZERhdGEubGVuZ3RoIC0gNDgpIC8gMTYwKSAqIDE2ICsgMTY7XG4gICAgY29uc3QgZW5jcnlwdGVkRGF0YSA9IG5ldyBJbnQ4QXJyYXkoZW5jcnlwdGVkRGF0YUxlbik7XG4gICAgbGV0IG91dHB1dFBvcyA9IDA7XG4gICAgZm9yIChsZXQgaW5wdXRQb3MgPSAzMjsgaW5wdXRQb3MgPCBkZWNvZGVkRGF0YS5sZW5ndGggLSAxNjsgaW5wdXRQb3MgKz0gMTYwLCBvdXRwdXRQb3MgKz0gMTYpIHtcbiAgICAgIGVuY3J5cHRlZERhdGEuc2V0KGRlY29kZWREYXRhLnN1YmFycmF5KGlucHV0UG9zLCBpbnB1dFBvcyArIDE2KSwgb3V0cHV0UG9zKTtcbiAgICB9XG4gICAgcmV0dXJuIGVuY3J5cHRlZERhdGE7XG4gIH1cbiAgZ2V0QXZjRGVjcnlwdGVkVW5pdChkZWNvZGVkRGF0YSwgZGVjcnlwdGVkRGF0YSkge1xuICAgIGNvbnN0IHVpbnQ4RGVjcnlwdGVkRGF0YSA9IG5ldyBVaW50OEFycmF5KGRlY3J5cHRlZERhdGEpO1xuICAgIGxldCBpbnB1dFBvcyA9IDA7XG4gICAgZm9yIChsZXQgb3V0cHV0UG9zID0gMzI7IG91dHB1dFBvcyA8IGRlY29kZWREYXRhLmxlbmd0aCAtIDE2OyBvdXRwdXRQb3MgKz0gMTYwLCBpbnB1dFBvcyArPSAxNikge1xuICAgICAgZGVjb2RlZERhdGEuc2V0KHVpbnQ4RGVjcnlwdGVkRGF0YS5zdWJhcnJheShpbnB1dFBvcywgaW5wdXRQb3MgKyAxNiksIG91dHB1dFBvcyk7XG4gICAgfVxuICAgIHJldHVybiBkZWNvZGVkRGF0YTtcbiAgfVxuICBkZWNyeXB0QXZjU2FtcGxlKHNhbXBsZXMsIHNhbXBsZUluZGV4LCB1bml0SW5kZXgsIGNhbGxiYWNrLCBjdXJVbml0KSB7XG4gICAgY29uc3QgZGVjb2RlZERhdGEgPSBkaXNjYXJkRVBCKGN1clVuaXQuZGF0YSk7XG4gICAgY29uc3QgZW5jcnlwdGVkRGF0YSA9IHRoaXMuZ2V0QXZjRW5jcnlwdGVkRGF0YShkZWNvZGVkRGF0YSk7XG4gICAgdGhpcy5kZWNyeXB0QnVmZmVyKGVuY3J5cHRlZERhdGEuYnVmZmVyKS50aGVuKGRlY3J5cHRlZEJ1ZmZlciA9PiB7XG4gICAgICBjdXJVbml0LmRhdGEgPSB0aGlzLmdldEF2Y0RlY3J5cHRlZFVuaXQoZGVjb2RlZERhdGEsIGRlY3J5cHRlZEJ1ZmZlcik7XG4gICAgICBpZiAoIXRoaXMuZGVjcnlwdGVyLmlzU3luYygpKSB7XG4gICAgICAgIHRoaXMuZGVjcnlwdEF2Y1NhbXBsZXMoc2FtcGxlcywgc2FtcGxlSW5kZXgsIHVuaXRJbmRleCArIDEsIGNhbGxiYWNrKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuICBkZWNyeXB0QXZjU2FtcGxlcyhzYW1wbGVzLCBzYW1wbGVJbmRleCwgdW5pdEluZGV4LCBjYWxsYmFjaykge1xuICAgIGlmIChzYW1wbGVzIGluc3RhbmNlb2YgVWludDhBcnJheSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdDYW5ub3QgZGVjcnlwdCBzYW1wbGVzIG9mIHR5cGUgVWludDhBcnJheScpO1xuICAgIH1cbiAgICBmb3IgKDs7IHNhbXBsZUluZGV4KyssIHVuaXRJbmRleCA9IDApIHtcbiAgICAgIGlmIChzYW1wbGVJbmRleCA+PSBzYW1wbGVzLmxlbmd0aCkge1xuICAgICAgICBjYWxsYmFjaygpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBjdXJVbml0cyA9IHNhbXBsZXNbc2FtcGxlSW5kZXhdLnVuaXRzO1xuICAgICAgZm9yICg7OyB1bml0SW5kZXgrKykge1xuICAgICAgICBpZiAodW5pdEluZGV4ID49IGN1clVuaXRzLmxlbmd0aCkge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGN1clVuaXQgPSBjdXJVbml0c1t1bml0SW5kZXhdO1xuICAgICAgICBpZiAoY3VyVW5pdC5kYXRhLmxlbmd0aCA8PSA0OCB8fCBjdXJVbml0LnR5cGUgIT09IDEgJiYgY3VyVW5pdC50eXBlICE9PSA1KSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5kZWNyeXB0QXZjU2FtcGxlKHNhbXBsZXMsIHNhbXBsZUluZGV4LCB1bml0SW5kZXgsIGNhbGxiYWNrLCBjdXJVbml0KTtcbiAgICAgICAgaWYgKCF0aGlzLmRlY3J5cHRlci5pc1N5bmMoKSkge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG5jb25zdCBQQUNLRVRfTEVOR1RIID0gMTg4O1xuY2xhc3MgVFNEZW11eGVyIHtcbiAgY29uc3RydWN0b3Iob2JzZXJ2ZXIsIGNvbmZpZywgdHlwZVN1cHBvcnRlZCkge1xuICAgIHRoaXMub2JzZXJ2ZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5jb25maWcgPSB2b2lkIDA7XG4gICAgdGhpcy50eXBlU3VwcG9ydGVkID0gdm9pZCAwO1xuICAgIHRoaXMuc2FtcGxlQWVzID0gbnVsbDtcbiAgICB0aGlzLnBtdFBhcnNlZCA9IGZhbHNlO1xuICAgIHRoaXMuYXVkaW9Db2RlYyA9IHZvaWQgMDtcbiAgICB0aGlzLnZpZGVvQ29kZWMgPSB2b2lkIDA7XG4gICAgdGhpcy5fZHVyYXRpb24gPSAwO1xuICAgIHRoaXMuX3BtdElkID0gLTE7XG4gICAgdGhpcy5fdmlkZW9UcmFjayA9IHZvaWQgMDtcbiAgICB0aGlzLl9hdWRpb1RyYWNrID0gdm9pZCAwO1xuICAgIHRoaXMuX2lkM1RyYWNrID0gdm9pZCAwO1xuICAgIHRoaXMuX3R4dFRyYWNrID0gdm9pZCAwO1xuICAgIHRoaXMuYWFjT3ZlckZsb3cgPSBudWxsO1xuICAgIHRoaXMucmVtYWluZGVyRGF0YSA9IG51bGw7XG4gICAgdGhpcy52aWRlb1BhcnNlciA9IHZvaWQgMDtcbiAgICB0aGlzLm9ic2VydmVyID0gb2JzZXJ2ZXI7XG4gICAgdGhpcy5jb25maWcgPSBjb25maWc7XG4gICAgdGhpcy50eXBlU3VwcG9ydGVkID0gdHlwZVN1cHBvcnRlZDtcbiAgICB0aGlzLnZpZGVvUGFyc2VyID0gbmV3IEF2Y1ZpZGVvUGFyc2VyKCk7XG4gIH1cbiAgc3RhdGljIHByb2JlKGRhdGEpIHtcbiAgICBjb25zdCBzeW5jT2Zmc2V0ID0gVFNEZW11eGVyLnN5bmNPZmZzZXQoZGF0YSk7XG4gICAgaWYgKHN5bmNPZmZzZXQgPiAwKSB7XG4gICAgICBsb2dnZXIud2FybihgTVBFRzItVFMgZGV0ZWN0ZWQgYnV0IGZpcnN0IHN5bmMgd29yZCBmb3VuZCBAIG9mZnNldCAke3N5bmNPZmZzZXR9YCk7XG4gICAgfVxuICAgIHJldHVybiBzeW5jT2Zmc2V0ICE9PSAtMTtcbiAgfVxuICBzdGF0aWMgc3luY09mZnNldChkYXRhKSB7XG4gICAgY29uc3QgbGVuZ3RoID0gZGF0YS5sZW5ndGg7XG4gICAgbGV0IHNjYW53aW5kb3cgPSBNYXRoLm1pbihQQUNLRVRfTEVOR1RIICogNSwgbGVuZ3RoIC0gUEFDS0VUX0xFTkdUSCkgKyAxO1xuICAgIGxldCBpID0gMDtcbiAgICB3aGlsZSAoaSA8IHNjYW53aW5kb3cpIHtcbiAgICAgIC8vIGEgVFMgaW5pdCBzZWdtZW50IHNob3VsZCBjb250YWluIGF0IGxlYXN0IDIgVFMgcGFja2V0czogUEFUIGFuZCBQTVQsIGVhY2ggc3RhcnRpbmcgd2l0aCAweDQ3XG4gICAgICBsZXQgZm91bmRQYXQgPSBmYWxzZTtcbiAgICAgIGxldCBwYWNrZXRTdGFydCA9IC0xO1xuICAgICAgbGV0IHRzUGFja2V0cyA9IDA7XG4gICAgICBmb3IgKGxldCBqID0gaTsgaiA8IGxlbmd0aDsgaiArPSBQQUNLRVRfTEVOR1RIKSB7XG4gICAgICAgIGlmIChkYXRhW2pdID09PSAweDQ3ICYmIChsZW5ndGggLSBqID09PSBQQUNLRVRfTEVOR1RIIHx8IGRhdGFbaiArIFBBQ0tFVF9MRU5HVEhdID09PSAweDQ3KSkge1xuICAgICAgICAgIHRzUGFja2V0cysrO1xuICAgICAgICAgIGlmIChwYWNrZXRTdGFydCA9PT0gLTEpIHtcbiAgICAgICAgICAgIHBhY2tldFN0YXJ0ID0gajtcbiAgICAgICAgICAgIC8vIEZpcnN0IHN5bmMgd29yZCBmb3VuZCBhdCBvZmZzZXQsIGluY3JlYXNlIHNjYW4gbGVuZ3RoICgjNTI1MSlcbiAgICAgICAgICAgIGlmIChwYWNrZXRTdGFydCAhPT0gMCkge1xuICAgICAgICAgICAgICBzY2Fud2luZG93ID0gTWF0aC5taW4ocGFja2V0U3RhcnQgKyBQQUNLRVRfTEVOR1RIICogOTksIGRhdGEubGVuZ3RoIC0gUEFDS0VUX0xFTkdUSCkgKyAxO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoIWZvdW5kUGF0KSB7XG4gICAgICAgICAgICBmb3VuZFBhdCA9IHBhcnNlUElEKGRhdGEsIGopID09PSAwO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBTeW5jIHdvcmQgZm91bmQgYXQgMCB3aXRoIDMgcGFja2V0cywgb3IgZm91bmQgYXQgb2Zmc2V0IGxlYXN0IDIgcGFja2V0cyB1cCB0byBzY2Fud2luZG93ICgjNTUwMSlcbiAgICAgICAgICBpZiAoZm91bmRQYXQgJiYgdHNQYWNrZXRzID4gMSAmJiAocGFja2V0U3RhcnQgPT09IDAgJiYgdHNQYWNrZXRzID4gMiB8fCBqICsgUEFDS0VUX0xFTkdUSCA+IHNjYW53aW5kb3cpKSB7XG4gICAgICAgICAgICByZXR1cm4gcGFja2V0U3RhcnQ7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKHRzUGFja2V0cykge1xuICAgICAgICAgIC8vIEV4aXQgaWYgc3luYyB3b3JkIGZvdW5kLCBidXQgZG9lcyBub3QgY29udGFpbiBjb250aWd1b3VzIHBhY2tldHNcbiAgICAgICAgICByZXR1cm4gLTE7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGkrKztcbiAgICB9XG4gICAgcmV0dXJuIC0xO1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYSB0cmFjayBtb2RlbCBpbnRlcm5hbCB0byBkZW11eGVyIHVzZWQgdG8gZHJpdmUgcmVtdXhpbmcgaW5wdXRcbiAgICovXG4gIHN0YXRpYyBjcmVhdGVUcmFjayh0eXBlLCBkdXJhdGlvbikge1xuICAgIHJldHVybiB7XG4gICAgICBjb250YWluZXI6IHR5cGUgPT09ICd2aWRlbycgfHwgdHlwZSA9PT0gJ2F1ZGlvJyA/ICd2aWRlby9tcDJ0JyA6IHVuZGVmaW5lZCxcbiAgICAgIHR5cGUsXG4gICAgICBpZDogUmVtdXhlclRyYWNrSWRDb25maWdbdHlwZV0sXG4gICAgICBwaWQ6IC0xLFxuICAgICAgaW5wdXRUaW1lU2NhbGU6IDkwMDAwLFxuICAgICAgc2VxdWVuY2VOdW1iZXI6IDAsXG4gICAgICBzYW1wbGVzOiBbXSxcbiAgICAgIGRyb3BwZWQ6IDAsXG4gICAgICBkdXJhdGlvbjogdHlwZSA9PT0gJ2F1ZGlvJyA/IGR1cmF0aW9uIDogdW5kZWZpbmVkXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplcyBhIG5ldyBpbml0IHNlZ21lbnQgb24gdGhlIGRlbXV4ZXIvcmVtdXhlciBpbnRlcmZhY2UuIE5lZWRlZCBmb3IgZGlzY29udGludWl0aWVzL3RyYWNrLXN3aXRjaGVzIChvciBhdCBzdHJlYW0gc3RhcnQpXG4gICAqIFJlc2V0cyBhbGwgaW50ZXJuYWwgdHJhY2sgaW5zdGFuY2VzIG9mIHRoZSBkZW11eGVyLlxuICAgKi9cbiAgcmVzZXRJbml0U2VnbWVudChpbml0U2VnbWVudCwgYXVkaW9Db2RlYywgdmlkZW9Db2RlYywgdHJhY2tEdXJhdGlvbikge1xuICAgIHRoaXMucG10UGFyc2VkID0gZmFsc2U7XG4gICAgdGhpcy5fcG10SWQgPSAtMTtcbiAgICB0aGlzLl92aWRlb1RyYWNrID0gVFNEZW11eGVyLmNyZWF0ZVRyYWNrKCd2aWRlbycpO1xuICAgIHRoaXMuX2F1ZGlvVHJhY2sgPSBUU0RlbXV4ZXIuY3JlYXRlVHJhY2soJ2F1ZGlvJywgdHJhY2tEdXJhdGlvbik7XG4gICAgdGhpcy5faWQzVHJhY2sgPSBUU0RlbXV4ZXIuY3JlYXRlVHJhY2soJ2lkMycpO1xuICAgIHRoaXMuX3R4dFRyYWNrID0gVFNEZW11eGVyLmNyZWF0ZVRyYWNrKCd0ZXh0Jyk7XG4gICAgdGhpcy5fYXVkaW9UcmFjay5zZWdtZW50Q29kZWMgPSAnYWFjJztcblxuICAgIC8vIGZsdXNoIGFueSBwYXJ0aWFsIGNvbnRlbnRcbiAgICB0aGlzLmFhY092ZXJGbG93ID0gbnVsbDtcbiAgICB0aGlzLnJlbWFpbmRlckRhdGEgPSBudWxsO1xuICAgIHRoaXMuYXVkaW9Db2RlYyA9IGF1ZGlvQ29kZWM7XG4gICAgdGhpcy52aWRlb0NvZGVjID0gdmlkZW9Db2RlYztcbiAgICB0aGlzLl9kdXJhdGlvbiA9IHRyYWNrRHVyYXRpb247XG4gIH1cbiAgcmVzZXRUaW1lU3RhbXAoKSB7fVxuICByZXNldENvbnRpZ3VpdHkoKSB7XG4gICAgY29uc3Qge1xuICAgICAgX2F1ZGlvVHJhY2ssXG4gICAgICBfdmlkZW9UcmFjayxcbiAgICAgIF9pZDNUcmFja1xuICAgIH0gPSB0aGlzO1xuICAgIGlmIChfYXVkaW9UcmFjaykge1xuICAgICAgX2F1ZGlvVHJhY2sucGVzRGF0YSA9IG51bGw7XG4gICAgfVxuICAgIGlmIChfdmlkZW9UcmFjaykge1xuICAgICAgX3ZpZGVvVHJhY2sucGVzRGF0YSA9IG51bGw7XG4gICAgfVxuICAgIGlmIChfaWQzVHJhY2spIHtcbiAgICAgIF9pZDNUcmFjay5wZXNEYXRhID0gbnVsbDtcbiAgICB9XG4gICAgdGhpcy5hYWNPdmVyRmxvdyA9IG51bGw7XG4gICAgdGhpcy5yZW1haW5kZXJEYXRhID0gbnVsbDtcbiAgfVxuICBkZW11eChkYXRhLCB0aW1lT2Zmc2V0LCBpc1NhbXBsZUFlcyA9IGZhbHNlLCBmbHVzaCA9IGZhbHNlKSB7XG4gICAgaWYgKCFpc1NhbXBsZUFlcykge1xuICAgICAgdGhpcy5zYW1wbGVBZXMgPSBudWxsO1xuICAgIH1cbiAgICBsZXQgcGVzO1xuICAgIGNvbnN0IHZpZGVvVHJhY2sgPSB0aGlzLl92aWRlb1RyYWNrO1xuICAgIGNvbnN0IGF1ZGlvVHJhY2sgPSB0aGlzLl9hdWRpb1RyYWNrO1xuICAgIGNvbnN0IGlkM1RyYWNrID0gdGhpcy5faWQzVHJhY2s7XG4gICAgY29uc3QgdGV4dFRyYWNrID0gdGhpcy5fdHh0VHJhY2s7XG4gICAgbGV0IHZpZGVvUGlkID0gdmlkZW9UcmFjay5waWQ7XG4gICAgbGV0IHZpZGVvRGF0YSA9IHZpZGVvVHJhY2sucGVzRGF0YTtcbiAgICBsZXQgYXVkaW9QaWQgPSBhdWRpb1RyYWNrLnBpZDtcbiAgICBsZXQgaWQzUGlkID0gaWQzVHJhY2sucGlkO1xuICAgIGxldCBhdWRpb0RhdGEgPSBhdWRpb1RyYWNrLnBlc0RhdGE7XG4gICAgbGV0IGlkM0RhdGEgPSBpZDNUcmFjay5wZXNEYXRhO1xuICAgIGxldCB1bmtub3duUElEID0gbnVsbDtcbiAgICBsZXQgcG10UGFyc2VkID0gdGhpcy5wbXRQYXJzZWQ7XG4gICAgbGV0IHBtdElkID0gdGhpcy5fcG10SWQ7XG4gICAgbGV0IGxlbiA9IGRhdGEubGVuZ3RoO1xuICAgIGlmICh0aGlzLnJlbWFpbmRlckRhdGEpIHtcbiAgICAgIGRhdGEgPSBhcHBlbmRVaW50OEFycmF5KHRoaXMucmVtYWluZGVyRGF0YSwgZGF0YSk7XG4gICAgICBsZW4gPSBkYXRhLmxlbmd0aDtcbiAgICAgIHRoaXMucmVtYWluZGVyRGF0YSA9IG51bGw7XG4gICAgfVxuICAgIGlmIChsZW4gPCBQQUNLRVRfTEVOR1RIICYmICFmbHVzaCkge1xuICAgICAgdGhpcy5yZW1haW5kZXJEYXRhID0gZGF0YTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGF1ZGlvVHJhY2ssXG4gICAgICAgIHZpZGVvVHJhY2ssXG4gICAgICAgIGlkM1RyYWNrLFxuICAgICAgICB0ZXh0VHJhY2tcbiAgICAgIH07XG4gICAgfVxuICAgIGNvbnN0IHN5bmNPZmZzZXQgPSBNYXRoLm1heCgwLCBUU0RlbXV4ZXIuc3luY09mZnNldChkYXRhKSk7XG4gICAgbGVuIC09IChsZW4gLSBzeW5jT2Zmc2V0KSAlIFBBQ0tFVF9MRU5HVEg7XG4gICAgaWYgKGxlbiA8IGRhdGEuYnl0ZUxlbmd0aCAmJiAhZmx1c2gpIHtcbiAgICAgIHRoaXMucmVtYWluZGVyRGF0YSA9IG5ldyBVaW50OEFycmF5KGRhdGEuYnVmZmVyLCBsZW4sIGRhdGEuYnVmZmVyLmJ5dGVMZW5ndGggLSBsZW4pO1xuICAgIH1cblxuICAgIC8vIGxvb3AgdGhyb3VnaCBUUyBwYWNrZXRzXG4gICAgbGV0IHRzUGFja2V0RXJyb3JzID0gMDtcbiAgICBmb3IgKGxldCBzdGFydCA9IHN5bmNPZmZzZXQ7IHN0YXJ0IDwgbGVuOyBzdGFydCArPSBQQUNLRVRfTEVOR1RIKSB7XG4gICAgICBpZiAoZGF0YVtzdGFydF0gPT09IDB4NDcpIHtcbiAgICAgICAgY29uc3Qgc3R0ID0gISEoZGF0YVtzdGFydCArIDFdICYgMHg0MCk7XG4gICAgICAgIGNvbnN0IHBpZCA9IHBhcnNlUElEKGRhdGEsIHN0YXJ0KTtcbiAgICAgICAgY29uc3QgYXRmID0gKGRhdGFbc3RhcnQgKyAzXSAmIDB4MzApID4+IDQ7XG5cbiAgICAgICAgLy8gaWYgYW4gYWRhcHRpb24gZmllbGQgaXMgcHJlc2VudCwgaXRzIGxlbmd0aCBpcyBzcGVjaWZpZWQgYnkgdGhlIGZpZnRoIGJ5dGUgb2YgdGhlIFRTIHBhY2tldCBoZWFkZXIuXG4gICAgICAgIGxldCBvZmZzZXQ7XG4gICAgICAgIGlmIChhdGYgPiAxKSB7XG4gICAgICAgICAgb2Zmc2V0ID0gc3RhcnQgKyA1ICsgZGF0YVtzdGFydCArIDRdO1xuICAgICAgICAgIC8vIGNvbnRpbnVlIGlmIHRoZXJlIGlzIG9ubHkgYWRhcHRhdGlvbiBmaWVsZFxuICAgICAgICAgIGlmIChvZmZzZXQgPT09IHN0YXJ0ICsgUEFDS0VUX0xFTkdUSCkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIG9mZnNldCA9IHN0YXJ0ICsgNDtcbiAgICAgICAgfVxuICAgICAgICBzd2l0Y2ggKHBpZCkge1xuICAgICAgICAgIGNhc2UgdmlkZW9QaWQ6XG4gICAgICAgICAgICBpZiAoc3R0KSB7XG4gICAgICAgICAgICAgIGlmICh2aWRlb0RhdGEgJiYgKHBlcyA9IHBhcnNlUEVTKHZpZGVvRGF0YSkpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy52aWRlb1BhcnNlci5wYXJzZUFWQ1BFUyh2aWRlb1RyYWNrLCB0ZXh0VHJhY2ssIHBlcywgZmFsc2UsIHRoaXMuX2R1cmF0aW9uKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB2aWRlb0RhdGEgPSB7XG4gICAgICAgICAgICAgICAgZGF0YTogW10sXG4gICAgICAgICAgICAgICAgc2l6ZTogMFxuICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHZpZGVvRGF0YSkge1xuICAgICAgICAgICAgICB2aWRlb0RhdGEuZGF0YS5wdXNoKGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBzdGFydCArIFBBQ0tFVF9MRU5HVEgpKTtcbiAgICAgICAgICAgICAgdmlkZW9EYXRhLnNpemUgKz0gc3RhcnQgKyBQQUNLRVRfTEVOR1RIIC0gb2Zmc2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSBhdWRpb1BpZDpcbiAgICAgICAgICAgIGlmIChzdHQpIHtcbiAgICAgICAgICAgICAgaWYgKGF1ZGlvRGF0YSAmJiAocGVzID0gcGFyc2VQRVMoYXVkaW9EYXRhKSkpIHtcbiAgICAgICAgICAgICAgICBzd2l0Y2ggKGF1ZGlvVHJhY2suc2VnbWVudENvZGVjKSB7XG4gICAgICAgICAgICAgICAgICBjYXNlICdhYWMnOlxuICAgICAgICAgICAgICAgICAgICB0aGlzLnBhcnNlQUFDUEVTKGF1ZGlvVHJhY2ssIHBlcyk7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgY2FzZSAnbXAzJzpcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wYXJzZU1QRUdQRVMoYXVkaW9UcmFjaywgcGVzKTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICBjYXNlICdhYzMnOlxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgdGhpcy5wYXJzZUFDM1BFUyhhdWRpb1RyYWNrLCBwZXMpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBhdWRpb0RhdGEgPSB7XG4gICAgICAgICAgICAgICAgZGF0YTogW10sXG4gICAgICAgICAgICAgICAgc2l6ZTogMFxuICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGF1ZGlvRGF0YSkge1xuICAgICAgICAgICAgICBhdWRpb0RhdGEuZGF0YS5wdXNoKGRhdGEuc3ViYXJyYXkob2Zmc2V0LCBzdGFydCArIFBBQ0tFVF9MRU5HVEgpKTtcbiAgICAgICAgICAgICAgYXVkaW9EYXRhLnNpemUgKz0gc3RhcnQgKyBQQUNLRVRfTEVOR1RIIC0gb2Zmc2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSBpZDNQaWQ6XG4gICAgICAgICAgICBpZiAoc3R0KSB7XG4gICAgICAgICAgICAgIGlmIChpZDNEYXRhICYmIChwZXMgPSBwYXJzZVBFUyhpZDNEYXRhKSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBhcnNlSUQzUEVTKGlkM1RyYWNrLCBwZXMpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlkM0RhdGEgPSB7XG4gICAgICAgICAgICAgICAgZGF0YTogW10sXG4gICAgICAgICAgICAgICAgc2l6ZTogMFxuICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGlkM0RhdGEpIHtcbiAgICAgICAgICAgICAgaWQzRGF0YS5kYXRhLnB1c2goZGF0YS5zdWJhcnJheShvZmZzZXQsIHN0YXJ0ICsgUEFDS0VUX0xFTkdUSCkpO1xuICAgICAgICAgICAgICBpZDNEYXRhLnNpemUgKz0gc3RhcnQgKyBQQUNLRVRfTEVOR1RIIC0gb2Zmc2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgY2FzZSAwOlxuICAgICAgICAgICAgaWYgKHN0dCkge1xuICAgICAgICAgICAgICBvZmZzZXQgKz0gZGF0YVtvZmZzZXRdICsgMTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHBtdElkID0gdGhpcy5fcG10SWQgPSBwYXJzZVBBVChkYXRhLCBvZmZzZXQpO1xuICAgICAgICAgICAgLy8gbG9nZ2VyLmxvZygnUE1UIFBJRDonICArIHRoaXMuX3BtdElkKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgcG10SWQ6XG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgIGlmIChzdHQpIHtcbiAgICAgICAgICAgICAgICBvZmZzZXQgKz0gZGF0YVtvZmZzZXRdICsgMTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBjb25zdCBwYXJzZWRQSURzID0gcGFyc2VQTVQoZGF0YSwgb2Zmc2V0LCB0aGlzLnR5cGVTdXBwb3J0ZWQsIGlzU2FtcGxlQWVzLCB0aGlzLm9ic2VydmVyKTtcblxuICAgICAgICAgICAgICAvLyBvbmx5IHVwZGF0ZSB0cmFjayBpZCBpZiB0cmFjayBQSUQgZm91bmQgd2hpbGUgcGFyc2luZyBQTVRcbiAgICAgICAgICAgICAgLy8gdGhpcyBpcyB0byBhdm9pZCByZXNldHRpbmcgdGhlIFBJRCB0byAtMSBpbiBjYXNlXG4gICAgICAgICAgICAgIC8vIHRyYWNrIFBJRCB0cmFuc2llbnRseSBkaXNhcHBlYXJzIGZyb20gdGhlIHN0cmVhbVxuICAgICAgICAgICAgICAvLyB0aGlzIGNvdWxkIGhhcHBlbiBpbiBjYXNlIG9mIHRyYW5zaWVudCBtaXNzaW5nIGF1ZGlvIHNhbXBsZXMgZm9yIGV4YW1wbGVcbiAgICAgICAgICAgICAgLy8gTk9URSB0aGlzIGlzIG9ubHkgdGhlIFBJRCBvZiB0aGUgdHJhY2sgYXMgZm91bmQgaW4gVFMsXG4gICAgICAgICAgICAgIC8vIGJ1dCB3ZSBhcmUgbm90IHVzaW5nIHRoaXMgZm9yIE1QNCB0cmFjayBJRHMuXG4gICAgICAgICAgICAgIHZpZGVvUGlkID0gcGFyc2VkUElEcy52aWRlb1BpZDtcbiAgICAgICAgICAgICAgaWYgKHZpZGVvUGlkID4gMCkge1xuICAgICAgICAgICAgICAgIHZpZGVvVHJhY2sucGlkID0gdmlkZW9QaWQ7XG4gICAgICAgICAgICAgICAgdmlkZW9UcmFjay5zZWdtZW50Q29kZWMgPSBwYXJzZWRQSURzLnNlZ21lbnRWaWRlb0NvZGVjO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGF1ZGlvUGlkID0gcGFyc2VkUElEcy5hdWRpb1BpZDtcbiAgICAgICAgICAgICAgaWYgKGF1ZGlvUGlkID4gMCkge1xuICAgICAgICAgICAgICAgIGF1ZGlvVHJhY2sucGlkID0gYXVkaW9QaWQ7XG4gICAgICAgICAgICAgICAgYXVkaW9UcmFjay5zZWdtZW50Q29kZWMgPSBwYXJzZWRQSURzLnNlZ21lbnRBdWRpb0NvZGVjO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlkM1BpZCA9IHBhcnNlZFBJRHMuaWQzUGlkO1xuICAgICAgICAgICAgICBpZiAoaWQzUGlkID4gMCkge1xuICAgICAgICAgICAgICAgIGlkM1RyYWNrLnBpZCA9IGlkM1BpZDtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZiAodW5rbm93blBJRCAhPT0gbnVsbCAmJiAhcG10UGFyc2VkKSB7XG4gICAgICAgICAgICAgICAgbG9nZ2VyLndhcm4oYE1QRUctVFMgUE1UIGZvdW5kIGF0ICR7c3RhcnR9IGFmdGVyIHVua25vd24gUElEICcke3Vua25vd25QSUR9Jy4gQmFja3RyYWNraW5nIHRvIHN5bmMgYnl0ZSBAJHtzeW5jT2Zmc2V0fSB0byBwYXJzZSBhbGwgVFMgcGFja2V0cy5gKTtcbiAgICAgICAgICAgICAgICB1bmtub3duUElEID0gbnVsbDtcbiAgICAgICAgICAgICAgICAvLyB3ZSBzZXQgaXQgdG8gLTE4OCwgdGhlICs9IDE4OCBpbiB0aGUgZm9yIGxvb3Agd2lsbCByZXNldCBzdGFydCB0byAwXG4gICAgICAgICAgICAgICAgc3RhcnQgPSBzeW5jT2Zmc2V0IC0gMTg4O1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHBtdFBhcnNlZCA9IHRoaXMucG10UGFyc2VkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgY2FzZSAweDExOlxuICAgICAgICAgIGNhc2UgMHgxZmZmOlxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgIHVua25vd25QSUQgPSBwaWQ7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdHNQYWNrZXRFcnJvcnMrKztcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKHRzUGFja2V0RXJyb3JzID4gMCkge1xuICAgICAgZW1pdFBhcnNpbmdFcnJvcih0aGlzLm9ic2VydmVyLCBuZXcgRXJyb3IoYEZvdW5kICR7dHNQYWNrZXRFcnJvcnN9IFRTIHBhY2tldC9zIHRoYXQgZG8gbm90IHN0YXJ0IHdpdGggMHg0N2ApKTtcbiAgICB9XG4gICAgdmlkZW9UcmFjay5wZXNEYXRhID0gdmlkZW9EYXRhO1xuICAgIGF1ZGlvVHJhY2sucGVzRGF0YSA9IGF1ZGlvRGF0YTtcbiAgICBpZDNUcmFjay5wZXNEYXRhID0gaWQzRGF0YTtcbiAgICBjb25zdCBkZW11eFJlc3VsdCA9IHtcbiAgICAgIGF1ZGlvVHJhY2ssXG4gICAgICB2aWRlb1RyYWNrLFxuICAgICAgaWQzVHJhY2ssXG4gICAgICB0ZXh0VHJhY2tcbiAgICB9O1xuICAgIGlmIChmbHVzaCkge1xuICAgICAgdGhpcy5leHRyYWN0UmVtYWluaW5nU2FtcGxlcyhkZW11eFJlc3VsdCk7XG4gICAgfVxuICAgIHJldHVybiBkZW11eFJlc3VsdDtcbiAgfVxuICBmbHVzaCgpIHtcbiAgICBjb25zdCB7XG4gICAgICByZW1haW5kZXJEYXRhXG4gICAgfSA9IHRoaXM7XG4gICAgdGhpcy5yZW1haW5kZXJEYXRhID0gbnVsbDtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGlmIChyZW1haW5kZXJEYXRhKSB7XG4gICAgICByZXN1bHQgPSB0aGlzLmRlbXV4KHJlbWFpbmRlckRhdGEsIC0xLCBmYWxzZSwgdHJ1ZSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJlc3VsdCA9IHtcbiAgICAgICAgdmlkZW9UcmFjazogdGhpcy5fdmlkZW9UcmFjayxcbiAgICAgICAgYXVkaW9UcmFjazogdGhpcy5fYXVkaW9UcmFjayxcbiAgICAgICAgaWQzVHJhY2s6IHRoaXMuX2lkM1RyYWNrLFxuICAgICAgICB0ZXh0VHJhY2s6IHRoaXMuX3R4dFRyYWNrXG4gICAgICB9O1xuICAgIH1cbiAgICB0aGlzLmV4dHJhY3RSZW1haW5pbmdTYW1wbGVzKHJlc3VsdCk7XG4gICAgaWYgKHRoaXMuc2FtcGxlQWVzKSB7XG4gICAgICByZXR1cm4gdGhpcy5kZWNyeXB0KHJlc3VsdCwgdGhpcy5zYW1wbGVBZXMpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGV4dHJhY3RSZW1haW5pbmdTYW1wbGVzKGRlbXV4UmVzdWx0KSB7XG4gICAgY29uc3Qge1xuICAgICAgYXVkaW9UcmFjayxcbiAgICAgIHZpZGVvVHJhY2ssXG4gICAgICBpZDNUcmFjayxcbiAgICAgIHRleHRUcmFja1xuICAgIH0gPSBkZW11eFJlc3VsdDtcbiAgICBjb25zdCB2aWRlb0RhdGEgPSB2aWRlb1RyYWNrLnBlc0RhdGE7XG4gICAgY29uc3QgYXVkaW9EYXRhID0gYXVkaW9UcmFjay5wZXNEYXRhO1xuICAgIGNvbnN0IGlkM0RhdGEgPSBpZDNUcmFjay5wZXNEYXRhO1xuICAgIC8vIHRyeSB0byBwYXJzZSBsYXN0IFBFUyBwYWNrZXRzXG4gICAgbGV0IHBlcztcbiAgICBpZiAodmlkZW9EYXRhICYmIChwZXMgPSBwYXJzZVBFUyh2aWRlb0RhdGEpKSkge1xuICAgICAgdGhpcy52aWRlb1BhcnNlci5wYXJzZUFWQ1BFUyh2aWRlb1RyYWNrLCB0ZXh0VHJhY2ssIHBlcywgdHJ1ZSwgdGhpcy5fZHVyYXRpb24pO1xuICAgICAgdmlkZW9UcmFjay5wZXNEYXRhID0gbnVsbDtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gZWl0aGVyIGF2Y0RhdGEgbnVsbCBvciBQRVMgdHJ1bmNhdGVkLCBrZWVwIGl0IGZvciBuZXh0IGZyYWcgcGFyc2luZ1xuICAgICAgdmlkZW9UcmFjay5wZXNEYXRhID0gdmlkZW9EYXRhO1xuICAgIH1cbiAgICBpZiAoYXVkaW9EYXRhICYmIChwZXMgPSBwYXJzZVBFUyhhdWRpb0RhdGEpKSkge1xuICAgICAgc3dpdGNoIChhdWRpb1RyYWNrLnNlZ21lbnRDb2RlYykge1xuICAgICAgICBjYXNlICdhYWMnOlxuICAgICAgICAgIHRoaXMucGFyc2VBQUNQRVMoYXVkaW9UcmFjaywgcGVzKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAnbXAzJzpcbiAgICAgICAgICB0aGlzLnBhcnNlTVBFR1BFUyhhdWRpb1RyYWNrLCBwZXMpO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdhYzMnOlxuICAgICAgICAgIHtcbiAgICAgICAgICAgIHRoaXMucGFyc2VBQzNQRVMoYXVkaW9UcmFjaywgcGVzKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBhdWRpb1RyYWNrLnBlc0RhdGEgPSBudWxsO1xuICAgIH0gZWxzZSB7XG4gICAgICBpZiAoYXVkaW9EYXRhICE9IG51bGwgJiYgYXVkaW9EYXRhLnNpemUpIHtcbiAgICAgICAgbG9nZ2VyLmxvZygnbGFzdCBBQUMgUEVTIHBhY2tldCB0cnVuY2F0ZWQsbWlnaHQgb3ZlcmxhcCBiZXR3ZWVuIGZyYWdtZW50cycpO1xuICAgICAgfVxuXG4gICAgICAvLyBlaXRoZXIgYXVkaW9EYXRhIG51bGwgb3IgUEVTIHRydW5jYXRlZCwga2VlcCBpdCBmb3IgbmV4dCBmcmFnIHBhcnNpbmdcbiAgICAgIGF1ZGlvVHJhY2sucGVzRGF0YSA9IGF1ZGlvRGF0YTtcbiAgICB9XG4gICAgaWYgKGlkM0RhdGEgJiYgKHBlcyA9IHBhcnNlUEVTKGlkM0RhdGEpKSkge1xuICAgICAgdGhpcy5wYXJzZUlEM1BFUyhpZDNUcmFjaywgcGVzKTtcbiAgICAgIGlkM1RyYWNrLnBlc0RhdGEgPSBudWxsO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBlaXRoZXIgaWQzRGF0YSBudWxsIG9yIFBFUyB0cnVuY2F0ZWQsIGtlZXAgaXQgZm9yIG5leHQgZnJhZyBwYXJzaW5nXG4gICAgICBpZDNUcmFjay5wZXNEYXRhID0gaWQzRGF0YTtcbiAgICB9XG4gIH1cbiAgZGVtdXhTYW1wbGVBZXMoZGF0YSwga2V5RGF0YSwgdGltZU9mZnNldCkge1xuICAgIGNvbnN0IGRlbXV4UmVzdWx0ID0gdGhpcy5kZW11eChkYXRhLCB0aW1lT2Zmc2V0LCB0cnVlLCAhdGhpcy5jb25maWcucHJvZ3Jlc3NpdmUpO1xuICAgIGNvbnN0IHNhbXBsZUFlcyA9IHRoaXMuc2FtcGxlQWVzID0gbmV3IFNhbXBsZUFlc0RlY3J5cHRlcih0aGlzLm9ic2VydmVyLCB0aGlzLmNvbmZpZywga2V5RGF0YSk7XG4gICAgcmV0dXJuIHRoaXMuZGVjcnlwdChkZW11eFJlc3VsdCwgc2FtcGxlQWVzKTtcbiAgfVxuICBkZWNyeXB0KGRlbXV4UmVzdWx0LCBzYW1wbGVBZXMpIHtcbiAgICByZXR1cm4gbmV3IFByb21pc2UocmVzb2x2ZSA9PiB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGF1ZGlvVHJhY2ssXG4gICAgICAgIHZpZGVvVHJhY2tcbiAgICAgIH0gPSBkZW11eFJlc3VsdDtcbiAgICAgIGlmIChhdWRpb1RyYWNrLnNhbXBsZXMgJiYgYXVkaW9UcmFjay5zZWdtZW50Q29kZWMgPT09ICdhYWMnKSB7XG4gICAgICAgIHNhbXBsZUFlcy5kZWNyeXB0QWFjU2FtcGxlcyhhdWRpb1RyYWNrLnNhbXBsZXMsIDAsICgpID0+IHtcbiAgICAgICAgICBpZiAodmlkZW9UcmFjay5zYW1wbGVzKSB7XG4gICAgICAgICAgICBzYW1wbGVBZXMuZGVjcnlwdEF2Y1NhbXBsZXModmlkZW9UcmFjay5zYW1wbGVzLCAwLCAwLCAoKSA9PiB7XG4gICAgICAgICAgICAgIHJlc29sdmUoZGVtdXhSZXN1bHQpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJlc29sdmUoZGVtdXhSZXN1bHQpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2UgaWYgKHZpZGVvVHJhY2suc2FtcGxlcykge1xuICAgICAgICBzYW1wbGVBZXMuZGVjcnlwdEF2Y1NhbXBsZXModmlkZW9UcmFjay5zYW1wbGVzLCAwLCAwLCAoKSA9PiB7XG4gICAgICAgICAgcmVzb2x2ZShkZW11eFJlc3VsdCk7XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG4gIGRlc3Ryb3koKSB7XG4gICAgdGhpcy5fZHVyYXRpb24gPSAwO1xuICB9XG4gIHBhcnNlQUFDUEVTKHRyYWNrLCBwZXMpIHtcbiAgICBsZXQgc3RhcnRPZmZzZXQgPSAwO1xuICAgIGNvbnN0IGFhY092ZXJGbG93ID0gdGhpcy5hYWNPdmVyRmxvdztcbiAgICBsZXQgZGF0YSA9IHBlcy5kYXRhO1xuICAgIGlmIChhYWNPdmVyRmxvdykge1xuICAgICAgdGhpcy5hYWNPdmVyRmxvdyA9IG51bGw7XG4gICAgICBjb25zdCBmcmFtZU1pc3NpbmdCeXRlcyA9IGFhY092ZXJGbG93Lm1pc3Npbmc7XG4gICAgICBjb25zdCBzYW1wbGVMZW5ndGggPSBhYWNPdmVyRmxvdy5zYW1wbGUudW5pdC5ieXRlTGVuZ3RoO1xuICAgICAgLy8gbG9nZ2VyLmxvZyhgQUFDOiBhcHBlbmQgb3ZlcmZsb3dpbmcgJHtzYW1wbGVMZW5ndGh9IGJ5dGVzIHRvIGJlZ2lubmluZyBvZiBuZXcgUEVTYCk7XG4gICAgICBpZiAoZnJhbWVNaXNzaW5nQnl0ZXMgPT09IC0xKSB7XG4gICAgICAgIGRhdGEgPSBhcHBlbmRVaW50OEFycmF5KGFhY092ZXJGbG93LnNhbXBsZS51bml0LCBkYXRhKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IGZyYW1lT3ZlcmZsb3dCeXRlcyA9IHNhbXBsZUxlbmd0aCAtIGZyYW1lTWlzc2luZ0J5dGVzO1xuICAgICAgICBhYWNPdmVyRmxvdy5zYW1wbGUudW5pdC5zZXQoZGF0YS5zdWJhcnJheSgwLCBmcmFtZU1pc3NpbmdCeXRlcyksIGZyYW1lT3ZlcmZsb3dCeXRlcyk7XG4gICAgICAgIHRyYWNrLnNhbXBsZXMucHVzaChhYWNPdmVyRmxvdy5zYW1wbGUpO1xuICAgICAgICBzdGFydE9mZnNldCA9IGFhY092ZXJGbG93Lm1pc3Npbmc7XG4gICAgICB9XG4gICAgfVxuICAgIC8vIGxvb2sgZm9yIEFEVFMgaGVhZGVyICgweEZGRngpXG4gICAgbGV0IG9mZnNldDtcbiAgICBsZXQgbGVuO1xuICAgIGZvciAob2Zmc2V0ID0gc3RhcnRPZmZzZXQsIGxlbiA9IGRhdGEubGVuZ3RoOyBvZmZzZXQgPCBsZW4gLSAxOyBvZmZzZXQrKykge1xuICAgICAgaWYgKGlzSGVhZGVyJDEoZGF0YSwgb2Zmc2V0KSkge1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG4gICAgLy8gaWYgQURUUyBoZWFkZXIgZG9lcyBub3Qgc3RhcnQgc3RyYWlnaHQgZnJvbSB0aGUgYmVnaW5uaW5nIG9mIHRoZSBQRVMgcGF5bG9hZCwgcmFpc2UgYW4gZXJyb3JcbiAgICBpZiAob2Zmc2V0ICE9PSBzdGFydE9mZnNldCkge1xuICAgICAgbGV0IHJlYXNvbjtcbiAgICAgIGNvbnN0IHJlY292ZXJhYmxlID0gb2Zmc2V0IDwgbGVuIC0gMTtcbiAgICAgIGlmIChyZWNvdmVyYWJsZSkge1xuICAgICAgICByZWFzb24gPSBgQUFDIFBFUyBkaWQgbm90IHN0YXJ0IHdpdGggQURUUyBoZWFkZXIsb2Zmc2V0OiR7b2Zmc2V0fWA7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZWFzb24gPSAnTm8gQURUUyBoZWFkZXIgZm91bmQgaW4gQUFDIFBFUyc7XG4gICAgICB9XG4gICAgICBlbWl0UGFyc2luZ0Vycm9yKHRoaXMub2JzZXJ2ZXIsIG5ldyBFcnJvcihyZWFzb24pLCByZWNvdmVyYWJsZSk7XG4gICAgICBpZiAoIXJlY292ZXJhYmxlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICB9XG4gICAgaW5pdFRyYWNrQ29uZmlnKHRyYWNrLCB0aGlzLm9ic2VydmVyLCBkYXRhLCBvZmZzZXQsIHRoaXMuYXVkaW9Db2RlYyk7XG4gICAgbGV0IHB0cztcbiAgICBpZiAocGVzLnB0cyAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBwdHMgPSBwZXMucHRzO1xuICAgIH0gZWxzZSBpZiAoYWFjT3ZlckZsb3cpIHtcbiAgICAgIC8vIGlmIGxhc3QgQUFDIGZyYW1lIGlzIG92ZXJmbG93aW5nLCB3ZSBzaG91bGQgZW5zdXJlIHRpbWVzdGFtcHMgYXJlIGNvbnRpZ3VvdXM6XG4gICAgICAvLyBmaXJzdCBzYW1wbGUgUFRTIHNob3VsZCBiZSBlcXVhbCB0byBsYXN0IHNhbXBsZSBQVFMgKyBmcmFtZUR1cmF0aW9uXG4gICAgICBjb25zdCBmcmFtZUR1cmF0aW9uID0gZ2V0RnJhbWVEdXJhdGlvbih0cmFjay5zYW1wbGVyYXRlKTtcbiAgICAgIHB0cyA9IGFhY092ZXJGbG93LnNhbXBsZS5wdHMgKyBmcmFtZUR1cmF0aW9uO1xuICAgIH0gZWxzZSB7XG4gICAgICBsb2dnZXIud2FybignW3RzZGVtdXhlcl06IEFBQyBQRVMgdW5rbm93biBQVFMnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBzY2FuIGZvciBhYWMgc2FtcGxlc1xuICAgIGxldCBmcmFtZUluZGV4ID0gMDtcbiAgICBsZXQgZnJhbWU7XG4gICAgd2hpbGUgKG9mZnNldCA8IGxlbikge1xuICAgICAgZnJhbWUgPSBhcHBlbmRGcmFtZSQyKHRyYWNrLCBkYXRhLCBvZmZzZXQsIHB0cywgZnJhbWVJbmRleCk7XG4gICAgICBvZmZzZXQgKz0gZnJhbWUubGVuZ3RoO1xuICAgICAgaWYgKCFmcmFtZS5taXNzaW5nKSB7XG4gICAgICAgIGZyYW1lSW5kZXgrKztcbiAgICAgICAgZm9yICg7IG9mZnNldCA8IGxlbiAtIDE7IG9mZnNldCsrKSB7XG4gICAgICAgICAgaWYgKGlzSGVhZGVyJDEoZGF0YSwgb2Zmc2V0KSkge1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLmFhY092ZXJGbG93ID0gZnJhbWU7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBwYXJzZU1QRUdQRVModHJhY2ssIHBlcykge1xuICAgIGNvbnN0IGRhdGEgPSBwZXMuZGF0YTtcbiAgICBjb25zdCBsZW5ndGggPSBkYXRhLmxlbmd0aDtcbiAgICBsZXQgZnJhbWVJbmRleCA9IDA7XG4gICAgbGV0IG9mZnNldCA9IDA7XG4gICAgY29uc3QgcHRzID0gcGVzLnB0cztcbiAgICBpZiAocHRzID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbdHNkZW11eGVyXTogTVBFRyBQRVMgdW5rbm93biBQVFMnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgd2hpbGUgKG9mZnNldCA8IGxlbmd0aCkge1xuICAgICAgaWYgKGlzSGVhZGVyKGRhdGEsIG9mZnNldCkpIHtcbiAgICAgICAgY29uc3QgZnJhbWUgPSBhcHBlbmRGcmFtZSQxKHRyYWNrLCBkYXRhLCBvZmZzZXQsIHB0cywgZnJhbWVJbmRleCk7XG4gICAgICAgIGlmIChmcmFtZSkge1xuICAgICAgICAgIG9mZnNldCArPSBmcmFtZS5sZW5ndGg7XG4gICAgICAgICAgZnJhbWVJbmRleCsrO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIC8vIGxvZ2dlci5sb2coJ1VuYWJsZSB0byBwYXJzZSBNcGVnIGF1ZGlvIGZyYW1lJyk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG5vdGhpbmcgZm91bmQsIGtlZXAgbG9va2luZ1xuICAgICAgICBvZmZzZXQrKztcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcGFyc2VBQzNQRVModHJhY2ssIHBlcykge1xuICAgIHtcbiAgICAgIGNvbnN0IGRhdGEgPSBwZXMuZGF0YTtcbiAgICAgIGNvbnN0IHB0cyA9IHBlcy5wdHM7XG4gICAgICBpZiAocHRzID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1t0c2RlbXV4ZXJdOiBBQzMgUEVTIHVua25vd24gUFRTJyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGxlbmd0aCA9IGRhdGEubGVuZ3RoO1xuICAgICAgbGV0IGZyYW1lSW5kZXggPSAwO1xuICAgICAgbGV0IG9mZnNldCA9IDA7XG4gICAgICBsZXQgcGFyc2VkO1xuICAgICAgd2hpbGUgKG9mZnNldCA8IGxlbmd0aCAmJiAocGFyc2VkID0gYXBwZW5kRnJhbWUodHJhY2ssIGRhdGEsIG9mZnNldCwgcHRzLCBmcmFtZUluZGV4KyspKSA+IDApIHtcbiAgICAgICAgb2Zmc2V0ICs9IHBhcnNlZDtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcGFyc2VJRDNQRVMoaWQzVHJhY2ssIHBlcykge1xuICAgIGlmIChwZXMucHRzID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbdHNkZW11eGVyXTogSUQzIFBFUyB1bmtub3duIFBUUycpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBpZDNTYW1wbGUgPSBfZXh0ZW5kcyh7fSwgcGVzLCB7XG4gICAgICB0eXBlOiB0aGlzLl92aWRlb1RyYWNrID8gTWV0YWRhdGFTY2hlbWEuZW1zZyA6IE1ldGFkYXRhU2NoZW1hLmF1ZGlvSWQzLFxuICAgICAgZHVyYXRpb246IE51bWJlci5QT1NJVElWRV9JTkZJTklUWVxuICAgIH0pO1xuICAgIGlkM1RyYWNrLnNhbXBsZXMucHVzaChpZDNTYW1wbGUpO1xuICB9XG59XG5mdW5jdGlvbiBwYXJzZVBJRChkYXRhLCBvZmZzZXQpIHtcbiAgLy8gcGlkIGlzIGEgMTMtYml0IGZpZWxkIHN0YXJ0aW5nIGF0IHRoZSBsYXN0IGJpdCBvZiBUU1sxXVxuICByZXR1cm4gKChkYXRhW29mZnNldCArIDFdICYgMHgxZikgPDwgOCkgKyBkYXRhW29mZnNldCArIDJdO1xufVxuZnVuY3Rpb24gcGFyc2VQQVQoZGF0YSwgb2Zmc2V0KSB7XG4gIC8vIHNraXAgdGhlIFBTSSBoZWFkZXIgYW5kIHBhcnNlIHRoZSBmaXJzdCBQTVQgZW50cnlcbiAgcmV0dXJuIChkYXRhW29mZnNldCArIDEwXSAmIDB4MWYpIDw8IDggfCBkYXRhW29mZnNldCArIDExXTtcbn1cbmZ1bmN0aW9uIHBhcnNlUE1UKGRhdGEsIG9mZnNldCwgdHlwZVN1cHBvcnRlZCwgaXNTYW1wbGVBZXMsIG9ic2VydmVyKSB7XG4gIGNvbnN0IHJlc3VsdCA9IHtcbiAgICBhdWRpb1BpZDogLTEsXG4gICAgdmlkZW9QaWQ6IC0xLFxuICAgIGlkM1BpZDogLTEsXG4gICAgc2VnbWVudFZpZGVvQ29kZWM6ICdhdmMnLFxuICAgIHNlZ21lbnRBdWRpb0NvZGVjOiAnYWFjJ1xuICB9O1xuICBjb25zdCBzZWN0aW9uTGVuZ3RoID0gKGRhdGFbb2Zmc2V0ICsgMV0gJiAweDBmKSA8PCA4IHwgZGF0YVtvZmZzZXQgKyAyXTtcbiAgY29uc3QgdGFibGVFbmQgPSBvZmZzZXQgKyAzICsgc2VjdGlvbkxlbmd0aCAtIDQ7XG4gIC8vIHRvIGRldGVybWluZSB3aGVyZSB0aGUgdGFibGUgaXMsIHdlIGhhdmUgdG8gZmlndXJlIG91dCBob3dcbiAgLy8gbG9uZyB0aGUgcHJvZ3JhbSBpbmZvIGRlc2NyaXB0b3JzIGFyZVxuICBjb25zdCBwcm9ncmFtSW5mb0xlbmd0aCA9IChkYXRhW29mZnNldCArIDEwXSAmIDB4MGYpIDw8IDggfCBkYXRhW29mZnNldCArIDExXTtcbiAgLy8gYWR2YW5jZSB0aGUgb2Zmc2V0IHRvIHRoZSBmaXJzdCBlbnRyeSBpbiB0aGUgbWFwcGluZyB0YWJsZVxuICBvZmZzZXQgKz0gMTIgKyBwcm9ncmFtSW5mb0xlbmd0aDtcbiAgd2hpbGUgKG9mZnNldCA8IHRhYmxlRW5kKSB7XG4gICAgY29uc3QgcGlkID0gcGFyc2VQSUQoZGF0YSwgb2Zmc2V0KTtcbiAgICBjb25zdCBlc0luZm9MZW5ndGggPSAoZGF0YVtvZmZzZXQgKyAzXSAmIDB4MGYpIDw8IDggfCBkYXRhW29mZnNldCArIDRdO1xuICAgIHN3aXRjaCAoZGF0YVtvZmZzZXRdKSB7XG4gICAgICBjYXNlIDB4Y2Y6XG4gICAgICAgIC8vIFNBTVBMRS1BRVMgQUFDXG4gICAgICAgIGlmICghaXNTYW1wbGVBZXMpIHtcbiAgICAgICAgICBsb2dFbmNyeXB0ZWRTYW1wbGVzRm91bmRJblVuZW5jcnlwdGVkU3RyZWFtKCdBRFRTIEFBQycpO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAvKiBmYWxscyB0aHJvdWdoICovXG4gICAgICBjYXNlIDB4MGY6XG4gICAgICAgIC8vIElTTy9JRUMgMTM4MTgtNyBBRFRTIEFBQyAoTVBFRy0yIGxvd2VyIGJpdC1yYXRlIGF1ZGlvKVxuICAgICAgICAvLyBsb2dnZXIubG9nKCdBQUMgUElEOicgICsgcGlkKTtcbiAgICAgICAgaWYgKHJlc3VsdC5hdWRpb1BpZCA9PT0gLTEpIHtcbiAgICAgICAgICByZXN1bHQuYXVkaW9QaWQgPSBwaWQ7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG5cbiAgICAgIC8vIFBhY2tldGl6ZWQgbWV0YWRhdGEgKElEMylcbiAgICAgIGNhc2UgMHgxNTpcbiAgICAgICAgLy8gbG9nZ2VyLmxvZygnSUQzIFBJRDonICArIHBpZCk7XG4gICAgICAgIGlmIChyZXN1bHQuaWQzUGlkID09PSAtMSkge1xuICAgICAgICAgIHJlc3VsdC5pZDNQaWQgPSBwaWQ7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIDB4ZGI6XG4gICAgICAgIC8vIFNBTVBMRS1BRVMgQVZDXG4gICAgICAgIGlmICghaXNTYW1wbGVBZXMpIHtcbiAgICAgICAgICBsb2dFbmNyeXB0ZWRTYW1wbGVzRm91bmRJblVuZW5jcnlwdGVkU3RyZWFtKCdILjI2NCcpO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAvKiBmYWxscyB0aHJvdWdoICovXG4gICAgICBjYXNlIDB4MWI6XG4gICAgICAgIC8vIElUVS1UIFJlYy4gSC4yNjQgYW5kIElTTy9JRUMgMTQ0OTYtMTAgKGxvd2VyIGJpdC1yYXRlIHZpZGVvKVxuICAgICAgICAvLyBsb2dnZXIubG9nKCdBVkMgUElEOicgICsgcGlkKTtcbiAgICAgICAgaWYgKHJlc3VsdC52aWRlb1BpZCA9PT0gLTEpIHtcbiAgICAgICAgICByZXN1bHQudmlkZW9QaWQgPSBwaWQ7XG4gICAgICAgICAgcmVzdWx0LnNlZ21lbnRWaWRlb0NvZGVjID0gJ2F2Yyc7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG5cbiAgICAgIC8vIElTTy9JRUMgMTExNzItMyAoTVBFRy0xIGF1ZGlvKVxuICAgICAgLy8gb3IgSVNPL0lFQyAxMzgxOC0zIChNUEVHLTIgaGFsdmVkIHNhbXBsZSByYXRlIGF1ZGlvKVxuICAgICAgY2FzZSAweDAzOlxuICAgICAgY2FzZSAweDA0OlxuICAgICAgICAvLyBsb2dnZXIubG9nKCdNUEVHIFBJRDonICArIHBpZCk7XG4gICAgICAgIGlmICghdHlwZVN1cHBvcnRlZC5tcGVnICYmICF0eXBlU3VwcG9ydGVkLm1wMykge1xuICAgICAgICAgIGxvZ2dlci5sb2coJ01QRUcgYXVkaW8gZm91bmQsIG5vdCBzdXBwb3J0ZWQgaW4gdGhpcyBicm93c2VyJyk7XG4gICAgICAgIH0gZWxzZSBpZiAocmVzdWx0LmF1ZGlvUGlkID09PSAtMSkge1xuICAgICAgICAgIHJlc3VsdC5hdWRpb1BpZCA9IHBpZDtcbiAgICAgICAgICByZXN1bHQuc2VnbWVudEF1ZGlvQ29kZWMgPSAnbXAzJztcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMHhjMTpcbiAgICAgICAgLy8gU0FNUExFLUFFUyBBQzNcbiAgICAgICAgaWYgKCFpc1NhbXBsZUFlcykge1xuICAgICAgICAgIGxvZ0VuY3J5cHRlZFNhbXBsZXNGb3VuZEluVW5lbmNyeXB0ZWRTdHJlYW0oJ0FDLTMnKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgLyogZmFsbHMgdGhyb3VnaCAqL1xuICAgICAgY2FzZSAweDgxOlxuICAgICAgICB7XG4gICAgICAgICAgaWYgKCF0eXBlU3VwcG9ydGVkLmFjMykge1xuICAgICAgICAgICAgbG9nZ2VyLmxvZygnQUMtMyBhdWRpbyBmb3VuZCwgbm90IHN1cHBvcnRlZCBpbiB0aGlzIGJyb3dzZXInKTtcbiAgICAgICAgICB9IGVsc2UgaWYgKHJlc3VsdC5hdWRpb1BpZCA9PT0gLTEpIHtcbiAgICAgICAgICAgIHJlc3VsdC5hdWRpb1BpZCA9IHBpZDtcbiAgICAgICAgICAgIHJlc3VsdC5zZWdtZW50QXVkaW9Db2RlYyA9ICdhYzMnO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMHgwNjpcbiAgICAgICAgLy8gc3RyZWFtX3R5cGUgNiBjYW4gbWVhbiBhIGxvdCBvZiBkaWZmZXJlbnQgdGhpbmdzIGluIGNhc2Ugb2YgRFZCLlxuICAgICAgICAvLyBXZSBuZWVkIHRvIGxvb2sgYXQgdGhlIGRlc2NyaXB0b3JzLiBSaWdodCBub3csIHdlJ3JlIG9ubHkgaW50ZXJlc3RlZFxuICAgICAgICAvLyBpbiBBQy0zIGF1ZGlvLCBzbyB3ZSBkbyB0aGUgZGVzY3JpcHRvciBwYXJzaW5nIG9ubHkgd2hlbiB3ZSBkb24ndCBoYXZlXG4gICAgICAgIC8vIGFuIGF1ZGlvIFBJRCB5ZXQuXG4gICAgICAgIGlmIChyZXN1bHQuYXVkaW9QaWQgPT09IC0xICYmIGVzSW5mb0xlbmd0aCA+IDApIHtcbiAgICAgICAgICBsZXQgcGFyc2VQb3MgPSBvZmZzZXQgKyA1O1xuICAgICAgICAgIGxldCByZW1haW5pbmcgPSBlc0luZm9MZW5ndGg7XG4gICAgICAgICAgd2hpbGUgKHJlbWFpbmluZyA+IDIpIHtcbiAgICAgICAgICAgIGNvbnN0IGRlc2NyaXB0b3JJZCA9IGRhdGFbcGFyc2VQb3NdO1xuICAgICAgICAgICAgc3dpdGNoIChkZXNjcmlwdG9ySWQpIHtcbiAgICAgICAgICAgICAgY2FzZSAweDZhOlxuICAgICAgICAgICAgICAgIC8vIERWQiBEZXNjcmlwdG9yIGZvciBBQy0zXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgaWYgKHR5cGVTdXBwb3J0ZWQuYWMzICE9PSB0cnVlKSB7XG4gICAgICAgICAgICAgICAgICAgIGxvZ2dlci5sb2coJ0FDLTMgYXVkaW8gZm91bmQsIG5vdCBzdXBwb3J0ZWQgaW4gdGhpcyBicm93c2VyIGZvciBub3cnKTtcbiAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdC5hdWRpb1BpZCA9IHBpZDtcbiAgICAgICAgICAgICAgICAgICAgcmVzdWx0LnNlZ21lbnRBdWRpb0NvZGVjID0gJ2FjMyc7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgZGVzY3JpcHRvckxlbiA9IGRhdGFbcGFyc2VQb3MgKyAxXSArIDI7XG4gICAgICAgICAgICBwYXJzZVBvcyArPSBkZXNjcmlwdG9yTGVuO1xuICAgICAgICAgICAgcmVtYWluaW5nIC09IGRlc2NyaXB0b3JMZW47XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAweGMyOiAvLyBTQU1QTEUtQUVTIEVDM1xuICAgICAgLyogZmFsbHMgdGhyb3VnaCAqL1xuICAgICAgY2FzZSAweDg3OlxuICAgICAgICBlbWl0UGFyc2luZ0Vycm9yKG9ic2VydmVyLCBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIEVDLTMgaW4gTTJUUyBmb3VuZCcpKTtcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgIGNhc2UgMHgyNDpcbiAgICAgICAgZW1pdFBhcnNpbmdFcnJvcihvYnNlcnZlciwgbmV3IEVycm9yKCdVbnN1cHBvcnRlZCBIRVZDIGluIE0yVFMgZm91bmQnKSk7XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfVxuICAgIC8vIG1vdmUgdG8gdGhlIG5leHQgdGFibGUgZW50cnlcbiAgICAvLyBza2lwIHBhc3QgdGhlIGVsZW1lbnRhcnkgc3RyZWFtIGRlc2NyaXB0b3JzLCBpZiBwcmVzZW50XG4gICAgb2Zmc2V0ICs9IGVzSW5mb0xlbmd0aCArIDU7XG4gIH1cbiAgcmV0dXJuIHJlc3VsdDtcbn1cbmZ1bmN0aW9uIGVtaXRQYXJzaW5nRXJyb3Iob2JzZXJ2ZXIsIGVycm9yLCBsZXZlbFJldHJ5KSB7XG4gIGxvZ2dlci53YXJuKGBwYXJzaW5nIGVycm9yOiAke2Vycm9yLm1lc3NhZ2V9YCk7XG4gIG9ic2VydmVyLmVtaXQoRXZlbnRzLkVSUk9SLCBFdmVudHMuRVJST1IsIHtcbiAgICB0eXBlOiBFcnJvclR5cGVzLk1FRElBX0VSUk9SLFxuICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX1BBUlNJTkdfRVJST1IsXG4gICAgZmF0YWw6IGZhbHNlLFxuICAgIGxldmVsUmV0cnksXG4gICAgZXJyb3IsXG4gICAgcmVhc29uOiBlcnJvci5tZXNzYWdlXG4gIH0pO1xufVxuZnVuY3Rpb24gbG9nRW5jcnlwdGVkU2FtcGxlc0ZvdW5kSW5VbmVuY3J5cHRlZFN0cmVhbSh0eXBlKSB7XG4gIGxvZ2dlci5sb2coYCR7dHlwZX0gd2l0aCBBRVMtMTI4LUNCQyBlbmNyeXB0aW9uIGZvdW5kIGluIHVuZW5jcnlwdGVkIHN0cmVhbWApO1xufVxuZnVuY3Rpb24gcGFyc2VQRVMoc3RyZWFtKSB7XG4gIGxldCBpID0gMDtcbiAgbGV0IGZyYWc7XG4gIGxldCBwZXNMZW47XG4gIGxldCBwZXNIZHJMZW47XG4gIGxldCBwZXNQdHM7XG4gIGxldCBwZXNEdHM7XG4gIGNvbnN0IGRhdGEgPSBzdHJlYW0uZGF0YTtcbiAgLy8gc2FmZXR5IGNoZWNrXG4gIGlmICghc3RyZWFtIHx8IHN0cmVhbS5zaXplID09PSAwKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvLyB3ZSBtaWdodCBuZWVkIHVwIHRvIDE5IGJ5dGVzIHRvIHJlYWQgUEVTIGhlYWRlclxuICAvLyBpZiBmaXJzdCBjaHVuayBvZiBkYXRhIGlzIGxlc3MgdGhhbiAxOSBieXRlcywgbGV0J3MgbWVyZ2UgaXQgd2l0aCBmb2xsb3dpbmcgb25lcyB1bnRpbCB3ZSBnZXQgMTkgYnl0ZXNcbiAgLy8gdXN1YWxseSBvbmx5IG9uZSBtZXJnZSBpcyBuZWVkZWQgKGFuZCB0aGlzIGlzIHJhcmUgLi4uKVxuICB3aGlsZSAoZGF0YVswXS5sZW5ndGggPCAxOSAmJiBkYXRhLmxlbmd0aCA+IDEpIHtcbiAgICBkYXRhWzBdID0gYXBwZW5kVWludDhBcnJheShkYXRhWzBdLCBkYXRhWzFdKTtcbiAgICBkYXRhLnNwbGljZSgxLCAxKTtcbiAgfVxuICAvLyByZXRyaWV2ZSBQVFMvRFRTIGZyb20gZmlyc3QgZnJhZ21lbnRcbiAgZnJhZyA9IGRhdGFbMF07XG4gIGNvbnN0IHBlc1ByZWZpeCA9IChmcmFnWzBdIDw8IDE2KSArIChmcmFnWzFdIDw8IDgpICsgZnJhZ1syXTtcbiAgaWYgKHBlc1ByZWZpeCA9PT0gMSkge1xuICAgIHBlc0xlbiA9IChmcmFnWzRdIDw8IDgpICsgZnJhZ1s1XTtcbiAgICAvLyBpZiBQRVMgcGFyc2VkIGxlbmd0aCBpcyBub3QgemVybyBhbmQgZ3JlYXRlciB0aGFuIHRvdGFsIHJlY2VpdmVkIGxlbmd0aCwgc3RvcCBwYXJzaW5nLiBQRVMgbWlnaHQgYmUgdHJ1bmNhdGVkXG4gICAgLy8gbWludXMgNiA6IFBFUyBoZWFkZXIgc2l6ZVxuICAgIGlmIChwZXNMZW4gJiYgcGVzTGVuID4gc3RyZWFtLnNpemUgLSA2KSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgY29uc3QgcGVzRmxhZ3MgPSBmcmFnWzddO1xuICAgIGlmIChwZXNGbGFncyAmIDB4YzApIHtcbiAgICAgIC8qIFBFUyBoZWFkZXIgZGVzY3JpYmVkIGhlcmUgOiBodHRwOi8vZHZkLnNvdXJjZWZvcmdlLm5ldC9kdmRpbmZvL3Blcy1oZHIuaHRtbFxuICAgICAgICAgIGFzIFBUUyAvIERUUyBpcyAzMyBiaXQgd2UgY2Fubm90IHVzZSBiaXR3aXNlIG9wZXJhdG9yIGluIEpTLFxuICAgICAgICAgIGFzIEJpdHdpc2Ugb3BlcmF0b3JzIHRyZWF0IHRoZWlyIG9wZXJhbmRzIGFzIGEgc2VxdWVuY2Ugb2YgMzIgYml0cyAqL1xuICAgICAgcGVzUHRzID0gKGZyYWdbOV0gJiAweDBlKSAqIDUzNjg3MDkxMiArXG4gICAgICAvLyAxIDw8IDI5XG4gICAgICAoZnJhZ1sxMF0gJiAweGZmKSAqIDQxOTQzMDQgK1xuICAgICAgLy8gMSA8PCAyMlxuICAgICAgKGZyYWdbMTFdICYgMHhmZSkgKiAxNjM4NCArXG4gICAgICAvLyAxIDw8IDE0XG4gICAgICAoZnJhZ1sxMl0gJiAweGZmKSAqIDEyOCArXG4gICAgICAvLyAxIDw8IDdcbiAgICAgIChmcmFnWzEzXSAmIDB4ZmUpIC8gMjtcbiAgICAgIGlmIChwZXNGbGFncyAmIDB4NDApIHtcbiAgICAgICAgcGVzRHRzID0gKGZyYWdbMTRdICYgMHgwZSkgKiA1MzY4NzA5MTIgK1xuICAgICAgICAvLyAxIDw8IDI5XG4gICAgICAgIChmcmFnWzE1XSAmIDB4ZmYpICogNDE5NDMwNCArXG4gICAgICAgIC8vIDEgPDwgMjJcbiAgICAgICAgKGZyYWdbMTZdICYgMHhmZSkgKiAxNjM4NCArXG4gICAgICAgIC8vIDEgPDwgMTRcbiAgICAgICAgKGZyYWdbMTddICYgMHhmZikgKiAxMjggK1xuICAgICAgICAvLyAxIDw8IDdcbiAgICAgICAgKGZyYWdbMThdICYgMHhmZSkgLyAyO1xuICAgICAgICBpZiAocGVzUHRzIC0gcGVzRHRzID4gNjAgKiA5MDAwMCkge1xuICAgICAgICAgIGxvZ2dlci53YXJuKGAke01hdGgucm91bmQoKHBlc1B0cyAtIHBlc0R0cykgLyA5MDAwMCl9cyBkZWx0YSBiZXR3ZWVuIFBUUyBhbmQgRFRTLCBhbGlnbiB0aGVtYCk7XG4gICAgICAgICAgcGVzUHRzID0gcGVzRHRzO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBwZXNEdHMgPSBwZXNQdHM7XG4gICAgICB9XG4gICAgfVxuICAgIHBlc0hkckxlbiA9IGZyYWdbOF07XG4gICAgLy8gOSBieXRlcyA6IDYgYnl0ZXMgZm9yIFBFUyBoZWFkZXIgKyAzIGJ5dGVzIGZvciBQRVMgZXh0ZW5zaW9uXG4gICAgbGV0IHBheWxvYWRTdGFydE9mZnNldCA9IHBlc0hkckxlbiArIDk7XG4gICAgaWYgKHN0cmVhbS5zaXplIDw9IHBheWxvYWRTdGFydE9mZnNldCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHN0cmVhbS5zaXplIC09IHBheWxvYWRTdGFydE9mZnNldDtcbiAgICAvLyByZWFzc2VtYmxlIFBFUyBwYWNrZXRcbiAgICBjb25zdCBwZXNEYXRhID0gbmV3IFVpbnQ4QXJyYXkoc3RyZWFtLnNpemUpO1xuICAgIGZvciAobGV0IGogPSAwLCBkYXRhTGVuID0gZGF0YS5sZW5ndGg7IGogPCBkYXRhTGVuOyBqKyspIHtcbiAgICAgIGZyYWcgPSBkYXRhW2pdO1xuICAgICAgbGV0IGxlbiA9IGZyYWcuYnl0ZUxlbmd0aDtcbiAgICAgIGlmIChwYXlsb2FkU3RhcnRPZmZzZXQpIHtcbiAgICAgICAgaWYgKHBheWxvYWRTdGFydE9mZnNldCA+IGxlbikge1xuICAgICAgICAgIC8vIHRyaW0gZnVsbCBmcmFnIGlmIFBFUyBoZWFkZXIgYmlnZ2VyIHRoYW4gZnJhZ1xuICAgICAgICAgIHBheWxvYWRTdGFydE9mZnNldCAtPSBsZW47XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gdHJpbSBwYXJ0aWFsIGZyYWcgaWYgUEVTIGhlYWRlciBzbWFsbGVyIHRoYW4gZnJhZ1xuICAgICAgICAgIGZyYWcgPSBmcmFnLnN1YmFycmF5KHBheWxvYWRTdGFydE9mZnNldCk7XG4gICAgICAgICAgbGVuIC09IHBheWxvYWRTdGFydE9mZnNldDtcbiAgICAgICAgICBwYXlsb2FkU3RhcnRPZmZzZXQgPSAwO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBwZXNEYXRhLnNldChmcmFnLCBpKTtcbiAgICAgIGkgKz0gbGVuO1xuICAgIH1cbiAgICBpZiAocGVzTGVuKSB7XG4gICAgICAvLyBwYXlsb2FkIHNpemUgOiByZW1vdmUgUEVTIGhlYWRlciArIFBFUyBleHRlbnNpb25cbiAgICAgIHBlc0xlbiAtPSBwZXNIZHJMZW4gKyAzO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgZGF0YTogcGVzRGF0YSxcbiAgICAgIHB0czogcGVzUHRzLFxuICAgICAgZHRzOiBwZXNEdHMsXG4gICAgICBsZW46IHBlc0xlblxuICAgIH07XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5cbi8qKlxuICogTVAzIGRlbXV4ZXJcbiAqL1xuY2xhc3MgTVAzRGVtdXhlciBleHRlbmRzIEJhc2VBdWRpb0RlbXV4ZXIge1xuICByZXNldEluaXRTZWdtZW50KGluaXRTZWdtZW50LCBhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCB0cmFja0R1cmF0aW9uKSB7XG4gICAgc3VwZXIucmVzZXRJbml0U2VnbWVudChpbml0U2VnbWVudCwgYXVkaW9Db2RlYywgdmlkZW9Db2RlYywgdHJhY2tEdXJhdGlvbik7XG4gICAgdGhpcy5fYXVkaW9UcmFjayA9IHtcbiAgICAgIGNvbnRhaW5lcjogJ2F1ZGlvL21wZWcnLFxuICAgICAgdHlwZTogJ2F1ZGlvJyxcbiAgICAgIGlkOiAyLFxuICAgICAgcGlkOiAtMSxcbiAgICAgIHNlcXVlbmNlTnVtYmVyOiAwLFxuICAgICAgc2VnbWVudENvZGVjOiAnbXAzJyxcbiAgICAgIHNhbXBsZXM6IFtdLFxuICAgICAgbWFuaWZlc3RDb2RlYzogYXVkaW9Db2RlYyxcbiAgICAgIGR1cmF0aW9uOiB0cmFja0R1cmF0aW9uLFxuICAgICAgaW5wdXRUaW1lU2NhbGU6IDkwMDAwLFxuICAgICAgZHJvcHBlZDogMFxuICAgIH07XG4gIH1cbiAgc3RhdGljIHByb2JlKGRhdGEpIHtcbiAgICBpZiAoIWRhdGEpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBjaGVjayBpZiBkYXRhIGNvbnRhaW5zIElEMyB0aW1lc3RhbXAgYW5kIE1QRUcgc3luYyB3b3JkXG4gICAgLy8gTG9vayBmb3IgTVBFRyBoZWFkZXIgfCAxMTExIDExMTEgfCAxMTFYIFhZWlggfCB3aGVyZSBYIGNhbiBiZSBlaXRoZXIgMCBvciAxIGFuZCBZIG9yIFogc2hvdWxkIGJlIDFcbiAgICAvLyBMYXllciBiaXRzIChwb3NpdGlvbiAxNCBhbmQgMTUpIGluIGhlYWRlciBzaG91bGQgYmUgYWx3YXlzIGRpZmZlcmVudCBmcm9tIDAgKExheWVyIEkgb3IgTGF5ZXIgSUkgb3IgTGF5ZXIgSUlJKVxuICAgIC8vIE1vcmUgaW5mbyBodHRwOi8vd3d3Lm1wMy10ZWNoLm9yZy9wcm9ncmFtbWVyL2ZyYW1lX2hlYWRlci5odG1sXG4gICAgY29uc3QgaWQzRGF0YSA9IGdldElEM0RhdGEoZGF0YSwgMCk7XG4gICAgbGV0IG9mZnNldCA9IChpZDNEYXRhID09IG51bGwgPyB2b2lkIDAgOiBpZDNEYXRhLmxlbmd0aCkgfHwgMDtcblxuICAgIC8vIENoZWNrIGZvciBhYy0zfGVjLTMgc3luYyBieXRlcyBhbmQgcmV0dXJuIGZhbHNlIGlmIHByZXNlbnRcbiAgICBpZiAoaWQzRGF0YSAmJiBkYXRhW29mZnNldF0gPT09IDB4MGIgJiYgZGF0YVtvZmZzZXQgKyAxXSA9PT0gMHg3NyAmJiBnZXRUaW1lU3RhbXAoaWQzRGF0YSkgIT09IHVuZGVmaW5lZCAmJlxuICAgIC8vIGNoZWNrIHRoZSBic2lkIHRvIGNvbmZpcm0gYWMtMyBvciBlYy0zIChub3QgbXAzKVxuICAgIGdldEF1ZGlvQlNJRChkYXRhLCBvZmZzZXQpIDw9IDE2KSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGZvciAobGV0IGxlbmd0aCA9IGRhdGEubGVuZ3RoOyBvZmZzZXQgPCBsZW5ndGg7IG9mZnNldCsrKSB7XG4gICAgICBpZiAocHJvYmUoZGF0YSwgb2Zmc2V0KSkge1xuICAgICAgICBsb2dnZXIubG9nKCdNUEVHIEF1ZGlvIHN5bmMgd29yZCBmb3VuZCAhJyk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgY2FuUGFyc2UoZGF0YSwgb2Zmc2V0KSB7XG4gICAgcmV0dXJuIGNhblBhcnNlKGRhdGEsIG9mZnNldCk7XG4gIH1cbiAgYXBwZW5kRnJhbWUodHJhY2ssIGRhdGEsIG9mZnNldCkge1xuICAgIGlmICh0aGlzLmJhc2VQVFMgPT09IG51bGwpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcmV0dXJuIGFwcGVuZEZyYW1lJDEodHJhY2ssIGRhdGEsIG9mZnNldCwgdGhpcy5iYXNlUFRTLCB0aGlzLmZyYW1lSW5kZXgpO1xuICB9XG59XG5cbi8qKlxuICogIEFBQyBoZWxwZXJcbiAqL1xuXG5jbGFzcyBBQUMge1xuICBzdGF0aWMgZ2V0U2lsZW50RnJhbWUoY29kZWMsIGNoYW5uZWxDb3VudCkge1xuICAgIHN3aXRjaCAoY29kZWMpIHtcbiAgICAgIGNhc2UgJ21wNGEuNDAuMic6XG4gICAgICAgIGlmIChjaGFubmVsQ291bnQgPT09IDEpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4YzgsIDB4MDAsIDB4ODAsIDB4MjMsIDB4ODBdKTtcbiAgICAgICAgfSBlbHNlIGlmIChjaGFubmVsQ291bnQgPT09IDIpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MjEsIDB4MDAsIDB4NDksIDB4OTAsIDB4MDIsIDB4MTksIDB4MDAsIDB4MjMsIDB4ODBdKTtcbiAgICAgICAgfSBlbHNlIGlmIChjaGFubmVsQ291bnQgPT09IDMpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4YzgsIDB4MDAsIDB4ODAsIDB4MjAsIDB4ODQsIDB4MDEsIDB4MjYsIDB4NDAsIDB4MDgsIDB4NjQsIDB4MDAsIDB4OGVdKTtcbiAgICAgICAgfSBlbHNlIGlmIChjaGFubmVsQ291bnQgPT09IDQpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4YzgsIDB4MDAsIDB4ODAsIDB4MjAsIDB4ODQsIDB4MDEsIDB4MjYsIDB4NDAsIDB4MDgsIDB4NjQsIDB4MDAsIDB4ODAsIDB4MmMsIDB4ODAsIDB4MDgsIDB4MDIsIDB4MzhdKTtcbiAgICAgICAgfSBlbHNlIGlmIChjaGFubmVsQ291bnQgPT09IDUpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4YzgsIDB4MDAsIDB4ODAsIDB4MjAsIDB4ODQsIDB4MDEsIDB4MjYsIDB4NDAsIDB4MDgsIDB4NjQsIDB4MDAsIDB4ODIsIDB4MzAsIDB4MDQsIDB4OTksIDB4MDAsIDB4MjEsIDB4OTAsIDB4MDIsIDB4MzhdKTtcbiAgICAgICAgfSBlbHNlIGlmIChjaGFubmVsQ291bnQgPT09IDYpIHtcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4YzgsIDB4MDAsIDB4ODAsIDB4MjAsIDB4ODQsIDB4MDEsIDB4MjYsIDB4NDAsIDB4MDgsIDB4NjQsIDB4MDAsIDB4ODIsIDB4MzAsIDB4MDQsIDB4OTksIDB4MDAsIDB4MjEsIDB4OTAsIDB4MDIsIDB4MDAsIDB4YjIsIDB4MDAsIDB4MjAsIDB4MDgsIDB4ZTBdKTtcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIC8vIGhhbmRsZSBIRS1BQUMgYmVsb3cgKG1wNGEuNDAuNSAvIG1wNGEuNDAuMjkpXG4gICAgICBkZWZhdWx0OlxuICAgICAgICBpZiAoY2hhbm5lbENvdW50ID09PSAxKSB7XG4gICAgICAgICAgLy8gZmZtcGVnIC15IC1mIGxhdmZpIC1pIFwiYWV2YWxzcmM9MDpkPTAuMDVcIiAtYzphIGxpYmZka19hYWMgLXByb2ZpbGU6YSBhYWNfaGUgLWI6YSA0ayBvdXRwdXQuYWFjICYmIGhleGR1bXAgLXYgLWUgJzE2LzEgXCIweCV4LFwiIFwiXFxuXCInIC12IG91dHB1dC5hYWNcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MSwgMHg0MCwgMHgyMiwgMHg4MCwgMHhhMywgMHg0ZSwgMHhlNiwgMHg4MCwgMHhiYSwgMHg4LCAweDAsIDB4MCwgMHgwLCAweDFjLCAweDYsIDB4ZjEsIDB4YzEsIDB4YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1YSwgMHg1ZV0pO1xuICAgICAgICB9IGVsc2UgaWYgKGNoYW5uZWxDb3VudCA9PT0gMikge1xuICAgICAgICAgIC8vIGZmbXBlZyAteSAtZiBsYXZmaSAtaSBcImFldmFsc3JjPTB8MDpkPTAuMDVcIiAtYzphIGxpYmZka19hYWMgLXByb2ZpbGU6YSBhYWNfaGVfdjIgLWI6YSA0ayBvdXRwdXQuYWFjICYmIGhleGR1bXAgLXYgLWUgJzE2LzEgXCIweCV4LFwiIFwiXFxuXCInIC12IG91dHB1dC5hYWNcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MSwgMHg0MCwgMHgyMiwgMHg4MCwgMHhhMywgMHg1ZSwgMHhlNiwgMHg4MCwgMHhiYSwgMHg4LCAweDAsIDB4MCwgMHgwLCAweDAsIDB4OTUsIDB4MCwgMHg2LCAweGYxLCAweGExLCAweGEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWVdKTtcbiAgICAgICAgfSBlbHNlIGlmIChjaGFubmVsQ291bnQgPT09IDMpIHtcbiAgICAgICAgICAvLyBmZm1wZWcgLXkgLWYgbGF2ZmkgLWkgXCJhZXZhbHNyYz0wfDB8MDpkPTAuMDVcIiAtYzphIGxpYmZka19hYWMgLXByb2ZpbGU6YSBhYWNfaGVfdjIgLWI6YSA0ayBvdXRwdXQuYWFjICYmIGhleGR1bXAgLXYgLWUgJzE2LzEgXCIweCV4LFwiIFwiXFxuXCInIC12IG91dHB1dC5hYWNcbiAgICAgICAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MSwgMHg0MCwgMHgyMiwgMHg4MCwgMHhhMywgMHg1ZSwgMHhlNiwgMHg4MCwgMHhiYSwgMHg4LCAweDAsIDB4MCwgMHgwLCAweDAsIDB4OTUsIDB4MCwgMHg2LCAweGYxLCAweGExLCAweGEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWEsIDB4NWVdKTtcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICB9XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxufVxuXG4vKipcbiAqIEdlbmVyYXRlIE1QNCBCb3hcbiAqL1xuXG5jb25zdCBVSU5UMzJfTUFYID0gTWF0aC5wb3coMiwgMzIpIC0gMTtcbmNsYXNzIE1QNCB7XG4gIHN0YXRpYyBpbml0KCkge1xuICAgIE1QNC50eXBlcyA9IHtcbiAgICAgIGF2YzE6IFtdLFxuICAgICAgLy8gY29kaW5nbmFtZVxuICAgICAgYXZjQzogW10sXG4gICAgICBidHJ0OiBbXSxcbiAgICAgIGRpbmY6IFtdLFxuICAgICAgZHJlZjogW10sXG4gICAgICBlc2RzOiBbXSxcbiAgICAgIGZ0eXA6IFtdLFxuICAgICAgaGRscjogW10sXG4gICAgICBtZGF0OiBbXSxcbiAgICAgIG1kaGQ6IFtdLFxuICAgICAgbWRpYTogW10sXG4gICAgICBtZmhkOiBbXSxcbiAgICAgIG1pbmY6IFtdLFxuICAgICAgbW9vZjogW10sXG4gICAgICBtb292OiBbXSxcbiAgICAgIG1wNGE6IFtdLFxuICAgICAgJy5tcDMnOiBbXSxcbiAgICAgIGRhYzM6IFtdLFxuICAgICAgJ2FjLTMnOiBbXSxcbiAgICAgIG12ZXg6IFtdLFxuICAgICAgbXZoZDogW10sXG4gICAgICBwYXNwOiBbXSxcbiAgICAgIHNkdHA6IFtdLFxuICAgICAgc3RibDogW10sXG4gICAgICBzdGNvOiBbXSxcbiAgICAgIHN0c2M6IFtdLFxuICAgICAgc3RzZDogW10sXG4gICAgICBzdHN6OiBbXSxcbiAgICAgIHN0dHM6IFtdLFxuICAgICAgdGZkdDogW10sXG4gICAgICB0ZmhkOiBbXSxcbiAgICAgIHRyYWY6IFtdLFxuICAgICAgdHJhazogW10sXG4gICAgICB0cnVuOiBbXSxcbiAgICAgIHRyZXg6IFtdLFxuICAgICAgdGtoZDogW10sXG4gICAgICB2bWhkOiBbXSxcbiAgICAgIHNtaGQ6IFtdXG4gICAgfTtcbiAgICBsZXQgaTtcbiAgICBmb3IgKGkgaW4gTVA0LnR5cGVzKSB7XG4gICAgICBpZiAoTVA0LnR5cGVzLmhhc093blByb3BlcnR5KGkpKSB7XG4gICAgICAgIE1QNC50eXBlc1tpXSA9IFtpLmNoYXJDb2RlQXQoMCksIGkuY2hhckNvZGVBdCgxKSwgaS5jaGFyQ29kZUF0KDIpLCBpLmNoYXJDb2RlQXQoMyldO1xuICAgICAgfVxuICAgIH1cbiAgICBjb25zdCB2aWRlb0hkbHIgPSBuZXcgVWludDhBcnJheShbMHgwMCxcbiAgICAvLyB2ZXJzaW9uIDBcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBwcmVfZGVmaW5lZFxuICAgIDB4NzYsIDB4NjksIDB4NjQsIDB4NjUsXG4gICAgLy8gaGFuZGxlcl90eXBlOiAndmlkZSdcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHJlc2VydmVkXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyByZXNlcnZlZFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDU2LCAweDY5LCAweDY0LCAweDY1LCAweDZmLCAweDQ4LCAweDYxLCAweDZlLCAweDY0LCAweDZjLCAweDY1LCAweDcyLCAweDAwIC8vIG5hbWU6ICdWaWRlb0hhbmRsZXInXG4gICAgXSk7XG4gICAgY29uc3QgYXVkaW9IZGxyID0gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsXG4gICAgLy8gdmVyc2lvbiAwXG4gICAgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBmbGFnc1xuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcHJlX2RlZmluZWRcbiAgICAweDczLCAweDZmLCAweDc1LCAweDZlLFxuICAgIC8vIGhhbmRsZXJfdHlwZTogJ3NvdW4nXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyByZXNlcnZlZFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHJlc2VydmVkXG4gICAgMHg1MywgMHg2ZiwgMHg3NSwgMHg2ZSwgMHg2NCwgMHg0OCwgMHg2MSwgMHg2ZSwgMHg2NCwgMHg2YywgMHg2NSwgMHg3MiwgMHgwMCAvLyBuYW1lOiAnU291bmRIYW5kbGVyJ1xuICAgIF0pO1xuICAgIE1QNC5IRExSX1RZUEVTID0ge1xuICAgICAgdmlkZW86IHZpZGVvSGRscixcbiAgICAgIGF1ZGlvOiBhdWRpb0hkbHJcbiAgICB9O1xuICAgIGNvbnN0IGRyZWYgPSBuZXcgVWludDhBcnJheShbMHgwMCxcbiAgICAvLyB2ZXJzaW9uIDBcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMSxcbiAgICAvLyBlbnRyeV9jb3VudFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MGMsXG4gICAgLy8gZW50cnlfc2l6ZVxuICAgIDB4NzUsIDB4NzIsIDB4NmMsIDB4MjAsXG4gICAgLy8gJ3VybCcgdHlwZVxuICAgIDB4MDAsXG4gICAgLy8gdmVyc2lvbiAwXG4gICAgMHgwMCwgMHgwMCwgMHgwMSAvLyBlbnRyeV9mbGFnc1xuICAgIF0pO1xuICAgIGNvbnN0IHN0Y28gPSBuZXcgVWludDhBcnJheShbMHgwMCxcbiAgICAvLyB2ZXJzaW9uXG4gICAgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBmbGFnc1xuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAgLy8gZW50cnlfY291bnRcbiAgICBdKTtcbiAgICBNUDQuU1RUUyA9IE1QNC5TVFNDID0gTVA0LlNUQ08gPSBzdGNvO1xuICAgIE1QNC5TVFNaID0gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsXG4gICAgLy8gdmVyc2lvblxuICAgIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gZmxhZ3NcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHNhbXBsZV9zaXplXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCAvLyBzYW1wbGVfY291bnRcbiAgICBdKTtcbiAgICBNUDQuVk1IRCA9IG5ldyBVaW50OEFycmF5KFsweDAwLFxuICAgIC8vIHZlcnNpb25cbiAgICAweDAwLCAweDAwLCAweDAxLFxuICAgIC8vIGZsYWdzXG4gICAgMHgwMCwgMHgwMCxcbiAgICAvLyBncmFwaGljc21vZGVcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwIC8vIG9wY29sb3JcbiAgICBdKTtcbiAgICBNUDQuU01IRCA9IG5ldyBVaW50OEFycmF5KFsweDAwLFxuICAgIC8vIHZlcnNpb25cbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG4gICAgMHgwMCwgMHgwMCxcbiAgICAvLyBiYWxhbmNlXG4gICAgMHgwMCwgMHgwMCAvLyByZXNlcnZlZFxuICAgIF0pO1xuICAgIE1QNC5TVFNEID0gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsXG4gICAgLy8gdmVyc2lvbiAwXG4gICAgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBmbGFnc1xuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDFdKTsgLy8gZW50cnlfY291bnRcblxuICAgIGNvbnN0IG1ham9yQnJhbmQgPSBuZXcgVWludDhBcnJheShbMTA1LCAxMTUsIDExMSwgMTA5XSk7IC8vIGlzb21cbiAgICBjb25zdCBhdmMxQnJhbmQgPSBuZXcgVWludDhBcnJheShbOTcsIDExOCwgOTksIDQ5XSk7IC8vIGF2YzFcbiAgICBjb25zdCBtaW5vclZlcnNpb24gPSBuZXcgVWludDhBcnJheShbMCwgMCwgMCwgMV0pO1xuICAgIE1QNC5GVFlQID0gTVA0LmJveChNUDQudHlwZXMuZnR5cCwgbWFqb3JCcmFuZCwgbWlub3JWZXJzaW9uLCBtYWpvckJyYW5kLCBhdmMxQnJhbmQpO1xuICAgIE1QNC5ESU5GID0gTVA0LmJveChNUDQudHlwZXMuZGluZiwgTVA0LmJveChNUDQudHlwZXMuZHJlZiwgZHJlZikpO1xuICB9XG4gIHN0YXRpYyBib3godHlwZSwgLi4ucGF5bG9hZCkge1xuICAgIGxldCBzaXplID0gODtcbiAgICBsZXQgaSA9IHBheWxvYWQubGVuZ3RoO1xuICAgIGNvbnN0IGxlbiA9IGk7XG4gICAgLy8gY2FsY3VsYXRlIHRoZSB0b3RhbCBzaXplIHdlIG5lZWQgdG8gYWxsb2NhdGVcbiAgICB3aGlsZSAoaS0tKSB7XG4gICAgICBzaXplICs9IHBheWxvYWRbaV0uYnl0ZUxlbmd0aDtcbiAgICB9XG4gICAgY29uc3QgcmVzdWx0ID0gbmV3IFVpbnQ4QXJyYXkoc2l6ZSk7XG4gICAgcmVzdWx0WzBdID0gc2l6ZSA+PiAyNCAmIDB4ZmY7XG4gICAgcmVzdWx0WzFdID0gc2l6ZSA+PiAxNiAmIDB4ZmY7XG4gICAgcmVzdWx0WzJdID0gc2l6ZSA+PiA4ICYgMHhmZjtcbiAgICByZXN1bHRbM10gPSBzaXplICYgMHhmZjtcbiAgICByZXN1bHQuc2V0KHR5cGUsIDQpO1xuICAgIC8vIGNvcHkgdGhlIHBheWxvYWQgaW50byB0aGUgcmVzdWx0XG4gICAgZm9yIChpID0gMCwgc2l6ZSA9IDg7IGkgPCBsZW47IGkrKykge1xuICAgICAgLy8gY29weSBwYXlsb2FkW2ldIGFycmF5IEAgb2Zmc2V0IHNpemVcbiAgICAgIHJlc3VsdC5zZXQocGF5bG9hZFtpXSwgc2l6ZSk7XG4gICAgICBzaXplICs9IHBheWxvYWRbaV0uYnl0ZUxlbmd0aDtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuICBzdGF0aWMgaGRscih0eXBlKSB7XG4gICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLmhkbHIsIE1QNC5IRExSX1RZUEVTW3R5cGVdKTtcbiAgfVxuICBzdGF0aWMgbWRhdChkYXRhKSB7XG4gICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLm1kYXQsIGRhdGEpO1xuICB9XG4gIHN0YXRpYyBtZGhkKHRpbWVzY2FsZSwgZHVyYXRpb24pIHtcbiAgICBkdXJhdGlvbiAqPSB0aW1lc2NhbGU7XG4gICAgY29uc3QgdXBwZXJXb3JkRHVyYXRpb24gPSBNYXRoLmZsb29yKGR1cmF0aW9uIC8gKFVJTlQzMl9NQVggKyAxKSk7XG4gICAgY29uc3QgbG93ZXJXb3JkRHVyYXRpb24gPSBNYXRoLmZsb29yKGR1cmF0aW9uICUgKFVJTlQzMl9NQVggKyAxKSk7XG4gICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLm1kaGQsIG5ldyBVaW50OEFycmF5KFsweDAxLFxuICAgIC8vIHZlcnNpb24gMVxuICAgIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gZmxhZ3NcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAyLFxuICAgIC8vIGNyZWF0aW9uX3RpbWVcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAzLFxuICAgIC8vIG1vZGlmaWNhdGlvbl90aW1lXG4gICAgdGltZXNjYWxlID4+IDI0ICYgMHhmZiwgdGltZXNjYWxlID4+IDE2ICYgMHhmZiwgdGltZXNjYWxlID4+IDggJiAweGZmLCB0aW1lc2NhbGUgJiAweGZmLFxuICAgIC8vIHRpbWVzY2FsZVxuICAgIHVwcGVyV29yZER1cmF0aW9uID4+IDI0LCB1cHBlcldvcmREdXJhdGlvbiA+PiAxNiAmIDB4ZmYsIHVwcGVyV29yZER1cmF0aW9uID4+IDggJiAweGZmLCB1cHBlcldvcmREdXJhdGlvbiAmIDB4ZmYsIGxvd2VyV29yZER1cmF0aW9uID4+IDI0LCBsb3dlcldvcmREdXJhdGlvbiA+PiAxNiAmIDB4ZmYsIGxvd2VyV29yZER1cmF0aW9uID4+IDggJiAweGZmLCBsb3dlcldvcmREdXJhdGlvbiAmIDB4ZmYsIDB4NTUsIDB4YzQsXG4gICAgLy8gJ3VuZCcgbGFuZ3VhZ2UgKHVuZGV0ZXJtaW5lZClcbiAgICAweDAwLCAweDAwXSkpO1xuICB9XG4gIHN0YXRpYyBtZGlhKHRyYWNrKSB7XG4gICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLm1kaWEsIE1QNC5tZGhkKHRyYWNrLnRpbWVzY2FsZSwgdHJhY2suZHVyYXRpb24pLCBNUDQuaGRscih0cmFjay50eXBlKSwgTVA0Lm1pbmYodHJhY2spKTtcbiAgfVxuICBzdGF0aWMgbWZoZChzZXF1ZW5jZU51bWJlcikge1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy5tZmhkLCBuZXcgVWludDhBcnJheShbMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBmbGFnc1xuICAgIHNlcXVlbmNlTnVtYmVyID4+IDI0LCBzZXF1ZW5jZU51bWJlciA+PiAxNiAmIDB4ZmYsIHNlcXVlbmNlTnVtYmVyID4+IDggJiAweGZmLCBzZXF1ZW5jZU51bWJlciAmIDB4ZmYgLy8gc2VxdWVuY2VfbnVtYmVyXG4gICAgXSkpO1xuICB9XG4gIHN0YXRpYyBtaW5mKHRyYWNrKSB7XG4gICAgaWYgKHRyYWNrLnR5cGUgPT09ICdhdWRpbycpIHtcbiAgICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy5taW5mLCBNUDQuYm94KE1QNC50eXBlcy5zbWhkLCBNUDQuU01IRCksIE1QNC5ESU5GLCBNUDQuc3RibCh0cmFjaykpO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gTVA0LmJveChNUDQudHlwZXMubWluZiwgTVA0LmJveChNUDQudHlwZXMudm1oZCwgTVA0LlZNSEQpLCBNUDQuRElORiwgTVA0LnN0YmwodHJhY2spKTtcbiAgICB9XG4gIH1cbiAgc3RhdGljIG1vb2Yoc24sIGJhc2VNZWRpYURlY29kZVRpbWUsIHRyYWNrKSB7XG4gICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLm1vb2YsIE1QNC5tZmhkKHNuKSwgTVA0LnRyYWYodHJhY2ssIGJhc2VNZWRpYURlY29kZVRpbWUpKTtcbiAgfVxuICBzdGF0aWMgbW9vdih0cmFja3MpIHtcbiAgICBsZXQgaSA9IHRyYWNrcy5sZW5ndGg7XG4gICAgY29uc3QgYm94ZXMgPSBbXTtcbiAgICB3aGlsZSAoaS0tKSB7XG4gICAgICBib3hlc1tpXSA9IE1QNC50cmFrKHRyYWNrc1tpXSk7XG4gICAgfVxuICAgIHJldHVybiBNUDQuYm94LmFwcGx5KG51bGwsIFtNUDQudHlwZXMubW9vdiwgTVA0Lm12aGQodHJhY2tzWzBdLnRpbWVzY2FsZSwgdHJhY2tzWzBdLmR1cmF0aW9uKV0uY29uY2F0KGJveGVzKS5jb25jYXQoTVA0Lm12ZXgodHJhY2tzKSkpO1xuICB9XG4gIHN0YXRpYyBtdmV4KHRyYWNrcykge1xuICAgIGxldCBpID0gdHJhY2tzLmxlbmd0aDtcbiAgICBjb25zdCBib3hlcyA9IFtdO1xuICAgIHdoaWxlIChpLS0pIHtcbiAgICAgIGJveGVzW2ldID0gTVA0LnRyZXgodHJhY2tzW2ldKTtcbiAgICB9XG4gICAgcmV0dXJuIE1QNC5ib3guYXBwbHkobnVsbCwgW01QNC50eXBlcy5tdmV4LCAuLi5ib3hlc10pO1xuICB9XG4gIHN0YXRpYyBtdmhkKHRpbWVzY2FsZSwgZHVyYXRpb24pIHtcbiAgICBkdXJhdGlvbiAqPSB0aW1lc2NhbGU7XG4gICAgY29uc3QgdXBwZXJXb3JkRHVyYXRpb24gPSBNYXRoLmZsb29yKGR1cmF0aW9uIC8gKFVJTlQzMl9NQVggKyAxKSk7XG4gICAgY29uc3QgbG93ZXJXb3JkRHVyYXRpb24gPSBNYXRoLmZsb29yKGR1cmF0aW9uICUgKFVJTlQzMl9NQVggKyAxKSk7XG4gICAgY29uc3QgYnl0ZXMgPSBuZXcgVWludDhBcnJheShbMHgwMSxcbiAgICAvLyB2ZXJzaW9uIDFcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMixcbiAgICAvLyBjcmVhdGlvbl90aW1lXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMyxcbiAgICAvLyBtb2RpZmljYXRpb25fdGltZVxuICAgIHRpbWVzY2FsZSA+PiAyNCAmIDB4ZmYsIHRpbWVzY2FsZSA+PiAxNiAmIDB4ZmYsIHRpbWVzY2FsZSA+PiA4ICYgMHhmZiwgdGltZXNjYWxlICYgMHhmZixcbiAgICAvLyB0aW1lc2NhbGVcbiAgICB1cHBlcldvcmREdXJhdGlvbiA+PiAyNCwgdXBwZXJXb3JkRHVyYXRpb24gPj4gMTYgJiAweGZmLCB1cHBlcldvcmREdXJhdGlvbiA+PiA4ICYgMHhmZiwgdXBwZXJXb3JkRHVyYXRpb24gJiAweGZmLCBsb3dlcldvcmREdXJhdGlvbiA+PiAyNCwgbG93ZXJXb3JkRHVyYXRpb24gPj4gMTYgJiAweGZmLCBsb3dlcldvcmREdXJhdGlvbiA+PiA4ICYgMHhmZiwgbG93ZXJXb3JkRHVyYXRpb24gJiAweGZmLCAweDAwLCAweDAxLCAweDAwLCAweDAwLFxuICAgIC8vIDEuMCByYXRlXG4gICAgMHgwMSwgMHgwMCxcbiAgICAvLyAxLjAgdm9sdW1lXG4gICAgMHgwMCwgMHgwMCxcbiAgICAvLyByZXNlcnZlZFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHJlc2VydmVkXG4gICAgMHgwMCwgMHgwMSwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMSwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHg0MCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyB0cmFuc2Zvcm1hdGlvbjogdW5pdHkgbWF0cml4XG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBwcmVfZGVmaW5lZFxuICAgIDB4ZmYsIDB4ZmYsIDB4ZmYsIDB4ZmYgLy8gbmV4dF90cmFja19JRFxuICAgIF0pO1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy5tdmhkLCBieXRlcyk7XG4gIH1cbiAgc3RhdGljIHNkdHAodHJhY2spIHtcbiAgICBjb25zdCBzYW1wbGVzID0gdHJhY2suc2FtcGxlcyB8fCBbXTtcbiAgICBjb25zdCBieXRlcyA9IG5ldyBVaW50OEFycmF5KDQgKyBzYW1wbGVzLmxlbmd0aCk7XG4gICAgbGV0IGk7XG4gICAgbGV0IGZsYWdzO1xuICAgIC8vIGxlYXZlIHRoZSBmdWxsIGJveCBoZWFkZXIgKDQgYnl0ZXMpIGFsbCB6ZXJvXG4gICAgLy8gd3JpdGUgdGhlIHNhbXBsZSB0YWJsZVxuICAgIGZvciAoaSA9IDA7IGkgPCBzYW1wbGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBmbGFncyA9IHNhbXBsZXNbaV0uZmxhZ3M7XG4gICAgICBieXRlc1tpICsgNF0gPSBmbGFncy5kZXBlbmRzT24gPDwgNCB8IGZsYWdzLmlzRGVwZW5kZWRPbiA8PCAyIHwgZmxhZ3MuaGFzUmVkdW5kYW5jeTtcbiAgICB9XG4gICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLnNkdHAsIGJ5dGVzKTtcbiAgfVxuICBzdGF0aWMgc3RibCh0cmFjaykge1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy5zdGJsLCBNUDQuc3RzZCh0cmFjayksIE1QNC5ib3goTVA0LnR5cGVzLnN0dHMsIE1QNC5TVFRTKSwgTVA0LmJveChNUDQudHlwZXMuc3RzYywgTVA0LlNUU0MpLCBNUDQuYm94KE1QNC50eXBlcy5zdHN6LCBNUDQuU1RTWiksIE1QNC5ib3goTVA0LnR5cGVzLnN0Y28sIE1QNC5TVENPKSk7XG4gIH1cbiAgc3RhdGljIGF2YzEodHJhY2spIHtcbiAgICBsZXQgc3BzID0gW107XG4gICAgbGV0IHBwcyA9IFtdO1xuICAgIGxldCBpO1xuICAgIGxldCBkYXRhO1xuICAgIGxldCBsZW47XG4gICAgLy8gYXNzZW1ibGUgdGhlIFNQU3NcblxuICAgIGZvciAoaSA9IDA7IGkgPCB0cmFjay5zcHMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGRhdGEgPSB0cmFjay5zcHNbaV07XG4gICAgICBsZW4gPSBkYXRhLmJ5dGVMZW5ndGg7XG4gICAgICBzcHMucHVzaChsZW4gPj4+IDggJiAweGZmKTtcbiAgICAgIHNwcy5wdXNoKGxlbiAmIDB4ZmYpO1xuXG4gICAgICAvLyBTUFNcbiAgICAgIHNwcyA9IHNwcy5jb25jYXQoQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoZGF0YSkpO1xuICAgIH1cblxuICAgIC8vIGFzc2VtYmxlIHRoZSBQUFNzXG4gICAgZm9yIChpID0gMDsgaSA8IHRyYWNrLnBwcy5sZW5ndGg7IGkrKykge1xuICAgICAgZGF0YSA9IHRyYWNrLnBwc1tpXTtcbiAgICAgIGxlbiA9IGRhdGEuYnl0ZUxlbmd0aDtcbiAgICAgIHBwcy5wdXNoKGxlbiA+Pj4gOCAmIDB4ZmYpO1xuICAgICAgcHBzLnB1c2gobGVuICYgMHhmZik7XG4gICAgICBwcHMgPSBwcHMuY29uY2F0KEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGRhdGEpKTtcbiAgICB9XG4gICAgY29uc3QgYXZjYyA9IE1QNC5ib3goTVA0LnR5cGVzLmF2Y0MsIG5ldyBVaW50OEFycmF5KFsweDAxLFxuICAgIC8vIHZlcnNpb25cbiAgICBzcHNbM10sXG4gICAgLy8gcHJvZmlsZVxuICAgIHNwc1s0XSxcbiAgICAvLyBwcm9maWxlIGNvbXBhdFxuICAgIHNwc1s1XSxcbiAgICAvLyBsZXZlbFxuICAgIDB4ZmMgfCAzLFxuICAgIC8vIGxlbmd0aFNpemVNaW51c09uZSwgaGFyZC1jb2RlZCB0byA0IGJ5dGVzXG4gICAgMHhlMCB8IHRyYWNrLnNwcy5sZW5ndGggLy8gM2JpdCByZXNlcnZlZCAoMTExKSArIG51bU9mU2VxdWVuY2VQYXJhbWV0ZXJTZXRzXG4gICAgXS5jb25jYXQoc3BzKS5jb25jYXQoW3RyYWNrLnBwcy5sZW5ndGggLy8gbnVtT2ZQaWN0dXJlUGFyYW1ldGVyU2V0c1xuICAgIF0pLmNvbmNhdChwcHMpKSk7IC8vIFwiUFBTXCJcbiAgICBjb25zdCB3aWR0aCA9IHRyYWNrLndpZHRoO1xuICAgIGNvbnN0IGhlaWdodCA9IHRyYWNrLmhlaWdodDtcbiAgICBjb25zdCBoU3BhY2luZyA9IHRyYWNrLnBpeGVsUmF0aW9bMF07XG4gICAgY29uc3QgdlNwYWNpbmcgPSB0cmFjay5waXhlbFJhdGlvWzFdO1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy5hdmMxLCBuZXcgVWludDhBcnJheShbMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyByZXNlcnZlZFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCAweDAxLFxuICAgIC8vIGRhdGFfcmVmZXJlbmNlX2luZGV4XG4gICAgMHgwMCwgMHgwMCxcbiAgICAvLyBwcmVfZGVmaW5lZFxuICAgIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHByZV9kZWZpbmVkXG4gICAgd2lkdGggPj4gOCAmIDB4ZmYsIHdpZHRoICYgMHhmZixcbiAgICAvLyB3aWR0aFxuICAgIGhlaWdodCA+PiA4ICYgMHhmZiwgaGVpZ2h0ICYgMHhmZixcbiAgICAvLyBoZWlnaHRcbiAgICAweDAwLCAweDQ4LCAweDAwLCAweDAwLFxuICAgIC8vIGhvcml6cmVzb2x1dGlvblxuICAgIDB4MDAsIDB4NDgsIDB4MDAsIDB4MDAsXG4gICAgLy8gdmVydHJlc29sdXRpb25cbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHJlc2VydmVkXG4gICAgMHgwMCwgMHgwMSxcbiAgICAvLyBmcmFtZV9jb3VudFxuICAgIDB4MTIsIDB4NjQsIDB4NjEsIDB4NjksIDB4NmMsXG4gICAgLy8gZGFpbHltb3Rpb24vaGxzLmpzXG4gICAgMHg3OSwgMHg2ZCwgMHg2ZiwgMHg3NCwgMHg2OSwgMHg2ZiwgMHg2ZSwgMHgyZiwgMHg2OCwgMHg2YywgMHg3MywgMHgyZSwgMHg2YSwgMHg3MywgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBjb21wcmVzc29ybmFtZVxuICAgIDB4MDAsIDB4MTgsXG4gICAgLy8gZGVwdGggPSAyNFxuICAgIDB4MTEsIDB4MTFdKSxcbiAgICAvLyBwcmVfZGVmaW5lZCA9IC0xXG4gICAgYXZjYywgTVA0LmJveChNUDQudHlwZXMuYnRydCwgbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4MWMsIDB4OWMsIDB4ODAsXG4gICAgLy8gYnVmZmVyU2l6ZURCXG4gICAgMHgwMCwgMHgyZCwgMHhjNiwgMHhjMCxcbiAgICAvLyBtYXhCaXRyYXRlXG4gICAgMHgwMCwgMHgyZCwgMHhjNiwgMHhjMF0pKSxcbiAgICAvLyBhdmdCaXRyYXRlXG4gICAgTVA0LmJveChNUDQudHlwZXMucGFzcCwgbmV3IFVpbnQ4QXJyYXkoW2hTcGFjaW5nID4+IDI0LFxuICAgIC8vIGhTcGFjaW5nXG4gICAgaFNwYWNpbmcgPj4gMTYgJiAweGZmLCBoU3BhY2luZyA+PiA4ICYgMHhmZiwgaFNwYWNpbmcgJiAweGZmLCB2U3BhY2luZyA+PiAyNCxcbiAgICAvLyB2U3BhY2luZ1xuICAgIHZTcGFjaW5nID4+IDE2ICYgMHhmZiwgdlNwYWNpbmcgPj4gOCAmIDB4ZmYsIHZTcGFjaW5nICYgMHhmZl0pKSk7XG4gIH1cbiAgc3RhdGljIGVzZHModHJhY2spIHtcbiAgICBjb25zdCBjb25maWdsZW4gPSB0cmFjay5jb25maWcubGVuZ3RoO1xuICAgIHJldHVybiBuZXcgVWludDhBcnJheShbMHgwMCxcbiAgICAvLyB2ZXJzaW9uIDBcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG5cbiAgICAweDAzLFxuICAgIC8vIGRlc2NyaXB0b3JfdHlwZVxuICAgIDB4MTcgKyBjb25maWdsZW4sXG4gICAgLy8gbGVuZ3RoXG4gICAgMHgwMCwgMHgwMSxcbiAgICAvLyBlc19pZFxuICAgIDB4MDAsXG4gICAgLy8gc3RyZWFtX3ByaW9yaXR5XG5cbiAgICAweDA0LFxuICAgIC8vIGRlc2NyaXB0b3JfdHlwZVxuICAgIDB4MGYgKyBjb25maWdsZW4sXG4gICAgLy8gbGVuZ3RoXG4gICAgMHg0MCxcbiAgICAvLyBjb2RlYyA6IG1wZWc0X2F1ZGlvXG4gICAgMHgxNSxcbiAgICAvLyBzdHJlYW1fdHlwZVxuICAgIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gYnVmZmVyX3NpemVcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIG1heEJpdHJhdGVcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGF2Z0JpdHJhdGVcblxuICAgIDB4MDUgLy8gZGVzY3JpcHRvcl90eXBlXG4gICAgXS5jb25jYXQoW2NvbmZpZ2xlbl0pLmNvbmNhdCh0cmFjay5jb25maWcpLmNvbmNhdChbMHgwNiwgMHgwMSwgMHgwMl0pKTsgLy8gR0FTcGVjaWZpY0NvbmZpZykpOyAvLyBsZW5ndGggKyBhdWRpbyBjb25maWcgZGVzY3JpcHRvclxuICB9XG4gIHN0YXRpYyBhdWRpb1N0c2QodHJhY2spIHtcbiAgICBjb25zdCBzYW1wbGVyYXRlID0gdHJhY2suc2FtcGxlcmF0ZTtcbiAgICByZXR1cm4gbmV3IFVpbnQ4QXJyYXkoWzB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIHJlc2VydmVkXG4gICAgMHgwMCwgMHgwMSxcbiAgICAvLyBkYXRhX3JlZmVyZW5jZV9pbmRleFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCB0cmFjay5jaGFubmVsQ291bnQsXG4gICAgLy8gY2hhbm5lbGNvdW50XG4gICAgMHgwMCwgMHgxMCxcbiAgICAvLyBzYW1wbGVTaXplOjE2Yml0c1xuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWQyXG4gICAgc2FtcGxlcmF0ZSA+PiA4ICYgMHhmZiwgc2FtcGxlcmF0ZSAmIDB4ZmYsXG4gICAgLy9cbiAgICAweDAwLCAweDAwXSk7XG4gIH1cbiAgc3RhdGljIG1wNGEodHJhY2spIHtcbiAgICByZXR1cm4gTVA0LmJveChNUDQudHlwZXMubXA0YSwgTVA0LmF1ZGlvU3RzZCh0cmFjayksIE1QNC5ib3goTVA0LnR5cGVzLmVzZHMsIE1QNC5lc2RzKHRyYWNrKSkpO1xuICB9XG4gIHN0YXRpYyBtcDModHJhY2spIHtcbiAgICByZXR1cm4gTVA0LmJveChNUDQudHlwZXNbJy5tcDMnXSwgTVA0LmF1ZGlvU3RzZCh0cmFjaykpO1xuICB9XG4gIHN0YXRpYyBhYzModHJhY2spIHtcbiAgICByZXR1cm4gTVA0LmJveChNUDQudHlwZXNbJ2FjLTMnXSwgTVA0LmF1ZGlvU3RzZCh0cmFjayksIE1QNC5ib3goTVA0LnR5cGVzLmRhYzMsIHRyYWNrLmNvbmZpZykpO1xuICB9XG4gIHN0YXRpYyBzdHNkKHRyYWNrKSB7XG4gICAgaWYgKHRyYWNrLnR5cGUgPT09ICdhdWRpbycpIHtcbiAgICAgIGlmICh0cmFjay5zZWdtZW50Q29kZWMgPT09ICdtcDMnICYmIHRyYWNrLmNvZGVjID09PSAnbXAzJykge1xuICAgICAgICByZXR1cm4gTVA0LmJveChNUDQudHlwZXMuc3RzZCwgTVA0LlNUU0QsIE1QNC5tcDModHJhY2spKTtcbiAgICAgIH1cbiAgICAgIGlmICh0cmFjay5zZWdtZW50Q29kZWMgPT09ICdhYzMnKSB7XG4gICAgICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy5zdHNkLCBNUDQuU1RTRCwgTVA0LmFjMyh0cmFjaykpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIE1QNC5ib3goTVA0LnR5cGVzLnN0c2QsIE1QNC5TVFNELCBNUDQubXA0YSh0cmFjaykpO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gTVA0LmJveChNUDQudHlwZXMuc3RzZCwgTVA0LlNUU0QsIE1QNC5hdmMxKHRyYWNrKSk7XG4gICAgfVxuICB9XG4gIHN0YXRpYyB0a2hkKHRyYWNrKSB7XG4gICAgY29uc3QgaWQgPSB0cmFjay5pZDtcbiAgICBjb25zdCBkdXJhdGlvbiA9IHRyYWNrLmR1cmF0aW9uICogdHJhY2sudGltZXNjYWxlO1xuICAgIGNvbnN0IHdpZHRoID0gdHJhY2sud2lkdGg7XG4gICAgY29uc3QgaGVpZ2h0ID0gdHJhY2suaGVpZ2h0O1xuICAgIGNvbnN0IHVwcGVyV29yZER1cmF0aW9uID0gTWF0aC5mbG9vcihkdXJhdGlvbiAvIChVSU5UMzJfTUFYICsgMSkpO1xuICAgIGNvbnN0IGxvd2VyV29yZER1cmF0aW9uID0gTWF0aC5mbG9vcihkdXJhdGlvbiAlIChVSU5UMzJfTUFYICsgMSkpO1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy50a2hkLCBuZXcgVWludDhBcnJheShbMHgwMSxcbiAgICAvLyB2ZXJzaW9uIDFcbiAgICAweDAwLCAweDAwLCAweDA3LFxuICAgIC8vIGZsYWdzXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMixcbiAgICAvLyBjcmVhdGlvbl90aW1lXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMyxcbiAgICAvLyBtb2RpZmljYXRpb25fdGltZVxuICAgIGlkID4+IDI0ICYgMHhmZiwgaWQgPj4gMTYgJiAweGZmLCBpZCA+PiA4ICYgMHhmZiwgaWQgJiAweGZmLFxuICAgIC8vIHRyYWNrX0lEXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyByZXNlcnZlZFxuICAgIHVwcGVyV29yZER1cmF0aW9uID4+IDI0LCB1cHBlcldvcmREdXJhdGlvbiA+PiAxNiAmIDB4ZmYsIHVwcGVyV29yZER1cmF0aW9uID4+IDggJiAweGZmLCB1cHBlcldvcmREdXJhdGlvbiAmIDB4ZmYsIGxvd2VyV29yZER1cmF0aW9uID4+IDI0LCBsb3dlcldvcmREdXJhdGlvbiA+PiAxNiAmIDB4ZmYsIGxvd2VyV29yZER1cmF0aW9uID4+IDggJiAweGZmLCBsb3dlcldvcmREdXJhdGlvbiAmIDB4ZmYsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDAsXG4gICAgLy8gcmVzZXJ2ZWRcbiAgICAweDAwLCAweDAwLFxuICAgIC8vIGxheWVyXG4gICAgMHgwMCwgMHgwMCxcbiAgICAvLyBhbHRlcm5hdGVfZ3JvdXBcbiAgICAweDAwLCAweDAwLFxuICAgIC8vIG5vbi1hdWRpbyB0cmFjayB2b2x1bWVcbiAgICAweDAwLCAweDAwLFxuICAgIC8vIHJlc2VydmVkXG4gICAgMHgwMCwgMHgwMSwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMSwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCwgMHg0MCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyB0cmFuc2Zvcm1hdGlvbjogdW5pdHkgbWF0cml4XG4gICAgd2lkdGggPj4gOCAmIDB4ZmYsIHdpZHRoICYgMHhmZiwgMHgwMCwgMHgwMCxcbiAgICAvLyB3aWR0aFxuICAgIGhlaWdodCA+PiA4ICYgMHhmZiwgaGVpZ2h0ICYgMHhmZiwgMHgwMCwgMHgwMCAvLyBoZWlnaHRcbiAgICBdKSk7XG4gIH1cbiAgc3RhdGljIHRyYWYodHJhY2ssIGJhc2VNZWRpYURlY29kZVRpbWUpIHtcbiAgICBjb25zdCBzYW1wbGVEZXBlbmRlbmN5VGFibGUgPSBNUDQuc2R0cCh0cmFjayk7XG4gICAgY29uc3QgaWQgPSB0cmFjay5pZDtcbiAgICBjb25zdCB1cHBlcldvcmRCYXNlTWVkaWFEZWNvZGVUaW1lID0gTWF0aC5mbG9vcihiYXNlTWVkaWFEZWNvZGVUaW1lIC8gKFVJTlQzMl9NQVggKyAxKSk7XG4gICAgY29uc3QgbG93ZXJXb3JkQmFzZU1lZGlhRGVjb2RlVGltZSA9IE1hdGguZmxvb3IoYmFzZU1lZGlhRGVjb2RlVGltZSAlIChVSU5UMzJfTUFYICsgMSkpO1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy50cmFmLCBNUDQuYm94KE1QNC50eXBlcy50ZmhkLCBuZXcgVWludDhBcnJheShbMHgwMCxcbiAgICAvLyB2ZXJzaW9uIDBcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG4gICAgaWQgPj4gMjQsIGlkID4+IDE2ICYgMHhmZiwgaWQgPj4gOCAmIDB4ZmYsIGlkICYgMHhmZiAvLyB0cmFja19JRFxuICAgIF0pKSwgTVA0LmJveChNUDQudHlwZXMudGZkdCwgbmV3IFVpbnQ4QXJyYXkoWzB4MDEsXG4gICAgLy8gdmVyc2lvbiAxXG4gICAgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBmbGFnc1xuICAgIHVwcGVyV29yZEJhc2VNZWRpYURlY29kZVRpbWUgPj4gMjQsIHVwcGVyV29yZEJhc2VNZWRpYURlY29kZVRpbWUgPj4gMTYgJiAweGZmLCB1cHBlcldvcmRCYXNlTWVkaWFEZWNvZGVUaW1lID4+IDggJiAweGZmLCB1cHBlcldvcmRCYXNlTWVkaWFEZWNvZGVUaW1lICYgMHhmZiwgbG93ZXJXb3JkQmFzZU1lZGlhRGVjb2RlVGltZSA+PiAyNCwgbG93ZXJXb3JkQmFzZU1lZGlhRGVjb2RlVGltZSA+PiAxNiAmIDB4ZmYsIGxvd2VyV29yZEJhc2VNZWRpYURlY29kZVRpbWUgPj4gOCAmIDB4ZmYsIGxvd2VyV29yZEJhc2VNZWRpYURlY29kZVRpbWUgJiAweGZmXSkpLCBNUDQudHJ1bih0cmFjaywgc2FtcGxlRGVwZW5kZW5jeVRhYmxlLmxlbmd0aCArIDE2ICtcbiAgICAvLyB0ZmhkXG4gICAgMjAgK1xuICAgIC8vIHRmZHRcbiAgICA4ICtcbiAgICAvLyB0cmFmIGhlYWRlclxuICAgIDE2ICtcbiAgICAvLyBtZmhkXG4gICAgOCArXG4gICAgLy8gbW9vZiBoZWFkZXJcbiAgICA4KSxcbiAgICAvLyBtZGF0IGhlYWRlclxuICAgIHNhbXBsZURlcGVuZGVuY3lUYWJsZSk7XG4gIH1cblxuICAvKipcbiAgICogR2VuZXJhdGUgYSB0cmFjayBib3guXG4gICAqIEBwYXJhbSB0cmFjayBhIHRyYWNrIGRlZmluaXRpb25cbiAgICovXG4gIHN0YXRpYyB0cmFrKHRyYWNrKSB7XG4gICAgdHJhY2suZHVyYXRpb24gPSB0cmFjay5kdXJhdGlvbiB8fCAweGZmZmZmZmZmO1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy50cmFrLCBNUDQudGtoZCh0cmFjayksIE1QNC5tZGlhKHRyYWNrKSk7XG4gIH1cbiAgc3RhdGljIHRyZXgodHJhY2spIHtcbiAgICBjb25zdCBpZCA9IHRyYWNrLmlkO1xuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy50cmV4LCBuZXcgVWludDhBcnJheShbMHgwMCxcbiAgICAvLyB2ZXJzaW9uIDBcbiAgICAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGZsYWdzXG4gICAgaWQgPj4gMjQsIGlkID4+IDE2ICYgMHhmZiwgaWQgPj4gOCAmIDB4ZmYsIGlkICYgMHhmZixcbiAgICAvLyB0cmFja19JRFxuICAgIDB4MDAsIDB4MDAsIDB4MDAsIDB4MDEsXG4gICAgLy8gZGVmYXVsdF9zYW1wbGVfZGVzY3JpcHRpb25faW5kZXhcbiAgICAweDAwLCAweDAwLCAweDAwLCAweDAwLFxuICAgIC8vIGRlZmF1bHRfc2FtcGxlX2R1cmF0aW9uXG4gICAgMHgwMCwgMHgwMCwgMHgwMCwgMHgwMCxcbiAgICAvLyBkZWZhdWx0X3NhbXBsZV9zaXplXG4gICAgMHgwMCwgMHgwMSwgMHgwMCwgMHgwMSAvLyBkZWZhdWx0X3NhbXBsZV9mbGFnc1xuICAgIF0pKTtcbiAgfVxuICBzdGF0aWMgdHJ1bih0cmFjaywgb2Zmc2V0KSB7XG4gICAgY29uc3Qgc2FtcGxlcyA9IHRyYWNrLnNhbXBsZXMgfHwgW107XG4gICAgY29uc3QgbGVuID0gc2FtcGxlcy5sZW5ndGg7XG4gICAgY29uc3QgYXJyYXlsZW4gPSAxMiArIDE2ICogbGVuO1xuICAgIGNvbnN0IGFycmF5ID0gbmV3IFVpbnQ4QXJyYXkoYXJyYXlsZW4pO1xuICAgIGxldCBpO1xuICAgIGxldCBzYW1wbGU7XG4gICAgbGV0IGR1cmF0aW9uO1xuICAgIGxldCBzaXplO1xuICAgIGxldCBmbGFncztcbiAgICBsZXQgY3RzO1xuICAgIG9mZnNldCArPSA4ICsgYXJyYXlsZW47XG4gICAgYXJyYXkuc2V0KFt0cmFjay50eXBlID09PSAndmlkZW8nID8gMHgwMSA6IDB4MDAsXG4gICAgLy8gdmVyc2lvbiAxIGZvciB2aWRlbyB3aXRoIHNpZ25lZC1pbnQgc2FtcGxlX2NvbXBvc2l0aW9uX3RpbWVfb2Zmc2V0XG4gICAgMHgwMCwgMHgwZiwgMHgwMSxcbiAgICAvLyBmbGFnc1xuICAgIGxlbiA+Pj4gMjQgJiAweGZmLCBsZW4gPj4+IDE2ICYgMHhmZiwgbGVuID4+PiA4ICYgMHhmZiwgbGVuICYgMHhmZixcbiAgICAvLyBzYW1wbGVfY291bnRcbiAgICBvZmZzZXQgPj4+IDI0ICYgMHhmZiwgb2Zmc2V0ID4+PiAxNiAmIDB4ZmYsIG9mZnNldCA+Pj4gOCAmIDB4ZmYsIG9mZnNldCAmIDB4ZmYgLy8gZGF0YV9vZmZzZXRcbiAgICBdLCAwKTtcbiAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIHNhbXBsZSA9IHNhbXBsZXNbaV07XG4gICAgICBkdXJhdGlvbiA9IHNhbXBsZS5kdXJhdGlvbjtcbiAgICAgIHNpemUgPSBzYW1wbGUuc2l6ZTtcbiAgICAgIGZsYWdzID0gc2FtcGxlLmZsYWdzO1xuICAgICAgY3RzID0gc2FtcGxlLmN0cztcbiAgICAgIGFycmF5LnNldChbZHVyYXRpb24gPj4+IDI0ICYgMHhmZiwgZHVyYXRpb24gPj4+IDE2ICYgMHhmZiwgZHVyYXRpb24gPj4+IDggJiAweGZmLCBkdXJhdGlvbiAmIDB4ZmYsXG4gICAgICAvLyBzYW1wbGVfZHVyYXRpb25cbiAgICAgIHNpemUgPj4+IDI0ICYgMHhmZiwgc2l6ZSA+Pj4gMTYgJiAweGZmLCBzaXplID4+PiA4ICYgMHhmZiwgc2l6ZSAmIDB4ZmYsXG4gICAgICAvLyBzYW1wbGVfc2l6ZVxuICAgICAgZmxhZ3MuaXNMZWFkaW5nIDw8IDIgfCBmbGFncy5kZXBlbmRzT24sIGZsYWdzLmlzRGVwZW5kZWRPbiA8PCA2IHwgZmxhZ3MuaGFzUmVkdW5kYW5jeSA8PCA0IHwgZmxhZ3MucGFkZGluZ1ZhbHVlIDw8IDEgfCBmbGFncy5pc05vblN5bmMsIGZsYWdzLmRlZ3JhZFByaW8gJiAweGYwIDw8IDgsIGZsYWdzLmRlZ3JhZFByaW8gJiAweDBmLFxuICAgICAgLy8gc2FtcGxlX2ZsYWdzXG4gICAgICBjdHMgPj4+IDI0ICYgMHhmZiwgY3RzID4+PiAxNiAmIDB4ZmYsIGN0cyA+Pj4gOCAmIDB4ZmYsIGN0cyAmIDB4ZmYgLy8gc2FtcGxlX2NvbXBvc2l0aW9uX3RpbWVfb2Zmc2V0XG4gICAgICBdLCAxMiArIDE2ICogaSk7XG4gICAgfVxuICAgIHJldHVybiBNUDQuYm94KE1QNC50eXBlcy50cnVuLCBhcnJheSk7XG4gIH1cbiAgc3RhdGljIGluaXRTZWdtZW50KHRyYWNrcykge1xuICAgIGlmICghTVA0LnR5cGVzKSB7XG4gICAgICBNUDQuaW5pdCgpO1xuICAgIH1cbiAgICBjb25zdCBtb3ZpZSA9IE1QNC5tb292KHRyYWNrcyk7XG4gICAgY29uc3QgcmVzdWx0ID0gYXBwZW5kVWludDhBcnJheShNUDQuRlRZUCwgbW92aWUpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbn1cbk1QNC50eXBlcyA9IHZvaWQgMDtcbk1QNC5IRExSX1RZUEVTID0gdm9pZCAwO1xuTVA0LlNUVFMgPSB2b2lkIDA7XG5NUDQuU1RTQyA9IHZvaWQgMDtcbk1QNC5TVENPID0gdm9pZCAwO1xuTVA0LlNUU1ogPSB2b2lkIDA7XG5NUDQuVk1IRCA9IHZvaWQgMDtcbk1QNC5TTUhEID0gdm9pZCAwO1xuTVA0LlNUU0QgPSB2b2lkIDA7XG5NUDQuRlRZUCA9IHZvaWQgMDtcbk1QNC5ESU5GID0gdm9pZCAwO1xuXG5jb25zdCBNUEVHX1RTX0NMT0NLX0ZSRVFfSFogPSA5MDAwMDtcbmZ1bmN0aW9uIHRvVGltZXNjYWxlRnJvbUJhc2UoYmFzZVRpbWUsIGRlc3RTY2FsZSwgc3JjQmFzZSA9IDEsIHJvdW5kID0gZmFsc2UpIHtcbiAgY29uc3QgcmVzdWx0ID0gYmFzZVRpbWUgKiBkZXN0U2NhbGUgKiBzcmNCYXNlOyAvLyBlcXVpdmFsZW50IHRvIGAodmFsdWUgKiBzY2FsZSkgLyAoMSAvIGJhc2UpYFxuICByZXR1cm4gcm91bmQgPyBNYXRoLnJvdW5kKHJlc3VsdCkgOiByZXN1bHQ7XG59XG5mdW5jdGlvbiB0b1RpbWVzY2FsZUZyb21TY2FsZShiYXNlVGltZSwgZGVzdFNjYWxlLCBzcmNTY2FsZSA9IDEsIHJvdW5kID0gZmFsc2UpIHtcbiAgcmV0dXJuIHRvVGltZXNjYWxlRnJvbUJhc2UoYmFzZVRpbWUsIGRlc3RTY2FsZSwgMSAvIHNyY1NjYWxlLCByb3VuZCk7XG59XG5mdW5jdGlvbiB0b01zRnJvbU1wZWdUc0Nsb2NrKGJhc2VUaW1lLCByb3VuZCA9IGZhbHNlKSB7XG4gIHJldHVybiB0b1RpbWVzY2FsZUZyb21CYXNlKGJhc2VUaW1lLCAxMDAwLCAxIC8gTVBFR19UU19DTE9DS19GUkVRX0haLCByb3VuZCk7XG59XG5mdW5jdGlvbiB0b01wZWdUc0Nsb2NrRnJvbVRpbWVzY2FsZShiYXNlVGltZSwgc3JjU2NhbGUgPSAxKSB7XG4gIHJldHVybiB0b1RpbWVzY2FsZUZyb21CYXNlKGJhc2VUaW1lLCBNUEVHX1RTX0NMT0NLX0ZSRVFfSFosIDEgLyBzcmNTY2FsZSk7XG59XG5cbmNvbnN0IE1BWF9TSUxFTlRfRlJBTUVfRFVSQVRJT04gPSAxMCAqIDEwMDA7IC8vIDEwIHNlY29uZHNcbmNvbnN0IEFBQ19TQU1QTEVTX1BFUl9GUkFNRSA9IDEwMjQ7XG5jb25zdCBNUEVHX0FVRElPX1NBTVBMRV9QRVJfRlJBTUUgPSAxMTUyO1xuY29uc3QgQUMzX1NBTVBMRVNfUEVSX0ZSQU1FID0gMTUzNjtcbmxldCBjaHJvbWVWZXJzaW9uID0gbnVsbDtcbmxldCBzYWZhcmlXZWJraXRWZXJzaW9uID0gbnVsbDtcbmNsYXNzIE1QNFJlbXV4ZXIge1xuICBjb25zdHJ1Y3RvcihvYnNlcnZlciwgY29uZmlnLCB0eXBlU3VwcG9ydGVkLCB2ZW5kb3IgPSAnJykge1xuICAgIHRoaXMub2JzZXJ2ZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5jb25maWcgPSB2b2lkIDA7XG4gICAgdGhpcy50eXBlU3VwcG9ydGVkID0gdm9pZCAwO1xuICAgIHRoaXMuSVNHZW5lcmF0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLl9pbml0UFRTID0gbnVsbDtcbiAgICB0aGlzLl9pbml0RFRTID0gbnVsbDtcbiAgICB0aGlzLm5leHRBdmNEdHMgPSBudWxsO1xuICAgIHRoaXMubmV4dEF1ZGlvUHRzID0gbnVsbDtcbiAgICB0aGlzLnZpZGVvU2FtcGxlRHVyYXRpb24gPSBudWxsO1xuICAgIHRoaXMuaXNBdWRpb0NvbnRpZ3VvdXMgPSBmYWxzZTtcbiAgICB0aGlzLmlzVmlkZW9Db250aWd1b3VzID0gZmFsc2U7XG4gICAgdGhpcy52aWRlb1RyYWNrQ29uZmlnID0gdm9pZCAwO1xuICAgIHRoaXMub2JzZXJ2ZXIgPSBvYnNlcnZlcjtcbiAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgICB0aGlzLnR5cGVTdXBwb3J0ZWQgPSB0eXBlU3VwcG9ydGVkO1xuICAgIHRoaXMuSVNHZW5lcmF0ZWQgPSBmYWxzZTtcbiAgICBpZiAoY2hyb21lVmVyc2lvbiA9PT0gbnVsbCkge1xuICAgICAgY29uc3QgdXNlckFnZW50ID0gbmF2aWdhdG9yLnVzZXJBZ2VudCB8fCAnJztcbiAgICAgIGNvbnN0IHJlc3VsdCA9IHVzZXJBZ2VudC5tYXRjaCgvQ2hyb21lXFwvKFxcZCspL2kpO1xuICAgICAgY2hyb21lVmVyc2lvbiA9IHJlc3VsdCA/IHBhcnNlSW50KHJlc3VsdFsxXSkgOiAwO1xuICAgIH1cbiAgICBpZiAoc2FmYXJpV2Via2l0VmVyc2lvbiA9PT0gbnVsbCkge1xuICAgICAgY29uc3QgcmVzdWx0ID0gbmF2aWdhdG9yLnVzZXJBZ2VudC5tYXRjaCgvU2FmYXJpXFwvKFxcZCspL2kpO1xuICAgICAgc2FmYXJpV2Via2l0VmVyc2lvbiA9IHJlc3VsdCA/IHBhcnNlSW50KHJlc3VsdFsxXSkgOiAwO1xuICAgIH1cbiAgfVxuICBkZXN0cm95KCkge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmNvbmZpZyA9IHRoaXMudmlkZW9UcmFja0NvbmZpZyA9IHRoaXMuX2luaXRQVFMgPSB0aGlzLl9pbml0RFRTID0gbnVsbDtcbiAgfVxuICByZXNldFRpbWVTdGFtcChkZWZhdWx0VGltZVN0YW1wKSB7XG4gICAgbG9nZ2VyLmxvZygnW21wNC1yZW11eGVyXTogaW5pdFBUUyAmIGluaXREVFMgcmVzZXQnKTtcbiAgICB0aGlzLl9pbml0UFRTID0gdGhpcy5faW5pdERUUyA9IGRlZmF1bHRUaW1lU3RhbXA7XG4gIH1cbiAgcmVzZXROZXh0VGltZXN0YW1wKCkge1xuICAgIGxvZ2dlci5sb2coJ1ttcDQtcmVtdXhlcl06IHJlc2V0IG5leHQgdGltZXN0YW1wJyk7XG4gICAgdGhpcy5pc1ZpZGVvQ29udGlndW91cyA9IGZhbHNlO1xuICAgIHRoaXMuaXNBdWRpb0NvbnRpZ3VvdXMgPSBmYWxzZTtcbiAgfVxuICByZXNldEluaXRTZWdtZW50KCkge1xuICAgIGxvZ2dlci5sb2coJ1ttcDQtcmVtdXhlcl06IElTR2VuZXJhdGVkIGZsYWcgcmVzZXQnKTtcbiAgICB0aGlzLklTR2VuZXJhdGVkID0gZmFsc2U7XG4gICAgdGhpcy52aWRlb1RyYWNrQ29uZmlnID0gdW5kZWZpbmVkO1xuICB9XG4gIGdldFZpZGVvU3RhcnRQdHModmlkZW9TYW1wbGVzKSB7XG4gICAgbGV0IHJvbGxvdmVyRGV0ZWN0ZWQgPSBmYWxzZTtcbiAgICBjb25zdCBzdGFydFBUUyA9IHZpZGVvU2FtcGxlcy5yZWR1Y2UoKG1pblBUUywgc2FtcGxlKSA9PiB7XG4gICAgICBjb25zdCBkZWx0YSA9IHNhbXBsZS5wdHMgLSBtaW5QVFM7XG4gICAgICBpZiAoZGVsdGEgPCAtNDI5NDk2NzI5Nikge1xuICAgICAgICAvLyAyXjMyLCBzZWUgUFRTTm9ybWFsaXplIGZvciByZWFzb25pbmcsIGJ1dCB3ZSdyZSBoaXR0aW5nIGEgcm9sbG92ZXIgaGVyZSwgYW5kIHdlIGRvbid0IHdhbnQgdGhhdCB0byBpbXBhY3QgdGhlIHRpbWVPZmZzZXQgY2FsY3VsYXRpb25cbiAgICAgICAgcm9sbG92ZXJEZXRlY3RlZCA9IHRydWU7XG4gICAgICAgIHJldHVybiBub3JtYWxpemVQdHMobWluUFRTLCBzYW1wbGUucHRzKTtcbiAgICAgIH0gZWxzZSBpZiAoZGVsdGEgPiAwKSB7XG4gICAgICAgIHJldHVybiBtaW5QVFM7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gc2FtcGxlLnB0cztcbiAgICAgIH1cbiAgICB9LCB2aWRlb1NhbXBsZXNbMF0ucHRzKTtcbiAgICBpZiAocm9sbG92ZXJEZXRlY3RlZCkge1xuICAgICAgbG9nZ2VyLmRlYnVnKCdQVFMgcm9sbG92ZXIgZGV0ZWN0ZWQnKTtcbiAgICB9XG4gICAgcmV0dXJuIHN0YXJ0UFRTO1xuICB9XG4gIHJlbXV4KGF1ZGlvVHJhY2ssIHZpZGVvVHJhY2ssIGlkM1RyYWNrLCB0ZXh0VHJhY2ssIHRpbWVPZmZzZXQsIGFjY3VyYXRlVGltZU9mZnNldCwgZmx1c2gsIHBsYXlsaXN0VHlwZSkge1xuICAgIGxldCB2aWRlbztcbiAgICBsZXQgYXVkaW87XG4gICAgbGV0IGluaXRTZWdtZW50O1xuICAgIGxldCB0ZXh0O1xuICAgIGxldCBpZDM7XG4gICAgbGV0IGluZGVwZW5kZW50O1xuICAgIGxldCBhdWRpb1RpbWVPZmZzZXQgPSB0aW1lT2Zmc2V0O1xuICAgIGxldCB2aWRlb1RpbWVPZmZzZXQgPSB0aW1lT2Zmc2V0O1xuXG4gICAgLy8gSWYgd2UncmUgcmVtdXhpbmcgYXVkaW8gYW5kIHZpZGVvIHByb2dyZXNzaXZlbHksIHdhaXQgdW50aWwgd2UndmUgcmVjZWl2ZWQgZW5vdWdoIHNhbXBsZXMgZm9yIGVhY2ggdHJhY2sgYmVmb3JlIHByb2NlZWRpbmcuXG4gICAgLy8gVGhpcyBpcyBkb25lIHRvIHN5bmNocm9uaXplIHRoZSBhdWRpbyBhbmQgdmlkZW8gc3RyZWFtcy4gV2Uga25vdyBpZiB0aGUgY3VycmVudCBzZWdtZW50IHdpbGwgaGF2ZSBzYW1wbGVzIGlmIHRoZSBcInBpZFwiXG4gICAgLy8gcGFyYW1ldGVyIGlzIGdyZWF0ZXIgdGhhbiAtMS4gVGhlIHBpZCBpcyBzZXQgd2hlbiB0aGUgUE1UIGlzIHBhcnNlZCwgd2hpY2ggY29udGFpbnMgdGhlIHRyYWNrcyBsaXN0LlxuICAgIC8vIEhvd2V2ZXIsIGlmIHRoZSBpbml0U2VnbWVudCBoYXMgYWxyZWFkeSBiZWVuIGdlbmVyYXRlZCwgb3Igd2UndmUgcmVhY2hlZCB0aGUgZW5kIG9mIGEgc2VnbWVudCAoZmx1c2gpLFxuICAgIC8vIHRoZW4gd2UgY2FuIHJlbXV4IG9uZSB0cmFjayB3aXRob3V0IHdhaXRpbmcgZm9yIHRoZSBvdGhlci5cbiAgICBjb25zdCBoYXNBdWRpbyA9IGF1ZGlvVHJhY2sucGlkID4gLTE7XG4gICAgY29uc3QgaGFzVmlkZW8gPSB2aWRlb1RyYWNrLnBpZCA+IC0xO1xuICAgIGNvbnN0IGxlbmd0aCA9IHZpZGVvVHJhY2suc2FtcGxlcy5sZW5ndGg7XG4gICAgY29uc3QgZW5vdWdoQXVkaW9TYW1wbGVzID0gYXVkaW9UcmFjay5zYW1wbGVzLmxlbmd0aCA+IDA7XG4gICAgY29uc3QgZW5vdWdoVmlkZW9TYW1wbGVzID0gZmx1c2ggJiYgbGVuZ3RoID4gMCB8fCBsZW5ndGggPiAxO1xuICAgIGNvbnN0IGNhblJlbXV4QXZjID0gKCFoYXNBdWRpbyB8fCBlbm91Z2hBdWRpb1NhbXBsZXMpICYmICghaGFzVmlkZW8gfHwgZW5vdWdoVmlkZW9TYW1wbGVzKSB8fCB0aGlzLklTR2VuZXJhdGVkIHx8IGZsdXNoO1xuICAgIGlmIChjYW5SZW11eEF2Yykge1xuICAgICAgaWYgKHRoaXMuSVNHZW5lcmF0ZWQpIHtcbiAgICAgICAgdmFyIF92aWRlb1RyYWNrJHBpeGVsUmF0aSwgX2NvbmZpZyRwaXhlbFJhdGlvLCBfdmlkZW9UcmFjayRwaXhlbFJhdGkyLCBfY29uZmlnJHBpeGVsUmF0aW8yO1xuICAgICAgICBjb25zdCBjb25maWcgPSB0aGlzLnZpZGVvVHJhY2tDb25maWc7XG4gICAgICAgIGlmIChjb25maWcgJiYgKHZpZGVvVHJhY2sud2lkdGggIT09IGNvbmZpZy53aWR0aCB8fCB2aWRlb1RyYWNrLmhlaWdodCAhPT0gY29uZmlnLmhlaWdodCB8fCAoKF92aWRlb1RyYWNrJHBpeGVsUmF0aSA9IHZpZGVvVHJhY2sucGl4ZWxSYXRpbykgPT0gbnVsbCA/IHZvaWQgMCA6IF92aWRlb1RyYWNrJHBpeGVsUmF0aVswXSkgIT09ICgoX2NvbmZpZyRwaXhlbFJhdGlvID0gY29uZmlnLnBpeGVsUmF0aW8pID09IG51bGwgPyB2b2lkIDAgOiBfY29uZmlnJHBpeGVsUmF0aW9bMF0pIHx8ICgoX3ZpZGVvVHJhY2skcGl4ZWxSYXRpMiA9IHZpZGVvVHJhY2sucGl4ZWxSYXRpbykgPT0gbnVsbCA/IHZvaWQgMCA6IF92aWRlb1RyYWNrJHBpeGVsUmF0aTJbMV0pICE9PSAoKF9jb25maWckcGl4ZWxSYXRpbzIgPSBjb25maWcucGl4ZWxSYXRpbykgPT0gbnVsbCA/IHZvaWQgMCA6IF9jb25maWckcGl4ZWxSYXRpbzJbMV0pKSkge1xuICAgICAgICAgIHRoaXMucmVzZXRJbml0U2VnbWVudCgpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpbml0U2VnbWVudCA9IHRoaXMuZ2VuZXJhdGVJUyhhdWRpb1RyYWNrLCB2aWRlb1RyYWNrLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQpO1xuICAgICAgfVxuICAgICAgY29uc3QgaXNWaWRlb0NvbnRpZ3VvdXMgPSB0aGlzLmlzVmlkZW9Db250aWd1b3VzO1xuICAgICAgbGV0IGZpcnN0S2V5RnJhbWVJbmRleCA9IC0xO1xuICAgICAgbGV0IGZpcnN0S2V5RnJhbWVQVFM7XG4gICAgICBpZiAoZW5vdWdoVmlkZW9TYW1wbGVzKSB7XG4gICAgICAgIGZpcnN0S2V5RnJhbWVJbmRleCA9IGZpbmRLZXlmcmFtZUluZGV4KHZpZGVvVHJhY2suc2FtcGxlcyk7XG4gICAgICAgIGlmICghaXNWaWRlb0NvbnRpZ3VvdXMgJiYgdGhpcy5jb25maWcuZm9yY2VLZXlGcmFtZU9uRGlzY29udGludWl0eSkge1xuICAgICAgICAgIGluZGVwZW5kZW50ID0gdHJ1ZTtcbiAgICAgICAgICBpZiAoZmlyc3RLZXlGcmFtZUluZGV4ID4gMCkge1xuICAgICAgICAgICAgbG9nZ2VyLndhcm4oYFttcDQtcmVtdXhlcl06IERyb3BwZWQgJHtmaXJzdEtleUZyYW1lSW5kZXh9IG91dCBvZiAke2xlbmd0aH0gdmlkZW8gc2FtcGxlcyBkdWUgdG8gYSBtaXNzaW5nIGtleWZyYW1lYCk7XG4gICAgICAgICAgICBjb25zdCBzdGFydFBUUyA9IHRoaXMuZ2V0VmlkZW9TdGFydFB0cyh2aWRlb1RyYWNrLnNhbXBsZXMpO1xuICAgICAgICAgICAgdmlkZW9UcmFjay5zYW1wbGVzID0gdmlkZW9UcmFjay5zYW1wbGVzLnNsaWNlKGZpcnN0S2V5RnJhbWVJbmRleCk7XG4gICAgICAgICAgICB2aWRlb1RyYWNrLmRyb3BwZWQgKz0gZmlyc3RLZXlGcmFtZUluZGV4O1xuICAgICAgICAgICAgdmlkZW9UaW1lT2Zmc2V0ICs9ICh2aWRlb1RyYWNrLnNhbXBsZXNbMF0ucHRzIC0gc3RhcnRQVFMpIC8gdmlkZW9UcmFjay5pbnB1dFRpbWVTY2FsZTtcbiAgICAgICAgICAgIGZpcnN0S2V5RnJhbWVQVFMgPSB2aWRlb1RpbWVPZmZzZXQ7XG4gICAgICAgICAgfSBlbHNlIGlmIChmaXJzdEtleUZyYW1lSW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihgW21wNC1yZW11eGVyXTogTm8ga2V5ZnJhbWUgZm91bmQgb3V0IG9mICR7bGVuZ3RofSB2aWRlbyBzYW1wbGVzYCk7XG4gICAgICAgICAgICBpbmRlcGVuZGVudCA9IGZhbHNlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKHRoaXMuSVNHZW5lcmF0ZWQpIHtcbiAgICAgICAgaWYgKGVub3VnaEF1ZGlvU2FtcGxlcyAmJiBlbm91Z2hWaWRlb1NhbXBsZXMpIHtcbiAgICAgICAgICAvLyB0aW1lT2Zmc2V0IGlzIGV4cGVjdGVkIHRvIGJlIHRoZSBvZmZzZXQgb2YgdGhlIGZpcnN0IHRpbWVzdGFtcCBvZiB0aGlzIGZyYWdtZW50IChmaXJzdCBEVFMpXG4gICAgICAgICAgLy8gaWYgZmlyc3QgYXVkaW8gRFRTIGlzIG5vdCBhbGlnbmVkIHdpdGggZmlyc3QgdmlkZW8gRFRTIHRoZW4gd2UgbmVlZCB0byB0YWtlIHRoYXQgaW50byBhY2NvdW50XG4gICAgICAgICAgLy8gd2hlbiBwcm92aWRpbmcgdGltZU9mZnNldCB0byByZW11eEF1ZGlvIC8gcmVtdXhWaWRlby4gaWYgd2UgZG9uJ3QgZG8gdGhhdCwgdGhlcmUgbWlnaHQgYmUgYSBwZXJtYW5lbnQgLyBzbWFsbFxuICAgICAgICAgIC8vIGRyaWZ0IGJldHdlZW4gYXVkaW8gYW5kIHZpZGVvIHN0cmVhbXNcbiAgICAgICAgICBjb25zdCBzdGFydFBUUyA9IHRoaXMuZ2V0VmlkZW9TdGFydFB0cyh2aWRlb1RyYWNrLnNhbXBsZXMpO1xuICAgICAgICAgIGNvbnN0IHRzRGVsdGEgPSBub3JtYWxpemVQdHMoYXVkaW9UcmFjay5zYW1wbGVzWzBdLnB0cywgc3RhcnRQVFMpIC0gc3RhcnRQVFM7XG4gICAgICAgICAgY29uc3QgYXVkaW92aWRlb1RpbWVzdGFtcERlbHRhID0gdHNEZWx0YSAvIHZpZGVvVHJhY2suaW5wdXRUaW1lU2NhbGU7XG4gICAgICAgICAgYXVkaW9UaW1lT2Zmc2V0ICs9IE1hdGgubWF4KDAsIGF1ZGlvdmlkZW9UaW1lc3RhbXBEZWx0YSk7XG4gICAgICAgICAgdmlkZW9UaW1lT2Zmc2V0ICs9IE1hdGgubWF4KDAsIC1hdWRpb3ZpZGVvVGltZXN0YW1wRGVsdGEpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gUHVycG9zZWZ1bGx5IHJlbXV4aW5nIGF1ZGlvIGJlZm9yZSB2aWRlbywgc28gdGhhdCByZW11eFZpZGVvIGNhbiB1c2UgbmV4dEF1ZGlvUHRzLCB3aGljaCBpcyBjYWxjdWxhdGVkIGluIHJlbXV4QXVkaW8uXG4gICAgICAgIGlmIChlbm91Z2hBdWRpb1NhbXBsZXMpIHtcbiAgICAgICAgICAvLyBpZiBpbml0U2VnbWVudCB3YXMgZ2VuZXJhdGVkIHdpdGhvdXQgYXVkaW8gc2FtcGxlcywgcmVnZW5lcmF0ZSBpdCBhZ2FpblxuICAgICAgICAgIGlmICghYXVkaW9UcmFjay5zYW1wbGVyYXRlKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybignW21wNC1yZW11eGVyXTogcmVnZW5lcmF0ZSBJbml0U2VnbWVudCBhcyBhdWRpbyBkZXRlY3RlZCcpO1xuICAgICAgICAgICAgaW5pdFNlZ21lbnQgPSB0aGlzLmdlbmVyYXRlSVMoYXVkaW9UcmFjaywgdmlkZW9UcmFjaywgdGltZU9mZnNldCwgYWNjdXJhdGVUaW1lT2Zmc2V0KTtcbiAgICAgICAgICB9XG4gICAgICAgICAgYXVkaW8gPSB0aGlzLnJlbXV4QXVkaW8oYXVkaW9UcmFjaywgYXVkaW9UaW1lT2Zmc2V0LCB0aGlzLmlzQXVkaW9Db250aWd1b3VzLCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGhhc1ZpZGVvIHx8IGVub3VnaFZpZGVvU2FtcGxlcyB8fCBwbGF5bGlzdFR5cGUgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLkFVRElPID8gdmlkZW9UaW1lT2Zmc2V0IDogdW5kZWZpbmVkKTtcbiAgICAgICAgICBpZiAoZW5vdWdoVmlkZW9TYW1wbGVzKSB7XG4gICAgICAgICAgICBjb25zdCBhdWRpb1RyYWNrTGVuZ3RoID0gYXVkaW8gPyBhdWRpby5lbmRQVFMgLSBhdWRpby5zdGFydFBUUyA6IDA7XG4gICAgICAgICAgICAvLyBpZiBpbml0U2VnbWVudCB3YXMgZ2VuZXJhdGVkIHdpdGhvdXQgdmlkZW8gc2FtcGxlcywgcmVnZW5lcmF0ZSBpdCBhZ2FpblxuICAgICAgICAgICAgaWYgKCF2aWRlb1RyYWNrLmlucHV0VGltZVNjYWxlKSB7XG4gICAgICAgICAgICAgIGxvZ2dlci53YXJuKCdbbXA0LXJlbXV4ZXJdOiByZWdlbmVyYXRlIEluaXRTZWdtZW50IGFzIHZpZGVvIGRldGVjdGVkJyk7XG4gICAgICAgICAgICAgIGluaXRTZWdtZW50ID0gdGhpcy5nZW5lcmF0ZUlTKGF1ZGlvVHJhY2ssIHZpZGVvVHJhY2ssIHRpbWVPZmZzZXQsIGFjY3VyYXRlVGltZU9mZnNldCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2aWRlbyA9IHRoaXMucmVtdXhWaWRlbyh2aWRlb1RyYWNrLCB2aWRlb1RpbWVPZmZzZXQsIGlzVmlkZW9Db250aWd1b3VzLCBhdWRpb1RyYWNrTGVuZ3RoKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSBpZiAoZW5vdWdoVmlkZW9TYW1wbGVzKSB7XG4gICAgICAgICAgdmlkZW8gPSB0aGlzLnJlbXV4VmlkZW8odmlkZW9UcmFjaywgdmlkZW9UaW1lT2Zmc2V0LCBpc1ZpZGVvQ29udGlndW91cywgMCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHZpZGVvKSB7XG4gICAgICAgICAgdmlkZW8uZmlyc3RLZXlGcmFtZSA9IGZpcnN0S2V5RnJhbWVJbmRleDtcbiAgICAgICAgICB2aWRlby5pbmRlcGVuZGVudCA9IGZpcnN0S2V5RnJhbWVJbmRleCAhPT0gLTE7XG4gICAgICAgICAgdmlkZW8uZmlyc3RLZXlGcmFtZVBUUyA9IGZpcnN0S2V5RnJhbWVQVFM7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBBbGxvdyBJRDMgYW5kIHRleHQgdG8gcmVtdXgsIGV2ZW4gaWYgbW9yZSBhdWRpby92aWRlbyBzYW1wbGVzIGFyZSByZXF1aXJlZFxuICAgIGlmICh0aGlzLklTR2VuZXJhdGVkICYmIHRoaXMuX2luaXRQVFMgJiYgdGhpcy5faW5pdERUUykge1xuICAgICAgaWYgKGlkM1RyYWNrLnNhbXBsZXMubGVuZ3RoKSB7XG4gICAgICAgIGlkMyA9IGZsdXNoVGV4dFRyYWNrTWV0YWRhdGFDdWVTYW1wbGVzKGlkM1RyYWNrLCB0aW1lT2Zmc2V0LCB0aGlzLl9pbml0UFRTLCB0aGlzLl9pbml0RFRTKTtcbiAgICAgIH1cbiAgICAgIGlmICh0ZXh0VHJhY2suc2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgICAgdGV4dCA9IGZsdXNoVGV4dFRyYWNrVXNlcmRhdGFDdWVTYW1wbGVzKHRleHRUcmFjaywgdGltZU9mZnNldCwgdGhpcy5faW5pdFBUUyk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBhdWRpbyxcbiAgICAgIHZpZGVvLFxuICAgICAgaW5pdFNlZ21lbnQsXG4gICAgICBpbmRlcGVuZGVudCxcbiAgICAgIHRleHQsXG4gICAgICBpZDNcbiAgICB9O1xuICB9XG4gIGdlbmVyYXRlSVMoYXVkaW9UcmFjaywgdmlkZW9UcmFjaywgdGltZU9mZnNldCwgYWNjdXJhdGVUaW1lT2Zmc2V0KSB7XG4gICAgY29uc3QgYXVkaW9TYW1wbGVzID0gYXVkaW9UcmFjay5zYW1wbGVzO1xuICAgIGNvbnN0IHZpZGVvU2FtcGxlcyA9IHZpZGVvVHJhY2suc2FtcGxlcztcbiAgICBjb25zdCB0eXBlU3VwcG9ydGVkID0gdGhpcy50eXBlU3VwcG9ydGVkO1xuICAgIGNvbnN0IHRyYWNrcyA9IHt9O1xuICAgIGNvbnN0IF9pbml0UFRTID0gdGhpcy5faW5pdFBUUztcbiAgICBsZXQgY29tcHV0ZVBUU0RUUyA9ICFfaW5pdFBUUyB8fCBhY2N1cmF0ZVRpbWVPZmZzZXQ7XG4gICAgbGV0IGNvbnRhaW5lciA9ICdhdWRpby9tcDQnO1xuICAgIGxldCBpbml0UFRTO1xuICAgIGxldCBpbml0RFRTO1xuICAgIGxldCB0aW1lc2NhbGU7XG4gICAgaWYgKGNvbXB1dGVQVFNEVFMpIHtcbiAgICAgIGluaXRQVFMgPSBpbml0RFRTID0gSW5maW5pdHk7XG4gICAgfVxuICAgIGlmIChhdWRpb1RyYWNrLmNvbmZpZyAmJiBhdWRpb1NhbXBsZXMubGVuZ3RoKSB7XG4gICAgICAvLyBsZXQncyB1c2UgYXVkaW8gc2FtcGxpbmcgcmF0ZSBhcyBNUDQgdGltZSBzY2FsZS5cbiAgICAgIC8vIHJhdGlvbmFsZSBpcyB0aGF0IHRoZXJlIGlzIGEgaW50ZWdlciBuYiBvZiBhdWRpbyBmcmFtZXMgcGVyIGF1ZGlvIHNhbXBsZSAoMTAyNCBmb3IgQUFDKVxuICAgICAgLy8gdXNpbmcgYXVkaW8gc2FtcGxpbmcgcmF0ZSBoZXJlIGhlbHBzIGhhdmluZyBhbiBpbnRlZ2VyIE1QNCBmcmFtZSBkdXJhdGlvblxuICAgICAgLy8gdGhpcyBhdm9pZHMgcG90ZW50aWFsIHJvdW5kaW5nIGlzc3VlIGFuZCBBViBzeW5jIGlzc3VlXG4gICAgICBhdWRpb1RyYWNrLnRpbWVzY2FsZSA9IGF1ZGlvVHJhY2suc2FtcGxlcmF0ZTtcbiAgICAgIHN3aXRjaCAoYXVkaW9UcmFjay5zZWdtZW50Q29kZWMpIHtcbiAgICAgICAgY2FzZSAnbXAzJzpcbiAgICAgICAgICBpZiAodHlwZVN1cHBvcnRlZC5tcGVnKSB7XG4gICAgICAgICAgICAvLyBDaHJvbWUgYW5kIFNhZmFyaVxuICAgICAgICAgICAgY29udGFpbmVyID0gJ2F1ZGlvL21wZWcnO1xuICAgICAgICAgICAgYXVkaW9UcmFjay5jb2RlYyA9ICcnO1xuICAgICAgICAgIH0gZWxzZSBpZiAodHlwZVN1cHBvcnRlZC5tcDMpIHtcbiAgICAgICAgICAgIC8vIEZpcmVmb3hcbiAgICAgICAgICAgIGF1ZGlvVHJhY2suY29kZWMgPSAnbXAzJztcbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ2FjMyc6XG4gICAgICAgICAgYXVkaW9UcmFjay5jb2RlYyA9ICdhYy0zJztcbiAgICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIHRyYWNrcy5hdWRpbyA9IHtcbiAgICAgICAgaWQ6ICdhdWRpbycsXG4gICAgICAgIGNvbnRhaW5lcjogY29udGFpbmVyLFxuICAgICAgICBjb2RlYzogYXVkaW9UcmFjay5jb2RlYyxcbiAgICAgICAgaW5pdFNlZ21lbnQ6IGF1ZGlvVHJhY2suc2VnbWVudENvZGVjID09PSAnbXAzJyAmJiB0eXBlU3VwcG9ydGVkLm1wZWcgPyBuZXcgVWludDhBcnJheSgwKSA6IE1QNC5pbml0U2VnbWVudChbYXVkaW9UcmFja10pLFxuICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgIGNoYW5uZWxDb3VudDogYXVkaW9UcmFjay5jaGFubmVsQ291bnRcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICAgIGlmIChjb21wdXRlUFRTRFRTKSB7XG4gICAgICAgIHRpbWVzY2FsZSA9IGF1ZGlvVHJhY2suaW5wdXRUaW1lU2NhbGU7XG4gICAgICAgIGlmICghX2luaXRQVFMgfHwgdGltZXNjYWxlICE9PSBfaW5pdFBUUy50aW1lc2NhbGUpIHtcbiAgICAgICAgICAvLyByZW1lbWJlciBmaXJzdCBQVFMgb2YgdGhpcyBkZW11eGluZyBjb250ZXh0LiBmb3IgYXVkaW8sIFBUUyA9IERUU1xuICAgICAgICAgIGluaXRQVFMgPSBpbml0RFRTID0gYXVkaW9TYW1wbGVzWzBdLnB0cyAtIE1hdGgucm91bmQodGltZXNjYWxlICogdGltZU9mZnNldCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY29tcHV0ZVBUU0RUUyA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIGlmICh2aWRlb1RyYWNrLnNwcyAmJiB2aWRlb1RyYWNrLnBwcyAmJiB2aWRlb1NhbXBsZXMubGVuZ3RoKSB7XG4gICAgICAvLyBsZXQncyB1c2UgaW5wdXQgdGltZSBzY2FsZSBhcyBNUDQgdmlkZW8gdGltZXNjYWxlXG4gICAgICAvLyB3ZSB1c2UgaW5wdXQgdGltZSBzY2FsZSBzdHJhaWdodCBhd2F5IHRvIGF2b2lkIHJvdW5kaW5nIGlzc3VlcyBvbiBmcmFtZSBkdXJhdGlvbiAvIGN0cyBjb21wdXRhdGlvblxuICAgICAgdmlkZW9UcmFjay50aW1lc2NhbGUgPSB2aWRlb1RyYWNrLmlucHV0VGltZVNjYWxlO1xuICAgICAgdHJhY2tzLnZpZGVvID0ge1xuICAgICAgICBpZDogJ21haW4nLFxuICAgICAgICBjb250YWluZXI6ICd2aWRlby9tcDQnLFxuICAgICAgICBjb2RlYzogdmlkZW9UcmFjay5jb2RlYyxcbiAgICAgICAgaW5pdFNlZ21lbnQ6IE1QNC5pbml0U2VnbWVudChbdmlkZW9UcmFja10pLFxuICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgIHdpZHRoOiB2aWRlb1RyYWNrLndpZHRoLFxuICAgICAgICAgIGhlaWdodDogdmlkZW9UcmFjay5oZWlnaHRcbiAgICAgICAgfVxuICAgICAgfTtcbiAgICAgIGlmIChjb21wdXRlUFRTRFRTKSB7XG4gICAgICAgIHRpbWVzY2FsZSA9IHZpZGVvVHJhY2suaW5wdXRUaW1lU2NhbGU7XG4gICAgICAgIGlmICghX2luaXRQVFMgfHwgdGltZXNjYWxlICE9PSBfaW5pdFBUUy50aW1lc2NhbGUpIHtcbiAgICAgICAgICBjb25zdCBzdGFydFBUUyA9IHRoaXMuZ2V0VmlkZW9TdGFydFB0cyh2aWRlb1NhbXBsZXMpO1xuICAgICAgICAgIGNvbnN0IHN0YXJ0T2Zmc2V0ID0gTWF0aC5yb3VuZCh0aW1lc2NhbGUgKiB0aW1lT2Zmc2V0KTtcbiAgICAgICAgICBpbml0RFRTID0gTWF0aC5taW4oaW5pdERUUywgbm9ybWFsaXplUHRzKHZpZGVvU2FtcGxlc1swXS5kdHMsIHN0YXJ0UFRTKSAtIHN0YXJ0T2Zmc2V0KTtcbiAgICAgICAgICBpbml0UFRTID0gTWF0aC5taW4oaW5pdFBUUywgc3RhcnRQVFMgLSBzdGFydE9mZnNldCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY29tcHV0ZVBUU0RUUyA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLnZpZGVvVHJhY2tDb25maWcgPSB7XG4gICAgICAgIHdpZHRoOiB2aWRlb1RyYWNrLndpZHRoLFxuICAgICAgICBoZWlnaHQ6IHZpZGVvVHJhY2suaGVpZ2h0LFxuICAgICAgICBwaXhlbFJhdGlvOiB2aWRlb1RyYWNrLnBpeGVsUmF0aW9cbiAgICAgIH07XG4gICAgfVxuICAgIGlmIChPYmplY3Qua2V5cyh0cmFja3MpLmxlbmd0aCkge1xuICAgICAgdGhpcy5JU0dlbmVyYXRlZCA9IHRydWU7XG4gICAgICBpZiAoY29tcHV0ZVBUU0RUUykge1xuICAgICAgICB0aGlzLl9pbml0UFRTID0ge1xuICAgICAgICAgIGJhc2VUaW1lOiBpbml0UFRTLFxuICAgICAgICAgIHRpbWVzY2FsZTogdGltZXNjYWxlXG4gICAgICAgIH07XG4gICAgICAgIHRoaXMuX2luaXREVFMgPSB7XG4gICAgICAgICAgYmFzZVRpbWU6IGluaXREVFMsXG4gICAgICAgICAgdGltZXNjYWxlOiB0aW1lc2NhbGVcbiAgICAgICAgfTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGluaXRQVFMgPSB0aW1lc2NhbGUgPSB1bmRlZmluZWQ7XG4gICAgICB9XG4gICAgICByZXR1cm4ge1xuICAgICAgICB0cmFja3MsXG4gICAgICAgIGluaXRQVFMsXG4gICAgICAgIHRpbWVzY2FsZVxuICAgICAgfTtcbiAgICB9XG4gIH1cbiAgcmVtdXhWaWRlbyh0cmFjaywgdGltZU9mZnNldCwgY29udGlndW91cywgYXVkaW9UcmFja0xlbmd0aCkge1xuICAgIGNvbnN0IHRpbWVTY2FsZSA9IHRyYWNrLmlucHV0VGltZVNjYWxlO1xuICAgIGNvbnN0IGlucHV0U2FtcGxlcyA9IHRyYWNrLnNhbXBsZXM7XG4gICAgY29uc3Qgb3V0cHV0U2FtcGxlcyA9IFtdO1xuICAgIGNvbnN0IG5iU2FtcGxlcyA9IGlucHV0U2FtcGxlcy5sZW5ndGg7XG4gICAgY29uc3QgaW5pdFBUUyA9IHRoaXMuX2luaXRQVFM7XG4gICAgbGV0IG5leHRBdmNEdHMgPSB0aGlzLm5leHRBdmNEdHM7XG4gICAgbGV0IG9mZnNldCA9IDg7XG4gICAgbGV0IG1wNFNhbXBsZUR1cmF0aW9uID0gdGhpcy52aWRlb1NhbXBsZUR1cmF0aW9uO1xuICAgIGxldCBmaXJzdERUUztcbiAgICBsZXQgbGFzdERUUztcbiAgICBsZXQgbWluUFRTID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuICAgIGxldCBtYXhQVFMgPSBOdW1iZXIuTkVHQVRJVkVfSU5GSU5JVFk7XG4gICAgbGV0IHNvcnRTYW1wbGVzID0gZmFsc2U7XG5cbiAgICAvLyBpZiBwYXJzZWQgZnJhZ21lbnQgaXMgY29udGlndW91cyB3aXRoIGxhc3Qgb25lLCBsZXQncyB1c2UgbGFzdCBEVFMgdmFsdWUgYXMgcmVmZXJlbmNlXG4gICAgaWYgKCFjb250aWd1b3VzIHx8IG5leHRBdmNEdHMgPT09IG51bGwpIHtcbiAgICAgIGNvbnN0IHB0cyA9IHRpbWVPZmZzZXQgKiB0aW1lU2NhbGU7XG4gICAgICBjb25zdCBjdHMgPSBpbnB1dFNhbXBsZXNbMF0ucHRzIC0gbm9ybWFsaXplUHRzKGlucHV0U2FtcGxlc1swXS5kdHMsIGlucHV0U2FtcGxlc1swXS5wdHMpO1xuICAgICAgaWYgKGNocm9tZVZlcnNpb24gJiYgbmV4dEF2Y0R0cyAhPT0gbnVsbCAmJiBNYXRoLmFicyhwdHMgLSBjdHMgLSBuZXh0QXZjRHRzKSA8IDE1MDAwKSB7XG4gICAgICAgIC8vIHRyZWF0IGFzIGNvbnRpZ291cyB0byBhZGp1c3Qgc2FtcGxlcyB0aGF0IHdvdWxkIG90aGVyd2lzZSBwcm9kdWNlIHZpZGVvIGJ1ZmZlciBnYXBzIGluIENocm9tZVxuICAgICAgICBjb250aWd1b3VzID0gdHJ1ZTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIGlmIG5vdCBjb250aWd1b3VzLCBsZXQncyB1c2UgdGFyZ2V0IHRpbWVPZmZzZXRcbiAgICAgICAgbmV4dEF2Y0R0cyA9IHB0cyAtIGN0cztcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBQVFMgaXMgY29kZWQgb24gMzNiaXRzLCBhbmQgY2FuIGxvb3AgZnJvbSAtMl4zMiB0byAyXjMyXG4gICAgLy8gUFRTTm9ybWFsaXplIHdpbGwgbWFrZSBQVFMvRFRTIHZhbHVlIG1vbm90b25pYywgd2UgdXNlIGxhc3Qga25vd24gRFRTIHZhbHVlIGFzIHJlZmVyZW5jZSB2YWx1ZVxuICAgIGNvbnN0IGluaXRUaW1lID0gaW5pdFBUUy5iYXNlVGltZSAqIHRpbWVTY2FsZSAvIGluaXRQVFMudGltZXNjYWxlO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbmJTYW1wbGVzOyBpKyspIHtcbiAgICAgIGNvbnN0IHNhbXBsZSA9IGlucHV0U2FtcGxlc1tpXTtcbiAgICAgIHNhbXBsZS5wdHMgPSBub3JtYWxpemVQdHMoc2FtcGxlLnB0cyAtIGluaXRUaW1lLCBuZXh0QXZjRHRzKTtcbiAgICAgIHNhbXBsZS5kdHMgPSBub3JtYWxpemVQdHMoc2FtcGxlLmR0cyAtIGluaXRUaW1lLCBuZXh0QXZjRHRzKTtcbiAgICAgIGlmIChzYW1wbGUuZHRzIDwgaW5wdXRTYW1wbGVzW2kgPiAwID8gaSAtIDEgOiBpXS5kdHMpIHtcbiAgICAgICAgc29ydFNhbXBsZXMgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHNvcnQgdmlkZW8gc2FtcGxlcyBieSBEVFMgdGhlbiBQVFMgdGhlbiBkZW11eCBpZCBvcmRlclxuICAgIGlmIChzb3J0U2FtcGxlcykge1xuICAgICAgaW5wdXRTYW1wbGVzLnNvcnQoZnVuY3Rpb24gKGEsIGIpIHtcbiAgICAgICAgY29uc3QgZGVsdGFkdHMgPSBhLmR0cyAtIGIuZHRzO1xuICAgICAgICBjb25zdCBkZWx0YXB0cyA9IGEucHRzIC0gYi5wdHM7XG4gICAgICAgIHJldHVybiBkZWx0YWR0cyB8fCBkZWx0YXB0cztcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8vIEdldCBmaXJzdC9sYXN0IERUU1xuICAgIGZpcnN0RFRTID0gaW5wdXRTYW1wbGVzWzBdLmR0cztcbiAgICBsYXN0RFRTID0gaW5wdXRTYW1wbGVzW2lucHV0U2FtcGxlcy5sZW5ndGggLSAxXS5kdHM7XG5cbiAgICAvLyBTYW1wbGUgZHVyYXRpb24gKGFzIGV4cGVjdGVkIGJ5IHRydW4gTVA0IGJveGVzKSwgc2hvdWxkIGJlIHRoZSBkZWx0YSBiZXR3ZWVuIHNhbXBsZSBEVFNcbiAgICAvLyBzZXQgdGhpcyBjb25zdGFudCBkdXJhdGlvbiBhcyBiZWluZyB0aGUgYXZnIGRlbHRhIGJldHdlZW4gY29uc2VjdXRpdmUgRFRTLlxuICAgIGNvbnN0IGlucHV0RHVyYXRpb24gPSBsYXN0RFRTIC0gZmlyc3REVFM7XG4gICAgY29uc3QgYXZlcmFnZVNhbXBsZUR1cmF0aW9uID0gaW5wdXREdXJhdGlvbiA/IE1hdGgucm91bmQoaW5wdXREdXJhdGlvbiAvIChuYlNhbXBsZXMgLSAxKSkgOiBtcDRTYW1wbGVEdXJhdGlvbiB8fCB0cmFjay5pbnB1dFRpbWVTY2FsZSAvIDMwO1xuXG4gICAgLy8gaWYgZnJhZ21lbnQgYXJlIGNvbnRpZ3VvdXMsIGRldGVjdCBob2xlL292ZXJsYXBwaW5nIGJldHdlZW4gZnJhZ21lbnRzXG4gICAgaWYgKGNvbnRpZ3VvdXMpIHtcbiAgICAgIC8vIGNoZWNrIHRpbWVzdGFtcCBjb250aW51aXR5IGFjcm9zcyBjb25zZWN1dGl2ZSBmcmFnbWVudHMgKHRoaXMgaXMgdG8gcmVtb3ZlIGludGVyLWZyYWdtZW50IGdhcC9ob2xlKVxuICAgICAgY29uc3QgZGVsdGEgPSBmaXJzdERUUyAtIG5leHRBdmNEdHM7XG4gICAgICBjb25zdCBmb3VuZEhvbGUgPSBkZWx0YSA+IGF2ZXJhZ2VTYW1wbGVEdXJhdGlvbjtcbiAgICAgIGNvbnN0IGZvdW5kT3ZlcmxhcCA9IGRlbHRhIDwgLTE7XG4gICAgICBpZiAoZm91bmRIb2xlIHx8IGZvdW5kT3ZlcmxhcCkge1xuICAgICAgICBpZiAoZm91bmRIb2xlKSB7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oYEFWQzogJHt0b01zRnJvbU1wZWdUc0Nsb2NrKGRlbHRhLCB0cnVlKX0gbXMgKCR7ZGVsdGF9ZHRzKSBob2xlIGJldHdlZW4gZnJhZ21lbnRzIGRldGVjdGVkIGF0ICR7dGltZU9mZnNldC50b0ZpeGVkKDMpfWApO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGxvZ2dlci53YXJuKGBBVkM6ICR7dG9Nc0Zyb21NcGVnVHNDbG9jaygtZGVsdGEsIHRydWUpfSBtcyAoJHtkZWx0YX1kdHMpIG92ZXJsYXBwaW5nIGJldHdlZW4gZnJhZ21lbnRzIGRldGVjdGVkIGF0ICR7dGltZU9mZnNldC50b0ZpeGVkKDMpfWApO1xuICAgICAgICB9XG4gICAgICAgIGlmICghZm91bmRPdmVybGFwIHx8IG5leHRBdmNEdHMgPj0gaW5wdXRTYW1wbGVzWzBdLnB0cyB8fCBjaHJvbWVWZXJzaW9uKSB7XG4gICAgICAgICAgZmlyc3REVFMgPSBuZXh0QXZjRHRzO1xuICAgICAgICAgIGNvbnN0IGZpcnN0UFRTID0gaW5wdXRTYW1wbGVzWzBdLnB0cyAtIGRlbHRhO1xuICAgICAgICAgIGlmIChmb3VuZEhvbGUpIHtcbiAgICAgICAgICAgIGlucHV0U2FtcGxlc1swXS5kdHMgPSBmaXJzdERUUztcbiAgICAgICAgICAgIGlucHV0U2FtcGxlc1swXS5wdHMgPSBmaXJzdFBUUztcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBpbnB1dFNhbXBsZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgaWYgKGlucHV0U2FtcGxlc1tpXS5kdHMgPiBmaXJzdFBUUykge1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlucHV0U2FtcGxlc1tpXS5kdHMgLT0gZGVsdGE7XG4gICAgICAgICAgICAgIGlucHV0U2FtcGxlc1tpXS5wdHMgLT0gZGVsdGE7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGxvZ2dlci5sb2coYFZpZGVvOiBJbml0aWFsIFBUUy9EVFMgYWRqdXN0ZWQ6ICR7dG9Nc0Zyb21NcGVnVHNDbG9jayhmaXJzdFBUUywgdHJ1ZSl9LyR7dG9Nc0Zyb21NcGVnVHNDbG9jayhmaXJzdERUUywgdHJ1ZSl9LCBkZWx0YTogJHt0b01zRnJvbU1wZWdUc0Nsb2NrKGRlbHRhLCB0cnVlKX0gbXNgKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBmaXJzdERUUyA9IE1hdGgubWF4KDAsIGZpcnN0RFRTKTtcbiAgICBsZXQgbmJOYWx1ID0gMDtcbiAgICBsZXQgbmFsdUxlbiA9IDA7XG4gICAgbGV0IGR0c1N0ZXAgPSBmaXJzdERUUztcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IG5iU2FtcGxlczsgaSsrKSB7XG4gICAgICAvLyBjb21wdXRlIHRvdGFsL2F2YyBzYW1wbGUgbGVuZ3RoIGFuZCBuYiBvZiBOQUwgdW5pdHNcbiAgICAgIGNvbnN0IHNhbXBsZSA9IGlucHV0U2FtcGxlc1tpXTtcbiAgICAgIGNvbnN0IHVuaXRzID0gc2FtcGxlLnVuaXRzO1xuICAgICAgY29uc3QgbmJVbml0cyA9IHVuaXRzLmxlbmd0aDtcbiAgICAgIGxldCBzYW1wbGVMZW4gPSAwO1xuICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBuYlVuaXRzOyBqKyspIHtcbiAgICAgICAgc2FtcGxlTGVuICs9IHVuaXRzW2pdLmRhdGEubGVuZ3RoO1xuICAgICAgfVxuICAgICAgbmFsdUxlbiArPSBzYW1wbGVMZW47XG4gICAgICBuYk5hbHUgKz0gbmJVbml0cztcbiAgICAgIHNhbXBsZS5sZW5ndGggPSBzYW1wbGVMZW47XG5cbiAgICAgIC8vIGVuc3VyZSBzYW1wbGUgbW9ub3RvbmljIERUU1xuICAgICAgaWYgKHNhbXBsZS5kdHMgPCBkdHNTdGVwKSB7XG4gICAgICAgIHNhbXBsZS5kdHMgPSBkdHNTdGVwO1xuICAgICAgICBkdHNTdGVwICs9IGF2ZXJhZ2VTYW1wbGVEdXJhdGlvbiAvIDQgfCAwIHx8IDE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBkdHNTdGVwID0gc2FtcGxlLmR0cztcbiAgICAgIH1cbiAgICAgIG1pblBUUyA9IE1hdGgubWluKHNhbXBsZS5wdHMsIG1pblBUUyk7XG4gICAgICBtYXhQVFMgPSBNYXRoLm1heChzYW1wbGUucHRzLCBtYXhQVFMpO1xuICAgIH1cbiAgICBsYXN0RFRTID0gaW5wdXRTYW1wbGVzW25iU2FtcGxlcyAtIDFdLmR0cztcblxuICAgIC8qIGNvbmNhdGVuYXRlIHRoZSB2aWRlbyBkYXRhIGFuZCBjb25zdHJ1Y3QgdGhlIG1kYXQgaW4gcGxhY2VcbiAgICAgIChuZWVkIDggbW9yZSBieXRlcyB0byBmaWxsIGxlbmd0aCBhbmQgbXBkYXQgdHlwZSkgKi9cbiAgICBjb25zdCBtZGF0U2l6ZSA9IG5hbHVMZW4gKyA0ICogbmJOYWx1ICsgODtcbiAgICBsZXQgbWRhdDtcbiAgICB0cnkge1xuICAgICAgbWRhdCA9IG5ldyBVaW50OEFycmF5KG1kYXRTaXplKTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMub2JzZXJ2ZXIuZW1pdChFdmVudHMuRVJST1IsIEV2ZW50cy5FUlJPUiwge1xuICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk1VWF9FUlJPUixcbiAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLlJFTVVYX0FMTE9DX0VSUk9SLFxuICAgICAgICBmYXRhbDogZmFsc2UsXG4gICAgICAgIGVycm9yOiBlcnIsXG4gICAgICAgIGJ5dGVzOiBtZGF0U2l6ZSxcbiAgICAgICAgcmVhc29uOiBgZmFpbCBhbGxvY2F0aW5nIHZpZGVvIG1kYXQgJHttZGF0U2l6ZX1gXG4gICAgICB9KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgdmlldyA9IG5ldyBEYXRhVmlldyhtZGF0LmJ1ZmZlcik7XG4gICAgdmlldy5zZXRVaW50MzIoMCwgbWRhdFNpemUpO1xuICAgIG1kYXQuc2V0KE1QNC50eXBlcy5tZGF0LCA0KTtcbiAgICBsZXQgc3RyZXRjaGVkTGFzdEZyYW1lID0gZmFsc2U7XG4gICAgbGV0IG1pbkR0c0RlbHRhID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuICAgIGxldCBtaW5QdHNEZWx0YSA9IE51bWJlci5QT1NJVElWRV9JTkZJTklUWTtcbiAgICBsZXQgbWF4RHRzRGVsdGEgPSBOdW1iZXIuTkVHQVRJVkVfSU5GSU5JVFk7XG4gICAgbGV0IG1heFB0c0RlbHRhID0gTnVtYmVyLk5FR0FUSVZFX0lORklOSVRZO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbmJTYW1wbGVzOyBpKyspIHtcbiAgICAgIGNvbnN0IFZpZGVvU2FtcGxlID0gaW5wdXRTYW1wbGVzW2ldO1xuICAgICAgY29uc3QgVmlkZW9TYW1wbGVVbml0cyA9IFZpZGVvU2FtcGxlLnVuaXRzO1xuICAgICAgbGV0IG1wNFNhbXBsZUxlbmd0aCA9IDA7XG4gICAgICAvLyBjb252ZXJ0IE5BTFUgYml0c3RyZWFtIHRvIE1QNCBmb3JtYXQgKHByZXBlbmQgTkFMVSB3aXRoIHNpemUgZmllbGQpXG4gICAgICBmb3IgKGxldCBqID0gMCwgbmJVbml0cyA9IFZpZGVvU2FtcGxlVW5pdHMubGVuZ3RoOyBqIDwgbmJVbml0czsgaisrKSB7XG4gICAgICAgIGNvbnN0IHVuaXQgPSBWaWRlb1NhbXBsZVVuaXRzW2pdO1xuICAgICAgICBjb25zdCB1bml0RGF0YSA9IHVuaXQuZGF0YTtcbiAgICAgICAgY29uc3QgdW5pdERhdGFMZW4gPSB1bml0LmRhdGEuYnl0ZUxlbmd0aDtcbiAgICAgICAgdmlldy5zZXRVaW50MzIob2Zmc2V0LCB1bml0RGF0YUxlbik7XG4gICAgICAgIG9mZnNldCArPSA0O1xuICAgICAgICBtZGF0LnNldCh1bml0RGF0YSwgb2Zmc2V0KTtcbiAgICAgICAgb2Zmc2V0ICs9IHVuaXREYXRhTGVuO1xuICAgICAgICBtcDRTYW1wbGVMZW5ndGggKz0gNCArIHVuaXREYXRhTGVuO1xuICAgICAgfVxuXG4gICAgICAvLyBleHBlY3RlZCBzYW1wbGUgZHVyYXRpb24gaXMgdGhlIERlY29kaW5nIFRpbWVzdGFtcCBkaWZmIG9mIGNvbnNlY3V0aXZlIHNhbXBsZXNcbiAgICAgIGxldCBwdHNEZWx0YTtcbiAgICAgIGlmIChpIDwgbmJTYW1wbGVzIC0gMSkge1xuICAgICAgICBtcDRTYW1wbGVEdXJhdGlvbiA9IGlucHV0U2FtcGxlc1tpICsgMV0uZHRzIC0gVmlkZW9TYW1wbGUuZHRzO1xuICAgICAgICBwdHNEZWx0YSA9IGlucHV0U2FtcGxlc1tpICsgMV0ucHRzIC0gVmlkZW9TYW1wbGUucHRzO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY29uc3QgY29uZmlnID0gdGhpcy5jb25maWc7XG4gICAgICAgIGNvbnN0IGxhc3RGcmFtZUR1cmF0aW9uID0gaSA+IDAgPyBWaWRlb1NhbXBsZS5kdHMgLSBpbnB1dFNhbXBsZXNbaSAtIDFdLmR0cyA6IGF2ZXJhZ2VTYW1wbGVEdXJhdGlvbjtcbiAgICAgICAgcHRzRGVsdGEgPSBpID4gMCA/IFZpZGVvU2FtcGxlLnB0cyAtIGlucHV0U2FtcGxlc1tpIC0gMV0ucHRzIDogYXZlcmFnZVNhbXBsZUR1cmF0aW9uO1xuICAgICAgICBpZiAoY29uZmlnLnN0cmV0Y2hTaG9ydFZpZGVvVHJhY2sgJiYgdGhpcy5uZXh0QXVkaW9QdHMgIT09IG51bGwpIHtcbiAgICAgICAgICAvLyBJbiBzb21lIGNhc2VzLCBhIHNlZ21lbnQncyBhdWRpbyB0cmFjayBkdXJhdGlvbiBtYXkgZXhjZWVkIHRoZSB2aWRlbyB0cmFjayBkdXJhdGlvbi5cbiAgICAgICAgICAvLyBTaW5jZSB3ZSd2ZSBhbHJlYWR5IHJlbXV4ZWQgYXVkaW8sIGFuZCB3ZSBrbm93IGhvdyBsb25nIHRoZSBhdWRpbyB0cmFjayBpcywgd2UgbG9vayB0b1xuICAgICAgICAgIC8vIHNlZSBpZiB0aGUgZGVsdGEgdG8gdGhlIG5leHQgc2VnbWVudCBpcyBsb25nZXIgdGhhbiBtYXhCdWZmZXJIb2xlLlxuICAgICAgICAgIC8vIElmIHNvLCBwbGF5YmFjayB3b3VsZCBwb3RlbnRpYWxseSBnZXQgc3R1Y2ssIHNvIHdlIGFydGlmaWNpYWxseSBpbmZsYXRlXG4gICAgICAgICAgLy8gdGhlIGR1cmF0aW9uIG9mIHRoZSBsYXN0IGZyYW1lIHRvIG1pbmltaXplIGFueSBwb3RlbnRpYWwgZ2FwIGJldHdlZW4gc2VnbWVudHMuXG4gICAgICAgICAgY29uc3QgZ2FwVG9sZXJhbmNlID0gTWF0aC5mbG9vcihjb25maWcubWF4QnVmZmVySG9sZSAqIHRpbWVTY2FsZSk7XG4gICAgICAgICAgY29uc3QgZGVsdGFUb0ZyYW1lRW5kID0gKGF1ZGlvVHJhY2tMZW5ndGggPyBtaW5QVFMgKyBhdWRpb1RyYWNrTGVuZ3RoICogdGltZVNjYWxlIDogdGhpcy5uZXh0QXVkaW9QdHMpIC0gVmlkZW9TYW1wbGUucHRzO1xuICAgICAgICAgIGlmIChkZWx0YVRvRnJhbWVFbmQgPiBnYXBUb2xlcmFuY2UpIHtcbiAgICAgICAgICAgIC8vIFdlIHN1YnRyYWN0IGxhc3RGcmFtZUR1cmF0aW9uIGZyb20gZGVsdGFUb0ZyYW1lRW5kIHRvIHRyeSB0byBwcmV2ZW50IGFueSB2aWRlb1xuICAgICAgICAgICAgLy8gZnJhbWUgb3ZlcmxhcC4gbWF4QnVmZmVySG9sZSBzaG91bGQgYmUgPj4gbGFzdEZyYW1lRHVyYXRpb24gYW55d2F5LlxuICAgICAgICAgICAgbXA0U2FtcGxlRHVyYXRpb24gPSBkZWx0YVRvRnJhbWVFbmQgLSBsYXN0RnJhbWVEdXJhdGlvbjtcbiAgICAgICAgICAgIGlmIChtcDRTYW1wbGVEdXJhdGlvbiA8IDApIHtcbiAgICAgICAgICAgICAgbXA0U2FtcGxlRHVyYXRpb24gPSBsYXN0RnJhbWVEdXJhdGlvbjtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIHN0cmV0Y2hlZExhc3RGcmFtZSA9IHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBsb2dnZXIubG9nKGBbbXA0LXJlbXV4ZXJdOiBJdCBpcyBhcHByb3hpbWF0ZWx5ICR7ZGVsdGFUb0ZyYW1lRW5kIC8gOTB9IG1zIHRvIHRoZSBuZXh0IHNlZ21lbnQ7IHVzaW5nIGR1cmF0aW9uICR7bXA0U2FtcGxlRHVyYXRpb24gLyA5MH0gbXMgZm9yIHRoZSBsYXN0IHZpZGVvIGZyYW1lLmApO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBtcDRTYW1wbGVEdXJhdGlvbiA9IGxhc3RGcmFtZUR1cmF0aW9uO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBtcDRTYW1wbGVEdXJhdGlvbiA9IGxhc3RGcmFtZUR1cmF0aW9uO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBjb25zdCBjb21wb3NpdGlvblRpbWVPZmZzZXQgPSBNYXRoLnJvdW5kKFZpZGVvU2FtcGxlLnB0cyAtIFZpZGVvU2FtcGxlLmR0cyk7XG4gICAgICBtaW5EdHNEZWx0YSA9IE1hdGgubWluKG1pbkR0c0RlbHRhLCBtcDRTYW1wbGVEdXJhdGlvbik7XG4gICAgICBtYXhEdHNEZWx0YSA9IE1hdGgubWF4KG1heER0c0RlbHRhLCBtcDRTYW1wbGVEdXJhdGlvbik7XG4gICAgICBtaW5QdHNEZWx0YSA9IE1hdGgubWluKG1pblB0c0RlbHRhLCBwdHNEZWx0YSk7XG4gICAgICBtYXhQdHNEZWx0YSA9IE1hdGgubWF4KG1heFB0c0RlbHRhLCBwdHNEZWx0YSk7XG4gICAgICBvdXRwdXRTYW1wbGVzLnB1c2gobmV3IE1wNFNhbXBsZShWaWRlb1NhbXBsZS5rZXksIG1wNFNhbXBsZUR1cmF0aW9uLCBtcDRTYW1wbGVMZW5ndGgsIGNvbXBvc2l0aW9uVGltZU9mZnNldCkpO1xuICAgIH1cbiAgICBpZiAob3V0cHV0U2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgIGlmIChjaHJvbWVWZXJzaW9uKSB7XG4gICAgICAgIGlmIChjaHJvbWVWZXJzaW9uIDwgNzApIHtcbiAgICAgICAgICAvLyBDaHJvbWUgd29ya2Fyb3VuZCwgbWFyayBmaXJzdCBzYW1wbGUgYXMgYmVpbmcgYSBSYW5kb20gQWNjZXNzIFBvaW50IChrZXlmcmFtZSkgdG8gYXZvaWQgc291cmNlYnVmZmVyIGFwcGVuZCBpc3N1ZVxuICAgICAgICAgIC8vIGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3AvY2hyb21pdW0vaXNzdWVzL2RldGFpbD9pZD0yMjk0MTJcbiAgICAgICAgICBjb25zdCBmbGFncyA9IG91dHB1dFNhbXBsZXNbMF0uZmxhZ3M7XG4gICAgICAgICAgZmxhZ3MuZGVwZW5kc09uID0gMjtcbiAgICAgICAgICBmbGFncy5pc05vblN5bmMgPSAwO1xuICAgICAgICB9XG4gICAgICB9IGVsc2UgaWYgKHNhZmFyaVdlYmtpdFZlcnNpb24pIHtcbiAgICAgICAgLy8gRml4IGZvciBcIkNOTiBzcGVjaWFsIHJlcG9ydCwgd2l0aCBDQ1wiIGluIHRlc3Qtc3RyZWFtcyAoU2FmYXJpIGJyb3dzZXIgb25seSlcbiAgICAgICAgLy8gSWdub3JlIERUUyB3aGVuIGZyYW1lIGR1cmF0aW9ucyBhcmUgaXJyZWd1bGFyLiBTYWZhcmkgTVNFIGRvZXMgbm90IGhhbmRsZSB0aGlzIGxlYWRpbmcgdG8gZ2Fwcy5cbiAgICAgICAgaWYgKG1heFB0c0RlbHRhIC0gbWluUHRzRGVsdGEgPCBtYXhEdHNEZWx0YSAtIG1pbkR0c0RlbHRhICYmIGF2ZXJhZ2VTYW1wbGVEdXJhdGlvbiAvIG1heER0c0RlbHRhIDwgMC4wMjUgJiYgb3V0cHV0U2FtcGxlc1swXS5jdHMgPT09IDApIHtcbiAgICAgICAgICBsb2dnZXIud2FybignRm91bmQgaXJyZWd1bGFyIGdhcHMgaW4gc2FtcGxlIGR1cmF0aW9uLiBVc2luZyBQVFMgaW5zdGVhZCBvZiBEVFMgdG8gZGV0ZXJtaW5lIE1QNCBzYW1wbGUgZHVyYXRpb24uJyk7XG4gICAgICAgICAgbGV0IGR0cyA9IGZpcnN0RFRTO1xuICAgICAgICAgIGZvciAobGV0IGkgPSAwLCBsZW4gPSBvdXRwdXRTYW1wbGVzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBuZXh0RHRzID0gZHRzICsgb3V0cHV0U2FtcGxlc1tpXS5kdXJhdGlvbjtcbiAgICAgICAgICAgIGNvbnN0IHB0cyA9IGR0cyArIG91dHB1dFNhbXBsZXNbaV0uY3RzO1xuICAgICAgICAgICAgaWYgKGkgPCBsZW4gLSAxKSB7XG4gICAgICAgICAgICAgIGNvbnN0IG5leHRQdHMgPSBuZXh0RHRzICsgb3V0cHV0U2FtcGxlc1tpICsgMV0uY3RzO1xuICAgICAgICAgICAgICBvdXRwdXRTYW1wbGVzW2ldLmR1cmF0aW9uID0gbmV4dFB0cyAtIHB0cztcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIG91dHB1dFNhbXBsZXNbaV0uZHVyYXRpb24gPSBpID8gb3V0cHV0U2FtcGxlc1tpIC0gMV0uZHVyYXRpb24gOiBhdmVyYWdlU2FtcGxlRHVyYXRpb247XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBvdXRwdXRTYW1wbGVzW2ldLmN0cyA9IDA7XG4gICAgICAgICAgICBkdHMgPSBuZXh0RHRzO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICAvLyBuZXh0IEFWQyBzYW1wbGUgRFRTIHNob3VsZCBiZSBlcXVhbCB0byBsYXN0IHNhbXBsZSBEVFMgKyBsYXN0IHNhbXBsZSBkdXJhdGlvbiAoaW4gUEVTIHRpbWVzY2FsZSlcbiAgICBtcDRTYW1wbGVEdXJhdGlvbiA9IHN0cmV0Y2hlZExhc3RGcmFtZSB8fCAhbXA0U2FtcGxlRHVyYXRpb24gPyBhdmVyYWdlU2FtcGxlRHVyYXRpb24gOiBtcDRTYW1wbGVEdXJhdGlvbjtcbiAgICB0aGlzLm5leHRBdmNEdHMgPSBuZXh0QXZjRHRzID0gbGFzdERUUyArIG1wNFNhbXBsZUR1cmF0aW9uO1xuICAgIHRoaXMudmlkZW9TYW1wbGVEdXJhdGlvbiA9IG1wNFNhbXBsZUR1cmF0aW9uO1xuICAgIHRoaXMuaXNWaWRlb0NvbnRpZ3VvdXMgPSB0cnVlO1xuICAgIGNvbnN0IG1vb2YgPSBNUDQubW9vZih0cmFjay5zZXF1ZW5jZU51bWJlcisrLCBmaXJzdERUUywgX2V4dGVuZHMoe30sIHRyYWNrLCB7XG4gICAgICBzYW1wbGVzOiBvdXRwdXRTYW1wbGVzXG4gICAgfSkpO1xuICAgIGNvbnN0IHR5cGUgPSAndmlkZW8nO1xuICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICBkYXRhMTogbW9vZixcbiAgICAgIGRhdGEyOiBtZGF0LFxuICAgICAgc3RhcnRQVFM6IG1pblBUUyAvIHRpbWVTY2FsZSxcbiAgICAgIGVuZFBUUzogKG1heFBUUyArIG1wNFNhbXBsZUR1cmF0aW9uKSAvIHRpbWVTY2FsZSxcbiAgICAgIHN0YXJ0RFRTOiBmaXJzdERUUyAvIHRpbWVTY2FsZSxcbiAgICAgIGVuZERUUzogbmV4dEF2Y0R0cyAvIHRpbWVTY2FsZSxcbiAgICAgIHR5cGUsXG4gICAgICBoYXNBdWRpbzogZmFsc2UsXG4gICAgICBoYXNWaWRlbzogdHJ1ZSxcbiAgICAgIG5iOiBvdXRwdXRTYW1wbGVzLmxlbmd0aCxcbiAgICAgIGRyb3BwZWQ6IHRyYWNrLmRyb3BwZWRcbiAgICB9O1xuICAgIHRyYWNrLnNhbXBsZXMgPSBbXTtcbiAgICB0cmFjay5kcm9wcGVkID0gMDtcbiAgICByZXR1cm4gZGF0YTtcbiAgfVxuICBnZXRTYW1wbGVzUGVyRnJhbWUodHJhY2spIHtcbiAgICBzd2l0Y2ggKHRyYWNrLnNlZ21lbnRDb2RlYykge1xuICAgICAgY2FzZSAnbXAzJzpcbiAgICAgICAgcmV0dXJuIE1QRUdfQVVESU9fU0FNUExFX1BFUl9GUkFNRTtcbiAgICAgIGNhc2UgJ2FjMyc6XG4gICAgICAgIHJldHVybiBBQzNfU0FNUExFU19QRVJfRlJBTUU7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICByZXR1cm4gQUFDX1NBTVBMRVNfUEVSX0ZSQU1FO1xuICAgIH1cbiAgfVxuICByZW11eEF1ZGlvKHRyYWNrLCB0aW1lT2Zmc2V0LCBjb250aWd1b3VzLCBhY2N1cmF0ZVRpbWVPZmZzZXQsIHZpZGVvVGltZU9mZnNldCkge1xuICAgIGNvbnN0IGlucHV0VGltZVNjYWxlID0gdHJhY2suaW5wdXRUaW1lU2NhbGU7XG4gICAgY29uc3QgbXA0dGltZVNjYWxlID0gdHJhY2suc2FtcGxlcmF0ZSA/IHRyYWNrLnNhbXBsZXJhdGUgOiBpbnB1dFRpbWVTY2FsZTtcbiAgICBjb25zdCBzY2FsZUZhY3RvciA9IGlucHV0VGltZVNjYWxlIC8gbXA0dGltZVNjYWxlO1xuICAgIGNvbnN0IG1wNFNhbXBsZUR1cmF0aW9uID0gdGhpcy5nZXRTYW1wbGVzUGVyRnJhbWUodHJhY2spO1xuICAgIGNvbnN0IGlucHV0U2FtcGxlRHVyYXRpb24gPSBtcDRTYW1wbGVEdXJhdGlvbiAqIHNjYWxlRmFjdG9yO1xuICAgIGNvbnN0IGluaXRQVFMgPSB0aGlzLl9pbml0UFRTO1xuICAgIGNvbnN0IHJhd01QRUcgPSB0cmFjay5zZWdtZW50Q29kZWMgPT09ICdtcDMnICYmIHRoaXMudHlwZVN1cHBvcnRlZC5tcGVnO1xuICAgIGNvbnN0IG91dHB1dFNhbXBsZXMgPSBbXTtcbiAgICBjb25zdCBhbGlnbmVkV2l0aFZpZGVvID0gdmlkZW9UaW1lT2Zmc2V0ICE9PSB1bmRlZmluZWQ7XG4gICAgbGV0IGlucHV0U2FtcGxlcyA9IHRyYWNrLnNhbXBsZXM7XG4gICAgbGV0IG9mZnNldCA9IHJhd01QRUcgPyAwIDogODtcbiAgICBsZXQgbmV4dEF1ZGlvUHRzID0gdGhpcy5uZXh0QXVkaW9QdHMgfHwgLTE7XG5cbiAgICAvLyB3aW5kb3cuYXVkaW9TYW1wbGVzID8gd2luZG93LmF1ZGlvU2FtcGxlcy5wdXNoKGlucHV0U2FtcGxlcy5tYXAocyA9PiBzLnB0cykpIDogKHdpbmRvdy5hdWRpb1NhbXBsZXMgPSBbaW5wdXRTYW1wbGVzLm1hcChzID0+IHMucHRzKV0pO1xuXG4gICAgLy8gZm9yIGF1ZGlvIHNhbXBsZXMsIGFsc28gY29uc2lkZXIgY29uc2VjdXRpdmUgZnJhZ21lbnRzIGFzIGJlaW5nIGNvbnRpZ3VvdXMgKGV2ZW4gaWYgYSBsZXZlbCBzd2l0Y2ggb2NjdXJzKSxcbiAgICAvLyBmb3Igc2FrZSBvZiBjbGFyaXR5OlxuICAgIC8vIGNvbnNlY3V0aXZlIGZyYWdtZW50cyBhcmUgZnJhZ3Mgd2l0aFxuICAgIC8vICAtIGxlc3MgdGhhbiAxMDBtcyBnYXBzIGJldHdlZW4gbmV3IHRpbWUgb2Zmc2V0IChpZiBhY2N1cmF0ZSkgYW5kIG5leHQgZXhwZWN0ZWQgUFRTIE9SXG4gICAgLy8gIC0gbGVzcyB0aGFuIDIwIGF1ZGlvIGZyYW1lcyBkaXN0YW5jZVxuICAgIC8vIGNvbnRpZ3VvdXMgZnJhZ21lbnRzIGFyZSBjb25zZWN1dGl2ZSBmcmFnbWVudHMgZnJvbSBzYW1lIHF1YWxpdHkgbGV2ZWwgKHNhbWUgbGV2ZWwsIG5ldyBTTiA9IG9sZCBTTiArIDEpXG4gICAgLy8gdGhpcyBoZWxwcyBlbnN1cmluZyBhdWRpbyBjb250aW51aXR5XG4gICAgLy8gYW5kIHRoaXMgYWxzbyBhdm9pZHMgYXVkaW8gZ2xpdGNoZXMvY3V0IHdoZW4gc3dpdGNoaW5nIHF1YWxpdHksIG9yIHJlcG9ydGluZyB3cm9uZyBkdXJhdGlvbiBvbiBmaXJzdCBhdWRpbyBmcmFtZVxuICAgIGNvbnN0IHRpbWVPZmZzZXRNcGVnVFMgPSB0aW1lT2Zmc2V0ICogaW5wdXRUaW1lU2NhbGU7XG4gICAgY29uc3QgaW5pdFRpbWUgPSBpbml0UFRTLmJhc2VUaW1lICogaW5wdXRUaW1lU2NhbGUgLyBpbml0UFRTLnRpbWVzY2FsZTtcbiAgICB0aGlzLmlzQXVkaW9Db250aWd1b3VzID0gY29udGlndW91cyA9IGNvbnRpZ3VvdXMgfHwgaW5wdXRTYW1wbGVzLmxlbmd0aCAmJiBuZXh0QXVkaW9QdHMgPiAwICYmIChhY2N1cmF0ZVRpbWVPZmZzZXQgJiYgTWF0aC5hYnModGltZU9mZnNldE1wZWdUUyAtIG5leHRBdWRpb1B0cykgPCA5MDAwIHx8IE1hdGguYWJzKG5vcm1hbGl6ZVB0cyhpbnB1dFNhbXBsZXNbMF0ucHRzIC0gaW5pdFRpbWUsIHRpbWVPZmZzZXRNcGVnVFMpIC0gbmV4dEF1ZGlvUHRzKSA8IDIwICogaW5wdXRTYW1wbGVEdXJhdGlvbik7XG5cbiAgICAvLyBjb21wdXRlIG5vcm1hbGl6ZWQgUFRTXG4gICAgaW5wdXRTYW1wbGVzLmZvckVhY2goZnVuY3Rpb24gKHNhbXBsZSkge1xuICAgICAgc2FtcGxlLnB0cyA9IG5vcm1hbGl6ZVB0cyhzYW1wbGUucHRzIC0gaW5pdFRpbWUsIHRpbWVPZmZzZXRNcGVnVFMpO1xuICAgIH0pO1xuICAgIGlmICghY29udGlndW91cyB8fCBuZXh0QXVkaW9QdHMgPCAwKSB7XG4gICAgICAvLyBmaWx0ZXIgb3V0IHNhbXBsZSB3aXRoIG5lZ2F0aXZlIFBUUyB0aGF0IGFyZSBub3QgcGxheWFibGUgYW55d2F5XG4gICAgICAvLyBpZiB3ZSBkb24ndCByZW1vdmUgdGhlc2UgbmVnYXRpdmUgc2FtcGxlcywgdGhleSB3aWxsIHNoaWZ0IGFsbCBhdWRpbyBzYW1wbGVzIGZvcndhcmQuXG4gICAgICAvLyBsZWFkaW5nIHRvIGF1ZGlvIG92ZXJsYXAgYmV0d2VlbiBjdXJyZW50IC8gbmV4dCBmcmFnbWVudFxuICAgICAgaW5wdXRTYW1wbGVzID0gaW5wdXRTYW1wbGVzLmZpbHRlcihzYW1wbGUgPT4gc2FtcGxlLnB0cyA+PSAwKTtcblxuICAgICAgLy8gaW4gY2FzZSBhbGwgc2FtcGxlcyBoYXZlIG5lZ2F0aXZlIFBUUywgYW5kIGhhdmUgYmVlbiBmaWx0ZXJlZCBvdXQsIHJldHVybiBub3dcbiAgICAgIGlmICghaW5wdXRTYW1wbGVzLmxlbmd0aCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBpZiAodmlkZW9UaW1lT2Zmc2V0ID09PSAwKSB7XG4gICAgICAgIC8vIFNldCB0aGUgc3RhcnQgdG8gMCB0byBtYXRjaCB2aWRlbyBzbyB0aGF0IHN0YXJ0IGdhcHMgbGFyZ2VyIHRoYW4gaW5wdXRTYW1wbGVEdXJhdGlvbiBhcmUgZmlsbGVkIHdpdGggc2lsZW5jZVxuICAgICAgICBuZXh0QXVkaW9QdHMgPSAwO1xuICAgICAgfSBlbHNlIGlmIChhY2N1cmF0ZVRpbWVPZmZzZXQgJiYgIWFsaWduZWRXaXRoVmlkZW8pIHtcbiAgICAgICAgLy8gV2hlbiBub3Qgc2Vla2luZywgbm90IGxpdmUsIGFuZCBMZXZlbERldGFpbHMuUFRTS25vd24sIHVzZSBmcmFnbWVudCBzdGFydCBhcyBwcmVkaWN0ZWQgbmV4dCBhdWRpbyBQVFNcbiAgICAgICAgbmV4dEF1ZGlvUHRzID0gTWF0aC5tYXgoMCwgdGltZU9mZnNldE1wZWdUUyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBpZiBmcmFncyBhcmUgbm90IGNvbnRpZ3VvdXMgYW5kIGlmIHdlIGNhbnQgdHJ1c3QgdGltZSBvZmZzZXQsIGxldCdzIHVzZSBmaXJzdCBzYW1wbGUgUFRTIGFzIG5leHQgYXVkaW8gUFRTXG4gICAgICAgIG5leHRBdWRpb1B0cyA9IGlucHV0U2FtcGxlc1swXS5wdHM7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlIGF1ZGlvIHRyYWNrIGlzIG1pc3Npbmcgc2FtcGxlcywgdGhlIGZyYW1lcyBzZWVtIHRvIGdldCBcImxlZnQtc2hpZnRlZFwiIHdpdGhpbiB0aGVcbiAgICAvLyByZXN1bHRpbmcgbXA0IHNlZ21lbnQsIGNhdXNpbmcgc3luYyBpc3N1ZXMgYW5kIGxlYXZpbmcgZ2FwcyBhdCB0aGUgZW5kIG9mIHRoZSBhdWRpbyBzZWdtZW50LlxuICAgIC8vIEluIGFuIGVmZm9ydCB0byBwcmV2ZW50IHRoaXMgZnJvbSBoYXBwZW5pbmcsIHdlIGluamVjdCBmcmFtZXMgaGVyZSB3aGVyZSB0aGVyZSBhcmUgZ2Fwcy5cbiAgICAvLyBXaGVuIHBvc3NpYmxlLCB3ZSBpbmplY3QgYSBzaWxlbnQgZnJhbWU7IHdoZW4gdGhhdCdzIG5vdCBwb3NzaWJsZSwgd2UgZHVwbGljYXRlIHRoZSBsYXN0XG4gICAgLy8gZnJhbWUuXG5cbiAgICBpZiAodHJhY2suc2VnbWVudENvZGVjID09PSAnYWFjJykge1xuICAgICAgY29uc3QgbWF4QXVkaW9GcmFtZXNEcmlmdCA9IHRoaXMuY29uZmlnLm1heEF1ZGlvRnJhbWVzRHJpZnQ7XG4gICAgICBmb3IgKGxldCBpID0gMCwgbmV4dFB0cyA9IG5leHRBdWRpb1B0czsgaSA8IGlucHV0U2FtcGxlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAvLyBGaXJzdCwgbGV0J3Mgc2VlIGhvdyBmYXIgb2ZmIHRoaXMgZnJhbWUgaXMgZnJvbSB3aGVyZSB3ZSBleHBlY3QgaXQgdG8gYmVcbiAgICAgICAgY29uc3Qgc2FtcGxlID0gaW5wdXRTYW1wbGVzW2ldO1xuICAgICAgICBjb25zdCBwdHMgPSBzYW1wbGUucHRzO1xuICAgICAgICBjb25zdCBkZWx0YSA9IHB0cyAtIG5leHRQdHM7XG4gICAgICAgIGNvbnN0IGR1cmF0aW9uID0gTWF0aC5hYnMoMTAwMCAqIGRlbHRhIC8gaW5wdXRUaW1lU2NhbGUpO1xuXG4gICAgICAgIC8vIFdoZW4gcmVtdXhpbmcgd2l0aCB2aWRlbywgaWYgd2UncmUgb3ZlcmxhcHBpbmcgYnkgbW9yZSB0aGFuIGEgZHVyYXRpb24sIGRyb3AgdGhpcyBzYW1wbGUgdG8gc3RheSBpbiBzeW5jXG4gICAgICAgIGlmIChkZWx0YSA8PSAtbWF4QXVkaW9GcmFtZXNEcmlmdCAqIGlucHV0U2FtcGxlRHVyYXRpb24gJiYgYWxpZ25lZFdpdGhWaWRlbykge1xuICAgICAgICAgIGlmIChpID09PSAwKSB7XG4gICAgICAgICAgICBsb2dnZXIud2FybihgQXVkaW8gZnJhbWUgQCAkeyhwdHMgLyBpbnB1dFRpbWVTY2FsZSkudG9GaXhlZCgzKX1zIG92ZXJsYXBzIG5leHRBdWRpb1B0cyBieSAke01hdGgucm91bmQoMTAwMCAqIGRlbHRhIC8gaW5wdXRUaW1lU2NhbGUpfSBtcy5gKTtcbiAgICAgICAgICAgIHRoaXMubmV4dEF1ZGlvUHRzID0gbmV4dEF1ZGlvUHRzID0gbmV4dFB0cyA9IHB0cztcbiAgICAgICAgICB9XG4gICAgICAgIH0gLy8gZXNsaW50LWRpc2FibGUtbGluZSBicmFjZS1zdHlsZVxuXG4gICAgICAgIC8vIEluc2VydCBtaXNzaW5nIGZyYW1lcyBpZjpcbiAgICAgICAgLy8gMTogV2UncmUgbW9yZSB0aGFuIG1heEF1ZGlvRnJhbWVzRHJpZnQgZnJhbWUgYXdheVxuICAgICAgICAvLyAyOiBOb3QgbW9yZSB0aGFuIE1BWF9TSUxFTlRfRlJBTUVfRFVSQVRJT04gYXdheVxuICAgICAgICAvLyAzOiBjdXJyZW50VGltZSAoYWthIG5leHRQdHNOb3JtKSBpcyBub3QgMFxuICAgICAgICAvLyA0OiByZW11eGluZyB3aXRoIHZpZGVvICh2aWRlb1RpbWVPZmZzZXQgIT09IHVuZGVmaW5lZClcbiAgICAgICAgZWxzZSBpZiAoZGVsdGEgPj0gbWF4QXVkaW9GcmFtZXNEcmlmdCAqIGlucHV0U2FtcGxlRHVyYXRpb24gJiYgZHVyYXRpb24gPCBNQVhfU0lMRU5UX0ZSQU1FX0RVUkFUSU9OICYmIGFsaWduZWRXaXRoVmlkZW8pIHtcbiAgICAgICAgICBsZXQgbWlzc2luZyA9IE1hdGgucm91bmQoZGVsdGEgLyBpbnB1dFNhbXBsZUR1cmF0aW9uKTtcbiAgICAgICAgICAvLyBBZGp1c3QgbmV4dFB0cyBzbyB0aGF0IHNpbGVudCBzYW1wbGVzIGFyZSBhbGlnbmVkIHdpdGggbWVkaWEgcHRzLiBUaGlzIHdpbGwgcHJldmVudCBtZWRpYSBzYW1wbGVzIGZyb21cbiAgICAgICAgICAvLyBsYXRlciBiZWluZyBzaGlmdGVkIGlmIG5leHRQdHMgaXMgYmFzZWQgb24gdGltZU9mZnNldCBhbmQgZGVsdGEgaXMgbm90IGEgbXVsdGlwbGUgb2YgaW5wdXRTYW1wbGVEdXJhdGlvbi5cbiAgICAgICAgICBuZXh0UHRzID0gcHRzIC0gbWlzc2luZyAqIGlucHV0U2FtcGxlRHVyYXRpb247XG4gICAgICAgICAgaWYgKG5leHRQdHMgPCAwKSB7XG4gICAgICAgICAgICBtaXNzaW5nLS07XG4gICAgICAgICAgICBuZXh0UHRzICs9IGlucHV0U2FtcGxlRHVyYXRpb247XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmIChpID09PSAwKSB7XG4gICAgICAgICAgICB0aGlzLm5leHRBdWRpb1B0cyA9IG5leHRBdWRpb1B0cyA9IG5leHRQdHM7XG4gICAgICAgICAgfVxuICAgICAgICAgIGxvZ2dlci53YXJuKGBbbXA0LXJlbXV4ZXJdOiBJbmplY3RpbmcgJHttaXNzaW5nfSBhdWRpbyBmcmFtZSBAICR7KG5leHRQdHMgLyBpbnB1dFRpbWVTY2FsZSkudG9GaXhlZCgzKX1zIGR1ZSB0byAke01hdGgucm91bmQoMTAwMCAqIGRlbHRhIC8gaW5wdXRUaW1lU2NhbGUpfSBtcyBnYXAuYCk7XG4gICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBtaXNzaW5nOyBqKyspIHtcbiAgICAgICAgICAgIGNvbnN0IG5ld1N0YW1wID0gTWF0aC5tYXgobmV4dFB0cywgMCk7XG4gICAgICAgICAgICBsZXQgZmlsbEZyYW1lID0gQUFDLmdldFNpbGVudEZyYW1lKHRyYWNrLm1hbmlmZXN0Q29kZWMgfHwgdHJhY2suY29kZWMsIHRyYWNrLmNoYW5uZWxDb3VudCk7XG4gICAgICAgICAgICBpZiAoIWZpbGxGcmFtZSkge1xuICAgICAgICAgICAgICBsb2dnZXIubG9nKCdbbXA0LXJlbXV4ZXJdOiBVbmFibGUgdG8gZ2V0IHNpbGVudCBmcmFtZSBmb3IgZ2l2ZW4gYXVkaW8gY29kZWM7IGR1cGxpY2F0aW5nIGxhc3QgZnJhbWUgaW5zdGVhZC4nKTtcbiAgICAgICAgICAgICAgZmlsbEZyYW1lID0gc2FtcGxlLnVuaXQuc3ViYXJyYXkoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlucHV0U2FtcGxlcy5zcGxpY2UoaSwgMCwge1xuICAgICAgICAgICAgICB1bml0OiBmaWxsRnJhbWUsXG4gICAgICAgICAgICAgIHB0czogbmV3U3RhbXBcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgbmV4dFB0cyArPSBpbnB1dFNhbXBsZUR1cmF0aW9uO1xuICAgICAgICAgICAgaSsrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBzYW1wbGUucHRzID0gbmV4dFB0cztcbiAgICAgICAgbmV4dFB0cyArPSBpbnB1dFNhbXBsZUR1cmF0aW9uO1xuICAgICAgfVxuICAgIH1cbiAgICBsZXQgZmlyc3RQVFMgPSBudWxsO1xuICAgIGxldCBsYXN0UFRTID0gbnVsbDtcbiAgICBsZXQgbWRhdDtcbiAgICBsZXQgbWRhdFNpemUgPSAwO1xuICAgIGxldCBzYW1wbGVMZW5ndGggPSBpbnB1dFNhbXBsZXMubGVuZ3RoO1xuICAgIHdoaWxlIChzYW1wbGVMZW5ndGgtLSkge1xuICAgICAgbWRhdFNpemUgKz0gaW5wdXRTYW1wbGVzW3NhbXBsZUxlbmd0aF0udW5pdC5ieXRlTGVuZ3RoO1xuICAgIH1cbiAgICBmb3IgKGxldCBqID0gMCwgX25iU2FtcGxlcyA9IGlucHV0U2FtcGxlcy5sZW5ndGg7IGogPCBfbmJTYW1wbGVzOyBqKyspIHtcbiAgICAgIGNvbnN0IGF1ZGlvU2FtcGxlID0gaW5wdXRTYW1wbGVzW2pdO1xuICAgICAgY29uc3QgdW5pdCA9IGF1ZGlvU2FtcGxlLnVuaXQ7XG4gICAgICBsZXQgcHRzID0gYXVkaW9TYW1wbGUucHRzO1xuICAgICAgaWYgKGxhc3RQVFMgIT09IG51bGwpIHtcbiAgICAgICAgLy8gSWYgd2UgaGF2ZSBtb3JlIHRoYW4gb25lIHNhbXBsZSwgc2V0IHRoZSBkdXJhdGlvbiBvZiB0aGUgc2FtcGxlIHRvIHRoZSBcInJlYWxcIiBkdXJhdGlvbjsgdGhlIFBUUyBkaWZmIHdpdGhcbiAgICAgICAgLy8gdGhlIHByZXZpb3VzIHNhbXBsZVxuICAgICAgICBjb25zdCBwcmV2U2FtcGxlID0gb3V0cHV0U2FtcGxlc1tqIC0gMV07XG4gICAgICAgIHByZXZTYW1wbGUuZHVyYXRpb24gPSBNYXRoLnJvdW5kKChwdHMgLSBsYXN0UFRTKSAvIHNjYWxlRmFjdG9yKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGlmIChjb250aWd1b3VzICYmIHRyYWNrLnNlZ21lbnRDb2RlYyA9PT0gJ2FhYycpIHtcbiAgICAgICAgICAvLyBzZXQgUFRTL0RUUyB0byBleHBlY3RlZCBQVFMvRFRTXG4gICAgICAgICAgcHRzID0gbmV4dEF1ZGlvUHRzO1xuICAgICAgICB9XG4gICAgICAgIC8vIHJlbWVtYmVyIGZpcnN0IFBUUyBvZiBvdXIgYXVkaW9TYW1wbGVzXG4gICAgICAgIGZpcnN0UFRTID0gcHRzO1xuICAgICAgICBpZiAobWRhdFNpemUgPiAwKSB7XG4gICAgICAgICAgLyogY29uY2F0ZW5hdGUgdGhlIGF1ZGlvIGRhdGEgYW5kIGNvbnN0cnVjdCB0aGUgbWRhdCBpbiBwbGFjZVxuICAgICAgICAgICAgKG5lZWQgOCBtb3JlIGJ5dGVzIHRvIGZpbGwgbGVuZ3RoIGFuZCBtZGF0IHR5cGUpICovXG4gICAgICAgICAgbWRhdFNpemUgKz0gb2Zmc2V0O1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBtZGF0ID0gbmV3IFVpbnQ4QXJyYXkobWRhdFNpemUpO1xuICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgdGhpcy5vYnNlcnZlci5lbWl0KEV2ZW50cy5FUlJPUiwgRXZlbnRzLkVSUk9SLCB7XG4gICAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTVVYX0VSUk9SLFxuICAgICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuUkVNVVhfQUxMT0NfRVJST1IsXG4gICAgICAgICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgICAgICAgZXJyb3I6IGVycixcbiAgICAgICAgICAgICAgYnl0ZXM6IG1kYXRTaXplLFxuICAgICAgICAgICAgICByZWFzb246IGBmYWlsIGFsbG9jYXRpbmcgYXVkaW8gbWRhdCAke21kYXRTaXplfWBcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoIXJhd01QRUcpIHtcbiAgICAgICAgICAgIGNvbnN0IHZpZXcgPSBuZXcgRGF0YVZpZXcobWRhdC5idWZmZXIpO1xuICAgICAgICAgICAgdmlldy5zZXRVaW50MzIoMCwgbWRhdFNpemUpO1xuICAgICAgICAgICAgbWRhdC5zZXQoTVA0LnR5cGVzLm1kYXQsIDQpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBubyBhdWRpbyBzYW1wbGVzXG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBtZGF0LnNldCh1bml0LCBvZmZzZXQpO1xuICAgICAgY29uc3QgdW5pdExlbiA9IHVuaXQuYnl0ZUxlbmd0aDtcbiAgICAgIG9mZnNldCArPSB1bml0TGVuO1xuICAgICAgLy8gRGVmYXVsdCB0aGUgc2FtcGxlJ3MgZHVyYXRpb24gdG8gdGhlIGNvbXB1dGVkIG1wNFNhbXBsZUR1cmF0aW9uLCB3aGljaCB3aWxsIGVpdGhlciBiZSAxMDI0IGZvciBBQUMgb3IgMTE1MiBmb3IgTVBFR1xuICAgICAgLy8gSW4gdGhlIGNhc2UgdGhhdCB3ZSBoYXZlIDEgc2FtcGxlLCB0aGlzIHdpbGwgYmUgdGhlIGR1cmF0aW9uLiBJZiB3ZSBoYXZlIG1vcmUgdGhhbiBvbmUgc2FtcGxlLCB0aGUgZHVyYXRpb25cbiAgICAgIC8vIGJlY29tZXMgdGhlIFBUUyBkaWZmIHdpdGggdGhlIHByZXZpb3VzIHNhbXBsZVxuICAgICAgb3V0cHV0U2FtcGxlcy5wdXNoKG5ldyBNcDRTYW1wbGUodHJ1ZSwgbXA0U2FtcGxlRHVyYXRpb24sIHVuaXRMZW4sIDApKTtcbiAgICAgIGxhc3RQVFMgPSBwdHM7XG4gICAgfVxuXG4gICAgLy8gV2UgY291bGQgZW5kIHVwIHdpdGggbm8gYXVkaW8gc2FtcGxlcyBpZiBhbGwgaW5wdXQgc2FtcGxlcyB3ZXJlIG92ZXJsYXBwaW5nIHdpdGggdGhlIHByZXZpb3VzbHkgcmVtdXhlZCBvbmVzXG4gICAgY29uc3QgbmJTYW1wbGVzID0gb3V0cHV0U2FtcGxlcy5sZW5ndGg7XG4gICAgaWYgKCFuYlNhbXBsZXMpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBUaGUgbmV4dCBhdWRpbyBzYW1wbGUgUFRTIHNob3VsZCBiZSBlcXVhbCB0byBsYXN0IHNhbXBsZSBQVFMgKyBkdXJhdGlvblxuICAgIGNvbnN0IGxhc3RTYW1wbGUgPSBvdXRwdXRTYW1wbGVzW291dHB1dFNhbXBsZXMubGVuZ3RoIC0gMV07XG4gICAgdGhpcy5uZXh0QXVkaW9QdHMgPSBuZXh0QXVkaW9QdHMgPSBsYXN0UFRTICsgc2NhbGVGYWN0b3IgKiBsYXN0U2FtcGxlLmR1cmF0aW9uO1xuXG4gICAgLy8gU2V0IHRoZSB0cmFjayBzYW1wbGVzIGZyb20gaW5wdXRTYW1wbGVzIHRvIG91dHB1dFNhbXBsZXMgYmVmb3JlIHJlbXV4aW5nXG4gICAgY29uc3QgbW9vZiA9IHJhd01QRUcgPyBuZXcgVWludDhBcnJheSgwKSA6IE1QNC5tb29mKHRyYWNrLnNlcXVlbmNlTnVtYmVyKyssIGZpcnN0UFRTIC8gc2NhbGVGYWN0b3IsIF9leHRlbmRzKHt9LCB0cmFjaywge1xuICAgICAgc2FtcGxlczogb3V0cHV0U2FtcGxlc1xuICAgIH0pKTtcblxuICAgIC8vIENsZWFyIHRoZSB0cmFjayBzYW1wbGVzLiBUaGlzIGFsc28gY2xlYXJzIHRoZSBzYW1wbGVzIGFycmF5IGluIHRoZSBkZW11eGVyLCBzaW5jZSB0aGUgcmVmZXJlbmNlIGlzIHNoYXJlZFxuICAgIHRyYWNrLnNhbXBsZXMgPSBbXTtcbiAgICBjb25zdCBzdGFydCA9IGZpcnN0UFRTIC8gaW5wdXRUaW1lU2NhbGU7XG4gICAgY29uc3QgZW5kID0gbmV4dEF1ZGlvUHRzIC8gaW5wdXRUaW1lU2NhbGU7XG4gICAgY29uc3QgdHlwZSA9ICdhdWRpbyc7XG4gICAgY29uc3QgYXVkaW9EYXRhID0ge1xuICAgICAgZGF0YTE6IG1vb2YsXG4gICAgICBkYXRhMjogbWRhdCxcbiAgICAgIHN0YXJ0UFRTOiBzdGFydCxcbiAgICAgIGVuZFBUUzogZW5kLFxuICAgICAgc3RhcnREVFM6IHN0YXJ0LFxuICAgICAgZW5kRFRTOiBlbmQsXG4gICAgICB0eXBlLFxuICAgICAgaGFzQXVkaW86IHRydWUsXG4gICAgICBoYXNWaWRlbzogZmFsc2UsXG4gICAgICBuYjogbmJTYW1wbGVzXG4gICAgfTtcbiAgICB0aGlzLmlzQXVkaW9Db250aWd1b3VzID0gdHJ1ZTtcbiAgICByZXR1cm4gYXVkaW9EYXRhO1xuICB9XG4gIHJlbXV4RW1wdHlBdWRpbyh0cmFjaywgdGltZU9mZnNldCwgY29udGlndW91cywgdmlkZW9EYXRhKSB7XG4gICAgY29uc3QgaW5wdXRUaW1lU2NhbGUgPSB0cmFjay5pbnB1dFRpbWVTY2FsZTtcbiAgICBjb25zdCBtcDR0aW1lU2NhbGUgPSB0cmFjay5zYW1wbGVyYXRlID8gdHJhY2suc2FtcGxlcmF0ZSA6IGlucHV0VGltZVNjYWxlO1xuICAgIGNvbnN0IHNjYWxlRmFjdG9yID0gaW5wdXRUaW1lU2NhbGUgLyBtcDR0aW1lU2NhbGU7XG4gICAgY29uc3QgbmV4dEF1ZGlvUHRzID0gdGhpcy5uZXh0QXVkaW9QdHM7XG4gICAgLy8gc3luYyB3aXRoIHZpZGVvJ3MgdGltZXN0YW1wXG4gICAgY29uc3QgaW5pdERUUyA9IHRoaXMuX2luaXREVFM7XG4gICAgY29uc3QgaW5pdDkwa0h6ID0gaW5pdERUUy5iYXNlVGltZSAqIDkwMDAwIC8gaW5pdERUUy50aW1lc2NhbGU7XG4gICAgY29uc3Qgc3RhcnREVFMgPSAobmV4dEF1ZGlvUHRzICE9PSBudWxsID8gbmV4dEF1ZGlvUHRzIDogdmlkZW9EYXRhLnN0YXJ0RFRTICogaW5wdXRUaW1lU2NhbGUpICsgaW5pdDkwa0h6O1xuICAgIGNvbnN0IGVuZERUUyA9IHZpZGVvRGF0YS5lbmREVFMgKiBpbnB1dFRpbWVTY2FsZSArIGluaXQ5MGtIejtcbiAgICAvLyBvbmUgc2FtcGxlJ3MgZHVyYXRpb24gdmFsdWVcbiAgICBjb25zdCBmcmFtZUR1cmF0aW9uID0gc2NhbGVGYWN0b3IgKiBBQUNfU0FNUExFU19QRVJfRlJBTUU7XG4gICAgLy8gc2FtcGxlcyBjb3VudCBvZiB0aGlzIHNlZ21lbnQncyBkdXJhdGlvblxuICAgIGNvbnN0IG5iU2FtcGxlcyA9IE1hdGguY2VpbCgoZW5kRFRTIC0gc3RhcnREVFMpIC8gZnJhbWVEdXJhdGlvbik7XG4gICAgLy8gc2lsZW50IGZyYW1lXG4gICAgY29uc3Qgc2lsZW50RnJhbWUgPSBBQUMuZ2V0U2lsZW50RnJhbWUodHJhY2subWFuaWZlc3RDb2RlYyB8fCB0cmFjay5jb2RlYywgdHJhY2suY2hhbm5lbENvdW50KTtcbiAgICBsb2dnZXIud2FybignW21wNC1yZW11eGVyXTogcmVtdXggZW1wdHkgQXVkaW8nKTtcbiAgICAvLyBDYW4ndCByZW11eCBpZiB3ZSBjYW4ndCBnZW5lcmF0ZSBhIHNpbGVudCBmcmFtZS4uLlxuICAgIGlmICghc2lsZW50RnJhbWUpIHtcbiAgICAgIGxvZ2dlci50cmFjZSgnW21wNC1yZW11eGVyXTogVW5hYmxlIHRvIHJlbXV4RW1wdHlBdWRpbyBzaW5jZSB3ZSB3ZXJlIHVuYWJsZSB0byBnZXQgYSBzaWxlbnQgZnJhbWUgZm9yIGdpdmVuIGF1ZGlvIGNvZGVjJyk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHNhbXBsZXMgPSBbXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IG5iU2FtcGxlczsgaSsrKSB7XG4gICAgICBjb25zdCBzdGFtcCA9IHN0YXJ0RFRTICsgaSAqIGZyYW1lRHVyYXRpb247XG4gICAgICBzYW1wbGVzLnB1c2goe1xuICAgICAgICB1bml0OiBzaWxlbnRGcmFtZSxcbiAgICAgICAgcHRzOiBzdGFtcCxcbiAgICAgICAgZHRzOiBzdGFtcFxuICAgICAgfSk7XG4gICAgfVxuICAgIHRyYWNrLnNhbXBsZXMgPSBzYW1wbGVzO1xuICAgIHJldHVybiB0aGlzLnJlbXV4QXVkaW8odHJhY2ssIHRpbWVPZmZzZXQsIGNvbnRpZ3VvdXMsIGZhbHNlKTtcbiAgfVxufVxuZnVuY3Rpb24gbm9ybWFsaXplUHRzKHZhbHVlLCByZWZlcmVuY2UpIHtcbiAgbGV0IG9mZnNldDtcbiAgaWYgKHJlZmVyZW5jZSA9PT0gbnVsbCkge1xuICAgIHJldHVybiB2YWx1ZTtcbiAgfVxuICBpZiAocmVmZXJlbmNlIDwgdmFsdWUpIHtcbiAgICAvLyAtIDJeMzNcbiAgICBvZmZzZXQgPSAtODU4OTkzNDU5MjtcbiAgfSBlbHNlIHtcbiAgICAvLyArIDJeMzNcbiAgICBvZmZzZXQgPSA4NTg5OTM0NTkyO1xuICB9XG4gIC8qIFBUUyBpcyAzM2JpdCAoZnJvbSAwIHRvIDJeMzMgLTEpXG4gICAgaWYgZGlmZiBiZXR3ZWVuIHZhbHVlIGFuZCByZWZlcmVuY2UgaXMgYmlnZ2VyIHRoYW4gaGFsZiBvZiB0aGUgYW1wbGl0dWRlICgyXjMyKSB0aGVuIGl0IG1lYW5zIHRoYXRcbiAgICBQVFMgbG9vcGluZyBvY2N1cmVkLiBmaWxsIHRoZSBnYXAgKi9cbiAgd2hpbGUgKE1hdGguYWJzKHZhbHVlIC0gcmVmZXJlbmNlKSA+IDQyOTQ5NjcyOTYpIHtcbiAgICB2YWx1ZSArPSBvZmZzZXQ7XG4gIH1cbiAgcmV0dXJuIHZhbHVlO1xufVxuZnVuY3Rpb24gZmluZEtleWZyYW1lSW5kZXgoc2FtcGxlcykge1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHNhbXBsZXMubGVuZ3RoOyBpKyspIHtcbiAgICBpZiAoc2FtcGxlc1tpXS5rZXkpIHtcbiAgICAgIHJldHVybiBpO1xuICAgIH1cbiAgfVxuICByZXR1cm4gLTE7XG59XG5mdW5jdGlvbiBmbHVzaFRleHRUcmFja01ldGFkYXRhQ3VlU2FtcGxlcyh0cmFjaywgdGltZU9mZnNldCwgaW5pdFBUUywgaW5pdERUUykge1xuICBjb25zdCBsZW5ndGggPSB0cmFjay5zYW1wbGVzLmxlbmd0aDtcbiAgaWYgKCFsZW5ndGgpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgY29uc3QgaW5wdXRUaW1lU2NhbGUgPSB0cmFjay5pbnB1dFRpbWVTY2FsZTtcbiAgZm9yIChsZXQgaW5kZXggPSAwOyBpbmRleCA8IGxlbmd0aDsgaW5kZXgrKykge1xuICAgIGNvbnN0IHNhbXBsZSA9IHRyYWNrLnNhbXBsZXNbaW5kZXhdO1xuICAgIC8vIHNldHRpbmcgaWQzIHB0cywgZHRzIHRvIHJlbGF0aXZlIHRpbWVcbiAgICAvLyB1c2luZyB0aGlzLl9pbml0UFRTIGFuZCB0aGlzLl9pbml0RFRTIHRvIGNhbGN1bGF0ZSByZWxhdGl2ZSB0aW1lXG4gICAgc2FtcGxlLnB0cyA9IG5vcm1hbGl6ZVB0cyhzYW1wbGUucHRzIC0gaW5pdFBUUy5iYXNlVGltZSAqIGlucHV0VGltZVNjYWxlIC8gaW5pdFBUUy50aW1lc2NhbGUsIHRpbWVPZmZzZXQgKiBpbnB1dFRpbWVTY2FsZSkgLyBpbnB1dFRpbWVTY2FsZTtcbiAgICBzYW1wbGUuZHRzID0gbm9ybWFsaXplUHRzKHNhbXBsZS5kdHMgLSBpbml0RFRTLmJhc2VUaW1lICogaW5wdXRUaW1lU2NhbGUgLyBpbml0RFRTLnRpbWVzY2FsZSwgdGltZU9mZnNldCAqIGlucHV0VGltZVNjYWxlKSAvIGlucHV0VGltZVNjYWxlO1xuICB9XG4gIGNvbnN0IHNhbXBsZXMgPSB0cmFjay5zYW1wbGVzO1xuICB0cmFjay5zYW1wbGVzID0gW107XG4gIHJldHVybiB7XG4gICAgc2FtcGxlc1xuICB9O1xufVxuZnVuY3Rpb24gZmx1c2hUZXh0VHJhY2tVc2VyZGF0YUN1ZVNhbXBsZXModHJhY2ssIHRpbWVPZmZzZXQsIGluaXRQVFMpIHtcbiAgY29uc3QgbGVuZ3RoID0gdHJhY2suc2FtcGxlcy5sZW5ndGg7XG4gIGlmICghbGVuZ3RoKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIGNvbnN0IGlucHV0VGltZVNjYWxlID0gdHJhY2suaW5wdXRUaW1lU2NhbGU7XG4gIGZvciAobGV0IGluZGV4ID0gMDsgaW5kZXggPCBsZW5ndGg7IGluZGV4KyspIHtcbiAgICBjb25zdCBzYW1wbGUgPSB0cmFjay5zYW1wbGVzW2luZGV4XTtcbiAgICAvLyBzZXR0aW5nIHRleHQgcHRzLCBkdHMgdG8gcmVsYXRpdmUgdGltZVxuICAgIC8vIHVzaW5nIHRoaXMuX2luaXRQVFMgYW5kIHRoaXMuX2luaXREVFMgdG8gY2FsY3VsYXRlIHJlbGF0aXZlIHRpbWVcbiAgICBzYW1wbGUucHRzID0gbm9ybWFsaXplUHRzKHNhbXBsZS5wdHMgLSBpbml0UFRTLmJhc2VUaW1lICogaW5wdXRUaW1lU2NhbGUgLyBpbml0UFRTLnRpbWVzY2FsZSwgdGltZU9mZnNldCAqIGlucHV0VGltZVNjYWxlKSAvIGlucHV0VGltZVNjYWxlO1xuICB9XG4gIHRyYWNrLnNhbXBsZXMuc29ydCgoYSwgYikgPT4gYS5wdHMgLSBiLnB0cyk7XG4gIGNvbnN0IHNhbXBsZXMgPSB0cmFjay5zYW1wbGVzO1xuICB0cmFjay5zYW1wbGVzID0gW107XG4gIHJldHVybiB7XG4gICAgc2FtcGxlc1xuICB9O1xufVxuY2xhc3MgTXA0U2FtcGxlIHtcbiAgY29uc3RydWN0b3IoaXNLZXlmcmFtZSwgZHVyYXRpb24sIHNpemUsIGN0cykge1xuICAgIHRoaXMuc2l6ZSA9IHZvaWQgMDtcbiAgICB0aGlzLmR1cmF0aW9uID0gdm9pZCAwO1xuICAgIHRoaXMuY3RzID0gdm9pZCAwO1xuICAgIHRoaXMuZmxhZ3MgPSB2b2lkIDA7XG4gICAgdGhpcy5kdXJhdGlvbiA9IGR1cmF0aW9uO1xuICAgIHRoaXMuc2l6ZSA9IHNpemU7XG4gICAgdGhpcy5jdHMgPSBjdHM7XG4gICAgdGhpcy5mbGFncyA9IHtcbiAgICAgIGlzTGVhZGluZzogMCxcbiAgICAgIGlzRGVwZW5kZWRPbjogMCxcbiAgICAgIGhhc1JlZHVuZGFuY3k6IDAsXG4gICAgICBkZWdyYWRQcmlvOiAwLFxuICAgICAgZGVwZW5kc09uOiBpc0tleWZyYW1lID8gMiA6IDEsXG4gICAgICBpc05vblN5bmM6IGlzS2V5ZnJhbWUgPyAwIDogMVxuICAgIH07XG4gIH1cbn1cblxuY2xhc3MgUGFzc1Rocm91Z2hSZW11eGVyIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy5lbWl0SW5pdFNlZ21lbnQgPSBmYWxzZTtcbiAgICB0aGlzLmF1ZGlvQ29kZWMgPSB2b2lkIDA7XG4gICAgdGhpcy52aWRlb0NvZGVjID0gdm9pZCAwO1xuICAgIHRoaXMuaW5pdERhdGEgPSB2b2lkIDA7XG4gICAgdGhpcy5pbml0UFRTID0gbnVsbDtcbiAgICB0aGlzLmluaXRUcmFja3MgPSB2b2lkIDA7XG4gICAgdGhpcy5sYXN0RW5kVGltZSA9IG51bGw7XG4gIH1cbiAgZGVzdHJveSgpIHt9XG4gIHJlc2V0VGltZVN0YW1wKGRlZmF1bHRJbml0UFRTKSB7XG4gICAgdGhpcy5pbml0UFRTID0gZGVmYXVsdEluaXRQVFM7XG4gICAgdGhpcy5sYXN0RW5kVGltZSA9IG51bGw7XG4gIH1cbiAgcmVzZXROZXh0VGltZXN0YW1wKCkge1xuICAgIHRoaXMubGFzdEVuZFRpbWUgPSBudWxsO1xuICB9XG4gIHJlc2V0SW5pdFNlZ21lbnQoaW5pdFNlZ21lbnQsIGF1ZGlvQ29kZWMsIHZpZGVvQ29kZWMsIGRlY3J5cHRkYXRhKSB7XG4gICAgdGhpcy5hdWRpb0NvZGVjID0gYXVkaW9Db2RlYztcbiAgICB0aGlzLnZpZGVvQ29kZWMgPSB2aWRlb0NvZGVjO1xuICAgIHRoaXMuZ2VuZXJhdGVJbml0U2VnbWVudChwYXRjaEVuY3lwdGlvbkRhdGEoaW5pdFNlZ21lbnQsIGRlY3J5cHRkYXRhKSk7XG4gICAgdGhpcy5lbWl0SW5pdFNlZ21lbnQgPSB0cnVlO1xuICB9XG4gIGdlbmVyYXRlSW5pdFNlZ21lbnQoaW5pdFNlZ21lbnQpIHtcbiAgICBsZXQge1xuICAgICAgYXVkaW9Db2RlYyxcbiAgICAgIHZpZGVvQ29kZWNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIShpbml0U2VnbWVudCAhPSBudWxsICYmIGluaXRTZWdtZW50LmJ5dGVMZW5ndGgpKSB7XG4gICAgICB0aGlzLmluaXRUcmFja3MgPSB1bmRlZmluZWQ7XG4gICAgICB0aGlzLmluaXREYXRhID0gdW5kZWZpbmVkO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBpbml0RGF0YSA9IHRoaXMuaW5pdERhdGEgPSBwYXJzZUluaXRTZWdtZW50KGluaXRTZWdtZW50KTtcblxuICAgIC8vIEdldCBjb2RlYyBmcm9tIGluaXRTZWdtZW50IG9yIGZhbGxiYWNrIHRvIGRlZmF1bHRcbiAgICBpZiAoaW5pdERhdGEuYXVkaW8pIHtcbiAgICAgIGF1ZGlvQ29kZWMgPSBnZXRQYXJzZWRUcmFja0NvZGVjKGluaXREYXRhLmF1ZGlvLCBFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU8pO1xuICAgIH1cbiAgICBpZiAoaW5pdERhdGEudmlkZW8pIHtcbiAgICAgIHZpZGVvQ29kZWMgPSBnZXRQYXJzZWRUcmFja0NvZGVjKGluaXREYXRhLnZpZGVvLCBFbGVtZW50YXJ5U3RyZWFtVHlwZXMuVklERU8pO1xuICAgIH1cbiAgICBjb25zdCB0cmFja3MgPSB7fTtcbiAgICBpZiAoaW5pdERhdGEuYXVkaW8gJiYgaW5pdERhdGEudmlkZW8pIHtcbiAgICAgIHRyYWNrcy5hdWRpb3ZpZGVvID0ge1xuICAgICAgICBjb250YWluZXI6ICd2aWRlby9tcDQnLFxuICAgICAgICBjb2RlYzogYXVkaW9Db2RlYyArICcsJyArIHZpZGVvQ29kZWMsXG4gICAgICAgIGluaXRTZWdtZW50LFxuICAgICAgICBpZDogJ21haW4nXG4gICAgICB9O1xuICAgIH0gZWxzZSBpZiAoaW5pdERhdGEuYXVkaW8pIHtcbiAgICAgIHRyYWNrcy5hdWRpbyA9IHtcbiAgICAgICAgY29udGFpbmVyOiAnYXVkaW8vbXA0JyxcbiAgICAgICAgY29kZWM6IGF1ZGlvQ29kZWMsXG4gICAgICAgIGluaXRTZWdtZW50LFxuICAgICAgICBpZDogJ2F1ZGlvJ1xuICAgICAgfTtcbiAgICB9IGVsc2UgaWYgKGluaXREYXRhLnZpZGVvKSB7XG4gICAgICB0cmFja3MudmlkZW8gPSB7XG4gICAgICAgIGNvbnRhaW5lcjogJ3ZpZGVvL21wNCcsXG4gICAgICAgIGNvZGVjOiB2aWRlb0NvZGVjLFxuICAgICAgICBpbml0U2VnbWVudCxcbiAgICAgICAgaWQ6ICdtYWluJ1xuICAgICAgfTtcbiAgICB9IGVsc2Uge1xuICAgICAgbG9nZ2VyLndhcm4oJ1twYXNzdGhyb3VnaC1yZW11eGVyLnRzXTogaW5pdFNlZ21lbnQgZG9lcyBub3QgY29udGFpbiBtb292IG9yIHRyYWsgYm94ZXMuJyk7XG4gICAgfVxuICAgIHRoaXMuaW5pdFRyYWNrcyA9IHRyYWNrcztcbiAgfVxuICByZW11eChhdWRpb1RyYWNrLCB2aWRlb1RyYWNrLCBpZDNUcmFjaywgdGV4dFRyYWNrLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQpIHtcbiAgICB2YXIgX2luaXREYXRhLCBfaW5pdERhdGEyO1xuICAgIGxldCB7XG4gICAgICBpbml0UFRTLFxuICAgICAgbGFzdEVuZFRpbWVcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCByZXN1bHQgPSB7XG4gICAgICBhdWRpbzogdW5kZWZpbmVkLFxuICAgICAgdmlkZW86IHVuZGVmaW5lZCxcbiAgICAgIHRleHQ6IHRleHRUcmFjayxcbiAgICAgIGlkMzogaWQzVHJhY2ssXG4gICAgICBpbml0U2VnbWVudDogdW5kZWZpbmVkXG4gICAgfTtcblxuICAgIC8vIElmIHdlIGhhdmVuJ3QgeWV0IHNldCBhIGxhc3RFbmREVFMsIG9yIGl0IHdhcyByZXNldCwgc2V0IGl0IHRvIHRoZSBwcm92aWRlZCB0aW1lT2Zmc2V0LiBXZSB3YW50IHRvIHVzZSB0aGVcbiAgICAvLyBsYXN0RW5kRFRTIG92ZXIgdGltZU9mZnNldCB3aGVuZXZlciBwb3NzaWJsZTsgZHVyaW5nIHByb2dyZXNzaXZlIHBsYXliYWNrLCB0aGUgbWVkaWEgc291cmNlIHdpbGwgbm90IHVwZGF0ZVxuICAgIC8vIHRoZSBtZWRpYSBkdXJhdGlvbiAod2hpY2ggaXMgd2hhdCB0aW1lT2Zmc2V0IGlzIHByb3ZpZGVkIGFzKSBiZWZvcmUgd2UgbmVlZCB0byBwcm9jZXNzIHRoZSBuZXh0IGNodW5rLlxuICAgIGlmICghaXNGaW5pdGVOdW1iZXIobGFzdEVuZFRpbWUpKSB7XG4gICAgICBsYXN0RW5kVGltZSA9IHRoaXMubGFzdEVuZFRpbWUgPSB0aW1lT2Zmc2V0IHx8IDA7XG4gICAgfVxuXG4gICAgLy8gVGhlIGJpbmFyeSBzZWdtZW50IGRhdGEgaXMgYWRkZWQgdG8gdGhlIHZpZGVvVHJhY2sgaW4gdGhlIG1wNGRlbXV4ZXIuIFdlIGRvbid0IGNoZWNrIHRvIHNlZSBpZiB0aGUgZGF0YSBpcyBvbmx5XG4gICAgLy8gYXVkaW8gb3IgdmlkZW8gKG9yIGJvdGgpOyBhZGRpbmcgaXQgdG8gdmlkZW8gd2FzIGFuIGFyYml0cmFyeSBjaG9pY2UuXG4gICAgY29uc3QgZGF0YSA9IHZpZGVvVHJhY2suc2FtcGxlcztcbiAgICBpZiAoIShkYXRhICE9IG51bGwgJiYgZGF0YS5sZW5ndGgpKSB7XG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cbiAgICBjb25zdCBpbml0U2VnbWVudCA9IHtcbiAgICAgIGluaXRQVFM6IHVuZGVmaW5lZCxcbiAgICAgIHRpbWVzY2FsZTogMVxuICAgIH07XG4gICAgbGV0IGluaXREYXRhID0gdGhpcy5pbml0RGF0YTtcbiAgICBpZiAoISgoX2luaXREYXRhID0gaW5pdERhdGEpICE9IG51bGwgJiYgX2luaXREYXRhLmxlbmd0aCkpIHtcbiAgICAgIHRoaXMuZ2VuZXJhdGVJbml0U2VnbWVudChkYXRhKTtcbiAgICAgIGluaXREYXRhID0gdGhpcy5pbml0RGF0YTtcbiAgICB9XG4gICAgaWYgKCEoKF9pbml0RGF0YTIgPSBpbml0RGF0YSkgIT0gbnVsbCAmJiBfaW5pdERhdGEyLmxlbmd0aCkpIHtcbiAgICAgIC8vIFdlIGNhbid0IHJlbXV4IGlmIHRoZSBpbml0U2VnbWVudCBjb3VsZCBub3QgYmUgZ2VuZXJhdGVkXG4gICAgICBsb2dnZXIud2FybignW3Bhc3N0aHJvdWdoLXJlbXV4ZXIudHNdOiBGYWlsZWQgdG8gZ2VuZXJhdGUgaW5pdFNlZ21lbnQuJyk7XG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH1cbiAgICBpZiAodGhpcy5lbWl0SW5pdFNlZ21lbnQpIHtcbiAgICAgIGluaXRTZWdtZW50LnRyYWNrcyA9IHRoaXMuaW5pdFRyYWNrcztcbiAgICAgIHRoaXMuZW1pdEluaXRTZWdtZW50ID0gZmFsc2U7XG4gICAgfVxuICAgIGNvbnN0IGR1cmF0aW9uID0gZ2V0RHVyYXRpb24oZGF0YSwgaW5pdERhdGEpO1xuICAgIGNvbnN0IHN0YXJ0RFRTID0gZ2V0U3RhcnREVFMoaW5pdERhdGEsIGRhdGEpO1xuICAgIGNvbnN0IGRlY29kZVRpbWUgPSBzdGFydERUUyA9PT0gbnVsbCA/IHRpbWVPZmZzZXQgOiBzdGFydERUUztcbiAgICBpZiAoaXNJbnZhbGlkSW5pdFB0cyhpbml0UFRTLCBkZWNvZGVUaW1lLCB0aW1lT2Zmc2V0LCBkdXJhdGlvbikgfHwgaW5pdFNlZ21lbnQudGltZXNjYWxlICE9PSBpbml0UFRTLnRpbWVzY2FsZSAmJiBhY2N1cmF0ZVRpbWVPZmZzZXQpIHtcbiAgICAgIGluaXRTZWdtZW50LmluaXRQVFMgPSBkZWNvZGVUaW1lIC0gdGltZU9mZnNldDtcbiAgICAgIGlmIChpbml0UFRTICYmIGluaXRQVFMudGltZXNjYWxlID09PSAxKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGBBZGp1c3RpbmcgaW5pdFBUUyBieSAke2luaXRTZWdtZW50LmluaXRQVFMgLSBpbml0UFRTLmJhc2VUaW1lfWApO1xuICAgICAgfVxuICAgICAgdGhpcy5pbml0UFRTID0gaW5pdFBUUyA9IHtcbiAgICAgICAgYmFzZVRpbWU6IGluaXRTZWdtZW50LmluaXRQVFMsXG4gICAgICAgIHRpbWVzY2FsZTogMVxuICAgICAgfTtcbiAgICB9XG4gICAgY29uc3Qgc3RhcnRUaW1lID0gYXVkaW9UcmFjayA/IGRlY29kZVRpbWUgLSBpbml0UFRTLmJhc2VUaW1lIC8gaW5pdFBUUy50aW1lc2NhbGUgOiBsYXN0RW5kVGltZTtcbiAgICBjb25zdCBlbmRUaW1lID0gc3RhcnRUaW1lICsgZHVyYXRpb247XG4gICAgb2Zmc2V0U3RhcnREVFMoaW5pdERhdGEsIGRhdGEsIGluaXRQVFMuYmFzZVRpbWUgLyBpbml0UFRTLnRpbWVzY2FsZSk7XG4gICAgaWYgKGR1cmF0aW9uID4gMCkge1xuICAgICAgdGhpcy5sYXN0RW5kVGltZSA9IGVuZFRpbWU7XG4gICAgfSBlbHNlIHtcbiAgICAgIGxvZ2dlci53YXJuKCdEdXJhdGlvbiBwYXJzZWQgZnJvbSBtcDQgc2hvdWxkIGJlIGdyZWF0ZXIgdGhhbiB6ZXJvJyk7XG4gICAgICB0aGlzLnJlc2V0TmV4dFRpbWVzdGFtcCgpO1xuICAgIH1cbiAgICBjb25zdCBoYXNBdWRpbyA9ICEhaW5pdERhdGEuYXVkaW87XG4gICAgY29uc3QgaGFzVmlkZW8gPSAhIWluaXREYXRhLnZpZGVvO1xuICAgIGxldCB0eXBlID0gJyc7XG4gICAgaWYgKGhhc0F1ZGlvKSB7XG4gICAgICB0eXBlICs9ICdhdWRpbyc7XG4gICAgfVxuICAgIGlmIChoYXNWaWRlbykge1xuICAgICAgdHlwZSArPSAndmlkZW8nO1xuICAgIH1cbiAgICBjb25zdCB0cmFjayA9IHtcbiAgICAgIGRhdGExOiBkYXRhLFxuICAgICAgc3RhcnRQVFM6IHN0YXJ0VGltZSxcbiAgICAgIHN0YXJ0RFRTOiBzdGFydFRpbWUsXG4gICAgICBlbmRQVFM6IGVuZFRpbWUsXG4gICAgICBlbmREVFM6IGVuZFRpbWUsXG4gICAgICB0eXBlLFxuICAgICAgaGFzQXVkaW8sXG4gICAgICBoYXNWaWRlbyxcbiAgICAgIG5iOiAxLFxuICAgICAgZHJvcHBlZDogMFxuICAgIH07XG4gICAgcmVzdWx0LmF1ZGlvID0gdHJhY2sudHlwZSA9PT0gJ2F1ZGlvJyA/IHRyYWNrIDogdW5kZWZpbmVkO1xuICAgIHJlc3VsdC52aWRlbyA9IHRyYWNrLnR5cGUgIT09ICdhdWRpbycgPyB0cmFjayA6IHVuZGVmaW5lZDtcbiAgICByZXN1bHQuaW5pdFNlZ21lbnQgPSBpbml0U2VnbWVudDtcbiAgICByZXN1bHQuaWQzID0gZmx1c2hUZXh0VHJhY2tNZXRhZGF0YUN1ZVNhbXBsZXMoaWQzVHJhY2ssIHRpbWVPZmZzZXQsIGluaXRQVFMsIGluaXRQVFMpO1xuICAgIGlmICh0ZXh0VHJhY2suc2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgIHJlc3VsdC50ZXh0ID0gZmx1c2hUZXh0VHJhY2tVc2VyZGF0YUN1ZVNhbXBsZXModGV4dFRyYWNrLCB0aW1lT2Zmc2V0LCBpbml0UFRTKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxufVxuZnVuY3Rpb24gaXNJbnZhbGlkSW5pdFB0cyhpbml0UFRTLCBzdGFydERUUywgdGltZU9mZnNldCwgZHVyYXRpb24pIHtcbiAgaWYgKGluaXRQVFMgPT09IG51bGwpIHtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICAvLyBJbml0UFRTIGlzIGludmFsaWQgd2hlbiBkaXN0YW5jZSBmcm9tIHByb2dyYW0gd291bGQgYmUgbW9yZSB0aGFuIHNlZ21lbnQgZHVyYXRpb24gb3IgYSBtaW5pbXVtIG9mIG9uZSBzZWNvbmRcbiAgY29uc3QgbWluRHVyYXRpb24gPSBNYXRoLm1heChkdXJhdGlvbiwgMSk7XG4gIGNvbnN0IHN0YXJ0VGltZSA9IHN0YXJ0RFRTIC0gaW5pdFBUUy5iYXNlVGltZSAvIGluaXRQVFMudGltZXNjYWxlO1xuICByZXR1cm4gTWF0aC5hYnMoc3RhcnRUaW1lIC0gdGltZU9mZnNldCkgPiBtaW5EdXJhdGlvbjtcbn1cbmZ1bmN0aW9uIGdldFBhcnNlZFRyYWNrQ29kZWModHJhY2ssIHR5cGUpIHtcbiAgY29uc3QgcGFyc2VkQ29kZWMgPSB0cmFjayA9PSBudWxsID8gdm9pZCAwIDogdHJhY2suY29kZWM7XG4gIGlmIChwYXJzZWRDb2RlYyAmJiBwYXJzZWRDb2RlYy5sZW5ndGggPiA0KSB7XG4gICAgcmV0dXJuIHBhcnNlZENvZGVjO1xuICB9XG4gIGlmICh0eXBlID09PSBFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU8pIHtcbiAgICBpZiAocGFyc2VkQ29kZWMgPT09ICdlYy0zJyB8fCBwYXJzZWRDb2RlYyA9PT0gJ2FjLTMnIHx8IHBhcnNlZENvZGVjID09PSAnYWxhYycpIHtcbiAgICAgIHJldHVybiBwYXJzZWRDb2RlYztcbiAgICB9XG4gICAgaWYgKHBhcnNlZENvZGVjID09PSAnZkxhQycgfHwgcGFyc2VkQ29kZWMgPT09ICdPcHVzJykge1xuICAgICAgLy8gT3B0aW5nIG5vdCB0byBnZXQgYHByZWZlck1hbmFnZWRNZWRpYVNvdXJjZWAgZnJvbSBwbGF5ZXIgY29uZmlnIGZvciBpc1N1cHBvcnRlZCgpIGNoZWNrIGZvciBzaW1wbGljaXR5XG4gICAgICBjb25zdCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UgPSBmYWxzZTtcbiAgICAgIHJldHVybiBnZXRDb2RlY0NvbXBhdGlibGVOYW1lKHBhcnNlZENvZGVjLCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UpO1xuICAgIH1cbiAgICBjb25zdCByZXN1bHQgPSAnbXA0YS40MC41JztcbiAgICBsb2dnZXIuaW5mbyhgUGFyc2VkIGF1ZGlvIGNvZGVjIFwiJHtwYXJzZWRDb2RlY31cIiBvciBhdWRpbyBvYmplY3QgdHlwZSBub3QgaGFuZGxlZC4gVXNpbmcgXCIke3Jlc3VsdH1cImApO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgLy8gUHJvdmlkZSBkZWZhdWx0cyBiYXNlZCBvbiBjb2RlYyB0eXBlXG4gIC8vIFRoaXMgYWxsb3dzIGZvciBzb21lIHBsYXliYWNrIG9mIHNvbWUgZm1wNCBwbGF5bGlzdHMgd2l0aG91dCBDT0RFQ1MgZGVmaW5lZCBpbiBtYW5pZmVzdFxuICBsb2dnZXIud2FybihgVW5oYW5kbGVkIHZpZGVvIGNvZGVjIFwiJHtwYXJzZWRDb2RlY31cImApO1xuICBpZiAocGFyc2VkQ29kZWMgPT09ICdodmMxJyB8fCBwYXJzZWRDb2RlYyA9PT0gJ2hldjEnKSB7XG4gICAgcmV0dXJuICdodmMxLjEuNi5MMTIwLjkwJztcbiAgfVxuICBpZiAocGFyc2VkQ29kZWMgPT09ICdhdjAxJykge1xuICAgIHJldHVybiAnYXYwMS4wLjA0TS4wOCc7XG4gIH1cbiAgcmV0dXJuICdhdmMxLjQyZTAxZSc7XG59XG5cbmxldCBub3c7XG4vLyBwZXJmb3JtYW5jZS5ub3coKSBub3QgYXZhaWxhYmxlIG9uIFdlYldvcmtlciwgYXQgbGVhc3Qgb24gU2FmYXJpIERlc2t0b3BcbnRyeSB7XG4gIG5vdyA9IHNlbGYucGVyZm9ybWFuY2Uubm93LmJpbmQoc2VsZi5wZXJmb3JtYW5jZSk7XG59IGNhdGNoIChlcnIpIHtcbiAgbG9nZ2VyLmRlYnVnKCdVbmFibGUgdG8gdXNlIFBlcmZvcm1hbmNlIEFQSSBvbiB0aGlzIGVudmlyb25tZW50Jyk7XG4gIG5vdyA9IG9wdGlvbmFsU2VsZiA9PSBudWxsID8gdm9pZCAwIDogb3B0aW9uYWxTZWxmLkRhdGUubm93O1xufVxuY29uc3QgbXV4Q29uZmlnID0gW3tcbiAgZGVtdXg6IE1QNERlbXV4ZXIsXG4gIHJlbXV4OiBQYXNzVGhyb3VnaFJlbXV4ZXJcbn0sIHtcbiAgZGVtdXg6IFRTRGVtdXhlcixcbiAgcmVtdXg6IE1QNFJlbXV4ZXJcbn0sIHtcbiAgZGVtdXg6IEFBQ0RlbXV4ZXIsXG4gIHJlbXV4OiBNUDRSZW11eGVyXG59LCB7XG4gIGRlbXV4OiBNUDNEZW11eGVyLFxuICByZW11eDogTVA0UmVtdXhlclxufV07XG57XG4gIG11eENvbmZpZy5zcGxpY2UoMiwgMCwge1xuICAgIGRlbXV4OiBBQzNEZW11eGVyLFxuICAgIHJlbXV4OiBNUDRSZW11eGVyXG4gIH0pO1xufVxuY2xhc3MgVHJhbnNtdXhlciB7XG4gIGNvbnN0cnVjdG9yKG9ic2VydmVyLCB0eXBlU3VwcG9ydGVkLCBjb25maWcsIHZlbmRvciwgaWQpIHtcbiAgICB0aGlzLmFzeW5jID0gZmFsc2U7XG4gICAgdGhpcy5vYnNlcnZlciA9IHZvaWQgMDtcbiAgICB0aGlzLnR5cGVTdXBwb3J0ZWQgPSB2b2lkIDA7XG4gICAgdGhpcy5jb25maWcgPSB2b2lkIDA7XG4gICAgdGhpcy52ZW5kb3IgPSB2b2lkIDA7XG4gICAgdGhpcy5pZCA9IHZvaWQgMDtcbiAgICB0aGlzLmRlbXV4ZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5yZW11eGVyID0gdm9pZCAwO1xuICAgIHRoaXMuZGVjcnlwdGVyID0gdm9pZCAwO1xuICAgIHRoaXMucHJvYmUgPSB2b2lkIDA7XG4gICAgdGhpcy5kZWNyeXB0aW9uUHJvbWlzZSA9IG51bGw7XG4gICAgdGhpcy50cmFuc211eENvbmZpZyA9IHZvaWQgMDtcbiAgICB0aGlzLmN1cnJlbnRUcmFuc211eFN0YXRlID0gdm9pZCAwO1xuICAgIHRoaXMub2JzZXJ2ZXIgPSBvYnNlcnZlcjtcbiAgICB0aGlzLnR5cGVTdXBwb3J0ZWQgPSB0eXBlU3VwcG9ydGVkO1xuICAgIHRoaXMuY29uZmlnID0gY29uZmlnO1xuICAgIHRoaXMudmVuZG9yID0gdmVuZG9yO1xuICAgIHRoaXMuaWQgPSBpZDtcbiAgfVxuICBjb25maWd1cmUodHJhbnNtdXhDb25maWcpIHtcbiAgICB0aGlzLnRyYW5zbXV4Q29uZmlnID0gdHJhbnNtdXhDb25maWc7XG4gICAgaWYgKHRoaXMuZGVjcnlwdGVyKSB7XG4gICAgICB0aGlzLmRlY3J5cHRlci5yZXNldCgpO1xuICAgIH1cbiAgfVxuICBwdXNoKGRhdGEsIGRlY3J5cHRkYXRhLCBjaHVua01ldGEsIHN0YXRlKSB7XG4gICAgY29uc3Qgc3RhdHMgPSBjaHVua01ldGEudHJhbnNtdXhpbmc7XG4gICAgc3RhdHMuZXhlY3V0ZVN0YXJ0ID0gbm93KCk7XG4gICAgbGV0IHVpbnREYXRhID0gbmV3IFVpbnQ4QXJyYXkoZGF0YSk7XG4gICAgY29uc3Qge1xuICAgICAgY3VycmVudFRyYW5zbXV4U3RhdGUsXG4gICAgICB0cmFuc211eENvbmZpZ1xuICAgIH0gPSB0aGlzO1xuICAgIGlmIChzdGF0ZSkge1xuICAgICAgdGhpcy5jdXJyZW50VHJhbnNtdXhTdGF0ZSA9IHN0YXRlO1xuICAgIH1cbiAgICBjb25zdCB7XG4gICAgICBjb250aWd1b3VzLFxuICAgICAgZGlzY29udGludWl0eSxcbiAgICAgIHRyYWNrU3dpdGNoLFxuICAgICAgYWNjdXJhdGVUaW1lT2Zmc2V0LFxuICAgICAgdGltZU9mZnNldCxcbiAgICAgIGluaXRTZWdtZW50Q2hhbmdlXG4gICAgfSA9IHN0YXRlIHx8IGN1cnJlbnRUcmFuc211eFN0YXRlO1xuICAgIGNvbnN0IHtcbiAgICAgIGF1ZGlvQ29kZWMsXG4gICAgICB2aWRlb0NvZGVjLFxuICAgICAgZGVmYXVsdEluaXRQdHMsXG4gICAgICBkdXJhdGlvbixcbiAgICAgIGluaXRTZWdtZW50RGF0YVxuICAgIH0gPSB0cmFuc211eENvbmZpZztcbiAgICBjb25zdCBrZXlEYXRhID0gZ2V0RW5jcnlwdGlvblR5cGUodWludERhdGEsIGRlY3J5cHRkYXRhKTtcbiAgICBpZiAoa2V5RGF0YSAmJiBrZXlEYXRhLm1ldGhvZCA9PT0gJ0FFUy0xMjgnKSB7XG4gICAgICBjb25zdCBkZWNyeXB0ZXIgPSB0aGlzLmdldERlY3J5cHRlcigpO1xuICAgICAgLy8gU29mdHdhcmUgZGVjcnlwdGlvbiBpcyBzeW5jaHJvbm91czsgd2ViQ3J5cHRvIGlzIG5vdFxuICAgICAgaWYgKGRlY3J5cHRlci5pc1N5bmMoKSkge1xuICAgICAgICAvLyBTb2Z0d2FyZSBkZWNyeXB0aW9uIGlzIHByb2dyZXNzaXZlLiBQcm9ncmVzc2l2ZSBkZWNyeXB0aW9uIG1heSBub3QgcmV0dXJuIGEgcmVzdWx0IG9uIGVhY2ggY2FsbC4gQW55IGNhY2hlZFxuICAgICAgICAvLyBkYXRhIGlzIGhhbmRsZWQgaW4gdGhlIGZsdXNoKCkgY2FsbFxuICAgICAgICBsZXQgZGVjcnlwdGVkRGF0YSA9IGRlY3J5cHRlci5zb2Z0d2FyZURlY3J5cHQodWludERhdGEsIGtleURhdGEua2V5LmJ1ZmZlciwga2V5RGF0YS5pdi5idWZmZXIpO1xuICAgICAgICAvLyBGb3IgTG93LUxhdGVuY3kgSExTIFBhcnRzLCBkZWNyeXB0IGluIHBsYWNlLCBzaW5jZSBwYXJ0IHBhcnNpbmcgaXMgZXhwZWN0ZWQgb24gcHVzaCBwcm9ncmVzc1xuICAgICAgICBjb25zdCBsb2FkaW5nUGFydHMgPSBjaHVua01ldGEucGFydCA+IC0xO1xuICAgICAgICBpZiAobG9hZGluZ1BhcnRzKSB7XG4gICAgICAgICAgZGVjcnlwdGVkRGF0YSA9IGRlY3J5cHRlci5mbHVzaCgpO1xuICAgICAgICB9XG4gICAgICAgIGlmICghZGVjcnlwdGVkRGF0YSkge1xuICAgICAgICAgIHN0YXRzLmV4ZWN1dGVFbmQgPSBub3coKTtcbiAgICAgICAgICByZXR1cm4gZW1wdHlSZXN1bHQoY2h1bmtNZXRhKTtcbiAgICAgICAgfVxuICAgICAgICB1aW50RGF0YSA9IG5ldyBVaW50OEFycmF5KGRlY3J5cHRlZERhdGEpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5kZWNyeXB0aW9uUHJvbWlzZSA9IGRlY3J5cHRlci53ZWJDcnlwdG9EZWNyeXB0KHVpbnREYXRhLCBrZXlEYXRhLmtleS5idWZmZXIsIGtleURhdGEuaXYuYnVmZmVyKS50aGVuKGRlY3J5cHRlZERhdGEgPT4ge1xuICAgICAgICAgIC8vIENhbGxpbmcgcHVzaCBoZXJlIGlzIGltcG9ydGFudDsgaWYgZmx1c2goKSBpcyBjYWxsZWQgd2hpbGUgdGhpcyBpcyBzdGlsbCByZXNvbHZpbmcsIHRoaXMgZW5zdXJlcyB0aGF0XG4gICAgICAgICAgLy8gdGhlIGRlY3J5cHRlZCBkYXRhIGhhcyBiZWVuIHRyYW5zbXV4ZWRcbiAgICAgICAgICBjb25zdCByZXN1bHQgPSB0aGlzLnB1c2goZGVjcnlwdGVkRGF0YSwgbnVsbCwgY2h1bmtNZXRhKTtcbiAgICAgICAgICB0aGlzLmRlY3J5cHRpb25Qcm9taXNlID0gbnVsbDtcbiAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIHRoaXMuZGVjcnlwdGlvblByb21pc2U7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IHJlc2V0TXV4ZXJzID0gdGhpcy5uZWVkc1Byb2JpbmcoZGlzY29udGludWl0eSwgdHJhY2tTd2l0Y2gpO1xuICAgIGlmIChyZXNldE11eGVycykge1xuICAgICAgY29uc3QgZXJyb3IgPSB0aGlzLmNvbmZpZ3VyZVRyYW5zbXV4ZXIodWludERhdGEpO1xuICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGBbdHJhbnNtdXhlcl0gJHtlcnJvci5tZXNzYWdlfWApO1xuICAgICAgICB0aGlzLm9ic2VydmVyLmVtaXQoRXZlbnRzLkVSUk9SLCBFdmVudHMuRVJST1IsIHtcbiAgICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk1FRElBX0VSUk9SLFxuICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX1BBUlNJTkdfRVJST1IsXG4gICAgICAgICAgZmF0YWw6IGZhbHNlLFxuICAgICAgICAgIGVycm9yLFxuICAgICAgICAgIHJlYXNvbjogZXJyb3IubWVzc2FnZVxuICAgICAgICB9KTtcbiAgICAgICAgc3RhdHMuZXhlY3V0ZUVuZCA9IG5vdygpO1xuICAgICAgICByZXR1cm4gZW1wdHlSZXN1bHQoY2h1bmtNZXRhKTtcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKGRpc2NvbnRpbnVpdHkgfHwgdHJhY2tTd2l0Y2ggfHwgaW5pdFNlZ21lbnRDaGFuZ2UgfHwgcmVzZXRNdXhlcnMpIHtcbiAgICAgIHRoaXMucmVzZXRJbml0U2VnbWVudChpbml0U2VnbWVudERhdGEsIGF1ZGlvQ29kZWMsIHZpZGVvQ29kZWMsIGR1cmF0aW9uLCBkZWNyeXB0ZGF0YSk7XG4gICAgfVxuICAgIGlmIChkaXNjb250aW51aXR5IHx8IGluaXRTZWdtZW50Q2hhbmdlIHx8IHJlc2V0TXV4ZXJzKSB7XG4gICAgICB0aGlzLnJlc2V0SW5pdGlhbFRpbWVzdGFtcChkZWZhdWx0SW5pdFB0cyk7XG4gICAgfVxuICAgIGlmICghY29udGlndW91cykge1xuICAgICAgdGhpcy5yZXNldENvbnRpZ3VpdHkoKTtcbiAgICB9XG4gICAgY29uc3QgcmVzdWx0ID0gdGhpcy50cmFuc211eCh1aW50RGF0YSwga2V5RGF0YSwgdGltZU9mZnNldCwgYWNjdXJhdGVUaW1lT2Zmc2V0LCBjaHVua01ldGEpO1xuICAgIGNvbnN0IGN1cnJlbnRTdGF0ZSA9IHRoaXMuY3VycmVudFRyYW5zbXV4U3RhdGU7XG4gICAgY3VycmVudFN0YXRlLmNvbnRpZ3VvdXMgPSB0cnVlO1xuICAgIGN1cnJlbnRTdGF0ZS5kaXNjb250aW51aXR5ID0gZmFsc2U7XG4gICAgY3VycmVudFN0YXRlLnRyYWNrU3dpdGNoID0gZmFsc2U7XG4gICAgc3RhdHMuZXhlY3V0ZUVuZCA9IG5vdygpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cblxuICAvLyBEdWUgdG8gZGF0YSBjYWNoaW5nLCBmbHVzaCBjYWxscyBjYW4gcHJvZHVjZSBtb3JlIHRoYW4gb25lIFRyYW5zbXV4ZXJSZXN1bHQgKGhlbmNlIHRoZSBBcnJheSB0eXBlKVxuICBmbHVzaChjaHVua01ldGEpIHtcbiAgICBjb25zdCBzdGF0cyA9IGNodW5rTWV0YS50cmFuc211eGluZztcbiAgICBzdGF0cy5leGVjdXRlU3RhcnQgPSBub3coKTtcbiAgICBjb25zdCB7XG4gICAgICBkZWNyeXB0ZXIsXG4gICAgICBjdXJyZW50VHJhbnNtdXhTdGF0ZSxcbiAgICAgIGRlY3J5cHRpb25Qcm9taXNlXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKGRlY3J5cHRpb25Qcm9taXNlKSB7XG4gICAgICAvLyBVcG9uIHJlc29sdXRpb24sIHRoZSBkZWNyeXB0aW9uIHByb21pc2UgY2FsbHMgcHVzaCgpIGFuZCByZXR1cm5zIGl0cyBUcmFuc211eGVyUmVzdWx0IHVwIHRoZSBzdGFjay4gVGhlcmVmb3JlXG4gICAgICAvLyBvbmx5IGZsdXNoaW5nIGlzIHJlcXVpcmVkIGZvciBhc3luYyBkZWNyeXB0aW9uXG4gICAgICByZXR1cm4gZGVjcnlwdGlvblByb21pc2UudGhlbigoKSA9PiB7XG4gICAgICAgIHJldHVybiB0aGlzLmZsdXNoKGNodW5rTWV0YSk7XG4gICAgICB9KTtcbiAgICB9XG4gICAgY29uc3QgdHJhbnNtdXhSZXN1bHRzID0gW107XG4gICAgY29uc3Qge1xuICAgICAgdGltZU9mZnNldFxuICAgIH0gPSBjdXJyZW50VHJhbnNtdXhTdGF0ZTtcbiAgICBpZiAoZGVjcnlwdGVyKSB7XG4gICAgICAvLyBUaGUgZGVjcnlwdGVyIG1heSBoYXZlIGRhdGEgY2FjaGVkLCB3aGljaCBuZWVkcyB0byBiZSBkZW11eGVkLiBJbiB0aGlzIGNhc2Ugd2UnbGwgaGF2ZSB0d28gVHJhbnNtdXhSZXN1bHRzXG4gICAgICAvLyBUaGlzIGhhcHBlbnMgaW4gdGhlIGNhc2UgdGhhdCB3ZSByZWNlaXZlIG9ubHkgMSBwdXNoIGNhbGwgZm9yIGEgc2VnbWVudCAoZWl0aGVyIGZvciBub24tcHJvZ3Jlc3NpdmUgZG93bmxvYWRzLFxuICAgICAgLy8gb3IgZm9yIHByb2dyZXNzaXZlIGRvd25sb2FkcyB3aXRoIHNtYWxsIHNlZ21lbnRzKVxuICAgICAgY29uc3QgZGVjcnlwdGVkRGF0YSA9IGRlY3J5cHRlci5mbHVzaCgpO1xuICAgICAgaWYgKGRlY3J5cHRlZERhdGEpIHtcbiAgICAgICAgLy8gUHVzaCBhbHdheXMgcmV0dXJucyBhIFRyYW5zbXV4ZXJSZXN1bHQgaWYgZGVjcnlwdGRhdGEgaXMgbnVsbFxuICAgICAgICB0cmFuc211eFJlc3VsdHMucHVzaCh0aGlzLnB1c2goZGVjcnlwdGVkRGF0YSwgbnVsbCwgY2h1bmtNZXRhKSk7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGRlbXV4ZXIsXG4gICAgICByZW11eGVyXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFkZW11eGVyIHx8ICFyZW11eGVyKSB7XG4gICAgICAvLyBJZiBwcm9iaW5nIGZhaWxlZCwgdGhlbiBIbHMuanMgaGFzIGJlZW4gZ2l2ZW4gY29udGVudCBpdHMgbm90IGFibGUgdG8gaGFuZGxlXG4gICAgICBzdGF0cy5leGVjdXRlRW5kID0gbm93KCk7XG4gICAgICByZXR1cm4gW2VtcHR5UmVzdWx0KGNodW5rTWV0YSldO1xuICAgIH1cbiAgICBjb25zdCBkZW11eFJlc3VsdE9yUHJvbWlzZSA9IGRlbXV4ZXIuZmx1c2godGltZU9mZnNldCk7XG4gICAgaWYgKGlzUHJvbWlzZShkZW11eFJlc3VsdE9yUHJvbWlzZSkpIHtcbiAgICAgIC8vIERlY3J5cHQgZmluYWwgU0FNUExFLUFFUyBzYW1wbGVzXG4gICAgICByZXR1cm4gZGVtdXhSZXN1bHRPclByb21pc2UudGhlbihkZW11eFJlc3VsdCA9PiB7XG4gICAgICAgIHRoaXMuZmx1c2hSZW11eCh0cmFuc211eFJlc3VsdHMsIGRlbXV4UmVzdWx0LCBjaHVua01ldGEpO1xuICAgICAgICByZXR1cm4gdHJhbnNtdXhSZXN1bHRzO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHRoaXMuZmx1c2hSZW11eCh0cmFuc211eFJlc3VsdHMsIGRlbXV4UmVzdWx0T3JQcm9taXNlLCBjaHVua01ldGEpO1xuICAgIHJldHVybiB0cmFuc211eFJlc3VsdHM7XG4gIH1cbiAgZmx1c2hSZW11eCh0cmFuc211eFJlc3VsdHMsIGRlbXV4UmVzdWx0LCBjaHVua01ldGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBhdWRpb1RyYWNrLFxuICAgICAgdmlkZW9UcmFjayxcbiAgICAgIGlkM1RyYWNrLFxuICAgICAgdGV4dFRyYWNrXG4gICAgfSA9IGRlbXV4UmVzdWx0O1xuICAgIGNvbnN0IHtcbiAgICAgIGFjY3VyYXRlVGltZU9mZnNldCxcbiAgICAgIHRpbWVPZmZzZXRcbiAgICB9ID0gdGhpcy5jdXJyZW50VHJhbnNtdXhTdGF0ZTtcbiAgICBsb2dnZXIubG9nKGBbdHJhbnNtdXhlci50c106IEZsdXNoZWQgZnJhZ21lbnQgJHtjaHVua01ldGEuc259JHtjaHVua01ldGEucGFydCA+IC0xID8gJyBwOiAnICsgY2h1bmtNZXRhLnBhcnQgOiAnJ30gb2YgbGV2ZWwgJHtjaHVua01ldGEubGV2ZWx9YCk7XG4gICAgY29uc3QgcmVtdXhSZXN1bHQgPSB0aGlzLnJlbXV4ZXIucmVtdXgoYXVkaW9UcmFjaywgdmlkZW9UcmFjaywgaWQzVHJhY2ssIHRleHRUcmFjaywgdGltZU9mZnNldCwgYWNjdXJhdGVUaW1lT2Zmc2V0LCB0cnVlLCB0aGlzLmlkKTtcbiAgICB0cmFuc211eFJlc3VsdHMucHVzaCh7XG4gICAgICByZW11eFJlc3VsdCxcbiAgICAgIGNodW5rTWV0YVxuICAgIH0pO1xuICAgIGNodW5rTWV0YS50cmFuc211eGluZy5leGVjdXRlRW5kID0gbm93KCk7XG4gIH1cbiAgcmVzZXRJbml0aWFsVGltZXN0YW1wKGRlZmF1bHRJbml0UHRzKSB7XG4gICAgY29uc3Qge1xuICAgICAgZGVtdXhlcixcbiAgICAgIHJlbXV4ZXJcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIWRlbXV4ZXIgfHwgIXJlbXV4ZXIpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgZGVtdXhlci5yZXNldFRpbWVTdGFtcChkZWZhdWx0SW5pdFB0cyk7XG4gICAgcmVtdXhlci5yZXNldFRpbWVTdGFtcChkZWZhdWx0SW5pdFB0cyk7XG4gIH1cbiAgcmVzZXRDb250aWd1aXR5KCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGRlbXV4ZXIsXG4gICAgICByZW11eGVyXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFkZW11eGVyIHx8ICFyZW11eGVyKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGRlbXV4ZXIucmVzZXRDb250aWd1aXR5KCk7XG4gICAgcmVtdXhlci5yZXNldE5leHRUaW1lc3RhbXAoKTtcbiAgfVxuICByZXNldEluaXRTZWdtZW50KGluaXRTZWdtZW50RGF0YSwgYXVkaW9Db2RlYywgdmlkZW9Db2RlYywgdHJhY2tEdXJhdGlvbiwgZGVjcnlwdGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBkZW11eGVyLFxuICAgICAgcmVtdXhlclxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghZGVtdXhlciB8fCAhcmVtdXhlcikge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBkZW11eGVyLnJlc2V0SW5pdFNlZ21lbnQoaW5pdFNlZ21lbnREYXRhLCBhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCB0cmFja0R1cmF0aW9uKTtcbiAgICByZW11eGVyLnJlc2V0SW5pdFNlZ21lbnQoaW5pdFNlZ21lbnREYXRhLCBhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCBkZWNyeXB0ZGF0YSk7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICBpZiAodGhpcy5kZW11eGVyKSB7XG4gICAgICB0aGlzLmRlbXV4ZXIuZGVzdHJveSgpO1xuICAgICAgdGhpcy5kZW11eGVyID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgICBpZiAodGhpcy5yZW11eGVyKSB7XG4gICAgICB0aGlzLnJlbXV4ZXIuZGVzdHJveSgpO1xuICAgICAgdGhpcy5yZW11eGVyID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuICB0cmFuc211eChkYXRhLCBrZXlEYXRhLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGNodW5rTWV0YSkge1xuICAgIGxldCByZXN1bHQ7XG4gICAgaWYgKGtleURhdGEgJiYga2V5RGF0YS5tZXRob2QgPT09ICdTQU1QTEUtQUVTJykge1xuICAgICAgcmVzdWx0ID0gdGhpcy50cmFuc211eFNhbXBsZUFlcyhkYXRhLCBrZXlEYXRhLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGNodW5rTWV0YSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJlc3VsdCA9IHRoaXMudHJhbnNtdXhVbmVuY3J5cHRlZChkYXRhLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGNodW5rTWV0YSk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cbiAgdHJhbnNtdXhVbmVuY3J5cHRlZChkYXRhLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGNodW5rTWV0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGF1ZGlvVHJhY2ssXG4gICAgICB2aWRlb1RyYWNrLFxuICAgICAgaWQzVHJhY2ssXG4gICAgICB0ZXh0VHJhY2tcbiAgICB9ID0gdGhpcy5kZW11eGVyLmRlbXV4KGRhdGEsIHRpbWVPZmZzZXQsIGZhbHNlLCAhdGhpcy5jb25maWcucHJvZ3Jlc3NpdmUpO1xuICAgIGNvbnN0IHJlbXV4UmVzdWx0ID0gdGhpcy5yZW11eGVyLnJlbXV4KGF1ZGlvVHJhY2ssIHZpZGVvVHJhY2ssIGlkM1RyYWNrLCB0ZXh0VHJhY2ssIHRpbWVPZmZzZXQsIGFjY3VyYXRlVGltZU9mZnNldCwgZmFsc2UsIHRoaXMuaWQpO1xuICAgIHJldHVybiB7XG4gICAgICByZW11eFJlc3VsdCxcbiAgICAgIGNodW5rTWV0YVxuICAgIH07XG4gIH1cbiAgdHJhbnNtdXhTYW1wbGVBZXMoZGF0YSwgZGVjcnlwdERhdGEsIHRpbWVPZmZzZXQsIGFjY3VyYXRlVGltZU9mZnNldCwgY2h1bmtNZXRhKSB7XG4gICAgcmV0dXJuIHRoaXMuZGVtdXhlci5kZW11eFNhbXBsZUFlcyhkYXRhLCBkZWNyeXB0RGF0YSwgdGltZU9mZnNldCkudGhlbihkZW11eFJlc3VsdCA9PiB7XG4gICAgICBjb25zdCByZW11eFJlc3VsdCA9IHRoaXMucmVtdXhlci5yZW11eChkZW11eFJlc3VsdC5hdWRpb1RyYWNrLCBkZW11eFJlc3VsdC52aWRlb1RyYWNrLCBkZW11eFJlc3VsdC5pZDNUcmFjaywgZGVtdXhSZXN1bHQudGV4dFRyYWNrLCB0aW1lT2Zmc2V0LCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGZhbHNlLCB0aGlzLmlkKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHJlbXV4UmVzdWx0LFxuICAgICAgICBjaHVua01ldGFcbiAgICAgIH07XG4gICAgfSk7XG4gIH1cbiAgY29uZmlndXJlVHJhbnNtdXhlcihkYXRhKSB7XG4gICAgY29uc3Qge1xuICAgICAgY29uZmlnLFxuICAgICAgb2JzZXJ2ZXIsXG4gICAgICB0eXBlU3VwcG9ydGVkLFxuICAgICAgdmVuZG9yXG4gICAgfSA9IHRoaXM7XG4gICAgLy8gcHJvYmUgZm9yIGNvbnRlbnQgdHlwZVxuICAgIGxldCBtdXg7XG4gICAgZm9yIChsZXQgaSA9IDAsIGxlbiA9IG11eENvbmZpZy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgdmFyIF9tdXhDb25maWckaSRkZW11eDtcbiAgICAgIGlmICgoX211eENvbmZpZyRpJGRlbXV4ID0gbXV4Q29uZmlnW2ldLmRlbXV4KSAhPSBudWxsICYmIF9tdXhDb25maWckaSRkZW11eC5wcm9iZShkYXRhKSkge1xuICAgICAgICBtdXggPSBtdXhDb25maWdbaV07XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoIW11eCkge1xuICAgICAgcmV0dXJuIG5ldyBFcnJvcignRmFpbGVkIHRvIGZpbmQgZGVtdXhlciBieSBwcm9iaW5nIGZyYWdtZW50IGRhdGEnKTtcbiAgICB9XG4gICAgLy8gc28gbGV0J3MgY2hlY2sgdGhhdCBjdXJyZW50IHJlbXV4ZXIgYW5kIGRlbXV4ZXIgYXJlIHN0aWxsIHZhbGlkXG4gICAgY29uc3QgZGVtdXhlciA9IHRoaXMuZGVtdXhlcjtcbiAgICBjb25zdCByZW11eGVyID0gdGhpcy5yZW11eGVyO1xuICAgIGNvbnN0IFJlbXV4ZXIgPSBtdXgucmVtdXg7XG4gICAgY29uc3QgRGVtdXhlciA9IG11eC5kZW11eDtcbiAgICBpZiAoIXJlbXV4ZXIgfHwgIShyZW11eGVyIGluc3RhbmNlb2YgUmVtdXhlcikpIHtcbiAgICAgIHRoaXMucmVtdXhlciA9IG5ldyBSZW11eGVyKG9ic2VydmVyLCBjb25maWcsIHR5cGVTdXBwb3J0ZWQsIHZlbmRvcik7XG4gICAgfVxuICAgIGlmICghZGVtdXhlciB8fCAhKGRlbXV4ZXIgaW5zdGFuY2VvZiBEZW11eGVyKSkge1xuICAgICAgdGhpcy5kZW11eGVyID0gbmV3IERlbXV4ZXIob2JzZXJ2ZXIsIGNvbmZpZywgdHlwZVN1cHBvcnRlZCk7XG4gICAgICB0aGlzLnByb2JlID0gRGVtdXhlci5wcm9iZTtcbiAgICB9XG4gIH1cbiAgbmVlZHNQcm9iaW5nKGRpc2NvbnRpbnVpdHksIHRyYWNrU3dpdGNoKSB7XG4gICAgLy8gaW4gY2FzZSBvZiBjb250aW51aXR5IGNoYW5nZSwgb3IgdHJhY2sgc3dpdGNoXG4gICAgLy8gd2UgbWlnaHQgc3dpdGNoIGZyb20gY29udGVudCB0eXBlIChBQUMgY29udGFpbmVyIHRvIFRTIGNvbnRhaW5lciwgb3IgVFMgdG8gZm1wNCBmb3IgZXhhbXBsZSlcbiAgICByZXR1cm4gIXRoaXMuZGVtdXhlciB8fCAhdGhpcy5yZW11eGVyIHx8IGRpc2NvbnRpbnVpdHkgfHwgdHJhY2tTd2l0Y2g7XG4gIH1cbiAgZ2V0RGVjcnlwdGVyKCkge1xuICAgIGxldCBkZWNyeXB0ZXIgPSB0aGlzLmRlY3J5cHRlcjtcbiAgICBpZiAoIWRlY3J5cHRlcikge1xuICAgICAgZGVjcnlwdGVyID0gdGhpcy5kZWNyeXB0ZXIgPSBuZXcgRGVjcnlwdGVyKHRoaXMuY29uZmlnKTtcbiAgICB9XG4gICAgcmV0dXJuIGRlY3J5cHRlcjtcbiAgfVxufVxuZnVuY3Rpb24gZ2V0RW5jcnlwdGlvblR5cGUoZGF0YSwgZGVjcnlwdERhdGEpIHtcbiAgbGV0IGVuY3J5cHRpb25UeXBlID0gbnVsbDtcbiAgaWYgKGRhdGEuYnl0ZUxlbmd0aCA+IDAgJiYgKGRlY3J5cHREYXRhID09IG51bGwgPyB2b2lkIDAgOiBkZWNyeXB0RGF0YS5rZXkpICE9IG51bGwgJiYgZGVjcnlwdERhdGEuaXYgIT09IG51bGwgJiYgZGVjcnlwdERhdGEubWV0aG9kICE9IG51bGwpIHtcbiAgICBlbmNyeXB0aW9uVHlwZSA9IGRlY3J5cHREYXRhO1xuICB9XG4gIHJldHVybiBlbmNyeXB0aW9uVHlwZTtcbn1cbmNvbnN0IGVtcHR5UmVzdWx0ID0gY2h1bmtNZXRhID0+ICh7XG4gIHJlbXV4UmVzdWx0OiB7fSxcbiAgY2h1bmtNZXRhXG59KTtcbmZ1bmN0aW9uIGlzUHJvbWlzZShwKSB7XG4gIHJldHVybiAndGhlbicgaW4gcCAmJiBwLnRoZW4gaW5zdGFuY2VvZiBGdW5jdGlvbjtcbn1cbmNsYXNzIFRyYW5zbXV4Q29uZmlnIHtcbiAgY29uc3RydWN0b3IoYXVkaW9Db2RlYywgdmlkZW9Db2RlYywgaW5pdFNlZ21lbnREYXRhLCBkdXJhdGlvbiwgZGVmYXVsdEluaXRQdHMpIHtcbiAgICB0aGlzLmF1ZGlvQ29kZWMgPSB2b2lkIDA7XG4gICAgdGhpcy52aWRlb0NvZGVjID0gdm9pZCAwO1xuICAgIHRoaXMuaW5pdFNlZ21lbnREYXRhID0gdm9pZCAwO1xuICAgIHRoaXMuZHVyYXRpb24gPSB2b2lkIDA7XG4gICAgdGhpcy5kZWZhdWx0SW5pdFB0cyA9IHZvaWQgMDtcbiAgICB0aGlzLmF1ZGlvQ29kZWMgPSBhdWRpb0NvZGVjO1xuICAgIHRoaXMudmlkZW9Db2RlYyA9IHZpZGVvQ29kZWM7XG4gICAgdGhpcy5pbml0U2VnbWVudERhdGEgPSBpbml0U2VnbWVudERhdGE7XG4gICAgdGhpcy5kdXJhdGlvbiA9IGR1cmF0aW9uO1xuICAgIHRoaXMuZGVmYXVsdEluaXRQdHMgPSBkZWZhdWx0SW5pdFB0cyB8fCBudWxsO1xuICB9XG59XG5jbGFzcyBUcmFuc211eFN0YXRlIHtcbiAgY29uc3RydWN0b3IoZGlzY29udGludWl0eSwgY29udGlndW91cywgYWNjdXJhdGVUaW1lT2Zmc2V0LCB0cmFja1N3aXRjaCwgdGltZU9mZnNldCwgaW5pdFNlZ21lbnRDaGFuZ2UpIHtcbiAgICB0aGlzLmRpc2NvbnRpbnVpdHkgPSB2b2lkIDA7XG4gICAgdGhpcy5jb250aWd1b3VzID0gdm9pZCAwO1xuICAgIHRoaXMuYWNjdXJhdGVUaW1lT2Zmc2V0ID0gdm9pZCAwO1xuICAgIHRoaXMudHJhY2tTd2l0Y2ggPSB2b2lkIDA7XG4gICAgdGhpcy50aW1lT2Zmc2V0ID0gdm9pZCAwO1xuICAgIHRoaXMuaW5pdFNlZ21lbnRDaGFuZ2UgPSB2b2lkIDA7XG4gICAgdGhpcy5kaXNjb250aW51aXR5ID0gZGlzY29udGludWl0eTtcbiAgICB0aGlzLmNvbnRpZ3VvdXMgPSBjb250aWd1b3VzO1xuICAgIHRoaXMuYWNjdXJhdGVUaW1lT2Zmc2V0ID0gYWNjdXJhdGVUaW1lT2Zmc2V0O1xuICAgIHRoaXMudHJhY2tTd2l0Y2ggPSB0cmFja1N3aXRjaDtcbiAgICB0aGlzLnRpbWVPZmZzZXQgPSB0aW1lT2Zmc2V0O1xuICAgIHRoaXMuaW5pdFNlZ21lbnRDaGFuZ2UgPSBpbml0U2VnbWVudENoYW5nZTtcbiAgfVxufVxuXG52YXIgZXZlbnRlbWl0dGVyMyA9IHtleHBvcnRzOiB7fX07XG5cbihmdW5jdGlvbiAobW9kdWxlKSB7XG5cblx0dmFyIGhhcyA9IE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHlcblx0ICAsIHByZWZpeCA9ICd+JztcblxuXHQvKipcblx0ICogQ29uc3RydWN0b3IgdG8gY3JlYXRlIGEgc3RvcmFnZSBmb3Igb3VyIGBFRWAgb2JqZWN0cy5cblx0ICogQW4gYEV2ZW50c2AgaW5zdGFuY2UgaXMgYSBwbGFpbiBvYmplY3Qgd2hvc2UgcHJvcGVydGllcyBhcmUgZXZlbnQgbmFtZXMuXG5cdCAqXG5cdCAqIEBjb25zdHJ1Y3RvclxuXHQgKiBAcHJpdmF0ZVxuXHQgKi9cblx0ZnVuY3Rpb24gRXZlbnRzKCkge31cblxuXHQvL1xuXHQvLyBXZSB0cnkgdG8gbm90IGluaGVyaXQgZnJvbSBgT2JqZWN0LnByb3RvdHlwZWAuIEluIHNvbWUgZW5naW5lcyBjcmVhdGluZyBhblxuXHQvLyBpbnN0YW5jZSBpbiB0aGlzIHdheSBpcyBmYXN0ZXIgdGhhbiBjYWxsaW5nIGBPYmplY3QuY3JlYXRlKG51bGwpYCBkaXJlY3RseS5cblx0Ly8gSWYgYE9iamVjdC5jcmVhdGUobnVsbClgIGlzIG5vdCBzdXBwb3J0ZWQgd2UgcHJlZml4IHRoZSBldmVudCBuYW1lcyB3aXRoIGFcblx0Ly8gY2hhcmFjdGVyIHRvIG1ha2Ugc3VyZSB0aGF0IHRoZSBidWlsdC1pbiBvYmplY3QgcHJvcGVydGllcyBhcmUgbm90XG5cdC8vIG92ZXJyaWRkZW4gb3IgdXNlZCBhcyBhbiBhdHRhY2sgdmVjdG9yLlxuXHQvL1xuXHRpZiAoT2JqZWN0LmNyZWF0ZSkge1xuXHQgIEV2ZW50cy5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKG51bGwpO1xuXG5cdCAgLy9cblx0ICAvLyBUaGlzIGhhY2sgaXMgbmVlZGVkIGJlY2F1c2UgdGhlIGBfX3Byb3RvX19gIHByb3BlcnR5IGlzIHN0aWxsIGluaGVyaXRlZCBpblxuXHQgIC8vIHNvbWUgb2xkIGJyb3dzZXJzIGxpa2UgQW5kcm9pZCA0LCBpUGhvbmUgNS4xLCBPcGVyYSAxMSBhbmQgU2FmYXJpIDUuXG5cdCAgLy9cblx0ICBpZiAoIW5ldyBFdmVudHMoKS5fX3Byb3RvX18pIHByZWZpeCA9IGZhbHNlO1xuXHR9XG5cblx0LyoqXG5cdCAqIFJlcHJlc2VudGF0aW9uIG9mIGEgc2luZ2xlIGV2ZW50IGxpc3RlbmVyLlxuXHQgKlxuXHQgKiBAcGFyYW0ge0Z1bmN0aW9ufSBmbiBUaGUgbGlzdGVuZXIgZnVuY3Rpb24uXG5cdCAqIEBwYXJhbSB7Kn0gY29udGV4dCBUaGUgY29udGV4dCB0byBpbnZva2UgdGhlIGxpc3RlbmVyIHdpdGguXG5cdCAqIEBwYXJhbSB7Qm9vbGVhbn0gW29uY2U9ZmFsc2VdIFNwZWNpZnkgaWYgdGhlIGxpc3RlbmVyIGlzIGEgb25lLXRpbWUgbGlzdGVuZXIuXG5cdCAqIEBjb25zdHJ1Y3RvclxuXHQgKiBAcHJpdmF0ZVxuXHQgKi9cblx0ZnVuY3Rpb24gRUUoZm4sIGNvbnRleHQsIG9uY2UpIHtcblx0ICB0aGlzLmZuID0gZm47XG5cdCAgdGhpcy5jb250ZXh0ID0gY29udGV4dDtcblx0ICB0aGlzLm9uY2UgPSBvbmNlIHx8IGZhbHNlO1xuXHR9XG5cblx0LyoqXG5cdCAqIEFkZCBhIGxpc3RlbmVyIGZvciBhIGdpdmVuIGV2ZW50LlxuXHQgKlxuXHQgKiBAcGFyYW0ge0V2ZW50RW1pdHRlcn0gZW1pdHRlciBSZWZlcmVuY2UgdG8gdGhlIGBFdmVudEVtaXR0ZXJgIGluc3RhbmNlLlxuXHQgKiBAcGFyYW0geyhTdHJpbmd8U3ltYm9sKX0gZXZlbnQgVGhlIGV2ZW50IG5hbWUuXG5cdCAqIEBwYXJhbSB7RnVuY3Rpb259IGZuIFRoZSBsaXN0ZW5lciBmdW5jdGlvbi5cblx0ICogQHBhcmFtIHsqfSBjb250ZXh0IFRoZSBjb250ZXh0IHRvIGludm9rZSB0aGUgbGlzdGVuZXIgd2l0aC5cblx0ICogQHBhcmFtIHtCb29sZWFufSBvbmNlIFNwZWNpZnkgaWYgdGhlIGxpc3RlbmVyIGlzIGEgb25lLXRpbWUgbGlzdGVuZXIuXG5cdCAqIEByZXR1cm5zIHtFdmVudEVtaXR0ZXJ9XG5cdCAqIEBwcml2YXRlXG5cdCAqL1xuXHRmdW5jdGlvbiBhZGRMaXN0ZW5lcihlbWl0dGVyLCBldmVudCwgZm4sIGNvbnRleHQsIG9uY2UpIHtcblx0ICBpZiAodHlwZW9mIGZuICE9PSAnZnVuY3Rpb24nKSB7XG5cdCAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGUgbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cdCAgfVxuXG5cdCAgdmFyIGxpc3RlbmVyID0gbmV3IEVFKGZuLCBjb250ZXh0IHx8IGVtaXR0ZXIsIG9uY2UpXG5cdCAgICAsIGV2dCA9IHByZWZpeCA/IHByZWZpeCArIGV2ZW50IDogZXZlbnQ7XG5cblx0ICBpZiAoIWVtaXR0ZXIuX2V2ZW50c1tldnRdKSBlbWl0dGVyLl9ldmVudHNbZXZ0XSA9IGxpc3RlbmVyLCBlbWl0dGVyLl9ldmVudHNDb3VudCsrO1xuXHQgIGVsc2UgaWYgKCFlbWl0dGVyLl9ldmVudHNbZXZ0XS5mbikgZW1pdHRlci5fZXZlbnRzW2V2dF0ucHVzaChsaXN0ZW5lcik7XG5cdCAgZWxzZSBlbWl0dGVyLl9ldmVudHNbZXZ0XSA9IFtlbWl0dGVyLl9ldmVudHNbZXZ0XSwgbGlzdGVuZXJdO1xuXG5cdCAgcmV0dXJuIGVtaXR0ZXI7XG5cdH1cblxuXHQvKipcblx0ICogQ2xlYXIgZXZlbnQgYnkgbmFtZS5cblx0ICpcblx0ICogQHBhcmFtIHtFdmVudEVtaXR0ZXJ9IGVtaXR0ZXIgUmVmZXJlbmNlIHRvIHRoZSBgRXZlbnRFbWl0dGVyYCBpbnN0YW5jZS5cblx0ICogQHBhcmFtIHsoU3RyaW5nfFN5bWJvbCl9IGV2dCBUaGUgRXZlbnQgbmFtZS5cblx0ICogQHByaXZhdGVcblx0ICovXG5cdGZ1bmN0aW9uIGNsZWFyRXZlbnQoZW1pdHRlciwgZXZ0KSB7XG5cdCAgaWYgKC0tZW1pdHRlci5fZXZlbnRzQ291bnQgPT09IDApIGVtaXR0ZXIuX2V2ZW50cyA9IG5ldyBFdmVudHMoKTtcblx0ICBlbHNlIGRlbGV0ZSBlbWl0dGVyLl9ldmVudHNbZXZ0XTtcblx0fVxuXG5cdC8qKlxuXHQgKiBNaW5pbWFsIGBFdmVudEVtaXR0ZXJgIGludGVyZmFjZSB0aGF0IGlzIG1vbGRlZCBhZ2FpbnN0IHRoZSBOb2RlLmpzXG5cdCAqIGBFdmVudEVtaXR0ZXJgIGludGVyZmFjZS5cblx0ICpcblx0ICogQGNvbnN0cnVjdG9yXG5cdCAqIEBwdWJsaWNcblx0ICovXG5cdGZ1bmN0aW9uIEV2ZW50RW1pdHRlcigpIHtcblx0ICB0aGlzLl9ldmVudHMgPSBuZXcgRXZlbnRzKCk7XG5cdCAgdGhpcy5fZXZlbnRzQ291bnQgPSAwO1xuXHR9XG5cblx0LyoqXG5cdCAqIFJldHVybiBhbiBhcnJheSBsaXN0aW5nIHRoZSBldmVudHMgZm9yIHdoaWNoIHRoZSBlbWl0dGVyIGhhcyByZWdpc3RlcmVkXG5cdCAqIGxpc3RlbmVycy5cblx0ICpcblx0ICogQHJldHVybnMge0FycmF5fVxuXHQgKiBAcHVibGljXG5cdCAqL1xuXHRFdmVudEVtaXR0ZXIucHJvdG90eXBlLmV2ZW50TmFtZXMgPSBmdW5jdGlvbiBldmVudE5hbWVzKCkge1xuXHQgIHZhciBuYW1lcyA9IFtdXG5cdCAgICAsIGV2ZW50c1xuXHQgICAgLCBuYW1lO1xuXG5cdCAgaWYgKHRoaXMuX2V2ZW50c0NvdW50ID09PSAwKSByZXR1cm4gbmFtZXM7XG5cblx0ICBmb3IgKG5hbWUgaW4gKGV2ZW50cyA9IHRoaXMuX2V2ZW50cykpIHtcblx0ICAgIGlmIChoYXMuY2FsbChldmVudHMsIG5hbWUpKSBuYW1lcy5wdXNoKHByZWZpeCA/IG5hbWUuc2xpY2UoMSkgOiBuYW1lKTtcblx0ICB9XG5cblx0ICBpZiAoT2JqZWN0LmdldE93blByb3BlcnR5U3ltYm9scykge1xuXHQgICAgcmV0dXJuIG5hbWVzLmNvbmNhdChPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKGV2ZW50cykpO1xuXHQgIH1cblxuXHQgIHJldHVybiBuYW1lcztcblx0fTtcblxuXHQvKipcblx0ICogUmV0dXJuIHRoZSBsaXN0ZW5lcnMgcmVnaXN0ZXJlZCBmb3IgYSBnaXZlbiBldmVudC5cblx0ICpcblx0ICogQHBhcmFtIHsoU3RyaW5nfFN5bWJvbCl9IGV2ZW50IFRoZSBldmVudCBuYW1lLlxuXHQgKiBAcmV0dXJucyB7QXJyYXl9IFRoZSByZWdpc3RlcmVkIGxpc3RlbmVycy5cblx0ICogQHB1YmxpY1xuXHQgKi9cblx0RXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lcnMgPSBmdW5jdGlvbiBsaXN0ZW5lcnMoZXZlbnQpIHtcblx0ICB2YXIgZXZ0ID0gcHJlZml4ID8gcHJlZml4ICsgZXZlbnQgOiBldmVudFxuXHQgICAgLCBoYW5kbGVycyA9IHRoaXMuX2V2ZW50c1tldnRdO1xuXG5cdCAgaWYgKCFoYW5kbGVycykgcmV0dXJuIFtdO1xuXHQgIGlmIChoYW5kbGVycy5mbikgcmV0dXJuIFtoYW5kbGVycy5mbl07XG5cblx0ICBmb3IgKHZhciBpID0gMCwgbCA9IGhhbmRsZXJzLmxlbmd0aCwgZWUgPSBuZXcgQXJyYXkobCk7IGkgPCBsOyBpKyspIHtcblx0ICAgIGVlW2ldID0gaGFuZGxlcnNbaV0uZm47XG5cdCAgfVxuXG5cdCAgcmV0dXJuIGVlO1xuXHR9O1xuXG5cdC8qKlxuXHQgKiBSZXR1cm4gdGhlIG51bWJlciBvZiBsaXN0ZW5lcnMgbGlzdGVuaW5nIHRvIGEgZ2l2ZW4gZXZlbnQuXG5cdCAqXG5cdCAqIEBwYXJhbSB7KFN0cmluZ3xTeW1ib2wpfSBldmVudCBUaGUgZXZlbnQgbmFtZS5cblx0ICogQHJldHVybnMge051bWJlcn0gVGhlIG51bWJlciBvZiBsaXN0ZW5lcnMuXG5cdCAqIEBwdWJsaWNcblx0ICovXG5cdEV2ZW50RW1pdHRlci5wcm90b3R5cGUubGlzdGVuZXJDb3VudCA9IGZ1bmN0aW9uIGxpc3RlbmVyQ291bnQoZXZlbnQpIHtcblx0ICB2YXIgZXZ0ID0gcHJlZml4ID8gcHJlZml4ICsgZXZlbnQgOiBldmVudFxuXHQgICAgLCBsaXN0ZW5lcnMgPSB0aGlzLl9ldmVudHNbZXZ0XTtcblxuXHQgIGlmICghbGlzdGVuZXJzKSByZXR1cm4gMDtcblx0ICBpZiAobGlzdGVuZXJzLmZuKSByZXR1cm4gMTtcblx0ICByZXR1cm4gbGlzdGVuZXJzLmxlbmd0aDtcblx0fTtcblxuXHQvKipcblx0ICogQ2FsbHMgZWFjaCBvZiB0aGUgbGlzdGVuZXJzIHJlZ2lzdGVyZWQgZm9yIGEgZ2l2ZW4gZXZlbnQuXG5cdCAqXG5cdCAqIEBwYXJhbSB7KFN0cmluZ3xTeW1ib2wpfSBldmVudCBUaGUgZXZlbnQgbmFtZS5cblx0ICogQHJldHVybnMge0Jvb2xlYW59IGB0cnVlYCBpZiB0aGUgZXZlbnQgaGFkIGxpc3RlbmVycywgZWxzZSBgZmFsc2VgLlxuXHQgKiBAcHVibGljXG5cdCAqL1xuXHRFdmVudEVtaXR0ZXIucHJvdG90eXBlLmVtaXQgPSBmdW5jdGlvbiBlbWl0KGV2ZW50LCBhMSwgYTIsIGEzLCBhNCwgYTUpIHtcblx0ICB2YXIgZXZ0ID0gcHJlZml4ID8gcHJlZml4ICsgZXZlbnQgOiBldmVudDtcblxuXHQgIGlmICghdGhpcy5fZXZlbnRzW2V2dF0pIHJldHVybiBmYWxzZTtcblxuXHQgIHZhciBsaXN0ZW5lcnMgPSB0aGlzLl9ldmVudHNbZXZ0XVxuXHQgICAgLCBsZW4gPSBhcmd1bWVudHMubGVuZ3RoXG5cdCAgICAsIGFyZ3Ncblx0ICAgICwgaTtcblxuXHQgIGlmIChsaXN0ZW5lcnMuZm4pIHtcblx0ICAgIGlmIChsaXN0ZW5lcnMub25jZSkgdGhpcy5yZW1vdmVMaXN0ZW5lcihldmVudCwgbGlzdGVuZXJzLmZuLCB1bmRlZmluZWQsIHRydWUpO1xuXG5cdCAgICBzd2l0Y2ggKGxlbikge1xuXHQgICAgICBjYXNlIDE6IHJldHVybiBsaXN0ZW5lcnMuZm4uY2FsbChsaXN0ZW5lcnMuY29udGV4dCksIHRydWU7XG5cdCAgICAgIGNhc2UgMjogcmV0dXJuIGxpc3RlbmVycy5mbi5jYWxsKGxpc3RlbmVycy5jb250ZXh0LCBhMSksIHRydWU7XG5cdCAgICAgIGNhc2UgMzogcmV0dXJuIGxpc3RlbmVycy5mbi5jYWxsKGxpc3RlbmVycy5jb250ZXh0LCBhMSwgYTIpLCB0cnVlO1xuXHQgICAgICBjYXNlIDQ6IHJldHVybiBsaXN0ZW5lcnMuZm4uY2FsbChsaXN0ZW5lcnMuY29udGV4dCwgYTEsIGEyLCBhMyksIHRydWU7XG5cdCAgICAgIGNhc2UgNTogcmV0dXJuIGxpc3RlbmVycy5mbi5jYWxsKGxpc3RlbmVycy5jb250ZXh0LCBhMSwgYTIsIGEzLCBhNCksIHRydWU7XG5cdCAgICAgIGNhc2UgNjogcmV0dXJuIGxpc3RlbmVycy5mbi5jYWxsKGxpc3RlbmVycy5jb250ZXh0LCBhMSwgYTIsIGEzLCBhNCwgYTUpLCB0cnVlO1xuXHQgICAgfVxuXG5cdCAgICBmb3IgKGkgPSAxLCBhcmdzID0gbmV3IEFycmF5KGxlbiAtMSk7IGkgPCBsZW47IGkrKykge1xuXHQgICAgICBhcmdzW2kgLSAxXSA9IGFyZ3VtZW50c1tpXTtcblx0ICAgIH1cblxuXHQgICAgbGlzdGVuZXJzLmZuLmFwcGx5KGxpc3RlbmVycy5jb250ZXh0LCBhcmdzKTtcblx0ICB9IGVsc2Uge1xuXHQgICAgdmFyIGxlbmd0aCA9IGxpc3RlbmVycy5sZW5ndGhcblx0ICAgICAgLCBqO1xuXG5cdCAgICBmb3IgKGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcblx0ICAgICAgaWYgKGxpc3RlbmVyc1tpXS5vbmNlKSB0aGlzLnJlbW92ZUxpc3RlbmVyKGV2ZW50LCBsaXN0ZW5lcnNbaV0uZm4sIHVuZGVmaW5lZCwgdHJ1ZSk7XG5cblx0ICAgICAgc3dpdGNoIChsZW4pIHtcblx0ICAgICAgICBjYXNlIDE6IGxpc3RlbmVyc1tpXS5mbi5jYWxsKGxpc3RlbmVyc1tpXS5jb250ZXh0KTsgYnJlYWs7XG5cdCAgICAgICAgY2FzZSAyOiBsaXN0ZW5lcnNbaV0uZm4uY2FsbChsaXN0ZW5lcnNbaV0uY29udGV4dCwgYTEpOyBicmVhaztcblx0ICAgICAgICBjYXNlIDM6IGxpc3RlbmVyc1tpXS5mbi5jYWxsKGxpc3RlbmVyc1tpXS5jb250ZXh0LCBhMSwgYTIpOyBicmVhaztcblx0ICAgICAgICBjYXNlIDQ6IGxpc3RlbmVyc1tpXS5mbi5jYWxsKGxpc3RlbmVyc1tpXS5jb250ZXh0LCBhMSwgYTIsIGEzKTsgYnJlYWs7XG5cdCAgICAgICAgZGVmYXVsdDpcblx0ICAgICAgICAgIGlmICghYXJncykgZm9yIChqID0gMSwgYXJncyA9IG5ldyBBcnJheShsZW4gLTEpOyBqIDwgbGVuOyBqKyspIHtcblx0ICAgICAgICAgICAgYXJnc1tqIC0gMV0gPSBhcmd1bWVudHNbal07XG5cdCAgICAgICAgICB9XG5cblx0ICAgICAgICAgIGxpc3RlbmVyc1tpXS5mbi5hcHBseShsaXN0ZW5lcnNbaV0uY29udGV4dCwgYXJncyk7XG5cdCAgICAgIH1cblx0ICAgIH1cblx0ICB9XG5cblx0ICByZXR1cm4gdHJ1ZTtcblx0fTtcblxuXHQvKipcblx0ICogQWRkIGEgbGlzdGVuZXIgZm9yIGEgZ2l2ZW4gZXZlbnQuXG5cdCAqXG5cdCAqIEBwYXJhbSB7KFN0cmluZ3xTeW1ib2wpfSBldmVudCBUaGUgZXZlbnQgbmFtZS5cblx0ICogQHBhcmFtIHtGdW5jdGlvbn0gZm4gVGhlIGxpc3RlbmVyIGZ1bmN0aW9uLlxuXHQgKiBAcGFyYW0geyp9IFtjb250ZXh0PXRoaXNdIFRoZSBjb250ZXh0IHRvIGludm9rZSB0aGUgbGlzdGVuZXIgd2l0aC5cblx0ICogQHJldHVybnMge0V2ZW50RW1pdHRlcn0gYHRoaXNgLlxuXHQgKiBAcHVibGljXG5cdCAqL1xuXHRFdmVudEVtaXR0ZXIucHJvdG90eXBlLm9uID0gZnVuY3Rpb24gb24oZXZlbnQsIGZuLCBjb250ZXh0KSB7XG5cdCAgcmV0dXJuIGFkZExpc3RlbmVyKHRoaXMsIGV2ZW50LCBmbiwgY29udGV4dCwgZmFsc2UpO1xuXHR9O1xuXG5cdC8qKlxuXHQgKiBBZGQgYSBvbmUtdGltZSBsaXN0ZW5lciBmb3IgYSBnaXZlbiBldmVudC5cblx0ICpcblx0ICogQHBhcmFtIHsoU3RyaW5nfFN5bWJvbCl9IGV2ZW50IFRoZSBldmVudCBuYW1lLlxuXHQgKiBAcGFyYW0ge0Z1bmN0aW9ufSBmbiBUaGUgbGlzdGVuZXIgZnVuY3Rpb24uXG5cdCAqIEBwYXJhbSB7Kn0gW2NvbnRleHQ9dGhpc10gVGhlIGNvbnRleHQgdG8gaW52b2tlIHRoZSBsaXN0ZW5lciB3aXRoLlxuXHQgKiBAcmV0dXJucyB7RXZlbnRFbWl0dGVyfSBgdGhpc2AuXG5cdCAqIEBwdWJsaWNcblx0ICovXG5cdEV2ZW50RW1pdHRlci5wcm90b3R5cGUub25jZSA9IGZ1bmN0aW9uIG9uY2UoZXZlbnQsIGZuLCBjb250ZXh0KSB7XG5cdCAgcmV0dXJuIGFkZExpc3RlbmVyKHRoaXMsIGV2ZW50LCBmbiwgY29udGV4dCwgdHJ1ZSk7XG5cdH07XG5cblx0LyoqXG5cdCAqIFJlbW92ZSB0aGUgbGlzdGVuZXJzIG9mIGEgZ2l2ZW4gZXZlbnQuXG5cdCAqXG5cdCAqIEBwYXJhbSB7KFN0cmluZ3xTeW1ib2wpfSBldmVudCBUaGUgZXZlbnQgbmFtZS5cblx0ICogQHBhcmFtIHtGdW5jdGlvbn0gZm4gT25seSByZW1vdmUgdGhlIGxpc3RlbmVycyB0aGF0IG1hdGNoIHRoaXMgZnVuY3Rpb24uXG5cdCAqIEBwYXJhbSB7Kn0gY29udGV4dCBPbmx5IHJlbW92ZSB0aGUgbGlzdGVuZXJzIHRoYXQgaGF2ZSB0aGlzIGNvbnRleHQuXG5cdCAqIEBwYXJhbSB7Qm9vbGVhbn0gb25jZSBPbmx5IHJlbW92ZSBvbmUtdGltZSBsaXN0ZW5lcnMuXG5cdCAqIEByZXR1cm5zIHtFdmVudEVtaXR0ZXJ9IGB0aGlzYC5cblx0ICogQHB1YmxpY1xuXHQgKi9cblx0RXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVMaXN0ZW5lciA9IGZ1bmN0aW9uIHJlbW92ZUxpc3RlbmVyKGV2ZW50LCBmbiwgY29udGV4dCwgb25jZSkge1xuXHQgIHZhciBldnQgPSBwcmVmaXggPyBwcmVmaXggKyBldmVudCA6IGV2ZW50O1xuXG5cdCAgaWYgKCF0aGlzLl9ldmVudHNbZXZ0XSkgcmV0dXJuIHRoaXM7XG5cdCAgaWYgKCFmbikge1xuXHQgICAgY2xlYXJFdmVudCh0aGlzLCBldnQpO1xuXHQgICAgcmV0dXJuIHRoaXM7XG5cdCAgfVxuXG5cdCAgdmFyIGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50c1tldnRdO1xuXG5cdCAgaWYgKGxpc3RlbmVycy5mbikge1xuXHQgICAgaWYgKFxuXHQgICAgICBsaXN0ZW5lcnMuZm4gPT09IGZuICYmXG5cdCAgICAgICghb25jZSB8fCBsaXN0ZW5lcnMub25jZSkgJiZcblx0ICAgICAgKCFjb250ZXh0IHx8IGxpc3RlbmVycy5jb250ZXh0ID09PSBjb250ZXh0KVxuXHQgICAgKSB7XG5cdCAgICAgIGNsZWFyRXZlbnQodGhpcywgZXZ0KTtcblx0ICAgIH1cblx0ICB9IGVsc2Uge1xuXHQgICAgZm9yICh2YXIgaSA9IDAsIGV2ZW50cyA9IFtdLCBsZW5ndGggPSBsaXN0ZW5lcnMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcblx0ICAgICAgaWYgKFxuXHQgICAgICAgIGxpc3RlbmVyc1tpXS5mbiAhPT0gZm4gfHxcblx0ICAgICAgICAob25jZSAmJiAhbGlzdGVuZXJzW2ldLm9uY2UpIHx8XG5cdCAgICAgICAgKGNvbnRleHQgJiYgbGlzdGVuZXJzW2ldLmNvbnRleHQgIT09IGNvbnRleHQpXG5cdCAgICAgICkge1xuXHQgICAgICAgIGV2ZW50cy5wdXNoKGxpc3RlbmVyc1tpXSk7XG5cdCAgICAgIH1cblx0ICAgIH1cblxuXHQgICAgLy9cblx0ICAgIC8vIFJlc2V0IHRoZSBhcnJheSwgb3IgcmVtb3ZlIGl0IGNvbXBsZXRlbHkgaWYgd2UgaGF2ZSBubyBtb3JlIGxpc3RlbmVycy5cblx0ICAgIC8vXG5cdCAgICBpZiAoZXZlbnRzLmxlbmd0aCkgdGhpcy5fZXZlbnRzW2V2dF0gPSBldmVudHMubGVuZ3RoID09PSAxID8gZXZlbnRzWzBdIDogZXZlbnRzO1xuXHQgICAgZWxzZSBjbGVhckV2ZW50KHRoaXMsIGV2dCk7XG5cdCAgfVxuXG5cdCAgcmV0dXJuIHRoaXM7XG5cdH07XG5cblx0LyoqXG5cdCAqIFJlbW92ZSBhbGwgbGlzdGVuZXJzLCBvciB0aG9zZSBvZiB0aGUgc3BlY2lmaWVkIGV2ZW50LlxuXHQgKlxuXHQgKiBAcGFyYW0geyhTdHJpbmd8U3ltYm9sKX0gW2V2ZW50XSBUaGUgZXZlbnQgbmFtZS5cblx0ICogQHJldHVybnMge0V2ZW50RW1pdHRlcn0gYHRoaXNgLlxuXHQgKiBAcHVibGljXG5cdCAqL1xuXHRFdmVudEVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUFsbExpc3RlbmVycyA9IGZ1bmN0aW9uIHJlbW92ZUFsbExpc3RlbmVycyhldmVudCkge1xuXHQgIHZhciBldnQ7XG5cblx0ICBpZiAoZXZlbnQpIHtcblx0ICAgIGV2dCA9IHByZWZpeCA/IHByZWZpeCArIGV2ZW50IDogZXZlbnQ7XG5cdCAgICBpZiAodGhpcy5fZXZlbnRzW2V2dF0pIGNsZWFyRXZlbnQodGhpcywgZXZ0KTtcblx0ICB9IGVsc2Uge1xuXHQgICAgdGhpcy5fZXZlbnRzID0gbmV3IEV2ZW50cygpO1xuXHQgICAgdGhpcy5fZXZlbnRzQ291bnQgPSAwO1xuXHQgIH1cblxuXHQgIHJldHVybiB0aGlzO1xuXHR9O1xuXG5cdC8vXG5cdC8vIEFsaWFzIG1ldGhvZHMgbmFtZXMgYmVjYXVzZSBwZW9wbGUgcm9sbCBsaWtlIHRoYXQuXG5cdC8vXG5cdEV2ZW50RW1pdHRlci5wcm90b3R5cGUub2ZmID0gRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVMaXN0ZW5lcjtcblx0RXZlbnRFbWl0dGVyLnByb3RvdHlwZS5hZGRMaXN0ZW5lciA9IEV2ZW50RW1pdHRlci5wcm90b3R5cGUub247XG5cblx0Ly9cblx0Ly8gRXhwb3NlIHRoZSBwcmVmaXguXG5cdC8vXG5cdEV2ZW50RW1pdHRlci5wcmVmaXhlZCA9IHByZWZpeDtcblxuXHQvL1xuXHQvLyBBbGxvdyBgRXZlbnRFbWl0dGVyYCB0byBiZSBpbXBvcnRlZCBhcyBtb2R1bGUgbmFtZXNwYWNlLlxuXHQvL1xuXHRFdmVudEVtaXR0ZXIuRXZlbnRFbWl0dGVyID0gRXZlbnRFbWl0dGVyO1xuXG5cdC8vXG5cdC8vIEV4cG9zZSB0aGUgbW9kdWxlLlxuXHQvL1xuXHR7XG5cdCAgbW9kdWxlLmV4cG9ydHMgPSBFdmVudEVtaXR0ZXI7XG5cdH0gXG59IChldmVudGVtaXR0ZXIzKSk7XG5cbnZhciBldmVudGVtaXR0ZXIzRXhwb3J0cyA9IGV2ZW50ZW1pdHRlcjMuZXhwb3J0cztcbnZhciBFdmVudEVtaXR0ZXIgPSAvKkBfX1BVUkVfXyovZ2V0RGVmYXVsdEV4cG9ydEZyb21DanMoZXZlbnRlbWl0dGVyM0V4cG9ydHMpO1xuXG5jbGFzcyBUcmFuc211eGVySW50ZXJmYWNlIHtcbiAgY29uc3RydWN0b3IoaGxzLCBpZCwgb25UcmFuc211eENvbXBsZXRlLCBvbkZsdXNoKSB7XG4gICAgdGhpcy5lcnJvciA9IG51bGw7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5pZCA9IHZvaWQgMDtcbiAgICB0aGlzLm9ic2VydmVyID0gdm9pZCAwO1xuICAgIHRoaXMuZnJhZyA9IG51bGw7XG4gICAgdGhpcy5wYXJ0ID0gbnVsbDtcbiAgICB0aGlzLnVzZVdvcmtlciA9IHZvaWQgMDtcbiAgICB0aGlzLndvcmtlckNvbnRleHQgPSBudWxsO1xuICAgIHRoaXMub253bXNnID0gdm9pZCAwO1xuICAgIHRoaXMudHJhbnNtdXhlciA9IG51bGw7XG4gICAgdGhpcy5vblRyYW5zbXV4Q29tcGxldGUgPSB2b2lkIDA7XG4gICAgdGhpcy5vbkZsdXNoID0gdm9pZCAwO1xuICAgIGNvbnN0IGNvbmZpZyA9IGhscy5jb25maWc7XG4gICAgdGhpcy5obHMgPSBobHM7XG4gICAgdGhpcy5pZCA9IGlkO1xuICAgIHRoaXMudXNlV29ya2VyID0gISFjb25maWcuZW5hYmxlV29ya2VyO1xuICAgIHRoaXMub25UcmFuc211eENvbXBsZXRlID0gb25UcmFuc211eENvbXBsZXRlO1xuICAgIHRoaXMub25GbHVzaCA9IG9uRmx1c2g7XG4gICAgY29uc3QgZm9yd2FyZE1lc3NhZ2UgPSAoZXYsIGRhdGEpID0+IHtcbiAgICAgIGRhdGEgPSBkYXRhIHx8IHt9O1xuICAgICAgZGF0YS5mcmFnID0gdGhpcy5mcmFnO1xuICAgICAgZGF0YS5pZCA9IHRoaXMuaWQ7XG4gICAgICBpZiAoZXYgPT09IEV2ZW50cy5FUlJPUikge1xuICAgICAgICB0aGlzLmVycm9yID0gZGF0YS5lcnJvcjtcbiAgICAgIH1cbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoZXYsIGRhdGEpO1xuICAgIH07XG5cbiAgICAvLyBmb3J3YXJkIGV2ZW50cyB0byBtYWluIHRocmVhZFxuICAgIHRoaXMub2JzZXJ2ZXIgPSBuZXcgRXZlbnRFbWl0dGVyKCk7XG4gICAgdGhpcy5vYnNlcnZlci5vbihFdmVudHMuRlJBR19ERUNSWVBURUQsIGZvcndhcmRNZXNzYWdlKTtcbiAgICB0aGlzLm9ic2VydmVyLm9uKEV2ZW50cy5FUlJPUiwgZm9yd2FyZE1lc3NhZ2UpO1xuICAgIGNvbnN0IE1lZGlhU291cmNlID0gZ2V0TWVkaWFTb3VyY2UoY29uZmlnLnByZWZlck1hbmFnZWRNZWRpYVNvdXJjZSkgfHwge1xuICAgICAgaXNUeXBlU3VwcG9ydGVkOiAoKSA9PiBmYWxzZVxuICAgIH07XG4gICAgY29uc3QgbTJ0c1R5cGVTdXBwb3J0ZWQgPSB7XG4gICAgICBtcGVnOiBNZWRpYVNvdXJjZS5pc1R5cGVTdXBwb3J0ZWQoJ2F1ZGlvL21wZWcnKSxcbiAgICAgIG1wMzogTWVkaWFTb3VyY2UuaXNUeXBlU3VwcG9ydGVkKCdhdWRpby9tcDQ7IGNvZGVjcz1cIm1wM1wiJyksXG4gICAgICBhYzM6IE1lZGlhU291cmNlLmlzVHlwZVN1cHBvcnRlZCgnYXVkaW8vbXA0OyBjb2RlY3M9XCJhYy0zXCInKSBcbiAgICB9O1xuICAgIGlmICh0aGlzLnVzZVdvcmtlciAmJiB0eXBlb2YgV29ya2VyICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgY29uc3QgY2FuQ3JlYXRlV29ya2VyID0gY29uZmlnLndvcmtlclBhdGggfHwgaGFzVU1EV29ya2VyKCk7XG4gICAgICBpZiAoY2FuQ3JlYXRlV29ya2VyKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWYgKGNvbmZpZy53b3JrZXJQYXRoKSB7XG4gICAgICAgICAgICBsb2dnZXIubG9nKGBsb2FkaW5nIFdlYiBXb3JrZXIgJHtjb25maWcud29ya2VyUGF0aH0gZm9yIFwiJHtpZH1cImApO1xuICAgICAgICAgICAgdGhpcy53b3JrZXJDb250ZXh0ID0gbG9hZFdvcmtlcihjb25maWcud29ya2VyUGF0aCk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGxvZ2dlci5sb2coYGluamVjdGluZyBXZWIgV29ya2VyIGZvciBcIiR7aWR9XCJgKTtcbiAgICAgICAgICAgIHRoaXMud29ya2VyQ29udGV4dCA9IGluamVjdFdvcmtlcigpO1xuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLm9ud21zZyA9IGV2ZW50ID0+IHRoaXMub25Xb3JrZXJNZXNzYWdlKGV2ZW50KTtcbiAgICAgICAgICBjb25zdCB7XG4gICAgICAgICAgICB3b3JrZXJcbiAgICAgICAgICB9ID0gdGhpcy53b3JrZXJDb250ZXh0O1xuICAgICAgICAgIHdvcmtlci5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgdGhpcy5vbndtc2cpO1xuICAgICAgICAgIHdvcmtlci5vbmVycm9yID0gZXZlbnQgPT4ge1xuICAgICAgICAgICAgY29uc3QgZXJyb3IgPSBuZXcgRXJyb3IoYCR7ZXZlbnQubWVzc2FnZX0gICgke2V2ZW50LmZpbGVuYW1lfToke2V2ZW50LmxpbmVub30pYCk7XG4gICAgICAgICAgICBjb25maWcuZW5hYmxlV29ya2VyID0gZmFsc2U7XG4gICAgICAgICAgICBsb2dnZXIud2FybihgRXJyb3IgaW4gXCIke2lkfVwiIFdlYiBXb3JrZXIsIGZhbGxiYWNrIHRvIGlubGluZWApO1xuICAgICAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5PVEhFUl9FUlJPUixcbiAgICAgICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLklOVEVSTkFMX0VYQ0VQVElPTixcbiAgICAgICAgICAgICAgZmF0YWw6IGZhbHNlLFxuICAgICAgICAgICAgICBldmVudDogJ2RlbXV4ZXJXb3JrZXInLFxuICAgICAgICAgICAgICBlcnJvclxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfTtcbiAgICAgICAgICB3b3JrZXIucG9zdE1lc3NhZ2Uoe1xuICAgICAgICAgICAgY21kOiAnaW5pdCcsXG4gICAgICAgICAgICB0eXBlU3VwcG9ydGVkOiBtMnRzVHlwZVN1cHBvcnRlZCxcbiAgICAgICAgICAgIHZlbmRvcjogJycsXG4gICAgICAgICAgICBpZDogaWQsXG4gICAgICAgICAgICBjb25maWc6IEpTT04uc3RyaW5naWZ5KGNvbmZpZylcbiAgICAgICAgICB9KTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oYEVycm9yIHNldHRpbmcgdXAgXCIke2lkfVwiIFdlYiBXb3JrZXIsIGZhbGxiYWNrIHRvIGlubGluZWAsIGVycik7XG4gICAgICAgICAgdGhpcy5yZXNldFdvcmtlcigpO1xuICAgICAgICAgIHRoaXMuZXJyb3IgPSBudWxsO1xuICAgICAgICAgIHRoaXMudHJhbnNtdXhlciA9IG5ldyBUcmFuc211eGVyKHRoaXMub2JzZXJ2ZXIsIG0ydHNUeXBlU3VwcG9ydGVkLCBjb25maWcsICcnLCBpZCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnRyYW5zbXV4ZXIgPSBuZXcgVHJhbnNtdXhlcih0aGlzLm9ic2VydmVyLCBtMnRzVHlwZVN1cHBvcnRlZCwgY29uZmlnLCAnJywgaWQpO1xuICB9XG4gIHJlc2V0V29ya2VyKCkge1xuICAgIGlmICh0aGlzLndvcmtlckNvbnRleHQpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgd29ya2VyLFxuICAgICAgICBvYmplY3RVUkxcbiAgICAgIH0gPSB0aGlzLndvcmtlckNvbnRleHQ7XG4gICAgICBpZiAob2JqZWN0VVJMKSB7XG4gICAgICAgIC8vIHJldm9rZSB0aGUgT2JqZWN0IFVSTCB0aGF0IHdhcyB1c2VkIHRvIGNyZWF0ZSB0cmFuc211eGVyIHdvcmtlciwgc28gYXMgbm90IHRvIGxlYWsgaXRcbiAgICAgICAgc2VsZi5VUkwucmV2b2tlT2JqZWN0VVJMKG9iamVjdFVSTCk7XG4gICAgICB9XG4gICAgICB3b3JrZXIucmVtb3ZlRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIHRoaXMub253bXNnKTtcbiAgICAgIHdvcmtlci5vbmVycm9yID0gbnVsbDtcbiAgICAgIHdvcmtlci50ZXJtaW5hdGUoKTtcbiAgICAgIHRoaXMud29ya2VyQ29udGV4dCA9IG51bGw7XG4gICAgfVxuICB9XG4gIGRlc3Ryb3koKSB7XG4gICAgaWYgKHRoaXMud29ya2VyQ29udGV4dCkge1xuICAgICAgdGhpcy5yZXNldFdvcmtlcigpO1xuICAgICAgdGhpcy5vbndtc2cgPSB1bmRlZmluZWQ7XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbnN0IHRyYW5zbXV4ZXIgPSB0aGlzLnRyYW5zbXV4ZXI7XG4gICAgICBpZiAodHJhbnNtdXhlcikge1xuICAgICAgICB0cmFuc211eGVyLmRlc3Ryb3koKTtcbiAgICAgICAgdGhpcy50cmFuc211eGVyID0gbnVsbDtcbiAgICAgIH1cbiAgICB9XG4gICAgY29uc3Qgb2JzZXJ2ZXIgPSB0aGlzLm9ic2VydmVyO1xuICAgIGlmIChvYnNlcnZlcikge1xuICAgICAgb2JzZXJ2ZXIucmVtb3ZlQWxsTGlzdGVuZXJzKCk7XG4gICAgfVxuICAgIHRoaXMuZnJhZyA9IG51bGw7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMub2JzZXJ2ZXIgPSBudWxsO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmhscyA9IG51bGw7XG4gIH1cbiAgcHVzaChkYXRhLCBpbml0U2VnbWVudERhdGEsIGF1ZGlvQ29kZWMsIHZpZGVvQ29kZWMsIGZyYWcsIHBhcnQsIGR1cmF0aW9uLCBhY2N1cmF0ZVRpbWVPZmZzZXQsIGNodW5rTWV0YSwgZGVmYXVsdEluaXRQVFMpIHtcbiAgICB2YXIgX2ZyYWckaW5pdFNlZ21lbnQsIF9sYXN0RnJhZyRpbml0U2VnbWVudDtcbiAgICBjaHVua01ldGEudHJhbnNtdXhpbmcuc3RhcnQgPSBzZWxmLnBlcmZvcm1hbmNlLm5vdygpO1xuICAgIGNvbnN0IHtcbiAgICAgIHRyYW5zbXV4ZXJcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCB0aW1lT2Zmc2V0ID0gcGFydCA/IHBhcnQuc3RhcnQgOiBmcmFnLnN0YXJ0O1xuICAgIC8vIFRPRE86IHB1c2ggXCJjbGVhci1sZWFkXCIgZGVjcnlwdCBkYXRhIGZvciB1bmVuY3J5cHRlZCBmcmFnbWVudHMgaW4gc3RyZWFtcyB3aXRoIGVuY3J5cHRlZCBvbmVzXG4gICAgY29uc3QgZGVjcnlwdGRhdGEgPSBmcmFnLmRlY3J5cHRkYXRhO1xuICAgIGNvbnN0IGxhc3RGcmFnID0gdGhpcy5mcmFnO1xuICAgIGNvbnN0IGRpc2NvbnRpbnVpdHkgPSAhKGxhc3RGcmFnICYmIGZyYWcuY2MgPT09IGxhc3RGcmFnLmNjKTtcbiAgICBjb25zdCB0cmFja1N3aXRjaCA9ICEobGFzdEZyYWcgJiYgY2h1bmtNZXRhLmxldmVsID09PSBsYXN0RnJhZy5sZXZlbCk7XG4gICAgY29uc3Qgc25EaWZmID0gbGFzdEZyYWcgPyBjaHVua01ldGEuc24gLSBsYXN0RnJhZy5zbiA6IC0xO1xuICAgIGNvbnN0IHBhcnREaWZmID0gdGhpcy5wYXJ0ID8gY2h1bmtNZXRhLnBhcnQgLSB0aGlzLnBhcnQuaW5kZXggOiAtMTtcbiAgICBjb25zdCBwcm9ncmVzc2l2ZSA9IHNuRGlmZiA9PT0gMCAmJiBjaHVua01ldGEuaWQgPiAxICYmIGNodW5rTWV0YS5pZCA9PT0gKGxhc3RGcmFnID09IG51bGwgPyB2b2lkIDAgOiBsYXN0RnJhZy5zdGF0cy5jaHVua0NvdW50KTtcbiAgICBjb25zdCBjb250aWd1b3VzID0gIXRyYWNrU3dpdGNoICYmIChzbkRpZmYgPT09IDEgfHwgc25EaWZmID09PSAwICYmIChwYXJ0RGlmZiA9PT0gMSB8fCBwcm9ncmVzc2l2ZSAmJiBwYXJ0RGlmZiA8PSAwKSk7XG4gICAgY29uc3Qgbm93ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICBpZiAodHJhY2tTd2l0Y2ggfHwgc25EaWZmIHx8IGZyYWcuc3RhdHMucGFyc2luZy5zdGFydCA9PT0gMCkge1xuICAgICAgZnJhZy5zdGF0cy5wYXJzaW5nLnN0YXJ0ID0gbm93O1xuICAgIH1cbiAgICBpZiAocGFydCAmJiAocGFydERpZmYgfHwgIWNvbnRpZ3VvdXMpKSB7XG4gICAgICBwYXJ0LnN0YXRzLnBhcnNpbmcuc3RhcnQgPSBub3c7XG4gICAgfVxuICAgIGNvbnN0IGluaXRTZWdtZW50Q2hhbmdlID0gIShsYXN0RnJhZyAmJiAoKF9mcmFnJGluaXRTZWdtZW50ID0gZnJhZy5pbml0U2VnbWVudCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9mcmFnJGluaXRTZWdtZW50LnVybCkgPT09ICgoX2xhc3RGcmFnJGluaXRTZWdtZW50ID0gbGFzdEZyYWcuaW5pdFNlZ21lbnQpID09IG51bGwgPyB2b2lkIDAgOiBfbGFzdEZyYWckaW5pdFNlZ21lbnQudXJsKSk7XG4gICAgY29uc3Qgc3RhdGUgPSBuZXcgVHJhbnNtdXhTdGF0ZShkaXNjb250aW51aXR5LCBjb250aWd1b3VzLCBhY2N1cmF0ZVRpbWVPZmZzZXQsIHRyYWNrU3dpdGNoLCB0aW1lT2Zmc2V0LCBpbml0U2VnbWVudENoYW5nZSk7XG4gICAgaWYgKCFjb250aWd1b3VzIHx8IGRpc2NvbnRpbnVpdHkgfHwgaW5pdFNlZ21lbnRDaGFuZ2UpIHtcbiAgICAgIGxvZ2dlci5sb2coYFt0cmFuc211eGVyLWludGVyZmFjZSwgJHtmcmFnLnR5cGV9XTogU3RhcnRpbmcgbmV3IHRyYW5zbXV4IHNlc3Npb24gZm9yIHNuOiAke2NodW5rTWV0YS5zbn0gcDogJHtjaHVua01ldGEucGFydH0gbGV2ZWw6ICR7Y2h1bmtNZXRhLmxldmVsfSBpZDogJHtjaHVua01ldGEuaWR9XG4gICAgICAgIGRpc2NvbnRpbnVpdHk6ICR7ZGlzY29udGludWl0eX1cbiAgICAgICAgdHJhY2tTd2l0Y2g6ICR7dHJhY2tTd2l0Y2h9XG4gICAgICAgIGNvbnRpZ3VvdXM6ICR7Y29udGlndW91c31cbiAgICAgICAgYWNjdXJhdGVUaW1lT2Zmc2V0OiAke2FjY3VyYXRlVGltZU9mZnNldH1cbiAgICAgICAgdGltZU9mZnNldDogJHt0aW1lT2Zmc2V0fVxuICAgICAgICBpbml0U2VnbWVudENoYW5nZTogJHtpbml0U2VnbWVudENoYW5nZX1gKTtcbiAgICAgIGNvbnN0IGNvbmZpZyA9IG5ldyBUcmFuc211eENvbmZpZyhhdWRpb0NvZGVjLCB2aWRlb0NvZGVjLCBpbml0U2VnbWVudERhdGEsIGR1cmF0aW9uLCBkZWZhdWx0SW5pdFBUUyk7XG4gICAgICB0aGlzLmNvbmZpZ3VyZVRyYW5zbXV4ZXIoY29uZmlnKTtcbiAgICB9XG4gICAgdGhpcy5mcmFnID0gZnJhZztcbiAgICB0aGlzLnBhcnQgPSBwYXJ0O1xuXG4gICAgLy8gRnJhZ3Mgd2l0aCBzbiBvZiAnaW5pdFNlZ21lbnQnIGFyZSBub3QgdHJhbnNtdXhlZFxuICAgIGlmICh0aGlzLndvcmtlckNvbnRleHQpIHtcbiAgICAgIC8vIHBvc3QgZnJhZ21lbnQgcGF5bG9hZCBhcyB0cmFuc2ZlcmFibGUgb2JqZWN0cyBmb3IgQXJyYXlCdWZmZXIgKG5vIGNvcHkpXG4gICAgICB0aGlzLndvcmtlckNvbnRleHQud29ya2VyLnBvc3RNZXNzYWdlKHtcbiAgICAgICAgY21kOiAnZGVtdXgnLFxuICAgICAgICBkYXRhLFxuICAgICAgICBkZWNyeXB0ZGF0YSxcbiAgICAgICAgY2h1bmtNZXRhLFxuICAgICAgICBzdGF0ZVxuICAgICAgfSwgZGF0YSBpbnN0YW5jZW9mIEFycmF5QnVmZmVyID8gW2RhdGFdIDogW10pO1xuICAgIH0gZWxzZSBpZiAodHJhbnNtdXhlcikge1xuICAgICAgY29uc3QgdHJhbnNtdXhSZXN1bHQgPSB0cmFuc211eGVyLnB1c2goZGF0YSwgZGVjcnlwdGRhdGEsIGNodW5rTWV0YSwgc3RhdGUpO1xuICAgICAgaWYgKGlzUHJvbWlzZSh0cmFuc211eFJlc3VsdCkpIHtcbiAgICAgICAgdHJhbnNtdXhlci5hc3luYyA9IHRydWU7XG4gICAgICAgIHRyYW5zbXV4UmVzdWx0LnRoZW4oZGF0YSA9PiB7XG4gICAgICAgICAgdGhpcy5oYW5kbGVUcmFuc211eENvbXBsZXRlKGRhdGEpO1xuICAgICAgICB9KS5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgICAgdGhpcy50cmFuc211eGVyRXJyb3IoZXJyb3IsIGNodW5rTWV0YSwgJ3RyYW5zbXV4ZXItaW50ZXJmYWNlIHB1c2ggZXJyb3InKTtcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0cmFuc211eGVyLmFzeW5jID0gZmFsc2U7XG4gICAgICAgIHRoaXMuaGFuZGxlVHJhbnNtdXhDb21wbGV0ZSh0cmFuc211eFJlc3VsdCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGZsdXNoKGNodW5rTWV0YSkge1xuICAgIGNodW5rTWV0YS50cmFuc211eGluZy5zdGFydCA9IHNlbGYucGVyZm9ybWFuY2Uubm93KCk7XG4gICAgY29uc3Qge1xuICAgICAgdHJhbnNtdXhlclxuICAgIH0gPSB0aGlzO1xuICAgIGlmICh0aGlzLndvcmtlckNvbnRleHQpIHtcbiAgICAgIHRoaXMud29ya2VyQ29udGV4dC53b3JrZXIucG9zdE1lc3NhZ2Uoe1xuICAgICAgICBjbWQ6ICdmbHVzaCcsXG4gICAgICAgIGNodW5rTWV0YVxuICAgICAgfSk7XG4gICAgfSBlbHNlIGlmICh0cmFuc211eGVyKSB7XG4gICAgICBsZXQgdHJhbnNtdXhSZXN1bHQgPSB0cmFuc211eGVyLmZsdXNoKGNodW5rTWV0YSk7XG4gICAgICBjb25zdCBhc3luY0ZsdXNoID0gaXNQcm9taXNlKHRyYW5zbXV4UmVzdWx0KTtcbiAgICAgIGlmIChhc3luY0ZsdXNoIHx8IHRyYW5zbXV4ZXIuYXN5bmMpIHtcbiAgICAgICAgaWYgKCFpc1Byb21pc2UodHJhbnNtdXhSZXN1bHQpKSB7XG4gICAgICAgICAgdHJhbnNtdXhSZXN1bHQgPSBQcm9taXNlLnJlc29sdmUodHJhbnNtdXhSZXN1bHQpO1xuICAgICAgICB9XG4gICAgICAgIHRyYW5zbXV4UmVzdWx0LnRoZW4oZGF0YSA9PiB7XG4gICAgICAgICAgdGhpcy5oYW5kbGVGbHVzaFJlc3VsdChkYXRhLCBjaHVua01ldGEpO1xuICAgICAgICB9KS5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgICAgdGhpcy50cmFuc211eGVyRXJyb3IoZXJyb3IsIGNodW5rTWV0YSwgJ3RyYW5zbXV4ZXItaW50ZXJmYWNlIGZsdXNoIGVycm9yJyk7XG4gICAgICAgIH0pO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5oYW5kbGVGbHVzaFJlc3VsdCh0cmFuc211eFJlc3VsdCwgY2h1bmtNZXRhKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgdHJhbnNtdXhlckVycm9yKGVycm9yLCBjaHVua01ldGEsIHJlYXNvbikge1xuICAgIGlmICghdGhpcy5obHMpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5lcnJvciA9IGVycm9yO1xuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCB7XG4gICAgICB0eXBlOiBFcnJvclR5cGVzLk1FRElBX0VSUk9SLFxuICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLkZSQUdfUEFSU0lOR19FUlJPUixcbiAgICAgIGNodW5rTWV0YSxcbiAgICAgIGZyYWc6IHRoaXMuZnJhZyB8fCB1bmRlZmluZWQsXG4gICAgICBmYXRhbDogZmFsc2UsXG4gICAgICBlcnJvcixcbiAgICAgIGVycjogZXJyb3IsXG4gICAgICByZWFzb25cbiAgICB9KTtcbiAgfVxuICBoYW5kbGVGbHVzaFJlc3VsdChyZXN1bHRzLCBjaHVua01ldGEpIHtcbiAgICByZXN1bHRzLmZvckVhY2gocmVzdWx0ID0+IHtcbiAgICAgIHRoaXMuaGFuZGxlVHJhbnNtdXhDb21wbGV0ZShyZXN1bHQpO1xuICAgIH0pO1xuICAgIHRoaXMub25GbHVzaChjaHVua01ldGEpO1xuICB9XG4gIG9uV29ya2VyTWVzc2FnZShldmVudCkge1xuICAgIGNvbnN0IGRhdGEgPSBldmVudC5kYXRhO1xuICAgIGlmICghKGRhdGEgIT0gbnVsbCAmJiBkYXRhLmV2ZW50KSkge1xuICAgICAgbG9nZ2VyLndhcm4oYHdvcmtlciBtZXNzYWdlIHJlY2VpdmVkIHdpdGggbm8gJHtkYXRhID8gJ2V2ZW50IG5hbWUnIDogJ2RhdGEnfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBobHMgPSB0aGlzLmhscztcbiAgICBpZiAoIXRoaXMuaGxzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHN3aXRjaCAoZGF0YS5ldmVudCkge1xuICAgICAgY2FzZSAnaW5pdCc6XG4gICAgICAgIHtcbiAgICAgICAgICB2YXIgX3RoaXMkd29ya2VyQ29udGV4dDtcbiAgICAgICAgICBjb25zdCBvYmplY3RVUkwgPSAoX3RoaXMkd29ya2VyQ29udGV4dCA9IHRoaXMud29ya2VyQ29udGV4dCkgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJHdvcmtlckNvbnRleHQub2JqZWN0VVJMO1xuICAgICAgICAgIGlmIChvYmplY3RVUkwpIHtcbiAgICAgICAgICAgIC8vIHJldm9rZSB0aGUgT2JqZWN0IFVSTCB0aGF0IHdhcyB1c2VkIHRvIGNyZWF0ZSB0cmFuc211eGVyIHdvcmtlciwgc28gYXMgbm90IHRvIGxlYWsgaXRcbiAgICAgICAgICAgIHNlbGYuVVJMLnJldm9rZU9iamVjdFVSTChvYmplY3RVUkwpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgY2FzZSAndHJhbnNtdXhDb21wbGV0ZSc6XG4gICAgICAgIHtcbiAgICAgICAgICB0aGlzLmhhbmRsZVRyYW5zbXV4Q29tcGxldGUoZGF0YS5kYXRhKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgY2FzZSAnZmx1c2gnOlxuICAgICAgICB7XG4gICAgICAgICAgdGhpcy5vbkZsdXNoKGRhdGEuZGF0YSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cblxuICAgICAgLy8gcGFzcyBsb2dzIGZyb20gdGhlIHdvcmtlciB0aHJlYWQgdG8gdGhlIG1haW4gbG9nZ2VyXG4gICAgICBjYXNlICd3b3JrZXJMb2cnOlxuICAgICAgICBpZiAobG9nZ2VyW2RhdGEuZGF0YS5sb2dUeXBlXSkge1xuICAgICAgICAgIGxvZ2dlcltkYXRhLmRhdGEubG9nVHlwZV0oZGF0YS5kYXRhLm1lc3NhZ2UpO1xuICAgICAgICB9XG4gICAgICAgIGJyZWFrO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAge1xuICAgICAgICAgIGRhdGEuZGF0YSA9IGRhdGEuZGF0YSB8fCB7fTtcbiAgICAgICAgICBkYXRhLmRhdGEuZnJhZyA9IHRoaXMuZnJhZztcbiAgICAgICAgICBkYXRhLmRhdGEuaWQgPSB0aGlzLmlkO1xuICAgICAgICAgIGhscy50cmlnZ2VyKGRhdGEuZXZlbnQsIGRhdGEuZGF0YSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICB9XG4gIH1cbiAgY29uZmlndXJlVHJhbnNtdXhlcihjb25maWcpIHtcbiAgICBjb25zdCB7XG4gICAgICB0cmFuc211eGVyXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKHRoaXMud29ya2VyQ29udGV4dCkge1xuICAgICAgdGhpcy53b3JrZXJDb250ZXh0Lndvcmtlci5wb3N0TWVzc2FnZSh7XG4gICAgICAgIGNtZDogJ2NvbmZpZ3VyZScsXG4gICAgICAgIGNvbmZpZ1xuICAgICAgfSk7XG4gICAgfSBlbHNlIGlmICh0cmFuc211eGVyKSB7XG4gICAgICB0cmFuc211eGVyLmNvbmZpZ3VyZShjb25maWcpO1xuICAgIH1cbiAgfVxuICBoYW5kbGVUcmFuc211eENvbXBsZXRlKHJlc3VsdCkge1xuICAgIHJlc3VsdC5jaHVua01ldGEudHJhbnNtdXhpbmcuZW5kID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICB0aGlzLm9uVHJhbnNtdXhDb21wbGV0ZShyZXN1bHQpO1xuICB9XG59XG5cbmZ1bmN0aW9uIHN1YnRpdGxlT3B0aW9uc0lkZW50aWNhbCh0cmFja0xpc3QxLCB0cmFja0xpc3QyKSB7XG4gIGlmICh0cmFja0xpc3QxLmxlbmd0aCAhPT0gdHJhY2tMaXN0Mi5sZW5ndGgpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgZm9yIChsZXQgaSA9IDA7IGkgPCB0cmFja0xpc3QxLmxlbmd0aDsgaSsrKSB7XG4gICAgaWYgKCFtZWRpYUF0dHJpYnV0ZXNJZGVudGljYWwodHJhY2tMaXN0MVtpXS5hdHRycywgdHJhY2tMaXN0MltpXS5hdHRycykpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5mdW5jdGlvbiBtZWRpYUF0dHJpYnV0ZXNJZGVudGljYWwoYXR0cnMxLCBhdHRyczIsIGN1c3RvbUF0dHJpYnV0ZXMpIHtcbiAgLy8gTWVkaWEgb3B0aW9ucyB3aXRoIHRoZSBzYW1lIHJlbmRpdGlvbiBJRCBtdXN0IGJlIGJpdCBpZGVudGljYWxcbiAgY29uc3Qgc3RhYmxlUmVuZGl0aW9uSWQgPSBhdHRyczFbJ1NUQUJMRS1SRU5ESVRJT04tSUQnXTtcbiAgaWYgKHN0YWJsZVJlbmRpdGlvbklkICYmICFjdXN0b21BdHRyaWJ1dGVzKSB7XG4gICAgcmV0dXJuIHN0YWJsZVJlbmRpdGlvbklkID09PSBhdHRyczJbJ1NUQUJMRS1SRU5ESVRJT04tSUQnXTtcbiAgfVxuICAvLyBXaGVuIHJlbmRpdGlvbiBJRCBpcyBub3QgcHJlc2VudCwgY29tcGFyZSBhdHRyaWJ1dGVzXG4gIHJldHVybiAhKGN1c3RvbUF0dHJpYnV0ZXMgfHwgWydMQU5HVUFHRScsICdOQU1FJywgJ0NIQVJBQ1RFUklTVElDUycsICdBVVRPU0VMRUNUJywgJ0RFRkFVTFQnLCAnRk9SQ0VEJywgJ0FTU09DLUxBTkdVQUdFJ10pLnNvbWUoc3VidGl0bGVBdHRyaWJ1dGUgPT4gYXR0cnMxW3N1YnRpdGxlQXR0cmlidXRlXSAhPT0gYXR0cnMyW3N1YnRpdGxlQXR0cmlidXRlXSk7XG59XG5mdW5jdGlvbiBzdWJ0aXRsZVRyYWNrTWF0Y2hlc1RleHRUcmFjayhzdWJ0aXRsZVRyYWNrLCB0ZXh0VHJhY2spIHtcbiAgcmV0dXJuIHRleHRUcmFjay5sYWJlbC50b0xvd2VyQ2FzZSgpID09PSBzdWJ0aXRsZVRyYWNrLm5hbWUudG9Mb3dlckNhc2UoKSAmJiAoIXRleHRUcmFjay5sYW5ndWFnZSB8fCB0ZXh0VHJhY2subGFuZ3VhZ2UudG9Mb3dlckNhc2UoKSA9PT0gKHN1YnRpdGxlVHJhY2subGFuZyB8fCAnJykudG9Mb3dlckNhc2UoKSk7XG59XG5cbmNvbnN0IFRJQ0tfSU5URVJWQUwkMiA9IDEwMDsgLy8gaG93IG9mdGVuIHRvIHRpY2sgaW4gbXNcblxuY2xhc3MgQXVkaW9TdHJlYW1Db250cm9sbGVyIGV4dGVuZHMgQmFzZVN0cmVhbUNvbnRyb2xsZXIge1xuICBjb25zdHJ1Y3RvcihobHMsIGZyYWdtZW50VHJhY2tlciwga2V5TG9hZGVyKSB7XG4gICAgc3VwZXIoaGxzLCBmcmFnbWVudFRyYWNrZXIsIGtleUxvYWRlciwgJ1thdWRpby1zdHJlYW0tY29udHJvbGxlcl0nLCBQbGF5bGlzdExldmVsVHlwZS5BVURJTyk7XG4gICAgdGhpcy52aWRlb0J1ZmZlciA9IG51bGw7XG4gICAgdGhpcy52aWRlb1RyYWNrQ0MgPSAtMTtcbiAgICB0aGlzLndhaXRpbmdWaWRlb0NDID0gLTE7XG4gICAgdGhpcy5idWZmZXJlZFRyYWNrID0gbnVsbDtcbiAgICB0aGlzLnN3aXRjaGluZ1RyYWNrID0gbnVsbDtcbiAgICB0aGlzLnRyYWNrSWQgPSAtMTtcbiAgICB0aGlzLndhaXRpbmdEYXRhID0gbnVsbDtcbiAgICB0aGlzLm1haW5EZXRhaWxzID0gbnVsbDtcbiAgICB0aGlzLmZsdXNoaW5nID0gZmFsc2U7XG4gICAgdGhpcy5idWZmZXJGbHVzaGVkID0gZmFsc2U7XG4gICAgdGhpcy5jYWNoZWRUcmFja0xvYWRlZERhdGEgPSBudWxsO1xuICAgIHRoaXMuX3JlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gIH1cbiAgb25IYW5kbGVyRGVzdHJveWluZygpIHtcbiAgICB0aGlzLl91bnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgc3VwZXIub25IYW5kbGVyRGVzdHJveWluZygpO1xuICAgIHRoaXMubWFpbkRldGFpbHMgPSBudWxsO1xuICAgIHRoaXMuYnVmZmVyZWRUcmFjayA9IG51bGw7XG4gICAgdGhpcy5zd2l0Y2hpbmdUcmFjayA9IG51bGw7XG4gIH1cbiAgX3JlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1FRElBX0RFVEFDSElORywgdGhpcy5vbk1lZGlhRGV0YWNoaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTEVWRUxfTE9BREVELCB0aGlzLm9uTGV2ZWxMb2FkZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQVVESU9fVFJBQ0tTX1VQREFURUQsIHRoaXMub25BdWRpb1RyYWNrc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQVVESU9fVFJBQ0tfU1dJVENISU5HLCB0aGlzLm9uQXVkaW9UcmFja1N3aXRjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5BVURJT19UUkFDS19MT0FERUQsIHRoaXMub25BdWRpb1RyYWNrTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQlVGRkVSX1JFU0VULCB0aGlzLm9uQnVmZmVyUmVzZXQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQlVGRkVSX0NSRUFURUQsIHRoaXMub25CdWZmZXJDcmVhdGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9GTFVTSElORywgdGhpcy5vbkJ1ZmZlckZsdXNoaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9GTFVTSEVELCB0aGlzLm9uQnVmZmVyRmx1c2hlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5JTklUX1BUU19GT1VORCwgdGhpcy5vbkluaXRQdHNGb3VuZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgfVxuICBfdW5yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub2ZmKEV2ZW50cy5NRURJQV9BVFRBQ0hFRCwgdGhpcy5vbk1lZGlhQXR0YWNoZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1FRElBX0RFVEFDSElORywgdGhpcy5vbk1lZGlhRGV0YWNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5MRVZFTF9MT0FERUQsIHRoaXMub25MZXZlbExvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuQVVESU9fVFJBQ0tTX1VQREFURUQsIHRoaXMub25BdWRpb1RyYWNrc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkFVRElPX1RSQUNLX1NXSVRDSElORywgdGhpcy5vbkF1ZGlvVHJhY2tTd2l0Y2hpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkFVRElPX1RSQUNLX0xPQURFRCwgdGhpcy5vbkF1ZGlvVHJhY2tMb2FkZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9SRVNFVCwgdGhpcy5vbkJ1ZmZlclJlc2V0LCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfQ1JFQVRFRCwgdGhpcy5vbkJ1ZmZlckNyZWF0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9GTFVTSElORywgdGhpcy5vbkJ1ZmZlckZsdXNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfRkxVU0hFRCwgdGhpcy5vbkJ1ZmZlckZsdXNoZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLklOSVRfUFRTX0ZPVU5ELCB0aGlzLm9uSW5pdFB0c0ZvdW5kLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgfVxuXG4gIC8vIElOSVRfUFRTX0ZPVU5EIGlzIHRyaWdnZXJlZCB3aGVuIHRoZSB2aWRlbyB0cmFjayBwYXJzZWQgaW4gdGhlIHN0cmVhbS1jb250cm9sbGVyIGhhcyBhIG5ldyBQVFMgdmFsdWVcbiAgb25Jbml0UHRzRm91bmQoZXZlbnQsIHtcbiAgICBmcmFnLFxuICAgIGlkLFxuICAgIGluaXRQVFMsXG4gICAgdGltZXNjYWxlXG4gIH0pIHtcbiAgICAvLyBBbHdheXMgdXBkYXRlIHRoZSBuZXcgSU5JVCBQVFNcbiAgICAvLyBDYW4gY2hhbmdlIGR1ZSBsZXZlbCBzd2l0Y2hcbiAgICBpZiAoaWQgPT09ICdtYWluJykge1xuICAgICAgY29uc3QgY2MgPSBmcmFnLmNjO1xuICAgICAgdGhpcy5pbml0UFRTW2ZyYWcuY2NdID0ge1xuICAgICAgICBiYXNlVGltZTogaW5pdFBUUyxcbiAgICAgICAgdGltZXNjYWxlXG4gICAgICB9O1xuICAgICAgdGhpcy5sb2coYEluaXRQVFMgZm9yIGNjOiAke2NjfSBmb3VuZCBmcm9tIG1haW46ICR7aW5pdFBUU31gKTtcbiAgICAgIHRoaXMudmlkZW9UcmFja0NDID0gY2M7XG4gICAgICAvLyBJZiB3ZSBhcmUgd2FpdGluZywgdGljayBpbW1lZGlhdGVseSB0byB1bmJsb2NrIGF1ZGlvIGZyYWdtZW50IHRyYW5zbXV4aW5nXG4gICAgICBpZiAodGhpcy5zdGF0ZSA9PT0gU3RhdGUuV0FJVElOR19JTklUX1BUUykge1xuICAgICAgICB0aGlzLnRpY2soKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgc3RhcnRMb2FkKHN0YXJ0UG9zaXRpb24pIHtcbiAgICBpZiAoIXRoaXMubGV2ZWxzKSB7XG4gICAgICB0aGlzLnN0YXJ0UG9zaXRpb24gPSBzdGFydFBvc2l0aW9uO1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLlNUT1BQRUQ7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGxhc3RDdXJyZW50VGltZSA9IHRoaXMubGFzdEN1cnJlbnRUaW1lO1xuICAgIHRoaXMuc3RvcExvYWQoKTtcbiAgICB0aGlzLnNldEludGVydmFsKFRJQ0tfSU5URVJWQUwkMik7XG4gICAgaWYgKGxhc3RDdXJyZW50VGltZSA+IDAgJiYgc3RhcnRQb3NpdGlvbiA9PT0gLTEpIHtcbiAgICAgIHRoaXMubG9nKGBPdmVycmlkZSBzdGFydFBvc2l0aW9uIHdpdGggbGFzdEN1cnJlbnRUaW1lIEAke2xhc3RDdXJyZW50VGltZS50b0ZpeGVkKDMpfWApO1xuICAgICAgc3RhcnRQb3NpdGlvbiA9IGxhc3RDdXJyZW50VGltZTtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmxvYWRlZG1ldGFkYXRhID0gZmFsc2U7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuV0FJVElOR19UUkFDSztcbiAgICB9XG4gICAgdGhpcy5uZXh0TG9hZFBvc2l0aW9uID0gdGhpcy5zdGFydFBvc2l0aW9uID0gdGhpcy5sYXN0Q3VycmVudFRpbWUgPSBzdGFydFBvc2l0aW9uO1xuICAgIHRoaXMudGljaygpO1xuICB9XG4gIGRvVGljaygpIHtcbiAgICBzd2l0Y2ggKHRoaXMuc3RhdGUpIHtcbiAgICAgIGNhc2UgU3RhdGUuSURMRTpcbiAgICAgICAgdGhpcy5kb1RpY2tJZGxlKCk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBTdGF0ZS5XQUlUSU5HX1RSQUNLOlxuICAgICAgICB7XG4gICAgICAgICAgdmFyIF9sZXZlbHMkdHJhY2tJZDtcbiAgICAgICAgICBjb25zdCB7XG4gICAgICAgICAgICBsZXZlbHMsXG4gICAgICAgICAgICB0cmFja0lkXG4gICAgICAgICAgfSA9IHRoaXM7XG4gICAgICAgICAgY29uc3QgZGV0YWlscyA9IGxldmVscyA9PSBudWxsID8gdm9pZCAwIDogKF9sZXZlbHMkdHJhY2tJZCA9IGxldmVsc1t0cmFja0lkXSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9sZXZlbHMkdHJhY2tJZC5kZXRhaWxzO1xuICAgICAgICAgIGlmIChkZXRhaWxzKSB7XG4gICAgICAgICAgICBpZiAodGhpcy53YWl0Rm9yQ2RuVHVuZUluKGRldGFpbHMpKSB7XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLldBSVRJTkdfSU5JVF9QVFM7XG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICBjYXNlIFN0YXRlLkZSQUdfTE9BRElOR19XQUlUSU5HX1JFVFJZOlxuICAgICAgICB7XG4gICAgICAgICAgdmFyIF90aGlzJG1lZGlhO1xuICAgICAgICAgIGNvbnN0IG5vdyA9IHBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICAgIGNvbnN0IHJldHJ5RGF0ZSA9IHRoaXMucmV0cnlEYXRlO1xuICAgICAgICAgIC8vIGlmIGN1cnJlbnQgdGltZSBpcyBndCB0aGFuIHJldHJ5RGF0ZSwgb3IgaWYgbWVkaWEgc2Vla2luZyBsZXQncyBzd2l0Y2ggdG8gSURMRSBzdGF0ZSB0byByZXRyeSBsb2FkaW5nXG4gICAgICAgICAgaWYgKCFyZXRyeURhdGUgfHwgbm93ID49IHJldHJ5RGF0ZSB8fCAoX3RoaXMkbWVkaWEgPSB0aGlzLm1lZGlhKSAhPSBudWxsICYmIF90aGlzJG1lZGlhLnNlZWtpbmcpIHtcbiAgICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgICAgbGV2ZWxzLFxuICAgICAgICAgICAgICB0cmFja0lkXG4gICAgICAgICAgICB9ID0gdGhpcztcbiAgICAgICAgICAgIHRoaXMubG9nKCdSZXRyeURhdGUgcmVhY2hlZCwgc3dpdGNoIGJhY2sgdG8gSURMRSBzdGF0ZScpO1xuICAgICAgICAgICAgdGhpcy5yZXNldFN0YXJ0V2hlbk5vdExvYWRlZCgobGV2ZWxzID09IG51bGwgPyB2b2lkIDAgOiBsZXZlbHNbdHJhY2tJZF0pIHx8IG51bGwpO1xuICAgICAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICAgICAgfVxuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICBjYXNlIFN0YXRlLldBSVRJTkdfSU5JVF9QVFM6XG4gICAgICAgIHtcbiAgICAgICAgICAvLyBFbnN1cmUgd2UgZG9uJ3QgZ2V0IHN0dWNrIGluIHRoZSBXQUlUSU5HX0lOSVRfUFRTIHN0YXRlIGlmIHRoZSB3YWl0aW5nIGZyYWcgQ0MgZG9lc24ndCBtYXRjaCBhbnkgaW5pdFBUU1xuICAgICAgICAgIGNvbnN0IHdhaXRpbmdEYXRhID0gdGhpcy53YWl0aW5nRGF0YTtcbiAgICAgICAgICBpZiAod2FpdGluZ0RhdGEpIHtcbiAgICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgICAgZnJhZyxcbiAgICAgICAgICAgICAgcGFydCxcbiAgICAgICAgICAgICAgY2FjaGUsXG4gICAgICAgICAgICAgIGNvbXBsZXRlXG4gICAgICAgICAgICB9ID0gd2FpdGluZ0RhdGE7XG4gICAgICAgICAgICBpZiAodGhpcy5pbml0UFRTW2ZyYWcuY2NdICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgdGhpcy53YWl0aW5nRGF0YSA9IG51bGw7XG4gICAgICAgICAgICAgIHRoaXMud2FpdGluZ1ZpZGVvQ0MgPSAtMTtcbiAgICAgICAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLkZSQUdfTE9BRElORztcbiAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZCA9IGNhY2hlLmZsdXNoKCk7XG4gICAgICAgICAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgICAgICAgICAgZnJhZyxcbiAgICAgICAgICAgICAgICBwYXJ0LFxuICAgICAgICAgICAgICAgIHBheWxvYWQsXG4gICAgICAgICAgICAgICAgbmV0d29ya0RldGFpbHM6IG51bGxcbiAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgdGhpcy5faGFuZGxlRnJhZ21lbnRMb2FkUHJvZ3Jlc3MoZGF0YSk7XG4gICAgICAgICAgICAgIGlmIChjb21wbGV0ZSkge1xuICAgICAgICAgICAgICAgIHN1cGVyLl9oYW5kbGVGcmFnbWVudExvYWRDb21wbGV0ZShkYXRhKTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIGlmICh0aGlzLnZpZGVvVHJhY2tDQyAhPT0gdGhpcy53YWl0aW5nVmlkZW9DQykge1xuICAgICAgICAgICAgICAvLyBEcm9wIHdhaXRpbmcgZnJhZ21lbnQgaWYgdmlkZW9UcmFja0NDIGhhcyBjaGFuZ2VkIHNpbmNlIHdhaXRpbmdGcmFnbWVudCB3YXMgc2V0IGFuZCBpbml0UFRTIHdhcyBub3QgZm91bmRcbiAgICAgICAgICAgICAgdGhpcy5sb2coYFdhaXRpbmcgZnJhZ21lbnQgY2MgKCR7ZnJhZy5jY30pIGNhbmNlbGxlZCBiZWNhdXNlIHZpZGVvIGlzIGF0IGNjICR7dGhpcy52aWRlb1RyYWNrQ0N9YCk7XG4gICAgICAgICAgICAgIHRoaXMuY2xlYXJXYWl0aW5nRnJhZ21lbnQoKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIC8vIERyb3Agd2FpdGluZyBmcmFnbWVudCBpZiBhbiBlYXJsaWVyIGZyYWdtZW50IGlzIG5lZWRlZFxuICAgICAgICAgICAgICBjb25zdCBwb3MgPSB0aGlzLmdldExvYWRQb3NpdGlvbigpO1xuICAgICAgICAgICAgICBjb25zdCBidWZmZXJJbmZvID0gQnVmZmVySGVscGVyLmJ1ZmZlckluZm8odGhpcy5tZWRpYUJ1ZmZlciwgcG9zLCB0aGlzLmNvbmZpZy5tYXhCdWZmZXJIb2xlKTtcbiAgICAgICAgICAgICAgY29uc3Qgd2FpdGluZ0ZyYWdtZW50QXRQb3NpdGlvbiA9IGZyYWdtZW50V2l0aGluVG9sZXJhbmNlVGVzdChidWZmZXJJbmZvLmVuZCwgdGhpcy5jb25maWcubWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSwgZnJhZyk7XG4gICAgICAgICAgICAgIGlmICh3YWl0aW5nRnJhZ21lbnRBdFBvc2l0aW9uIDwgMCkge1xuICAgICAgICAgICAgICAgIHRoaXMubG9nKGBXYWl0aW5nIGZyYWdtZW50IGNjICgke2ZyYWcuY2N9KSBAICR7ZnJhZy5zdGFydH0gY2FuY2VsbGVkIGJlY2F1c2UgYW5vdGhlciBmcmFnbWVudCBhdCAke2J1ZmZlckluZm8uZW5kfSBpcyBuZWVkZWRgKTtcbiAgICAgICAgICAgICAgICB0aGlzLmNsZWFyV2FpdGluZ0ZyYWdtZW50KCk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIHRoaXMub25UaWNrRW5kKCk7XG4gIH1cbiAgY2xlYXJXYWl0aW5nRnJhZ21lbnQoKSB7XG4gICAgY29uc3Qgd2FpdGluZ0RhdGEgPSB0aGlzLndhaXRpbmdEYXRhO1xuICAgIGlmICh3YWl0aW5nRGF0YSkge1xuICAgICAgdGhpcy5mcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQod2FpdGluZ0RhdGEuZnJhZyk7XG4gICAgICB0aGlzLndhaXRpbmdEYXRhID0gbnVsbDtcbiAgICAgIHRoaXMud2FpdGluZ1ZpZGVvQ0MgPSAtMTtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIH1cbiAgfVxuICByZXNldExvYWRpbmdTdGF0ZSgpIHtcbiAgICB0aGlzLmNsZWFyV2FpdGluZ0ZyYWdtZW50KCk7XG4gICAgc3VwZXIucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgfVxuICBvblRpY2tFbmQoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbWVkaWFcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIShtZWRpYSAhPSBudWxsICYmIG1lZGlhLnJlYWR5U3RhdGUpKSB7XG4gICAgICAvLyBFeGl0IGVhcmx5IGlmIHdlIGRvbid0IGhhdmUgbWVkaWEgb3IgaWYgdGhlIG1lZGlhIGhhc24ndCBidWZmZXJlZCBhbnl0aGluZyB5ZXQgKHJlYWR5U3RhdGUgMClcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5sYXN0Q3VycmVudFRpbWUgPSBtZWRpYS5jdXJyZW50VGltZTtcbiAgfVxuICBkb1RpY2tJZGxlKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhscyxcbiAgICAgIGxldmVscyxcbiAgICAgIG1lZGlhLFxuICAgICAgdHJhY2tJZFxuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IGNvbmZpZyA9IGhscy5jb25maWc7XG5cbiAgICAvLyAxLiBpZiB2aWRlbyBub3QgYXR0YWNoZWQgQU5EXG4gICAgLy8gICAgc3RhcnQgZnJhZ21lbnQgYWxyZWFkeSByZXF1ZXN0ZWQgT1Igc3RhcnQgZnJhZyBwcmVmZXRjaCBub3QgZW5hYmxlZFxuICAgIC8vIDIuIGlmIHRyYWNrcyBvciB0cmFjayBub3QgbG9hZGVkIGFuZCBzZWxlY3RlZFxuICAgIC8vIHRoZW4gZXhpdCBsb29wXG4gICAgLy8gPT4gaWYgbWVkaWEgbm90IGF0dGFjaGVkIGJ1dCBzdGFydCBmcmFnIHByZWZldGNoIGlzIGVuYWJsZWQgYW5kIHN0YXJ0IGZyYWcgbm90IHJlcXVlc3RlZCB5ZXQsIHdlIHdpbGwgbm90IGV4aXQgbG9vcFxuICAgIGlmICghbWVkaWEgJiYgKHRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkIHx8ICFjb25maWcuc3RhcnRGcmFnUHJlZmV0Y2gpIHx8ICEobGV2ZWxzICE9IG51bGwgJiYgbGV2ZWxzW3RyYWNrSWRdKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBsZXZlbEluZm8gPSBsZXZlbHNbdHJhY2tJZF07XG4gICAgY29uc3QgdHJhY2tEZXRhaWxzID0gbGV2ZWxJbmZvLmRldGFpbHM7XG4gICAgaWYgKCF0cmFja0RldGFpbHMgfHwgdHJhY2tEZXRhaWxzLmxpdmUgJiYgdGhpcy5sZXZlbExhc3RMb2FkZWQgIT09IGxldmVsSW5mbyB8fCB0aGlzLndhaXRGb3JDZG5UdW5lSW4odHJhY2tEZXRhaWxzKSkge1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLldBSVRJTkdfVFJBQ0s7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGJ1ZmZlcmFibGUgPSB0aGlzLm1lZGlhQnVmZmVyID8gdGhpcy5tZWRpYUJ1ZmZlciA6IHRoaXMubWVkaWE7XG4gICAgaWYgKHRoaXMuYnVmZmVyRmx1c2hlZCAmJiBidWZmZXJhYmxlKSB7XG4gICAgICB0aGlzLmJ1ZmZlckZsdXNoZWQgPSBmYWxzZTtcbiAgICAgIHRoaXMuYWZ0ZXJCdWZmZXJGbHVzaGVkKGJ1ZmZlcmFibGUsIEVsZW1lbnRhcnlTdHJlYW1UeXBlcy5BVURJTywgUGxheWxpc3RMZXZlbFR5cGUuQVVESU8pO1xuICAgIH1cbiAgICBjb25zdCBidWZmZXJJbmZvID0gdGhpcy5nZXRGd2RCdWZmZXJJbmZvKGJ1ZmZlcmFibGUsIFBsYXlsaXN0TGV2ZWxUeXBlLkFVRElPKTtcbiAgICBpZiAoYnVmZmVySW5mbyA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7XG4gICAgICBidWZmZXJlZFRyYWNrLFxuICAgICAgc3dpdGNoaW5nVHJhY2tcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIXN3aXRjaGluZ1RyYWNrICYmIHRoaXMuX3N0cmVhbUVuZGVkKGJ1ZmZlckluZm8sIHRyYWNrRGV0YWlscykpIHtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfRU9TLCB7XG4gICAgICAgIHR5cGU6ICdhdWRpbydcbiAgICAgIH0pO1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLkVOREVEO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBtYWluQnVmZmVySW5mbyA9IHRoaXMuZ2V0RndkQnVmZmVySW5mbyh0aGlzLnZpZGVvQnVmZmVyID8gdGhpcy52aWRlb0J1ZmZlciA6IHRoaXMubWVkaWEsIFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4pO1xuICAgIGNvbnN0IGJ1ZmZlckxlbiA9IGJ1ZmZlckluZm8ubGVuO1xuICAgIGNvbnN0IG1heEJ1ZkxlbiA9IHRoaXMuZ2V0TWF4QnVmZmVyTGVuZ3RoKG1haW5CdWZmZXJJbmZvID09IG51bGwgPyB2b2lkIDAgOiBtYWluQnVmZmVySW5mby5sZW4pO1xuICAgIGNvbnN0IGZyYWdtZW50cyA9IHRyYWNrRGV0YWlscy5mcmFnbWVudHM7XG4gICAgY29uc3Qgc3RhcnQgPSBmcmFnbWVudHNbMF0uc3RhcnQ7XG4gICAgbGV0IHRhcmdldEJ1ZmZlclRpbWUgPSB0aGlzLmZsdXNoaW5nID8gdGhpcy5nZXRMb2FkUG9zaXRpb24oKSA6IGJ1ZmZlckluZm8uZW5kO1xuICAgIGlmIChzd2l0Y2hpbmdUcmFjayAmJiBtZWRpYSkge1xuICAgICAgY29uc3QgcG9zID0gdGhpcy5nZXRMb2FkUG9zaXRpb24oKTtcbiAgICAgIC8vIFNUQUJMRVxuICAgICAgaWYgKGJ1ZmZlcmVkVHJhY2sgJiYgIW1lZGlhQXR0cmlidXRlc0lkZW50aWNhbChzd2l0Y2hpbmdUcmFjay5hdHRycywgYnVmZmVyZWRUcmFjay5hdHRycykpIHtcbiAgICAgICAgdGFyZ2V0QnVmZmVyVGltZSA9IHBvcztcbiAgICAgIH1cbiAgICAgIC8vIGlmIGN1cnJlbnRUaW1lIChwb3MpIGlzIGxlc3MgdGhhbiBhbHQgYXVkaW8gcGxheWxpc3Qgc3RhcnQgdGltZSwgaXQgbWVhbnMgdGhhdCBhbHQgYXVkaW8gaXMgYWhlYWQgb2YgY3VycmVudFRpbWVcbiAgICAgIGlmICh0cmFja0RldGFpbHMuUFRTS25vd24gJiYgcG9zIDwgc3RhcnQpIHtcbiAgICAgICAgLy8gaWYgZXZlcnl0aGluZyBpcyBidWZmZXJlZCBmcm9tIHBvcyB0byBzdGFydCBvciBpZiBhdWRpbyBidWZmZXIgdXBmcm9udCwgbGV0J3Mgc2VlayB0byBzdGFydFxuICAgICAgICBpZiAoYnVmZmVySW5mby5lbmQgPiBzdGFydCB8fCBidWZmZXJJbmZvLm5leHRTdGFydCkge1xuICAgICAgICAgIHRoaXMubG9nKCdBbHQgYXVkaW8gdHJhY2sgYWhlYWQgb2YgbWFpbiB0cmFjaywgc2VlayB0byBzdGFydCBvZiBhbHQgYXVkaW8gdHJhY2snKTtcbiAgICAgICAgICBtZWRpYS5jdXJyZW50VGltZSA9IHN0YXJ0ICsgMC4wNTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIGlmIGJ1ZmZlciBsZW5ndGggaXMgbGVzcyB0aGFuIG1heEJ1Zkxlbiwgb3IgbmVhciB0aGUgZW5kLCBmaW5kIGEgZnJhZ21lbnQgdG8gbG9hZFxuICAgIGlmIChidWZmZXJMZW4gPj0gbWF4QnVmTGVuICYmICFzd2l0Y2hpbmdUcmFjayAmJiB0YXJnZXRCdWZmZXJUaW1lIDwgZnJhZ21lbnRzW2ZyYWdtZW50cy5sZW5ndGggLSAxXS5zdGFydCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsZXQgZnJhZyA9IHRoaXMuZ2V0TmV4dEZyYWdtZW50KHRhcmdldEJ1ZmZlclRpbWUsIHRyYWNrRGV0YWlscyk7XG4gICAgbGV0IGF0R2FwID0gZmFsc2U7XG4gICAgLy8gQXZvaWQgbG9vcCBsb2FkaW5nIGJ5IHVzaW5nIG5leHRMb2FkUG9zaXRpb24gc2V0IGZvciBiYWNrdHJhY2tpbmcgYW5kIHNraXBwaW5nIGNvbnNlY3V0aXZlIEdBUCB0YWdzXG4gICAgaWYgKGZyYWcgJiYgdGhpcy5pc0xvb3BMb2FkaW5nKGZyYWcsIHRhcmdldEJ1ZmZlclRpbWUpKSB7XG4gICAgICBhdEdhcCA9ICEhZnJhZy5nYXA7XG4gICAgICBmcmFnID0gdGhpcy5nZXROZXh0RnJhZ21lbnRMb29wTG9hZGluZyhmcmFnLCB0cmFja0RldGFpbHMsIGJ1ZmZlckluZm8sIFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4sIG1heEJ1Zkxlbik7XG4gICAgfVxuICAgIGlmICghZnJhZykge1xuICAgICAgdGhpcy5idWZmZXJGbHVzaGVkID0gdHJ1ZTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBCdWZmZXIgYXVkaW8gdXAgdG8gb25lIHRhcmdldCBkdXJhdGlvbiBhaGVhZCBvZiBtYWluIGJ1ZmZlclxuICAgIGNvbnN0IGF0QnVmZmVyU3luY0xpbWl0ID0gbWFpbkJ1ZmZlckluZm8gJiYgZnJhZy5zdGFydCA+IG1haW5CdWZmZXJJbmZvLmVuZCArIHRyYWNrRGV0YWlscy50YXJnZXRkdXJhdGlvbjtcbiAgICBpZiAoYXRCdWZmZXJTeW5jTGltaXQgfHxcbiAgICAvLyBPciB3YWl0IGZvciBtYWluIGJ1ZmZlciBhZnRlciBidWZmaW5nIHNvbWUgYXVkaW9cbiAgICAhKG1haW5CdWZmZXJJbmZvICE9IG51bGwgJiYgbWFpbkJ1ZmZlckluZm8ubGVuKSAmJiBidWZmZXJJbmZvLmxlbikge1xuICAgICAgLy8gQ2hlY2sgZnJhZ21lbnQtdHJhY2tlciBmb3IgbWFpbiBmcmFnbWVudHMgc2luY2UgR0FQIHNlZ21lbnRzIGRvIG5vdCBzaG93IHVwIGluIGJ1ZmZlckluZm9cbiAgICAgIGNvbnN0IG1haW5GcmFnID0gdGhpcy5nZXRBcHBlbmRlZEZyYWcoZnJhZy5zdGFydCwgUGxheWxpc3RMZXZlbFR5cGUuTUFJTik7XG4gICAgICBpZiAobWFpbkZyYWcgPT09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgLy8gQnJpZGdlIGdhcHMgaW4gbWFpbiBidWZmZXJcbiAgICAgIGF0R2FwIHx8IChhdEdhcCA9ICEhbWFpbkZyYWcuZ2FwIHx8ICEhYXRCdWZmZXJTeW5jTGltaXQgJiYgbWFpbkJ1ZmZlckluZm8ubGVuID09PSAwKTtcbiAgICAgIGlmIChhdEJ1ZmZlclN5bmNMaW1pdCAmJiAhYXRHYXAgfHwgYXRHYXAgJiYgYnVmZmVySW5mby5uZXh0U3RhcnQgJiYgYnVmZmVySW5mby5uZXh0U3RhcnQgPCBtYWluRnJhZy5lbmQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLmxvYWRGcmFnbWVudChmcmFnLCBsZXZlbEluZm8sIHRhcmdldEJ1ZmZlclRpbWUpO1xuICB9XG4gIGdldE1heEJ1ZmZlckxlbmd0aChtYWluQnVmZmVyTGVuZ3RoKSB7XG4gICAgY29uc3QgbWF4Q29uZmlnQnVmZmVyID0gc3VwZXIuZ2V0TWF4QnVmZmVyTGVuZ3RoKCk7XG4gICAgaWYgKCFtYWluQnVmZmVyTGVuZ3RoKSB7XG4gICAgICByZXR1cm4gbWF4Q29uZmlnQnVmZmVyO1xuICAgIH1cbiAgICByZXR1cm4gTWF0aC5taW4oTWF0aC5tYXgobWF4Q29uZmlnQnVmZmVyLCBtYWluQnVmZmVyTGVuZ3RoKSwgdGhpcy5jb25maWcubWF4TWF4QnVmZmVyTGVuZ3RoKTtcbiAgfVxuICBvbk1lZGlhRGV0YWNoaW5nKCkge1xuICAgIHRoaXMudmlkZW9CdWZmZXIgPSBudWxsO1xuICAgIHRoaXMuYnVmZmVyRmx1c2hlZCA9IHRoaXMuZmx1c2hpbmcgPSBmYWxzZTtcbiAgICBzdXBlci5vbk1lZGlhRGV0YWNoaW5nKCk7XG4gIH1cbiAgb25BdWRpb1RyYWNrc1VwZGF0ZWQoZXZlbnQsIHtcbiAgICBhdWRpb1RyYWNrc1xuICB9KSB7XG4gICAgLy8gUmVzZXQgdHJhbnhtdXhlciBpcyBlc3NlbnRpYWwgZm9yIGxhcmdlIGNvbnRleHQgc3dpdGNoZXMgKENvbnRlbnQgU3RlZXJpbmcpXG4gICAgdGhpcy5yZXNldFRyYW5zbXV4ZXIoKTtcbiAgICB0aGlzLmxldmVscyA9IGF1ZGlvVHJhY2tzLm1hcChtZWRpYVBsYXlsaXN0ID0+IG5ldyBMZXZlbChtZWRpYVBsYXlsaXN0KSk7XG4gIH1cbiAgb25BdWRpb1RyYWNrU3dpdGNoaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgLy8gaWYgYW55IFVSTCBmb3VuZCBvbiBuZXcgYXVkaW8gdHJhY2ssIGl0IGlzIGFuIGFsdGVybmF0ZSBhdWRpbyB0cmFja1xuICAgIGNvbnN0IGFsdEF1ZGlvID0gISFkYXRhLnVybDtcbiAgICB0aGlzLnRyYWNrSWQgPSBkYXRhLmlkO1xuICAgIGNvbnN0IHtcbiAgICAgIGZyYWdDdXJyZW50XG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKGZyYWdDdXJyZW50KSB7XG4gICAgICBmcmFnQ3VycmVudC5hYm9ydFJlcXVlc3RzKCk7XG4gICAgICB0aGlzLnJlbW92ZVVuYnVmZmVyZWRGcmFncyhmcmFnQ3VycmVudC5zdGFydCk7XG4gICAgfVxuICAgIHRoaXMucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgICAvLyBkZXN0cm95IHVzZWxlc3MgdHJhbnNtdXhlciB3aGVuIHN3aXRjaGluZyBhdWRpbyB0byBtYWluXG4gICAgaWYgKCFhbHRBdWRpbykge1xuICAgICAgdGhpcy5yZXNldFRyYW5zbXV4ZXIoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gc3dpdGNoaW5nIHRvIGF1ZGlvIHRyYWNrLCBzdGFydCB0aW1lciBpZiBub3QgYWxyZWFkeSBzdGFydGVkXG4gICAgICB0aGlzLnNldEludGVydmFsKFRJQ0tfSU5URVJWQUwkMik7XG4gICAgfVxuXG4gICAgLy8gc2hvdWxkIHdlIHN3aXRjaCB0cmFja3MgP1xuICAgIGlmIChhbHRBdWRpbykge1xuICAgICAgdGhpcy5zd2l0Y2hpbmdUcmFjayA9IGRhdGE7XG4gICAgICAvLyBtYWluIGF1ZGlvIHRyYWNrIGFyZSBoYW5kbGVkIGJ5IHN0cmVhbS1jb250cm9sbGVyLCBqdXN0IGRvIHNvbWV0aGluZyBpZiBzd2l0Y2hpbmcgdG8gYWx0IGF1ZGlvIHRyYWNrXG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgIHRoaXMuZmx1c2hBdWRpb0lmTmVlZGVkKGRhdGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLnN3aXRjaGluZ1RyYWNrID0gbnVsbDtcbiAgICAgIHRoaXMuYnVmZmVyZWRUcmFjayA9IGRhdGE7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuU1RPUFBFRDtcbiAgICB9XG4gICAgdGhpcy50aWNrKCk7XG4gIH1cbiAgb25NYW5pZmVzdExvYWRpbmcoKSB7XG4gICAgdGhpcy5mcmFnbWVudFRyYWNrZXIucmVtb3ZlQWxsRnJhZ21lbnRzKCk7XG4gICAgdGhpcy5zdGFydFBvc2l0aW9uID0gdGhpcy5sYXN0Q3VycmVudFRpbWUgPSAwO1xuICAgIHRoaXMuYnVmZmVyRmx1c2hlZCA9IHRoaXMuZmx1c2hpbmcgPSBmYWxzZTtcbiAgICB0aGlzLmxldmVscyA9IHRoaXMubWFpbkRldGFpbHMgPSB0aGlzLndhaXRpbmdEYXRhID0gdGhpcy5idWZmZXJlZFRyYWNrID0gdGhpcy5jYWNoZWRUcmFja0xvYWRlZERhdGEgPSB0aGlzLnN3aXRjaGluZ1RyYWNrID0gbnVsbDtcbiAgICB0aGlzLnN0YXJ0RnJhZ1JlcXVlc3RlZCA9IGZhbHNlO1xuICAgIHRoaXMudHJhY2tJZCA9IHRoaXMudmlkZW9UcmFja0NDID0gdGhpcy53YWl0aW5nVmlkZW9DQyA9IC0xO1xuICB9XG4gIG9uTGV2ZWxMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICB0aGlzLm1haW5EZXRhaWxzID0gZGF0YS5kZXRhaWxzO1xuICAgIGlmICh0aGlzLmNhY2hlZFRyYWNrTG9hZGVkRGF0YSAhPT0gbnVsbCkge1xuICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuQVVESU9fVFJBQ0tfTE9BREVELCB0aGlzLmNhY2hlZFRyYWNrTG9hZGVkRGF0YSk7XG4gICAgICB0aGlzLmNhY2hlZFRyYWNrTG9hZGVkRGF0YSA9IG51bGw7XG4gICAgfVxuICB9XG4gIG9uQXVkaW9UcmFja0xvYWRlZChldmVudCwgZGF0YSkge1xuICAgIHZhciBfdHJhY2skZGV0YWlscztcbiAgICBpZiAodGhpcy5tYWluRGV0YWlscyA9PSBudWxsKSB7XG4gICAgICB0aGlzLmNhY2hlZFRyYWNrTG9hZGVkRGF0YSA9IGRhdGE7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGxldmVsc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIGRldGFpbHM6IG5ld0RldGFpbHMsXG4gICAgICBpZDogdHJhY2tJZFxuICAgIH0gPSBkYXRhO1xuICAgIGlmICghbGV2ZWxzKSB7XG4gICAgICB0aGlzLndhcm4oYEF1ZGlvIHRyYWNrcyB3ZXJlIHJlc2V0IHdoaWxlIGxvYWRpbmcgbGV2ZWwgJHt0cmFja0lkfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmxvZyhgQXVkaW8gdHJhY2sgJHt0cmFja0lkfSBsb2FkZWQgWyR7bmV3RGV0YWlscy5zdGFydFNOfSwke25ld0RldGFpbHMuZW5kU059XSR7bmV3RGV0YWlscy5sYXN0UGFydFNuID8gYFtwYXJ0LSR7bmV3RGV0YWlscy5sYXN0UGFydFNufS0ke25ld0RldGFpbHMubGFzdFBhcnRJbmRleH1dYCA6ICcnfSxkdXJhdGlvbjoke25ld0RldGFpbHMudG90YWxkdXJhdGlvbn1gKTtcbiAgICBjb25zdCB0cmFjayA9IGxldmVsc1t0cmFja0lkXTtcbiAgICBsZXQgc2xpZGluZyA9IDA7XG4gICAgaWYgKG5ld0RldGFpbHMubGl2ZSB8fCAoX3RyYWNrJGRldGFpbHMgPSB0cmFjay5kZXRhaWxzKSAhPSBudWxsICYmIF90cmFjayRkZXRhaWxzLmxpdmUpIHtcbiAgICAgIHRoaXMuY2hlY2tMaXZlVXBkYXRlKG5ld0RldGFpbHMpO1xuICAgICAgY29uc3QgbWFpbkRldGFpbHMgPSB0aGlzLm1haW5EZXRhaWxzO1xuICAgICAgaWYgKG5ld0RldGFpbHMuZGVsdGFVcGRhdGVGYWlsZWQgfHwgIW1haW5EZXRhaWxzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGlmICghdHJhY2suZGV0YWlscyAmJiBuZXdEZXRhaWxzLmhhc1Byb2dyYW1EYXRlVGltZSAmJiBtYWluRGV0YWlscy5oYXNQcm9ncmFtRGF0ZVRpbWUpIHtcbiAgICAgICAgLy8gTWFrZSBzdXJlIG91ciBhdWRpbyByZW5kaXRpb24gaXMgYWxpZ25lZCB3aXRoIHRoZSBcIm1haW5cIiByZW5kaXRpb24sIHVzaW5nXG4gICAgICAgIC8vIHBkdCBhcyBvdXIgcmVmZXJlbmNlIHRpbWVzLlxuICAgICAgICBhbGlnbk1lZGlhUGxheWxpc3RCeVBEVChuZXdEZXRhaWxzLCBtYWluRGV0YWlscyk7XG4gICAgICAgIHNsaWRpbmcgPSBuZXdEZXRhaWxzLmZyYWdtZW50c1swXS5zdGFydDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHZhciBfdGhpcyRsZXZlbExhc3RMb2FkZWQ7XG4gICAgICAgIHNsaWRpbmcgPSB0aGlzLmFsaWduUGxheWxpc3RzKG5ld0RldGFpbHMsIHRyYWNrLmRldGFpbHMsIChfdGhpcyRsZXZlbExhc3RMb2FkZWQgPSB0aGlzLmxldmVsTGFzdExvYWRlZCkgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGxldmVsTGFzdExvYWRlZC5kZXRhaWxzKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdHJhY2suZGV0YWlscyA9IG5ld0RldGFpbHM7XG4gICAgdGhpcy5sZXZlbExhc3RMb2FkZWQgPSB0cmFjaztcblxuICAgIC8vIGNvbXB1dGUgc3RhcnQgcG9zaXRpb24gaWYgd2UgYXJlIGFsaWduZWQgd2l0aCB0aGUgbWFpbiBwbGF5bGlzdFxuICAgIGlmICghdGhpcy5zdGFydEZyYWdSZXF1ZXN0ZWQgJiYgKHRoaXMubWFpbkRldGFpbHMgfHwgIW5ld0RldGFpbHMubGl2ZSkpIHtcbiAgICAgIHRoaXMuc2V0U3RhcnRQb3NpdGlvbih0aGlzLm1haW5EZXRhaWxzIHx8IG5ld0RldGFpbHMsIHNsaWRpbmcpO1xuICAgIH1cbiAgICAvLyBvbmx5IHN3aXRjaCBiYWNrIHRvIElETEUgc3RhdGUgaWYgd2Ugd2VyZSB3YWl0aW5nIGZvciB0cmFjayB0byBzdGFydCBkb3dubG9hZGluZyBhIG5ldyBmcmFnbWVudFxuICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5XQUlUSU5HX1RSQUNLICYmICF0aGlzLndhaXRGb3JDZG5UdW5lSW4obmV3RGV0YWlscykpIHtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIH1cblxuICAgIC8vIHRyaWdnZXIgaGFuZGxlciByaWdodCBub3dcbiAgICB0aGlzLnRpY2soKTtcbiAgfVxuICBfaGFuZGxlRnJhZ21lbnRMb2FkUHJvZ3Jlc3MoZGF0YSkge1xuICAgIHZhciBfZnJhZyRpbml0U2VnbWVudDtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydCxcbiAgICAgIHBheWxvYWRcbiAgICB9ID0gZGF0YTtcbiAgICBjb25zdCB7XG4gICAgICBjb25maWcsXG4gICAgICB0cmFja0lkLFxuICAgICAgbGV2ZWxzXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFsZXZlbHMpIHtcbiAgICAgIHRoaXMud2FybihgQXVkaW8gdHJhY2tzIHdlcmUgcmVzZXQgd2hpbGUgZnJhZ21lbnQgbG9hZCB3YXMgaW4gcHJvZ3Jlc3MuIEZyYWdtZW50ICR7ZnJhZy5zbn0gb2YgbGV2ZWwgJHtmcmFnLmxldmVsfSB3aWxsIG5vdCBiZSBidWZmZXJlZGApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB0cmFjayA9IGxldmVsc1t0cmFja0lkXTtcbiAgICBpZiAoIXRyYWNrKSB7XG4gICAgICB0aGlzLndhcm4oJ0F1ZGlvIHRyYWNrIGlzIHVuZGVmaW5lZCBvbiBmcmFnbWVudCBsb2FkIHByb2dyZXNzJyk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGRldGFpbHMgPSB0cmFjay5kZXRhaWxzO1xuICAgIGlmICghZGV0YWlscykge1xuICAgICAgdGhpcy53YXJuKCdBdWRpbyB0cmFjayBkZXRhaWxzIHVuZGVmaW5lZCBvbiBmcmFnbWVudCBsb2FkIHByb2dyZXNzJyk7XG4gICAgICB0aGlzLnJlbW92ZVVuYnVmZmVyZWRGcmFncyhmcmFnLnN0YXJ0KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgYXVkaW9Db2RlYyA9IGNvbmZpZy5kZWZhdWx0QXVkaW9Db2RlYyB8fCB0cmFjay5hdWRpb0NvZGVjIHx8ICdtcDRhLjQwLjInO1xuICAgIGxldCB0cmFuc211eGVyID0gdGhpcy50cmFuc211eGVyO1xuICAgIGlmICghdHJhbnNtdXhlcikge1xuICAgICAgdHJhbnNtdXhlciA9IHRoaXMudHJhbnNtdXhlciA9IG5ldyBUcmFuc211eGVySW50ZXJmYWNlKHRoaXMuaGxzLCBQbGF5bGlzdExldmVsVHlwZS5BVURJTywgdGhpcy5faGFuZGxlVHJhbnNtdXhDb21wbGV0ZS5iaW5kKHRoaXMpLCB0aGlzLl9oYW5kbGVUcmFuc211eGVyRmx1c2guYmluZCh0aGlzKSk7XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgaWYgd2UgaGF2ZSB2aWRlbyBpbml0UFRTXG4gICAgLy8gSWYgbm90IHdlIG5lZWQgdG8gd2FpdCBmb3IgaXRcbiAgICBjb25zdCBpbml0UFRTID0gdGhpcy5pbml0UFRTW2ZyYWcuY2NdO1xuICAgIGNvbnN0IGluaXRTZWdtZW50RGF0YSA9IChfZnJhZyRpbml0U2VnbWVudCA9IGZyYWcuaW5pdFNlZ21lbnQpID09IG51bGwgPyB2b2lkIDAgOiBfZnJhZyRpbml0U2VnbWVudC5kYXRhO1xuICAgIGlmIChpbml0UFRTICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIC8vIHRoaXMubG9nKGBUcmFuc211eGluZyAke3NufSBvZiBbJHtkZXRhaWxzLnN0YXJ0U059ICwke2RldGFpbHMuZW5kU059XSx0cmFjayAke3RyYWNrSWR9YCk7XG4gICAgICAvLyB0aW1lIE9mZnNldCBpcyBhY2N1cmF0ZSBpZiBsZXZlbCBQVFMgaXMga25vd24sIG9yIGlmIHBsYXlsaXN0IGlzIG5vdCBzbGlkaW5nIChub3QgbGl2ZSlcbiAgICAgIGNvbnN0IGFjY3VyYXRlVGltZU9mZnNldCA9IGZhbHNlOyAvLyBkZXRhaWxzLlBUU0tub3duIHx8ICFkZXRhaWxzLmxpdmU7XG4gICAgICBjb25zdCBwYXJ0SW5kZXggPSBwYXJ0ID8gcGFydC5pbmRleCA6IC0xO1xuICAgICAgY29uc3QgcGFydGlhbCA9IHBhcnRJbmRleCAhPT0gLTE7XG4gICAgICBjb25zdCBjaHVua01ldGEgPSBuZXcgQ2h1bmtNZXRhZGF0YShmcmFnLmxldmVsLCBmcmFnLnNuLCBmcmFnLnN0YXRzLmNodW5rQ291bnQsIHBheWxvYWQuYnl0ZUxlbmd0aCwgcGFydEluZGV4LCBwYXJ0aWFsKTtcbiAgICAgIHRyYW5zbXV4ZXIucHVzaChwYXlsb2FkLCBpbml0U2VnbWVudERhdGEsIGF1ZGlvQ29kZWMsICcnLCBmcmFnLCBwYXJ0LCBkZXRhaWxzLnRvdGFsZHVyYXRpb24sIGFjY3VyYXRlVGltZU9mZnNldCwgY2h1bmtNZXRhLCBpbml0UFRTKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5sb2coYFVua25vd24gdmlkZW8gUFRTIGZvciBjYyAke2ZyYWcuY2N9LCB3YWl0aW5nIGZvciB2aWRlbyBQVFMgYmVmb3JlIGRlbXV4aW5nIGF1ZGlvIGZyYWcgJHtmcmFnLnNufSBvZiBbJHtkZXRhaWxzLnN0YXJ0U059ICwke2RldGFpbHMuZW5kU059XSx0cmFjayAke3RyYWNrSWR9YCk7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGNhY2hlXG4gICAgICB9ID0gdGhpcy53YWl0aW5nRGF0YSA9IHRoaXMud2FpdGluZ0RhdGEgfHwge1xuICAgICAgICBmcmFnLFxuICAgICAgICBwYXJ0LFxuICAgICAgICBjYWNoZTogbmV3IENodW5rQ2FjaGUoKSxcbiAgICAgICAgY29tcGxldGU6IGZhbHNlXG4gICAgICB9O1xuICAgICAgY2FjaGUucHVzaChuZXcgVWludDhBcnJheShwYXlsb2FkKSk7XG4gICAgICB0aGlzLndhaXRpbmdWaWRlb0NDID0gdGhpcy52aWRlb1RyYWNrQ0M7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuV0FJVElOR19JTklUX1BUUztcbiAgICB9XG4gIH1cbiAgX2hhbmRsZUZyYWdtZW50TG9hZENvbXBsZXRlKGZyYWdMb2FkZWREYXRhKSB7XG4gICAgaWYgKHRoaXMud2FpdGluZ0RhdGEpIHtcbiAgICAgIHRoaXMud2FpdGluZ0RhdGEuY29tcGxldGUgPSB0cnVlO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBzdXBlci5faGFuZGxlRnJhZ21lbnRMb2FkQ29tcGxldGUoZnJhZ0xvYWRlZERhdGEpO1xuICB9XG4gIG9uQnVmZmVyUmVzZXQoIC8qIGV2ZW50OiBFdmVudHMuQlVGRkVSX1JFU0VUICovXG4gICkge1xuICAgIC8vIHJlc2V0IHJlZmVyZW5jZSB0byBzb3VyY2VidWZmZXJzXG4gICAgdGhpcy5tZWRpYUJ1ZmZlciA9IHRoaXMudmlkZW9CdWZmZXIgPSBudWxsO1xuICAgIHRoaXMubG9hZGVkbWV0YWRhdGEgPSBmYWxzZTtcbiAgfVxuICBvbkJ1ZmZlckNyZWF0ZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCBhdWRpb1RyYWNrID0gZGF0YS50cmFja3MuYXVkaW87XG4gICAgaWYgKGF1ZGlvVHJhY2spIHtcbiAgICAgIHRoaXMubWVkaWFCdWZmZXIgPSBhdWRpb1RyYWNrLmJ1ZmZlciB8fCBudWxsO1xuICAgIH1cbiAgICBpZiAoZGF0YS50cmFja3MudmlkZW8pIHtcbiAgICAgIHRoaXMudmlkZW9CdWZmZXIgPSBkYXRhLnRyYWNrcy52aWRlby5idWZmZXIgfHwgbnVsbDtcbiAgICB9XG4gIH1cbiAgb25GcmFnQnVmZmVyZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydFxuICAgIH0gPSBkYXRhO1xuICAgIGlmIChmcmFnLnR5cGUgIT09IFBsYXlsaXN0TGV2ZWxUeXBlLkFVRElPKSB7XG4gICAgICBpZiAoIXRoaXMubG9hZGVkbWV0YWRhdGEgJiYgZnJhZy50eXBlID09PSBQbGF5bGlzdExldmVsVHlwZS5NQUlOKSB7XG4gICAgICAgIGNvbnN0IGJ1ZmZlcmFibGUgPSB0aGlzLnZpZGVvQnVmZmVyIHx8IHRoaXMubWVkaWE7XG4gICAgICAgIGlmIChidWZmZXJhYmxlKSB7XG4gICAgICAgICAgY29uc3QgYnVmZmVyZWRUaW1lUmFuZ2VzID0gQnVmZmVySGVscGVyLmdldEJ1ZmZlcmVkKGJ1ZmZlcmFibGUpO1xuICAgICAgICAgIGlmIChidWZmZXJlZFRpbWVSYW5nZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICB0aGlzLmxvYWRlZG1ldGFkYXRhID0gdHJ1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHRoaXMuZnJhZ0NvbnRleHRDaGFuZ2VkKGZyYWcpKSB7XG4gICAgICAvLyBJZiBhIGxldmVsIHN3aXRjaCB3YXMgcmVxdWVzdGVkIHdoaWxlIGEgZnJhZ21lbnQgd2FzIGJ1ZmZlcmluZywgaXQgd2lsbCBlbWl0IHRoZSBGUkFHX0JVRkZFUkVEIGV2ZW50IHVwb24gY29tcGxldGlvblxuICAgICAgLy8gQXZvaWQgc2V0dGluZyBzdGF0ZSBiYWNrIHRvIElETEUgb3IgY29uY2x1ZGluZyB0aGUgYXVkaW8gc3dpdGNoOyBvdGhlcndpc2UsIHRoZSBzd2l0Y2hlZC10byB0cmFjayB3aWxsIG5vdCBidWZmZXJcbiAgICAgIHRoaXMud2FybihgRnJhZ21lbnQgJHtmcmFnLnNufSR7cGFydCA/ICcgcDogJyArIHBhcnQuaW5kZXggOiAnJ30gb2YgbGV2ZWwgJHtmcmFnLmxldmVsfSBmaW5pc2hlZCBidWZmZXJpbmcsIGJ1dCB3YXMgYWJvcnRlZC4gc3RhdGU6ICR7dGhpcy5zdGF0ZX0sIGF1ZGlvU3dpdGNoOiAke3RoaXMuc3dpdGNoaW5nVHJhY2sgPyB0aGlzLnN3aXRjaGluZ1RyYWNrLm5hbWUgOiAnZmFsc2UnfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoZnJhZy5zbiAhPT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgdGhpcy5mcmFnUHJldmlvdXMgPSBmcmFnO1xuICAgICAgY29uc3QgdHJhY2sgPSB0aGlzLnN3aXRjaGluZ1RyYWNrO1xuICAgICAgaWYgKHRyYWNrKSB7XG4gICAgICAgIHRoaXMuYnVmZmVyZWRUcmFjayA9IHRyYWNrO1xuICAgICAgICB0aGlzLnN3aXRjaGluZ1RyYWNrID0gbnVsbDtcbiAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuQVVESU9fVFJBQ0tfU1dJVENIRUQsIF9vYmplY3RTcHJlYWQyKHt9LCB0cmFjaykpO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLmZyYWdCdWZmZXJlZENvbXBsZXRlKGZyYWcsIHBhcnQpO1xuICB9XG4gIG9uRXJyb3IoZXZlbnQsIGRhdGEpIHtcbiAgICB2YXIgX2RhdGEkY29udGV4dDtcbiAgICBpZiAoZGF0YS5mYXRhbCkge1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLkVSUk9SO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBzd2l0Y2ggKGRhdGEuZGV0YWlscykge1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuRlJBR19HQVA6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX1BBUlNJTkdfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX0RFQ1JZUFRfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX0xPQURfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5GUkFHX0xPQURfVElNRU9VVDpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLktFWV9MT0FEX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuS0VZX0xPQURfVElNRU9VVDpcbiAgICAgICAgdGhpcy5vbkZyYWdtZW50T3JLZXlMb2FkRXJyb3IoUGxheWxpc3RMZXZlbFR5cGUuQVVESU8sIGRhdGEpO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkFVRElPX1RSQUNLX0xPQURfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5BVURJT19UUkFDS19MT0FEX1RJTUVPVVQ6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5MRVZFTF9QQVJTSU5HX0VSUk9SOlxuICAgICAgICAvLyBpbiBjYXNlIG9mIG5vbiBmYXRhbCBlcnJvciB3aGlsZSBsb2FkaW5nIHRyYWNrLCBpZiBub3QgcmV0cnlpbmcgdG8gbG9hZCB0cmFjaywgc3dpdGNoIGJhY2sgdG8gSURMRVxuICAgICAgICBpZiAoIWRhdGEubGV2ZWxSZXRyeSAmJiB0aGlzLnN0YXRlID09PSBTdGF0ZS5XQUlUSU5HX1RSQUNLICYmICgoX2RhdGEkY29udGV4dCA9IGRhdGEuY29udGV4dCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9kYXRhJGNvbnRleHQudHlwZSkgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuQVVESU9fVFJBQ0spIHtcbiAgICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkJVRkZFUl9BUFBFTkRfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5CVUZGRVJfRlVMTF9FUlJPUjpcbiAgICAgICAgaWYgKCFkYXRhLnBhcmVudCB8fCBkYXRhLnBhcmVudCAhPT0gJ2F1ZGlvJykge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBpZiAoZGF0YS5kZXRhaWxzID09PSBFcnJvckRldGFpbHMuQlVGRkVSX0FQUEVORF9FUlJPUikge1xuICAgICAgICAgIHRoaXMucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMucmVkdWNlTGVuZ3RoQW5kRmx1c2hCdWZmZXIoZGF0YSkpIHtcbiAgICAgICAgICB0aGlzLmJ1ZmZlcmVkVHJhY2sgPSBudWxsO1xuICAgICAgICAgIHN1cGVyLmZsdXNoTWFpbkJ1ZmZlcigwLCBOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFksICdhdWRpbycpO1xuICAgICAgICB9XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuSU5URVJOQUxfRVhDRVBUSU9OOlxuICAgICAgICB0aGlzLnJlY292ZXJXb3JrZXJFcnJvcihkYXRhKTtcbiAgICAgICAgYnJlYWs7XG4gICAgfVxuICB9XG4gIG9uQnVmZmVyRmx1c2hpbmcoZXZlbnQsIHtcbiAgICB0eXBlXG4gIH0pIHtcbiAgICBpZiAodHlwZSAhPT0gRWxlbWVudGFyeVN0cmVhbVR5cGVzLlZJREVPKSB7XG4gICAgICB0aGlzLmZsdXNoaW5nID0gdHJ1ZTtcbiAgICB9XG4gIH1cbiAgb25CdWZmZXJGbHVzaGVkKGV2ZW50LCB7XG4gICAgdHlwZVxuICB9KSB7XG4gICAgaWYgKHR5cGUgIT09IEVsZW1lbnRhcnlTdHJlYW1UeXBlcy5WSURFTykge1xuICAgICAgdGhpcy5mbHVzaGluZyA9IGZhbHNlO1xuICAgICAgdGhpcy5idWZmZXJGbHVzaGVkID0gdHJ1ZTtcbiAgICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5FTkRFRCkge1xuICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IG1lZGlhQnVmZmVyID0gdGhpcy5tZWRpYUJ1ZmZlciB8fCB0aGlzLm1lZGlhO1xuICAgICAgaWYgKG1lZGlhQnVmZmVyKSB7XG4gICAgICAgIHRoaXMuYWZ0ZXJCdWZmZXJGbHVzaGVkKG1lZGlhQnVmZmVyLCB0eXBlLCBQbGF5bGlzdExldmVsVHlwZS5BVURJTyk7XG4gICAgICAgIHRoaXMudGljaygpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBfaGFuZGxlVHJhbnNtdXhDb21wbGV0ZSh0cmFuc211eFJlc3VsdCkge1xuICAgIHZhciBfaWQzJHNhbXBsZXM7XG4gICAgY29uc3QgaWQgPSAnYXVkaW8nO1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIHJlbXV4UmVzdWx0LFxuICAgICAgY2h1bmtNZXRhXG4gICAgfSA9IHRyYW5zbXV4UmVzdWx0O1xuICAgIGNvbnN0IGNvbnRleHQgPSB0aGlzLmdldEN1cnJlbnRDb250ZXh0KGNodW5rTWV0YSk7XG4gICAgaWYgKCFjb250ZXh0KSB7XG4gICAgICB0aGlzLnJlc2V0V2hlbk1pc3NpbmdDb250ZXh0KGNodW5rTWV0YSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBwYXJ0LFxuICAgICAgbGV2ZWxcbiAgICB9ID0gY29udGV4dDtcbiAgICBjb25zdCB7XG4gICAgICBkZXRhaWxzXG4gICAgfSA9IGxldmVsO1xuICAgIGNvbnN0IHtcbiAgICAgIGF1ZGlvLFxuICAgICAgdGV4dCxcbiAgICAgIGlkMyxcbiAgICAgIGluaXRTZWdtZW50XG4gICAgfSA9IHJlbXV4UmVzdWx0O1xuXG4gICAgLy8gQ2hlY2sgaWYgdGhlIGN1cnJlbnQgZnJhZ21lbnQgaGFzIGJlZW4gYWJvcnRlZC4gV2UgY2hlY2sgdGhpcyBieSBmaXJzdCBzZWVpbmcgaWYgd2UncmUgc3RpbGwgcGxheWluZyB0aGUgY3VycmVudCBsZXZlbC5cbiAgICAvLyBJZiB3ZSBhcmUsIHN1YnNlcXVlbnRseSBjaGVjayBpZiB0aGUgY3VycmVudGx5IGxvYWRpbmcgZnJhZ21lbnQgKGZyYWdDdXJyZW50KSBoYXMgY2hhbmdlZC5cbiAgICBpZiAodGhpcy5mcmFnQ29udGV4dENoYW5nZWQoZnJhZykgfHwgIWRldGFpbHMpIHtcbiAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50KGZyYWcpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLnN0YXRlID0gU3RhdGUuUEFSU0lORztcbiAgICBpZiAodGhpcy5zd2l0Y2hpbmdUcmFjayAmJiBhdWRpbykge1xuICAgICAgdGhpcy5jb21wbGV0ZUF1ZGlvU3dpdGNoKHRoaXMuc3dpdGNoaW5nVHJhY2spO1xuICAgIH1cbiAgICBpZiAoaW5pdFNlZ21lbnQgIT0gbnVsbCAmJiBpbml0U2VnbWVudC50cmFja3MpIHtcbiAgICAgIGNvbnN0IG1hcEZyYWdtZW50ID0gZnJhZy5pbml0U2VnbWVudCB8fCBmcmFnO1xuICAgICAgdGhpcy5fYnVmZmVySW5pdFNlZ21lbnQobGV2ZWwsIGluaXRTZWdtZW50LnRyYWNrcywgbWFwRnJhZ21lbnQsIGNodW5rTWV0YSk7XG4gICAgICBobHMudHJpZ2dlcihFdmVudHMuRlJBR19QQVJTSU5HX0lOSVRfU0VHTUVOVCwge1xuICAgICAgICBmcmFnOiBtYXBGcmFnbWVudCxcbiAgICAgICAgaWQsXG4gICAgICAgIHRyYWNrczogaW5pdFNlZ21lbnQudHJhY2tzXG4gICAgICB9KTtcbiAgICAgIC8vIE9ubHkgZmx1c2ggYXVkaW8gZnJvbSBvbGQgYXVkaW8gdHJhY2tzIHdoZW4gUFRTIGlzIGtub3duIG9uIG5ldyBhdWRpbyB0cmFja1xuICAgIH1cbiAgICBpZiAoYXVkaW8pIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgc3RhcnRQVFMsXG4gICAgICAgIGVuZFBUUyxcbiAgICAgICAgc3RhcnREVFMsXG4gICAgICAgIGVuZERUU1xuICAgICAgfSA9IGF1ZGlvO1xuICAgICAgaWYgKHBhcnQpIHtcbiAgICAgICAgcGFydC5lbGVtZW50YXJ5U3RyZWFtc1tFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU9dID0ge1xuICAgICAgICAgIHN0YXJ0UFRTLFxuICAgICAgICAgIGVuZFBUUyxcbiAgICAgICAgICBzdGFydERUUyxcbiAgICAgICAgICBlbmREVFNcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIGZyYWcuc2V0RWxlbWVudGFyeVN0cmVhbUluZm8oRWxlbWVudGFyeVN0cmVhbVR5cGVzLkFVRElPLCBzdGFydFBUUywgZW5kUFRTLCBzdGFydERUUywgZW5kRFRTKTtcbiAgICAgIHRoaXMuYnVmZmVyRnJhZ21lbnREYXRhKGF1ZGlvLCBmcmFnLCBwYXJ0LCBjaHVua01ldGEpO1xuICAgIH1cbiAgICBpZiAoaWQzICE9IG51bGwgJiYgKF9pZDMkc2FtcGxlcyA9IGlkMy5zYW1wbGVzKSAhPSBudWxsICYmIF9pZDMkc2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgIGNvbnN0IGVtaXR0ZWRJRDMgPSBfZXh0ZW5kcyh7XG4gICAgICAgIGlkLFxuICAgICAgICBmcmFnLFxuICAgICAgICBkZXRhaWxzXG4gICAgICB9LCBpZDMpO1xuICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkZSQUdfUEFSU0lOR19NRVRBREFUQSwgZW1pdHRlZElEMyk7XG4gICAgfVxuICAgIGlmICh0ZXh0KSB7XG4gICAgICBjb25zdCBlbWl0dGVkVGV4dCA9IF9leHRlbmRzKHtcbiAgICAgICAgaWQsXG4gICAgICAgIGZyYWcsXG4gICAgICAgIGRldGFpbHNcbiAgICAgIH0sIHRleHQpO1xuICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkZSQUdfUEFSU0lOR19VU0VSREFUQSwgZW1pdHRlZFRleHQpO1xuICAgIH1cbiAgfVxuICBfYnVmZmVySW5pdFNlZ21lbnQoY3VycmVudExldmVsLCB0cmFja3MsIGZyYWcsIGNodW5rTWV0YSkge1xuICAgIGlmICh0aGlzLnN0YXRlICE9PSBTdGF0ZS5QQVJTSU5HKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIGRlbGV0ZSBhbnkgdmlkZW8gdHJhY2sgZm91bmQgb24gYXVkaW8gdHJhbnNtdXhlclxuICAgIGlmICh0cmFja3MudmlkZW8pIHtcbiAgICAgIGRlbGV0ZSB0cmFja3MudmlkZW87XG4gICAgfVxuXG4gICAgLy8gaW5jbHVkZSBsZXZlbENvZGVjIGluIGF1ZGlvIGFuZCB2aWRlbyB0cmFja3NcbiAgICBjb25zdCB0cmFjayA9IHRyYWNrcy5hdWRpbztcbiAgICBpZiAoIXRyYWNrKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRyYWNrLmlkID0gJ2F1ZGlvJztcbiAgICBjb25zdCB2YXJpYW50QXVkaW9Db2RlY3MgPSBjdXJyZW50TGV2ZWwuYXVkaW9Db2RlYztcbiAgICB0aGlzLmxvZyhgSW5pdCBhdWRpbyBidWZmZXIsIGNvbnRhaW5lcjoke3RyYWNrLmNvbnRhaW5lcn0sIGNvZGVjc1tsZXZlbC9wYXJzZWRdPVske3ZhcmlhbnRBdWRpb0NvZGVjc30vJHt0cmFjay5jb2RlY31dYCk7XG4gICAgLy8gU291cmNlQnVmZmVyIHdpbGwgdXNlIHRyYWNrLmxldmVsQ29kZWMgaWYgZGVmaW5lZFxuICAgIGlmICh2YXJpYW50QXVkaW9Db2RlY3MgJiYgdmFyaWFudEF1ZGlvQ29kZWNzLnNwbGl0KCcsJykubGVuZ3RoID09PSAxKSB7XG4gICAgICB0cmFjay5sZXZlbENvZGVjID0gdmFyaWFudEF1ZGlvQ29kZWNzO1xuICAgIH1cbiAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfQ09ERUNTLCB0cmFja3MpO1xuICAgIGNvbnN0IGluaXRTZWdtZW50ID0gdHJhY2suaW5pdFNlZ21lbnQ7XG4gICAgaWYgKGluaXRTZWdtZW50ICE9IG51bGwgJiYgaW5pdFNlZ21lbnQuYnl0ZUxlbmd0aCkge1xuICAgICAgY29uc3Qgc2VnbWVudCA9IHtcbiAgICAgICAgdHlwZTogJ2F1ZGlvJyxcbiAgICAgICAgZnJhZyxcbiAgICAgICAgcGFydDogbnVsbCxcbiAgICAgICAgY2h1bmtNZXRhLFxuICAgICAgICBwYXJlbnQ6IGZyYWcudHlwZSxcbiAgICAgICAgZGF0YTogaW5pdFNlZ21lbnRcbiAgICAgIH07XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfQVBQRU5ESU5HLCBzZWdtZW50KTtcbiAgICB9XG4gICAgLy8gdHJpZ2dlciBoYW5kbGVyIHJpZ2h0IG5vd1xuICAgIHRoaXMudGlja0ltbWVkaWF0ZSgpO1xuICB9XG4gIGxvYWRGcmFnbWVudChmcmFnLCB0cmFjaywgdGFyZ2V0QnVmZmVyVGltZSkge1xuICAgIC8vIG9ubHkgbG9hZCBpZiBmcmFnbWVudCBpcyBub3QgbG9hZGVkIG9yIGlmIGluIGF1ZGlvIHN3aXRjaFxuICAgIGNvbnN0IGZyYWdTdGF0ZSA9IHRoaXMuZnJhZ21lbnRUcmFja2VyLmdldFN0YXRlKGZyYWcpO1xuICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSBmcmFnO1xuXG4gICAgLy8gd2UgZm9yY2UgYSBmcmFnIGxvYWRpbmcgaW4gYXVkaW8gc3dpdGNoIGFzIGZyYWdtZW50IHRyYWNrZXIgbWlnaHQgbm90IGhhdmUgZXZpY3RlZCBwcmV2aW91cyBmcmFncyBpbiBjYXNlIG9mIHF1aWNrIGF1ZGlvIHN3aXRjaFxuICAgIGlmICh0aGlzLnN3aXRjaGluZ1RyYWNrIHx8IGZyYWdTdGF0ZSA9PT0gRnJhZ21lbnRTdGF0ZS5OT1RfTE9BREVEIHx8IGZyYWdTdGF0ZSA9PT0gRnJhZ21lbnRTdGF0ZS5QQVJUSUFMKSB7XG4gICAgICB2YXIgX3RyYWNrJGRldGFpbHMyO1xuICAgICAgaWYgKGZyYWcuc24gPT09ICdpbml0U2VnbWVudCcpIHtcbiAgICAgICAgdGhpcy5fbG9hZEluaXRTZWdtZW50KGZyYWcsIHRyYWNrKTtcbiAgICAgIH0gZWxzZSBpZiAoKF90cmFjayRkZXRhaWxzMiA9IHRyYWNrLmRldGFpbHMpICE9IG51bGwgJiYgX3RyYWNrJGRldGFpbHMyLmxpdmUgJiYgIXRoaXMuaW5pdFBUU1tmcmFnLmNjXSkge1xuICAgICAgICB0aGlzLmxvZyhgV2FpdGluZyBmb3IgdmlkZW8gUFRTIGluIGNvbnRpbnVpdHkgY291bnRlciAke2ZyYWcuY2N9IG9mIGxpdmUgc3RyZWFtIGJlZm9yZSBsb2FkaW5nIGF1ZGlvIGZyYWdtZW50ICR7ZnJhZy5zbn0gb2YgbGV2ZWwgJHt0aGlzLnRyYWNrSWR9YCk7XG4gICAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5XQUlUSU5HX0lOSVRfUFRTO1xuICAgICAgICBjb25zdCBtYWluRGV0YWlscyA9IHRoaXMubWFpbkRldGFpbHM7XG4gICAgICAgIGlmIChtYWluRGV0YWlscyAmJiBtYWluRGV0YWlscy5mcmFnbWVudHNbMF0uc3RhcnQgIT09IHRyYWNrLmRldGFpbHMuZnJhZ21lbnRzWzBdLnN0YXJ0KSB7XG4gICAgICAgICAgYWxpZ25NZWRpYVBsYXlsaXN0QnlQRFQodHJhY2suZGV0YWlscywgbWFpbkRldGFpbHMpO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLnN0YXJ0RnJhZ1JlcXVlc3RlZCA9IHRydWU7XG4gICAgICAgIHN1cGVyLmxvYWRGcmFnbWVudChmcmFnLCB0cmFjaywgdGFyZ2V0QnVmZmVyVGltZSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuY2xlYXJUcmFja2VySWZOZWVkZWQoZnJhZyk7XG4gICAgfVxuICB9XG4gIGZsdXNoQXVkaW9JZk5lZWRlZChzd2l0Y2hpbmdUcmFjaykge1xuICAgIGNvbnN0IHtcbiAgICAgIG1lZGlhLFxuICAgICAgYnVmZmVyZWRUcmFja1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IGJ1ZmZlcmVkQXR0cmlidXRlcyA9IGJ1ZmZlcmVkVHJhY2sgPT0gbnVsbCA/IHZvaWQgMCA6IGJ1ZmZlcmVkVHJhY2suYXR0cnM7XG4gICAgY29uc3Qgc3dpdGNoQXR0cmlidXRlcyA9IHN3aXRjaGluZ1RyYWNrLmF0dHJzO1xuICAgIGlmIChtZWRpYSAmJiBidWZmZXJlZEF0dHJpYnV0ZXMgJiYgKGJ1ZmZlcmVkQXR0cmlidXRlcy5DSEFOTkVMUyAhPT0gc3dpdGNoQXR0cmlidXRlcy5DSEFOTkVMUyB8fCBidWZmZXJlZFRyYWNrLm5hbWUgIT09IHN3aXRjaGluZ1RyYWNrLm5hbWUgfHwgYnVmZmVyZWRUcmFjay5sYW5nICE9PSBzd2l0Y2hpbmdUcmFjay5sYW5nKSkge1xuICAgICAgdGhpcy5sb2coJ1N3aXRjaGluZyBhdWRpbyB0cmFjayA6IGZsdXNoaW5nIGFsbCBhdWRpbycpO1xuICAgICAgc3VwZXIuZmx1c2hNYWluQnVmZmVyKDAsIE51bWJlci5QT1NJVElWRV9JTkZJTklUWSwgJ2F1ZGlvJyk7XG4gICAgICB0aGlzLmJ1ZmZlcmVkVHJhY2sgPSBudWxsO1xuICAgIH1cbiAgfVxuICBjb21wbGV0ZUF1ZGlvU3dpdGNoKHN3aXRjaGluZ1RyYWNrKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgdGhpcy5mbHVzaEF1ZGlvSWZOZWVkZWQoc3dpdGNoaW5nVHJhY2spO1xuICAgIHRoaXMuYnVmZmVyZWRUcmFjayA9IHN3aXRjaGluZ1RyYWNrO1xuICAgIHRoaXMuc3dpdGNoaW5nVHJhY2sgPSBudWxsO1xuICAgIGhscy50cmlnZ2VyKEV2ZW50cy5BVURJT19UUkFDS19TV0lUQ0hFRCwgX29iamVjdFNwcmVhZDIoe30sIHN3aXRjaGluZ1RyYWNrKSk7XG4gIH1cbn1cblxuY2xhc3MgQXVkaW9UcmFja0NvbnRyb2xsZXIgZXh0ZW5kcyBCYXNlUGxheWxpc3RDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgc3VwZXIoaGxzLCAnW2F1ZGlvLXRyYWNrLWNvbnRyb2xsZXJdJyk7XG4gICAgdGhpcy50cmFja3MgPSBbXTtcbiAgICB0aGlzLmdyb3VwSWRzID0gbnVsbDtcbiAgICB0aGlzLnRyYWNrc0luR3JvdXAgPSBbXTtcbiAgICB0aGlzLnRyYWNrSWQgPSAtMTtcbiAgICB0aGlzLmN1cnJlbnRUcmFjayA9IG51bGw7XG4gICAgdGhpcy5zZWxlY3REZWZhdWx0VHJhY2sgPSB0cnVlO1xuICAgIHRoaXMucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICByZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTEVWRUxfTE9BRElORywgdGhpcy5vbkxldmVsTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTF9TV0lUQ0hJTkcsIHRoaXMub25MZXZlbFN3aXRjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5BVURJT19UUkFDS19MT0FERUQsIHRoaXMub25BdWRpb1RyYWNrTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICB9XG4gIHVucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX0xPQURJTkcsIHRoaXMub25MZXZlbExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX1NXSVRDSElORywgdGhpcy5vbkxldmVsU3dpdGNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5BVURJT19UUkFDS19MT0FERUQsIHRoaXMub25BdWRpb1RyYWNrTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5FUlJPUiwgdGhpcy5vbkVycm9yLCB0aGlzKTtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMudW5yZWdpc3Rlckxpc3RlbmVycygpO1xuICAgIHRoaXMudHJhY2tzLmxlbmd0aCA9IDA7XG4gICAgdGhpcy50cmFja3NJbkdyb3VwLmxlbmd0aCA9IDA7XG4gICAgdGhpcy5jdXJyZW50VHJhY2sgPSBudWxsO1xuICAgIHN1cGVyLmRlc3Ryb3koKTtcbiAgfVxuICBvbk1hbmlmZXN0TG9hZGluZygpIHtcbiAgICB0aGlzLnRyYWNrcyA9IFtdO1xuICAgIHRoaXMudHJhY2tzSW5Hcm91cCA9IFtdO1xuICAgIHRoaXMuZ3JvdXBJZHMgPSBudWxsO1xuICAgIHRoaXMuY3VycmVudFRyYWNrID0gbnVsbDtcbiAgICB0aGlzLnRyYWNrSWQgPSAtMTtcbiAgICB0aGlzLnNlbGVjdERlZmF1bHRUcmFjayA9IHRydWU7XG4gIH1cbiAgb25NYW5pZmVzdFBhcnNlZChldmVudCwgZGF0YSkge1xuICAgIHRoaXMudHJhY2tzID0gZGF0YS5hdWRpb1RyYWNrcyB8fCBbXTtcbiAgfVxuICBvbkF1ZGlvVHJhY2tMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBpZCxcbiAgICAgIGdyb3VwSWQsXG4gICAgICBkZXRhaWxzXG4gICAgfSA9IGRhdGE7XG4gICAgY29uc3QgdHJhY2tJbkFjdGl2ZUdyb3VwID0gdGhpcy50cmFja3NJbkdyb3VwW2lkXTtcbiAgICBpZiAoIXRyYWNrSW5BY3RpdmVHcm91cCB8fCB0cmFja0luQWN0aXZlR3JvdXAuZ3JvdXBJZCAhPT0gZ3JvdXBJZCkge1xuICAgICAgdGhpcy53YXJuKGBBdWRpbyB0cmFjayB3aXRoIGlkOiR7aWR9IGFuZCBncm91cDoke2dyb3VwSWR9IG5vdCBmb3VuZCBpbiBhY3RpdmUgZ3JvdXAgJHt0cmFja0luQWN0aXZlR3JvdXAgPT0gbnVsbCA/IHZvaWQgMCA6IHRyYWNrSW5BY3RpdmVHcm91cC5ncm91cElkfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBjdXJEZXRhaWxzID0gdHJhY2tJbkFjdGl2ZUdyb3VwLmRldGFpbHM7XG4gICAgdHJhY2tJbkFjdGl2ZUdyb3VwLmRldGFpbHMgPSBkYXRhLmRldGFpbHM7XG4gICAgdGhpcy5sb2coYEF1ZGlvIHRyYWNrICR7aWR9IFwiJHt0cmFja0luQWN0aXZlR3JvdXAubmFtZX1cIiBsYW5nOiR7dHJhY2tJbkFjdGl2ZUdyb3VwLmxhbmd9IGdyb3VwOiR7Z3JvdXBJZH0gbG9hZGVkIFske2RldGFpbHMuc3RhcnRTTn0tJHtkZXRhaWxzLmVuZFNOfV1gKTtcbiAgICBpZiAoaWQgPT09IHRoaXMudHJhY2tJZCkge1xuICAgICAgdGhpcy5wbGF5bGlzdExvYWRlZChpZCwgZGF0YSwgY3VyRGV0YWlscyk7XG4gICAgfVxuICB9XG4gIG9uTGV2ZWxMb2FkaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5zd2l0Y2hMZXZlbChkYXRhLmxldmVsKTtcbiAgfVxuICBvbkxldmVsU3dpdGNoaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5zd2l0Y2hMZXZlbChkYXRhLmxldmVsKTtcbiAgfVxuICBzd2l0Y2hMZXZlbChsZXZlbEluZGV4KSB7XG4gICAgY29uc3QgbGV2ZWxJbmZvID0gdGhpcy5obHMubGV2ZWxzW2xldmVsSW5kZXhdO1xuICAgIGlmICghbGV2ZWxJbmZvKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGF1ZGlvR3JvdXBzID0gbGV2ZWxJbmZvLmF1ZGlvR3JvdXBzIHx8IG51bGw7XG4gICAgY29uc3QgY3VycmVudEdyb3VwcyA9IHRoaXMuZ3JvdXBJZHM7XG4gICAgbGV0IGN1cnJlbnRUcmFjayA9IHRoaXMuY3VycmVudFRyYWNrO1xuICAgIGlmICghYXVkaW9Hcm91cHMgfHwgKGN1cnJlbnRHcm91cHMgPT0gbnVsbCA/IHZvaWQgMCA6IGN1cnJlbnRHcm91cHMubGVuZ3RoKSAhPT0gKGF1ZGlvR3JvdXBzID09IG51bGwgPyB2b2lkIDAgOiBhdWRpb0dyb3Vwcy5sZW5ndGgpIHx8IGF1ZGlvR3JvdXBzICE9IG51bGwgJiYgYXVkaW9Hcm91cHMuc29tZShncm91cElkID0+IChjdXJyZW50R3JvdXBzID09IG51bGwgPyB2b2lkIDAgOiBjdXJyZW50R3JvdXBzLmluZGV4T2YoZ3JvdXBJZCkpID09PSAtMSkpIHtcbiAgICAgIHRoaXMuZ3JvdXBJZHMgPSBhdWRpb0dyb3VwcztcbiAgICAgIHRoaXMudHJhY2tJZCA9IC0xO1xuICAgICAgdGhpcy5jdXJyZW50VHJhY2sgPSBudWxsO1xuICAgICAgY29uc3QgYXVkaW9UcmFja3MgPSB0aGlzLnRyYWNrcy5maWx0ZXIodHJhY2sgPT4gIWF1ZGlvR3JvdXBzIHx8IGF1ZGlvR3JvdXBzLmluZGV4T2YodHJhY2suZ3JvdXBJZCkgIT09IC0xKTtcbiAgICAgIGlmIChhdWRpb1RyYWNrcy5sZW5ndGgpIHtcbiAgICAgICAgLy8gRGlzYWJsZSBzZWxlY3REZWZhdWx0VHJhY2sgaWYgdGhlcmUgYXJlIG5vIGRlZmF1bHQgdHJhY2tzXG4gICAgICAgIGlmICh0aGlzLnNlbGVjdERlZmF1bHRUcmFjayAmJiAhYXVkaW9UcmFja3Muc29tZSh0cmFjayA9PiB0cmFjay5kZWZhdWx0KSkge1xuICAgICAgICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgLy8gdHJhY2suaWQgc2hvdWxkIG1hdGNoIGhscy5hdWRpb1RyYWNrcyBpbmRleFxuICAgICAgICBhdWRpb1RyYWNrcy5mb3JFYWNoKCh0cmFjaywgaSkgPT4ge1xuICAgICAgICAgIHRyYWNrLmlkID0gaTtcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2UgaWYgKCFjdXJyZW50VHJhY2sgJiYgIXRoaXMudHJhY2tzSW5Hcm91cC5sZW5ndGgpIHtcbiAgICAgICAgLy8gRG8gbm90IGRpc3BhdGNoIEFVRElPX1RSQUNLU19VUERBVEVEIHdoZW4gdGhlcmUgd2VyZSBhbmQgYXJlIG5vIHRyYWNrc1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICB0aGlzLnRyYWNrc0luR3JvdXAgPSBhdWRpb1RyYWNrcztcblxuICAgICAgLy8gRmluZCBwcmVmZXJyZWQgdHJhY2tcbiAgICAgIGNvbnN0IGF1ZGlvUHJlZmVyZW5jZSA9IHRoaXMuaGxzLmNvbmZpZy5hdWRpb1ByZWZlcmVuY2U7XG4gICAgICBpZiAoIWN1cnJlbnRUcmFjayAmJiBhdWRpb1ByZWZlcmVuY2UpIHtcbiAgICAgICAgY29uc3QgZ3JvdXBJbmRleCA9IGZpbmRNYXRjaGluZ09wdGlvbihhdWRpb1ByZWZlcmVuY2UsIGF1ZGlvVHJhY2tzLCBhdWRpb01hdGNoUHJlZGljYXRlKTtcbiAgICAgICAgaWYgKGdyb3VwSW5kZXggPiAtMSkge1xuICAgICAgICAgIGN1cnJlbnRUcmFjayA9IGF1ZGlvVHJhY2tzW2dyb3VwSW5kZXhdO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnN0IGFsbEluZGV4ID0gZmluZE1hdGNoaW5nT3B0aW9uKGF1ZGlvUHJlZmVyZW5jZSwgdGhpcy50cmFja3MpO1xuICAgICAgICAgIGN1cnJlbnRUcmFjayA9IHRoaXMudHJhY2tzW2FsbEluZGV4XTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBTZWxlY3QgaW5pdGlhbCB0cmFja1xuICAgICAgbGV0IHRyYWNrSWQgPSB0aGlzLmZpbmRUcmFja0lkKGN1cnJlbnRUcmFjayk7XG4gICAgICBpZiAodHJhY2tJZCA9PT0gLTEgJiYgY3VycmVudFRyYWNrKSB7XG4gICAgICAgIHRyYWNrSWQgPSB0aGlzLmZpbmRUcmFja0lkKG51bGwpO1xuICAgICAgfVxuXG4gICAgICAvLyBEaXNwYXRjaCBldmVudHMgYW5kIGxvYWQgdHJhY2sgaWYgbmVlZGVkXG4gICAgICBjb25zdCBhdWRpb1RyYWNrc1VwZGF0ZWQgPSB7XG4gICAgICAgIGF1ZGlvVHJhY2tzXG4gICAgICB9O1xuICAgICAgdGhpcy5sb2coYFVwZGF0aW5nIGF1ZGlvIHRyYWNrcywgJHthdWRpb1RyYWNrcy5sZW5ndGh9IHRyYWNrKHMpIGZvdW5kIGluIGdyb3VwKHMpOiAke2F1ZGlvR3JvdXBzID09IG51bGwgPyB2b2lkIDAgOiBhdWRpb0dyb3Vwcy5qb2luKCcsJyl9YCk7XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5BVURJT19UUkFDS1NfVVBEQVRFRCwgYXVkaW9UcmFja3NVcGRhdGVkKTtcbiAgICAgIGNvbnN0IHNlbGVjdGVkVHJhY2tJZCA9IHRoaXMudHJhY2tJZDtcbiAgICAgIGlmICh0cmFja0lkICE9PSAtMSAmJiBzZWxlY3RlZFRyYWNrSWQgPT09IC0xKSB7XG4gICAgICAgIHRoaXMuc2V0QXVkaW9UcmFjayh0cmFja0lkKTtcbiAgICAgIH0gZWxzZSBpZiAoYXVkaW9UcmFja3MubGVuZ3RoICYmIHNlbGVjdGVkVHJhY2tJZCA9PT0gLTEpIHtcbiAgICAgICAgdmFyIF90aGlzJGdyb3VwSWRzO1xuICAgICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcihgTm8gYXVkaW8gdHJhY2sgc2VsZWN0ZWQgZm9yIGN1cnJlbnQgYXVkaW8gZ3JvdXAtSUQocyk6ICR7KF90aGlzJGdyb3VwSWRzID0gdGhpcy5ncm91cElkcykgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGdyb3VwSWRzLmpvaW4oJywnKX0gdHJhY2sgY291bnQ6ICR7YXVkaW9UcmFja3MubGVuZ3RofWApO1xuICAgICAgICB0aGlzLndhcm4oZXJyb3IubWVzc2FnZSk7XG4gICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCB7XG4gICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5NRURJQV9FUlJPUixcbiAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQVVESU9fVFJBQ0tfTE9BRF9FUlJPUixcbiAgICAgICAgICBmYXRhbDogdHJ1ZSxcbiAgICAgICAgICBlcnJvclxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHRoaXMuc2hvdWxkUmVsb2FkUGxheWxpc3QoY3VycmVudFRyYWNrKSkge1xuICAgICAgLy8gUmV0cnkgcGxheWxpc3QgbG9hZGluZyBpZiBubyBwbGF5bGlzdCBpcyBvciBoYXMgYmVlbiBsb2FkZWQgeWV0XG4gICAgICB0aGlzLnNldEF1ZGlvVHJhY2sodGhpcy50cmFja0lkKTtcbiAgICB9XG4gIH1cbiAgb25FcnJvcihldmVudCwgZGF0YSkge1xuICAgIGlmIChkYXRhLmZhdGFsIHx8ICFkYXRhLmNvbnRleHQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGRhdGEuY29udGV4dC50eXBlID09PSBQbGF5bGlzdENvbnRleHRUeXBlLkFVRElPX1RSQUNLICYmIGRhdGEuY29udGV4dC5pZCA9PT0gdGhpcy50cmFja0lkICYmICghdGhpcy5ncm91cElkcyB8fCB0aGlzLmdyb3VwSWRzLmluZGV4T2YoZGF0YS5jb250ZXh0Lmdyb3VwSWQpICE9PSAtMSkpIHtcbiAgICAgIHRoaXMucmVxdWVzdFNjaGVkdWxlZCA9IC0xO1xuICAgICAgdGhpcy5jaGVja1JldHJ5KGRhdGEpO1xuICAgIH1cbiAgfVxuICBnZXQgYWxsQXVkaW9UcmFja3MoKSB7XG4gICAgcmV0dXJuIHRoaXMudHJhY2tzO1xuICB9XG4gIGdldCBhdWRpb1RyYWNrcygpIHtcbiAgICByZXR1cm4gdGhpcy50cmFja3NJbkdyb3VwO1xuICB9XG4gIGdldCBhdWRpb1RyYWNrKCkge1xuICAgIHJldHVybiB0aGlzLnRyYWNrSWQ7XG4gIH1cbiAgc2V0IGF1ZGlvVHJhY2sobmV3SWQpIHtcbiAgICAvLyBJZiBhdWRpbyB0cmFjayBpcyBzZWxlY3RlZCBmcm9tIEFQSSB0aGVuIGRvbid0IGNob29zZSBmcm9tIHRoZSBtYW5pZmVzdCBkZWZhdWx0IHRyYWNrXG4gICAgdGhpcy5zZWxlY3REZWZhdWx0VHJhY2sgPSBmYWxzZTtcbiAgICB0aGlzLnNldEF1ZGlvVHJhY2sobmV3SWQpO1xuICB9XG4gIHNldEF1ZGlvT3B0aW9uKGF1ZGlvT3B0aW9uKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgaGxzLmNvbmZpZy5hdWRpb1ByZWZlcmVuY2UgPSBhdWRpb09wdGlvbjtcbiAgICBpZiAoYXVkaW9PcHRpb24pIHtcbiAgICAgIGNvbnN0IGFsbEF1ZGlvVHJhY2tzID0gdGhpcy5hbGxBdWRpb1RyYWNrcztcbiAgICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gZmFsc2U7XG4gICAgICBpZiAoYWxsQXVkaW9UcmFja3MubGVuZ3RoKSB7XG4gICAgICAgIC8vIEZpcnN0IHNlZSBpZiBjdXJyZW50IG9wdGlvbiBtYXRjaGVzIChubyBzd2l0Y2ggb3ApXG4gICAgICAgIGNvbnN0IGN1cnJlbnRUcmFjayA9IHRoaXMuY3VycmVudFRyYWNrO1xuICAgICAgICBpZiAoY3VycmVudFRyYWNrICYmIG1hdGNoZXNPcHRpb24oYXVkaW9PcHRpb24sIGN1cnJlbnRUcmFjaywgYXVkaW9NYXRjaFByZWRpY2F0ZSkpIHtcbiAgICAgICAgICByZXR1cm4gY3VycmVudFRyYWNrO1xuICAgICAgICB9XG4gICAgICAgIC8vIEZpbmQgb3B0aW9uIGluIGF2YWlsYWJsZSB0cmFja3MgKHRyYWNrc0luR3JvdXApXG4gICAgICAgIGNvbnN0IGdyb3VwSW5kZXggPSBmaW5kTWF0Y2hpbmdPcHRpb24oYXVkaW9PcHRpb24sIHRoaXMudHJhY2tzSW5Hcm91cCwgYXVkaW9NYXRjaFByZWRpY2F0ZSk7XG4gICAgICAgIGlmIChncm91cEluZGV4ID4gLTEpIHtcbiAgICAgICAgICBjb25zdCB0cmFjayA9IHRoaXMudHJhY2tzSW5Hcm91cFtncm91cEluZGV4XTtcbiAgICAgICAgICB0aGlzLnNldEF1ZGlvVHJhY2soZ3JvdXBJbmRleCk7XG4gICAgICAgICAgcmV0dXJuIHRyYWNrO1xuICAgICAgICB9IGVsc2UgaWYgKGN1cnJlbnRUcmFjaykge1xuICAgICAgICAgIC8vIEZpbmQgb3B0aW9uIGluIG5lYXJlc3QgbGV2ZWwgYXVkaW8gZ3JvdXBcbiAgICAgICAgICBsZXQgc2VhcmNoSW5kZXggPSBobHMubG9hZExldmVsO1xuICAgICAgICAgIGlmIChzZWFyY2hJbmRleCA9PT0gLTEpIHtcbiAgICAgICAgICAgIHNlYXJjaEluZGV4ID0gaGxzLmZpcnN0QXV0b0xldmVsO1xuICAgICAgICAgIH1cbiAgICAgICAgICBjb25zdCBzd2l0Y2hJbmRleCA9IGZpbmRDbG9zZXN0TGV2ZWxXaXRoQXVkaW9Hcm91cChhdWRpb09wdGlvbiwgaGxzLmxldmVscywgYWxsQXVkaW9UcmFja3MsIHNlYXJjaEluZGV4LCBhdWRpb01hdGNoUHJlZGljYXRlKTtcbiAgICAgICAgICBpZiAoc3dpdGNoSW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICAvLyBjb3VsZCBub3QgZmluZCBtYXRjaGluZyB2YXJpYW50XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgICB9XG4gICAgICAgICAgLy8gYW5kIHN3aXRjaCBsZXZlbCB0byBhY2hlaXZlIHRoZSBhdWRpbyBncm91cCBzd2l0Y2hcbiAgICAgICAgICBobHMubmV4dExvYWRMZXZlbCA9IHN3aXRjaEluZGV4O1xuICAgICAgICB9XG4gICAgICAgIGlmIChhdWRpb09wdGlvbi5jaGFubmVscyB8fCBhdWRpb09wdGlvbi5hdWRpb0NvZGVjKSB7XG4gICAgICAgICAgLy8gQ291bGQgbm90IGZpbmQgYSBtYXRjaCB3aXRoIGNvZGVjIC8gY2hhbm5lbHMgcHJlZGljYXRlXG4gICAgICAgICAgLy8gRmluZCBhIG1hdGNoIHdpdGhvdXQgY2hhbm5lbHMgb3IgY29kZWNcbiAgICAgICAgICBjb25zdCB3aXRob3V0Q29kZWNBbmRDaGFubmVsc01hdGNoID0gZmluZE1hdGNoaW5nT3B0aW9uKGF1ZGlvT3B0aW9uLCBhbGxBdWRpb1RyYWNrcyk7XG4gICAgICAgICAgaWYgKHdpdGhvdXRDb2RlY0FuZENoYW5uZWxzTWF0Y2ggPiAtMSkge1xuICAgICAgICAgICAgcmV0dXJuIGFsbEF1ZGlvVHJhY2tzW3dpdGhvdXRDb2RlY0FuZENoYW5uZWxzTWF0Y2hdO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBzZXRBdWRpb1RyYWNrKG5ld0lkKSB7XG4gICAgY29uc3QgdHJhY2tzID0gdGhpcy50cmFja3NJbkdyb3VwO1xuXG4gICAgLy8gY2hlY2sgaWYgbGV2ZWwgaWR4IGlzIHZhbGlkXG4gICAgaWYgKG5ld0lkIDwgMCB8fCBuZXdJZCA+PSB0cmFja3MubGVuZ3RoKSB7XG4gICAgICB0aGlzLndhcm4oYEludmFsaWQgYXVkaW8gdHJhY2sgaWQ6ICR7bmV3SWR9YCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gc3RvcHBpbmcgbGl2ZSByZWxvYWRpbmcgdGltZXIgaWYgYW55XG4gICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgdGhpcy5zZWxlY3REZWZhdWx0VHJhY2sgPSBmYWxzZTtcbiAgICBjb25zdCBsYXN0VHJhY2sgPSB0aGlzLmN1cnJlbnRUcmFjaztcbiAgICBjb25zdCB0cmFjayA9IHRyYWNrc1tuZXdJZF07XG4gICAgY29uc3QgdHJhY2tMb2FkZWQgPSB0cmFjay5kZXRhaWxzICYmICF0cmFjay5kZXRhaWxzLmxpdmU7XG4gICAgaWYgKG5ld0lkID09PSB0aGlzLnRyYWNrSWQgJiYgdHJhY2sgPT09IGxhc3RUcmFjayAmJiB0cmFja0xvYWRlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmxvZyhgU3dpdGNoaW5nIHRvIGF1ZGlvLXRyYWNrICR7bmV3SWR9IFwiJHt0cmFjay5uYW1lfVwiIGxhbmc6JHt0cmFjay5sYW5nfSBncm91cDoke3RyYWNrLmdyb3VwSWR9IGNoYW5uZWxzOiR7dHJhY2suY2hhbm5lbHN9YCk7XG4gICAgdGhpcy50cmFja0lkID0gbmV3SWQ7XG4gICAgdGhpcy5jdXJyZW50VHJhY2sgPSB0cmFjaztcbiAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5BVURJT19UUkFDS19TV0lUQ0hJTkcsIF9vYmplY3RTcHJlYWQyKHt9LCB0cmFjaykpO1xuICAgIC8vIERvIG5vdCByZWxvYWQgdHJhY2sgdW5sZXNzIGxpdmVcbiAgICBpZiAodHJhY2tMb2FkZWQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgaGxzVXJsUGFyYW1ldGVycyA9IHRoaXMuc3dpdGNoUGFyYW1zKHRyYWNrLnVybCwgbGFzdFRyYWNrID09IG51bGwgPyB2b2lkIDAgOiBsYXN0VHJhY2suZGV0YWlscywgdHJhY2suZGV0YWlscyk7XG4gICAgdGhpcy5sb2FkUGxheWxpc3QoaGxzVXJsUGFyYW1ldGVycyk7XG4gIH1cbiAgZmluZFRyYWNrSWQoY3VycmVudFRyYWNrKSB7XG4gICAgY29uc3QgYXVkaW9UcmFja3MgPSB0aGlzLnRyYWNrc0luR3JvdXA7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhdWRpb1RyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgICAgY29uc3QgdHJhY2sgPSBhdWRpb1RyYWNrc1tpXTtcbiAgICAgIGlmICh0aGlzLnNlbGVjdERlZmF1bHRUcmFjayAmJiAhdHJhY2suZGVmYXVsdCkge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmICghY3VycmVudFRyYWNrIHx8IG1hdGNoZXNPcHRpb24oY3VycmVudFRyYWNrLCB0cmFjaywgYXVkaW9NYXRjaFByZWRpY2F0ZSkpIHtcbiAgICAgICAgcmV0dXJuIGk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChjdXJyZW50VHJhY2spIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgbmFtZSxcbiAgICAgICAgbGFuZyxcbiAgICAgICAgYXNzb2NMYW5nLFxuICAgICAgICBjaGFyYWN0ZXJpc3RpY3MsXG4gICAgICAgIGF1ZGlvQ29kZWMsXG4gICAgICAgIGNoYW5uZWxzXG4gICAgICB9ID0gY3VycmVudFRyYWNrO1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhdWRpb1RyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCB0cmFjayA9IGF1ZGlvVHJhY2tzW2ldO1xuICAgICAgICBpZiAobWF0Y2hlc09wdGlvbih7XG4gICAgICAgICAgbmFtZSxcbiAgICAgICAgICBsYW5nLFxuICAgICAgICAgIGFzc29jTGFuZyxcbiAgICAgICAgICBjaGFyYWN0ZXJpc3RpY3MsXG4gICAgICAgICAgYXVkaW9Db2RlYyxcbiAgICAgICAgICBjaGFubmVsc1xuICAgICAgICB9LCB0cmFjaywgYXVkaW9NYXRjaFByZWRpY2F0ZSkpIHtcbiAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBhdWRpb1RyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCB0cmFjayA9IGF1ZGlvVHJhY2tzW2ldO1xuICAgICAgICBpZiAobWVkaWFBdHRyaWJ1dGVzSWRlbnRpY2FsKGN1cnJlbnRUcmFjay5hdHRycywgdHJhY2suYXR0cnMsIFsnTEFOR1VBR0UnLCAnQVNTT0MtTEFOR1VBR0UnLCAnQ0hBUkFDVEVSSVNUSUNTJ10pKSB7XG4gICAgICAgICAgcmV0dXJuIGk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYXVkaW9UcmFja3MubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3QgdHJhY2sgPSBhdWRpb1RyYWNrc1tpXTtcbiAgICAgICAgaWYgKG1lZGlhQXR0cmlidXRlc0lkZW50aWNhbChjdXJyZW50VHJhY2suYXR0cnMsIHRyYWNrLmF0dHJzLCBbJ0xBTkdVQUdFJ10pKSB7XG4gICAgICAgICAgcmV0dXJuIGk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIC0xO1xuICB9XG4gIGxvYWRQbGF5bGlzdChobHNVcmxQYXJhbWV0ZXJzKSB7XG4gICAgY29uc3QgYXVkaW9UcmFjayA9IHRoaXMuY3VycmVudFRyYWNrO1xuICAgIGlmICh0aGlzLnNob3VsZExvYWRQbGF5bGlzdChhdWRpb1RyYWNrKSAmJiBhdWRpb1RyYWNrKSB7XG4gICAgICBzdXBlci5sb2FkUGxheWxpc3QoKTtcbiAgICAgIGNvbnN0IGlkID0gYXVkaW9UcmFjay5pZDtcbiAgICAgIGNvbnN0IGdyb3VwSWQgPSBhdWRpb1RyYWNrLmdyb3VwSWQ7XG4gICAgICBsZXQgdXJsID0gYXVkaW9UcmFjay51cmw7XG4gICAgICBpZiAoaGxzVXJsUGFyYW1ldGVycykge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIHVybCA9IGhsc1VybFBhcmFtZXRlcnMuYWRkRGlyZWN0aXZlcyh1cmwpO1xuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIHRoaXMud2FybihgQ291bGQgbm90IGNvbnN0cnVjdCBuZXcgVVJMIHdpdGggSExTIERlbGl2ZXJ5IERpcmVjdGl2ZXM6ICR7ZXJyb3J9YCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIC8vIHRyYWNrIG5vdCByZXRyaWV2ZWQgeWV0LCBvciBsaXZlIHBsYXlsaXN0IHdlIG5lZWQgdG8gKHJlKWxvYWQgaXRcbiAgICAgIHRoaXMubG9nKGBsb2FkaW5nIGF1ZGlvLXRyYWNrIHBsYXlsaXN0ICR7aWR9IFwiJHthdWRpb1RyYWNrLm5hbWV9XCIgbGFuZzoke2F1ZGlvVHJhY2subGFuZ30gZ3JvdXA6JHtncm91cElkfWApO1xuICAgICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5BVURJT19UUkFDS19MT0FESU5HLCB7XG4gICAgICAgIHVybCxcbiAgICAgICAgaWQsXG4gICAgICAgIGdyb3VwSWQsXG4gICAgICAgIGRlbGl2ZXJ5RGlyZWN0aXZlczogaGxzVXJsUGFyYW1ldGVycyB8fCBudWxsXG4gICAgICB9KTtcbiAgICB9XG4gIH1cbn1cblxuY29uc3QgVElDS19JTlRFUlZBTCQxID0gNTAwOyAvLyBob3cgb2Z0ZW4gdG8gdGljayBpbiBtc1xuXG5jbGFzcyBTdWJ0aXRsZVN0cmVhbUNvbnRyb2xsZXIgZXh0ZW5kcyBCYXNlU3RyZWFtQ29udHJvbGxlciB7XG4gIGNvbnN0cnVjdG9yKGhscywgZnJhZ21lbnRUcmFja2VyLCBrZXlMb2FkZXIpIHtcbiAgICBzdXBlcihobHMsIGZyYWdtZW50VHJhY2tlciwga2V5TG9hZGVyLCAnW3N1YnRpdGxlLXN0cmVhbS1jb250cm9sbGVyXScsIFBsYXlsaXN0TGV2ZWxUeXBlLlNVQlRJVExFKTtcbiAgICB0aGlzLmN1cnJlbnRUcmFja0lkID0gLTE7XG4gICAgdGhpcy50cmFja3NCdWZmZXJlZCA9IFtdO1xuICAgIHRoaXMubWFpbkRldGFpbHMgPSBudWxsO1xuICAgIHRoaXMuX3JlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gIH1cbiAgb25IYW5kbGVyRGVzdHJveWluZygpIHtcbiAgICB0aGlzLl91bnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgc3VwZXIub25IYW5kbGVyRGVzdHJveWluZygpO1xuICAgIHRoaXMubWFpbkRldGFpbHMgPSBudWxsO1xuICB9XG4gIF9yZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub24oRXZlbnRzLk1FRElBX0FUVEFDSEVELCB0aGlzLm9uTWVkaWFBdHRhY2hlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkxFVkVMX0xPQURFRCwgdGhpcy5vbkxldmVsTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuU1VCVElUTEVfVFJBQ0tTX1VQREFURUQsIHRoaXMub25TdWJ0aXRsZVRyYWNrc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuU1VCVElUTEVfVFJBQ0tfU1dJVENILCB0aGlzLm9uU3VidGl0bGVUcmFja1N3aXRjaCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5TVUJUSVRMRV9UUkFDS19MT0FERUQsIHRoaXMub25TdWJ0aXRsZVRyYWNrTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLlNVQlRJVExFX0ZSQUdfUFJPQ0VTU0VELCB0aGlzLm9uU3VidGl0bGVGcmFnUHJvY2Vzc2VkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9GTFVTSElORywgdGhpcy5vbkJ1ZmZlckZsdXNoaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkZSQUdfQlVGRkVSRUQsIHRoaXMub25GcmFnQnVmZmVyZWQsIHRoaXMpO1xuICB9XG4gIF91bnJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vZmYoRXZlbnRzLk1FRElBX0FUVEFDSEVELCB0aGlzLm9uTWVkaWFBdHRhY2hlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfREVUQUNISU5HLCB0aGlzLm9uTWVkaWFEZXRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX0xPQURFRCwgdGhpcy5vbkxldmVsTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5FUlJPUiwgdGhpcy5vbkVycm9yLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5TVUJUSVRMRV9UUkFDS1NfVVBEQVRFRCwgdGhpcy5vblN1YnRpdGxlVHJhY2tzVXBkYXRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuU1VCVElUTEVfVFJBQ0tfU1dJVENILCB0aGlzLm9uU3VidGl0bGVUcmFja1N3aXRjaCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuU1VCVElUTEVfVFJBQ0tfTE9BREVELCB0aGlzLm9uU3VidGl0bGVUcmFja0xvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuU1VCVElUTEVfRlJBR19QUk9DRVNTRUQsIHRoaXMub25TdWJ0aXRsZUZyYWdQcm9jZXNzZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9GTFVTSElORywgdGhpcy5vbkJ1ZmZlckZsdXNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgfVxuICBzdGFydExvYWQoc3RhcnRQb3NpdGlvbikge1xuICAgIHRoaXMuc3RvcExvYWQoKTtcbiAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICB0aGlzLnNldEludGVydmFsKFRJQ0tfSU5URVJWQUwkMSk7XG4gICAgdGhpcy5uZXh0TG9hZFBvc2l0aW9uID0gdGhpcy5zdGFydFBvc2l0aW9uID0gdGhpcy5sYXN0Q3VycmVudFRpbWUgPSBzdGFydFBvc2l0aW9uO1xuICAgIHRoaXMudGljaygpO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKCkge1xuICAgIHRoaXMubWFpbkRldGFpbHMgPSBudWxsO1xuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUFsbEZyYWdtZW50cygpO1xuICB9XG4gIG9uTWVkaWFEZXRhY2hpbmcoKSB7XG4gICAgdGhpcy50cmFja3NCdWZmZXJlZCA9IFtdO1xuICAgIHN1cGVyLm9uTWVkaWFEZXRhY2hpbmcoKTtcbiAgfVxuICBvbkxldmVsTG9hZGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5tYWluRGV0YWlscyA9IGRhdGEuZGV0YWlscztcbiAgfVxuICBvblN1YnRpdGxlRnJhZ1Byb2Nlc3NlZChldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBzdWNjZXNzXG4gICAgfSA9IGRhdGE7XG4gICAgdGhpcy5mcmFnUHJldmlvdXMgPSBmcmFnO1xuICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIGlmICghc3VjY2Vzcykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBidWZmZXJlZCA9IHRoaXMudHJhY2tzQnVmZmVyZWRbdGhpcy5jdXJyZW50VHJhY2tJZF07XG4gICAgaWYgKCFidWZmZXJlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZS91cGRhdGUgYSBidWZmZXJlZCBhcnJheSBtYXRjaGluZyB0aGUgaW50ZXJmYWNlIHVzZWQgYnkgQnVmZmVySGVscGVyLmJ1ZmZlcmVkSW5mb1xuICAgIC8vIHNvIHdlIGNhbiByZS11c2UgdGhlIGxvZ2ljIHVzZWQgdG8gZGV0ZWN0IGhvdyBtdWNoIGhhcyBiZWVuIGJ1ZmZlcmVkXG4gICAgbGV0IHRpbWVSYW5nZTtcbiAgICBjb25zdCBmcmFnU3RhcnQgPSBmcmFnLnN0YXJ0O1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYnVmZmVyZWQubGVuZ3RoOyBpKyspIHtcbiAgICAgIGlmIChmcmFnU3RhcnQgPj0gYnVmZmVyZWRbaV0uc3RhcnQgJiYgZnJhZ1N0YXJ0IDw9IGJ1ZmZlcmVkW2ldLmVuZCkge1xuICAgICAgICB0aW1lUmFuZ2UgPSBidWZmZXJlZFtpXTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IGZyYWdFbmQgPSBmcmFnLnN0YXJ0ICsgZnJhZy5kdXJhdGlvbjtcbiAgICBpZiAodGltZVJhbmdlKSB7XG4gICAgICB0aW1lUmFuZ2UuZW5kID0gZnJhZ0VuZDtcbiAgICB9IGVsc2Uge1xuICAgICAgdGltZVJhbmdlID0ge1xuICAgICAgICBzdGFydDogZnJhZ1N0YXJ0LFxuICAgICAgICBlbmQ6IGZyYWdFbmRcbiAgICAgIH07XG4gICAgICBidWZmZXJlZC5wdXNoKHRpbWVSYW5nZSk7XG4gICAgfVxuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLmZyYWdCdWZmZXJlZChmcmFnKTtcbiAgICB0aGlzLmZyYWdCdWZmZXJlZENvbXBsZXRlKGZyYWcsIG51bGwpO1xuICB9XG4gIG9uQnVmZmVyRmx1c2hpbmcoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBzdGFydE9mZnNldCxcbiAgICAgIGVuZE9mZnNldFxuICAgIH0gPSBkYXRhO1xuICAgIGlmIChzdGFydE9mZnNldCA9PT0gMCAmJiBlbmRPZmZzZXQgIT09IE51bWJlci5QT1NJVElWRV9JTkZJTklUWSkge1xuICAgICAgY29uc3QgZW5kT2Zmc2V0U3VidGl0bGVzID0gZW5kT2Zmc2V0IC0gMTtcbiAgICAgIGlmIChlbmRPZmZzZXRTdWJ0aXRsZXMgPD0gMCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBkYXRhLmVuZE9mZnNldFN1YnRpdGxlcyA9IE1hdGgubWF4KDAsIGVuZE9mZnNldFN1YnRpdGxlcyk7XG4gICAgICB0aGlzLnRyYWNrc0J1ZmZlcmVkLmZvckVhY2goYnVmZmVyZWQgPT4ge1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGJ1ZmZlcmVkLmxlbmd0aDspIHtcbiAgICAgICAgICBpZiAoYnVmZmVyZWRbaV0uZW5kIDw9IGVuZE9mZnNldFN1YnRpdGxlcykge1xuICAgICAgICAgICAgYnVmZmVyZWQuc2hpZnQoKTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgIH0gZWxzZSBpZiAoYnVmZmVyZWRbaV0uc3RhcnQgPCBlbmRPZmZzZXRTdWJ0aXRsZXMpIHtcbiAgICAgICAgICAgIGJ1ZmZlcmVkW2ldLnN0YXJ0ID0gZW5kT2Zmc2V0U3VidGl0bGVzO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICB9XG4gICAgICAgICAgaSsrO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50c0luUmFuZ2Uoc3RhcnRPZmZzZXQsIGVuZE9mZnNldFN1YnRpdGxlcywgUGxheWxpc3RMZXZlbFR5cGUuU1VCVElUTEUpO1xuICAgIH1cbiAgfVxuICBvbkZyYWdCdWZmZXJlZChldmVudCwgZGF0YSkge1xuICAgIGlmICghdGhpcy5sb2FkZWRtZXRhZGF0YSAmJiBkYXRhLmZyYWcudHlwZSA9PT0gUGxheWxpc3RMZXZlbFR5cGUuTUFJTikge1xuICAgICAgdmFyIF90aGlzJG1lZGlhO1xuICAgICAgaWYgKChfdGhpcyRtZWRpYSA9IHRoaXMubWVkaWEpICE9IG51bGwgJiYgX3RoaXMkbWVkaWEuYnVmZmVyZWQubGVuZ3RoKSB7XG4gICAgICAgIHRoaXMubG9hZGVkbWV0YWRhdGEgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIElmIHNvbWV0aGluZyBnb2VzIHdyb25nLCBwcm9jZWVkIHRvIG5leHQgZnJhZywgaWYgd2Ugd2VyZSBwcm9jZXNzaW5nIG9uZS5cbiAgb25FcnJvcihldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IGZyYWcgPSBkYXRhLmZyYWc7XG4gICAgaWYgKChmcmFnID09IG51bGwgPyB2b2lkIDAgOiBmcmFnLnR5cGUpID09PSBQbGF5bGlzdExldmVsVHlwZS5TVUJUSVRMRSkge1xuICAgICAgaWYgKGRhdGEuZGV0YWlscyA9PT0gRXJyb3JEZXRhaWxzLkZSQUdfR0FQKSB7XG4gICAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLmZyYWdCdWZmZXJlZChmcmFnLCB0cnVlKTtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLmZyYWdDdXJyZW50KSB7XG4gICAgICAgIHRoaXMuZnJhZ0N1cnJlbnQuYWJvcnRSZXF1ZXN0cygpO1xuICAgICAgfVxuICAgICAgaWYgKHRoaXMuc3RhdGUgIT09IFN0YXRlLlNUT1BQRUQpIHtcbiAgICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLy8gR290IGFsbCBuZXcgc3VidGl0bGUgbGV2ZWxzLlxuICBvblN1YnRpdGxlVHJhY2tzVXBkYXRlZChldmVudCwge1xuICAgIHN1YnRpdGxlVHJhY2tzXG4gIH0pIHtcbiAgICBpZiAodGhpcy5sZXZlbHMgJiYgc3VidGl0bGVPcHRpb25zSWRlbnRpY2FsKHRoaXMubGV2ZWxzLCBzdWJ0aXRsZVRyYWNrcykpIHtcbiAgICAgIHRoaXMubGV2ZWxzID0gc3VidGl0bGVUcmFja3MubWFwKG1lZGlhUGxheWxpc3QgPT4gbmV3IExldmVsKG1lZGlhUGxheWxpc3QpKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy50cmFja3NCdWZmZXJlZCA9IFtdO1xuICAgIHRoaXMubGV2ZWxzID0gc3VidGl0bGVUcmFja3MubWFwKG1lZGlhUGxheWxpc3QgPT4ge1xuICAgICAgY29uc3QgbGV2ZWwgPSBuZXcgTGV2ZWwobWVkaWFQbGF5bGlzdCk7XG4gICAgICB0aGlzLnRyYWNrc0J1ZmZlcmVkW2xldmVsLmlkXSA9IFtdO1xuICAgICAgcmV0dXJuIGxldmVsO1xuICAgIH0pO1xuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50c0luUmFuZ2UoMCwgTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZLCBQbGF5bGlzdExldmVsVHlwZS5TVUJUSVRMRSk7XG4gICAgdGhpcy5mcmFnUHJldmlvdXMgPSBudWxsO1xuICAgIHRoaXMubWVkaWFCdWZmZXIgPSBudWxsO1xuICB9XG4gIG9uU3VidGl0bGVUcmFja1N3aXRjaChldmVudCwgZGF0YSkge1xuICAgIHZhciBfdGhpcyRsZXZlbHM7XG4gICAgdGhpcy5jdXJyZW50VHJhY2tJZCA9IGRhdGEuaWQ7XG4gICAgaWYgKCEoKF90aGlzJGxldmVscyA9IHRoaXMubGV2ZWxzKSAhPSBudWxsICYmIF90aGlzJGxldmVscy5sZW5ndGgpIHx8IHRoaXMuY3VycmVudFRyYWNrSWQgPT09IC0xKSB7XG4gICAgICB0aGlzLmNsZWFySW50ZXJ2YWwoKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiB0cmFjayBoYXMgdGhlIG5lY2Vzc2FyeSBkZXRhaWxzIHRvIGxvYWQgZnJhZ21lbnRzXG4gICAgY29uc3QgY3VycmVudFRyYWNrID0gdGhpcy5sZXZlbHNbdGhpcy5jdXJyZW50VHJhY2tJZF07XG4gICAgaWYgKGN1cnJlbnRUcmFjayAhPSBudWxsICYmIGN1cnJlbnRUcmFjay5kZXRhaWxzKSB7XG4gICAgICB0aGlzLm1lZGlhQnVmZmVyID0gdGhpcy5tZWRpYUJ1ZmZlclRpbWVSYW5nZXM7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMubWVkaWFCdWZmZXIgPSBudWxsO1xuICAgIH1cbiAgICBpZiAoY3VycmVudFRyYWNrKSB7XG4gICAgICB0aGlzLnNldEludGVydmFsKFRJQ0tfSU5URVJWQUwkMSk7XG4gICAgfVxuICB9XG5cbiAgLy8gR290IGEgbmV3IHNldCBvZiBzdWJ0aXRsZSBmcmFnbWVudHMuXG4gIG9uU3VidGl0bGVUcmFja0xvYWRlZChldmVudCwgZGF0YSkge1xuICAgIHZhciBfdHJhY2skZGV0YWlscztcbiAgICBjb25zdCB7XG4gICAgICBjdXJyZW50VHJhY2tJZCxcbiAgICAgIGxldmVsc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIGRldGFpbHM6IG5ld0RldGFpbHMsXG4gICAgICBpZDogdHJhY2tJZFxuICAgIH0gPSBkYXRhO1xuICAgIGlmICghbGV2ZWxzKSB7XG4gICAgICB0aGlzLndhcm4oYFN1YnRpdGxlIHRyYWNrcyB3ZXJlIHJlc2V0IHdoaWxlIGxvYWRpbmcgbGV2ZWwgJHt0cmFja0lkfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB0cmFjayA9IGxldmVsc1t0cmFja0lkXTtcbiAgICBpZiAodHJhY2tJZCA+PSBsZXZlbHMubGVuZ3RoIHx8ICF0cmFjaykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmxvZyhgU3VidGl0bGUgdHJhY2sgJHt0cmFja0lkfSBsb2FkZWQgWyR7bmV3RGV0YWlscy5zdGFydFNOfSwke25ld0RldGFpbHMuZW5kU059XSR7bmV3RGV0YWlscy5sYXN0UGFydFNuID8gYFtwYXJ0LSR7bmV3RGV0YWlscy5sYXN0UGFydFNufS0ke25ld0RldGFpbHMubGFzdFBhcnRJbmRleH1dYCA6ICcnfSxkdXJhdGlvbjoke25ld0RldGFpbHMudG90YWxkdXJhdGlvbn1gKTtcbiAgICB0aGlzLm1lZGlhQnVmZmVyID0gdGhpcy5tZWRpYUJ1ZmZlclRpbWVSYW5nZXM7XG4gICAgbGV0IHNsaWRpbmcgPSAwO1xuICAgIGlmIChuZXdEZXRhaWxzLmxpdmUgfHwgKF90cmFjayRkZXRhaWxzID0gdHJhY2suZGV0YWlscykgIT0gbnVsbCAmJiBfdHJhY2skZGV0YWlscy5saXZlKSB7XG4gICAgICBjb25zdCBtYWluRGV0YWlscyA9IHRoaXMubWFpbkRldGFpbHM7XG4gICAgICBpZiAobmV3RGV0YWlscy5kZWx0YVVwZGF0ZUZhaWxlZCB8fCAhbWFpbkRldGFpbHMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3QgbWFpblNsaWRpbmdTdGFydEZyYWdtZW50ID0gbWFpbkRldGFpbHMuZnJhZ21lbnRzWzBdO1xuICAgICAgaWYgKCF0cmFjay5kZXRhaWxzKSB7XG4gICAgICAgIGlmIChuZXdEZXRhaWxzLmhhc1Byb2dyYW1EYXRlVGltZSAmJiBtYWluRGV0YWlscy5oYXNQcm9ncmFtRGF0ZVRpbWUpIHtcbiAgICAgICAgICBhbGlnbk1lZGlhUGxheWxpc3RCeVBEVChuZXdEZXRhaWxzLCBtYWluRGV0YWlscyk7XG4gICAgICAgICAgc2xpZGluZyA9IG5ld0RldGFpbHMuZnJhZ21lbnRzWzBdLnN0YXJ0O1xuICAgICAgICB9IGVsc2UgaWYgKG1haW5TbGlkaW5nU3RhcnRGcmFnbWVudCkge1xuICAgICAgICAgIC8vIGxpbmUgdXAgbGl2ZSBwbGF5bGlzdCB3aXRoIG1haW4gc28gdGhhdCBmcmFnbWVudHMgaW4gcmFuZ2UgYXJlIGxvYWRlZFxuICAgICAgICAgIHNsaWRpbmcgPSBtYWluU2xpZGluZ1N0YXJ0RnJhZ21lbnQuc3RhcnQ7XG4gICAgICAgICAgYWRkU2xpZGluZyhuZXdEZXRhaWxzLCBzbGlkaW5nKTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdmFyIF90aGlzJGxldmVsTGFzdExvYWRlZDtcbiAgICAgICAgc2xpZGluZyA9IHRoaXMuYWxpZ25QbGF5bGlzdHMobmV3RGV0YWlscywgdHJhY2suZGV0YWlscywgKF90aGlzJGxldmVsTGFzdExvYWRlZCA9IHRoaXMubGV2ZWxMYXN0TG9hZGVkKSA9PSBudWxsID8gdm9pZCAwIDogX3RoaXMkbGV2ZWxMYXN0TG9hZGVkLmRldGFpbHMpO1xuICAgICAgICBpZiAoc2xpZGluZyA9PT0gMCAmJiBtYWluU2xpZGluZ1N0YXJ0RnJhZ21lbnQpIHtcbiAgICAgICAgICAvLyByZWFsaWduIHdpdGggbWFpbiB3aGVuIHRoZXJlIGlzIG5vIG92ZXJsYXAgd2l0aCBsYXN0IHJlZnJlc2hcbiAgICAgICAgICBzbGlkaW5nID0gbWFpblNsaWRpbmdTdGFydEZyYWdtZW50LnN0YXJ0O1xuICAgICAgICAgIGFkZFNsaWRpbmcobmV3RGV0YWlscywgc2xpZGluZyk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgdHJhY2suZGV0YWlscyA9IG5ld0RldGFpbHM7XG4gICAgdGhpcy5sZXZlbExhc3RMb2FkZWQgPSB0cmFjaztcbiAgICBpZiAodHJhY2tJZCAhPT0gY3VycmVudFRyYWNrSWQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCF0aGlzLnN0YXJ0RnJhZ1JlcXVlc3RlZCAmJiAodGhpcy5tYWluRGV0YWlscyB8fCAhbmV3RGV0YWlscy5saXZlKSkge1xuICAgICAgdGhpcy5zZXRTdGFydFBvc2l0aW9uKHRoaXMubWFpbkRldGFpbHMgfHwgbmV3RGV0YWlscywgc2xpZGluZyk7XG4gICAgfVxuXG4gICAgLy8gdHJpZ2dlciBoYW5kbGVyIHJpZ2h0IG5vd1xuICAgIHRoaXMudGljaygpO1xuXG4gICAgLy8gSWYgcGxheWxpc3QgaXMgbWlzYWxpZ25lZCBiZWNhdXNlIG9mIGJhZCBQRFQgb3IgZHJpZnQsIGRlbGV0ZSBkZXRhaWxzIHRvIHJlc3luYyB3aXRoIG1haW4gb24gcmVsb2FkXG4gICAgaWYgKG5ld0RldGFpbHMubGl2ZSAmJiAhdGhpcy5mcmFnQ3VycmVudCAmJiB0aGlzLm1lZGlhICYmIHRoaXMuc3RhdGUgPT09IFN0YXRlLklETEUpIHtcbiAgICAgIGNvbnN0IGZvdW5kRnJhZyA9IGZpbmRGcmFnbWVudEJ5UFRTKG51bGwsIG5ld0RldGFpbHMuZnJhZ21lbnRzLCB0aGlzLm1lZGlhLmN1cnJlbnRUaW1lLCAwKTtcbiAgICAgIGlmICghZm91bmRGcmFnKSB7XG4gICAgICAgIHRoaXMud2FybignU3VidGl0bGUgcGxheWxpc3Qgbm90IGFsaWduZWQgd2l0aCBwbGF5YmFjaycpO1xuICAgICAgICB0cmFjay5kZXRhaWxzID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBfaGFuZGxlRnJhZ21lbnRMb2FkQ29tcGxldGUoZnJhZ0xvYWRlZERhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAgcGF5bG9hZFxuICAgIH0gPSBmcmFnTG9hZGVkRGF0YTtcbiAgICBjb25zdCBkZWNyeXB0RGF0YSA9IGZyYWcuZGVjcnlwdGRhdGE7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgaWYgKHRoaXMuZnJhZ0NvbnRleHRDaGFuZ2VkKGZyYWcpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIGNoZWNrIHRvIHNlZSBpZiB0aGUgcGF5bG9hZCBuZWVkcyB0byBiZSBkZWNyeXB0ZWRcbiAgICBpZiAocGF5bG9hZCAmJiBwYXlsb2FkLmJ5dGVMZW5ndGggPiAwICYmIGRlY3J5cHREYXRhICE9IG51bGwgJiYgZGVjcnlwdERhdGEua2V5ICYmIGRlY3J5cHREYXRhLml2ICYmIGRlY3J5cHREYXRhLm1ldGhvZCA9PT0gJ0FFUy0xMjgnKSB7XG4gICAgICBjb25zdCBzdGFydFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgIC8vIGRlY3J5cHQgdGhlIHN1YnRpdGxlc1xuICAgICAgdGhpcy5kZWNyeXB0ZXIuZGVjcnlwdChuZXcgVWludDhBcnJheShwYXlsb2FkKSwgZGVjcnlwdERhdGEua2V5LmJ1ZmZlciwgZGVjcnlwdERhdGEuaXYuYnVmZmVyKS5jYXRjaChlcnIgPT4ge1xuICAgICAgICBobHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk1FRElBX0VSUk9SLFxuICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5GUkFHX0RFQ1JZUFRfRVJST1IsXG4gICAgICAgICAgZmF0YWw6IGZhbHNlLFxuICAgICAgICAgIGVycm9yOiBlcnIsXG4gICAgICAgICAgcmVhc29uOiBlcnIubWVzc2FnZSxcbiAgICAgICAgICBmcmFnXG4gICAgICAgIH0pO1xuICAgICAgICB0aHJvdyBlcnI7XG4gICAgICB9KS50aGVuKGRlY3J5cHRlZERhdGEgPT4ge1xuICAgICAgICBjb25zdCBlbmRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5GUkFHX0RFQ1JZUFRFRCwge1xuICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgcGF5bG9hZDogZGVjcnlwdGVkRGF0YSxcbiAgICAgICAgICBzdGF0czoge1xuICAgICAgICAgICAgdHN0YXJ0OiBzdGFydFRpbWUsXG4gICAgICAgICAgICB0ZGVjcnlwdDogZW5kVGltZVxuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9KS5jYXRjaChlcnIgPT4ge1xuICAgICAgICB0aGlzLndhcm4oYCR7ZXJyLm5hbWV9OiAke2Vyci5tZXNzYWdlfWApO1xuICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICBkb1RpY2soKSB7XG4gICAgaWYgKCF0aGlzLm1lZGlhKSB7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHRoaXMuc3RhdGUgPT09IFN0YXRlLklETEUpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgY3VycmVudFRyYWNrSWQsXG4gICAgICAgIGxldmVsc1xuICAgICAgfSA9IHRoaXM7XG4gICAgICBjb25zdCB0cmFjayA9IGxldmVscyA9PSBudWxsID8gdm9pZCAwIDogbGV2ZWxzW2N1cnJlbnRUcmFja0lkXTtcbiAgICAgIGlmICghdHJhY2sgfHwgIWxldmVscy5sZW5ndGggfHwgIXRyYWNrLmRldGFpbHMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3Qge1xuICAgICAgICBjb25maWdcbiAgICAgIH0gPSB0aGlzO1xuICAgICAgY29uc3QgY3VycmVudFRpbWUgPSB0aGlzLmdldExvYWRQb3NpdGlvbigpO1xuICAgICAgY29uc3QgYnVmZmVyZWRJbmZvID0gQnVmZmVySGVscGVyLmJ1ZmZlcmVkSW5mbyh0aGlzLnRyYWNrc0J1ZmZlcmVkW3RoaXMuY3VycmVudFRyYWNrSWRdIHx8IFtdLCBjdXJyZW50VGltZSwgY29uZmlnLm1heEJ1ZmZlckhvbGUpO1xuICAgICAgY29uc3Qge1xuICAgICAgICBlbmQ6IHRhcmdldEJ1ZmZlclRpbWUsXG4gICAgICAgIGxlbjogYnVmZmVyTGVuXG4gICAgICB9ID0gYnVmZmVyZWRJbmZvO1xuICAgICAgY29uc3QgbWFpbkJ1ZmZlckluZm8gPSB0aGlzLmdldEZ3ZEJ1ZmZlckluZm8odGhpcy5tZWRpYSwgUGxheWxpc3RMZXZlbFR5cGUuTUFJTik7XG4gICAgICBjb25zdCB0cmFja0RldGFpbHMgPSB0cmFjay5kZXRhaWxzO1xuICAgICAgY29uc3QgbWF4QnVmTGVuID0gdGhpcy5nZXRNYXhCdWZmZXJMZW5ndGgobWFpbkJ1ZmZlckluZm8gPT0gbnVsbCA/IHZvaWQgMCA6IG1haW5CdWZmZXJJbmZvLmxlbikgKyB0cmFja0RldGFpbHMubGV2ZWxUYXJnZXREdXJhdGlvbjtcbiAgICAgIGlmIChidWZmZXJMZW4gPiBtYXhCdWZMZW4pIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3QgZnJhZ21lbnRzID0gdHJhY2tEZXRhaWxzLmZyYWdtZW50cztcbiAgICAgIGNvbnN0IGZyYWdMZW4gPSBmcmFnbWVudHMubGVuZ3RoO1xuICAgICAgY29uc3QgZW5kID0gdHJhY2tEZXRhaWxzLmVkZ2U7XG4gICAgICBsZXQgZm91bmRGcmFnID0gbnVsbDtcbiAgICAgIGNvbnN0IGZyYWdQcmV2aW91cyA9IHRoaXMuZnJhZ1ByZXZpb3VzO1xuICAgICAgaWYgKHRhcmdldEJ1ZmZlclRpbWUgPCBlbmQpIHtcbiAgICAgICAgY29uc3QgdG9sZXJhbmNlID0gY29uZmlnLm1heEZyYWdMb29rVXBUb2xlcmFuY2U7XG4gICAgICAgIGNvbnN0IGxvb2t1cFRvbGVyYW5jZSA9IHRhcmdldEJ1ZmZlclRpbWUgPiBlbmQgLSB0b2xlcmFuY2UgPyAwIDogdG9sZXJhbmNlO1xuICAgICAgICBmb3VuZEZyYWcgPSBmaW5kRnJhZ21lbnRCeVBUUyhmcmFnUHJldmlvdXMsIGZyYWdtZW50cywgTWF0aC5tYXgoZnJhZ21lbnRzWzBdLnN0YXJ0LCB0YXJnZXRCdWZmZXJUaW1lKSwgbG9va3VwVG9sZXJhbmNlKTtcbiAgICAgICAgaWYgKCFmb3VuZEZyYWcgJiYgZnJhZ1ByZXZpb3VzICYmIGZyYWdQcmV2aW91cy5zdGFydCA8IGZyYWdtZW50c1swXS5zdGFydCkge1xuICAgICAgICAgIGZvdW5kRnJhZyA9IGZyYWdtZW50c1swXTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZm91bmRGcmFnID0gZnJhZ21lbnRzW2ZyYWdMZW4gLSAxXTtcbiAgICAgIH1cbiAgICAgIGlmICghZm91bmRGcmFnKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGZvdW5kRnJhZyA9IHRoaXMubWFwVG9Jbml0RnJhZ1doZW5SZXF1aXJlZChmb3VuZEZyYWcpO1xuICAgICAgaWYgKGZvdW5kRnJhZy5zbiAhPT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgICAvLyBMb2FkIGVhcmxpZXIgZnJhZ21lbnQgaW4gc2FtZSBkaXNjb250aW51aXR5IHRvIG1ha2UgdXAgZm9yIG1pc2FsaWduZWQgcGxheWxpc3RzIGFuZCBjdWVzIHRoYXQgZXh0ZW5kIGJleW9uZCBlbmQgb2Ygc2VnbWVudFxuICAgICAgICBjb25zdCBjdXJTTklkeCA9IGZvdW5kRnJhZy5zbiAtIHRyYWNrRGV0YWlscy5zdGFydFNOO1xuICAgICAgICBjb25zdCBwcmV2RnJhZyA9IGZyYWdtZW50c1tjdXJTTklkeCAtIDFdO1xuICAgICAgICBpZiAocHJldkZyYWcgJiYgcHJldkZyYWcuY2MgPT09IGZvdW5kRnJhZy5jYyAmJiB0aGlzLmZyYWdtZW50VHJhY2tlci5nZXRTdGF0ZShwcmV2RnJhZykgPT09IEZyYWdtZW50U3RhdGUuTk9UX0xPQURFRCkge1xuICAgICAgICAgIGZvdW5kRnJhZyA9IHByZXZGcmFnO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5mcmFnbWVudFRyYWNrZXIuZ2V0U3RhdGUoZm91bmRGcmFnKSA9PT0gRnJhZ21lbnRTdGF0ZS5OT1RfTE9BREVEKSB7XG4gICAgICAgIC8vIG9ubHkgbG9hZCBpZiBmcmFnbWVudCBpcyBub3QgbG9hZGVkXG4gICAgICAgIHRoaXMubG9hZEZyYWdtZW50KGZvdW5kRnJhZywgdHJhY2ssIHRhcmdldEJ1ZmZlclRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBnZXRNYXhCdWZmZXJMZW5ndGgobWFpbkJ1ZmZlckxlbmd0aCkge1xuICAgIGNvbnN0IG1heENvbmZpZ0J1ZmZlciA9IHN1cGVyLmdldE1heEJ1ZmZlckxlbmd0aCgpO1xuICAgIGlmICghbWFpbkJ1ZmZlckxlbmd0aCkge1xuICAgICAgcmV0dXJuIG1heENvbmZpZ0J1ZmZlcjtcbiAgICB9XG4gICAgcmV0dXJuIE1hdGgubWF4KG1heENvbmZpZ0J1ZmZlciwgbWFpbkJ1ZmZlckxlbmd0aCk7XG4gIH1cbiAgbG9hZEZyYWdtZW50KGZyYWcsIGxldmVsLCB0YXJnZXRCdWZmZXJUaW1lKSB7XG4gICAgdGhpcy5mcmFnQ3VycmVudCA9IGZyYWc7XG4gICAgaWYgKGZyYWcuc24gPT09ICdpbml0U2VnbWVudCcpIHtcbiAgICAgIHRoaXMuX2xvYWRJbml0U2VnbWVudChmcmFnLCBsZXZlbCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkID0gdHJ1ZTtcbiAgICAgIHN1cGVyLmxvYWRGcmFnbWVudChmcmFnLCBsZXZlbCwgdGFyZ2V0QnVmZmVyVGltZSk7XG4gICAgfVxuICB9XG4gIGdldCBtZWRpYUJ1ZmZlclRpbWVSYW5nZXMoKSB7XG4gICAgcmV0dXJuIG5ldyBCdWZmZXJhYmxlSW5zdGFuY2UodGhpcy50cmFja3NCdWZmZXJlZFt0aGlzLmN1cnJlbnRUcmFja0lkXSB8fCBbXSk7XG4gIH1cbn1cbmNsYXNzIEJ1ZmZlcmFibGVJbnN0YW5jZSB7XG4gIGNvbnN0cnVjdG9yKHRpbWVyYW5nZXMpIHtcbiAgICB0aGlzLmJ1ZmZlcmVkID0gdm9pZCAwO1xuICAgIGNvbnN0IGdldFJhbmdlID0gKG5hbWUsIGluZGV4LCBsZW5ndGgpID0+IHtcbiAgICAgIGluZGV4ID0gaW5kZXggPj4+IDA7XG4gICAgICBpZiAoaW5kZXggPiBsZW5ndGggLSAxKSB7XG4gICAgICAgIHRocm93IG5ldyBET01FeGNlcHRpb24oYEZhaWxlZCB0byBleGVjdXRlICcke25hbWV9JyBvbiAnVGltZVJhbmdlcyc6IFRoZSBpbmRleCBwcm92aWRlZCAoJHtpbmRleH0pIGlzIGdyZWF0ZXIgdGhhbiB0aGUgbWF4aW11bSBib3VuZCAoJHtsZW5ndGh9KWApO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRpbWVyYW5nZXNbaW5kZXhdW25hbWVdO1xuICAgIH07XG4gICAgdGhpcy5idWZmZXJlZCA9IHtcbiAgICAgIGdldCBsZW5ndGgoKSB7XG4gICAgICAgIHJldHVybiB0aW1lcmFuZ2VzLmxlbmd0aDtcbiAgICAgIH0sXG4gICAgICBlbmQoaW5kZXgpIHtcbiAgICAgICAgcmV0dXJuIGdldFJhbmdlKCdlbmQnLCBpbmRleCwgdGltZXJhbmdlcy5sZW5ndGgpO1xuICAgICAgfSxcbiAgICAgIHN0YXJ0KGluZGV4KSB7XG4gICAgICAgIHJldHVybiBnZXRSYW5nZSgnc3RhcnQnLCBpbmRleCwgdGltZXJhbmdlcy5sZW5ndGgpO1xuICAgICAgfVxuICAgIH07XG4gIH1cbn1cblxuY2xhc3MgU3VidGl0bGVUcmFja0NvbnRyb2xsZXIgZXh0ZW5kcyBCYXNlUGxheWxpc3RDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgc3VwZXIoaGxzLCAnW3N1YnRpdGxlLXRyYWNrLWNvbnRyb2xsZXJdJyk7XG4gICAgdGhpcy5tZWRpYSA9IG51bGw7XG4gICAgdGhpcy50cmFja3MgPSBbXTtcbiAgICB0aGlzLmdyb3VwSWRzID0gbnVsbDtcbiAgICB0aGlzLnRyYWNrc0luR3JvdXAgPSBbXTtcbiAgICB0aGlzLnRyYWNrSWQgPSAtMTtcbiAgICB0aGlzLmN1cnJlbnRUcmFjayA9IG51bGw7XG4gICAgdGhpcy5zZWxlY3REZWZhdWx0VHJhY2sgPSB0cnVlO1xuICAgIHRoaXMucXVldWVkRGVmYXVsdFRyYWNrID0gLTE7XG4gICAgdGhpcy5hc3luY1BvbGxUcmFja0NoYW5nZSA9ICgpID0+IHRoaXMucG9sbFRyYWNrQ2hhbmdlKDApO1xuICAgIHRoaXMudXNlVGV4dFRyYWNrUG9sbGluZyA9IGZhbHNlO1xuICAgIHRoaXMuc3VidGl0bGVQb2xsaW5nSW50ZXJ2YWwgPSAtMTtcbiAgICB0aGlzLl9zdWJ0aXRsZURpc3BsYXkgPSB0cnVlO1xuICAgIHRoaXMub25UZXh0VHJhY2tzQ2hhbmdlZCA9ICgpID0+IHtcbiAgICAgIGlmICghdGhpcy51c2VUZXh0VHJhY2tQb2xsaW5nKSB7XG4gICAgICAgIHNlbGYuY2xlYXJJbnRlcnZhbCh0aGlzLnN1YnRpdGxlUG9sbGluZ0ludGVydmFsKTtcbiAgICAgIH1cbiAgICAgIC8vIE1lZGlhIGlzIHVuZGVmaW5lZCB3aGVuIHN3aXRjaGluZyBzdHJlYW1zIHZpYSBsb2FkU291cmNlKClcbiAgICAgIGlmICghdGhpcy5tZWRpYSB8fCAhdGhpcy5obHMuY29uZmlnLnJlbmRlclRleHRUcmFja3NOYXRpdmVseSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBsZXQgdGV4dFRyYWNrID0gbnVsbDtcbiAgICAgIGNvbnN0IHRyYWNrcyA9IGZpbHRlclN1YnRpdGxlVHJhY2tzKHRoaXMubWVkaWEudGV4dFRyYWNrcyk7XG4gICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgICAgICBpZiAodHJhY2tzW2ldLm1vZGUgPT09ICdoaWRkZW4nKSB7XG4gICAgICAgICAgLy8gRG8gbm90IGJyZWFrIGluIGNhc2UgdGhlcmUgaXMgYSBmb2xsb3dpbmcgdHJhY2sgd2l0aCBzaG93aW5nLlxuICAgICAgICAgIHRleHRUcmFjayA9IHRyYWNrc1tpXTtcbiAgICAgICAgfSBlbHNlIGlmICh0cmFja3NbaV0ubW9kZSA9PT0gJ3Nob3dpbmcnKSB7XG4gICAgICAgICAgdGV4dFRyYWNrID0gdHJhY2tzW2ldO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIEZpbmQgaW50ZXJuYWwgdHJhY2sgaW5kZXggZm9yIFRleHRUcmFja1xuICAgICAgY29uc3QgdHJhY2tJZCA9IHRoaXMuZmluZFRyYWNrRm9yVGV4dFRyYWNrKHRleHRUcmFjayk7XG4gICAgICBpZiAodGhpcy5zdWJ0aXRsZVRyYWNrICE9PSB0cmFja0lkKSB7XG4gICAgICAgIHRoaXMuc2V0U3VidGl0bGVUcmFjayh0cmFja0lkKTtcbiAgICAgIH1cbiAgICB9O1xuICAgIHRoaXMucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMudW5yZWdpc3Rlckxpc3RlbmVycygpO1xuICAgIHRoaXMudHJhY2tzLmxlbmd0aCA9IDA7XG4gICAgdGhpcy50cmFja3NJbkdyb3VwLmxlbmd0aCA9IDA7XG4gICAgdGhpcy5jdXJyZW50VHJhY2sgPSBudWxsO1xuICAgIHRoaXMub25UZXh0VHJhY2tzQ2hhbmdlZCA9IHRoaXMuYXN5bmNQb2xsVHJhY2tDaGFuZ2UgPSBudWxsO1xuICAgIHN1cGVyLmRlc3Ryb3koKTtcbiAgfVxuICBnZXQgc3VidGl0bGVEaXNwbGF5KCkge1xuICAgIHJldHVybiB0aGlzLl9zdWJ0aXRsZURpc3BsYXk7XG4gIH1cbiAgc2V0IHN1YnRpdGxlRGlzcGxheSh2YWx1ZSkge1xuICAgIHRoaXMuX3N1YnRpdGxlRGlzcGxheSA9IHZhbHVlO1xuICAgIGlmICh0aGlzLnRyYWNrSWQgPiAtMSkge1xuICAgICAgdGhpcy50b2dnbGVUcmFja01vZGVzKCk7XG4gICAgfVxuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1FRElBX0RFVEFDSElORywgdGhpcy5vbk1lZGlhRGV0YWNoaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTEVWRUxfTE9BRElORywgdGhpcy5vbkxldmVsTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTF9TV0lUQ0hJTkcsIHRoaXMub25MZXZlbFN3aXRjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5TVUJUSVRMRV9UUkFDS19MT0FERUQsIHRoaXMub25TdWJ0aXRsZVRyYWNrTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICB9XG4gIHVucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX0xPQURJTkcsIHRoaXMub25MZXZlbExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX1NXSVRDSElORywgdGhpcy5vbkxldmVsU3dpdGNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5TVUJUSVRMRV9UUkFDS19MT0FERUQsIHRoaXMub25TdWJ0aXRsZVRyYWNrTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5FUlJPUiwgdGhpcy5vbkVycm9yLCB0aGlzKTtcbiAgfVxuXG4gIC8vIExpc3RlbiBmb3Igc3VidGl0bGUgdHJhY2sgY2hhbmdlLCB0aGVuIGV4dHJhY3QgdGhlIGN1cnJlbnQgdHJhY2sgSUQuXG4gIG9uTWVkaWFBdHRhY2hlZChldmVudCwgZGF0YSkge1xuICAgIHRoaXMubWVkaWEgPSBkYXRhLm1lZGlhO1xuICAgIGlmICghdGhpcy5tZWRpYSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAodGhpcy5xdWV1ZWREZWZhdWx0VHJhY2sgPiAtMSkge1xuICAgICAgdGhpcy5zdWJ0aXRsZVRyYWNrID0gdGhpcy5xdWV1ZWREZWZhdWx0VHJhY2s7XG4gICAgICB0aGlzLnF1ZXVlZERlZmF1bHRUcmFjayA9IC0xO1xuICAgIH1cbiAgICB0aGlzLnVzZVRleHRUcmFja1BvbGxpbmcgPSAhKHRoaXMubWVkaWEudGV4dFRyYWNrcyAmJiAnb25jaGFuZ2UnIGluIHRoaXMubWVkaWEudGV4dFRyYWNrcyk7XG4gICAgaWYgKHRoaXMudXNlVGV4dFRyYWNrUG9sbGluZykge1xuICAgICAgdGhpcy5wb2xsVHJhY2tDaGFuZ2UoNTAwKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5tZWRpYS50ZXh0VHJhY2tzLmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsIHRoaXMuYXN5bmNQb2xsVHJhY2tDaGFuZ2UpO1xuICAgIH1cbiAgfVxuICBwb2xsVHJhY2tDaGFuZ2UodGltZW91dCkge1xuICAgIHNlbGYuY2xlYXJJbnRlcnZhbCh0aGlzLnN1YnRpdGxlUG9sbGluZ0ludGVydmFsKTtcbiAgICB0aGlzLnN1YnRpdGxlUG9sbGluZ0ludGVydmFsID0gc2VsZi5zZXRJbnRlcnZhbCh0aGlzLm9uVGV4dFRyYWNrc0NoYW5nZWQsIHRpbWVvdXQpO1xuICB9XG4gIG9uTWVkaWFEZXRhY2hpbmcoKSB7XG4gICAgaWYgKCF0aGlzLm1lZGlhKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHNlbGYuY2xlYXJJbnRlcnZhbCh0aGlzLnN1YnRpdGxlUG9sbGluZ0ludGVydmFsKTtcbiAgICBpZiAoIXRoaXMudXNlVGV4dFRyYWNrUG9sbGluZykge1xuICAgICAgdGhpcy5tZWRpYS50ZXh0VHJhY2tzLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsIHRoaXMuYXN5bmNQb2xsVHJhY2tDaGFuZ2UpO1xuICAgIH1cbiAgICBpZiAodGhpcy50cmFja0lkID4gLTEpIHtcbiAgICAgIHRoaXMucXVldWVkRGVmYXVsdFRyYWNrID0gdGhpcy50cmFja0lkO1xuICAgIH1cbiAgICBjb25zdCB0ZXh0VHJhY2tzID0gZmlsdGVyU3VidGl0bGVUcmFja3ModGhpcy5tZWRpYS50ZXh0VHJhY2tzKTtcbiAgICAvLyBDbGVhciBsb2FkZWQgY3VlcyBvbiBtZWRpYSBkZXRhY2htZW50IGZyb20gdHJhY2tzXG4gICAgdGV4dFRyYWNrcy5mb3JFYWNoKHRyYWNrID0+IHtcbiAgICAgIGNsZWFyQ3VycmVudEN1ZXModHJhY2spO1xuICAgIH0pO1xuICAgIC8vIERpc2FibGUgYWxsIHN1YnRpdGxlIHRyYWNrcyBiZWZvcmUgZGV0YWNobWVudCBzbyB3aGVuIHJlYXR0YWNoZWQgb25seSB0cmFja3MgaW4gdGhhdCBjb250ZW50IGFyZSBlbmFibGVkLlxuICAgIHRoaXMuc3VidGl0bGVUcmFjayA9IC0xO1xuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKCkge1xuICAgIHRoaXMudHJhY2tzID0gW107XG4gICAgdGhpcy5ncm91cElkcyA9IG51bGw7XG4gICAgdGhpcy50cmFja3NJbkdyb3VwID0gW107XG4gICAgdGhpcy50cmFja0lkID0gLTE7XG4gICAgdGhpcy5jdXJyZW50VHJhY2sgPSBudWxsO1xuICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gdHJ1ZTtcbiAgfVxuXG4gIC8vIEZpcmVkIHdoZW5ldmVyIGEgbmV3IG1hbmlmZXN0IGlzIGxvYWRlZC5cbiAgb25NYW5pZmVzdFBhcnNlZChldmVudCwgZGF0YSkge1xuICAgIHRoaXMudHJhY2tzID0gZGF0YS5zdWJ0aXRsZVRyYWNrcztcbiAgfVxuICBvblN1YnRpdGxlVHJhY2tMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBpZCxcbiAgICAgIGdyb3VwSWQsXG4gICAgICBkZXRhaWxzXG4gICAgfSA9IGRhdGE7XG4gICAgY29uc3QgdHJhY2tJbkFjdGl2ZUdyb3VwID0gdGhpcy50cmFja3NJbkdyb3VwW2lkXTtcbiAgICBpZiAoIXRyYWNrSW5BY3RpdmVHcm91cCB8fCB0cmFja0luQWN0aXZlR3JvdXAuZ3JvdXBJZCAhPT0gZ3JvdXBJZCkge1xuICAgICAgdGhpcy53YXJuKGBTdWJ0aXRsZSB0cmFjayB3aXRoIGlkOiR7aWR9IGFuZCBncm91cDoke2dyb3VwSWR9IG5vdCBmb3VuZCBpbiBhY3RpdmUgZ3JvdXAgJHt0cmFja0luQWN0aXZlR3JvdXAgPT0gbnVsbCA/IHZvaWQgMCA6IHRyYWNrSW5BY3RpdmVHcm91cC5ncm91cElkfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBjdXJEZXRhaWxzID0gdHJhY2tJbkFjdGl2ZUdyb3VwLmRldGFpbHM7XG4gICAgdHJhY2tJbkFjdGl2ZUdyb3VwLmRldGFpbHMgPSBkYXRhLmRldGFpbHM7XG4gICAgdGhpcy5sb2coYFN1YnRpdGxlIHRyYWNrICR7aWR9IFwiJHt0cmFja0luQWN0aXZlR3JvdXAubmFtZX1cIiBsYW5nOiR7dHJhY2tJbkFjdGl2ZUdyb3VwLmxhbmd9IGdyb3VwOiR7Z3JvdXBJZH0gbG9hZGVkIFske2RldGFpbHMuc3RhcnRTTn0tJHtkZXRhaWxzLmVuZFNOfV1gKTtcbiAgICBpZiAoaWQgPT09IHRoaXMudHJhY2tJZCkge1xuICAgICAgdGhpcy5wbGF5bGlzdExvYWRlZChpZCwgZGF0YSwgY3VyRGV0YWlscyk7XG4gICAgfVxuICB9XG4gIG9uTGV2ZWxMb2FkaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5zd2l0Y2hMZXZlbChkYXRhLmxldmVsKTtcbiAgfVxuICBvbkxldmVsU3dpdGNoaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5zd2l0Y2hMZXZlbChkYXRhLmxldmVsKTtcbiAgfVxuICBzd2l0Y2hMZXZlbChsZXZlbEluZGV4KSB7XG4gICAgY29uc3QgbGV2ZWxJbmZvID0gdGhpcy5obHMubGV2ZWxzW2xldmVsSW5kZXhdO1xuICAgIGlmICghbGV2ZWxJbmZvKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHN1YnRpdGxlR3JvdXBzID0gbGV2ZWxJbmZvLnN1YnRpdGxlR3JvdXBzIHx8IG51bGw7XG4gICAgY29uc3QgY3VycmVudEdyb3VwcyA9IHRoaXMuZ3JvdXBJZHM7XG4gICAgbGV0IGN1cnJlbnRUcmFjayA9IHRoaXMuY3VycmVudFRyYWNrO1xuICAgIGlmICghc3VidGl0bGVHcm91cHMgfHwgKGN1cnJlbnRHcm91cHMgPT0gbnVsbCA/IHZvaWQgMCA6IGN1cnJlbnRHcm91cHMubGVuZ3RoKSAhPT0gKHN1YnRpdGxlR3JvdXBzID09IG51bGwgPyB2b2lkIDAgOiBzdWJ0aXRsZUdyb3Vwcy5sZW5ndGgpIHx8IHN1YnRpdGxlR3JvdXBzICE9IG51bGwgJiYgc3VidGl0bGVHcm91cHMuc29tZShncm91cElkID0+IChjdXJyZW50R3JvdXBzID09IG51bGwgPyB2b2lkIDAgOiBjdXJyZW50R3JvdXBzLmluZGV4T2YoZ3JvdXBJZCkpID09PSAtMSkpIHtcbiAgICAgIHRoaXMuZ3JvdXBJZHMgPSBzdWJ0aXRsZUdyb3VwcztcbiAgICAgIHRoaXMudHJhY2tJZCA9IC0xO1xuICAgICAgdGhpcy5jdXJyZW50VHJhY2sgPSBudWxsO1xuICAgICAgY29uc3Qgc3VidGl0bGVUcmFja3MgPSB0aGlzLnRyYWNrcy5maWx0ZXIodHJhY2sgPT4gIXN1YnRpdGxlR3JvdXBzIHx8IHN1YnRpdGxlR3JvdXBzLmluZGV4T2YodHJhY2suZ3JvdXBJZCkgIT09IC0xKTtcbiAgICAgIGlmIChzdWJ0aXRsZVRyYWNrcy5sZW5ndGgpIHtcbiAgICAgICAgLy8gRGlzYWJsZSBzZWxlY3REZWZhdWx0VHJhY2sgaWYgdGhlcmUgYXJlIG5vIGRlZmF1bHQgdHJhY2tzXG4gICAgICAgIGlmICh0aGlzLnNlbGVjdERlZmF1bHRUcmFjayAmJiAhc3VidGl0bGVUcmFja3Muc29tZSh0cmFjayA9PiB0cmFjay5kZWZhdWx0KSkge1xuICAgICAgICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgLy8gdHJhY2suaWQgc2hvdWxkIG1hdGNoIGhscy5hdWRpb1RyYWNrcyBpbmRleFxuICAgICAgICBzdWJ0aXRsZVRyYWNrcy5mb3JFYWNoKCh0cmFjaywgaSkgPT4ge1xuICAgICAgICAgIHRyYWNrLmlkID0gaTtcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2UgaWYgKCFjdXJyZW50VHJhY2sgJiYgIXRoaXMudHJhY2tzSW5Hcm91cC5sZW5ndGgpIHtcbiAgICAgICAgLy8gRG8gbm90IGRpc3BhdGNoIFNVQlRJVExFX1RSQUNLU19VUERBVEVEIHdoZW4gdGhlcmUgd2VyZSBhbmQgYXJlIG5vIHRyYWNrc1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICB0aGlzLnRyYWNrc0luR3JvdXAgPSBzdWJ0aXRsZVRyYWNrcztcblxuICAgICAgLy8gRmluZCBwcmVmZXJyZWQgdHJhY2tcbiAgICAgIGNvbnN0IHN1YnRpdGxlUHJlZmVyZW5jZSA9IHRoaXMuaGxzLmNvbmZpZy5zdWJ0aXRsZVByZWZlcmVuY2U7XG4gICAgICBpZiAoIWN1cnJlbnRUcmFjayAmJiBzdWJ0aXRsZVByZWZlcmVuY2UpIHtcbiAgICAgICAgdGhpcy5zZWxlY3REZWZhdWx0VHJhY2sgPSBmYWxzZTtcbiAgICAgICAgY29uc3QgZ3JvdXBJbmRleCA9IGZpbmRNYXRjaGluZ09wdGlvbihzdWJ0aXRsZVByZWZlcmVuY2UsIHN1YnRpdGxlVHJhY2tzKTtcbiAgICAgICAgaWYgKGdyb3VwSW5kZXggPiAtMSkge1xuICAgICAgICAgIGN1cnJlbnRUcmFjayA9IHN1YnRpdGxlVHJhY2tzW2dyb3VwSW5kZXhdO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnN0IGFsbEluZGV4ID0gZmluZE1hdGNoaW5nT3B0aW9uKHN1YnRpdGxlUHJlZmVyZW5jZSwgdGhpcy50cmFja3MpO1xuICAgICAgICAgIGN1cnJlbnRUcmFjayA9IHRoaXMudHJhY2tzW2FsbEluZGV4XTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBTZWxlY3QgaW5pdGlhbCB0cmFja1xuICAgICAgbGV0IHRyYWNrSWQgPSB0aGlzLmZpbmRUcmFja0lkKGN1cnJlbnRUcmFjayk7XG4gICAgICBpZiAodHJhY2tJZCA9PT0gLTEgJiYgY3VycmVudFRyYWNrKSB7XG4gICAgICAgIHRyYWNrSWQgPSB0aGlzLmZpbmRUcmFja0lkKG51bGwpO1xuICAgICAgfVxuXG4gICAgICAvLyBEaXNwYXRjaCBldmVudHMgYW5kIGxvYWQgdHJhY2sgaWYgbmVlZGVkXG4gICAgICBjb25zdCBzdWJ0aXRsZVRyYWNrc1VwZGF0ZWQgPSB7XG4gICAgICAgIHN1YnRpdGxlVHJhY2tzXG4gICAgICB9O1xuICAgICAgdGhpcy5sb2coYFVwZGF0aW5nIHN1YnRpdGxlIHRyYWNrcywgJHtzdWJ0aXRsZVRyYWNrcy5sZW5ndGh9IHRyYWNrKHMpIGZvdW5kIGluIFwiJHtzdWJ0aXRsZUdyb3VwcyA9PSBudWxsID8gdm9pZCAwIDogc3VidGl0bGVHcm91cHMuam9pbignLCcpfVwiIGdyb3VwLWlkYCk7XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5TVUJUSVRMRV9UUkFDS1NfVVBEQVRFRCwgc3VidGl0bGVUcmFja3NVcGRhdGVkKTtcbiAgICAgIGlmICh0cmFja0lkICE9PSAtMSAmJiB0aGlzLnRyYWNrSWQgPT09IC0xKSB7XG4gICAgICAgIHRoaXMuc2V0U3VidGl0bGVUcmFjayh0cmFja0lkKTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKHRoaXMuc2hvdWxkUmVsb2FkUGxheWxpc3QoY3VycmVudFRyYWNrKSkge1xuICAgICAgLy8gUmV0cnkgcGxheWxpc3QgbG9hZGluZyBpZiBubyBwbGF5bGlzdCBpcyBvciBoYXMgYmVlbiBsb2FkZWQgeWV0XG4gICAgICB0aGlzLnNldFN1YnRpdGxlVHJhY2sodGhpcy50cmFja0lkKTtcbiAgICB9XG4gIH1cbiAgZmluZFRyYWNrSWQoY3VycmVudFRyYWNrKSB7XG4gICAgY29uc3QgdHJhY2tzID0gdGhpcy50cmFja3NJbkdyb3VwO1xuICAgIGNvbnN0IHNlbGVjdERlZmF1bHQgPSB0aGlzLnNlbGVjdERlZmF1bHRUcmFjaztcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgICAgY29uc3QgdHJhY2sgPSB0cmFja3NbaV07XG4gICAgICBpZiAoc2VsZWN0RGVmYXVsdCAmJiAhdHJhY2suZGVmYXVsdCB8fCAhc2VsZWN0RGVmYXVsdCAmJiAhY3VycmVudFRyYWNrKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgaWYgKCFjdXJyZW50VHJhY2sgfHwgbWF0Y2hlc09wdGlvbih0cmFjaywgY3VycmVudFRyYWNrKSkge1xuICAgICAgICByZXR1cm4gaTtcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKGN1cnJlbnRUcmFjaykge1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0cmFja3MubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3QgdHJhY2sgPSB0cmFja3NbaV07XG4gICAgICAgIGlmIChtZWRpYUF0dHJpYnV0ZXNJZGVudGljYWwoY3VycmVudFRyYWNrLmF0dHJzLCB0cmFjay5hdHRycywgWydMQU5HVUFHRScsICdBU1NPQy1MQU5HVUFHRScsICdDSEFSQUNURVJJU1RJQ1MnXSkpIHtcbiAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0cmFja3MubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3QgdHJhY2sgPSB0cmFja3NbaV07XG4gICAgICAgIGlmIChtZWRpYUF0dHJpYnV0ZXNJZGVudGljYWwoY3VycmVudFRyYWNrLmF0dHJzLCB0cmFjay5hdHRycywgWydMQU5HVUFHRSddKSkge1xuICAgICAgICAgIHJldHVybiBpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiAtMTtcbiAgfVxuICBmaW5kVHJhY2tGb3JUZXh0VHJhY2sodGV4dFRyYWNrKSB7XG4gICAgaWYgKHRleHRUcmFjaykge1xuICAgICAgY29uc3QgdHJhY2tzID0gdGhpcy50cmFja3NJbkdyb3VwO1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0cmFja3MubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgY29uc3QgdHJhY2sgPSB0cmFja3NbaV07XG4gICAgICAgIGlmIChzdWJ0aXRsZVRyYWNrTWF0Y2hlc1RleHRUcmFjayh0cmFjaywgdGV4dFRyYWNrKSkge1xuICAgICAgICAgIHJldHVybiBpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiAtMTtcbiAgfVxuICBvbkVycm9yKGV2ZW50LCBkYXRhKSB7XG4gICAgaWYgKGRhdGEuZmF0YWwgfHwgIWRhdGEuY29udGV4dCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoZGF0YS5jb250ZXh0LnR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuU1VCVElUTEVfVFJBQ0sgJiYgZGF0YS5jb250ZXh0LmlkID09PSB0aGlzLnRyYWNrSWQgJiYgKCF0aGlzLmdyb3VwSWRzIHx8IHRoaXMuZ3JvdXBJZHMuaW5kZXhPZihkYXRhLmNvbnRleHQuZ3JvdXBJZCkgIT09IC0xKSkge1xuICAgICAgdGhpcy5jaGVja1JldHJ5KGRhdGEpO1xuICAgIH1cbiAgfVxuICBnZXQgYWxsU3VidGl0bGVUcmFja3MoKSB7XG4gICAgcmV0dXJuIHRoaXMudHJhY2tzO1xuICB9XG5cbiAgLyoqIGdldCBhbHRlcm5hdGUgc3VidGl0bGUgdHJhY2tzIGxpc3QgZnJvbSBwbGF5bGlzdCAqKi9cbiAgZ2V0IHN1YnRpdGxlVHJhY2tzKCkge1xuICAgIHJldHVybiB0aGlzLnRyYWNrc0luR3JvdXA7XG4gIH1cblxuICAvKiogZ2V0L3NldCBpbmRleCBvZiB0aGUgc2VsZWN0ZWQgc3VidGl0bGUgdHJhY2sgKGJhc2VkIG9uIGluZGV4IGluIHN1YnRpdGxlIHRyYWNrIGxpc3RzKSAqKi9cbiAgZ2V0IHN1YnRpdGxlVHJhY2soKSB7XG4gICAgcmV0dXJuIHRoaXMudHJhY2tJZDtcbiAgfVxuICBzZXQgc3VidGl0bGVUcmFjayhuZXdJZCkge1xuICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gZmFsc2U7XG4gICAgdGhpcy5zZXRTdWJ0aXRsZVRyYWNrKG5ld0lkKTtcbiAgfVxuICBzZXRTdWJ0aXRsZU9wdGlvbihzdWJ0aXRsZU9wdGlvbikge1xuICAgIHRoaXMuaGxzLmNvbmZpZy5zdWJ0aXRsZVByZWZlcmVuY2UgPSBzdWJ0aXRsZU9wdGlvbjtcbiAgICBpZiAoc3VidGl0bGVPcHRpb24pIHtcbiAgICAgIGNvbnN0IGFsbFN1YnRpdGxlVHJhY2tzID0gdGhpcy5hbGxTdWJ0aXRsZVRyYWNrcztcbiAgICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gZmFsc2U7XG4gICAgICBpZiAoYWxsU3VidGl0bGVUcmFja3MubGVuZ3RoKSB7XG4gICAgICAgIC8vIEZpcnN0IHNlZSBpZiBjdXJyZW50IG9wdGlvbiBtYXRjaGVzIChubyBzd2l0Y2ggb3ApXG4gICAgICAgIGNvbnN0IGN1cnJlbnRUcmFjayA9IHRoaXMuY3VycmVudFRyYWNrO1xuICAgICAgICBpZiAoY3VycmVudFRyYWNrICYmIG1hdGNoZXNPcHRpb24oc3VidGl0bGVPcHRpb24sIGN1cnJlbnRUcmFjaykpIHtcbiAgICAgICAgICByZXR1cm4gY3VycmVudFRyYWNrO1xuICAgICAgICB9XG4gICAgICAgIC8vIEZpbmQgb3B0aW9uIGluIGN1cnJlbnQgZ3JvdXBcbiAgICAgICAgY29uc3QgZ3JvdXBJbmRleCA9IGZpbmRNYXRjaGluZ09wdGlvbihzdWJ0aXRsZU9wdGlvbiwgdGhpcy50cmFja3NJbkdyb3VwKTtcbiAgICAgICAgaWYgKGdyb3VwSW5kZXggPiAtMSkge1xuICAgICAgICAgIGNvbnN0IHRyYWNrID0gdGhpcy50cmFja3NJbkdyb3VwW2dyb3VwSW5kZXhdO1xuICAgICAgICAgIHRoaXMuc2V0U3VidGl0bGVUcmFjayhncm91cEluZGV4KTtcbiAgICAgICAgICByZXR1cm4gdHJhY2s7XG4gICAgICAgIH0gZWxzZSBpZiAoY3VycmVudFRyYWNrKSB7XG4gICAgICAgICAgLy8gSWYgdGhpcyBpcyBub3QgdGhlIGluaXRpYWwgc2VsZWN0aW9uIHJldHVybiBudWxsXG4gICAgICAgICAgLy8gb3B0aW9uIHNob3VsZCBoYXZlIG1hdGNoZWQgb25lIGluIGFjdGl2ZSBncm91cFxuICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIC8vIEZpbmQgdGhlIG9wdGlvbiBpbiBhbGwgdHJhY2tzIGZvciBpbml0aWFsIHNlbGVjdGlvblxuICAgICAgICAgIGNvbnN0IGFsbEluZGV4ID0gZmluZE1hdGNoaW5nT3B0aW9uKHN1YnRpdGxlT3B0aW9uLCBhbGxTdWJ0aXRsZVRyYWNrcyk7XG4gICAgICAgICAgaWYgKGFsbEluZGV4ID4gLTEpIHtcbiAgICAgICAgICAgIHJldHVybiBhbGxTdWJ0aXRsZVRyYWNrc1thbGxJbmRleF07XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGxvYWRQbGF5bGlzdChobHNVcmxQYXJhbWV0ZXJzKSB7XG4gICAgc3VwZXIubG9hZFBsYXlsaXN0KCk7XG4gICAgY29uc3QgY3VycmVudFRyYWNrID0gdGhpcy5jdXJyZW50VHJhY2s7XG4gICAgaWYgKHRoaXMuc2hvdWxkTG9hZFBsYXlsaXN0KGN1cnJlbnRUcmFjaykgJiYgY3VycmVudFRyYWNrKSB7XG4gICAgICBjb25zdCBpZCA9IGN1cnJlbnRUcmFjay5pZDtcbiAgICAgIGNvbnN0IGdyb3VwSWQgPSBjdXJyZW50VHJhY2suZ3JvdXBJZDtcbiAgICAgIGxldCB1cmwgPSBjdXJyZW50VHJhY2sudXJsO1xuICAgICAgaWYgKGhsc1VybFBhcmFtZXRlcnMpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICB1cmwgPSBobHNVcmxQYXJhbWV0ZXJzLmFkZERpcmVjdGl2ZXModXJsKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICB0aGlzLndhcm4oYENvdWxkIG5vdCBjb25zdHJ1Y3QgbmV3IFVSTCB3aXRoIEhMUyBEZWxpdmVyeSBEaXJlY3RpdmVzOiAke2Vycm9yfWApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLmxvZyhgTG9hZGluZyBzdWJ0aXRsZSBwbGF5bGlzdCBmb3IgaWQgJHtpZH1gKTtcbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLlNVQlRJVExFX1RSQUNLX0xPQURJTkcsIHtcbiAgICAgICAgdXJsLFxuICAgICAgICBpZCxcbiAgICAgICAgZ3JvdXBJZCxcbiAgICAgICAgZGVsaXZlcnlEaXJlY3RpdmVzOiBobHNVcmxQYXJhbWV0ZXJzIHx8IG51bGxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEaXNhYmxlcyB0aGUgb2xkIHN1YnRpdGxlVHJhY2sgYW5kIHNldHMgY3VycmVudCBtb2RlIG9uIHRoZSBuZXh0IHN1YnRpdGxlVHJhY2suXG4gICAqIFRoaXMgb3BlcmF0ZXMgb24gdGhlIERPTSB0ZXh0VHJhY2tzLlxuICAgKiBBIHZhbHVlIG9mIC0xIHdpbGwgZGlzYWJsZSBhbGwgc3VidGl0bGUgdHJhY2tzLlxuICAgKi9cbiAgdG9nZ2xlVHJhY2tNb2RlcygpIHtcbiAgICBjb25zdCB7XG4gICAgICBtZWRpYVxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghbWVkaWEpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgdGV4dFRyYWNrcyA9IGZpbHRlclN1YnRpdGxlVHJhY2tzKG1lZGlhLnRleHRUcmFja3MpO1xuICAgIGNvbnN0IGN1cnJlbnRUcmFjayA9IHRoaXMuY3VycmVudFRyYWNrO1xuICAgIGxldCBuZXh0VHJhY2s7XG4gICAgaWYgKGN1cnJlbnRUcmFjaykge1xuICAgICAgbmV4dFRyYWNrID0gdGV4dFRyYWNrcy5maWx0ZXIodGV4dFRyYWNrID0+IHN1YnRpdGxlVHJhY2tNYXRjaGVzVGV4dFRyYWNrKGN1cnJlbnRUcmFjaywgdGV4dFRyYWNrKSlbMF07XG4gICAgICBpZiAoIW5leHRUcmFjaykge1xuICAgICAgICB0aGlzLndhcm4oYFVuYWJsZSB0byBmaW5kIHN1YnRpdGxlIFRleHRUcmFjayB3aXRoIG5hbWUgXCIke2N1cnJlbnRUcmFjay5uYW1lfVwiIGFuZCBsYW5ndWFnZSBcIiR7Y3VycmVudFRyYWNrLmxhbmd9XCJgKTtcbiAgICAgIH1cbiAgICB9XG4gICAgW10uc2xpY2UuY2FsbCh0ZXh0VHJhY2tzKS5mb3JFYWNoKHRyYWNrID0+IHtcbiAgICAgIGlmICh0cmFjay5tb2RlICE9PSAnZGlzYWJsZWQnICYmIHRyYWNrICE9PSBuZXh0VHJhY2spIHtcbiAgICAgICAgdHJhY2subW9kZSA9ICdkaXNhYmxlZCc7XG4gICAgICB9XG4gICAgfSk7XG4gICAgaWYgKG5leHRUcmFjaykge1xuICAgICAgY29uc3QgbW9kZSA9IHRoaXMuc3VidGl0bGVEaXNwbGF5ID8gJ3Nob3dpbmcnIDogJ2hpZGRlbic7XG4gICAgICBpZiAobmV4dFRyYWNrLm1vZGUgIT09IG1vZGUpIHtcbiAgICAgICAgbmV4dFRyYWNrLm1vZGUgPSBtb2RlO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBUaGlzIG1ldGhvZCBpcyByZXNwb25zaWJsZSBmb3IgdmFsaWRhdGluZyB0aGUgc3VidGl0bGUgaW5kZXggYW5kIHBlcmlvZGljYWxseSByZWxvYWRpbmcgaWYgbGl2ZS5cbiAgICogRGlzcGF0Y2hlcyB0aGUgU1VCVElUTEVfVFJBQ0tfU1dJVENIIGV2ZW50LCB3aGljaCBpbnN0cnVjdHMgdGhlIHN1YnRpdGxlLXN0cmVhbS1jb250cm9sbGVyIHRvIGxvYWQgdGhlIHNlbGVjdGVkIHRyYWNrLlxuICAgKi9cbiAgc2V0U3VidGl0bGVUcmFjayhuZXdJZCkge1xuICAgIGNvbnN0IHRyYWNrcyA9IHRoaXMudHJhY2tzSW5Hcm91cDtcblxuICAgIC8vIHNldHRpbmcgdGhpcy5zdWJ0aXRsZVRyYWNrIHdpbGwgdHJpZ2dlciBpbnRlcm5hbCBsb2dpY1xuICAgIC8vIGlmIG1lZGlhIGhhcyBub3QgYmVlbiBhdHRhY2hlZCB5ZXQsIGl0IHdpbGwgZmFpbFxuICAgIC8vIHdlIGtlZXAgYSByZWZlcmVuY2UgdG8gdGhlIGRlZmF1bHQgdHJhY2sgaWRcbiAgICAvLyBhbmQgd2UnbGwgc2V0IHN1YnRpdGxlVHJhY2sgd2hlbiBvbk1lZGlhQXR0YWNoZWQgaXMgdHJpZ2dlcmVkXG4gICAgaWYgKCF0aGlzLm1lZGlhKSB7XG4gICAgICB0aGlzLnF1ZXVlZERlZmF1bHRUcmFjayA9IG5ld0lkO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIGV4aXQgaWYgdHJhY2sgaWQgYXMgYWxyZWFkeSBzZXQgb3IgaW52YWxpZFxuICAgIGlmIChuZXdJZCA8IC0xIHx8IG5ld0lkID49IHRyYWNrcy5sZW5ndGggfHwgIWlzRmluaXRlTnVtYmVyKG5ld0lkKSkge1xuICAgICAgdGhpcy53YXJuKGBJbnZhbGlkIHN1YnRpdGxlIHRyYWNrIGlkOiAke25ld0lkfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIHN0b3BwaW5nIGxpdmUgcmVsb2FkaW5nIHRpbWVyIGlmIGFueVxuICAgIHRoaXMuY2xlYXJUaW1lcigpO1xuICAgIHRoaXMuc2VsZWN0RGVmYXVsdFRyYWNrID0gZmFsc2U7XG4gICAgY29uc3QgbGFzdFRyYWNrID0gdGhpcy5jdXJyZW50VHJhY2s7XG4gICAgY29uc3QgdHJhY2sgPSB0cmFja3NbbmV3SWRdIHx8IG51bGw7XG4gICAgdGhpcy50cmFja0lkID0gbmV3SWQ7XG4gICAgdGhpcy5jdXJyZW50VHJhY2sgPSB0cmFjaztcbiAgICB0aGlzLnRvZ2dsZVRyYWNrTW9kZXMoKTtcbiAgICBpZiAoIXRyYWNrKSB7XG4gICAgICAvLyBzd2l0Y2ggdG8gLTFcbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLlNVQlRJVExFX1RSQUNLX1NXSVRDSCwge1xuICAgICAgICBpZDogbmV3SWRcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB0cmFja0xvYWRlZCA9ICEhdHJhY2suZGV0YWlscyAmJiAhdHJhY2suZGV0YWlscy5saXZlO1xuICAgIGlmIChuZXdJZCA9PT0gdGhpcy50cmFja0lkICYmIHRyYWNrID09PSBsYXN0VHJhY2sgJiYgdHJhY2tMb2FkZWQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5sb2coYFN3aXRjaGluZyB0byBzdWJ0aXRsZS10cmFjayAke25ld0lkfWAgKyAodHJhY2sgPyBgIFwiJHt0cmFjay5uYW1lfVwiIGxhbmc6JHt0cmFjay5sYW5nfSBncm91cDoke3RyYWNrLmdyb3VwSWR9YCA6ICcnKSk7XG4gICAgY29uc3Qge1xuICAgICAgaWQsXG4gICAgICBncm91cElkID0gJycsXG4gICAgICBuYW1lLFxuICAgICAgdHlwZSxcbiAgICAgIHVybFxuICAgIH0gPSB0cmFjaztcbiAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5TVUJUSVRMRV9UUkFDS19TV0lUQ0gsIHtcbiAgICAgIGlkLFxuICAgICAgZ3JvdXBJZCxcbiAgICAgIG5hbWUsXG4gICAgICB0eXBlLFxuICAgICAgdXJsXG4gICAgfSk7XG4gICAgY29uc3QgaGxzVXJsUGFyYW1ldGVycyA9IHRoaXMuc3dpdGNoUGFyYW1zKHRyYWNrLnVybCwgbGFzdFRyYWNrID09IG51bGwgPyB2b2lkIDAgOiBsYXN0VHJhY2suZGV0YWlscywgdHJhY2suZGV0YWlscyk7XG4gICAgdGhpcy5sb2FkUGxheWxpc3QoaGxzVXJsUGFyYW1ldGVycyk7XG4gIH1cbn1cblxuY2xhc3MgQnVmZmVyT3BlcmF0aW9uUXVldWUge1xuICBjb25zdHJ1Y3Rvcihzb3VyY2VCdWZmZXJSZWZlcmVuY2UpIHtcbiAgICB0aGlzLmJ1ZmZlcnMgPSB2b2lkIDA7XG4gICAgdGhpcy5xdWV1ZXMgPSB7XG4gICAgICB2aWRlbzogW10sXG4gICAgICBhdWRpbzogW10sXG4gICAgICBhdWRpb3ZpZGVvOiBbXVxuICAgIH07XG4gICAgdGhpcy5idWZmZXJzID0gc291cmNlQnVmZmVyUmVmZXJlbmNlO1xuICB9XG4gIGFwcGVuZChvcGVyYXRpb24sIHR5cGUsIHBlbmRpbmcpIHtcbiAgICBjb25zdCBxdWV1ZSA9IHRoaXMucXVldWVzW3R5cGVdO1xuICAgIHF1ZXVlLnB1c2gob3BlcmF0aW9uKTtcbiAgICBpZiAocXVldWUubGVuZ3RoID09PSAxICYmICFwZW5kaW5nKSB7XG4gICAgICB0aGlzLmV4ZWN1dGVOZXh0KHR5cGUpO1xuICAgIH1cbiAgfVxuICBpbnNlcnRBYm9ydChvcGVyYXRpb24sIHR5cGUpIHtcbiAgICBjb25zdCBxdWV1ZSA9IHRoaXMucXVldWVzW3R5cGVdO1xuICAgIHF1ZXVlLnVuc2hpZnQob3BlcmF0aW9uKTtcbiAgICB0aGlzLmV4ZWN1dGVOZXh0KHR5cGUpO1xuICB9XG4gIGFwcGVuZEJsb2NrZXIodHlwZSkge1xuICAgIGxldCBleGVjdXRlO1xuICAgIGNvbnN0IHByb21pc2UgPSBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHtcbiAgICAgIGV4ZWN1dGUgPSByZXNvbHZlO1xuICAgIH0pO1xuICAgIGNvbnN0IG9wZXJhdGlvbiA9IHtcbiAgICAgIGV4ZWN1dGUsXG4gICAgICBvblN0YXJ0OiAoKSA9PiB7fSxcbiAgICAgIG9uQ29tcGxldGU6ICgpID0+IHt9LFxuICAgICAgb25FcnJvcjogKCkgPT4ge31cbiAgICB9O1xuICAgIHRoaXMuYXBwZW5kKG9wZXJhdGlvbiwgdHlwZSk7XG4gICAgcmV0dXJuIHByb21pc2U7XG4gIH1cbiAgZXhlY3V0ZU5leHQodHlwZSkge1xuICAgIGNvbnN0IHF1ZXVlID0gdGhpcy5xdWV1ZXNbdHlwZV07XG4gICAgaWYgKHF1ZXVlLmxlbmd0aCkge1xuICAgICAgY29uc3Qgb3BlcmF0aW9uID0gcXVldWVbMF07XG4gICAgICB0cnkge1xuICAgICAgICAvLyBPcGVyYXRpb25zIGFyZSBleHBlY3RlZCB0byByZXN1bHQgaW4gYW4gJ3VwZGF0ZWVuZCcgZXZlbnQgYmVpbmcgZmlyZWQuIElmIG5vdCwgdGhlIHF1ZXVlIHdpbGwgbG9jay4gT3BlcmF0aW9uc1xuICAgICAgICAvLyB3aGljaCBkbyBub3QgZW5kIHdpdGggdGhpcyBldmVudCBtdXN0IGNhbGwgX29uU0JVcGRhdGVFbmQgbWFudWFsbHlcbiAgICAgICAgb3BlcmF0aW9uLmV4ZWN1dGUoKTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGBbYnVmZmVyLW9wZXJhdGlvbi1xdWV1ZV06IEV4Y2VwdGlvbiBleGVjdXRpbmcgXCIke3R5cGV9XCIgU291cmNlQnVmZmVyIG9wZXJhdGlvbjogJHtlcnJvcn1gKTtcbiAgICAgICAgb3BlcmF0aW9uLm9uRXJyb3IoZXJyb3IpO1xuXG4gICAgICAgIC8vIE9ubHkgc2hpZnQgdGhlIGN1cnJlbnQgb3BlcmF0aW9uIG9mZiwgb3RoZXJ3aXNlIHRoZSB1cGRhdGVlbmQgaGFuZGxlciB3aWxsIGRvIHRoaXMgZm9yIHVzXG4gICAgICAgIGNvbnN0IHNiID0gdGhpcy5idWZmZXJzW3R5cGVdO1xuICAgICAgICBpZiAoIShzYiAhPSBudWxsICYmIHNiLnVwZGF0aW5nKSkge1xuICAgICAgICAgIHRoaXMuc2hpZnRBbmRFeGVjdXRlTmV4dCh0eXBlKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICBzaGlmdEFuZEV4ZWN1dGVOZXh0KHR5cGUpIHtcbiAgICB0aGlzLnF1ZXVlc1t0eXBlXS5zaGlmdCgpO1xuICAgIHRoaXMuZXhlY3V0ZU5leHQodHlwZSk7XG4gIH1cbiAgY3VycmVudCh0eXBlKSB7XG4gICAgcmV0dXJuIHRoaXMucXVldWVzW3R5cGVdWzBdO1xuICB9XG59XG5cbmNvbnN0IFZJREVPX0NPREVDX1BST0ZJTEVfUkVQTEFDRSA9IC8oYXZjWzEyMzRdfGh2YzF8aGV2MXxkdmhbMWVdfHZwMDl8YXYwMSkoPzpcXC5bXi4sXSspKy87XG5jbGFzcyBCdWZmZXJDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgLy8gVGhlIGxldmVsIGRldGFpbHMgdXNlZCB0byBkZXRlcm1pbmUgZHVyYXRpb24sIHRhcmdldC1kdXJhdGlvbiBhbmQgbGl2ZVxuICAgIHRoaXMuZGV0YWlscyA9IG51bGw7XG4gICAgLy8gY2FjaGUgdGhlIHNlbGYgZ2VuZXJhdGVkIG9iamVjdCB1cmwgdG8gZGV0ZWN0IGhpamFjayBvZiB2aWRlbyB0YWdcbiAgICB0aGlzLl9vYmplY3RVcmwgPSBudWxsO1xuICAgIC8vIEEgcXVldWUgb2YgYnVmZmVyIG9wZXJhdGlvbnMgd2hpY2ggcmVxdWlyZSB0aGUgU291cmNlQnVmZmVyIHRvIG5vdCBiZSB1cGRhdGluZyB1cG9uIGV4ZWN1dGlvblxuICAgIHRoaXMub3BlcmF0aW9uUXVldWUgPSB2b2lkIDA7XG4gICAgLy8gUmVmZXJlbmNlcyB0byBldmVudCBsaXN0ZW5lcnMgZm9yIGVhY2ggU291cmNlQnVmZmVyLCBzbyB0aGF0IHRoZXkgY2FuIGJlIHJlZmVyZW5jZWQgZm9yIGV2ZW50IHJlbW92YWxcbiAgICB0aGlzLmxpc3RlbmVycyA9IHZvaWQgMDtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICAvLyBUaGUgbnVtYmVyIG9mIEJVRkZFUl9DT0RFQyBldmVudHMgcmVjZWl2ZWQgYmVmb3JlIGFueSBzb3VyY2VCdWZmZXJzIGFyZSBjcmVhdGVkXG4gICAgdGhpcy5idWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkID0gMDtcbiAgICAvLyBUaGUgdG90YWwgbnVtYmVyIG9mIEJVRkZFUl9DT0RFQyBldmVudHMgcmVjZWl2ZWRcbiAgICB0aGlzLl9idWZmZXJDb2RlY0V2ZW50c1RvdGFsID0gMDtcbiAgICAvLyBBIHJlZmVyZW5jZSB0byB0aGUgYXR0YWNoZWQgbWVkaWEgZWxlbWVudFxuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIC8vIEEgcmVmZXJlbmNlIHRvIHRoZSBhY3RpdmUgbWVkaWEgc291cmNlXG4gICAgdGhpcy5tZWRpYVNvdXJjZSA9IG51bGw7XG4gICAgLy8gTGFzdCBNUDMgYXVkaW8gY2h1bmsgYXBwZW5kZWRcbiAgICB0aGlzLmxhc3RNcGVnQXVkaW9DaHVuayA9IG51bGw7XG4gICAgdGhpcy5hcHBlbmRTb3VyY2UgPSB2b2lkIDA7XG4gICAgLy8gY291bnRlcnNcbiAgICB0aGlzLmFwcGVuZEVycm9ycyA9IHtcbiAgICAgIGF1ZGlvOiAwLFxuICAgICAgdmlkZW86IDAsXG4gICAgICBhdWRpb3ZpZGVvOiAwXG4gICAgfTtcbiAgICB0aGlzLnRyYWNrcyA9IHt9O1xuICAgIHRoaXMucGVuZGluZ1RyYWNrcyA9IHt9O1xuICAgIHRoaXMuc291cmNlQnVmZmVyID0gdm9pZCAwO1xuICAgIHRoaXMubG9nID0gdm9pZCAwO1xuICAgIHRoaXMud2FybiA9IHZvaWQgMDtcbiAgICB0aGlzLmVycm9yID0gdm9pZCAwO1xuICAgIHRoaXMuX29uRW5kU3RyZWFtaW5nID0gZXZlbnQgPT4ge1xuICAgICAgaWYgKCF0aGlzLmhscykge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICB0aGlzLmhscy5wYXVzZUJ1ZmZlcmluZygpO1xuICAgIH07XG4gICAgdGhpcy5fb25TdGFydFN0cmVhbWluZyA9IGV2ZW50ID0+IHtcbiAgICAgIGlmICghdGhpcy5obHMpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5obHMucmVzdW1lQnVmZmVyaW5nKCk7XG4gICAgfTtcbiAgICAvLyBLZWVwIGFzIGFycm93IGZ1bmN0aW9ucyBzbyB0aGF0IHdlIGNhbiBkaXJlY3RseSByZWZlcmVuY2UgdGhlc2UgZnVuY3Rpb25zIGRpcmVjdGx5IGFzIGV2ZW50IGxpc3RlbmVyc1xuICAgIHRoaXMuX29uTWVkaWFTb3VyY2VPcGVuID0gKCkgPT4ge1xuICAgICAgY29uc3Qge1xuICAgICAgICBtZWRpYSxcbiAgICAgICAgbWVkaWFTb3VyY2VcbiAgICAgIH0gPSB0aGlzO1xuICAgICAgdGhpcy5sb2coJ01lZGlhIHNvdXJjZSBvcGVuZWQnKTtcbiAgICAgIGlmIChtZWRpYSkge1xuICAgICAgICBtZWRpYS5yZW1vdmVFdmVudExpc3RlbmVyKCdlbXB0aWVkJywgdGhpcy5fb25NZWRpYUVtcHRpZWQpO1xuICAgICAgICB0aGlzLnVwZGF0ZU1lZGlhRWxlbWVudER1cmF0aW9uKCk7XG4gICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLk1FRElBX0FUVEFDSEVELCB7XG4gICAgICAgICAgbWVkaWEsXG4gICAgICAgICAgbWVkaWFTb3VyY2U6IG1lZGlhU291cmNlXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgICAgaWYgKG1lZGlhU291cmNlKSB7XG4gICAgICAgIC8vIG9uY2UgcmVjZWl2ZWQsIGRvbid0IGxpc3RlbiBhbnltb3JlIHRvIHNvdXJjZW9wZW4gZXZlbnRcbiAgICAgICAgbWVkaWFTb3VyY2UucmVtb3ZlRXZlbnRMaXN0ZW5lcignc291cmNlb3BlbicsIHRoaXMuX29uTWVkaWFTb3VyY2VPcGVuKTtcbiAgICAgIH1cbiAgICAgIHRoaXMuY2hlY2tQZW5kaW5nVHJhY2tzKCk7XG4gICAgfTtcbiAgICB0aGlzLl9vbk1lZGlhU291cmNlQ2xvc2UgPSAoKSA9PiB7XG4gICAgICB0aGlzLmxvZygnTWVkaWEgc291cmNlIGNsb3NlZCcpO1xuICAgIH07XG4gICAgdGhpcy5fb25NZWRpYVNvdXJjZUVuZGVkID0gKCkgPT4ge1xuICAgICAgdGhpcy5sb2coJ01lZGlhIHNvdXJjZSBlbmRlZCcpO1xuICAgIH07XG4gICAgdGhpcy5fb25NZWRpYUVtcHRpZWQgPSAoKSA9PiB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIG1lZGlhU3JjLFxuICAgICAgICBfb2JqZWN0VXJsXG4gICAgICB9ID0gdGhpcztcbiAgICAgIGlmIChtZWRpYVNyYyAhPT0gX29iamVjdFVybCkge1xuICAgICAgICBsb2dnZXIuZXJyb3IoYE1lZGlhIGVsZW1lbnQgc3JjIHdhcyBzZXQgd2hpbGUgYXR0YWNoaW5nIE1lZGlhU291cmNlICgke19vYmplY3RVcmx9ID4gJHttZWRpYVNyY30pYCk7XG4gICAgICB9XG4gICAgfTtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgICBjb25zdCBsb2dQcmVmaXggPSAnW2J1ZmZlci1jb250cm9sbGVyXSc7XG4gICAgdGhpcy5hcHBlbmRTb3VyY2UgPSBpc01hbmFnZWRNZWRpYVNvdXJjZShnZXRNZWRpYVNvdXJjZShobHMuY29uZmlnLnByZWZlck1hbmFnZWRNZWRpYVNvdXJjZSkpO1xuICAgIHRoaXMubG9nID0gbG9nZ2VyLmxvZy5iaW5kKGxvZ2dlciwgbG9nUHJlZml4KTtcbiAgICB0aGlzLndhcm4gPSBsb2dnZXIud2Fybi5iaW5kKGxvZ2dlciwgbG9nUHJlZml4KTtcbiAgICB0aGlzLmVycm9yID0gbG9nZ2VyLmVycm9yLmJpbmQobG9nZ2VyLCBsb2dQcmVmaXgpO1xuICAgIHRoaXMuX2luaXRTb3VyY2VCdWZmZXIoKTtcbiAgICB0aGlzLnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gIH1cbiAgaGFzU291cmNlVHlwZXMoKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKS5sZW5ndGggPiAwIHx8IE9iamVjdC5rZXlzKHRoaXMucGVuZGluZ1RyYWNrcykubGVuZ3RoID4gMDtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMudW5yZWdpc3Rlckxpc3RlbmVycygpO1xuICAgIHRoaXMuZGV0YWlscyA9IG51bGw7XG4gICAgdGhpcy5sYXN0TXBlZ0F1ZGlvQ2h1bmsgPSBudWxsO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmhscyA9IG51bGw7XG4gIH1cbiAgcmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9uKEV2ZW50cy5NRURJQV9BVFRBQ0hJTkcsIHRoaXMub25NZWRpYUF0dGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX1BBUlNFRCwgdGhpcy5vbk1hbmlmZXN0UGFyc2VkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9SRVNFVCwgdGhpcy5vbkJ1ZmZlclJlc2V0LCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9BUFBFTkRJTkcsIHRoaXMub25CdWZmZXJBcHBlbmRpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQlVGRkVSX0NPREVDUywgdGhpcy5vbkJ1ZmZlckNvZGVjcywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5CVUZGRVJfRU9TLCB0aGlzLm9uQnVmZmVyRW9zLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9GTFVTSElORywgdGhpcy5vbkJ1ZmZlckZsdXNoaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkxFVkVMX1VQREFURUQsIHRoaXMub25MZXZlbFVwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuRlJBR19QQVJTRUQsIHRoaXMub25GcmFnUGFyc2VkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkZSQUdfQ0hBTkdFRCwgdGhpcy5vbkZyYWdDaGFuZ2VkLCB0aGlzKTtcbiAgfVxuICB1bnJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vZmYoRXZlbnRzLk1FRElBX0FUVEFDSElORywgdGhpcy5vbk1lZGlhQXR0YWNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9SRVNFVCwgdGhpcy5vbkJ1ZmZlclJlc2V0LCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfQVBQRU5ESU5HLCB0aGlzLm9uQnVmZmVyQXBwZW5kaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfQ09ERUNTLCB0aGlzLm9uQnVmZmVyQ29kZWNzLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfRU9TLCB0aGlzLm9uQnVmZmVyRW9zLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfRkxVU0hJTkcsIHRoaXMub25CdWZmZXJGbHVzaGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTEVWRUxfVVBEQVRFRCwgdGhpcy5vbkxldmVsVXBkYXRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRlJBR19QQVJTRUQsIHRoaXMub25GcmFnUGFyc2VkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0NIQU5HRUQsIHRoaXMub25GcmFnQ2hhbmdlZCwgdGhpcyk7XG4gIH1cbiAgX2luaXRTb3VyY2VCdWZmZXIoKSB7XG4gICAgdGhpcy5zb3VyY2VCdWZmZXIgPSB7fTtcbiAgICB0aGlzLm9wZXJhdGlvblF1ZXVlID0gbmV3IEJ1ZmZlck9wZXJhdGlvblF1ZXVlKHRoaXMuc291cmNlQnVmZmVyKTtcbiAgICB0aGlzLmxpc3RlbmVycyA9IHtcbiAgICAgIGF1ZGlvOiBbXSxcbiAgICAgIHZpZGVvOiBbXSxcbiAgICAgIGF1ZGlvdmlkZW86IFtdXG4gICAgfTtcbiAgICB0aGlzLmFwcGVuZEVycm9ycyA9IHtcbiAgICAgIGF1ZGlvOiAwLFxuICAgICAgdmlkZW86IDAsXG4gICAgICBhdWRpb3ZpZGVvOiAwXG4gICAgfTtcbiAgICB0aGlzLmxhc3RNcGVnQXVkaW9DaHVuayA9IG51bGw7XG4gIH1cbiAgb25NYW5pZmVzdExvYWRpbmcoKSB7XG4gICAgdGhpcy5idWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkID0gdGhpcy5fYnVmZmVyQ29kZWNFdmVudHNUb3RhbCA9IDA7XG4gICAgdGhpcy5kZXRhaWxzID0gbnVsbDtcbiAgfVxuICBvbk1hbmlmZXN0UGFyc2VkKGV2ZW50LCBkYXRhKSB7XG4gICAgLy8gaW4gY2FzZSBvZiBhbHQgYXVkaW8gMiBCVUZGRVJfQ09ERUNTIGV2ZW50cyB3aWxsIGJlIHRyaWdnZXJlZCwgb25lIHBlciBzdHJlYW0gY29udHJvbGxlclxuICAgIC8vIHNvdXJjZWJ1ZmZlcnMgd2lsbCBiZSBjcmVhdGVkIGFsbCBhdCBvbmNlIHdoZW4gdGhlIGV4cGVjdGVkIG5iIG9mIHRyYWNrcyB3aWxsIGJlIHJlYWNoZWRcbiAgICAvLyBpbiBjYXNlIGFsdCBhdWRpbyBpcyBub3QgdXNlZCwgb25seSBvbmUgQlVGRkVSX0NPREVDIGV2ZW50IHdpbGwgYmUgZmlyZWQgZnJvbSBtYWluIHN0cmVhbSBjb250cm9sbGVyXG4gICAgLy8gaXQgd2lsbCBjb250YWluIHRoZSBleHBlY3RlZCBuYiBvZiBzb3VyY2UgYnVmZmVycywgbm8gbmVlZCB0byBjb21wdXRlIGl0XG4gICAgbGV0IGNvZGVjRXZlbnRzID0gMjtcbiAgICBpZiAoZGF0YS5hdWRpbyAmJiAhZGF0YS52aWRlbyB8fCAhZGF0YS5hbHRBdWRpbyB8fCAhdHJ1ZSkge1xuICAgICAgY29kZWNFdmVudHMgPSAxO1xuICAgIH1cbiAgICB0aGlzLmJ1ZmZlckNvZGVjRXZlbnRzRXhwZWN0ZWQgPSB0aGlzLl9idWZmZXJDb2RlY0V2ZW50c1RvdGFsID0gY29kZWNFdmVudHM7XG4gICAgdGhpcy5sb2coYCR7dGhpcy5idWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkfSBidWZmZXJDb2RlYyBldmVudChzKSBleHBlY3RlZGApO1xuICB9XG4gIG9uTWVkaWFBdHRhY2hpbmcoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCBtZWRpYSA9IHRoaXMubWVkaWEgPSBkYXRhLm1lZGlhO1xuICAgIGNvbnN0IE1lZGlhU291cmNlID0gZ2V0TWVkaWFTb3VyY2UodGhpcy5hcHBlbmRTb3VyY2UpO1xuICAgIGlmIChtZWRpYSAmJiBNZWRpYVNvdXJjZSkge1xuICAgICAgdmFyIF9tcyRjb25zdHJ1Y3RvcjtcbiAgICAgIGNvbnN0IG1zID0gdGhpcy5tZWRpYVNvdXJjZSA9IG5ldyBNZWRpYVNvdXJjZSgpO1xuICAgICAgdGhpcy5sb2coYGNyZWF0ZWQgbWVkaWEgc291cmNlOiAkeyhfbXMkY29uc3RydWN0b3IgPSBtcy5jb25zdHJ1Y3RvcikgPT0gbnVsbCA/IHZvaWQgMCA6IF9tcyRjb25zdHJ1Y3Rvci5uYW1lfWApO1xuICAgICAgLy8gTWVkaWFTb3VyY2UgbGlzdGVuZXJzIGFyZSBhcnJvdyBmdW5jdGlvbnMgd2l0aCBhIGxleGljYWwgc2NvcGUsIGFuZCBkbyBub3QgbmVlZCB0byBiZSBib3VuZFxuICAgICAgbXMuYWRkRXZlbnRMaXN0ZW5lcignc291cmNlb3BlbicsIHRoaXMuX29uTWVkaWFTb3VyY2VPcGVuKTtcbiAgICAgIG1zLmFkZEV2ZW50TGlzdGVuZXIoJ3NvdXJjZWVuZGVkJywgdGhpcy5fb25NZWRpYVNvdXJjZUVuZGVkKTtcbiAgICAgIG1zLmFkZEV2ZW50TGlzdGVuZXIoJ3NvdXJjZWNsb3NlJywgdGhpcy5fb25NZWRpYVNvdXJjZUNsb3NlKTtcbiAgICAgIGlmICh0aGlzLmFwcGVuZFNvdXJjZSkge1xuICAgICAgICBtcy5hZGRFdmVudExpc3RlbmVyKCdzdGFydHN0cmVhbWluZycsIHRoaXMuX29uU3RhcnRTdHJlYW1pbmcpO1xuICAgICAgICBtcy5hZGRFdmVudExpc3RlbmVyKCdlbmRzdHJlYW1pbmcnLCB0aGlzLl9vbkVuZFN0cmVhbWluZyk7XG4gICAgICB9XG5cbiAgICAgIC8vIGNhY2hlIHRoZSBsb2NhbGx5IGdlbmVyYXRlZCBvYmplY3QgdXJsXG4gICAgICBjb25zdCBvYmplY3RVcmwgPSB0aGlzLl9vYmplY3RVcmwgPSBzZWxmLlVSTC5jcmVhdGVPYmplY3RVUkwobXMpO1xuICAgICAgLy8gbGluayB2aWRlbyBhbmQgbWVkaWEgU291cmNlXG4gICAgICBpZiAodGhpcy5hcHBlbmRTb3VyY2UpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBtZWRpYS5yZW1vdmVBdHRyaWJ1dGUoJ3NyYycpO1xuICAgICAgICAgIC8vIE1hbmFnZWRNZWRpYVNvdXJjZSB3aWxsIG5vdCBvcGVuIHdpdGhvdXQgZGlzYWJsZVJlbW90ZVBsYXliYWNrIHNldCB0byBmYWxzZSBvciBzb3VyY2UgYWx0ZXJuYXRpdmVzXG4gICAgICAgICAgY29uc3QgTU1TID0gc2VsZi5NYW5hZ2VkTWVkaWFTb3VyY2U7XG4gICAgICAgICAgbWVkaWEuZGlzYWJsZVJlbW90ZVBsYXliYWNrID0gbWVkaWEuZGlzYWJsZVJlbW90ZVBsYXliYWNrIHx8IE1NUyAmJiBtcyBpbnN0YW5jZW9mIE1NUztcbiAgICAgICAgICByZW1vdmVTb3VyY2VDaGlsZHJlbihtZWRpYSk7XG4gICAgICAgICAgYWRkU291cmNlKG1lZGlhLCBvYmplY3RVcmwpO1xuICAgICAgICAgIG1lZGlhLmxvYWQoKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICBtZWRpYS5zcmMgPSBvYmplY3RVcmw7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIG1lZGlhLnNyYyA9IG9iamVjdFVybDtcbiAgICAgIH1cbiAgICAgIG1lZGlhLmFkZEV2ZW50TGlzdGVuZXIoJ2VtcHRpZWQnLCB0aGlzLl9vbk1lZGlhRW1wdGllZCk7XG4gICAgfVxuICB9XG4gIG9uTWVkaWFEZXRhY2hpbmcoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbWVkaWEsXG4gICAgICBtZWRpYVNvdXJjZSxcbiAgICAgIF9vYmplY3RVcmxcbiAgICB9ID0gdGhpcztcbiAgICBpZiAobWVkaWFTb3VyY2UpIHtcbiAgICAgIHRoaXMubG9nKCdtZWRpYSBzb3VyY2UgZGV0YWNoaW5nJyk7XG4gICAgICBpZiAobWVkaWFTb3VyY2UucmVhZHlTdGF0ZSA9PT0gJ29wZW4nKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgLy8gZW5kT2ZTdHJlYW0gY291bGQgdHJpZ2dlciBleGNlcHRpb24gaWYgYW55IHNvdXJjZWJ1ZmZlciBpcyBpbiB1cGRhdGluZyBzdGF0ZVxuICAgICAgICAgIC8vIHdlIGRvbid0IHJlYWxseSBjYXJlIGFib3V0IGNoZWNraW5nIHNvdXJjZWJ1ZmZlciBzdGF0ZSBoZXJlLFxuICAgICAgICAgIC8vIGFzIHdlIGFyZSBhbnl3YXkgZGV0YWNoaW5nIHRoZSBNZWRpYVNvdXJjZVxuICAgICAgICAgIC8vIGxldCdzIGp1c3QgYXZvaWQgdGhpcyBleGNlcHRpb24gdG8gcHJvcGFnYXRlXG4gICAgICAgICAgbWVkaWFTb3VyY2UuZW5kT2ZTdHJlYW0oKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgdGhpcy53YXJuKGBvbk1lZGlhRGV0YWNoaW5nOiAke2Vyci5tZXNzYWdlfSB3aGlsZSBjYWxsaW5nIGVuZE9mU3RyZWFtYCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIC8vIENsZWFuIHVwIHRoZSBTb3VyY2VCdWZmZXJzIGJ5IGludm9raW5nIG9uQnVmZmVyUmVzZXRcbiAgICAgIHRoaXMub25CdWZmZXJSZXNldCgpO1xuICAgICAgbWVkaWFTb3VyY2UucmVtb3ZlRXZlbnRMaXN0ZW5lcignc291cmNlb3BlbicsIHRoaXMuX29uTWVkaWFTb3VyY2VPcGVuKTtcbiAgICAgIG1lZGlhU291cmNlLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3NvdXJjZWVuZGVkJywgdGhpcy5fb25NZWRpYVNvdXJjZUVuZGVkKTtcbiAgICAgIG1lZGlhU291cmNlLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3NvdXJjZWNsb3NlJywgdGhpcy5fb25NZWRpYVNvdXJjZUNsb3NlKTtcbiAgICAgIGlmICh0aGlzLmFwcGVuZFNvdXJjZSkge1xuICAgICAgICBtZWRpYVNvdXJjZS5yZW1vdmVFdmVudExpc3RlbmVyKCdzdGFydHN0cmVhbWluZycsIHRoaXMuX29uU3RhcnRTdHJlYW1pbmcpO1xuICAgICAgICBtZWRpYVNvdXJjZS5yZW1vdmVFdmVudExpc3RlbmVyKCdlbmRzdHJlYW1pbmcnLCB0aGlzLl9vbkVuZFN0cmVhbWluZyk7XG4gICAgICB9XG5cbiAgICAgIC8vIERldGFjaCBwcm9wZXJseSB0aGUgTWVkaWFTb3VyY2UgZnJvbSB0aGUgSFRNTE1lZGlhRWxlbWVudCBhc1xuICAgICAgLy8gc3VnZ2VzdGVkIGluIGh0dHBzOi8vZ2l0aHViLmNvbS93M2MvbWVkaWEtc291cmNlL2lzc3Vlcy81My5cbiAgICAgIGlmIChtZWRpYSkge1xuICAgICAgICBtZWRpYS5yZW1vdmVFdmVudExpc3RlbmVyKCdlbXB0aWVkJywgdGhpcy5fb25NZWRpYUVtcHRpZWQpO1xuICAgICAgICBpZiAoX29iamVjdFVybCkge1xuICAgICAgICAgIHNlbGYuVVJMLnJldm9rZU9iamVjdFVSTChfb2JqZWN0VXJsKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGNsZWFuIHVwIHZpZGVvIHRhZyBzcmMgb25seSBpZiBpdCdzIG91ciBvd24gdXJsLiBzb21lIGV4dGVybmFsIGxpYnJhcmllcyBtaWdodFxuICAgICAgICAvLyBoaWphY2sgdGhlIHZpZGVvIHRhZyBhbmQgY2hhbmdlIGl0cyAnc3JjJyB3aXRob3V0IGRlc3Ryb3lpbmcgdGhlIEhscyBpbnN0YW5jZSBmaXJzdFxuICAgICAgICBpZiAodGhpcy5tZWRpYVNyYyA9PT0gX29iamVjdFVybCkge1xuICAgICAgICAgIG1lZGlhLnJlbW92ZUF0dHJpYnV0ZSgnc3JjJyk7XG4gICAgICAgICAgaWYgKHRoaXMuYXBwZW5kU291cmNlKSB7XG4gICAgICAgICAgICByZW1vdmVTb3VyY2VDaGlsZHJlbihtZWRpYSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIG1lZGlhLmxvYWQoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLndhcm4oJ21lZGlhfHNvdXJjZS5zcmMgd2FzIGNoYW5nZWQgYnkgYSB0aGlyZCBwYXJ0eSAtIHNraXAgY2xlYW51cCcpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLm1lZGlhU291cmNlID0gbnVsbDtcbiAgICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgICAgdGhpcy5fb2JqZWN0VXJsID0gbnVsbDtcbiAgICAgIHRoaXMuYnVmZmVyQ29kZWNFdmVudHNFeHBlY3RlZCA9IHRoaXMuX2J1ZmZlckNvZGVjRXZlbnRzVG90YWw7XG4gICAgICB0aGlzLnBlbmRpbmdUcmFja3MgPSB7fTtcbiAgICAgIHRoaXMudHJhY2tzID0ge307XG4gICAgfVxuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLk1FRElBX0RFVEFDSEVELCB1bmRlZmluZWQpO1xuICB9XG4gIG9uQnVmZmVyUmVzZXQoKSB7XG4gICAgdGhpcy5nZXRTb3VyY2VCdWZmZXJUeXBlcygpLmZvckVhY2godHlwZSA9PiB7XG4gICAgICB0aGlzLnJlc2V0QnVmZmVyKHR5cGUpO1xuICAgIH0pO1xuICAgIHRoaXMuX2luaXRTb3VyY2VCdWZmZXIoKTtcbiAgfVxuICByZXNldEJ1ZmZlcih0eXBlKSB7XG4gICAgY29uc3Qgc2IgPSB0aGlzLnNvdXJjZUJ1ZmZlclt0eXBlXTtcbiAgICB0cnkge1xuICAgICAgaWYgKHNiKSB7XG4gICAgICAgIHZhciBfdGhpcyRtZWRpYVNvdXJjZTtcbiAgICAgICAgdGhpcy5yZW1vdmVCdWZmZXJMaXN0ZW5lcnModHlwZSk7XG4gICAgICAgIC8vIFN5bmNocm9ub3VzbHkgcmVtb3ZlIHRoZSBTQiBmcm9tIHRoZSBtYXAgYmVmb3JlIHRoZSBuZXh0IGNhbGwgaW4gb3JkZXIgdG8gcHJldmVudCBhbiBhc3luYyBmdW5jdGlvbiBmcm9tXG4gICAgICAgIC8vIGFjY2Vzc2luZyBpdFxuICAgICAgICB0aGlzLnNvdXJjZUJ1ZmZlclt0eXBlXSA9IHVuZGVmaW5lZDtcbiAgICAgICAgaWYgKChfdGhpcyRtZWRpYVNvdXJjZSA9IHRoaXMubWVkaWFTb3VyY2UpICE9IG51bGwgJiYgX3RoaXMkbWVkaWFTb3VyY2Uuc291cmNlQnVmZmVycy5sZW5ndGgpIHtcbiAgICAgICAgICB0aGlzLm1lZGlhU291cmNlLnJlbW92ZVNvdXJjZUJ1ZmZlcihzYik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMud2Fybihgb25CdWZmZXJSZXNldCAke3R5cGV9YCwgZXJyKTtcbiAgICB9XG4gIH1cbiAgb25CdWZmZXJDb2RlY3MoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCBzb3VyY2VCdWZmZXJDb3VudCA9IHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKS5sZW5ndGg7XG4gICAgY29uc3QgdHJhY2tOYW1lcyA9IE9iamVjdC5rZXlzKGRhdGEpO1xuICAgIHRyYWNrTmFtZXMuZm9yRWFjaCh0cmFja05hbWUgPT4ge1xuICAgICAgaWYgKHNvdXJjZUJ1ZmZlckNvdW50KSB7XG4gICAgICAgIC8vIGNoZWNrIGlmIFNvdXJjZUJ1ZmZlciBjb2RlYyBuZWVkcyB0byBjaGFuZ2VcbiAgICAgICAgY29uc3QgdHJhY2sgPSB0aGlzLnRyYWNrc1t0cmFja05hbWVdO1xuICAgICAgICBpZiAodHJhY2sgJiYgdHlwZW9mIHRyYWNrLmJ1ZmZlci5jaGFuZ2VUeXBlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgICAgdmFyIF90cmFja0NvZGVjO1xuICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgIGlkLFxuICAgICAgICAgICAgY29kZWMsXG4gICAgICAgICAgICBsZXZlbENvZGVjLFxuICAgICAgICAgICAgY29udGFpbmVyLFxuICAgICAgICAgICAgbWV0YWRhdGFcbiAgICAgICAgICB9ID0gZGF0YVt0cmFja05hbWVdO1xuICAgICAgICAgIGNvbnN0IGN1cnJlbnRDb2RlY0Z1bGwgPSBwaWNrTW9zdENvbXBsZXRlQ29kZWNOYW1lKHRyYWNrLmNvZGVjLCB0cmFjay5sZXZlbENvZGVjKTtcbiAgICAgICAgICBjb25zdCBjdXJyZW50Q29kZWMgPSBjdXJyZW50Q29kZWNGdWxsID09IG51bGwgPyB2b2lkIDAgOiBjdXJyZW50Q29kZWNGdWxsLnJlcGxhY2UoVklERU9fQ09ERUNfUFJPRklMRV9SRVBMQUNFLCAnJDEnKTtcbiAgICAgICAgICBsZXQgdHJhY2tDb2RlYyA9IHBpY2tNb3N0Q29tcGxldGVDb2RlY05hbWUoY29kZWMsIGxldmVsQ29kZWMpO1xuICAgICAgICAgIGNvbnN0IG5leHRDb2RlYyA9IChfdHJhY2tDb2RlYyA9IHRyYWNrQ29kZWMpID09IG51bGwgPyB2b2lkIDAgOiBfdHJhY2tDb2RlYy5yZXBsYWNlKFZJREVPX0NPREVDX1BST0ZJTEVfUkVQTEFDRSwgJyQxJyk7XG4gICAgICAgICAgaWYgKHRyYWNrQ29kZWMgJiYgY3VycmVudENvZGVjICE9PSBuZXh0Q29kZWMpIHtcbiAgICAgICAgICAgIGlmICh0cmFja05hbWUuc2xpY2UoMCwgNSkgPT09ICdhdWRpbycpIHtcbiAgICAgICAgICAgICAgdHJhY2tDb2RlYyA9IGdldENvZGVjQ29tcGF0aWJsZU5hbWUodHJhY2tDb2RlYywgdGhpcy5hcHBlbmRTb3VyY2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgbWltZVR5cGUgPSBgJHtjb250YWluZXJ9O2NvZGVjcz0ke3RyYWNrQ29kZWN9YDtcbiAgICAgICAgICAgIHRoaXMuYXBwZW5kQ2hhbmdlVHlwZSh0cmFja05hbWUsIG1pbWVUeXBlKTtcbiAgICAgICAgICAgIHRoaXMubG9nKGBzd2l0Y2hpbmcgY29kZWMgJHtjdXJyZW50Q29kZWNGdWxsfSB0byAke3RyYWNrQ29kZWN9YCk7XG4gICAgICAgICAgICB0aGlzLnRyYWNrc1t0cmFja05hbWVdID0ge1xuICAgICAgICAgICAgICBidWZmZXI6IHRyYWNrLmJ1ZmZlcixcbiAgICAgICAgICAgICAgY29kZWMsXG4gICAgICAgICAgICAgIGNvbnRhaW5lcixcbiAgICAgICAgICAgICAgbGV2ZWxDb2RlYyxcbiAgICAgICAgICAgICAgbWV0YWRhdGEsXG4gICAgICAgICAgICAgIGlkXG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gaWYgc291cmNlIGJ1ZmZlcihzKSBub3QgY3JlYXRlZCB5ZXQsIGFwcGVuZGVkIGJ1ZmZlciB0cmFja3MgaW4gdGhpcy5wZW5kaW5nVHJhY2tzXG4gICAgICAgIHRoaXMucGVuZGluZ1RyYWNrc1t0cmFja05hbWVdID0gZGF0YVt0cmFja05hbWVdO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgLy8gaWYgc291cmNlYnVmZmVycyBhbHJlYWR5IGNyZWF0ZWQsIGRvIG5vdGhpbmcgLi4uXG4gICAgaWYgKHNvdXJjZUJ1ZmZlckNvdW50KSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGJ1ZmZlckNvZGVjRXZlbnRzRXhwZWN0ZWQgPSBNYXRoLm1heCh0aGlzLmJ1ZmZlckNvZGVjRXZlbnRzRXhwZWN0ZWQgLSAxLCAwKTtcbiAgICBpZiAodGhpcy5idWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkICE9PSBidWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkKSB7XG4gICAgICB0aGlzLmxvZyhgJHtidWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkfSBidWZmZXJDb2RlYyBldmVudChzKSBleHBlY3RlZCAke3RyYWNrTmFtZXMuam9pbignLCcpfWApO1xuICAgICAgdGhpcy5idWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkID0gYnVmZmVyQ29kZWNFdmVudHNFeHBlY3RlZDtcbiAgICB9XG4gICAgaWYgKHRoaXMubWVkaWFTb3VyY2UgJiYgdGhpcy5tZWRpYVNvdXJjZS5yZWFkeVN0YXRlID09PSAnb3BlbicpIHtcbiAgICAgIHRoaXMuY2hlY2tQZW5kaW5nVHJhY2tzKCk7XG4gICAgfVxuICB9XG4gIGFwcGVuZENoYW5nZVR5cGUodHlwZSwgbWltZVR5cGUpIHtcbiAgICBjb25zdCB7XG4gICAgICBvcGVyYXRpb25RdWV1ZVxuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IG9wZXJhdGlvbiA9IHtcbiAgICAgIGV4ZWN1dGU6ICgpID0+IHtcbiAgICAgICAgY29uc3Qgc2IgPSB0aGlzLnNvdXJjZUJ1ZmZlclt0eXBlXTtcbiAgICAgICAgaWYgKHNiKSB7XG4gICAgICAgICAgdGhpcy5sb2coYGNoYW5naW5nICR7dHlwZX0gc291cmNlQnVmZmVyIHR5cGUgdG8gJHttaW1lVHlwZX1gKTtcbiAgICAgICAgICBzYi5jaGFuZ2VUeXBlKG1pbWVUeXBlKTtcbiAgICAgICAgfVxuICAgICAgICBvcGVyYXRpb25RdWV1ZS5zaGlmdEFuZEV4ZWN1dGVOZXh0KHR5cGUpO1xuICAgICAgfSxcbiAgICAgIG9uU3RhcnQ6ICgpID0+IHt9LFxuICAgICAgb25Db21wbGV0ZTogKCkgPT4ge30sXG4gICAgICBvbkVycm9yOiBlcnJvciA9PiB7XG4gICAgICAgIHRoaXMud2FybihgRmFpbGVkIHRvIGNoYW5nZSAke3R5cGV9IFNvdXJjZUJ1ZmZlciB0eXBlYCwgZXJyb3IpO1xuICAgICAgfVxuICAgIH07XG4gICAgb3BlcmF0aW9uUXVldWUuYXBwZW5kKG9wZXJhdGlvbiwgdHlwZSwgISF0aGlzLnBlbmRpbmdUcmFja3NbdHlwZV0pO1xuICB9XG4gIG9uQnVmZmVyQXBwZW5kaW5nKGV2ZW50LCBldmVudERhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHMsXG4gICAgICBvcGVyYXRpb25RdWV1ZSxcbiAgICAgIHRyYWNrc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIGRhdGEsXG4gICAgICB0eXBlLFxuICAgICAgZnJhZyxcbiAgICAgIHBhcnQsXG4gICAgICBjaHVua01ldGFcbiAgICB9ID0gZXZlbnREYXRhO1xuICAgIGNvbnN0IGNodW5rU3RhdHMgPSBjaHVua01ldGEuYnVmZmVyaW5nW3R5cGVdO1xuICAgIGNvbnN0IGJ1ZmZlckFwcGVuZGluZ1N0YXJ0ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICBjaHVua1N0YXRzLnN0YXJ0ID0gYnVmZmVyQXBwZW5kaW5nU3RhcnQ7XG4gICAgY29uc3QgZnJhZ0J1ZmZlcmluZyA9IGZyYWcuc3RhdHMuYnVmZmVyaW5nO1xuICAgIGNvbnN0IHBhcnRCdWZmZXJpbmcgPSBwYXJ0ID8gcGFydC5zdGF0cy5idWZmZXJpbmcgOiBudWxsO1xuICAgIGlmIChmcmFnQnVmZmVyaW5nLnN0YXJ0ID09PSAwKSB7XG4gICAgICBmcmFnQnVmZmVyaW5nLnN0YXJ0ID0gYnVmZmVyQXBwZW5kaW5nU3RhcnQ7XG4gICAgfVxuICAgIGlmIChwYXJ0QnVmZmVyaW5nICYmIHBhcnRCdWZmZXJpbmcuc3RhcnQgPT09IDApIHtcbiAgICAgIHBhcnRCdWZmZXJpbmcuc3RhcnQgPSBidWZmZXJBcHBlbmRpbmdTdGFydDtcbiAgICB9XG5cbiAgICAvLyBUT0RPOiBPbmx5IHVwZGF0ZSB0aW1lc3RhbXBPZmZzZXQgd2hlbiBhdWRpby9tcGVnIGZyYWdtZW50IG9yIHBhcnQgaXMgbm90IGNvbnRpZ3VvdXMgd2l0aCBwcmV2aW91c2x5IGFwcGVuZGVkXG4gICAgLy8gQWRqdXN0aW5nIGBTb3VyY2VCdWZmZXIudGltZXN0YW1wT2Zmc2V0YCAoZGVzaXJlZCBwb2ludCBpbiB0aGUgdGltZWxpbmUgd2hlcmUgdGhlIG5leHQgZnJhbWVzIHNob3VsZCBiZSBhcHBlbmRlZClcbiAgICAvLyBpbiBDaHJvbWUgYnJvd3NlciB3aGVuIHdlIGRldGVjdCBNUEVHIGF1ZGlvIGNvbnRhaW5lciBhbmQgdGltZSBkZWx0YSBiZXR3ZWVuIGxldmVsIFBUUyBhbmQgYFNvdXJjZUJ1ZmZlci50aW1lc3RhbXBPZmZzZXRgXG4gICAgLy8gaXMgZ3JlYXRlciB0aGFuIDEwMG1zICh0aGlzIGlzIGVub3VnaCB0byBoYW5kbGUgc2VlayBmb3IgVk9EIG9yIGxldmVsIGNoYW5nZSBmb3IgTElWRSB2aWRlb3MpLlxuICAgIC8vIE1vcmUgaW5mbyBoZXJlOiBodHRwczovL2dpdGh1Yi5jb20vdmlkZW8tZGV2L2hscy5qcy9pc3N1ZXMvMzMyI2lzc3VlY29tbWVudC0yNTc5ODY0ODZcbiAgICBjb25zdCBhdWRpb1RyYWNrID0gdHJhY2tzLmF1ZGlvO1xuICAgIGxldCBjaGVja1RpbWVzdGFtcE9mZnNldCA9IGZhbHNlO1xuICAgIGlmICh0eXBlID09PSAnYXVkaW8nICYmIChhdWRpb1RyYWNrID09IG51bGwgPyB2b2lkIDAgOiBhdWRpb1RyYWNrLmNvbnRhaW5lcikgPT09ICdhdWRpby9tcGVnJykge1xuICAgICAgY2hlY2tUaW1lc3RhbXBPZmZzZXQgPSAhdGhpcy5sYXN0TXBlZ0F1ZGlvQ2h1bmsgfHwgY2h1bmtNZXRhLmlkID09PSAxIHx8IHRoaXMubGFzdE1wZWdBdWRpb0NodW5rLnNuICE9PSBjaHVua01ldGEuc247XG4gICAgICB0aGlzLmxhc3RNcGVnQXVkaW9DaHVuayA9IGNodW5rTWV0YTtcbiAgICB9XG4gICAgY29uc3QgZnJhZ1N0YXJ0ID0gZnJhZy5zdGFydDtcbiAgICBjb25zdCBvcGVyYXRpb24gPSB7XG4gICAgICBleGVjdXRlOiAoKSA9PiB7XG4gICAgICAgIGNodW5rU3RhdHMuZXhlY3V0ZVN0YXJ0ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgaWYgKGNoZWNrVGltZXN0YW1wT2Zmc2V0KSB7XG4gICAgICAgICAgY29uc3Qgc2IgPSB0aGlzLnNvdXJjZUJ1ZmZlclt0eXBlXTtcbiAgICAgICAgICBpZiAoc2IpIHtcbiAgICAgICAgICAgIGNvbnN0IGRlbHRhID0gZnJhZ1N0YXJ0IC0gc2IudGltZXN0YW1wT2Zmc2V0O1xuICAgICAgICAgICAgaWYgKE1hdGguYWJzKGRlbHRhKSA+PSAwLjEpIHtcbiAgICAgICAgICAgICAgdGhpcy5sb2coYFVwZGF0aW5nIGF1ZGlvIFNvdXJjZUJ1ZmZlciB0aW1lc3RhbXBPZmZzZXQgdG8gJHtmcmFnU3RhcnR9IChkZWx0YTogJHtkZWx0YX0pIHNuOiAke2ZyYWcuc259KWApO1xuICAgICAgICAgICAgICBzYi50aW1lc3RhbXBPZmZzZXQgPSBmcmFnU3RhcnQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHRoaXMuYXBwZW5kRXhlY3V0b3IoZGF0YSwgdHlwZSk7XG4gICAgICB9LFxuICAgICAgb25TdGFydDogKCkgPT4ge1xuICAgICAgICAvLyBsb2dnZXIuZGVidWcoYFtidWZmZXItY29udHJvbGxlcl06ICR7dHlwZX0gU291cmNlQnVmZmVyIHVwZGF0ZXN0YXJ0YCk7XG4gICAgICB9LFxuICAgICAgb25Db21wbGV0ZTogKCkgPT4ge1xuICAgICAgICAvLyBsb2dnZXIuZGVidWcoYFtidWZmZXItY29udHJvbGxlcl06ICR7dHlwZX0gU291cmNlQnVmZmVyIHVwZGF0ZWVuZGApO1xuICAgICAgICBjb25zdCBlbmQgPSBzZWxmLnBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICBjaHVua1N0YXRzLmV4ZWN1dGVFbmQgPSBjaHVua1N0YXRzLmVuZCA9IGVuZDtcbiAgICAgICAgaWYgKGZyYWdCdWZmZXJpbmcuZmlyc3QgPT09IDApIHtcbiAgICAgICAgICBmcmFnQnVmZmVyaW5nLmZpcnN0ID0gZW5kO1xuICAgICAgICB9XG4gICAgICAgIGlmIChwYXJ0QnVmZmVyaW5nICYmIHBhcnRCdWZmZXJpbmcuZmlyc3QgPT09IDApIHtcbiAgICAgICAgICBwYXJ0QnVmZmVyaW5nLmZpcnN0ID0gZW5kO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHtcbiAgICAgICAgICBzb3VyY2VCdWZmZXJcbiAgICAgICAgfSA9IHRoaXM7XG4gICAgICAgIGNvbnN0IHRpbWVSYW5nZXMgPSB7fTtcbiAgICAgICAgZm9yIChjb25zdCB0eXBlIGluIHNvdXJjZUJ1ZmZlcikge1xuICAgICAgICAgIHRpbWVSYW5nZXNbdHlwZV0gPSBCdWZmZXJIZWxwZXIuZ2V0QnVmZmVyZWQoc291cmNlQnVmZmVyW3R5cGVdKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLmFwcGVuZEVycm9yc1t0eXBlXSA9IDA7XG4gICAgICAgIGlmICh0eXBlID09PSAnYXVkaW8nIHx8IHR5cGUgPT09ICd2aWRlbycpIHtcbiAgICAgICAgICB0aGlzLmFwcGVuZEVycm9ycy5hdWRpb3ZpZGVvID0gMDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLmFwcGVuZEVycm9ycy5hdWRpbyA9IDA7XG4gICAgICAgICAgdGhpcy5hcHBlbmRFcnJvcnMudmlkZW8gPSAwO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkJVRkZFUl9BUFBFTkRFRCwge1xuICAgICAgICAgIHR5cGUsXG4gICAgICAgICAgZnJhZyxcbiAgICAgICAgICBwYXJ0LFxuICAgICAgICAgIGNodW5rTWV0YSxcbiAgICAgICAgICBwYXJlbnQ6IGZyYWcudHlwZSxcbiAgICAgICAgICB0aW1lUmFuZ2VzXG4gICAgICAgIH0pO1xuICAgICAgfSxcbiAgICAgIG9uRXJyb3I6IGVycm9yID0+IHtcbiAgICAgICAgLy8gaW4gY2FzZSBhbnkgZXJyb3Igb2NjdXJlZCB3aGlsZSBhcHBlbmRpbmcsIHB1dCBiYWNrIHNlZ21lbnQgaW4gc2VnbWVudHMgdGFibGVcbiAgICAgICAgY29uc3QgZXZlbnQgPSB7XG4gICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5NRURJQV9FUlJPUixcbiAgICAgICAgICBwYXJlbnQ6IGZyYWcudHlwZSxcbiAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQlVGRkVSX0FQUEVORF9FUlJPUixcbiAgICAgICAgICBzb3VyY2VCdWZmZXJOYW1lOiB0eXBlLFxuICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgcGFydCxcbiAgICAgICAgICBjaHVua01ldGEsXG4gICAgICAgICAgZXJyb3IsXG4gICAgICAgICAgZXJyOiBlcnJvcixcbiAgICAgICAgICBmYXRhbDogZmFsc2VcbiAgICAgICAgfTtcbiAgICAgICAgaWYgKGVycm9yLmNvZGUgPT09IERPTUV4Y2VwdGlvbi5RVU9UQV9FWENFRURFRF9FUlIpIHtcbiAgICAgICAgICAvLyBRdW90YUV4Y2VlZGVkRXJyb3I6IGh0dHA6Ly93d3cudzMub3JnL1RSL2h0bWw1L2luZnJhc3RydWN0dXJlLmh0bWwjcXVvdGFleGNlZWRlZGVycm9yXG4gICAgICAgICAgLy8gbGV0J3Mgc3RvcCBhcHBlbmRpbmcgYW55IHNlZ21lbnRzLCBhbmQgcmVwb3J0IEJVRkZFUl9GVUxMX0VSUk9SIGVycm9yXG4gICAgICAgICAgZXZlbnQuZGV0YWlscyA9IEVycm9yRGV0YWlscy5CVUZGRVJfRlVMTF9FUlJPUjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBjb25zdCBhcHBlbmRFcnJvckNvdW50ID0gKyt0aGlzLmFwcGVuZEVycm9yc1t0eXBlXTtcbiAgICAgICAgICBldmVudC5kZXRhaWxzID0gRXJyb3JEZXRhaWxzLkJVRkZFUl9BUFBFTkRfRVJST1I7XG4gICAgICAgICAgLyogd2l0aCBVSEQgY29udGVudCwgd2UgY291bGQgZ2V0IGxvb3Agb2YgcXVvdGEgZXhjZWVkZWQgZXJyb3IgdW50aWxcbiAgICAgICAgICAgIGJyb3dzZXIgaXMgYWJsZSB0byBldmljdCBzb21lIGRhdGEgZnJvbSBzb3VyY2VidWZmZXIuIFJldHJ5aW5nIGNhbiBoZWxwIHJlY292ZXIuXG4gICAgICAgICAgKi9cbiAgICAgICAgICB0aGlzLndhcm4oYEZhaWxlZCAke2FwcGVuZEVycm9yQ291bnR9LyR7aGxzLmNvbmZpZy5hcHBlbmRFcnJvck1heFJldHJ5fSB0aW1lcyB0byBhcHBlbmQgc2VnbWVudCBpbiBcIiR7dHlwZX1cIiBzb3VyY2VCdWZmZXJgKTtcbiAgICAgICAgICBpZiAoYXBwZW5kRXJyb3JDb3VudCA+PSBobHMuY29uZmlnLmFwcGVuZEVycm9yTWF4UmV0cnkpIHtcbiAgICAgICAgICAgIGV2ZW50LmZhdGFsID0gdHJ1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCBldmVudCk7XG4gICAgICB9XG4gICAgfTtcbiAgICBvcGVyYXRpb25RdWV1ZS5hcHBlbmQob3BlcmF0aW9uLCB0eXBlLCAhIXRoaXMucGVuZGluZ1RyYWNrc1t0eXBlXSk7XG4gIH1cbiAgb25CdWZmZXJGbHVzaGluZyhldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIG9wZXJhdGlvblF1ZXVlXG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3QgZmx1c2hPcGVyYXRpb24gPSB0eXBlID0+ICh7XG4gICAgICBleGVjdXRlOiB0aGlzLnJlbW92ZUV4ZWN1dG9yLmJpbmQodGhpcywgdHlwZSwgZGF0YS5zdGFydE9mZnNldCwgZGF0YS5lbmRPZmZzZXQpLFxuICAgICAgb25TdGFydDogKCkgPT4ge1xuICAgICAgICAvLyBsb2dnZXIuZGVidWcoYFtidWZmZXItY29udHJvbGxlcl06IFN0YXJ0ZWQgZmx1c2hpbmcgJHtkYXRhLnN0YXJ0T2Zmc2V0fSAtPiAke2RhdGEuZW5kT2Zmc2V0fSBmb3IgJHt0eXBlfSBTb3VyY2UgQnVmZmVyYCk7XG4gICAgICB9LFxuICAgICAgb25Db21wbGV0ZTogKCkgPT4ge1xuICAgICAgICAvLyBsb2dnZXIuZGVidWcoYFtidWZmZXItY29udHJvbGxlcl06IEZpbmlzaGVkIGZsdXNoaW5nICR7ZGF0YS5zdGFydE9mZnNldH0gLT4gJHtkYXRhLmVuZE9mZnNldH0gZm9yICR7dHlwZX0gU291cmNlIEJ1ZmZlcmApO1xuICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfRkxVU0hFRCwge1xuICAgICAgICAgIHR5cGVcbiAgICAgICAgfSk7XG4gICAgICB9LFxuICAgICAgb25FcnJvcjogZXJyb3IgPT4ge1xuICAgICAgICB0aGlzLndhcm4oYEZhaWxlZCB0byByZW1vdmUgZnJvbSAke3R5cGV9IFNvdXJjZUJ1ZmZlcmAsIGVycm9yKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICBpZiAoZGF0YS50eXBlKSB7XG4gICAgICBvcGVyYXRpb25RdWV1ZS5hcHBlbmQoZmx1c2hPcGVyYXRpb24oZGF0YS50eXBlKSwgZGF0YS50eXBlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5nZXRTb3VyY2VCdWZmZXJUeXBlcygpLmZvckVhY2godHlwZSA9PiB7XG4gICAgICAgIG9wZXJhdGlvblF1ZXVlLmFwcGVuZChmbHVzaE9wZXJhdGlvbih0eXBlKSwgdHlwZSk7XG4gICAgICB9KTtcbiAgICB9XG4gIH1cbiAgb25GcmFnUGFyc2VkKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZyxcbiAgICAgIHBhcnRcbiAgICB9ID0gZGF0YTtcbiAgICBjb25zdCBidWZmZXJzQXBwZW5kZWRUbyA9IFtdO1xuICAgIGNvbnN0IGVsZW1lbnRhcnlTdHJlYW1zID0gcGFydCA/IHBhcnQuZWxlbWVudGFyeVN0cmVhbXMgOiBmcmFnLmVsZW1lbnRhcnlTdHJlYW1zO1xuICAgIGlmIChlbGVtZW50YXJ5U3RyZWFtc1tFbGVtZW50YXJ5U3RyZWFtVHlwZXMuQVVESU9WSURFT10pIHtcbiAgICAgIGJ1ZmZlcnNBcHBlbmRlZFRvLnB1c2goJ2F1ZGlvdmlkZW8nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgaWYgKGVsZW1lbnRhcnlTdHJlYW1zW0VsZW1lbnRhcnlTdHJlYW1UeXBlcy5BVURJT10pIHtcbiAgICAgICAgYnVmZmVyc0FwcGVuZGVkVG8ucHVzaCgnYXVkaW8nKTtcbiAgICAgIH1cbiAgICAgIGlmIChlbGVtZW50YXJ5U3RyZWFtc1tFbGVtZW50YXJ5U3RyZWFtVHlwZXMuVklERU9dKSB7XG4gICAgICAgIGJ1ZmZlcnNBcHBlbmRlZFRvLnB1c2goJ3ZpZGVvJyk7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IG9uVW5ibG9ja2VkID0gKCkgPT4ge1xuICAgICAgY29uc3Qgbm93ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgIGZyYWcuc3RhdHMuYnVmZmVyaW5nLmVuZCA9IG5vdztcbiAgICAgIGlmIChwYXJ0KSB7XG4gICAgICAgIHBhcnQuc3RhdHMuYnVmZmVyaW5nLmVuZCA9IG5vdztcbiAgICAgIH1cbiAgICAgIGNvbnN0IHN0YXRzID0gcGFydCA/IHBhcnQuc3RhdHMgOiBmcmFnLnN0YXRzO1xuICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRlJBR19CVUZGRVJFRCwge1xuICAgICAgICBmcmFnLFxuICAgICAgICBwYXJ0LFxuICAgICAgICBzdGF0cyxcbiAgICAgICAgaWQ6IGZyYWcudHlwZVxuICAgICAgfSk7XG4gICAgfTtcbiAgICBpZiAoYnVmZmVyc0FwcGVuZGVkVG8ubGVuZ3RoID09PSAwKSB7XG4gICAgICB0aGlzLndhcm4oYEZyYWdtZW50cyBtdXN0IGhhdmUgYXQgbGVhc3Qgb25lIEVsZW1lbnRhcnlTdHJlYW1UeXBlIHNldC4gdHlwZTogJHtmcmFnLnR5cGV9IGxldmVsOiAke2ZyYWcubGV2ZWx9IHNuOiAke2ZyYWcuc259YCk7XG4gICAgfVxuICAgIHRoaXMuYmxvY2tCdWZmZXJzKG9uVW5ibG9ja2VkLCBidWZmZXJzQXBwZW5kZWRUbyk7XG4gIH1cbiAgb25GcmFnQ2hhbmdlZChldmVudCwgZGF0YSkge1xuICAgIHRoaXMudHJpbUJ1ZmZlcnMoKTtcbiAgfVxuXG4gIC8vIG9uIEJVRkZFUl9FT1MgbWFyayBtYXRjaGluZyBzb3VyY2VidWZmZXIocykgYXMgZW5kZWQgYW5kIHRyaWdnZXIgY2hlY2tFb3MoKVxuICAvLyBhbiB1bmRlZmluZWQgZGF0YS50eXBlIHdpbGwgbWFyayBhbGwgYnVmZmVycyBhcyBFT1MuXG4gIG9uQnVmZmVyRW9zKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgZW5kZWQgPSB0aGlzLmdldFNvdXJjZUJ1ZmZlclR5cGVzKCkucmVkdWNlKChhY2MsIHR5cGUpID0+IHtcbiAgICAgIGNvbnN0IHNiID0gdGhpcy5zb3VyY2VCdWZmZXJbdHlwZV07XG4gICAgICBpZiAoc2IgJiYgKCFkYXRhLnR5cGUgfHwgZGF0YS50eXBlID09PSB0eXBlKSkge1xuICAgICAgICBzYi5lbmRpbmcgPSB0cnVlO1xuICAgICAgICBpZiAoIXNiLmVuZGVkKSB7XG4gICAgICAgICAgc2IuZW5kZWQgPSB0cnVlO1xuICAgICAgICAgIHRoaXMubG9nKGAke3R5cGV9IHNvdXJjZUJ1ZmZlciBub3cgRU9TYCk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBhY2MgJiYgISEoIXNiIHx8IHNiLmVuZGVkKTtcbiAgICB9LCB0cnVlKTtcbiAgICBpZiAoZW5kZWQpIHtcbiAgICAgIHRoaXMubG9nKGBRdWV1ZWluZyBtZWRpYVNvdXJjZS5lbmRPZlN0cmVhbSgpYCk7XG4gICAgICB0aGlzLmJsb2NrQnVmZmVycygoKSA9PiB7XG4gICAgICAgIHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKS5mb3JFYWNoKHR5cGUgPT4ge1xuICAgICAgICAgIGNvbnN0IHNiID0gdGhpcy5zb3VyY2VCdWZmZXJbdHlwZV07XG4gICAgICAgICAgaWYgKHNiKSB7XG4gICAgICAgICAgICBzYi5lbmRpbmcgPSBmYWxzZTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICBjb25zdCB7XG4gICAgICAgICAgbWVkaWFTb3VyY2VcbiAgICAgICAgfSA9IHRoaXM7XG4gICAgICAgIGlmICghbWVkaWFTb3VyY2UgfHwgbWVkaWFTb3VyY2UucmVhZHlTdGF0ZSAhPT0gJ29wZW4nKSB7XG4gICAgICAgICAgaWYgKG1lZGlhU291cmNlKSB7XG4gICAgICAgICAgICB0aGlzLmxvZyhgQ291bGQgbm90IGNhbGwgbWVkaWFTb3VyY2UuZW5kT2ZTdHJlYW0oKS4gbWVkaWFTb3VyY2UucmVhZHlTdGF0ZTogJHttZWRpYVNvdXJjZS5yZWFkeVN0YXRlfWApO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5sb2coYENhbGxpbmcgbWVkaWFTb3VyY2UuZW5kT2ZTdHJlYW0oKWApO1xuICAgICAgICAvLyBBbGxvdyB0aGlzIHRvIHRocm93IGFuZCBiZSBjYXVnaHQgYnkgdGhlIGVucXVldWVpbmcgZnVuY3Rpb25cbiAgICAgICAgbWVkaWFTb3VyY2UuZW5kT2ZTdHJlYW0oKTtcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICBvbkxldmVsVXBkYXRlZChldmVudCwge1xuICAgIGRldGFpbHNcbiAgfSkge1xuICAgIGlmICghZGV0YWlscy5mcmFnbWVudHMubGVuZ3RoKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuZGV0YWlscyA9IGRldGFpbHM7XG4gICAgaWYgKHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKS5sZW5ndGgpIHtcbiAgICAgIHRoaXMuYmxvY2tCdWZmZXJzKHRoaXMudXBkYXRlTWVkaWFFbGVtZW50RHVyYXRpb24uYmluZCh0aGlzKSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMudXBkYXRlTWVkaWFFbGVtZW50RHVyYXRpb24oKTtcbiAgICB9XG4gIH1cbiAgdHJpbUJ1ZmZlcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzLFxuICAgICAgZGV0YWlscyxcbiAgICAgIG1lZGlhXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFtZWRpYSB8fCBkZXRhaWxzID09PSBudWxsKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHNvdXJjZUJ1ZmZlclR5cGVzID0gdGhpcy5nZXRTb3VyY2VCdWZmZXJUeXBlcygpO1xuICAgIGlmICghc291cmNlQnVmZmVyVHlwZXMubGVuZ3RoKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGNvbmZpZyA9IGhscy5jb25maWc7XG4gICAgY29uc3QgY3VycmVudFRpbWUgPSBtZWRpYS5jdXJyZW50VGltZTtcbiAgICBjb25zdCB0YXJnZXREdXJhdGlvbiA9IGRldGFpbHMubGV2ZWxUYXJnZXREdXJhdGlvbjtcblxuICAgIC8vIFN1cHBvcnQgZm9yIGRlcHJlY2F0ZWQgbGl2ZUJhY2tCdWZmZXJMZW5ndGhcbiAgICBjb25zdCBiYWNrQnVmZmVyTGVuZ3RoID0gZGV0YWlscy5saXZlICYmIGNvbmZpZy5saXZlQmFja0J1ZmZlckxlbmd0aCAhPT0gbnVsbCA/IGNvbmZpZy5saXZlQmFja0J1ZmZlckxlbmd0aCA6IGNvbmZpZy5iYWNrQnVmZmVyTGVuZ3RoO1xuICAgIGlmIChpc0Zpbml0ZU51bWJlcihiYWNrQnVmZmVyTGVuZ3RoKSAmJiBiYWNrQnVmZmVyTGVuZ3RoID4gMCkge1xuICAgICAgY29uc3QgbWF4QmFja0J1ZmZlckxlbmd0aCA9IE1hdGgubWF4KGJhY2tCdWZmZXJMZW5ndGgsIHRhcmdldER1cmF0aW9uKTtcbiAgICAgIGNvbnN0IHRhcmdldEJhY2tCdWZmZXJQb3NpdGlvbiA9IE1hdGguZmxvb3IoY3VycmVudFRpbWUgLyB0YXJnZXREdXJhdGlvbikgKiB0YXJnZXREdXJhdGlvbiAtIG1heEJhY2tCdWZmZXJMZW5ndGg7XG4gICAgICB0aGlzLmZsdXNoQmFja0J1ZmZlcihjdXJyZW50VGltZSwgdGFyZ2V0RHVyYXRpb24sIHRhcmdldEJhY2tCdWZmZXJQb3NpdGlvbik7XG4gICAgfVxuICAgIGlmIChpc0Zpbml0ZU51bWJlcihjb25maWcuZnJvbnRCdWZmZXJGbHVzaFRocmVzaG9sZCkgJiYgY29uZmlnLmZyb250QnVmZmVyRmx1c2hUaHJlc2hvbGQgPiAwKSB7XG4gICAgICBjb25zdCBmcm9udEJ1ZmZlckxlbmd0aCA9IE1hdGgubWF4KGNvbmZpZy5tYXhCdWZmZXJMZW5ndGgsIGNvbmZpZy5mcm9udEJ1ZmZlckZsdXNoVGhyZXNob2xkKTtcbiAgICAgIGNvbnN0IG1heEZyb250QnVmZmVyTGVuZ3RoID0gTWF0aC5tYXgoZnJvbnRCdWZmZXJMZW5ndGgsIHRhcmdldER1cmF0aW9uKTtcbiAgICAgIGNvbnN0IHRhcmdldEZyb250QnVmZmVyUG9zaXRpb24gPSBNYXRoLmZsb29yKGN1cnJlbnRUaW1lIC8gdGFyZ2V0RHVyYXRpb24pICogdGFyZ2V0RHVyYXRpb24gKyBtYXhGcm9udEJ1ZmZlckxlbmd0aDtcbiAgICAgIHRoaXMuZmx1c2hGcm9udEJ1ZmZlcihjdXJyZW50VGltZSwgdGFyZ2V0RHVyYXRpb24sIHRhcmdldEZyb250QnVmZmVyUG9zaXRpb24pO1xuICAgIH1cbiAgfVxuICBmbHVzaEJhY2tCdWZmZXIoY3VycmVudFRpbWUsIHRhcmdldER1cmF0aW9uLCB0YXJnZXRCYWNrQnVmZmVyUG9zaXRpb24pIHtcbiAgICBjb25zdCB7XG4gICAgICBkZXRhaWxzLFxuICAgICAgc291cmNlQnVmZmVyXG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3Qgc291cmNlQnVmZmVyVHlwZXMgPSB0aGlzLmdldFNvdXJjZUJ1ZmZlclR5cGVzKCk7XG4gICAgc291cmNlQnVmZmVyVHlwZXMuZm9yRWFjaCh0eXBlID0+IHtcbiAgICAgIGNvbnN0IHNiID0gc291cmNlQnVmZmVyW3R5cGVdO1xuICAgICAgaWYgKHNiKSB7XG4gICAgICAgIGNvbnN0IGJ1ZmZlcmVkID0gQnVmZmVySGVscGVyLmdldEJ1ZmZlcmVkKHNiKTtcbiAgICAgICAgLy8gd2hlbiB0YXJnZXQgYnVmZmVyIHN0YXJ0IGV4Y2VlZHMgYWN0dWFsIGJ1ZmZlciBzdGFydFxuICAgICAgICBpZiAoYnVmZmVyZWQubGVuZ3RoID4gMCAmJiB0YXJnZXRCYWNrQnVmZmVyUG9zaXRpb24gPiBidWZmZXJlZC5zdGFydCgwKSkge1xuICAgICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkJBQ0tfQlVGRkVSX1JFQUNIRUQsIHtcbiAgICAgICAgICAgIGJ1ZmZlckVuZDogdGFyZ2V0QmFja0J1ZmZlclBvc2l0aW9uXG4gICAgICAgICAgfSk7XG5cbiAgICAgICAgICAvLyBTdXBwb3J0IGZvciBkZXByZWNhdGVkIGV2ZW50OlxuICAgICAgICAgIGlmIChkZXRhaWxzICE9IG51bGwgJiYgZGV0YWlscy5saXZlKSB7XG4gICAgICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5MSVZFX0JBQ0tfQlVGRkVSX1JFQUNIRUQsIHtcbiAgICAgICAgICAgICAgYnVmZmVyRW5kOiB0YXJnZXRCYWNrQnVmZmVyUG9zaXRpb25cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH0gZWxzZSBpZiAoc2IuZW5kZWQgJiYgYnVmZmVyZWQuZW5kKGJ1ZmZlcmVkLmxlbmd0aCAtIDEpIC0gY3VycmVudFRpbWUgPCB0YXJnZXREdXJhdGlvbiAqIDIpIHtcbiAgICAgICAgICAgIHRoaXMubG9nKGBDYW5ub3QgZmx1c2ggJHt0eXBlfSBiYWNrIGJ1ZmZlciB3aGlsZSBTb3VyY2VCdWZmZXIgaXMgaW4gZW5kZWQgc3RhdGVgKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuQlVGRkVSX0ZMVVNISU5HLCB7XG4gICAgICAgICAgICBzdGFydE9mZnNldDogMCxcbiAgICAgICAgICAgIGVuZE9mZnNldDogdGFyZ2V0QmFja0J1ZmZlclBvc2l0aW9uLFxuICAgICAgICAgICAgdHlwZVxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbiAgZmx1c2hGcm9udEJ1ZmZlcihjdXJyZW50VGltZSwgdGFyZ2V0RHVyYXRpb24sIHRhcmdldEZyb250QnVmZmVyUG9zaXRpb24pIHtcbiAgICBjb25zdCB7XG4gICAgICBzb3VyY2VCdWZmZXJcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCBzb3VyY2VCdWZmZXJUeXBlcyA9IHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKTtcbiAgICBzb3VyY2VCdWZmZXJUeXBlcy5mb3JFYWNoKHR5cGUgPT4ge1xuICAgICAgY29uc3Qgc2IgPSBzb3VyY2VCdWZmZXJbdHlwZV07XG4gICAgICBpZiAoc2IpIHtcbiAgICAgICAgY29uc3QgYnVmZmVyZWQgPSBCdWZmZXJIZWxwZXIuZ2V0QnVmZmVyZWQoc2IpO1xuICAgICAgICBjb25zdCBudW1CdWZmZXJlZFJhbmdlcyA9IGJ1ZmZlcmVkLmxlbmd0aDtcbiAgICAgICAgLy8gVGhlIGJ1ZmZlciBpcyBlaXRoZXIgZW1wdHkgb3IgY29udGlndW91c1xuICAgICAgICBpZiAobnVtQnVmZmVyZWRSYW5nZXMgPCAyKSB7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGJ1ZmZlclN0YXJ0ID0gYnVmZmVyZWQuc3RhcnQobnVtQnVmZmVyZWRSYW5nZXMgLSAxKTtcbiAgICAgICAgY29uc3QgYnVmZmVyRW5kID0gYnVmZmVyZWQuZW5kKG51bUJ1ZmZlcmVkUmFuZ2VzIC0gMSk7XG4gICAgICAgIC8vIE5vIGZsdXNoIGlmIHdlIGNhbiB0b2xlcmF0ZSB0aGUgY3VycmVudCBidWZmZXIgbGVuZ3RoIG9yIHRoZSBjdXJyZW50IGJ1ZmZlciByYW5nZSB3ZSB3b3VsZCBmbHVzaCBpcyBjb250aWd1b3VzIHdpdGggY3VycmVudCBwb3NpdGlvblxuICAgICAgICBpZiAodGFyZ2V0RnJvbnRCdWZmZXJQb3NpdGlvbiA+IGJ1ZmZlclN0YXJ0IHx8IGN1cnJlbnRUaW1lID49IGJ1ZmZlclN0YXJ0ICYmIGN1cnJlbnRUaW1lIDw9IGJ1ZmZlckVuZCkge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfSBlbHNlIGlmIChzYi5lbmRlZCAmJiBjdXJyZW50VGltZSAtIGJ1ZmZlckVuZCA8IDIgKiB0YXJnZXREdXJhdGlvbikge1xuICAgICAgICAgIHRoaXMubG9nKGBDYW5ub3QgZmx1c2ggJHt0eXBlfSBmcm9udCBidWZmZXIgd2hpbGUgU291cmNlQnVmZmVyIGlzIGluIGVuZGVkIHN0YXRlYCk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkJVRkZFUl9GTFVTSElORywge1xuICAgICAgICAgIHN0YXJ0T2Zmc2V0OiBidWZmZXJTdGFydCxcbiAgICAgICAgICBlbmRPZmZzZXQ6IEluZmluaXR5LFxuICAgICAgICAgIHR5cGVcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogVXBkYXRlIE1lZGlhIFNvdXJjZSBkdXJhdGlvbiB0byBjdXJyZW50IGxldmVsIGR1cmF0aW9uIG9yIG92ZXJyaWRlIHRvIEluZmluaXR5IGlmIGNvbmZpZ3VyYXRpb24gcGFyYW1ldGVyXG4gICAqICdsaXZlRHVyYXRpb25JbmZpbml0eWAgaXMgc2V0IHRvIGB0cnVlYFxuICAgKiBNb3JlIGRldGFpbHM6IGh0dHBzOi8vZ2l0aHViLmNvbS92aWRlby1kZXYvaGxzLmpzL2lzc3Vlcy8zNTVcbiAgICovXG4gIHVwZGF0ZU1lZGlhRWxlbWVudER1cmF0aW9uKCkge1xuICAgIGlmICghdGhpcy5kZXRhaWxzIHx8ICF0aGlzLm1lZGlhIHx8ICF0aGlzLm1lZGlhU291cmNlIHx8IHRoaXMubWVkaWFTb3VyY2UucmVhZHlTdGF0ZSAhPT0gJ29wZW4nKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGRldGFpbHMsXG4gICAgICBobHMsXG4gICAgICBtZWRpYSxcbiAgICAgIG1lZGlhU291cmNlXG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3QgbGV2ZWxEdXJhdGlvbiA9IGRldGFpbHMuZnJhZ21lbnRzWzBdLnN0YXJ0ICsgZGV0YWlscy50b3RhbGR1cmF0aW9uO1xuICAgIGNvbnN0IG1lZGlhRHVyYXRpb24gPSBtZWRpYS5kdXJhdGlvbjtcbiAgICBjb25zdCBtc0R1cmF0aW9uID0gaXNGaW5pdGVOdW1iZXIobWVkaWFTb3VyY2UuZHVyYXRpb24pID8gbWVkaWFTb3VyY2UuZHVyYXRpb24gOiAwO1xuICAgIGlmIChkZXRhaWxzLmxpdmUgJiYgaGxzLmNvbmZpZy5saXZlRHVyYXRpb25JbmZpbml0eSkge1xuICAgICAgLy8gT3ZlcnJpZGUgZHVyYXRpb24gdG8gSW5maW5pdHlcbiAgICAgIG1lZGlhU291cmNlLmR1cmF0aW9uID0gSW5maW5pdHk7XG4gICAgICB0aGlzLnVwZGF0ZVNlZWthYmxlUmFuZ2UoZGV0YWlscyk7XG4gICAgfSBlbHNlIGlmIChsZXZlbER1cmF0aW9uID4gbXNEdXJhdGlvbiAmJiBsZXZlbER1cmF0aW9uID4gbWVkaWFEdXJhdGlvbiB8fCAhaXNGaW5pdGVOdW1iZXIobWVkaWFEdXJhdGlvbikpIHtcbiAgICAgIC8vIGxldmVsRHVyYXRpb24gd2FzIHRoZSBsYXN0IHZhbHVlIHdlIHNldC5cbiAgICAgIC8vIG5vdCB1c2luZyBtZWRpYVNvdXJjZS5kdXJhdGlvbiBhcyB0aGUgYnJvd3NlciBtYXkgdHdlYWsgdGhpcyB2YWx1ZVxuICAgICAgLy8gb25seSB1cGRhdGUgTWVkaWEgU291cmNlIGR1cmF0aW9uIGlmIGl0cyB2YWx1ZSBpbmNyZWFzZSwgdGhpcyBpcyB0byBhdm9pZFxuICAgICAgLy8gZmx1c2hpbmcgYWxyZWFkeSBidWZmZXJlZCBwb3J0aW9uIHdoZW4gc3dpdGNoaW5nIGJldHdlZW4gcXVhbGl0eSBsZXZlbFxuICAgICAgdGhpcy5sb2coYFVwZGF0aW5nIE1lZGlhIFNvdXJjZSBkdXJhdGlvbiB0byAke2xldmVsRHVyYXRpb24udG9GaXhlZCgzKX1gKTtcbiAgICAgIG1lZGlhU291cmNlLmR1cmF0aW9uID0gbGV2ZWxEdXJhdGlvbjtcbiAgICB9XG4gIH1cbiAgdXBkYXRlU2Vla2FibGVSYW5nZShsZXZlbERldGFpbHMpIHtcbiAgICBjb25zdCBtZWRpYVNvdXJjZSA9IHRoaXMubWVkaWFTb3VyY2U7XG4gICAgY29uc3QgZnJhZ21lbnRzID0gbGV2ZWxEZXRhaWxzLmZyYWdtZW50cztcbiAgICBjb25zdCBsZW4gPSBmcmFnbWVudHMubGVuZ3RoO1xuICAgIGlmIChsZW4gJiYgbGV2ZWxEZXRhaWxzLmxpdmUgJiYgbWVkaWFTb3VyY2UgIT0gbnVsbCAmJiBtZWRpYVNvdXJjZS5zZXRMaXZlU2Vla2FibGVSYW5nZSkge1xuICAgICAgY29uc3Qgc3RhcnQgPSBNYXRoLm1heCgwLCBmcmFnbWVudHNbMF0uc3RhcnQpO1xuICAgICAgY29uc3QgZW5kID0gTWF0aC5tYXgoc3RhcnQsIHN0YXJ0ICsgbGV2ZWxEZXRhaWxzLnRvdGFsZHVyYXRpb24pO1xuICAgICAgdGhpcy5sb2coYE1lZGlhIFNvdXJjZSBkdXJhdGlvbiBpcyBzZXQgdG8gJHttZWRpYVNvdXJjZS5kdXJhdGlvbn0uIFNldHRpbmcgc2Vla2FibGUgcmFuZ2UgdG8gJHtzdGFydH0tJHtlbmR9LmApO1xuICAgICAgbWVkaWFTb3VyY2Uuc2V0TGl2ZVNlZWthYmxlUmFuZ2Uoc3RhcnQsIGVuZCk7XG4gICAgfVxuICB9XG4gIGNoZWNrUGVuZGluZ1RyYWNrcygpIHtcbiAgICBjb25zdCB7XG4gICAgICBidWZmZXJDb2RlY0V2ZW50c0V4cGVjdGVkLFxuICAgICAgb3BlcmF0aW9uUXVldWUsXG4gICAgICBwZW5kaW5nVHJhY2tzXG4gICAgfSA9IHRoaXM7XG5cbiAgICAvLyBDaGVjayBpZiB3ZSd2ZSByZWNlaXZlZCBhbGwgb2YgdGhlIGV4cGVjdGVkIGJ1ZmZlckNvZGVjIGV2ZW50cy4gV2hlbiBub25lIHJlbWFpbiwgY3JlYXRlIGFsbCB0aGUgc291cmNlQnVmZmVycyBhdCBvbmNlLlxuICAgIC8vIFRoaXMgaXMgaW1wb3J0YW50IGJlY2F1c2UgdGhlIE1TRSBzcGVjIGFsbG93cyBpbXBsZW1lbnRhdGlvbnMgdG8gdGhyb3cgUXVvdGFFeGNlZWRlZEVycm9ycyBpZiBjcmVhdGluZyBuZXcgc291cmNlQnVmZmVycyBhZnRlclxuICAgIC8vIGRhdGEgaGFzIGJlZW4gYXBwZW5kZWQgdG8gZXhpc3Rpbmcgb25lcy5cbiAgICAvLyAyIHRyYWNrcyBpcyB0aGUgbWF4IChvbmUgZm9yIGF1ZGlvLCBvbmUgZm9yIHZpZGVvKS4gSWYgd2UndmUgcmVhY2ggdGhpcyBtYXggZ28gYWhlYWQgYW5kIGNyZWF0ZSB0aGUgYnVmZmVycy5cbiAgICBjb25zdCBwZW5kaW5nVHJhY2tzQ291bnQgPSBPYmplY3Qua2V5cyhwZW5kaW5nVHJhY2tzKS5sZW5ndGg7XG4gICAgaWYgKHBlbmRpbmdUcmFja3NDb3VudCAmJiAoIWJ1ZmZlckNvZGVjRXZlbnRzRXhwZWN0ZWQgfHwgcGVuZGluZ1RyYWNrc0NvdW50ID09PSAyIHx8ICdhdWRpb3ZpZGVvJyBpbiBwZW5kaW5nVHJhY2tzKSkge1xuICAgICAgLy8gb2ssIGxldCdzIGNyZWF0ZSB0aGVtIG5vdyAhXG4gICAgICB0aGlzLmNyZWF0ZVNvdXJjZUJ1ZmZlcnMocGVuZGluZ1RyYWNrcyk7XG4gICAgICB0aGlzLnBlbmRpbmdUcmFja3MgPSB7fTtcbiAgICAgIC8vIGFwcGVuZCBhbnkgcGVuZGluZyBzZWdtZW50cyBub3cgIVxuICAgICAgY29uc3QgYnVmZmVycyA9IHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKTtcbiAgICAgIGlmIChidWZmZXJzLmxlbmd0aCkge1xuICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfQ1JFQVRFRCwge1xuICAgICAgICAgIHRyYWNrczogdGhpcy50cmFja3NcbiAgICAgICAgfSk7XG4gICAgICAgIGJ1ZmZlcnMuZm9yRWFjaCh0eXBlID0+IHtcbiAgICAgICAgICBvcGVyYXRpb25RdWV1ZS5leGVjdXRlTmV4dCh0eXBlKTtcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcignY291bGQgbm90IGNyZWF0ZSBzb3VyY2UgYnVmZmVyIGZvciBtZWRpYSBjb2RlYyhzKScpO1xuICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwge1xuICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLkJVRkZFUl9JTkNPTVBBVElCTEVfQ09ERUNTX0VSUk9SLFxuICAgICAgICAgIGZhdGFsOiB0cnVlLFxuICAgICAgICAgIGVycm9yLFxuICAgICAgICAgIHJlYXNvbjogZXJyb3IubWVzc2FnZVxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgY3JlYXRlU291cmNlQnVmZmVycyh0cmFja3MpIHtcbiAgICBjb25zdCB7XG4gICAgICBzb3VyY2VCdWZmZXIsXG4gICAgICBtZWRpYVNvdXJjZVxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghbWVkaWFTb3VyY2UpIHtcbiAgICAgIHRocm93IEVycm9yKCdjcmVhdGVTb3VyY2VCdWZmZXJzIGNhbGxlZCB3aGVuIG1lZGlhU291cmNlIHdhcyBudWxsJyk7XG4gICAgfVxuICAgIGZvciAoY29uc3QgdHJhY2tOYW1lIGluIHRyYWNrcykge1xuICAgICAgaWYgKCFzb3VyY2VCdWZmZXJbdHJhY2tOYW1lXSkge1xuICAgICAgICB2YXIgX3RyYWNrJGxldmVsQ29kZWM7XG4gICAgICAgIGNvbnN0IHRyYWNrID0gdHJhY2tzW3RyYWNrTmFtZV07XG4gICAgICAgIGlmICghdHJhY2spIHtcbiAgICAgICAgICB0aHJvdyBFcnJvcihgc291cmNlIGJ1ZmZlciBleGlzdHMgZm9yIHRyYWNrICR7dHJhY2tOYW1lfSwgaG93ZXZlciB0cmFjayBkb2VzIG5vdGApO1xuICAgICAgICB9XG4gICAgICAgIC8vIHVzZSBsZXZlbENvZGVjIGFzIGZpcnN0IHByaW9yaXR5IHVubGVzcyBpdCBjb250YWlucyBtdWx0aXBsZSBjb21tYS1zZXBhcmF0ZWQgY29kZWMgdmFsdWVzXG4gICAgICAgIGxldCBjb2RlYyA9ICgoX3RyYWNrJGxldmVsQ29kZWMgPSB0cmFjay5sZXZlbENvZGVjKSA9PSBudWxsID8gdm9pZCAwIDogX3RyYWNrJGxldmVsQ29kZWMuaW5kZXhPZignLCcpKSA9PT0gLTEgPyB0cmFjay5sZXZlbENvZGVjIDogdHJhY2suY29kZWM7XG4gICAgICAgIGlmIChjb2RlYykge1xuICAgICAgICAgIGlmICh0cmFja05hbWUuc2xpY2UoMCwgNSkgPT09ICdhdWRpbycpIHtcbiAgICAgICAgICAgIGNvZGVjID0gZ2V0Q29kZWNDb21wYXRpYmxlTmFtZShjb2RlYywgdGhpcy5hcHBlbmRTb3VyY2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBjb25zdCBtaW1lVHlwZSA9IGAke3RyYWNrLmNvbnRhaW5lcn07Y29kZWNzPSR7Y29kZWN9YDtcbiAgICAgICAgdGhpcy5sb2coYGNyZWF0aW5nIHNvdXJjZUJ1ZmZlcigke21pbWVUeXBlfSlgKTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCBzYiA9IHNvdXJjZUJ1ZmZlclt0cmFja05hbWVdID0gbWVkaWFTb3VyY2UuYWRkU291cmNlQnVmZmVyKG1pbWVUeXBlKTtcbiAgICAgICAgICBjb25zdCBzYk5hbWUgPSB0cmFja05hbWU7XG4gICAgICAgICAgdGhpcy5hZGRCdWZmZXJMaXN0ZW5lcihzYk5hbWUsICd1cGRhdGVzdGFydCcsIHRoaXMuX29uU0JVcGRhdGVTdGFydCk7XG4gICAgICAgICAgdGhpcy5hZGRCdWZmZXJMaXN0ZW5lcihzYk5hbWUsICd1cGRhdGVlbmQnLCB0aGlzLl9vblNCVXBkYXRlRW5kKTtcbiAgICAgICAgICB0aGlzLmFkZEJ1ZmZlckxpc3RlbmVyKHNiTmFtZSwgJ2Vycm9yJywgdGhpcy5fb25TQlVwZGF0ZUVycm9yKTtcbiAgICAgICAgICAvLyBNYW5hZ2VkU291cmNlQnVmZmVyIGJ1ZmZlcmVkY2hhbmdlIGV2ZW50XG4gICAgICAgICAgaWYgKHRoaXMuYXBwZW5kU291cmNlKSB7XG4gICAgICAgICAgICB0aGlzLmFkZEJ1ZmZlckxpc3RlbmVyKHNiTmFtZSwgJ2J1ZmZlcmVkY2hhbmdlJywgKHR5cGUsIGV2ZW50KSA9PiB7XG4gICAgICAgICAgICAgIC8vIElmIG1lZGlhIHdhcyBlamVjdGVkIGNoZWNrIGZvciBhIGNoYW5nZS4gQWRkZWQgcmFuZ2VzIGFyZSByZWR1bmRhbnQgd2l0aCBjaGFuZ2VzIG9uICd1cGRhdGVlbmQnIGV2ZW50LlxuICAgICAgICAgICAgICBjb25zdCByZW1vdmVkUmFuZ2VzID0gZXZlbnQucmVtb3ZlZFJhbmdlcztcbiAgICAgICAgICAgICAgaWYgKHJlbW92ZWRSYW5nZXMgIT0gbnVsbCAmJiByZW1vdmVkUmFuZ2VzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkJVRkZFUl9GTFVTSEVELCB7XG4gICAgICAgICAgICAgICAgICB0eXBlOiB0cmFja05hbWVcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRoaXMudHJhY2tzW3RyYWNrTmFtZV0gPSB7XG4gICAgICAgICAgICBidWZmZXI6IHNiLFxuICAgICAgICAgICAgY29kZWM6IGNvZGVjLFxuICAgICAgICAgICAgY29udGFpbmVyOiB0cmFjay5jb250YWluZXIsXG4gICAgICAgICAgICBsZXZlbENvZGVjOiB0cmFjay5sZXZlbENvZGVjLFxuICAgICAgICAgICAgbWV0YWRhdGE6IHRyYWNrLm1ldGFkYXRhLFxuICAgICAgICAgICAgaWQ6IHRyYWNrLmlkXG4gICAgICAgICAgfTtcbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgdGhpcy5lcnJvcihgZXJyb3Igd2hpbGUgdHJ5aW5nIHRvIGFkZCBzb3VyY2VCdWZmZXI6ICR7ZXJyLm1lc3NhZ2V9YCk7XG4gICAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQlVGRkVSX0FERF9DT0RFQ19FUlJPUixcbiAgICAgICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgICAgIGVycm9yOiBlcnIsXG4gICAgICAgICAgICBzb3VyY2VCdWZmZXJOYW1lOiB0cmFja05hbWUsXG4gICAgICAgICAgICBtaW1lVHlwZTogbWltZVR5cGVcbiAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICBnZXQgbWVkaWFTcmMoKSB7XG4gICAgdmFyIF90aGlzJG1lZGlhLCBfdGhpcyRtZWRpYSRxdWVyeVNlbGU7XG4gICAgY29uc3QgbWVkaWEgPSAoKF90aGlzJG1lZGlhID0gdGhpcy5tZWRpYSkgPT0gbnVsbCA/IHZvaWQgMCA6IChfdGhpcyRtZWRpYSRxdWVyeVNlbGUgPSBfdGhpcyRtZWRpYS5xdWVyeVNlbGVjdG9yKSA9PSBudWxsID8gdm9pZCAwIDogX3RoaXMkbWVkaWEkcXVlcnlTZWxlLmNhbGwoX3RoaXMkbWVkaWEsICdzb3VyY2UnKSkgfHwgdGhpcy5tZWRpYTtcbiAgICByZXR1cm4gbWVkaWEgPT0gbnVsbCA/IHZvaWQgMCA6IG1lZGlhLnNyYztcbiAgfVxuICBfb25TQlVwZGF0ZVN0YXJ0KHR5cGUpIHtcbiAgICBjb25zdCB7XG4gICAgICBvcGVyYXRpb25RdWV1ZVxuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IG9wZXJhdGlvbiA9IG9wZXJhdGlvblF1ZXVlLmN1cnJlbnQodHlwZSk7XG4gICAgb3BlcmF0aW9uLm9uU3RhcnQoKTtcbiAgfVxuICBfb25TQlVwZGF0ZUVuZCh0eXBlKSB7XG4gICAgdmFyIF90aGlzJG1lZGlhU291cmNlMjtcbiAgICBpZiAoKChfdGhpcyRtZWRpYVNvdXJjZTIgPSB0aGlzLm1lZGlhU291cmNlKSA9PSBudWxsID8gdm9pZCAwIDogX3RoaXMkbWVkaWFTb3VyY2UyLnJlYWR5U3RhdGUpID09PSAnY2xvc2VkJykge1xuICAgICAgdGhpcy5yZXNldEJ1ZmZlcih0eXBlKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qge1xuICAgICAgb3BlcmF0aW9uUXVldWVcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCBvcGVyYXRpb24gPSBvcGVyYXRpb25RdWV1ZS5jdXJyZW50KHR5cGUpO1xuICAgIG9wZXJhdGlvbi5vbkNvbXBsZXRlKCk7XG4gICAgb3BlcmF0aW9uUXVldWUuc2hpZnRBbmRFeGVjdXRlTmV4dCh0eXBlKTtcbiAgfVxuICBfb25TQlVwZGF0ZUVycm9yKHR5cGUsIGV2ZW50KSB7XG4gICAgdmFyIF90aGlzJG1lZGlhU291cmNlMztcbiAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcihgJHt0eXBlfSBTb3VyY2VCdWZmZXIgZXJyb3IuIE1lZGlhU291cmNlIHJlYWR5U3RhdGU6ICR7KF90aGlzJG1lZGlhU291cmNlMyA9IHRoaXMubWVkaWFTb3VyY2UpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRtZWRpYVNvdXJjZTMucmVhZHlTdGF0ZX1gKTtcbiAgICB0aGlzLmVycm9yKGAke2Vycm9yfWAsIGV2ZW50KTtcbiAgICAvLyBhY2NvcmRpbmcgdG8gaHR0cDovL3d3dy53My5vcmcvVFIvbWVkaWEtc291cmNlLyNzb3VyY2VidWZmZXItYXBwZW5kLWVycm9yXG4gICAgLy8gU291cmNlQnVmZmVyIGVycm9ycyBhcmUgbm90IG5lY2Vzc2FyaWx5IGZhdGFsOyBpZiBzbywgdGhlIEhUTUxNZWRpYUVsZW1lbnQgd2lsbCBmaXJlIGFuIGVycm9yIGV2ZW50XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQlVGRkVSX0FQUEVORElOR19FUlJPUixcbiAgICAgIHNvdXJjZUJ1ZmZlck5hbWU6IHR5cGUsXG4gICAgICBlcnJvcixcbiAgICAgIGZhdGFsOiBmYWxzZVxuICAgIH0pO1xuICAgIC8vIHVwZGF0ZWVuZCBpcyBhbHdheXMgZmlyZWQgYWZ0ZXIgZXJyb3IsIHNvIHdlJ2xsIGFsbG93IHRoYXQgdG8gc2hpZnQgdGhlIGN1cnJlbnQgb3BlcmF0aW9uIG9mZiBvZiB0aGUgcXVldWVcbiAgICBjb25zdCBvcGVyYXRpb24gPSB0aGlzLm9wZXJhdGlvblF1ZXVlLmN1cnJlbnQodHlwZSk7XG4gICAgaWYgKG9wZXJhdGlvbikge1xuICAgICAgb3BlcmF0aW9uLm9uRXJyb3IoZXJyb3IpO1xuICAgIH1cbiAgfVxuXG4gIC8vIFRoaXMgbWV0aG9kIG11c3QgcmVzdWx0IGluIGFuIHVwZGF0ZWVuZCBldmVudDsgaWYgcmVtb3ZlIGlzIG5vdCBjYWxsZWQsIF9vblNCVXBkYXRlRW5kIG11c3QgYmUgY2FsbGVkIG1hbnVhbGx5XG4gIHJlbW92ZUV4ZWN1dG9yKHR5cGUsIHN0YXJ0T2Zmc2V0LCBlbmRPZmZzZXQpIHtcbiAgICBjb25zdCB7XG4gICAgICBtZWRpYSxcbiAgICAgIG1lZGlhU291cmNlLFxuICAgICAgb3BlcmF0aW9uUXVldWUsXG4gICAgICBzb3VyY2VCdWZmZXJcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCBzYiA9IHNvdXJjZUJ1ZmZlclt0eXBlXTtcbiAgICBpZiAoIW1lZGlhIHx8ICFtZWRpYVNvdXJjZSB8fCAhc2IpIHtcbiAgICAgIHRoaXMud2FybihgQXR0ZW1wdGluZyB0byByZW1vdmUgZnJvbSB0aGUgJHt0eXBlfSBTb3VyY2VCdWZmZXIsIGJ1dCBpdCBkb2VzIG5vdCBleGlzdGApO1xuICAgICAgb3BlcmF0aW9uUXVldWUuc2hpZnRBbmRFeGVjdXRlTmV4dCh0eXBlKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgbWVkaWFEdXJhdGlvbiA9IGlzRmluaXRlTnVtYmVyKG1lZGlhLmR1cmF0aW9uKSA/IG1lZGlhLmR1cmF0aW9uIDogSW5maW5pdHk7XG4gICAgY29uc3QgbXNEdXJhdGlvbiA9IGlzRmluaXRlTnVtYmVyKG1lZGlhU291cmNlLmR1cmF0aW9uKSA/IG1lZGlhU291cmNlLmR1cmF0aW9uIDogSW5maW5pdHk7XG4gICAgY29uc3QgcmVtb3ZlU3RhcnQgPSBNYXRoLm1heCgwLCBzdGFydE9mZnNldCk7XG4gICAgY29uc3QgcmVtb3ZlRW5kID0gTWF0aC5taW4oZW5kT2Zmc2V0LCBtZWRpYUR1cmF0aW9uLCBtc0R1cmF0aW9uKTtcbiAgICBpZiAocmVtb3ZlRW5kID4gcmVtb3ZlU3RhcnQgJiYgKCFzYi5lbmRpbmcgfHwgc2IuZW5kZWQpKSB7XG4gICAgICBzYi5lbmRlZCA9IGZhbHNlO1xuICAgICAgdGhpcy5sb2coYFJlbW92aW5nIFske3JlbW92ZVN0YXJ0fSwke3JlbW92ZUVuZH1dIGZyb20gdGhlICR7dHlwZX0gU291cmNlQnVmZmVyYCk7XG4gICAgICBzYi5yZW1vdmUocmVtb3ZlU3RhcnQsIHJlbW92ZUVuZCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIEN5Y2xlIHRoZSBxdWV1ZVxuICAgICAgb3BlcmF0aW9uUXVldWUuc2hpZnRBbmRFeGVjdXRlTmV4dCh0eXBlKTtcbiAgICB9XG4gIH1cblxuICAvLyBUaGlzIG1ldGhvZCBtdXN0IHJlc3VsdCBpbiBhbiB1cGRhdGVlbmQgZXZlbnQ7IGlmIGFwcGVuZCBpcyBub3QgY2FsbGVkLCBfb25TQlVwZGF0ZUVuZCBtdXN0IGJlIGNhbGxlZCBtYW51YWxseVxuICBhcHBlbmRFeGVjdXRvcihkYXRhLCB0eXBlKSB7XG4gICAgY29uc3Qgc2IgPSB0aGlzLnNvdXJjZUJ1ZmZlclt0eXBlXTtcbiAgICBpZiAoIXNiKSB7XG4gICAgICBpZiAoIXRoaXMucGVuZGluZ1RyYWNrc1t0eXBlXSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEF0dGVtcHRpbmcgdG8gYXBwZW5kIHRvIHRoZSAke3R5cGV9IFNvdXJjZUJ1ZmZlciwgYnV0IGl0IGRvZXMgbm90IGV4aXN0YCk7XG4gICAgICB9XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHNiLmVuZGVkID0gZmFsc2U7XG4gICAgc2IuYXBwZW5kQnVmZmVyKGRhdGEpO1xuICB9XG5cbiAgLy8gRW5xdWV1ZXMgYW4gb3BlcmF0aW9uIHRvIGVhY2ggU291cmNlQnVmZmVyIHF1ZXVlIHdoaWNoLCB1cG9uIGV4ZWN1dGlvbiwgcmVzb2x2ZXMgYSBwcm9taXNlLiBXaGVuIGFsbCBwcm9taXNlc1xuICAvLyByZXNvbHZlLCB0aGUgb25VbmJsb2NrZWQgZnVuY3Rpb24gaXMgZXhlY3V0ZWQuIEZ1bmN0aW9ucyBjYWxsaW5nIHRoaXMgbWV0aG9kIGRvIG5vdCBuZWVkIHRvIHVuYmxvY2sgdGhlIHF1ZXVlXG4gIC8vIHVwb24gY29tcGxldGlvbiwgc2luY2Ugd2UgYWxyZWFkeSBkbyBpdCBoZXJlXG4gIGJsb2NrQnVmZmVycyhvblVuYmxvY2tlZCwgYnVmZmVycyA9IHRoaXMuZ2V0U291cmNlQnVmZmVyVHlwZXMoKSkge1xuICAgIGlmICghYnVmZmVycy5sZW5ndGgpIHtcbiAgICAgIHRoaXMubG9nKCdCbG9ja2luZyBvcGVyYXRpb24gcmVxdWVzdGVkLCBidXQgbm8gU291cmNlQnVmZmVycyBleGlzdCcpO1xuICAgICAgUHJvbWlzZS5yZXNvbHZlKCkudGhlbihvblVuYmxvY2tlZCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIG9wZXJhdGlvblF1ZXVlXG4gICAgfSA9IHRoaXM7XG5cbiAgICAvLyBsb2dnZXIuZGVidWcoYFtidWZmZXItY29udHJvbGxlcl06IEJsb2NraW5nICR7YnVmZmVyc30gU291cmNlQnVmZmVyYCk7XG4gICAgY29uc3QgYmxvY2tpbmdPcGVyYXRpb25zID0gYnVmZmVycy5tYXAodHlwZSA9PiBvcGVyYXRpb25RdWV1ZS5hcHBlbmRCbG9ja2VyKHR5cGUpKTtcbiAgICBQcm9taXNlLmFsbChibG9ja2luZ09wZXJhdGlvbnMpLnRoZW4oKCkgPT4ge1xuICAgICAgLy8gbG9nZ2VyLmRlYnVnKGBbYnVmZmVyLWNvbnRyb2xsZXJdOiBCbG9ja2luZyBvcGVyYXRpb24gcmVzb2x2ZWQ7IHVuYmxvY2tpbmcgJHtidWZmZXJzfSBTb3VyY2VCdWZmZXJgKTtcbiAgICAgIG9uVW5ibG9ja2VkKCk7XG4gICAgICBidWZmZXJzLmZvckVhY2godHlwZSA9PiB7XG4gICAgICAgIGNvbnN0IHNiID0gdGhpcy5zb3VyY2VCdWZmZXJbdHlwZV07XG4gICAgICAgIC8vIE9ubHkgY3ljbGUgdGhlIHF1ZXVlIGlmIHRoZSBTQiBpcyBub3QgdXBkYXRpbmcuIFRoZXJlJ3MgYSBidWcgaW4gQ2hyb21lIHdoaWNoIHNldHMgdGhlIFNCIHVwZGF0aW5nIGZsYWcgdG9cbiAgICAgICAgLy8gdHJ1ZSB3aGVuIGNoYW5naW5nIHRoZSBNZWRpYVNvdXJjZSBkdXJhdGlvbiAoaHR0cHM6Ly9idWdzLmNocm9taXVtLm9yZy9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9OTU5MzU5JmNhbj0yJnE9bWVkaWFzb3VyY2UlMjBkdXJhdGlvbilcbiAgICAgICAgLy8gV2hpbGUgdGhpcyBpcyBhIHdvcmthcm91bmQsIGl0J3MgcHJvYmFibHkgdXNlZnVsIHRvIGhhdmUgYXJvdW5kXG4gICAgICAgIGlmICghKHNiICE9IG51bGwgJiYgc2IudXBkYXRpbmcpKSB7XG4gICAgICAgICAgb3BlcmF0aW9uUXVldWUuc2hpZnRBbmRFeGVjdXRlTmV4dCh0eXBlKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cbiAgZ2V0U291cmNlQnVmZmVyVHlwZXMoKSB7XG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuc291cmNlQnVmZmVyKTtcbiAgfVxuICBhZGRCdWZmZXJMaXN0ZW5lcih0eXBlLCBldmVudCwgZm4pIHtcbiAgICBjb25zdCBidWZmZXIgPSB0aGlzLnNvdXJjZUJ1ZmZlclt0eXBlXTtcbiAgICBpZiAoIWJ1ZmZlcikge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBsaXN0ZW5lciA9IGZuLmJpbmQodGhpcywgdHlwZSk7XG4gICAgdGhpcy5saXN0ZW5lcnNbdHlwZV0ucHVzaCh7XG4gICAgICBldmVudCxcbiAgICAgIGxpc3RlbmVyXG4gICAgfSk7XG4gICAgYnVmZmVyLmFkZEV2ZW50TGlzdGVuZXIoZXZlbnQsIGxpc3RlbmVyKTtcbiAgfVxuICByZW1vdmVCdWZmZXJMaXN0ZW5lcnModHlwZSkge1xuICAgIGNvbnN0IGJ1ZmZlciA9IHRoaXMuc291cmNlQnVmZmVyW3R5cGVdO1xuICAgIGlmICghYnVmZmVyKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMubGlzdGVuZXJzW3R5cGVdLmZvckVhY2gobCA9PiB7XG4gICAgICBidWZmZXIucmVtb3ZlRXZlbnRMaXN0ZW5lcihsLmV2ZW50LCBsLmxpc3RlbmVyKTtcbiAgICB9KTtcbiAgfVxufVxuZnVuY3Rpb24gcmVtb3ZlU291cmNlQ2hpbGRyZW4obm9kZSkge1xuICBjb25zdCBzb3VyY2VDaGlsZHJlbiA9IG5vZGUucXVlcnlTZWxlY3RvckFsbCgnc291cmNlJyk7XG4gIFtdLnNsaWNlLmNhbGwoc291cmNlQ2hpbGRyZW4pLmZvckVhY2goc291cmNlID0+IHtcbiAgICBub2RlLnJlbW92ZUNoaWxkKHNvdXJjZSk7XG4gIH0pO1xufVxuZnVuY3Rpb24gYWRkU291cmNlKG1lZGlhLCB1cmwpIHtcbiAgY29uc3Qgc291cmNlID0gc2VsZi5kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzb3VyY2UnKTtcbiAgc291cmNlLnR5cGUgPSAndmlkZW8vbXA0JztcbiAgc291cmNlLnNyYyA9IHVybDtcbiAgbWVkaWEuYXBwZW5kQ2hpbGQoc291cmNlKTtcbn1cblxuLyoqXG4gKlxuICogVGhpcyBjb2RlIHdhcyBwb3J0ZWQgZnJvbSB0aGUgZGFzaC5qcyBwcm9qZWN0IGF0OlxuICogICBodHRwczovL2dpdGh1Yi5jb20vRGFzaC1JbmR1c3RyeS1Gb3J1bS9kYXNoLmpzL2Jsb2IvZGV2ZWxvcG1lbnQvZXh0ZXJuYWxzL2NlYTYwOC1wYXJzZXIuanNcbiAqICAgaHR0cHM6Ly9naXRodWIuY29tL0Rhc2gtSW5kdXN0cnktRm9ydW0vZGFzaC5qcy9jb21taXQvODI2OWIyNmE3NjFlMDg1M2JiMjFkNzg3ODBlZDk0NTE0NGVjZGQ0ZCNkaWZmLTcxYmMyOTVhMmQ2YjZiNzA5M2ExZDMyOTBkNTNhNGIyXG4gKlxuICogVGhlIG9yaWdpbmFsIGNvcHlyaWdodCBhcHBlYXJzIGJlbG93OlxuICpcbiAqIFRoZSBjb3B5cmlnaHQgaW4gdGhpcyBzb2Z0d2FyZSBpcyBiZWluZyBtYWRlIGF2YWlsYWJsZSB1bmRlciB0aGUgQlNEIExpY2Vuc2UsXG4gKiBpbmNsdWRlZCBiZWxvdy4gVGhpcyBzb2Z0d2FyZSBtYXkgYmUgc3ViamVjdCB0byBvdGhlciB0aGlyZCBwYXJ0eSBhbmQgY29udHJpYnV0b3JcbiAqIHJpZ2h0cywgaW5jbHVkaW5nIHBhdGVudCByaWdodHMsIGFuZCBubyBzdWNoIHJpZ2h0cyBhcmUgZ3JhbnRlZCB1bmRlciB0aGlzIGxpY2Vuc2UuXG4gKlxuICogQ29weXJpZ2h0IChjKSAyMDE1LTIwMTYsIERBU0ggSW5kdXN0cnkgRm9ydW0uXG4gKiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICpcbiAqIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dCBtb2RpZmljYXRpb24sXG4gKiBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZSBtZXQ6XG4gKiAgMS4gUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlLCB0aGlzXG4gKiAgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuXG4gKiAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UsXG4gKiAgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3JcbiAqICBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLlxuICogIDIuIE5laXRoZXIgdGhlIG5hbWUgb2YgRGFzaCBJbmR1c3RyeSBGb3J1bSBub3IgdGhlIG5hbWVzIG9mIGl0c1xuICogIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0byBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tIHRoaXMgc29mdHdhcmVcbiAqICB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi5cbiAqXG4gKiAgVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUyBBUyBJUyBBTkQgQU5ZXG4gKiAgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRFxuICogIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC5cbiAqICBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUIEhPTERFUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULFxuICogIElORElSRUNULCBJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVRcbiAqICBOT1QgTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsIERBVEEsIE9SXG4gKiAgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZIFRIRU9SWSBPRiBMSUFCSUxJVFksXG4gKiAgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKVxuICogIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRSBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFXG4gKiAgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuXG4gKi9cbi8qKlxuICogIEV4Y2VwdGlvbnMgZnJvbSByZWd1bGFyIEFTQ0lJLiBDb2RlUG9pbnRzIGFyZSBtYXBwZWQgdG8gVVRGLTE2IGNvZGVzXG4gKi9cblxuY29uc3Qgc3BlY2lhbENlYTYwOENoYXJzQ29kZXMgPSB7XG4gIDB4MmE6IDB4ZTEsXG4gIC8vIGxvd2VyY2FzZSBhLCBhY3V0ZSBhY2NlbnRcbiAgMHg1YzogMHhlOSxcbiAgLy8gbG93ZXJjYXNlIGUsIGFjdXRlIGFjY2VudFxuICAweDVlOiAweGVkLFxuICAvLyBsb3dlcmNhc2UgaSwgYWN1dGUgYWNjZW50XG4gIDB4NWY6IDB4ZjMsXG4gIC8vIGxvd2VyY2FzZSBvLCBhY3V0ZSBhY2NlbnRcbiAgMHg2MDogMHhmYSxcbiAgLy8gbG93ZXJjYXNlIHUsIGFjdXRlIGFjY2VudFxuICAweDdiOiAweGU3LFxuICAvLyBsb3dlcmNhc2UgYyB3aXRoIGNlZGlsbGFcbiAgMHg3YzogMHhmNyxcbiAgLy8gZGl2aXNpb24gc3ltYm9sXG4gIDB4N2Q6IDB4ZDEsXG4gIC8vIHVwcGVyY2FzZSBOIHRpbGRlXG4gIDB4N2U6IDB4ZjEsXG4gIC8vIGxvd2VyY2FzZSBuIHRpbGRlXG4gIDB4N2Y6IDB4MjU4OCxcbiAgLy8gRnVsbCBibG9ja1xuICAvLyBUSElTIEJMT0NLIElOQ0xVREVTIFRIRSAxNiBFWFRFTkRFRCAoVFdPLUJZVEUpIExJTkUgMjEgQ0hBUkFDVEVSU1xuICAvLyBUSEFUIENPTUUgRlJPTSBISSBCWVRFPTB4MTEgQU5EIExPVyBCRVRXRUVOIDB4MzAgQU5EIDB4M0ZcbiAgLy8gVEhJUyBNRUFOUyBUSEFUIFxceDUwIE1VU1QgQkUgQURERUQgVE8gVEhFIFZBTFVFU1xuICAweDgwOiAweGFlLFxuICAvLyBSZWdpc3RlcmVkIHN5bWJvbCAoUilcbiAgMHg4MTogMHhiMCxcbiAgLy8gZGVncmVlIHNpZ25cbiAgMHg4MjogMHhiZCxcbiAgLy8gMS8yIHN5bWJvbFxuICAweDgzOiAweGJmLFxuICAvLyBJbnZlcnRlZCAob3BlbikgcXVlc3Rpb24gbWFya1xuICAweDg0OiAweDIxMjIsXG4gIC8vIFRyYWRlbWFyayBzeW1ib2wgKFRNKVxuICAweDg1OiAweGEyLFxuICAvLyBDZW50cyBzeW1ib2xcbiAgMHg4NjogMHhhMyxcbiAgLy8gUG91bmRzIHN0ZXJsaW5nXG4gIDB4ODc6IDB4MjY2YSxcbiAgLy8gTXVzaWMgOCd0aCBub3RlXG4gIDB4ODg6IDB4ZTAsXG4gIC8vIGxvd2VyY2FzZSBhLCBncmF2ZSBhY2NlbnRcbiAgMHg4OTogMHgyMCxcbiAgLy8gdHJhbnNwYXJlbnQgc3BhY2UgKHJlZ3VsYXIpXG4gIDB4OGE6IDB4ZTgsXG4gIC8vIGxvd2VyY2FzZSBlLCBncmF2ZSBhY2NlbnRcbiAgMHg4YjogMHhlMixcbiAgLy8gbG93ZXJjYXNlIGEsIGNpcmN1bWZsZXggYWNjZW50XG4gIDB4OGM6IDB4ZWEsXG4gIC8vIGxvd2VyY2FzZSBlLCBjaXJjdW1mbGV4IGFjY2VudFxuICAweDhkOiAweGVlLFxuICAvLyBsb3dlcmNhc2UgaSwgY2lyY3VtZmxleCBhY2NlbnRcbiAgMHg4ZTogMHhmNCxcbiAgLy8gbG93ZXJjYXNlIG8sIGNpcmN1bWZsZXggYWNjZW50XG4gIDB4OGY6IDB4ZmIsXG4gIC8vIGxvd2VyY2FzZSB1LCBjaXJjdW1mbGV4IGFjY2VudFxuICAvLyBUSElTIEJMT0NLIElOQ0xVREVTIFRIRSAzMiBFWFRFTkRFRCAoVFdPLUJZVEUpIExJTkUgMjEgQ0hBUkFDVEVSU1xuICAvLyBUSEFUIENPTUUgRlJPTSBISSBCWVRFPTB4MTIgQU5EIExPVyBCRVRXRUVOIDB4MjAgQU5EIDB4M0ZcbiAgMHg5MDogMHhjMSxcbiAgLy8gY2FwaXRhbCBsZXR0ZXIgQSB3aXRoIGFjdXRlXG4gIDB4OTE6IDB4YzksXG4gIC8vIGNhcGl0YWwgbGV0dGVyIEUgd2l0aCBhY3V0ZVxuICAweDkyOiAweGQzLFxuICAvLyBjYXBpdGFsIGxldHRlciBPIHdpdGggYWN1dGVcbiAgMHg5MzogMHhkYSxcbiAgLy8gY2FwaXRhbCBsZXR0ZXIgVSB3aXRoIGFjdXRlXG4gIDB4OTQ6IDB4ZGMsXG4gIC8vIGNhcGl0YWwgbGV0dGVyIFUgd2l0aCBkaWFyZXNpc1xuICAweDk1OiAweGZjLFxuICAvLyBsb3dlcmNhc2UgbGV0dGVyIFUgd2l0aCBkaWFlcmVzaXNcbiAgMHg5NjogMHgyMDE4LFxuICAvLyBvcGVuaW5nIHNpbmdsZSBxdW90ZVxuICAweDk3OiAweGExLFxuICAvLyBpbnZlcnRlZCBleGNsYW1hdGlvbiBtYXJrXG4gIDB4OTg6IDB4MmEsXG4gIC8vIGFzdGVyaXNrXG4gIDB4OTk6IDB4MjAxOSxcbiAgLy8gY2xvc2luZyBzaW5nbGUgcXVvdGVcbiAgMHg5YTogMHgyNTAxLFxuICAvLyBib3ggZHJhd2luZ3MgaGVhdnkgaG9yaXpvbnRhbFxuICAweDliOiAweGE5LFxuICAvLyBjb3B5cmlnaHQgc2lnblxuICAweDljOiAweDIxMjAsXG4gIC8vIFNlcnZpY2UgbWFya1xuICAweDlkOiAweDIwMjIsXG4gIC8vIChyb3VuZCkgYnVsbGV0XG4gIDB4OWU6IDB4MjAxYyxcbiAgLy8gTGVmdCBkb3VibGUgcXVvdGF0aW9uIG1hcmtcbiAgMHg5ZjogMHgyMDFkLFxuICAvLyBSaWdodCBkb3VibGUgcXVvdGF0aW9uIG1hcmtcbiAgMHhhMDogMHhjMCxcbiAgLy8gdXBwZXJjYXNlIEEsIGdyYXZlIGFjY2VudFxuICAweGExOiAweGMyLFxuICAvLyB1cHBlcmNhc2UgQSwgY2lyY3VtZmxleFxuICAweGEyOiAweGM3LFxuICAvLyB1cHBlcmNhc2UgQyB3aXRoIGNlZGlsbGFcbiAgMHhhMzogMHhjOCxcbiAgLy8gdXBwZXJjYXNlIEUsIGdyYXZlIGFjY2VudFxuICAweGE0OiAweGNhLFxuICAvLyB1cHBlcmNhc2UgRSwgY2lyY3VtZmxleFxuICAweGE1OiAweGNiLFxuICAvLyBjYXBpdGFsIGxldHRlciBFIHdpdGggZGlhcmVzaXNcbiAgMHhhNjogMHhlYixcbiAgLy8gbG93ZXJjYXNlIGxldHRlciBlIHdpdGggZGlhcmVzaXNcbiAgMHhhNzogMHhjZSxcbiAgLy8gdXBwZXJjYXNlIEksIGNpcmN1bWZsZXhcbiAgMHhhODogMHhjZixcbiAgLy8gdXBwZXJjYXNlIEksIHdpdGggZGlhcmVzaXNcbiAgMHhhOTogMHhlZixcbiAgLy8gbG93ZXJjYXNlIGksIHdpdGggZGlhcmVzaXNcbiAgMHhhYTogMHhkNCxcbiAgLy8gdXBwZXJjYXNlIE8sIGNpcmN1bWZsZXhcbiAgMHhhYjogMHhkOSxcbiAgLy8gdXBwZXJjYXNlIFUsIGdyYXZlIGFjY2VudFxuICAweGFjOiAweGY5LFxuICAvLyBsb3dlcmNhc2UgdSwgZ3JhdmUgYWNjZW50XG4gIDB4YWQ6IDB4ZGIsXG4gIC8vIHVwcGVyY2FzZSBVLCBjaXJjdW1mbGV4XG4gIDB4YWU6IDB4YWIsXG4gIC8vIGxlZnQtcG9pbnRpbmcgZG91YmxlIGFuZ2xlIHF1b3RhdGlvbiBtYXJrXG4gIDB4YWY6IDB4YmIsXG4gIC8vIHJpZ2h0LXBvaW50aW5nIGRvdWJsZSBhbmdsZSBxdW90YXRpb24gbWFya1xuICAvLyBUSElTIEJMT0NLIElOQ0xVREVTIFRIRSAzMiBFWFRFTkRFRCAoVFdPLUJZVEUpIExJTkUgMjEgQ0hBUkFDVEVSU1xuICAvLyBUSEFUIENPTUUgRlJPTSBISSBCWVRFPTB4MTMgQU5EIExPVyBCRVRXRUVOIDB4MjAgQU5EIDB4M0ZcbiAgMHhiMDogMHhjMyxcbiAgLy8gVXBwZXJjYXNlIEEsIHRpbGRlXG4gIDB4YjE6IDB4ZTMsXG4gIC8vIExvd2VyY2FzZSBhLCB0aWxkZVxuICAweGIyOiAweGNkLFxuICAvLyBVcHBlcmNhc2UgSSwgYWN1dGUgYWNjZW50XG4gIDB4YjM6IDB4Y2MsXG4gIC8vIFVwcGVyY2FzZSBJLCBncmF2ZSBhY2NlbnRcbiAgMHhiNDogMHhlYyxcbiAgLy8gTG93ZXJjYXNlIGksIGdyYXZlIGFjY2VudFxuICAweGI1OiAweGQyLFxuICAvLyBVcHBlcmNhc2UgTywgZ3JhdmUgYWNjZW50XG4gIDB4YjY6IDB4ZjIsXG4gIC8vIExvd2VyY2FzZSBvLCBncmF2ZSBhY2NlbnRcbiAgMHhiNzogMHhkNSxcbiAgLy8gVXBwZXJjYXNlIE8sIHRpbGRlXG4gIDB4Yjg6IDB4ZjUsXG4gIC8vIExvd2VyY2FzZSBvLCB0aWxkZVxuICAweGI5OiAweDdiLFxuICAvLyBPcGVuIGN1cmx5IGJyYWNlXG4gIDB4YmE6IDB4N2QsXG4gIC8vIENsb3NpbmcgY3VybHkgYnJhY2VcbiAgMHhiYjogMHg1YyxcbiAgLy8gQmFja3NsYXNoXG4gIDB4YmM6IDB4NWUsXG4gIC8vIENhcmV0XG4gIDB4YmQ6IDB4NWYsXG4gIC8vIFVuZGVyc2NvcmVcbiAgMHhiZTogMHg3YyxcbiAgLy8gUGlwZSAodmVydGljYWwgbGluZSlcbiAgMHhiZjogMHgyMjNjLFxuICAvLyBUaWxkZSBvcGVyYXRvclxuICAweGMwOiAweGM0LFxuICAvLyBVcHBlcmNhc2UgQSwgdW1sYXV0XG4gIDB4YzE6IDB4ZTQsXG4gIC8vIExvd2VyY2FzZSBBLCB1bWxhdXRcbiAgMHhjMjogMHhkNixcbiAgLy8gVXBwZXJjYXNlIE8sIHVtbGF1dFxuICAweGMzOiAweGY2LFxuICAvLyBMb3dlcmNhc2UgbywgdW1sYXV0XG4gIDB4YzQ6IDB4ZGYsXG4gIC8vIEVzc3pldHQgKHNoYXJwIFMpXG4gIDB4YzU6IDB4YTUsXG4gIC8vIFllbiBzeW1ib2xcbiAgMHhjNjogMHhhNCxcbiAgLy8gR2VuZXJpYyBjdXJyZW5jeSBzaWduXG4gIDB4Yzc6IDB4MjUwMyxcbiAgLy8gQm94IGRyYXdpbmdzIGhlYXZ5IHZlcnRpY2FsXG4gIDB4Yzg6IDB4YzUsXG4gIC8vIFVwcGVyY2FzZSBBLCByaW5nXG4gIDB4Yzk6IDB4ZTUsXG4gIC8vIExvd2VyY2FzZSBBLCByaW5nXG4gIDB4Y2E6IDB4ZDgsXG4gIC8vIFVwcGVyY2FzZSBPLCBzdHJva2VcbiAgMHhjYjogMHhmOCxcbiAgLy8gTG93ZXJjYXNlIG8sIHN0cm9rXG4gIDB4Y2M6IDB4MjUwZixcbiAgLy8gQm94IGRyYXdpbmdzIGhlYXZ5IGRvd24gYW5kIHJpZ2h0XG4gIDB4Y2Q6IDB4MjUxMyxcbiAgLy8gQm94IGRyYXdpbmdzIGhlYXZ5IGRvd24gYW5kIGxlZnRcbiAgMHhjZTogMHgyNTE3LFxuICAvLyBCb3ggZHJhd2luZ3MgaGVhdnkgdXAgYW5kIHJpZ2h0XG4gIDB4Y2Y6IDB4MjUxYiAvLyBCb3ggZHJhd2luZ3MgaGVhdnkgdXAgYW5kIGxlZnRcbn07XG5cbi8qKlxuICogVXRpbHNcbiAqL1xuY29uc3QgZ2V0Q2hhckZvckJ5dGUgPSBieXRlID0+IFN0cmluZy5mcm9tQ2hhckNvZGUoc3BlY2lhbENlYTYwOENoYXJzQ29kZXNbYnl0ZV0gfHwgYnl0ZSk7XG5jb25zdCBOUl9ST1dTID0gMTU7XG5jb25zdCBOUl9DT0xTID0gMTAwO1xuLy8gVGFibGVzIHRvIGxvb2sgdXAgcm93IGZyb20gUEFDIGRhdGFcbmNvbnN0IHJvd3NMb3dDaDEgPSB7XG4gIDB4MTE6IDEsXG4gIDB4MTI6IDMsXG4gIDB4MTU6IDUsXG4gIDB4MTY6IDcsXG4gIDB4MTc6IDksXG4gIDB4MTA6IDExLFxuICAweDEzOiAxMixcbiAgMHgxNDogMTRcbn07XG5jb25zdCByb3dzSGlnaENoMSA9IHtcbiAgMHgxMTogMixcbiAgMHgxMjogNCxcbiAgMHgxNTogNixcbiAgMHgxNjogOCxcbiAgMHgxNzogMTAsXG4gIDB4MTM6IDEzLFxuICAweDE0OiAxNVxufTtcbmNvbnN0IHJvd3NMb3dDaDIgPSB7XG4gIDB4MTk6IDEsXG4gIDB4MWE6IDMsXG4gIDB4MWQ6IDUsXG4gIDB4MWU6IDcsXG4gIDB4MWY6IDksXG4gIDB4MTg6IDExLFxuICAweDFiOiAxMixcbiAgMHgxYzogMTRcbn07XG5jb25zdCByb3dzSGlnaENoMiA9IHtcbiAgMHgxOTogMixcbiAgMHgxYTogNCxcbiAgMHgxZDogNixcbiAgMHgxZTogOCxcbiAgMHgxZjogMTAsXG4gIDB4MWI6IDEzLFxuICAweDFjOiAxNVxufTtcbmNvbnN0IGJhY2tncm91bmRDb2xvcnMgPSBbJ3doaXRlJywgJ2dyZWVuJywgJ2JsdWUnLCAnY3lhbicsICdyZWQnLCAneWVsbG93JywgJ21hZ2VudGEnLCAnYmxhY2snLCAndHJhbnNwYXJlbnQnXTtcbmNsYXNzIENhcHRpb25zTG9nZ2VyIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy50aW1lID0gbnVsbDtcbiAgICB0aGlzLnZlcmJvc2VMZXZlbCA9IDA7XG4gIH1cbiAgbG9nKHNldmVyaXR5LCBtc2cpIHtcbiAgICBpZiAodGhpcy52ZXJib3NlTGV2ZWwgPj0gc2V2ZXJpdHkpIHtcbiAgICAgIGNvbnN0IG0gPSB0eXBlb2YgbXNnID09PSAnZnVuY3Rpb24nID8gbXNnKCkgOiBtc2c7XG4gICAgICBsb2dnZXIubG9nKGAke3RoaXMudGltZX0gWyR7c2V2ZXJpdHl9XSAke219YCk7XG4gICAgfVxuICB9XG59XG5jb25zdCBudW1BcnJheVRvSGV4QXJyYXkgPSBmdW5jdGlvbiBudW1BcnJheVRvSGV4QXJyYXkobnVtQXJyYXkpIHtcbiAgY29uc3QgaGV4QXJyYXkgPSBbXTtcbiAgZm9yIChsZXQgaiA9IDA7IGogPCBudW1BcnJheS5sZW5ndGg7IGorKykge1xuICAgIGhleEFycmF5LnB1c2gobnVtQXJyYXlbal0udG9TdHJpbmcoMTYpKTtcbiAgfVxuICByZXR1cm4gaGV4QXJyYXk7XG59O1xuY2xhc3MgUGVuU3RhdGUge1xuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLmZvcmVncm91bmQgPSAnd2hpdGUnO1xuICAgIHRoaXMudW5kZXJsaW5lID0gZmFsc2U7XG4gICAgdGhpcy5pdGFsaWNzID0gZmFsc2U7XG4gICAgdGhpcy5iYWNrZ3JvdW5kID0gJ2JsYWNrJztcbiAgICB0aGlzLmZsYXNoID0gZmFsc2U7XG4gIH1cbiAgcmVzZXQoKSB7XG4gICAgdGhpcy5mb3JlZ3JvdW5kID0gJ3doaXRlJztcbiAgICB0aGlzLnVuZGVybGluZSA9IGZhbHNlO1xuICAgIHRoaXMuaXRhbGljcyA9IGZhbHNlO1xuICAgIHRoaXMuYmFja2dyb3VuZCA9ICdibGFjayc7XG4gICAgdGhpcy5mbGFzaCA9IGZhbHNlO1xuICB9XG4gIHNldFN0eWxlcyhzdHlsZXMpIHtcbiAgICBjb25zdCBhdHRyaWJzID0gWydmb3JlZ3JvdW5kJywgJ3VuZGVybGluZScsICdpdGFsaWNzJywgJ2JhY2tncm91bmQnLCAnZmxhc2gnXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGF0dHJpYnMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGNvbnN0IHN0eWxlID0gYXR0cmlic1tpXTtcbiAgICAgIGlmIChzdHlsZXMuaGFzT3duUHJvcGVydHkoc3R5bGUpKSB7XG4gICAgICAgIHRoaXNbc3R5bGVdID0gc3R5bGVzW3N0eWxlXTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgaXNEZWZhdWx0KCkge1xuICAgIHJldHVybiB0aGlzLmZvcmVncm91bmQgPT09ICd3aGl0ZScgJiYgIXRoaXMudW5kZXJsaW5lICYmICF0aGlzLml0YWxpY3MgJiYgdGhpcy5iYWNrZ3JvdW5kID09PSAnYmxhY2snICYmICF0aGlzLmZsYXNoO1xuICB9XG4gIGVxdWFscyhvdGhlcikge1xuICAgIHJldHVybiB0aGlzLmZvcmVncm91bmQgPT09IG90aGVyLmZvcmVncm91bmQgJiYgdGhpcy51bmRlcmxpbmUgPT09IG90aGVyLnVuZGVybGluZSAmJiB0aGlzLml0YWxpY3MgPT09IG90aGVyLml0YWxpY3MgJiYgdGhpcy5iYWNrZ3JvdW5kID09PSBvdGhlci5iYWNrZ3JvdW5kICYmIHRoaXMuZmxhc2ggPT09IG90aGVyLmZsYXNoO1xuICB9XG4gIGNvcHkobmV3UGVuU3RhdGUpIHtcbiAgICB0aGlzLmZvcmVncm91bmQgPSBuZXdQZW5TdGF0ZS5mb3JlZ3JvdW5kO1xuICAgIHRoaXMudW5kZXJsaW5lID0gbmV3UGVuU3RhdGUudW5kZXJsaW5lO1xuICAgIHRoaXMuaXRhbGljcyA9IG5ld1BlblN0YXRlLml0YWxpY3M7XG4gICAgdGhpcy5iYWNrZ3JvdW5kID0gbmV3UGVuU3RhdGUuYmFja2dyb3VuZDtcbiAgICB0aGlzLmZsYXNoID0gbmV3UGVuU3RhdGUuZmxhc2g7XG4gIH1cbiAgdG9TdHJpbmcoKSB7XG4gICAgcmV0dXJuICdjb2xvcj0nICsgdGhpcy5mb3JlZ3JvdW5kICsgJywgdW5kZXJsaW5lPScgKyB0aGlzLnVuZGVybGluZSArICcsIGl0YWxpY3M9JyArIHRoaXMuaXRhbGljcyArICcsIGJhY2tncm91bmQ9JyArIHRoaXMuYmFja2dyb3VuZCArICcsIGZsYXNoPScgKyB0aGlzLmZsYXNoO1xuICB9XG59XG5cbi8qKlxuICogVW5pY29kZSBjaGFyYWN0ZXIgd2l0aCBzdHlsaW5nIGFuZCBiYWNrZ3JvdW5kLlxuICogQGNvbnN0cnVjdG9yXG4gKi9cbmNsYXNzIFN0eWxlZFVuaWNvZGVDaGFyIHtcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgdGhpcy51Y2hhciA9ICcgJztcbiAgICB0aGlzLnBlblN0YXRlID0gbmV3IFBlblN0YXRlKCk7XG4gIH1cbiAgcmVzZXQoKSB7XG4gICAgdGhpcy51Y2hhciA9ICcgJztcbiAgICB0aGlzLnBlblN0YXRlLnJlc2V0KCk7XG4gIH1cbiAgc2V0Q2hhcih1Y2hhciwgbmV3UGVuU3RhdGUpIHtcbiAgICB0aGlzLnVjaGFyID0gdWNoYXI7XG4gICAgdGhpcy5wZW5TdGF0ZS5jb3B5KG5ld1BlblN0YXRlKTtcbiAgfVxuICBzZXRQZW5TdGF0ZShuZXdQZW5TdGF0ZSkge1xuICAgIHRoaXMucGVuU3RhdGUuY29weShuZXdQZW5TdGF0ZSk7XG4gIH1cbiAgZXF1YWxzKG90aGVyKSB7XG4gICAgcmV0dXJuIHRoaXMudWNoYXIgPT09IG90aGVyLnVjaGFyICYmIHRoaXMucGVuU3RhdGUuZXF1YWxzKG90aGVyLnBlblN0YXRlKTtcbiAgfVxuICBjb3B5KG5ld0NoYXIpIHtcbiAgICB0aGlzLnVjaGFyID0gbmV3Q2hhci51Y2hhcjtcbiAgICB0aGlzLnBlblN0YXRlLmNvcHkobmV3Q2hhci5wZW5TdGF0ZSk7XG4gIH1cbiAgaXNFbXB0eSgpIHtcbiAgICByZXR1cm4gdGhpcy51Y2hhciA9PT0gJyAnICYmIHRoaXMucGVuU3RhdGUuaXNEZWZhdWx0KCk7XG4gIH1cbn1cblxuLyoqXG4gKiBDRUEtNjA4IHJvdyBjb25zaXN0aW5nIG9mIE5SX0NPTFMgaW5zdGFuY2VzIG9mIFN0eWxlZFVuaWNvZGVDaGFyLlxuICogQGNvbnN0cnVjdG9yXG4gKi9cbmNsYXNzIFJvdyB7XG4gIGNvbnN0cnVjdG9yKGxvZ2dlcikge1xuICAgIHRoaXMuY2hhcnMgPSBbXTtcbiAgICB0aGlzLnBvcyA9IDA7XG4gICAgdGhpcy5jdXJyUGVuU3RhdGUgPSBuZXcgUGVuU3RhdGUoKTtcbiAgICB0aGlzLmN1ZVN0YXJ0VGltZSA9IG51bGw7XG4gICAgdGhpcy5sb2dnZXIgPSB2b2lkIDA7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBOUl9DT0xTOyBpKyspIHtcbiAgICAgIHRoaXMuY2hhcnMucHVzaChuZXcgU3R5bGVkVW5pY29kZUNoYXIoKSk7XG4gICAgfVxuICAgIHRoaXMubG9nZ2VyID0gbG9nZ2VyO1xuICB9XG4gIGVxdWFscyhvdGhlcikge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTlJfQ09MUzsgaSsrKSB7XG4gICAgICBpZiAoIXRoaXMuY2hhcnNbaV0uZXF1YWxzKG90aGVyLmNoYXJzW2ldKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuICB9XG4gIGNvcHkob3RoZXIpIHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IE5SX0NPTFM7IGkrKykge1xuICAgICAgdGhpcy5jaGFyc1tpXS5jb3B5KG90aGVyLmNoYXJzW2ldKTtcbiAgICB9XG4gIH1cbiAgaXNFbXB0eSgpIHtcbiAgICBsZXQgZW1wdHkgPSB0cnVlO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTlJfQ09MUzsgaSsrKSB7XG4gICAgICBpZiAoIXRoaXMuY2hhcnNbaV0uaXNFbXB0eSgpKSB7XG4gICAgICAgIGVtcHR5ID0gZmFsc2U7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZW1wdHk7XG4gIH1cblxuICAvKipcbiAgICogIFNldCB0aGUgY3Vyc29yIHRvIGEgdmFsaWQgY29sdW1uLlxuICAgKi9cbiAgc2V0Q3Vyc29yKGFic1Bvcykge1xuICAgIGlmICh0aGlzLnBvcyAhPT0gYWJzUG9zKSB7XG4gICAgICB0aGlzLnBvcyA9IGFic1BvcztcbiAgICB9XG4gICAgaWYgKHRoaXMucG9zIDwgMCkge1xuICAgICAgdGhpcy5sb2dnZXIubG9nKDMsICdOZWdhdGl2ZSBjdXJzb3IgcG9zaXRpb24gJyArIHRoaXMucG9zKTtcbiAgICAgIHRoaXMucG9zID0gMDtcbiAgICB9IGVsc2UgaWYgKHRoaXMucG9zID4gTlJfQ09MUykge1xuICAgICAgdGhpcy5sb2dnZXIubG9nKDMsICdUb28gbGFyZ2UgY3Vyc29yIHBvc2l0aW9uICcgKyB0aGlzLnBvcyk7XG4gICAgICB0aGlzLnBvcyA9IE5SX0NPTFM7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIE1vdmUgdGhlIGN1cnNvciByZWxhdGl2ZSB0byBjdXJyZW50IHBvc2l0aW9uLlxuICAgKi9cbiAgbW92ZUN1cnNvcihyZWxQb3MpIHtcbiAgICBjb25zdCBuZXdQb3MgPSB0aGlzLnBvcyArIHJlbFBvcztcbiAgICBpZiAocmVsUG9zID4gMSkge1xuICAgICAgZm9yIChsZXQgaSA9IHRoaXMucG9zICsgMTsgaSA8IG5ld1BvcyArIDE7IGkrKykge1xuICAgICAgICB0aGlzLmNoYXJzW2ldLnNldFBlblN0YXRlKHRoaXMuY3VyclBlblN0YXRlKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5zZXRDdXJzb3IobmV3UG9zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBCYWNrc3BhY2UsIG1vdmUgb25lIHN0ZXAgYmFjayBhbmQgY2xlYXIgY2hhcmFjdGVyLlxuICAgKi9cbiAgYmFja1NwYWNlKCkge1xuICAgIHRoaXMubW92ZUN1cnNvcigtMSk7XG4gICAgdGhpcy5jaGFyc1t0aGlzLnBvc10uc2V0Q2hhcignICcsIHRoaXMuY3VyclBlblN0YXRlKTtcbiAgfVxuICBpbnNlcnRDaGFyKGJ5dGUpIHtcbiAgICBpZiAoYnl0ZSA+PSAweDkwKSB7XG4gICAgICAvLyBFeHRlbmRlZCBjaGFyXG4gICAgICB0aGlzLmJhY2tTcGFjZSgpO1xuICAgIH1cbiAgICBjb25zdCBjaGFyID0gZ2V0Q2hhckZvckJ5dGUoYnl0ZSk7XG4gICAgaWYgKHRoaXMucG9zID49IE5SX0NPTFMpIHtcbiAgICAgIHRoaXMubG9nZ2VyLmxvZygwLCAoKSA9PiAnQ2Fubm90IGluc2VydCAnICsgYnl0ZS50b1N0cmluZygxNikgKyAnICgnICsgY2hhciArICcpIGF0IHBvc2l0aW9uICcgKyB0aGlzLnBvcyArICcuIFNraXBwaW5nIGl0IScpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmNoYXJzW3RoaXMucG9zXS5zZXRDaGFyKGNoYXIsIHRoaXMuY3VyclBlblN0YXRlKTtcbiAgICB0aGlzLm1vdmVDdXJzb3IoMSk7XG4gIH1cbiAgY2xlYXJGcm9tUG9zKHN0YXJ0UG9zKSB7XG4gICAgbGV0IGk7XG4gICAgZm9yIChpID0gc3RhcnRQb3M7IGkgPCBOUl9DT0xTOyBpKyspIHtcbiAgICAgIHRoaXMuY2hhcnNbaV0ucmVzZXQoKTtcbiAgICB9XG4gIH1cbiAgY2xlYXIoKSB7XG4gICAgdGhpcy5jbGVhckZyb21Qb3MoMCk7XG4gICAgdGhpcy5wb3MgPSAwO1xuICAgIHRoaXMuY3VyclBlblN0YXRlLnJlc2V0KCk7XG4gIH1cbiAgY2xlYXJUb0VuZE9mUm93KCkge1xuICAgIHRoaXMuY2xlYXJGcm9tUG9zKHRoaXMucG9zKTtcbiAgfVxuICBnZXRUZXh0U3RyaW5nKCkge1xuICAgIGNvbnN0IGNoYXJzID0gW107XG4gICAgbGV0IGVtcHR5ID0gdHJ1ZTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IE5SX0NPTFM7IGkrKykge1xuICAgICAgY29uc3QgY2hhciA9IHRoaXMuY2hhcnNbaV0udWNoYXI7XG4gICAgICBpZiAoY2hhciAhPT0gJyAnKSB7XG4gICAgICAgIGVtcHR5ID0gZmFsc2U7XG4gICAgICB9XG4gICAgICBjaGFycy5wdXNoKGNoYXIpO1xuICAgIH1cbiAgICBpZiAoZW1wdHkpIHtcbiAgICAgIHJldHVybiAnJztcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIGNoYXJzLmpvaW4oJycpO1xuICAgIH1cbiAgfVxuICBzZXRQZW5TdHlsZXMoc3R5bGVzKSB7XG4gICAgdGhpcy5jdXJyUGVuU3RhdGUuc2V0U3R5bGVzKHN0eWxlcyk7XG4gICAgY29uc3QgY3VyckNoYXIgPSB0aGlzLmNoYXJzW3RoaXMucG9zXTtcbiAgICBjdXJyQ2hhci5zZXRQZW5TdGF0ZSh0aGlzLmN1cnJQZW5TdGF0ZSk7XG4gIH1cbn1cblxuLyoqXG4gKiBLZWVwIGEgQ0VBLTYwOCBzY3JlZW4gb2YgMzJ4MTUgc3R5bGVkIGNoYXJhY3RlcnNcbiAqIEBjb25zdHJ1Y3RvclxuICovXG5jbGFzcyBDYXB0aW9uU2NyZWVuIHtcbiAgY29uc3RydWN0b3IobG9nZ2VyKSB7XG4gICAgdGhpcy5yb3dzID0gW107XG4gICAgdGhpcy5jdXJyUm93ID0gTlJfUk9XUyAtIDE7XG4gICAgdGhpcy5uclJvbGxVcFJvd3MgPSBudWxsO1xuICAgIHRoaXMubGFzdE91dHB1dFNjcmVlbiA9IG51bGw7XG4gICAgdGhpcy5sb2dnZXIgPSB2b2lkIDA7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBOUl9ST1dTOyBpKyspIHtcbiAgICAgIHRoaXMucm93cy5wdXNoKG5ldyBSb3cobG9nZ2VyKSk7XG4gICAgfVxuICAgIHRoaXMubG9nZ2VyID0gbG9nZ2VyO1xuICB9XG4gIHJlc2V0KCkge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTlJfUk9XUzsgaSsrKSB7XG4gICAgICB0aGlzLnJvd3NbaV0uY2xlYXIoKTtcbiAgICB9XG4gICAgdGhpcy5jdXJyUm93ID0gTlJfUk9XUyAtIDE7XG4gIH1cbiAgZXF1YWxzKG90aGVyKSB7XG4gICAgbGV0IGVxdWFsID0gdHJ1ZTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IE5SX1JPV1M7IGkrKykge1xuICAgICAgaWYgKCF0aGlzLnJvd3NbaV0uZXF1YWxzKG90aGVyLnJvd3NbaV0pKSB7XG4gICAgICAgIGVxdWFsID0gZmFsc2U7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZXF1YWw7XG4gIH1cbiAgY29weShvdGhlcikge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTlJfUk9XUzsgaSsrKSB7XG4gICAgICB0aGlzLnJvd3NbaV0uY29weShvdGhlci5yb3dzW2ldKTtcbiAgICB9XG4gIH1cbiAgaXNFbXB0eSgpIHtcbiAgICBsZXQgZW1wdHkgPSB0cnVlO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTlJfUk9XUzsgaSsrKSB7XG4gICAgICBpZiAoIXRoaXMucm93c1tpXS5pc0VtcHR5KCkpIHtcbiAgICAgICAgZW1wdHkgPSBmYWxzZTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBlbXB0eTtcbiAgfVxuICBiYWNrU3BhY2UoKSB7XG4gICAgY29uc3Qgcm93ID0gdGhpcy5yb3dzW3RoaXMuY3VyclJvd107XG4gICAgcm93LmJhY2tTcGFjZSgpO1xuICB9XG4gIGNsZWFyVG9FbmRPZlJvdygpIHtcbiAgICBjb25zdCByb3cgPSB0aGlzLnJvd3NbdGhpcy5jdXJyUm93XTtcbiAgICByb3cuY2xlYXJUb0VuZE9mUm93KCk7XG4gIH1cblxuICAvKipcbiAgICogSW5zZXJ0IGEgY2hhcmFjdGVyICh3aXRob3V0IHN0eWxpbmcpIGluIHRoZSBjdXJyZW50IHJvdy5cbiAgICovXG4gIGluc2VydENoYXIoY2hhcikge1xuICAgIGNvbnN0IHJvdyA9IHRoaXMucm93c1t0aGlzLmN1cnJSb3ddO1xuICAgIHJvdy5pbnNlcnRDaGFyKGNoYXIpO1xuICB9XG4gIHNldFBlbihzdHlsZXMpIHtcbiAgICBjb25zdCByb3cgPSB0aGlzLnJvd3NbdGhpcy5jdXJyUm93XTtcbiAgICByb3cuc2V0UGVuU3R5bGVzKHN0eWxlcyk7XG4gIH1cbiAgbW92ZUN1cnNvcihyZWxQb3MpIHtcbiAgICBjb25zdCByb3cgPSB0aGlzLnJvd3NbdGhpcy5jdXJyUm93XTtcbiAgICByb3cubW92ZUN1cnNvcihyZWxQb3MpO1xuICB9XG4gIHNldEN1cnNvcihhYnNQb3MpIHtcbiAgICB0aGlzLmxvZ2dlci5sb2coMiwgJ3NldEN1cnNvcjogJyArIGFic1Bvcyk7XG4gICAgY29uc3Qgcm93ID0gdGhpcy5yb3dzW3RoaXMuY3VyclJvd107XG4gICAgcm93LnNldEN1cnNvcihhYnNQb3MpO1xuICB9XG4gIHNldFBBQyhwYWNEYXRhKSB7XG4gICAgdGhpcy5sb2dnZXIubG9nKDIsICgpID0+ICdwYWNEYXRhID0gJyArIEpTT04uc3RyaW5naWZ5KHBhY0RhdGEpKTtcbiAgICBsZXQgbmV3Um93ID0gcGFjRGF0YS5yb3cgLSAxO1xuICAgIGlmICh0aGlzLm5yUm9sbFVwUm93cyAmJiBuZXdSb3cgPCB0aGlzLm5yUm9sbFVwUm93cyAtIDEpIHtcbiAgICAgIG5ld1JvdyA9IHRoaXMubnJSb2xsVXBSb3dzIC0gMTtcbiAgICB9XG5cbiAgICAvLyBNYWtlIHN1cmUgdGhpcyBvbmx5IGFmZmVjdHMgUm9sbC11cCBDYXB0aW9ucyBieSBjaGVja2luZyB0aGlzLm5yUm9sbFVwUm93c1xuICAgIGlmICh0aGlzLm5yUm9sbFVwUm93cyAmJiB0aGlzLmN1cnJSb3cgIT09IG5ld1Jvdykge1xuICAgICAgLy8gY2xlYXIgYWxsIHJvd3MgZmlyc3RcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTlJfUk9XUzsgaSsrKSB7XG4gICAgICAgIHRoaXMucm93c1tpXS5jbGVhcigpO1xuICAgICAgfVxuXG4gICAgICAvLyBDb3B5IHRoaXMubnJSb2xsVXBSb3dzIHJvd3MgZnJvbSBsYXN0T3V0cHV0U2NyZWVuIGFuZCBwbGFjZSBpdCBpbiB0aGUgbmV3Um93IGxvY2F0aW9uXG4gICAgICAvLyB0b3BSb3dJbmRleCAtIHRoZSBzdGFydCBvZiByb3dzIHRvIGNvcHkgKGluY2x1c2l2ZSBpbmRleClcbiAgICAgIGNvbnN0IHRvcFJvd0luZGV4ID0gdGhpcy5jdXJyUm93ICsgMSAtIHRoaXMubnJSb2xsVXBSb3dzO1xuICAgICAgLy8gV2Ugb25seSBjb3B5IGlmIHRoZSBsYXN0IHBvc2l0aW9uIHdhcyBhbHJlYWR5IHNob3duLlxuICAgICAgLy8gV2UgdXNlIHRoZSBjdWVTdGFydFRpbWUgdmFsdWUgdG8gY2hlY2sgdGhpcy5cbiAgICAgIGNvbnN0IGxhc3RPdXRwdXRTY3JlZW4gPSB0aGlzLmxhc3RPdXRwdXRTY3JlZW47XG4gICAgICBpZiAobGFzdE91dHB1dFNjcmVlbikge1xuICAgICAgICBjb25zdCBwcmV2TGluZVRpbWUgPSBsYXN0T3V0cHV0U2NyZWVuLnJvd3NbdG9wUm93SW5kZXhdLmN1ZVN0YXJ0VGltZTtcbiAgICAgICAgY29uc3QgdGltZSA9IHRoaXMubG9nZ2VyLnRpbWU7XG4gICAgICAgIGlmIChwcmV2TGluZVRpbWUgIT09IG51bGwgJiYgdGltZSAhPT0gbnVsbCAmJiBwcmV2TGluZVRpbWUgPCB0aW1lKSB7XG4gICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLm5yUm9sbFVwUm93czsgaSsrKSB7XG4gICAgICAgICAgICB0aGlzLnJvd3NbbmV3Um93IC0gdGhpcy5uclJvbGxVcFJvd3MgKyBpICsgMV0uY29weShsYXN0T3V0cHV0U2NyZWVuLnJvd3NbdG9wUm93SW5kZXggKyBpXSk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuY3VyclJvdyA9IG5ld1JvdztcbiAgICBjb25zdCByb3cgPSB0aGlzLnJvd3NbdGhpcy5jdXJyUm93XTtcbiAgICBpZiAocGFjRGF0YS5pbmRlbnQgIT09IG51bGwpIHtcbiAgICAgIGNvbnN0IGluZGVudCA9IHBhY0RhdGEuaW5kZW50O1xuICAgICAgY29uc3QgcHJldlBvcyA9IE1hdGgubWF4KGluZGVudCAtIDEsIDApO1xuICAgICAgcm93LnNldEN1cnNvcihwYWNEYXRhLmluZGVudCk7XG4gICAgICBwYWNEYXRhLmNvbG9yID0gcm93LmNoYXJzW3ByZXZQb3NdLnBlblN0YXRlLmZvcmVncm91bmQ7XG4gICAgfVxuICAgIGNvbnN0IHN0eWxlcyA9IHtcbiAgICAgIGZvcmVncm91bmQ6IHBhY0RhdGEuY29sb3IsXG4gICAgICB1bmRlcmxpbmU6IHBhY0RhdGEudW5kZXJsaW5lLFxuICAgICAgaXRhbGljczogcGFjRGF0YS5pdGFsaWNzLFxuICAgICAgYmFja2dyb3VuZDogJ2JsYWNrJyxcbiAgICAgIGZsYXNoOiBmYWxzZVxuICAgIH07XG4gICAgdGhpcy5zZXRQZW4oc3R5bGVzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZXQgYmFja2dyb3VuZC9leHRyYSBmb3JlZ3JvdW5kLCBidXQgZmlyc3QgZG8gYmFja19zcGFjZSwgYW5kIHRoZW4gaW5zZXJ0IHNwYWNlIChiYWNrd2FyZHMgY29tcGF0aWJpbGl0eSkuXG4gICAqL1xuICBzZXRCa2dEYXRhKGJrZ0RhdGEpIHtcbiAgICB0aGlzLmxvZ2dlci5sb2coMiwgKCkgPT4gJ2JrZ0RhdGEgPSAnICsgSlNPTi5zdHJpbmdpZnkoYmtnRGF0YSkpO1xuICAgIHRoaXMuYmFja1NwYWNlKCk7XG4gICAgdGhpcy5zZXRQZW4oYmtnRGF0YSk7XG4gICAgdGhpcy5pbnNlcnRDaGFyKDB4MjApOyAvLyBTcGFjZVxuICB9XG4gIHNldFJvbGxVcFJvd3MobnJSb3dzKSB7XG4gICAgdGhpcy5uclJvbGxVcFJvd3MgPSBuclJvd3M7XG4gIH1cbiAgcm9sbFVwKCkge1xuICAgIGlmICh0aGlzLm5yUm9sbFVwUm93cyA9PT0gbnVsbCkge1xuICAgICAgdGhpcy5sb2dnZXIubG9nKDMsICdyb2xsX3VwIGJ1dCBuclJvbGxVcFJvd3Mgbm90IHNldCB5ZXQnKTtcbiAgICAgIHJldHVybjsgLy8gTm90IHByb3Blcmx5IHNldHVwXG4gICAgfVxuICAgIHRoaXMubG9nZ2VyLmxvZygxLCAoKSA9PiB0aGlzLmdldERpc3BsYXlUZXh0KCkpO1xuICAgIGNvbnN0IHRvcFJvd0luZGV4ID0gdGhpcy5jdXJyUm93ICsgMSAtIHRoaXMubnJSb2xsVXBSb3dzO1xuICAgIGNvbnN0IHRvcFJvdyA9IHRoaXMucm93cy5zcGxpY2UodG9wUm93SW5kZXgsIDEpWzBdO1xuICAgIHRvcFJvdy5jbGVhcigpO1xuICAgIHRoaXMucm93cy5zcGxpY2UodGhpcy5jdXJyUm93LCAwLCB0b3BSb3cpO1xuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnUm9sbGluZyB1cCcpO1xuICAgIC8vIHRoaXMubG9nZ2VyLmxvZyhWZXJib3NlTGV2ZWwuVEVYVCwgdGhpcy5nZXRfZGlzcGxheV90ZXh0KCkpXG4gIH1cblxuICAvKipcbiAgICogR2V0IGFsbCBub24tZW1wdHkgcm93cyB3aXRoIGFzIHVuaWNvZGUgdGV4dC5cbiAgICovXG4gIGdldERpc3BsYXlUZXh0KGFzT25lUm93KSB7XG4gICAgYXNPbmVSb3cgPSBhc09uZVJvdyB8fCBmYWxzZTtcbiAgICBjb25zdCBkaXNwbGF5VGV4dCA9IFtdO1xuICAgIGxldCB0ZXh0ID0gJyc7XG4gICAgbGV0IHJvd05yID0gLTE7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBOUl9ST1dTOyBpKyspIHtcbiAgICAgIGNvbnN0IHJvd1RleHQgPSB0aGlzLnJvd3NbaV0uZ2V0VGV4dFN0cmluZygpO1xuICAgICAgaWYgKHJvd1RleHQpIHtcbiAgICAgICAgcm93TnIgPSBpICsgMTtcbiAgICAgICAgaWYgKGFzT25lUm93KSB7XG4gICAgICAgICAgZGlzcGxheVRleHQucHVzaCgnUm93ICcgKyByb3dOciArIFwiOiAnXCIgKyByb3dUZXh0ICsgXCInXCIpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGRpc3BsYXlUZXh0LnB1c2gocm93VGV4dC50cmltKCkpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChkaXNwbGF5VGV4dC5sZW5ndGggPiAwKSB7XG4gICAgICBpZiAoYXNPbmVSb3cpIHtcbiAgICAgICAgdGV4dCA9ICdbJyArIGRpc3BsYXlUZXh0LmpvaW4oJyB8ICcpICsgJ10nO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGV4dCA9IGRpc3BsYXlUZXh0LmpvaW4oJ1xcbicpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gdGV4dDtcbiAgfVxuICBnZXRUZXh0QW5kRm9ybWF0KCkge1xuICAgIHJldHVybiB0aGlzLnJvd3M7XG4gIH1cbn1cblxuLy8gdmFyIG1vZGVzID0gWydNT0RFX1JPTEwtVVAnLCAnTU9ERV9QT1AtT04nLCAnTU9ERV9QQUlOVC1PTicsICdNT0RFX1RFWFQnXTtcblxuY2xhc3MgQ2VhNjA4Q2hhbm5lbCB7XG4gIGNvbnN0cnVjdG9yKGNoYW5uZWxOdW1iZXIsIG91dHB1dEZpbHRlciwgbG9nZ2VyKSB7XG4gICAgdGhpcy5jaE5yID0gdm9pZCAwO1xuICAgIHRoaXMub3V0cHV0RmlsdGVyID0gdm9pZCAwO1xuICAgIHRoaXMubW9kZSA9IHZvaWQgMDtcbiAgICB0aGlzLnZlcmJvc2UgPSB2b2lkIDA7XG4gICAgdGhpcy5kaXNwbGF5ZWRNZW1vcnkgPSB2b2lkIDA7XG4gICAgdGhpcy5ub25EaXNwbGF5ZWRNZW1vcnkgPSB2b2lkIDA7XG4gICAgdGhpcy5sYXN0T3V0cHV0U2NyZWVuID0gdm9pZCAwO1xuICAgIHRoaXMuY3VyclJvbGxVcFJvdyA9IHZvaWQgMDtcbiAgICB0aGlzLndyaXRlU2NyZWVuID0gdm9pZCAwO1xuICAgIHRoaXMuY3VlU3RhcnRUaW1lID0gdm9pZCAwO1xuICAgIHRoaXMubG9nZ2VyID0gdm9pZCAwO1xuICAgIHRoaXMuY2hOciA9IGNoYW5uZWxOdW1iZXI7XG4gICAgdGhpcy5vdXRwdXRGaWx0ZXIgPSBvdXRwdXRGaWx0ZXI7XG4gICAgdGhpcy5tb2RlID0gbnVsbDtcbiAgICB0aGlzLnZlcmJvc2UgPSAwO1xuICAgIHRoaXMuZGlzcGxheWVkTWVtb3J5ID0gbmV3IENhcHRpb25TY3JlZW4obG9nZ2VyKTtcbiAgICB0aGlzLm5vbkRpc3BsYXllZE1lbW9yeSA9IG5ldyBDYXB0aW9uU2NyZWVuKGxvZ2dlcik7XG4gICAgdGhpcy5sYXN0T3V0cHV0U2NyZWVuID0gbmV3IENhcHRpb25TY3JlZW4obG9nZ2VyKTtcbiAgICB0aGlzLmN1cnJSb2xsVXBSb3cgPSB0aGlzLmRpc3BsYXllZE1lbW9yeS5yb3dzW05SX1JPV1MgLSAxXTtcbiAgICB0aGlzLndyaXRlU2NyZWVuID0gdGhpcy5kaXNwbGF5ZWRNZW1vcnk7XG4gICAgdGhpcy5tb2RlID0gbnVsbDtcbiAgICB0aGlzLmN1ZVN0YXJ0VGltZSA9IG51bGw7IC8vIEtlZXBzIHRyYWNrIG9mIHdoZXJlIGEgY3VlIHN0YXJ0ZWQuXG4gICAgdGhpcy5sb2dnZXIgPSBsb2dnZXI7XG4gIH1cbiAgcmVzZXQoKSB7XG4gICAgdGhpcy5tb2RlID0gbnVsbDtcbiAgICB0aGlzLmRpc3BsYXllZE1lbW9yeS5yZXNldCgpO1xuICAgIHRoaXMubm9uRGlzcGxheWVkTWVtb3J5LnJlc2V0KCk7XG4gICAgdGhpcy5sYXN0T3V0cHV0U2NyZWVuLnJlc2V0KCk7XG4gICAgdGhpcy5vdXRwdXRGaWx0ZXIucmVzZXQoKTtcbiAgICB0aGlzLmN1cnJSb2xsVXBSb3cgPSB0aGlzLmRpc3BsYXllZE1lbW9yeS5yb3dzW05SX1JPV1MgLSAxXTtcbiAgICB0aGlzLndyaXRlU2NyZWVuID0gdGhpcy5kaXNwbGF5ZWRNZW1vcnk7XG4gICAgdGhpcy5tb2RlID0gbnVsbDtcbiAgICB0aGlzLmN1ZVN0YXJ0VGltZSA9IG51bGw7XG4gIH1cbiAgZ2V0SGFuZGxlcigpIHtcbiAgICByZXR1cm4gdGhpcy5vdXRwdXRGaWx0ZXI7XG4gIH1cbiAgc2V0SGFuZGxlcihuZXdIYW5kbGVyKSB7XG4gICAgdGhpcy5vdXRwdXRGaWx0ZXIgPSBuZXdIYW5kbGVyO1xuICB9XG4gIHNldFBBQyhwYWNEYXRhKSB7XG4gICAgdGhpcy53cml0ZVNjcmVlbi5zZXRQQUMocGFjRGF0YSk7XG4gIH1cbiAgc2V0QmtnRGF0YShia2dEYXRhKSB7XG4gICAgdGhpcy53cml0ZVNjcmVlbi5zZXRCa2dEYXRhKGJrZ0RhdGEpO1xuICB9XG4gIHNldE1vZGUobmV3TW9kZSkge1xuICAgIGlmIChuZXdNb2RlID09PSB0aGlzLm1vZGUpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5tb2RlID0gbmV3TW9kZTtcbiAgICB0aGlzLmxvZ2dlci5sb2coMiwgKCkgPT4gJ01PREU9JyArIG5ld01vZGUpO1xuICAgIGlmICh0aGlzLm1vZGUgPT09ICdNT0RFX1BPUC1PTicpIHtcbiAgICAgIHRoaXMud3JpdGVTY3JlZW4gPSB0aGlzLm5vbkRpc3BsYXllZE1lbW9yeTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy53cml0ZVNjcmVlbiA9IHRoaXMuZGlzcGxheWVkTWVtb3J5O1xuICAgICAgdGhpcy53cml0ZVNjcmVlbi5yZXNldCgpO1xuICAgIH1cbiAgICBpZiAodGhpcy5tb2RlICE9PSAnTU9ERV9ST0xMLVVQJykge1xuICAgICAgdGhpcy5kaXNwbGF5ZWRNZW1vcnkubnJSb2xsVXBSb3dzID0gbnVsbDtcbiAgICAgIHRoaXMubm9uRGlzcGxheWVkTWVtb3J5Lm5yUm9sbFVwUm93cyA9IG51bGw7XG4gICAgfVxuICAgIHRoaXMubW9kZSA9IG5ld01vZGU7XG4gIH1cbiAgaW5zZXJ0Q2hhcnMoY2hhcnMpIHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGNoYXJzLmxlbmd0aDsgaSsrKSB7XG4gICAgICB0aGlzLndyaXRlU2NyZWVuLmluc2VydENoYXIoY2hhcnNbaV0pO1xuICAgIH1cbiAgICBjb25zdCBzY3JlZW4gPSB0aGlzLndyaXRlU2NyZWVuID09PSB0aGlzLmRpc3BsYXllZE1lbW9yeSA/ICdESVNQJyA6ICdOT05fRElTUCc7XG4gICAgdGhpcy5sb2dnZXIubG9nKDIsICgpID0+IHNjcmVlbiArICc6ICcgKyB0aGlzLndyaXRlU2NyZWVuLmdldERpc3BsYXlUZXh0KHRydWUpKTtcbiAgICBpZiAodGhpcy5tb2RlID09PSAnTU9ERV9QQUlOVC1PTicgfHwgdGhpcy5tb2RlID09PSAnTU9ERV9ST0xMLVVQJykge1xuICAgICAgdGhpcy5sb2dnZXIubG9nKDEsICgpID0+ICdESVNQTEFZRUQ6ICcgKyB0aGlzLmRpc3BsYXllZE1lbW9yeS5nZXREaXNwbGF5VGV4dCh0cnVlKSk7XG4gICAgICB0aGlzLm91dHB1dERhdGFVcGRhdGUoKTtcbiAgICB9XG4gIH1cbiAgY2NSQ0woKSB7XG4gICAgLy8gUmVzdW1lIENhcHRpb24gTG9hZGluZyAoc3dpdGNoIG1vZGUgdG8gUG9wIE9uKVxuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnUkNMIC0gUmVzdW1lIENhcHRpb24gTG9hZGluZycpO1xuICAgIHRoaXMuc2V0TW9kZSgnTU9ERV9QT1AtT04nKTtcbiAgfVxuICBjY0JTKCkge1xuICAgIC8vIEJhY2tTcGFjZVxuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnQlMgLSBCYWNrU3BhY2UnKTtcbiAgICBpZiAodGhpcy5tb2RlID09PSAnTU9ERV9URVhUJykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLndyaXRlU2NyZWVuLmJhY2tTcGFjZSgpO1xuICAgIGlmICh0aGlzLndyaXRlU2NyZWVuID09PSB0aGlzLmRpc3BsYXllZE1lbW9yeSkge1xuICAgICAgdGhpcy5vdXRwdXREYXRhVXBkYXRlKCk7XG4gICAgfVxuICB9XG4gIGNjQU9GKCkge1xuICAgIC8vIFJlc2VydmVkIChmb3JtZXJseSBBbGFybSBPZmYpXG4gIH1cbiAgY2NBT04oKSB7XG4gICAgLy8gUmVzZXJ2ZWQgKGZvcm1lcmx5IEFsYXJtIE9uKVxuICB9XG4gIGNjREVSKCkge1xuICAgIC8vIERlbGV0ZSB0byBFbmQgb2YgUm93XG4gICAgdGhpcy5sb2dnZXIubG9nKDIsICdERVItIERlbGV0ZSB0byBFbmQgb2YgUm93Jyk7XG4gICAgdGhpcy53cml0ZVNjcmVlbi5jbGVhclRvRW5kT2ZSb3coKTtcbiAgICB0aGlzLm91dHB1dERhdGFVcGRhdGUoKTtcbiAgfVxuICBjY1JVKG5yUm93cykge1xuICAgIC8vIFJvbGwtVXAgQ2FwdGlvbnMtMiwzLG9yIDQgUm93c1xuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnUlUoJyArIG5yUm93cyArICcpIC0gUm9sbCBVcCcpO1xuICAgIHRoaXMud3JpdGVTY3JlZW4gPSB0aGlzLmRpc3BsYXllZE1lbW9yeTtcbiAgICB0aGlzLnNldE1vZGUoJ01PREVfUk9MTC1VUCcpO1xuICAgIHRoaXMud3JpdGVTY3JlZW4uc2V0Um9sbFVwUm93cyhuclJvd3MpO1xuICB9XG4gIGNjRk9OKCkge1xuICAgIC8vIEZsYXNoIE9uXG4gICAgdGhpcy5sb2dnZXIubG9nKDIsICdGT04gLSBGbGFzaCBPbicpO1xuICAgIHRoaXMud3JpdGVTY3JlZW4uc2V0UGVuKHtcbiAgICAgIGZsYXNoOiB0cnVlXG4gICAgfSk7XG4gIH1cbiAgY2NSREMoKSB7XG4gICAgLy8gUmVzdW1lIERpcmVjdCBDYXB0aW9uaW5nIChzd2l0Y2ggbW9kZSB0byBQYWludE9uKVxuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnUkRDIC0gUmVzdW1lIERpcmVjdCBDYXB0aW9uaW5nJyk7XG4gICAgdGhpcy5zZXRNb2RlKCdNT0RFX1BBSU5ULU9OJyk7XG4gIH1cbiAgY2NUUigpIHtcbiAgICAvLyBUZXh0IFJlc3RhcnQgaW4gdGV4dCBtb2RlIChub3Qgc3VwcG9ydGVkLCBob3dldmVyKVxuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnVFInKTtcbiAgICB0aGlzLnNldE1vZGUoJ01PREVfVEVYVCcpO1xuICB9XG4gIGNjUlREKCkge1xuICAgIC8vIFJlc3VtZSBUZXh0IERpc3BsYXkgaW4gVGV4dCBtb2RlIChub3Qgc3VwcG9ydGVkLCBob3dldmVyKVxuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnUlREJyk7XG4gICAgdGhpcy5zZXRNb2RlKCdNT0RFX1RFWFQnKTtcbiAgfVxuICBjY0VETSgpIHtcbiAgICAvLyBFcmFzZSBEaXNwbGF5ZWQgTWVtb3J5XG4gICAgdGhpcy5sb2dnZXIubG9nKDIsICdFRE0gLSBFcmFzZSBEaXNwbGF5ZWQgTWVtb3J5Jyk7XG4gICAgdGhpcy5kaXNwbGF5ZWRNZW1vcnkucmVzZXQoKTtcbiAgICB0aGlzLm91dHB1dERhdGFVcGRhdGUodHJ1ZSk7XG4gIH1cbiAgY2NDUigpIHtcbiAgICAvLyBDYXJyaWFnZSBSZXR1cm5cbiAgICB0aGlzLmxvZ2dlci5sb2coMiwgJ0NSIC0gQ2FycmlhZ2UgUmV0dXJuJyk7XG4gICAgdGhpcy53cml0ZVNjcmVlbi5yb2xsVXAoKTtcbiAgICB0aGlzLm91dHB1dERhdGFVcGRhdGUodHJ1ZSk7XG4gIH1cbiAgY2NFTk0oKSB7XG4gICAgLy8gRXJhc2UgTm9uLURpc3BsYXllZCBNZW1vcnlcbiAgICB0aGlzLmxvZ2dlci5sb2coMiwgJ0VOTSAtIEVyYXNlIE5vbi1kaXNwbGF5ZWQgTWVtb3J5Jyk7XG4gICAgdGhpcy5ub25EaXNwbGF5ZWRNZW1vcnkucmVzZXQoKTtcbiAgfVxuICBjY0VPQygpIHtcbiAgICAvLyBFbmQgb2YgQ2FwdGlvbiAoRmxpcCBNZW1vcmllcylcbiAgICB0aGlzLmxvZ2dlci5sb2coMiwgJ0VPQyAtIEVuZCBPZiBDYXB0aW9uJyk7XG4gICAgaWYgKHRoaXMubW9kZSA9PT0gJ01PREVfUE9QLU9OJykge1xuICAgICAgY29uc3QgdG1wID0gdGhpcy5kaXNwbGF5ZWRNZW1vcnk7XG4gICAgICB0aGlzLmRpc3BsYXllZE1lbW9yeSA9IHRoaXMubm9uRGlzcGxheWVkTWVtb3J5O1xuICAgICAgdGhpcy5ub25EaXNwbGF5ZWRNZW1vcnkgPSB0bXA7XG4gICAgICB0aGlzLndyaXRlU2NyZWVuID0gdGhpcy5ub25EaXNwbGF5ZWRNZW1vcnk7XG4gICAgICB0aGlzLmxvZ2dlci5sb2coMSwgKCkgPT4gJ0RJU1A6ICcgKyB0aGlzLmRpc3BsYXllZE1lbW9yeS5nZXREaXNwbGF5VGV4dCgpKTtcbiAgICB9XG4gICAgdGhpcy5vdXRwdXREYXRhVXBkYXRlKHRydWUpO1xuICB9XG4gIGNjVE8obnJDb2xzKSB7XG4gICAgLy8gVGFiIE9mZnNldCAxLDIsIG9yIDMgY29sdW1uc1xuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnVE8oJyArIG5yQ29scyArICcpIC0gVGFiIE9mZnNldCcpO1xuICAgIHRoaXMud3JpdGVTY3JlZW4ubW92ZUN1cnNvcihuckNvbHMpO1xuICB9XG4gIGNjTUlEUk9XKHNlY29uZEJ5dGUpIHtcbiAgICAvLyBQYXJzZSBNSURST1cgY29tbWFuZFxuICAgIGNvbnN0IHN0eWxlcyA9IHtcbiAgICAgIGZsYXNoOiBmYWxzZVxuICAgIH07XG4gICAgc3R5bGVzLnVuZGVybGluZSA9IHNlY29uZEJ5dGUgJSAyID09PSAxO1xuICAgIHN0eWxlcy5pdGFsaWNzID0gc2Vjb25kQnl0ZSA+PSAweDJlO1xuICAgIGlmICghc3R5bGVzLml0YWxpY3MpIHtcbiAgICAgIGNvbnN0IGNvbG9ySW5kZXggPSBNYXRoLmZsb29yKHNlY29uZEJ5dGUgLyAyKSAtIDB4MTA7XG4gICAgICBjb25zdCBjb2xvcnMgPSBbJ3doaXRlJywgJ2dyZWVuJywgJ2JsdWUnLCAnY3lhbicsICdyZWQnLCAneWVsbG93JywgJ21hZ2VudGEnXTtcbiAgICAgIHN0eWxlcy5mb3JlZ3JvdW5kID0gY29sb3JzW2NvbG9ySW5kZXhdO1xuICAgIH0gZWxzZSB7XG4gICAgICBzdHlsZXMuZm9yZWdyb3VuZCA9ICd3aGl0ZSc7XG4gICAgfVxuICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnTUlEUk9XOiAnICsgSlNPTi5zdHJpbmdpZnkoc3R5bGVzKSk7XG4gICAgdGhpcy53cml0ZVNjcmVlbi5zZXRQZW4oc3R5bGVzKTtcbiAgfVxuICBvdXRwdXREYXRhVXBkYXRlKGRpc3BhdGNoID0gZmFsc2UpIHtcbiAgICBjb25zdCB0aW1lID0gdGhpcy5sb2dnZXIudGltZTtcbiAgICBpZiAodGltZSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAodGhpcy5vdXRwdXRGaWx0ZXIpIHtcbiAgICAgIGlmICh0aGlzLmN1ZVN0YXJ0VGltZSA9PT0gbnVsbCAmJiAhdGhpcy5kaXNwbGF5ZWRNZW1vcnkuaXNFbXB0eSgpKSB7XG4gICAgICAgIC8vIFN0YXJ0IG9mIGEgbmV3IGN1ZVxuICAgICAgICB0aGlzLmN1ZVN0YXJ0VGltZSA9IHRpbWU7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAoIXRoaXMuZGlzcGxheWVkTWVtb3J5LmVxdWFscyh0aGlzLmxhc3RPdXRwdXRTY3JlZW4pKSB7XG4gICAgICAgICAgdGhpcy5vdXRwdXRGaWx0ZXIubmV3Q3VlKHRoaXMuY3VlU3RhcnRUaW1lLCB0aW1lLCB0aGlzLmxhc3RPdXRwdXRTY3JlZW4pO1xuICAgICAgICAgIGlmIChkaXNwYXRjaCAmJiB0aGlzLm91dHB1dEZpbHRlci5kaXNwYXRjaEN1ZSkge1xuICAgICAgICAgICAgdGhpcy5vdXRwdXRGaWx0ZXIuZGlzcGF0Y2hDdWUoKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5jdWVTdGFydFRpbWUgPSB0aGlzLmRpc3BsYXllZE1lbW9yeS5pc0VtcHR5KCkgPyBudWxsIDogdGltZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdGhpcy5sYXN0T3V0cHV0U2NyZWVuLmNvcHkodGhpcy5kaXNwbGF5ZWRNZW1vcnkpO1xuICAgIH1cbiAgfVxuICBjdWVTcGxpdEF0VGltZSh0KSB7XG4gICAgaWYgKHRoaXMub3V0cHV0RmlsdGVyKSB7XG4gICAgICBpZiAoIXRoaXMuZGlzcGxheWVkTWVtb3J5LmlzRW1wdHkoKSkge1xuICAgICAgICBpZiAodGhpcy5vdXRwdXRGaWx0ZXIubmV3Q3VlKSB7XG4gICAgICAgICAgdGhpcy5vdXRwdXRGaWx0ZXIubmV3Q3VlKHRoaXMuY3VlU3RhcnRUaW1lLCB0LCB0aGlzLmRpc3BsYXllZE1lbW9yeSk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5jdWVTdGFydFRpbWUgPSB0O1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vLyBXaWxsIGJlIDEgb3IgMiB3aGVuIHBhcnNpbmcgY2FwdGlvbnNcblxuY2xhc3MgQ2VhNjA4UGFyc2VyIHtcbiAgY29uc3RydWN0b3IoZmllbGQsIG91dDEsIG91dDIpIHtcbiAgICB0aGlzLmNoYW5uZWxzID0gdm9pZCAwO1xuICAgIHRoaXMuY3VycmVudENoYW5uZWwgPSAwO1xuICAgIHRoaXMuY21kSGlzdG9yeSA9IGNyZWF0ZUNtZEhpc3RvcnkoKTtcbiAgICB0aGlzLmxvZ2dlciA9IHZvaWQgMDtcbiAgICBjb25zdCBsb2dnZXIgPSB0aGlzLmxvZ2dlciA9IG5ldyBDYXB0aW9uc0xvZ2dlcigpO1xuICAgIHRoaXMuY2hhbm5lbHMgPSBbbnVsbCwgbmV3IENlYTYwOENoYW5uZWwoZmllbGQsIG91dDEsIGxvZ2dlciksIG5ldyBDZWE2MDhDaGFubmVsKGZpZWxkICsgMSwgb3V0MiwgbG9nZ2VyKV07XG4gIH1cbiAgZ2V0SGFuZGxlcihjaGFubmVsKSB7XG4gICAgcmV0dXJuIHRoaXMuY2hhbm5lbHNbY2hhbm5lbF0uZ2V0SGFuZGxlcigpO1xuICB9XG4gIHNldEhhbmRsZXIoY2hhbm5lbCwgbmV3SGFuZGxlcikge1xuICAgIHRoaXMuY2hhbm5lbHNbY2hhbm5lbF0uc2V0SGFuZGxlcihuZXdIYW5kbGVyKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGQgZGF0YSBmb3IgdGltZSB0IGluIGZvcm1zIG9mIGxpc3Qgb2YgYnl0ZXMgKHVuc2lnbmVkIGludHMpLiBUaGUgYnl0ZXMgYXJlIHRyZWF0ZWQgYXMgcGFpcnMuXG4gICAqL1xuICBhZGREYXRhKHRpbWUsIGJ5dGVMaXN0KSB7XG4gICAgdGhpcy5sb2dnZXIudGltZSA9IHRpbWU7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBieXRlTGlzdC5sZW5ndGg7IGkgKz0gMikge1xuICAgICAgY29uc3QgYSA9IGJ5dGVMaXN0W2ldICYgMHg3ZjtcbiAgICAgIGNvbnN0IGIgPSBieXRlTGlzdFtpICsgMV0gJiAweDdmO1xuICAgICAgbGV0IGNtZEZvdW5kID0gZmFsc2U7XG4gICAgICBsZXQgY2hhcnNGb3VuZCA9IG51bGw7XG4gICAgICBpZiAoYSA9PT0gMCAmJiBiID09PSAwKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5sb2dnZXIubG9nKDMsICgpID0+ICdbJyArIG51bUFycmF5VG9IZXhBcnJheShbYnl0ZUxpc3RbaV0sIGJ5dGVMaXN0W2kgKyAxXV0pICsgJ10gLT4gKCcgKyBudW1BcnJheVRvSGV4QXJyYXkoW2EsIGJdKSArICcpJyk7XG4gICAgICB9XG4gICAgICBjb25zdCBjbWRIaXN0b3J5ID0gdGhpcy5jbWRIaXN0b3J5O1xuICAgICAgY29uc3QgaXNDb250cm9sQ29kZSA9IGEgPj0gMHgxMCAmJiBhIDw9IDB4MWY7XG4gICAgICBpZiAoaXNDb250cm9sQ29kZSkge1xuICAgICAgICAvLyBTa2lwIHJlZHVuZGFudCBjb250cm9sIGNvZGVzXG4gICAgICAgIGlmIChoYXNDbWRSZXBlYXRlZChhLCBiLCBjbWRIaXN0b3J5KSkge1xuICAgICAgICAgIHNldExhc3RDbWQobnVsbCwgbnVsbCwgY21kSGlzdG9yeSk7XG4gICAgICAgICAgdGhpcy5sb2dnZXIubG9nKDMsICgpID0+ICdSZXBlYXRlZCBjb21tYW5kICgnICsgbnVtQXJyYXlUb0hleEFycmF5KFthLCBiXSkgKyAnKSBpcyBkcm9wcGVkJyk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgc2V0TGFzdENtZChhLCBiLCB0aGlzLmNtZEhpc3RvcnkpO1xuICAgICAgICBjbWRGb3VuZCA9IHRoaXMucGFyc2VDbWQoYSwgYik7XG4gICAgICAgIGlmICghY21kRm91bmQpIHtcbiAgICAgICAgICBjbWRGb3VuZCA9IHRoaXMucGFyc2VNaWRyb3coYSwgYik7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFjbWRGb3VuZCkge1xuICAgICAgICAgIGNtZEZvdW5kID0gdGhpcy5wYXJzZVBBQyhhLCBiKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoIWNtZEZvdW5kKSB7XG4gICAgICAgICAgY21kRm91bmQgPSB0aGlzLnBhcnNlQmFja2dyb3VuZEF0dHJpYnV0ZXMoYSwgYik7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHNldExhc3RDbWQobnVsbCwgbnVsbCwgY21kSGlzdG9yeSk7XG4gICAgICB9XG4gICAgICBpZiAoIWNtZEZvdW5kKSB7XG4gICAgICAgIGNoYXJzRm91bmQgPSB0aGlzLnBhcnNlQ2hhcnMoYSwgYik7XG4gICAgICAgIGlmIChjaGFyc0ZvdW5kKSB7XG4gICAgICAgICAgY29uc3QgY3VyckNoTnIgPSB0aGlzLmN1cnJlbnRDaGFubmVsO1xuICAgICAgICAgIGlmIChjdXJyQ2hOciAmJiBjdXJyQ2hOciA+IDApIHtcbiAgICAgICAgICAgIGNvbnN0IGNoYW5uZWwgPSB0aGlzLmNoYW5uZWxzW2N1cnJDaE5yXTtcbiAgICAgICAgICAgIGNoYW5uZWwuaW5zZXJ0Q2hhcnMoY2hhcnNGb3VuZCk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMubG9nZ2VyLmxvZygyLCAnTm8gY2hhbm5lbCBmb3VuZCB5ZXQuIFRFWFQtTU9ERT8nKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmICghY21kRm91bmQgJiYgIWNoYXJzRm91bmQpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIubG9nKDIsICgpID0+IFwiQ291bGRuJ3QgcGFyc2UgY2xlYW5lZCBkYXRhIFwiICsgbnVtQXJyYXlUb0hleEFycmF5KFthLCBiXSkgKyAnIG9yaWc6ICcgKyBudW1BcnJheVRvSGV4QXJyYXkoW2J5dGVMaXN0W2ldLCBieXRlTGlzdFtpICsgMV1dKSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFBhcnNlIENvbW1hbmQuXG4gICAqIEByZXR1cm5zIFRydWUgaWYgYSBjb21tYW5kIHdhcyBmb3VuZFxuICAgKi9cbiAgcGFyc2VDbWQoYSwgYikge1xuICAgIGNvbnN0IGNvbmQxID0gKGEgPT09IDB4MTQgfHwgYSA9PT0gMHgxYyB8fCBhID09PSAweDE1IHx8IGEgPT09IDB4MWQpICYmIGIgPj0gMHgyMCAmJiBiIDw9IDB4MmY7XG4gICAgY29uc3QgY29uZDIgPSAoYSA9PT0gMHgxNyB8fCBhID09PSAweDFmKSAmJiBiID49IDB4MjEgJiYgYiA8PSAweDIzO1xuICAgIGlmICghKGNvbmQxIHx8IGNvbmQyKSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBjb25zdCBjaE5yID0gYSA9PT0gMHgxNCB8fCBhID09PSAweDE1IHx8IGEgPT09IDB4MTcgPyAxIDogMjtcbiAgICBjb25zdCBjaGFubmVsID0gdGhpcy5jaGFubmVsc1tjaE5yXTtcbiAgICBpZiAoYSA9PT0gMHgxNCB8fCBhID09PSAweDE1IHx8IGEgPT09IDB4MWMgfHwgYSA9PT0gMHgxZCkge1xuICAgICAgaWYgKGIgPT09IDB4MjApIHtcbiAgICAgICAgY2hhbm5lbC5jY1JDTCgpO1xuICAgICAgfSBlbHNlIGlmIChiID09PSAweDIxKSB7XG4gICAgICAgIGNoYW5uZWwuY2NCUygpO1xuICAgICAgfSBlbHNlIGlmIChiID09PSAweDIyKSB7XG4gICAgICAgIGNoYW5uZWwuY2NBT0YoKTtcbiAgICAgIH0gZWxzZSBpZiAoYiA9PT0gMHgyMykge1xuICAgICAgICBjaGFubmVsLmNjQU9OKCk7XG4gICAgICB9IGVsc2UgaWYgKGIgPT09IDB4MjQpIHtcbiAgICAgICAgY2hhbm5lbC5jY0RFUigpO1xuICAgICAgfSBlbHNlIGlmIChiID09PSAweDI1KSB7XG4gICAgICAgIGNoYW5uZWwuY2NSVSgyKTtcbiAgICAgIH0gZWxzZSBpZiAoYiA9PT0gMHgyNikge1xuICAgICAgICBjaGFubmVsLmNjUlUoMyk7XG4gICAgICB9IGVsc2UgaWYgKGIgPT09IDB4MjcpIHtcbiAgICAgICAgY2hhbm5lbC5jY1JVKDQpO1xuICAgICAgfSBlbHNlIGlmIChiID09PSAweDI4KSB7XG4gICAgICAgIGNoYW5uZWwuY2NGT04oKTtcbiAgICAgIH0gZWxzZSBpZiAoYiA9PT0gMHgyOSkge1xuICAgICAgICBjaGFubmVsLmNjUkRDKCk7XG4gICAgICB9IGVsc2UgaWYgKGIgPT09IDB4MmEpIHtcbiAgICAgICAgY2hhbm5lbC5jY1RSKCk7XG4gICAgICB9IGVsc2UgaWYgKGIgPT09IDB4MmIpIHtcbiAgICAgICAgY2hhbm5lbC5jY1JURCgpO1xuICAgICAgfSBlbHNlIGlmIChiID09PSAweDJjKSB7XG4gICAgICAgIGNoYW5uZWwuY2NFRE0oKTtcbiAgICAgIH0gZWxzZSBpZiAoYiA9PT0gMHgyZCkge1xuICAgICAgICBjaGFubmVsLmNjQ1IoKTtcbiAgICAgIH0gZWxzZSBpZiAoYiA9PT0gMHgyZSkge1xuICAgICAgICBjaGFubmVsLmNjRU5NKCk7XG4gICAgICB9IGVsc2UgaWYgKGIgPT09IDB4MmYpIHtcbiAgICAgICAgY2hhbm5lbC5jY0VPQygpO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBhID09IDB4MTcgfHwgYSA9PSAweDFGXG4gICAgICBjaGFubmVsLmNjVE8oYiAtIDB4MjApO1xuICAgIH1cbiAgICB0aGlzLmN1cnJlbnRDaGFubmVsID0gY2hOcjtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBQYXJzZSBtaWRyb3cgc3R5bGluZyBjb21tYW5kXG4gICAqL1xuICBwYXJzZU1pZHJvdyhhLCBiKSB7XG4gICAgbGV0IGNoTnIgPSAwO1xuICAgIGlmICgoYSA9PT0gMHgxMSB8fCBhID09PSAweDE5KSAmJiBiID49IDB4MjAgJiYgYiA8PSAweDJmKSB7XG4gICAgICBpZiAoYSA9PT0gMHgxMSkge1xuICAgICAgICBjaE5yID0gMTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNoTnIgPSAyO1xuICAgICAgfVxuICAgICAgaWYgKGNoTnIgIT09IHRoaXMuY3VycmVudENoYW5uZWwpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIubG9nKDAsICdNaXNtYXRjaCBjaGFubmVsIGluIG1pZHJvdyBwYXJzaW5nJyk7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGNoYW5uZWwgPSB0aGlzLmNoYW5uZWxzW2NoTnJdO1xuICAgICAgaWYgKCFjaGFubmVsKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIGNoYW5uZWwuY2NNSURST1coYik7XG4gICAgICB0aGlzLmxvZ2dlci5sb2coMywgKCkgPT4gJ01JRFJPVyAoJyArIG51bUFycmF5VG9IZXhBcnJheShbYSwgYl0pICsgJyknKTtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogUGFyc2UgUHJlYWJsZSBBY2Nlc3MgQ29kZXMgKFRhYmxlIDUzKS5cbiAgICogQHJldHVybnMge0Jvb2xlYW59IFRlbGxzIGlmIFBBQyBmb3VuZFxuICAgKi9cbiAgcGFyc2VQQUMoYSwgYikge1xuICAgIGxldCByb3c7XG4gICAgY29uc3QgY2FzZTEgPSAoYSA+PSAweDExICYmIGEgPD0gMHgxNyB8fCBhID49IDB4MTkgJiYgYSA8PSAweDFmKSAmJiBiID49IDB4NDAgJiYgYiA8PSAweDdmO1xuICAgIGNvbnN0IGNhc2UyID0gKGEgPT09IDB4MTAgfHwgYSA9PT0gMHgxOCkgJiYgYiA+PSAweDQwICYmIGIgPD0gMHg1ZjtcbiAgICBpZiAoIShjYXNlMSB8fCBjYXNlMikpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgY29uc3QgY2hOciA9IGEgPD0gMHgxNyA/IDEgOiAyO1xuICAgIGlmIChiID49IDB4NDAgJiYgYiA8PSAweDVmKSB7XG4gICAgICByb3cgPSBjaE5yID09PSAxID8gcm93c0xvd0NoMVthXSA6IHJvd3NMb3dDaDJbYV07XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIDB4NjAgPD0gYiA8PSAweDdGXG4gICAgICByb3cgPSBjaE5yID09PSAxID8gcm93c0hpZ2hDaDFbYV0gOiByb3dzSGlnaENoMlthXTtcbiAgICB9XG4gICAgY29uc3QgY2hhbm5lbCA9IHRoaXMuY2hhbm5lbHNbY2hOcl07XG4gICAgaWYgKCFjaGFubmVsKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGNoYW5uZWwuc2V0UEFDKHRoaXMuaW50ZXJwcmV0UEFDKHJvdywgYikpO1xuICAgIHRoaXMuY3VycmVudENoYW5uZWwgPSBjaE5yO1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgLyoqXG4gICAqIEludGVycHJldCB0aGUgc2Vjb25kIGJ5dGUgb2YgdGhlIHBhYywgYW5kIHJldHVybiB0aGUgaW5mb3JtYXRpb24uXG4gICAqIEByZXR1cm5zIHBhY0RhdGEgd2l0aCBzdHlsZSBwYXJhbWV0ZXJzXG4gICAqL1xuICBpbnRlcnByZXRQQUMocm93LCBieXRlKSB7XG4gICAgbGV0IHBhY0luZGV4O1xuICAgIGNvbnN0IHBhY0RhdGEgPSB7XG4gICAgICBjb2xvcjogbnVsbCxcbiAgICAgIGl0YWxpY3M6IGZhbHNlLFxuICAgICAgaW5kZW50OiBudWxsLFxuICAgICAgdW5kZXJsaW5lOiBmYWxzZSxcbiAgICAgIHJvdzogcm93XG4gICAgfTtcbiAgICBpZiAoYnl0ZSA+IDB4NWYpIHtcbiAgICAgIHBhY0luZGV4ID0gYnl0ZSAtIDB4NjA7XG4gICAgfSBlbHNlIHtcbiAgICAgIHBhY0luZGV4ID0gYnl0ZSAtIDB4NDA7XG4gICAgfVxuICAgIHBhY0RhdGEudW5kZXJsaW5lID0gKHBhY0luZGV4ICYgMSkgPT09IDE7XG4gICAgaWYgKHBhY0luZGV4IDw9IDB4ZCkge1xuICAgICAgcGFjRGF0YS5jb2xvciA9IFsnd2hpdGUnLCAnZ3JlZW4nLCAnYmx1ZScsICdjeWFuJywgJ3JlZCcsICd5ZWxsb3cnLCAnbWFnZW50YScsICd3aGl0ZSddW01hdGguZmxvb3IocGFjSW5kZXggLyAyKV07XG4gICAgfSBlbHNlIGlmIChwYWNJbmRleCA8PSAweGYpIHtcbiAgICAgIHBhY0RhdGEuaXRhbGljcyA9IHRydWU7XG4gICAgICBwYWNEYXRhLmNvbG9yID0gJ3doaXRlJztcbiAgICB9IGVsc2Uge1xuICAgICAgcGFjRGF0YS5pbmRlbnQgPSBNYXRoLmZsb29yKChwYWNJbmRleCAtIDB4MTApIC8gMikgKiA0O1xuICAgIH1cbiAgICByZXR1cm4gcGFjRGF0YTsgLy8gTm90ZSB0aGF0IHJvdyBoYXMgemVybyBvZmZzZXQuIFRoZSBzcGVjIHVzZXMgMS5cbiAgfVxuXG4gIC8qKlxuICAgKiBQYXJzZSBjaGFyYWN0ZXJzLlxuICAgKiBAcmV0dXJucyBBbiBhcnJheSB3aXRoIDEgdG8gMiBjb2RlcyBjb3JyZXNwb25kaW5nIHRvIGNoYXJzLCBpZiBmb3VuZC4gbnVsbCBvdGhlcndpc2UuXG4gICAqL1xuICBwYXJzZUNoYXJzKGEsIGIpIHtcbiAgICBsZXQgY2hhbm5lbE5yO1xuICAgIGxldCBjaGFyQ29kZXMgPSBudWxsO1xuICAgIGxldCBjaGFyQ29kZTEgPSBudWxsO1xuICAgIGlmIChhID49IDB4MTkpIHtcbiAgICAgIGNoYW5uZWxOciA9IDI7XG4gICAgICBjaGFyQ29kZTEgPSBhIC0gODtcbiAgICB9IGVsc2Uge1xuICAgICAgY2hhbm5lbE5yID0gMTtcbiAgICAgIGNoYXJDb2RlMSA9IGE7XG4gICAgfVxuICAgIGlmIChjaGFyQ29kZTEgPj0gMHgxMSAmJiBjaGFyQ29kZTEgPD0gMHgxMykge1xuICAgICAgLy8gU3BlY2lhbCBjaGFyYWN0ZXJcbiAgICAgIGxldCBvbmVDb2RlO1xuICAgICAgaWYgKGNoYXJDb2RlMSA9PT0gMHgxMSkge1xuICAgICAgICBvbmVDb2RlID0gYiArIDB4NTA7XG4gICAgICB9IGVsc2UgaWYgKGNoYXJDb2RlMSA9PT0gMHgxMikge1xuICAgICAgICBvbmVDb2RlID0gYiArIDB4NzA7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBvbmVDb2RlID0gYiArIDB4OTA7XG4gICAgICB9XG4gICAgICB0aGlzLmxvZ2dlci5sb2coMiwgKCkgPT4gXCJTcGVjaWFsIGNoYXIgJ1wiICsgZ2V0Q2hhckZvckJ5dGUob25lQ29kZSkgKyBcIicgaW4gY2hhbm5lbCBcIiArIGNoYW5uZWxOcik7XG4gICAgICBjaGFyQ29kZXMgPSBbb25lQ29kZV07XG4gICAgfSBlbHNlIGlmIChhID49IDB4MjAgJiYgYSA8PSAweDdmKSB7XG4gICAgICBjaGFyQ29kZXMgPSBiID09PSAwID8gW2FdIDogW2EsIGJdO1xuICAgIH1cbiAgICBpZiAoY2hhckNvZGVzKSB7XG4gICAgICB0aGlzLmxvZ2dlci5sb2coMywgKCkgPT4gJ0NoYXIgY29kZXMgPSAgJyArIG51bUFycmF5VG9IZXhBcnJheShjaGFyQ29kZXMpLmpvaW4oJywnKSk7XG4gICAgfVxuICAgIHJldHVybiBjaGFyQ29kZXM7XG4gIH1cblxuICAvKipcbiAgICogUGFyc2UgZXh0ZW5kZWQgYmFja2dyb3VuZCBhdHRyaWJ1dGVzIGFzIHdlbGwgYXMgbmV3IGZvcmVncm91bmQgY29sb3IgYmxhY2suXG4gICAqIEByZXR1cm5zIFRydWUgaWYgYmFja2dyb3VuZCBhdHRyaWJ1dGVzIGFyZSBmb3VuZFxuICAgKi9cbiAgcGFyc2VCYWNrZ3JvdW5kQXR0cmlidXRlcyhhLCBiKSB7XG4gICAgY29uc3QgY2FzZTEgPSAoYSA9PT0gMHgxMCB8fCBhID09PSAweDE4KSAmJiBiID49IDB4MjAgJiYgYiA8PSAweDJmO1xuICAgIGNvbnN0IGNhc2UyID0gKGEgPT09IDB4MTcgfHwgYSA9PT0gMHgxZikgJiYgYiA+PSAweDJkICYmIGIgPD0gMHgyZjtcbiAgICBpZiAoIShjYXNlMSB8fCBjYXNlMikpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgbGV0IGluZGV4O1xuICAgIGNvbnN0IGJrZ0RhdGEgPSB7fTtcbiAgICBpZiAoYSA9PT0gMHgxMCB8fCBhID09PSAweDE4KSB7XG4gICAgICBpbmRleCA9IE1hdGguZmxvb3IoKGIgLSAweDIwKSAvIDIpO1xuICAgICAgYmtnRGF0YS5iYWNrZ3JvdW5kID0gYmFja2dyb3VuZENvbG9yc1tpbmRleF07XG4gICAgICBpZiAoYiAlIDIgPT09IDEpIHtcbiAgICAgICAgYmtnRGF0YS5iYWNrZ3JvdW5kID0gYmtnRGF0YS5iYWNrZ3JvdW5kICsgJ19zZW1pJztcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKGIgPT09IDB4MmQpIHtcbiAgICAgIGJrZ0RhdGEuYmFja2dyb3VuZCA9ICd0cmFuc3BhcmVudCc7XG4gICAgfSBlbHNlIHtcbiAgICAgIGJrZ0RhdGEuZm9yZWdyb3VuZCA9ICdibGFjayc7XG4gICAgICBpZiAoYiA9PT0gMHgyZikge1xuICAgICAgICBia2dEYXRhLnVuZGVybGluZSA9IHRydWU7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IGNoTnIgPSBhIDw9IDB4MTcgPyAxIDogMjtcbiAgICBjb25zdCBjaGFubmVsID0gdGhpcy5jaGFubmVsc1tjaE5yXTtcbiAgICBjaGFubmVsLnNldEJrZ0RhdGEoYmtnRGF0YSk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICAvKipcbiAgICogUmVzZXQgc3RhdGUgb2YgcGFyc2VyIGFuZCBpdHMgY2hhbm5lbHMuXG4gICAqL1xuICByZXNldCgpIHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IE9iamVjdC5rZXlzKHRoaXMuY2hhbm5lbHMpLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBjaGFubmVsID0gdGhpcy5jaGFubmVsc1tpXTtcbiAgICAgIGlmIChjaGFubmVsKSB7XG4gICAgICAgIGNoYW5uZWwucmVzZXQoKTtcbiAgICAgIH1cbiAgICB9XG4gICAgc2V0TGFzdENtZChudWxsLCBudWxsLCB0aGlzLmNtZEhpc3RvcnkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFRyaWdnZXIgdGhlIGdlbmVyYXRpb24gb2YgYSBjdWUsIGFuZCB0aGUgc3RhcnQgb2YgYSBuZXcgb25lIGlmIGRpc3BsYXlTY3JlZW5zIGFyZSBub3QgZW1wdHkuXG4gICAqL1xuICBjdWVTcGxpdEF0VGltZSh0KSB7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLmNoYW5uZWxzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBjaGFubmVsID0gdGhpcy5jaGFubmVsc1tpXTtcbiAgICAgIGlmIChjaGFubmVsKSB7XG4gICAgICAgIGNoYW5uZWwuY3VlU3BsaXRBdFRpbWUodCk7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5mdW5jdGlvbiBzZXRMYXN0Q21kKGEsIGIsIGNtZEhpc3RvcnkpIHtcbiAgY21kSGlzdG9yeS5hID0gYTtcbiAgY21kSGlzdG9yeS5iID0gYjtcbn1cbmZ1bmN0aW9uIGhhc0NtZFJlcGVhdGVkKGEsIGIsIGNtZEhpc3RvcnkpIHtcbiAgcmV0dXJuIGNtZEhpc3RvcnkuYSA9PT0gYSAmJiBjbWRIaXN0b3J5LmIgPT09IGI7XG59XG5mdW5jdGlvbiBjcmVhdGVDbWRIaXN0b3J5KCkge1xuICByZXR1cm4ge1xuICAgIGE6IG51bGwsXG4gICAgYjogbnVsbFxuICB9O1xufVxuXG5jbGFzcyBPdXRwdXRGaWx0ZXIge1xuICBjb25zdHJ1Y3Rvcih0aW1lbGluZUNvbnRyb2xsZXIsIHRyYWNrTmFtZSkge1xuICAgIHRoaXMudGltZWxpbmVDb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMuY3VlUmFuZ2VzID0gW107XG4gICAgdGhpcy50cmFja05hbWUgPSB2b2lkIDA7XG4gICAgdGhpcy5zdGFydFRpbWUgPSBudWxsO1xuICAgIHRoaXMuZW5kVGltZSA9IG51bGw7XG4gICAgdGhpcy5zY3JlZW4gPSBudWxsO1xuICAgIHRoaXMudGltZWxpbmVDb250cm9sbGVyID0gdGltZWxpbmVDb250cm9sbGVyO1xuICAgIHRoaXMudHJhY2tOYW1lID0gdHJhY2tOYW1lO1xuICB9XG4gIGRpc3BhdGNoQ3VlKCkge1xuICAgIGlmICh0aGlzLnN0YXJ0VGltZSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLnRpbWVsaW5lQ29udHJvbGxlci5hZGRDdWVzKHRoaXMudHJhY2tOYW1lLCB0aGlzLnN0YXJ0VGltZSwgdGhpcy5lbmRUaW1lLCB0aGlzLnNjcmVlbiwgdGhpcy5jdWVSYW5nZXMpO1xuICAgIHRoaXMuc3RhcnRUaW1lID0gbnVsbDtcbiAgfVxuICBuZXdDdWUoc3RhcnRUaW1lLCBlbmRUaW1lLCBzY3JlZW4pIHtcbiAgICBpZiAodGhpcy5zdGFydFRpbWUgPT09IG51bGwgfHwgdGhpcy5zdGFydFRpbWUgPiBzdGFydFRpbWUpIHtcbiAgICAgIHRoaXMuc3RhcnRUaW1lID0gc3RhcnRUaW1lO1xuICAgIH1cbiAgICB0aGlzLmVuZFRpbWUgPSBlbmRUaW1lO1xuICAgIHRoaXMuc2NyZWVuID0gc2NyZWVuO1xuICAgIHRoaXMudGltZWxpbmVDb250cm9sbGVyLmNyZWF0ZUNhcHRpb25zVHJhY2sodGhpcy50cmFja05hbWUpO1xuICB9XG4gIHJlc2V0KCkge1xuICAgIHRoaXMuY3VlUmFuZ2VzID0gW107XG4gICAgdGhpcy5zdGFydFRpbWUgPSBudWxsO1xuICB9XG59XG5cbi8qKlxuICogQ29weXJpZ2h0IDIwMTMgdnR0LmpzIENvbnRyaWJ1dG9yc1xuICpcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAnTGljZW5zZScpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjBcbiAqXG4gKiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4gKiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAnQVMgSVMnIEJBU0lTLFxuICogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gKiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4gKiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS5cbiAqL1xuXG52YXIgVlRUQ3VlID0gKGZ1bmN0aW9uICgpIHtcbiAgaWYgKG9wdGlvbmFsU2VsZiAhPSBudWxsICYmIG9wdGlvbmFsU2VsZi5WVFRDdWUpIHtcbiAgICByZXR1cm4gc2VsZi5WVFRDdWU7XG4gIH1cbiAgY29uc3QgQWxsb3dlZERpcmVjdGlvbnMgPSBbJycsICdscicsICdybCddO1xuICBjb25zdCBBbGxvd2VkQWxpZ25tZW50cyA9IFsnc3RhcnQnLCAnbWlkZGxlJywgJ2VuZCcsICdsZWZ0JywgJ3JpZ2h0J107XG4gIGZ1bmN0aW9uIGlzQWxsb3dlZFZhbHVlKGFsbG93ZWQsIHZhbHVlKSB7XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gJ3N0cmluZycpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgLy8gbmVjZXNzYXJ5IGZvciBhc3N1cmluZyB0aGUgZ2VuZXJpYyBjb25mb3JtcyB0byB0aGUgQXJyYXkgaW50ZXJmYWNlXG4gICAgaWYgKCFBcnJheS5pc0FycmF5KGFsbG93ZWQpKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIC8vIHJlc2V0IHRoZSB0eXBlIHNvIHRoYXQgdGhlIG5leHQgbmFycm93aW5nIHdvcmtzIHdlbGxcbiAgICBjb25zdCBsY1ZhbHVlID0gdmFsdWUudG9Mb3dlckNhc2UoKTtcbiAgICAvLyB1c2UgdGhlIGFsbG93IGxpc3QgdG8gbmFycm93IHRoZSB0eXBlIHRvIGEgc3BlY2lmaWMgc3Vic2V0IG9mIHN0cmluZ3NcbiAgICBpZiAofmFsbG93ZWQuaW5kZXhPZihsY1ZhbHVlKSkge1xuICAgICAgcmV0dXJuIGxjVmFsdWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBmdW5jdGlvbiBmaW5kRGlyZWN0aW9uU2V0dGluZyh2YWx1ZSkge1xuICAgIHJldHVybiBpc0FsbG93ZWRWYWx1ZShBbGxvd2VkRGlyZWN0aW9ucywgdmFsdWUpO1xuICB9XG4gIGZ1bmN0aW9uIGZpbmRBbGlnblNldHRpbmcodmFsdWUpIHtcbiAgICByZXR1cm4gaXNBbGxvd2VkVmFsdWUoQWxsb3dlZEFsaWdubWVudHMsIHZhbHVlKTtcbiAgfVxuICBmdW5jdGlvbiBleHRlbmQob2JqLCAuLi5yZXN0KSB7XG4gICAgbGV0IGkgPSAxO1xuICAgIGZvciAoOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBjb2JqID0gYXJndW1lbnRzW2ldO1xuICAgICAgZm9yIChjb25zdCBwIGluIGNvYmopIHtcbiAgICAgICAgb2JqW3BdID0gY29ialtwXTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbiAgfVxuICBmdW5jdGlvbiBWVFRDdWUoc3RhcnRUaW1lLCBlbmRUaW1lLCB0ZXh0KSB7XG4gICAgY29uc3QgY3VlID0gdGhpcztcbiAgICBjb25zdCBiYXNlT2JqID0ge1xuICAgICAgZW51bWVyYWJsZTogdHJ1ZVxuICAgIH07XG4gICAgLyoqXG4gICAgICogU2hpbSBpbXBsZW1lbnRhdGlvbiBzcGVjaWZpYyBwcm9wZXJ0aWVzLiBUaGVzZSBwcm9wZXJ0aWVzIGFyZSBub3QgaW5cbiAgICAgKiB0aGUgc3BlYy5cbiAgICAgKi9cblxuICAgIC8vIExldHMgdXMga25vdyB3aGVuIHRoZSBWVFRDdWUncyBkYXRhIGhhcyBjaGFuZ2VkIGluIHN1Y2ggYSB3YXkgdGhhdCB3ZSBuZWVkXG4gICAgLy8gdG8gcmVjb21wdXRlIGl0cyBkaXNwbGF5IHN0YXRlLiBUaGlzIGxldHMgdXMgY29tcHV0ZSBpdHMgZGlzcGxheSBzdGF0ZVxuICAgIC8vIGxhemlseS5cbiAgICBjdWUuaGFzQmVlblJlc2V0ID0gZmFsc2U7XG5cbiAgICAvKipcbiAgICAgKiBWVFRDdWUgYW5kIFRleHRUcmFja0N1ZSBwcm9wZXJ0aWVzXG4gICAgICogaHR0cDovL2Rldi53My5vcmcvaHRtbDUvd2VidnR0LyN2dHRjdWUtaW50ZXJmYWNlXG4gICAgICovXG5cbiAgICBsZXQgX2lkID0gJyc7XG4gICAgbGV0IF9wYXVzZU9uRXhpdCA9IGZhbHNlO1xuICAgIGxldCBfc3RhcnRUaW1lID0gc3RhcnRUaW1lO1xuICAgIGxldCBfZW5kVGltZSA9IGVuZFRpbWU7XG4gICAgbGV0IF90ZXh0ID0gdGV4dDtcbiAgICBsZXQgX3JlZ2lvbiA9IG51bGw7XG4gICAgbGV0IF92ZXJ0aWNhbCA9ICcnO1xuICAgIGxldCBfc25hcFRvTGluZXMgPSB0cnVlO1xuICAgIGxldCBfbGluZSA9ICdhdXRvJztcbiAgICBsZXQgX2xpbmVBbGlnbiA9ICdzdGFydCc7XG4gICAgbGV0IF9wb3NpdGlvbiA9IDUwO1xuICAgIGxldCBfcG9zaXRpb25BbGlnbiA9ICdtaWRkbGUnO1xuICAgIGxldCBfc2l6ZSA9IDUwO1xuICAgIGxldCBfYWxpZ24gPSAnbWlkZGxlJztcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoY3VlLCAnaWQnLCBleHRlbmQoe30sIGJhc2VPYmosIHtcbiAgICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gX2lkO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIF9pZCA9ICcnICsgdmFsdWU7XG4gICAgICB9XG4gICAgfSkpO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjdWUsICdwYXVzZU9uRXhpdCcsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfcGF1c2VPbkV4aXQ7XG4gICAgICB9LFxuICAgICAgc2V0OiBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgX3BhdXNlT25FeGl0ID0gISF2YWx1ZTtcbiAgICAgIH1cbiAgICB9KSk7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGN1ZSwgJ3N0YXJ0VGltZScsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfc3RhcnRUaW1lO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGlmICh0eXBlb2YgdmFsdWUgIT09ICdudW1iZXInKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignU3RhcnQgdGltZSBtdXN0IGJlIHNldCB0byBhIG51bWJlci4nKTtcbiAgICAgICAgfVxuICAgICAgICBfc3RhcnRUaW1lID0gdmFsdWU7XG4gICAgICAgIHRoaXMuaGFzQmVlblJlc2V0ID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9KSk7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGN1ZSwgJ2VuZFRpbWUnLCBleHRlbmQoe30sIGJhc2VPYmosIHtcbiAgICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gX2VuZFRpbWU7XG4gICAgICB9LFxuICAgICAgc2V0OiBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gJ251bWJlcicpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdFbmQgdGltZSBtdXN0IGJlIHNldCB0byBhIG51bWJlci4nKTtcbiAgICAgICAgfVxuICAgICAgICBfZW5kVGltZSA9IHZhbHVlO1xuICAgICAgICB0aGlzLmhhc0JlZW5SZXNldCA9IHRydWU7XG4gICAgICB9XG4gICAgfSkpO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjdWUsICd0ZXh0JywgZXh0ZW5kKHt9LCBiYXNlT2JqLCB7XG4gICAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIF90ZXh0O1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIF90ZXh0ID0gJycgKyB2YWx1ZTtcbiAgICAgICAgdGhpcy5oYXNCZWVuUmVzZXQgPSB0cnVlO1xuICAgICAgfVxuICAgIH0pKTtcblxuICAgIC8vIHRvZG86IGltcGxlbWVudCBWVFRSZWdpb24gcG9seWZpbGw/XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGN1ZSwgJ3JlZ2lvbicsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfcmVnaW9uO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIF9yZWdpb24gPSB2YWx1ZTtcbiAgICAgICAgdGhpcy5oYXNCZWVuUmVzZXQgPSB0cnVlO1xuICAgICAgfVxuICAgIH0pKTtcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoY3VlLCAndmVydGljYWwnLCBleHRlbmQoe30sIGJhc2VPYmosIHtcbiAgICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gX3ZlcnRpY2FsO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGNvbnN0IHNldHRpbmcgPSBmaW5kRGlyZWN0aW9uU2V0dGluZyh2YWx1ZSk7XG4gICAgICAgIC8vIEhhdmUgdG8gY2hlY2sgZm9yIGZhbHNlIGJlY2F1c2UgdGhlIHNldHRpbmcgYW4gYmUgYW4gZW1wdHkgc3RyaW5nLlxuICAgICAgICBpZiAoc2V0dGluZyA9PT0gZmFsc2UpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgU3ludGF4RXJyb3IoJ0FuIGludmFsaWQgb3IgaWxsZWdhbCBzdHJpbmcgd2FzIHNwZWNpZmllZC4nKTtcbiAgICAgICAgfVxuICAgICAgICBfdmVydGljYWwgPSBzZXR0aW5nO1xuICAgICAgICB0aGlzLmhhc0JlZW5SZXNldCA9IHRydWU7XG4gICAgICB9XG4gICAgfSkpO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjdWUsICdzbmFwVG9MaW5lcycsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfc25hcFRvTGluZXM7XG4gICAgICB9LFxuICAgICAgc2V0OiBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgX3NuYXBUb0xpbmVzID0gISF2YWx1ZTtcbiAgICAgICAgdGhpcy5oYXNCZWVuUmVzZXQgPSB0cnVlO1xuICAgICAgfVxuICAgIH0pKTtcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoY3VlLCAnbGluZScsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfbGluZTtcbiAgICAgIH0sXG4gICAgICBzZXQ6IGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnbnVtYmVyJyAmJiB2YWx1ZSAhPT0gJ2F1dG8nKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFN5bnRheEVycm9yKCdBbiBpbnZhbGlkIG51bWJlciBvciBpbGxlZ2FsIHN0cmluZyB3YXMgc3BlY2lmaWVkLicpO1xuICAgICAgICB9XG4gICAgICAgIF9saW5lID0gdmFsdWU7XG4gICAgICAgIHRoaXMuaGFzQmVlblJlc2V0ID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9KSk7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGN1ZSwgJ2xpbmVBbGlnbicsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfbGluZUFsaWduO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGNvbnN0IHNldHRpbmcgPSBmaW5kQWxpZ25TZXR0aW5nKHZhbHVlKTtcbiAgICAgICAgaWYgKCFzZXR0aW5nKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFN5bnRheEVycm9yKCdBbiBpbnZhbGlkIG9yIGlsbGVnYWwgc3RyaW5nIHdhcyBzcGVjaWZpZWQuJyk7XG4gICAgICAgIH1cbiAgICAgICAgX2xpbmVBbGlnbiA9IHNldHRpbmc7XG4gICAgICAgIHRoaXMuaGFzQmVlblJlc2V0ID0gdHJ1ZTtcbiAgICAgIH1cbiAgICB9KSk7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGN1ZSwgJ3Bvc2l0aW9uJywgZXh0ZW5kKHt9LCBiYXNlT2JqLCB7XG4gICAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIF9wb3NpdGlvbjtcbiAgICAgIH0sXG4gICAgICBzZXQ6IGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICBpZiAodmFsdWUgPCAwIHx8IHZhbHVlID4gMTAwKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdQb3NpdGlvbiBtdXN0IGJlIGJldHdlZW4gMCBhbmQgMTAwLicpO1xuICAgICAgICB9XG4gICAgICAgIF9wb3NpdGlvbiA9IHZhbHVlO1xuICAgICAgICB0aGlzLmhhc0JlZW5SZXNldCA9IHRydWU7XG4gICAgICB9XG4gICAgfSkpO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjdWUsICdwb3NpdGlvbkFsaWduJywgZXh0ZW5kKHt9LCBiYXNlT2JqLCB7XG4gICAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIF9wb3NpdGlvbkFsaWduO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGNvbnN0IHNldHRpbmcgPSBmaW5kQWxpZ25TZXR0aW5nKHZhbHVlKTtcbiAgICAgICAgaWYgKCFzZXR0aW5nKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFN5bnRheEVycm9yKCdBbiBpbnZhbGlkIG9yIGlsbGVnYWwgc3RyaW5nIHdhcyBzcGVjaWZpZWQuJyk7XG4gICAgICAgIH1cbiAgICAgICAgX3Bvc2l0aW9uQWxpZ24gPSBzZXR0aW5nO1xuICAgICAgICB0aGlzLmhhc0JlZW5SZXNldCA9IHRydWU7XG4gICAgICB9XG4gICAgfSkpO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjdWUsICdzaXplJywgZXh0ZW5kKHt9LCBiYXNlT2JqLCB7XG4gICAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIF9zaXplO1xuICAgICAgfSxcbiAgICAgIHNldDogZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgIGlmICh2YWx1ZSA8IDAgfHwgdmFsdWUgPiAxMDApIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NpemUgbXVzdCBiZSBiZXR3ZWVuIDAgYW5kIDEwMC4nKTtcbiAgICAgICAgfVxuICAgICAgICBfc2l6ZSA9IHZhbHVlO1xuICAgICAgICB0aGlzLmhhc0JlZW5SZXNldCA9IHRydWU7XG4gICAgICB9XG4gICAgfSkpO1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShjdWUsICdhbGlnbicsIGV4dGVuZCh7fSwgYmFzZU9iaiwge1xuICAgICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBfYWxpZ247XG4gICAgICB9LFxuICAgICAgc2V0OiBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgY29uc3Qgc2V0dGluZyA9IGZpbmRBbGlnblNldHRpbmcodmFsdWUpO1xuICAgICAgICBpZiAoIXNldHRpbmcpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgU3ludGF4RXJyb3IoJ0FuIGludmFsaWQgb3IgaWxsZWdhbCBzdHJpbmcgd2FzIHNwZWNpZmllZC4nKTtcbiAgICAgICAgfVxuICAgICAgICBfYWxpZ24gPSBzZXR0aW5nO1xuICAgICAgICB0aGlzLmhhc0JlZW5SZXNldCA9IHRydWU7XG4gICAgICB9XG4gICAgfSkpO1xuXG4gICAgLyoqXG4gICAgICogT3RoZXIgPHRyYWNrPiBzcGVjIGRlZmluZWQgcHJvcGVydGllc1xuICAgICAqL1xuXG4gICAgLy8gaHR0cDovL3d3dy53aGF0d2cub3JnL3NwZWNzL3dlYi1hcHBzL2N1cnJlbnQtd29yay9tdWx0aXBhZ2UvdGhlLXZpZGVvLWVsZW1lbnQuaHRtbCN0ZXh0LXRyYWNrLWN1ZS1kaXNwbGF5LXN0YXRlXG4gICAgY3VlLmRpc3BsYXlTdGF0ZSA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBWVFRDdWUgbWV0aG9kc1xuICAgKi9cblxuICBWVFRDdWUucHJvdG90eXBlLmdldEN1ZUFzSFRNTCA9IGZ1bmN0aW9uICgpIHtcbiAgICAvLyBBc3N1bWUgV2ViVlRULmNvbnZlcnRDdWVUb0RPTVRyZWUgaXMgb24gdGhlIGdsb2JhbC5cbiAgICBjb25zdCBXZWJWVFQgPSBzZWxmLldlYlZUVDtcbiAgICByZXR1cm4gV2ViVlRULmNvbnZlcnRDdWVUb0RPTVRyZWUoc2VsZiwgdGhpcy50ZXh0KTtcbiAgfTtcbiAgLy8gdGhpcyBpcyBhIHBvbHlmaWxsIGhhY2tcbiAgcmV0dXJuIFZUVEN1ZTtcbn0pKCk7XG5cbi8qXG4gKiBTb3VyY2U6IGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3Z0dC5qcy9ibG9iL21hc3Rlci9kaXN0L3Z0dC5qc1xuICovXG5cbmNsYXNzIFN0cmluZ0RlY29kZXIge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVudXNlZC12YXJzXG4gIGRlY29kZShkYXRhLCBvcHRpb25zKSB7XG4gICAgaWYgKCFkYXRhKSB7XG4gICAgICByZXR1cm4gJyc7XG4gICAgfVxuICAgIGlmICh0eXBlb2YgZGF0YSAhPT0gJ3N0cmluZycpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignRXJyb3IgLSBleHBlY3RlZCBzdHJpbmcgZGF0YS4nKTtcbiAgICB9XG4gICAgcmV0dXJuIGRlY29kZVVSSUNvbXBvbmVudChlbmNvZGVVUklDb21wb25lbnQoZGF0YSkpO1xuICB9XG59XG5cbi8vIFRyeSB0byBwYXJzZSBpbnB1dCBhcyBhIHRpbWUgc3RhbXAuXG5mdW5jdGlvbiBwYXJzZVRpbWVTdGFtcChpbnB1dCkge1xuICBmdW5jdGlvbiBjb21wdXRlU2Vjb25kcyhoLCBtLCBzLCBmKSB7XG4gICAgcmV0dXJuIChoIHwgMCkgKiAzNjAwICsgKG0gfCAwKSAqIDYwICsgKHMgfCAwKSArIHBhcnNlRmxvYXQoZiB8fCAwKTtcbiAgfVxuICBjb25zdCBtID0gaW5wdXQubWF0Y2goL14oPzooXFxkKyk6KT8oXFxkezJ9KTooXFxkezJ9KShcXC5cXGQrKT8vKTtcbiAgaWYgKCFtKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgaWYgKHBhcnNlRmxvYXQobVsyXSkgPiA1OSkge1xuICAgIC8vIFRpbWVzdGFtcCB0YWtlcyB0aGUgZm9ybSBvZiBbaG91cnNdOlttaW51dGVzXS5bbWlsbGlzZWNvbmRzXVxuICAgIC8vIEZpcnN0IHBvc2l0aW9uIGlzIGhvdXJzIGFzIGl0J3Mgb3ZlciA1OS5cbiAgICByZXR1cm4gY29tcHV0ZVNlY29uZHMobVsyXSwgbVszXSwgMCwgbVs0XSk7XG4gIH1cbiAgLy8gVGltZXN0YW1wIHRha2VzIHRoZSBmb3JtIG9mIFtob3VycyAob3B0aW9uYWwpXTpbbWludXRlc106W3NlY29uZHNdLlttaWxsaXNlY29uZHNdXG4gIHJldHVybiBjb21wdXRlU2Vjb25kcyhtWzFdLCBtWzJdLCBtWzNdLCBtWzRdKTtcbn1cblxuLy8gQSBzZXR0aW5ncyBvYmplY3QgaG9sZHMga2V5L3ZhbHVlIHBhaXJzIGFuZCB3aWxsIGlnbm9yZSBhbnl0aGluZyBidXQgdGhlIGZpcnN0XG4vLyBhc3NpZ25tZW50IHRvIGEgc3BlY2lmaWMga2V5LlxuY2xhc3MgU2V0dGluZ3Mge1xuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLnZhbHVlcyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gIH1cbiAgLy8gT25seSBhY2NlcHQgdGhlIGZpcnN0IGFzc2lnbm1lbnQgdG8gYW55IGtleS5cbiAgc2V0KGssIHYpIHtcbiAgICBpZiAoIXRoaXMuZ2V0KGspICYmIHYgIT09ICcnKSB7XG4gICAgICB0aGlzLnZhbHVlc1trXSA9IHY7XG4gICAgfVxuICB9XG4gIC8vIFJldHVybiB0aGUgdmFsdWUgZm9yIGEga2V5LCBvciBhIGRlZmF1bHQgdmFsdWUuXG4gIC8vIElmICdkZWZhdWx0S2V5JyBpcyBwYXNzZWQgdGhlbiAnZGZsdCcgaXMgYXNzdW1lZCB0byBiZSBhbiBvYmplY3Qgd2l0aFxuICAvLyBhIG51bWJlciBvZiBwb3NzaWJsZSBkZWZhdWx0IHZhbHVlcyBhcyBwcm9wZXJ0aWVzIHdoZXJlICdkZWZhdWx0S2V5JyBpc1xuICAvLyB0aGUga2V5IG9mIHRoZSBwcm9wZXJ0eSB0aGF0IHdpbGwgYmUgY2hvc2VuOyBvdGhlcndpc2UgaXQncyBhc3N1bWVkIHRvIGJlXG4gIC8vIGEgc2luZ2xlIHZhbHVlLlxuICBnZXQoaywgZGZsdCwgZGVmYXVsdEtleSkge1xuICAgIGlmIChkZWZhdWx0S2V5KSB7XG4gICAgICByZXR1cm4gdGhpcy5oYXMoaykgPyB0aGlzLnZhbHVlc1trXSA6IGRmbHRbZGVmYXVsdEtleV07XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmhhcyhrKSA/IHRoaXMudmFsdWVzW2tdIDogZGZsdDtcbiAgfVxuICAvLyBDaGVjayB3aGV0aGVyIHdlIGhhdmUgYSB2YWx1ZSBmb3IgYSBrZXkuXG4gIGhhcyhrKSB7XG4gICAgcmV0dXJuIGsgaW4gdGhpcy52YWx1ZXM7XG4gIH1cbiAgLy8gQWNjZXB0IGEgc2V0dGluZyBpZiBpdHMgb25lIG9mIHRoZSBnaXZlbiBhbHRlcm5hdGl2ZXMuXG4gIGFsdChrLCB2LCBhKSB7XG4gICAgZm9yIChsZXQgbiA9IDA7IG4gPCBhLmxlbmd0aDsgKytuKSB7XG4gICAgICBpZiAodiA9PT0gYVtuXSkge1xuICAgICAgICB0aGlzLnNldChrLCB2KTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIC8vIEFjY2VwdCBhIHNldHRpbmcgaWYgaXRzIGEgdmFsaWQgKHNpZ25lZCkgaW50ZWdlci5cbiAgaW50ZWdlcihrLCB2KSB7XG4gICAgaWYgKC9eLT9cXGQrJC8udGVzdCh2KSkge1xuICAgICAgLy8gaW50ZWdlclxuICAgICAgdGhpcy5zZXQoaywgcGFyc2VJbnQodiwgMTApKTtcbiAgICB9XG4gIH1cbiAgLy8gQWNjZXB0IGEgc2V0dGluZyBpZiBpdHMgYSB2YWxpZCBwZXJjZW50YWdlLlxuICBwZXJjZW50KGssIHYpIHtcbiAgICBpZiAoL14oW1xcZF17MSwzfSkoXFwuW1xcZF0qKT8lJC8udGVzdCh2KSkge1xuICAgICAgY29uc3QgcGVyY2VudCA9IHBhcnNlRmxvYXQodik7XG4gICAgICBpZiAocGVyY2VudCA+PSAwICYmIHBlcmNlbnQgPD0gMTAwKSB7XG4gICAgICAgIHRoaXMuc2V0KGssIHBlcmNlbnQpO1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8vIEhlbHBlciBmdW5jdGlvbiB0byBwYXJzZSBpbnB1dCBpbnRvIGdyb3VwcyBzZXBhcmF0ZWQgYnkgJ2dyb3VwRGVsaW0nLCBhbmRcbi8vIGludGVycHJldCBlYWNoIGdyb3VwIGFzIGEga2V5L3ZhbHVlIHBhaXIgc2VwYXJhdGVkIGJ5ICdrZXlWYWx1ZURlbGltJy5cbmZ1bmN0aW9uIHBhcnNlT3B0aW9ucyhpbnB1dCwgY2FsbGJhY2ssIGtleVZhbHVlRGVsaW0sIGdyb3VwRGVsaW0pIHtcbiAgY29uc3QgZ3JvdXBzID0gZ3JvdXBEZWxpbSA/IGlucHV0LnNwbGl0KGdyb3VwRGVsaW0pIDogW2lucHV0XTtcbiAgZm9yIChjb25zdCBpIGluIGdyb3Vwcykge1xuICAgIGlmICh0eXBlb2YgZ3JvdXBzW2ldICE9PSAnc3RyaW5nJykge1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGNvbnN0IGt2ID0gZ3JvdXBzW2ldLnNwbGl0KGtleVZhbHVlRGVsaW0pO1xuICAgIGlmIChrdi5sZW5ndGggIT09IDIpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICBjb25zdCBrID0ga3ZbMF07XG4gICAgY29uc3QgdiA9IGt2WzFdO1xuICAgIGNhbGxiYWNrKGssIHYpO1xuICB9XG59XG5jb25zdCBkZWZhdWx0cyA9IG5ldyBWVFRDdWUoMCwgMCwgJycpO1xuLy8gJ21pZGRsZScgd2FzIGNoYW5nZWQgdG8gJ2NlbnRlcicgaW4gdGhlIHNwZWM6IGh0dHBzOi8vZ2l0aHViLmNvbS93M2Mvd2VidnR0L3B1bGwvMjQ0XG4vLyAgU2FmYXJpIGRvZXNuJ3QgeWV0IHN1cHBvcnQgdGhpcyBjaGFuZ2UsIGJ1dCBGRiBhbmQgQ2hyb21lIGRvLlxuY29uc3QgY2VudGVyID0gZGVmYXVsdHMuYWxpZ24gPT09ICdtaWRkbGUnID8gJ21pZGRsZScgOiAnY2VudGVyJztcbmZ1bmN0aW9uIHBhcnNlQ3VlKGlucHV0LCBjdWUsIHJlZ2lvbkxpc3QpIHtcbiAgLy8gUmVtZW1iZXIgdGhlIG9yaWdpbmFsIGlucHV0IGlmIHdlIG5lZWQgdG8gdGhyb3cgYW4gZXJyb3IuXG4gIGNvbnN0IG9JbnB1dCA9IGlucHV0O1xuICAvLyA0LjEgV2ViVlRUIHRpbWVzdGFtcFxuICBmdW5jdGlvbiBjb25zdW1lVGltZVN0YW1wKCkge1xuICAgIGNvbnN0IHRzID0gcGFyc2VUaW1lU3RhbXAoaW5wdXQpO1xuICAgIGlmICh0cyA9PT0gbnVsbCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdNYWxmb3JtZWQgdGltZXN0YW1wOiAnICsgb0lucHV0KTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgdGltZSBzdGFtcCBmcm9tIGlucHV0LlxuICAgIGlucHV0ID0gaW5wdXQucmVwbGFjZSgvXlteXFxzYS16QS1aLV0rLywgJycpO1xuICAgIHJldHVybiB0cztcbiAgfVxuXG4gIC8vIDQuNC4yIFdlYlZUVCBjdWUgc2V0dGluZ3NcbiAgZnVuY3Rpb24gY29uc3VtZUN1ZVNldHRpbmdzKGlucHV0LCBjdWUpIHtcbiAgICBjb25zdCBzZXR0aW5ncyA9IG5ldyBTZXR0aW5ncygpO1xuICAgIHBhcnNlT3B0aW9ucyhpbnB1dCwgZnVuY3Rpb24gKGssIHYpIHtcbiAgICAgIGxldCB2YWxzO1xuICAgICAgc3dpdGNoIChrKSB7XG4gICAgICAgIGNhc2UgJ3JlZ2lvbic6XG4gICAgICAgICAgLy8gRmluZCB0aGUgbGFzdCByZWdpb24gd2UgcGFyc2VkIHdpdGggdGhlIHNhbWUgcmVnaW9uIGlkLlxuICAgICAgICAgIGZvciAobGV0IGkgPSByZWdpb25MaXN0Lmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICAgICAgICBpZiAocmVnaW9uTGlzdFtpXS5pZCA9PT0gdikge1xuICAgICAgICAgICAgICBzZXR0aW5ncy5zZXQoaywgcmVnaW9uTGlzdFtpXS5yZWdpb24pO1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ3ZlcnRpY2FsJzpcbiAgICAgICAgICBzZXR0aW5ncy5hbHQoaywgdiwgWydybCcsICdsciddKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAnbGluZSc6XG4gICAgICAgICAgdmFscyA9IHYuc3BsaXQoJywnKTtcbiAgICAgICAgICBzZXR0aW5ncy5pbnRlZ2VyKGssIHZhbHNbMF0pO1xuICAgICAgICAgIGlmIChzZXR0aW5ncy5wZXJjZW50KGssIHZhbHNbMF0pKSB7XG4gICAgICAgICAgICBzZXR0aW5ncy5zZXQoJ3NuYXBUb0xpbmVzJywgZmFsc2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBzZXR0aW5ncy5hbHQoaywgdmFsc1swXSwgWydhdXRvJ10pO1xuICAgICAgICAgIGlmICh2YWxzLmxlbmd0aCA9PT0gMikge1xuICAgICAgICAgICAgc2V0dGluZ3MuYWx0KCdsaW5lQWxpZ24nLCB2YWxzWzFdLCBbJ3N0YXJ0JywgY2VudGVyLCAnZW5kJ10pO1xuICAgICAgICAgIH1cbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAncG9zaXRpb24nOlxuICAgICAgICAgIHZhbHMgPSB2LnNwbGl0KCcsJyk7XG4gICAgICAgICAgc2V0dGluZ3MucGVyY2VudChrLCB2YWxzWzBdKTtcbiAgICAgICAgICBpZiAodmFscy5sZW5ndGggPT09IDIpIHtcbiAgICAgICAgICAgIHNldHRpbmdzLmFsdCgncG9zaXRpb25BbGlnbicsIHZhbHNbMV0sIFsnc3RhcnQnLCBjZW50ZXIsICdlbmQnLCAnbGluZS1sZWZ0JywgJ2xpbmUtcmlnaHQnLCAnYXV0byddKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ3NpemUnOlxuICAgICAgICAgIHNldHRpbmdzLnBlcmNlbnQoaywgdik7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJ2FsaWduJzpcbiAgICAgICAgICBzZXR0aW5ncy5hbHQoaywgdiwgWydzdGFydCcsIGNlbnRlciwgJ2VuZCcsICdsZWZ0JywgJ3JpZ2h0J10pO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH0sIC86LywgL1xccy8pO1xuXG4gICAgLy8gQXBwbHkgZGVmYXVsdCB2YWx1ZXMgZm9yIGFueSBtaXNzaW5nIGZpZWxkcy5cbiAgICBjdWUucmVnaW9uID0gc2V0dGluZ3MuZ2V0KCdyZWdpb24nLCBudWxsKTtcbiAgICBjdWUudmVydGljYWwgPSBzZXR0aW5ncy5nZXQoJ3ZlcnRpY2FsJywgJycpO1xuICAgIGxldCBsaW5lID0gc2V0dGluZ3MuZ2V0KCdsaW5lJywgJ2F1dG8nKTtcbiAgICBpZiAobGluZSA9PT0gJ2F1dG8nICYmIGRlZmF1bHRzLmxpbmUgPT09IC0xKSB7XG4gICAgICAvLyBzZXQgbnVtZXJpYyBsaW5lIG51bWJlciBmb3IgU2FmYXJpXG4gICAgICBsaW5lID0gLTE7XG4gICAgfVxuICAgIGN1ZS5saW5lID0gbGluZTtcbiAgICBjdWUubGluZUFsaWduID0gc2V0dGluZ3MuZ2V0KCdsaW5lQWxpZ24nLCAnc3RhcnQnKTtcbiAgICBjdWUuc25hcFRvTGluZXMgPSBzZXR0aW5ncy5nZXQoJ3NuYXBUb0xpbmVzJywgdHJ1ZSk7XG4gICAgY3VlLnNpemUgPSBzZXR0aW5ncy5nZXQoJ3NpemUnLCAxMDApO1xuICAgIGN1ZS5hbGlnbiA9IHNldHRpbmdzLmdldCgnYWxpZ24nLCBjZW50ZXIpO1xuICAgIGxldCBwb3NpdGlvbiA9IHNldHRpbmdzLmdldCgncG9zaXRpb24nLCAnYXV0bycpO1xuICAgIGlmIChwb3NpdGlvbiA9PT0gJ2F1dG8nICYmIGRlZmF1bHRzLnBvc2l0aW9uID09PSA1MCkge1xuICAgICAgLy8gc2V0IG51bWVyaWMgcG9zaXRpb24gZm9yIFNhZmFyaVxuICAgICAgcG9zaXRpb24gPSBjdWUuYWxpZ24gPT09ICdzdGFydCcgfHwgY3VlLmFsaWduID09PSAnbGVmdCcgPyAwIDogY3VlLmFsaWduID09PSAnZW5kJyB8fCBjdWUuYWxpZ24gPT09ICdyaWdodCcgPyAxMDAgOiA1MDtcbiAgICB9XG4gICAgY3VlLnBvc2l0aW9uID0gcG9zaXRpb247XG4gIH1cbiAgZnVuY3Rpb24gc2tpcFdoaXRlc3BhY2UoKSB7XG4gICAgaW5wdXQgPSBpbnB1dC5yZXBsYWNlKC9eXFxzKy8sICcnKTtcbiAgfVxuXG4gIC8vIDQuMSBXZWJWVFQgY3VlIHRpbWluZ3MuXG4gIHNraXBXaGl0ZXNwYWNlKCk7XG4gIGN1ZS5zdGFydFRpbWUgPSBjb25zdW1lVGltZVN0YW1wKCk7IC8vICgxKSBjb2xsZWN0IGN1ZSBzdGFydCB0aW1lXG4gIHNraXBXaGl0ZXNwYWNlKCk7XG4gIGlmIChpbnB1dC5zbGljZSgwLCAzKSAhPT0gJy0tPicpIHtcbiAgICAvLyAoMykgbmV4dCBjaGFyYWN0ZXJzIG11c3QgbWF0Y2ggJy0tPidcbiAgICB0aHJvdyBuZXcgRXJyb3IoXCJNYWxmb3JtZWQgdGltZSBzdGFtcCAodGltZSBzdGFtcHMgbXVzdCBiZSBzZXBhcmF0ZWQgYnkgJy0tPicpOiBcIiArIG9JbnB1dCk7XG4gIH1cbiAgaW5wdXQgPSBpbnB1dC5zbGljZSgzKTtcbiAgc2tpcFdoaXRlc3BhY2UoKTtcbiAgY3VlLmVuZFRpbWUgPSBjb25zdW1lVGltZVN0YW1wKCk7IC8vICg1KSBjb2xsZWN0IGN1ZSBlbmQgdGltZVxuXG4gIC8vIDQuMSBXZWJWVFQgY3VlIHNldHRpbmdzIGxpc3QuXG4gIHNraXBXaGl0ZXNwYWNlKCk7XG4gIGNvbnN1bWVDdWVTZXR0aW5ncyhpbnB1dCwgY3VlKTtcbn1cbmZ1bmN0aW9uIGZpeExpbmVCcmVha3MoaW5wdXQpIHtcbiAgcmV0dXJuIGlucHV0LnJlcGxhY2UoLzxicig/OiBcXC8pPz4vZ2ksICdcXG4nKTtcbn1cbmNsYXNzIFZUVFBhcnNlciB7XG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHRoaXMuc3RhdGUgPSAnSU5JVElBTCc7XG4gICAgdGhpcy5idWZmZXIgPSAnJztcbiAgICB0aGlzLmRlY29kZXIgPSBuZXcgU3RyaW5nRGVjb2RlcigpO1xuICAgIHRoaXMucmVnaW9uTGlzdCA9IFtdO1xuICAgIHRoaXMuY3VlID0gbnVsbDtcbiAgICB0aGlzLm9uY3VlID0gdm9pZCAwO1xuICAgIHRoaXMub25wYXJzaW5nZXJyb3IgPSB2b2lkIDA7XG4gICAgdGhpcy5vbmZsdXNoID0gdm9pZCAwO1xuICB9XG4gIHBhcnNlKGRhdGEpIHtcbiAgICBjb25zdCBfdGhpcyA9IHRoaXM7XG5cbiAgICAvLyBJZiB0aGVyZSBpcyBubyBkYXRhIHRoZW4gd2Ugd29uJ3QgZGVjb2RlIGl0LCBidXQgd2lsbCBqdXN0IHRyeSB0byBwYXJzZVxuICAgIC8vIHdoYXRldmVyIGlzIGluIGJ1ZmZlciBhbHJlYWR5LiBUaGlzIG1heSBvY2N1ciBpbiBjaXJjdW1zdGFuY2VzLCBmb3JcbiAgICAvLyBleGFtcGxlIHdoZW4gZmx1c2goKSBpcyBjYWxsZWQuXG4gICAgaWYgKGRhdGEpIHtcbiAgICAgIC8vIFRyeSB0byBkZWNvZGUgdGhlIGRhdGEgdGhhdCB3ZSByZWNlaXZlZC5cbiAgICAgIF90aGlzLmJ1ZmZlciArPSBfdGhpcy5kZWNvZGVyLmRlY29kZShkYXRhLCB7XG4gICAgICAgIHN0cmVhbTogdHJ1ZVxuICAgICAgfSk7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGNvbGxlY3ROZXh0TGluZSgpIHtcbiAgICAgIGxldCBidWZmZXIgPSBfdGhpcy5idWZmZXI7XG4gICAgICBsZXQgcG9zID0gMDtcbiAgICAgIGJ1ZmZlciA9IGZpeExpbmVCcmVha3MoYnVmZmVyKTtcbiAgICAgIHdoaWxlIChwb3MgPCBidWZmZXIubGVuZ3RoICYmIGJ1ZmZlcltwb3NdICE9PSAnXFxyJyAmJiBidWZmZXJbcG9zXSAhPT0gJ1xcbicpIHtcbiAgICAgICAgKytwb3M7XG4gICAgICB9XG4gICAgICBjb25zdCBsaW5lID0gYnVmZmVyLnNsaWNlKDAsIHBvcyk7XG4gICAgICAvLyBBZHZhbmNlIHRoZSBidWZmZXIgZWFybHkgaW4gY2FzZSB3ZSBmYWlsIGJlbG93LlxuICAgICAgaWYgKGJ1ZmZlcltwb3NdID09PSAnXFxyJykge1xuICAgICAgICArK3BvcztcbiAgICAgIH1cbiAgICAgIGlmIChidWZmZXJbcG9zXSA9PT0gJ1xcbicpIHtcbiAgICAgICAgKytwb3M7XG4gICAgICB9XG4gICAgICBfdGhpcy5idWZmZXIgPSBidWZmZXIuc2xpY2UocG9zKTtcbiAgICAgIHJldHVybiBsaW5lO1xuICAgIH1cblxuICAgIC8vIDMuMiBXZWJWVFQgbWV0YWRhdGEgaGVhZGVyIHN5bnRheFxuICAgIGZ1bmN0aW9uIHBhcnNlSGVhZGVyKGlucHV0KSB7XG4gICAgICBwYXJzZU9wdGlvbnMoaW5wdXQsIGZ1bmN0aW9uIChrLCB2KSB7XG4gICAgICAgIC8vIHN3aXRjaCAoaykge1xuICAgICAgICAvLyBjYXNlICdyZWdpb24nOlxuICAgICAgICAvLyAzLjMgV2ViVlRUIHJlZ2lvbiBtZXRhZGF0YSBoZWFkZXIgc3ludGF4XG4gICAgICAgIC8vIGNvbnNvbGUubG9nKCdwYXJzZSByZWdpb24nLCB2KTtcbiAgICAgICAgLy8gcGFyc2VSZWdpb24odik7XG4gICAgICAgIC8vIGJyZWFrO1xuICAgICAgICAvLyB9XG4gICAgICB9LCAvOi8pO1xuICAgIH1cblxuICAgIC8vIDUuMSBXZWJWVFQgZmlsZSBwYXJzaW5nLlxuICAgIHRyeSB7XG4gICAgICBsZXQgbGluZSA9ICcnO1xuICAgICAgaWYgKF90aGlzLnN0YXRlID09PSAnSU5JVElBTCcpIHtcbiAgICAgICAgLy8gV2UgY2FuJ3Qgc3RhcnQgcGFyc2luZyB1bnRpbCB3ZSBoYXZlIHRoZSBmaXJzdCBsaW5lLlxuICAgICAgICBpZiAoIS9cXHJcXG58XFxuLy50ZXN0KF90aGlzLmJ1ZmZlcikpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBsaW5lID0gY29sbGVjdE5leHRMaW5lKCk7XG4gICAgICAgIC8vIHN0cmlwIG9mIFVURi04IEJPTSBpZiBhbnlcbiAgICAgICAgLy8gaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQnl0ZV9vcmRlcl9tYXJrI1VURi04XG4gICAgICAgIGNvbnN0IG0gPSBsaW5lLm1hdGNoKC9eKMOvwrvCvyk/V0VCVlRUKFsgXFx0XS4qKT8kLyk7XG4gICAgICAgIGlmICghKG0gIT0gbnVsbCAmJiBtWzBdKSkge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcignTWFsZm9ybWVkIFdlYlZUVCBzaWduYXR1cmUuJyk7XG4gICAgICAgIH1cbiAgICAgICAgX3RoaXMuc3RhdGUgPSAnSEVBREVSJztcbiAgICAgIH1cbiAgICAgIGxldCBhbHJlYWR5Q29sbGVjdGVkTGluZSA9IGZhbHNlO1xuICAgICAgd2hpbGUgKF90aGlzLmJ1ZmZlcikge1xuICAgICAgICAvLyBXZSBjYW4ndCBwYXJzZSBhIGxpbmUgdW50aWwgd2UgaGF2ZSB0aGUgZnVsbCBsaW5lLlxuICAgICAgICBpZiAoIS9cXHJcXG58XFxuLy50ZXN0KF90aGlzLmJ1ZmZlcikpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoIWFscmVhZHlDb2xsZWN0ZWRMaW5lKSB7XG4gICAgICAgICAgbGluZSA9IGNvbGxlY3ROZXh0TGluZSgpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGFscmVhZHlDb2xsZWN0ZWRMaW5lID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICAgICAgc3dpdGNoIChfdGhpcy5zdGF0ZSkge1xuICAgICAgICAgIGNhc2UgJ0hFQURFUic6XG4gICAgICAgICAgICAvLyAxMy0xOCAtIEFsbG93IGEgaGVhZGVyIChtZXRhZGF0YSkgdW5kZXIgdGhlIFdFQlZUVCBsaW5lLlxuICAgICAgICAgICAgaWYgKC86Ly50ZXN0KGxpbmUpKSB7XG4gICAgICAgICAgICAgIHBhcnNlSGVhZGVyKGxpbmUpO1xuICAgICAgICAgICAgfSBlbHNlIGlmICghbGluZSkge1xuICAgICAgICAgICAgICAvLyBBbiBlbXB0eSBsaW5lIHRlcm1pbmF0ZXMgdGhlIGhlYWRlciBhbmQgc3RhcnRzIHRoZSBib2R5IChjdWVzKS5cbiAgICAgICAgICAgICAgX3RoaXMuc3RhdGUgPSAnSUQnO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgY2FzZSAnTk9URSc6XG4gICAgICAgICAgICAvLyBJZ25vcmUgTk9URSBibG9ja3MuXG4gICAgICAgICAgICBpZiAoIWxpbmUpIHtcbiAgICAgICAgICAgICAgX3RoaXMuc3RhdGUgPSAnSUQnO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgY2FzZSAnSUQnOlxuICAgICAgICAgICAgLy8gQ2hlY2sgZm9yIHRoZSBzdGFydCBvZiBOT1RFIGJsb2Nrcy5cbiAgICAgICAgICAgIGlmICgvXk5PVEUoJHxbIFxcdF0pLy50ZXN0KGxpbmUpKSB7XG4gICAgICAgICAgICAgIF90aGlzLnN0YXRlID0gJ05PVEUnO1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIDE5LTI5IC0gQWxsb3cgYW55IG51bWJlciBvZiBsaW5lIHRlcm1pbmF0b3JzLCB0aGVuIGluaXRpYWxpemUgbmV3IGN1ZSB2YWx1ZXMuXG4gICAgICAgICAgICBpZiAoIWxpbmUpIHtcbiAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBfdGhpcy5jdWUgPSBuZXcgVlRUQ3VlKDAsIDAsICcnKTtcbiAgICAgICAgICAgIF90aGlzLnN0YXRlID0gJ0NVRSc7XG4gICAgICAgICAgICAvLyAzMC0zOSAtIENoZWNrIGlmIHNlbGYgbGluZSBjb250YWlucyBhbiBvcHRpb25hbCBpZGVudGlmaWVyIG9yIHRpbWluZyBkYXRhLlxuICAgICAgICAgICAgaWYgKGxpbmUuaW5kZXhPZignLS0+JykgPT09IC0xKSB7XG4gICAgICAgICAgICAgIF90aGlzLmN1ZS5pZCA9IGxpbmU7XG4gICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIC8vIFByb2Nlc3MgbGluZSBhcyBzdGFydCBvZiBhIGN1ZS5cbiAgICAgICAgICAvKiBmYWxscyB0aHJvdWdoICovXG4gICAgICAgICAgY2FzZSAnQ1VFJzpcbiAgICAgICAgICAgIC8vIDQwIC0gQ29sbGVjdCBjdWUgdGltaW5ncyBhbmQgc2V0dGluZ3MuXG4gICAgICAgICAgICBpZiAoIV90aGlzLmN1ZSkge1xuICAgICAgICAgICAgICBfdGhpcy5zdGF0ZSA9ICdCQURDVUUnO1xuICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgIHBhcnNlQ3VlKGxpbmUsIF90aGlzLmN1ZSwgX3RoaXMucmVnaW9uTGlzdCk7XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgIC8vIEluIGNhc2Ugb2YgYW4gZXJyb3IgaWdub3JlIHJlc3Qgb2YgdGhlIGN1ZS5cbiAgICAgICAgICAgICAgX3RoaXMuY3VlID0gbnVsbDtcbiAgICAgICAgICAgICAgX3RoaXMuc3RhdGUgPSAnQkFEQ1VFJztcbiAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBfdGhpcy5zdGF0ZSA9ICdDVUVURVhUJztcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgIGNhc2UgJ0NVRVRFWFQnOlxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICBjb25zdCBoYXNTdWJzdHJpbmcgPSBsaW5lLmluZGV4T2YoJy0tPicpICE9PSAtMTtcbiAgICAgICAgICAgICAgLy8gMzQgLSBJZiB3ZSBoYXZlIGFuIGVtcHR5IGxpbmUgdGhlbiByZXBvcnQgdGhlIGN1ZS5cbiAgICAgICAgICAgICAgLy8gMzUgLSBJZiB3ZSBoYXZlIHRoZSBzcGVjaWFsIHN1YnN0cmluZyAnLS0+JyB0aGVuIHJlcG9ydCB0aGUgY3VlLFxuICAgICAgICAgICAgICAvLyBidXQgZG8gbm90IGNvbGxlY3QgdGhlIGxpbmUgYXMgd2UgbmVlZCB0byBwcm9jZXNzIHRoZSBjdXJyZW50XG4gICAgICAgICAgICAgIC8vIG9uZSBhcyBhIG5ldyBjdWUuXG4gICAgICAgICAgICAgIGlmICghbGluZSB8fCBoYXNTdWJzdHJpbmcgJiYgKGFscmVhZHlDb2xsZWN0ZWRMaW5lID0gdHJ1ZSkpIHtcbiAgICAgICAgICAgICAgICAvLyBXZSBhcmUgZG9uZSBwYXJzaW5nIHNlbGYgY3VlLlxuICAgICAgICAgICAgICAgIGlmIChfdGhpcy5vbmN1ZSAmJiBfdGhpcy5jdWUpIHtcbiAgICAgICAgICAgICAgICAgIF90aGlzLm9uY3VlKF90aGlzLmN1ZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIF90aGlzLmN1ZSA9IG51bGw7XG4gICAgICAgICAgICAgICAgX3RoaXMuc3RhdGUgPSAnSUQnO1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChfdGhpcy5jdWUgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZiAoX3RoaXMuY3VlLnRleHQpIHtcbiAgICAgICAgICAgICAgICBfdGhpcy5jdWUudGV4dCArPSAnXFxuJztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBfdGhpcy5jdWUudGV4dCArPSBsaW5lO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgY2FzZSAnQkFEQ1VFJzpcbiAgICAgICAgICAgIC8vIDU0LTYyIC0gQ29sbGVjdCBhbmQgZGlzY2FyZCB0aGUgcmVtYWluaW5nIGN1ZS5cbiAgICAgICAgICAgIGlmICghbGluZSkge1xuICAgICAgICAgICAgICBfdGhpcy5zdGF0ZSA9ICdJRCc7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAvLyBJZiB3ZSBhcmUgY3VycmVudGx5IHBhcnNpbmcgYSBjdWUsIHJlcG9ydCB3aGF0IHdlIGhhdmUuXG4gICAgICBpZiAoX3RoaXMuc3RhdGUgPT09ICdDVUVURVhUJyAmJiBfdGhpcy5jdWUgJiYgX3RoaXMub25jdWUpIHtcbiAgICAgICAgX3RoaXMub25jdWUoX3RoaXMuY3VlKTtcbiAgICAgIH1cbiAgICAgIF90aGlzLmN1ZSA9IG51bGw7XG4gICAgICAvLyBFbnRlciBCQURXRUJWVFQgc3RhdGUgaWYgaGVhZGVyIHdhcyBub3QgcGFyc2VkIGNvcnJlY3RseSBvdGhlcndpc2VcbiAgICAgIC8vIGFub3RoZXIgZXhjZXB0aW9uIG9jY3VycmVkIHNvIGVudGVyIEJBRENVRSBzdGF0ZS5cbiAgICAgIF90aGlzLnN0YXRlID0gX3RoaXMuc3RhdGUgPT09ICdJTklUSUFMJyA/ICdCQURXRUJWVFQnIDogJ0JBRENVRSc7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9XG4gIGZsdXNoKCkge1xuICAgIGNvbnN0IF90aGlzID0gdGhpcztcbiAgICB0cnkge1xuICAgICAgLy8gRmluaXNoIGRlY29kaW5nIHRoZSBzdHJlYW0uXG4gICAgICAvLyBfdGhpcy5idWZmZXIgKz0gX3RoaXMuZGVjb2Rlci5kZWNvZGUoKTtcbiAgICAgIC8vIFN5bnRoZXNpemUgdGhlIGVuZCBvZiB0aGUgY3VycmVudCBjdWUgb3IgcmVnaW9uLlxuICAgICAgaWYgKF90aGlzLmN1ZSB8fCBfdGhpcy5zdGF0ZSA9PT0gJ0hFQURFUicpIHtcbiAgICAgICAgX3RoaXMuYnVmZmVyICs9ICdcXG5cXG4nO1xuICAgICAgICBfdGhpcy5wYXJzZSgpO1xuICAgICAgfVxuICAgICAgLy8gSWYgd2UndmUgZmx1c2hlZCwgcGFyc2VkLCBhbmQgd2UncmUgc3RpbGwgb24gdGhlIElOSVRJQUwgc3RhdGUgdGhlblxuICAgICAgLy8gdGhhdCBtZWFucyB3ZSBkb24ndCBoYXZlIGVub3VnaCBvZiB0aGUgc3RyZWFtIHRvIHBhcnNlIHRoZSBmaXJzdFxuICAgICAgLy8gbGluZS5cbiAgICAgIGlmIChfdGhpcy5zdGF0ZSA9PT0gJ0lOSVRJQUwnIHx8IF90aGlzLnN0YXRlID09PSAnQkFEV0VCVlRUJykge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ01hbGZvcm1lZCBXZWJWVFQgc2lnbmF0dXJlLicpO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGlmIChfdGhpcy5vbnBhcnNpbmdlcnJvcikge1xuICAgICAgICBfdGhpcy5vbnBhcnNpbmdlcnJvcihlKTtcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKF90aGlzLm9uZmx1c2gpIHtcbiAgICAgIF90aGlzLm9uZmx1c2goKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH1cbn1cblxuY29uc3QgTElORUJSRUFLUyA9IC9cXHJcXG58XFxuXFxyfFxcbnxcXHIvZztcblxuLy8gU3RyaW5nLnByb3RvdHlwZS5zdGFydHNXaXRoIGlzIG5vdCBzdXBwb3J0ZWQgaW4gSUUxMVxuY29uc3Qgc3RhcnRzV2l0aCA9IGZ1bmN0aW9uIHN0YXJ0c1dpdGgoaW5wdXRTdHJpbmcsIHNlYXJjaFN0cmluZywgcG9zaXRpb24gPSAwKSB7XG4gIHJldHVybiBpbnB1dFN0cmluZy5zbGljZShwb3NpdGlvbiwgcG9zaXRpb24gKyBzZWFyY2hTdHJpbmcubGVuZ3RoKSA9PT0gc2VhcmNoU3RyaW5nO1xufTtcbmNvbnN0IGN1ZVN0cmluZzJtaWxsaXMgPSBmdW5jdGlvbiBjdWVTdHJpbmcybWlsbGlzKHRpbWVTdHJpbmcpIHtcbiAgbGV0IHRzID0gcGFyc2VJbnQodGltZVN0cmluZy5zbGljZSgtMykpO1xuICBjb25zdCBzZWNzID0gcGFyc2VJbnQodGltZVN0cmluZy5zbGljZSgtNiwgLTQpKTtcbiAgY29uc3QgbWlucyA9IHBhcnNlSW50KHRpbWVTdHJpbmcuc2xpY2UoLTksIC03KSk7XG4gIGNvbnN0IGhvdXJzID0gdGltZVN0cmluZy5sZW5ndGggPiA5ID8gcGFyc2VJbnQodGltZVN0cmluZy5zdWJzdHJpbmcoMCwgdGltZVN0cmluZy5pbmRleE9mKCc6JykpKSA6IDA7XG4gIGlmICghaXNGaW5pdGVOdW1iZXIodHMpIHx8ICFpc0Zpbml0ZU51bWJlcihzZWNzKSB8fCAhaXNGaW5pdGVOdW1iZXIobWlucykgfHwgIWlzRmluaXRlTnVtYmVyKGhvdXJzKSkge1xuICAgIHRocm93IEVycm9yKGBNYWxmb3JtZWQgWC1USU1FU1RBTVAtTUFQOiBMb2NhbDoke3RpbWVTdHJpbmd9YCk7XG4gIH1cbiAgdHMgKz0gMTAwMCAqIHNlY3M7XG4gIHRzICs9IDYwICogMTAwMCAqIG1pbnM7XG4gIHRzICs9IDYwICogNjAgKiAxMDAwICogaG91cnM7XG4gIHJldHVybiB0cztcbn07XG5cbi8vIEZyb20gaHR0cHM6Ly9naXRodWIuY29tL2Rhcmtza3lhcHAvc3RyaW5nLWhhc2hcbmNvbnN0IGhhc2ggPSBmdW5jdGlvbiBoYXNoKHRleHQpIHtcbiAgbGV0IF9oYXNoID0gNTM4MTtcbiAgbGV0IGkgPSB0ZXh0Lmxlbmd0aDtcbiAgd2hpbGUgKGkpIHtcbiAgICBfaGFzaCA9IF9oYXNoICogMzMgXiB0ZXh0LmNoYXJDb2RlQXQoLS1pKTtcbiAgfVxuICByZXR1cm4gKF9oYXNoID4+PiAwKS50b1N0cmluZygpO1xufTtcblxuLy8gQ3JlYXRlIGEgdW5pcXVlIGhhc2ggaWQgZm9yIGEgY3VlIGJhc2VkIG9uIHN0YXJ0L2VuZCB0aW1lcyBhbmQgdGV4dC5cbi8vIFRoaXMgaGVscHMgdGltZWxpbmUtY29udHJvbGxlciB0byBhdm9pZCBzaG93aW5nIHJlcGVhdGVkIGNhcHRpb25zLlxuZnVuY3Rpb24gZ2VuZXJhdGVDdWVJZChzdGFydFRpbWUsIGVuZFRpbWUsIHRleHQpIHtcbiAgcmV0dXJuIGhhc2goc3RhcnRUaW1lLnRvU3RyaW5nKCkpICsgaGFzaChlbmRUaW1lLnRvU3RyaW5nKCkpICsgaGFzaCh0ZXh0KTtcbn1cbmNvbnN0IGNhbGN1bGF0ZU9mZnNldCA9IGZ1bmN0aW9uIGNhbGN1bGF0ZU9mZnNldCh2dHRDQ3MsIGNjLCBwcmVzZW50YXRpb25UaW1lKSB7XG4gIGxldCBjdXJyQ0MgPSB2dHRDQ3NbY2NdO1xuICBsZXQgcHJldkNDID0gdnR0Q0NzW2N1cnJDQy5wcmV2Q0NdO1xuXG4gIC8vIFRoaXMgaXMgdGhlIGZpcnN0IGRpc2NvbnRpbnVpdHkgb3IgY3VlcyBoYXZlIGJlZW4gcHJvY2Vzc2VkIHNpbmNlIHRoZSBsYXN0IGRpc2NvbnRpbnVpdHlcbiAgLy8gT2Zmc2V0ID0gY3VycmVudCBkaXNjb250aW51aXR5IHRpbWVcbiAgaWYgKCFwcmV2Q0MgfHwgIXByZXZDQy5uZXcgJiYgY3VyckNDLm5ldykge1xuICAgIHZ0dENDcy5jY09mZnNldCA9IHZ0dENDcy5wcmVzZW50YXRpb25PZmZzZXQgPSBjdXJyQ0Muc3RhcnQ7XG4gICAgY3VyckNDLm5ldyA9IGZhbHNlO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIC8vIFRoZXJlIGhhdmUgYmVlbiBkaXNjb250aW51aXRpZXMgc2luY2UgY3VlcyB3ZXJlIGxhc3QgcGFyc2VkLlxuICAvLyBPZmZzZXQgPSB0aW1lIGVsYXBzZWRcbiAgd2hpbGUgKChfcHJldkNDID0gcHJldkNDKSAhPSBudWxsICYmIF9wcmV2Q0MubmV3KSB7XG4gICAgdmFyIF9wcmV2Q0M7XG4gICAgdnR0Q0NzLmNjT2Zmc2V0ICs9IGN1cnJDQy5zdGFydCAtIHByZXZDQy5zdGFydDtcbiAgICBjdXJyQ0MubmV3ID0gZmFsc2U7XG4gICAgY3VyckNDID0gcHJldkNDO1xuICAgIHByZXZDQyA9IHZ0dENDc1tjdXJyQ0MucHJldkNDXTtcbiAgfVxuICB2dHRDQ3MucHJlc2VudGF0aW9uT2Zmc2V0ID0gcHJlc2VudGF0aW9uVGltZTtcbn07XG5mdW5jdGlvbiBwYXJzZVdlYlZUVCh2dHRCeXRlQXJyYXksIGluaXRQVFMsIHZ0dENDcywgY2MsIHRpbWVPZmZzZXQsIGNhbGxCYWNrLCBlcnJvckNhbGxCYWNrKSB7XG4gIGNvbnN0IHBhcnNlciA9IG5ldyBWVFRQYXJzZXIoKTtcbiAgLy8gQ29udmVydCBieXRlQXJyYXkgaW50byBzdHJpbmcsIHJlcGxhY2luZyBhbnkgc29tZXdoYXQgZXhvdGljIGxpbmVmZWVkcyB3aXRoIFwiXFxuXCIsIHRoZW4gc3BsaXQgb24gdGhhdCBjaGFyYWN0ZXIuXG4gIC8vIFVpbnQ4QXJyYXkucHJvdG90eXBlLnJlZHVjZSBpcyBub3QgaW1wbGVtZW50ZWQgaW4gSUUxMVxuICBjb25zdCB2dHRMaW5lcyA9IHV0ZjhBcnJheVRvU3RyKG5ldyBVaW50OEFycmF5KHZ0dEJ5dGVBcnJheSkpLnRyaW0oKS5yZXBsYWNlKExJTkVCUkVBS1MsICdcXG4nKS5zcGxpdCgnXFxuJyk7XG4gIGNvbnN0IGN1ZXMgPSBbXTtcbiAgY29uc3QgaW5pdDkwa0h6ID0gaW5pdFBUUyA/IHRvTXBlZ1RzQ2xvY2tGcm9tVGltZXNjYWxlKGluaXRQVFMuYmFzZVRpbWUsIGluaXRQVFMudGltZXNjYWxlKSA6IDA7XG4gIGxldCBjdWVUaW1lID0gJzAwOjAwLjAwMCc7XG4gIGxldCB0aW1lc3RhbXBNYXBNUEVHVFMgPSAwO1xuICBsZXQgdGltZXN0YW1wTWFwTE9DQUwgPSAwO1xuICBsZXQgcGFyc2luZ0Vycm9yO1xuICBsZXQgaW5IZWFkZXIgPSB0cnVlO1xuICBwYXJzZXIub25jdWUgPSBmdW5jdGlvbiAoY3VlKSB7XG4gICAgLy8gQWRqdXN0IGN1ZSB0aW1pbmc7IGNsYW1wIGN1ZXMgdG8gc3RhcnQgbm8gZWFybGllciB0aGFuIC0gYW5kIGRyb3AgY3VlcyB0aGF0IGRvbid0IGVuZCBhZnRlciAtIDAgb24gdGltZWxpbmUuXG4gICAgY29uc3QgY3VyckNDID0gdnR0Q0NzW2NjXTtcbiAgICBsZXQgY3VlT2Zmc2V0ID0gdnR0Q0NzLmNjT2Zmc2V0O1xuXG4gICAgLy8gQ2FsY3VsYXRlIHN1YnRpdGxlIFBUUyBvZmZzZXRcbiAgICBjb25zdCB3ZWJWdHRNcGVnVHNNYXBPZmZzZXQgPSAodGltZXN0YW1wTWFwTVBFR1RTIC0gaW5pdDkwa0h6KSAvIDkwMDAwO1xuXG4gICAgLy8gVXBkYXRlIG9mZnNldHMgZm9yIG5ldyBkaXNjb250aW51aXRpZXNcbiAgICBpZiAoY3VyckNDICE9IG51bGwgJiYgY3VyckNDLm5ldykge1xuICAgICAgaWYgKHRpbWVzdGFtcE1hcExPQ0FMICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgLy8gV2hlbiBsb2NhbCB0aW1lIGlzIHByb3ZpZGVkLCBvZmZzZXQgPSBkaXNjb250aW51aXR5IHN0YXJ0IHRpbWUgLSBsb2NhbCB0aW1lXG4gICAgICAgIGN1ZU9mZnNldCA9IHZ0dENDcy5jY09mZnNldCA9IGN1cnJDQy5zdGFydDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNhbGN1bGF0ZU9mZnNldCh2dHRDQ3MsIGNjLCB3ZWJWdHRNcGVnVHNNYXBPZmZzZXQpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAod2ViVnR0TXBlZ1RzTWFwT2Zmc2V0KSB7XG4gICAgICBpZiAoIWluaXRQVFMpIHtcbiAgICAgICAgcGFyc2luZ0Vycm9yID0gbmV3IEVycm9yKCdNaXNzaW5nIGluaXRQVFMgZm9yIFZUVCBNUEVHVFMnKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgLy8gSWYgd2UgaGF2ZSBNUEVHVFMsIG9mZnNldCA9IHByZXNlbnRhdGlvbiB0aW1lICsgZGlzY29udGludWl0eSBvZmZzZXRcbiAgICAgIGN1ZU9mZnNldCA9IHdlYlZ0dE1wZWdUc01hcE9mZnNldCAtIHZ0dENDcy5wcmVzZW50YXRpb25PZmZzZXQ7XG4gICAgfVxuICAgIGNvbnN0IGR1cmF0aW9uID0gY3VlLmVuZFRpbWUgLSBjdWUuc3RhcnRUaW1lO1xuICAgIGNvbnN0IHN0YXJ0VGltZSA9IG5vcm1hbGl6ZVB0cygoY3VlLnN0YXJ0VGltZSArIGN1ZU9mZnNldCAtIHRpbWVzdGFtcE1hcExPQ0FMKSAqIDkwMDAwLCB0aW1lT2Zmc2V0ICogOTAwMDApIC8gOTAwMDA7XG4gICAgY3VlLnN0YXJ0VGltZSA9IE1hdGgubWF4KHN0YXJ0VGltZSwgMCk7XG4gICAgY3VlLmVuZFRpbWUgPSBNYXRoLm1heChzdGFydFRpbWUgKyBkdXJhdGlvbiwgMCk7XG5cbiAgICAvL3RyaW0gdHJhaWxpbmcgd2VidnR0IGJsb2NrIHdoaXRlc3BhY2VzXG4gICAgY29uc3QgdGV4dCA9IGN1ZS50ZXh0LnRyaW0oKTtcblxuICAgIC8vIEZpeCBlbmNvZGluZyBvZiBzcGVjaWFsIGNoYXJhY3RlcnNcbiAgICBjdWUudGV4dCA9IGRlY29kZVVSSUNvbXBvbmVudChlbmNvZGVVUklDb21wb25lbnQodGV4dCkpO1xuXG4gICAgLy8gSWYgdGhlIGN1ZSB3YXMgbm90IGFzc2lnbmVkIGFuIGlkIGZyb20gdGhlIFZUVCBmaWxlIChsaW5lIGFib3ZlIHRoZSBjb250ZW50KSwgY3JlYXRlIG9uZS5cbiAgICBpZiAoIWN1ZS5pZCkge1xuICAgICAgY3VlLmlkID0gZ2VuZXJhdGVDdWVJZChjdWUuc3RhcnRUaW1lLCBjdWUuZW5kVGltZSwgdGV4dCk7XG4gICAgfVxuICAgIGlmIChjdWUuZW5kVGltZSA+IDApIHtcbiAgICAgIGN1ZXMucHVzaChjdWUpO1xuICAgIH1cbiAgfTtcbiAgcGFyc2VyLm9ucGFyc2luZ2Vycm9yID0gZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgcGFyc2luZ0Vycm9yID0gZXJyb3I7XG4gIH07XG4gIHBhcnNlci5vbmZsdXNoID0gZnVuY3Rpb24gKCkge1xuICAgIGlmIChwYXJzaW5nRXJyb3IpIHtcbiAgICAgIGVycm9yQ2FsbEJhY2socGFyc2luZ0Vycm9yKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY2FsbEJhY2soY3Vlcyk7XG4gIH07XG5cbiAgLy8gR28gdGhyb3VnaCBjb250ZW50cyBsaW5lIGJ5IGxpbmUuXG4gIHZ0dExpbmVzLmZvckVhY2gobGluZSA9PiB7XG4gICAgaWYgKGluSGVhZGVyKSB7XG4gICAgICAvLyBMb29rIGZvciBYLVRJTUVTVEFNUC1NQVAgaW4gaGVhZGVyLlxuICAgICAgaWYgKHN0YXJ0c1dpdGgobGluZSwgJ1gtVElNRVNUQU1QLU1BUD0nKSkge1xuICAgICAgICAvLyBPbmNlIGZvdW5kLCBubyBtb3JlIGFyZSBhbGxvd2VkIGFueXdheSwgc28gc3RvcCBzZWFyY2hpbmcuXG4gICAgICAgIGluSGVhZGVyID0gZmFsc2U7XG4gICAgICAgIC8vIEV4dHJhY3QgTE9DQUwgYW5kIE1QRUdUUy5cbiAgICAgICAgbGluZS5zbGljZSgxNikuc3BsaXQoJywnKS5mb3JFYWNoKHRpbWVzdGFtcCA9PiB7XG4gICAgICAgICAgaWYgKHN0YXJ0c1dpdGgodGltZXN0YW1wLCAnTE9DQUw6JykpIHtcbiAgICAgICAgICAgIGN1ZVRpbWUgPSB0aW1lc3RhbXAuc2xpY2UoNik7XG4gICAgICAgICAgfSBlbHNlIGlmIChzdGFydHNXaXRoKHRpbWVzdGFtcCwgJ01QRUdUUzonKSkge1xuICAgICAgICAgICAgdGltZXN0YW1wTWFwTVBFR1RTID0gcGFyc2VJbnQodGltZXN0YW1wLnNsaWNlKDcpKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICB0cnkge1xuICAgICAgICAgIC8vIENvbnZlcnQgY3VlIHRpbWUgdG8gc2Vjb25kc1xuICAgICAgICAgIHRpbWVzdGFtcE1hcExPQ0FMID0gY3VlU3RyaW5nMm1pbGxpcyhjdWVUaW1lKSAvIDEwMDA7XG4gICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgcGFyc2luZ0Vycm9yID0gZXJyb3I7XG4gICAgICAgIH1cbiAgICAgICAgLy8gUmV0dXJuIHdpdGhvdXQgcGFyc2luZyBYLVRJTUVTVEFNUC1NQVAgbGluZS5cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfSBlbHNlIGlmIChsaW5lID09PSAnJykge1xuICAgICAgICBpbkhlYWRlciA9IGZhbHNlO1xuICAgICAgfVxuICAgIH1cbiAgICAvLyBQYXJzZSBsaW5lIGJ5IGRlZmF1bHQuXG4gICAgcGFyc2VyLnBhcnNlKGxpbmUgKyAnXFxuJyk7XG4gIH0pO1xuICBwYXJzZXIuZmx1c2goKTtcbn1cblxuY29uc3QgSU1TQzFfQ09ERUMgPSAnc3RwcC50dG1sLmltMXQnO1xuXG4vLyBUaW1lIGZvcm1hdDogaDptOnM6ZnJhbWVzKC5zdWJmcmFtZXMpXG5jb25zdCBITVNGX1JFR0VYID0gL14oXFxkezIsfSk6KFxcZHsyfSk6KFxcZHsyfSk6KFxcZHsyfSlcXC4/KFxcZCspPyQvO1xuXG4vLyBUaW1lIGZvcm1hdDogaG91cnMsIG1pbnV0ZXMsIHNlY29uZHMsIG1pbGxpc2Vjb25kcywgZnJhbWVzLCB0aWNrc1xuY29uc3QgVElNRV9VTklUX1JFR0VYID0gL14oXFxkKig/OlxcLlxcZCopPykoaHxtfHN8bXN8Znx0KSQvO1xuY29uc3QgdGV4dEFsaWduVG9MaW5lQWxpZ24gPSB7XG4gIGxlZnQ6ICdzdGFydCcsXG4gIGNlbnRlcjogJ2NlbnRlcicsXG4gIHJpZ2h0OiAnZW5kJyxcbiAgc3RhcnQ6ICdzdGFydCcsXG4gIGVuZDogJ2VuZCdcbn07XG5mdW5jdGlvbiBwYXJzZUlNU0MxKHBheWxvYWQsIGluaXRQVFMsIGNhbGxCYWNrLCBlcnJvckNhbGxCYWNrKSB7XG4gIGNvbnN0IHJlc3VsdHMgPSBmaW5kQm94KG5ldyBVaW50OEFycmF5KHBheWxvYWQpLCBbJ21kYXQnXSk7XG4gIGlmIChyZXN1bHRzLmxlbmd0aCA9PT0gMCkge1xuICAgIGVycm9yQ2FsbEJhY2sobmV3IEVycm9yKCdDb3VsZCBub3QgcGFyc2UgSU1TQzEgbWRhdCcpKTtcbiAgICByZXR1cm47XG4gIH1cbiAgY29uc3QgdHRtbExpc3QgPSByZXN1bHRzLm1hcChtZGF0ID0+IHV0ZjhBcnJheVRvU3RyKG1kYXQpKTtcbiAgY29uc3Qgc3luY1RpbWUgPSB0b1RpbWVzY2FsZUZyb21TY2FsZShpbml0UFRTLmJhc2VUaW1lLCAxLCBpbml0UFRTLnRpbWVzY2FsZSk7XG4gIHRyeSB7XG4gICAgdHRtbExpc3QuZm9yRWFjaCh0dG1sID0+IGNhbGxCYWNrKHBhcnNlVFRNTCh0dG1sLCBzeW5jVGltZSkpKTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBlcnJvckNhbGxCYWNrKGVycm9yKTtcbiAgfVxufVxuZnVuY3Rpb24gcGFyc2VUVE1MKHR0bWwsIHN5bmNUaW1lKSB7XG4gIGNvbnN0IHBhcnNlciA9IG5ldyBET01QYXJzZXIoKTtcbiAgY29uc3QgeG1sRG9jID0gcGFyc2VyLnBhcnNlRnJvbVN0cmluZyh0dG1sLCAndGV4dC94bWwnKTtcbiAgY29uc3QgdHQgPSB4bWxEb2MuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ3R0JylbMF07XG4gIGlmICghdHQpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgdHRtbCcpO1xuICB9XG4gIGNvbnN0IGRlZmF1bHRSYXRlSW5mbyA9IHtcbiAgICBmcmFtZVJhdGU6IDMwLFxuICAgIHN1YkZyYW1lUmF0ZTogMSxcbiAgICBmcmFtZVJhdGVNdWx0aXBsaWVyOiAwLFxuICAgIHRpY2tSYXRlOiAwXG4gIH07XG4gIGNvbnN0IHJhdGVJbmZvID0gT2JqZWN0LmtleXMoZGVmYXVsdFJhdGVJbmZvKS5yZWR1Y2UoKHJlc3VsdCwga2V5KSA9PiB7XG4gICAgcmVzdWx0W2tleV0gPSB0dC5nZXRBdHRyaWJ1dGUoYHR0cDoke2tleX1gKSB8fCBkZWZhdWx0UmF0ZUluZm9ba2V5XTtcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9LCB7fSk7XG4gIGNvbnN0IHRyaW0gPSB0dC5nZXRBdHRyaWJ1dGUoJ3htbDpzcGFjZScpICE9PSAncHJlc2VydmUnO1xuICBjb25zdCBzdHlsZUVsZW1lbnRzID0gY29sbGVjdGlvblRvRGljdGlvbmFyeShnZXRFbGVtZW50Q29sbGVjdGlvbih0dCwgJ3N0eWxpbmcnLCAnc3R5bGUnKSk7XG4gIGNvbnN0IHJlZ2lvbkVsZW1lbnRzID0gY29sbGVjdGlvblRvRGljdGlvbmFyeShnZXRFbGVtZW50Q29sbGVjdGlvbih0dCwgJ2xheW91dCcsICdyZWdpb24nKSk7XG4gIGNvbnN0IGN1ZUVsZW1lbnRzID0gZ2V0RWxlbWVudENvbGxlY3Rpb24odHQsICdib2R5JywgJ1tiZWdpbl0nKTtcbiAgcmV0dXJuIFtdLm1hcC5jYWxsKGN1ZUVsZW1lbnRzLCBjdWVFbGVtZW50ID0+IHtcbiAgICBjb25zdCBjdWVUZXh0ID0gZ2V0VGV4dENvbnRlbnQoY3VlRWxlbWVudCwgdHJpbSk7XG4gICAgaWYgKCFjdWVUZXh0IHx8ICFjdWVFbGVtZW50Lmhhc0F0dHJpYnV0ZSgnYmVnaW4nKSkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGNvbnN0IHN0YXJ0VGltZSA9IHBhcnNlVHRtbFRpbWUoY3VlRWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2JlZ2luJyksIHJhdGVJbmZvKTtcbiAgICBjb25zdCBkdXJhdGlvbiA9IHBhcnNlVHRtbFRpbWUoY3VlRWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2R1cicpLCByYXRlSW5mbyk7XG4gICAgbGV0IGVuZFRpbWUgPSBwYXJzZVR0bWxUaW1lKGN1ZUVsZW1lbnQuZ2V0QXR0cmlidXRlKCdlbmQnKSwgcmF0ZUluZm8pO1xuICAgIGlmIChzdGFydFRpbWUgPT09IG51bGwpIHtcbiAgICAgIHRocm93IHRpbWVzdGFtcFBhcnNpbmdFcnJvcihjdWVFbGVtZW50KTtcbiAgICB9XG4gICAgaWYgKGVuZFRpbWUgPT09IG51bGwpIHtcbiAgICAgIGlmIChkdXJhdGlvbiA9PT0gbnVsbCkge1xuICAgICAgICB0aHJvdyB0aW1lc3RhbXBQYXJzaW5nRXJyb3IoY3VlRWxlbWVudCk7XG4gICAgICB9XG4gICAgICBlbmRUaW1lID0gc3RhcnRUaW1lICsgZHVyYXRpb247XG4gICAgfVxuICAgIGNvbnN0IGN1ZSA9IG5ldyBWVFRDdWUoc3RhcnRUaW1lIC0gc3luY1RpbWUsIGVuZFRpbWUgLSBzeW5jVGltZSwgY3VlVGV4dCk7XG4gICAgY3VlLmlkID0gZ2VuZXJhdGVDdWVJZChjdWUuc3RhcnRUaW1lLCBjdWUuZW5kVGltZSwgY3VlLnRleHQpO1xuICAgIGNvbnN0IHJlZ2lvbiA9IHJlZ2lvbkVsZW1lbnRzW2N1ZUVsZW1lbnQuZ2V0QXR0cmlidXRlKCdyZWdpb24nKV07XG4gICAgY29uc3Qgc3R5bGUgPSBzdHlsZUVsZW1lbnRzW2N1ZUVsZW1lbnQuZ2V0QXR0cmlidXRlKCdzdHlsZScpXTtcblxuICAgIC8vIEFwcGx5IHN0eWxlcyB0byBjdWVcbiAgICBjb25zdCBzdHlsZXMgPSBnZXRUdG1sU3R5bGVzKHJlZ2lvbiwgc3R5bGUsIHN0eWxlRWxlbWVudHMpO1xuICAgIGNvbnN0IHtcbiAgICAgIHRleHRBbGlnblxuICAgIH0gPSBzdHlsZXM7XG4gICAgaWYgKHRleHRBbGlnbikge1xuICAgICAgLy8gY3VlLnBvc2l0aW9uQWxpZ24gbm90IHNldHRhYmxlIGluIEZGfjIwMTZcbiAgICAgIGNvbnN0IGxpbmVBbGlnbiA9IHRleHRBbGlnblRvTGluZUFsaWduW3RleHRBbGlnbl07XG4gICAgICBpZiAobGluZUFsaWduKSB7XG4gICAgICAgIGN1ZS5saW5lQWxpZ24gPSBsaW5lQWxpZ247XG4gICAgICB9XG4gICAgICBjdWUuYWxpZ24gPSB0ZXh0QWxpZ247XG4gICAgfVxuICAgIF9leHRlbmRzKGN1ZSwgc3R5bGVzKTtcbiAgICByZXR1cm4gY3VlO1xuICB9KS5maWx0ZXIoY3VlID0+IGN1ZSAhPT0gbnVsbCk7XG59XG5mdW5jdGlvbiBnZXRFbGVtZW50Q29sbGVjdGlvbihmcm9tRWxlbWVudCwgcGFyZW50TmFtZSwgY2hpbGROYW1lKSB7XG4gIGNvbnN0IHBhcmVudCA9IGZyb21FbGVtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKHBhcmVudE5hbWUpWzBdO1xuICBpZiAocGFyZW50KSB7XG4gICAgcmV0dXJuIFtdLnNsaWNlLmNhbGwocGFyZW50LnF1ZXJ5U2VsZWN0b3JBbGwoY2hpbGROYW1lKSk7XG4gIH1cbiAgcmV0dXJuIFtdO1xufVxuZnVuY3Rpb24gY29sbGVjdGlvblRvRGljdGlvbmFyeShlbGVtZW50c1dpdGhJZCkge1xuICByZXR1cm4gZWxlbWVudHNXaXRoSWQucmVkdWNlKChkaWN0LCBlbGVtZW50KSA9PiB7XG4gICAgY29uc3QgaWQgPSBlbGVtZW50LmdldEF0dHJpYnV0ZSgneG1sOmlkJyk7XG4gICAgaWYgKGlkKSB7XG4gICAgICBkaWN0W2lkXSA9IGVsZW1lbnQ7XG4gICAgfVxuICAgIHJldHVybiBkaWN0O1xuICB9LCB7fSk7XG59XG5mdW5jdGlvbiBnZXRUZXh0Q29udGVudChlbGVtZW50LCB0cmltKSB7XG4gIHJldHVybiBbXS5zbGljZS5jYWxsKGVsZW1lbnQuY2hpbGROb2RlcykucmVkdWNlKChzdHIsIG5vZGUsIGkpID0+IHtcbiAgICB2YXIgX25vZGUkY2hpbGROb2RlcztcbiAgICBpZiAobm9kZS5ub2RlTmFtZSA9PT0gJ2JyJyAmJiBpKSB7XG4gICAgICByZXR1cm4gc3RyICsgJ1xcbic7XG4gICAgfVxuICAgIGlmICgoX25vZGUkY2hpbGROb2RlcyA9IG5vZGUuY2hpbGROb2RlcykgIT0gbnVsbCAmJiBfbm9kZSRjaGlsZE5vZGVzLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIGdldFRleHRDb250ZW50KG5vZGUsIHRyaW0pO1xuICAgIH0gZWxzZSBpZiAodHJpbSkge1xuICAgICAgcmV0dXJuIHN0ciArIG5vZGUudGV4dENvbnRlbnQudHJpbSgpLnJlcGxhY2UoL1xccysvZywgJyAnKTtcbiAgICB9XG4gICAgcmV0dXJuIHN0ciArIG5vZGUudGV4dENvbnRlbnQ7XG4gIH0sICcnKTtcbn1cbmZ1bmN0aW9uIGdldFR0bWxTdHlsZXMocmVnaW9uLCBzdHlsZSwgc3R5bGVFbGVtZW50cykge1xuICBjb25zdCB0dHNOcyA9ICdodHRwOi8vd3d3LnczLm9yZy9ucy90dG1sI3N0eWxpbmcnO1xuICBsZXQgcmVnaW9uU3R5bGUgPSBudWxsO1xuICBjb25zdCBzdHlsZUF0dHJpYnV0ZXMgPSBbJ2Rpc3BsYXlBbGlnbicsICd0ZXh0QWxpZ24nLCAnY29sb3InLCAnYmFja2dyb3VuZENvbG9yJywgJ2ZvbnRTaXplJywgJ2ZvbnRGYW1pbHknXG4gIC8vICdmb250V2VpZ2h0JyxcbiAgLy8gJ2xpbmVIZWlnaHQnLFxuICAvLyAnd3JhcE9wdGlvbicsXG4gIC8vICdmb250U3R5bGUnLFxuICAvLyAnZGlyZWN0aW9uJyxcbiAgLy8gJ3dyaXRpbmdNb2RlJ1xuICBdO1xuICBjb25zdCByZWdpb25TdHlsZU5hbWUgPSByZWdpb24gIT0gbnVsbCAmJiByZWdpb24uaGFzQXR0cmlidXRlKCdzdHlsZScpID8gcmVnaW9uLmdldEF0dHJpYnV0ZSgnc3R5bGUnKSA6IG51bGw7XG4gIGlmIChyZWdpb25TdHlsZU5hbWUgJiYgc3R5bGVFbGVtZW50cy5oYXNPd25Qcm9wZXJ0eShyZWdpb25TdHlsZU5hbWUpKSB7XG4gICAgcmVnaW9uU3R5bGUgPSBzdHlsZUVsZW1lbnRzW3JlZ2lvblN0eWxlTmFtZV07XG4gIH1cbiAgcmV0dXJuIHN0eWxlQXR0cmlidXRlcy5yZWR1Y2UoKHN0eWxlcywgbmFtZSkgPT4ge1xuICAgIGNvbnN0IHZhbHVlID0gZ2V0QXR0cmlidXRlTlMoc3R5bGUsIHR0c05zLCBuYW1lKSB8fCBnZXRBdHRyaWJ1dGVOUyhyZWdpb24sIHR0c05zLCBuYW1lKSB8fCBnZXRBdHRyaWJ1dGVOUyhyZWdpb25TdHlsZSwgdHRzTnMsIG5hbWUpO1xuICAgIGlmICh2YWx1ZSkge1xuICAgICAgc3R5bGVzW25hbWVdID0gdmFsdWU7XG4gICAgfVxuICAgIHJldHVybiBzdHlsZXM7XG4gIH0sIHt9KTtcbn1cbmZ1bmN0aW9uIGdldEF0dHJpYnV0ZU5TKGVsZW1lbnQsIG5zLCBuYW1lKSB7XG4gIGlmICghZWxlbWVudCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIHJldHVybiBlbGVtZW50Lmhhc0F0dHJpYnV0ZU5TKG5zLCBuYW1lKSA/IGVsZW1lbnQuZ2V0QXR0cmlidXRlTlMobnMsIG5hbWUpIDogbnVsbDtcbn1cbmZ1bmN0aW9uIHRpbWVzdGFtcFBhcnNpbmdFcnJvcihub2RlKSB7XG4gIHJldHVybiBuZXcgRXJyb3IoYENvdWxkIG5vdCBwYXJzZSB0dG1sIHRpbWVzdGFtcCAke25vZGV9YCk7XG59XG5mdW5jdGlvbiBwYXJzZVR0bWxUaW1lKHRpbWVBdHRyaWJ1dGVWYWx1ZSwgcmF0ZUluZm8pIHtcbiAgaWYgKCF0aW1lQXR0cmlidXRlVmFsdWUpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBsZXQgc2Vjb25kcyA9IHBhcnNlVGltZVN0YW1wKHRpbWVBdHRyaWJ1dGVWYWx1ZSk7XG4gIGlmIChzZWNvbmRzID09PSBudWxsKSB7XG4gICAgaWYgKEhNU0ZfUkVHRVgudGVzdCh0aW1lQXR0cmlidXRlVmFsdWUpKSB7XG4gICAgICBzZWNvbmRzID0gcGFyc2VIb3Vyc01pbnV0ZXNTZWNvbmRzRnJhbWVzKHRpbWVBdHRyaWJ1dGVWYWx1ZSwgcmF0ZUluZm8pO1xuICAgIH0gZWxzZSBpZiAoVElNRV9VTklUX1JFR0VYLnRlc3QodGltZUF0dHJpYnV0ZVZhbHVlKSkge1xuICAgICAgc2Vjb25kcyA9IHBhcnNlVGltZVVuaXRzKHRpbWVBdHRyaWJ1dGVWYWx1ZSwgcmF0ZUluZm8pO1xuICAgIH1cbiAgfVxuICByZXR1cm4gc2Vjb25kcztcbn1cbmZ1bmN0aW9uIHBhcnNlSG91cnNNaW51dGVzU2Vjb25kc0ZyYW1lcyh0aW1lQXR0cmlidXRlVmFsdWUsIHJhdGVJbmZvKSB7XG4gIGNvbnN0IG0gPSBITVNGX1JFR0VYLmV4ZWModGltZUF0dHJpYnV0ZVZhbHVlKTtcbiAgY29uc3QgZnJhbWVzID0gKG1bNF0gfCAwKSArIChtWzVdIHwgMCkgLyByYXRlSW5mby5zdWJGcmFtZVJhdGU7XG4gIHJldHVybiAobVsxXSB8IDApICogMzYwMCArIChtWzJdIHwgMCkgKiA2MCArIChtWzNdIHwgMCkgKyBmcmFtZXMgLyByYXRlSW5mby5mcmFtZVJhdGU7XG59XG5mdW5jdGlvbiBwYXJzZVRpbWVVbml0cyh0aW1lQXR0cmlidXRlVmFsdWUsIHJhdGVJbmZvKSB7XG4gIGNvbnN0IG0gPSBUSU1FX1VOSVRfUkVHRVguZXhlYyh0aW1lQXR0cmlidXRlVmFsdWUpO1xuICBjb25zdCB2YWx1ZSA9IE51bWJlcihtWzFdKTtcbiAgY29uc3QgdW5pdCA9IG1bMl07XG4gIHN3aXRjaCAodW5pdCkge1xuICAgIGNhc2UgJ2gnOlxuICAgICAgcmV0dXJuIHZhbHVlICogMzYwMDtcbiAgICBjYXNlICdtJzpcbiAgICAgIHJldHVybiB2YWx1ZSAqIDYwO1xuICAgIGNhc2UgJ21zJzpcbiAgICAgIHJldHVybiB2YWx1ZSAqIDEwMDA7XG4gICAgY2FzZSAnZic6XG4gICAgICByZXR1cm4gdmFsdWUgLyByYXRlSW5mby5mcmFtZVJhdGU7XG4gICAgY2FzZSAndCc6XG4gICAgICByZXR1cm4gdmFsdWUgLyByYXRlSW5mby50aWNrUmF0ZTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbmNsYXNzIFRpbWVsaW5lQ29udHJvbGxlciB7XG4gIGNvbnN0cnVjdG9yKGhscykge1xuICAgIHRoaXMuaGxzID0gdm9pZCAwO1xuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIHRoaXMuY29uZmlnID0gdm9pZCAwO1xuICAgIHRoaXMuZW5hYmxlZCA9IHRydWU7XG4gICAgdGhpcy5DdWVzID0gdm9pZCAwO1xuICAgIHRoaXMudGV4dFRyYWNrcyA9IFtdO1xuICAgIHRoaXMudHJhY2tzID0gW107XG4gICAgdGhpcy5pbml0UFRTID0gW107XG4gICAgdGhpcy51bnBhcnNlZFZ0dEZyYWdzID0gW107XG4gICAgdGhpcy5jYXB0aW9uc1RyYWNrcyA9IHt9O1xuICAgIHRoaXMubm9uTmF0aXZlQ2FwdGlvbnNUcmFja3MgPSB7fTtcbiAgICB0aGlzLmNlYTYwOFBhcnNlcjEgPSB2b2lkIDA7XG4gICAgdGhpcy5jZWE2MDhQYXJzZXIyID0gdm9pZCAwO1xuICAgIHRoaXMubGFzdENjID0gLTE7XG4gICAgLy8gTGFzdCB2aWRlbyAoQ0VBLTYwOCkgZnJhZ21lbnQgQ0NcbiAgICB0aGlzLmxhc3RTbiA9IC0xO1xuICAgIC8vIExhc3QgdmlkZW8gKENFQS02MDgpIGZyYWdtZW50IE1TTlxuICAgIHRoaXMubGFzdFBhcnRJbmRleCA9IC0xO1xuICAgIC8vIExhc3QgdmlkZW8gKENFQS02MDgpIGZyYWdtZW50IFBhcnQgSW5kZXhcbiAgICB0aGlzLnByZXZDQyA9IC0xO1xuICAgIC8vIExhc3Qgc3VidGl0bGUgZnJhZ21lbnQgQ0NcbiAgICB0aGlzLnZ0dENDcyA9IG5ld1ZUVENDcygpO1xuICAgIHRoaXMuY2FwdGlvbnNQcm9wZXJ0aWVzID0gdm9pZCAwO1xuICAgIHRoaXMuaGxzID0gaGxzO1xuICAgIHRoaXMuY29uZmlnID0gaGxzLmNvbmZpZztcbiAgICB0aGlzLkN1ZXMgPSBobHMuY29uZmlnLmN1ZUhhbmRsZXI7XG4gICAgdGhpcy5jYXB0aW9uc1Byb3BlcnRpZXMgPSB7XG4gICAgICB0ZXh0VHJhY2sxOiB7XG4gICAgICAgIGxhYmVsOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazFMYWJlbCxcbiAgICAgICAgbGFuZ3VhZ2VDb2RlOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazFMYW5ndWFnZUNvZGVcbiAgICAgIH0sXG4gICAgICB0ZXh0VHJhY2syOiB7XG4gICAgICAgIGxhYmVsOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazJMYWJlbCxcbiAgICAgICAgbGFuZ3VhZ2VDb2RlOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazJMYW5ndWFnZUNvZGVcbiAgICAgIH0sXG4gICAgICB0ZXh0VHJhY2szOiB7XG4gICAgICAgIGxhYmVsOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazNMYWJlbCxcbiAgICAgICAgbGFuZ3VhZ2VDb2RlOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazNMYW5ndWFnZUNvZGVcbiAgICAgIH0sXG4gICAgICB0ZXh0VHJhY2s0OiB7XG4gICAgICAgIGxhYmVsOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazRMYWJlbCxcbiAgICAgICAgbGFuZ3VhZ2VDb2RlOiB0aGlzLmNvbmZpZy5jYXB0aW9uc1RleHRUcmFjazRMYW5ndWFnZUNvZGVcbiAgICAgIH1cbiAgICB9O1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfQVRUQUNISU5HLCB0aGlzLm9uTWVkaWFBdHRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfREVUQUNISU5HLCB0aGlzLm9uTWVkaWFEZXRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FERUQsIHRoaXMub25NYW5pZmVzdExvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5TVUJUSVRMRV9UUkFDS1NfVVBEQVRFRCwgdGhpcy5vblN1YnRpdGxlVHJhY2tzVXBkYXRlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0xPQURJTkcsIHRoaXMub25GcmFnTG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0xPQURFRCwgdGhpcy5vbkZyYWdMb2FkZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuRlJBR19QQVJTSU5HX1VTRVJEQVRBLCB0aGlzLm9uRnJhZ1BhcnNpbmdVc2VyZGF0YSwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0RFQ1JZUFRFRCwgdGhpcy5vbkZyYWdEZWNyeXB0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuSU5JVF9QVFNfRk9VTkQsIHRoaXMub25Jbml0UHRzRm91bmQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuU1VCVElUTEVfVFJBQ0tTX0NMRUFSRUQsIHRoaXMub25TdWJ0aXRsZVRyYWNrc0NsZWFyZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQlVGRkVSX0ZMVVNISU5HLCB0aGlzLm9uQnVmZmVyRmx1c2hpbmcsIHRoaXMpO1xuICB9XG4gIGRlc3Ryb3koKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNISU5HLCB0aGlzLm9uTWVkaWFBdHRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1FRElBX0RFVEFDSElORywgdGhpcy5vbk1lZGlhRGV0YWNoaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9MT0FERUQsIHRoaXMub25NYW5pZmVzdExvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuU1VCVElUTEVfVFJBQ0tTX1VQREFURUQsIHRoaXMub25TdWJ0aXRsZVRyYWNrc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkZSQUdfTE9BRElORywgdGhpcy5vbkZyYWdMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0xPQURFRCwgdGhpcy5vbkZyYWdMb2FkZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkZSQUdfUEFSU0lOR19VU0VSREFUQSwgdGhpcy5vbkZyYWdQYXJzaW5nVXNlcmRhdGEsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkZSQUdfREVDUllQVEVELCB0aGlzLm9uRnJhZ0RlY3J5cHRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuSU5JVF9QVFNfRk9VTkQsIHRoaXMub25Jbml0UHRzRm91bmQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLlNVQlRJVExFX1RSQUNLU19DTEVBUkVELCB0aGlzLm9uU3VidGl0bGVUcmFja3NDbGVhcmVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfRkxVU0hJTkcsIHRoaXMub25CdWZmZXJGbHVzaGluZywgdGhpcyk7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuaGxzID0gdGhpcy5jb25maWcgPSBudWxsO1xuICAgIHRoaXMuY2VhNjA4UGFyc2VyMSA9IHRoaXMuY2VhNjA4UGFyc2VyMiA9IHVuZGVmaW5lZDtcbiAgfVxuICBpbml0Q2VhNjA4UGFyc2VycygpIHtcbiAgICBpZiAodGhpcy5jb25maWcuZW5hYmxlQ0VBNzA4Q2FwdGlvbnMgJiYgKCF0aGlzLmNlYTYwOFBhcnNlcjEgfHwgIXRoaXMuY2VhNjA4UGFyc2VyMikpIHtcbiAgICAgIGNvbnN0IGNoYW5uZWwxID0gbmV3IE91dHB1dEZpbHRlcih0aGlzLCAndGV4dFRyYWNrMScpO1xuICAgICAgY29uc3QgY2hhbm5lbDIgPSBuZXcgT3V0cHV0RmlsdGVyKHRoaXMsICd0ZXh0VHJhY2syJyk7XG4gICAgICBjb25zdCBjaGFubmVsMyA9IG5ldyBPdXRwdXRGaWx0ZXIodGhpcywgJ3RleHRUcmFjazMnKTtcbiAgICAgIGNvbnN0IGNoYW5uZWw0ID0gbmV3IE91dHB1dEZpbHRlcih0aGlzLCAndGV4dFRyYWNrNCcpO1xuICAgICAgdGhpcy5jZWE2MDhQYXJzZXIxID0gbmV3IENlYTYwOFBhcnNlcigxLCBjaGFubmVsMSwgY2hhbm5lbDIpO1xuICAgICAgdGhpcy5jZWE2MDhQYXJzZXIyID0gbmV3IENlYTYwOFBhcnNlcigzLCBjaGFubmVsMywgY2hhbm5lbDQpO1xuICAgIH1cbiAgfVxuICBhZGRDdWVzKHRyYWNrTmFtZSwgc3RhcnRUaW1lLCBlbmRUaW1lLCBzY3JlZW4sIGN1ZVJhbmdlcykge1xuICAgIC8vIHNraXAgY3VlcyB3aGljaCBvdmVybGFwIG1vcmUgdGhhbiA1MCUgd2l0aCBwcmV2aW91c2x5IHBhcnNlZCB0aW1lIHJhbmdlc1xuICAgIGxldCBtZXJnZWQgPSBmYWxzZTtcbiAgICBmb3IgKGxldCBpID0gY3VlUmFuZ2VzLmxlbmd0aDsgaS0tOykge1xuICAgICAgY29uc3QgY3VlUmFuZ2UgPSBjdWVSYW5nZXNbaV07XG4gICAgICBjb25zdCBvdmVybGFwID0gaW50ZXJzZWN0aW9uKGN1ZVJhbmdlWzBdLCBjdWVSYW5nZVsxXSwgc3RhcnRUaW1lLCBlbmRUaW1lKTtcbiAgICAgIGlmIChvdmVybGFwID49IDApIHtcbiAgICAgICAgY3VlUmFuZ2VbMF0gPSBNYXRoLm1pbihjdWVSYW5nZVswXSwgc3RhcnRUaW1lKTtcbiAgICAgICAgY3VlUmFuZ2VbMV0gPSBNYXRoLm1heChjdWVSYW5nZVsxXSwgZW5kVGltZSk7XG4gICAgICAgIG1lcmdlZCA9IHRydWU7XG4gICAgICAgIGlmIChvdmVybGFwIC8gKGVuZFRpbWUgLSBzdGFydFRpbWUpID4gMC41KSB7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIGlmICghbWVyZ2VkKSB7XG4gICAgICBjdWVSYW5nZXMucHVzaChbc3RhcnRUaW1lLCBlbmRUaW1lXSk7XG4gICAgfVxuICAgIGlmICh0aGlzLmNvbmZpZy5yZW5kZXJUZXh0VHJhY2tzTmF0aXZlbHkpIHtcbiAgICAgIGNvbnN0IHRyYWNrID0gdGhpcy5jYXB0aW9uc1RyYWNrc1t0cmFja05hbWVdO1xuICAgICAgdGhpcy5DdWVzLm5ld0N1ZSh0cmFjaywgc3RhcnRUaW1lLCBlbmRUaW1lLCBzY3JlZW4pO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBjdWVzID0gdGhpcy5DdWVzLm5ld0N1ZShudWxsLCBzdGFydFRpbWUsIGVuZFRpbWUsIHNjcmVlbik7XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5DVUVTX1BBUlNFRCwge1xuICAgICAgICB0eXBlOiAnY2FwdGlvbnMnLFxuICAgICAgICBjdWVzLFxuICAgICAgICB0cmFjazogdHJhY2tOYW1lXG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICAvLyBUcmlnZ2VyZWQgd2hlbiBhbiBpbml0aWFsIFBUUyBpcyBmb3VuZDsgdXNlZCBmb3Igc3luY2hyb25pc2F0aW9uIG9mIFdlYlZUVC5cbiAgb25Jbml0UHRzRm91bmQoZXZlbnQsIHtcbiAgICBmcmFnLFxuICAgIGlkLFxuICAgIGluaXRQVFMsXG4gICAgdGltZXNjYWxlXG4gIH0pIHtcbiAgICBjb25zdCB7XG4gICAgICB1bnBhcnNlZFZ0dEZyYWdzXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKGlkID09PSAnbWFpbicpIHtcbiAgICAgIHRoaXMuaW5pdFBUU1tmcmFnLmNjXSA9IHtcbiAgICAgICAgYmFzZVRpbWU6IGluaXRQVFMsXG4gICAgICAgIHRpbWVzY2FsZVxuICAgICAgfTtcbiAgICB9XG5cbiAgICAvLyBEdWUgdG8gYXN5bmNocm9ub3VzIHByb2Nlc3NpbmcsIGluaXRpYWwgUFRTIG1heSBhcnJpdmUgbGF0ZXIgdGhhbiB0aGUgZmlyc3QgVlRUIGZyYWdtZW50cyBhcmUgbG9hZGVkLlxuICAgIC8vIFBhcnNlIGFueSB1bnBhcnNlZCBmcmFnbWVudHMgdXBvbiByZWNlaXZpbmcgdGhlIGluaXRpYWwgUFRTLlxuICAgIGlmICh1bnBhcnNlZFZ0dEZyYWdzLmxlbmd0aCkge1xuICAgICAgdGhpcy51bnBhcnNlZFZ0dEZyYWdzID0gW107XG4gICAgICB1bnBhcnNlZFZ0dEZyYWdzLmZvckVhY2goZnJhZyA9PiB7XG4gICAgICAgIHRoaXMub25GcmFnTG9hZGVkKEV2ZW50cy5GUkFHX0xPQURFRCwgZnJhZyk7XG4gICAgICB9KTtcbiAgICB9XG4gIH1cbiAgZ2V0RXhpc3RpbmdUcmFjayhsYWJlbCwgbGFuZ3VhZ2UpIHtcbiAgICBjb25zdCB7XG4gICAgICBtZWRpYVxuICAgIH0gPSB0aGlzO1xuICAgIGlmIChtZWRpYSkge1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBtZWRpYS50ZXh0VHJhY2tzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGNvbnN0IHRleHRUcmFjayA9IG1lZGlhLnRleHRUcmFja3NbaV07XG4gICAgICAgIGlmIChjYW5SZXVzZVZ0dFRleHRUcmFjayh0ZXh0VHJhY2ssIHtcbiAgICAgICAgICBuYW1lOiBsYWJlbCxcbiAgICAgICAgICBsYW5nOiBsYW5ndWFnZSxcbiAgICAgICAgICBhdHRyczoge31cbiAgICAgICAgfSkpIHtcbiAgICAgICAgICByZXR1cm4gdGV4dFRyYWNrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGNyZWF0ZUNhcHRpb25zVHJhY2sodHJhY2tOYW1lKSB7XG4gICAgaWYgKHRoaXMuY29uZmlnLnJlbmRlclRleHRUcmFja3NOYXRpdmVseSkge1xuICAgICAgdGhpcy5jcmVhdGVOYXRpdmVUcmFjayh0cmFja05hbWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmNyZWF0ZU5vbk5hdGl2ZVRyYWNrKHRyYWNrTmFtZSk7XG4gICAgfVxuICB9XG4gIGNyZWF0ZU5hdGl2ZVRyYWNrKHRyYWNrTmFtZSkge1xuICAgIGlmICh0aGlzLmNhcHRpb25zVHJhY2tzW3RyYWNrTmFtZV0pIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qge1xuICAgICAgY2FwdGlvbnNQcm9wZXJ0aWVzLFxuICAgICAgY2FwdGlvbnNUcmFja3MsXG4gICAgICBtZWRpYVxuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIGxhYmVsLFxuICAgICAgbGFuZ3VhZ2VDb2RlXG4gICAgfSA9IGNhcHRpb25zUHJvcGVydGllc1t0cmFja05hbWVdO1xuICAgIC8vIEVuYWJsZSByZXVzZSBvZiBleGlzdGluZyB0ZXh0IHRyYWNrLlxuICAgIGNvbnN0IGV4aXN0aW5nVHJhY2sgPSB0aGlzLmdldEV4aXN0aW5nVHJhY2sobGFiZWwsIGxhbmd1YWdlQ29kZSk7XG4gICAgaWYgKCFleGlzdGluZ1RyYWNrKSB7XG4gICAgICBjb25zdCB0ZXh0VHJhY2sgPSB0aGlzLmNyZWF0ZVRleHRUcmFjaygnY2FwdGlvbnMnLCBsYWJlbCwgbGFuZ3VhZ2VDb2RlKTtcbiAgICAgIGlmICh0ZXh0VHJhY2spIHtcbiAgICAgICAgLy8gU2V0IGEgc3BlY2lhbCBwcm9wZXJ0eSBvbiB0aGUgdHJhY2sgc28gd2Uga25vdyBpdCdzIG1hbmFnZWQgYnkgSGxzLmpzXG4gICAgICAgIHRleHRUcmFja1t0cmFja05hbWVdID0gdHJ1ZTtcbiAgICAgICAgY2FwdGlvbnNUcmFja3NbdHJhY2tOYW1lXSA9IHRleHRUcmFjaztcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgY2FwdGlvbnNUcmFja3NbdHJhY2tOYW1lXSA9IGV4aXN0aW5nVHJhY2s7XG4gICAgICBjbGVhckN1cnJlbnRDdWVzKGNhcHRpb25zVHJhY2tzW3RyYWNrTmFtZV0pO1xuICAgICAgc2VuZEFkZFRyYWNrRXZlbnQoY2FwdGlvbnNUcmFja3NbdHJhY2tOYW1lXSwgbWVkaWEpO1xuICAgIH1cbiAgfVxuICBjcmVhdGVOb25OYXRpdmVUcmFjayh0cmFja05hbWUpIHtcbiAgICBpZiAodGhpcy5ub25OYXRpdmVDYXB0aW9uc1RyYWNrc1t0cmFja05hbWVdKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIENyZWF0ZSBhIGxpc3Qgb2YgYSBzaW5nbGUgdHJhY2sgZm9yIHRoZSBwcm92aWRlciB0byBjb25zdW1lXG4gICAgY29uc3QgdHJhY2tQcm9wZXJ0aWVzID0gdGhpcy5jYXB0aW9uc1Byb3BlcnRpZXNbdHJhY2tOYW1lXTtcbiAgICBpZiAoIXRyYWNrUHJvcGVydGllcykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBsYWJlbCA9IHRyYWNrUHJvcGVydGllcy5sYWJlbDtcbiAgICBjb25zdCB0cmFjayA9IHtcbiAgICAgIF9pZDogdHJhY2tOYW1lLFxuICAgICAgbGFiZWwsXG4gICAgICBraW5kOiAnY2FwdGlvbnMnLFxuICAgICAgZGVmYXVsdDogdHJhY2tQcm9wZXJ0aWVzLm1lZGlhID8gISF0cmFja1Byb3BlcnRpZXMubWVkaWEuZGVmYXVsdCA6IGZhbHNlLFxuICAgICAgY2xvc2VkQ2FwdGlvbnM6IHRyYWNrUHJvcGVydGllcy5tZWRpYVxuICAgIH07XG4gICAgdGhpcy5ub25OYXRpdmVDYXB0aW9uc1RyYWNrc1t0cmFja05hbWVdID0gdHJhY2s7XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuTk9OX05BVElWRV9URVhUX1RSQUNLU19GT1VORCwge1xuICAgICAgdHJhY2tzOiBbdHJhY2tdXG4gICAgfSk7XG4gIH1cbiAgY3JlYXRlVGV4dFRyYWNrKGtpbmQsIGxhYmVsLCBsYW5nKSB7XG4gICAgY29uc3QgbWVkaWEgPSB0aGlzLm1lZGlhO1xuICAgIGlmICghbWVkaWEpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcmV0dXJuIG1lZGlhLmFkZFRleHRUcmFjayhraW5kLCBsYWJlbCwgbGFuZyk7XG4gIH1cbiAgb25NZWRpYUF0dGFjaGluZyhldmVudCwgZGF0YSkge1xuICAgIHRoaXMubWVkaWEgPSBkYXRhLm1lZGlhO1xuICAgIHRoaXMuX2NsZWFuVHJhY2tzKCk7XG4gIH1cbiAgb25NZWRpYURldGFjaGluZygpIHtcbiAgICBjb25zdCB7XG4gICAgICBjYXB0aW9uc1RyYWNrc1xuICAgIH0gPSB0aGlzO1xuICAgIE9iamVjdC5rZXlzKGNhcHRpb25zVHJhY2tzKS5mb3JFYWNoKHRyYWNrTmFtZSA9PiB7XG4gICAgICBjbGVhckN1cnJlbnRDdWVzKGNhcHRpb25zVHJhY2tzW3RyYWNrTmFtZV0pO1xuICAgICAgZGVsZXRlIGNhcHRpb25zVHJhY2tzW3RyYWNrTmFtZV07XG4gICAgfSk7XG4gICAgdGhpcy5ub25OYXRpdmVDYXB0aW9uc1RyYWNrcyA9IHt9O1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKCkge1xuICAgIC8vIERldGVjdCBkaXNjb250aW51aXR5IGluIHZpZGVvIGZyYWdtZW50IChDRUEtNjA4KSBwYXJzaW5nXG4gICAgdGhpcy5sYXN0Q2MgPSAtMTtcbiAgICB0aGlzLmxhc3RTbiA9IC0xO1xuICAgIHRoaXMubGFzdFBhcnRJbmRleCA9IC0xO1xuICAgIC8vIERldGVjdCBkaXNjb250aW51aXR5IGluIHN1YnRpdGxlIG1hbmlmZXN0c1xuICAgIHRoaXMucHJldkNDID0gLTE7XG4gICAgdGhpcy52dHRDQ3MgPSBuZXdWVFRDQ3MoKTtcbiAgICAvLyBSZXNldCB0cmFja3NcbiAgICB0aGlzLl9jbGVhblRyYWNrcygpO1xuICAgIHRoaXMudHJhY2tzID0gW107XG4gICAgdGhpcy5jYXB0aW9uc1RyYWNrcyA9IHt9O1xuICAgIHRoaXMubm9uTmF0aXZlQ2FwdGlvbnNUcmFja3MgPSB7fTtcbiAgICB0aGlzLnRleHRUcmFja3MgPSBbXTtcbiAgICB0aGlzLnVucGFyc2VkVnR0RnJhZ3MgPSBbXTtcbiAgICB0aGlzLmluaXRQVFMgPSBbXTtcbiAgICBpZiAodGhpcy5jZWE2MDhQYXJzZXIxICYmIHRoaXMuY2VhNjA4UGFyc2VyMikge1xuICAgICAgdGhpcy5jZWE2MDhQYXJzZXIxLnJlc2V0KCk7XG4gICAgICB0aGlzLmNlYTYwOFBhcnNlcjIucmVzZXQoKTtcbiAgICB9XG4gIH1cbiAgX2NsZWFuVHJhY2tzKCkge1xuICAgIC8vIGNsZWFyIG91dGRhdGVkIHN1YnRpdGxlc1xuICAgIGNvbnN0IHtcbiAgICAgIG1lZGlhXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFtZWRpYSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB0ZXh0VHJhY2tzID0gbWVkaWEudGV4dFRyYWNrcztcbiAgICBpZiAodGV4dFRyYWNrcykge1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0ZXh0VHJhY2tzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGNsZWFyQ3VycmVudEN1ZXModGV4dFRyYWNrc1tpXSk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIG9uU3VidGl0bGVUcmFja3NVcGRhdGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgdHJhY2tzID0gZGF0YS5zdWJ0aXRsZVRyYWNrcyB8fCBbXTtcbiAgICBjb25zdCBoYXNJTVNDMSA9IHRyYWNrcy5zb21lKHRyYWNrID0+IHRyYWNrLnRleHRDb2RlYyA9PT0gSU1TQzFfQ09ERUMpO1xuICAgIGlmICh0aGlzLmNvbmZpZy5lbmFibGVXZWJWVFQgfHwgaGFzSU1TQzEgJiYgdGhpcy5jb25maWcuZW5hYmxlSU1TQzEpIHtcbiAgICAgIGNvbnN0IGxpc3RJc0lkZW50aWNhbCA9IHN1YnRpdGxlT3B0aW9uc0lkZW50aWNhbCh0aGlzLnRyYWNrcywgdHJhY2tzKTtcbiAgICAgIGlmIChsaXN0SXNJZGVudGljYWwpIHtcbiAgICAgICAgdGhpcy50cmFja3MgPSB0cmFja3M7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHRoaXMudGV4dFRyYWNrcyA9IFtdO1xuICAgICAgdGhpcy50cmFja3MgPSB0cmFja3M7XG4gICAgICBpZiAodGhpcy5jb25maWcucmVuZGVyVGV4dFRyYWNrc05hdGl2ZWx5KSB7XG4gICAgICAgIGNvbnN0IG1lZGlhID0gdGhpcy5tZWRpYTtcbiAgICAgICAgY29uc3QgaW5Vc2VUcmFja3MgPSBtZWRpYSA/IGZpbHRlclN1YnRpdGxlVHJhY2tzKG1lZGlhLnRleHRUcmFja3MpIDogbnVsbDtcbiAgICAgICAgdGhpcy50cmFja3MuZm9yRWFjaCgodHJhY2ssIGluZGV4KSA9PiB7XG4gICAgICAgICAgLy8gUmV1c2UgdHJhY2tzIHdpdGggdGhlIHNhbWUgbGFiZWwgYW5kIGxhbmcsIGJ1dCBkbyBub3QgcmV1c2UgNjA4LzcwOCB0cmFja3NcbiAgICAgICAgICBsZXQgdGV4dFRyYWNrO1xuICAgICAgICAgIGlmIChpblVzZVRyYWNrcykge1xuICAgICAgICAgICAgbGV0IGluVXNlVHJhY2sgPSBudWxsO1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBpblVzZVRyYWNrcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICBpZiAoaW5Vc2VUcmFja3NbaV0gJiYgY2FuUmV1c2VWdHRUZXh0VHJhY2soaW5Vc2VUcmFja3NbaV0sIHRyYWNrKSkge1xuICAgICAgICAgICAgICAgIGluVXNlVHJhY2sgPSBpblVzZVRyYWNrc1tpXTtcbiAgICAgICAgICAgICAgICBpblVzZVRyYWNrc1tpXSA9IG51bGw7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChpblVzZVRyYWNrKSB7XG4gICAgICAgICAgICAgIHRleHRUcmFjayA9IGluVXNlVHJhY2s7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmICh0ZXh0VHJhY2spIHtcbiAgICAgICAgICAgIGNsZWFyQ3VycmVudEN1ZXModGV4dFRyYWNrKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY29uc3QgdGV4dFRyYWNrS2luZCA9IGNhcHRpb25zT3JTdWJ0aXRsZXNGcm9tQ2hhcmFjdGVyaXN0aWNzKHRyYWNrKTtcbiAgICAgICAgICAgIHRleHRUcmFjayA9IHRoaXMuY3JlYXRlVGV4dFRyYWNrKHRleHRUcmFja0tpbmQsIHRyYWNrLm5hbWUsIHRyYWNrLmxhbmcpO1xuICAgICAgICAgICAgaWYgKHRleHRUcmFjaykge1xuICAgICAgICAgICAgICB0ZXh0VHJhY2subW9kZSA9ICdkaXNhYmxlZCc7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmICh0ZXh0VHJhY2spIHtcbiAgICAgICAgICAgIHRoaXMudGV4dFRyYWNrcy5wdXNoKHRleHRUcmFjayk7XG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgLy8gV2FybiB3aGVuIHZpZGVvIGVsZW1lbnQgaGFzIGNhcHRpb25zIG9yIHN1YnRpdGxlIFRleHRUcmFja3MgY2FycmllZCBvdmVyIGZyb20gYW5vdGhlciBzb3VyY2VcbiAgICAgICAgaWYgKGluVXNlVHJhY2tzICE9IG51bGwgJiYgaW5Vc2VUcmFja3MubGVuZ3RoKSB7XG4gICAgICAgICAgY29uc3QgdW51c2VkVGV4dFRyYWNrcyA9IGluVXNlVHJhY2tzLmZpbHRlcih0ID0+IHQgIT09IG51bGwpLm1hcCh0ID0+IHQubGFiZWwpO1xuICAgICAgICAgIGlmICh1bnVzZWRUZXh0VHJhY2tzLmxlbmd0aCkge1xuICAgICAgICAgICAgbG9nZ2VyLndhcm4oYE1lZGlhIGVsZW1lbnQgY29udGFpbnMgdW51c2VkIHN1YnRpdGxlIHRyYWNrczogJHt1bnVzZWRUZXh0VHJhY2tzLmpvaW4oJywgJyl9LiBSZXBsYWNlIG1lZGlhIGVsZW1lbnQgZm9yIGVhY2ggc291cmNlIHRvIGNsZWFyIFRleHRUcmFja3MgYW5kIGNhcHRpb25zIG1lbnUuYCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGVsc2UgaWYgKHRoaXMudHJhY2tzLmxlbmd0aCkge1xuICAgICAgICAvLyBDcmVhdGUgYSBsaXN0IG9mIHRyYWNrcyBmb3IgdGhlIHByb3ZpZGVyIHRvIGNvbnN1bWVcbiAgICAgICAgY29uc3QgdHJhY2tzTGlzdCA9IHRoaXMudHJhY2tzLm1hcCh0cmFjayA9PiB7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGxhYmVsOiB0cmFjay5uYW1lLFxuICAgICAgICAgICAga2luZDogdHJhY2sudHlwZS50b0xvd2VyQ2FzZSgpLFxuICAgICAgICAgICAgZGVmYXVsdDogdHJhY2suZGVmYXVsdCxcbiAgICAgICAgICAgIHN1YnRpdGxlVHJhY2s6IHRyYWNrXG4gICAgICAgICAgfTtcbiAgICAgICAgfSk7XG4gICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLk5PTl9OQVRJVkVfVEVYVF9UUkFDS1NfRk9VTkQsIHtcbiAgICAgICAgICB0cmFja3M6IHRyYWNrc0xpc3RcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIG9uTWFuaWZlc3RMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBpZiAodGhpcy5jb25maWcuZW5hYmxlQ0VBNzA4Q2FwdGlvbnMgJiYgZGF0YS5jYXB0aW9ucykge1xuICAgICAgZGF0YS5jYXB0aW9ucy5mb3JFYWNoKGNhcHRpb25zVHJhY2sgPT4ge1xuICAgICAgICBjb25zdCBpbnN0cmVhbUlkTWF0Y2ggPSAvKD86Q0N8U0VSVklDRSkoWzEtNF0pLy5leGVjKGNhcHRpb25zVHJhY2suaW5zdHJlYW1JZCk7XG4gICAgICAgIGlmICghaW5zdHJlYW1JZE1hdGNoKSB7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHRyYWNrTmFtZSA9IGB0ZXh0VHJhY2ske2luc3RyZWFtSWRNYXRjaFsxXX1gO1xuICAgICAgICBjb25zdCB0cmFja1Byb3BlcnRpZXMgPSB0aGlzLmNhcHRpb25zUHJvcGVydGllc1t0cmFja05hbWVdO1xuICAgICAgICBpZiAoIXRyYWNrUHJvcGVydGllcykge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICB0cmFja1Byb3BlcnRpZXMubGFiZWwgPSBjYXB0aW9uc1RyYWNrLm5hbWU7XG4gICAgICAgIGlmIChjYXB0aW9uc1RyYWNrLmxhbmcpIHtcbiAgICAgICAgICAvLyBvcHRpb25hbCBhdHRyaWJ1dGVcbiAgICAgICAgICB0cmFja1Byb3BlcnRpZXMubGFuZ3VhZ2VDb2RlID0gY2FwdGlvbnNUcmFjay5sYW5nO1xuICAgICAgICB9XG4gICAgICAgIHRyYWNrUHJvcGVydGllcy5tZWRpYSA9IGNhcHRpb25zVHJhY2s7XG4gICAgICB9KTtcbiAgICB9XG4gIH1cbiAgY2xvc2VkQ2FwdGlvbnNGb3JMZXZlbChmcmFnKSB7XG4gICAgY29uc3QgbGV2ZWwgPSB0aGlzLmhscy5sZXZlbHNbZnJhZy5sZXZlbF07XG4gICAgcmV0dXJuIGxldmVsID09IG51bGwgPyB2b2lkIDAgOiBsZXZlbC5hdHRyc1snQ0xPU0VELUNBUFRJT05TJ107XG4gIH1cbiAgb25GcmFnTG9hZGluZyhldmVudCwgZGF0YSkge1xuICAgIC8vIGlmIHRoaXMgZnJhZyBpc24ndCBjb250aWd1b3VzLCBjbGVhciB0aGUgcGFyc2VyIHNvIGN1ZXMgd2l0aCBiYWQgc3RhcnQvZW5kIHRpbWVzIGFyZW4ndCBhZGRlZCB0byB0aGUgdGV4dFRyYWNrXG4gICAgaWYgKHRoaXMuZW5hYmxlZCAmJiBkYXRhLmZyYWcudHlwZSA9PT0gUGxheWxpc3RMZXZlbFR5cGUuTUFJTikge1xuICAgICAgdmFyIF9kYXRhJHBhcnQkaW5kZXgsIF9kYXRhJHBhcnQ7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGNlYTYwOFBhcnNlcjEsXG4gICAgICAgIGNlYTYwOFBhcnNlcjIsXG4gICAgICAgIGxhc3RTblxuICAgICAgfSA9IHRoaXM7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGNjLFxuICAgICAgICBzblxuICAgICAgfSA9IGRhdGEuZnJhZztcbiAgICAgIGNvbnN0IHBhcnRJbmRleCA9IChfZGF0YSRwYXJ0JGluZGV4ID0gKF9kYXRhJHBhcnQgPSBkYXRhLnBhcnQpID09IG51bGwgPyB2b2lkIDAgOiBfZGF0YSRwYXJ0LmluZGV4KSAhPSBudWxsID8gX2RhdGEkcGFydCRpbmRleCA6IC0xO1xuICAgICAgaWYgKGNlYTYwOFBhcnNlcjEgJiYgY2VhNjA4UGFyc2VyMikge1xuICAgICAgICBpZiAoc24gIT09IGxhc3RTbiArIDEgfHwgc24gPT09IGxhc3RTbiAmJiBwYXJ0SW5kZXggIT09IHRoaXMubGFzdFBhcnRJbmRleCArIDEgfHwgY2MgIT09IHRoaXMubGFzdENjKSB7XG4gICAgICAgICAgY2VhNjA4UGFyc2VyMS5yZXNldCgpO1xuICAgICAgICAgIGNlYTYwOFBhcnNlcjIucmVzZXQoKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdGhpcy5sYXN0Q2MgPSBjYztcbiAgICAgIHRoaXMubGFzdFNuID0gc247XG4gICAgICB0aGlzLmxhc3RQYXJ0SW5kZXggPSBwYXJ0SW5kZXg7XG4gICAgfVxuICB9XG4gIG9uRnJhZ0xvYWRlZChldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBwYXlsb2FkXG4gICAgfSA9IGRhdGE7XG4gICAgaWYgKGZyYWcudHlwZSA9PT0gUGxheWxpc3RMZXZlbFR5cGUuU1VCVElUTEUpIHtcbiAgICAgIC8vIElmIGZyYWdtZW50IGlzIHN1YnRpdGxlIHR5cGUsIHBhcnNlIGFzIFdlYlZUVC5cbiAgICAgIGlmIChwYXlsb2FkLmJ5dGVMZW5ndGgpIHtcbiAgICAgICAgY29uc3QgZGVjcnlwdERhdGEgPSBmcmFnLmRlY3J5cHRkYXRhO1xuICAgICAgICAvLyBmcmFnbWVudCBhZnRlciBkZWNyeXB0aW9uIGhhcyBhIHN0YXRzIG9iamVjdFxuICAgICAgICBjb25zdCBkZWNyeXB0ZWQgPSAoJ3N0YXRzJyBpbiBkYXRhKTtcbiAgICAgICAgLy8gSWYgdGhlIHN1YnRpdGxlcyBhcmUgbm90IGVuY3J5cHRlZCwgcGFyc2UgVlRUcyBub3cuIE90aGVyd2lzZSwgd2UgbmVlZCB0byB3YWl0LlxuICAgICAgICBpZiAoZGVjcnlwdERhdGEgPT0gbnVsbCB8fCAhZGVjcnlwdERhdGEuZW5jcnlwdGVkIHx8IGRlY3J5cHRlZCkge1xuICAgICAgICAgIGNvbnN0IHRyYWNrUGxheWxpc3RNZWRpYSA9IHRoaXMudHJhY2tzW2ZyYWcubGV2ZWxdO1xuICAgICAgICAgIGNvbnN0IHZ0dENDcyA9IHRoaXMudnR0Q0NzO1xuICAgICAgICAgIGlmICghdnR0Q0NzW2ZyYWcuY2NdKSB7XG4gICAgICAgICAgICB2dHRDQ3NbZnJhZy5jY10gPSB7XG4gICAgICAgICAgICAgIHN0YXJ0OiBmcmFnLnN0YXJ0LFxuICAgICAgICAgICAgICBwcmV2Q0M6IHRoaXMucHJldkNDLFxuICAgICAgICAgICAgICBuZXc6IHRydWVcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICB0aGlzLnByZXZDQyA9IGZyYWcuY2M7XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmICh0cmFja1BsYXlsaXN0TWVkaWEgJiYgdHJhY2tQbGF5bGlzdE1lZGlhLnRleHRDb2RlYyA9PT0gSU1TQzFfQ09ERUMpIHtcbiAgICAgICAgICAgIHRoaXMuX3BhcnNlSU1TQzEoZnJhZywgcGF5bG9hZCk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuX3BhcnNlVlRUcyhkYXRhKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIEluIGNhc2UgdGhlcmUgaXMgbm8gcGF5bG9hZCwgZmluaXNoIHVuc3VjY2Vzc2Z1bGx5LlxuICAgICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5TVUJUSVRMRV9GUkFHX1BST0NFU1NFRCwge1xuICAgICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgZXJyb3I6IG5ldyBFcnJvcignRW1wdHkgc3VidGl0bGUgcGF5bG9hZCcpXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBfcGFyc2VJTVNDMShmcmFnLCBwYXlsb2FkKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgcGFyc2VJTVNDMShwYXlsb2FkLCB0aGlzLmluaXRQVFNbZnJhZy5jY10sIGN1ZXMgPT4ge1xuICAgICAgdGhpcy5fYXBwZW5kQ3VlcyhjdWVzLCBmcmFnLmxldmVsKTtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5TVUJUSVRMRV9GUkFHX1BST0NFU1NFRCwge1xuICAgICAgICBzdWNjZXNzOiB0cnVlLFxuICAgICAgICBmcmFnOiBmcmFnXG4gICAgICB9KTtcbiAgICB9LCBlcnJvciA9PiB7XG4gICAgICBsb2dnZXIubG9nKGBGYWlsZWQgdG8gcGFyc2UgSU1TQzE6ICR7ZXJyb3J9YCk7XG4gICAgICBobHMudHJpZ2dlcihFdmVudHMuU1VCVElUTEVfRlJBR19QUk9DRVNTRUQsIHtcbiAgICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICAgIGZyYWc6IGZyYWcsXG4gICAgICAgIGVycm9yXG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuICBfcGFyc2VWVFRzKGRhdGEpIHtcbiAgICB2YXIgX2ZyYWckaW5pdFNlZ21lbnQ7XG4gICAgY29uc3Qge1xuICAgICAgZnJhZyxcbiAgICAgIHBheWxvYWRcbiAgICB9ID0gZGF0YTtcbiAgICAvLyBXZSBuZWVkIGFuIGluaXRpYWwgc3luY2hyb25pc2F0aW9uIFBUUy4gU3RvcmUgZnJhZ21lbnRzIGFzIGxvbmcgYXMgbm9uZSBoYXMgYXJyaXZlZFxuICAgIGNvbnN0IHtcbiAgICAgIGluaXRQVFMsXG4gICAgICB1bnBhcnNlZFZ0dEZyYWdzXG4gICAgfSA9IHRoaXM7XG4gICAgY29uc3QgbWF4QXZDQyA9IGluaXRQVFMubGVuZ3RoIC0gMTtcbiAgICBpZiAoIWluaXRQVFNbZnJhZy5jY10gJiYgbWF4QXZDQyA9PT0gLTEpIHtcbiAgICAgIHVucGFyc2VkVnR0RnJhZ3MucHVzaChkYXRhKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgLy8gUGFyc2UgdGhlIFdlYlZUVCBmaWxlIGNvbnRlbnRzLlxuICAgIGNvbnN0IHBheWxvYWRXZWJWVFQgPSAoX2ZyYWckaW5pdFNlZ21lbnQgPSBmcmFnLmluaXRTZWdtZW50KSAhPSBudWxsICYmIF9mcmFnJGluaXRTZWdtZW50LmRhdGEgPyBhcHBlbmRVaW50OEFycmF5KGZyYWcuaW5pdFNlZ21lbnQuZGF0YSwgbmV3IFVpbnQ4QXJyYXkocGF5bG9hZCkpIDogcGF5bG9hZDtcbiAgICBwYXJzZVdlYlZUVChwYXlsb2FkV2ViVlRULCB0aGlzLmluaXRQVFNbZnJhZy5jY10sIHRoaXMudnR0Q0NzLCBmcmFnLmNjLCBmcmFnLnN0YXJ0LCBjdWVzID0+IHtcbiAgICAgIHRoaXMuX2FwcGVuZEN1ZXMoY3VlcywgZnJhZy5sZXZlbCk7XG4gICAgICBobHMudHJpZ2dlcihFdmVudHMuU1VCVElUTEVfRlJBR19QUk9DRVNTRUQsIHtcbiAgICAgICAgc3VjY2VzczogdHJ1ZSxcbiAgICAgICAgZnJhZzogZnJhZ1xuICAgICAgfSk7XG4gICAgfSwgZXJyb3IgPT4ge1xuICAgICAgY29uc3QgbWlzc2luZ0luaXRQVFMgPSBlcnJvci5tZXNzYWdlID09PSAnTWlzc2luZyBpbml0UFRTIGZvciBWVFQgTVBFR1RTJztcbiAgICAgIGlmIChtaXNzaW5nSW5pdFBUUykge1xuICAgICAgICB1bnBhcnNlZFZ0dEZyYWdzLnB1c2goZGF0YSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLl9mYWxsYmFja1RvSU1TQzEoZnJhZywgcGF5bG9hZCk7XG4gICAgICB9XG4gICAgICAvLyBTb21ldGhpbmcgd2VudCB3cm9uZyB3aGlsZSBwYXJzaW5nLiBUcmlnZ2VyIGV2ZW50IHdpdGggc3VjY2VzcyBmYWxzZS5cbiAgICAgIGxvZ2dlci5sb2coYEZhaWxlZCB0byBwYXJzZSBWVFQgY3VlOiAke2Vycm9yfWApO1xuICAgICAgaWYgKG1pc3NpbmdJbml0UFRTICYmIG1heEF2Q0MgPiBmcmFnLmNjKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5TVUJUSVRMRV9GUkFHX1BST0NFU1NFRCwge1xuICAgICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgICAgZnJhZzogZnJhZyxcbiAgICAgICAgZXJyb3JcbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG4gIF9mYWxsYmFja1RvSU1TQzEoZnJhZywgcGF5bG9hZCkge1xuICAgIC8vIElmIHRleHRDb2RlYyBpcyB1bmtub3duLCB0cnkgcGFyc2luZyBhcyBJTVNDMS4gU2V0IHRleHRDb2RlYyBiYXNlZCBvbiB0aGUgcmVzdWx0XG4gICAgY29uc3QgdHJhY2tQbGF5bGlzdE1lZGlhID0gdGhpcy50cmFja3NbZnJhZy5sZXZlbF07XG4gICAgaWYgKCF0cmFja1BsYXlsaXN0TWVkaWEudGV4dENvZGVjKSB7XG4gICAgICBwYXJzZUlNU0MxKHBheWxvYWQsIHRoaXMuaW5pdFBUU1tmcmFnLmNjXSwgKCkgPT4ge1xuICAgICAgICB0cmFja1BsYXlsaXN0TWVkaWEudGV4dENvZGVjID0gSU1TQzFfQ09ERUM7XG4gICAgICAgIHRoaXMuX3BhcnNlSU1TQzEoZnJhZywgcGF5bG9hZCk7XG4gICAgICB9LCAoKSA9PiB7XG4gICAgICAgIHRyYWNrUGxheWxpc3RNZWRpYS50ZXh0Q29kZWMgPSAnd3Z0dCc7XG4gICAgICB9KTtcbiAgICB9XG4gIH1cbiAgX2FwcGVuZEN1ZXMoY3VlcywgZnJhZ0xldmVsKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgaWYgKHRoaXMuY29uZmlnLnJlbmRlclRleHRUcmFja3NOYXRpdmVseSkge1xuICAgICAgY29uc3QgdGV4dFRyYWNrID0gdGhpcy50ZXh0VHJhY2tzW2ZyYWdMZXZlbF07XG4gICAgICAvLyBXZWJWVFRQYXJzZXIucGFyc2UgaXMgYW4gYXN5bmMgbWV0aG9kIGFuZCBpZiB0aGUgY3VycmVudGx5IHNlbGVjdGVkIHRleHQgdHJhY2sgbW9kZSBpcyBzZXQgdG8gXCJkaXNhYmxlZFwiXG4gICAgICAvLyBiZWZvcmUgcGFyc2luZyBpcyBkb25lIHRoZW4gZG9uJ3QgdHJ5IHRvIGFjY2VzcyBjdXJyZW50VHJhY2suY3Vlcy5nZXRDdWVCeUlkIGFzIGN1ZXMgd2lsbCBiZSBudWxsXG4gICAgICAvLyBhbmQgdHJ5aW5nIHRvIGFjY2VzcyBnZXRDdWVCeUlkIG1ldGhvZCBvZiBjdWVzIHdpbGwgdGhyb3cgYW4gZXhjZXB0aW9uXG4gICAgICAvLyBCZWNhdXNlIHdlIGNoZWNrIGlmIHRoZSBtb2RlIGlzIGRpc2FibGVkLCB3ZSBjYW4gZm9yY2UgY2hlY2sgYGN1ZXNgIGJlbG93LiBUaGV5IGNhbid0IGJlIG51bGwuXG4gICAgICBpZiAoIXRleHRUcmFjayB8fCB0ZXh0VHJhY2subW9kZSA9PT0gJ2Rpc2FibGVkJykge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjdWVzLmZvckVhY2goY3VlID0+IGFkZEN1ZVRvVHJhY2sodGV4dFRyYWNrLCBjdWUpKTtcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgY3VycmVudFRyYWNrID0gdGhpcy50cmFja3NbZnJhZ0xldmVsXTtcbiAgICAgIGlmICghY3VycmVudFRyYWNrKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IHRyYWNrID0gY3VycmVudFRyYWNrLmRlZmF1bHQgPyAnZGVmYXVsdCcgOiAnc3VidGl0bGVzJyArIGZyYWdMZXZlbDtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5DVUVTX1BBUlNFRCwge1xuICAgICAgICB0eXBlOiAnc3VidGl0bGVzJyxcbiAgICAgICAgY3VlcyxcbiAgICAgICAgdHJhY2tcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICBvbkZyYWdEZWNyeXB0ZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnXG4gICAgfSA9IGRhdGE7XG4gICAgaWYgKGZyYWcudHlwZSA9PT0gUGxheWxpc3RMZXZlbFR5cGUuU1VCVElUTEUpIHtcbiAgICAgIHRoaXMub25GcmFnTG9hZGVkKEV2ZW50cy5GUkFHX0xPQURFRCwgZGF0YSk7XG4gICAgfVxuICB9XG4gIG9uU3VidGl0bGVUcmFja3NDbGVhcmVkKCkge1xuICAgIHRoaXMudHJhY2tzID0gW107XG4gICAgdGhpcy5jYXB0aW9uc1RyYWNrcyA9IHt9O1xuICB9XG4gIG9uRnJhZ1BhcnNpbmdVc2VyZGF0YShldmVudCwgZGF0YSkge1xuICAgIHRoaXMuaW5pdENlYTYwOFBhcnNlcnMoKTtcbiAgICBjb25zdCB7XG4gICAgICBjZWE2MDhQYXJzZXIxLFxuICAgICAgY2VhNjA4UGFyc2VyMlxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghdGhpcy5lbmFibGVkIHx8ICFjZWE2MDhQYXJzZXIxIHx8ICFjZWE2MDhQYXJzZXIyKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBzYW1wbGVzXG4gICAgfSA9IGRhdGE7XG4gICAgaWYgKGZyYWcudHlwZSA9PT0gUGxheWxpc3RMZXZlbFR5cGUuTUFJTiAmJiB0aGlzLmNsb3NlZENhcHRpb25zRm9yTGV2ZWwoZnJhZykgPT09ICdOT05FJykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBJZiB0aGUgZXZlbnQgY29udGFpbnMgY2FwdGlvbnMgKGZvdW5kIGluIHRoZSBieXRlcyBwcm9wZXJ0eSksIHB1c2ggYWxsIGJ5dGVzIGludG8gdGhlIHBhcnNlciBpbW1lZGlhdGVseVxuICAgIC8vIEl0IHdpbGwgY3JlYXRlIHRoZSBwcm9wZXIgdGltZXN0YW1wcyBiYXNlZCBvbiB0aGUgUFRTIHZhbHVlXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzYW1wbGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBjY0J5dGVzID0gc2FtcGxlc1tpXS5ieXRlcztcbiAgICAgIGlmIChjY0J5dGVzKSB7XG4gICAgICAgIGNvbnN0IGNjZGF0YXMgPSB0aGlzLmV4dHJhY3RDZWE2MDhEYXRhKGNjQnl0ZXMpO1xuICAgICAgICBjZWE2MDhQYXJzZXIxLmFkZERhdGEoc2FtcGxlc1tpXS5wdHMsIGNjZGF0YXNbMF0pO1xuICAgICAgICBjZWE2MDhQYXJzZXIyLmFkZERhdGEoc2FtcGxlc1tpXS5wdHMsIGNjZGF0YXNbMV0pO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBvbkJ1ZmZlckZsdXNoaW5nKGV2ZW50LCB7XG4gICAgc3RhcnRPZmZzZXQsXG4gICAgZW5kT2Zmc2V0LFxuICAgIGVuZE9mZnNldFN1YnRpdGxlcyxcbiAgICB0eXBlXG4gIH0pIHtcbiAgICBjb25zdCB7XG4gICAgICBtZWRpYVxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghbWVkaWEgfHwgbWVkaWEuY3VycmVudFRpbWUgPCBlbmRPZmZzZXQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gQ2xlYXIgNjA4IGNhcHRpb24gY3VlcyBmcm9tIHRoZSBjYXB0aW9ucyBUZXh0VHJhY2tzIHdoZW4gdGhlIHZpZGVvIGJhY2sgYnVmZmVyIGlzIGZsdXNoZWRcbiAgICAvLyBGb3J3YXJkIGN1ZXMgYXJlIG5ldmVyIHJlbW92ZWQgYmVjYXVzZSB3ZSBjYW4gbG9vc2Ugc3RyZWFtZWQgNjA4IGNvbnRlbnQgZnJvbSByZWNlbnQgZnJhZ21lbnRzXG4gICAgaWYgKCF0eXBlIHx8IHR5cGUgPT09ICd2aWRlbycpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgY2FwdGlvbnNUcmFja3NcbiAgICAgIH0gPSB0aGlzO1xuICAgICAgT2JqZWN0LmtleXMoY2FwdGlvbnNUcmFja3MpLmZvckVhY2godHJhY2tOYW1lID0+IHJlbW92ZUN1ZXNJblJhbmdlKGNhcHRpb25zVHJhY2tzW3RyYWNrTmFtZV0sIHN0YXJ0T2Zmc2V0LCBlbmRPZmZzZXQpKTtcbiAgICB9XG4gICAgaWYgKHRoaXMuY29uZmlnLnJlbmRlclRleHRUcmFja3NOYXRpdmVseSkge1xuICAgICAgLy8gQ2xlYXIgVlRUL0lNU0MxIHN1YnRpdGxlIGN1ZXMgZnJvbSB0aGUgc3VidGl0bGUgVGV4dFRyYWNrcyB3aGVuIHRoZSBiYWNrIGJ1ZmZlciBpcyBmbHVzaGVkXG4gICAgICBpZiAoc3RhcnRPZmZzZXQgPT09IDAgJiYgZW5kT2Zmc2V0U3VidGl0bGVzICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgY29uc3Qge1xuICAgICAgICAgIHRleHRUcmFja3NcbiAgICAgICAgfSA9IHRoaXM7XG4gICAgICAgIE9iamVjdC5rZXlzKHRleHRUcmFja3MpLmZvckVhY2godHJhY2tOYW1lID0+IHJlbW92ZUN1ZXNJblJhbmdlKHRleHRUcmFja3NbdHJhY2tOYW1lXSwgc3RhcnRPZmZzZXQsIGVuZE9mZnNldFN1YnRpdGxlcykpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBleHRyYWN0Q2VhNjA4RGF0YShieXRlQXJyYXkpIHtcbiAgICBjb25zdCBhY3R1YWxDQ0J5dGVzID0gW1tdLCBbXV07XG4gICAgY29uc3QgY291bnQgPSBieXRlQXJyYXlbMF0gJiAweDFmO1xuICAgIGxldCBwb3NpdGlvbiA9IDI7XG4gICAgZm9yIChsZXQgaiA9IDA7IGogPCBjb3VudDsgaisrKSB7XG4gICAgICBjb25zdCB0bXBCeXRlID0gYnl0ZUFycmF5W3Bvc2l0aW9uKytdO1xuICAgICAgY29uc3QgY2NieXRlMSA9IDB4N2YgJiBieXRlQXJyYXlbcG9zaXRpb24rK107XG4gICAgICBjb25zdCBjY2J5dGUyID0gMHg3ZiAmIGJ5dGVBcnJheVtwb3NpdGlvbisrXTtcbiAgICAgIGlmIChjY2J5dGUxID09PSAwICYmIGNjYnl0ZTIgPT09IDApIHtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG4gICAgICBjb25zdCBjY1ZhbGlkID0gKDB4MDQgJiB0bXBCeXRlKSAhPT0gMDsgLy8gU3VwcG9ydCBhbGwgZm91ciBjaGFubmVsc1xuICAgICAgaWYgKGNjVmFsaWQpIHtcbiAgICAgICAgY29uc3QgY2NUeXBlID0gMHgwMyAmIHRtcEJ5dGU7XG4gICAgICAgIGlmICgweDAwIC8qIENFQTYwOCBmaWVsZDEqLyA9PT0gY2NUeXBlIHx8IDB4MDEgLyogQ0VBNjA4IGZpZWxkMiovID09PSBjY1R5cGUpIHtcbiAgICAgICAgICAvLyBFeGNsdWRlIENFQTcwOCBDQyBkYXRhLlxuICAgICAgICAgIGFjdHVhbENDQnl0ZXNbY2NUeXBlXS5wdXNoKGNjYnl0ZTEpO1xuICAgICAgICAgIGFjdHVhbENDQnl0ZXNbY2NUeXBlXS5wdXNoKGNjYnl0ZTIpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBhY3R1YWxDQ0J5dGVzO1xuICB9XG59XG5mdW5jdGlvbiBjYXB0aW9uc09yU3VidGl0bGVzRnJvbUNoYXJhY3RlcmlzdGljcyh0cmFjaykge1xuICBpZiAodHJhY2suY2hhcmFjdGVyaXN0aWNzKSB7XG4gICAgaWYgKC90cmFuc2NyaWJlcy1zcG9rZW4tZGlhbG9nL2dpLnRlc3QodHJhY2suY2hhcmFjdGVyaXN0aWNzKSAmJiAvZGVzY3JpYmVzLW11c2ljLWFuZC1zb3VuZC9naS50ZXN0KHRyYWNrLmNoYXJhY3RlcmlzdGljcykpIHtcbiAgICAgIHJldHVybiAnY2FwdGlvbnMnO1xuICAgIH1cbiAgfVxuICByZXR1cm4gJ3N1YnRpdGxlcyc7XG59XG5mdW5jdGlvbiBjYW5SZXVzZVZ0dFRleHRUcmFjayhpblVzZVRyYWNrLCBtYW5pZmVzdFRyYWNrKSB7XG4gIHJldHVybiAhIWluVXNlVHJhY2sgJiYgaW5Vc2VUcmFjay5raW5kID09PSBjYXB0aW9uc09yU3VidGl0bGVzRnJvbUNoYXJhY3RlcmlzdGljcyhtYW5pZmVzdFRyYWNrKSAmJiBzdWJ0aXRsZVRyYWNrTWF0Y2hlc1RleHRUcmFjayhtYW5pZmVzdFRyYWNrLCBpblVzZVRyYWNrKTtcbn1cbmZ1bmN0aW9uIGludGVyc2VjdGlvbih4MSwgeDIsIHkxLCB5Mikge1xuICByZXR1cm4gTWF0aC5taW4oeDIsIHkyKSAtIE1hdGgubWF4KHgxLCB5MSk7XG59XG5mdW5jdGlvbiBuZXdWVFRDQ3MoKSB7XG4gIHJldHVybiB7XG4gICAgY2NPZmZzZXQ6IDAsXG4gICAgcHJlc2VudGF0aW9uT2Zmc2V0OiAwLFxuICAgIDA6IHtcbiAgICAgIHN0YXJ0OiAwLFxuICAgICAgcHJldkNDOiAtMSxcbiAgICAgIG5ldzogdHJ1ZVxuICAgIH1cbiAgfTtcbn1cblxuY2xhc3MgQ2FwTGV2ZWxDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5hdXRvTGV2ZWxDYXBwaW5nID0gdm9pZCAwO1xuICAgIHRoaXMuZmlyc3RMZXZlbCA9IHZvaWQgMDtcbiAgICB0aGlzLm1lZGlhID0gdm9pZCAwO1xuICAgIHRoaXMucmVzdHJpY3RlZExldmVscyA9IHZvaWQgMDtcbiAgICB0aGlzLnRpbWVyID0gdm9pZCAwO1xuICAgIHRoaXMuY2xpZW50UmVjdCA9IHZvaWQgMDtcbiAgICB0aGlzLnN0cmVhbUNvbnRyb2xsZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5obHMgPSBobHM7XG4gICAgdGhpcy5hdXRvTGV2ZWxDYXBwaW5nID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuICAgIHRoaXMuZmlyc3RMZXZlbCA9IC0xO1xuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIHRoaXMucmVzdHJpY3RlZExldmVscyA9IFtdO1xuICAgIHRoaXMudGltZXIgPSB1bmRlZmluZWQ7XG4gICAgdGhpcy5jbGllbnRSZWN0ID0gbnVsbDtcbiAgICB0aGlzLnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gIH1cbiAgc2V0U3RyZWFtQ29udHJvbGxlcihzdHJlYW1Db250cm9sbGVyKSB7XG4gICAgdGhpcy5zdHJlYW1Db250cm9sbGVyID0gc3RyZWFtQ29udHJvbGxlcjtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIGlmICh0aGlzLmhscykge1xuICAgICAgdGhpcy51bnJlZ2lzdGVyTGlzdGVuZXIoKTtcbiAgICB9XG4gICAgaWYgKHRoaXMudGltZXIpIHtcbiAgICAgIHRoaXMuc3RvcENhcHBpbmcoKTtcbiAgICB9XG4gICAgdGhpcy5tZWRpYSA9IG51bGw7XG4gICAgdGhpcy5jbGllbnRSZWN0ID0gbnVsbDtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdGhpcy5obHMgPSB0aGlzLnN0cmVhbUNvbnRyb2xsZXIgPSBudWxsO1xuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vbihFdmVudHMuRlBTX0RST1BfTEVWRUxfQ0FQUElORywgdGhpcy5vbkZwc0Ryb3BMZXZlbENhcHBpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfQVRUQUNISU5HLCB0aGlzLm9uTWVkaWFBdHRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTEVWRUxTX1VQREFURUQsIHRoaXMub25MZXZlbHNVcGRhdGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9DT0RFQ1MsIHRoaXMub25CdWZmZXJDb2RlY3MsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfREVUQUNISU5HLCB0aGlzLm9uTWVkaWFEZXRhY2hpbmcsIHRoaXMpO1xuICB9XG4gIHVucmVnaXN0ZXJMaXN0ZW5lcigpIHtcbiAgICBjb25zdCB7XG4gICAgICBobHNcbiAgICB9ID0gdGhpcztcbiAgICBobHMub2ZmKEV2ZW50cy5GUFNfRFJPUF9MRVZFTF9DQVBQSU5HLCB0aGlzLm9uRnBzRHJvcExldmVsQ2FwcGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNISU5HLCB0aGlzLm9uTWVkaWFBdHRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX1BBUlNFRCwgdGhpcy5vbk1hbmlmZXN0UGFyc2VkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5MRVZFTFNfVVBEQVRFRCwgdGhpcy5vbkxldmVsc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9DT0RFQ1MsIHRoaXMub25CdWZmZXJDb2RlY3MsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1FRElBX0RFVEFDSElORywgdGhpcy5vbk1lZGlhRGV0YWNoaW5nLCB0aGlzKTtcbiAgfVxuICBvbkZwc0Ryb3BMZXZlbENhcHBpbmcoZXZlbnQsIGRhdGEpIHtcbiAgICAvLyBEb24ndCBhZGQgYSByZXN0cmljdGVkIGxldmVsIG1vcmUgdGhhbiBvbmNlXG4gICAgY29uc3QgbGV2ZWwgPSB0aGlzLmhscy5sZXZlbHNbZGF0YS5kcm9wcGVkTGV2ZWxdO1xuICAgIGlmICh0aGlzLmlzTGV2ZWxBbGxvd2VkKGxldmVsKSkge1xuICAgICAgdGhpcy5yZXN0cmljdGVkTGV2ZWxzLnB1c2goe1xuICAgICAgICBiaXRyYXRlOiBsZXZlbC5iaXRyYXRlLFxuICAgICAgICBoZWlnaHQ6IGxldmVsLmhlaWdodCxcbiAgICAgICAgd2lkdGg6IGxldmVsLndpZHRoXG4gICAgICB9KTtcbiAgICB9XG4gIH1cbiAgb25NZWRpYUF0dGFjaGluZyhldmVudCwgZGF0YSkge1xuICAgIHRoaXMubWVkaWEgPSBkYXRhLm1lZGlhIGluc3RhbmNlb2YgSFRNTFZpZGVvRWxlbWVudCA/IGRhdGEubWVkaWEgOiBudWxsO1xuICAgIHRoaXMuY2xpZW50UmVjdCA9IG51bGw7XG4gICAgaWYgKHRoaXMudGltZXIgJiYgdGhpcy5obHMubGV2ZWxzLmxlbmd0aCkge1xuICAgICAgdGhpcy5kZXRlY3RQbGF5ZXJTaXplKCk7XG4gICAgfVxuICB9XG4gIG9uTWFuaWZlc3RQYXJzZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCBobHMgPSB0aGlzLmhscztcbiAgICB0aGlzLnJlc3RyaWN0ZWRMZXZlbHMgPSBbXTtcbiAgICB0aGlzLmZpcnN0TGV2ZWwgPSBkYXRhLmZpcnN0TGV2ZWw7XG4gICAgaWYgKGhscy5jb25maWcuY2FwTGV2ZWxUb1BsYXllclNpemUgJiYgZGF0YS52aWRlbykge1xuICAgICAgLy8gU3RhcnQgY2FwcGluZyBpbW1lZGlhdGVseSBpZiB0aGUgbWFuaWZlc3QgaGFzIHNpZ25hbGVkIHZpZGVvIGNvZGVjc1xuICAgICAgdGhpcy5zdGFydENhcHBpbmcoKTtcbiAgICB9XG4gIH1cbiAgb25MZXZlbHNVcGRhdGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgaWYgKHRoaXMudGltZXIgJiYgaXNGaW5pdGVOdW1iZXIodGhpcy5hdXRvTGV2ZWxDYXBwaW5nKSkge1xuICAgICAgdGhpcy5kZXRlY3RQbGF5ZXJTaXplKCk7XG4gICAgfVxuICB9XG5cbiAgLy8gT25seSBhY3RpdmF0ZSBjYXBwaW5nIHdoZW4gcGxheWluZyBhIHZpZGVvIHN0cmVhbTsgb3RoZXJ3aXNlLCBtdWx0aS1iaXRyYXRlIGF1ZGlvLW9ubHkgc3RyZWFtcyB3aWxsIGJlIHJlc3RyaWN0ZWRcbiAgLy8gdG8gdGhlIGZpcnN0IGxldmVsXG4gIG9uQnVmZmVyQ29kZWNzKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgaWYgKGhscy5jb25maWcuY2FwTGV2ZWxUb1BsYXllclNpemUgJiYgZGF0YS52aWRlbykge1xuICAgICAgLy8gSWYgdGhlIG1hbmlmZXN0IGRpZCBub3Qgc2lnbmFsIGEgdmlkZW8gY29kZWMgY2FwcGluZyBoYXMgYmVlbiBkZWZlcnJlZCB1bnRpbCB3ZSdyZSBjZXJ0YWluIHZpZGVvIGlzIHByZXNlbnRcbiAgICAgIHRoaXMuc3RhcnRDYXBwaW5nKCk7XG4gICAgfVxuICB9XG4gIG9uTWVkaWFEZXRhY2hpbmcoKSB7XG4gICAgdGhpcy5zdG9wQ2FwcGluZygpO1xuICB9XG4gIGRldGVjdFBsYXllclNpemUoKSB7XG4gICAgaWYgKHRoaXMubWVkaWEpIHtcbiAgICAgIGlmICh0aGlzLm1lZGlhSGVpZ2h0IDw9IDAgfHwgdGhpcy5tZWRpYVdpZHRoIDw9IDApIHtcbiAgICAgICAgdGhpcy5jbGllbnRSZWN0ID0gbnVsbDtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3QgbGV2ZWxzID0gdGhpcy5obHMubGV2ZWxzO1xuICAgICAgaWYgKGxldmVscy5sZW5ndGgpIHtcbiAgICAgICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgICAgIGNvbnN0IG1heExldmVsID0gdGhpcy5nZXRNYXhMZXZlbChsZXZlbHMubGVuZ3RoIC0gMSk7XG4gICAgICAgIGlmIChtYXhMZXZlbCAhPT0gdGhpcy5hdXRvTGV2ZWxDYXBwaW5nKSB7XG4gICAgICAgICAgbG9nZ2VyLmxvZyhgU2V0dGluZyBhdXRvTGV2ZWxDYXBwaW5nIHRvICR7bWF4TGV2ZWx9OiAke2xldmVsc1ttYXhMZXZlbF0uaGVpZ2h0fXBAJHtsZXZlbHNbbWF4TGV2ZWxdLmJpdHJhdGV9IGZvciBtZWRpYSAke3RoaXMubWVkaWFXaWR0aH14JHt0aGlzLm1lZGlhSGVpZ2h0fWApO1xuICAgICAgICB9XG4gICAgICAgIGhscy5hdXRvTGV2ZWxDYXBwaW5nID0gbWF4TGV2ZWw7XG4gICAgICAgIGlmIChobHMuYXV0b0xldmVsQ2FwcGluZyA+IHRoaXMuYXV0b0xldmVsQ2FwcGluZyAmJiB0aGlzLnN0cmVhbUNvbnRyb2xsZXIpIHtcbiAgICAgICAgICAvLyBpZiBhdXRvIGxldmVsIGNhcHBpbmcgaGFzIGEgaGlnaGVyIHZhbHVlIGZvciB0aGUgcHJldmlvdXMgb25lLCBmbHVzaCB0aGUgYnVmZmVyIHVzaW5nIG5leHRMZXZlbFN3aXRjaFxuICAgICAgICAgIC8vIHVzdWFsbHkgaGFwcGVuIHdoZW4gdGhlIHVzZXIgZ28gdG8gdGhlIGZ1bGxzY3JlZW4gbW9kZS5cbiAgICAgICAgICB0aGlzLnN0cmVhbUNvbnRyb2xsZXIubmV4dExldmVsU3dpdGNoKCk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5hdXRvTGV2ZWxDYXBwaW5nID0gaGxzLmF1dG9MZXZlbENhcHBpbmc7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLypcbiAgICogcmV0dXJucyBsZXZlbCBzaG91bGQgYmUgdGhlIG9uZSB3aXRoIHRoZSBkaW1lbnNpb25zIGVxdWFsIG9yIGdyZWF0ZXIgdGhhbiB0aGUgbWVkaWEgKHBsYXllcikgZGltZW5zaW9ucyAoc28gdGhlIHZpZGVvIHdpbGwgYmUgZG93bnNjYWxlZClcbiAgICovXG4gIGdldE1heExldmVsKGNhcExldmVsSW5kZXgpIHtcbiAgICBjb25zdCBsZXZlbHMgPSB0aGlzLmhscy5sZXZlbHM7XG4gICAgaWYgKCFsZXZlbHMubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuICAgIGNvbnN0IHZhbGlkTGV2ZWxzID0gbGV2ZWxzLmZpbHRlcigobGV2ZWwsIGluZGV4KSA9PiB0aGlzLmlzTGV2ZWxBbGxvd2VkKGxldmVsKSAmJiBpbmRleCA8PSBjYXBMZXZlbEluZGV4KTtcbiAgICB0aGlzLmNsaWVudFJlY3QgPSBudWxsO1xuICAgIHJldHVybiBDYXBMZXZlbENvbnRyb2xsZXIuZ2V0TWF4TGV2ZWxCeU1lZGlhU2l6ZSh2YWxpZExldmVscywgdGhpcy5tZWRpYVdpZHRoLCB0aGlzLm1lZGlhSGVpZ2h0KTtcbiAgfVxuICBzdGFydENhcHBpbmcoKSB7XG4gICAgaWYgKHRoaXMudGltZXIpIHtcbiAgICAgIC8vIERvbid0IHJlc2V0IGNhcHBpbmcgaWYgc3RhcnRlZCB0d2ljZTsgdGhpcyBjYW4gaGFwcGVuIGlmIHRoZSBtYW5pZmVzdCBzaWduYWxzIGEgdmlkZW8gY29kZWNcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5hdXRvTGV2ZWxDYXBwaW5nID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuICAgIHNlbGYuY2xlYXJJbnRlcnZhbCh0aGlzLnRpbWVyKTtcbiAgICB0aGlzLnRpbWVyID0gc2VsZi5zZXRJbnRlcnZhbCh0aGlzLmRldGVjdFBsYXllclNpemUuYmluZCh0aGlzKSwgMTAwMCk7XG4gICAgdGhpcy5kZXRlY3RQbGF5ZXJTaXplKCk7XG4gIH1cbiAgc3RvcENhcHBpbmcoKSB7XG4gICAgdGhpcy5yZXN0cmljdGVkTGV2ZWxzID0gW107XG4gICAgdGhpcy5maXJzdExldmVsID0gLTE7XG4gICAgdGhpcy5hdXRvTGV2ZWxDYXBwaW5nID0gTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuICAgIGlmICh0aGlzLnRpbWVyKSB7XG4gICAgICBzZWxmLmNsZWFySW50ZXJ2YWwodGhpcy50aW1lcik7XG4gICAgICB0aGlzLnRpbWVyID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuICBnZXREaW1lbnNpb25zKCkge1xuICAgIGlmICh0aGlzLmNsaWVudFJlY3QpIHtcbiAgICAgIHJldHVybiB0aGlzLmNsaWVudFJlY3Q7XG4gICAgfVxuICAgIGNvbnN0IG1lZGlhID0gdGhpcy5tZWRpYTtcbiAgICBjb25zdCBib3VuZHNSZWN0ID0ge1xuICAgICAgd2lkdGg6IDAsXG4gICAgICBoZWlnaHQ6IDBcbiAgICB9O1xuICAgIGlmIChtZWRpYSkge1xuICAgICAgY29uc3QgY2xpZW50UmVjdCA9IG1lZGlhLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgYm91bmRzUmVjdC53aWR0aCA9IGNsaWVudFJlY3Qud2lkdGg7XG4gICAgICBib3VuZHNSZWN0LmhlaWdodCA9IGNsaWVudFJlY3QuaGVpZ2h0O1xuICAgICAgaWYgKCFib3VuZHNSZWN0LndpZHRoICYmICFib3VuZHNSZWN0LmhlaWdodCkge1xuICAgICAgICAvLyBXaGVuIHRoZSBtZWRpYSBlbGVtZW50IGhhcyBubyB3aWR0aCBvciBoZWlnaHQgKGVxdWl2YWxlbnQgdG8gbm90IGJlaW5nIGluIHRoZSBET00pLFxuICAgICAgICAvLyB0aGVuIHVzZSBpdHMgd2lkdGggYW5kIGhlaWdodCBhdHRyaWJ1dGVzIChtZWRpYS53aWR0aCwgbWVkaWEuaGVpZ2h0KVxuICAgICAgICBib3VuZHNSZWN0LndpZHRoID0gY2xpZW50UmVjdC5yaWdodCAtIGNsaWVudFJlY3QubGVmdCB8fCBtZWRpYS53aWR0aCB8fCAwO1xuICAgICAgICBib3VuZHNSZWN0LmhlaWdodCA9IGNsaWVudFJlY3QuYm90dG9tIC0gY2xpZW50UmVjdC50b3AgfHwgbWVkaWEuaGVpZ2h0IHx8IDA7XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuY2xpZW50UmVjdCA9IGJvdW5kc1JlY3Q7XG4gICAgcmV0dXJuIGJvdW5kc1JlY3Q7XG4gIH1cbiAgZ2V0IG1lZGlhV2lkdGgoKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0RGltZW5zaW9ucygpLndpZHRoICogdGhpcy5jb250ZW50U2NhbGVGYWN0b3I7XG4gIH1cbiAgZ2V0IG1lZGlhSGVpZ2h0KCkge1xuICAgIHJldHVybiB0aGlzLmdldERpbWVuc2lvbnMoKS5oZWlnaHQgKiB0aGlzLmNvbnRlbnRTY2FsZUZhY3RvcjtcbiAgfVxuICBnZXQgY29udGVudFNjYWxlRmFjdG9yKCkge1xuICAgIGxldCBwaXhlbFJhdGlvID0gMTtcbiAgICBpZiAoIXRoaXMuaGxzLmNvbmZpZy5pZ25vcmVEZXZpY2VQaXhlbFJhdGlvKSB7XG4gICAgICB0cnkge1xuICAgICAgICBwaXhlbFJhdGlvID0gc2VsZi5kZXZpY2VQaXhlbFJhdGlvO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAvKiBuby1vcCAqL1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcGl4ZWxSYXRpbztcbiAgfVxuICBpc0xldmVsQWxsb3dlZChsZXZlbCkge1xuICAgIGNvbnN0IHJlc3RyaWN0ZWRMZXZlbHMgPSB0aGlzLnJlc3RyaWN0ZWRMZXZlbHM7XG4gICAgcmV0dXJuICFyZXN0cmljdGVkTGV2ZWxzLnNvbWUocmVzdHJpY3RlZExldmVsID0+IHtcbiAgICAgIHJldHVybiBsZXZlbC5iaXRyYXRlID09PSByZXN0cmljdGVkTGV2ZWwuYml0cmF0ZSAmJiBsZXZlbC53aWR0aCA9PT0gcmVzdHJpY3RlZExldmVsLndpZHRoICYmIGxldmVsLmhlaWdodCA9PT0gcmVzdHJpY3RlZExldmVsLmhlaWdodDtcbiAgICB9KTtcbiAgfVxuICBzdGF0aWMgZ2V0TWF4TGV2ZWxCeU1lZGlhU2l6ZShsZXZlbHMsIHdpZHRoLCBoZWlnaHQpIHtcbiAgICBpZiAoIShsZXZlbHMgIT0gbnVsbCAmJiBsZXZlbHMubGVuZ3RoKSkge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH1cblxuICAgIC8vIExldmVscyBjYW4gaGF2ZSB0aGUgc2FtZSBkaW1lbnNpb25zIGJ1dCBkaWZmZXJpbmcgYmFuZHdpZHRocyAtIHNpbmNlIGxldmVscyBhcmUgb3JkZXJlZCwgd2UgY2FuIGxvb2sgdG8gdGhlIG5leHRcbiAgICAvLyB0byBkZXRlcm1pbmUgd2hldGhlciB3ZSd2ZSBjaG9zZW4gdGhlIGdyZWF0ZXN0IGJhbmR3aWR0aCBmb3IgdGhlIG1lZGlhJ3MgZGltZW5zaW9uc1xuICAgIGNvbnN0IGF0R3JlYXRlc3RCYW5kd2lkdGggPSAoY3VyTGV2ZWwsIG5leHRMZXZlbCkgPT4ge1xuICAgICAgaWYgKCFuZXh0TGV2ZWwpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgICByZXR1cm4gY3VyTGV2ZWwud2lkdGggIT09IG5leHRMZXZlbC53aWR0aCB8fCBjdXJMZXZlbC5oZWlnaHQgIT09IG5leHRMZXZlbC5oZWlnaHQ7XG4gICAgfTtcblxuICAgIC8vIElmIHdlIHJ1biB0aHJvdWdoIHRoZSBsb29wIHdpdGhvdXQgYnJlYWtpbmcsIHRoZSBtZWRpYSdzIGRpbWVuc2lvbnMgYXJlIGdyZWF0ZXIgdGhhbiBldmVyeSBsZXZlbCwgc28gZGVmYXVsdCB0b1xuICAgIC8vIHRoZSBtYXggbGV2ZWxcbiAgICBsZXQgbWF4TGV2ZWxJbmRleCA9IGxldmVscy5sZW5ndGggLSAxO1xuICAgIC8vIFByZXZlbnQgY2hhbmdlcyBpbiBhc3BlY3QtcmF0aW8gZnJvbSBjYXVzaW5nIGNhcHBpbmcgdG8gdG9nZ2xlIGJhY2sgYW5kIGZvcnRoXG4gICAgY29uc3Qgc3F1YXJlU2l6ZSA9IE1hdGgubWF4KHdpZHRoLCBoZWlnaHQpO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGV2ZWxzLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgICBjb25zdCBsZXZlbCA9IGxldmVsc1tpXTtcbiAgICAgIGlmICgobGV2ZWwud2lkdGggPj0gc3F1YXJlU2l6ZSB8fCBsZXZlbC5oZWlnaHQgPj0gc3F1YXJlU2l6ZSkgJiYgYXRHcmVhdGVzdEJhbmR3aWR0aChsZXZlbCwgbGV2ZWxzW2kgKyAxXSkpIHtcbiAgICAgICAgbWF4TGV2ZWxJbmRleCA9IGk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbWF4TGV2ZWxJbmRleDtcbiAgfVxufVxuXG5jbGFzcyBGUFNDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5pc1ZpZGVvUGxheWJhY2tRdWFsaXR5QXZhaWxhYmxlID0gZmFsc2U7XG4gICAgdGhpcy50aW1lciA9IHZvaWQgMDtcbiAgICB0aGlzLm1lZGlhID0gbnVsbDtcbiAgICB0aGlzLmxhc3RUaW1lID0gdm9pZCAwO1xuICAgIHRoaXMubGFzdERyb3BwZWRGcmFtZXMgPSAwO1xuICAgIHRoaXMubGFzdERlY29kZWRGcmFtZXMgPSAwO1xuICAgIC8vIHN0cmVhbSBjb250cm9sbGVyIG11c3QgYmUgcHJvdmlkZWQgYXMgYSBkZXBlbmRlbmN5IVxuICAgIHRoaXMuc3RyZWFtQ29udHJvbGxlciA9IHZvaWQgMDtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgICB0aGlzLnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gIH1cbiAgc2V0U3RyZWFtQ29udHJvbGxlcihzdHJlYW1Db250cm9sbGVyKSB7XG4gICAgdGhpcy5zdHJlYW1Db250cm9sbGVyID0gc3RyZWFtQ29udHJvbGxlcjtcbiAgfVxuICByZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICB0aGlzLmhscy5vbihFdmVudHMuTUVESUFfQVRUQUNISU5HLCB0aGlzLm9uTWVkaWFBdHRhY2hpbmcsIHRoaXMpO1xuICB9XG4gIHVucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgdGhpcy5obHMub2ZmKEV2ZW50cy5NRURJQV9BVFRBQ0hJTkcsIHRoaXMub25NZWRpYUF0dGFjaGluZywgdGhpcyk7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICBpZiAodGhpcy50aW1lcikge1xuICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLnRpbWVyKTtcbiAgICB9XG4gICAgdGhpcy51bnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgdGhpcy5pc1ZpZGVvUGxheWJhY2tRdWFsaXR5QXZhaWxhYmxlID0gZmFsc2U7XG4gICAgdGhpcy5tZWRpYSA9IG51bGw7XG4gIH1cbiAgb25NZWRpYUF0dGFjaGluZyhldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuaGxzLmNvbmZpZztcbiAgICBpZiAoY29uZmlnLmNhcExldmVsT25GUFNEcm9wKSB7XG4gICAgICBjb25zdCBtZWRpYSA9IGRhdGEubWVkaWEgaW5zdGFuY2VvZiBzZWxmLkhUTUxWaWRlb0VsZW1lbnQgPyBkYXRhLm1lZGlhIDogbnVsbDtcbiAgICAgIHRoaXMubWVkaWEgPSBtZWRpYTtcbiAgICAgIGlmIChtZWRpYSAmJiB0eXBlb2YgbWVkaWEuZ2V0VmlkZW9QbGF5YmFja1F1YWxpdHkgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgdGhpcy5pc1ZpZGVvUGxheWJhY2tRdWFsaXR5QXZhaWxhYmxlID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHNlbGYuY2xlYXJJbnRlcnZhbCh0aGlzLnRpbWVyKTtcbiAgICAgIHRoaXMudGltZXIgPSBzZWxmLnNldEludGVydmFsKHRoaXMuY2hlY2tGUFNJbnRlcnZhbC5iaW5kKHRoaXMpLCBjb25maWcuZnBzRHJvcHBlZE1vbml0b3JpbmdQZXJpb2QpO1xuICAgIH1cbiAgfVxuICBjaGVja0ZQUyh2aWRlbywgZGVjb2RlZEZyYW1lcywgZHJvcHBlZEZyYW1lcykge1xuICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgaWYgKGRlY29kZWRGcmFtZXMpIHtcbiAgICAgIGlmICh0aGlzLmxhc3RUaW1lKSB7XG4gICAgICAgIGNvbnN0IGN1cnJlbnRQZXJpb2QgPSBjdXJyZW50VGltZSAtIHRoaXMubGFzdFRpbWU7XG4gICAgICAgIGNvbnN0IGN1cnJlbnREcm9wcGVkID0gZHJvcHBlZEZyYW1lcyAtIHRoaXMubGFzdERyb3BwZWRGcmFtZXM7XG4gICAgICAgIGNvbnN0IGN1cnJlbnREZWNvZGVkID0gZGVjb2RlZEZyYW1lcyAtIHRoaXMubGFzdERlY29kZWRGcmFtZXM7XG4gICAgICAgIGNvbnN0IGRyb3BwZWRGUFMgPSAxMDAwICogY3VycmVudERyb3BwZWQgLyBjdXJyZW50UGVyaW9kO1xuICAgICAgICBjb25zdCBobHMgPSB0aGlzLmhscztcbiAgICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkZQU19EUk9QLCB7XG4gICAgICAgICAgY3VycmVudERyb3BwZWQ6IGN1cnJlbnREcm9wcGVkLFxuICAgICAgICAgIGN1cnJlbnREZWNvZGVkOiBjdXJyZW50RGVjb2RlZCxcbiAgICAgICAgICB0b3RhbERyb3BwZWRGcmFtZXM6IGRyb3BwZWRGcmFtZXNcbiAgICAgICAgfSk7XG4gICAgICAgIGlmIChkcm9wcGVkRlBTID4gMCkge1xuICAgICAgICAgIC8vIGxvZ2dlci5sb2coJ2NoZWNrRlBTIDogZHJvcHBlZEZQUy9kZWNvZGVkRlBTOicgKyBkcm9wcGVkRlBTLygxMDAwICogY3VycmVudERlY29kZWQgLyBjdXJyZW50UGVyaW9kKSk7XG4gICAgICAgICAgaWYgKGN1cnJlbnREcm9wcGVkID4gaGxzLmNvbmZpZy5mcHNEcm9wcGVkTW9uaXRvcmluZ1RocmVzaG9sZCAqIGN1cnJlbnREZWNvZGVkKSB7XG4gICAgICAgICAgICBsZXQgY3VycmVudExldmVsID0gaGxzLmN1cnJlbnRMZXZlbDtcbiAgICAgICAgICAgIGxvZ2dlci53YXJuKCdkcm9wIEZQUyByYXRpbyBncmVhdGVyIHRoYW4gbWF4IGFsbG93ZWQgdmFsdWUgZm9yIGN1cnJlbnRMZXZlbDogJyArIGN1cnJlbnRMZXZlbCk7XG4gICAgICAgICAgICBpZiAoY3VycmVudExldmVsID4gMCAmJiAoaGxzLmF1dG9MZXZlbENhcHBpbmcgPT09IC0xIHx8IGhscy5hdXRvTGV2ZWxDYXBwaW5nID49IGN1cnJlbnRMZXZlbCkpIHtcbiAgICAgICAgICAgICAgY3VycmVudExldmVsID0gY3VycmVudExldmVsIC0gMTtcbiAgICAgICAgICAgICAgaGxzLnRyaWdnZXIoRXZlbnRzLkZQU19EUk9QX0xFVkVMX0NBUFBJTkcsIHtcbiAgICAgICAgICAgICAgICBsZXZlbDogY3VycmVudExldmVsLFxuICAgICAgICAgICAgICAgIGRyb3BwZWRMZXZlbDogaGxzLmN1cnJlbnRMZXZlbFxuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgaGxzLmF1dG9MZXZlbENhcHBpbmcgPSBjdXJyZW50TGV2ZWw7XG4gICAgICAgICAgICAgIHRoaXMuc3RyZWFtQ29udHJvbGxlci5uZXh0TGV2ZWxTd2l0Y2goKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHRoaXMubGFzdFRpbWUgPSBjdXJyZW50VGltZTtcbiAgICAgIHRoaXMubGFzdERyb3BwZWRGcmFtZXMgPSBkcm9wcGVkRnJhbWVzO1xuICAgICAgdGhpcy5sYXN0RGVjb2RlZEZyYW1lcyA9IGRlY29kZWRGcmFtZXM7XG4gICAgfVxuICB9XG4gIGNoZWNrRlBTSW50ZXJ2YWwoKSB7XG4gICAgY29uc3QgdmlkZW8gPSB0aGlzLm1lZGlhO1xuICAgIGlmICh2aWRlbykge1xuICAgICAgaWYgKHRoaXMuaXNWaWRlb1BsYXliYWNrUXVhbGl0eUF2YWlsYWJsZSkge1xuICAgICAgICBjb25zdCB2aWRlb1BsYXliYWNrUXVhbGl0eSA9IHZpZGVvLmdldFZpZGVvUGxheWJhY2tRdWFsaXR5KCk7XG4gICAgICAgIHRoaXMuY2hlY2tGUFModmlkZW8sIHZpZGVvUGxheWJhY2tRdWFsaXR5LnRvdGFsVmlkZW9GcmFtZXMsIHZpZGVvUGxheWJhY2tRdWFsaXR5LmRyb3BwZWRWaWRlb0ZyYW1lcyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBIVE1MVmlkZW9FbGVtZW50IGRvZXNuJ3QgaW5jbHVkZSB0aGUgd2Via2l0IHR5cGVzXG4gICAgICAgIHRoaXMuY2hlY2tGUFModmlkZW8sIHZpZGVvLndlYmtpdERlY29kZWRGcmFtZUNvdW50LCB2aWRlby53ZWJraXREcm9wcGVkRnJhbWVDb3VudCk7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbmNvbnN0IExPR0dFUl9QUkVGSVggPSAnW2VtZV0nO1xuLyoqXG4gKiBDb250cm9sbGVyIHRvIGRlYWwgd2l0aCBlbmNyeXB0ZWQgbWVkaWEgZXh0ZW5zaW9ucyAoRU1FKVxuICogQHNlZSBodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvRW5jcnlwdGVkX01lZGlhX0V4dGVuc2lvbnNfQVBJXG4gKlxuICogQGNsYXNzXG4gKiBAY29uc3RydWN0b3JcbiAqL1xuY2xhc3MgRU1FQ29udHJvbGxlciB7XG4gIGNvbnN0cnVjdG9yKGhscykge1xuICAgIHRoaXMuaGxzID0gdm9pZCAwO1xuICAgIHRoaXMuY29uZmlnID0gdm9pZCAwO1xuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIHRoaXMua2V5Rm9ybWF0UHJvbWlzZSA9IG51bGw7XG4gICAgdGhpcy5rZXlTeXN0ZW1BY2Nlc3NQcm9taXNlcyA9IHt9O1xuICAgIHRoaXMuX3JlcXVlc3RMaWNlbnNlRmFpbHVyZUNvdW50ID0gMDtcbiAgICB0aGlzLm1lZGlhS2V5U2Vzc2lvbnMgPSBbXTtcbiAgICB0aGlzLmtleUlkVG9LZXlTZXNzaW9uUHJvbWlzZSA9IHt9O1xuICAgIHRoaXMuc2V0TWVkaWFLZXlzUXVldWUgPSBFTUVDb250cm9sbGVyLkNETUNsZWFudXBQcm9taXNlID8gW0VNRUNvbnRyb2xsZXIuQ0RNQ2xlYW51cFByb21pc2VdIDogW107XG4gICAgdGhpcy5vbk1lZGlhRW5jcnlwdGVkID0gdGhpcy5fb25NZWRpYUVuY3J5cHRlZC5iaW5kKHRoaXMpO1xuICAgIHRoaXMub25XYWl0aW5nRm9yS2V5ID0gdGhpcy5fb25XYWl0aW5nRm9yS2V5LmJpbmQodGhpcyk7XG4gICAgdGhpcy5kZWJ1ZyA9IGxvZ2dlci5kZWJ1Zy5iaW5kKGxvZ2dlciwgTE9HR0VSX1BSRUZJWCk7XG4gICAgdGhpcy5sb2cgPSBsb2dnZXIubG9nLmJpbmQobG9nZ2VyLCBMT0dHRVJfUFJFRklYKTtcbiAgICB0aGlzLndhcm4gPSBsb2dnZXIud2Fybi5iaW5kKGxvZ2dlciwgTE9HR0VSX1BSRUZJWCk7XG4gICAgdGhpcy5lcnJvciA9IGxvZ2dlci5lcnJvci5iaW5kKGxvZ2dlciwgTE9HR0VSX1BSRUZJWCk7XG4gICAgdGhpcy5obHMgPSBobHM7XG4gICAgdGhpcy5jb25maWcgPSBobHMuY29uZmlnO1xuICAgIHRoaXMucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMudW5yZWdpc3Rlckxpc3RlbmVycygpO1xuICAgIHRoaXMub25NZWRpYURldGFjaGVkKCk7XG4gICAgLy8gUmVtb3ZlIGFueSByZWZlcmVuY2VzIHRoYXQgY291bGQgYmUgaGVsZCBpbiBjb25maWcgb3B0aW9ucyBvciBjYWxsYmFja3NcbiAgICBjb25zdCBjb25maWcgPSB0aGlzLmNvbmZpZztcbiAgICBjb25maWcucmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzRnVuYyA9IG51bGw7XG4gICAgY29uZmlnLmxpY2Vuc2VYaHJTZXR1cCA9IGNvbmZpZy5saWNlbnNlUmVzcG9uc2VDYWxsYmFjayA9IHVuZGVmaW5lZDtcbiAgICBjb25maWcuZHJtU3lzdGVtcyA9IGNvbmZpZy5kcm1TeXN0ZW1PcHRpb25zID0ge307XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuaGxzID0gdGhpcy5vbk1lZGlhRW5jcnlwdGVkID0gdGhpcy5vbldhaXRpbmdGb3JLZXkgPSB0aGlzLmtleUlkVG9LZXlTZXNzaW9uUHJvbWlzZSA9IG51bGw7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuY29uZmlnID0gbnVsbDtcbiAgfVxuICByZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICB0aGlzLmhscy5vbihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICB0aGlzLmhscy5vbihFdmVudHMuTUVESUFfREVUQUNIRUQsIHRoaXMub25NZWRpYURldGFjaGVkLCB0aGlzKTtcbiAgICB0aGlzLmhscy5vbihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgdGhpcy5obHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURFRCwgdGhpcy5vbk1hbmlmZXN0TG9hZGVkLCB0aGlzKTtcbiAgfVxuICB1bnJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIHRoaXMuaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICB0aGlzLmhscy5vZmYoRXZlbnRzLk1FRElBX0RFVEFDSEVELCB0aGlzLm9uTWVkaWFEZXRhY2hlZCwgdGhpcyk7XG4gICAgdGhpcy5obHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICB0aGlzLmhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURFRCwgdGhpcy5vbk1hbmlmZXN0TG9hZGVkLCB0aGlzKTtcbiAgfVxuICBnZXRMaWNlbnNlU2VydmVyVXJsKGtleVN5c3RlbSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGRybVN5c3RlbXMsXG4gICAgICB3aWRldmluZUxpY2Vuc2VVcmxcbiAgICB9ID0gdGhpcy5jb25maWc7XG4gICAgY29uc3Qga2V5U3lzdGVtQ29uZmlndXJhdGlvbiA9IGRybVN5c3RlbXNba2V5U3lzdGVtXTtcbiAgICBpZiAoa2V5U3lzdGVtQ29uZmlndXJhdGlvbikge1xuICAgICAgcmV0dXJuIGtleVN5c3RlbUNvbmZpZ3VyYXRpb24ubGljZW5zZVVybDtcbiAgICB9XG5cbiAgICAvLyBGb3IgYmFja3dhcmQgY29tcGF0aWJpbGl0eVxuICAgIGlmIChrZXlTeXN0ZW0gPT09IEtleVN5c3RlbXMuV0lERVZJTkUgJiYgd2lkZXZpbmVMaWNlbnNlVXJsKSB7XG4gICAgICByZXR1cm4gd2lkZXZpbmVMaWNlbnNlVXJsO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoYG5vIGxpY2Vuc2Ugc2VydmVyIFVSTCBjb25maWd1cmVkIGZvciBrZXktc3lzdGVtIFwiJHtrZXlTeXN0ZW19XCJgKTtcbiAgfVxuICBnZXRTZXJ2ZXJDZXJ0aWZpY2F0ZVVybChrZXlTeXN0ZW0pIHtcbiAgICBjb25zdCB7XG4gICAgICBkcm1TeXN0ZW1zXG4gICAgfSA9IHRoaXMuY29uZmlnO1xuICAgIGNvbnN0IGtleVN5c3RlbUNvbmZpZ3VyYXRpb24gPSBkcm1TeXN0ZW1zW2tleVN5c3RlbV07XG4gICAgaWYgKGtleVN5c3RlbUNvbmZpZ3VyYXRpb24pIHtcbiAgICAgIHJldHVybiBrZXlTeXN0ZW1Db25maWd1cmF0aW9uLnNlcnZlckNlcnRpZmljYXRlVXJsO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmxvZyhgTm8gU2VydmVyIENlcnRpZmljYXRlIGluIGNvbmZpZy5kcm1TeXN0ZW1zW1wiJHtrZXlTeXN0ZW19XCJdYCk7XG4gICAgfVxuICB9XG4gIGF0dGVtcHRLZXlTeXN0ZW1BY2Nlc3Moa2V5U3lzdGVtc1RvQXR0ZW1wdCkge1xuICAgIGNvbnN0IGxldmVscyA9IHRoaXMuaGxzLmxldmVscztcbiAgICBjb25zdCB1bmlxdWVDb2RlYyA9ICh2YWx1ZSwgaSwgYSkgPT4gISF2YWx1ZSAmJiBhLmluZGV4T2YodmFsdWUpID09PSBpO1xuICAgIGNvbnN0IGF1ZGlvQ29kZWNzID0gbGV2ZWxzLm1hcChsZXZlbCA9PiBsZXZlbC5hdWRpb0NvZGVjKS5maWx0ZXIodW5pcXVlQ29kZWMpO1xuICAgIGNvbnN0IHZpZGVvQ29kZWNzID0gbGV2ZWxzLm1hcChsZXZlbCA9PiBsZXZlbC52aWRlb0NvZGVjKS5maWx0ZXIodW5pcXVlQ29kZWMpO1xuICAgIGlmIChhdWRpb0NvZGVjcy5sZW5ndGggKyB2aWRlb0NvZGVjcy5sZW5ndGggPT09IDApIHtcbiAgICAgIHZpZGVvQ29kZWNzLnB1c2goJ2F2YzEuNDJlMDFlJyk7XG4gICAgfVxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBhdHRlbXB0ID0ga2V5U3lzdGVtcyA9PiB7XG4gICAgICAgIGNvbnN0IGtleVN5c3RlbSA9IGtleVN5c3RlbXMuc2hpZnQoKTtcbiAgICAgICAgdGhpcy5nZXRNZWRpYUtleXNQcm9taXNlKGtleVN5c3RlbSwgYXVkaW9Db2RlY3MsIHZpZGVvQ29kZWNzKS50aGVuKG1lZGlhS2V5cyA9PiByZXNvbHZlKHtcbiAgICAgICAgICBrZXlTeXN0ZW0sXG4gICAgICAgICAgbWVkaWFLZXlzXG4gICAgICAgIH0pKS5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgICAgaWYgKGtleVN5c3RlbXMubGVuZ3RoKSB7XG4gICAgICAgICAgICBhdHRlbXB0KGtleVN5c3RlbXMpO1xuICAgICAgICAgIH0gZWxzZSBpZiAoZXJyb3IgaW5zdGFuY2VvZiBFTUVLZXlFcnJvcikge1xuICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcmVqZWN0KG5ldyBFTUVLZXlFcnJvcih7XG4gICAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLktFWV9TWVNURU1fTk9fQUNDRVNTLFxuICAgICAgICAgICAgICBlcnJvcixcbiAgICAgICAgICAgICAgZmF0YWw6IHRydWVcbiAgICAgICAgICAgIH0sIGVycm9yLm1lc3NhZ2UpKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgfTtcbiAgICAgIGF0dGVtcHQoa2V5U3lzdGVtc1RvQXR0ZW1wdCk7XG4gICAgfSk7XG4gIH1cbiAgcmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzKGtleVN5c3RlbSwgc3VwcG9ydGVkQ29uZmlndXJhdGlvbnMpIHtcbiAgICBjb25zdCB7XG4gICAgICByZXF1ZXN0TWVkaWFLZXlTeXN0ZW1BY2Nlc3NGdW5jXG4gICAgfSA9IHRoaXMuY29uZmlnO1xuICAgIGlmICghKHR5cGVvZiByZXF1ZXN0TWVkaWFLZXlTeXN0ZW1BY2Nlc3NGdW5jID09PSAnZnVuY3Rpb24nKSkge1xuICAgICAgbGV0IGVyck1lc3NhZ2UgPSBgQ29uZmlndXJlZCByZXF1ZXN0TWVkaWFLZXlTeXN0ZW1BY2Nlc3MgaXMgbm90IGEgZnVuY3Rpb24gJHtyZXF1ZXN0TWVkaWFLZXlTeXN0ZW1BY2Nlc3NGdW5jfWA7XG4gICAgICBpZiAocmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzID09PSBudWxsICYmIHNlbGYubG9jYXRpb24ucHJvdG9jb2wgPT09ICdodHRwOicpIHtcbiAgICAgICAgZXJyTWVzc2FnZSA9IGBuYXZpZ2F0b3IucmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzIGlzIG5vdCBhdmFpbGFibGUgb3ZlciBpbnNlY3VyZSBwcm90b2NvbCAke2xvY2F0aW9uLnByb3RvY29sfWA7XG4gICAgICB9XG4gICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEVycm9yKGVyck1lc3NhZ2UpKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlcXVlc3RNZWRpYUtleVN5c3RlbUFjY2Vzc0Z1bmMoa2V5U3lzdGVtLCBzdXBwb3J0ZWRDb25maWd1cmF0aW9ucyk7XG4gIH1cbiAgZ2V0TWVkaWFLZXlzUHJvbWlzZShrZXlTeXN0ZW0sIGF1ZGlvQ29kZWNzLCB2aWRlb0NvZGVjcykge1xuICAgIC8vIFRoaXMgY2FuIHRocm93LCBidXQgaXMgY2F1Z2h0IGluIGV2ZW50IGhhbmRsZXIgY2FsbHBhdGhcbiAgICBjb25zdCBtZWRpYUtleVN5c3RlbUNvbmZpZ3MgPSBnZXRTdXBwb3J0ZWRNZWRpYUtleVN5c3RlbUNvbmZpZ3VyYXRpb25zKGtleVN5c3RlbSwgYXVkaW9Db2RlY3MsIHZpZGVvQ29kZWNzLCB0aGlzLmNvbmZpZy5kcm1TeXN0ZW1PcHRpb25zKTtcbiAgICBjb25zdCBrZXlTeXN0ZW1BY2Nlc3NQcm9taXNlcyA9IHRoaXMua2V5U3lzdGVtQWNjZXNzUHJvbWlzZXNba2V5U3lzdGVtXTtcbiAgICBsZXQga2V5U3lzdGVtQWNjZXNzID0ga2V5U3lzdGVtQWNjZXNzUHJvbWlzZXMgPT0gbnVsbCA/IHZvaWQgMCA6IGtleVN5c3RlbUFjY2Vzc1Byb21pc2VzLmtleVN5c3RlbUFjY2VzcztcbiAgICBpZiAoIWtleVN5c3RlbUFjY2Vzcykge1xuICAgICAgdGhpcy5sb2coYFJlcXVlc3RpbmcgZW5jcnlwdGVkIG1lZGlhIFwiJHtrZXlTeXN0ZW19XCIga2V5LXN5c3RlbSBhY2Nlc3Mgd2l0aCBjb25maWc6ICR7SlNPTi5zdHJpbmdpZnkobWVkaWFLZXlTeXN0ZW1Db25maWdzKX1gKTtcbiAgICAgIGtleVN5c3RlbUFjY2VzcyA9IHRoaXMucmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzKGtleVN5c3RlbSwgbWVkaWFLZXlTeXN0ZW1Db25maWdzKTtcbiAgICAgIGNvbnN0IF9rZXlTeXN0ZW1BY2Nlc3NQcm9taXNlcyA9IHRoaXMua2V5U3lzdGVtQWNjZXNzUHJvbWlzZXNba2V5U3lzdGVtXSA9IHtcbiAgICAgICAga2V5U3lzdGVtQWNjZXNzXG4gICAgICB9O1xuICAgICAga2V5U3lzdGVtQWNjZXNzLmNhdGNoKGVycm9yID0+IHtcbiAgICAgICAgdGhpcy5sb2coYEZhaWxlZCB0byBvYnRhaW4gYWNjZXNzIHRvIGtleS1zeXN0ZW0gXCIke2tleVN5c3RlbX1cIjogJHtlcnJvcn1gKTtcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGtleVN5c3RlbUFjY2Vzcy50aGVuKG1lZGlhS2V5U3lzdGVtQWNjZXNzID0+IHtcbiAgICAgICAgdGhpcy5sb2coYEFjY2VzcyBmb3Iga2V5LXN5c3RlbSBcIiR7bWVkaWFLZXlTeXN0ZW1BY2Nlc3Mua2V5U3lzdGVtfVwiIG9idGFpbmVkYCk7XG4gICAgICAgIGNvbnN0IGNlcnRpZmljYXRlUmVxdWVzdCA9IHRoaXMuZmV0Y2hTZXJ2ZXJDZXJ0aWZpY2F0ZShrZXlTeXN0ZW0pO1xuICAgICAgICB0aGlzLmxvZyhgQ3JlYXRlIG1lZGlhLWtleXMgZm9yIFwiJHtrZXlTeXN0ZW19XCJgKTtcbiAgICAgICAgX2tleVN5c3RlbUFjY2Vzc1Byb21pc2VzLm1lZGlhS2V5cyA9IG1lZGlhS2V5U3lzdGVtQWNjZXNzLmNyZWF0ZU1lZGlhS2V5cygpLnRoZW4obWVkaWFLZXlzID0+IHtcbiAgICAgICAgICB0aGlzLmxvZyhgTWVkaWEta2V5cyBjcmVhdGVkIGZvciBcIiR7a2V5U3lzdGVtfVwiYCk7XG4gICAgICAgICAgcmV0dXJuIGNlcnRpZmljYXRlUmVxdWVzdC50aGVuKGNlcnRpZmljYXRlID0+IHtcbiAgICAgICAgICAgIGlmIChjZXJ0aWZpY2F0ZSkge1xuICAgICAgICAgICAgICByZXR1cm4gdGhpcy5zZXRNZWRpYUtleXNTZXJ2ZXJDZXJ0aWZpY2F0ZShtZWRpYUtleXMsIGtleVN5c3RlbSwgY2VydGlmaWNhdGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIG1lZGlhS2V5cztcbiAgICAgICAgICB9KTtcbiAgICAgICAgfSk7XG4gICAgICAgIF9rZXlTeXN0ZW1BY2Nlc3NQcm9taXNlcy5tZWRpYUtleXMuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgICAgIHRoaXMuZXJyb3IoYEZhaWxlZCB0byBjcmVhdGUgbWVkaWEta2V5cyBmb3IgXCIke2tleVN5c3RlbX1cIn06ICR7ZXJyb3J9YCk7XG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gX2tleVN5c3RlbUFjY2Vzc1Byb21pc2VzLm1lZGlhS2V5cztcbiAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm4ga2V5U3lzdGVtQWNjZXNzLnRoZW4oKCkgPT4ga2V5U3lzdGVtQWNjZXNzUHJvbWlzZXMubWVkaWFLZXlzKTtcbiAgfVxuICBjcmVhdGVNZWRpYUtleVNlc3Npb25Db250ZXh0KHtcbiAgICBkZWNyeXB0ZGF0YSxcbiAgICBrZXlTeXN0ZW0sXG4gICAgbWVkaWFLZXlzXG4gIH0pIHtcbiAgICB0aGlzLmxvZyhgQ3JlYXRpbmcga2V5LXN5c3RlbSBzZXNzaW9uIFwiJHtrZXlTeXN0ZW19XCIga2V5SWQ6ICR7SGV4LmhleER1bXAoZGVjcnlwdGRhdGEua2V5SWQgfHwgW10pfWApO1xuICAgIGNvbnN0IG1lZGlhS2V5c1Nlc3Npb24gPSBtZWRpYUtleXMuY3JlYXRlU2Vzc2lvbigpO1xuICAgIGNvbnN0IG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQgPSB7XG4gICAgICBkZWNyeXB0ZGF0YSxcbiAgICAgIGtleVN5c3RlbSxcbiAgICAgIG1lZGlhS2V5cyxcbiAgICAgIG1lZGlhS2V5c1Nlc3Npb24sXG4gICAgICBrZXlTdGF0dXM6ICdzdGF0dXMtcGVuZGluZydcbiAgICB9O1xuICAgIHRoaXMubWVkaWFLZXlTZXNzaW9ucy5wdXNoKG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQpO1xuICAgIHJldHVybiBtZWRpYUtleVNlc3Npb25Db250ZXh0O1xuICB9XG4gIHJlbmV3S2V5U2Vzc2lvbihtZWRpYUtleVNlc3Npb25Db250ZXh0KSB7XG4gICAgY29uc3QgZGVjcnlwdGRhdGEgPSBtZWRpYUtleVNlc3Npb25Db250ZXh0LmRlY3J5cHRkYXRhO1xuICAgIGlmIChkZWNyeXB0ZGF0YS5wc3NoKSB7XG4gICAgICBjb25zdCBrZXlTZXNzaW9uQ29udGV4dCA9IHRoaXMuY3JlYXRlTWVkaWFLZXlTZXNzaW9uQ29udGV4dChtZWRpYUtleVNlc3Npb25Db250ZXh0KTtcbiAgICAgIGNvbnN0IGtleUlkID0gdGhpcy5nZXRLZXlJZFN0cmluZyhkZWNyeXB0ZGF0YSk7XG4gICAgICBjb25zdCBzY2hlbWUgPSAnY2VuYyc7XG4gICAgICB0aGlzLmtleUlkVG9LZXlTZXNzaW9uUHJvbWlzZVtrZXlJZF0gPSB0aGlzLmdlbmVyYXRlUmVxdWVzdFdpdGhQcmVmZXJyZWRLZXlTZXNzaW9uKGtleVNlc3Npb25Db250ZXh0LCBzY2hlbWUsIGRlY3J5cHRkYXRhLnBzc2gsICdleHBpcmVkJyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMud2FybihgQ291bGQgbm90IHJlbmV3IGV4cGlyZWQgc2Vzc2lvbi4gTWlzc2luZyBwc3NoIGluaXREYXRhLmApO1xuICAgIH1cbiAgICB0aGlzLnJlbW92ZVNlc3Npb24obWVkaWFLZXlTZXNzaW9uQ29udGV4dCk7XG4gIH1cbiAgZ2V0S2V5SWRTdHJpbmcoZGVjcnlwdGRhdGEpIHtcbiAgICBpZiAoIWRlY3J5cHRkYXRhKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCByZWFkIGtleUlkIG9mIHVuZGVmaW5lZCBkZWNyeXB0ZGF0YScpO1xuICAgIH1cbiAgICBpZiAoZGVjcnlwdGRhdGEua2V5SWQgPT09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcigna2V5SWQgaXMgbnVsbCcpO1xuICAgIH1cbiAgICByZXR1cm4gSGV4LmhleER1bXAoZGVjcnlwdGRhdGEua2V5SWQpO1xuICB9XG4gIHVwZGF0ZUtleVNlc3Npb24obWVkaWFLZXlTZXNzaW9uQ29udGV4dCwgZGF0YSkge1xuICAgIHZhciBfbWVkaWFLZXlTZXNzaW9uQ29udGU7XG4gICAgY29uc3Qga2V5U2Vzc2lvbiA9IG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQubWVkaWFLZXlzU2Vzc2lvbjtcbiAgICB0aGlzLmxvZyhgVXBkYXRpbmcga2V5LXNlc3Npb24gXCIke2tleVNlc3Npb24uc2Vzc2lvbklkfVwiIGZvciBrZXlJRCAke0hleC5oZXhEdW1wKCgoX21lZGlhS2V5U2Vzc2lvbkNvbnRlID0gbWVkaWFLZXlTZXNzaW9uQ29udGV4dC5kZWNyeXB0ZGF0YSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9tZWRpYUtleVNlc3Npb25Db250ZS5rZXlJZCkgfHwgW10pfVxuICAgICAgfSAoZGF0YSBsZW5ndGg6ICR7ZGF0YSA/IGRhdGEuYnl0ZUxlbmd0aCA6IGRhdGF9KWApO1xuICAgIHJldHVybiBrZXlTZXNzaW9uLnVwZGF0ZShkYXRhKTtcbiAgfVxuICBzZWxlY3RLZXlTeXN0ZW1Gb3JtYXQoZnJhZykge1xuICAgIGNvbnN0IGtleUZvcm1hdHMgPSBPYmplY3Qua2V5cyhmcmFnLmxldmVsa2V5cyB8fCB7fSk7XG4gICAgaWYgKCF0aGlzLmtleUZvcm1hdFByb21pc2UpIHtcbiAgICAgIHRoaXMubG9nKGBTZWxlY3Rpbmcga2V5LXN5c3RlbSBmcm9tIGZyYWdtZW50IChzbjogJHtmcmFnLnNufSAke2ZyYWcudHlwZX06ICR7ZnJhZy5sZXZlbH0pIGtleSBmb3JtYXRzICR7a2V5Rm9ybWF0cy5qb2luKCcsICcpfWApO1xuICAgICAgdGhpcy5rZXlGb3JtYXRQcm9taXNlID0gdGhpcy5nZXRLZXlGb3JtYXRQcm9taXNlKGtleUZvcm1hdHMpO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5rZXlGb3JtYXRQcm9taXNlO1xuICB9XG4gIGdldEtleUZvcm1hdFByb21pc2Uoa2V5Rm9ybWF0cykge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBrZXlTeXN0ZW1zSW5Db25maWcgPSBnZXRLZXlTeXN0ZW1zRm9yQ29uZmlnKHRoaXMuY29uZmlnKTtcbiAgICAgIGNvbnN0IGtleVN5c3RlbXNUb0F0dGVtcHQgPSBrZXlGb3JtYXRzLm1hcChrZXlTeXN0ZW1Gb3JtYXRUb0tleVN5c3RlbURvbWFpbikuZmlsdGVyKHZhbHVlID0+ICEhdmFsdWUgJiYga2V5U3lzdGVtc0luQ29uZmlnLmluZGV4T2YodmFsdWUpICE9PSAtMSk7XG4gICAgICByZXR1cm4gdGhpcy5nZXRLZXlTeXN0ZW1TZWxlY3Rpb25Qcm9taXNlKGtleVN5c3RlbXNUb0F0dGVtcHQpLnRoZW4oKHtcbiAgICAgICAga2V5U3lzdGVtXG4gICAgICB9KSA9PiB7XG4gICAgICAgIGNvbnN0IGtleVN5c3RlbUZvcm1hdCA9IGtleVN5c3RlbURvbWFpblRvS2V5U3lzdGVtRm9ybWF0KGtleVN5c3RlbSk7XG4gICAgICAgIGlmIChrZXlTeXN0ZW1Gb3JtYXQpIHtcbiAgICAgICAgICByZXNvbHZlKGtleVN5c3RlbUZvcm1hdCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgVW5hYmxlIHRvIGZpbmQgZm9ybWF0IGZvciBrZXktc3lzdGVtIFwiJHtrZXlTeXN0ZW19XCJgKSk7XG4gICAgICAgIH1cbiAgICAgIH0pLmNhdGNoKHJlamVjdCk7XG4gICAgfSk7XG4gIH1cbiAgbG9hZEtleShkYXRhKSB7XG4gICAgY29uc3QgZGVjcnlwdGRhdGEgPSBkYXRhLmtleUluZm8uZGVjcnlwdGRhdGE7XG4gICAgY29uc3Qga2V5SWQgPSB0aGlzLmdldEtleUlkU3RyaW5nKGRlY3J5cHRkYXRhKTtcbiAgICBjb25zdCBrZXlEZXRhaWxzID0gYChrZXlJZDogJHtrZXlJZH0gZm9ybWF0OiBcIiR7ZGVjcnlwdGRhdGEua2V5Rm9ybWF0fVwiIG1ldGhvZDogJHtkZWNyeXB0ZGF0YS5tZXRob2R9IHVyaTogJHtkZWNyeXB0ZGF0YS51cml9KWA7XG4gICAgdGhpcy5sb2coYFN0YXJ0aW5nIHNlc3Npb24gZm9yIGtleSAke2tleURldGFpbHN9YCk7XG4gICAgbGV0IGtleVNlc3Npb25Db250ZXh0UHJvbWlzZSA9IHRoaXMua2V5SWRUb0tleVNlc3Npb25Qcm9taXNlW2tleUlkXTtcbiAgICBpZiAoIWtleVNlc3Npb25Db250ZXh0UHJvbWlzZSkge1xuICAgICAga2V5U2Vzc2lvbkNvbnRleHRQcm9taXNlID0gdGhpcy5rZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vba2V5SWRdID0gdGhpcy5nZXRLZXlTeXN0ZW1Gb3JLZXlQcm9taXNlKGRlY3J5cHRkYXRhKS50aGVuKCh7XG4gICAgICAgIGtleVN5c3RlbSxcbiAgICAgICAgbWVkaWFLZXlzXG4gICAgICB9KSA9PiB7XG4gICAgICAgIHRoaXMudGhyb3dJZkRlc3Ryb3llZCgpO1xuICAgICAgICB0aGlzLmxvZyhgSGFuZGxlIGVuY3J5cHRlZCBtZWRpYSBzbjogJHtkYXRhLmZyYWcuc259ICR7ZGF0YS5mcmFnLnR5cGV9OiAke2RhdGEuZnJhZy5sZXZlbH0gdXNpbmcga2V5ICR7a2V5RGV0YWlsc31gKTtcbiAgICAgICAgcmV0dXJuIHRoaXMuYXR0ZW1wdFNldE1lZGlhS2V5cyhrZXlTeXN0ZW0sIG1lZGlhS2V5cykudGhlbigoKSA9PiB7XG4gICAgICAgICAgdGhpcy50aHJvd0lmRGVzdHJveWVkKCk7XG4gICAgICAgICAgY29uc3Qga2V5U2Vzc2lvbkNvbnRleHQgPSB0aGlzLmNyZWF0ZU1lZGlhS2V5U2Vzc2lvbkNvbnRleHQoe1xuICAgICAgICAgICAga2V5U3lzdGVtLFxuICAgICAgICAgICAgbWVkaWFLZXlzLFxuICAgICAgICAgICAgZGVjcnlwdGRhdGFcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBjb25zdCBzY2hlbWUgPSAnY2VuYyc7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuZ2VuZXJhdGVSZXF1ZXN0V2l0aFByZWZlcnJlZEtleVNlc3Npb24oa2V5U2Vzc2lvbkNvbnRleHQsIHNjaGVtZSwgZGVjcnlwdGRhdGEucHNzaCwgJ3BsYXlsaXN0LWtleScpO1xuICAgICAgICB9KTtcbiAgICAgIH0pO1xuICAgICAga2V5U2Vzc2lvbkNvbnRleHRQcm9taXNlLmNhdGNoKGVycm9yID0+IHRoaXMuaGFuZGxlRXJyb3IoZXJyb3IpKTtcbiAgICB9XG4gICAgcmV0dXJuIGtleVNlc3Npb25Db250ZXh0UHJvbWlzZTtcbiAgfVxuICB0aHJvd0lmRGVzdHJveWVkKG1lc3NhZ2UgPSAnSW52YWxpZCBzdGF0ZScpIHtcbiAgICBpZiAoIXRoaXMuaGxzKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ2ludmFsaWQgc3RhdGUnKTtcbiAgICB9XG4gIH1cbiAgaGFuZGxlRXJyb3IoZXJyb3IpIHtcbiAgICBpZiAoIXRoaXMuaGxzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuZXJyb3IoZXJyb3IubWVzc2FnZSk7XG4gICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRU1FS2V5RXJyb3IpIHtcbiAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkVSUk9SLCBlcnJvci5kYXRhKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5LRVlfU1lTVEVNX0VSUk9SLFxuICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9OT19LRVlTLFxuICAgICAgICBlcnJvcixcbiAgICAgICAgZmF0YWw6IHRydWVcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICBnZXRLZXlTeXN0ZW1Gb3JLZXlQcm9taXNlKGRlY3J5cHRkYXRhKSB7XG4gICAgY29uc3Qga2V5SWQgPSB0aGlzLmdldEtleUlkU3RyaW5nKGRlY3J5cHRkYXRhKTtcbiAgICBjb25zdCBtZWRpYUtleVNlc3Npb25Db250ZXh0ID0gdGhpcy5rZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vba2V5SWRdO1xuICAgIGlmICghbWVkaWFLZXlTZXNzaW9uQ29udGV4dCkge1xuICAgICAgY29uc3Qga2V5U3lzdGVtID0ga2V5U3lzdGVtRm9ybWF0VG9LZXlTeXN0ZW1Eb21haW4oZGVjcnlwdGRhdGEua2V5Rm9ybWF0KTtcbiAgICAgIGNvbnN0IGtleVN5c3RlbXNUb0F0dGVtcHQgPSBrZXlTeXN0ZW0gPyBba2V5U3lzdGVtXSA6IGdldEtleVN5c3RlbXNGb3JDb25maWcodGhpcy5jb25maWcpO1xuICAgICAgcmV0dXJuIHRoaXMuYXR0ZW1wdEtleVN5c3RlbUFjY2VzcyhrZXlTeXN0ZW1zVG9BdHRlbXB0KTtcbiAgICB9XG4gICAgcmV0dXJuIG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQ7XG4gIH1cbiAgZ2V0S2V5U3lzdGVtU2VsZWN0aW9uUHJvbWlzZShrZXlTeXN0ZW1zVG9BdHRlbXB0KSB7XG4gICAgaWYgKCFrZXlTeXN0ZW1zVG9BdHRlbXB0Lmxlbmd0aCkge1xuICAgICAga2V5U3lzdGVtc1RvQXR0ZW1wdCA9IGdldEtleVN5c3RlbXNGb3JDb25maWcodGhpcy5jb25maWcpO1xuICAgIH1cbiAgICBpZiAoa2V5U3lzdGVtc1RvQXR0ZW1wdC5sZW5ndGggPT09IDApIHtcbiAgICAgIHRocm93IG5ldyBFTUVLZXlFcnJvcih7XG4gICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLktFWV9TWVNURU1fTk9fQ09ORklHVVJFRF9MSUNFTlNFLFxuICAgICAgICBmYXRhbDogdHJ1ZVxuICAgICAgfSwgYE1pc3Npbmcga2V5LXN5c3RlbSBsaWNlbnNlIGNvbmZpZ3VyYXRpb24gb3B0aW9ucyAke0pTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgZHJtU3lzdGVtczogdGhpcy5jb25maWcuZHJtU3lzdGVtc1xuICAgICAgfSl9YCk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmF0dGVtcHRLZXlTeXN0ZW1BY2Nlc3Moa2V5U3lzdGVtc1RvQXR0ZW1wdCk7XG4gIH1cbiAgX29uTWVkaWFFbmNyeXB0ZWQoZXZlbnQpIHtcbiAgICBjb25zdCB7XG4gICAgICBpbml0RGF0YVR5cGUsXG4gICAgICBpbml0RGF0YVxuICAgIH0gPSBldmVudDtcbiAgICBjb25zdCBsb2dNZXNzYWdlID0gYFwiJHtldmVudC50eXBlfVwiIGV2ZW50OiBpbml0IGRhdGEgdHlwZTogXCIke2luaXREYXRhVHlwZX1cImA7XG4gICAgdGhpcy5kZWJ1Zyhsb2dNZXNzYWdlKTtcblxuICAgIC8vIElnbm9yZSBldmVudCB3aGVuIGluaXREYXRhIGlzIG51bGxcbiAgICBpZiAoaW5pdERhdGEgPT09IG51bGwpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IGtleUlkO1xuICAgIGxldCBrZXlTeXN0ZW1Eb21haW47XG4gICAgaWYgKGluaXREYXRhVHlwZSA9PT0gJ3NpbmYnICYmIHRoaXMuY29uZmlnLmRybVN5c3RlbXNbS2V5U3lzdGVtcy5GQUlSUExBWV0pIHtcbiAgICAgIC8vIE1hdGNoIHNpbmYga2V5SWQgdG8gcGxheWxpc3Qgc2tkOi8va2V5SWQ9XG4gICAgICBjb25zdCBqc29uID0gYmluMnN0cihuZXcgVWludDhBcnJheShpbml0RGF0YSkpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3Qgc2luZiA9IGJhc2U2NERlY29kZShKU09OLnBhcnNlKGpzb24pLnNpbmYpO1xuICAgICAgICBjb25zdCB0ZW5jID0gcGFyc2VTaW5mKG5ldyBVaW50OEFycmF5KHNpbmYpKTtcbiAgICAgICAgaWYgKCF0ZW5jKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGAnc2NobScgYm94IG1pc3Npbmcgb3Igbm90IGNiY3MvY2VuYyB3aXRoIHNjaGkgPiB0ZW5jYCk7XG4gICAgICAgIH1cbiAgICAgICAga2V5SWQgPSB0ZW5jLnN1YmFycmF5KDgsIDI0KTtcbiAgICAgICAga2V5U3lzdGVtRG9tYWluID0gS2V5U3lzdGVtcy5GQUlSUExBWTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIHRoaXMud2FybihgJHtsb2dNZXNzYWdlfSBGYWlsZWQgdG8gcGFyc2Ugc2luZjogJHtlcnJvcn1gKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBTdXBwb3J0IFdpZGV2aW5lIGNsZWFyLWxlYWQga2V5LXNlc3Npb24gY3JlYXRpb24gKG90aGVyd2lzZSBkZXBlbmQgb24gcGxheWxpc3Qga2V5cylcbiAgICAgIGNvbnN0IHBzc2hSZXN1bHRzID0gcGFyc2VNdWx0aVBzc2goaW5pdERhdGEpO1xuICAgICAgY29uc3QgcHNzaEluZm8gPSBwc3NoUmVzdWx0cy5maWx0ZXIocHNzaCA9PiBwc3NoLnN5c3RlbUlkID09PSBLZXlTeXN0ZW1JZHMuV0lERVZJTkUpWzBdO1xuICAgICAgaWYgKCFwc3NoSW5mbykge1xuICAgICAgICBpZiAocHNzaFJlc3VsdHMubGVuZ3RoID09PSAwIHx8IHBzc2hSZXN1bHRzLnNvbWUocHNzaCA9PiAhcHNzaC5zeXN0ZW1JZCkpIHtcbiAgICAgICAgICB0aGlzLndhcm4oYCR7bG9nTWVzc2FnZX0gY29udGFpbnMgaW5jb21wbGV0ZSBvciBpbnZhbGlkIHBzc2ggZGF0YWApO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRoaXMubG9nKGBpZ25vcmluZyAke2xvZ01lc3NhZ2V9IGZvciAke3Bzc2hSZXN1bHRzLm1hcChwc3NoID0+IGtleVN5c3RlbUlkVG9LZXlTeXN0ZW1Eb21haW4ocHNzaC5zeXN0ZW1JZCkpLmpvaW4oJywnKX0gcHNzaCBkYXRhIGluIGZhdm9yIG9mIHBsYXlsaXN0IGtleXNgKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBrZXlTeXN0ZW1Eb21haW4gPSBrZXlTeXN0ZW1JZFRvS2V5U3lzdGVtRG9tYWluKHBzc2hJbmZvLnN5c3RlbUlkKTtcbiAgICAgIGlmIChwc3NoSW5mby52ZXJzaW9uID09PSAwICYmIHBzc2hJbmZvLmRhdGEpIHtcbiAgICAgICAgY29uc3Qgb2Zmc2V0ID0gcHNzaEluZm8uZGF0YS5sZW5ndGggLSAyMjtcbiAgICAgICAga2V5SWQgPSBwc3NoSW5mby5kYXRhLnN1YmFycmF5KG9mZnNldCwgb2Zmc2V0ICsgMTYpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoIWtleVN5c3RlbURvbWFpbiB8fCAha2V5SWQpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qga2V5SWRIZXggPSBIZXguaGV4RHVtcChrZXlJZCk7XG4gICAgY29uc3Qge1xuICAgICAga2V5SWRUb0tleVNlc3Npb25Qcm9taXNlLFxuICAgICAgbWVkaWFLZXlTZXNzaW9uc1xuICAgIH0gPSB0aGlzO1xuICAgIGxldCBrZXlTZXNzaW9uQ29udGV4dFByb21pc2UgPSBrZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vba2V5SWRIZXhdO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbWVkaWFLZXlTZXNzaW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgLy8gTWF0Y2ggcGxheWxpc3Qga2V5XG4gICAgICBjb25zdCBrZXlDb250ZXh0ID0gbWVkaWFLZXlTZXNzaW9uc1tpXTtcbiAgICAgIGNvbnN0IGRlY3J5cHRkYXRhID0ga2V5Q29udGV4dC5kZWNyeXB0ZGF0YTtcbiAgICAgIGlmICghZGVjcnlwdGRhdGEua2V5SWQpIHtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG4gICAgICBjb25zdCBvbGRLZXlJZEhleCA9IEhleC5oZXhEdW1wKGRlY3J5cHRkYXRhLmtleUlkKTtcbiAgICAgIGlmIChrZXlJZEhleCA9PT0gb2xkS2V5SWRIZXggfHwgZGVjcnlwdGRhdGEudXJpLnJlcGxhY2UoLy0vZywgJycpLmluZGV4T2Yoa2V5SWRIZXgpICE9PSAtMSkge1xuICAgICAgICBrZXlTZXNzaW9uQ29udGV4dFByb21pc2UgPSBrZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vbb2xkS2V5SWRIZXhdO1xuICAgICAgICBpZiAoZGVjcnlwdGRhdGEucHNzaCkge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAgIGRlbGV0ZSBrZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vbb2xkS2V5SWRIZXhdO1xuICAgICAgICBkZWNyeXB0ZGF0YS5wc3NoID0gbmV3IFVpbnQ4QXJyYXkoaW5pdERhdGEpO1xuICAgICAgICBkZWNyeXB0ZGF0YS5rZXlJZCA9IGtleUlkO1xuICAgICAgICBrZXlTZXNzaW9uQ29udGV4dFByb21pc2UgPSBrZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vba2V5SWRIZXhdID0ga2V5U2Vzc2lvbkNvbnRleHRQcm9taXNlLnRoZW4oKCkgPT4ge1xuICAgICAgICAgIHJldHVybiB0aGlzLmdlbmVyYXRlUmVxdWVzdFdpdGhQcmVmZXJyZWRLZXlTZXNzaW9uKGtleUNvbnRleHQsIGluaXREYXRhVHlwZSwgaW5pdERhdGEsICdlbmNyeXB0ZWQtZXZlbnQta2V5LW1hdGNoJyk7XG4gICAgICAgIH0pO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKCFrZXlTZXNzaW9uQ29udGV4dFByb21pc2UpIHtcbiAgICAgIC8vIENsZWFyLWxlYWQga2V5IChub3QgZW5jb3VudGVyZWQgaW4gcGxheWxpc3QpXG4gICAgICBrZXlTZXNzaW9uQ29udGV4dFByb21pc2UgPSBrZXlJZFRvS2V5U2Vzc2lvblByb21pc2Vba2V5SWRIZXhdID0gdGhpcy5nZXRLZXlTeXN0ZW1TZWxlY3Rpb25Qcm9taXNlKFtrZXlTeXN0ZW1Eb21haW5dKS50aGVuKCh7XG4gICAgICAgIGtleVN5c3RlbSxcbiAgICAgICAgbWVkaWFLZXlzXG4gICAgICB9KSA9PiB7XG4gICAgICAgIHZhciBfa2V5U3lzdGVtVG9LZXlTeXN0ZW07XG4gICAgICAgIHRoaXMudGhyb3dJZkRlc3Ryb3llZCgpO1xuICAgICAgICBjb25zdCBkZWNyeXB0ZGF0YSA9IG5ldyBMZXZlbEtleSgnSVNPLTIzMDAxLTcnLCBrZXlJZEhleCwgKF9rZXlTeXN0ZW1Ub0tleVN5c3RlbSA9IGtleVN5c3RlbURvbWFpblRvS2V5U3lzdGVtRm9ybWF0KGtleVN5c3RlbSkpICE9IG51bGwgPyBfa2V5U3lzdGVtVG9LZXlTeXN0ZW0gOiAnJyk7XG4gICAgICAgIGRlY3J5cHRkYXRhLnBzc2ggPSBuZXcgVWludDhBcnJheShpbml0RGF0YSk7XG4gICAgICAgIGRlY3J5cHRkYXRhLmtleUlkID0ga2V5SWQ7XG4gICAgICAgIHJldHVybiB0aGlzLmF0dGVtcHRTZXRNZWRpYUtleXMoa2V5U3lzdGVtLCBtZWRpYUtleXMpLnRoZW4oKCkgPT4ge1xuICAgICAgICAgIHRoaXMudGhyb3dJZkRlc3Ryb3llZCgpO1xuICAgICAgICAgIGNvbnN0IGtleVNlc3Npb25Db250ZXh0ID0gdGhpcy5jcmVhdGVNZWRpYUtleVNlc3Npb25Db250ZXh0KHtcbiAgICAgICAgICAgIGRlY3J5cHRkYXRhLFxuICAgICAgICAgICAga2V5U3lzdGVtLFxuICAgICAgICAgICAgbWVkaWFLZXlzXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuZ2VuZXJhdGVSZXF1ZXN0V2l0aFByZWZlcnJlZEtleVNlc3Npb24oa2V5U2Vzc2lvbkNvbnRleHQsIGluaXREYXRhVHlwZSwgaW5pdERhdGEsICdlbmNyeXB0ZWQtZXZlbnQtbm8tbWF0Y2gnKTtcbiAgICAgICAgfSk7XG4gICAgICB9KTtcbiAgICB9XG4gICAga2V5U2Vzc2lvbkNvbnRleHRQcm9taXNlLmNhdGNoKGVycm9yID0+IHRoaXMuaGFuZGxlRXJyb3IoZXJyb3IpKTtcbiAgfVxuICBfb25XYWl0aW5nRm9yS2V5KGV2ZW50KSB7XG4gICAgdGhpcy5sb2coYFwiJHtldmVudC50eXBlfVwiIGV2ZW50YCk7XG4gIH1cbiAgYXR0ZW1wdFNldE1lZGlhS2V5cyhrZXlTeXN0ZW0sIG1lZGlhS2V5cykge1xuICAgIGNvbnN0IHF1ZXVlID0gdGhpcy5zZXRNZWRpYUtleXNRdWV1ZS5zbGljZSgpO1xuICAgIHRoaXMubG9nKGBTZXR0aW5nIG1lZGlhLWtleXMgZm9yIFwiJHtrZXlTeXN0ZW19XCJgKTtcbiAgICAvLyBPbmx5IG9uZSBzZXRNZWRpYUtleXMoKSBjYW4gcnVuIGF0IG9uZSB0aW1lLCBhbmQgbXVsdGlwbGUgc2V0TWVkaWFLZXlzKCkgb3BlcmF0aW9uc1xuICAgIC8vIGNhbiBiZSBxdWV1ZWQgZm9yIGV4ZWN1dGlvbiBmb3IgbXVsdGlwbGUga2V5IHNlc3Npb25zLlxuICAgIGNvbnN0IHNldE1lZGlhS2V5c1Byb21pc2UgPSBQcm9taXNlLmFsbChxdWV1ZSkudGhlbigoKSA9PiB7XG4gICAgICBpZiAoIXRoaXMubWVkaWEpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdBdHRlbXB0ZWQgdG8gc2V0IG1lZGlhS2V5cyB3aXRob3V0IG1lZGlhIGVsZW1lbnQgYXR0YWNoZWQnKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0aGlzLm1lZGlhLnNldE1lZGlhS2V5cyhtZWRpYUtleXMpO1xuICAgIH0pO1xuICAgIHRoaXMuc2V0TWVkaWFLZXlzUXVldWUucHVzaChzZXRNZWRpYUtleXNQcm9taXNlKTtcbiAgICByZXR1cm4gc2V0TWVkaWFLZXlzUHJvbWlzZS50aGVuKCgpID0+IHtcbiAgICAgIHRoaXMubG9nKGBNZWRpYS1rZXlzIHNldCBmb3IgXCIke2tleVN5c3RlbX1cImApO1xuICAgICAgcXVldWUucHVzaChzZXRNZWRpYUtleXNQcm9taXNlKTtcbiAgICAgIHRoaXMuc2V0TWVkaWFLZXlzUXVldWUgPSB0aGlzLnNldE1lZGlhS2V5c1F1ZXVlLmZpbHRlcihwID0+IHF1ZXVlLmluZGV4T2YocCkgPT09IC0xKTtcbiAgICB9KTtcbiAgfVxuICBnZW5lcmF0ZVJlcXVlc3RXaXRoUHJlZmVycmVkS2V5U2Vzc2lvbihjb250ZXh0LCBpbml0RGF0YVR5cGUsIGluaXREYXRhLCByZWFzb24pIHtcbiAgICB2YXIgX3RoaXMkY29uZmlnJGRybVN5c3RlLCBfdGhpcyRjb25maWckZHJtU3lzdGUyO1xuICAgIGNvbnN0IGdlbmVyYXRlUmVxdWVzdEZpbHRlciA9IChfdGhpcyRjb25maWckZHJtU3lzdGUgPSB0aGlzLmNvbmZpZy5kcm1TeXN0ZW1zKSA9PSBudWxsID8gdm9pZCAwIDogKF90aGlzJGNvbmZpZyRkcm1TeXN0ZTIgPSBfdGhpcyRjb25maWckZHJtU3lzdGVbY29udGV4dC5rZXlTeXN0ZW1dKSA9PSBudWxsID8gdm9pZCAwIDogX3RoaXMkY29uZmlnJGRybVN5c3RlMi5nZW5lcmF0ZVJlcXVlc3Q7XG4gICAgaWYgKGdlbmVyYXRlUmVxdWVzdEZpbHRlcikge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgbWFwcGVkSW5pdERhdGEgPSBnZW5lcmF0ZVJlcXVlc3RGaWx0ZXIuY2FsbCh0aGlzLmhscywgaW5pdERhdGFUeXBlLCBpbml0RGF0YSwgY29udGV4dCk7XG4gICAgICAgIGlmICghbWFwcGVkSW5pdERhdGEpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgcmVzcG9uc2UgZnJvbSBjb25maWd1cmVkIGdlbmVyYXRlUmVxdWVzdCBmaWx0ZXInKTtcbiAgICAgICAgfVxuICAgICAgICBpbml0RGF0YVR5cGUgPSBtYXBwZWRJbml0RGF0YS5pbml0RGF0YVR5cGU7XG4gICAgICAgIGluaXREYXRhID0gY29udGV4dC5kZWNyeXB0ZGF0YS5wc3NoID0gbWFwcGVkSW5pdERhdGEuaW5pdERhdGEgPyBuZXcgVWludDhBcnJheShtYXBwZWRJbml0RGF0YS5pbml0RGF0YSkgOiBudWxsO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgdmFyIF90aGlzJGhscztcbiAgICAgICAgdGhpcy53YXJuKGVycm9yLm1lc3NhZ2UpO1xuICAgICAgICBpZiAoKF90aGlzJGhscyA9IHRoaXMuaGxzKSAhPSBudWxsICYmIF90aGlzJGhscy5jb25maWcuZGVidWcpIHtcbiAgICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBpZiAoaW5pdERhdGEgPT09IG51bGwpIHtcbiAgICAgIHRoaXMubG9nKGBTa2lwcGluZyBrZXktc2Vzc2lvbiByZXF1ZXN0IGZvciBcIiR7cmVhc29ufVwiIChubyBpbml0RGF0YSlgKTtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoY29udGV4dCk7XG4gICAgfVxuICAgIGNvbnN0IGtleUlkID0gdGhpcy5nZXRLZXlJZFN0cmluZyhjb250ZXh0LmRlY3J5cHRkYXRhKTtcbiAgICB0aGlzLmxvZyhgR2VuZXJhdGluZyBrZXktc2Vzc2lvbiByZXF1ZXN0IGZvciBcIiR7cmVhc29ufVwiOiAke2tleUlkfSAoaW5pdCBkYXRhIHR5cGU6ICR7aW5pdERhdGFUeXBlfSBsZW5ndGg6ICR7aW5pdERhdGEgPyBpbml0RGF0YS5ieXRlTGVuZ3RoIDogbnVsbH0pYCk7XG4gICAgY29uc3QgbGljZW5zZVN0YXR1cyA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcbiAgICBjb25zdCBvbm1lc3NhZ2UgPSBjb250ZXh0Ll9vbm1lc3NhZ2UgPSBldmVudCA9PiB7XG4gICAgICBjb25zdCBrZXlTZXNzaW9uID0gY29udGV4dC5tZWRpYUtleXNTZXNzaW9uO1xuICAgICAgaWYgKCFrZXlTZXNzaW9uKSB7XG4gICAgICAgIGxpY2Vuc2VTdGF0dXMuZW1pdCgnZXJyb3InLCBuZXcgRXJyb3IoJ2ludmFsaWQgc3RhdGUnKSk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IHtcbiAgICAgICAgbWVzc2FnZVR5cGUsXG4gICAgICAgIG1lc3NhZ2VcbiAgICAgIH0gPSBldmVudDtcbiAgICAgIHRoaXMubG9nKGBcIiR7bWVzc2FnZVR5cGV9XCIgbWVzc2FnZSBldmVudCBmb3Igc2Vzc2lvbiBcIiR7a2V5U2Vzc2lvbi5zZXNzaW9uSWR9XCIgbWVzc2FnZSBzaXplOiAke21lc3NhZ2UuYnl0ZUxlbmd0aH1gKTtcbiAgICAgIGlmIChtZXNzYWdlVHlwZSA9PT0gJ2xpY2Vuc2UtcmVxdWVzdCcgfHwgbWVzc2FnZVR5cGUgPT09ICdsaWNlbnNlLXJlbmV3YWwnKSB7XG4gICAgICAgIHRoaXMucmVuZXdMaWNlbnNlKGNvbnRleHQsIG1lc3NhZ2UpLmNhdGNoKGVycm9yID0+IHtcbiAgICAgICAgICB0aGlzLmhhbmRsZUVycm9yKGVycm9yKTtcbiAgICAgICAgICBsaWNlbnNlU3RhdHVzLmVtaXQoJ2Vycm9yJywgZXJyb3IpO1xuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSBpZiAobWVzc2FnZVR5cGUgPT09ICdsaWNlbnNlLXJlbGVhc2UnKSB7XG4gICAgICAgIGlmIChjb250ZXh0LmtleVN5c3RlbSA9PT0gS2V5U3lzdGVtcy5GQUlSUExBWSkge1xuICAgICAgICAgIHRoaXMudXBkYXRlS2V5U2Vzc2lvbihjb250ZXh0LCBzdHJUb1V0ZjhhcnJheSgnYWNrbm93bGVkZ2VkJykpO1xuICAgICAgICAgIHRoaXMucmVtb3ZlU2Vzc2lvbihjb250ZXh0KTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy53YXJuKGB1bmhhbmRsZWQgbWVkaWEga2V5IG1lc3NhZ2UgdHlwZSBcIiR7bWVzc2FnZVR5cGV9XCJgKTtcbiAgICAgIH1cbiAgICB9O1xuICAgIGNvbnN0IG9ua2V5c3RhdHVzZXNjaGFuZ2UgPSBjb250ZXh0Ll9vbmtleXN0YXR1c2VzY2hhbmdlID0gZXZlbnQgPT4ge1xuICAgICAgY29uc3Qga2V5U2Vzc2lvbiA9IGNvbnRleHQubWVkaWFLZXlzU2Vzc2lvbjtcbiAgICAgIGlmICgha2V5U2Vzc2lvbikge1xuICAgICAgICBsaWNlbnNlU3RhdHVzLmVtaXQoJ2Vycm9yJywgbmV3IEVycm9yKCdpbnZhbGlkIHN0YXRlJykpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICB0aGlzLm9uS2V5U3RhdHVzQ2hhbmdlKGNvbnRleHQpO1xuICAgICAgY29uc3Qga2V5U3RhdHVzID0gY29udGV4dC5rZXlTdGF0dXM7XG4gICAgICBsaWNlbnNlU3RhdHVzLmVtaXQoJ2tleVN0YXR1cycsIGtleVN0YXR1cyk7XG4gICAgICBpZiAoa2V5U3RhdHVzID09PSAnZXhwaXJlZCcpIHtcbiAgICAgICAgdGhpcy53YXJuKGAke2NvbnRleHQua2V5U3lzdGVtfSBleHBpcmVkIGZvciBrZXkgJHtrZXlJZH1gKTtcbiAgICAgICAgdGhpcy5yZW5ld0tleVNlc3Npb24oY29udGV4dCk7XG4gICAgICB9XG4gICAgfTtcbiAgICBjb250ZXh0Lm1lZGlhS2V5c1Nlc3Npb24uYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIG9ubWVzc2FnZSk7XG4gICAgY29udGV4dC5tZWRpYUtleXNTZXNzaW9uLmFkZEV2ZW50TGlzdGVuZXIoJ2tleXN0YXR1c2VzY2hhbmdlJywgb25rZXlzdGF0dXNlc2NoYW5nZSk7XG4gICAgY29uc3Qga2V5VXNhYmxlUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIGxpY2Vuc2VTdGF0dXMub24oJ2Vycm9yJywgcmVqZWN0KTtcbiAgICAgIGxpY2Vuc2VTdGF0dXMub24oJ2tleVN0YXR1cycsIGtleVN0YXR1cyA9PiB7XG4gICAgICAgIGlmIChrZXlTdGF0dXMuc3RhcnRzV2l0aCgndXNhYmxlJykpIHtcbiAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgIH0gZWxzZSBpZiAoa2V5U3RhdHVzID09PSAnb3V0cHV0LXJlc3RyaWN0ZWQnKSB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFTUVLZXlFcnJvcih7XG4gICAgICAgICAgICB0eXBlOiBFcnJvclR5cGVzLktFWV9TWVNURU1fRVJST1IsXG4gICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9TVEFUVVNfT1VUUFVUX1JFU1RSSUNURUQsXG4gICAgICAgICAgICBmYXRhbDogZmFsc2VcbiAgICAgICAgICB9LCAnSERDUCBsZXZlbCBvdXRwdXQgcmVzdHJpY3RlZCcpKTtcbiAgICAgICAgfSBlbHNlIGlmIChrZXlTdGF0dXMgPT09ICdpbnRlcm5hbC1lcnJvcicpIHtcbiAgICAgICAgICByZWplY3QobmV3IEVNRUtleUVycm9yKHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5LRVlfU1lTVEVNX1NUQVRVU19JTlRFUk5BTF9FUlJPUixcbiAgICAgICAgICAgIGZhdGFsOiB0cnVlXG4gICAgICAgICAgfSwgYGtleSBzdGF0dXMgY2hhbmdlZCB0byBcIiR7a2V5U3RhdHVzfVwiYCkpO1xuICAgICAgICB9IGVsc2UgaWYgKGtleVN0YXR1cyA9PT0gJ2V4cGlyZWQnKSB7XG4gICAgICAgICAgcmVqZWN0KG5ldyBFcnJvcigna2V5IGV4cGlyZWQgd2hpbGUgZ2VuZXJhdGluZyByZXF1ZXN0JykpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRoaXMud2FybihgdW5oYW5kbGVkIGtleSBzdGF0dXMgY2hhbmdlIFwiJHtrZXlTdGF0dXN9XCJgKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gICAgcmV0dXJuIGNvbnRleHQubWVkaWFLZXlzU2Vzc2lvbi5nZW5lcmF0ZVJlcXVlc3QoaW5pdERhdGFUeXBlLCBpbml0RGF0YSkudGhlbigoKSA9PiB7XG4gICAgICB2YXIgX2NvbnRleHQkbWVkaWFLZXlzU2VzO1xuICAgICAgdGhpcy5sb2coYFJlcXVlc3QgZ2VuZXJhdGVkIGZvciBrZXktc2Vzc2lvbiBcIiR7KF9jb250ZXh0JG1lZGlhS2V5c1NlcyA9IGNvbnRleHQubWVkaWFLZXlzU2Vzc2lvbikgPT0gbnVsbCA/IHZvaWQgMCA6IF9jb250ZXh0JG1lZGlhS2V5c1Nlcy5zZXNzaW9uSWR9XCIga2V5SWQ6ICR7a2V5SWR9YCk7XG4gICAgfSkuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgdGhyb3cgbmV3IEVNRUtleUVycm9yKHtcbiAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5LRVlfU1lTVEVNX0VSUk9SLFxuICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9OT19TRVNTSU9OLFxuICAgICAgICBlcnJvcixcbiAgICAgICAgZmF0YWw6IGZhbHNlXG4gICAgICB9LCBgRXJyb3IgZ2VuZXJhdGluZyBrZXktc2Vzc2lvbiByZXF1ZXN0OiAke2Vycm9yfWApO1xuICAgIH0pLnRoZW4oKCkgPT4ga2V5VXNhYmxlUHJvbWlzZSkuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgbGljZW5zZVN0YXR1cy5yZW1vdmVBbGxMaXN0ZW5lcnMoKTtcbiAgICAgIHRoaXMucmVtb3ZlU2Vzc2lvbihjb250ZXh0KTtcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH0pLnRoZW4oKCkgPT4ge1xuICAgICAgbGljZW5zZVN0YXR1cy5yZW1vdmVBbGxMaXN0ZW5lcnMoKTtcbiAgICAgIHJldHVybiBjb250ZXh0O1xuICAgIH0pO1xuICB9XG4gIG9uS2V5U3RhdHVzQ2hhbmdlKG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQpIHtcbiAgICBtZWRpYUtleVNlc3Npb25Db250ZXh0Lm1lZGlhS2V5c1Nlc3Npb24ua2V5U3RhdHVzZXMuZm9yRWFjaCgoc3RhdHVzLCBrZXlJZCkgPT4ge1xuICAgICAgdGhpcy5sb2coYGtleSBzdGF0dXMgY2hhbmdlIFwiJHtzdGF0dXN9XCIgZm9yIGtleVN0YXR1c2VzIGtleUlkOiAke0hleC5oZXhEdW1wKCdidWZmZXInIGluIGtleUlkID8gbmV3IFVpbnQ4QXJyYXkoa2V5SWQuYnVmZmVyLCBrZXlJZC5ieXRlT2Zmc2V0LCBrZXlJZC5ieXRlTGVuZ3RoKSA6IG5ldyBVaW50OEFycmF5KGtleUlkKSl9IHNlc3Npb24ga2V5SWQ6ICR7SGV4LmhleER1bXAobmV3IFVpbnQ4QXJyYXkobWVkaWFLZXlTZXNzaW9uQ29udGV4dC5kZWNyeXB0ZGF0YS5rZXlJZCB8fCBbXSkpfSB1cmk6ICR7bWVkaWFLZXlTZXNzaW9uQ29udGV4dC5kZWNyeXB0ZGF0YS51cml9YCk7XG4gICAgICBtZWRpYUtleVNlc3Npb25Db250ZXh0LmtleVN0YXR1cyA9IHN0YXR1cztcbiAgICB9KTtcbiAgfVxuICBmZXRjaFNlcnZlckNlcnRpZmljYXRlKGtleVN5c3RlbSkge1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuY29uZmlnO1xuICAgIGNvbnN0IExvYWRlciA9IGNvbmZpZy5sb2FkZXI7XG4gICAgY29uc3QgY2VydExvYWRlciA9IG5ldyBMb2FkZXIoY29uZmlnKTtcbiAgICBjb25zdCB1cmwgPSB0aGlzLmdldFNlcnZlckNlcnRpZmljYXRlVXJsKGtleVN5c3RlbSk7XG4gICAgaWYgKCF1cmwpIHtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKTtcbiAgICB9XG4gICAgdGhpcy5sb2coYEZldGNoaW5nIHNlcnZlciBjZXJ0aWZpY2F0ZSBmb3IgXCIke2tleVN5c3RlbX1cImApO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBsb2FkZXJDb250ZXh0ID0ge1xuICAgICAgICByZXNwb25zZVR5cGU6ICdhcnJheWJ1ZmZlcicsXG4gICAgICAgIHVybFxuICAgICAgfTtcbiAgICAgIGNvbnN0IGxvYWRQb2xpY3kgPSBjb25maWcuY2VydExvYWRQb2xpY3kuZGVmYXVsdDtcbiAgICAgIGNvbnN0IGxvYWRlckNvbmZpZyA9IHtcbiAgICAgICAgbG9hZFBvbGljeSxcbiAgICAgICAgdGltZW91dDogbG9hZFBvbGljeS5tYXhMb2FkVGltZU1zLFxuICAgICAgICBtYXhSZXRyeTogMCxcbiAgICAgICAgcmV0cnlEZWxheTogMCxcbiAgICAgICAgbWF4UmV0cnlEZWxheTogMFxuICAgICAgfTtcbiAgICAgIGNvbnN0IGxvYWRlckNhbGxiYWNrcyA9IHtcbiAgICAgICAgb25TdWNjZXNzOiAocmVzcG9uc2UsIHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICAgIHJlc29sdmUocmVzcG9uc2UuZGF0YSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uRXJyb3I6IChyZXNwb25zZSwgY29udGV4LCBuZXR3b3JrRGV0YWlscywgc3RhdHMpID0+IHtcbiAgICAgICAgICByZWplY3QobmV3IEVNRUtleUVycm9yKHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5LRVlfU1lTVEVNX1NFUlZFUl9DRVJUSUZJQ0FURV9SRVFVRVNUX0ZBSUxFRCxcbiAgICAgICAgICAgIGZhdGFsOiB0cnVlLFxuICAgICAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgICAgICByZXNwb25zZTogX29iamVjdFNwcmVhZDIoe1xuICAgICAgICAgICAgICB1cmw6IGxvYWRlckNvbnRleHQudXJsLFxuICAgICAgICAgICAgICBkYXRhOiB1bmRlZmluZWRcbiAgICAgICAgICAgIH0sIHJlc3BvbnNlKVxuICAgICAgICAgIH0sIGBcIiR7a2V5U3lzdGVtfVwiIGNlcnRpZmljYXRlIHJlcXVlc3QgZmFpbGVkICgke3VybH0pLiBTdGF0dXM6ICR7cmVzcG9uc2UuY29kZX0gKCR7cmVzcG9uc2UudGV4dH0pYCkpO1xuICAgICAgICB9LFxuICAgICAgICBvblRpbWVvdXQ6IChzdGF0cywgY29udGV4dCwgbmV0d29ya0RldGFpbHMpID0+IHtcbiAgICAgICAgICByZWplY3QobmV3IEVNRUtleUVycm9yKHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5LRVlfU1lTVEVNX1NFUlZFUl9DRVJUSUZJQ0FURV9SRVFVRVNUX0ZBSUxFRCxcbiAgICAgICAgICAgIGZhdGFsOiB0cnVlLFxuICAgICAgICAgICAgbmV0d29ya0RldGFpbHMsXG4gICAgICAgICAgICByZXNwb25zZToge1xuICAgICAgICAgICAgICB1cmw6IGxvYWRlckNvbnRleHQudXJsLFxuICAgICAgICAgICAgICBkYXRhOiB1bmRlZmluZWRcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LCBgXCIke2tleVN5c3RlbX1cIiBjZXJ0aWZpY2F0ZSByZXF1ZXN0IHRpbWVkIG91dCAoJHt1cmx9KWApKTtcbiAgICAgICAgfSxcbiAgICAgICAgb25BYm9ydDogKHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoJ2Fib3J0ZWQnKSk7XG4gICAgICAgIH1cbiAgICAgIH07XG4gICAgICBjZXJ0TG9hZGVyLmxvYWQobG9hZGVyQ29udGV4dCwgbG9hZGVyQ29uZmlnLCBsb2FkZXJDYWxsYmFja3MpO1xuICAgIH0pO1xuICB9XG4gIHNldE1lZGlhS2V5c1NlcnZlckNlcnRpZmljYXRlKG1lZGlhS2V5cywga2V5U3lzdGVtLCBjZXJ0KSB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIG1lZGlhS2V5cy5zZXRTZXJ2ZXJDZXJ0aWZpY2F0ZShjZXJ0KS50aGVuKHN1Y2Nlc3MgPT4ge1xuICAgICAgICB0aGlzLmxvZyhgc2V0U2VydmVyQ2VydGlmaWNhdGUgJHtzdWNjZXNzID8gJ3N1Y2Nlc3MnIDogJ25vdCBzdXBwb3J0ZWQgYnkgQ0RNJ30gKCR7Y2VydCA9PSBudWxsID8gdm9pZCAwIDogY2VydC5ieXRlTGVuZ3RofSkgb24gXCIke2tleVN5c3RlbX1cImApO1xuICAgICAgICByZXNvbHZlKG1lZGlhS2V5cyk7XG4gICAgICB9KS5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgIHJlamVjdChuZXcgRU1FS2V5RXJyb3Ioe1xuICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9TRVJWRVJfQ0VSVElGSUNBVEVfVVBEQVRFX0ZBSUxFRCxcbiAgICAgICAgICBlcnJvcixcbiAgICAgICAgICBmYXRhbDogdHJ1ZVxuICAgICAgICB9LCBlcnJvci5tZXNzYWdlKSk7XG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuICByZW5ld0xpY2Vuc2UoY29udGV4dCwga2V5TWVzc2FnZSkge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3RMaWNlbnNlKGNvbnRleHQsIG5ldyBVaW50OEFycmF5KGtleU1lc3NhZ2UpKS50aGVuKGRhdGEgPT4ge1xuICAgICAgcmV0dXJuIHRoaXMudXBkYXRlS2V5U2Vzc2lvbihjb250ZXh0LCBuZXcgVWludDhBcnJheShkYXRhKSkuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgICB0aHJvdyBuZXcgRU1FS2V5RXJyb3Ioe1xuICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9TRVNTSU9OX1VQREFURV9GQUlMRUQsXG4gICAgICAgICAgZXJyb3IsXG4gICAgICAgICAgZmF0YWw6IHRydWVcbiAgICAgICAgfSwgZXJyb3IubWVzc2FnZSk7XG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuICB1bnBhY2tQbGF5UmVhZHlLZXlNZXNzYWdlKHhociwgbGljZW5zZUNoYWxsZW5nZSkge1xuICAgIC8vIE9uIEVkZ2UsIHRoZSByYXcgbGljZW5zZSBtZXNzYWdlIGlzIFVURi0xNi1lbmNvZGVkIFhNTC4gIFdlIG5lZWRcbiAgICAvLyB0byB1bnBhY2sgdGhlIENoYWxsZW5nZSBlbGVtZW50IChiYXNlNjQtZW5jb2RlZCBzdHJpbmcgY29udGFpbmluZyB0aGVcbiAgICAvLyBhY3R1YWwgbGljZW5zZSByZXF1ZXN0KSBhbmQgYW55IEh0dHBIZWFkZXIgZWxlbWVudHMgKHNlbnQgYXMgcmVxdWVzdFxuICAgIC8vIGhlYWRlcnMpLlxuICAgIC8vIEZvciBQbGF5UmVhZHkgQ0RNcywgd2UgbmVlZCB0byBkaWcgdGhlIENoYWxsZW5nZSBvdXQgb2YgdGhlIFhNTC5cbiAgICBjb25zdCB4bWxTdHJpbmcgPSBTdHJpbmcuZnJvbUNoYXJDb2RlLmFwcGx5KG51bGwsIG5ldyBVaW50MTZBcnJheShsaWNlbnNlQ2hhbGxlbmdlLmJ1ZmZlcikpO1xuICAgIGlmICgheG1sU3RyaW5nLmluY2x1ZGVzKCdQbGF5UmVhZHlLZXlNZXNzYWdlJykpIHtcbiAgICAgIC8vIFRoaXMgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgd3JhcHBlZCBtZXNzYWdlIGFzIG9uIEVkZ2UuICBTb21lXG4gICAgICAvLyBjbGllbnRzIGRvIG5vdCBuZWVkIHRoaXMgdW53cmFwcGluZywgc28gd2Ugd2lsbCBhc3N1bWUgdGhpcyBpcyBvbmUgb2ZcbiAgICAgIC8vIHRoZW0uICBOb3RlIHRoYXQgXCJ4bWxcIiBhdCB0aGlzIHBvaW50IHByb2JhYmx5IGxvb2tzIGxpa2UgcmFuZG9tXG4gICAgICAvLyBnYXJiYWdlLCBzaW5jZSB3ZSBpbnRlcnByZXRlZCBVVEYtOCBhcyBVVEYtMTYuXG4gICAgICB4aHIuc2V0UmVxdWVzdEhlYWRlcignQ29udGVudC1UeXBlJywgJ3RleHQveG1sOyBjaGFyc2V0PXV0Zi04Jyk7XG4gICAgICByZXR1cm4gbGljZW5zZUNoYWxsZW5nZTtcbiAgICB9XG4gICAgY29uc3Qga2V5TWVzc2FnZVhtbCA9IG5ldyBET01QYXJzZXIoKS5wYXJzZUZyb21TdHJpbmcoeG1sU3RyaW5nLCAnYXBwbGljYXRpb24veG1sJyk7XG4gICAgLy8gU2V0IHJlcXVlc3QgaGVhZGVycy5cbiAgICBjb25zdCBoZWFkZXJzID0ga2V5TWVzc2FnZVhtbC5xdWVyeVNlbGVjdG9yQWxsKCdIdHRwSGVhZGVyJyk7XG4gICAgaWYgKGhlYWRlcnMubGVuZ3RoID4gMCkge1xuICAgICAgbGV0IGhlYWRlcjtcbiAgICAgIGZvciAobGV0IGkgPSAwLCBsZW4gPSBoZWFkZXJzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIHZhciBfaGVhZGVyJHF1ZXJ5U2VsZWN0b3IsIF9oZWFkZXIkcXVlcnlTZWxlY3RvcjI7XG4gICAgICAgIGhlYWRlciA9IGhlYWRlcnNbaV07XG4gICAgICAgIGNvbnN0IG5hbWUgPSAoX2hlYWRlciRxdWVyeVNlbGVjdG9yID0gaGVhZGVyLnF1ZXJ5U2VsZWN0b3IoJ25hbWUnKSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9oZWFkZXIkcXVlcnlTZWxlY3Rvci50ZXh0Q29udGVudDtcbiAgICAgICAgY29uc3QgdmFsdWUgPSAoX2hlYWRlciRxdWVyeVNlbGVjdG9yMiA9IGhlYWRlci5xdWVyeVNlbGVjdG9yKCd2YWx1ZScpKSA9PSBudWxsID8gdm9pZCAwIDogX2hlYWRlciRxdWVyeVNlbGVjdG9yMi50ZXh0Q29udGVudDtcbiAgICAgICAgaWYgKG5hbWUgJiYgdmFsdWUpIHtcbiAgICAgICAgICB4aHIuc2V0UmVxdWVzdEhlYWRlcihuYW1lLCB2YWx1ZSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgY29uc3QgY2hhbGxlbmdlRWxlbWVudCA9IGtleU1lc3NhZ2VYbWwucXVlcnlTZWxlY3RvcignQ2hhbGxlbmdlJyk7XG4gICAgY29uc3QgY2hhbGxlbmdlVGV4dCA9IGNoYWxsZW5nZUVsZW1lbnQgPT0gbnVsbCA/IHZvaWQgMCA6IGNoYWxsZW5nZUVsZW1lbnQudGV4dENvbnRlbnQ7XG4gICAgaWYgKCFjaGFsbGVuZ2VUZXh0KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYENhbm5vdCBmaW5kIDxDaGFsbGVuZ2U+IGluIGtleSBtZXNzYWdlYCk7XG4gICAgfVxuICAgIHJldHVybiBzdHJUb1V0ZjhhcnJheShhdG9iKGNoYWxsZW5nZVRleHQpKTtcbiAgfVxuICBzZXR1cExpY2Vuc2VYSFIoeGhyLCB1cmwsIGtleXNMaXN0SXRlbSwgbGljZW5zZUNoYWxsZW5nZSkge1xuICAgIGNvbnN0IGxpY2Vuc2VYaHJTZXR1cCA9IHRoaXMuY29uZmlnLmxpY2Vuc2VYaHJTZXR1cDtcbiAgICBpZiAoIWxpY2Vuc2VYaHJTZXR1cCkge1xuICAgICAgeGhyLm9wZW4oJ1BPU1QnLCB1cmwsIHRydWUpO1xuICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSh7XG4gICAgICAgIHhocixcbiAgICAgICAgbGljZW5zZUNoYWxsZW5nZVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKS50aGVuKCgpID0+IHtcbiAgICAgIGlmICgha2V5c0xpc3RJdGVtLmRlY3J5cHRkYXRhKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignS2V5IHJlbW92ZWQnKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBsaWNlbnNlWGhyU2V0dXAuY2FsbCh0aGlzLmhscywgeGhyLCB1cmwsIGtleXNMaXN0SXRlbSwgbGljZW5zZUNoYWxsZW5nZSk7XG4gICAgfSkuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgaWYgKCFrZXlzTGlzdEl0ZW0uZGVjcnlwdGRhdGEpIHtcbiAgICAgICAgLy8gS2V5IHNlc3Npb24gcmVtb3ZlZC4gQ2FuY2VsIGxpY2Vuc2UgcmVxdWVzdC5cbiAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICB9XG4gICAgICAvLyBsZXQncyB0cnkgdG8gb3BlbiBiZWZvcmUgcnVubmluZyBzZXR1cFxuICAgICAgeGhyLm9wZW4oJ1BPU1QnLCB1cmwsIHRydWUpO1xuICAgICAgcmV0dXJuIGxpY2Vuc2VYaHJTZXR1cC5jYWxsKHRoaXMuaGxzLCB4aHIsIHVybCwga2V5c0xpc3RJdGVtLCBsaWNlbnNlQ2hhbGxlbmdlKTtcbiAgICB9KS50aGVuKGxpY2Vuc2VYaHJTZXR1cFJlc3VsdCA9PiB7XG4gICAgICAvLyBpZiBsaWNlbnNlWGhyU2V0dXAgZGlkIG5vdCB5ZXQgY2FsbCBvcGVuLCBsZXQncyBkbyBpdCBub3dcbiAgICAgIGlmICgheGhyLnJlYWR5U3RhdGUpIHtcbiAgICAgICAgeGhyLm9wZW4oJ1BPU1QnLCB1cmwsIHRydWUpO1xuICAgICAgfVxuICAgICAgY29uc3QgZmluYWxMaWNlbnNlQ2hhbGxlbmdlID0gbGljZW5zZVhoclNldHVwUmVzdWx0ID8gbGljZW5zZVhoclNldHVwUmVzdWx0IDogbGljZW5zZUNoYWxsZW5nZTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHhocixcbiAgICAgICAgbGljZW5zZUNoYWxsZW5nZTogZmluYWxMaWNlbnNlQ2hhbGxlbmdlXG4gICAgICB9O1xuICAgIH0pO1xuICB9XG4gIHJlcXVlc3RMaWNlbnNlKGtleVNlc3Npb25Db250ZXh0LCBsaWNlbnNlQ2hhbGxlbmdlKSB7XG4gICAgY29uc3Qga2V5TG9hZFBvbGljeSA9IHRoaXMuY29uZmlnLmtleUxvYWRQb2xpY3kuZGVmYXVsdDtcbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgdXJsID0gdGhpcy5nZXRMaWNlbnNlU2VydmVyVXJsKGtleVNlc3Npb25Db250ZXh0LmtleVN5c3RlbSk7XG4gICAgICB0aGlzLmxvZyhgU2VuZGluZyBsaWNlbnNlIHJlcXVlc3QgdG8gVVJMOiAke3VybH1gKTtcbiAgICAgIGNvbnN0IHhociA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpO1xuICAgICAgeGhyLnJlc3BvbnNlVHlwZSA9ICdhcnJheWJ1ZmZlcic7XG4gICAgICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gKCkgPT4ge1xuICAgICAgICBpZiAoIXRoaXMuaGxzIHx8ICFrZXlTZXNzaW9uQ29udGV4dC5tZWRpYUtleXNTZXNzaW9uKSB7XG4gICAgICAgICAgcmV0dXJuIHJlamVjdChuZXcgRXJyb3IoJ2ludmFsaWQgc3RhdGUnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHhoci5yZWFkeVN0YXRlID09PSA0KSB7XG4gICAgICAgICAgaWYgKHhoci5zdGF0dXMgPT09IDIwMCkge1xuICAgICAgICAgICAgdGhpcy5fcmVxdWVzdExpY2Vuc2VGYWlsdXJlQ291bnQgPSAwO1xuICAgICAgICAgICAgbGV0IGRhdGEgPSB4aHIucmVzcG9uc2U7XG4gICAgICAgICAgICB0aGlzLmxvZyhgTGljZW5zZSByZWNlaXZlZCAke2RhdGEgaW5zdGFuY2VvZiBBcnJheUJ1ZmZlciA/IGRhdGEuYnl0ZUxlbmd0aCA6IGRhdGF9YCk7XG4gICAgICAgICAgICBjb25zdCBsaWNlbnNlUmVzcG9uc2VDYWxsYmFjayA9IHRoaXMuY29uZmlnLmxpY2Vuc2VSZXNwb25zZUNhbGxiYWNrO1xuICAgICAgICAgICAgaWYgKGxpY2Vuc2VSZXNwb25zZUNhbGxiYWNrKSB7XG4gICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgZGF0YSA9IGxpY2Vuc2VSZXNwb25zZUNhbGxiYWNrLmNhbGwodGhpcy5obHMsIHhociwgdXJsLCBrZXlTZXNzaW9uQ29udGV4dCk7XG4gICAgICAgICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5lcnJvcihlcnJvcik7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJlc29sdmUoZGF0YSk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IHJldHJ5Q29uZmlnID0ga2V5TG9hZFBvbGljeS5lcnJvclJldHJ5O1xuICAgICAgICAgICAgY29uc3QgbWF4TnVtUmV0cnkgPSByZXRyeUNvbmZpZyA/IHJldHJ5Q29uZmlnLm1heE51bVJldHJ5IDogMDtcbiAgICAgICAgICAgIHRoaXMuX3JlcXVlc3RMaWNlbnNlRmFpbHVyZUNvdW50Kys7XG4gICAgICAgICAgICBpZiAodGhpcy5fcmVxdWVzdExpY2Vuc2VGYWlsdXJlQ291bnQgPiBtYXhOdW1SZXRyeSB8fCB4aHIuc3RhdHVzID49IDQwMCAmJiB4aHIuc3RhdHVzIDwgNTAwKSB7XG4gICAgICAgICAgICAgIHJlamVjdChuZXcgRU1FS2V5RXJyb3Ioe1xuICAgICAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuS0VZX1NZU1RFTV9FUlJPUixcbiAgICAgICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuS0VZX1NZU1RFTV9MSUNFTlNFX1JFUVVFU1RfRkFJTEVELFxuICAgICAgICAgICAgICAgIGZhdGFsOiB0cnVlLFxuICAgICAgICAgICAgICAgIG5ldHdvcmtEZXRhaWxzOiB4aHIsXG4gICAgICAgICAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICAgICAgICAgIHVybCxcbiAgICAgICAgICAgICAgICAgIGRhdGE6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgICAgICAgIGNvZGU6IHhoci5zdGF0dXMsXG4gICAgICAgICAgICAgICAgICB0ZXh0OiB4aHIuc3RhdHVzVGV4dFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfSwgYExpY2Vuc2UgUmVxdWVzdCBYSFIgZmFpbGVkICgke3VybH0pLiBTdGF0dXM6ICR7eGhyLnN0YXR1c30gKCR7eGhyLnN0YXR1c1RleHR9KWApKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIGNvbnN0IGF0dGVtcHRzTGVmdCA9IG1heE51bVJldHJ5IC0gdGhpcy5fcmVxdWVzdExpY2Vuc2VGYWlsdXJlQ291bnQgKyAxO1xuICAgICAgICAgICAgICB0aGlzLndhcm4oYFJldHJ5aW5nIGxpY2Vuc2UgcmVxdWVzdCwgJHthdHRlbXB0c0xlZnR9IGF0dGVtcHRzIGxlZnRgKTtcbiAgICAgICAgICAgICAgdGhpcy5yZXF1ZXN0TGljZW5zZShrZXlTZXNzaW9uQ29udGV4dCwgbGljZW5zZUNoYWxsZW5nZSkudGhlbihyZXNvbHZlLCByZWplY3QpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfTtcbiAgICAgIGlmIChrZXlTZXNzaW9uQ29udGV4dC5saWNlbnNlWGhyICYmIGtleVNlc3Npb25Db250ZXh0LmxpY2Vuc2VYaHIucmVhZHlTdGF0ZSAhPT0gWE1MSHR0cFJlcXVlc3QuRE9ORSkge1xuICAgICAgICBrZXlTZXNzaW9uQ29udGV4dC5saWNlbnNlWGhyLmFib3J0KCk7XG4gICAgICB9XG4gICAgICBrZXlTZXNzaW9uQ29udGV4dC5saWNlbnNlWGhyID0geGhyO1xuICAgICAgdGhpcy5zZXR1cExpY2Vuc2VYSFIoeGhyLCB1cmwsIGtleVNlc3Npb25Db250ZXh0LCBsaWNlbnNlQ2hhbGxlbmdlKS50aGVuKCh7XG4gICAgICAgIHhocixcbiAgICAgICAgbGljZW5zZUNoYWxsZW5nZVxuICAgICAgfSkgPT4ge1xuICAgICAgICBpZiAoa2V5U2Vzc2lvbkNvbnRleHQua2V5U3lzdGVtID09IEtleVN5c3RlbXMuUExBWVJFQURZKSB7XG4gICAgICAgICAgbGljZW5zZUNoYWxsZW5nZSA9IHRoaXMudW5wYWNrUGxheVJlYWR5S2V5TWVzc2FnZSh4aHIsIGxpY2Vuc2VDaGFsbGVuZ2UpO1xuICAgICAgICB9XG4gICAgICAgIHhoci5zZW5kKGxpY2Vuc2VDaGFsbGVuZ2UpO1xuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cbiAgb25NZWRpYUF0dGFjaGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgaWYgKCF0aGlzLmNvbmZpZy5lbWVFbmFibGVkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IG1lZGlhID0gZGF0YS5tZWRpYTtcblxuICAgIC8vIGtlZXAgcmVmZXJlbmNlIG9mIG1lZGlhXG4gICAgdGhpcy5tZWRpYSA9IG1lZGlhO1xuICAgIG1lZGlhLmFkZEV2ZW50TGlzdGVuZXIoJ2VuY3J5cHRlZCcsIHRoaXMub25NZWRpYUVuY3J5cHRlZCk7XG4gICAgbWVkaWEuYWRkRXZlbnRMaXN0ZW5lcignd2FpdGluZ2ZvcmtleScsIHRoaXMub25XYWl0aW5nRm9yS2V5KTtcbiAgfVxuICBvbk1lZGlhRGV0YWNoZWQoKSB7XG4gICAgY29uc3QgbWVkaWEgPSB0aGlzLm1lZGlhO1xuICAgIGNvbnN0IG1lZGlhS2V5c0xpc3QgPSB0aGlzLm1lZGlhS2V5U2Vzc2lvbnM7XG4gICAgaWYgKG1lZGlhKSB7XG4gICAgICBtZWRpYS5yZW1vdmVFdmVudExpc3RlbmVyKCdlbmNyeXB0ZWQnLCB0aGlzLm9uTWVkaWFFbmNyeXB0ZWQpO1xuICAgICAgbWVkaWEucmVtb3ZlRXZlbnRMaXN0ZW5lcignd2FpdGluZ2ZvcmtleScsIHRoaXMub25XYWl0aW5nRm9yS2V5KTtcbiAgICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIH1cbiAgICB0aGlzLl9yZXF1ZXN0TGljZW5zZUZhaWx1cmVDb3VudCA9IDA7XG4gICAgdGhpcy5zZXRNZWRpYUtleXNRdWV1ZSA9IFtdO1xuICAgIHRoaXMubWVkaWFLZXlTZXNzaW9ucyA9IFtdO1xuICAgIHRoaXMua2V5SWRUb0tleVNlc3Npb25Qcm9taXNlID0ge307XG4gICAgTGV2ZWxLZXkuY2xlYXJLZXlVcmlUb0tleUlkTWFwKCk7XG5cbiAgICAvLyBDbG9zZSBhbGwgc2Vzc2lvbnMgYW5kIHJlbW92ZSBtZWRpYSBrZXlzIGZyb20gdGhlIHZpZGVvIGVsZW1lbnQuXG4gICAgY29uc3Qga2V5U2Vzc2lvbkNvdW50ID0gbWVkaWFLZXlzTGlzdC5sZW5ndGg7XG4gICAgRU1FQ29udHJvbGxlci5DRE1DbGVhbnVwUHJvbWlzZSA9IFByb21pc2UuYWxsKG1lZGlhS2V5c0xpc3QubWFwKG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQgPT4gdGhpcy5yZW1vdmVTZXNzaW9uKG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQpKS5jb25jYXQobWVkaWEgPT0gbnVsbCA/IHZvaWQgMCA6IG1lZGlhLnNldE1lZGlhS2V5cyhudWxsKS5jYXRjaChlcnJvciA9PiB7XG4gICAgICB0aGlzLmxvZyhgQ291bGQgbm90IGNsZWFyIG1lZGlhIGtleXM6ICR7ZXJyb3J9YCk7XG4gICAgfSkpKS50aGVuKCgpID0+IHtcbiAgICAgIGlmIChrZXlTZXNzaW9uQ291bnQpIHtcbiAgICAgICAgdGhpcy5sb2coJ2ZpbmlzaGVkIGNsb3Npbmcga2V5IHNlc3Npb25zIGFuZCBjbGVhcmluZyBtZWRpYSBrZXlzJyk7XG4gICAgICAgIG1lZGlhS2V5c0xpc3QubGVuZ3RoID0gMDtcbiAgICAgIH1cbiAgICB9KS5jYXRjaChlcnJvciA9PiB7XG4gICAgICB0aGlzLmxvZyhgQ291bGQgbm90IGNsb3NlIHNlc3Npb25zIGFuZCBjbGVhciBtZWRpYSBrZXlzOiAke2Vycm9yfWApO1xuICAgIH0pO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKCkge1xuICAgIHRoaXMua2V5Rm9ybWF0UHJvbWlzZSA9IG51bGw7XG4gIH1cbiAgb25NYW5pZmVzdExvYWRlZChldmVudCwge1xuICAgIHNlc3Npb25LZXlzXG4gIH0pIHtcbiAgICBpZiAoIXNlc3Npb25LZXlzIHx8ICF0aGlzLmNvbmZpZy5lbWVFbmFibGVkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghdGhpcy5rZXlGb3JtYXRQcm9taXNlKSB7XG4gICAgICBjb25zdCBrZXlGb3JtYXRzID0gc2Vzc2lvbktleXMucmVkdWNlKChmb3JtYXRzLCBzZXNzaW9uS2V5KSA9PiB7XG4gICAgICAgIGlmIChmb3JtYXRzLmluZGV4T2Yoc2Vzc2lvbktleS5rZXlGb3JtYXQpID09PSAtMSkge1xuICAgICAgICAgIGZvcm1hdHMucHVzaChzZXNzaW9uS2V5LmtleUZvcm1hdCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGZvcm1hdHM7XG4gICAgICB9LCBbXSk7XG4gICAgICB0aGlzLmxvZyhgU2VsZWN0aW5nIGtleS1zeXN0ZW0gZnJvbSBzZXNzaW9uLWtleXMgJHtrZXlGb3JtYXRzLmpvaW4oJywgJyl9YCk7XG4gICAgICB0aGlzLmtleUZvcm1hdFByb21pc2UgPSB0aGlzLmdldEtleUZvcm1hdFByb21pc2Uoa2V5Rm9ybWF0cyk7XG4gICAgfVxuICB9XG4gIHJlbW92ZVNlc3Npb24obWVkaWFLZXlTZXNzaW9uQ29udGV4dCkge1xuICAgIGNvbnN0IHtcbiAgICAgIG1lZGlhS2V5c1Nlc3Npb24sXG4gICAgICBsaWNlbnNlWGhyXG4gICAgfSA9IG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQ7XG4gICAgaWYgKG1lZGlhS2V5c1Nlc3Npb24pIHtcbiAgICAgIHRoaXMubG9nKGBSZW1vdmUgbGljZW5zZXMgYW5kIGtleXMgYW5kIGNsb3NlIHNlc3Npb24gJHttZWRpYUtleXNTZXNzaW9uLnNlc3Npb25JZH1gKTtcbiAgICAgIGlmIChtZWRpYUtleVNlc3Npb25Db250ZXh0Ll9vbm1lc3NhZ2UpIHtcbiAgICAgICAgbWVkaWFLZXlzU2Vzc2lvbi5yZW1vdmVFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgbWVkaWFLZXlTZXNzaW9uQ29udGV4dC5fb25tZXNzYWdlKTtcbiAgICAgICAgbWVkaWFLZXlTZXNzaW9uQ29udGV4dC5fb25tZXNzYWdlID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgICAgaWYgKG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQuX29ua2V5c3RhdHVzZXNjaGFuZ2UpIHtcbiAgICAgICAgbWVkaWFLZXlzU2Vzc2lvbi5yZW1vdmVFdmVudExpc3RlbmVyKCdrZXlzdGF0dXNlc2NoYW5nZScsIG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQuX29ua2V5c3RhdHVzZXNjaGFuZ2UpO1xuICAgICAgICBtZWRpYUtleVNlc3Npb25Db250ZXh0Ll9vbmtleXN0YXR1c2VzY2hhbmdlID0gdW5kZWZpbmVkO1xuICAgICAgfVxuICAgICAgaWYgKGxpY2Vuc2VYaHIgJiYgbGljZW5zZVhoci5yZWFkeVN0YXRlICE9PSBYTUxIdHRwUmVxdWVzdC5ET05FKSB7XG4gICAgICAgIGxpY2Vuc2VYaHIuYWJvcnQoKTtcbiAgICAgIH1cbiAgICAgIG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQubWVkaWFLZXlzU2Vzc2lvbiA9IG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQuZGVjcnlwdGRhdGEgPSBtZWRpYUtleVNlc3Npb25Db250ZXh0LmxpY2Vuc2VYaHIgPSB1bmRlZmluZWQ7XG4gICAgICBjb25zdCBpbmRleCA9IHRoaXMubWVkaWFLZXlTZXNzaW9ucy5pbmRleE9mKG1lZGlhS2V5U2Vzc2lvbkNvbnRleHQpO1xuICAgICAgaWYgKGluZGV4ID4gLTEpIHtcbiAgICAgICAgdGhpcy5tZWRpYUtleVNlc3Npb25zLnNwbGljZShpbmRleCwgMSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gbWVkaWFLZXlzU2Vzc2lvbi5yZW1vdmUoKS5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgIHRoaXMubG9nKGBDb3VsZCBub3QgcmVtb3ZlIHNlc3Npb246ICR7ZXJyb3J9YCk7XG4gICAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgICAgcmV0dXJuIG1lZGlhS2V5c1Nlc3Npb24uY2xvc2UoKTtcbiAgICAgIH0pLmNhdGNoKGVycm9yID0+IHtcbiAgICAgICAgdGhpcy5sb2coYENvdWxkIG5vdCBjbG9zZSBzZXNzaW9uOiAke2Vycm9yfWApO1xuICAgICAgfSk7XG4gICAgfVxuICB9XG59XG5FTUVDb250cm9sbGVyLkNETUNsZWFudXBQcm9taXNlID0gdm9pZCAwO1xuY2xhc3MgRU1FS2V5RXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKGRhdGEsIG1lc3NhZ2UpIHtcbiAgICBzdXBlcihtZXNzYWdlKTtcbiAgICB0aGlzLmRhdGEgPSB2b2lkIDA7XG4gICAgZGF0YS5lcnJvciB8fCAoZGF0YS5lcnJvciA9IG5ldyBFcnJvcihtZXNzYWdlKSk7XG4gICAgdGhpcy5kYXRhID0gZGF0YTtcbiAgICBkYXRhLmVyciA9IGRhdGEuZXJyb3I7XG4gIH1cbn1cblxuLyoqXG4gKiBDb21tb24gTWVkaWEgT2JqZWN0IFR5cGVcbiAqXG4gKiBAZ3JvdXAgQ01DRFxuICogQGdyb3VwIENNU0RcbiAqXG4gKiBAYmV0YVxuICovXG52YXIgQ21PYmplY3RUeXBlO1xuKGZ1bmN0aW9uIChDbU9iamVjdFR5cGUpIHtcbiAgLyoqXG4gICAqIHRleHQgZmlsZSwgc3VjaCBhcyBhIG1hbmlmZXN0IG9yIHBsYXlsaXN0XG4gICAqL1xuICBDbU9iamVjdFR5cGVbXCJNQU5JRkVTVFwiXSA9IFwibVwiO1xuICAvKipcbiAgICogYXVkaW8gb25seVxuICAgKi9cbiAgQ21PYmplY3RUeXBlW1wiQVVESU9cIl0gPSBcImFcIjtcbiAgLyoqXG4gICAqIHZpZGVvIG9ubHlcbiAgICovXG4gIENtT2JqZWN0VHlwZVtcIlZJREVPXCJdID0gXCJ2XCI7XG4gIC8qKlxuICAgKiBtdXhlZCBhdWRpbyBhbmQgdmlkZW9cbiAgICovXG4gIENtT2JqZWN0VHlwZVtcIk1VWEVEXCJdID0gXCJhdlwiO1xuICAvKipcbiAgICogaW5pdCBzZWdtZW50XG4gICAqL1xuICBDbU9iamVjdFR5cGVbXCJJTklUXCJdID0gXCJpXCI7XG4gIC8qKlxuICAgKiBjYXB0aW9uIG9yIHN1YnRpdGxlXG4gICAqL1xuICBDbU9iamVjdFR5cGVbXCJDQVBUSU9OXCJdID0gXCJjXCI7XG4gIC8qKlxuICAgKiBJU09CTUZGIHRpbWVkIHRleHQgdHJhY2tcbiAgICovXG4gIENtT2JqZWN0VHlwZVtcIlRJTUVEX1RFWFRcIl0gPSBcInR0XCI7XG4gIC8qKlxuICAgKiBjcnlwdG9ncmFwaGljIGtleSwgbGljZW5zZSBvciBjZXJ0aWZpY2F0ZS5cbiAgICovXG4gIENtT2JqZWN0VHlwZVtcIktFWVwiXSA9IFwia1wiO1xuICAvKipcbiAgICogb3RoZXJcbiAgICovXG4gIENtT2JqZWN0VHlwZVtcIk9USEVSXCJdID0gXCJvXCI7XG59KShDbU9iamVjdFR5cGUgfHwgKENtT2JqZWN0VHlwZSA9IHt9KSk7XG5cbi8qKlxuICogQ29tbW9uIE1lZGlhIFN0cmVhbWluZyBGb3JtYXRcbiAqXG4gKiBAZ3JvdXAgQ01DRFxuICogQGdyb3VwIENNU0RcbiAqXG4gKiBAYmV0YVxuICovXG52YXIgQ21TdHJlYW1pbmdGb3JtYXQ7XG4oZnVuY3Rpb24gKENtU3RyZWFtaW5nRm9ybWF0KSB7XG4gIC8qKlxuICAgKiBNUEVHIERBU0hcbiAgICovXG4gIENtU3RyZWFtaW5nRm9ybWF0W1wiREFTSFwiXSA9IFwiZFwiO1xuICAvKipcbiAgICogSFRUUCBMaXZlIFN0cmVhbWluZyAoSExTKVxuICAgKi9cbiAgQ21TdHJlYW1pbmdGb3JtYXRbXCJITFNcIl0gPSBcImhcIjtcbiAgLyoqXG4gICAqIFNtb290aCBTdHJlYW1pbmdcbiAgICovXG4gIENtU3RyZWFtaW5nRm9ybWF0W1wiU01PT1RIXCJdID0gXCJzXCI7XG4gIC8qKlxuICAgKiBPdGhlclxuICAgKi9cbiAgQ21TdHJlYW1pbmdGb3JtYXRbXCJPVEhFUlwiXSA9IFwib1wiO1xufSkoQ21TdHJlYW1pbmdGb3JtYXQgfHwgKENtU3RyZWFtaW5nRm9ybWF0ID0ge30pKTtcblxuLyoqXG4gKiBDTUNEIGhlYWRlciBmaWVsZHMuXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqXG4gKiBAYmV0YVxuICovXG52YXIgQ21jZEhlYWRlckZpZWxkO1xuKGZ1bmN0aW9uIChDbWNkSGVhZGVyRmllbGQpIHtcbiAgLyoqXG4gICAqIGtleXMgd2hvc2UgdmFsdWVzIHZhcnkgd2l0aCB0aGUgb2JqZWN0IGJlaW5nIHJlcXVlc3RlZC5cbiAgICovXG4gIENtY2RIZWFkZXJGaWVsZFtcIk9CSkVDVFwiXSA9IFwiQ01DRC1PYmplY3RcIjtcbiAgLyoqXG4gICAqIGtleXMgd2hvc2UgdmFsdWVzIHZhcnkgd2l0aCBlYWNoIHJlcXVlc3QuXG4gICAqL1xuICBDbWNkSGVhZGVyRmllbGRbXCJSRVFVRVNUXCJdID0gXCJDTUNELVJlcXVlc3RcIjtcbiAgLyoqXG4gICAqIGtleXMgd2hvc2UgdmFsdWVzIGFyZSBleHBlY3RlZCB0byBiZSBpbnZhcmlhbnQgb3ZlciB0aGUgbGlmZSBvZiB0aGUgc2Vzc2lvbi5cbiAgICovXG4gIENtY2RIZWFkZXJGaWVsZFtcIlNFU1NJT05cIl0gPSBcIkNNQ0QtU2Vzc2lvblwiO1xuICAvKipcbiAgICoga2V5cyB3aG9zZSB2YWx1ZXMgZG8gbm90IHZhcnkgd2l0aCBldmVyeSByZXF1ZXN0IG9yIG9iamVjdC5cbiAgICovXG4gIENtY2RIZWFkZXJGaWVsZFtcIlNUQVRVU1wiXSA9IFwiQ01DRC1TdGF0dXNcIjtcbn0pKENtY2RIZWFkZXJGaWVsZCB8fCAoQ21jZEhlYWRlckZpZWxkID0ge30pKTtcblxuLyoqXG4gKiBUaGUgbWFwIG9mIENNQ0QgaGVhZGVyIGZpZWxkcyB0byBvZmZpY2lhbCBDTUNEIGtleXMuXG4gKlxuICogQGludGVybmFsXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqL1xuY29uc3QgQ21jZEhlYWRlck1hcCA9IHtcbiAgW0NtY2RIZWFkZXJGaWVsZC5PQkpFQ1RdOiBbJ2JyJywgJ2QnLCAnb3QnLCAndGInXSxcbiAgW0NtY2RIZWFkZXJGaWVsZC5SRVFVRVNUXTogWydibCcsICdkbCcsICdtdHAnLCAnbm9yJywgJ25ycicsICdzdSddLFxuICBbQ21jZEhlYWRlckZpZWxkLlNFU1NJT05dOiBbJ2NpZCcsICdwcicsICdzZicsICdzaWQnLCAnc3QnLCAndiddLFxuICBbQ21jZEhlYWRlckZpZWxkLlNUQVRVU106IFsnYnMnLCAncnRwJ11cbn07XG5cbi8qKlxuICogU3RydWN0dXJlZCBGaWVsZCBJdGVtXG4gKlxuICogQGdyb3VwIFN0cnVjdHVyZWQgRmllbGRcbiAqXG4gKiBAYmV0YVxuICovXG5jbGFzcyBTZkl0ZW0ge1xuICBjb25zdHJ1Y3Rvcih2YWx1ZSwgcGFyYW1zKSB7XG4gICAgdGhpcy52YWx1ZSA9IHZvaWQgMDtcbiAgICB0aGlzLnBhcmFtcyA9IHZvaWQgMDtcbiAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHtcbiAgICAgIHZhbHVlID0gdmFsdWUubWFwKHYgPT4gdiBpbnN0YW5jZW9mIFNmSXRlbSA/IHYgOiBuZXcgU2ZJdGVtKHYpKTtcbiAgICB9XG4gICAgdGhpcy52YWx1ZSA9IHZhbHVlO1xuICAgIHRoaXMucGFyYW1zID0gcGFyYW1zO1xuICB9XG59XG5cbi8qKlxuICogQSBjbGFzcyB0byByZXByZXNlbnQgc3RydWN0dXJlZCBmaWVsZCB0b2tlbnMgd2hlbiBgU3ltYm9sYCBpcyBub3QgYXZhaWxhYmxlLlxuICpcbiAqIEBncm91cCBTdHJ1Y3R1cmVkIEZpZWxkXG4gKlxuICogQGJldGFcbiAqL1xuY2xhc3MgU2ZUb2tlbiB7XG4gIGNvbnN0cnVjdG9yKGRlc2NyaXB0aW9uKSB7XG4gICAgdGhpcy5kZXNjcmlwdGlvbiA9IHZvaWQgMDtcbiAgICB0aGlzLmRlc2NyaXB0aW9uID0gZGVzY3JpcHRpb247XG4gIH1cbn1cblxuY29uc3QgRElDVCA9ICdEaWN0JztcblxuZnVuY3Rpb24gZm9ybWF0KHZhbHVlKSB7XG4gIGlmIChBcnJheS5pc0FycmF5KHZhbHVlKSkge1xuICAgIHJldHVybiBKU09OLnN0cmluZ2lmeSh2YWx1ZSk7XG4gIH1cbiAgaWYgKHZhbHVlIGluc3RhbmNlb2YgTWFwKSB7XG4gICAgcmV0dXJuICdNYXB7fSc7XG4gIH1cbiAgaWYgKHZhbHVlIGluc3RhbmNlb2YgU2V0KSB7XG4gICAgcmV0dXJuICdTZXR7fSc7XG4gIH1cbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcpIHtcbiAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkodmFsdWUpO1xuICB9XG4gIHJldHVybiBTdHJpbmcodmFsdWUpO1xufVxuZnVuY3Rpb24gdGhyb3dFcnJvcihhY3Rpb24sIHNyYywgdHlwZSwgY2F1c2UpIHtcbiAgcmV0dXJuIG5ldyBFcnJvcihgZmFpbGVkIHRvICR7YWN0aW9ufSBcIiR7Zm9ybWF0KHNyYyl9XCIgYXMgJHt0eXBlfWAsIHtcbiAgICBjYXVzZVxuICB9KTtcbn1cblxuY29uc3QgQkFSRV9JVEVNID0gJ0JhcmUgSXRlbSc7XG5cbmNvbnN0IEJPT0xFQU4gPSAnQm9vbGVhbic7XG5cbmNvbnN0IEJZVEVTID0gJ0J5dGUgU2VxdWVuY2UnO1xuXG5jb25zdCBERUNJTUFMID0gJ0RlY2ltYWwnO1xuXG5jb25zdCBJTlRFR0VSID0gJ0ludGVnZXInO1xuXG5mdW5jdGlvbiBpc0ludmFsaWRJbnQodmFsdWUpIHtcbiAgcmV0dXJuIHZhbHVlIDwgLTk5OTk5OTk5OTk5OTk5OSB8fCA5OTk5OTk5OTk5OTk5OTkgPCB2YWx1ZTtcbn1cblxuY29uc3QgU1RSSU5HX1JFR0VYID0gL1tcXHgwMC1cXHgxZlxceDdmXSsvOyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIG5vLWNvbnRyb2wtcmVnZXhcblxuY29uc3QgVE9LRU4gPSAnVG9rZW4nO1xuXG5jb25zdCBLRVkgPSAnS2V5JztcblxuZnVuY3Rpb24gc2VyaWFsaXplRXJyb3Ioc3JjLCB0eXBlLCBjYXVzZSkge1xuICByZXR1cm4gdGhyb3dFcnJvcignc2VyaWFsaXplJywgc3JjLCB0eXBlLCBjYXVzZSk7XG59XG5cbi8vIDQuMS45LiAgU2VyaWFsaXppbmcgYSBCb29sZWFuXG4vL1xuLy8gR2l2ZW4gYSBCb29sZWFuIGFzIGlucHV0X2Jvb2xlYW4sIHJldHVybiBhbiBBU0NJSSBzdHJpbmcgc3VpdGFibGUgZm9yXG4vLyB1c2UgaW4gYSBIVFRQIGZpZWxkIHZhbHVlLlxuLy9cbi8vIDEuICBJZiBpbnB1dF9ib29sZWFuIGlzIG5vdCBhIGJvb2xlYW4sIGZhaWwgc2VyaWFsaXphdGlvbi5cbi8vXG4vLyAyLiAgTGV0IG91dHB1dCBiZSBhbiBlbXB0eSBzdHJpbmcuXG4vL1xuLy8gMy4gIEFwcGVuZCBcIj9cIiB0byBvdXRwdXQuXG4vL1xuLy8gNC4gIElmIGlucHV0X2Jvb2xlYW4gaXMgdHJ1ZSwgYXBwZW5kIFwiMVwiIHRvIG91dHB1dC5cbi8vXG4vLyA1LiAgSWYgaW5wdXRfYm9vbGVhbiBpcyBmYWxzZSwgYXBwZW5kIFwiMFwiIHRvIG91dHB1dC5cbi8vXG4vLyA2LiAgUmV0dXJuIG91dHB1dC5cbmZ1bmN0aW9uIHNlcmlhbGl6ZUJvb2xlYW4odmFsdWUpIHtcbiAgaWYgKHR5cGVvZiB2YWx1ZSAhPT0gJ2Jvb2xlYW4nKSB7XG4gICAgdGhyb3cgc2VyaWFsaXplRXJyb3IodmFsdWUsIEJPT0xFQU4pO1xuICB9XG4gIHJldHVybiB2YWx1ZSA/ICc/MScgOiAnPzAnO1xufVxuXG4vKipcbiAqIEVuY29kZXMgYmluYXJ5IGRhdGEgdG8gYmFzZTY0XG4gKlxuICogQHBhcmFtIGJpbmFyeSAtIFRoZSBiaW5hcnkgZGF0YSB0byBlbmNvZGVcbiAqIEByZXR1cm5zIFRoZSBiYXNlNjQgZW5jb2RlZCBzdHJpbmdcbiAqXG4gKiBAZ3JvdXAgVXRpbHNcbiAqXG4gKiBAYmV0YVxuICovXG5mdW5jdGlvbiBiYXNlNjRlbmNvZGUoYmluYXJ5KSB7XG4gIHJldHVybiBidG9hKFN0cmluZy5mcm9tQ2hhckNvZGUoLi4uYmluYXJ5KSk7XG59XG5cbi8vIDQuMS44LiAgU2VyaWFsaXppbmcgYSBCeXRlIFNlcXVlbmNlXG4vL1xuLy8gR2l2ZW4gYSBCeXRlIFNlcXVlbmNlIGFzIGlucHV0X2J5dGVzLCByZXR1cm4gYW4gQVNDSUkgc3RyaW5nIHN1aXRhYmxlXG4vLyBmb3IgdXNlIGluIGEgSFRUUCBmaWVsZCB2YWx1ZS5cbi8vXG4vLyAxLiAgSWYgaW5wdXRfYnl0ZXMgaXMgbm90IGEgc2VxdWVuY2Ugb2YgYnl0ZXMsIGZhaWwgc2VyaWFsaXphdGlvbi5cbi8vXG4vLyAyLiAgTGV0IG91dHB1dCBiZSBhbiBlbXB0eSBzdHJpbmcuXG4vL1xuLy8gMy4gIEFwcGVuZCBcIjpcIiB0byBvdXRwdXQuXG4vL1xuLy8gNC4gIEFwcGVuZCB0aGUgcmVzdWx0IG9mIGJhc2U2NC1lbmNvZGluZyBpbnB1dF9ieXRlcyBhcyBwZXJcbi8vICAgICBbUkZDNDY0OF0sIFNlY3Rpb24gNCwgdGFraW5nIGFjY291bnQgb2YgdGhlIHJlcXVpcmVtZW50cyBiZWxvdy5cbi8vXG4vLyA1LiAgQXBwZW5kIFwiOlwiIHRvIG91dHB1dC5cbi8vXG4vLyA2LiAgUmV0dXJuIG91dHB1dC5cbi8vXG4vLyBUaGUgZW5jb2RlZCBkYXRhIGlzIHJlcXVpcmVkIHRvIGJlIHBhZGRlZCB3aXRoIFwiPVwiLCBhcyBwZXIgW1JGQzQ2NDhdLFxuLy8gU2VjdGlvbiAzLjIuXG4vL1xuLy8gTGlrZXdpc2UsIGVuY29kZWQgZGF0YSBTSE9VTEQgaGF2ZSBwYWQgYml0cyBzZXQgdG8gemVybywgYXMgcGVyXG4vLyBbUkZDNDY0OF0sIFNlY3Rpb24gMy41LCB1bmxlc3MgaXQgaXMgbm90IHBvc3NpYmxlIHRvIGRvIHNvIGR1ZSB0b1xuLy8gaW1wbGVtZW50YXRpb24gY29uc3RyYWludHMuXG5mdW5jdGlvbiBzZXJpYWxpemVCeXRlU2VxdWVuY2UodmFsdWUpIHtcbiAgaWYgKEFycmF5QnVmZmVyLmlzVmlldyh2YWx1ZSkgPT09IGZhbHNlKSB7XG4gICAgdGhyb3cgc2VyaWFsaXplRXJyb3IodmFsdWUsIEJZVEVTKTtcbiAgfVxuICByZXR1cm4gYDoke2Jhc2U2NGVuY29kZSh2YWx1ZSl9OmA7XG59XG5cbi8vIDQuMS40LiAgU2VyaWFsaXppbmcgYW4gSW50ZWdlclxuLy9cbi8vIEdpdmVuIGFuIEludGVnZXIgYXMgaW5wdXRfaW50ZWdlciwgcmV0dXJuIGFuIEFTQ0lJIHN0cmluZyBzdWl0YWJsZVxuLy8gZm9yIHVzZSBpbiBhIEhUVFAgZmllbGQgdmFsdWUuXG4vL1xuLy8gMS4gIElmIGlucHV0X2ludGVnZXIgaXMgbm90IGFuIGludGVnZXIgaW4gdGhlIHJhbmdlIG9mXG4vLyAgICAgLTk5OSw5OTksOTk5LDk5OSw5OTkgdG8gOTk5LDk5OSw5OTksOTk5LDk5OSBpbmNsdXNpdmUsIGZhaWxcbi8vICAgICBzZXJpYWxpemF0aW9uLlxuLy9cbi8vIDIuICBMZXQgb3V0cHV0IGJlIGFuIGVtcHR5IHN0cmluZy5cbi8vXG4vLyAzLiAgSWYgaW5wdXRfaW50ZWdlciBpcyBsZXNzIHRoYW4gKGJ1dCBub3QgZXF1YWwgdG8pIDAsIGFwcGVuZCBcIi1cIiB0b1xuLy8gICAgIG91dHB1dC5cbi8vXG4vLyA0LiAgQXBwZW5kIGlucHV0X2ludGVnZXIncyBudW1lcmljIHZhbHVlIHJlcHJlc2VudGVkIGluIGJhc2UgMTAgdXNpbmdcbi8vICAgICBvbmx5IGRlY2ltYWwgZGlnaXRzIHRvIG91dHB1dC5cbi8vXG4vLyA1LiAgUmV0dXJuIG91dHB1dC5cbmZ1bmN0aW9uIHNlcmlhbGl6ZUludGVnZXIodmFsdWUpIHtcbiAgaWYgKGlzSW52YWxpZEludCh2YWx1ZSkpIHtcbiAgICB0aHJvdyBzZXJpYWxpemVFcnJvcih2YWx1ZSwgSU5URUdFUik7XG4gIH1cbiAgcmV0dXJuIHZhbHVlLnRvU3RyaW5nKCk7XG59XG5cbi8vIDQuMS4xMC4gIFNlcmlhbGl6aW5nIGEgRGF0ZVxuLy9cbi8vIEdpdmVuIGEgRGF0ZSBhcyBpbnB1dF9pbnRlZ2VyLCByZXR1cm4gYW4gQVNDSUkgc3RyaW5nIHN1aXRhYmxlIGZvclxuLy8gdXNlIGluIGFuIEhUVFAgZmllbGQgdmFsdWUuXG4vLyAxLiAgTGV0IG91dHB1dCBiZSBcIkBcIi5cbi8vIDIuICBBcHBlbmQgdG8gb3V0cHV0IHRoZSByZXN1bHQgb2YgcnVubmluZyBTZXJpYWxpemluZyBhbiBJbnRlZ2VyXG4vLyAgICAgd2l0aCBpbnB1dF9kYXRlIChTZWN0aW9uIDQuMS40KS5cbi8vIDMuICBSZXR1cm4gb3V0cHV0LlxuZnVuY3Rpb24gc2VyaWFsaXplRGF0ZSh2YWx1ZSkge1xuICByZXR1cm4gYEAke3NlcmlhbGl6ZUludGVnZXIodmFsdWUuZ2V0VGltZSgpIC8gMTAwMCl9YDtcbn1cblxuLyoqXG4gKiBUaGlzIGltcGxlbWVudHMgdGhlIHJvdW5kaW5nIHByb2NlZHVyZSBkZXNjcmliZWQgaW4gc3RlcCAyIG9mIHRoZSBcIlNlcmlhbGl6aW5nIGEgRGVjaW1hbFwiIHNwZWNpZmljYXRpb24uXG4gKiBUaGlzIHJvdW5kaW5nIHN0eWxlIGlzIGtub3duIGFzIFwiZXZlbiByb3VuZGluZ1wiLCBcImJhbmtlcidzIHJvdW5kaW5nXCIsIG9yIFwiY29tbWVyY2lhbCByb3VuZGluZ1wiLlxuICpcbiAqIEBwYXJhbSB2YWx1ZSAtIFRoZSB2YWx1ZSB0byByb3VuZFxuICogQHBhcmFtIHByZWNpc2lvbiAtIFRoZSBudW1iZXIgb2YgZGVjaW1hbCBwbGFjZXMgdG8gcm91bmQgdG9cbiAqIEByZXR1cm5zIFRoZSByb3VuZGVkIHZhbHVlXG4gKlxuICogQGdyb3VwIFV0aWxzXG4gKlxuICogQGJldGFcbiAqL1xuZnVuY3Rpb24gcm91bmRUb0V2ZW4odmFsdWUsIHByZWNpc2lvbikge1xuICBpZiAodmFsdWUgPCAwKSB7XG4gICAgcmV0dXJuIC1yb3VuZFRvRXZlbigtdmFsdWUsIHByZWNpc2lvbik7XG4gIH1cbiAgY29uc3QgZGVjaW1hbFNoaWZ0ID0gTWF0aC5wb3coMTAsIHByZWNpc2lvbik7XG4gIGNvbnN0IGlzRXF1aWRpc3RhbnQgPSBNYXRoLmFicyh2YWx1ZSAqIGRlY2ltYWxTaGlmdCAlIDEgLSAwLjUpIDwgTnVtYmVyLkVQU0lMT047XG4gIGlmIChpc0VxdWlkaXN0YW50KSB7XG4gICAgLy8gSWYgdGhlIHRhaWwgb2YgdGhlIGRlY2ltYWwgcGxhY2UgaXMgJ2VxdWlkaXN0YW50JyB3ZSByb3VuZCB0byB0aGUgbmVhcmVzdCBldmVuIHZhbHVlXG4gICAgY29uc3QgZmxvb3JlZFZhbHVlID0gTWF0aC5mbG9vcih2YWx1ZSAqIGRlY2ltYWxTaGlmdCk7XG4gICAgcmV0dXJuIChmbG9vcmVkVmFsdWUgJSAyID09PSAwID8gZmxvb3JlZFZhbHVlIDogZmxvb3JlZFZhbHVlICsgMSkgLyBkZWNpbWFsU2hpZnQ7XG4gIH0gZWxzZSB7XG4gICAgLy8gT3RoZXJ3aXNlLCBwcm9jZWVkIGFzIG5vcm1hbFxuICAgIHJldHVybiBNYXRoLnJvdW5kKHZhbHVlICogZGVjaW1hbFNoaWZ0KSAvIGRlY2ltYWxTaGlmdDtcbiAgfVxufVxuXG4vLyA0LjEuNS4gIFNlcmlhbGl6aW5nIGEgRGVjaW1hbFxuLy9cbi8vIEdpdmVuIGEgZGVjaW1hbCBudW1iZXIgYXMgaW5wdXRfZGVjaW1hbCwgcmV0dXJuIGFuIEFTQ0lJIHN0cmluZ1xuLy8gc3VpdGFibGUgZm9yIHVzZSBpbiBhIEhUVFAgZmllbGQgdmFsdWUuXG4vL1xuLy8gMS4gICBJZiBpbnB1dF9kZWNpbWFsIGlzIG5vdCBhIGRlY2ltYWwgbnVtYmVyLCBmYWlsIHNlcmlhbGl6YXRpb24uXG4vL1xuLy8gMi4gICBJZiBpbnB1dF9kZWNpbWFsIGhhcyBtb3JlIHRoYW4gdGhyZWUgc2lnbmlmaWNhbnQgZGlnaXRzIHRvIHRoZVxuLy8gICAgICByaWdodCBvZiB0aGUgZGVjaW1hbCBwb2ludCwgcm91bmQgaXQgdG8gdGhyZWUgZGVjaW1hbCBwbGFjZXMsXG4vLyAgICAgIHJvdW5kaW5nIHRoZSBmaW5hbCBkaWdpdCB0byB0aGUgbmVhcmVzdCB2YWx1ZSwgb3IgdG8gdGhlIGV2ZW5cbi8vICAgICAgdmFsdWUgaWYgaXQgaXMgZXF1aWRpc3RhbnQuXG4vL1xuLy8gMy4gICBJZiBpbnB1dF9kZWNpbWFsIGhhcyBtb3JlIHRoYW4gMTIgc2lnbmlmaWNhbnQgZGlnaXRzIHRvIHRoZSBsZWZ0XG4vLyAgICAgIG9mIHRoZSBkZWNpbWFsIHBvaW50IGFmdGVyIHJvdW5kaW5nLCBmYWlsIHNlcmlhbGl6YXRpb24uXG4vL1xuLy8gNC4gICBMZXQgb3V0cHV0IGJlIGFuIGVtcHR5IHN0cmluZy5cbi8vXG4vLyA1LiAgIElmIGlucHV0X2RlY2ltYWwgaXMgbGVzcyB0aGFuIChidXQgbm90IGVxdWFsIHRvKSAwLCBhcHBlbmQgXCItXCJcbi8vICAgICAgdG8gb3V0cHV0LlxuLy9cbi8vIDYuICAgQXBwZW5kIGlucHV0X2RlY2ltYWwncyBpbnRlZ2VyIGNvbXBvbmVudCByZXByZXNlbnRlZCBpbiBiYXNlIDEwXG4vLyAgICAgICh1c2luZyBvbmx5IGRlY2ltYWwgZGlnaXRzKSB0byBvdXRwdXQ7IGlmIGl0IGlzIHplcm8sIGFwcGVuZFxuLy8gICAgICBcIjBcIi5cbi8vXG4vLyA3LiAgIEFwcGVuZCBcIi5cIiB0byBvdXRwdXQuXG4vL1xuLy8gOC4gICBJZiBpbnB1dF9kZWNpbWFsJ3MgZnJhY3Rpb25hbCBjb21wb25lbnQgaXMgemVybywgYXBwZW5kIFwiMFwiIHRvXG4vLyAgICAgIG91dHB1dC5cbi8vXG4vLyA5LiAgIE90aGVyd2lzZSwgYXBwZW5kIHRoZSBzaWduaWZpY2FudCBkaWdpdHMgb2YgaW5wdXRfZGVjaW1hbCdzXG4vLyAgICAgIGZyYWN0aW9uYWwgY29tcG9uZW50IHJlcHJlc2VudGVkIGluIGJhc2UgMTAgKHVzaW5nIG9ubHkgZGVjaW1hbFxuLy8gICAgICBkaWdpdHMpIHRvIG91dHB1dC5cbi8vXG4vLyAxMC4gIFJldHVybiBvdXRwdXQuXG5mdW5jdGlvbiBzZXJpYWxpemVEZWNpbWFsKHZhbHVlKSB7XG4gIGNvbnN0IHJvdW5kZWRWYWx1ZSA9IHJvdW5kVG9FdmVuKHZhbHVlLCAzKTsgLy8gcm91bmQgdG8gMyBkZWNpbWFsIHBsYWNlc1xuICBpZiAoTWF0aC5mbG9vcihNYXRoLmFicyhyb3VuZGVkVmFsdWUpKS50b1N0cmluZygpLmxlbmd0aCA+IDEyKSB7XG4gICAgdGhyb3cgc2VyaWFsaXplRXJyb3IodmFsdWUsIERFQ0lNQUwpO1xuICB9XG4gIGNvbnN0IHN0cmluZ1ZhbHVlID0gcm91bmRlZFZhbHVlLnRvU3RyaW5nKCk7XG4gIHJldHVybiBzdHJpbmdWYWx1ZS5pbmNsdWRlcygnLicpID8gc3RyaW5nVmFsdWUgOiBgJHtzdHJpbmdWYWx1ZX0uMGA7XG59XG5cbmNvbnN0IFNUUklORyA9ICdTdHJpbmcnO1xuXG4vLyA0LjEuNi4gIFNlcmlhbGl6aW5nIGEgU3RyaW5nXG4vL1xuLy8gR2l2ZW4gYSBTdHJpbmcgYXMgaW5wdXRfc3RyaW5nLCByZXR1cm4gYW4gQVNDSUkgc3RyaW5nIHN1aXRhYmxlIGZvclxuLy8gdXNlIGluIGEgSFRUUCBmaWVsZCB2YWx1ZS5cbi8vXG4vLyAxLiAgQ29udmVydCBpbnB1dF9zdHJpbmcgaW50byBhIHNlcXVlbmNlIG9mIEFTQ0lJIGNoYXJhY3RlcnM7IGlmXG4vLyAgICAgY29udmVyc2lvbiBmYWlscywgZmFpbCBzZXJpYWxpemF0aW9uLlxuLy9cbi8vIDIuICBJZiBpbnB1dF9zdHJpbmcgY29udGFpbnMgY2hhcmFjdGVycyBpbiB0aGUgcmFuZ2UgJXgwMC0xZiBvciAleDdmXG4vLyAgICAgKGkuZS4sIG5vdCBpbiBWQ0hBUiBvciBTUCksIGZhaWwgc2VyaWFsaXphdGlvbi5cbi8vXG4vLyAzLiAgTGV0IG91dHB1dCBiZSB0aGUgc3RyaW5nIERRVU9URS5cbi8vXG4vLyA0LiAgRm9yIGVhY2ggY2hhcmFjdGVyIGNoYXIgaW4gaW5wdXRfc3RyaW5nOlxuLy9cbi8vICAgICAxLiAgSWYgY2hhciBpcyBcIlxcXCIgb3IgRFFVT1RFOlxuLy9cbi8vICAgICAgICAgMS4gIEFwcGVuZCBcIlxcXCIgdG8gb3V0cHV0LlxuLy9cbi8vICAgICAyLiAgQXBwZW5kIGNoYXIgdG8gb3V0cHV0LlxuLy9cbi8vIDUuICBBcHBlbmQgRFFVT1RFIHRvIG91dHB1dC5cbi8vXG4vLyA2LiAgUmV0dXJuIG91dHB1dC5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVN0cmluZyh2YWx1ZSkge1xuICBpZiAoU1RSSU5HX1JFR0VYLnRlc3QodmFsdWUpKSB7XG4gICAgdGhyb3cgc2VyaWFsaXplRXJyb3IodmFsdWUsIFNUUklORyk7XG4gIH1cbiAgcmV0dXJuIGBcIiR7dmFsdWUucmVwbGFjZSgvXFxcXC9nLCBgXFxcXFxcXFxgKS5yZXBsYWNlKC9cIi9nLCBgXFxcXFwiYCl9XCJgO1xufVxuXG5mdW5jdGlvbiBzeW1ib2xUb1N0cihzeW1ib2wpIHtcbiAgcmV0dXJuIHN5bWJvbC5kZXNjcmlwdGlvbiB8fCBzeW1ib2wudG9TdHJpbmcoKS5zbGljZSg3LCAtMSk7XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVRva2VuKHRva2VuKSB7XG4gIGNvbnN0IHZhbHVlID0gc3ltYm9sVG9TdHIodG9rZW4pO1xuICBpZiAoL14oW2EtekEtWipdKShbISMkJSYnKitcXC0uXl9gfH5cXHc6L10qKSQvLnRlc3QodmFsdWUpID09PSBmYWxzZSkge1xuICAgIHRocm93IHNlcmlhbGl6ZUVycm9yKHZhbHVlLCBUT0tFTik7XG4gIH1cbiAgcmV0dXJuIHZhbHVlO1xufVxuXG4vLyA0LjEuMy4xLiAgU2VyaWFsaXppbmcgYSBCYXJlIEl0ZW1cbi8vXG4vLyBHaXZlbiBhbiBJdGVtIGFzIGlucHV0X2l0ZW0sIHJldHVybiBhbiBBU0NJSSBzdHJpbmcgc3VpdGFibGUgZm9yIHVzZVxuLy8gaW4gYSBIVFRQIGZpZWxkIHZhbHVlLlxuLy9cbi8vIDEuICBJZiBpbnB1dF9pdGVtIGlzIGFuIEludGVnZXIsIHJldHVybiB0aGUgcmVzdWx0IG9mIHJ1bm5pbmdcbi8vICAgICBTZXJpYWxpemluZyBhbiBJbnRlZ2VyIChTZWN0aW9uIDQuMS40KSB3aXRoIGlucHV0X2l0ZW0uXG4vL1xuLy8gMi4gIElmIGlucHV0X2l0ZW0gaXMgYSBEZWNpbWFsLCByZXR1cm4gdGhlIHJlc3VsdCBvZiBydW5uaW5nXG4vLyAgICAgU2VyaWFsaXppbmcgYSBEZWNpbWFsIChTZWN0aW9uIDQuMS41KSB3aXRoIGlucHV0X2l0ZW0uXG4vL1xuLy8gMy4gIElmIGlucHV0X2l0ZW0gaXMgYSBTdHJpbmcsIHJldHVybiB0aGUgcmVzdWx0IG9mIHJ1bm5pbmdcbi8vICAgICBTZXJpYWxpemluZyBhIFN0cmluZyAoU2VjdGlvbiA0LjEuNikgd2l0aCBpbnB1dF9pdGVtLlxuLy9cbi8vIDQuICBJZiBpbnB1dF9pdGVtIGlzIGEgVG9rZW4sIHJldHVybiB0aGUgcmVzdWx0IG9mIHJ1bm5pbmdcbi8vICAgICBTZXJpYWxpemluZyBhIFRva2VuIChTZWN0aW9uIDQuMS43KSB3aXRoIGlucHV0X2l0ZW0uXG4vL1xuLy8gNS4gIElmIGlucHV0X2l0ZW0gaXMgYSBCb29sZWFuLCByZXR1cm4gdGhlIHJlc3VsdCBvZiBydW5uaW5nXG4vLyAgICAgU2VyaWFsaXppbmcgYSBCb29sZWFuIChTZWN0aW9uIDQuMS45KSB3aXRoIGlucHV0X2l0ZW0uXG4vL1xuLy8gNi4gIElmIGlucHV0X2l0ZW0gaXMgYSBCeXRlIFNlcXVlbmNlLCByZXR1cm4gdGhlIHJlc3VsdCBvZiBydW5uaW5nXG4vLyAgICAgU2VyaWFsaXppbmcgYSBCeXRlIFNlcXVlbmNlIChTZWN0aW9uIDQuMS44KSB3aXRoIGlucHV0X2l0ZW0uXG4vL1xuLy8gNy4gIElmIGlucHV0X2l0ZW0gaXMgYSBEYXRlLCByZXR1cm4gdGhlIHJlc3VsdCBvZiBydW5uaW5nIFNlcmlhbGl6aW5nXG4vLyAgICAgYSBEYXRlIChTZWN0aW9uIDQuMS4xMCkgd2l0aCBpbnB1dF9pdGVtLlxuLy9cbi8vIDguICBPdGhlcndpc2UsIGZhaWwgc2VyaWFsaXphdGlvbi5cbmZ1bmN0aW9uIHNlcmlhbGl6ZUJhcmVJdGVtKHZhbHVlKSB7XG4gIHN3aXRjaCAodHlwZW9mIHZhbHVlKSB7XG4gICAgY2FzZSAnbnVtYmVyJzpcbiAgICAgIGlmICghaXNGaW5pdGVOdW1iZXIodmFsdWUpKSB7XG4gICAgICAgIHRocm93IHNlcmlhbGl6ZUVycm9yKHZhbHVlLCBCQVJFX0lURU0pO1xuICAgICAgfVxuICAgICAgaWYgKE51bWJlci5pc0ludGVnZXIodmFsdWUpKSB7XG4gICAgICAgIHJldHVybiBzZXJpYWxpemVJbnRlZ2VyKHZhbHVlKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBzZXJpYWxpemVEZWNpbWFsKHZhbHVlKTtcbiAgICBjYXNlICdzdHJpbmcnOlxuICAgICAgcmV0dXJuIHNlcmlhbGl6ZVN0cmluZyh2YWx1ZSk7XG4gICAgY2FzZSAnc3ltYm9sJzpcbiAgICAgIHJldHVybiBzZXJpYWxpemVUb2tlbih2YWx1ZSk7XG4gICAgY2FzZSAnYm9vbGVhbic6XG4gICAgICByZXR1cm4gc2VyaWFsaXplQm9vbGVhbih2YWx1ZSk7XG4gICAgY2FzZSAnb2JqZWN0JzpcbiAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIERhdGUpIHtcbiAgICAgICAgcmV0dXJuIHNlcmlhbGl6ZURhdGUodmFsdWUpO1xuICAgICAgfVxuICAgICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgVWludDhBcnJheSkge1xuICAgICAgICByZXR1cm4gc2VyaWFsaXplQnl0ZVNlcXVlbmNlKHZhbHVlKTtcbiAgICAgIH1cbiAgICAgIGlmICh2YWx1ZSBpbnN0YW5jZW9mIFNmVG9rZW4pIHtcbiAgICAgICAgcmV0dXJuIHNlcmlhbGl6ZVRva2VuKHZhbHVlKTtcbiAgICAgIH1cbiAgICBkZWZhdWx0OlxuICAgICAgLy8gZmFpbFxuICAgICAgdGhyb3cgc2VyaWFsaXplRXJyb3IodmFsdWUsIEJBUkVfSVRFTSk7XG4gIH1cbn1cblxuLy8gNC4xLjEuMy4gIFNlcmlhbGl6aW5nIGEgS2V5XG4vL1xuLy8gR2l2ZW4gYSBrZXkgYXMgaW5wdXRfa2V5LCByZXR1cm4gYW4gQVNDSUkgc3RyaW5nIHN1aXRhYmxlIGZvciB1c2UgaW5cbi8vIGEgSFRUUCBmaWVsZCB2YWx1ZS5cbi8vXG4vLyAxLiAgQ29udmVydCBpbnB1dF9rZXkgaW50byBhIHNlcXVlbmNlIG9mIEFTQ0lJIGNoYXJhY3RlcnM7IGlmXG4vLyAgICAgY29udmVyc2lvbiBmYWlscywgZmFpbCBzZXJpYWxpemF0aW9uLlxuLy9cbi8vIDIuICBJZiBpbnB1dF9rZXkgY29udGFpbnMgY2hhcmFjdGVycyBub3QgaW4gbGNhbHBoYSwgRElHSVQsIFwiX1wiLCBcIi1cIixcbi8vICAgICBcIi5cIiwgb3IgXCIqXCIgZmFpbCBzZXJpYWxpemF0aW9uLlxuLy9cbi8vIDMuICBJZiB0aGUgZmlyc3QgY2hhcmFjdGVyIG9mIGlucHV0X2tleSBpcyBub3QgbGNhbHBoYSBvciBcIipcIiwgZmFpbFxuLy8gICAgIHNlcmlhbGl6YXRpb24uXG4vL1xuLy8gNC4gIExldCBvdXRwdXQgYmUgYW4gZW1wdHkgc3RyaW5nLlxuLy9cbi8vIDUuICBBcHBlbmQgaW5wdXRfa2V5IHRvIG91dHB1dC5cbi8vXG4vLyA2LiAgUmV0dXJuIG91dHB1dC5cbmZ1bmN0aW9uIHNlcmlhbGl6ZUtleSh2YWx1ZSkge1xuICBpZiAoL15bYS16Kl1bYS16MC05XFwtXy4qXSokLy50ZXN0KHZhbHVlKSA9PT0gZmFsc2UpIHtcbiAgICB0aHJvdyBzZXJpYWxpemVFcnJvcih2YWx1ZSwgS0VZKTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbi8vIDQuMS4xLjIuICBTZXJpYWxpemluZyBQYXJhbWV0ZXJzXG4vL1xuLy8gR2l2ZW4gYW4gb3JkZXJlZCBEaWN0aW9uYXJ5IGFzIGlucHV0X3BhcmFtZXRlcnMgKGVhY2ggbWVtYmVyIGhhdmluZyBhXG4vLyBwYXJhbV9uYW1lIGFuZCBhIHBhcmFtX3ZhbHVlKSwgcmV0dXJuIGFuIEFTQ0lJIHN0cmluZyBzdWl0YWJsZSBmb3Jcbi8vIHVzZSBpbiBhIEhUVFAgZmllbGQgdmFsdWUuXG4vL1xuLy8gMS4gIExldCBvdXRwdXQgYmUgYW4gZW1wdHkgc3RyaW5nLlxuLy9cbi8vIDIuICBGb3IgZWFjaCBwYXJhbV9uYW1lIHdpdGggYSB2YWx1ZSBvZiBwYXJhbV92YWx1ZSBpblxuLy8gICAgIGlucHV0X3BhcmFtZXRlcnM6XG4vL1xuLy8gICAgIDEuICBBcHBlbmQgXCI7XCIgdG8gb3V0cHV0LlxuLy9cbi8vICAgICAyLiAgQXBwZW5kIHRoZSByZXN1bHQgb2YgcnVubmluZyBTZXJpYWxpemluZyBhIEtleVxuLy8gICAgICAgICAoU2VjdGlvbiA0LjEuMS4zKSB3aXRoIHBhcmFtX25hbWUgdG8gb3V0cHV0LlxuLy9cbi8vICAgICAzLiAgSWYgcGFyYW1fdmFsdWUgaXMgbm90IEJvb2xlYW4gdHJ1ZTpcbi8vXG4vLyAgICAgICAgIDEuICBBcHBlbmQgXCI9XCIgdG8gb3V0cHV0LlxuLy9cbi8vICAgICAgICAgMi4gIEFwcGVuZCB0aGUgcmVzdWx0IG9mIHJ1bm5pbmcgU2VyaWFsaXppbmcgYSBiYXJlIEl0ZW1cbi8vICAgICAgICAgICAgIChTZWN0aW9uIDQuMS4zLjEpIHdpdGggcGFyYW1fdmFsdWUgdG8gb3V0cHV0LlxuLy9cbi8vIDMuICBSZXR1cm4gb3V0cHV0LlxuZnVuY3Rpb24gc2VyaWFsaXplUGFyYW1zKHBhcmFtcykge1xuICBpZiAocGFyYW1zID09IG51bGwpIHtcbiAgICByZXR1cm4gJyc7XG4gIH1cbiAgcmV0dXJuIE9iamVjdC5lbnRyaWVzKHBhcmFtcykubWFwKChba2V5LCB2YWx1ZV0pID0+IHtcbiAgICBpZiAodmFsdWUgPT09IHRydWUpIHtcbiAgICAgIHJldHVybiBgOyR7c2VyaWFsaXplS2V5KGtleSl9YDsgLy8gb21pdCB0cnVlXG4gICAgfVxuICAgIHJldHVybiBgOyR7c2VyaWFsaXplS2V5KGtleSl9PSR7c2VyaWFsaXplQmFyZUl0ZW0odmFsdWUpfWA7XG4gIH0pLmpvaW4oJycpO1xufVxuXG4vLyA0LjEuMy4gIFNlcmlhbGl6aW5nIGFuIEl0ZW1cbi8vXG4vLyBHaXZlbiBhbiBJdGVtIGFzIGJhcmVfaXRlbSBhbmQgUGFyYW1ldGVycyBhcyBpdGVtX3BhcmFtZXRlcnMsIHJldHVyblxuLy8gYW4gQVNDSUkgc3RyaW5nIHN1aXRhYmxlIGZvciB1c2UgaW4gYSBIVFRQIGZpZWxkIHZhbHVlLlxuLy9cbi8vIDEuICBMZXQgb3V0cHV0IGJlIGFuIGVtcHR5IHN0cmluZy5cbi8vXG4vLyAyLiAgQXBwZW5kIHRoZSByZXN1bHQgb2YgcnVubmluZyBTZXJpYWxpemluZyBhIEJhcmUgSXRlbVxuLy8gICAgIFNlY3Rpb24gNC4xLjMuMSB3aXRoIGJhcmVfaXRlbSB0byBvdXRwdXQuXG4vL1xuLy8gMy4gIEFwcGVuZCB0aGUgcmVzdWx0IG9mIHJ1bm5pbmcgU2VyaWFsaXppbmcgUGFyYW1ldGVyc1xuLy8gICAgIFNlY3Rpb24gNC4xLjEuMiB3aXRoIGl0ZW1fcGFyYW1ldGVycyB0byBvdXRwdXQuXG4vL1xuLy8gNC4gIFJldHVybiBvdXRwdXQuXG5mdW5jdGlvbiBzZXJpYWxpemVJdGVtKHZhbHVlKSB7XG4gIGlmICh2YWx1ZSBpbnN0YW5jZW9mIFNmSXRlbSkge1xuICAgIHJldHVybiBgJHtzZXJpYWxpemVCYXJlSXRlbSh2YWx1ZS52YWx1ZSl9JHtzZXJpYWxpemVQYXJhbXModmFsdWUucGFyYW1zKX1gO1xuICB9IGVsc2Uge1xuICAgIHJldHVybiBzZXJpYWxpemVCYXJlSXRlbSh2YWx1ZSk7XG4gIH1cbn1cblxuLy8gNC4xLjEuMS4gIFNlcmlhbGl6aW5nIGFuIElubmVyIExpc3Rcbi8vXG4vLyBHaXZlbiBhbiBhcnJheSBvZiAobWVtYmVyX3ZhbHVlLCBwYXJhbWV0ZXJzKSB0dXBsZXMgYXMgaW5uZXJfbGlzdCxcbi8vIGFuZCBwYXJhbWV0ZXJzIGFzIGxpc3RfcGFyYW1ldGVycywgcmV0dXJuIGFuIEFTQ0lJIHN0cmluZyBzdWl0YWJsZVxuLy8gZm9yIHVzZSBpbiBhIEhUVFAgZmllbGQgdmFsdWUuXG4vL1xuLy8gMS4gIExldCBvdXRwdXQgYmUgdGhlIHN0cmluZyBcIihcIi5cbi8vXG4vLyAyLiAgRm9yIGVhY2ggKG1lbWJlcl92YWx1ZSwgcGFyYW1ldGVycykgb2YgaW5uZXJfbGlzdDpcbi8vXG4vLyAgICAgMS4gIEFwcGVuZCB0aGUgcmVzdWx0IG9mIHJ1bm5pbmcgU2VyaWFsaXppbmcgYW4gSXRlbVxuLy8gICAgICAgICAoU2VjdGlvbiA0LjEuMykgd2l0aCAobWVtYmVyX3ZhbHVlLCBwYXJhbWV0ZXJzKSB0byBvdXRwdXQuXG4vL1xuLy8gICAgIDIuICBJZiBtb3JlIHZhbHVlcyByZW1haW4gaW4gaW5uZXJfbGlzdCwgYXBwZW5kIGEgc2luZ2xlIFNQIHRvXG4vLyAgICAgICAgIG91dHB1dC5cbi8vXG4vLyAzLiAgQXBwZW5kIFwiKVwiIHRvIG91dHB1dC5cbi8vXG4vLyA0LiAgQXBwZW5kIHRoZSByZXN1bHQgb2YgcnVubmluZyBTZXJpYWxpemluZyBQYXJhbWV0ZXJzXG4vLyAgICAgKFNlY3Rpb24gNC4xLjEuMikgd2l0aCBsaXN0X3BhcmFtZXRlcnMgdG8gb3V0cHV0LlxuLy9cbi8vIDUuICBSZXR1cm4gb3V0cHV0LlxuZnVuY3Rpb24gc2VyaWFsaXplSW5uZXJMaXN0KHZhbHVlKSB7XG4gIHJldHVybiBgKCR7dmFsdWUudmFsdWUubWFwKHNlcmlhbGl6ZUl0ZW0pLmpvaW4oJyAnKX0pJHtzZXJpYWxpemVQYXJhbXModmFsdWUucGFyYW1zKX1gO1xufVxuXG4vLyA0LjEuMi4gIFNlcmlhbGl6aW5nIGEgRGljdGlvbmFyeVxuLy9cbi8vIEdpdmVuIGFuIG9yZGVyZWQgRGljdGlvbmFyeSBhcyBpbnB1dF9kaWN0aW9uYXJ5IChlYWNoIG1lbWJlciBoYXZpbmcgYVxuLy8gbWVtYmVyX25hbWUgYW5kIGEgdHVwbGUgdmFsdWUgb2YgKG1lbWJlcl92YWx1ZSwgcGFyYW1ldGVycykpLCByZXR1cm5cbi8vIGFuIEFTQ0lJIHN0cmluZyBzdWl0YWJsZSBmb3IgdXNlIGluIGEgSFRUUCBmaWVsZCB2YWx1ZS5cbi8vXG4vLyAxLiAgTGV0IG91dHB1dCBiZSBhbiBlbXB0eSBzdHJpbmcuXG4vL1xuLy8gMi4gIEZvciBlYWNoIG1lbWJlcl9uYW1lIHdpdGggYSB2YWx1ZSBvZiAobWVtYmVyX3ZhbHVlLCBwYXJhbWV0ZXJzKVxuLy8gICAgIGluIGlucHV0X2RpY3Rpb25hcnk6XG4vL1xuLy8gICAgIDEuICBBcHBlbmQgdGhlIHJlc3VsdCBvZiBydW5uaW5nIFNlcmlhbGl6aW5nIGEgS2V5XG4vLyAgICAgICAgIChTZWN0aW9uIDQuMS4xLjMpIHdpdGggbWVtYmVyJ3MgbWVtYmVyX25hbWUgdG8gb3V0cHV0LlxuLy9cbi8vICAgICAyLiAgSWYgbWVtYmVyX3ZhbHVlIGlzIEJvb2xlYW4gdHJ1ZTpcbi8vXG4vLyAgICAgICAgIDEuICBBcHBlbmQgdGhlIHJlc3VsdCBvZiBydW5uaW5nIFNlcmlhbGl6aW5nIFBhcmFtZXRlcnNcbi8vICAgICAgICAgICAgIChTZWN0aW9uIDQuMS4xLjIpIHdpdGggcGFyYW1ldGVycyB0byBvdXRwdXQuXG4vL1xuLy8gICAgIDMuICBPdGhlcndpc2U6XG4vL1xuLy8gICAgICAgICAxLiAgQXBwZW5kIFwiPVwiIHRvIG91dHB1dC5cbi8vXG4vLyAgICAgICAgIDIuICBJZiBtZW1iZXJfdmFsdWUgaXMgYW4gYXJyYXksIGFwcGVuZCB0aGUgcmVzdWx0IG9mIHJ1bm5pbmdcbi8vICAgICAgICAgICAgIFNlcmlhbGl6aW5nIGFuIElubmVyIExpc3QgKFNlY3Rpb24gNC4xLjEuMSkgd2l0aFxuLy8gICAgICAgICAgICAgKG1lbWJlcl92YWx1ZSwgcGFyYW1ldGVycykgdG8gb3V0cHV0LlxuLy9cbi8vICAgICAgICAgMy4gIE90aGVyd2lzZSwgYXBwZW5kIHRoZSByZXN1bHQgb2YgcnVubmluZyBTZXJpYWxpemluZyBhblxuLy8gICAgICAgICAgICAgSXRlbSAoU2VjdGlvbiA0LjEuMykgd2l0aCAobWVtYmVyX3ZhbHVlLCBwYXJhbWV0ZXJzKSB0b1xuLy8gICAgICAgICAgICAgb3V0cHV0LlxuLy9cbi8vICAgICA0LiAgSWYgbW9yZSBtZW1iZXJzIHJlbWFpbiBpbiBpbnB1dF9kaWN0aW9uYXJ5OlxuLy9cbi8vICAgICAgICAgMS4gIEFwcGVuZCBcIixcIiB0byBvdXRwdXQuXG4vL1xuLy8gICAgICAgICAyLiAgQXBwZW5kIGEgc2luZ2xlIFNQIHRvIG91dHB1dC5cbi8vXG4vLyAzLiAgUmV0dXJuIG91dHB1dC5cbmZ1bmN0aW9uIHNlcmlhbGl6ZURpY3QoZGljdCwgb3B0aW9ucyA9IHtcbiAgd2hpdGVzcGFjZTogdHJ1ZVxufSkge1xuICBpZiAodHlwZW9mIGRpY3QgIT09ICdvYmplY3QnKSB7XG4gICAgdGhyb3cgc2VyaWFsaXplRXJyb3IoZGljdCwgRElDVCk7XG4gIH1cbiAgY29uc3QgZW50cmllcyA9IGRpY3QgaW5zdGFuY2VvZiBNYXAgPyBkaWN0LmVudHJpZXMoKSA6IE9iamVjdC5lbnRyaWVzKGRpY3QpO1xuICBjb25zdCBvcHRpb25hbFdoaXRlU3BhY2UgPSBvcHRpb25zICE9IG51bGwgJiYgb3B0aW9ucy53aGl0ZXNwYWNlID8gJyAnIDogJyc7XG4gIHJldHVybiBBcnJheS5mcm9tKGVudHJpZXMpLm1hcCgoW2tleSwgaXRlbV0pID0+IHtcbiAgICBpZiAoaXRlbSBpbnN0YW5jZW9mIFNmSXRlbSA9PT0gZmFsc2UpIHtcbiAgICAgIGl0ZW0gPSBuZXcgU2ZJdGVtKGl0ZW0pO1xuICAgIH1cbiAgICBsZXQgb3V0cHV0ID0gc2VyaWFsaXplS2V5KGtleSk7XG4gICAgaWYgKGl0ZW0udmFsdWUgPT09IHRydWUpIHtcbiAgICAgIG91dHB1dCArPSBzZXJpYWxpemVQYXJhbXMoaXRlbS5wYXJhbXMpO1xuICAgIH0gZWxzZSB7XG4gICAgICBvdXRwdXQgKz0gJz0nO1xuICAgICAgaWYgKEFycmF5LmlzQXJyYXkoaXRlbS52YWx1ZSkpIHtcbiAgICAgICAgb3V0cHV0ICs9IHNlcmlhbGl6ZUlubmVyTGlzdChpdGVtKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIG91dHB1dCArPSBzZXJpYWxpemVJdGVtKGl0ZW0pO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gb3V0cHV0O1xuICB9KS5qb2luKGAsJHtvcHRpb25hbFdoaXRlU3BhY2V9YCk7XG59XG5cbi8qKlxuICogRW5jb2RlIGFuIG9iamVjdCBpbnRvIGEgc3RydWN0dXJlZCBmaWVsZCBkaWN0aW9uYXJ5XG4gKlxuICogQHBhcmFtIGlucHV0IC0gVGhlIHN0cnVjdHVyZWQgZmllbGQgZGljdGlvbmFyeSB0byBlbmNvZGVcbiAqIEByZXR1cm5zIFRoZSBzdHJ1Y3R1cmVkIGZpZWxkIHN0cmluZ1xuICpcbiAqIEBncm91cCBTdHJ1Y3R1cmVkIEZpZWxkXG4gKlxuICogQGJldGFcbiAqL1xuZnVuY3Rpb24gZW5jb2RlU2ZEaWN0KHZhbHVlLCBvcHRpb25zKSB7XG4gIHJldHVybiBzZXJpYWxpemVEaWN0KHZhbHVlLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGdpdmVuIGtleSBpcyBhIHRva2VuIGZpZWxkLlxuICpcbiAqIEBwYXJhbSBrZXkgLSBUaGUga2V5IHRvIGNoZWNrLlxuICpcbiAqIEByZXR1cm5zIGB0cnVlYCBpZiB0aGUga2V5IGlzIGEgdG9rZW4gZmllbGQuXG4gKlxuICogQGludGVybmFsXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqL1xuY29uc3QgaXNUb2tlbkZpZWxkID0ga2V5ID0+IGtleSA9PT0gJ290JyB8fCBrZXkgPT09ICdzZicgfHwga2V5ID09PSAnc3QnO1xuXG5jb25zdCBpc1ZhbGlkID0gdmFsdWUgPT4ge1xuICBpZiAodHlwZW9mIHZhbHVlID09PSAnbnVtYmVyJykge1xuICAgIHJldHVybiBpc0Zpbml0ZU51bWJlcih2YWx1ZSk7XG4gIH1cbiAgcmV0dXJuIHZhbHVlICE9IG51bGwgJiYgdmFsdWUgIT09ICcnICYmIHZhbHVlICE9PSBmYWxzZTtcbn07XG5cbi8qKlxuICogQ29uc3RydWN0cyBhIHJlbGF0aXZlIHBhdGggZnJvbSBhIFVSTC5cbiAqXG4gKiBAcGFyYW0gdXJsIC0gVGhlIGRlc3RpbmF0aW9uIFVSTFxuICogQHBhcmFtIGJhc2UgLSBUaGUgYmFzZSBVUkxcbiAqIEByZXR1cm5zIFRoZSByZWxhdGl2ZSBwYXRoXG4gKlxuICogQGdyb3VwIFV0aWxzXG4gKlxuICogQGJldGFcbiAqL1xuZnVuY3Rpb24gdXJsVG9SZWxhdGl2ZVBhdGgodXJsLCBiYXNlKSB7XG4gIGNvbnN0IHRvID0gbmV3IFVSTCh1cmwpO1xuICBjb25zdCBmcm9tID0gbmV3IFVSTChiYXNlKTtcbiAgaWYgKHRvLm9yaWdpbiAhPT0gZnJvbS5vcmlnaW4pIHtcbiAgICByZXR1cm4gdXJsO1xuICB9XG4gIGNvbnN0IHRvUGF0aCA9IHRvLnBhdGhuYW1lLnNwbGl0KCcvJykuc2xpY2UoMSk7XG4gIGNvbnN0IGZyb21QYXRoID0gZnJvbS5wYXRobmFtZS5zcGxpdCgnLycpLnNsaWNlKDEsIC0xKTtcbiAgLy8gcmVtb3ZlIGNvbW1vbiBwYXJlbnRzXG4gIHdoaWxlICh0b1BhdGhbMF0gPT09IGZyb21QYXRoWzBdKSB7XG4gICAgdG9QYXRoLnNoaWZ0KCk7XG4gICAgZnJvbVBhdGguc2hpZnQoKTtcbiAgfVxuICAvLyBhZGQgYmFjayBwYXRoc1xuICB3aGlsZSAoZnJvbVBhdGgubGVuZ3RoKSB7XG4gICAgZnJvbVBhdGguc2hpZnQoKTtcbiAgICB0b1BhdGgudW5zaGlmdCgnLi4nKTtcbiAgfVxuICByZXR1cm4gdG9QYXRoLmpvaW4oJy8nKTtcbn1cblxuLyoqXG4gKiBHZW5lcmF0ZSBhIHJhbmRvbSB2NCBVVUlEXG4gKlxuICogQHJldHVybnMgQSByYW5kb20gdjQgVVVJRFxuICpcbiAqIEBncm91cCBVdGlsc1xuICpcbiAqIEBiZXRhXG4gKi9cbmZ1bmN0aW9uIHV1aWQoKSB7XG4gIHRyeSB7XG4gICAgcmV0dXJuIGNyeXB0by5yYW5kb21VVUlEKCk7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHVybCA9IFVSTC5jcmVhdGVPYmplY3RVUkwobmV3IEJsb2IoKSk7XG4gICAgICBjb25zdCB1dWlkID0gdXJsLnRvU3RyaW5nKCk7XG4gICAgICBVUkwucmV2b2tlT2JqZWN0VVJMKHVybCk7XG4gICAgICByZXR1cm4gdXVpZC5zbGljZSh1dWlkLmxhc3RJbmRleE9mKCcvJykgKyAxKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbGV0IGR0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICBjb25zdCB1dWlkID0gJ3h4eHh4eHh4LXh4eHgtNHh4eC15eHh4LXh4eHh4eHh4eHh4eCcucmVwbGFjZSgvW3h5XS9nLCBjID0+IHtcbiAgICAgICAgY29uc3QgciA9IChkdCArIE1hdGgucmFuZG9tKCkgKiAxNikgJSAxNiB8IDA7XG4gICAgICAgIGR0ID0gTWF0aC5mbG9vcihkdCAvIDE2KTtcbiAgICAgICAgcmV0dXJuIChjID09ICd4JyA/IHIgOiByICYgMHgzIHwgMHg4KS50b1N0cmluZygxNik7XG4gICAgICB9KTtcbiAgICAgIHJldHVybiB1dWlkO1xuICAgIH1cbiAgfVxufVxuXG5jb25zdCB0b1JvdW5kZWQgPSB2YWx1ZSA9PiBNYXRoLnJvdW5kKHZhbHVlKTtcbmNvbnN0IHRvVXJsU2FmZSA9ICh2YWx1ZSwgb3B0aW9ucykgPT4ge1xuICBpZiAob3B0aW9ucyAhPSBudWxsICYmIG9wdGlvbnMuYmFzZVVybCkge1xuICAgIHZhbHVlID0gdXJsVG9SZWxhdGl2ZVBhdGgodmFsdWUsIG9wdGlvbnMuYmFzZVVybCk7XG4gIH1cbiAgcmV0dXJuIGVuY29kZVVSSUNvbXBvbmVudCh2YWx1ZSk7XG59O1xuY29uc3QgdG9IdW5kcmVkID0gdmFsdWUgPT4gdG9Sb3VuZGVkKHZhbHVlIC8gMTAwKSAqIDEwMDtcbi8qKlxuICogVGhlIGRlZmF1bHQgZm9ybWF0dGVycyBmb3IgQ01DRCB2YWx1ZXMuXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqXG4gKiBAYmV0YVxuICovXG5jb25zdCBDbWNkRm9ybWF0dGVycyA9IHtcbiAgLyoqXG4gICAqIEJpdHJhdGUgKGticHMpIHJvdW5kZWQgaW50ZWdlclxuICAgKi9cbiAgYnI6IHRvUm91bmRlZCxcbiAgLyoqXG4gICAqIER1cmF0aW9uIChtaWxsaXNlY29uZHMpIHJvdW5kZWQgaW50ZWdlclxuICAgKi9cbiAgZDogdG9Sb3VuZGVkLFxuICAvKipcbiAgICogQnVmZmVyIExlbmd0aCAobWlsbGlzZWNvbmRzKSByb3VuZGVkIG5lYXJlc3QgMTAwbXNcbiAgICovXG4gIGJsOiB0b0h1bmRyZWQsXG4gIC8qKlxuICAgKiBEZWFkbGluZSAobWlsbGlzZWNvbmRzKSByb3VuZGVkIG5lYXJlc3QgMTAwbXNcbiAgICovXG4gIGRsOiB0b0h1bmRyZWQsXG4gIC8qKlxuICAgKiBNZWFzdXJlZCBUaHJvdWdocHV0IChrYnBzKSByb3VuZGVkIG5lYXJlc3QgMTAwa2Jwc1xuICAgKi9cbiAgbXRwOiB0b0h1bmRyZWQsXG4gIC8qKlxuICAgKiBOZXh0IE9iamVjdCBSZXF1ZXN0IFVSTCBlbmNvZGVkXG4gICAqL1xuICBub3I6IHRvVXJsU2FmZSxcbiAgLyoqXG4gICAqIFJlcXVlc3RlZCBtYXhpbXVtIHRocm91Z2hwdXQgKGticHMpIHJvdW5kZWQgbmVhcmVzdCAxMDBrYnBzXG4gICAqL1xuICBydHA6IHRvSHVuZHJlZCxcbiAgLyoqXG4gICAqIFRvcCBCaXRyYXRlIChrYnBzKSByb3VuZGVkIGludGVnZXJcbiAgICovXG4gIHRiOiB0b1JvdW5kZWRcbn07XG5cbi8qKlxuICogSW50ZXJuYWwgQ01DRCBwcm9jZXNzaW5nIGZ1bmN0aW9uLlxuICpcbiAqIEBwYXJhbSBvYmogLSBUaGUgQ01DRCBvYmplY3QgdG8gcHJvY2Vzcy5cbiAqIEBwYXJhbSBtYXAgLSBUaGUgbWFwcGluZyBmdW5jdGlvbiB0byB1c2UuXG4gKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgZm9yIGVuY29kaW5nLlxuICpcbiAqIEBpbnRlcm5hbFxuICpcbiAqIEBncm91cCBDTUNEXG4gKi9cbmZ1bmN0aW9uIHByb2Nlc3NDbWNkKG9iaiwgb3B0aW9ucykge1xuICBjb25zdCByZXN1bHRzID0ge307XG4gIGlmIChvYmogPT0gbnVsbCB8fCB0eXBlb2Ygb2JqICE9PSAnb2JqZWN0Jykge1xuICAgIHJldHVybiByZXN1bHRzO1xuICB9XG4gIGNvbnN0IGtleXMgPSBPYmplY3Qua2V5cyhvYmopLnNvcnQoKTtcbiAgY29uc3QgZm9ybWF0dGVycyA9IF9leHRlbmRzKHt9LCBDbWNkRm9ybWF0dGVycywgb3B0aW9ucyA9PSBudWxsID8gdm9pZCAwIDogb3B0aW9ucy5mb3JtYXR0ZXJzKTtcbiAgY29uc3QgZmlsdGVyID0gb3B0aW9ucyA9PSBudWxsID8gdm9pZCAwIDogb3B0aW9ucy5maWx0ZXI7XG4gIGtleXMuZm9yRWFjaChrZXkgPT4ge1xuICAgIGlmIChmaWx0ZXIgIT0gbnVsbCAmJiBmaWx0ZXIoa2V5KSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsZXQgdmFsdWUgPSBvYmpba2V5XTtcbiAgICBjb25zdCBmb3JtYXR0ZXIgPSBmb3JtYXR0ZXJzW2tleV07XG4gICAgaWYgKGZvcm1hdHRlcikge1xuICAgICAgdmFsdWUgPSBmb3JtYXR0ZXIodmFsdWUsIG9wdGlvbnMpO1xuICAgIH1cbiAgICAvLyBWZXJzaW9uIHNob3VsZCBvbmx5IGJlIHJlcG9ydGVkIGlmIG5vdCBlcXVhbCB0byAxLlxuICAgIGlmIChrZXkgPT09ICd2JyAmJiB2YWx1ZSA9PT0gMSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICAvLyBQbGF5YmFjayByYXRlIHNob3VsZCBvbmx5IGJlIHNlbnQgaWYgbm90IGVxdWFsIHRvIDEuXG4gICAgaWYgKGtleSA9PSAncHInICYmIHZhbHVlID09PSAxKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIGlnbm9yZSBpbnZhbGlkIHZhbHVlc1xuICAgIGlmICghaXNWYWxpZCh2YWx1ZSkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGlzVG9rZW5GaWVsZChrZXkpICYmIHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHZhbHVlID0gbmV3IFNmVG9rZW4odmFsdWUpO1xuICAgIH1cbiAgICByZXN1bHRzW2tleV0gPSB2YWx1ZTtcbiAgfSk7XG4gIHJldHVybiByZXN1bHRzO1xufVxuXG4vKipcbiAqIEVuY29kZSBhIENNQ0Qgb2JqZWN0IHRvIGEgc3RyaW5nLlxuICpcbiAqIEBwYXJhbSBjbWNkIC0gVGhlIENNQ0Qgb2JqZWN0IHRvIGVuY29kZS5cbiAqIEBwYXJhbSBvcHRpb25zIC0gT3B0aW9ucyBmb3IgZW5jb2RpbmcuXG4gKlxuICogQHJldHVybnMgVGhlIGVuY29kZWQgQ01DRCBzdHJpbmcuXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqXG4gKiBAYmV0YVxuICovXG5mdW5jdGlvbiBlbmNvZGVDbWNkKGNtY2QsIG9wdGlvbnMgPSB7fSkge1xuICBpZiAoIWNtY2QpIHtcbiAgICByZXR1cm4gJyc7XG4gIH1cbiAgcmV0dXJuIGVuY29kZVNmRGljdChwcm9jZXNzQ21jZChjbWNkLCBvcHRpb25zKSwgX2V4dGVuZHMoe1xuICAgIHdoaXRlc3BhY2U6IGZhbHNlXG4gIH0sIG9wdGlvbnMpKTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0IGEgQ01DRCBkYXRhIG9iamVjdCB0byByZXF1ZXN0IGhlYWRlcnNcbiAqXG4gKiBAcGFyYW0gY21jZCAtIFRoZSBDTUNEIGRhdGEgb2JqZWN0IHRvIGNvbnZlcnQuXG4gKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgZm9yIGVuY29kaW5nIHRoZSBDTUNEIG9iamVjdC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgQ01DRCBoZWFkZXIgc2hhcmRzLlxuICpcbiAqIEBncm91cCBDTUNEXG4gKlxuICogQGJldGFcbiAqL1xuZnVuY3Rpb24gdG9DbWNkSGVhZGVycyhjbWNkLCBvcHRpb25zID0ge30pIHtcbiAgaWYgKCFjbWNkKSB7XG4gICAgcmV0dXJuIHt9O1xuICB9XG4gIGNvbnN0IGVudHJpZXMgPSBPYmplY3QuZW50cmllcyhjbWNkKTtcbiAgY29uc3QgaGVhZGVyTWFwID0gT2JqZWN0LmVudHJpZXMoQ21jZEhlYWRlck1hcCkuY29uY2F0KE9iamVjdC5lbnRyaWVzKChvcHRpb25zID09IG51bGwgPyB2b2lkIDAgOiBvcHRpb25zLmN1c3RvbUhlYWRlck1hcCkgfHwge30pKTtcbiAgY29uc3Qgc2hhcmRzID0gZW50cmllcy5yZWR1Y2UoKGFjYywgZW50cnkpID0+IHtcbiAgICB2YXIgX2hlYWRlck1hcCRmaW5kLCBfYWNjJGZpZWxkO1xuICAgIGNvbnN0IFtrZXksIHZhbHVlXSA9IGVudHJ5O1xuICAgIGNvbnN0IGZpZWxkID0gKChfaGVhZGVyTWFwJGZpbmQgPSBoZWFkZXJNYXAuZmluZChlbnRyeSA9PiBlbnRyeVsxXS5pbmNsdWRlcyhrZXkpKSkgPT0gbnVsbCA/IHZvaWQgMCA6IF9oZWFkZXJNYXAkZmluZFswXSkgfHwgQ21jZEhlYWRlckZpZWxkLlJFUVVFU1Q7XG4gICAgKF9hY2MkZmllbGQgPSBhY2NbZmllbGRdKSAhPSBudWxsID8gX2FjYyRmaWVsZCA6IGFjY1tmaWVsZF0gPSB7fTtcbiAgICBhY2NbZmllbGRdW2tleV0gPSB2YWx1ZTtcbiAgICByZXR1cm4gYWNjO1xuICB9LCB7fSk7XG4gIHJldHVybiBPYmplY3QuZW50cmllcyhzaGFyZHMpLnJlZHVjZSgoYWNjLCBbZmllbGQsIHZhbHVlXSkgPT4ge1xuICAgIGFjY1tmaWVsZF0gPSBlbmNvZGVDbWNkKHZhbHVlLCBvcHRpb25zKTtcbiAgICByZXR1cm4gYWNjO1xuICB9LCB7fSk7XG59XG5cbi8qKlxuICogQXBwZW5kIENNQ0QgcXVlcnkgYXJncyB0byBhIGhlYWRlciBvYmplY3QuXG4gKlxuICogQHBhcmFtIGhlYWRlcnMgLSBUaGUgaGVhZGVycyB0byBhcHBlbmQgdG8uXG4gKiBAcGFyYW0gY21jZCAtIFRoZSBDTUNEIG9iamVjdCB0byBhcHBlbmQuXG4gKiBAcGFyYW0gY3VzdG9tSGVhZGVyTWFwIC0gQSBtYXAgb2YgY3VzdG9tIENNQ0Qga2V5cyB0byBoZWFkZXIgZmllbGRzLlxuICpcbiAqIEByZXR1cm5zIFRoZSBoZWFkZXJzIHdpdGggdGhlIENNQ0QgaGVhZGVyIHNoYXJkcyBhcHBlbmRlZC5cbiAqXG4gKiBAZ3JvdXAgQ01DRFxuICpcbiAqIEBiZXRhXG4gKi9cbmZ1bmN0aW9uIGFwcGVuZENtY2RIZWFkZXJzKGhlYWRlcnMsIGNtY2QsIG9wdGlvbnMpIHtcbiAgcmV0dXJuIF9leHRlbmRzKGhlYWRlcnMsIHRvQ21jZEhlYWRlcnMoY21jZCwgb3B0aW9ucykpO1xufVxuXG4vKipcbiAqIENNQ0QgcGFyYW1ldGVyIG5hbWUuXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqXG4gKiBAYmV0YVxuICovXG5jb25zdCBDTUNEX1BBUkFNID0gJ0NNQ0QnO1xuXG4vKipcbiAqIENvbnZlcnQgYSBDTUNEIGRhdGEgb2JqZWN0IHRvIGEgcXVlcnkgYXJnLlxuICpcbiAqIEBwYXJhbSBjbWNkIC0gVGhlIENNQ0Qgb2JqZWN0IHRvIGNvbnZlcnQuXG4gKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgZm9yIGVuY29kaW5nIHRoZSBDTUNEIG9iamVjdC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgQ01DRCBxdWVyeSBhcmcuXG4gKlxuICogQGdyb3VwIENNQ0RcbiAqXG4gKiBAYmV0YVxuICovXG5mdW5jdGlvbiB0b0NtY2RRdWVyeShjbWNkLCBvcHRpb25zID0ge30pIHtcbiAgaWYgKCFjbWNkKSB7XG4gICAgcmV0dXJuICcnO1xuICB9XG4gIGNvbnN0IHBhcmFtcyA9IGVuY29kZUNtY2QoY21jZCwgb3B0aW9ucyk7XG4gIHJldHVybiBgJHtDTUNEX1BBUkFNfT0ke2VuY29kZVVSSUNvbXBvbmVudChwYXJhbXMpfWA7XG59XG5cbmNvbnN0IFJFR0VYID0gL0NNQ0Q9W14mI10rLztcbi8qKlxuICogQXBwZW5kIENNQ0QgcXVlcnkgYXJncyB0byBhIFVSTC5cbiAqXG4gKiBAcGFyYW0gdXJsIC0gVGhlIFVSTCB0byBhcHBlbmQgdG8uXG4gKiBAcGFyYW0gY21jZCAtIFRoZSBDTUNEIG9iamVjdCB0byBhcHBlbmQuXG4gKiBAcGFyYW0gb3B0aW9ucyAtIE9wdGlvbnMgZm9yIGVuY29kaW5nIHRoZSBDTUNEIG9iamVjdC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgVVJMIHdpdGggdGhlIENNQ0QgcXVlcnkgYXJncyBhcHBlbmRlZC5cbiAqXG4gKiBAZ3JvdXAgQ01DRFxuICpcbiAqIEBiZXRhXG4gKi9cbmZ1bmN0aW9uIGFwcGVuZENtY2RRdWVyeSh1cmwsIGNtY2QsIG9wdGlvbnMpIHtcbiAgLy8gVE9ETzogUmVwbGFjZSB3aXRoIFVSTFNlYXJjaFBhcmFtcyBvbmNlIHdlIGRyb3AgU2FmYXJpIDwgMTAuMSAmIENocm9tZSA8IDQ5IHN1cHBvcnQuXG4gIC8vIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9VUkxTZWFyY2hQYXJhbXNcbiAgY29uc3QgcXVlcnkgPSB0b0NtY2RRdWVyeShjbWNkLCBvcHRpb25zKTtcbiAgaWYgKCFxdWVyeSkge1xuICAgIHJldHVybiB1cmw7XG4gIH1cbiAgaWYgKFJFR0VYLnRlc3QodXJsKSkge1xuICAgIHJldHVybiB1cmwucmVwbGFjZShSRUdFWCwgcXVlcnkpO1xuICB9XG4gIGNvbnN0IHNlcGFyYXRvciA9IHVybC5pbmNsdWRlcygnPycpID8gJyYnIDogJz8nO1xuICByZXR1cm4gYCR7dXJsfSR7c2VwYXJhdG9yfSR7cXVlcnl9YDtcbn1cblxuLyoqXG4gKiBDb250cm9sbGVyIHRvIGRlYWwgd2l0aCBDb21tb24gTWVkaWEgQ2xpZW50IERhdGEgKENNQ0QpXG4gKiBAc2VlIGh0dHBzOi8vY2RuLmN0YS50ZWNoL2N0YS9tZWRpYS9tZWRpYS9yZXNvdXJjZXMvc3RhbmRhcmRzL3BkZnMvY3RhLTUwMDQtZmluYWwucGRmXG4gKi9cbmNsYXNzIENNQ0RDb250cm9sbGVyIHtcbiAgLy8gZXNsaW50LWRpc2FibGUtbGluZSBuby1yZXN0cmljdGVkLWdsb2JhbHNcblxuICBjb25zdHJ1Y3RvcihobHMpIHtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLmNvbmZpZyA9IHZvaWQgMDtcbiAgICB0aGlzLm1lZGlhID0gdm9pZCAwO1xuICAgIHRoaXMuc2lkID0gdm9pZCAwO1xuICAgIHRoaXMuY2lkID0gdm9pZCAwO1xuICAgIHRoaXMudXNlSGVhZGVycyA9IGZhbHNlO1xuICAgIHRoaXMuaW5jbHVkZUtleXMgPSB2b2lkIDA7XG4gICAgdGhpcy5pbml0aWFsaXplZCA9IGZhbHNlO1xuICAgIHRoaXMuc3RhcnZlZCA9IGZhbHNlO1xuICAgIHRoaXMuYnVmZmVyaW5nID0gdHJ1ZTtcbiAgICB0aGlzLmF1ZGlvQnVmZmVyID0gdm9pZCAwO1xuICAgIC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tcmVzdHJpY3RlZC1nbG9iYWxzXG4gICAgdGhpcy52aWRlb0J1ZmZlciA9IHZvaWQgMDtcbiAgICB0aGlzLm9uV2FpdGluZyA9ICgpID0+IHtcbiAgICAgIGlmICh0aGlzLmluaXRpYWxpemVkKSB7XG4gICAgICAgIHRoaXMuc3RhcnZlZCA9IHRydWU7XG4gICAgICB9XG4gICAgICB0aGlzLmJ1ZmZlcmluZyA9IHRydWU7XG4gICAgfTtcbiAgICB0aGlzLm9uUGxheWluZyA9ICgpID0+IHtcbiAgICAgIGlmICghdGhpcy5pbml0aWFsaXplZCkge1xuICAgICAgICB0aGlzLmluaXRpYWxpemVkID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHRoaXMuYnVmZmVyaW5nID0gZmFsc2U7XG4gICAgfTtcbiAgICAvKipcbiAgICAgKiBBcHBseSBDTUNEIGRhdGEgdG8gYSBtYW5pZmVzdCByZXF1ZXN0LlxuICAgICAqL1xuICAgIHRoaXMuYXBwbHlQbGF5bGlzdERhdGEgPSBjb250ZXh0ID0+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHRoaXMuYXBwbHkoY29udGV4dCwge1xuICAgICAgICAgIG90OiBDbU9iamVjdFR5cGUuTUFOSUZFU1QsXG4gICAgICAgICAgc3U6ICF0aGlzLmluaXRpYWxpemVkXG4gICAgICAgIH0pO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ0NvdWxkIG5vdCBnZW5lcmF0ZSBtYW5pZmVzdCBDTUNEIGRhdGEuJywgZXJyb3IpO1xuICAgICAgfVxuICAgIH07XG4gICAgLyoqXG4gICAgICogQXBwbHkgQ01DRCBkYXRhIHRvIGEgc2VnbWVudCByZXF1ZXN0XG4gICAgICovXG4gICAgdGhpcy5hcHBseUZyYWdtZW50RGF0YSA9IGNvbnRleHQgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgZnJhZ21lbnQgPSBjb250ZXh0LmZyYWc7XG4gICAgICAgIGNvbnN0IGxldmVsID0gdGhpcy5obHMubGV2ZWxzW2ZyYWdtZW50LmxldmVsXTtcbiAgICAgICAgY29uc3Qgb3QgPSB0aGlzLmdldE9iamVjdFR5cGUoZnJhZ21lbnQpO1xuICAgICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICAgIGQ6IGZyYWdtZW50LmR1cmF0aW9uICogMTAwMCxcbiAgICAgICAgICBvdFxuICAgICAgICB9O1xuICAgICAgICBpZiAob3QgPT09IENtT2JqZWN0VHlwZS5WSURFTyB8fCBvdCA9PT0gQ21PYmplY3RUeXBlLkFVRElPIHx8IG90ID09IENtT2JqZWN0VHlwZS5NVVhFRCkge1xuICAgICAgICAgIGRhdGEuYnIgPSBsZXZlbC5iaXRyYXRlIC8gMTAwMDtcbiAgICAgICAgICBkYXRhLnRiID0gdGhpcy5nZXRUb3BCYW5kd2lkdGgob3QpIC8gMTAwMDtcbiAgICAgICAgICBkYXRhLmJsID0gdGhpcy5nZXRCdWZmZXJMZW5ndGgob3QpO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuYXBwbHkoY29udGV4dCwgZGF0YSk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBsb2dnZXIud2FybignQ291bGQgbm90IGdlbmVyYXRlIHNlZ21lbnQgQ01DRCBkYXRhLicsIGVycm9yKTtcbiAgICAgIH1cbiAgICB9O1xuICAgIHRoaXMuaGxzID0gaGxzO1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuY29uZmlnID0gaGxzLmNvbmZpZztcbiAgICBjb25zdCB7XG4gICAgICBjbWNkXG4gICAgfSA9IGNvbmZpZztcbiAgICBpZiAoY21jZCAhPSBudWxsKSB7XG4gICAgICBjb25maWcucExvYWRlciA9IHRoaXMuY3JlYXRlUGxheWxpc3RMb2FkZXIoKTtcbiAgICAgIGNvbmZpZy5mTG9hZGVyID0gdGhpcy5jcmVhdGVGcmFnbWVudExvYWRlcigpO1xuICAgICAgdGhpcy5zaWQgPSBjbWNkLnNlc3Npb25JZCB8fCB1dWlkKCk7XG4gICAgICB0aGlzLmNpZCA9IGNtY2QuY29udGVudElkO1xuICAgICAgdGhpcy51c2VIZWFkZXJzID0gY21jZC51c2VIZWFkZXJzID09PSB0cnVlO1xuICAgICAgdGhpcy5pbmNsdWRlS2V5cyA9IGNtY2QuaW5jbHVkZUtleXM7XG4gICAgICB0aGlzLnJlZ2lzdGVyTGlzdGVuZXJzKCk7XG4gICAgfVxuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1FRElBX0RFVEFDSEVELCB0aGlzLm9uTWVkaWFEZXRhY2hlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5CVUZGRVJfQ1JFQVRFRCwgdGhpcy5vbkJ1ZmZlckNyZWF0ZWQsIHRoaXMpO1xuICB9XG4gIHVucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NRURJQV9ERVRBQ0hFRCwgdGhpcy5vbk1lZGlhRGV0YWNoZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9DUkVBVEVELCB0aGlzLm9uQnVmZmVyQ3JlYXRlZCwgdGhpcyk7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLnVucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICB0aGlzLm9uTWVkaWFEZXRhY2hlZCgpO1xuXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMuaGxzID0gdGhpcy5jb25maWcgPSB0aGlzLmF1ZGlvQnVmZmVyID0gdGhpcy52aWRlb0J1ZmZlciA9IG51bGw7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMub25XYWl0aW5nID0gdGhpcy5vblBsYXlpbmcgPSBudWxsO1xuICB9XG4gIG9uTWVkaWFBdHRhY2hlZChldmVudCwgZGF0YSkge1xuICAgIHRoaXMubWVkaWEgPSBkYXRhLm1lZGlhO1xuICAgIHRoaXMubWVkaWEuYWRkRXZlbnRMaXN0ZW5lcignd2FpdGluZycsIHRoaXMub25XYWl0aW5nKTtcbiAgICB0aGlzLm1lZGlhLmFkZEV2ZW50TGlzdGVuZXIoJ3BsYXlpbmcnLCB0aGlzLm9uUGxheWluZyk7XG4gIH1cbiAgb25NZWRpYURldGFjaGVkKCkge1xuICAgIGlmICghdGhpcy5tZWRpYSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLm1lZGlhLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3dhaXRpbmcnLCB0aGlzLm9uV2FpdGluZyk7XG4gICAgdGhpcy5tZWRpYS5yZW1vdmVFdmVudExpc3RlbmVyKCdwbGF5aW5nJywgdGhpcy5vblBsYXlpbmcpO1xuXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICB9XG4gIG9uQnVmZmVyQ3JlYXRlZChldmVudCwgZGF0YSkge1xuICAgIHZhciBfZGF0YSR0cmFja3MkYXVkaW8sIF9kYXRhJHRyYWNrcyR2aWRlbztcbiAgICB0aGlzLmF1ZGlvQnVmZmVyID0gKF9kYXRhJHRyYWNrcyRhdWRpbyA9IGRhdGEudHJhY2tzLmF1ZGlvKSA9PSBudWxsID8gdm9pZCAwIDogX2RhdGEkdHJhY2tzJGF1ZGlvLmJ1ZmZlcjtcbiAgICB0aGlzLnZpZGVvQnVmZmVyID0gKF9kYXRhJHRyYWNrcyR2aWRlbyA9IGRhdGEudHJhY2tzLnZpZGVvKSA9PSBudWxsID8gdm9pZCAwIDogX2RhdGEkdHJhY2tzJHZpZGVvLmJ1ZmZlcjtcbiAgfVxuICAvKipcbiAgICogQ3JlYXRlIGJhc2VsaW5lIENNQ0QgZGF0YVxuICAgKi9cbiAgY3JlYXRlRGF0YSgpIHtcbiAgICB2YXIgX3RoaXMkbWVkaWE7XG4gICAgcmV0dXJuIHtcbiAgICAgIHY6IDEsXG4gICAgICBzZjogQ21TdHJlYW1pbmdGb3JtYXQuSExTLFxuICAgICAgc2lkOiB0aGlzLnNpZCxcbiAgICAgIGNpZDogdGhpcy5jaWQsXG4gICAgICBwcjogKF90aGlzJG1lZGlhID0gdGhpcy5tZWRpYSkgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJG1lZGlhLnBsYXliYWNrUmF0ZSxcbiAgICAgIG10cDogdGhpcy5obHMuYmFuZHdpZHRoRXN0aW1hdGUgLyAxMDAwXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBBcHBseSBDTUNEIGRhdGEgdG8gYSByZXF1ZXN0LlxuICAgKi9cbiAgYXBwbHkoY29udGV4dCwgZGF0YSA9IHt9KSB7XG4gICAgLy8gYXBwbHkgYmFzZWxpbmUgZGF0YVxuICAgIF9leHRlbmRzKGRhdGEsIHRoaXMuY3JlYXRlRGF0YSgpKTtcbiAgICBjb25zdCBpc1ZpZGVvID0gZGF0YS5vdCA9PT0gQ21PYmplY3RUeXBlLklOSVQgfHwgZGF0YS5vdCA9PT0gQ21PYmplY3RUeXBlLlZJREVPIHx8IGRhdGEub3QgPT09IENtT2JqZWN0VHlwZS5NVVhFRDtcbiAgICBpZiAodGhpcy5zdGFydmVkICYmIGlzVmlkZW8pIHtcbiAgICAgIGRhdGEuYnMgPSB0cnVlO1xuICAgICAgZGF0YS5zdSA9IHRydWU7XG4gICAgICB0aGlzLnN0YXJ2ZWQgPSBmYWxzZTtcbiAgICB9XG4gICAgaWYgKGRhdGEuc3UgPT0gbnVsbCkge1xuICAgICAgZGF0YS5zdSA9IHRoaXMuYnVmZmVyaW5nO1xuICAgIH1cblxuICAgIC8vIFRPRE86IEltcGxlbWVudCBydHAsIG5yciwgbm9yLCBkbFxuXG4gICAgY29uc3Qge1xuICAgICAgaW5jbHVkZUtleXNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoaW5jbHVkZUtleXMpIHtcbiAgICAgIGRhdGEgPSBPYmplY3Qua2V5cyhkYXRhKS5yZWR1Y2UoKGFjYywga2V5KSA9PiB7XG4gICAgICAgIGluY2x1ZGVLZXlzLmluY2x1ZGVzKGtleSkgJiYgKGFjY1trZXldID0gZGF0YVtrZXldKTtcbiAgICAgICAgcmV0dXJuIGFjYztcbiAgICAgIH0sIHt9KTtcbiAgICB9XG4gICAgaWYgKHRoaXMudXNlSGVhZGVycykge1xuICAgICAgaWYgKCFjb250ZXh0LmhlYWRlcnMpIHtcbiAgICAgICAgY29udGV4dC5oZWFkZXJzID0ge307XG4gICAgICB9XG4gICAgICBhcHBlbmRDbWNkSGVhZGVycyhjb250ZXh0LmhlYWRlcnMsIGRhdGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb250ZXh0LnVybCA9IGFwcGVuZENtY2RRdWVyeShjb250ZXh0LnVybCwgZGF0YSk7XG4gICAgfVxuICB9XG4gIC8qKlxuICAgKiBUaGUgQ01DRCBvYmplY3QgdHlwZS5cbiAgICovXG4gIGdldE9iamVjdFR5cGUoZnJhZ21lbnQpIHtcbiAgICBjb25zdCB7XG4gICAgICB0eXBlXG4gICAgfSA9IGZyYWdtZW50O1xuICAgIGlmICh0eXBlID09PSAnc3VidGl0bGUnKSB7XG4gICAgICByZXR1cm4gQ21PYmplY3RUeXBlLlRJTUVEX1RFWFQ7XG4gICAgfVxuICAgIGlmIChmcmFnbWVudC5zbiA9PT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgcmV0dXJuIENtT2JqZWN0VHlwZS5JTklUO1xuICAgIH1cbiAgICBpZiAodHlwZSA9PT0gJ2F1ZGlvJykge1xuICAgICAgcmV0dXJuIENtT2JqZWN0VHlwZS5BVURJTztcbiAgICB9XG4gICAgaWYgKHR5cGUgPT09ICdtYWluJykge1xuICAgICAgaWYgKCF0aGlzLmhscy5hdWRpb1RyYWNrcy5sZW5ndGgpIHtcbiAgICAgICAgcmV0dXJuIENtT2JqZWN0VHlwZS5NVVhFRDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBDbU9iamVjdFR5cGUuVklERU87XG4gICAgfVxuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBoaWdoZXN0IGJpdHJhdGUuXG4gICAqL1xuICBnZXRUb3BCYW5kd2lkdGgodHlwZSkge1xuICAgIGxldCBiaXRyYXRlID0gMDtcbiAgICBsZXQgbGV2ZWxzO1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGlmICh0eXBlID09PSBDbU9iamVjdFR5cGUuQVVESU8pIHtcbiAgICAgIGxldmVscyA9IGhscy5hdWRpb1RyYWNrcztcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgbWF4ID0gaGxzLm1heEF1dG9MZXZlbDtcbiAgICAgIGNvbnN0IGxlbiA9IG1heCA+IC0xID8gbWF4ICsgMSA6IGhscy5sZXZlbHMubGVuZ3RoO1xuICAgICAgbGV2ZWxzID0gaGxzLmxldmVscy5zbGljZSgwLCBsZW4pO1xuICAgIH1cbiAgICBmb3IgKGNvbnN0IGxldmVsIG9mIGxldmVscykge1xuICAgICAgaWYgKGxldmVsLmJpdHJhdGUgPiBiaXRyYXRlKSB7XG4gICAgICAgIGJpdHJhdGUgPSBsZXZlbC5iaXRyYXRlO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gYml0cmF0ZSA+IDAgPyBiaXRyYXRlIDogTmFOO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgYnVmZmVyIGxlbmd0aCBmb3IgYSBtZWRpYSB0eXBlIGluIG1pbGxpc2Vjb25kc1xuICAgKi9cbiAgZ2V0QnVmZmVyTGVuZ3RoKHR5cGUpIHtcbiAgICBjb25zdCBtZWRpYSA9IHRoaXMuaGxzLm1lZGlhO1xuICAgIGNvbnN0IGJ1ZmZlciA9IHR5cGUgPT09IENtT2JqZWN0VHlwZS5BVURJTyA/IHRoaXMuYXVkaW9CdWZmZXIgOiB0aGlzLnZpZGVvQnVmZmVyO1xuICAgIGlmICghYnVmZmVyIHx8ICFtZWRpYSkge1xuICAgICAgcmV0dXJuIE5hTjtcbiAgICB9XG4gICAgY29uc3QgaW5mbyA9IEJ1ZmZlckhlbHBlci5idWZmZXJJbmZvKGJ1ZmZlciwgbWVkaWEuY3VycmVudFRpbWUsIHRoaXMuY29uZmlnLm1heEJ1ZmZlckhvbGUpO1xuICAgIHJldHVybiBpbmZvLmxlbiAqIDEwMDA7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgcGxheWxpc3QgbG9hZGVyXG4gICAqL1xuICBjcmVhdGVQbGF5bGlzdExvYWRlcigpIHtcbiAgICBjb25zdCB7XG4gICAgICBwTG9hZGVyXG4gICAgfSA9IHRoaXMuY29uZmlnO1xuICAgIGNvbnN0IGFwcGx5ID0gdGhpcy5hcHBseVBsYXlsaXN0RGF0YTtcbiAgICBjb25zdCBDdG9yID0gcExvYWRlciB8fCB0aGlzLmNvbmZpZy5sb2FkZXI7XG4gICAgcmV0dXJuIGNsYXNzIENtY2RQbGF5bGlzdExvYWRlciB7XG4gICAgICBjb25zdHJ1Y3Rvcihjb25maWcpIHtcbiAgICAgICAgdGhpcy5sb2FkZXIgPSB2b2lkIDA7XG4gICAgICAgIHRoaXMubG9hZGVyID0gbmV3IEN0b3IoY29uZmlnKTtcbiAgICAgIH1cbiAgICAgIGdldCBzdGF0cygpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMubG9hZGVyLnN0YXRzO1xuICAgICAgfVxuICAgICAgZ2V0IGNvbnRleHQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmxvYWRlci5jb250ZXh0O1xuICAgICAgfVxuICAgICAgZGVzdHJveSgpIHtcbiAgICAgICAgdGhpcy5sb2FkZXIuZGVzdHJveSgpO1xuICAgICAgfVxuICAgICAgYWJvcnQoKSB7XG4gICAgICAgIHRoaXMubG9hZGVyLmFib3J0KCk7XG4gICAgICB9XG4gICAgICBsb2FkKGNvbnRleHQsIGNvbmZpZywgY2FsbGJhY2tzKSB7XG4gICAgICAgIGFwcGx5KGNvbnRleHQpO1xuICAgICAgICB0aGlzLmxvYWRlci5sb2FkKGNvbnRleHQsIGNvbmZpZywgY2FsbGJhY2tzKTtcbiAgICAgIH1cbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIHBsYXlsaXN0IGxvYWRlclxuICAgKi9cbiAgY3JlYXRlRnJhZ21lbnRMb2FkZXIoKSB7XG4gICAgY29uc3Qge1xuICAgICAgZkxvYWRlclxuICAgIH0gPSB0aGlzLmNvbmZpZztcbiAgICBjb25zdCBhcHBseSA9IHRoaXMuYXBwbHlGcmFnbWVudERhdGE7XG4gICAgY29uc3QgQ3RvciA9IGZMb2FkZXIgfHwgdGhpcy5jb25maWcubG9hZGVyO1xuICAgIHJldHVybiBjbGFzcyBDbWNkRnJhZ21lbnRMb2FkZXIge1xuICAgICAgY29uc3RydWN0b3IoY29uZmlnKSB7XG4gICAgICAgIHRoaXMubG9hZGVyID0gdm9pZCAwO1xuICAgICAgICB0aGlzLmxvYWRlciA9IG5ldyBDdG9yKGNvbmZpZyk7XG4gICAgICB9XG4gICAgICBnZXQgc3RhdHMoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmxvYWRlci5zdGF0cztcbiAgICAgIH1cbiAgICAgIGdldCBjb250ZXh0KCkge1xuICAgICAgICByZXR1cm4gdGhpcy5sb2FkZXIuY29udGV4dDtcbiAgICAgIH1cbiAgICAgIGRlc3Ryb3koKSB7XG4gICAgICAgIHRoaXMubG9hZGVyLmRlc3Ryb3koKTtcbiAgICAgIH1cbiAgICAgIGFib3J0KCkge1xuICAgICAgICB0aGlzLmxvYWRlci5hYm9ydCgpO1xuICAgICAgfVxuICAgICAgbG9hZChjb250ZXh0LCBjb25maWcsIGNhbGxiYWNrcykge1xuICAgICAgICBhcHBseShjb250ZXh0KTtcbiAgICAgICAgdGhpcy5sb2FkZXIubG9hZChjb250ZXh0LCBjb25maWcsIGNhbGxiYWNrcyk7XG4gICAgICB9XG4gICAgfTtcbiAgfVxufVxuXG5jb25zdCBQQVRIV0FZX1BFTkFMVFlfRFVSQVRJT05fTVMgPSAzMDAwMDA7XG5jbGFzcyBDb250ZW50U3RlZXJpbmdDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoaGxzKSB7XG4gICAgdGhpcy5obHMgPSB2b2lkIDA7XG4gICAgdGhpcy5sb2cgPSB2b2lkIDA7XG4gICAgdGhpcy5sb2FkZXIgPSBudWxsO1xuICAgIHRoaXMudXJpID0gbnVsbDtcbiAgICB0aGlzLnBhdGh3YXlJZCA9ICcuJztcbiAgICB0aGlzLnBhdGh3YXlQcmlvcml0eSA9IG51bGw7XG4gICAgdGhpcy50aW1lVG9Mb2FkID0gMzAwO1xuICAgIHRoaXMucmVsb2FkVGltZXIgPSAtMTtcbiAgICB0aGlzLnVwZGF0ZWQgPSAwO1xuICAgIHRoaXMuc3RhcnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuZW5hYmxlZCA9IHRydWU7XG4gICAgdGhpcy5sZXZlbHMgPSBudWxsO1xuICAgIHRoaXMuYXVkaW9UcmFja3MgPSBudWxsO1xuICAgIHRoaXMuc3VidGl0bGVUcmFja3MgPSBudWxsO1xuICAgIHRoaXMucGVuYWxpemVkUGF0aHdheXMgPSB7fTtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgICB0aGlzLmxvZyA9IGxvZ2dlci5sb2cuYmluZChsb2dnZXIsIGBbY29udGVudC1zdGVlcmluZ106YCk7XG4gICAgdGhpcy5yZWdpc3Rlckxpc3RlbmVycygpO1xuICB9XG4gIHJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FERUQsIHRoaXMub25NYW5pZmVzdExvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9QQVJTRUQsIHRoaXMub25NYW5pZmVzdFBhcnNlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5FUlJPUiwgdGhpcy5vbkVycm9yLCB0aGlzKTtcbiAgfVxuICB1bnJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IGhscyA9IHRoaXMuaGxzO1xuICAgIGlmICghaGxzKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURFRCwgdGhpcy5vbk1hbmlmZXN0TG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NQU5JRkVTVF9QQVJTRUQsIHRoaXMub25NYW5pZmVzdFBhcnNlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuRVJST1IsIHRoaXMub25FcnJvciwgdGhpcyk7XG4gIH1cbiAgc3RhcnRMb2FkKCkge1xuICAgIHRoaXMuc3RhcnRlZCA9IHRydWU7XG4gICAgdGhpcy5jbGVhclRpbWVvdXQoKTtcbiAgICBpZiAodGhpcy5lbmFibGVkICYmIHRoaXMudXJpKSB7XG4gICAgICBpZiAodGhpcy51cGRhdGVkKSB7XG4gICAgICAgIGNvbnN0IHR0bCA9IHRoaXMudGltZVRvTG9hZCAqIDEwMDAgLSAocGVyZm9ybWFuY2Uubm93KCkgLSB0aGlzLnVwZGF0ZWQpO1xuICAgICAgICBpZiAodHRsID4gMCkge1xuICAgICAgICAgIHRoaXMuc2NoZWR1bGVSZWZyZXNoKHRoaXMudXJpLCB0dGwpO1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgdGhpcy5sb2FkU3RlZXJpbmdNYW5pZmVzdCh0aGlzLnVyaSk7XG4gICAgfVxuICB9XG4gIHN0b3BMb2FkKCkge1xuICAgIHRoaXMuc3RhcnRlZCA9IGZhbHNlO1xuICAgIGlmICh0aGlzLmxvYWRlcikge1xuICAgICAgdGhpcy5sb2FkZXIuZGVzdHJveSgpO1xuICAgICAgdGhpcy5sb2FkZXIgPSBudWxsO1xuICAgIH1cbiAgICB0aGlzLmNsZWFyVGltZW91dCgpO1xuICB9XG4gIGNsZWFyVGltZW91dCgpIHtcbiAgICBpZiAodGhpcy5yZWxvYWRUaW1lciAhPT0gLTEpIHtcbiAgICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmVsb2FkVGltZXIpO1xuICAgICAgdGhpcy5yZWxvYWRUaW1lciA9IC0xO1xuICAgIH1cbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMudW5yZWdpc3Rlckxpc3RlbmVycygpO1xuICAgIHRoaXMuc3RvcExvYWQoKTtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdGhpcy5obHMgPSBudWxsO1xuICAgIHRoaXMubGV2ZWxzID0gdGhpcy5hdWRpb1RyYWNrcyA9IHRoaXMuc3VidGl0bGVUcmFja3MgPSBudWxsO1xuICB9XG4gIHJlbW92ZUxldmVsKGxldmVsVG9SZW1vdmUpIHtcbiAgICBjb25zdCBsZXZlbHMgPSB0aGlzLmxldmVscztcbiAgICBpZiAobGV2ZWxzKSB7XG4gICAgICB0aGlzLmxldmVscyA9IGxldmVscy5maWx0ZXIobGV2ZWwgPT4gbGV2ZWwgIT09IGxldmVsVG9SZW1vdmUpO1xuICAgIH1cbiAgfVxuICBvbk1hbmlmZXN0TG9hZGluZygpIHtcbiAgICB0aGlzLnN0b3BMb2FkKCk7XG4gICAgdGhpcy5lbmFibGVkID0gdHJ1ZTtcbiAgICB0aGlzLnRpbWVUb0xvYWQgPSAzMDA7XG4gICAgdGhpcy51cGRhdGVkID0gMDtcbiAgICB0aGlzLnVyaSA9IG51bGw7XG4gICAgdGhpcy5wYXRod2F5SWQgPSAnLic7XG4gICAgdGhpcy5sZXZlbHMgPSB0aGlzLmF1ZGlvVHJhY2tzID0gdGhpcy5zdWJ0aXRsZVRyYWNrcyA9IG51bGw7XG4gIH1cbiAgb25NYW5pZmVzdExvYWRlZChldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbnRlbnRTdGVlcmluZ1xuICAgIH0gPSBkYXRhO1xuICAgIGlmIChjb250ZW50U3RlZXJpbmcgPT09IG51bGwpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5wYXRod2F5SWQgPSBjb250ZW50U3RlZXJpbmcucGF0aHdheUlkO1xuICAgIHRoaXMudXJpID0gY29udGVudFN0ZWVyaW5nLnVyaTtcbiAgICBpZiAodGhpcy5zdGFydGVkKSB7XG4gICAgICB0aGlzLnN0YXJ0TG9hZCgpO1xuICAgIH1cbiAgfVxuICBvbk1hbmlmZXN0UGFyc2VkKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5hdWRpb1RyYWNrcyA9IGRhdGEuYXVkaW9UcmFja3M7XG4gICAgdGhpcy5zdWJ0aXRsZVRyYWNrcyA9IGRhdGEuc3VidGl0bGVUcmFja3M7XG4gIH1cbiAgb25FcnJvcihldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGVycm9yQWN0aW9uXG4gICAgfSA9IGRhdGE7XG4gICAgaWYgKChlcnJvckFjdGlvbiA9PSBudWxsID8gdm9pZCAwIDogZXJyb3JBY3Rpb24uYWN0aW9uKSA9PT0gTmV0d29ya0Vycm9yQWN0aW9uLlNlbmRBbHRlcm5hdGVUb1BlbmFsdHlCb3ggJiYgZXJyb3JBY3Rpb24uZmxhZ3MgPT09IEVycm9yQWN0aW9uRmxhZ3MuTW92ZUFsbEFsdGVybmF0ZXNNYXRjaGluZ0hvc3QpIHtcbiAgICAgIGNvbnN0IGxldmVscyA9IHRoaXMubGV2ZWxzO1xuICAgICAgbGV0IHBhdGh3YXlQcmlvcml0eSA9IHRoaXMucGF0aHdheVByaW9yaXR5O1xuICAgICAgbGV0IGVycm9yUGF0aHdheSA9IHRoaXMucGF0aHdheUlkO1xuICAgICAgaWYgKGRhdGEuY29udGV4dCkge1xuICAgICAgICBjb25zdCB7XG4gICAgICAgICAgZ3JvdXBJZCxcbiAgICAgICAgICBwYXRod2F5SWQsXG4gICAgICAgICAgdHlwZVxuICAgICAgICB9ID0gZGF0YS5jb250ZXh0O1xuICAgICAgICBpZiAoZ3JvdXBJZCAmJiBsZXZlbHMpIHtcbiAgICAgICAgICBlcnJvclBhdGh3YXkgPSB0aGlzLmdldFBhdGh3YXlGb3JHcm91cElkKGdyb3VwSWQsIHR5cGUsIGVycm9yUGF0aHdheSk7XG4gICAgICAgIH0gZWxzZSBpZiAocGF0aHdheUlkKSB7XG4gICAgICAgICAgZXJyb3JQYXRod2F5ID0gcGF0aHdheUlkO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAoIShlcnJvclBhdGh3YXkgaW4gdGhpcy5wZW5hbGl6ZWRQYXRod2F5cykpIHtcbiAgICAgICAgdGhpcy5wZW5hbGl6ZWRQYXRod2F5c1tlcnJvclBhdGh3YXldID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICB9XG4gICAgICBpZiAoIXBhdGh3YXlQcmlvcml0eSAmJiBsZXZlbHMpIHtcbiAgICAgICAgLy8gSWYgUEFUSFdBWS1QUklPUklUWSB3YXMgbm90IHByb3ZpZGVkLCBsaXN0IHBhdGh3YXlzIGZvciBlcnJvciBoYW5kbGluZ1xuICAgICAgICBwYXRod2F5UHJpb3JpdHkgPSBsZXZlbHMucmVkdWNlKChwYXRod2F5cywgbGV2ZWwpID0+IHtcbiAgICAgICAgICBpZiAocGF0aHdheXMuaW5kZXhPZihsZXZlbC5wYXRod2F5SWQpID09PSAtMSkge1xuICAgICAgICAgICAgcGF0aHdheXMucHVzaChsZXZlbC5wYXRod2F5SWQpO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4gcGF0aHdheXM7XG4gICAgICAgIH0sIFtdKTtcbiAgICAgIH1cbiAgICAgIGlmIChwYXRod2F5UHJpb3JpdHkgJiYgcGF0aHdheVByaW9yaXR5Lmxlbmd0aCA+IDEpIHtcbiAgICAgICAgdGhpcy51cGRhdGVQYXRod2F5UHJpb3JpdHkocGF0aHdheVByaW9yaXR5KTtcbiAgICAgICAgZXJyb3JBY3Rpb24ucmVzb2x2ZWQgPSB0aGlzLnBhdGh3YXlJZCAhPT0gZXJyb3JQYXRod2F5O1xuICAgICAgfVxuICAgICAgaWYgKCFlcnJvckFjdGlvbi5yZXNvbHZlZCkge1xuICAgICAgICBsb2dnZXIud2FybihgQ291bGQgbm90IHJlc29sdmUgJHtkYXRhLmRldGFpbHN9IChcIiR7ZGF0YS5lcnJvci5tZXNzYWdlfVwiKSB3aXRoIGNvbnRlbnQtc3RlZXJpbmcgZm9yIFBhdGh3YXk6ICR7ZXJyb3JQYXRod2F5fSBsZXZlbHM6ICR7bGV2ZWxzID8gbGV2ZWxzLmxlbmd0aCA6IGxldmVsc30gcHJpb3JpdGllczogJHtKU09OLnN0cmluZ2lmeShwYXRod2F5UHJpb3JpdHkpfSBwZW5hbGl6ZWQ6ICR7SlNPTi5zdHJpbmdpZnkodGhpcy5wZW5hbGl6ZWRQYXRod2F5cyl9YCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGZpbHRlclBhcnNlZExldmVscyhsZXZlbHMpIHtcbiAgICAvLyBGaWx0ZXIgbGV2ZWxzIHRvIG9ubHkgaW5jbHVkZSB0aG9zZSB0aGF0IGFyZSBpbiB0aGUgaW5pdGlhbCBwYXRod2F5XG4gICAgdGhpcy5sZXZlbHMgPSBsZXZlbHM7XG4gICAgbGV0IHBhdGh3YXlMZXZlbHMgPSB0aGlzLmdldExldmVsc0ZvclBhdGh3YXkodGhpcy5wYXRod2F5SWQpO1xuICAgIGlmIChwYXRod2F5TGV2ZWxzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgY29uc3QgcGF0aHdheUlkID0gbGV2ZWxzWzBdLnBhdGh3YXlJZDtcbiAgICAgIHRoaXMubG9nKGBObyBsZXZlbHMgZm91bmQgaW4gUGF0aHdheSAke3RoaXMucGF0aHdheUlkfS4gU2V0dGluZyBpbml0aWFsIFBhdGh3YXkgdG8gXCIke3BhdGh3YXlJZH1cImApO1xuICAgICAgcGF0aHdheUxldmVscyA9IHRoaXMuZ2V0TGV2ZWxzRm9yUGF0aHdheShwYXRod2F5SWQpO1xuICAgICAgdGhpcy5wYXRod2F5SWQgPSBwYXRod2F5SWQ7XG4gICAgfVxuICAgIGlmIChwYXRod2F5TGV2ZWxzLmxlbmd0aCAhPT0gbGV2ZWxzLmxlbmd0aCkge1xuICAgICAgdGhpcy5sb2coYEZvdW5kICR7cGF0aHdheUxldmVscy5sZW5ndGh9LyR7bGV2ZWxzLmxlbmd0aH0gbGV2ZWxzIGluIFBhdGh3YXkgXCIke3RoaXMucGF0aHdheUlkfVwiYCk7XG4gICAgICByZXR1cm4gcGF0aHdheUxldmVscztcbiAgICB9XG4gICAgcmV0dXJuIGxldmVscztcbiAgfVxuICBnZXRMZXZlbHNGb3JQYXRod2F5KHBhdGh3YXlJZCkge1xuICAgIGlmICh0aGlzLmxldmVscyA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuIFtdO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5sZXZlbHMuZmlsdGVyKGxldmVsID0+IHBhdGh3YXlJZCA9PT0gbGV2ZWwucGF0aHdheUlkKTtcbiAgfVxuICB1cGRhdGVQYXRod2F5UHJpb3JpdHkocGF0aHdheVByaW9yaXR5KSB7XG4gICAgdGhpcy5wYXRod2F5UHJpb3JpdHkgPSBwYXRod2F5UHJpb3JpdHk7XG4gICAgbGV0IGxldmVscztcblxuICAgIC8vIEV2YWx1YXRlIGlmIHdlIHNob3VsZCByZW1vdmUgdGhlIHBhdGh3YXkgZnJvbSB0aGUgcGVuYWxpemVkIGxpc3RcbiAgICBjb25zdCBwZW5hbGl6ZWRQYXRod2F5cyA9IHRoaXMucGVuYWxpemVkUGF0aHdheXM7XG4gICAgY29uc3Qgbm93ID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgT2JqZWN0LmtleXMocGVuYWxpemVkUGF0aHdheXMpLmZvckVhY2gocGF0aHdheUlkID0+IHtcbiAgICAgIGlmIChub3cgLSBwZW5hbGl6ZWRQYXRod2F5c1twYXRod2F5SWRdID4gUEFUSFdBWV9QRU5BTFRZX0RVUkFUSU9OX01TKSB7XG4gICAgICAgIGRlbGV0ZSBwZW5hbGl6ZWRQYXRod2F5c1twYXRod2F5SWRdO1xuICAgICAgfVxuICAgIH0pO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgcGF0aHdheVByaW9yaXR5Lmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBwYXRod2F5SWQgPSBwYXRod2F5UHJpb3JpdHlbaV07XG4gICAgICBpZiAocGF0aHdheUlkIGluIHBlbmFsaXplZFBhdGh3YXlzKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgaWYgKHBhdGh3YXlJZCA9PT0gdGhpcy5wYXRod2F5SWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgY29uc3Qgc2VsZWN0ZWRJbmRleCA9IHRoaXMuaGxzLm5leHRMb2FkTGV2ZWw7XG4gICAgICBjb25zdCBzZWxlY3RlZExldmVsID0gdGhpcy5obHMubGV2ZWxzW3NlbGVjdGVkSW5kZXhdO1xuICAgICAgbGV2ZWxzID0gdGhpcy5nZXRMZXZlbHNGb3JQYXRod2F5KHBhdGh3YXlJZCk7XG4gICAgICBpZiAobGV2ZWxzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgdGhpcy5sb2coYFNldHRpbmcgUGF0aHdheSB0byBcIiR7cGF0aHdheUlkfVwiYCk7XG4gICAgICAgIHRoaXMucGF0aHdheUlkID0gcGF0aHdheUlkO1xuICAgICAgICByZWFzc2lnbkZyYWdtZW50TGV2ZWxJbmRleGVzKGxldmVscyk7XG4gICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkxFVkVMU19VUERBVEVELCB7XG4gICAgICAgICAgbGV2ZWxzXG4gICAgICAgIH0pO1xuICAgICAgICAvLyBTZXQgTGV2ZWxDb250cm9sbGVyJ3MgbGV2ZWwgdG8gdHJpZ2dlciBMRVZFTF9TV0lUQ0hJTkcgd2hpY2ggbG9hZHMgcGxheWxpc3QgaWYgbmVlZGVkXG4gICAgICAgIGNvbnN0IGxldmVsQWZ0ZXJDaGFuZ2UgPSB0aGlzLmhscy5sZXZlbHNbc2VsZWN0ZWRJbmRleF07XG4gICAgICAgIGlmIChzZWxlY3RlZExldmVsICYmIGxldmVsQWZ0ZXJDaGFuZ2UgJiYgdGhpcy5sZXZlbHMpIHtcbiAgICAgICAgICBpZiAobGV2ZWxBZnRlckNoYW5nZS5hdHRyc1snU1RBQkxFLVZBUklBTlQtSUQnXSAhPT0gc2VsZWN0ZWRMZXZlbC5hdHRyc1snU1RBQkxFLVZBUklBTlQtSUQnXSAmJiBsZXZlbEFmdGVyQ2hhbmdlLmJpdHJhdGUgIT09IHNlbGVjdGVkTGV2ZWwuYml0cmF0ZSkge1xuICAgICAgICAgICAgdGhpcy5sb2coYFVuc3RhYmxlIFBhdGh3YXlzIGNoYW5nZSBmcm9tIGJpdHJhdGUgJHtzZWxlY3RlZExldmVsLmJpdHJhdGV9IHRvICR7bGV2ZWxBZnRlckNoYW5nZS5iaXRyYXRlfWApO1xuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLmhscy5uZXh0TG9hZExldmVsID0gc2VsZWN0ZWRJbmRleDtcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgZ2V0UGF0aHdheUZvckdyb3VwSWQoZ3JvdXBJZCwgdHlwZSwgZGVmYXVsdFBhdGh3YXkpIHtcbiAgICBjb25zdCBsZXZlbHMgPSB0aGlzLmdldExldmVsc0ZvclBhdGh3YXkoZGVmYXVsdFBhdGh3YXkpLmNvbmNhdCh0aGlzLmxldmVscyB8fCBbXSk7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsZXZlbHMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGlmICh0eXBlID09PSBQbGF5bGlzdENvbnRleHRUeXBlLkFVRElPX1RSQUNLICYmIGxldmVsc1tpXS5oYXNBdWRpb0dyb3VwKGdyb3VwSWQpIHx8IHR5cGUgPT09IFBsYXlsaXN0Q29udGV4dFR5cGUuU1VCVElUTEVfVFJBQ0sgJiYgbGV2ZWxzW2ldLmhhc1N1YnRpdGxlR3JvdXAoZ3JvdXBJZCkpIHtcbiAgICAgICAgcmV0dXJuIGxldmVsc1tpXS5wYXRod2F5SWQ7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBkZWZhdWx0UGF0aHdheTtcbiAgfVxuICBjbG9uZVBhdGh3YXlzKHBhdGh3YXlDbG9uZXMpIHtcbiAgICBjb25zdCBsZXZlbHMgPSB0aGlzLmxldmVscztcbiAgICBpZiAoIWxldmVscykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBhdWRpb0dyb3VwQ2xvbmVNYXAgPSB7fTtcbiAgICBjb25zdCBzdWJ0aXRsZUdyb3VwQ2xvbmVNYXAgPSB7fTtcbiAgICBwYXRod2F5Q2xvbmVzLmZvckVhY2gocGF0aHdheUNsb25lID0+IHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgSUQ6IGNsb25lSWQsXG4gICAgICAgICdCQVNFLUlEJzogYmFzZUlkLFxuICAgICAgICAnVVJJLVJFUExBQ0VNRU5UJzogdXJpUmVwbGFjZW1lbnRcbiAgICAgIH0gPSBwYXRod2F5Q2xvbmU7XG4gICAgICBpZiAobGV2ZWxzLnNvbWUobGV2ZWwgPT4gbGV2ZWwucGF0aHdheUlkID09PSBjbG9uZUlkKSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBjbG9uZWRWYXJpYW50cyA9IHRoaXMuZ2V0TGV2ZWxzRm9yUGF0aHdheShiYXNlSWQpLm1hcChiYXNlTGV2ZWwgPT4ge1xuICAgICAgICBjb25zdCBhdHRyaWJ1dGVzID0gbmV3IEF0dHJMaXN0KGJhc2VMZXZlbC5hdHRycyk7XG4gICAgICAgIGF0dHJpYnV0ZXNbJ1BBVEhXQVktSUQnXSA9IGNsb25lSWQ7XG4gICAgICAgIGNvbnN0IGNsb25lZEF1ZGlvR3JvdXBJZCA9IGF0dHJpYnV0ZXMuQVVESU8gJiYgYCR7YXR0cmlidXRlcy5BVURJT31fY2xvbmVfJHtjbG9uZUlkfWA7XG4gICAgICAgIGNvbnN0IGNsb25lZFN1YnRpdGxlR3JvdXBJZCA9IGF0dHJpYnV0ZXMuU1VCVElUTEVTICYmIGAke2F0dHJpYnV0ZXMuU1VCVElUTEVTfV9jbG9uZV8ke2Nsb25lSWR9YDtcbiAgICAgICAgaWYgKGNsb25lZEF1ZGlvR3JvdXBJZCkge1xuICAgICAgICAgIGF1ZGlvR3JvdXBDbG9uZU1hcFthdHRyaWJ1dGVzLkFVRElPXSA9IGNsb25lZEF1ZGlvR3JvdXBJZDtcbiAgICAgICAgICBhdHRyaWJ1dGVzLkFVRElPID0gY2xvbmVkQXVkaW9Hcm91cElkO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjbG9uZWRTdWJ0aXRsZUdyb3VwSWQpIHtcbiAgICAgICAgICBzdWJ0aXRsZUdyb3VwQ2xvbmVNYXBbYXR0cmlidXRlcy5TVUJUSVRMRVNdID0gY2xvbmVkU3VidGl0bGVHcm91cElkO1xuICAgICAgICAgIGF0dHJpYnV0ZXMuU1VCVElUTEVTID0gY2xvbmVkU3VidGl0bGVHcm91cElkO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHVybCA9IHBlcmZvcm1VcmlSZXBsYWNlbWVudChiYXNlTGV2ZWwudXJpLCBhdHRyaWJ1dGVzWydTVEFCTEUtVkFSSUFOVC1JRCddLCAnUEVSLVZBUklBTlQtVVJJUycsIHVyaVJlcGxhY2VtZW50KTtcbiAgICAgICAgY29uc3QgY2xvbmVkTGV2ZWwgPSBuZXcgTGV2ZWwoe1xuICAgICAgICAgIGF0dHJzOiBhdHRyaWJ1dGVzLFxuICAgICAgICAgIGF1ZGlvQ29kZWM6IGJhc2VMZXZlbC5hdWRpb0NvZGVjLFxuICAgICAgICAgIGJpdHJhdGU6IGJhc2VMZXZlbC5iaXRyYXRlLFxuICAgICAgICAgIGhlaWdodDogYmFzZUxldmVsLmhlaWdodCxcbiAgICAgICAgICBuYW1lOiBiYXNlTGV2ZWwubmFtZSxcbiAgICAgICAgICB1cmwsXG4gICAgICAgICAgdmlkZW9Db2RlYzogYmFzZUxldmVsLnZpZGVvQ29kZWMsXG4gICAgICAgICAgd2lkdGg6IGJhc2VMZXZlbC53aWR0aFxuICAgICAgICB9KTtcbiAgICAgICAgaWYgKGJhc2VMZXZlbC5hdWRpb0dyb3Vwcykge1xuICAgICAgICAgIGZvciAobGV0IGkgPSAxOyBpIDwgYmFzZUxldmVsLmF1ZGlvR3JvdXBzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBjbG9uZWRMZXZlbC5hZGRHcm91cElkKCdhdWRpbycsIGAke2Jhc2VMZXZlbC5hdWRpb0dyb3Vwc1tpXX1fY2xvbmVfJHtjbG9uZUlkfWApO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBpZiAoYmFzZUxldmVsLnN1YnRpdGxlR3JvdXBzKSB7XG4gICAgICAgICAgZm9yIChsZXQgaSA9IDE7IGkgPCBiYXNlTGV2ZWwuc3VidGl0bGVHcm91cHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGNsb25lZExldmVsLmFkZEdyb3VwSWQoJ3RleHQnLCBgJHtiYXNlTGV2ZWwuc3VidGl0bGVHcm91cHNbaV19X2Nsb25lXyR7Y2xvbmVJZH1gKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGNsb25lZExldmVsO1xuICAgICAgfSk7XG4gICAgICBsZXZlbHMucHVzaCguLi5jbG9uZWRWYXJpYW50cyk7XG4gICAgICBjbG9uZVJlbmRpdGlvbkdyb3Vwcyh0aGlzLmF1ZGlvVHJhY2tzLCBhdWRpb0dyb3VwQ2xvbmVNYXAsIHVyaVJlcGxhY2VtZW50LCBjbG9uZUlkKTtcbiAgICAgIGNsb25lUmVuZGl0aW9uR3JvdXBzKHRoaXMuc3VidGl0bGVUcmFja3MsIHN1YnRpdGxlR3JvdXBDbG9uZU1hcCwgdXJpUmVwbGFjZW1lbnQsIGNsb25lSWQpO1xuICAgIH0pO1xuICB9XG4gIGxvYWRTdGVlcmluZ01hbmlmZXN0KHVyaSkge1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuaGxzLmNvbmZpZztcbiAgICBjb25zdCBMb2FkZXIgPSBjb25maWcubG9hZGVyO1xuICAgIGlmICh0aGlzLmxvYWRlcikge1xuICAgICAgdGhpcy5sb2FkZXIuZGVzdHJveSgpO1xuICAgIH1cbiAgICB0aGlzLmxvYWRlciA9IG5ldyBMb2FkZXIoY29uZmlnKTtcbiAgICBsZXQgdXJsO1xuICAgIHRyeSB7XG4gICAgICB1cmwgPSBuZXcgc2VsZi5VUkwodXJpKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhpcy5lbmFibGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmxvZyhgRmFpbGVkIHRvIHBhcnNlIFN0ZWVyaW5nIE1hbmlmZXN0IFVSSTogJHt1cml9YCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh1cmwucHJvdG9jb2wgIT09ICdkYXRhOicpIHtcbiAgICAgIGNvbnN0IHRocm91Z2hwdXQgPSAodGhpcy5obHMuYmFuZHdpZHRoRXN0aW1hdGUgfHwgY29uZmlnLmFickV3bWFEZWZhdWx0RXN0aW1hdGUpIHwgMDtcbiAgICAgIHVybC5zZWFyY2hQYXJhbXMuc2V0KCdfSExTX3BhdGh3YXknLCB0aGlzLnBhdGh3YXlJZCk7XG4gICAgICB1cmwuc2VhcmNoUGFyYW1zLnNldCgnX0hMU190aHJvdWdocHV0JywgJycgKyB0aHJvdWdocHV0KTtcbiAgICB9XG4gICAgY29uc3QgY29udGV4dCA9IHtcbiAgICAgIHJlc3BvbnNlVHlwZTogJ2pzb24nLFxuICAgICAgdXJsOiB1cmwuaHJlZlxuICAgIH07XG4gICAgY29uc3QgbG9hZFBvbGljeSA9IGNvbmZpZy5zdGVlcmluZ01hbmlmZXN0TG9hZFBvbGljeS5kZWZhdWx0O1xuICAgIGNvbnN0IGxlZ2FjeVJldHJ5Q29tcGF0aWJpbGl0eSA9IGxvYWRQb2xpY3kuZXJyb3JSZXRyeSB8fCBsb2FkUG9saWN5LnRpbWVvdXRSZXRyeSB8fCB7fTtcbiAgICBjb25zdCBsb2FkZXJDb25maWcgPSB7XG4gICAgICBsb2FkUG9saWN5LFxuICAgICAgdGltZW91dDogbG9hZFBvbGljeS5tYXhMb2FkVGltZU1zLFxuICAgICAgbWF4UmV0cnk6IGxlZ2FjeVJldHJ5Q29tcGF0aWJpbGl0eS5tYXhOdW1SZXRyeSB8fCAwLFxuICAgICAgcmV0cnlEZWxheTogbGVnYWN5UmV0cnlDb21wYXRpYmlsaXR5LnJldHJ5RGVsYXlNcyB8fCAwLFxuICAgICAgbWF4UmV0cnlEZWxheTogbGVnYWN5UmV0cnlDb21wYXRpYmlsaXR5Lm1heFJldHJ5RGVsYXlNcyB8fCAwXG4gICAgfTtcbiAgICBjb25zdCBjYWxsYmFja3MgPSB7XG4gICAgICBvblN1Y2Nlc3M6IChyZXNwb25zZSwgc3RhdHMsIGNvbnRleHQsIG5ldHdvcmtEZXRhaWxzKSA9PiB7XG4gICAgICAgIHRoaXMubG9nKGBMb2FkZWQgc3RlZXJpbmcgbWFuaWZlc3Q6IFwiJHt1cmx9XCJgKTtcbiAgICAgICAgY29uc3Qgc3RlZXJpbmdEYXRhID0gcmVzcG9uc2UuZGF0YTtcbiAgICAgICAgaWYgKHN0ZWVyaW5nRGF0YS5WRVJTSU9OICE9PSAxKSB7XG4gICAgICAgICAgdGhpcy5sb2coYFN0ZWVyaW5nIFZFUlNJT04gJHtzdGVlcmluZ0RhdGEuVkVSU0lPTn0gbm90IHN1cHBvcnRlZCFgKTtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy51cGRhdGVkID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgIHRoaXMudGltZVRvTG9hZCA9IHN0ZWVyaW5nRGF0YS5UVEw7XG4gICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAnUkVMT0FELVVSSSc6IHJlbG9hZFVyaSxcbiAgICAgICAgICAnUEFUSFdBWS1DTE9ORVMnOiBwYXRod2F5Q2xvbmVzLFxuICAgICAgICAgICdQQVRIV0FZLVBSSU9SSVRZJzogcGF0aHdheVByaW9yaXR5XG4gICAgICAgIH0gPSBzdGVlcmluZ0RhdGE7XG4gICAgICAgIGlmIChyZWxvYWRVcmkpIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgdGhpcy51cmkgPSBuZXcgc2VsZi5VUkwocmVsb2FkVXJpLCB1cmwpLmhyZWY7XG4gICAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgIHRoaXMuZW5hYmxlZCA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5sb2coYEZhaWxlZCB0byBwYXJzZSBTdGVlcmluZyBNYW5pZmVzdCBSRUxPQUQtVVJJOiAke3JlbG9hZFVyaX1gKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5zY2hlZHVsZVJlZnJlc2godGhpcy51cmkgfHwgY29udGV4dC51cmwpO1xuICAgICAgICBpZiAocGF0aHdheUNsb25lcykge1xuICAgICAgICAgIHRoaXMuY2xvbmVQYXRod2F5cyhwYXRod2F5Q2xvbmVzKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBsb2FkZWRTdGVlcmluZ0RhdGEgPSB7XG4gICAgICAgICAgc3RlZXJpbmdNYW5pZmVzdDogc3RlZXJpbmdEYXRhLFxuICAgICAgICAgIHVybDogdXJsLnRvU3RyaW5nKClcbiAgICAgICAgfTtcbiAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuU1RFRVJJTkdfTUFOSUZFU1RfTE9BREVELCBsb2FkZWRTdGVlcmluZ0RhdGEpO1xuICAgICAgICBpZiAocGF0aHdheVByaW9yaXR5KSB7XG4gICAgICAgICAgdGhpcy51cGRhdGVQYXRod2F5UHJpb3JpdHkocGF0aHdheVByaW9yaXR5KTtcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIG9uRXJyb3I6IChlcnJvciwgY29udGV4dCwgbmV0d29ya0RldGFpbHMsIHN0YXRzKSA9PiB7XG4gICAgICAgIHRoaXMubG9nKGBFcnJvciBsb2FkaW5nIHN0ZWVyaW5nIG1hbmlmZXN0OiAke2Vycm9yLmNvZGV9ICR7ZXJyb3IudGV4dH0gKCR7Y29udGV4dC51cmx9KWApO1xuICAgICAgICB0aGlzLnN0b3BMb2FkKCk7XG4gICAgICAgIGlmIChlcnJvci5jb2RlID09PSA0MTApIHtcbiAgICAgICAgICB0aGlzLmVuYWJsZWQgPSBmYWxzZTtcbiAgICAgICAgICB0aGlzLmxvZyhgU3RlZXJpbmcgbWFuaWZlc3QgJHtjb250ZXh0LnVybH0gbm8gbG9uZ2VyIGF2YWlsYWJsZWApO1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBsZXQgdHRsID0gdGhpcy50aW1lVG9Mb2FkICogMTAwMDtcbiAgICAgICAgaWYgKGVycm9yLmNvZGUgPT09IDQyOSkge1xuICAgICAgICAgIGNvbnN0IGxvYWRlciA9IHRoaXMubG9hZGVyO1xuICAgICAgICAgIGlmICh0eXBlb2YgKGxvYWRlciA9PSBudWxsID8gdm9pZCAwIDogbG9hZGVyLmdldFJlc3BvbnNlSGVhZGVyKSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAgICAgY29uc3QgcmV0cnlBZnRlciA9IGxvYWRlci5nZXRSZXNwb25zZUhlYWRlcignUmV0cnktQWZ0ZXInKTtcbiAgICAgICAgICAgIGlmIChyZXRyeUFmdGVyKSB7XG4gICAgICAgICAgICAgIHR0bCA9IHBhcnNlRmxvYXQocmV0cnlBZnRlcikgKiAxMDAwO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLmxvZyhgU3RlZXJpbmcgbWFuaWZlc3QgJHtjb250ZXh0LnVybH0gcmF0ZSBsaW1pdGVkYCk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuc2NoZWR1bGVSZWZyZXNoKHRoaXMudXJpIHx8IGNvbnRleHQudXJsLCB0dGwpO1xuICAgICAgfSxcbiAgICAgIG9uVGltZW91dDogKHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICB0aGlzLmxvZyhgVGltZW91dCBsb2FkaW5nIHN0ZWVyaW5nIG1hbmlmZXN0ICgke2NvbnRleHQudXJsfSlgKTtcbiAgICAgICAgdGhpcy5zY2hlZHVsZVJlZnJlc2godGhpcy51cmkgfHwgY29udGV4dC51cmwpO1xuICAgICAgfVxuICAgIH07XG4gICAgdGhpcy5sb2coYFJlcXVlc3Rpbmcgc3RlZXJpbmcgbWFuaWZlc3Q6ICR7dXJsfWApO1xuICAgIHRoaXMubG9hZGVyLmxvYWQoY29udGV4dCwgbG9hZGVyQ29uZmlnLCBjYWxsYmFja3MpO1xuICB9XG4gIHNjaGVkdWxlUmVmcmVzaCh1cmksIHR0bE1zID0gdGhpcy50aW1lVG9Mb2FkICogMTAwMCkge1xuICAgIHRoaXMuY2xlYXJUaW1lb3V0KCk7XG4gICAgdGhpcy5yZWxvYWRUaW1lciA9IHNlbGYuc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICB2YXIgX3RoaXMkaGxzO1xuICAgICAgY29uc3QgbWVkaWEgPSAoX3RoaXMkaGxzID0gdGhpcy5obHMpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRobHMubWVkaWE7XG4gICAgICBpZiAobWVkaWEgJiYgIW1lZGlhLmVuZGVkKSB7XG4gICAgICAgIHRoaXMubG9hZFN0ZWVyaW5nTWFuaWZlc3QodXJpKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5zY2hlZHVsZVJlZnJlc2godXJpLCB0aGlzLnRpbWVUb0xvYWQgKiAxMDAwKTtcbiAgICB9LCB0dGxNcyk7XG4gIH1cbn1cbmZ1bmN0aW9uIGNsb25lUmVuZGl0aW9uR3JvdXBzKHRyYWNrcywgZ3JvdXBDbG9uZU1hcCwgdXJpUmVwbGFjZW1lbnQsIGNsb25lSWQpIHtcbiAgaWYgKCF0cmFja3MpIHtcbiAgICByZXR1cm47XG4gIH1cbiAgT2JqZWN0LmtleXMoZ3JvdXBDbG9uZU1hcCkuZm9yRWFjaChhdWRpb0dyb3VwSWQgPT4ge1xuICAgIGNvbnN0IGNsb25lZFRyYWNrcyA9IHRyYWNrcy5maWx0ZXIodHJhY2sgPT4gdHJhY2suZ3JvdXBJZCA9PT0gYXVkaW9Hcm91cElkKS5tYXAodHJhY2sgPT4ge1xuICAgICAgY29uc3QgY2xvbmVkVHJhY2sgPSBfZXh0ZW5kcyh7fSwgdHJhY2spO1xuICAgICAgY2xvbmVkVHJhY2suZGV0YWlscyA9IHVuZGVmaW5lZDtcbiAgICAgIGNsb25lZFRyYWNrLmF0dHJzID0gbmV3IEF0dHJMaXN0KGNsb25lZFRyYWNrLmF0dHJzKTtcbiAgICAgIGNsb25lZFRyYWNrLnVybCA9IGNsb25lZFRyYWNrLmF0dHJzLlVSSSA9IHBlcmZvcm1VcmlSZXBsYWNlbWVudCh0cmFjay51cmwsIHRyYWNrLmF0dHJzWydTVEFCTEUtUkVORElUSU9OLUlEJ10sICdQRVItUkVORElUSU9OLVVSSVMnLCB1cmlSZXBsYWNlbWVudCk7XG4gICAgICBjbG9uZWRUcmFjay5ncm91cElkID0gY2xvbmVkVHJhY2suYXR0cnNbJ0dST1VQLUlEJ10gPSBncm91cENsb25lTWFwW2F1ZGlvR3JvdXBJZF07XG4gICAgICBjbG9uZWRUcmFjay5hdHRyc1snUEFUSFdBWS1JRCddID0gY2xvbmVJZDtcbiAgICAgIHJldHVybiBjbG9uZWRUcmFjaztcbiAgICB9KTtcbiAgICB0cmFja3MucHVzaCguLi5jbG9uZWRUcmFja3MpO1xuICB9KTtcbn1cbmZ1bmN0aW9uIHBlcmZvcm1VcmlSZXBsYWNlbWVudCh1cmksIHN0YWJsZUlkLCBwZXJPcHRpb25LZXksIHVyaVJlcGxhY2VtZW50KSB7XG4gIGNvbnN0IHtcbiAgICBIT1NUOiBob3N0LFxuICAgIFBBUkFNUzogcGFyYW1zLFxuICAgIFtwZXJPcHRpb25LZXldOiBwZXJPcHRpb25VcmlzXG4gIH0gPSB1cmlSZXBsYWNlbWVudDtcbiAgbGV0IHBlclZhcmlhbnRVcmk7XG4gIGlmIChzdGFibGVJZCkge1xuICAgIHBlclZhcmlhbnRVcmkgPSBwZXJPcHRpb25VcmlzID09IG51bGwgPyB2b2lkIDAgOiBwZXJPcHRpb25VcmlzW3N0YWJsZUlkXTtcbiAgICBpZiAocGVyVmFyaWFudFVyaSkge1xuICAgICAgdXJpID0gcGVyVmFyaWFudFVyaTtcbiAgICB9XG4gIH1cbiAgY29uc3QgdXJsID0gbmV3IHNlbGYuVVJMKHVyaSk7XG4gIGlmIChob3N0ICYmICFwZXJWYXJpYW50VXJpKSB7XG4gICAgdXJsLmhvc3QgPSBob3N0O1xuICB9XG4gIGlmIChwYXJhbXMpIHtcbiAgICBPYmplY3Qua2V5cyhwYXJhbXMpLnNvcnQoKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBpZiAoa2V5KSB7XG4gICAgICAgIHVybC5zZWFyY2hQYXJhbXMuc2V0KGtleSwgcGFyYW1zW2tleV0pO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG4gIHJldHVybiB1cmwuaHJlZjtcbn1cblxuY29uc3QgQUdFX0hFQURFUl9MSU5FX1JFR0VYID0gL15hZ2U6XFxzKltcXGQuXStcXHMqJC9pbTtcbmNsYXNzIFhockxvYWRlciB7XG4gIGNvbnN0cnVjdG9yKGNvbmZpZykge1xuICAgIHRoaXMueGhyU2V0dXAgPSB2b2lkIDA7XG4gICAgdGhpcy5yZXF1ZXN0VGltZW91dCA9IHZvaWQgMDtcbiAgICB0aGlzLnJldHJ5VGltZW91dCA9IHZvaWQgMDtcbiAgICB0aGlzLnJldHJ5RGVsYXkgPSB2b2lkIDA7XG4gICAgdGhpcy5jb25maWcgPSBudWxsO1xuICAgIHRoaXMuY2FsbGJhY2tzID0gbnVsbDtcbiAgICB0aGlzLmNvbnRleHQgPSBudWxsO1xuICAgIHRoaXMubG9hZGVyID0gbnVsbDtcbiAgICB0aGlzLnN0YXRzID0gdm9pZCAwO1xuICAgIHRoaXMueGhyU2V0dXAgPSBjb25maWcgPyBjb25maWcueGhyU2V0dXAgfHwgbnVsbCA6IG51bGw7XG4gICAgdGhpcy5zdGF0cyA9IG5ldyBMb2FkU3RhdHMoKTtcbiAgICB0aGlzLnJldHJ5RGVsYXkgPSAwO1xuICB9XG4gIGRlc3Ryb3koKSB7XG4gICAgdGhpcy5jYWxsYmFja3MgPSBudWxsO1xuICAgIHRoaXMuYWJvcnRJbnRlcm5hbCgpO1xuICAgIHRoaXMubG9hZGVyID0gbnVsbDtcbiAgICB0aGlzLmNvbmZpZyA9IG51bGw7XG4gICAgdGhpcy5jb250ZXh0ID0gbnVsbDtcbiAgICB0aGlzLnhoclNldHVwID0gbnVsbDtcbiAgfVxuICBhYm9ydEludGVybmFsKCkge1xuICAgIGNvbnN0IGxvYWRlciA9IHRoaXMubG9hZGVyO1xuICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmVxdWVzdFRpbWVvdXQpO1xuICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmV0cnlUaW1lb3V0KTtcbiAgICBpZiAobG9hZGVyKSB7XG4gICAgICBsb2FkZXIub25yZWFkeXN0YXRlY2hhbmdlID0gbnVsbDtcbiAgICAgIGxvYWRlci5vbnByb2dyZXNzID0gbnVsbDtcbiAgICAgIGlmIChsb2FkZXIucmVhZHlTdGF0ZSAhPT0gNCkge1xuICAgICAgICB0aGlzLnN0YXRzLmFib3J0ZWQgPSB0cnVlO1xuICAgICAgICBsb2FkZXIuYWJvcnQoKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgYWJvcnQoKSB7XG4gICAgdmFyIF90aGlzJGNhbGxiYWNrcztcbiAgICB0aGlzLmFib3J0SW50ZXJuYWwoKTtcbiAgICBpZiAoKF90aGlzJGNhbGxiYWNrcyA9IHRoaXMuY2FsbGJhY2tzKSAhPSBudWxsICYmIF90aGlzJGNhbGxiYWNrcy5vbkFib3J0KSB7XG4gICAgICB0aGlzLmNhbGxiYWNrcy5vbkFib3J0KHRoaXMuc3RhdHMsIHRoaXMuY29udGV4dCwgdGhpcy5sb2FkZXIpO1xuICAgIH1cbiAgfVxuICBsb2FkKGNvbnRleHQsIGNvbmZpZywgY2FsbGJhY2tzKSB7XG4gICAgaWYgKHRoaXMuc3RhdHMubG9hZGluZy5zdGFydCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdMb2FkZXIgY2FuIG9ubHkgYmUgdXNlZCBvbmNlLicpO1xuICAgIH1cbiAgICB0aGlzLnN0YXRzLmxvYWRpbmcuc3RhcnQgPSBzZWxmLnBlcmZvcm1hbmNlLm5vdygpO1xuICAgIHRoaXMuY29udGV4dCA9IGNvbnRleHQ7XG4gICAgdGhpcy5jb25maWcgPSBjb25maWc7XG4gICAgdGhpcy5jYWxsYmFja3MgPSBjYWxsYmFja3M7XG4gICAgdGhpcy5sb2FkSW50ZXJuYWwoKTtcbiAgfVxuICBsb2FkSW50ZXJuYWwoKSB7XG4gICAgY29uc3Qge1xuICAgICAgY29uZmlnLFxuICAgICAgY29udGV4dFxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghY29uZmlnIHx8ICFjb250ZXh0KSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHhociA9IHRoaXMubG9hZGVyID0gbmV3IHNlbGYuWE1MSHR0cFJlcXVlc3QoKTtcbiAgICBjb25zdCBzdGF0cyA9IHRoaXMuc3RhdHM7XG4gICAgc3RhdHMubG9hZGluZy5maXJzdCA9IDA7XG4gICAgc3RhdHMubG9hZGVkID0gMDtcbiAgICBzdGF0cy5hYm9ydGVkID0gZmFsc2U7XG4gICAgY29uc3QgeGhyU2V0dXAgPSB0aGlzLnhoclNldHVwO1xuICAgIGlmICh4aHJTZXR1cCkge1xuICAgICAgUHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKSA9PiB7XG4gICAgICAgIGlmICh0aGlzLmxvYWRlciAhPT0geGhyIHx8IHRoaXMuc3RhdHMuYWJvcnRlZCkgcmV0dXJuO1xuICAgICAgICByZXR1cm4geGhyU2V0dXAoeGhyLCBjb250ZXh0LnVybCk7XG4gICAgICB9KS5jYXRjaChlcnJvciA9PiB7XG4gICAgICAgIGlmICh0aGlzLmxvYWRlciAhPT0geGhyIHx8IHRoaXMuc3RhdHMuYWJvcnRlZCkgcmV0dXJuO1xuICAgICAgICB4aHIub3BlbignR0VUJywgY29udGV4dC51cmwsIHRydWUpO1xuICAgICAgICByZXR1cm4geGhyU2V0dXAoeGhyLCBjb250ZXh0LnVybCk7XG4gICAgICB9KS50aGVuKCgpID0+IHtcbiAgICAgICAgaWYgKHRoaXMubG9hZGVyICE9PSB4aHIgfHwgdGhpcy5zdGF0cy5hYm9ydGVkKSByZXR1cm47XG4gICAgICAgIHRoaXMub3BlbkFuZFNlbmRYaHIoeGhyLCBjb250ZXh0LCBjb25maWcpO1xuICAgICAgfSkuY2F0Y2goZXJyb3IgPT4ge1xuICAgICAgICAvLyBJRTExIHRocm93cyBhbiBleGNlcHRpb24gb24geGhyLm9wZW4gaWYgYXR0ZW1wdGluZyB0byBhY2Nlc3MgYW4gSFRUUCByZXNvdXJjZSBvdmVyIEhUVFBTXG4gICAgICAgIHRoaXMuY2FsbGJhY2tzLm9uRXJyb3Ioe1xuICAgICAgICAgIGNvZGU6IHhoci5zdGF0dXMsXG4gICAgICAgICAgdGV4dDogZXJyb3IubWVzc2FnZVxuICAgICAgICB9LCBjb250ZXh0LCB4aHIsIHN0YXRzKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMub3BlbkFuZFNlbmRYaHIoeGhyLCBjb250ZXh0LCBjb25maWcpO1xuICAgIH1cbiAgfVxuICBvcGVuQW5kU2VuZFhocih4aHIsIGNvbnRleHQsIGNvbmZpZykge1xuICAgIGlmICgheGhyLnJlYWR5U3RhdGUpIHtcbiAgICAgIHhoci5vcGVuKCdHRVQnLCBjb250ZXh0LnVybCwgdHJ1ZSk7XG4gICAgfVxuICAgIGNvbnN0IGhlYWRlcnMgPSBjb250ZXh0LmhlYWRlcnM7XG4gICAgY29uc3Qge1xuICAgICAgbWF4VGltZVRvRmlyc3RCeXRlTXMsXG4gICAgICBtYXhMb2FkVGltZU1zXG4gICAgfSA9IGNvbmZpZy5sb2FkUG9saWN5O1xuICAgIGlmIChoZWFkZXJzKSB7XG4gICAgICBmb3IgKGNvbnN0IGhlYWRlciBpbiBoZWFkZXJzKSB7XG4gICAgICAgIHhoci5zZXRSZXF1ZXN0SGVhZGVyKGhlYWRlciwgaGVhZGVyc1toZWFkZXJdKTtcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKGNvbnRleHQucmFuZ2VFbmQpIHtcbiAgICAgIHhoci5zZXRSZXF1ZXN0SGVhZGVyKCdSYW5nZScsICdieXRlcz0nICsgY29udGV4dC5yYW5nZVN0YXJ0ICsgJy0nICsgKGNvbnRleHQucmFuZ2VFbmQgLSAxKSk7XG4gICAgfVxuICAgIHhoci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSB0aGlzLnJlYWR5c3RhdGVjaGFuZ2UuYmluZCh0aGlzKTtcbiAgICB4aHIub25wcm9ncmVzcyA9IHRoaXMubG9hZHByb2dyZXNzLmJpbmQodGhpcyk7XG4gICAgeGhyLnJlc3BvbnNlVHlwZSA9IGNvbnRleHQucmVzcG9uc2VUeXBlO1xuICAgIC8vIHNldHVwIHRpbWVvdXQgYmVmb3JlIHdlIHBlcmZvcm0gcmVxdWVzdFxuICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmVxdWVzdFRpbWVvdXQpO1xuICAgIGNvbmZpZy50aW1lb3V0ID0gbWF4VGltZVRvRmlyc3RCeXRlTXMgJiYgaXNGaW5pdGVOdW1iZXIobWF4VGltZVRvRmlyc3RCeXRlTXMpID8gbWF4VGltZVRvRmlyc3RCeXRlTXMgOiBtYXhMb2FkVGltZU1zO1xuICAgIHRoaXMucmVxdWVzdFRpbWVvdXQgPSBzZWxmLnNldFRpbWVvdXQodGhpcy5sb2FkdGltZW91dC5iaW5kKHRoaXMpLCBjb25maWcudGltZW91dCk7XG4gICAgeGhyLnNlbmQoKTtcbiAgfVxuICByZWFkeXN0YXRlY2hhbmdlKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbnRleHQsXG4gICAgICBsb2FkZXI6IHhocixcbiAgICAgIHN0YXRzXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFjb250ZXh0IHx8ICF4aHIpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgcmVhZHlTdGF0ZSA9IHhoci5yZWFkeVN0YXRlO1xuICAgIGNvbnN0IGNvbmZpZyA9IHRoaXMuY29uZmlnO1xuXG4gICAgLy8gZG9uJ3QgcHJvY2VlZCBpZiB4aHIgaGFzIGJlZW4gYWJvcnRlZFxuICAgIGlmIChzdGF0cy5hYm9ydGVkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gPj0gSEVBREVSU19SRUNFSVZFRFxuICAgIGlmIChyZWFkeVN0YXRlID49IDIpIHtcbiAgICAgIGlmIChzdGF0cy5sb2FkaW5nLmZpcnN0ID09PSAwKSB7XG4gICAgICAgIHN0YXRzLmxvYWRpbmcuZmlyc3QgPSBNYXRoLm1heChzZWxmLnBlcmZvcm1hbmNlLm5vdygpLCBzdGF0cy5sb2FkaW5nLnN0YXJ0KTtcbiAgICAgICAgLy8gcmVhZHlTdGF0ZSA+PSAyIEFORCByZWFkeVN0YXRlICE9PTQgKHJlYWR5U3RhdGUgPSBIRUFERVJTX1JFQ0VJVkVEIHx8IExPQURJTkcpIHJlYXJtIHRpbWVvdXQgYXMgeGhyIG5vdCBmaW5pc2hlZCB5ZXRcbiAgICAgICAgaWYgKGNvbmZpZy50aW1lb3V0ICE9PSBjb25maWcubG9hZFBvbGljeS5tYXhMb2FkVGltZU1zKSB7XG4gICAgICAgICAgc2VsZi5jbGVhclRpbWVvdXQodGhpcy5yZXF1ZXN0VGltZW91dCk7XG4gICAgICAgICAgY29uZmlnLnRpbWVvdXQgPSBjb25maWcubG9hZFBvbGljeS5tYXhMb2FkVGltZU1zO1xuICAgICAgICAgIHRoaXMucmVxdWVzdFRpbWVvdXQgPSBzZWxmLnNldFRpbWVvdXQodGhpcy5sb2FkdGltZW91dC5iaW5kKHRoaXMpLCBjb25maWcubG9hZFBvbGljeS5tYXhMb2FkVGltZU1zIC0gKHN0YXRzLmxvYWRpbmcuZmlyc3QgLSBzdGF0cy5sb2FkaW5nLnN0YXJ0KSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChyZWFkeVN0YXRlID09PSA0KSB7XG4gICAgICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmVxdWVzdFRpbWVvdXQpO1xuICAgICAgICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gbnVsbDtcbiAgICAgICAgeGhyLm9ucHJvZ3Jlc3MgPSBudWxsO1xuICAgICAgICBjb25zdCBzdGF0dXMgPSB4aHIuc3RhdHVzO1xuICAgICAgICAvLyBodHRwIHN0YXR1cyBiZXR3ZWVuIDIwMCB0byAyOTkgYXJlIGFsbCBzdWNjZXNzZnVsXG4gICAgICAgIGNvbnN0IHVzZVJlc3BvbnNlID0geGhyLnJlc3BvbnNlVHlwZSAhPT0gJ3RleHQnO1xuICAgICAgICBpZiAoc3RhdHVzID49IDIwMCAmJiBzdGF0dXMgPCAzMDAgJiYgKHVzZVJlc3BvbnNlICYmIHhoci5yZXNwb25zZSB8fCB4aHIucmVzcG9uc2VUZXh0ICE9PSBudWxsKSkge1xuICAgICAgICAgIHN0YXRzLmxvYWRpbmcuZW5kID0gTWF0aC5tYXgoc2VsZi5wZXJmb3JtYW5jZS5ub3coKSwgc3RhdHMubG9hZGluZy5maXJzdCk7XG4gICAgICAgICAgY29uc3QgZGF0YSA9IHVzZVJlc3BvbnNlID8geGhyLnJlc3BvbnNlIDogeGhyLnJlc3BvbnNlVGV4dDtcbiAgICAgICAgICBjb25zdCBsZW4gPSB4aHIucmVzcG9uc2VUeXBlID09PSAnYXJyYXlidWZmZXInID8gZGF0YS5ieXRlTGVuZ3RoIDogZGF0YS5sZW5ndGg7XG4gICAgICAgICAgc3RhdHMubG9hZGVkID0gc3RhdHMudG90YWwgPSBsZW47XG4gICAgICAgICAgc3RhdHMuYndFc3RpbWF0ZSA9IHN0YXRzLnRvdGFsICogODAwMCAvIChzdGF0cy5sb2FkaW5nLmVuZCAtIHN0YXRzLmxvYWRpbmcuZmlyc3QpO1xuICAgICAgICAgIGlmICghdGhpcy5jYWxsYmFja3MpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgICAgY29uc3Qgb25Qcm9ncmVzcyA9IHRoaXMuY2FsbGJhY2tzLm9uUHJvZ3Jlc3M7XG4gICAgICAgICAgaWYgKG9uUHJvZ3Jlc3MpIHtcbiAgICAgICAgICAgIG9uUHJvZ3Jlc3Moc3RhdHMsIGNvbnRleHQsIGRhdGEsIHhocik7XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmICghdGhpcy5jYWxsYmFja3MpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSB7XG4gICAgICAgICAgICB1cmw6IHhoci5yZXNwb25zZVVSTCxcbiAgICAgICAgICAgIGRhdGE6IGRhdGEsXG4gICAgICAgICAgICBjb2RlOiBzdGF0dXNcbiAgICAgICAgICB9O1xuICAgICAgICAgIHRoaXMuY2FsbGJhY2tzLm9uU3VjY2VzcyhyZXNwb25zZSwgc3RhdHMsIGNvbnRleHQsIHhocik7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY29uc3QgcmV0cnlDb25maWcgPSBjb25maWcubG9hZFBvbGljeS5lcnJvclJldHJ5O1xuICAgICAgICAgIGNvbnN0IHJldHJ5Q291bnQgPSBzdGF0cy5yZXRyeTtcbiAgICAgICAgICAvLyBpZiBtYXggbmIgb2YgcmV0cmllcyByZWFjaGVkIG9yIGlmIGh0dHAgc3RhdHVzIGJldHdlZW4gNDAwIGFuZCA0OTkgKHN1Y2ggZXJyb3IgY2Fubm90IGJlIHJlY292ZXJlZCwgcmV0cnlpbmcgaXMgdXNlbGVzcyksIHJldHVybiBlcnJvclxuICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0ge1xuICAgICAgICAgICAgdXJsOiBjb250ZXh0LnVybCxcbiAgICAgICAgICAgIGRhdGE6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgIGNvZGU6IHN0YXR1c1xuICAgICAgICAgIH07XG4gICAgICAgICAgaWYgKHNob3VsZFJldHJ5KHJldHJ5Q29uZmlnLCByZXRyeUNvdW50LCBmYWxzZSwgcmVzcG9uc2UpKSB7XG4gICAgICAgICAgICB0aGlzLnJldHJ5KHJldHJ5Q29uZmlnKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgbG9nZ2VyLmVycm9yKGAke3N0YXR1c30gd2hpbGUgbG9hZGluZyAke2NvbnRleHQudXJsfWApO1xuICAgICAgICAgICAgdGhpcy5jYWxsYmFja3Mub25FcnJvcih7XG4gICAgICAgICAgICAgIGNvZGU6IHN0YXR1cyxcbiAgICAgICAgICAgICAgdGV4dDogeGhyLnN0YXR1c1RleHRcbiAgICAgICAgICAgIH0sIGNvbnRleHQsIHhociwgc3RhdHMpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICBsb2FkdGltZW91dCgpIHtcbiAgICBpZiAoIXRoaXMuY29uZmlnKSByZXR1cm47XG4gICAgY29uc3QgcmV0cnlDb25maWcgPSB0aGlzLmNvbmZpZy5sb2FkUG9saWN5LnRpbWVvdXRSZXRyeTtcbiAgICBjb25zdCByZXRyeUNvdW50ID0gdGhpcy5zdGF0cy5yZXRyeTtcbiAgICBpZiAoc2hvdWxkUmV0cnkocmV0cnlDb25maWcsIHJldHJ5Q291bnQsIHRydWUpKSB7XG4gICAgICB0aGlzLnJldHJ5KHJldHJ5Q29uZmlnKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIF90aGlzJGNvbnRleHQ7XG4gICAgICBsb2dnZXIud2FybihgdGltZW91dCB3aGlsZSBsb2FkaW5nICR7KF90aGlzJGNvbnRleHQgPSB0aGlzLmNvbnRleHQpID09IG51bGwgPyB2b2lkIDAgOiBfdGhpcyRjb250ZXh0LnVybH1gKTtcbiAgICAgIGNvbnN0IGNhbGxiYWNrcyA9IHRoaXMuY2FsbGJhY2tzO1xuICAgICAgaWYgKGNhbGxiYWNrcykge1xuICAgICAgICB0aGlzLmFib3J0SW50ZXJuYWwoKTtcbiAgICAgICAgY2FsbGJhY2tzLm9uVGltZW91dCh0aGlzLnN0YXRzLCB0aGlzLmNvbnRleHQsIHRoaXMubG9hZGVyKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmV0cnkocmV0cnlDb25maWcpIHtcbiAgICBjb25zdCB7XG4gICAgICBjb250ZXh0LFxuICAgICAgc3RhdHNcbiAgICB9ID0gdGhpcztcbiAgICB0aGlzLnJldHJ5RGVsYXkgPSBnZXRSZXRyeURlbGF5KHJldHJ5Q29uZmlnLCBzdGF0cy5yZXRyeSk7XG4gICAgc3RhdHMucmV0cnkrKztcbiAgICBsb2dnZXIud2FybihgJHtzdGF0dXMgPyAnSFRUUCBTdGF0dXMgJyArIHN0YXR1cyA6ICdUaW1lb3V0J30gd2hpbGUgbG9hZGluZyAke2NvbnRleHQgPT0gbnVsbCA/IHZvaWQgMCA6IGNvbnRleHQudXJsfSwgcmV0cnlpbmcgJHtzdGF0cy5yZXRyeX0vJHtyZXRyeUNvbmZpZy5tYXhOdW1SZXRyeX0gaW4gJHt0aGlzLnJldHJ5RGVsYXl9bXNgKTtcbiAgICAvLyBhYm9ydCBhbmQgcmVzZXQgaW50ZXJuYWwgc3RhdGVcbiAgICB0aGlzLmFib3J0SW50ZXJuYWwoKTtcbiAgICB0aGlzLmxvYWRlciA9IG51bGw7XG4gICAgLy8gc2NoZWR1bGUgcmV0cnlcbiAgICBzZWxmLmNsZWFyVGltZW91dCh0aGlzLnJldHJ5VGltZW91dCk7XG4gICAgdGhpcy5yZXRyeVRpbWVvdXQgPSBzZWxmLnNldFRpbWVvdXQodGhpcy5sb2FkSW50ZXJuYWwuYmluZCh0aGlzKSwgdGhpcy5yZXRyeURlbGF5KTtcbiAgfVxuICBsb2FkcHJvZ3Jlc3MoZXZlbnQpIHtcbiAgICBjb25zdCBzdGF0cyA9IHRoaXMuc3RhdHM7XG4gICAgc3RhdHMubG9hZGVkID0gZXZlbnQubG9hZGVkO1xuICAgIGlmIChldmVudC5sZW5ndGhDb21wdXRhYmxlKSB7XG4gICAgICBzdGF0cy50b3RhbCA9IGV2ZW50LnRvdGFsO1xuICAgIH1cbiAgfVxuICBnZXRDYWNoZUFnZSgpIHtcbiAgICBsZXQgcmVzdWx0ID0gbnVsbDtcbiAgICBpZiAodGhpcy5sb2FkZXIgJiYgQUdFX0hFQURFUl9MSU5FX1JFR0VYLnRlc3QodGhpcy5sb2FkZXIuZ2V0QWxsUmVzcG9uc2VIZWFkZXJzKCkpKSB7XG4gICAgICBjb25zdCBhZ2VIZWFkZXIgPSB0aGlzLmxvYWRlci5nZXRSZXNwb25zZUhlYWRlcignYWdlJyk7XG4gICAgICByZXN1bHQgPSBhZ2VIZWFkZXIgPyBwYXJzZUZsb2F0KGFnZUhlYWRlcikgOiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGdldFJlc3BvbnNlSGVhZGVyKG5hbWUpIHtcbiAgICBpZiAodGhpcy5sb2FkZXIgJiYgbmV3IFJlZ0V4cChgXiR7bmFtZX06XFxcXHMqW1xcXFxkLl0rXFxcXHMqJGAsICdpbScpLnRlc3QodGhpcy5sb2FkZXIuZ2V0QWxsUmVzcG9uc2VIZWFkZXJzKCkpKSB7XG4gICAgICByZXR1cm4gdGhpcy5sb2FkZXIuZ2V0UmVzcG9uc2VIZWFkZXIobmFtZSk7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG59XG5cbmZ1bmN0aW9uIGZldGNoU3VwcG9ydGVkKCkge1xuICBpZiAoXG4gIC8vIEB0cy1pZ25vcmVcbiAgc2VsZi5mZXRjaCAmJiBzZWxmLkFib3J0Q29udHJvbGxlciAmJiBzZWxmLlJlYWRhYmxlU3RyZWFtICYmIHNlbGYuUmVxdWVzdCkge1xuICAgIHRyeSB7XG4gICAgICBuZXcgc2VsZi5SZWFkYWJsZVN0cmVhbSh7fSk7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tbmV3XG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAvKiBub29wICovXG4gICAgfVxuICB9XG4gIHJldHVybiBmYWxzZTtcbn1cbmNvbnN0IEJZVEVSQU5HRSA9IC8oXFxkKyktKFxcZCspXFwvKFxcZCspLztcbmNsYXNzIEZldGNoTG9hZGVyIHtcbiAgY29uc3RydWN0b3IoY29uZmlnIC8qIEhsc0NvbmZpZyAqLykge1xuICAgIHRoaXMuZmV0Y2hTZXR1cCA9IHZvaWQgMDtcbiAgICB0aGlzLnJlcXVlc3RUaW1lb3V0ID0gdm9pZCAwO1xuICAgIHRoaXMucmVxdWVzdCA9IG51bGw7XG4gICAgdGhpcy5yZXNwb25zZSA9IG51bGw7XG4gICAgdGhpcy5jb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMuY29udGV4dCA9IG51bGw7XG4gICAgdGhpcy5jb25maWcgPSBudWxsO1xuICAgIHRoaXMuY2FsbGJhY2tzID0gbnVsbDtcbiAgICB0aGlzLnN0YXRzID0gdm9pZCAwO1xuICAgIHRoaXMubG9hZGVyID0gbnVsbDtcbiAgICB0aGlzLmZldGNoU2V0dXAgPSBjb25maWcuZmV0Y2hTZXR1cCB8fCBnZXRSZXF1ZXN0O1xuICAgIHRoaXMuY29udHJvbGxlciA9IG5ldyBzZWxmLkFib3J0Q29udHJvbGxlcigpO1xuICAgIHRoaXMuc3RhdHMgPSBuZXcgTG9hZFN0YXRzKCk7XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLmxvYWRlciA9IHRoaXMuY2FsbGJhY2tzID0gdGhpcy5jb250ZXh0ID0gdGhpcy5jb25maWcgPSB0aGlzLnJlcXVlc3QgPSBudWxsO1xuICAgIHRoaXMuYWJvcnRJbnRlcm5hbCgpO1xuICAgIHRoaXMucmVzcG9uc2UgPSBudWxsO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmZldGNoU2V0dXAgPSB0aGlzLmNvbnRyb2xsZXIgPSB0aGlzLnN0YXRzID0gbnVsbDtcbiAgfVxuICBhYm9ydEludGVybmFsKCkge1xuICAgIGlmICh0aGlzLmNvbnRyb2xsZXIgJiYgIXRoaXMuc3RhdHMubG9hZGluZy5lbmQpIHtcbiAgICAgIHRoaXMuc3RhdHMuYWJvcnRlZCA9IHRydWU7XG4gICAgICB0aGlzLmNvbnRyb2xsZXIuYWJvcnQoKTtcbiAgICB9XG4gIH1cbiAgYWJvcnQoKSB7XG4gICAgdmFyIF90aGlzJGNhbGxiYWNrcztcbiAgICB0aGlzLmFib3J0SW50ZXJuYWwoKTtcbiAgICBpZiAoKF90aGlzJGNhbGxiYWNrcyA9IHRoaXMuY2FsbGJhY2tzKSAhPSBudWxsICYmIF90aGlzJGNhbGxiYWNrcy5vbkFib3J0KSB7XG4gICAgICB0aGlzLmNhbGxiYWNrcy5vbkFib3J0KHRoaXMuc3RhdHMsIHRoaXMuY29udGV4dCwgdGhpcy5yZXNwb25zZSk7XG4gICAgfVxuICB9XG4gIGxvYWQoY29udGV4dCwgY29uZmlnLCBjYWxsYmFja3MpIHtcbiAgICBjb25zdCBzdGF0cyA9IHRoaXMuc3RhdHM7XG4gICAgaWYgKHN0YXRzLmxvYWRpbmcuc3RhcnQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignTG9hZGVyIGNhbiBvbmx5IGJlIHVzZWQgb25jZS4nKTtcbiAgICB9XG4gICAgc3RhdHMubG9hZGluZy5zdGFydCA9IHNlbGYucGVyZm9ybWFuY2Uubm93KCk7XG4gICAgY29uc3QgaW5pdFBhcmFtcyA9IGdldFJlcXVlc3RQYXJhbWV0ZXJzKGNvbnRleHQsIHRoaXMuY29udHJvbGxlci5zaWduYWwpO1xuICAgIGNvbnN0IG9uUHJvZ3Jlc3MgPSBjYWxsYmFja3Mub25Qcm9ncmVzcztcbiAgICBjb25zdCBpc0FycmF5QnVmZmVyID0gY29udGV4dC5yZXNwb25zZVR5cGUgPT09ICdhcnJheWJ1ZmZlcic7XG4gICAgY29uc3QgTEVOR1RIID0gaXNBcnJheUJ1ZmZlciA/ICdieXRlTGVuZ3RoJyA6ICdsZW5ndGgnO1xuICAgIGNvbnN0IHtcbiAgICAgIG1heFRpbWVUb0ZpcnN0Qnl0ZU1zLFxuICAgICAgbWF4TG9hZFRpbWVNc1xuICAgIH0gPSBjb25maWcubG9hZFBvbGljeTtcbiAgICB0aGlzLmNvbnRleHQgPSBjb250ZXh0O1xuICAgIHRoaXMuY29uZmlnID0gY29uZmlnO1xuICAgIHRoaXMuY2FsbGJhY2tzID0gY2FsbGJhY2tzO1xuICAgIHRoaXMucmVxdWVzdCA9IHRoaXMuZmV0Y2hTZXR1cChjb250ZXh0LCBpbml0UGFyYW1zKTtcbiAgICBzZWxmLmNsZWFyVGltZW91dCh0aGlzLnJlcXVlc3RUaW1lb3V0KTtcbiAgICBjb25maWcudGltZW91dCA9IG1heFRpbWVUb0ZpcnN0Qnl0ZU1zICYmIGlzRmluaXRlTnVtYmVyKG1heFRpbWVUb0ZpcnN0Qnl0ZU1zKSA/IG1heFRpbWVUb0ZpcnN0Qnl0ZU1zIDogbWF4TG9hZFRpbWVNcztcbiAgICB0aGlzLnJlcXVlc3RUaW1lb3V0ID0gc2VsZi5zZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIHRoaXMuYWJvcnRJbnRlcm5hbCgpO1xuICAgICAgY2FsbGJhY2tzLm9uVGltZW91dChzdGF0cywgY29udGV4dCwgdGhpcy5yZXNwb25zZSk7XG4gICAgfSwgY29uZmlnLnRpbWVvdXQpO1xuICAgIHNlbGYuZmV0Y2godGhpcy5yZXF1ZXN0KS50aGVuKHJlc3BvbnNlID0+IHtcbiAgICAgIHRoaXMucmVzcG9uc2UgPSB0aGlzLmxvYWRlciA9IHJlc3BvbnNlO1xuICAgICAgY29uc3QgZmlyc3QgPSBNYXRoLm1heChzZWxmLnBlcmZvcm1hbmNlLm5vdygpLCBzdGF0cy5sb2FkaW5nLnN0YXJ0KTtcbiAgICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmVxdWVzdFRpbWVvdXQpO1xuICAgICAgY29uZmlnLnRpbWVvdXQgPSBtYXhMb2FkVGltZU1zO1xuICAgICAgdGhpcy5yZXF1ZXN0VGltZW91dCA9IHNlbGYuc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIHRoaXMuYWJvcnRJbnRlcm5hbCgpO1xuICAgICAgICBjYWxsYmFja3Mub25UaW1lb3V0KHN0YXRzLCBjb250ZXh0LCB0aGlzLnJlc3BvbnNlKTtcbiAgICAgIH0sIG1heExvYWRUaW1lTXMgLSAoZmlyc3QgLSBzdGF0cy5sb2FkaW5nLnN0YXJ0KSk7XG4gICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgIGNvbnN0IHtcbiAgICAgICAgICBzdGF0dXMsXG4gICAgICAgICAgc3RhdHVzVGV4dFxuICAgICAgICB9ID0gcmVzcG9uc2U7XG4gICAgICAgIHRocm93IG5ldyBGZXRjaEVycm9yKHN0YXR1c1RleHQgfHwgJ2ZldGNoLCBiYWQgbmV0d29yayByZXNwb25zZScsIHN0YXR1cywgcmVzcG9uc2UpO1xuICAgICAgfVxuICAgICAgc3RhdHMubG9hZGluZy5maXJzdCA9IGZpcnN0O1xuICAgICAgc3RhdHMudG90YWwgPSBnZXRDb250ZW50TGVuZ3RoKHJlc3BvbnNlLmhlYWRlcnMpIHx8IHN0YXRzLnRvdGFsO1xuICAgICAgaWYgKG9uUHJvZ3Jlc3MgJiYgaXNGaW5pdGVOdW1iZXIoY29uZmlnLmhpZ2hXYXRlck1hcmspKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmxvYWRQcm9ncmVzc2l2ZWx5KHJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgY29uZmlnLmhpZ2hXYXRlck1hcmssIG9uUHJvZ3Jlc3MpO1xuICAgICAgfVxuICAgICAgaWYgKGlzQXJyYXlCdWZmZXIpIHtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlLmFycmF5QnVmZmVyKCk7XG4gICAgICB9XG4gICAgICBpZiAoY29udGV4dC5yZXNwb25zZVR5cGUgPT09ICdqc29uJykge1xuICAgICAgICByZXR1cm4gcmVzcG9uc2UuanNvbigpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHJlc3BvbnNlLnRleHQoKTtcbiAgICB9KS50aGVuKHJlc3BvbnNlRGF0YSA9PiB7XG4gICAgICBjb25zdCByZXNwb25zZSA9IHRoaXMucmVzcG9uc2U7XG4gICAgICBpZiAoIXJlc3BvbnNlKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignbG9hZGVyIGRlc3Ryb3llZCcpO1xuICAgICAgfVxuICAgICAgc2VsZi5jbGVhclRpbWVvdXQodGhpcy5yZXF1ZXN0VGltZW91dCk7XG4gICAgICBzdGF0cy5sb2FkaW5nLmVuZCA9IE1hdGgubWF4KHNlbGYucGVyZm9ybWFuY2Uubm93KCksIHN0YXRzLmxvYWRpbmcuZmlyc3QpO1xuICAgICAgY29uc3QgdG90YWwgPSByZXNwb25zZURhdGFbTEVOR1RIXTtcbiAgICAgIGlmICh0b3RhbCkge1xuICAgICAgICBzdGF0cy5sb2FkZWQgPSBzdGF0cy50b3RhbCA9IHRvdGFsO1xuICAgICAgfVxuICAgICAgY29uc3QgbG9hZGVyUmVzcG9uc2UgPSB7XG4gICAgICAgIHVybDogcmVzcG9uc2UudXJsLFxuICAgICAgICBkYXRhOiByZXNwb25zZURhdGEsXG4gICAgICAgIGNvZGU6IHJlc3BvbnNlLnN0YXR1c1xuICAgICAgfTtcbiAgICAgIGlmIChvblByb2dyZXNzICYmICFpc0Zpbml0ZU51bWJlcihjb25maWcuaGlnaFdhdGVyTWFyaykpIHtcbiAgICAgICAgb25Qcm9ncmVzcyhzdGF0cywgY29udGV4dCwgcmVzcG9uc2VEYXRhLCByZXNwb25zZSk7XG4gICAgICB9XG4gICAgICBjYWxsYmFja3Mub25TdWNjZXNzKGxvYWRlclJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgcmVzcG9uc2UpO1xuICAgIH0pLmNhdGNoKGVycm9yID0+IHtcbiAgICAgIHNlbGYuY2xlYXJUaW1lb3V0KHRoaXMucmVxdWVzdFRpbWVvdXQpO1xuICAgICAgaWYgKHN0YXRzLmFib3J0ZWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgLy8gQ09SUyBlcnJvcnMgcmVzdWx0IGluIGFuIHVuZGVmaW5lZCBjb2RlLiBTZXQgaXQgdG8gMCBoZXJlIHRvIGFsaWduIHdpdGggWEhSJ3MgYmVoYXZpb3JcbiAgICAgIC8vIHdoZW4gZGVzdHJveWluZywgJ2Vycm9yJyBpdHNlbGYgY2FuIGJlIHVuZGVmaW5lZFxuICAgICAgY29uc3QgY29kZSA9ICFlcnJvciA/IDAgOiBlcnJvci5jb2RlIHx8IDA7XG4gICAgICBjb25zdCB0ZXh0ID0gIWVycm9yID8gbnVsbCA6IGVycm9yLm1lc3NhZ2U7XG4gICAgICBjYWxsYmFja3Mub25FcnJvcih7XG4gICAgICAgIGNvZGUsXG4gICAgICAgIHRleHRcbiAgICAgIH0sIGNvbnRleHQsIGVycm9yID8gZXJyb3IuZGV0YWlscyA6IG51bGwsIHN0YXRzKTtcbiAgICB9KTtcbiAgfVxuICBnZXRDYWNoZUFnZSgpIHtcbiAgICBsZXQgcmVzdWx0ID0gbnVsbDtcbiAgICBpZiAodGhpcy5yZXNwb25zZSkge1xuICAgICAgY29uc3QgYWdlSGVhZGVyID0gdGhpcy5yZXNwb25zZS5oZWFkZXJzLmdldCgnYWdlJyk7XG4gICAgICByZXN1bHQgPSBhZ2VIZWFkZXIgPyBwYXJzZUZsb2F0KGFnZUhlYWRlcikgOiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGdldFJlc3BvbnNlSGVhZGVyKG5hbWUpIHtcbiAgICByZXR1cm4gdGhpcy5yZXNwb25zZSA/IHRoaXMucmVzcG9uc2UuaGVhZGVycy5nZXQobmFtZSkgOiBudWxsO1xuICB9XG4gIGxvYWRQcm9ncmVzc2l2ZWx5KHJlc3BvbnNlLCBzdGF0cywgY29udGV4dCwgaGlnaFdhdGVyTWFyayA9IDAsIG9uUHJvZ3Jlc3MpIHtcbiAgICBjb25zdCBjaHVua0NhY2hlID0gbmV3IENodW5rQ2FjaGUoKTtcbiAgICBjb25zdCByZWFkZXIgPSByZXNwb25zZS5ib2R5LmdldFJlYWRlcigpO1xuICAgIGNvbnN0IHB1bXAgPSAoKSA9PiB7XG4gICAgICByZXR1cm4gcmVhZGVyLnJlYWQoKS50aGVuKGRhdGEgPT4ge1xuICAgICAgICBpZiAoZGF0YS5kb25lKSB7XG4gICAgICAgICAgaWYgKGNodW5rQ2FjaGUuZGF0YUxlbmd0aCkge1xuICAgICAgICAgICAgb25Qcm9ncmVzcyhzdGF0cywgY29udGV4dCwgY2h1bmtDYWNoZS5mbHVzaCgpLCByZXNwb25zZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUobmV3IEFycmF5QnVmZmVyKDApKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBjaHVuayA9IGRhdGEudmFsdWU7XG4gICAgICAgIGNvbnN0IGxlbiA9IGNodW5rLmxlbmd0aDtcbiAgICAgICAgc3RhdHMubG9hZGVkICs9IGxlbjtcbiAgICAgICAgaWYgKGxlbiA8IGhpZ2hXYXRlck1hcmsgfHwgY2h1bmtDYWNoZS5kYXRhTGVuZ3RoKSB7XG4gICAgICAgICAgLy8gVGhlIGN1cnJlbnQgY2h1bmsgaXMgdG9vIHNtYWxsIHRvIHRvIGJlIGVtaXR0ZWQgb3IgdGhlIGNhY2hlIGFscmVhZHkgaGFzIGRhdGFcbiAgICAgICAgICAvLyBQdXNoIGl0IHRvIHRoZSBjYWNoZVxuICAgICAgICAgIGNodW5rQ2FjaGUucHVzaChjaHVuayk7XG4gICAgICAgICAgaWYgKGNodW5rQ2FjaGUuZGF0YUxlbmd0aCA+PSBoaWdoV2F0ZXJNYXJrKSB7XG4gICAgICAgICAgICAvLyBmbHVzaCBpbiBvcmRlciB0byBqb2luIHRoZSB0eXBlZCBhcnJheXNcbiAgICAgICAgICAgIG9uUHJvZ3Jlc3Moc3RhdHMsIGNvbnRleHQsIGNodW5rQ2FjaGUuZmx1c2goKSwgcmVzcG9uc2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAvLyBJZiB0aGVyZSdzIG5vdGhpbmcgY2FjaGVkIGFscmVhZHksIGFuZCB0aGUgY2hhY2hlIGlzIGxhcmdlIGVub3VnaFxuICAgICAgICAgIC8vIGp1c3QgZW1pdCB0aGUgcHJvZ3Jlc3MgZXZlbnRcbiAgICAgICAgICBvblByb2dyZXNzKHN0YXRzLCBjb250ZXh0LCBjaHVuaywgcmVzcG9uc2UpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBwdW1wKCk7XG4gICAgICB9KS5jYXRjaCgoKSA9PiB7XG4gICAgICAgIC8qIGFib3J0ZWQgKi9cbiAgICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KCk7XG4gICAgICB9KTtcbiAgICB9O1xuICAgIHJldHVybiBwdW1wKCk7XG4gIH1cbn1cbmZ1bmN0aW9uIGdldFJlcXVlc3RQYXJhbWV0ZXJzKGNvbnRleHQsIHNpZ25hbCkge1xuICBjb25zdCBpbml0UGFyYW1zID0ge1xuICAgIG1ldGhvZDogJ0dFVCcsXG4gICAgbW9kZTogJ2NvcnMnLFxuICAgIGNyZWRlbnRpYWxzOiAnc2FtZS1vcmlnaW4nLFxuICAgIHNpZ25hbCxcbiAgICBoZWFkZXJzOiBuZXcgc2VsZi5IZWFkZXJzKF9leHRlbmRzKHt9LCBjb250ZXh0LmhlYWRlcnMpKVxuICB9O1xuICBpZiAoY29udGV4dC5yYW5nZUVuZCkge1xuICAgIGluaXRQYXJhbXMuaGVhZGVycy5zZXQoJ1JhbmdlJywgJ2J5dGVzPScgKyBjb250ZXh0LnJhbmdlU3RhcnQgKyAnLScgKyBTdHJpbmcoY29udGV4dC5yYW5nZUVuZCAtIDEpKTtcbiAgfVxuICByZXR1cm4gaW5pdFBhcmFtcztcbn1cbmZ1bmN0aW9uIGdldEJ5dGVSYW5nZUxlbmd0aChieXRlUmFuZ2VIZWFkZXIpIHtcbiAgY29uc3QgcmVzdWx0ID0gQllURVJBTkdFLmV4ZWMoYnl0ZVJhbmdlSGVhZGVyKTtcbiAgaWYgKHJlc3VsdCkge1xuICAgIHJldHVybiBwYXJzZUludChyZXN1bHRbMl0pIC0gcGFyc2VJbnQocmVzdWx0WzFdKSArIDE7XG4gIH1cbn1cbmZ1bmN0aW9uIGdldENvbnRlbnRMZW5ndGgoaGVhZGVycykge1xuICBjb25zdCBjb250ZW50UmFuZ2UgPSBoZWFkZXJzLmdldCgnQ29udGVudC1SYW5nZScpO1xuICBpZiAoY29udGVudFJhbmdlKSB7XG4gICAgY29uc3QgYnl0ZVJhbmdlTGVuZ3RoID0gZ2V0Qnl0ZVJhbmdlTGVuZ3RoKGNvbnRlbnRSYW5nZSk7XG4gICAgaWYgKGlzRmluaXRlTnVtYmVyKGJ5dGVSYW5nZUxlbmd0aCkpIHtcbiAgICAgIHJldHVybiBieXRlUmFuZ2VMZW5ndGg7XG4gICAgfVxuICB9XG4gIGNvbnN0IGNvbnRlbnRMZW5ndGggPSBoZWFkZXJzLmdldCgnQ29udGVudC1MZW5ndGgnKTtcbiAgaWYgKGNvbnRlbnRMZW5ndGgpIHtcbiAgICByZXR1cm4gcGFyc2VJbnQoY29udGVudExlbmd0aCk7XG4gIH1cbn1cbmZ1bmN0aW9uIGdldFJlcXVlc3QoY29udGV4dCwgaW5pdFBhcmFtcykge1xuICByZXR1cm4gbmV3IHNlbGYuUmVxdWVzdChjb250ZXh0LnVybCwgaW5pdFBhcmFtcyk7XG59XG5jbGFzcyBGZXRjaEVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlLCBjb2RlLCBkZXRhaWxzKSB7XG4gICAgc3VwZXIobWVzc2FnZSk7XG4gICAgdGhpcy5jb2RlID0gdm9pZCAwO1xuICAgIHRoaXMuZGV0YWlscyA9IHZvaWQgMDtcbiAgICB0aGlzLmNvZGUgPSBjb2RlO1xuICAgIHRoaXMuZGV0YWlscyA9IGRldGFpbHM7XG4gIH1cbn1cblxuY29uc3QgV0hJVEVTUEFDRV9DSEFSID0gL1xccy87XG5jb25zdCBDdWVzID0ge1xuICBuZXdDdWUodHJhY2ssIHN0YXJ0VGltZSwgZW5kVGltZSwgY2FwdGlvblNjcmVlbikge1xuICAgIGNvbnN0IHJlc3VsdCA9IFtdO1xuICAgIGxldCByb3c7XG4gICAgLy8gdGhlIHR5cGUgZGF0YSBzdGF0ZXMgdGhpcyBpcyBWVFRDdWUsIGJ1dCBpdCBjYW4gcG90ZW50aWFsbHkgYmUgYSBUZXh0VHJhY2tDdWUgb24gb2xkIGJyb3dzZXJzXG4gICAgbGV0IGN1ZTtcbiAgICBsZXQgaW5kZW50aW5nO1xuICAgIGxldCBpbmRlbnQ7XG4gICAgbGV0IHRleHQ7XG4gICAgY29uc3QgQ3VlID0gc2VsZi5WVFRDdWUgfHwgc2VsZi5UZXh0VHJhY2tDdWU7XG4gICAgZm9yIChsZXQgciA9IDA7IHIgPCBjYXB0aW9uU2NyZWVuLnJvd3MubGVuZ3RoOyByKyspIHtcbiAgICAgIHJvdyA9IGNhcHRpb25TY3JlZW4ucm93c1tyXTtcbiAgICAgIGluZGVudGluZyA9IHRydWU7XG4gICAgICBpbmRlbnQgPSAwO1xuICAgICAgdGV4dCA9ICcnO1xuICAgICAgaWYgKCFyb3cuaXNFbXB0eSgpKSB7XG4gICAgICAgIHZhciBfdHJhY2skY3VlcztcbiAgICAgICAgZm9yIChsZXQgYyA9IDA7IGMgPCByb3cuY2hhcnMubGVuZ3RoOyBjKyspIHtcbiAgICAgICAgICBpZiAoV0hJVEVTUEFDRV9DSEFSLnRlc3Qocm93LmNoYXJzW2NdLnVjaGFyKSAmJiBpbmRlbnRpbmcpIHtcbiAgICAgICAgICAgIGluZGVudCsrO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0ZXh0ICs9IHJvdy5jaGFyc1tjXS51Y2hhcjtcbiAgICAgICAgICAgIGluZGVudGluZyA9IGZhbHNlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBUbyBiZSB1c2VkIGZvciBjbGVhbmluZy11cCBvcnBoYW5lZCByb2xsLXVwIGNhcHRpb25zXG4gICAgICAgIHJvdy5jdWVTdGFydFRpbWUgPSBzdGFydFRpbWU7XG5cbiAgICAgICAgLy8gR2l2ZSBhIHNsaWdodCBidW1wIHRvIHRoZSBlbmRUaW1lIGlmIGl0J3MgZXF1YWwgdG8gc3RhcnRUaW1lIHRvIGF2b2lkIGEgU3ludGF4RXJyb3IgaW4gSUVcbiAgICAgICAgaWYgKHN0YXJ0VGltZSA9PT0gZW5kVGltZSkge1xuICAgICAgICAgIGVuZFRpbWUgKz0gMC4wMDAxO1xuICAgICAgICB9XG4gICAgICAgIGlmIChpbmRlbnQgPj0gMTYpIHtcbiAgICAgICAgICBpbmRlbnQtLTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBpbmRlbnQrKztcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBjdWVUZXh0ID0gZml4TGluZUJyZWFrcyh0ZXh0LnRyaW0oKSk7XG4gICAgICAgIGNvbnN0IGlkID0gZ2VuZXJhdGVDdWVJZChzdGFydFRpbWUsIGVuZFRpbWUsIGN1ZVRleHQpO1xuXG4gICAgICAgIC8vIElmIHRoaXMgY3VlIGFscmVhZHkgZXhpc3RzIGluIHRoZSB0cmFjayBkbyBub3QgcHVzaCBpdFxuICAgICAgICBpZiAoISh0cmFjayAhPSBudWxsICYmIChfdHJhY2skY3VlcyA9IHRyYWNrLmN1ZXMpICE9IG51bGwgJiYgX3RyYWNrJGN1ZXMuZ2V0Q3VlQnlJZChpZCkpKSB7XG4gICAgICAgICAgY3VlID0gbmV3IEN1ZShzdGFydFRpbWUsIGVuZFRpbWUsIGN1ZVRleHQpO1xuICAgICAgICAgIGN1ZS5pZCA9IGlkO1xuICAgICAgICAgIGN1ZS5saW5lID0gciArIDE7XG4gICAgICAgICAgY3VlLmFsaWduID0gJ2xlZnQnO1xuICAgICAgICAgIC8vIENsYW1wIHRoZSBwb3NpdGlvbiBiZXR3ZWVuIDEwIGFuZCA4MCBwZXJjZW50IChDRUEtNjA4IFBBQyBpbmRlbnQgY29kZSlcbiAgICAgICAgICAvLyBodHRwczovL2R2Y3MudzMub3JnL2hnL3RleHQtdHJhY2tzL3Jhdy1maWxlL2RlZmF1bHQvNjA4dG9WVFQvNjA4dG9WVFQuaHRtbCNwb3NpdGlvbmluZy1pbi1jZWEtNjA4XG4gICAgICAgICAgLy8gRmlyZWZveCB0aHJvd3MgYW4gZXhjZXB0aW9uIGFuZCBjYXB0aW9ucyBicmVhayB3aXRoIG91dCBvZiBib3VuZHMgMC0xMDAgdmFsdWVzXG4gICAgICAgICAgY3VlLnBvc2l0aW9uID0gMTAgKyBNYXRoLm1pbig4MCwgTWF0aC5mbG9vcihpbmRlbnQgKiA4IC8gMzIpICogMTApO1xuICAgICAgICAgIHJlc3VsdC5wdXNoKGN1ZSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKHRyYWNrICYmIHJlc3VsdC5sZW5ndGgpIHtcbiAgICAgIC8vIFNvcnQgYm90dG9tIGN1ZXMgaW4gcmV2ZXJzZSBvcmRlciBzbyB0aGF0IHRoZXkgcmVuZGVyIGluIGxpbmUgb3JkZXIgd2hlbiBvdmVybGFwcGluZyBpbiBDaHJvbWVcbiAgICAgIHJlc3VsdC5zb3J0KChjdWVBLCBjdWVCKSA9PiB7XG4gICAgICAgIGlmIChjdWVBLmxpbmUgPT09ICdhdXRvJyB8fCBjdWVCLmxpbmUgPT09ICdhdXRvJykge1xuICAgICAgICAgIHJldHVybiAwO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjdWVBLmxpbmUgPiA4ICYmIGN1ZUIubGluZSA+IDgpIHtcbiAgICAgICAgICByZXR1cm4gY3VlQi5saW5lIC0gY3VlQS5saW5lO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBjdWVBLmxpbmUgLSBjdWVCLmxpbmU7XG4gICAgICB9KTtcbiAgICAgIHJlc3VsdC5mb3JFYWNoKGN1ZSA9PiBhZGRDdWVUb1RyYWNrKHRyYWNrLCBjdWUpKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxufTtcblxuLyoqXG4gKiBAZGVwcmVjYXRlZCB1c2UgZnJhZ0xvYWRQb2xpY3kuZGVmYXVsdFxuICovXG5cbi8qKlxuICogQGRlcHJlY2F0ZWQgdXNlIG1hbmlmZXN0TG9hZFBvbGljeS5kZWZhdWx0IGFuZCBwbGF5bGlzdExvYWRQb2xpY3kuZGVmYXVsdFxuICovXG5cbmNvbnN0IGRlZmF1bHRMb2FkUG9saWN5ID0ge1xuICBtYXhUaW1lVG9GaXJzdEJ5dGVNczogODAwMCxcbiAgbWF4TG9hZFRpbWVNczogMjAwMDAsXG4gIHRpbWVvdXRSZXRyeTogbnVsbCxcbiAgZXJyb3JSZXRyeTogbnVsbFxufTtcblxuLyoqXG4gKiBAaWdub3JlXG4gKiBJZiBwb3NzaWJsZSwga2VlcCBobHNEZWZhdWx0Q29uZmlnIHNoYWxsb3dcbiAqIEl0IGlzIGNsb25lZCB3aGVuZXZlciBhIG5ldyBIbHMgaW5zdGFuY2UgaXMgY3JlYXRlZCwgYnkga2VlcGluZyB0aGUgY29uZmlnXG4gKiBzaGFsbG93IHRoZSBwcm9wZXJ0aWVzIGFyZSBjbG9uZWQsIGFuZCB3ZSBkb24ndCBlbmQgdXAgbWFuaXB1bGF0aW5nIHRoZSBkZWZhdWx0XG4gKi9cbmNvbnN0IGhsc0RlZmF1bHRDb25maWcgPSBfb2JqZWN0U3ByZWFkMihfb2JqZWN0U3ByZWFkMih7XG4gIGF1dG9TdGFydExvYWQ6IHRydWUsXG4gIC8vIHVzZWQgYnkgc3RyZWFtLWNvbnRyb2xsZXJcbiAgc3RhcnRQb3NpdGlvbjogLTEsXG4gIC8vIHVzZWQgYnkgc3RyZWFtLWNvbnRyb2xsZXJcbiAgZGVmYXVsdEF1ZGlvQ29kZWM6IHVuZGVmaW5lZCxcbiAgLy8gdXNlZCBieSBzdHJlYW0tY29udHJvbGxlclxuICBkZWJ1ZzogZmFsc2UsXG4gIC8vIHVzZWQgYnkgbG9nZ2VyXG4gIGNhcExldmVsT25GUFNEcm9wOiBmYWxzZSxcbiAgLy8gdXNlZCBieSBmcHMtY29udHJvbGxlclxuICBjYXBMZXZlbFRvUGxheWVyU2l6ZTogZmFsc2UsXG4gIC8vIHVzZWQgYnkgY2FwLWxldmVsLWNvbnRyb2xsZXJcbiAgaWdub3JlRGV2aWNlUGl4ZWxSYXRpbzogZmFsc2UsXG4gIC8vIHVzZWQgYnkgY2FwLWxldmVsLWNvbnRyb2xsZXJcbiAgcHJlZmVyTWFuYWdlZE1lZGlhU291cmNlOiB0cnVlLFxuICBpbml0aWFsTGl2ZU1hbmlmZXN0U2l6ZTogMSxcbiAgLy8gdXNlZCBieSBzdHJlYW0tY29udHJvbGxlclxuICBtYXhCdWZmZXJMZW5ndGg6IDMwLFxuICAvLyB1c2VkIGJ5IHN0cmVhbS1jb250cm9sbGVyXG4gIGJhY2tCdWZmZXJMZW5ndGg6IEluZmluaXR5LFxuICAvLyB1c2VkIGJ5IGJ1ZmZlci1jb250cm9sbGVyXG4gIGZyb250QnVmZmVyRmx1c2hUaHJlc2hvbGQ6IEluZmluaXR5LFxuICBtYXhCdWZmZXJTaXplOiA2MCAqIDEwMDAgKiAxMDAwLFxuICAvLyB1c2VkIGJ5IHN0cmVhbS1jb250cm9sbGVyXG4gIG1heEJ1ZmZlckhvbGU6IDAuMSxcbiAgLy8gdXNlZCBieSBzdHJlYW0tY29udHJvbGxlclxuICBoaWdoQnVmZmVyV2F0Y2hkb2dQZXJpb2Q6IDIsXG4gIC8vIHVzZWQgYnkgc3RyZWFtLWNvbnRyb2xsZXJcbiAgbnVkZ2VPZmZzZXQ6IDAuMSxcbiAgLy8gdXNlZCBieSBzdHJlYW0tY29udHJvbGxlclxuICBudWRnZU1heFJldHJ5OiAzLFxuICAvLyB1c2VkIGJ5IHN0cmVhbS1jb250cm9sbGVyXG4gIG1heEZyYWdMb29rVXBUb2xlcmFuY2U6IDAuMjUsXG4gIC8vIHVzZWQgYnkgc3RyZWFtLWNvbnRyb2xsZXJcbiAgbGl2ZVN5bmNEdXJhdGlvbkNvdW50OiAzLFxuICAvLyB1c2VkIGJ5IGxhdGVuY3ktY29udHJvbGxlclxuICBsaXZlTWF4TGF0ZW5jeUR1cmF0aW9uQ291bnQ6IEluZmluaXR5LFxuICAvLyB1c2VkIGJ5IGxhdGVuY3ktY29udHJvbGxlclxuICBsaXZlU3luY0R1cmF0aW9uOiB1bmRlZmluZWQsXG4gIC8vIHVzZWQgYnkgbGF0ZW5jeS1jb250cm9sbGVyXG4gIGxpdmVNYXhMYXRlbmN5RHVyYXRpb246IHVuZGVmaW5lZCxcbiAgLy8gdXNlZCBieSBsYXRlbmN5LWNvbnRyb2xsZXJcbiAgbWF4TGl2ZVN5bmNQbGF5YmFja1JhdGU6IDEsXG4gIC8vIHVzZWQgYnkgbGF0ZW5jeS1jb250cm9sbGVyXG4gIGxpdmVEdXJhdGlvbkluZmluaXR5OiBmYWxzZSxcbiAgLy8gdXNlZCBieSBidWZmZXItY29udHJvbGxlclxuICAvKipcbiAgICogQGRlcHJlY2F0ZWQgdXNlIGJhY2tCdWZmZXJMZW5ndGhcbiAgICovXG4gIGxpdmVCYWNrQnVmZmVyTGVuZ3RoOiBudWxsLFxuICAvLyB1c2VkIGJ5IGJ1ZmZlci1jb250cm9sbGVyXG4gIG1heE1heEJ1ZmZlckxlbmd0aDogNjAwLFxuICAvLyB1c2VkIGJ5IHN0cmVhbS1jb250cm9sbGVyXG4gIGVuYWJsZVdvcmtlcjogdHJ1ZSxcbiAgLy8gdXNlZCBieSB0cmFuc211eGVyXG4gIHdvcmtlclBhdGg6IG51bGwsXG4gIC8vIHVzZWQgYnkgdHJhbnNtdXhlclxuICBlbmFibGVTb2Z0d2FyZUFFUzogdHJ1ZSxcbiAgLy8gdXNlZCBieSBkZWNyeXB0ZXJcbiAgc3RhcnRMZXZlbDogdW5kZWZpbmVkLFxuICAvLyB1c2VkIGJ5IGxldmVsLWNvbnRyb2xsZXJcbiAgc3RhcnRGcmFnUHJlZmV0Y2g6IGZhbHNlLFxuICAvLyB1c2VkIGJ5IHN0cmVhbS1jb250cm9sbGVyXG4gIGZwc0Ryb3BwZWRNb25pdG9yaW5nUGVyaW9kOiA1MDAwLFxuICAvLyB1c2VkIGJ5IGZwcy1jb250cm9sbGVyXG4gIGZwc0Ryb3BwZWRNb25pdG9yaW5nVGhyZXNob2xkOiAwLjIsXG4gIC8vIHVzZWQgYnkgZnBzLWNvbnRyb2xsZXJcbiAgYXBwZW5kRXJyb3JNYXhSZXRyeTogMyxcbiAgLy8gdXNlZCBieSBidWZmZXItY29udHJvbGxlclxuICBsb2FkZXI6IFhockxvYWRlcixcbiAgLy8gbG9hZGVyOiBGZXRjaExvYWRlcixcbiAgZkxvYWRlcjogdW5kZWZpbmVkLFxuICAvLyB1c2VkIGJ5IGZyYWdtZW50LWxvYWRlclxuICBwTG9hZGVyOiB1bmRlZmluZWQsXG4gIC8vIHVzZWQgYnkgcGxheWxpc3QtbG9hZGVyXG4gIHhoclNldHVwOiB1bmRlZmluZWQsXG4gIC8vIHVzZWQgYnkgeGhyLWxvYWRlclxuICBsaWNlbnNlWGhyU2V0dXA6IHVuZGVmaW5lZCxcbiAgLy8gdXNlZCBieSBlbWUtY29udHJvbGxlclxuICBsaWNlbnNlUmVzcG9uc2VDYWxsYmFjazogdW5kZWZpbmVkLFxuICAvLyB1c2VkIGJ5IGVtZS1jb250cm9sbGVyXG4gIGFickNvbnRyb2xsZXI6IEFickNvbnRyb2xsZXIsXG4gIGJ1ZmZlckNvbnRyb2xsZXI6IEJ1ZmZlckNvbnRyb2xsZXIsXG4gIGNhcExldmVsQ29udHJvbGxlcjogQ2FwTGV2ZWxDb250cm9sbGVyLFxuICBlcnJvckNvbnRyb2xsZXI6IEVycm9yQ29udHJvbGxlcixcbiAgZnBzQ29udHJvbGxlcjogRlBTQ29udHJvbGxlcixcbiAgc3RyZXRjaFNob3J0VmlkZW9UcmFjazogZmFsc2UsXG4gIC8vIHVzZWQgYnkgbXA0LXJlbXV4ZXJcbiAgbWF4QXVkaW9GcmFtZXNEcmlmdDogMSxcbiAgLy8gdXNlZCBieSBtcDQtcmVtdXhlclxuICBmb3JjZUtleUZyYW1lT25EaXNjb250aW51aXR5OiB0cnVlLFxuICAvLyB1c2VkIGJ5IHRzLWRlbXV4ZXJcbiAgYWJyRXdtYUZhc3RMaXZlOiAzLFxuICAvLyB1c2VkIGJ5IGFici1jb250cm9sbGVyXG4gIGFickV3bWFTbG93TGl2ZTogOSxcbiAgLy8gdXNlZCBieSBhYnItY29udHJvbGxlclxuICBhYnJFd21hRmFzdFZvRDogMyxcbiAgLy8gdXNlZCBieSBhYnItY29udHJvbGxlclxuICBhYnJFd21hU2xvd1ZvRDogOSxcbiAgLy8gdXNlZCBieSBhYnItY29udHJvbGxlclxuICBhYnJFd21hRGVmYXVsdEVzdGltYXRlOiA1ZTUsXG4gIC8vIDUwMCBrYnBzICAvLyB1c2VkIGJ5IGFici1jb250cm9sbGVyXG4gIGFickV3bWFEZWZhdWx0RXN0aW1hdGVNYXg6IDVlNixcbiAgLy8gNSBtYnBzXG4gIGFickJhbmRXaWR0aEZhY3RvcjogMC45NSxcbiAgLy8gdXNlZCBieSBhYnItY29udHJvbGxlclxuICBhYnJCYW5kV2lkdGhVcEZhY3RvcjogMC43LFxuICAvLyB1c2VkIGJ5IGFici1jb250cm9sbGVyXG4gIGFick1heFdpdGhSZWFsQml0cmF0ZTogZmFsc2UsXG4gIC8vIHVzZWQgYnkgYWJyLWNvbnRyb2xsZXJcbiAgbWF4U3RhcnZhdGlvbkRlbGF5OiA0LFxuICAvLyB1c2VkIGJ5IGFici1jb250cm9sbGVyXG4gIG1heExvYWRpbmdEZWxheTogNCxcbiAgLy8gdXNlZCBieSBhYnItY29udHJvbGxlclxuICBtaW5BdXRvQml0cmF0ZTogMCxcbiAgLy8gdXNlZCBieSBobHNcbiAgZW1lRW5hYmxlZDogZmFsc2UsXG4gIC8vIHVzZWQgYnkgZW1lLWNvbnRyb2xsZXJcbiAgd2lkZXZpbmVMaWNlbnNlVXJsOiB1bmRlZmluZWQsXG4gIC8vIHVzZWQgYnkgZW1lLWNvbnRyb2xsZXJcbiAgZHJtU3lzdGVtczoge30sXG4gIC8vIHVzZWQgYnkgZW1lLWNvbnRyb2xsZXJcbiAgZHJtU3lzdGVtT3B0aW9uczoge30sXG4gIC8vIHVzZWQgYnkgZW1lLWNvbnRyb2xsZXJcbiAgcmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzRnVuYzogcmVxdWVzdE1lZGlhS2V5U3lzdGVtQWNjZXNzICxcbiAgLy8gdXNlZCBieSBlbWUtY29udHJvbGxlclxuICB0ZXN0QmFuZHdpZHRoOiB0cnVlLFxuICBwcm9ncmVzc2l2ZTogZmFsc2UsXG4gIGxvd0xhdGVuY3lNb2RlOiB0cnVlLFxuICBjbWNkOiB1bmRlZmluZWQsXG4gIGVuYWJsZURhdGVSYW5nZU1ldGFkYXRhQ3VlczogdHJ1ZSxcbiAgZW5hYmxlRW1zZ01ldGFkYXRhQ3VlczogdHJ1ZSxcbiAgZW5hYmxlSUQzTWV0YWRhdGFDdWVzOiB0cnVlLFxuICB1c2VNZWRpYUNhcGFiaWxpdGllczogdHJ1ZSxcbiAgY2VydExvYWRQb2xpY3k6IHtcbiAgICBkZWZhdWx0OiBkZWZhdWx0TG9hZFBvbGljeVxuICB9LFxuICBrZXlMb2FkUG9saWN5OiB7XG4gICAgZGVmYXVsdDoge1xuICAgICAgbWF4VGltZVRvRmlyc3RCeXRlTXM6IDgwMDAsXG4gICAgICBtYXhMb2FkVGltZU1zOiAyMDAwMCxcbiAgICAgIHRpbWVvdXRSZXRyeToge1xuICAgICAgICBtYXhOdW1SZXRyeTogMSxcbiAgICAgICAgcmV0cnlEZWxheU1zOiAxMDAwLFxuICAgICAgICBtYXhSZXRyeURlbGF5TXM6IDIwMDAwLFxuICAgICAgICBiYWNrb2ZmOiAnbGluZWFyJ1xuICAgICAgfSxcbiAgICAgIGVycm9yUmV0cnk6IHtcbiAgICAgICAgbWF4TnVtUmV0cnk6IDgsXG4gICAgICAgIHJldHJ5RGVsYXlNczogMTAwMCxcbiAgICAgICAgbWF4UmV0cnlEZWxheU1zOiAyMDAwMCxcbiAgICAgICAgYmFja29mZjogJ2xpbmVhcidcbiAgICAgIH1cbiAgICB9XG4gIH0sXG4gIG1hbmlmZXN0TG9hZFBvbGljeToge1xuICAgIGRlZmF1bHQ6IHtcbiAgICAgIG1heFRpbWVUb0ZpcnN0Qnl0ZU1zOiBJbmZpbml0eSxcbiAgICAgIG1heExvYWRUaW1lTXM6IDIwMDAwLFxuICAgICAgdGltZW91dFJldHJ5OiB7XG4gICAgICAgIG1heE51bVJldHJ5OiAyLFxuICAgICAgICByZXRyeURlbGF5TXM6IDAsXG4gICAgICAgIG1heFJldHJ5RGVsYXlNczogMFxuICAgICAgfSxcbiAgICAgIGVycm9yUmV0cnk6IHtcbiAgICAgICAgbWF4TnVtUmV0cnk6IDEsXG4gICAgICAgIHJldHJ5RGVsYXlNczogMTAwMCxcbiAgICAgICAgbWF4UmV0cnlEZWxheU1zOiA4MDAwXG4gICAgICB9XG4gICAgfVxuICB9LFxuICBwbGF5bGlzdExvYWRQb2xpY3k6IHtcbiAgICBkZWZhdWx0OiB7XG4gICAgICBtYXhUaW1lVG9GaXJzdEJ5dGVNczogMTAwMDAsXG4gICAgICBtYXhMb2FkVGltZU1zOiAyMDAwMCxcbiAgICAgIHRpbWVvdXRSZXRyeToge1xuICAgICAgICBtYXhOdW1SZXRyeTogMixcbiAgICAgICAgcmV0cnlEZWxheU1zOiAwLFxuICAgICAgICBtYXhSZXRyeURlbGF5TXM6IDBcbiAgICAgIH0sXG4gICAgICBlcnJvclJldHJ5OiB7XG4gICAgICAgIG1heE51bVJldHJ5OiAyLFxuICAgICAgICByZXRyeURlbGF5TXM6IDEwMDAsXG4gICAgICAgIG1heFJldHJ5RGVsYXlNczogODAwMFxuICAgICAgfVxuICAgIH1cbiAgfSxcbiAgZnJhZ0xvYWRQb2xpY3k6IHtcbiAgICBkZWZhdWx0OiB7XG4gICAgICBtYXhUaW1lVG9GaXJzdEJ5dGVNczogMTAwMDAsXG4gICAgICBtYXhMb2FkVGltZU1zOiAxMjAwMDAsXG4gICAgICB0aW1lb3V0UmV0cnk6IHtcbiAgICAgICAgbWF4TnVtUmV0cnk6IDQsXG4gICAgICAgIHJldHJ5RGVsYXlNczogMCxcbiAgICAgICAgbWF4UmV0cnlEZWxheU1zOiAwXG4gICAgICB9LFxuICAgICAgZXJyb3JSZXRyeToge1xuICAgICAgICBtYXhOdW1SZXRyeTogNixcbiAgICAgICAgcmV0cnlEZWxheU1zOiAxMDAwLFxuICAgICAgICBtYXhSZXRyeURlbGF5TXM6IDgwMDBcbiAgICAgIH1cbiAgICB9XG4gIH0sXG4gIHN0ZWVyaW5nTWFuaWZlc3RMb2FkUG9saWN5OiB7XG4gICAgZGVmYXVsdDoge1xuICAgICAgbWF4VGltZVRvRmlyc3RCeXRlTXM6IDEwMDAwLFxuICAgICAgbWF4TG9hZFRpbWVNczogMjAwMDAsXG4gICAgICB0aW1lb3V0UmV0cnk6IHtcbiAgICAgICAgbWF4TnVtUmV0cnk6IDIsXG4gICAgICAgIHJldHJ5RGVsYXlNczogMCxcbiAgICAgICAgbWF4UmV0cnlEZWxheU1zOiAwXG4gICAgICB9LFxuICAgICAgZXJyb3JSZXRyeToge1xuICAgICAgICBtYXhOdW1SZXRyeTogMSxcbiAgICAgICAgcmV0cnlEZWxheU1zOiAxMDAwLFxuICAgICAgICBtYXhSZXRyeURlbGF5TXM6IDgwMDBcbiAgICAgIH1cbiAgICB9IFxuICB9LFxuICAvLyBUaGVzZSBkZWZhdWx0IHNldHRpbmdzIGFyZSBkZXByZWNhdGVkIGluIGZhdm9yIG9mIHRoZSBhYm92ZSBwb2xpY2llc1xuICAvLyBhbmQgYXJlIG1haW50YWluZWQgZm9yIGJhY2t3YXJkcyBjb21wYXRpYmlsaXR5XG4gIG1hbmlmZXN0TG9hZGluZ1RpbWVPdXQ6IDEwMDAwLFxuICBtYW5pZmVzdExvYWRpbmdNYXhSZXRyeTogMSxcbiAgbWFuaWZlc3RMb2FkaW5nUmV0cnlEZWxheTogMTAwMCxcbiAgbWFuaWZlc3RMb2FkaW5nTWF4UmV0cnlUaW1lb3V0OiA2NDAwMCxcbiAgbGV2ZWxMb2FkaW5nVGltZU91dDogMTAwMDAsXG4gIGxldmVsTG9hZGluZ01heFJldHJ5OiA0LFxuICBsZXZlbExvYWRpbmdSZXRyeURlbGF5OiAxMDAwLFxuICBsZXZlbExvYWRpbmdNYXhSZXRyeVRpbWVvdXQ6IDY0MDAwLFxuICBmcmFnTG9hZGluZ1RpbWVPdXQ6IDIwMDAwLFxuICBmcmFnTG9hZGluZ01heFJldHJ5OiA2LFxuICBmcmFnTG9hZGluZ1JldHJ5RGVsYXk6IDEwMDAsXG4gIGZyYWdMb2FkaW5nTWF4UmV0cnlUaW1lb3V0OiA2NDAwMFxufSwgdGltZWxpbmVDb25maWcoKSksIHt9LCB7XG4gIHN1YnRpdGxlU3RyZWFtQ29udHJvbGxlcjogU3VidGl0bGVTdHJlYW1Db250cm9sbGVyICxcbiAgc3VidGl0bGVUcmFja0NvbnRyb2xsZXI6IFN1YnRpdGxlVHJhY2tDb250cm9sbGVyICxcbiAgdGltZWxpbmVDb250cm9sbGVyOiBUaW1lbGluZUNvbnRyb2xsZXIgLFxuICBhdWRpb1N0cmVhbUNvbnRyb2xsZXI6IEF1ZGlvU3RyZWFtQ29udHJvbGxlciAsXG4gIGF1ZGlvVHJhY2tDb250cm9sbGVyOiBBdWRpb1RyYWNrQ29udHJvbGxlciAsXG4gIGVtZUNvbnRyb2xsZXI6IEVNRUNvbnRyb2xsZXIgLFxuICBjbWNkQ29udHJvbGxlcjogQ01DRENvbnRyb2xsZXIgLFxuICBjb250ZW50U3RlZXJpbmdDb250cm9sbGVyOiBDb250ZW50U3RlZXJpbmdDb250cm9sbGVyIFxufSk7XG5mdW5jdGlvbiB0aW1lbGluZUNvbmZpZygpIHtcbiAgcmV0dXJuIHtcbiAgICBjdWVIYW5kbGVyOiBDdWVzLFxuICAgIC8vIHVzZWQgYnkgdGltZWxpbmUtY29udHJvbGxlclxuICAgIGVuYWJsZVdlYlZUVDogdHJ1ZSxcbiAgICAvLyB1c2VkIGJ5IHRpbWVsaW5lLWNvbnRyb2xsZXJcbiAgICBlbmFibGVJTVNDMTogdHJ1ZSxcbiAgICAvLyB1c2VkIGJ5IHRpbWVsaW5lLWNvbnRyb2xsZXJcbiAgICBlbmFibGVDRUE3MDhDYXB0aW9uczogdHJ1ZSxcbiAgICAvLyB1c2VkIGJ5IHRpbWVsaW5lLWNvbnRyb2xsZXJcbiAgICBjYXB0aW9uc1RleHRUcmFjazFMYWJlbDogJ0VuZ2xpc2gnLFxuICAgIC8vIHVzZWQgYnkgdGltZWxpbmUtY29udHJvbGxlclxuICAgIGNhcHRpb25zVGV4dFRyYWNrMUxhbmd1YWdlQ29kZTogJ2VuJyxcbiAgICAvLyB1c2VkIGJ5IHRpbWVsaW5lLWNvbnRyb2xsZXJcbiAgICBjYXB0aW9uc1RleHRUcmFjazJMYWJlbDogJ1NwYW5pc2gnLFxuICAgIC8vIHVzZWQgYnkgdGltZWxpbmUtY29udHJvbGxlclxuICAgIGNhcHRpb25zVGV4dFRyYWNrMkxhbmd1YWdlQ29kZTogJ2VzJyxcbiAgICAvLyB1c2VkIGJ5IHRpbWVsaW5lLWNvbnRyb2xsZXJcbiAgICBjYXB0aW9uc1RleHRUcmFjazNMYWJlbDogJ1Vua25vd24gQ0MnLFxuICAgIC8vIHVzZWQgYnkgdGltZWxpbmUtY29udHJvbGxlclxuICAgIGNhcHRpb25zVGV4dFRyYWNrM0xhbmd1YWdlQ29kZTogJycsXG4gICAgLy8gdXNlZCBieSB0aW1lbGluZS1jb250cm9sbGVyXG4gICAgY2FwdGlvbnNUZXh0VHJhY2s0TGFiZWw6ICdVbmtub3duIENDJyxcbiAgICAvLyB1c2VkIGJ5IHRpbWVsaW5lLWNvbnRyb2xsZXJcbiAgICBjYXB0aW9uc1RleHRUcmFjazRMYW5ndWFnZUNvZGU6ICcnLFxuICAgIC8vIHVzZWQgYnkgdGltZWxpbmUtY29udHJvbGxlclxuICAgIHJlbmRlclRleHRUcmFja3NOYXRpdmVseTogdHJ1ZVxuICB9O1xufVxuXG4vKipcbiAqIEBpZ25vcmVcbiAqL1xuZnVuY3Rpb24gbWVyZ2VDb25maWcoZGVmYXVsdENvbmZpZywgdXNlckNvbmZpZykge1xuICBpZiAoKHVzZXJDb25maWcubGl2ZVN5bmNEdXJhdGlvbkNvdW50IHx8IHVzZXJDb25maWcubGl2ZU1heExhdGVuY3lEdXJhdGlvbkNvdW50KSAmJiAodXNlckNvbmZpZy5saXZlU3luY0R1cmF0aW9uIHx8IHVzZXJDb25maWcubGl2ZU1heExhdGVuY3lEdXJhdGlvbikpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXCJJbGxlZ2FsIGhscy5qcyBjb25maWc6IGRvbid0IG1peCB1cCBsaXZlU3luY0R1cmF0aW9uQ291bnQvbGl2ZU1heExhdGVuY3lEdXJhdGlvbkNvdW50IGFuZCBsaXZlU3luY0R1cmF0aW9uL2xpdmVNYXhMYXRlbmN5RHVyYXRpb25cIik7XG4gIH1cbiAgaWYgKHVzZXJDb25maWcubGl2ZU1heExhdGVuY3lEdXJhdGlvbkNvdW50ICE9PSB1bmRlZmluZWQgJiYgKHVzZXJDb25maWcubGl2ZVN5bmNEdXJhdGlvbkNvdW50ID09PSB1bmRlZmluZWQgfHwgdXNlckNvbmZpZy5saXZlTWF4TGF0ZW5jeUR1cmF0aW9uQ291bnQgPD0gdXNlckNvbmZpZy5saXZlU3luY0R1cmF0aW9uQ291bnQpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdJbGxlZ2FsIGhscy5qcyBjb25maWc6IFwibGl2ZU1heExhdGVuY3lEdXJhdGlvbkNvdW50XCIgbXVzdCBiZSBncmVhdGVyIHRoYW4gXCJsaXZlU3luY0R1cmF0aW9uQ291bnRcIicpO1xuICB9XG4gIGlmICh1c2VyQ29uZmlnLmxpdmVNYXhMYXRlbmN5RHVyYXRpb24gIT09IHVuZGVmaW5lZCAmJiAodXNlckNvbmZpZy5saXZlU3luY0R1cmF0aW9uID09PSB1bmRlZmluZWQgfHwgdXNlckNvbmZpZy5saXZlTWF4TGF0ZW5jeUR1cmF0aW9uIDw9IHVzZXJDb25maWcubGl2ZVN5bmNEdXJhdGlvbikpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ0lsbGVnYWwgaGxzLmpzIGNvbmZpZzogXCJsaXZlTWF4TGF0ZW5jeUR1cmF0aW9uXCIgbXVzdCBiZSBncmVhdGVyIHRoYW4gXCJsaXZlU3luY0R1cmF0aW9uXCInKTtcbiAgfVxuICBjb25zdCBkZWZhdWx0c0NvcHkgPSBkZWVwQ3B5KGRlZmF1bHRDb25maWcpO1xuXG4gIC8vIEJhY2t3YXJkcyBjb21wYXRpYmlsaXR5IHdpdGggZGVwcmVjYXRlZCBjb25maWcgdmFsdWVzXG4gIGNvbnN0IGRlcHJlY2F0ZWRTZXR0aW5nVHlwZXMgPSBbJ21hbmlmZXN0JywgJ2xldmVsJywgJ2ZyYWcnXTtcbiAgY29uc3QgZGVwcmVjYXRlZFNldHRpbmdzID0gWydUaW1lT3V0JywgJ01heFJldHJ5JywgJ1JldHJ5RGVsYXknLCAnTWF4UmV0cnlUaW1lb3V0J107XG4gIGRlcHJlY2F0ZWRTZXR0aW5nVHlwZXMuZm9yRWFjaCh0eXBlID0+IHtcbiAgICBjb25zdCBwb2xpY3lOYW1lID0gYCR7dHlwZSA9PT0gJ2xldmVsJyA/ICdwbGF5bGlzdCcgOiB0eXBlfUxvYWRQb2xpY3lgO1xuICAgIGNvbnN0IHBvbGljeU5vdFNldCA9IHVzZXJDb25maWdbcG9saWN5TmFtZV0gPT09IHVuZGVmaW5lZDtcbiAgICBjb25zdCByZXBvcnQgPSBbXTtcbiAgICBkZXByZWNhdGVkU2V0dGluZ3MuZm9yRWFjaChzZXR0aW5nID0+IHtcbiAgICAgIGNvbnN0IGRlcHJlY2F0ZWRTZXR0aW5nID0gYCR7dHlwZX1Mb2FkaW5nJHtzZXR0aW5nfWA7XG4gICAgICBjb25zdCB2YWx1ZSA9IHVzZXJDb25maWdbZGVwcmVjYXRlZFNldHRpbmddO1xuICAgICAgaWYgKHZhbHVlICE9PSB1bmRlZmluZWQgJiYgcG9saWN5Tm90U2V0KSB7XG4gICAgICAgIHJlcG9ydC5wdXNoKGRlcHJlY2F0ZWRTZXR0aW5nKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSBkZWZhdWx0c0NvcHlbcG9saWN5TmFtZV0uZGVmYXVsdDtcbiAgICAgICAgdXNlckNvbmZpZ1twb2xpY3lOYW1lXSA9IHtcbiAgICAgICAgICBkZWZhdWx0OiBzZXR0aW5nc1xuICAgICAgICB9O1xuICAgICAgICBzd2l0Y2ggKHNldHRpbmcpIHtcbiAgICAgICAgICBjYXNlICdUaW1lT3V0JzpcbiAgICAgICAgICAgIHNldHRpbmdzLm1heExvYWRUaW1lTXMgPSB2YWx1ZTtcbiAgICAgICAgICAgIHNldHRpbmdzLm1heFRpbWVUb0ZpcnN0Qnl0ZU1zID0gdmFsdWU7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgICBjYXNlICdNYXhSZXRyeSc6XG4gICAgICAgICAgICBzZXR0aW5ncy5lcnJvclJldHJ5Lm1heE51bVJldHJ5ID0gdmFsdWU7XG4gICAgICAgICAgICBzZXR0aW5ncy50aW1lb3V0UmV0cnkubWF4TnVtUmV0cnkgPSB2YWx1ZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ1JldHJ5RGVsYXknOlxuICAgICAgICAgICAgc2V0dGluZ3MuZXJyb3JSZXRyeS5yZXRyeURlbGF5TXMgPSB2YWx1ZTtcbiAgICAgICAgICAgIHNldHRpbmdzLnRpbWVvdXRSZXRyeS5yZXRyeURlbGF5TXMgPSB2YWx1ZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIGNhc2UgJ01heFJldHJ5VGltZW91dCc6XG4gICAgICAgICAgICBzZXR0aW5ncy5lcnJvclJldHJ5Lm1heFJldHJ5RGVsYXlNcyA9IHZhbHVlO1xuICAgICAgICAgICAgc2V0dGluZ3MudGltZW91dFJldHJ5Lm1heFJldHJ5RGVsYXlNcyA9IHZhbHVlO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgICBpZiAocmVwb3J0Lmxlbmd0aCkge1xuICAgICAgbG9nZ2VyLndhcm4oYGhscy5qcyBjb25maWc6IFwiJHtyZXBvcnQuam9pbignXCIsIFwiJyl9XCIgc2V0dGluZyhzKSBhcmUgZGVwcmVjYXRlZCwgdXNlIFwiJHtwb2xpY3lOYW1lfVwiOiAke0pTT04uc3RyaW5naWZ5KHVzZXJDb25maWdbcG9saWN5TmFtZV0pfWApO1xuICAgIH1cbiAgfSk7XG4gIHJldHVybiBfb2JqZWN0U3ByZWFkMihfb2JqZWN0U3ByZWFkMih7fSwgZGVmYXVsdHNDb3B5KSwgdXNlckNvbmZpZyk7XG59XG5mdW5jdGlvbiBkZWVwQ3B5KG9iaikge1xuICBpZiAob2JqICYmIHR5cGVvZiBvYmogPT09ICdvYmplY3QnKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkob2JqKSkge1xuICAgICAgcmV0dXJuIG9iai5tYXAoZGVlcENweSk7XG4gICAgfVxuICAgIHJldHVybiBPYmplY3Qua2V5cyhvYmopLnJlZHVjZSgocmVzdWx0LCBrZXkpID0+IHtcbiAgICAgIHJlc3VsdFtrZXldID0gZGVlcENweShvYmpba2V5XSk7XG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH0sIHt9KTtcbiAgfVxuICByZXR1cm4gb2JqO1xufVxuXG4vKipcbiAqIEBpZ25vcmVcbiAqL1xuZnVuY3Rpb24gZW5hYmxlU3RyZWFtaW5nTW9kZShjb25maWcpIHtcbiAgY29uc3QgY3VycmVudExvYWRlciA9IGNvbmZpZy5sb2FkZXI7XG4gIGlmIChjdXJyZW50TG9hZGVyICE9PSBGZXRjaExvYWRlciAmJiBjdXJyZW50TG9hZGVyICE9PSBYaHJMb2FkZXIpIHtcbiAgICAvLyBJZiBhIGRldmVsb3BlciBoYXMgY29uZmlndXJlZCB0aGVpciBvd24gbG9hZGVyLCByZXNwZWN0IHRoYXQgY2hvaWNlXG4gICAgbG9nZ2VyLmxvZygnW2NvbmZpZ106IEN1c3RvbSBsb2FkZXIgZGV0ZWN0ZWQsIGNhbm5vdCBlbmFibGUgcHJvZ3Jlc3NpdmUgc3RyZWFtaW5nJyk7XG4gICAgY29uZmlnLnByb2dyZXNzaXZlID0gZmFsc2U7XG4gIH0gZWxzZSB7XG4gICAgY29uc3QgY2FuU3RyZWFtUHJvZ3Jlc3NpdmVseSA9IGZldGNoU3VwcG9ydGVkKCk7XG4gICAgaWYgKGNhblN0cmVhbVByb2dyZXNzaXZlbHkpIHtcbiAgICAgIGNvbmZpZy5sb2FkZXIgPSBGZXRjaExvYWRlcjtcbiAgICAgIGNvbmZpZy5wcm9ncmVzc2l2ZSA9IHRydWU7XG4gICAgICBjb25maWcuZW5hYmxlU29mdHdhcmVBRVMgPSB0cnVlO1xuICAgICAgbG9nZ2VyLmxvZygnW2NvbmZpZ106IFByb2dyZXNzaXZlIHN0cmVhbWluZyBlbmFibGVkLCB1c2luZyBGZXRjaExvYWRlcicpO1xuICAgIH1cbiAgfVxufVxuXG5sZXQgY2hyb21lT3JGaXJlZm94O1xuY2xhc3MgTGV2ZWxDb250cm9sbGVyIGV4dGVuZHMgQmFzZVBsYXlsaXN0Q29udHJvbGxlciB7XG4gIGNvbnN0cnVjdG9yKGhscywgY29udGVudFN0ZWVyaW5nQ29udHJvbGxlcikge1xuICAgIHN1cGVyKGhscywgJ1tsZXZlbC1jb250cm9sbGVyXScpO1xuICAgIHRoaXMuX2xldmVscyA9IFtdO1xuICAgIHRoaXMuX2ZpcnN0TGV2ZWwgPSAtMTtcbiAgICB0aGlzLl9tYXhBdXRvTGV2ZWwgPSAtMTtcbiAgICB0aGlzLl9zdGFydExldmVsID0gdm9pZCAwO1xuICAgIHRoaXMuY3VycmVudExldmVsID0gbnVsbDtcbiAgICB0aGlzLmN1cnJlbnRMZXZlbEluZGV4ID0gLTE7XG4gICAgdGhpcy5tYW51YWxMZXZlbEluZGV4ID0gLTE7XG4gICAgdGhpcy5zdGVlcmluZyA9IHZvaWQgMDtcbiAgICB0aGlzLm9uUGFyc2VkQ29tcGxldGUgPSB2b2lkIDA7XG4gICAgdGhpcy5zdGVlcmluZyA9IGNvbnRlbnRTdGVlcmluZ0NvbnRyb2xsZXI7XG4gICAgdGhpcy5fcmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICBfcmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB0aGlzLm9uTWFuaWZlc3RMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLk1BTklGRVNUX0xPQURFRCwgdGhpcy5vbk1hbmlmZXN0TG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkxFVkVMX0xPQURFRCwgdGhpcy5vbkxldmVsTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkxFVkVMU19VUERBVEVELCB0aGlzLm9uTGV2ZWxzVXBkYXRlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICB9XG4gIF91bnJlZ2lzdGVyTGlzdGVuZXJzKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURJTkcsIHRoaXMub25NYW5pZmVzdExvYWRpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLk1BTklGRVNUX0xPQURFRCwgdGhpcy5vbk1hbmlmZXN0TG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5MRVZFTF9MT0FERUQsIHRoaXMub25MZXZlbExvYWRlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTEVWRUxTX1VQREFURUQsIHRoaXMub25MZXZlbHNVcGRhdGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5FUlJPUiwgdGhpcy5vbkVycm9yLCB0aGlzKTtcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMuX3VucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICB0aGlzLnN0ZWVyaW5nID0gbnVsbDtcbiAgICB0aGlzLnJlc2V0TGV2ZWxzKCk7XG4gICAgc3VwZXIuZGVzdHJveSgpO1xuICB9XG4gIHN0b3BMb2FkKCkge1xuICAgIGNvbnN0IGxldmVscyA9IHRoaXMuX2xldmVscztcblxuICAgIC8vIGNsZWFuIHVwIGxpdmUgbGV2ZWwgZGV0YWlscyB0byBmb3JjZSByZWxvYWQgdGhlbSwgYW5kIHJlc2V0IGxvYWQgZXJyb3JzXG4gICAgbGV2ZWxzLmZvckVhY2gobGV2ZWwgPT4ge1xuICAgICAgbGV2ZWwubG9hZEVycm9yID0gMDtcbiAgICAgIGxldmVsLmZyYWdtZW50RXJyb3IgPSAwO1xuICAgIH0pO1xuICAgIHN1cGVyLnN0b3BMb2FkKCk7XG4gIH1cbiAgcmVzZXRMZXZlbHMoKSB7XG4gICAgdGhpcy5fc3RhcnRMZXZlbCA9IHVuZGVmaW5lZDtcbiAgICB0aGlzLm1hbnVhbExldmVsSW5kZXggPSAtMTtcbiAgICB0aGlzLmN1cnJlbnRMZXZlbEluZGV4ID0gLTE7XG4gICAgdGhpcy5jdXJyZW50TGV2ZWwgPSBudWxsO1xuICAgIHRoaXMuX2xldmVscyA9IFtdO1xuICAgIHRoaXMuX21heEF1dG9MZXZlbCA9IC0xO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKGV2ZW50LCBkYXRhKSB7XG4gICAgdGhpcy5yZXNldExldmVscygpO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UgPSB0aGlzLmhscy5jb25maWcucHJlZmVyTWFuYWdlZE1lZGlhU291cmNlO1xuICAgIGNvbnN0IGxldmVscyA9IFtdO1xuICAgIGNvbnN0IHJlZHVuZGFudFNldCA9IHt9O1xuICAgIGNvbnN0IGdlbmVyYXRlUGF0aHdheVNldCA9IHt9O1xuICAgIGxldCByZXNvbHV0aW9uRm91bmQgPSBmYWxzZTtcbiAgICBsZXQgdmlkZW9Db2RlY0ZvdW5kID0gZmFsc2U7XG4gICAgbGV0IGF1ZGlvQ29kZWNGb3VuZCA9IGZhbHNlO1xuICAgIGRhdGEubGV2ZWxzLmZvckVhY2gobGV2ZWxQYXJzZWQgPT4ge1xuICAgICAgdmFyIF9hdWRpb0NvZGVjLCBfdmlkZW9Db2RlYztcbiAgICAgIGNvbnN0IGF0dHJpYnV0ZXMgPSBsZXZlbFBhcnNlZC5hdHRycztcblxuICAgICAgLy8gZXJhc2UgYXVkaW8gY29kZWMgaW5mbyBpZiBicm93c2VyIGRvZXMgbm90IHN1cHBvcnQgbXA0YS40MC4zNC5cbiAgICAgIC8vIGRlbXV4ZXIgd2lsbCBhdXRvZGV0ZWN0IGNvZGVjIGFuZCBmYWxsYmFjayB0byBtcGVnL2F1ZGlvXG4gICAgICBsZXQge1xuICAgICAgICBhdWRpb0NvZGVjLFxuICAgICAgICB2aWRlb0NvZGVjXG4gICAgICB9ID0gbGV2ZWxQYXJzZWQ7XG4gICAgICBpZiAoKChfYXVkaW9Db2RlYyA9IGF1ZGlvQ29kZWMpID09IG51bGwgPyB2b2lkIDAgOiBfYXVkaW9Db2RlYy5pbmRleE9mKCdtcDRhLjQwLjM0JykpICE9PSAtMSkge1xuICAgICAgICBjaHJvbWVPckZpcmVmb3ggfHwgKGNocm9tZU9yRmlyZWZveCA9IC9jaHJvbWV8ZmlyZWZveC9pLnRlc3QobmF2aWdhdG9yLnVzZXJBZ2VudCkpO1xuICAgICAgICBpZiAoY2hyb21lT3JGaXJlZm94KSB7XG4gICAgICAgICAgbGV2ZWxQYXJzZWQuYXVkaW9Db2RlYyA9IGF1ZGlvQ29kZWMgPSB1bmRlZmluZWQ7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChhdWRpb0NvZGVjKSB7XG4gICAgICAgIGxldmVsUGFyc2VkLmF1ZGlvQ29kZWMgPSBhdWRpb0NvZGVjID0gZ2V0Q29kZWNDb21wYXRpYmxlTmFtZShhdWRpb0NvZGVjLCBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2UpO1xuICAgICAgfVxuICAgICAgaWYgKCgoX3ZpZGVvQ29kZWMgPSB2aWRlb0NvZGVjKSA9PSBudWxsID8gdm9pZCAwIDogX3ZpZGVvQ29kZWMuaW5kZXhPZignYXZjMScpKSA9PT0gMCkge1xuICAgICAgICB2aWRlb0NvZGVjID0gbGV2ZWxQYXJzZWQudmlkZW9Db2RlYyA9IGNvbnZlcnRBVkMxVG9BVkNPVEkodmlkZW9Db2RlYyk7XG4gICAgICB9XG5cbiAgICAgIC8vIG9ubHkga2VlcCBsZXZlbHMgd2l0aCBzdXBwb3J0ZWQgYXVkaW8vdmlkZW8gY29kZWNzXG4gICAgICBjb25zdCB7XG4gICAgICAgIHdpZHRoLFxuICAgICAgICBoZWlnaHQsXG4gICAgICAgIHVua25vd25Db2RlY3NcbiAgICAgIH0gPSBsZXZlbFBhcnNlZDtcbiAgICAgIHJlc29sdXRpb25Gb3VuZCB8fCAocmVzb2x1dGlvbkZvdW5kID0gISEod2lkdGggJiYgaGVpZ2h0KSk7XG4gICAgICB2aWRlb0NvZGVjRm91bmQgfHwgKHZpZGVvQ29kZWNGb3VuZCA9ICEhdmlkZW9Db2RlYyk7XG4gICAgICBhdWRpb0NvZGVjRm91bmQgfHwgKGF1ZGlvQ29kZWNGb3VuZCA9ICEhYXVkaW9Db2RlYyk7XG4gICAgICBpZiAodW5rbm93bkNvZGVjcyAhPSBudWxsICYmIHVua25vd25Db2RlY3MubGVuZ3RoIHx8IGF1ZGlvQ29kZWMgJiYgIWFyZUNvZGVjc01lZGlhU291cmNlU3VwcG9ydGVkKGF1ZGlvQ29kZWMsICdhdWRpbycsIHByZWZlck1hbmFnZWRNZWRpYVNvdXJjZSkgfHwgdmlkZW9Db2RlYyAmJiAhYXJlQ29kZWNzTWVkaWFTb3VyY2VTdXBwb3J0ZWQodmlkZW9Db2RlYywgJ3ZpZGVvJywgcHJlZmVyTWFuYWdlZE1lZGlhU291cmNlKSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCB7XG4gICAgICAgIENPREVDUyxcbiAgICAgICAgJ0ZSQU1FLVJBVEUnOiBGUkFNRVJBVEUsXG4gICAgICAgICdIRENQLUxFVkVMJzogSERDUCxcbiAgICAgICAgJ1BBVEhXQVktSUQnOiBQQVRIV0FZLFxuICAgICAgICBSRVNPTFVUSU9OLFxuICAgICAgICAnVklERU8tUkFOR0UnOiBWSURFT19SQU5HRVxuICAgICAgfSA9IGF0dHJpYnV0ZXM7XG4gICAgICBjb25zdCBjb250ZW50U3RlZXJpbmdQcmVmaXggPSBgJHtQQVRIV0FZIHx8ICcuJ30tYDtcbiAgICAgIGNvbnN0IGxldmVsS2V5ID0gYCR7Y29udGVudFN0ZWVyaW5nUHJlZml4fSR7bGV2ZWxQYXJzZWQuYml0cmF0ZX0tJHtSRVNPTFVUSU9OfS0ke0ZSQU1FUkFURX0tJHtDT0RFQ1N9LSR7VklERU9fUkFOR0V9LSR7SERDUH1gO1xuICAgICAgaWYgKCFyZWR1bmRhbnRTZXRbbGV2ZWxLZXldKSB7XG4gICAgICAgIGNvbnN0IGxldmVsID0gbmV3IExldmVsKGxldmVsUGFyc2VkKTtcbiAgICAgICAgcmVkdW5kYW50U2V0W2xldmVsS2V5XSA9IGxldmVsO1xuICAgICAgICBnZW5lcmF0ZVBhdGh3YXlTZXRbbGV2ZWxLZXldID0gMTtcbiAgICAgICAgbGV2ZWxzLnB1c2gobGV2ZWwpO1xuICAgICAgfSBlbHNlIGlmIChyZWR1bmRhbnRTZXRbbGV2ZWxLZXldLnVyaSAhPT0gbGV2ZWxQYXJzZWQudXJsICYmICFsZXZlbFBhcnNlZC5hdHRyc1snUEFUSFdBWS1JRCddKSB7XG4gICAgICAgIC8vIEFzc2lnbiBQYXRod2F5IElEcyB0byBSZWR1bmRhbnQgU3RyZWFtcyAoZGVmYXVsdCBQYXRod2F5cyBpcyBcIi5cIi4gUmVkdW5kYW50IFN0cmVhbXMgXCIuLlwiLCBcIi4uLlwiLCBhbmQgc28gb24uKVxuICAgICAgICAvLyBDb250ZW50IFN0ZWVyaW5nIGNvbnRyb2xsZXIgdG8gaGFuZGxlcyBQYXRod2F5IGZhbGxiYWNrIG9uIGVycm9yXG4gICAgICAgIGNvbnN0IHBhdGh3YXlDb3VudCA9IGdlbmVyYXRlUGF0aHdheVNldFtsZXZlbEtleV0gKz0gMTtcbiAgICAgICAgbGV2ZWxQYXJzZWQuYXR0cnNbJ1BBVEhXQVktSUQnXSA9IG5ldyBBcnJheShwYXRod2F5Q291bnQgKyAxKS5qb2luKCcuJyk7XG4gICAgICAgIGNvbnN0IGxldmVsID0gbmV3IExldmVsKGxldmVsUGFyc2VkKTtcbiAgICAgICAgcmVkdW5kYW50U2V0W2xldmVsS2V5XSA9IGxldmVsO1xuICAgICAgICBsZXZlbHMucHVzaChsZXZlbCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZWR1bmRhbnRTZXRbbGV2ZWxLZXldLmFkZEdyb3VwSWQoJ2F1ZGlvJywgYXR0cmlidXRlcy5BVURJTyk7XG4gICAgICAgIHJlZHVuZGFudFNldFtsZXZlbEtleV0uYWRkR3JvdXBJZCgndGV4dCcsIGF0dHJpYnV0ZXMuU1VCVElUTEVTKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICB0aGlzLmZpbHRlckFuZFNvcnRNZWRpYU9wdGlvbnMobGV2ZWxzLCBkYXRhLCByZXNvbHV0aW9uRm91bmQsIHZpZGVvQ29kZWNGb3VuZCwgYXVkaW9Db2RlY0ZvdW5kKTtcbiAgfVxuICBmaWx0ZXJBbmRTb3J0TWVkaWFPcHRpb25zKGZpbHRlcmVkTGV2ZWxzLCBkYXRhLCByZXNvbHV0aW9uRm91bmQsIHZpZGVvQ29kZWNGb3VuZCwgYXVkaW9Db2RlY0ZvdW5kKSB7XG4gICAgbGV0IGF1ZGlvVHJhY2tzID0gW107XG4gICAgbGV0IHN1YnRpdGxlVHJhY2tzID0gW107XG4gICAgbGV0IGxldmVscyA9IGZpbHRlcmVkTGV2ZWxzO1xuXG4gICAgLy8gcmVtb3ZlIGF1ZGlvLW9ubHkgYW5kIGludmFsaWQgdmlkZW8tcmFuZ2UgbGV2ZWxzIGlmIHdlIGFsc28gaGF2ZSBsZXZlbHMgd2l0aCB2aWRlbyBjb2RlY3Mgb3IgUkVTT0xVVElPTiBzaWduYWxsZWRcbiAgICBpZiAoKHJlc29sdXRpb25Gb3VuZCB8fCB2aWRlb0NvZGVjRm91bmQpICYmIGF1ZGlvQ29kZWNGb3VuZCkge1xuICAgICAgbGV2ZWxzID0gbGV2ZWxzLmZpbHRlcigoe1xuICAgICAgICB2aWRlb0NvZGVjLFxuICAgICAgICB2aWRlb1JhbmdlLFxuICAgICAgICB3aWR0aCxcbiAgICAgICAgaGVpZ2h0XG4gICAgICB9KSA9PiAoISF2aWRlb0NvZGVjIHx8ICEhKHdpZHRoICYmIGhlaWdodCkpICYmIGlzVmlkZW9SYW5nZSh2aWRlb1JhbmdlKSk7XG4gICAgfVxuICAgIGlmIChsZXZlbHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAvLyBEaXNwYXRjaCBlcnJvciBhZnRlciBNQU5JRkVTVF9MT0FERUQgaXMgZG9uZSBwcm9wYWdhdGluZ1xuICAgICAgUHJvbWlzZS5yZXNvbHZlKCkudGhlbigoKSA9PiB7XG4gICAgICAgIGlmICh0aGlzLmhscykge1xuICAgICAgICAgIGlmIChkYXRhLmxldmVscy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHRoaXMud2FybihgT25lIG9yIG1vcmUgQ09ERUNTIGluIHZhcmlhbnQgbm90IHN1cHBvcnRlZDogJHtKU09OLnN0cmluZ2lmeShkYXRhLmxldmVsc1swXS5hdHRycyl9YCk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IGVycm9yID0gbmV3IEVycm9yKCdubyBsZXZlbCB3aXRoIGNvbXBhdGlibGUgY29kZWNzIGZvdW5kIGluIG1hbmlmZXN0Jyk7XG4gICAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuTUFOSUZFU1RfSU5DT01QQVRJQkxFX0NPREVDU19FUlJPUixcbiAgICAgICAgICAgIGZhdGFsOiB0cnVlLFxuICAgICAgICAgICAgdXJsOiBkYXRhLnVybCxcbiAgICAgICAgICAgIGVycm9yLFxuICAgICAgICAgICAgcmVhc29uOiBlcnJvci5tZXNzYWdlXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoZGF0YS5hdWRpb1RyYWNrcykge1xuICAgICAgY29uc3Qge1xuICAgICAgICBwcmVmZXJNYW5hZ2VkTWVkaWFTb3VyY2VcbiAgICAgIH0gPSB0aGlzLmhscy5jb25maWc7XG4gICAgICBhdWRpb1RyYWNrcyA9IGRhdGEuYXVkaW9UcmFja3MuZmlsdGVyKHRyYWNrID0+ICF0cmFjay5hdWRpb0NvZGVjIHx8IGFyZUNvZGVjc01lZGlhU291cmNlU3VwcG9ydGVkKHRyYWNrLmF1ZGlvQ29kZWMsICdhdWRpbycsIHByZWZlck1hbmFnZWRNZWRpYVNvdXJjZSkpO1xuICAgICAgLy8gQXNzaWduIGlkcyBhZnRlciBmaWx0ZXJpbmcgYXMgYXJyYXkgaW5kaWNlcyBieSBncm91cC1pZFxuICAgICAgYXNzaWduVHJhY2tJZHNCeUdyb3VwKGF1ZGlvVHJhY2tzKTtcbiAgICB9XG4gICAgaWYgKGRhdGEuc3VidGl0bGVzKSB7XG4gICAgICBzdWJ0aXRsZVRyYWNrcyA9IGRhdGEuc3VidGl0bGVzO1xuICAgICAgYXNzaWduVHJhY2tJZHNCeUdyb3VwKHN1YnRpdGxlVHJhY2tzKTtcbiAgICB9XG4gICAgLy8gc3RhcnQgYml0cmF0ZSBpcyB0aGUgZmlyc3QgYml0cmF0ZSBvZiB0aGUgbWFuaWZlc3RcbiAgICBjb25zdCB1bnNvcnRlZExldmVscyA9IGxldmVscy5zbGljZSgwKTtcbiAgICAvLyBzb3J0IGxldmVscyBmcm9tIGxvd2VzdCB0byBoaWdoZXN0XG4gICAgbGV2ZWxzLnNvcnQoKGEsIGIpID0+IHtcbiAgICAgIGlmIChhLmF0dHJzWydIRENQLUxFVkVMJ10gIT09IGIuYXR0cnNbJ0hEQ1AtTEVWRUwnXSkge1xuICAgICAgICByZXR1cm4gKGEuYXR0cnNbJ0hEQ1AtTEVWRUwnXSB8fCAnJykgPiAoYi5hdHRyc1snSERDUC1MRVZFTCddIHx8ICcnKSA/IDEgOiAtMTtcbiAgICAgIH1cbiAgICAgIC8vIHNvcnQgb24gaGVpZ2h0IGJlZm9yZSBiaXRyYXRlIGZvciBjYXAtbGV2ZWwtY29udHJvbGxlclxuICAgICAgaWYgKHJlc29sdXRpb25Gb3VuZCAmJiBhLmhlaWdodCAhPT0gYi5oZWlnaHQpIHtcbiAgICAgICAgcmV0dXJuIGEuaGVpZ2h0IC0gYi5oZWlnaHQ7XG4gICAgICB9XG4gICAgICBpZiAoYS5mcmFtZVJhdGUgIT09IGIuZnJhbWVSYXRlKSB7XG4gICAgICAgIHJldHVybiBhLmZyYW1lUmF0ZSAtIGIuZnJhbWVSYXRlO1xuICAgICAgfVxuICAgICAgaWYgKGEudmlkZW9SYW5nZSAhPT0gYi52aWRlb1JhbmdlKSB7XG4gICAgICAgIHJldHVybiBWaWRlb1JhbmdlVmFsdWVzLmluZGV4T2YoYS52aWRlb1JhbmdlKSAtIFZpZGVvUmFuZ2VWYWx1ZXMuaW5kZXhPZihiLnZpZGVvUmFuZ2UpO1xuICAgICAgfVxuICAgICAgaWYgKGEudmlkZW9Db2RlYyAhPT0gYi52aWRlb0NvZGVjKSB7XG4gICAgICAgIGNvbnN0IHZhbHVlQSA9IHZpZGVvQ29kZWNQcmVmZXJlbmNlVmFsdWUoYS52aWRlb0NvZGVjKTtcbiAgICAgICAgY29uc3QgdmFsdWVCID0gdmlkZW9Db2RlY1ByZWZlcmVuY2VWYWx1ZShiLnZpZGVvQ29kZWMpO1xuICAgICAgICBpZiAodmFsdWVBICE9PSB2YWx1ZUIpIHtcbiAgICAgICAgICByZXR1cm4gdmFsdWVCIC0gdmFsdWVBO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAoYS51cmkgPT09IGIudXJpICYmIGEuY29kZWNTZXQgIT09IGIuY29kZWNTZXQpIHtcbiAgICAgICAgY29uc3QgdmFsdWVBID0gY29kZWNzU2V0U2VsZWN0aW9uUHJlZmVyZW5jZVZhbHVlKGEuY29kZWNTZXQpO1xuICAgICAgICBjb25zdCB2YWx1ZUIgPSBjb2RlY3NTZXRTZWxlY3Rpb25QcmVmZXJlbmNlVmFsdWUoYi5jb2RlY1NldCk7XG4gICAgICAgIGlmICh2YWx1ZUEgIT09IHZhbHVlQikge1xuICAgICAgICAgIHJldHVybiB2YWx1ZUIgLSB2YWx1ZUE7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChhLmF2ZXJhZ2VCaXRyYXRlICE9PSBiLmF2ZXJhZ2VCaXRyYXRlKSB7XG4gICAgICAgIHJldHVybiBhLmF2ZXJhZ2VCaXRyYXRlIC0gYi5hdmVyYWdlQml0cmF0ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiAwO1xuICAgIH0pO1xuICAgIGxldCBmaXJzdExldmVsSW5QbGF5bGlzdCA9IHVuc29ydGVkTGV2ZWxzWzBdO1xuICAgIGlmICh0aGlzLnN0ZWVyaW5nKSB7XG4gICAgICBsZXZlbHMgPSB0aGlzLnN0ZWVyaW5nLmZpbHRlclBhcnNlZExldmVscyhsZXZlbHMpO1xuICAgICAgaWYgKGxldmVscy5sZW5ndGggIT09IHVuc29ydGVkTGV2ZWxzLmxlbmd0aCkge1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHVuc29ydGVkTGV2ZWxzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgaWYgKHVuc29ydGVkTGV2ZWxzW2ldLnBhdGh3YXlJZCA9PT0gbGV2ZWxzWzBdLnBhdGh3YXlJZCkge1xuICAgICAgICAgICAgZmlyc3RMZXZlbEluUGxheWxpc3QgPSB1bnNvcnRlZExldmVsc1tpXTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLl9sZXZlbHMgPSBsZXZlbHM7XG5cbiAgICAvLyBmaW5kIGluZGV4IG9mIGZpcnN0IGxldmVsIGluIHNvcnRlZCBsZXZlbHNcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGxldmVscy5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKGxldmVsc1tpXSA9PT0gZmlyc3RMZXZlbEluUGxheWxpc3QpIHtcbiAgICAgICAgdmFyIF90aGlzJGhscyR1c2VyQ29uZmlnO1xuICAgICAgICB0aGlzLl9maXJzdExldmVsID0gaTtcbiAgICAgICAgY29uc3QgZmlyc3RMZXZlbEJpdHJhdGUgPSBmaXJzdExldmVsSW5QbGF5bGlzdC5iaXRyYXRlO1xuICAgICAgICBjb25zdCBiYW5kd2lkdGhFc3RpbWF0ZSA9IHRoaXMuaGxzLmJhbmR3aWR0aEVzdGltYXRlO1xuICAgICAgICB0aGlzLmxvZyhgbWFuaWZlc3QgbG9hZGVkLCAke2xldmVscy5sZW5ndGh9IGxldmVsKHMpIGZvdW5kLCBmaXJzdCBiaXRyYXRlOiAke2ZpcnN0TGV2ZWxCaXRyYXRlfWApO1xuICAgICAgICAvLyBVcGRhdGUgZGVmYXVsdCBid2UgdG8gZmlyc3QgdmFyaWFudCBiaXRyYXRlIGFzIGxvbmcgaXQgaGFzIG5vdCBiZWVuIGNvbmZpZ3VyZWQgb3Igc2V0XG4gICAgICAgIGlmICgoKF90aGlzJGhscyR1c2VyQ29uZmlnID0gdGhpcy5obHMudXNlckNvbmZpZykgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGhscyR1c2VyQ29uZmlnLmFickV3bWFEZWZhdWx0RXN0aW1hdGUpID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICBjb25zdCBzdGFydGluZ0J3RXN0aW1hdGUgPSBNYXRoLm1pbihmaXJzdExldmVsQml0cmF0ZSwgdGhpcy5obHMuY29uZmlnLmFickV3bWFEZWZhdWx0RXN0aW1hdGVNYXgpO1xuICAgICAgICAgIGlmIChzdGFydGluZ0J3RXN0aW1hdGUgPiBiYW5kd2lkdGhFc3RpbWF0ZSAmJiBiYW5kd2lkdGhFc3RpbWF0ZSA9PT0gaGxzRGVmYXVsdENvbmZpZy5hYnJFd21hRGVmYXVsdEVzdGltYXRlKSB7XG4gICAgICAgICAgICB0aGlzLmhscy5iYW5kd2lkdGhFc3RpbWF0ZSA9IHN0YXJ0aW5nQndFc3RpbWF0ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gQXVkaW8gaXMgb25seSBhbHRlcm5hdGUgaWYgbWFuaWZlc3QgaW5jbHVkZSBhIFVSSSBhbG9uZyB3aXRoIHRoZSBhdWRpbyBncm91cCB0YWcsXG4gICAgLy8gYW5kIHRoaXMgaXMgbm90IGFuIGF1ZGlvLW9ubHkgc3RyZWFtIHdoZXJlIGxldmVscyBjb250YWluIGF1ZGlvLW9ubHlcbiAgICBjb25zdCBhdWRpb09ubHkgPSBhdWRpb0NvZGVjRm91bmQgJiYgIXZpZGVvQ29kZWNGb3VuZDtcbiAgICBjb25zdCBlZGF0YSA9IHtcbiAgICAgIGxldmVscyxcbiAgICAgIGF1ZGlvVHJhY2tzLFxuICAgICAgc3VidGl0bGVUcmFja3MsXG4gICAgICBzZXNzaW9uRGF0YTogZGF0YS5zZXNzaW9uRGF0YSxcbiAgICAgIHNlc3Npb25LZXlzOiBkYXRhLnNlc3Npb25LZXlzLFxuICAgICAgZmlyc3RMZXZlbDogdGhpcy5fZmlyc3RMZXZlbCxcbiAgICAgIHN0YXRzOiBkYXRhLnN0YXRzLFxuICAgICAgYXVkaW86IGF1ZGlvQ29kZWNGb3VuZCxcbiAgICAgIHZpZGVvOiB2aWRlb0NvZGVjRm91bmQsXG4gICAgICBhbHRBdWRpbzogIWF1ZGlvT25seSAmJiBhdWRpb1RyYWNrcy5zb21lKHQgPT4gISF0LnVybClcbiAgICB9O1xuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLk1BTklGRVNUX1BBUlNFRCwgZWRhdGEpO1xuXG4gICAgLy8gSW5pdGlhdGUgbG9hZGluZyBhZnRlciBhbGwgY29udHJvbGxlcnMgaGF2ZSByZWNlaXZlZCBNQU5JRkVTVF9QQVJTRURcbiAgICBpZiAodGhpcy5obHMuY29uZmlnLmF1dG9TdGFydExvYWQgfHwgdGhpcy5obHMuZm9yY2VTdGFydExvYWQpIHtcbiAgICAgIHRoaXMuaGxzLnN0YXJ0TG9hZCh0aGlzLmhscy5jb25maWcuc3RhcnRQb3NpdGlvbik7XG4gICAgfVxuICB9XG4gIGdldCBsZXZlbHMoKSB7XG4gICAgaWYgKHRoaXMuX2xldmVscy5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fbGV2ZWxzO1xuICB9XG4gIGdldCBsZXZlbCgpIHtcbiAgICByZXR1cm4gdGhpcy5jdXJyZW50TGV2ZWxJbmRleDtcbiAgfVxuICBzZXQgbGV2ZWwobmV3TGV2ZWwpIHtcbiAgICBjb25zdCBsZXZlbHMgPSB0aGlzLl9sZXZlbHM7XG4gICAgaWYgKGxldmVscy5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gY2hlY2sgaWYgbGV2ZWwgaWR4IGlzIHZhbGlkXG4gICAgaWYgKG5ld0xldmVsIDwgMCB8fCBuZXdMZXZlbCA+PSBsZXZlbHMubGVuZ3RoKSB7XG4gICAgICAvLyBpbnZhbGlkIGxldmVsIGlkIGdpdmVuLCB0cmlnZ2VyIGVycm9yXG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcignaW52YWxpZCBsZXZlbCBpZHgnKTtcbiAgICAgIGNvbnN0IGZhdGFsID0gbmV3TGV2ZWwgPCAwO1xuICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5PVEhFUl9FUlJPUixcbiAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLkxFVkVMX1NXSVRDSF9FUlJPUixcbiAgICAgICAgbGV2ZWw6IG5ld0xldmVsLFxuICAgICAgICBmYXRhbCxcbiAgICAgICAgZXJyb3IsXG4gICAgICAgIHJlYXNvbjogZXJyb3IubWVzc2FnZVxuICAgICAgfSk7XG4gICAgICBpZiAoZmF0YWwpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgbmV3TGV2ZWwgPSBNYXRoLm1pbihuZXdMZXZlbCwgbGV2ZWxzLmxlbmd0aCAtIDEpO1xuICAgIH1cbiAgICBjb25zdCBsYXN0TGV2ZWxJbmRleCA9IHRoaXMuY3VycmVudExldmVsSW5kZXg7XG4gICAgY29uc3QgbGFzdExldmVsID0gdGhpcy5jdXJyZW50TGV2ZWw7XG4gICAgY29uc3QgbGFzdFBhdGh3YXlJZCA9IGxhc3RMZXZlbCA/IGxhc3RMZXZlbC5hdHRyc1snUEFUSFdBWS1JRCddIDogdW5kZWZpbmVkO1xuICAgIGNvbnN0IGxldmVsID0gbGV2ZWxzW25ld0xldmVsXTtcbiAgICBjb25zdCBwYXRod2F5SWQgPSBsZXZlbC5hdHRyc1snUEFUSFdBWS1JRCddO1xuICAgIHRoaXMuY3VycmVudExldmVsSW5kZXggPSBuZXdMZXZlbDtcbiAgICB0aGlzLmN1cnJlbnRMZXZlbCA9IGxldmVsO1xuICAgIGlmIChsYXN0TGV2ZWxJbmRleCA9PT0gbmV3TGV2ZWwgJiYgbGV2ZWwuZGV0YWlscyAmJiBsYXN0TGV2ZWwgJiYgbGFzdFBhdGh3YXlJZCA9PT0gcGF0aHdheUlkKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMubG9nKGBTd2l0Y2hpbmcgdG8gbGV2ZWwgJHtuZXdMZXZlbH0gKCR7bGV2ZWwuaGVpZ2h0ID8gbGV2ZWwuaGVpZ2h0ICsgJ3AgJyA6ICcnfSR7bGV2ZWwudmlkZW9SYW5nZSA/IGxldmVsLnZpZGVvUmFuZ2UgKyAnICcgOiAnJ30ke2xldmVsLmNvZGVjU2V0ID8gbGV2ZWwuY29kZWNTZXQgKyAnICcgOiAnJ31AJHtsZXZlbC5iaXRyYXRlfSkke3BhdGh3YXlJZCA/ICcgd2l0aCBQYXRod2F5ICcgKyBwYXRod2F5SWQgOiAnJ30gZnJvbSBsZXZlbCAke2xhc3RMZXZlbEluZGV4fSR7bGFzdFBhdGh3YXlJZCA/ICcgd2l0aCBQYXRod2F5ICcgKyBsYXN0UGF0aHdheUlkIDogJyd9YCk7XG4gICAgY29uc3QgbGV2ZWxTd2l0Y2hpbmdEYXRhID0ge1xuICAgICAgbGV2ZWw6IG5ld0xldmVsLFxuICAgICAgYXR0cnM6IGxldmVsLmF0dHJzLFxuICAgICAgZGV0YWlsczogbGV2ZWwuZGV0YWlscyxcbiAgICAgIGJpdHJhdGU6IGxldmVsLmJpdHJhdGUsXG4gICAgICBhdmVyYWdlQml0cmF0ZTogbGV2ZWwuYXZlcmFnZUJpdHJhdGUsXG4gICAgICBtYXhCaXRyYXRlOiBsZXZlbC5tYXhCaXRyYXRlLFxuICAgICAgcmVhbEJpdHJhdGU6IGxldmVsLnJlYWxCaXRyYXRlLFxuICAgICAgd2lkdGg6IGxldmVsLndpZHRoLFxuICAgICAgaGVpZ2h0OiBsZXZlbC5oZWlnaHQsXG4gICAgICBjb2RlY1NldDogbGV2ZWwuY29kZWNTZXQsXG4gICAgICBhdWRpb0NvZGVjOiBsZXZlbC5hdWRpb0NvZGVjLFxuICAgICAgdmlkZW9Db2RlYzogbGV2ZWwudmlkZW9Db2RlYyxcbiAgICAgIGF1ZGlvR3JvdXBzOiBsZXZlbC5hdWRpb0dyb3VwcyxcbiAgICAgIHN1YnRpdGxlR3JvdXBzOiBsZXZlbC5zdWJ0aXRsZUdyb3VwcyxcbiAgICAgIGxvYWRlZDogbGV2ZWwubG9hZGVkLFxuICAgICAgbG9hZEVycm9yOiBsZXZlbC5sb2FkRXJyb3IsXG4gICAgICBmcmFnbWVudEVycm9yOiBsZXZlbC5mcmFnbWVudEVycm9yLFxuICAgICAgbmFtZTogbGV2ZWwubmFtZSxcbiAgICAgIGlkOiBsZXZlbC5pZCxcbiAgICAgIHVyaTogbGV2ZWwudXJpLFxuICAgICAgdXJsOiBsZXZlbC51cmwsXG4gICAgICB1cmxJZDogMCxcbiAgICAgIGF1ZGlvR3JvdXBJZHM6IGxldmVsLmF1ZGlvR3JvdXBJZHMsXG4gICAgICB0ZXh0R3JvdXBJZHM6IGxldmVsLnRleHRHcm91cElkc1xuICAgIH07XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuTEVWRUxfU1dJVENISU5HLCBsZXZlbFN3aXRjaGluZ0RhdGEpO1xuICAgIC8vIGNoZWNrIGlmIHdlIG5lZWQgdG8gbG9hZCBwbGF5bGlzdCBmb3IgdGhpcyBsZXZlbFxuICAgIGNvbnN0IGxldmVsRGV0YWlscyA9IGxldmVsLmRldGFpbHM7XG4gICAgaWYgKCFsZXZlbERldGFpbHMgfHwgbGV2ZWxEZXRhaWxzLmxpdmUpIHtcbiAgICAgIC8vIGxldmVsIG5vdCByZXRyaWV2ZWQgeWV0LCBvciBsaXZlIHBsYXlsaXN0IHdlIG5lZWQgdG8gKHJlKWxvYWQgaXRcbiAgICAgIGNvbnN0IGhsc1VybFBhcmFtZXRlcnMgPSB0aGlzLnN3aXRjaFBhcmFtcyhsZXZlbC51cmksIGxhc3RMZXZlbCA9PSBudWxsID8gdm9pZCAwIDogbGFzdExldmVsLmRldGFpbHMsIGxldmVsRGV0YWlscyk7XG4gICAgICB0aGlzLmxvYWRQbGF5bGlzdChobHNVcmxQYXJhbWV0ZXJzKTtcbiAgICB9XG4gIH1cbiAgZ2V0IG1hbnVhbExldmVsKCkge1xuICAgIHJldHVybiB0aGlzLm1hbnVhbExldmVsSW5kZXg7XG4gIH1cbiAgc2V0IG1hbnVhbExldmVsKG5ld0xldmVsKSB7XG4gICAgdGhpcy5tYW51YWxMZXZlbEluZGV4ID0gbmV3TGV2ZWw7XG4gICAgaWYgKHRoaXMuX3N0YXJ0TGV2ZWwgPT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhpcy5fc3RhcnRMZXZlbCA9IG5ld0xldmVsO1xuICAgIH1cbiAgICBpZiAobmV3TGV2ZWwgIT09IC0xKSB7XG4gICAgICB0aGlzLmxldmVsID0gbmV3TGV2ZWw7XG4gICAgfVxuICB9XG4gIGdldCBmaXJzdExldmVsKCkge1xuICAgIHJldHVybiB0aGlzLl9maXJzdExldmVsO1xuICB9XG4gIHNldCBmaXJzdExldmVsKG5ld0xldmVsKSB7XG4gICAgdGhpcy5fZmlyc3RMZXZlbCA9IG5ld0xldmVsO1xuICB9XG4gIGdldCBzdGFydExldmVsKCkge1xuICAgIC8vIFNldHRpbmcgaGxzLnN0YXJ0TGV2ZWwgKHRoaXMuX3N0YXJ0TGV2ZWwpIG92ZXJyaWRlcyBjb25maWcuc3RhcnRMZXZlbFxuICAgIGlmICh0aGlzLl9zdGFydExldmVsID09PSB1bmRlZmluZWQpIHtcbiAgICAgIGNvbnN0IGNvbmZpZ1N0YXJ0TGV2ZWwgPSB0aGlzLmhscy5jb25maWcuc3RhcnRMZXZlbDtcbiAgICAgIGlmIChjb25maWdTdGFydExldmVsICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGNvbmZpZ1N0YXJ0TGV2ZWw7XG4gICAgICB9XG4gICAgICByZXR1cm4gdGhpcy5obHMuZmlyc3RBdXRvTGV2ZWw7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLl9zdGFydExldmVsO1xuICB9XG4gIHNldCBzdGFydExldmVsKG5ld0xldmVsKSB7XG4gICAgdGhpcy5fc3RhcnRMZXZlbCA9IG5ld0xldmVsO1xuICB9XG4gIG9uRXJyb3IoZXZlbnQsIGRhdGEpIHtcbiAgICBpZiAoZGF0YS5mYXRhbCB8fCAhZGF0YS5jb250ZXh0KSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChkYXRhLmNvbnRleHQudHlwZSA9PT0gUGxheWxpc3RDb250ZXh0VHlwZS5MRVZFTCAmJiBkYXRhLmNvbnRleHQubGV2ZWwgPT09IHRoaXMubGV2ZWwpIHtcbiAgICAgIHRoaXMuY2hlY2tSZXRyeShkYXRhKTtcbiAgICB9XG4gIH1cblxuICAvLyByZXNldCBlcnJvcnMgb24gdGhlIHN1Y2Nlc3NmdWwgbG9hZCBvZiBhIGZyYWdtZW50XG4gIG9uRnJhZ0J1ZmZlcmVkKGV2ZW50LCB7XG4gICAgZnJhZ1xuICB9KSB7XG4gICAgaWYgKGZyYWcgIT09IHVuZGVmaW5lZCAmJiBmcmFnLnR5cGUgPT09IFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4pIHtcbiAgICAgIGNvbnN0IGVsID0gZnJhZy5lbGVtZW50YXJ5U3RyZWFtcztcbiAgICAgIGlmICghT2JqZWN0LmtleXMoZWwpLnNvbWUodHlwZSA9PiAhIWVsW3R5cGVdKSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBsZXZlbCA9IHRoaXMuX2xldmVsc1tmcmFnLmxldmVsXTtcbiAgICAgIGlmIChsZXZlbCAhPSBudWxsICYmIGxldmVsLmxvYWRFcnJvcikge1xuICAgICAgICB0aGlzLmxvZyhgUmVzZXR0aW5nIGxldmVsIGVycm9yIGNvdW50IG9mICR7bGV2ZWwubG9hZEVycm9yfSBvbiBmcmFnIGJ1ZmZlcmVkYCk7XG4gICAgICAgIGxldmVsLmxvYWRFcnJvciA9IDA7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIG9uTGV2ZWxMb2FkZWQoZXZlbnQsIGRhdGEpIHtcbiAgICB2YXIgX2RhdGEkZGVsaXZlcnlEaXJlY3RpMjtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbCxcbiAgICAgIGRldGFpbHNcbiAgICB9ID0gZGF0YTtcbiAgICBjb25zdCBjdXJMZXZlbCA9IHRoaXMuX2xldmVsc1tsZXZlbF07XG4gICAgaWYgKCFjdXJMZXZlbCkge1xuICAgICAgdmFyIF9kYXRhJGRlbGl2ZXJ5RGlyZWN0aTtcbiAgICAgIHRoaXMud2FybihgSW52YWxpZCBsZXZlbCBpbmRleCAke2xldmVsfWApO1xuICAgICAgaWYgKChfZGF0YSRkZWxpdmVyeURpcmVjdGkgPSBkYXRhLmRlbGl2ZXJ5RGlyZWN0aXZlcykgIT0gbnVsbCAmJiBfZGF0YSRkZWxpdmVyeURpcmVjdGkuc2tpcCkge1xuICAgICAgICBkZXRhaWxzLmRlbHRhVXBkYXRlRmFpbGVkID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBvbmx5IHByb2Nlc3MgbGV2ZWwgbG9hZGVkIGV2ZW50cyBtYXRjaGluZyB3aXRoIGV4cGVjdGVkIGxldmVsXG4gICAgaWYgKGxldmVsID09PSB0aGlzLmN1cnJlbnRMZXZlbEluZGV4KSB7XG4gICAgICAvLyByZXNldCBsZXZlbCBsb2FkIGVycm9yIGNvdW50ZXIgb24gc3VjY2Vzc2Z1bCBsZXZlbCBsb2FkZWQgb25seSBpZiB0aGVyZSBpcyBubyBpc3N1ZXMgd2l0aCBmcmFnbWVudHNcbiAgICAgIGlmIChjdXJMZXZlbC5mcmFnbWVudEVycm9yID09PSAwKSB7XG4gICAgICAgIGN1ckxldmVsLmxvYWRFcnJvciA9IDA7XG4gICAgICB9XG4gICAgICB0aGlzLnBsYXlsaXN0TG9hZGVkKGxldmVsLCBkYXRhLCBjdXJMZXZlbC5kZXRhaWxzKTtcbiAgICB9IGVsc2UgaWYgKChfZGF0YSRkZWxpdmVyeURpcmVjdGkyID0gZGF0YS5kZWxpdmVyeURpcmVjdGl2ZXMpICE9IG51bGwgJiYgX2RhdGEkZGVsaXZlcnlEaXJlY3RpMi5za2lwKSB7XG4gICAgICAvLyByZWNlaXZlZCBhIGRlbHRhIHBsYXlsaXN0IHVwZGF0ZSB0aGF0IGNhbm5vdCBiZSBtZXJnZWRcbiAgICAgIGRldGFpbHMuZGVsdGFVcGRhdGVGYWlsZWQgPSB0cnVlO1xuICAgIH1cbiAgfVxuICBsb2FkUGxheWxpc3QoaGxzVXJsUGFyYW1ldGVycykge1xuICAgIHN1cGVyLmxvYWRQbGF5bGlzdCgpO1xuICAgIGNvbnN0IGN1cnJlbnRMZXZlbEluZGV4ID0gdGhpcy5jdXJyZW50TGV2ZWxJbmRleDtcbiAgICBjb25zdCBjdXJyZW50TGV2ZWwgPSB0aGlzLmN1cnJlbnRMZXZlbDtcbiAgICBpZiAoY3VycmVudExldmVsICYmIHRoaXMuc2hvdWxkTG9hZFBsYXlsaXN0KGN1cnJlbnRMZXZlbCkpIHtcbiAgICAgIGxldCB1cmwgPSBjdXJyZW50TGV2ZWwudXJpO1xuICAgICAgaWYgKGhsc1VybFBhcmFtZXRlcnMpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICB1cmwgPSBobHNVcmxQYXJhbWV0ZXJzLmFkZERpcmVjdGl2ZXModXJsKTtcbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICB0aGlzLndhcm4oYENvdWxkIG5vdCBjb25zdHJ1Y3QgbmV3IFVSTCB3aXRoIEhMUyBEZWxpdmVyeSBEaXJlY3RpdmVzOiAke2Vycm9yfWApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBjb25zdCBwYXRod2F5SWQgPSBjdXJyZW50TGV2ZWwuYXR0cnNbJ1BBVEhXQVktSUQnXTtcbiAgICAgIHRoaXMubG9nKGBMb2FkaW5nIGxldmVsIGluZGV4ICR7Y3VycmVudExldmVsSW5kZXh9JHsoaGxzVXJsUGFyYW1ldGVycyA9PSBudWxsID8gdm9pZCAwIDogaGxzVXJsUGFyYW1ldGVycy5tc24pICE9PSB1bmRlZmluZWQgPyAnIGF0IHNuICcgKyBobHNVcmxQYXJhbWV0ZXJzLm1zbiArICcgcGFydCAnICsgaGxzVXJsUGFyYW1ldGVycy5wYXJ0IDogJyd9IHdpdGgke3BhdGh3YXlJZCA/ICcgUGF0aHdheSAnICsgcGF0aHdheUlkIDogJyd9ICR7dXJsfWApO1xuXG4gICAgICAvLyBjb25zb2xlLmxvZygnQ3VycmVudCBhdWRpbyB0cmFjayBncm91cCBJRDonLCB0aGlzLmhscy5hdWRpb1RyYWNrc1t0aGlzLmhscy5hdWRpb1RyYWNrXS5ncm91cElkKTtcbiAgICAgIC8vIGNvbnNvbGUubG9nKCdOZXcgdmlkZW8gcXVhbGl0eSBsZXZlbCBhdWRpbyBncm91cCBpZDonLCBsZXZlbE9iamVjdC5hdHRycy5BVURJTywgbGV2ZWwpO1xuICAgICAgdGhpcy5jbGVhclRpbWVyKCk7XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5MRVZFTF9MT0FESU5HLCB7XG4gICAgICAgIHVybCxcbiAgICAgICAgbGV2ZWw6IGN1cnJlbnRMZXZlbEluZGV4LFxuICAgICAgICBwYXRod2F5SWQ6IGN1cnJlbnRMZXZlbC5hdHRyc1snUEFUSFdBWS1JRCddLFxuICAgICAgICBpZDogMCxcbiAgICAgICAgLy8gRGVwcmVjYXRlZCBMZXZlbCB1cmxJZFxuICAgICAgICBkZWxpdmVyeURpcmVjdGl2ZXM6IGhsc1VybFBhcmFtZXRlcnMgfHwgbnVsbFxuICAgICAgfSk7XG4gICAgfVxuICB9XG4gIGdldCBuZXh0TG9hZExldmVsKCkge1xuICAgIGlmICh0aGlzLm1hbnVhbExldmVsSW5kZXggIT09IC0xKSB7XG4gICAgICByZXR1cm4gdGhpcy5tYW51YWxMZXZlbEluZGV4O1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gdGhpcy5obHMubmV4dEF1dG9MZXZlbDtcbiAgICB9XG4gIH1cbiAgc2V0IG5leHRMb2FkTGV2ZWwobmV4dExldmVsKSB7XG4gICAgdGhpcy5sZXZlbCA9IG5leHRMZXZlbDtcbiAgICBpZiAodGhpcy5tYW51YWxMZXZlbEluZGV4ID09PSAtMSkge1xuICAgICAgdGhpcy5obHMubmV4dEF1dG9MZXZlbCA9IG5leHRMZXZlbDtcbiAgICB9XG4gIH1cbiAgcmVtb3ZlTGV2ZWwobGV2ZWxJbmRleCkge1xuICAgIHZhciBfdGhpcyRjdXJyZW50TGV2ZWw7XG4gICAgY29uc3QgbGV2ZWxzID0gdGhpcy5fbGV2ZWxzLmZpbHRlcigobGV2ZWwsIGluZGV4KSA9PiB7XG4gICAgICBpZiAoaW5kZXggIT09IGxldmVsSW5kZXgpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5zdGVlcmluZykge1xuICAgICAgICB0aGlzLnN0ZWVyaW5nLnJlbW92ZUxldmVsKGxldmVsKTtcbiAgICAgIH1cbiAgICAgIGlmIChsZXZlbCA9PT0gdGhpcy5jdXJyZW50TGV2ZWwpIHtcbiAgICAgICAgdGhpcy5jdXJyZW50TGV2ZWwgPSBudWxsO1xuICAgICAgICB0aGlzLmN1cnJlbnRMZXZlbEluZGV4ID0gLTE7XG4gICAgICAgIGlmIChsZXZlbC5kZXRhaWxzKSB7XG4gICAgICAgICAgbGV2ZWwuZGV0YWlscy5mcmFnbWVudHMuZm9yRWFjaChmID0+IGYubGV2ZWwgPSAtMSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9KTtcbiAgICByZWFzc2lnbkZyYWdtZW50TGV2ZWxJbmRleGVzKGxldmVscyk7XG4gICAgdGhpcy5fbGV2ZWxzID0gbGV2ZWxzO1xuICAgIGlmICh0aGlzLmN1cnJlbnRMZXZlbEluZGV4ID4gLTEgJiYgKF90aGlzJGN1cnJlbnRMZXZlbCA9IHRoaXMuY3VycmVudExldmVsKSAhPSBudWxsICYmIF90aGlzJGN1cnJlbnRMZXZlbC5kZXRhaWxzKSB7XG4gICAgICB0aGlzLmN1cnJlbnRMZXZlbEluZGV4ID0gdGhpcy5jdXJyZW50TGV2ZWwuZGV0YWlscy5mcmFnbWVudHNbMF0ubGV2ZWw7XG4gICAgfVxuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkxFVkVMU19VUERBVEVELCB7XG4gICAgICBsZXZlbHNcbiAgICB9KTtcbiAgfVxuICBvbkxldmVsc1VwZGF0ZWQoZXZlbnQsIHtcbiAgICBsZXZlbHNcbiAgfSkge1xuICAgIHRoaXMuX2xldmVscyA9IGxldmVscztcbiAgfVxuICBjaGVja01heEF1dG9VcGRhdGVkKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGF1dG9MZXZlbENhcHBpbmcsXG4gICAgICBtYXhBdXRvTGV2ZWwsXG4gICAgICBtYXhIZGNwTGV2ZWxcbiAgICB9ID0gdGhpcy5obHM7XG4gICAgaWYgKHRoaXMuX21heEF1dG9MZXZlbCAhPT0gbWF4QXV0b0xldmVsKSB7XG4gICAgICB0aGlzLl9tYXhBdXRvTGV2ZWwgPSBtYXhBdXRvTGV2ZWw7XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5NQVhfQVVUT19MRVZFTF9VUERBVEVELCB7XG4gICAgICAgIGF1dG9MZXZlbENhcHBpbmcsXG4gICAgICAgIGxldmVsczogdGhpcy5sZXZlbHMsXG4gICAgICAgIG1heEF1dG9MZXZlbCxcbiAgICAgICAgbWluQXV0b0xldmVsOiB0aGlzLmhscy5taW5BdXRvTGV2ZWwsXG4gICAgICAgIG1heEhkY3BMZXZlbFxuICAgICAgfSk7XG4gICAgfVxuICB9XG59XG5mdW5jdGlvbiBhc3NpZ25UcmFja0lkc0J5R3JvdXAodHJhY2tzKSB7XG4gIGNvbnN0IGdyb3VwcyA9IHt9O1xuICB0cmFja3MuZm9yRWFjaCh0cmFjayA9PiB7XG4gICAgY29uc3QgZ3JvdXBJZCA9IHRyYWNrLmdyb3VwSWQgfHwgJyc7XG4gICAgdHJhY2suaWQgPSBncm91cHNbZ3JvdXBJZF0gPSBncm91cHNbZ3JvdXBJZF0gfHwgMDtcbiAgICBncm91cHNbZ3JvdXBJZF0rKztcbiAgfSk7XG59XG5cbmNsYXNzIEtleUxvYWRlciB7XG4gIGNvbnN0cnVjdG9yKGNvbmZpZykge1xuICAgIHRoaXMuY29uZmlnID0gdm9pZCAwO1xuICAgIHRoaXMua2V5VXJpVG9LZXlJbmZvID0ge307XG4gICAgdGhpcy5lbWVDb250cm9sbGVyID0gbnVsbDtcbiAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgfVxuICBhYm9ydCh0eXBlKSB7XG4gICAgZm9yIChjb25zdCB1cmkgaW4gdGhpcy5rZXlVcmlUb0tleUluZm8pIHtcbiAgICAgIGNvbnN0IGxvYWRlciA9IHRoaXMua2V5VXJpVG9LZXlJbmZvW3VyaV0ubG9hZGVyO1xuICAgICAgaWYgKGxvYWRlcikge1xuICAgICAgICB2YXIgX2xvYWRlciRjb250ZXh0O1xuICAgICAgICBpZiAodHlwZSAmJiB0eXBlICE9PSAoKF9sb2FkZXIkY29udGV4dCA9IGxvYWRlci5jb250ZXh0KSA9PSBudWxsID8gdm9pZCAwIDogX2xvYWRlciRjb250ZXh0LmZyYWcudHlwZSkpIHtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgbG9hZGVyLmFib3J0KCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGRldGFjaCgpIHtcbiAgICBmb3IgKGNvbnN0IHVyaSBpbiB0aGlzLmtleVVyaVRvS2V5SW5mbykge1xuICAgICAgY29uc3Qga2V5SW5mbyA9IHRoaXMua2V5VXJpVG9LZXlJbmZvW3VyaV07XG4gICAgICAvLyBSZW1vdmUgY2FjaGVkIEVNRSBrZXlzIG9uIGRldGFjaFxuICAgICAgaWYgKGtleUluZm8ubWVkaWFLZXlTZXNzaW9uQ29udGV4dCB8fCBrZXlJbmZvLmRlY3J5cHRkYXRhLmlzQ29tbW9uRW5jcnlwdGlvbikge1xuICAgICAgICBkZWxldGUgdGhpcy5rZXlVcmlUb0tleUluZm9bdXJpXTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgZGVzdHJveSgpIHtcbiAgICB0aGlzLmRldGFjaCgpO1xuICAgIGZvciAoY29uc3QgdXJpIGluIHRoaXMua2V5VXJpVG9LZXlJbmZvKSB7XG4gICAgICBjb25zdCBsb2FkZXIgPSB0aGlzLmtleVVyaVRvS2V5SW5mb1t1cmldLmxvYWRlcjtcbiAgICAgIGlmIChsb2FkZXIpIHtcbiAgICAgICAgbG9hZGVyLmRlc3Ryb3koKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5rZXlVcmlUb0tleUluZm8gPSB7fTtcbiAgfVxuICBjcmVhdGVLZXlMb2FkRXJyb3IoZnJhZywgZGV0YWlscyA9IEVycm9yRGV0YWlscy5LRVlfTE9BRF9FUlJPUiwgZXJyb3IsIG5ldHdvcmtEZXRhaWxzLCByZXNwb25zZSkge1xuICAgIHJldHVybiBuZXcgTG9hZEVycm9yKHtcbiAgICAgIHR5cGU6IEVycm9yVHlwZXMuTkVUV09SS19FUlJPUixcbiAgICAgIGRldGFpbHMsXG4gICAgICBmYXRhbDogZmFsc2UsXG4gICAgICBmcmFnLFxuICAgICAgcmVzcG9uc2UsXG4gICAgICBlcnJvcixcbiAgICAgIG5ldHdvcmtEZXRhaWxzXG4gICAgfSk7XG4gIH1cbiAgbG9hZENsZWFyKGxvYWRpbmdGcmFnLCBlbmNyeXB0ZWRGcmFnbWVudHMpIHtcbiAgICBpZiAodGhpcy5lbWVDb250cm9sbGVyICYmIHRoaXMuY29uZmlnLmVtZUVuYWJsZWQpIHtcbiAgICAgIC8vIGFjY2VzcyBrZXktc3lzdGVtIHdpdGggbmVhcmVzdCBrZXkgb24gc3RhcnQgKGxvYWlkbmcgZnJhZyBpcyB1bmVuY3J5cHRlZClcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgc24sXG4gICAgICAgIGNjXG4gICAgICB9ID0gbG9hZGluZ0ZyYWc7XG4gICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGVuY3J5cHRlZEZyYWdtZW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgICBjb25zdCBmcmFnID0gZW5jcnlwdGVkRnJhZ21lbnRzW2ldO1xuICAgICAgICBpZiAoY2MgPD0gZnJhZy5jYyAmJiAoc24gPT09ICdpbml0U2VnbWVudCcgfHwgZnJhZy5zbiA9PT0gJ2luaXRTZWdtZW50JyB8fCBzbiA8IGZyYWcuc24pKSB7XG4gICAgICAgICAgdGhpcy5lbWVDb250cm9sbGVyLnNlbGVjdEtleVN5c3RlbUZvcm1hdChmcmFnKS50aGVuKGtleVN5c3RlbUZvcm1hdCA9PiB7XG4gICAgICAgICAgICBmcmFnLnNldEtleUZvcm1hdChrZXlTeXN0ZW1Gb3JtYXQpO1xuICAgICAgICAgIH0pO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGxvYWQoZnJhZykge1xuICAgIGlmICghZnJhZy5kZWNyeXB0ZGF0YSAmJiBmcmFnLmVuY3J5cHRlZCAmJiB0aGlzLmVtZUNvbnRyb2xsZXIpIHtcbiAgICAgIC8vIE11bHRpcGxlIGtleXMsIGJ1dCBub25lIHNlbGVjdGVkLCByZXNvbHZlIGluIGVtZS1jb250cm9sbGVyXG4gICAgICByZXR1cm4gdGhpcy5lbWVDb250cm9sbGVyLnNlbGVjdEtleVN5c3RlbUZvcm1hdChmcmFnKS50aGVuKGtleVN5c3RlbUZvcm1hdCA9PiB7XG4gICAgICAgIHJldHVybiB0aGlzLmxvYWRJbnRlcm5hbChmcmFnLCBrZXlTeXN0ZW1Gb3JtYXQpO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmxvYWRJbnRlcm5hbChmcmFnKTtcbiAgfVxuICBsb2FkSW50ZXJuYWwoZnJhZywga2V5U3lzdGVtRm9ybWF0KSB7XG4gICAgdmFyIF9rZXlJbmZvLCBfa2V5SW5mbzI7XG4gICAgaWYgKGtleVN5c3RlbUZvcm1hdCkge1xuICAgICAgZnJhZy5zZXRLZXlGb3JtYXQoa2V5U3lzdGVtRm9ybWF0KTtcbiAgICB9XG4gICAgY29uc3QgZGVjcnlwdGRhdGEgPSBmcmFnLmRlY3J5cHRkYXRhO1xuICAgIGlmICghZGVjcnlwdGRhdGEpIHtcbiAgICAgIGNvbnN0IGVycm9yID0gbmV3IEVycm9yKGtleVN5c3RlbUZvcm1hdCA/IGBFeHBlY3RlZCBmcmFnLmRlY3J5cHRkYXRhIHRvIGJlIGRlZmluZWQgYWZ0ZXIgc2V0dGluZyBmb3JtYXQgJHtrZXlTeXN0ZW1Gb3JtYXR9YCA6ICdNaXNzaW5nIGRlY3J5cHRpb24gZGF0YSBvbiBmcmFnbWVudCBpbiBvbktleUxvYWRpbmcnKTtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdCh0aGlzLmNyZWF0ZUtleUxvYWRFcnJvcihmcmFnLCBFcnJvckRldGFpbHMuS0VZX0xPQURfRVJST1IsIGVycm9yKSk7XG4gICAgfVxuICAgIGNvbnN0IHVyaSA9IGRlY3J5cHRkYXRhLnVyaTtcbiAgICBpZiAoIXVyaSkge1xuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KHRoaXMuY3JlYXRlS2V5TG9hZEVycm9yKGZyYWcsIEVycm9yRGV0YWlscy5LRVlfTE9BRF9FUlJPUiwgbmV3IEVycm9yKGBJbnZhbGlkIGtleSBVUkk6IFwiJHt1cml9XCJgKSkpO1xuICAgIH1cbiAgICBsZXQga2V5SW5mbyA9IHRoaXMua2V5VXJpVG9LZXlJbmZvW3VyaV07XG4gICAgaWYgKChfa2V5SW5mbyA9IGtleUluZm8pICE9IG51bGwgJiYgX2tleUluZm8uZGVjcnlwdGRhdGEua2V5KSB7XG4gICAgICBkZWNyeXB0ZGF0YS5rZXkgPSBrZXlJbmZvLmRlY3J5cHRkYXRhLmtleTtcbiAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoe1xuICAgICAgICBmcmFnLFxuICAgICAgICBrZXlJbmZvXG4gICAgICB9KTtcbiAgICB9XG4gICAgLy8gUmV0dXJuIGtleSBsb2FkIHByb21pc2UgYXMgbG9uZyBhcyBpdCBkb2VzIG5vdCBoYXZlIGEgbWVkaWFrZXkgc2Vzc2lvbiB3aXRoIGFuIHVudXNhYmxlIGtleSBzdGF0dXNcbiAgICBpZiAoKF9rZXlJbmZvMiA9IGtleUluZm8pICE9IG51bGwgJiYgX2tleUluZm8yLmtleUxvYWRQcm9taXNlKSB7XG4gICAgICB2YXIgX2tleUluZm8kbWVkaWFLZXlTZXNzO1xuICAgICAgc3dpdGNoICgoX2tleUluZm8kbWVkaWFLZXlTZXNzID0ga2V5SW5mby5tZWRpYUtleVNlc3Npb25Db250ZXh0KSA9PSBudWxsID8gdm9pZCAwIDogX2tleUluZm8kbWVkaWFLZXlTZXNzLmtleVN0YXR1cykge1xuICAgICAgICBjYXNlIHVuZGVmaW5lZDpcbiAgICAgICAgY2FzZSAnc3RhdHVzLXBlbmRpbmcnOlxuICAgICAgICBjYXNlICd1c2FibGUnOlxuICAgICAgICBjYXNlICd1c2FibGUtaW4tZnV0dXJlJzpcbiAgICAgICAgICByZXR1cm4ga2V5SW5mby5rZXlMb2FkUHJvbWlzZS50aGVuKGtleUxvYWRlZERhdGEgPT4ge1xuICAgICAgICAgICAgLy8gUmV0dXJuIHRoZSBjb3JyZWN0IGZyYWdtZW50IHdpdGggdXBkYXRlZCBkZWNyeXB0ZGF0YSBrZXkgYW5kIGxvYWRlZCBrZXlJbmZvXG4gICAgICAgICAgICBkZWNyeXB0ZGF0YS5rZXkgPSBrZXlMb2FkZWREYXRhLmtleUluZm8uZGVjcnlwdGRhdGEua2V5O1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgZnJhZyxcbiAgICAgICAgICAgICAga2V5SW5mb1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIC8vIElmIHdlIGhhdmUgYSBrZXkgc2Vzc2lvbiBhbmQgc3RhdHVzIGFuZCBpdCBpcyBub3QgcGVuZGluZyBvciB1c2FibGUsIGNvbnRpbnVlXG4gICAgICAvLyBUaGlzIHdpbGwgZ28gYmFjayB0byB0aGUgZW1lLWNvbnRyb2xsZXIgZm9yIGV4cGlyZWQga2V5cyB0byBnZXQgYSBuZXcga2V5TG9hZFByb21pc2VcbiAgICB9XG5cbiAgICAvLyBMb2FkIHRoZSBrZXkgb3IgcmV0dXJuIHRoZSBsb2FkaW5nIHByb21pc2VcbiAgICBrZXlJbmZvID0gdGhpcy5rZXlVcmlUb0tleUluZm9bdXJpXSA9IHtcbiAgICAgIGRlY3J5cHRkYXRhLFxuICAgICAga2V5TG9hZFByb21pc2U6IG51bGwsXG4gICAgICBsb2FkZXI6IG51bGwsXG4gICAgICBtZWRpYUtleVNlc3Npb25Db250ZXh0OiBudWxsXG4gICAgfTtcbiAgICBzd2l0Y2ggKGRlY3J5cHRkYXRhLm1ldGhvZCkge1xuICAgICAgY2FzZSAnSVNPLTIzMDAxLTcnOlxuICAgICAgY2FzZSAnU0FNUExFLUFFUyc6XG4gICAgICBjYXNlICdTQU1QTEUtQUVTLUNFTkMnOlxuICAgICAgY2FzZSAnU0FNUExFLUFFUy1DVFInOlxuICAgICAgICBpZiAoZGVjcnlwdGRhdGEua2V5Rm9ybWF0ID09PSAnaWRlbnRpdHknKSB7XG4gICAgICAgICAgLy8gbG9hZEtleUhUVFAgaGFuZGxlcyBodHRwKHMpIGFuZCBkYXRhIFVSTHNcbiAgICAgICAgICByZXR1cm4gdGhpcy5sb2FkS2V5SFRUUChrZXlJbmZvLCBmcmFnKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5sb2FkS2V5RU1FKGtleUluZm8sIGZyYWcpO1xuICAgICAgY2FzZSAnQUVTLTEyOCc6XG4gICAgICAgIHJldHVybiB0aGlzLmxvYWRLZXlIVFRQKGtleUluZm8sIGZyYWcpO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KHRoaXMuY3JlYXRlS2V5TG9hZEVycm9yKGZyYWcsIEVycm9yRGV0YWlscy5LRVlfTE9BRF9FUlJPUiwgbmV3IEVycm9yKGBLZXkgc3VwcGxpZWQgd2l0aCB1bnN1cHBvcnRlZCBNRVRIT0Q6IFwiJHtkZWNyeXB0ZGF0YS5tZXRob2R9XCJgKSkpO1xuICAgIH1cbiAgfVxuICBsb2FkS2V5RU1FKGtleUluZm8sIGZyYWcpIHtcbiAgICBjb25zdCBrZXlMb2FkZWREYXRhID0ge1xuICAgICAgZnJhZyxcbiAgICAgIGtleUluZm9cbiAgICB9O1xuICAgIGlmICh0aGlzLmVtZUNvbnRyb2xsZXIgJiYgdGhpcy5jb25maWcuZW1lRW5hYmxlZCkge1xuICAgICAgY29uc3Qga2V5U2Vzc2lvbkNvbnRleHRQcm9taXNlID0gdGhpcy5lbWVDb250cm9sbGVyLmxvYWRLZXkoa2V5TG9hZGVkRGF0YSk7XG4gICAgICBpZiAoa2V5U2Vzc2lvbkNvbnRleHRQcm9taXNlKSB7XG4gICAgICAgIHJldHVybiAoa2V5SW5mby5rZXlMb2FkUHJvbWlzZSA9IGtleVNlc3Npb25Db250ZXh0UHJvbWlzZS50aGVuKGtleVNlc3Npb25Db250ZXh0ID0+IHtcbiAgICAgICAgICBrZXlJbmZvLm1lZGlhS2V5U2Vzc2lvbkNvbnRleHQgPSBrZXlTZXNzaW9uQ29udGV4dDtcbiAgICAgICAgICByZXR1cm4ga2V5TG9hZGVkRGF0YTtcbiAgICAgICAgfSkpLmNhdGNoKGVycm9yID0+IHtcbiAgICAgICAgICAvLyBSZW1vdmUgcHJvbWlzZSBmb3IgbGljZW5zZSByZW5ld2FsIG9yIHJldHJ5XG4gICAgICAgICAga2V5SW5mby5rZXlMb2FkUHJvbWlzZSA9IG51bGw7XG4gICAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKGtleUxvYWRlZERhdGEpO1xuICB9XG4gIGxvYWRLZXlIVFRQKGtleUluZm8sIGZyYWcpIHtcbiAgICBjb25zdCBjb25maWcgPSB0aGlzLmNvbmZpZztcbiAgICBjb25zdCBMb2FkZXIgPSBjb25maWcubG9hZGVyO1xuICAgIGNvbnN0IGtleUxvYWRlciA9IG5ldyBMb2FkZXIoY29uZmlnKTtcbiAgICBmcmFnLmtleUxvYWRlciA9IGtleUluZm8ubG9hZGVyID0ga2V5TG9hZGVyO1xuICAgIHJldHVybiBrZXlJbmZvLmtleUxvYWRQcm9taXNlID0gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgbG9hZGVyQ29udGV4dCA9IHtcbiAgICAgICAga2V5SW5mbyxcbiAgICAgICAgZnJhZyxcbiAgICAgICAgcmVzcG9uc2VUeXBlOiAnYXJyYXlidWZmZXInLFxuICAgICAgICB1cmw6IGtleUluZm8uZGVjcnlwdGRhdGEudXJpXG4gICAgICB9O1xuXG4gICAgICAvLyBtYXhSZXRyeSBpcyAwIHNvIHRoYXQgaW5zdGVhZCBvZiByZXRyeWluZyB0aGUgc2FtZSBrZXkgb24gdGhlIHNhbWUgdmFyaWFudCBtdWx0aXBsZSB0aW1lcyxcbiAgICAgIC8vIGtleS1sb2FkZXIgd2lsbCB0cmlnZ2VyIGFuIGVycm9yIGFuZCByZWx5IG9uIHN0cmVhbS1jb250cm9sbGVyIHRvIGhhbmRsZSByZXRyeSBsb2dpYy5cbiAgICAgIC8vIHRoaXMgd2lsbCBhbHNvIGFsaWduIHJldHJ5IGxvZ2ljIHdpdGggZnJhZ21lbnQtbG9hZGVyXG4gICAgICBjb25zdCBsb2FkUG9saWN5ID0gY29uZmlnLmtleUxvYWRQb2xpY3kuZGVmYXVsdDtcbiAgICAgIGNvbnN0IGxvYWRlckNvbmZpZyA9IHtcbiAgICAgICAgbG9hZFBvbGljeSxcbiAgICAgICAgdGltZW91dDogbG9hZFBvbGljeS5tYXhMb2FkVGltZU1zLFxuICAgICAgICBtYXhSZXRyeTogMCxcbiAgICAgICAgcmV0cnlEZWxheTogMCxcbiAgICAgICAgbWF4UmV0cnlEZWxheTogMFxuICAgICAgfTtcbiAgICAgIGNvbnN0IGxvYWRlckNhbGxiYWNrcyA9IHtcbiAgICAgICAgb25TdWNjZXNzOiAocmVzcG9uc2UsIHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICBrZXlJbmZvLFxuICAgICAgICAgICAgdXJsOiB1cmlcbiAgICAgICAgICB9ID0gY29udGV4dDtcbiAgICAgICAgICBpZiAoIWZyYWcuZGVjcnlwdGRhdGEgfHwga2V5SW5mbyAhPT0gdGhpcy5rZXlVcmlUb0tleUluZm9bdXJpXSkge1xuICAgICAgICAgICAgcmV0dXJuIHJlamVjdCh0aGlzLmNyZWF0ZUtleUxvYWRFcnJvcihmcmFnLCBFcnJvckRldGFpbHMuS0VZX0xPQURfRVJST1IsIG5ldyBFcnJvcignYWZ0ZXIga2V5IGxvYWQsIGRlY3J5cHRkYXRhIHVuc2V0IG9yIGNoYW5nZWQnKSwgbmV0d29ya0RldGFpbHMpKTtcbiAgICAgICAgICB9XG4gICAgICAgICAga2V5SW5mby5kZWNyeXB0ZGF0YS5rZXkgPSBmcmFnLmRlY3J5cHRkYXRhLmtleSA9IG5ldyBVaW50OEFycmF5KHJlc3BvbnNlLmRhdGEpO1xuXG4gICAgICAgICAgLy8gZGV0YWNoIGZyYWdtZW50IGtleSBsb2FkZXIgb24gbG9hZCBzdWNjZXNzXG4gICAgICAgICAgZnJhZy5rZXlMb2FkZXIgPSBudWxsO1xuICAgICAgICAgIGtleUluZm8ubG9hZGVyID0gbnVsbDtcbiAgICAgICAgICByZXNvbHZlKHtcbiAgICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgICBrZXlJbmZvXG4gICAgICAgICAgfSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uRXJyb3I6IChyZXNwb25zZSwgY29udGV4dCwgbmV0d29ya0RldGFpbHMsIHN0YXRzKSA9PiB7XG4gICAgICAgICAgdGhpcy5yZXNldExvYWRlcihjb250ZXh0KTtcbiAgICAgICAgICByZWplY3QodGhpcy5jcmVhdGVLZXlMb2FkRXJyb3IoZnJhZywgRXJyb3JEZXRhaWxzLktFWV9MT0FEX0VSUk9SLCBuZXcgRXJyb3IoYEhUVFAgRXJyb3IgJHtyZXNwb25zZS5jb2RlfSBsb2FkaW5nIGtleSAke3Jlc3BvbnNlLnRleHR9YCksIG5ldHdvcmtEZXRhaWxzLCBfb2JqZWN0U3ByZWFkMih7XG4gICAgICAgICAgICB1cmw6IGxvYWRlckNvbnRleHQudXJsLFxuICAgICAgICAgICAgZGF0YTogdW5kZWZpbmVkXG4gICAgICAgICAgfSwgcmVzcG9uc2UpKSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uVGltZW91dDogKHN0YXRzLCBjb250ZXh0LCBuZXR3b3JrRGV0YWlscykgPT4ge1xuICAgICAgICAgIHRoaXMucmVzZXRMb2FkZXIoY29udGV4dCk7XG4gICAgICAgICAgcmVqZWN0KHRoaXMuY3JlYXRlS2V5TG9hZEVycm9yKGZyYWcsIEVycm9yRGV0YWlscy5LRVlfTE9BRF9USU1FT1VULCBuZXcgRXJyb3IoJ2tleSBsb2FkaW5nIHRpbWVkIG91dCcpLCBuZXR3b3JrRGV0YWlscykpO1xuICAgICAgICB9LFxuICAgICAgICBvbkFib3J0OiAoc3RhdHMsIGNvbnRleHQsIG5ldHdvcmtEZXRhaWxzKSA9PiB7XG4gICAgICAgICAgdGhpcy5yZXNldExvYWRlcihjb250ZXh0KTtcbiAgICAgICAgICByZWplY3QodGhpcy5jcmVhdGVLZXlMb2FkRXJyb3IoZnJhZywgRXJyb3JEZXRhaWxzLklOVEVSTkFMX0FCT1JURUQsIG5ldyBFcnJvcigna2V5IGxvYWRpbmcgYWJvcnRlZCcpLCBuZXR3b3JrRGV0YWlscykpO1xuICAgICAgICB9XG4gICAgICB9O1xuICAgICAga2V5TG9hZGVyLmxvYWQobG9hZGVyQ29udGV4dCwgbG9hZGVyQ29uZmlnLCBsb2FkZXJDYWxsYmFja3MpO1xuICAgIH0pO1xuICB9XG4gIHJlc2V0TG9hZGVyKGNvbnRleHQpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAga2V5SW5mbyxcbiAgICAgIHVybDogdXJpXG4gICAgfSA9IGNvbnRleHQ7XG4gICAgY29uc3QgbG9hZGVyID0ga2V5SW5mby5sb2FkZXI7XG4gICAgaWYgKGZyYWcua2V5TG9hZGVyID09PSBsb2FkZXIpIHtcbiAgICAgIGZyYWcua2V5TG9hZGVyID0gbnVsbDtcbiAgICAgIGtleUluZm8ubG9hZGVyID0gbnVsbDtcbiAgICB9XG4gICAgZGVsZXRlIHRoaXMua2V5VXJpVG9LZXlJbmZvW3VyaV07XG4gICAgaWYgKGxvYWRlcikge1xuICAgICAgbG9hZGVyLmRlc3Ryb3koKTtcbiAgICB9XG4gIH1cbn1cblxuZnVuY3Rpb24gZ2V0U291cmNlQnVmZmVyKCkge1xuICByZXR1cm4gc2VsZi5Tb3VyY2VCdWZmZXIgfHwgc2VsZi5XZWJLaXRTb3VyY2VCdWZmZXI7XG59XG5mdW5jdGlvbiBpc01TRVN1cHBvcnRlZCgpIHtcbiAgY29uc3QgbWVkaWFTb3VyY2UgPSBnZXRNZWRpYVNvdXJjZSgpO1xuICBpZiAoIW1lZGlhU291cmNlKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgLy8gaWYgU291cmNlQnVmZmVyIGlzIGV4cG9zZWQgZW5zdXJlIGl0cyBBUEkgaXMgdmFsaWRcbiAgLy8gT2xkZXIgYnJvd3NlcnMgZG8gbm90IGV4cG9zZSBTb3VyY2VCdWZmZXIgZ2xvYmFsbHkgc28gY2hlY2tpbmcgU291cmNlQnVmZmVyLnByb3RvdHlwZSBpcyBpbXBvc3NpYmxlXG4gIGNvbnN0IHNvdXJjZUJ1ZmZlciA9IGdldFNvdXJjZUJ1ZmZlcigpO1xuICByZXR1cm4gIXNvdXJjZUJ1ZmZlciB8fCBzb3VyY2VCdWZmZXIucHJvdG90eXBlICYmIHR5cGVvZiBzb3VyY2VCdWZmZXIucHJvdG90eXBlLmFwcGVuZEJ1ZmZlciA9PT0gJ2Z1bmN0aW9uJyAmJiB0eXBlb2Ygc291cmNlQnVmZmVyLnByb3RvdHlwZS5yZW1vdmUgPT09ICdmdW5jdGlvbic7XG59XG5mdW5jdGlvbiBpc1N1cHBvcnRlZCgpIHtcbiAgaWYgKCFpc01TRVN1cHBvcnRlZCgpKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIGNvbnN0IG1lZGlhU291cmNlID0gZ2V0TWVkaWFTb3VyY2UoKTtcbiAgcmV0dXJuIHR5cGVvZiAobWVkaWFTb3VyY2UgPT0gbnVsbCA/IHZvaWQgMCA6IG1lZGlhU291cmNlLmlzVHlwZVN1cHBvcnRlZCkgPT09ICdmdW5jdGlvbicgJiYgKFsnYXZjMS40MkUwMUUsbXA0YS40MC4yJywgJ2F2MDEuMC4wMU0uMDgnLCAndnAwOS4wMC41MC4wOCddLnNvbWUoY29kZWNzRm9yVmlkZW9Db250YWluZXIgPT4gbWVkaWFTb3VyY2UuaXNUeXBlU3VwcG9ydGVkKG1pbWVUeXBlRm9yQ29kZWMoY29kZWNzRm9yVmlkZW9Db250YWluZXIsICd2aWRlbycpKSkgfHwgWydtcDRhLjQwLjInLCAnZkxhQyddLnNvbWUoY29kZWNGb3JBdWRpb0NvbnRhaW5lciA9PiBtZWRpYVNvdXJjZS5pc1R5cGVTdXBwb3J0ZWQobWltZVR5cGVGb3JDb2RlYyhjb2RlY0ZvckF1ZGlvQ29udGFpbmVyLCAnYXVkaW8nKSkpKTtcbn1cbmZ1bmN0aW9uIGNoYW5nZVR5cGVTdXBwb3J0ZWQoKSB7XG4gIHZhciBfc291cmNlQnVmZmVyJHByb3RvdHk7XG4gIGNvbnN0IHNvdXJjZUJ1ZmZlciA9IGdldFNvdXJjZUJ1ZmZlcigpO1xuICByZXR1cm4gdHlwZW9mIChzb3VyY2VCdWZmZXIgPT0gbnVsbCA/IHZvaWQgMCA6IChfc291cmNlQnVmZmVyJHByb3RvdHkgPSBzb3VyY2VCdWZmZXIucHJvdG90eXBlKSA9PSBudWxsID8gdm9pZCAwIDogX3NvdXJjZUJ1ZmZlciRwcm90b3R5LmNoYW5nZVR5cGUpID09PSAnZnVuY3Rpb24nO1xufVxuXG5jb25zdCBTVEFMTF9NSU5JTVVNX0RVUkFUSU9OX01TID0gMjUwO1xuY29uc3QgTUFYX1NUQVJUX0dBUF9KVU1QID0gMi4wO1xuY29uc3QgU0tJUF9CVUZGRVJfSE9MRV9TVEVQX1NFQ09ORFMgPSAwLjE7XG5jb25zdCBTS0lQX0JVRkZFUl9SQU5HRV9TVEFSVCA9IDAuMDU7XG5jbGFzcyBHYXBDb250cm9sbGVyIHtcbiAgY29uc3RydWN0b3IoY29uZmlnLCBtZWRpYSwgZnJhZ21lbnRUcmFja2VyLCBobHMpIHtcbiAgICB0aGlzLmNvbmZpZyA9IHZvaWQgMDtcbiAgICB0aGlzLm1lZGlhID0gbnVsbDtcbiAgICB0aGlzLmZyYWdtZW50VHJhY2tlciA9IHZvaWQgMDtcbiAgICB0aGlzLmhscyA9IHZvaWQgMDtcbiAgICB0aGlzLm51ZGdlUmV0cnkgPSAwO1xuICAgIHRoaXMuc3RhbGxSZXBvcnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuc3RhbGxlZCA9IG51bGw7XG4gICAgdGhpcy5tb3ZlZCA9IGZhbHNlO1xuICAgIHRoaXMuc2Vla2luZyA9IGZhbHNlO1xuICAgIHRoaXMuY29uZmlnID0gY29uZmlnO1xuICAgIHRoaXMubWVkaWEgPSBtZWRpYTtcbiAgICB0aGlzLmZyYWdtZW50VHJhY2tlciA9IGZyYWdtZW50VHJhY2tlcjtcbiAgICB0aGlzLmhscyA9IGhscztcbiAgfVxuICBkZXN0cm95KCkge1xuICAgIHRoaXMubWVkaWEgPSBudWxsO1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB0aGlzLmhscyA9IHRoaXMuZnJhZ21lbnRUcmFja2VyID0gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVja3MgaWYgdGhlIHBsYXloZWFkIGlzIHN0dWNrIHdpdGhpbiBhIGdhcCwgYW5kIGlmIHNvLCBhdHRlbXB0cyB0byBmcmVlIGl0LlxuICAgKiBBIGdhcCBpcyBhbiB1bmJ1ZmZlcmVkIHJhbmdlIGJldHdlZW4gdHdvIGJ1ZmZlcmVkIHJhbmdlcyAob3IgdGhlIHN0YXJ0IGFuZCB0aGUgZmlyc3QgYnVmZmVyZWQgcmFuZ2UpLlxuICAgKlxuICAgKiBAcGFyYW0gbGFzdEN1cnJlbnRUaW1lIC0gUHJldmlvdXNseSByZWFkIHBsYXloZWFkIHBvc2l0aW9uXG4gICAqL1xuICBwb2xsKGxhc3RDdXJyZW50VGltZSwgYWN0aXZlRnJhZykge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZyxcbiAgICAgIG1lZGlhLFxuICAgICAgc3RhbGxlZFxuICAgIH0gPSB0aGlzO1xuICAgIGlmIChtZWRpYSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB7XG4gICAgICBjdXJyZW50VGltZSxcbiAgICAgIHNlZWtpbmdcbiAgICB9ID0gbWVkaWE7XG4gICAgY29uc3Qgc2Vla2VkID0gdGhpcy5zZWVraW5nICYmICFzZWVraW5nO1xuICAgIGNvbnN0IGJlZ2luU2VlayA9ICF0aGlzLnNlZWtpbmcgJiYgc2Vla2luZztcbiAgICB0aGlzLnNlZWtpbmcgPSBzZWVraW5nO1xuXG4gICAgLy8gVGhlIHBsYXloZWFkIGlzIG1vdmluZywgbm8tb3BcbiAgICBpZiAoY3VycmVudFRpbWUgIT09IGxhc3RDdXJyZW50VGltZSkge1xuICAgICAgdGhpcy5tb3ZlZCA9IHRydWU7XG4gICAgICBpZiAoIXNlZWtpbmcpIHtcbiAgICAgICAgdGhpcy5udWRnZVJldHJ5ID0gMDtcbiAgICAgIH1cbiAgICAgIGlmIChzdGFsbGVkICE9PSBudWxsKSB7XG4gICAgICAgIC8vIFRoZSBwbGF5aGVhZCBpcyBub3cgbW92aW5nLCBidXQgd2FzIHByZXZpb3VzbHkgc3RhbGxlZFxuICAgICAgICBpZiAodGhpcy5zdGFsbFJlcG9ydGVkKSB7XG4gICAgICAgICAgY29uc3QgX3N0YWxsZWREdXJhdGlvbiA9IHNlbGYucGVyZm9ybWFuY2Uubm93KCkgLSBzdGFsbGVkO1xuICAgICAgICAgIGxvZ2dlci53YXJuKGBwbGF5YmFjayBub3Qgc3R1Y2sgYW55bW9yZSBAJHtjdXJyZW50VGltZX0sIGFmdGVyICR7TWF0aC5yb3VuZChfc3RhbGxlZER1cmF0aW9uKX1tc2ApO1xuICAgICAgICAgIHRoaXMuc3RhbGxSZXBvcnRlZCA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuc3RhbGxlZCA9IG51bGw7XG4gICAgICB9XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gQ2xlYXIgc3RhbGxlZCBzdGF0ZSB3aGVuIGJlZ2lubmluZyBvciBmaW5pc2hpbmcgc2Vla2luZyBzbyB0aGF0IHdlIGRvbid0IHJlcG9ydCBzdGFsbHMgY29taW5nIG91dCBvZiBhIHNlZWtcbiAgICBpZiAoYmVnaW5TZWVrIHx8IHNlZWtlZCkge1xuICAgICAgdGhpcy5zdGFsbGVkID0gbnVsbDtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBUaGUgcGxheWhlYWQgc2hvdWxkIG5vdCBiZSBtb3ZpbmdcbiAgICBpZiAobWVkaWEucGF1c2VkICYmICFzZWVraW5nIHx8IG1lZGlhLmVuZGVkIHx8IG1lZGlhLnBsYXliYWNrUmF0ZSA9PT0gMCB8fCAhQnVmZmVySGVscGVyLmdldEJ1ZmZlcmVkKG1lZGlhKS5sZW5ndGgpIHtcbiAgICAgIHRoaXMubnVkZ2VSZXRyeSA9IDA7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGJ1ZmZlckluZm8gPSBCdWZmZXJIZWxwZXIuYnVmZmVySW5mbyhtZWRpYSwgY3VycmVudFRpbWUsIDApO1xuICAgIGNvbnN0IG5leHRTdGFydCA9IGJ1ZmZlckluZm8ubmV4dFN0YXJ0IHx8IDA7XG4gICAgaWYgKHNlZWtpbmcpIHtcbiAgICAgIC8vIFdhaXRpbmcgZm9yIHNlZWtpbmcgaW4gYSBidWZmZXJlZCByYW5nZSB0byBjb21wbGV0ZVxuICAgICAgY29uc3QgaGFzRW5vdWdoQnVmZmVyID0gYnVmZmVySW5mby5sZW4gPiBNQVhfU1RBUlRfR0FQX0pVTVA7XG4gICAgICAvLyBOZXh0IGJ1ZmZlcmVkIHJhbmdlIGlzIHRvbyBmYXIgYWhlYWQgdG8ganVtcCB0byB3aGlsZSBzdGlsbCBzZWVraW5nXG4gICAgICBjb25zdCBub0J1ZmZlckdhcCA9ICFuZXh0U3RhcnQgfHwgYWN0aXZlRnJhZyAmJiBhY3RpdmVGcmFnLnN0YXJ0IDw9IGN1cnJlbnRUaW1lIHx8IG5leHRTdGFydCAtIGN1cnJlbnRUaW1lID4gTUFYX1NUQVJUX0dBUF9KVU1QICYmICF0aGlzLmZyYWdtZW50VHJhY2tlci5nZXRQYXJ0aWFsRnJhZ21lbnQoY3VycmVudFRpbWUpO1xuICAgICAgaWYgKGhhc0Vub3VnaEJ1ZmZlciB8fCBub0J1ZmZlckdhcCkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICAvLyBSZXNldCBtb3ZlZCBzdGF0ZSB3aGVuIHNlZWtpbmcgdG8gYSBwb2ludCBpbiBvciBiZWZvcmUgYSBnYXBcbiAgICAgIHRoaXMubW92ZWQgPSBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBTa2lwIHN0YXJ0IGdhcHMgaWYgd2UgaGF2ZW4ndCBwbGF5ZWQsIGJ1dCB0aGUgbGFzdCBwb2xsIGRldGVjdGVkIHRoZSBzdGFydCBvZiBhIHN0YWxsXG4gICAgLy8gVGhlIGFkZGl0aW9uIHBvbGwgZ2l2ZXMgdGhlIGJyb3dzZXIgYSBjaGFuY2UgdG8ganVtcCB0aGUgZ2FwIGZvciB1c1xuICAgIGlmICghdGhpcy5tb3ZlZCAmJiB0aGlzLnN0YWxsZWQgIT09IG51bGwpIHtcbiAgICAgIHZhciBfbGV2ZWwkZGV0YWlscztcbiAgICAgIC8vIFRoZXJlIGlzIG5vIHBsYXlhYmxlIGJ1ZmZlciAoc2Vla2VkLCB3YWl0aW5nIGZvciBidWZmZXIpXG4gICAgICBjb25zdCBpc0J1ZmZlcmVkID0gYnVmZmVySW5mby5sZW4gPiAwO1xuICAgICAgaWYgKCFpc0J1ZmZlcmVkICYmICFuZXh0U3RhcnQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgLy8gSnVtcCBzdGFydCBnYXBzIHdpdGhpbiBqdW1wIHRocmVzaG9sZFxuICAgICAgY29uc3Qgc3RhcnRKdW1wID0gTWF0aC5tYXgobmV4dFN0YXJ0LCBidWZmZXJJbmZvLnN0YXJ0IHx8IDApIC0gY3VycmVudFRpbWU7XG5cbiAgICAgIC8vIFdoZW4gam9pbmluZyBhIGxpdmUgc3RyZWFtIHdpdGggYXVkaW8gdHJhY2tzLCBhY2NvdW50IGZvciBsaXZlIHBsYXlsaXN0IHdpbmRvdyBzbGlkaW5nIGJ5IGFsbG93aW5nXG4gICAgICAvLyBhIGxhcmdlciBqdW1wIG92ZXIgc3RhcnQgZ2FwcyBjYXVzZWQgYnkgdGhlIGF1ZGlvLXN0cmVhbS1jb250cm9sbGVyIGJ1ZmZlcmluZyBhIHN0YXJ0IGZyYWdtZW50XG4gICAgICAvLyB0aGF0IGJlZ2lucyBvdmVyIDEgdGFyZ2V0IGR1cmF0aW9uIGFmdGVyIHRoZSB2aWRlbyBzdGFydCBwb3NpdGlvbi5cbiAgICAgIGNvbnN0IGxldmVsID0gdGhpcy5obHMubGV2ZWxzID8gdGhpcy5obHMubGV2ZWxzW3RoaXMuaGxzLmN1cnJlbnRMZXZlbF0gOiBudWxsO1xuICAgICAgY29uc3QgaXNMaXZlID0gbGV2ZWwgPT0gbnVsbCA/IHZvaWQgMCA6IChfbGV2ZWwkZGV0YWlscyA9IGxldmVsLmRldGFpbHMpID09IG51bGwgPyB2b2lkIDAgOiBfbGV2ZWwkZGV0YWlscy5saXZlO1xuICAgICAgY29uc3QgbWF4U3RhcnRHYXBKdW1wID0gaXNMaXZlID8gbGV2ZWwuZGV0YWlscy50YXJnZXRkdXJhdGlvbiAqIDIgOiBNQVhfU1RBUlRfR0FQX0pVTVA7XG4gICAgICBjb25zdCBwYXJ0aWFsT3JHYXAgPSB0aGlzLmZyYWdtZW50VHJhY2tlci5nZXRQYXJ0aWFsRnJhZ21lbnQoY3VycmVudFRpbWUpO1xuICAgICAgaWYgKHN0YXJ0SnVtcCA+IDAgJiYgKHN0YXJ0SnVtcCA8PSBtYXhTdGFydEdhcEp1bXAgfHwgcGFydGlhbE9yR2FwKSkge1xuICAgICAgICBpZiAoIW1lZGlhLnBhdXNlZCkge1xuICAgICAgICAgIHRoaXMuX3RyeVNraXBCdWZmZXJIb2xlKHBhcnRpYWxPckdhcCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFN0YXJ0IHRyYWNraW5nIHN0YWxsIHRpbWVcbiAgICBjb25zdCB0bm93ID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICBpZiAoc3RhbGxlZCA9PT0gbnVsbCkge1xuICAgICAgdGhpcy5zdGFsbGVkID0gdG5vdztcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qgc3RhbGxlZER1cmF0aW9uID0gdG5vdyAtIHN0YWxsZWQ7XG4gICAgaWYgKCFzZWVraW5nICYmIHN0YWxsZWREdXJhdGlvbiA+PSBTVEFMTF9NSU5JTVVNX0RVUkFUSU9OX01TKSB7XG4gICAgICAvLyBSZXBvcnQgc3RhbGxpbmcgYWZ0ZXIgdHJ5aW5nIHRvIGZpeFxuICAgICAgdGhpcy5fcmVwb3J0U3RhbGwoYnVmZmVySW5mbyk7XG4gICAgICBpZiAoIXRoaXMubWVkaWEpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cbiAgICBjb25zdCBidWZmZXJlZFdpdGhIb2xlcyA9IEJ1ZmZlckhlbHBlci5idWZmZXJJbmZvKG1lZGlhLCBjdXJyZW50VGltZSwgY29uZmlnLm1heEJ1ZmZlckhvbGUpO1xuICAgIHRoaXMuX3RyeUZpeEJ1ZmZlclN0YWxsKGJ1ZmZlcmVkV2l0aEhvbGVzLCBzdGFsbGVkRHVyYXRpb24pO1xuICB9XG5cbiAgLyoqXG4gICAqIERldGVjdHMgYW5kIGF0dGVtcHRzIHRvIGZpeCBrbm93biBidWZmZXIgc3RhbGxpbmcgaXNzdWVzLlxuICAgKiBAcGFyYW0gYnVmZmVySW5mbyAtIFRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBjdXJyZW50IGJ1ZmZlci5cbiAgICogQHBhcmFtIHN0YWxsZWREdXJhdGlvbk1zIC0gVGhlIGFtb3VudCBvZiB0aW1lIEhscy5qcyBoYXMgYmVlbiBzdGFsbGluZyBmb3IuXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBfdHJ5Rml4QnVmZmVyU3RhbGwoYnVmZmVySW5mbywgc3RhbGxlZER1cmF0aW9uTXMpIHtcbiAgICBjb25zdCB7XG4gICAgICBjb25maWcsXG4gICAgICBmcmFnbWVudFRyYWNrZXIsXG4gICAgICBtZWRpYVxuICAgIH0gPSB0aGlzO1xuICAgIGlmIChtZWRpYSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBjdXJyZW50VGltZSA9IG1lZGlhLmN1cnJlbnRUaW1lO1xuICAgIGNvbnN0IHBhcnRpYWwgPSBmcmFnbWVudFRyYWNrZXIuZ2V0UGFydGlhbEZyYWdtZW50KGN1cnJlbnRUaW1lKTtcbiAgICBpZiAocGFydGlhbCkge1xuICAgICAgLy8gVHJ5IHRvIHNraXAgb3ZlciB0aGUgYnVmZmVyIGhvbGUgY2F1c2VkIGJ5IGEgcGFydGlhbCBmcmFnbWVudFxuICAgICAgLy8gVGhpcyBtZXRob2QgaXNuJ3QgbGltaXRlZCBieSB0aGUgc2l6ZSBvZiB0aGUgZ2FwIGJldHdlZW4gYnVmZmVyZWQgcmFuZ2VzXG4gICAgICBjb25zdCB0YXJnZXRUaW1lID0gdGhpcy5fdHJ5U2tpcEJ1ZmZlckhvbGUocGFydGlhbCk7XG4gICAgICAvLyB3ZSByZXR1cm4gaGVyZSBpbiB0aGlzIGNhc2UsIG1lYW5pbmdcbiAgICAgIC8vIHRoZSBicmFuY2ggYmVsb3cgb25seSBleGVjdXRlcyB3aGVuIHdlIGhhdmVuJ3Qgc2Vla2VkIHRvIGEgbmV3IHBvc2l0aW9uXG4gICAgICBpZiAodGFyZ2V0VGltZSB8fCAhdGhpcy5tZWRpYSkge1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gaWYgd2UgaGF2ZW4ndCBoYWQgdG8gc2tpcCBvdmVyIGEgYnVmZmVyIGhvbGUgb2YgYSBwYXJ0aWFsIGZyYWdtZW50XG4gICAgLy8gd2UgbWF5IGp1c3QgaGF2ZSB0byBcIm51ZGdlXCIgdGhlIHBsYXlsaXN0IGFzIHRoZSBicm93c2VyIGRlY29kaW5nL3JlbmRlcmluZyBlbmdpbmVcbiAgICAvLyBuZWVkcyB0byBjcm9zcyBzb21lIHNvcnQgb2YgdGhyZXNob2xkIGNvdmVyaW5nIGFsbCBzb3VyY2UtYnVmZmVycyBjb250ZW50XG4gICAgLy8gdG8gc3RhcnQgcGxheWluZyBwcm9wZXJseS5cbiAgICBpZiAoKGJ1ZmZlckluZm8ubGVuID4gY29uZmlnLm1heEJ1ZmZlckhvbGUgfHwgYnVmZmVySW5mby5uZXh0U3RhcnQgJiYgYnVmZmVySW5mby5uZXh0U3RhcnQgLSBjdXJyZW50VGltZSA8IGNvbmZpZy5tYXhCdWZmZXJIb2xlKSAmJiBzdGFsbGVkRHVyYXRpb25NcyA+IGNvbmZpZy5oaWdoQnVmZmVyV2F0Y2hkb2dQZXJpb2QgKiAxMDAwKSB7XG4gICAgICBsb2dnZXIud2FybignVHJ5aW5nIHRvIG51ZGdlIHBsYXloZWFkIG92ZXIgYnVmZmVyLWhvbGUnKTtcbiAgICAgIC8vIFRyeSB0byBudWRnZSBjdXJyZW50VGltZSBvdmVyIGEgYnVmZmVyIGhvbGUgaWYgd2UndmUgYmVlbiBzdGFsbGluZyBmb3IgdGhlIGNvbmZpZ3VyZWQgYW1vdW50IG9mIHNlY29uZHNcbiAgICAgIC8vIFdlIG9ubHkgdHJ5IHRvIGp1bXAgdGhlIGhvbGUgaWYgaXQncyB1bmRlciB0aGUgY29uZmlndXJlZCBzaXplXG4gICAgICAvLyBSZXNldCBzdGFsbGVkIHNvIHRvIHJlYXJtIHdhdGNoZG9nIHRpbWVyXG4gICAgICB0aGlzLnN0YWxsZWQgPSBudWxsO1xuICAgICAgdGhpcy5fdHJ5TnVkZ2VCdWZmZXIoKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVHJpZ2dlcnMgYSBCVUZGRVJfU1RBTExFRF9FUlJPUiBldmVudCwgYnV0IG9ubHkgb25jZSBwZXIgc3RhbGwgcGVyaW9kLlxuICAgKiBAcGFyYW0gYnVmZmVyTGVuIC0gVGhlIHBsYXloZWFkIGRpc3RhbmNlIGZyb20gdGhlIGVuZCBvZiB0aGUgY3VycmVudCBidWZmZXIgc2VnbWVudC5cbiAgICogQHByaXZhdGVcbiAgICovXG4gIF9yZXBvcnRTdGFsbChidWZmZXJJbmZvKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzLFxuICAgICAgbWVkaWEsXG4gICAgICBzdGFsbFJlcG9ydGVkXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFzdGFsbFJlcG9ydGVkICYmIG1lZGlhKSB7XG4gICAgICAvLyBSZXBvcnQgc3RhbGxlZCBlcnJvciBvbmNlXG4gICAgICB0aGlzLnN0YWxsUmVwb3J0ZWQgPSB0cnVlO1xuICAgICAgY29uc3QgZXJyb3IgPSBuZXcgRXJyb3IoYFBsYXliYWNrIHN0YWxsaW5nIGF0IEAke21lZGlhLmN1cnJlbnRUaW1lfSBkdWUgdG8gbG93IGJ1ZmZlciAoJHtKU09OLnN0cmluZ2lmeShidWZmZXJJbmZvKX0pYCk7XG4gICAgICBsb2dnZXIud2FybihlcnJvci5tZXNzYWdlKTtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwge1xuICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk1FRElBX0VSUk9SLFxuICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQlVGRkVSX1NUQUxMRURfRVJST1IsXG4gICAgICAgIGZhdGFsOiBmYWxzZSxcbiAgICAgICAgZXJyb3IsXG4gICAgICAgIGJ1ZmZlcjogYnVmZmVySW5mby5sZW5cbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBBdHRlbXB0cyB0byBmaXggYnVmZmVyIHN0YWxscyBieSBqdW1waW5nIG92ZXIga25vd24gZ2FwcyBjYXVzZWQgYnkgcGFydGlhbCBmcmFnbWVudHNcbiAgICogQHBhcmFtIHBhcnRpYWwgLSBUaGUgcGFydGlhbCBmcmFnbWVudCBmb3VuZCBhdCB0aGUgY3VycmVudCB0aW1lICh3aGVyZSBwbGF5YmFjayBpcyBzdGFsbGluZykuXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBfdHJ5U2tpcEJ1ZmZlckhvbGUocGFydGlhbCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZyxcbiAgICAgIGhscyxcbiAgICAgIG1lZGlhXG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKG1lZGlhID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiBjdXJyZW50VGltZSBpcyBiZXR3ZWVuIHVuYnVmZmVyZWQgcmVnaW9ucyBvZiBwYXJ0aWFsIGZyYWdtZW50c1xuICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gbWVkaWEuY3VycmVudFRpbWU7XG4gICAgY29uc3QgYnVmZmVySW5mbyA9IEJ1ZmZlckhlbHBlci5idWZmZXJJbmZvKG1lZGlhLCBjdXJyZW50VGltZSwgMCk7XG4gICAgY29uc3Qgc3RhcnRUaW1lID0gY3VycmVudFRpbWUgPCBidWZmZXJJbmZvLnN0YXJ0ID8gYnVmZmVySW5mby5zdGFydCA6IGJ1ZmZlckluZm8ubmV4dFN0YXJ0O1xuICAgIGlmIChzdGFydFRpbWUpIHtcbiAgICAgIGNvbnN0IGJ1ZmZlclN0YXJ2ZWQgPSBidWZmZXJJbmZvLmxlbiA8PSBjb25maWcubWF4QnVmZmVySG9sZTtcbiAgICAgIGNvbnN0IHdhaXRpbmcgPSBidWZmZXJJbmZvLmxlbiA+IDAgJiYgYnVmZmVySW5mby5sZW4gPCAxICYmIG1lZGlhLnJlYWR5U3RhdGUgPCAzO1xuICAgICAgY29uc3QgZ2FwTGVuZ3RoID0gc3RhcnRUaW1lIC0gY3VycmVudFRpbWU7XG4gICAgICBpZiAoZ2FwTGVuZ3RoID4gMCAmJiAoYnVmZmVyU3RhcnZlZCB8fCB3YWl0aW5nKSkge1xuICAgICAgICAvLyBPbmx5IGFsbG93IGxhcmdlIGdhcHMgdG8gYmUgc2tpcHBlZCBpZiBpdCBpcyBhIHN0YXJ0IGdhcCwgb3IgYWxsIGZyYWdtZW50cyBpbiBza2lwIHJhbmdlIGFyZSBwYXJ0aWFsXG4gICAgICAgIGlmIChnYXBMZW5ndGggPiBjb25maWcubWF4QnVmZmVySG9sZSkge1xuICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgIGZyYWdtZW50VHJhY2tlclxuICAgICAgICAgIH0gPSB0aGlzO1xuICAgICAgICAgIGxldCBzdGFydEdhcCA9IGZhbHNlO1xuICAgICAgICAgIGlmIChjdXJyZW50VGltZSA9PT0gMCkge1xuICAgICAgICAgICAgY29uc3Qgc3RhcnRGcmFnID0gZnJhZ21lbnRUcmFja2VyLmdldEFwcGVuZGVkRnJhZygwLCBQbGF5bGlzdExldmVsVHlwZS5NQUlOKTtcbiAgICAgICAgICAgIGlmIChzdGFydEZyYWcgJiYgc3RhcnRUaW1lIDwgc3RhcnRGcmFnLmVuZCkge1xuICAgICAgICAgICAgICBzdGFydEdhcCA9IHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmICghc3RhcnRHYXApIHtcbiAgICAgICAgICAgIGNvbnN0IHN0YXJ0UHJvdmlzaW9uZWQgPSBwYXJ0aWFsIHx8IGZyYWdtZW50VHJhY2tlci5nZXRBcHBlbmRlZEZyYWcoY3VycmVudFRpbWUsIFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4pO1xuICAgICAgICAgICAgaWYgKHN0YXJ0UHJvdmlzaW9uZWQpIHtcbiAgICAgICAgICAgICAgbGV0IG1vcmVUb0xvYWQgPSBmYWxzZTtcbiAgICAgICAgICAgICAgbGV0IHBvcyA9IHN0YXJ0UHJvdmlzaW9uZWQuZW5kO1xuICAgICAgICAgICAgICB3aGlsZSAocG9zIDwgc3RhcnRUaW1lKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcHJvdmlzaW9uZWQgPSBmcmFnbWVudFRyYWNrZXIuZ2V0UGFydGlhbEZyYWdtZW50KHBvcyk7XG4gICAgICAgICAgICAgICAgaWYgKHByb3Zpc2lvbmVkKSB7XG4gICAgICAgICAgICAgICAgICBwb3MgKz0gcHJvdmlzaW9uZWQuZHVyYXRpb247XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIG1vcmVUb0xvYWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChtb3JlVG9Mb2FkKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIDA7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgdGFyZ2V0VGltZSA9IE1hdGgubWF4KHN0YXJ0VGltZSArIFNLSVBfQlVGRkVSX1JBTkdFX1NUQVJULCBjdXJyZW50VGltZSArIFNLSVBfQlVGRkVSX0hPTEVfU1RFUF9TRUNPTkRTKTtcbiAgICAgICAgbG9nZ2VyLndhcm4oYHNraXBwaW5nIGhvbGUsIGFkanVzdGluZyBjdXJyZW50VGltZSBmcm9tICR7Y3VycmVudFRpbWV9IHRvICR7dGFyZ2V0VGltZX1gKTtcbiAgICAgICAgdGhpcy5tb3ZlZCA9IHRydWU7XG4gICAgICAgIHRoaXMuc3RhbGxlZCA9IG51bGw7XG4gICAgICAgIG1lZGlhLmN1cnJlbnRUaW1lID0gdGFyZ2V0VGltZTtcbiAgICAgICAgaWYgKHBhcnRpYWwgJiYgIXBhcnRpYWwuZ2FwKSB7XG4gICAgICAgICAgY29uc3QgZXJyb3IgPSBuZXcgRXJyb3IoYGZyYWdtZW50IGxvYWRlZCB3aXRoIGJ1ZmZlciBob2xlcywgc2Vla2luZyBmcm9tICR7Y3VycmVudFRpbWV9IHRvICR7dGFyZ2V0VGltZX1gKTtcbiAgICAgICAgICBobHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgICAgIHR5cGU6IEVycm9yVHlwZXMuTUVESUFfRVJST1IsXG4gICAgICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQlVGRkVSX1NFRUtfT1ZFUl9IT0xFLFxuICAgICAgICAgICAgZmF0YWw6IGZhbHNlLFxuICAgICAgICAgICAgZXJyb3IsXG4gICAgICAgICAgICByZWFzb246IGVycm9yLm1lc3NhZ2UsXG4gICAgICAgICAgICBmcmFnOiBwYXJ0aWFsXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRhcmdldFRpbWU7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiAwO1xuICB9XG5cbiAgLyoqXG4gICAqIEF0dGVtcHRzIHRvIGZpeCBidWZmZXIgc3RhbGxzIGJ5IGFkdmFuY2luZyB0aGUgbWVkaWFFbGVtZW50J3MgY3VycmVudCB0aW1lIGJ5IGEgc21hbGwgYW1vdW50LlxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgX3RyeU51ZGdlQnVmZmVyKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGNvbmZpZyxcbiAgICAgIGhscyxcbiAgICAgIG1lZGlhLFxuICAgICAgbnVkZ2VSZXRyeVxuICAgIH0gPSB0aGlzO1xuICAgIGlmIChtZWRpYSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBjdXJyZW50VGltZSA9IG1lZGlhLmN1cnJlbnRUaW1lO1xuICAgIHRoaXMubnVkZ2VSZXRyeSsrO1xuICAgIGlmIChudWRnZVJldHJ5IDwgY29uZmlnLm51ZGdlTWF4UmV0cnkpIHtcbiAgICAgIGNvbnN0IHRhcmdldFRpbWUgPSBjdXJyZW50VGltZSArIChudWRnZVJldHJ5ICsgMSkgKiBjb25maWcubnVkZ2VPZmZzZXQ7XG4gICAgICAvLyBwbGF5YmFjayBzdGFsbGVkIGluIGJ1ZmZlcmVkIGFyZWEgLi4uIGxldCdzIG51ZGdlIGN1cnJlbnRUaW1lIHRvIHRyeSB0byBvdmVyY29tZSB0aGlzXG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcihgTnVkZ2luZyAnY3VycmVudFRpbWUnIGZyb20gJHtjdXJyZW50VGltZX0gdG8gJHt0YXJnZXRUaW1lfWApO1xuICAgICAgbG9nZ2VyLndhcm4oZXJyb3IubWVzc2FnZSk7XG4gICAgICBtZWRpYS5jdXJyZW50VGltZSA9IHRhcmdldFRpbWU7XG4gICAgICBobHMudHJpZ2dlcihFdmVudHMuRVJST1IsIHtcbiAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5NRURJQV9FUlJPUixcbiAgICAgICAgZGV0YWlsczogRXJyb3JEZXRhaWxzLkJVRkZFUl9OVURHRV9PTl9TVEFMTCxcbiAgICAgICAgZXJyb3IsXG4gICAgICAgIGZhdGFsOiBmYWxzZVxuICAgICAgfSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbnN0IGVycm9yID0gbmV3IEVycm9yKGBQbGF5aGVhZCBzdGlsbCBub3QgbW92aW5nIHdoaWxlIGVub3VnaCBkYXRhIGJ1ZmZlcmVkIEAke2N1cnJlbnRUaW1lfSBhZnRlciAke2NvbmZpZy5udWRnZU1heFJldHJ5fSBudWRnZXNgKTtcbiAgICAgIGxvZ2dlci5lcnJvcihlcnJvci5tZXNzYWdlKTtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwge1xuICAgICAgICB0eXBlOiBFcnJvclR5cGVzLk1FRElBX0VSUk9SLFxuICAgICAgICBkZXRhaWxzOiBFcnJvckRldGFpbHMuQlVGRkVSX1NUQUxMRURfRVJST1IsXG4gICAgICAgIGVycm9yLFxuICAgICAgICBmYXRhbDogdHJ1ZVxuICAgICAgfSk7XG4gICAgfVxuICB9XG59XG5cbmNvbnN0IFRJQ0tfSU5URVJWQUwgPSAxMDA7IC8vIGhvdyBvZnRlbiB0byB0aWNrIGluIG1zXG5cbmNsYXNzIFN0cmVhbUNvbnRyb2xsZXIgZXh0ZW5kcyBCYXNlU3RyZWFtQ29udHJvbGxlciB7XG4gIGNvbnN0cnVjdG9yKGhscywgZnJhZ21lbnRUcmFja2VyLCBrZXlMb2FkZXIpIHtcbiAgICBzdXBlcihobHMsIGZyYWdtZW50VHJhY2tlciwga2V5TG9hZGVyLCAnW3N0cmVhbS1jb250cm9sbGVyXScsIFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4pO1xuICAgIHRoaXMuYXVkaW9Db2RlY1N3YXAgPSBmYWxzZTtcbiAgICB0aGlzLmdhcENvbnRyb2xsZXIgPSBudWxsO1xuICAgIHRoaXMubGV2ZWwgPSAtMTtcbiAgICB0aGlzLl9mb3JjZVN0YXJ0TG9hZCA9IGZhbHNlO1xuICAgIHRoaXMuYWx0QXVkaW8gPSBmYWxzZTtcbiAgICB0aGlzLmF1ZGlvT25seSA9IGZhbHNlO1xuICAgIHRoaXMuZnJhZ1BsYXlpbmcgPSBudWxsO1xuICAgIHRoaXMub252cGxheWluZyA9IG51bGw7XG4gICAgdGhpcy5vbnZzZWVrZWQgPSBudWxsO1xuICAgIHRoaXMuZnJhZ0xhc3RLYnBzID0gMDtcbiAgICB0aGlzLmNvdWxkQmFja3RyYWNrID0gZmFsc2U7XG4gICAgdGhpcy5iYWNrdHJhY2tGcmFnbWVudCA9IG51bGw7XG4gICAgdGhpcy5hdWRpb0NvZGVjU3dpdGNoID0gZmFsc2U7XG4gICAgdGhpcy52aWRlb0J1ZmZlciA9IG51bGw7XG4gICAgdGhpcy5fcmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgfVxuICBfcmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9uKEV2ZW50cy5NRURJQV9BVFRBQ0hFRCwgdGhpcy5vbk1lZGlhQXR0YWNoZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUVESUFfREVUQUNISU5HLCB0aGlzLm9uTWVkaWFEZXRhY2hpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5NQU5JRkVTVF9QQVJTRUQsIHRoaXMub25NYW5pZmVzdFBhcnNlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTF9MT0FESU5HLCB0aGlzLm9uTGV2ZWxMb2FkaW5nLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkxFVkVMX0xPQURFRCwgdGhpcy5vbkxldmVsTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkZSQUdfTE9BRF9FTUVSR0VOQ1lfQUJPUlRFRCwgdGhpcy5vbkZyYWdMb2FkRW1lcmdlbmN5QWJvcnRlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5FUlJPUiwgdGhpcy5vbkVycm9yLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkFVRElPX1RSQUNLX1NXSVRDSElORywgdGhpcy5vbkF1ZGlvVHJhY2tTd2l0Y2hpbmcsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQVVESU9fVFJBQ0tfU1dJVENIRUQsIHRoaXMub25BdWRpb1RyYWNrU3dpdGNoZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuQlVGRkVSX0NSRUFURUQsIHRoaXMub25CdWZmZXJDcmVhdGVkLCB0aGlzKTtcbiAgICBobHMub24oRXZlbnRzLkJVRkZFUl9GTFVTSEVELCB0aGlzLm9uQnVmZmVyRmx1c2hlZCwgdGhpcyk7XG4gICAgaGxzLm9uKEV2ZW50cy5MRVZFTFNfVVBEQVRFRCwgdGhpcy5vbkxldmVsc1VwZGF0ZWQsIHRoaXMpO1xuICAgIGhscy5vbihFdmVudHMuRlJBR19CVUZGRVJFRCwgdGhpcy5vbkZyYWdCdWZmZXJlZCwgdGhpcyk7XG4gIH1cbiAgX3VucmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzXG4gICAgfSA9IHRoaXM7XG4gICAgaGxzLm9mZihFdmVudHMuTUVESUFfQVRUQUNIRUQsIHRoaXMub25NZWRpYUF0dGFjaGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHRoaXMub25NZWRpYURldGFjaGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfTE9BRElORywgdGhpcy5vbk1hbmlmZXN0TG9hZGluZywgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTUFOSUZFU1RfUEFSU0VELCB0aGlzLm9uTWFuaWZlc3RQYXJzZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkxFVkVMX0xPQURFRCwgdGhpcy5vbkxldmVsTG9hZGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0xPQURfRU1FUkdFTkNZX0FCT1JURUQsIHRoaXMub25GcmFnTG9hZEVtZXJnZW5jeUFib3J0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkVSUk9SLCB0aGlzLm9uRXJyb3IsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkFVRElPX1RSQUNLX1NXSVRDSElORywgdGhpcy5vbkF1ZGlvVHJhY2tTd2l0Y2hpbmcsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkFVRElPX1RSQUNLX1NXSVRDSEVELCB0aGlzLm9uQXVkaW9UcmFja1N3aXRjaGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5CVUZGRVJfQ1JFQVRFRCwgdGhpcy5vbkJ1ZmZlckNyZWF0ZWQsIHRoaXMpO1xuICAgIGhscy5vZmYoRXZlbnRzLkJVRkZFUl9GTFVTSEVELCB0aGlzLm9uQnVmZmVyRmx1c2hlZCwgdGhpcyk7XG4gICAgaGxzLm9mZihFdmVudHMuTEVWRUxTX1VQREFURUQsIHRoaXMub25MZXZlbHNVcGRhdGVkLCB0aGlzKTtcbiAgICBobHMub2ZmKEV2ZW50cy5GUkFHX0JVRkZFUkVELCB0aGlzLm9uRnJhZ0J1ZmZlcmVkLCB0aGlzKTtcbiAgfVxuICBvbkhhbmRsZXJEZXN0cm95aW5nKCkge1xuICAgIHRoaXMuX3VucmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICBzdXBlci5vbkhhbmRsZXJEZXN0cm95aW5nKCk7XG4gIH1cbiAgc3RhcnRMb2FkKHN0YXJ0UG9zaXRpb24pIHtcbiAgICBpZiAodGhpcy5sZXZlbHMpIHtcbiAgICAgIGNvbnN0IHtcbiAgICAgICAgbGFzdEN1cnJlbnRUaW1lLFxuICAgICAgICBobHNcbiAgICAgIH0gPSB0aGlzO1xuICAgICAgdGhpcy5zdG9wTG9hZCgpO1xuICAgICAgdGhpcy5zZXRJbnRlcnZhbChUSUNLX0lOVEVSVkFMKTtcbiAgICAgIHRoaXMubGV2ZWwgPSAtMTtcbiAgICAgIGlmICghdGhpcy5zdGFydEZyYWdSZXF1ZXN0ZWQpIHtcbiAgICAgICAgLy8gZGV0ZXJtaW5lIGxvYWQgbGV2ZWxcbiAgICAgICAgbGV0IHN0YXJ0TGV2ZWwgPSBobHMuc3RhcnRMZXZlbDtcbiAgICAgICAgaWYgKHN0YXJ0TGV2ZWwgPT09IC0xKSB7XG4gICAgICAgICAgaWYgKGhscy5jb25maWcudGVzdEJhbmR3aWR0aCAmJiB0aGlzLmxldmVscy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICAvLyAtMSA6IGd1ZXNzIHN0YXJ0IExldmVsIGJ5IGRvaW5nIGEgYml0cmF0ZSB0ZXN0IGJ5IGxvYWRpbmcgZmlyc3QgZnJhZ21lbnQgb2YgbG93ZXN0IHF1YWxpdHkgbGV2ZWxcbiAgICAgICAgICAgIHN0YXJ0TGV2ZWwgPSAwO1xuICAgICAgICAgICAgdGhpcy5iaXRyYXRlVGVzdCA9IHRydWU7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHN0YXJ0TGV2ZWwgPSBobHMuZmlyc3RBdXRvTGV2ZWw7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIHNldCBuZXcgbGV2ZWwgdG8gcGxheWxpc3QgbG9hZGVyIDogdGhpcyB3aWxsIHRyaWdnZXIgc3RhcnQgbGV2ZWwgbG9hZFxuICAgICAgICAvLyBobHMubmV4dExvYWRMZXZlbCByZW1haW5zIHVudGlsIGl0IGlzIHNldCB0byBhIG5ldyB2YWx1ZSBvciB1bnRpbCBhIG5ldyBmcmFnIGlzIHN1Y2Nlc3NmdWxseSBsb2FkZWRcbiAgICAgICAgaGxzLm5leHRMb2FkTGV2ZWwgPSBzdGFydExldmVsO1xuICAgICAgICB0aGlzLmxldmVsID0gaGxzLmxvYWRMZXZlbDtcbiAgICAgICAgdGhpcy5sb2FkZWRtZXRhZGF0YSA9IGZhbHNlO1xuICAgICAgfVxuICAgICAgLy8gaWYgc3RhcnRQb3NpdGlvbiB1bmRlZmluZWQgYnV0IGxhc3RDdXJyZW50VGltZSBzZXQsIHNldCBzdGFydFBvc2l0aW9uIHRvIGxhc3QgY3VycmVudFRpbWVcbiAgICAgIGlmIChsYXN0Q3VycmVudFRpbWUgPiAwICYmIHN0YXJ0UG9zaXRpb24gPT09IC0xKSB7XG4gICAgICAgIHRoaXMubG9nKGBPdmVycmlkZSBzdGFydFBvc2l0aW9uIHdpdGggbGFzdEN1cnJlbnRUaW1lIEAke2xhc3RDdXJyZW50VGltZS50b0ZpeGVkKDMpfWApO1xuICAgICAgICBzdGFydFBvc2l0aW9uID0gbGFzdEN1cnJlbnRUaW1lO1xuICAgICAgfVxuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgICB0aGlzLm5leHRMb2FkUG9zaXRpb24gPSB0aGlzLnN0YXJ0UG9zaXRpb24gPSB0aGlzLmxhc3RDdXJyZW50VGltZSA9IHN0YXJ0UG9zaXRpb247XG4gICAgICB0aGlzLnRpY2soKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5fZm9yY2VTdGFydExvYWQgPSB0cnVlO1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLlNUT1BQRUQ7XG4gICAgfVxuICB9XG4gIHN0b3BMb2FkKCkge1xuICAgIHRoaXMuX2ZvcmNlU3RhcnRMb2FkID0gZmFsc2U7XG4gICAgc3VwZXIuc3RvcExvYWQoKTtcbiAgfVxuICBkb1RpY2soKSB7XG4gICAgc3dpdGNoICh0aGlzLnN0YXRlKSB7XG4gICAgICBjYXNlIFN0YXRlLldBSVRJTkdfTEVWRUw6XG4gICAgICAgIHtcbiAgICAgICAgICBjb25zdCB7XG4gICAgICAgICAgICBsZXZlbHMsXG4gICAgICAgICAgICBsZXZlbFxuICAgICAgICAgIH0gPSB0aGlzO1xuICAgICAgICAgIGNvbnN0IGN1cnJlbnRMZXZlbCA9IGxldmVscyA9PSBudWxsID8gdm9pZCAwIDogbGV2ZWxzW2xldmVsXTtcbiAgICAgICAgICBjb25zdCBkZXRhaWxzID0gY3VycmVudExldmVsID09IG51bGwgPyB2b2lkIDAgOiBjdXJyZW50TGV2ZWwuZGV0YWlscztcbiAgICAgICAgICBpZiAoZGV0YWlscyAmJiAoIWRldGFpbHMubGl2ZSB8fCB0aGlzLmxldmVsTGFzdExvYWRlZCA9PT0gY3VycmVudExldmVsKSkge1xuICAgICAgICAgICAgaWYgKHRoaXMud2FpdEZvckNkblR1bmVJbihkZXRhaWxzKSkge1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgfSBlbHNlIGlmICh0aGlzLmhscy5uZXh0TG9hZExldmVsICE9PSB0aGlzLmxldmVsKSB7XG4gICAgICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgY2FzZSBTdGF0ZS5GUkFHX0xPQURJTkdfV0FJVElOR19SRVRSWTpcbiAgICAgICAge1xuICAgICAgICAgIHZhciBfdGhpcyRtZWRpYTtcbiAgICAgICAgICBjb25zdCBub3cgPSBzZWxmLnBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICAgIGNvbnN0IHJldHJ5RGF0ZSA9IHRoaXMucmV0cnlEYXRlO1xuICAgICAgICAgIC8vIGlmIGN1cnJlbnQgdGltZSBpcyBndCB0aGFuIHJldHJ5RGF0ZSwgb3IgaWYgbWVkaWEgc2Vla2luZyBsZXQncyBzd2l0Y2ggdG8gSURMRSBzdGF0ZSB0byByZXRyeSBsb2FkaW5nXG4gICAgICAgICAgaWYgKCFyZXRyeURhdGUgfHwgbm93ID49IHJldHJ5RGF0ZSB8fCAoX3RoaXMkbWVkaWEgPSB0aGlzLm1lZGlhKSAhPSBudWxsICYmIF90aGlzJG1lZGlhLnNlZWtpbmcpIHtcbiAgICAgICAgICAgIGNvbnN0IHtcbiAgICAgICAgICAgICAgbGV2ZWxzLFxuICAgICAgICAgICAgICBsZXZlbFxuICAgICAgICAgICAgfSA9IHRoaXM7XG4gICAgICAgICAgICBjb25zdCBjdXJyZW50TGV2ZWwgPSBsZXZlbHMgPT0gbnVsbCA/IHZvaWQgMCA6IGxldmVsc1tsZXZlbF07XG4gICAgICAgICAgICB0aGlzLnJlc2V0U3RhcnRXaGVuTm90TG9hZGVkKGN1cnJlbnRMZXZlbCB8fCBudWxsKTtcbiAgICAgICAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICB9XG4gICAgaWYgKHRoaXMuc3RhdGUgPT09IFN0YXRlLklETEUpIHtcbiAgICAgIHRoaXMuZG9UaWNrSWRsZSgpO1xuICAgIH1cbiAgICB0aGlzLm9uVGlja0VuZCgpO1xuICB9XG4gIG9uVGlja0VuZCgpIHtcbiAgICBzdXBlci5vblRpY2tFbmQoKTtcbiAgICB0aGlzLmNoZWNrQnVmZmVyKCk7XG4gICAgdGhpcy5jaGVja0ZyYWdtZW50Q2hhbmdlZCgpO1xuICB9XG4gIGRvVGlja0lkbGUoKSB7XG4gICAgY29uc3Qge1xuICAgICAgaGxzLFxuICAgICAgbGV2ZWxMYXN0TG9hZGVkLFxuICAgICAgbGV2ZWxzLFxuICAgICAgbWVkaWFcbiAgICB9ID0gdGhpcztcblxuICAgIC8vIGlmIHN0YXJ0IGxldmVsIG5vdCBwYXJzZWQgeWV0IE9SXG4gICAgLy8gaWYgdmlkZW8gbm90IGF0dGFjaGVkIEFORCBzdGFydCBmcmFnbWVudCBhbHJlYWR5IHJlcXVlc3RlZCBPUiBzdGFydCBmcmFnIHByZWZldGNoIG5vdCBlbmFibGVkXG4gICAgLy8gZXhpdCBsb29wLCBhcyB3ZSBlaXRoZXIgbmVlZCBtb3JlIGluZm8gKGxldmVsIG5vdCBwYXJzZWQpIG9yIHdlIG5lZWQgbWVkaWEgdG8gYmUgYXR0YWNoZWQgdG8gbG9hZCBuZXcgZnJhZ21lbnRcbiAgICBpZiAobGV2ZWxMYXN0TG9hZGVkID09PSBudWxsIHx8ICFtZWRpYSAmJiAodGhpcy5zdGFydEZyYWdSZXF1ZXN0ZWQgfHwgIWhscy5jb25maWcuc3RhcnRGcmFnUHJlZmV0Y2gpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gSWYgdGhlIFwibWFpblwiIGxldmVsIGlzIGF1ZGlvLW9ubHkgYnV0IHdlIGFyZSBsb2FkaW5nIGFuIGFsdGVybmF0ZSB0cmFjayBpbiB0aGUgc2FtZSBncm91cCwgZG8gbm90IGxvYWQgYW55dGhpbmdcbiAgICBpZiAodGhpcy5hbHRBdWRpbyAmJiB0aGlzLmF1ZGlvT25seSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBsZXZlbCA9IGhscy5uZXh0TG9hZExldmVsO1xuICAgIGlmICghKGxldmVscyAhPSBudWxsICYmIGxldmVsc1tsZXZlbF0pKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGxldmVsSW5mbyA9IGxldmVsc1tsZXZlbF07XG5cbiAgICAvLyBpZiBidWZmZXIgbGVuZ3RoIGlzIGxlc3MgdGhhbiBtYXhCdWZMZW4gdHJ5IHRvIGxvYWQgYSBuZXcgZnJhZ21lbnRcblxuICAgIGNvbnN0IGJ1ZmZlckluZm8gPSB0aGlzLmdldE1haW5Gd2RCdWZmZXJJbmZvKCk7XG4gICAgaWYgKGJ1ZmZlckluZm8gPT09IG51bGwpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgbGFzdERldGFpbHMgPSB0aGlzLmdldExldmVsRGV0YWlscygpO1xuICAgIGlmIChsYXN0RGV0YWlscyAmJiB0aGlzLl9zdHJlYW1FbmRlZChidWZmZXJJbmZvLCBsYXN0RGV0YWlscykpIHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7fTtcbiAgICAgIGlmICh0aGlzLmFsdEF1ZGlvKSB7XG4gICAgICAgIGRhdGEudHlwZSA9ICd2aWRlbyc7XG4gICAgICB9XG4gICAgICB0aGlzLmhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfRU9TLCBkYXRhKTtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5FTkRFRDtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBzZXQgbmV4dCBsb2FkIGxldmVsIDogdGhpcyB3aWxsIHRyaWdnZXIgYSBwbGF5bGlzdCBsb2FkIGlmIG5lZWRlZFxuICAgIGlmIChobHMubG9hZExldmVsICE9PSBsZXZlbCAmJiBobHMubWFudWFsTGV2ZWwgPT09IC0xKSB7XG4gICAgICB0aGlzLmxvZyhgQWRhcHRpbmcgdG8gbGV2ZWwgJHtsZXZlbH0gZnJvbSBsZXZlbCAke3RoaXMubGV2ZWx9YCk7XG4gICAgfVxuICAgIHRoaXMubGV2ZWwgPSBobHMubmV4dExvYWRMZXZlbCA9IGxldmVsO1xuICAgIGNvbnN0IGxldmVsRGV0YWlscyA9IGxldmVsSW5mby5kZXRhaWxzO1xuICAgIC8vIGlmIGxldmVsIGluZm8gbm90IHJldHJpZXZlZCB5ZXQsIHN3aXRjaCBzdGF0ZSBhbmQgd2FpdCBmb3IgbGV2ZWwgcmV0cmlldmFsXG4gICAgLy8gaWYgbGl2ZSBwbGF5bGlzdCwgZW5zdXJlIHRoYXQgbmV3IHBsYXlsaXN0IGhhcyBiZWVuIHJlZnJlc2hlZCB0byBhdm9pZCBsb2FkaW5nL3RyeSB0byBsb2FkXG4gICAgLy8gYSB1c2VsZXNzIGFuZCBvdXRkYXRlZCBmcmFnbWVudCAodGhhdCBtaWdodCBldmVuIGludHJvZHVjZSBsb2FkIGVycm9yIGlmIGl0IGlzIGFscmVhZHkgb3V0IG9mIHRoZSBsaXZlIHBsYXlsaXN0KVxuICAgIGlmICghbGV2ZWxEZXRhaWxzIHx8IHRoaXMuc3RhdGUgPT09IFN0YXRlLldBSVRJTkdfTEVWRUwgfHwgbGV2ZWxEZXRhaWxzLmxpdmUgJiYgdGhpcy5sZXZlbExhc3RMb2FkZWQgIT09IGxldmVsSW5mbykge1xuICAgICAgdGhpcy5sZXZlbCA9IGxldmVsO1xuICAgICAgdGhpcy5zdGF0ZSA9IFN0YXRlLldBSVRJTkdfTEVWRUw7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGJ1ZmZlckxlbiA9IGJ1ZmZlckluZm8ubGVuO1xuXG4gICAgLy8gY29tcHV0ZSBtYXggQnVmZmVyIExlbmd0aCB0aGF0IHdlIGNvdWxkIGdldCBmcm9tIHRoaXMgbG9hZCBsZXZlbCwgYmFzZWQgb24gbGV2ZWwgYml0cmF0ZS4gZG9uJ3QgYnVmZmVyIG1vcmUgdGhhbiA2MCBNQiBhbmQgbW9yZSB0aGFuIDMwc1xuICAgIGNvbnN0IG1heEJ1ZkxlbiA9IHRoaXMuZ2V0TWF4QnVmZmVyTGVuZ3RoKGxldmVsSW5mby5tYXhCaXRyYXRlKTtcblxuICAgIC8vIFN0YXkgaWRsZSBpZiB3ZSBhcmUgc3RpbGwgd2l0aCBidWZmZXIgbWFyZ2luc1xuICAgIGlmIChidWZmZXJMZW4gPj0gbWF4QnVmTGVuKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICh0aGlzLmJhY2t0cmFja0ZyYWdtZW50ICYmIHRoaXMuYmFja3RyYWNrRnJhZ21lbnQuc3RhcnQgPiBidWZmZXJJbmZvLmVuZCkge1xuICAgICAgdGhpcy5iYWNrdHJhY2tGcmFnbWVudCA9IG51bGw7XG4gICAgfVxuICAgIGNvbnN0IHRhcmdldEJ1ZmZlclRpbWUgPSB0aGlzLmJhY2t0cmFja0ZyYWdtZW50ID8gdGhpcy5iYWNrdHJhY2tGcmFnbWVudC5zdGFydCA6IGJ1ZmZlckluZm8uZW5kO1xuICAgIGxldCBmcmFnID0gdGhpcy5nZXROZXh0RnJhZ21lbnQodGFyZ2V0QnVmZmVyVGltZSwgbGV2ZWxEZXRhaWxzKTtcbiAgICAvLyBBdm9pZCBiYWNrdHJhY2tpbmcgYnkgbG9hZGluZyBhbiBlYXJsaWVyIHNlZ21lbnQgaW4gc3RyZWFtcyB3aXRoIHNlZ21lbnRzIHRoYXQgZG8gbm90IHN0YXJ0IHdpdGggYSBrZXkgZnJhbWUgKGZsYWdnZWQgYnkgYGNvdWxkQmFja3RyYWNrYClcbiAgICBpZiAodGhpcy5jb3VsZEJhY2t0cmFjayAmJiAhdGhpcy5mcmFnUHJldmlvdXMgJiYgZnJhZyAmJiBmcmFnLnNuICE9PSAnaW5pdFNlZ21lbnQnICYmIHRoaXMuZnJhZ21lbnRUcmFja2VyLmdldFN0YXRlKGZyYWcpICE9PSBGcmFnbWVudFN0YXRlLk9LKSB7XG4gICAgICB2YXIgX3RoaXMkYmFja3RyYWNrRnJhZ21lO1xuICAgICAgY29uc3QgYmFja3RyYWNrU24gPSAoKF90aGlzJGJhY2t0cmFja0ZyYWdtZSA9IHRoaXMuYmFja3RyYWNrRnJhZ21lbnQpICE9IG51bGwgPyBfdGhpcyRiYWNrdHJhY2tGcmFnbWUgOiBmcmFnKS5zbjtcbiAgICAgIGNvbnN0IGZyYWdJZHggPSBiYWNrdHJhY2tTbiAtIGxldmVsRGV0YWlscy5zdGFydFNOO1xuICAgICAgY29uc3QgYmFja3RyYWNrRnJhZyA9IGxldmVsRGV0YWlscy5mcmFnbWVudHNbZnJhZ0lkeCAtIDFdO1xuICAgICAgaWYgKGJhY2t0cmFja0ZyYWcgJiYgZnJhZy5jYyA9PT0gYmFja3RyYWNrRnJhZy5jYykge1xuICAgICAgICBmcmFnID0gYmFja3RyYWNrRnJhZztcbiAgICAgICAgdGhpcy5mcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQoYmFja3RyYWNrRnJhZyk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmICh0aGlzLmJhY2t0cmFja0ZyYWdtZW50ICYmIGJ1ZmZlckluZm8ubGVuKSB7XG4gICAgICB0aGlzLmJhY2t0cmFja0ZyYWdtZW50ID0gbnVsbDtcbiAgICB9XG4gICAgLy8gQXZvaWQgbG9vcCBsb2FkaW5nIGJ5IHVzaW5nIG5leHRMb2FkUG9zaXRpb24gc2V0IGZvciBiYWNrdHJhY2tpbmcgYW5kIHNraXBwaW5nIGNvbnNlY3V0aXZlIEdBUCB0YWdzXG4gICAgaWYgKGZyYWcgJiYgdGhpcy5pc0xvb3BMb2FkaW5nKGZyYWcsIHRhcmdldEJ1ZmZlclRpbWUpKSB7XG4gICAgICBjb25zdCBnYXBTdGFydCA9IGZyYWcuZ2FwO1xuICAgICAgaWYgKCFnYXBTdGFydCkge1xuICAgICAgICAvLyBDbGVhbnVwIHRoZSBmcmFnbWVudCB0cmFja2VyIGJlZm9yZSB0cnlpbmcgdG8gZmluZCB0aGUgbmV4dCB1bmJ1ZmZlcmVkIGZyYWdtZW50XG4gICAgICAgIGNvbnN0IHR5cGUgPSB0aGlzLmF1ZGlvT25seSAmJiAhdGhpcy5hbHRBdWRpbyA/IEVsZW1lbnRhcnlTdHJlYW1UeXBlcy5BVURJTyA6IEVsZW1lbnRhcnlTdHJlYW1UeXBlcy5WSURFTztcbiAgICAgICAgY29uc3QgbWVkaWFCdWZmZXIgPSAodHlwZSA9PT0gRWxlbWVudGFyeVN0cmVhbVR5cGVzLlZJREVPID8gdGhpcy52aWRlb0J1ZmZlciA6IHRoaXMubWVkaWFCdWZmZXIpIHx8IHRoaXMubWVkaWE7XG4gICAgICAgIGlmIChtZWRpYUJ1ZmZlcikge1xuICAgICAgICAgIHRoaXMuYWZ0ZXJCdWZmZXJGbHVzaGVkKG1lZGlhQnVmZmVyLCB0eXBlLCBQbGF5bGlzdExldmVsVHlwZS5NQUlOKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZnJhZyA9IHRoaXMuZ2V0TmV4dEZyYWdtZW50TG9vcExvYWRpbmcoZnJhZywgbGV2ZWxEZXRhaWxzLCBidWZmZXJJbmZvLCBQbGF5bGlzdExldmVsVHlwZS5NQUlOLCBtYXhCdWZMZW4pO1xuICAgIH1cbiAgICBpZiAoIWZyYWcpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGZyYWcuaW5pdFNlZ21lbnQgJiYgIWZyYWcuaW5pdFNlZ21lbnQuZGF0YSAmJiAhdGhpcy5iaXRyYXRlVGVzdCkge1xuICAgICAgZnJhZyA9IGZyYWcuaW5pdFNlZ21lbnQ7XG4gICAgfVxuICAgIHRoaXMubG9hZEZyYWdtZW50KGZyYWcsIGxldmVsSW5mbywgdGFyZ2V0QnVmZmVyVGltZSk7XG4gIH1cbiAgbG9hZEZyYWdtZW50KGZyYWcsIGxldmVsLCB0YXJnZXRCdWZmZXJUaW1lKSB7XG4gICAgLy8gQ2hlY2sgaWYgZnJhZ21lbnQgaXMgbm90IGxvYWRlZFxuICAgIGNvbnN0IGZyYWdTdGF0ZSA9IHRoaXMuZnJhZ21lbnRUcmFja2VyLmdldFN0YXRlKGZyYWcpO1xuICAgIHRoaXMuZnJhZ0N1cnJlbnQgPSBmcmFnO1xuICAgIGlmIChmcmFnU3RhdGUgPT09IEZyYWdtZW50U3RhdGUuTk9UX0xPQURFRCB8fCBmcmFnU3RhdGUgPT09IEZyYWdtZW50U3RhdGUuUEFSVElBTCkge1xuICAgICAgaWYgKGZyYWcuc24gPT09ICdpbml0U2VnbWVudCcpIHtcbiAgICAgICAgdGhpcy5fbG9hZEluaXRTZWdtZW50KGZyYWcsIGxldmVsKTtcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5iaXRyYXRlVGVzdCkge1xuICAgICAgICB0aGlzLmxvZyhgRnJhZ21lbnQgJHtmcmFnLnNufSBvZiBsZXZlbCAke2ZyYWcubGV2ZWx9IGlzIGJlaW5nIGRvd25sb2FkZWQgdG8gdGVzdCBiaXRyYXRlIGFuZCB3aWxsIG5vdCBiZSBidWZmZXJlZGApO1xuICAgICAgICB0aGlzLl9sb2FkQml0cmF0ZVRlc3RGcmFnKGZyYWcsIGxldmVsKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkID0gdHJ1ZTtcbiAgICAgICAgc3VwZXIubG9hZEZyYWdtZW50KGZyYWcsIGxldmVsLCB0YXJnZXRCdWZmZXJUaW1lKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5jbGVhclRyYWNrZXJJZk5lZWRlZChmcmFnKTtcbiAgICB9XG4gIH1cbiAgZ2V0QnVmZmVyZWRGcmFnKHBvc2l0aW9uKSB7XG4gICAgcmV0dXJuIHRoaXMuZnJhZ21lbnRUcmFja2VyLmdldEJ1ZmZlcmVkRnJhZyhwb3NpdGlvbiwgUGxheWxpc3RMZXZlbFR5cGUuTUFJTik7XG4gIH1cbiAgZm9sbG93aW5nQnVmZmVyZWRGcmFnKGZyYWcpIHtcbiAgICBpZiAoZnJhZykge1xuICAgICAgLy8gdHJ5IHRvIGdldCByYW5nZSBvZiBuZXh0IGZyYWdtZW50ICg1MDBtcyBhZnRlciB0aGlzIHJhbmdlKVxuICAgICAgcmV0dXJuIHRoaXMuZ2V0QnVmZmVyZWRGcmFnKGZyYWcuZW5kICsgMC41KTtcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvKlxuICAgIG9uIGltbWVkaWF0ZSBsZXZlbCBzd2l0Y2ggOlxuICAgICAtIHBhdXNlIHBsYXliYWNrIGlmIHBsYXlpbmdcbiAgICAgLSBjYW5jZWwgYW55IHBlbmRpbmcgbG9hZCByZXF1ZXN0XG4gICAgIC0gYW5kIHRyaWdnZXIgYSBidWZmZXIgZmx1c2hcbiAgKi9cbiAgaW1tZWRpYXRlTGV2ZWxTd2l0Y2goKSB7XG4gICAgdGhpcy5hYm9ydEN1cnJlbnRGcmFnKCk7XG4gICAgdGhpcy5mbHVzaE1haW5CdWZmZXIoMCwgTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZKTtcbiAgfVxuXG4gIC8qKlxuICAgKiB0cnkgdG8gc3dpdGNoIEFTQVAgd2l0aG91dCBicmVha2luZyB2aWRlbyBwbGF5YmFjazpcbiAgICogaW4gb3JkZXIgdG8gZW5zdXJlIHNtb290aCBidXQgcXVpY2sgbGV2ZWwgc3dpdGNoaW5nLFxuICAgKiB3ZSBuZWVkIHRvIGZpbmQgdGhlIG5leHQgZmx1c2hhYmxlIGJ1ZmZlciByYW5nZVxuICAgKiB3ZSBzaG91bGQgdGFrZSBpbnRvIGFjY291bnQgbmV3IHNlZ21lbnQgZmV0Y2ggdGltZVxuICAgKi9cbiAgbmV4dExldmVsU3dpdGNoKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGxldmVscyxcbiAgICAgIG1lZGlhXG4gICAgfSA9IHRoaXM7XG4gICAgLy8gZW5zdXJlIHRoYXQgbWVkaWEgaXMgZGVmaW5lZCBhbmQgdGhhdCBtZXRhZGF0YSBhcmUgYXZhaWxhYmxlICh0byByZXRyaWV2ZSBjdXJyZW50VGltZSlcbiAgICBpZiAobWVkaWEgIT0gbnVsbCAmJiBtZWRpYS5yZWFkeVN0YXRlKSB7XG4gICAgICBsZXQgZmV0Y2hkZWxheTtcbiAgICAgIGNvbnN0IGZyYWdQbGF5aW5nQ3VycmVudCA9IHRoaXMuZ2V0QXBwZW5kZWRGcmFnKG1lZGlhLmN1cnJlbnRUaW1lKTtcbiAgICAgIGlmIChmcmFnUGxheWluZ0N1cnJlbnQgJiYgZnJhZ1BsYXlpbmdDdXJyZW50LnN0YXJ0ID4gMSkge1xuICAgICAgICAvLyBmbHVzaCBidWZmZXIgcHJlY2VkaW5nIGN1cnJlbnQgZnJhZ21lbnQgKGZsdXNoIHVudGlsIGN1cnJlbnQgZnJhZ21lbnQgc3RhcnQgb2Zmc2V0KVxuICAgICAgICAvLyBtaW51cyAxcyB0byBhdm9pZCB2aWRlbyBmcmVlemluZywgdGhhdCBjb3VsZCBoYXBwZW4gaWYgd2UgZmx1c2gga2V5ZnJhbWUgb2YgY3VycmVudCB2aWRlbyAuLi5cbiAgICAgICAgdGhpcy5mbHVzaE1haW5CdWZmZXIoMCwgZnJhZ1BsYXlpbmdDdXJyZW50LnN0YXJ0IC0gMSk7XG4gICAgICB9XG4gICAgICBjb25zdCBsZXZlbERldGFpbHMgPSB0aGlzLmdldExldmVsRGV0YWlscygpO1xuICAgICAgaWYgKGxldmVsRGV0YWlscyAhPSBudWxsICYmIGxldmVsRGV0YWlscy5saXZlKSB7XG4gICAgICAgIGNvbnN0IGJ1ZmZlckluZm8gPSB0aGlzLmdldE1haW5Gd2RCdWZmZXJJbmZvKCk7XG4gICAgICAgIC8vIERvIG5vdCBmbHVzaCBpbiBsaXZlIHN0cmVhbSB3aXRoIGxvdyBidWZmZXJcbiAgICAgICAgaWYgKCFidWZmZXJJbmZvIHx8IGJ1ZmZlckluZm8ubGVuIDwgbGV2ZWxEZXRhaWxzLnRhcmdldGR1cmF0aW9uICogMikge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKCFtZWRpYS5wYXVzZWQgJiYgbGV2ZWxzKSB7XG4gICAgICAgIC8vIGFkZCBhIHNhZmV0eSBkZWxheSBvZiAxc1xuICAgICAgICBjb25zdCBuZXh0TGV2ZWxJZCA9IHRoaXMuaGxzLm5leHRMb2FkTGV2ZWw7XG4gICAgICAgIGNvbnN0IG5leHRMZXZlbCA9IGxldmVsc1tuZXh0TGV2ZWxJZF07XG4gICAgICAgIGNvbnN0IGZyYWdMYXN0S2JwcyA9IHRoaXMuZnJhZ0xhc3RLYnBzO1xuICAgICAgICBpZiAoZnJhZ0xhc3RLYnBzICYmIHRoaXMuZnJhZ0N1cnJlbnQpIHtcbiAgICAgICAgICBmZXRjaGRlbGF5ID0gdGhpcy5mcmFnQ3VycmVudC5kdXJhdGlvbiAqIG5leHRMZXZlbC5tYXhCaXRyYXRlIC8gKDEwMDAgKiBmcmFnTGFzdEticHMpICsgMTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBmZXRjaGRlbGF5ID0gMDtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZmV0Y2hkZWxheSA9IDA7XG4gICAgICB9XG4gICAgICAvLyB0aGlzLmxvZygnZmV0Y2hkZWxheTonK2ZldGNoZGVsYXkpO1xuICAgICAgLy8gZmluZCBidWZmZXIgcmFuZ2UgdGhhdCB3aWxsIGJlIHJlYWNoZWQgb25jZSBuZXcgZnJhZ21lbnQgd2lsbCBiZSBmZXRjaGVkXG4gICAgICBjb25zdCBidWZmZXJlZEZyYWcgPSB0aGlzLmdldEJ1ZmZlcmVkRnJhZyhtZWRpYS5jdXJyZW50VGltZSArIGZldGNoZGVsYXkpO1xuICAgICAgaWYgKGJ1ZmZlcmVkRnJhZykge1xuICAgICAgICAvLyB3ZSBjYW4gZmx1c2ggYnVmZmVyIHJhbmdlIGZvbGxvd2luZyB0aGlzIG9uZSB3aXRob3V0IHN0YWxsaW5nIHBsYXliYWNrXG4gICAgICAgIGNvbnN0IG5leHRCdWZmZXJlZEZyYWcgPSB0aGlzLmZvbGxvd2luZ0J1ZmZlcmVkRnJhZyhidWZmZXJlZEZyYWcpO1xuICAgICAgICBpZiAobmV4dEJ1ZmZlcmVkRnJhZykge1xuICAgICAgICAgIC8vIGlmIHdlIGFyZSBoZXJlLCB3ZSBjYW4gYWxzbyBjYW5jZWwgYW55IGxvYWRpbmcvZGVtdXhpbmcgaW4gcHJvZ3Jlc3MsIGFzIHRoZXkgYXJlIHVzZWxlc3NcbiAgICAgICAgICB0aGlzLmFib3J0Q3VycmVudEZyYWcoKTtcbiAgICAgICAgICAvLyBzdGFydCBmbHVzaCBwb3NpdGlvbiBpcyBpbiBuZXh0IGJ1ZmZlcmVkIGZyYWcuIExlYXZlIHNvbWUgcGFkZGluZyBmb3Igbm9uLWluZGVwZW5kZW50IHNlZ21lbnRzIGFuZCBzbW9vdGhlciBwbGF5YmFjay5cbiAgICAgICAgICBjb25zdCBtYXhTdGFydCA9IG5leHRCdWZmZXJlZEZyYWcubWF4U3RhcnRQVFMgPyBuZXh0QnVmZmVyZWRGcmFnLm1heFN0YXJ0UFRTIDogbmV4dEJ1ZmZlcmVkRnJhZy5zdGFydDtcbiAgICAgICAgICBjb25zdCBmcmFnRHVyYXRpb24gPSBuZXh0QnVmZmVyZWRGcmFnLmR1cmF0aW9uO1xuICAgICAgICAgIGNvbnN0IHN0YXJ0UHRzID0gTWF0aC5tYXgoYnVmZmVyZWRGcmFnLmVuZCwgbWF4U3RhcnQgKyBNYXRoLm1pbihNYXRoLm1heChmcmFnRHVyYXRpb24gLSB0aGlzLmNvbmZpZy5tYXhGcmFnTG9va1VwVG9sZXJhbmNlLCBmcmFnRHVyYXRpb24gKiAodGhpcy5jb3VsZEJhY2t0cmFjayA/IDAuNSA6IDAuMTI1KSksIGZyYWdEdXJhdGlvbiAqICh0aGlzLmNvdWxkQmFja3RyYWNrID8gMC43NSA6IDAuMjUpKSk7XG4gICAgICAgICAgdGhpcy5mbHVzaE1haW5CdWZmZXIoc3RhcnRQdHMsIE51bWJlci5QT1NJVElWRV9JTkZJTklUWSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgYWJvcnRDdXJyZW50RnJhZygpIHtcbiAgICBjb25zdCBmcmFnQ3VycmVudCA9IHRoaXMuZnJhZ0N1cnJlbnQ7XG4gICAgdGhpcy5mcmFnQ3VycmVudCA9IG51bGw7XG4gICAgdGhpcy5iYWNrdHJhY2tGcmFnbWVudCA9IG51bGw7XG4gICAgaWYgKGZyYWdDdXJyZW50KSB7XG4gICAgICBmcmFnQ3VycmVudC5hYm9ydFJlcXVlc3RzKCk7XG4gICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5yZW1vdmVGcmFnbWVudChmcmFnQ3VycmVudCk7XG4gICAgfVxuICAgIHN3aXRjaCAodGhpcy5zdGF0ZSkge1xuICAgICAgY2FzZSBTdGF0ZS5LRVlfTE9BRElORzpcbiAgICAgIGNhc2UgU3RhdGUuRlJBR19MT0FESU5HOlxuICAgICAgY2FzZSBTdGF0ZS5GUkFHX0xPQURJTkdfV0FJVElOR19SRVRSWTpcbiAgICAgIGNhc2UgU3RhdGUuUEFSU0lORzpcbiAgICAgIGNhc2UgU3RhdGUuUEFSU0VEOlxuICAgICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgICAgYnJlYWs7XG4gICAgfVxuICAgIHRoaXMubmV4dExvYWRQb3NpdGlvbiA9IHRoaXMuZ2V0TG9hZFBvc2l0aW9uKCk7XG4gIH1cbiAgZmx1c2hNYWluQnVmZmVyKHN0YXJ0T2Zmc2V0LCBlbmRPZmZzZXQpIHtcbiAgICBzdXBlci5mbHVzaE1haW5CdWZmZXIoc3RhcnRPZmZzZXQsIGVuZE9mZnNldCwgdGhpcy5hbHRBdWRpbyA/ICd2aWRlbycgOiBudWxsKTtcbiAgfVxuICBvbk1lZGlhQXR0YWNoZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBzdXBlci5vbk1lZGlhQXR0YWNoZWQoZXZlbnQsIGRhdGEpO1xuICAgIGNvbnN0IG1lZGlhID0gZGF0YS5tZWRpYTtcbiAgICB0aGlzLm9udnBsYXlpbmcgPSB0aGlzLm9uTWVkaWFQbGF5aW5nLmJpbmQodGhpcyk7XG4gICAgdGhpcy5vbnZzZWVrZWQgPSB0aGlzLm9uTWVkaWFTZWVrZWQuYmluZCh0aGlzKTtcbiAgICBtZWRpYS5hZGRFdmVudExpc3RlbmVyKCdwbGF5aW5nJywgdGhpcy5vbnZwbGF5aW5nKTtcbiAgICBtZWRpYS5hZGRFdmVudExpc3RlbmVyKCdzZWVrZWQnLCB0aGlzLm9udnNlZWtlZCk7XG4gICAgdGhpcy5nYXBDb250cm9sbGVyID0gbmV3IEdhcENvbnRyb2xsZXIodGhpcy5jb25maWcsIG1lZGlhLCB0aGlzLmZyYWdtZW50VHJhY2tlciwgdGhpcy5obHMpO1xuICB9XG4gIG9uTWVkaWFEZXRhY2hpbmcoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbWVkaWFcbiAgICB9ID0gdGhpcztcbiAgICBpZiAobWVkaWEgJiYgdGhpcy5vbnZwbGF5aW5nICYmIHRoaXMub252c2Vla2VkKSB7XG4gICAgICBtZWRpYS5yZW1vdmVFdmVudExpc3RlbmVyKCdwbGF5aW5nJywgdGhpcy5vbnZwbGF5aW5nKTtcbiAgICAgIG1lZGlhLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3NlZWtlZCcsIHRoaXMub252c2Vla2VkKTtcbiAgICAgIHRoaXMub252cGxheWluZyA9IHRoaXMub252c2Vla2VkID0gbnVsbDtcbiAgICAgIHRoaXMudmlkZW9CdWZmZXIgPSBudWxsO1xuICAgIH1cbiAgICB0aGlzLmZyYWdQbGF5aW5nID0gbnVsbDtcbiAgICBpZiAodGhpcy5nYXBDb250cm9sbGVyKSB7XG4gICAgICB0aGlzLmdhcENvbnRyb2xsZXIuZGVzdHJveSgpO1xuICAgICAgdGhpcy5nYXBDb250cm9sbGVyID0gbnVsbDtcbiAgICB9XG4gICAgc3VwZXIub25NZWRpYURldGFjaGluZygpO1xuICB9XG4gIG9uTWVkaWFQbGF5aW5nKCkge1xuICAgIC8vIHRpY2sgdG8gc3BlZWQgdXAgRlJBR19DSEFOR0VEIHRyaWdnZXJpbmdcbiAgICB0aGlzLnRpY2soKTtcbiAgfVxuICBvbk1lZGlhU2Vla2VkKCkge1xuICAgIGNvbnN0IG1lZGlhID0gdGhpcy5tZWRpYTtcbiAgICBjb25zdCBjdXJyZW50VGltZSA9IG1lZGlhID8gbWVkaWEuY3VycmVudFRpbWUgOiBudWxsO1xuICAgIGlmIChpc0Zpbml0ZU51bWJlcihjdXJyZW50VGltZSkpIHtcbiAgICAgIHRoaXMubG9nKGBNZWRpYSBzZWVrZWQgdG8gJHtjdXJyZW50VGltZS50b0ZpeGVkKDMpfWApO1xuICAgIH1cblxuICAgIC8vIElmIHNlZWtlZCB3YXMgaXNzdWVkIGJlZm9yZSBidWZmZXIgd2FzIGFwcGVuZGVkIGRvIG5vdCB0aWNrIGltbWVkaWF0ZWx5XG4gICAgY29uc3QgYnVmZmVySW5mbyA9IHRoaXMuZ2V0TWFpbkZ3ZEJ1ZmZlckluZm8oKTtcbiAgICBpZiAoYnVmZmVySW5mbyA9PT0gbnVsbCB8fCBidWZmZXJJbmZvLmxlbiA9PT0gMCkge1xuICAgICAgdGhpcy53YXJuKGBNYWluIGZvcndhcmQgYnVmZmVyIGxlbmd0aCBvbiBcInNlZWtlZFwiIGV2ZW50ICR7YnVmZmVySW5mbyA/IGJ1ZmZlckluZm8ubGVuIDogJ2VtcHR5J30pYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gdGljayB0byBzcGVlZCB1cCBGUkFHX0NIQU5HRUQgdHJpZ2dlcmluZ1xuICAgIHRoaXMudGljaygpO1xuICB9XG4gIG9uTWFuaWZlc3RMb2FkaW5nKCkge1xuICAgIC8vIHJlc2V0IGJ1ZmZlciBvbiBtYW5pZmVzdCBsb2FkaW5nXG4gICAgdGhpcy5sb2coJ1RyaWdnZXIgQlVGRkVSX1JFU0VUJyk7XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuQlVGRkVSX1JFU0VULCB1bmRlZmluZWQpO1xuICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUFsbEZyYWdtZW50cygpO1xuICAgIHRoaXMuY291bGRCYWNrdHJhY2sgPSBmYWxzZTtcbiAgICB0aGlzLnN0YXJ0UG9zaXRpb24gPSB0aGlzLmxhc3RDdXJyZW50VGltZSA9IHRoaXMuZnJhZ0xhc3RLYnBzID0gMDtcbiAgICB0aGlzLmxldmVscyA9IHRoaXMuZnJhZ1BsYXlpbmcgPSB0aGlzLmJhY2t0cmFja0ZyYWdtZW50ID0gdGhpcy5sZXZlbExhc3RMb2FkZWQgPSBudWxsO1xuICAgIHRoaXMuYWx0QXVkaW8gPSB0aGlzLmF1ZGlvT25seSA9IHRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkID0gZmFsc2U7XG4gIH1cbiAgb25NYW5pZmVzdFBhcnNlZChldmVudCwgZGF0YSkge1xuICAgIC8vIGRldGVjdCBpZiB3ZSBoYXZlIGRpZmZlcmVudCBraW5kIG9mIGF1ZGlvIGNvZGVjcyB1c2VkIGFtb25nc3QgcGxheWxpc3RzXG4gICAgbGV0IGFhYyA9IGZhbHNlO1xuICAgIGxldCBoZWFhYyA9IGZhbHNlO1xuICAgIGRhdGEubGV2ZWxzLmZvckVhY2gobGV2ZWwgPT4ge1xuICAgICAgY29uc3QgY29kZWMgPSBsZXZlbC5hdWRpb0NvZGVjO1xuICAgICAgaWYgKGNvZGVjKSB7XG4gICAgICAgIGFhYyA9IGFhYyB8fCBjb2RlYy5pbmRleE9mKCdtcDRhLjQwLjInKSAhPT0gLTE7XG4gICAgICAgIGhlYWFjID0gaGVhYWMgfHwgY29kZWMuaW5kZXhPZignbXA0YS40MC41JykgIT09IC0xO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHRoaXMuYXVkaW9Db2RlY1N3aXRjaCA9IGFhYyAmJiBoZWFhYyAmJiAhY2hhbmdlVHlwZVN1cHBvcnRlZCgpO1xuICAgIGlmICh0aGlzLmF1ZGlvQ29kZWNTd2l0Y2gpIHtcbiAgICAgIHRoaXMubG9nKCdCb3RoIEFBQy9IRS1BQUMgYXVkaW8gZm91bmQgaW4gbGV2ZWxzOyBkZWNsYXJpbmcgbGV2ZWwgY29kZWMgYXMgSEUtQUFDJyk7XG4gICAgfVxuICAgIHRoaXMubGV2ZWxzID0gZGF0YS5sZXZlbHM7XG4gICAgdGhpcy5zdGFydEZyYWdSZXF1ZXN0ZWQgPSBmYWxzZTtcbiAgfVxuICBvbkxldmVsTG9hZGluZyhldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHtcbiAgICAgIGxldmVsc1xuICAgIH0gPSB0aGlzO1xuICAgIGlmICghbGV2ZWxzIHx8IHRoaXMuc3RhdGUgIT09IFN0YXRlLklETEUpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgbGV2ZWwgPSBsZXZlbHNbZGF0YS5sZXZlbF07XG4gICAgaWYgKCFsZXZlbC5kZXRhaWxzIHx8IGxldmVsLmRldGFpbHMubGl2ZSAmJiB0aGlzLmxldmVsTGFzdExvYWRlZCAhPT0gbGV2ZWwgfHwgdGhpcy53YWl0Rm9yQ2RuVHVuZUluKGxldmVsLmRldGFpbHMpKSB7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuV0FJVElOR19MRVZFTDtcbiAgICB9XG4gIH1cbiAgb25MZXZlbExvYWRlZChldmVudCwgZGF0YSkge1xuICAgIHZhciBfY3VyTGV2ZWwkZGV0YWlscztcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbHNcbiAgICB9ID0gdGhpcztcbiAgICBjb25zdCBuZXdMZXZlbElkID0gZGF0YS5sZXZlbDtcbiAgICBjb25zdCBuZXdEZXRhaWxzID0gZGF0YS5kZXRhaWxzO1xuICAgIGNvbnN0IGR1cmF0aW9uID0gbmV3RGV0YWlscy50b3RhbGR1cmF0aW9uO1xuICAgIGlmICghbGV2ZWxzKSB7XG4gICAgICB0aGlzLndhcm4oYExldmVscyB3ZXJlIHJlc2V0IHdoaWxlIGxvYWRpbmcgbGV2ZWwgJHtuZXdMZXZlbElkfWApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmxvZyhgTGV2ZWwgJHtuZXdMZXZlbElkfSBsb2FkZWQgWyR7bmV3RGV0YWlscy5zdGFydFNOfSwke25ld0RldGFpbHMuZW5kU059XSR7bmV3RGV0YWlscy5sYXN0UGFydFNuID8gYFtwYXJ0LSR7bmV3RGV0YWlscy5sYXN0UGFydFNufS0ke25ld0RldGFpbHMubGFzdFBhcnRJbmRleH1dYCA6ICcnfSwgY2MgWyR7bmV3RGV0YWlscy5zdGFydENDfSwgJHtuZXdEZXRhaWxzLmVuZENDfV0gZHVyYXRpb246JHtkdXJhdGlvbn1gKTtcbiAgICBjb25zdCBjdXJMZXZlbCA9IGxldmVsc1tuZXdMZXZlbElkXTtcbiAgICBjb25zdCBmcmFnQ3VycmVudCA9IHRoaXMuZnJhZ0N1cnJlbnQ7XG4gICAgaWYgKGZyYWdDdXJyZW50ICYmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5GUkFHX0xPQURJTkcgfHwgdGhpcy5zdGF0ZSA9PT0gU3RhdGUuRlJBR19MT0FESU5HX1dBSVRJTkdfUkVUUlkpKSB7XG4gICAgICBpZiAoZnJhZ0N1cnJlbnQubGV2ZWwgIT09IGRhdGEubGV2ZWwgJiYgZnJhZ0N1cnJlbnQubG9hZGVyKSB7XG4gICAgICAgIHRoaXMuYWJvcnRDdXJyZW50RnJhZygpO1xuICAgICAgfVxuICAgIH1cbiAgICBsZXQgc2xpZGluZyA9IDA7XG4gICAgaWYgKG5ld0RldGFpbHMubGl2ZSB8fCAoX2N1ckxldmVsJGRldGFpbHMgPSBjdXJMZXZlbC5kZXRhaWxzKSAhPSBudWxsICYmIF9jdXJMZXZlbCRkZXRhaWxzLmxpdmUpIHtcbiAgICAgIHZhciBfdGhpcyRsZXZlbExhc3RMb2FkZWQ7XG4gICAgICB0aGlzLmNoZWNrTGl2ZVVwZGF0ZShuZXdEZXRhaWxzKTtcbiAgICAgIGlmIChuZXdEZXRhaWxzLmRlbHRhVXBkYXRlRmFpbGVkKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHNsaWRpbmcgPSB0aGlzLmFsaWduUGxheWxpc3RzKG5ld0RldGFpbHMsIGN1ckxldmVsLmRldGFpbHMsIChfdGhpcyRsZXZlbExhc3RMb2FkZWQgPSB0aGlzLmxldmVsTGFzdExvYWRlZCkgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJGxldmVsTGFzdExvYWRlZC5kZXRhaWxzKTtcbiAgICB9XG4gICAgLy8gb3ZlcnJpZGUgbGV2ZWwgaW5mb1xuICAgIGN1ckxldmVsLmRldGFpbHMgPSBuZXdEZXRhaWxzO1xuICAgIHRoaXMubGV2ZWxMYXN0TG9hZGVkID0gY3VyTGV2ZWw7XG4gICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuTEVWRUxfVVBEQVRFRCwge1xuICAgICAgZGV0YWlsczogbmV3RGV0YWlscyxcbiAgICAgIGxldmVsOiBuZXdMZXZlbElkXG4gICAgfSk7XG5cbiAgICAvLyBvbmx5IHN3aXRjaCBiYWNrIHRvIElETEUgc3RhdGUgaWYgd2Ugd2VyZSB3YWl0aW5nIGZvciBsZXZlbCB0byBzdGFydCBkb3dubG9hZGluZyBhIG5ldyBmcmFnbWVudFxuICAgIGlmICh0aGlzLnN0YXRlID09PSBTdGF0ZS5XQUlUSU5HX0xFVkVMKSB7XG4gICAgICBpZiAodGhpcy53YWl0Rm9yQ2RuVHVuZUluKG5ld0RldGFpbHMpKSB7XG4gICAgICAgIC8vIFdhaXQgZm9yIExvdy1MYXRlbmN5IENETiBUdW5lLWluXG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgIH1cbiAgICBpZiAoIXRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkKSB7XG4gICAgICB0aGlzLnNldFN0YXJ0UG9zaXRpb24obmV3RGV0YWlscywgc2xpZGluZyk7XG4gICAgfSBlbHNlIGlmIChuZXdEZXRhaWxzLmxpdmUpIHtcbiAgICAgIHRoaXMuc3luY2hyb25pemVUb0xpdmVFZGdlKG5ld0RldGFpbHMpO1xuICAgIH1cblxuICAgIC8vIHRyaWdnZXIgaGFuZGxlciByaWdodCBub3dcbiAgICB0aGlzLnRpY2soKTtcbiAgfVxuICBfaGFuZGxlRnJhZ21lbnRMb2FkUHJvZ3Jlc3MoZGF0YSkge1xuICAgIHZhciBfZnJhZyRpbml0U2VnbWVudDtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydCxcbiAgICAgIHBheWxvYWRcbiAgICB9ID0gZGF0YTtcbiAgICBjb25zdCB7XG4gICAgICBsZXZlbHNcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIWxldmVscykge1xuICAgICAgdGhpcy53YXJuKGBMZXZlbHMgd2VyZSByZXNldCB3aGlsZSBmcmFnbWVudCBsb2FkIHdhcyBpbiBwcm9ncmVzcy4gRnJhZ21lbnQgJHtmcmFnLnNufSBvZiBsZXZlbCAke2ZyYWcubGV2ZWx9IHdpbGwgbm90IGJlIGJ1ZmZlcmVkYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGN1cnJlbnRMZXZlbCA9IGxldmVsc1tmcmFnLmxldmVsXTtcbiAgICBjb25zdCBkZXRhaWxzID0gY3VycmVudExldmVsLmRldGFpbHM7XG4gICAgaWYgKCFkZXRhaWxzKSB7XG4gICAgICB0aGlzLndhcm4oYERyb3BwaW5nIGZyYWdtZW50ICR7ZnJhZy5zbn0gb2YgbGV2ZWwgJHtmcmFnLmxldmVsfSBhZnRlciBsZXZlbCBkZXRhaWxzIHdlcmUgcmVzZXRgKTtcbiAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50KGZyYWcpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB2aWRlb0NvZGVjID0gY3VycmVudExldmVsLnZpZGVvQ29kZWM7XG5cbiAgICAvLyB0aW1lIE9mZnNldCBpcyBhY2N1cmF0ZSBpZiBsZXZlbCBQVFMgaXMga25vd24sIG9yIGlmIHBsYXlsaXN0IGlzIG5vdCBzbGlkaW5nIChub3QgbGl2ZSlcbiAgICBjb25zdCBhY2N1cmF0ZVRpbWVPZmZzZXQgPSBkZXRhaWxzLlBUU0tub3duIHx8ICFkZXRhaWxzLmxpdmU7XG4gICAgY29uc3QgaW5pdFNlZ21lbnREYXRhID0gKF9mcmFnJGluaXRTZWdtZW50ID0gZnJhZy5pbml0U2VnbWVudCkgPT0gbnVsbCA/IHZvaWQgMCA6IF9mcmFnJGluaXRTZWdtZW50LmRhdGE7XG4gICAgY29uc3QgYXVkaW9Db2RlYyA9IHRoaXMuX2dldEF1ZGlvQ29kZWMoY3VycmVudExldmVsKTtcblxuICAgIC8vIHRyYW5zbXV4IHRoZSBNUEVHLVRTIGRhdGEgdG8gSVNPLUJNRkYgc2VnbWVudHNcbiAgICAvLyB0aGlzLmxvZyhgVHJhbnNtdXhpbmcgJHtmcmFnLnNufSBvZiBbJHtkZXRhaWxzLnN0YXJ0U059ICwke2RldGFpbHMuZW5kU059XSxsZXZlbCAke2ZyYWcubGV2ZWx9LCBjYyAke2ZyYWcuY2N9YCk7XG4gICAgY29uc3QgdHJhbnNtdXhlciA9IHRoaXMudHJhbnNtdXhlciA9IHRoaXMudHJhbnNtdXhlciB8fCBuZXcgVHJhbnNtdXhlckludGVyZmFjZSh0aGlzLmhscywgUGxheWxpc3RMZXZlbFR5cGUuTUFJTiwgdGhpcy5faGFuZGxlVHJhbnNtdXhDb21wbGV0ZS5iaW5kKHRoaXMpLCB0aGlzLl9oYW5kbGVUcmFuc211eGVyRmx1c2guYmluZCh0aGlzKSk7XG4gICAgY29uc3QgcGFydEluZGV4ID0gcGFydCA/IHBhcnQuaW5kZXggOiAtMTtcbiAgICBjb25zdCBwYXJ0aWFsID0gcGFydEluZGV4ICE9PSAtMTtcbiAgICBjb25zdCBjaHVua01ldGEgPSBuZXcgQ2h1bmtNZXRhZGF0YShmcmFnLmxldmVsLCBmcmFnLnNuLCBmcmFnLnN0YXRzLmNodW5rQ291bnQsIHBheWxvYWQuYnl0ZUxlbmd0aCwgcGFydEluZGV4LCBwYXJ0aWFsKTtcbiAgICBjb25zdCBpbml0UFRTID0gdGhpcy5pbml0UFRTW2ZyYWcuY2NdO1xuICAgIHRyYW5zbXV4ZXIucHVzaChwYXlsb2FkLCBpbml0U2VnbWVudERhdGEsIGF1ZGlvQ29kZWMsIHZpZGVvQ29kZWMsIGZyYWcsIHBhcnQsIGRldGFpbHMudG90YWxkdXJhdGlvbiwgYWNjdXJhdGVUaW1lT2Zmc2V0LCBjaHVua01ldGEsIGluaXRQVFMpO1xuICB9XG4gIG9uQXVkaW9UcmFja1N3aXRjaGluZyhldmVudCwgZGF0YSkge1xuICAgIC8vIGlmIGFueSBVUkwgZm91bmQgb24gbmV3IGF1ZGlvIHRyYWNrLCBpdCBpcyBhbiBhbHRlcm5hdGUgYXVkaW8gdHJhY2tcbiAgICBjb25zdCBmcm9tQWx0QXVkaW8gPSB0aGlzLmFsdEF1ZGlvO1xuICAgIGNvbnN0IGFsdEF1ZGlvID0gISFkYXRhLnVybDtcbiAgICAvLyBpZiB3ZSBzd2l0Y2ggb24gbWFpbiBhdWRpbywgZW5zdXJlIHRoYXQgbWFpbiBmcmFnbWVudCBzY2hlZHVsaW5nIGlzIHN5bmNlZCB3aXRoIG1lZGlhLmJ1ZmZlcmVkXG4gICAgLy8gZG9uJ3QgZG8gYW55dGhpbmcgaWYgd2Ugc3dpdGNoIHRvIGFsdCBhdWRpbzogYXVkaW8gc3RyZWFtIGNvbnRyb2xsZXIgaXMgaGFuZGxpbmcgaXQuXG4gICAgLy8gd2Ugd2lsbCBqdXN0IGhhdmUgdG8gY2hhbmdlIGJ1ZmZlciBzY2hlZHVsaW5nIG9uIGF1ZGlvVHJhY2tTd2l0Y2hlZFxuICAgIGlmICghYWx0QXVkaW8pIHtcbiAgICAgIGlmICh0aGlzLm1lZGlhQnVmZmVyICE9PSB0aGlzLm1lZGlhKSB7XG4gICAgICAgIHRoaXMubG9nKCdTd2l0Y2hpbmcgb24gbWFpbiBhdWRpbywgdXNlIG1lZGlhLmJ1ZmZlcmVkIHRvIHNjaGVkdWxlIG1haW4gZnJhZ21lbnQgbG9hZGluZycpO1xuICAgICAgICB0aGlzLm1lZGlhQnVmZmVyID0gdGhpcy5tZWRpYTtcbiAgICAgICAgY29uc3QgZnJhZ0N1cnJlbnQgPSB0aGlzLmZyYWdDdXJyZW50O1xuICAgICAgICAvLyB3ZSBuZWVkIHRvIHJlZmlsbCBhdWRpbyBidWZmZXIgZnJvbSBtYWluOiBjYW5jZWwgYW55IGZyYWcgbG9hZGluZyB0byBzcGVlZCB1cCBhdWRpbyBzd2l0Y2hcbiAgICAgICAgaWYgKGZyYWdDdXJyZW50KSB7XG4gICAgICAgICAgdGhpcy5sb2coJ1N3aXRjaGluZyB0byBtYWluIGF1ZGlvIHRyYWNrLCBjYW5jZWwgbWFpbiBmcmFnbWVudCBsb2FkJyk7XG4gICAgICAgICAgZnJhZ0N1cnJlbnQuYWJvcnRSZXF1ZXN0cygpO1xuICAgICAgICAgIHRoaXMuZnJhZ21lbnRUcmFja2VyLnJlbW92ZUZyYWdtZW50KGZyYWdDdXJyZW50KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBkZXN0cm95IHRyYW5zbXV4ZXIgdG8gZm9yY2UgaW5pdCBzZWdtZW50IGdlbmVyYXRpb24gKGZvbGxvd2luZyBhdWRpbyBzd2l0Y2gpXG4gICAgICAgIHRoaXMucmVzZXRUcmFuc211eGVyKCk7XG4gICAgICAgIC8vIHN3aXRjaCB0byBJRExFIHN0YXRlIHRvIGxvYWQgbmV3IGZyYWdtZW50XG4gICAgICAgIHRoaXMucmVzZXRMb2FkaW5nU3RhdGUoKTtcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5hdWRpb09ubHkpIHtcbiAgICAgICAgLy8gUmVzZXQgYXVkaW8gdHJhbnNtdXhlciBzbyB3aGVuIHN3aXRjaGluZyBiYWNrIHRvIG1haW4gYXVkaW8gd2UncmUgbm90IHN0aWxsIGFwcGVuZGluZyB3aGVyZSB3ZSBsZWZ0IG9mZlxuICAgICAgICB0aGlzLnJlc2V0VHJhbnNtdXhlcigpO1xuICAgICAgfVxuICAgICAgY29uc3QgaGxzID0gdGhpcy5obHM7XG4gICAgICAvLyBJZiBzd2l0Y2hpbmcgZnJvbSBhbHQgdG8gbWFpbiBhdWRpbywgZmx1c2ggYWxsIGF1ZGlvIGFuZCB0cmlnZ2VyIHRyYWNrIHN3aXRjaGVkXG4gICAgICBpZiAoZnJvbUFsdEF1ZGlvKSB7XG4gICAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5CVUZGRVJfRkxVU0hJTkcsIHtcbiAgICAgICAgICBzdGFydE9mZnNldDogMCxcbiAgICAgICAgICBlbmRPZmZzZXQ6IE51bWJlci5QT1NJVElWRV9JTkZJTklUWSxcbiAgICAgICAgICB0eXBlOiBudWxsXG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5yZW1vdmVBbGxGcmFnbWVudHMoKTtcbiAgICAgIH1cbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5BVURJT19UUkFDS19TV0lUQ0hFRCwgZGF0YSk7XG4gICAgfVxuICB9XG4gIG9uQXVkaW9UcmFja1N3aXRjaGVkKGV2ZW50LCBkYXRhKSB7XG4gICAgY29uc3QgdHJhY2tJZCA9IGRhdGEuaWQ7XG4gICAgY29uc3QgYWx0QXVkaW8gPSAhIXRoaXMuaGxzLmF1ZGlvVHJhY2tzW3RyYWNrSWRdLnVybDtcbiAgICBpZiAoYWx0QXVkaW8pIHtcbiAgICAgIGNvbnN0IHZpZGVvQnVmZmVyID0gdGhpcy52aWRlb0J1ZmZlcjtcbiAgICAgIC8vIGlmIHdlIHN3aXRjaGVkIG9uIGFsdGVybmF0ZSBhdWRpbywgZW5zdXJlIHRoYXQgbWFpbiBmcmFnbWVudCBzY2hlZHVsaW5nIGlzIHN5bmNlZCB3aXRoIHZpZGVvIHNvdXJjZWJ1ZmZlciBidWZmZXJlZFxuICAgICAgaWYgKHZpZGVvQnVmZmVyICYmIHRoaXMubWVkaWFCdWZmZXIgIT09IHZpZGVvQnVmZmVyKSB7XG4gICAgICAgIHRoaXMubG9nKCdTd2l0Y2hpbmcgb24gYWx0ZXJuYXRlIGF1ZGlvLCB1c2UgdmlkZW8uYnVmZmVyZWQgdG8gc2NoZWR1bGUgbWFpbiBmcmFnbWVudCBsb2FkaW5nJyk7XG4gICAgICAgIHRoaXMubWVkaWFCdWZmZXIgPSB2aWRlb0J1ZmZlcjtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5hbHRBdWRpbyA9IGFsdEF1ZGlvO1xuICAgIHRoaXMudGljaygpO1xuICB9XG4gIG9uQnVmZmVyQ3JlYXRlZChldmVudCwgZGF0YSkge1xuICAgIGNvbnN0IHRyYWNrcyA9IGRhdGEudHJhY2tzO1xuICAgIGxldCBtZWRpYVRyYWNrO1xuICAgIGxldCBuYW1lO1xuICAgIGxldCBhbHRlcm5hdGUgPSBmYWxzZTtcbiAgICBmb3IgKGNvbnN0IHR5cGUgaW4gdHJhY2tzKSB7XG4gICAgICBjb25zdCB0cmFjayA9IHRyYWNrc1t0eXBlXTtcbiAgICAgIGlmICh0cmFjay5pZCA9PT0gJ21haW4nKSB7XG4gICAgICAgIG5hbWUgPSB0eXBlO1xuICAgICAgICBtZWRpYVRyYWNrID0gdHJhY2s7XG4gICAgICAgIC8vIGtlZXAgdmlkZW8gc291cmNlIGJ1ZmZlciByZWZlcmVuY2VcbiAgICAgICAgaWYgKHR5cGUgPT09ICd2aWRlbycpIHtcbiAgICAgICAgICBjb25zdCB2aWRlb1RyYWNrID0gdHJhY2tzW3R5cGVdO1xuICAgICAgICAgIGlmICh2aWRlb1RyYWNrKSB7XG4gICAgICAgICAgICB0aGlzLnZpZGVvQnVmZmVyID0gdmlkZW9UcmFjay5idWZmZXI7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBhbHRlcm5hdGUgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoYWx0ZXJuYXRlICYmIG1lZGlhVHJhY2spIHtcbiAgICAgIHRoaXMubG9nKGBBbHRlcm5hdGUgdHJhY2sgZm91bmQsIHVzZSAke25hbWV9LmJ1ZmZlcmVkIHRvIHNjaGVkdWxlIG1haW4gZnJhZ21lbnQgbG9hZGluZ2ApO1xuICAgICAgdGhpcy5tZWRpYUJ1ZmZlciA9IG1lZGlhVHJhY2suYnVmZmVyO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLm1lZGlhQnVmZmVyID0gdGhpcy5tZWRpYTtcbiAgICB9XG4gIH1cbiAgb25GcmFnQnVmZmVyZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBjb25zdCB7XG4gICAgICBmcmFnLFxuICAgICAgcGFydFxuICAgIH0gPSBkYXRhO1xuICAgIGlmIChmcmFnICYmIGZyYWcudHlwZSAhPT0gUGxheWxpc3RMZXZlbFR5cGUuTUFJTikge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAodGhpcy5mcmFnQ29udGV4dENoYW5nZWQoZnJhZykpIHtcbiAgICAgIC8vIElmIGEgbGV2ZWwgc3dpdGNoIHdhcyByZXF1ZXN0ZWQgd2hpbGUgYSBmcmFnbWVudCB3YXMgYnVmZmVyaW5nLCBpdCB3aWxsIGVtaXQgdGhlIEZSQUdfQlVGRkVSRUQgZXZlbnQgdXBvbiBjb21wbGV0aW9uXG4gICAgICAvLyBBdm9pZCBzZXR0aW5nIHN0YXRlIGJhY2sgdG8gSURMRSwgc2luY2UgdGhhdCB3aWxsIGludGVyZmVyZSB3aXRoIGEgbGV2ZWwgc3dpdGNoXG4gICAgICB0aGlzLndhcm4oYEZyYWdtZW50ICR7ZnJhZy5zbn0ke3BhcnQgPyAnIHA6ICcgKyBwYXJ0LmluZGV4IDogJyd9IG9mIGxldmVsICR7ZnJhZy5sZXZlbH0gZmluaXNoZWQgYnVmZmVyaW5nLCBidXQgd2FzIGFib3J0ZWQuIHN0YXRlOiAke3RoaXMuc3RhdGV9YCk7XG4gICAgICBpZiAodGhpcy5zdGF0ZSA9PT0gU3RhdGUuUEFSU0VEKSB7XG4gICAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgICAgfVxuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBzdGF0cyA9IHBhcnQgPyBwYXJ0LnN0YXRzIDogZnJhZy5zdGF0cztcbiAgICB0aGlzLmZyYWdMYXN0S2JwcyA9IE1hdGgucm91bmQoOCAqIHN0YXRzLnRvdGFsIC8gKHN0YXRzLmJ1ZmZlcmluZy5lbmQgLSBzdGF0cy5sb2FkaW5nLmZpcnN0KSk7XG4gICAgaWYgKGZyYWcuc24gIT09ICdpbml0U2VnbWVudCcpIHtcbiAgICAgIHRoaXMuZnJhZ1ByZXZpb3VzID0gZnJhZztcbiAgICB9XG4gICAgdGhpcy5mcmFnQnVmZmVyZWRDb21wbGV0ZShmcmFnLCBwYXJ0KTtcbiAgfVxuICBvbkVycm9yKGV2ZW50LCBkYXRhKSB7XG4gICAgdmFyIF9kYXRhJGNvbnRleHQ7XG4gICAgaWYgKGRhdGEuZmF0YWwpIHtcbiAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5FUlJPUjtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgc3dpdGNoIChkYXRhLmRldGFpbHMpIHtcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkZSQUdfR0FQOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuRlJBR19QQVJTSU5HX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuRlJBR19ERUNSWVBUX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuRlJBR19MT0FEX0VSUk9SOlxuICAgICAgY2FzZSBFcnJvckRldGFpbHMuRlJBR19MT0FEX1RJTUVPVVQ6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5LRVlfTE9BRF9FUlJPUjpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLktFWV9MT0FEX1RJTUVPVVQ6XG4gICAgICAgIHRoaXMub25GcmFnbWVudE9yS2V5TG9hZEVycm9yKFBsYXlsaXN0TGV2ZWxUeXBlLk1BSU4sIGRhdGEpO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkxFVkVMX0xPQURfRVJST1I6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5MRVZFTF9MT0FEX1RJTUVPVVQ6XG4gICAgICBjYXNlIEVycm9yRGV0YWlscy5MRVZFTF9QQVJTSU5HX0VSUk9SOlxuICAgICAgICAvLyBpbiBjYXNlIG9mIG5vbiBmYXRhbCBlcnJvciB3aGlsZSBsb2FkaW5nIGxldmVsLCBpZiBsZXZlbCBjb250cm9sbGVyIGlzIG5vdCByZXRyeWluZyB0byBsb2FkIGxldmVsLCBzd2l0Y2ggYmFjayB0byBJRExFXG4gICAgICAgIGlmICghZGF0YS5sZXZlbFJldHJ5ICYmIHRoaXMuc3RhdGUgPT09IFN0YXRlLldBSVRJTkdfTEVWRUwgJiYgKChfZGF0YSRjb250ZXh0ID0gZGF0YS5jb250ZXh0KSA9PSBudWxsID8gdm9pZCAwIDogX2RhdGEkY29udGV4dC50eXBlKSA9PT0gUGxheWxpc3RDb250ZXh0VHlwZS5MRVZFTCkge1xuICAgICAgICAgIHRoaXMuc3RhdGUgPSBTdGF0ZS5JRExFO1xuICAgICAgICB9XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBFcnJvckRldGFpbHMuQlVGRkVSX0FQUEVORF9FUlJPUjpcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLkJVRkZFUl9GVUxMX0VSUk9SOlxuICAgICAgICBpZiAoIWRhdGEucGFyZW50IHx8IGRhdGEucGFyZW50ICE9PSAnbWFpbicpIHtcbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRhdGEuZGV0YWlscyA9PT0gRXJyb3JEZXRhaWxzLkJVRkZFUl9BUFBFTkRfRVJST1IpIHtcbiAgICAgICAgICB0aGlzLnJlc2V0TG9hZGluZ1N0YXRlKCk7XG4gICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLnJlZHVjZUxlbmd0aEFuZEZsdXNoQnVmZmVyKGRhdGEpKSB7XG4gICAgICAgICAgdGhpcy5mbHVzaE1haW5CdWZmZXIoMCwgTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZKTtcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgRXJyb3JEZXRhaWxzLklOVEVSTkFMX0VYQ0VQVElPTjpcbiAgICAgICAgdGhpcy5yZWNvdmVyV29ya2VyRXJyb3IoZGF0YSk7XG4gICAgICAgIGJyZWFrO1xuICAgIH1cbiAgfVxuXG4gIC8vIENoZWNrcyB0aGUgaGVhbHRoIG9mIHRoZSBidWZmZXIgYW5kIGF0dGVtcHRzIHRvIHJlc29sdmUgcGxheWJhY2sgc3RhbGxzLlxuICBjaGVja0J1ZmZlcigpIHtcbiAgICBjb25zdCB7XG4gICAgICBtZWRpYSxcbiAgICAgIGdhcENvbnRyb2xsZXJcbiAgICB9ID0gdGhpcztcbiAgICBpZiAoIW1lZGlhIHx8ICFnYXBDb250cm9sbGVyIHx8ICFtZWRpYS5yZWFkeVN0YXRlKSB7XG4gICAgICAvLyBFeGl0IGVhcmx5IGlmIHdlIGRvbid0IGhhdmUgbWVkaWEgb3IgaWYgdGhlIG1lZGlhIGhhc24ndCBidWZmZXJlZCBhbnl0aGluZyB5ZXQgKHJlYWR5U3RhdGUgMClcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHRoaXMubG9hZGVkbWV0YWRhdGEgfHwgIUJ1ZmZlckhlbHBlci5nZXRCdWZmZXJlZChtZWRpYSkubGVuZ3RoKSB7XG4gICAgICAvLyBSZXNvbHZlIGdhcHMgdXNpbmcgdGhlIG1haW4gYnVmZmVyLCB3aG9zZSByYW5nZXMgYXJlIHRoZSBpbnRlcnNlY3Rpb25zIG9mIHRoZSBBL1Ygc291cmNlYnVmZmVyc1xuICAgICAgY29uc3QgYWN0aXZlRnJhZyA9IHRoaXMuc3RhdGUgIT09IFN0YXRlLklETEUgPyB0aGlzLmZyYWdDdXJyZW50IDogbnVsbDtcbiAgICAgIGdhcENvbnRyb2xsZXIucG9sbCh0aGlzLmxhc3RDdXJyZW50VGltZSwgYWN0aXZlRnJhZyk7XG4gICAgfVxuICAgIHRoaXMubGFzdEN1cnJlbnRUaW1lID0gbWVkaWEuY3VycmVudFRpbWU7XG4gIH1cbiAgb25GcmFnTG9hZEVtZXJnZW5jeUFib3J0ZWQoKSB7XG4gICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gICAgLy8gaWYgbG9hZGVkbWV0YWRhdGEgaXMgbm90IHNldCwgaXQgbWVhbnMgdGhhdCB3ZSBhcmUgZW1lcmdlbmN5IHN3aXRjaCBkb3duIG9uIGZpcnN0IGZyYWdcbiAgICAvLyBpbiB0aGF0IGNhc2UsIHJlc2V0IHN0YXJ0RnJhZ1JlcXVlc3RlZCBmbGFnXG4gICAgaWYgKCF0aGlzLmxvYWRlZG1ldGFkYXRhKSB7XG4gICAgICB0aGlzLnN0YXJ0RnJhZ1JlcXVlc3RlZCA9IGZhbHNlO1xuICAgICAgdGhpcy5uZXh0TG9hZFBvc2l0aW9uID0gdGhpcy5zdGFydFBvc2l0aW9uO1xuICAgIH1cbiAgICB0aGlzLnRpY2tJbW1lZGlhdGUoKTtcbiAgfVxuICBvbkJ1ZmZlckZsdXNoZWQoZXZlbnQsIHtcbiAgICB0eXBlXG4gIH0pIHtcbiAgICBpZiAodHlwZSAhPT0gRWxlbWVudGFyeVN0cmVhbVR5cGVzLkFVRElPIHx8IHRoaXMuYXVkaW9Pbmx5ICYmICF0aGlzLmFsdEF1ZGlvKSB7XG4gICAgICBjb25zdCBtZWRpYUJ1ZmZlciA9ICh0eXBlID09PSBFbGVtZW50YXJ5U3RyZWFtVHlwZXMuVklERU8gPyB0aGlzLnZpZGVvQnVmZmVyIDogdGhpcy5tZWRpYUJ1ZmZlcikgfHwgdGhpcy5tZWRpYTtcbiAgICAgIHRoaXMuYWZ0ZXJCdWZmZXJGbHVzaGVkKG1lZGlhQnVmZmVyLCB0eXBlLCBQbGF5bGlzdExldmVsVHlwZS5NQUlOKTtcbiAgICAgIHRoaXMudGljaygpO1xuICAgIH1cbiAgfVxuICBvbkxldmVsc1VwZGF0ZWQoZXZlbnQsIGRhdGEpIHtcbiAgICBpZiAodGhpcy5sZXZlbCA+IC0xICYmIHRoaXMuZnJhZ0N1cnJlbnQpIHtcbiAgICAgIHRoaXMubGV2ZWwgPSB0aGlzLmZyYWdDdXJyZW50LmxldmVsO1xuICAgIH1cbiAgICB0aGlzLmxldmVscyA9IGRhdGEubGV2ZWxzO1xuICB9XG4gIHN3YXBBdWRpb0NvZGVjKCkge1xuICAgIHRoaXMuYXVkaW9Db2RlY1N3YXAgPSAhdGhpcy5hdWRpb0NvZGVjU3dhcDtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZWVrcyB0byB0aGUgc2V0IHN0YXJ0UG9zaXRpb24gaWYgbm90IGVxdWFsIHRvIHRoZSBtZWRpYUVsZW1lbnQncyBjdXJyZW50IHRpbWUuXG4gICAqL1xuICBzZWVrVG9TdGFydFBvcygpIHtcbiAgICBjb25zdCB7XG4gICAgICBtZWRpYVxuICAgIH0gPSB0aGlzO1xuICAgIGlmICghbWVkaWEpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgY3VycmVudFRpbWUgPSBtZWRpYS5jdXJyZW50VGltZTtcbiAgICBsZXQgc3RhcnRQb3NpdGlvbiA9IHRoaXMuc3RhcnRQb3NpdGlvbjtcbiAgICAvLyBvbmx5IGFkanVzdCBjdXJyZW50VGltZSBpZiBkaWZmZXJlbnQgZnJvbSBzdGFydFBvc2l0aW9uIG9yIGlmIHN0YXJ0UG9zaXRpb24gbm90IGJ1ZmZlcmVkXG4gICAgLy8gYXQgdGhhdCBzdGFnZSwgdGhlcmUgc2hvdWxkIGJlIG9ubHkgb25lIGJ1ZmZlcmVkIHJhbmdlLCBhcyB3ZSByZWFjaCB0aGF0IGNvZGUgYWZ0ZXIgZmlyc3QgZnJhZ21lbnQgaGFzIGJlZW4gYnVmZmVyZWRcbiAgICBpZiAoc3RhcnRQb3NpdGlvbiA+PSAwICYmIGN1cnJlbnRUaW1lIDwgc3RhcnRQb3NpdGlvbikge1xuICAgICAgaWYgKG1lZGlhLnNlZWtpbmcpIHtcbiAgICAgICAgdGhpcy5sb2coYGNvdWxkIG5vdCBzZWVrIHRvICR7c3RhcnRQb3NpdGlvbn0sIGFscmVhZHkgc2Vla2luZyBhdCAke2N1cnJlbnRUaW1lfWApO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjb25zdCBidWZmZXJlZCA9IEJ1ZmZlckhlbHBlci5nZXRCdWZmZXJlZChtZWRpYSk7XG4gICAgICBjb25zdCBidWZmZXJTdGFydCA9IGJ1ZmZlcmVkLmxlbmd0aCA/IGJ1ZmZlcmVkLnN0YXJ0KDApIDogMDtcbiAgICAgIGNvbnN0IGRlbHRhID0gYnVmZmVyU3RhcnQgLSBzdGFydFBvc2l0aW9uO1xuICAgICAgaWYgKGRlbHRhID4gMCAmJiAoZGVsdGEgPCB0aGlzLmNvbmZpZy5tYXhCdWZmZXJIb2xlIHx8IGRlbHRhIDwgdGhpcy5jb25maWcubWF4RnJhZ0xvb2tVcFRvbGVyYW5jZSkpIHtcbiAgICAgICAgdGhpcy5sb2coYGFkanVzdGluZyBzdGFydCBwb3NpdGlvbiBieSAke2RlbHRhfSB0byBtYXRjaCBidWZmZXIgc3RhcnRgKTtcbiAgICAgICAgc3RhcnRQb3NpdGlvbiArPSBkZWx0YTtcbiAgICAgICAgdGhpcy5zdGFydFBvc2l0aW9uID0gc3RhcnRQb3NpdGlvbjtcbiAgICAgIH1cbiAgICAgIHRoaXMubG9nKGBzZWVrIHRvIHRhcmdldCBzdGFydCBwb3NpdGlvbiAke3N0YXJ0UG9zaXRpb259IGZyb20gY3VycmVudCB0aW1lICR7Y3VycmVudFRpbWV9YCk7XG4gICAgICBtZWRpYS5jdXJyZW50VGltZSA9IHN0YXJ0UG9zaXRpb247XG4gICAgfVxuICB9XG4gIF9nZXRBdWRpb0NvZGVjKGN1cnJlbnRMZXZlbCkge1xuICAgIGxldCBhdWRpb0NvZGVjID0gdGhpcy5jb25maWcuZGVmYXVsdEF1ZGlvQ29kZWMgfHwgY3VycmVudExldmVsLmF1ZGlvQ29kZWM7XG4gICAgaWYgKHRoaXMuYXVkaW9Db2RlY1N3YXAgJiYgYXVkaW9Db2RlYykge1xuICAgICAgdGhpcy5sb2coJ1N3YXBwaW5nIGF1ZGlvIGNvZGVjJyk7XG4gICAgICBpZiAoYXVkaW9Db2RlYy5pbmRleE9mKCdtcDRhLjQwLjUnKSAhPT0gLTEpIHtcbiAgICAgICAgYXVkaW9Db2RlYyA9ICdtcDRhLjQwLjInO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgYXVkaW9Db2RlYyA9ICdtcDRhLjQwLjUnO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gYXVkaW9Db2RlYztcbiAgfVxuICBfbG9hZEJpdHJhdGVUZXN0RnJhZyhmcmFnLCBsZXZlbCkge1xuICAgIGZyYWcuYml0cmF0ZVRlc3QgPSB0cnVlO1xuICAgIHRoaXMuX2RvRnJhZ0xvYWQoZnJhZywgbGV2ZWwpLnRoZW4oZGF0YSA9PiB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIGhsc1xuICAgICAgfSA9IHRoaXM7XG4gICAgICBpZiAoIWRhdGEgfHwgdGhpcy5mcmFnQ29udGV4dENoYW5nZWQoZnJhZykpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgbGV2ZWwuZnJhZ21lbnRFcnJvciA9IDA7XG4gICAgICB0aGlzLnN0YXRlID0gU3RhdGUuSURMRTtcbiAgICAgIHRoaXMuc3RhcnRGcmFnUmVxdWVzdGVkID0gZmFsc2U7XG4gICAgICB0aGlzLmJpdHJhdGVUZXN0ID0gZmFsc2U7XG4gICAgICBjb25zdCBzdGF0cyA9IGZyYWcuc3RhdHM7XG4gICAgICAvLyBCaXRyYXRlIHRlc3RzIGZyYWdtZW50cyBhcmUgbmVpdGhlciBwYXJzZWQgbm9yIGJ1ZmZlcmVkXG4gICAgICBzdGF0cy5wYXJzaW5nLnN0YXJ0ID0gc3RhdHMucGFyc2luZy5lbmQgPSBzdGF0cy5idWZmZXJpbmcuc3RhcnQgPSBzdGF0cy5idWZmZXJpbmcuZW5kID0gc2VsZi5wZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5GUkFHX0xPQURFRCwgZGF0YSk7XG4gICAgICBmcmFnLmJpdHJhdGVUZXN0ID0gZmFsc2U7XG4gICAgfSk7XG4gIH1cbiAgX2hhbmRsZVRyYW5zbXV4Q29tcGxldGUodHJhbnNtdXhSZXN1bHQpIHtcbiAgICB2YXIgX2lkMyRzYW1wbGVzO1xuICAgIGNvbnN0IGlkID0gJ21haW4nO1xuICAgIGNvbnN0IHtcbiAgICAgIGhsc1xuICAgIH0gPSB0aGlzO1xuICAgIGNvbnN0IHtcbiAgICAgIHJlbXV4UmVzdWx0LFxuICAgICAgY2h1bmtNZXRhXG4gICAgfSA9IHRyYW5zbXV4UmVzdWx0O1xuICAgIGNvbnN0IGNvbnRleHQgPSB0aGlzLmdldEN1cnJlbnRDb250ZXh0KGNodW5rTWV0YSk7XG4gICAgaWYgKCFjb250ZXh0KSB7XG4gICAgICB0aGlzLnJlc2V0V2hlbk1pc3NpbmdDb250ZXh0KGNodW5rTWV0YSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHtcbiAgICAgIGZyYWcsXG4gICAgICBwYXJ0LFxuICAgICAgbGV2ZWxcbiAgICB9ID0gY29udGV4dDtcbiAgICBjb25zdCB7XG4gICAgICB2aWRlbyxcbiAgICAgIHRleHQsXG4gICAgICBpZDMsXG4gICAgICBpbml0U2VnbWVudFxuICAgIH0gPSByZW11eFJlc3VsdDtcbiAgICBjb25zdCB7XG4gICAgICBkZXRhaWxzXG4gICAgfSA9IGxldmVsO1xuICAgIC8vIFRoZSBhdWRpby1zdHJlYW0tY29udHJvbGxlciBoYW5kbGVzIGF1ZGlvIGJ1ZmZlcmluZyBpZiBIbHMuanMgaXMgcGxheWluZyBhbiBhbHRlcm5hdGUgYXVkaW8gdHJhY2tcbiAgICBjb25zdCBhdWRpbyA9IHRoaXMuYWx0QXVkaW8gPyB1bmRlZmluZWQgOiByZW11eFJlc3VsdC5hdWRpbztcblxuICAgIC8vIENoZWNrIGlmIHRoZSBjdXJyZW50IGZyYWdtZW50IGhhcyBiZWVuIGFib3J0ZWQuIFdlIGNoZWNrIHRoaXMgYnkgZmlyc3Qgc2VlaW5nIGlmIHdlJ3JlIHN0aWxsIHBsYXlpbmcgdGhlIGN1cnJlbnQgbGV2ZWwuXG4gICAgLy8gSWYgd2UgYXJlLCBzdWJzZXF1ZW50bHkgY2hlY2sgaWYgdGhlIGN1cnJlbnRseSBsb2FkaW5nIGZyYWdtZW50IChmcmFnQ3VycmVudCkgaGFzIGNoYW5nZWQuXG4gICAgaWYgKHRoaXMuZnJhZ0NvbnRleHRDaGFuZ2VkKGZyYWcpKSB7XG4gICAgICB0aGlzLmZyYWdtZW50VHJhY2tlci5yZW1vdmVGcmFnbWVudChmcmFnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5zdGF0ZSA9IFN0YXRlLlBBUlNJTkc7XG4gICAgaWYgKGluaXRTZWdtZW50KSB7XG4gICAgICBpZiAoaW5pdFNlZ21lbnQgIT0gbnVsbCAmJiBpbml0U2VnbWVudC50cmFja3MpIHtcbiAgICAgICAgY29uc3QgbWFwRnJhZ21lbnQgPSBmcmFnLmluaXRTZWdtZW50IHx8IGZyYWc7XG4gICAgICAgIHRoaXMuX2J1ZmZlckluaXRTZWdtZW50KGxldmVsLCBpbml0U2VnbWVudC50cmFja3MsIG1hcEZyYWdtZW50LCBjaHVua01ldGEpO1xuICAgICAgICBobHMudHJpZ2dlcihFdmVudHMuRlJBR19QQVJTSU5HX0lOSVRfU0VHTUVOVCwge1xuICAgICAgICAgIGZyYWc6IG1hcEZyYWdtZW50LFxuICAgICAgICAgIGlkLFxuICAgICAgICAgIHRyYWNrczogaW5pdFNlZ21lbnQudHJhY2tzXG4gICAgICAgIH0pO1xuICAgICAgfVxuXG4gICAgICAvLyBUaGlzIHdvdWxkIGJlIG5pY2UgaWYgTnVtYmVyLmlzRmluaXRlIGFjdGVkIGFzIGEgdHlwZWd1YXJkLCBidXQgaXQgZG9lc24ndC4gU2VlOiBodHRwczovL2dpdGh1Yi5jb20vTWljcm9zb2Z0L1R5cGVTY3JpcHQvaXNzdWVzLzEwMDM4XG4gICAgICBjb25zdCBpbml0UFRTID0gaW5pdFNlZ21lbnQuaW5pdFBUUztcbiAgICAgIGNvbnN0IHRpbWVzY2FsZSA9IGluaXRTZWdtZW50LnRpbWVzY2FsZTtcbiAgICAgIGlmIChpc0Zpbml0ZU51bWJlcihpbml0UFRTKSkge1xuICAgICAgICB0aGlzLmluaXRQVFNbZnJhZy5jY10gPSB7XG4gICAgICAgICAgYmFzZVRpbWU6IGluaXRQVFMsXG4gICAgICAgICAgdGltZXNjYWxlXG4gICAgICAgIH07XG4gICAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5JTklUX1BUU19GT1VORCwge1xuICAgICAgICAgIGZyYWcsXG4gICAgICAgICAgaWQsXG4gICAgICAgICAgaW5pdFBUUyxcbiAgICAgICAgICB0aW1lc2NhbGVcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gQXZvaWQgYnVmZmVyaW5nIGlmIGJhY2t0cmFja2luZyB0aGlzIGZyYWdtZW50XG4gICAgaWYgKHZpZGVvICYmIGRldGFpbHMgJiYgZnJhZy5zbiAhPT0gJ2luaXRTZWdtZW50Jykge1xuICAgICAgY29uc3QgcHJldkZyYWcgPSBkZXRhaWxzLmZyYWdtZW50c1tmcmFnLnNuIC0gMSAtIGRldGFpbHMuc3RhcnRTTl07XG4gICAgICBjb25zdCBpc0ZpcnN0RnJhZ21lbnQgPSBmcmFnLnNuID09PSBkZXRhaWxzLnN0YXJ0U047XG4gICAgICBjb25zdCBpc0ZpcnN0SW5EaXNjb250aW51aXR5ID0gIXByZXZGcmFnIHx8IGZyYWcuY2MgPiBwcmV2RnJhZy5jYztcbiAgICAgIGlmIChyZW11eFJlc3VsdC5pbmRlcGVuZGVudCAhPT0gZmFsc2UpIHtcbiAgICAgICAgY29uc3Qge1xuICAgICAgICAgIHN0YXJ0UFRTLFxuICAgICAgICAgIGVuZFBUUyxcbiAgICAgICAgICBzdGFydERUUyxcbiAgICAgICAgICBlbmREVFNcbiAgICAgICAgfSA9IHZpZGVvO1xuICAgICAgICBpZiAocGFydCkge1xuICAgICAgICAgIHBhcnQuZWxlbWVudGFyeVN0cmVhbXNbdmlkZW8udHlwZV0gPSB7XG4gICAgICAgICAgICBzdGFydFBUUyxcbiAgICAgICAgICAgIGVuZFBUUyxcbiAgICAgICAgICAgIHN0YXJ0RFRTLFxuICAgICAgICAgICAgZW5kRFRTXG4gICAgICAgICAgfTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBpZiAodmlkZW8uZmlyc3RLZXlGcmFtZSAmJiB2aWRlby5pbmRlcGVuZGVudCAmJiBjaHVua01ldGEuaWQgPT09IDEgJiYgIWlzRmlyc3RJbkRpc2NvbnRpbnVpdHkpIHtcbiAgICAgICAgICAgIHRoaXMuY291bGRCYWNrdHJhY2sgPSB0cnVlO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAodmlkZW8uZHJvcHBlZCAmJiB2aWRlby5pbmRlcGVuZGVudCkge1xuICAgICAgICAgICAgLy8gQmFja3RyYWNrIGlmIGRyb3BwZWQgZnJhbWVzIGNyZWF0ZSBhIGdhcCBhZnRlciBjdXJyZW50VGltZVxuXG4gICAgICAgICAgICBjb25zdCBidWZmZXJJbmZvID0gdGhpcy5nZXRNYWluRndkQnVmZmVySW5mbygpO1xuICAgICAgICAgICAgY29uc3QgdGFyZ2V0QnVmZmVyVGltZSA9IChidWZmZXJJbmZvID8gYnVmZmVySW5mby5lbmQgOiB0aGlzLmdldExvYWRQb3NpdGlvbigpKSArIHRoaXMuY29uZmlnLm1heEJ1ZmZlckhvbGU7XG4gICAgICAgICAgICBjb25zdCBzdGFydFRpbWUgPSB2aWRlby5maXJzdEtleUZyYW1lUFRTID8gdmlkZW8uZmlyc3RLZXlGcmFtZVBUUyA6IHN0YXJ0UFRTO1xuICAgICAgICAgICAgaWYgKCFpc0ZpcnN0RnJhZ21lbnQgJiYgdGFyZ2V0QnVmZmVyVGltZSA8IHN0YXJ0VGltZSAtIHRoaXMuY29uZmlnLm1heEJ1ZmZlckhvbGUgJiYgIWlzRmlyc3RJbkRpc2NvbnRpbnVpdHkpIHtcbiAgICAgICAgICAgICAgdGhpcy5iYWNrdHJhY2soZnJhZyk7XG4gICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoaXNGaXJzdEluRGlzY29udGludWl0eSkge1xuICAgICAgICAgICAgICAvLyBNYXJrIHNlZ21lbnQgd2l0aCBhIGdhcCB0byBhdm9pZCBsb29wIGxvYWRpbmdcbiAgICAgICAgICAgICAgZnJhZy5nYXAgPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gU2V0IHZpZGVvIHN0cmVhbSBzdGFydCB0byBmcmFnbWVudCBzdGFydCBzbyB0aGF0IHRydW5jYXRlZCBzYW1wbGVzIGRvIG5vdCBkaXN0b3J0IHRoZSB0aW1lbGluZSwgYW5kIG1hcmsgaXQgcGFydGlhbFxuICAgICAgICAgICAgZnJhZy5zZXRFbGVtZW50YXJ5U3RyZWFtSW5mbyh2aWRlby50eXBlLCBmcmFnLnN0YXJ0LCBlbmRQVFMsIGZyYWcuc3RhcnQsIGVuZERUUywgdHJ1ZSk7XG4gICAgICAgICAgfSBlbHNlIGlmIChpc0ZpcnN0RnJhZ21lbnQgJiYgc3RhcnRQVFMgPiBNQVhfU1RBUlRfR0FQX0pVTVApIHtcbiAgICAgICAgICAgIC8vIE1hcmsgc2VnbWVudCB3aXRoIGEgZ2FwIHRvIHNraXAgbGFyZ2Ugc3RhcnQgZ2FwXG4gICAgICAgICAgICBmcmFnLmdhcCA9IHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGZyYWcuc2V0RWxlbWVudGFyeVN0cmVhbUluZm8odmlkZW8udHlwZSwgc3RhcnRQVFMsIGVuZFBUUywgc3RhcnREVFMsIGVuZERUUyk7XG4gICAgICAgIGlmICh0aGlzLmJhY2t0cmFja0ZyYWdtZW50KSB7XG4gICAgICAgICAgdGhpcy5iYWNrdHJhY2tGcmFnbWVudCA9IGZyYWc7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5idWZmZXJGcmFnbWVudERhdGEodmlkZW8sIGZyYWcsIHBhcnQsIGNodW5rTWV0YSwgaXNGaXJzdEZyYWdtZW50IHx8IGlzRmlyc3RJbkRpc2NvbnRpbnVpdHkpO1xuICAgICAgfSBlbHNlIGlmIChpc0ZpcnN0RnJhZ21lbnQgfHwgaXNGaXJzdEluRGlzY29udGludWl0eSkge1xuICAgICAgICAvLyBNYXJrIHNlZ21lbnQgd2l0aCBhIGdhcCB0byBhdm9pZCBsb29wIGxvYWRpbmdcbiAgICAgICAgZnJhZy5nYXAgPSB0cnVlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5iYWNrdHJhY2soZnJhZyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKGF1ZGlvKSB7XG4gICAgICBjb25zdCB7XG4gICAgICAgIHN0YXJ0UFRTLFxuICAgICAgICBlbmRQVFMsXG4gICAgICAgIHN0YXJ0RFRTLFxuICAgICAgICBlbmREVFNcbiAgICAgIH0gPSBhdWRpbztcbiAgICAgIGlmIChwYXJ0KSB7XG4gICAgICAgIHBhcnQuZWxlbWVudGFyeVN0cmVhbXNbRWxlbWVudGFyeVN0cmVhbVR5cGVzLkFVRElPXSA9IHtcbiAgICAgICAgICBzdGFydFBUUyxcbiAgICAgICAgICBlbmRQVFMsXG4gICAgICAgICAgc3RhcnREVFMsXG4gICAgICAgICAgZW5kRFRTXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgICBmcmFnLnNldEVsZW1lbnRhcnlTdHJlYW1JbmZvKEVsZW1lbnRhcnlTdHJlYW1UeXBlcy5BVURJTywgc3RhcnRQVFMsIGVuZFBUUywgc3RhcnREVFMsIGVuZERUUyk7XG4gICAgICB0aGlzLmJ1ZmZlckZyYWdtZW50RGF0YShhdWRpbywgZnJhZywgcGFydCwgY2h1bmtNZXRhKTtcbiAgICB9XG4gICAgaWYgKGRldGFpbHMgJiYgaWQzICE9IG51bGwgJiYgKF9pZDMkc2FtcGxlcyA9IGlkMy5zYW1wbGVzKSAhPSBudWxsICYmIF9pZDMkc2FtcGxlcy5sZW5ndGgpIHtcbiAgICAgIGNvbnN0IGVtaXR0ZWRJRDMgPSB7XG4gICAgICAgIGlkLFxuICAgICAgICBmcmFnLFxuICAgICAgICBkZXRhaWxzLFxuICAgICAgICBzYW1wbGVzOiBpZDMuc2FtcGxlc1xuICAgICAgfTtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5GUkFHX1BBUlNJTkdfTUVUQURBVEEsIGVtaXR0ZWRJRDMpO1xuICAgIH1cbiAgICBpZiAoZGV0YWlscyAmJiB0ZXh0KSB7XG4gICAgICBjb25zdCBlbWl0dGVkVGV4dCA9IHtcbiAgICAgICAgaWQsXG4gICAgICAgIGZyYWcsXG4gICAgICAgIGRldGFpbHMsXG4gICAgICAgIHNhbXBsZXM6IHRleHQuc2FtcGxlc1xuICAgICAgfTtcbiAgICAgIGhscy50cmlnZ2VyKEV2ZW50cy5GUkFHX1BBUlNJTkdfVVNFUkRBVEEsIGVtaXR0ZWRUZXh0KTtcbiAgICB9XG4gIH1cbiAgX2J1ZmZlckluaXRTZWdtZW50KGN1cnJlbnRMZXZlbCwgdHJhY2tzLCBmcmFnLCBjaHVua01ldGEpIHtcbiAgICBpZiAodGhpcy5zdGF0ZSAhPT0gU3RhdGUuUEFSU0lORykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmF1ZGlvT25seSA9ICEhdHJhY2tzLmF1ZGlvICYmICF0cmFja3MudmlkZW87XG5cbiAgICAvLyBpZiBhdWRpbyB0cmFjayBpcyBleHBlY3RlZCB0byBjb21lIGZyb20gYXVkaW8gc3RyZWFtIGNvbnRyb2xsZXIsIGRpc2NhcmQgYW55IGNvbWluZyBmcm9tIG1haW5cbiAgICBpZiAodGhpcy5hbHRBdWRpbyAmJiAhdGhpcy5hdWRpb09ubHkpIHtcbiAgICAgIGRlbGV0ZSB0cmFja3MuYXVkaW87XG4gICAgfVxuICAgIC8vIGluY2x1ZGUgbGV2ZWxDb2RlYyBpbiBhdWRpbyBhbmQgdmlkZW8gdHJhY2tzXG4gICAgY29uc3Qge1xuICAgICAgYXVkaW8sXG4gICAgICB2aWRlbyxcbiAgICAgIGF1ZGlvdmlkZW9cbiAgICB9ID0gdHJhY2tzO1xuICAgIGlmIChhdWRpbykge1xuICAgICAgbGV0IGF1ZGlvQ29kZWMgPSBjdXJyZW50TGV2ZWwuYXVkaW9Db2RlYztcbiAgICAgIGNvbnN0IHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpO1xuICAgICAgaWYgKHRoaXMuYXVkaW9Db2RlY1N3aXRjaCkge1xuICAgICAgICBpZiAoYXVkaW9Db2RlYykge1xuICAgICAgICAgIGlmIChhdWRpb0NvZGVjLmluZGV4T2YoJ21wNGEuNDAuNScpICE9PSAtMSkge1xuICAgICAgICAgICAgYXVkaW9Db2RlYyA9ICdtcDRhLjQwLjInO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBhdWRpb0NvZGVjID0gJ21wNGEuNDAuNSc7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIEluIHRoZSBjYXNlIHRoYXQgQUFDIGFuZCBIRS1BQUMgYXVkaW8gY29kZWNzIGFyZSBzaWduYWxsZWQgaW4gbWFuaWZlc3QsXG4gICAgICAgIC8vIGZvcmNlIEhFLUFBQywgYXMgaXQgc2VlbXMgdGhhdCBtb3N0IGJyb3dzZXJzIHByZWZlcnMgaXQuXG4gICAgICAgIC8vIGRvbid0IGZvcmNlIEhFLUFBQyBpZiBtb25vIHN0cmVhbSwgb3IgaW4gRmlyZWZveFxuICAgICAgICBjb25zdCBhdWRpb01ldGFkYXRhID0gYXVkaW8ubWV0YWRhdGE7XG4gICAgICAgIGlmIChhdWRpb01ldGFkYXRhICYmICdjaGFubmVsQ291bnQnIGluIGF1ZGlvTWV0YWRhdGEgJiYgKGF1ZGlvTWV0YWRhdGEuY2hhbm5lbENvdW50IHx8IDEpICE9PSAxICYmIHVhLmluZGV4T2YoJ2ZpcmVmb3gnKSA9PT0gLTEpIHtcbiAgICAgICAgICBhdWRpb0NvZGVjID0gJ21wNGEuNDAuNSc7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIC8vIEhFLUFBQyBpcyBicm9rZW4gb24gQW5kcm9pZCwgYWx3YXlzIHNpZ25hbCBhdWRpbyBjb2RlYyBhcyBBQUMgZXZlbiBpZiB2YXJpYW50IG1hbmlmZXN0IHN0YXRlcyBvdGhlcndpc2VcbiAgICAgIGlmIChhdWRpb0NvZGVjICYmIGF1ZGlvQ29kZWMuaW5kZXhPZignbXA0YS40MC41JykgIT09IC0xICYmIHVhLmluZGV4T2YoJ2FuZHJvaWQnKSAhPT0gLTEgJiYgYXVkaW8uY29udGFpbmVyICE9PSAnYXVkaW8vbXBlZycpIHtcbiAgICAgICAgLy8gRXhjbHVkZSBtcGVnIGF1ZGlvXG4gICAgICAgIGF1ZGlvQ29kZWMgPSAnbXA0YS40MC4yJztcbiAgICAgICAgdGhpcy5sb2coYEFuZHJvaWQ6IGZvcmNlIGF1ZGlvIGNvZGVjIHRvICR7YXVkaW9Db2RlY31gKTtcbiAgICAgIH1cbiAgICAgIGlmIChjdXJyZW50TGV2ZWwuYXVkaW9Db2RlYyAmJiBjdXJyZW50TGV2ZWwuYXVkaW9Db2RlYyAhPT0gYXVkaW9Db2RlYykge1xuICAgICAgICB0aGlzLmxvZyhgU3dhcHBpbmcgbWFuaWZlc3QgYXVkaW8gY29kZWMgXCIke2N1cnJlbnRMZXZlbC5hdWRpb0NvZGVjfVwiIGZvciBcIiR7YXVkaW9Db2RlY31cImApO1xuICAgICAgfVxuICAgICAgYXVkaW8ubGV2ZWxDb2RlYyA9IGF1ZGlvQ29kZWM7XG4gICAgICBhdWRpby5pZCA9ICdtYWluJztcbiAgICAgIHRoaXMubG9nKGBJbml0IGF1ZGlvIGJ1ZmZlciwgY29udGFpbmVyOiR7YXVkaW8uY29udGFpbmVyfSwgY29kZWNzW3NlbGVjdGVkL2xldmVsL3BhcnNlZF09WyR7YXVkaW9Db2RlYyB8fCAnJ30vJHtjdXJyZW50TGV2ZWwuYXVkaW9Db2RlYyB8fCAnJ30vJHthdWRpby5jb2RlY31dYCk7XG4gICAgfVxuICAgIGlmICh2aWRlbykge1xuICAgICAgdmlkZW8ubGV2ZWxDb2RlYyA9IGN1cnJlbnRMZXZlbC52aWRlb0NvZGVjO1xuICAgICAgdmlkZW8uaWQgPSAnbWFpbic7XG4gICAgICB0aGlzLmxvZyhgSW5pdCB2aWRlbyBidWZmZXIsIGNvbnRhaW5lcjoke3ZpZGVvLmNvbnRhaW5lcn0sIGNvZGVjc1tsZXZlbC9wYXJzZWRdPVske2N1cnJlbnRMZXZlbC52aWRlb0NvZGVjIHx8ICcnfS8ke3ZpZGVvLmNvZGVjfV1gKTtcbiAgICB9XG4gICAgaWYgKGF1ZGlvdmlkZW8pIHtcbiAgICAgIHRoaXMubG9nKGBJbml0IGF1ZGlvdmlkZW8gYnVmZmVyLCBjb250YWluZXI6JHthdWRpb3ZpZGVvLmNvbnRhaW5lcn0sIGNvZGVjc1tsZXZlbC9wYXJzZWRdPVske2N1cnJlbnRMZXZlbC5jb2RlY3N9LyR7YXVkaW92aWRlby5jb2RlY31dYCk7XG4gICAgfVxuICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkJVRkZFUl9DT0RFQ1MsIHRyYWNrcyk7XG4gICAgLy8gbG9vcCB0aHJvdWdoIHRyYWNrcyB0aGF0IGFyZSBnb2luZyB0byBiZSBwcm92aWRlZCB0byBidWZmZXJDb250cm9sbGVyXG4gICAgT2JqZWN0LmtleXModHJhY2tzKS5mb3JFYWNoKHRyYWNrTmFtZSA9PiB7XG4gICAgICBjb25zdCB0cmFjayA9IHRyYWNrc1t0cmFja05hbWVdO1xuICAgICAgY29uc3QgaW5pdFNlZ21lbnQgPSB0cmFjay5pbml0U2VnbWVudDtcbiAgICAgIGlmIChpbml0U2VnbWVudCAhPSBudWxsICYmIGluaXRTZWdtZW50LmJ5dGVMZW5ndGgpIHtcbiAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuQlVGRkVSX0FQUEVORElORywge1xuICAgICAgICAgIHR5cGU6IHRyYWNrTmFtZSxcbiAgICAgICAgICBkYXRhOiBpbml0U2VnbWVudCxcbiAgICAgICAgICBmcmFnLFxuICAgICAgICAgIHBhcnQ6IG51bGwsXG4gICAgICAgICAgY2h1bmtNZXRhLFxuICAgICAgICAgIHBhcmVudDogZnJhZy50eXBlXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH0pO1xuICAgIC8vIHRyaWdnZXIgaGFuZGxlciByaWdodCBub3dcbiAgICB0aGlzLnRpY2tJbW1lZGlhdGUoKTtcbiAgfVxuICBnZXRNYWluRndkQnVmZmVySW5mbygpIHtcbiAgICByZXR1cm4gdGhpcy5nZXRGd2RCdWZmZXJJbmZvKHRoaXMubWVkaWFCdWZmZXIgPyB0aGlzLm1lZGlhQnVmZmVyIDogdGhpcy5tZWRpYSwgUGxheWxpc3RMZXZlbFR5cGUuTUFJTik7XG4gIH1cbiAgYmFja3RyYWNrKGZyYWcpIHtcbiAgICB0aGlzLmNvdWxkQmFja3RyYWNrID0gdHJ1ZTtcbiAgICAvLyBDYXVzZXMgZmluZEZyYWdtZW50cyB0byBiYWNrdHJhY2sgdGhyb3VnaCBmcmFnbWVudHMgdG8gZmluZCB0aGUga2V5ZnJhbWVcbiAgICB0aGlzLmJhY2t0cmFja0ZyYWdtZW50ID0gZnJhZztcbiAgICB0aGlzLnJlc2V0VHJhbnNtdXhlcigpO1xuICAgIHRoaXMuZmx1c2hCdWZmZXJHYXAoZnJhZyk7XG4gICAgdGhpcy5mcmFnbWVudFRyYWNrZXIucmVtb3ZlRnJhZ21lbnQoZnJhZyk7XG4gICAgdGhpcy5mcmFnUHJldmlvdXMgPSBudWxsO1xuICAgIHRoaXMubmV4dExvYWRQb3NpdGlvbiA9IGZyYWcuc3RhcnQ7XG4gICAgdGhpcy5zdGF0ZSA9IFN0YXRlLklETEU7XG4gIH1cbiAgY2hlY2tGcmFnbWVudENoYW5nZWQoKSB7XG4gICAgY29uc3QgdmlkZW8gPSB0aGlzLm1lZGlhO1xuICAgIGxldCBmcmFnUGxheWluZ0N1cnJlbnQgPSBudWxsO1xuICAgIGlmICh2aWRlbyAmJiB2aWRlby5yZWFkeVN0YXRlID4gMSAmJiB2aWRlby5zZWVraW5nID09PSBmYWxzZSkge1xuICAgICAgY29uc3QgY3VycmVudFRpbWUgPSB2aWRlby5jdXJyZW50VGltZTtcbiAgICAgIC8qIGlmIHZpZGVvIGVsZW1lbnQgaXMgaW4gc2Vla2VkIHN0YXRlLCBjdXJyZW50VGltZSBjYW4gb25seSBpbmNyZWFzZS5cbiAgICAgICAgKGFzc3VtaW5nIHRoYXQgcGxheWJhY2sgcmF0ZSBpcyBwb3NpdGl2ZSAuLi4pXG4gICAgICAgIEFzIHNvbWV0aW1lcyBjdXJyZW50VGltZSBqdW1wcyBiYWNrIHRvIHplcm8gYWZ0ZXIgYVxuICAgICAgICBtZWRpYSBkZWNvZGUgZXJyb3IsIGNoZWNrIHRoaXMsIHRvIGF2b2lkIHNlZWtpbmcgYmFjayB0b1xuICAgICAgICB3cm9uZyBwb3NpdGlvbiBhZnRlciBhIG1lZGlhIGRlY29kZSBlcnJvclxuICAgICAgKi9cblxuICAgICAgaWYgKEJ1ZmZlckhlbHBlci5pc0J1ZmZlcmVkKHZpZGVvLCBjdXJyZW50VGltZSkpIHtcbiAgICAgICAgZnJhZ1BsYXlpbmdDdXJyZW50ID0gdGhpcy5nZXRBcHBlbmRlZEZyYWcoY3VycmVudFRpbWUpO1xuICAgICAgfSBlbHNlIGlmIChCdWZmZXJIZWxwZXIuaXNCdWZmZXJlZCh2aWRlbywgY3VycmVudFRpbWUgKyAwLjEpKSB7XG4gICAgICAgIC8qIGVuc3VyZSB0aGF0IEZSQUdfQ0hBTkdFRCBldmVudCBpcyB0cmlnZ2VyZWQgYXQgc3RhcnR1cCxcbiAgICAgICAgICB3aGVuIGZpcnN0IHZpZGVvIGZyYW1lIGlzIGRpc3BsYXllZCBhbmQgcGxheWJhY2sgaXMgcGF1c2VkLlxuICAgICAgICAgIGFkZCBhIHRvbGVyYW5jZSBvZiAxMDBtcywgaW4gY2FzZSBjdXJyZW50IHBvc2l0aW9uIGlzIG5vdCBidWZmZXJlZCxcbiAgICAgICAgICBjaGVjayBpZiBjdXJyZW50IHBvcysxMDBtcyBpcyBidWZmZXJlZCBhbmQgdXNlIHRoYXQgYnVmZmVyIHJhbmdlXG4gICAgICAgICAgZm9yIEZSQUdfQ0hBTkdFRCBldmVudCByZXBvcnRpbmcgKi9cbiAgICAgICAgZnJhZ1BsYXlpbmdDdXJyZW50ID0gdGhpcy5nZXRBcHBlbmRlZEZyYWcoY3VycmVudFRpbWUgKyAwLjEpO1xuICAgICAgfVxuICAgICAgaWYgKGZyYWdQbGF5aW5nQ3VycmVudCkge1xuICAgICAgICB0aGlzLmJhY2t0cmFja0ZyYWdtZW50ID0gbnVsbDtcbiAgICAgICAgY29uc3QgZnJhZ1BsYXlpbmcgPSB0aGlzLmZyYWdQbGF5aW5nO1xuICAgICAgICBjb25zdCBmcmFnQ3VycmVudExldmVsID0gZnJhZ1BsYXlpbmdDdXJyZW50LmxldmVsO1xuICAgICAgICBpZiAoIWZyYWdQbGF5aW5nIHx8IGZyYWdQbGF5aW5nQ3VycmVudC5zbiAhPT0gZnJhZ1BsYXlpbmcuc24gfHwgZnJhZ1BsYXlpbmcubGV2ZWwgIT09IGZyYWdDdXJyZW50TGV2ZWwpIHtcbiAgICAgICAgICB0aGlzLmZyYWdQbGF5aW5nID0gZnJhZ1BsYXlpbmdDdXJyZW50O1xuICAgICAgICAgIHRoaXMuaGxzLnRyaWdnZXIoRXZlbnRzLkZSQUdfQ0hBTkdFRCwge1xuICAgICAgICAgICAgZnJhZzogZnJhZ1BsYXlpbmdDdXJyZW50XG4gICAgICAgICAgfSk7XG4gICAgICAgICAgaWYgKCFmcmFnUGxheWluZyB8fCBmcmFnUGxheWluZy5sZXZlbCAhPT0gZnJhZ0N1cnJlbnRMZXZlbCkge1xuICAgICAgICAgICAgdGhpcy5obHMudHJpZ2dlcihFdmVudHMuTEVWRUxfU1dJVENIRUQsIHtcbiAgICAgICAgICAgICAgbGV2ZWw6IGZyYWdDdXJyZW50TGV2ZWxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICBnZXQgbmV4dExldmVsKCkge1xuICAgIGNvbnN0IGZyYWcgPSB0aGlzLm5leHRCdWZmZXJlZEZyYWc7XG4gICAgaWYgKGZyYWcpIHtcbiAgICAgIHJldHVybiBmcmFnLmxldmVsO1xuICAgIH1cbiAgICByZXR1cm4gLTE7XG4gIH1cbiAgZ2V0IGN1cnJlbnRGcmFnKCkge1xuICAgIGNvbnN0IG1lZGlhID0gdGhpcy5tZWRpYTtcbiAgICBpZiAobWVkaWEpIHtcbiAgICAgIHJldHVybiB0aGlzLmZyYWdQbGF5aW5nIHx8IHRoaXMuZ2V0QXBwZW5kZWRGcmFnKG1lZGlhLmN1cnJlbnRUaW1lKTtcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgZ2V0IGN1cnJlbnRQcm9ncmFtRGF0ZVRpbWUoKSB7XG4gICAgY29uc3QgbWVkaWEgPSB0aGlzLm1lZGlhO1xuICAgIGlmIChtZWRpYSkge1xuICAgICAgY29uc3QgY3VycmVudFRpbWUgPSBtZWRpYS5jdXJyZW50VGltZTtcbiAgICAgIGNvbnN0IGZyYWcgPSB0aGlzLmN1cnJlbnRGcmFnO1xuICAgICAgaWYgKGZyYWcgJiYgaXNGaW5pdGVOdW1iZXIoY3VycmVudFRpbWUpICYmIGlzRmluaXRlTnVtYmVyKGZyYWcucHJvZ3JhbURhdGVUaW1lKSkge1xuICAgICAgICBjb25zdCBlcG9jTXMgPSBmcmFnLnByb2dyYW1EYXRlVGltZSArIChjdXJyZW50VGltZSAtIGZyYWcuc3RhcnQpICogMTAwMDtcbiAgICAgICAgcmV0dXJuIG5ldyBEYXRlKGVwb2NNcyk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGdldCBjdXJyZW50TGV2ZWwoKSB7XG4gICAgY29uc3QgZnJhZyA9IHRoaXMuY3VycmVudEZyYWc7XG4gICAgaWYgKGZyYWcpIHtcbiAgICAgIHJldHVybiBmcmFnLmxldmVsO1xuICAgIH1cbiAgICByZXR1cm4gLTE7XG4gIH1cbiAgZ2V0IG5leHRCdWZmZXJlZEZyYWcoKSB7XG4gICAgY29uc3QgZnJhZyA9IHRoaXMuY3VycmVudEZyYWc7XG4gICAgaWYgKGZyYWcpIHtcbiAgICAgIHJldHVybiB0aGlzLmZvbGxvd2luZ0J1ZmZlcmVkRnJhZyhmcmFnKTtcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgZ2V0IGZvcmNlU3RhcnRMb2FkKCkge1xuICAgIHJldHVybiB0aGlzLl9mb3JjZVN0YXJ0TG9hZDtcbiAgfVxufVxuXG4vKipcbiAqIFRoZSBgSGxzYCBjbGFzcyBpcyB0aGUgY29yZSBvZiB0aGUgSExTLmpzIGxpYnJhcnkgdXNlZCB0byBpbnN0YW50aWF0ZSBwbGF5ZXIgaW5zdGFuY2VzLlxuICogQHB1YmxpY1xuICovXG5jbGFzcyBIbHMge1xuICAvKipcbiAgICogR2V0IHRoZSB2aWRlby1kZXYvaGxzLmpzIHBhY2thZ2UgdmVyc2lvbi5cbiAgICovXG4gIHN0YXRpYyBnZXQgdmVyc2lvbigpIHtcbiAgICByZXR1cm4gXCIxLjUuMTVcIjtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiB0aGUgcmVxdWlyZWQgTWVkaWFTb3VyY2UgRXh0ZW5zaW9ucyBhcmUgYXZhaWxhYmxlLlxuICAgKi9cbiAgc3RhdGljIGlzTVNFU3VwcG9ydGVkKCkge1xuICAgIHJldHVybiBpc01TRVN1cHBvcnRlZCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIE1lZGlhU291cmNlIEV4dGVuc2lvbnMgYXJlIGF2YWlsYWJsZSBhbmQgaXNUeXBlU3VwcG9ydGVkIGNoZWNrcyBwYXNzIGZvciBhbnkgYmFzZWxpbmUgY29kZWNzLlxuICAgKi9cbiAgc3RhdGljIGlzU3VwcG9ydGVkKCkge1xuICAgIHJldHVybiBpc1N1cHBvcnRlZCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgTWVkaWFTb3VyY2UgZ2xvYmFsIHVzZWQgZm9yIE1TRSBwbGF5YmFjayAoTWFuYWdlZE1lZGlhU291cmNlLCBNZWRpYVNvdXJjZSwgb3IgV2ViS2l0TWVkaWFTb3VyY2UpLlxuICAgKi9cbiAgc3RhdGljIGdldE1lZGlhU291cmNlKCkge1xuICAgIHJldHVybiBnZXRNZWRpYVNvdXJjZSgpO1xuICB9XG4gIHN0YXRpYyBnZXQgRXZlbnRzKCkge1xuICAgIHJldHVybiBFdmVudHM7XG4gIH1cbiAgc3RhdGljIGdldCBFcnJvclR5cGVzKCkge1xuICAgIHJldHVybiBFcnJvclR5cGVzO1xuICB9XG4gIHN0YXRpYyBnZXQgRXJyb3JEZXRhaWxzKCkge1xuICAgIHJldHVybiBFcnJvckRldGFpbHM7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBkZWZhdWx0IGNvbmZpZ3VyYXRpb24gYXBwbGllZCB0byBuZXcgaW5zdGFuY2VzLlxuICAgKi9cbiAgc3RhdGljIGdldCBEZWZhdWx0Q29uZmlnKCkge1xuICAgIGlmICghSGxzLmRlZmF1bHRDb25maWcpIHtcbiAgICAgIHJldHVybiBobHNEZWZhdWx0Q29uZmlnO1xuICAgIH1cbiAgICByZXR1cm4gSGxzLmRlZmF1bHRDb25maWc7XG4gIH1cblxuICAvKipcbiAgICogUmVwbGFjZSB0aGUgZGVmYXVsdCBjb25maWd1cmF0aW9uIGFwcGxpZWQgdG8gbmV3IGluc3RhbmNlcy5cbiAgICovXG4gIHN0YXRpYyBzZXQgRGVmYXVsdENvbmZpZyhkZWZhdWx0Q29uZmlnKSB7XG4gICAgSGxzLmRlZmF1bHRDb25maWcgPSBkZWZhdWx0Q29uZmlnO1xuICB9XG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYW4gaW5zdGFuY2Ugb2YgYW4gSExTIGNsaWVudCB0aGF0IGNhbiBhdHRhY2ggdG8gZXhhY3RseSBvbmUgYEhUTUxNZWRpYUVsZW1lbnRgLlxuICAgKiBAcGFyYW0gdXNlckNvbmZpZyAtIENvbmZpZ3VyYXRpb24gb3B0aW9ucyBhcHBsaWVkIG92ZXIgYEhscy5EZWZhdWx0Q29uZmlnYFxuICAgKi9cbiAgY29uc3RydWN0b3IodXNlckNvbmZpZyA9IHt9KSB7XG4gICAgLyoqXG4gICAgICogVGhlIHJ1bnRpbWUgY29uZmlndXJhdGlvbiB1c2VkIGJ5IHRoZSBwbGF5ZXIuIEF0IGluc3RhbnRpYXRpb24gdGhpcyBpcyBjb21iaW5hdGlvbiBvZiBgaGxzLnVzZXJDb25maWdgIG1lcmdlZCBvdmVyIGBIbHMuRGVmYXVsdENvbmZpZ2AuXG4gICAgICovXG4gICAgdGhpcy5jb25maWcgPSB2b2lkIDA7XG4gICAgLyoqXG4gICAgICogVGhlIGNvbmZpZ3VyYXRpb24gb2JqZWN0IHByb3ZpZGVkIG9uIHBsYXllciBpbnN0YW50aWF0aW9uLlxuICAgICAqL1xuICAgIHRoaXMudXNlckNvbmZpZyA9IHZvaWQgMDtcbiAgICB0aGlzLmNvcmVDb21wb25lbnRzID0gdm9pZCAwO1xuICAgIHRoaXMubmV0d29ya0NvbnRyb2xsZXJzID0gdm9pZCAwO1xuICAgIHRoaXMuc3RhcnRlZCA9IGZhbHNlO1xuICAgIHRoaXMuX2VtaXR0ZXIgPSBuZXcgRXZlbnRFbWl0dGVyKCk7XG4gICAgdGhpcy5fYXV0b0xldmVsQ2FwcGluZyA9IC0xO1xuICAgIHRoaXMuX21heEhkY3BMZXZlbCA9IG51bGw7XG4gICAgdGhpcy5hYnJDb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMuYnVmZmVyQ29udHJvbGxlciA9IHZvaWQgMDtcbiAgICB0aGlzLmNhcExldmVsQ29udHJvbGxlciA9IHZvaWQgMDtcbiAgICB0aGlzLmxhdGVuY3lDb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMubGV2ZWxDb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMuc3RyZWFtQ29udHJvbGxlciA9IHZvaWQgMDtcbiAgICB0aGlzLmF1ZGlvVHJhY2tDb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMuc3VidGl0bGVUcmFja0NvbnRyb2xsZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5lbWVDb250cm9sbGVyID0gdm9pZCAwO1xuICAgIHRoaXMuY21jZENvbnRyb2xsZXIgPSB2b2lkIDA7XG4gICAgdGhpcy5fbWVkaWEgPSBudWxsO1xuICAgIHRoaXMudXJsID0gbnVsbDtcbiAgICB0aGlzLnRyaWdnZXJpbmdFeGNlcHRpb24gPSB2b2lkIDA7XG4gICAgZW5hYmxlTG9ncyh1c2VyQ29uZmlnLmRlYnVnIHx8IGZhbHNlLCAnSGxzIGluc3RhbmNlJyk7XG4gICAgY29uc3QgY29uZmlnID0gdGhpcy5jb25maWcgPSBtZXJnZUNvbmZpZyhIbHMuRGVmYXVsdENvbmZpZywgdXNlckNvbmZpZyk7XG4gICAgdGhpcy51c2VyQ29uZmlnID0gdXNlckNvbmZpZztcbiAgICBpZiAoY29uZmlnLnByb2dyZXNzaXZlKSB7XG4gICAgICBlbmFibGVTdHJlYW1pbmdNb2RlKGNvbmZpZyk7XG4gICAgfVxuXG4gICAgLy8gY29yZSBjb250cm9sbGVycyBhbmQgbmV0d29yayBsb2FkZXJzXG4gICAgY29uc3Qge1xuICAgICAgYWJyQ29udHJvbGxlcjogQ29uZmlnQWJyQ29udHJvbGxlcixcbiAgICAgIGJ1ZmZlckNvbnRyb2xsZXI6IENvbmZpZ0J1ZmZlckNvbnRyb2xsZXIsXG4gICAgICBjYXBMZXZlbENvbnRyb2xsZXI6IENvbmZpZ0NhcExldmVsQ29udHJvbGxlcixcbiAgICAgIGVycm9yQ29udHJvbGxlcjogQ29uZmlnRXJyb3JDb250cm9sbGVyLFxuICAgICAgZnBzQ29udHJvbGxlcjogQ29uZmlnRnBzQ29udHJvbGxlclxuICAgIH0gPSBjb25maWc7XG4gICAgY29uc3QgZXJyb3JDb250cm9sbGVyID0gbmV3IENvbmZpZ0Vycm9yQ29udHJvbGxlcih0aGlzKTtcbiAgICBjb25zdCBhYnJDb250cm9sbGVyID0gdGhpcy5hYnJDb250cm9sbGVyID0gbmV3IENvbmZpZ0FickNvbnRyb2xsZXIodGhpcyk7XG4gICAgY29uc3QgYnVmZmVyQ29udHJvbGxlciA9IHRoaXMuYnVmZmVyQ29udHJvbGxlciA9IG5ldyBDb25maWdCdWZmZXJDb250cm9sbGVyKHRoaXMpO1xuICAgIGNvbnN0IGNhcExldmVsQ29udHJvbGxlciA9IHRoaXMuY2FwTGV2ZWxDb250cm9sbGVyID0gbmV3IENvbmZpZ0NhcExldmVsQ29udHJvbGxlcih0aGlzKTtcbiAgICBjb25zdCBmcHNDb250cm9sbGVyID0gbmV3IENvbmZpZ0Zwc0NvbnRyb2xsZXIodGhpcyk7XG4gICAgY29uc3QgcGxheUxpc3RMb2FkZXIgPSBuZXcgUGxheWxpc3RMb2FkZXIodGhpcyk7XG4gICAgY29uc3QgaWQzVHJhY2tDb250cm9sbGVyID0gbmV3IElEM1RyYWNrQ29udHJvbGxlcih0aGlzKTtcbiAgICBjb25zdCBDb25maWdDb250ZW50U3RlZXJpbmdDb250cm9sbGVyID0gY29uZmlnLmNvbnRlbnRTdGVlcmluZ0NvbnRyb2xsZXI7XG4gICAgLy8gQ29uZW50U3RlZXJpbmdDb250cm9sbGVyIGlzIGRlZmluZWQgYmVmb3JlIExldmVsQ29udHJvbGxlciB0byByZWNlaXZlIE11bHRpdmFyaWFudCBQbGF5bGlzdCBldmVudHMgZmlyc3RcbiAgICBjb25zdCBjb250ZW50U3RlZXJpbmcgPSBDb25maWdDb250ZW50U3RlZXJpbmdDb250cm9sbGVyID8gbmV3IENvbmZpZ0NvbnRlbnRTdGVlcmluZ0NvbnRyb2xsZXIodGhpcykgOiBudWxsO1xuICAgIGNvbnN0IGxldmVsQ29udHJvbGxlciA9IHRoaXMubGV2ZWxDb250cm9sbGVyID0gbmV3IExldmVsQ29udHJvbGxlcih0aGlzLCBjb250ZW50U3RlZXJpbmcpO1xuICAgIC8vIEZyYWdtZW50VHJhY2tlciBtdXN0IGJlIGRlZmluZWQgYmVmb3JlIFN0cmVhbUNvbnRyb2xsZXIgYmVjYXVzZSB0aGUgb3JkZXIgb2YgZXZlbnQgaGFuZGxpbmcgaXMgaW1wb3J0YW50XG4gICAgY29uc3QgZnJhZ21lbnRUcmFja2VyID0gbmV3IEZyYWdtZW50VHJhY2tlcih0aGlzKTtcbiAgICBjb25zdCBrZXlMb2FkZXIgPSBuZXcgS2V5TG9hZGVyKHRoaXMuY29uZmlnKTtcbiAgICBjb25zdCBzdHJlYW1Db250cm9sbGVyID0gdGhpcy5zdHJlYW1Db250cm9sbGVyID0gbmV3IFN0cmVhbUNvbnRyb2xsZXIodGhpcywgZnJhZ21lbnRUcmFja2VyLCBrZXlMb2FkZXIpO1xuXG4gICAgLy8gQ2FwIGxldmVsIGNvbnRyb2xsZXIgdXNlcyBzdHJlYW1Db250cm9sbGVyIHRvIGZsdXNoIHRoZSBidWZmZXJcbiAgICBjYXBMZXZlbENvbnRyb2xsZXIuc2V0U3RyZWFtQ29udHJvbGxlcihzdHJlYW1Db250cm9sbGVyKTtcbiAgICAvLyBmcHNDb250cm9sbGVyIHVzZXMgc3RyZWFtQ29udHJvbGxlciB0byBzd2l0Y2ggd2hlbiBmcmFtZXMgYXJlIGJlaW5nIGRyb3BwZWRcbiAgICBmcHNDb250cm9sbGVyLnNldFN0cmVhbUNvbnRyb2xsZXIoc3RyZWFtQ29udHJvbGxlcik7XG4gICAgY29uc3QgbmV0d29ya0NvbnRyb2xsZXJzID0gW3BsYXlMaXN0TG9hZGVyLCBsZXZlbENvbnRyb2xsZXIsIHN0cmVhbUNvbnRyb2xsZXJdO1xuICAgIGlmIChjb250ZW50U3RlZXJpbmcpIHtcbiAgICAgIG5ldHdvcmtDb250cm9sbGVycy5zcGxpY2UoMSwgMCwgY29udGVudFN0ZWVyaW5nKTtcbiAgICB9XG4gICAgdGhpcy5uZXR3b3JrQ29udHJvbGxlcnMgPSBuZXR3b3JrQ29udHJvbGxlcnM7XG4gICAgY29uc3QgY29yZUNvbXBvbmVudHMgPSBbYWJyQ29udHJvbGxlciwgYnVmZmVyQ29udHJvbGxlciwgY2FwTGV2ZWxDb250cm9sbGVyLCBmcHNDb250cm9sbGVyLCBpZDNUcmFja0NvbnRyb2xsZXIsIGZyYWdtZW50VHJhY2tlcl07XG4gICAgdGhpcy5hdWRpb1RyYWNrQ29udHJvbGxlciA9IHRoaXMuY3JlYXRlQ29udHJvbGxlcihjb25maWcuYXVkaW9UcmFja0NvbnRyb2xsZXIsIG5ldHdvcmtDb250cm9sbGVycyk7XG4gICAgY29uc3QgQXVkaW9TdHJlYW1Db250cm9sbGVyQ2xhc3MgPSBjb25maWcuYXVkaW9TdHJlYW1Db250cm9sbGVyO1xuICAgIGlmIChBdWRpb1N0cmVhbUNvbnRyb2xsZXJDbGFzcykge1xuICAgICAgbmV0d29ya0NvbnRyb2xsZXJzLnB1c2gobmV3IEF1ZGlvU3RyZWFtQ29udHJvbGxlckNsYXNzKHRoaXMsIGZyYWdtZW50VHJhY2tlciwga2V5TG9hZGVyKSk7XG4gICAgfVxuICAgIC8vIHN1YnRpdGxlVHJhY2tDb250cm9sbGVyIG11c3QgYmUgZGVmaW5lZCBiZWZvcmUgc3VidGl0bGVTdHJlYW1Db250cm9sbGVyIGJlY2F1c2UgdGhlIG9yZGVyIG9mIGV2ZW50IGhhbmRsaW5nIGlzIGltcG9ydGFudFxuICAgIHRoaXMuc3VidGl0bGVUcmFja0NvbnRyb2xsZXIgPSB0aGlzLmNyZWF0ZUNvbnRyb2xsZXIoY29uZmlnLnN1YnRpdGxlVHJhY2tDb250cm9sbGVyLCBuZXR3b3JrQ29udHJvbGxlcnMpO1xuICAgIGNvbnN0IFN1YnRpdGxlU3RyZWFtQ29udHJvbGxlckNsYXNzID0gY29uZmlnLnN1YnRpdGxlU3RyZWFtQ29udHJvbGxlcjtcbiAgICBpZiAoU3VidGl0bGVTdHJlYW1Db250cm9sbGVyQ2xhc3MpIHtcbiAgICAgIG5ldHdvcmtDb250cm9sbGVycy5wdXNoKG5ldyBTdWJ0aXRsZVN0cmVhbUNvbnRyb2xsZXJDbGFzcyh0aGlzLCBmcmFnbWVudFRyYWNrZXIsIGtleUxvYWRlcikpO1xuICAgIH1cbiAgICB0aGlzLmNyZWF0ZUNvbnRyb2xsZXIoY29uZmlnLnRpbWVsaW5lQ29udHJvbGxlciwgY29yZUNvbXBvbmVudHMpO1xuICAgIGtleUxvYWRlci5lbWVDb250cm9sbGVyID0gdGhpcy5lbWVDb250cm9sbGVyID0gdGhpcy5jcmVhdGVDb250cm9sbGVyKGNvbmZpZy5lbWVDb250cm9sbGVyLCBjb3JlQ29tcG9uZW50cyk7XG4gICAgdGhpcy5jbWNkQ29udHJvbGxlciA9IHRoaXMuY3JlYXRlQ29udHJvbGxlcihjb25maWcuY21jZENvbnRyb2xsZXIsIGNvcmVDb21wb25lbnRzKTtcbiAgICB0aGlzLmxhdGVuY3lDb250cm9sbGVyID0gdGhpcy5jcmVhdGVDb250cm9sbGVyKExhdGVuY3lDb250cm9sbGVyLCBjb3JlQ29tcG9uZW50cyk7XG4gICAgdGhpcy5jb3JlQ29tcG9uZW50cyA9IGNvcmVDb21wb25lbnRzO1xuXG4gICAgLy8gRXJyb3IgY29udHJvbGxlciBoYW5kbGVzIGVycm9ycyBiZWZvcmUgYW5kIGFmdGVyIGFsbCBvdGhlciBjb250cm9sbGVyc1xuICAgIC8vIFRoaXMgbGlzdGVuZXIgd2lsbCBiZSBpbnZva2VkIGFmdGVyIGFsbCBvdGhlciBjb250cm9sbGVycyBlcnJvciBsaXN0ZW5lcnNcbiAgICBuZXR3b3JrQ29udHJvbGxlcnMucHVzaChlcnJvckNvbnRyb2xsZXIpO1xuICAgIGNvbnN0IG9uRXJyb3JPdXQgPSBlcnJvckNvbnRyb2xsZXIub25FcnJvck91dDtcbiAgICBpZiAodHlwZW9mIG9uRXJyb3JPdXQgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIHRoaXMub24oRXZlbnRzLkVSUk9SLCBvbkVycm9yT3V0LCBlcnJvckNvbnRyb2xsZXIpO1xuICAgIH1cbiAgfVxuICBjcmVhdGVDb250cm9sbGVyKENvbnRyb2xsZXJDbGFzcywgY29tcG9uZW50cykge1xuICAgIGlmIChDb250cm9sbGVyQ2xhc3MpIHtcbiAgICAgIGNvbnN0IGNvbnRyb2xsZXJJbnN0YW5jZSA9IG5ldyBDb250cm9sbGVyQ2xhc3ModGhpcyk7XG4gICAgICBpZiAoY29tcG9uZW50cykge1xuICAgICAgICBjb21wb25lbnRzLnB1c2goY29udHJvbGxlckluc3RhbmNlKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBjb250cm9sbGVySW5zdGFuY2U7XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgLy8gRGVsZWdhdGUgdGhlIEV2ZW50RW1pdHRlciB0aHJvdWdoIHRoZSBwdWJsaWMgQVBJIG9mIEhscy5qc1xuICBvbihldmVudCwgbGlzdGVuZXIsIGNvbnRleHQgPSB0aGlzKSB7XG4gICAgdGhpcy5fZW1pdHRlci5vbihldmVudCwgbGlzdGVuZXIsIGNvbnRleHQpO1xuICB9XG4gIG9uY2UoZXZlbnQsIGxpc3RlbmVyLCBjb250ZXh0ID0gdGhpcykge1xuICAgIHRoaXMuX2VtaXR0ZXIub25jZShldmVudCwgbGlzdGVuZXIsIGNvbnRleHQpO1xuICB9XG4gIHJlbW92ZUFsbExpc3RlbmVycyhldmVudCkge1xuICAgIHRoaXMuX2VtaXR0ZXIucmVtb3ZlQWxsTGlzdGVuZXJzKGV2ZW50KTtcbiAgfVxuICBvZmYoZXZlbnQsIGxpc3RlbmVyLCBjb250ZXh0ID0gdGhpcywgb25jZSkge1xuICAgIHRoaXMuX2VtaXR0ZXIub2ZmKGV2ZW50LCBsaXN0ZW5lciwgY29udGV4dCwgb25jZSk7XG4gIH1cbiAgbGlzdGVuZXJzKGV2ZW50KSB7XG4gICAgcmV0dXJuIHRoaXMuX2VtaXR0ZXIubGlzdGVuZXJzKGV2ZW50KTtcbiAgfVxuICBlbWl0KGV2ZW50LCBuYW1lLCBldmVudE9iamVjdCkge1xuICAgIHJldHVybiB0aGlzLl9lbWl0dGVyLmVtaXQoZXZlbnQsIG5hbWUsIGV2ZW50T2JqZWN0KTtcbiAgfVxuICB0cmlnZ2VyKGV2ZW50LCBldmVudE9iamVjdCkge1xuICAgIGlmICh0aGlzLmNvbmZpZy5kZWJ1Zykge1xuICAgICAgcmV0dXJuIHRoaXMuZW1pdChldmVudCwgZXZlbnQsIGV2ZW50T2JqZWN0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZW1pdChldmVudCwgZXZlbnQsIGV2ZW50T2JqZWN0KTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignQW4gaW50ZXJuYWwgZXJyb3IgaGFwcGVuZWQgd2hpbGUgaGFuZGxpbmcgZXZlbnQgJyArIGV2ZW50ICsgJy4gRXJyb3IgbWVzc2FnZTogXCInICsgZXJyb3IubWVzc2FnZSArICdcIi4gSGVyZSBpcyBhIHN0YWNrdHJhY2U6JywgZXJyb3IpO1xuICAgICAgICAvLyBQcmV2ZW50IHJlY3Vyc2lvbiBpbiBlcnJvciBldmVudCBoYW5kbGVycyB0aGF0IHRocm93ICM1NDk3XG4gICAgICAgIGlmICghdGhpcy50cmlnZ2VyaW5nRXhjZXB0aW9uKSB7XG4gICAgICAgICAgdGhpcy50cmlnZ2VyaW5nRXhjZXB0aW9uID0gdHJ1ZTtcbiAgICAgICAgICBjb25zdCBmYXRhbCA9IGV2ZW50ID09PSBFdmVudHMuRVJST1I7XG4gICAgICAgICAgdGhpcy50cmlnZ2VyKEV2ZW50cy5FUlJPUiwge1xuICAgICAgICAgICAgdHlwZTogRXJyb3JUeXBlcy5PVEhFUl9FUlJPUixcbiAgICAgICAgICAgIGRldGFpbHM6IEVycm9yRGV0YWlscy5JTlRFUk5BTF9FWENFUFRJT04sXG4gICAgICAgICAgICBmYXRhbCxcbiAgICAgICAgICAgIGV2ZW50LFxuICAgICAgICAgICAgZXJyb3JcbiAgICAgICAgICB9KTtcbiAgICAgICAgICB0aGlzLnRyaWdnZXJpbmdFeGNlcHRpb24gPSBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbiAgbGlzdGVuZXJDb3VudChldmVudCkge1xuICAgIHJldHVybiB0aGlzLl9lbWl0dGVyLmxpc3RlbmVyQ291bnQoZXZlbnQpO1xuICB9XG5cbiAgLyoqXG4gICAqIERpc3Bvc2Ugb2YgdGhlIGluc3RhbmNlXG4gICAqL1xuICBkZXN0cm95KCkge1xuICAgIGxvZ2dlci5sb2coJ2Rlc3Ryb3knKTtcbiAgICB0aGlzLnRyaWdnZXIoRXZlbnRzLkRFU1RST1lJTkcsIHVuZGVmaW5lZCk7XG4gICAgdGhpcy5kZXRhY2hNZWRpYSgpO1xuICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKCk7XG4gICAgdGhpcy5fYXV0b0xldmVsQ2FwcGluZyA9IC0xO1xuICAgIHRoaXMudXJsID0gbnVsbDtcbiAgICB0aGlzLm5ldHdvcmtDb250cm9sbGVycy5mb3JFYWNoKGNvbXBvbmVudCA9PiBjb21wb25lbnQuZGVzdHJveSgpKTtcbiAgICB0aGlzLm5ldHdvcmtDb250cm9sbGVycy5sZW5ndGggPSAwO1xuICAgIHRoaXMuY29yZUNvbXBvbmVudHMuZm9yRWFjaChjb21wb25lbnQgPT4gY29tcG9uZW50LmRlc3Ryb3koKSk7XG4gICAgdGhpcy5jb3JlQ29tcG9uZW50cy5sZW5ndGggPSAwO1xuICAgIC8vIFJlbW92ZSBhbnkgcmVmZXJlbmNlcyB0aGF0IGNvdWxkIGJlIGhlbGQgaW4gY29uZmlnIG9wdGlvbnMgb3IgY2FsbGJhY2tzXG4gICAgY29uc3QgY29uZmlnID0gdGhpcy5jb25maWc7XG4gICAgY29uZmlnLnhoclNldHVwID0gY29uZmlnLmZldGNoU2V0dXAgPSB1bmRlZmluZWQ7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRoaXMudXNlckNvbmZpZyA9IG51bGw7XG4gIH1cblxuICAvKipcbiAgICogQXR0YWNoZXMgSGxzLmpzIHRvIGEgbWVkaWEgZWxlbWVudFxuICAgKi9cbiAgYXR0YWNoTWVkaWEobWVkaWEpIHtcbiAgICBsb2dnZXIubG9nKCdhdHRhY2hNZWRpYScpO1xuICAgIHRoaXMuX21lZGlhID0gbWVkaWE7XG4gICAgdGhpcy50cmlnZ2VyKEV2ZW50cy5NRURJQV9BVFRBQ0hJTkcsIHtcbiAgICAgIG1lZGlhOiBtZWRpYVxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIERldGFjaCBIbHMuanMgZnJvbSB0aGUgbWVkaWFcbiAgICovXG4gIGRldGFjaE1lZGlhKCkge1xuICAgIGxvZ2dlci5sb2coJ2RldGFjaE1lZGlhJyk7XG4gICAgdGhpcy50cmlnZ2VyKEV2ZW50cy5NRURJQV9ERVRBQ0hJTkcsIHVuZGVmaW5lZCk7XG4gICAgdGhpcy5fbWVkaWEgPSBudWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldCB0aGUgc291cmNlIFVSTC4gQ2FuIGJlIHJlbGF0aXZlIG9yIGFic29sdXRlLlxuICAgKi9cbiAgbG9hZFNvdXJjZSh1cmwpIHtcbiAgICB0aGlzLnN0b3BMb2FkKCk7XG4gICAgY29uc3QgbWVkaWEgPSB0aGlzLm1lZGlhO1xuICAgIGNvbnN0IGxvYWRlZFNvdXJjZSA9IHRoaXMudXJsO1xuICAgIGNvbnN0IGxvYWRpbmdTb3VyY2UgPSB0aGlzLnVybCA9IHVybFRvb2xraXRFeHBvcnRzLmJ1aWxkQWJzb2x1dGVVUkwoc2VsZi5sb2NhdGlvbi5ocmVmLCB1cmwsIHtcbiAgICAgIGFsd2F5c05vcm1hbGl6ZTogdHJ1ZVxuICAgIH0pO1xuICAgIHRoaXMuX2F1dG9MZXZlbENhcHBpbmcgPSAtMTtcbiAgICB0aGlzLl9tYXhIZGNwTGV2ZWwgPSBudWxsO1xuICAgIGxvZ2dlci5sb2coYGxvYWRTb3VyY2U6JHtsb2FkaW5nU291cmNlfWApO1xuICAgIGlmIChtZWRpYSAmJiBsb2FkZWRTb3VyY2UgJiYgKGxvYWRlZFNvdXJjZSAhPT0gbG9hZGluZ1NvdXJjZSB8fCB0aGlzLmJ1ZmZlckNvbnRyb2xsZXIuaGFzU291cmNlVHlwZXMoKSkpIHtcbiAgICAgIHRoaXMuZGV0YWNoTWVkaWEoKTtcbiAgICAgIHRoaXMuYXR0YWNoTWVkaWEobWVkaWEpO1xuICAgIH1cbiAgICAvLyB3aGVuIGF0dGFjaGluZyB0byBhIHNvdXJjZSBVUkwsIHRyaWdnZXIgYSBwbGF5bGlzdCBsb2FkXG4gICAgdGhpcy50cmlnZ2VyKEV2ZW50cy5NQU5JRkVTVF9MT0FESU5HLCB7XG4gICAgICB1cmw6IHVybFxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0YXJ0IGxvYWRpbmcgZGF0YSBmcm9tIHRoZSBzdHJlYW0gc291cmNlLlxuICAgKiBEZXBlbmRpbmcgb24gZGVmYXVsdCBjb25maWcsIGNsaWVudCBzdGFydHMgbG9hZGluZyBhdXRvbWF0aWNhbGx5IHdoZW4gYSBzb3VyY2UgaXMgc2V0LlxuICAgKlxuICAgKiBAcGFyYW0gc3RhcnRQb3NpdGlvbiAtIFNldCB0aGUgc3RhcnQgcG9zaXRpb24gdG8gc3RyZWFtIGZyb20uXG4gICAqIERlZmF1bHRzIHRvIC0xIChOb25lOiBzdGFydHMgZnJvbSBlYXJsaWVzdCBwb2ludClcbiAgICovXG4gIHN0YXJ0TG9hZChzdGFydFBvc2l0aW9uID0gLTEpIHtcbiAgICBsb2dnZXIubG9nKGBzdGFydExvYWQoJHtzdGFydFBvc2l0aW9ufSlgKTtcbiAgICB0aGlzLnN0YXJ0ZWQgPSB0cnVlO1xuICAgIHRoaXMubmV0d29ya0NvbnRyb2xsZXJzLmZvckVhY2goY29udHJvbGxlciA9PiB7XG4gICAgICBjb250cm9sbGVyLnN0YXJ0TG9hZChzdGFydFBvc2l0aW9uKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdG9wIGxvYWRpbmcgb2YgYW55IHN0cmVhbSBkYXRhLlxuICAgKi9cbiAgc3RvcExvYWQoKSB7XG4gICAgbG9nZ2VyLmxvZygnc3RvcExvYWQnKTtcbiAgICB0aGlzLnN0YXJ0ZWQgPSBmYWxzZTtcbiAgICB0aGlzLm5ldHdvcmtDb250cm9sbGVycy5mb3JFYWNoKGNvbnRyb2xsZXIgPT4ge1xuICAgICAgY29udHJvbGxlci5zdG9wTG9hZCgpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlc3VtZXMgc3RyZWFtIGNvbnRyb2xsZXIgc2VnbWVudCBsb2FkaW5nIGlmIHByZXZpb3VzbHkgc3RhcnRlZC5cbiAgICovXG4gIHJlc3VtZUJ1ZmZlcmluZygpIHtcbiAgICBpZiAodGhpcy5zdGFydGVkKSB7XG4gICAgICB0aGlzLm5ldHdvcmtDb250cm9sbGVycy5mb3JFYWNoKGNvbnRyb2xsZXIgPT4ge1xuICAgICAgICBpZiAoJ2ZyYWdtZW50TG9hZGVyJyBpbiBjb250cm9sbGVyKSB7XG4gICAgICAgICAgY29udHJvbGxlci5zdGFydExvYWQoLTEpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU3RvcHMgc3RyZWFtIGNvbnRyb2xsZXIgc2VnbWVudCBsb2FkaW5nIHdpdGhvdXQgY2hhbmdpbmcgJ3N0YXJ0ZWQnIHN0YXRlIGxpa2Ugc3RvcExvYWQoKS5cbiAgICogVGhpcyBhbGxvd3MgZm9yIG1lZGlhIGJ1ZmZlcmluZyB0byBiZSBwYXVzZWQgd2l0aG91dCBpbnRlcnVwdGluZyBwbGF5bGlzdCBsb2FkaW5nLlxuICAgKi9cbiAgcGF1c2VCdWZmZXJpbmcoKSB7XG4gICAgdGhpcy5uZXR3b3JrQ29udHJvbGxlcnMuZm9yRWFjaChjb250cm9sbGVyID0+IHtcbiAgICAgIGlmICgnZnJhZ21lbnRMb2FkZXInIGluIGNvbnRyb2xsZXIpIHtcbiAgICAgICAgY29udHJvbGxlci5zdG9wTG9hZCgpO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFN3YXAgdGhyb3VnaCBwb3NzaWJsZSBhdWRpbyBjb2RlY3MgaW4gdGhlIHN0cmVhbSAoZm9yIGV4YW1wbGUgdG8gc3dpdGNoIGZyb20gc3RlcmVvIHRvIDUuMSlcbiAgICovXG4gIHN3YXBBdWRpb0NvZGVjKCkge1xuICAgIGxvZ2dlci5sb2coJ3N3YXBBdWRpb0NvZGVjJyk7XG4gICAgdGhpcy5zdHJlYW1Db250cm9sbGVyLnN3YXBBdWRpb0NvZGVjKCk7XG4gIH1cblxuICAvKipcbiAgICogV2hlbiB0aGUgbWVkaWEtZWxlbWVudCBmYWlscywgdGhpcyBhbGxvd3MgdG8gZGV0YWNoIGFuZCB0aGVuIHJlLWF0dGFjaCBpdFxuICAgKiBhcyBvbmUgY2FsbCAoY29udmVuaWVuY2UgbWV0aG9kKS5cbiAgICpcbiAgICogQXV0b21hdGljIHJlY292ZXJ5IG9mIG1lZGlhLWVycm9ycyBieSB0aGlzIHByb2Nlc3MgaXMgY29uZmlndXJhYmxlLlxuICAgKi9cbiAgcmVjb3Zlck1lZGlhRXJyb3IoKSB7XG4gICAgbG9nZ2VyLmxvZygncmVjb3Zlck1lZGlhRXJyb3InKTtcbiAgICBjb25zdCBtZWRpYSA9IHRoaXMuX21lZGlhO1xuICAgIHRoaXMuZGV0YWNoTWVkaWEoKTtcbiAgICBpZiAobWVkaWEpIHtcbiAgICAgIHRoaXMuYXR0YWNoTWVkaWEobWVkaWEpO1xuICAgIH1cbiAgfVxuICByZW1vdmVMZXZlbChsZXZlbEluZGV4KSB7XG4gICAgdGhpcy5sZXZlbENvbnRyb2xsZXIucmVtb3ZlTGV2ZWwobGV2ZWxJbmRleCk7XG4gIH1cblxuICAvKipcbiAgICogQHJldHVybnMgYW4gYXJyYXkgb2YgbGV2ZWxzICh2YXJpYW50cykgc29ydGVkIGJ5IEhEQ1AtTEVWRUwsIFJFU09MVVRJT04gKGhlaWdodCksIEZSQU1FLVJBVEUsIENPREVDUywgVklERU8tUkFOR0UsIGFuZCBCQU5EV0lEVEhcbiAgICovXG4gIGdldCBsZXZlbHMoKSB7XG4gICAgY29uc3QgbGV2ZWxzID0gdGhpcy5sZXZlbENvbnRyb2xsZXIubGV2ZWxzO1xuICAgIHJldHVybiBsZXZlbHMgPyBsZXZlbHMgOiBbXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbmRleCBvZiBxdWFsaXR5IGxldmVsICh2YXJpYW50KSBjdXJyZW50bHkgcGxheWVkXG4gICAqL1xuICBnZXQgY3VycmVudExldmVsKCkge1xuICAgIHJldHVybiB0aGlzLnN0cmVhbUNvbnRyb2xsZXIuY3VycmVudExldmVsO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldCBxdWFsaXR5IGxldmVsIGluZGV4IGltbWVkaWF0ZWx5LiBUaGlzIHdpbGwgZmx1c2ggdGhlIGN1cnJlbnQgYnVmZmVyIHRvIHJlcGxhY2UgdGhlIHF1YWxpdHkgYXNhcC4gVGhhdCBtZWFucyBwbGF5YmFjayB3aWxsIGludGVycnVwdCBhdCBsZWFzdCBzaG9ydGx5IHRvIHJlLWJ1ZmZlciBhbmQgcmUtc3luYyBldmVudHVhbGx5LiBTZXQgdG8gLTEgZm9yIGF1dG9tYXRpYyBsZXZlbCBzZWxlY3Rpb24uXG4gICAqL1xuICBzZXQgY3VycmVudExldmVsKG5ld0xldmVsKSB7XG4gICAgbG9nZ2VyLmxvZyhgc2V0IGN1cnJlbnRMZXZlbDoke25ld0xldmVsfWApO1xuICAgIHRoaXMubGV2ZWxDb250cm9sbGVyLm1hbnVhbExldmVsID0gbmV3TGV2ZWw7XG4gICAgdGhpcy5zdHJlYW1Db250cm9sbGVyLmltbWVkaWF0ZUxldmVsU3dpdGNoKCk7XG4gIH1cblxuICAvKipcbiAgICogSW5kZXggb2YgbmV4dCBxdWFsaXR5IGxldmVsIGxvYWRlZCBhcyBzY2hlZHVsZWQgYnkgc3RyZWFtIGNvbnRyb2xsZXIuXG4gICAqL1xuICBnZXQgbmV4dExldmVsKCkge1xuICAgIHJldHVybiB0aGlzLnN0cmVhbUNvbnRyb2xsZXIubmV4dExldmVsO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldCBxdWFsaXR5IGxldmVsIGluZGV4IGZvciBuZXh0IGxvYWRlZCBkYXRhLlxuICAgKiBUaGlzIHdpbGwgc3dpdGNoIHRoZSB2aWRlbyBxdWFsaXR5IGFzYXAsIHdpdGhvdXQgaW50ZXJydXB0aW5nIHBsYXliYWNrLlxuICAgKiBNYXkgYWJvcnQgY3VycmVudCBsb2FkaW5nIG9mIGRhdGEsIGFuZCBmbHVzaCBwYXJ0cyBvZiBidWZmZXIgKG91dHNpZGUgY3VycmVudGx5IHBsYXllZCBmcmFnbWVudCByZWdpb24pLlxuICAgKiBAcGFyYW0gbmV3TGV2ZWwgLSBQYXNzIC0xIGZvciBhdXRvbWF0aWMgbGV2ZWwgc2VsZWN0aW9uXG4gICAqL1xuICBzZXQgbmV4dExldmVsKG5ld0xldmVsKSB7XG4gICAgbG9nZ2VyLmxvZyhgc2V0IG5leHRMZXZlbDoke25ld0xldmVsfWApO1xuICAgIHRoaXMubGV2ZWxDb250cm9sbGVyLm1hbnVhbExldmVsID0gbmV3TGV2ZWw7XG4gICAgdGhpcy5zdHJlYW1Db250cm9sbGVyLm5leHRMZXZlbFN3aXRjaCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybiB0aGUgcXVhbGl0eSBsZXZlbCBvZiB0aGUgY3VycmVudGx5IG9yIGxhc3QgKG9mIG5vbmUgaXMgbG9hZGVkIGN1cnJlbnRseSkgc2VnbWVudFxuICAgKi9cbiAgZ2V0IGxvYWRMZXZlbCgpIHtcbiAgICByZXR1cm4gdGhpcy5sZXZlbENvbnRyb2xsZXIubGV2ZWw7XG4gIH1cblxuICAvKipcbiAgICogU2V0IHF1YWxpdHkgbGV2ZWwgaW5kZXggZm9yIG5leHQgbG9hZGVkIGRhdGEgaW4gYSBjb25zZXJ2YXRpdmUgd2F5LlxuICAgKiBUaGlzIHdpbGwgc3dpdGNoIHRoZSBxdWFsaXR5IHdpdGhvdXQgZmx1c2hpbmcsIGJ1dCBpbnRlcnJ1cHQgY3VycmVudCBsb2FkaW5nLlxuICAgKiBUaHVzIHRoZSBtb21lbnQgd2hlbiB0aGUgcXVhbGl0eSBzd2l0Y2ggd2lsbCBhcHBlYXIgaW4gZWZmZWN0IHdpbGwgb25seSBiZSBhZnRlciB0aGUgYWxyZWFkeSBleGlzdGluZyBidWZmZXIuXG4gICAqIEBwYXJhbSBuZXdMZXZlbCAtIFBhc3MgLTEgZm9yIGF1dG9tYXRpYyBsZXZlbCBzZWxlY3Rpb25cbiAgICovXG4gIHNldCBsb2FkTGV2ZWwobmV3TGV2ZWwpIHtcbiAgICBsb2dnZXIubG9nKGBzZXQgbG9hZExldmVsOiR7bmV3TGV2ZWx9YCk7XG4gICAgdGhpcy5sZXZlbENvbnRyb2xsZXIubWFudWFsTGV2ZWwgPSBuZXdMZXZlbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBnZXQgbmV4dCBxdWFsaXR5IGxldmVsIGxvYWRlZFxuICAgKi9cbiAgZ2V0IG5leHRMb2FkTGV2ZWwoKSB7XG4gICAgcmV0dXJuIHRoaXMubGV2ZWxDb250cm9sbGVyLm5leHRMb2FkTGV2ZWw7XG4gIH1cblxuICAvKipcbiAgICogU2V0IHF1YWxpdHkgbGV2ZWwgb2YgbmV4dCBsb2FkZWQgc2VnbWVudCBpbiBhIGZ1bGx5IFwibm9uLWRlc3RydWN0aXZlXCIgd2F5LlxuICAgKiBTYW1lIGFzIGBsb2FkTGV2ZWxgIGJ1dCB3aWxsIHdhaXQgZm9yIG5leHQgc3dpdGNoICh1bnRpbCBjdXJyZW50IGxvYWRpbmcgaXMgZG9uZSkuXG4gICAqL1xuICBzZXQgbmV4dExvYWRMZXZlbChsZXZlbCkge1xuICAgIHRoaXMubGV2ZWxDb250cm9sbGVyLm5leHRMb2FkTGV2ZWwgPSBsZXZlbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm4gXCJmaXJzdCBsZXZlbFwiOiBsaWtlIGEgZGVmYXVsdCBsZXZlbCwgaWYgbm90IHNldCxcbiAgICogZmFsbHMgYmFjayB0byBpbmRleCBvZiBmaXJzdCBsZXZlbCByZWZlcmVuY2VkIGluIG1hbmlmZXN0XG4gICAqL1xuICBnZXQgZmlyc3RMZXZlbCgpIHtcbiAgICByZXR1cm4gTWF0aC5tYXgodGhpcy5sZXZlbENvbnRyb2xsZXIuZmlyc3RMZXZlbCwgdGhpcy5taW5BdXRvTGV2ZWwpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldHMgXCJmaXJzdC1sZXZlbFwiLCBzZWUgZ2V0dGVyLlxuICAgKi9cbiAgc2V0IGZpcnN0TGV2ZWwobmV3TGV2ZWwpIHtcbiAgICBsb2dnZXIubG9nKGBzZXQgZmlyc3RMZXZlbDoke25ld0xldmVsfWApO1xuICAgIHRoaXMubGV2ZWxDb250cm9sbGVyLmZpcnN0TGV2ZWwgPSBuZXdMZXZlbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm4gdGhlIGRlc2lyZWQgc3RhcnQgbGV2ZWwgZm9yIHRoZSBmaXJzdCBmcmFnbWVudCB0aGF0IHdpbGwgYmUgbG9hZGVkLlxuICAgKiBUaGUgZGVmYXVsdCB2YWx1ZSBvZiAtMSBpbmRpY2F0ZXMgYXV0b21hdGljIHN0YXJ0IGxldmVsIHNlbGVjdGlvbi5cbiAgICogU2V0dGluZyBobHMubmV4dEF1dG9MZXZlbCB3aXRob3V0IHNldHRpbmcgYSBzdGFydExldmVsIHdpbGwgcmVzdWx0IGluXG4gICAqIHRoZSBuZXh0QXV0b0xldmVsIHZhbHVlIGJlaW5nIHVzZWQgZm9yIG9uZSBmcmFnbWVudCBsb2FkLlxuICAgKi9cbiAgZ2V0IHN0YXJ0TGV2ZWwoKSB7XG4gICAgY29uc3Qgc3RhcnRMZXZlbCA9IHRoaXMubGV2ZWxDb250cm9sbGVyLnN0YXJ0TGV2ZWw7XG4gICAgaWYgKHN0YXJ0TGV2ZWwgPT09IC0xICYmIHRoaXMuYWJyQ29udHJvbGxlci5mb3JjZWRBdXRvTGV2ZWwgPiAtMSkge1xuICAgICAgcmV0dXJuIHRoaXMuYWJyQ29udHJvbGxlci5mb3JjZWRBdXRvTGV2ZWw7XG4gICAgfVxuICAgIHJldHVybiBzdGFydExldmVsO1xuICB9XG5cbiAgLyoqXG4gICAqIHNldCAgc3RhcnQgbGV2ZWwgKGxldmVsIG9mIGZpcnN0IGZyYWdtZW50IHRoYXQgd2lsbCBiZSBwbGF5ZWQgYmFjaylcbiAgICogaWYgbm90IG92ZXJyaWRlZCBieSB1c2VyLCBmaXJzdCBsZXZlbCBhcHBlYXJpbmcgaW4gbWFuaWZlc3Qgd2lsbCBiZSB1c2VkIGFzIHN0YXJ0IGxldmVsXG4gICAqIGlmIC0xIDogYXV0b21hdGljIHN0YXJ0IGxldmVsIHNlbGVjdGlvbiwgcGxheWJhY2sgd2lsbCBzdGFydCBmcm9tIGxldmVsIG1hdGNoaW5nIGRvd25sb2FkIGJhbmR3aWR0aFxuICAgKiAoZGV0ZXJtaW5lZCBmcm9tIGRvd25sb2FkIG9mIGZpcnN0IHNlZ21lbnQpXG4gICAqL1xuICBzZXQgc3RhcnRMZXZlbChuZXdMZXZlbCkge1xuICAgIGxvZ2dlci5sb2coYHNldCBzdGFydExldmVsOiR7bmV3TGV2ZWx9YCk7XG4gICAgLy8gaWYgbm90IGluIGF1dG9tYXRpYyBzdGFydCBsZXZlbCBkZXRlY3Rpb24sIGVuc3VyZSBzdGFydExldmVsIGlzIGdyZWF0ZXIgdGhhbiBtaW5BdXRvTGV2ZWxcbiAgICBpZiAobmV3TGV2ZWwgIT09IC0xKSB7XG4gICAgICBuZXdMZXZlbCA9IE1hdGgubWF4KG5ld0xldmVsLCB0aGlzLm1pbkF1dG9MZXZlbCk7XG4gICAgfVxuICAgIHRoaXMubGV2ZWxDb250cm9sbGVyLnN0YXJ0TGV2ZWwgPSBuZXdMZXZlbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBXaGV0aGVyIGxldmVsIGNhcHBpbmcgaXMgZW5hYmxlZC5cbiAgICogRGVmYXVsdCB2YWx1ZSBpcyBzZXQgdmlhIGBjb25maWcuY2FwTGV2ZWxUb1BsYXllclNpemVgLlxuICAgKi9cbiAgZ2V0IGNhcExldmVsVG9QbGF5ZXJTaXplKCkge1xuICAgIHJldHVybiB0aGlzLmNvbmZpZy5jYXBMZXZlbFRvUGxheWVyU2l6ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFbmFibGVzIG9yIGRpc2FibGVzIGxldmVsIGNhcHBpbmcuIElmIGRpc2FibGVkIGFmdGVyIHByZXZpb3VzbHkgZW5hYmxlZCwgYG5leHRMZXZlbFN3aXRjaGAgd2lsbCBiZSBpbW1lZGlhdGVseSBjYWxsZWQuXG4gICAqL1xuICBzZXQgY2FwTGV2ZWxUb1BsYXllclNpemUoc2hvdWxkU3RhcnRDYXBwaW5nKSB7XG4gICAgY29uc3QgbmV3Q2FwTGV2ZWxUb1BsYXllclNpemUgPSAhIXNob3VsZFN0YXJ0Q2FwcGluZztcbiAgICBpZiAobmV3Q2FwTGV2ZWxUb1BsYXllclNpemUgIT09IHRoaXMuY29uZmlnLmNhcExldmVsVG9QbGF5ZXJTaXplKSB7XG4gICAgICBpZiAobmV3Q2FwTGV2ZWxUb1BsYXllclNpemUpIHtcbiAgICAgICAgdGhpcy5jYXBMZXZlbENvbnRyb2xsZXIuc3RhcnRDYXBwaW5nKCk7IC8vIElmIGNhcHBpbmcgb2NjdXJzLCBuZXh0TGV2ZWxTd2l0Y2ggd2lsbCBoYXBwZW4gYmFzZWQgb24gc2l6ZS5cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuY2FwTGV2ZWxDb250cm9sbGVyLnN0b3BDYXBwaW5nKCk7XG4gICAgICAgIHRoaXMuYXV0b0xldmVsQ2FwcGluZyA9IC0xO1xuICAgICAgICB0aGlzLnN0cmVhbUNvbnRyb2xsZXIubmV4dExldmVsU3dpdGNoKCk7IC8vIE5vdyB3ZSdyZSB1bmNhcHBlZCwgZ2V0IHRoZSBuZXh0IGxldmVsIGFzYXAuXG4gICAgICB9XG4gICAgICB0aGlzLmNvbmZpZy5jYXBMZXZlbFRvUGxheWVyU2l6ZSA9IG5ld0NhcExldmVsVG9QbGF5ZXJTaXplO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDYXBwaW5nL21heCBsZXZlbCB2YWx1ZSB0aGF0IHNob3VsZCBiZSB1c2VkIGJ5IGF1dG9tYXRpYyBsZXZlbCBzZWxlY3Rpb24gYWxnb3JpdGhtIChgQUJSQ29udHJvbGxlcmApXG4gICAqL1xuICBnZXQgYXV0b0xldmVsQ2FwcGluZygpIHtcbiAgICByZXR1cm4gdGhpcy5fYXV0b0xldmVsQ2FwcGluZztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBjdXJyZW50IGJhbmR3aWR0aCBlc3RpbWF0ZSBpbiBiaXRzIHBlciBzZWNvbmQsIHdoZW4gYXZhaWxhYmxlLiBPdGhlcndpc2UsIGBOYU5gIGlzIHJldHVybmVkLlxuICAgKi9cbiAgZ2V0IGJhbmR3aWR0aEVzdGltYXRlKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGJ3RXN0aW1hdG9yXG4gICAgfSA9IHRoaXMuYWJyQ29udHJvbGxlcjtcbiAgICBpZiAoIWJ3RXN0aW1hdG9yKSB7XG4gICAgICByZXR1cm4gTmFOO1xuICAgIH1cbiAgICByZXR1cm4gYndFc3RpbWF0b3IuZ2V0RXN0aW1hdGUoKTtcbiAgfVxuICBzZXQgYmFuZHdpZHRoRXN0aW1hdGUoYWJyRXdtYURlZmF1bHRFc3RpbWF0ZSkge1xuICAgIHRoaXMuYWJyQ29udHJvbGxlci5yZXNldEVzdGltYXRvcihhYnJFd21hRGVmYXVsdEVzdGltYXRlKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBnZXQgdGltZSB0byBmaXJzdCBieXRlIGVzdGltYXRlXG4gICAqIEB0eXBlIHtudW1iZXJ9XG4gICAqL1xuICBnZXQgdHRmYkVzdGltYXRlKCkge1xuICAgIGNvbnN0IHtcbiAgICAgIGJ3RXN0aW1hdG9yXG4gICAgfSA9IHRoaXMuYWJyQ29udHJvbGxlcjtcbiAgICBpZiAoIWJ3RXN0aW1hdG9yKSB7XG4gICAgICByZXR1cm4gTmFOO1xuICAgIH1cbiAgICByZXR1cm4gYndFc3RpbWF0b3IuZ2V0RXN0aW1hdGVUVEZCKCk7XG4gIH1cblxuICAvKipcbiAgICogQ2FwcGluZy9tYXggbGV2ZWwgdmFsdWUgdGhhdCBzaG91bGQgYmUgdXNlZCBieSBhdXRvbWF0aWMgbGV2ZWwgc2VsZWN0aW9uIGFsZ29yaXRobSAoYEFCUkNvbnRyb2xsZXJgKVxuICAgKi9cbiAgc2V0IGF1dG9MZXZlbENhcHBpbmcobmV3TGV2ZWwpIHtcbiAgICBpZiAodGhpcy5fYXV0b0xldmVsQ2FwcGluZyAhPT0gbmV3TGV2ZWwpIHtcbiAgICAgIGxvZ2dlci5sb2coYHNldCBhdXRvTGV2ZWxDYXBwaW5nOiR7bmV3TGV2ZWx9YCk7XG4gICAgICB0aGlzLl9hdXRvTGV2ZWxDYXBwaW5nID0gbmV3TGV2ZWw7XG4gICAgICB0aGlzLmxldmVsQ29udHJvbGxlci5jaGVja01heEF1dG9VcGRhdGVkKCk7XG4gICAgfVxuICB9XG4gIGdldCBtYXhIZGNwTGV2ZWwoKSB7XG4gICAgcmV0dXJuIHRoaXMuX21heEhkY3BMZXZlbDtcbiAgfVxuICBzZXQgbWF4SGRjcExldmVsKHZhbHVlKSB7XG4gICAgaWYgKGlzSGRjcExldmVsKHZhbHVlKSAmJiB0aGlzLl9tYXhIZGNwTGV2ZWwgIT09IHZhbHVlKSB7XG4gICAgICB0aGlzLl9tYXhIZGNwTGV2ZWwgPSB2YWx1ZTtcbiAgICAgIHRoaXMubGV2ZWxDb250cm9sbGVyLmNoZWNrTWF4QXV0b1VwZGF0ZWQoKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVHJ1ZSB3aGVuIGF1dG9tYXRpYyBsZXZlbCBzZWxlY3Rpb24gZW5hYmxlZFxuICAgKi9cbiAgZ2V0IGF1dG9MZXZlbEVuYWJsZWQoKSB7XG4gICAgcmV0dXJuIHRoaXMubGV2ZWxDb250cm9sbGVyLm1hbnVhbExldmVsID09PSAtMTtcbiAgfVxuXG4gIC8qKlxuICAgKiBMZXZlbCBzZXQgbWFudWFsbHkgKGlmIGFueSlcbiAgICovXG4gIGdldCBtYW51YWxMZXZlbCgpIHtcbiAgICByZXR1cm4gdGhpcy5sZXZlbENvbnRyb2xsZXIubWFudWFsTGV2ZWw7XG4gIH1cblxuICAvKipcbiAgICogbWluIGxldmVsIHNlbGVjdGFibGUgaW4gYXV0byBtb2RlIGFjY29yZGluZyB0byBjb25maWcubWluQXV0b0JpdHJhdGVcbiAgICovXG4gIGdldCBtaW5BdXRvTGV2ZWwoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbGV2ZWxzLFxuICAgICAgY29uZmlnOiB7XG4gICAgICAgIG1pbkF1dG9CaXRyYXRlXG4gICAgICB9XG4gICAgfSA9IHRoaXM7XG4gICAgaWYgKCFsZXZlbHMpIHJldHVybiAwO1xuICAgIGNvbnN0IGxlbiA9IGxldmVscy5sZW5ndGg7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgaWYgKGxldmVsc1tpXS5tYXhCaXRyYXRlID49IG1pbkF1dG9CaXRyYXRlKSB7XG4gICAgICAgIHJldHVybiBpO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBtYXggbGV2ZWwgc2VsZWN0YWJsZSBpbiBhdXRvIG1vZGUgYWNjb3JkaW5nIHRvIGF1dG9MZXZlbENhcHBpbmdcbiAgICovXG4gIGdldCBtYXhBdXRvTGV2ZWwoKSB7XG4gICAgY29uc3Qge1xuICAgICAgbGV2ZWxzLFxuICAgICAgYXV0b0xldmVsQ2FwcGluZyxcbiAgICAgIG1heEhkY3BMZXZlbFxuICAgIH0gPSB0aGlzO1xuICAgIGxldCBtYXhBdXRvTGV2ZWw7XG4gICAgaWYgKGF1dG9MZXZlbENhcHBpbmcgPT09IC0xICYmIGxldmVscyAhPSBudWxsICYmIGxldmVscy5sZW5ndGgpIHtcbiAgICAgIG1heEF1dG9MZXZlbCA9IGxldmVscy5sZW5ndGggLSAxO1xuICAgIH0gZWxzZSB7XG4gICAgICBtYXhBdXRvTGV2ZWwgPSBhdXRvTGV2ZWxDYXBwaW5nO1xuICAgIH1cbiAgICBpZiAobWF4SGRjcExldmVsKSB7XG4gICAgICBmb3IgKGxldCBpID0gbWF4QXV0b0xldmVsOyBpLS07KSB7XG4gICAgICAgIGNvbnN0IGhkY3BMZXZlbCA9IGxldmVsc1tpXS5hdHRyc1snSERDUC1MRVZFTCddO1xuICAgICAgICBpZiAoaGRjcExldmVsICYmIGhkY3BMZXZlbCA8PSBtYXhIZGNwTGV2ZWwpIHtcbiAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbWF4QXV0b0xldmVsO1xuICB9XG4gIGdldCBmaXJzdEF1dG9MZXZlbCgpIHtcbiAgICByZXR1cm4gdGhpcy5hYnJDb250cm9sbGVyLmZpcnN0QXV0b0xldmVsO1xuICB9XG5cbiAgLyoqXG4gICAqIG5leHQgYXV0b21hdGljYWxseSBzZWxlY3RlZCBxdWFsaXR5IGxldmVsXG4gICAqL1xuICBnZXQgbmV4dEF1dG9MZXZlbCgpIHtcbiAgICByZXR1cm4gdGhpcy5hYnJDb250cm9sbGVyLm5leHRBdXRvTGV2ZWw7XG4gIH1cblxuICAvKipcbiAgICogdGhpcyBzZXR0ZXIgaXMgdXNlZCB0byBmb3JjZSBuZXh0IGF1dG8gbGV2ZWwuXG4gICAqIHRoaXMgaXMgdXNlZnVsIHRvIGZvcmNlIGEgc3dpdGNoIGRvd24gaW4gYXV0byBtb2RlOlxuICAgKiBpbiBjYXNlIG9mIGxvYWQgZXJyb3Igb24gbGV2ZWwgTiwgaGxzLmpzIGNhbiBzZXQgbmV4dEF1dG9MZXZlbCB0byBOLTEgZm9yIGV4YW1wbGUpXG4gICAqIGZvcmNlZCB2YWx1ZSBpcyB2YWxpZCBmb3Igb25lIGZyYWdtZW50LiB1cG9uIHN1Y2Nlc3NmdWwgZnJhZyBsb2FkaW5nIGF0IGZvcmNlZCBsZXZlbCxcbiAgICogdGhpcyB2YWx1ZSB3aWxsIGJlIHJlc2V0dGVkIHRvIC0xIGJ5IEFCUiBjb250cm9sbGVyLlxuICAgKi9cbiAgc2V0IG5leHRBdXRvTGV2ZWwobmV4dExldmVsKSB7XG4gICAgdGhpcy5hYnJDb250cm9sbGVyLm5leHRBdXRvTGV2ZWwgPSBuZXh0TGV2ZWw7XG4gIH1cblxuICAvKipcbiAgICogZ2V0IHRoZSBkYXRldGltZSB2YWx1ZSByZWxhdGl2ZSB0byBtZWRpYS5jdXJyZW50VGltZSBmb3IgdGhlIGFjdGl2ZSBsZXZlbCBQcm9ncmFtIERhdGUgVGltZSBpZiBwcmVzZW50XG4gICAqL1xuICBnZXQgcGxheWluZ0RhdGUoKSB7XG4gICAgcmV0dXJuIHRoaXMuc3RyZWFtQ29udHJvbGxlci5jdXJyZW50UHJvZ3JhbURhdGVUaW1lO1xuICB9XG4gIGdldCBtYWluRm9yd2FyZEJ1ZmZlckluZm8oKSB7XG4gICAgcmV0dXJuIHRoaXMuc3RyZWFtQ29udHJvbGxlci5nZXRNYWluRndkQnVmZmVySW5mbygpO1xuICB9XG5cbiAgLyoqXG4gICAqIEZpbmQgYW5kIHNlbGVjdCB0aGUgYmVzdCBtYXRjaGluZyBhdWRpbyB0cmFjaywgbWFraW5nIGEgbGV2ZWwgc3dpdGNoIHdoZW4gYSBHcm91cCBjaGFuZ2UgaXMgbmVjZXNzYXJ5LlxuICAgKiBVcGRhdGVzIGBobHMuY29uZmlnLmF1ZGlvUHJlZmVyZW5jZWAuIFJldHVybnMgdGhlIHNlbGVjdGVkIHRyYWNrLCBvciBudWxsIHdoZW4gbm8gbWF0Y2hpbmcgdHJhY2sgaXMgZm91bmQuXG4gICAqL1xuICBzZXRBdWRpb09wdGlvbihhdWRpb09wdGlvbikge1xuICAgIHZhciBfdGhpcyRhdWRpb1RyYWNrQ29udHI7XG4gICAgcmV0dXJuIChfdGhpcyRhdWRpb1RyYWNrQ29udHIgPSB0aGlzLmF1ZGlvVHJhY2tDb250cm9sbGVyKSA9PSBudWxsID8gdm9pZCAwIDogX3RoaXMkYXVkaW9UcmFja0NvbnRyLnNldEF1ZGlvT3B0aW9uKGF1ZGlvT3B0aW9uKTtcbiAgfVxuICAvKipcbiAgICogRmluZCBhbmQgc2VsZWN0IHRoZSBiZXN0IG1hdGNoaW5nIHN1YnRpdGxlIHRyYWNrLCBtYWtpbmcgYSBsZXZlbCBzd2l0Y2ggd2hlbiBhIEdyb3VwIGNoYW5nZSBpcyBuZWNlc3NhcnkuXG4gICAqIFVwZGF0ZXMgYGhscy5jb25maWcuc3VidGl0bGVQcmVmZXJlbmNlYC4gUmV0dXJucyB0aGUgc2VsZWN0ZWQgdHJhY2ssIG9yIG51bGwgd2hlbiBubyBtYXRjaGluZyB0cmFjayBpcyBmb3VuZC5cbiAgICovXG4gIHNldFN1YnRpdGxlT3B0aW9uKHN1YnRpdGxlT3B0aW9uKSB7XG4gICAgdmFyIF90aGlzJHN1YnRpdGxlVHJhY2tDbztcbiAgICAoX3RoaXMkc3VidGl0bGVUcmFja0NvID0gdGhpcy5zdWJ0aXRsZVRyYWNrQ29udHJvbGxlcikgPT0gbnVsbCA/IHZvaWQgMCA6IF90aGlzJHN1YnRpdGxlVHJhY2tDby5zZXRTdWJ0aXRsZU9wdGlvbihzdWJ0aXRsZU9wdGlvbik7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBjb21wbGV0ZSBsaXN0IG9mIGF1ZGlvIHRyYWNrcyBhY3Jvc3MgYWxsIG1lZGlhIGdyb3Vwc1xuICAgKi9cbiAgZ2V0IGFsbEF1ZGlvVHJhY2tzKCkge1xuICAgIGNvbnN0IGF1ZGlvVHJhY2tDb250cm9sbGVyID0gdGhpcy5hdWRpb1RyYWNrQ29udHJvbGxlcjtcbiAgICByZXR1cm4gYXVkaW9UcmFja0NvbnRyb2xsZXIgPyBhdWRpb1RyYWNrQ29udHJvbGxlci5hbGxBdWRpb1RyYWNrcyA6IFtdO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgbGlzdCBvZiBzZWxlY3RhYmxlIGF1ZGlvIHRyYWNrc1xuICAgKi9cbiAgZ2V0IGF1ZGlvVHJhY2tzKCkge1xuICAgIGNvbnN0IGF1ZGlvVHJhY2tDb250cm9sbGVyID0gdGhpcy5hdWRpb1RyYWNrQ29udHJvbGxlcjtcbiAgICByZXR1cm4gYXVkaW9UcmFja0NvbnRyb2xsZXIgPyBhdWRpb1RyYWNrQ29udHJvbGxlci5hdWRpb1RyYWNrcyA6IFtdO1xuICB9XG5cbiAgLyoqXG4gICAqIGluZGV4IG9mIHRoZSBzZWxlY3RlZCBhdWRpbyB0cmFjayAoaW5kZXggaW4gYXVkaW8gdHJhY2sgbGlzdHMpXG4gICAqL1xuICBnZXQgYXVkaW9UcmFjaygpIHtcbiAgICBjb25zdCBhdWRpb1RyYWNrQ29udHJvbGxlciA9IHRoaXMuYXVkaW9UcmFja0NvbnRyb2xsZXI7XG4gICAgcmV0dXJuIGF1ZGlvVHJhY2tDb250cm9sbGVyID8gYXVkaW9UcmFja0NvbnRyb2xsZXIuYXVkaW9UcmFjayA6IC0xO1xuICB9XG5cbiAgLyoqXG4gICAqIHNlbGVjdHMgYW4gYXVkaW8gdHJhY2ssIGJhc2VkIG9uIGl0cyBpbmRleCBpbiBhdWRpbyB0cmFjayBsaXN0c1xuICAgKi9cbiAgc2V0IGF1ZGlvVHJhY2soYXVkaW9UcmFja0lkKSB7XG4gICAgY29uc3QgYXVkaW9UcmFja0NvbnRyb2xsZXIgPSB0aGlzLmF1ZGlvVHJhY2tDb250cm9sbGVyO1xuICAgIGlmIChhdWRpb1RyYWNrQ29udHJvbGxlcikge1xuICAgICAgYXVkaW9UcmFja0NvbnRyb2xsZXIuYXVkaW9UcmFjayA9IGF1ZGlvVHJhY2tJZDtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogZ2V0IHRoZSBjb21wbGV0ZSBsaXN0IG9mIHN1YnRpdGxlIHRyYWNrcyBhY3Jvc3MgYWxsIG1lZGlhIGdyb3Vwc1xuICAgKi9cbiAgZ2V0IGFsbFN1YnRpdGxlVHJhY2tzKCkge1xuICAgIGNvbnN0IHN1YnRpdGxlVHJhY2tDb250cm9sbGVyID0gdGhpcy5zdWJ0aXRsZVRyYWNrQ29udHJvbGxlcjtcbiAgICByZXR1cm4gc3VidGl0bGVUcmFja0NvbnRyb2xsZXIgPyBzdWJ0aXRsZVRyYWNrQ29udHJvbGxlci5hbGxTdWJ0aXRsZVRyYWNrcyA6IFtdO1xuICB9XG5cbiAgLyoqXG4gICAqIGdldCBhbHRlcm5hdGUgc3VidGl0bGUgdHJhY2tzIGxpc3QgZnJvbSBwbGF5bGlzdFxuICAgKi9cbiAgZ2V0IHN1YnRpdGxlVHJhY2tzKCkge1xuICAgIGNvbnN0IHN1YnRpdGxlVHJhY2tDb250cm9sbGVyID0gdGhpcy5zdWJ0aXRsZVRyYWNrQ29udHJvbGxlcjtcbiAgICByZXR1cm4gc3VidGl0bGVUcmFja0NvbnRyb2xsZXIgPyBzdWJ0aXRsZVRyYWNrQ29udHJvbGxlci5zdWJ0aXRsZVRyYWNrcyA6IFtdO1xuICB9XG5cbiAgLyoqXG4gICAqIGluZGV4IG9mIHRoZSBzZWxlY3RlZCBzdWJ0aXRsZSB0cmFjayAoaW5kZXggaW4gc3VidGl0bGUgdHJhY2sgbGlzdHMpXG4gICAqL1xuICBnZXQgc3VidGl0bGVUcmFjaygpIHtcbiAgICBjb25zdCBzdWJ0aXRsZVRyYWNrQ29udHJvbGxlciA9IHRoaXMuc3VidGl0bGVUcmFja0NvbnRyb2xsZXI7XG4gICAgcmV0dXJuIHN1YnRpdGxlVHJhY2tDb250cm9sbGVyID8gc3VidGl0bGVUcmFja0NvbnRyb2xsZXIuc3VidGl0bGVUcmFjayA6IC0xO1xuICB9XG4gIGdldCBtZWRpYSgpIHtcbiAgICByZXR1cm4gdGhpcy5fbWVkaWE7XG4gIH1cblxuICAvKipcbiAgICogc2VsZWN0IGFuIHN1YnRpdGxlIHRyYWNrLCBiYXNlZCBvbiBpdHMgaW5kZXggaW4gc3VidGl0bGUgdHJhY2sgbGlzdHNcbiAgICovXG4gIHNldCBzdWJ0aXRsZVRyYWNrKHN1YnRpdGxlVHJhY2tJZCkge1xuICAgIGNvbnN0IHN1YnRpdGxlVHJhY2tDb250cm9sbGVyID0gdGhpcy5zdWJ0aXRsZVRyYWNrQ29udHJvbGxlcjtcbiAgICBpZiAoc3VidGl0bGVUcmFja0NvbnRyb2xsZXIpIHtcbiAgICAgIHN1YnRpdGxlVHJhY2tDb250cm9sbGVyLnN1YnRpdGxlVHJhY2sgPSBzdWJ0aXRsZVRyYWNrSWQ7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgc3VidGl0bGUgZGlzcGxheSBpcyBlbmFibGVkIG9yIG5vdFxuICAgKi9cbiAgZ2V0IHN1YnRpdGxlRGlzcGxheSgpIHtcbiAgICBjb25zdCBzdWJ0aXRsZVRyYWNrQ29udHJvbGxlciA9IHRoaXMuc3VidGl0bGVUcmFja0NvbnRyb2xsZXI7XG4gICAgcmV0dXJuIHN1YnRpdGxlVHJhY2tDb250cm9sbGVyID8gc3VidGl0bGVUcmFja0NvbnRyb2xsZXIuc3VidGl0bGVEaXNwbGF5IDogZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogRW5hYmxlL2Rpc2FibGUgc3VidGl0bGUgZGlzcGxheSByZW5kZXJpbmdcbiAgICovXG4gIHNldCBzdWJ0aXRsZURpc3BsYXkodmFsdWUpIHtcbiAgICBjb25zdCBzdWJ0aXRsZVRyYWNrQ29udHJvbGxlciA9IHRoaXMuc3VidGl0bGVUcmFja0NvbnRyb2xsZXI7XG4gICAgaWYgKHN1YnRpdGxlVHJhY2tDb250cm9sbGVyKSB7XG4gICAgICBzdWJ0aXRsZVRyYWNrQ29udHJvbGxlci5zdWJ0aXRsZURpc3BsYXkgPSB2YWx1ZTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogZ2V0IG1vZGUgZm9yIExvdy1MYXRlbmN5IEhMUyBsb2FkaW5nXG4gICAqL1xuICBnZXQgbG93TGF0ZW5jeU1vZGUoKSB7XG4gICAgcmV0dXJuIHRoaXMuY29uZmlnLmxvd0xhdGVuY3lNb2RlO1xuICB9XG5cbiAgLyoqXG4gICAqIEVuYWJsZS9kaXNhYmxlIExvdy1MYXRlbmN5IEhMUyBwYXJ0IHBsYXlsaXN0IGFuZCBzZWdtZW50IGxvYWRpbmcsIGFuZCBzdGFydCBsaXZlIHN0cmVhbXMgYXQgcGxheWxpc3QgUEFSVC1IT0xELUJBQ0sgcmF0aGVyIHRoYW4gSE9MRC1CQUNLLlxuICAgKi9cbiAgc2V0IGxvd0xhdGVuY3lNb2RlKG1vZGUpIHtcbiAgICB0aGlzLmNvbmZpZy5sb3dMYXRlbmN5TW9kZSA9IG1vZGU7XG4gIH1cblxuICAvKipcbiAgICogUG9zaXRpb24gKGluIHNlY29uZHMpIG9mIGxpdmUgc3luYyBwb2ludCAoaWUgZWRnZSBvZiBsaXZlIHBvc2l0aW9uIG1pbnVzIHNhZmV0eSBkZWxheSBkZWZpbmVkIGJ5IGBgYGhscy5jb25maWcubGl2ZVN5bmNEdXJhdGlvbmBgYClcbiAgICogQHJldHVybnMgbnVsbCBwcmlvciB0byBsb2FkaW5nIGxpdmUgUGxheWxpc3RcbiAgICovXG4gIGdldCBsaXZlU3luY1Bvc2l0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLmxhdGVuY3lDb250cm9sbGVyLmxpdmVTeW5jUG9zaXRpb247XG4gIH1cblxuICAvKipcbiAgICogRXN0aW1hdGVkIHBvc2l0aW9uIChpbiBzZWNvbmRzKSBvZiBsaXZlIGVkZ2UgKGllIGVkZ2Ugb2YgbGl2ZSBwbGF5bGlzdCBwbHVzIHRpbWUgc3luYyBwbGF5bGlzdCBhZHZhbmNlZClcbiAgICogQHJldHVybnMgMCBiZWZvcmUgZmlyc3QgcGxheWxpc3QgaXMgbG9hZGVkXG4gICAqL1xuICBnZXQgbGF0ZW5jeSgpIHtcbiAgICByZXR1cm4gdGhpcy5sYXRlbmN5Q29udHJvbGxlci5sYXRlbmN5O1xuICB9XG5cbiAgLyoqXG4gICAqIG1heGltdW0gZGlzdGFuY2UgZnJvbSB0aGUgZWRnZSBiZWZvcmUgdGhlIHBsYXllciBzZWVrcyBmb3J3YXJkIHRvIGBgYGhscy5saXZlU3luY1Bvc2l0aW9uYGBgXG4gICAqIGNvbmZpZ3VyZWQgdXNpbmcgYGBgbGl2ZU1heExhdGVuY3lEdXJhdGlvbkNvdW50YGBgIChtdWx0aXBsZSBvZiB0YXJnZXQgZHVyYXRpb24pIG9yIGBgYGxpdmVNYXhMYXRlbmN5RHVyYXRpb25gYGBcbiAgICogQHJldHVybnMgMCBiZWZvcmUgZmlyc3QgcGxheWxpc3QgaXMgbG9hZGVkXG4gICAqL1xuICBnZXQgbWF4TGF0ZW5jeSgpIHtcbiAgICByZXR1cm4gdGhpcy5sYXRlbmN5Q29udHJvbGxlci5tYXhMYXRlbmN5O1xuICB9XG5cbiAgLyoqXG4gICAqIHRhcmdldCBkaXN0YW5jZSBmcm9tIHRoZSBlZGdlIGFzIGNhbGN1bGF0ZWQgYnkgdGhlIGxhdGVuY3kgY29udHJvbGxlclxuICAgKi9cbiAgZ2V0IHRhcmdldExhdGVuY3koKSB7XG4gICAgcmV0dXJuIHRoaXMubGF0ZW5jeUNvbnRyb2xsZXIudGFyZ2V0TGF0ZW5jeTtcbiAgfVxuXG4gIC8qKlxuICAgKiB0aGUgcmF0ZSBhdCB3aGljaCB0aGUgZWRnZSBvZiB0aGUgY3VycmVudCBsaXZlIHBsYXlsaXN0IGlzIGFkdmFuY2luZyBvciAxIGlmIHRoZXJlIGlzIG5vbmVcbiAgICovXG4gIGdldCBkcmlmdCgpIHtcbiAgICByZXR1cm4gdGhpcy5sYXRlbmN5Q29udHJvbGxlci5kcmlmdDtcbiAgfVxuXG4gIC8qKlxuICAgKiBzZXQgdG8gdHJ1ZSB3aGVuIHN0YXJ0TG9hZCBpcyBjYWxsZWQgYmVmb3JlIE1BTklGRVNUX1BBUlNFRCBldmVudFxuICAgKi9cbiAgZ2V0IGZvcmNlU3RhcnRMb2FkKCkge1xuICAgIHJldHVybiB0aGlzLnN0cmVhbUNvbnRyb2xsZXIuZm9yY2VTdGFydExvYWQ7XG4gIH1cbn1cbkhscy5kZWZhdWx0Q29uZmlnID0gdm9pZCAwO1xuXG5leHBvcnQgeyBBYnJDb250cm9sbGVyLCBBdHRyTGlzdCwgQXVkaW9TdHJlYW1Db250cm9sbGVyLCBBdWRpb1RyYWNrQ29udHJvbGxlciwgQmFzZVBsYXlsaXN0Q29udHJvbGxlciwgQmFzZVNlZ21lbnQsIEJhc2VTdHJlYW1Db250cm9sbGVyLCBCdWZmZXJDb250cm9sbGVyLCBDTUNEQ29udHJvbGxlciwgQ2FwTGV2ZWxDb250cm9sbGVyLCBDaHVua01ldGFkYXRhLCBDb250ZW50U3RlZXJpbmdDb250cm9sbGVyLCBEYXRlUmFuZ2UsIEVNRUNvbnRyb2xsZXIsIEVycm9yQWN0aW9uRmxhZ3MsIEVycm9yQ29udHJvbGxlciwgRXJyb3JEZXRhaWxzLCBFcnJvclR5cGVzLCBFdmVudHMsIEZQU0NvbnRyb2xsZXIsIEZyYWdtZW50LCBIbHMsIEhsc1NraXAsIEhsc1VybFBhcmFtZXRlcnMsIEtleVN5c3RlbUZvcm1hdHMsIEtleVN5c3RlbXMsIExldmVsLCBMZXZlbERldGFpbHMsIExldmVsS2V5LCBMb2FkU3RhdHMsIE1ldGFkYXRhU2NoZW1hLCBOZXR3b3JrRXJyb3JBY3Rpb24sIFBhcnQsIFBsYXlsaXN0TGV2ZWxUeXBlLCBTdWJ0aXRsZVN0cmVhbUNvbnRyb2xsZXIsIFN1YnRpdGxlVHJhY2tDb250cm9sbGVyLCBUaW1lbGluZUNvbnRyb2xsZXIsIEhscyBhcyBkZWZhdWx0LCBnZXRNZWRpYVNvdXJjZSwgaXNNU0VTdXBwb3J0ZWQsIGlzU3VwcG9ydGVkIH07XG4vLyMgc291cmNlTWFwcGluZ1VSTD1obHMubWpzLm1hcFxuIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9