diff --git a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFormatContext.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFormatContext.h index efcec50cb8..d4e53eaf6b 100644 --- a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFormatContext.h +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFormatContext.h @@ -44,6 +44,7 @@ extern int FFMpegCodecIdVP9; - (bool)isAttachedPicAtStreamIndex:(int32_t)streamIndex; - (int)codecIdAtStreamIndex:(int32_t)streamIndex; - (double)duration; +- (int64_t)startTimeAtStreamIndex:(int32_t)streamIndex; - (int64_t)durationAtStreamIndex:(int32_t)streamIndex; - (bool)codecParamsAtStreamIndex:(int32_t)streamIndex toContext:(FFMpegAVCodecContext *)context; - (FFMpegFpsAndTimebase)fpsAndTimebaseForStreamIndex:(int32_t)streamIndex defaultTimeBase:(CMTime)defaultTimeBase; diff --git a/submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m b/submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m index bd4a655015..45dd51550e 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVFormatContext.m @@ -103,6 +103,10 @@ int FFMpegCodecIdVP9 = AV_CODEC_ID_VP9; return (double)_impl->duration / AV_TIME_BASE; } +- (int64_t)startTimeAtStreamIndex:(int32_t)streamIndex { + return _impl->streams[streamIndex]->start_time; +} + - (int64_t)durationAtStreamIndex:(int32_t)streamIndex { return _impl->streams[streamIndex]->duration; } diff --git a/submodules/MediaPlayer/Sources/SoftwareVideoSource.swift b/submodules/MediaPlayer/Sources/SoftwareVideoSource.swift index 0ac0e1c56c..625ac8da70 100644 --- a/submodules/MediaPlayer/Sources/SoftwareVideoSource.swift +++ b/submodules/MediaPlayer/Sources/SoftwareVideoSource.swift @@ -38,15 +38,17 @@ private final class SoftwareVideoStream { let index: Int let fps: CMTime let timebase: CMTime + let startTime: CMTime let duration: CMTime let decoder: FFMpegMediaVideoFrameDecoder let rotationAngle: Double let aspect: Double - init(index: Int, fps: CMTime, timebase: CMTime, duration: CMTime, decoder: FFMpegMediaVideoFrameDecoder, rotationAngle: Double, aspect: Double) { + init(index: Int, fps: CMTime, timebase: CMTime, startTime: CMTime, duration: CMTime, decoder: FFMpegMediaVideoFrameDecoder, rotationAngle: Double, aspect: Double) { self.index = index self.fps = fps self.timebase = timebase + self.startTime = startTime self.duration = duration self.decoder = decoder self.rotationAngle = rotationAngle @@ -126,6 +128,13 @@ public final class SoftwareVideoSource { let fpsAndTimebase = avFormatContext.fpsAndTimebase(forStreamIndex: streamIndex, defaultTimeBase: CMTimeMake(value: 1, timescale: 40000)) let (fps, timebase) = (fpsAndTimebase.fps, fpsAndTimebase.timebase) + let startTime: CMTime + let rawStartTime = avFormatContext.startTime(atStreamIndex: streamIndex) + if rawStartTime == Int64(bitPattern: 0x8000000000000000 as UInt64) { + startTime = CMTime(value: 0, timescale: timebase.timescale) + } else { + startTime = CMTimeMake(value: rawStartTime, timescale: timebase.timescale) + } let duration = CMTimeMake(value: avFormatContext.duration(atStreamIndex: streamIndex), timescale: timebase.timescale) let metrics = avFormatContext.metricsForStream(at: streamIndex) @@ -137,7 +146,7 @@ public final class SoftwareVideoSource { let codecContext = FFMpegAVCodecContext(codec: codec) if avFormatContext.codecParams(atStreamIndex: streamIndex, to: codecContext) { if codecContext.open() { - videoStream = SoftwareVideoStream(index: Int(streamIndex), fps: fps, timebase: timebase, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) + videoStream = SoftwareVideoStream(index: Int(streamIndex), fps: fps, timebase: timebase, startTime: startTime, duration: duration, decoder: FFMpegMediaVideoFrameDecoder(codecContext: codecContext), rotationAngle: rotationAngle, aspect: aspect) break } } @@ -222,6 +231,13 @@ public final class SoftwareVideoSource { } } + public func readTrackInfo() -> (offset: CMTime, duration: CMTime)? { + guard let videoStream = self.videoStream else { + return nil + } + return (videoStream.startTime, CMTimeMaximum(CMTime(value: 0, timescale: videoStream.duration.timescale), CMTimeSubtract(videoStream.duration, videoStream.startTime))) + } + public func readFrame(maxPts: CMTime?) -> (MediaTrackFrame?, CGFloat, CGFloat, Bool) { guard let videoStream = self.videoStream, let avFormatContext = self.avFormatContext else { return (nil, 0.0, 1.0, false) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 3d6d2ebb00..db52366c58 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -7098,10 +7098,7 @@ final class VoiceChatContextReferenceContentSource: ContextReferenceContentSourc } public func shouldUseV2VideoChatImpl(context: AccountContext) -> Bool { - var useV2 = false - if let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_enable_videochatui_v2"] { - useV2 = true - } + var useV2 = true if context.sharedContext.immediateExperimentalUISettings.disableCallV2 { useV2 = false } diff --git a/submodules/TelegramUniversalVideoContent/BUILD b/submodules/TelegramUniversalVideoContent/BUILD index b705ae876b..2818802355 100644 --- a/submodules/TelegramUniversalVideoContent/BUILD +++ b/submodules/TelegramUniversalVideoContent/BUILD @@ -1,4 +1,44 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") +load( + "@build_bazel_rules_apple//apple:resources.bzl", + "apple_resource_bundle", + "apple_resource_group", +) +load("//build-system/bazel-utils:plist_fragment.bzl", + "plist_fragment", +) + +filegroup( + name = "HlsBundleContents", + srcs = glob([ + "HlsBundle/**", + ]), + visibility = ["//visibility:public"], +) + +plist_fragment( + name = "HlsBundleInfoPlist", + extension = "plist", + template = + """ + CFBundleIdentifier + org.telegram.TelegramUniversalVideoContent + CFBundleDevelopmentRegion + en + CFBundleName + TelegramUniversalVideoContent + """ +) + +apple_resource_bundle( + name = "HlsBundle", + infoplists = [ + ":HlsBundleInfoPlist", + ], + resources = [ + ":HlsBundleContents", + ], +) swift_library( name = "TelegramUniversalVideoContent", @@ -9,6 +49,9 @@ swift_library( copts = [ "-warnings-as-errors", ], + data = [ + ":HlsBundle", + ], deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", diff --git a/submodules/TelegramUniversalVideoContent/HlsBundle/index.bundle.js b/submodules/TelegramUniversalVideoContent/HlsBundle/index.bundle.js new file mode 100644 index 0000000000..2a2715e3e4 --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/HlsBundle/index.bundle.js @@ -0,0 +1,29369 @@ +"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 \ No newline at end of file diff --git a/submodules/TelegramUniversalVideoContent/HlsBundle/index.html b/submodules/TelegramUniversalVideoContent/HlsBundle/index.html new file mode 100644 index 0000000000..25e3cfe610 --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/HlsBundle/index.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Developement + + + + \ No newline at end of file diff --git a/submodules/TelegramUniversalVideoContent/HlsBundle/print.bundle.js b/submodules/TelegramUniversalVideoContent/HlsBundle/print.bundle.js new file mode 100644 index 0000000000..34e789f4fe --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/HlsBundle/print.bundle.js @@ -0,0 +1,27 @@ +"use strict"; +(self["webpackChunkmy3d"] = self["webpackChunkmy3d"] || []).push([["print"],{ + +/***/ "./src/print.js": +/*!**********************!*\ + !*** ./src/print.js ***! + \**********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "default": () => (/* binding */ printMe) +/* harmony export */ }); +function printMe() { + console.log('I get called from print.js1234!'); +} + + +/***/ }) + +}, +/******/ __webpack_require__ => { // webpackRuntimeModules +/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId)) +/******/ var __webpack_exports__ = (__webpack_exec__("./src/print.js")); +/******/ } +]); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbnQuYnVuZGxlLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7QUFBZTtBQUNmO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9teTNkLy4vc3JjL3ByaW50LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIHByaW50TWUoKSB7XG4gIGNvbnNvbGUubG9nKCdJIGdldCBjYWxsZWQgZnJvbSBwcmludC5qczEyMzQhJyk7XG59XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file diff --git a/submodules/TelegramUniversalVideoContent/HlsBundle/runtime.bundle.js b/submodules/TelegramUniversalVideoContent/HlsBundle/runtime.bundle.js new file mode 100644 index 0000000000..b29450be9d --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/HlsBundle/runtime.bundle.js @@ -0,0 +1,151 @@ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({}); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ var deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ var notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ var [chunkIds, fn, priority] = deferred[i]; +/******/ var fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ var r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __webpack_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ var installedChunks = { +/******/ "runtime": 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ var [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ var chunkLoadingGlobal = self["webpackChunkmy3d"] = self["webpackChunkmy3d"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ +/******/ })() +; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVudGltZS5idW5kbGUuanMiLCJtYXBwaW5ncyI6Ijs7OztVQUFBO1VBQ0E7O1VBRUE7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7VUFDQTtVQUNBO1VBQ0E7O1VBRUE7VUFDQTs7VUFFQTtVQUNBO1VBQ0E7O1VBRUE7VUFDQTs7Ozs7V0N6QkE7V0FDQTtXQUNBO1dBQ0E7V0FDQSwrQkFBK0Isd0NBQXdDO1dBQ3ZFO1dBQ0E7V0FDQTtXQUNBO1dBQ0EsaUJBQWlCLHFCQUFxQjtXQUN0QztXQUNBO1dBQ0Esa0JBQWtCLHFCQUFxQjtXQUN2QztXQUNBO1dBQ0EsS0FBSztXQUNMO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTs7Ozs7V0MzQkE7V0FDQTtXQUNBO1dBQ0E7V0FDQSx5Q0FBeUMsd0NBQXdDO1dBQ2pGO1dBQ0E7V0FDQTs7Ozs7V0NQQTs7Ozs7V0NBQTtXQUNBO1dBQ0E7V0FDQSx1REFBdUQsaUJBQWlCO1dBQ3hFO1dBQ0EsZ0RBQWdELGFBQWE7V0FDN0Q7Ozs7O1dDTkE7O1dBRUE7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBOztXQUVBOztXQUVBOztXQUVBOztXQUVBOztXQUVBOztXQUVBOztXQUVBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBLE1BQU0scUJBQXFCO1dBQzNCO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7V0FDQTtXQUNBO1dBQ0E7O1dBRUE7V0FDQTtXQUNBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vbXkzZC93ZWJwYWNrL2Jvb3RzdHJhcCIsIndlYnBhY2s6Ly9teTNkL3dlYnBhY2svcnVudGltZS9jaHVuayBsb2FkZWQiLCJ3ZWJwYWNrOi8vbXkzZC93ZWJwYWNrL3J1bnRpbWUvZGVmaW5lIHByb3BlcnR5IGdldHRlcnMiLCJ3ZWJwYWNrOi8vbXkzZC93ZWJwYWNrL3J1bnRpbWUvaGFzT3duUHJvcGVydHkgc2hvcnRoYW5kIiwid2VicGFjazovL215M2Qvd2VicGFjay9ydW50aW1lL21ha2UgbmFtZXNwYWNlIG9iamVjdCIsIndlYnBhY2s6Ly9teTNkL3dlYnBhY2svcnVudGltZS9qc29ucCBjaHVuayBsb2FkaW5nIiwid2VicGFjazovL215M2Qvd2VicGFjay9iZWZvcmUtc3RhcnR1cCIsIndlYnBhY2s6Ly9teTNkL3dlYnBhY2svc3RhcnR1cCIsIndlYnBhY2s6Ly9teTNkL3dlYnBhY2svYWZ0ZXItc3RhcnR1cCJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBUaGUgbW9kdWxlIGNhY2hlXG52YXIgX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fID0ge307XG5cbi8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG5mdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuXHR2YXIgY2FjaGVkTW9kdWxlID0gX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fW21vZHVsZUlkXTtcblx0aWYgKGNhY2hlZE1vZHVsZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0cmV0dXJuIGNhY2hlZE1vZHVsZS5leHBvcnRzO1xuXHR9XG5cdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG5cdHZhciBtb2R1bGUgPSBfX3dlYnBhY2tfbW9kdWxlX2NhY2hlX19bbW9kdWxlSWRdID0ge1xuXHRcdC8vIG5vIG1vZHVsZS5pZCBuZWVkZWRcblx0XHQvLyBubyBtb2R1bGUubG9hZGVkIG5lZWRlZFxuXHRcdGV4cG9ydHM6IHt9XG5cdH07XG5cblx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG5cdF9fd2VicGFja19tb2R1bGVzX19bbW9kdWxlSWRdKG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG5cdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG5cdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbn1cblxuLy8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbl9fd2VicGFja19yZXF1aXJlX18ubSA9IF9fd2VicGFja19tb2R1bGVzX187XG5cbiIsInZhciBkZWZlcnJlZCA9IFtdO1xuX193ZWJwYWNrX3JlcXVpcmVfXy5PID0gKHJlc3VsdCwgY2h1bmtJZHMsIGZuLCBwcmlvcml0eSkgPT4ge1xuXHRpZihjaHVua0lkcykge1xuXHRcdHByaW9yaXR5ID0gcHJpb3JpdHkgfHwgMDtcblx0XHRmb3IodmFyIGkgPSBkZWZlcnJlZC5sZW5ndGg7IGkgPiAwICYmIGRlZmVycmVkW2kgLSAxXVsyXSA+IHByaW9yaXR5OyBpLS0pIGRlZmVycmVkW2ldID0gZGVmZXJyZWRbaSAtIDFdO1xuXHRcdGRlZmVycmVkW2ldID0gW2NodW5rSWRzLCBmbiwgcHJpb3JpdHldO1xuXHRcdHJldHVybjtcblx0fVxuXHR2YXIgbm90RnVsZmlsbGVkID0gSW5maW5pdHk7XG5cdGZvciAodmFyIGkgPSAwOyBpIDwgZGVmZXJyZWQubGVuZ3RoOyBpKyspIHtcblx0XHR2YXIgW2NodW5rSWRzLCBmbiwgcHJpb3JpdHldID0gZGVmZXJyZWRbaV07XG5cdFx0dmFyIGZ1bGZpbGxlZCA9IHRydWU7XG5cdFx0Zm9yICh2YXIgaiA9IDA7IGogPCBjaHVua0lkcy5sZW5ndGg7IGorKykge1xuXHRcdFx0aWYgKChwcmlvcml0eSAmIDEgPT09IDAgfHwgbm90RnVsZmlsbGVkID49IHByaW9yaXR5KSAmJiBPYmplY3Qua2V5cyhfX3dlYnBhY2tfcmVxdWlyZV9fLk8pLmV2ZXJ5KChrZXkpID0+IChfX3dlYnBhY2tfcmVxdWlyZV9fLk9ba2V5XShjaHVua0lkc1tqXSkpKSkge1xuXHRcdFx0XHRjaHVua0lkcy5zcGxpY2Uoai0tLCAxKTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGZ1bGZpbGxlZCA9IGZhbHNlO1xuXHRcdFx0XHRpZihwcmlvcml0eSA8IG5vdEZ1bGZpbGxlZCkgbm90RnVsZmlsbGVkID0gcHJpb3JpdHk7XG5cdFx0XHR9XG5cdFx0fVxuXHRcdGlmKGZ1bGZpbGxlZCkge1xuXHRcdFx0ZGVmZXJyZWQuc3BsaWNlKGktLSwgMSlcblx0XHRcdHZhciByID0gZm4oKTtcblx0XHRcdGlmIChyICE9PSB1bmRlZmluZWQpIHJlc3VsdCA9IHI7XG5cdFx0fVxuXHR9XG5cdHJldHVybiByZXN1bHQ7XG59OyIsIi8vIGRlZmluZSBnZXR0ZXIgZnVuY3Rpb25zIGZvciBoYXJtb255IGV4cG9ydHNcbl9fd2VicGFja19yZXF1aXJlX18uZCA9IChleHBvcnRzLCBkZWZpbml0aW9uKSA9PiB7XG5cdGZvcih2YXIga2V5IGluIGRlZmluaXRpb24pIHtcblx0XHRpZihfX3dlYnBhY2tfcmVxdWlyZV9fLm8oZGVmaW5pdGlvbiwga2V5KSAmJiAhX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIGtleSkpIHtcblx0XHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBrZXksIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBkZWZpbml0aW9uW2tleV0gfSk7XG5cdFx0fVxuXHR9XG59OyIsIl9fd2VicGFja19yZXF1aXJlX18ubyA9IChvYmosIHByb3ApID0+IChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqLCBwcm9wKSkiLCIvLyBkZWZpbmUgX19lc01vZHVsZSBvbiBleHBvcnRzXG5fX3dlYnBhY2tfcmVxdWlyZV9fLnIgPSAoZXhwb3J0cykgPT4ge1xuXHRpZih0eXBlb2YgU3ltYm9sICE9PSAndW5kZWZpbmVkJyAmJiBTeW1ib2wudG9TdHJpbmdUYWcpIHtcblx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgU3ltYm9sLnRvU3RyaW5nVGFnLCB7IHZhbHVlOiAnTW9kdWxlJyB9KTtcblx0fVxuXHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xufTsiLCIvLyBubyBiYXNlVVJJXG5cbi8vIG9iamVjdCB0byBzdG9yZSBsb2FkZWQgYW5kIGxvYWRpbmcgY2h1bmtzXG4vLyB1bmRlZmluZWQgPSBjaHVuayBub3QgbG9hZGVkLCBudWxsID0gY2h1bmsgcHJlbG9hZGVkL3ByZWZldGNoZWRcbi8vIFtyZXNvbHZlLCByZWplY3QsIFByb21pc2VdID0gY2h1bmsgbG9hZGluZywgMCA9IGNodW5rIGxvYWRlZFxudmFyIGluc3RhbGxlZENodW5rcyA9IHtcblx0XCJydW50aW1lXCI6IDBcbn07XG5cbi8vIG5vIGNodW5rIG9uIGRlbWFuZCBsb2FkaW5nXG5cbi8vIG5vIHByZWZldGNoaW5nXG5cbi8vIG5vIHByZWxvYWRlZFxuXG4vLyBubyBITVJcblxuLy8gbm8gSE1SIG1hbmlmZXN0XG5cbl9fd2VicGFja19yZXF1aXJlX18uTy5qID0gKGNodW5rSWQpID0+IChpbnN0YWxsZWRDaHVua3NbY2h1bmtJZF0gPT09IDApO1xuXG4vLyBpbnN0YWxsIGEgSlNPTlAgY2FsbGJhY2sgZm9yIGNodW5rIGxvYWRpbmdcbnZhciB3ZWJwYWNrSnNvbnBDYWxsYmFjayA9IChwYXJlbnRDaHVua0xvYWRpbmdGdW5jdGlvbiwgZGF0YSkgPT4ge1xuXHR2YXIgW2NodW5rSWRzLCBtb3JlTW9kdWxlcywgcnVudGltZV0gPSBkYXRhO1xuXHQvLyBhZGQgXCJtb3JlTW9kdWxlc1wiIHRvIHRoZSBtb2R1bGVzIG9iamVjdCxcblx0Ly8gdGhlbiBmbGFnIGFsbCBcImNodW5rSWRzXCIgYXMgbG9hZGVkIGFuZCBmaXJlIGNhbGxiYWNrXG5cdHZhciBtb2R1bGVJZCwgY2h1bmtJZCwgaSA9IDA7XG5cdGlmKGNodW5rSWRzLnNvbWUoKGlkKSA9PiAoaW5zdGFsbGVkQ2h1bmtzW2lkXSAhPT0gMCkpKSB7XG5cdFx0Zm9yKG1vZHVsZUlkIGluIG1vcmVNb2R1bGVzKSB7XG5cdFx0XHRpZihfX3dlYnBhY2tfcmVxdWlyZV9fLm8obW9yZU1vZHVsZXMsIG1vZHVsZUlkKSkge1xuXHRcdFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLm1bbW9kdWxlSWRdID0gbW9yZU1vZHVsZXNbbW9kdWxlSWRdO1xuXHRcdFx0fVxuXHRcdH1cblx0XHRpZihydW50aW1lKSB2YXIgcmVzdWx0ID0gcnVudGltZShfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblx0fVxuXHRpZihwYXJlbnRDaHVua0xvYWRpbmdGdW5jdGlvbikgcGFyZW50Q2h1bmtMb2FkaW5nRnVuY3Rpb24oZGF0YSk7XG5cdGZvcig7aSA8IGNodW5rSWRzLmxlbmd0aDsgaSsrKSB7XG5cdFx0Y2h1bmtJZCA9IGNodW5rSWRzW2ldO1xuXHRcdGlmKF9fd2VicGFja19yZXF1aXJlX18ubyhpbnN0YWxsZWRDaHVua3MsIGNodW5rSWQpICYmIGluc3RhbGxlZENodW5rc1tjaHVua0lkXSkge1xuXHRcdFx0aW5zdGFsbGVkQ2h1bmtzW2NodW5rSWRdWzBdKCk7XG5cdFx0fVxuXHRcdGluc3RhbGxlZENodW5rc1tjaHVua0lkXSA9IDA7XG5cdH1cblx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18uTyhyZXN1bHQpO1xufVxuXG52YXIgY2h1bmtMb2FkaW5nR2xvYmFsID0gc2VsZltcIndlYnBhY2tDaHVua215M2RcIl0gPSBzZWxmW1wid2VicGFja0NodW5rbXkzZFwiXSB8fCBbXTtcbmNodW5rTG9hZGluZ0dsb2JhbC5mb3JFYWNoKHdlYnBhY2tKc29ucENhbGxiYWNrLmJpbmQobnVsbCwgMCkpO1xuY2h1bmtMb2FkaW5nR2xvYmFsLnB1c2ggPSB3ZWJwYWNrSnNvbnBDYWxsYmFjay5iaW5kKG51bGwsIGNodW5rTG9hZGluZ0dsb2JhbC5wdXNoLmJpbmQoY2h1bmtMb2FkaW5nR2xvYmFsKSk7IiwiIiwiIiwiIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift index de44e0b2c6..cdf2058777 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift @@ -215,7 +215,11 @@ public final class HLSVideoContent: UniversalVideoContent { public func makeContentNode(accountId: AccountRecordId, postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode { if #available(iOS 17.1, *) { + #if DEBUG + return HLSVideoJSNativeContentNode(accountId: accountId, postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically) + #else return HLSVideoJSContentNode(accountId: accountId, postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically) + #endif } else { return HLSVideoAVContentNode(accountId: accountId, postbox: postbox, audioSessionManager: audioSession, userLocation: self.userLocation, fileReference: self.fileReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift new file mode 100644 index 0000000000..500fca3ae2 --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSNativeContentNode.swift @@ -0,0 +1,1212 @@ +import Foundation +import AVFoundation +import SwiftSignalKit +import UniversalMediaPlayer +import Postbox +import TelegramCore +import WebKit +import AsyncDisplayKit +import AccountContext +import TelegramAudio +import Display +import PhotoResources +import TelegramVoip +import RangeSet +import AppBundle +import ManagedFile +import FFMpegBinding + +final class HLSJSServerSource: SharedHLSServer.Source { + let id: String + let postbox: Postbox + let userLocation: MediaResourceUserLocation + let playlistFiles: [Int: FileMediaReference] + let qualityFiles: [Int: FileMediaReference] + + private var playlistFetchDisposables: [Int: Disposable] = [:] + + init(accountId: Int64, fileId: Int64, postbox: Postbox, userLocation: MediaResourceUserLocation, playlistFiles: [Int: FileMediaReference], qualityFiles: [Int: FileMediaReference]) { + self.id = "\(UInt64(bitPattern: accountId))_\(fileId)" + self.postbox = postbox + self.userLocation = userLocation + self.playlistFiles = playlistFiles + self.qualityFiles = qualityFiles + } + + deinit { + for (_, disposable) in self.playlistFetchDisposables { + disposable.dispose() + } + } + + func arbitraryFileData(path: String) -> Signal<(data: Data, contentType: String)?, NoError> { + return Signal { subscriber in + let bundle = Bundle(for: HLSJSServerSource.self) + + let bundlePath = bundle.bundlePath + "/HlsBundle.bundle" + if let data = try? Data(contentsOf: URL(fileURLWithPath: bundlePath + "/" + path)) { + let mimeType: String + let pathExtension = (path as NSString).pathExtension + if pathExtension == "html" { + mimeType = "text/html" + } else if pathExtension == "html" { + mimeType = "application/javascript" + } else { + mimeType = "application/octet-stream" + } + subscriber.putNext((data, mimeType)) + } else { + subscriber.putNext(nil) + } + + subscriber.putCompletion() + + return EmptyDisposable + } + } + + func masterPlaylistData() -> Signal { + var playlistString: String = "" + playlistString.append("#EXTM3U\n") + + for (quality, file) in self.qualityFiles.sorted(by: { $0.key > $1.key }) { + let width = file.media.dimensions?.width ?? 1280 + let height = file.media.dimensions?.height ?? 720 + + let bandwidth: Int + if let size = file.media.size, let duration = file.media.duration, duration != 0.0 { + bandwidth = Int(Double(size) / duration) * 8 + } else { + bandwidth = 1000000 + } + + playlistString.append("#EXT-X-STREAM-INF:BANDWIDTH=\(bandwidth),RESOLUTION=\(width)x\(height)\n") + playlistString.append("hls_level_\(quality).m3u8\n") + } + return .single(playlistString) + } + + func playlistData(quality: Int) -> Signal { + guard let playlistFile = self.playlistFiles[quality] else { + return .never() + } + if self.playlistFetchDisposables[quality] == nil { + self.playlistFetchDisposables[quality] = freeMediaFileResourceInteractiveFetched(postbox: self.postbox, userLocation: self.userLocation, fileReference: playlistFile, resource: playlistFile.media.resource).startStrict() + } + + return self.postbox.mediaBox.resourceData(playlistFile.media.resource) + |> filter { data in + return data.complete + } + |> map { data -> String in + guard data.complete else { + return "" + } + guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else { + return "" + } + guard var playlistString = String(data: data, encoding: .utf8) else { + return "" + } + let partRegex = try! NSRegularExpression(pattern: "mtproto:([\\d]+)", options: []) + let results = partRegex.matches(in: playlistString, range: NSRange(playlistString.startIndex..., in: playlistString)) + for result in results.reversed() { + if let range = Range(result.range, in: playlistString) { + if let fileIdRange = Range(result.range(at: 1), in: playlistString) { + let fileId = String(playlistString[fileIdRange]) + playlistString.replaceSubrange(range, with: "partfile\(fileId).mp4") + } + } + } + return playlistString + } + } + + func partData(index: Int, quality: Int) -> Signal { + return .never() + } + + func fileData(id: Int64, range: Range) -> Signal<(TempBoxFile, Range, Int)?, NoError> { + guard let (quality, file) = self.qualityFiles.first(where: { $0.value.media.fileId.id == id }) else { + return .single(nil) + } + let _ = quality + guard let size = file.media.size else { + return .single(nil) + } + + let postbox = self.postbox + let userLocation = self.userLocation + + let mappedRange: Range = Int64(range.lowerBound) ..< Int64(range.upperBound) + + let queue = postbox.mediaBox.dataQueue + let fetchFromRemote: Signal<(TempBoxFile, Range, Int)?, NoError> = Signal { subscriber in + let partialFile = TempBox.shared.tempFile(fileName: "data") + + if let cachedData = postbox.mediaBox.internal_resourceData(id: file.media.resource.id, size: size, in: Int64(range.lowerBound) ..< Int64(range.upperBound)) { + #if DEBUG + print("Fetched \(quality)p part from cache") + #endif + + let outputFile = ManagedFile(queue: nil, path: partialFile.path, mode: .readwrite) + if let outputFile { + let blockSize = 128 * 1024 + var tempBuffer = Data(count: blockSize) + var blockOffset = 0 + while blockOffset < cachedData.length { + let currentBlockSize = min(cachedData.length - blockOffset, blockSize) + + tempBuffer.withUnsafeMutableBytes { bytes -> Void in + let _ = cachedData.file.read(bytes.baseAddress!, currentBlockSize) + let _ = outputFile.write(bytes.baseAddress!, count: currentBlockSize) + } + + blockOffset += blockSize + } + outputFile._unsafeClose() + subscriber.putNext((partialFile, 0 ..< cachedData.length, Int(size))) + subscriber.putCompletion() + } else { + #if DEBUG + print("Error writing cached file to disk") + #endif + } + + return EmptyDisposable + } + + guard let fetchResource = postbox.mediaBox.fetchResource else { + return EmptyDisposable + } + + let location = MediaResourceStorageLocation(userLocation: userLocation, reference: file.resourceReference(file.media.resource)) + let params = MediaResourceFetchParameters( + tag: TelegramMediaResourceFetchTag(statsCategory: .video, userContentType: .video), + info: TelegramCloudMediaResourceFetchInfo(reference: file.resourceReference(file.media.resource), preferBackgroundReferenceRevalidation: true, continueInBackground: true), + location: location, + contentType: .video, + isRandomAccessAllowed: true + ) + + let completeFile = TempBox.shared.tempFile(fileName: "data") + let metaFile = TempBox.shared.tempFile(fileName: "data") + + guard let fileContext = MediaBoxFileContextV2Impl( + queue: queue, + manager: postbox.mediaBox.dataFileManager, + storageBox: nil, + resourceId: file.media.resource.id.stringRepresentation.data(using: .utf8)!, + path: completeFile.path, + partialPath: partialFile.path, + metaPath: metaFile.path + ) else { + return EmptyDisposable + } + + let fetchDisposable = fileContext.fetched( + range: mappedRange, + priority: .default, + fetch: { intervals in + return fetchResource(file.media.resource, intervals, params) + }, + error: { _ in + }, + completed: { + } + ) + + #if DEBUG + let startTime = CFAbsoluteTimeGetCurrent() + #endif + + let dataDisposable = fileContext.data( + range: mappedRange, + waitUntilAfterInitialFetch: true, + next: { result in + if result.complete { + #if DEBUG + let fetchTime = CFAbsoluteTimeGetCurrent() - startTime + print("Fetching \(quality)p part took \(fetchTime * 1000.0) ms") + #endif + subscriber.putNext((partialFile, Int(result.offset) ..< Int(result.offset + result.size), Int(size))) + subscriber.putCompletion() + } + } + ) + + return ActionDisposable { + queue.async { + fetchDisposable.dispose() + dataDisposable.dispose() + fileContext.cancelFullRangeFetches() + + TempBox.shared.dispose(completeFile) + TempBox.shared.dispose(metaFile) + } + } + } + |> runOn(queue) + + return fetchFromRemote + } +} + +private class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler { + private let f: (WKScriptMessage) -> () + + init(_ f: @escaping (WKScriptMessage) -> ()) { + self.f = f + + super.init() + } + + func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { + self.f(scriptMessage) + } +} + +final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNode { + private struct Level { + let bitrate: Int + let width: Int + let height: Int + + init(bitrate: Int, width: Int, height: Int) { + self.bitrate = bitrate + self.width = width + self.height = height + } + } + + private static var sharedBandwidthEstimate: Double? + + private let postbox: Postbox + private let userLocation: MediaResourceUserLocation + private let fileReference: FileMediaReference + private let approximateDuration: Double + private let intrinsicDimensions: CGSize + + private let audioSessionManager: ManagedAudioSession + private let audioSessionDisposable = MetaDisposable() + private var hasAudioSession = false + + private let playerSource: HLSJSServerSource? + private var serverDisposable: Disposable? + + private let playbackCompletedListeners = Bag<() -> Void>() + + private var initializedStatus = false + private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) + private var isBuffering = false + private var seekId: Int = 0 + private let _status = ValuePromise() + var status: Signal { + return self._status.get() + } + + private let _bufferingStatus = Promise<(RangeSet, Int64)?>() + var bufferingStatus: Signal<(RangeSet, Int64)?, NoError> { + return self._bufferingStatus.get() + } + + private let _isNativePictureInPictureActive = ValuePromise(false, ignoreRepeated: true) + var isNativePictureInPictureActive: Signal { + return self._isNativePictureInPictureActive.get() + } + + private let _ready = Promise() + var ready: Signal { + return self._ready.get() + } + + private let _preloadCompleted = ValuePromise() + var preloadCompleted: Signal { + return self._preloadCompleted.get() + } + + private let imageNode: TransformImageNode + private let webView: WKWebView + + private var testPlayer: AVPlayer? + private var controlledPlayer: ControlledPlayer? + private let playerNode: ASDisplayNode + + private let fetchDisposable = MetaDisposable() + + private var dimensions: CGSize? + private let dimensionsPromise = ValuePromise(CGSize()) + + private var validLayout: (size: CGSize, actualSize: CGSize)? + + private var statusTimer: Foundation.Timer? + + private var preferredVideoQuality: UniversalVideoContentVideoQuality = .auto + + private var playerIsReady: Bool = false + private var playerIsFirstFrameReady: Bool = false + private var playerIsPlaying: Bool = false + private var playerRate: Double = 0.0 + private var playerDefaultRate: Double = 1.0 + private var playerTime: Double = 0.0 + private var playerTimeGenerationTimestamp: Double = 0.0 + private var playerAvailableLevels: [Int: Level] = [:] + private var playerCurrentLevelIndex: Int? + + private var hasRequestedPlayerLoad: Bool = false + + private var requestedPlaying: Bool = false + private var requestedBaseRate: Double = 1.0 + private var requestedLevelIndex: Int? + + private var sourceBuffers: [Int: SourceBuffer] = [:] + + private var didBecomeActiveObserver: NSObjectProtocol? + private var willResignActiveObserver: NSObjectProtocol? + + init(accountId: AccountRecordId, postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { + self.postbox = postbox + self.fileReference = fileReference + self.approximateDuration = fileReference.media.duration ?? 0.0 + self.audioSessionManager = audioSessionManager + self.userLocation = userLocation + self.requestedBaseRate = baseRate + + if var dimensions = fileReference.media.dimensions { + if let thumbnail = fileReference.media.previewRepresentations.first { + let dimensionsVertical = dimensions.width < dimensions.height + let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height + if dimensionsVertical != thumbnailVertical { + dimensions = PixelDimensions(width: dimensions.height, height: dimensions.width) + } + } + self.dimensions = dimensions.cgSize + } else { + self.dimensions = CGSize(width: 128.0, height: 128.0) + } + + self.imageNode = TransformImageNode() + + let config = WKWebViewConfiguration() + config.allowsInlineMediaPlayback = true + config.mediaTypesRequiringUserActionForPlayback = [] + config.allowsPictureInPictureMediaPlayback = true + + var playerSource: HLSJSServerSource? + if let qualitySet = HLSQualitySet(baseFile: fileReference) { + let playerSourceValue = HLSJSServerSource(accountId: accountId.int64, fileId: fileReference.media.fileId.id, postbox: postbox, userLocation: userLocation, playlistFiles: qualitySet.playlistFiles, qualityFiles: qualitySet.qualityFiles) + playerSource = playerSourceValue + } + self.playerSource = playerSource + + let userController = WKUserContentController() + + var handleScriptMessage: ((WKScriptMessage) -> Void)? + userController.add(WeakScriptMessageHandler { message in + handleScriptMessage?(message) + }, name: "performAction") + + let isDebug: Bool + #if DEBUG + isDebug = true + #else + isDebug = false + #endif + + let mediaDimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 480.0, height: 320.0) + var intrinsicDimensions = mediaDimensions.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0)) + + config.userContentController = userController + + intrinsicDimensions.width = floor(intrinsicDimensions.width / UIScreenScale) + intrinsicDimensions.height = floor(intrinsicDimensions.height / UIScreenScale) + self.intrinsicDimensions = intrinsicDimensions + + self.webView = WKWebView(frame: CGRect(origin: CGPoint(), size: self.intrinsicDimensions), configuration: config) + self.webView.scrollView.isScrollEnabled = false + self.webView.allowsLinkPreview = false + self.webView.allowsBackForwardNavigationGestures = false + self.webView.accessibilityIgnoresInvertColors = true + self.webView.scrollView.contentInsetAdjustmentBehavior = .never + self.webView.alpha = 0.0 + + if #available(iOS 16.4, *) { + #if DEBUG + self.webView.isInspectable = true + #endif + } + + if "".isEmpty { + let controlledPlayer = ControlledPlayer() + self.controlledPlayer = controlledPlayer + } else { + let testPlayer = AVPlayer(playerItem: nil) + if #available(iOS 16.0, *) { + testPlayer.defaultRate = Float(baseRate) + } + if !enableSound { + testPlayer.volume = 0.0 + } + self.testPlayer = testPlayer + } + + let targetPlayer = self.controlledPlayer?.player ?? self.testPlayer + self.playerNode = ASDisplayNode() + self.playerNode.setLayerBlock({ + return AVPlayerLayer(player: targetPlayer) + }) + + super.init() + + self.playerNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicDimensions) + + self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: fileReference) |> map { [weak self] getSize, getData in + Queue.mainQueue().async { + if let strongSelf = self, strongSelf.dimensions == nil { + if let dimensions = getSize() { + strongSelf.dimensions = dimensions + strongSelf.dimensionsPromise.set(dimensions) + if let validLayout = strongSelf.validLayout { + strongSelf.updateLayout(size: validLayout.size, actualSize: validLayout.actualSize, transition: .immediate) + } + } + } + } + return getData + }) + + self.addSubnode(self.imageNode) + self.view.addSubview(self.webView) + self.addSubnode(self.playerNode) + + self.imageNode.imageUpdated = { [weak self] _ in + self?._ready.set(.single(Void())) + } + + self._bufferingStatus.set(.single(nil)) + + handleScriptMessage = { [weak self] message in + Queue.mainQueue().async { + guard let self else { + return + } + guard let body = message.body as? [String: Any] else { + return + } + guard let eventName = body["event"] as? String else { + return + } + + switch eventName { + case "windowOnLoad": + let userScriptJs = """ + playerInitialize({ + 'debug': \(isDebug), + 'bandwidthEstimate': \(HLSVideoJSNativeContentNode.sharedBandwidthEstimate ?? 500000.0) + }); + """ + if "".isEmpty { + self.webView.evaluateJavaScript(userScriptJs, completionHandler: nil) + } + case "bridgeInvoke": + guard let eventData = body["data"] as? [String: Any] else { + return + } + guard let bridgeId = eventData["bridgeId"] as? Int else { + return + } + guard let callbackId = eventData["callbackId"] as? Int else { + return + } + guard let className = eventData["className"] as? String else { + return + } + guard let methodName = eventData["methodName"] as? String else { + return + } + guard let params = eventData["params"] as? [String: Any] else { + return + } + self.bridgeInvoke( + bridgeId: bridgeId, + className: className, + methodName: methodName, + params: params, + completion: { [weak self] result in + guard let self else { + return + } + let jsonResult = try! JSONSerialization.data(withJSONObject: result) + let jsonResultString = String(data: jsonResult, encoding: .utf8)! + self.webView.evaluateJavaScript("bridgeInvokeCallback(\(callbackId), \(jsonResultString));", completionHandler: nil) + } + ) + case "playerStatus": + guard let eventData = body["data"] as? [String: Any] else { + return + } + if let isReady = eventData["isReady"] as? Bool { + self.playerIsReady = isReady + } else { + self.playerIsReady = false + } + if let isFirstFrameReady = eventData["isFirstFrameReady"] as? Bool { + self.playerIsFirstFrameReady = isFirstFrameReady + } else { + self.playerIsFirstFrameReady = false + } + if let isPlaying = eventData["isPlaying"] as? Bool { + self.playerIsPlaying = isPlaying + } else { + self.playerIsPlaying = false + } + if let rate = eventData["rate"] as? Double { + self.playerRate = rate + } else { + self.playerRate = 0.0 + } + if let defaultRate = eventData["defaultRate"] as? Double { + self.playerDefaultRate = defaultRate + } else { + self.playerDefaultRate = 0.0 + } + if let levels = eventData["levels"] as? [[String: Any]] { + self.playerAvailableLevels.removeAll() + + for level in levels { + guard let levelIndex = level["index"] as? Int else { + continue + } + guard let levelBitrate = level["bitrate"] as? Int else { + continue + } + guard let levelWidth = level["width"] as? Int else { + continue + } + guard let levelHeight = level["height"] as? Int else { + continue + } + self.playerAvailableLevels[levelIndex] = Level( + bitrate: levelBitrate, + width: levelWidth, + height: levelHeight + ) + } + } else { + self.playerAvailableLevels.removeAll() + } + + self._isNativePictureInPictureActive.set(eventData["isPictureInPictureActive"] as? Bool ?? false) + + if let currentLevel = eventData["currentLevel"] as? Int { + if self.playerAvailableLevels[currentLevel] != nil { + self.playerCurrentLevelIndex = currentLevel + } else { + self.playerCurrentLevelIndex = nil + } + } else { + self.playerCurrentLevelIndex = nil + } + + if self.playerIsReady { + if !self.hasRequestedPlayerLoad { + if !self.playerAvailableLevels.isEmpty { + var selectedLevelIndex: Int? + if let minimizedQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file { + if let dimensions = minimizedQualityFile.media.dimensions { + for (index, level) in self.playerAvailableLevels { + if level.height == Int(dimensions.height) { + selectedLevelIndex = index + break + } + } + } + } + if selectedLevelIndex == nil { + selectedLevelIndex = self.playerAvailableLevels.sorted(by: { $0.value.height > $1.value.height }).first?.key + } + if let selectedLevelIndex { + self.hasRequestedPlayerLoad = true + self.webView.evaluateJavaScript("playerLoad(\(selectedLevelIndex));", completionHandler: nil) + } + } + } + + self.webView.evaluateJavaScript("playerSetBaseRate(\(self.requestedBaseRate));", completionHandler: nil) + + if self.requestedPlaying { + self.requestPlay() + } else { + self.requestPause() + } + } + + self.updateStatus() + case "playerCurrentTime": + guard let eventData = body["data"] as? [String: Any] else { + return + } + guard let value = eventData["value"] as? Double else { + return + } + + self.playerTime = value + self.playerTimeGenerationTimestamp = CACurrentMediaTime() + + var bandwidthEstimate = eventData["bandwidthEstimate"] as? Double + if let bandwidthEstimateValue = bandwidthEstimate, bandwidthEstimateValue.isNaN || bandwidthEstimateValue.isInfinite { + bandwidthEstimate = nil + } + + HLSVideoJSNativeContentNode.sharedBandwidthEstimate = bandwidthEstimate + + self.updateStatus() + + self.controlledPlayer?.currentReferenceTime = value + default: + break + } + } + } + + if let playerSource = self.playerSource { + self.serverDisposable = SharedHLSServer.shared.registerPlayer(source: playerSource, completion: { [weak self] in + Queue.mainQueue().async { + guard let self else { + return + } + + let htmlUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(playerSource.id)/index.html" + self.webView.load(URLRequest(url: URL(string: htmlUrl)!)) + } + }) + } + + self.didBecomeActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in + guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else { + return + } + layer.player = strongSelf.controlledPlayer?.player ?? strongSelf.testPlayer + }) + self.willResignActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil, using: { [weak self] _ in + guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else { + return + } + layer.player = nil + }) + } + + deinit { + if let didBecomeActiveObserver = self.didBecomeActiveObserver { + NotificationCenter.default.removeObserver(didBecomeActiveObserver) + } + if let willResignActiveObserver = self.willResignActiveObserver { + NotificationCenter.default.removeObserver(willResignActiveObserver) + } + + self.serverDisposable?.dispose() + self.audioSessionDisposable.dispose() + + self.statusTimer?.invalidate() + } + + private func bridgeInvoke( + bridgeId: Int, + className: String, + methodName: String, + params: [String: Any], + completion: @escaping ([String: Any]) -> Void + ) { + if (className == "SourceBuffer") { + if (methodName == "constructor") { + guard let mimeType = params["mimeType"] as? String else { + assertionFailure() + return + } + let sourceBuffer = SourceBuffer(mimeType: mimeType) + self.sourceBuffers[bridgeId] = sourceBuffer + self.controlledPlayer?.setSourceBuffer(sourceBuffer: sourceBuffer) + completion([:]) + } else if (methodName == "appendBuffer") { + guard let base64Data = params["data"] as? String else { + assertionFailure() + return + } + guard let data = Data(base64Encoded: base64Data.data(using: .utf8)!) else { + assertionFailure() + return + } + guard let sourceBuffer = self.sourceBuffers[bridgeId] else { + assertionFailure() + return + } + sourceBuffer.appendBuffer(data: data, completion: { result in + if let result { + completion([ + "rangeStart": result.0, + "rangeEnd": result.1 + ]) + + if let sourceBuffer = self.sourceBuffers[bridgeId], let testPlayer = self.testPlayer { + var rangeEnd: Double = 0.0 + for item in sourceBuffer.items { + rangeEnd += item.endTime - item.startTime + } + if rangeEnd >= 30.0 && testPlayer.currentItem == nil { + let tempFile = TempBox.shared.tempFile(fileName: "data.mp4") + if let initializationData = sourceBuffer.initializationData, let outputFile = ManagedFile(queue: nil, path: tempFile.path, mode: .readwrite) { + let _ = outputFile.write(initializationData) + for item in sourceBuffer.items.sorted(by: { $0.startTime < $1.startTime }) { + let _ = outputFile.write(item.rawData) + } + outputFile._unsafeClose() + + let playerItem = AVPlayerItem(url: URL(fileURLWithPath: tempFile.path)) + testPlayer.replaceCurrentItem(with: playerItem) + testPlayer.play() + } + } + } + } else { + completion([:]) + } + }) + } else if methodName == "remove" { + guard let start = params["start"] as? Double, let end = params["end"] as? Double else { + assertionFailure() + return + } + guard let sourceBuffer = self.sourceBuffers[bridgeId] else { + assertionFailure() + return + } + sourceBuffer.remove(start: start, end: end) + completion([:]) + } + } + } + + private func updateStatus() { + let isPlaying = self.requestedPlaying && self.playerRate != 0.0 + let status: MediaPlayerPlaybackStatus + if self.requestedPlaying && !isPlaying { + status = .buffering(initial: false, whilePlaying: self.requestedPlaying, progress: 0.0, display: true) + } else { + status = self.requestedPlaying ? .playing : .paused + } + var timestamp = self.playerTime + if timestamp.isFinite && !timestamp.isNaN { + } else { + timestamp = 0.0 + } + self.statusValue = MediaPlayerStatus(generationTimestamp: self.playerTimeGenerationTimestamp, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: timestamp, baseRate: self.requestedBaseRate, seekId: self.seekId, status: status, soundEnabled: true) + self._status.set(self.statusValue) + + if case .playing = status { + if self.statusTimer == nil { + self.statusTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true, block: { [weak self] _ in + guard let self else { + return + } + self.updateStatus() + }) + } + } else if let statusTimer = self.statusTimer { + self.statusTimer = nil + statusTimer.invalidate() + } + } + + private func performActionAtEnd() { + for listener in self.playbackCompletedListeners.copyItems() { + listener() + } + } + + func updateLayout(size: CGSize, actualSize: CGSize, transition: ContainedViewLayoutTransition) { + transition.updatePosition(layer: self.webView.layer, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + transition.updateTransformScale(layer: self.webView.layer, scale: size.width / self.intrinsicDimensions.width) + + transition.updatePosition(node: self.playerNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + transition.updateTransformScale(node: self.playerNode, scale: size.width / self.intrinsicDimensions.width) + + transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) + + if let dimensions = self.dimensions { + let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) + let makeLayout = self.imageNode.asyncLayout() + let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) + applyLayout() + } + } + + func play() { + assert(Queue.mainQueue().isCurrent()) + if !self.initializedStatus { + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: self.requestedBaseRate, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) + } + /*if !self.hasAudioSession { + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in + Queue.mainQueue().async { + guard let self else { + return + } + self.hasAudioSession = true + self.requestPlay() + } + }, deactivate: { [weak self] _ in + return Signal { subscriber in + if let self { + self.hasAudioSession = false + self.requestPause() + } + + subscriber.putCompletion() + + return EmptyDisposable + } + |> runOn(.mainQueue()) + })) + } else*/ do { + self.requestPlay() + } + } + + private func requestPlay() { + self.requestedPlaying = true + if self.playerIsReady { + self.webView.evaluateJavaScript("playerPlay();", completionHandler: nil) + } + self.updateStatus() + } + + private func requestPause() { + self.requestedPlaying = false + if self.playerIsReady { + self.webView.evaluateJavaScript("playerPause();", completionHandler: nil) + } + self.updateStatus() + } + + func pause() { + assert(Queue.mainQueue().isCurrent()) + self.requestPause() + } + + func togglePlayPause() { + assert(Queue.mainQueue().isCurrent()) + + if self.requestedPlaying { + self.pause() + } else { + self.play() + } + } + + func setSoundEnabled(_ value: Bool) { + assert(Queue.mainQueue().isCurrent()) + /*if value { + if !self.hasAudioSession { + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in + self?.hasAudioSession = true + self?.player?.volume = 1.0 + }, deactivate: { [weak self] _ in + self?.hasAudioSession = false + self?.player?.pause() + return .complete() + })) + } + } else { + self.player?.volume = 0.0 + self.hasAudioSession = false + self.audioSessionDisposable.set(nil) + }*/ + } + + func seek(_ timestamp: Double) { + assert(Queue.mainQueue().isCurrent()) + self.seekId += 1 + + self.webView.evaluateJavaScript("playerSeek(\(timestamp));", completionHandler: nil) + } + + func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { + self.webView.evaluateJavaScript("playerSetIsMuted(false);", completionHandler: nil) + + self.play() + } + + func setSoundMuted(soundMuted: Bool) { + self.webView.evaluateJavaScript("playerSetIsMuted(\(soundMuted));", completionHandler: nil) + } + + func continueWithOverridingAmbientMode(isAmbient: Bool) { + } + + func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { + } + + func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { + self.webView.evaluateJavaScript("playerSetIsMuted(true);", completionHandler: nil) + self.hasAudioSession = false + self.audioSessionDisposable.set(nil) + } + + func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) { + } + + func setBaseRate(_ baseRate: Double) { + self.requestedBaseRate = baseRate + if self.playerIsReady { + self.webView.evaluateJavaScript("playerSetBaseRate(\(self.requestedBaseRate));", completionHandler: nil) + } + self.updateStatus() + } + + func setVideoQuality(_ videoQuality: UniversalVideoContentVideoQuality) { + self.preferredVideoQuality = videoQuality + + switch videoQuality { + case .auto: + self.requestedLevelIndex = nil + case let .quality(quality): + if let level = self.playerAvailableLevels.first(where: { $0.value.height == quality }) { + self.requestedLevelIndex = level.key + } else { + self.requestedLevelIndex = nil + } + } + + if self.playerIsReady { + self.webView.evaluateJavaScript("playerSetLevel(\(self.requestedLevelIndex ?? -1));", completionHandler: nil) + } + } + + func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { + guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else { + return nil + } + guard let currentLevel = self.playerAvailableLevels[playerCurrentLevelIndex] else { + return nil + } + + var available = self.playerAvailableLevels.values.map(\.height) + available.sort(by: { $0 > $1 }) + + return (currentLevel.height, self.preferredVideoQuality, available) + } + + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { + return self.playbackCompletedListeners.add(f) + } + + func removePlaybackCompleted(_ index: Int) { + self.playbackCompletedListeners.remove(index) + } + + func fetchControl(_ control: UniversalVideoNodeFetchControl) { + } + + func notifyPlaybackControlsHidden(_ hidden: Bool) { + } + + func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { + } + + func enterNativePictureInPicture() -> Bool { + self.webView.evaluateJavaScript("playerRequestPictureInPicture();", completionHandler: nil) + return true + } + + func exitNativePictureInPicture() { + self.webView.evaluateJavaScript("playerStopPictureInPicture();", completionHandler: nil) + } +} + +private final class SourceBuffer { + private static let sharedQueue = Queue(name: "SourceBuffer") + + final class Item { + let tempFile: TempBoxFile + let asset: AVURLAsset + let startTime: Double + let endTime: Double + let rawData: Data + + init(tempFile: TempBoxFile, asset: AVURLAsset, startTime: Double, endTime: Double, rawData: Data) { + self.tempFile = tempFile + self.asset = asset + self.startTime = startTime + self.endTime = endTime + self.rawData = rawData + } + } + + let mimeType: String + var initializationData: Data? + var items: [Item] = [] + + let updated = ValuePipe() + + init(mimeType: String) { + self.mimeType = mimeType + } + + func appendBuffer(data: Data, completion: @escaping ((Double, Double)?) -> Void) { + let initializationData = self.initializationData + SourceBuffer.sharedQueue.async { [weak self] in + let tempFile = TempBox.shared.tempFile(fileName: "data.mp4") + + var combinedData = Data() + if let initializationData { + combinedData.append(initializationData) + } + combinedData.append(data) + guard let _ = try? combinedData.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) else { + Queue.mainQueue().async { + completion(nil) + } + return + } + + if let fragmentInfo = parseFragment(filePath: tempFile.path) { + Queue.mainQueue().async { + guard let self else { + completion(nil) + return + } + if fragmentInfo.duration.value == 0 { + self.initializationData = data + + completion((0.0, 0.0)) + } else { + let item = Item( + tempFile: tempFile, + asset: AVURLAsset(url: URL(fileURLWithPath: tempFile.path)), + startTime: round(fragmentInfo.offset.seconds * 1000.0) / 1000.0, + endTime: round((fragmentInfo.offset.seconds + fragmentInfo.duration.seconds) * 1000.0) / 1000.0, + rawData: data + ) + self.items.append(item) + + completion((item.startTime, item.endTime)) + + self.updated.putNext(Void()) + } + } + } else { + assertionFailure() + Queue.mainQueue().async { + completion(nil) + } + return + } + } + } + + func remove(start: Double, end: Double) { + self.items.removeAll(where: { item in + if item.startTime >= start && item.endTime <= end { + return true + } else { + return false + } + }) + + self.updated.putNext(Void()) + } +} + +private func parseFragment(filePath: String) -> (offset: CMTime, duration: CMTime)? { + let source = SoftwareVideoSource(path: filePath, hintVP9: false, unpremultiplyAlpha: false) + return source.readTrackInfo() +} + +private final class ControlledPlayer { + let player: AVPlayer + + private var sourceBuffer: SourceBuffer? + private var sourceBufferUpdatedDisposable: Disposable? + + var currentReferenceTime: Double? + private var currentItem: SourceBuffer.Item? + + private var updateLink: SharedDisplayLinkDriver.Link? + + init() { + self.player = AVPlayer(playerItem: nil) + + self.updateLink = SharedDisplayLinkDriver.shared.add { [weak self] _ in + guard let self else { + return + } + self.update() + } + } + + deinit { + self.sourceBufferUpdatedDisposable?.dispose() + } + + func setSourceBuffer(sourceBuffer: SourceBuffer) { + if self.sourceBuffer === sourceBuffer { + return + } + self.sourceBufferUpdatedDisposable?.dispose() + self.sourceBuffer = sourceBuffer + + self.sourceBufferUpdatedDisposable = (sourceBuffer.updated.signal() + |> deliverOnMainQueue).start(next: { [weak self] _ in + guard let self else { + return + } + self.update() + }) + } + + private func update() { + guard let sourceBuffer = self.sourceBuffer else { + return + } + guard let currentReferenceTime = self.currentReferenceTime else { + return + } + var replaceItem = false + if let currentItem = self.currentItem { + if currentReferenceTime < currentItem.startTime || currentReferenceTime > currentItem.endTime { + replaceItem = true + } + } else { + replaceItem = true + } + if replaceItem { + let item = sourceBuffer.items.last(where: { item in + if currentReferenceTime >= item.startTime && currentReferenceTime <= item.endTime { + return true + } else { + return false + } + }) + if let item { + self.currentItem = item + let playerItem = AVPlayerItem(asset: item.asset) + self.player.replaceCurrentItem(with: playerItem) + self.player.seek(to: CMTime(seconds: currentReferenceTime - item.startTime, preferredTimescale: 240), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero, completionHandler: { _ in }) + self.player.play() + } else if self.player.currentItem != nil { + self.player.replaceCurrentItem(with: nil) + } + } + } + + func play() { + self.player.play() + } + + func pause() { + self.player.pause() + } + + func seek(timestamp: Double) { + } +}