vibetunnel
Version:
Terminal sharing server with web interface - supports macOS, Linux, and headless environments
595 lines • 69.5 kB
JavaScript
"use strict";
/**
* AsciinemaWriter - Records terminal sessions in asciinema format
*
* This class writes terminal output in the standard asciinema cast format (v2),
* which is compatible with asciinema players and the existing web interface.
* It handles real-time streaming of terminal data while properly managing:
* - UTF-8 encoding and incomplete multi-byte sequences
* - ANSI escape sequences preservation
* - Buffering and backpressure
* - Atomic writes with fsync for durability
*
* Key features:
* - Real-time recording with minimal buffering
* - Proper handling of escape sequences across buffer boundaries
* - Support for all asciinema event types (output, input, resize, markers)
* - Automatic directory creation and file management
* - Thread-safe write queue for concurrent operations
*
* @example
* ```typescript
* // Create a writer for a new recording
* const writer = AsciinemaWriter.create(
* '/path/to/recording.cast',
* 80, // terminal width
* 24, // terminal height
* 'npm test', // command being recorded
* 'Test Run Recording' // title
* );
*
* // Write terminal output
* writer.writeOutput(Buffer.from('Hello, world!\r\n'));
*
* // Record user input
* writer.writeInput('ls -la');
*
* // Handle terminal resize
* writer.writeResize(120, 40);
*
* // Add a bookmark/marker
* writer.writeMarker('Test started');
*
* // Close the recording when done
* await writer.close();
* ```
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsciinemaWriter = void 0;
const events_1 = require("events");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const util_1 = require("util");
const logger_js_1 = require("../utils/logger.js");
const pruning_detector_js_1 = require("../utils/pruning-detector.js");
const write_queue_js_1 = require("../utils/write-queue.js");
const types_js_1 = require("./types.js");
const _logger = (0, logger_js_1.createLogger)('AsciinemaWriter');
const fsync = (0, util_1.promisify)(fs.fsync);
class AsciinemaWriter {
constructor(filePath, header) {
this.filePath = filePath;
this.header = header;
this.utf8Buffer = Buffer.alloc(0);
this.headerWritten = false;
this.fd = null;
this.writeQueue = new write_queue_js_1.WriteQueue();
// Byte position tracking
this.bytesWritten = 0; // Bytes actually written to disk
this.pendingBytes = 0; // Bytes queued but not yet written
// Validation tracking
this.lastValidatedPosition = 0;
this.validationErrors = 0;
this.startTime = new Date();
// Ensure directory exists
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create write stream with no buffering for real-time performance
this.writeStream = fs.createWriteStream(filePath, {
flags: 'w',
encoding: 'utf8',
highWaterMark: 0, // Disable internal buffering
});
// Get file descriptor for fsync
this.writeStream.on('open', (fd) => {
this.fd = fd;
});
this.writeHeader();
}
/**
* Create an AsciinemaWriter with standard parameters
*/
static create(filePath, width = 80, height = 24, command, title, env) {
const header = {
version: 2,
width,
height,
timestamp: Math.floor(Date.now() / 1000),
command,
title,
env,
};
return new AsciinemaWriter(filePath, header);
}
/**
* Get the current byte position in the file
* @returns Object with current position and pending bytes
*/
getPosition() {
return {
written: this.bytesWritten, // Bytes actually written to disk
pending: this.pendingBytes, // Bytes in queue
total: this.bytesWritten + this.pendingBytes, // Total position after queue flush
};
}
/**
* Set a callback to be notified when pruning sequences are detected
* @param callback Function called with sequence info and byte position
*/
onPruningSequence(callback) {
this.pruningCallback = callback;
}
/**
* Write the asciinema header to the file
*/
writeHeader() {
if (this.headerWritten)
return;
this.writeQueue.enqueue(async () => {
const headerJson = JSON.stringify(this.header);
const headerLine = `${headerJson}\n`;
const headerBytes = Buffer.from(headerLine, 'utf8').length;
// Track pending bytes before write
this.pendingBytes += headerBytes;
const canWrite = this.writeStream.write(headerLine);
if (!canWrite) {
await (0, events_1.once)(this.writeStream, 'drain');
}
// Move bytes from pending to written
this.bytesWritten += headerBytes;
this.pendingBytes -= headerBytes;
});
this.headerWritten = true;
}
/**
* Write terminal output data
*/
writeOutput(data) {
this.writeQueue.enqueue(async () => {
const time = this.getElapsedTime();
// Combine any buffered bytes with the new data
const combinedBuffer = Buffer.concat([this.utf8Buffer, data]);
// Process data in escape-sequence-aware chunks
const { processedData, remainingBuffer } = this.processTerminalData(combinedBuffer);
if (processedData.length > 0) {
// First, check for pruning sequences in the data
let pruningInfo = null;
if (this.pruningCallback) {
// Use shared detector to find pruning sequences
const detection = (0, pruning_detector_js_1.detectLastPruningSequence)(processedData);
if (detection) {
pruningInfo = detection;
_logger.debug(`Found pruning sequence '${detection.sequence.split('\x1b').join('\\x1b')}' ` +
`at string index ${detection.index} in output data`);
}
}
// Create the event with ALL data (not truncated)
const event = {
time,
type: 'o',
data: processedData,
};
// Calculate the byte position where the event will start
const eventStartPos = this.bytesWritten + this.pendingBytes;
// Write the event
await this.writeEvent(event);
// Now that the write is complete, handle pruning callback if needed
if (pruningInfo && this.pruningCallback) {
// Use shared calculator for exact byte position
const exactSequenceEndPos = (0, pruning_detector_js_1.calculateSequenceBytePosition)(eventStartPos, time, processedData, pruningInfo.index, pruningInfo.sequence.length);
// Validate the calculation
const eventJson = `${JSON.stringify([time, 'o', processedData])}\n`;
const totalEventSize = Buffer.from(eventJson, 'utf8').length;
const calculatedEventEndPos = eventStartPos + totalEventSize;
if ((0, logger_js_1.isDebugEnabled)()) {
_logger.debug(`Pruning sequence byte calculation:\n` +
` Event start position: ${eventStartPos}\n` +
` Event total size: ${totalEventSize} bytes\n` +
` Event end position: ${calculatedEventEndPos}\n` +
` Exact sequence position: ${exactSequenceEndPos}\n` +
` Current file position: ${this.bytesWritten}`);
}
// Sanity check: sequence position should be within the event
if (exactSequenceEndPos > calculatedEventEndPos) {
_logger.error(`Pruning sequence position calculation error: ` +
`sequence position ${exactSequenceEndPos} is beyond event end ${calculatedEventEndPos}`);
}
else {
// Call the callback with the exact position
this.pruningCallback({
sequence: pruningInfo.sequence,
position: exactSequenceEndPos,
timestamp: time,
});
// Use shared logging function
(0, pruning_detector_js_1.logPruningDetection)(pruningInfo.sequence, exactSequenceEndPos, '(real-time)');
}
}
}
// Store any remaining incomplete data for next time
this.utf8Buffer = remainingBuffer;
});
}
/**
* Write terminal input data (usually from user)
*/
writeInput(data) {
this.writeQueue.enqueue(async () => {
const time = this.getElapsedTime();
const event = {
time,
type: 'i',
data,
};
await this.writeEvent(event);
});
}
/**
* Write terminal resize event
*/
writeResize(cols, rows) {
this.writeQueue.enqueue(async () => {
const time = this.getElapsedTime();
const event = {
time,
type: 'r',
data: `${cols}x${rows}`,
};
await this.writeEvent(event);
});
}
/**
* Write marker event (for bookmarks/annotations)
*/
writeMarker(message) {
this.writeQueue.enqueue(async () => {
const time = this.getElapsedTime();
const event = {
time,
type: 'm',
data: message,
};
await this.writeEvent(event);
});
}
/**
* Write a raw JSON event (for custom events like exit)
*/
writeRawJson(jsonValue) {
this.writeQueue.enqueue(async () => {
const jsonString = JSON.stringify(jsonValue);
const jsonLine = `${jsonString}\n`;
const jsonBytes = Buffer.from(jsonLine, 'utf8').length;
// Track pending bytes before write
this.pendingBytes += jsonBytes;
const canWrite = this.writeStream.write(jsonLine);
if (!canWrite) {
await (0, events_1.once)(this.writeStream, 'drain');
}
// Move bytes from pending to written
this.bytesWritten += jsonBytes;
this.pendingBytes -= jsonBytes;
});
}
/**
* Write an asciinema event to the file
*/
async writeEvent(event) {
// Asciinema format: [time, type, data]
const eventArray = [event.time, event.type, event.data];
const eventJson = JSON.stringify(eventArray);
const eventLine = `${eventJson}\n`;
const eventBytes = Buffer.from(eventLine, 'utf8').length;
// Log detailed write information for debugging
if (event.type === 'o' && (0, logger_js_1.isDebugEnabled)()) {
_logger.debug(`Writing output event: ${eventBytes} bytes, ` +
`data length: ${event.data.length} chars, ` +
`position: ${this.bytesWritten + this.pendingBytes}`);
}
// Track pending bytes before write
this.pendingBytes += eventBytes;
// Write and handle backpressure
const canWrite = this.writeStream.write(eventLine);
if (!canWrite) {
_logger.debug('Write stream backpressure detected, waiting for drain');
await (0, events_1.once)(this.writeStream, 'drain');
}
// Move bytes from pending to written
this.bytesWritten += eventBytes;
this.pendingBytes -= eventBytes;
// Validate position periodically
if (this.bytesWritten - this.lastValidatedPosition > 1024 * 1024) {
// Every 1MB
await this.validateFilePosition();
}
// Sync to disk asynchronously
if (this.fd !== null) {
try {
await fsync(this.fd);
}
catch (err) {
_logger.debug(`fsync failed for ${this.filePath}:`, err);
}
}
}
/**
* Process terminal data while preserving escape sequences and handling UTF-8
*/
processTerminalData(buffer) {
let result = '';
let pos = 0;
while (pos < buffer.length) {
// Look for escape sequences starting with ESC (0x1B)
if (buffer[pos] === 0x1b) {
// Try to find complete escape sequence
const seqEnd = this.findEscapeSequenceEnd(buffer.subarray(pos));
if (seqEnd !== null) {
const seqBytes = buffer.subarray(pos, pos + seqEnd);
// Preserve escape sequence as-is using toString to maintain exact bytes
result += seqBytes.toString('latin1');
pos += seqEnd;
}
else {
// Incomplete escape sequence at end of buffer - save for later
return {
processedData: result,
remainingBuffer: buffer.subarray(pos),
};
}
}
else {
// Regular text - find the next escape sequence or end of valid UTF-8
const chunkStart = pos;
while (pos < buffer.length && buffer[pos] !== 0x1b) {
pos++;
}
const textChunk = buffer.subarray(chunkStart, pos);
// Handle UTF-8 validation for text chunks
try {
const validText = textChunk.toString('utf8');
result += validText;
}
catch (_e) {
// Try to find how much is valid UTF-8
const { validData, invalidStart } = this.findValidUtf8(textChunk);
if (validData.length > 0) {
result += validData.toString('utf8');
}
// Check if we have incomplete UTF-8 at the end
if (invalidStart < textChunk.length && pos >= buffer.length) {
const remaining = buffer.subarray(chunkStart + invalidStart);
// If it might be incomplete UTF-8 at buffer end, save it
if (remaining.length <= 4 && this.mightBeIncompleteUtf8(remaining)) {
return {
processedData: result,
remainingBuffer: remaining,
};
}
}
// Invalid UTF-8 in middle or complete invalid sequence
// Use lossy conversion for this part
const invalidPart = textChunk.subarray(invalidStart);
result += invalidPart.toString('latin1');
}
}
}
return { processedData: result, remainingBuffer: Buffer.alloc(0) };
}
/**
* Find the end of an ANSI escape sequence
*/
findEscapeSequenceEnd(buffer) {
if (buffer.length === 0 || buffer[0] !== 0x1b) {
return null;
}
if (buffer.length < 2) {
return null; // Incomplete - need more data
}
switch (buffer[1]) {
// CSI sequences: ESC [ ... final_char
case 0x5b: {
// '['
let pos = 2;
// Skip parameter and intermediate characters
while (pos < buffer.length) {
const byte = buffer[pos];
if (byte >= 0x20 && byte <= 0x3f) {
// Parameter characters 0-9 : ; < = > ? and Intermediate characters
pos++;
}
else if (byte >= 0x40 && byte <= 0x7e) {
// Final character @ A-Z [ \ ] ^ _ ` a-z { | } ~
return pos + 1;
}
else {
// Invalid sequence, stop here
return pos;
}
}
return null; // Incomplete sequence
}
// OSC sequences: ESC ] ... (ST or BEL)
case 0x5d: {
// ']'
let pos = 2;
while (pos < buffer.length) {
const byte = buffer[pos];
if (byte === 0x07) {
// BEL terminator
return pos + 1;
}
else if (byte === 0x1b && pos + 1 < buffer.length && buffer[pos + 1] === 0x5c) {
// ESC \ (ST) terminator
return pos + 2;
}
pos++;
}
return null; // Incomplete sequence
}
// Simple two-character sequences: ESC letter
default:
return 2;
}
}
/**
* Find valid UTF-8 portion of a buffer
*/
findValidUtf8(buffer) {
for (let i = 0; i < buffer.length; i++) {
try {
const testSlice = buffer.subarray(0, i + 1);
testSlice.toString('utf8');
}
catch (_e) {
// Found invalid UTF-8, return valid portion
return {
validData: buffer.subarray(0, i),
invalidStart: i,
};
}
}
// All valid
return {
validData: buffer,
invalidStart: buffer.length,
};
}
/**
* Check if a buffer might contain incomplete UTF-8 sequence
*/
mightBeIncompleteUtf8(buffer) {
if (buffer.length === 0)
return false;
// Check if first byte indicates multi-byte UTF-8 character
const firstByte = buffer[0];
// Single byte (ASCII) - not incomplete
if (firstByte < 0x80)
return false;
// Multi-byte sequence starters
if (firstByte >= 0xc0) {
// 2-byte sequence needs 2 bytes
if (firstByte < 0xe0)
return buffer.length < 2;
// 3-byte sequence needs 3 bytes
if (firstByte < 0xf0)
return buffer.length < 3;
// 4-byte sequence needs 4 bytes
if (firstByte < 0xf8)
return buffer.length < 4;
}
return false;
}
/**
* Get elapsed time since start in seconds
*/
getElapsedTime() {
return (Date.now() - this.startTime.getTime()) / 1000;
}
/**
* Validate that our tracked position matches the actual file size
*/
async validateFilePosition() {
try {
const stats = await fs.promises.stat(this.filePath);
const actualSize = stats.size;
const expectedSize = this.bytesWritten;
if (actualSize !== expectedSize) {
this.validationErrors++;
_logger.error(`AsciinemaWriter position mismatch! ` +
`Expected: ${expectedSize} bytes, Actual: ${actualSize} bytes, ` +
`Difference: ${actualSize - expectedSize} bytes, ` +
`Validation errors: ${this.validationErrors}`);
// If the difference is significant, this is a critical error
if (Math.abs(actualSize - expectedSize) > 100) {
throw new types_js_1.PtyError(`Critical byte position tracking error: expected ${expectedSize}, actual ${actualSize}`, 'POSITION_MISMATCH');
}
}
else {
_logger.debug(`Position validation passed: ${actualSize} bytes`);
}
this.lastValidatedPosition = this.bytesWritten;
}
catch (error) {
if (error instanceof types_js_1.PtyError) {
throw error;
}
_logger.error(`Failed to validate file position:`, error);
}
}
/**
* Close the writer and finalize the file
*/
async close() {
// Flush any remaining UTF-8 buffer through the queue
if (this.utf8Buffer.length > 0) {
// Force write any remaining data using lossy conversion
const time = this.getElapsedTime();
const event = {
time,
type: 'o',
data: this.utf8Buffer.toString('latin1'),
};
// Use the queue to ensure ordering
this.writeQueue.enqueue(async () => {
await this.writeEvent(event);
});
this.utf8Buffer = Buffer.alloc(0);
}
// Wait for all queued writes to complete
await this.writeQueue.drain();
// Now it's safe to end the stream
return new Promise((resolve, reject) => {
this.writeStream.end((error) => {
if (error) {
reject(new types_js_1.PtyError(`Failed to close asciinema writer: ${error.message}`));
}
else {
resolve();
}
});
});
}
/**
* Check if the writer is still open
*/
isOpen() {
return !this.writeStream.destroyed;
}
}
exports.AsciinemaWriter = AsciinemaWriter;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXNjaWluZW1hLXdyaXRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2ZXIvcHR5L2FzY2lpbmVtYS13cml0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTRDRzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRUgsbUNBQThCO0FBQzlCLHVDQUF5QjtBQUN6QiwyQ0FBNkI7QUFDN0IsK0JBQWlDO0FBQ2pDLGtEQUFrRTtBQUNsRSxzRUFJc0M7QUFDdEMsNERBQXFEO0FBQ3JELHlDQUFpRjtBQUVqRixNQUFNLE9BQU8sR0FBRyxJQUFBLHdCQUFZLEVBQUMsaUJBQWlCLENBQUMsQ0FBQztBQUNoRCxNQUFNLEtBQUssR0FBRyxJQUFBLGdCQUFTLEVBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBU2xDLE1BQWEsZUFBZTtJQW1CMUIsWUFDVSxRQUFnQixFQUNoQixNQUF1QjtRQUR2QixhQUFRLEdBQVIsUUFBUSxDQUFRO1FBQ2hCLFdBQU0sR0FBTixNQUFNLENBQWlCO1FBbEJ6QixlQUFVLEdBQVcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNyQyxrQkFBYSxHQUFHLEtBQUssQ0FBQztRQUN0QixPQUFFLEdBQWtCLElBQUksQ0FBQztRQUN6QixlQUFVLEdBQUcsSUFBSSwyQkFBVSxFQUFFLENBQUM7UUFFdEMseUJBQXlCO1FBQ2pCLGlCQUFZLEdBQVcsQ0FBQyxDQUFDLENBQUMsaUNBQWlDO1FBQzNELGlCQUFZLEdBQVcsQ0FBQyxDQUFDLENBQUMsbUNBQW1DO1FBS3JFLHNCQUFzQjtRQUNkLDBCQUFxQixHQUFXLENBQUMsQ0FBQztRQUNsQyxxQkFBZ0IsR0FBVyxDQUFDLENBQUM7UUFNbkMsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO1FBRTVCLDBCQUEwQjtRQUMxQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ25DLElBQUksQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDeEIsRUFBRSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN6QyxDQUFDO1FBRUQsa0VBQWtFO1FBQ2xFLElBQUksQ0FBQyxXQUFXLEdBQUcsRUFBRSxDQUFDLGlCQUFpQixDQUFDLFFBQVEsRUFBRTtZQUNoRCxLQUFLLEVBQUUsR0FBRztZQUNWLFFBQVEsRUFBRSxNQUFNO1lBQ2hCLGFBQWEsRUFBRSxDQUFDLEVBQUUsNkJBQTZCO1NBQ2hELENBQUMsQ0FBQztRQUVILGdDQUFnQztRQUNoQyxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRTtZQUNqQyxJQUFJLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQztRQUNmLENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7T0FFRztJQUNILE1BQU0sQ0FBQyxNQUFNLENBQ1gsUUFBZ0IsRUFDaEIsUUFBZ0IsRUFBRSxFQUNsQixTQUFpQixFQUFFLEVBQ25CLE9BQWdCLEVBQ2hCLEtBQWMsRUFDZCxHQUE0QjtRQUU1QixNQUFNLE1BQU0sR0FBb0I7WUFDOUIsT0FBTyxFQUFFLENBQUM7WUFDVixLQUFLO1lBQ0wsTUFBTTtZQUNOLFNBQVMsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7WUFDeEMsT0FBTztZQUNQLEtBQUs7WUFDTCxHQUFHO1NBQ0osQ0FBQztRQUVGLE9BQU8sSUFBSSxlQUFlLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQy9DLENBQUM7SUFFRDs7O09BR0c7SUFDSCxXQUFXO1FBQ1QsT0FBTztZQUNMLE9BQU8sRUFBRSxJQUFJLENBQUMsWUFBWSxFQUFFLGlDQUFpQztZQUM3RCxPQUFPLEVBQUUsSUFBSSxDQUFDLFlBQVksRUFBRSxpQkFBaUI7WUFDN0MsS0FBSyxFQUFFLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksRUFBRSxtQ0FBbUM7U0FDbEYsQ0FBQztJQUNKLENBQUM7SUFFRDs7O09BR0c7SUFDSCxpQkFBaUIsQ0FBQyxRQUF5QjtRQUN6QyxJQUFJLENBQUMsZUFBZSxHQUFHLFFBQVEsQ0FBQztJQUNsQyxDQUFDO0lBRUQ7O09BRUc7SUFDSyxXQUFXO1FBQ2pCLElBQUksSUFBSSxDQUFDLGFBQWE7WUFBRSxPQUFPO1FBRS9CLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQ2pDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQy9DLE1BQU0sVUFBVSxHQUFHLEdBQUcsVUFBVSxJQUFJLENBQUM7WUFDckMsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUMsTUFBTSxDQUFDO1lBRTNELG1DQUFtQztZQUNuQyxJQUFJLENBQUMsWUFBWSxJQUFJLFdBQVcsQ0FBQztZQUVqQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztZQUNwRCxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ2QsTUFBTSxJQUFBLGFBQUksRUFBQyxJQUFJLENBQUMsV0FBVyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQ3hDLENBQUM7WUFFRCxxQ0FBcUM7WUFDckMsSUFBSSxDQUFDLFlBQVksSUFBSSxXQUFXLENBQUM7WUFDakMsSUFBSSxDQUFDLFlBQVksSUFBSSxXQUFXLENBQUM7UUFDbkMsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQztJQUM1QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxXQUFXLENBQUMsSUFBWTtRQUN0QixJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLElBQUksRUFBRTtZQUNqQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFFbkMsK0NBQStDO1lBQy9DLE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7WUFFOUQsK0NBQStDO1lBQy9DLE1BQU0sRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFFLEdBQUcsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGNBQWMsQ0FBQyxDQUFDO1lBRXBGLElBQUksYUFBYSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDN0IsaURBQWlEO2dCQUNqRCxJQUFJLFdBQVcsR0FBK0MsSUFBSSxDQUFDO2dCQUVuRSxJQUFJLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztvQkFDekIsZ0RBQWdEO29CQUNoRCxNQUFNLFNBQVMsR0FBRyxJQUFBLCtDQUF5QixFQUFDLGFBQWEsQ0FBQyxDQUFDO29CQUUzRCxJQUFJLFNBQVMsRUFBRSxDQUFDO3dCQUNkLFdBQVcsR0FBRyxTQUFTLENBQUM7d0JBQ3hCLE9BQU8sQ0FBQyxLQUFLLENBQ1gsMkJBQTJCLFNBQVMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSTs0QkFDM0UsbUJBQW1CLFNBQVMsQ0FBQyxLQUFLLGlCQUFpQixDQUN0RCxDQUFDO29CQUNKLENBQUM7Z0JBQ0gsQ0FBQztnQkFFRCxpREFBaUQ7Z0JBQ2pELE1BQU0sS0FBSyxHQUFtQjtvQkFDNUIsSUFBSTtvQkFDSixJQUFJLEVBQUUsR0FBRztvQkFDVCxJQUFJLEVBQUUsYUFBYTtpQkFDcEIsQ0FBQztnQkFFRix5REFBeUQ7Z0JBQ3pELE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztnQkFFNUQsa0JBQWtCO2dCQUNsQixNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBRTdCLG9FQUFvRTtnQkFDcEUsSUFBSSxXQUFXLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO29CQUN4QyxnREFBZ0Q7b0JBQ2hELE1BQU0sbUJBQW1CLEdBQUcsSUFBQSxtREFBNkIsRUFDdkQsYUFBYSxFQUNiLElBQUksRUFDSixhQUFhLEVBQ2IsV0FBVyxDQUFDLEtBQUssRUFDakIsV0FBVyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQzVCLENBQUM7b0JBRUYsMkJBQTJCO29CQUMzQixNQUFNLFNBQVMsR0FBRyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLGFBQWEsQ0FBQyxDQUFDLElBQUksQ0FBQztvQkFDcEUsTUFBTSxjQUFjLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLENBQUMsTUFBTSxDQUFDO29CQUM3RCxNQUFNLHFCQUFxQixHQUFHLGFBQWEsR0FBRyxjQUFjLENBQUM7b0JBRTdELElBQUksSUFBQSwwQkFBYyxHQUFFLEVBQUUsQ0FBQzt3QkFDckIsT0FBTyxDQUFDLEtBQUssQ0FDWCxzQ0FBc0M7NEJBQ3BDLDJCQUEyQixhQUFhLElBQUk7NEJBQzVDLHVCQUF1QixjQUFjLFVBQVU7NEJBQy9DLHlCQUF5QixxQkFBcUIsSUFBSTs0QkFDbEQsOEJBQThCLG1CQUFtQixJQUFJOzRCQUNyRCw0QkFBNEIsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUNsRCxDQUFDO29CQUNKLENBQUM7b0JBRUQsNkRBQTZEO29CQUM3RCxJQUFJLG1CQUFtQixHQUFHLHFCQUFxQixFQUFFLENBQUM7d0JBQ2hELE9BQU8sQ0FBQyxLQUFLLENBQ1gsK0NBQStDOzRCQUM3QyxxQkFBcUIsbUJBQW1CLHdCQUF3QixxQkFBcUIsRUFBRSxDQUMxRixDQUFDO29CQUNKLENBQUM7eUJBQU0sQ0FBQzt3QkFDTiw0Q0FBNEM7d0JBQzVDLElBQUksQ0FBQyxlQUFlLENBQUM7NEJBQ25CLFFBQVEsRUFBRSxXQUFXLENBQUMsUUFBUTs0QkFDOUIsUUFBUSxFQUFFLG1CQUFtQjs0QkFDN0IsU0FBUyxFQUFFLElBQUk7eUJBQ2hCLENBQUMsQ0FBQzt3QkFFSCw4QkFBOEI7d0JBQzlCLElBQUEseUNBQW1CLEVBQUMsV0FBVyxDQUFDLFFBQVEsRUFBRSxtQkFBbUIsRUFBRSxhQUFhLENBQUMsQ0FBQztvQkFDaEYsQ0FBQztnQkFDSCxDQUFDO1lBQ0gsQ0FBQztZQUVELG9EQUFvRDtZQUNwRCxJQUFJLENBQUMsVUFBVSxHQUFHLGVBQWUsQ0FBQztRQUNwQyxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNILFVBQVUsQ0FBQyxJQUFZO1FBQ3JCLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQ2pDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUNuQyxNQUFNLEtBQUssR0FBbUI7Z0JBQzVCLElBQUk7Z0JBQ0osSUFBSSxFQUFFLEdBQUc7Z0JBQ1QsSUFBSTthQUNMLENBQUM7WUFDRixNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDL0IsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxXQUFXLENBQUMsSUFBWSxFQUFFLElBQVk7UUFDcEMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDakMsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQ25DLE1BQU0sS0FBSyxHQUFtQjtnQkFDNUIsSUFBSTtnQkFDSixJQUFJLEVBQUUsR0FBRztnQkFDVCxJQUFJLEVBQUUsR0FBRyxJQUFJLElBQUksSUFBSSxFQUFFO2FBQ3hCLENBQUM7WUFDRixNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDL0IsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxXQUFXLENBQUMsT0FBZTtRQUN6QixJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLElBQUksRUFBRTtZQUNqQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDbkMsTUFBTSxLQUFLLEdBQW1CO2dCQUM1QixJQUFJO2dCQUNKLElBQUksRUFBRSxHQUFHO2dCQUNULElBQUksRUFBRSxPQUFPO2FBQ2QsQ0FBQztZQUNGLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMvQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNILFlBQVksQ0FBQyxTQUFrQjtRQUM3QixJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLElBQUksRUFBRTtZQUNqQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQzdDLE1BQU0sUUFBUSxHQUFHLEdBQUcsVUFBVSxJQUFJLENBQUM7WUFDbkMsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUMsTUFBTSxDQUFDO1lBRXZELG1DQUFtQztZQUNuQyxJQUFJLENBQUMsWUFBWSxJQUFJLFNBQVMsQ0FBQztZQUUvQixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUNsRCxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ2QsTUFBTSxJQUFBLGFBQUksRUFBQyxJQUFJLENBQUMsV0FBVyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQ3hDLENBQUM7WUFFRCxxQ0FBcUM7WUFDckMsSUFBSSxDQUFDLFlBQVksSUFBSSxTQUFTLENBQUM7WUFDL0IsSUFBSSxDQUFDLFlBQVksSUFBSSxTQUFTLENBQUM7UUFDakMsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsVUFBVSxDQUFDLEtBQXFCO1FBQzVDLHVDQUF1QztRQUN2QyxNQUFNLFVBQVUsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUM3QyxNQUFNLFNBQVMsR0FBRyxHQUFHLFNBQVMsSUFBSSxDQUFDO1FBQ25DLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQztRQUV6RCwrQ0FBK0M7UUFDL0MsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLEdBQUcsSUFBSSxJQUFBLDBCQUFjLEdBQUUsRUFBRSxDQUFDO1lBQzNDLE9BQU8sQ0FBQyxLQUFLLENBQ1gseUJBQXlCLFVBQVUsVUFBVTtnQkFDM0MsZ0JBQWdCLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxVQUFVO2dCQUMzQyxhQUFhLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUN2RCxDQUFDO1FBQ0osQ0FBQztRQUVELG1DQUFtQztRQUNuQyxJQUFJLENBQUMsWUFBWSxJQUFJLFVBQVUsQ0FBQztRQUVoQyxnQ0FBZ0M7UUFDaEMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDbkQsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ2QsT0FBTyxDQUFDLEtBQUssQ0FBQyx1REFBdUQsQ0FBQyxDQUFDO1lBQ3ZFLE1BQU0sSUFBQSxhQUFJLEVBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUN4QyxDQUFDO1FBRUQscUNBQXFDO1FBQ3JDLElBQUksQ0FBQyxZQUFZLElBQUksVUFBVSxDQUFDO1FBQ2hDLElBQUksQ0FBQyxZQUFZLElBQUksVUFBVSxDQUFDO1FBRWhDLGlDQUFpQztRQUNqQyxJQUFJLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixHQUFHLElBQUksR0FBRyxJQUFJLEVBQUUsQ0FBQztZQUNqRSxZQUFZO1lBQ1osTUFBTSxJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztRQUNwQyxDQUFDO1FBRUQsOEJBQThCO1FBQzlCLElBQUksSUFBSSxDQUFDLEVBQUUsS0FBSyxJQUFJLEVBQUUsQ0FBQztZQUNyQixJQUFJLENBQUM7Z0JBQ0gsTUFBTSxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ3ZCLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLE9BQU8sQ0FBQyxLQUFLLENBQUMsb0JBQW9CLElBQUksQ0FBQyxRQUFRLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUMzRCxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNLLG1CQUFtQixDQUFDLE1BQWM7UUFDeEMsSUFBSSxNQUFNLEdBQUcsRUFBRSxDQUFDO1FBQ2hCLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztRQUVaLE9BQU8sR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUMzQixxREFBcUQ7WUFDckQsSUFBSSxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUM7Z0JBQ3pCLHVDQUF1QztnQkFDdkMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDaEUsSUFBSSxNQUFNLEtBQUssSUFBSSxFQUFFLENBQUM7b0JBQ3BCLE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLEdBQUcsR0FBRyxNQUFNLENBQUMsQ0FBQztvQkFDcEQsd0VBQXdFO29CQUN4RSxNQUFNLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztvQkFDdEMsR0FBRyxJQUFJLE1BQU0sQ0FBQztnQkFDaEIsQ0FBQztxQkFBTSxDQUFDO29CQUNOLCtEQUErRDtvQkFDL0QsT0FBTzt3QkFDTCxhQUFhLEVBQUUsTUFBTTt3QkFDckIsZUFBZSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDO3FCQUN0QyxDQUFDO2dCQUNKLENBQUM7WUFDSCxDQUFDO2lCQUFNLENBQUM7Z0JBQ04scUVBQXFFO2dCQUNyRSxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUM7Z0JBQ3ZCLE9BQU8sR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO29CQUNuRCxHQUFHLEVBQUUsQ0FBQztnQkFDUixDQUFDO2dCQUVELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxFQUFFLEdBQUcsQ0FBQyxDQUFDO2dCQUVuRCwwQ0FBMEM7Z0JBQzFDLElBQUksQ0FBQztvQkFDSCxNQUFNLFNBQVMsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO29CQUM3QyxNQUFNLElBQUksU0FBUyxDQUFDO2dCQUN0QixDQUFDO2dCQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7b0JBQ1osc0NBQXNDO29CQUN0QyxNQUFNLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsU0FBUyxDQUFDLENBQUM7b0JBRWxFLElBQUksU0FBUyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQzt3QkFDekIsTUFBTSxJQUFJLFNBQVMsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7b0JBQ3ZDLENBQUM7b0JBRUQsK0NBQStDO29CQUMvQyxJQUFJLFlBQVksR0FBRyxTQUFTLENBQUMsTUFBTSxJQUFJLEdBQUcsSUFBSSxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7d0JBQzVELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxHQUFHLFlBQVksQ0FBQyxDQUFDO3dCQUU3RCx5REFBeUQ7d0JBQ3pELElBQUksU0FBUyxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksSUFBSSxDQUFDLHFCQUFxQixDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7NEJBQ25FLE9BQU87Z0NBQ0wsYUFBYSxFQUFFLE1BQU07Z0NBQ3JCLGVBQWUsRUFBRSxTQUFTOzZCQUMzQixDQUFDO3dCQUNKLENBQUM7b0JBQ0gsQ0FBQztvQkFFRCx1REFBdUQ7b0JBQ3ZELHFDQUFxQztvQkFDckMsTUFBTSxXQUFXLEdBQUcsU0FBUyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQztvQkFDckQsTUFBTSxJQUFJLFdBQVcsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7Z0JBQzNDLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxFQUFFLGVBQWUsRUFBRSxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDckUsQ0FBQztJQUVEOztPQUVHO0lBQ0sscUJBQXFCLENBQUMsTUFBYztRQUMxQyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQztZQUM5QyxPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFFRCxJQUFJLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDdEIsT0FBTyxJQUFJLENBQUMsQ0FBQyw4QkFBOEI7UUFDN0MsQ0FBQztRQUVELFFBQVEsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDbEIsc0NBQXNDO1lBQ3RDLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDVixNQUFNO2dCQUNOLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztnQkFDWiw2Q0FBNkM7Z0JBQzdDLE9BQU8sR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQztvQkFDM0IsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO29CQUN6QixJQUFJLElBQUksSUFBSSxJQUFJLElBQUksSUFBSSxJQUFJLElBQUksRUFBRSxDQUFDO3dCQUNqQyxtRUFBbUU7d0JBQ25FLEdBQUcsRUFBRSxDQUFDO29CQUNSLENBQUM7eUJBQU0sSUFBSSxJQUFJLElBQUksSUFBSSxJQUFJLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQzt3QkFDeEMsZ0RBQWdEO3dCQUNoRCxPQUFPLEdBQUcsR0FBRyxDQUFDLENBQUM7b0JBQ2pCLENBQUM7eUJBQU0sQ0FBQzt3QkFDTiw4QkFBOEI7d0JBQzlCLE9BQU8sR0FBRyxDQUFDO29CQUNiLENBQUM7Z0JBQ0gsQ0FBQztnQkFDRCxPQUFPLElBQUksQ0FBQyxDQUFDLHNCQUFzQjtZQUNyQyxDQUFDO1lBRUQsdUNBQXVDO1lBQ3ZDLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDVixNQUFNO2dCQUNOLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztnQkFDWixPQUFPLEdBQUcsR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7b0JBQzNCLE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztvQkFDekIsSUFBSSxJQUFJLEtBQUssSUFBSSxFQUFFLENBQUM7d0JBQ2xCLGlCQUFpQjt3QkFDakIsT0FBTyxHQUFHLEdBQUcsQ0FBQyxDQUFDO29CQUNqQixDQUFDO3lCQUFNLElBQUksSUFBSSxLQUFLLElBQUksSUFBSSxHQUFHLEdBQUcsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQzt3QkFDaEYsd0JBQXdCO3dCQUN4QixPQUFPLEdBQUcsR0FBRyxDQUFDLENBQUM7b0JBQ2pCLENBQUM7b0JBQ0QsR0FBRyxFQUFFLENBQUM7Z0JBQ1IsQ0FBQztnQkFDRCxPQUFPLElBQUksQ0FBQyxDQUFDLHNCQUFzQjtZQUNyQyxDQUFDO1lBRUQsNkNBQTZDO1lBQzdDO2dCQUNFLE9BQU8sQ0FBQyxDQUFDO1FBQ2IsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNLLGFBQWEsQ0FBQyxNQUFjO1FBQ2xDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDdkMsSUFBSSxDQUFDO2dCQUNILE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDNUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM3QixDQUFDO1lBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztnQkFDWiw0Q0FBNEM7Z0JBQzVDLE9BQU87b0JBQ0wsU0FBUyxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQztvQkFDaEMsWUFBWSxFQUFFLENBQUM7aUJBQ2hCLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQztRQUVELFlBQVk7UUFDWixPQUFPO1lBQ0wsU0FBUyxFQUFFLE1BQU07WUFDakIsWUFBWSxFQUFFLE1BQU0sQ0FBQyxNQUFNO1NBQzVCLENBQUM7SUFDSixDQUFDO0lBRUQ7O09BRUc7SUFDSyxxQkFBcUIsQ0FBQyxNQUFjO1FBQzFDLElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFFdEMsMkRBQTJEO1FBQzNELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUU1Qix1Q0FBdUM7UUFDdkMsSUFBSSxTQUFTLEdBQUcsSUFBSTtZQUFFLE9BQU8sS0FBSyxDQUFDO1FBRW5DLCtCQUErQjtRQUMvQixJQUFJLFNBQVMsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN0QixnQ0FBZ0M7WUFDaEMsSUFBSSxTQUFTLEdBQUcsSUFBSTtnQkFBRSxPQUFPLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO1lBQy9DLGdDQUFnQztZQUNoQyxJQUFJLFNBQVMsR0FBRyxJQUFJO2dCQUFFLE9BQU8sTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7WUFDL0MsZ0NBQWdDO1lBQ2hDLElBQUksU0FBUyxHQUFHLElBQUk7Z0JBQUUsT0FBTyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztRQUNqRCxDQUFDO1FBRUQsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQ7O09BRUc7SUFDSyxjQUFjO1FBQ3BCLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQztJQUN4RCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsb0JBQW9CO1FBQ2hDLElBQUksQ0FBQztZQUNILE1BQU0sS0FBSyxHQUFHLE1BQU0sRUFBRSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQ3BELE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUM7WUFDOUIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztZQUV2QyxJQUFJLFVBQVUsS0FBSyxZQUFZLEVBQUUsQ0FBQztnQkFDaEMsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7Z0JBQ3hCLE9BQU8sQ0FBQyxLQUFLLENBQ1gscUNBQXFDO29CQUNuQyxhQUFhLFlBQVksbUJBQW1CLFVBQVUsVUFBVTtvQkFDaEUsZUFBZSxVQUFVLEdBQUcsWUFBWSxVQUFVO29CQUNsRCxzQkFBc0IsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQ2hELENBQUM7Z0JBRUYsNkRBQTZEO2dCQUM3RCxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsVUFBVSxHQUFHLFlBQVksQ0FBQyxHQUFHLEdBQUcsRUFBRSxDQUFDO29CQUM5QyxNQUFNLElBQUksbUJBQVEsQ0FDaEIsbURBQW1ELFlBQVksWUFBWSxVQUFVLEVBQUUsRUFDdkYsbUJBQW1CLENBQ3BCLENBQUM7Z0JBQ0osQ0FBQztZQUNILENBQUM7aUJBQU0sQ0FBQztnQkFDTixPQUFPLENBQUMsS0FBSyxDQUFDLCtCQUErQixVQUFVLFFBQVEsQ0FBQyxDQUFDO1lBQ25FLENBQUM7WUFFRCxJQUFJLENBQUMscUJBQXFCLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQztRQUNqRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksS0FBSyxZQUFZLG1CQUFRLEVBQUUsQ0FBQztnQkFDOUIsTUFBTSxLQUFLLENBQUM7WUFDZCxDQUFDO1lBQ0QsT0FBTyxDQUFDLEtBQUssQ0FBQyxtQ0FBbUMsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUM1RCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLEtBQUs7UUFDVCxxREFBcUQ7UUFDckQsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUMvQix3REFBd0Q7WUFDeEQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQ25DLE1BQU0sS0FBSyxHQUFtQjtnQkFDNUIsSUFBSTtnQkFDSixJQUFJLEVBQUUsR0FBRztnQkFDVCxJQUFJLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO2FBQ3pDLENBQUM7WUFDRixtQ0FBbUM7WUFDbkMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxJQUFJLEVBQUU7Z0JBQ2pDLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUMvQixDQUFDLENBQUMsQ0FBQztZQUNILElBQUksQ0FBQyxVQUFVLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNwQyxDQUFDO1FBRUQseUNBQXlDO1FBQ3pDLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUU5QixrQ0FBa0M7UUFDbEMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUNyQyxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQWEsRUFBRSxFQUFFO2dCQUNyQyxJQUFJLEtBQUssRUFBRSxDQUFDO29CQUNWLE1BQU0sQ0FBQyxJQUFJLG1CQUFRLENBQUMscUNBQXFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzdFLENBQUM7cUJBQU0sQ0FBQztvQkFDTixPQUFPLEVBQUUsQ0FBQztnQkFDWixDQUFDO1lBQ0gsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNILE1BQU07UUFDSixPQUFPLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUM7SUFDckMsQ0FBQztDQUNGO0FBOWtCRCwwQ0E4a0JDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBBc2NpaW5lbWFXcml0ZXIgLSBSZWNvcmRzIHRlcm1pbmFsIHNlc3Npb25zIGluIGFzY2lpbmVtYSBmb3JtYXRcbiAqXG4gKiBUaGlzIGNsYXNzIHdyaXRlcyB0ZXJtaW5hbCBvdXRwdXQgaW4gdGhlIHN0YW5kYXJkIGFzY2lpbmVtYSBjYXN0IGZvcm1hdCAodjIpLFxuICogd2hpY2ggaXMgY29tcGF0aWJsZSB3aXRoIGFzY2lpbmVtYSBwbGF5ZXJzIGFuZCB0aGUgZXhpc3Rpbmcgd2ViIGludGVyZmFjZS5cbiAqIEl0IGhhbmRsZXMgcmVhbC10aW1lIHN0cmVhbWluZyBvZiB0ZXJtaW5hbCBkYXRhIHdoaWxlIHByb3Blcmx5IG1hbmFnaW5nOlxuICogLSBVVEYtOCBlbmNvZGluZyBhbmQgaW5jb21wbGV0ZSBtdWx0aS1ieXRlIHNlcXVlbmNlc1xuICogLSBBTlNJIGVzY2FwZSBzZXF1ZW5jZXMgcHJlc2VydmF0aW9uXG4gKiAtIEJ1ZmZlcmluZyBhbmQgYmFja3ByZXNzdXJlXG4gKiAtIEF0b21pYyB3cml0ZXMgd2l0aCBmc3luYyBmb3IgZHVyYWJpbGl0eVxuICpcbiAqIEtleSBmZWF0dXJlczpcbiAqIC0gUmVhbC10aW1lIHJlY29yZGluZyB3aXRoIG1pbmltYWwgYnVmZmVyaW5nXG4gKiAtIFByb3BlciBoYW5kbGluZyBvZiBlc2NhcGUgc2VxdWVuY2VzIGFjcm9zcyBidWZmZXIgYm91bmRhcmllc1xuICogLSBTdXBwb3J0IGZvciBhbGwgYXNjaWluZW1hIGV2ZW50IHR5cGVzIChvdXRwdXQsIGlucHV0LCByZXNpemUsIG1hcmtlcnMpXG4gKiAtIEF1dG9tYXRpYyBkaXJlY3RvcnkgY3JlYXRpb24gYW5kIGZpbGUgbWFuYWdlbWVudFxuICogLSBUaHJlYWQtc2FmZSB3cml0ZSBxdWV1ZSBmb3IgY29uY3VycmVudCBvcGVyYXRpb25zXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIENyZWF0ZSBhIHdyaXRlciBmb3IgYSBuZXcgcmVjb3JkaW5nXG4gKiBjb25zdCB3cml0ZXIgPSBBc2NpaW5lbWFXcml0ZXIuY3JlYXRlKFxuICogICAnL3BhdGgvdG8vcmVjb3JkaW5nLmNhc3QnLFxuICogICA4MCwgIC8vIHRlcm1pbmFsIHdpZHRoXG4gKiAgIDI0LCAgLy8gdGVybWluYWwgaGVpZ2h0XG4gKiAgICducG0gdGVzdCcsICAvLyBjb21tYW5kIGJlaW5nIHJlY29yZGVkXG4gKiAgICdUZXN0IFJ1biBSZWNvcmRpbmcnICAvLyB0aXRsZVxuICogKTtcbiAqXG4gKiAvLyBXcml0ZSB0ZXJtaW5hbCBvdXRwdXRcbiAqIHdyaXRlci53cml0ZU91dHB1dChCdWZmZXIuZnJvbSgnSGVsbG8sIHdvcmxkIVxcclxcbicpKTtcbiAqXG4gKiAvLyBSZWNvcmQgdXNlciBpbnB1dFxuICogd3JpdGVyLndyaXRlSW5wdXQoJ2xzIC1sYScpO1xuICpcbiAqIC8vIEhhbmRsZSB0ZXJtaW5hbCByZXNpemVcbiAqIHdyaXRlci53cml0ZVJlc2l6ZSgxMjAsIDQwKTtcbiAqXG4gKiAvLyBBZGQgYSBib29rbWFyay9tYXJrZXJcbiAqIHdyaXRlci53cml0ZU1hcmtlcignVGVzdCBzdGFydGVkJyk7XG4gKlxuICogLy8gQ2xvc2UgdGhlIHJlY29yZGluZyB3aGVuIGRvbmVcbiAqIGF3YWl0IHdyaXRlci5jbG9zZSgpO1xuICogYGBgXG4gKi9cblxuaW1wb3J0IHsgb25jZSB9IGZyb20gJ2V2ZW50cyc7XG5pbXBvcnQgKiBhcyBmcyBmcm9tICdmcyc7XG5pbXBvcnQgKiBhcyBwYXRoIGZyb20gJ3BhdGgnO1xuaW1wb3J0IHsgcHJvbWlzaWZ5IH0gZnJvbSAndXRpbCc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIsIGlzRGVidWdFbmFibGVkIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7XG4gIGNhbGN1bGF0ZVNlcXVlbmNlQnl0ZVBvc2l0aW9uLFxuICBkZXRlY3RMYXN0UHJ1bmluZ1NlcXVlbmNlLFxuICBsb2dQcnVuaW5nRGV0ZWN0aW9uLFxufSBmcm9tICcuLi91dGlscy9wcnVuaW5nLWRldGVjdG9yLmpzJztcbmltcG9ydCB7IFdyaXRlUXVldWUgfSBmcm9tICcuLi91dGlscy93cml0ZS1xdWV1ZS5qcyc7XG5pbXBvcnQgeyB0eXBlIEFzY2lpbmVtYUV2ZW50LCB0eXBlIEFzY2lpbmVtYUhlYWRlciwgUHR5RXJyb3IgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuY29uc3QgX2xvZ2dlciA9IGNyZWF0ZUxvZ2dlcignQXNjaWluZW1hV3JpdGVyJyk7XG5jb25zdCBmc3luYyA9IHByb21pc2lmeShmcy5mc3luYyk7XG5cbi8vIFR5cGUgZm9yIHBydW5pbmcgc2VxdWVuY2UgY2FsbGJhY2tcbmV4cG9ydCB0eXBlIFBydW5pbmdDYWxsYmFjayA9IChpbmZvOiB7XG4gIHNlcXVlbmNlOiBzdHJpbmc7XG4gIHBvc2l0aW9uOiBudW1iZXI7XG4gIHRpbWVzdGFtcDogbnVtYmVyO1xufSkgPT4gdm9pZDtcblxuZXhwb3J0IGNsYXNzIEFzY2lpbmVtYVdyaXRlciB7XG4gIHByaXZhdGUgd3JpdGVTdHJlYW06IGZzLldyaXRlU3RyZWFtO1xuICBwcml2YXRlIHN0YXJ0VGltZTogRGF0ZTtcbiAgcHJpdmF0ZSB1dGY4QnVmZmVyOiBCdWZmZXIgPSBCdWZmZXIuYWxsb2MoMCk7XG4gIHByaXZhdGUgaGVhZGVyV3JpdHRlbiA9IGZhbHNlO1xuICBwcml2YXRlIGZkOiBudW1iZXIgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSB3cml0ZVF1ZXVlID0gbmV3IFdyaXRlUXVldWUoKTtcblxuICAvLyBCeXRlIHBvc2l0aW9uIHRyYWNraW5nXG4gIHByaXZhdGUgYnl0ZXNXcml0dGVuOiBudW1iZXIgPSAwOyAvLyBCeXRlcyBhY3R1YWxseSB3cml0dGVuIHRvIGRpc2tcbiAgcHJpdmF0ZSBwZW5kaW5nQnl0ZXM6IG51bWJlciA9IDA7IC8vIEJ5dGVzIHF1ZXVlZCBidXQgbm90IHlldCB3cml0dGVuXG5cbiAgLy8gUHJ1bmluZyBzZXF1ZW5jZSBkZXRlY3Rpb24gY2FsbGJhY2tcbiAgcHJpdmF0ZSBwcnVuaW5nQ2FsbGJhY2s/OiBQcnVuaW5nQ2FsbGJhY2s7XG5cbiAgLy8gVmFsaWRhdGlvbiB0cmFja2luZ1xuICBwcml2YXRlIGxhc3RWYWxpZGF0ZWRQb3NpdGlvbjogbnVtYmVyID0gMDtcbiAgcHJpdmF0ZSB2YWxpZGF0aW9uRXJyb3JzOiBudW1iZXIgPSAwO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgZmlsZVBhdGg6IHN0cmluZyxcbiAgICBwcml2YXRlIGhlYWRlcjogQXNjaWluZW1hSGVhZGVyXG4gICkge1xuICAgIHRoaXMuc3RhcnRUaW1lID0gbmV3IERhdGUoKTtcblxuICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzXG4gICAgY29uc3QgZGlyID0gcGF0aC5kaXJuYW1lKGZpbGVQYXRoKTtcbiAgICBpZiAoIWZzLmV4aXN0c1N5bmMoZGlyKSkge1xuICAgICAgZnMubWtkaXJTeW5jKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgfVxuXG4gICAgLy8gQ3JlYXRlIHdyaXRlIHN0cmVhbSB3aXRoIG5vIGJ1ZmZlcmluZyBmb3IgcmVhbC10aW1lIHBlcmZvcm1hbmNlXG4gICAgdGhpcy53cml0ZVN0cmVhbSA9IGZzLmNyZWF0ZVdyaXRlU3RyZWFtKGZpbGVQYXRoLCB7XG4gICAgICBmbGFnczogJ3cnLFxuICAgICAgZW5jb2Rpbmc6ICd1dGY4JyxcbiAgICAgIGhpZ2hXYXRlck1hcms6IDAsIC8vIERpc2FibGUgaW50ZXJuYWwgYnVmZmVyaW5nXG4gICAgfSk7XG5cbiAgICAvLyBHZXQgZmlsZSBkZXNjcmlwdG9yIGZvciBmc3luY1xuICAgIHRoaXMud3JpdGVTdHJlYW0ub24oJ29wZW4nLCAoZmQpID0+IHtcbiAgICAgIHRoaXMuZmQgPSBmZDtcbiAgICB9KTtcblxuICAgIHRoaXMud3JpdGVIZWFkZXIoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYW4gQXNjaWluZW1hV3JpdGVyIHdpdGggc3RhbmRhcmQgcGFyYW1ldGVyc1xuICAgKi9cbiAgc3RhdGljIGNyZWF0ZShcbiAgICBmaWxlUGF0aDogc3RyaW5nLFxuICAgIHdpZHRoOiBudW1iZXIgPSA4MCxcbiAgICBoZWlnaHQ6IG51bWJlciA9IDI0LFxuICAgIGNvbW1hbmQ/OiBzdHJpbmcsXG4gICAgdGl0bGU/OiBzdHJpbmcsXG4gICAgZW52PzogUmVjb3JkPHN0cmluZywgc3RyaW5nPlxuICApOiBBc2NpaW5lbWFXcml0ZXIge1xuICAgIGNvbnN0IGhlYWRlcjogQXNjaWluZW1hSGVhZGVyID0ge1xuICAgICAgdmVyc2lvbjogMixcbiAgICAgIHdpZHRoLFxuICAgICAgaGVpZ2h0LFxuICAgICAgdGltZXN0YW1wOiBNYXRoLmZsb29yKERhdGUubm93KCkgLyAxMDAwKSxcbiAgICAgIGNvbW1hbmQsXG4gICAgICB0aXRsZSxcbiAgICAgIGVudixcbiAgICB9O1xuXG4gICAgcmV0dXJuIG5ldyBBc2NpaW5lbWFXcml0ZXIoZmlsZVBhdGgsIGhlYWRlcik7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBjdXJyZW50IGJ5dGUgcG9zaXRpb24gaW4gdGhlIGZpbGVcbiAgICogQHJldHVybnMgT2JqZWN0IHdpdGggY3VycmVudCBwb3NpdGlvbiBhbmQgcGVuZGluZyBieXRlc1xuICAgKi9cbiAgZ2V0UG9zaXRpb24oKTogeyB3cml0dGVuOiBudW1iZXI7IHBlbmRpbmc6IG51bWJlcjsgdG90YWw6IG51bWJlciB9IHtcbiAgICByZXR1cm4ge1xuICAgICAgd3JpdHRlbjogdGhpcy5ieXRlc1dyaXR0ZW4sIC8vIEJ5dGVzIGFjdHVhbGx5IHdyaXR0ZW4gdG8gZGlza1xuICAgICAgcGVuZGluZzogdGhpcy5wZW5kaW5nQnl0ZXMsIC8vIEJ5dGVzIGluIHF1ZXVlXG4gICAgICB0b3RhbDogdGhpcy5ieXRlc1dyaXR0ZW4gKyB0aGlzLnBlbmRpbmdCeXRlcywgLy8gVG90YWwgcG9zaXRpb24gYWZ0ZXIgcXVldWUgZmx1c2hcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFNldCBhIGNhbGxiYWNrIHRvIGJlIG5vdGlmaWVkIHdoZW4gcHJ1bmluZyBzZXF1ZW5jZXMgYXJlIGRldGVjdGVkXG4gICAqIEBwYXJhbSBjYWxsYmFjayBGdW5jdGlvbiBjYWxsZWQgd2l0aCBzZXF1ZW5jZSBpbmZvIGFuZCBieXRlIHBvc2l0aW9uXG4gICAqL1xuICBvblBydW5pbmdTZXF1ZW5jZShjYWxsYmFjazogUHJ1bmluZ0NhbGxiYWNrKTogdm9pZCB7XG4gICAgdGhpcy5wcnVuaW5nQ2FsbGJhY2sgPSBjYWxsYmFjaztcbiAgfVxuXG4gIC8qKlxuICAgKiBXcml0ZSB0aGUgYXNjaWluZW1hIGhlYWRlciB0byB0aGUgZmlsZVxuICAgKi9cbiAgcHJpdmF0ZSB3cml0ZUhlYWRlcigpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5oZWFkZXJXcml0dGVuKSByZXR1cm47XG5cbiAgICB0aGlzLndyaXRlUXVldWUuZW5xdWV1ZShhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBoZWFkZXJKc29uID0gSlNPTi5zdHJpbmdpZnkodGhpcy5oZWFkZXIpO1xuICAgICAgY29uc3QgaGVhZGVyTGluZSA9IGAke2hlYWRlckpzb259XFxuYDtcbiAgICAgIGNvbnN0IGhlYWRlckJ5dGVzID0gQnVmZmVyLmZyb20oaGVhZGVyTGluZSwgJ3V0ZjgnKS5sZW5ndGg7XG5cbiAgICAgIC8vIFRyYWNrIHBlbmRpbmcgYnl0ZXMgYmVmb3JlIHdyaXRlXG4gICAgICB0aGlzLnBlbmRpbmdCeXRlcyArPSBoZWFkZXJCeXRlcztcblxuICAgICAgY29uc3QgY2FuV3JpdGUgPSB0aGlzLndyaXRlU3RyZWFtLndyaXRlKGhlYWRlckxpbmUpO1xuICAgICAgaWYgKCFjYW5Xcml0ZSkge1xuICAgICAgICBhd2FpdCBvbmNlKHRoaXMud3JpdGVTdHJlYW0sICdkcmFpbicpO1xuICAgICAgfVxuXG4gICAgICAvLyBNb3ZlIGJ5dGVzIGZyb20gcGVuZGluZyB0byB3cml0dGVuXG4gICAgICB0aGlzLmJ5dGVzV3JpdHRlbiArPSBoZWFkZXJCeXRlcztcbiAgICAgIHRoaXMucGVuZGluZ0J5dGVzIC09IGhlYWRlckJ5dGVzO1xuICAgIH0pO1xuICAgIHRoaXMuaGVhZGVyV3JpdHRlbiA9IHRydWU7XG4gIH1cblxuICAvKipcbiAgICogV3JpdGUgdGVybWluYWwgb3V0cHV0IGRhdGFcbiAgICovXG4gIHdyaXRlT3V0cHV0KGRhdGE6IEJ1ZmZlcik6IHZvaWQge1xuICAgIHRoaXMud3JpdGVRdWV1ZS5lbnF1ZXVlKGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHRpbWUgPSB0aGlzLmdldEVsYXBzZWRUaW1lKCk7XG5cbiAgICAgIC8vIENvbWJpbmUgYW55IGJ1ZmZlcmVkIGJ5dGVzIHdpdGggdGhlIG5ldyBkYXRhXG4gICAgICBjb25zdCBjb21iaW5lZEJ1ZmZlciA9IEJ1ZmZlci5jb25jYXQoW3RoaXMudXRmOEJ1ZmZlciwgZGF0YV0pO1xuXG4gICAgICAvLyBQcm9jZXNzIGRhdGEgaW4gZXNjYXBlLXNlcXVlbmNlLWF3YXJlIGNodW5rc1xuICAgICAgY29uc3QgeyBwcm9jZXNzZWREYXRhLCByZW1haW5pbmdCdWZmZXIgfSA9IHRoaXMucHJvY2Vzc1Rlcm1pbmFsRGF0YShjb21iaW5lZEJ1ZmZlcik7XG5cbiAgICAgIGlmIChwcm9jZXNzZWREYXRhLmxlbmd0aCA+IDApIHtcbiAgICAgICAgLy8gRmlyc3QsIGNoZWNrIGZvciBwcnVuaW5nIHNlcXVlbmNlcyBpbiB0aGUgZGF0YVxuICAgICAgICBsZXQgcHJ1bmluZ0luZm86IHsgc2VxdWVuY2U6IHN0cmluZzsgaW5kZXg6IG51bWJlciB9IHwgbnVsbCA9IG51bGw7XG5cbiAgICAgICAgaWYgKHRoaXMucHJ1bmluZ0NhbGxiYWNrKSB7XG4gICAgICAgICAgLy8gVXNlIHNoYXJlZCBkZXRlY3RvciB0byBmaW5kIHBydW5pbmcgc2VxdWVuY2VzXG4gICAgICAgICAgY29uc3QgZGV0ZWN0aW9uID0gZGV0ZWN0TGFzdFBydW5pbmdTZXF1ZW5jZShwcm9jZXNzZWREYXRhKTtcblxuICAgICAgICAgIGlmIChkZXRlY3Rpb24pIHtcbiAgICAgICAgICAgIHBydW5pbmdJbmZvID0gZGV0ZWN0aW9uO1xuICAgICAgICAgICAgX2xvZ2dlci5kZWJ1ZyhcbiAgICAgICAgICAgICAgYEZvdW5kIHBydW5pbmcgc2VxdWVuY2UgJyR7ZGV0ZWN0aW9uLnNlcXVlbmNlLnNwbGl0KCdcXHgxYicpLmpvaW4oJ1xcXFx4MWInKX0nIGAgK1xuICAgICAgICAgICAgICAgIGBhdCBzdHJpbmcgaW5kZXggJHtkZXRlY3Rpb24uaW5kZXh9IGluIG91dHB1dCBkYXRhYFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBDcmVhdGUgdGhlIGV2ZW50IHdpdGggQUxMIGRhdGEgKG5vdCB0cnVuY2F0ZWQpXG4gICAgICAgIGNvbnN0IGV2ZW50OiBBc2NpaW5lbWFFdmVudCA9IHtcbiAgICAgICAgICB0aW1lLFxuICAgICAgICAgIHR5cGU6ICdvJyxcbiAgICAgICAgICBkYXRhOiBwcm9jZXNzZWREYXRhLFxuICAgICAgICB9O1xuXG4gICAgICAgIC8vIENhbGN1bGF0ZSB0aGUgYnl0ZSBwb3NpdGlvbiB3aGVyZSB0aGUgZXZlbnQgd2lsbCBzdGFydFxuICAgICAgICBjb25zdCBldmVudFN0YXJ0UG9zID0gdGhpcy5ieXRlc1dyaXR0ZW4gKyB0aGlzLnBlbmRpbmdCeXRlcztcblxuICAgICAgICAvLyBXcml0ZSB0aGUgZXZlbnRcbiAgICAgICAgYXdhaXQgdGhpcy53cml0ZUV2ZW50KGV2ZW50KTtcblxuICAgICAgICAvLyBOb3cgdGhhdCB0aGUgd3JpdGUgaXMgY29tcGxldGUsIGhhbmRsZSBwcnVuaW5nIGNhbGxiYWNrIGlmIG5lZWRlZFxuICAgICAgICBpZiAocHJ1bmluZ0luZm8gJiYgdGhpcy5wcnVuaW5nQ2FsbGJhY2spIHtcbiAgICAgICAgICAvLyBVc2Ugc2hhcmVkIGNhbGN1bGF0b3IgZm9yIGV4YWN0IGJ5dGUgcG9zaXRpb25cbiAgICAgICAgICBjb25zdCBleGFjdFNlcXVlbmNlRW5kUG9zID0gY2FsY3VsYXRlU2VxdWVuY2VCeXRlUG9zaXRpb24oXG4gICAgICAgICAgICBldmVudFN0YXJ0UG9zLFxuICAgICAgICAgICAgdGltZSxcbiAgICAgICAgICAgIHByb2Nlc3NlZERhdGEsXG4gICAgICAgICAgICBwcnVuaW5nSW5mby5pbmRleCxcbiAgICAgICAgICAgIHBydW5pbmdJbmZvLnNlcXVlbmNlLmxlbmd0aFxuICAgICAgICAgICk7XG5cbiAgICAgICAgICAvLyBWYWxpZGF0ZSB0aGUgY2FsY3VsYXRpb25cbiAgICAgICAgICBjb25zdCBldmVudEpzb24gPSBgJHtKU09OLnN0cmluZ2lmeShbdGltZSwgJ28nLCBwcm9jZXNzZWREYXRhXSl9XFxuYDtcbiAgICAgICAgICBjb25zdCB0b3RhbEV2ZW50U2l6ZSA9IEJ1ZmZlci5mcm9tKGV2ZW50SnNvbiwgJ3V0ZjgnKS5sZW5ndGg7XG4gICAgICAgICAgY29uc3QgY2FsY3VsYXRlZEV2ZW50RW5kUG9zID0gZXZlbnRTdGFydFBvcyArIHRvdGFsRXZlbnRTaXplO1xuXG4gICAgICAgICAgaWYgKGlzRGVidWdFbmFibGVkKCkpIHtcbiAgICAgICAgICAgIF9sb2dnZXIuZGVidWcoXG4gICAgICAgICAgICAgIGBQcnVuaW5nIHNlcXVlbmNlIGJ5dGUgY2FsY3VsYXRpb246XFxuYCArXG4gICAgICAgICAgICAgICAgYCAgRXZlbnQgc3RhcnQgcG9zaXRpb246ICR7ZXZlbnRTdGFydFBvc31cXG5gICtcbiAgICAgICAgICAgICAgICBgICBFdmVudCB0b3RhbCBzaXplOiAke3RvdGFsRXZlbnRTaXplfSBieXRlc1xcbmAgK1xuICAgICAgICAgICAgICAgIGAgIEV2ZW50IGVuZCBwb3NpdGlvbjogJHtjYWxjdWxhdGVkRXZlbnRFbmRQb3N9XFxuYCArXG4gICAgICAgICAgICAgICAgYCAgRXhhY3Qgc2VxdWVuY2UgcG9zaXRpb246ICR7ZXhhY3RTZXF1ZW5jZUVuZFBvc31cXG5gICtcbiAgICAgICAgICAgICAgICBgICBDdXJyZW50IGZpbGUgcG9zaXRpb246ICR7dGhpcy5ieXRlc1dyaXR0ZW59YFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBTYW5pdHkgY2hlY2s6IHNlcXVlbmNlIHBvc2l0aW9uIHNob3VsZCBiZSB3aXRoaW4gdGhlIGV2ZW50XG4gICAgICAgICAgaWYgKGV4YWN0U2VxdWVuY2VFbmRQb3MgPiBjYWxjdWxhdGVkRXZlbnRFbmRQb3MpIHtcbiAgICAgICAgICAgIF9sb2dnZXIuZXJyb3IoXG4gICAgICAgICAgICAgIGBQcnVuaW5nIHNlcXVlbmNlIHBvc2l0aW9uIGNhbGN1bGF0aW9uIGVycm9yOiBgICtcbiAgICAgICAgICAgICAgICBgc2VxdWVuY2UgcG9zaXRpb24gJHtleGFjdFNlcXVlbmNlRW5kUG9zfSBpcyBiZXlvbmQgZXZlbnQgZW5kICR7Y2FsY3VsYXRlZEV2ZW50RW5kUG9zfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIENhbGwgdGhlIGNhbGxiYWNrIHdpdGggdGhlIGV4YWN0IHBvc2l0aW9uXG4gICAgICAgICAgICB0aGlzLnBydW5pbmdDYWxsYmFjayh7XG4gICAgICAgICAgICAgIHNlcXVlbmNlOiBwcnVuaW5nSW5mby5zZXF1ZW5jZSxcbiAgICAgICAgICAgICAgcG9zaXRpb246IGV4YWN0U2VxdWVuY2VFbmRQb3MsXG4gICAgICAgICAgICAgIHRpbWVzdGFtcDogdGltZSxcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvLyBVc2Ugc2hhcmVkIGxvZ2dpbmcgZnVuY3Rpb25cbiAgICAgICAgICAgIGxvZ1BydW5pbmdEZXRlY3Rpb24ocHJ1bmluZ0luZm8uc2VxdWVuY2UsIGV4YWN0U2VxdWVuY2VFbmRQb3MsICcocmVhbC10aW1lKScpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBTdG9yZSBhbnkgcmVtYWluaW5nIGluY29tcGxldGUgZGF0YSBmb3IgbmV4dCB0aW1lXG4gICAgICB0aGlzLnV0ZjhCdWZmZXIgPSByZW1haW5pbmdCdWZmZXI7XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogV3JpdGUgdGVybWluYWwgaW5wdXQgZGF0YSAodXN1YWxseSBmcm9tIHVzZXIpXG4gICAqL1xuICB3cml0ZUlucHV0KGRhdGE6IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMud3JpdGVRdWV1ZS5lbnF1ZXVlKGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHRpbWUgPSB0aGlzLmdldEVsYXBzZWRUaW1lKCk7XG4gICAgICBjb25zdCBldmVudDogQXNjaWluZW1hRXZlbnQgPSB7XG4gICAgICAgIHRpbWUsXG4gICAgICAgIHR5cGU6ICdpJyxcbiAgICAgICAgZGF0YSxcbiAgICAgIH07XG4gICAgICBhd2FpdCB0aGlzLndyaXRlRXZlbnQoZXZlbnQpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFdyaXRlIHRlcm1pbmFsIHJlc2l6ZSBldmVudFxuICAgKi9cbiAgd3JpdGVSZXNpemUoY29sczogbnVtYmVyLCByb3dzOiBudW1iZXIpOiB2b2lkIHtcbiAgICB0aGlzLndyaXRlUXVldWUuZW5xdWV1ZShhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCB0aW1lID0gdGhpcy5nZXRFbGFwc2VkVGltZSgpO1xuICAgICAgY29uc3QgZXZlbnQ6IEFzY2lpbmVtYUV2ZW50ID0ge1xuICAgICAgICB0aW1lLFxuICAgICAgICB0eXBlOiAncicsXG4gICAgICAgIGRhdGE6IGAke2NvbHN9eCR7cm93c31gLFxuICAgICAgfTtcbiAgICAgIGF3YWl0IHRoaXMud3JpdGVFdmVudChldmVudCk7XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogV3JpdGUgbWFya2VyIGV2ZW50IChmb3IgYm9va21hcmtzL2Fubm90YXRpb25zKVxuICAgKi9cbiAgd3JpdGVNYXJrZXIobWVzc2FnZTogc3RyaW5nKTogdm9pZCB7XG4gICAgdGhpcy53cml0ZVF1ZXVlLmVucXVldWUoYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgdGltZSA9IHRoaXMuZ2V0RWxhcHNlZFRpbWUoKTtcbiAgICAgIGNvbnN0IGV2ZW50OiBBc2NpaW5lbWFFdmVudCA9IHtcbiAgICAgICAgdGltZSxcbiAgICAgICAgdHlwZTogJ20nLFxuICAgICAgICBkYXRhOiBtZXNzYWdlLFxuICAgICAgfTtcbiAgICAgIGF3YWl0IHRoaXMud3JpdGVFdmVudChldmVudCk7XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogV3JpdGUgYSByYXcgSlNPTiBldmVudCAoZm9yIGN1c3RvbSBldmVudHMgbGlrZSBleGl0KVxuICAgKi9cbiAgd3JpdGVSYXdKc29uKGpzb25WYWx1ZTogdW5rbm93bik6IHZvaWQge1xuICAgIHRoaXMud3JpdGVRdWV1ZS5lbnF1ZXVlKGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGpzb25TdHJpbmcgPSBKU09OLnN0cmluZ2lmeShqc29uVmFsdWUpO1xuICAgICAgY29uc3QganNvbkxpbmUgPSBgJHtqc29uU3RyaW5nfVxcbmA7XG4gICAgICBjb25zdCBqc29uQnl0ZXMgPSBCdWZmZXIuZnJvbShqc29uTGluZSwgJ3V0ZjgnKS5sZW5ndGg7XG5cbiAgICAgIC8vIFRyYWNrIHBlbmRpbmcgYnl0ZXMgYmVmb3JlIHdyaXRlXG4gICAgICB0aGlzLnBlbmRpbmdCeXRlcyArPSBqc29uQnl0ZXM7XG5cbiAgICAgIGNvbnN0IGNhbldyaXRlID0gdGhpcy53cml0ZVN0cmVhbS53cml0ZShqc29uTGluZSk7XG4gICAgICBpZiAoIWNhbldyaXRlKSB7XG4gICAgICAgIGF3YWl0IG9uY2UodGhpcy53cml0ZVN0cmVhbSwgJ2RyYWluJyk7XG4gICAgICB9XG5cbiAgICAgIC8vIE1vdmUgYnl0ZXMgZnJvbSBwZW5kaW5nIHRvIHdyaXR0ZW5cbiAgICAgIHRoaXMuYnl0ZXNXcml0dGVuICs9IGpzb25CeXRlcztcbiAgICAgIHRoaXMucGVuZGluZ0J5dGVzIC09IGpzb25CeXRlcztcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBXcml0ZSBhbiBhc2NpaW5lbWEgZXZlbnQgdG8gdGhlIGZpbGVcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgd3JpdGVFdmVudChldmVudDogQXNjaWluZW1hRXZlbnQpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAvLyBBc2NpaW5lbWEgZm9ybWF0OiBbdGltZSwgdHlwZSwgZGF0YV1cbiAgICBjb25zdCBldmVudEFycmF5ID0gW2V2ZW50LnRpbWUsIGV2ZW50LnR5cGUsIGV2ZW50LmRhdGFdO1xuICAgIGNvbnN0IGV2ZW50SnNvbiA9IEpTT04uc3RyaW5naWZ5KGV2ZW50QXJyYXkpO1xuICAgIGNvbnN0IGV2ZW50TGluZSA9IGAke2V2ZW50SnNvbn1cXG5gO1xuICAgIGNvbnN0IGV2ZW50Qnl0ZXMgPSBCdWZmZXIuZnJvbShldmVudExpbmUsICd1dGY4JykubGVuZ3RoO1xuXG4gICAgLy8gTG9nIGRldGFpbGVkIHdyaXRlIGluZm9ybWF0aW9uIGZvciBkZWJ1Z2dpbmdcbiAgICBpZiAoZXZlbnQudHlwZSA9PT0gJ28nICYmIGlzRGVidWdFbmFibGVkKCkpIHtcbiAgICAgIF9sb2dnZXIuZGVidWcoXG4gICAgICAgIGBXcml0aW5nIG91dHB1dCBldmVudDogJHtldmVudEJ5dGVzfSBieXRlcywgYCArXG4gICAgICAgICAgYGRhdGEgbGVuZ3RoOiAke2V2ZW50LmRhdGEubGVuZ3RofSBjaGFycywgYCArXG4gICAgICAgICAgYHBvc2l0aW9uOiAke3RoaXMuYnl0ZXNXcml0dGVuICsgdGhpcy5wZW5kaW5nQnl0ZXN9YFxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBUcmFjayBwZW5kaW5nIGJ5dGVzIGJlZm9yZSB3cml0ZVxuICAgIHRoaXMucGVuZGluZ0J5dGVzICs9IGV2ZW50Qnl0ZXM7XG5cbiAgICAvLyBXcml0ZSBhbmQgaGFuZGxlIGJhY2twcmVzc3VyZVxuICAgIGNvbnN0IGNhbldyaXRlID0gdGhpcy53cml0ZVN0cmVhbS53cml0ZShldmVudExpbmUpO1xuICAgIGlmICghY2FuV3JpdGUpIHtcbiAgICAgIF9sb2dnZXIuZGVidWcoJ1dyaXRlIHN0cmVhbSBiYWNrcHJlc3N1cmUgZGV0ZWN0ZWQsIHdhaXRpbmcgZm9yIGRyYWluJyk7XG4gICAgICBhd2FpdCBvbmNlKHRoaXMud3JpdGVTdHJlYW0sICdkcmFpbicpO1xuICAgIH1cblxuICAgIC8vIE1vdmUgYnl0ZXMgZnJvbSBwZW5kaW5nIHRvIHdyaXR0ZW5cbiAgICB0aGlzLmJ5dGVzV3JpdHRlbiArPSBldmVudEJ5dGVzO1xuICAgIHRoaXMucGVuZGluZ0J5dGVzIC09IGV2ZW50Qnl0ZXM7XG5cbiAgICAvLyBWYWxpZGF0ZSBwb3NpdGlvbiBwZXJpb2RpY2FsbHlcbiAgICBpZiAodGhpcy5ieXRlc1dyaXR0ZW4gLSB0aGlzLmxhc3RWYWxpZGF0ZWRQb3NpdGlvbiA+IDEwMjQgKiAxMDI0KSB7XG4gICAgICAvLyBFdmVyeSAxTUJcbiAgICAgIGF3YWl0IHRoaXMudmFsaWRhdGVGaWxlUG9zaXRpb24oKTtcbiAgICB9XG5cbiAgICAvLyBTeW5jIHRvIGRpc2sgYXN5bmNocm9ub3VzbHlcbiAgICBpZiAodGhpcy5mZCAhPT0gbnVsbCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgZnN5bmModGhpcy5mZCk7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgX2xvZ2dlci5kZWJ1ZyhgZnN5bmMgZmFpbGVkIGZvciAke3RoaXMuZmlsZVBhdGh9OmAsIGVycik7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFByb2Nlc3MgdGVybWluYWwgZGF0YSB3aGlsZSBwcmVzZXJ2aW5nIGVzY2FwZSBzZXF1ZW5jZXMgYW5kIGhhbmRsaW5nIFVURi04XG4gICAqL1xuICBwcml2YXRlIHByb2Nlc3NUZXJtaW5hbERhdGEoYnVmZmVyOiBCdWZmZXIpOiB7IHByb2Nlc3NlZERhdGE6IHN0cmluZzsgcmVtYWluaW5nQnVmZmVyOiBCdWZmZXIgfSB7XG4gICAgbGV0IHJlc3VsdCA9ICcnO1xuICAgIGxldCBwb3MgPSAwO1xuXG4gICAgd2hpbGUgKHBvcyA8IGJ1ZmZlci5sZW5ndGgpIHtcbiAgICAgIC8vIExvb2sgZm9yIGVzY2FwZSBzZXF1ZW5jZXMgc3RhcnRpbmcgd2l0aCBFU0MgKDB4MUIpXG4gICAgICBpZiAoYnVmZmVyW3Bvc10gPT09IDB4MWIpIHtcbiAgICAgICAgLy8gVHJ5IHRvIGZpbmQgY29tcGxldGUgZXNjYXBlIHNlcXVlbmNlXG4gICAgICAgIGNvbnN0IHNlcUVuZCA9IHRoaXMuZmluZEVzY2FwZVNlcXVlbmNlRW5kKGJ1ZmZlci5zdWJhcnJheShwb3MpKTtcbiAgICAgICAgaWYgKHNlcUVuZCAhPT0gbnVsbCkge1xuICAgICAgICAgIGNvbnN0IHNlcUJ5dGVzID0gYnVmZmVyLnN1YmFycmF5KHBvcywgcG9zICsgc2VxRW5kKTtcbiAgICAgICAgICAvLyBQcmVzZXJ2ZSBlc2NhcGUgc2VxdWVuY2UgYXMtaXMgdXNpbmcgdG9TdHJpbmcgdG8gbWFpbnRhaW4gZXhhY3QgYnl0ZXNcbiAgICAgICAgICByZXN1bHQgKz0gc2VxQnl0ZXMudG9TdHJpbmcoJ2xhdGluMScpO1xuICAgICAgICAgIHBvcyArPSBzZXFFbmQ7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gSW5jb21wbGV0ZSBlc2NhcGUgc2VxdWVuY2UgYXQgZW5kIG9mIGJ1ZmZlciAtIHNhdmUgZm9yIGxhdGVyXG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHByb2Nlc3NlZERhdGE6IHJlc3VsdCxcbiAgICAgICAgICAgIHJlbWFpbmluZ0J1ZmZlcjogYnVmZmVyLnN1YmFycmF5KHBvcyksXG4gICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gUmVndWxhciB0ZXh0IC0gZmluZCB0aGUgbmV4dCBlc2NhcGUgc2VxdWVuY2Ugb3IgZW5kIG9mIHZhbGlkIFVURi04XG4gICAgICAgIGNvbnN0IGNodW5rU3RhcnQgPSBwb3M7XG4gICAgICAgIHdoaWxlIChwb3MgPCBidWZmZXIubGVuZ3RoICYmIGJ1ZmZlcltwb3NdICE9PSAweDFiKSB7XG4gICAgICAgICAgcG9zKys7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCB0ZXh0Q2h1bmsgPSBidWZmZXIuc3ViYXJyYXkoY2h1bmtTdGFydCwgcG9zKTtcblxuICAgICAgICAvLyBIYW5kbGUgVVRGLTggdmFsaWRhdGlvbiBmb3IgdGV4dCBjaHVua3NcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCB2YWxpZFRleHQgPSB0ZXh0Q2h1bmsudG9TdHJpbmcoJ3V0ZjgnKTtcbiAgICAgICAgICByZXN1bHQgKz0gdmFsaWRUZXh0O1xuICAgICAgICB9IGNhdGNoIChfZSkge1xuICAgICAgICAgIC8vIFRyeSB0byBmaW5kIGhvdyBtdWNoIGlzIHZhbGlkIFVURi04XG4gICAgICAgICAgY29uc3QgeyB2YWxpZERhdGEsIGludmFsaWRTdGFydCB9ID0gdGhpcy5maW5kVmFsaWRVdGY4KHRleHRDaHVuayk7XG5cbiAgICAgICAgICBpZiAodmFsaWREYXRhLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIHJlc3VsdCArPSB2YWxpZERhdGEudG9TdHJpbmcoJ3V0ZjgnKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBDaGVjayBpZiB3ZSBoYXZlIGluY29tcGxldGUgVVRGLTggYXQgdGhlIGVuZFxuICAgICAgICAgIGlmIChpbnZhbGlkU3RhcnQgPCB0ZXh0Q2h1bmsubGVuZ3RoICYmIHBvcyA+PSBidWZmZXIubGVuZ3RoKSB7XG4gICAgICAgICAgICBjb25zdCByZW1haW5pbmcgPSBidWZmZXIuc3ViYXJyYXkoY2h1bmtTdGFydCArIGludmFsaWRTdGFydCk7XG5cbiAgICAgICAgICAgIC8vIElmIGl0IG1pZ2h0IGJlIGluY29tcGxldGUgVVRGLTggYXQgYnVmZmVyIGVuZCwgc2F2ZSBpdFxuICAgICAgICAgICAgaWYgKHJlbWFpbmluZy5sZW5ndGggPD0gNCAmJiB0aGlzLm1pZ2h0QmVJbmNvbXBsZXRlVXRmOChyZW1haW5pbmcpKSB7XG4gICAgICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICAgICAgcHJvY2Vzc2VkRGF0YTogcmVzdWx0LFxuICAgICAgICAgICAgICAgIHJlbWFpbmluZ0J1ZmZlcjogcmVtYWluaW5nLFxuICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIEludmFsaWQgVVRGLTggaW4gbWlkZGxlIG9yIGNvbXBsZXRlIGludmFsaWQgc2VxdWVuY2VcbiAgICAgICAgICAvLyBVc2UgbG9zc3kgY29udmVyc2lvbiBmb3IgdGhpcyBwYXJ0XG4gICAgICAgICAgY29uc3QgaW52YWxpZFBhcnQgPSB0ZXh0Q2h1bmsuc3ViYXJyYXkoaW52YWxpZFN0YXJ0KTtcbiAgICAgICAgICByZXN1bHQgKz0gaW52YWxpZFBhcnQudG9TdHJpbmcoJ2xhdGluMScpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHsgcHJvY2Vzc2VkRGF0YTogcmVzdWx0LCByZW1haW5pbmdCdWZmZXI6IEJ1ZmZlci5hbGxvYygwKSB9O1xuICB9XG5cbiAgLyoqXG4gICAqIEZpbmQgdGhlIGVuZCBvZiBhbiBBTlNJIGVzY2FwZSBzZXF1ZW5jZVxuICAgKi9cbiAgcHJpdmF0ZSBmaW5kRXNjYXBlU2VxdWVuY2VFbmQoYnVmZmVyOiBCdWZmZXIpOiBudW1iZXIgfCBudWxsIHtcbiAgICBpZiAoYnVmZmVyLmxlbmd0aCA9PT0gMCB8fCBidWZmZXJbMF0gIT09IDB4MWIpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIGlmIChidWZmZXIubGVuZ3RoIDwgMikge1xuICAgICAgcmV0dXJuIG51bGw7IC8vIEluY29tcGxldGUgLSBuZWVkIG1vcmUgZGF0YVxuICAgIH1cblxuICAgIHN3aXRjaCAoYnVmZmVyWzFdKSB7XG4gICAgICAvLyBDU0kgc2VxdWVuY2VzOiBFU0MgWyAuLi4gZmluYWxfY2hhclxuICAgICAgY2FzZSAweDViOiB7XG4gICAgICAgIC8vICdbJ1xuICAgICAgICBsZXQgcG9zID0gMjtcbiAgICAgICAgLy8gU2tpcCBwYXJhbWV0ZXIgYW5kIGludGVybWVkaWF0ZSBjaGFyYWN0ZXJzXG4gICAgICAgIHdoaWxlIChwb3MgPCBidWZmZXIubGVuZ3RoKSB7XG4gICAgICAgICAgY29uc3QgYnl0ZSA9IGJ1ZmZlcltwb3NdO1xuICAgICAgICAgIGlmIChieXRlID49IDB4MjAgJiYgYnl0ZSA8PSAweDNmKSB7XG4gICAgICAgICAgICAvLyBQYXJhbWV0ZXIgY2hhcmFjdGVycyAwLTkgOiA7IDwgPSA+ID8gYW5kIEludGVybWVkaWF0ZSBjaGFyYWN0ZXJzXG4gICAgICAgICAgICBwb3MrKztcbiAgICAgICAgICB9IGVsc2UgaWYgKGJ5dGUgPj0gMHg0MCAmJiBieXRlIDw9IDB4N2UpIHtcbiAgICAgICAgICAgIC8vIEZpbmFsIGNoYXJhY3RlciBAIEEtWiBbIFxcIF0gXiBfIGAgYS16IHsgfCB9IH5cbiAgICAgICAgICAgIHJldHVybiBwb3MgKyAxO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBJbnZhbGlkIHNlcXVlbmNlLCBzdG9wIGhlcmVcbiAgICAgICAgICAgIHJldHVybiBwb3M7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBudWxsOyAvLyBJbmNvbXBsZXRlIHNlcXVlbmNlXG4gICAgICB9XG5cbiAgICAgIC8vIE9TQyBzZXF1ZW5jZXM6IEVTQyBdIC4uLiAoU1Qgb3IgQkVMKVxuICAgICAgY2FzZSAweDVkOiB7XG4gICAgICAgIC8vICddJ1xuICAgICAgICBsZXQgcG9zID0gMjtcbiAgICAgICAgd2hpbGUgKHBvcyA8IGJ1ZmZlci5sZW5ndGgpIHtcbiAgICAgICAgICBjb25zdCBieXRlID0gYnVmZmVyW3Bvc107XG4gICAgICAgICAgaWYgKGJ5dGUgPT09IDB4MDcpIHtcbiAgICAgICAgICAgIC8vIEJFTCB0ZXJtaW5hdG9yXG4gICAgICAgICAgICByZXR1cm4gcG9zICsgMTtcbiAgICAgICAgICB9IGVsc2UgaWYgKGJ5dGUgPT09IDB4MWIgJiYgcG9zICsgMSA8IGJ1ZmZlci5sZW5ndGggJiYgYnVmZmVyW3BvcyArIDFdID09PSAweDVjKSB7XG4gICAgICAgICAgICAvLyBFU0MgXFwgKFNUKSB0ZXJtaW5hdG9yXG4gICAgICAgICAgICByZXR1cm4gcG9zICsgMjtcbiAgICAgICAgICB9XG4gICAgICAgICAgcG9zKys7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIG51bGw7IC8vIEluY29tcGxldGUgc2VxdWVuY2VcbiAgICAgIH1cblxuICAgICAgLy8gU2ltcGxlIHR3by1jaGFyYWN0ZXIgc2VxdWVuY2VzOiBFU0MgbGV0dGVyXG4gICAgICBkZWZhdWx0OlxuICAgICAgICByZXR1cm4gMjtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRmluZCB2YWxpZCBVVEYtOCBwb3J0aW9uIG9mIGEgYnVmZmVyXG4gICAqL1xuICBwcml2YXRlIGZpbmRWYWxpZFV0ZjgoYnVmZmVyOiBCdWZmZXIpOiB7IHZhbGlkRGF0YTogQnVmZmVyOyBpbnZhbGlkU3RhcnQ6IG51bWJlciB9IHtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGJ1ZmZlci5sZW5ndGg7IGkrKykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgdGVzdFNsaWNlID0gYnVmZmVyLnN1YmFycmF5KDAsIGkgKyAxKTtcbiAgICAgICAgdGVzdFNsaWNlLnRvU3RyaW5nKCd1dGY4Jyk7XG4gICAgICB9IGNhdGNoIChfZSkge1xuICAgICAgICAvLyBGb3VuZCBpbnZhbGlkIFVURi04LCByZXR1cm4gdmFsaWQgcG9ydGlvblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHZhbGlkRGF0YTogYnVmZmVyLnN1YmFycmF5KDAsIGkpLFxuICAgICAgICAgIGludmFsaWRTdGFydDogaSxcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBBbGwgdmFsaWRcbiAgICByZXR1cm4ge1xuICAgICAgdmFsaWREYXRhOiBidWZmZXIsXG4gICAgICBpbnZhbGlkU3RhcnQ6IGJ1ZmZlci5sZW5ndGgsXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBhIGJ1ZmZlciBtaWdodCBjb250YWluIGluY29tcGxldGUgVVRGLTggc2VxdWVuY2VcbiAgICovXG4gIHByaXZhdGUgbWlnaHRCZUluY29tcGxldGVVdGY4KGJ1ZmZlcjogQnVmZmVyKTogYm9vbGVhbiB7XG4gICAgaWYgKGJ1ZmZlci5sZW5ndGggPT09IDApIHJldHVybiBmYWxzZTtcblxuICAgIC8vIENoZWNrIGlmIGZpcnN0IGJ5dGUgaW5kaWNhdGVzIG11bHRpLWJ5dGUgVVRGLTggY2hhcmFjdGVyXG4gICAgY29uc3QgZmlyc3RCeXRlID0gYnVmZmVyWzBdO1xuXG4gICAgLy8gU2luZ2xlIGJ5dGUgKEFTQ0lJKSAtIG5vdCBpbmNvbXBsZXRlXG4gICAgaWYgKGZpcnN0Qnl0ZSA8IDB4ODApIHJldHVybiBmYWxzZTtcblxuICAgIC8vIE11bHRpLWJ5dGUgc2VxdWVuY2Ugc3RhcnRlcnNcbiAgICBpZiAoZmlyc3RCeXRlID49IDB4YzApIHtcbiAgICAgIC8vIDItYnl0ZSBzZXF1ZW5jZSBuZWVkcyAyIGJ5dGVzXG4gICAgICBpZiAoZmlyc3RCeXRlIDwgMHhlMCkgcmV0dXJuIGJ1ZmZlci5sZW5ndGggPCAyO1xuICAgICAgLy8gMy1ieXRlIHNlcXVlbmNlIG5lZWRzIDMgYnl0ZXNcbiAgICAgIGlmIChmaXJzdEJ5dGUgPCAweGYwKSByZXR1cm4gYnVmZmVyLmxlbmd0aCA8IDM7XG4gICAgICAvLyA0LWJ5dGUgc2VxdWVuY2UgbmVlZHMgNCBieXRlc1xuICAgICAgaWYgKGZpcnN0Qnl0ZSA8IDB4ZjgpIHJldHVybiBidWZmZXIubGVuZ3RoIDwgNDtcbiAgICB9XG5cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogR2V0IGVsYXBzZWQgdGltZSBzaW5jZSBzdGFydCBpbiBzZWNvbmRzXG4gICAqL1xuICBwcml2YXRlIGdldEVsYXBzZWRUaW1lKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIChEYXRlLm5vdygpIC0gdGhpcy5zdGFydFRpbWUuZ2V0VGltZSgpKSAvIDEwMDA7XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGUgdGhhdCBvdXIgdHJhY2tlZCBwb3NpdGlvbiBtYXRjaGVzIHRoZSBhY3R1YWwgZmlsZSBzaXplXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHZhbGlkYXRlRmlsZVBvc2l0aW9uKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGZzLnByb21pc2VzLnN0YXQodGhpcy5maWxlUGF0aCk7XG4gICAgICBjb25zdCBhY3R1YWxTaXplID0gc3RhdHMuc2l6ZTtcbiAgICAgIGNvbnN0IGV4cGVjdGVkU2l6ZSA9IHRoaXMuYnl0ZXNXcml0dGVuO1xuXG4gICAgICBpZiAoYWN0dWFsU2l6ZSAhPT0gZXhwZWN0ZWRTaXplKSB7XG4gICAgICAgIHRoaXMudmFsaWRhdGlvbkVycm9ycysrO1xuICAgICAgICBfbG9nZ2VyLmVycm9yKFxuICAgICAgICAgIGBBc2NpaW5lbWFXcml0ZXIgcG9zaXRpb24gbWlzbWF0Y2ghIGAgK1xuICAgICAgICAgICAgYEV4cGVjdGVkOiAke2V4cGVjdGVkU2l6ZX0gYnl0ZXMsIEFjdHVhbDogJHthY3R1YWxTaXplfSBieXRlcywgYCArXG4gICAgICAgICAgICBgRGlmZmVyZW5jZTogJHthY3R1YWxTaXplIC0gZXhwZWN0ZWRTaXplfSBieXRlcywgYCArXG4gICAgICAgICAgICBgVmFsaWRhdGlvbiBlcnJvcnM6ICR7dGhpcy52YWxpZGF0aW9uRXJyb3JzfWBcbiAgICAgICAgKTtcblxuICAgICAgICAvLyBJZiB0aGUgZGlmZmVyZW5jZSBpcyBzaWduaWZpY2FudCwgdGhpcyBpcyBhIGNyaXRpY2FsIGVycm9yXG4gICAgICAgIGlmIChNYXRoLmFicyhhY3R1YWxTaXplIC0gZXhwZWN0ZWRTaXplKSA+IDEwMCkge1xuICAgICAgICAgIHRocm93IG5ldyBQdHlFcnJvcihcbiAgICAgICAgICAgIGBDcml0aWNhbCBieXRlIHBvc2l0aW9uIHRyYWNraW5nIGVycm9yOiBleHBlY3RlZCAke2V4cGVjdGVkU2l6ZX0sIGFjdHVhbCAke2FjdHVhbFNpemV9YCxcbiAgICAgICAgICAgICdQT1NJVElPTl9NSVNNQVRDSCdcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBfbG9nZ2VyLmRlYnVnKGBQb3NpdGlvbiB2YWxpZGF0aW9uIHBhc3NlZDogJHthY3R1YWxTaXplfSBieXRlc2ApO1xuICAgICAgfVxuXG4gICAgICB0aGlzLmxhc3RWYWxpZGF0ZWRQb3NpdGlvbiA9IHRoaXMuYnl0ZXNXcml0dGVuO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBQdHlFcnJvcikge1xuICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgIH1cbiAgICAgIF9sb2dnZXIuZXJyb3IoYEZhaWxlZCB0byB2YWxpZGF0ZSBmaWxlIHBvc2l0aW9uOmAsIGVycm9yKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ2xvc2UgdGhlIHdyaXRlciBhbmQgZmluYWxpemUgdGhlIGZpbGVcbiAgICovXG4gIGFzeW5jIGNsb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIC8vIEZsdXNoIGFueSByZW1haW5pbmcgVVRGLTggYnVmZmVyIHRocm91Z2ggdGhlIHF1ZXVlXG4gICAgaWYgKHRoaXMudXRmOEJ1ZmZlci5sZW5ndGggPiAwKSB7XG4gICAgICAvLyBGb3JjZSB3cml0ZSBhbnkgcmVtYWluaW5nIGRhdGEgdXNpbmcgbG9zc3kgY29udmVyc2lvblxuICAgICAgY29uc3QgdGltZSA9IHRoaXMuZ2V0RWxhcHNlZFRpbWUoKTtcbiAgICAgIGNvbnN0IGV2ZW50OiBBc2NpaW5lbWFFdmVudCA9IHtcbiAgICAgICAgdGltZSxcbiAgICAgICAgdHlwZTogJ28nLFxuICAgICAgICBkYXRhOiB0aGlzLnV0ZjhCdWZmZXIudG9TdHJpbmcoJ2xhdGluMScpLFxuICAgICAgfTtcbiAgICAgIC8vIFVzZSB0aGUgcXVldWUgdG8gZW5zdXJlIG9yZGVyaW5nXG4gICAgICB0aGlzLndyaXRlUXVldWUuZW5xdWV1ZShhc3luYyAoKSA9PiB7XG4gICAgICAgIGF3YWl0IHRoaXMud3JpdGVFdmVudChldmVudCk7XG4gICAgICB9KTtcbiAgICAgIHRoaXMudXRmOEJ1ZmZlciA9IEJ1ZmZlci5hbGxvYygwKTtcbiAgICB9XG5cbiAgICAvLyBXYWl0IGZvciBhbGwgcXVldWVkIHdyaXRlcyB0byBjb21wbGV0ZVxuICAgIGF3YWl0IHRoaXMud3JpdGVRdWV1ZS5kcmFpbigpO1xuXG4gICAgLy8gTm93IGl0J3Mgc2FmZSB0byBlbmQgdGhlIHN0cmVhbVxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICB0aGlzLndyaXRlU3RyZWFtLmVuZCgoZXJyb3I/OiBFcnJvcikgPT4ge1xuICAgICAgICBpZiAoZXJyb3IpIHtcbiAgICAgICAgICByZWplY3QobmV3IFB0eUVycm9yKGBGYWlsZWQgdG8gY2xvc2UgYXNjaWluZW1hIHdyaXRlcjogJHtlcnJvci5tZXNzYWdlfWApKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIGlmIHRoZSB3cml0ZXIgaXMgc3RpbGwgb3BlblxuICAgKi9cbiAgaXNPcGVuKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAhdGhpcy53cml0ZVN0cmVhbS5kZXN0cm95ZWQ7XG4gIH1cbn1cbiJdfQ==