514 lines
14 KiB
C
514 lines
14 KiB
C
//NXDN frame handler
|
|
//Reworked portions from Osmocom OP25 rx_sync.cc
|
|
|
|
/* -*- c++ -*- */
|
|
/*
|
|
* NXDN Encoder/Decoder (C) Copyright 2019 Max H. Parke KA1RBI
|
|
*
|
|
*
|
|
* This is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This software is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this software; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "dsd.h"
|
|
#include "nxdn_const.h"
|
|
|
|
#include <assert.h>
|
|
|
|
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;
|
|
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 udch = 0;
|
|
int sacch = 0;
|
|
int cac = 0;
|
|
int sr_structure;
|
|
int sr_ran;
|
|
|
|
//new, and even more confusing NXDN Type-D / "IDAS" acronyms
|
|
int idas = 0;
|
|
int scch = 0;
|
|
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];
|
|
int nxdn_dibit_buffer[182];
|
|
|
|
//init all arrays
|
|
memset (dbuf, 0, sizeof(dbuf));
|
|
memset (answer, 0, sizeof(answer));
|
|
memset (sacch_answer, 0, sizeof(sacch_answer));
|
|
memset (lich_dibits, 0, sizeof(lich_dibits));
|
|
memset (sacch_bits, 0, sizeof(sacch_bits));
|
|
memset (facch_bits_b, 0, sizeof(facch_bits_b));
|
|
memset (facch_bits_a, 0, sizeof(facch_bits_a));
|
|
memset (cac_bits, 0, sizeof(cac_bits));
|
|
memset (facch2_bits, 0, sizeof(facch2_bits));
|
|
|
|
memset (nxdn_bit_buffer, 0, sizeof(nxdn_bit_buffer));
|
|
memset (nxdn_dibit_buffer, 0, sizeof(nxdn_dibit_buffer));
|
|
|
|
//collect lich bits first, if they are good, then we can collect the rest of them
|
|
for (int i = 0; i < 8; i++) lich_dibits[i] = dbuf[i] = getDibit(opts, state);
|
|
|
|
nxdn_descramble (lich_dibits, 8);
|
|
|
|
lich = 0;
|
|
for (int i=0; i<8; i++) lich |= (lich_dibits[i] >> 1) << (7-i);
|
|
|
|
lich_parity_received = lich & 1;
|
|
lich_parity_computed = ((lich >> 7) + (lich >> 6) + (lich >> 5) + (lich >> 4)) & 1;
|
|
lich = lich >> 1;
|
|
if (lich_parity_received != lich_parity_computed)
|
|
{
|
|
if (opts->payload == 1) fprintf(stderr, " Lich Parity Error %02X\n", lich);
|
|
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
|
goto END;
|
|
}
|
|
|
|
voice = 0;
|
|
facch = 0;
|
|
facch2 = 0;
|
|
sacch = 0;
|
|
cac = 0;
|
|
|
|
//test for inbound direction lich when trunking (false positive) and skip
|
|
//all inbound lich are even value (lsb is set to 0 for inbound direction)
|
|
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
|
|
goto END;
|
|
}
|
|
|
|
switch(lich) { //cases without breaks continue to flow downwards until they hit the break
|
|
case 0x01: // CAC types
|
|
case 0x05:
|
|
cac = 1;
|
|
break;
|
|
case 0x28: //facch2 types
|
|
case 0x29:
|
|
case 0x48:
|
|
case 0x49:
|
|
facch2 = 1;
|
|
break;
|
|
case 0x2e: //udch types
|
|
case 0x2f:
|
|
case 0x4e:
|
|
case 0x4f:
|
|
udch = 1;
|
|
break;
|
|
case 0x32: //facch in 1, vch in 2
|
|
case 0x33:
|
|
case 0x52:
|
|
case 0x53:
|
|
voice = 2;
|
|
facch = 1;
|
|
sacch = 1;
|
|
break;
|
|
case 0x34: //vch in 1, facch in 2
|
|
case 0x35:
|
|
case 0x54:
|
|
case 0x55: //disabled for testing, IDAS system randomly triggers this one, probably due to poor signal
|
|
voice = 1;
|
|
facch = 2;
|
|
sacch = 1;
|
|
break;
|
|
case 0x36: //vch in both
|
|
case 0x37:
|
|
case 0x56:
|
|
case 0x57:
|
|
voice = 3;
|
|
facch = 0;
|
|
sacch = 1;
|
|
break;
|
|
case 0x20: //facch in both
|
|
case 0x21:
|
|
case 0x30:
|
|
case 0x31:
|
|
case 0x40:
|
|
case 0x41:
|
|
case 0x50:
|
|
case 0x51:
|
|
voice = 0;
|
|
facch = 3;
|
|
sacch = 1;
|
|
break;
|
|
case 0x38: //sacch only (NULL?)
|
|
case 0x39:
|
|
sacch = 1;
|
|
break;
|
|
|
|
//NXDN "Type-D" or "IDAS" Specific Lich Codes
|
|
case 0x76: //normal vch voice (in one and two)
|
|
case 0x77:
|
|
idas = 1;
|
|
scch = 1;
|
|
voice = 3;
|
|
break;
|
|
case 0x74: //vch in 1, facch1 in 2 (facch 2 steal)
|
|
case 0x75:
|
|
idas = 1;
|
|
scch = 1;
|
|
voice = 1;
|
|
facch = 2;
|
|
break;
|
|
case 0x72: //facch in 1, vch in 2 (facch 1 steal)
|
|
case 0x73:
|
|
idas = 1;
|
|
scch = 1;
|
|
voice = 2;
|
|
facch = 1;
|
|
break;
|
|
case 0x70: //facch steal in vch 1 and vch 2 (during voice only)
|
|
case 0x71:
|
|
idas = 1;
|
|
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, null data and post field in 2
|
|
idas = 1;
|
|
scch = 1;
|
|
facch = 1;
|
|
break;
|
|
case 0x60:
|
|
case 0x61: //facch1 in both (non vch)
|
|
idas = 1;
|
|
scch = 1;
|
|
facch = 3;
|
|
break;
|
|
|
|
default:
|
|
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, 1, sizeof(state->nxdn_sacch_frame_segment));
|
|
memset (state->nxdn_sacch_frame_segcrc, 1, sizeof(state->nxdn_sacch_frame_segcrc));
|
|
state->lastsynctype = -1; //set to -1 so we don't jump back here too quickly
|
|
voice = 0;
|
|
goto END;
|
|
break;
|
|
} // end of switch(lich)
|
|
|
|
//enable these after good lich parity and known lich value
|
|
state->carrier = 1;
|
|
state->last_cc_sync_time = time(NULL);
|
|
|
|
//printframesync after determining we have a good lich and it has something in it
|
|
if (idas)
|
|
{
|
|
if (opts->frame_nxdn48 == 1)
|
|
{
|
|
printFrameSync (opts, state, "IDAS D ", 0, "-");
|
|
}
|
|
if (opts->payload == 1)
|
|
fprintf (stderr, "L%02X - ", lich);
|
|
}
|
|
else if (voice || facch || sacch || facch2 || cac)
|
|
{
|
|
if (opts->frame_nxdn48 == 1)
|
|
{
|
|
printFrameSync (opts, state, "NXDN48 ", 0, "-");
|
|
}
|
|
else printFrameSync (opts, state, "NXDN96 ", 0, "-");
|
|
if (opts->payload == 1)
|
|
fprintf (stderr, "L%02X - ", lich);
|
|
}
|
|
|
|
//now that we have a good LICH, we can collect all of our dibits
|
|
//and push them to the proper places for decoding (if LICH calls for that type)
|
|
for (int i = 0; i < 174; i++) //192total-10FSW-8lich = 174
|
|
{
|
|
dbuf[i+8] = getDibit(opts, state);
|
|
}
|
|
|
|
nxdn_descramble (dbuf, 182); //sizeof(dbuf)
|
|
|
|
//seperate our dbuf (dibit_buffer) into individual bit array
|
|
for (int i = 0; i < 182; i++)
|
|
{
|
|
nxdn_bit_buffer[i*2] = dbuf[i] >> 1;
|
|
nxdn_bit_buffer[i*2+1] = dbuf[i] & 1;
|
|
}
|
|
|
|
//sacch or scch bits
|
|
for (int i = 0; i < 60; i++)
|
|
{
|
|
sacch_bits[i] = nxdn_bit_buffer[i+16];
|
|
}
|
|
|
|
//facch
|
|
for (int i = 0; i < 144; i++)
|
|
{
|
|
facch_bits_a[i] = nxdn_bit_buffer[i+16+60];
|
|
facch_bits_b[i] = nxdn_bit_buffer[i+16+60+144];
|
|
}
|
|
|
|
//cac
|
|
for (int i = 0; i < 300; i++)
|
|
{
|
|
cac_bits[i] = nxdn_bit_buffer[i+16];
|
|
}
|
|
|
|
//udch or facch2
|
|
for (int i = 0; i < 348; i++)
|
|
{
|
|
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
|
|
|
|
//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 == 2) 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 ");
|
|
// }
|
|
|
|
#ifdef LIMAZULUTWEAKS
|
|
|
|
//LimaZulu specific tweak, load keys from frequency value, if avalable -- test before VCALL
|
|
//needs to be loaded here, if superframe data pair, then we need to run the LFSR on it as well
|
|
|
|
if (voice) //can this run TOO frequently?
|
|
{
|
|
long int freq = 0;
|
|
uint8_t hash_bits[24];
|
|
memset (hash_bits, 0, sizeof(hash_bits));
|
|
uint16_t limazulu = 0;
|
|
|
|
//if not available, then poll rigctl if its available
|
|
if (opts->use_rigctl == 1)
|
|
freq = GetCurrentFreq (opts->rigctl_sockfd);
|
|
|
|
//if using rtl input, we can ask for the current frequency tuned
|
|
else if (opts->audio_in_type == 3)
|
|
freq = (long int)opts->rtlsdr_center_freq;
|
|
|
|
// freq = 167831250; //hardset for testing
|
|
|
|
//since a frequency value will be larger than the 16-bit max, we need to hash it first
|
|
//the hash has to be run the same way as the import, so at a 24-bit depth, which hopefully
|
|
//will not lead to any duplicate key loads due to multiple CRC16 collisions on a larger value?
|
|
for (int i = 0; i < 24; i++)
|
|
hash_bits[i] = ((freq << i) & 0x800000) >> 23; //load into array for CRC16
|
|
|
|
if (freq) limazulu = ComputeCrcCCITT16d (hash_bits, 24);
|
|
limazulu = limazulu & 0xFFFF; //make sure no larger than 16-bits
|
|
|
|
fprintf (stderr, "%s", KYEL);
|
|
if (freq) fprintf (stderr, "\n Freq: %ld - Freq Hash: %0ld", freq, limazulu);
|
|
if (state->rkey_array[limazulu] != 0) fprintf (stderr, " - Key Loaded: %lld", state->rkey_array[limazulu]);
|
|
fprintf (stderr, "%s", KNRM);
|
|
|
|
if (state->rkey_array[limazulu] != 0)
|
|
state->R = state->rkey_array[limazulu];
|
|
|
|
if (state->R != 0 && state->M == 1) state->nxdn_cipher_type = 0x1;
|
|
|
|
//add additional time to last_sync_time for LimaZulu to hold on current frequency
|
|
//a little longer without affecting normal scan time on trunk_hangtime variable
|
|
state->last_cc_sync_time = time(NULL) + 2; //ask him for an ideal wait timer
|
|
}
|
|
|
|
#endif //end LIMAZULUTWEAKS
|
|
|
|
//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 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 //Covers FACCH1 in both, FACCH2, UDCH, UDCH2, CAC
|
|
{
|
|
fprintf (stderr, "%s", KCYN);
|
|
fprintf (stderr, " Data ");
|
|
fprintf (stderr, "%s", KNRM);
|
|
|
|
//roll the voice scrambler LFSR here if key available to advance seed (usually just needed on NXDN96)
|
|
if (state->nxdn_cipher_type == 0x1 && state->R != 0)
|
|
{
|
|
char ambe_temp[49] = {0};
|
|
char ambe_d[49] = {0};
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
LFSRN(ambe_temp, ambe_d, state);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (voice && facch == 1) //facch steal 1 -- before voice
|
|
{
|
|
//force scrambler here, but with unspecified key (just use what's loaded)
|
|
if (state->M == 1 && state->R != 0) state->nxdn_cipher_type = 0x1;
|
|
//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)
|
|
{
|
|
char ambe_temp[49] = {0};
|
|
char ambe_d[49] = {0};
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
LFSRN(ambe_temp, ambe_d, 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 and/or LICH to all decoding functions
|
|
if (scch) nxdn_deperm_scch (opts, state, sacch_bits, direction);
|
|
|
|
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);
|
|
|
|
//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)
|
|
{
|
|
//restore MBE file open here
|
|
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 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 frames mixing
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
char ambe_temp[49] = {0};
|
|
char ambe_d[49] = {0};
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
LFSRN(ambe_temp, ambe_d, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opts->payload == 1 && !voice) fprintf (stderr, "\n");
|
|
else if (opts->payload == 0) fprintf (stderr, "\n");
|
|
|
|
END: ; //do nothing
|
|
}
|