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;
This commit is contained in:
lwvmobile
2025-08-20 23:12:57 -04:00
parent 6f171620dc
commit 24c1dd97df
9 changed files with 184 additions and 76 deletions

View File

@@ -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 <float> Audio Digital Output Gain (Default: 0 = Auto; )
(Manual: 1 = 2%; 50 = 100%)
-n <float> Audio Analog Output Gain (Default: 0 = Auto; 0-100% )
-w <file> Output synthesized speech to a single static .wav file.
-6 <file> Output raw audio .wav file (48K/1). (WARNING! Large File Sizes 1 Hour ~= 360 MB)
-7 <dir> 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 <file> 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 <file> Specify Filename for Event Log Output.
-L <file> Specify Filename for LRRP Data Output.
@@ -173,7 +172,7 @@ Advanced Decoder options:
-1 <hex> Manually Enter RC4 or DES Key Value (DMR, P25, NXDN) (Hex Value)
-2 <hex> Manually Enter and TYT Enforce 16-bit BP Key Value (DMR) (Hex Value)
-2 <hex> Manually Enter and Enforce TYT 16-bit BP Key Value (DMR) (Hex Value)
-! <hex> 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 <dec> Manually Enter and Enforce Kenwood 15-bit Scrambler Key Value (DMR) (Dec Value)
-A <hex> Manually Enter and Enforce Anytone 16-bit BP Key Value (DMR) (Hex Value)
-k <file> 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!!).

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 <float> Audio Digital Output Gain (Default: 0 = Auto; )\n");
printf (" (Manual: 1 = 2%%; 50 = 100%%)\n");
printf (" -n <float> Audio Analog Output Gain (Default: 0 = Auto; 0-100%% )\n");
printf (" -w <file> Output synthesized speech to a single static .wav file.\n");
printf (" -6 <file> Output raw audio .wav file (48K/1). (WARNING! Large File Sizes 1 Hour ~= 360 MB)\n");
printf (" -7 <dir> 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 <file> 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 <file> Specify Filename for Event Log Output.\n");
printf (" -L <file> Specify Filename for LRRP Data Output.\n");
printf (" -Q <file> Specify Filename for OK-DMRlib Structured File Output. (placed in DSP folder)\n");
printf (" -Q <file> Specify Filename for M17 Float Stream Output. (placed in DSP folder)\n");
printf (" -c <file> 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 <num> 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 <num> QPSK modulation auto detection threshold (default=26)\n");
// printf (" -S <num> Symbol buffer size for QPSK decision point tracking\n");
// printf (" (default=36)\n");
// printf (" -M <num> 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 <hex> Manually Enter RC4 or DES Key Value (DMR, P25, NXDN) (Hex Value) \n");
printf (" \n");
printf (" -2 <hex> Manually Enter and TYT Enforce 16-bit BP Key Value (DMR) (Hex Value) \n");
printf (" -2 <hex> Manually Enter and Enforce TYT 16-bit BP Key Value (DMR) (Hex Value) \n");
printf (" \n");
printf (" -! <hex> 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 <dec> Manually Enter and Enforce Kenwood 15-bit Scrambler Key Value (DMR) (Dec Value) \n");
printf (" \n");
printf (" -A <hex> Manually Enter and Enforce Anytone 16-bit BP Key Value (DMR) (Hex Value) \n");
printf (" \n");
printf (" -k <file> 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;

View File

@@ -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