diff --git a/src/index.test.ts b/src/index.test.ts index feac508..7d98222 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,28 +1,30 @@ -import { describe, it, expect } from 'vitest'; -import { Reader, Writer } from '.'; +import { describe, it, expect } from "vitest"; +import { Reader, Writer } from "."; -describe('Reader/Writer round-trip', () => { - it('writes then reads a variety of types correctly', () => { +describe("Reader/Writer round-trip", () => { + it("writes then reads a variety of types correctly", () => { const writer = new Writer(1024); // values to write const bBool = true; + const bChar = "A"; const bInt8 = -42; const bUint8 = 200; - const bUint16 = 0xBEEF; - const bUint32 = 0xDEADBEEF >>> 0; + const bUint16 = 0xbeef; + const bUint32 = 0xdeadbeef >>> 0; const bUint64 = 0x1122334455667788n; const bFloat32 = 3.1415927; const bFloat64 = 1.23456789e5; - const bCString = 'hello'; - const bUtf8 = 'π≈3.14'; - const words = [0x1234, 0x5678, 0x9ABC]; + const bCString = "hello"; + const bUtf8 = "π≈3.14"; + const words = [0x1234, 0x5678, 0x9abc]; const dwords = [0x11223344 >>> 0, 0x55667788 >>> 0]; - const qwords = [0x8000000000000000n, 0xAABBCCDDEEFF0011n]; + const qwords = [0x8000000000000000n, 0xaabbccddeeff0011n]; const rawBytes = new Uint8Array([1, 2, 3, 4]); // write sequence writer.bool(bBool); + writer.char(bChar); writer.int8(bInt8); writer.uint8(bUint8); writer.uint16(bUint16); @@ -45,6 +47,7 @@ describe('Reader/Writer round-trip', () => { // read back in same order expect(reader.bool()).toBe(bBool); + expect(reader.char()).toBe(bChar); expect(reader.int8()).toBe(bInt8); expect(reader.uint8()).toBe(bUint8); expect(reader.uint16()).toBe(bUint16); diff --git a/src/index.ts b/src/index.ts index 8875a2a..05752cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,17 @@ export class Reader { return value !== 0; } + /** + * Read an 8-bit character from the buffer at the current offset, and advance the offset by 1 byte. + * @returns A string containing the character read from the buffer. + */ + public char(): string { + this.checkBounds(1); + const value = this.view.getUint8(this.offset); + this.offset += 1; + return String.fromCharCode(value); + } + /** * Read an 8-bit signed integer from the buffer at the current offset, and advance the offset by 1 byte. * @@ -434,6 +445,27 @@ export class Writer { this.offset += 1; } + /** + * Write an 8-bit character to the buffer at the current offset, and advance the offset by 1 byte. + * + * @param value The character or its ASCII code to write. + */ + public char(value: string | number): void { + if (typeof value === "string") { + if (value.length !== 1) { + throw new Error("Input must be a single character"); + } + this.checkBounds(1); + this.view.setUint8(this.offset, value.charCodeAt(0)); + } else if (typeof value === "number") { + this.checkBounds(1); + this.view.setUint8(this.offset, value); + } else { + throw new Error("Input must be a string or number"); + } + this.offset += 1; + } + /** * Write an 8-bit signed integer to the buffer at the current offset, and advance the offset by * 1 byte. diff --git a/src/types.ts b/src/types.ts index 4e415ee..59315fe 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,7 @@ - /** * Type definitions for the packet parsing library. */ - /** * Enumeration of supported field types for dissecting packets. * @@ -13,62 +11,63 @@ */ export const FieldType = { // Boolean types - BOOL: 'BOOL', // Boolean value (stored as a byte, 0 for false, non-zero for true) + BOOL: "BOOL", // Boolean value (stored as a byte, 0 for false, non-zero for true) // Number types - BITS: 'BITS', // 1-bit values stored in a number (1 bit per value, packed into bytes) - INT8: 'INT8', // 8-bit signed integer (1 byte) - INT16_LE: 'INT16_LE', // 16-bit signed integer (little-endian) - INT16_BE: 'INT16_BE', // 16-bit signed integer (big-endian) - INT32_LE: 'INT32_LE', // 32-bit signed integer (little-endian) - INT32_BE: 'INT32_BE', // 32-bit signed integer (big-endian) - INT64_LE: 'INT64_LE', // 64-bit signed integer (little-endian) - INT64_BE: 'INT64_BE', // 64-bit signed integer (big-endian) - UINT8: 'UINT8', // 8-bit unsigned integer - UINT16_LE: 'UINT16_LE', // 16-bit unsigned integer (little-endian) - UINT16_BE: 'UINT16_BE', // 16-bit unsigned integer (big-endian) - UINT32_LE: 'UINT32_LE', // 32-bit unsigned integer (little-endian) - UINT32_BE: 'UINT32_BE', // 32-bit unsigned integer (big-endian) - UINT64_LE: 'UINT64_LE', // 64-bit unsigned integer (little-endian) - UINT64_BE: 'UINT64_BE', // 64-bit unsigned integer (big-endian) - FLOAT32_LE: 'FLOAT32_LE', // 32-bit IEEE floating point (little-endian) - FLOAT32_BE: 'FLOAT32_BE', // 32-bit IEEE floating point (big-endian) - FLOAT64_LE: 'FLOAT64_LE', // 64-bit IEEE floating point (little-endian) - FLOAT64_BE: 'FLOAT64_BE', // 64-bit IEEE floating point (big-endian) - VARINT: 'VARINT', // Variable-length integer (unsigned, LEB128 encoding) - VARSINT: 'VARSINT', // Variable-length integer (signed, LEB128 encoding) + BITS: "BITS", // 1-bit values stored in a number (1 bit per value, packed into bytes) + CHAR: "CHAR", // 8-bit character (1 byte) + INT8: "INT8", // 8-bit signed integer (1 byte) + INT16_LE: "INT16_LE", // 16-bit signed integer (little-endian) + INT16_BE: "INT16_BE", // 16-bit signed integer (big-endian) + INT32_LE: "INT32_LE", // 32-bit signed integer (little-endian) + INT32_BE: "INT32_BE", // 32-bit signed integer (big-endian) + INT64_LE: "INT64_LE", // 64-bit signed integer (little-endian) + INT64_BE: "INT64_BE", // 64-bit signed integer (big-endian) + UINT8: "UINT8", // 8-bit unsigned integer + UINT16_LE: "UINT16_LE", // 16-bit unsigned integer (little-endian) + UINT16_BE: "UINT16_BE", // 16-bit unsigned integer (big-endian) + UINT32_LE: "UINT32_LE", // 32-bit unsigned integer (little-endian) + UINT32_BE: "UINT32_BE", // 32-bit unsigned integer (big-endian) + UINT64_LE: "UINT64_LE", // 64-bit unsigned integer (little-endian) + UINT64_BE: "UINT64_BE", // 64-bit unsigned integer (big-endian) + FLOAT32_LE: "FLOAT32_LE", // 32-bit IEEE floating point (little-endian) + FLOAT32_BE: "FLOAT32_BE", // 32-bit IEEE floating point (big-endian) + FLOAT64_LE: "FLOAT64_LE", // 64-bit IEEE floating point (little-endian) + FLOAT64_BE: "FLOAT64_BE", // 64-bit IEEE floating point (big-endian) + VARINT: "VARINT", // Variable-length integer (unsigned, LEB128 encoding) + VARSINT: "VARSINT", // Variable-length integer (signed, LEB128 encoding) // Date/time types (stored as integer timestamps) - DATE32_LE: 'DATE32_LE', // 32-bit integer date (e.g., seconds since epoch) little-endian - DATE32_BE: 'DATE32_BE', // 32-bit integer date big-endian - DATE64_LE: 'DATE64_LE', // 64-bit integer date (e.g., ms since epoch) little-endian - DATE64_BE: 'DATE64_BE', // 64-bit integer date big-endian + DATE32_LE: "DATE32_LE", // 32-bit integer date (e.g., seconds since epoch) little-endian + DATE32_BE: "DATE32_BE", // 32-bit integer date big-endian + DATE64_LE: "DATE64_LE", // 64-bit integer date (e.g., ms since epoch) little-endian + DATE64_BE: "DATE64_BE", // 64-bit integer date big-endian // Array buffer types - BYTES: 'BYTES', // 8-bits per value array (Uint8Array) - C_STRING: 'C_STRING', // Null-terminated string (C-style) (Uint8Array) - UTF8_STRING: 'UTF8_STRING', // UTF-8 encoded string (Uint8Array) - WORDS: 'WORDS', // 16-bits per value array (Uint16Array) - DWORDS: 'DWORDS', // 32-bits per value array (Uint32Array) - QWORDS: 'QWORDS', // 64-bits per value array (BigUint64Array) + BYTES: "BYTES", // 8-bits per value array (Uint8Array) + C_STRING: "C_STRING", // Null-terminated string (C-style) (Uint8Array) + UTF8_STRING: "UTF8_STRING", // UTF-8 encoded string (Uint8Array) + WORDS: "WORDS", // 16-bits per value array (Uint16Array) + DWORDS: "DWORDS", // 32-bits per value array (Uint32Array) + QWORDS: "QWORDS", // 64-bits per value array (BigUint64Array) // Aliases - FLAG: 'BOOL', // alternate name for boolean/flag fields - STRING: 'UTF8_STRING', // alias for UTF8 encoded strings + FLAG: "BOOL", // alternate name for boolean/flag fields + STRING: "UTF8_STRING" // alias for UTF8 encoded strings } as const; -export type FieldType = typeof FieldType[keyof typeof FieldType]; +export type FieldType = (typeof FieldType)[keyof typeof FieldType]; /** * Interface for a packet, which can be dissected into segments and fields. This is a placeholder * for any additional properties or methods that may be needed for representing a packet in the future. */ export interface Packet { - data: string | Uint8Array | ArrayBuffer; // Raw packet data as an ArrayBuffer - snr?: number; // Optional signal-to-noise ratio (for radio packets) - rssi?: number; // Optional received signal strength indicator (for radio packets) - parsed?: unknown; // Optional parsed representation of the packet (e.g., a structured object) - dissected?: Dissected; // Optional dissected representation of the packet (array of segments) + data: string | Uint8Array | ArrayBuffer; // Raw packet data as an ArrayBuffer + snr?: number; // Optional signal-to-noise ratio (for radio packets) + rssi?: number; // Optional received signal strength indicator (for radio packets) + parsed?: unknown; // Optional parsed representation of the packet (e.g., a structured object) + dissected?: Dissected; // Optional dissected representation of the packet (array of segments) /** * Method to dissect the packet into segments and fields, returning an array of segments. @@ -108,8 +107,9 @@ export type Dissected = Segment[]; * Each field specifies the type and length of data it represents. */ export interface Segment { - name: string; - data?: ArrayBuffer; // Optional raw data for the segment (if needed for parsing / serialization) + name: string; + data?: ArrayBuffer; // Optional raw data for the segment (if needed for parsing / serialization) + isString?: boolean; // Optional flag indicating if the segment represents a string (for special handling) fields: Field[]; } @@ -118,11 +118,11 @@ export interface Segment { * for bit fields and array lengths. */ export interface Field { - type: FieldType; - name: string; - value?: unknown; // Optional value for the field (used for serialization or as a default value) - bits?: BitField[]; // Optional array of bit field definitions (for BITS type) - length?: number; // Optional length for array types (e.g., BYTES, WORDS) + type: FieldType; + name: string; + value?: unknown; // Optional value for the field (used for serialization or as a default value) + bits?: BitField[]; // Optional array of bit field definitions (for BITS type) + length?: number; // Optional length for array types (e.g., BYTES, WORDS) } /** @@ -131,6 +131,6 @@ export interface Field { */ export interface BitField { name: string; - size: number; // Number of bits for this field (must be > 0) + size: number; // Number of bits for this field (must be > 0) lsb?: boolean; // Optional flag indicating if this field is the least significant bit in the byte (for BITS type) }