2024-10-11 20:11:16 +04:00

29523 lines
2.5 MiB
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
(self["webpackChunkmyhls"] = self["webpackChunkmyhls"] || []).push([["index"],{
/***/ "./src/MediaSourceStub.js":
/*!********************************!*\
!*** ./src/MediaSourceStub.js ***!
\********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ MediaSourceStub: () => (/* binding */ MediaSourceStub),
/* harmony export */ SourceBufferListStub: () => (/* binding */ SourceBufferListStub),
/* harmony export */ SourceBufferStub: () => (/* binding */ SourceBufferStub)
/* harmony export */ });
/* harmony import */ var _TimeRangesStub_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./TimeRangesStub.js */ "./src/TimeRangesStub.js");
function bytesToBase64(bytes) {
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte),
).join("");
return btoa(binString);
}
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]();
}
}
class SourceBufferStub extends EventTarget {
constructor(mediaSource, mimeType) {
super();
this.mediaSource = mediaSource;
this.mimeType = mimeType;
this.updating = false;
this.buffered = new _TimeRangesStub_js__WEBPACK_IMPORTED_MODULE_0__.TimeRangesStub();
this.timestampOffset = 0;
this.appendWindowStart = 0;
this.appendWindowEnd = Infinity;
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this;
window.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'));
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "appendBuffer", {
"data": bytesToBase64(data)
}).then((result) => {
const updatedRanges = result["ranges"];
var ranges = [];
for (var i = 0; i < updatedRanges.length; i += 2) {
ranges.push({
start: updatedRanges[i],
end: updatedRanges[i + 1]
});
}
this.buffered._ranges = ranges;
this.mediaSource._reopen();
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'));
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "abort", {}).then((result) => {
});
}
}
remove(start, end) {
if (this.updating) {
throw new DOMException('SourceBuffer is updating', 'InvalidStateError');
}
this.updating = true;
this.dispatchEvent(new Event('updatestart'));
window.bridgeInvokeAsync(this.bridgeId, "SourceBuffer", "remove", {
"start": start,
"end": end
}).then((result) => {
const updatedRanges = result["ranges"];
var ranges = [];
for (var i = 0; i < updatedRanges.length; i += 2) {
ranges.push({
start: updatedRanges[i],
end: updatedRanges[i + 1]
});
}
this.buffered._ranges = ranges;
this.mediaSource._reopen();
this.updating = false;
this.dispatchEvent(new Event('update'));
this.dispatchEvent(new Event('updateend'));
});
}
}
class MediaSourceStub extends EventTarget {
constructor() {
super();
this.internalId = window.nextInternalId;
window.nextInternalId += 1;
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this;
this.sourceBuffers = new SourceBufferListStub();
this.activeSourceBuffers = new SourceBufferListStub();
this.readyState = 'closed';
this._duration = NaN;
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "constructor", {
});
// 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'));
}
_reopen() {
if (this.readyState !== 'open') {
this.readyState = 'open';
this.dispatchEvent(new Event('sourceopen'));
}
}
set duration(value) {
if (this.readyState === 'closed') {
throw new DOMException('MediaSource is closed', 'InvalidStateError');
}
this._duration = value;
window.bridgeInvokeAsync(this.bridgeId, "MediaSource", "setDuration", {
"duration": value
}).then((result) => {
})
}
get duration() {
return this._duration;
}
}
/***/ }),
/***/ "./src/TextTrackStub.js":
/*!******************************!*\
!*** ./src/TextTrackStub.js ***!
\******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ TextTrackCueListStub: () => (/* binding */ TextTrackCueListStub),
/* harmony export */ TextTrackListStub: () => (/* binding */ TextTrackListStub),
/* harmony export */ TextTrackStub: () => (/* binding */ TextTrackStub)
/* harmony export */ });
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);
}
}
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]();
}
}
/***/ }),
/***/ "./src/TimeRangesStub.js":
/*!*******************************!*\
!*** ./src/TimeRangesStub.js ***!
\*******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ TimeRangesStub: () => (/* binding */ TimeRangesStub)
/* harmony export */ });
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;
}
}
/***/ }),
/***/ "./src/VideoElementStub.js":
/*!*********************************!*\
!*** ./src/VideoElementStub.js ***!
\*********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ VideoElementStub: () => (/* binding */ VideoElementStub)
/* harmony export */ });
/* harmony import */ var _TimeRangesStub_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./TimeRangesStub.js */ "./src/TimeRangesStub.js");
/* harmony import */ var _TextTrackStub_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./TextTrackStub.js */ "./src/TextTrackStub.js");
class VideoElementStub extends EventTarget {
constructor() {
super();
this.bridgeId = window.nextInternalId;
window.nextInternalId += 1;
window.bridgeObjectMap[this.bridgeId] = this;
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_js__WEBPACK_IMPORTED_MODULE_0__.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 _TextTrackStub_js__WEBPACK_IMPORTED_MODULE_1__.TextTrackListStub();
this.isWaiting = false;
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "constructor", {
});
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('seeking'));
window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "setCurrentTime", {
"currentTime": value
}).then((result) => {
this.dispatchEvent(new Event('seeked'));
})
}
}
bridgeUpdateBuffered(value) {
const updatedRanges = value;
var ranges = [];
for (var i = 0; i < updatedRanges.length; i += 2) {
ranges.push({
start: updatedRanges[i],
end: updatedRanges[i + 1]
});
}
this.buffered._ranges = ranges;
}
bridgeUpdateStatus(dict) {
var paused = !dict["isPlaying"];
var isWaiting = dict["isWaiting"];
var currentTime = dict["currentTime"];
if (this.paused != paused) {
this.paused = paused;
if (paused) {
this.dispatchEvent(new Event('pause'));
} else {
this.dispatchEvent(new Event('play'));
this.dispatchEvent(new Event('playing'));
}
}
if (this.isWaiting != isWaiting) {
this.isWaiting = isWaiting;
if (isWaiting) {
this.dispatchEvent(new Event('waiting'));
}
}
if (this._currentTime != currentTime) {
this._currentTime = currentTime;
this.dispatchEvent(new Event('timeupdate'));
}
}
play() {
if (this.paused) {
return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "play", {
}).then((result) => {
this.dispatchEvent(new Event('play'));
this.dispatchEvent(new Event('playing'));
})
} else {
return Promise.resolve();
}
}
pause() {
if (!this.paused) {
this.paused = true;
this.dispatchEvent(new Event('pause'));
return window.bridgeInvokeAsync(this.bridgeId, "VideoElement", "pause", {
}).then((result) => {
})
}
}
canPlayType(type) {
return 'probably';
}
_getMedia() {
return window.mediaSourceMap[this.src];
}
/*_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_js__WEBPACK_IMPORTED_MODULE_1__.TextTrackStub(kind, label, language);
this.textTracks._add(textTrack);
return textTrack;
}
}
/***/ }),
/***/ "./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");
/* harmony import */ var _VideoElementStub_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./VideoElementStub.js */ "./src/VideoElementStub.js");
/* harmony import */ var _MediaSourceStub_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./MediaSourceStub.js */ "./src/MediaSourceStub.js");
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 = window.nextInternalId;
window.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;
}
window.bridgeInvokeAsync = bridgeInvokeAsync
function bridgeInvokeCallback(callbackId, result) {
const callback = window.bridgeCallbackMap[callbackId];
if (callback) {
callback(result);
}
}
var useStubs = true;
window.nextInternalId = 0;
window.mediaSourceMap = {};
// Replace the global MediaSource with our stub
if (useStubs && typeof window !== 'undefined') {
window.MediaSource = _MediaSourceStub_js__WEBPACK_IMPORTED_MODULE_2__.MediaSourceStub;
window.ManagedMediaSource = _MediaSourceStub_js__WEBPACK_IMPORTED_MODULE_2__.MediaSourceStub;
window.SourceBuffer = _MediaSourceStub_js__WEBPACK_IMPORTED_MODULE_2__.SourceBufferStub;
URL.createObjectURL = function(ms) {
const url = "blob:mock-media-source:" + ms.internalId;
window.mediaSourceMap[url] = ms;
return url;
};
}
function postPlayerEvent(eventName, eventData) {
if (window.webkit && 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,
maxFragLookUpTolerance: 0.001,
nudgeMaxRetry: 10000
});
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_js__WEBPACK_IMPORTED_MODULE_1__.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 <net_loc> is non-empty, we skip to
// Step 7. Otherwise, the embedded URL inherits the <net_loc>
// (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 <params> is non-empty, we skip to
// step 7; otherwise, it inherits the <params> of the base
// URL (if any) and
if (!relativeParts.params) {
builtParts.params = baseParts.params;
// 5b) if the embedded URL's <query> is non-empty, we skip to
// step 7; otherwise, it inherits the <query> 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 "<segment>/../", where <segment> 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 "<segment>/..", where <segment> is a
// complete path segment not equal to "..", that
// "<segment>/.." 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:[<media type][;attribute=value][;base64],<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: <text string>\0<binary data>
*/
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 <iz@onicos.co.jp>
* 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:<duration>,<title>), 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,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QUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx3QkFBd0I7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEI7QUFDMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047O0FBRUE7QUFDQSxFQUFFO0FBQ0YsRUFBRTs7QUFFRjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0Isc0JBQXNCO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxDQUFDLEdBQUc7O0FBRUo7QUFDQSwrREFBK0QsOEJBQThCO0FBQzdGOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsQ0FBQyxHQUFHO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUMsR0FBRzs7QUFFSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHVDQUF1QyxLQUFLO0FBQzVDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELEdBQUcsc0JBQXNCLFNBQVM7QUFDdEYsTUFBTTtBQUNOO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLDRCQUE0QjtBQUNsRDtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsSUFBSSxzQ0FBc0MsaUJBQWlCO0FBQzlHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEM7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxrRUFBa0Usb0NBQW9DO0FBQ3RHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDBFQUEwRSxtQ0FBbUM7QUFDN0c7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQSx5RUFBeUUsMkJBQTJCO0FBQ3BHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5QkFBeUIsa0JBQWtCO0FBQzNDO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRDtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0EsQ0FBQzs7QUFFRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLFVBQVU7QUFDdkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQkFBK0IsVUFBVSxNQUFNO0FBQy9DO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQSwrQkFBK0IsVUFBVSxNQUFNO0FBQy9DO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQixnQkFBZ0I7QUFDaEIsZ0JBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLG1CQUFtQjtBQUNyQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2IsYUFBYSxZQUFZLEdBQUc7QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1gsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2IsYUFBYSxZQUFZLEdBQUc7QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isa0JBQWtCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixRQUFRO0FBQzFCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixRQUFRO0FBQzFCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLHFCQUFxQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixrQkFBa0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCO0FBQ2xCO0FBQ0E7QUFDQSxvQkFBb0I7QUFDcEI7QUFDQTtBQUNBLCtCQUErQjtBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRCxJQUFJO0FBQ3JEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0RBQXdELG9CQUFvQixvQkFBb0Isd0JBQXdCLEtBQUssbUJBQW1CO0FBQ2hKO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVCxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0Isa0JBQWtCO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixrQkFBa0I7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isa0JBQWtCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBLGFBQWE7QUFDYixNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixpQkFBaUI7QUFDbkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTCxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCO0FBQ3hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQzs7QUFFaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZCQUE2QixrQkFBa0I7QUFDL0M7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQjtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0wsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNOztBQUVOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLDZDQUE2QyxhQUFhLHFCQUFxQixVQUFVO0FBQ3pGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxnQkFBZ0I7QUFDaEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0Esd0JBQXdCLFFBQVE7QUFDaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixZQUFZO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWMsZUFBZTtBQUM3QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixTQUFTO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxQkFBcUIsb0JBQW9CO0FBQ3pDLDRCQUE0QjtBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHdCQUF3QjtBQUM1QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7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Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQixtQkFBbUI7QUFDckM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLDhEQUE4RDs7QUFFOUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsbUJBQW1CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCLCtFQUErRSxXQUFXO0FBQzFGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixtQkFBbUI7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDLEtBQUs7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCLGdFQUFnRSxPQUFPO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQjtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQSxnQkFBZ0I7QUFDaEIsdUVBQXVFLE9BQU87QUFDOUU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUNBQXFDLFlBQVksR0FBRyxNQUFNO0FBQzFELGtCQUFrQjtBQUNsQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdEQUF3RCxPQUFPO0FBQy9EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQ0FBMkM7QUFDM0M7QUFDQTtBQUNBLGdDQUFnQyxZQUFZO0FBQzVDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWUsS0FBSztBQUNwQjtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBOEIsSUFBSTtBQUNsQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FB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QTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscURBQXFELFNBQVM7QUFDOUQ7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBa0IsMEJBQTBCO0FBQzVDO0FBQ0EsMENBQTBDO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUVBQXFFLEVBQUU7QUFDdkU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHVCQUF1QjtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixvQkFBb0I7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLG1CQUFtQjtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQ0FBZ0MsSUFBSTtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUNBQXVDLElBQUk7QUFDM0M7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGdCQUFnQjtBQUNwQztBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IsdUJBQXVCO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0I7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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QUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1Isa0NBQWtDLElBQUk7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1YsZ0RBQWdELHNCQUFzQjtBQUN0RTtBQUNBO0FB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QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1EQUFtRCxTQUFTO0FBQzVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQSxTQUFTO0FBQ1Q7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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QTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUMsSUFBSTtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLDhCQUE4QixJQUFJO0FBQ2xDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isc0JBQXNCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWSxjQUFjLEdBQUcsZUFBZSxHQUFHLFlBQVk7QUFDM0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx3QkFBd0IscUJBQXFCO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQSxXQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IscUJBQXFCO0FBQzNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSwwQ0FBMEMsU0FBUztBQUNuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQ0FBMEMsU0FBUztBQUNuRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyRkFBMkYsWUFBWTtBQUN2RztBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtB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QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLE9BQU87QUFDM0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCLFNBQVM7QUFDekI7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0IsU0FBUztBQUN6QjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsZ0JBQWdCO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1QkFBdUIsbUJBQW1CO0FBQzFDO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxrQkFBa0IsYUFBYTtBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSx1QkFBdUI7O0FBRXZCO0FBQ0E7QUFDQTtBQUNBLElBQUksSUFBSTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsMEVBQTBFLFNBQVMsSUFBSSxZQUFZO0FBQ25HO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtCQUErQixJQUFJO0FBQ25DO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsU0FBUztBQUM3QixpQkFBaUIsc0JBQXNCLEdBQUcsb0JBQW9CO0FBQzlEO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBDQUEwQyxVQUFVO0FBQ3BELDRDQUE0QyxVQUFVO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxpQ0FBaUMsbUVBQW1FLFdBQVcsTUFBTTtBQUNySDtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7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Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDLGNBQWM7QUFDN0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1FQUFtRSxRQUFRLFVBQVUsd0JBQXdCO0FBQzdHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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QUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQyxpQkFBaUI7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdHQUF3RztBQUN4RztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBbUM7QUFDbkMsbUNBQW1DO0FBQ25DLGtDQUFrQztBQUNsQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0EsZUFBZTtBQUNmOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQW1CO0FBQ25CO0FBQ0EsNEJBQTRCO0FBQzVCOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxtQkFBbUI7QUFDbkI7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxtREFBbUQ7QUFDbkQsMENBQTBDO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsMEJBQTBCO0FBQzFCLCtCQUErQix1Q0FBdUM7QUFDdEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsK0JBQStCO0FBQy9CO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGlDQUFpQztBQUNqQztBQUNBO0FBQ0EsNkJBQTZCO0FBQzdCLE1BQU07QUFDTixnQ0FBZ0M7QUFDaEM7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsV0FBVztBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9DQUFvQztBQUNwQyxpQkFBaUI7QUFDakIsaUJBQWlCO0FBQ2pCLGlCQUFpQjtBQUNqQixlQUFlO0FBQ2Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7O0FBRVIsaUJBQWlCO0FBQ2pCLGlCQUFpQjtBQUNqQixtQkFBbUI7QUFDbkI7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHNCQUFzQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxlQUFlO0FBQ2Y7QUFDQTtBQUNBLGlCQUFpQjtBQUNqQixNQUFNO0FBQ04sbUJBQW1CO0FBQ25CLGdCQUFnQjtBQUNoQixnQkFBZ0I7QUFDaEI7QUFDQSxrQkFBa0Isb0NBQW9DO0FBQ3REO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsZUFBZTtBQUNmLGlCQUFpQjtBQUNqQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTs7QUFFTixpQkFBaUI7QUFDakI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QixPQUFPO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixvQ0FBb0M7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw2QkFBNkIscUNBQXFDO0FBQ2xFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBFQUEwRSxXQUFXO0FBQ3JGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFzQixZQUFZO0FBQ2xDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGlDQUFpQyxhQUFhO0FBQzlDO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELE9BQU8scUJBQXFCLFdBQVcsZ0NBQWdDLFlBQVk7QUFDdkk7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF5RCxnQkFBZ0I7QUFDekU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiLFlBQVk7QUFDWjtBQUNBO0FBQ0EsU0FBUztBQUNULFFBQVE7QUFDUjtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxjQUFjO0FBQzdEO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtELGtCQUFrQjtBQUNwRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrRUFBa0UsT0FBTztBQUN6RSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWUsa0JBQWtCO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsaUNBQWlDO0FBQ2pDO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0NBQWdDLGNBQWM7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBLGdCQUFnQixNQUFNO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlCQUF5QixzQ0FBc0M7QUFDL0Q7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQ0FBMkMsYUFBYTtBQUN4RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQyxpQkFBaUI7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBOEI7O0FBRTlCLDZEQUE2RDtBQUM3RCx5REFBeUQ7QUFDekQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIsU0FBUztBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxnQkFBZ0Isc0JBQXNCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLGdCQUFnQixzQkFBc0I7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw0RUFBNEUsdUJBQXVCO0FBQ25HO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZ0IsU0FBUztBQUN6QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLGlEQUFpRDtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLDZDQUE2QztBQUM3QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrREFBa0Qsb0JBQW9CLFNBQVMsUUFBUTtBQUN2RjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaLG1FQUFtRSxRQUFRO0FBQzNFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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O0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLHFEQUFxRDtBQUNyRDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLGlDQUFpQztBQUNqQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRDQUE0Qyx1Q0FBdUM7QUFDbkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVDQUF1QyxZQUFZLDZDQUE2QyxPQUFPO0FBQ3ZHO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0NBQXdDLFlBQVk7QUFDcEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEVBQUU7QUFDRjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxDQUFDO0FBQ0Q7QUFDQTtBQUNBLENBQUM7QUFDRDtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQSxDQUFDO0FBQ0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsNkNBQTZDO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSw2Q0FBNkM7QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQ0FBb0MsY0FBYztBQUNsRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTixvREFBb0QsYUFBYSxFQUFFLG9EQUFvRCxXQUFXLGdCQUFnQjtBQUNsSjtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsNENBQTRDLFNBQVM7QUFDckQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxpQkFBaUI7QUFDakI7QUFDQSxDQUFDO0FBQ0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLHFCQUFxQjs7QUFFckI7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsWUFBWSxVQUFVO0FBQ3RCLFlBQVksR0FBRztBQUNmLFlBQVksU0FBUztBQUNyQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVksY0FBYztBQUMxQixZQUFZLGlCQUFpQjtBQUM3QixZQUFZLFVBQVU7QUFDdEIsWUFBWSxHQUFHO0FBQ2YsWUFBWSxTQUFTO0FBQ3JCLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGNBQWM7QUFDMUIsWUFBWSxpQkFBaUI7QUFDN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixjQUFjLE9BQU87QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBLDJEQUEyRCxPQUFPO0FBQ2xFO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixjQUFjLFFBQVE7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixjQUFjLFNBQVM7QUFDdkI7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLDJDQUEyQyxTQUFTO0FBQ3BEO0FBQ0E7O0FBRUE7QUFDQSxLQUFLO0FBQ0w7QUFDQTs7QUFFQSxpQkFBaUIsWUFBWTtBQUM3Qjs7QUFFQTtBQUNBLDZEQUE2RDtBQUM3RCxpRUFBaUU7QUFDakUscUVBQXFFO0FBQ3JFLHlFQUF5RTtBQUN6RTtBQUNBLDREQUE0RCxTQUFTO0FBQ3JFO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLGlCQUFpQjtBQUM3QixZQUFZLFVBQVU7QUFDdEIsWUFBWSxHQUFHO0FBQ2YsY0FBYyxjQUFjO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsWUFBWSxpQkFBaUI7QUFDN0IsWUFBWSxVQUFVO0FBQ3RCLFlBQVksR0FBRztBQUNmLGNBQWMsY0FBYztBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVksaUJBQWlCO0FBQzdCLFlBQVksVUFBVTtBQUN0QixZQUFZLEdBQUc7QUFDZixZQUFZLFNBQVM7QUFDckIsY0FBYyxjQUFjO0FBQzVCO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTCw2REFBNkQsWUFBWTtBQUN6RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVksaUJBQWlCO0FBQzdCLGNBQWMsY0FBYztBQUM1QjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxFQUFFOztBQUVGO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQ7QUFDbkQsbURBQW1EO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZDQUE2QyxtQkFBbUIsT0FBTyxHQUFHO0FBQzFFO0FBQ0EsWUFBWTtBQUNaLG9EQUFvRCxHQUFHO0FBQ3ZEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBLHVDQUF1QyxnQkFBZ0IsR0FBRyxlQUFlLEdBQUcsYUFBYTtBQUN6RjtBQUNBLHFDQUFxQyxHQUFHO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWCxVQUFVO0FBQ1YsMkNBQTJDLEdBQUc7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMkNBQTJDLFVBQVUsMkNBQTJDLGNBQWMsS0FBSyxnQkFBZ0IsU0FBUyxpQkFBaUIsTUFBTTtBQUNuSyx5QkFBeUI7QUFDekIsdUJBQXVCO0FBQ3ZCLHNCQUFzQjtBQUN0Qiw4QkFBOEI7QUFDOUIsc0JBQXNCO0FBQ3RCLDZCQUE2QixrQkFBa0I7QUFDL0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1AsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQSxTQUFTO0FBQ1QsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLFNBQVM7QUFDVCxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxREFBcUQsNkJBQTZCO0FBQ2xGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtCQUFrQix1QkFBdUI7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLDZCQUE2Qjs7QUFFN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0NBQWtDLElBQUksbUJBQW1CLFFBQVE7QUFDakU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLCtEQUErRCwyQkFBMkI7QUFDMUY7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBLCtDQUErQyxRQUFRLHFDQUFxQyxrQkFBa0I7QUFDOUc7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlEQUFpRCxRQUFRLE1BQU0sWUFBWSx3Q0FBd0MsZ0JBQWdCO0FBQ25JO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtB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QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0Esa0VBQWtFLFFBQVE7QUFDMUU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0JBQStCLFNBQVMsVUFBVSxtQkFBbUIsR0FBRyxpQkFBaUIsR0FBRyxpQ0FBaUMsc0JBQXNCLEdBQUcseUJBQXlCLFFBQVEsWUFBWSx5QkFBeUI7QUFDNU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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Q0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSwrRUFBK0UsWUFBWSxJQUFJLFNBQVM7QUFDeEc7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFnQixn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Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FB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QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0Isa0JBQWtCO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IscUJBQXFCO0FBQ3pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxlQUFlLFNBQVM7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLG9CQUFvQjtBQUNwQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLHVDQUF1QztBQUMzRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQiwwQkFBMEI7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7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Q0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLEVBQUUsTUFBTSxFQUFFO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtEO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLGNBQWM7QUFDbEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCLElBQUk7QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDLFFBQVE7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLHNDQUFzQztBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9DQUFvQzs7QUFFcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVU7QUFDVjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWM7QUFDZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELFdBQVc7QUFDL0Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUEwQjtBQUMxQjtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTs7QUFFQTs7QUFFQTtBQUNBLHlCQUF5QixHQUFHLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFOztBQUVwRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDLElBQUk7QUFDN0M7QUFDQSxHQUFHLElBQUk7QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHLElBQUk7QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFEQUFxRCxLQUFLO0FBQzFEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBbUMsSUFBSTtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTs7QUFFQSw2Q0FBNkM7QUFDN0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBLHNCQUFzQiw2QkFBNkI7QUFDbkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IsdUJBQXVCO0FBQzdDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBNEIsd0JBQXdCO0FBQ3BEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwRUFBMEUsNEJBQTRCO0FBQ3RHO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLG1CQUFtQjtBQUN6RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTCwyQ0FBMkMsTUFBTTtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0EsNkNBQTZDLE1BQU07QUFDbkQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1AsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFvQixvQkFBb0I7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsV0FBVztBQUMvQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4Q0FBOEM7QUFDOUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsd0RBQXdEO0FBQ3hEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0RBQW9ELFNBQVMsSUFBSSx3QkFBd0IsSUFBSSwwQkFBMEIsWUFBWSxnQkFBZ0IsR0FBRyxpQkFBaUI7QUFDdks7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDO0FBQy9DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLG1CQUFtQjtBQUN2QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdFQUF3RSxVQUFVO0FBQ2xGO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTiw4REFBOEQsVUFBVTtBQUN4RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FB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QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUEseUNBQXlDOztBQUV6Qzs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhLG9CQUFvQjtBQUNqQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSx5Q0FBeUM7QUFDdEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQStDO0FBQy9DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOENBQThDO0FBQzlDO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0RBQXNELFlBQVk7QUFDbEU7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGlFQUFpRTtBQUNqRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWEsa0RBQWtEO0FBQy9EOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOERBQThEO0FBQzlEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0I7QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZSxFQUFFLGtCQUFrQixHQUFHO0FBQ3RDO0FBQ0EsYUFBYSxFQUFFLGtCQUFrQixHQUFHLHlCQUF5QjtBQUM3RCxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjLCtCQUErQixFQUFFLDhCQUE4QjtBQUM3RSxJQUFJO0FBQ0o7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFhLHlDQUF5QyxHQUFHLDhCQUE4QjtBQUN2Rjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUM7QUFDRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRyxXQUFXLG1CQUFtQjtBQUNqQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUk7QUFDSjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQztBQUNoQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDO0FBQ3RDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0lBQWtJO0FBQ2xJO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7QUFDQTtBQUNBO0FBQ0EsR0FBRyxJQUFJO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUM7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLFdBQVcsR0FBRywyQkFBMkI7QUFDckQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZLElBQUksRUFBRSxVQUFVLEVBQUUsTUFBTTtBQUNwQzs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1QsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEI7QUFDMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTyxJQUFJO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07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QUNBLE9BQU87QUFDUDtBQUNBLHVEQUF1RCxZQUFZO0FBQ25FO0FBQ0E7QUFDQTtBQUNBLDhDQUE4QyxJQUFJO0FBQ2xEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EscUNBQXFDO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0EsT0FBTztBQUNQLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1osNEJBQTRCLFFBQVEsZ0JBQWdCLFlBQVk7QUFDaEU7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQSwyQ0FBMkMsb0VBQW9FO0FBQy9HO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLG1CQUFtQiw4Q0FBOEMsZ0JBQWdCLHVDQUF1QyxhQUFhLFlBQVksR0FBRyx5QkFBeUIsS0FBSyxnQkFBZ0I7QUFDbE07QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNDQUFzQyxLQUFLO0FBQzNDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxHQUFHO0FBQ25DO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUCxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFVO0FBQ1Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXlDO0FBQ3pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBb0IsK0JBQStCO0FBQ25EO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdCQUF3QixzQkFBc0I7QUFDOUM7QUFDQTtBQUNBLFlBQVk7QUFDWjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCO0FBQ2hCO0FBQ0Esc0JBQXNCO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsQ0FBQyx1QkFBdUI7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUM7QUFDRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSwwQkFBMEIscUNBQXFDO0FBQy9EO0FBQ0E7QUFDQTtBQUNBLG1DQUFtQyxLQUFLLFNBQVMsUUFBUTtBQUN6RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMO0FBQ0EscUNBQXFDLG9CQUFvQixvQ0FBb0MsV0FBVyxLQUFLLHVDQUF1QztBQUNwSjtBQUNBLEdBQUc7QUFDSCx5Q0FBeUM7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FB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QUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFEQUFxRCxZQUFZLFVBQVUsNkJBQTZCO0FBQ3hHO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtB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O0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjO0FBQ2Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07O0FBRU47QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxvQ0FBb0MsT0FBTyxhQUFhLFdBQVc7QUFDbkU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSLDZCQUE2QixTQUFTLFdBQVcsWUFBWTtBQUM3RDtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtDQUFrQyx1QkFBdUI7QUFDekQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsZ0VBQWdFLHNDQUFzQztBQUN0RztBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBLHVEQUF1RDtBQUN2RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQSx5REFBeUQsV0FBVztBQUNwRTtBQUNBO0FBQ0Esc0JBQXNCLFlBQVksVUFBVSxtQkFBbUIsR0FBRyxpQkFBaUIsR0FBRyxpQ0FBaUMsc0JBQXNCLEdBQUcseUJBQXlCLFFBQVEsUUFBUSxtQkFBbUIsSUFBSSxpQkFBaUIsYUFBYSxTQUFTO0FBQ3ZQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0EsbUZBQW1GLFNBQVMsV0FBVyxZQUFZO0FBQ25IO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxQ0FBcUMsU0FBUyxXQUFXLFlBQVk7QUFDckU7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSwrQkFBK0IsU0FBUyxNQUFNLGlCQUFpQixHQUFHLGNBQWMsVUFBVSxXQUFXLE9BQU8sUUFBUTtBQUNwSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBLDZDQUE2QyxLQUFLO0FBQ2xEO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUE0QixRQUFRLEVBQUUsaUNBQWlDLFdBQVcsWUFBWSw4Q0FBOEMsV0FBVztBQUN2SjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0NBQXNDLGNBQWMsdUJBQXVCLFlBQVk7QUFDdkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0RBQWdELE9BQU87QUFDdkQ7QUFDQTtBQUNBO0FBQ0EsZ0RBQWdELGVBQWUsb0JBQW9CLFlBQVk7QUFDL0Y7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBTTtBQUNOO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxTQUFTO0FBQ1Q7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBVTtBQUNWO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYztBQUNkO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFRO0FBQ1I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFZO0FBQ1o7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWtELFdBQVc7QUFDN0Q7QUFDQTtBQUNBLG1EQUFtRCx3QkFBd0IsU0FBUyxXQUFXO0FBQy9GO0FBQ0E7QUFDQTtBQUNBLCtDQUErQyxnQkFBZ0IsbUNBQW1DLGlCQUFpQixHQUFHLDhCQUE4QixHQUFHLFlBQVk7QUFDbks7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQ0FBK0MsZ0JBQWdCLDBCQUEwQiw4QkFBOEIsR0FBRyxZQUFZO0FBQ3RJO0FBQ0E7QUFDQSxvREFBb0QscUJBQXFCLDBCQUEwQixvQkFBb0IsR0FBRyxpQkFBaUI7QUFDM0k7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsUUFBUTtBQUNSO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQSxhQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkJBQTZCO0FBQzdCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFNO0FBQ047QUFDQTtBQUNBLFFBQVE7QUFDUjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtB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Q3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRTRvQjtBQUM1b0IiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9teWhscy8uL3NyYy9NZWRpYVNvdXJjZVN0dWIuanMiLCJ3ZWJwYWNrOi8vbXlobHMvLi9zcmMvVGV4dFRyYWNrU3R1Yi5qcyIsIndlYnBhY2s6Ly9teWhscy8uL3NyYy9UaW1lUmFuZ2VzU3R1Yi5qcyIsIndlYnBhY2s6Ly9teWhscy8uL3NyYy9WaWRlb0VsZW1lbnRTdHViLmpzIiwid2VicGFjazovL215aGxzLy4vc3JjL2luZGV4LmpzIiwid2VicGFjazovL215aGxzLy4vbm9kZV9tb2R1bGVzL2hscy5qcy9kaXN0L2hscy5tanMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgVGltZVJhbmdlc1N0dWIgfSBmcm9tIFwiLi9UaW1lUmFuZ2VzU3R1Yi5qc1wiXG5cbmZ1bmN0aW9uIGJ5dGVzVG9CYXNlNjQoYnl0ZXMpIHtcbiAgICBjb25zdCBiaW5TdHJpbmcgPSBBcnJheS5mcm9tKGJ5dGVzLCAoYnl0ZSkgPT5cbiAgICAgICAgU3RyaW5nLmZyb21Db2RlUG9pbnQoYnl0ZSksXG4gICAgKS5qb2luKFwiXCIpO1xuICAgIHJldHVybiBidG9hKGJpblN0cmluZyk7XG59XG5cbmV4cG9ydCBjbGFzcyBTb3VyY2VCdWZmZXJMaXN0U3R1YiBleHRlbmRzIEV2ZW50VGFyZ2V0IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5fYnVmZmVycyA9IFtdO1xuICAgIH1cblxuICAgIF9hZGQoYnVmZmVyKSB7XG4gICAgICAgIHRoaXMuX2J1ZmZlcnMucHVzaChidWZmZXIpO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdhZGRzb3VyY2VidWZmZXInKSk7XG4gICAgfVxuXG4gICAgX3JlbW92ZShidWZmZXIpIHtcbiAgICAgICAgY29uc3QgaW5kZXggPSB0aGlzLl9idWZmZXJzLmluZGV4T2YoYnVmZmVyKTtcbiAgICAgICAgaWYgKGluZGV4ID09PSAtMSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuX2J1ZmZlcnMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgncmVtb3Zlc291cmNlYnVmZmVyJykpO1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG5cbiAgICBnZXQgbGVuZ3RoKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fYnVmZmVycy5sZW5ndGg7XG4gICAgfVxuXG4gICAgaXRlbShpbmRleCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fYnVmZmVyc1tpbmRleF07XG4gICAgfVxuXG4gICAgW1N5bWJvbC5pdGVyYXRvcl0oKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9idWZmZXJzW1N5bWJvbC5pdGVyYXRvcl0oKTtcbiAgICB9XG59XG5cbmV4cG9ydCBjbGFzcyBTb3VyY2VCdWZmZXJTdHViIGV4dGVuZHMgRXZlbnRUYXJnZXQge1xuICAgIGNvbnN0cnVjdG9yKG1lZGlhU291cmNlLCBtaW1lVHlwZSkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLm1lZGlhU291cmNlID0gbWVkaWFTb3VyY2U7XG4gICAgICAgIHRoaXMubWltZVR5cGUgPSBtaW1lVHlwZTtcbiAgICAgICAgdGhpcy51cGRhdGluZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmJ1ZmZlcmVkID0gbmV3IFRpbWVSYW5nZXNTdHViKCk7XG4gICAgICAgIHRoaXMudGltZXN0YW1wT2Zmc2V0ID0gMDtcbiAgICAgICAgdGhpcy5hcHBlbmRXaW5kb3dTdGFydCA9IDA7XG4gICAgICAgIHRoaXMuYXBwZW5kV2luZG93RW5kID0gSW5maW5pdHk7XG5cbiAgICAgICAgdGhpcy5icmlkZ2VJZCA9IHdpbmRvdy5uZXh0SW50ZXJuYWxJZDtcbiAgICAgICAgd2luZG93Lm5leHRJbnRlcm5hbElkICs9IDE7XG4gICAgICAgIHdpbmRvdy5icmlkZ2VPYmplY3RNYXBbdGhpcy5icmlkZ2VJZF0gPSB0aGlzO1xuXG4gICAgICAgIHdpbmRvdy5icmlkZ2VJbnZva2VBc3luYyh0aGlzLmJyaWRnZUlkLCBcIlNvdXJjZUJ1ZmZlclwiLCBcImNvbnN0cnVjdG9yXCIsIHtcbiAgICAgICAgICAgIFwibWltZVR5cGVcIjogbWltZVR5cGVcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgYXBwZW5kQnVmZmVyKGRhdGEpIHtcbiAgICAgICAgaWYgKHRoaXMudXBkYXRpbmcpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBET01FeGNlcHRpb24oJ1NvdXJjZUJ1ZmZlciBpcyB1cGRhdGluZycsICdJbnZhbGlkU3RhdGVFcnJvcicpO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMudXBkYXRpbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCd1cGRhdGVzdGFydCcpKTtcblxuICAgICAgICB3aW5kb3cuYnJpZGdlSW52b2tlQXN5bmModGhpcy5icmlkZ2VJZCwgXCJTb3VyY2VCdWZmZXJcIiwgXCJhcHBlbmRCdWZmZXJcIiwge1xuICAgICAgICAgICAgXCJkYXRhXCI6IGJ5dGVzVG9CYXNlNjQoZGF0YSlcbiAgICAgICAgfSkudGhlbigocmVzdWx0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB1cGRhdGVkUmFuZ2VzID0gcmVzdWx0W1wicmFuZ2VzXCJdO1xuICAgICAgICAgICAgdmFyIHJhbmdlcyA9IFtdO1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB1cGRhdGVkUmFuZ2VzLmxlbmd0aDsgaSArPSAyKSB7XG4gICAgICAgICAgICAgICAgcmFuZ2VzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICBzdGFydDogdXBkYXRlZFJhbmdlc1tpXSxcbiAgICAgICAgICAgICAgICAgICAgZW5kOiB1cGRhdGVkUmFuZ2VzW2kgKyAxXVxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5idWZmZXJlZC5fcmFuZ2VzID0gcmFuZ2VzO1xuXG4gICAgICAgICAgICB0aGlzLm1lZGlhU291cmNlLl9yZW9wZW4oKTtcblxuICAgICAgICAgICAgdGhpcy51cGRhdGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlJykpO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlZW5kJykpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBhYm9ydCgpIHtcbiAgICAgICAgaWYgKHRoaXMudXBkYXRpbmcpIHtcbiAgICAgICAgICAgIHRoaXMudXBkYXRpbmcgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ2Fib3J0JykpO1xuXG4gICAgICAgICAgICB3aW5kb3cuYnJpZGdlSW52b2tlQXN5bmModGhpcy5icmlkZ2VJZCwgXCJTb3VyY2VCdWZmZXJcIiwgXCJhYm9ydFwiLCB7fSkudGhlbigocmVzdWx0KSA9PiB7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJlbW92ZShzdGFydCwgZW5kKSB7XG4gICAgICAgIGlmICh0aGlzLnVwZGF0aW5nKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCdTb3VyY2VCdWZmZXIgaXMgdXBkYXRpbmcnLCAnSW52YWxpZFN0YXRlRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnVwZGF0aW5nID0gdHJ1ZTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlc3RhcnQnKSk7XG5cbiAgICAgICAgd2luZG93LmJyaWRnZUludm9rZUFzeW5jKHRoaXMuYnJpZGdlSWQsIFwiU291cmNlQnVmZmVyXCIsIFwicmVtb3ZlXCIsIHtcbiAgICAgICAgICAgIFwic3RhcnRcIjogc3RhcnQsXG4gICAgICAgICAgICBcImVuZFwiOiBlbmRcbiAgICAgICAgfSkudGhlbigocmVzdWx0KSA9PiB7XG4gICAgICAgICAgICBjb25zdCB1cGRhdGVkUmFuZ2VzID0gcmVzdWx0W1wicmFuZ2VzXCJdO1xuICAgICAgICAgICAgdmFyIHJhbmdlcyA9IFtdO1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB1cGRhdGVkUmFuZ2VzLmxlbmd0aDsgaSArPSAyKSB7XG4gICAgICAgICAgICAgICAgcmFuZ2VzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICBzdGFydDogdXBkYXRlZFJhbmdlc1tpXSxcbiAgICAgICAgICAgICAgICAgICAgZW5kOiB1cGRhdGVkUmFuZ2VzW2kgKyAxXVxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5idWZmZXJlZC5fcmFuZ2VzID0gcmFuZ2VzO1xuXG4gICAgICAgICAgICB0aGlzLm1lZGlhU291cmNlLl9yZW9wZW4oKTtcblxuICAgICAgICAgICAgdGhpcy51cGRhdGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlJykpO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndXBkYXRlZW5kJykpO1xuICAgICAgICB9KTtcbiAgICB9XG59XG5cbmV4cG9ydCBjbGFzcyBNZWRpYVNvdXJjZVN0dWIgZXh0ZW5kcyBFdmVudFRhcmdldCB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHN1cGVyKCk7XG5cbiAgICAgICAgdGhpcy5pbnRlcm5hbElkID0gd2luZG93Lm5leHRJbnRlcm5hbElkO1xuICAgICAgICB3aW5kb3cubmV4dEludGVybmFsSWQgKz0gMTtcblxuICAgICAgICB0aGlzLmJyaWRnZUlkID0gd2luZG93Lm5leHRJbnRlcm5hbElkO1xuICAgICAgICB3aW5kb3cubmV4dEludGVybmFsSWQgKz0gMTtcbiAgICAgICAgd2luZG93LmJyaWRnZU9iamVjdE1hcFt0aGlzLmJyaWRnZUlkXSA9IHRoaXM7XG5cbiAgICAgICAgdGhpcy5zb3VyY2VCdWZmZXJzID0gbmV3IFNvdXJjZUJ1ZmZlckxpc3RTdHViKCk7XG4gICAgICAgIHRoaXMuYWN0aXZlU291cmNlQnVmZmVycyA9IG5ldyBTb3VyY2VCdWZmZXJMaXN0U3R1YigpO1xuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSAnY2xvc2VkJztcbiAgICAgICAgdGhpcy5fZHVyYXRpb24gPSBOYU47XG5cbiAgICAgICAgd2luZG93LmJyaWRnZUludm9rZUFzeW5jKHRoaXMuYnJpZGdlSWQsIFwiTWVkaWFTb3VyY2VcIiwgXCJjb25zdHJ1Y3RvclwiLCB7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIFNpbXVsYXRlIGFzeW5jaHJvbm91cyBvcGVuaW5nIG9mIE1lZGlhU291cmNlXG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ29wZW4nO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnc291cmNlb3BlbicpKTtcbiAgICAgICAgfSwgMCk7XG4gICAgfVxuXG4gICAgc3RhdGljIGlzVHlwZVN1cHBvcnRlZChtaW1lVHlwZSkge1xuICAgICAgICAvLyBBc3N1bWUgYWxsIE1JTUUgdHlwZXMgYXJlIHN1cHBvcnRlZCBpbiB0aGlzIHN0dWJcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgYWRkU291cmNlQnVmZmVyKG1pbWVUeXBlKSB7XG4gICAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgIT09ICdvcGVuJykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IERPTUV4Y2VwdGlvbignTWVkaWFTb3VyY2UgaXMgbm90IG9wZW4nLCAnSW52YWxpZFN0YXRlRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBzb3VyY2VCdWZmZXIgPSBuZXcgU291cmNlQnVmZmVyU3R1Yih0aGlzLCBtaW1lVHlwZSk7XG4gICAgICAgIHRoaXMuc291cmNlQnVmZmVycy5fYWRkKHNvdXJjZUJ1ZmZlcik7XG4gICAgICAgIHRoaXMuYWN0aXZlU291cmNlQnVmZmVycy5fYWRkKHNvdXJjZUJ1ZmZlcik7XG4gICAgICAgIHJldHVybiBzb3VyY2VCdWZmZXI7XG4gICAgfVxuXG4gICAgcmVtb3ZlU291cmNlQnVmZmVyKHNvdXJjZUJ1ZmZlcikge1xuICAgICAgICBpZiAoIXRoaXMuc291cmNlQnVmZmVycy5fcmVtb3ZlKHNvdXJjZUJ1ZmZlcikpIHtcbiAgICAgICAgdGhyb3cgbmV3IERPTUV4Y2VwdGlvbignU291cmNlQnVmZmVyIG5vdCBmb3VuZCcsICdOb3RGb3VuZEVycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5hY3RpdmVTb3VyY2VCdWZmZXJzLl9yZW1vdmUoc291cmNlQnVmZmVyKTtcbiAgICB9XG5cbiAgICBlbmRPZlN0cmVhbShlcnJvcikge1xuICAgICAgICBpZiAodGhpcy5yZWFkeVN0YXRlICE9PSAnb3BlbicpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBET01FeGNlcHRpb24oJ01lZGlhU291cmNlIGlzIG5vdCBvcGVuJywgJ0ludmFsaWRTdGF0ZUVycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gJ2VuZGVkJztcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnc291cmNlZW5kZWQnKSk7XG4gICAgfVxuXG4gICAgX3Jlb3BlbigpIHtcbiAgICAgICAgaWYgKHRoaXMucmVhZHlTdGF0ZSAhPT0gJ29wZW4nKSB7XG4gICAgICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSAnb3Blbic7XG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdzb3VyY2VvcGVuJykpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgc2V0IGR1cmF0aW9uKHZhbHVlKSB7XG4gICAgICAgIGlmICh0aGlzLnJlYWR5U3RhdGUgPT09ICdjbG9zZWQnKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCdNZWRpYVNvdXJjZSBpcyBjbG9zZWQnLCAnSW52YWxpZFN0YXRlRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9kdXJhdGlvbiA9IHZhbHVlO1xuXG4gICAgICAgIHdpbmRvdy5icmlkZ2VJbnZva2VBc3luYyh0aGlzLmJyaWRnZUlkLCBcIk1lZGlhU291cmNlXCIsIFwic2V0RHVyYXRpb25cIiwge1xuICAgICAgICAgICAgXCJkdXJhdGlvblwiOiB2YWx1ZVxuICAgICAgICB9KS50aGVuKChyZXN1bHQpID0+IHtcbiAgICAgICAgfSlcbiAgICB9XG5cbiAgICBnZXQgZHVyYXRpb24oKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9kdXJhdGlvbjtcbiAgICB9XG59XG4iLCJcbmV4cG9ydCBjbGFzcyBUZXh0VHJhY2tTdHViIGV4dGVuZHMgRXZlbnRUYXJnZXQge1xuICAgIGNvbnN0cnVjdG9yKGtpbmQgPSAnJywgbGFiZWwgPSAnJywgbGFuZ3VhZ2UgPSAnJykge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aGlzLmtpbmQgPSBraW5kO1xuICAgICAgICB0aGlzLmxhYmVsID0gbGFiZWw7XG4gICAgICAgIHRoaXMubGFuZ3VhZ2UgPSBsYW5ndWFnZTtcbiAgICAgICAgdGhpcy5tb2RlID0gJ2Rpc2FibGVkJzsgLy8gJ2Rpc2FibGVkJywgJ2hpZGRlbicsIG9yICdzaG93aW5nJ1xuICAgICAgICB0aGlzLmN1ZXMgPSBuZXcgVGV4dFRyYWNrQ3VlTGlzdFN0dWIoKTtcbiAgICAgICAgdGhpcy5hY3RpdmVDdWVzID0gbmV3IFRleHRUcmFja0N1ZUxpc3RTdHViKCk7XG4gICAgfVxuXG4gICAgYWRkQ3VlKGN1ZSkge1xuICAgICAgICB0aGlzLmN1ZXMuX2FkZChjdWUpO1xuICAgIH1cblxuICAgIHJlbW92ZUN1ZShjdWUpIHtcbiAgICAgICAgdGhpcy5jdWVzLl9yZW1vdmUoY3VlKTtcbiAgICB9XG59XG5cbmV4cG9ydCBjbGFzcyBUZXh0VHJhY2tDdWVMaXN0U3R1YiB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuX2N1ZXMgPSBbXTtcbiAgICB9XG5cbiAgICBnZXQgbGVuZ3RoKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3Vlcy5sZW5ndGg7XG4gICAgfVxuXG4gICAgaXRlbShpbmRleCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3Vlc1tpbmRleF07XG4gICAgfVxuXG4gICAgZ2V0Q3VlQnlJZChpZCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3Vlcy5maW5kKGN1ZSA9PiBjdWUuaWQgPT09IGlkKSB8fCBudWxsO1xuICAgIH1cblxuICAgIF9hZGQoY3VlKSB7XG4gICAgICAgIHRoaXMuX2N1ZXMucHVzaChjdWUpO1xuICAgIH1cblxuICAgIF9yZW1vdmUoY3VlKSB7XG4gICAgICAgIGNvbnN0IGluZGV4ID0gdGhpcy5fY3Vlcy5pbmRleE9mKGN1ZSk7XG4gICAgICAgIGlmIChpbmRleCAhPT0gLTEpIHtcbiAgICAgICAgdGhpcy5fY3Vlcy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgW1N5bWJvbC5pdGVyYXRvcl0oKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jdWVzW1N5bWJvbC5pdGVyYXRvcl0oKTtcbiAgICB9XG59XG5cbmV4cG9ydCBjbGFzcyBUZXh0VHJhY2tMaXN0U3R1YiBleHRlbmRzIEV2ZW50VGFyZ2V0IHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgc3VwZXIoKTtcbiAgICAgICAgdGhpcy5fdHJhY2tzID0gW107XG4gICAgfVxuXG4gICAgZ2V0IGxlbmd0aCgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3RyYWNrcy5sZW5ndGg7XG4gICAgfVxuXG4gICAgaXRlbShpbmRleCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fdHJhY2tzW2luZGV4XTtcbiAgICB9XG5cbiAgICBfYWRkKHRyYWNrKSB7XG4gICAgICAgIHRoaXMuX3RyYWNrcy5wdXNoKHRyYWNrKTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnYWRkdHJhY2snKSk7XG4gICAgfVxuXG4gICAgX3JlbW92ZSh0cmFjaykge1xuICAgICAgICBjb25zdCBpbmRleCA9IHRoaXMuX3RyYWNrcy5pbmRleE9mKHRyYWNrKTtcbiAgICAgICAgaWYgKGluZGV4ICE9PSAtMSkge1xuICAgICAgICB0aGlzLl90cmFja3Muc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgncmVtb3ZldHJhY2snKSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBbU3ltYm9sLml0ZXJhdG9yXSgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3RyYWNrc1tTeW1ib2wuaXRlcmF0b3JdKCk7XG4gICAgfVxufVxuIiwiXG5leHBvcnQgY2xhc3MgVGltZVJhbmdlc1N0dWIge1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLl9yYW5nZXMgPSBbXTtcbiAgICB9XG5cbiAgICBnZXQgbGVuZ3RoKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fcmFuZ2VzLmxlbmd0aDtcbiAgICB9XG5cbiAgICBzdGFydChpbmRleCkge1xuICAgICAgICBpZiAoaW5kZXggPCAwIHx8IGluZGV4ID49IHRoaXMuX3Jhbmdlcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBET01FeGNlcHRpb24oJ0ludmFsaWQgaW5kZXgnLCAnSW5kZXhTaXplRXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5fcmFuZ2VzW2luZGV4XS5zdGFydDtcbiAgICB9XG5cbiAgICBlbmQoaW5kZXgpIHtcbiAgICAgICAgaWYgKGluZGV4IDwgMCB8fCBpbmRleCA+PSB0aGlzLl9yYW5nZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRE9NRXhjZXB0aW9uKCdJbnZhbGlkIGluZGV4JywgJ0luZGV4U2l6ZUVycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX3Jhbmdlc1tpbmRleF0uZW5kO1xuICAgIH1cblxuICAgIC8vIEhlbHBlciBtZXRob2QgdG8gYWRkIGEgcmFuZ2VcbiAgICBfYWRkUmFuZ2Uoc3RhcnQsIGVuZCkge1xuICAgICAgICB0aGlzLl9yYW5nZXMucHVzaCh7IHN0YXJ0LCBlbmQgfSk7XG4gICAgICAgIHRoaXMuX25vcm1hbGl6ZVJhbmdlcygpO1xuICAgIH1cblxuICAgIC8vIEhlbHBlciBtZXRob2QgdG8gcmVtb3ZlIHJhbmdlcyB0aGF0IG92ZXJsYXAgd2l0aCBhIGdpdmVuIHJhbmdlXG4gICAgX3JlbW92ZVJhbmdlKHN0YXJ0LCBlbmQpIHtcbiAgICAgICAgbGV0IHVwZGF0ZWRSYW5nZXMgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgcmFuZ2Ugb2YgdGhpcy5fcmFuZ2VzKSB7XG4gICAgICAgICAgICBpZiAocmFuZ2UuZW5kIDw9IHN0YXJ0IHx8IHJhbmdlLnN0YXJ0ID49IGVuZCkge1xuICAgICAgICAgICAgICAgIC8vIE5vIG92ZXJsYXAsIGtlZXAgdGhlIHJhbmdlIGFzIGlzXG4gICAgICAgICAgICAgICAgdXBkYXRlZFJhbmdlcy5wdXNoKHJhbmdlKTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAocmFuZ2Uuc3RhcnQgPCBzdGFydCAmJiByYW5nZS5lbmQgPiBlbmQpIHtcbiAgICAgICAgICAgICAgICAvLyBUaGUgcmFuZ2UgZnVsbHkgY292ZXJzIHRoZSByZW1vdmFsIHJhbmdlLCBzcGxpdCBpbnRvIHR3byByYW5nZXNcbiAgICAgICAgICAgICAgICB1cGRhdGVkUmFuZ2VzLnB1c2goeyBzdGFydDogcmFuZ2Uuc3RhcnQsIGVuZDogc3RhcnQgfSk7XG4gICAgICAgICAgICAgICAgdXBkYXRlZFJhbmdlcy5wdXNoKHsgc3RhcnQ6IGVuZCwgZW5kOiByYW5nZS5lbmQgfSk7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHJhbmdlLnN0YXJ0ID49IHN0YXJ0ICYmIHJhbmdlLmVuZCA8PSBlbmQpIHtcbiAgICAgICAgICAgICAgICAvLyBUaGUgcmFuZ2UgaXMgZW50aXJlbHkgd2l0aGluIHRoZSByZW1vdmFsIHJhbmdlLCByZW1vdmUgaXRcbiAgICAgICAgICAgICAgICAvLyBEbyBub3QgYWRkIHRvIHVwZGF0ZWRSYW5nZXNcbiAgICAgICAgICAgIH0gZWxzZSBpZiAocmFuZ2Uuc3RhcnQgPCBzdGFydCAmJiByYW5nZS5lbmQgPiBzdGFydCAmJiByYW5nZS5lbmQgPD0gZW5kKSB7XG4gICAgICAgICAgICAgICAgLy8gVGhlIHJhbmdlIG92ZXJsYXBzIHdpdGggdGhlIHJlbW92YWwgcmFuZ2Ugb24gdGhlIGxlZnRcbiAgICAgICAgICAgICAgICB1cGRhdGVkUmFuZ2VzLnB1c2goeyBzdGFydDogcmFuZ2Uuc3RhcnQsIGVuZDogc3RhcnQgfSk7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHJhbmdlLnN0YXJ0ID49IHN0YXJ0ICYmIHJhbmdlLnN0YXJ0IDwgZW5kICYmIHJhbmdlLmVuZCA+IGVuZCkge1xuICAgICAgICAgICAgICAgIC8vIFRoZSByYW5nZSBvdmVybGFwcyB3aXRoIHRoZSByZW1vdmFsIHJhbmdlIG9uIHRoZSByaWdodFxuICAgICAgICAgICAgICAgIHVwZGF0ZWRSYW5nZXMucHVzaCh7IHN0YXJ0OiBlbmQsIGVuZDogcmFuZ2UuZW5kIH0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHRoaXMuX3JhbmdlcyA9IHVwZGF0ZWRSYW5nZXM7XG4gICAgfVxuXG4gICAgLy8gTm9ybWFsaXplIGFuZCBtZXJnZSBvdmVybGFwcGluZyByYW5nZXNcbiAgICBfbm9ybWFsaXplUmFuZ2VzKCkge1xuICAgICAgICB0aGlzLl9yYW5nZXMuc29ydCgoYSwgYikgPT4gYS5zdGFydCAtIGIuc3RhcnQpO1xuICAgICAgICBsZXQgbm9ybWFsaXplZCA9IFtdO1xuICAgICAgICBmb3IgKGxldCByYW5nZSBvZiB0aGlzLl9yYW5nZXMpIHtcbiAgICAgICAgICAgIGlmIChub3JtYWxpemVkLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgIG5vcm1hbGl6ZWQucHVzaChyYW5nZSk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGxldCBsYXN0ID0gbm9ybWFsaXplZFtub3JtYWxpemVkLmxlbmd0aCAtIDFdO1xuICAgICAgICAgICAgICAgIGlmIChyYW5nZS5zdGFydCA8PSBsYXN0LmVuZCkge1xuICAgICAgICAgICAgICAgICAgICBsYXN0LmVuZCA9IE1hdGgubWF4KGxhc3QuZW5kLCByYW5nZS5lbmQpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZWQucHVzaChyYW5nZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHRoaXMuX3JhbmdlcyA9IG5vcm1hbGl6ZWQ7XG4gICAgfVxufSIsImltcG9ydCB7IFRpbWVSYW5nZXNTdHViIH0gZnJvbSBcIi4vVGltZVJhbmdlc1N0dWIuanNcIlxuaW1wb3J0IHsgVGV4dFRyYWNrU3R1YiwgVGV4dFRyYWNrTGlzdFN0dWIgfSBmcm9tIFwiLi9UZXh0VHJhY2tTdHViLmpzXCJcblxuZXhwb3J0IGNsYXNzIFZpZGVvRWxlbWVudFN0dWIgZXh0ZW5kcyBFdmVudFRhcmdldCB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHN1cGVyKCk7XG5cbiAgICAgICAgdGhpcy5icmlkZ2VJZCA9IHdpbmRvdy5uZXh0SW50ZXJuYWxJZDtcbiAgICAgICAgd2luZG93Lm5leHRJbnRlcm5hbElkICs9IDE7XG4gICAgICAgIHdpbmRvdy5icmlkZ2VPYmplY3RNYXBbdGhpcy5icmlkZ2VJZF0gPSB0aGlzO1xuXG4gICAgICAgIHRoaXMuX2N1cnJlbnRUaW1lID0gMC4wO1xuICAgICAgICB0aGlzLmR1cmF0aW9uID0gTmFOO1xuICAgICAgICB0aGlzLnBhdXNlZCA9IHRydWU7XG4gICAgICAgIHRoaXMucGxheWJhY2tSYXRlID0gMS4wO1xuICAgICAgICB0aGlzLnZvbHVtZSA9IDEuMDtcbiAgICAgICAgdGhpcy5tdXRlZCA9IGZhbHNlO1xuICAgICAgICB0aGlzLnJlYWR5U3RhdGUgPSAwO1xuICAgICAgICB0aGlzLm5ldHdvcmtTdGF0ZSA9IDA7XG4gICAgICAgIHRoaXMuYnVmZmVyZWQgPSBuZXcgVGltZVJhbmdlc1N0dWIoKTtcbiAgICAgICAgdGhpcy5zZWVraW5nID0gZmFsc2U7XG4gICAgICAgIHRoaXMubG9vcCA9IGZhbHNlO1xuICAgICAgICB0aGlzLmF1dG9wbGF5ID0gZmFsc2U7XG4gICAgICAgIHRoaXMuY29udHJvbHMgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5lcnJvciA9IG51bGw7XG4gICAgICAgIHRoaXMuc3JjID0gJyc7XG4gICAgICAgIHRoaXMudmlkZW9XaWR0aCA9IDA7XG4gICAgICAgIHRoaXMudmlkZW9IZWlnaHQgPSAwO1xuICAgICAgICB0aGlzLnRleHRUcmFja3MgPSBuZXcgVGV4dFRyYWNrTGlzdFN0dWIoKTtcbiAgICAgICAgdGhpcy5pc1dhaXRpbmcgPSBmYWxzZTtcblxuICAgICAgICB3aW5kb3cuYnJpZGdlSW52b2tlQXN5bmModGhpcy5icmlkZ2VJZCwgXCJWaWRlb0VsZW1lbnRcIiwgXCJjb25zdHJ1Y3RvclwiLCB7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5yZWFkeVN0YXRlID0gNDsgLy8gSEFWRV9FTk9VR0hfREFUQVxuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnbG9hZGVkbWV0YWRhdGEnKSk7XG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdsb2FkZWRkYXRhJykpO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnY2FucGxheScpKTtcbiAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ2NhbnBsYXl0aHJvdWdoJykpO1xuICAgICAgICB9LCAwKTtcbiAgICB9XG5cbiAgICBnZXQgY3VycmVudFRpbWUoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jdXJyZW50VGltZTtcbiAgICB9XG5cbiAgICBzZXQgY3VycmVudFRpbWUodmFsdWUpIHtcbiAgICAgICAgaWYgKHRoaXMuX2N1cnJlbnRUaW1lICE9IHZhbHVlKSB7XG4gICAgICAgICAgICB0aGlzLl9jdXJyZW50VGltZSA9IHZhbHVlO1xuXG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdzZWVraW5nJykpO1xuXG4gICAgICAgICAgICB3aW5kb3cuYnJpZGdlSW52b2tlQXN5bmModGhpcy5icmlkZ2VJZCwgXCJWaWRlb0VsZW1lbnRcIiwgXCJzZXRDdXJyZW50VGltZVwiLCB7XG4gICAgICAgICAgICAgICAgXCJjdXJyZW50VGltZVwiOiB2YWx1ZVxuICAgICAgICAgICAgfSkudGhlbigocmVzdWx0KSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnc2Vla2VkJykpO1xuICAgICAgICAgICAgfSlcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGJyaWRnZVVwZGF0ZUJ1ZmZlcmVkKHZhbHVlKSB7XG4gICAgICAgIGNvbnN0IHVwZGF0ZWRSYW5nZXMgPSB2YWx1ZTtcbiAgICAgICAgdmFyIHJhbmdlcyA9IFtdO1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHVwZGF0ZWRSYW5nZXMubGVuZ3RoOyBpICs9IDIpIHtcbiAgICAgICAgICAgIHJhbmdlcy5wdXNoKHtcbiAgICAgICAgICAgICAgICBzdGFydDogdXBkYXRlZFJhbmdlc1tpXSxcbiAgICAgICAgICAgICAgICBlbmQ6IHVwZGF0ZWRSYW5nZXNbaSArIDFdXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLmJ1ZmZlcmVkLl9yYW5nZXMgPSByYW5nZXM7XG4gICAgfVxuICAgIFxuICAgIGJyaWRnZVVwZGF0ZVN0YXR1cyhkaWN0KSB7XG4gICAgICAgIHZhciBwYXVzZWQgPSAhZGljdFtcImlzUGxheWluZ1wiXTtcbiAgICAgICAgdmFyIGlzV2FpdGluZyA9IGRpY3RbXCJpc1dhaXRpbmdcIl07XG4gICAgICAgIHZhciBjdXJyZW50VGltZSA9IGRpY3RbXCJjdXJyZW50VGltZVwiXTtcblxuICAgICAgICBpZiAodGhpcy5wYXVzZWQgIT0gcGF1c2VkKSB7XG4gICAgICAgICAgICB0aGlzLnBhdXNlZCA9IHBhdXNlZDtcblxuICAgICAgICAgICAgaWYgKHBhdXNlZCkge1xuICAgICAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ3BhdXNlJykpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdwbGF5JykpO1xuICAgICAgICAgICAgICAgIHRoaXMuZGlzcGF0Y2hFdmVudChuZXcgRXZlbnQoJ3BsYXlpbmcnKSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGhpcy5pc1dhaXRpbmcgIT0gaXNXYWl0aW5nKSB7XG4gICAgICAgICAgICB0aGlzLmlzV2FpdGluZyA9IGlzV2FpdGluZztcbiAgICAgICAgICAgIGlmIChpc1dhaXRpbmcpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCd3YWl0aW5nJykpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuX2N1cnJlbnRUaW1lICE9IGN1cnJlbnRUaW1lKSB7XG4gICAgICAgICAgICB0aGlzLl9jdXJyZW50VGltZSA9IGN1cnJlbnRUaW1lO1xuICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndGltZXVwZGF0ZScpKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHBsYXkoKSB7XG4gICAgICAgIGlmICh0aGlzLnBhdXNlZCkge1xuICAgICAgICAgICAgcmV0dXJuIHdpbmRvdy5icmlkZ2VJbnZva2VBc3luYyh0aGlzLmJyaWRnZUlkLCBcIlZpZGVvRWxlbWVudFwiLCBcInBsYXlcIiwge1xuICAgICAgICAgICAgfSkudGhlbigocmVzdWx0KSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgncGxheScpKTtcbiAgICAgICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdwbGF5aW5nJykpO1xuICAgICAgICAgICAgfSlcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJldHVybiBQcm9taXNlLnJlc29sdmUoKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHBhdXNlKCkge1xuICAgICAgICBpZiAoIXRoaXMucGF1c2VkKSB7XG4gICAgICAgICAgICB0aGlzLnBhdXNlZCA9IHRydWU7XG4gICAgICAgICAgICB0aGlzLmRpc3BhdGNoRXZlbnQobmV3IEV2ZW50KCdwYXVzZScpKTtcblxuICAgICAgICAgICAgcmV0dXJuIHdpbmRvdy5icmlkZ2VJbnZva2VBc3luYyh0aGlzLmJyaWRnZUlkLCBcIlZpZGVvRWxlbWVudFwiLCBcInBhdXNlXCIsIHtcbiAgICAgICAgICAgIH0pLnRoZW4oKHJlc3VsdCkgPT4ge1xuICAgICAgICAgICAgfSlcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGNhblBsYXlUeXBlKHR5cGUpIHtcbiAgICAgICAgcmV0dXJuICdwcm9iYWJseSc7XG4gICAgfVxuXG4gICAgX2dldE1lZGlhKCkge1xuICAgICAgICByZXR1cm4gd2luZG93Lm1lZGlhU291cmNlTWFwW3RoaXMuc3JjXTtcbiAgICB9XG5cbiAgICAvKl9zaW11bGF0ZVRpbWVVcGRhdGUoKSB7XG4gICAgICAgIGlmICh0aGlzLl9pc1BsYXlpbmcpIHtcbiAgICAgICAgICAgIC8vIFNpbXVsYXRlIHRpbWUgcHJvZ3Jlc3Npb25cbiAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgICAgIHZhciBidWZmZXJlZEVuZCA9IDAuMDtcbiAgICAgICAgICAgICAgICBcbiAgICAgICAgICAgICAgICBjb25zdCBtZWRpYSA9IHRoaXMuX2dldE1lZGlhKCk7XG4gICAgICAgICAgICAgICAgaWYgKG1lZGlhKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChtZWRpYS5zb3VyY2VCdWZmZXJzLmxlbmd0aCAhPSAwKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmJ1ZmZlcmVkID0gbWVkaWEuc291cmNlQnVmZmVycy5fYnVmZmVyc1swXS5idWZmZXJlZDtcbiAgICAgICAgICAgICAgICAgICAgICAgIGJ1ZmZlcmVkRW5kID0gdGhpcy5idWZmZXJlZC5sZW5ndGggPT0gMCA/IDAgOiB0aGlzLmJ1ZmZlcmVkLmVuZCh0aGlzLmJ1ZmZlcmVkLmxlbmd0aCAtIDEpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgLy8gQ29uc3VtZSBidWZmZXJlZCBkYXRhXG4gICAgICAgICAgICAgICAgaWYgKHRoaXMuY3VycmVudFRpbWUgPCBidWZmZXJlZEVuZCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBBZHZhbmNlIGN1cnJlbnRUaW1lXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX2N1cnJlbnRUaW1lICs9IDAuMSAqIHRoaXMucGxheWJhY2tSYXRlOyAvLyBJbmNyZW1lbnQgY3VycmVudFRpbWVcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgndGltZXVwZGF0ZScpKTtcblxuICAgICAgICAgICAgICAgICAgICAvLyBDb250aW51ZSBzaW11bGF0aW9uXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX3NpbXVsYXRlVGltZVVwZGF0ZSgpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiQnVmZmVyIHVuZGVycnVuXCIpO1xuICAgICAgICAgICAgICAgICAgICAvLyBCdWZmZXIgdW5kZXJydW5cbiAgICAgICAgICAgICAgICAgICAgdGhpcy5faXNQbGF5aW5nID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMucGF1c2VkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5kaXNwYXRjaEV2ZW50KG5ldyBFdmVudCgnd2FpdGluZycpKTtcbiAgICAgICAgICAgICAgICAgICAgLy8gVGhlIHBsYXllciBzaG91bGQgcmVhY3QgYnkgYnVmZmVyaW5nIG1vcmUgZGF0YVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sIDEwMCk7XG4gICAgICAgIH1cbiAgICB9Ki9cblxuICAgIGFkZFRleHRUcmFjayhraW5kLCBsYWJlbCwgbGFuZ3VhZ2UpIHtcbiAgICAgICAgY29uc3QgdGV4dFRyYWNrID0gbmV3IFRleHRUcmFja1N0dWIoa2luZCwgbGFiZWwsIGxhbmd1YWdlKTtcbiAgICAgICAgdGhpcy50ZXh0VHJhY2tzLl9hZGQodGV4dFRyYWNrKTtcbiAgICAgICAgcmV0dXJuIHRleHRUcmFjaztcbiAgICB9XG59XG4iLCJpbXBvcnQgSGxzIGZyb20gXCJobHMuanNcIjtcbmltcG9ydCB7IFZpZGVvRWxlbWVudFN0dWIgfSBmcm9tIFwiLi9WaWRlb0VsZW1lbnRTdHViLmpzXCJcbmltcG9ydCB7IE1lZGlhU291cmNlU3R1YiwgU291cmNlQnVmZmVyU3R1YiB9IGZyb20gXCIuL01lZGlhU291cmNlU3R1Yi5qc1wiXG5cbndpbmRvdy5icmlkZ2VPYmplY3RNYXAgPSB7fTtcbndpbmRvdy5icmlkZ2VDYWxsYmFja01hcCA9IHt9O1xuXG5mdW5jdGlvbiBicmlkZ2VJbnZva2VBc3luYyhicmlkZ2VJZCwgY2xhc3NOYW1lLCBtZXRob2ROYW1lLCBwYXJhbXMpIHtcbiAgICB2YXIgcHJvbWlzZVJlc29sdmU7XG4gICAgdmFyIHByb21pc2VSZWplY3Q7XG4gICAgdmFyIHJlc3VsdCA9IG5ldyBQcm9taXNlKGZ1bmN0aW9uKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgICBwcm9taXNlUmVzb2x2ZSA9IHJlc29sdmU7XG4gICAgICAgIHByb21pc2VSZWplY3QgPSByZWplY3Q7XG4gICAgfSk7XG4gICAgY29uc3QgY2FsbGJhY2tJZCA9IHdpbmRvdy5uZXh0SW50ZXJuYWxJZDtcbiAgICB3aW5kb3cubmV4dEludGVybmFsSWQgKz0gMTtcbiAgICB3aW5kb3cuYnJpZGdlQ2FsbGJhY2tNYXBbY2FsbGJhY2tJZF0gPSBwcm9taXNlUmVzb2x2ZTtcblxuICAgIGlmICh3aW5kb3cud2Via2l0Lm1lc3NhZ2VIYW5kbGVycykge1xuICAgICAgICB3aW5kb3cud2Via2l0Lm1lc3NhZ2VIYW5kbGVycy5wZXJmb3JtQWN0aW9uLnBvc3RNZXNzYWdlKHtcbiAgICAgICAgICAgICdldmVudCc6ICdicmlkZ2VJbnZva2UnLFxuICAgICAgICAgICAgJ2RhdGEnOiB7XG4gICAgICAgICAgICAgICAgJ2JyaWRnZUlkJzogYnJpZGdlSWQsXG4gICAgICAgICAgICAgICAgJ2NsYXNzTmFtZSc6IGNsYXNzTmFtZSxcbiAgICAgICAgICAgICAgICAnbWV0aG9kTmFtZSc6IG1ldGhvZE5hbWUsXG4gICAgICAgICAgICAgICAgJ3BhcmFtcyc6IHBhcmFtcyxcbiAgICAgICAgICAgICAgICAnY2FsbGJhY2tJZCc6IGNhbGxiYWNrSWRcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdDtcbn1cbndpbmRvdy5icmlkZ2VJbnZva2VBc3luYyA9IGJyaWRnZUludm9rZUFzeW5jXG5cbmV4cG9ydCBmdW5jdGlvbiBicmlkZ2VJbnZva2VDYWxsYmFjayhjYWxsYmFja0lkLCByZXN1bHQpIHtcbiAgICBjb25zdCBjYWxsYmFjayA9IHdpbmRvdy5icmlkZ2VDYWxsYmFja01hcFtjYWxsYmFja0lkXTtcbiAgICBpZiAoY2FsbGJhY2spIHtcbiAgICAgICAgY2FsbGJhY2socmVzdWx0KTtcbiAgICB9XG59XG5cbnZhciB1c2VTdHVicyA9IHRydWU7XG5cbndpbmRvdy5uZXh0SW50ZXJuYWxJZCA9IDA7XG53aW5kb3cubWVkaWFTb3VyY2VNYXAgPSB7fTtcblxuLy8gUmVwbGFjZSB0aGUgZ2xvYmFsIE1lZGlhU291cmNlIHdpdGggb3VyIHN0dWJcbmlmICh1c2VTdHVicyAmJiB0eXBlb2Ygd2luZG93ICE9PSAndW5kZWZpbmVkJykge1xuICAgIHdpbmRvdy5NZWRpYVNvdXJjZSA9IE1lZGlhU291cmNlU3R1YjtcbiAgICB3aW5kb3cuTWFuYWdlZE1lZGlhU291cmNlID0gTWVkaWFTb3VyY2VTdHViO1xuICAgIHdpbmRvdy5Tb3VyY2VCdWZmZXIgPSBTb3VyY2VCdWZmZXJTdHViO1xuICAgIFVSTC5jcmVhdGVPYmplY3RVUkwgPSBmdW5jdGlvbihtcykge1xuICAgICAgICBjb25zdCB1cmwgPSBcImJsb2I6bW9jay1tZWRpYS1zb3VyY2U6XCIgKyBtcy5pbnRlcm5hbElkO1xuICAgICAgICB3aW5kb3cubWVkaWFTb3VyY2VNYXBbdXJsXSA9IG1zO1xuICAgICAgICByZXR1cm4gdXJsO1xuICAgIH07XG59XG5cblxuZnVuY3Rpb24gcG9zdFBsYXllckV2ZW50KGV2ZW50TmFtZSwgZXZlbnREYXRhKSB7XG4gICAgaWYgKHdpbmRvdy53ZWJraXQgJiYgd2luZG93LndlYmtpdC5tZXNzYWdlSGFuZGxlcnMpIHtcbiAgICAgICAgd2luZG93LndlYmtpdC5tZXNzYWdlSGFuZGxlcnMucGVyZm9ybUFjdGlvbi5wb3N0TWVzc2FnZSh7J2V2ZW50JzogZXZlbnROYW1lLCAnZGF0YSc6IGV2ZW50RGF0YX0pO1xuICAgIH1cbn07XG5cbnZhciB2aWRlbztcbnZhciBobHM7XG5cbnZhciBpc01hbmlmZXN0UGFyc2VkID0gZmFsc2U7XG52YXIgaXNGaXJzdEZyYW1lUmVhZHkgPSBmYWxzZTtcblxudmFyIGN1cnJlbnRUaW1lVXBkYXRlVGltZW91dCA9IG51bGw7XG5cbmV4cG9ydCBmdW5jdGlvbiBwbGF5ZXJJbml0aWFsaXplKHBhcmFtcykge1xuICAgIHZpZGVvLm11dGVkID0gZmFsc2U7XG5cbiAgICB2aWRlby5hZGRFdmVudExpc3RlbmVyKCdsb2FkZWRkYXRhJywgKGV2ZW50KSA9PiB7XG4gICAgICAgIGlmICghaXNGaXJzdEZyYW1lUmVhZHkpIHtcbiAgICAgICAgICAgIGlzRmlyc3RGcmFtZVJlYWR5ID0gdHJ1ZTtcbiAgICAgICAgICAgIHJlZnJlc2hQbGF5ZXJTdGF0dXMoKTtcbiAgICAgICAgfVxuICAgIH0pO1xuICAgIHZpZGVvLmFkZEV2ZW50TGlzdGVuZXIoXCJwbGF5aW5nXCIsIGZ1bmN0aW9uKCkge1xuICAgICAgICByZWZyZXNoUGxheWVyU3RhdHVzKCk7XG4gICAgfSk7XG4gICAgdmlkZW8uYWRkRXZlbnRMaXN0ZW5lcihcInBhdXNlXCIsIGZ1bmN0aW9uKCkgeyBcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuICAgIHZpZGVvLmFkZEV2ZW50TGlzdGVuZXIoXCJzZWVraW5nXCIsIGZ1bmN0aW9uKCkgeyBcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuICAgIHZpZGVvLmFkZEV2ZW50TGlzdGVuZXIoXCJ3YWl0aW5nXCIsIGZ1bmN0aW9uKCkgeyBcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuXG4gICAgaGxzID0gbmV3IEhscyh7XG4gICAgICAgIHN0YXJ0TGV2ZWw6IDAsXG4gICAgICAgIHRlc3RCYW5kd2lkdGg6IGZhbHNlLFxuICAgICAgICBkZWJ1ZzogcGFyYW1zWydkZWJ1ZyddIHx8IHRydWUsXG4gICAgICAgIGF1dG9TdGFydExvYWQ6IGZhbHNlLFxuICAgICAgICBiYWNrQnVmZmVyTGVuZ3RoOiAzMCxcbiAgICAgICAgbWF4QnVmZmVyTGVuZ3RoOiA2MCxcbiAgICAgICAgbWF4TWF4QnVmZmVyTGVuZ3RoOiA2MCxcbiAgICAgICAgbWF4RnJhZ0xvb2tVcFRvbGVyYW5jZTogMC4wMDEsXG4gICAgICAgIG51ZGdlTWF4UmV0cnk6IDEwMDAwXG4gICAgfSk7XG4gICAgaGxzLm9uKEhscy5FdmVudHMuTUFOSUZFU1RfUEFSU0VELCBmdW5jdGlvbigpIHtcbiAgICAgICAgaXNNYW5pZmVzdFBhcnNlZCA9IHRydWU7XG4gICAgICAgIHJlZnJlc2hQbGF5ZXJTdGF0dXMoKTtcbiAgICB9KTtcblxuICAgIGhscy5vbihIbHMuRXZlbnRzLkxFVkVMX1NXSVRDSEVELCBmdW5jdGlvbigpIHtcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuICAgIGhscy5vbihIbHMuRXZlbnRzLkxFVkVMU19VUERBVEVELCBmdW5jdGlvbigpIHtcbiAgICAgICAgcmVmcmVzaFBsYXllclN0YXR1cygpO1xuICAgIH0pO1xuXG4gICAgaGxzLmxvYWRTb3VyY2UoJ21hc3Rlci5tM3U4Jyk7XG4gICAgaGxzLmF0dGFjaE1lZGlhKHZpZGVvKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllckxvYWQoaW5pdGlhbExldmVsSW5kZXgpIHtcbiAgICBobHMuc3RhcnRMZXZlbCA9IGluaXRpYWxMZXZlbEluZGV4O1xuICAgIGhscy5zdGFydExvYWQoLTEsIGZhbHNlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllclBsYXkoKSB7XG4gICAgdmlkZW8ucGxheSgpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcGxheWVyUGF1c2UoKSB7XG4gICAgdmlkZW8ucGF1c2UoKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHBsYXllclNldEJhc2VSYXRlKHZhbHVlKSB7XG4gICAgdmlkZW8ucGxheWJhY2tSYXRlID0gdmFsdWU7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwbGF5ZXJTZXRMZXZlbChsZXZlbCkge1xuICAgIGlmIChsZXZlbCA+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