UNPKG

firewalla-mcp-server

Version:

Model Context Protocol (MCP) server for Firewalla MSP API - Provides real-time network monitoring, security analysis, and firewall management through 28 specialized tools compatible with any MCP client

258 lines 9.07 kB
/** * Time Range Filter Implementation * Handles timestamp-based filtering for flows, alarms, and rules */ import { BaseFilter } from './base.js'; import { unixToISOString } from '../../utils/timestamp.js'; export class TimeRangeFilter extends BaseFilter { constructor() { super(...arguments); this.name = 'time_range'; this.timeFields = [ 'timestamp', 'ts', 'created_at', 'updated_at', 'last_updated', 'lastSeen', ]; /** * Standardized time margin for timestamp matching (30 seconds) * * This margin is used consistently across both API-level filtering and * post-processing to ensure identical behavior regardless of where * the filtering occurs. The 30-second value provides a reasonable * balance between accuracy and flexibility while accounting for: * * - Network latency between client and Firewalla API (typically <5s) * - Clock synchronization differences between systems (typically <10s) * - Timestamp precision variations (seconds vs milliseconds) * - Small delays in data processing pipelines (<15s) * * Using a consistent value prevents subtle bugs where the same query * might return different results for different entity types based on * whether they support API-level time filtering or require post-processing. * * Note: Previously used inconsistent values (10s for API, 60s for post-processing) * which caused discrepancies. Standardized to 30s for optimal balance. */ this.STANDARD_TIME_MARGIN = 30; // seconds } canHandle(node) { if (node.type === 'field' || node.type === 'range' || node.type === 'comparison') { return this.timeFields.includes(node.field); } return false; } apply(node, context) { switch (node.type) { case 'field': { return this.handleFieldQuery(node, context); } case 'range': { return this.handleRangeQuery(node, context); } case 'comparison': { return this.handleComparisonQuery(node, context); } case 'logical': case 'group': case 'wildcard': { // These node types are not handled by time filter return { apiParams: {} }; } default: { return { apiParams: {} }; } } } handleFieldQuery(node, context) { const timestamp = this.parseTimestamp(node.value); if (timestamp === null) { return { apiParams: {} }; } // For exact timestamp matches, use a configurable small range const margin = context.timeMargin || this.STANDARD_TIME_MARGIN; return { apiParams: this.buildTimeParams(timestamp - margin, timestamp + margin, context), cacheKeyComponent: this.createCacheKey(node), }; } handleRangeQuery(node, context) { const minTime = node.min ? this.parseTimestamp(node.min) : null; const maxTime = node.max ? this.parseTimestamp(node.max) : null; return { apiParams: this.buildTimeParams(minTime, maxTime, context), cacheKeyComponent: this.createCacheKey(node), }; } handleComparisonQuery(node, context) { const timestamp = this.parseTimestamp(node.value); if (timestamp === null) { return { apiParams: {} }; } let minTime = null; let maxTime = null; switch (node.operator) { case '>': { minTime = timestamp + 1; break; } case '>=': { minTime = timestamp; break; } case '<': { maxTime = timestamp - 1; break; } case '<=': { maxTime = timestamp; break; } } return { apiParams: this.buildTimeParams(minTime, maxTime, context), cacheKeyComponent: this.createCacheKey(node), }; } buildTimeParams(minTime, maxTime, context) { const params = {}; // Different entities use different parameter names switch (context.entityType) { case 'flows': { if (minTime) { params.start_time = unixToISOString(minTime); } if (maxTime) { params.end_time = unixToISOString(maxTime); } break; } case 'alarms': { if (minTime) { params.since = minTime; } if (maxTime) { params.until = maxTime; } break; } case 'rules': { // Rules API might not support time filtering directly // Will need post-processing break; } case 'devices': { // Device API doesn't typically support time filtering break; } case 'target_lists': { // Target lists might filter by last_updated if (minTime) { params.updated_since = minTime; } break; } } return params; } /** * Create post-processing filter for entities that don't support API-level time filtering */ createPostProcessingFilter(node) { return (items) => { return items.filter(item => { const timestamp = this.extractTimestamp(item); if (timestamp === null) { return true; } // Keep items without timestamps return this.matchesTimeCondition(timestamp, node); }); }; } extractTimestamp(item) { // Try different timestamp fields for (const field of this.timeFields) { const value = this.getNestedValue(item, field); if (value) { const parsed = this.parseTimestamp(value); if (parsed !== null) { return parsed; } } } return null; } matchesTimeCondition(timestamp, node) { switch (node.type) { case 'field': { const targetTime = this.parseTimestamp(node.value); return Boolean(targetTime && Math.abs(timestamp - targetTime) <= this.STANDARD_TIME_MARGIN); } case 'range': { const rangeNode = node; const min = rangeNode.min ? this.parseTimestamp(rangeNode.min) : null; const max = rangeNode.max ? this.parseTimestamp(rangeNode.max) : null; if (min && timestamp < min) { return false; } if (max && timestamp > max) { return false; } return true; } case 'comparison': { const compNode = node; const compTime = this.parseTimestamp(compNode.value); if (!compTime) { return false; } switch (compNode.operator) { case '>': { return timestamp > compTime; } case '>=': { return timestamp >= compTime; } case '<': { return timestamp < compTime; } case '<=': { return timestamp <= compTime; } default: { return false; } } } case 'group': case 'logical': case 'wildcard': // These node types are not applicable for time-based filtering return false; default: return false; } } getOptimizations() { return [ { type: 'index', priority: 10, description: 'Time-based queries can use timestamp indexes', condition: (context) => context.entityType === 'flows' || context.entityType === 'alarms', }, { type: 'pushdown', priority: 8, description: 'Time filters should be applied at API level when possible', condition: (context) => context.entityType !== 'rules', }, ]; } } //# sourceMappingURL=time.js.map