@dollhousemcp/mcp-server
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.
530 lines • 68.3 kB
JavaScript
/**
* ValidationService - Centralized validation and sanitization for DollhouseMCP
*
* Eliminates duplicate validation code across element managers by providing
* a unified security-first validation approach.
*
* Key Features:
* - Security-first pattern: validate → sanitize → re-validate
* - Wraps existing validators (UnicodeValidator, ContentValidator, sanitizeInput)
* - Consistent validation algorithm across all element types
* - Comprehensive error reporting and logging
* - Full TypeScript type safety
*
* Security Pattern:
* The service enforces the correct validation sequence to prevent security bypasses:
* 1. VALIDATE input against pattern/rules (reject malicious input)
* 2. SANITIZE if validation passes (clean up input)
* 3. RE-VALIDATE sanitized output (ensure sanitization didn't break constraints)
*
* WRONG (common bug):
* const sanitized = sanitizeInput(category);
* metadata.category = sanitized.toLowerCase(); // No validation!
*
* CORRECT (what this service enforces):
* if (!isValidCategory(category)) throw error; // Validate FIRST
* const sanitized = sanitizeInput(category); // Then sanitize
* if (!isValidCategory(sanitized)) throw error; // Re-validate
*
* @example
* ```typescript
* import { validationService } from './services/ValidationService';
*
* // High-level coordinated validation
* const result = validationService.validateAndSanitizeInput(userInput, {
* maxLength: 200,
* allowSpaces: true
* });
*
* if (!result.isValid) {
* console.error(result.errors);
* } else {
* use(result.sanitizedValue);
* }
*
* // Metadata field validation
* const categoryResult = validationService.validateCategory('creative');
* const usernameResult = validationService.validateUsername('john-doe');
* const emailResult = validationService.validateEmail('user@example.com');
* ```
*/
import { sanitizeInput, validateCategory, validateUsername } from '../../security/InputValidator.js';
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
import { ContentValidator } from '../../security/contentValidator.js';
import { SecurityMonitor } from '../../security/securityMonitor.js';
import { VALIDATION_PATTERNS, PATTERN_DESCRIPTIONS } from '../../security/constants.js';
/**
* Service for validating and sanitizing input across all element types
*
* This service provides centralized validation to eliminate code duplication
* and enforce the security-first validation pattern across the codebase.
*/
export class ValidationService {
/**
* Validate and sanitize general input with sanitize-then-validate approach
*
* Validation process:
* 1. Check for empty/null input
* 2. SANITIZE input first (remove control chars, shell metacharacters, dangerous patterns)
* 3. Normalize Unicode if enabled (convert confusables, remove zero-width)
* 4. VALIDATE the cleaned result against field-appropriate pattern
* 5. Log any security issues detected
*
* This approach allows legitimate Unicode content (like "Cafe Assistant") to pass
* while still blocking security threats after they've been normalized/sanitized.
*
* @param input - Raw input string to validate
* @param options - Validation options
* @returns Validation result with sanitized value or errors
*
* @example
* ```typescript
* const result = service.validateAndSanitizeInput('user-input', {
* maxLength: 100,
* allowSpaces: true,
* fieldType: 'name'
* });
*
* if (result.isValid) {
* console.log('Clean input:', result.sanitizedValue);
* } else {
* console.error('Validation failed:', result.errors);
* }
* ```
*/
validateAndSanitizeInput(input, options = {}) {
const errors = [];
const warnings = [];
const maxLength = options.maxLength ?? 1000;
// Step 1: Check for empty/null input
if (!input || typeof input !== 'string') {
errors.push('Input must be a non-empty string');
return { isValid: false, errors, warnings };
}
// Step 2: SANITIZE the input first (remove known threats)
// This removes control chars, shell metacharacters, HTML tags, etc.
let cleaned = sanitizeInput(input, maxLength);
// Step 3: Unicode normalization (if not skipped)
if (!options.skipUnicode) {
const unicodeResult = this.normalizeUnicode(cleaned);
cleaned = unicodeResult.normalizedContent;
if (!unicodeResult.isValid && unicodeResult.detectedIssues) {
warnings.push(...unicodeResult.detectedIssues.map(issue => `Unicode: ${issue}`));
// Log high/critical Unicode issues
if (unicodeResult.severity === 'high' || unicodeResult.severity === 'critical') {
SecurityMonitor.logSecurityEvent({
type: 'UNICODE_VALIDATION_ERROR',
severity: unicodeResult.severity.toUpperCase(),
source: 'validation_service',
details: `Unicode validation detected: ${unicodeResult.detectedIssues.join(', ')}`,
});
}
}
}
// Step 4: Check if empty after sanitization
if (!cleaned || cleaned.length === 0) {
errors.push('Input is empty after sanitization');
return { isValid: false, errors, warnings };
}
// Step 5: VALIDATE the cleaned result against field-appropriate pattern
const pattern = this.getPatternForField(options);
if (!pattern.test(cleaned)) {
const desc = this.getPatternDescription(options);
const invalidChars = desc ? this.findInvalidCharacters(cleaned, desc.charTest) : [];
const charDetail = invalidChars.length > 0 ? `: ${this.formatCharList(invalidChars)}` : '';
const allowedDetail = desc ? `. Allowed: ${desc.allowed}` : '';
const structDetail = desc?.structural ? ` (${desc.structural})` : '';
errors.push(`Input contains invalid characters${charDetail}${allowedDetail}${structDetail}`);
return { isValid: false, errors, warnings };
}
// Validation success is the normal path — only failures need logging.
// Failures throw errors with full context upstream.
return {
isValid: true,
sanitizedValue: cleaned,
warnings: warnings.length > 0 ? warnings : undefined,
};
}
/**
* Get the appropriate validation pattern for a field type
*
* @param options - Input validation options
* @returns RegExp pattern appropriate for the field type
*/
getPatternForField(options) {
// If custom pattern provided, use it
if (options.customPattern) {
return options.customPattern;
}
// Use field type if specified
if (options.fieldType) {
switch (options.fieldType) {
case 'name':
return VALIDATION_PATTERNS.SAFE_NAME;
case 'description':
return VALIDATION_PATTERNS.SAFE_DESCRIPTION;
case 'content':
return VALIDATION_PATTERNS.SAFE_CONTENT;
case 'filename':
return VALIDATION_PATTERNS.SAFE_FILENAME_CREATE;
}
}
// Fall back to allowSpaces-based pattern for backward compatibility
return options.allowSpaces
? VALIDATION_PATTERNS.SAFE_NAME // Names/descriptions allow spaces and Unicode
: VALIDATION_PATTERNS.SAFE_FILENAME_CREATE; // No spaces = stricter filename pattern
}
/**
* Validate a metadata field with sanitize-then-validate approach
*
* @param fieldName - Name of the field being validated
* @param value - Field value to validate
* @param options - Field validation options
* @returns Field validation result
*
* @example
* ```typescript
* const result = service.validateMetadataField('category', 'creative', {
* required: true,
* maxLength: 50
* });
* ```
*/
validateMetadataField(fieldName, value, options = {}) {
const errors = [];
const warnings = [];
const required = options.required ?? true;
const maxLength = options.maxLength ?? 500;
// Check if value is string
if (typeof value !== 'string') {
if (required) {
errors.push(`${fieldName}: Must be a string (got ${typeof value})`);
return { isValid: false, errors, warnings, fieldName };
}
return { isValid: true, fieldName, wasEmpty: true };
}
// Check if empty
const wasEmpty = value.trim().length === 0;
if (wasEmpty && required) {
errors.push(`${fieldName}: Required field cannot be empty`);
return { isValid: false, errors, warnings, fieldName, wasEmpty };
}
if (wasEmpty && !required) {
return { isValid: true, fieldName, wasEmpty };
}
// Length check
if (value.length > maxLength) {
errors.push(`${fieldName}: Exceeds maximum length of ${maxLength} characters`);
return { isValid: false, errors, warnings, fieldName };
}
// SANITIZE first (remove known threats)
const sanitized = sanitizeInput(value, maxLength);
// Check if empty after sanitization
if (!sanitized || sanitized.length === 0) {
errors.push(`${fieldName}: Empty after sanitization`);
return { isValid: false, errors, warnings, fieldName };
}
// VALIDATE the sanitized result
// Use provided pattern or default to SAFE_NAME which allows Unicode letters
const pattern = options.pattern ?? VALIDATION_PATTERNS.SAFE_NAME;
if (!pattern.test(sanitized)) {
const desc = this.getDescriptionForPattern(pattern);
const invalidChars = desc ? this.findInvalidCharacters(sanitized, desc.charTest) : [];
const charDetail = invalidChars.length > 0 ? `: ${this.formatCharList(invalidChars)}` : '';
const allowedDetail = desc ? `. Allowed: ${desc.allowed}` : '';
errors.push(`${fieldName}: Contains invalid characters${charDetail}${allowedDetail}`);
return { isValid: false, errors, warnings, fieldName };
}
return {
isValid: true,
sanitizedValue: sanitized,
warnings: warnings.length > 0 ? warnings : undefined,
fieldName,
wasEmpty,
};
}
/**
* Validate a category field
*
* Enforces security-first pattern:
* 1. Validate format (lowercase letters and hyphens only)
* 2. Sanitize input
* 3. Re-validate sanitized output
*
* @param category - Category string to validate
* @returns Validation result with sanitized category
*
* @example
* ```typescript
* const result = service.validateCategory('creative');
* if (result.isValid) {
* metadata.category = result.sanitizedValue;
* }
* ```
*/
validateCategory(category) {
const errors = [];
const warnings = [];
try {
// VALIDATE before sanitization
if (!category || typeof category !== 'string') {
errors.push('Category must be a non-empty string');
return { isValid: false, errors };
}
if (!VALIDATION_PATTERNS.SAFE_CATEGORY.test(category)) {
const desc = PATTERN_DESCRIPTIONS.SAFE_CATEGORY;
const invalidChars = this.findInvalidCharacters(category, desc.charTest);
const charDetail = invalidChars.length > 0 ? `: ${this.formatCharList(invalidChars)}` : '';
const structDetail = desc.structural ? ` (${desc.structural})` : '';
errors.push(`Category contains invalid characters${charDetail}. Allowed: ${desc.allowed}${structDetail}`);
return { isValid: false, errors };
}
// SANITIZE using existing validator (which also validates format)
const sanitized = validateCategory(category);
// RE-VALIDATE is done inside validateCategory()
// The function throws if format is invalid, so if we get here, it's valid
return {
isValid: true,
sanitizedValue: sanitized,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(errorMessage);
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_INJECTION_ATTEMPT',
severity: 'MEDIUM',
source: 'validation_service',
details: `Category validation failed: ${errorMessage}`,
});
return { isValid: false, errors, warnings };
}
}
/**
* Validate a username field
*
* @param username - Username string to validate
* @returns Validation result with sanitized username
*
* @example
* ```typescript
* const result = service.validateUsername('john-doe');
* ```
*/
validateUsername(username) {
const errors = [];
const warnings = [];
try {
// VALIDATE before sanitization
if (!username || typeof username !== 'string') {
errors.push('Username must be a non-empty string');
return { isValid: false, errors };
}
if (!VALIDATION_PATTERNS.SAFE_USERNAME.test(username)) {
const desc = PATTERN_DESCRIPTIONS.SAFE_USERNAME;
const invalidChars = this.findInvalidCharacters(username, desc.charTest);
const charDetail = invalidChars.length > 0 ? `: ${this.formatCharList(invalidChars)}` : '';
const structDetail = desc.structural ? ` (${desc.structural})` : '';
errors.push(`Username contains invalid characters${charDetail}. Allowed: ${desc.allowed}${structDetail}`);
return { isValid: false, errors };
}
// SANITIZE using existing validator (which also validates)
const sanitized = validateUsername(username);
return {
isValid: true,
sanitizedValue: sanitized,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(errorMessage);
SecurityMonitor.logSecurityEvent({
type: 'CONTENT_INJECTION_ATTEMPT',
severity: 'MEDIUM',
source: 'validation_service',
details: `Username validation failed: ${errorMessage}`,
});
return { isValid: false, errors, warnings };
}
}
/**
* Validate an email address
*
* @param email - Email address to validate
* @returns Validation result with sanitized email
*
* @example
* ```typescript
* const result = service.validateEmail('user@example.com');
* ```
*/
validateEmail(email) {
const errors = [];
const warnings = [];
try {
// VALIDATE format
if (!email || typeof email !== 'string') {
errors.push('Email must be a non-empty string');
return { isValid: false, errors };
}
// Basic email pattern (more lenient for international domains)
const emailPattern = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailPattern.test(email)) {
errors.push('Invalid email format');
return { isValid: false, errors };
}
// SANITIZE (trim and lowercase)
const sanitized = email.trim().toLowerCase();
// RE-VALIDATE after sanitization
if (!emailPattern.test(sanitized)) {
errors.push('Email invalid after sanitization');
return { isValid: false, errors };
}
return {
isValid: true,
sanitizedValue: sanitized,
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(errorMessage);
return { isValid: false, errors, warnings };
}
}
/**
* Normalize Unicode content using UnicodeValidator
*
* Delegates to existing UnicodeValidator.normalize() to prevent
* homograph attacks, direction override attacks, and other Unicode-based bypasses.
*
* @param text - Text to normalize
* @returns Unicode validation result with normalized content
*
* @example
* ```typescript
* const result = service.normalizeUnicode('text with unicode');
* console.log(result.normalizedContent);
* ```
*/
normalizeUnicode(text) {
return UnicodeValidator.normalize(text);
}
/**
* Validate content for security threats using ContentValidator
*
* Delegates to existing ContentValidator.validateAndSanitize() to detect
* prompt injection, YAML bombs, command injection, and other content-based attacks.
*
* @param content - Content to validate
* @param options - Content validation options
* @returns Content validation result
*
* @example
* ```typescript
* const result = service.validateContent(personaContent, {
* skipSizeCheck: false
* });
*
* if (!result.isValid) {
* console.error('Security threat detected:', result.detectedPatterns);
* }
* ```
*/
validateContent(content, options) {
return ContentValidator.validateAndSanitize(content, options);
}
/**
* Find characters in `input` that do not match `charTest`.
* Returns deduplicated list of invalid characters.
*/
findInvalidCharacters(input, charTest) {
const seen = new Set();
const result = [];
for (const ch of input) {
if (!charTest.test(ch) && !seen.has(ch)) {
seen.add(ch);
result.push(ch);
}
}
return result;
}
/**
* Format a list of characters for display in error messages.
* Labels spaces and non-printable characters; caps output at 5 unique chars.
*/
formatCharList(chars) {
const MAX_DISPLAY = 5;
const display = chars.slice(0, MAX_DISPLAY).map(ch => {
if (ch === ' ')
return "' ' (space)";
if (ch === '\t')
return "'\\t' (tab)";
if (ch === '\n')
return "'\\n' (newline)";
if (ch === '\r')
return "'\\r' (carriage return)";
// Non-printable: show unicode codepoint
if (ch.charCodeAt(0) < 32 || ch.charCodeAt(0) === 127) {
return `U+${ch.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`;
}
return `'${ch}'`;
});
if (chars.length > MAX_DISPLAY) {
display.push(`and ${chars.length - MAX_DISPLAY} more`);
}
return display.join(', ');
}
/**
* Resolve which PATTERN_DESCRIPTIONS entry applies for InputValidationOptions.
* Mirrors the logic of `getPatternForField`.
*/
getPatternDescription(options) {
if (options.customPattern) {
return this.getDescriptionForPattern(options.customPattern);
}
if (options.fieldType) {
switch (options.fieldType) {
case 'name': return PATTERN_DESCRIPTIONS.SAFE_NAME;
case 'description': return PATTERN_DESCRIPTIONS.SAFE_DESCRIPTION;
case 'content': return PATTERN_DESCRIPTIONS.SAFE_CONTENT;
case 'filename': return PATTERN_DESCRIPTIONS.SAFE_FILENAME_CREATE;
}
}
return options.allowSpaces
? PATTERN_DESCRIPTIONS.SAFE_NAME
: PATTERN_DESCRIPTIONS.SAFE_FILENAME_CREATE;
}
/**
* Reverse-lookup from a RegExp instance to its PATTERN_DESCRIPTIONS entry.
* Compares by source+flags. Returns undefined for unknown patterns.
*/
getDescriptionForPattern(pattern) {
const key = `${pattern.source}|${pattern.flags}`;
for (const [name, vp] of Object.entries(VALIDATION_PATTERNS)) {
if (`${vp.source}|${vp.flags}` === key) {
return PATTERN_DESCRIPTIONS[name] ?? undefined;
}
}
return undefined;
}
}
/**
* Singleton instance of ValidationService
*
* Use this instance throughout the application to ensure consistent
* validation behavior across all element managers.
*
* @example
* ```typescript
* import { validationService } from './services/ValidationService';
*
* // In any element manager:
* const result = validationService.validateCategory(metadata.category);
* if (!result.isValid) {
* throw new Error(result.errors.join(', '));
* }
* metadata.category = result.sanitizedValue;
* ```
*/
export const validationService = new ValidationService();
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVmFsaWRhdGlvblNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvc2VydmljZXMvdmFsaWRhdGlvbi9WYWxpZGF0aW9uU2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWlERztBQUVILE9BQU8sRUFBRSxhQUFhLEVBQUUsZ0JBQWdCLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUNyRyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUNqRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUV0RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDcEUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLG9CQUFvQixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUF5RXhGOzs7OztHQUtHO0FBQ0gsTUFBTSxPQUFPLGlCQUFpQjtJQUM1Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQStCRztJQUNILHdCQUF3QixDQUN0QixLQUFhLEVBQ2IsVUFBa0MsRUFBRTtRQUVwQyxNQUFNLE1BQU0sR0FBYSxFQUFFLENBQUM7UUFDNUIsTUFBTSxRQUFRLEdBQWEsRUFBRSxDQUFDO1FBQzlCLE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxTQUFTLElBQUksSUFBSSxDQUFDO1FBRTVDLHFDQUFxQztRQUNyQyxJQUFJLENBQUMsS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQ3hDLE1BQU0sQ0FBQyxJQUFJLENBQUMsa0NBQWtDLENBQUMsQ0FBQztZQUNoRCxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLENBQUM7UUFDOUMsQ0FBQztRQUVELDBEQUEwRDtRQUMxRCxvRUFBb0U7UUFDcEUsSUFBSSxPQUFPLEdBQUcsYUFBYSxDQUFDLEtBQUssRUFBRSxTQUFTLENBQUMsQ0FBQztRQUU5QyxpREFBaUQ7UUFDakQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN6QixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDckQsT0FBTyxHQUFHLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQztZQUUxQyxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sSUFBSSxhQUFhLENBQUMsY0FBYyxFQUFFLENBQUM7Z0JBQzNELFFBQVEsQ0FBQyxJQUFJLENBQUMsR0FBRyxhQUFhLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUVqRixtQ0FBbUM7Z0JBQ25DLElBQUksYUFBYSxDQUFDLFFBQVEsS0FBSyxNQUFNLElBQUksYUFBYSxDQUFDLFFBQVEsS0FBSyxVQUFVLEVBQUUsQ0FBQztvQkFDL0UsZUFBZSxDQUFDLGdCQUFnQixDQUFDO3dCQUMvQixJQUFJLEVBQUUsMEJBQTBCO3dCQUNoQyxRQUFRLEVBQUUsYUFBYSxDQUFDLFFBQVEsQ0FBQyxXQUFXLEVBQXlCO3dCQUNyRSxNQUFNLEVBQUUsb0JBQW9CO3dCQUM1QixPQUFPLEVBQUUsZ0NBQWdDLGFBQWEsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO3FCQUNuRixDQUFDLENBQUM7Z0JBQ0wsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO1FBRUQsNENBQTRDO1FBQzVDLElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNyQyxNQUFNLENBQUMsSUFBSSxDQUFDLG1DQUFtQyxDQUFDLENBQUM7WUFDakQsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQzlDLENBQUM7UUFFRCx3RUFBd0U7UUFDeEUsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRWpELElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDM0IsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ2pELE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLHFCQUFxQixDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNwRixNQUFNLFVBQVUsR0FBRyxZQUFZLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsY0FBYyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMzRixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLGNBQWMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDL0QsTUFBTSxZQUFZLEdBQUcsSUFBSSxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsVUFBVSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUNyRSxNQUFNLENBQUMsSUFBSSxDQUFDLG9DQUFvQyxVQUFVLEdBQUcsYUFBYSxHQUFHLFlBQVksRUFBRSxDQUFDLENBQUM7WUFDN0YsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQzlDLENBQUM7UUFFRCxzRUFBc0U7UUFDdEUsb0RBQW9EO1FBRXBELE9BQU87WUFDTCxPQUFPLEVBQUUsSUFBSTtZQUNiLGNBQWMsRUFBRSxPQUFPO1lBQ3ZCLFFBQVEsRUFBRSxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxTQUFTO1NBQ3JELENBQUM7SUFDSixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSyxrQkFBa0IsQ0FBQyxPQUErQjtRQUN4RCxxQ0FBcUM7UUFDckMsSUFBSSxPQUFPLENBQUMsYUFBYSxFQUFFLENBQUM7WUFDMUIsT0FBTyxPQUFPLENBQUMsYUFBYSxDQUFDO1FBQy9CLENBQUM7UUFFRCw4QkFBOEI7UUFDOUIsSUFBSSxPQUFPLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDdEIsUUFBUSxPQUFPLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQzFCLEtBQUssTUFBTTtvQkFDVCxPQUFPLG1CQUFtQixDQUFDLFNBQVMsQ0FBQztnQkFDdkMsS0FBSyxhQUFhO29CQUNoQixPQUFPLG1CQUFtQixDQUFDLGdCQUFnQixDQUFDO2dCQUM5QyxLQUFLLFNBQVM7b0JBQ1osT0FBTyxtQkFBbUIsQ0FBQyxZQUFZLENBQUM7Z0JBQzFDLEtBQUssVUFBVTtvQkFDYixPQUFPLG1CQUFtQixDQUFDLG9CQUFvQixDQUFDO1lBQ3BELENBQUM7UUFDSCxDQUFDO1FBRUQsb0VBQW9FO1FBQ3BFLE9BQU8sT0FBTyxDQUFDLFdBQVc7WUFDeEIsQ0FBQyxDQUFDLG1CQUFtQixDQUFDLFNBQVMsQ0FBRSw4Q0FBOEM7WUFDL0UsQ0FBQyxDQUFDLG1CQUFtQixDQUFDLG9CQUFvQixDQUFDLENBQUUsd0NBQXdDO0lBQ3pGLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7O09BZUc7SUFDSCxxQkFBcUIsQ0FDbkIsU0FBaUIsRUFDakIsS0FBYyxFQUNkLFVBQWtDLEVBQUU7UUFFcEMsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO1FBQzVCLE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztRQUM5QixNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQztRQUMxQyxNQUFNLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLEdBQUcsQ0FBQztRQUUzQywyQkFBMkI7UUFDM0IsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUM5QixJQUFJLFFBQVEsRUFBRSxDQUFDO2dCQUNiLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxTQUFTLDJCQUEyQixPQUFPLEtBQUssR0FBRyxDQUFDLENBQUM7Z0JBQ3BFLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLENBQUM7WUFDekQsQ0FBQztZQUNELE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDdEQsQ0FBQztRQUVELGlCQUFpQjtRQUNqQixNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxLQUFLLENBQUMsQ0FBQztRQUMzQyxJQUFJLFFBQVEsSUFBSSxRQUFRLEVBQUUsQ0FBQztZQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsU0FBUyxrQ0FBa0MsQ0FBQyxDQUFDO1lBQzVELE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQ25FLENBQUM7UUFFRCxJQUFJLFFBQVEsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzFCLE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQztRQUNoRCxDQUFDO1FBRUQsZUFBZTtRQUNmLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxTQUFTLEVBQUUsQ0FBQztZQUM3QixNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsU0FBUywrQkFBK0IsU0FBUyxhQUFhLENBQUMsQ0FBQztZQUMvRSxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxDQUFDO1FBQ3pELENBQUM7UUFFRCx3Q0FBd0M7UUFDeEMsTUFBTSxTQUFTLEdBQUcsYUFBYSxDQUFDLEtBQUssRUFBRSxTQUFTLENBQUMsQ0FBQztRQUVsRCxvQ0FBb0M7UUFDcEMsSUFBSSxDQUFDLFNBQVMsSUFBSSxTQUFTLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ3pDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxTQUFTLDRCQUE0QixDQUFDLENBQUM7WUFDdEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQztRQUN6RCxDQUFDO1FBRUQsZ0NBQWdDO1FBQ2hDLDRFQUE0RTtRQUM1RSxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxJQUFJLG1CQUFtQixDQUFDLFNBQVMsQ0FBQztRQUNqRSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO1lBQzdCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNwRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDdEYsTUFBTSxVQUFVLEdBQUcsWUFBWSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDM0YsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxjQUFjLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQy9ELE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxTQUFTLGdDQUFnQyxVQUFVLEdBQUcsYUFBYSxFQUFFLENBQUMsQ0FBQztZQUN0RixPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxDQUFDO1FBQ3pELENBQUM7UUFFRCxPQUFPO1lBQ0wsT0FBTyxFQUFFLElBQUk7WUFDYixjQUFjLEVBQUUsU0FBUztZQUN6QixRQUFRLEVBQUUsUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsU0FBUztZQUNwRCxTQUFTO1lBQ1QsUUFBUTtTQUNULENBQUM7SUFDSixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQWtCRztJQUNILGdCQUFnQixDQUFDLFFBQWdCO1FBQy9CLE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztRQUM1QixNQUFNLFFBQVEsR0FBYSxFQUFFLENBQUM7UUFFOUIsSUFBSSxDQUFDO1lBQ0gsK0JBQStCO1lBQy9CLElBQUksQ0FBQyxRQUFRLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxFQUFFLENBQUM7Z0JBQzlDLE1BQU0sQ0FBQyxJQUFJLENBQUMscUNBQXFDLENBQUMsQ0FBQztnQkFDbkQsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUM7WUFDcEMsQ0FBQztZQUVELElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7Z0JBQ3RELE1BQU0sSUFBSSxHQUFHLG9CQUFvQixDQUFDLGFBQWEsQ0FBQztnQkFDaEQsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLHFCQUFxQixDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7Z0JBQ3pFLE1BQU0sVUFBVSxHQUFHLFlBQVksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxjQUFjLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUMzRixNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxVQUFVLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNwRSxNQUFNLENBQUMsSUFBSSxDQUFDLHVDQUF1QyxVQUFVLGNBQWMsSUFBSSxDQUFDLE9BQU8sR0FBRyxZQUFZLEVBQUUsQ0FBQyxDQUFDO2dCQUMxRyxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztZQUNwQyxDQUFDO1lBRUQsa0VBQWtFO1lBQ2xFLE1BQU0sU0FBUyxHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBRTdDLGdEQUFnRDtZQUNoRCwwRUFBMEU7WUFFMUUsT0FBTztnQkFDTCxPQUFPLEVBQUUsSUFBSTtnQkFDYixjQUFjLEVBQUUsU0FBUzthQUMxQixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLFlBQVksR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDNUUsTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUUxQixlQUFlLENBQUMsZ0JBQWdCLENBQUM7Z0JBQy9CLElBQUksRUFBRSwyQkFBMkI7Z0JBQ2pDLFFBQVEsRUFBRSxRQUFRO2dCQUNsQixNQUFNLEVBQUUsb0JBQW9CO2dCQUM1QixPQUFPLEVBQUUsK0JBQStCLFlBQVksRUFBRTthQUN2RCxDQUFDLENBQUM7WUFFSCxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLENBQUM7UUFDOUMsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsZ0JBQWdCLENBQUMsUUFBZ0I7UUFDL0IsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO1FBQzVCLE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztRQUU5QixJQUFJLENBQUM7WUFDSCwrQkFBK0I7WUFDL0IsSUFBSSxDQUFDLFFBQVEsSUFBSSxPQUFPLFFBQVEsS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDOUMsTUFBTSxDQUFDLElBQUksQ0FBQyxxQ0FBcUMsQ0FBQyxDQUFDO2dCQUNuRCxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztZQUNwQyxDQUFDO1lBRUQsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztnQkFDdEQsTUFBTSxJQUFJLEdBQUcsb0JBQW9CLENBQUMsYUFBYSxDQUFDO2dCQUNoRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMscUJBQXFCLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDekUsTUFBTSxVQUFVLEdBQUcsWUFBWSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7Z0JBQzNGLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLFVBQVUsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7Z0JBQ3BFLE1BQU0sQ0FBQyxJQUFJLENBQUMsdUNBQXVDLFVBQVUsY0FBYyxJQUFJLENBQUMsT0FBTyxHQUFHLFlBQVksRUFBRSxDQUFDLENBQUM7Z0JBQzFHLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDO1lBQ3BDLENBQUM7WUFFRCwyREFBMkQ7WUFDM0QsTUFBTSxTQUFTLEdBQUcsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLENBQUM7WUFFN0MsT0FBTztnQkFDTCxPQUFPLEVBQUUsSUFBSTtnQkFDYixjQUFjLEVBQUUsU0FBUzthQUMxQixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLFlBQVksR0FBRyxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDNUUsTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUUxQixlQUFlLENBQUMsZ0JBQWdCLENBQUM7Z0JBQy9CLElBQUksRUFBRSwyQkFBMkI7Z0JBQ2pDLFFBQVEsRUFBRSxRQUFRO2dCQUNsQixNQUFNLEVBQUUsb0JBQW9CO2dCQUM1QixPQUFPLEVBQUUsK0JBQStCLFlBQVksRUFBRTthQUN2RCxDQUFDLENBQUM7WUFFSCxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUFFLENBQUM7UUFDOUMsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsYUFBYSxDQUFDLEtBQWE7UUFDekIsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO1FBQzVCLE1BQU0sUUFBUSxHQUFhLEVBQUUsQ0FBQztRQUU5QixJQUFJLENBQUM7WUFDSCxrQkFBa0I7WUFDbEIsSUFBSSxDQUFDLEtBQUssSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDeEMsTUFBTSxDQUFDLElBQUksQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFDO2dCQUNoRCxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztZQUNwQyxDQUFDO1lBRUQsK0RBQStEO1lBQy9ELE1BQU0sWUFBWSxHQUFHLGlEQUFpRCxDQUFDO1lBRXZFLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQzlCLE1BQU0sQ0FBQyxJQUFJLENBQUMsc0JBQXNCLENBQUMsQ0FBQztnQkFDcEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUM7WUFDcEMsQ0FBQztZQUVELGdDQUFnQztZQUNoQyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7WUFFN0MsaUNBQWlDO1lBQ2pDLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7Z0JBQ2xDLE1BQU0sQ0FBQyxJQUFJLENBQUMsa0NBQWtDLENBQUMsQ0FBQztnQkFDaEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUM7WUFDcEMsQ0FBQztZQUVELE9BQU87Z0JBQ0wsT0FBTyxFQUFFLElBQUk7Z0JBQ2IsY0FBYyxFQUFFLFNBQVM7YUFDMUIsQ0FBQztRQUNKLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsTUFBTSxZQUFZLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQzVFLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDMUIsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQzlDLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7O09BY0c7SUFDSCxnQkFBZ0IsQ0FBQyxJQUFZO1FBQzNCLE9BQU8sZ0JBQWdCLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FvQkc7SUFDSCxlQUFlLENBQ2IsT0FBZSxFQUNmLE9BQWlDO1FBRWpDLE9BQU8sZ0JBQWdCLENBQUMsbUJBQW1CLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ2hFLENBQUM7SUFFRDs7O09BR0c7SUFDSyxxQkFBcUIsQ0FBQyxLQUFhLEVBQUUsUUFBZ0I7UUFDM0QsTUFBTSxJQUFJLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztRQUMvQixNQUFNLE1BQU0sR0FBYSxFQUFFLENBQUM7UUFDNUIsS0FBSyxNQUFNLEVBQUUsSUFBSSxLQUFLLEVBQUUsQ0FBQztZQUN2QixJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQztnQkFDeEMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztnQkFDYixNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ2xCLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVEOzs7T0FHRztJQUNLLGNBQWMsQ0FBQyxLQUFlO1FBQ3BDLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQztRQUN0QixNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxXQUFXLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUU7WUFDbkQsSUFBSSxFQUFFLEtBQUssR0FBRztnQkFBRSxPQUFPLGFBQWEsQ0FBQztZQUNyQyxJQUFJLEVBQUUsS0FBSyxJQUFJO2dCQUFFLE9BQU8sYUFBYSxDQUFDO1lBQ3RDLElBQUksRUFBRSxLQUFLLElBQUk7Z0JBQUUsT0FBTyxpQkFBaUIsQ0FBQztZQUMxQyxJQUFJLEVBQUUsS0FBSyxJQUFJO2dCQUFFLE9BQU8seUJBQXlCLENBQUM7WUFDbEQsd0NBQXdDO1lBQ3hDLElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsS0FBSyxHQUFHLEVBQUUsQ0FBQztnQkFDdEQsT0FBTyxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM3RSxDQUFDO1lBQ0QsT0FBTyxJQUFJLEVBQUUsR0FBRyxDQUFDO1FBQ25CLENBQUMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLFdBQVcsRUFBRSxDQUFDO1lBQy9CLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxLQUFLLENBQUMsTUFBTSxHQUFHLFdBQVcsT0FBTyxDQUFDLENBQUM7UUFDekQsQ0FBQztRQUNELE9BQU8sT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM1QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0sscUJBQXFCLENBQUMsT0FBK0I7UUFDM0QsSUFBSSxPQUFPLENBQUMsYUFBYSxFQUFFLENBQUM7WUFDMUIsT0FBTyxJQUFJLENBQUMsd0JBQXdCLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQzlELENBQUM7UUFDRCxJQUFJLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUN0QixRQUFRLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDMUIsS0FBSyxNQUFNLENBQUMsQ0FBQyxPQUFPLG9CQUFvQixDQUFDLFNBQVMsQ0FBQztnQkFDbkQsS0FBSyxhQUFhLENBQUMsQ0FBQyxPQUFPLG9CQUFvQixDQUFDLGdCQUFnQixDQUFDO2dCQUNqRSxLQUFLLFNBQVMsQ0FBQyxDQUFDLE9BQU8sb0JBQW9CLENBQUMsWUFBWSxDQUFDO2dCQUN6RCxLQUFLLFVBQVUsQ0FBQyxDQUFDLE9BQU8sb0JBQW9CLENBQUMsb0JBQW9CLENBQUM7WUFDcEUsQ0FBQztRQUNILENBQUM7UUFDRCxPQUFPLE9BQU8sQ0FBQyxXQUFXO1lBQ3hCLENBQUMsQ0FBQyxvQkFBb0IsQ0FBQyxTQUFTO1lBQ2hDLENBQUMsQ0FBQyxvQkFBb0IsQ0FBQyxvQkFBb0IsQ0FBQztJQUNoRCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ssd0JBQXdCLENBQUMsT0FBZTtRQUM5QyxNQUFNLEdBQUcsR0FBRyxHQUFHLE9BQU8sQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2pELEtBQUssTUFBTSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEVBQUUsQ0FBQztZQUM3RCxJQUFJLEdBQUcsRUFBRSxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUMsS0FBSyxFQUFFLEtBQUssR0FBRyxFQUFFLENBQUM7Z0JBQ3ZDLE9BQU8sb0JBQW9CLENBQUMsSUFBSSxDQUFDLElBQUksU0FBUyxDQUFDO1lBQ2pELENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztDQUNGO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxpQkFBaUIsRUFBRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBWYWxpZGF0aW9uU2VydmljZSAtIENlbnRyYWxpemVkIHZhbGlkYXRpb24gYW5kIHNhbml0aXphdGlvbiBmb3IgRG9sbGhvdXNlTUNQXG4gKlxuICogRWxpbWluYXRlcyBkdXBsaWNhdGUgdmFsaWRhdGlvbiBjb2RlIGFjcm9zcyBlbGVtZW50IG1hbmFnZXJzIGJ5IHByb3ZpZGluZ1xuICogYSB1bmlmaWVkIHNlY3VyaXR5LWZpcnN0IHZhbGlkYXRpb24gYXBwcm9hY2guXG4gKlxuICogS2V5IEZlYXR1cmVzOlxuICogLSBTZWN1cml0eS1maXJzdCBwYXR0ZXJuOiB2YWxpZGF0ZSDihpIgc2FuaXRpemUg4oaSIHJlLXZhbGlkYXRlXG4gKiAtIFdyYXBzIGV4aXN0aW5nIHZhbGlkYXRvcnMgKFVuaWNvZGVWYWxpZGF0b3IsIENvbnRlbnRWYWxpZGF0b3IsIHNhbml0aXplSW5wdXQpXG4gKiAtIENvbnNpc3RlbnQgdmFsaWRhdGlvbiBhbGdvcml0aG0gYWNyb3NzIGFsbCBlbGVtZW50IHR5cGVzXG4gKiAtIENvbXByZWhlbnNpdmUgZXJyb3IgcmVwb3J0aW5nIGFuZCBsb2dnaW5nXG4gKiAtIEZ1bGwgVHlwZVNjcmlwdCB0eXBlIHNhZmV0eVxuICpcbiAqIFNlY3VyaXR5IFBhdHRlcm46XG4gKiBUaGUgc2VydmljZSBlbmZvcmNlcyB0aGUgY29ycmVjdCB2YWxpZGF0aW9uIHNlcXVlbmNlIHRvIHByZXZlbnQgc2VjdXJpdHkgYnlwYXNzZXM6XG4gKiAxLiBWQUxJREFURSBpbnB1dCBhZ2FpbnN0IHBhdHRlcm4vcnVsZXMgKHJlamVjdCBtYWxpY2lvdXMgaW5wdXQpXG4gKiAyLiBTQU5JVElaRSBpZiB2YWxpZGF0aW9uIHBhc3NlcyAoY2xlYW4gdXAgaW5wdXQpXG4gKiAzLiBSRS1WQUxJREFURSBzYW5pdGl6ZWQgb3V0cHV0IChlbnN1cmUgc2FuaXRpemF0aW9uIGRpZG4ndCBicmVhayBjb25zdHJhaW50cylcbiAqXG4gKiBXUk9ORyAoY29tbW9uIGJ1Zyk6XG4gKiAgIGNvbnN0IHNhbml0aXplZCA9IHNhbml0aXplSW5wdXQoY2F0ZWdvcnkpO1xuICogICBtZXRhZGF0YS5jYXRlZ29yeSA9IHNhbml0aXplZC50b0xvd2VyQ2FzZSgpOyAvLyBObyB2YWxpZGF0aW9uIVxuICpcbiAqIENPUlJFQ1QgKHdoYXQgdGhpcyBzZXJ2aWNlIGVuZm9yY2VzKTpcbiAqICAgaWYgKCFpc1ZhbGlkQ2F0ZWdvcnkoY2F0ZWdvcnkpKSB0aHJvdyBlcnJvcjsgIC8vIFZhbGlkYXRlIEZJUlNUXG4gKiAgIGNvbnN0IHNhbml0aXplZCA9IHNhbml0aXplSW5wdXQoY2F0ZWdvcnkpOyAgICAgLy8gVGhlbiBzYW5pdGl6ZVxuICogICBpZiAoIWlzVmFsaWRDYXRlZ29yeShzYW5pdGl6ZWQpKSB0aHJvdyBlcnJvcjsgIC8vIFJlLXZhbGlkYXRlXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IHZhbGlkYXRpb25TZXJ2aWNlIH0gZnJvbSAnLi9zZXJ2aWNlcy9WYWxpZGF0aW9uU2VydmljZSc7XG4gKlxuICogLy8gSGlnaC1sZXZlbCBjb29yZGluYXRlZCB2YWxpZGF0aW9uXG4gKiBjb25zdCByZXN1bHQgPSB2YWxpZGF0aW9uU2VydmljZS52YWxpZGF0ZUFuZFNhbml0aXplSW5wdXQodXNlcklucHV0LCB7XG4gKiAgIG1heExlbmd0aDogMjAwLFxuICogICBhbGxvd1NwYWNlczogdHJ1ZVxuICogfSk7XG4gKlxuICogaWYgKCFyZXN1bHQuaXNWYWxpZCkge1xuICogICBjb25zb2xlLmVycm9yKHJlc3VsdC5lcnJvcnMpO1xuICogfSBlbHNlIHtcbiAqICAgdXNlKHJlc3VsdC5zYW5pdGl6ZWRWYWx1ZSk7XG4gKiB9XG4gKlxuICogLy8gTWV0YWRhdGEgZmllbGQgdmFsaWRhdGlvblxuICogY29uc3QgY2F0ZWdvcnlSZXN1bHQgPSB2YWxpZGF0aW9uU2VydmljZS52YWxpZGF0ZUNhdGVnb3J5KCdjcmVhdGl2ZScpO1xuICogY29uc3QgdXNlcm5hbWVSZXN1bHQgPSB2YWxpZGF0aW9uU2VydmljZS52YWxpZGF0ZVVzZXJuYW1lKCdqb2huLWRvZScpO1xuICogY29uc3QgZW1haWxSZXN1bHQgPSB2YWxpZGF0aW9uU2VydmljZS52YWxpZGF0ZUVtYWlsKCd1c2VyQGV4YW1wbGUuY29tJyk7XG4gKiBgYGBcbiAqL1xuXG5pbXBvcnQgeyBzYW5pdGl6ZUlucHV0LCB2YWxpZGF0ZUNhdGVnb3J5LCB2YWxpZGF0ZVVzZXJuYW1lIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvSW5wdXRWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgVW5pY29kZVZhbGlkYXRvciB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L3ZhbGlkYXRvcnMvdW5pY29kZVZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBDb250ZW50VmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvY29udGVudFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgdHlwZSB7IENvbnRlbnRWYWxpZGF0b3JPcHRpb25zLCBDb250ZW50VmFsaWRhdGlvblJlc3VsdCB9IGZyb20gJy4uLy4uL3NlY3VyaXR5L2NvbnRlbnRWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlNb25pdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvc2VjdXJpdHlNb25pdG9yLmpzJztcbmltcG9ydCB7IFZBTElEQVRJT05fUEFUVEVSTlMsIFBBVFRFUk5fREVTQ1JJUFRJT05TIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvY29uc3RhbnRzLmpzJztcblxuLyoqXG4gKiBGaWVsZCB0eXBlcyBmb3IgdmFsaWRhdGlvbiB3aXRoIGFwcHJvcHJpYXRlIHN0cmljdG5lc3MgbGV2ZWxzXG4gKi9cbmV4cG9ydCB0eXBlIFZhbGlkYXRpb25GaWVsZFR5cGUgPSAnbmFtZScgfCAnZGVzY3JpcHRpb24nIHwgJ2NvbnRlbnQnIHwgJ2ZpbGVuYW1lJztcblxuLyoqXG4gKiBPcHRpb25zIGZvciBpbnB1dCB2YWxpZGF0aW9uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5wdXRWYWxpZGF0aW9uT3B0aW9ucyB7XG4gIC8qKiBNYXhpbXVtIGxlbmd0aCBvZiBpbnB1dCAoZGVmYXVsdDogMTAwMCkgKi9cbiAgbWF4TGVuZ3RoPzogbnVtYmVyO1xuICAvKiogQWxsb3cgc3BhY2VzIGluIGlucHV0IChkZWZhdWx0OiBmYWxzZSkgKi9cbiAgYWxsb3dTcGFjZXM/OiBib29sZWFuO1xuICAvKiogQ3VzdG9tIHZhbGlkYXRpb24gcGF0dGVybiAob3ZlcnJpZGVzIGRlZmF1bHQpICovXG4gIGN1c3RvbVBhdHRlcm4/OiBSZWdFeHA7XG4gIC8qKiBTa2lwIFVuaWNvZGUgbm9ybWFsaXphdGlvbiAoZGVmYXVsdDogZmFsc2UpICovXG4gIHNraXBVbmljb2RlPzogYm9vbGVhbjtcbiAgLyoqIEZpZWxkIHR5cGUgZm9yIGFwcHJvcHJpYXRlIHZhbGlkYXRpb24gc3RyaWN0bmVzcyAoZGVmYXVsdDogdmFsaWRhdGVzIGJhc2VkIG9uIGFsbG93U3BhY2VzKSAqL1xuICBmaWVsZFR5cGU/OiBWYWxpZGF0aW9uRmllbGRUeXBlO1xufVxuXG4vKipcbiAqIE9wdGlvbnMgZm9yIG1ldGFkYXRhIGZpZWxkIHZhbGlkYXRpb25cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBGaWVsZFZhbGlkYXRpb25PcHRpb25zIHtcbiAgLyoqIFdoZXRoZXIgZmllbGQgaXMgcmVxdWlyZWQgKGRlZmF1bHQ6IHRydWUpICovXG4gIHJlcXVpcmVkPzogYm9vbGVhbjtcbiAgLyoqIE1heGltdW0gZmllbGQgbGVuZ3RoIChkZWZhdWx0OiA1MDApICovXG4gIG1heExlbmd0aD86IG51bWJlcjtcbiAgLyoqIEN1c3RvbSB2YWxpZGF0aW9uIHBhdHRlcm4gKi9cbiAgcGF0dGVybj86IFJlZ0V4cDtcbn1cblxuLyoqXG4gKiBHZW5lcmljIHZhbGlkYXRpb24gcmVzdWx0XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgVmFsaWRhdGlvblJlc3VsdCB7XG4gIC8qKiBXaGV0aGVyIHRoZSBpbnB1dCBwYXNzZWQgYWxsIHZhbGlkYXRpb24gY2hlY2tzICovXG4gIGlzVmFsaWQ6IGJvb2xlYW47XG4gIC8qKiBTYW5pdGl6ZWQgYW5kIHZhbGlkYXRlZCB2YWx1ZSAob25seSBwcmVzZW50IGlmIGlzVmFsaWQgaXMgdHJ1ZSkgKi9cbiAgc2FuaXRpemVkVmFsdWU/OiBzdHJpbmc7XG4gIC8qKiBMaXN0IG9mIHZhbGlkYXRpb24gZXJyb3JzIChvbmx5IHByZXNlbnQgaWYgaXNWYWxpZCBpcyBmYWxzZSkgKi9cbiAgZXJyb3JzPzogc3RyaW5nW107XG4gIC8qKiBMaXN0IG9mIHZhbGlkYXRpb24gd2FybmluZ3MgKG5vbi1mYXRhbCBpc3N1ZXMpICovXG4gIHdhcm5pbmdzPzogc3RyaW5nW107XG59XG5cbi8qKlxuICogRmllbGQtc3BlY2lmaWMgdmFsaWRhdGlvbiByZXN1bHQgd2l0aCBhZGRpdGlvbmFsIG1ldGFkYXRhXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRmllbGRWYWxpZGF0aW9uUmVzdWx0IGV4dGVuZHMgVmFsaWRhdGlvblJlc3VsdCB7XG4gIC8qKiBPcmlnaW5hbCBmaWVsZCBuYW1lIHRoYXQgd2FzIHZhbGlkYXRlZCAqL1xuICBmaWVsZE5hbWU/OiBzdHJpbmc7XG4gIC8qKiBXaGV0aGVyIHRoZSBmaWVsZCB3YXMgZW1wdHkgYmVmb3JlIHZhbGlkYXRpb24gKi9cbiAgd2FzRW1wdHk/OiBib29sZWFuO1xufVxuXG4vKipcbiAqIFJlc3VsdCBmcm9tIFVuaWNvZGUgdmFsaWRhdGlvblxuICovXG5leHBvcnQgaW50ZXJmYWNlIFVuaWNvZGVWYWxpZGF0aW9uUmVzdWx0IHtcbiAgLyoqIFdoZXRoZXIgdGhlIFVuaWNvZGUgY29udGVudCBpcyB2YWxpZCAqL1xuICBpc1ZhbGlkOiBib29sZWFuO1xuICAvKiogTm9ybWFsaXplZCBVbmljb2RlIGNvbnRlbnQgKi9cbiAgbm9ybWFsaXplZENvbnRlbnQ6IHN0cmluZztcbiAgLyoqIERldGVjdGVkIFVuaWNvZGUgaXNzdWVzICovXG4gIGRldGVjdGVkSXNzdWVzPzogc3RyaW5nW107XG4gIC8qKiBTZXZlcml0eSBvZiBkZXRlY3RlZCBpc3N1ZXMgKi9cbiAgc2V2ZXJpdHk/OiAnbG93JyB8ICdtZWRpdW0nIHwgJ2hpZ2gnIHwgJ2NyaXRpY2FsJztcbn1cblxuLyoqXG4gKiBTZXJ2aWNlIGZvciB2YWxpZGF0aW5nIGFuZCBzYW5pdGl6aW5nIGlucHV0IGFjcm9zcyBhbGwgZWxlbWVudCB0eXBlc1xuICpcbiAqIFRoaXMgc2VydmljZSBwcm92aWRlcyBjZW50cmFsaXplZCB2YWxpZGF0aW9uIHRvIGVsaW1pbmF0ZSBjb2RlIGR1cGxpY2F0aW9uXG4gKiBhbmQgZW5mb3JjZSB0aGUgc2VjdXJpdHktZmlyc3QgdmFsaWRhdGlvbiBwYXR0ZXJuIGFjcm9zcyB0aGUgY29kZWJhc2UuXG4gKi9cbmV4cG9ydCBjbGFzcyBWYWxpZGF0aW9uU2VydmljZSB7XG4gIC8qKlxuICAgKiBWYWxpZGF0ZSBhbmQgc2FuaXRpemUgZ2VuZXJhbCBpbnB1dCB3aXRoIHNhbml0aXplLXRoZW4tdmFsaWRhdGUgYXBwcm9hY2hcbiAgICpcbiAgICogVmFsaWRhdGlvbiBwcm9jZXNzOlxuICAgKiAxLiBDaGVjayBmb3IgZW1wdHkvbnVsbCBpbnB1dFxuICAgKiAyLiBTQU5JVElaRSBpbnB1dCBmaXJzdCAocmVtb3ZlIGNvbnRyb2wgY2hhcnMsIHNoZWxsIG1ldGFjaGFyYWN0ZXJzLCBkYW5nZXJvdXMgcGF0dGVybnMpXG4gICAqIDMuIE5vcm1hbGl6ZSBVbmljb2RlIGlmIGVuYWJsZWQgKGNvbnZlcnQgY29uZnVzYWJsZXMsIHJlbW92ZSB6ZXJvLXdpZHRoKVxuICAgKiA0LiBWQUxJREFURSB0aGUgY2xlYW5lZCByZXN1bHQgYWdhaW5zdCBmaWVsZC1hcHByb3ByaWF0ZSBwYXR0ZXJuXG4gICAqIDUuIExvZyBhbnkgc2VjdXJpdHkgaXNzdWVzIGRldGVjdGVkXG4gICAqXG4gICAqIFRoaXMgYXBwcm9hY2ggYWxsb3dzIGxlZ2l0aW1hdGUgVW5pY29kZSBjb250ZW50IChsaWtlIFwiQ2FmZSBBc3Npc3RhbnRcIikgdG8gcGFzc1xuICAgKiB3aGlsZSBzdGlsbCBibG9ja2luZyBzZWN1cml0eSB0aHJlYXRzIGFmdGVyIHRoZXkndmUgYmVlbiBub3JtYWxpemVkL3Nhbml0aXplZC5cbiAgICpcbiAgICogQHBhcmFtIGlucHV0IC0gUmF3IGlucHV0IHN0cmluZyB0byB2YWxpZGF0ZVxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIFZhbGlkYXRpb24gb3B0aW9uc1xuICAgKiBAcmV0dXJucyBWYWxpZGF0aW9uIHJlc3VsdCB3aXRoIHNhbml0aXplZCB2YWx1ZSBvciBlcnJvcnNcbiAgICpcbiAgICogQGV4YW1wbGVcbiAgICogYGBgdHlwZXNjcmlwdFxuICAgKiBjb25zdCByZXN1bHQgPSBzZXJ2aWNlLnZhbGlkYXRlQW5kU2FuaXRpemVJbnB1dCgndXNlci1pbnB1dCcsIHtcbiAgICogICBtYXhMZW5ndGg6IDEwMCxcbiAgICogICBhbGxvd1NwYWNlczogdHJ1ZSxcbiAgICogICBmaWVsZFR5cGU6ICduYW1lJ1xuICAgKiB9KTtcbiAgICpcbiAgICogaWYgKHJlc3VsdC5pc1ZhbGlkKSB7XG4gICAqICAgY29uc29sZS5sb2coJ0NsZWFuIGlucHV0OicsIHJlc3VsdC5zYW5pdGl6ZWRWYWx1ZSk7XG4gICAqIH0gZWxzZSB7XG4gICAqICAgY29uc29sZS5lcnJvcignVmFsaWRhdGlvbiBmYWlsZWQ6JywgcmVzdWx0LmVycm9ycyk7XG4gICAqIH1cbiAgICogYGBgXG4gICAqL1xuICB2YWxpZGF0ZUFuZFNhbml0aXplSW5wdXQoXG4gICAgaW5wdXQ6IHN0cmluZyxcbiAgICBvcHRpb25zOiBJbnB1dFZhbGlkYXRpb25PcHRpb25zID0ge31cbiAgKTogVmFsaWRhdGlvblJlc3VsdCB7XG4gICAgY29uc3QgZXJyb3JzOiBzdHJpbmdbXSA9IFtdO1xuICAgIGNvbnN0IHdhcm5pbmdzOiBzdHJpbmdbXSA9IFtdO1xuICAgIGNvbnN0IG1heExlbmd0aCA9IG9wdGlvbnMubWF4TGVuZ3RoID8/IDEwMDA7XG5cbiAgICAvLyBTdGVwIDE6IENoZWNrIGZvciBlbXB0eS9udWxsIGlucHV0XG4gICAgaWYgKCFpbnB1dCB8fCB0eXBlb2YgaW5wdXQgIT09ICdzdHJpbmcnKSB7XG4gICAgICBlcnJvcnMucHVzaCgnSW5wdXQgbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICAgIHJldHVybiB7IGlzVmFsaWQ6IGZhbHNlLCBlcnJvcnMsIHdhcm5pbmdzIH07XG4gICAgfVxuXG4gICAgLy8gU3RlcCAyOiBTQU5JVElaRSB0aGUgaW5wdXQgZmlyc3QgKHJlbW92ZSBrbm93biB0aHJlYXRzKVxuICAgIC8vIFRoaXMgcmVtb3ZlcyBjb250cm9sIGNoYXJzLCBzaGVsbCBtZXRhY2hhcmFjdGVycywgSFRNTCB0YWdzLCBldGMuXG4gICAgbGV0IGNsZWFuZWQgPSBzYW5pdGl6ZUlucHV0KGlucHV0LCBtYXhMZW5ndGgpO1xuXG4gICAgLy8gU3RlcCAzOiBVbmljb2RlIG5vcm1hbGl6YXRpb24gKGlmIG5vdCBza2lwcGVkKVxuICAgIGlmICghb3B0aW9ucy5za2lwVW5pY29kZSkge1xuICAgICAgY29uc3QgdW5pY29kZVJlc3VsdCA9IHRoaXMubm9ybWFsaXplVW5pY29kZShjbGVhbmVkKTtcbiAgICAgIGNsZWFuZWQgPSB1bmljb2RlUmVzdWx0Lm5vcm1hbGl6ZWRDb250ZW50O1xuXG4gICAgICBpZiAoIXVuaWNvZGVSZXN1bHQuaXNWYWxpZCAmJiB1bmljb2RlUmVzdWx0LmRldGVjdGVkSXNzdWVzKSB7XG4gICAgICAgIHdhcm5pbmdzLnB1c2goLi4udW5pY29kZVJlc3VsdC5kZXRlY3RlZElzc3Vlcy5tYXAoaXNzdWUgPT4gYFVuaWNvZGU6ICR7aXNzdWV9YCkpO1xuXG4gICAgICAgIC8vIExvZyBoaWdoL2NyaXRpY2FsIFVuaWNvZGUgaXNzdWVzXG4gICAgICAgIGlmICh1bmljb2RlUmVzdWx0LnNldmVyaXR5ID09PSAnaGlnaCcgfHwgdW5pY29kZVJlc3VsdC5zZXZlcml0eSA9PT0gJ2NyaXRpY2FsJykge1xuICAgICAgICAgIFNlY3VyaXR5TW9uaXRvci5sb2dTZWN1cml0eUV2ZW50KHtcbiAgICAgICAgICAgIHR5cGU6ICdVTklDT0RFX1ZBTElEQVRJT05fRVJST1InLFxuICAgICAgICAgICAgc2V2ZXJpdHk6IHVuaWNvZGVSZXN1bHQuc2V2ZXJpdHkudG9VcHBlckNhc2UoKSBhcyAnSElHSCcgfCAnQ1JJVElDQUwnLFxuICAgICAgICAgICAgc291cmNlOiAndmFsaWRhdGlvbl9zZXJ2aWNlJyxcbiAgICAgICAgICAgIGRldGFpbHM6IGBVbmljb2RlIHZhbGlkYXRpb24gZGV0ZWN0ZWQ6ICR7dW5pY29kZVJlc3VsdC5kZXRlY3RlZElzc3Vlcy5qb2luKCcsICcpfWAsXG4gICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBTdGVwIDQ6IENoZWNrIGlmIGVtcHR5IGFmdGVyIHNhbml0aXphdGlvblxuICAgIGlmICghY2xlYW5lZCB8fCBjbGVhbmVkLmxlbmd0aCA9PT0gMCkge1xuICAgICAgZXJyb3JzLnB1c2goJ0lucHV0IGlzIGVtcHR5IGFmdGVyIHNhbml0aXphdGlvbicpO1xuICAgICAgcmV0dXJuIHsgaXNWYWxpZDogZmFsc2UsIGVycm9ycywgd2FybmluZ3MgfTtcbiAgICB9XG5cbiAgICAvLyBTdGVwIDU6IFZBTElEQVRFIHRoZSBjbGVhbmVkIHJlc3VsdCBhZ2FpbnN0IGZpZWxkLWFwcHJvcHJpYXRlIHBhdHRlcm5cbiAgICBjb25zdCBwYXR0ZXJuID0gdGhpcy5nZXRQYXR0ZXJuRm9yRmllbGQob3B0aW9ucyk7XG5cbiAgICBpZiAoIXBhdHRlcm4udGVzdChjbGVhbmVkKSkge1xuICAgICAgY29uc3QgZGVzYyA9IHRoaXMuZ2V0UGF0dGVybkRlc2NyaXB0aW9uKG9wdGlvbnMpO1xuICAgICAgY29uc3QgaW52YWxpZENoYXJzID0gZGVzYyA/IHRoaXMuZmluZEludmFsaWRDaGFyYWN0ZXJzKGNsZWFuZWQsIGRlc2MuY2hhclRlc3QpIDogW107XG4gICAgICBjb25zdCBjaGFyRGV0YWlsID0gaW52YWxpZENoYXJzLmxlbmd0aCA+IDAgPyBgOiAke3RoaXMuZm9ybWF0Q2hhckxpc3QoaW52YWxpZENoYXJzKX1gIDogJyc7XG4gICAgICBjb25zdCBhbGxvd2VkRGV0YWlsID0gZGVzYyA/IGAuIEFsbG93ZWQ6ICR7ZGVzYy5hbGxvd2VkfWAgOiAnJztcbiAgICAgIGNvbnN0IHN0cnVjdERldGFpbCA9IGRlc2M/LnN0cnVjdHVyYWwgPyBgICgke2Rlc2Muc3RydWN0dXJhbH0pYCA6ICcnO1xuICAgICAgZXJyb3JzLnB1c2goYElucHV0IGNvbnRhaW5zIGludmFsaWQgY2hhcmFjdGVycyR7Y2hhckRldGFpbH0ke2FsbG93ZWREZXRhaWx9JHtzdHJ1Y3REZXRhaWx9YCk7XG4gICAgICByZXR1cm4geyBpc1ZhbGlkOiBmYWxzZSwgZXJyb3JzLCB3YXJuaW5ncyB9O1xuICAgIH1cblxuICAgIC8vIFZhbGlkYXRpb24gc3VjY2VzcyBpcyB0aGUgbm9ybWFsIHBhdGgg4oCUIG9ubHkgZmFpbHVyZXMgbmVlZCBsb2dnaW5nLlxuICAgIC8vIEZhaWx1cmVzIHRocm93IGVycm9ycyB3aXRoIGZ1bGwgY29udGV4dCB1cHN0cmVhbS5cblxuICAgIHJldHVybiB7XG4gICAgICBpc1ZhbGlkOiB0cnVlLFxuICAgICAgc2FuaXRpemVkVmFsdWU6IGNsZWFuZWQsXG4gICAgICB3YXJuaW5nczogd2FybmluZ3MubGVuZ3RoID4gMCA/IHdhcm5pbmdzIDogdW5kZWZpbmVkLFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogR2V0IHRoZSBhcHByb3ByaWF0ZSB2YWxpZGF0aW9uIHBhdHRlcm4gZm9yIGEgZmllbGQgdHlwZVxuICAgKlxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIElucHV0IHZhbGlkYXRpb24gb3B0aW9uc1xuICAgKiBAcmV0dXJucyBSZWdFeHAgcGF0dGVybiBhcHByb3ByaWF0ZSBmb3IgdGhlIGZpZWxkIHR5cGVcbiAgICovXG4gIHByaXZhdGUgZ2V0UGF0dGVybkZvckZpZWxkKG9wdGlvbnM6IElucHV0VmFsaWRhdGlvbk9wdGlvbnMpOiBSZWdFeHAge1xuICAgIC8vIElmIGN1c3RvbSBwYXR0ZXJuIHByb3ZpZGVkLCB1c2UgaXRcbiAgICBpZiAob3B0aW9ucy5jdXN0b21QYXR0ZXJuKSB7XG4gICAgICByZXR1cm4gb3B0aW9ucy5jdXN0b21QYXR0ZXJuO1xuICAgIH1cblxuICAgIC8vIFVzZSBmaWVsZCB0eXBlIGlmIHNwZWNpZmllZFxuICAgIGlmIChvcHRpb25zLmZpZWxkVHlwZSkge1xuICAgICAgc3dpdGNoIChvcHRpb25zLmZpZWxkVHlwZSkge1xuICAgICAgICBjYXNlICduYW1lJzpcbiAgICAgICAgICByZXR1cm4gVkFMSURBVElPTl9QQVRURVJOUy5TQUZFX05BTUU7XG4gICAgICAgIG