import { describe, it, expect, afterEach } from 'vitest'; import { createServer, Server, Socket } from 'net'; import { TcpTransport } from '../../src/transport/tcp-transport.js'; import { encodeTPKT } from '../../src/protocol/tpkt.js'; describe('TcpTransport', () => { let server: Server | null = null; let serverPort = 0; afterEach(async () => { if (server) { server.close(); server = null; } }); function setupServer(): Promise<{ server: Server; port: number }> { return new Promise((resolve, reject) => { const srv = createServer(); srv.listen(0, () => { const addr = srv.address(); if (addr && typeof addr === 'object') { resolve({ server: srv, port: addr.port }); } else { reject(new Error('Failed to get server address')); } }); srv.on('error', reject); }); } describe('connect / disconnect', () => { it('should connect to a server', async () => { const { server: srv, port } = await setupServer(); server = srv; const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); expect(transport.isConnected()).toBe(true); transport.disconnect(); expect(transport.isConnected()).toBe(false); }); it('should fail to connect to non-existent server', async () => { const transport = new TcpTransport(); await expect(transport.connect('127.0.0.1', 59999, 1000)).rejects.toThrow(); expect(transport.isConnected()).toBe(false); }); it('should emit close event on disconnect', async () => { const { server: srv, port } = await setupServer(); server = srv; const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); const closePromise = new Promise((resolve) => { transport.on('close', () => resolve()); }); transport.disconnect(); await closePromise; expect(transport.isConnected()).toBe(false); }); }); describe('send / recv', () => { it('should send and receive TPKT-framed data', async () => { const { server: srv, port } = await setupServer(); server = srv; // Echo server: send back whatever it receives srv.on('connection', (socket: Socket) => { socket.on('data', (data) => socket.write(data)); }); const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); // Send a COTP CR (example PDU) const testPdu = Buffer.from([0x02, 0xF0, 0x80]); // COTP DT-like await transport.send(testPdu); const received = await transport.recv(); // Received data should be the TPKT payload (without TPKT header) expect(received).toEqual(testPdu); transport.disconnect(); }); it('should handle multiple send/recv cycles', async () => { const { server: srv, port } = await setupServer(); server = srv; srv.on('connection', (socket: Socket) => { socket.on('data', (data) => socket.write(data)); }); const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); for (let i = 0; i < 5; i++) { const pdu = Buffer.from([0x01, 0x02, 0x03, i]); await transport.send(pdu); const received = await transport.recv(); expect(received).toEqual(pdu); } transport.disconnect(); }); it('should throw when sending on disconnected transport', async () => { const transport = new TcpTransport(); await expect(transport.send(Buffer.from([0x01]))).rejects.toThrow('Not connected'); }); }); describe('TPKT framing', () => { it('should handle fragmented TPKT frames', async () => { const { server: srv, port } = await setupServer(); server = srv; let clientSocket: Socket | null = null; srv.on('connection', (socket: Socket) => { clientSocket = socket; }); const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); // Wait for server to get the connection await new Promise((resolve) => setTimeout(resolve, 50)); expect(clientSocket).not.toBeNull(); // Manually send a TPKT frame in two fragments const payload = Buffer.from([0xAA, 0xBB, 0xCC]); const tpktFrame = encodeTPKT(payload); // Send first 3 bytes (partial header) clientSocket!.write(tpktFrame.subarray(0, 3)); // Wait a bit, then send the rest await new Promise((resolve) => setTimeout(resolve, 20)); clientSocket!.write(tpktFrame.subarray(3)); const received = await transport.recv(); expect(received).toEqual(payload); transport.disconnect(); }); it('should handle multiple TPKT frames in one TCP segment', async () => { const { server: srv, port } = await setupServer(); server = srv; let clientSocket: Socket | null = null; srv.on('connection', (socket: Socket) => { clientSocket = socket; }); const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); await new Promise((resolve) => setTimeout(resolve, 50)); expect(clientSocket).not.toBeNull(); // Send two TPKT frames concatenated const payload1 = Buffer.from([0x01, 0x02]); const payload2 = Buffer.from([0x03, 0x04, 0x05]); const frame1 = encodeTPKT(payload1); const frame2 = encodeTPKT(payload2); clientSocket!.write(Buffer.concat([frame1, frame2])); const recv1 = await transport.recv(); expect(recv1).toEqual(payload1); const recv2 = await transport.recv(); expect(recv2).toEqual(payload2); transport.disconnect(); }); }); describe('edge cases', () => { it('should handle connection timeout', async () => { const transport = new TcpTransport(); // Connect to a non-routable IP to force timeout await expect(transport.connect('10.255.255.1', 102, 500)).rejects.toThrow(); }); it('should detect server closing connection', async () => { const { server: srv, port } = await setupServer(); server = srv; let clientSocket: Socket | null = null; srv.on('connection', (socket: Socket) => { clientSocket = socket; }); const transport = new TcpTransport(); await transport.connect('127.0.0.1', port, 3000); await new Promise((resolve) => setTimeout(resolve, 50)); const closePromise = new Promise((resolve) => { transport.on('close', () => resolve()); }); // Server closes the connection clientSocket!.destroy(); srv.close(); await closePromise; expect(transport.isConnected()).toBe(false); }); }); });