NXDN Type-D Decoding/Trunking; NXDN Tweaks; #115

This commit is contained in:
lwvmobile 2023-04-23 19:52:32 -04:00
parent 3101bbde28
commit c507ed229c
5 changed files with 1041 additions and 255 deletions

View File

@ -672,6 +672,7 @@ typedef struct
//multi-key array //multi-key array
unsigned long long int rkey_array[0xFFFF]; unsigned long long int rkey_array[0xFFFF];
int keyloader; //let us know the keyloader is active
//dmr late entry mi //dmr late entry mi
uint64_t late_entry_mi_fragment[2][7][3]; uint64_t late_entry_mi_fragment[2][7][3];
@ -857,10 +858,15 @@ void ncursesClose ();
//new NXDN Functions start here! //new NXDN Functions start here!
void nxdn_frame (dsd_opts * opts, dsd_state * state); void nxdn_frame (dsd_opts * opts, dsd_state * state);
void nxdn_descramble (uint8_t dibits[], int len); void nxdn_descramble (uint8_t dibits[], int len);
//nxdn deinterleaving/depuncturing functions
void nxdn_deperm_facch (dsd_opts * opts, dsd_state * state, uint8_t bits[144]); void nxdn_deperm_facch (dsd_opts * opts, dsd_state * state, uint8_t bits[144]);
void nxdn_deperm_sacch (dsd_opts * opts, dsd_state * state, uint8_t bits[60]); void nxdn_deperm_sacch (dsd_opts * opts, dsd_state * state, uint8_t bits[60]);
void nxdn_deperm_cac (dsd_opts * opts, dsd_state * state, uint8_t bits[300]); void nxdn_deperm_cac (dsd_opts * opts, dsd_state * state, uint8_t bits[300]);
void nxdn_deperm_facch2_udch (dsd_opts * opts, dsd_state * state, uint8_t bits[348]); void nxdn_deperm_facch2_udch (dsd_opts * opts, dsd_state * state, uint8_t bits[348], uint8_t type);
//type-d 'idas' deinterleaving/depuncturing functions
void nxdn_deperm_scch(dsd_opts * opts, dsd_state * state, uint8_t bits[60], uint8_t direction);
void nxdn_deperm_facch3_udch2(dsd_opts * opts, dsd_state * state, uint8_t bits[288], uint8_t type);
//end
void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType); void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType);
void nxdn_voice (dsd_opts * opts, dsd_state * state, int voice, uint8_t dbuf[182]); void nxdn_voice (dsd_opts * opts, dsd_state * state, int voice, uint8_t dbuf[182]);
@ -870,6 +876,7 @@ static uint8_t crc6(const uint8_t buf[], int len);
static uint16_t crc12f(const uint8_t buf[], int len); static uint16_t crc12f(const uint8_t buf[], int len);
static uint16_t crc15(const uint8_t buf[], int len); static uint16_t crc15(const uint8_t buf[], int len);
static uint16_t crc16cac(const uint8_t buf[], int len); static uint16_t crc16cac(const uint8_t buf[], int len);
static uint8_t crc7_scch(uint8_t bits[], int len); //converted from op25 crc6
/* NXDN Convolution functions */ /* NXDN Convolution functions */
void CNXDNConvolution_start(void); void CNXDNConvolution_start(void);
@ -893,6 +900,8 @@ void NXDN_decode_cch_info(dsd_opts * opts, dsd_state * state, uint8_t * Message)
void NXDN_decode_srv_info(dsd_opts * opts, dsd_state * state, uint8_t * Message); void NXDN_decode_srv_info(dsd_opts * opts, dsd_state * state, uint8_t * Message);
void NXDN_decode_site_info(dsd_opts * opts, dsd_state * state, uint8_t * Message); void NXDN_decode_site_info(dsd_opts * opts, dsd_state * state, uint8_t * Message);
void NXDN_decode_adj_site(dsd_opts * opts, dsd_state * state, uint8_t * Message); void NXDN_decode_adj_site(dsd_opts * opts, dsd_state * state, uint8_t * Message);
//Type-D SCCH Message Decoder
void NXDN_decode_scch(dsd_opts * opts, dsd_state * state, uint8_t * Message, uint8_t direction);
void dPMRVoiceFrameProcess(dsd_opts * opts, dsd_state * state); void dPMRVoiceFrameProcess(dsd_opts * opts, dsd_state * state);

View File

@ -894,7 +894,8 @@ initState (dsd_state * state)
state->nxdn_bw = 0; state->nxdn_bw = 0;
//multi-key array //multi-key array
memset (state->rkey_array, 0, sizeof(state->rkey_array)); memset (state->rkey_array, 0, sizeof(state->rkey_array));
state->keyloader = 0; //keyloader off
//Remus DMR End Call Alert Beep //Remus DMR End Call Alert Beep
state->dmr_end_alert[0] = 0; state->dmr_end_alert[0] = 0;
@ -1301,10 +1302,11 @@ main (int argc, char **argv)
opts.p25_trunk = 0; //turn off trunking mode if user enabled it opts.p25_trunk = 0; //turn off trunking mode if user enabled it
break; break;
case 'k': //NXDN multi-key loader case 'k': //multi-key loader
strncpy(opts.key_in_file, optarg, 1023); strncpy(opts.key_in_file, optarg, 1023);
opts.key_in_file[1023] = '\0'; opts.key_in_file[1023] = '\0';
csvKeyImport(&opts, &state); csvKeyImport(&opts, &state);
state.keyloader = 1;
break; break;
case 'Q': //'DSP' Structured Output file for OKDMRlib case 'Q': //'DSP' Structured Output file for OKDMRlib
@ -1443,17 +1445,9 @@ main (int argc, char **argv)
case 'R': case 'R':
sscanf (optarg, "%lld", &state.R); sscanf (optarg, "%lld", &state.R);
if (state.R > 0x7FFF) if (state.R > 0x7FFF) state.R = 0x7FFF;
{ //disable keyloader in case user tries to use this and it at the same time
state.R = 0x7FFF; state.keyloader = 0;
}
// opts.dmr_mute_encL = 0;
// opts.dmr_mute_encR = 0;
if (state.R == 0)
{
// opts.dmr_mute_encL = 1;
// opts.dmr_mute_encR = 1;
}
break; break;
case 'H': case 'H':

View File

