feat: establish standalone Device Core repository

This commit is contained in:
DCCONSTRUCTIONS
2026-08-21 11:51:21 +03:00
commit e0bac205d0
244 changed files with 51962 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
{
"name": "@nodedc/arusnavi-b2-adapter",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.mjs"
},
"scripts": {
"test": "node --test test/*.test.mjs"
},
"engines": {
"node": ">=20"
}
}
+331
View File
@@ -0,0 +1,331 @@
import { createHash } from "node:crypto";
import {
DEVICE_ADAPTER_CONTRACT_VERSION,
defineDeviceAdapter,
} from "../../device-adapter-runtime/src/index.mjs";
export const ARUSNAVI_INTERNAL_SPECIFICATION_REF =
"arusnavi.internal.protocol-sheet.gid-12.v1";
const HEADER2_LENGTH = 10;
const HEADER_START = 0xff;
const HEADER2_GPRS_VERSION = 0x23;
const PACKAGE_START = 0x5b;
const PACKAGE_END = 0x5d;
const SERVER_COMMAND_START = 0x7b;
const SERVER_COMMAND_END = 0x7d;
const MIN_PACKAGE_NUMBER = 0x01;
const MAX_PACKAGE_NUMBER = 0xfb;
const PACKET_FIXED_LENGTH = 8;
const MAX_PACKET_DATA_LENGTH = 32 * 1024;
const MAX_PACKAGE_LENGTH = 64 * 1024;
export const ARUSNAVI_B2_MODEL_PROFILE = deepFreeze({
schemaVersion: "nodedc.device-model-profile.v1",
profileRef: "arusnavi.b2.internal.v1",
vendor: "ARUSNAVI",
model: "B2",
deviceType: "tracker",
protocol: "INTERNAL",
monitoringServerSlots: 4,
serverIdentity: {
kind: "imei",
source: "modem",
trust: "claimed-not-ownership-proof",
},
bootstrap: {
operatorSurface: "ARUSNAVI_WEB_OR_LOCAL_CONFIGURATOR",
platformCredentialRequired: false,
preserveExistingRoutes: true,
},
framing: {
status: "verified-read-only",
specificationRef: ARUSNAVI_INTERNAL_SPECIFICATION_REF,
headerVersion: "HEADER2_GPRS_0x23",
headerBytes: HEADER2_LENGTH,
maxBufferedBytes: MAX_PACKAGE_LENGTH,
},
acknowledgement: {
header: "server-time-only",
package: "package-number-only",
},
commandTransport: {
status: "typed-service-ping-v1",
exportedCommandBuilders: 1,
},
routeCompatibility: {
gelios: "parallel-preserved",
automaticCommandFailover: false,
},
});
export const ARUSNAVI_B2_ADAPTER = defineDeviceAdapter({
contractVersion: DEVICE_ADAPTER_CONTRACT_VERSION,
adapterRef: "arusnavi-b2",
runtimePackageRef: "@nodedc/arusnavi-b2-adapter",
profiles: [ARUSNAVI_B2_MODEL_PROFILE],
createSession({ profileRef } = {}) {
if (profileRef !== ARUSNAVI_B2_MODEL_PROFILE.profileRef) {
throw new TypeError("b2_adapter_profile_unsupported");
}
return {
parseHeader: tryParseB2Header2,
buildHeaderAcknowledgement: buildB2HeaderAcknowledgement,
parseMessage: tryParseB2Package,
buildMessageAcknowledgement(message) {
return buildB2PackageAcknowledgement(message.packageNumber);
},
buildTypedCommand: buildB2TypedCommand,
parseTypedCommandResponse: tryParseB2TypedCommandResponse,
};
},
});
export function tryParseB2Header2(input) {
assertBuffer(input, "b2_header_buffer_required");
if (input.length < HEADER2_LENGTH) {
return Object.freeze({
status: "incomplete",
minimumBytes: HEADER2_LENGTH,
});
}
if (input[0] !== HEADER_START) {
throw new TypeError("b2_header_start_invalid");
}
if (input[1] !== HEADER2_GPRS_VERSION) {
throw new TypeError("b2_header_version_unsupported");
}
const identifier = input.readBigUInt64LE(2).toString(10);
if (!/^\d{15}$/.test(identifier)) {
throw new TypeError("b2_header_imei_invalid");
}
return Object.freeze({
status: "complete",
bytesConsumed: HEADER2_LENGTH,
identifier: Object.freeze({
kind: "imei",
value: identifier,
trust: "claimed-not-ownership-proof",
}),
evidence: Object.freeze({
transport: "tcp",
bytesObserved: HEADER2_LENGTH,
framingStatus: "verified",
specificationRef: ARUSNAVI_INTERNAL_SPECIFICATION_REF,
}),
});
}
export function buildB2HeaderAcknowledgement(unixSeconds) {
const timestamp = normalizeUInt32(
unixSeconds,
"b2_header_ack_timestamp_invalid",
);
const commandData = Buffer.allocUnsafe(4);
commandData.writeUInt32LE(timestamp);
return Buffer.from([
SERVER_COMMAND_START,
commandData.length,
0x00,
checksum(commandData),
...commandData,
SERVER_COMMAND_END,
]);
}
export function tryParseB2Package(input) {
assertBuffer(input, "b2_package_buffer_required");
if (input.length === 0) {
return Object.freeze({ status: "incomplete", minimumBytes: 1 });
}
if (input[0] !== PACKAGE_START) {
throw new TypeError("b2_package_start_invalid");
}
if (input.length < 3) {
return Object.freeze({ status: "incomplete", minimumBytes: 3 });
}
const packageNumber = input[1];
if (
packageNumber < MIN_PACKAGE_NUMBER
|| packageNumber > MAX_PACKAGE_NUMBER
) {
throw new TypeError("b2_package_number_invalid");
}
let offset = 2;
let packetCount = 0;
while (true) {
if (offset >= MAX_PACKAGE_LENGTH) {
throw new TypeError("b2_package_length_exceeded");
}
if (offset >= input.length) {
return Object.freeze({
status: "incomplete",
minimumBytes: offset + 1,
});
}
if (input[offset] === PACKAGE_END) {
if (packetCount === 0) {
throw new TypeError("b2_package_empty");
}
return Object.freeze({
status: "complete",
bytesConsumed: offset + 1,
packageNumber,
packetCount,
messageType: "telemetry.package",
payloadSchemaRef: "arusnavi.internal.package-metadata.v1",
payload: Object.freeze({
packageNumber,
packetCount,
byteLength: offset + 1,
packageDigest: `sha256:${createHash("sha256")
.update(input.subarray(0, offset + 1))
.digest("hex")}`,
}),
});
}
if (input.length - offset < 3) {
return Object.freeze({
status: "incomplete",
minimumBytes: offset + 3,
});
}
const dataLength = input.readUInt16LE(offset + 1);
if (dataLength > MAX_PACKET_DATA_LENGTH) {
throw new TypeError("b2_packet_data_length_exceeded");
}
const packetLength = PACKET_FIXED_LENGTH + dataLength;
const packetEnd = offset + packetLength;
if (packetEnd + 1 > MAX_PACKAGE_LENGTH) {
throw new TypeError("b2_package_length_exceeded");
}
if (input.length < packetEnd) {
return Object.freeze({
status: "incomplete",
minimumBytes: packetEnd,
});
}
const expectedChecksum = checksum(
input.subarray(offset + 3, packetEnd - 1),
);
if (input[packetEnd - 1] !== expectedChecksum) {
throw new TypeError("b2_packet_checksum_invalid");
}
packetCount += 1;
offset = packetEnd;
}
}
export function buildB2PackageAcknowledgement(packageNumber) {
const normalized = Number(packageNumber);
if (
!Number.isSafeInteger(normalized)
|| normalized < MIN_PACKAGE_NUMBER
|| normalized > MAX_PACKAGE_NUMBER
) {
throw new TypeError("b2_package_ack_number_invalid");
}
return Buffer.from([
SERVER_COMMAND_START,
0x00,
normalized,
SERVER_COMMAND_END,
]);
}
export function buildB2TypedCommand({ commandType, accessCode } = {}) {
if (commandType !== "service.ping") {
throw new TypeError("b2_typed_command_unsupported");
}
if (typeof accessCode !== "string" || !/^\d{6}$/.test(accessCode)) {
throw new TypeError("b2_command_access_code_invalid");
}
return Buffer.from(`${accessCode}*SERV*1.1`, "ascii");
}
export function tryParseB2TypedCommandResponse(input, { commandType } = {}) {
assertBuffer(input, "b2_command_response_buffer_required");
if (commandType !== "service.ping") {
throw new TypeError("b2_typed_command_unsupported");
}
const expected = Buffer.from("SERV OK", "ascii");
const compared = Math.min(input.length, expected.length);
if (!input.subarray(0, compared).equals(expected.subarray(0, compared))) {
return Object.freeze({ status: "not-command" });
}
if (input.length < expected.length) {
return Object.freeze({ status: "incomplete", minimumBytes: expected.length });
}
return Object.freeze({
status: "acknowledged",
bytesConsumed: expected.length,
resultCode: "serv_ok",
});
}
export function assertB2ProfileInvariant(profile = ARUSNAVI_B2_MODEL_PROFILE) {
if (profile.monitoringServerSlots !== 4) {
throw new TypeError("b2_server_slot_count_invalid");
}
if (profile.protocol !== "INTERNAL") {
throw new TypeError("b2_protocol_invalid");
}
if (profile.serverIdentity.kind !== "imei") {
throw new TypeError("b2_identity_kind_invalid");
}
if (profile.framing.status !== "verified-read-only") {
throw new TypeError("b2_framing_must_be_verified");
}
if (
profile.framing.specificationRef
!== ARUSNAVI_INTERNAL_SPECIFICATION_REF
) {
throw new TypeError("b2_framing_specification_invalid");
}
if (profile.commandTransport.status !== "typed-service-ping-v1") {
throw new TypeError("b2_command_transport_profile_invalid");
}
if (profile.routeCompatibility.gelios !== "parallel-preserved") {
throw new TypeError("b2_gelios_route_must_be_preserved");
}
return true;
}
function checksum(input) {
let value = 0;
for (const byte of input) value = (value + byte) & 0xff;
return value;
}
function normalizeUInt32(value, errorCode) {
const normalized = Number(value);
if (
!Number.isSafeInteger(normalized)
|| normalized < 0
|| normalized > 0xffffffff
) {
throw new TypeError(errorCode);
}
return normalized;
}
function assertBuffer(input, errorCode) {
if (!Buffer.isBuffer(input)) {
throw new TypeError(errorCode);
}
}
function deepFreeze(value) {
if (!value || typeof value !== "object" || Object.isFrozen(value)) {
return value;
}
Object.values(value).forEach(deepFreeze);
return Object.freeze(value);
}
@@ -0,0 +1,164 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
ARUSNAVI_B2_MODEL_PROFILE,
ARUSNAVI_INTERNAL_SPECIFICATION_REF,
assertB2ProfileInvariant,
buildB2HeaderAcknowledgement,
buildB2PackageAcknowledgement,
buildB2TypedCommand,
tryParseB2Header2,
tryParseB2Package,
tryParseB2TypedCommandResponse,
} from "../src/index.mjs";
const specificationHeader = Buffer.from(
"FF23E9EF782DE7120300",
"hex",
);
const specificationPackage = Buffer.from(
"5B01010000FBDEC251EC5D",
"hex",
);
test("records the official B2 route, framing and identity evidence", () => {
assert.equal(assertB2ProfileInvariant(), true);
assert.equal(ARUSNAVI_B2_MODEL_PROFILE.monitoringServerSlots, 4);
assert.equal(ARUSNAVI_B2_MODEL_PROFILE.protocol, "INTERNAL");
assert.equal(ARUSNAVI_B2_MODEL_PROFILE.serverIdentity.kind, "imei");
assert.equal(
ARUSNAVI_B2_MODEL_PROFILE.framing.specificationRef,
ARUSNAVI_INTERNAL_SPECIFICATION_REF,
);
assert.equal(
ARUSNAVI_B2_MODEL_PROFILE.routeCompatibility.gelios,
"parallel-preserved",
);
});
test("parses the official HEADER2 example as a claimed IMEI", () => {
assert.equal(
tryParseB2Header2(specificationHeader.subarray(0, 9)).status,
"incomplete",
);
const parsed = tryParseB2Header2(specificationHeader);
assert.equal(parsed.status, "complete");
assert.equal(parsed.bytesConsumed, 10);
assert.equal(parsed.identifier.kind, "imei");
assert.equal(parsed.identifier.value, "865209039777769");
assert.equal(parsed.identifier.trust, "claimed-not-ownership-proof");
assert.deepEqual(parsed.evidence, {
transport: "tcp",
bytesObserved: 10,
framingStatus: "verified",
specificationRef: ARUSNAVI_INTERNAL_SPECIFICATION_REF,
});
});
test("builds the official HEADER2 acknowledgement example", () => {
assert.equal(
buildB2HeaderAcknowledgement(0x52db95de).toString("hex").toUpperCase(),
"7B0400A0DE95DB527D",
);
});
test("parses and acknowledges the official package example", () => {
assert.equal(
tryParseB2Package(specificationPackage.subarray(0, -1)).status,
"incomplete",
);
const parsed = tryParseB2Package(specificationPackage);
assert.equal(parsed.status, "complete");
assert.equal(parsed.bytesConsumed, specificationPackage.length);
assert.equal(parsed.packageNumber, 1);
assert.equal(parsed.packetCount, 1);
assert.equal(parsed.messageType, "telemetry.package");
assert.equal(
parsed.payloadSchemaRef,
"arusnavi.internal.package-metadata.v1",
);
assert.deepEqual(parsed.payload, {
packageNumber: 1,
packetCount: 1,
byteLength: specificationPackage.length,
packageDigest:
"sha256:bba7205d2f613ac3dfdb7bccdee292b3837e1c8d3d1254ee68bba2dda3853e11",
});
assert.equal(
buildB2PackageAcknowledgement(1).toString("hex").toUpperCase(),
"7B00017D",
);
});
test("uses packet lengths and checksum instead of scanning for 0x5D", () => {
const packetData = Buffer.from([0x5d]);
const unixTime = Buffer.from([0x01, 0x00, 0x00, 0x00]);
const checksum = (0x01 + 0x5d) & 0xff;
const packageBytes = Buffer.from([
0x5b,
0x02,
0x01,
packetData.length,
0x00,
...unixTime,
...packetData,
checksum,
0x5d,
]);
const parsed = tryParseB2Package(packageBytes);
assert.equal(parsed.status, "complete");
assert.equal(parsed.bytesConsumed, packageBytes.length);
assert.equal(parsed.packageNumber, 2);
assert.equal(parsed.packetCount, 1);
assert.equal(parsed.messageType, "telemetry.package");
assert.equal(parsed.payload.byteLength, packageBytes.length);
assert.match(parsed.payload.packageDigest, /^sha256:[a-f0-9]{64}$/);
});
test("fails closed on unsupported headers and malformed packages", () => {
assert.throws(
() => tryParseB2Header2(Buffer.from("FE23E9EF782DE7120300", "hex")),
/b2_header_start_invalid/,
);
assert.throws(
() => tryParseB2Header2(Buffer.from("FF24E9EF782DE7120300", "hex")),
/b2_header_version_unsupported/,
);
const badChecksum = Buffer.from(specificationPackage);
badChecksum[badChecksum.length - 2] ^= 0xff;
assert.throws(
() => tryParseB2Package(badChecksum),
/b2_packet_checksum_invalid/,
);
});
test("exports only the typed service-ping command", () => {
assert.equal(
ARUSNAVI_B2_MODEL_PROFILE.commandTransport.status,
"typed-service-ping-v1",
);
assert.equal(
ARUSNAVI_B2_MODEL_PROFILE.commandTransport.exportedCommandBuilders,
1,
);
assert.equal(
buildB2TypedCommand({ commandType: "service.ping", accessCode: "123456" })
.toString("ascii"),
"123456*SERV*1.1",
);
assert.deepEqual(
tryParseB2TypedCommandResponse(Buffer.from("SERV OK", "ascii"), {
commandType: "service.ping",
}),
{ status: "acknowledged", bytesConsumed: 7, resultCode: "serv_ok" },
);
assert.throws(
() => buildB2TypedCommand({ commandType: "service.ping", accessCode: "12345" }),
/b2_command_access_code_invalid/,
);
assert.throws(
() => buildB2TypedCommand({ commandType: "firmware.update", accessCode: "123456" }),
/b2_typed_command_unsupported/,
);
});