UNPKG

@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.

473 lines 65.6 kB
/** * SkillManager - Refactored to extend BaseElementManager for shared CRUD logic. * Maintains skill-specific behaviors such as trigger validation, import/export, * and creation workflows while eliminating duplicated file handling code. */ import { BaseElementManager } from '../base/BaseElementManager.js'; import { Skill } from './Skill.js'; import { ElementType } from '../../portfolio/types.js'; import { toSingularLabel } from '../../utils/elementTypeNormalization.js'; import { SecurityMonitor } from '../../security/securityMonitor.js'; import { sanitizeInput } from '../../security/InputValidator.js'; import { logger } from '../../utils/logger.js'; import { ElementMessages } from '../../utils/elementMessages.js'; import { sanitizeGatekeeperPolicy } from '../../handlers/mcp-aql/policies/ElementPolicies.js'; import { SECURITY_LIMITS } from '../../security/constants.js'; // Validation constants for skill triggers const MAX_TRIGGER_LENGTH = 50; // Allows alphanumeric, hyphens, underscores, @ (mentions/emails), . (domains) const TRIGGER_VALIDATION_REGEX = /^[a-zA-Z0-9\-_@.]+$/; // Issue #83: Centralized active element limits (configurable via env vars) import { getActiveElementLimitConfig, getMaxActiveLimit } from '../../config/active-element-limits.js'; export class SkillManager extends BaseElementManager { metadataService; triggerValidationService; validationService; serializationService; // Track active skills by name (stable identifier) activeSkillNames = new Set(); constructor(portfolioManager, fileLockManager, fileOperationsService, validationRegistry, serializationService, metadataService, fileWatchService, memoryBudget, backupService) { super(ElementType.SKILL, portfolioManager, fileLockManager, { fileWatchService, memoryBudget, backupService }, fileOperationsService, validationRegistry); this.metadataService = metadataService; this.triggerValidationService = validationRegistry.getTriggerValidationService(); this.validationService = validationRegistry.getValidationService(); this.serializationService = serializationService; } getElementLabel() { return 'skill'; } /** * Create a new skill element and persist it to disk. * FIX: Issue #20 - Add duplicate name checking */ async create(data) { // Use specialized validator for input validation const validationResult = await this.validator.validateCreate(data); if (!validationResult.isValid) { throw new Error(`Validation failed: ${validationResult.errors.join(', ')}`); } // Log warnings if any if (validationResult.warnings && validationResult.warnings.length > 0) { logger.warn(`Skill creation warnings: ${validationResult.warnings.join(', ')}`); } // Get sanitized values for file operations (validator already validated, we just need sanitized values) const nameInput = data.name || 'new-skill'; const nameResult = this.validationService.validateAndSanitizeInput(nameInput, { maxLength: SECURITY_LIMITS.MAX_NAME_LENGTH, allowSpaces: true }); const sanitizedName = nameResult.sanitizedValue; // Use inherited getElementFilename() for consistent filename normalization const filename = this.getElementFilename(sanitizedName); // FIX: Issue #20 - Check for duplicate before creating const existingSkills = await this.list(); const duplicate = existingSkills.find(s => s.metadata.name.toLowerCase() === sanitizedName.toLowerCase()); if (duplicate) { throw new Error(`A skill named "${sanitizedName}" already exists`); } const { content, instructions, ...metadata } = data; // Dual-field: instructions = behavioral directives, content = reference material // For skills, instructions is the primary field (how to apply the skill) const effectiveInstructions = instructions || content || ''; const effectiveContent = instructions ? (content || '') : ''; const skill = new Skill({ ...metadata, name: sanitizedName, description: data.description || '' }, effectiveInstructions, this.metadataService, effectiveContent); await this.save(skill, filename); // Note: No reload() here — save() caches the element correctly. // See Issue #491 for why PersonaManager's reload-after-create was removed. SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_CREATED', severity: 'LOW', source: 'SkillManager.create', details: `Skill created: ${skill.metadata.name}` }); return skill; } /** * Import a skill from YAML or JSON input formats. */ async importElement(data, format = 'yaml') { try { let metadata; let instructions; if (format === 'yaml') { // Use SerializationService for YAML parsing const result = this.serializationService.parseFrontmatter(data, { maxYamlSize: 64 * 1024, validateContent: true, source: 'SkillManager.importElement' }); // Extract metadata and instructions if (result.data.metadata) { metadata = result.data.metadata; instructions = result.data.instructions || result.content || ''; } else { metadata = result.data; instructions = result.data.instructions || result.content || ''; delete metadata.instructions; } } else { // Use SerializationService for JSON parsing const parsed = this.serializationService.parseJson(data, { source: 'SkillManager.importElement' }); if (parsed.metadata) { metadata = parsed.metadata; instructions = parsed.instructions || ''; } else { metadata = parsed; instructions = parsed.instructions || ''; delete metadata.instructions; } } return new Skill(metadata, instructions, this.metadataService); } catch (error) { logger.error('Failed to import skill:', error); throw error; } } /** * Export a skill to YAML or JSON. */ async exportElement(element, format = 'yaml') { if (format === 'yaml') { const data = { metadata: element.metadata, instructions: element.instructions, parameters: Object.fromEntries(element.parameters) }; return this.serializationService.dumpYaml(data, { schema: 'json', // Fix #914: failsafe corrupts booleans/numbers to strings noRefs: true, skipInvalid: true }); } // For JSON, spread metadata properties directly (maintain backward compatibility) const data = { ...element.metadata, instructions: element.instructions, parameters: Object.fromEntries(element.parameters) }; return this.serializationService.stringifyJson(data, { pretty: true, indent: 2 }); } getFileExtension() { return '.md'; } /** * Validate and normalize metadata parsed from frontmatter. */ async parseMetadata(data) { const metadata = { ...data }; if (Array.isArray(data?.triggers)) { const validationResult = this.triggerValidationService.validateTriggers(data.triggers, ElementType.SKILL, metadata.name || 'unknown'); metadata.triggers = validationResult.validTriggers; } // Issue #676: Sanitize gatekeeper policy on load to prevent prompt-injection attacks // Malformed policies are stripped and logged as security events (never reach enforcement) if (metadata.gatekeeper) { metadata.gatekeeper = sanitizeGatekeeperPolicy(metadata.gatekeeper, metadata.name || 'unknown', 'skill', metadata); } return metadata; } /** * Create skill instance from metadata/content. * Dual-field: detects v2 format (instructions in YAML frontmatter) vs v1 (body = instructions). */ createElement(metadata, bodyContent) { // Fix #912: Prefer explicit format_version marker, fall back to instructions-presence check const isV2 = metadata.format_version === 'v2' || !!metadata.instructions; delete metadata.format_version; // Strip marker from runtime metadata const metadataInstructions = metadata.instructions; let instructions; let content; if (isV2 && metadataInstructions) { // v2 format: instructions from YAML, body is reference content instructions = metadataInstructions; content = bodyContent; delete metadata.instructions; } else { // v1 format: body text is instructions instructions = bodyContent; content = ''; } return new Skill(metadata, instructions, this.metadataService, content); } /** * Serialize a skill to markdown with frontmatter. * v2.0 format: instructions in YAML frontmatter, content as body. */ async serializeElement(element) { // Prepare metadata with version and instructions const metadata = { ...element.metadata }; // Issue #755: Serialize type as singular and persist unique_id metadata.type = toSingularLabel(ElementType.SKILL); metadata.unique_id = element.id; if (element.version) { metadata.version = element.version; } // Fix #912: Explicit format marker replaces fragile instructions-presence detection metadata.format_version = 'v2'; // Write instructions to YAML frontmatter (v2.0 dual-field format) if (element.instructions) { metadata.instructions = element.instructions; } // Body is the reference content const body = element.content || this.buildDefaultBody(element); return this.serializationService.createFrontmatter(metadata, body, { method: 'matter', cleanMetadata: true, cleaningStrategy: 'remove-both', // Fix #913: standardize across all managers schema: 'json' // Fix #914: failsafe corrupts booleans/numbers to strings }); } buildDefaultBody(skill) { const name = (skill.metadata.name ?? '').trim(); const description = (skill.metadata.description ?? '').trim(); const lines = []; if (name) { lines.push(`# ${name}`); lines.push(''); } if (description) { lines.push(description); } return lines.join('\n'); } /** * Override list() to apply active status to skills */ async list() { const skills = await super.list(); // Apply active status to skills that are in the active set (by name) for (const skill of skills) { if (this.activeSkillNames.has(skill.metadata.name)) { // Access the protected _status field and set to ACTIVE (value 2) await skill.activate(); } } return skills; } /** * Activate a skill by name or identifier * * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages * Issue #24 (LOW PRIORITY): Cleanup trigger for memory leak prevention */ async activateSkill(identifier) { // PERFORMANCE FIX: Use findByName() instead of list() to avoid loading all skills // This provides O(1) or O(log n) lookup instead of O(n) for large portfolios const skill = await this.findByName(identifier); if (!skill) { return { success: false, // CONSISTENCY FIX: Use standardized error message format message: ElementMessages.notFound(ElementType.SKILL, identifier) }; } // MEMORY LEAK FIX: Check if cleanup is needed before adding this.checkAndCleanupActiveSet(); // Add to active set (by name, which is stable across reloads) this.activeSkillNames.add(skill.metadata.name); // Update skill status in memory await skill.activate(); SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_CREATED', severity: 'LOW', source: 'SkillManager.activateSkill', details: `Skill activated: ${skill.metadata.name}` }); logger.info(`Skill activated: ${skill.metadata.name}`); return { success: true, // CONSISTENCY FIX: Use standardized success message format message: ElementMessages.activated(ElementType.SKILL, skill.metadata.name), skill }; } /** * Deactivate a skill by name or identifier * * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages */ async deactivateSkill(identifier) { // PERFORMANCE FIX: Use findByName() instead of list() const skill = await this.findByName(identifier); if (!skill) { return { success: false, // CONSISTENCY FIX: Use standardized error message format message: ElementMessages.notFound(ElementType.SKILL, identifier) }; } // Remove from active set this.activeSkillNames.delete(skill.metadata.name); // Update skill status in memory await skill.deactivate(); logger.info(`Skill deactivated: ${skill.metadata.name}`); return { success: true, // CONSISTENCY FIX: Use standardized success message format message: ElementMessages.deactivated(ElementType.SKILL, skill.metadata.name) }; } /** * Get all active skills */ async getActiveSkills() { const results = []; for (const name of this.activeSkillNames) { const skill = await this.findByName(name); if (skill) results.push(skill); } return results; } /** * Check if active set cleanup is needed and perform cleanup if necessary * * Issue #24 (LOW PRIORITY): Memory leak prevention * * CLEANUP STRATEGY: * - Triggers when set reaches 90% of maximum capacity * - Validates that all active skills still exist in portfolio * - Removes stale references (skills that were deleted) * - Logs cleanup operations for monitoring * * PERFORMANCE NOTES: * - Only runs when threshold is reached (not on every activation) * - Uses efficient Set operations * - Minimal impact on activation performance * * @private */ checkAndCleanupActiveSet() { const { max, cleanupThreshold } = getActiveElementLimitConfig('skills'); // Below threshold — no action needed if (this.activeSkillNames.size < cleanupThreshold) { return; } // At or above max — warn before cleanup if (this.activeSkillNames.size >= max) { logger.warn(`Active skills limit reached (${max}). ` + `Consider deactivating unused skills or setting DOLLHOUSE_MAX_ACTIVE_SKILLS to a higher value.`); SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_CREATED', severity: 'MEDIUM', source: 'SkillManager.checkAndCleanupActiveSet', details: `Active skills limit reached: ${this.activeSkillNames.size}/${max}` }); } // At or above threshold — proactively clean stale entries void this.cleanupStaleActiveSkills(); } /** * Clean up stale entries from active skills set * * Issue #24 (LOW PRIORITY): Memory leak prevention * * Validates that all active skills still exist and removes orphaned references. * This prevents memory leaks from deleted skills that weren't properly deactivated. * * @private */ async cleanupStaleActiveSkills() { try { const startSize = this.activeSkillNames.size; const skills = await this.list(); const existingSkillNames = new Set(skills.map(s => s.metadata.name)); // Remove any active skill names that no longer exist in portfolio const staleNames = []; for (const activeName of this.activeSkillNames) { if (!existingSkillNames.has(activeName)) { this.activeSkillNames.delete(activeName); staleNames.push(activeName); } } const endSize = this.activeSkillNames.size; const removed = startSize - endSize; if (removed > 0) { logger.info(`Cleaned up ${removed} stale active skill reference(s). ` + `Active skills: ${endSize}/${getMaxActiveLimit('skills')}`); SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_DELETED', severity: 'LOW', source: 'SkillManager.cleanupStaleActiveSkills', details: `Removed ${removed} stale active skill references`, additionalData: { removedCount: removed, activeCount: endSize, staleNames: staleNames.join(', ') } }); } } catch (error) { // Log error but don't throw - cleanup failures shouldn't break activation logger.error('Failed to cleanup stale active skills:', error); SecurityMonitor.logSecurityEvent({ type: 'ELEMENT_DELETED', severity: 'LOW', source: 'SkillManager.cleanupStaleActiveSkills', details: `Cleanup failed: ${error instanceof Error ? error.message : String(error)}` }); } } /** * Normalize and validate skill triggers. */ validateAndProcessTriggers(triggers, skillName) { const validTriggers = []; const rejectedTriggers = []; const rawTriggers = triggers.slice(0, 20); // SECURITY: Validate BEFORE sanitization to reject invalid characters // This prevents 'bad!trigger' from becoming 'badtrigger' and passing for (const raw of rawTriggers) { const rawTrigger = String(raw).trim(); // Check if empty if (!rawTrigger) { rejectedTriggers.push(`"${raw}" (empty)`); continue; } // SECURITY: Validate format BEFORE sanitization if (!TRIGGER_VALIDATION_REGEX.test(rawTrigger)) { rejectedTriggers.push(`"${raw}" (invalid format - allowed: letters, numbers, hyphens, underscores, @ and .)`); continue; } // Only sanitize AFTER validation passes (for length limits) const sanitized = sanitizeInput(rawTrigger, MAX_TRIGGER_LENGTH); if (sanitized) { validTriggers.push(sanitized); } } if (rejectedTriggers.length > 0) { logger.warn(`Skill "${skillName}": Rejected ${rejectedTriggers.length} invalid trigger(s)`, { skillName, rejectedTriggers, acceptedCount: validTriggers.length }); } if (triggers.length > 20) { logger.warn(`Skill "${skillName}": Trigger limit exceeded`, { skillName, providedCount: triggers.length, limit: 20, truncated: triggers.length - 20 }); } return validTriggers; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2tpbGxNYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VsZW1lbnRzL3NraWxscy9Ta2lsbE1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7R0FJRztBQUVILE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ25FLE9BQU8sRUFBRSxLQUFLLEVBQWlCLE1BQU0sWUFBWSxDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDMUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ3BFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUVqRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFTL0MsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLG9EQUFvRCxDQUFDO0FBQzlGLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUU5RCwwQ0FBMEM7QUFDMUMsTUFBTSxrQkFBa0IsR0FBRyxFQUFFLENBQUM7QUFDOUIsOEVBQThFO0FBQzlFLE1BQU0sd0JBQXdCLEdBQUcscUJBQXFCLENBQUM7QUFFdkQsMkVBQTJFO0FBQzNFLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBRXZHLE1BQU0sT0FBTyxZQUFhLFNBQVEsa0JBQXlCO0lBYS9DO0lBWkYsd0JBQXdCLENBQTJCO0lBQ25ELGlCQUFpQixDQUFvQjtJQUNyQyxvQkFBb0IsQ0FBdUI7SUFDbkQsa0RBQWtEO0lBQzFDLGdCQUFnQixHQUFnQixJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRWxELFlBQ0UsZ0JBQWtDLEVBQ2xDLGVBQWdDLEVBQ2hDLHFCQUE0QyxFQUM1QyxrQkFBc0MsRUFDdEMsb0JBQTBDLEVBQ2xDLGVBQWdDLEVBQ3hDLGdCQUFtQyxFQUNuQyxZQUEyRSxFQUMzRSxhQUF1RTtRQUV2RSxLQUFLLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxnQkFBZ0IsRUFBRSxlQUFlLEVBQUUsRUFBRSxnQkFBZ0IsRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLEVBQUUscUJBQXFCLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztRQUxsSixvQkFBZSxHQUFmLGVBQWUsQ0FBaUI7UUFNeEMsSUFBSSxDQUFDLHdCQUF3QixHQUFHLGtCQUFrQixDQUFDLDJCQUEyQixFQUFFLENBQUM7UUFDakYsSUFBSSxDQUFDLGlCQUFpQixHQUFHLGtCQUFrQixDQUFDLG9CQUFvQixFQUFFLENBQUM7UUFDbkUsSUFBSSxDQUFDLG9CQUFvQixHQUFHLG9CQUFvQixDQUFDO0lBQ25ELENBQUM7SUFFa0IsZUFBZTtRQUNoQyxPQUFPLE9BQU8sQ0FBQztJQUNqQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLE1BQU0sQ0FBQyxJQUEwRTtRQUNyRixpREFBaUQ7UUFDakQsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRW5FLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUM5QixNQUFNLElBQUksS0FBSyxDQUFDLHNCQUFzQixnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUM5RSxDQUFDO1FBRUQsc0JBQXNCO1FBQ3RCLElBQUksZ0JBQWdCLENBQUMsUUFBUSxJQUFJLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDdEUsTUFBTSxDQUFDLElBQUksQ0FBQyw0QkFBNEIsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDbEYsQ0FBQztRQUVELHdHQUF3RztRQUN4RyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsSUFBSSxJQUFJLFdBQVcsQ0FBQztRQUMzQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsd0JBQXdCLENBQUMsU0FBUyxFQUFFO1lBQzVFLFNBQVMsRUFBRSxlQUFlLENBQUMsZUFBZTtZQUMxQyxXQUFXLEVBQUUsSUFBSTtTQUNsQixDQUFDLENBQUM7UUFDSCxNQUFNLGFBQWEsR0FBRyxVQUFVLENBQUMsY0FBZSxDQUFDO1FBRWpELDJFQUEyRTtRQUMzRSxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsYUFBYSxDQUFDLENBQUM7UUFFeEQsdURBQXVEO1FBQ3ZELE1BQU0sY0FBYyxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3pDLE1BQU0sU0FBUyxHQUFHLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FDeEMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLEtBQUssYUFBYSxDQUFDLFdBQVcsRUFBRSxDQUM5RCxDQUFDO1FBRUYsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsa0JBQWtCLGFBQWEsa0JBQWtCLENBQUMsQ0FBQztRQUNyRSxDQUFDO1FBRUQsTUFBTSxFQUFFLE9BQU8sRUFBRSxZQUFZLEVBQUUsR0FBRyxRQUFRLEVBQUUsR0FBRyxJQUFJLENBQUM7UUFDcEQsaUZBQWlGO1FBQ2pGLHlFQUF5RTtRQUN6RSxNQUFNLHFCQUFxQixHQUFHLFlBQVksSUFBSSxPQUFPLElBQUksRUFBRSxDQUFDO1FBQzVELE1BQU0sZ0JBQWdCLEdBQUcsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzdELE1BQU0sS0FBSyxHQUFHLElBQUksS0FBSyxDQUNyQjtZQUNFLEdBQUcsUUFBUTtZQUNYLElBQUksRUFBRSxhQUFhO1lBQ25CLFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBVyxJQUFJLEVBQUU7U0FDcEMsRUFDRCxxQkFBcUIsRUFDckIsSUFBSSxDQUFDLGVBQWUsRUFDcEIsZ0JBQWdCLENBQ2pCLENBQUM7UUFFRixNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ2pDLGdFQUFnRTtRQUNoRSwyRUFBMkU7UUFFM0UsZUFBZSxDQUFDLGdCQUFnQixDQUFDO1lBQy9CLElBQUksRUFBRSxpQkFBaUI7WUFDdkIsUUFBUSxFQUFFLEtBQUs7WUFDZixNQUFNLEVBQUUscUJBQXFCO1lBQzdCLE9BQU8sRUFBRSxrQkFBa0IsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUU7U0FDakQsQ0FBQyxDQUFDO1FBRUgsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQ7O09BRUc7SUFDSCxLQUFLLENBQUMsYUFBYSxDQUFDLElBQVksRUFBRSxTQUEwQixNQUFNO1FBQ2hFLElBQUksQ0FBQztZQUNILElBQUksUUFBYSxDQUFDO1lBQ2xCLElBQUksWUFBb0IsQ0FBQztZQUV6QixJQUFJLE1BQU0sS0FBSyxNQUFNLEVBQUUsQ0FBQztnQkFDdEIsNENBQTRDO2dCQUM1QyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsb0JBQW9CLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFO29CQUM5RCxXQUFXLEVBQUUsRUFBRSxHQUFHLElBQUk7b0JBQ3RCLGVBQWUsRUFBRSxJQUFJO29CQUNyQixNQUFNLEVBQUUsNEJBQTRCO2lCQUNyQyxDQUFDLENBQUM7Z0JBRUgsb0NBQW9DO2dCQUNwQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7b0JBQ3pCLFFBQVEsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQztvQkFDaEMsWUFBWSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxJQUFJLE1BQU0sQ0FBQyxPQUFPLElBQUksRUFBRSxDQUFDO2dCQUNsRSxDQUFDO3FCQUFNLENBQUM7b0JBQ04sUUFBUSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUM7b0JBQ3ZCLFlBQVksR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksSUFBSSxNQUFNLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQztvQkFDaEUsT0FBTyxRQUFRLENBQUMsWUFBWSxDQUFDO2dCQUMvQixDQUFDO1lBQ0gsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLDRDQUE0QztnQkFDNUMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLG9CQUFvQixDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUU7b0JBQ3ZELE1BQU0sRUFBRSw0QkFBNEI7aUJBQ3JDLENBQUMsQ0FBQztnQkFFSCxJQUFJLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQkFDcEIsUUFBUSxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUM7b0JBQzNCLFlBQVksR0FBRyxNQUFNLENBQUMsWUFBWSxJQUFJLEVBQUUsQ0FBQztnQkFDM0MsQ0FBQztxQkFBTSxDQUFDO29CQUNOLFFBQVEsR0FBRyxNQUFNLENBQUM7b0JBQ2xCLFlBQVksR0FBRyxNQUFNLENBQUMsWUFBWSxJQUFJLEVBQUUsQ0FBQztvQkFDekMsT0FBTyxRQUFRLENBQUMsWUFBWSxDQUFDO2dCQUMvQixDQUFDO1lBQ0gsQ0FBQztZQUVELE9BQU8sSUFBSSxLQUFLLENBQUMsUUFBUSxFQUFFLFlBQVksRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLENBQUMsS0FBSyxDQUFDLHlCQUF5QixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQy9DLE1BQU0sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxhQUFhLENBQUMsT0FBYyxFQUFFLFNBQTBCLE1BQU07UUFDbEUsSUFBSSxNQUFNLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDdEIsTUFBTSxJQUFJLEdBQUc7Z0JBQ1gsUUFBUSxFQUFFLE9BQU8sQ0FBQyxRQUFRO2dCQUMxQixZQUFZLEVBQUUsT0FBTyxDQUFDLFlBQVk7Z0JBQ2xDLFVBQVUsRUFBRSxNQUFNLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7YUFDbkQsQ0FBQztZQUVGLE9BQU8sSUFBSSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUU7Z0JBQzlDLE1BQU0sRUFBRSxNQUFNLEVBQUcsMERBQTBEO2dCQUMzRSxNQUFNLEVBQUUsSUFBSTtnQkFDWixXQUFXLEVBQUUsSUFBSTthQUNsQixDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsa0ZBQWtGO1FBQ2xGLE1BQU0sSUFBSSxHQUFHO1lBQ1gsR0FBRyxPQUFPLENBQUMsUUFBUTtZQUNuQixZQUFZLEVBQUUsT0FBTyxDQUFDLFlBQVk7WUFDbEMsVUFBVSxFQUFFLE1BQU0sQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQztTQUNuRCxDQUFDO1FBRUYsT0FBTyxJQUFJLENBQUMsb0JBQW9CLENBQUMsYUFBYSxDQUFDLElBQUksRUFBRTtZQUNuRCxNQUFNLEVBQUUsSUFBSTtZQUNaLE1BQU0sRUFBRSxDQUFDO1NBQ1YsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELGdCQUFnQjtRQUNkLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVEOztPQUVHO0lBQ08sS0FBSyxDQUFDLGFBQWEsQ0FBQyxJQUFTO1FBQ3JDLE1BQU0sUUFBUSxHQUFHLEVBQUUsR0FBSSxJQUFzQixFQUFFLENBQUM7UUFFaEQsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ2xDLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLHdCQUF3QixDQUFDLGdCQUFnQixDQUNyRSxJQUFJLENBQUMsUUFBUSxFQUNiLFdBQVcsQ0FBQyxLQUFLLEVBQ2pCLFFBQVEsQ0FBQyxJQUFJLElBQUksU0FBUyxDQUMzQixDQUFDO1lBQ0YsUUFBUSxDQUFDLFFBQVEsR0FBRyxnQkFBZ0IsQ0FBQyxhQUFhLENBQUM7UUFDckQsQ0FBQztRQUVELHFGQUFxRjtRQUNyRiwwRkFBMEY7UUFDMUYsSUFBSSxRQUFRLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDeEIsUUFBUSxDQUFDLFVBQVUsR0FBRyx3QkFBd0IsQ0FBQyxRQUFRLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQyxJQUFJLElBQUksU0FBUyxFQUFFLE9BQU8sRUFBRSxRQUFtQyxDQUFDLENBQUM7UUFDaEosQ0FBQztRQUVELE9BQU8sUUFBUSxDQUFDO0lBQ2xCLENBQUM7SUFFRDs7O09BR0c7SUFDTyxhQUFhLENBQUMsUUFBdUIsRUFBRSxXQUFtQjtRQUNsRSw0RkFBNEY7UUFDNUYsTUFBTSxJQUFJLEdBQUksUUFBZ0IsQ0FBQyxjQUFjLEtBQUssSUFBSSxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDO1FBQ2xGLE9BQVEsUUFBZ0IsQ0FBQyxjQUFjLENBQUMsQ0FBRSxxQ0FBcUM7UUFDL0UsTUFBTSxvQkFBb0IsR0FBRyxRQUFRLENBQUMsWUFBWSxDQUFDO1FBQ25ELElBQUksWUFBb0IsQ0FBQztRQUN6QixJQUFJLE9BQWUsQ0FBQztRQUVwQixJQUFJLElBQUksSUFBSSxvQkFBb0IsRUFBRSxDQUFDO1lBQ2pDLCtEQUErRDtZQUMvRCxZQUFZLEdBQUcsb0JBQW9CLENBQUM7WUFDcEMsT0FBTyxHQUFHLFdBQVcsQ0FBQztZQUN0QixPQUFPLFFBQVEsQ0FBQyxZQUFZLENBQUM7UUFDL0IsQ0FBQzthQUFNLENBQUM7WUFDTix1Q0FBdUM7WUFDdkMsWUFBWSxHQUFHLFdBQVcsQ0FBQztZQUMzQixPQUFPLEdBQUcsRUFBRSxDQUFDO1FBQ2YsQ0FBQztRQUVELE9BQU8sSUFBSSxLQUFLLENBQUMsUUFBUSxFQUFFLFlBQVksRUFBRSxJQUFJLENBQUMsZUFBZSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzFFLENBQUM7SUFFRDs7O09BR0c7SUFDTyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsT0FBYztRQUM3QyxpREFBaUQ7UUFDakQsTUFBTSxRQUFRLEdBQXdCLEVBQUUsR0FBRyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDOUQsK0RBQStEO1FBQy9ELFFBQVEsQ0FBQyxJQUFJLEdBQUcsZUFBZSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNuRCxRQUFRLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDaEMsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDcEIsUUFBUSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDO1FBQ3JDLENBQUM7UUFDRCxvRkFBb0Y7UUFDcEYsUUFBUSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUM7UUFDL0Isa0VBQWtFO1FBQ2xFLElBQUksT0FBTyxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3pCLFFBQVEsQ0FBQyxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQztRQUMvQyxDQUFDO1FBRUQsZ0NBQWdDO1FBQ2hDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRS9ELE9BQU8sSUFBSSxDQUFDLG9CQUFvQixDQUFDLGlCQUFpQixDQUFDLFFBQVEsRUFBRSxJQUFJLEVBQUU7WUFDakUsTUFBTSxFQUFFLFFBQVE7WUFDaEIsYUFBYSxFQUFFLElBQUk7WUFDbkIsZ0JBQWdCLEVBQUUsYUFBYSxFQUFHLDRDQUE0QztZQUM5RSxNQUFNLEVBQUUsTUFBTSxDQUFFLDBEQUEwRDtTQUMzRSxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRU8sZ0JBQWdCLENBQUMsS0FBWTtRQUNuQyxNQUFNLElBQUksR0FBRyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ2hELE1BQU0sV0FBVyxHQUFHLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxXQUFXLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDOUQsTUFBTSxLQUFLLEdBQWEsRUFBRSxDQUFDO1FBQzNCLElBQUksSUFBSSxFQUFFLENBQUM7WUFDVCxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQztZQUN4QixLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ2pCLENBQUM7UUFDRCxJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQ2hCLEtBQUssQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDMUIsQ0FBQztRQUNELE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMxQixDQUFDO0lBRUQ7O09BRUc7SUFDTSxLQUFLLENBQUMsSUFBSTtRQUNqQixNQUFNLE1BQU0sR0FBRyxNQUFNLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUVsQyxxRUFBcUU7UUFDckUsS0FBSyxNQUFNLEtBQUssSUFBSSxNQUFNLEVBQUUsQ0FBQztZQUMzQixJQUFJLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO2dCQUNuRCxpRUFBaUU7Z0JBQ2pFLE1BQU0sS0FBSyxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3pCLENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxhQUFhLENBQUMsVUFBa0I7UUFDcEMsa0ZBQWtGO1FBQ2xGLDZFQUE2RTtRQUM3RSxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsVUFBVSxDQUFDLENBQUM7UUFFaEQsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ1gsT0FBTztnQkFDTCxPQUFPLEVBQUUsS0FBSztnQkFDZCx5REFBeUQ7Z0JBQ3pELE9BQU8sRUFBRSxlQUFlLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUUsVUFBVSxDQUFDO2FBQ2pFLENBQUM7UUFDSixDQUFDO1FBRUQsNERBQTREO1FBQzVELElBQUksQ0FBQyx3QkFBd0IsRUFBRSxDQUFDO1FBRWhDLDhEQUE4RDtRQUM5RCxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFL0MsZ0NBQWdDO1FBQ2hDLE1BQU0sS0FBSyxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRXZCLGVBQWUsQ0FBQyxnQkFBZ0IsQ0FBQztZQUMvQixJQUFJLEVBQUUsaUJBQWlCO1lBQ3ZCLFFBQVEsRUFBRSxLQUFLO1lBQ2YsTUFBTSxFQUFFLDRCQUE0QjtZQUNwQyxPQUFPLEVBQUUsb0JBQW9CLEtBQUssQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFO1NBQ25ELENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEtBQUssQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUV2RCxPQUFPO1lBQ0wsT0FBTyxFQUFFLElBQUk7WUFDYiwyREFBMkQ7WUFDM0QsT0FBTyxFQUFFLGVBQWUsQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQztZQUMxRSxLQUFLO1NBQ04sQ0FBQztJQUNKLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILEtBQUssQ0FBQyxlQUFlLENBQUMsVUFBa0I7UUFDdEMsc0RBQXNEO1FBQ3RELE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUVoRCxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDWCxPQUFPO2dCQUNMLE9BQU8sRUFBRSxLQUFLO2dCQUNkLHlEQUF5RDtnQkFDekQsT0FBTyxFQUFFLGVBQWUsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxVQUFVLENBQUM7YUFDakUsQ0FBQztRQUNKLENBQUM7UUFFRCx5QkFBeUI7UUFDekIsSUFBSSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRWxELGdDQUFnQztRQUNoQyxNQUFNLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUV6QixNQUFNLENBQUMsSUFBSSxDQUFDLHNCQUFzQixLQUFLLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFFekQsT0FBTztZQUNMLE9BQU8sRUFBRSxJQUFJO1lBQ2IsMkRBQTJEO1lBQzNELE9BQU8sRUFBRSxlQUFlLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7U0FDN0UsQ0FBQztJQUNKLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxlQUFlO1FBQ25CLE1BQU0sT0FBTyxHQUFZLEVBQUUsQ0FBQztRQUM1QixLQUFLLE1BQU0sSUFBSSxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1lBQ3pDLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMxQyxJQUFJLEtBQUs7Z0JBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNqQyxDQUFDO1FBQ0QsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7OztPQWlCRztJQUNLLHdCQUF3QjtRQUM5QixNQUFNLEVBQUUsR0FBRyxFQUFFLGdCQUFnQixFQUFFLEdBQUcsMkJBQTJCLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFeEUscUNBQXFDO1FBQ3JDLElBQUksSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO1lBQ2xELE9BQU87UUFDVCxDQUFDO1FBRUQsd0NBQXdDO1FBQ3hDLElBQUksSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksSUFBSSxHQUFHLEVBQUUsQ0FBQztZQUN0QyxNQUFNLENBQUMsSUFBSSxDQUNULGdDQUFnQyxHQUFHLEtBQUs7Z0JBQ3hDLCtGQUErRixDQUNoRyxDQUFDO1lBRUYsZUFBZSxDQUFDLGdCQUFnQixDQUFDO2dCQUMvQixJQUFJLEVBQUUsaUJBQWlCO2dCQUN2QixRQUFRLEVBQUUsUUFBUTtnQkFDbEIsTUFBTSxFQUFFLHVDQUF1QztnQkFDL0MsT0FBTyxFQUFFLGdDQUFnQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxJQUFJLEdBQUcsRUFBRTthQUM3RSxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsMERBQTBEO1FBQzFELEtBQUssSUFBSSxDQUFDLHdCQUF3QixFQUFFLENBQUM7SUFDdkMsQ0FBQztJQUVEOzs7Ozs7Ozs7T0FTRztJQUNLLEtBQUssQ0FBQyx3QkFBd0I7UUFDcEMsSUFBSSxDQUFDO1lBQ0gsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQztZQUM3QyxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNqQyxNQUFNLGtCQUFrQixHQUFHLElBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFFckUsa0VBQWtFO1lBQ2xFLE1BQU0sVUFBVSxHQUFhLEVBQUUsQ0FBQztZQUNoQyxLQUFLLE1BQU0sVUFBVSxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO2dCQUMvQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7b0JBQ3hDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7b0JBQ3pDLFVBQVUsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7Z0JBQzlCLENBQUM7WUFDSCxDQUFDO1lBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQztZQUMzQyxNQUFNLE9BQU8sR0FBRyxTQUFTLEdBQUcsT0FBTyxDQUFDO1lBRXBDLElBQUksT0FBTyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNoQixNQUFNLENBQUMsSUFBSSxDQUNULGNBQWMsT0FBTyxvQ0FBb0M7b0JBQ3pELGtCQUFrQixPQUFPLElBQUksaUJBQWlCLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FDM0QsQ0FBQztnQkFFRixlQUFlLENBQUMsZ0JBQWdCLENBQUM7b0JBQy9CLElBQUksRUFBRSxpQkFBaUI7b0JBQ3ZCLFFBQVEsRUFBRSxLQUFLO29CQUNmLE1BQU0sRUFBRSx1Q0FBdUM7b0JBQy9DLE9BQU8sRUFBRSxXQUFXLE9BQU8sZ0NBQWdDO29CQUMzRCxjQUFjLEVBQUU7d0JBQ2QsWUFBWSxFQUFFLE9BQU87d0JBQ3JCLFdBQVcsRUFBRSxPQUFPO3dCQUNwQixVQUFVLEVBQUUsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7cUJBQ2xDO2lCQUNGLENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLDBFQUEwRTtZQUMxRSxNQUFNLENBQUMsS0FBSyxDQUFDLHdDQUF3QyxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBRTlELGVBQWUsQ0FBQyxnQkFBZ0IsQ0FBQztnQkFDL0IsSUFBSSxFQUFFLGlCQUFpQjtnQkFDdkIsUUFBUSxFQUFFLEtBQUs7Z0JBQ2YsTUFBTSxFQUFFLHVDQUF1QztnQkFDL0MsT0FBTyxFQUFFLG1CQUFtQixLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUU7YUFDckYsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNLLDBCQUEwQixDQUFDLFFBQWUsRUFBRSxTQUFpQjtRQUNuRSxNQUFNLGFBQWEsR0FBYSxFQUFFLENBQUM7UUFDbkMsTUFBTSxnQkFBZ0IsR0FBYSxFQUFFLENBQUM7UUFDdEMsTUFBTSxXQUFXLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFMUMsc0VBQXNFO1FBQ3RFLHFFQUFxRTtRQUNyRSxLQUFLLE1BQU0sR0FBRyxJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQzlCLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUV0QyxpQkFBaUI7WUFDakIsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNoQixnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFdBQVcsQ0FBQyxDQUFDO2dCQUMxQyxTQUFTO1lBQ1gsQ0FBQztZQUVELGdEQUFnRDtZQUNoRCxJQUFJLENBQUMsd0JBQXdCLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7Z0JBQy9DLGdCQUFnQixDQUFDLElBQUksQ0FDbkIsSUFBSSxHQUFHLCtFQUErRSxDQUN2RixDQUFDO2dCQUNGLFNBQVM7WUFDWCxDQUFDO1lBRUQsNERBQTREO1lBQzVELE1BQU0sU0FBUyxHQUFHLGFBQWEsQ0FBQyxVQUFVLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztZQUNoRSxJQUFJLFNBQVMsRUFBRSxDQUFDO2dCQUNkLGFBQWEsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDaEMsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLGdCQUFnQixDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNoQyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsU0FBUyxlQUFlLGdCQUFnQixDQUFDLE1BQU0scUJBQXFCLEVBQUU7Z0JBQzFGLFNBQVM7Z0JBQ1QsZ0JBQWdCO2dCQUNoQixhQUFhLEVBQUUsYUFBYSxDQUFDLE1BQU07YUFDcEMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELElBQUksUUFBUSxDQUFDLE1BQU0sR0FBRyxFQUFFLEVBQUUsQ0FBQztZQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsU0FBUywyQkFBMkIsRUFBRTtnQkFDMUQsU0FBUztnQkFDVCxhQUFhLEVBQUUsUUFBUSxDQUFDLE1BQU07Z0JBQzlCLEtBQUssRUFBRSxFQUFFO2dCQUNULFNBQVMsRUFBRSxRQUFRLENBQUMsTUFBTSxHQUFHLEVBQUU7YUFDaEMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUVELE9BQU8sYUFBYSxDQUFDO0lBQ3ZCLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2tpbGxNYW5hZ2VyIC0gUmVmYWN0b3JlZCB0byBleHRlbmQgQmFzZUVsZW1lbnRNYW5hZ2VyIGZvciBzaGFyZWQgQ1JVRCBsb2dpYy5cbiAqIE1haW50YWlucyBza2lsbC1zcGVjaWZpYyBiZWhhdmlvcnMgc3VjaCBhcyB0cmlnZ2VyIHZhbGlkYXRpb24sIGltcG9ydC9leHBvcnQsXG4gKiBhbmQgY3JlYXRpb24gd29ya2Zsb3dzIHdoaWxlIGVsaW1pbmF0aW5nIGR1cGxpY2F0ZWQgZmlsZSBoYW5kbGluZyBjb2RlLlxuICovXG5cbmltcG9ydCB7IEJhc2VFbGVtZW50TWFuYWdlciB9IGZyb20gJy4uL2Jhc2UvQmFzZUVsZW1lbnRNYW5hZ2VyLmpzJztcbmltcG9ydCB7IFNraWxsLCBTa2lsbE1ldGFkYXRhIH0gZnJvbSAnLi9Ta2lsbC5qcyc7XG5pbXBvcnQgeyBFbGVtZW50VHlwZSB9IGZyb20gJy4uLy4uL3BvcnRmb2xpby90eXBlcy5qcyc7XG5pbXBvcnQgeyB0b1Npbmd1bGFyTGFiZWwgfSBmcm9tICcuLi8uLi91dGlscy9lbGVtZW50VHlwZU5vcm1hbGl6YXRpb24uanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlNb25pdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvc2VjdXJpdHlNb25pdG9yLmpzJztcbmltcG9ydCB7IHNhbml0aXplSW5wdXQgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9JbnB1dFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBGaWxlTG9ja01hbmFnZXIgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9maWxlTG9ja01hbmFnZXIuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFBvcnRmb2xpb01hbmFnZXIgfSBmcm9tICcuLi8uLi9wb3J0Zm9saW8vUG9ydGZvbGlvTWFuYWdlci5qcyc7XG5pbXBvcnQgeyBWYWxpZGF0aW9uUmVnaXN0cnkgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy92YWxpZGF0aW9uL1ZhbGlkYXRpb25SZWdpc3RyeS5qcyc7XG5pbXBvcnQgeyBUcmlnZ2VyVmFsaWRhdGlvblNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy92YWxpZGF0aW9uL1RyaWdnZXJWYWxpZGF0aW9uU2VydmljZS5qcyc7XG5pbXBvcnQgeyBWYWxpZGF0aW9uU2VydmljZSB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL3ZhbGlkYXRpb24vVmFsaWRhdGlvblNlcnZpY2UuanMnO1xuaW1wb3J0IHsgU2VyaWFsaXphdGlvblNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9TZXJpYWxpemF0aW9uU2VydmljZS5qcyc7XG5pbXBvcnQgeyBNZXRhZGF0YVNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9NZXRhZGF0YVNlcnZpY2UuanMnO1xuaW1wb3J0IHsgRmlsZU9wZXJhdGlvbnNTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvRmlsZU9wZXJhdGlvbnNTZXJ2aWNlLmpzJztcbmltcG9ydCB7IEZpbGVXYXRjaFNlcnZpY2UgfSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9GaWxlV2F0Y2hTZXJ2aWNlLmpzJztcbmltcG9ydCB7IEVsZW1lbnRNZXNzYWdlcyB9IGZyb20gJy4uLy4uL3V0aWxzL2VsZW1lbnRNZXNzYWdlcy5qcyc7XG5pbXBvcnQgeyBzYW5pdGl6ZUdhdGVrZWVwZXJQb2xpY3kgfSBmcm9tICcuLi8uLi9oYW5kbGVycy9tY3AtYXFsL3BvbGljaWVzL0VsZW1lbnRQb2xpY2llcy5qcyc7XG5pbXBvcnQgeyBTRUNVUklUWV9MSU1JVFMgfSBmcm9tICcuLi8uLi9zZWN1cml0eS9jb25zdGFudHMuanMnO1xuXG4vLyBWYWxpZGF0aW9uIGNvbnN0YW50cyBmb3Igc2tpbGwgdHJpZ2dlcnNcbmNvbnN0IE1BWF9UUklHR0VSX0xFTkdUSCA9IDUwO1xuLy8gQWxsb3dzIGFscGhhbnVtZXJpYywgaHlwaGVucywgdW5kZXJzY29yZXMsIEAgKG1lbnRpb25zL2VtYWlscyksIC4gKGRvbWFpbnMpXG5jb25zdCBUUklHR0VSX1ZBTElEQVRJT05fUkVHRVggPSAvXlthLXpBLVowLTlcXC1fQC5dKyQvO1xuXG4vLyBJc3N1ZSAjODM6IENlbnRyYWxpemVkIGFjdGl2ZSBlbGVtZW50IGxpbWl0cyAoY29uZmlndXJhYmxlIHZpYSBlbnYgdmFycylcbmltcG9ydCB7IGdldEFjdGl2ZUVsZW1lbnRMaW1pdENvbmZpZywgZ2V0TWF4QWN0aXZlTGltaXQgfSBmcm9tICcuLi8uLi9jb25maWcvYWN0aXZlLWVsZW1lbnQtbGltaXRzLmpzJztcblxuZXhwb3J0IGNsYXNzIFNraWxsTWFuYWdlciBleHRlbmRzIEJhc2VFbGVtZW50TWFuYWdlcjxTa2lsbD4ge1xuICBwcml2YXRlIHRyaWdnZXJWYWxpZGF0aW9uU2VydmljZTogVHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlO1xuICBwcml2YXRlIHZhbGlkYXRpb25TZXJ2aWNlOiBWYWxpZGF0aW9uU2VydmljZTtcbiAgcHJpdmF0ZSBzZXJpYWxpemF0aW9uU2VydmljZTogU2VyaWFsaXphdGlvblNlcnZpY2U7XG4gIC8vIFRyYWNrIGFjdGl2ZSBza2lsbHMgYnkgbmFtZSAoc3RhYmxlIGlkZW50aWZpZXIpXG4gIHByaXZhdGUgYWN0aXZlU2tpbGxOYW1lczogU2V0PHN0cmluZz4gPSBuZXcgU2V0KCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcG9ydGZvbGlvTWFuYWdlcjogUG9ydGZvbGlvTWFuYWdlcixcbiAgICBmaWxlTG9ja01hbmFnZXI6IEZpbGVMb2NrTWFuYWdlcixcbiAgICBmaWxlT3BlcmF0aW9uc1NlcnZpY2U6IEZpbGVPcGVyYXRpb25zU2VydmljZSxcbiAgICB2YWxpZGF0aW9uUmVnaXN0cnk6IFZhbGlkYXRpb25SZWdpc3RyeSxcbiAgICBzZXJpYWxpemF0aW9uU2VydmljZTogU2VyaWFsaXphdGlvblNlcnZpY2UsXG4gICAgcHJpdmF0ZSBtZXRhZGF0YVNlcnZpY2U6IE1ldGFkYXRhU2VydmljZSxcbiAgICBmaWxlV2F0Y2hTZXJ2aWNlPzogRmlsZVdhdGNoU2VydmljZSxcbiAgICBtZW1vcnlCdWRnZXQ/OiBpbXBvcnQoJy4uLy4uL2NhY2hlL0NhY2hlTWVtb3J5QnVkZ2V0LmpzJykuQ2FjaGVNZW1vcnlCdWRnZXQsXG4gICAgYmFja3VwU2VydmljZT86IGltcG9ydCgnLi4vLi4vc2VydmljZXMvQmFja3VwU2VydmljZS5qcycpLkJhY2t1cFNlcnZpY2VcbiAgKSB7XG4gICAgc3VwZXIoRWxlbWVudFR5cGUuU0tJTEwsIHBvcnRmb2xpb01hbmFnZXIsIGZpbGVMb2NrTWFuYWdlciwgeyBmaWxlV2F0Y2hTZXJ2aWNlLCBtZW1vcnlCdWRnZXQsIGJhY2t1cFNlcnZpY2UgfSwgZmlsZU9wZXJhdGlvbnNTZXJ2aWNlLCB2YWxpZGF0aW9uUmVnaXN0cnkpO1xuICAgIHRoaXMudHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlID0gdmFsaWRhdGlvblJlZ2lzdHJ5LmdldFRyaWdnZXJWYWxpZGF0aW9uU2VydmljZSgpO1xuICAgIHRoaXMudmFsaWRhdGlvblNlcnZpY2UgPSB2YWxpZGF0aW9uUmVnaXN0cnkuZ2V0VmFsaWRhdGlvblNlcnZpY2UoKTtcbiAgICB0aGlzLnNlcmlhbGl6YXRpb25TZXJ2aWNlID0gc2VyaWFsaXphdGlvblNlcnZpY2U7XG4gIH1cblxuICBwcm90ZWN0ZWQgb3ZlcnJpZGUgZ2V0RWxlbWVudExhYmVsKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuICdza2lsbCc7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlIGEgbmV3IHNraWxsIGVsZW1lbnQgYW5kIHBlcnNpc3QgaXQgdG8gZGlzay5cbiAgICogRklYOiBJc3N1ZSAjMjAgLSBBZGQgZHVwbGljYXRlIG5hbWUgY2hlY2tpbmdcbiAgICovXG4gIGFzeW5jIGNyZWF0ZShkYXRhOiBQYXJ0aWFsPFNraWxsTWV0YWRhdGE+ICYgeyBjb250ZW50Pzogc3RyaW5nOyBpbnN0cnVjdGlvbnM/OiBzdHJpbmcgfSk6IFByb21pc2U8U2tpbGw+IHtcbiAgICAvLyBVc2Ugc3BlY2lhbGl6ZWQgdmFsaWRhdG9yIGZvciBpbnB1dCB2YWxpZGF0aW9uXG4gICAgY29uc3QgdmFsaWRhdGlvblJlc3VsdCA9IGF3YWl0IHRoaXMudmFsaWRhdG9yLnZhbGlkYXRlQ3JlYXRlKGRhdGEpO1xuXG4gICAgaWYgKCF2YWxpZGF0aW9uUmVzdWx0LmlzVmFsaWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgVmFsaWRhdGlvbiBmYWlsZWQ6ICR7dmFsaWRhdGlvblJlc3VsdC5lcnJvcnMuam9pbignLCAnKX1gKTtcbiAgICB9XG5cbiAgICAvLyBMb2cgd2FybmluZ3MgaWYgYW55XG4gICAgaWYgKHZhbGlkYXRpb25SZXN1bHQud2FybmluZ3MgJiYgdmFsaWRhdGlvblJlc3VsdC53YXJuaW5ncy5sZW5ndGggPiAwKSB7XG4gICAgICBsb2dnZXIud2FybihgU2tpbGwgY3JlYXRpb24gd2FybmluZ3M6ICR7dmFsaWRhdGlvblJlc3VsdC53YXJuaW5ncy5qb2luKCcsICcpfWApO1xuICAgIH1cblxuICAgIC8vIEdldCBzYW5pdGl6ZWQgdmFsdWVzIGZvciBmaWxlIG9wZXJhdGlvbnMgKHZhbGlkYXRvciBhbHJlYWR5IHZhbGlkYXRlZCwgd2UganVzdCBuZWVkIHNhbml0aXplZCB2YWx1ZXMpXG4gICAgY29uc3QgbmFtZUlucHV0ID0gZGF0YS5uYW1lIHx8ICduZXctc2tpbGwnO1xuICAgIGNvbnN0IG5hbWVSZXN1bHQgPSB0aGlzLnZhbGlkYXRpb25TZXJ2aWNlLnZhbGlkYXRlQW5kU2FuaXRpemVJbnB1dChuYW1lSW5wdXQsIHtcbiAgICAgIG1heExlbmd0aDogU0VDVVJJVFlfTElNSVRTLk1BWF9OQU1FX0xFTkdUSCxcbiAgICAgIGFsbG93U3BhY2VzOiB0cnVlXG4gICAgfSk7XG4gICAgY29uc3Qgc2FuaXRpemVkTmFtZSA9IG5hbWVSZXN1bHQuc2FuaXRpemVkVmFsdWUhO1xuXG4gICAgLy8gVXNlIGluaGVyaXRlZCBnZXRFbGVtZW50RmlsZW5hbWUoKSBmb3IgY29uc2lzdGVudCBmaWxlbmFtZSBub3JtYWxpemF0aW9uXG4gICAgY29uc3QgZmlsZW5hbWUgPSB0aGlzLmdldEVsZW1lbnRGaWxlbmFtZShzYW5pdGl6ZWROYW1lKTtcblxuICAgIC8vIEZJWDogSXNzdWUgIzIwIC0gQ2hlY2sgZm9yIGR1cGxpY2F0ZSBiZWZvcmUgY3JlYXRpbmdcbiAgICBjb25zdCBleGlzdGluZ1NraWxscyA9IGF3YWl0IHRoaXMubGlzdCgpO1xuICAgIGNvbnN0IGR1cGxpY2F0ZSA9IGV4aXN0aW5nU2tpbGxzLmZpbmQocyA9PlxuICAgICAgcy5tZXRhZGF0YS5uYW1lLnRvTG93ZXJDYXNlKCkgPT09IHNhbml0aXplZE5hbWUudG9Mb3dlckNhc2UoKVxuICAgICk7XG5cbiAgICBpZiAoZHVwbGljYXRlKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYEEgc2tpbGwgbmFtZWQgXCIke3Nhbml0aXplZE5hbWV9XCIgYWxyZWFkeSBleGlzdHNgKTtcbiAgICB9XG5cbiAgICBjb25zdCB7IGNvbnRlbnQsIGluc3RydWN0aW9ucywgLi4ubWV0YWRhdGEgfSA9IGRhdGE7XG4gICAgLy8gRHVhbC1maWVsZDogaW5zdHJ1Y3Rpb25zID0gYmVoYXZpb3JhbCBkaXJlY3RpdmVzLCBjb250ZW50ID0gcmVmZXJlbmNlIG1hdGVyaWFsXG4gICAgLy8gRm9yIHNraWxscywgaW5zdHJ1Y3Rpb25zIGlzIHRoZSBwcmltYXJ5IGZpZWxkIChob3cgdG8gYXBwbHkgdGhlIHNraWxsKVxuICAgIGNvbnN0IGVmZmVjdGl2ZUluc3RydWN0aW9ucyA9IGluc3RydWN0aW9ucyB8fCBjb250ZW50IHx8ICcnO1xuICAgIGNvbnN0IGVmZmVjdGl2ZUNvbnRlbnQgPSBpbnN0cnVjdGlvbnMgPyAoY29udGVudCB8fCAnJykgOiAnJztcbiAgICBjb25zdCBza2lsbCA9IG5ldyBTa2lsbChcbiAgICAgIHtcbiAgICAgICAgLi4ubWV0YWRhdGEsXG4gICAgICAgIG5hbWU6IHNhbml0aXplZE5hbWUsXG4gICAgICAgIGRlc2NyaXB0aW9uOiBkYXRhLmRlc2NyaXB0aW9uIHx8ICcnXG4gICAgICB9LFxuICAgICAgZWZmZWN0aXZlSW5zdHJ1Y3Rpb25zLFxuICAgICAgdGhpcy5tZXRhZGF0YVNlcnZpY2UsXG4gICAgICBlZmZlY3RpdmVDb250ZW50XG4gICAgKTtcblxuICAgIGF3YWl0IHRoaXMuc2F2ZShza2lsbCwgZmlsZW5hbWUpO1xuICAgIC8vIE5vdGU6IE5vIHJlbG9hZCgpIGhlcmUg4oCUIHNhdmUoKSBjYWNoZXMgdGhlIGVsZW1lbnQgY29ycmVjdGx5LlxuICAgIC8vIFNlZSBJc3N1ZSAjNDkxIGZvciB3aHkgUGVyc29uYU1hbmFnZXIncyByZWxvYWQtYWZ0ZXItY3JlYXRlIHdhcyByZW1vdmVkLlxuXG4gICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgdHlwZTogJ0VMRU1FTlRfQ1JFQVRFRCcsXG4gICAgICBzZXZlcml0eTogJ0xPVycsXG4gICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuY3JlYXRlJyxcbiAgICAgIGRldGFpbHM6IGBTa2lsbCBjcmVhdGVkOiAke3NraWxsLm1ldGFkYXRhLm5hbWV9YFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHNraWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIEltcG9ydCBhIHNraWxsIGZyb20gWUFNTCBvciBKU09OIGlucHV0IGZvcm1hdHMuXG4gICAqL1xuICBhc3luYyBpbXBvcnRFbGVtZW50KGRhdGE6IHN0cmluZywgZm9ybWF0OiAneWFtbCcgfCAnanNvbicgPSAneWFtbCcpOiBQcm9taXNlPFNraWxsPiB7XG4gICAgdHJ5IHtcbiAgICAgIGxldCBtZXRhZGF0YTogYW55O1xuICAgICAgbGV0IGluc3RydWN0aW9uczogc3RyaW5nO1xuXG4gICAgICBpZiAoZm9ybWF0ID09PSAneWFtbCcpIHtcbiAgICAgICAgLy8gVXNlIFNlcmlhbGl6YXRpb25TZXJ2aWNlIGZvciBZQU1MIHBhcnNpbmdcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGhpcy5zZXJpYWxpemF0aW9uU2VydmljZS5wYXJzZUZyb250bWF0dGVyKGRhdGEsIHtcbiAgICAgICAgICBtYXhZYW1sU2l6ZTogNjQgKiAxMDI0LFxuICAgICAgICAgIHZhbGlkYXRlQ29udGVudDogdHJ1ZSxcbiAgICAgICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuaW1wb3J0RWxlbWVudCdcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gRXh0cmFjdCBtZXRhZGF0YSBhbmQgaW5zdHJ1Y3Rpb25zXG4gICAgICAgIGlmIChyZXN1bHQuZGF0YS5tZXRhZGF0YSkge1xuICAgICAgICAgIG1ldGFkYXRhID0gcmVzdWx0LmRhdGEubWV0YWRhdGE7XG4gICAgICAgICAgaW5zdHJ1Y3Rpb25zID0gcmVzdWx0LmRhdGEuaW5zdHJ1Y3Rpb25zIHx8IHJlc3VsdC5jb250ZW50IHx8ICcnO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIG1ldGFkYXRhID0gcmVzdWx0LmRhdGE7XG4gICAgICAgICAgaW5zdHJ1Y3Rpb25zID0gcmVzdWx0LmRhdGEuaW5zdHJ1Y3Rpb25zIHx8IHJlc3VsdC5jb250ZW50IHx8ICcnO1xuICAgICAgICAgIGRlbGV0ZSBtZXRhZGF0YS5pbnN0cnVjdGlvbnM7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIFVzZSBTZXJpYWxpemF0aW9uU2VydmljZSBmb3IgSlNPTiBwYXJzaW5nXG4gICAgICAgIGNvbnN0IHBhcnNlZCA9IHRoaXMuc2VyaWFsaXphdGlvblNlcnZpY2UucGFyc2VKc29uKGRhdGEsIHtcbiAgICAgICAgICBzb3VyY2U6ICdTa2lsbE1hbmFnZXIuaW1wb3J0RWxlbWVudCdcbiAgICAgICAgfSk7XG5cbiAgICAgICAgaWYgKHBhcnNlZC5tZXRhZGF0YSkge1xuICAgICAgICAgIG1ldGFkYXRhID0gcGFyc2VkLm1ldGFkYXRhO1xuICAgICAgICAgIGluc3RydWN0aW9ucyA9IHBhcnNlZC5pbnN0cnVjdGlvbnMgfHwgJyc7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgbWV0YWRhdGEgPSBwYXJzZWQ7XG4gICAgICAgICAgaW5zdHJ1Y3Rpb25zID0gcGFyc2VkLmluc3RydWN0aW9ucyB8fCAnJztcbiAgICAgICAgICBkZWxldGUgbWV0YWRhdGEuaW5zdHJ1Y3Rpb25zO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBuZXcgU2tpbGwobWV0YWRhdGEsIGluc3RydWN0aW9ucywgdGhpcy5tZXRhZGF0YVNlcnZpY2UpO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIuZXJyb3IoJ0ZhaWxlZCB0byBpbXBvcnQgc2tpbGw6JywgZXJyb3IpO1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEV4cG9ydCBhIHNraWxsIHRvIFlBTUwgb3IgSlNPTi5cbiAgICovXG4gIGFzeW5jIGV4cG9ydEVsZW1lbnQoZWxlbWVudDogU2tpbGwsIGZvcm1hdDogJ3lhbWwnIHwgJ2pzb24nID0gJ3lhbWwnKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBpZiAoZm9ybWF0ID09PSAneWFtbCcpIHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIG1ldGFkYXRhOiBlbGVtZW50Lm1ldGFkYXRhLFxuICAgICAgICBpbnN0cnVjdGlvbnM6IGVsZW1lbnQuaW5zdHJ1Y3Rpb25zLFxuICAgICAgICBwYXJhbWV0ZXJzOiBPYmplY3QuZnJvbUVudHJpZXMoZWxlbWVudC5wYXJhbWV0ZXJzKVxuICAgICAgfTtcblxuICAgICAgcmV0dXJuIHRoaXMuc2VyaWFsaXphdGlvblNlcnZpY2UuZHVtcFlhbWwoZGF0YSwge1xuICAgICAgICBzY2hlbWE6ICdqc29uJywgIC8vIEZpeCAjOTE0OiBmYWlsc2FmZSBjb3JydXB0cyBib29sZWFucy9udW1iZXJzIHRvIHN0cmluZ3NcbiAgICAgICAgbm9SZWZzOiB0cnVlLFxuICAgICAgICBza2lwSW52YWxpZDogdHJ1ZVxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gRm9yIEpTT04sIHNwcmVhZCBtZXRhZGF0YSBwcm9wZXJ0aWVzIGRpcmVjdGx5IChtYWludGFpbiBiYWNrd2FyZCBjb21wYXRpYmlsaXR5KVxuICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAuLi5lbGVtZW50Lm1ldGFkYXRhLFxuICAgICAgaW5zdHJ1Y3Rpb25zOiBlbGVtZW50Lmluc3RydWN0aW9ucyxcbiAgICAgIHBhcmFtZXRlcnM6IE9iamVjdC5mcm9tRW50cmllcyhlbGVtZW50LnBhcmFtZXRlcnMpXG4gICAgfTtcblxuICAgIHJldHVybiB0aGlzLnNlcmlhbGl6YXRpb25TZXJ2aWNlLnN0cmluZ2lmeUpzb24oZGF0YSwge1xuICAgICAgcHJldHR5OiB0cnVlLFxuICAgICAgaW5kZW50OiAyXG4gICAgfSk7XG4gIH1cblxuICBnZXRGaWxlRXh0ZW5zaW9uKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuICcubWQnO1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGFuZCBub3JtYWxpemUgbWV0YWRhdGEgcGFyc2VkIGZyb20gZnJvbnRtYXR0ZXIuXG4gICAqL1xuICBwcm90ZWN0ZWQgYXN5bmMgcGFyc2VNZXRhZGF0YShkYXRhOiBhbnkpOiBQcm9taXNlPFNraWxsTWV0YWRhdGE+IHtcbiAgICBjb25zdCBtZXRhZGF0YSA9IHsgLi4uKGRhdGEgYXMgU2tpbGxNZXRhZGF0YSkgfTtcblxuICAgIGlmIChBcnJheS5pc0FycmF5KGRhdGE/LnRyaWdnZXJzKSkge1xuICAgICAgY29uc3QgdmFsaWRhdGlvblJlc3VsdCA9IHRoaXMudHJpZ2dlclZhbGlkYXRpb25TZXJ2aWNlLnZhbGlkYXRlVHJpZ2dlcnMoXG4gICAgICAgIGRhdGEudHJpZ2dlcnMsXG4gICAgICAgIEVsZW1lbnRUeXBlLlNLSUxMLFxuICAgICAgICBtZXRhZGF0YS5uYW1lIHx8ICd1bmtub3duJ1xuICAgICAgKTtcbiAgICAgIG1ldGFkYXRhLnRyaWdnZXJzID0gdmFsaWRhdGlvblJlc3VsdC52YWxpZFRyaWdnZXJzO1xuICAgIH1cblxuICAgIC8vIElzc3VlICM2NzY6IFNhbml0aXplIGdhdGVrZWVwZXIgcG9saWN5IG9uIGxvYWQgdG8gcHJldmVudCBwcm9tcHQtaW5qZWN0aW9uIGF0dGFja3NcbiAgICAvLyBNYWxmb3JtZWQgcG9saWNpZXMgYXJlIHN0cmlwcGVkIGFuZCBsb2dnZWQgYXMgc2VjdXJpdHkgZXZlbnRzIChuZXZlciByZWFjaCBlbmZvcmNlbWVudClcbiAgICBpZiAobWV0YWRhdGEuZ2F0ZWtlZXBlcikge1xuICAgICAgbWV0YWRhdGEuZ2F0ZWtlZXBlciA9IHNhbml0aX