@ -121,6 +121,14 @@ void nxdn_deperm_facch(dsd_opts * opts, dsd_state * state, uint8_t bits[144])
check = check | trellis_buf[84+i]; //80 check = check | trellis_buf[84+i]; //80
} }
if (crc != check)
{
fprintf (stderr, " FACCH");
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
if (crc == check) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf); if (crc == check) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf);
else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, trellis_buf); else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, trellis_buf);
@ -132,12 +140,12 @@ void nxdn_deperm_facch(dsd_opts * opts, dsd_state * state, uint8_t bits[144])
{ {
fprintf (stderr, "[%02X]", m_data[i]); fprintf (stderr, "[%02X]", m_data[i]);
} }
fprintf (stderr, " - %03X %03X", check, crc); // fprintf (stderr, " - %03X %03X", check, crc);
} }
} }
//new sacch //sacch
void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60]) void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60])
{ {
//see about initializing these variables //see about initializing these variables
@ -237,9 +245,23 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60])
nsf_sacch[i] = trellis_buf[i+8]; nsf_sacch[i] = trellis_buf[i+8];
} }
if (crc == check)
{
ran = (trellis_buf[2] << 5) | (trellis_buf[3] << 4) | (trellis_buf[4] << 3) | (trellis_buf[5] << 2) | (trellis_buf[6] << 1) | trellis_buf[7];
state->nxdn_last_ran = ran;
}
if (crc == check) NXDN_Elements_Content_decode(opts, state, 1, nsf_sacch); if (crc == check) NXDN_Elements_Content_decode(opts, state, 1, nsf_sacch);
else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, nsf_sacch); else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, nsf_sacch);
if (crc != check)
{
fprintf (stderr, " SACCH NSF");
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
if (opts->payload == 1) if (opts->payload == 1)
{ {
fprintf (stderr, "\n SACCH NSF "); fprintf (stderr, "\n SACCH NSF ");
@ -247,7 +269,7 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60])
{ {
fprintf (stderr, "[%02X]", m_data[i]); fprintf (stderr, "[%02X]", m_data[i]);
} }
if (crc != check) fprintf (stderr, " CRC ERR - %02X %02X", check, crc); // if (crc != check) fprintf (stderr, " CRC ERR - %02X %02X", check, crc);
} }
} }
@ -258,7 +280,7 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60])
//sf and ran together are denoted as SR in the manual (more confusing acronyms) //sf and ran together are denoted as SR in the manual (more confusing acronyms)
//sf (structure field) and RAN will always exist in first 8 bits of each SACCH, then the next 18 bits are the fragment of the superframe //sf (structure field) and RAN will always exist in first 8 bits of each SACCH, then the next 18 bits are the fragment of the superframe
sf = (trellis_buf[0] << 1) | trellis_buf[1]; sf = (trellis_buf[0] << 1) | trellis_buf[1];
ran = (trellis_buf[2] << 5) | (trellis_buf[3] << 4) | (trellis_buf[4] << 3) | (trellis_buf[5] << 2) | (trellis_buf[6] << 1) | trellis_buf[7];
if (sf == 3) part_of_frame = 0; if (sf == 3) part_of_frame = 0;
else if (sf == 2) part_of_frame = 1; else if (sf == 2) part_of_frame = 1;
else if (sf == 1) part_of_frame = 2; else if (sf == 1) part_of_frame = 2;
@ -269,12 +291,21 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60])
if (state->nxdn_last_ran != -1) fprintf (stderr, " RAN %02d ", state->nxdn_last_ran); if (state->nxdn_last_ran != -1) fprintf (stderr, " RAN %02d ", state->nxdn_last_ran);
else fprintf (stderr, " "); else fprintf (stderr, " ");
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
if (crc != check)
{
fprintf (stderr, " SACCH SF%d", sf);
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
//reset scrambler seed to key value on new superframe //reset scrambler seed to key value on new superframe
if (part_of_frame == 0 && state->nxdn_cipher_type == 0x1) state->payload_miN = 0; if (part_of_frame == 0 && state->nxdn_cipher_type == 0x1) state->payload_miN = 0;
if (crc == check) if (crc == check)
{ {
ran = (trellis_buf[2] << 5) | (trellis_buf[3] << 4) | (trellis_buf[4] << 3) | (trellis_buf[5] << 2) | (trellis_buf[6] << 1) | trellis_buf[7];
state->nxdn_ran = state->nxdn_last_ran = ran; state->nxdn_ran = state->nxdn_last_ran = ran;
state->nxdn_sf = sf; state->nxdn_sf = sf;
state->nxdn_part_of_frame = part_of_frame; state->nxdn_part_of_frame = part_of_frame;
@ -305,15 +336,14 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60])
{ {
fprintf (stderr, "[%02X]", m_data[i]); fprintf (stderr, "[%02X]", m_data[i]);
} }
if (crc != check) fprintf (stderr, " CRC ERR - %02X %02X", crc, check); // if (crc != check) fprintf (stderr, " CRC ERR - %02X %02X", crc, check);
} }
} }
} }
void nxdn_deperm_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t bits[348]) void nxdn_deperm_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t bits[348], uint8_t type)
{ {
uint8_t deperm[500]; //348 uint8_t deperm[500]; //348
uint8_t depunc[500]; //406 uint8_t depunc[500]; //406
uint8_t trellis_buf[400]; //199 uint8_t trellis_buf[400]; //199
@ -394,6 +424,16 @@ void nxdn_deperm_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t bits[34
check = check | trellis_buf[i+184]; check = check | trellis_buf[i+184];
} }
if (crc != check)
{
fprintf (stderr, "%s", KYEL);
if (type == 0) fprintf (stderr, " UDCH");
if (type == 1) fprintf (stderr, " FACCH2");
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
uint8_t f2u_message_buffer[400]; //199 uint8_t f2u_message_buffer[400]; //199
memset (f2u_message_buffer, 0, sizeof(f2u_message_buffer)); memset (f2u_message_buffer, 0, sizeof(f2u_message_buffer));
@ -405,33 +445,52 @@ void nxdn_deperm_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t bits[34
} }
if (crc == check) if (crc == check)
{ {
fprintf (stderr, " F2/U "); if (type == 1) NXDN_Elements_Content_decode(opts, state, 1, f2u_message_buffer);
NXDN_Elements_Content_decode(opts, state, 1, f2u_message_buffer); if (type == 0) ; //need handling for user data (text messages and AVL)
}
else if (opts->aggressive_framesync == 0)
{
if (type == 1) NXDN_Elements_Content_decode(opts, state, 0, f2u_message_buffer);
if (type == 0) ; //need handling for user data (text messages and AVL)
} }
else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, f2u_message_buffer);
if (opts->payload == 1) if (opts->payload == 1)
{ {
fprintf (stderr, "\n"); fprintf (stderr, "\n");
fprintf (stderr, " F2/U Payload\n "); if (type == 0) fprintf (stderr, " UDCH");
if (type == 1) fprintf (stderr, " FACCH2");
fprintf (stderr, " Payload\n ");
for (int i = 0; i < 26; i++) for (int i = 0; i < 26; i++)
{ {
if (i == 13) fprintf (stderr, "\n ");
fprintf (stderr, "[%02X]", m_data[i]); fprintf (stderr, "[%02X]", m_data[i]);
if (i == 12) fprintf (stderr, "\n ");
} }
if (crc != check) fprintf (stderr, " CRC ERR "); // if (crc != check) fprintf (stderr, " CRC ERR ");
if (type == 0)
{
fprintf (stderr, "\n UDCH Data: ASCII - " );
for (int i = 0; i < 24; i++) //remove crc portion
{
if (m_data[i] <= 0x7E && m_data[i] >=0x20)
{
fprintf (stderr, "%c", m_data[i]);
}
else fprintf (stderr, " ");
}
}
} }
} }
void nxdn_deperm_cac(dsd_opts * opts, dsd_state * state, uint8_t bits[300]) void nxdn_deperm_cac(dsd_opts * opts, dsd_state * state, uint8_t bits[300])
{ {
uint8_t deperm[500]; //300 uint8_t deperm[500]; //300
uint8_t depunc[500]; //350 uint8_t depunc[500]; //350
uint8_t trellis_buf[400]; //171 uint8_t trellis_buf[400]; //171
int id = 0; int id = 0;
int ran = 0;
uint16_t crc = 0; uint16_t crc = 0;
memset (deperm, 0, sizeof(deperm)); memset (deperm, 0, sizeof(deperm));
@ -505,11 +564,27 @@ void nxdn_deperm_cac(dsd_opts * opts, dsd_state * state, uint8_t bits[300])
cac_message_buffer[i] = trellis_buf[i+8]; cac_message_buffer[i] = trellis_buf[i+8];
} }
if (state->nxdn_last_ran != -1) fprintf (stderr, " RAN %02d ", state->nxdn_last_ran);
else fprintf (stderr, " ");
if (crc == 0) if (crc == 0)
{ {
fprintf (stderr, " CAC "); ran = (trellis_buf[2] << 5) | (trellis_buf[3] << 4) | (trellis_buf[4] << 3) | (trellis_buf[5] << 2) | (trellis_buf[6] << 1) | trellis_buf[7];
NXDN_Elements_Content_decode(opts, state, 1, cac_message_buffer); state->nxdn_last_ran = ran;
} }
fprintf (stderr, "%s", KYEL);
fprintf (stderr, " CAC");
fprintf (stderr, "%s", KNRM);
if (crc != 0)
{
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
if (crc == 0) NXDN_Elements_Content_decode(opts, state, 1, cac_message_buffer);
else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, cac_message_buffer); else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, cac_message_buffer);
if (opts->payload == 1) if (opts->payload == 1)
@ -521,32 +596,320 @@ void nxdn_deperm_cac(dsd_opts * opts, dsd_state * state, uint8_t bits[300])
fprintf (stderr, "[%02X]", m_data[i]); fprintf (stderr, "[%02X]", m_data[i]);
if (i == 10) fprintf (stderr, "\n "); if (i == 10) fprintf (stderr, "\n ");
} }
if (crc != 0) fprintf (stderr, " CRC ERR "); // if (crc != 0) fprintf (stderr, " CRC ERR ");
} }
} }
//Type-D "IDAS"
void nxdn_deperm_scch(dsd_opts * opts, dsd_state * state, uint8_t bits[60], uint8_t direction)
{
fprintf (stderr, "%s", KYEL);
fprintf (stderr, " SCCH");
//see about initializing these variables
uint8_t deperm[200]; //60
uint8_t depunc[200]; //72
uint8_t trellis_buf[400]; //32
memset (deperm, 0, sizeof(deperm));
memset (depunc, 0, sizeof(depunc));
memset (trellis_buf, 0, sizeof(trellis_buf));
int o = 0;
uint8_t crc = 0; //value computed by crc7 on payload
uint8_t check = 0; //value pulled from last 7 bits
int sf = 0;
int part_of_frame = 0;
for (int i=0; i<60; i++)
deperm[PERM_12_5[i]] = bits[i];
for (int p=0; p<60; p+= 10) {
depunc[o++] = deperm[p+0];
depunc[o++] = deperm[p+1];
depunc[o++] = deperm[p+2];
depunc[o++] = deperm[p+3];
depunc[o++] = deperm[p+4];
depunc[o++] = 0;
depunc[o++] = deperm[p+5];
depunc[o++] = deperm[p+6];
depunc[o++] = deperm[p+7];
depunc[o++] = deperm[p+8];
depunc[o++] = deperm[p+9];
depunc[o++] = 0;
}
//switch to the convolutional decoder
uint8_t temp[90];
uint8_t s0;
uint8_t s1;
uint8_t m_data[10]; //5
memset (temp, 0, sizeof (temp));
memset (m_data, 0, sizeof (m_data));
memset (trellis_buf, 0, sizeof(trellis_buf));
for (int i = 0; i < 72; i++)
{
temp[i] = depunc[i] << 1;
}
for (int i = 0; i < 8; i++)
{
temp[i+72] = 0;
}
CNXDNConvolution_start();
for (int i = 0U; i < 36U; i++)
{
s0 = temp[(2*i)];
s1 = temp[(2*i)+1];
CNXDNConvolution_decode(s0, s1);
}
CNXDNConvolution_chainback(m_data, 32U);
for(int i = 0; i < 4; i++)
{
trellis_buf[(i*8)+0] = (m_data[i] >> 7) & 1;
trellis_buf[(i*8)+1] = (m_data[i] >> 6) & 1;
trellis_buf[(i*8)+2] = (m_data[i] >> 5) & 1;
trellis_buf[(i*8)+3] = (m_data[i] >> 4) & 1;
trellis_buf[(i*8)+4] = (m_data[i] >> 3) & 1;
trellis_buf[(i*8)+5] = (m_data[i] >> 2) & 1;
trellis_buf[(i*8)+6] = (m_data[i] >> 1) & 1;
trellis_buf[(i*8)+7] = (m_data[i] >> 0) & 1;
}
crc = crc7_scch(trellis_buf, 25);
for (int i = 0; i < 7; i++)
{
check = check << 1;
check = check | trellis_buf[i+25];
}
//check the sf early for scrambler reset, if required
sf = (trellis_buf[0] << 1) | trellis_buf[1];
if (sf == 3) part_of_frame = 0;
else if (sf == 2) part_of_frame = 1;
else if (sf == 1) part_of_frame = 2;
else if (sf == 0) part_of_frame = 3;
else part_of_frame = 0;
//reset scrambler seed to key value on new superframe
if (part_of_frame == 0 && state->nxdn_cipher_type == 0x1) state->payload_miN = 0;
/*
4.3.2. Mapping to Functional Channel
When Subscriber Unit performs voice communication in RTCH2, SCCH is allocated as
described below.
SCCH has super Frame Structure with 4 frame unit during communication, and has non
super Frame Structure with single frame unit at the start time and end time of sending.
Allocation of the other channel is not specified particularly.
*/
//English Translation: Superframe when voice, Non Superframe when not voice
//What I've found is that by not using the superframe structure, and decoding each 'unit'
//individually instead, we can get more expedient decoding on elements without having to
//sacrifice an entire superframe for one bad CRC, also each element cleanly divides into a
//single 'unit' except for enc parms IV, which can be stored seperately if needed
//NOTE: scch has its own message format, and thus, doesn't go to content element decoding
//like everything else does
if (crc != check)
{
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
if (crc == check) NXDN_decode_scch (opts, state, trellis_buf, direction);
else if (opts->aggressive_framesync == 0) NXDN_decode_scch (opts, state, trellis_buf, direction);
fprintf (stderr, "%s", KNRM);
if (opts->payload == 1)
{
fprintf (stderr, "\n SCCH Payload ");
for (int i = 0; i < 4; i++)
{
fprintf (stderr, "[%02X]", m_data[i]);
}
// if (crc != check) fprintf (stderr, " CRC ERR - %02X %02X", check, crc);
}
fprintf (stderr, "%s", KNRM);
}
void nxdn_deperm_facch3_udch2(dsd_opts * opts, dsd_state * state, uint8_t bits[288], uint8_t type)
{
uint8_t deperm[300]; //144
uint8_t depunc[300]; //192
uint8_t trellis_buf[600]; //96
uint8_t f3_udch2[288]; //completed bitstream without crc and tailing bits attached
uint8_t f3_udch2_bytes[36]; //completed bytes - with crc and tail
uint16_t crc[2]; //crc calculated by function
uint16_t check[2]; //crc from payload for comparison
int out;
//switch to the convolutional decoder
uint8_t temp[500];
uint8_t s0;
uint8_t s1;
uint8_t m_data[40]; //13
memset (temp, 0, sizeof(temp));
memset (m_data, 0, sizeof(m_data));
memset (trellis_buf, 0, sizeof(trellis_buf));
memset (deperm, 0, sizeof(deperm));
memset (depunc, 0, sizeof(depunc));
memset (crc, 0, sizeof(crc));
memset (check, 0, sizeof(check));
memset (f3_udch2, 0, sizeof(f3_udch2));
memset (f3_udch2_bytes, 0, sizeof(f3_udch2_bytes));
for (int j=0; j<2; j++)
{
for (int i=0; i<144; i++)
deperm[PERM_16_9[i]] = bits[i+(j*144)];
out = 0;
for (int i=0; i<144; i+=3) {
depunc[out++] = deperm[i+0];
depunc[out++] = 0;
depunc[out++] = deperm[i+1];
depunc[out++] = deperm[i+2];
}
for (int i = 0; i < 192; i++)
{
temp[i] = depunc[i] << 1;
}
for (int i = 0; i < 8; i++)
{
temp[i+192] = 0;
}
CNXDNConvolution_start();
for (int i = 0U; i < 100U; i++)
{
s0 = temp[(2*i)];
s1 = temp[(2*i)+1];
CNXDNConvolution_decode(s0, s1);
}
CNXDNConvolution_chainback(m_data, 96U);
for(int i = 0; i < 12; i++)
{
trellis_buf[(i*8)+0] = (m_data[i] >> 7) & 1;
trellis_buf[(i*8)+1] = (m_data[i] >> 6) & 1;
trellis_buf[(i*8)+2] = (m_data[i] >> 5) & 1;
trellis_buf[(i*8)+3] = (m_data[i] >> 4) & 1;
trellis_buf[(i*8)+4] = (m_data[i] >> 3) & 1;
trellis_buf[(i*8)+5] = (m_data[i] >> 2) & 1;
trellis_buf[(i*8)+6] = (m_data[i] >> 1) & 1;
trellis_buf[(i*8)+7] = (m_data[i] >> 0) & 1;
}
crc[j] = crc12f (trellis_buf, 84); //84
for (int i = 0; i < 12; i++)
{
check[j] = check[j] << 1;
check[j] = check[j] | trellis_buf[84+i]; //84
}
//transfer to storage sans crc and tail bits
for (int i = 0; i < 80; i++) f3_udch2[i+(j*80)] = trellis_buf[i];
for (int i = 0; i < 12; i++) f3_udch2_bytes[i+(j*12)] = m_data[i];
}
if (crc[0] != check[0] || crc[1] != check[1])
{
if (type == 0) fprintf (stderr, " UDCH2");
if (type == 1) fprintf (stderr, " FACCH3");
fprintf (stderr, "%s", KRED);
fprintf (stderr, " (CRC ERR)");
fprintf (stderr, "%s", KNRM);
}
if (crc[0] == check[0] && crc[1] == check[1])
{
if (type == 1) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf);
if (type == 0) ; //need handling for user data (text messages and AVL)
}
else if (opts->aggressive_framesync == 0)
{
if (type == 1) NXDN_Elements_Content_decode(opts, state, 0, trellis_buf);
if (type == 0) ; //need handling for user data (text messages and AVL)
}
if (opts->payload == 1)
{
fprintf (stderr, "\n");
if (type == 0) fprintf (stderr, " UDCH2");
if (type == 1) fprintf (stderr, " FACCH3");
fprintf (stderr, " Payload \n ");
for (int i = 0; i < 24; i++)
{
if (i == 12) fprintf (stderr, "\n ");
fprintf (stderr, "[%02X]", f3_udch2_bytes[i]);
}
// fprintf (stderr, " - %03X %03X", check[0], crc[0]);
// fprintf (stderr, " - %03X %03X", check[1], crc[1]);
if (type == 0)
{
fprintf (stderr, "\n UDCH2 Data: ASCII - " );
for (int i = 0; i < 22; i++) //all but last crc portion
{
if (i == 12) i = 14; //skip first crc portion
if (f3_udch2_bytes[i] <= 0x7E && f3_udch2_bytes[i] >=0x20)
{
fprintf (stderr, "%c", f3_udch2_bytes[i]);
}
else fprintf (stderr, " ");
}
}
}
}
void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType) void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType)
{ {
//NOTE: Most Req/Resp (request and respone) share same message type but differ depending on channel type
//RTCH Outbound will take precedent when differences may occur (except CALL_ASSGN)
fprintf (stderr, "%s", KYEL); fprintf (stderr, "%s", KYEL);
if (MessageType == 0x10) fprintf(stderr, " IDLE"); if (MessageType == 0x10) fprintf(stderr, " IDLE");
else if (MessageType == 0x11) fprintf(stderr, " DISC"); //disconnect else if (MessageType == 0x00) fprintf(stderr, " CALL_RESP");
else if (MessageType == 0x01) fprintf(stderr, " VCALL"); else if (MessageType == 0x01) fprintf(stderr, " VCALL");
else if (MessageType == 0x02) fprintf(stderr, " VCALL_REC_REQ");
else if (MessageType == 0x03) fprintf(stderr, " VCALL_IV"); else if (MessageType == 0x03) fprintf(stderr, " VCALL_IV");
else if (MessageType == 0x07) fprintf(stderr, " TX_REL_EX");
else if (MessageType == 0x08) fprintf(stderr, " TX_REL");
else if (MessageType == 0x04) fprintf(stderr, " VCALL_ASSGN"); else if (MessageType == 0x04) fprintf(stderr, " VCALL_ASSGN");
else if (MessageType == 0x05) fprintf(stderr, " VCALL_ASSGN_DUP"); else if (MessageType == 0x05) fprintf(stderr, " VCALL_ASSGN_DUP");
else if (MessageType == 0x06) fprintf(stderr, " CALL_CONN_RESP");
else if (MessageType == 0x07) fprintf(stderr, " TX_REL_EX");
else if (MessageType == 0x08) fprintf(stderr, " TX_REL");
else if (MessageType == 0x09) fprintf(stderr, " DCALL_HEADER");
else if (MessageType == 0x0A) fprintf(stderr, " DCALL_REC_REQ");
else if (MessageType == 0x0B) fprintf(stderr, " DCALL_UDATA");
else if (MessageType == 0x0C) fprintf(stderr, " DCALL_ACK");
else if (MessageType == 0x0D) fprintf(stderr, " DCALL_ASSGN_DUP");
else if (MessageType == 0x0E) fprintf(stderr, " DCALL_ASSGN"); else if (MessageType == 0x0E) fprintf(stderr, " DCALL_ASSGN");
else if (MessageType == 0x11) fprintf(stderr, " DISC");
else if (MessageType == 0x17) fprintf(stderr, " DST_ID_INFO");
else if (MessageType == 0x18) fprintf(stderr, " SITE_INFO"); else if (MessageType == 0x18) fprintf(stderr, " SITE_INFO");
else if (MessageType == 0x19) fprintf(stderr, " SRV_INFO"); else if (MessageType == 0x19) fprintf(stderr, " SRV_INFO");
else if (MessageType == 0x1C) fprintf(stderr, " FAIL_STAT_INFO");
else if (MessageType == 0x1A) fprintf(stderr, " CCH_INFO"); else if (MessageType == 0x1A) fprintf(stderr, " CCH_INFO");
else if (MessageType == 0x1B) fprintf(stderr, " ADJ_SITE_INFO"); else if (MessageType == 0x1B) fprintf(stderr, " ADJ_SITE_INFO");
else if (MessageType == 0x1C) fprintf(stderr, " FAIL_STAT_INFO");
else if (MessageType == 0x20) fprintf(stderr, " REG_RESP"); else if (MessageType == 0x20) fprintf(stderr, " REG_RESP");
else if (MessageType == 0x22) fprintf(stderr, " REG_C_RESP"); else if (MessageType == 0x22) fprintf(stderr, " REG_C_RESP");
else if (MessageType == 0x24) fprintf(stderr, " GRP_REG_RESP"); else if (MessageType == 0x24) fprintf(stderr, " GRP_REG_RESP");
@ -555,39 +918,20 @@ void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType)
else if (MessageType == 0x38) fprintf(stderr, " SDCALL_REQ_HEADER"); else if (MessageType == 0x38) fprintf(stderr, " SDCALL_REQ_HEADER");
else if (MessageType == 0x39) fprintf(stderr, " SDCALL_REQ_USERDATA"); else if (MessageType == 0x39) fprintf(stderr, " SDCALL_REQ_USERDATA");
else if (MessageType == 0x3B) fprintf(stderr, " SDCALL_RESP"); else if (MessageType == 0x3B) fprintf(stderr, " SDCALL_RESP");
else if (MessageType == 0x3F) fprintf(stderr, " ALIAS"); else if (MessageType == 0x3F) fprintf(stderr, " ALIAS");
else if (MessageType == 0x0C) fprintf(stderr, " DCALL_ACK");
else fprintf(stderr, " Unknown M-%02X", MessageType); else fprintf(stderr, " Unknown M-%02X", MessageType);
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
//on nxdn48 trunking -- zero out stale values so they won't persist after a transmit release, idle, or disconnect //Zero out stale values on DISC or TX_REL only (IDLE messaages occur often on NXDN96 VCH, and randomly on Type-C FACCH1 steals for some reason)
if (opts->frame_nxdn48 == 1) if (MessageType == 0x08 || MessageType == 0x11)
{ {
if (MessageType == 0x08 || MessageType == 0x10 || MessageType == 0x11) //tx_rel, idle, or disc memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment));
{ state->nxdn_last_rid = 0;
memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment)); state->nxdn_last_tg = 0;
state->nxdn_last_rid = 0; if (state->M == 0) state->nxdn_cipher_type = 0;
state->nxdn_last_tg = 0; memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
state->nxdn_cipher_type = 0; memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); sprintf (state->nxdn_call_type, "%s", "");
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
sprintf (state->nxdn_call_type, "%s", "");
}
}
//if nxdn96, only zero out on disc or tx_rel, some systems have data frames have idle that wipe out the tg/rid and cipher
else
{
if (MessageType == 0x08 || MessageType == 0x11) //tx_rel, or disc
{
memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment));
state->nxdn_last_rid = 0;
state->nxdn_last_tg = 0;
state->nxdn_cipher_type = 0;
// memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
// memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
sprintf (state->nxdn_call_type, "%s", "");
}
} }
} }
@ -701,4 +1045,23 @@ static uint16_t crc16cac(const uint8_t buf[], int len)
} }
crc = crc ^ 0xffff; crc = crc ^ 0xffff;
return crc & 0xffff; return crc & 0xffff;
}
static uint8_t crc7_scch(uint8_t bits[], int len)
{
uint8_t s[7];
uint8_t a;
for (int i=0;i<7;i++)
s[i] = 1;
for (int i=0;i<len;i++) {
a = bits[i] ^ s[0];
s[0] = s[1];
s[1] = s[2];
s[2] = s[3];
s[3] = a ^ s[4];
s[4] = s[5];
s[5] = s[6];
s[6] = a;
}
return load_i(s, 7);
} }

