diff --git a/include/dsd.h b/include/dsd.h index 1233a7e..4f2b9cf 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -645,7 +645,12 @@ typedef struct uint8_t nxdn_sacch_frame_segment[4][18]; //part of frame by 18 bits uint8_t nxdn_sacch_frame_segcrc[4]; uint8_t nxdn_alias_block_number; - char nxdn_alias_block_segment[4][4][8]; + char nxdn_alias_block_segment[4][4][8]; + //site/srv/cch info + char nxdn_location_category[14]; + uint32_t nxdn_location_sys_code; + uint16_t nxdn_location_site_code; + //Roman DMR End Call Alert Beep int dmr_end_alert[2]; //dmr TLC end call alert beep has already played once flag diff --git a/src/dsd_main.c b/src/dsd_main.c index 4bd7055..5ae4ae6 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -182,11 +182,6 @@ noCarrier (dsd_opts * opts, dsd_state * state) state->payload_keyid = 0; state->payload_keyidR = 0; - // state->dmr_fid = 0; - // state->dmr_so = 0; - // state->dmr_fidR = 0; - // state->dmr_soR = 0; - state->HYTL = 0; state->HYTR = 0; state->DMRvcL = 0; @@ -228,9 +223,6 @@ noCarrier (dsd_opts * opts, dsd_state * state) state->data_p_head[0] = 0; state->data_p_head[1] = 0; - // state->dmr_so = 0; //let TLC or Voice LC/Burst zero or set this instead? - // state->dmr_soR = 0; - state->dmr_encL = 0; state->dmr_encR = 0; @@ -259,6 +251,7 @@ noCarrier (dsd_opts * opts, dsd_state * state) memset (state->nxdn_sacch_frame_segment, 0, sizeof(state->nxdn_sacch_frame_segment)); state->nxdn_alias_block_number = 0; memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment)); + sprintf (state->nxdn_call_type, "%s", ""); } @@ -710,6 +703,10 @@ initState (dsd_state * state) memset (state->nxdn_sacch_frame_segment, 0, sizeof(state->nxdn_sacch_frame_segment)); state->nxdn_alias_block_number = 0; memset (state->nxdn_alias_block_segment, 0, sizeof(state->nxdn_alias_block_segment)); + //site/srv/cch info + state->nxdn_location_site_code = 0; + state->nxdn_location_sys_code = 0; + sprintf (state->nxdn_location_category, "%s", " "); //Roman DMR End Call Alert Beep state->dmr_end_alert[0] = 0; @@ -839,7 +836,7 @@ usage () printf (" (See lcn.csv for example)\n"); printf (" -2 Import Group List Allow/Block and Label from csv file (numeral 'two')\n"); printf (" (See group.csv for example)\n"); - printf (" -3 Enable Experimental Trunking Features (P25/EDACS for now) with RIGCTL/TCP or RTL Input\n"); + printf (" -3 Enable Extremely Experimental Trunking Features (NXDN/P25/EDACS for now) with RIGCTL/TCP or RTL Input\n"); printf (" -5 Enable RIGCTL/TCP; Set UDP Port for RIGCTL. (4532 on SDR++)\n"); //printf (" (Currently only available on UDP port 4532)\n"); printf ("\n"); diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 2e4e9a8..d8ae440 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -2125,15 +2125,50 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } //NXDN - //if (lls == 8 || lls == 9 || lls == 16 || lls == 17 || lls == 28 || lls == 29) if (lls == 28 || lls == 29) { - printw ("| RAN: [%2d] ", rn); + + if (opts->p25_trunk == 1) + { + + printw ("| "); + if (opts->p25_is_tuned == 0) + { + printw ("Monitoring Control Channel"); + if (state->p25_cc_freq != 0) + { + printw (" - CC Freq: [%.06lf] Mhz ", (double)state->p25_cc_freq/1000000); + } + } + else if (opts->p25_is_tuned == 1) + { + printw ("Monitoring Voice Channel"); + if (state->p25_vc_freq[0] != 0) + { + printw (" - VC Freq: [%.06lf] Mhz ", (double)state->p25_vc_freq[0]/1000000); + } + } + + printw ("\n"); + + } + + printw ("| "); + printw ("NXDN - RAN: [%2d] ", rn); + if (state->nxdn_location_site_code != 0) + { + printw ("Cat [%s] ", state->nxdn_location_category); + printw ("Sys Code [%d] ", state->nxdn_location_sys_code); + printw ("Site Code [%d] ", state->nxdn_location_site_code); + } + + printw ("\n"); + printw ("| "); printw ("TGT: [%4d] ", tgn); printw ("SRC: [%4d] ", src); - if (state->nxdn_alias_block_segment[0][0] > 0) //could not display if this segment is bad + if (state->nxdn_alias_block_segment[0][0] > 0) { - printw ("["); + printw ("ALIAS: ["); for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) @@ -2143,10 +2178,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } printw ("]"); } - printw ("\n| ALG: [0x%02X] KEY: [0x%02X] ", state->nxdn_cipher_type, state->nxdn_key); if (state->carrier == 1) { - printw("%s ", state->nxdn_call_type); + printw(" %s ", state->nxdn_call_type); + } + printw ("\n|"); + if (state->nxdn_cipher_type > 0) + { + printw (" ALG: [0x%02X] KEY: [0x%02X] ", state->nxdn_cipher_type, state->nxdn_key); } if (state->nxdn_cipher_type == 0x1 && state->carrier == 1) { @@ -2156,7 +2195,6 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) attron(COLOR_PAIR(3)); if (state->R != 0) { - //printw ("\n| "); attron(COLOR_PAIR(1)); printw ("KEY VALUE: [%05lld] ", state->R ); printw ("SEED: [%04llX]", state->payload_miN); @@ -2165,6 +2203,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } if (state->nxdn_cipher_type == 0x2 && state->carrier == 1) { + printw ("IV: [%016llX]", state->payload_miN); attron(COLOR_PAIR(2)); printw ("DES-OFB "); attroff(COLOR_PAIR(2)); @@ -2172,6 +2211,7 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } if (state->nxdn_cipher_type == 0x3 && state->carrier == 1) { + printw ("IV: [%016llX]", state->payload_miN); attron(COLOR_PAIR(2)); printw ("AES-256 "); attroff(COLOR_PAIR(2)); @@ -2184,11 +2224,10 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) attroff(COLOR_PAIR(2)); attron(COLOR_PAIR(3)); } - //printw("%s ", state->nxdn_call_type); printw("\n"); } - //DMR BS/MS Voice and Data Types + //P25 and DMR BS/MS if ( lls == 0 || lls == 1 || lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 32 || lls == 33 || lls == 34 || lls == 35 || lls == 36) { @@ -2738,10 +2777,32 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } refresh(); - if (c == 27) //esc key + + //keyboard shortcuts - codes same as ascii codes + if (c == 27) //esc key, open menu { ncursesMenu (opts, state); //just a quick test } + if (c == 99) //'c' key, toggle compact mode + { + if (opts->ncurses_compact == 1) opts->ncurses_compact = 0; + else opts->ncurses_compact = 1; + } + if (c == 116) //'t' key, toggle trunking + { + if (opts->p25_trunk == 1) opts->p25_trunk = 0; + else opts->p25_trunk = 1; + } + if (c == 97) //'a' key, toggle call alert beep + { + if (opts->call_alert == 1) opts->call_alert = 0; + else opts->call_alert = 1; + } + if (c == 104) //'h' key, toggle history + { + if (opts->ncurses_history == 1) opts->ncurses_history = 0; + else opts->ncurses_history = 1; + } } //end ncursesPrinter diff --git a/src/nxdn_convolution.c b/src/nxdn_convolution.c index a3b280e..5d53435 100644 --- a/src/nxdn_convolution.c +++ b/src/nxdn_convolution.c @@ -59,17 +59,6 @@ static uint64_t * m_dp = NULL; /* Functions ----------------------------------------------------------------*/ -void CNXDNConvolution_start(void) -{ - memset(m_metrics1, 0x00U, CNXDNConvolution_NUM_OF_STATES * sizeof(uint16_t)); - memset(m_metrics2, 0x00U, CNXDNConvolution_NUM_OF_STATES * sizeof(uint16_t)); - - m_oldMetrics = m_metrics1; - m_newMetrics = m_metrics2; - m_dp = m_decisions; -} - - void CNXDNConvolution_decode(uint8_t s0, uint8_t s1) { uint8_t i = 0; @@ -167,3 +156,15 @@ void CNXDNConvolution_encode(const unsigned char* in, unsigned char* out, unsign } } +void CNXDNConvolution_start(void) +{ + memset(m_metrics1, 0x00U, CNXDNConvolution_NUM_OF_STATES * sizeof(uint16_t)); + memset(m_metrics2, 0x00U, CNXDNConvolution_NUM_OF_STATES * sizeof(uint16_t)); + + m_oldMetrics = m_metrics1; + m_newMetrics = m_metrics2; + m_dp = m_decisions; + + for (int x = 0; x < 4; x++) CNXDNConvolution_decode(0U, 0U); +} + diff --git a/src/nxdn_deperm.c b/src/nxdn_deperm.c index 35f0b89..b956f42 100644 --- a/src/nxdn_deperm.c +++ b/src/nxdn_deperm.c @@ -1,5 +1,6 @@ //NXDN descramble/deperm/depuncture and utility functions //Reworked portions from Osmocom OP25 + /* -*- c++ -*- */ /* * NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI @@ -52,24 +53,65 @@ void nxdn_deperm_facch(dsd_opts * opts, dsd_state * state, uint8_t bits[144]) { uint8_t deperm[144]; uint8_t depunc[192]; - uint8_t trellis_buf[92]; + uint8_t trellis_buf[96]; uint16_t crc = 0; //crc calculated by function uint16_t check = 0; //crc from payload for comparison int out; char buf[128]; - uint8_t answer[12]; for (int i=0; i<144; i++) deperm[PERM_16_9[i]] = bits[i]; out = 0; for (int i=0; i<144; i+=3) { depunc[out++] = deperm[i+0]; - depunc[out++] = 0; //0 + depunc[out++] = 0; depunc[out++] = deperm[i+1]; depunc[out++] = deperm[i+2]; } - trellis_decode(trellis_buf, depunc, 92); + //trellis_decode(trellis_buf, depunc, 92); + + //switch to the convolutional decoder + uint8_t temp[210]; + uint8_t s0; + uint8_t s1; + uint8_t m_data[13]; + memset (temp, 0, sizeof(temp)); + memset (m_data, 0, sizeof(m_data)); + memset (trellis_buf, 0, sizeof(trellis_buf)); + + 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; + } //load tail 16 bits into check variable to compare vs computed crc value for (int i = 0; i < 12; i++) @@ -79,33 +121,31 @@ void nxdn_deperm_facch(dsd_opts * opts, dsd_state * state, uint8_t bits[144]) } crc = crc12f (trellis_buf, 80); - cfill(answer, trellis_buf, 12); //<-92 bits in 12 bytes, leaves 4 bits on tail to scrub off + + if (crc == check) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf); + //else if (opts->payload == 1) NXDN_Elements_Content_decode(opts, state, 0, trellis_buf); + if (opts->payload == 1) { fprintf (stderr, "\n"); fprintf (stderr, " FACCH Payload "); - answer[11] = answer[11] & 0xF0; //scrub the garbage 4 bits from cfill on the end for ppayload print for (int i = 0; i < 12; i++) { - fprintf (stderr, "[%02X]", answer[i]); + fprintf (stderr, "[%02X]", m_data[i]); } - fprintf (stderr, " - %03X %03X", crc, check); - } - if (crc == check) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf); - //only run data with bad CRC when payload is enabled and hide the ugly - else if (opts->payload == 1) NXDN_Elements_Content_decode(opts, state, 0, trellis_buf); } +//new sacch void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60]) { uint8_t deperm[60]; uint8_t depunc[72]; uint8_t trellis_buf[32]; uint8_t answer[26]; - int o=0; + int o = 0; uint8_t crc = 0; //value computed by crc6 on payload uint8_t check = 0; //value pulled from last 6 bits int sf = 0; @@ -120,16 +160,60 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60]) depunc[o++] = deperm[p+2]; depunc[o++] = deperm[p+3]; depunc[o++] = deperm[p+4]; - depunc[o++] = 0; //0 + 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; //0 + depunc[o++] = 0; } - trellis_decode(trellis_buf, depunc, 32); + //trellis_decode(trellis_buf, depunc, 32); + + //switch to the convolutional decoder + uint8_t temp[90]; + uint8_t s0; + uint8_t s1; + uint8_t m_data[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 < 40U; i++) + { + s0 = temp[(2*i)]; + s1 = temp[(2*i)+1]; + + CNXDNConvolution_decode(s0, s1); + } + + //stored as 5 bytes, will need to convert to trellis_buf after running + CNXDNConvolution_chainback(m_data, 36U); + + 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 = crc6(trellis_buf, 32); for (int i = 0; i < 6; i++) @@ -138,59 +222,89 @@ void nxdn_deperm_sacch(dsd_opts * opts, dsd_state * state, uint8_t bits[60]) check = check + trellis_buf[i+26]; } - //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; - else if (sf == 0) part_of_frame = 3; - else part_of_frame = 0; - - //gives unusual results when non_superframe SF, not sure why, or if its accurate yet - if (state->nxdn_sacch_non_superframe == FALSE) + //FIRST! If part of a non_superframe, and CRC is good, send directly to NXDN_Elements_Content_decode + if (state->nxdn_sacch_non_superframe == TRUE) { - fprintf (stderr, "%s", KCYN); - fprintf (stderr, " RAN %02d ", state->nxdn_last_ran); - //fprintf (stderr, " RAN %02d - SF %01d - POF %02d", ran, sf, part_of_frame); - fprintf (stderr, "%s", KNRM); + if (state->nxdn_last_ran != -1) fprintf (stderr, " RAN %02d ", state->nxdn_last_ran); + else fprintf (stderr, " "); + + uint8_t nsf_sacch[26]; + memset (nsf_sacch, 0, sizeof(nsf_sacch)); + for (int i = 0; i < 26; i++) + { + nsf_sacch[i] = trellis_buf[i+8]; + } + + if (crc == 0) NXDN_Elements_Content_decode(opts, state, 1, nsf_sacch); + + if (opts->payload == 1) + { + fprintf (stderr, "\n SACCH NSF "); + for (int i = 0; i < 5; i++) + { + fprintf (stderr, "[%02X]", m_data[i]); + } + if (crc != 0) fprintf (stderr, " CRC ERR - %02X %02X", crc, check); + } + } - else fprintf (stderr, " "); - //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 == 0 && state->nxdn_sacch_non_superframe == FALSE) //only set these when superframe type, or get odd results (can't verify correct) + //If part of superframe, collect the fragments and send to NXDN_SACCH_Full_decode instead + else if (state->nxdn_sacch_non_superframe == FALSE) { - state->nxdn_ran = state->nxdn_last_ran = ran; - state->nxdn_sf = sf; - state->nxdn_part_of_frame = part_of_frame; - state->nxdn_sacch_frame_segcrc[part_of_frame] = 0; //zero indicates good check - } - else state->nxdn_sacch_frame_segcrc[part_of_frame] = 1; //1 indicates bad check + //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; + else if (sf == 0) part_of_frame = 3; + else part_of_frame = 0; - int sacch_segment = 0; + fprintf (stderr, "%s", KCYN); + if (state->nxdn_last_ran != -1) fprintf (stderr, " RAN %02d ", state->nxdn_last_ran); + else fprintf (stderr, " "); + 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; - for (int i = 0; i < 18; i++) - { - sacch_segment = sacch_segment << 1; - sacch_segment = sacch_segment + trellis_buf[i+8]; - state->nxdn_sacch_frame_segment[part_of_frame][i] = trellis_buf[i+8]; - } + if (crc == 0) + { + state->nxdn_ran = state->nxdn_last_ran = ran; + state->nxdn_sf = sf; + state->nxdn_part_of_frame = part_of_frame; + state->nxdn_sacch_frame_segcrc[part_of_frame] = 0; //zero indicates good check + } + else state->nxdn_sacch_frame_segcrc[part_of_frame] = 1; //1 indicates bad check - //Hand off to LEH NXDN SACCH handling - if (part_of_frame == 3 && state->nxdn_sacch_non_superframe == FALSE) - { - NXDN_SACCH_Full_decode (opts, state); - } + int sacch_segment = 0; - if (opts->payload == 1) - { - fprintf (stderr, "\n"); - fprintf (stderr, " SACCH Segment #%d - %05X", part_of_frame+1, sacch_segment); - if (crc != 0) fprintf (stderr, " CRC ERR - %02X %02X", crc, check); - } + for (int i = 0; i < 18; i++) + { + sacch_segment = sacch_segment << 1; + sacch_segment = sacch_segment + trellis_buf[i+8]; + state->nxdn_sacch_frame_segment[part_of_frame][i] = trellis_buf[i+8]; + } + + //Hand off to LEH NXDN_SACCH_Full_decode + if (part_of_frame == 3) + { + NXDN_SACCH_Full_decode (opts, state); + } + + if (opts->payload == 1) + { + fprintf (stderr, "\n"); + fprintf (stderr, " SACCH SF Segment #%d ", part_of_frame+1); + for (int i = 0; i < 5; i++) + { + fprintf (stderr, "[%02X]", m_data[i]); + } + if (crc != 0) fprintf (stderr, " CRC ERR - %02X %02X", crc, check); + } + } } @@ -222,23 +336,68 @@ void nxdn_deperm_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t bits[34 depunc[id++] = deperm[i*12+10]; depunc[id++] = deperm[i*12+11]; } - trellis_decode(trellis_buf, depunc, 199); - crc = crc15(trellis_buf, 199); + + //trellis_decode(trellis_buf, depunc, 199); - uint8_t MessageType = 0; - for (int i = 0; i < 6; i++) + //switch to the convolutional decoder + uint8_t temp[220]; + uint8_t s0; + uint8_t s1; + uint8_t m_data[26]; + memset (trellis_buf, 0, sizeof(trellis_buf)); + memset (temp, 0, sizeof (temp)); + memset (m_data, 0, sizeof (m_data)); + + for (int i = 0; i < 406; i++) { - MessageType = MessageType << 1; - MessageType = MessageType + trellis_buf[i+2]; //on +2 on any facch1 + temp[i] = depunc[i] << 1; } - fprintf (stderr, " FACCH2/UDCH "); - if (crc == 0) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf); - else + for (int i = 0; i < 8; i++) { - fprintf (stderr, "CRC ERR "); - //NXDN_Elements_Content_decode(opts, state, 0, trellis_buf); - } + temp[i+203] = 0; + } + + CNXDNConvolution_start(); + for (int i = 0U; i < 207U; i++) + { + s0 = temp[(2*i)]; + s1 = temp[(2*i)+1]; + + CNXDNConvolution_decode(s0, s1); + } + + //numerals seem okay now + CNXDNConvolution_chainback(m_data, 203U); + + for(int i = 0; i < 26; 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 = crc15(trellis_buf, 199); + + fprintf (stderr, " F2/U "); + if (crc == 0) NXDN_Elements_Content_decode(opts, state, 1, trellis_buf); + + if (opts->payload == 1) + { + fprintf (stderr, "\n"); + fprintf (stderr, " F2/U Payload\n "); + for (int i = 0; i < 26; i++) + { + fprintf (stderr, "[%02X]", m_data[i]); + if (i == 12) fprintf (stderr, "\n "); + } + if (crc != 0) fprintf (stderr, " CRC ERR "); + } } @@ -247,7 +406,7 @@ void nxdn_deperm_cac(dsd_opts * opts, dsd_state * state, uint8_t bits[300]) uint8_t deperm[300]; uint8_t depunc[350]; - uint8_t decode[171]; + uint8_t trellis_buf[171]; int id = 0; uint16_t crc = 0; @@ -270,57 +429,108 @@ void nxdn_deperm_cac(dsd_opts * opts, dsd_state * state, uint8_t bits[300]) depunc[id++] = deperm[i*12+10]; depunc[id++] = deperm[i*12+11]; } - trellis_decode(decode, depunc, 171); - crc = crc16cac(decode, 171); - fprintf (stderr, " CAC "); - - //these messages can also be double depending on CAC type, so need to check that first, and split the - //decode first, then we can run each message type and element decode if necessary + //trellis_decode(trellis_buf, depunc, 171); + + //switch to the convolutional decoder + uint8_t temp[360]; + uint8_t s0; + uint8_t s1; + uint8_t m_data[26]; + memset (trellis_buf, 0, sizeof(trellis_buf)); + memset (temp, 0, sizeof (temp)); + memset (m_data, 0, sizeof (m_data)); + + for (int i = 0; i < 350; i++) + { + temp[i] = depunc[i] << 1; + } + + for (int i = 0; i < 8; i++) + { + temp[i+350] = 0; + } + + CNXDNConvolution_start(); + for (int i = 0U; i < 179U; i++) + { + s0 = temp[(2*i)]; + s1 = temp[(2*i)+1]; + + CNXDNConvolution_decode(s0, s1); + } + + CNXDNConvolution_chainback(m_data, 175U); + + for(int i = 0; i < 22; 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 = crc16cac(trellis_buf, 171); + + fprintf (stderr, " CAC "); + + //message type will probably be neccesary beforehand on single/dual meessage runs + //run message, check len, load into a seperate buffer, send to element, do same with other piece? uint8_t MessageType = 0; for (int i = 0; i < 6; i++) { MessageType = MessageType << 1; - MessageType = MessageType + decode[i+2]; //double check this on CAC + MessageType = MessageType + trellis_buf[i+2]; //double check this on CAC } - //nxdn_message_type (opts, state, MessageType); + uint8_t cac_message_buffer[171]; + memset (cac_message_buffer, 0, sizeof(cac_message_buffer)); - //not sure if CAC will contain same type of 'elements' as a FACCH or SACCH - //or if its purely for Control Signalling - - //need to check CAC type, CAC can contain two messages, or one long message, - //then we need to break up the messages and process them individually - if (crc == 0) NXDN_Elements_Content_decode(opts, state, 1, decode); - else + //shift the cac_message into the appropriate byte arrangement for element_decoder + for (int i = 0; i < 160; i++) //in future, we can use this to send multiple messages to decoder, if present { - fprintf (stderr, " CRC ERR "); - //NXDN_Elements_Content_decode(opts, state, 0, decode); + cac_message_buffer[i] = trellis_buf[i+8]; + } + + if (crc == 0) NXDN_Elements_Content_decode(opts, state, 1, cac_message_buffer); + + if (opts->payload == 1) + { + fprintf (stderr, "\n"); + fprintf (stderr, " CAC Payload\n "); + for (int i = 0; i < 22; i++) + { + fprintf (stderr, "[%02X]", m_data[i]); + if (i == 10) fprintf (stderr, "\n "); + } + if (crc != 0) fprintf (stderr, " CRC ERR "); + } } -//its own function now to consolidate all the message types to one area to keep track of -//may need to add a 'source' of message so we don't call messages not associated with cac, facch, sacch, etc void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType) { - //if (opts->payload == 1) fprintf (stderr, " M%02X ", MessageType); fprintf (stderr, "%s", KYEL); if (MessageType == 0x10) fprintf(stderr, " IDLE"); + else if (MessageType == 0x11) fprintf(stderr, " DISC"); //disconnect else if (MessageType == 0x01) fprintf(stderr, " VCALL"); 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"); - - //CAC only? valid in all, or just CAC? - else if (MessageType == 0x04) fprintf(stderr, " VCALL ASSGN"); - else if (MessageType == 0x0E) fprintf(stderr, " DCALL ASSGN"); + else if (MessageType == 0x04) fprintf(stderr, " VCALL_ASSGN"); + else if (MessageType == 0x05) fprintf(stderr, " VCALL_ASSGN_DUP"); + else if (MessageType == 0x0E) fprintf(stderr, " DCALL_ASSGN"); else if (MessageType == 0x18) fprintf(stderr, " SITE_INFO"); - else if (MessageType == 0x19) fprintf(stderr, " SRV_INFO"); //per Op25 - else if (MessageType == 0x1C) fprintf(stderr, " FAIL_STAT_INFO"); //per the damn manual + 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 == 0x1B) fprintf(stderr, " ADJ_SITE_INFO"); else if (MessageType == 0x20) fprintf(stderr, " REG_RESP"); @@ -331,20 +541,23 @@ 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"); - //end CAC only? - else if (MessageType == 0x3F) fprintf(stderr, " ALIAS"); //observed from DSDPlus and OP25 dumps - else if (MessageType == 0x0C) fprintf(stderr, " DCALL_ACK"); //need to verify these for accuracy vs DSDPlus - else fprintf(stderr, " Unknown M%02X", MessageType); + 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); //zero out stale values so they won't persist after a transmit release - if (MessageType == 0x08 || MessageType == 0x10) + //disable if random messages wipe out the alias + if (MessageType == 0x08 || MessageType == 0x10) //idle, tx_rel { 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, 0, sizeof(state->nxdn_sacch_frame_segment)); + sprintf (state->nxdn_call_type, "%s", ""); } } diff --git a/src/nxdn_element.c b/src/nxdn_element.c index a128605..101d745 100644 --- a/src/nxdn_element.c +++ b/src/nxdn_element.c @@ -12,53 +12,6 @@ #include "dsd.h" -#define NXDN_VCALL 0b000001 /* VCALL = VCALL_REQ = VCALL_RESP */ -#define NXDN_VCALL_IV 0b000011 -#define NXDN_DCALL_HDR 0b001001 /* Header Format - NXDN_DCALL_HDR = NXDN_DCALL_REQ = NXDN_DCALL_RESP */ -#define NXDN_DCALL_USR 0b001011 /* User Data Format */ -#define NXDN_DCALL_ACK 0b001100 -#define NXDN_REL_EX 0b000111 -#define NXDN_HEAD_DLY 0b001111 -#define NXDN_SDCALL_REQ_HDR 0b111000 /* Header Format */ -#define NXDN_SDCALL_REQ_USR 0b111001 /* User Data Format */ -#define NXDN_SDCALL_RESP 0b111011 -#define NXDN_SDCALL_IV 0b111010 -#define NXDN_STAT_INQ_REQ 0b110000 -#define NXDN_STAT_INQ_RESP 0b110001 -#define NXDN_STAT_REQ 0b110010 -#define NXDN_STAT_RESP 0b110011 -#define NXDN_REM_CON_REQ 0b110100 -#define NXDN_REM_CON_RESP 0b110101 -#define NXDN_REM_CON_E_REQ 0b110110 -#define NXDN_REM_CON_E_RESP 0b110111 -//NXDN_VCALL_REQ = 0b000001, /* VCALL = VCALL_REQ = VCALL_RESP */ -//NXDN_VCALL_RESP = 0b000001, /* VCALL = VCALL_REQ = VCALL_RESP */ -#define NXDN_VCALL_REC_REQ = 0b000010 /* NXDN_VCALL_REC_REQ = NXDN_VCALL_REC_RESP */ -//NXDN_VCALL_REC_RESP = 0b000010, -#define NXDN_VCALL_CONN_REQ = 0b000011 /* NXDN_VCALL_CONN_REQ = NXDN_VCALL_CONN_RESP */ -//NXDN_VCALL_CONN_RESP = 0b000011, /* NXDN_VCALL_CONN_REQ = NXDN_VCALL_CONN_RESP */ -#define NXDN_VCALL_ASSGN_DUP = 0b000101 -//NXDN_DCALL_REQ = 0b001001, /* NXDN_DCALL_HDR = NXDN_DCALL_REQ = NXDN_DCALL_RESP */ -//NXDN_DCALL_RESP = 0b001001, /* NXDN_DCALL_HDR = NXDN_DCALL_REQ = NXDN_DCALL_RESP */ -#define NXDN_DCALL_REC_REQ = 0b001010 /* NXDN_DCALL_REC_REQ = NXDN_DCALL_REC_RESP */ -//NXDN_DCALL_REC_RESP = 0b001010, /* NXDN_DCALL_REC_REQ = NXDN_DCALL_REC_RESP */ -#define NXDN_DCALL_ASSGN = 0b001110 -#define NXDN_DCALL_ASSGN_DUP = 0b001101 -#define NXDN_IDLE 0b010000 -#define NXDN_DISC_REQ = 0b010001 /* NXDN_DISC_REQ = NXDN_DISC */ -#define NXDN_DISC = 0b010001 /* NXDN_DISC_REQ = NXDN_DISC */ - -/* - * @brief : This function decodes the full SACCH (when 4 voice frame parts - * have been successfully received) - * - * @param opts : Option structure parameters pointer - * - * @param state : State structure parameters pointer - * - * @return None - * - */ void NXDN_SACCH_Full_decode(dsd_opts * opts, dsd_state * state) { uint8_t SACCH[72]; @@ -78,8 +31,8 @@ void NXDN_SACCH_Full_decode(dsd_opts * opts, dsd_state * state) } /* Decodes the element content */ - //run it under crccorrect, or under payload (if incorrect, hide bad data unless payload enabled) - if (CrcCorrect == 1 || opts->payload == 1) NXDN_Elements_Content_decode(opts, state, CrcCorrect, SACCH); + // currently only going to run this if all four CRCs are good + if (CrcCorrect == 1) NXDN_Elements_Content_decode(opts, state, CrcCorrect, SACCH); } /* End NXDN_SACCH_Full_decode() */ @@ -112,23 +65,49 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, /* Decode the right "Message Type" */ switch(MessageType) { + //VCALL_ASSGN + case 0x04: + //continue flow to DUP, both use same message format + + //VCALL_ASSGN_DUP + case 0x05: + NXDN_decode_VCALL_ASSGN(opts, state, ElementsContent); + break; + //Alias 0x3F case 0x3F: - { + state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect; NXDN_decode_Alias(opts, state, ElementsContent); - } - break; + break; + + //SRV_INFO + case 0x19: + NXDN_decode_srv_info(opts, state, ElementsContent); + break; + + //CCH_INFO + case 0x1A: + NXDN_decode_cch_info(opts, state, ElementsContent); + break; + + //SITE_INFO + case 0x18: + NXDN_decode_site_info(opts, state, ElementsContent); + break; + + //DISC + case 0x11: + //NXDN_decode_VCALL(opts, state, ElementsContent); + break; //Idle - case NXDN_IDLE: + case 0x10: { - //sprintf (state->nxdn_call_type, "NXDN IDLE "); break; } - /* VCALL */ - case NXDN_VCALL: + case 0x01: { /* Set the CRC state */ @@ -147,7 +126,7 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, } /* End case NXDN_VCALL: */ /* VCALL_IV */ - case NXDN_VCALL_IV: + case 0x03: { /* Set the CRC state */ @@ -181,13 +160,131 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, /* Unknown Message Type */ default: { - //fprintf(stderr, "Unknown Message type "); break; } } /* End switch(MessageType) */ } /* End NXDN_Elements_Content_decode() */ +void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Message) +{ + //just using 'short form' M only data, not the optional data + uint8_t CCOption = 0; + uint8_t CallType = 0; + uint8_t VoiceCallOption = 0; + uint16_t SourceUnitID = 0; + uint16_t DestinationID = 0; + uint8_t CallTimer = 0; + uint16_t Channel = 0; + uint8_t LocationIDOption = 0; + + uint8_t DuplexMode[32] = {0}; + uint8_t TransmissionMode[32] = {0}; + + /* Decode "CC Option" */ + CCOption = (uint8_t)ConvertBitIntoBytes(&Message[8], 8); + state->NxdnElementsContent.CCOption = CCOption; + + /* Decode "Call Type" */ + CallType = (uint8_t)ConvertBitIntoBytes(&Message[16], 3); + state->NxdnElementsContent.CallType = CallType; + + /* Decode "Voice Call Option" */ + VoiceCallOption = (uint8_t)ConvertBitIntoBytes(&Message[19], 5); + state->NxdnElementsContent.VoiceCallOption = VoiceCallOption; + + /* Decode "Source Unit ID" */ + SourceUnitID = (uint16_t)ConvertBitIntoBytes(&Message[24], 16); + state->NxdnElementsContent.SourceUnitID = SourceUnitID; + + /* Decode "Destination ID" */ + DestinationID = (uint16_t)ConvertBitIntoBytes(&Message[40], 16); + state->NxdnElementsContent.DestinationID = DestinationID; + + /* Decode "Call Timer" */ //unsure of format of call timer, not required info for trunking + CallTimer = (uint8_t)ConvertBitIntoBytes(&Message[56], 6); + + /* Decode "Channel" */ + Channel = (uint16_t)ConvertBitIntoBytes(&Message[62], 10); + + /* Decode "Location ID Option" */ //is in the optional field, probably best not to run this one + //LocationIDOption = (uint8_t)ConvertBitIntoBytes(&Message[72], 5); + + /* 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)); + + /* Print the "Voice Call Option" */ + NXDN_Voice_Call_Option_To_Str(VoiceCallOption, DuplexMode, TransmissionMode); + fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); + + /* Print Source ID and Destination ID (Talk Group or Unit ID) */ + fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); + + /* Print Channel (Skipping Call Timer)*/ + fprintf(stderr, "- Channel [%03X][%04d] ", Channel & 0x3FF, Channel & 0x3FF); + + //run process to figure out frequency value from the channel, or from lcn import array + long int freq = 0; + freq = nxdn_channel_to_frequency(opts, Channel); + + //run group/source 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 + + for (int i = 0; i < state->group_tally; i++) + { + if (state->group_array[i].groupNumber == DestinationID) //source, or destination? + { + fprintf (stderr, " [%s]", state->group_array[i].groupName); + strcpy (mode, state->group_array[i].groupMode); + } + //might not be ideal if both source and group/target are both in the array + else if (state->group_array[i].groupNumber == SourceUnitID) //source, or destination? + { + fprintf (stderr, " [%s]", state->group_array[i].groupName); + strcpy (mode, state->group_array[i].groupMode); + } + } + + //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_is_tuned == 0 && freq != 0) //if we aren't already on a VC and have a valid frequency already + { + //rigctl + if (opts->use_rigctl == 1) + { + //may or may not use setmod here, let user control it instead? + if (opts->frame_nxdn48 == 1) SetModulation(opts->rigctl_sockfd, 6250); + else SetModulation(opts->rigctl_sockfd, 12500); + SetFreq(opts->rigctl_sockfd, freq); + state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq; + opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop + } + //rtl_udp + else if (opts->audio_in_type == 3) + { + rtl_udp_tune (opts, state, freq); + state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq; + 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); + sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType)); + } + } + + fprintf (stderr, "%s", KNRM); + +} /* End NXDN_decode_VCALL_ASSGN() */ + void NXDN_decode_Alias(dsd_opts * opts, dsd_state * state, uint8_t * Message) { uint8_t Alias1 = 0x0; //value of an ascii 'NULL' character @@ -195,8 +292,15 @@ void NXDN_decode_Alias(dsd_opts * opts, dsd_state * state, uint8_t * Message) uint8_t Alias3 = 0x0; uint8_t Alias4 = 0x0; uint8_t blocknumber = 0; - uint8_t CrcCorrect = 1; - state->NxdnElementsContent.VCallCrcIsGood = CrcCorrect; + uint8_t CrcCorrect = 0; + + //alias can also hit on a facch1 so that would be with a non_sf_sacch attached + if (state->nxdn_sacch_non_superframe == FALSE) + { + CrcCorrect = state->NxdnElementsContent.VCallCrcIsGood; + } + else CrcCorrect = 1; //FACCH1 with bad CRC won't make it this far anyways, so set as 1 + //FACCH Payload [3F][68][82][04][2 <- block number4] "[69][6F][6E][20]" <- 4 alias octets [00][7F][1C] blocknumber = (uint8_t)ConvertBitIntoBytes(&Message[32], 4) & 0x7; // & 0x7, might just be three bits, unsure @@ -226,9 +330,11 @@ void NXDN_decode_Alias(dsd_opts * opts, dsd_state * state, uint8_t * Message) else ; //sprintf (state->nxdn_alias_block_segment[blocknumber-1][3], "%c", 0); } - if (CrcCorrect) //was by blocknumber being 4, but wasn't as frequent as I would like in the console + //crc errs in one repitition may occlude an otherwise good alias, so test and change if needed + //completed alias should still appear in ncurses terminal regardless, so this may be okay + if (CrcCorrect) { - fprintf (stderr, " "); //probably just want a space instead + fprintf (stderr, " "); //spacer for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) @@ -243,25 +349,197 @@ void NXDN_decode_Alias(dsd_opts * opts, dsd_state * state, uint8_t * Message) void NXDN_decode_cch_info(dsd_opts * opts, dsd_state * state, uint8_t * Message) { - uint8_t location_id = 0; - uint8_t channel_ifno = 0; + uint32_t location_id = 0; + uint8_t channel1sts = 0; + uint16_t channel1 = 0; + uint8_t channel2sts = 0; + uint16_t channel2 = 0; + long int freq1 = 0; + long int freq2 = 0; + + location_id = (uint32_t)ConvertBitIntoBytes(&Message[8], 24); + channel1sts = (uint8_t)ConvertBitIntoBytes(&Message[32], 6); + channel1 = (uint16_t)ConvertBitIntoBytes(&Message[38], 10); + channel2sts = (uint8_t)ConvertBitIntoBytes(&Message[48], 6); + channel2 = (uint16_t)ConvertBitIntoBytes(&Message[54], 10); + + fprintf (stderr, "%s", KYEL); + nxdn_location_id_handler (state, location_id); + fprintf (stderr, "\n Control Channel Information \n"); + fprintf (stderr, " Location ID [%06X] CC1 [%03X][%04d] CC2 [%03X][%04d] Status: ", location_id, channel1, channel1, channel2, channel2); + + //check the sts bits to determine if current, new, add, or delete + if (channel1sts & 0x20) fprintf (stderr, "Current "); + else if (channel1sts & 0x10) fprintf (stderr, "New "); + else if (channel1sts & 0x8) fprintf (stderr, "Candidate Added "); + else if (channel1sts & 0x4) fprintf (stderr, "Candidate Deleted "); + freq1 = nxdn_channel_to_frequency (opts, channel1); + freq2 = nxdn_channel_to_frequency (opts, channel2); + + //add handling for adding (or deleting?) frequencies to CC list + if (channel1sts & 0x20 || channel1sts & 0x8) //current or new only + { + if (freq1 != 0) state->p25_cc_freq = freq1; + } + else if (channel1sts & 0x8) //Candidate Added + { + if (freq1 != 0) + { + //add to trunk_lcn_freq list? + for (int i = 0; i <= state->lcn_freq_count; i++) //<= pretty sure is correct here since we need to go through atleast one rep + { + if (freq1 == state->trunk_lcn_freq[i]) goto SKIP; + } + //assign if freq1 not found in array + state->trunk_lcn_freq[state->lcn_freq_count] = freq1; + state->lcn_freq_count++; + } + } + + //if a delete, let's not bother trying to remove it from the list for now + SKIP: ; //do nothing + //channel 2 should only be valid on 'current' status, but not sure what value it would contain vs channel 1 + fprintf (stderr, "%s", KNRM); +} + +void NXDN_decode_srv_info(dsd_opts * opts, dsd_state * state, uint8_t * Message) +{ + uint32_t location_id = 0; + uint16_t svc_info = 0; //service information + uint32_t rst_info = 0; //restriction information + + location_id = (uint32_t)ConvertBitIntoBytes(&Message[8], 24); + svc_info = (uint16_t)ConvertBitIntoBytes(&Message[32], 16); + rst_info = (uint32_t)ConvertBitIntoBytes(&Message[48], 24); + + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n Service Information - "); + fprintf (stderr, "Location ID [%06X] SVC [%04X] RST [%06X] ", location_id, svc_info, rst_info); + nxdn_location_id_handler (state, location_id); + fprintf (stderr, "%s", KNRM); + +} + +//externalize the location handler to its own function +void nxdn_location_id_handler (dsd_state * state, uint32_t location_id) +{ + //6.5.2 Location ID + uint8_t category_bit = location_id >> 22; + uint32_t sys_code = 0; + uint16_t site_code = 0; + + char category[14]; //G, R, or L + + if (category_bit == 0) + { + sys_code = ( (location_id & 0x3FFFFF) >> 12); //10 bits + site_code = location_id & 0x3FF; //12 bits + sprintf (category, "%s", "Global"); + } + else if (category_bit == 2) + { + sys_code = ( (location_id & 0x3FFFFF) >> 8); //14 bits + site_code = location_id & 0xFF; //8 bits + sprintf (category, "%s", "Regional"); + } + else if (category_bit == 1) + { + sys_code = ( (location_id & 0x3FFFFF) >> 5); //17 bits + site_code = location_id & 0x1F; //5 bits + sprintf (category, "%s", "Local"); + } + else + { + //err, or we shouldn't ever get here + sprintf (category, "%s", "Reserved/Err"); + } + + //not entirely convinced that site code and ran are the exact same thing due to bit len differences + //site code is meant to be a color code (but this could also be like the P25 p1 nac and p2 cc being the same) + state->nxdn_last_ran = site_code; //this one may drop on nocarrier + if (site_code != 0) state->nxdn_location_site_code = site_code; //this one won't + if (sys_code != 0) state->nxdn_location_sys_code = sys_code; + sprintf (state->nxdn_location_category, "%s", category); + + fprintf (stderr, "\n Location Information - Cat: %s - Sys Code: %d - Site Code %d ", category, sys_code, site_code); + +} + +void NXDN_decode_site_info(dsd_opts * opts, dsd_state * state, uint8_t * Message) +{ + uint32_t location_id = 0; + uint16_t cs_info = 0; //channel structure information + uint16_t svc_info = 0; //service information + uint32_t rst_info = 0; //restriction information + uint32_t ca_info = 0; //channel access information + uint8_t version_num = 0; + uint8_t adj_alloc = 0; //number of adjacent sites (on same system?) + + uint16_t channel1 = 0; + uint16_t channel2 = 0; + long int freq1 = 0; + long int freq2 = 0; + + location_id = (uint32_t)ConvertBitIntoBytes(&Message[8], 24); + cs_info = (uint16_t)ConvertBitIntoBytes(&Message[32], 16); + svc_info = (uint16_t)ConvertBitIntoBytes(&Message[48], 16); + rst_info = (uint32_t)ConvertBitIntoBytes(&Message[64], 24); + ca_info = (uint32_t)ConvertBitIntoBytes(&Message[88], 24); + version_num = (uint8_t)ConvertBitIntoBytes(&Message[112], 8); + adj_alloc = (uint8_t)ConvertBitIntoBytes(&Message[120], 4); + channel1 = (uint16_t)ConvertBitIntoBytes(&Message[124], 10); + channel2 = (uint16_t)ConvertBitIntoBytes(&Message[134], 10); + + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n Location ID [%06X] CSC [%04X] SVC [%04X] RST [%06X] \n CA [%06X] V[%X] ADJ [%01X] ", + location_id, cs_info, svc_info, rst_info, ca_info, version_num, adj_alloc); + nxdn_location_id_handler(state, location_id); + if (channel1 != 0) + { + fprintf (stderr, "\n Control Channel 1 [%03X][%04d] ", channel1, channel1 ); + freq1 = nxdn_channel_to_frequency (opts, channel1); + } + if (channel2 != 0) + { + fprintf (stderr, "\n Control Channel 2 [%03X][%04d] ", channel2, channel2 ); + freq2 = nxdn_channel_to_frequency (opts, channel2); + } + + fprintf (stderr, "%s", KNRM); + + //place the freqs into the array for hunting CC or + //rotating CCs without user LCN list + if (freq1 != 0) + { + for (int i = 0; i <= state->lcn_freq_count; i++) //<= pretty sure is correct here since we need to go through atleast one rep + { + if (freq1 == state->trunk_lcn_freq[i]) goto SKIP; + } + //assign if freq1 not found in array + state->trunk_lcn_freq[state->lcn_freq_count] = freq1; + state->lcn_freq_count++; + //shim here for main CC freq? + state->p25_cc_freq = freq1; + } + + if (freq2 != 0) + { + for (int i = 0; i <= state->lcn_freq_count; i++) //<= pretty sure is correct here since we need to go through atleast one rep + { + if (freq2 == state->trunk_lcn_freq[i]) goto SKIP; + } + //assign if freq2 not found in array + state->trunk_lcn_freq[state->lcn_freq_count] = freq2; + state->lcn_freq_count++; + } + + SKIP: ; //do nothing + } -/* - * @brief : This function decodes the VCALL message - * - * @param opts : Option structure parameters pointer - * - * @param state : State structure parameters pointer - * - * @param Message : A 64 bit buffer containing the VCALL message to decode - * - * @return None - * - */ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) { - //uint32_t i; + uint8_t CCOption = 0; uint8_t CallType = 0; uint8_t VoiceCallOption = 0; @@ -273,13 +551,6 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) uint8_t TransmissionMode[32] = {0}; unsigned long long int FullMessage = 0; - /* Message[0..7] contains : - * - The "F1" and "F2" flags - * - The "Message Type" (already decoded before calling this function) - * - * So no need to decode it a second time - */ - /* Decode "CC Option" */ CCOption = (uint8_t)ConvertBitIntoBytes(&Message[8], 8); state->NxdnElementsContent.CCOption = CCOption; @@ -334,7 +605,6 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) fprintf(stderr, "%s %s - ", DuplexMode, TransmissionMode); /* Print Source ID and Destination ID (Talk Group or Unit ID) */ - //fprintf (stderr, "%s", KGRN); fprintf(stderr, "Src=%u - Dst/TG=%u ", SourceUnitID & 0xFFFF, DestinationID & 0xFFFF); fprintf (stderr, "%s", KNRM); @@ -343,16 +613,13 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) { fprintf (stderr, "\n %s", KYEL); fprintf(stderr, "%s - ", NXDN_Cipher_Type_To_Str(CipherType)); - //state->nxdn_cipher_type = CipherType; } /* Print the Key ID */ if(CipherType != 0) { - fprintf(stderr, "Key ID %u - ", KeyID & 0xFF); fprintf (stderr, "%s", KNRM); - //state->nxdn_key = (KeyID & 0xFF); } if (state->nxdn_cipher_type == 0x01 && state->R > 0) //scrambler key value @@ -362,14 +629,15 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) fprintf (stderr, "%s", KNRM); } + //only grab if CRC is okay if(state->NxdnElementsContent.VCallCrcIsGood) { if ( (SourceUnitID & 0xFFFF) > 0 ) { - state->nxdn_last_rid = SourceUnitID & 0xFFFF; //only grab if CRC is okay + state->nxdn_last_rid = SourceUnitID & 0xFFFF; state->nxdn_last_tg = (DestinationID & 0xFFFF); state->nxdn_key = (KeyID & 0xFF); - state->nxdn_cipher_type = CipherType; //will this set to zero if no enc? + state->nxdn_cipher_type = CipherType; } } else @@ -390,32 +658,11 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message) } } /* End NXDN_decode_VCALL() */ - -/* - * @brief : This function decodes the VCALL_IV message - * - * @param opts : Option structure parameters pointer - * - * @param state : State structure parameters pointer - * - * @param Message : A 72 bit buffer containing the VCALL_IV message to decode - * - * @return None - * - */ void NXDN_decode_VCALL_IV(dsd_opts * opts, dsd_state * state, uint8_t * Message) { uint32_t i; - - /* Message[0..7] contains : - * - The "F1" and "F2" flags - * - The "Message Type" (already decoded before calling this function) - * - * So no need to decode it a second time - * - * Message[8..71] contains : The 64 bits IV - */ - state->payload_miN = 0; //zero out + state->payload_miN = 0; //zero out + /* Extract the IV from the VCALL_IV message */ for(i = 0; i < 8; i++) { @@ -430,15 +677,6 @@ void NXDN_decode_VCALL_IV(dsd_opts * opts, dsd_state * state, uint8_t * Message) } /* End NXDN_decode_VCALL_IV() */ -/* - * @brief : This function decodes the "Call Type" and return the - * ASCII string corresponding to it. - * - * @param CallType : The call type parameter to decode - * - * @return An ASCII string of the "Call Type" - * - */ char * NXDN_Call_Type_To_Str(uint8_t CallType) { char * Ptr = NULL; @@ -459,22 +697,6 @@ char * NXDN_Call_Type_To_Str(uint8_t CallType) return Ptr; } /* End NXDN_Call_Type_To_Str() */ - -/* - * @brief : This function decodes the "Voice Call Option" and return the - * ASCII string corresponding to it. - * - * @param VoiceCallOption : The call type parameter to decode - * - * @param Duplex : A 32 bytes ASCII buffer pointer where store - * the Duplex/Half duplex mode - * - * @param TransmissionMode : A 32 bytes ASCII buffer pointer where store - * the transmission mode (bit rate) - * - * @return An ASCII string of the "Voice Call Option" - * - */ void NXDN_Voice_Call_Option_To_Str(uint8_t VoiceCallOption, uint8_t * Duplex, uint8_t * TransmissionMode) { char * Ptr = NULL; @@ -496,15 +718,6 @@ void NXDN_Voice_Call_Option_To_Str(uint8_t VoiceCallOption, uint8_t * Duplex, ui strcpy((char *)TransmissionMode, Ptr); } /* End NXDN_Voice_Call_Option_To_Str() */ -/* - * @brief : This function decodes the "Cipher Type" and return the - * ASCII string corresponding to it. - * - * @param CipherType : The cipher type parameter to decode - * - * @return An ASCII string of the "Cipher Type" - * - */ char * NXDN_Cipher_Type_To_Str(uint8_t CipherType) { char * Ptr = NULL; diff --git a/src/nxdn_frame.c b/src/nxdn_frame.c index 591eef2..9adffa7 100644 --- a/src/nxdn_frame.c +++ b/src/nxdn_frame.c @@ -1,6 +1,6 @@ //NXDN frame handler -//Reworked portions from Osmocom OP25 -//from Osmocom OP25 rx_sync.cc +//Reworked portions from Osmocom OP25 rx_sync.cc + /* -*- c++ -*- */ /* * NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI @@ -59,7 +59,6 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) memset (dbuf, 0, sizeof(dbuf)); //collect lich bits first, if they are good, then we can collect the rest of them - //otherwise we will either exit, or skip and then exit (depends on performance) for (int i = 0; i < 8; i++) { lich_dibits[i] = dbuf[i] = getDibit(opts, state); @@ -150,8 +149,10 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) sacch = 1; break; default: - if (opts->payload == 1) fprintf(stderr, "unsupported NXDN lich type 0x%02X\n", lich); - //fprintf(stderr, " unsupported NXDN lich 0x%02X\n", lich); + if (opts->payload == 1) fprintf(stderr, " false sync or unsupported NXDN lich type 0x%02X\n", lich); + //reset the sacch field, we probably got a false sync and need to wipe or give a bad crc + memset (state->nxdn_sacch_frame_segment, 0, sizeof(state->nxdn_sacch_frame_segment)); + memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc)); voice = 0; goto END; break; @@ -239,21 +240,13 @@ void nxdn_frame (dsd_opts * opts, dsd_state * state) } - if (sacch) - { - state->nxdn_sacch_non_superframe = (lich == 0x20 || lich == 0x21 || lich == 0x61 || lich == 0x40 || lich == 0x41) ? true : false; - nxdn_deperm_sacch(opts, state, sacch_bits); - } + state->nxdn_sacch_non_superframe = (lich == 0x20 || lich == 0x21 || lich == 0x61 || lich == 0x40 || lich == 0x41) ? true : false; + 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 (facch & 1) nxdn_deperm_facch(opts, state, facch_bits_a); - if (facch & 2) - { - //don't run if duplicate of facch in first, may still run both if one has bad CRC - //hopefully can mitigate that later when we having the CRC function working properly for FACCH - if (facch_bits_a != facch_bits_b) nxdn_deperm_facch(opts, state, facch_bits_b); //don't think this check works correctly - } - if (voice) nxdn_voice (opts, state, voice, dbuf); //working great now with new simplified method! + if (facch & 2) nxdn_deperm_facch(opts, state, facch_bits_b); + if (voice) nxdn_voice (opts, state, voice, dbuf); fprintf (stderr, "\n"); END: ; //do nothing diff --git a/src/p25_frequency.c b/src/p25_frequency.c index bcb651c..0c3d177 100644 --- a/src/p25_frequency.c +++ b/src/p25_frequency.c @@ -2,12 +2,15 @@ * p25_frequency.c * P25 Channel to Frequency Calculator * + * NXDN Channel to Frequency, Courtesy of IcomIcR20 and EricCottrell (base values) + * * LWVMOBILE - * 2022-09 DSD-FME Florida Man Edition + * 2022-11 DSD-FME Florida Man Edition *-----------------------------------------------------------------------------*/ #include "dsd.h" +//P25 long int process_channel_to_freq (dsd_opts * opts, dsd_state * state, int channel) { @@ -41,4 +44,44 @@ long int process_channel_to_freq (dsd_opts * opts, dsd_state * state, int channe return (0); } +} + +//NXDN Channel to Frequency, Courtesy of IcomIcR20 on RR Forums +long int nxdn_channel_to_frequency(dsd_opts * opts, uint16_t channel) +{ + long int freq; + long int base = 0; + + //the base freq value is per EricCottrell in the Understanding NXDN Trunking Forum Thread + //will need to do some research or tests to confirm these values are accurate + + if ((channel > 0) && (channel <= 400)) + { + if (opts->frame_nxdn48 == 1) base = 450000000; + else base = 451000000; + + freq = base + (channel - 1) * 12500; + fprintf (stderr, "\n Frequency [%.6lf] MHz", (double)freq/1000000); + return (freq); + } + else if ((channel >= 401) && (channel <= 800)) + { + if (opts->frame_nxdn48 == 1) base = 460000000; + else base = 461000000; + + freq = base + (channel - 401) * 12500; + fprintf (stderr, "\n Frequency [%.6lf] MHz", (double)freq/1000000); + return (freq); + } + else if (channel > 800) + { + //write function to check the LCN import array for custom frequency values! + //return (freq); + } + else + { + fprintf(stderr, "\n This LCN is used for a non-standard frequency. No frequency can be reported"); + return (0); + } + } \ No newline at end of file