UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.

342 lines 27.6 kB
import * as plugins from '../../plugins.js'; import { matchRouteDomain, calculateRouteSpecificity } from './route-utils.js'; import { DomainMatcher, PathMatcher, IpMatcher } from './matchers/index.js'; /** * Shared RouteManager used by both SmartProxy and NetworkProxy * * This provides a unified implementation for route management, * route matching, and port handling. */ export class SharedRouteManager extends plugins.EventEmitter { constructor(options) { super(); this.routes = []; this.portMap = new Map(); /** * Memoization cache for expanded port ranges */ this.portRangeCache = new Map(); // Set up logger (use console if not provided) this.logger = options.logger || { info: console.log, warn: console.warn, error: console.error, debug: options.enableDetailedLogging ? console.log : undefined }; this.enableDetailedLogging = options.enableDetailedLogging || false; // Initialize routes if provided if (options.routes) { this.updateRoutes(options.routes); } } /** * Update routes with new configuration */ updateRoutes(routes = []) { // Sort routes by priority (higher first) this.routes = [...(routes || [])].sort((a, b) => { const priorityA = a.priority ?? 0; const priorityB = b.priority ?? 0; return priorityB - priorityA; }); // Rebuild port mapping for fast lookups this.rebuildPortMap(); this.logger.info(`Updated RouteManager with ${this.routes.length} routes`); } /** * Get all routes */ getRoutes() { return [...this.routes]; } /** * Rebuild the port mapping for fast lookups * Also logs information about the ports being listened on */ rebuildPortMap() { this.portMap.clear(); this.portRangeCache.clear(); // Clear cache when rebuilding // Track ports for logging const portToRoutesMap = new Map(); for (const route of this.routes) { const ports = this.expandPortRange(route.match.ports); // Skip if no ports were found if (ports.length === 0) { this.logger.warn(`Route ${route.name || 'unnamed'} has no valid ports to listen on`); continue; } for (const port of ports) { // Add to portMap for routing if (!this.portMap.has(port)) { this.portMap.set(port, []); } this.portMap.get(port).push(route); // Add to tracking for logging if (!portToRoutesMap.has(port)) { portToRoutesMap.set(port, []); } portToRoutesMap.get(port).push(route.name || 'unnamed'); } } // Log summary of ports and routes const totalPorts = this.portMap.size; const totalRoutes = this.routes.length; this.logger.info(`Route manager configured with ${totalRoutes} routes across ${totalPorts} ports`); // Log port details if detailed logging is enabled if (this.enableDetailedLogging) { for (const [port, routes] of this.portMap.entries()) { this.logger.info(`Port ${port}: ${routes.length} routes (${portToRoutesMap.get(port).join(', ')})`); } } } /** * Expand a port range specification into an array of individual ports * Uses caching to improve performance for frequently used port ranges * * @public - Made public to allow external code to interpret port ranges */ expandPortRange(portRange) { // For simple number, return immediately if (typeof portRange === 'number') { return [portRange]; } // Create a cache key for this port range const cacheKey = JSON.stringify(portRange); // Check if we have a cached result if (this.portRangeCache.has(cacheKey)) { return this.portRangeCache.get(cacheKey); } // Process the port range let result = []; if (Array.isArray(portRange)) { // Handle array of port objects or numbers result = portRange.flatMap(item => { if (typeof item === 'number') { return [item]; } else if (typeof item === 'object' && 'from' in item && 'to' in item) { // Handle port range object - check valid range if (item.from > item.to) { this.logger.warn(`Invalid port range: from (${item.from}) > to (${item.to})`); return []; } // Handle port range object const ports = []; for (let p = item.from; p <= item.to; p++) { ports.push(p); } return ports; } return []; }); } // Cache the result this.portRangeCache.set(cacheKey, result); return result; } /** * Get all ports that should be listened on * This method automatically infers all required ports from route configurations */ getListeningPorts() { // Return the unique set of ports from all routes return Array.from(this.portMap.keys()); } /** * Get all routes for a given port */ getRoutesForPort(port) { return this.portMap.get(port) || []; } /** * Find the matching route for a connection */ findMatchingRoute(context) { // Get routes for this port if using port-based filtering const routesToCheck = context.port ? (this.portMap.get(context.port) || []) : this.routes; // Find the first matching route based on priority order for (const route of routesToCheck) { if (this.matchesRoute(route, context)) { return { route }; } } return null; } /** * Check if a route matches the given context */ matchesRoute(route, context) { // Skip disabled routes if (route.enabled === false) { return false; } // Check port match if provided in context if (context.port !== undefined) { const ports = this.expandPortRange(route.match.ports); if (!ports.includes(context.port)) { return false; } } // Check domain match if specified if (route.match.domains && context.domain) { const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains]; if (!domains.some(domainPattern => DomainMatcher.match(domainPattern, context.domain))) { return false; } } // Check path match if specified if (route.match.path && context.path) { if (!PathMatcher.match(route.match.path, context.path).matches) { return false; } } // Check client IP match if specified if (route.match.clientIp && context.clientIp) { if (!route.match.clientIp.some(ip => IpMatcher.match(ip, context.clientIp))) { return false; } } // Check TLS version match if specified if (route.match.tlsVersion && context.tlsVersion) { if (!route.match.tlsVersion.includes(context.tlsVersion)) { return false; } } // Check header match if specified if (route.match.headers && context.headers) { for (const [headerName, expectedValue] of Object.entries(route.match.headers)) { const actualValue = context.headers[headerName.toLowerCase()]; // If header doesn't exist, no match if (actualValue === undefined) { return false; } // Match against string or regex if (typeof expectedValue === 'string') { if (actualValue !== expectedValue) { return false; } } else if (expectedValue instanceof RegExp) { if (!expectedValue.test(actualValue)) { return false; } } } } // All criteria matched return true; } /** * Validate the route configuration and return any warnings */ validateConfiguration() { const warnings = []; const duplicatePorts = new Map(); // Check for routes with the same exact match criteria for (let i = 0; i < this.routes.length; i++) { for (let j = i + 1; j < this.routes.length; j++) { const route1 = this.routes[i]; const route2 = this.routes[j]; // Check if route match criteria are the same if (this.areMatchesSimilar(route1.match, route2.match)) { warnings.push(`Routes "${route1.name || i}" and "${route2.name || j}" have similar match criteria. ` + `The route with higher priority (${Math.max(route1.priority || 0, route2.priority || 0)}) will be used.`); } } } // Check for routes that may never be matched due to priority for (let i = 0; i < this.routes.length; i++) { const route = this.routes[i]; const higherPriorityRoutes = this.routes.filter(r => (r.priority || 0) > (route.priority || 0)); for (const higherRoute of higherPriorityRoutes) { if (this.isRouteShadowed(route, higherRoute)) { warnings.push(`Route "${route.name || i}" may never be matched because it is shadowed by ` + `higher priority route "${higherRoute.name || 'unnamed'}"`); break; } } } return warnings; } /** * Check if two route matches are similar (potential conflict) */ areMatchesSimilar(match1, match2) { // Check port overlap const ports1 = new Set(this.expandPortRange(match1.ports)); const ports2 = new Set(this.expandPortRange(match2.ports)); let havePortOverlap = false; for (const port of ports1) { if (ports2.has(port)) { havePortOverlap = true; break; } } if (!havePortOverlap) { return false; } // Check domain overlap if (match1.domains && match2.domains) { const domains1 = Array.isArray(match1.domains) ? match1.domains : [match1.domains]; const domains2 = Array.isArray(match2.domains) ? match2.domains : [match2.domains]; // Check if any domain pattern from match1 could match any from match2 let haveDomainOverlap = false; for (const domain1 of domains1) { for (const domain2 of domains2) { if (domain1 === domain2 || (domain1.includes('*') || domain2.includes('*'))) { haveDomainOverlap = true; break; } } if (haveDomainOverlap) break; } if (!haveDomainOverlap) { return false; } } else if (match1.domains || match2.domains) { // One has domains, the other doesn't - they could overlap // The one with domains is more specific, so it's not exactly a conflict return false; } // Check path overlap if (match1.path && match2.path) { // This is a simplified check - in a real implementation, // you'd need to check if the path patterns could match the same paths return match1.path === match2.path || match1.path.includes('*') || match2.path.includes('*'); } else if (match1.path || match2.path) { // One has a path, the other doesn't return false; } // If we get here, the matches have significant overlap return true; } /** * Check if a route is completely shadowed by a higher priority route */ isRouteShadowed(route, higherPriorityRoute) { // If they don't have similar match criteria, no shadowing occurs if (!this.areMatchesSimilar(route.match, higherPriorityRoute.match)) { return false; } // If higher priority route has more specific criteria, no shadowing const routeSpecificity = calculateRouteSpecificity(route.match); const higherRouteSpecificity = calculateRouteSpecificity(higherPriorityRoute.match); if (higherRouteSpecificity > routeSpecificity) { return false; } // If higher priority route is equally or less specific but has higher priority, // it shadows the lower priority route return true; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL2NvcmUvcm91dGluZy9yb3V0ZS1tYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFRNUMsT0FBTyxFQUNMLGdCQUFnQixFQUNoQix5QkFBeUIsRUFDMUIsTUFBTSxrQkFBa0IsQ0FBQztBQUMxQixPQUFPLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQXFCNUU7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sa0JBQW1CLFNBQVEsT0FBTyxDQUFDLFlBQVk7SUFXMUQsWUFBWSxPQUlYO1FBQ0MsS0FBSyxFQUFFLENBQUM7UUFmRixXQUFNLEdBQW1CLEVBQUUsQ0FBQztRQUM1QixZQUFPLEdBQWdDLElBQUksR0FBRyxFQUFFLENBQUM7UUFJekQ7O1dBRUc7UUFDSyxtQkFBYyxHQUEwQixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBU3hELDhDQUE4QztRQUM5QyxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLElBQUk7WUFDOUIsSUFBSSxFQUFFLE9BQU8sQ0FBQyxHQUFHO1lBQ2pCLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSTtZQUNsQixLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7WUFDcEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsU0FBUztTQUMvRCxDQUFDO1FBRUYsSUFBSSxDQUFDLHFCQUFxQixHQUFHLE9BQU8sQ0FBQyxxQkFBcUIsSUFBSSxLQUFLLENBQUM7UUFFcEUsZ0NBQWdDO1FBQ2hDLElBQUksT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ25CLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BDLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxZQUFZLENBQUMsU0FBeUIsRUFBRTtRQUM3Qyx5Q0FBeUM7UUFDekMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxNQUFNLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDOUMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxDQUFDLFFBQVEsSUFBSSxDQUFDLENBQUM7WUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxDQUFDLFFBQVEsSUFBSSxDQUFDLENBQUM7WUFDbEMsT0FBTyxTQUFTLEdBQUcsU0FBUyxDQUFDO1FBQy9CLENBQUMsQ0FBQyxDQUFDO1FBRUgsd0NBQXdDO1FBQ3hDLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUV0QixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyw2QkFBNkIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLFNBQVMsQ0FBQyxDQUFDO0lBQzdFLENBQUM7SUFFRDs7T0FFRztJQUNJLFNBQVM7UUFDZCxPQUFPLENBQUMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDMUIsQ0FBQztJQUVEOzs7T0FHRztJQUNLLGNBQWM7UUFDcEIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNyQixJQUFJLENBQUMsY0FBYyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsOEJBQThCO1FBRTNELDBCQUEwQjtRQUMxQixNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsRUFBb0IsQ0FBQztRQUVwRCxLQUFLLE1BQU0sS0FBSyxJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNoQyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7WUFFdEQsOEJBQThCO1lBQzlCLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDdkIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxLQUFLLENBQUMsSUFBSSxJQUFJLFNBQVMsa0NBQWtDLENBQUMsQ0FBQztnQkFDckYsU0FBUztZQUNYLENBQUM7WUFFRCxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssRUFBRSxDQUFDO2dCQUN6Qiw2QkFBNkI7Z0JBQzdCLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO29CQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7Z0JBQzdCLENBQUM7Z0JBQ0QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFFLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUVwQyw4QkFBOEI7Z0JBQzlCLElBQUksQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7b0JBQy9CLGVBQWUsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUNoQyxDQUFDO2dCQUNELGVBQWUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFFLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLElBQUksU0FBUyxDQUFDLENBQUM7WUFDM0QsQ0FBQztRQUNILENBQUM7UUFFRCxrQ0FBa0M7UUFDbEMsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDckMsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDdkMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsaUNBQWlDLFdBQVcsa0JBQWtCLFVBQVUsUUFBUSxDQUFDLENBQUM7UUFFbkcsa0RBQWtEO1FBQ2xELElBQUksSUFBSSxDQUFDLHFCQUFxQixFQUFFLENBQUM7WUFDL0IsS0FBSyxNQUFNLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztnQkFDcEQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxJQUFJLEtBQUssTUFBTSxDQUFDLE1BQU0sWUFBWSxlQUFlLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBRSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDdkcsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxlQUFlLENBQUMsU0FBcUI7UUFDMUMsd0NBQXdDO1FBQ3hDLElBQUksT0FBTyxTQUFTLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDbEMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3JCLENBQUM7UUFFRCx5Q0FBeUM7UUFDekMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUUzQyxtQ0FBbUM7UUFDbkMsSUFBSSxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ3RDLE9BQU8sSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFFLENBQUM7UUFDNUMsQ0FBQztRQUVELHlCQUF5QjtRQUN6QixJQUFJLE1BQU0sR0FBYSxFQUFFLENBQUM7UUFFMUIsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7WUFDN0IsMENBQTBDO1lBQzFDLE1BQU0sR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFO2dCQUNoQyxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsRUFBRSxDQUFDO29CQUM3QixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ2hCLENBQUM7cUJBQU0sSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksTUFBTSxJQUFJLElBQUksSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUM7b0JBQ3RFLCtDQUErQztvQkFDL0MsSUFBSSxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQzt3QkFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsNkJBQTZCLElBQUksQ0FBQyxJQUFJLFdBQVcsSUFBSSxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7d0JBQzlFLE9BQU8sRUFBRSxDQUFDO29CQUNaLENBQUM7b0JBRUQsMkJBQTJCO29CQUMzQixNQUFNLEtBQUssR0FBYSxFQUFFLENBQUM7b0JBQzNCLEtBQUssSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO3dCQUMxQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO29CQUNoQixDQUFDO29CQUNELE9BQU8sS0FBSyxDQUFDO2dCQUNmLENBQUM7Z0JBQ0QsT0FBTyxFQUFFLENBQUM7WUFDWixDQUFDLENBQUMsQ0FBQztRQUNMLENBQUM7UUFFRCxtQkFBbUI7UUFDbkIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRTFDLE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFRDs7O09BR0c7SUFDSSxpQkFBaUI7UUFDdEIsaURBQWlEO1FBQ2pELE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksZ0JBQWdCLENBQUMsSUFBWTtRQUNsQyxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN0QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxpQkFBaUIsQ0FBQyxPQUFzQjtRQUM3Qyx5REFBeUQ7UUFDekQsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLElBQUk7WUFDaEMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUN4QyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUVoQix3REFBd0Q7UUFDeEQsS0FBSyxNQUFNLEtBQUssSUFBSSxhQUFhLEVBQUUsQ0FBQztZQUNsQyxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ3RDLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQztZQUNuQixDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVEOztPQUVHO0lBQ0ssWUFBWSxDQUFDLEtBQW1CLEVBQUUsT0FBc0I7UUFDOUQsdUJBQXVCO1FBQ3ZCLElBQUksS0FBSyxDQUFDLE9BQU8sS0FBSyxLQUFLLEVBQUUsQ0FBQztZQUM1QixPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCwwQ0FBMEM7UUFDMUMsSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQy9CLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUN0RCxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDbEMsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1FBQ0gsQ0FBQztRQUVELGtDQUFrQztRQUNsQyxJQUFJLEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUMxQyxNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDO2dCQUNoRCxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPO2dCQUNyQixDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBRTFCLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxhQUFhLEVBQUUsT0FBTyxDQUFDLE1BQU8sQ0FBQyxDQUFDLEVBQUUsQ0FBQztnQkFDeEYsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1FBQ0gsQ0FBQztRQUVELGdDQUFnQztRQUNoQyxJQUFJLEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxJQUFJLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNyQyxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7Z0JBQy9ELE9BQU8sS0FBSyxDQUFDO1lBQ2YsQ0FBQztRQUNILENBQUM7UUFFRCxxQ0FBcUM7UUFDckMsSUFBSSxLQUFLLENBQUMsS0FBSyxDQUFDLFFBQVEsSUFBSSxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDN0MsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxFQUFFLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUM7Z0JBQzVFLE9BQU8sS0FBSyxDQUFDO1lBQ2YsQ0FBQztRQUNILENBQUM7UUFFRCx1Q0FBdUM7UUFDdkMsSUFBSSxLQUFLLENBQUMsS0FBSyxDQUFDLFVBQVUsSUFBSSxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDakQsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztnQkFDekQsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1FBQ0gsQ0FBQztRQUVELGtDQUFrQztRQUNsQyxJQUFJLEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUMzQyxLQUFLLE1BQU0sQ0FBQyxVQUFVLEVBQUUsYUFBYSxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQzlFLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7Z0JBRTlELG9DQUFvQztnQkFDcEMsSUFBSSxXQUFXLEtBQUssU0FBUyxFQUFFLENBQUM7b0JBQzlCLE9BQU8sS0FBSyxDQUFDO2dCQUNmLENBQUM7Z0JBRUQsZ0NBQWdDO2dCQUNoQyxJQUFJLE9BQU8sYUFBYSxLQUFLLFFBQVEsRUFBRSxDQUFDO29CQUN0QyxJQUFJLFdBQVcsS0FBSyxhQUFhLEVBQUUsQ0FBQzt3QkFDbEMsT0FBTyxLQUFLLENBQUM7b0JBQ2YsQ0FBQztnQkFDSCxDQUFDO3FCQUFNLElBQUksYUFBYSxZQUFZLE1BQU0sRUFBRSxDQUFDO29CQUMzQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDO3dCQUNyQyxPQUFPLEtBQUssQ0FBQztvQkFDZixDQUFDO2dCQUNILENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELHVCQUF1QjtRQUN2QixPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFJRDs7T0FFRztJQUNJLHFCQUFxQjtRQUMxQixNQUFNLFFBQVEsR0FBYSxFQUFFLENBQUM7UUFDOUIsTUFBTSxjQUFjLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7UUFFakQsc0RBQXNEO1FBQ3RELEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQzVDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztnQkFDaEQsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDOUIsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFFOUIsNkNBQTZDO2dCQUM3QyxJQUFJLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO29CQUN2RCxRQUFRLENBQUMsSUFBSSxDQUNYLFdBQVcsTUFBTSxDQUFDLElBQUksSUFBSSxDQUFDLFVBQVUsTUFBTSxDQUFDLElBQUksSUFBSSxDQUFDLGlDQUFpQzt3QkFDdEYsbUNBQW1DLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLFFBQVEsSUFBSSxDQUFDLEVBQUUsTUFBTSxDQUFDLFFBQVEsSUFBSSxDQUFDLENBQUMsaUJBQWlCLENBQ3pHLENBQUM7Z0JBQ0osQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO1FBRUQsNkRBQTZEO1FBQzdELEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQzVDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDN0IsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUNsRCxDQUFDLENBQUMsQ0FBQyxRQUFRLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsUUFBUSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFN0MsS0FBSyxNQUFNLFdBQVcsSUFBSSxvQkFBb0IsRUFBRSxDQUFDO2dCQUMvQyxJQUFJLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLFdBQVcsQ0FBQyxFQUFFLENBQUM7b0JBQzdDLFFBQVEsQ0FBQyxJQUFJLENBQ1gsVUFBVSxLQUFLLENBQUMsSUFBSSxJQUFJLENBQUMsbURBQW1EO3dCQUM1RSwwQkFBMEIsV0FBVyxDQUFDLElBQUksSUFBSSxTQUFTLEdBQUcsQ0FDM0QsQ0FBQztvQkFDRixNQUFNO2dCQUNSLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sUUFBUSxDQUFDO0lBQ2xCLENBQUM7SUFFRDs7T0FFRztJQUNLLGlCQUFpQixDQUFDLE1BQW1CLEVBQUUsTUFBbUI7UUFDaEUscUJBQXFCO1FBQ3JCLE1BQU0sTUFBTSxHQUFHLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDM0QsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUUzRCxJQUFJLGVBQWUsR0FBRyxLQUFLLENBQUM7UUFDNUIsS0FBSyxNQUFNLElBQUksSUFBSSxNQUFNLEVBQUUsQ0FBQztZQUMxQixJQUFJLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDckIsZUFBZSxHQUFHLElBQUksQ0FBQztnQkFDdkIsTUFBTTtZQUNSLENBQUM7UUFDSCxDQUFDO1FBRUQsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQ3JCLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztRQUVELHVCQUF1QjtRQUN2QixJQUFJLE1BQU0sQ0FBQyxPQUFPLElBQUksTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3JDLE1BQU0sUUFBUSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNuRixNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7WUFFbkYsc0VBQXNFO1lBQ3RFLElBQUksaUJBQWlCLEdBQUcsS0FBSyxDQUFDO1lBQzlCLEtBQUssTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFLENBQUM7Z0JBQy9CLEtBQUssTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFLENBQUM7b0JBQy9CLElBQUksT0FBTyxLQUFLLE9BQU87d0JBQ25CLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQzt3QkFDckQsaUJBQWlCLEdBQUcsSUFBSSxDQUFDO3dCQUN6QixNQUFNO29CQUNSLENBQUM7Z0JBQ0gsQ0FBQztnQkFDRCxJQUFJLGlCQUFpQjtvQkFBRSxNQUFNO1lBQy9CLENBQUM7WUFFRCxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztnQkFDdkIsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1FBQ0gsQ0FBQzthQUFNLElBQUksTUFBTSxDQUFDLE9BQU8sSUFBSSxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDNUMsMERBQTBEO1lBQzFELHdFQUF3RTtZQUN4RSxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCxxQkFBcUI7UUFDckIsSUFBSSxNQUFNLENBQUMsSUFBSSxJQUFJLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUMvQix5REFBeUQ7WUFDekQsc0VBQXNFO1lBQ3RFLE9BQU8sTUFBTSxDQUFDLElBQUksS0FBSyxNQUFNLENBQUMsSUFBSTtnQkFDM0IsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDO2dCQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQyxDQUFDO2FBQU0sSUFBSSxNQUFNLENBQUMsSUFBSSxJQUFJLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUN0QyxvQ0FBb0M7WUFDcEMsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBRUQsdURBQXVEO1FBQ3ZELE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVEOztPQUVHO0lBQ0ssZUFBZSxDQUFDLEtBQW1CLEVBQUUsbUJBQWlDO1FBQzVFLGlFQUFpRTtRQUNqRSxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsbUJBQW1CLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNwRSxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCxvRUFBb0U7UUFDcEUsTUFBTSxnQkFBZ0IsR0FBRyx5QkFBeUIsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDaEUsTUFBTSxzQkFBc0IsR0FBRyx5QkFBeUIsQ0FBQyxtQkFBbUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUVwRixJQUFJLHNCQUFzQixHQUFHLGdCQUFnQixFQUFFLENBQUM7WUFDOUMsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBRUQsZ0ZBQWdGO1FBQ2hGLHNDQUFzQztRQUN0QyxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7Q0FFRiJ9