From 640a952df575f2c67a9fecc101b52379bbed5bf9 Mon Sep 17 00:00:00 2001 From: Cort Buffington Date: Tue, 13 Oct 2026 10:51:04 -0500 Subject: [PATCH] IPSC->HBP late entry: test burst E control bits, not the whole byte Late entry required data[32] == 0x16 exactly. On a weak signal the repeater also sets 0x01 (AMBE frame 1 bad) and 0x80 (embedded LC failed its check). In the OTA capture every 0x80 burst E carried a corrupt LC, while 0x17 carried a good one, so the exact match was right to reject 0x9x but also rejected 0x17 and delayed late entry a superframe. Identify burst E by the embedded-LC-present bit (0x10) and reject it only when the LC-error bit is set. The data[32] flags now share one set of named constants. Co-Authored-By: Claude Opus 5.5 --- src/ipsc_const.h | 17 +++++++------- src/translate.c | 11 ++++++--- tests/lateentry_ipsc_in.txt | 47 +++++++++++++++++++++++++++++++++++++ tests/test_parity.c | 44 ++++++++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 tests/lateentry_ipsc_in.txt diff --git a/src/ipsc_const.h b/src/ipsc_const.h index feccfa6..707541d 100644 --- a/src/ipsc_const.h +++ b/src/ipsc_const.h @@ -58,16 +58,15 @@ #define GV_BURST_TYPE_OFF 30 #define GV_PAYLOAD_OFF 31 #define GV_MIN_LEN 31 +/* Voice burst control byte (data[32]); bit names after node-dmr-lib DMRVoicePayload. */ +#define GV_CTRL_OFF 32 +#define GV_CTRL_LC_ERR 0x80 /* embedded LC failed its check (burst E LC is corrupt) */ +#define GV_CTRL_SYNC 0x40 /* burst carries voice sync (burst A) */ +#define GV_CTRL_EMB_LC 0x10 /* reassembled embedded LC present (burst E) */ +#define GV_CTRL_BAD1 0x01 /* AMBE frame 1 uncorrectable on RF receive */ #define GV_AMBE_OFF 33 /* 19-byte AMBE field: three 49-bit frames at bits 0, 50, 100 */ -/* Repeater-set "bad voice" flags, one per AMBE frame (uncorrectable on RF receive): - * frame 1 = data[32] bit 0; frames 2/3 = bits 49/99 of the AMBE field. */ -#define GV_BAD1_OFF 32 -#define GV_SYNC_OFF 32 /* data[32] & GV_SYNC_MSK: burst carries voice sync (burst A) */ -#define GV_SYNC_MSK 0x40 -#define GV_BAD1_MSK 0x01 -#define GV_BAD2_BIT 49 -#define GV_BAD3_BIT 99 -#define GV_BE_FLAG 0x16 /* data[32] value identifying burst E (carries reassembled LC repeat) */ +#define GV_BAD2_BIT 49 /* AMBE field bit: frame 2 uncorrectable */ +#define GV_BAD3_BIT 99 /* AMBE field bit: frame 3 uncorrectable */ #define GV_BE_LC_FLCO_OFF 56 /* FLCO of the reassembled LC repeat carried only on burst E */ #define FLCO_GROUP 0x00 /* Group Voice Channel User (real call identity); 0x04-0x08 = TA/GPS */ #define FLCO_PRIVATE 0x03 /* Unit-to-Unit Voice Channel User (private call) */ diff --git a/src/translate.c b/src/translate.c index ef5f4fd..857dd45 100644 --- a/src/translate.c +++ b/src/translate.c @@ -414,7 +414,12 @@ void translator_ipsc_voice_received(translator *tr, const uint8_t *data, int len * group, UU_V_Ch_Usr for private). A burst E carrying Talker Alias / GPS puts * alias bytes in the header src/dst, so we must not adopt them — wait for the * next voice-LC superframe. */ - if (len <= GV_BE_LC_FLCO_OFF || data[32] != GV_BE_FLAG) return; + int ctrl = data[GV_CTRL_OFF]; + if (len <= GV_BE_LC_FLCO_OFF || !(ctrl & GV_CTRL_EMB_LC)) return; + if (ctrl & GV_CTRL_LC_ERR) { + LOGD(LOGN, "IPSC late entry deferred ts=%d — burst E embedded LC failed its check", ts); + return; + } if (data[GV_BE_LC_FLCO_OFF] != (is_pvt ? FLCO_PRIVATE : FLCO_GROUP)) { LOGD(LOGN, "IPSC late entry deferred ts=%d — burst E embedded LC is not the voice LC " "(flco=0x%02x, Talker Alias/GPS); waiting for voice-LC superframe", @@ -439,14 +444,14 @@ void translator_ipsc_voice_received(translator *tr, const uint8_t *data, int len dmr_bit raw[152]; dmr_bytes_to_bits(data + GV_AMBE_OFF, 19, raw); dmr_bit a1_72[72], a2_72[72], a3_72[72]; dmr_bit *a72[3] = {a1_72, a2_72, a3_72}; - int bad[3] = {data[GV_BAD1_OFF] & GV_BAD1_MSK, raw[GV_BAD2_BIT], raw[GV_BAD3_BIT]}; + int bad[3] = {data[GV_CTRL_OFF] & GV_CTRL_BAD1, raw[GV_BAD2_BIT], raw[GV_BAD3_BIT]}; for (int i = 0; i < 3; i++) { if (bad[i]) dmr_bytes_to_bits(AMBE_SILENCE_MMDVM, 9, a72[i]); else dmr_ambe_49_to_72(raw + 50 * i, a72[i]); } if (bad[0] || bad[1] || bad[2]) LOGD(LOGN, "IPSC bad AMBE ts=%d flags=%d%d%d — sent as silence", ts, bad[0], bad[1], bad[2]); - if (data[GV_SYNC_OFF] & GV_SYNC_MSK) tr->out_frame_pos[ts] = 0; /* burst A: re-sync superframe */ + if (data[GV_CTRL_OFF] & GV_CTRL_SYNC) tr->out_frame_pos[ts] = 0; /* burst A: re-sync superframe */ int pos = tr->out_frame_pos[ts] % 6; dmr_bit embed[48]; build_embed(tr, ts, pos, embed); dmr_bit fb[264]; int at=0; diff --git a/tests/lateentry_ipsc_in.txt b/tests/lateentry_ipsc_in.txt new file mode 100644 index 0000000..c14d829 --- /dev/null +++ b/tests/lateentry_ipsc_in.txt @@ -0,0 +1,47 @@ +1 1 800004c313052f9be5000002020000430e0080dd32fe69dc34dd000000000100000a800a00600000000000022f9be50bbebf0011705d +1 1 800004c313052f9be5000002020000430e00805d32ff69dc36bd000000000100000a800a00600000000000022f9be50bbebf00116fff +1 1 800004c313052f9be5000002020000430e00805d330069dc389d000000000100000a800a00600000000000022f9be50bbebf001170a2 +1 1 800004c313052f9be5000002020000430e00805d330169dc3a7d000000000100000a800a00600000000000022f9be50bbebf001170f0 +1 10 800004c313052f9be5000002020000430e00805d330269dc3c5d000000000a1440b80b485d4b2cb60169b678b8ab80b411c83050 +1 10 800004c313052f9be5000002020000430e00805d330369dc3e3d000000000a1906b801b80da3d0ae00e0771887ececa1c4a8aca88212060612 +1 10 800004c313052f9be5000002020000430e00805d330469dc401d000000000a1906e81755d603e12609d5b758916881b96bf4fc700f00000616 +1 10 800004c313052f9be5000002020000430e00805d330569dc41fd000000000a1906e81a0566aebeb3866c736436a981f5c320cc380c03240a16 +1 10 800004c313052f9be5000002020000430e00805d330669dc43dd000000000a2296c81255ec4ee7ba05cf14ebe2eb81f91dbaf99838a0b333840000004093239bf714 +1 10 800004c313052f9be5000002020000430e00805d330769dc45bd000000000a19069818796e21d72605c52c727e4691d48d8299b80008000010 +1 10 800004c313052f9be5000002020000430e00805d330869dc479d000000000a1440e81b5d666fafba074967f011ed81b08a8289b0 +1 10 800004c313052f9be5000002020000430e00805d330969dc497d000000000a1906e81dd67227cbfa062d771bfde872f2908d48000606060612 +1 10 800004c313052f9be5000002020000430e00805d330a69dc4b5d000000000a1906866fff4a4a80206789e8bfd72a0cb2c1e49bd80f00002616 +1 10 800004c313052f9be5000002020000430e00805d330b69dc4d3d000000000a1906a0eded3f0dd168337713e3e0cb0bda62f0b6f80c033c0a16 +1 10 800004c313052f9be5000002020000430e00805d330c69dc4f1d000000000a2216b0b6b62b255fac1d97a893fe0c03cd21321d4806323b370000000000022f9be514 +1 10 800004c313052f9be5000002020000430e00805d330d69dc50fd000000000a1906c62d1b61c96731a3d8a3e034ac64c8e51a2eb80098000010 +1 10 800004c313052f9be5000002020000430e00805d330e69dc52dd000000000a1440c63438be07d035e2ea871ac825e64e54fbb1a8 +1 10 800004c313052f9be5000002020000430e00805d330f69dc54bd000000000a1906edab6d3d8f9ca5230db4ca65e83b53e9efe0f80606868612 +1 10 800004c313052f9be5000002020000430e00805d331069dc569d000000000a190680683ebf231520d11728e411273645ae35ec701f40000616 +1 10 800004c313052f9be5000002020000430e00805d331169dc587d000000000a1906738092b47de89ce515960551a709454bc27b884c033c0c16 +1 10 800004c313052f9be5000002020000430e00805d331269dc5a5d000000000a221680801e361136202126de02b9e84661dbb22088067633330000000000022f9be514 +1 10 800004c313052f9be5000002020000430e00805d331369dc5c3d000000000a190684443d42eb01a01585d4a1006808891288c0780000000010 +1 10 800004c313052f9be5000002020000430e00805d331469dc5e1d000000000a1440809447044d87202c81e5c0b4080b5969063d20 +1 10 800004c313052f9be5000002020000430e00805d331569dc5ffd000000000a1906909d07b600a22409868862034923d1e82308380002060612 +1 10 800004c313052f9be5000002020000430e00805d331669dc61dd000000000a1906923d92591236a593ce326bc04a6bfebbf6b3880f00000616 +1 10 800004c313052f9be5000002020000430e00805d331769dc63bd000000000a1906a4aef45f0bc1252947ece2c7a91981e6e7d5c82c033c0a16 +1 10 800004c313052f9be5000002020000430e00805d331869dc659d000000000a190690c70f4c8d1da42d974ba1d48a0af0f2f085800636333310 +1 10 800004c313052f9be5000002020000430e00805d331969dc677d000000000a1906a0aa9d2ec858a82f4bcfe16d0a0cb9d2e41cf00000000010 +1 10 800004c313052f9be5000002020000430e00805d331a69dc695d000000000a1440a0c84f220126a833534782b14a0c4269343d50 +1 10 800004c313052f9be5000002020000430e00805d331b69dc6b3d000000000a1906a0ac472147f52c1be595591aeb63ba41bef7700606069686 +1 10 800004c313052f9be5000002020000430e00805d331c69dc6d1d000000000a1906b86e7a8fcb96ae0f8a8812e7cae143400363b80f04000616 +1 10 800004c313052f9be5000002020000430e00805d331d69dc6efd000000000a1906b8e2e1fb505caddf4512ce700c4c514235bf608c033c0a16 +1 10 800004c313052f9be5000002020000430e00805d331e69dc70dd000000000a2216c1a074198fe5b4240dc418b0ae252978049fa8063631330000000000022f9be514 +1 10 800004c313052f9be5000002020000430e00805d331f69dc72bd000000000a19069c38571b4fa2398f950dc1e40e830f6a06f7c80080000010 +1 10 800004c313052f9be5000002020000430e00805d332069dc749d000000000a1440d85bcd46a330b5da90d652872d86eaa01a9dd8 +1 10 800004c313052f9be5000002020000430e00805d332169dc767d000000000a1906ed2fab109f098311ce22deb52b8623477512880606060612 +1 10 800004c313052f9be5000002020000430e00805d332269dc785d000000000a1906c842279f512e26cded88e7c08e82a9a186fd588f00000616 +1 10 800004c313052f9be5000002020000430e00805d332369dc7a3d000000000a1906e82a92238fcfb5ca8581d50ccd72cbab04c0c00c033d0a16 +1 10 800004c313052f9be5000002020000430e00805d332469dc7c1d000000000a2217992867afb8e1ba0b2ea261dfaa815b53e292c806363b330000000000022f9be514 +1 10 800004c313052f9be5000002020000430e00805d332569dc7dfd000000000a1906b81a328fa9c9b205ce5592970c81f54b20aea80000240010 +1 10 800004c313052f9be5000002020000430e00805d332669dc7fdd000000000a1440c81255a6ebb626059daac0512981d23b209200 +1 10 800004c313052f9be5000002020000430e00805d332769dc81bd000000000a1906dd1465e20189a60aae0e50a0ac81273424b2880606060612 +1 10 800004c313052f9be5000002020000430e00805d332869dc839d000000000a1906c82d246e234ea2458b766b3eee82df7b2417880f00000416 +1 10 800004c313052f9be5000002020000430e00805d332969dc857d000000000a1906e810b429aba532079df4e3ea2c81755bfef2780e023c0a16 +1 10 800004c313052f9be5000002020000430e00805d332a69dc875d000000000a2216c818ff444fdaae0b2a6602abc9cf365a7e0a68063633330000000000022f9be514 +1 10 800004c313052f9be5000002020000430e00805d332b69dc893d000000000a1907be1a351a659bb5cb1e0f28f0400000000800000000000010 +1 0 800004c313052f9be5000002020000430e00805d332c69dc8b1d000000000002000a800a00600000000000022f9be504b1b000107011 diff --git a/tests/test_parity.c b/tests/test_parity.c index 5360af2..72d2269 100644 --- a/tests/test_parity.c +++ b/tests/test_parity.c @@ -194,7 +194,47 @@ int main(void) } } printf("Superframe re-sync (IPSC->HBP): %d burst A checked, %d failures\n", sync_seen, sync_fails); + translator_free(tr); - translator_free(tr); ev_free(loop); - return (fails || in_fails || bad_fails || sync_fails) ? 1 : 0; + /* ---- late entry on weak signal (IPSC->HBP): weak-signal call 3120101 -> TG 2. + * Joining at packet 4, the first burst E (packet 8, ctrl 0x96) has a corrupt LC and + * must be skipped; anchor on packet 14 (0x16). Joining at packet 33, the first + * burst E (packet 38, ctrl 0x17: voice frame bad, LC good) is usable. ---- */ + static const int le_join[2] = {4, 33}, le_anchor[2] = {14, 38}; + int le_fails = 0; + for (int c = 0; c < 2; c++) { + tr = translator_new(&cfg, loop); + translator_set_protocols(tr, (struct ipsc *)1, (struct hbp *)1); + ncap = 0; + int idx = -1, anchor = -1; + fi = fopen("tests/lateentry_ipsc_in.txt", "r"); + while (fi && fgets(line, sizeof line, fi)) { + int ts, bt; char hex[400]; + if (sscanf(line, "%d %d %399s", &ts, &bt, hex) != 3) continue; + idx++; + if (idx < le_join[c] || bt == 1 || (bt & 0x7F) == 0x13) continue; + uint8_t raw[400]; int n = unhex(hex, raw); + int before = ncap; + translator_ipsc_voice_received(tr, raw, n, ts, bt); + if (anchor < 0 && ncap > before) anchor = idx; + } + if (fi) fclose(fi); + if (anchor != le_anchor[c]) { + fprintf(stderr, "FAIL late entry: joined at %d, anchored at packet %d, want %d\n", le_join[c], anchor, le_anchor[c]); + le_fails++; + } + for (int i = 0; i < ncap; i++) { + unsigned src = (unsigned)(cap[i][5]<<16 | cap[i][6]<<8 | cap[i][7]); + unsigned dst = (unsigned)(cap[i][8]<<16 | cap[i][9]<<8 | cap[i][10]); + if (src != 3120101 || dst != 2) { + fprintf(stderr, "FAIL late entry: joined at %d, DMRD[%d] %u->%u, want 3120101->2\n", le_join[c], i, src, dst); + le_fails++; break; + } + } + translator_free(tr); + } + printf("Late entry on weak signal (IPSC->HBP): %d failures\n", le_fails); + + ev_free(loop); + return (fails || in_fails || bad_fails || sync_fails || le_fails) ? 1 : 0; }