import { expect, tap } from '@git.zone/tstest/tapbundle'; import { RadiusPacket, ERadiusCode, ERadiusAttributeType, } from '../../ts_server/index.js'; tap.test('should encode and decode a basic Access-Request packet', async () => { const identifier = 42; const secret = 'testing123'; const attributes = [ { type: ERadiusAttributeType.UserName, value: 'testuser' }, { type: ERadiusAttributeType.UserPassword, value: 'testpass' }, { type: ERadiusAttributeType.NasIpAddress, value: '192.168.1.1' }, { type: ERadiusAttributeType.NasPort, value: 1 }, ]; const packet = RadiusPacket.createAccessRequest(identifier, secret, attributes); // Verify minimum packet size (20 bytes header) expect(packet.length).toBeGreaterThanOrEqual(RadiusPacket.MIN_PACKET_SIZE); // Verify maximum packet size expect(packet.length).toBeLessThanOrEqual(RadiusPacket.MAX_PACKET_SIZE); // Verify header expect(packet[0]).toEqual(ERadiusCode.AccessRequest); expect(packet[1]).toEqual(identifier); // Decode the packet const decoded = RadiusPacket.decode(packet); expect(decoded.code).toEqual(ERadiusCode.AccessRequest); expect(decoded.identifier).toEqual(identifier); expect(decoded.authenticator.length).toEqual(16); expect(decoded.attributes.length).toBeGreaterThan(0); }); tap.test('should handle packet length validation', async () => { // Packet too short const shortPacket = Buffer.alloc(19); let error: Error | undefined; try { RadiusPacket.decode(shortPacket); } catch (e) { error = e as Error; } expect(error).toBeDefined(); expect(error!.message).toInclude('too short'); }); tap.test('should handle invalid length in header', async () => { const packet = Buffer.alloc(20); packet[0] = ERadiusCode.AccessRequest; packet[1] = 1; // identifier packet.writeUInt16BE(10, 2); // length too small let error: Error | undefined; try { RadiusPacket.decode(packet); } catch (e) { error = e as Error; } expect(error).toBeDefined(); }); tap.test('should handle maximum packet size', async () => { const secret = 'testing123'; const identifier = 1; // Create a packet that would exceed max size const hugeAttributes: Array<{ type: number; value: Buffer }> = []; // Each attribute can be max 255 bytes. Create enough to exceed 4096 for (let i = 0; i < 20; i++) { hugeAttributes.push({ type: ERadiusAttributeType.ReplyMessage, value: Buffer.alloc(250, 65), // 250 bytes of 'A' }); } // This should throw because packet would be too large let error: Error | undefined; try { // Manually build the packet to test size limit const rawAttrs = hugeAttributes.map((a) => ({ type: a.type, value: a.value, })); RadiusPacket.encode({ code: ERadiusCode.AccessRequest, identifier, authenticator: Buffer.alloc(16), attributes: rawAttrs, }); } catch (e) { error = e as Error; } expect(error).toBeDefined(); expect(error!.message).toInclude('too large'); }); tap.test('should parse and encode attributes correctly', async () => { const secret = 'testing123'; const identifier = 1; // Create packet with various attribute types const packet = RadiusPacket.createAccessRequest(identifier, secret, [ { type: 'User-Name', value: 'john.doe' }, // text { type: 'NAS-IP-Address', value: '10.0.0.1' }, // address { type: 'NAS-Port', value: 5060 }, // integer { type: 'NAS-Identifier', value: 'nas01.example.com' }, // text ]); const decoded = RadiusPacket.decodeAndParse(packet); // Find username const usernameAttr = decoded.parsedAttributes.find( (a) => a.type === ERadiusAttributeType.UserName ); expect(usernameAttr).toBeDefined(); expect(usernameAttr!.value).toEqual('john.doe'); // Find NAS-IP-Address const nasIpAttr = decoded.parsedAttributes.find( (a) => a.type === ERadiusAttributeType.NasIpAddress ); expect(nasIpAttr).toBeDefined(); expect(nasIpAttr!.value).toEqual('10.0.0.1'); // Find NAS-Port const nasPortAttr = decoded.parsedAttributes.find( (a) => a.type === ERadiusAttributeType.NasPort ); expect(nasPortAttr).toBeDefined(); expect(nasPortAttr!.value).toEqual(5060); }); tap.test('should create Access-Accept packet', async () => { const identifier = 1; const requestAuth = Buffer.alloc(16); const secret = 'testing123'; const packet = RadiusPacket.createAccessAccept(identifier, requestAuth, secret, [ { type: ERadiusAttributeType.ReplyMessage, value: 'Welcome!' }, { type: ERadiusAttributeType.SessionTimeout, value: 3600 }, ]); const decoded = RadiusPacket.decode(packet); expect(decoded.code).toEqual(ERadiusCode.AccessAccept); expect(decoded.identifier).toEqual(identifier); }); tap.test('should create Access-Reject packet', async () => { const identifier = 1; const requestAuth = Buffer.alloc(16); const secret = 'testing123'; const packet = RadiusPacket.createAccessReject(identifier, requestAuth, secret, [ { type: ERadiusAttributeType.ReplyMessage, value: 'Invalid credentials' }, ]); const decoded = RadiusPacket.decode(packet); expect(decoded.code).toEqual(ERadiusCode.AccessReject); }); tap.test('should create Access-Challenge packet', async () => { const identifier = 1; const requestAuth = Buffer.alloc(16); const secret = 'testing123'; const state = Buffer.from('challenge-state-123'); const packet = RadiusPacket.createAccessChallenge(identifier, requestAuth, secret, [ { type: ERadiusAttributeType.ReplyMessage, value: 'Enter OTP' }, { type: ERadiusAttributeType.State, value: state }, ]); const decoded = RadiusPacket.decode(packet); expect(decoded.code).toEqual(ERadiusCode.AccessChallenge); }); tap.test('should get code name', async () => { expect(RadiusPacket.getCodeName(ERadiusCode.AccessRequest)).toEqual('Access-Request'); expect(RadiusPacket.getCodeName(ERadiusCode.AccessAccept)).toEqual('Access-Accept'); expect(RadiusPacket.getCodeName(ERadiusCode.AccessReject)).toEqual('Access-Reject'); expect(RadiusPacket.getCodeName(ERadiusCode.AccountingRequest)).toEqual('Accounting-Request'); expect(RadiusPacket.getCodeName(ERadiusCode.AccountingResponse)).toEqual('Accounting-Response'); expect(RadiusPacket.getCodeName(ERadiusCode.AccessChallenge)).toEqual('Access-Challenge'); }); export default tap.start();