View File

@ -55,8 +55,6 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
MessageType |= (ElementsContent[6] & 1) << 1; MessageType |= (ElementsContent[6] & 1) << 1;
MessageType |= (ElementsContent[7] & 1) << 0; MessageType |= (ElementsContent[7] & 1) << 0;
nxdn_message_type (opts, state, MessageType);
/* Save the "F1" and "F2" flags */ /* Save the "F1" and "F2" flags */
state->NxdnElementsContent.F1 = ElementsContent[0]; state->NxdnElementsContent.F1 = ElementsContent[0];
state->NxdnElementsContent.F2 = ElementsContent[1]; state->NxdnElementsContent.F2 = ElementsContent[1];
@ -64,21 +62,30 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
/* Save the "Message Type" field */ /* Save the "Message Type" field */
state->NxdnElementsContent.MessageType = MessageType; state->NxdnElementsContent.MessageType = MessageType;
/* Set the CRC state */
state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect;
/* Decode the right "Message Type" */ /* Decode the right "Message Type" */
switch(MessageType) switch(MessageType)
{ {
//VCALL_ASSGN //Debug: Disable DUP messages if they cause random issues with Type-C trunking (i.e. changing SRC ang TGT IDs, hopping in the middle of calls, etc)
case 0x04:
//continue flow to DUP, both use same message format
//VCALL_ASSGN_DUP //VCALL_ASSGN_DUP
case 0x05: case 0x05:
//DCALL_ASSGN_DUP
case 0x0D:
//VCALL_ASSGN
case 0x04:
//DCALL_ASSGN
case 0x0E:
NXDN_decode_VCALL_ASSGN(opts, state, ElementsContent); NXDN_decode_VCALL_ASSGN(opts, state, ElementsContent);
break; break;
//Alias 0x3F //Alias 0x3F
case 0x3F: case 0x3F:
state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect;
NXDN_decode_Alias(opts, state, ElementsContent); NXDN_decode_Alias(opts, state, ElementsContent);
break; break;
@ -102,9 +109,16 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
NXDN_decode_adj_site(opts, state, ElementsContent); NXDN_decode_adj_site(opts, state, ElementsContent);
break; break;
//VCALL, TX_REL_EXT and TX_REL
case 0x01:
case 0x07:
case 0x08:
NXDN_decode_VCALL(opts, state, ElementsContent);
break;
//DISC //DISC
case 0x11: case 0x11:
//NXDN_decode_VCALL(opts, state, ElementsContent); NXDN_decode_VCALL(opts, state, ElementsContent);
//tune back to CC here - save about 1-2 seconds //tune back to CC here - save about 1-2 seconds
if (opts->p25_trunk == 1 && state->p25_cc_freq != 0 && opts->p25_is_tuned == 1) if (opts->p25_trunk == 1 && state->p25_cc_freq != 0 && opts->p25_is_tuned == 1)
@ -115,8 +129,6 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
//extra safeguards due to sync issues with NXDN //extra safeguards due to sync issues with NXDN
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment)); memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
// state->lastsynctype = -1;
// state->last_cc_sync_time = time(NULL);
opts->p25_is_tuned = 0; opts->p25_is_tuned = 0;
if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw); if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw);
SetFreq(opts->rigctl_sockfd, state->p25_cc_freq); SetFreq(opts->rigctl_sockfd, state->p25_cc_freq);
@ -128,8 +140,6 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
//extra safeguards due to sync issues with NXDN //extra safeguards due to sync issues with NXDN
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment)); memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
// state->lastsynctype = -1;
// state->last_cc_sync_time = time(NULL);
opts->p25_is_tuned = 0; opts->p25_is_tuned = 0;
rtl_udp_tune (opts, state, state->p25_cc_freq); rtl_udp_tune (opts, state, state->p25_cc_freq);
} }
@ -139,60 +149,12 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
//Idle //Idle
case 0x10: case 0x10:
{
break; break;
}
/* VCALL */ //VCALL_IV
case 0x01: case 0x03:
{
/* Set the CRC state */
state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect;
/* Decode the "VCALL" message */
NXDN_decode_VCALL(opts, state, ElementsContent);
/* Check the "Cipher Type" and the "Key ID" validity */
if(CrcCorrect)
{
state->NxdnElementsContent.CipherParameterValidity = 1;
}
else state->NxdnElementsContent.CipherParameterValidity = 0;
break;
} /* End case NXDN_VCALL: */
/* VCALL_IV */
case 0x03:
{
/* Set the CRC state */
state->NxdnElementsContent.VCallIvCrcIsGood = CrcCorrect;
/* Decode the "VCALL_IV" message */
NXDN_decode_VCALL_IV(opts, state, ElementsContent); NXDN_decode_VCALL_IV(opts, state, ElementsContent);
if(CrcCorrect)
{
/* CRC is correct, copy the next theorical IV to use directly from the
* received VCALL_IV */
memcpy(state->NxdnElementsContent.NextIVComputed, state->NxdnElementsContent.IV, 8);
}
else
{
/* CRC is incorrect, compute the next IV to use */
CurrentIV = 0;
/* Convert the 8 bytes buffer into a 64 bits integer */
for(i = 0; i < 8; i++)
{
CurrentIV |= state->NxdnElementsContent.NextIVComputed[i];
CurrentIV = CurrentIV << 8;
}
}
break; break;
} /* End case NXDN_VCALL_IV: */
/* Unknown Message Type */ /* Unknown Message Type */
default: default:
@ -201,6 +163,8 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state,
} }
} /* End switch(MessageType) */ } /* End switch(MessageType) */
nxdn_message_type (opts, state, MessageType);
} /* End NXDN_Elements_Content_decode() */ } /* End NXDN_Elements_Content_decode() */
//externalize multiple sub-element handlers //externalize multiple sub-element handlers
@ -368,6 +332,20 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
uint8_t DuplexMode[32] = {0}; uint8_t DuplexMode[32] = {0};
uint8_t TransmissionMode[32] = {0}; uint8_t TransmissionMode[32] = {0};
uint8_t MessageType;
/* Get the "Message Type" field */
MessageType = (Message[2] & 1) << 5;
MessageType |= (Message[3] & 1) << 4;
MessageType |= (Message[4] & 1) << 3;
MessageType |= (Message[5] & 1) << 2;
MessageType |= (Message[6] & 1) << 1;
MessageType |= (Message[7] & 1) << 0;
if (MessageType == 0x4) fprintf (stderr, "%s", KGRN); //VCALL_ASSGN
else if (MessageType == 0x05) fprintf (stderr, "%s", KGRN); //VCALL_ASSGN_DUP
else if (MessageType == 0x0E) fprintf (stderr, "%s", KCYN); //DCALL_ASSGN
else if (MessageType == 0x0D) fprintf (stderr, "%s", KCYN); //DCALL_ASSGN_DUP
//DFA specific variables //DFA specific variables
uint8_t bw = 0; uint8_t bw = 0;
uint16_t OFN = 0; uint16_t OFN = 0;
@ -406,35 +384,88 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
OFN = (uint16_t)ConvertBitIntoBytes(&Message[64], 16); OFN = (uint16_t)ConvertBitIntoBytes(&Message[64], 16);
IFN = (uint16_t)ConvertBitIntoBytes(&Message[80], 16); IFN = (uint16_t)ConvertBitIntoBytes(&Message[80], 16);
} }
//Part 1-E Common Air Interface Ver.1.3 - 6.4.1.23. Voice Call Assignment (VCALL_ASSGN)
//While I've only seen Busy Repeater Message on the SCCH message (different format)
//The manual suggests this message exists on Type-D systems with this configuration
//On Type-D systems, need to truncate to an 11-bit value
//other 5-bits are repeater or prefix value
// uint8_t idas = 0;
// uint8_t rep1 = 0;
// uint8_t rep2 = 0;
// if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1;
// if (idas)
// {
// rep1 = (SourceUnitID >> 11) & 0x1F;
// rep2 = (DestinationID >> 11) & 0x1F;
// SourceUnitID = SourceUnitID & 0x7FF;
// DestinationID = DestinationID & 0x7FF;
// //assign source prefix/rep1 as the tune to channel?
// //In normal VCALL, both prefix/rep values are the same on the Type-D samples I have
// Channel = rep1;
// }
if (MessageType == 0x4 || MessageType == 0x5)
fprintf (stderr, "%s", KGRN); //VCALL_ASSGN
else fprintf (stderr, "%s", KCYN); //DCALL_ASSGN
fprintf (stderr, "\n ");
/* Print the "CC Option" */ /* Print the "CC Option" */
if(CCOption & 0x80) fprintf(stderr, "Emergency "); if(CCOption & 0x80) fprintf(stderr, "Emergency ");
if(CCOption & 0x40) fprintf(stderr, "Visitor "); if(CCOption & 0x40) fprintf(stderr, "Visitor ");
if(CCOption & 0x20) fprintf(stderr, "Priority Paging "); if(CCOption & 0x20) fprintf(stderr, "Priority Paging ");
/* Print the "Call Type" */ /* Print the "Call Type" */
fprintf (stderr, "%s", KGRN); fprintf(stderr, "%s - ", NXDN_Call_Type_To_Str(CallType));
fprintf(stderr, "\n %s - ", NXDN_Call_Type_To_Str(CallType));
/* Print the "Voice Call Option" */ /* Print the "Voice Call Option" */
NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); if (MessageType == 0x4 || MessageType == 0x5) NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode);
fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); if (MessageType == 0x4 || MessageType == 0x5) fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode);
else fprintf (stderr, " Data Call Assignment - "); //DCALL_ASSGN or DCALL_ASSGN_DUP
/* Print Source ID and Destination ID (Talk Group or Unit ID) */ /* Print Source ID and Destination ID (Talk Group or Unit ID) */
fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF);
// if (idas) fprintf (stderr, "- Ch: %d ", rep1);
/* Print Channel */ /* Print Channel */
if (state->nxdn_rcn == 0) if (state->nxdn_rcn == 0)
fprintf(stderr, "- Channel [%03X][%04d] ", Channel & 0x3FF, Channel & 0x3FF); fprintf(stderr, "- Channel [%03X][%04d] ", Channel & 0x3FF, Channel & 0x3FF);
if (state->nxdn_rcn == 1) if (state->nxdn_rcn == 1)
{
fprintf(stderr, "- DFA Channel [%04X][%05d] ", OFN, OFN); fprintf(stderr, "- DFA Channel [%04X][%05d] ", OFN, OFN);
//running the BW here is a bit repetitive since running the call type/option will echo the same thing
// if (bw == 0) fprintf (stderr, "BW: 6.25 kHz - 4800 bps"); //test VCALL_ASSGN_DUP, if no voice sync activity in 1-2 seconds, then convert to assgn and allow tuning
// else if (bw == 1) fprintf (stderr, "BW: 12.5 kHz - 9600 bps"); //VCALL_ASSGN_DUP has been seen in the middle of calls, but also on the tail end instead of a TX_REL or DISC
// else fprintf (stderr, "BW: %d Reserved Value", bw); if (MessageType == 0x5 && opts->p25_is_tuned == 1 && opts->p25_trunk == 1)
{
if ( (time(NULL) - state->last_vc_sync_time) > 1 )
{
MessageType = 0x04; //convert to VCALL
opts->p25_is_tuned = 0; //open tuning back up to tune
}
} }
//Add support for tuning data and group/private calls on trunking systems
uint8_t tune = 0;
//DCALL_ASSGN and DCALL_ASSGN_DUP
if (MessageType == 0x0D || MessageType == 0x0E )
{
if (opts->trunk_tune_data_calls == 1) tune = 1;
}
else if (MessageType == 0x04) //VCALL_ASSGN and converted VCALL_ASSGN_DUP
{
if (CallType == 4) //individual/private call
{
if (opts->trunk_tune_private_calls) tune = 1;
}
else if (opts->trunk_tune_group_calls) tune = 1;
}
if (tune == 0) goto END_ASSGN;
//run process to figure out frequency value from the channel import or from DFA //run process to figure out frequency value from the channel import or from DFA
long int freq = 0; long int freq = 0;
if (state->nxdn_rcn == 0) if (state->nxdn_rcn == 0)
@ -442,11 +473,6 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
if (state->nxdn_rcn == 1) if (state->nxdn_rcn == 1)
freq = nxdn_channel_to_frequency(opts, state, OFN); freq = nxdn_channel_to_frequency(opts, state, OFN);
//check the rkey array for a scrambler key value
//TGT ID and Key ID could clash though if csv or system has both with different keys
if (state->rkey_array[DestinationID] != 0) state->R = state->rkey_array[DestinationID];
if (state->M == 1) state->nxdn_cipher_type = 0x1;
//check for control channel frequency in the channel map if not available //check for control channel frequency in the channel map if not available
if (opts->p25_trunk == 1 && state->p25_cc_freq == 0) if (opts->p25_trunk == 1 && state->p25_cc_freq == 0)
{ {
@ -492,7 +518,7 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
//check to see if the source/target candidate is blocked first //check to see if the source/target candidate is blocked first
if (opts->p25_trunk == 1 && (strcmp(mode, "DE") != 0) && (strcmp(mode, "B") != 0)) //DE is digital encrypted, B is block if (opts->p25_trunk == 1 && (strcmp(mode, "DE") != 0) && (strcmp(mode, "B") != 0)) //DE is digital encrypted, B is block
{ {
if (state->p25_cc_freq != 0 && opts->p25_is_tuned == 0 && freq != 0) //if we aren't already on a VC and have a valid frequency already if (state->p25_cc_freq != 0 && opts->p25_is_tuned == 0 && freq != 0) //if we aren't already on a VC and have a valid frequency
{ {
//rigctl //rigctl
if (opts->use_rigctl == 1) if (opts->use_rigctl == 1)
@ -502,7 +528,7 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
state->lastsynctype = -1; state->lastsynctype = -1;
state->last_cc_sync_time = time(NULL); state->last_cc_sync_time = time(NULL);
state->last_vc_sync_time = time(NULL); //useful? state->last_vc_sync_time = time(NULL);
// //
if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw); if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw);
@ -511,9 +537,19 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop
//set rid and tg when we actually tune to it //set rid and tg when we actually tune to it
state->nxdn_last_rid = SourceUnitID & 0xFFFF; state->nxdn_last_rid = SourceUnitID;
state->nxdn_last_tg = (DestinationID & 0xFFFF); state->nxdn_last_tg = DestinationID;
sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType)); sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType));
//check the rkey array for a scrambler key value
//TGT ID and Key ID could clash though if csv or system has both with different keys
if (state->rkey_array[DestinationID] != 0)
{
state->R = state->rkey_array[DestinationID];
fprintf (stderr, " %s", KYEL);
fprintf (stderr, " Key Loaded: %lld", state->rkey_array[DestinationID]);
}
if (state->M == 1) state->nxdn_cipher_type = 0x1;
} }
//rtl_udp //rtl_udp
else if (opts->audio_in_type == 3) else if (opts->audio_in_type == 3)
@ -531,14 +567,25 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
opts->p25_is_tuned = 1; opts->p25_is_tuned = 1;
//set rid and tg when we actually tune to it //set rid and tg when we actually tune to it
state->nxdn_last_rid = SourceUnitID & 0xFFFF; state->nxdn_last_rid = SourceUnitID;
state->nxdn_last_tg = (DestinationID & 0xFFFF); state->nxdn_last_tg = DestinationID;
sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType)); sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType));
//check the rkey array for a scrambler key value
//TGT ID and Key ID could clash though if csv or system has both with different keys
if (state->rkey_array[DestinationID] != 0)
{
state->R = state->rkey_array[DestinationID];
fprintf (stderr, " %s", KYEL);
fprintf (stderr, " Key Loaded: %lld", state->rkey_array[DestinationID]);
}
if (state->M == 1) state->nxdn_cipher_type = 0x1;
} }
} }
} }
END_ASSGN: ; //do nothing
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
} /* End NXDN_decode_VCALL_ASSGN() */ } /* End NXDN_decode_VCALL_ASSGN() */
@ -954,6 +1001,20 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message)
uint8_t TransmissionMode[32] = {0}; uint8_t TransmissionMode[32] = {0};
unsigned long long int FullMessage = 0; unsigned long long int FullMessage = 0;
uint8_t MessageType;
/* Get the "Message Type" field */
MessageType = (Message[2] & 1) << 5;
MessageType |= (Message[3] & 1) << 4;
MessageType |= (Message[4] & 1) << 3;
MessageType |= (Message[5] & 1) << 2;
MessageType |= (Message[6] & 1) << 1;
MessageType |= (Message[7] & 1) << 0;
if (MessageType == 0x1) fprintf (stderr, "%s", KGRN); //VCALL
else if (MessageType == 0x07) fprintf (stderr, "%s", KYEL); //TX_REL_EXT
else if (MessageType == 0x08) fprintf (stderr, "%s", KYEL); //TX_REL
else if (MessageType == 0x11) fprintf (stderr, "%s", KRED); //DISC
/* Decode "CC Option" */ /* Decode "CC Option" */
CCOption = (uint8_t)ConvertBitIntoBytes(&Message[8], 8); CCOption = (uint8_t)ConvertBitIntoBytes(&Message[8], 8);
state->NxdnElementsContent.CCOption = CCOption; state->NxdnElementsContent.CCOption = CCOption;
@ -974,6 +1035,20 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message)
DestinationID = (uint16_t)ConvertBitIntoBytes(&Message[40], 16); DestinationID = (uint16_t)ConvertBitIntoBytes(&Message[40], 16);
state->NxdnElementsContent.DestinationID = DestinationID; state->NxdnElementsContent.DestinationID = DestinationID;
//On Type-D systems, need to truncate to an 11-bit value
//other 5-bits are repeater or prefix value
uint8_t idas = 0;
uint8_t rep1 = 0;
uint8_t rep2 = 0;
if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1;
if (idas)
{
rep1 = (SourceUnitID >> 11) & 0x1F;
rep2 = (DestinationID >> 11) & 0x1F;
SourceUnitID = SourceUnitID & 0x7FF;
DestinationID = DestinationID & 0x7FF;
}
/* Decode the "Cipher Type" */ /* Decode the "Cipher Type" */
CipherType = (uint8_t)ConvertBitIntoBytes(&Message[56], 2); CipherType = (uint8_t)ConvertBitIntoBytes(&Message[56], 2);
state->NxdnElementsContent.CipherType = CipherType; state->NxdnElementsContent.CipherType = CipherType;
@ -982,6 +1057,8 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message)
KeyID = (uint8_t)ConvertBitIntoBytes(&Message[58], 6); KeyID = (uint8_t)ConvertBitIntoBytes(&Message[58], 6);
state->NxdnElementsContent.KeyID = KeyID; state->NxdnElementsContent.KeyID = KeyID;
fprintf (stderr, "\n ");
/* Print the "CC Option" */ /* Print the "CC Option" */
if(CCOption & 0x80) fprintf(stderr, "Emergency "); if(CCOption & 0x80) fprintf(stderr, "Emergency ");
if(CCOption & 0x40) fprintf(stderr, "Visitor "); if(CCOption & 0x40) fprintf(stderr, "Visitor ");
@ -999,16 +1076,21 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message)
} }
/* Print the "Call Type" */ /* Print the "Call Type" */
fprintf (stderr, "%s", KGRN); fprintf (stderr, "%s - ", NXDN_Call_Type_To_Str(CallType));
fprintf(stderr, "\n %s - ", NXDN_Call_Type_To_Str(CallType)); sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType));
sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType));
/* Print the "Voice Call Option" */ /* Print the "Voice Call Option" */
NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); if (MessageType == 0x1) NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode);
fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); if (MessageType == 0x1) fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode);
else if (MessageType == 0x07) fprintf (stderr, "Transmission Release Ex - "); //TX_REL_EX
else if (MessageType == 0x08) fprintf (stderr, " Transmission Release - "); //TX_REL
else if (MessageType == 0x11) fprintf (stderr, " Disconnect - "); //DISC
/* Print Source ID and Destination ID (Talk Group or Unit ID) */ /* Print Source ID and Destination ID (Talk Group or Unit ID) */
fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF);
if (idas) fprintf (stderr, "- Ch: %d ", rep1);
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
//check the rkey array for a scrambler key value //check the rkey array for a scrambler key value
@ -1017,58 +1099,38 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message)
if (state->rkey_array[KeyID] != 0) state->R = state->rkey_array[KeyID]; if (state->rkey_array[KeyID] != 0) state->R = state->rkey_array[KeyID];
else if (state->rkey_array[DestinationID] != 0) state->R = state->rkey_array[DestinationID]; else if (state->rkey_array[DestinationID] != 0) state->R = state->rkey_array[DestinationID];
//consider removing code below, if we get this far (under good crc), //Don't zero key if no keyloader
//then we will always have a correct cipher type and won't need to if (CipherType != 0x1 && state->keyloader == 1) state->R = 0;
//force it, its only needed on the vcall_assgn where no cipher is set
//dsd_mbe can also force the scrambler without triggering the settings here
// if (state->M == 1)
// {
// state->nxdn_cipher_type = 0x1;
// CipherType = 0x1;
// }
//debug
// if (CipherType > 0x1) state->R = 0; //don't use on manual key entry
/* Print the "Cipher Type" */ /* Print the "Cipher Type" */
if(CipherType != 0) if(CipherType != 0 && MessageType == 0x1)
{ {
fprintf (stderr, "\n %s", KYEL); fprintf (stderr, "\n %s", KYEL);
fprintf(stderr, "%s - ", NXDN_Cipher_Type_To_Str(CipherType)); fprintf(stderr, "%s - ", NXDN_Cipher_Type_To_Str(CipherType));
} }
/* Print the Key ID */ /* Print the Key ID */
if(CipherType != 0) if(CipherType != 0 && MessageType == 0x1)
{ {
fprintf(stderr, "Key ID %u - ", KeyID & 0xFF); fprintf(stderr, "Key ID %u - ", KeyID & 0xFF);
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
} }
if (state->nxdn_cipher_type == 0x01 && state->R > 0) //scrambler key value if (CipherType == 0x01 && state->R > 0) //scrambler key value
{ {
fprintf (stderr, "%s", KYEL); fprintf (stderr, "%s", KYEL);
fprintf(stderr, "Value: %05lld", state->R); fprintf(stderr, "Value: %05lld", state->R);
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
} }
//only grab if CRC is okay //only grab if VCALL
if(state->NxdnElementsContent.VCallCrcIsGood) if(MessageType == 0x1)
{ {
if ( (SourceUnitID & 0xFFFF) > 0 ) // state->nxdn_last_rid = SourceUnitID;
{ state->nxdn_last_tg = DestinationID;
state->nxdn_last_rid = SourceUnitID & 0xFFFF; state->nxdn_key = KeyID;
state->nxdn_last_tg = (DestinationID & 0xFFFF); state->nxdn_cipher_type = CipherType;
state->nxdn_key = (KeyID & 0xFF);
state->nxdn_cipher_type = CipherType;
}
}
else
{
fprintf (stderr, "%s", KRED);
fprintf(stderr, "(CRC ERR) ");
fprintf (stderr, "%s", KNRM);
} }
//set enc bit here so we can tell playSynthesizedVoice whether or not to play enc traffic //set enc bit here so we can tell playSynthesizedVoice whether or not to play enc traffic
@ -1086,20 +1148,328 @@ void NXDN_decode_VCALL_IV(dsd_opts * opts, dsd_state * state, uint8_t * Message)
{ {
uint32_t i; uint32_t i;
state->payload_miN = 0; //zero out state->payload_miN = 0; //zero out
uint64_t IV = 0;
/* Extract the IV from the VCALL_IV message */
for(i = 0; i < 8; i++)
{
state->NxdnElementsContent.IV[i] = (uint8_t)ConvertBitIntoBytes(&Message[(i + 1) * 8], 8);
state->payload_miN = state->payload_miN << 8 | state->NxdnElementsContent.IV[i]; //NOTE: On Type-D systems, the FACCH1 version of message only carries a 4-octet payload and also only carries a 22-bit IV
} //which makes no sense since you still have the full 80-bits of FACCH1 to use
uint8_t idas = 0;
if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1;
if (!idas) IV = (uint64_t)ConvertBitIntoBytes(&Message[8], 64);
else IV = (uint64_t)ConvertBitIntoBytes(&Message[8], 22);
//At this point, I would assume an LFSR function is needed to expand the 22-bit IV collected here into a 64-bit IV, or 128-bit IV
state->payload_miN = IV;
state->NxdnElementsContent.PartOfCurrentEncryptedFrame = 2; state->NxdnElementsContent.PartOfCurrentEncryptedFrame = 2;
state->NxdnElementsContent.PartOfNextEncryptedFrame = 1; state->NxdnElementsContent.PartOfNextEncryptedFrame = 1;
fprintf (stderr, "\n VCALL_IV: %016llX", state->payload_miN);
} /* End NXDN_decode_VCALL_IV() */ } /* End NXDN_decode_VCALL_IV() */
//SCCH messages have a unique format that can be used in a super frame, but each 'unit'
//can also (mostly) be decoded seperately, except an enc IV
void NXDN_decode_scch(dsd_opts * opts, dsd_state * state, uint8_t * Message, uint8_t direction)
{
uint8_t sf = (uint8_t)ConvertBitIntoBytes(&Message[0], 2);
uint8_t opcode = (direction << 2 | sf);
fprintf (stderr, "\n " ); //initial line break
if (opts->payload == 1)
{
if (direction == 0) fprintf (stderr, "ISM ");
else fprintf (stderr, "OSM ");
if (sf == 0) fprintf (stderr, "INFO4 ");
else if (sf == 1) fprintf (stderr, "INFO3 ");
else if (sf == 2) fprintf (stderr, "INFO2 ");
else fprintf (stderr, "INFO1 ");
fprintf (stderr, "- ");
fprintf (stderr, "%02X ",opcode);
}
//elements used will be determined by other elements in the ISM/OSM messages
//OSM 4 elements
uint8_t area = Message[2];
uint8_t rep1 = (uint8_t)ConvertBitIntoBytes(&Message[3], 5); //repeater 1 value
uint8_t rep2 = (uint8_t)ConvertBitIntoBytes(&Message[8], 5); //repeater 2 value
uint16_t id = (uint16_t)ConvertBitIntoBytes(&Message[13], 11); //id is context dependent
uint8_t sitet = (uint8_t)ConvertBitIntoBytes(&Message[3], 5); //site type on id=2041 only
uint8_t gu = Message[24]; //group or unit bit
//OSM 2 and 3 elements -- unique only
uint64_t iv_a = (uint64_t)ConvertBitIntoBytes(&Message[13], 12); //initialization vector (0-11)
//OSM 1 elements -- unique only -- DOUBLE CHECK ALL OF THESE!
uint64_t iv_b = (uint64_t)ConvertBitIntoBytes(&Message[18], 6); //initialization vector (b12-17)
uint64_t iv_c = (uint64_t)ConvertBitIntoBytes(&Message[8], 5); //initialization vector (b18-22)
uint8_t iv_type = Message[24]; //0 or 1 tells us whether or not this is for IV, or for cipher/key
uint8_t call_opt = (uint8_t)ConvertBitIntoBytes(&Message[13], 3); //Call Options
uint8_t key_id = (uint8_t)ConvertBitIntoBytes(&Message[18], 6); //Key ID
uint8_t cipher = (uint8_t)ConvertBitIntoBytes(&Message[16], 2); //Cipher Type
uint8_t DuplexMode[32] = {0};
uint8_t TransmissionMode[32] = {0};
//ISM messages are seemingly exactly the same info as OSM, so just going to use OSM versions
//(manual doesn't specify other than that ISM 4 is the same as ISM2, but probably doesn't have IDLE/HALT/SITE ID messages)
//set category to "Type-D" to alert users this is an a Distributed Trunking System
sprintf (state->nxdn_location_category, "Type-D");
//placeholder numbers, using 99 if these aren't set by the Site ID Message (single sites)
if (state->nxdn_location_site_code == 0) state->nxdn_location_site_code = 99;
if (state->nxdn_location_sys_code == 0) state->nxdn_location_sys_code = 99;
if (state->nxdn_last_ran == -1) state->nxdn_last_ran = 99;
state->last_cc_sync_time = time(NULL);
//OSM messages
if (opcode == 0x4 || opcode == 0x0) //INFO 4
{
if (id == 2046)
{
fprintf (stderr, "Idle Repeater Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Repeater 1: %d; ", rep1);
fprintf (stderr, "Repeater 2: %d; ", rep2);
}
else if (id == 2045)
{
fprintf (stderr, "Halt Repeater Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Repeater 1: %d; ", rep1);
fprintf (stderr, "Repeater 2: %d; ", rep2);
}
else if (id == 2044)
{
fprintf (stderr, "Free Repeater Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Free Repeater 1: %d; ", rep1);
fprintf (stderr, "Free Repeater 2: %d; ", rep2);
}
else if (id == 2041)
{
fprintf (stderr, "Site ID Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Site Type: %d ", sitet); //Roaming Algorithm by SU; See below;
if (sitet == 0) fprintf (stderr, "Reserved; ");
else if (sitet == 1) fprintf (stderr, "Wide; ");
else if (sitet == 2) fprintf (stderr, "Middle; ");
else fprintf (stderr, "Narrow; ");
fprintf (stderr, "Site Code: %d ", rep2);
if (rep2 == 0) fprintf (stderr, "Reserved; ");
else if (rep2 < 251) fprintf (stderr, "Open Access; "); //Usable voluntary every TRS...?
else fprintf (stderr, "Reserved; ");
state->nxdn_location_site_code = sitet;
state->nxdn_location_sys_code = sitet;
state->nxdn_last_ran = sitet;
}
else //Busy Repeater Message is essentially our 'call in progess' message for a repeater
{
if (gu && rep1 == 0) fprintf (stderr, "REG_COMM; ");
else fprintf (stderr, "Busy Repeater Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Go to Repeater: %d; ", rep1);
fprintf (stderr, "Home Repeater: %d; ", rep2);
if (rep1 == 31)
{
fprintf (stderr, "\n%s ", KRED);
state->nxdn_last_tg = 0;
state->nxdn_last_rid = 0;
}
else
{
fprintf (stderr, "\n%s ", KGRN);
//only set this is during a voice tx, and not a data tx
if ( time(NULL) - state->last_vc_sync_time < 1 )
state->nxdn_last_tg = id;
}
fprintf (stderr, " Channel Update - CH: %d - TGT: %d ", rep1, id);
if (gu == 0) fprintf (stderr, "Group Call ");
else fprintf (stderr, "Private Call ");
if (rep1 == 31) fprintf (stderr, "Termination ");
//start tuning section here
uint8_t tune = 0; //use this to check to see if okay to tune
//only tune group calls if user set
if (gu == 0 && opts->trunk_tune_group_calls == 1) tune = 1;
//only tune private calls if user set
if (gu == 1 && opts->trunk_tune_private_calls == 1) tune = 1;
//tune back to 'channel 31' on termination, will be user provided 'default' listening channel
if (rep1 == 31) tune = 1;
//begin tuning
if (tune == 1 && rep1 != 0)
{
//check for control channel frequency in the channel map if not available
if (state->trunk_chan_map[31] != 0) state->p25_cc_freq = state->trunk_chan_map[31]; //user provided channel to go to for '31' -- may change to 0 later?
else if (state->trunk_chan_map[rep2] != 0) state->p25_cc_freq = state->trunk_chan_map[rep2]; //rep2 is home repeater under this message
//run group/tgt analysis and tune if available/desired
//group list mode so we can look and see if we need to block tuning any groups, etc
char mode[8]; //allow, block, digital, enc, etc
//if we are using allow/whitelist mode, then write 'B' to mode for block
//comparison below will look for an 'A' to write to mode if it is allowed
if (opts->trunk_use_allow_list == 1) sprintf (mode, "%s", "B");
for (int i = 0; i < state->group_tally; i++)
{
if (state->group_array[i].groupNumber == id) //tg/tgt only on info4 unit
{
fprintf (stderr, " [%s]", state->group_array[i].groupName);
strcpy (mode, state->group_array[i].groupMode);
}
}
long int freq = 0;
freq = nxdn_channel_to_frequency(opts, state, rep1);
//check to see if the source/target candidate is blocked first
if (opts->p25_trunk == 1 && (strcmp(mode, "DE") != 0) && (strcmp(mode, "B") != 0)) //DE is digital encrypted, B is block
{
if (state->p25_cc_freq != 0 && state->last_vc_sync_time > 1 && freq != 0) //if we aren't already on a VC and have a valid frequency already
{
//rigctl
if (opts->use_rigctl == 1)
{
//extra safeguards due to sync issues with NXDN
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
state->lastsynctype = -1;
state->last_cc_sync_time = time(NULL);
state->last_vc_sync_time = time(NULL); //should we use this here, or not?
//
if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw);
SetFreq(opts->rigctl_sockfd, freq);
state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq;
opts->p25_is_tuned = 1;
//check the rkey array for a scrambler key value
//TGT ID and Key ID could clash though if csv or system has both with different keys
if (state->rkey_array[id] != 0) state->R = state->rkey_array[id];
if (state->M == 1) state->nxdn_cipher_type = 0x1;
}
//rtl_udp
else if (opts->audio_in_type == 3)
{
//extra safeguards due to sync issues with NXDN
memset (state->nxdn_sacch_frame_segment, 1, sizeof(state->nxdn_sacch_frame_segment));
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
state->lastsynctype = -1;
state->last_cc_sync_time = time(NULL);
state->last_vc_sync_time = time(NULL); //should we use this here, or not?
//
rtl_udp_tune (opts, state, freq);
state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq;
opts->p25_is_tuned = 1;
//check the rkey array for a scrambler key value
//TGT ID and Key ID could clash though if csv or system has both with different keys
if (state->rkey_array[id] != 0) state->R = state->rkey_array[id];
if (state->M == 1) state->nxdn_cipher_type = 0x1;
}
}
}
} //end tuning -- NOTE: since we are only going by last time voice sync detected, tuning here is always 'unlocked'
} //end 'busy' repeater
}
if (opcode == 0x5 || opcode == 0x1) //INFO 3
{
fprintf (stderr, "Source Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Free Repeater 1: %d; ", rep1);
if (id == 31)
{
fprintf (stderr, "\n%s ", KYEL);
fprintf (stderr, " Call IV A: %04lX", iv_a);
}
else
{
//rep2 is the home repeater/source prefix in this instance
fprintf (stderr, "\n%s ", KGRN);
fprintf (stderr, " Source Update - Prefix CH: %d SRC: %d - (%d-%d) ", rep2, id, rep2, id); //rep2
//only set this is during a voice tx, and not a data tx
if ( time(NULL) - state->last_vc_sync_time < 1 )
state->nxdn_last_rid = id;
}
}
if (opcode == 0x6 || opcode == 0x2) //OSM INFO 2 -- same as 4
{
fprintf (stderr, "Target Message - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Go to Repeater: %d; ", rep1);
if (id == 31)
{
fprintf (stderr, "\n%s ", KYEL);
fprintf (stderr, " Call IV A: %04lX; ", iv_a); //MSB 0-11
//zero out IV storage and append this chunk to MSB position
state->payload_miN = 0;
state->payload_miN = state->payload_miN | (iv_a << 11); //MSB 11, so shift 11 (22 total bits).
}
else
{
//rep2 is the home repeater/source prefix in this instance
fprintf (stderr, "\n%s ", KGRN);
fprintf (stderr, " Target Update - Prefix CH: %d SRC: %d - (%d-%d) ", rep2, id, rep2, id); //rep2
//only set this is during a voice tx, and not a data tx
if ( time(NULL) - state->last_vc_sync_time < 1 )
state->nxdn_last_tg = id;
}
}
if (opcode == 0x7 || opcode == 0x3) //INFO 1 - Call Option/Encryption Parms
{
fprintf (stderr, "Call Option - ");
fprintf (stderr, "Area: %d; ", area);
fprintf (stderr, "Free Repeater 1: %d; ", rep1);
if (iv_type == 0)
{
fprintf (stderr, "Free Repeater 2: %d; ", rep2); //or Pass Character (31) on ISM
fprintf (stderr, "\n%s ", KYEL);
NXDN_Voice_Call_Option_To_Str(call_opt, DuplexMode, TransmissionMode);
fprintf(stderr, " %s %s ", DuplexMode, TransmissionMode);
if (cipher)
{
fprintf(stderr, "- %s - ", NXDN_Cipher_Type_To_Str(cipher));
fprintf (stderr, "Key ID: %d; ", key_id);
state->nxdn_cipher_type = cipher;
state->nxdn_key = key_id;
}
}
else
{
fprintf (stderr, "\n%s ", KYEL);
fprintf (stderr, "Call IV B: %04lX; ", iv_b);
fprintf (stderr, "Call IV C: %04lX; ", iv_c);
//Append to Call IV storage
state->payload_miN = state->payload_miN | (iv_c << 6); //middle 5 bits..shifts 6 to accomodate last 6 bits below
state->payload_miN = state->payload_miN | (iv_b << 0); //last 6 bits...no shift needed
//At this point, I would assume an LFSR function is needed to expand the 22-bit IV collected here into a 64-bit IV, or 128-bit IV
fprintf (stderr, "Completed IV: %016llX", state->payload_miN);
}
}
}
char * NXDN_Call_Type_To_Str(uint8_t CallType) char * NXDN_Call_Type_To_Str(uint8_t CallType)
{ {
@ -1107,14 +1477,14 @@ char * NXDN_Call_Type_To_Str(uint8_t CallType)
switch(CallType) switch(CallType)
{ {
case 0: Ptr = "Broadcast Call"; break; case 0: Ptr = "Broadcast Call"; break;
case 1: Ptr = "Group Call"; break; case 1: Ptr = "Group Call"; break;
case 2: Ptr = "Unspecified Call"; break; case 2: Ptr = "Transmission Release"; break; //"Unspecified Call" This value is used only on TX_REL message.
case 3: Ptr = "Reserved"; break; case 3: Ptr = "Session Call"; break; //"reserved" is session call on Type D
case 4: Ptr = "Individual Call"; break; case 4: Ptr = "Private Call"; break;
case 5: Ptr = "Reserved"; break; case 5: Ptr = "Reserved"; break;
case 6: Ptr = "Interconnect Call"; break; case 6: Ptr = "PSTN Interconnect Call"; break;
case 7: Ptr = "Speed Dial Call"; break; case 7: Ptr = "PSTN Speed Dial Call"; break;
default: Ptr = "Unknown Call Type"; break; default: Ptr = "Unknown Call Type"; break;
} }

View File

@ -32,16 +32,17 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
// length is implicitly 192, with frame sync in first 10 dibits // length is implicitly 192, with frame sync in first 10 dibits
uint8_t dbuf[182]; uint8_t dbuf[182];
uint8_t lich; uint8_t lich;
int answer_len=0; int answer_len = 0;
uint8_t answer[32]; uint8_t answer[32];
uint8_t sacch_answer[32]; uint8_t sacch_answer[32];
int lich_parity_received; int lich_parity_received;
int lich_parity_computed; int lich_parity_computed;
int voice=0; int voice = 0;
int facch=0; int facch = 0;
int facch2=0; int facch2 = 0;
int sacch=0; int udch = 0;
int cac=0; int sacch = 0;
int cac = 0;
int sr_structure; int sr_structure;
int sr_ran; int sr_ran;
@ -51,12 +52,19 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
int facch3 = 0; int facch3 = 0;
int udch2 = 0; int udch2 = 0;
//new breakdown of lich codes
uint8_t lich_rf = 0; //RF Channel Type
uint8_t lich_fc = 0; //Functional Channel Type
uint8_t lich_op = 0; //Options
uint8_t direction; //inbound or outbound direction
uint8_t lich_dibits[8]; uint8_t lich_dibits[8];
uint8_t sacch_bits[60]; uint8_t sacch_bits[60];
uint8_t facch_bits_a[144]; uint8_t facch_bits_a[144];
uint8_t facch_bits_b[144]; uint8_t facch_bits_b[144];
uint8_t cac_bits[300]; uint8_t cac_bits[300];
uint8_t facch2_bits[348]; //facch2 or udch, same amount of bits uint8_t facch2_bits[348]; //facch2 or udch, same amount of bits
uint8_t facch3_bits[288]; //facch3 or udch2, same amoount of bits
//nxdn bit buffer, for easy assignment handling //nxdn bit buffer, for easy assignment handling
int nxdn_bit_buffer[364]; int nxdn_bit_buffer[364];
@ -89,7 +97,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
lich = lich >> 1; lich = lich >> 1;
if (lich_parity_received != lich_parity_computed) if (lich_parity_received != lich_parity_computed)
{ {
// state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
goto END; goto END;
} }
@ -104,7 +112,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
if (lich % 2 == 0 && opts->p25_trunk == 1) if (lich % 2 == 0 && opts->p25_trunk == 1)
{ {
if (opts->payload == 1) fprintf(stderr, " Simplex/Inbound NXDN lich on trunking system - type 0x%02X\n", lich); if (opts->payload == 1) fprintf(stderr, " Simplex/Inbound NXDN lich on trunking system - type 0x%02X\n", lich);
// state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
goto END; goto END;
} }
@ -115,13 +123,15 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
break; break;
case 0x28: //facch2 types case 0x28: //facch2 types
case 0x29: case 0x29:
case 0x2e:
case 0x2f:
case 0x48: case 0x48:
case 0x49: case 0x49:
facch2 = 1;
break;
case 0x2e: //udch types
case 0x2f:
case 0x4e: case 0x4e:
case 0x4f: case 0x4f:
facch2 = 1; udch = 1;
break; break;
case 0x32: //facch in 1, vch in 2 case 0x32: //facch in 1, vch in 2
case 0x33: case 0x33:
@ -134,7 +144,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
case 0x34: //vch in 1, facch in 2 case 0x34: //vch in 1, facch in 2
case 0x35: case 0x35:
case 0x54: case 0x54:
case 0x55: case 0x55: //disabled for testing, IDAS system randomly triggers this one, probably due to poor signal
voice = 1; voice = 1;
facch = 2; facch = 2;
sacch = 1; sacch = 1;
@ -155,7 +165,6 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
case 0x41: case 0x41:
case 0x50: case 0x50:
case 0x51: case 0x51:
voice = 0; voice = 0;
facch = 3; facch = 3;
sacch = 1; sacch = 1;
@ -166,47 +175,52 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
break; break;
//NXDN "Type-D" or "IDAS" Specific Lich Codes //NXDN "Type-D" or "IDAS" Specific Lich Codes
case 0x76: //normal voice (in one and two) case 0x76: //normal vch voice (in one and two)
case 0x77: case 0x77:
idas = 1; idas = 1;
scch = 1; scch = 1;
voice = 3; voice = 3;
break; break;
case 0x74: //vch in 1, scch in 2 (scch 2 steal) case 0x74: //vch in 1, facch1 in 2 (facch 2 steal)
case 0x75: case 0x75:
idas = 1; idas = 1;
scch = 1;
voice = 1; voice = 1;
scch = 2; facch = 2;
break; break;
case 0x72: //scch in 1, vch in 2 (scch 1 steal) case 0x72: //facch in 1, vch in 2 (facch 1 steal)
case 0x73: case 0x73:
idas = 1; idas = 1;
scch = 1;
voice = 2; voice = 2;
scch = 2; facch = 1;
break; break;
case 0x70: //scch in 1 and 2 case 0x70: //facch steal in vch 1 and vch 2 (during voice only)
case 0x71: case 0x71:
idas = 1; idas = 1;
scch = 4; //total of three scch positions scch = 1;
facch = 3;
break; break;
case 0x6E: //udch2 case 0x6E: //udch2
case 0x6F: case 0x6F:
idas = 1; idas = 1;
scch = 1;
udch2 = 1; udch2 = 1;
break; break;
case 0x68: case 0x68:
case 0x69: //facch3 case 0x69: //facch3
idas = 1; idas = 1;
scch = 1;
facch3 = 1; facch3 = 1;
break; break;
case 0x62: case 0x62:
case 0x63: //facch1 in 1, n/post in 2 case 0x63: //facch1 in 1, null data and post field in 2
idas = 1; idas = 1;
scch = 1; scch = 1;
facch = 1; facch = 1;
break; break;
case 0x60: case 0x60:
case 0x61: //facch1 in both case 0x61: //facch1 in both (non vch)
idas = 1; idas = 1;
scch = 1; scch = 1;
facch = 3; facch = 3;
@ -230,7 +244,8 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
{ {
printFrameSync (opts, state, "IDAS D ", 0, "-"); printFrameSync (opts, state, "IDAS D ", 0, "-");
} }
if (opts->payload == 1) fprintf (stderr, "L%02X -", lich); if (opts->payload == 1)
fprintf (stderr, "L%02X - ", lich);
} }
else if (voice || facch || sacch || facch2 || cac) else if (voice || facch || sacch || facch2 || cac)
{ {
@ -239,7 +254,8 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
printFrameSync (opts, state, "NXDN48 ", 0, "-"); printFrameSync (opts, state, "NXDN48 ", 0, "-");
} }
else printFrameSync (opts, state, "NXDN96 ", 0, "-"); else printFrameSync (opts, state, "NXDN96 ", 0, "-");
if (opts->payload == 1) fprintf (stderr, "L%02X -", lich); if (opts->payload == 1)
fprintf (stderr, "L%02X - ", lich);
} }
//now that we have a good LICH, we can collect all of our dibits //now that we have a good LICH, we can collect all of our dibits
@ -258,7 +274,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
nxdn_bit_buffer[i*2+1] = dbuf[i] & 1; nxdn_bit_buffer[i*2+1] = dbuf[i] & 1;
} }
//sacch //sacch or scch bits
for (int i = 0; i < 60; i++) for (int i = 0; i < 60; i++)
{ {
sacch_bits[i] = nxdn_bit_buffer[i+16]; sacch_bits[i] = nxdn_bit_buffer[i+16];
@ -283,28 +299,64 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
facch2_bits[i] = nxdn_bit_buffer[i+16]; facch2_bits[i] = nxdn_bit_buffer[i+16];
} }
//udch2 or facch3
for (int i = 0; i < 288; i++)
{
facch3_bits[i] = nxdn_bit_buffer[i+16+60];
}
//vch frames stay inside dbuf, easier to assign that to ambe_fr frames //vch frames stay inside dbuf, easier to assign that to ambe_fr frames
//sacch needs extra handling depending on superframe or non-superframe variety //sacch needs extra handling depending on superframe or non-superframe variety
if (voice && !facch && scch < 2) //voice only, no facch/scch steal //Add advanced decoding of LICH (RF, FC, OPT, and Direction
lich_rf = (lich >> 5) & 0x3;
lich_fc = (lich >> 3) & 0x3;
lich_op = (lich >> 1) & 0x3;
if (lich % 2 == 0) direction = 0;
else direction = 1;
// RF Channel Type
if (lich_rf == 0) fprintf (stderr, "RCCH ");
else if (lich_rf == 1) fprintf (stderr, "RTCH ");
else if (lich_rf == 1) fprintf (stderr, "RDCH ");
else
{
if (lich < 0x60) fprintf (stderr, "RTCH_C ");
else fprintf (stderr, "RTCH2 ");
}
// Functional Channel Type -- things start to get really convoluted here
// These will echo when handled, either with the decoded message type, or relevant crc err
// if (lich_rf == 0) //CAC Type
// {
// //Technically, we should be checking direction as well, but the fc never has split meaning on CAC
// if (lich_fc == 0) fprintf (stderr, "CAC ");
// else if (lich_fc == 1) fprintf (stderr, "Long CAC ");
// else if (lich_fc == 3) fprintf (stderr, "Short CAC ");
// else fprintf (stderr, "Reserved ");
// }
// else //USC Type
// {
// if (lich_fc == 0) fprintf (stderr, "NSF SACCH ");
// else if (lich_fc == 1) fprintf (stderr, "UDCH ");
// else if (lich_fc == 2) fprintf (stderr, "SF SACCH ");
// else if (lich_fc == 3) fprintf (stderr, "SF SACCH/IDLE ");
// }
//Option/Steal Flags echoed in Voice, V+F, or Data
if (voice && !facch) //voice only, no facch steal
{ {
fprintf (stderr, "%s", KGRN); fprintf (stderr, "%s", KGRN);
fprintf (stderr, " Voice "); fprintf (stderr, " Voice ");
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
} }
else if (voice && facch) //voice with facch steal else if (voice && facch) //voice with facch1 steal
{ {
fprintf (stderr, "%s", KGRN); fprintf (stderr, "%s", KGRN);
fprintf (stderr, " V%d+F%d ", 3 - facch, facch); //print which position on each fprintf (stderr, " V%d+F%d ", 3 - facch, facch); //print which position on each
fprintf (stderr, "%s", KNRM); fprintf (stderr, "%s", KNRM);
} }
else if (voice && scch > 1) //voice with scch steal else //Covers FACCH1 in both, FACCH2, UDCH, UDCH2, CAC
{
fprintf (stderr, "%s", KGRN);
fprintf (stderr, " V%d+S%d ", 4 - scch, scch); //print which position on each
fprintf (stderr, "%s", KNRM);
}
else
{ {
fprintf (stderr, "%s", KCYN); fprintf (stderr, "%s", KCYN);
fprintf (stderr, " Data "); fprintf (stderr, " Data ");
@ -342,28 +394,22 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
if (lich == 0x20 || lich == 0x21 || lich == 0x61 || lich == 0x40 || lich == 0x41) state->nxdn_sacch_non_superframe = TRUE; if (lich == 0x20 || lich == 0x21 || lich == 0x61 || lich == 0x40 || lich == 0x41) state->nxdn_sacch_non_superframe = TRUE;
else state->nxdn_sacch_non_superframe = FALSE; else state->nxdn_sacch_non_superframe = FALSE;
//TODO Later: Add Direction to all decoding functions //TODO Later: Add Direction and/or LICH to all decoding functions
uint8_t direction; if (scch) nxdn_deperm_scch (opts, state, sacch_bits, direction);
if (lich % 2 == 0) direction = 0;
else direction = 1;
//TODO: SCCH handling -- direction will be needed if (udch2) nxdn_deperm_facch3_udch2(opts, state, facch3_bits, 0);
if (scch) fprintf (stderr, " SCCH "); if (facch3) nxdn_deperm_facch3_udch2(opts, state, facch3_bits, 1);
//TODO Much Later: FACCH3 and UDCH2 handling
if (facch3) fprintf (stderr, " FACCH3 ");
if (udch2) fprintf (stderr, " UDCH2 ");
if (sacch) nxdn_deperm_sacch(opts, state, sacch_bits); if (sacch) nxdn_deperm_sacch(opts, state, sacch_bits);
if (cac) nxdn_deperm_cac(opts, state, cac_bits); if (cac) nxdn_deperm_cac(opts, state, cac_bits);
if (facch2) nxdn_deperm_facch2_udch(opts, state, facch2_bits);
//testing purposes -- don't run facch steals on enc -- causing issues with key loader (VCALL_ASSGN_DUP) //Seperated UDCH user data from facch2 data
if (state->nxdn_cipher_type == 0 || state->R == 0) //!voice if (udch) nxdn_deperm_facch2_udch(opts, state, facch2_bits, 0);
{ if (facch2) nxdn_deperm_facch2_udch(opts, state, facch2_bits, 1);
if (facch & 1) nxdn_deperm_facch(opts, state, facch_bits_a);
if (facch & 2) nxdn_deperm_facch(opts, state, facch_bits_b); //SHOULD be okay to run facch1's again on steal frames, will need testing
} if (facch & 1) nxdn_deperm_facch(opts, state, facch_bits_a);
if (facch & 2) nxdn_deperm_facch(opts, state, facch_bits_b);
if (voice) if (voice)
{ {
@ -371,26 +417,29 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
if ((opts->mbe_out_dir[0] != 0) && (opts->mbe_out_f == NULL)) openMbeOutFile (opts, state); if ((opts->mbe_out_dir[0] != 0) && (opts->mbe_out_f == NULL)) openMbeOutFile (opts, state);
//update last voice sync time //update last voice sync time
state->last_vc_sync_time = time(NULL); state->last_vc_sync_time = time(NULL);
//turn on scrambler if forced by user option
if (state->M == 1 && state->R != 0) state->nxdn_cipher_type = 0x1;
//process voice frame //process voice frame
nxdn_voice (opts, state, voice, dbuf); nxdn_voice (opts, state, voice, dbuf);
} }
//close MBE file if no voice and its open //close MBE file if no voice and its open
if (!voice) if (!voice)
{ {
if (opts->mbe_out_f != NULL) if (opts->mbe_out_f != NULL)
{ {
if (opts->frame_nxdn96 == 1) //nxdn96 has voice and data mixed together, so we will need to do a time check first if (opts->frame_nxdn96 == 1) //nxdn96 has pure voice and data frames mixed together, so we will need to do a time check first
{ {
if ( (time(NULL) - state->last_vc_sync_time) > 1) //test for optimal time, 1 sec should be okay if ( (time(NULL) - state->last_vc_sync_time) > 1) //test for optimal time, 1 sec should be okay
{ {
closeMbeOutFile (opts, state); closeMbeOutFile (opts, state);
} }
} }
if (opts->frame_nxdn48 == 1) closeMbeOutFile (opts, state); //okay to close right away if nxdn48, no data/voice mixing if (opts->frame_nxdn48 == 1) closeMbeOutFile (opts, state); //okay to close right away if nxdn48, no data/voice frames mixing
} }
} }
if (voice && facch == 2) //facch steal 2 -- after voice if (voice && facch == 2) //facch steal 2 -- after voice 1
{ {
//roll the voice scrambler LFSR here if key available to advance seed -- half rotation on a facch steal //roll the voice scrambler LFSR here if key available to advance seed -- half rotation on a facch steal
if (state->nxdn_cipher_type == 0x1 && state->R != 0) if (state->nxdn_cipher_type == 0x1 && state->R != 0)
@ -404,7 +453,8 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state)
} }
} }
if (opts->payload == 1 && !voice) fprintf (stderr, "\n");
else if (opts->payload == 0) fprintf (stderr, "\n");
fprintf (stderr, "\n");
END: ; //do nothing END: ; //do nothing
} }