diff --git a/include/dsd.h b/include/dsd.h index a97a7b4..6f2efa7 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -1291,6 +1291,7 @@ uint16_t crc12f(const uint8_t buf[], int len); uint16_t crc15(const uint8_t buf[], int len); uint16_t crc16cac(const uint8_t buf[], int len); uint8_t crc7_scch(uint8_t bits[], int len); //converted from op25 crc6 +uint32_t nxdn_message_crc32(uint8_t * input, int len); //YSF Soft Decision Viterbi uint32_t ysf_soft_decision_viterbi(uint8_t * dbuf, int d_len, int num_bytes, int offset, uint8_t * viterbi_bits, uint8_t * viterbi_bytes); @@ -1320,6 +1321,7 @@ void NXDN_decode_VCALL(dsd_opts * opts, dsd_state * state, uint8_t * Message); void NXDN_decode_VCALL_IV(dsd_opts * opts, dsd_state * state, uint8_t * Message); char * NXDN_Call_Type_To_Str(uint8_t CallType); void NXDN_Voice_Call_Option_To_Str(uint8_t VoiceCallOption, uint8_t * Duplex, uint8_t * TransmissionMode); +void NXDN_Data_Call_Option_To_Str(uint8_t DataCallOption, uint8_t * Duplex, uint8_t * TransmissionMode); char * NXDN_Cipher_Type_To_Str(uint8_t CipherType); //added these void NXDN_decode_Prop(dsd_opts * opts, dsd_state * state, uint8_t * Message); @@ -1331,6 +1333,10 @@ 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_dst_info(dsd_opts * opts, dsd_state * state, uint8_t * Message); void NXDN_decode_adj_site(dsd_opts * opts, dsd_state * state, uint8_t * Message); +void nxdn_sdcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message); +void nxdn_dcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message); +void nxdn_dcall_iv(dsd_opts * opts, dsd_state * state, int type, uint8_t * Message); +int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, 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 NXDN_decode_VCALL_ARIB(dsd_opts * opts, dsd_state * state, uint8_t * Message); @@ -1610,6 +1616,9 @@ int udp_socket_connectA(dsd_opts * opts, dsd_state * state); void udp_socket_blaster(dsd_opts * opts, dsd_state * state, size_t nsam, void * data); void udp_socket_blasterA(dsd_opts * opts, dsd_state * state, size_t nsam, void * data); +//15-bit Scrambler +void pdu_scrambler_keystream_creation(uint8_t * ks, int lfsr, int len); + //RC4 function prototypes void rc4_voice_decrypt (int drop, uint8_t keylength, uint8_t messagelength, uint8_t key[], uint8_t cipher[], uint8_t plain[]); void rc4_block_output (int drop, int keylen, int meslen, uint8_t * key, uint8_t * output_blocks); @@ -1652,6 +1661,7 @@ void lfsr_64_to_128(uint8_t * iv); void LFSR128(dsd_state * state); void LFSR128n(dsd_state * state); void LFSR128d(dsd_state * state); +void LFSR128npdu(dsd_state * state); #ifdef __cplusplus diff --git a/src/nxdn_deperm.c b/src/nxdn_deperm.c index b2ffb9b..f3b480b 100644 --- a/src/nxdn_deperm.c +++ b/src/nxdn_deperm.c @@ -6,7 +6,7 @@ * NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI * * LWVMOBILE - * 2026-01 DSD-FME Florida Man Edition + * 2026-03 DSD-FME Florida Man Edition *-----------------------------------------------------------------------------*/ #include "dsd.h" @@ -77,7 +77,7 @@ void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType) 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 == 0x0B) fprintf(stderr, " DCALL_DATA"); 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"); @@ -107,7 +107,7 @@ void nxdn_message_type (dsd_opts * opts, dsd_state * state, uint8_t MessageType) else if (MessageType == 0x36) fprintf(stderr, " REM_CON_E_REQ"); else if (MessageType == 0x37) fprintf(stderr, " REM_CON_E_RESP"); else if (MessageType == 0x38) fprintf(stderr, " SDCALL_REQ_HEADER"); - else if (MessageType == 0x39) fprintf(stderr, " SDCALL_REQ_USERDATA"); + else if (MessageType == 0x39) fprintf(stderr, " SDCALL_REQ_DATA"); else if (MessageType == 0x3A) fprintf(stderr, " SDCALL_IV"); else if (MessageType == 0x3B) fprintf(stderr, " SDCALL_RESP"); @@ -270,6 +270,24 @@ uint16_t crc16cac(const uint8_t buf[], int len) return crc & 0xffff; } +uint32_t nxdn_message_crc32(uint8_t * input, int len) +{ + uint32_t crc = 0xFFFFFFFF; + uint32_t poly = 0x04C11DB7; + + for (int i = 0; i < len; i++) + { + + if( ((crc >> 31) & 1) ^ (input[i] & 1) ) + crc = (crc << 1) ^ poly; + else + crc <<= 1; + + } + + return crc; +} + uint8_t crc7_scch(uint8_t bits[], int len) { uint8_t s[7]; @@ -329,6 +347,58 @@ void LFSR128n(dsd_state * state) } +void pdu_scrambler_keystream_creation(uint8_t * ks, int lfsr, int len) +{ + int bit = 0; + for (int i = 0; i < len; i++) + { + ks[i] = lfsr & 0x1; + bit = ( (lfsr >> 1) ^ (lfsr >> 0) ) & 1; + lfsr = ( (lfsr >> 1 ) | (bit << 14) ); + } + +} + +void LFSR128npdu(dsd_state * state) +{ + //generate a 128-bit IV from a 64-bit IV for AES blocks + unsigned long long int lfsr = state->payload_mi; + + //start packing aes_ivR (for data in case of simultaneous voice and data call) + state->aes_ivR[0] = (lfsr >> 56) & 0xFF; + state->aes_ivR[1] = (lfsr >> 48) & 0xFF; + state->aes_ivR[2] = (lfsr >> 40) & 0xFF; + state->aes_ivR[3] = (lfsr >> 32) & 0xFF; + state->aes_ivR[4] = (lfsr >> 24) & 0xFF; + state->aes_ivR[5] = (lfsr >> 16) & 0xFF; + state->aes_ivR[6] = (lfsr >> 8 ) & 0xFF; + state->aes_ivR[7] = (lfsr >> 0 ) & 0xFF; + + + int cnt = 0; int x = 64; + unsigned long long int bit; + //polynomial P(x) = 1 + X15 + X27 + X38 + X46 + X62 + X64 + for(cnt=0;cnt<64;cnt++) + { + //63,61,45,37,27,14 + // Polynomial is C(x) = x^64 + x^62 + x^46 + x^38 + x^27 + x^15 + 1 + bit = ((lfsr >> 63) ^ (lfsr >> 61) ^ (lfsr >> 45) ^ (lfsr >> 37) ^ (lfsr >> 26) ^ (lfsr >> 14)) & 0x1; + lfsr = (lfsr << 1) | bit; + + //continue packing aes_iv + state->aes_ivR[x/8] = (state->aes_ivR[x/8] << 1) + bit; + x++; + } + + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n"); + fprintf (stderr, " IV(128): "); + for (x = 0; x < 16; x++) + fprintf (stderr, "%02X", state->aes_ivR[x]); + fprintf (stderr, "%s", KNRM); + +} + //viterbi bit and byte array sizes are max value for largest required (facch2/udch) uint8_t viterbi_bits[26*8]; uint8_t viterbi_bytes[26]; @@ -627,6 +697,8 @@ void nxdn_facch1(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_t fra if (frame == 1) memcpy(facch1_storage, viterbi_bytes, sizeof(facch1_storage)); + state->data_header_format[0] = 3; + if (crc == check && duplicate == 0) NXDN_Elements_Content_decode(opts, state, 1, viterbi_bits); // else if (opts->aggressive_framesync == 0) NXDN_Elements_Content_decode(opts, state, 0, viterbi_bits); @@ -692,6 +764,7 @@ void nxdn_cac(dsd_opts * opts, dsd_state * state, uint8_t * bits) if (crc == 0) { + state->data_header_format[0] = 2; ran = (viterbi_bits[2] << 5) | (viterbi_bits[3] << 4) | (viterbi_bits[4] << 3) | (viterbi_bits[5] << 2) | (viterbi_bits[6] << 1) | viterbi_bits[7]; state->nxdn_last_ran = ran; } @@ -760,6 +833,19 @@ void nxdn_cac(dsd_opts * opts, dsd_state * state, uint8_t * bits) memset (state->dibit_buf, 0, sizeof (int) * 200); state->offset = 0; + //invalidate any active data call assembly and wipe storage + //clear storage + memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0])); + + //reset block number + state->data_header_blocks[0] = 1; + + //reset delivery format (type) + state->data_header_format[0] = 0; + + //reset header validity + state->data_header_valid[0] = 0; + //debug notification // fprintf (stderr, " RESET CARRIER; "); @@ -905,34 +991,36 @@ void idas_facch3_udch2(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8 if (crc[0] == check[0] && crc[1] == check[1]) { + state->data_header_format[0] = 1; if (type == 1) NXDN_Elements_Content_decode(opts, state, 1, f3_udch2); - if (type == 0) {} //need handling for user data (text messages and AVL) + if (type == 0) NXDN_Elements_Content_decode(opts, state, 1, f3_udch2); } + // else TODO: invalidate UDCH2 data buffers - if (type == 0) - { - fprintf (stderr, "\n UDCH2 Data: " ); - for (int i = 0; i < 22; i++) //all but last crc portion - { - if (i == 10) - { - fprintf (stderr, " "); //space seperator? - i = 12; //skip first crc portion - } - fprintf (stderr, "%02X", f3_udch2_bytes[i]); - } + // if (type == 0) + // { + // fprintf (stderr, "\n UDCH2 Data: " ); + // for (int i = 0; i < 22; i++) //all but last crc portion + // { + // if (i == 10) + // { + // fprintf (stderr, " "); //space seperator? + // i = 12; //skip first crc portion + // } + // fprintf (stderr, "%02X", f3_udch2_bytes[i]); + // } - fprintf (stderr, "\n UDCH2 Data: ASCII - " ); - for (int i = 0; i < 22; i++) //all but last crc portion - { - if (i == 10) i = 12; //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, " "); - } - } + // fprintf (stderr, "\n UDCH2 Data: ASCII - " ); + // for (int i = 0; i < 22; i++) //all but last crc portion + // { + // if (i == 10) i = 12; //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, " "); + // } + // } if (opts->payload == 1) { @@ -1359,27 +1447,29 @@ void nxdn_facch2_udch(dsd_opts * opts, dsd_state * state, uint8_t * bits, uint8_ if (crc == check) { + state->data_header_format[0] = 1; 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) + if (type == 0) NXDN_Elements_Content_decode(opts, state, 1, f2u_message_buffer); } + // else TODO: invalidate UDCH data buffers - if (type == 0 && crc == check) - { - fprintf (stderr, "\n UDCH Data: " ); - for (int i = 0; i < 24; i++) //all but last crc portion - fprintf (stderr, "%02X", viterbi_bytes[i]); + // if (type == 0 && crc == check) + // { + // fprintf (stderr, "\n UDCH Data: " ); + // for (int i = 0; i < 24; i++) //all but last crc portion + // fprintf (stderr, "%02X", viterbi_bytes[i]); - fprintf (stderr, "\n UDCH Data: ASCII - " ); - for (int i = 0; i < 24; i++) //remove crc portion - { - if (viterbi_bytes[i] <= 0x7E && viterbi_bytes[i] >=0x20) - { - fprintf (stderr, "%c", viterbi_bytes[i]); - } - else fprintf (stderr, " "); - } + // fprintf (stderr, "\n UDCH Data: ASCII - " ); + // for (int i = 0; i < 24; i++) //remove crc portion + // { + // if (viterbi_bytes[i] <= 0x7E && viterbi_bytes[i] >=0x20) + // { + // fprintf (stderr, "%c", viterbi_bytes[i]); + // } + // else fprintf (stderr, " "); + // } - } + // } if (opts->payload == 1) { diff --git a/src/nxdn_element.c b/src/nxdn_element.c index 1975bb6..8f321b1 100644 --- a/src/nxdn_element.c +++ b/src/nxdn_element.c @@ -6,7 +6,7 @@ * Originally found at - https://github.com/LouisErigHerve/dsd * * LWVMOBILE -* 2026-01 DSD-FME Florida Man Edition +* 2026-03 DSD-FME Florida Man Edition *-----------------------------------------------------------------------------*/ #include "dsd.h" @@ -81,17 +81,30 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, uint8_t Cr switch(MessageType) { - /* - //Note: CAC Message with same Message Type -- This is a private call request and rejection (TODO: Seperate handling depending on CAC, FACCH< Sacch, etc) - 20:56:15 Sync: NXDN96 RCCH Data RAN 01 CAC VCALL (VCALL_REQ) - Private Call - Half Duplex 9600bps/EHR (02) - Src=211 - Dst/TG=1603 - 20:56:15 Sync: NXDN96 RCCH Data RAN 01 CAC DISC (VCALL_REJECTION) - Private Call - Disconnect - Src=1603 - Dst/TG=211 + //SDCALL Header + case 0x38: + nxdn_sdcall_header(opts, state, ElementsContent); + break; - */ - //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) + //SDCALL Data Blocks + case 0x39: + nxdn_dcall_data(opts, state, state->data_header_format[0], ElementsContent); //TODO: type field as delivery format (FACCH1, UDCH, or CAC) + break; - //observed new messages in #318, should also be noted that F1 and F2 are both set on these messages + //SDCALL_IV + case 0x3A: + nxdn_dcall_iv(opts, state, 2, ElementsContent); + break; + + //DCALL Header + case 0x09: + nxdn_dcall_header(opts, state, ElementsContent); + break; + + //DCALL Data Blocks + case 0x0B: + nxdn_dcall_data(opts, state, state->data_header_format[0], ElementsContent); //TODO: type field as delivery format (FACCH1, UDCH, or CAC) + break; //VCALL and TX_REL custom to certain radios, but they have different elements in them case 0xE1: @@ -157,6 +170,24 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, uint8_t Cr case 0x08: //TX_REL sprintf (state->call_string[0], "%s", ""); sprintf (state->nxdn_call_type, "%s", ""); + //invalidate any active data call assembly and wipe storage + //clear storage + memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0])); + + //reset block number + state->data_header_blocks[0] = 1; + + //reset delivery format (type) + state->data_header_format[0] = 0; + + //reset header validity + state->data_header_valid[0] = 0; + + //reset encryption variables (for pdu) + state->payload_algid = 0; + state->payload_keyid = 0; + state->payload_mi = 0; + case 0x01: //VCALL NXDN_decode_VCALL(opts, state, ElementsContent); break; @@ -169,6 +200,24 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, uint8_t Cr sprintf (state->call_string[0], "%s", ""); sprintf (state->nxdn_call_type, "%s", ""); + //invalidate any active data call assembly and wipe storage + //clear storage + memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0])); + + //reset block number + state->data_header_blocks[0] = 1; + + //reset delivery format (type) + state->data_header_format[0] = 0; + + //reset header validity + state->data_header_valid[0] = 0; + + //reset encryption variables (for pdu) + state->payload_algid = 0; + state->payload_keyid = 0; + state->payload_mi = 0; + //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) { @@ -1115,8 +1164,12 @@ void nxdn_decode_dst_info(dsd_opts * opts, dsd_state * state, uint8_t * Message) fprintf (stderr, "\n Option: %02X; Start: %d; End %d; Characters: %d or Sequence: %02X;", option, start, end, num_chars, option & 0x3F); //This message has a CRC, but it appears that it is carried out across - //multiple segments if this is a multi-part message, but CRC type is not - //indicated, so will just rely on the overall CRC from each message prior + //multiple segments (string part only) if this is a multi-part message + + //CRC32 on this with Denny's dst_info sample, works, but its a single segment message + // uint32_t crc_ext = convert_bits_into_output(Message+16+(num_chars*8), 32); + // uint32_t crc_chk = nxdn_message_crc32(Message+16, num_chars*8); + // fprintf (stderr, " CRC: %08X / %08X", crc_ext, crc_chk); fprintf (stderr, "%s", KNRM); @@ -2389,6 +2442,470 @@ void NXDN_decode_ALIAS_ARIB(dsd_opts * opts, dsd_state * state, uint8_t * Messag } /* End NXDN_decode_ALIAS_ARIB() */ +void nxdn_dcall_iv(dsd_opts * opts, dsd_state * state, int type, uint8_t * Message) +{ + UNUSED(opts); + + uint8_t idas = 0; + if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1; + + uint8_t iv[16]; memset(iv, 0, sizeof(iv)); + + if (idas) + { + state->payload_mi = (unsigned long long int)ConvertBitIntoBytes(Message+8, 22); + pack_bit_array_into_byte_array(Message+8, iv, 3); + //TODO: Figure out how IDAS 22-bit expansion works, use taps on 22-bit config, then go from 64-128? + //I really don't expect this will ever be something that comes up, given how Type-D is not prevelant + } + else + { + state->payload_mi = (unsigned long long int)ConvertBitIntoBytes(Message+8, 64); + pack_bit_array_into_byte_array(Message+8, iv, 8); + if (state->nxdn_cipher_type == 3) + LFSR128npdu(state); + } + + fprintf (stderr, "%s", KYEL); + if (type == 1) + fprintf (stderr, "\n DCALL_IV: %016llX", state->payload_mi); + else fprintf (stderr, "\n SDCALL_IV: %016llX", state->payload_mi); + +} + +void nxdn_sdcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message) +{ + UNUSED(opts); + + uint8_t idas = 0; + if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1; + + uint8_t cc_option = (uint8_t)ConvertBitIntoBytes(Message+8, 8); UNUSED(cc_option); + uint8_t call_type = (uint8_t)ConvertBitIntoBytes(Message+16, 3); + uint8_t dcall_opt = (uint8_t)ConvertBitIntoBytes(Message+19, 5); + + uint16_t source = (uint16_t)ConvertBitIntoBytes(Message+24, 16); + uint16_t target = (uint16_t)ConvertBitIntoBytes(Message+40, 16); + uint8_t cipher = (uint8_t)ConvertBitIntoBytes(Message+56, 2); + uint8_t key_id = (uint8_t)ConvertBitIntoBytes(Message+58, 6); + + //encryption variable assignment for PDU + state->payload_algid = cipher; + state->payload_keyid = key_id; + + //idas channel split + uint16_t source_ch = 0; + uint16_t target_ch = 0; + + if (idas) + { + source_ch = (source >> 11) & 0x1F; + target_ch = (target >> 11) & 0x1F; + source = source & 0x7FF; + target = target & 0x7FF; + } + + //packet information and its sub elements (bytes 0, and 1 only) + uint16_t pkt_info = (uint16_t)ConvertBitIntoBytes(Message+64, 16); + uint8_t confirmed_delivery = Message[64]; + uint8_t spare1 = Message[65]; + uint8_t selective_retry = Message[66]; + uint8_t spare2 = Message[67]; + uint8_t block_count = (uint8_t)ConvertBitIntoBytes(Message+68, 4); + uint8_t pad_bytes = (uint8_t)ConvertBitIntoBytes(Message+72, 5); + uint8_t start_frag = Message[77]; + uint8_t circulate = Message[78]; + + //NOTE: Location Info Element is only on UPCH (CAC) and not on FACCH1 message + //it will be treated as an optional element and will not be decoded. + + uint8_t duplex_string[32]; memset(duplex_string, 0, sizeof(duplex_string)); + uint8_t dcall_string[32]; memset(dcall_string, 0, sizeof(dcall_string)); + NXDN_Data_Call_Option_To_Str(dcall_opt, duplex_string, dcall_string); + + fprintf (stderr, "\n %s", KCYN); + fprintf (stderr, "SD Data Call Header (%04X) ", pkt_info); + if (idas == 0) + fprintf (stderr, "Source: %d; Target: %d; ", source, target); + else + fprintf (stderr, "Source: %d-%d; Target: %d-%d; ", source_ch, source, target_ch, target); + fprintf (stderr, "%s %s %s ", NXDN_Call_Type_To_Str(call_type), dcall_string, duplex_string); + + //TODO: Decode cc_option + + //next line + fprintf (stderr, "\n "); + + fprintf (stderr, "Blocks: %d; Padding: %d; ", block_count, pad_bytes); + + if (cipher) + { + fprintf (stderr, "ENC; "); + fprintf (stderr, "Cipher: %X; ", cipher); + fprintf (stderr, "Key ID; %02X; ", key_id); + } + + if (spare1) + fprintf (stderr, "S1; "); + + if (spare2) + fprintf (stderr, "S2; "); + + if (start_frag) + fprintf (stderr, "Starting Fragment; "); + + if (circulate) + fprintf (stderr, "Circulate; "); + + if (confirmed_delivery) + fprintf (stderr, "Confirmed Delivery; "); + + if (selective_retry) + fprintf (stderr, "Selective Retry; "); + + fprintf (stderr, "%s", KNRM); + + //clear storage + memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0])); + + //set header block expected value + if (block_count > 0) + state->data_header_blocks[0] = block_count; + else state->data_header_blocks[0] = 1; + + //set this is a valid header + state->data_header_valid[0] = 1; + +} + +void nxdn_dcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message) +{ + UNUSED(opts); + + uint8_t idas = 0; + if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1; + + uint8_t cc_option = (uint8_t)ConvertBitIntoBytes(Message+8, 8); UNUSED(cc_option); + uint8_t call_type = (uint8_t)ConvertBitIntoBytes(Message+16, 3); + uint8_t dcall_opt = (uint8_t)ConvertBitIntoBytes(Message+19, 5); + + uint16_t source = (uint16_t)ConvertBitIntoBytes(Message+24, 16); + uint16_t target = (uint16_t)ConvertBitIntoBytes(Message+40, 16); + uint8_t cipher = (uint8_t)ConvertBitIntoBytes(Message+56, 2); + uint8_t key_id = (uint8_t)ConvertBitIntoBytes(Message+58, 6); + + //idas channel split + uint16_t source_ch = 0; + uint16_t target_ch = 0; + + if (idas) + { + source_ch = (source >> 11) & 0x1F; + target_ch = (target >> 11) & 0x1F; + source = source & 0x7FF; + target = target & 0x7FF; + } + + //packet information and its sub elements + uint32_t pkt_info = (uint32_t)ConvertBitIntoBytes(Message+64, 24); + uint8_t confirmed_delivery = Message[64]; + uint8_t spare1 = Message[65]; + uint8_t selective_retry = Message[66]; + uint8_t spare2 = Message[67]; + uint8_t block_count = (uint8_t)ConvertBitIntoBytes(Message+68, 4); + uint8_t pad_bytes = (uint8_t)ConvertBitIntoBytes(Message+72, 5); + uint8_t start_frag = Message[77]; + uint8_t circulate = Message[78]; + uint16_t tx_frag_count = (uint16_t)ConvertBitIntoBytes(Message+79, 9); + + //check for optional IV + uint8_t iv[16]; memset(iv, 0, sizeof(iv)); + uint8_t iv_presence = (uint8_t)ConvertBitIntoBytes(Message+88, 8); + if (cipher > 1 && iv_presence != 0) + { + if (idas) + pack_bit_array_into_byte_array(Message+88, iv, 3); + else + pack_bit_array_into_byte_array(Message+88, iv, 8); + + if (idas) + { + //TODO: Idas expansion + state->payload_mi = (unsigned long long int)ConvertBitIntoBytes(Message+88, 22); + } + else + { + state->payload_mi = (unsigned long long int)ConvertBitIntoBytes(Message+88, 64); + } + } + + //encryption variable assignment for PDU + state->payload_algid = cipher; + state->payload_keyid = key_id; + + uint8_t duplex_string[32]; memset(duplex_string, 0, sizeof(duplex_string)); + uint8_t dcall_string[32]; memset(dcall_string, 0, sizeof(dcall_string)); + NXDN_Data_Call_Option_To_Str(dcall_opt, duplex_string, dcall_string); + + fprintf (stderr, "\n %s", KCYN); + fprintf (stderr, "Data Call Header (%04X) ", pkt_info); + if (idas == 0) + fprintf (stderr, "Source: %d; Target: %d; ", source, target); + else + fprintf (stderr, "Source: %d-%d; Target: %d-%d; ", source_ch, source, target_ch, target); + fprintf (stderr, "%s %s %s ", NXDN_Call_Type_To_Str(call_type), dcall_string, duplex_string); + + //TODO: Decode cc_option + + //next line + fprintf (stderr, "\n "); + + fprintf (stderr, "Blocks: %d; Padding: %d; TX Frag: %d; ", block_count, pad_bytes, tx_frag_count); + + if (cipher) + { + fprintf (stderr, "ENC; "); + fprintf (stderr, "Cipher: %X; ", cipher); + fprintf (stderr, "Key ID; %02X; ", key_id); + if (cipher > 1 && iv_presence) + { + fprintf (stderr, "IV: "); + if (idas) + { + for (int i = 0; i < 3; i++) + fprintf (stderr, "%02X", iv[i]); + } + else + { + for (int i = 0; i < 8; i++) + fprintf (stderr, "%02X", iv[i]); + } + } + + } + + if (spare1) + fprintf (stderr, "S1; "); + + if (spare2) + fprintf (stderr, "S2; "); + + if (start_frag) + fprintf (stderr, "Starting Fragment; "); + + if (circulate) + fprintf (stderr, "Circulate; "); + + if (confirmed_delivery) + fprintf (stderr, "Confirmed Delivery; "); + + if (selective_retry) + fprintf (stderr, "Selective Retry; "); + + //AES IV expansion + if (cipher == 3 && iv_presence != 0) + LFSR128npdu(state); + + fprintf (stderr, "%s", KNRM); + + //clear storage + memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0])); + + //set header block expected value + if (block_count > 0) + state->data_header_blocks[0] = block_count; + else state->data_header_blocks[0] = 1; + + //set this is a valid header + state->data_header_valid[0] = 1; + +} + +int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Message) +{ + UNUSED(opts); + + //packet frame number and block number, observation shows these seem to be the same value (observed Type-D) + //but the manual does not quite explain these values indicate super packet of up to 16 big packets, and 1 packet is up to 16 blocks? + uint8_t pf_num = (uint8_t)ConvertBitIntoBytes(Message+8, 4); + uint8_t blk_num = (uint8_t)ConvertBitIntoBytes(Message+12, 4); + + fprintf (stderr, "\n %s", KCYN); + fprintf (stderr, "Data Call (%d/%d); ", pf_num, blk_num); + fprintf (stderr, "%s", KNRM); + + //TODO: Will need to have a len of valid bytes of dcall data at this point, that depends on carrier type (cac, facch1, udch, udch2, etc) + //NOTE: Its possible this format only works with UDCH, or UDCH2 if using Type-D + //TODO: Copy / Append data in blocks to superframe for decode on last one received + //TODO: CRC Check on completed PDU? Is that available? + + int byte_len = 20; //relevant number of user data bytes in UDCH + uint8_t idas = 0; + if (strcmp (state->nxdn_location_category, "Type-D") == 0) idas = 1; + if (idas) + byte_len = 18; //relevant number of user data bytes in UDCH2 + + //CAC type byte_len overrides on SDCALL (UPCH) + if (type == 2) + byte_len = 14; + + //FACCH1 type byte_len overrides on SDCALL + if (type == 3) + byte_len = 8; + + //Set a pointer value to where this block fits into the PDU superframe structure (bits, not bytes) + int ptr = byte_len * 8 * (state->data_header_blocks[0] - blk_num); + int total_bytes = state->data_header_blocks[0]*byte_len; + + if (type == 2) //reported blocks is 2, but data blocks consists of 2,1,0 in sample obtained, doesn't occur on IDAS, but on Type-C UPCH + total_bytes = (state->data_header_blocks[0]+1)*byte_len; + + //sanity check and warning + if (state->data_header_valid[0] == 0) + { + fprintf (stderr, "Missing or Invalid Header; "); + return -1; + } + + if (blk_num > state->data_header_blocks[0]) + { + fprintf (stderr, "Block Num Exceeds Header Reported (%d/%d); ", state->data_header_blocks[0], blk_num); + state->data_header_valid[0] = 0; + return -1; + } + + //Debug + // fprintf (stderr, "Type: %d; Byte Len: %02d; Total: %03d; Ptr: %04d;", state->data_header_format[0], byte_len, total_bytes, ptr); + + //storage (bits, not bytes) + memcpy(state->dmr_pdu_sf[0]+ptr, Message+16, byte_len*8*sizeof(uint8_t)); + + //WIP: completed, evaluate, dump, zero out completed results + if (blk_num == 0) + { + + //WIP: KS creation and application, if applicable + //TODO: May need to use state->RR for simultaneous data and voice call where both are potentially encrypted? + uint8_t ks[3000]; memset(ks, 0, sizeof(ks)); + + //TODO: check for key available, if loader + // if (state->payload_algid == 2) + // { + // if (state->keyloader == 1) + // { + + // } + // } + + if (state->payload_algid != 0) + { + fprintf (stderr, "\n Encrypted Data; Cipher: %d; Key ID: %02X;", state->payload_algid, state->payload_keyid); + if (state->payload_algid > 1) + fprintf (stderr, " IV: %016llX;", state->payload_mi); + } + + //scrambler + if (state->payload_algid == 1 && state->R != 0) + { + fprintf (stderr, " Key: %05lld;", state->R); + pdu_scrambler_keystream_creation(ks, state->R, total_bytes*8); + } + //DES + else if (state->payload_algid == 2 && state->R != 0) + { + fprintf (stderr, " Key: %016llX;", state->R); + int nblocks = total_bytes/8; + if (total_bytes%8) + nblocks++; + uint8_t ks_bytes[nblocks*8]; memset(ks_bytes, 0, sizeof(ks_bytes)); + des_multi_keystream_output (state->payload_mi, state->R, ks_bytes, 1, nblocks); + unpack_byte_array_into_bit_array(ks_bytes, ks, nblocks*8); + } + //AES + else if (state->payload_algid == 3 && state->aes_key_loaded[1] == 1) + { + // fprintf (stderr, " KS: %016llX;", state->K4); //stub + int nblocks = total_bytes/16; + if (total_bytes%16) + nblocks++; + uint8_t ks_bytes[nblocks*16]; memset(ks_bytes, 0, sizeof(ks_bytes)); + aes_ofb_keystream_output (state->aes_ivR, state->aes_key, ks_bytes, 2, nblocks); + unpack_byte_array_into_bit_array(ks_bytes, ks, nblocks*16); + } + + //Debug Data Dump (before KS) + // fprintf (stderr, "\n DATA: "); + // for (int i = 0; i < total_bytes; i++) + // fprintf (stderr, "%02X", (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0]+(i*8), 8)); + + //apply KS + for (int i = 0; i < total_bytes*8; i++) + state->dmr_pdu_sf[0][i] ^= ks[i]; + + //Message CRC calculated using the entire User Data is included in User Data + //area in the User Data format used to send the last block, so the CRC is encrypted (...genius) + + ptr = (total_bytes * 8) - 32; + uint32_t crc_ext = convert_bits_into_output(state->dmr_pdu_sf[0]+ptr, 32); + uint32_t crc_chk = nxdn_message_crc32(state->dmr_pdu_sf[0], ptr); + + //Debug Data Dump + fprintf (stderr, "\n DATA: "); + for (int i = 0; i < total_bytes; i++) + fprintf (stderr, "%02X", (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0]+(i*8), 8)); + + //debug + // fprintf (stderr, " CRC DATA LEN: %03d;", ptr); + // fprintf (stderr, " CRC: %08X / %08X;", crc_ext, crc_chk); + + if (crc_ext == crc_chk) + { + + if ((uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0], 8) == 0x06) + { + //String Data Dump for nmea if first byte is 0x06 + fprintf (stderr, "\n NMEA: "); + for (int i = 1; i < total_bytes; i++) + fprintf (stderr, "%c", (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0]+(i*8), 8)); + } + // else + // { + + // //String Data Dump (not suitable unless this is text) + // fprintf (stderr, "\n String: "); + // for (int i = 0; i < total_bytes; i++) + // fprintf (stderr, "%c", (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0]+(i*8), 8)); + // } + + } + else + { + fprintf (stderr, " CRC: %08X / %08X;", crc_ext, crc_chk); + fprintf (stderr, " (CRC ERR) "); + } + + //clear storage + memset(state->dmr_pdu_sf[0], 0, sizeof(state->dmr_pdu_sf[0])); + + //reset block number + state->data_header_blocks[0] = 1; + + //reset delivery format (type) + state->data_header_format[0] = 0; + + //reset header validity + state->data_header_valid[0] = 0; + + //reset encryption variables + state->payload_algid = 0; + state->payload_keyid = 0; + state->payload_mi = 0; + } + + return 0; + +} + char * NXDN_Call_Type_To_Str(uint8_t CallType) { char * Ptr = NULL; @@ -2409,6 +2926,40 @@ char * NXDN_Call_Type_To_Str(uint8_t CallType) return Ptr; } /* End NXDN_Call_Type_To_Str() */ +void NXDN_Data_Call_Option_To_Str(uint8_t DataCallOption, uint8_t * Duplex, uint8_t * TransmissionMode) +{ + char * Ptr = NULL; + + Duplex[0] = 0; + TransmissionMode[0] = 0; + + if(DataCallOption & 0x10) strcpy((char *)Duplex, "Duplex"); //SU is in Simplex + else strcpy((char *)Duplex, "Half Duplex"); + + switch(DataCallOption & 0xF) + { //added all options, getting a random one on an NXDN96 System, need to know what it is + case 0: Ptr = "4800bps"; break; + case 1: Ptr = "Reserved 1"; break; + case 2: Ptr = "9600bps"; break; + case 3: Ptr = "Reserved 3"; break; + case 4: Ptr = "Reserved 4"; break; + case 5: Ptr = "Reserved 5"; break; + case 6: Ptr = "Reserved 6"; break; + case 7: Ptr = "Reserved 7"; break; + case 8: Ptr = "4800bps S:1"; break; //spare bit enabled + case 9: Ptr = "Reserved 9; S:1"; break; //spare bit enabled + case 10: Ptr = "9600bps S:1"; break; //spare bit enabled + case 11: Ptr = "9600bps S:1"; break; //spare bit enabled + case 12: Ptr = "Reserved C; S1;"; break; //spare bit enabled + case 13: Ptr = "Reserved D; S1"; break; //spare bit enabled + case 14: Ptr = "Reserved E; S1"; break; //spare bit enabled + case 15: Ptr = "Reserved F: S1"; break; //spare bit enabled + default: Ptr = "Unk;"; break; //should never get here + } + + strcpy((char *)TransmissionMode, Ptr); +} /* End NXDN_Voice_Call_Option_To_Str() */ + void NXDN_Voice_Call_Option_To_Str(uint8_t VoiceCallOption, uint8_t * Duplex, uint8_t * TransmissionMode) { char * Ptr = NULL; diff --git a/src/nxdn_frame.c b/src/nxdn_frame.c index 8411042..850ac5c 100644 --- a/src/nxdn_frame.c +++ b/src/nxdn_frame.c @@ -7,7 +7,7 @@ * NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI * * LWVMOBILE - * 2026-01 DSD-FME Florida Man Edition + * 2026-03 DSD-FME Florida Man Edition *-----------------------------------------------------------------------------*/ #include "dsd.h" @@ -15,7 +15,7 @@ #define LICH_ERR_THRESHOLD 7 //Threshold for lich dibit "dividing" errors (8 is perfect, 7 is 1 bit error) // #define NXDN_DEBUG_LICH //print LICH debug info on err on payload == 1 -#define NXDN_LICH_OFFBITS //use the offbits to help determine sync status (disable if bad signal / bad sample) +// #define NXDN_LICH_OFFBITS //use the offbits to help determine sync status (disable if bad signal / bad sample) //NXDN Type Enablement (handled by cmake -DTYPEC=OFF , -DTYPED=OFF, -DDCR=OFF) // #define JPNDCR_DECODE //Include Japanese DCR Decoding