4 Commits

Author SHA1 Message Date
78dbd3b0ef Version 1.1.3 2026-03-18 10:07:06 +01:00
df266bab12 Correctly parse compressed position with no timestamp 2026-03-18 10:06:45 +01:00
0ab62dab02 Version 1.1.2 2026-03-16 13:16:18 +01:00
38b617728c Bug fixes in structure parsing 2026-03-16 13:16:06 +01:00
6 changed files with 35 additions and 22 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@hamradio/aprs",
"version": "1.1.1",
"version": "1.1.3",
"description": "APRS (Automatic Packet Reporting System) protocol support for Typescript",
"keywords": [
"APRS",

View File

@@ -378,6 +378,13 @@ export class Frame implements IFrame {
if (routingSection) {
structure.push(routingSection);
}
// Add data type identifier section
structure.push({
name: "data type",
data: new TextEncoder().encode(this.payload.charAt(0)).buffer,
isString: true,
fields: [{ type: FieldType.CHAR, name: "identifier", length: 1 }],
});
if (payloadsegment) {
structure.push(...payloadsegment);
}
@@ -416,7 +423,9 @@ export class Frame implements IFrame {
offset += 7;
}
if (this.payload.length < offset + 19) return { payload: null };
// Need at least enough characters for compressed position (13) or
// uncompressed (19). Allow parsing to continue if compressed-length is present.
if (this.payload.length < offset + 13) return { payload: null };
// Check if compressed format
const isCompressed = this.isCompressedPosition(
@@ -2306,34 +2315,34 @@ const parseFrame = (data: string): Frame => {
pathFields.push({
type: FieldType.CHAR,
name: `Path separator ${i}`,
name: `path separator ${i}`,
length: 1,
});
pathFields.push({
type: FieldType.STRING,
name: `Repeater ${i}`,
name: `repeater ${i}`,
length: pathStr.length,
});
}
const routingSection: Segment = {
name: "Routing",
data: encoder.encode(data.slice(0, routeSepIndex)).buffer,
name: "routing",
data: encoder.encode(data.slice(0, routeSepIndex + 1)).buffer,
isString: true,
fields: [
{
type: FieldType.STRING,
name: "Source address",
name: "source address",
length: sourceStr.length,
},
{ type: FieldType.CHAR, name: "Route separator", length: 1 },
{ type: FieldType.CHAR, name: "route separator", length: 1 },
{
type: FieldType.STRING,
name: "Destination address",
name: "destination address",
length: destinationStr.length,
},
...pathFields,
{ type: FieldType.CHAR, name: "Payload separator", length: 1 },
{ type: FieldType.CHAR, name: "payload separator", length: 1 },
],
};

View File

@@ -1,6 +1,10 @@
import { describe, it, expect } from "vitest";
import { Frame } from "../src/frame";
import type { Payload, StationCapabilitiesPayload } from "../src/frame.types";
import {
DataType,
type Payload,
type StationCapabilitiesPayload,
} from "../src/frame.types";
import { Dissected } from "@hamradio/packet";
describe("Frame.decodeCapabilities", () => {
@@ -9,7 +13,7 @@ describe("Frame.decodeCapabilities", () => {
const frame = Frame.fromString(data);
const decoded = frame.decode() as StationCapabilitiesPayload;
expect(decoded).not.toBeNull();
expect(decoded.type).toBe("capabilities");
expect(decoded.type).toBe(DataType.StationCapabilities);
expect(Array.isArray(decoded.capabilities)).toBeTruthy();
expect(decoded.capabilities).toContain("IGATE");
expect(decoded.capabilities).toContain("MSG_CNT");
@@ -23,7 +27,7 @@ describe("Frame.decodeCapabilities", () => {
structure: Dissected;
};
expect(res.payload).not.toBeNull();
if (res.payload && res.payload.type !== "capabilities")
if (res.payload && res.payload.type !== DataType.StationCapabilities)
throw new Error("expected capabilities payload");
expect(res.structure).toBeDefined();
const caps = res.structure.find((s) => s.name === "capabilities");

View File

@@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest";
import { Frame } from "../src/frame";
import type { RawGPSPayload } from "../src/frame.types";
import { DataType, type RawGPSPayload } from "../src/frame.types";
import { Dissected } from "@hamradio/packet";
describe("Raw GPS decoding", () => {
@@ -13,7 +13,7 @@ describe("Raw GPS decoding", () => {
const payload = f.decode(false) as RawGPSPayload | null;
expect(payload).not.toBeNull();
expect(payload?.type).toBe("raw-gps");
expect(payload?.type).toBe(DataType.RawGPS);
expect(payload?.sentence).toBe(sentence);
expect(payload?.position).toBeDefined();
expect(typeof payload?.position?.latitude).toBe("number");
@@ -32,7 +32,7 @@ describe("Raw GPS decoding", () => {
};
expect(result.payload).not.toBeNull();
expect(result.payload?.type).toBe("raw-gps");
expect(result.payload?.type).toBe(DataType.RawGPS);
expect(result.payload?.sentence).toBe(sentence);
expect(result.payload?.position).toBeDefined();
expect(typeof result.payload?.position?.latitude).toBe("number");

View File

@@ -837,19 +837,19 @@ describe("Packet dissection with sections", () => {
expect(result.structure).toBeDefined();
expect(result.structure.length).toBeGreaterThan(0);
const routingSection = result.structure.find((s) => s.name === "Routing");
const routingSection = result.structure.find((s) => s.name === "routing");
expect(routingSection).toBeDefined();
expect(routingSection?.fields).toBeDefined();
expect(routingSection?.fields?.length).toBeGreaterThan(0);
const sourceField = routingSection?.fields?.find(
(a) => a.name === "Source address",
(a) => a.name === "source address",
);
expect(sourceField).toBeDefined();
expect(sourceField?.length).toBeGreaterThan(0);
const destField = routingSection?.fields?.find(
(a) => a.name === "Destination address",
(a) => a.name === "destination address",
);
expect(destField).toBeDefined();
expect(destField?.length).toBeGreaterThan(0);

View File

@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest";
import { Dissected } from "@hamradio/packet";
import { Frame } from "../src/frame";
import type { UserDefinedPayload } from "../src/frame.types";
import { DataType, type UserDefinedPayload } from "../src/frame.types";
describe("Frame.decodeUserDefined", () => {
it("parses packet type only", () => {
@@ -9,7 +9,7 @@ describe("Frame.decodeUserDefined", () => {
const frame = Frame.fromString(data);
const decoded = frame.decode() as UserDefinedPayload;
expect(decoded).not.toBeNull();
expect(decoded.type).toBe("user-defined");
expect(decoded.type).toBe(DataType.UserDefined);
expect(decoded.userPacketType).toBe("01");
expect(decoded.data).toBe("");
});
@@ -22,7 +22,7 @@ describe("Frame.decodeUserDefined", () => {
structure: Dissected;
};
expect(res.payload).not.toBeNull();
expect(res.payload.type).toBe("user-defined");
expect(res.payload.type).toBe(DataType.UserDefined);
expect(res.payload.userPacketType).toBe("TEX");
expect(res.payload.data).toBe("Hello world");