mirror of
https://github.com/n0mjs710/ipsc2hbpc.git
synced 2026-11-08 04:18:24 +00:00
IPSC->HBP: PVT_VOICE (0x81) was dropped. It uses the GROUP_VOICE layout, so it now goes through the same path: DMRD gets the private-call flag and the LC is FLCO 3. Late entry anchors on a burst E carrying FLCO 3. HBP->IPSC: the HBP header LC was already copied verbatim, so radios saw private calls, but the packets went out as GROUP_VOICE. The opcode now follows the LC's FLCO, and the fallback LCs (late entry, orphan terminator) honour the HBP private-call flag. No on/off setting: the translator carries private calls; the network server decides who may make them. Confirmed private calls are not supported. Tests replay a PVT_VOICE call captured from a MOTOTRBO repeater and an HBP private call captured from HBlink3, full and late entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
5.8 KiB
C
133 lines
5.8 KiB
C
/* test_private.c — private (unit-to-unit) voice in both directions, using calls
|
|
* captured from a MOTOTRBO repeater and an HBlink3 server.
|
|
*
|
|
* pvt_ipsc_in.txt : PVT_VOICE call 3120999 -> 3120101, TS1, from the repeater
|
|
* pvt_hbp_in.txt : HBP private-call DMRD 3120101 -> 3120999, TS1, from HBlink3
|
|
*
|
|
* Each direction is run with the full call and as a late entry (headers dropped).
|
|
* Stubs the ipsc and hbp layers so only translate.c + the dmr module run. */
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "../src/translate.h"
|
|
#include "../src/config.h"
|
|
#include "../src/eventloop.h"
|
|
#include "../src/dmr/dmr.h"
|
|
|
|
#define MAXCAP 256
|
|
static uint8_t cap[MAXCAP][128]; static int caplen[MAXCAP]; static int ncap;
|
|
static uint8_t gv[MAXCAP][128]; static int gvlen[MAXCAP]; static int ngv;
|
|
|
|
int ipsc_has_peers(struct ipsc *ip) { (void)ip; return 1; }
|
|
void ipsc_send_voice(struct ipsc *ip, const uint8_t *p, int n) {
|
|
(void)ip; if (ngv < MAXCAP) { memcpy(gv[ngv], p, (size_t)n); gvlen[ngv++] = n; }
|
|
}
|
|
int hbp_is_connected(struct hbp *hb) { (void)hb; return 1; }
|
|
void hbp_activate(struct hbp *hb) { (void)hb; }
|
|
void hbp_deactivate(struct hbp *hb) { (void)hb; }
|
|
void hbp_send_dmrd(struct hbp *hb, const uint8_t *d, int n) {
|
|
(void)hb; if (ncap < MAXCAP) { memcpy(cap[ncap], d, (size_t)n); caplen[ncap++] = n; }
|
|
}
|
|
|
|
static void stop_cb(ev_loop *loop, void *ud){ (void)ud; ev_stop(loop); }
|
|
static int hexv(char c){ if(c>='0'&&c<='9')return c-'0'; if(c>='a'&&c<='f')return c-'a'+10; return -1; }
|
|
static int unhex(const char *s, uint8_t *out){ int n=0; for(;s[0]&&s[1]&&hexv(s[0])>=0;s+=2) out[n++]=(uint8_t)((hexv(s[0])<<4)|hexv(s[1])); return n; }
|
|
static unsigned id3(const uint8_t *p) { return (unsigned)(p[0]<<16 | p[1]<<8 | p[2]); }
|
|
|
|
static int fails;
|
|
#define CHECK(cond, ...) do { if (!(cond)) { fprintf(stderr, "FAIL: " __VA_ARGS__); fprintf(stderr, "\n"); fails++; } } while (0)
|
|
|
|
/* LC carried in a DMRD voice header / terminator (BPTC full LC). */
|
|
static void dmrd_lc(const uint8_t *dmrd, uint8_t lc[9])
|
|
{
|
|
dmr_bit fb[264]; dmr_bytes_to_bits(dmrd + 20, 33, fb);
|
|
dmr_bit b[196]; memcpy(b, fb, 98); memcpy(b + 98, fb + 166, 98);
|
|
dmr_bptc_decode_full_lc(b, lc);
|
|
}
|
|
|
|
static void ipsc_to_hbp(const Config *cfg, ev_loop *loop, int late)
|
|
{
|
|
const char *name = late ? "IPSC->HBP late entry" : "IPSC->HBP";
|
|
translator *tr = translator_new(cfg, loop);
|
|
translator_set_protocols(tr, (struct ipsc *)1, (struct hbp *)1);
|
|
ncap = 0;
|
|
FILE *f = fopen("tests/pvt_ipsc_in.txt", "r");
|
|
if (!f) { perror("tests/pvt_ipsc_in.txt"); exit(2); }
|
|
char line[512]; int fed = 0;
|
|
while (fgets(line, sizeof line, f)) {
|
|
int ts, bt; char hex[400];
|
|
if (sscanf(line, "%d %d %399s", &ts, &bt, hex) != 3) continue;
|
|
if (late && bt == 1) continue; /* drop VOICE_HEADs */
|
|
uint8_t raw[400]; int n = unhex(hex, raw);
|
|
translator_ipsc_voice_received(tr, raw, n, ts, bt);
|
|
fed++;
|
|
}
|
|
fclose(f);
|
|
CHECK(ncap > 0, "%s: no DMRD produced", name);
|
|
int hdr = 0, term = 0;
|
|
for (int i = 0; i < ncap; i++) {
|
|
const uint8_t *d = cap[i]; int fl = d[15];
|
|
CHECK(fl & 0x40, "%s: DMRD[%d] flags 0x%02x missing private-call bit", name, i, fl);
|
|
CHECK(!(fl & 0x80), "%s: DMRD[%d] not TS1", name, i);
|
|
CHECK(id3(d + 5) == 3120999 && id3(d + 8) == 3120101, "%s: DMRD[%d] src/dst %u->%u", name, i, id3(d + 5), id3(d + 8));
|
|
if ((fl & 0x30) == 0x20) {
|
|
uint8_t lc[9]; dmrd_lc(d, lc);
|
|
CHECK((lc[0] & 0x3F) == 3, "%s: DMRD[%d] LC FLCO 0x%02x, want 3", name, i, lc[0] & 0x3F);
|
|
if ((fl & 0x0F) == 1) hdr++; else if ((fl & 0x0F) == 2) term++;
|
|
}
|
|
}
|
|
CHECK(term == 1, "%s: %d terminators, want 1", name, term);
|
|
if (!late) CHECK(hdr == 4 && ncap == fed, "%s: %d DMRD (%d headers) from %d IPSC packets", name, ncap, hdr, fed);
|
|
else CHECK(hdr == 0, "%s: %d headers, want 0", name, hdr);
|
|
printf("%s: %d DMRD\n", name, ncap);
|
|
translator_free(tr);
|
|
}
|
|
|
|
static void hbp_to_ipsc(const Config *cfg, ev_loop *loop, int late)
|
|
{
|
|
const char *name = late ? "HBP->IPSC late entry" : "HBP->IPSC";
|
|
translator *tr = translator_new(cfg, loop);
|
|
translator_set_protocols(tr, (struct ipsc *)1, (struct hbp *)1);
|
|
ngv = 0;
|
|
FILE *f = fopen("tests/pvt_hbp_in.txt", "r");
|
|
if (!f) { perror("tests/pvt_hbp_in.txt"); exit(2); }
|
|
char line[512];
|
|
while (fgets(line, sizeof line, f)) {
|
|
uint8_t raw[128]; int n = unhex(line, raw);
|
|
if (n < 55) continue;
|
|
if (late && (raw[15] & 0x3F) == 0x21) continue; /* drop voice headers */
|
|
translator_hbp_voice_received(tr, raw, n);
|
|
}
|
|
fclose(f);
|
|
ev_timer_after(loop, 4.0, stop_cb, loop);
|
|
ev_run(loop);
|
|
CHECK(ngv > 0, "%s: no IPSC voice produced", name);
|
|
int term = 0;
|
|
for (int i = 0; i < ngv; i++) {
|
|
const uint8_t *g = gv[i];
|
|
CHECK(g[0] == 0x81, "%s: GV[%d] opcode 0x%02x, want PVT_VOICE 0x81", name, i, g[0]);
|
|
CHECK(id3(g + 6) == 3120101 && id3(g + 9) == 3120999, "%s: GV[%d] src/dst %u->%u", name, i, id3(g + 6), id3(g + 9));
|
|
if (g[30] == 1 || g[30] == 2) { /* VOICE_HEAD / VOICE_TERM: LC at 38 */
|
|
CHECK((g[38] & 0x3F) == 3, "%s: GV[%d] LC FLCO 0x%02x, want 3", name, i, g[38] & 0x3F);
|
|
if (g[30] == 2) term++;
|
|
}
|
|
}
|
|
CHECK(term == 1, "%s: %d terminators, want 1", name, term);
|
|
printf("%s: %d PVT_VOICE\n", name, ngv);
|
|
translator_free(tr);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
Config cfg; char err[4096];
|
|
if (config_load("tests/parity.toml", &cfg, err, sizeof err)) { fprintf(stderr, "config: %s\n", err); return 2; }
|
|
ev_loop *loop = ev_new();
|
|
ipsc_to_hbp(&cfg, loop, 0);
|
|
ipsc_to_hbp(&cfg, loop, 1);
|
|
hbp_to_ipsc(&cfg, loop, 0);
|
|
hbp_to_ipsc(&cfg, loop, 1);
|
|
ev_free(loop);
|
|
printf("Private calls: %d failures\n", fails);
|
|
return fails ? 1 : 0;
|
|
}
|