From 24c1dd97dfc1257d3901796fa92fb9131d770032 Mon Sep 17 00:00:00 2001 From: lwvmobile Date: Wed, 20 Aug 2025 23:12:57 -0400 Subject: [PATCH] DMR: Add 16-bit Anytone BP Support; #303 Reset static_ks_counter as various places in code; Remove the static_ks_counter reset from KWD SCR link control and tweak SO bits; Fix Forced KS application to work in both slots; Allow Static Wav File or Per Call Wav File in SDRTrunk MBE format playback; #298 Same as above for .amb, .dmb, and .imb format playback; Update Usage and Docs; --- .../cygwin_bat/complete_usage_options.txt | 19 +-- include/dsd.h | 5 + src/crypt-etc.c | 42 ++++++ src/dmr_bs.c | 12 +- src/dmr_flco.c | 7 +- src/dmr_ms.c | 6 + src/dsd_file.c | 14 +- src/dsd_main.c | 32 ++--- src/dsd_mbe.c | 123 +++++++++++------- 9 files changed, 184 insertions(+), 76 deletions(-) diff --git a/examples/cygwin_bat/complete_usage_options.txt b/examples/cygwin_bat/complete_usage_options.txt index 3c4d14f..e9a332c 100644 --- a/examples/cygwin_bat/complete_usage_options.txt +++ b/examples/cygwin_bat/complete_usage_options.txt @@ -1,4 +1,4 @@ -Build Version: AW 2025-175-ga5b9984 +Build Version: AW 2025-181-g6f17162 MBElib Version: 1.3.4 CODEC2 Support Enabled @@ -44,18 +44,17 @@ Input/Output options: -g Audio Digital Output Gain (Default: 0 = Auto; ) (Manual: 1 = 2%; 50 = 100%) -n Audio Analog Output Gain (Default: 0 = Auto; 0-100% ) - -w Output synthesized speech to a single static .wav file. -6 Output raw audio .wav file (48K/1). (WARNING! Large File Sizes 1 Hour ~= 360 MB) -7 Create/Use Custom directory for Per Call decoded .wav file saving. (Use ./folder for Nested Directory!) (Use /path/to/folder for hard coded directory!) (Use Before the -P option!) - -8 Enable Experimental Source Audio Monitor (Pulse Audio Output Only!) - (Its recommended to use Squelch in SDR++ or GQRX, etc, if monitoring mixed analog/digital) - -P Enable Per Call WAV file saving in AUTO and NXDN decoding classes - (Per Call can only be used in Ncurses Terminal!) - (Running in console will use static wav files) - -a Enable Call Alert Beep (NCurses Terminal Only) + -8 Enable Source Audio Monitor + (Set Squelch in RTL, SDR++ or GQRX, etc, if monitoring mixed analog/digital) + -w Output synthesized speech to a single static .wav file. (Do not use with -P Per Call Switch) + -P Enable Per Call WAV file saving. (Do not use with -w filename.wav single wav file switch) + (Per Call works with everything now and doesn't require ncurses terminal!) + -a Enable Call Alert Beep (Warning! Might be annoying.) -J Specify Filename for Event Log Output. -L Specify Filename for LRRP Data Output. @@ -173,7 +172,7 @@ Advanced Decoder options: -1 Manually Enter RC4 or DES Key Value (DMR, P25, NXDN) (Hex Value) - -2 Manually Enter and TYT Enforce 16-bit BP Key Value (DMR) (Hex Value) + -2 Manually Enter and Enforce TYT 16-bit BP Key Value (DMR) (Hex Value) -! Manually Enter and Enforce TYT Advanced Privacy (PC4) AP Hex Key (see example below) Encapulate in Single Quotation Marks; Space every 16 chars. @@ -185,6 +184,8 @@ Advanced Decoder options: -9 Manually Enter and Enforce Kenwood 15-bit Scrambler Key Value (DMR) (Dec Value) + -A Manually Enter and Enforce Anytone 16-bit BP Key Value (DMR) (Hex Value) + -k Import Key List from csv file (Decimal Format) -- Lower Case 'k'. Only supports NXDN, DMR Basic Privacy (decimal value). (dPMR and Hytera 32/64 char not supported, DMR uses TG value as key id -- EXPERIMENTAL!!). diff --git a/include/dsd.h b/include/dsd.h index 0ae97e5..fae8e3b 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -994,6 +994,10 @@ typedef struct //kenwood scrambler on DMR with forced application int ken_sc; + + //anytone bp + int any_bp; + uint8_t static_ks_bits[2][882]; int static_ks_counter[2]; @@ -1627,6 +1631,7 @@ void tyt_ap_pc4_keystream_creation(dsd_state * state, char * input); //Misc Other Encryption Modes void ken_dmr_scrambler_keystream_creation(dsd_state * state, char * input); +void anytone_bp_keystream_creation(dsd_state * state, char * input); //Hytera Enhanced void hytera_enhanced_rc4_setup(dsd_opts * opts, dsd_state * state, unsigned long long int key_value, unsigned long long int mi_value); diff --git a/src/crypt-etc.c b/src/crypt-etc.c index a0a0782..5b635c3 100644 --- a/src/crypt-etc.c +++ b/src/crypt-etc.c @@ -28,4 +28,46 @@ void ken_dmr_scrambler_keystream_creation(dsd_state * state, char * input) state->ken_sc = 1; +} + +void anytone_bp_keystream_creation(dsd_state * state, char * input) +{ + uint16_t key = 0; + uint16_t kperm = 0; + + sscanf (input, "%hX", &key); + key &= 0xFFFF; //truncate to 16-bits + + //calculate key permutation using simple operations + uint8_t nib1, nib2, nib3, nib4; + + //nib 1 and 3 are simple inversions + nib1 = ~(key >> 12) & 0xF; + nib3 = ~(key >> 4) & 0xF; + + //nib 2 and 4 are +8 and mod 16 (& 0xF) + nib2 = (((key >> 8) & 0xF) + 8) % 16; + nib4 = (((key >> 0) & 0xF) + 8) % 16; + + //debug + // fprintf (stderr, "{%01X, %01X, %01X, %01X}", nib1, nib2, nib3, nib4); + + kperm = nib1; + kperm <<= 4; + kperm |= nib2; + kperm <<= 4; + kperm |= nib3; + kperm <<= 4; + kperm |= nib4; + + //load bits into static keystream + for (int i = 0; i < 16; i++) + { + state->static_ks_bits[0][i] = (kperm >> (15-i)) & 1; + state->static_ks_bits[1][i] = (kperm >> (15-i)) & 1; + } + + fprintf (stderr,"DMR Anytone Basic 16-bit Key 0x%04X with Forced Application\n", key); + state->any_bp = 1; + } \ No newline at end of file diff --git a/src/dmr_bs.c b/src/dmr_bs.c index 818f1c5..1bf90f5 100644 --- a/src/dmr_bs.c +++ b/src/dmr_bs.c @@ -586,6 +586,9 @@ void dmrBS (dsd_opts * opts, dsd_state * state) if (internalslot == 0 && vc1 == 6) dmr_alg_refresh (opts, state); if (internalslot == 1 && vc2 == 6) dmr_alg_refresh (opts, state); + if (internalslot == 0 && vc1 == 6) state->static_ks_counter[0] = 0; + if (internalslot == 1 && vc2 == 6) state->static_ks_counter[1] = 0; + if (opts->dmr_le != 2) //if not Hytera Enhanced dmr_late_entry_mi_fragment (opts, state, vc%7, m1, m2, m3); @@ -714,12 +717,16 @@ void dmrBS (dsd_opts * opts, dsd_state * state) } // - if (timestr != NULL) + if (timestr != NULL) { free (timestr); timestr = NULL; } + //reset static ks counter + state->static_ks_counter[0] = 0; + state->static_ks_counter[1] = 0; + } //Process buffered half frame and 2nd half and then jump to full BS decoding @@ -790,6 +797,9 @@ void dmrBSBootstrap (dsd_opts * opts, dsd_state * state) internalslot = state->currentslot = tact_bits[1]; + //reset static ks counter + state->static_ks_counter[internalslot] = 0; + //Setup for first AMBE Frame //Interleave Schedule diff --git a/src/dmr_flco.c b/src/dmr_flco.c index 313cd1b..8c9fa0b 100644 --- a/src/dmr_flco.c +++ b/src/dmr_flco.c @@ -63,14 +63,13 @@ void dmr_flco (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[], uint32_t C pf = 0; //turn off PF flag fid = 0; //unclear if this signals FID (or is cipher type for scrambler) - //reset bit counter on static key bit counter (should do this after VC6 instead of relying on this, but meh, obscure cipher type) - state->static_ks_counter[state->currentslot] = 0; + //NOTE: bit counter reset is handled after vc6 voice now is_kenwood_sc = 1; - //if forcing a keystream, remove the service options for this to unmute (workaround for now, need to fix in audio) + //if forcing a keystream, flip the encryption bit for this to unmute if (state->ken_sc == 1) - so = 0; + so ^= 0x40; } //read ahead a little to get this for the xpt flag diff --git a/src/dmr_ms.c b/src/dmr_ms.c index d1c8caa..28cd5f2 100644 --- a/src/dmr_ms.c +++ b/src/dmr_ms.c @@ -361,6 +361,9 @@ void dmrMS (dsd_opts * opts, dsd_state * state) timestr = NULL; } + //reset static ks counter + state->static_ks_counter[0] = 0; + } //collect buffered 1st half and get 2nd half voice payload and then jump to full MS Voice decoding. @@ -369,6 +372,9 @@ void dmrMSBootstrap (dsd_opts * opts, dsd_state * state) char * timestr = getTimeC(); + //reset static ks counter + state->static_ks_counter[0] = 0; + int i, dibit; int *dibit_p; diff --git a/src/dsd_file.c b/src/dsd_file.c index d315f54..da04ce7 100644 --- a/src/dsd_file.c +++ b/src/dsd_file.c @@ -1008,9 +1008,14 @@ uint16_t ambe2_str_to_decode(dsd_opts * opts, dsd_state * state, char * ambe_str if (opts->floating_point == 0) processAudio(opts, state); - if (opts->wav_out_f != NULL) + //per call wav files + if (opts->wav_out_f != NULL && opts->dmr_stereo_wav == 1) writeSynthesizedVoice (opts, state); + //NOTE: Static Wav file is handled inside of playSynthesizedVoiceMS, + //but if using -o null to mute audio, then the static file will not contain audio + //TODO: Fix above to allow audio in wav even if not put out to speakers? + if (opts->audio_out == 1 && opts->floating_point == 0) playSynthesizedVoiceMS (opts, state); @@ -1114,9 +1119,14 @@ uint16_t imbe_str_to_decode(dsd_opts * opts, dsd_state * state, char * imbe_str, if (opts->floating_point == 0) processAudio(opts, state); - if (opts->wav_out_f != NULL) + //per call wav files + if (opts->wav_out_f != NULL && opts->dmr_stereo_wav == 1) writeSynthesizedVoice (opts, state); + //NOTE: Static Wav file is handled inside of playSynthesizedVoiceMS, + //but if using -o null to mute audio, then the static file will not contain audio + //TODO: Fix above to allow audio in wav even if not put out to speakers? + if (opts->audio_out == 1 && opts->floating_point == 0) playSynthesizedVoiceMS (opts, state); diff --git a/src/dsd_main.c b/src/dsd_main.c index 798ba5e..9f9b143 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -1066,6 +1066,7 @@ state->tyt_ep = 0; state->ken_sc = 0; + state->any_bp = 0; //ks array storage and counters memset (state->ks_octetL, 0, sizeof(state->ks_octetL)); @@ -1423,25 +1424,24 @@ printf (" -g Audio Digital Output Gain (Default: 0 = Auto; )\n"); printf (" (Manual: 1 = 2%%; 50 = 100%%)\n"); printf (" -n Audio Analog Output Gain (Default: 0 = Auto; 0-100%% )\n"); - printf (" -w Output synthesized speech to a single static .wav file.\n"); printf (" -6 Output raw audio .wav file (48K/1). (WARNING! Large File Sizes 1 Hour ~= 360 MB)\n"); printf (" -7 Create/Use Custom directory for Per Call decoded .wav file saving.\n"); printf (" (Use ./folder for Nested Directory!)\n"); printf (" (Use /path/to/folder for hard coded directory!)\n"); printf (" (Use Before the -P option!)\n"); - printf (" -8 Enable Experimental Source Audio Monitor (Pulse Audio Output Only!)\n"); - printf (" (Its recommended to use Squelch in SDR++ or GQRX, etc, if monitoring mixed analog/digital)\n"); - printf (" -P Enable Per Call WAV file saving in AUTO and NXDN decoding classes\n"); - printf (" (Per Call can only be used in Ncurses Terminal!)\n"); - printf (" (Running in console will use static wav files)\n"); - printf (" -a Enable Call Alert Beep (NCurses Terminal Only)\n"); + printf (" -8 Enable Source Audio Monitor\n"); + printf (" (Set Squelch in RTL, SDR++ or GQRX, etc, if monitoring mixed analog/digital)\n"); + printf (" -w Output synthesized speech to a single static .wav file. (Do not use with -P Per Call Switch)\n"); + printf (" -P Enable Per Call WAV file saving. (Do not use with -w filename.wav single wav file switch)\n"); + printf (" (Per Call works with everything now and doesn't require ncurses terminal!)\n"); + printf (" -a Enable Call Alert Beep\n"); printf (" (Warning! Might be annoying.)\n"); printf (" -J Specify Filename for Event Log Output.\n"); printf (" -L Specify Filename for LRRP Data Output.\n"); printf (" -Q Specify Filename for OK-DMRlib Structured File Output. (placed in DSP folder)\n"); printf (" -Q Specify Filename for M17 Float Stream Output. (placed in DSP folder)\n"); printf (" -c Output symbol capture to .bin file\n"); - printf (" -q Reverse Mute - Mute Unencrypted Voice and Unmute Encrypted Voice\n"); + printf (" -q Reverse Mute - Mute Unencrypted Voice and Unmute Encrypted Voice\n"); //does this still work correctly? printf (" -V Enable TDMA Voice Synthesis on Slot 1 (1), Slot 2 (2), or Both (3); Default is 3; \n"); #ifdef __CYGWIN__ printf (" If using /dev/dsp input and output at 48k1, launch two instances of DSD-FME w -V 1 and -V 2 if needed\n"); @@ -1527,11 +1527,6 @@ printf (" (Value defaults to max n bit value for site model size)\n"); printf (" (Setting 0 will show full Site ID, no area/subarea)\n"); printf ("\n"); - // printf (" -A QPSK modulation auto detection threshold (default=26)\n"); - // printf (" -S Symbol buffer size for QPSK decision point tracking\n"); - // printf (" (default=36)\n"); - // printf (" -M Min/Max buffer size for QPSK decision point tracking\n"); - // printf (" (default=15)\n"); printf (" -ma Auto-select modulation optimizations\n"); printf (" -mc Use only C4FM modulation optimizations (default)\n"); printf (" -mg Use only GFSK modulation optimizations\n"); @@ -1562,7 +1557,7 @@ printf (" \n"); printf (" -1 Manually Enter RC4 or DES Key Value (DMR, P25, NXDN) (Hex Value) \n"); printf (" \n"); - printf (" -2 Manually Enter and TYT Enforce 16-bit BP Key Value (DMR) (Hex Value) \n"); + printf (" -2 Manually Enter and Enforce TYT 16-bit BP Key Value (DMR) (Hex Value) \n"); printf (" \n"); printf (" -! Manually Enter and Enforce TYT Advanced Privacy (PC4) AP Hex Key (see example below)\n"); printf (" Encapulate in Single Quotation Marks; Space every 16 chars.\n"); @@ -1574,6 +1569,8 @@ printf (" \n"); printf (" -9 Manually Enter and Enforce Kenwood 15-bit Scrambler Key Value (DMR) (Dec Value) \n"); printf (" \n"); + printf (" -A Manually Enter and Enforce Anytone 16-bit BP Key Value (DMR) (Hex Value) \n"); + printf (" \n"); printf (" -k Import Key List from csv file (Decimal Format) -- Lower Case 'k'.\n"); printf (" Only supports NXDN, DMR Basic Privacy (decimal value). \n"); printf (" (dPMR and Hytera 32/64 char not supported, DMR uses TG value as key id -- EXPERIMENTAL!!). \n"); @@ -1986,10 +1983,15 @@ tyt_ep_aes_keystream_creation(&state, optarg); break; - //get user Kenwood DMR Scrambler Key + //get user Kenwood DMR Scrambler Key and Force Its application case '9': ken_dmr_scrambler_keystream_creation(&state, optarg); break; + + //get user Anytone BP Key and Force Its application + case 'A': + anytone_bp_keystream_creation(&state, optarg); + break; case '3': opts.dmr_le = 0; diff --git a/src/dsd_mbe.c b/src/dsd_mbe.c index 58e9c2c..c59c8c1 100644 --- a/src/dsd_mbe.c +++ b/src/dsd_mbe.c @@ -87,7 +87,11 @@ void playMbeFiles (dsd_opts * opts, dsd_state * state, int argc, char **argv) { processAudio(opts, state); } - if (opts->wav_out_f != NULL) + + //static wav file only, handled by playSynthesizedVoiceMS + //NOTE: if using -o null, playSynthesizedVoiceMS will not write to static wav file + //Per call will work, but will end up with a single file with no meta info + if (opts->wav_out_f != NULL && opts->dmr_stereo_wav == 1) { writeSynthesizedVoice (opts, state); } @@ -131,7 +135,11 @@ void playMbeFiles (dsd_opts * opts, dsd_state * state, int argc, char **argv) { processAudio(opts, state); } - if (opts->wav_out_f != NULL) + + //static wav file only, handled by playSynthesizedVoiceMS + //NOTE: if using -o null, playSynthesizedVoiceMS will not write to static wav file + //Per call will work, but will end up with a single file with no meta info + if (opts->wav_out_f != NULL && opts->dmr_stereo_wav == 1) { writeSynthesizedVoice (opts, state); } @@ -811,33 +819,6 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a unpack_ambe(plain, ambe_d); } - - //DMR TYT AP, Either Slot (static single key'd enforced KS) - if (state->tyt_ap == 1) - { - - short frame1_cipher[49]; - for (int i = 0; i < 49; i++) frame1_cipher[i] = ambe_d[i]; - decrypt_frame_49(frame1_cipher); - - memset (ambe_d, 0, 49*sizeof(char)); - for (int i = 0; i < 49; i++) ambe_d[i] = ctx.bits[i]; - - } - - //DMR TYT EP, Either Slot (static single key'd enforced KS) - if (state->tyt_ep == 1) - { - for (int i = 0; i < 49; i++) - ambe_d[i] ^= (uint8_t)(ctx.bits[i] & 1); - } - - //DMR Kenwood Scrambler, Either Slot (static single key'd enforced KS) //should probably break this up, but this is a test for now - if (state->ken_sc == 1) - { - for (int i = 0; i < 49; i++) - ambe_d[i] ^= (uint8_t)(state->static_ks_bits[state->currentslot][(state->static_ks_counter[state->currentslot]++)%882] & 1); //Yikes! - } //P25p2 RC4 Handling, VCH 0 if (state->currentslot == 0 && state->payload_algid == 0xAA && state->R != 0 && ((state->synctype == 35) || (state->synctype == 36))) @@ -877,6 +858,40 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } + //DMR TYT AP, Either Slot (static single key'd enforced KS) + if (state->tyt_ap == 1) + { + + short frame1_cipher[49]; + for (int i = 0; i < 49; i++) frame1_cipher[i] = ambe_d[i]; + decrypt_frame_49(frame1_cipher); + + memset (ambe_d, 0, 49*sizeof(char)); + for (int i = 0; i < 49; i++) ambe_d[i] = ctx.bits[i]; + + } + + //DMR TYT EP, Either Slot (static single key'd enforced KS) + if (state->tyt_ep == 1) + { + for (int i = 0; i < 49; i++) + ambe_d[i] ^= (uint8_t)(ctx.bits[i] & 1); + } + + //DMR Kenwood Scrambler, Either Slot (static single key'd enforced KS) //should probably break this up, but this is a test for now + if (state->ken_sc == 1) + { + for (int i = 0; i < 49; i++) + ambe_d[i] ^= (uint8_t)(state->static_ks_bits[state->currentslot][(state->static_ks_counter[state->currentslot]++)%882] & 1); //Yikes! + } + + //DMR Anytone BP, Either Slot (static single key'd enforced KS) + if (state->any_bp == 1) + { + for (int i = 0; i < 49; i++) + ambe_d[i] ^= (uint8_t)(state->static_ks_bits[state->currentslot][(state->static_ks_counter[state->currentslot]++)%16] & 1); //Yikes! + } + mbe_processAmbe2450Dataf (state->audio_out_temp_buf, &state->errs, &state->errs2, state->err_str, ambe_d, state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); @@ -1208,6 +1223,40 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } + //DMR TYT AP, Either Slot (static single key'd enforced KS) + if (state->tyt_ap == 1) + { + + short frame1_cipher[49]; + for (int i = 0; i < 49; i++) frame1_cipher[i] = ambe_d[i]; + decrypt_frame_49(frame1_cipher); + + memset (ambe_d, 0, 49*sizeof(char)); + for (int i = 0; i < 49; i++) ambe_d[i] = ctx.bits[i]; + + } + + //DMR TYT EP, Either Slot (static single key'd enforced KS) + if (state->tyt_ep == 1) + { + for (int i = 0; i < 49; i++) + ambe_d[i] ^= (uint8_t)(ctx.bits[i] & 1); + } + + //DMR Kenwood Scrambler, Either Slot (static single key'd enforced KS) //should probably break this up, but this is a test for now + if (state->ken_sc == 1) + { + for (int i = 0; i < 49; i++) + ambe_d[i] ^= (uint8_t)(state->static_ks_bits[state->currentslot][(state->static_ks_counter[state->currentslot]++)%882] & 1); //Yikes! + } + + //DMR Anytone BP, Either Slot (static single key'd enforced KS) + if (state->any_bp == 1) + { + for (int i = 0; i < 49; i++) + ambe_d[i] ^= (uint8_t)(state->static_ks_bits[state->currentslot][(state->static_ks_counter[state->currentslot]++)%16] & 1); //Yikes! + } + mbe_processAmbe2450Dataf (state->audio_out_temp_bufR, &state->errsR, &state->errs2R, state->err_strR, ambe_d, state->cur_mp2, state->prev_mp2, state->prev_mp_enhanced2, opts->uvquality); @@ -1225,22 +1274,6 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } } - //X2-TDMA? Not sure what still makes it this far to run under Framef - // if (opts->dmr_stereo == 0) - // { - // mbe_processAmbe3600x2450Framef (state->audio_out_temp_buf, &state->errs, &state->errs2, state->err_str, ambe_fr, ambe_d, state->cur_mp, state->prev_mp, state->prev_mp_enhanced, opts->uvquality); - // if (opts->payload == 1) - // { - // PrintAMBEData (opts, state, ambe_d); - // } - // //only save MBE files if not enc or unmuted, THIS does not seem to work for some reason - // if (opts->mbe_out_f != NULL && (opts->unmute_encrypted_p25 == 1 || state->dmr_encL == 0) ) - // { - // saveAmbe2450Data (opts, state, ambe_d); - // } - // } - - } //quick enc check to determine whether or not to play enc traffic