mirror of
https://github.com/n0mjs710/ipsc2hbpc.git
synced 2026-11-08 04:18:24 +00:00
Feature-for-feature, config-for-config port of the Python ipsc2hbp IPSC↔HBP translator to dependency-free C11 (links only libc). - src/dmr/: self-contained DMR DSP/FEC library (BPTC(196,96), embedded LC, AMBE 49<->72, RS(12,9), Hamming, Golay), validated bit-for-bit against dmr_utils3 via golden vectors. - src/: config (in-tree TOML parser), logging, poll() event loop, UDP, SHA-1/SHA-256/HMAC, IPSC master/peer protocol, HBP client, call translator. - Single-threaded event loop mirrors the original asyncio design. - tests/: DSP self-test + byte-for-byte translator parity vs the Python reference (IPSC->HBP DMRD and HBP->IPSC GROUP_VOICE). make && make test both pass; integration-tested against the fake peer/master. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
179 lines
6.5 KiB
C
179 lines
6.5 KiB
C
/* test_dsp.c — validate the C DMR DSP port against golden vectors generated
|
|
* from the Python dmr_utils3 reference (tests/dsp_vectors.txt). */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "../src/dmr/dmr.h"
|
|
#include "../src/dmr/dmr_internal.h"
|
|
|
|
#define MAXV 512
|
|
static char vkey[MAXV][48];
|
|
static uint8_t vbuf[MAXV][64];
|
|
static int vlen[MAXV];
|
|
static int nvec = 0;
|
|
|
|
static int hexval(char c) {
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
static void load(const char *path) {
|
|
FILE *f = fopen(path, "r");
|
|
if (!f) { perror(path); exit(2); }
|
|
char line[512];
|
|
while (fgets(line, sizeof line, f)) {
|
|
if (line[0] == '#' || line[0] == '\n') continue;
|
|
char *eq = strchr(line, '=');
|
|
if (!eq) continue;
|
|
*eq = 0;
|
|
char *hex = eq + 1;
|
|
strncpy(vkey[nvec], line, sizeof vkey[0] - 1);
|
|
int n = 0;
|
|
for (char *p = hex; p[0] && p[1] && hexval(p[0]) >= 0; p += 2) {
|
|
vbuf[nvec][n++] = (uint8_t)((hexval(p[0]) << 4) | hexval(p[1]));
|
|
}
|
|
vlen[nvec] = n;
|
|
nvec++;
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
static const uint8_t *get(const char *key, int *len) {
|
|
for (int i = 0; i < nvec; i++)
|
|
if (strcmp(vkey[i], key) == 0) { if (len) *len = vlen[i]; return vbuf[i]; }
|
|
fprintf(stderr, "MISSING vector: %s\n", key);
|
|
exit(2);
|
|
}
|
|
|
|
static int fails = 0, checks = 0;
|
|
/* Look up the expected vector by key and compare against got. */
|
|
static void check(const char *what, const uint8_t *got, int gn) {
|
|
checks++;
|
|
int en;
|
|
const uint8_t *exp = get(what, &en);
|
|
if (gn != en || memcmp(got, exp, gn) != 0) {
|
|
fails++;
|
|
fprintf(stderr, "FAIL %s\n got(%d): ", what, gn);
|
|
for (int i = 0; i < gn; i++) fprintf(stderr, "%02x", got[i]);
|
|
fprintf(stderr, "\n exp(%d): ", en);
|
|
for (int i = 0; i < en; i++) fprintf(stderr, "%02x", exp[i]);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
load(argc > 1 ? argv[1] : "tests/dsp_vectors.txt");
|
|
char key[64];
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
int n;
|
|
char base[16]; snprintf(base, sizeof base, "lc%d", i);
|
|
snprintf(key, sizeof key, "%s.in", base);
|
|
const uint8_t *lc = get(key, &n);
|
|
uint8_t lc9[9]; memcpy(lc9, lc, 9);
|
|
|
|
uint8_t out3[3];
|
|
dmr_rs129_lc_encode(lc9, 0, out3);
|
|
snprintf(key, sizeof key, "%s.rs_header", base);
|
|
check(key, out3, 3);
|
|
dmr_rs129_lc_encode(lc9, 1, out3);
|
|
snprintf(key, sizeof key, "%s.rs_term", base);
|
|
check(key, out3, 3);
|
|
|
|
/* csum5: stored byte is the 5-bit value left-shifted by 3 (bitarray pad) */
|
|
uint8_t cs = (uint8_t)(dmr_csum5(lc9) << 3);
|
|
snprintf(key, sizeof key, "%s.csum5", base);
|
|
check(key, &cs, 1);
|
|
|
|
/* encode_19696 of lc + rs_header */
|
|
uint8_t data12[12]; memcpy(data12, lc9, 9);
|
|
dmr_rs129_lc_encode(lc9, 0, data12 + 9);
|
|
dmr_bit b196[196]; uint8_t bytes25[25];
|
|
dmr_bptc_encode_19696(data12, b196);
|
|
dmr_bits_to_bytes(b196, 196, bytes25);
|
|
snprintf(key, sizeof key, "%s.encode_19696", base);
|
|
check(key, bytes25, 25);
|
|
|
|
/* encode_header_lc / encode_terminator_lc */
|
|
dmr_bptc_encode_lc(lc9, 0, b196);
|
|
dmr_bits_to_bytes(b196, 196, bytes25);
|
|
snprintf(key, sizeof key, "%s.encode_header_lc", base);
|
|
check(key, bytes25, 25);
|
|
|
|
dmr_bit hdr196[196];
|
|
memcpy(hdr196, b196, 196); /* keep header codeword for decode test */
|
|
|
|
dmr_bptc_encode_lc(lc9, 1, b196);
|
|
dmr_bits_to_bytes(b196, 196, bytes25);
|
|
snprintf(key, sizeof key, "%s.encode_terminator_lc", base);
|
|
check(key, bytes25, 25);
|
|
|
|
/* decode_full_lc of the header codeword → original LC */
|
|
uint8_t dec9[9];
|
|
dmr_bptc_decode_full_lc(hdr196, dec9);
|
|
snprintf(key, sizeof key, "%s.decode_full_lc", base);
|
|
check(key, dec9, 9);
|
|
|
|
/* embedded LC */
|
|
uint8_t emb[4][4];
|
|
dmr_encode_emblc(lc9, emb);
|
|
for (int k = 1; k <= 4; k++) {
|
|
snprintf(key, sizeof key, "%s.emblc%d", base, k);
|
|
check(key, emb[k - 1], 4);
|
|
}
|
|
}
|
|
|
|
/* Hamming */
|
|
int v15[] = {0x000,0x7FF,0x555,0x2AA,0x123,0x400,0x001};
|
|
int v9[] = {0x000,0x1FF,0x155,0x0AA,0x123,0x100,0x001};
|
|
for (size_t i = 0; i < sizeof v15/sizeof v15[0]; i++) {
|
|
int val = v15[i], n;
|
|
dmr_bit d[11]; for (int b = 0; b < 11; b++) d[b] = (val >> (10 - b)) & 1;
|
|
dmr_bit c[5]; uint8_t ob[1];
|
|
dmr_ham_15113(d, c); dmr_bits_to_bytes(c, 4, ob);
|
|
snprintf(key, sizeof key, "ham15113.%03x", val); check(key, ob, 1);
|
|
dmr_ham_16114(d, c); dmr_bits_to_bytes(c, 5, ob);
|
|
snprintf(key, sizeof key, "ham16114.%03x", val); check(key, ob, 1);
|
|
}
|
|
for (size_t i = 0; i < sizeof v9/sizeof v9[0]; i++) {
|
|
int val = v9[i], n;
|
|
dmr_bit d[9]; for (int b = 0; b < 9; b++) d[b] = (val >> (8 - b)) & 1;
|
|
dmr_bit c[4]; uint8_t ob[1];
|
|
dmr_ham_1393(d, c); dmr_bits_to_bytes(c, 4, ob);
|
|
snprintf(key, sizeof key, "ham1393.%03x", val); check(key, ob, 1);
|
|
}
|
|
|
|
/* AMBE round trips */
|
|
for (int i = 0; i < 4; i++) {
|
|
int n;
|
|
snprintf(key, sizeof key, "ambe72to49.%d.in", i);
|
|
const uint8_t *in = get(key, &n);
|
|
dmr_bit in72[72]; dmr_bytes_to_bits(in, 9, in72);
|
|
dmr_bit a49[49]; dmr_ambe_72_to_49(in72, a49);
|
|
uint8_t out7[7]; dmr_bits_to_bytes(a49, 49, out7);
|
|
snprintf(key, sizeof key, "ambe72to49.%d.out", i);
|
|
check(key, out7, 7);
|
|
dmr_bit a72[72]; dmr_ambe_49_to_72(a49, a72);
|
|
uint8_t out9[9]; dmr_bits_to_bytes(a72, 72, out9);
|
|
snprintf(key, sizeof key, "ambe49to72.%d.out", i);
|
|
check(key, out9, 9);
|
|
}
|
|
|
|
/* const tables */
|
|
{ int n; uint8_t b[8];
|
|
dmr_bits_to_bytes(DMR_BS_VOICE_SYNC, 48, b); check("BS_VOICE_SYNC", b, 6);
|
|
dmr_bits_to_bytes(DMR_BS_DATA_SYNC, 48, b); check("BS_DATA_SYNC", b, 6);
|
|
check("LC_OPT", DMR_LC_OPT, 3);
|
|
const char *en[] = {"BURST_B","BURST_C","BURST_D","BURST_E","BURST_F"};
|
|
for (int e = 0; e < 5; e++) { dmr_bits_to_bytes(DMR_EMB[e],16,b);
|
|
snprintf(key,sizeof key,"EMB.%s",en[e]); check(key,b,2); }
|
|
dmr_bits_to_bytes(DMR_SLOT_TYPE_VHEAD,20,b); check("SLOT_TYPE.VOICE_LC_HEAD",b,3);
|
|
dmr_bits_to_bytes(DMR_SLOT_TYPE_VTERM,20,b); check("SLOT_TYPE.VOICE_LC_TERM",b,3);
|
|
}
|
|
|
|
printf("DSP self-test: %d checks, %d failures\n", checks, fails);
|
|
return fails ? 1 : 0;
|
|
}
|