/** * Protocol Detector * * Simplified protocol detection using the new architecture */ import type { IDetectionResult, IDetectionOptions } from './models/detection-types.js'; import type { IConnectionContext } from '../protocols/common/types.js'; import { TlsDetector } from './detectors/tls-detector.js'; import { HttpDetector } from './detectors/http-detector.js'; import { DetectionFragmentManager } from './utils/fragment-manager.js'; /** * Main protocol detector class */ export class ProtocolDetector { private static instance: ProtocolDetector; private fragmentManager: DetectionFragmentManager; private tlsDetector: TlsDetector; private httpDetector: HttpDetector; private connectionProtocols: Map = new Map(); constructor() { this.fragmentManager = new DetectionFragmentManager(); this.tlsDetector = new TlsDetector(); this.httpDetector = new HttpDetector(this.fragmentManager); } private static getInstance(): ProtocolDetector { if (!this.instance) { this.instance = new ProtocolDetector(); } return this.instance; } /** * Detect protocol from buffer data */ static async detect(buffer: Buffer, options?: IDetectionOptions): Promise { return this.getInstance().detectInstance(buffer, options); } private async detectInstance(buffer: Buffer, options?: IDetectionOptions): Promise { // Quick sanity check if (!buffer || buffer.length === 0) { return { protocol: 'unknown', connectionInfo: { protocol: 'unknown' }, isComplete: true }; } // Try TLS detection first (more specific) if (this.tlsDetector.canHandle(buffer)) { const tlsResult = this.tlsDetector.detect(buffer, options); if (tlsResult) { return tlsResult; } } // Try HTTP detection if (this.httpDetector.canHandle(buffer)) { const httpResult = this.httpDetector.detect(buffer, options); if (httpResult) { return httpResult; } } // Neither TLS nor HTTP return { protocol: 'unknown', connectionInfo: { protocol: 'unknown' }, isComplete: true }; } /** * Detect protocol with connection tracking for fragmented data * @deprecated Use detectWithContext instead */ static async detectWithConnectionTracking( buffer: Buffer, connectionId: string, options?: IDetectionOptions ): Promise { // Convert connection ID to context const context: IConnectionContext = { id: connectionId, sourceIp: 'unknown', sourcePort: 0, destIp: 'unknown', destPort: 0, timestamp: Date.now() }; return this.getInstance().detectWithContextInstance(buffer, context, options); } /** * Detect protocol with connection context for fragmented data */ static async detectWithContext( buffer: Buffer, context: IConnectionContext, options?: IDetectionOptions ): Promise { return this.getInstance().detectWithContextInstance(buffer, context, options); } private async detectWithContextInstance( buffer: Buffer, context: IConnectionContext, options?: IDetectionOptions ): Promise { // Quick sanity check if (!buffer || buffer.length === 0) { return { protocol: 'unknown', connectionInfo: { protocol: 'unknown' }, isComplete: true }; } const connectionId = DetectionFragmentManager.createConnectionId(context); // Check if we already know the protocol for this connection const knownProtocol = this.connectionProtocols.get(connectionId); if (knownProtocol === 'http') { const result = this.httpDetector.detectWithContext(buffer, context, options); if (result) { if (result.isComplete) { this.connectionProtocols.delete(connectionId); } return result; } } else if (knownProtocol === 'tls') { // Handle TLS with fragment accumulation const handler = this.fragmentManager.getHandler('tls'); const fragmentResult = handler.addFragment(connectionId, buffer); if (fragmentResult.error) { handler.complete(connectionId); this.connectionProtocols.delete(connectionId); return { protocol: 'unknown', connectionInfo: { protocol: 'unknown' }, isComplete: true }; } const result = this.tlsDetector.detect(fragmentResult.buffer!, options); if (result) { if (result.isComplete) { handler.complete(connectionId); this.connectionProtocols.delete(connectionId); } return result; } } // If we don't know the protocol yet, try to detect it if (!knownProtocol) { // First peek to determine protocol type if (this.tlsDetector.canHandle(buffer)) { this.connectionProtocols.set(connectionId, 'tls'); // Handle TLS with fragment accumulation const handler = this.fragmentManager.getHandler('tls'); const fragmentResult = handler.addFragment(connectionId, buffer); if (fragmentResult.error) { handler.complete(connectionId); this.connectionProtocols.delete(connectionId); return { protocol: 'unknown', connectionInfo: { protocol: 'unknown' }, isComplete: true }; } const result = this.tlsDetector.detect(fragmentResult.buffer!, options); if (result) { if (result.isComplete) { handler.complete(connectionId); this.connectionProtocols.delete(connectionId); } return result; } } if (this.httpDetector.canHandle(buffer)) { this.connectionProtocols.set(connectionId, 'http'); const result = this.httpDetector.detectWithContext(buffer, context, options); if (result) { if (result.isComplete) { this.connectionProtocols.delete(connectionId); } return result; } } } // Can't determine protocol return { protocol: 'unknown', connectionInfo: { protocol: 'unknown' }, isComplete: false, bytesNeeded: Math.max( this.tlsDetector.getMinimumBytes(), this.httpDetector.getMinimumBytes() ) }; } /** * Clean up resources */ static cleanup(): void { this.getInstance().cleanupInstance(); } private cleanupInstance(): void { this.fragmentManager.cleanup(); } /** * Destroy detector instance */ static destroy(): void { this.getInstance().destroyInstance(); this.instance = null as any; } private destroyInstance(): void { this.fragmentManager.destroy(); } /** * Clean up old connection tracking entries * * @param _maxAge Maximum age in milliseconds (default: 30 seconds) */ static cleanupConnections(_maxAge: number = 30000): void { // Cleanup is now handled internally by the fragment manager this.getInstance().fragmentManager.cleanup(); } /** * Clean up fragments for a specific connection */ static cleanupConnection(context: IConnectionContext): void { const instance = this.getInstance(); const connectionId = DetectionFragmentManager.createConnectionId(context); // Clean up both TLS and HTTP fragments for this connection instance.fragmentManager.getHandler('tls').complete(connectionId); instance.fragmentManager.getHandler('http').complete(connectionId); // Remove from connection protocols tracking instance.connectionProtocols.delete(connectionId); } /** * Extract domain from connection info */ static extractDomain(connectionInfo: any): string | undefined { return connectionInfo.domain || connectionInfo.sni || connectionInfo.host; } /** * Create a connection ID from connection parameters * @deprecated Use createConnectionContext instead */ static createConnectionId(params: { sourceIp?: string; sourcePort?: number; destIp?: string; destPort?: number; socketId?: string; }): string { // If socketId is provided, use it if (params.socketId) { return params.socketId; } // Otherwise create from connection tuple const { sourceIp = 'unknown', sourcePort = 0, destIp = 'unknown', destPort = 0 } = params; return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`; } /** * Create a connection context from parameters */ static createConnectionContext(params: { sourceIp?: string; sourcePort?: number; destIp?: string; destPort?: number; socketId?: string; }): IConnectionContext { return { id: params.socketId, sourceIp: params.sourceIp || 'unknown', sourcePort: params.sourcePort || 0, destIp: params.destIp || 'unknown', destPort: params.destPort || 0, timestamp: Date.now() }; } }