diff --git a/test/test.acme-http-challenge.ts b/test/test.acme-http-challenge.ts index ea0de3f..62ed7f8 100644 --- a/test/test.acme-http-challenge.ts +++ b/test/test.acme-http-challenge.ts @@ -124,4 +124,4 @@ tap.test('should parse HTTP headers correctly', async (tools) => { await proxy.stop(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.acme-http01-challenge.ts b/test/test.acme-http01-challenge.ts index c47abe8..a70a256 100644 --- a/test/test.acme-http01-challenge.ts +++ b/test/test.acme-http01-challenge.ts @@ -159,4 +159,4 @@ tap.test('should return 404 for non-existent challenge tokens', async (tapTest) await proxy.stop(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.acme-route-creation.ts b/test/test.acme-route-creation.ts index e4dabdc..1e86b4f 100644 --- a/test/test.acme-route-creation.ts +++ b/test/test.acme-route-creation.ts @@ -215,4 +215,4 @@ tap.test('should handle HTTP request parsing correctly', async (tools) => { await proxy.stop(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.acme-simple.ts b/test/test.acme-simple.ts index c028d96..74d8c2a 100644 --- a/test/test.acme-simple.ts +++ b/test/test.acme-simple.ts @@ -117,4 +117,4 @@ tap.test('should configure ACME challenge route', async () => { expect(challengeRoute.action.socketHandler).toBeDefined(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.acme-timing-simple.ts b/test/test.acme-timing-simple.ts index 58b8d81..39b738b 100644 --- a/test/test.acme-timing-simple.ts +++ b/test/test.acme-timing-simple.ts @@ -119,4 +119,4 @@ tap.test('should defer certificate provisioning until ports are ready', async (t await proxy.stop(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.certificate-provisioning.ts b/test/test.certificate-provisioning.ts index 852d9b0..6cab058 100644 --- a/test/test.certificate-provisioning.ts +++ b/test/test.certificate-provisioning.ts @@ -238,4 +238,4 @@ tap.test('should renew certificates', async () => { await proxy.stop(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.certificate-simple.ts b/test/test.certificate-simple.ts index 85a21d7..122d46b 100644 --- a/test/test.certificate-simple.ts +++ b/test/test.certificate-simple.ts @@ -57,4 +57,4 @@ tap.test('should handle socket handler route type', async () => { expect(route.action.socketHandler).toBeDefined(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.cleanup-queue-bug.node.ts b/test/test.cleanup-queue-bug.node.ts index 88e7134..544d026 100644 --- a/test/test.cleanup-queue-bug.node.ts +++ b/test/test.cleanup-queue-bug.node.ts @@ -143,4 +143,4 @@ tap.test('cleanup queue bug - verify queue processing handles more than batch si console.log('\n✓ Test complete: Cleanup queue now correctly processes all connections'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.connect-disconnect-cleanup.node.ts b/test/test.connect-disconnect-cleanup.node.ts index 551e92c..08fd07a 100644 --- a/test/test.connect-disconnect-cleanup.node.ts +++ b/test/test.connect-disconnect-cleanup.node.ts @@ -239,4 +239,4 @@ tap.test('should handle clients that error during connection', async () => { console.log('\n✅ PASS: Connection error cleanup working correctly!'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.connection-cleanup-comprehensive.node.ts b/test/test.connection-cleanup-comprehensive.node.ts index e59e792..f52ed1d 100644 --- a/test/test.connection-cleanup-comprehensive.node.ts +++ b/test/test.connection-cleanup-comprehensive.node.ts @@ -276,4 +276,4 @@ tap.test('comprehensive connection cleanup test - all scenarios', async () => { console.log('- NFTables connections'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.connection-limits.node.ts b/test/test.connection-limits.node.ts index 5999c2c..1d789e3 100644 --- a/test/test.connection-limits.node.ts +++ b/test/test.connection-limits.node.ts @@ -296,4 +296,4 @@ tap.test('Cleanup and shutdown', async () => { allServers.length = 0; }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.connection-stability.ts b/test/test.connection-stability.ts deleted file mode 100644 index a10d7ff..0000000 --- a/test/test.connection-stability.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { expect, tap } from '@git.zone/tstest/tapbundle'; -import * as smartproxy from '../ts/index.js'; -import * as net from 'net'; -import * as crypto from 'crypto'; - -tap.test('Connection Stability - Fragment Cleanup', async () => { - // Create a simple TCP server - const server = net.createServer(); - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => resolve()); - }); - const serverPort = (server.address() as net.AddressInfo).port; - - // Configure a route - const routes: smartproxy.IRouteConfig[] = [{ - match: { - ports: 9000, - domains: '*' - }, - action: { - type: 'forward', - target: { - host: '127.0.0.1', - port: serverPort - } - } - }]; - - // Create SmartProxy instance with routes - const proxy = new smartproxy.SmartProxy({ - keepAliveTimeoutMs: 5000, - routes - }); - - await proxy.start(); - - // Test 1: Send fragmented TLS hello - const tlsHello = Buffer.concat([ - Buffer.from([0x16, 0x03, 0x03]), // TLS handshake, version 1.2 - Buffer.from([0x00, 0x50]), // Length: 80 bytes - Buffer.from([0x01]), // ClientHello - Buffer.from([0x00, 0x00, 0x4c]), // Handshake length - Buffer.from([0x03, 0x03]), // TLS 1.2 - crypto.randomBytes(32), // Random - Buffer.from([0x00]), // Session ID length - Buffer.from([0x00, 0x04]), // Cipher suites length - Buffer.from([0xc0, 0x2f, 0xc0, 0x30]), // Cipher suites - Buffer.from([0x01, 0x00]), // Compression methods - Buffer.from([0x00, 0x1f]), // Extensions length - // SNI extension - Buffer.from([0x00, 0x00]), // Server name extension - Buffer.from([0x00, 0x1b]), // Extension length - Buffer.from([0x00, 0x19]), // Server name list length - Buffer.from([0x00]), // Host name type - Buffer.from([0x00, 0x16]), // Name length - Buffer.from('test.example.com') // Server name - ]); - - // Function to check fragment manager size - const getFragmentCount = () => { - // Access the fragment manager through the singleton - const detector = (smartproxy.detection.ProtocolDetector as any).getInstance(); - const tlsFragments = detector.fragmentManager.getHandler('tls'); - const httpFragments = detector.fragmentManager.getHandler('http'); - return tlsFragments.size + httpFragments.size; - }; - - // Test fragmented connections - const connections: net.Socket[] = []; - - // Create multiple fragmented connections - for (let i = 0; i < 5; i++) { - const client = new net.Socket(); - connections.push(client); - - await new Promise((resolve, reject) => { - client.connect(9000, '127.0.0.1', () => { - // Send first fragment - client.write(tlsHello.subarray(0, 20)); - resolve(); - }); - client.on('error', reject); - }); - } - - // Give time for fragments to accumulate - await new Promise(resolve => setTimeout(resolve, 100)); - - // Check that fragments are being tracked - const fragmentCount = getFragmentCount(); - expect(fragmentCount).toBeGreaterThan(0); - - // Send remaining fragments and close connections - for (const client of connections) { - client.write(tlsHello.subarray(20)); - client.end(); - } - - // Wait for connections to close - await new Promise(resolve => setTimeout(resolve, 500)); - - // Check that fragments are cleaned up - const finalFragmentCount = getFragmentCount(); - expect(finalFragmentCount).toEqual(0); - - // Cleanup - await proxy.stop(); - server.close(); -}); - -tap.test('Connection Stability - Memory Leak Prevention', async () => { - // Create a simple echo server - const server = net.createServer((socket) => { - socket.pipe(socket); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => resolve()); - }); - const serverPort = (server.address() as net.AddressInfo).port; - - // Configure a route - const routes: smartproxy.IRouteConfig[] = [{ - match: { - ports: 9001, - domains: '*' - }, - action: { - type: 'forward', - target: { - host: '127.0.0.1', - port: serverPort - } - } - }]; - - // Create SmartProxy instance with routes - const proxy = new smartproxy.SmartProxy({ - keepAliveTimeoutMs: 5000, - routes - }); - - await proxy.start(); - - // Function to get active connection count - const getConnectionCount = () => { - const connectionManager = (proxy as any).connectionManager; - return connectionManager.getActiveConnectionCount(); - }; - - // Create many short-lived connections - const connectionPromises: Promise[] = []; - - for (let i = 0; i < 20; i++) { - const promise = new Promise((resolve, reject) => { - const client = new net.Socket(); - - client.connect(9001, '127.0.0.1', () => { - // Send some data - client.write('Hello World'); - - // Close after a short delay - setTimeout(() => { - client.end(); - }, 50); - }); - - client.on('close', () => resolve()); - client.on('error', reject); - }); - - connectionPromises.push(promise); - - // Stagger connection creation - await new Promise(resolve => setTimeout(resolve, 10)); - } - - // Wait for all connections to complete - await Promise.all(connectionPromises); - - // Give time for cleanup - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Check that all connections are cleaned up - const finalConnectionCount = getConnectionCount(); - expect(finalConnectionCount).toEqual(0); - - // Check fragment cleanup - const fragmentCount = (() => { - const detector = (smartproxy.detection.ProtocolDetector as any).getInstance(); - const tlsFragments = detector.fragmentManager.getHandler('tls'); - const httpFragments = detector.fragmentManager.getHandler('http'); - return tlsFragments.size + httpFragments.size; - })(); - - expect(fragmentCount).toEqual(0); - - // Cleanup - await proxy.stop(); - server.close(); -}); - -tap.test('Connection Stability - Rapid Connect/Disconnect', async () => { - // Create a server that immediately closes connections - const server = net.createServer((socket) => { - socket.end(); - }); - - await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => resolve()); - }); - const serverPort = (server.address() as net.AddressInfo).port; - - // Configure a route - const routes: smartproxy.IRouteConfig[] = [{ - match: { - ports: 9002, - domains: '*' - }, - action: { - type: 'forward', - target: { - host: '127.0.0.1', - port: serverPort - } - } - }]; - - // Create SmartProxy instance with routes - const proxy = new smartproxy.SmartProxy({ - keepAliveTimeoutMs: 5000, - routes - }); - - await proxy.start(); - - let errors = 0; - const connections: Promise[] = []; - - // Create many rapid connections - for (let i = 0; i < 50; i++) { - const promise = new Promise((resolve) => { - const client = new net.Socket(); - - client.on('error', () => { - errors++; - resolve(); - }); - - client.on('close', () => { - resolve(); - }); - - client.connect(9002, '127.0.0.1'); - }); - - connections.push(promise); - } - - // Wait for all to complete - await Promise.all(connections); - - // Give time for cleanup - await new Promise(resolve => setTimeout(resolve, 500)); - - // Check that connections are cleaned up despite rapid connect/disconnect - const connectionManager = (proxy as any).connectionManager; - const finalConnectionCount = connectionManager.getActiveConnectionCount(); - expect(finalConnectionCount).toEqual(0); - - // Check fragment cleanup - const fragmentCount = (() => { - const detector = (smartproxy.detection.ProtocolDetector as any).getInstance(); - const tlsFragments = detector.fragmentManager.getHandler('tls'); - const httpFragments = detector.fragmentManager.getHandler('http'); - return tlsFragments.size + httpFragments.size; - })(); - - expect(fragmentCount).toEqual(0); - - // Cleanup - await proxy.stop(); - server.close(); -}); - -tap.start(); \ No newline at end of file diff --git a/test/test.detection.ts b/test/test.detection.ts index 57f5ff0..77ec1d4 100644 --- a/test/test.detection.ts +++ b/test/test.detection.ts @@ -138,4 +138,9 @@ tap.test('Protocol Detection - Invalid Data', async () => { expect(result.protocol).toEqual('unknown'); }); -tap.start(); \ No newline at end of file +tap.test('cleanup detection', async () => { + // Clean up the protocol detector instance + smartproxy.detection.ProtocolDetector.destroy(); +}); + +export default tap.start(); \ No newline at end of file diff --git a/test/test.fix-verification.ts b/test/test.fix-verification.ts index be2446b..c5d5ad4 100644 --- a/test/test.fix-verification.ts +++ b/test/test.fix-verification.ts @@ -79,4 +79,4 @@ tap.test('should verify certificate manager callback is preserved on updateRoute console.log('Fix verified: Certificate manager callback is preserved on updateRoutes'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.http-fix-unit.ts b/test/test.http-fix-unit.ts index 80e7a3b..0b2de7a 100644 --- a/test/test.http-fix-unit.ts +++ b/test/test.http-fix-unit.ts @@ -180,4 +180,4 @@ tap.test('should handle ACME HTTP-01 challenges on port 80 with HttpProxy', asyn console.log('Test passed: ACME HTTP-01 challenges on port 80 use HttpProxy'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.http-port8080-simple.ts b/test/test.http-port8080-simple.ts index 05a0a49..fc53e35 100644 --- a/test/test.http-port8080-simple.ts +++ b/test/test.http-port8080-simple.ts @@ -242,4 +242,4 @@ tap.test('should handle ACME challenges on port 8080 with improved port binding } }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.http-proxy-security-limits.node.ts b/test/test.http-proxy-security-limits.node.ts index 07bb800..0e2d983 100644 --- a/test/test.http-proxy-security-limits.node.ts +++ b/test/test.http-proxy-security-limits.node.ts @@ -117,4 +117,4 @@ tap.test('Cleanup HttpProxy SecurityManager', async () => { securityManager.clearIPTracking(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.keepalive-support.node.ts b/test/test.keepalive-support.node.ts index 0c5976c..95dd86e 100644 --- a/test/test.keepalive-support.node.ts +++ b/test/test.keepalive-support.node.ts @@ -247,4 +247,4 @@ tap.test('keepalive support - verify keepalive connections are properly handled' console.log(' - Zombie detection respects keepalive settings'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.log-deduplication.node.ts b/test/test.log-deduplication.node.ts index 78448b9..6ee3f7d 100644 --- a/test/test.log-deduplication.node.ts +++ b/test/test.log-deduplication.node.ts @@ -109,4 +109,4 @@ tap.test('Cleanup deduplicator', async () => { expect(deduplicator).toBeInstanceOf(LogDeduplicator); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.memory-leak-check.node.ts b/test/test.memory-leak-check.node.ts index 4524ae1..734f503 100644 --- a/test/test.memory-leak-check.node.ts +++ b/test/test.memory-leak-check.node.ts @@ -149,4 +149,4 @@ tap.test('should not have memory leaks in long-running operations', async (tools }); // Run with: node --expose-gc test.memory-leak-check.node.ts -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.memory-leak-simple.ts b/test/test.memory-leak-simple.ts index fb49575..f7a4e59 100644 --- a/test/test.memory-leak-simple.ts +++ b/test/test.memory-leak-simple.ts @@ -57,4 +57,4 @@ tap.test('memory leak fixes verification', async () => { console.log('\n✅ All memory leak fixes verified!'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.memory-leak-unit.ts b/test/test.memory-leak-unit.ts index 1545218..eeb21a0 100644 --- a/test/test.memory-leak-unit.ts +++ b/test/test.memory-leak-unit.ts @@ -128,4 +128,4 @@ tap.test('memory leak fixes - unit tests', async () => { console.log('\n✅ All memory leak fixes verified!'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.metrics-new.ts b/test/test.metrics-new.ts index a95d844..42e7bde 100644 --- a/test/test.metrics-new.ts +++ b/test/test.metrics-new.ts @@ -258,4 +258,4 @@ tap.test('should clean up resources', async () => { }); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.proxy-chain-simple.node.ts b/test/test.proxy-chain-simple.node.ts index c8c6222..55ec24c 100644 --- a/test/test.proxy-chain-simple.node.ts +++ b/test/test.proxy-chain-simple.node.ts @@ -192,4 +192,4 @@ tap.test('simple proxy chain test - identify connection accumulation', async () expect(finalCounts.proxy2).toEqual(0); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.proxy-protocol.ts b/test/test.proxy-protocol.ts index aa6f71f..2ae3608 100644 --- a/test/test.proxy-protocol.ts +++ b/test/test.proxy-protocol.ts @@ -130,4 +130,4 @@ tap.test('PROXY protocol v1 generator', async () => { // Skipping integration tests for now - focus on unit tests // Integration tests would require more complex setup and teardown -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.rapid-retry-cleanup.node.ts b/test/test.rapid-retry-cleanup.node.ts index 9528331..28de729 100644 --- a/test/test.rapid-retry-cleanup.node.ts +++ b/test/test.rapid-retry-cleanup.node.ts @@ -198,4 +198,4 @@ tap.test('should handle routing failures without leaking connections', async () console.log('\n✅ PASS: Routing failures cleaned up correctly!'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.route-callback-simple.ts b/test/test.route-callback-simple.ts index ec94c12..9b7e60d 100644 --- a/test/test.route-callback-simple.ts +++ b/test/test.route-callback-simple.ts @@ -113,4 +113,4 @@ tap.test('should set update routes callback on certificate manager', async () => await proxy.stop(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.route-security-unit.ts b/test/test.route-security-unit.ts index f7c942e..1e7e4d8 100644 --- a/test/test.route-security-unit.ts +++ b/test/test.route-security-unit.ts @@ -58,4 +58,4 @@ tap.test('route security should be correctly configured', async () => { expect(isBlockedIPAllowed).toBeFalse(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.route-update-callback.node.ts b/test/test.route-update-callback.node.ts index 1a309a4..438afad 100644 --- a/test/test.route-update-callback.node.ts +++ b/test/test.route-update-callback.node.ts @@ -336,4 +336,4 @@ tap.test('real code integration test - verify fix is applied', async () => { console.log('Real code integration test passed - fix is correctly applied!'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.shared-security-manager-limits.node.ts b/test/test.shared-security-manager-limits.node.ts index f0494eb..071e6f3 100644 --- a/test/test.shared-security-manager-limits.node.ts +++ b/test/test.shared-security-manager-limits.node.ts @@ -154,4 +154,4 @@ tap.test('Cleanup SharedSecurityManager', async () => { securityManager.clearIPTracking(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.smartacme-integration.ts b/test/test.smartacme-integration.ts index 198967e..e720557 100644 --- a/test/test.smartacme-integration.ts +++ b/test/test.smartacme-integration.ts @@ -51,4 +51,4 @@ tap.test('should verify SmartAcme cert managers are accessible', async () => { expect(memoryCertManager).toBeDefined(); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.stuck-connection-cleanup.node.ts b/test/test.stuck-connection-cleanup.node.ts index 6b01f5b..2bd0c6e 100644 --- a/test/test.stuck-connection-cleanup.node.ts +++ b/test/test.stuck-connection-cleanup.node.ts @@ -141,4 +141,4 @@ tap.test('stuck connection cleanup - verify connections to hanging backends are console.log('✓ Test complete: Stuck connections are properly detected and cleaned up'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.websocket-keepalive.node.ts b/test/test.websocket-keepalive.node.ts index f4183d7..a9e6a84 100644 --- a/test/test.websocket-keepalive.node.ts +++ b/test/test.websocket-keepalive.node.ts @@ -155,4 +155,4 @@ tap.test('long-lived connection survival test', async (tools) => { console.log('✅ Long-lived connection survived past 30-second timeout!'); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/test/test.zombie-connection-cleanup.node.ts b/test/test.zombie-connection-cleanup.node.ts index 7e8ceb6..dd6b8f1 100644 --- a/test/test.zombie-connection-cleanup.node.ts +++ b/test/test.zombie-connection-cleanup.node.ts @@ -303,4 +303,4 @@ tap.test('zombie connection cleanup - verify inactivity check detects and cleans expect(details.inner.halfZombies.length).toEqual(0); }); -tap.start(); \ No newline at end of file +export default tap.start(); \ No newline at end of file diff --git a/ts/detection/detectors/http-detector.ts b/ts/detection/detectors/http-detector.ts index 00c0cc6..61d5574 100644 --- a/ts/detection/detectors/http-detector.ts +++ b/ts/detection/detectors/http-detector.ts @@ -49,11 +49,15 @@ export class HttpDetector implements IProtocolDetector { return null; } + // Check if we have complete headers first + const headersEnd = buffer.indexOf('\r\n\r\n'); + const isComplete = headersEnd !== -1; + // Extract routing information const routing = RoutingExtractor.extract(buffer, 'http'); - // If we don't need full headers, we can return early - if (quickResult.confidence >= 95 && !options?.extractFullHeaders) { + // If we don't need full headers and we have complete headers, we can return early + if (quickResult.confidence >= 95 && !options?.extractFullHeaders && isComplete) { return { protocol: 'http', connectionInfo: { @@ -66,10 +70,6 @@ export class HttpDetector implements IProtocolDetector { }; } - // Check if we have complete headers - const headersEnd = buffer.indexOf('\r\n\r\n'); - const isComplete = headersEnd !== -1; - return { protocol: 'http', connectionInfo: { diff --git a/ts/detection/protocol-detector.ts b/ts/detection/protocol-detector.ts index c6c3945..f7adf5c 100644 --- a/ts/detection/protocol-detector.ts +++ b/ts/detection/protocol-detector.ts @@ -18,6 +18,7 @@ export class ProtocolDetector { private fragmentManager: DetectionFragmentManager; private tlsDetector: TlsDetector; private httpDetector: HttpDetector; + private connectionProtocols: Map = new Map(); constructor() { this.fragmentManager = new DetectionFragmentManager(); @@ -122,14 +123,25 @@ export class ProtocolDetector { const connectionId = DetectionFragmentManager.createConnectionId(context); - // First peek to determine protocol type - if (this.tlsDetector.canHandle(buffer)) { + // 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' }, @@ -141,15 +153,50 @@ export class ProtocolDetector { if (result) { if (result.isComplete) { handler.complete(connectionId); + this.connectionProtocols.delete(connectionId); } return result; } } - if (this.httpDetector.canHandle(buffer)) { - const result = this.httpDetector.detectWithContext(buffer, context, options); - if (result) { - 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; + } } } @@ -208,6 +255,9 @@ export class ProtocolDetector { // 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); } /**