From c507ed229cfeadb62c443e427ee47d8225ea59a3 Mon Sep 17 00:00:00 2001 From: lwvmobile Date: Sun, 23 Apr 2023 19:52:32 -0400 Subject: [PATCH] NXDN Type-D Decoding/Trunking; NXDN Tweaks; #115 --- include/dsd.h | 11 +- src/dsd_main.c | 20 +- src/nxdn_deperm.c | 469 +++++++++++++++++++++++++++++---- src/nxdn_element.c | 634 +++++++++++++++++++++++++++++++++++---------- src/nxdn_frame.c | 162 ++++++++---- 5 files changed, 1041 insertions(+), 255 deletions(-) diff --git a/include/dsd.h b/include/dsd.h index f6c2e9d..d5b6544 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -672,6 +672,7 @@ typedef struct //multi-key array unsigned long long int rkey_array[0xFFFF]; + int keyloader; //let us know the keyloader is active //dmr late entry mi uint64_t late_entry_mi_fragment[2][7][3]; @@ -857,10 +858,15 @@ void ncursesClose (); //new NXDN Functions start here! void nxdn_frame (dsd_opts * opts, dsd_state * state); 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_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_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_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 crc15(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 */ 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_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); +//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); diff --git a/src/dsd_main.c b/src/dsd_main.c index c55649c..463aa91 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -894,7 +894,8 @@ initState (dsd_state * state) state->nxdn_bw = 0; //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 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 break; - case 'k': //NXDN multi-key loader + case 'k': //multi-key loader strncpy(opts.key_in_file, optarg, 1023); opts.key_in_file[1023] = '\0'; csvKeyImport(&opts, &state); + state.keyloader = 1; break; case 'Q': //'DSP' Structured Output file for OKDMRlib @@ -1443,17 +1445,9 @@ main (int argc, char **argv) case 'R': sscanf (optarg, "%lld", &state.R); - if (state.R > 0x7FFF) - { - state.R = 0x7FFF; - } - // opts.dmr_mute_encL = 0; - // opts.dmr_mute_encR = 0; - if (state.R == 0) - { - // opts.dmr_mute_encL = 1; - // opts.dmr_mute_encR = 1; - } + if (state.R > 0x7FFF) state.R = 0x7FFF; + //disable keyloader in case user tries to use this and it at the same time + state.keyloader = 0; break; case 'H': diff --git a/src/nxdn_deperm.c b/src/nxdn_deperm.c index aa8b76e..1e451c3 100644 --- a/src/nxdn_deperm.c +++ b/src/nxdn_deperm.c @@ -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 } + 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); 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, " - %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]) { //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]; } + 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); 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) { 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]); } - 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 (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]; - 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; else if (sf == 2) part_of_frame = 1; 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); else fprintf (stderr, " "); 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 if (part_of_frame == 0 && state->nxdn_cipher_type == 0x1) state->payload_miN = 0; 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_sf = sf; 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]); } - 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 depunc[500]; //406 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]; } + 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 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) - { - fprintf (stderr, " F2/U "); - NXDN_Elements_Content_decode(opts, state, 1, f2u_message_buffer); + { + if (type == 1) 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) { 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++) { + if (i == 13) fprintf (stderr, "\n "); 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]) { - uint8_t deperm[500]; //300 uint8_t depunc[500]; //350 uint8_t trellis_buf[400]; //171 int id = 0; + int ran = 0; uint16_t crc = 0; 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]; } + if (state->nxdn_last_ran != -1) fprintf (stderr, " RAN %02d ", state->nxdn_last_ran); + else fprintf (stderr, " "); + if (crc == 0) { - fprintf (stderr, " CAC "); - NXDN_Elements_Content_decode(opts, state, 1, cac_message_buffer); + 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; } + + 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); 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]); 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) { + //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); 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 == 0x02) fprintf(stderr, " VCALL_REC_REQ"); 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 == 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 == 0x11) fprintf(stderr, " DISC"); + else if (MessageType == 0x17) fprintf(stderr, " DST_ID_INFO"); else if (MessageType == 0x18) fprintf(stderr, " SITE_INFO"); - else if (MessageType == 0x19) fprintf(stderr, " SRV_INFO"); - else if (MessageType == 0x1C) fprintf(stderr, " FAIL_STAT_INFO"); + else if (MessageType == 0x19) fprintf(stderr, " SRV_INFO"); else if (MessageType == 0x1A) fprintf(stderr, " CCH_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 == 0x22) fprintf(stderr, " REG_C_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 == 0x39) fprintf(stderr, " SDCALL_REQ_USERDATA"); else if (MessageType == 0x3B) fprintf(stderr, " SDCALL_RESP"); - else if (MessageType == 0x3F) fprintf(stderr, " ALIAS"); - else if (MessageType == 0x0C) fprintf(stderr, " DCALL_ACK"); else fprintf(stderr, " Unknown M-%02X", MessageType); fprintf (stderr, "%s", KNRM); - //on nxdn48 trunking -- zero out stale values so they won't persist after a transmit release, idle, or disconnect - if (opts->frame_nxdn48 == 1) + //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 (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; - 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", ""); - } - } - //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", ""); - } + memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment)); + state->nxdn_last_rid = 0; + state->nxdn_last_tg = 0; + if (state->M == 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; 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;iNxdnElementsContent.F1 = ElementsContent[0]; 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 */ state->NxdnElementsContent.MessageType = MessageType; + /* Set the CRC state */ + state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect; + /* Decode the right "Message Type" */ switch(MessageType) { - //VCALL_ASSGN - case 0x04: - //continue flow to DUP, both use same message format + //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) //VCALL_ASSGN_DUP case 0x05: + + //DCALL_ASSGN_DUP + case 0x0D: + + //VCALL_ASSGN + case 0x04: + + //DCALL_ASSGN + case 0x0E: NXDN_decode_VCALL_ASSGN(opts, state, ElementsContent); break; //Alias 0x3F case 0x3F: - state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect; NXDN_decode_Alias(opts, state, ElementsContent); break; @@ -102,9 +109,16 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, NXDN_decode_adj_site(opts, state, ElementsContent); break; + //VCALL, TX_REL_EXT and TX_REL + case 0x01: + case 0x07: + case 0x08: + NXDN_decode_VCALL(opts, state, ElementsContent); + break; + //DISC case 0x11: - //NXDN_decode_VCALL(opts, state, ElementsContent); + NXDN_decode_VCALL(opts, state, ElementsContent); //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) @@ -115,8 +129,6 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, //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); opts->p25_is_tuned = 0; if (opts->setmod_bw != 0 ) SetModulation(opts->rigctl_sockfd, opts->setmod_bw); 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 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); opts->p25_is_tuned = 0; 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 case 0x10: - { break; - } - /* VCALL */ - case 0x01: - { - - /* 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 */ + //VCALL_IV + case 0x03: 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; - } /* End case NXDN_VCALL_IV: */ /* Unknown Message Type */ default: @@ -201,6 +163,8 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, } } /* End switch(MessageType) */ + nxdn_message_type (opts, state, MessageType); + } /* End NXDN_Elements_Content_decode() */ //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 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 uint8_t bw = 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); 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" */ if(CCOption & 0x80) fprintf(stderr, "Emergency "); if(CCOption & 0x40) fprintf(stderr, "Visitor "); if(CCOption & 0x20) fprintf(stderr, "Priority Paging "); /* Print the "Call Type" */ - fprintf (stderr, "%s", KGRN); - fprintf(stderr, "\n %s - ", NXDN_Call_Type_To_Str(CallType)); + fprintf(stderr, "%s - ", NXDN_Call_Type_To_Str(CallType)); /* Print the "Voice Call Option" */ - NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); - fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); + if (MessageType == 0x4 || MessageType == 0x5) NXDN_Voice_Call_Option_To_Str(VoiceCallOption, 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) */ fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); + // if (idas) fprintf (stderr, "- Ch: %d ", rep1); + /* Print Channel */ if (state->nxdn_rcn == 0) fprintf(stderr, "- Channel [%03X][%04d] ", Channel & 0x3FF, Channel & 0x3FF); if (state->nxdn_rcn == 1) - { 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"); - // else if (bw == 1) fprintf (stderr, "BW: 12.5 kHz - 9600 bps"); - // else fprintf (stderr, "BW: %d Reserved Value", bw); + + //test VCALL_ASSGN_DUP, if no voice sync activity in 1-2 seconds, then convert to assgn and allow tuning + //VCALL_ASSGN_DUP has been seen in the middle of calls, but also on the tail end instead of a TX_REL or DISC + 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 long int freq = 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) 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 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 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 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)); state->lastsynctype = -1; 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); @@ -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 //set rid and tg when we actually tune to it - state->nxdn_last_rid = SourceUnitID & 0xFFFF; - state->nxdn_last_tg = (DestinationID & 0xFFFF); + state->nxdn_last_rid = SourceUnitID; + state->nxdn_last_tg = DestinationID; 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 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; //set rid and tg when we actually tune to it - state->nxdn_last_rid = SourceUnitID & 0xFFFF; - state->nxdn_last_tg = (DestinationID & 0xFFFF); + state->nxdn_last_rid = SourceUnitID; + state->nxdn_last_tg = DestinationID; 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); } /* 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}; 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" */ CCOption = (uint8_t)ConvertBitIntoBytes(&Message[8], 8); 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); 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" */ CipherType = (uint8_t)ConvertBitIntoBytes(&Message[56], 2); 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); state->NxdnElementsContent.KeyID = KeyID; + fprintf (stderr, "\n "); + /* Print the "CC Option" */ if(CCOption & 0x80) fprintf(stderr, "Emergency "); 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" */ - fprintf (stderr, "%s", KGRN); - fprintf(stderr, "\n %s - ", NXDN_Call_Type_To_Str(CallType)); - sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType)); + fprintf (stderr, "%s - ", NXDN_Call_Type_To_Str(CallType)); + sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType)); /* Print the "Voice Call Option" */ - NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); - fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); + if (MessageType == 0x1) NXDN_Voice_Call_Option_To_Str(VoiceCallOption, 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) */ fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); + + if (idas) fprintf (stderr, "- Ch: %d ", rep1); + fprintf (stderr, "%s", KNRM); //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]; 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), - //then we will always have a correct cipher type and won't need to - //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 + //Don't zero key if no keyloader + if (CipherType != 0x1 && state->keyloader == 1) state->R = 0; /* Print the "Cipher Type" */ - if(CipherType != 0) + if(CipherType != 0 && MessageType == 0x1) { fprintf (stderr, "\n %s", KYEL); fprintf(stderr, "%s - ", NXDN_Cipher_Type_To_Str(CipherType)); } /* Print the Key ID */ - if(CipherType != 0) + if(CipherType != 0 && MessageType == 0x1) { fprintf(stderr, "Key ID %u - ", KeyID & 0xFF); 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, "Value: %05lld", state->R); fprintf (stderr, "%s", KNRM); } - //only grab if CRC is okay - if(state->NxdnElementsContent.VCallCrcIsGood) + //only grab if VCALL + if(MessageType == 0x1) { - if ( (SourceUnitID & 0xFFFF) > 0 ) // - { - state->nxdn_last_rid = SourceUnitID & 0xFFFF; - state->nxdn_last_tg = (DestinationID & 0xFFFF); - state->nxdn_key = (KeyID & 0xFF); - state->nxdn_cipher_type = CipherType; - } - } - else - { - fprintf (stderr, "%s", KRED); - fprintf(stderr, "(CRC ERR) "); - fprintf (stderr, "%s", KNRM); + state->nxdn_last_rid = SourceUnitID; + state->nxdn_last_tg = DestinationID; + state->nxdn_key = KeyID; + state->nxdn_cipher_type = CipherType; } //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; state->payload_miN = 0; //zero out - - /* 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); + uint64_t IV = 0; - 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.PartOfNextEncryptedFrame = 1; + fprintf (stderr, "\n VCALL_IV: %016llX", state->payload_miN); + } /* 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) { @@ -1107,14 +1477,14 @@ char * NXDN_Call_Type_To_Str(uint8_t CallType) switch(CallType) { - case 0: Ptr = "Broadcast Call"; break; - case 1: Ptr = "Group Call"; break; - case 2: Ptr = "Unspecified Call"; break; - case 3: Ptr = "Reserved"; break; - case 4: Ptr = "Individual Call"; break; - case 5: Ptr = "Reserved"; break; - case 6: Ptr = "Interconnect Call"; break; - case 7: Ptr = "Speed Dial Call"; break; + case 0: Ptr = "Broadcast Call"; break; + case 1: Ptr = "Group Call"; break; + case 2: Ptr = "Transmission Release"; break; //"Unspecified Call" This value is used only on TX_REL message. + case 3: Ptr = "Session Call"; break; //"reserved" is session call on Type D + case 4: Ptr = "Private Call"; break; + case 5: Ptr = "Reserved"; break; + case 6: Ptr = "PSTN Interconnect Call"; break; + case 7: Ptr = "PSTN Speed Dial Call"; break; default: Ptr = "Unknown Call Type"; break; } diff --git a/src/nxdn_frame.c b/src/nxdn_frame.c index 6a9a3c4..14e543a 100644 --- a/src/nxdn_frame.c +++ b/src/nxdn_frame.c @@ -32,16 +32,17 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) // length is implicitly 192, with frame sync in first 10 dibits uint8_t dbuf[182]; uint8_t lich; - int answer_len=0; + int answer_len = 0; uint8_t answer[32]; uint8_t sacch_answer[32]; int lich_parity_received; int lich_parity_computed; - int voice=0; - int facch=0; - int facch2=0; - int sacch=0; - int cac=0; + int voice = 0; + int facch = 0; + int facch2 = 0; + int udch = 0; + int sacch = 0; + int cac = 0; int sr_structure; int sr_ran; @@ -51,12 +52,19 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) int facch3 = 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 sacch_bits[60]; uint8_t facch_bits_a[144]; uint8_t facch_bits_b[144]; uint8_t cac_bits[300]; 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 int nxdn_bit_buffer[364]; @@ -89,7 +97,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) lich = lich >> 1; 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; } @@ -104,7 +112,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) 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); - // 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; } @@ -115,13 +123,15 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) break; case 0x28: //facch2 types case 0x29: - case 0x2e: - case 0x2f: case 0x48: case 0x49: + facch2 = 1; + break; + case 0x2e: //udch types + case 0x2f: case 0x4e: case 0x4f: - facch2 = 1; + udch = 1; break; case 0x32: //facch in 1, vch in 2 case 0x33: @@ -134,7 +144,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) case 0x34: //vch in 1, facch in 2 case 0x35: case 0x54: - case 0x55: + case 0x55: //disabled for testing, IDAS system randomly triggers this one, probably due to poor signal voice = 1; facch = 2; sacch = 1; @@ -155,7 +165,6 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) case 0x41: case 0x50: case 0x51: - voice = 0; facch = 3; sacch = 1; @@ -166,47 +175,52 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) break; //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: idas = 1; scch = 1; voice = 3; 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: idas = 1; + scch = 1; voice = 1; - scch = 2; + facch = 2; 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: idas = 1; + scch = 1; voice = 2; - scch = 2; + facch = 1; break; - case 0x70: //scch in 1 and 2 + case 0x70: //facch steal in vch 1 and vch 2 (during voice only) case 0x71: idas = 1; - scch = 4; //total of three scch positions + scch = 1; + facch = 3; break; case 0x6E: //udch2 case 0x6F: idas = 1; + scch = 1; udch2 = 1; break; case 0x68: case 0x69: //facch3 idas = 1; + scch = 1; facch3 = 1; break; 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; scch = 1; facch = 1; break; case 0x60: - case 0x61: //facch1 in both + case 0x61: //facch1 in both (non vch) idas = 1; scch = 1; facch = 3; @@ -230,7 +244,8 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) { 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) { @@ -239,7 +254,8 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) printFrameSync (opts, state, "NXDN48 ", 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 @@ -258,7 +274,7 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) nxdn_bit_buffer[i*2+1] = dbuf[i] & 1; } - //sacch + //sacch or scch bits for (int i = 0; i < 60; i++) { 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]; } + //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 //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, " Voice "); 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, " V%d+F%d ", 3 - facch, facch); //print which position on each fprintf (stderr, "%s", KNRM); } - else if (voice && scch > 1) //voice with scch steal - { - fprintf (stderr, "%s", KGRN); - fprintf (stderr, " V%d+S%d ", 4 - scch, scch); //print which position on each - fprintf (stderr, "%s", KNRM); - } - else + else //Covers FACCH1 in both, FACCH2, UDCH, UDCH2, CAC { fprintf (stderr, "%s", KCYN); 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; else state->nxdn_sacch_non_superframe = FALSE; - //TODO Later: Add Direction to all decoding functions - uint8_t direction; - if (lich % 2 == 0) direction = 0; - else direction = 1; + //TODO Later: Add Direction and/or LICH to all decoding functions + if (scch) nxdn_deperm_scch (opts, state, sacch_bits, direction); - //TODO: SCCH handling -- direction will be needed - if (scch) fprintf (stderr, " SCCH "); - - //TODO Much Later: FACCH3 and UDCH2 handling - if (facch3) fprintf (stderr, " FACCH3 "); - if (udch2) fprintf (stderr, " UDCH2 "); + if (udch2) nxdn_deperm_facch3_udch2(opts, state, facch3_bits, 0); + if (facch3) nxdn_deperm_facch3_udch2(opts, state, facch3_bits, 1); if (sacch) nxdn_deperm_sacch(opts, state, sacch_bits); - if (cac) nxdn_deperm_cac(opts, state, cac_bits); - if (facch2) nxdn_deperm_facch2_udch(opts, state, facch2_bits); + if (cac) nxdn_deperm_cac(opts, state, cac_bits); - //testing purposes -- don't run facch steals on enc -- causing issues with key loader (VCALL_ASSGN_DUP) - if (state->nxdn_cipher_type == 0 || state->R == 0) //!voice - { - if (facch & 1) nxdn_deperm_facch(opts, state, facch_bits_a); - if (facch & 2) nxdn_deperm_facch(opts, state, facch_bits_b); - } + //Seperated UDCH user data from facch2 data + if (udch) nxdn_deperm_facch2_udch(opts, state, facch2_bits, 0); + if (facch2) nxdn_deperm_facch2_udch(opts, state, facch2_bits, 1); + + //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) { @@ -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); //update last voice sync time 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 nxdn_voice (opts, state, voice, dbuf); } + //close MBE file if no voice and its open if (!voice) { 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 { 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 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 }