Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
8994fb7b45
|
|||
|
a5acb5ed03
|
|||
|
2a5e4b1052
|
|||
|
c18a544a2e
|
|||
|
25c07c947a
|
@@ -11,16 +11,22 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
- id: shellcheck
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
- repo: local
|
||||||
rev: v10.0.3
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
name: prettier
|
||||||
|
entry: npx prettier --write
|
||||||
|
language: system
|
||||||
|
files: "\\.(js|jsx|ts|tsx)$"
|
||||||
|
|
||||||
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: eslint
|
- id: eslint
|
||||||
|
name: eslint
|
||||||
|
entry: npx eslint --fix
|
||||||
|
language: system
|
||||||
files: "\\.(js|jsx|ts|tsx)$"
|
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
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: stylelint
|
- id: stylelint
|
||||||
|
|||||||
8
.prettierrc.ts
Normal file
8
.prettierrc.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { type Config } from "prettier";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
trailingComma: "none",
|
||||||
|
printWidth: 120
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hamradio/packet",
|
"name": "@hamradio/packet",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.2",
|
"version": "1.0.5",
|
||||||
"description": "Low level packet parsing library (for radio protocols)",
|
"description": "Low level packet parsing library (for radio protocols)",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"HAM radio",
|
"HAM radio",
|
||||||
@@ -13,12 +13,12 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.maze.io/ham/packet.js"
|
"url": "https://git.maze.io/ham/packet.ts"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Wijnand Modderman-Lenstra",
|
"author": "Wijnand Modderman-Lenstra",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.mjs",
|
"module": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"import": "./dist/index.mjs",
|
"import": "./dist/index.js",
|
||||||
"require": "./dist/index.js"
|
"require": "./dist/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,12 +40,12 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
|
"prettier": "3.8.1",
|
||||||
"tsup": "^8.5.1",
|
"tsup": "^8.5.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.57.0",
|
"typescript-eslint": "^8.57.0",
|
||||||
|
|||||||
150
src/index.ts
150
src/index.ts
@@ -15,7 +15,7 @@ export class Reader {
|
|||||||
const srcBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
const srcBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
||||||
this.buffer = srcBuffer instanceof ArrayBuffer ? srcBuffer : new ArrayBuffer(srcBuffer.byteLength);
|
this.buffer = srcBuffer instanceof ArrayBuffer ? srcBuffer : new ArrayBuffer(srcBuffer.byteLength);
|
||||||
} else {
|
} 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.view = new DataView(this.buffer);
|
||||||
this.offset = 0;
|
this.offset = 0;
|
||||||
@@ -179,7 +179,7 @@ export class Reader {
|
|||||||
while (true) {
|
while (true) {
|
||||||
this.checkBounds(1);
|
this.checkBounds(1);
|
||||||
const byte = this.view.getUint8(this.offset++);
|
const byte = this.view.getUint8(this.offset++);
|
||||||
result |= (byte & 0x7F) << shift;
|
result |= (byte & 0x7f) << shift;
|
||||||
if ((byte & 0x80) === 0) {
|
if ((byte & 0x80) === 0) {
|
||||||
break; // Last byte of the varint
|
break; // Last byte of the varint
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,7 @@ export class Reader {
|
|||||||
while (true) {
|
while (true) {
|
||||||
this.checkBounds(1);
|
this.checkBounds(1);
|
||||||
const byte = this.view.getUint8(this.offset++);
|
const byte = this.view.getUint8(this.offset++);
|
||||||
result |= (byte & 0x7F) << shift;
|
result |= (byte & 0x7f) << shift;
|
||||||
if ((byte & 0x80) === 0) {
|
if ((byte & 0x80) === 0) {
|
||||||
break; // Last byte of the varint
|
break; // Last byte of the varint
|
||||||
}
|
}
|
||||||
@@ -342,32 +342,36 @@ export class Reader {
|
|||||||
return new Reader(bytes, littleEndian);
|
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;
|
let bytes: Uint8Array;
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case 'utf8':
|
case "utf8":
|
||||||
bytes = new TextEncoder().encode(str);
|
bytes = new TextEncoder().encode(str);
|
||||||
break;
|
break;
|
||||||
case 'ascii':
|
case "ascii":
|
||||||
bytes = new Uint8Array(str.split('').map(c => c.charCodeAt(0)));
|
bytes = new Uint8Array(str.split("").map((c) => c.charCodeAt(0)));
|
||||||
break;
|
break;
|
||||||
case 'hex':
|
case "hex":
|
||||||
bytes = new Uint8Array(str.length / 2);
|
bytes = new Uint8Array(str.length / 2);
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
bytes[i] = parseInt(str.substr(i * 2, 2), 16);
|
bytes[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'base64':
|
case "base64":
|
||||||
bytes = Uint8Array.from(atob(str), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
case 'rawbase64':
|
case "rawbase64":
|
||||||
bytes = Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(str.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
case 'urlbase64':
|
case "urlbase64":
|
||||||
bytes = Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(str.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
case 'rawurlbase64':
|
case "rawurlbase64":
|
||||||
bytes = Uint8Array.from(atob(str.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(str.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return new Reader(bytes.slice().buffer, littleEndian);
|
return new Reader(bytes.slice().buffer, littleEndian);
|
||||||
@@ -387,7 +391,9 @@ export class Reader {
|
|||||||
|
|
||||||
private checkBounds(length: number) {
|
private checkBounds(length: number) {
|
||||||
if (this.offset + length > this.view.byteLength) {
|
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 {
|
public int64(value: number | bigint): void {
|
||||||
this.checkBounds(8);
|
this.checkBounds(8);
|
||||||
if (typeof value === 'number') {
|
if (typeof value === "number") {
|
||||||
value = BigInt(value);
|
value = BigInt(value);
|
||||||
}
|
}
|
||||||
this.view.setBigInt64(this.offset, value, this.littleEndian);
|
this.view.setBigInt64(this.offset, value, this.littleEndian);
|
||||||
@@ -523,7 +529,7 @@ export class Writer {
|
|||||||
*/
|
*/
|
||||||
public uint64(value: number | bigint): void {
|
public uint64(value: number | bigint): void {
|
||||||
this.checkBounds(8);
|
this.checkBounds(8);
|
||||||
if (typeof value === 'number') {
|
if (typeof value === "number") {
|
||||||
value = BigInt(value);
|
value = BigInt(value);
|
||||||
}
|
}
|
||||||
this.view.setBigUint64(this.offset, value, this.littleEndian);
|
this.view.setBigUint64(this.offset, value, this.littleEndian);
|
||||||
@@ -570,7 +576,7 @@ export class Writer {
|
|||||||
// Useful for pre-sizing buffers.
|
// Useful for pre-sizing buffers.
|
||||||
let remaining = value >>> 0; // Ensure unsigned
|
let remaining = value >>> 0; // Ensure unsigned
|
||||||
while (remaining >= 0x80) {
|
while (remaining >= 0x80) {
|
||||||
this.view.setUint8(this.offset++, (remaining & 0x7F) | 0x80);
|
this.view.setUint8(this.offset++, (remaining & 0x7f) | 0x80);
|
||||||
remaining >>>= 7;
|
remaining >>>= 7;
|
||||||
}
|
}
|
||||||
this.view.setUint8(this.offset++, remaining);
|
this.view.setUint8(this.offset++, remaining);
|
||||||
@@ -591,7 +597,7 @@ export class Writer {
|
|||||||
let remaining = value >>> 0; // Ensure unsigned
|
let remaining = value >>> 0; // Ensure unsigned
|
||||||
const isNegative = value < 0;
|
const isNegative = value < 0;
|
||||||
while (remaining >= 0x80 || (isNegative && remaining < 0x80)) {
|
while (remaining >= 0x80 || (isNegative && remaining < 0x80)) {
|
||||||
this.view.setUint8(this.offset++, (remaining & 0x7F) | 0x80);
|
this.view.setUint8(this.offset++, (remaining & 0x7f) | 0x80);
|
||||||
remaining >>>= 7;
|
remaining >>>= 7;
|
||||||
}
|
}
|
||||||
this.view.setUint8(this.offset++, remaining);
|
this.view.setUint8(this.offset++, remaining);
|
||||||
@@ -701,46 +707,60 @@ export class Writer {
|
|||||||
* @param encoding The encoding to use for the string conversion.
|
* @param encoding The encoding to use for the string conversion.
|
||||||
* @returns The encoded string.
|
* @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();
|
const bytes = this.toBytes();
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case 'utf-8':
|
case "utf-8":
|
||||||
return new TextDecoder().decode(bytes);
|
return new TextDecoder().decode(bytes);
|
||||||
case 'hex':
|
case "hex":
|
||||||
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
return Array.from(bytes)
|
||||||
case 'base64':
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
case "base64":
|
||||||
return btoa(String.fromCharCode(...bytes));
|
return btoa(String.fromCharCode(...bytes));
|
||||||
case 'rawbase64':
|
case "rawbase64":
|
||||||
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
return btoa(String.fromCharCode(...bytes))
|
||||||
case 'urlbase64':
|
.replace(/\+/g, "-")
|
||||||
return btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_');
|
.replace(/\//g, "_")
|
||||||
case 'rawurlbase64':
|
.replace(/=+$/, "");
|
||||||
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:
|
default:
|
||||||
throw new Error(`Unsupported encoding: ${encoding}`);
|
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;
|
let bytes: Uint8Array;
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case 'utf-8':
|
case "utf-8":
|
||||||
bytes = new TextEncoder().encode(value);
|
bytes = new TextEncoder().encode(value);
|
||||||
break;
|
break;
|
||||||
case 'hex':
|
case "hex":
|
||||||
bytes = new Uint8Array(value.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)));
|
bytes = new Uint8Array(value.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));
|
||||||
break;
|
break;
|
||||||
case 'base64':
|
case "base64":
|
||||||
bytes = Uint8Array.from(atob(value), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
case 'rawbase64':
|
case "rawbase64":
|
||||||
bytes = Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
case 'urlbase64':
|
case "urlbase64":
|
||||||
bytes = Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
case 'rawurlbase64':
|
case "rawurlbase64":
|
||||||
bytes = Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
|
bytes = Uint8Array.from(atob(value.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported encoding: ${encoding}`);
|
throw new Error(`Unsupported encoding: ${encoding}`);
|
||||||
@@ -764,32 +784,48 @@ export class Writer {
|
|||||||
|
|
||||||
private checkBounds(length: number) {
|
private checkBounds(length: number) {
|
||||||
if (this.offset + length > this.view.byteLength) {
|
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}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { FieldType } from './types';
|
// Exporting types and utilities for external use:
|
||||||
|
export * from "./types";
|
||||||
|
export {
|
||||||
|
FieldType,
|
||||||
|
type Packet,
|
||||||
|
type Protocol,
|
||||||
|
type Dissected,
|
||||||
|
type Segment,
|
||||||
|
type Field,
|
||||||
|
type BitField
|
||||||
|
} from "./types";
|
||||||
export {
|
export {
|
||||||
isBytes,
|
|
||||||
assertBytes,
|
|
||||||
bytesToHex,
|
|
||||||
hexToBytes,
|
|
||||||
bytesToBase64,
|
|
||||||
base64ToBytes,
|
|
||||||
U8,
|
|
||||||
U16,
|
|
||||||
U32,
|
|
||||||
I8,
|
I8,
|
||||||
I16,
|
I16,
|
||||||
I32,
|
I32,
|
||||||
|
U8,
|
||||||
|
U16,
|
||||||
|
U32,
|
||||||
F32,
|
F32,
|
||||||
F64,
|
F64,
|
||||||
|
isBytes,
|
||||||
|
assertBytes,
|
||||||
|
equalBytes,
|
||||||
|
constantTimeEqualBytes,
|
||||||
|
base64ToBytes,
|
||||||
|
bytesToBase64,
|
||||||
|
hexToBytes,
|
||||||
|
bytesToHex,
|
||||||
|
decodeBytes,
|
||||||
|
encodeBytes,
|
||||||
type TypedArray,
|
type TypedArray,
|
||||||
type Encoding,
|
|
||||||
type HexEncoding,
|
type HexEncoding,
|
||||||
type Base64Encoding,
|
type Base64Encoding,
|
||||||
type Base64RawEncoding,
|
type Base64RawEncoding,
|
||||||
type Base64URLEncoding,
|
type Base64URLEncoding,
|
||||||
type Base64RawURLEncoding,
|
type Base64RawURLEncoding,
|
||||||
} from './utils';
|
type Encoding
|
||||||
|
} from "./utils";
|
||||||
|
|||||||
Reference in New Issue
Block a user