@mickdarling/dollhousemcp
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
460 lines • 65.4 kB
JavaScript
/**
* UpdateChecker - Secure GitHub release update checking with comprehensive sanitization
*
* Security measures implemented:
* 1. XSS Protection: DOMPurify with strict no-tags/no-attributes policy
* 2. Command Injection Prevention: Multiple regex patterns for various escape sequences
* 3. URL Validation: Whitelist approach allowing only http/https schemes
* 4. Information Disclosure Prevention: Sanitized logging of sensitive data
* 5. Length Limits: Configurable limits to prevent DoS attacks
* 6. OWASP Patterns: Protection against PHP, ASP, hex, unicode, and octal escapes
*
* Performance optimizations:
* - Cached DOMPurify instance to avoid recreation overhead
* - Single-pass regex processing for injection patterns
* - Exponential backoff for network retries
*/
import { RELEASES_API_URL } from '../config/constants.js';
import { RateLimiterFactory } from './RateLimiter.js';
import { SignatureVerifier } from './SignatureVerifier.js';
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
export class UpdateChecker {
versionManager;
rateLimiter;
signatureVerifier;
// Static cache for DOMPurify to improve performance
// We use 'any' for JSDOM window to avoid complex type conflicts
// but maintain type safety for DOMPurify instance
static purifyWindow = null;
static purify = null;
// Security configuration with sensible defaults
releaseNotesMaxLength;
urlMaxLength;
securityLogger;
requireSignedReleases;
constructor(versionManager, options) {
if (!versionManager) {
throw new Error('VersionManager is required');
}
this.versionManager = versionManager;
// Apply options with defaults and validation
this.releaseNotesMaxLength = options?.releaseNotesMaxLength ?? 5000;
this.urlMaxLength = options?.urlMaxLength ?? 2048;
this.securityLogger = options?.securityLogger;
// Use provided rate limiter or create default
this.rateLimiter = options?.rateLimiter || RateLimiterFactory.createUpdateCheckLimiter();
// Determine if we're in production environment
const isProduction = process.env.NODE_ENV === 'production' ||
process.env.CI === 'true' ||
!process.env.ALLOW_UNSIGNED_RELEASES;
// Use provided signature verifier or create default
this.signatureVerifier = options?.signatureVerifier || new SignatureVerifier({
// In production, we should require signed releases
allowUnsignedInDev: !isProduction
});
// Whether to require signed releases (default: true in production)
this.requireSignedReleases = options?.requireSignedReleases ?? isProduction;
// Validate configuration for security
if (this.releaseNotesMaxLength < 100) {
throw new Error('releaseNotesMaxLength must be at least 100 characters for security');
}
if (this.urlMaxLength < 50) {
throw new Error('urlMaxLength must be at least 50 characters');
}
// Initialize cached DOMPurify instance for performance
// This avoids creating a new JSDOM window for each sanitization
if (!UpdateChecker.purifyWindow) {
const dom = new JSDOM('');
UpdateChecker.purifyWindow = dom.window;
// DOMPurify expects a Window-like object from JSDOM
UpdateChecker.purify = DOMPurify(UpdateChecker.purifyWindow);
}
}
/**
* Execute a network operation with retry logic and exponential backoff
* @param operation - The async operation to execute
* @param maxRetries - Maximum number of retry attempts (default: 3)
* @param baseDelay - Base delay in milliseconds for exponential backoff (default: 1000ms)
* @returns Promise resolving to the operation result
* @throws The last error if all retries fail
*/
async retryNetworkOperation(operation, maxRetries = 3, baseDelay = 1000) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
}
catch (error) {
lastError = error;
// Don't retry on the last attempt
if (attempt === maxRetries) {
break;
}
// Don't retry certain errors (like 404, 401)
if (error instanceof Error && error.message.includes('404')) {
break;
}
// Calculate delay with exponential backoff
const delay = baseDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
/**
* Check for updates from GitHub releases with security and error handling
* @returns UpdateCheckResult if update info is available, null if no releases found
* @throws Error for network or API failures or rate limit exceeded
*/
async checkForUpdates() {
// Check rate limit before making API request
const rateLimitStatus = this.rateLimiter.checkLimit();
if (!rateLimitStatus.allowed) {
const waitTime = Math.ceil(rateLimitStatus.retryAfterMs / 1000);
const waitMinutes = Math.floor(waitTime / 60);
const waitSeconds = waitTime % 60;
const timeStr = waitMinutes > 0
? `${waitMinutes} minute${waitMinutes > 1 ? 's' : ''} ${waitSeconds} second${waitSeconds !== 1 ? 's' : ''}`
: `${waitSeconds} second${waitSeconds !== 1 ? 's' : ''}`;
throw new Error(`Rate limit exceeded. Please wait ${timeStr} before checking for updates again. ` +
`(${rateLimitStatus.remainingTokens} requests remaining, resets at ${rateLimitStatus.resetTime.toLocaleTimeString()})`);
}
// Consume a rate limit token
this.rateLimiter.consumeToken();
const currentVersion = await this.versionManager.getCurrentVersion();
// Check GitHub releases API for latest version with retry logic
const response = await this.retryNetworkOperation(async () => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
try {
const response = await fetch(RELEASES_API_URL, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'DollhouseMCP/1.0'
},
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
}
catch (error) {
clearTimeout(timeoutId);
throw error;
}
});
if (!response.ok) {
if (response.status === 404) {
return null; // No releases found
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const releaseData = await response.json();
const tagName = releaseData.tag_name;
const latestVersion = tagName?.replace(/^v/, '') || releaseData.name;
// Use consistent date formatting method
const publishedAt = releaseData.published_at;
// Compare versions
const isUpdateAvailable = this.versionManager.compareVersions(currentVersion, latestVersion) < 0;
const releaseNotes = releaseData.body || 'See release notes on GitHub';
// Verify release signature if we have a tag
let signatureVerified = false;
let signerInfo;
if (tagName) {
try {
const verificationResult = await this.signatureVerifier.verifyTagSignature(tagName);
signatureVerified = verificationResult.verified;
if (verificationResult.signerEmail) {
signerInfo = verificationResult.signerEmail;
if (verificationResult.signerKey) {
signerInfo += ` (${verificationResult.signerKey})`;
}
}
// Log signature verification
if (this.securityLogger) {
this.securityLogger('signature_verification', {
tagName,
verified: signatureVerified,
signerKey: verificationResult.signerKey,
error: verificationResult.error
});
}
// If signature verification is required and failed, throw error
if (this.requireSignedReleases && !signatureVerified) {
throw new Error(`Release signature verification failed: ${verificationResult.error || 'Unknown error'}. ` +
'Only signed releases are accepted in production mode.');
}
}
catch (error) {
// If we can't verify the signature and it's required, fail
if (this.requireSignedReleases) {
throw error;
}
// Otherwise, log and continue
if (this.securityLogger) {
this.securityLogger('signature_verification_error', {
tagName,
error: error instanceof Error ? error.message : String(error)
});
}
}
}
return {
currentVersion,
latestVersion,
isUpdateAvailable,
releaseDate: publishedAt, // Will be formatted by formatDate() when displayed
releaseNotes,
releaseUrl: releaseData.html_url,
tagName,
signatureVerified,
signerInfo
};
}
/**
* Get current rate limit status
* @returns Current rate limit status including remaining requests and reset time
*/
getRateLimitStatus() {
const status = this.rateLimiter.getStatus();
return {
allowed: status.allowed,
remainingRequests: status.remainingTokens,
resetTime: status.resetTime,
waitTimeSeconds: status.retryAfterMs ? Math.ceil(status.retryAfterMs / 1000) : undefined
};
}
/**
* Format update check results for display with comprehensive sanitization
* @param result - The update check result to format
* @param error - Optional error from update check
* @param personaIndicator - Optional persona indicator prefix
* @returns Formatted string safe for display
*/
formatUpdateCheckResult(result, error, personaIndicator = '') {
if (error) {
const isAbortError = error.name === 'AbortError';
const errorMessage = error.message || String(error);
const isRateLimitError = errorMessage.includes('Rate limit exceeded');
if (isRateLimitError) {
return personaIndicator +
'⏳ **Rate Limit Exceeded**\n\n' +
error.message + '\n\n' +
'**Why this happens:**\n' +
'• Update checks are limited to prevent API abuse\n' +
'• GitHub API has rate limits for all applications\n\n' +
'**What you can do:**\n' +
'• Wait for the specified time before checking again\n' +
'• Use `get_server_status` to see current version without API calls\n' +
'• Visit https://github.com/DollhouseMCP/mcp-server/releases directly';
}
return personaIndicator +
'❌ **Update Check Failed**\n\n' +
'Error: ' + errorMessage + '\n\n' +
(isAbortError
? 'The request timed out. Please check your internet connection and try again.'
: 'Tips:\n' +
'• Check your internet connection\n' +
'• Ensure GitHub.com is accessible\n' +
'• Try running `update_server true` for manual update\n' +
'• Visit https://github.com/DollhouseMCP/mcp-server/releases');
}
if (!result) {
const currentVersion = 'unknown';
return personaIndicator +
'📦 **Update Check Complete**\n\n' +
'🔄 **Current Version:** ' + currentVersion + '\n' +
'📡 **Remote Status:** No releases found on GitHub\n' +
'ℹ️ **Note:** This may be a development version or releases haven\'t been published yet.\n\n' +
'**Manual Update:**\n' +
'Use `update_server true` to pull latest changes from main branch.';
}
const statusParts = [
personaIndicator + '📦 **Update Check Complete**\n\n',
'🔄 **Current Version:** ' + result.currentVersion + '\n',
'📡 **Latest Version:** ' + result.latestVersion + '\n',
'📅 **Released:** ' + this.formatDate(result.releaseDate) + '\n'
];
// Add signature verification status
if (result.signatureVerified !== undefined) {
if (result.signatureVerified) {
statusParts.push('✅ **Signature:** Verified');
if (result.signerInfo) {
statusParts.push(` by ${result.signerInfo}`);
}
statusParts.push('\n');
}
else {
statusParts.push('⚠️ **Signature:** Not verified\n');
}
}
statusParts.push('\n');
if (result.isUpdateAvailable) {
statusParts.push('✨ **Update Available!**\n\n', '**What\'s New:**\n' + this.sanitizeReleaseNotes(result.releaseNotes) + '\n\n', '**To Update:**\n', '• Use: `update_server true`\n', '• Or visit: ' + this.sanitizeUrl(result.releaseUrl) + '\n\n', '⚠️ **Note:** Update will restart the server and reload all personas.');
}
else {
statusParts.push('✅ **You\'re Up to Date!**\n\n', 'Your DollhouseMCP installation is current.\n', 'Check back later for new features and improvements.');
}
return statusParts.join('');
}
/**
* Sanitize URLs to prevent dangerous schemes and information disclosure
*
* Security measures:
* - Length validation to prevent DoS
* - Whitelist approach: only http/https allowed
* - Sanitized logging to prevent sensitive data exposure
*
* @param url - The URL to sanitize
* @returns Empty string if invalid/dangerous, original URL if safe
*/
sanitizeUrl(url) {
if (!url)
return '';
// Check URL length
if (url.length > this.urlMaxLength) {
this.logSecurityEvent('url_too_long', {
length: url.length,
maxLength: this.urlMaxLength,
urlPrefix: url.substring(0, 50) + '...' // Only log first 50 chars
});
return ''; // URL too long
}
// Only allow http and https schemes
const allowedSchemes = ['http:', 'https:'];
try {
const parsed = new URL(url);
if (!allowedSchemes.includes(parsed.protocol)) {
this.logSecurityEvent('dangerous_url_scheme', {
scheme: parsed.protocol,
host: parsed.hostname // Log only hostname, not full URL
});
return ''; // Return empty string for dangerous schemes
}
return url;
}
catch {
this.logSecurityEvent('invalid_url', {
urlLength: url.length // Log length only, not content
});
return ''; // Invalid URL
}
}
/**
* Sanitize release notes to prevent XSS, command injection, and DoS
*
* Security layers:
* 1. Length limiting (configurable, default 5000 chars)
* 2. HTML/JS sanitization via DOMPurify (no tags/attributes allowed)
* 3. Command injection pattern removal (backticks, command substitution)
* 4. OWASP pattern removal (PHP, ASP, hex/unicode/octal escapes)
*
* @param notes - The release notes to sanitize
* @returns Sanitized release notes safe for display
*/
sanitizeReleaseNotes(notes) {
if (!notes)
return 'See release notes on GitHub';
// Apply length limit
let sanitized = notes;
if (sanitized.length > this.releaseNotesMaxLength) {
this.logSecurityEvent('release_notes_truncated', {
originalLength: sanitized.length,
maxLength: this.releaseNotesMaxLength
});
sanitized = sanitized.substring(0, this.releaseNotesMaxLength) + '...';
}
// Use cached DOMPurify instance with automatic recovery
if (!UpdateChecker.purify || !UpdateChecker.purifyWindow) {
// Reinitialize if somehow corrupted - provides resilience
const dom = new JSDOM('');
UpdateChecker.purifyWindow = dom.window;
UpdateChecker.purify = DOMPurify(UpdateChecker.purifyWindow);
}
const beforeSanitize = sanitized;
// DOMPurify configuration for maximum security
// ALLOWED_TAGS: [] strips all HTML tags
// ALLOWED_ATTR: [] strips all attributes
// Additional options for extra security
sanitized = UpdateChecker.purify.sanitize(sanitized, {
ALLOWED_TAGS: [], // Strip all HTML tags
ALLOWED_ATTR: [], // Strip all attributes
FORBID_TAGS: ['style', 'script', 'iframe', 'object', 'embed', 'link'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover']
});
if (beforeSanitize !== sanitized) {
this.logSecurityEvent('html_content_removed', {
removedLength: beforeSanitize.length - sanitized.length
});
}
// Additional sanitization for command injection patterns
// Single-pass processing for performance while maintaining security
// These patterns cover various injection vectors beyond HTML/JS
// Length limits added to prevent ReDoS attacks
const patterns = [
/`[^`]{0,1000}`/g, // Backtick expressions (limited to 1000 chars)
/\$\([^)]{0,1000}\)/g, // Command substitution (limited to 1000 chars)
/\$\{[^}]{0,1000}\}/g, // Variable expansion (limited to 1000 chars)
/<\?[^>]{0,1000}\?>/g, // PHP tags (OWASP) (limited to 1000 chars)
/<%[^>]{0,1000}%>/g, // ASP tags (HTML-encoded by DOMPurify) (limited to 1000 chars)
/<%[^>]{0,1000}%>/g, // ASP tags (raw) (limited to 1000 chars)
/\\x[0-9a-fA-F]{2}/g, // Hex escapes (OWASP) - already limited by {2}
/\\u[0-9a-fA-F]{4}/g, // Unicode escapes - already limited by {4}
/\\[0-7]{1,3}/g // Octal escapes - already limited by {1,3}
];
const beforePatterns = sanitized;
for (const pattern of patterns) {
sanitized = sanitized.replace(pattern, '');
}
if (beforePatterns !== sanitized) {
this.logSecurityEvent('injection_patterns_removed', {
removedLength: beforePatterns.length - sanitized.length
});
}
return sanitized;
}
/**
* Format date to human-readable format with consistent timezone handling
* @param dateStr - ISO date string to format
* @returns Human-readable date string (e.g., "January 5, 2025")
*/
formatDate(dateStr) {
try {
const date = new Date(dateStr);
if (isNaN(date.getTime())) {
return dateStr; // Return original if invalid
}
// Use UTC methods to ensure consistent timezone handling
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
timeZone: 'UTC' // Ensure consistent timezone
});
}
catch {
return dateStr; // Return original on error
}
}
/**
* Log security events for monitoring and alerting
* Only logs if securityLogger callback was provided in constructor
* @param event - The security event type
* @param details - Event details (sanitized to prevent info disclosure)
*/
logSecurityEvent(event, details) {
if (this.securityLogger) {
this.securityLogger(event, details);
}
}
/**
* Reset static DOMPurify cache (useful for long-running processes)
* This prevents memory accumulation in services that run for extended periods
* @static
*/
static resetCache() {
UpdateChecker.purifyWindow = null;
UpdateChecker.purify = null;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXBkYXRlQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy91cGRhdGUvVXBkYXRlQ2hlY2tlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUUxRCxPQUFPLEVBQWUsa0JBQWtCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNuRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUMzRCxPQUFPLFNBQVMsTUFBTSxXQUFXLENBQUM7QUFDbEMsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLE9BQU8sQ0FBQztBQWlCOUIsTUFBTSxPQUFPLGFBQWE7SUFDaEIsY0FBYyxDQUFpQjtJQUMvQixXQUFXLENBQWM7SUFDekIsaUJBQWlCLENBQW9CO0lBRTdDLG9EQUFvRDtJQUNwRCxnRUFBZ0U7SUFDaEUsa0RBQWtEO0lBQzFDLE1BQU0sQ0FBQyxZQUFZLEdBQVEsSUFBSSxDQUFDO0lBQ2hDLE1BQU0sQ0FBQyxNQUFNLEdBQTZCLElBQUksQ0FBQztJQUV2RCxnREFBZ0Q7SUFDL0IscUJBQXFCLENBQVM7SUFDOUIsWUFBWSxDQUFTO0lBQ3JCLGNBQWMsQ0FBeUM7SUFDdkQscUJBQXFCLENBQVU7SUFFaEQsWUFDRSxjQUE4QixFQUM5QixPQU9DO1FBRUQsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQztRQUNoRCxDQUFDO1FBQ0QsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7UUFFckMsNkNBQTZDO1FBQzdDLElBQUksQ0FBQyxxQkFBcUIsR0FBRyxPQUFPLEVBQUUscUJBQXFCLElBQUksSUFBSSxDQUFDO1FBQ3BFLElBQUksQ0FBQyxZQUFZLEdBQUcsT0FBTyxFQUFFLFlBQVksSUFBSSxJQUFJLENBQUM7UUFDbEQsSUFBSSxDQUFDLGNBQWMsR0FBRyxPQUFPLEVBQUUsY0FBYyxDQUFDO1FBRTlDLDhDQUE4QztRQUM5QyxJQUFJLENBQUMsV0FBVyxHQUFHLE9BQU8sRUFBRSxXQUFXLElBQUksa0JBQWtCLENBQUMsd0JBQXdCLEVBQUUsQ0FBQztRQUV6RiwrQ0FBK0M7UUFDL0MsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLEtBQUssWUFBWTtZQUN0QyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsS0FBSyxNQUFNO1lBQ3pCLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1QkFBdUIsQ0FBQztRQUV6RCxvREFBb0Q7UUFDcEQsSUFBSSxDQUFDLGlCQUFpQixHQUFHLE9BQU8sRUFBRSxpQkFBaUIsSUFBSSxJQUFJLGlCQUFpQixDQUFDO1lBQzNFLG1EQUFtRDtZQUNuRCxrQkFBa0IsRUFBRSxDQUFDLFlBQVk7U0FDbEMsQ0FBQyxDQUFDO1FBRUgsbUVBQW1FO1FBQ25FLElBQUksQ0FBQyxxQkFBcUIsR0FBRyxPQUFPLEVBQUUscUJBQXFCLElBQUksWUFBWSxDQUFDO1FBRTVFLHNDQUFzQztRQUN0QyxJQUFJLElBQUksQ0FBQyxxQkFBcUIsR0FBRyxHQUFHLEVBQUUsQ0FBQztZQUNyQyxNQUFNLElBQUksS0FBSyxDQUFDLG9FQUFvRSxDQUFDLENBQUM7UUFDeEYsQ0FBQztRQUNELElBQUksSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUVELHVEQUF1RDtRQUN2RCxnRUFBZ0U7UUFDaEUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxZQUFZLEVBQUUsQ0FBQztZQUNoQyxNQUFNLEdBQUcsR0FBRyxJQUFJLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUMxQixhQUFhLENBQUMsWUFBWSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7WUFDeEMsb0RBQW9EO1lBQ3BELGFBQWEsQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDLGFBQWEsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUMvRCxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSyxLQUFLLENBQUMscUJBQXFCLENBQ2pDLFNBQTJCLEVBQzNCLGFBQXFCLENBQUMsRUFDdEIsWUFBb0IsSUFBSTtRQUV4QixJQUFJLFNBQWdCLENBQUM7UUFFckIsS0FBSyxJQUFJLE9BQU8sR0FBRyxDQUFDLEVBQUUsT0FBTyxJQUFJLFVBQVUsRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUFDO1lBQ3ZELElBQUksQ0FBQztnQkFDSCxPQUFPLE1BQU0sU0FBUyxFQUFFLENBQUM7WUFDM0IsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsU0FBUyxHQUFHLEtBQWMsQ0FBQztnQkFFM0Isa0NBQWtDO2dCQUNsQyxJQUFJLE9BQU8sS0FBSyxVQUFVLEVBQUUsQ0FBQztvQkFDM0IsTUFBTTtnQkFDUixDQUFDO2dCQUVELDZDQUE2QztnQkFDN0MsSUFBSSxLQUFLLFlBQVksS0FBSyxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7b0JBQzVELE1BQU07Z0JBQ1IsQ0FBQztnQkFFRCwyQ0FBMkM7Z0JBQzNDLE1BQU0sS0FBSyxHQUFHLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsQ0FBQztnQkFDL0MsTUFBTSxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztZQUMzRCxDQUFDO1FBQ0gsQ0FBQztRQUVELE1BQU0sU0FBVSxDQUFDO0lBQ25CLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLGVBQWU7UUFDbkIsNkNBQTZDO1FBQzdDLE1BQU0sZUFBZSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDdEQsSUFBSSxDQUFDLGVBQWUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUM3QixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxZQUFhLEdBQUcsSUFBSSxDQUFDLENBQUM7WUFDakUsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDLENBQUM7WUFDOUMsTUFBTSxXQUFXLEdBQUcsUUFBUSxHQUFHLEVBQUUsQ0FBQztZQUVsQyxNQUFNLE9BQU8sR0FBRyxXQUFXLEdBQUcsQ0FBQztnQkFDN0IsQ0FBQyxDQUFDLEdBQUcsV0FBVyxVQUFVLFdBQVcsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxJQUFJLFdBQVcsVUFBVSxXQUFXLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtnQkFDM0csQ0FBQyxDQUFDLEdBQUcsV0FBVyxVQUFVLFdBQVcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUM7WUFFM0QsTUFBTSxJQUFJLEtBQUssQ0FDYixvQ0FBb0MsT0FBTyxzQ0FBc0M7Z0JBQ2pGLElBQUksZUFBZSxDQUFDLGVBQWUsa0NBQWtDLGVBQWUsQ0FBQyxTQUFTLENBQUMsa0JBQWtCLEVBQUUsR0FBRyxDQUN2SCxDQUFDO1FBQ0osQ0FBQztRQUVELDZCQUE2QjtRQUM3QixJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksRUFBRSxDQUFDO1FBRWhDLE1BQU0sY0FBYyxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBRXJFLGdFQUFnRTtRQUNoRSxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxLQUFLLElBQUksRUFBRTtZQUMzRCxNQUFNLFVBQVUsR0FBRyxJQUFJLGVBQWUsRUFBRSxDQUFDO1lBQ3pDLE1BQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxVQUFVLENBQUMsS0FBSyxFQUFFLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxvQkFBb0I7WUFFbkYsSUFBSSxDQUFDO2dCQUNILE1BQU0sUUFBUSxHQUFHLE1BQU0sS0FBSyxDQUFDLGdCQUFnQixFQUFFO29CQUM3QyxPQUFPLEVBQUU7d0JBQ1AsUUFBUSxFQUFFLGdDQUFnQzt3QkFDMUMsWUFBWSxFQUFFLGtCQUFrQjtxQkFDakM7b0JBQ0QsTUFBTSxFQUFFLFVBQVUsQ0FBQyxNQUFNO2lCQUMxQixDQUFDLENBQUM7Z0JBRUgsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUN4QixPQUFPLFFBQVEsQ0FBQztZQUNsQixDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixZQUFZLENBQUMsU0FBUyxDQUFDLENBQUM7Z0JBQ3hCLE1BQU0sS0FBSyxDQUFDO1lBQ2QsQ0FBQztRQUNILENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUNqQixJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUM7Z0JBQzVCLE9BQU8sSUFBSSxDQUFDLENBQUMsb0JBQW9CO1lBQ25DLENBQUM7WUFDRCxNQUFNLElBQUksS0FBSyxDQUFDLHFCQUFxQixRQUFRLENBQUMsTUFBTSxJQUFJLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO1FBQ2pGLENBQUM7UUFFRCxNQUFNLFdBQVcsR0FBRyxNQUFNLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUMxQyxNQUFNLE9BQU8sR0FBRyxXQUFXLENBQUMsUUFBUSxDQUFDO1FBQ3JDLE1BQU0sYUFBYSxHQUFHLE9BQU8sRUFBRSxPQUFPLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLFdBQVcsQ0FBQyxJQUFJLENBQUM7UUFDckUsd0NBQXdDO1FBQ3hDLE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxZQUFZLENBQUM7UUFFN0MsbUJBQW1CO1FBQ25CLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxlQUFlLENBQUMsY0FBYyxFQUFFLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUVqRyxNQUFNLFlBQVksR0FBRyxXQUFXLENBQUMsSUFBSSxJQUFJLDZCQUE2QixDQUFDO1FBRXZFLDRDQUE0QztRQUM1QyxJQUFJLGlCQUFpQixHQUFHLEtBQUssQ0FBQztRQUM5QixJQUFJLFVBQThCLENBQUM7UUFFbkMsSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUNaLElBQUksQ0FBQztnQkFDSCxNQUFNLGtCQUFrQixHQUFHLE1BQU0sSUFBSSxDQUFDLGlCQUFpQixDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDO2dCQUNwRixpQkFBaUIsR0FBRyxrQkFBa0IsQ0FBQyxRQUFRLENBQUM7Z0JBRWhELElBQUksa0JBQWtCLENBQUMsV0FBVyxFQUFFLENBQUM7b0JBQ25DLFVBQVUsR0FBRyxrQkFBa0IsQ0FBQyxXQUFXLENBQUM7b0JBQzVDLElBQUksa0JBQWtCLENBQUMsU0FBUyxFQUFFLENBQUM7d0JBQ2pDLFVBQVUsSUFBSSxLQUFLLGtCQUFrQixDQUFDLFNBQVMsR0FBRyxDQUFDO29CQUNyRCxDQUFDO2dCQUNILENBQUM7Z0JBRUQsNkJBQTZCO2dCQUM3QixJQUFJLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztvQkFDeEIsSUFBSSxDQUFDLGNBQWMsQ0FBQyx3QkFBd0IsRUFBRTt3QkFDNUMsT0FBTzt3QkFDUCxRQUFRLEVBQUUsaUJBQWlCO3dCQUMzQixTQUFTLEVBQUUsa0JBQWtCLENBQUMsU0FBUzt3QkFDdkMsS0FBSyxFQUFFLGtCQUFrQixDQUFDLEtBQUs7cUJBQ2hDLENBQUMsQ0FBQztnQkFDTCxDQUFDO2dCQUVELGdFQUFnRTtnQkFDaEUsSUFBSSxJQUFJLENBQUMscUJBQXFCLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO29CQUNyRCxNQUFNLElBQUksS0FBSyxDQUNiLDBDQUEwQyxrQkFBa0IsQ0FBQyxLQUFLLElBQUksZUFBZSxJQUFJO3dCQUN6Rix1REFBdUQsQ0FDeEQsQ0FBQztnQkFDSixDQUFDO1lBQ0gsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsMkRBQTJEO2dCQUMzRCxJQUFJLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO29CQUMvQixNQUFNLEtBQUssQ0FBQztnQkFDZCxDQUFDO2dCQUNELDhCQUE4QjtnQkFDOUIsSUFBSSxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7b0JBQ3hCLElBQUksQ0FBQyxjQUFjLENBQUMsOEJBQThCLEVBQUU7d0JBQ2xELE9BQU87d0JBQ1AsS0FBSyxFQUFFLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7cUJBQzlELENBQUMsQ0FBQztnQkFDTCxDQUFDO1lBQ0gsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPO1lBQ0wsY0FBYztZQUNkLGFBQWE7WUFDYixpQkFBaUI7WUFDakIsV0FBVyxFQUFFLFdBQVcsRUFBRyxtREFBbUQ7WUFDOUUsWUFBWTtZQUNaLFVBQVUsRUFBRSxXQUFXLENBQUMsUUFBUTtZQUNoQyxPQUFPO1lBQ1AsaUJBQWlCO1lBQ2pCLFVBQVU7U0FDWCxDQUFDO0lBQ0osQ0FBQztJQUVEOzs7T0FHRztJQUNILGtCQUFrQjtRQU1oQixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQzVDLE9BQU87WUFDTCxPQUFPLEVBQUUsTUFBTSxDQUFDLE9BQU87WUFDdkIsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLGVBQWU7WUFDekMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxTQUFTO1lBQzNCLGVBQWUsRUFBRSxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDekYsQ0FBQztJQUNKLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCx1QkFBdUIsQ0FBQyxNQUFnQyxFQUFFLEtBQWEsRUFBRSxtQkFBMkIsRUFBRTtRQUNwRyxJQUFJLEtBQUssRUFBRSxDQUFDO1lBQ1YsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLElBQUksS0FBSyxZQUFZLENBQUM7WUFDakQsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLE9BQU8sSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDcEQsTUFBTSxnQkFBZ0IsR0FBRyxZQUFZLENBQUMsUUFBUSxDQUFDLHFCQUFxQixDQUFDLENBQUM7WUFFdEUsSUFBSSxnQkFBZ0IsRUFBRSxDQUFDO2dCQUNyQixPQUFPLGdCQUFnQjtvQkFDckIsK0JBQStCO29CQUMvQixLQUFLLENBQUMsT0FBTyxHQUFHLE1BQU07b0JBQ3RCLHlCQUF5QjtvQkFDekIsb0RBQW9EO29CQUNwRCx1REFBdUQ7b0JBQ3ZELHdCQUF3QjtvQkFDeEIsdURBQXVEO29CQUN2RCxzRUFBc0U7b0JBQ3RFLHNFQUFzRSxDQUFDO1lBQzNFLENBQUM7WUFFRCxPQUFPLGdCQUFnQjtnQkFDckIsK0JBQStCO2dCQUMvQixTQUFTLEdBQUcsWUFBWSxHQUFHLE1BQU07Z0JBQ2pDLENBQUMsWUFBWTtvQkFDWCxDQUFDLENBQUMsNkVBQTZFO29CQUMvRSxDQUFDLENBQUMsU0FBUzt3QkFDVCxvQ0FBb0M7d0JBQ3BDLHFDQUFxQzt3QkFDckMsd0RBQXdEO3dCQUN4RCw2REFBNkQsQ0FBQyxDQUFDO1FBQ3ZFLENBQUM7UUFFRCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLGNBQWMsR0FBRyxTQUFTLENBQUM7WUFDakMsT0FBTyxnQkFBZ0I7Z0JBQ3JCLGtDQUFrQztnQkFDbEMsMEJBQTBCLEdBQUcsY0FBYyxHQUFHLElBQUk7Z0JBQ2xELHFEQUFxRDtnQkFDckQsNkZBQTZGO2dCQUM3RixzQkFBc0I7Z0JBQ3RCLG1FQUFtRSxDQUFDO1FBQ3hFLENBQUM7UUFFRCxNQUFNLFdBQVcsR0FBRztZQUNsQixnQkFBZ0IsR0FBRyxrQ0FBa0M7WUFDckQsMEJBQTBCLEdBQUcsTUFBTSxDQUFDLGNBQWMsR0FBRyxJQUFJO1lBQ3pELHlCQUF5QixHQUFHLE1BQU0sQ0FBQyxhQUFhLEdBQUcsSUFBSTtZQUN2RCxtQkFBbUIsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsR0FBRyxJQUFJO1NBQ2pFLENBQUM7UUFFRixvQ0FBb0M7UUFDcEMsSUFBSSxNQUFNLENBQUMsaUJBQWlCLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDM0MsSUFBSSxNQUFNLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztnQkFDN0IsV0FBVyxDQUFDLElBQUksQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO2dCQUM5QyxJQUFJLE1BQU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQztvQkFDdEIsV0FBVyxDQUFDLElBQUksQ0FBQyxPQUFPLE1BQU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO2dCQUMvQyxDQUFDO2dCQUNELFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDekIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLFdBQVcsQ0FBQyxJQUFJLENBQUMsa0NBQWtDLENBQUMsQ0FBQztZQUN2RCxDQUFDO1FBQ0gsQ0FBQztRQUVELFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFdkIsSUFBSSxNQUFNLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztZQUM3QixXQUFXLENBQUMsSUFBSSxDQUNkLDZCQUE2QixFQUM3QixvQkFBb0IsR0FBRyxJQUFJLENBQUMsb0JBQW9CLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxHQUFHLE1BQU0sRUFDOUUsa0JBQWtCLEVBQ2xCLCtCQUErQixFQUMvQixjQUFjLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLEdBQUcsTUFBTSxFQUM3RCxzRUFBc0UsQ0FDdkUsQ0FBQztRQUNKLENBQUM7YUFBTSxDQUFDO1lBQ04sV0FBVyxDQUFDLElBQUksQ0FDZCwrQkFBK0IsRUFDL0IsOENBQThDLEVBQzlDLHFEQUFxRCxDQUN0RCxDQUFDO1FBQ0osQ0FBQztRQUVELE9BQU8sV0FBVyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNLLFdBQVcsQ0FBQyxHQUFXO1FBQzdCLElBQUksQ0FBQyxHQUFHO1lBQUUsT0FBTyxFQUFFLENBQUM7UUFFcEIsbUJBQW1CO1FBQ25CLElBQUksR0FBRyxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDbkMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLGNBQWMsRUFBRTtnQkFDcEMsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNO2dCQUNsQixTQUFTLEVBQUUsSUFBSSxDQUFDLFlBQVk7Z0JBQzVCLFNBQVMsRUFBRSxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUUsMEJBQTBCO2FBQ3BFLENBQUMsQ0FBQztZQUNILE9BQU8sRUFBRSxDQUFDLENBQUUsZUFBZTtRQUM3QixDQUFDO1FBRUQsb0NBQW9DO1FBQ3BDLE1BQU0sY0FBYyxHQUFHLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzNDLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFHLElBQUksR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzVCLElBQUksQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO2dCQUM5QyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsc0JBQXNCLEVBQUU7b0JBQzVDLE1BQU0sRUFBRSxNQUFNLENBQUMsUUFBUTtvQkFDdkIsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUUsa0NBQWtDO2lCQUMxRCxDQUFDLENBQUM7Z0JBQ0gsT0FBTyxFQUFFLENBQUMsQ0FBRSw0Q0FBNEM7WUFDMUQsQ0FBQztZQUNELE9BQU8sR0FBRyxDQUFDO1FBQ2IsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxhQUFhLEVBQUU7Z0JBQ25DLFNBQVMsRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFFLCtCQUErQjthQUN2RCxDQUFDLENBQUM7WUFDSCxPQUFPLEVBQUUsQ0FBQyxDQUFFLGNBQWM7UUFDNUIsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7Ozs7Ozs7T0FXRztJQUNLLG9CQUFvQixDQUFDLEtBQWE7UUFDeEMsSUFBSSxDQUFDLEtBQUs7WUFBRSxPQUFPLDZCQUE2QixDQUFDO1FBRWpELHFCQUFxQjtRQUNyQixJQUFJLFNBQVMsR0FBRyxLQUFLLENBQUM7UUFDdEIsSUFBSSxTQUFTLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1lBQ2xELElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyx5QkFBeUIsRUFBRTtnQkFDL0MsY0FBYyxFQUFFLFNBQVMsQ0FBQyxNQUFNO2dCQUNoQyxTQUFTLEVBQUUsSUFBSSxDQUFDLHFCQUFxQjthQUN0QyxDQUFDLENBQUM7WUFDSCxTQUFTLEdBQUcsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLHFCQUFxQixDQUFDLEdBQUcsS0FBSyxDQUFDO1FBQ3pFLENBQUM7UUFFRCx3REFBd0Q7UUFDeEQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDekQsMERBQTBEO1lBQzFELE1BQU0sR0FBRyxHQUFHLElBQUksS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQzFCLGFBQWEsQ0FBQyxZQUFZLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQztZQUN4QyxhQUFhLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDL0QsQ0FBQztRQUVELE1BQU0sY0FBYyxHQUFHLFNBQVMsQ0FBQztRQUNqQywrQ0FBK0M7UUFDL0Msd0NBQXdDO1FBQ3hDLHlDQUF5QztRQUN6Qyx3Q0FBd0M7UUFDeEMsU0FBUyxHQUFHLGFBQWEsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLFNBQVMsRUFBRTtZQUNuRCxZQUFZLEVBQUUsRUFBRSxFQUFPLHNCQUFzQjtZQUM3QyxZQUFZLEVBQUUsRUFBRSxFQUFPLHVCQUF1QjtZQUM5QyxXQUFXLEVBQUUsQ0FBQyxPQUFPLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLE1BQU0sQ0FBQztZQUNyRSxXQUFXLEVBQUUsQ0FBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxhQUFhLENBQUM7U0FDN0QsQ0FBQyxDQUFDO1FBRUgsSUFBSSxjQUFjLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDakMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLHNCQUFzQixFQUFFO2dCQUM1QyxhQUFhLEVBQUUsY0FBYyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsTUFBTTthQUN4RCxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQseURBQXlEO1FBQ3pELG9FQUFvRTtRQUNwRSxnRUFBZ0U7UUFDaEUsK0NBQStDO1FBQy9DLE1BQU0sUUFBUSxHQUFHO1lBQ2YsaUJBQWlCLEVBQVksK0NBQStDO1lBQzVFLHFCQUFxQixFQUFRLCtDQUErQztZQUM1RSxxQkFBcUIsRUFBUSw2Q0FBNkM7WUFDMUUscUJBQXFCLEVBQVEsMkNBQTJDO1lBQ3hFLHlCQUF5QixFQUFJLCtEQUErRDtZQUM1RixtQkFBbUIsRUFBVSx5Q0FBeUM7WUFDdEUsb0JBQW9CLEVBQVMsK0NBQStDO1lBQzVFLG9CQUFvQixFQUFTLDJDQUEyQztZQUN4RSxlQUFlLENBQWMsMkNBQTJDO1NBQ3pFLENBQUM7UUFFRixNQUFNLGNBQWMsR0FBRyxTQUFTLENBQUM7UUFDakMsS0FBSyxNQUFNLE9BQU8sSUFBSSxRQUFRLEVBQUUsQ0FBQztZQUMvQixTQUFTLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDN0MsQ0FBQztRQUVELElBQUksY0FBYyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ2pDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyw0QkFBNEIsRUFBRTtnQkFDbEQsYUFBYSxFQUFFLGNBQWMsQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDLE1BQU07YUFDeEQsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ssVUFBVSxDQUFDLE9BQWU7UUFDaEMsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLEdBQUcsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDL0IsSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQztnQkFDMUIsT0FBTyxPQUFPLENBQUMsQ0FBRSw2QkFBNkI7WUFDaEQsQ0FBQztZQUVELHlEQUF5RDtZQUN6RCxPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQUU7Z0JBQ3RDLElBQUksRUFBRSxTQUFTO2dCQUNmLEtBQUssRUFBRSxNQUFNO2dCQUNiLEdBQUcsRUFBRSxTQUFTO2dCQUNkLFFBQVEsRUFBRSxLQUFLLENBQUUsNkJBQTZCO2FBQy9DLENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxPQUFPLE9BQU8sQ0FBQyxDQUFFLDJCQUEyQjtRQUM5QyxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ssZ0JBQWdCLENBQUMsS0FBYSxFQUFFLE9BQVk7UUFDbEQsSUFBSSxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDeEIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEMsQ0FBQztJQUNILENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksTUFBTSxDQUFDLFVBQVU7UUFDdEIsYUFBYSxDQUFDLFlBQVksR0FBRyxJQUFJLENBQUM7UUFDbEMsYUFBYSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7SUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVXBkYXRlQ2hlY2tlciAtIFNlY3VyZSBHaXRIdWIgcmVsZWFzZSB1cGRhdGUgY2hlY2tpbmcgd2l0aCBjb21wcmVoZW5zaXZlIHNhbml0aXphdGlvblxuICogXG4gKiBTZWN1cml0eSBtZWFzdXJlcyBpbXBsZW1lbnRlZDpcbiAqIDEuIFhTUyBQcm90ZWN0aW9uOiBET01QdXJpZnkgd2l0aCBzdHJpY3Qgbm8tdGFncy9uby1hdHRyaWJ1dGVzIHBvbGljeVxuICogMi4gQ29tbWFuZCBJbmplY3Rpb24gUHJldmVudGlvbjogTXVsdGlwbGUgcmVnZXggcGF0dGVybnMgZm9yIHZhcmlvdXMgZXNjYXBlIHNlcXVlbmNlc1xuICogMy4gVVJMIFZhbGlkYXRpb246IFdoaXRlbGlzdCBhcHByb2FjaCBhbGxvd2luZyBvbmx5IGh0dHAvaHR0cHMgc2NoZW1lc1xuICogNC4gSW5mb3JtYXRpb24gRGlzY2xvc3VyZSBQcmV2ZW50aW9uOiBTYW5pdGl6ZWQgbG9nZ2luZyBvZiBzZW5zaXRpdmUgZGF0YVxuICogNS4gTGVuZ3RoIExpbWl0czogQ29uZmlndXJhYmxlIGxpbWl0cyB0byBwcmV2ZW50IERvUyBhdHRhY2tzXG4gKiA2LiBPV0FTUCBQYXR0ZXJuczogUHJvdGVjdGlvbiBhZ2FpbnN0IFBIUCwgQVNQLCBoZXgsIHVuaWNvZGUsIGFuZCBvY3RhbCBlc2NhcGVzXG4gKiBcbiAqIFBlcmZvcm1hbmNlIG9wdGltaXphdGlvbnM6XG4gKiAtIENhY2hlZCBET01QdXJpZnkgaW5zdGFuY2UgdG8gYXZvaWQgcmVjcmVhdGlvbiBvdmVyaGVhZFxuICogLSBTaW5nbGUtcGFzcyByZWdleCBwcm9jZXNzaW5nIGZvciBpbmplY3Rpb24gcGF0dGVybnNcbiAqIC0gRXhwb25lbnRpYWwgYmFja29mZiBmb3IgbmV0d29yayByZXRyaWVzXG4gKi9cblxuaW1wb3J0IHsgUkVMRUFTRVNfQVBJX1VSTCB9IGZyb20gJy4uL2NvbmZpZy9jb25zdGFudHMuanMnO1xuaW1wb3J0IHsgVmVyc2lvbk1hbmFnZXIgfSBmcm9tICcuL1ZlcnNpb25NYW5hZ2VyLmpzJztcbmltcG9ydCB7IFJhdGVMaW1pdGVyLCBSYXRlTGltaXRlckZhY3RvcnkgfSBmcm9tICcuL1JhdGVMaW1pdGVyLmpzJztcbmltcG9ydCB7IFNpZ25hdHVyZVZlcmlmaWVyIH0gZnJvbSAnLi9TaWduYXR1cmVWZXJpZmllci5qcyc7XG5pbXBvcnQgRE9NUHVyaWZ5IGZyb20gJ2RvbXB1cmlmeSc7XG5pbXBvcnQgeyBKU0RPTSB9IGZyb20gJ2pzZG9tJztcblxuZXhwb3J0IGludGVyZmFjZSBVcGRhdGVDaGVja1Jlc3VsdCB7XG4gIGN1cnJlbnRWZXJzaW9uOiBzdHJpbmc7XG4gIGxhdGVzdFZlcnNpb246IHN0cmluZztcbiAgaXNVcGRhdGVBdmFpbGFibGU6IGJvb2xlYW47XG4gIHJlbGVhc2VEYXRlOiBzdHJpbmc7XG4gIHJlbGVhc2VOb3Rlczogc3RyaW5nO1xuICByZWxlYXNlVXJsOiBzdHJpbmc7XG4gIHRhZ05hbWU/OiBzdHJpbmc7XG4gIHNpZ25hdHVyZVZlcmlmaWVkPzogYm9vbGVhbjtcbiAgc2lnbmVySW5mbz86IHN0cmluZztcbn1cblxuLy8gVHlwZSBkZWNsYXJhdGlvbnMgZm9yIGJldHRlciB0eXBlIHNhZmV0eVxudHlwZSBET01QdXJpZnlJbnN0YW5jZSA9IFJldHVyblR5cGU8dHlwZW9mIERPTVB1cmlmeT47XG5cbmV4cG9ydCBjbGFzcyBVcGRhdGVDaGVja2VyIHtcbiAgcHJpdmF0ZSB2ZXJzaW9uTWFuYWdlcjogVmVyc2lvbk1hbmFnZXI7XG4gIHByaXZhdGUgcmF0ZUxpbWl0ZXI6IFJhdGVMaW1pdGVyO1xuICBwcml2YXRlIHNpZ25hdHVyZVZlcmlmaWVyOiBTaWduYXR1cmVWZXJpZmllcjtcbiAgXG4gIC8vIFN0YXRpYyBjYWNoZSBmb3IgRE9NUHVyaWZ5IHRvIGltcHJvdmUgcGVyZm9ybWFuY2VcbiAgLy8gV2UgdXNlICdhbnknIGZvciBKU0RPTSB3aW5kb3cgdG8gYXZvaWQgY29tcGxleCB0eXBlIGNvbmZsaWN0c1xuICAvLyBidXQgbWFpbnRhaW4gdHlwZSBzYWZldHkgZm9yIERPTVB1cmlmeSBpbnN0YW5jZVxuICBwcml2YXRlIHN0YXRpYyBwdXJpZnlXaW5kb3c6IGFueSA9IG51bGw7XG4gIHByaXZhdGUgc3RhdGljIHB1cmlmeTogRE9NUHVyaWZ5SW5zdGFuY2UgfCBudWxsID0gbnVsbDtcbiAgXG4gIC8vIFNlY3VyaXR5IGNvbmZpZ3VyYXRpb24gd2l0aCBzZW5zaWJsZSBkZWZhdWx0c1xuICBwcml2YXRlIHJlYWRvbmx5IHJlbGVhc2VOb3Rlc01heExlbmd0aDogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IHVybE1heExlbmd0aDogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IHNlY3VyaXR5TG9nZ2VyPzogKGV2ZW50OiBzdHJpbmcsIGRldGFpbHM6IGFueSkgPT4gdm9pZDtcbiAgcHJpdmF0ZSByZWFkb25seSByZXF1aXJlU2lnbmVkUmVsZWFzZXM6IGJvb2xlYW47XG4gIFxuICBjb25zdHJ1Y3RvcihcbiAgICB2ZXJzaW9uTWFuYWdlcjogVmVyc2lvbk1hbmFnZXIsXG4gICAgb3B0aW9ucz86IHtcbiAgICAgIHJlbGVhc2VOb3Rlc01heExlbmd0aD86IG51bWJlcjtcbiAgICAgIHVybE1heExlbmd0aD86IG51bWJlcjtcbiAgICAgIHNlY3VyaXR5TG9nZ2VyPzogKGV2ZW50OiBzdHJpbmcsIGRldGFpbHM6IGFueSkgPT4gdm9pZDtcbiAgICAgIHJhdGVMaW1pdGVyPzogUmF0ZUxpbWl0ZXI7XG4gICAgICBzaWduYXR1cmVWZXJpZmllcj86IFNpZ25hdHVyZVZlcmlmaWVyO1xuICAgICAgcmVxdWlyZVNpZ25lZFJlbGVhc2VzPzogYm9vbGVhbjtcbiAgICB9XG4gICkge1xuICAgIGlmICghdmVyc2lvbk1hbmFnZXIpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignVmVyc2lvbk1hbmFnZXIgaXMgcmVxdWlyZWQnKTtcbiAgICB9XG4gICAgdGhpcy52ZXJzaW9uTWFuYWdlciA9IHZlcnNpb25NYW5hZ2VyO1xuICAgIFxuICAgIC8vIEFwcGx5IG9wdGlvbnMgd2l0aCBkZWZhdWx0cyBhbmQgdmFsaWRhdGlvblxuICAgIHRoaXMucmVsZWFzZU5vdGVzTWF4TGVuZ3RoID0gb3B0aW9ucz8ucmVsZWFzZU5vdGVzTWF4TGVuZ3RoID8/IDUwMDA7XG4gICAgdGhpcy51cmxNYXhMZW5ndGggPSBvcHRpb25zPy51cmxNYXhMZW5ndGggPz8gMjA0ODtcbiAgICB0aGlzLnNlY3VyaXR5TG9nZ2VyID0gb3B0aW9ucz8uc2VjdXJpdHlMb2dnZXI7XG4gICAgXG4gICAgLy8gVXNlIHByb3ZpZGVkIHJhdGUgbGltaXRlciBvciBjcmVhdGUgZGVmYXVsdFxuICAgIHRoaXMucmF0ZUxpbWl0ZXIgPSBvcHRpb25zPy5yYXRlTGltaXRlciB8fCBSYXRlTGltaXRlckZhY3RvcnkuY3JlYXRlVXBkYXRlQ2hlY2tMaW1pdGVyKCk7XG4gICAgXG4gICAgLy8gRGV0ZXJtaW5lIGlmIHdlJ3JlIGluIHByb2R1Y3Rpb24gZW52aXJvbm1lbnRcbiAgICBjb25zdCBpc1Byb2R1Y3Rpb24gPSBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gJ3Byb2R1Y3Rpb24nIHx8IFxuICAgICAgICAgICAgICAgICAgICAgICAgcHJvY2Vzcy5lbnYuQ0kgPT09ICd0cnVlJyB8fFxuICAgICAgICAgICAgICAgICAgICAgICAgIXByb2Nlc3MuZW52LkFMTE9XX1VOU0lHTkVEX1JFTEVBU0VTO1xuICAgIFxuICAgIC8vIFVzZSBwcm92aWRlZCBzaWduYXR1cmUgdmVyaWZpZXIgb3IgY3JlYXRlIGRlZmF1bHRcbiAgICB0aGlzLnNpZ25hdHVyZVZlcmlmaWVyID0gb3B0aW9ucz8uc2lnbmF0dXJlVmVyaWZpZXIgfHwgbmV3IFNpZ25hdHVyZVZlcmlmaWVyKHtcbiAgICAgIC8vIEluIHByb2R1Y3Rpb24sIHdlIHNob3VsZCByZXF1aXJlIHNpZ25lZCByZWxlYXNlc1xuICAgICAgYWxsb3dVbnNpZ25lZEluRGV2OiAhaXNQcm9kdWN0aW9uXG4gICAgfSk7XG4gICAgXG4gICAgLy8gV2hldGhlciB0byByZXF1aXJlIHNpZ25lZCByZWxlYXNlcyAoZGVmYXVsdDogdHJ1ZSBpbiBwcm9kdWN0aW9uKVxuICAgIHRoaXMucmVxdWlyZVNpZ25lZFJlbGVhc2VzID0gb3B0aW9ucz8ucmVxdWlyZVNpZ25lZFJlbGVhc2VzID8/IGlzUHJvZHVjdGlvbjtcbiAgICBcbiAgICAvLyBWYWxpZGF0ZSBjb25maWd1cmF0aW9uIGZvciBzZWN1cml0eVxuICAgIGlmICh0aGlzLnJlbGVhc2VOb3Rlc01heExlbmd0aCA8IDEwMCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdyZWxlYXNlTm90ZXNNYXhMZW5ndGggbXVzdCBiZSBhdCBsZWFzdCAxMDAgY2hhcmFjdGVycyBmb3Igc2VjdXJpdHknKTtcbiAgICB9XG4gICAgaWYgKHRoaXMudXJsTWF4TGVuZ3RoIDwgNTApIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcigndXJsTWF4TGVuZ3RoIG11c3QgYmUgYXQgbGVhc3QgNTAgY2hhcmFjdGVycycpO1xuICAgIH1cbiAgICBcbiAgICAvLyBJbml0aWFsaXplIGNhY2hlZCBET01QdXJpZnkgaW5zdGFuY2UgZm9yIHBlcmZvcm1hbmNlXG4gICAgLy8gVGhpcyBhdm9pZHMgY3JlYXRpbmcgYSBuZXcgSlNET00gd2luZG93IGZvciBlYWNoIHNhbml0aXphdGlvblxuICAgIGlmICghVXBkYXRlQ2hlY2tlci5wdXJpZnlXaW5kb3cpIHtcbiAgICAgIGNvbnN0IGRvbSA9IG5ldyBKU0RPTSgnJyk7XG4gICAgICBVcGRhdGVDaGVja2VyLnB1cmlmeVdpbmRvdyA9IGRvbS53aW5kb3c7XG4gICAgICAvLyBET01QdXJpZnkgZXhwZWN0cyBhIFdpbmRvdy1saWtlIG9iamVjdCBmcm9tIEpTRE9NXG4gICAgICBVcGRhdGVDaGVja2VyLnB1cmlmeSA9IERPTVB1cmlmeShVcGRhdGVDaGVja2VyLnB1cmlmeVdpbmRvdyk7XG4gICAgfVxuICB9XG4gIFxuICAvKipcbiAgICogRXhlY3V0ZSBhIG5ldHdvcmsgb3BlcmF0aW9uIHdpdGggcmV0cnkgbG9naWMgYW5kIGV4cG9uZW50aWFsIGJhY2tvZmZcbiAgICogQHBhcmFtIG9wZXJhdGlvbiAtIFRoZSBhc3luYyBvcGVyYXRpb24gdG8gZXhlY3V0ZVxuICAgKiBAcGFyYW0gbWF4UmV0cmllcyAtIE1heGltdW0gbnVtYmVyIG9mIHJldHJ5IGF0dGVtcHRzIChkZWZhdWx0OiAzKVxuICAgKiBAcGFyYW0gYmFzZURlbGF5IC0gQmFzZSBkZWxheSBpbiBtaWxsaXNlY29uZHMgZm9yIGV4cG9uZW50aWFsIGJhY2tvZmYgKGRlZmF1bHQ6IDEwMDBtcylcbiAgICogQHJldHVybnMgUHJvbWlzZSByZXNvbHZpbmcgdG8gdGhlIG9wZXJhdGlvbiByZXN1bHRcbiAgICogQHRocm93cyBUaGUgbGFzdCBlcnJvciBpZiBhbGwgcmV0cmllcyBmYWlsXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHJldHJ5TmV0d29ya09wZXJhdGlvbjxUPihcbiAgICBvcGVyYXRpb246ICgpID0+IFByb21pc2U8VD4sIFxuICAgIG1heFJldHJpZXM6IG51bWJlciA9IDMsXG4gICAgYmFzZURlbGF5OiBudW1iZXIgPSAxMDAwXG4gICk6IFByb21pc2U8VD4ge1xuICAgIGxldCBsYXN0RXJyb3I6IEVycm9yO1xuICAgIFxuICAgIGZvciAobGV0IGF0dGVtcHQgPSAwOyBhdHRlbXB0IDw9IG1heFJldHJpZXM7IGF0dGVtcHQrKykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuIGF3YWl0IG9wZXJhdGlvbigpO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbGFzdEVycm9yID0gZXJyb3IgYXMgRXJyb3I7XG4gICAgICAgIFxuICAgICAgICAvLyBEb24ndCByZXRyeSBvbiB0aGUgbGFzdCBhdHRlbXB0XG4gICAgICAgIGlmIChhdHRlbXB0ID09PSBtYXhSZXRyaWVzKSB7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIC8vIERvbid0IHJldHJ5IGNlcnRhaW4gZXJyb3JzIChsaWtlIDQwNCwgNDAxKVxuICAgICAgICBpZiAoZXJyb3IgaW5zdGFuY2VvZiBFcnJvciAmJiBlcnJvci5tZXNzYWdlLmluY2x1ZGVzKCc0MDQnKSkge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBDYWxjdWxhdGUgZGVsYXkgd2l0aCBleHBvbmVudGlhbCBiYWNrb2ZmXG4gICAgICAgIGNvbnN0IGRlbGF5ID0gYmFzZURlbGF5ICogTWF0aC5wb3coMiwgYXR0ZW1wdCk7XG4gICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gc2V0VGltZW91dChyZXNvbHZlLCBkZWxheSkpO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICB0aHJvdyBsYXN0RXJyb3IhO1xuICB9XG4gIFxuICAvKipcbiAgICogQ2hlY2sgZm9yIHVwZGF0ZXMgZnJvbSBHaXRIdWIgcmVsZWFzZXMgd2l0aCBzZWN1cml0eSBhbmQgZXJyb3IgaGFuZGxpbmdcbiAgICogQHJldHVybnMgVXBkYXRlQ2hlY2tSZXN1bHQgaWYgdXBkYXRlIGluZm8gaXMgYXZhaWxhYmxlLCBudWxsIGlmIG5vIHJlbGVhc2VzIGZvdW5kXG4gICAqIEB0aHJvd3MgRXJyb3IgZm9yIG5ldHdvcmsgb3IgQVBJIGZhaWx1cmVzIG9yIHJhdGUgbGltaXQgZXhjZWVkZWRcbiAgICovXG4gIGFzeW5jIGNoZWNrRm9yVXBkYXRlcygpOiBQcm9taXNlPFVwZGF0ZUNoZWNrUmVzdWx0IHwgbnVsbD4ge1xuICAgIC8vIENoZWNrIHJhdGUgbGltaXQgYmVmb3JlIG1ha2luZyBBUEkgcmVxdWVzdFxuICAgIGNvbnN0IHJhdGVMaW1pdFN0YXR1cyA9IHRoaXMucmF0ZUxpbWl0ZXIuY2hlY2tMaW1pdCgpO1xuICAgIGlmICghcmF0ZUxpbWl0U3RhdHVzLmFsbG93ZWQpIHtcbiAgICAgIGNvbnN0IHdhaXRUaW1lID0gTWF0aC5jZWlsKHJhdGVMaW1pdFN0YXR1cy5yZXRyeUFmdGVyTXMhIC8gMTAwMCk7XG4gICAgICBjb25zdCB3YWl0TWludXRlcyA9IE1hdGguZmxvb3Iod2FpdFRpbWUgLyA2MCk7XG4gICAgICBjb25zdCB3YWl0U2Vjb25kcyA9IHdhaXRUaW1lICUgNjA7XG4gICAgICBcbiAgICAgIGNvbnN0IHRpbWVTdHIgPSB3YWl0TWludXRlcyA+IDAgXG4gICAgICAgID8gYCR7d2FpdE1pbnV0ZXN9IG1pbnV0ZSR7d2FpdE1pbnV0ZXMgPiAxID8gJ3MnIDogJyd9ICR7d2FpdFNlY29uZHN9IHNlY29uZCR7d2FpdFNlY29uZHMgIT09IDEgPyAncycgOiAnJ31gXG4gICAgICAgIDogYCR7d2FpdFNlY29uZHN9IHNlY29uZCR7d2FpdFNlY29uZHMgIT09IDEgPyAncycgOiAnJ31gO1xuICAgICAgXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBSYXRlIGxpbWl0IGV4Y2VlZGVkLiBQbGVhc2Ugd2FpdCAke3RpbWVTdHJ9IGJlZm9yZSBjaGVja2luZyBmb3IgdXBkYXRlcyBhZ2Fpbi4gYCArXG4gICAgICAgIGAoJHtyYXRlTGltaXRTdGF0dXMucmVtYWluaW5nVG9rZW5zfSByZXF1ZXN0cyByZW1haW5pbmcsIHJlc2V0cyBhdCAke3JhdGVMaW1pdFN0YXR1cy5yZXNldFRpbWUudG9Mb2NhbGVUaW1lU3RyaW5nKCl9KWBcbiAgICAgICk7XG4gICAgfVxuICAgIFxuICAgIC8vIENvbnN1bWUgYSByYXRlIGxpbWl0IHRva2VuXG4gICAgdGhpcy5yYXRlTGltaXRlci5jb25zdW1lVG9rZW4oKTtcbiAgICBcbiAgICBjb25zdCBjdXJyZW50VmVyc2lvbiA9IGF3YWl0IHRoaXMudmVyc2lvbk1hbmFnZXIuZ2V0Q3VycmVudFZlcnNpb24oKTtcbiAgICBcbiAgICAvLyBDaGVjayBHaXRIdWIgcmVsZWFzZXMgQVBJIGZvciBsYXRlc3QgdmVyc2lvbiB3aXRoIHJldHJ5IGxvZ2ljXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0aGlzLnJldHJ5TmV0d29ya09wZXJhdGlvbihhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgICAgY29uc3QgdGltZW91dElkID0gc2V0VGltZW91dCgoKSA9PiBjb250cm9sbGVyLmFib3J0KCksIDEwMDAwKTsgLy8gMTAgc2Vjb25kIHRpbWVvdXRcbiAgICAgIFxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChSRUxFQVNFU19BUElfVVJMLCB7XG4gICAgICAgICAgaGVhZGVyczoge1xuICAgICAgICAgICAgJ0FjY2VwdCc6ICdhcHBsaWNhdGlvbi92bmQuZ2l0aHViLnYzK2pzb24nLFxuICAgICAgICAgICAgJ1VzZXItQWdlbnQnOiAnRG9sbGhvdXNlTUNQLzEuMCdcbiAgICAgICAgICB9LFxuICAgICAgICAgIHNpZ25hbDogY29udHJvbGxlci5zaWduYWxcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dElkKTtcbiAgICAgICAgcmV0dXJuIHJlc3BvbnNlO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXRJZCk7XG4gICAgICAgIHRocm93IGVycm9yO1xuICAgICAgfVxuICAgIH0pO1xuICAgIFxuICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQwNCkge1xuICAgICAgICByZXR1cm4gbnVsbDsgLy8gTm8gcmVsZWFzZXMgZm91bmRcbiAgICAgIH1cbiAgICAgIHRocm93IG5ldyBFcnJvcihgR2l0SHViIEFQSSBlcnJvcjogJHtyZXNwb25zZS5zdGF0dXN9ICR7cmVzcG9uc2Uuc3RhdHVzVGV4dH1gKTtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgcmVsZWFzZURhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgY29uc3QgdGFnTmFtZSA9IHJlbGVhc2VEYXRhLnRhZ19uYW1lO1xuICAgIGNvbnN0IGxhdGVzdFZlcnNpb24gPSB0YWdOYW1lPy5yZXBsYWNlKC9edi8sICcnKSB8fCByZWxlYXNlRGF0YS5uYW1lO1xuICAgIC8vIFVzZSBjb25zaXN0ZW50IGRhdGUgZm9ybWF0dGluZyBtZXRob2RcbiAgICBjb25zdCBwdWJsaXNoZWRBdCA9IHJlbGVhc2VEYXRhLnB1Ymxpc2hlZF9hdDtcbiAgICBcbiAgICAvLyBDb21wYXJlIHZlcnNpb25zXG4gICAgY29uc3QgaXNVcGRhdGVBdmFpbGFibGUgPSB0aGlzLnZlcnNpb25NYW5hZ2VyLmNvbXBhcmVWZXJzaW9ucyhjdXJyZW50VmVyc2lvbiwgbGF0ZXN0VmVyc2lvbikgPCAwO1xuICAgIFxuICAgIGNvbnN0IHJlbGVhc2VOb3RlcyA9IHJlbGVhc2VEYXRhLmJvZHkgfHwgJ1NlZSByZWxlYXNlIG5vdGVzIG9uIEdpdEh1Yic7XG4gICAgXG4gICAgLy8gVmVyaWZ5IHJlbGVhc2Ugc2lnbmF0dXJlIGlmIHdlIGhhdmUgYSB0YWdcbiAgICBsZXQgc2lnbmF0dXJlVmVyaWZpZWQgPSBmYWxzZTtcbiAgICBsZXQgc2lnbmVySW5mbzogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIFxuICAgIGlmICh0YWdOYW1lKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCB2ZXJpZmljYXRpb25SZXN1bHQgPSBhd2FpdCB0aGlzLnNpZ25hdHVyZVZlcmlmaWVyLnZlcmlmeVRhZ1NpZ25hdHVyZSh0YWdOYW1lKTtcbiAgICAgICAgc2lnbmF0dXJlVmVyaWZpZWQgPSB2ZXJpZmljYXRpb25SZXN1bHQudmVyaWZpZWQ7XG4gICAgICAgIFxuICAgICAgICBpZiAodmVyaWZpY2F0aW9uUmVzdWx0LnNpZ25lckVtYWlsKSB7XG4gICAgICAgICAgc2lnbmVySW5mbyA9IHZlcmlmaWNhdGlvblJlc3VsdC5zaWduZXJFbWFpbDtcbiAgICAgICAgICBpZiAodmVyaWZpY2F0aW9uUmVzdWx0LnNpZ25lcktleSkge1xuICAgICAgICAgICAgc2lnbmVySW5mbyArPSBgICgke3ZlcmlmaWNhdGlvblJlc3VsdC5zaWduZXJLZXl9KWA7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBMb2cgc2lnbmF0dXJlIHZlcmlmaWNhdGlvblxuICAgICAgICBpZiAodGhpcy5zZWN1cml0eUxvZ2dlcikge1xuICAgICAgICAgIHRoaXMuc2VjdXJpdHlMb2dnZXIoJ3NpZ25hdHVyZV92ZXJpZmljYXRpb24nLCB7XG4gICAgICAgICAgICB0YWdOYW1lLFxuICAgICAgICAgICAgdmVyaWZpZWQ6IHNpZ25hdHVyZVZlcmlmaWVkLFxuICAgICAgICAgICAgc2lnbmVyS2V5OiB2ZXJpZmljYXRpb25SZXN1bHQuc2lnbmVyS2V5LFxuICAgICAgICAgICAgZXJyb3I6IHZlcmlmaWNhdGlvblJlc3VsdC5lcnJvclxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIFxuICAgICAgICAvLyBJZiBzaWduYXR1cmUgdmVyaWZpY2F0aW9uIGlzIHJlcXVpcmVkIGFuZCBmYWlsZWQsIHRocm93IGVycm9yXG4gICAgICAgIGlmICh0aGlzLnJlcXVpcmVTaWduZWRSZWxlYXNlcyAmJiAhc2lnbmF0dXJlVmVyaWZpZWQpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgUmVsZWFzZSBzaWduYXR1cmUgdmVyaWZpY2F0aW9uIGZhaWxlZDogJHt2ZXJpZmljYXRpb25SZXN1bHQuZXJyb3IgfHwgJ1Vua25vd24gZXJyb3InfS4gYCArXG4gICAgICAgICAgICAnT25seSBzaWduZWQgcmVsZWFzZXMgYXJlIGFjY2VwdGVkIGluIHByb2R1Y3Rpb24gbW9kZS4nXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgLy8gSWYgd2UgY2FuJ3QgdmVyaWZ5IHRoZSBzaWduYXR1cmUgYW5kIGl0J3MgcmVxdWlyZWQsIGZhaWxcbiAgICAgICAgaWYgKHRoaXMucmVxdWlyZVNpZ25lZFJlbGVhc2VzKSB7XG4gI