diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f8c7ac..c2670a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,16 +11,22 @@ repos: hooks: - id: shellcheck -- repo: https://github.com/pre-commit/mirrors-eslint - rev: v10.0.3 +- repo: local + hooks: + - id: prettier + name: prettier + entry: npx prettier --write + language: system + files: "\\.(js|jsx|ts|tsx)$" + +- repo: local hooks: - id: eslint + name: eslint + entry: npx eslint --fix + language: system files: "\\.(js|jsx|ts|tsx)$" - exclude: node_modules/ -# Use stylelint (local) instead of the deprecated scss-lint Ruby gem which -# cannot parse modern Sass `@use` and module syntax. This invokes the -# project's installed `stylelint` via `npx` so the devDependency is used. - repo: local hooks: - id: stylelint diff --git a/.prettierrc.ts b/.prettierrc.ts new file mode 100644 index 0000000..fc73471 --- /dev/null +++ b/.prettierrc.ts @@ -0,0 +1,8 @@ +import { type Config } from "prettier"; + +const config: Config = { + trailingComma: "none", + printWidth: 120 +}; + +export default config; diff --git a/package.json b/package.json index d67a996..535e3e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hamradio/packet", "type": "module", - "version": "1.0.3", + "version": "1.0.4", "description": "Low level packet parsing library (for radio protocols)", "keywords": [ "HAM radio", @@ -18,7 +18,7 @@ "license": "MIT", "author": "Wijnand Modderman-Lenstra", "main": "dist/index.js", - "module": "dist/index.mjs", + "module": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist" @@ -26,7 +26,7 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", + "import": "./dist/index.js", "require": "./dist/index.js" } }, @@ -40,12 +40,12 @@ "lint": "eslint .", "prepare": "npm run build" }, - "dependencies": {}, "devDependencies": { "@eslint/js": "^10.0.1", "@vitest/coverage-v8": "^4.0.18", "eslint": "^10.0.3", "globals": "^17.4.0", + "prettier": "3.8.1", "tsup": "^8.5.1", "typescript": "^5.9.3", "typescript-eslint": "^8.57.0", diff --git a/src/index.ts b/src/index.ts index 85278fb..8875a2a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ export class Reader { const srcBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); this.buffer = srcBuffer instanceof ArrayBuffer ? srcBuffer : new ArrayBuffer(srcBuffer.byteLength); } else { - throw new TypeError('Invalid buffer type. Expected ArrayBuffer, Uint8Array, or ArrayBufferView.'); + throw new TypeError("Invalid buffer type. Expected ArrayBuffer, Uint8Array, or ArrayBufferView."); } this.view = new DataView(this.buffer); this.offset = 0; @@ -179,7 +179,7 @@ export class Reader { while (true) { this.checkBounds(1); const byte = this.view.getUint8(this.offset++); - result |= (byte & 0x7F) << shift; + result |= (byte & 0x7f) << shift; if ((byte & 0x80) === 0) { break; // Last byte of the varint } @@ -205,7 +205,7 @@ export class Reader { while (true) { this.checkBounds(1); const byte = this.view.getUint8(this.offset++); - result |= (byte & 0x7F) << shift; + result |= (byte & 0x7f) << shift; if ((byte & 0x80) === 0) { break; // Last byte of the varint } @@ -342,32 +342,36 @@ export class Reader { return new Reader(bytes, littleEndian); } - public static fromString(str: string, encoding: 'utf8' | 'ascii' | 'hex' | 'base64' | 'rawbase64' | 'urlbase64' | 'rawurlbase64' = 'utf8', littleEndian: boolean = true): Reader { + public static fromString( + str: string, + encoding: "utf8" | "ascii" | "hex" | "base64" | "rawbase64" | "urlbase64" | "rawurlbase64" = "utf8", + littleEndian: boolean = true + ): Reader { let bytes: Uint8Array; switch (encoding) { - case 'utf8': + case "utf8": bytes = new TextEncoder().encode(str); break; - case 'ascii': - bytes = new Uint8Array(str.split('').map(c => c.charCodeAt(0))); + case "ascii": + bytes = new Uint8Array(str.split("").map((c) => c.charCodeAt(0))); break; - case 'hex': + case "hex": bytes = new Uint8Array(str.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(str.substr(i * 2, 2), 16); } break; - case 'base64': - bytes = Uint8Array.from(atob(str), c => c.charCodeAt(0)); + case "base64": + bytes = Uint8Array.from(atob(str), (c) => c.charCodeAt(0)); break; - case 'rawbase64': - bytes = Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); + case "rawbase64": + bytes = Uint8Array.from(atob(str.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)); break; - case 'urlbase64': - bytes = Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); + case "urlbase64": + bytes = Uint8Array.from(atob(str.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)); break; - case 'rawurlbase64': - bytes = Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); + case "rawurlbase64": + bytes = Uint8Array.from(atob(str.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)); break; } return new Reader(bytes.slice().buffer, littleEndian); @@ -387,7 +391,9 @@ export class Reader { private checkBounds(length: number) { if (this.offset + length > this.view.byteLength) { - throw new RangeError(`Attempt to read beyond end of buffer: offset=${this.offset}, length=${length}, bufferLength=${this.view.byteLength}`); + throw new RangeError( + `Attempt to read beyond end of buffer: offset=${this.offset}, length=${length}, bufferLength=${this.view.byteLength}` + ); } } } @@ -472,7 +478,7 @@ export class Writer { */ public int64(value: number | bigint): void { this.checkBounds(8); - if (typeof value === 'number') { + if (typeof value === "number") { value = BigInt(value); } this.view.setBigInt64(this.offset, value, this.littleEndian); @@ -523,7 +529,7 @@ export class Writer { */ public uint64(value: number | bigint): void { this.checkBounds(8); - if (typeof value === 'number') { + if (typeof value === "number") { value = BigInt(value); } this.view.setBigUint64(this.offset, value, this.littleEndian); @@ -570,7 +576,7 @@ export class Writer { // Useful for pre-sizing buffers. let remaining = value >>> 0; // Ensure unsigned while (remaining >= 0x80) { - this.view.setUint8(this.offset++, (remaining & 0x7F) | 0x80); + this.view.setUint8(this.offset++, (remaining & 0x7f) | 0x80); remaining >>>= 7; } this.view.setUint8(this.offset++, remaining); @@ -591,7 +597,7 @@ export class Writer { let remaining = value >>> 0; // Ensure unsigned const isNegative = value < 0; while (remaining >= 0x80 || (isNegative && remaining < 0x80)) { - this.view.setUint8(this.offset++, (remaining & 0x7F) | 0x80); + this.view.setUint8(this.offset++, (remaining & 0x7f) | 0x80); remaining >>>= 7; } this.view.setUint8(this.offset++, remaining); @@ -701,46 +707,60 @@ export class Writer { * @param encoding The encoding to use for the string conversion. * @returns The encoded string. */ - public toString(encoding: 'utf-8' | 'hex' | 'base64' | 'rawbase64' | 'urlbase64' | 'rawurlbase64' = 'utf-8'): string { + public toString(encoding: "utf-8" | "hex" | "base64" | "rawbase64" | "urlbase64" | "rawurlbase64" = "utf-8"): string { const bytes = this.toBytes(); switch (encoding) { - case 'utf-8': + case "utf-8": return new TextDecoder().decode(bytes); - case 'hex': - return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); - case 'base64': + case "hex": + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + case "base64": return btoa(String.fromCharCode(...bytes)); - case 'rawbase64': - return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); - case 'urlbase64': - return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_'); - case 'rawurlbase64': - return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + case "rawbase64": + return btoa(String.fromCharCode(...bytes)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + case "urlbase64": + return btoa(String.fromCharCode(...bytes)) + .replace(/\+/g, "-") + .replace(/\//g, "_"); + case "rawurlbase64": + return btoa(String.fromCharCode(...bytes)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); default: throw new Error(`Unsupported encoding: ${encoding}`); } } - public static fromString(value: string, encoding: 'utf-8' | 'hex' | 'base64' | 'rawbase64' | 'urlbase64' | 'rawurlbase64' = 'utf-8', littleEndian: boolean = true): Writer { + public static fromString( + value: string, + encoding: "utf-8" | "hex" | "base64" | "rawbase64" | "urlbase64" | "rawurlbase64" = "utf-8", + littleEndian: boolean = true + ): Writer { let bytes: Uint8Array; switch (encoding) { - case 'utf-8': + case "utf-8": bytes = new TextEncoder().encode(value); break; - case 'hex': - bytes = new Uint8Array(value.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))); + case "hex": + bytes = new Uint8Array(value.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))); break; - case 'base64': - bytes = Uint8Array.from(atob(value), c => c.charCodeAt(0)); + case "base64": + bytes = Uint8Array.from(atob(value), (c) => c.charCodeAt(0)); break; - case 'rawbase64': - bytes = Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); + case "rawbase64": + bytes = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)); break; - case 'urlbase64': - bytes = Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); + case "urlbase64": + bytes = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)); break; - case 'rawurlbase64': - bytes = Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); + case "rawurlbase64": + bytes = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)); break; default: throw new Error(`Unsupported encoding: ${encoding}`); @@ -764,13 +784,48 @@ export class Writer { private checkBounds(length: number) { if (this.offset + length > this.view.byteLength) { - throw new RangeError(`Attempt to write beyond end of buffer: offset=${this.offset}, length=${length}, bufferLength=${this.view.byteLength}`); + throw new RangeError( + `Attempt to write beyond end of buffer: offset=${this.offset}, length=${length}, bufferLength=${this.view.byteLength}` + ); } } } // Exporting types and utilities for external use: -export * from './types'; -export type * from './types'; -export * from './utils'; -export type * from './utils' +export * from "./types"; +export { + FieldType, + type Packet, + type Protocol, + type Dissected, + type Segment, + type Field, + type BitField +} from "./types"; +export { + I8, + I16, + I32, + U8, + U16, + U32, + F32, + F64, + isBytes, + assertBytes, + equalBytes, + constantTimeEqualBytes, + base64ToBytes, + bytesToBase64, + hexToBytes, + bytesToHex, + decodeBytes, + encodeBytes, + type TypedArray, + type HexEncoding, + type Base64Encoding, + type Base64RawEncoding, + type Base64URLEncoding, + type Base64RawURLEncoding, + type Encoding +} from "./utils";