From ca7372b29f07442aae6682c223ad5f0483786c59 Mon Sep 17 00:00:00 2001 From: lwvemike Date: Wed, 20 Mar 2024 02:36:34 +0200 Subject: [PATCH] feat: create header structure --- .vscode/settings.json | 3 + eslint.config.js | 2 +- src/index.ts | 124 ++++++++++++++++++++++++++++++++++++++++++ test/index.test.ts | 41 +++++++++++++- 4 files changed, 166 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c5632ba..8fbde55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,5 +37,8 @@ "json", "jsonc", "yaml" + ], + "cSpell.words": [ + "Tacacs" ] } diff --git a/eslint.config.js b/eslint.config.js index 64149c8..da75f40 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,7 +9,7 @@ export default antfu( }, { rules: { - // overrides + curly: ['error', 'all'], }, }, ) diff --git a/src/index.ts b/src/index.ts index e69de29..cb59390 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1,124 @@ +import type { Buffer } from 'node:buffer' + +class TacacsPlusError extends Error { + constructor(message: string) { + super(`[@noction/tacacs-plus] ${message}`) + } +} + +const _DEFAULT_PORT = 49 +const _TAC_PLUS_MAJOR_VER = 0xC +const _TAC_PLUS_MINOR_VER_DEFAULT = 0x0 +const _TAC_PLUS_MINOR_VER_ONE = 0x1 + +export enum Flags { + TAC_PLUS_UNENCRYPTED_FLAG = 0x01, + TAC_PLUS_SINGLE_CONNECT_FLAG = 0x04, +} + +export enum PacketType { + TAC_PLUS_AUTHEN = 0x01, // Authentication + TAC_PLUS_AUTHOR = 0x02, // Authorization + TAC_PLUS_ACCT = 0x03, // Accounting +} + +export class Header { + #majorVersion: number // 4 bits + #minorVersion: number // 4 bits + #type: PacketType // 1 byte + #seqNo: number // 1 byte + #flags: Flags// 1 byte + #sessionId: number // 4 bytes + #length: number // 4 bytes + + constructor( + majorVersion: number, + minorVersion: number, + type: PacketType, + seqNo: number, + flags: number, + sessionId: number, + length: number, + ) { + this.#majorVersion = majorVersion + this.#minorVersion = minorVersion + this.#type = type + this.#seqNo = seqNo + this.#flags = flags + this.#sessionId = sessionId + this.#length = length + } + + /** + * @throws TacacsPlusError + * @param raw + * @returns + */ + static decodeHeader(raw: Buffer) { + if (raw.length !== Header.SIZE) { + throw new TacacsPlusError(`Header size must be ${Header.SIZE}, but received ${raw.length}`) + } + + let offset = 0 + + const versionByte = raw.subarray(offset, 1).readUInt8(0) + offset += 1 + + const majorVersion = ((versionByte >> 4) & 0xF) + const minorVersion = (versionByte & 0xF) + + const type = raw.subarray(offset, 2).readUInt8(0) + offset += 1 + + const seqNo = raw.subarray(offset, 3).readUInt8(0) + offset += 1 + + const flags = raw.subarray(offset, 4).readUint8(0) + offset += 1 + + const sessionId = raw.subarray(offset, 8).readUInt32BE(0) + offset += 4 + + const length = raw.subarray(offset, 12).readUInt32BE(0) + + return new Header( + majorVersion, + minorVersion, + type, + seqNo, + flags, + sessionId, + length, + ) + } + + get majorVersion(): number { + return this.#majorVersion + } + + get minorVersion(): number { + return this.#minorVersion + } + + get type(): PacketType { + return this.#type + } + + get seq_no(): number { + return this.#seqNo + } + + get flags(): number { + return this.#flags + } + + get session_id(): number { + return this.#sessionId + } + + get length(): number { + return this.#length + } + + static readonly SIZE = 12 +} diff --git a/test/index.test.ts b/test/index.test.ts index 401553c..15e9a89 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,7 +1,42 @@ +import { Buffer } from 'node:buffer' import { describe, expect, it } from 'vitest' +import { Header, PacketType } from '../src' -describe('should', () => { - it('exported', () => { - expect(1).toEqual(1) +describe('@noction/tacacs-plus', () => { + describe('header', () => { + it('should decode a valid header', () => { + const buffer = Buffer.from([ + 0x12, + 0x01, + 0x42, + 0x10, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0C, + ]) + + const header = Header.decodeHeader(buffer) + + expect(header.majorVersion).toBe(1) + expect(header.minorVersion).toBe(2) + expect(header.type).toBe(PacketType.TAC_PLUS_AUTHEN) + expect(header.seq_no).toBe(66) + expect(header.flags).toBe(16) + expect(header.session_id).toBe(1) + expect(header.length).toBe(12) + }) + + it('should throw an error for an invalid header size', () => { + const invalidBuffer = Buffer.from([0x00, 0x01]) + + expect(() => Header.decodeHeader(invalidBuffer)).toThrowError( + 'Header size must be 12, but received 2', + ) + }) }) })