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.

321 lines 38.7 kB
/** * Background Validation Service for Memory Security * * Part of Issue #1314 Phase 1: Memory Security Architecture * * PURPOSE: * Asynchronously validates UNTRUSTED memory entries and updates their trust levels * without blocking memory creation. Runs outside the LLM request path to avoid * token costs and latency. * * ARCHITECTURE: * - Layer 2 in the Memory Security Architecture (see docs/development/MEMORY_SECURITY_ARCHITECTURE.md) * - Runs server-side, not in LLM context * - No token cost for validation * - Updates trust levels in-place * * TRUST LEVEL TRANSITIONS: * UNTRUSTED → VALIDATED (clean content, no patterns) * UNTRUSTED → FLAGGED (dangerous patterns detected, needs encryption) * UNTRUSTED → QUARANTINED (explicitly malicious, critical threat) * * @module BackgroundValidator */ import { logger } from '../../utils/logger.js'; import { ContentValidator } from '../contentValidator.js'; import { TRUST_LEVELS } from '../../elements/memories/constants.js'; /** * Default configuration for background validation */ const DEFAULT_CONFIG = { enabled: true, intervalSeconds: 300, // 5 minutes batchSize: 10, validationTimeoutMs: 5000, }; /** * Background validation service for memory entries * * This service runs outside the LLM request path to validate UNTRUSTED * memory entries and update their trust levels based on security analysis. * * REFACTOR NOTE: * Converted to full DI architecture. Removed singleton export. * PatternExtractor is now injected as a dependency instead of static calls. */ export class BackgroundValidator { patternExtractor; memoryManager; config; intervalHandle; isProcessing = false; constructor(patternExtractor, memoryManager, // MemoryManager - using any to avoid circular import config) { this.patternExtractor = patternExtractor; this.memoryManager = memoryManager; this.config = { ...DEFAULT_CONFIG, ...config }; logger.info('BackgroundValidator initialized', { enabled: this.config.enabled, intervalSeconds: this.config.intervalSeconds, batchSize: this.config.batchSize, }); } /** * Start the background validation service * Begins periodic validation of UNTRUSTED memories */ start() { if (!this.config.enabled) { logger.info('Background validation disabled in config'); return; } if (this.intervalHandle) { logger.warn('Background validation already running'); return; } logger.info('Starting background validation service', { intervalSeconds: this.config.intervalSeconds, }); // Run immediately on start void this.processUntrustedMemories(); // Then run periodically this.intervalHandle = setInterval(() => { void this.processUntrustedMemories(); }, this.config.intervalSeconds * 1000); } /** * Stop the background validation service */ stop() { if (this.intervalHandle) { clearInterval(this.intervalHandle); this.intervalHandle = undefined; logger.info('Background validation service stopped'); } } /** * Process all UNTRUSTED memory entries * This is the main background validation loop */ async processUntrustedMemories() { if (this.isProcessing) { logger.debug('Validation already in progress, skipping this run'); return; } this.isProcessing = true; try { // PHASE 1 INCOMPLETE: Memory discovery integration pending // Issue #1314 Phase 1 - This will be connected to Memory loading system // in a follow-up PR once Memory.find() API is available const untrustedMemories = await this.findMemoriesWithUntrustedEntries(); if (untrustedMemories.length === 0) { return; } logger.info('Found untrusted memories to validate', { count: untrustedMemories.length, batchSize: this.config.batchSize, }); // Process in batches const batches = this.createBatches(untrustedMemories, this.config.batchSize); for (const batch of batches) { await this.processBatch(batch); } logger.info('Validation pass complete'); } catch (error) { logger.error('Error during background validation', { error }); } finally { this.isProcessing = false; } } /** * Find all memories that have UNTRUSTED entries * * DI REFACTOR: Use injected MemoryManager instead of static Memory methods * Loads memories from filesystem and filters by trust level */ async findMemoriesWithUntrustedEntries() { // Load all memories using the injected MemoryManager const allMemories = await this.memoryManager.list(); // Filter to only memories with UNTRUSTED entries const untrustedMemories = []; const limit = this.config.batchSize * 10; for (const memory of allMemories) { const hasUntrustedEntries = memory.getEntriesByTrustLevel(TRUST_LEVELS.UNTRUSTED).length > 0; if (hasUntrustedEntries) { untrustedMemories.push(memory); // Apply limit if (untrustedMemories.length >= limit) { break; } } } if (untrustedMemories.length > 0) { logger.debug('Found memories with untrusted entries', { count: untrustedMemories.length, limit }); } return untrustedMemories; } /** * Process a batch of memories for validation */ async processBatch(memories) { logger.debug('Processing batch', { size: memories.length }); for (const memory of memories) { try { await this.validateMemory(memory); } catch (error) { logger.error('Error validating memory', { memoryId: memory.id, error, }); } } } /** * Validate all UNTRUSTED entries in a memory * FIX #1320: Now uses public Memory API and saves changes * FIX (Claude Bot Review): Removed type casting for memory.id */ async validateMemory(memory) { logger.debug('Validating memory', { memoryId: memory.id }); let updatedCount = 0; // FIX #1320: Use public API instead of casting to any const entries = memory.getEntriesByTrustLevel(TRUST_LEVELS.UNTRUSTED); for (const entry of entries) { try { const updated = await this.validateEntry(entry); if (updated) { updatedCount++; } } catch (error) { logger.error('Error validating entry', { entryId: entry.id, error, }); } } if (updatedCount > 0) { logger.info('Updated trust levels in memory', { memoryId: memory.id, updatedCount, }); // DI REFACTOR: Use injected MemoryManager to save try { await this.memoryManager.save(memory); logger.debug('Memory saved successfully', { memoryId: memory.id }); } catch (error) { logger.error('Failed to save memory after validation', { memoryId: memory.id, error }); // Don't throw - continue with other memories } } } /** * Validate a single memory entry and update its trust level * * @param entry - The memory entry to validate * @returns true if the entry was updated, false otherwise */ async validateEntry(entry) { logger.debug('Validating entry', { entryId: entry.id }); // Validate content using ContentValidator const validationResult = ContentValidator.validateAndSanitize(entry.content, { skipSizeCheck: true, }); // Determine new trust level based on validation results const newTrustLevel = this.determineTrustLevel(validationResult); if (newTrustLevel === entry.trustLevel) { logger.debug('Trust level unchanged', { entryId: entry.id, trustLevel: entry.trustLevel, }); return false; } // Update trust level entry.trustLevel = newTrustLevel; logger.info('Updated entry trust level', { entryId: entry.id, oldTrustLevel: TRUST_LEVELS.UNTRUSTED, newTrustLevel, detectedPatterns: validationResult.detectedPatterns?.length || 0, }); // If FLAGGED, extract patterns and create sanitized content if (newTrustLevel === TRUST_LEVELS.FLAGGED) { logger.debug('Entry flagged, extracting patterns', { entryId: entry.id, patterns: validationResult.detectedPatterns, }); // Phase 1: Extract patterns and create sanitized content const extractionResult = this.patternExtractor.extractPatterns(entry.content, validationResult); // Store sanitized patterns and content in entry metadata // Phase 2 will add encryption to these patterns entry.sanitizedPatterns = extractionResult.patterns; entry.sanitizedContent = extractionResult.sanitizedContent; logger.info('Patterns extracted from entry', { entryId: entry.id, patternCount: extractionResult.patternCount, }); } return true; } /** * Determine the appropriate trust level based on validation results */ determineTrustLevel(validationResult) { // If validation passed with no patterns, mark as VALIDATED if (validationResult.isValid && (!validationResult.detectedPatterns || validationResult.detectedPatterns.length === 0)) { return TRUST_LEVELS.VALIDATED; } // If critical/high severity threats detected, mark as FLAGGED if (validationResult.severity === 'critical' || validationResult.severity === 'high') { // PHASE 1 INCOMPLETE: QUARANTINED trust level logic deferred (Issue #1314) // Will add distinction between FLAGGED (dangerous) vs QUARANTINED (malicious) // For now, all high/critical severity goes to FLAGGED return TRUST_LEVELS.FLAGGED; } // Medium/low severity or minor issues - keep as UNTRUSTED for now // (Could be VALIDATED in a more lenient policy) return TRUST_LEVELS.UNTRUSTED; } /** * Split an array into batches of specified size */ createBatches(items, batchSize) { const batches = []; for (let i = 0; i < items.length; i += batchSize) { batches.push(items.slice(i, i + batchSize)); } return batches; } /** * Get current validation statistics */ getStats() { return { enabled: this.config.enabled, isProcessing: this.isProcessing, intervalSeconds: this.config.intervalSeconds, batchSize: this.config.batchSize, }; } /** * Dispose of the validator and clean up resources * Implements cleanup for proper DI lifecycle management */ async dispose() { this.stop(); logger.debug('BackgroundValidator disposed'); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmFja2dyb3VuZFZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZWN1cml0eS92YWxpZGF0aW9uL0JhY2tncm91bmRWYWxpZGF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FzQkc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDL0MsT0FBTyxFQUFFLGdCQUFnQixFQUFnQyxNQUFNLHdCQUF3QixDQUFDO0FBQ3hGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQXFCcEU7O0dBRUc7QUFDSCxNQUFNLGNBQWMsR0FBOEI7SUFDaEQsT0FBTyxFQUFFLElBQUk7SUFDYixlQUFlLEVBQUUsR0FBRyxFQUFHLFlBQVk7SUFDbkMsU0FBUyxFQUFFLEVBQUU7SUFDYixtQkFBbUIsRUFBRSxJQUFJO0NBQzFCLENBQUM7QUFrQ0Y7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBTSxPQUFPLG1CQUFtQjtJQU1YO0lBQ0E7SUFORixNQUFNLENBQTRCO0lBQzNDLGNBQWMsQ0FBa0I7SUFDaEMsWUFBWSxHQUFZLEtBQUssQ0FBQztJQUV0QyxZQUNtQixnQkFBa0MsRUFDbEMsYUFBa0IsRUFBRSxxREFBcUQ7SUFDMUYsTUFBMkM7UUFGMUIscUJBQWdCLEdBQWhCLGdCQUFnQixDQUFrQjtRQUNsQyxrQkFBYSxHQUFiLGFBQWEsQ0FBSztRQUduQyxJQUFJLENBQUMsTUFBTSxHQUFHLEVBQUUsR0FBRyxjQUFjLEVBQUUsR0FBRyxNQUFNLEVBQUUsQ0FBQztRQUMvQyxNQUFNLENBQUMsSUFBSSxDQUFDLGlDQUFpQyxFQUFFO1lBQzdDLE9BQU8sRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU87WUFDNUIsZUFBZSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZTtZQUM1QyxTQUFTLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FB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LElBQUksZ0JBQWdCLENBQUMsUUFBUSxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQ3JGLDJFQUEyRTtZQUMzRSw4RUFBOEU7WUFDOUUsc0RBQXNEO1lBQ3RELE9BQU8sWUFBWSxDQUFDLE9BQU8sQ0FBQztRQUM5QixDQUFDO1FBRUQsa0VBQWtFO1FBQ2xFLGdEQUFnRDtRQUNoRCxPQUFPLFlBQVksQ0FBQyxTQUFTLENBQUM7SUFDaEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ssYUFBYSxDQUFJLEtBQVUsRUFBRSxTQUFpQjtRQUNwRCxNQUFNLE9BQU8sR0FBVSxFQUFFLENBQUM7UUFDMUIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQ2pELE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxHQUFHLFNBQVMsQ0FBQyxDQUFDLENBQUM7UUFDOUMsQ0FBQztRQUNELE9BQU8sT0FBTyxDQUFDO0lBQ2pCLENBQUM7SUFFRDs7T0FFRztJQUNILFFBQVE7UUFDTixPQUFPO1lBQ0wsT0FBTyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTztZQUM1QixZQUFZLEVBQUUsSUFBSSxDQUFDLFlBQVk7WUFDL0IsZUFBZSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZTtZQUM1QyxTQUFTLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTO1NBQ2pDLENBQUM7SUFDSixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLE9BQU87UUFDWCxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDWixNQUFNLENBQUMsS0FBSyxDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFDL0MsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBCYWNrZ3JvdW5kIFZhbGlkYXRpb24gU2VydmljZSBmb3IgTWVtb3J5IFNlY3VyaXR5XG4gKlxuICogUGFydCBvZiBJc3N1ZSAjMTMxNCBQaGFzZSAxOiBNZW1vcnkgU2VjdXJpdHkgQXJjaGl0ZWN0dXJlXG4gKlxuICogUFVSUE9TRTpcbiAqIEFzeW5jaHJvbm91c2x5IHZhbGlkYXRlcyBVTlRSVVNURUQgbWVtb3J5IGVudHJpZXMgYW5kIHVwZGF0ZXMgdGhlaXIgdHJ1c3QgbGV2ZWxzXG4gKiB3aXRob3V0IGJsb2NraW5nIG1lbW9yeSBjcmVhdGlvbi4gUnVucyBvdXRzaWRlIHRoZSBMTE0gcmVxdWVzdCBwYXRoIHRvIGF2b2lkXG4gKiB0b2tlbiBjb3N0cyBhbmQgbGF0ZW5jeS5cbiAqXG4gKiBBUkNISVRFQ1RVUkU6XG4gKiAtIExheWVyIDIgaW4gdGhlIE1lbW9yeSBTZWN1cml0eSBBcmNoaXRlY3R1cmUgKHNlZSBkb2NzL2RldmVsb3BtZW50L01FTU9SWV9TRUNVUklUWV9BUkNISVRFQ1RVUkUubWQpXG4gKiAtIFJ1bnMgc2VydmVyLXNpZGUsIG5vdCBpbiBMTE0gY29udGV4dFxuICogLSBObyB0b2tlbiBjb3N0IGZvciB2YWxpZGF0aW9uXG4gKiAtIFVwZGF0ZXMgdHJ1c3QgbGV2ZWxzIGluLXBsYWNlXG4gKlxuICogVFJVU1QgTEVWRUwgVFJBTlNJVElPTlM6XG4gKiBVTlRSVVNURUQg4oaSIFZBTElEQVRFRCAoY2xlYW4gY29udGVudCwgbm8gcGF0dGVybnMpXG4gKiBVTlRSVVNURUQg4oaSIEZMQUdHRUQgKGRhbmdlcm91cyBwYXR0ZXJucyBkZXRlY3RlZCwgbmVlZHMgZW5jcnlwdGlvbilcbiAqIFVOVFJVU1RFRCDihpIgUVVBUkFOVElORUQgKGV4cGxpY2l0bHkgbWFsaWNpb3VzLCBjcml0aWNhbCB0aHJlYXQpXG4gKlxuICogQG1vZHVsZSBCYWNrZ3JvdW5kVmFsaWRhdG9yXG4gKi9cblxuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IENvbnRlbnRWYWxpZGF0b3IsIHR5cGUgQ29udGVudFZhbGlkYXRpb25SZXN1bHQgfSBmcm9tICcuLi9jb250ZW50VmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFRSVVNUX0xFVkVMUyB9IGZyb20gJy4uLy4uL2VsZW1lbnRzL21lbW9yaWVzL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBQYXR0ZXJuRXh0cmFjdG9yIH0gZnJvbSAnLi9QYXR0ZXJuRXh0cmFjdG9yLmpzJztcbmltcG9ydCB0eXBlIHsgTWVtb3J5IH0gZnJvbSAnLi4vLi4vZWxlbWVudHMvbWVtb3JpZXMvTWVtb3J5LmpzJztcblxuLyoqXG4gKiBDb25maWd1cmF0aW9uIGZvciBiYWNrZ3JvdW5kIHZhbGlkYXRpb24gYmVoYXZpb3JcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBCYWNrZ3JvdW5kVmFsaWRhdG9yQ29uZmlnIHtcbiAgLyoqIEVuYWJsZSBiYWNrZ3JvdW5kIHZhbGlkYXRpb24gKGRlZmF1bHQ6IHRydWUpICovXG4gIGVuYWJsZWQ6IGJvb2xlYW47XG5cbiAgLyoqIEludGVydmFsIGluIHNlY29uZHMgYmV0d2VlbiB2YWxpZGF0aW9uIHJ1bnMgKGRlZmF1bHQ6IDMwMCA9IDUgbWludXRlcykgKi9cbiAgaW50ZXJ2YWxTZWNvbmRzOiBudW1iZXI7XG5cbiAgLyoqIE1heGltdW0gbnVtYmVyIG9mIG1lbW9yaWVzIHRvIHByb2Nlc3MgcGVyIGJhdGNoIChkZWZhdWx0OiAxMCkgKi9cbiAgYmF0Y2hTaXplOiBudW1iZXI7XG5cbiAgLyoqIE1heGltdW0gdGltZSBpbiBtcyBmb3IgYSBzaW5nbGUgdmFsaWRhdGlvbiBvcGVyYXRpb24gKGRlZmF1bHQ6IDUwMDApICovXG4gIHZhbGlkYXRpb25UaW1lb3V0TXM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBEZWZhdWx0IGNvbmZpZ3VyYXRpb24gZm9yIGJhY2tncm91bmQgdmFsaWRhdGlvblxuICovXG5jb25zdCBERUZBVUxUX0NPTkZJRzogQmFja2dyb3VuZFZhbGlkYXRvckNvbmZpZyA9IHtcbiAgZW5hYmxlZDogdHJ1ZSxcbiAgaW50ZXJ2YWxTZWNvbmRzOiAzMDAsICAvLyA1IG1pbnV0ZXNcbiAgYmF0Y2hTaXplOiAxMCxcbiAgdmFsaWRhdGlvblRpbWVvdXRNczogNTAwMCxcbn07XG5cbi8qKlxuICogUGF0dGVybiBpbmZvcm1hdGlvbiBmb3IgZW5jcnlwdGVkIHN0b3JhZ2VcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTYW5pdGl6ZWRQYXR0ZXJuIHtcbiAgLyoqIFVuaXF1ZSByZWZlcmVuY2UgSUQgZm9yIHRoaXMgcGF0dGVybiAqL1xuICByZWY6IHN0cmluZztcblxuICAvKiogSHVtYW4tcmVhZGFibGUgZGVzY3JpcHRpb24gb2YgdGhlIHBhdHRlcm4gKi9cbiAgZGVzY3JpcHRpb246IHN0cmluZztcblxuICAvKiogU2V2ZXJpdHkgbGV2ZWwgb2YgdGhlIHBhdHRlcm4gKi9cbiAgc2V2ZXJpdHk6ICdjcml0aWNhbCcgfCAnaGlnaCcgfCAnbWVkaXVtJyB8ICdsb3cnO1xuXG4gIC8qKiBMb2NhdGlvbiBpbiBvcmlnaW5hbCBjb250ZW50IChvZmZzZXQgYW5kIGxlbmd0aCkgKi9cbiAgbG9jYXRpb246IHN0cmluZztcblxuICAvKiogRW5jcnlwdGVkIHBhdHRlcm4gKEFFUy0yNTYtR0NNKSAtIFBoYXNlIDIgKi9cbiAgZW5jcnlwdGVkUGF0dGVybj86IHN0cmluZztcblxuICAvKiogRW5jcnlwdGlvbiBhbGdvcml0aG0gdXNlZCAtIFBoYXNlIDIgKi9cbiAgYWxnb3JpdGhtPzogc3RyaW5nO1xuXG4gIC8qKiBJbml0aWFsaXphdGlvbiB2ZWN0b3IgZm9yIGRlY3J5cHRpb24gLSBQaGFzZSAyICovXG4gIGl2Pzogc3RyaW5nO1xuXG4gIC8qKiBHQ00gYXV0aGVudGljYXRpb24gdGFnIGZvciBpbnRlZ3JpdHkgdmVyaWZpY2F0aW9uIC0gUGhhc2UgMiAqL1xuICBhdXRoVGFnPzogc3RyaW5nO1xuXG4gIC8qKiBTYWZldHkgaW5zdHJ1Y3Rpb24gZm9yIHBhdHRlcm4gdXNhZ2UgKi9cbiAgc2FmZXR5SW5zdHJ1Y3Rpb246IHN0cmluZztcbn1cblxuLyoqXG4gKiBCYWNrZ3JvdW5kIHZhbGlkYXRpb24gc2VydmljZSBmb3IgbWVtb3J5IGVudHJpZXNcbiAqXG4gKiBUaGlzIHNlcnZpY2UgcnVucyBvdXRzaWRlIHRoZSBMTE0gcmVxdWVzdCBwYXRoIHRvIHZhbGlkYXRlIFVOVFJVU1RFRFxuICogbWVtb3J5IGVudHJpZXMgYW5kIHVwZGF0ZSB0aGVpciB0cnVzdCBsZXZlbHMgYmFzZWQgb24gc2VjdXJpdHkgYW5hbHlzaXMuXG4gKlxuICogUkVGQUNUT1IgTk9URTpcbiAqIENvbnZlcnRlZCB0byBmdWxsIERJIGFyY2hpdGVjdHVyZS4gUmVtb3ZlZCBzaW5nbGV0b24gZXhwb3J0LlxuICogUGF0dGVybkV4dHJhY3RvciBpcyBub3cgaW5qZWN0ZWQgYXMgYSBkZXBlbmRlbmN5IGluc3RlYWQgb2Ygc3RhdGljIGNhbGxzLlxuICovXG5leHBvcnQgY2xhc3MgQmFja2dyb3VuZFZhbGlkYXRvciB7XG4gIHByaXZhdGUgcmVhZG9ubHkgY29uZmlnOiBCYWNrZ3JvdW5kVmFsaWRhdG9yQ29uZmlnO1xuICBwcml2YXRlIGludGVydmFsSGFuZGxlPzogTm9kZUpTLlRpbWVvdXQ7XG4gIHByaXZhdGUgaXNQcm9jZXNzaW5nOiBib29sZWFuID0gZmFsc2U7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSBwYXR0ZXJuRXh0cmFjdG9yOiBQYXR0ZXJuRXh0cmFjdG9yLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgbWVtb3J5TWFuYWdlcjogYW55LCAvLyBNZW1vcnlNYW5hZ2VyIC0gdXNpbmcgYW55IHRvIGF2b2lkIGNpcmN1bGFyIGltcG9ydFxuICAgIGNvbmZpZz86IFBhcnRpYWw8QmFja2dyb3VuZFZhbGlkYXRvckNvbmZpZz5cbiAgKSB7XG4gICAgdGhpcy5jb25maWcgPSB7IC4uLkRFRkFVTFRfQ09ORklHLCAuLi5jb25maWcgfTtcbiAgICBsb2dnZXIuaW5mbygnQmFja2dyb3VuZFZhbGlkYXRvciBpbml0aWFsaXplZCcsIHtcbiAgICAgIGVuYWJsZWQ6IHRoaXMuY29uZmlnLmVuYWJsZWQsXG4gICAgICBpbnRlcnZhbFNlY29uZHM6IHRoaXMuY29uZmlnLmludGVydmFsU2Vjb25kcyxcbiAgICAgIGJhdGNoU2l6ZTogdGhpcy5jb25maWcuYmF0Y2hTaXplLFxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0YXJ0IHRoZSBiYWNrZ3JvdW5kIHZhbGlkYXRpb24gc2VydmljZVxuICAgKiBCZWdpbnMgcGVyaW9kaWMgdmFsaWRhdGlvbiBvZiBVTlRSVVNURUQgbWVtb3JpZXNcbiAgICovXG4gIHN0YXJ0KCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5jb25maWcuZW5hYmxlZCkge1xuICAgICAgbG9nZ2VyLmluZm8oJ0JhY2tncm91bmQgdmFsaWRhdGlvbiBkaXNhYmxlZCBpbiBjb25maWcnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5pbnRlcnZhbEhhbmRsZSkge1xuICAgICAgbG9nZ2VyLndhcm4oJ0JhY2tncm91bmQgdmFsaWRhdGlvbiBhbHJlYWR5IHJ1bm5pbmcnKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBsb2dnZXIuaW5mbygnU3RhcnRpbmcgYmFja2dyb3VuZCB2YWxpZGF0aW9uIHNlcnZpY2UnLCB7XG4gICAgICBpbnRlcnZhbFNlY29uZHM6IHRoaXMuY29uZmlnLmludGVydmFsU2Vjb25kcyxcbiAgICB9KTtcblxuICAgIC8vIFJ1biBpbW1lZGlhdGVseSBvbiBzdGFydFxuICAgIHZvaWQgdGhpcy5wcm9jZXNzVW50cnVzdGVkTWVtb3JpZXMoKTtcblxuICAgIC8vIFRoZW4gcnVuIHBlcmlvZGljYWxseVxuICAgIHRoaXMuaW50ZXJ2YWxIYW5kbGUgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICB2b2lkIHRoaXMucHJvY2Vzc1VudHJ1c3RlZE1lbW9yaWVzKCk7XG4gICAgfSwgdGhpcy5jb25maWcuaW50ZXJ2YWxTZWNvbmRzICogMTAwMCk7XG4gIH1cblxuICAvKipcbiAgICogU3RvcCB0aGUgYmFja2dyb3VuZCB2YWxpZGF0aW9uIHNlcnZpY2VcbiAgICovXG4gIHN0b3AoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuaW50ZXJ2YWxIYW5kbGUpIHtcbiAgICAgIGNsZWFySW50ZXJ2YWwodGhpcy5pbnRlcnZhbEhhbmRsZSk7XG4gICAgICB0aGlzLmludGVydmFsSGFuZGxlID0gdW5kZWZpbmVkO1xuICAgICAgbG9nZ2VyLmluZm8oJ0JhY2tncm91bmQgdmFsaWRhdGlvbiBzZXJ2aWNlIHN0b3BwZWQnKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUHJvY2VzcyBhbGwgVU5UUlVTVEVEIG1lbW9yeSBlbnRyaWVzXG4gICAqIFRoaXMgaXMgdGhlIG1haW4gYmFja2dyb3VuZCB2YWxpZGF0aW9uIGxvb3BcbiAgICovXG4gIGFzeW5jIHByb2Nlc3NVbnRydXN0ZWRNZW1vcmllcygpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5pc1Byb2Nlc3NpbmcpIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnVmFsaWRhdGlvbiBhbHJlYWR5IGluIHByb2dyZXNzLCBza2lwcGluZyB0aGlzIHJ1bicpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRoaXMuaXNQcm9jZXNzaW5nID0gdHJ1ZTtcblxuICAgIHRyeSB7XG4gICAgICAvLyBQSEFTRSAxIElOQ09NUExFVEU6IE1lbW9yeSBkaXNjb3ZlcnkgaW50ZWdyYXRpb24gcGVuZGluZ1xuICAgICAgLy8gSXNzdWUgIzEzMTQgUGhhc2UgMSAtIFRoaXMgd2lsbCBiZSBjb25uZWN0ZWQgdG8gTWVtb3J5IGxvYWRpbmcgc3lzdGVtXG4gICAgICAvLyBpbiBhIGZvbGxvdy11cCBQUiBvbmNlIE1lbW9yeS5maW5kKCkgQVBJIGlzIGF2YWlsYWJsZVxuICAgICAgY29uc3QgdW50cnVzdGVkTWVtb3JpZXMgPSBhd2FpdCB0aGlzLmZpbmRNZW1vcmllc1dpdGhVbnRydXN0ZWRFbnRyaWVzKCk7XG5cbiAgICAgIGlmICh1bnRydXN0ZWRNZW1vcmllcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBsb2dnZXIuaW5mbygnRm91bmQgdW50cnVzdGVkIG1lbW9yaWVzIHRvIHZhbGlkYXRlJywge1xuICAgICAgICBjb3VudDogdW50cnVzdGVkTWVtb3JpZXMubGVuZ3RoLFxuICAgICAgICBiYXRjaFNpemU6IHRoaXMuY29uZmlnLmJhdGNoU2l6ZSxcbiAgICAgIH0pO1xuXG4gICAgICAvLyBQcm9jZXNzIGluIGJhdGNoZXNcbiAgICAgIGNvbnN0IGJhdGNoZXMgPSB0aGlzLmNyZWF0ZUJhdGNoZXModW50cnVzdGVkTWVtb3JpZXMsIHRoaXMuY29uZmlnLmJhdGNoU2l6ZSk7XG5cbiAgICAgIGZvciAoY29uc3QgYmF0Y2ggb2YgYmF0Y2hlcykge1xuICAgICAgICBhd2FpdCB0aGlzLnByb2Nlc3NCYXRjaChiYXRjaCk7XG4gICAgICB9XG5cbiAgICAgIGxvZ2dlci5pbmZvKCdWYWxpZGF0aW9uIHBhc3MgY29tcGxldGUnKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdFcnJvciBkdXJpbmcgYmFja2dyb3VuZCB2YWxpZGF0aW9uJywgeyBlcnJvciB9KTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5pc1Byb2Nlc3NpbmcgPSBmYWxzZTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRmluZCBhbGwgbWVtb3JpZXMgdGhhdCBoYXZlIFVOVFJVU1RFRCBlbnRyaWVzXG4gICAqXG4gICAqIERJIFJFRkFDVE9SOiBVc2UgaW5qZWN0ZWQgTWVtb3J5TWFuYWdlciBpbnN0ZWFkIG9mIHN0YXRpYyBNZW1vcnkgbWV0aG9kc1xuICAgKiBMb2FkcyBtZW1vcmllcyBmcm9tIGZpbGVzeXN0ZW0gYW5kIGZpbHRlcnMgYnkgdHJ1c3QgbGV2ZWxcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZmluZE1lbW9yaWVzV2l0aFVudHJ1c3RlZEVudHJpZXMoKTogUHJvbWlzZTxNZW1vcnlbXT4ge1xuICAgIC8vIExvYWQgYWxsIG1lbW9yaWVzIHVzaW5nIHRoZSBpbmplY3RlZCBNZW1vcnlNYW5hZ2VyXG4gICAgY29uc3QgYWxsTWVtb3JpZXMgPSBhd2FpdCB0aGlzLm1lbW9yeU1hbmFnZXIubGlzdCgpO1xuXG4gICAgLy8gRmlsdGVyIHRvIG9ubHkgbWVtb3JpZXMgd2l0aCBVTlRSVVNURUQgZW50cmllc1xuICAgIGNvbnN0IHVudHJ1c3RlZE1lbW9yaWVzOiBNZW1vcnlbXSA9IFtdO1xuICAgIGNvbnN0IGxpbWl0ID0gdGhpcy5jb25maWcuYmF0Y2hTaXplICogMTA7XG5cbiAgICBmb3IgKGNvbnN0IG1lbW9yeSBvZiBhbGxNZW1vcmllcykge1xuICAgICAgY29uc3QgaGFzVW50cnVzdGVkRW50cmllcyA9IG1lbW9yeS5nZXRFbnRyaWVzQnlUcnVzdExldmVsKFRSVVNUX0xFVkVMUy5VTlRSVVNURUQpLmxlbmd0aCA+IDA7XG4gICAgICBpZiAoaGFzVW50cnVzdGVkRW50cmllcykge1xuICAgICAgICB1bnRydXN0ZWRNZW1vcmllcy5wdXNoKG1lbW9yeSk7XG5cbiAgICAgICAgLy8gQXBwbHkgbGltaXRcbiAgICAgICAgaWYgKHVudHJ1c3RlZE1lbW9yaWVzLmxlbmd0aCA+PSBsaW1pdCkge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHVudHJ1c3RlZE1lbW9yaWVzLmxlbmd0aCA+IDApIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnRm91bmQgbWVtb3JpZXMgd2l0aCB1bnRydXN0ZWQgZW50cmllcycsIHtcbiAgICAgICAgY291bnQ6IHVudHJ1c3RlZE1lbW9yaWVzLmxlbmd0aCxcbiAgICAgICAgbGltaXRcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIHJldHVybiB1bnRydXN0ZWRNZW1vcmllcztcbiAgfVxuXG4gIC8qKlxuICAgKiBQcm9jZXNzIGEgYmF0Y2ggb2YgbWVtb3JpZXMgZm9yIHZhbGlkYXRpb25cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgcHJvY2Vzc0JhdGNoKG1lbW9yaWVzOiBNZW1vcnlbXSk6IFByb21pc2U8dm9pZD4ge1xuICAgIGxvZ2dlci5kZWJ1ZygnUHJvY2Vzc2luZyBiYXRjaCcsIHsgc2l6ZTogbWVtb3JpZXMubGVuZ3RoIH0pO1xuXG4gICAgZm9yIChjb25zdCBtZW1vcnkgb2YgbWVtb3JpZXMpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMudmFsaWRhdGVNZW1vcnkobWVtb3J5KTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignRXJyb3IgdmFsaWRhdGluZyBtZW1vcnknLCB7XG4gICAgICAgICAgbWVtb3J5SWQ6IG1lbW9yeS5pZCxcbiAgICAgICAgICBlcnJvcixcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGFsbCBVTlRSVVNURUQgZW50cmllcyBpbiBhIG1lbW9yeVxuICAgKiBGSVggIzEzMjA6IE5vdyB1c2VzIHB1YmxpYyBNZW1vcnkgQVBJIGFuZCBzYXZlcyBjaGFuZ2VzXG4gICAqIEZJWCAoQ2xhdWRlIEJvdCBSZXZpZXcpOiBSZW1vdmVkIHR5cGUgY2FzdGluZyBmb3IgbWVtb3J5LmlkXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIHZhbGlkYXRlTWVtb3J5KG1lbW9yeTogTWVtb3J5KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgbG9nZ2VyLmRlYnVnKCdWYWxpZGF0aW5nIG1lbW9yeScsIHsgbWVtb3J5SWQ6IG1lbW9yeS5pZCB9KTtcblxuICAgIGxldCB1cGRhdGVkQ291bnQgPSAwO1xuXG4gICAgLy8gRklYICMxMzIwOiBVc2UgcHVibGljIEFQSSBpbnN0ZWFkIG9mIGNhc3RpbmcgdG8gYW55XG4gICAgY29uc3QgZW50cmllcyA9IG1lbW9yeS5nZXRFbnRyaWVzQnlUcnVzdExldmVsKFRSVVNUX0xFVkVMUy5VTlRSVVNURUQpO1xuXG4gICAgZm9yIChjb25zdCBlbnRyeSBvZiBlbnRyaWVzKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCB1cGRhdGVkID0gYXdhaXQgdGhpcy52YWxpZGF0ZUVudHJ5KGVudHJ5KTtcbiAgICAgICAgaWYgKHVwZGF0ZWQpIHtcbiAgICAgICAgICB1cGRhdGVkQ291bnQrKztcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbG9nZ2VyLmVycm9yKCdFcnJvciB2YWxpZGF0aW5nIGVudHJ5Jywge1xuICAgICAgICAgIGVudHJ5SWQ6IGVudHJ5LmlkLFxuICAgICAgICAgIGVycm9yLFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAodXBkYXRlZENvdW50ID4gMCkge1xuICAgICAgbG9nZ2VyLmluZm8oJ1VwZGF0ZWQgdHJ1c3QgbGV2ZWxzIGluIG1lbW9yeScsIHtcbiAgICAgICAgbWVtb3J5SWQ6IG1lbW9yeS5pZCxcbiAgICAgICAgdXBkYXRlZENvdW50LFxuICAgICAgfSk7XG5cbiAgICAgIC8vIERJIFJFRkFDVE9SOiBVc2UgaW5qZWN0ZWQgTWVtb3J5TWFuYWdlciB0byBzYXZlXG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB0aGlzLm1lbW9yeU1hbmFnZXIuc2F2ZShtZW1vcnkpO1xuICAgICAgICBsb2dnZXIuZGVidWcoJ01lbW9yeSBzYXZlZCBzdWNjZXNzZnVsbHknLCB7XG4gICAgICAgICAgbWVtb3J5SWQ6IG1lbW9yeS5pZFxuICAgICAgICB9KTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGxvZ2dlci5lcnJvcignRmFpbGVkIHRvIHNhdmUgbWVtb3J5IGFmdGVyIHZhbGlkYXRpb24nLCB7XG4gICAgICAgICAgbWVtb3J5SWQ6IG1lbW9yeS5pZCxcbiAgICAgICAgICBlcnJvclxuICAgICAgICB9KTtcbiAgICAgICAgLy8gRG9uJ3QgdGhyb3cgLSBjb250aW51ZSB3aXRoIG90aGVyIG1lbW9yaWVzXG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGEgc2luZ2xlIG1lbW9yeSBlbnRyeSBhbmQgdXBkYXRlIGl0cyB0cnVzdCBsZXZlbFxuICAgKlxuICAgKiBAcGFyYW0gZW50cnkgLSBUaGUgbWVtb3J5IGVudHJ5IHRvIHZhbGlkYXRlXG4gICAqIEByZXR1cm5zIHRydWUgaWYgdGhlIGVudHJ5IHdhcyB1cGRhdGVkLCBmYWxzZSBvdGhlcndpc2VcbiAgICovXG4gIHByaXZhdGUgYXN5bmMgdmFsaWRhdGVFbnRyeShlbnRyeTogYW55KTogUHJvbWlzZTxib29sZWFuPiB7XG4gICAgbG9nZ2VyLmRlYnVnKCdWYWxpZGF0aW5nIGVudHJ5JywgeyBlbnRyeUlkOiBlbnRyeS5pZCB9KTtcblxuICAgIC8vIFZhbGlkYXRlIGNvbnRlbnQgdXNpbmcgQ29udGVudFZhbGlkYXRvclxuICAgIGNvbnN0IHZhbGlkYXRpb25SZXN1bHQgPSBDb250ZW50VmFsaWRhdG9yLnZhbGlkYXRlQW5kU2FuaXRpemUoZW50cnkuY29udGVudCwge1xuICAgICAgc2tpcFNpemVDaGVjazogdHJ1ZSxcbiAgICB9KTtcblxuICAgIC8vIERldGVybWluZSBuZXcgdHJ1c3QgbGV2ZWwgYmFzZWQgb24gdmFsaWRhdGlvbiByZXN1bHRzXG4gICAgY29uc3QgbmV3VHJ1c3RMZXZlbCA9IHRoaXMuZGV0ZXJtaW5lVHJ1c3RMZXZlbCh2YWxpZGF0aW9uUmVzdWx0KTtcblxuICAgIGlmIChuZXdUcnVzdExldmVsID09PSBlbnRyeS50cnVzdExldmVsKSB7XG4gICAgICBsb2dnZXIuZGVidWcoJ1RydXN0IGxldmVsIHVuY2hhbmdlZCcsIHtcbiAgICAgICAgZW50cnlJZDogZW50cnkuaWQsXG4gICAgICAgIHRydXN0TGV2ZWw6IGVudHJ5LnRydXN0TGV2ZWwsXG4gICAgICB9KTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgdHJ1c3QgbGV2ZWxcbiAgICBlbnRyeS50cnVzdExldmVsID0gbmV3VHJ1c3RMZXZlbDtcblxuICAgIGxvZ2dlci5pbmZvKCdVcGRhdGVkIGVudHJ5IHRydXN0IGxldmVsJywge1xuICAgICAgZW50cnlJZDogZW50cnkuaWQsXG4gICAgICBvbGRUcnVzdExldmVsOiBUUlVTVF9MRVZFTFMuVU5UUlVTVEVELFxuICAgICAgbmV3VHJ1c3RMZXZlbCxcbiAgICAgIGRldGVjdGVkUGF0dGVybnM6IHZhbGlkYXRpb25SZXN1bHQuZGV0ZWN0ZWRQYXR0ZXJucz8ubGVuZ3RoIHx8IDAsXG4gICAgfSk7XG5cbiAgICAvLyBJZiBGTEFHR0VELCBleHRyYWN0IHBhdHRlcm5zIGFuZCBjcmVhdGUgc2FuaXRpemVkIGNvbnRlbnRcbiAgICBpZiAobmV3VHJ1c3RMZXZlbCA9PT0gVFJVU1RfTEVWRUxTLkZMQUdHRUQpIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZygnRW50cnkgZmxhZ2dlZCwgZXh0cmFjdGluZyBwYXR0ZXJucycsIHtcbiAgICAgICAgZW50cnlJZDogZW50cnkuaWQsXG4gICAgICAgIHBhdHRlcm5zOiB2YWxpZGF0aW9uUmVzdWx0LmRldGVjdGVkUGF0dGVybnMsXG4gICAgICB9KTtcblxuICAgICAgLy8gUGhhc2UgMTogRXh0cmFjdCBwYXR0ZXJucyBhbmQgY3JlYXRlIHNhbml0aXplZCBjb250ZW50XG4gICAgICBjb25zdCBleHRyYWN0aW9uUmVzdWx0ID0gdGhpcy5wYXR0ZXJuRXh0cmFjdG9yLmV4dHJhY3RQYXR0ZXJucyhcbiAgICAgICAgZW50cnkuY29udGVudCxcbiAgICAgICAgdmFsaWRhdGlvblJlc3VsdFxuICAgICAgKTtcblxuICAgICAgLy8gU3RvcmUgc2FuaXRpemVkIHBhdHRlcm5zIGFuZCBjb250ZW50IGluIGVudHJ5IG1ldGFkYXRhXG4gICAgICAvLyBQaGFzZSAyIHdpbGwgYWRkIGVuY3J5cHRpb24gdG8gdGhlc2UgcGF0dGVybnNcbiAgICAgIGVudHJ5LnNhbml0aXplZFBhdHRlcm5zID0gZXh0cmFjdGlvblJlc3VsdC5wYXR0ZXJucztcbiAgICAgIGVudHJ5LnNhbml0aXplZENvbnRlbnQgPSBleHRyYWN0aW9uUmVzdWx0LnNhbml0aXplZENvbnRlbnQ7XG5cbiAgICAgIGxvZ2dlci5pbmZvKCdQYXR0ZXJucyBleHRyYWN0ZWQgZnJvbSBlbnRyeScsIHtcbiAgICAgICAgZW50cnlJZDogZW50cnkuaWQsXG4gICAgICAgIHBhdHRlcm5Db3VudDogZXh0cmFjdGlvblJlc3VsdC5wYXR0ZXJuQ291bnQsXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEZXRlcm1pbmUgdGhlIGFwcHJvcHJpYXRlIHRydXN0IGxldmVsIGJhc2VkIG9uIHZhbGlkYXRpb24gcmVzdWx0c1xuICAgKi9cbiAgcHJpdmF0ZSBkZXRlcm1pbmVUcnVzdExldmVsKHZhbGlkYXRpb25SZXN1bHQ6IENvbnRlbnRWYWxpZGF0aW9uUmVzdWx0KTogc3RyaW5nIHtcbiAgICAvLyBJZiB2YWxpZGF0aW9uIHBhc3NlZCB3aXRoIG5vIHBhdHRlcm5zLCBtYXJrIGFzIFZBTElEQVRFRFxuICAgIGlmICh2YWxpZGF0aW9uUmVzdWx0LmlzVmFsaWQgJiYgKCF2YWxpZGF0aW9uUmVzdWx0LmRldGVjdGVkUGF0dGVybnMgfHwgdmFsaWRhdGlvblJlc3VsdC5kZXRlY3RlZFBhdHRlcm5zLmxlbmd0aCA9PT0gMCkpIHtcbiAgICAgIHJldHVybiBUUlVTVF9MRVZFTFMuVkFMSURBVEVEO1xuICAgIH1cblxuICAgIC8vIElmIGNyaXRpY2FsL2hpZ2ggc2V2ZXJpdHkgdGhyZWF0cyBkZXRlY3RlZCwgbWFyayBhcyBGTEFHR0VEXG4gICAgaWYgKHZhbGlkYXRpb25SZXN1bHQuc2V2ZXJpdHkgPT09ICdjcml0aWNhbCcgfHwgdmFsaWRhdGlvblJlc3VsdC5zZXZlcml0eSA9PT0gJ2hpZ2gnKSB7XG4gICAgICAvLyBQSEFTRSAxIElOQ09NUExFVEU6IFFVQVJBTlRJTkVEIHRydXN0IGxldmVsIGxvZ2ljIGRlZmVycmVkIChJc3N1ZSAjMTMxNClcbiAgICAgIC8vIFdpbGwgYWRkIGRpc3RpbmN0aW9uIGJldHdlZW4gRkxBR0dFRCAoZGFuZ2Vyb3VzKSB2cyBRVUFSQU5USU5FRCAobWFsaWNpb3VzKVxuICAgICAgLy8gRm9yIG5vdywgYWxsIGhpZ2gvY3JpdGljYWwgc2V2ZXJpdHkgZ29lcyB0byBGTEFHR0VEXG4gICAgICByZXR1cm4gVFJVU1RfTEVWRUxTLkZMQUdHRUQ7XG4gICAgfVxuXG4gICAgLy8gTWVkaXVtL2xvdyBzZXZlcml0eSBvciBtaW5vciBpc3N1ZXMgLSBrZWVwIGFzIFVOVFJVU1RFRCBmb3Igbm93XG4gICAgLy8gKENvdWxkIGJlIFZBTElEQVRFRCBpbiBhIG1vcmUgbGVuaWVudCBwb2xpY3kpXG4gICAgcmV0dXJuIFRSVVNUX0xFVkVMUy5VTlRSVVNURUQ7XG4gIH1cblxuICAvKipcbiAgICogU3BsaXQgYW4gYXJyYXkgaW50byBiYXRjaGVzIG9mIHNwZWNpZmllZCBzaXplXG4gICAqL1xuICBwcml2YXRlIGNyZWF0ZUJhdGNoZXM8VD4oaXRlbXM6IFRbXSwgYmF0Y2hTaXplOiBudW1iZXIpOiBUW11bXSB7XG4gICAgY29uc3QgYmF0Y2hlczogVFtdW10gPSBbXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGl0ZW1zLmxlbmd0aDsgaSArPSBiYXRjaFNpemUpIHtcbiAgICAgIGJhdGNoZXMucHVzaChpdGVtcy5zbGljZShpLCBpICsgYmF0Y2hTaXplKSk7XG4gICAgfVxuICAgIHJldHVybiBiYXRjaGVzO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBjdXJyZW50IHZhbGlkYXRpb24gc3RhdGlzdGljc1xuICAgKi9cbiAgZ2V0U3RhdHMoKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGVuYWJsZWQ6IHRoaXMuY29uZmlnLmVuYWJsZWQsXG4gICAgICBpc1Byb2Nlc3Npbmc6IHRoaXMuaXNQcm9jZXNzaW5nLFxuICAgICAgaW50ZXJ2YWxTZWNvbmRzOiB0aGlzLmNvbmZpZy5pbnRlcnZhbFNlY29uZHMsXG4gICAgICBiYXRjaFNpemU6IHRoaXMuY29uZmlnLmJhdGNoU2l6ZSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIERpc3Bvc2Ugb2YgdGhlIHZhbGlkYXRvciBhbmQgY2xlYW4gdXAgcmVzb3VyY2VzXG4gICAqIEltcGxlbWVudHMgY2xlYW51cCBmb3IgcHJvcGVyIERJIGxpZmVjeWNsZSBtYW5hZ2VtZW50XG4gICAqL1xuICBhc3luYyBkaXNwb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuc3RvcCgpO1xuICAgIGxvZ2dlci5kZWJ1ZygnQmFja2dyb3VuZFZhbGlkYXRvciBkaXNwb3NlZCcpO1xuICB9XG59XG4iXX0=