Files
julian 1e9219d14a Implement Phase 1 tasks 1.1-1.4 (scaffold + core shell + Teltonika framing)
- Project scaffold (Node 22 + TS 5 + pnpm + vitest + ESLint flat config)
- Core shell: TCP server, session loop, adapter registry, types
- Configuration (zod-validated env) and pino logger
- Teltonika adapter: IMEI handshake, frame envelope, CRC-16/IBM,
  codec dispatch registry, DeviceAuthority seam (AllowAllAuthority default)

Codec data parsers (1.5-1.7), Redis publisher (1.8), and downstream
tasks remain. 36 tests covering CRC, framing, handshake, device
authority, config, and core server. typecheck/lint/test/build all clean.
2026-04-30 15:51:07 +02:00

86 lines
2.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { crc16Ibm } from '../src/adapters/teltonika/crc.js';
describe('crc16Ibm', () => {
it('returns 0x0000 for an empty buffer', () => {
expect(crc16Ibm(Buffer.alloc(0))).toBe(0x0000);
});
it('matches the canonical Teltonika Codec 8 first example (0xC7CF)', () => {
// Full frame (hex stream from the Teltonika doc, first example):
// 000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7CF
//
// Structure:
// 00 00 00 00 preamble
// 00 00 00 36 DataFieldLength = 0x36 = 54
// 08 01 ... body (CodecID through N2) — 54 bytes
// 00 00 C7 CF CRC field
//
// CRC is computed over the 54-byte body only.
const body = Buffer.from(
'08' + // CodecID
'01' + // N1 = 1
'0000016B40D8EA30' + // Timestamp
'01' + // Priority
'00000000' + // Longitude
'00000000' + // Latitude
'0000' + // Altitude
'0000' + // Angle
'00' + // Satellites
'0000' + // Speed
'01' + // Event IO ID
'05' + // N total IO
'02' + // N1 IO (1-byte values)
'15' + '03' + // IO 21 = 3
'01' + '01' + // IO 1 = 1
'01' + // N2 IO (2-byte values)
'42' + '5E0F' + // IO 66 = 0x5E0F
'01' + // N4 IO (4-byte values)
'F1' + '0000601A' + // IO 241 = 0x0000601A
'01' + // N8 IO (8-byte values)
'4E' + '0000000000000000' + // IO 78 = 0
'01', // N2 = 1
'hex',
);
expect(body.length).toBe(0x36); // sanity-check: must be 54 bytes
expect(crc16Ibm(body)).toBe(0xc7cf);
});
it('matches the second Teltonika Codec 8 example (0xF22A)', () => {
// Full frame hex:
// 000000000000002808010000016B40D9AD80010000000000000000000000000000000103021503010101425E100000010000F22A
// Body = bytes from CodecID through N2 (0x28 = 40 bytes)
const body = Buffer.from(
'08' +
'01' +
'0000016B40D9AD80' +
'01' +
'00000000' +
'00000000' +
'0000' +
'0000' +
'00' +
'0000' +
'01' +
'03' +
'02' +
'15' + '03' +
'01' + '01' +
'01' +
'42' + '5E10' +
'00' +
'00' +
'01',
'hex',
);
expect(body.length).toBe(0x28); // 40 bytes
expect(crc16Ibm(body)).toBe(0xf22a);
});
it('produces a non-zero CRC for a single 0xFF byte', () => {
expect(crc16Ibm(Buffer.from([0xff]))).not.toBe(0);
});
});