diff --git a/README.md b/README.md index 8794e3c..4b7f7af 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ # Digital Speech Decoder - Florida Man Edition -This version of DSD is a flavor blend of [szechyjs](https://github.com/szechyjs/dsd "szechyjs") RTL branch and some of my own additions, along with portions of DMR and NXDN code from the [LouisErigHerve](https://github.com/LouisErigHerve/dsd "LouisErigHerve") branch as well. This code also borrows snippets, inspiration, and ideas from other open source works including [Boatbod OP25](https://github.com/boatbod/op25 "Boatbod OP25"), [DSDcc](https://github.com/f4exb/dsdcc "DSDcc"), [SDTRunk](https://github.com/DSheirer/sdrtrunk "SDRTrunk"), [MMDVMHost](https://github.com/g4klx/MMDVMHost "MMDVMHost"), and [LFSR](https://github.com/mattames/LFSR "LFSR"). This project wouldn't be possible without a few good people providing me plenty of sample audio files to run over and over again. Special thanks to jurek1111, KrisMar, noamlivne, racingfan360, iScottyBotty, LimaZulu and hrh17 for the many hours of wav samples submitted by them. +This version of DSD is a flavor blend of [szechyjs](https://github.com/szechyjs/dsd "szechyjs") RTL branch and some of my own additions, along with portions of DMR and NXDN code from the [LouisErigHerve](https://github.com/LouisErigHerve/dsd "LouisErigHerve") branch as well. This code also borrows snippets, inspiration, and ideas from other open source works including [Boatbod OP25](https://github.com/boatbod/op25 "Boatbod OP25"), [DSDcc](https://github.com/f4exb/dsdcc "DSDcc"), [SDTRunk](https://github.com/DSheirer/sdrtrunk "SDRTrunk"), [MMDVMHost](https://github.com/g4klx/MMDVMHost "MMDVMHost"), [LFSR](https://github.com/mattames/LFSR "LFSR"), and [EZPWD-Reed-Solomon](https://github.com/pjkundert/ezpwd-reed-solomon "EZPWD"). This project wouldn't be possible without a few good people providing me plenty of sample audio files to run over and over again. Special thanks to jurek1111, KrisMar, noamlivne, racingfan360, iScottyBotty, LimaZulu and hrh17 for the many hours of wav samples submitted by them. + +## 2022.09.17 Update ## +P25 Phase 2 Audio decoding has been implemented into DSD-FME. Currently, Phase 2 will only work from OP25 symbol capture bin files, or from wav files/SDR applications/RTL input that are sourced from FSK4 sources. CQPSK (LSM/H-D8PSK) will not work from audio sources. With the addition of Phases 2 audio, a new default decoder class has been implemented, XDMA, which is P25 Phase 1, Phase 2, DMR BS and MS (DMR Stereo). Furthermore, very limited Phase 1 TSBK support and Phase 2 FAcch/SaCCH/LCCH has been worked in just to get Network Status Broadcasts, which can fill in the required P2 parameters for WACN, SYSID, and CC for Phase 2 frame de-scrambling, and active voice call information. + +With the implementation of the XDMA decoder class (default decoder), the CLI switches for DMR Stereo has been repurposed. +-T option will now open a per call wav file saving when used with NCurses Terminal, otherwise it will write wav files to slot 1 and slot 2 wav files. + +Anybody using dsd-fme -fr -T ...... should now just use dsd-fme -ft instead! + +To see the up to date CLI options, please look at the help options with the -h CLI option. ## 2022.08.12 Update ## -A new menu system has been introduced in the NCURSES Terminal. While running DSD-FME, a user can simply use the ESC or arrow keys to open the menu. This is extremely beneficial when wanting to change settings on the fly, and also to alleviate the need to use complex start up command line arguments. All of the previous command line arguments still work and will get you up faster, but if you would rather configure after start up, or change settings while running, the menu system is very helpful in that regard. +A new menu system has been introduced in the NCURSES Terminal. Also includes support for LRRP to text file type of choice and reading OP25 symbol capture bin files. [![DSD-FME](https://github.com/lwvmobile/dsd-fme/blob/pulseaudio/dsd-fme.png)](https://www.youtube.com/watch?v=TqTfAfaPJ4s "DSD-FME Update 2022.08.12") @@ -11,14 +21,6 @@ To get started with the new menu system, simply launch with: `dsd-fme -N 2> log.txt` -Virtually all the examples listed below can now be set up on the fly with the menu system, no need to check the help or refer to this page. - -This release also has provisional support for playing back OP25 capture bin files, and the creation and playback of its own capture bin files as well (most likely not OP25 compatible). Mileage may vary. Be sure to choose the decoder option required when playing a bin file back, do not rely on auto-detect. Capture Bin files can be used to reliably replay P25, DMR BS, and NXDN decodings, and can replace the need for MBE file saving in the case of DMR Stereo, allowing for a complete replay of all events on a system, rather than just voice only. - -Per Call wav file creation has been implemented, currently only for DMR Stereo. Complete audio decode wav is available for all other decoder types. LRRP has been revamped, working more consistently, and an option to dump LRRP data to ~/lrrp.txt in the user home directory now exists, and can be imported into open-source QGIS to provide mapping data for LRRP output. Simply open the newly included map file in QGIS and point it at your geographic region. (Be sure the lrrp.txt file exists, and has data populated in it prior to opening the map file in QGIS, otherwise, the map layer may not want to show back up!) - -DMR Data Header decoding has been revamped, and most data burst decoding types tightened up and code cleaned. FEC error checking now implemented on CACH and Burst types, cleaning up some erroneous slot and burst errors. - ## 2022.06.20 Update - Current Users Please Read!! ## The executable for this project has been changed from `dsd` to `dsd-fme` so after pulling or cloning the lastest version, make sure to call the software with `dsd-fme`. @@ -28,20 +30,22 @@ DMR Stereo method added for listening to voice audio in both TDMA channels/slots ### Example Usage and Note! `dsd-fme` or `./dsd-fme` may need to be used in your usage, depending on if you are running it from the build folder, or if you have run make install. You will need to tweak the examples to your particular usage. Users can always check `dsd-fme -h` for help and all command line switches to use. -`dsd-fme` is all you need to run for pulse input, pulse output, and auto detect for DMR, P25P1, D-STAR, and X2-TDMA decoding. To use other decoding methods which cannot be auto detected, please use the following command line switches. Make sure to route audio into and out of DSD-FME using pavucontrol and virtual sinks as needed. +`dsd-fme` is all you need to run for pulse input, pulse output, and auto detect for DMR BS/MS, P25 (1 and 2) and X2-TDMA decoding. To use other decoding methods which cannot be auto detected, please use the following command line switches. Make sure to route audio into and out of DSD-FME using pavucontrol and virtual sinks as needed. ``` +-ft XDMA decoder class (P25 1 and 2, DMR BS/MS, X2-TDMA) +-fa Legacy Auto -fi NXDN48 -fn NXDN96 -fp ProVoice -fm dPMR, also may need to use -xd if inverted dPMR. --fr DMR, also may need to use -xr if inverted DMR, use -T for Stereo, and -F for passive sync +-fr DMR, also may need to use -xr if inverted DMR. -f1 P25P1 -fx X2-TDMA ``` -## Example Usage - New DMR Stereo, Ncurses Terminal, Pulse Input/Output, and Log Console to file -`dsd-fme -fr -T -N 2> voice.log` +## Example Usage - New XDMA Decoder, Ncurses Terminal, Pulse Input/Output, and Log Console to file +`dsd-fme -ft -N 2> voice.log` and in a second terminal tab, same folder, run @@ -90,12 +94,12 @@ and in a second terminal tab, same folder, run ## Roadmap The Current list of objectives include: -1. Include P25 P2 Audio decoding with capture bin files, then RTL input and/or disc tap input. +1. ~~Include P25 P2 Audio decoding with capture bin files~~, new demodulators and input method(s) (considering liquid-dsp). ~~Random Tinkering~~ ~~More Random Tinkering~~ 2. ~~Implemented Pulse Audio~~ ~~Remove remaining PortAudio code,~~ ~~improved Pulse Audio for stereo output and channel/slot separation~~ ~~and reimplement wav file saving using DMR Stereo method for stereo wav files, or seperate as per call wav files.~~ -3. ~~Improve NXDN and DMR support~~ Continue to improve ~~NXDN and DMR~~ all data and voice decoding. +3. ~~Improve NXDN and DMR support~~ Continue to improve all data and voice decoding. 4. ~~More Concise Printouts - Ncurses~~ diff --git a/include/dsd.h b/include/dsd.h index 97ec1a4..9b58061 100644 --- a/include/dsd.h +++ b/include/dsd.h @@ -32,7 +32,7 @@ #include #include #include -#define __USE_XOPEN //compiler warning on this, needed for strptime, seems benign +//#define __USE_XOPEN //compiler warning on this, need to rewrite dsd_file so it doesn't use strptime #include #include #include @@ -279,6 +279,7 @@ typedef struct char symbol_out_file[1024]; char lrrp_out_file[1024]; short int symbol_out; + short int mbe_out; //flag for mbe out, don't attempt fclose more than once SNDFILE *wav_out_f; SNDFILE *wav_out_fR; SNDFILE *wav_out_raw; @@ -291,6 +292,8 @@ typedef struct int frame_x2tdma; int frame_p25p1; int frame_p25p2; + int inverted_p2; + int p2counter; int frame_nxdn48; int frame_nxdn96; int frame_dmr; @@ -464,6 +467,7 @@ typedef struct unsigned long long int K2; unsigned long long int K3; unsigned long long int K4; + int M; int menuopen; unsigned int debug_audio_errors; @@ -567,15 +571,27 @@ typedef struct unsigned int dmrburstL; unsigned int dmrburstR; unsigned long long int R; + unsigned long long int RR; unsigned long long int H; unsigned long long int HYTL; unsigned long long int HYTR; int DMRvcL; int DMRvcR; - // int block_count; + short int dmr_encL; short int dmr_encR; + //P2 variables + unsigned long long int p2_wacn; + unsigned long long int p2_sysid; + unsigned long long int p2_cc; //p1 NAC + int p2_hardset; //flag for checking whether or not P2 wacn and sysid are hard set by user + int p2_scramble_offset; //offset counter for scrambling application + int p2_vch_chan_num; //vch channel number (0 or 1, not the 0-11 TS) + int ess_b[2][96]; //external storage for ESS_B fragments + int fourv_counter[2]; //external reference counter for ESS_B fragment collection + int p2_is_lcch; //flag to tell us when a frame is lcch and not sacch + //dstar header for ncurses unsigned char dstarradioheader[41]; @@ -602,20 +618,8 @@ typedef struct #define INV_P25P1_SYNC "333331331133111131311111" #define P25P1_SYNC "111113113311333313133333" -//preliminary P2 sync - -// static const uint64_t P25P2_FRAME_SYNC_MAGIC = 0x575D57F7FFLL; // 40 bits -// static const uint64_t P25P2_FRAME_SYNC_REV_P = 0x575D57F7FFLL ^ 0xAAAAAAAAAALL; #define P25P2_SYNC "11131131111333133333" #define INV_P25P2_SYNC "33313313333111311111" -// static const uint64_t P25P2_FRAME_SYNC_MASK = 0xFFFFFFFFFFLL; //wonder what the mask was for if its all F -// -// static const uint64_t P25P2_FRAME_SYNC_N1200 = 0x0104015155LL; -//#define P25P2_SYNC_N "" -// static const uint64_t P25P2_FRAME_SYNC_X2400 = 0xa8a2a8d800LL; -//#define P25P2_SYNC_X "" -// static const uint64_t P25P2_FRAME_SYNC_P1200 = 0xfefbfeaeaaLL; -//#define P25P2_SYNC_P "" #define X2TDMA_BS_VOICE_SYNC "113131333331313331113311" #define X2TDMA_BS_DATA_SYNC "331313111113131113331133" @@ -750,6 +754,7 @@ void processX2TDMAvoice (dsd_opts * opts, dsd_state * state); void processDSTAR_HD (dsd_opts * opts, dsd_state * state); void processYSF(dsd_opts * opts, dsd_state * state); //YSF void processP2(dsd_opts * opts, dsd_state * state); //P2 +void processTSBK(dsd_opts * opts, dsd_state * state); short dmr_filter(short sample); short nxdn_filter(short sample); @@ -915,6 +920,16 @@ void CDMRTrellisPointsToDibits(const unsigned char* points, signed char* dibits) void CDMRTrellisBitsToTribits(const unsigned char* payload, unsigned char* tribits); bool CDMRTrellisDecode(const unsigned char* data, unsigned char* payload); +//Phase 2 Helper Functions +int ez_rs28_ess (int payload[96], int parity[168]); //ezpwd bridge for FME +int ez_rs28_facch (int payload[156], int parity[114]); //ezpwd bridge for FME +int ez_rs28_sacch (int payload[180], int parity[132]); //ezpwd bridge for FME +unsigned long long int isch_lookup (unsigned long long int isch); //isch map lookup +int bd_bridge (int payload[196], uint8_t decoded[12]); //bridge to Michael Ossmann Block De-interleaver and 1/2 rate trellis decoder +// uint16_t crc16(uint8_t buf[], int len); +int crc16_lb_bridge (int payload[190], int len); +int crc12_xb_bridge (int payload[190], int len); + #ifdef __cplusplus } #endif diff --git a/src/dmr_ms.c b/src/dmr_ms.c index 57855ee..a46c688 100644 --- a/src/dmr_ms.c +++ b/src/dmr_ms.c @@ -817,8 +817,8 @@ void dmrMSData (dsd_opts * opts, dsd_state * state) } - sprintf(state->slot1light, ""); - sprintf(state->slot2light, ""); + sprintf(state->slot1light, "%s", ""); + sprintf(state->slot2light, "%s", ""); //process data state->dmr_stereo = 1; diff --git a/src/dmr_process_voice.c b/src/dmr_process_voice.c index a4c5235..9f38197 100644 --- a/src/dmr_process_voice.c +++ b/src/dmr_process_voice.c @@ -72,7 +72,8 @@ int Pr[256] = { }; -if (state->currentslot == 0 && state->K > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x10) +if ( (state->currentslot == 0 && state->K > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x10) || + (state->currentslot == 0 && state->K > 0 && state->M == 1) ) { fprintf (stderr, "%s", KYEL); fprintf(stderr, " PrK %lld", state->K); @@ -93,7 +94,8 @@ if (state->currentslot == 0 && state->K > 0 && state->dmr_so & 0x40 && state->pa } } -if (state->currentslot == 1 && state->K > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x10) +if ( (state->currentslot == 1 && state->K > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x10) || + (state->currentslot == 1 && state->K > 0 && state->M == 1) ) { fprintf (stderr, "%s", KYEL); fprintf(stderr, " PrK %lld", state->K); @@ -114,10 +116,11 @@ if (state->currentslot == 1 && state->K > 0 && state->dmr_soR & 0x40 && state->p } } -if (state->currentslot == 0 && state->K1 > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x68) +if ( (state->currentslot == 0 && state->K1 > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x68) || + (state->currentslot == 0 && state->K1 > 0 && state->M == 1) ) { - int pos = 0; + int pos = 0; unsigned long long int k1 = state->K1; unsigned long long int k2 = state->K2; @@ -186,7 +189,8 @@ if (state->currentslot == 0 && state->K1 > 0 && state->dmr_so & 0x40 && state->p } -if (state->currentslot == 1 && state->K1 > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x68) +if ( (state->currentslot == 1 && state->K1 > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x68) || + (state->currentslot == 1 && state->K1 > 0 && state->M == 1) ) { int pos = 0; diff --git a/src/dmr_sync.c b/src/dmr_sync.c index 128401e..31ada54 100644 --- a/src/dmr_sync.c +++ b/src/dmr_sync.c @@ -484,15 +484,14 @@ void ProcessDataData(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint } - //set source here so we can reference it in our text dump - state->dmr_lrrp_source[state->currentslot] = (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7]; fprintf (stderr, "%s ", KNRM); //end collecting data header info + //look at see which type of data service ap we have, and then assign src and dst later for lrrp text dump, etc. fprintf (stderr, "%s ", KMAG); - if (state->data_header_sap[state->currentslot] == 0x4 && state->data_p_head[state->currentslot] == 0) //4 is IP data + if (state->data_header_sap[state->currentslot] == 0x4 && state->data_p_head[state->currentslot] == 0) { - fprintf (stderr, "\n IP4 Data - Source: "); + fprintf (stderr, "\n IP Data - Source: "); //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[0] & 0x3F), DmrDataByte[1], DmrDataByte[2], DmrDataByte[3]); //not sure if these are right or not fprintf (stderr, "[%d]", (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7] ); fprintf (stderr, " Destination: "); @@ -507,6 +506,90 @@ void ProcessDataData(dsd_opts * opts, dsd_state * state, uint8_t info[196], uint ( (DmrDataByte[2] <<16 ) + (DmrDataByte[3] <<8 ) + DmrDataByte[4]) ); } + state->dmr_lrrp_source[state->currentslot] = (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7]; + + } + + if (state->data_header_sap[state->currentslot] == 0x0 && state->data_p_head[state->currentslot] == 0) + { + fprintf (stderr, "\n Unified Data Transport - Source: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[0] & 0x3F), DmrDataByte[1], DmrDataByte[2], DmrDataByte[3]); //not sure if these are right or not + fprintf (stderr, "[%d]", (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7] ); + fprintf (stderr, " Destination: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[4] & 0x3F), DmrDataByte[5], DmrDataByte[6], DmrDataByte[7]); //not sure if these are right or not + fprintf (stderr, "[%d]", (DmrDataByte[2] <<16 ) + (DmrDataByte[3] <<8 ) + DmrDataByte[4] ); + + //part below should probably be inside of above if condition + if (1 == 1) //already setting by current slot, no need for checking first + { + sprintf ( state->dmr_lrrp[state->currentslot][1], "SRC [%d] DST [%d] ", + ( (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7]), + ( (DmrDataByte[2] <<16 ) + (DmrDataByte[3] <<8 ) + DmrDataByte[4]) ); + } + + state->dmr_lrrp_source[state->currentslot] = (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7]; + + } + + if (state->data_header_sap[state->currentslot] == 0x9 && state->data_p_head[state->currentslot] == 0) + { + fprintf (stderr, "\n Proprietary Packet Data - Source: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[0] & 0x3F), DmrDataByte[1], DmrDataByte[2], DmrDataByte[3]); //not sure if these are right or not + fprintf (stderr, "[%d]", (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7] ); + fprintf (stderr, " Destination: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[4] & 0x3F), DmrDataByte[5], DmrDataByte[6], DmrDataByte[7]); //not sure if these are right or not + fprintf (stderr, "[%d]", (DmrDataByte[2] <<16 ) + (DmrDataByte[3] <<8 ) + DmrDataByte[4] ); + + //part below should probably be inside of above if condition + if (1 == 1) //already setting by current slot, no need for checking first + { + sprintf ( state->dmr_lrrp[state->currentslot][1], "SRC [%d] DST [%d] ", + ( (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7]), + ( (DmrDataByte[2] <<16 ) + (DmrDataByte[3] <<8 ) + DmrDataByte[4]) ); + } + + state->dmr_lrrp_source[state->currentslot] = (DmrDataByte[5] <<16 ) + (DmrDataByte[6] << 8) + DmrDataByte[7]; + + } + + if (state->data_header_sap[state->currentslot] == 0x2 && state->data_p_head[state->currentslot] == 0) + { + fprintf (stderr, "\n TCP/IP Header Compression Data - Source: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[0] & 0x3F), DmrDataByte[1], DmrDataByte[2], DmrDataByte[3]); //not sure if these are right or not + fprintf (stderr, "[%d]", DmrDataByte[3]); + fprintf (stderr, " Destination: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[4] & 0x3F), DmrDataByte[5], DmrDataByte[6], DmrDataByte[7]); //not sure if these are right or not + fprintf (stderr, "[%d]", DmrDataByte[2]); + + //part below should probably be inside of above if condition + if (1 == 1) //already setting by current slot, no need for checking first + { + sprintf ( state->dmr_lrrp[state->currentslot][1], "SRC [%d] DST [%d] ", + DmrDataByte[3], DmrDataByte[2] ); + } + + state->dmr_lrrp_source[state->currentslot] = DmrDataByte[3]; + + } + + if (state->data_header_sap[state->currentslot] == 0x3 && state->data_p_head[state->currentslot] == 0) + { + fprintf (stderr, "\n UDP/IP Header Compression Data - Source: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[0] & 0x3F), DmrDataByte[1], DmrDataByte[2], DmrDataByte[3]); //not sure if these are right or not + fprintf (stderr, "[%d]", DmrDataByte[3]); + fprintf (stderr, " Destination: "); + //fprintf (stderr, " %d.%d.%d.%d", (DmrDataByte[4] & 0x3F), DmrDataByte[5], DmrDataByte[6], DmrDataByte[7]); //not sure if these are right or not + fprintf (stderr, "[%d]", DmrDataByte[2]); + + //part below should probably be inside of above if condition + if (1 == 1) //already setting by current slot, no need for checking first + { + sprintf ( state->dmr_lrrp[state->currentslot][1], "SRC [%d] DST [%d] ", + DmrDataByte[3], DmrDataByte[2] ); + } + + state->dmr_lrrp_source[state->currentslot] = DmrDataByte[3]; + } if (state->data_header_sap[state->currentslot] == 0x5 && state->data_p_head[state->currentslot] == 0) //5 is ARP Address Resolution Protocol @@ -2404,7 +2487,7 @@ void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state) //Embedded Alias if ( TSVoiceSupFrame->FullLC.FullLinkControlOpcode > 0x03 && TSVoiceSupFrame->FullLC.FullLinkControlOpcode < 0x08) { - sprintf (state->dmr_callsign[state->currentslot][TSVoiceSupFrame->FullLC.FullLinkControlOpcode - 3], ""); //blank here so it doesn't grow out of control? + sprintf (state->dmr_callsign[state->currentslot][TSVoiceSupFrame->FullLC.FullLinkControlOpcode - 3], "%s", ""); //blank here so it doesn't grow out of control? for (i = 0; i < 10; i++) { //full range of alphanumerical characters? @@ -2431,7 +2514,7 @@ void ProcessVoiceBurstSync(dsd_opts * opts, dsd_state * state) if ( TSVoiceSupFrame->FullLC.FullLinkControlOpcode == 0x08 && opts->payload == 1) //Embedded GPS { - sprintf (state->dmr_callsign[state->currentslot][TSVoiceSupFrame->FullLC.FullLinkControlOpcode - 3], ""); //blank here so it doesn't grow out of control? + sprintf (state->dmr_callsign[state->currentslot][TSVoiceSupFrame->FullLC.FullLinkControlOpcode - 3], "%s", ""); //blank here so it doesn't grow out of control? for (i = 0; i < 10; i++) { //full range of alphanumerical characters? diff --git a/src/dsd_audio.c b/src/dsd_audio.c index 73df689..0c298f3 100644 --- a/src/dsd_audio.c +++ b/src/dsd_audio.c @@ -64,8 +64,8 @@ void openPulseOutput(dsd_opts * opts) if (opts->dmr_stereo == 1) { - opts->pulse_digi_dev_out = pa_simple_new(NULL, "DSD-FME1", PA_STREAM_PLAYBACK, NULL, "DMR SLOT 1", &tt, left, NULL, NULL); - opts->pulse_digi_dev_outR = pa_simple_new(NULL, "DSD-FME2", PA_STREAM_PLAYBACK, NULL, "DMR SLOT 2", &tt, right, NULL, NULL); + opts->pulse_digi_dev_out = pa_simple_new(NULL, "DSD-FME1", PA_STREAM_PLAYBACK, NULL, "XDMA SLOT 1", &tt, left, NULL, NULL); + opts->pulse_digi_dev_outR = pa_simple_new(NULL, "DSD-FME2", PA_STREAM_PLAYBACK, NULL, "XDMA SLOT 2", &tt, right, NULL, NULL); } } @@ -92,6 +92,11 @@ processAudio (dsd_opts * opts, dsd_state * state) { // detect max level max = 0; + //attempt to reduce crackle by overriding the max value when not using pulse input + if (opts->audio_in_type != 0) + { + max = 3000; + } state->audio_out_temp_buf_p = state->audio_out_temp_buf; for (n = 0; n < 160; n++) { @@ -233,6 +238,11 @@ processAudioR (dsd_opts * opts, dsd_state * state) { // detect max level max = 0; + //attempt to reduce crackle by overriding the max value when not using pulse input + if (opts->audio_in_type != 0) + { + max = 3000; + } state->audio_out_temp_buf_pR = state->audio_out_temp_bufR; for (n = 0; n < 160; n++) { diff --git a/src/dsd_file.c b/src/dsd_file.c index e15d24c..b62af6c 100644 --- a/src/dsd_file.c +++ b/src/dsd_file.c @@ -17,6 +17,34 @@ #include "dsd.h" +time_t nowF; +char * getTimeF(void) //get pretty hh:mm:ss timestamp +{ + time_t t = time(NULL); + + char * curr; + char * stamp = asctime(localtime( & t)); + + curr = strtok(stamp, " "); + curr = strtok(NULL, " "); + curr = strtok(NULL, " "); + curr = strtok(NULL, " "); + + return curr; +} + +char * getDateF(void) { + char datename[120]; //bug in 32-bit Ubuntu when using date in filename, date is garbage text + char * curr2; + struct tm * to; + time_t t; + t = time(NULL); + to = localtime( & t); + strftime(datename, sizeof(datename), "%Y-%m-%d", to); + curr2 = strtok(datename, " "); + return curr2; +} + void saveImbe4400Data (dsd_opts * opts, dsd_state * state, char *imbe_d) { @@ -72,7 +100,7 @@ saveAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) //fprintf(stderr, "\n AMBE "); } //for (i = 0; i < 6; i++) - for (i = 0; i < 7; i++) //using 7 seems to break older amb files where it was 6, need to test 7 on 7 some more + for (i = 0; i < 6; i++) //using 7 seems to break older amb files where it was 6, need to test 7 on 7 some more { b = 0; for (j = 0; j < 8; j++) @@ -181,7 +209,7 @@ PrintAMBEData (dsd_opts * opts, dsd_state * state, char *ambe_d) //For DMR Stere b = ambe_d[48]; if (opts->dmr_stereo == 1) //need to fix the printouts again { - fprintf (stderr, "\n"); + fprintf (stderr, "\n"); } } @@ -250,7 +278,7 @@ readAmbe2450Data (dsd_opts * opts, dsd_state * state, char *ambe_d) fprintf(stderr, "\n"); } //for (i = 0; i < 6; i++) - for (i = 0; i < 7; i++) //breaks backwards compatablilty with 6 files + for (i = 0; i < 6; i++) //breaks backwards compatablilty with 6 files { b = fgetc (opts->mbe_in_f); if (feof (opts->mbe_in_f)) @@ -321,85 +349,105 @@ openMbeInFile (dsd_opts * opts, dsd_state * state) } -void -closeMbeOutFile (dsd_opts * opts, dsd_state * state) +//temporarily disabled until the compiler warnings can be fixed, use symbol capture or per call instead! +//probably the easiest thing is to just have this run fclose, and have name assignment on open instead. +// void +// closeMbeOutFile (dsd_opts * opts, dsd_state * state) +// { +// +// char shell[255], newfilename[64], ext[5], datestr[32], new_path[1024]; +// char tgid[17]; +// int sum, i, j; +// int talkgroup; +// struct tm timep; +// int result; +// +// if (opts->mbe_out_f != NULL) +// { +// if ((state->synctype == 0) || (state->synctype == 1) || (state->synctype == 14) || (state->synctype == 15)) +// { +// sprintf (ext, ".imb"); +// //strptime (opts->mbe_out_file, "%s.imb", &timep); +// } +// else +// { +// sprintf (ext, ".amb"); +// //strptime (opts->mbe_out_file, "%s.amb", &timep); +// } +// +// if (state->tgcount > 0) +// { +// for (i = 0; i < 16; i++) +// { +// sum = 0; +// for (j = 0; j < state->tgcount; j++) +// { +// sum = sum + state->tg[j][i] - 48; +// } +// tgid[i] = (char) (((float) sum / (float) state->tgcount) + 48.5); +// } +// tgid[16] = 0; +// talkgroup = (int) strtol (tgid, NULL, 2); +// } +// else +// { +// talkgroup = 0; +// } +// +// fflush (opts->mbe_out_f); +// fclose (opts->mbe_out_f); +// opts->mbe_out_f = NULL; +// strftime (datestr, 31, "%Y-%m-%d-%H%M%S", &timep); +// +// sprintf (newfilename, "%s%s", datestr, ext); //this one +// +// if (state->lastsynctype == 0 || state->lastsynctype == 1) +// { +// sprintf (newfilename, "%s-nac%X-tg%i%s", datestr, state->nac, talkgroup, ext); +// } +// +// //sprintf (new_path, "%s%s", opts->mbe_out_dir, newfilename); +// #ifdef _WIN32 +// //sprintf (shell, "move %s %s", opts->mbe_out_path, new_path); +// #else +// //sprintf (shell, "mv %s %s", opts->mbe_out_path, new_path); +// #endif +// //result = system (shell); +// +// state->tgcount = 0; +// for (i = 0; i < 25; i++) +// { +// for (j = 0; j < 16; j++) +// { +// state->tg[i][j] = 48; +// } +// } +// } +// } + +//much simpler version +void closeMbeOutFile (dsd_opts * opts, dsd_state * state) { - - char shell[255], newfilename[64], ext[5], datestr[32], new_path[1024]; - char tgid[17]; - int sum, i, j; - int talkgroup; - struct tm timep; - int result; - - if (opts->mbe_out_f != NULL) + if (opts->mbe_out == 1) + { + if (opts->mbe_out_f != NULL) { - if ((state->synctype == 0) || (state->synctype == 1) || (state->synctype == 14) || (state->synctype == 15)) - { - sprintf (ext, ".imb"); - strptime (opts->mbe_out_file, "%s.imb", &timep); - } - else - { - sprintf (ext, ".amb"); - strptime (opts->mbe_out_file, "%s.amb", &timep); - } - - if (state->tgcount > 0) - { - for (i = 0; i < 16; i++) - { - sum = 0; - for (j = 0; j < state->tgcount; j++) - { - sum = sum + state->tg[j][i] - 48; - } - tgid[i] = (char) (((float) sum / (float) state->tgcount) + 48.5); - } - tgid[16] = 0; - talkgroup = (int) strtol (tgid, NULL, 2); - } - else - { - talkgroup = 0; - } - fflush (opts->mbe_out_f); fclose (opts->mbe_out_f); opts->mbe_out_f = NULL; - strftime (datestr, 31, "%Y-%m-%d-%H%M%S", &timep); - - sprintf (newfilename, "%s%s", datestr, ext); //this one - - if (state->lastsynctype == 0 || state->lastsynctype == 1) - { - sprintf (newfilename, "%s-nac%X-tg%i%s", datestr, state->nac, talkgroup, ext); - } - - sprintf (new_path, "%s%s", opts->mbe_out_dir, newfilename); -#ifdef _WIN32 - sprintf (shell, "move %s %s", opts->mbe_out_path, new_path); -#else - sprintf (shell, "mv %s %s", opts->mbe_out_path, new_path); -#endif - result = system (shell); - - state->tgcount = 0; - for (i = 0; i < 25; i++) - { - for (j = 0; j < 16; j++) - { - state->tg[i][j] = 48; - } - } + opts->mbe_out = 0; + fprintf (stderr, "\nClosing MBE out file."); } + + } + } void openMbeOutFile (dsd_opts * opts, dsd_state * state) { - struct timeval tv; + //struct timeval tv; int i, j; char ext[5]; @@ -423,18 +471,22 @@ openMbeOutFile (dsd_opts * opts, dsd_state * state) state->tgcount = 0; - gettimeofday (&tv, NULL); - sprintf (opts->mbe_out_file, "%i%s", (int) tv.tv_sec, ext); + //gettimeofday (&tv, NULL); + //rewrite below line to take getTime function instead? + // sprintf (opts->mbe_out_file, "%i%s", (int) tv.tv_sec, ext); + + sprintf (opts->mbe_out_file, "%s %s%s", getDateF(), getTimeF(), ext); sprintf (opts->mbe_out_path, "%s%s", opts->mbe_out_dir, opts->mbe_out_file); opts->mbe_out_f = fopen (opts->mbe_out_path, "w"); if (opts->mbe_out_f == NULL) - { - fprintf (stderr,"Error, couldn't open %s\n", opts->mbe_out_path); - } + { + fprintf (stderr,"Error, couldn't open %s\n", opts->mbe_out_path); + } + else opts->mbe_out = 1; - // write magic + // fprintf (opts->mbe_out_f, "%s", ext); fflush (opts->mbe_out_f); diff --git a/src/dsd_frame.c b/src/dsd_frame.c index b5c910c..ef31c55 100644 --- a/src/dsd_frame.c +++ b/src/dsd_frame.c @@ -209,7 +209,7 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->dmr_stereo == 0 && state->synctype == 32) { fprintf (stderr, "%s", KRED); - fprintf (stderr, "Please use the -T option to handle MS Mode with DMR Stereo TDMA Handling\n"); + fprintf (stderr, "Please use XDMA decoding class to decode MS/Simplex \n"); fprintf (stderr, "%s", KNRM); } if (opts->dmr_stereo == 1) @@ -229,7 +229,7 @@ processFrame (dsd_opts * opts, dsd_state * state) closeMbeOutFile (opts, state); state->err_str[0] = 0; fprintf (stderr, "%s", KRED); - fprintf (stderr, "Please use the -T option to handle MS Mode with DMR Stereo TDMA Handling\n"); + fprintf (stderr, "Please use XDMA decoding class to decode MS/Simplex \n"); fprintf (stderr, "%s", KNRM); } if (opts->dmr_stereo == 1) @@ -397,7 +397,8 @@ processFrame (dsd_opts * opts, dsd_state * state) bch_code[index_bch_code] = v; index_bch_code++; } - state->nac = strtol (nac, NULL, 2); + //this one setting bogus nac data + // state->nac = strtol (nac, NULL, 2); // Read the DUID, 4 bits for (i = 0; i < 2; i++) @@ -445,6 +446,10 @@ processFrame (dsd_opts * opts, dsd_state * state) if (new_nac != state->nac) { // NAC fixed by error correction state->nac = new_nac; + if (state->p2_hardset == 0) + { + state->p2_cc = new_nac; + } state->debug_header_errors++; } if (strcmp(new_duid, duid) != 0) { @@ -478,6 +483,8 @@ processFrame (dsd_opts * opts, dsd_state * state) } mbe_initMbeParms (state->cur_mp, state->prev_mp, state->prev_mp_enhanced); state->lastp25type = 2; + state->dmrburstL = 25; + state->currentslot = 0; sprintf (state->fsubtype, " HDU "); processHDU (opts, state); } @@ -497,12 +504,13 @@ processFrame (dsd_opts * opts, dsd_state * state) } } state->lastp25type = 1; + state->dmrburstL = 26; + state->currentslot = 0; sprintf (state->fsubtype, " LDU1 "); state->numtdulc = 0; if (state->payload_algid == 0x81) { fprintf (stderr, "\n"); - //LFSRP (state); //HERE HERE best placement? } processLDU1 (opts, state); @@ -510,6 +518,8 @@ processFrame (dsd_opts * opts, dsd_state * state) else if (strcmp (duid, "22") == 0) { // Logical Link Data Unit 2 + state->dmrburstL = 27; + state->currentslot = 0; if (state->lastp25type != 1) { if (opts->errorbars == 1) @@ -543,6 +553,7 @@ processFrame (dsd_opts * opts, dsd_state * state) else if (strcmp (duid, "33") == 0) { // Terminator with subsequent Link Control + state->dmrburstL = 28; if (opts->errorbars == 1) { printFrameInfo (opts, state); @@ -569,6 +580,7 @@ processFrame (dsd_opts * opts, dsd_state * state) else if (strcmp (duid, "03") == 0) { // Terminator without subsequent Link Control + state->dmrburstL = 28; if (opts->errorbars == 1) { printFrameInfo (opts, state); @@ -589,10 +601,11 @@ processFrame (dsd_opts * opts, dsd_state * state) } else if (strcmp (duid, "13") == 0) { + state->dmrburstL = 29; if (opts->errorbars == 1) { printFrameInfo (opts, state); - fprintf (stderr," TSDU\n"); + fprintf (stderr," TSBK"); } if (opts->resume > 0) { @@ -601,11 +614,11 @@ processFrame (dsd_opts * opts, dsd_state * state) state->lasttg = 0; state->lastsrc = 0; state->lastp25type = 3; - sprintf (state->fsubtype, " TSDU "); + sprintf (state->fsubtype, " TSBK "); // Now processing NID + processTSBK(opts, state); - skipDibit (opts, state, 328-25); } else if (strcmp (duid, "30") == 0) { @@ -676,15 +689,14 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->errorbars == 1) { printFrameInfo (opts, state); - fprintf (stderr," (TSDU)\n"); + fprintf (stderr," (TSBK)\n"); } //state->lastp25type = 0; - // Guess that the state is TSDU + // Guess that the state is TSBK state->lastp25type = 3; - sprintf (state->fsubtype, "(TSDU) "); + sprintf (state->fsubtype, "(TSBK) "); // Now processing NID - skipDibit (opts, state, 328-25); } else if (state->lastp25type == 4) @@ -703,8 +715,8 @@ processFrame (dsd_opts * opts, dsd_state * state) if (opts->errorbars == 1) { printFrameInfo (opts, state); - //fprintf (stderr," duid:%s *Unknown DUID*\n", duid); //prints on dPMR frame 3 - fprintf (stderr, "\n"); //prints on dPMR frame 3 + fprintf (stderr," duid:%s *Unknown DUID*\n", duid); //prints on dPMR frame 3 + // fprintf (stderr, "\n"); //prints on dPMR frame 3 } } } diff --git a/src/dsd_frame_sync.c b/src/dsd_frame_sync.c index dfe2ab3..a6a3c07 100644 --- a/src/dsd_frame_sync.c +++ b/src/dsd_frame_sync.c @@ -295,14 +295,8 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->dmr_payload_p = state->dmr_payload_buf + 200; } - // int valid; - //running estimate_symbol causes an issue with P25 syncing properly - // if (opts->frame_p25p1 != 1) - // { - // valid = estimate_symbol(state->rf_mod, &(state->p25_heuristics), state->last_dibit, symbol, &dibit); - // } - if (1 == 1) //opts->dmr_stereo //opts->frame_dmr == 1 && valid == 0 + if (1 == 1) { if (symbol > state->center) { @@ -474,6 +468,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; + state->dmrburstR = 17; sprintf (state->ftype, "P25 Phase 1"); if (opts->errorbars == 1) { @@ -489,6 +484,7 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; + state->dmrburstR = 17; sprintf (state->ftype, "P25 Phase 1"); if (opts->errorbars == 1) { @@ -616,11 +612,12 @@ getFrameSync (dsd_opts * opts, dsd_state * state) } } //end YSF sync - //P25 P2 sync - strncpy(synctest20, (synctest_p - 19), 20); //double check make sure this is right - if(opts->frame_p25p2 == 1) //(opts->frame_ysf == 1 + + //P25 P2 sync S-ISCH VCH + strncpy(synctest20, (synctest_p - 19), 20); + if(opts->frame_p25p2 == 1) { - if (0 == 0) //opts->inverted_ysf == 0 + if (0 == 0) { if (strcmp(synctest20, P25P2_SYNC) == 0) { @@ -628,40 +625,69 @@ getFrameSync (dsd_opts * opts, dsd_state * state) state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - fprintf (stderr, "\n+P25-P2 Sync"); - opts->inverted_ysf = 0; //should we set this here? - state->lastsynctype = 30; + opts->inverted_p2 = 0; + state->lastsynctype = 35; //35 + if (opts->errorbars == 1) + { + printFrameSync (opts, state, "+P25p2 SISCH", synctest_pos + 1, modulation); + } + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + fprintf (stderr, " WACN [%05llX] SYS [%03llX] NAC [%03llX] ", state->p2_wacn, state->p2_sysid, state->p2_cc); + } + else + { + fprintf (stderr, "%s", KRED); + fprintf (stderr, " P2 Missing Parameters "); + fprintf (stderr, "%s", KNRM); + } if ( opts->monitor_input_audio == 1) { - pa_simple_flush(opts->pulse_raw_dev_out, NULL); } - return (35); + return (35); //35 } } } - if(opts->frame_p25p2 == 1) //(opts->frame_p25p2 == 1 + if(opts->frame_p25p2 == 1) { - if (0 == 0) //inverted p25 + if (0 == 0) { + //S-ISCH VCH if (strcmp(synctest20, INV_P25P2_SYNC) == 0) { state->carrier = 1; state->offset = synctest_pos; state->max = ((state->max) + lmax) / 2; state->min = ((state->min) + lmin) / 2; - fprintf (stderr, "\n-P25-P2 Sync"); - opts->inverted_ysf = 1; //should we set this here? - state->lastsynctype = 31; + opts->inverted_p2 = 1; + if (opts->errorbars == 1) + { + printFrameSync (opts, state, "-P25p2 SISCH", synctest_pos + 1, modulation); + } + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + //fprintf (stderr, "%s", KCYN); + fprintf (stderr, " WACN [%05llX] SYS [%03llX] NAC [%03llX] ", state->p2_wacn, state->p2_sysid, state->p2_cc); + //fprintf (stderr, "%s", KNRM); + } + else + { + fprintf (stderr, "%s", KRED); + fprintf (stderr, " P2 Missing Parameters "); + fprintf (stderr, "%s", KNRM); + } + + state->lastsynctype = 36; //36 if ( opts->monitor_input_audio == 1) { - pa_simple_flush(opts->pulse_raw_dev_out, NULL); } - return (36); + return (36); //36 } } } + //dPMR sync strncpy(synctest, (synctest_p - 23), 24); strncpy(synctest12, (synctest_p - 11), 12); diff --git a/src/dsd_main.c b/src/dsd_main.c index ae4c2df..0af3616 100644 --- a/src/dsd_main.c +++ b/src/dsd_main.c @@ -43,7 +43,6 @@ int pretty_colors() #include "p25p1_heuristics.h" #include "pa_devs.h" -short int butt = 0; char * FM_banner[9] = { " ", @@ -69,6 +68,7 @@ comp (const void *a, const void *b) //struct for checking existence of directory to write to struct stat st = {0}; +char wav_file_directory[1024] = {0}; void noCarrier (dsd_opts * opts, dsd_state * state) @@ -105,10 +105,8 @@ noCarrier (dsd_opts * opts, dsd_state * state) state->repeat = 0; state->nac = 0; state->numtdulc = 0; - //sprintf (state->slot0light, " slot0 "); - //sprintf (state->slot0light, " slot0 "); - sprintf (state->slot1light, ""); - sprintf (state->slot2light, ""); + sprintf (state->slot1light, "%s", ""); + sprintf (state->slot2light, "%s", ""); state->firstframe = 0; if (opts->audio_gain == (float) 0) { @@ -196,6 +194,18 @@ noCarrier (dsd_opts * opts, dsd_state * state) state->dmr_encL = 0; state->dmr_encR = 0; + state->dmrburstL = 17; + state->dmrburstR = 17; + + //reset P2 ESS_B fragments and 4V counter + for (short i = 0; i < 4; i++) + { + state->ess_b[0][i] = 0; + state->ess_b[1][i] = 0; + } + state->fourv_counter[0] = 0; + state->fourv_counter[1] = 0; + } void @@ -237,6 +247,7 @@ initOpts (dsd_opts * opts) opts->lrrp_out_file[0] = 0; opts->symbol_out_f = NULL; opts->symbol_out = 0; + opts->mbe_out = 0; opts->wav_out_f = NULL; opts->wav_out_fR = NULL; opts->wav_out_raw = NULL; @@ -246,18 +257,18 @@ initOpts (dsd_opts * opts) opts->serial_baud = 115200; sprintf (opts->serial_dev, "/dev/ttyUSB0"); opts->resume = 0; - opts->frame_dstar = 1; + opts->frame_dstar = 0; opts->frame_x2tdma = 1; opts->frame_p25p1 = 1; - opts->frame_p25p2 = 0; + opts->frame_p25p2 = 1; opts->frame_nxdn48 = 0; opts->frame_nxdn96 = 0; opts->frame_dmr = 1; opts->frame_dpmr = 0; opts->frame_provoice = 0; opts->mod_c4fm = 1; - opts->mod_qpsk = 1; - opts->mod_gfsk = 1; + opts->mod_qpsk = 0; + opts->mod_gfsk = 0; opts->uvquality = 3; opts->inverted_x2tdma = 1; // most transmitter + scanner + sound card combinations show inverted signals for this opts->inverted_dmr = 0; // most transmitter + scanner + sound card combinations show non-inverted signals for this @@ -279,20 +290,20 @@ initOpts (dsd_opts * opts) opts->pulse_raw_rate_in = 48000; opts->pulse_raw_rate_out = 48000; // opts->pulse_digi_rate_in = 48000; - opts->pulse_digi_rate_out = 8000; // reverted to 8/1, crisper sound, dmr stereo will switch to 24/2 + opts->pulse_digi_rate_out = 24000; //new default for XDMA opts->pulse_raw_in_channels = 1; opts->pulse_raw_out_channels = 1; opts->pulse_digi_in_channels = 1; //2 - opts->pulse_digi_out_channels = 1; //2 + opts->pulse_digi_out_channels = 2; //new default for XDMA - sprintf (opts->output_name, "Auto Detect"); + sprintf (opts->output_name, "XDMA"); opts->pulse_flush = 1; //set 0 to flush, 1 for flushed opts->use_ncurses_terminal = 0; opts->ncurses_compact = 0; opts->ncurses_history = 1; opts->payload = 0; opts->inverted_dpmr = 0; - opts->dmr_stereo = 0; + opts->dmr_stereo = 1; opts->aggressive_framesync = 1; //more aggressive to combat wonk wonk voice decoding //see if initializing these values causes issues elsewhere, if so, then disable. opts->audio_in_type = 0; //this was never initialized, causes issues on rPI 64 (bullseye) if not initialized @@ -305,6 +316,9 @@ initOpts (dsd_opts * opts) opts->monitor_input_audio = 0; + opts->inverted_p2 = 0; + opts->p2counter = 0; + } void @@ -395,10 +409,8 @@ initState (dsd_state * state) state->optind = 0; state->numtdulc = 0; state->firstframe = 0; - //sprintf (state->slot0light, " slot0 "); - //sprintf (state->slot1light, " slot1 "); - sprintf (state->slot1light, ""); - sprintf (state->slot2light, ""); + sprintf (state->slot1light, "%s", ""); + sprintf (state->slot2light, "%s", ""); state->aout_gain = 25; memset (state->aout_max_buf, 0, sizeof (float) * 200); state->aout_max_buf_p = state->aout_max_buf; @@ -435,7 +447,7 @@ initState (dsd_state * state) state->nxdn_last_tg = 0; state->nxdn_cipher_type = 0; state->nxdn_key = 0; - sprintf (state->nxdn_call_type, " "); + sprintf (state->nxdn_call_type, "%s", ""); state->payload_miN = 0; state->dpmr_color_code = -1; @@ -451,41 +463,52 @@ initState (dsd_state * state) state->payload_keyid = 0; state->payload_keyidR = 0; - sprintf (state->dmr_branding, " "); - sprintf (state->dmr_callsign[0][0], ""); - sprintf (state->dmr_callsign[0][1], ""); - sprintf (state->dmr_callsign[0][2], ""); - sprintf (state->dmr_callsign[0][3], ""); - sprintf (state->dmr_callsign[0][4], ""); - sprintf (state->dmr_callsign[0][5], ""); - sprintf (state->dmr_callsign[1][0], ""); - sprintf (state->dmr_callsign[1][1], ""); - sprintf (state->dmr_callsign[1][2], ""); - sprintf (state->dmr_callsign[1][3], ""); - sprintf (state->dmr_callsign[1][4], ""); - sprintf (state->dmr_callsign[1][5], ""); - sprintf (state->dmr_lrrp[0][0], ""); - sprintf (state->dmr_lrrp[0][1], ""); - sprintf (state->dmr_lrrp[0][2], ""); - sprintf (state->dmr_lrrp[0][3], ""); - sprintf (state->dmr_lrrp[0][4], ""); - sprintf (state->dmr_lrrp[0][5], ""); - sprintf (state->dmr_lrrp[1][0], ""); - sprintf (state->dmr_lrrp[1][1], ""); - sprintf (state->dmr_lrrp[1][2], ""); - sprintf (state->dmr_lrrp[1][3], ""); - sprintf (state->dmr_lrrp[1][4], ""); - sprintf (state->dmr_lrrp[1][5], ""); + //init P2 ESS_B fragments and 4V counter + for (short i = 0; i < 4; i++) + { + state->ess_b[0][i] = 0; + state->ess_b[1][i] = 0; + } + state->fourv_counter[0] = 0; + state->fourv_counter[1] = 0; + + sprintf (state->dmr_branding, "%s", ""); + sprintf (state->dmr_callsign[0][0], "%s", ""); + sprintf (state->dmr_callsign[0][1], "%s", ""); + sprintf (state->dmr_callsign[0][2], "%s", ""); + sprintf (state->dmr_callsign[0][3], "%s", ""); + sprintf (state->dmr_callsign[0][4], "%s", ""); + sprintf (state->dmr_callsign[0][5], "%s", ""); + sprintf (state->dmr_callsign[1][0], "%s", ""); + sprintf (state->dmr_callsign[1][1], "%s", ""); + sprintf (state->dmr_callsign[1][2], "%s", ""); + sprintf (state->dmr_callsign[1][3], "%s", ""); + sprintf (state->dmr_callsign[1][4], "%s", ""); + sprintf (state->dmr_callsign[1][5], "%s", ""); + sprintf (state->dmr_lrrp[0][0], "%s", ""); + sprintf (state->dmr_lrrp[0][1], "%s", ""); + sprintf (state->dmr_lrrp[0][2], "%s", ""); + sprintf (state->dmr_lrrp[0][3], "%s", ""); + sprintf (state->dmr_lrrp[0][4], "%s", ""); + sprintf (state->dmr_lrrp[0][5], "%s", ""); + sprintf (state->dmr_lrrp[1][0], "%s", ""); + sprintf (state->dmr_lrrp[1][1], "%s", ""); + sprintf (state->dmr_lrrp[1][2], "%s", ""); + sprintf (state->dmr_lrrp[1][3], "%s", ""); + sprintf (state->dmr_lrrp[1][4], "%s", ""); + sprintf (state->dmr_lrrp[1][5], "%s", ""); state->K = 0; state->R = 0; + state->RR = 0; state->H = 0; state->K1 = 0; state->K2 = 0; state->K3 = 0; state->K4 = 0; + state->M = 0; //force key priority over settings from fid/so - state->dmr_stereo = 0; + state->dmr_stereo = 0; //1, or 0? state->dmrburstL = 17; //initialize at higher value than possible state->dmrburstR = 17; //17 in char array is set for ERR state->dmr_so = 0; @@ -542,6 +565,16 @@ initState (dsd_state * state) state->dmr_encL = 0; state->dmr_encR = 0; + //P2 variables + state->p2_wacn = 0; + state->p2_sysid = 0; + state->p2_cc = 0; + state->p2_hardset = 0; + state->p2_is_lcch = 0; + + state->p2_scramble_offset = 0; + state->p2_vch_chan_num = 0; + #ifdef TRACE_DSD state->debug_sample_index = 0; state->debug_label_file = NULL; @@ -563,7 +596,7 @@ usage () printf ("\n"); printf ("Display Options:\n"); printf (" -N Use NCurses Terminal\n"); - printf (" -use with 2> log.txt at end of command\n"); + printf (" dsd-fme -N 2> log.txt \n"); printf (" -e Show Frame Info and errorbars (default)\n"); printf (" -pe Show P25 encryption sync bits\n"); printf (" -pl Show P25 link control bits\n"); @@ -582,8 +615,11 @@ usage () printf (" -r Read/Play saved mbe data from file(s)\n"); printf (" -g Audio output gain (default = 0 = auto, disable = -1)\n"); printf (" -w Output synthesized speech to a .wav file\n"); + printf (" -T Enable Per Call WAV file saving in XDMA and NXDN decoding classes\n"); + printf (" (Per Call can only be used in Ncurses Terminal!)\n"); + printf (" (Running in console will use static wav files)\n"); //printf (" -a Display port audio devices\n"); - //printf (" -W Monitor Source Audio When No Sync Detected (WIP!)\n"); + //printf (" -W Monitor Source Audio When No Sync Detected (broken!)\n"); printf ("\n"); printf ("RTL-SDR options:\n"); printf (" -c RTL-SDR Frequency\n"); @@ -601,7 +637,8 @@ usage () printf (" -R Resume scan after TDULC frames or any PDU or TSDU\n"); printf ("\n"); printf ("Decoder options:\n"); - printf (" -fa Auto-detect frame type (default)\n"); + printf (" -fa Legacy Auto Detection (old methods default)\n"); + printf (" -ft XDMA P25 and DMR BS/MS frame types (new default)\n"); printf (" -f1 Decode only P25 Phase 1\n"); printf (" -fd Decode only D-STAR\n"); printf (" -fr Decode only DMR\n"); @@ -615,7 +652,7 @@ usage () printf (" -u Unvoiced speech quality (default=3)\n"); printf (" -xx Expect non-inverted X2-TDMA signal\n"); printf (" -xr Expect inverted DMR signal\n"); - printf (" -xd Expect inverted ICOM dPMR signal\n"); //still need to do this + printf (" -xd Expect inverted ICOM dPMR signal\n"); printf ("\n"); printf (" * denotes frame types that cannot be auto-detected.\n"); printf ("\n"); @@ -625,18 +662,19 @@ usage () printf (" (default=36)\n"); printf (" -M Min/Max buffer size for QPSK decision point tracking\n"); printf (" (default=15)\n"); - printf (" -T Enable DMR TDMA Stereo Voice (Two Slot Dual Voices)\n"); - printf (" This feature will open two streams for slot 1 voice and slot 2 voice\n"); - printf (" May Cause Skipping or sync issues if bad signal/errors\n"); - printf (" -F Enable DMR TDMA Stereo Passive Frame Sync\n"); - printf (" This feature will attempt to resync less often due to excessive voice errors\n"); - printf (" Use if skipping occurs, but may cause wonky audio due to loss of good sync\n"); + // printf (" -T Enable DMR TDMA Stereo Voice (Two Slot Dual Voices)\n"); + // printf (" This feature will open two streams for slot 1 voice and slot 2 voice\n"); + // printf (" May Cause Skipping or sync issues if bad signal/errors\n"); + // printf (" -F Enable DMR TDMA Stereo Passive Frame Sync\n"); + // printf (" This feature will attempt to resync less often due to excessive voice errors\n"); + // printf (" Use if skipping occurs, but may cause wonky audio due to loss of good sync\n"); printf (" -Z Log MBE/Frame Payloads to console\n"); printf ("\n"); printf (" -K Manually Enter DMRA Privacy Key (Decimal Value of Key Number)\n"); printf ("\n"); printf (" -H Manually Enter **tera 10 Privacy Key (40-Bit/10-Char Hex Value)\n"); printf (" (32/64-Char values can only be entered in the NCurses Terminal)\n"); + printf ("\n"); printf (" -R Manually Enter NXDN 4800 EHR Scrambler Key Value \n"); printf (" \n"); printf ("\n"); @@ -757,7 +795,7 @@ cleanupAndExit (dsd_opts * opts, dsd_state * state) { closeWavOutFileRaw (opts, state); } - if (opts->dmr_stereo_wav == 1) + if (opts->dmr_stereo_wav == 1) //cause of crash on exit, need to check if NULL first, may need to set NULL when turning off in nterm { closeWavOutFileL (opts, state); closeWavOutFileR (opts, state); @@ -1028,19 +1066,22 @@ main (int argc, char **argv) fprintf (stderr,"Logging LRRP data to ~/lrrp.txt \n"); break; - case 'T': - opts.dmr_stereo = 1; //this value is the end user option - state.dmr_stereo = 1; //this values toggles on and off depending on voice or data handling - opts.pulse_digi_rate_out = 24000; - opts.pulse_digi_out_channels = 2; - sprintf (opts.output_name, "DMR STEREO"); - fprintf (stderr, "%s", KYEL); - fprintf (stderr,"DMR Stereo Sync and Functionality Enabled.\n"); - fprintf (stderr,"DMR Stereo will disable MBE file saving!\n"); - fprintf (stderr,"DMR Stereo per call WAV file can be enabled in the NCurses Terminal!\n"); - fprintf (stderr,"Also consider using -F if playback is too choppy!\n"); - fprintf (stderr, "%s", KNRM); - break; + case 'T': //repurposed to TDMA/NXDN Per Call + sprintf (wav_file_directory, "./WAV"); // /wav, or ./wav + wav_file_directory[1023] = '\0'; + if (stat(wav_file_directory, &st) == -1) + { + fprintf (stderr, "-T %s WAV file directory does not exist\n", wav_file_directory); + fprintf (stderr, "Creating directory %s to save decoded wav files\n", wav_file_directory); + mkdir(wav_file_directory, 0700); //user read write execute, needs execute for some reason or segfault + } + fprintf (stderr,"XDMA and NXDN Per Call Wav File Saving Enabled. (NCurses Terminal Only)\n"); + sprintf (opts.wav_out_file, "./WAV/DSD-FME-X1.wav"); // foward slash here, on wav_file_directory? + sprintf (opts.wav_out_fileR, "./WAV/DSD-FME-X2.wav"); + opts.dmr_stereo_wav = 1; + openWavOutFileL (&opts, &state); //testing for now, will want to move to per call later + openWavOutFileR (&opts, &state); //testing for now, will want to move to per call later + break; case 'F': opts.aggressive_framesync = 0; @@ -1079,6 +1120,7 @@ main (int argc, char **argv) mkdir(opts.mbe_out_dir, 0700); //user read write execute, needs execute for some reason or segfault } else fprintf (stderr,"Writing mbe data files to directory %s\n", opts.mbe_out_dir); + // fprintf (stderr,"Writing mbe data temporarily disabled!\n"); break; case 'c': opts.rtlsdr_center_freq = (uint32_t)atofs(optarg); @@ -1130,23 +1172,33 @@ main (int argc, char **argv) opts.frame_dstar = 1; opts.frame_x2tdma = 1; opts.frame_p25p1 = 1; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 1; opts.frame_dpmr = 0; opts.frame_provoice = 0; - sprintf (opts.output_name, "Auto Detect"); + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; + sprintf (opts.output_name, "Legacy Auto"); } else if (optarg[0] == 'd') { opts.frame_dstar = 1; opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; opts.frame_dpmr = 0; opts.frame_provoice = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; state.rf_mod = 0; sprintf (opts.output_name, "D-STAR"); fprintf (stderr,"Decoding only D-STAR frames.\n"); @@ -1156,11 +1208,16 @@ main (int argc, char **argv) opts.frame_dstar = 0; opts.frame_x2tdma = 1; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; opts.frame_dpmr = 0; opts.frame_provoice = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "X2-TDMA"); fprintf (stderr,"Decoding only X2-TDMA frames.\n"); } @@ -1169,6 +1226,7 @@ main (int argc, char **argv) opts.frame_dstar = 0; opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; @@ -1180,6 +1238,10 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 1; state.rf_mod = 2; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "ProVoice"); fprintf (stderr,"Setting symbol rate to 9600 / second\n"); fprintf (stderr,"Decoding only ProVoice frames.\n"); @@ -1189,15 +1251,20 @@ main (int argc, char **argv) opts.frame_dstar = 0; opts.frame_x2tdma = 0; opts.frame_p25p1 = 1; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; opts.frame_dpmr = 0; opts.frame_provoice = 0; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; opts.mod_c4fm = 1; opts.mod_qpsk = 0; opts.mod_gfsk = 0; state.rf_mod = 0; // + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; sprintf (opts.output_name, "P25P1"); fprintf (stderr,"Decoding only P25 Phase 1 frames.\n"); } @@ -1206,6 +1273,7 @@ main (int argc, char **argv) opts.frame_dstar = 0; opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 1; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; @@ -1218,6 +1286,10 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; state.rf_mod = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "NXDN48"); fprintf (stderr,"Setting symbol rate to 2400 / second\n"); fprintf (stderr,"Decoding only NXDN 4800 baud frames.\n"); @@ -1228,6 +1300,7 @@ main (int argc, char **argv) opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; opts.frame_p25p2 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; @@ -1240,6 +1313,10 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; state.rf_mod = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "YSF"); fprintf (stderr,"Setting symbol rate to 2400 / second\n"); fprintf (stderr,"Decoding only YSF frames.\nNot working yet!\n"); @@ -1262,14 +1339,43 @@ main (int argc, char **argv) opts.mod_qpsk = 1; opts.mod_gfsk = 0; state.rf_mod = 0; + opts.dmr_stereo = 1; + state.dmr_stereo = 1; sprintf (opts.output_name, "P25-P2"); - fprintf (stderr,"Decoding P25-P2 frames.\nNot working yet!\n"); + fprintf (stderr,"Decoding P25-P2 frames C4FM or OP25 Symbol Captures!\n"); } + else if (optarg[0] == 't') + { + opts.frame_dstar = 0; + opts.frame_x2tdma = 1; + opts.frame_p25p1 = 1; + opts.frame_p25p2 = 1; + opts.inverted_p2 = 0; + opts.frame_nxdn48 = 0; + opts.frame_nxdn96 = 0; + opts.frame_dmr = 1; + opts.frame_dpmr = 0; + opts.frame_provoice = 0; + opts.frame_ysf = 0; + opts.mod_c4fm = 1; + opts.mod_qpsk = 0; + opts.mod_gfsk = 0; + //Need a new demodulator is needed to handle p2 cqpsk + //or consider using an external modulator (GNURadio?) + state.rf_mod = 0; + opts.dmr_stereo = 1; + //state.dmr_stereo = 1; + opts.pulse_digi_rate_out = 24000; + opts.pulse_digi_out_channels = 2; + sprintf (opts.output_name, "XDMA"); + fprintf (stderr,"Decoding XDMA P25 and DMR\n"); + } else if (optarg[0] == 'n') { opts.frame_dstar = 0; opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 1; opts.frame_dmr = 0; @@ -1279,6 +1385,10 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; state.rf_mod = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "NXDN96"); fprintf (stderr,"Decoding only NXDN 9600 baud frames.\n"); } @@ -1287,6 +1397,7 @@ main (int argc, char **argv) opts.frame_dstar = 0; opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 1; @@ -1296,8 +1407,12 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; // state.rf_mod = 0; // + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "DMR LEH"); - //fprintf(stderr, "Notice: DMR cannot autodetect polarity. \n Use -xr option if Inverted Signal expected.\n"); + fprintf(stderr, "Notice: DMR cannot autodetect polarity. \n Use -xr option if Inverted Signal expected.\n"); fprintf (stderr,"Decoding only DMR frames.\n"); } else if (optarg[0] == 'm') @@ -1305,6 +1420,7 @@ main (int argc, char **argv) opts.frame_dstar = 0; opts.frame_x2tdma = 0; opts.frame_p25p1 = 0; + opts.frame_p25p2 = 0; opts.frame_nxdn48 = 0; opts.frame_nxdn96 = 0; opts.frame_dmr = 0; @@ -1317,6 +1433,10 @@ main (int argc, char **argv) opts.mod_qpsk = 0; opts.mod_gfsk = 0; state.rf_mod = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "dPMR"); fprintf(stderr, "Notice: dPMR cannot autodetect polarity. \n Use -xd option if Inverted Signal expected.\n"); fprintf(stderr, "Decoding only dPMR frames.\n"); @@ -1326,34 +1446,34 @@ main (int argc, char **argv) case 'm': if (optarg[0] == 'a') { - //opts.mod_c4fm = 1; - //opts.mod_qpsk = 1; - //opts.mod_gfsk = 1; - //state.rf_mod = 0; + opts.mod_c4fm = 1; + opts.mod_qpsk = 1; + opts.mod_gfsk = 1; + state.rf_mod = 0; } else if (optarg[0] == 'c') { - //opts.mod_c4fm = 1; - //opts.mod_qpsk = 0; - //opts.mod_gfsk = 0; - //state.rf_mod = 0; - //fprintf (stderr,"Enabling only C4FM modulation optimizations.\n"); + opts.mod_c4fm = 1; + opts.mod_qpsk = 0; + opts.mod_gfsk = 0; + state.rf_mod = 0; + fprintf (stderr,"Enabling only C4FM modulation optimizations.\n"); } else if (optarg[0] == 'g') { - //opts.mod_c4fm = 0; - //opts.mod_qpsk = 0; - //opts.mod_gfsk = 1; - //state.rf_mod = 2; - //fprintf (stderr,"Enabling only GFSK modulation optimizations.\n"); + opts.mod_c4fm = 0; + opts.mod_qpsk = 0; + opts.mod_gfsk = 1; + state.rf_mod = 2; + fprintf (stderr,"Enabling only GFSK modulation optimizations.\n"); } else if (optarg[0] == 'q') { - //opts.mod_c4fm = 0; - //opts.mod_qpsk = 1; - //opts.mod_gfsk = 0; - //state.rf_mod = 1; - //fprintf (stderr,"Enabling only QPSK modulation optimizations.\n"); + opts.mod_c4fm = 0; + opts.mod_qpsk = 1; + opts.mod_gfsk = 0; + state.rf_mod = 1; + fprintf (stderr,"Enabling only QPSK modulation optimizations.\n"); } break; case 'u': @@ -1416,6 +1536,10 @@ main (int argc, char **argv) opts.playfiles = 1; opts.errorbars = 0; opts.datascope = 0; + opts.pulse_digi_rate_out = 8000; + opts.pulse_digi_out_channels = 1; + opts.dmr_stereo = 0; + state.dmr_stereo = 0; sprintf (opts.output_name, "MBE Playback"); state.optind = optind; break; diff --git a/src/dsd_mbe.c b/src/dsd_mbe.c index 8842444..400e40b 100644 --- a/src/dsd_mbe.c +++ b/src/dsd_mbe.c @@ -202,7 +202,8 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a mbe_demodulateAmbe3600x2450Data (ambe_fr); state->errs2 += mbe_eccAmbe3600x2450Data (ambe_fr, ambe_d); - if (state->nxdn_cipher_type == 0x01 && state->R > 0) + if ( (state->nxdn_cipher_type == 0x01 && state->R > 0) || + (state->M == 1 && state->R > 0) ) { if (state->payload_miN == 0) @@ -245,7 +246,8 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a mbe_demodulateAmbe3600x2450Data (ambe_fr); state->errs2 += mbe_eccAmbe3600x2450Data (ambe_fr, ambe_d); - if (state->K > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x10) + if ( (state->K > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x10) || + (state->K > 0 && state->M == 1) ) { k = Pr[state->K]; k = ( ((k & 0xFF0F) << 32 ) + (k << 16) + k ); @@ -256,7 +258,8 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } } - if (state->K1 > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x68) + if ( (state->K1 > 0 && state->dmr_so & 0x40 && state->payload_keyid == 0 && state->dmr_fid == 0x68) || + (state->K1 > 0 && state->M == 1) ) { int pos = 0; @@ -338,7 +341,8 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a mbe_demodulateAmbe3600x2450Data (ambe_fr); state->errs2R += mbe_eccAmbe3600x2450Data (ambe_fr, ambe_d); - if (state->K > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x10) + if ( (state->K > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x10) || + (state->K > 0 && state->M == 1) ) { k = Pr[state->K]; k = ( ((k & 0xFF0F) << 32 ) + (k << 16) + k ); @@ -349,7 +353,8 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } } - if (state->K1 > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x68) + if ( (state->K1 > 0 && state->dmr_soR & 0x40 && state->payload_keyidR == 0 && state->dmr_fidR == 0x68) || + (state->K1 > 0 && state->M == 1)) { int pos = 0; @@ -451,7 +456,23 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a { state->dmr_encL = 1; } + + //checkdown for P25 1 and 2 + else if (state->payload_algid != 0 && state->payload_algid != 0x80) + { + state->dmr_encL = 1; + } else state->dmr_encL = 0; + + //second checkdown for P25-2 WACN, SYSID, and CC set + if (state->synctype == 35 || state->synctype == 36) + { + if (state->p2_wacn == 0 || state->p2_sysid == 0 || state->p2_cc == 0) + { + state->dmr_encL = 1; + } + } + if (state->dmr_encL == 0 || opts->dmr_mute_encL == 0) { state->debug_audio_errors += state->errs2; @@ -470,7 +491,23 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a { state->dmr_encR = 1; } + + //checkdown for P25 1 and 2 + else if (state->payload_algidR != 0 && state->payload_algidR != 0x80) + { + state->dmr_encR = 1; + } else state->dmr_encR = 0; + + //second checkdown for P25-2 WACN, SYSID, and CC set + if (state->synctype == 35 || state->synctype == 36) + { + if (state->p2_wacn == 0 || state->p2_sysid == 0 || state->p2_cc == 0) + { + state->dmr_encR = 1; + } + } + if (state->dmr_encR == 0 || opts->dmr_mute_encR == 0) { state->debug_audio_errorsR += state->errs2R; @@ -500,7 +537,7 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } //per call can only be used when ncurses terminal is active since we use its call history matrix for when to record - if (opts->use_ncurses_terminal == 1 && opts->dmr_stereo_wav == 1 && opts->dmr_stereo == 1 && state->currentslot == 0) + if (opts->dmr_stereo_wav == 1 && opts->dmr_stereo == 1 && state->currentslot == 0) //opts->use_ncurses_terminal == 1 && { if (state->dmr_encL == 0 || opts->dmr_mute_encL == 0) { @@ -510,7 +547,7 @@ processMbeFrame (dsd_opts * opts, dsd_state * state, char imbe_fr[8][23], char a } //per call can only be used when ncurses terminal is active since we use its call history matrix for when to record - if (opts->use_ncurses_terminal == 1 && opts->dmr_stereo_wav == 1 && opts->dmr_stereo == 1 && state->currentslot == 1) + if (opts->dmr_stereo_wav == 1 && opts->dmr_stereo == 1 && state->currentslot == 1) //opts->use_ncurses_terminal == 1 && { if (state->dmr_encR == 0 || opts->dmr_mute_encR == 0) { diff --git a/src/dsd_ncurses.c b/src/dsd_ncurses.c index 30df074..0755cb1 100644 --- a/src/dsd_ncurses.c +++ b/src/dsd_ncurses.c @@ -46,6 +46,9 @@ struct sockaddr_in address; uint32_t temp_freq = -1; // +//struct for checking existence of directory to write to +struct stat st_wav = {0}; + int reset = 0; int tg; @@ -61,61 +64,64 @@ int dcc = -1; int i = 0; char versionstr[25]; unsigned long long int call_matrix[33][6]; -//current news story for tagline -//https://www.facebook.com/watch/?ref=external&v=1254505325380758 + char * FM_bannerN[9] = { - " ESC or Arrow Keys For Menu ", - " ██████╗ ██████╗██████╗    ███████╗███╗ ███╗███████╗ 'They ain't wrong, ", - " ██╔══██╗██╔════╝██╔══██╗   ██╔════╝████╗ ████║██╔════╝ I ain't blame the ", - " ██║ ██║╚█████╗ ██║ ██║   █████╗ ██╔████╔██║█████╗ police...they just ", - " ██║ ██║ ╚═══██╗██║ ██║   ██╔══╝ ██║╚██╔╝██║██╔══╝ protect the people.", - " ██████╔╝██████╔╝██████╔╝   ██║ ██║ ╚═╝ ██║███████╗ I ain't even mad at", - " ╚═════╝ ╚═════╝ ╚═════╝    ╚═╝ ╚═╝ ╚═╝╚══════╝ y'all..' - FM News ", - " ", - " " + " ESC or Arrow Keys For Menu ", + " ██████╗ ██████╗██████╗    ███████╗███╗ ███╗███████╗ ", + " ██╔══██╗██╔════╝██╔══██╗   ██╔════╝████╗ ████║██╔════╝ ", + " ██║ ██║╚█████╗ ██║ ██║   █████╗ ██╔████╔██║█████╗ ", + " ██║ ██║ ╚═══██╗██║ ██║   ██╔══╝ ██║╚██╔╝██║██╔══╝ ", + " ██████╔╝██████╔╝██████╔╝   ██║ ██║ ╚═╝ ██║███████╗ ", + " ╚═════╝ ╚═════╝ ╚═════╝    ╚═╝ ╚═╝ ╚═╝╚══════╝ ", + " ", + " " }; -char * SyncTypes[38] = { - "+P25P1", - "-P25P1", - "+X2TDMA DATA", - "-X2TDMA DATA", - "+X2TDMA VOICE", - "-X2TDMA VOICE", - "+DSTAR", - "-DSTAR", - "+NXDN VOICE", - "-NXDN VOICE", - "+DMR DATA", - "-DMR DATA", - "+DMR VOICE", - "-DMR VOICE", - "+PROVOICE", - "-PROVOICE", - "+NXDN DATA", - "-NXDN DATA", - "+DSTAR HD", - "-DSTAR HD", - "+dPMR", - "+dPMR", - "+dPMR", - "+dPMR", - "-dPMR", - "-dPMR", - "-dPMR", - "-dPMR", - "+NXDN (sync only)", - "-NXDN (sync only)", - "+YSF", - "-YSF", +char * SyncTypes[44] = { + "P25P1", + "P25P1", + "X2TDMA DATA", + "X2TDMA DATA", + "X2TDMA VOICE", + "X2TDMA VOICE", + "DSTAR", + "DSTAR", + "NXDN VOICE", + "NXDN VOICE", + "DMR DATA", //10 + "DMR DATA", + "DMR VOICE", + "DMR VOICE", + "PROVOICE", + "PROVOICE", + "NXDN DATA", + "NXDN DATA", + "DSTAR HD", + "DSTAR HD", + "dPMR", //20 + "dPMR", + "dPMR", + "dPMR", + "dPMR", + "dPMR", + "dPMR", + "dPMR", + "NXDN (sync only)", + "NXDN (sync only)", + "YSF", //30 + "YSF", "DMR MS VOICE", "DMR MS DATA", "DMR RC DATA", - "+P25-P2", - "-P25-P2" + "P25P2", + "P25P2", + "FDMA", //37 + "TDMA", //38 + "", + "" }; -char * DMRBusrtTypes[18] = { +char * DMRBusrtTypes[32] = { "PI Header ", "VOICE LC HDR ", "TLC ", @@ -128,12 +134,26 @@ char * DMRBusrtTypes[18] = { "Slot Idle ", "Rate 1 DATA ", "ERR ", //These values for ERR may be Reserved for use in future? - "ERR ", - "ERR ", - "ERR ", - "ERR ", - "Voice ", //Using 16 for Voice since its higher than possible value? - "INITIAL " //17 is assigned on start up + "DUID ERR ", + "R-S ERR ", + "CRC ERR ", + "NULL ", + "Voice ", //Using 16 for Voice since its higher than possible value in DMR + "INITIAL ", //17 is assigned on start up + "INITIAL ", + "INITIAL ",//expanded to include P1/2 signalling + "MAC PTT", //20 + "MAC ACTIVE", //21 + "MAC HANGTIME", //22 + "MAC PTT END", //23 + "MAC IDLE", //24 + "HDU", + "VOICE LDU", //26 + "VOICE LDU", + "TDU/TDULC", + "TSBK", + "MAC_SIGNAL", + "MAC_SIGNAL" }; @@ -174,14 +194,14 @@ int starty = 0; char *choicesc[] = { "Return", - "Save Decoded Audio WAV (NO DMR STEREO!)", + "Save Decoded Audio WAV (Legacy Mode)", "Save Signal to Symbol Capture Bin", - "Toggle Muting Enrypted Traffic ", - "Per Call WAV REC (DMR Stereo/MS)", + "Toggle Muting Encrypted Traffic ", + "Save Per Call Decoded WAV (XDMA and NXDN)", "Setup and Start RTL Input ", - "Pulse Audio 48kHz Output", - "Pulse Audio 8kHz Output (NO DMR STEREO!)", - "Reset States and Heuristics", + "Retune RTL Dongle ", + " ", + " ", //removing options no longer necesary or not recommended "Toggle NCurses Compact Mode", "Toggle NCurses Call History", "Stop All Decoded WAV Saving", @@ -189,17 +209,17 @@ char *choicesc[] = { "Replay Last Symbol Capture Bin", "Stop & Close Symbol Capture Bin Playback", "Stop & Close Symbol Capture Bin Saving", - "Retune RTL Dongle", + " ", "Resume Decoding" }; char *choices[] = { "Resume Decoding", - "Decode Auto**", - "Decode ProVoice", + "Decode Legacy Auto**", + "Decode XDMA (P25 and DMR BS/MS)", "Decode D-STAR*", "Decode P25-P1*", - "Decode DMR (STEREO BS/MS)", + "Decode ProVoice", "Decode DMR* (LEH)", "Decode dPMR", "Decode NXDN48", @@ -209,7 +229,7 @@ char *choices[] = { "Privacy Key Entry", "Reset Call History", "Toggle Payloads to Console", - " ", //spacer + "Manually Set P2 Parameters", //16 "Input & Output Options", "LRRP Data to File", "Exit DSD-FME", @@ -373,11 +393,21 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) { info_win = newwin(6, WIDTH+18, starty, startx+20); box (info_win, 0, 0); - mvwprintw(info_win, 2, 2, " Auto Decoding can only detect the following:."); + mvwprintw(info_win, 2, 2, " Legacy Auto can only detect the following:"); mvwprintw(info_win, 3, 2, " P25-P1, D-STAR, DMR LEH, and X2-TDMA"); wrefresh(info_win); } + if (highlight == 3) + { + info_win = newwin(7, WIDTH+18, starty, startx+20); + box (info_win, 0, 0); + mvwprintw(info_win, 2, 2, " XDMA Decoding Class Supports the following:"); + mvwprintw(info_win, 3, 2, " P25-P1, P25-P2, DMR Stereo BS/MS and X2-TDMA"); + mvwprintw(info_win, 4, 2, " --C4FM / FSK4 only, and OP25 P2 Capture Bins"); + wrefresh(info_win); + } + //Input Output Options if (choice == 17) { @@ -429,7 +459,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) wscanw(entry_win, "%s", &opts->symbol_out_file); noecho(); - if (opts->symbol_out_file[0] != NULL) + if (opts->symbol_out_file[0] != 0) //NULL { opts->symbol_out = 1; //set flag to 1 openSymbolOutFile (opts, state); @@ -460,14 +490,21 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) } if (choicec == 5) { + char wav_file_directory[1024]; + sprintf (wav_file_directory, "./WAV"); + wav_file_directory[1023] = '\0'; + if (stat(wav_file_directory, &st_wav) == -1) + { + fprintf (stderr, "%s wav file directory does not exist\n", wav_file_directory); + fprintf (stderr, "Creating directory %s to save decoded wav files\n", wav_file_directory); + mkdir(wav_file_directory, 0700); + } opts->dmr_stereo_wav = 1; //catch all in case of no file name set, won't crash or something - sprintf (opts->wav_out_file, "DSD-FME-SLOT1.wav"); - //sprintf (opts->wav_out_file, "%s %s S1 - CC %d - TG %d - RD %d.wav", getDateN(), getTimeN(), dcc, tg, rd); - sprintf (opts->wav_out_fileR, "DSD-FME-SLOT2.wav"); - //sprintf (opts->wav_out_fileR, "%s %s S2 - CC %d - TG %d - RD %d.wav", getDateN(), getTimeN(), dcc, tg, rd); - openWavOutFileL (opts, state); //testing for now, will want to move to per call later - openWavOutFileR (opts, state); //testing for now, will want to move to per call later + sprintf (opts->wav_out_file, "./WAV/DSD-FME-X1.wav"); + sprintf (opts->wav_out_fileR, "./WAV/DSD-FME-X2.wav"); + openWavOutFileL (opts, state); + openWavOutFileR (opts, state); } @@ -571,66 +608,18 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) noecho(); refresh(); - // opts->rtlsdr_center_freq = 851800000; //test with pV LCN 2 - //opts->rtl_gain_value = 46; - //opts->rtl_dev_index = 1; - // opts->rtl_bandwidth = 24; //12 or 24. - //opts->rtlsdr_ppm_error = -1; - //opts->rtl_udp_port = 6020; - //fprintf (stderr, "Confirm = %d", confirm); //works well, but can't release dongle later, so its a one way trip until exit if (confirm == 1) { opts->audio_in_type = 3; //RTL input, only set this on confirm - //open_rtlsdr_stream(opts); //may move to outside of this box so we return to the normal ncurses term first choicec = 18; //exit to decoder screen only if confirmed, otherwise, just go back } - // if (confirm != 1) - // { - // opts->audio_in_type = 0; - // } - #endif } - if (choicec == 7) - { - if (opts->dmr_stereo == 0) - { - opts->pulse_digi_rate_out = 48000; - //fprintf (stderr, "Rate Out set to 48kHz\n"); - } - if (opts->dmr_stereo == 1) - { - opts->pulse_digi_rate_out = 24000; - //fprintf (stderr, "Rate Out set to 24kHz\n"); - } - - } - - if (choicec == 8) - { - if (opts->dmr_stereo == 0) - { - opts->pulse_digi_rate_out = 8000; - //fprintf (stderr, "Rate Out set to 8kHz\n"); - } - - if (opts->dmr_stereo == 1) - { - opts->pulse_digi_rate_out = 24000; //always force 24kHz with DMR Stereo - //fprintf (stderr, "Rate Out set to 16kHz\n"); - } - } - - if (choicec == 9) - { - resetState (state); //use sparingly, may cause memory leak - } - if (choicec == 10) { if (opts->ncurses_compact == 0) @@ -657,8 +646,8 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) closeWavOutFileL (opts, state); closeWavOutFileR (opts, state); //closeWavOutFileRaw (opts, state); - sprintf (opts->wav_out_file, ""); - sprintf (opts->wav_out_fileR, ""); + sprintf (opts->wav_out_file, "%s", ""); + sprintf (opts->wav_out_fileR, "%s", ""); opts->dmr_stereo_wav = 0; } @@ -678,7 +667,6 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) if (stat(opts->audio_in_dev, &stat_buf) != 0) { fprintf (stderr,"Error, couldn't open %s\n", opts->audio_in_dev); - //exit(1); goto SKIP; } if (S_ISREG(stat_buf.st_mode)) @@ -696,7 +684,6 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) if (stat(opts->audio_in_dev, &stat_buf) != 0) { fprintf (stderr,"Error, couldn't open %s\n", opts->audio_in_dev); - //exit(1); goto SKIPR; } if (S_ISREG(stat_buf.st_mode)) @@ -728,10 +715,10 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) { if (opts->symbol_out == 1) { - if (opts->symbol_out_file[0] != NULL) //check first, or issuing a second fclose will crash the SOFTWARE + if (opts->symbol_out_file[0] != 0) //NULL { fclose(opts->symbol_out_f); //free(): double free detected in tcache 2 (this is a new one) happens when closing more than once - sprintf (opts->audio_in_dev, opts->symbol_out_file); //swap output bin filename to input for quick replay + sprintf (opts->audio_in_dev, "%s", opts->symbol_out_file); //swap output bin filename to input for quick replay } opts->symbol_out = 0; //set flag to 1 @@ -740,7 +727,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) choicec = 18; //exit } - if (choicec == 17) //RTL UDP Retune + if (choicec == 7) //RTL UDP Retune { //read in new rtl frequency #ifdef USE_RTLSDR @@ -769,8 +756,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) //return choice = 0; choicec = 0; - //highlightb = 1; - //highlight = 1; + keypad(test_win, FALSE); keypad(menu_win, TRUE); delwin(test_win); @@ -783,35 +769,33 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) //exit choice = 1; choicec = 0; - //highlightb = 0; - //highlight = 0; + keypad(test_win, FALSE); keypad(menu_win, TRUE); delwin(test_win); print_menu(menu_win, highlight); - //wrefresh(menu_win); break; } } clrtoeol(); //clear to end of line? refresh(); - //endwin(); //causes quick blink in and out of ncursesprinter, not required } - //Cheat Code Entry + //Privacy Key Entry if (choice == 13) { state->payload_keyid = 0; state->payload_keyidR = 0; short int option = 0; - entry_win = newwin(10, WIDTH+6, starty+10, startx+10); + entry_win = newwin(11, WIDTH+6, starty+10, startx+10); box (entry_win, 0, 0); mvwprintw(entry_win, 2, 2, "Key Type Selection"); mvwprintw(entry_win, 3, 2, " "); mvwprintw(entry_win, 4, 2, "1 - DMRA Privacy "); mvwprintw(entry_win, 5, 2, "2 - **tera Privacy "); mvwprintw(entry_win, 6, 2, "3 - NXDN Scrambler "); - mvwprintw(entry_win, 7, 3, " "); + mvwprintw(entry_win, 7, 2, "4 - Force Key Priority "); + mvwprintw(entry_win, 8, 3, " "); echo(); refresh(); wscanw(entry_win, "%d", &option); @@ -900,6 +884,15 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) state->K = 0x7FFF; } } + //toggle enforcement of privacy key over enc bit set on traffic + if (option == 4) + { + if (state->M == 0) + { + state->M = 1; + } + else state->M = 0; + } if (state->K == 0 && state->K1 == 0) { opts->dmr_mute_encL = 1; @@ -915,7 +908,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) resetState (state); //use sparingly, may cause memory leak state->samplesPerSymbol = 10; state->symbolCenter = 4; - sprintf (opts->output_name, "Auto Detect"); + sprintf (opts->output_name, "Legacy Auto"); opts->dmr_stereo = 0; //this value is the end user option state->dmr_stereo = 0; //this values toggles on and off depending on voice or data handling opts->pulse_digi_rate_out = 8000; @@ -923,6 +916,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->frame_dstar = 1; opts->frame_x2tdma = 1; opts->frame_p25p1 = 1; + opts->frame_p25p2 = 0; opts->frame_nxdn48 = 0; opts->frame_nxdn96 = 0; opts->frame_dmr = 1; @@ -933,10 +927,10 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->mod_gfsk = 0; state->rf_mod = 0; opts->unmute_encrypted_p25 = 0; - break; //break? + break; } - if (choice == 3) + if (choice == 6) { //ProVoice Specifics resetState (state); //use sparingly, may cause memory leak @@ -950,6 +944,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->frame_dstar = 0; opts->frame_x2tdma = 0; opts->frame_p25p1 = 0; + opts->frame_p25p2 = 0; opts->frame_nxdn48 = 0; opts->frame_nxdn96 = 0; opts->frame_dmr = 0; @@ -975,6 +970,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->frame_dstar = 1; opts->frame_x2tdma = 0; opts->frame_p25p1 = 0; + opts->frame_p25p2 = 0; opts->frame_nxdn48 = 0; opts->frame_nxdn96 = 0; opts->frame_dmr = 0; @@ -993,6 +989,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) //P25 P1 resetState (state); //use sparingly, may cause memory leak opts->frame_p25p1 = 1; + opts->frame_p25p2 = 0; state->samplesPerSymbol = 10; state->symbolCenter = 4; state->rf_mod = 0; @@ -1017,25 +1014,25 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) break; } - if (choice == 6) + if (choice == 3) { - //DMR Stereo + //XDMA Stereo P25 1, 2, and DMR resetState (state); //use sparingly, seems to cause issue when switching back to other formats opts->frame_dmr = 1; state->samplesPerSymbol = 10; state->symbolCenter = 4; state->rf_mod = 0; - sprintf (opts->output_name, "DMR STEREO"); + sprintf (opts->output_name, "XDMA"); opts->dmr_stereo = 1; //this value is the end user option state->dmr_stereo = 1; //this values toggles on and off depending on voice or data handling opts->pulse_digi_rate_out = 24000; opts->pulse_digi_out_channels = 2; opts->frame_dstar = 0; - opts->frame_x2tdma = 0; - opts->frame_p25p1 = 0; + opts->frame_x2tdma = 1; + opts->frame_p25p1 = 1; + opts->frame_p25p2 = 1; opts->frame_nxdn48 = 0; opts->frame_nxdn96 = 0; - //opts->frame_dmr = 0; opts->frame_dpmr = 0; opts->frame_provoice = 0; opts->mod_c4fm = 1; @@ -1094,7 +1091,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->mod_qpsk = 0; opts->mod_gfsk = 0; state->rf_mod = 0; - opts->unmute_encrypted_p25 = 0; + //opts->unmute_encrypted_p25 = 0; break; } if (choice == 9) @@ -1119,7 +1116,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->frame_dpmr = 0; opts->frame_provoice = 0; opts->mod_c4fm = 1; - opts->unmute_encrypted_p25 = 0; + //opts->unmute_encrypted_p25 = 0; // opts->mod_qpsk = 0; // opts->mod_gfsk = 0; // state->rf_mod = 0; @@ -1147,7 +1144,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->mod_qpsk = 0; opts->mod_gfsk = 0; state->rf_mod = 0; - opts->unmute_encrypted_p25 = 0; + //opts->unmute_encrypted_p25 = 0; } if (choice == 11) { @@ -1173,7 +1170,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) opts->mod_qpsk = 0; opts->mod_gfsk = 0; state->rf_mod = 0; - opts->unmute_encrypted_p25 = 0; + //opts->unmute_encrypted_p25 = 0; } if (choice == 12) { @@ -1205,6 +1202,18 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) call_matrix[k][4] = 0; call_matrix[k][5] = 0; } + src = 0; + rn = 0; + tgn = 0; + dcc = 0; + tg = 0; + tgR = 0; + rd = 0; + rdR = 0; + state->lastsrc = 0; + state->lastsrcR = 0; + state->lasttg = 0; + state->lasttgR = 0; } if (choice == 15) //toggle payload printing @@ -1222,6 +1231,43 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) } if (choice == 16) { + //hardset P2 WACN, SYSID, and NAC + entry_win = newwin(6, WIDTH+16, starty+10, startx+10); + box (entry_win, 0, 0); + mvwprintw(entry_win, 2, 2, " Enter Phase 2 WACN"); + mvwprintw(entry_win, 3, 3, " "); + echo(); + refresh(); + wscanw(entry_win, "%X", &state->p2_wacn); + noecho(); + + entry_win = newwin(6, WIDTH+16, starty+10, startx+10); + box (entry_win, 0, 0); + mvwprintw(entry_win, 2, 2, " Enter Phase 2 SYSID"); + mvwprintw(entry_win, 3, 3, " "); + echo(); + refresh(); + wscanw(entry_win, "%X", &state->p2_sysid); + noecho(); + + entry_win = newwin(6, WIDTH+16, starty+10, startx+10); + box (entry_win, 0, 0); + mvwprintw(entry_win, 2, 2, " Enter Phase 2 NAC/CC"); + mvwprintw(entry_win, 3, 3, " "); + echo(); + refresh(); + wscanw(entry_win, "%X", &state->p2_cc); + noecho(); + + //need handling to truncate larger than expected values + + //set our hardset flag to 1 if values inserted, else set to 0 so we can attempt to get them from TSBK/LCCH + if (state->p2_wacn != 0 && state->p2_sysid != 0 && state->p2_cc != 0) + { + state->p2_hardset = 1; + } + else state->p2_hardset = 0; + } if (choice == 18) @@ -1250,7 +1296,7 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) strncpy (filepath, home_dir, strlen(home_dir) + 1); strncat (filepath, filename, strlen(filename) + 1); //assign home directory/filename to lrrp_out_file - sprintf (opts->lrrp_out_file, filepath); + sprintf (opts->lrrp_out_file, "%s", filepath); //double check make sure this still works opts->lrrp_file_output = 1; } @@ -1272,21 +1318,21 @@ void ncursesMenu (dsd_opts * opts, dsd_state * state) refresh(); wscanw(entry_win, "%s", &opts->lrrp_out_file); noecho(); - if (opts->lrrp_out_file[0] != NULL) + if (opts->lrrp_out_file[0] != 0) //NULL { opts->lrrp_file_output = 1; } else { opts->lrrp_file_output = 0; - sprintf (opts->lrrp_out_file, ""); + sprintf (opts->lrrp_out_file, "%s", ""); opts->lrrp_out_file[0] = 0; } } else { opts->lrrp_file_output = 0; - sprintf (opts->lrrp_out_file, ""); + sprintf (opts->lrrp_out_file, "%s", ""); opts->lrrp_out_file[0] = 0; } } @@ -1364,11 +1410,11 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //carrier reset if (state->carrier == 0) //reset these to 0 when no carrier { - sprintf(state->dmr_branding, " "); + sprintf(state->dmr_branding, "%s", ""); } //set lls sync types - if (state->synctype >= 0 && state->synctype < 35) //not sure if this will be okay + if (state->synctype >= 0 && state->synctype < 37) //not sure if this will be okay { lls = state->synctype; } @@ -1378,14 +1424,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { for (short i = 0; i < 6; i++) { - sprintf (state->dmr_callsign[0][i], ""); + sprintf (state->dmr_callsign[0][i], "%s", ""); } } if (state->dmrburstR != 16 || state->carrier == 0) { for (short i = 0; i < 6; i++) { - sprintf (state->dmr_callsign[1][i], ""); + sprintf (state->dmr_callsign[1][i], "%s", ""); } } @@ -1394,14 +1440,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { for (short i = 0; i < 6; i++) { - sprintf (state->dmr_lrrp[0][i], ""); + sprintf (state->dmr_lrrp[0][i], "%s", ""); } } if (state->dmrburstR == 16 || state->carrier == 0) { for (short i = 0; i < 6; i++) { - sprintf (state->dmr_lrrp[1][i], ""); + sprintf (state->dmr_lrrp[1][i], "%s", ""); } } @@ -1459,19 +1505,33 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) } - //P25 - if (state->nac > 0) + //P25 P1 and VCH0 + if (state->p2_cc > 0) { - nc = state->nac; + nc = state->p2_cc; } - if ( state->lasttg > 0 && (lls == 0 || lls == 1) ) + if ( state->lasttg > 0 && (lls == 0 || lls == 1 || lls == 35 || lls == 36) ) { tg = state->lasttg; } - if ( state->lastsrc > 0 && (lls == 0 || lls == 1) ) + if ( state->lastsrc > 0 && (lls == 0 || lls == 1 || lls == 35 || lls == 36) ) { rd = state->lastsrc; } + //P25 P2 VCH2 + if (state->lasttgR > 0 && (lls == 35 || lls == 36) ) + { + tgR = state->lasttgR; + } + if (state->lastsrcR > 0 && (lls == 35 || lls == 36) ) + { + rdR = state->lastsrcR; + } + //P25 P2 NAC to dcc for matrix shim + if (state->p2_cc > 0 && (lls == 35 || lls == 36) ) + { + dcc = state->p2_cc; + } //Call History Matrix Shuffling //ProVoice @@ -1537,6 +1597,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) call_matrix[9][4] = tgn; call_matrix[9][5] = time(NULL); + //open wav file if enabled and both rd and tg are not 0 + if (opts->dmr_stereo_wav == 1 && src != 0 && tgn != 0) + { + //close old first, assign name based on time and radio, open wav file + closeWavOutFileL (opts, state); + sprintf (opts->wav_out_file, "./WAV/%s NXDN - RAN %d - TGT %d - SRC %d.wav", getTimeN(), rn, tgn, src); + openWavOutFileL (opts, state); //testing for now, will want to move to per call later + } + } //DMR MS @@ -1565,14 +1634,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { //close old first, assign name based on time and radio, open wav file closeWavOutFileL (opts, state); - sprintf (opts->wav_out_file, "%s MS - CC %d - TG %d - RD %d.wav", getTimeN(), dcc, tg, rd); + sprintf (opts->wav_out_file, "./WAV/%s MS - CC %d - TG %d - RD %d.wav", getTimeN(), dcc, tg, rd); openWavOutFileL (opts, state); //testing for now, will want to move to per call later } } //DMR BS Slot 1 - matrix 0-4 - if ( call_matrix[4][2] != rd && (lls == 12 || lls == 13 || lls == 10 || lls == 11) ) + if ( call_matrix[4][2] != rd && (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 35 || lls == 36) ) { for (short int k = 0; k < 4; k++) @@ -1598,14 +1667,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) //close old first, assign name based on time and radio, open wav file closeWavOutFileL (opts, state); - sprintf (opts->wav_out_file, "%s S1 - CC %d - TG %d - RD %d.wav", getTimeN(), dcc, tg, rd); + sprintf (opts->wav_out_file, "./WAV/%s X1 - CC %d - TG %d - RD %d.wav", getTimeN(), dcc, tg, rd); openWavOutFileL (opts, state); //testing for now, will want to move to per call later } } //DMR BS Slot 2 - matrix 5-9 - if ( call_matrix[9][2] != rdR && (lls == 12 || lls == 13 || lls == 10 || lls == 11) ) + if ( call_matrix[9][2] != rdR && (lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 35 || lls == 36) ) { for (short int k = 5; k < 9; k++) @@ -1630,14 +1699,14 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { //close old first, assign name based on time and radio, open wav file closeWavOutFileR (opts, state); - sprintf (opts->wav_out_fileR, "%s S2 - CC %d - TG %d - RD %d.wav", getTimeN(), dcc, tgR, rdR); + sprintf (opts->wav_out_fileR, "./WAV/%s X2 - CC %d - TG %d - RD %d.wav", getTimeN(), dcc, tgR, rdR); openWavOutFileR (opts, state); //testing for now, will want to move to per call later } } - //P25 - if ( (lls == 0 || lls == 1) && call_matrix[9][2] != rd && nc > 0 && tg > 0) + //P25 P1 + if ( (lls == 0 || lls == 1) && call_matrix[9][2] != rd && nc > 0 && tg > 0 && state->dmrburstL == 26) { for (short int k = 0; k < 9; k++) { @@ -1656,6 +1725,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) call_matrix[9][4] = nc; call_matrix[9][5] = time(NULL); + //open wav file if enabled and both rd and tg are not 0 + if (opts->dmr_stereo_wav == 1 && rd != 0 && tg != 0) + { + //close old first, assign name based on time and radio, open wav file + closeWavOutFileL (opts, state); + sprintf (opts->wav_out_file, "./WAV/%s P1 - NAC %X - TGT %d - SRC %d.wav", getTimeN(), nc, tg, rd); + openWavOutFileL (opts, state); //testing for now, will want to move to per call later + } + } //Start Printing Section @@ -1672,13 +1750,15 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) { printw("%s \n", FM_bannerN[i]); } + printw (" https://github.com/lwvmobile/dsd-fme/tree/pulseaudio\n"); + printw (" Github Build Version: %s \n", GIT_TAG); attroff(COLOR_PAIR(6)); //6 - printw ("--Build Info------------------------------------------------------------------\n"); - printw ("| https://github.com/lwvmobile/dsd-fme/tree/pulseaudio\n"); //http link - printw ("| Digital Speech Decoder: Florida Man Edition\n"); - printw ("| Github Build Version: %s \n", GIT_TAG); - printw ("| MBElib version %s\n", versionstr); - printw ("------------------------------------------------------------------------------\n"); + // printw ("--Build Info------------------------------------------------------------------\n"); + // printw ("| https://github.com/lwvmobile/dsd-fme/tree/pulseaudio\n"); //http link + // printw ("| Digital Speech Decoder: Florida Man Edition\n"); + // printw ("| Github Build Version: %s \n", GIT_TAG); + // printw ("| MBElib version %s\n", versionstr); + // printw ("------------------------------------------------------------------------------\n"); attron(COLOR_PAIR(4)); } @@ -1837,56 +1917,71 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw("\n"); } - //P25 - if (lls == 0 || lls == 1) - { - // printw("| TID:[%8i] RID:[%8i] ", tg, rd); - printw("| TID:[%8i] RID:[%8i] ", state->lasttg, state->lastsrc); - printw(" MFID:[0x%02X] ", state->payload_mfid); - printw("NAC: [0x%3X] \n", nc); - printw("| ALG:[0x%02X] ", state->payload_algid); - printw(" KID:[0x%04X] ", state->payload_keyid); - printw(" MI:[0x%016llX] ", state->payload_miP); - - if (state->payload_algid != 0x80 && state->payload_algid != 0x0 && state->carrier == 1 && state->R == 0) - { - attron(COLOR_PAIR(2)); - printw("**ENC**"); - attroff(COLOR_PAIR(2)); - attron(COLOR_PAIR(3)); - if (state->payload_algid == 0xAA) - { - attron(COLOR_PAIR(1)); - printw(" RC4"); - attron(COLOR_PAIR(3)); - } - if (state->payload_algid == 0x81) - { - attron(COLOR_PAIR(1)); - printw(" DES-OFB"); - attron(COLOR_PAIR(3)); - } - } - printw("\n"); - } + //P25-P1 + // if (lls == 0 || lls == 1) + // { + // // printw("| TID:[%8i] RID:[%8i] ", tg, rd); + // printw("| TID:[%8i] RID:[%8i] ", state->lasttg, state->lastsrc); + // printw(" MFID:[0x%02X] ", state->payload_mfid); + // printw("NAC: [0x%3X] \n", nc); + // printw("| ALG:[0x%02X] ", state->payload_algid); + // printw(" KID:[0x%04X] ", state->payload_keyid); + // printw(" MI:[0x%016llX] ", state->payload_miP); + // + // if (state->payload_algid != 0x80 && state->payload_algid != 0x0 && state->carrier == 1 && state->R == 0) + // { + // attron(COLOR_PAIR(2)); + // printw("**ENC**"); + // attroff(COLOR_PAIR(2)); + // attron(COLOR_PAIR(3)); + // if (state->payload_algid == 0xAA) + // { + // attron(COLOR_PAIR(1)); + // printw(" RC4"); + // attron(COLOR_PAIR(3)); + // } + // if (state->payload_algid == 0x81) + // { + // attron(COLOR_PAIR(1)); + // printw(" DES-OFB"); + // attron(COLOR_PAIR(3)); + // } + // } + // printw("\n"); + // } //DMR BS/MS Voice and Data Types - if ( lls == 12 || lls == 13 || lls == 10 || lls == 11 || lls == 32 || lls == 33 || lls == 34) + if ( lls == 0 || lls == 1 || lls == 12 || lls == 13 || lls == 10 || + lls == 11 || lls == 32 || lls == 33 || lls == 34 || lls == 35 || lls == 36) { printw ("| "); - if (lls < 30) + if (lls > 1 && lls < 30) { printw ("DMR BS - DCC: [%02i] ", dcc); } - else + else if (lls == 32 || lls == 33 || lls == 34) { printw ("DMR MS - DCC: [%02i] ", dcc); } + else if (lls == 0 || lls == 1) //P1 + { + printw ("P25 P1 - WACN: [%05llX] SYS: [%03llX] NAC: [%03llX] ", state->p2_wacn, state->p2_sysid, state->p2_cc); + } + else if (lls == 35 || lls == 36) //P2 + { + printw ("P25 P2 - WACN: [%05llX] SYS: [%03llX] NAC: [%03llX] ", state->p2_wacn, state->p2_sysid, state->p2_cc); + if (state->p2_wacn == 0 || state->p2_sysid == 0 || state->p2_cc == 0) + { + attron(COLOR_PAIR(2)); + printw (" Phase 2 Missing Parameters "); + attron(COLOR_PAIR(3)); + } + } printw ("\n"); //Slot 1 [0] printw ("| SLOT 1 - "); - if (state->dmrburstL != 16 && state->carrier == 1 && state->lasttg > 0 && state->lastsrc > 0) + if (state->dmrburstL < 16 && state->carrier == 1 && state->lasttg > 0 && state->lastsrc > 0) { attron(COLOR_PAIR(2)); } @@ -1930,11 +2025,33 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if(state->dmrburstL == 16 && state->payload_algid > 0 && (state->dmr_so & 0xCF) == 0x40) { attron(COLOR_PAIR(1)); - printw ("ALG: [0x%02X] KEY: [0x%02X] MI: [0x%08X]", state->payload_algid, state->payload_keyid, state->payload_mi); + printw ("ALG: [0x%02X] KEY: [0x%02X] MI: [0x%08X] ", state->payload_algid, state->payload_keyid, state->payload_mi); attroff(COLOR_PAIR(1)); attron(COLOR_PAIR(3)); } + //P25 FDMA/TDMA + if(state->dmrburstL > 19 && state->payload_algid > 0 && state->payload_algid != 0x80) + { + attron(COLOR_PAIR(1)); + printw ("ALG: [0x%02X] KEY: [0x%04X] MI: [0x%016llX] ", state->payload_algid, state->payload_keyid, state->payload_miP); + attroff(COLOR_PAIR(1)); + attron(COLOR_PAIR(3)); + } + + if (state->payload_algid == 0xAA || state->payload_algid == 0x21) + { + attron(COLOR_PAIR(1)); + printw("ADP-RC4"); + attron(COLOR_PAIR(3)); + } + if (state->payload_algid == 0x81 || state->payload_algid == 0x22) + { + attron(COLOR_PAIR(1)); + printw("DES-OFB"); + attron(COLOR_PAIR(3)); + } + if(state->dmrburstL == 16 && state->dmr_so == 0x40 && state->R == 0) //0100 0000 { attron(COLOR_PAIR(2)); @@ -1993,9 +2110,9 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("\n"); //Slot 2 [1] - if (lls < 30){ //Don't print on MS mode + if (lls < 30 || lls == 35 || lls == 36){ //Don't print on MS mode printw ("| SLOT 2 - "); - if (state->dmrburstR != 16 && state->carrier == 1 && state->lasttgR > 0 && state->lastsrcR > 0) + if (state->dmrburstR < 16 && state->carrier == 1 && state->lasttgR > 0 && state->lastsrcR > 0) { attron(COLOR_PAIR(2)); } @@ -2037,10 +2154,30 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) if(state->dmrburstR == 16 && state->payload_algidR > 0 && (state->dmr_soR & 0xCF) == 0x40) { attron(COLOR_PAIR(1)); - printw ("ALG: [0x%02X] KEY: [0x%02X] MI: [0x%08X]", state->payload_algidR, state->payload_keyidR, state->payload_miR); + printw ("ALG: [0x%02X] KEY: [0x%02X] MI: [0x%08X] ", state->payload_algidR, state->payload_keyidR, state->payload_miR); attroff(COLOR_PAIR(1)); attron(COLOR_PAIR(3)); } + //P25-P1 and P2 + if(state->dmrburstR > 19 && state->payload_algidR > 0 && state->payload_algidR != 0x80) + { + attron(COLOR_PAIR(1)); + printw ("ALG: [0x%02X] KEY: [0x%04X] MI: [0x%016llX] ", state->payload_algidR, state->payload_keyidR, state->payload_miN); + attroff(COLOR_PAIR(1)); + attron(COLOR_PAIR(3)); + } + if (state->payload_algidR == 0xAA || state->payload_algidR == 0x21) + { + attron(COLOR_PAIR(1)); + printw("ADP-RC4"); + attron(COLOR_PAIR(3)); + } + if (state->payload_algidR == 0x81 || state->payload_algidR == 0x22) + { + attron(COLOR_PAIR(1)); + printw("DES-OFB"); + attron(COLOR_PAIR(3)); + } //Call Types, may switch to the more robust version later? if(state->dmrburstR == 16 && state->dmr_soR == 0x40 && state->R == 0) //0100 0000 { @@ -2143,11 +2280,11 @@ ncursesPrinter (dsd_opts * opts, dsd_state * state) printw ("SRC [%8lld] ", call_matrix[9-j][2]); printw ("DCC [%2lld] ", call_matrix[9-j][4]); } - //P25P1 Voice and Data - if (call_matrix[9-j][0] == 0 || call_matrix[9-j][0] == 1) + //P25 + if (call_matrix[9-j][0] == 0 || call_matrix[9-j][0] == 1 || call_matrix[9-j][0] == 35 || call_matrix[9-j][0] == 36) { - printw ("TID [%8lld] ", call_matrix[9-j][1]); - printw ("RID [%8lld] ", call_matrix[9-j][2]); + printw ("TGT [%8lld] ", call_matrix[9-j][1]); + printw ("SRC [%8lld] ", call_matrix[9-j][2]); printw ("NAC [0x%03llX] ", call_matrix[9-j][4]); } //DMR BS Types diff --git a/src/dsd_symbol.c b/src/dsd_symbol.c index 1c6d85c..707f549 100644 --- a/src/dsd_symbol.c +++ b/src/dsd_symbol.c @@ -371,9 +371,8 @@ getSymbol (dsd_opts * opts, dsd_state * state, int have_sync) openPulseInput(opts); } - //flipping these to 'positive' produces terrible results in P25 P1, and I tried flopping it every way I could - //positive polarity works fine with DMR, but not P25, so may look at reading and storing based on frame type? - if (opts->frame_p25p1 == 1) //use inverted polarity for reading P25, but not DMR (or others?) + //flip dibit values when p25-p1, has issues when read in positive polarity + if (state->synctype == 0 || state->synctype == 1 ) { if (state->symbolc == 0) { diff --git a/src/ez.cpp b/src/ez.cpp new file mode 100644 index 0000000..06290d3 --- /dev/null +++ b/src/ez.cpp @@ -0,0 +1,311 @@ +/*------------------------------------------------------------------------------- + * ez.cpp + * EZPWD R-S bridge and ISCH map lookup + * + * original copyrights for portions used below (OP25, EZPWD) + * + * LWVMOBILE + * 2022-09 DSD-FME Florida Man Edition + *-----------------------------------------------------------------------------*/ + +#include "dsd.h" +#include "ezpwd/rs" +#include + +std::vector ESS_A(28,0); // ESS_A and ESS_B are hexbits vectors +std::vector ESS_B(16,0); +ezpwd::RS<63,35> rs28; +std::vector HB(63,0); +std::vector HBS(63,0); +std::vector HBL(63,0); +std::vector Erasures; + +//Reed-Solomon Correction of ESS section +int ez_rs28_ess (int payload[96], int parity[168]) +{ + //do something + + uint8_t a, b, i, j, k; + k = 0; + for (i = 0; i < 16; i++) + { + b = 0; + for (j = 0; j < 6; j++) + { + b = b << 1; + b = b + payload[k]; //convert bits to hexbits. + k++; + } + ESS_B[i] = b; + // fprintf (stderr, " %X", ESS_B[i]); + } + + k = 0; + for (i = 0; i < 28; i++) + { + a = 0; + for (j = 0; j < 6; j++) + { + a = a << 1; + a = a + parity[k]; //convert bits to hexbits. + k++; + } + ESS_A[i] = a; + // fprintf (stderr, " %X ", ESS_A[i]); + } + + int ec; + + ec = rs28.decode (ESS_B, ESS_A); + // fprintf (stderr, "\n EC = %d \n", ec); + + //convert ESS_B back to bits + k = 0; + for (i = 0; i < 16; i++) + { + b = 0; + for (j = 0; j < 6; j++) + { + b = (ESS_B[i] >> (5-j) & 0x1); + payload[k] = b; + k++; + } + // fprintf (stderr, " %X", ESS_B[i]); + } + + return(ec); + +} + +//Reed-Solomon Correction of FACCH section +int ez_rs28_facch (int payload[156], int parity[114]) +{ + //do something! + int ec = -2; + int i, j, k, b; + + //Erasures for FACCH + Erasures = {0,1,2,3,4,5,6,7,8,54,55,56,57,58,59,60,61,62}; + + //convert bits to hexbits, 156 for payload, 114 parity + j = 9; //starting position according to OP25 + for (i = 0; i < 156; i += 6) + { + HB[j] = (payload[i] << 5) + (payload[i+1] << 4) + (payload[i+2] << 3) + (payload[i+3] << 2) + (payload[i+4] << 1) + payload[i+5]; + j++; + } + //j should continue from its last increment + for (i = 0; i < 114; i += 6) + { + HB[j] = (parity[i] << 5) + (parity[i+1] << 4) + (parity[i+2] << 3) + (parity[i+3] << 2) + (parity[i+4] << 1) + parity[i+5]; + j++; + } + + ec = rs28.decode(HB, Erasures); + + //convert HB back to bits + //fprintf (stderr, "\n"); + k = 0; + for (i = 0; i < 26; i++) //26*6=156 bits + { + b = 0; + for (j = 0; j < 6; j++) + { + b = (HB[i+9] >> (5-j) & 0x1); //+9 to mach our starting position + payload[k] = b; + //fprintf (stderr, "%d", payload[k]); + k++; + } + + } + + return (ec); +} + +//Reed-Solomon Correction of SACCH section +int ez_rs28_sacch (int payload[180], int parity[132]) +{ + //do something! + int ec = -2; + int i, j, k, b; + + //Erasures for SACCH + Erasures = {0,1,2,3,4,57,58,59,60,61,62}; + + //convert bits to hexbits, 156 for payload, 114 parity + j = 5; //starting position according to OP25 + for (i = 0; i < 180; i += 6) + { + HBS[j] = (payload[i] << 5) + (payload[i+1] << 4) + (payload[i+2] << 3) + (payload[i+3] << 2) + (payload[i+4] << 1) + payload[i+5]; + j++; + } + //j should continue from its last increment + for (i = 0; i < 132; i += 6) + { + HBS[j] = (parity[i] << 5) + (parity[i+1] << 4) + (parity[i+2] << 3) + (parity[i+3] << 2) + (parity[i+4] << 1) + parity[i+5]; + j++; + } + + ec = rs28.decode(HBS, Erasures); + + //convert HBS back to bits + // fprintf (stderr, "\n"); + k = 0; + for (i = 0; i < 30; i++) //30*6=180 bits + { + b = 0; + for (j = 0; j < 6; j++) + { + b = (HBS[i+5] >> (5-j) & 0x1); //+5 to mach our starting position + payload[k] = b; + // fprintf (stderr, "%d", payload[k]); + k++; + } + + } + return (ec); +} + +//I-ISCH Lookup section, borrowed from OP25 +std::map isch_map; +void map_isch() +{ + isch_map["184229d461"] = 0; + isch_map["18761451f6"] = 1; + isch_map["181ae27e2f"] = 2; + isch_map["182edffbb8"] = 3; + isch_map["18df8a7510"] = 4; + isch_map["18ebb7f087"] = 5; + isch_map["188741df5e"] = 6; + isch_map["18b37c5ac9"] = 7; + isch_map["1146a44f13"] = 8; + isch_map["117299ca84"] = 9; + isch_map["111e6fe55d"] = 10; + isch_map["112a5260ca"] = 11; + isch_map["11db07ee62"] = 12; + isch_map["11ef3a6bf5"] = 13; + isch_map["1183cc442c"] = 14; + isch_map["11b7f1c1bb"] = 15; + isch_map["1a4a2e239e"] = 16; + isch_map["1a7e13a609"] = 17; + isch_map["1a12e589d0"] = 18; + isch_map["1a26d80c47"] = 19; + isch_map["1ad78d82ef"] = 20; + isch_map["1ae3b00778"] = 21; + isch_map["1a8f4628a1"] = 22; + isch_map["1abb7bad36"] = 23; + isch_map["134ea3b8ec"] = 24; + isch_map["137a9e3d7b"] = 25; + isch_map["13166812a2"] = 26; + isch_map["1322559735"] = 27; + isch_map["13d300199d"] = 28; + isch_map["13e73d9c0a"] = 29; + isch_map["138bcbb3d3"] = 30; + isch_map["13bff63644"] = 31; + isch_map["1442f705ef"] = 32; + isch_map["1476ca8078"] = 33; + isch_map["141a3cafa1"] = 34; + isch_map["142e012a36"] = 35; + isch_map["14df54a49e"] = 36; + isch_map["14eb692109"] = 37; + isch_map["14879f0ed0"] = 38; + isch_map["14b3a28b47"] = 39; + isch_map["1d467a9e9d"] = 40; + isch_map["1d72471b0a"] = 41; + isch_map["1d1eb134d3"] = 42; + isch_map["1d2a8cb144"] = 43; + isch_map["1ddbd93fec"] = 44; + isch_map["1defe4ba7b"] = 45; + isch_map["1d831295a2"] = 46; + isch_map["1db72f1035"] = 47; + isch_map["164af0f210"] = 48; + isch_map["167ecd7787"] = 49; + isch_map["16123b585e"] = 50; + isch_map["162606ddc9"] = 51; + isch_map["16d7535361"] = 52; + isch_map["16e36ed6f6"] = 53; + isch_map["168f98f92f"] = 54; + isch_map["16bba57cb8"] = 55; + isch_map["1f4e7d6962"] = 56; + isch_map["1f7a40ecf5"] = 57; + isch_map["1f16b6c32c"] = 58; + isch_map["1f228b46bb"] = 59; + isch_map["1fd3dec813"] = 60; + isch_map["1fe7e34d84"] = 61; + isch_map["1f8b15625d"] = 62; + isch_map["1fbf28e7ca"] = 63; + isch_map["84d62c339"] = 64; + isch_map["8795f46ae"] = 65; + isch_map["815a96977"] = 66; + isch_map["82194ece0"] = 67; + isch_map["8d0c16248"] = 68; + isch_map["8e4fce7df"] = 69; + isch_map["8880ac806"] = 70; + isch_map["8bc374d91"] = 71; + isch_map["149ef584b"] = 72; + isch_map["17dd2dddc"] = 73; + isch_map["11124f205"] = 74; + isch_map["125197792"] = 75; + isch_map["1d44cf93a"] = 76; + isch_map["1e0717cad"] = 77; + isch_map["18c875374"] = 78; + isch_map["1b8bad6e3"] = 79; + isch_map["a456534c6"] = 80; + isch_map["a7158b151"] = 81; + isch_map["a1dae9e88"] = 82; + isch_map["a29931b1f"] = 83; + isch_map["ad8c695b7"] = 84; + isch_map["aecfb1020"] = 85; + isch_map["a800d3ff9"] = 86; + isch_map["ab430ba6e"] = 87; + isch_map["341e8afb4"] = 88; + isch_map["375d52a23"] = 89; + isch_map["3192305fa"] = 90; + isch_map["32d1e806d"] = 91; + isch_map["3dc4b0ec5"] = 92; + isch_map["3e8768b52"] = 93; + isch_map["38480a48b"] = 94; + isch_map["3b0bd211c"] = 95; + isch_map["44dbc12b7"] = 96; + isch_map["479819720"] = 97; + isch_map["41577b8f9"] = 98; + isch_map["4214a3d6e"] = 99; + isch_map["4d01fb3c6"] = 100; + isch_map["4e4223651"] = 101; + isch_map["488d41988"] = 102; + isch_map["4bce99c1f"] = 103; + isch_map["d493189c5"] = 104; + isch_map["d7d0c0c52"] = 105; + isch_map["d11fa238b"] = 106; + isch_map["d25c7a61c"] = 107; + isch_map["dd49228b4"] = 108; + isch_map["de0afad23"] = 109; + isch_map["d8c5982fa"] = 110; + isch_map["db864076d"] = 111; + isch_map["645bbe548"] = 112; + isch_map["6718660df"] = 113; + isch_map["61d704f06"] = 114; + isch_map["6294dca91"] = 115; + isch_map["6d8184439"] = 116; + isch_map["6ec25c1ae"] = 117; + isch_map["680d3ee77"] = 118; + isch_map["6b4ee6be0"] = 119; + isch_map["f41367e3a"] = 120; + isch_map["f750bfbad"] = 121; + isch_map["f19fdd474"] = 122; + isch_map["f2dc051e3"] = 123; + isch_map["fdc95df4b"] = 124; + isch_map["fe8a85adc"] = 125; + isch_map["f845e7505"] = 126; + isch_map["fb063f092"] = 127; +} + +unsigned long long int isch_lookup (unsigned long long int isch) +{ + map_isch(); //initialize the lookup map + char s[64]; + unsigned long long int decoded = -2; //initialize lookup to an invalid number + sprintf(s, "%llx", isch); + decoded = isch_map[s]; + return(decoded); +} diff --git a/src/ezpwd/asserter b/src/ezpwd/asserter new file mode 100644 index 0000000..5e0a19e --- /dev/null +++ b/src/ezpwd/asserter @@ -0,0 +1,128 @@ +#ifndef _EZPWD_ASSERTER +#define _EZPWD_ASSERTER + +#include +#include +#include +#include +#include + +namespace ezpwd { + +#define ISEQUAL( ... ) isequal(__FILE__, __LINE__, __VA_ARGS__ ) +#define ISTRUE( ... ) istrue( __FILE__, __LINE__, __VA_ARGS__ ) +#define ISFALSE( ... ) isfalse(__FILE__, __LINE__, __VA_ARGS__ ) +#define ISNEAR( ... ) isnear( __FILE__, __LINE__, __VA_ARGS__ ) +#define FAILURE( ... ) failure(__FILE__, __LINE__, __VA_ARGS__ ) + + struct asserter { + bool failed; // The last test failed + int failures; // Total number of failures + std::string out; // Last failure + + asserter() + : failed( false ) + , failures( 0 ) + , out() + { + ; + } + + // + // output( ) -- Output description of last failed test (or nothing if successful) + // << + // + std::ostream &output( + std::ostream &lhs ) + const + { + return lhs << out; + } + + // + // (bool) -- Return status of last test + // + operator bool() + { + return failed; + } + + template < typename T > + asserter &istrue( const char *file, int line, const T &a, const std::string &comment = std::string() ) + { + return isequal( file, line, !!a, true, comment ); + } + + template < typename T > + asserter &isfalse( const char *file, int line, const T &a, const std::string &comment = std::string() ) + { + return isequal( file, line, !!a, false, comment ); + } + + template < typename T > + asserter &isequal( const char *file, int line, const T &a, const T &b, const std::string &comment = std::string() ) + { + if ( ! ( a == b )) { + std::ostringstream oss; + oss << a << " != " << b; + return failure( file, line, oss.str(), comment ); + } + return success(); + } + + template < typename T > + asserter &isnear( const char *file, int line, const T &a, const T &b, const T &delta, const std::string &comment = std::string() ) + { + T difference; + difference = ( a < b + ? T( b - a ) + : T( a - b )); + if ( ! ( difference < ( delta < T( 0 ) ? T( -delta ) : T( delta )))) { + std::ostringstream oss; + oss << std::setprecision( 13 ) << a << " != " << b << " +/- " << delta; + return failure( file, line, oss.str(), comment ); + } + return success(); + } + + asserter &failure( const char *file, int line, const std::string &comparison, + const std::string &comment = std::string() ) + { + ++failures; + const char *needle = "/"; + const char *slash = std::find_end( file, file + strlen( file ), + needle, needle + strlen( needle )); + if ( slash == file + strlen( file )) + slash = file; + else + slash += 1; + + std::ostringstream oss; + oss + << std::setw( 24 ) << slash << ", " + << std::setw( -5 ) << line + << "; FAILURE: " << comparison + << ( comment.size() ? ": " : "" ) << comment + << std::endl; + out = oss.str(); + failed = true; + return *this; + } + + asserter &success() + { + out.clear(); + failed = false; + return *this; + } + }; // class asserter +} // namespace ezpwd + +std::ostream &operator<<( + std::ostream &lhs, + ezpwd::asserter &rhs ) +{ + return rhs.output( lhs ); +} + +#endif // _EZPWD_ARRAY diff --git a/src/ezpwd/bch b/src/ezpwd/bch new file mode 100644 index 0000000..eb45db6 --- /dev/null +++ b/src/ezpwd/bch @@ -0,0 +1,485 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2017, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. The Djelic BCH code + * under djelic/ and the c++/ezpwd/bch_base wrapper is redistributed under the terms of the GPLv2+, + * regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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. + */ +#ifndef _EZPWD_BCH +#define _EZPWD_BCH + +#include +#include "rs_base" // Basic DEBUG, EZPWD_... preprocessor stuff, ezpwd::log_, etc. +#include "bch_base" + +namespace ezpwd { + // + // ezpwd::bch_base -- Interface to underlying Djelic Linux Kernel API + // ezpwd::bch -- General BCH codec types; min. CORRECTION capacity, undefined PAYLOAD + // + // These implementations retain the original Djelic Linux Kernel API; specifically, they find + // a BCH codec of the given Galois order M (ie. has codewords of size 2**M-1), and at least the + // target bit-error correction capacity T. They may correct more than T errors, and the number + // of parity ECC bits will be selected by the algorithm. You need to compute the maximum + // non-parity payload by computing _bch->n - _bch->ecc_bits. + // + // The data and parity bits must always be on separate blocks of int8_t/uint8_t-sized data + // in the container. This is required because the underlying API must break the data and parity + // out as separate arrays for processing. So, if the computed ecc_bits is not evenly divisible + // by 8, some care must be taken to ensure that it is packed into exactly ecc_bytes of data at + // the end of the supplied container. Alternatively, it can be kept in a separate container. + // This is probably safest, as the bch_base/bch/BCH classes will never attempt to resize the + // data/parity containers when supplied separately. + // + // Like the Reed-Solomon APIs, the bch_base/bch/BCH APIs will alter the size of a variable + // container in encode(...), to add the BCH ECC "parity" data (eg. std::vector, std::string). + // Fixed containers (eg. std::array) are never resized, and it is assumed that ecc_bytes of + // parity data exist at the end of the container. + // + class bch_base { + public: + ezpwd::bch_control *_bch; + + bch_base( const bch_base & ) = delete; // no copy-constructor + + bch_base( + size_t m, + size_t t, + unsigned int prim_poly = 0 ) + : _bch( ezpwd::init_bch( int( m ), int( t ), prim_poly )) + { + ; + } + + virtual ~bch_base() + { + ezpwd::free_bch( this->_bch ); + } + + size_t ecc_bytes() + const + { + return _bch->ecc_bytes; + } + size_t ecc_bits() + const + { + return _bch->ecc_bits; + } + + size_t t() + const + { + return _bch->t; + } + + // + // << bch_base -- output codec in standard BCH( N, N-ECC, T ) form + // + virtual std::ostream &output( + std::ostream &lhs ) + const + { + return lhs << *this->_bch; + } + + // + // encode -- container interfaces + // + // Returns number of ECC *bits* initialized (to be consistent w/ decode, which returns + // number of bit errors corrected). + // + int encode( + std::string &data ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + data.resize( data.size() + ecc_bytes() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + + int encode( + const std::string &data, + std::string &parity ) + const + { + typedef uint8_t uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( ecc_bytes() ); + return encode( cuTpair( (const uT *)&data.front(), (const uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T > + int encode( + std::vector &data ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + data.resize( data.size() + ecc_bytes() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + + template < typename T > + int encode( + const std::vector&data, + std::vector &parity ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( ecc_bytes() ); + return encode( cuTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T, size_t N > + int encode( + std::array &data, + int pad = 0 ) // ignore 'pad' symbols at start of array + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return encode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() )); + } + + + // + // encode -- denote data+parity or data, parity using pairs of uint8_t iterators + // encode -- base implementation, in terms of uint8_t pointers + // + virtual int encode( + const std::pair + &data ) + const + { + return encode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes() ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( size_t( parity.second - parity.first ) != ecc_bytes() ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: parity length incompatible with number of ECC bytes", -1 ); + } + return encode( data.first, data.second - data.first, parity.first ); + } + + virtual int encode( + const uint8_t *data, + size_t len, + uint8_t *parity ) + const + { + memset( parity, 0, ecc_bytes() ); // Djelic encode_bch requires ECC to be initialized to 0 + ezpwd::encode_bch( this->_bch, data, len, parity ); + return int( ecc_bits() ); + } + + // + // decode -- container interface, w/ optional corrected bit-error positions reported + // + // Does not correct errors in parity! + // + int decode( + std::string &data, + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + position ); + } + + int decode( + std::string &data, + std::string &parity, + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + position ); + } + + template < typename T > + int decode( + std::vector &data, + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + position ); + } + + template < typename T > + int decode( + std::vector &data, + std::vector &parity, + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + position ); + } + + template < typename T, size_t N > + int decode( + std::array &data, + int pad = 0, // ignore 'pad' symbols at start of array + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() ), + position ); + } + + // + // decode -- denote data+parity or data, parity using pairs of uint8_t iterators + // decode -- decode and correct BCH codeword, returning number of corrections, or -1 if failed + // + // Corrects data in-place (unlike the basic Djelic Linux Kernel API, which only returns + // error positions. For consistency with ezpwd::rs..., we report all error positions as + // std::vector, even though the underlying Djelic API reports them as arrays of + // unsigned int. + // + virtual int decode( + const std::pair + &data, + std::vector *position= 0 ) + const + { + return decode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes(), + position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + std::vector *position= 0 ) + const + { + if ( size_t( parity.second - parity.first ) != ecc_bytes() ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: parity length incompatible with number ECC bytes", -1 ); + } + return decode( data.first, data.second - data.first, parity.first, + position ); + } + virtual int decode( + uint8_t *data, + size_t len, + uint8_t *parity, + std::vector *position= 0 ) + const + { + if ( position ) + position->resize( t() * 2 ); // may be able to correct beyond stated capacity! + int corrects = ezpwd::correct_bch( + this->_bch, data, len, parity, 0, 0, + position ? (unsigned int *)&(*position)[0] : 0 ); + if ( position && corrects >= 0 ) + position->resize( corrects ); + return corrects; + } + + // + // {en,de}coded -- returns an encoded/corrected copy of the provided container + // + // NOTE: + // + // Must return exceptions on failure; If exceptions inhibited, returns a + // default-constructed instance of the supplied data container. This may be sufficient to + // reliably deduce failure; if not, this interface should not be used. + // + // Overloads decoded to also allow recovery of corrected error positions and count. + // + template + C encoded( + C data ) + const + { + if ( encode( data ) < 0 ) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not encode data", C() ); + return data; + } + + template + C decoded( + C data ) + const + { + if ( decode( data ) < 0 ) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not decode data", C() ); + return data; + } + + template + C decoded( + C data, + std::vector &position ) + const + { + if ( decode( data, &position ) < 0 ) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not decode data", C() ); + return data; + } + + }; // class bch_base + + template < size_t SYMBOLS, size_t CORRECTION > + class bch + : public bch_base + { + public: + bch() + : bch_base( ezpwd::log_::value, CORRECTION ) + { + ; + } + + virtual ~bch() + { + ; + } + }; // class bch + + // + // std::ostream << ezpwd::bch_base + // + // Output a BCH codec description in standard form eg. BCH( 255, 239, 2 ) + // + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::bch_base + &rhs ) + { + return rhs.output( lhs ); + } + + // + // ezpwd::BCH -- Standard BCH codec types + // + // Specify and create a standard BCH codec with exactly the specified capacities. We create + // the undering BCH codec using SYMBOLS and CORRECTION capacity; the actual correction capacity + // T, the number of PARITY bits and hence PAYLOAD (CAPACITY - PARITY) is selected automatically + // by the underlying Djelic Linux Kernel BCH codec API. For this interface, we demand that the + // caller *knows* all of these values at compile time, however, mostly for future optimization + // purposes. We validate them, and fail the constructor if they don't match. See bch_test for + // an enumeration of all possible BCH codecs. + // + // In the future, this API may be re-implemented to not use the generic BCH API, but a more + // optimized locally-defined implementation that leverages the fixed SYMBOLS, PAYLOAD and + // CORRECTION capacities to produce more optimal code. + // + template < size_t SYMBOLS, size_t PAYLOAD, size_t CORRECTION > + class BCH + : public bch + { + public: + static const size_t M = ezpwd::log_::value; // Galois field order; eg. 255 --> 8 + static const size_t N = SYMBOLS; + static const size_t T = CORRECTION; + static const size_t LOAD = PAYLOAD; + + BCH() + : bch() + { + if ( this->_bch->t != T || this->_bch->n != N + || this->_bch->n - this->_bch->ecc_bits != LOAD ) { + std::ostringstream err; + this->output( err ) + << " specified doesn't match underlying " << *this->_bch << " produced."; + EZPWD_RAISE_OR_ABORT( std::runtime_error, err.str().c_str() ); + } + } + + virtual ~BCH() + { + ; + } + + // + // << BCH<...> -- output codec in standard BCH( N, N-ECC, T ) form + // + virtual std::ostream &output( + std::ostream &lhs ) + const + { + return lhs + << "BCH( " << std::setw( 3 ) << N + << ", " << std::setw( 3 ) << LOAD + << ", " << std::setw( 3 ) << T + << " )"; + } + }; // class BCH + + + // + // std::ostream << ezpwd::BCH<...> + // + // Output a BCH codec description in standard form eg. BCH( 255, 239, 2 ) + // + // NOTE: clang/gcc disagree on the scoping of operator<< template/non-template functions... + // + template + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::BCH + &rhs ) + { + return rhs.output( lhs ); + } + +} // namespace ezpwd + +#endif // _EZPWD_BCH diff --git a/src/ezpwd/bch_base b/src/ezpwd/bch_base new file mode 100644 index 0000000..0aa0592 --- /dev/null +++ b/src/ezpwd/bch_base @@ -0,0 +1,219 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2017, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. The Djelic BCH code + * under djelic/ and the c++/ezpwd/bch_base wrapper is redistributed under the terms of the GPLv2+, + * regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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. + */ +#ifndef _EZPWD_BCH_BASE +#define _EZPWD_BCH_BASE + +#include +#include +#include + +// +// Presently, we simply import the Linux Kernel's "C" BCH API directly into the ezpwd:: namespace In +// order to compile, you must (at least) run "make djelic" in the base directory of the +// https://github.com/pjkundert/bch.git repo. +// +namespace ezpwd { + /** + * struct bch_control - BCH control structure + * @m: Galois field order + * @n: maximum codeword size in bits (= 2^m-1) + * @t: error correction capability in bits + * @ecc_bits: ecc exact size in bits, i.e. generator polynomial degree (<=m*t) + * @ecc_bytes: ecc max size (m*t bits) in bytes + * @a_pow_tab: Galois field GF(2^m) exponentiation lookup table + * @a_log_tab: Galois field GF(2^m) log lookup table + * @mod8_tab: remainder generator polynomial lookup tables + * @ecc_buf: ecc parity words buffer + * @ecc_buf2: ecc parity words buffer + * @xi_tab: GF(2^m) base for solving degree 2 polynomial roots + * @syn: syndrome buffer + * @cache: log-based polynomial representation buffer + * @elp: error locator polynomial + * @poly_2t: temporary polynomials of degree 2t + */ + + /** + * init_bch - initialize a BCH encoder/decoder + * @m: Galois field order, should be in the range 5-15 + * @t: maximum error correction capability, in bits + * @prim_poly: user-provided primitive polynomial (or 0 to use default) + * + * Returns: + * a newly allocated BCH control structure if successful, NULL otherwise + * + * This initialization can take some time, as lookup tables are built for fast + * encoding/decoding; make sure not to call this function from a time critical + * path. Usually, init_bch() should be called on module/driver init and + * free_bch() should be called to release memory on exit. + * + * You may provide your own primitive polynomial of degree @m in argument + * @prim_poly, or let init_bch() use its default polynomial. + * + * Once init_bch() has successfully returned a pointer to a newly allocated + * BCH control structure, ecc length in bytes is given by member @ecc_bytes of + * the structure. + */ + + /** + * encode_bch - calculate BCH ecc parity of data + * @bch: BCH control structure + * @data: data to encode + * @len: data length in bytes + * @ecc: ecc parity data, must be initialized by caller + * + * The @ecc parity array is used both as input and output parameter, in order to + * allow incremental computations. It should be of the size indicated by member + * @ecc_bytes of @bch, and should be initialized to 0 before the first call. + * + * The exact number of computed ecc parity bits is given by member @ecc_bits of + * @bch; it may be less than m*t for large values of t. + */ + + /** + * decode_bch - decode received codeword and find bit error locations + * @bch: BCH control structure + * @data: received data, ignored if @calc_ecc is provided + * @len: data length in bytes, must always be provided + * @recv_ecc: received ecc, if NULL then assume it was XORed in @calc_ecc + * @calc_ecc: calculated ecc, if NULL then calc_ecc is computed from @data + * @syn: hw computed syndrome data (if NULL, syndrome is calculated) + * @errloc: output array of error locations + * + * Returns: + * The number of errors found, or -EBADMSG if decoding failed, or -EINVAL if + * invalid parameters were provided + * + * Depending on the available hw BCH support and the need to compute @calc_ecc + * separately (using encode_bch()), this function should be called with one of + * the following parameter configurations - + * + * by providing @data and @recv_ecc only: + * decode_bch(@bch, @data, @len, @recv_ecc, NULL, NULL, @errloc) + * + * by providing @recv_ecc and @calc_ecc: + * decode_bch(@bch, NULL, @len, @recv_ecc, @calc_ecc, NULL, @errloc) + * + * by providing ecc = recv_ecc XOR calc_ecc: + * decode_bch(@bch, NULL, @len, NULL, ecc, NULL, @errloc) + * + * by providing syndrome results @syn: + * decode_bch(@bch, NULL, @len, NULL, NULL, @syn, @errloc) + * + * Once decode_bch() has successfully returned with a positive value, error + * locations returned in array @errloc should be interpreted as follows - + * + * if (errloc[n] >= 8*len), then n-th error is located in ecc (no need for + * data correction) + * + * if (errloc[n] < 8*len), then n-th error is located in data and can be + * corrected with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8); + * + * Note that this function does not perform any data correction by itself, it + * merely indicates error locations. + */ + + /** + * init_bch - initialize a BCH encoder/decoder + * @m: Galois field order, should be in the range 5-15 + * @t: maximum error correction capability, in bits + * @prim_poly: user-provided primitive polynomial (or 0 to use default) + * + * Returns: + * a newly allocated BCH control structure if successful, NULL otherwise + * + * This initialization can take some time, as lookup tables are built for fast + * encoding/decoding; make sure not to call this function from a time critical + * path. Usually, init_bch() should be called on module/driver init and + * free_bch() should be called to release memory on exit. + * + * You may provide your own primitive polynomial of degree @m in argument + * @prim_poly, or let init_bch() use its default polynomial. + * + * Once init_bch() has successfully returned a pointer to a newly allocated + * BCH control structure, ecc length in bytes is given by member @ecc_bytes of + * the structure. + */ + + extern "C" { + #include "../../djelic_bch.h" + } + + // + // correct_bch -- corrects data (but not parity!), as suggested by decode_bch, above + // + // A convenience interface that defaults all of the not strictly required parameters, and + // automatically corrects bit-errors in data *and* the supplied parity. Does not attempt to + // correct bit errors found in the parity data. If not supplied, 'errloc' is allocated + // internally; otherwise, it is assumed to be of at least size bch->t (the minimum error + // correction capacity of the BCH codec). + // + // However, beware -- at larger values of T, the actual correction capacity of the BCH codec + // could be greater than the requested T. Therefore, it is recommended that you always supply a + // larger than required errloc array; recommend T*2? + // + inline + int correct_bch( + struct bch_control *bch, + uint8_t *data, + unsigned int len, + uint8_t *recv_ecc, + const uint8_t *calc_ecc= 0, + const unsigned int *syn = 0, + unsigned int *errloc = 0 ) // must be sized at least bch->t; often, greater! + { + unsigned int _errloc[511]; // much larger than the correction capacity of largest supported BCH codec + if ( ! errloc ) + errloc = _errloc; + int err = decode_bch( bch, data, len, recv_ecc, calc_ecc, syn, errloc ); + if ( err > 0 ) { + // A +'ve number of bit-error correction locations were found + for ( int n = 0; n < err; ++n ) { + /** + * if (errloc[n] < 8*len), then n-th error is located in data and can be corrected + * with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8). If in the parity, it + * is assumed to be located at the end of the data, so offset by 'len' bytes. + */ + if ( errloc[n] < 8*len ) { + data[errloc[n] / 8] ^= 1 << ( errloc[n] % 8 ); + } else if ( recv_ecc && errloc[n] < 8 * len + 8 * bch->ecc_bytes ) { + recv_ecc[errloc[n] / 8 - len] + ^= 1 << ( errloc[n] % 8 ); + } + } + } + return err; + } + + // + // << -- output codec in standard BCH( N, N-ECC, T ) form + // + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::bch_control + &bch ) + { + return lhs + << "BCH( " << std::setw( 3 ) << bch.n + << ", " << std::setw( 3 ) << bch.n - bch.ecc_bits + << ", " << std::setw( 3 ) << bch.t + << " )"; + } + +} // namespace ezpwd + +#endif // _EZPWD_BCH_BASE diff --git a/src/ezpwd/corrector b/src/ezpwd/corrector new file mode 100644 index 0000000..23a36a0 --- /dev/null +++ b/src/ezpwd/corrector @@ -0,0 +1,506 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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. + */ +#ifndef _EZPWD_CORRECTOR +#define _EZPWD_CORRECTOR + +#include "rs" +#include "serialize" + +namespace ezpwd { + + // + // best_avg -- collect , guesses, and return the unambiguous best one + // + typedef std::map> // (, (, )) + best_avg_base_t; + class best_avg + : public best_avg_base_t + { + public: + using best_avg_base_t::begin; + using best_avg_base_t::end; + using best_avg_base_t::insert; + using best_avg_base_t::find; + using best_avg_base_t::iterator; + using best_avg_base_t::const_iterator; + using best_avg_base_t::value_type; + using best_avg_base_t::mapped_type; + // + // add -- add the given pct to the current average for str + // + iterator add( + const std::string &str, + int pct ) + { + iterator i = find( str ); + if ( i == end() ) + i = insert( i, value_type( str, mapped_type() )); + i->second.first += 1; + i->second.second += pct; + return i; + } + + // + // best -- return the unambiguously best value (average is >, or == but longer), or end() + // + const_iterator best() + const + { + const_iterator top = end(); + bool uni = false; + for ( const_iterator i = begin(); i != end(); ++i ) { + if ( top == end() + or i->second.second/i->second.first > top->second.second/top->second.first + or ( i->second.second/i->second.first == top->second.second/top->second.first + and i->first.size() > top->first.size())) { + top = i; + uni = true; + } else if ( i->second.second/i->second.first == top->second.second/top->second.first + and i->first.size() == top->first.size()) { + uni = false; + } + } + return uni ? top : end(); + } + + // + // evaluation -- process a (,(,)) into (,) + // sort -- return a multimap indexed by --> + // output -- output the : , sorted by average + // + static std::pair + evaluation( const value_type &val ) + { + return std::pair( val.second.second/val.second.first, val.first ); + } + typedef std::multimap + sorted_t; + sorted_t sort() + const + { + sorted_t dst; + std::transform( begin(), end(), std::inserter( dst, dst.begin() ), evaluation ); + return dst; + } + std::ostream &output( + std::ostream &lhs ) + const + { + for ( auto i : sort() ) + lhs << std::setw( 16 ) << i.second + << ": " << std::setw( 3 ) << i.first + << std::endl; + return lhs; + } + }; +} // namespace ezpwd + +std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::best_avg &rhs ) +{ + return rhs.output( lhs ); +} + +namespace ezpwd { + // + // ezpwd::corrector -- Apply statistical corrections to a string, returning the confidence + // + // All methods are static; no instance is required, as this is primarily used to create + // external language APIs. + // + template < + size_t PARITY, + size_t N = 64, + typename SERIAL = serialize::base< N, serialize::ezpwd< N >>> + class corrector { + public: + static + std::ostream &output( + std::ostream &lhs ) + { + lhs << "corrector"; + return lhs; + } + + // + // parity() -- Returns 'PARITY' base-N symbols of R-S parity to the supplied password + // + static std::string parity( + const std::string &password ) + { + std::string parity; + rscodec.encode( password, parity ); + SERIAL::encode( parity ); + return parity; + } + + // + // encode() -- append PARITY base-N parity symbols to password + // + // The supplied password buffer size must be sufficient to contain PARITY additional + // symbols, plus the terminating NUL. Returns the resultant encoded password size + // (excluding the NUL). + // + static size_t encode( + std::string &password ) + { + password += parity( password ); + return password.size(); + } + + static size_t encode( + char *password, + size_t size ) // maximum available size + { + size_t len = ::strlen( password ); // length w/o terminating NUL + if ( len + PARITY + 1 > size ) + throw std::runtime_error( "ezpwd::rspwd::encode password buffer has insufficient capacity" ); + std::string par = parity( std::string( password, password + len )); + if ( par.size() != PARITY ) + throw std::runtime_error( "ezpwd::rspwd::encode computed parity with incorrect size" ); + std::copy( par.begin(), par.end(), password + len ); + len += PARITY; + password[len] = 0; + return len; + } + + // + // decode([,...]) -- Applies R-S error correction on the encoded string, removing parity + // + // Up to 'PARITY' Reed-Solomon parity symbols are examined, to determine if the supplied + // string is a valid R-S codeword and hence very likely to be correct. Optionally supply a + // vector of erasure positions. + // + // An optional 'minimum' final password length may be provided; no R-S parity is assumed + // to exist in the first 'minimum' password characters (default: PARITY). This prevents + // accidentally finding valid R-S codewords in passwords of known minimum length; validation + // codes, for example. Likewise, the optional 'maximum' allows us to limit the number of + // parity symbols that may be assumed to be missing from the end of the codeword. + // + // Returns a confidence strength rating, which is the ratio: + // + // 100 - ( errors * 2 + erasures ) * 100 / parity + // + // if an R-S codeword was solved, and 0 otherwise. If a codeword is solved, but the number + // of errors and erasures corrected indicates that all parity was consumed, the caller may + // opt to not use the corrected string, because there is a chance that our R-S polynomial + // was overwhelmed with errors and actually returned an incorrect codeword. Therefore, + // solving a codeword using all available parity results in 100 - PARITY * 100 / PARITY == + // 0, which indicates that there is no certainty of correctness; all R-S parity resources + // were used in error/erasure recover, with none left to confirm that the result is actually + // correct. If only zero-strength results are achieved, the longest will be returned (the + // full, original string). + // + // Supports the following forms of error/erasure: + // + // 0) Full parity. All data and parity supplied, and an R-S codeword is solved. + // + // 1) Partial parity. All data and some parity supplied; remainder are deemed erasures. + // + // If PARITY > 2, then up to PARITY/2-1 trailing parity terms are marked as erasures. + // If the R-S codeword is solved and a safe number of errors are found, then we can have + // reasonable confidence that the string is correct. + // + // 1a) Erase errors. Permute the combinations of up to PARITY-1 erasures. + // + // o) Raw password. No parity terms supplied; not an R-S codeword + // + // If none of the error/erasure forms succeed, the password is returned unmodified. + // + // If a non-zero 'minimum' or 'maximum' are provided, they constrain the possible + // resultant password sizes that will be attempted. + // + static + int decode( + std::string &password, + const std::vector + &erasures, + size_t minimum = PARITY,//always deemed at least 1 + size_t maximum = 0 ) // if 0, no limit + { + int confidence; + best_avg best; + + // Full/Partial parity. Apply some parity erasure if we have some erasure/correction + // capability while maintaining at least one excess parity symbol for verification. + // This can potentially result in longer password being returned, if the R-S decoder + // accidentally solves a codeword. + // + // For example, if PARITY=3 (or 4) then (PARITY+1)/2 == 2, and we would only attempt up + // to 1 parity erasure. This would leave 1 parity symbol to replace the 1 erasure, and + // 1 remaining to validate the integrity of the password. + // + // The password must be long enough to contain at least 1 non-parity symbol, and the + // designated number of non-erased parity symbols! However, by convention we'll demand + // that the password contain at least PARITY symbols -- any less, and we can + // accidentally correct the few remaining password symbols. + // + // Also, if any parity symbols won't decode (eg. were entered in error), we must deem + // them to be erasures, too, and if the number of erasures exceeds the capacity of the + // R-S codec, it'll fail (throw an exception, or at best solve with 0 confidence). + for ( size_t era = 0 // how many parity symbols to deem erased + ; era < (PARITY+1)/2 + ; ++era ) { + if ( password.size() < ( minimum ? minimum : 1 ) + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too short password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << era << " parity skipped)" + << std::endl; +#endif + continue; // too few password symbols to start checking parity + } + + if ( maximum and password.size() > maximum + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too long password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << era << " parity skipped)" + << std::endl; +#endif + continue; // too few parity symbols erased to start checking parity + } + + // Copy password, adding 'era' additional NULs + std::string fixed( password.size() + era, 0 ); + std::copy( password.begin(), password.end(), fixed.begin() ); + + // Decode the base-N parity, denoting any invalid (mistyped or trailing NUL) symbols + // as erasures (adjust erasure offsets to be from start of password, not start of + // parity). All newly added 'era' symbols will be NUL, and will be invalid. After + // decoding parity, if we've slipped below our minimum R-S capacity threshold + // (ie. because of mistyped parity symbols), don't attempt. + std::vector all_era; + SERIAL::decode( fixed.begin() + fixed.size() - PARITY, + fixed.begin() + fixed.size(), &all_era, 0, + serialize::ws_invalid, serialize::pd_invalid ); + if ( all_era.size() >= (PARITY+1)/2 ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected low parity password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << all_era.size() << " parity erasures + " + << era << " skipped)" + << std::endl; +#endif + continue; // Too many missing parity symbols + } + if ( all_era.size() + erasures.size() > PARITY ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected hi erasure password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << all_era.size() + erasures.size() << " total erasures + " + << era << " skipped)" + << std::endl; +#endif + continue; // Total erasures beyond capacity + } + for ( auto &o : all_era ) + o += fixed.size() - PARITY; + std::copy( erasures.begin(), erasures.end(), std::back_inserter( all_era )); + + // Enough parity to try to decode. A successful R-S decode with 0 (remaining) + // confidence indicates a successfully validated R-S codeword! Use it (ex. parity). + try { + std::vector position; + int corrects= rscodec.decode( fixed, all_era, &position ); + confidence = strength( corrects, all_era, position ); + fixed.resize( fixed.size() - PARITY ); + if ( confidence >= 0 ) + best.add( fixed, confidence ); +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Reed-Solomon w/ " << era << " of " << PARITY + << " parity erasures " << std::setw( 3 ) << confidence + << "% confidence: \"" << password + << "\" ==> \"" << fixed + << "\" (corrects: " << corrects + << ", erasures at " << all_era + << ", fixed at " << position << "): " + << std::endl + << best; +#endif + } catch ( std::exception &exc ) { +#if defined( DEBUG ) && DEBUG >= 2 // should see only when ezpwd::reed_solomon<...>::decode fails + output( std::cout ) << " invalid part parity password: " << exc.what() << std::endl; +#endif + } + } + + // Partial parity, but below threshold for usable error detection. For the first 1 to + // (PARITY+1)/2 parity symbols (eg. for PARITY == 3, (PARITY+1)/2 == 1 ), we cannot + // perform meaningful error or erasure detection. However, if we see that the terminal + // symbols match the R-S symbols we expect from a correct password, we'll ascribe a + // partial confidence due to the matching parity symbols. + // + // password: sock1t + // w/ 3 parity: sock1tkeB + // password ----^^^^^^ + // ^^^--- parity + // + for ( size_t era = (PARITY+1)/2 // how many parity symbols are not present + ; era < PARITY + ; ++era ) { + if ( password.size() < ( minimum ? minimum : 1 ) + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too short password \"" + << password << std::string( era, '_' ) + << "\"" + << std::endl; +#endif + continue; // too few password symbols to start checking parity + } + if ( maximum and password.size() > maximum + PARITY - era ) { +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Rejected too long password \"" + << password << std::string( era, '_' ) + << "\"" << " (" << era << " parity skipped)" + << std::endl; +#endif + continue; // too few parity symbols erased to start checking parity + } + std::string fixed = password; + size_t len = password.size() - ( PARITY - era ); + fixed.resize( len ); + encode( fixed ); + auto differs = std::mismatch( fixed.begin(), fixed.end(), password.begin() ); + size_t par_equ = differs.second - password.begin(); + if ( par_equ < len || par_equ > len + PARITY ) + throw std::runtime_error( "miscomputed R-S parity matching length" ); + par_equ -= len; + + // At least one parity symbol is requires to give any confidence + if ( par_equ > 0 ) { + std::string basic( fixed.begin(), fixed.begin() + len ); + confidence = par_equ * 100 / PARITY; // each worth a normal parity symbol + best.add( basic, confidence ); +#if defined( DEBUG ) && DEBUG >= 1 + output( std::cout ) + << " Check Chars. w/ " << era << " of " << PARITY + << " parity missing " << std::setw( 3 ) << confidence + << "% confidence: \"" << password + << "\" ==> \"" << basic + << " (from computed: \"" << fixed << "\")" + << ": " + << std::endl + << best; +#endif + } + } + + // Select the best guess and return its confidence. Otherwise, use raw password? If no + // error/erasure attempts succeeded (if no 'best' w/ confidence >= 0), then we'll use + // the raw password w/ 0 confidence, if it meets the minimum/maximum length + // requirements. + confidence = -1; + if ( password.size() >= ( minimum ? minimum : 1 ) + and ( maximum == 0 or password.size() <= maximum )) + confidence = 0; + + typename best_avg::const_iterator + bi = best.best(); +#if defined( DEBUG ) + output( std::cout ) + << " Selected " << ( bi != best.end() ? "corrected" : "unmodified" ) + << " password \"" << ( bi != best.end() ? bi->first : password ) + << "\" of length " << ( minimum ? minimum : 1) << "-" << maximum + << " (vs. \"" << password + << "\") w/ confidence " << (bi != best.end() ? bi->second.second : confidence ) + << "%, from: " + << std::endl + << best; +#endif + if ( bi != best.end() ) { + auto better = best.evaluation( *bi ); // --> (,) + password = better.second; + confidence = better.first; + } + return confidence; + } + + static + int decode( + std::string &password, + size_t minimum = PARITY, + size_t maximum = 0 ) + { + return decode( password, std::vector(), minimum, maximum ); + } + + // + // decode(,,,) -- C interface to decode() + // + // Traditional C interface. The provided NUL-terminated password+parity is decoded + // (parity removed), and the confidence % is returned. + // + // If any failure occurs, a -'ve value will be returned, and the supplied password + // buffer will be used to contain an error description. + // + static int decode( + char *password, // NUL terminated + size_t siz, // available size + size_t minimum = PARITY,//minimum resultant password length + size_t maximum = 0 ) // maximum '' + { + std::string corrected( password ); + int confidence; + try { + confidence = decode( corrected, minimum, maximum ); + if ( corrected.size() + 1 > siz ) + throw std::runtime_error( "password buffer has insufficient capacity" ); + std::copy( corrected.begin(), corrected.end(), password ); + password[corrected.size()] = 0; + } catch ( std::exception &exc ) { + confidence = -1; + ezpwd::streambuf_to_buffer sbf( password, siz ); + std::ostream( &sbf ) << "corrector<" << PARITY << "> failed: " << exc.what(); + } + return confidence; + } + + // + // rscodec -- A ?-bit RS(N-1,N-1-PARITY) Reed-Solomon codec + // + // Encodes and decodes R-S symbols over the lower 6 bits of the supplied data. Requires + // that the last N (parity) symbols of the data are in the range [0,63]. The excess bits on + // the data symbols are masked and restored during decoding. + // + static const ezpwd::RS + rscodec; + }; + + template < size_t PARITY, size_t N, typename SERIAL > + const ezpwd::RS + corrector::rscodec; + +} // namespace ezpwd + +template < size_t PARITY, size_t N, typename SERIAL > +std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::corrector + &rhs ) +{ + return rhs.output( lhs ); +} + +#endif // _EZPWD_CORRECTOR diff --git a/src/ezpwd/definitions b/src/ezpwd/definitions new file mode 100644 index 0000000..1ecf9a4 --- /dev/null +++ b/src/ezpwd/definitions @@ -0,0 +1,9 @@ +// +// C++ Definitions -- include once in a single C++ compilation unit +// +#ifndef _EZPWD_DEFINITIONS +#define _EZPWD_DEFINITIONS + +#include "serialize_definitions" + +#endif // _EZPWD_DEFINITIONS \ No newline at end of file diff --git a/src/ezpwd/ezcod b/src/ezpwd/ezcod new file mode 100644 index 0000000..8fe0a90 --- /dev/null +++ b/src/ezpwd/ezcod @@ -0,0 +1,725 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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. + */ +#ifndef _EZPWD_EZCOD +#define _EZPWD_EZCOD + +#include // M_PI +#include +#include + +#include +#include +#include +#include + +// +// EZCOD 3:10 location code w/ Reed-Solomon Error Correction, and average 3m accuracy +// +// - each successive symbol provides greater precision +// - codes nearby each-other are identical in leading characters +// - average 3m precision achieved in 9 symbols +// - more than 4 base-10 digits of precision in both lat and lon after the decimal +// - from 1 to 3 symbols of Reed-Solomon parity +// - 1 parity symbol supplies validation w/ strength equivalent to a check character +// - 2 parity symbols provides correction of 1 lost symbol (no errors) +// - 3 parity symbols provides correction of any 1 error, with verification, +// or recovery of up to any 3 lost symbols (with no other errors) +// + +// +// To achieve at least 4 decimal digits of precision after the decimal point, we must have +// defined lat to within 1 part in 1,800,000, and lon to within 1 part in 3,600,000. As each symbol +// supplies bits, we'll refine the computed lat/lon further, reducing the outstanding fraction of +// "parts" yet to be defined. +// +// bits +// symbols latitude longitude +// bits mul parts bits mul parts +// 1 2 4 4 3 8 8 +// 2 2 4 16 3 8 64 +// 3 3 8 128 2 4 256 // not quite integer lat/lon accuracy +// +// 4 2 4 512 3 8 2,048 +// 5 3 8 4,096 2 4 8,192 +// 6 2 4 16,384 3 8 65,536 +// +// 7 3 8 131,072 2 4 262,144 +// 8 2 4 524,288 3 8 2,097,152 +// 9 3 8 4,194,304 2 4 8,388,608 parts resolution in 3:10 code +// over [-90, 90] over [-180,180] yields ~3m resolution +// +// vs. 1,800,000 3,600,000 parts resolution in 10:10 code +// over [-90, 90] over [-180,180] yields ~10m resolution +// +// Therefore, within 9 symbols we define lat and lon with better than double the precision of +// 10:10 code's 4 decimal digits after the decimal point. This yields an approximate lineal +// precision of 40,075,000m / 8,388,608 == ~5m in both dimensions at the equator, vs. 40,075,000m / +// 3,600,000 == ~11m for 10:10 codes. +// +// The 10:10 code provides a single check character, which provides about P(1-1/32) certainty +// that the provided code is correct. With EZCOD 3:10/11/12 codes, we provide varying levels of +// detection/correction strength. +// +// - 1 parity symbol: act as a check character (like 10:10 codes), or provide 1 symbol of erasure +// (lost symbol) recovery with no excess parity for validation. +// +// - 2 parity symbols: provide 1 symbol of erasure correction (w/ no other errors) with 1 excess parity +// symbol for validation, or 1 symbol of error detection with no excess parity for validation. +// +// - 3 parity symbols: correct 1 error anywhere w/ 1 excess parity symbol for validation, or up +// to 3 erasures with no excess parity for validation. +// +// Therefore, we'll provide Reed-Solomon RS(31,28-30) error correction (5 bit symbols, +// indicating 31 symbols in the field, and from 1 to 3 roots, therefore up to 28 data symbols in the +// field) over the 9 lat/lon data symbols. +// +// +// MINIMIZING ERROR +// +// Each input lat/lon coordinate will be effectively truncated by the encoding procedure to the +// level of precision (parts) encoded by each symbol. Subsequent symbols then add their (smaller) +// parts to increase precision. +// +// After the last symbol, we know that the actual input coordinates where somewhere +// within the rectangle: +// +// [0,0] -> [0,lon_precision] -> [lat_precision,lon_precision] -> [lat_precision,0] +// +// At first glance, the best way is to perform rounding instead of truncation on ecoding, by +// simply adding 1/2 of the precision. Then, the unmodified output lat/lon decoded represents the +// point nearest actual input coordinate. However, this is NOT ideal. Remember -- the decoding may +// not have access to all the symbols! We want to minimize the error even if only some of the +// symbols are available. Thus, we must apply a correction on decoding. +// +// One way gain rounding instead of truncation on decoding is, after adding the last symbol's +// precision, to add 50% of the value represented by the first bit of the next (missing) symbol's +// precision parts. This would be analogous to receiving the first 2 digits of a 3 digit number: +// +// original: 123 +// received: 12_ +// range: [120,130) +// guessed: 125 (add 1/2 of the parts represented by the missing digit) +// +// If this is done, then the resulting coordinate would be in the middle of the rectangle of +// possible input lat/lon values that could have resulted in the encoded value. This also works if +// we don't receive and decode all of the symbols; We'll end up with a lat/lon in the middle of the +// (large) rectangle of possible input coordinates. +// + + +namespace ezpwd { + + class ezcod_base { + public: + double latitude; // [-90,+90] angle, degrees + double latitude_error; // total error bar, in meters + double longitude; // [-180,180] + double longitude_error; // total error bar, in meters + double accuracy; // linear accuracy radius, in meters + int confidence; // % parity in excess of last decode + double certainty; // and the associated probability + + explicit ezcod_base( + double _lat = 0, + double _lon = 0 ) + : latitude( _lat ) + , latitude_error( 0 ) + , longitude( _lon ) + , longitude_error( 0 ) + , accuracy( 0 ) + , confidence( 100 ) + , certainty( 1 ) + { + ; + } + virtual ~ezcod_base() + { + ; + } + + typedef std::pair + symbols_t; + virtual symbols_t symbols() + const + = 0; + virtual std::ostream &output( + std::ostream &lhs ) + const + = 0; + virtual std::string encode( + unsigned _preci = 0 ) // override precision + const + = 0; + virtual int decode( + const std::string &_ezcod ) + = 0; + }; +} // namespace ezpwd + +inline std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::ezcod_base + &rhs ) +{ + return rhs.output( lhs ); +} + +namespace ezpwd { + + // + // ezcod -- defaults to 1 PARITY and 9 location symbols (3m) of PRECISION + // + template < unsigned P=1, unsigned L=9 > + class ezcod + : public ezcod_base { + + private: + typedef std::array + bits_t; + static const bits_t bits; + typedef std::array, 12> + parts_t; + static const parts_t parts; + +#if defined( DEBUG ) + public: +#endif + static const ezpwd::RS<31,31-P> + rscodec; + + public: + static constexpr const unsigned PARITY = P; // specified symbols of R-S parity + static constexpr const unsigned PRECISION = L; // default symbols of location precision + static constexpr const unsigned CHUNK = 3; // default chunk size + + static constexpr const char SEP_NONE = -1; + static constexpr const char SEP_DEFAULT = 0; + static constexpr const char SEP_DOT = '.'; + static constexpr const char SEP_BANG = '!'; + static constexpr const char SEP_SPACE = ' '; + + static constexpr const char CHK_NONE = -1; + static constexpr const char CHK_DEFAULT = 0; + static constexpr const char CHK_DASH = '-'; + static constexpr const char CHK_SPACE = ' '; + + unsigned precision; + unsigned chunk; // Location symbol chunk sizes + char separator; // Separator between location and parity symbols + char space; // Fill space between location symbol chunks + + // + // ezcod() -- supply non-defaults for location precision, chunk size, etc. + // + explicit ezcod( + double _lat = 0, + double _lon = 0, + unsigned _preci = 0, + unsigned _chunk = 0, + char _seper = 0, + char _space = 0 ) + : ezcod_base( _lat, _lon ) + , precision( _preci ? _preci : PRECISION ) + , chunk( _chunk ? _chunk : CHUNK ) + , separator( _seper ) + , space( _space ) + { + if ( P < 1 ) + throw std::runtime_error( "ezpwd::ezcod:: At least one parity symbol must be specified" ); + if ( precision < 1 || precision > bits.max_size() ) + throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" ) + + std::to_string( bits.max_size() ) + + " location symbol may be specified" ); + } + explicit ezcod( + const std::string &_ezcod, + unsigned _preci = 0, + unsigned _chunk = 0, + char _seper = 0, + char _space = 0 ) + : ezcod( 0, 0, _preci, _chunk, _seper, _space ) + { + decode( _ezcod ); + } + virtual ~ezcod() + { + ; + } + + // + // symbols -- return working parity and location precision + // + virtual ezcod_base::symbols_t + symbols() + const + { + return ezcod_base::symbols_t( P, precision ); + } + + virtual std::ostream &output( + std::ostream &lhs ) + const + { + std::streamsize prec = lhs.precision(); + std::ios_base::fmtflags + flg = lhs.flags(); + lhs.precision( 10 ); + std::string uni = "m "; + double acc = accuracy; + double dec = 2; + if ( acc > 1000 ) { + uni = "km"; + acc /= 1000; + } else if ( acc < 1 ) { + uni = "mm"; + acc *= 1000; + } + if ( acc >= 100 ) + dec = 0; + else if ( acc >= 10 ) + dec = 1; + + lhs << encode( precision ) + << " (" << std::setw( 3 ) << confidence + << "%) == " << std::showpos << std::fixed << std::setprecision( 10 ) << std::setw( 15 ) << latitude + << ", " << std::showpos << std::fixed << std::setprecision( 10 ) << std::setw( 15 ) << longitude + << " +/- " << std::noshowpos << std::fixed << std::setprecision( dec ) << std::setw( 6 ) << acc << uni; + lhs.precision( prec ); + lhs.flags( flg ); + return lhs; + } + + // + // encode() -- encode the lat/lon to 'precision' symbols EZCOD representation + // + virtual std::string encode( + unsigned _preci = 0 ) // override precision + const + { + // Convert lat/lon into a fraction of number of parts assigned to each + double lat_frac= ( latitude + 90 ) / 180; + if ( lat_frac < 0 || lat_frac > 1 ) + throw std::runtime_error( "ezpwd::ezcod::encode: Latitude not in range [-90,90]" ); + double lon_frac= ( longitude + 180 ) / 360; + if ( lon_frac < 0 || lon_frac > 1 ) + throw std::runtime_error( "ezpwd::ezcod::encode: Longitude not in range [-180,180]" ); + if ( _preci == 0 ) + _preci = precision; + if ( _preci < 1 || _preci > bits.max_size() ) + throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" ) + + std::to_string( bits.max_size() ) + + " location symbol may be specified" ); + + // Compute the integer number of lat/lon parts represented by each coordinate, for the + // specified level of precision, and then truncate to the range [0,..._parts), + // eg. Latitude 90 --> 89.999... + uint32_t lat_parts = parts[_preci-1].first; // [ -90,90 ] / 4,194,304 parts in 9 symbols + uint32_t lon_parts = parts[_preci-1].second; // [-180,180] / 8,388,608 parts '' + + uint32_t lat_rem = std::min( lat_parts-1, uint32_t( lat_parts * lat_frac )); + uint32_t lon_rem = std::min( lon_parts-1, uint32_t( lon_parts * lon_frac )); + + // Initial loop condition; lat/lon multiplier is left at the base multiplier of the + // previous loop. Then, loop computing the units multiplier, and hten removing the most + // significant bits (multiples of the units multiplier). They will both reach 1 + unsigned int lat_mult= lat_parts; + unsigned int lon_mult= lon_parts; + + std::string res; + res.reserve( _preci // approximate result length + + ( chunk && chunk < _preci + ? _preci / chunk - 1 + : 0 ) + + 1 + P ); + for ( auto &b : bits ) { + unsigned char lat_bits= b.first; + unsigned char lon_bits= b.second; + lat_mult >>= lat_bits; + lon_mult >>= lon_bits; + if ( ! lat_mult || ! lon_mult ) + break; + + // Each set of bits represents the number of times the current multiplier (after + // division by the number of bits we're outputting) would go into the remainder. + // Eg. If _mult was 1024, and _rem is 123 and _bits is 3, we're going to put out + // the next 3 bits of the value 199. The last value ended removing all multiples of + // 1024. So, we first get the new multiplier: 1024 >> 3 == 128. So, we're + // indicating, as a 3-bit value, how many multiples of 128 there are in the value + // 199: 199 / 128 == 1, so the 3-bit value we output is 001 + uint32_t lat_val = lat_rem / lat_mult; + lat_rem -= lat_val * lat_mult; + + uint32_t lon_val = lon_rem / lon_mult; + lon_rem -= lon_val * lon_mult; + + res += char( ( lat_val << lon_bits ) | lon_val ); + } + + // Add the R-S parity symbols and base-32 encode, add parity separator and chunk + rscodec.encode( res ); + serialize::base32::encode( res ); + switch( separator ) { + case SEP_NONE: + break; + case SEP_DOT: default: + res.insert( _preci, 1, SEP_DOT ); + break; + case SEP_BANG: + case SEP_SPACE: + res.insert( _preci, 1, separator ); + break; + } + if ( space != CHK_NONE && chunk && chunk < _preci ) { + for ( unsigned c = _preci / chunk - 1; c > 0; --c ) { + switch ( space ) { + case CHK_NONE: + break; + case CHK_SPACE: default: + res.insert( c * chunk, 1, CHK_SPACE ); + break; + case CHK_DASH: + res.insert( c * chunk, 1, space ); + break; + } + } + } + + return res; + } + + // + // deserialize -- Extract base-32, skip whitespace, mark invalid symbols as erasures + // validate -- Remove base-32 encoding, validate and remove parity, returning confidence + // decode -- Attempt to decode a lat/lon, returning the confidence percentage + // + // If data but no parity symbols are supplied, no error checking is performed, and the + // confidence returned will be 0%. No erasures within the supplied data are allowed (as + // there is no capacity to correct them), and an exception will be thrown. + // + // If parity is supplied, then erasures are allowed. So long as the total number of + // erasures is <= the supplied parity symbols, then the decode will proceed (using the + // parity symbols to fill in the erasures), and the returned confidence will reflect the + // amount of unused parity capacity. Each erasure consumes one parity symbol to repair. + // + // We'll allow question-mark or any of the slash characters: "_/\?" to indicate an + // erasure. Either of the "!." symbol may be used to indicates the split between location + // symbols and parity symbols, and must be in a position that corresponds to the indicated + // number of location (this->precision) and parity 'P' symbols. Whitespace symbols and dash + // are ignored: " -". + // + // Thus, an EZCOD like "R3U 1JU QUY!0" may only be decoded by an ezcod. Without + // the "!" or ".", it could be an ezcod w/precision == 8 -- there's no way to know for + // sure. If no explicit position-parity separator is given, then we assume the default: + // this->precision location symbols, then up to P parity symbols. If additional parity + // symbols are supplied after the separator, then However, an ezcod... + // + // If an explicit "!" or "." separator IS provided, then we will attempt to decode the + // position with the given number of position symbols, and up to P parity symbols. + // + // NOTE + // + // Due to a perhaps unexpected feature of R-S codewords, a codeword with MORE parity + // can be successfully decoded by an R-S codec specifying LESS parity symbols. It so + // happens that the data plus (excess) parity + (remaining) parity is STILL a valid codeword + // (so long as the R-S Galois parameters are identical). + // + // Therefore, EZCODs with more parity are accepted by EZCOD parsers configured for less + // parity. Of course, they will have less error/erasure correction strength -- using the + // correctly configured EZCOD codec expecting more R-S parity will maximize the value of all + // the supplied parity. + // + // The full amount of parity (ie. everything after the location/parity separator) is + // discarded in all cases, before the EZCOD location is decoded. + // + private: + + unsigned deserialize( + std::string &dec, + std::vector &erasure, + std::vector &invalid ) + const + { + serialize::base32::decode( dec, &erasure, &invalid ); + + // The special symbol '!' or '.' indicates the end of the EZCOD location symbols and the + // beginning of parity: ensure the symbol counts are consistent with the encoding. By + // default the parity symbols begin at offset precision. If we see more than precision + // symbols, we assume that the Lth and subsequent symbols are parity. If a + // location/parity separator is provided, it must be at position this->precision! + // Return offset of start of parity in codeword. + unsigned parbeg = this->PRECISION; // Parity begins after Location, by default + for ( unsigned i = 0; i < invalid.size(); ++i ) { + switch ( invalid[i] ) { + case '!': case '.': + // Remember the offset of the first parity symbol (it'll be in the position of + // the last '!' or '.' symbol we're about to erase), and adjust the indices of + // any erasures following. + parbeg = erasure[i]; + dec.erase( parbeg, 1 ); + invalid.erase( invalid.begin() + i ); + erasure.erase( erasure.begin() + i ); + for ( unsigned j = i; j < erasure.size(); ++j ) + erasure[j] -= 1; + break; + case '_': case '/': case '\\': case '?': + break; + default: + throw std::runtime_error( std::string( "ezpwd::ezcod::decode: invalid symbol presented: '" ) + + invalid[i] + "'" ); + } + } +#if defined( DEBUG ) && DEBUG >= 1 + std::cout << " --> 0x" << std::vector( dec.begin(), dec.begin() + std::min( size_t( parbeg ), dec.length()) ) + << " + 0x" << std::vector( dec.begin() + std::min( size_t( parbeg ), dec.length()), + dec.begin() + dec.length() ) + << " parity" << std::endl; +#endif + return parbeg; + } + + int validate( + std::string &dec ) + const + { + // Compute and return validity (which may later be assigned to this->confidence) + int validity = 0; // if no R-S parity provided + +#if defined( DEBUG ) && DEBUG >= 1 + std::cout << *this << " validate( " << dec << " ) "; +#endif + std::vector erasure; + std::vector invalid; + unsigned parbeg = deserialize( dec, erasure, invalid ); + + if ( dec.size() > parbeg || erasure.size() > 0 ) { + // Some R-S parity symbol(s) were provided (or erasures were marked). See if we can + // successfully decode/correct, or (at least) use one parity symbol as a check + // character. If we identify more erasures than R-S parity, we must fail; we can't + // recover the data. This will of course be the case if we have *any* erasures in + // the data, and no parity. + unsigned parity = 0; + if ( dec.size() > parbeg ) + parity = dec.size() - parbeg; + while ( dec.size() < parbeg + P ) { + erasure.push_back( dec.size() ); + dec.resize( dec.size() + 1 ); + } +#if defined( DEBUG ) && DEBUG >= 2 + std::cout << " --> erasures: " << erasure.size() << " vs. parity: " << parity + << ": " << std::vector( dec.begin(), dec.end() ) << std::endl; +#endif + if ( erasure.size() > parity ) { + // We cannot do R-S decoding; not enough parity symbols to even cover erasures. + // If parity symbol(s) were provided ('parity' > 0), and all erasures were due the + // missing remaining parity symbols, we can use the existing parity symbol(s) as + // "check character(s)", by simply re-encoding the supplied non-parity data, and + // see if the generated parity symbol(s) match the supplied parity. This has + // basically the same strength as the 10:10 code's check character. + if ( parity + erasure.size() == P ) { + // All erasures must be at end, in remaining parity symbols! + std::string chk( dec.begin(), dec.begin() + parbeg ); + rscodec.encode( chk ); + // each parity symbol provided must match the corresponding encoded chk symbol + for ( unsigned i = 0; i < parity; ++i ) + if ( dec[parbeg+i] != chk[parbeg+i] ) + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; check character mismatch" ); + // Check character(s) matched; erasure.size()/P of confidence gone + validity = ezpwd::strength

( erasure.size(), erasure, erasure ); + } else + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; too many erasures" ); + } else { + // We can try R-S decoding; we have (at least) enough parity to try to recover + // any missing symbol(s). + std::vectorposition; + int corrects= rscodec.decode( dec, erasure, &position ); + if ( corrects < 0 ) + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode failed" ); + // Compute confidence, from spare parity capacity. Since R-S decode will not + // return the position of erasures that turn out (by accident) to be correct, + // but they have consumed parity capacity, we re-add them into the correction + // position vector. If the R-S correction reports more corrections than the + // parity can possibly have handled correctly, (eg. 2 reported erasures and an + // unexpected error), then the decode is almost certainly incorrect; fail. + validity = ezpwd::strength

( corrects, erasure, position ); + if ( validity < 0 ) + throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode overwhelmed" ); + } + if ( dec.size() > parbeg ) + dec.resize( parbeg ); // Discard any parity symbols + } + return validity; + } + + public: + virtual int decode( const std::string &str ) + { + // Decode the R-S encoding, computing the confidence. Will raise an exception on any + // error. Don't change this->confidence, this->latitude, ... until there is no longer a + // chance of exception. + std::string decoded( str ); + int validity= validate( decoded ); + + // Unpack the supplied location data; we'll take as much as we are given, up to the + // maximum possible 12 symbols supported (9 symbols yielding ~3m resolution). + uint32_t lat_tot = 0; + uint32_t lon_tot = 0; + + uint32_t lat_mult= 1; + uint32_t lon_mult= 1; + + auto di = decoded.begin(); + for ( auto &b : bits ) { + if ( di == decoded.end() ) + break; + unsigned char c = *di++; + + unsigned char lat_bits= b.first; + unsigned char lon_bits= b.second; + + uint32_t lat_val = c >> lon_bits; + uint32_t lon_val = c & (( 1 << lon_bits ) - 1 ); + + lat_mult <<= lat_bits; + lat_tot <<= lat_bits; + lat_tot += lat_val; + + lon_mult <<= lon_bits; + lon_tot <<= lon_bits; + lon_tot += lon_val; + } + + // Convert the sum of lat/lon parts back into degrees, and round the (truncated) value + // to the middle of the error rectangle. This allows us to minimize error even if we + // didn't have access to all of the origin symbols to decode. The absolute error bar as + // a proportional factor [0,1) for lat/lon is at most the scale of the last parts + // multiplier used. We'll use this later to compute the error in meters; for example, + // if the last value we added worked out to be worth units of 25m of the circumference, + // then we must now be within [0,25m) of the original point. + double lat_err = 1.0 / lat_mult; + double lon_err = 1.0 / lon_mult; + latitude = 180 * ( double( lat_tot ) / lat_mult + lat_err / 2 ) - 90; + longitude = 360 * ( double( lon_tot ) / lon_mult + lon_err / 2 ) - 180; + + // Remember the decoded location precision for future encoding (overrides the default). + // Compute the certainty probability (0.0,1.0] given the number of parity symbols in + // excess: Given a base-32 symbol: 1 - 1 / ( 32 ^ P ) where P is the number of + // unconsumed parity. + precision = decoded.size(); + confidence = validity; + certainty = 0.0; + if ( PARITY * confidence / 100 ) + certainty = 1.0 - 1.0 / std::pow( double( 32.0 ), + double( PARITY * confidence / 100 )); + + // Compute the resolution error (in m.) of the decoded lat/lon and compute the minimum + // accuracy -- the radius of the circle around the computed latitude/longitude, inside + // which the original latitude/longitude must have been. + // + // original latitude error bar + // \ / + // o - + // | longitude error bar + // | / + // |--x--| + // /| + // / | + // computed - + // + // The maximum distance is the length of the diagonal of the error rectangle defined by + // 1/2 the latitude/longitude error bars. + // + double lon_circ= 1 * M_PI * 6371000; + double lat_circ= 2 * M_PI * 6371000 * std::cos( latitude * M_PI / 180 ); + latitude_error = lat_err * lon_circ; + longitude_error = lon_err * lat_circ; + + accuracy = sqrt( latitude_error / 2 * latitude_error / 2 + + longitude_error / 2 * longitude_error / 2 ); + return confidence; + } + }; // class ezcod + + + // + // ezcod::rscodec -- Reed-Solomon parity codec + // ezcod::bits -- distribution of lat/lon precision in each code symbol + // + // Quickly establishes an extra bit of precision for Longitude, and then evenly distributes + // future precision between lat/lon, always maintaining extra precision for Longitude. + // + template < unsigned P, unsigned L > + const ezpwd::RS<31,31-P> ezcod::rscodec; + + // Number of lat/lon bits represented for each location symbol + template < unsigned P, unsigned L > + const typename ezcod::bits_t + ezcod::bits = { + { + // bits per symbol lat lon + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + // -- -- + // 7 8 + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + ezcod::bits_t::value_type( 2, 3 ), + // -- -- + // 14 16 + ezcod::bits_t::value_type( 3, 2 ), + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + // -- -- + // 22 23 + ezcod::bits_t::value_type( 2, 3 ), + ezcod::bits_t::value_type( 3, 2 ), + ezcod::bits_t::value_type( 2, 3 ), + // -- -- + // 29 31 + } + }; + + // Total number of parts that lat/lon is subdivided into, for that number of location symbols. + template < unsigned P, unsigned L > + const typename ezcod::parts_t + ezcod::parts = { + { + // parts per symbol lat parts lon parts lat lon bits + ezcod::parts_t::value_type( 1UL << 2, 1UL << 3 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 4, 1UL << 6 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 7, 1UL << 8 ), // 3, 2 + // -- -- + // 7 8 + ezcod::parts_t::value_type( 1UL << 9, 1UL << 11 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 12, 1UL << 13 ), // 3, 2 + ezcod::parts_t::value_type( 1UL << 14, 1UL << 16 ), // 2, 3 + // -- -- + // 14 16 + ezcod::parts_t::value_type( 1UL << 17, 1UL << 18 ), // 3, 2 + ezcod::parts_t::value_type( 1UL << 19, 1UL << 21 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 22, 1UL << 23 ), // 3, 2 + // -- -- + // 22 23 + ezcod::parts_t::value_type( 1UL << 24, 1UL << 26 ), // 2, 3 + ezcod::parts_t::value_type( 1UL << 27, 1UL << 28 ), // 3, 2 + ezcod::parts_t::value_type( 1UL << 29, 1UL << 31 ), // 2, 3 + // -- -- + // 29 31 + } + }; +} // namespace ezpwd + +#endif // _EZPWD_EZCOD diff --git a/src/ezpwd/output b/src/ezpwd/output new file mode 100644 index 0000000..230be80 --- /dev/null +++ b/src/ezpwd/output @@ -0,0 +1,344 @@ +#ifndef _EZPWD_OUTPUT +#define _EZPWD_OUTPUT + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// +// ezpwd::hexchr -- escape/hexify char c, output using func/meth f, in width w >= 2 +// ezpwd::hexify -- hexify something that can be converted to an unsigned char +// ezpwd::hexout -- hexify each element in the range (beg,end], limited by stream's width +// +// std::ostream << ezpwd::hexify( c ) // output any char escaped/hex +// std::ostream << ezpwd::hexout( beg, end ) // output any char iterator to ostream +// std::ostream << std::vector +// std::ostream << std::array +// ezpwd::hexchr( c, [](unsigned char c){...;} )// output escaped/hex char via functor +// ezpwd::hexout( beg, end, FILE* ) // output any char iterator to FILE* +// +// Output unprintable unsigned char data in hex, escape printable/whitespace data. +// +namespace ezpwd { + + struct hexify { + unsigned char c; + std::streamsize w; + explicit hexify( + unsigned char _c, + std::streamsize _w = 2 ) + : c( _c ) + , w( _w ) + { ; } + explicit hexify( + char _c, + std::streamsize _w = 2 ) + : c( (unsigned char)_c ) + , w( _w ) + { ; } + }; + struct hexstr { + const std::string &s; + explicit hexstr( + const std::string &_s ) + : s( _s ) + { ; } + }; + + template // a functor taking a char + void + hexchr( unsigned char c, F f = []( unsigned char c ) { std::cout.put( c );}, size_t w = 2 ) + { + for ( ; w > 2; --w ) + f( ' ' ); + if ( std::isprint( c ) || std::isspace( c ) + || c == '\0' || c == '\a' || c == '\b' || c == 0x1B ) { // '\e' is not standard + switch ( c ) { + case 0x00: f( '\\' ); f( '0' ); break; // NUL + case 0x07: f( '\\' ); f( 'a' ); break; // BEL + case 0x08: f( '\\' ); f( 'b' ); break; // BS + case 0x09: f( '\\' ); f( 't' ); break; // HT + case 0x0A: f( '\\' ); f( 'n' ); break; // LF + case 0x0B: f( '\\' ); f( 'v' ); break; // VT + case 0x0C: f( '\\' ); f( 'f' ); break; // FF + case 0x0D: f( '\\' ); f( 'r' ); break; // CR + case 0x1B: f( '\\' ); f( 'e' ); break; // ESC + case '\"': f( '\\' ); f( '"' ); break; // " + case '\'': f( '\\' ); f( '\''); break; // ' + case '\\': f( '\\' ); f( '\\'); break; // '\' + default: f( ' ' ); f( c ); // space, any other printable character + } + } else { + f( "0123456789ABCDEF"[( c >> 4 ) & 0x0f ] ); + f( "0123456789ABCDEF"[( c >> 0 ) & 0x0f ] ); + } + } + + + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::hexify&rhs ) + { + ezpwd::hexchr( rhs.c, [ &lhs ]( unsigned char c ) { lhs.put( c ); }, rhs.w ); + return lhs; + } + + template < typename iter_t > + inline + std::ostream &hexout( + std::ostream &lhs, + const iter_t &beg, + const iter_t &end ) + { + std::streamsize wid = lhs.width( 0 ); + int col = 0; + for ( auto i = beg; i != end; ++i ) { + if ( wid && col == wid ) { + lhs << std::endl; + col = 0; + } + lhs << hexify( *i ); + ++col; + } + return lhs; + } + + template < typename iter_t > + inline + std::FILE *hexout( + const iter_t &beg, + const iter_t &end, + std::FILE *lhs ) + { + for ( auto i = beg; i != end; ++i ) { + ezpwd::hexchr( *i, [ lhs ]( unsigned char c ) { std::fputc( c, lhs ); } ); + } + return lhs; + } + + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::hexstr&rhs ) + { + return ezpwd::hexout( lhs, rhs.s.begin(), rhs.s.end() ); + } +} // namespace ezpwd + +namespace std { + template < size_t S > + inline + std::ostream &operator<<( + std::ostream &lhs, + const std::array + &rhs ) + { + return ezpwd::hexout( lhs, rhs.begin(), rhs.end() ); + } + + inline + std::ostream &operator<<( + std::ostream &lhs, + const std::vector + &rhs ) + { + return ezpwd::hexout( lhs, rhs.begin(), rhs.end() ); + } + + // + // << pair + // << set -- sorted by T + // << map -- sorted by T (key) + // << vector + // + // Handle output of various container types. + // + // Output pairs and sets of pairs, respecting specified widths (as appropriate). For example + // a set of pairs of integeters 's', if output as "... << std::setw( 13 ) << s;", would yield: + // + // ( 1, 2) ( 3, 4) ... + // + + template + std::ostream &operator<<( + std::ostream &lhs, + const std::pair &rhs ) + { + std::streamsize w = std::max( std::streamsize( 0 ), + std::streamsize( lhs.width() - 3 )); + lhs << std::setw( 0 ) + << '(' << std::setw( w / 2 ) << rhs.first + << ',' << std::setw( w - w / 2 ) << rhs.second + << ')'; + return lhs; + } + + template + std::ostream &operator<<( + std::ostream &lhs, + const std::set &rhs ) + { + std::streamsize w = lhs.width(); // If width is set, use if for each item + for ( typename std::set::const_iterator + si = rhs.begin() + ; si != rhs.end() + ; ++si ) { + if ( si != rhs.begin()) + lhs << ' '; + lhs << std::setw( w ) << *si; + } + lhs << std::setw( 0 ); // If container empty, must clear + return lhs; + } + +template +std::ostream &operator<<( + std::ostream &lhs, + const std::map&rhs ) +{ + std::streamsize w = lhs.width(); // If width is set, use if for each item + std::vector key; + for ( typename std::map::const_iterator + mi = rhs.begin() + ; mi != rhs.end() + ; ++mi ) + key.push_back( mi->first ); + std::sort( key.begin(), key.end() ); + for ( typename std::vector::const_iterator + ki = key.begin() + ; ki != key.end() + ; ++ki ) { + if ( ki != key.begin()) + lhs << ' '; + lhs << std::setw( w ) << *rhs.find( *ki ); + } + lhs << std::setw( 0 ); // If container empty, must clear + return lhs; +} + +template +std::ostream &operator<<( + std::ostream &lhs, + const std::vector &rhs ) +{ + for( size_t i = 0; i < rhs.size(); ++i ) { + if ( i ) + lhs << ", "; + lhs << rhs[i]; + } + + return lhs; +} +} // namespace std + +// +// ezpwd::buf_t -- describe a C string buffer, to allow C++ output operations +// ezpwd::streambuf_to_buf_t -- output charcters, always NUL terminated +// +// << ... -- Copy the into the C buffer, always NUL terminating +// +// Copies contents into buffer, and always NUL-terminates. Returns advanced buf_t (NOT +// including the terminating NUL, suitable for repeating ... << operations. +// +// std::ostream( & ) << ... +// +// Use standard ostream operations to send output to a C buffer, always NUL +// terminating, and never exceeding capacity. +// +namespace ezpwd { + + typedef std::pair + buf_t; + + class streambuf_to_buffer + : public std::streambuf { + private: + char *_buf; + size_t _siz; + public: + // + // streambuf_to_buf_t -- remember buf_t details + // ~streambuf_to_buf_t -- no virtual destructor required; nothing to clean up + // + streambuf_to_buffer( + char *buf, + size_t siz ) + : _buf( buf ) + , _siz( siz ) + { + if ( _siz > 0 ) + *_buf = 0; + } + explicit streambuf_to_buffer( + const buf_t &buf ) + : streambuf_to_buffer( buf.first, buf.second ) + { + ; + } + + // + // overflow -- Append c, always NUL terminating + // + virtual int overflow( + int c ) + { + if ( _siz <= 1 ) + return EOF; // No room for c and NUL; EOF + if ( EOF == c ) + return 0; // EOF provided; do nothing + --_siz; + *_buf++ = char( c ); + *_buf = 0; + return c; + } + }; // class streambuf_to_buffer + +} // namespace ezpwd + +namespace std { + + inline + ezpwd::buf_t operator<<( + const ezpwd::buf_t &buf, + const std::string &str ) + { + if ( buf.first && str.size() + 1 <= buf.second ) { + std::copy( str.begin(), str.end(), buf.first ); + buf.first[str.size()] = 0; + return ezpwd::buf_t( buf.first + str.size(), buf.second - str.size() ); + } else if ( buf.first && buf.second ) { + std::copy( str.begin(), str.begin() + buf.second - 1, buf.first ); + buf.first[buf.second-1] = 0; + return ezpwd::buf_t( buf.first + buf.second - 1, 1 ); + } + return buf; // NULL pointer or 0 size. + } + + + // + // << ... + // + // Useful (but inefficient) standard output formatting directly to a std::string. Use only for + // testing code, for efficiency reasons... + // + template < typename T > + inline + std::string operator<<( + const std::string &lhs, + const T &rhs ) + { + std::ostringstream oss; + oss << rhs; + return std::string( lhs ).append( oss.str() ); + } + +} // namespace std + +#endif // _EZPWD_OUTPUT diff --git a/src/ezpwd/rs b/src/ezpwd/rs new file mode 100644 index 0000000..3479a49 --- /dev/null +++ b/src/ezpwd/rs @@ -0,0 +1,168 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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. + */ +#ifndef _EZPWD_RS +#define _EZPWD_RS + +#include "rs_base" + +// +// ezpwd::RS -- Implements an RS(SYMBOLS,PAYLOAD) codec +// ezpwd::RS_CCSDS<...> -- CCSDS standard 8-bit R-S codec +// +// Support for Reed-Solomon codecs for symbols of 2 to 16 bits is supported. The R-S "codeword" +// for an N-bit symbol is defined to be 2^N-1 symbols in size. For example, for 5-bit symbols, +// 2^5-1 == 31, so the notation for defining an Reed-Solomon codec for 5-bit symbols is always: +// RS(31,PAYLOAD), where PAYLOAD is always some value less than 31. The difference is the number of +// "parity" symbols. +// +// For example, to define an RS codeword of 31 symbols w/ 4 symbols of parity and up to 27 +// symbols of data, you would say: RS(31,27). Of course, you can supply smaller amounts of data; +// the balance is assumed to be NUL (zero) symbols. +// +namespace ezpwd { + + // + // __RS( ... ) -- Define a reed-solomon codec + // + // @SYMBOLS: Total number of symbols; must be a power of 2 minus 1, eg 2^8-1 == 255 + // @PAYLOAD: The maximum number of non-parity symbols, eg 253 ==> 2 parity symbols + // @POLY: A primitive polynomial appropriate to the SYMBOLS size + // @FCR: The first consecutive root of the Reed-Solomon generator polynomial + // @PRIM: The primitive root of the generator polynomial + // +# define __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) \ + ezpwd::reed_solomon< \ + TYPE, \ + ezpwd::log_< (SYMBOLS) + 1 >::value, \ + (SYMBOLS) - (PAYLOAD), FCR, PRIM, \ + ezpwd::gfpoly< \ + ezpwd::log_< (SYMBOLS) + 1 >::value, \ + POLY >> + +# define __RS( NAME, TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) \ + __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) { \ + NAME() \ + : __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM )() \ + {;} \ + } + + // + // RS -- Standard partial specializations for Reed-Solomon codec type access + // + // Normally, Reed-Solomon codecs are described with terms like RS(255,252). Obtain various + // standard Reed-Solomon codecs using macros of a similar form, eg. RS<255, 252>. Standard PLY, + // FCR and PRM values are provided for various SYMBOL sizes, along with appropriate basic types + // capable of holding all internal Reed-Solomon tabular data. + // + // In order to provide "default initialization" of const RS<...> types, a user-provided + // default constructor must be provided. + // + template < size_t SYMBOLS, size_t PAYLOAD > struct RS; + template < size_t PAYLOAD > struct RS< 3, PAYLOAD> : public __RS( RS, uint8_t, 3, PAYLOAD, 0x7, 1, 1 ); + template < size_t PAYLOAD > struct RS< 7, PAYLOAD> : public __RS( RS, uint8_t, 7, PAYLOAD, 0xb, 1, 1 ); + template < size_t PAYLOAD > struct RS< 15, PAYLOAD> : public __RS( RS, uint8_t, 15, PAYLOAD, 0x13, 1, 1 ); + template < size_t PAYLOAD > struct RS< 31, PAYLOAD> : public __RS( RS, uint8_t, 31, PAYLOAD, 0x25, 1, 1 ); + template < size_t PAYLOAD > struct RS< 63, PAYLOAD> : public __RS( RS, uint8_t, 63, PAYLOAD, 0x43, 1, 1 ); + template < size_t PAYLOAD > struct RS< 127, PAYLOAD> : public __RS( RS, uint8_t, 127, PAYLOAD, 0x89, 1, 1 ); + template < size_t PAYLOAD > struct RS< 255, PAYLOAD> : public __RS( RS, uint8_t, 255, PAYLOAD, 0x11d, 1, 1 ); + template < size_t PAYLOAD > struct RS< 511, PAYLOAD> : public __RS( RS, uint16_t, 511, PAYLOAD, 0x211, 1, 1 ); + template < size_t PAYLOAD > struct RS< 1023, PAYLOAD> : public __RS( RS, uint16_t, 1023, PAYLOAD, 0x409, 1, 1 ); + template < size_t PAYLOAD > struct RS< 2047, PAYLOAD> : public __RS( RS, uint16_t, 2047, PAYLOAD, 0x805, 1, 1 ); + template < size_t PAYLOAD > struct RS< 4095, PAYLOAD> : public __RS( RS, uint16_t, 4095, PAYLOAD, 0x1053, 1, 1 ); + template < size_t PAYLOAD > struct RS< 8191, PAYLOAD> : public __RS( RS, uint16_t, 8191, PAYLOAD, 0x201b, 1, 1 ); + template < size_t PAYLOAD > struct RS<16383, PAYLOAD> : public __RS( RS, uint16_t, 16383, PAYLOAD, 0x4443, 1, 1 ); + template < size_t PAYLOAD > struct RS<32767, PAYLOAD> : public __RS( RS, uint16_t, 32767, PAYLOAD, 0x8003, 1, 1 ); + template < size_t PAYLOAD > struct RS<65535, PAYLOAD> : public __RS( RS, uint16_t, 65535, PAYLOAD, 0x1100b, 1, 1 ); + + template < size_t SYMBOLS, size_t PAYLOAD > struct RS_CCSDS; + template < size_t PAYLOAD > struct RS_CCSDS<255, PAYLOAD> : public __RS( RS_CCSDS, uint8_t, 255, PAYLOAD, 0x187, 112, 11 ); + + + // + // strength -- compute strength (given N parity symbols) of R-S correction + // + // Returns a confidence strength rating, which is the ratio: + // + // 100 - ( errors * 2 + erasures ) * 100 / parity + // + // which is proportional to the number of parity symbols unused by the reported number of + // corrected symbols. If 0, then all parity resources were consumed to recover the R-S + // codeword, and we can have no confidence in the result. If -'ve, indicates more parity + // resources were consumed than available, indicating that the result is likely incorrect. + // + // Accounts for the fact that a signalled erasure may not be reported in the corrected + // position vector, if the symbol happens to match the computed value. Note that even if the + // error or erasure occurs within the "parity" portion of the codeword, this doesn't reduce the + // effective strength -- all symbols in the R-S complete codeword are equally effective in + // recovering any other symbol in error/erasure. + // + template < size_t PARITY > + int strength( + int corrected, + const std::vector&erasures, // original erasures positions + const std::vector&positions ) // all reported correction positions + { + // -'ve indicates R-S failure; all parity consumed, but insufficient to correct the R-S + // codeword. Missing an unknown number of additional required parity symbols, so just + // return -1 as the strength. + if ( corrected < 0 ) { +#if defined( DEBUG ) && DEBUG >= 2 + std::cout + << corrected << " corrections (R-S decode failure) == -1 confidence" + << std::endl; +#endif + return -1; + } + if ( corrected != int( positions.size() )) + EZPWD_RAISE_OR_RETURN( std::runtime_error, "inconsistent R-S decode results", -1 ); + + // Any erasures that don't turn out to contain errors are not returned as fixed positions. + // However, they have consumed parity resources. Search for each erasure location in + // positions, and if not reflected, add to the corrected/missed counters. + int missed = 0; + for ( auto e : erasures ) { + if ( std::find( positions.begin(), positions.end(), e ) == positions.end() ) { + ++corrected; + ++missed; +#if defined( DEBUG ) && DEBUG >= 2 + std::cout + << corrected << " corrections (R-S erasure missed): " << e + << std::endl; +#endif + } + } + int errors = corrected - erasures.size(); + int consumed= errors * 2 + erasures.size(); + int confidence= 100 - consumed * 100 / PARITY; +#if defined( DEBUG ) && DEBUG >= 2 + std::cout + << corrected << " corrections (R-S decode success)" + << " at: " << positions + << ", " << erasures.size() + missed + << " erasures (" << missed + << " unreported) at: " << erasures + << ") ==> " << errors + << " errors, and " << consumed << " / " << PARITY + << " parity used == " << confidence + << "% confidence" + << std::endl; +#endif + return confidence; + } + +} // namespace ezpwd + +#endif // _EZPWD_RS diff --git a/src/ezpwd/rs_base b/src/ezpwd/rs_base new file mode 100644 index 0000000..5dad4e3 --- /dev/null +++ b/src/ezpwd/rs_base @@ -0,0 +1,1344 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. The + * c++/ezpwd/rs_base file is redistributed under the terms of the LGPL, regardless of the overall + * licensing terms. + * + * Ezpwd Reed-Solomon 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. + * + * The core Reed-Solomon codec implementation in c++/ezpwd/rs_base is by Phil Karn, converted to C++ + * by Perry Kundert (perry@hardconsulting.com), and may be used under the terms of the LGPL. Here + * is the terms from Phil's README file (see phil-karn/fec-3.0.1/README): + * + * COPYRIGHT + * + * This package is copyright 2006 by Phil Karn, KA9Q. It may be used + * under the terms of the GNU Lesser General Public License (LGPL). See + * the file "lesser.txt" in this package for license details. + * + * The c++/ezpwd/rs_base file is, therefore, redistributed under the terms of the LGPL, while the + * rest of Ezpwd Reed-Solomon is distributed under either the GPL or Commercial licenses. + * Therefore, even if you have obtained Ezpwd Reed-Solomon under a Commercial license, you must make + * available the source code of the c++/ezpwd/rs_base file with your product. One simple way to + * accomplish this is to include the following URL in your code or documentation: + * + * https://github.com/pjkundert/ezpwd-reed-solomon/blob/master/c++/ezpwd/rs_base + * + * + * The Linux 3.15.1 version of lib/reed_solomon was also consulted as a cross-reference, which (in + * turn) is basically verbatim copied from Phil Karn's LGPL implementation, to ensure that no new + * defects had been found and fixed; there were no meaningful changes made to Phil's implementation. + * I've personally been using Phil's implementation for years in a heavy industrial use, and it is + * rock-solid. + * + * However, both Phil's and the Linux kernel's (copy of Phil's) implementation will return a + * "corrected" decoding with impossible error positions, in some cases where the error load + * completely overwhelms the R-S encoding. These cases, when detected, are rejected in this + * implementation. This could be considered a defect in Phil's (and hence the Linux kernel's) + * implementations, which results in them accepting clearly incorrect R-S decoded values as valid + * (corrected) R-S codewords. We chose the report failure on these attempts. + * + */ +#ifndef _EZPWD_RS_BASE +#define _EZPWD_RS_BASE + +#include +#include +#include +#include +#include +#include +#include + +// +// Preprocessor defines available: +// +// EZPWD_NO_EXCEPTS -- define to use no exceptions; return -1, or abort on catastrophic failures +// EZPWD_NO_MOD_TAB -- define to force no "modnn" Galois modulo table acceleration +// EZPWD_ARRAY_SAFE -- define to force usage of bounds-checked arrays for most tabular data +// EZPWD_ARRAY_TEST -- define to force erroneous sizing of some arrays for non-production testing +// + +#if defined( DEBUG ) && DEBUG >= 2 +# include "output" // ezpwd::hex... std::ostream shims for outputting containers of uint8_t data +#endif + +#if defined( EZPWD_NO_EXCEPTS ) +# include // No exceptions; don't use C++ ostream +# define EZPWD_RAISE_OR_ABORT( typ, str ) do { \ + std::fputs(( str ), stderr ); std::fputc( '\n', stderr ); \ + abort(); \ + } while ( false ) +# define EZPWD_RAISE_OR_RETURN( typ, str, ret ) return ( ret ) +#else +# define EZPWD_RAISE_OR_ABORT( typ, str ) throw ( typ )( str ) +# define EZPWD_RAISE_OR_RETURN( typ, str, ret ) throw ( typ )( str ) +#endif + +namespace ezpwd { + + // ezpwd::log_ -- compute the log base B of N at compile-time + template struct log_{ enum { value = 1 + log_::value }; }; + template struct log_<1, B>{ enum { value = 0 }; }; + template struct log_<0, B>{ enum { value = 0 }; }; + + // + // reed_solomon_base - Reed-Solomon codec generic base class + // + class reed_solomon_base { + public: + virtual size_t datum() const = 0; // a data element's bits + virtual size_t symbol() const = 0; // a symbol's bits + virtual int size() const = 0; // R-S block size (maximum total symbols) + virtual int nroots() const = 0; // R-S roots (parity symbols) + virtual int load() const = 0; // R-S net payload (data symbols) + + virtual ~reed_solomon_base() + { + ; + } + reed_solomon_base() + { + ; + } + + virtual std::ostream &output( + std::ostream &lhs ) + const + { + return lhs << "RS(" << this->size() << "," << this->load() << ")"; + } + + // + // {en,de}code -- Compute/Correct errors/erasures in a Reed-Solomon encoded container + // + /// The encoded parity symbols may be included in 'data' (len includes nroots() parity + /// symbols), or may (optionally) supplied separately in (at least nroots()-sized) + /// 'parity'. + /// + /// For decode, optionally specify some known erasure positions (up to nroots()). If + /// non-empty 'erasures' is provided, it contains the positions of each erasure. If a + /// non-zero pointer to a 'position' vector is provided, its capacity will be increased to + /// be capable of storing up to 'nroots()' ints; the actual deduced error locations will be + /// returned. + /// + /// RETURN VALUE + /// + /// Return -1 on error. The encode returns the number of parity symbols produced; + /// decode returns the number of symbols corrected. Both errors and erasures are included, + /// so long as they are actually different than the deduced value. In other words, if a + /// symbol is marked as an erasure but it actually turns out to be correct, it's index will + /// NOT be included in the returned count, nor the modified erasure vector! + /// + + // + // encode() -- extend string to contain parity, or place in supplied parity string + // encode() -- extend vector to contain parity, or place in supplied parity vector + // encode() -- ignore 'pad' elements of array, puts nroots() parity symbols at end + // encode(pair) -- encode all except the last nroots() of the data, put parity at end + // encode(pair, pair) -- encode data between first pair, put parity in second pair + // + int encode( + std::string &data ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + data.resize( data.size() + nroots() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + + int encode( + const std::string &data, + std::string &parity ) + const + { + typedef uint8_t uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( nroots() ); + return encode( cuTpair( (const uT *)&data.front(), (const uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T > + int encode( + std::vector &data ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + data.resize( data.size() + nroots() ); + return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() )); + } + template < typename T > + int encode( + const std::vector&data, + std::vector &parity ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + cuTpair; + typedef std::pair + uTpair; + parity.resize( nroots() ); + return encode( cuTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() )); + } + + template < typename T, size_t N > + int encode( + std::array &data, + int pad = 0 ) // ignore 'pad' symbols at start of array + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return encode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() )); + } + + virtual int encode( + const std::pair + &data ) + const + = 0; + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + = 0; + virtual int encode( + const std::pair + &data ) + const + = 0; + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + = 0; + virtual int encode( + const std::pair + &data ) + const + = 0; + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + = 0; + + int decode( + std::string &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + erasure, position ); + } + + int decode( + std::string &data, + std::string &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef uint8_t uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + erasure, position ); + } + + template < typename T > + int decode( + std::vector &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + erasure, position ); + } + + template < typename T > + int decode( + std::vector &data, + std::vector &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ), + uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ), + erasure, position ); + } + + template < typename T, size_t N > + int decode( + std::array &data, + int pad = 0, // ignore 'pad' symbols at start of array + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + typedef typename std::make_unsigned::type + uT; + typedef std::pair + uTpair; + return decode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() ), + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + = 0; + }; // class reed_solomon_base + + // + // std::ostream << ezpwd::reed_solomon<...> + // + // Output a R-S codec description in standard form eg. RS(255,253) + // + inline + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::reed_solomon_base + &rhs ) + { + return rhs.output( lhs ); + } + + // + // gfpoly - default field polynomial generator functor. + // + template < int SYM, int PLY > + struct gfpoly { + int operator() ( int sr ) + const + { + if ( sr == 0 ) + sr = 1; + else { + sr <<= 1; + if ( sr & ( 1 << SYM )) + sr ^= PLY; + sr &= (( 1 << SYM ) - 1); + } + return sr; + } + }; + + // + // class reed_solomon_tabs -- R-S tables common to all RS(NN,*) with same SYM, PRM and PLY + // + template < typename TYP, int SYM, int PRM, class PLY > + class reed_solomon_tabs + : public reed_solomon_base { + + public: + typedef TYP symbol_t; + static const size_t DATUM = 8 * sizeof TYP(); // bits / TYP + static const size_t SYMBOL = SYM; // bits / symbol + static const int MM = SYM; + static const int SIZE = ( 1 << SYM ) - 1; // maximum symbols in field + static const int NN = SIZE; + static const int A0 = SIZE; + static const int MODS // modulo table: 1/2 the symbol size squared, up to 4k +#if defined( EZPWD_NO_MOD_TAB ) + = 0; +#else + = SYM > 8 ? ( 1 << 12 ) : ( 1 << SYM << SYM/2 ); +#endif + + static int iprim; // initialized to -1, below + + protected: + static std::array +#else +# warning "EZPWD_ARRAY_TEST: Erroneously declaring alpha_to size!" + NN > +#endif + alpha_to; + static std::array + index_of; + static std::array + mod_of; + virtual ~reed_solomon_tabs() + { + ; + } + reed_solomon_tabs() + : reed_solomon_base() + { + // Do init if not already done. We check one value which is initialized to -1; this is + // safe, 'cause the value will not be set 'til the initializing thread has completely + // initialized the structure. Worst case scenario: multiple threads will initialize + // identically. No mutex necessary. + if ( iprim >= 0 ) + return; + +#if defined( DEBUG ) && DEBUG >= 1 + std::cout << "RS(" << SIZE << ",*): Initialize for " << NN << " symbols size, " << MODS << " modulo table." << std::endl; +#endif + // Generate Galois field lookup tables + index_of[0] = A0; // log(zero) = -inf + alpha_to[A0] = 0; // alpha**-inf = 0 + PLY poly; + int sr = poly( 0 ); + for ( int i = 0; i < NN; i++ ) { + index_of[sr] = i; + alpha_to[i] = sr; + sr = poly( sr ); + } + // If it's not primitive, raise exception or abort + if ( sr != alpha_to[0] ) { + EZPWD_RAISE_OR_ABORT( std::runtime_error, "reed-solomon: Galois field polynomial not primitive" ); + } + + // Generate modulo table for some commonly used (non-trivial) values + for ( int x = NN; x < NN + MODS; ++x ) + mod_of[x-NN] = _modnn( x ); + // Find prim-th root of 1, index form, used in decoding. + int iptmp = 1; + while ( iptmp % PRM != 0 ) + iptmp += NN; + iprim = iptmp / PRM; + } + + // + // modnn -- modulo replacement for galois field arithmetics, optionally w/ table acceleration + // + // @x: the value to reduce (will never be -'ve) + // + // where + // MM = number of bits per symbol + // NN = (2^MM) - 1 + // + // Simple arithmetic modulo would return a wrong result for values >= 3 * NN + // + TYP _modnn( + int x ) + const + { + while ( x >= NN ) { + x -= NN; + x = ( x >> MM ) + ( x & NN ); + } + return x; + } + TYP modnn( + int x ) + const + { + while ( x >= NN + MODS ) { + x -= NN; + x = ( x >> MM ) + ( x & NN ); + } + if ( MODS && x >= NN ) + x = mod_of[x-NN]; + return x; + } + }; + + // + // class reed_solomon - Reed-Solomon codec + // + // @TYP: A symbol datum; {en,de}code operates on arrays of these + // @DATUM: Bits per datum (a TYP()) + // @SYM{BOL}, MM: Bits per symbol + // @NN: Symbols per block (== (1< instances with the same template type parameters share a common + // (static) set of alpha_to, index_of and genpoly tables. The first instance to be constructed + // initializes the tables. + // + // Each specialized type of reed_solomon implements a specific encode/decode method + // appropriate to its datum 'TYP'. When accessed via a generic reed_solomon_base pointer, only + // access via "safe" (size specifying) containers or iterators is available. + // + template < typename TYP, int SYM, int RTS, int FCR, int PRM, class PLY > + class reed_solomon + : public reed_solomon_tabs { + + public: + typedef reed_solomon_tabs + tabs_t; + using tabs_t::DATUM; + using tabs_t::SYMBOL; + using tabs_t::MM; + using tabs_t::SIZE; + using tabs_t::NN; + using tabs_t::A0; + + using tabs_t::iprim; + + using tabs_t::alpha_to; + using tabs_t::index_of; + + using tabs_t::modnn; + + static const int NROOTS = RTS; + static const int LOAD = SIZE - NROOTS; // maximum non-parity symbol payload + + protected: + static std::array + genpoly; + + public: + virtual size_t datum() const + { + return DATUM; + } + + virtual size_t symbol() const + { + return SYMBOL; + } + + virtual int size() const + { + return SIZE; + } + + virtual int nroots() const + { + return NROOTS; + } + + virtual int load() const + { + return LOAD; + } + + using reed_solomon_base::encode; + virtual int encode( + const std::pair + &data ) + const + { + return encode_mask( data.first, data.second - data.first - NROOTS, data.second - NROOTS ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return encode_mask( data.first, data.second - data.first, parity.first ); + } + + virtual int encode( + const std::pair + &data ) + const + { + return encode_mask( data.first, data.second - data.first - NROOTS, data.second - NROOTS ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return encode_mask( data.first, data.second - data.first, parity.first ); + } + + virtual int encode( + const std::pair + &data ) + const + { + return encode_mask( data.first, data.second - data.first - NROOTS, data.second - NROOTS ); + } + + virtual int encode( + const std::pair + &data, + const std::pair + &parity ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return encode_mask( data.first, data.second - data.first, parity.first ); + } + + template < typename INP > + int encode_mask( + const INP *data, + int len, + INP *parity ) // pointer to all NROOTS parity symbols + + const + { + if ( len < 1 ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: must provide space for all parity and at least one non-parity symbol", -1 ); + } + + const TYP *dataptr; + TYP *pariptr; + const size_t INPUT = 8 * sizeof ( INP ); + + if ( DATUM != SYMBOL || DATUM != INPUT ) { + // Our DATUM (TYP) size (eg. uint8_t ==> 8, uint16_t ==> 16, uint32_t ==> 32) + // doesn't exactly match our R-S SYMBOL size (eg. 6), or our INP size; Must mask and + // copy. The INP data must fit at least the SYMBOL size! + if ( SYMBOL > INPUT ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: output data type too small to contain symbols", -1 ); + } + std::array tmp; + TYP msk = static_cast( ~0UL << SYMBOL ); + for ( int i = 0; i < len; ++i ) + tmp[LOAD - len + i] = data[i] & ~msk; + dataptr = &tmp[LOAD - len]; + pariptr = &tmp[LOAD]; + + encode( dataptr, len, pariptr ); + + // we copied/masked data; copy the parity symbols back (may be different sizes) + for ( int i = 0; i < NROOTS; ++i ) + parity[i] = pariptr[i]; + } else { + // Our R-S SYMBOL size, DATUM size and INP type size exactly matches; use in-place. + dataptr = reinterpret_cast( data ); + pariptr = reinterpret_cast( parity ); + + encode( dataptr, len, pariptr ); + } + return NROOTS; + } + + using reed_solomon_base::decode; + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + return decode_mask( data.first, data.second - data.first, (uint8_t *)0, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return decode_mask( data.first, data.second - data.first, parity.first, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + return decode_mask( data.first, data.second - data.first, (uint16_t *)0, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return decode_mask( data.first, data.second - data.first, parity.first, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + return decode_mask( data.first, data.second - data.first, (uint32_t *)0, + erasure, position ); + } + + virtual int decode( + const std::pair + &data, + const std::pair + &parity, + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( parity.second - parity.first != NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity length incompatible with number of roots", -1 ); + } + return decode_mask( data.first, data.second - data.first, parity.first, + erasure, position ); + } + + // + // decode_mask -- mask INP data into valid SYMBOL data + // + /// Incoming data may be in a variety of sizes, and may contain information beyond the + /// R-S symbol capacity. For example, we might use a 6-bit R-S symbol to correct the lower + /// 6 bits of an 8-bit data character. This would allow us to correct common substitution + /// errors (such as '2' for '3', 'R' for 'T', 'n' for 'm'). + /// + template < typename INP > + int decode_mask( + INP *data, + int len, + INP *parity = 0, // either 0, or pointer to all parity symbols + const std::vector + &erasure = std::vector(), + std::vector *position= 0 ) + const + { + if ( len < ( parity ? 0 : NROOTS ) + 1 ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: must provide all parity and at least one non-parity symbol", -1 ); + } + if ( ! parity ) { + len -= NROOTS; + parity = data + len; + } + + TYP *dataptr; + TYP *pariptr; + const size_t INPUT = 8 * sizeof ( INP ); + + std::array tmp; + TYP msk = static_cast( ~0UL << SYMBOL ); + const bool cpy = DATUM != SYMBOL || DATUM != INPUT; + if ( cpy ) { + // Our DATUM (TYP) size (eg. uint8_t ==> 8, uint16_t ==> 16, uint32_t ==> 32) + // doesn't exactly match our R-S SYMBOL size (eg. 6), or our INP size; Must copy. + // The INP data must fit at least the SYMBOL size! + if ( SYMBOL > INPUT ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: input data type too small to contain symbols", -1 ); + } + for ( int i = 0; i < len; ++i ) { + tmp[LOAD - len + i] = data[i] & ~msk; + } + dataptr = &tmp[LOAD - len]; + for ( int i = 0; i < NROOTS; ++i ) { + if ( TYP( parity[i] ) & msk ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: parity data contains information beyond R-S symbol size", -1 ); + } + tmp[LOAD + i] = parity[i]; + } + pariptr = &tmp[LOAD]; + } else { + // Our R-S SYMBOL size, DATUM size and INPUT type sizes exactly matches + dataptr = reinterpret_cast( data ); + pariptr = reinterpret_cast( parity ); + } + + int corrects; + if ( ! erasure.size() && ! position ) { + // No erasures, and error position info not wanted. + corrects = decode( dataptr, len, pariptr ); + } else { + // Either erasure location info specified, or resultant error position info wanted; + // Prepare pos (a temporary, if no position vector provided), and copy any provided + // erasure positions. After number of corrections is known, resize the position + // vector. Thus, we use any supplied erasure info, and optionally return any + // correction position info separately. + std::vector _pos; + std::vector &pos = position ? *position : _pos; + pos.resize( std::max( size_t( NROOTS ), erasure.size() )); + std::copy( erasure.begin(), erasure.end(), pos.begin() ); + corrects = decode( dataptr, len, pariptr, + &pos.front(), erasure.size() ); + if ( corrects > int( pos.size() )) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: FATAL: produced too many corrections; possible corruption!", -1 ); + } + pos.resize( std::max( 0, corrects )); + } + + if ( cpy && corrects > 0 ) { + for ( int i = 0; i < len; ++i ) { + data[i] &= msk; + data[i] |= tmp[LOAD - len + i]; + } + for ( int i = 0; i < NROOTS; ++i ) { + parity[i] = tmp[LOAD + i]; + } + } + return corrects; + } + + virtual ~reed_solomon() + { + ; + } + reed_solomon() + : reed_solomon_tabs() + { + // We check one element of the array; this is safe, 'cause the value will not be + // initialized 'til the initializing thread has completely initialized the array. Worst + // case scenario: multiple threads will initialize identically. No mutex necessary. + if ( genpoly[0] ) + return; + +#if defined( DEBUG ) && DEBUG >= 2 + std::cout << "RS(" << SIZE << "," << LOAD << "): Initialize for " << NROOTS << " roots." << std::endl; +#endif + std::array + tmppoly; // uninitialized + // Form RS code generator polynomial from its roots. Only lower-index entries are + // consulted, when computing subsequent entries; only index 0 needs initialization. + tmppoly[0] = 1; + for ( int i = 0, root = FCR * PRM; i < NROOTS; i++, root += PRM ) { + tmppoly[i + 1] = 1; + // Multiply tmppoly[] by @**(root + x) + for ( int j = i; j > 0; j-- ) { + if ( tmppoly[j] != 0 ) + tmppoly[j] = tmppoly[j - 1] + ^ alpha_to[modnn(index_of[tmppoly[j]] + root)]; + else + tmppoly[j] = tmppoly[j - 1]; + } + // tmppoly[0] can never be zero + tmppoly[0] = alpha_to[modnn(index_of[tmppoly[0]] + root)]; + } + // convert NROOTS entries of tmppoly[] to genpoly[] in index form for quicker encoding, + // in reverse order so genpoly[0] is last element initialized. + for ( int i = NROOTS; i >= 0; --i ) + genpoly[i] = index_of[tmppoly[i]]; + } + + int encode( + const TYP *data, + int len, + TYP *parity ) // at least nroots + const + { + // Check length parameter for validity + int pad = NN - NROOTS - len; + if ( pad < 0 || pad >= NN ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: data length incompatible with block size and error correction symbols", -1 ); + } + for ( int i = 0; i < NROOTS; i++ ) + parity[i] = 0; + for ( int i = 0; i < len; i++ ) { + TYP feedback= index_of[data[i] ^ parity[0]]; + if ( feedback != A0 ) + for ( int j = 1; j < NROOTS; j++ ) + parity[j] ^= alpha_to[modnn(feedback + genpoly[NROOTS - j])]; + + std::rotate( parity, parity + 1, parity + NROOTS ); + if ( feedback != A0 ) + parity[NROOTS - 1] = alpha_to[modnn(feedback + genpoly[0])]; + else + parity[NROOTS - 1] = 0; + } +#if defined( DEBUG ) && DEBUG >= 2 + std::cout << *this << " encode " << std::vector( data, data + len ) + << " --> " << std::vector( parity, parity + NROOTS ) << std::endl; +#endif + return NROOTS; + } + + int decode( + TYP *data, + int len, + TYP *parity, // Requires: at least NROOTS + int *eras_pos= 0, // Capacity: at least NROOTS + int no_eras = 0, // Maximum: at most NROOTS + TYP *corr = 0 ) // Capacity: at least NROOTS + const + { + typedef std::array< TYP, NROOTS > + typ_nroots; + typedef std::array< TYP, NROOTS+1 > + typ_nroots_1; + typedef std::array< int, NROOTS > + int_nroots; + + typ_nroots_1 lambda { { 0 } }; + typ_nroots syn; + typ_nroots_1 b; + typ_nroots_1 t; + typ_nroots_1 omega; + int_nroots root; + typ_nroots_1 reg; + int_nroots loc; + int count = 0; + + // Check length parameter and erasures for validity + int pad = NN - NROOTS - len; + if ( pad < 0 || pad >= NN ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: data length incompatible with block size and error correction symbols", -1 ); + } + if ( no_eras ) { + if ( no_eras > NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: number of erasures exceeds capacity (number of roots)", -1 ); + } + for ( int i = 0; i < no_eras; ++i ) { + if ( eras_pos[i] < 0 || eras_pos[i] >= len + NROOTS ) { + EZPWD_RAISE_OR_RETURN( std::runtime_error, "reed-solomon: erasure positions outside data+parity", -1 ); + } + } + } + + // form the syndromes; i.e., evaluate data(x) at roots of g(x) + for ( int i = 0; i < NROOTS; i++ ) + syn[i] = data[0]; + + for ( int j = 1; j < len; j++ ) { + for ( int i = 0; i < NROOTS; i++ ) { + if ( syn[i] == 0 ) { + syn[i] = data[j]; + } else { + syn[i] = data[j] + ^ alpha_to[modnn(index_of[syn[i]] + ( FCR + i ) * PRM)]; + } + } + } + + for ( int j = 0; j < NROOTS; j++ ) { + for ( int i = 0; i < NROOTS; i++ ) { + if ( syn[i] == 0 ) { + syn[i] = parity[j]; + } else { + syn[i] = parity[j] + ^ alpha_to[modnn(index_of[syn[i]] + ( FCR + i ) * PRM)]; + } + } + } + + // Convert syndromes to index form, checking for nonzero condition + TYP syn_error = 0; + for ( int i = 0; i < NROOTS; i++ ) { + syn_error |= syn[i]; + syn[i] = index_of[syn[i]]; + } + + int deg_lambda = 0; + int deg_omega = 0; + int r = no_eras; + int el = no_eras; + if ( ! syn_error ) { + // if syndrome is zero, data[] is a codeword and there are no errors to correct. + count = 0; + goto finish; + } + + lambda[0] = 1; + if ( no_eras > 0 ) { + // Init lambda to be the erasure locator polynomial. Convert erasure positions + // from index into data, to index into Reed-Solomon block. + lambda[1] = alpha_to[modnn(PRM * (NN - 1 - ( eras_pos[0] + pad )))]; + for ( int i = 1; i < no_eras; i++ ) { + TYP u = modnn(PRM * (NN - 1 - ( eras_pos[i] + pad ))); + for ( int j = i + 1; j > 0; j-- ) { + TYP tmp = index_of[lambda[j - 1]]; + if ( tmp != A0 ) { + lambda[j] ^= alpha_to[modnn(u + tmp)]; + } + } + } + } + +#if DEBUG >= 1 + // Test code that verifies the erasure locator polynomial just constructed + // Needed only for decoder debugging. + + // find roots of the erasure location polynomial + for( int i = 1; i<= no_eras; i++ ) + reg[i] = index_of[lambda[i]]; + + count = 0; + for ( int i = 1, k = iprim - 1; i <= NN; i++, k = modnn( k + iprim )) { + TYP q = 1; + for ( int j = 1; j <= no_eras; j++ ) { + if ( reg[j] != A0 ) { + reg[j] = modnn( reg[j] + j ); + q ^= alpha_to[reg[j]]; + } + } + if ( q != 0 ) + continue; + // store root and error location number indices + root[count] = i; + loc[count] = k; + count++; + } + if ( count != no_eras ) { + std::cout << "ERROR: count = " << count << ", no_eras = " << no_eras + << "lambda(x) is WRONG" + << std::endl; + count = -1; + goto finish; + } +#if DEBUG >= 2 + if ( count ) { + std::cout + << "Erasure positions as determined by roots of Eras Loc Poly: "; + for ( int i = 0; i < count; i++ ) + std::cout << loc[i] << ' '; + std::cout << std::endl; + std::cout + << "Erasure positions as determined by roots of eras_pos array: "; + for ( int i = 0; i < no_eras; i++ ) + std::cout << eras_pos[i] << ' '; + std::cout << std::endl; + } +#endif +#endif + + for ( int i = 0; i < NROOTS + 1; i++ ) + b[i] = index_of[lambda[i]]; + + // + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial + // + while ( ++r <= NROOTS ) { // r is the step number + // Compute discrepancy at the r-th step in poly-form + TYP discr_r = 0; + for ( int i = 0; i < r; i++ ) { + if (( lambda[i] != 0 ) && ( syn[r - i - 1] != A0 )) { + discr_r ^= alpha_to[modnn(index_of[lambda[i]] + syn[r - i - 1])]; + } + } + discr_r = index_of[discr_r]; // Index form + if ( discr_r == A0 ) { + // 2 lines below: B(x) <-- x*B(x) + // Rotate the last element of b[NROOTS+1] to b[0] + std::rotate( b.begin(), b.begin()+NROOTS, b.end() ); + b[0] = A0; + } else { + // 7 lines below: T(x) <-- lambda(x)-discr_r*x*b(x) + t[0] = lambda[0]; + for ( int i = 0; i < NROOTS; i++ ) { + if ( b[i] != A0 ) { + t[i + 1] = lambda[i + 1] + ^ alpha_to[modnn(discr_r + b[i])]; + } else + t[i + 1] = lambda[i + 1]; + } + if ( 2 * el <= r + no_eras - 1 ) { + el = r + no_eras - el; + // 2 lines below: B(x) <-- inv(discr_r) * lambda(x) + for ( int i = 0; i <= NROOTS; i++ ) { + b[i] = ((lambda[i] == 0) + ? A0 + : modnn(index_of[lambda[i]] - discr_r + NN)); + } + } else { + // 2 lines below: B(x) <-- x*B(x) + std::rotate( b.begin(), b.begin()+NROOTS, b.end() ); + b[0] = A0; + } + lambda = t; + } + } + + // Convert lambda to index form and compute deg(lambda(x)) + for ( int i = 0; i < NROOTS + 1; i++ ) { + lambda[i] = index_of[lambda[i]]; + if ( lambda[i] != NN ) + deg_lambda = i; + } + // Find roots of error+erasure locator polynomial by Chien search + reg = lambda; + count = 0; // Number of roots of lambda(x) + for ( int i = 1, k = iprim - 1; i <= NN; i++, k = modnn( k + iprim )) { + TYP q = 1; // lambda[0] is always 0 + for ( int j = deg_lambda; j > 0; j-- ) { + if ( reg[j] != A0 ) { + reg[j] = modnn( reg[j] + j ); + q ^= alpha_to[reg[j]]; + } + } + if ( q != 0 ) + continue; // Not a root + // store root (index-form) and error location number +#if DEBUG >= 2 + std::cout << "count " << count << " root " << i << " loc " << k << std::endl; +#endif + root[count] = i; + loc[count] = k; + // If we've already found max possible roots, abort the search to save time + if ( ++count == deg_lambda ) + break; + } + if ( deg_lambda != count ) { + // deg(lambda) unequal to number of roots => uncorrectable error detected + count = -1; + goto finish; + } + // + // Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo x**NROOTS). in + // index form. Also find deg(omega). + // + deg_omega = deg_lambda - 1; + for ( int i = 0; i <= deg_omega; i++ ) { + TYP tmp = 0; + for ( int j = i; j >= 0; j-- ) { + if (( syn[i - j] != A0 ) && ( lambda[j] != A0 )) + tmp ^= alpha_to[modnn(syn[i - j] + lambda[j])]; + } + omega[i] = index_of[tmp]; + } + + // + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(fcr-1) + // and den = lambda_pr(inv(X(l))) all in poly-form + // + for ( int j = count - 1; j >= 0; j-- ) { + TYP num1 = 0; + for ( int i = deg_omega; i >= 0; i-- ) { + if ( omega[i] != A0 ) + num1 ^= alpha_to[modnn(omega[i] + i * root[j])]; + } + TYP num2 = alpha_to[modnn(root[j] * ( FCR - 1 ) + NN)]; + TYP den = 0; + + // lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] + for ( int i = std::min(deg_lambda, NROOTS - 1) & ~1; i >= 0; i -= 2 ) { + if ( lambda[i + 1] != A0 ) { + den ^= alpha_to[modnn(lambda[i + 1] + i * root[j])]; + } + } +#if defined( DEBUG ) && DEBUG >= 1 + if ( den == 0 ) { + std::cout << "ERROR: denominator = 0" << std::endl; + count = -1; + goto finish; + } +#endif + // Apply error to data. Padding ('pad' unused symbols) begin at index 0. + if ( num1 != 0 ) { + if ( loc[j] < pad ) { + // If the computed error position is in the 'pad' (the unused portion of the + // R-S data capacity), then our solution has failed -- we've computed a + // correction location outside of the data and parity we've been provided! +#if DEBUG >= 2 + std::cout + << "ERROR: RS(" << SIZE <<"," << LOAD + << ") computed error location: " << loc[j] + << " within " << pad << " pad symbols, not within " + << LOAD - pad << " data or " << NROOTS << " parity" + << std::endl; +#endif + count = -1; + goto finish; + } + + TYP cor = alpha_to[modnn(index_of[num1] + + index_of[num2] + + NN - index_of[den])]; + // Store the error correction pattern, if a correction buffer is available + if ( corr ) + corr[j] = cor; + // If a data/parity buffer is given and the error is inside the message or + // parity data, correct it + if ( loc[j] < ( NN - NROOTS )) { + if ( data ) { + data[loc[j] - pad] ^= cor; + } + } else if ( loc[j] < NN ) { + if ( parity ) + parity[loc[j] - ( NN - NROOTS )] ^= cor; + } + } + } + + finish: +#if defined( DEBUG ) && DEBUG > 0 + if ( count > NROOTS ) + std::cout << "ERROR: Number of corrections: " << count << " exceeds NROOTS: " << NROOTS << std::endl; +#endif +#if defined( DEBUG ) && DEBUG > 1 + std::cout << "data x" << std::setw( 3 ) << len << ": " << std::vector( data, data + len ) << std::endl; + std::cout << "parity x" << std::setw( 3 ) << NROOTS << ": " << std::string( len * 2, ' ' ) << std::vector( parity, parity + NROOTS ) << std::endl; + if ( count > 0 ) { + std::string errors( 2 * ( len + NROOTS ), ' ' ); + for ( int i = 0; i < count; ++i ) { + errors[2*(loc[i]-pad)+0] = 'E'; + errors[2*(loc[i]-pad)+1] = 'E'; + } + for ( int i = 0; i < no_eras; ++i ) { + errors[2*(eras_pos[i])+0] = 'e'; + errors[2*(eras_pos[i])+1] = 'e'; + } + std::cout << "e)ra,E)rr x" << std::setw( 3 ) << count << ": " << errors << std::endl; + } +#endif + if ( eras_pos != NULL ) { + for ( int i = 0; i < count; i++) + eras_pos[i] = loc[i] - pad; + } + return count; + } + }; // class reed_solomon + + // + // Define the static reed_solomon...<...> members; allowed in header for template types. + // + // The reed_solomon_tags<...>::iprim < 0 is used to indicate to the first instance that the + // static tables require initialization. + // + template < typename TYP, int SYM, int PRM, class PLY > + int reed_solomon_tabs< TYP, SYM, PRM, PLY >::iprim = -1; + + template < typename TYP, int SYM, int PRM, class PLY > + std::array< TYP, reed_solomon_tabs< TYP, SYM, PRM, PLY > +#if not defined( EZPWD_ARRAY_TEST ) + ::NN + 1 > +#else +# warning "EZPWD_ARRAY_TEST: Erroneously defining alpha_to size!" + ::NN > +#endif + reed_solomon_tabs< TYP, SYM, PRM, PLY >::alpha_to; + + template < typename TYP, int SYM, int PRM, class PLY > + std::array< TYP, reed_solomon_tabs< TYP, SYM, PRM, PLY >::NN + 1 > + reed_solomon_tabs< TYP, SYM, PRM, PLY >::index_of; + template < typename TYP, int SYM, int PRM, class PLY > + std::array< TYP, reed_solomon_tabs< TYP, SYM, PRM, PLY >::MODS > + reed_solomon_tabs< TYP, SYM, PRM, PLY >::mod_of; + + template < typename TYP, int SYM, int RTS, int FCR, int PRM, class PLY > + std::array< TYP, reed_solomon< TYP, SYM, RTS, FCR, PRM, PLY >::NROOTS + 1 > + reed_solomon< TYP, SYM, RTS, FCR, PRM, PLY >::genpoly; + +} // namespace ezpwd + +#endif // _EZPWD_RS_BASE diff --git a/src/ezpwd/serialize b/src/ezpwd/serialize new file mode 100644 index 0000000..ae4a62c --- /dev/null +++ b/src/ezpwd/serialize @@ -0,0 +1,1188 @@ +/* + * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library + * + * Copyright (c) 2014, Hard Consulting Corporation. + * + * Ezpwd Reed-Solomon 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 of + * the License, or (at your option) any later version. See the LICENSE file at the top of the + * source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base + * is redistributed under the terms of the LGPL, regardless of the overall licensing terms. + * + * Ezpwd Reed-Solomon 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. + */ +#ifndef _EZPWD_SERIALIZE +#define _EZPWD_SERIALIZE + +#include +#include + +#include "output" + +// +// EZPWD (no padding) and RFC4648 (enforce padding) base-N codecs +// +// scatter -- 8-bit binary data into 5-bit (base32) and 6-bit (base64) chunks (optionally w/pad) +// gather -- collect up 5/6-bit binary data back into 8-bit characters (optionally w/pad) +// ..._standard -- enforce RFC4648-standard padding +// +// Scatters or gathers 8-bit binary character data to 5/6-bit symbols, suitable for base32/64 +// encoding. +// +// encode -- convert binary data to ASCII base32/64, in-place +// decode -- convert ASCII base32/64 to binary data, in-place +// ..._standard -- enforce RFC4648-standard padding +// +// Transforms iterable containers of char between ASCII symbols and binary data, always in-place. +// The decode may alter the size of the result (by ignoring whitespace). +// +// In general the ezpwd::base32/64 en/decoders are designed to produce easily human-usable +// encodings, and can ignore common whitespace characters and '-' to allow human-readable +// formatting. The RFC4648 Standard base 32/64 and Crockford base32 encodings are also supported. +// +// Adding new symbol encodings (and even new bases, up to base-128) is trivial. +// +namespace ezpwd { + + namespace serialize { + + enum chr_t { + pd = -1, // padding + nv = -2, // invalid + ws = -3, // whitespace + }; + + enum ws_use_t { + ws_invalid = 0, // Whitespace is invalid + ws_ignored = 1, // Whitespace ignored (the default) + }; + + enum pd_use_t { + pd_invalid = 0, // Padding is not expected (invalid) + pd_ignored = 1, // Padding is ignored, and automatically supplied if required (the default) + pd_enforce = 2, // Padding is expected and enforced + }; + + // + // serialize::tables -- base class for TABLES in all base + // + // Every serialize::table specialization must have an encoder table of size BASE, + // and a decoder table of size 127. + // + + // + // hex<16/32> -- RFC4648 4-bit (standard 16 symbol) and 5-bit (32 symbol) + // standard<16/32/64> -- RFC4648 standard tables + // standard_url<64> -- RFC4648 standard tables (base-64 URL-safe) + // ezpwd<16/32/64> -- EZPWD tables + // crockford<32> -- Crockford (base32 only) + // + // These types are passed as the TABLE template parameter to base<32/64,TABLE> class + // template instantiations. They must specify an encoder table of size BASE, and a decoder + // table of size 127. + // + template < size_t N > struct hex { }; + template < size_t N > struct uuencode { }; + template < size_t N > struct standard { }; + template < size_t N > struct standard_url { }; + template < size_t N > struct ezpwd { }; + template < size_t N > struct crockford { }; + + // + // base<16> tables -- basic hexadecimal supported (hex, standard, ezpwd identical) + // + template <> struct hex<16> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // !"#$%&`()*+,-./ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, nv, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, nv, nv, nv, nv, nv, nv, nv, nv, nv, // @ABCDEFGHIJKLMNO + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, nv, nv, nv, nv, nv, nv, nv, nv, nv, // `abcdefghijklmno + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::hex<16> + + template <> struct standard<16> { + static const constexpr std::array + encoder = hex<16>::encoder; + static const constexpr std::array + decoder = hex<16>::decoder; + }; + + template <> struct ezpwd<16> { + static const constexpr std::array + encoder = hex<16>::encoder; + static const constexpr std::array + decoder = hex<16>::decoder; + }; + + // + // base<32> tables -- ezpwd, and RFC4648 hex32, crockford and standard + // + template <> struct hex<32> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // !"#$%&`()*+,-./ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, nv, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // @ABCDEFGHIJKLMNO + 25, 26, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // `abcdefghijklmno + 25, 26, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::hex<32> + + template <> struct standard<32> { // RFC4648 Standard + static const constexpr std::array + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '2', '3', '4', '5', '6', '7', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // !"#$%&`()*+,-./ + nv, nv, 26, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO '=' is pad + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // `abcdefghijklmno + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standard<32> + + template <> struct ezpwd<32> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', + 'Q', 'R', 'T', 'U', 'V', 'W', 'X', 'Y', + } }; + + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, nv, nv, // !"#$%&`()*+,-./ '-' is whitespace + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 20, 21, 22, 0, // @ABCDEFGHIJKLMNO + 23, 24, 25, 5, 26, 27, 28, 29, 30, 31, 2, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 20, 21, 22, 0, // `abcdefghijklmno + 23, 24, 25, 5, 26, 27, 28, 29, 30, 31, 2, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::ezpwd<32> + + template <> struct crockford<32> { + static const constexpr std::array + encoder { { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', + 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', + } }; + + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, nv, nv, // !"#$%&`()*+,-./ '-' is whitespace + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, // @ABCDEFGHIJKLMNO + 22, 23, 24, 25, 26, nv, 27, 28, 29, 30, 31, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, // `abcdefghijklmno + 22, 23, 24, 25, 26, nv, 27, 28, 29, 30, 31, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::crockford<32> + + // + // base<64> tables + // + template <> struct uuencode<64> { + static const constexpr std::array + encoder { { + ' ', '!', '"', '#', '$', '%', '&','\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[','\\', ']', '^', '_', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // !"#$%&`()*+,-./ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, // 0123456789:;<=>? '=' is pad + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // @ABCDEFGHIJKLMNO + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // PQRSTUVWXYZ[\]^_ + nv, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // `abcdefghijklmno + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, nv, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standard<64> + + template <> struct standard<64> { + static const constexpr std::array // RFC4648 Standard + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, 62, nv, nv, nv, 63, // !"#$%&`()*+,-./ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, nv, // PQRSTUVWXYZ[\]^_ + nv, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // `abcdefghijklmno + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standard<64> + + template <> struct standard_url<64> { + static const constexpr std::array + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '-', '_', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, 62, nv, nv, // !"#$%&`()*+,-./ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, 63, // PQRSTUVWXYZ[\]^_ + nv, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // `abcdefghijklmno + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::standar_url<64> + + template <> struct ezpwd<64> { + static const constexpr std::array // '+' and '.' are URL safe, and we treat '-' as whitespace + encoder { { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '.', + } }; + static const constexpr std::array + decoder { { + nv, nv, nv, nv, nv, nv, nv, nv, nv, ws, ws, ws, ws, ws, nv, nv, // 9-13: ,,,, + nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, // + ws, nv, nv, nv, nv, nv, nv, nv, nv, nv, nv, 62, nv, ws, 63, nv, // !"#$%&`()*+,-./ '-' is whitespace + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, nv, nv, nv, pd, nv, nv, // 0123456789:;<=>? '=' is pad + nv, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // @ABCDEFGHIJKLMNO + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, nv, nv, nv, nv, 63, // PQRSTUVWXYZ[\]^_ + nv, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // `abcdefghijklmno + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nv, nv, nv, nv, // pqrstuvwxyz{|}~ + } }; + }; // struct serialize::ezpwd<64> + + // + // base_generic -- generic base-N conversion using TABLES. Doesn't have scatter/gather. + // + // Every base-N converter requires a TABLE class of matching N, and can encode/decode. + // in-place using the table::encoder/decoder arrays. Only specific values of N have class + // template specialization which includes scatter/gather methods -- the generic base-N + // template does not.. + // + template < size_t N, typename TABLES = ezpwd< N >> + class base_generic + : public TABLES { + public: + static + std::ostream &output( + std::ostream &lhs ) + { + lhs << "base<" << N; +#if defined( DEBUG ) && DEBUG >= 3 + lhs << "," << TABLES::encoder; +#endif + lhs << ">"; + return lhs; + } + + // + // base::encode() -- encode the supplied sequence of data in the domain (0,N] to base-N + // base::encode(,) -- encode the supplied std::string of (0,N] symbols in-place to base-N + // + // Items from iterator must be convertible to a table index (ie. not -'ve, and within + // the table size N). If the table contains 'ws' entries, they may optionally be ignored. + // If the input symbol indexes outside the provided encoder table (or an 'nv' entry), then an + // exception will be thrown. + // + // If a non-zero 'pad' character is supplied, then the -1 (pd) character value will be + // allowed (normally occurring only at the end of the input range), and a 'pad' will be + // emitted for each one. + // + template < typename I > + static + I encode( + I begin, + I end, + char pad = '=' ) // '=' for standards-compliance + { + for ( I i = begin; i != end; ++i ) { + if ( *i >= 0 and size_t( *i ) < TABLES::encoder.size() ) + *i = TABLES::encoder[*i]; + else if ( pd == *i and pad ) + *i = pad; + else + throw std::runtime_error( + std::string( "ezpwd::serialize::encode: invalid base-" ) << N + << " binary symbol presented: " << *i ); + } + return end; + } + + static + std::string &encode( + std::string &symbols, + char pad = '=' ) // '=' for standards-compliance + { + encode( symbols.begin(), symbols.end(), pad ); + return symbols; + } + + // + // base::decode(,) -- decode base-N symbols in-place, collapsing spaces. + // base::decode() -- decode base-N symbols in supplied std::string, collapsing spaces, in-place. + // + // Items from iterator must be convertible to a table index (ie. not -'ve, and within + // the table size, which is always 127). + // + // ws_ignored -- skip whitepace/- symbols (the default) + // ws_invalid -- consider whitespace/- symbols as invalid symbols + // + // pd_invalid -- consider padding symbols as invalid + // pd_ignored -- skip padding symbols + // pd_enforce -- leave any padding symbols in place (the default) + // + // If erasure vector supplied, marks invalid symbols as erasures; otherwise, throws + // exception. Ignores whitespace. Will return an iterator to just after the last output + // symbol used in the provided range (eg. to shorten the container, if desired), leaving any + // remaining symbols unchanged. The version returns the same string reference + // passed in (shortened, if spaces/padding ignored). + // + // If an invalid vector is supplied, we'll also return the offending input symbol(s); if + // an exception is raised (no erasure vector supplied), only one symbol will be in invalid. + // + // NOTE: will quite likely return an iterator before the supplied 'end', indicating that the + // output has been truncated (shortened), due to collapsing spaces! + // + template < typename I > + static + I decode( + I begin, + I end, + std::vector *erasure, // Deem invalid symbols as erasures + std::vector *invalid = 0, // and return the symbols + ws_use_t ws_use = ws_ignored, // skip any whitespace + pd_use_t pd_use = pd_invalid ) // no padding expected; invalid + { + if ( erasure ) + erasure->clear(); + if ( invalid ) + invalid->clear(); + I i, o; + for ( i = o = begin; i != end; ++i ) { + size_t ti( *i ); + char c = ti < TABLES::decoder.size() ? TABLES::decoder[ti] : char( nv ); + if ( ws == c ) + switch ( ws_use ) { + case ws_invalid: + c = nv; + break; + case ws_ignored: default: + continue; + } + if ( pd == c ) + switch ( pd_use ) { + case pd_invalid: + c = nv; + break; + case pd_enforce: + break; + case pd_ignored: default: + continue; + } + if ( nv == c ) { + // Invalid symbol; optionally remember them. Mark as erasure? Or throw. + if ( invalid ) + invalid->push_back( *i ); + if ( erasure ) { + erasure->push_back( o - begin ); // index of offending symbol in output + c = 0; // will be output w/ 0 value + } else { + throw std::runtime_error( + std::string( "ezpwd::serialize::decode: invalid base-" ) << N + << " ASCII symbol presented: " << int( *i ) << " '" << *i << "'" ); + } + } + *o++ = c; + } + return o; + } + + template < typename I > + static + I decode( + I begin, + I end, + ws_use_t ws_use = ws_ignored, + pd_use_t pd_use = pd_invalid ) + { + return decode( begin, end, 0, 0, ws_use, pd_use ); + } + + template < typename I > + static + I decode_standard( + I begin, + I end ) + { + return decode( begin, end, 0, 0, ws_ignored, pd_enforce ); // RFC4648 padding + } + + static + std::string &decode( + std::string &symbols, + std::vector *erasure = 0, + std::vector *invalid = 0, + ws_use_t ws_use = ws_ignored, + pd_use_t pd_use = pd_invalid ) + { + auto last = decode( symbols.begin(), symbols.end(), erasure, invalid, + ws_use, pd_use ); + if ( last != symbols.end() ) + symbols.resize( last - symbols.begin() ); // eliminated some whitespace + return symbols; + } + static + std::string &decode_standard( + std::string &symbols, + std::vector *erasure = 0, + std::vector *invalid = 0 ) + { + return decode( symbols, erasure, invalid, ws_ignored, pd_enforce ); // RFC4648 padding + } + + // + // gather_next -- return next symbol to gather into 8-bit output + // + // Fails if padding mixed in with data, or a data symbol exceeds encoding bit depth + // (eg. 5 bits for base-32). + // + // If auto-padding, allow the caller to differentiate + // + template < typename I > + static + int gather_next( I &beg, I end, pd_use_t pd_use, int previous ) + { + if ( beg == end ) { + if ( pd_enforce == pd_use ) + throw std::logic_error( std::string( "base-" ) << N << " gather error; insufficient data"); + // automatically pad; return nv on underflow while un-emitted data remains, finally pd + return previous >= 0 ? nv : pd; + } + int c = *beg++; + if ( previous < 0 and c >= 0 ) + throw std::logic_error( + std::string( "base-" ) << N << " gather error; data following padding" ); + if ( c >= char( N ) or c < pd ) + throw std::logic_error( + std::string( "base-" ) << N << " gather error; symbol value " << int( c ) << " beyond capacity" ); + return c; + } + }; // class serialize::base_generic + } // namespace serialize + + template < size_t N, typename TABLES > + std::ostream &operator<<( + std::ostream &lhs, + const ezpwd::serialize::base_generic + &rhs ) + { + return rhs.output( lhs ); + } + + namespace serialize { + // + // ezpwd::serialize::base -- Arbitrary bases (other than those with specializations below) + // + // Can en/decode, but no scatter/gather implementation, nor encode_size. + // + template < size_t N, typename TABLES > + class base + : public base_generic< N, TABLES > { + public: + using base_generic< N, TABLES >::encode; + using base_generic< N, TABLES >::decode; + }; + + + // + // ezpwd::serialize::base<16> -- transform individual characters between 4-bit binary and a base-16 + // + template < typename TABLES > + class base< 16, TABLES > + : public base_generic< 16, TABLES > { + public: + using base_generic< 16, TABLES >::encode; + using base_generic< 16, TABLES >::decode; + using base_generic< 16, TABLES >::gather_next; + + static constexpr size_t encode_size( + size_t len, + pd_use_t /* pd_use */ = pd_invalid ) + { + return len * 2; // encoding base-16 always exactly doubles size + } + + // + // base<16>::scatter -- distribute a range of input bytes to an output iterator in 4-bit chunks + // + // The same implementation is used for both random-access iterators, and for all + // other forward iterators. + // + template < typename I, typename O, typename iterator_tag > + static + O scatter( + I beg, + I end, + O out, + pd_use_t, // ignored; never needs to pad + iterator_tag ) // ignored + { + while ( beg != end ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 4); + *out++ = char((c0 & 0x0f)); + } + return out; + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + return scatter( beg, end, out, pd_use, typename std::iterator_traits::iterator_category() ); + } + + template < typename I, typename O > + static + O scatter_standard( + I beg, + I end, + O out ) + { + return scatter( beg, end, out, pd_enforce, typename std::iterator_traits::iterator_category() ); + } + + // + // base<16>::gather -- collect 4-bit chunks into 8-bit characters + // + // If underflow occurs (not enough data collected to output complete last char), then an + // exception will be raised. However, if 'pad' is set, then output will automatically be + // padded, flushing out any un-emitted data remaining in previous 4-bit base-16 symbol. + // + // For correct base-16 data produced by 'scatter', this will allow 'gather' to always + // produce the identical output as was originally provided to 'scatter'. However, if simply + // truncated base-16 input is provided (eg. only 1 symbols of an 2-symbol 8-bit + // base-16 group), then the final 8-bit symbol from the original data will be missing. + // + template < typename I, typename O > + static + O gather( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + while ( beg != end ) { + int c0 = gather_next( beg, end, pd_use, 0 ); + int c1 = gather_next( beg, end, pd_use, c0 ); + if ( c0 >= 0 and c1 >= 0 ) + *out++ = ( ((c0 < 0 ? 0 : c0 ) << 4) + | (c1 < 0 ? 0 : c1 )); + } + return out; + } + template < typename I, typename O > + static + O gather_standard( + I beg, + I end, + O out ) + { + return gather( beg, end, out, pd_enforce ); + } + }; // ezpwd::base<16> + + // + // ezpwd::serialize::base16... + // + // Shortcut typedefs for the available base16 codec. + // + typedef base< 16, hex< 16 >> + base16; + typedef base< 16, hex< 16 >> + base16_standard; + + + // + // ezpwd::serialize::base<32> -- transform individual characters between 5-bit binary and a base-32 + // + // The char values [0,32) are mapped by base<32>::encode onto something like + // + // 0123456789ABCDEFGHJKLMNPQRTUVWXY TABLES=ezpwd<32> (default) + // 0123456789ABCDEFGHJKMNPQRSTVWXYZ TABLES=crockford<32> + // ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 TABLES=standard<32> + // + // and base<32>::decode performs the inverse. In addition to folding lower-case to + // upper-case, the following mappings occur in the ezpwd<32>::decoder table: + // + // O -> 0 + // Z -> 2 + // S -> 5 + // I -> 1 + // + // B -> 8 could not be included due to the limited size of the capitalized alpha-numeric + // symbol pool, unfortunately, but the other substitutions were prioritized as they are + // more likely to occur in standard printed text. + // + // Any characters encountered outside [0,32) by encode and outside the above set by + // decode raise an exception, unless an erasure vector is provided, in which case we supply + // a 0 value and store the index of the invalid symbol in the vector. + // + template < typename TABLES > + class base< 32, TABLES > + : public base_generic< 32, TABLES > { + public: + using base_generic< 32, TABLES >::encode; + using base_generic< 32, TABLES >::decode; + using base_generic< 32, TABLES >::gather_next; + + static constexpr size_t encode_size( + size_t len, + pd_use_t pd_use = pd_invalid ) + { + return ( pd_enforce == pd_use + ? ( len + 4 ) / 5 * 8 + : ( len * 8 + 4 ) / 5 ); + } + + // + // base<32>::scatter -- distribute a range of input bytes to an output iterator in 5-bit chunks + // + // Separate implementation are provided for random-access iterators (with fewer + // comparisons necessary) and for all other forward iterators. + // + template < typename I, typename O, typename iterator_tag > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + iterator_tag ) + { + while ( beg != end ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + + if ( beg == end ) { + *out++ = char((c0 & 0x07) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + + if ( beg == end ) { + *out++ = char((c1 & 0x01) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + + if ( beg == end ) { + *out++ = char((c2 & 0x0f) << 1); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c3 = *beg++; + *out++ = char((c2 & 0x0f) << 1 | (c3 & 0xff) >> 7); + *out++ = char((c3 & 0x7f) >> 2); + + if ( beg == end ) { + *out++ = char((c3 & 0x03) << 3); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + + int c4 = *beg++; + *out++ = char((c3 & 0x03) << 3 | (c4 & 0xff) >> 5); + *out++ = char((c4 & 0x1f)); + } + + return out; + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + std::random_access_iterator_tag ) + { + while ( end - beg >= 5 ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + int c3 = *beg++; + *out++ = char((c2 & 0x0f) << 1 | (c3 & 0xff) >> 7); + *out++ = char((c3 & 0x7f) >> 2); + int c4 = *beg++; + *out++ = char((c3 & 0x03) << 3 | (c4 & 0xff) >> 5); + *out++ = char((c4 & 0x1f)); + } + + switch ( end - beg ) { + case 4: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + int c3 = *beg++; + *out++ = char((c2 & 0x0f) << 1 | (c3 & 0xff) >> 7); + *out++ = char((c3 & 0x7f) >> 2); + *out++ = char((c3 & 0x03) << 3); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + case 3: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + int c2 = *beg++; + *out++ = char((c1 & 0x01) << 4 | (c2 & 0xff) >> 4); + *out++ = char((c2 & 0x0f) << 1); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + case 2: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + int c1 = *beg++; + *out++ = char((c0 & 0x07) << 2 | (c1 & 0xff) >> 6); + *out++ = char((c1 & 0x3f) >> 1); + *out++ = char((c1 & 0x01) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + case 1: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 3); + *out++ = char((c0 & 0x07) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + *out++ = EOF; + } + return out; + } + default: + return out; + } + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + return scatter( beg, end, out, pd_use, typename std::iterator_traits::iterator_category() ); + } + + template < typename I, typename O > + static + O scatter_standard( + I beg, + I end, + O out ) + { + return scatter( beg, end, out, pd_enforce, typename std::iterator_traits::iterator_category() ); + } + + // + // base<32>::gather -- collect 5-bit chunks into 8-bit characters + // + // If underflow occurs (not enough data collected to output complete last char), then an + // exception will be raised. However, if 'pad' is set, then output will automatically be + // padded, discarding any un-emitted data remaining in previous 5-bit base-32 symbols. + // + // For correct base-32 data produced by 'scatter', this will allow 'gather' to always + // produce the identical output as was originally provided to 'scatter'. However, if simply + // truncated base-32 input is provided (eg. only 1, 3 or 5 symbols of an 8-symbol 40-bit + // base-32 group), then the final 8-bit symbol from the original data will be missing. + // + template < typename I, typename O > + static + O gather( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + while ( beg != end ) { + int c0 = gather_next( beg, end, pd_use, 0 ); + int c1 = gather_next( beg, end, pd_use, c0 ); + if ( c0 >= 0 and c1 >= 0 ) + *out++ = ( ((c0 < 0 ? 0 : c0 ) << 3) + | (c1 < 0 ? 0 : c1 ) >> 2 ); + int c2 = gather_next( beg, end, pd_use, c1 ); + int c3 = gather_next( beg, end, pd_use, c2 ); + if ( c1 >= 0 and c2 >= 0 and c3 >= 0 ) + *out++ = ( ((c1 < 0 ? 0 : c1) & 0x03) << 6 + | (c2 < 0 ? 0 : c2) << 1 + | (c3 < 0 ? 0 : c3) >> 4 ); + int c4 = gather_next( beg, end, pd_use, c3 ); + if ( c3 >= 0 and c4 >= 0 ) + *out++ = ( ((c3 < 0 ? 0 : c3) & 0x0f) << 4 + | (c4 < 0 ? 0 : c4) >> 1 ); + int c5 = gather_next( beg, end, pd_use, c4 ); + int c6 = gather_next( beg, end, pd_use, c5 ); + if ( c4 >=0 and c5 >= 0 and c6 >= 0 ) + *out++ = ( ((c4 < 0 ? 0 : c4) & 0x01) << 7 + | (c5 < 0 ? 0 : c5) << 2 + | (c6 < 0 ? 0 : c6) >> 3 ); + int c7 = gather_next( beg, end, pd_use, c6 ); + if ( c6 >= 0 and c7 >= 0 ) + *out++ = ( ((c6 < 0 ? 0 : c6 ) & 0x07) << 5 + | (c7 < 0 ? 0 : c7 )); + } + return out; + } + template < typename I, typename O > + static + O gather_standard( + I beg, + I end, + O out ) + { + return gather( beg, end, out, pd_enforce ); + } + }; // ezpwd::base<32> + + // + // ezpwd::serialize::base32... + // + // Shortcut typedefs for the available base32 codecs. + // + typedef base< 32, ezpwd< 32 >> + base32; + typedef base< 32, hex< 32 >> + base32_hex; + typedef base< 32, serialize::standard< 32 >> + base32_standard; + typedef base< 32, crockford< 32 >> + base32_crockford; + + + // + // ezpwd::serialize::base<64> -- transform individual characters between 6-bit binary and base-64 + // + // The char values [0,64) are mapped by base64::encode onto either the + // standard or ezpwd (url-safe) tables: + // + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ -- RFC4648 standard + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_ -- RFC4648 standard_url + // ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+. -- ezpwd + // + // and base<64>::decode performs the inverse (handles both standard and url-safe encodings). + // + // Any characters encountered outside [0,64) by encode and outside the above set + // by decode raise an exception. + // + template < typename TABLES > + class base< 64, TABLES > + : public base_generic< 64, TABLES > { + public: + using base_generic< 64, TABLES >::encode; + using base_generic< 64, TABLES >::decode; + using base_generic< 64, TABLES >::gather_next; + + static constexpr size_t encode_size( + size_t len, + pd_use_t pd_use = pd_invalid ) + { + return ( pd_enforce == pd_use + ? ( len + 2 ) / 3 * 4 + : ( len * 4 + 2 ) / 3 ); + } + + // + // base<64>::scatter -- distribute a range of input bytes to an output iterator in 6-bit chunks + // + template < typename I, typename O, typename iterator_tag > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + iterator_tag ) + { + while ( beg != end ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + + if ( beg == end ) { + *out++ = char((c0 & 0x03) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + } + return out; + } + + int c1 = *beg++; + *out++ = char((c0 & 0x03) << 4 | (c1 & 0xff) >> 4); + + if ( beg == end ) { + *out++ = char((c1 & 0x0f) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + + int c2 = *beg++; + *out++ = char((c1 & 0x0f) << 2 | (c2 & 0xff) >> 6); + *out++ = char((c2 & 0x3f)); + } + + return out; + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use, + std::random_access_iterator_tag ) + { + while ( end - beg >= 3 ) { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + int c1 = *beg++; + *out++ = char((c0 & 0x03) << 4 | (c1 & 0xff) >> 4); + int c2 = *beg++; + *out++ = char((c1 & 0x0f) << 2 | (c2 & 0xff) >> 6); + *out++ = char((c2 & 0x3f)); + } + + switch ( end - beg ) { + case 2: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + int c1 = *beg++; + *out++ = char((c0 & 0x03) << 4 | (c1 & 0xff) >> 4); + *out++ = char((c1 & 0x0f) << 2); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + } + return out; + } + case 1: { + int c0 = *beg++; + *out++ = char((c0 & 0xff) >> 2); + *out++ = char((c0 & 0x03) << 4); + if ( pd_enforce == pd_use ) { + *out++ = EOF; + *out++ = EOF; + } + return out; + } + default: + return out; + } + } + + template < typename I, typename O > + static + O scatter( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + return scatter( beg, end, out, pd_use, typename std::iterator_traits::iterator_category() ); + } + + + template < typename I, typename O > + static + O scatter_standard( + I beg, + I end, + O out ) + { + return scatter( beg, end, out, pd_enforce, typename std::iterator_traits::iterator_category() ); + } + + // + // base<64>::gather -- collect 6-bit chunks into 8-bit characters + // + template < typename I, typename O > + static + O gather( + I beg, + I end, + O out, + pd_use_t pd_use = pd_invalid ) + { + while ( beg != end ) { + int c0 = gather_next( beg, end, pd_use, 0 ); + int c1 = gather_next( beg, end, pd_use, c0 ); + if ( c0 >= 0 and c1 >= 0 ) + *out++ = ( ((c0 < 0 ? 0 : c0 ) << 2 ) + | (c1 < 0 ? 0 : c1 ) >> 4 ); + int c2 = gather_next( beg, end, pd_use, c1 ); + if ( c1 >= 0 and c2 >= 0 ) + *out++ = ( ((c1 < 0 ? 0 : c1) & 0x0f) << 4 + | (c2 < 0 ? 0 : c2) >> 2 ); + int c3 = gather_next( beg, end, pd_use, c2 ); + if ( c2 >= 0 and c3 >= 0 ) + *out++ = ( ((c2 < 0 ? 0 : c2) & 0x03) << 6 + | (c3 < 0 ? 0 : c3)); + } + return out; + } + template < typename I, typename O > + static + O gather_standard( + I beg, + I end, + O out ) + { + return gather( beg, end, out, pd_enforce ); + } + }; // ezpwd::serialize::base<64> + + // + // ezpwd::serialize::base64... + // + // Shortcut typedefs for the standard base-64 codecs. + // + typedef base< 64, ezpwd< 64 >> + base64; + typedef base< 64, standard< 64 >> + base64_standard; + typedef base< 64, standard_url< 64 >> + base64_standard_url; + typedef base< 64, uuencode< 64 >> + base64_uuencode; + } // namespace ezpwd::serialize + +} // namespace ezpwd + +#endif // _EZPWD_SERIALIZE diff --git a/src/ezpwd/serialize_definitions b/src/ezpwd/serialize_definitions new file mode 100644 index 0000000..00b3fb7 --- /dev/null +++ b/src/ezpwd/serialize_definitions @@ -0,0 +1,57 @@ + +// +// The encoder/decoder tables for all ezpwd::serialize::... base codecs +// +// Must be included in exactly one C++ compilation unit. +// + +#ifndef _EZPWD_SERIALIZE_DEFINITIONS +#define _EZPWD_SERIALIZE_DEFINITIONS + +#include "serialize" + +// +// base<16> tables for RFC4864 standard +// +const constexpr std::array + ezpwd::serialize::hex<16>::encoder; +const constexpr std::array + ezpwd::serialize::hex<16>::decoder; + +// +// base<32> tables for RFC4864 standard, and the Hex32, EZPWD and Crockford codecs +// +const constexpr std::array + ezpwd::serialize::hex<32>::encoder; +const constexpr std::array + ezpwd::serialize::hex<32>::decoder; +const constexpr std::array + ezpwd::serialize::standard<32>::encoder; +const constexpr std::array + ezpwd::serialize::standard<32>::decoder; +const constexpr std::array + ezpwd::serialize::ezpwd<32>::encoder; +const constexpr std::array + ezpwd::serialize::ezpwd<32>::decoder; +const constexpr std::array + ezpwd::serialize::crockford<32>::encoder; +const constexpr std::array + ezpwd::serialize::crockford<32>::decoder; + +// +// base<64> tables for RFC4864 standard (regular and url), and the EZPWD codecs +// +const constexpr std::array + ezpwd::serialize::standard<64>::encoder; +const constexpr std::array + ezpwd::serialize::standard<64>::decoder; +const constexpr std::array + ezpwd::serialize::standard_url<64>::encoder; +const constexpr std::array + ezpwd::serialize::standard_url<64>::decoder; +const constexpr std::array + ezpwd::serialize::ezpwd<64>::encoder; +const constexpr std::array + ezpwd::serialize::ezpwd<64>::decoder; + +#endif // _EZPWD_SERIALIZE_DEFINITIONS diff --git a/src/ezpwd/timeofday b/src/ezpwd/timeofday new file mode 100644 index 0000000..3016138 --- /dev/null +++ b/src/ezpwd/timeofday @@ -0,0 +1,73 @@ +#ifndef _EZPWD_TIMEOFDAY +#define _EZPWD_TIMEOFDAY + +#include + +// +// ezpwd::timeofday -- Return current time. +// ezpwd::epoch -- The UNIX epoch. +// ezpwd::seconds -- convert timeval to a real-valued seconds +// < -- less-than comparison on timevals +// - -- difference on timevals +// +namespace ezpwd { + inline + timeval timeofday() + { + timeval tv; + ::gettimeofday( &tv, NULL ); + return tv; + } + + timeval epoch() + { + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + return tv; + } + + inline + double seconds( const timeval &rhs ) + { + return rhs.tv_usec / 1000000.0 + rhs.tv_sec; + } +} // namespace ezpwd + +inline +bool operator<( + const timeval &lhs, + const timeval &rhs ) +{ + return ( lhs.tv_sec < rhs.tv_sec + || (( lhs.tv_sec == rhs.tv_sec ) + && ( lhs.tv_usec < rhs.tv_usec ))); +} + +inline +timeval operator-( + const timeval &lhs, + timeval rhs ) // copy; adjusted... +{ + timeval result; + if ( lhs < rhs ) { + result = ezpwd::epoch(); + } else { + // See http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html + if ( lhs.tv_usec < rhs.tv_usec ) { + int sec = ( rhs.tv_usec - lhs.tv_usec ) / 1000000 + 1; + rhs.tv_usec -= sec * 1000000; + rhs.tv_sec += sec; + } + if ( lhs.tv_usec - rhs.tv_usec > 1000000 ) { + int sec = ( lhs.tv_usec - rhs.tv_usec ) / 1000000; + rhs.tv_usec += sec * 1000000; + rhs.tv_sec -= sec; + } + result.tv_sec = lhs.tv_sec - rhs.tv_sec; + result.tv_usec = lhs.tv_usec - rhs.tv_usec; + } + return result; +} + +#endif // _EZPWD_TIMEOFDAY diff --git a/src/p25_p2.c b/src/p25_p2.c index 46dcf23..621d6e6 100644 --- a/src/p25_p2.c +++ b/src/p25_p2.c @@ -1,9 +1,11 @@ /*------------------------------------------------------------------------------- * p25_p2.c - * Phase 2 TDMA Voice handling + * Phase 2 TDMA Voice Processing + * + * original copyrights for portions used below (OP25 DUID table, MAC len table) * * LWVMOBILE - * 2022-08 DSD-FME Florida Man Edition + * 2022-09 DSD-FME Florida Man Edition *-----------------------------------------------------------------------------*/ /* * Copyright (C) 2010 DSD Author @@ -23,8 +25,1572 @@ */ #include "dsd.h" -void processP2(dsd_opts * opts, dsd_state * state) +//DUID Look Up Table from OP25 +static const int16_t duid_lookup[256] = { + 0,0,0,-1,0,-1,-1,1,0,-1,-1,4,-1,8,2,-1, + 0,-1,-1,1,-1,1,1,1,-1,3,9,-1,5,-1,-1,1, + 0,-1,-1,10,-1,6,2,-1,-1,3,2,-1,2,-1,2,2, + -1,3,7,-1,11,-1,-1,1,3,3,-1,3,-1,3,2,-1, + 0,-1,-1,4,-1,6,12,-1,-1,4,4,4,5,-1,-1,4, + -1,13,7,-1,5,-1,-1,1,5,-1,-1,4,5,5,5,-1, + -1,6,7,-1,6,6,-1,6,14,-1,-1,4,-1,6,2,-1, + 7,-1,7,7,-1,6,7,-1,-1,3,7,-1,5,-1,-1,15, + 0,-1,-1,10,-1,8,12,-1,-1,8,9,-1,8,8,-1,8, + -1,13,9,-1,11,-1,-1,1,9,-1,9,9,-1,8,9,-1, + -1,10,10,10,11,-1,-1,10,14,-1,-1,10,-1,8,2,-1, + 11,-1,-1,10,11,11,11,-1,-1,3,9,-1,11,-1,-1,15, + -1,13,12,-1,12,-1,12,12,14,-1,-1,4,-1,8,12,-1, + 13,13,-1,13,-1,13,12,-1,-1,13,9,-1,5,-1,-1,15, + 14,-1,-1,10,-1,6,12,-1,14,14,14,-1,14,-1,-1,15, + -1,13,7,-1,11,-1,-1,15,14,-1,-1,15,-1,15,15,15, +}; + +//4V and 2V deinterleave schedule +const int c0[24] = { + 23,5,22,4,21,3,20,2,19,1,18,0, + 17,16,15,14,13,12,11,10,9,7,6 +}; + +const int c1[24] = { + 10,9,8,7,6,5,22,4,21,3,20,2, + 19,1,18,0,17,16,15,14,13,12,11 +}; + +const int c2[24] = { + 3,2,1,0,10,9,8,7,6,5,4 +}; + +const int c3[24] = { + 13,12,11,10,9,8,7,6,5,4,3,2,1,0 +}; + +const int csubset[72] = { + 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, + 0,0,1,3,0,0,1,3,0,1,1,3,0,1,1,3, + 0,1,1,3,0,1,1,3,0,1,1,3,0,1,2,3, + 0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3, + 0,1,2,3 +}; + +const int *w; + +char ambe_fr1[4][24] = {0}; +char ambe_fr2[4][24] = {0}; +char ambe_fr3[4][24] = {0}; +char ambe_fr4[4][24] = {0}; + +//MAC message lengths, from OP25 +static const uint8_t mac_msg_len[256] = { + 0, 7, 8, 7, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //1F + 0, 14, 15, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //2F + 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //3F + 9, 7, 9, 0, 9, 8, 9, 0, 10, 10, 9, 0, 10, 0, 0, 0, //4F + 0, 0, 0, 0, 9, 7, 0, 0, 10, 0, 7, 0, 10, 8, 14, 7, //5F + 9, 9, 0, 0, 9, 0, 0, 9, 10, 0, 7, 10, 10, 7, 0, 9, //6F + 9, 29, 9, 9, 9, 9, 10, 13, 9, 9, 9, 11, 9, 9, 0, 0, //7F + 8, 0, 0, 7, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //8F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //9F + 16, 0, 0, 11, 13, 11, 11, 11, 10, 0, 0, 0, 0, 0, 0, 0, //AF + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //BF + 11, 0, 0, 8, 15, 12, 15, 32, 12, 12, 0, 27, 14, 29, 29, 32, //CF + 0, 0, 0, 0, 0, 0, 9, 0, 14, 29, 11, 27, 14, 0, 40, 11, //DF //DE = 40? must be multi fragment? + 28, 0, 0, 14, 17, 14, 0, 0, 16, 8, 11, 0, 13, 19, 0, 0, //EF + 0, 0, 16, 14, 0, 0, 12, 0, 22, 0, 11, 13, 11, 0, 15, 0 }; //FF + + +int ts_counter = 0; //timeslot counter for time slots 0-11 +int p2bit[4320] = {0}; //4320 +int p2lbit[8640] = {0}; //bits generated by lsfr scrambler, doubling up for offset roll-over +int p2xbit[4320] = {0}; //bits xored from p2bit and p2lbit +int dibit = 0; +int vc_counter = 0; +int fourv_counter = 0; +int framing_counter = 0; + +unsigned long long int isch = 0; +unsigned long long int isch_decoded = 0; +int p2_duid[8] = {0}; +int16_t duid_decoded = 0; +int mac_opcode = -1; //initialize to -1 +int mac_offset = -1; +int mco = -1; + +int ess_b[2][96] = {0}; //96 bits for 4 - 24 bit ESS_B fields starting bit 168 (RS 44,16,29) +int ess_a[2][168] = {0}; //ESS_A 1 (96 bit) and 2 (72 bit) fields, starting at bit 168 and bit 266 (RS Parity) + +int facch[2][156] = {0}; +int facch_rs[2][114] = {0}; + +int sacch[2][180] = {0}; +int sacch_rs[2][132] = {0}; + +//not sure we need this seperation for LCCH, seems to work fine as SACCH +int lcch[2][180] = {0}; +int lcch_rs[2][132] = {0}; + +//How long is a superframe? What is just one superframe? +//superframe is 360ms, contains 12 consecutive 30 msec timeslots numbered 0 through 11 +//superframe contains Logical Channel LCH 0 and LCH 1 (LCH 0 - 0,2,4,6,8,11)(LCH 1 - 1,3,5,7,9,10) +//one ultraframe contains 4 superframes + +//store an entire p2 superframe worth of dibits into a bit buffer +void p2_dibit_buffer (dsd_opts * opts, dsd_state * state) { - //Do Something - fprintf (stderr, " Future Development\n"); //obligatory + //S-ISCH alternates, so we should start at VC1 right after S-ISCH + for (int i = 0; i < 2140; i++) //2160 for an entire superframe of dibits (Symbols) //2140 //2120 cut off last ISCH + { + //rip dibits directly from the capture bin file instead of getDibit + if (opts->audio_in_type == 4) //4 + { + dibit = fgetc(opts->symbolfile); + } + else + { + dibit = getDibit(opts, state); + if (opts->inverted_p2 == 1) + { + dibit = (dibit ^ 2); //invert dibit here since we are reading it upside down + } + } + + p2bit[i*2] = (dibit >> 1) & 1; + p2bit[i*2+1] = (dibit & 1); + + } + + //grab the entire VCH S-ISCH from the buffer and tack it onto the very end of the bitstream for reference? + int *dibit_p; + dibit_p = state->dibit_buf_p - 20; //rewind 20 dibits + for (int i = 2140; i < 2160; i++) //2160 for an entire superframe of dibits (Symbols) + { + dibit = *dibit_p; + dibit_p++; + p2bit[i*2] = (dibit >> 1) & 1; + p2bit[i*2+1] = (dibit & 1); + } +} + +void process_Frame_Scramble (dsd_opts * opts, dsd_state * state) +{ + //The bits of the scramble sequence corresponding to signal bits that are not scrambled or not used are discarded. + //descramble voice scrambled by LFSR of WACN, SysID, and CC(NAC) + unsigned long long int seed = 0; + //below calc is the same as shifting left the required number of bits...neat. + seed = ( (state->p2_wacn * 16777216) + (state->p2_sysid * 4096) + state->p2_cc ); + unsigned long long int bit = 1; //temp bit for storage during LFSR operation + + for (int i = 0; i < 4320; i++) + { + //External LFSR in figure 7.1 BBAC + + //assign our scramble bit to the array + p2lbit[i] = (seed >> 43) & 0x1; + //assign same bit to position +4320 to allow for a rollover with an offset value + p2lbit[i+4320] = (seed >> 43) & 0x1; + //compute our next scramble bit and shift the seed register and append bit to LSB + bit = ((seed >> 33) ^ (seed >> 19) ^ (seed >> 14) ^ (seed >> 8) ^ (seed >> 3) ^ (seed >> 43)) & 0x1; + seed = (seed << 1) | bit; + } + + for (int i = 0; i < 4300; i++) + { + //offset by 20 for sync, then 360 for each ts frame off + p2xbit[i] = p2bit[i] ^ p2lbit[i+20+(360*state->p2_scramble_offset)]; + } + +} + +void process_MAC_VPDU(dsd_opts * opts, dsd_state * state, int type, unsigned long long int MAC[24]) +{ + //handle variable content MAC PDUs (Active, Idle, or Hangtime) + //use type to specify SACCH or FACCH, so we know if we should invert the currentslot when assigning ids etc + //get first b1 and b2 values, and first offset value + //need a lookup table for message length values...aquired one from OP25 + + //b values - 0 = Unique TDMA Message, 1 Phase 1 OSP/ISP abbreviated + // 2 = Manufacturer Message, 3 Phase 1 OSP/ISP extended/explicit + + int b_a = MAC[1] >> 6; + int mco_a = MAC[1] & 0x3F; + int len_a = 0; //initial on zero, then set equal to len_b after first message rep + int len_b = mac_msg_len[MAC[1]]; //thinking the lookup is based on the full byte 1 + + int slot = 9; + if (type == 1) //0 for F, 1 for S + { + slot = (state->currentslot ^ 1) & 1; //flip slot internally for SACCH + } + else slot = state->currentslot; + + //init potential second message info + //this area needs reworking, consider using the DMR 1/2 or 3/4 rate superframe buffer + int b_b = 0; + int mco_b = 0; + + //assing here if OECI MAC SIGNAL, after passing RS and CRC + if (state->p2_is_lcch) + { + if (slot == 0) state->dmrburstL = 30; + else state->dmrburstR = 30; + } + + //if len is 0, then just skip the check entirely, or greater than 14? See comment below. + if (len_b == 0 || len_b > 14) //DE had a message length of 40! 40 What!? Way out of bounds on the array! + { + goto END_PDU; + } + + for (int i = 0; i < 1; i++) //short loop to get both messages if available + { + //MFID90 Group Regroup Voice Channel User - Abbreviated + //this is the one that was playing but had no group or src in tdma6.bin + if (MAC[1+len_a] == 0x80 && MAC[2+len_a] == 0x90) + { + + int gr = (MAC[4+len_a] << 8) | MAC[5+len_a]; + int src = (MAC[6+len_a] << 16) | (MAC[7+len_a] << 8) | MAC[8+len_a]; + fprintf (stderr, "\n VCH %d - Super Group %d SRC %d ", slot, gr, src); + fprintf (stderr, "MFID90 Group Regroup Voice"); + + if (slot == 0) + { + state->lasttg = gr; + if (src != 0) state->lastsrc = src; + } + else + { + state->lasttgR = gr; + if (src != 0) state->lastsrcR = src; + } + } + //MFID90 Group Regroup Voice Channel User - Extended + if (MAC[1+len_a] == 0xA0 && MAC[2+len_a] == 0x90) + { + + int gr = (MAC[5+len_a] << 8) | MAC[6+len_a]; + int src = (MAC[7+len_a] << 16) | (MAC[8+len_a] << 8) | MAC[9+len_a]; + fprintf (stderr, "\n VCH %d - Super Group %d SRC %d ", slot, gr, src); + fprintf (stderr, "MFID90 Group Regroup Voice"); + + if (slot == 0) + { + state->lasttg = gr; + if (src != 0) state->lastsrc = src; + } + else + { + state->lasttgR = gr; + if (src != 0) state->lastsrcR = src; + } + } + + //1 or 21, group voice channel message, abb and ext + if (MAC[1+len_a] == 0x1 || MAC[1+len_a] == 0x21) + { + int svc = MAC[2+len_a]; + int gr = (MAC[3+len_a] << 8) | MAC[4+len_a]; + int src = (MAC[5+len_a] << 16) | (MAC[6+len_a] << 8) | MAC[7+len_a]; + + fprintf (stderr, "\n VCH %d - TG %d SRC %d ", slot, gr, src); + fprintf (stderr, "Group Voice"); + + if (slot == 0) + { + state->lasttg = gr; + if (src != 0) state->lastsrc = src; + } + else + { + state->lasttgR = gr; + if (src != 0) state->lastsrcR = src; + } + } + //1 or 21, group voice channel message, abb and ext + if (MAC[1+len_a] == 0x2 || MAC[1+len_a] == 0x22) + { + int svc = MAC[2+len_a]; + int gr = (MAC[3+len_a] << 16) | (MAC[4+len_a] << 8) | MAC[5+len_a]; + int src = (MAC[6+len_a] << 16) | (MAC[7+len_a] << 8) | MAC[8+len_a]; + + fprintf (stderr, "\n VCH %d - TG %d SRC %d ", slot, gr, src); + fprintf (stderr, "Unit to Unit Voice"); + + if (slot == 0) + { + state->lasttg = gr; + if (src != 0) state->lastsrc = src; + } + else + { + state->lasttgR = gr; + if (src != 0) state->lastsrcR = src; + } + } + //network status broadcast, abbreviated + if (MAC[1+len_a] == 0x7B) + { + fprintf (stderr, "%s", KCYN); + int lra = MAC[2+len_a]; + int lwacn = (MAC[3+len_a] << 12) | (MAC[4+len_a] << 4) | ((MAC[5+len_a] & 0xF0) >> 4); + int lsysid = ((MAC[5+len_a] & 0xF) << 8) | MAC[6+len_a]; + int channel = (MAC[7+len_a] << 8) | MAC[8+len_a]; + int sysclass = MAC[9+len_a]; + int lcolorcode = ((MAC[10+len_a] & 0xF) << 8) | MAC[11+len_a]; + fprintf (stderr, "\n Network Status Broadcast - Abbreviated \n"); + fprintf (stderr, " LRA [%02X] WACN [%05X] SYSID [%03X] NAC [%03X] CHAN-T [%04X]", lra, lwacn, lsysid, lcolorcode, channel); + fprintf (stderr, "%s", KNRM); + if (state->p2_hardset == 0 ) + { + state->p2_wacn = lwacn; + state->p2_sysid = lsysid; + state->p2_cc = lcolorcode; + } + + } + //network status broadcast, extended + if (MAC[1+len_a] == 0xFB) + { + int lra = MAC[2+len_a]; + int lwacn = (MAC[3+len_a] << 12) | (MAC[4+len_a] << 4) | ((MAC[5+len_a] & 0xF0) >> 4); + int lsysid = ((MAC[5+len_a] & 0xF) << 8) | MAC[6+len_a]; + int channelt = (MAC[7+len_a] << 8) | MAC[8+len_a]; + int channelr = (MAC[9+len_a] << 8) | MAC[10+len_a]; + int sysclass = MAC[9+len_a]; + int lcolorcode = ((MAC[12+len_a] & 0xF) << 8) | MAC[13+len_a]; + fprintf (stderr, "%s", KCYN); + fprintf (stderr, "\n Network Status Broadcast - Extended \n"); + fprintf (stderr, " LRA [%02X] WACN [%05X] SYSID [%03X] NAC [%03X] CHAN-T [%04X] CHAN-R [%04X]", lra, lwacn, lsysid, lcolorcode, channelt, channelr); + fprintf (stderr, "%s", KNRM); + if (state->p2_hardset == 0 ) + { + state->p2_wacn = lwacn; + state->p2_sysid = lsysid; + state->p2_cc = lcolorcode; + } + } + + //Adjacent Status Broadcast, abbreviated + if (MAC[1+len_a] == 0x7C) + { + int lra = MAC[2+len_a]; + int lsysid = ((MAC[3+len_a] & 0xF) << 8) | MAC[4+len_a]; + int rfssid = MAC[5+len_a]; + int siteid = MAC[6+len_a]; + int channelt = (MAC[7+len_a] << 8) | MAC[8+len_a]; + //int channelr = (MAC[9+len_a] << 8) | MAC[10+len_a]; + int sysclass = MAC[9+len_a]; + //int lcolorcode = ((MAC[12+len_a] & 0xF) << 8) | MAC[13+len_a]; + if (1 == 1) //state->p2_is_lcch == 1 + { + fprintf (stderr, "%s", KCYN); + fprintf (stderr, "\n Adjacent Status Broadcast - Abbreviated\n"); + fprintf (stderr, " LRA [%02X] RFSS[%03d] SYSID [%03X] CHAN-T [%04X] SSC [%02X]", lra, rfssid, lsysid, channelt, sysclass); + fprintf (stderr, "%s", KNRM); + } + + } + + //Adjacent Status Broadcast, extended + if (MAC[1+len_a] == 0xFC) + { + int lra = MAC[2+len_a]; + int lsysid = ((MAC[3+len_a] & 0xF) << 8) | MAC[4+len_a]; + int rfssid = MAC[5+len_a]; + int siteid = MAC[6+len_a]; + int channelt = (MAC[7+len_a] << 8) | MAC[8+len_a]; + int channelr = (MAC[9+len_a] << 8) | MAC[10+len_a]; + int sysclass = MAC[9+len_a]; + //int lcolorcode = ((MAC[12+len_a] & 0xF) << 8) | MAC[13+len_a]; + if (1 == 1) //state->p2_is_lcch == 1 + { + fprintf (stderr, "%s", KCYN); + fprintf (stderr, "\n Adjacent Status Broadcast - Extended\n"); + fprintf (stderr, " LRA [%02X] RFSS[%03d] SYSID [%03X] CHAN-T [%04X] CHAN-R [%04X] SSC [%02X]", lra, rfssid, lsysid, channelt, channelr, sysclass); + fprintf (stderr, "%s", KNRM); + } + + } + + //set len_b for next potential message + len_b = mac_msg_len[MAC[1+len_b]]; + //set len_a as offset for next message if applicable, or exit loop early + if (len_b != 0 || len_b < 15) //DE had a message length of 40! 40 What!? Way out of bounds on the array! + { + len_a = len_b; + } + else + { + goto END_PDU; + } + + } + + END_PDU: + state->p2_is_lcch = 0; //toggle off so subsequent things can't trip it again + //debug printing + if (opts->payload == 1 && MAC[1] != 0 && MAC[2] != 0) //print only if not a null type + { + fprintf (stderr, "%s", KCYN); + fprintf (stderr, "\n Full MAC vPDU Payload\n "); + for (int i = 0; i < 24; i++) + { + fprintf (stderr, "[%02llX]", MAC[i]); + if (i == 11) fprintf (stderr, "\n "); + } + fprintf (stderr, "%s", KNRM); + } + +} + +//MAC PDU 3-bit Opcodes BBAC (8.4.1) p 123: +//0 - reserved //1 - Mac PTT //2 - Mac End PTT //3 - Mac Idle //4 - Mac Active +//5 - reserved //6 - Mac Hangtime //7 - reserved //Mac PTT BBAC p80 + +void process_SACCH_MAC_PDU (dsd_opts * opts, dsd_state * state, int payload[180]) +{ + //Figure out which PDU we are looking at, see above info on 8.4.1 + //reorganize bits into bytes and process accordingly + + unsigned long long int SMAC[24] = {0}; //22.5 bytes for SACCH MAC PDUs + int byte = 0; + int k = 0; + for (int j = 0; j < 22; j++) + { + for (int i = 0; i < 8; i++) + { + byte = byte << 1; + byte = byte | payload[k]; + k++; + } + SMAC[j] = byte; + byte = 0; //reset byte + } + SMAC[22] = (payload[176] << 7) | (payload[177] << 6) | (payload[178] << 5) | (payload[179] << 4); + + int opcode = 0; + opcode = (payload[0] << 2) | (payload[1] << 1) | (payload[2] << 0); + int mac_offset = 0; + mac_offset = (payload[3] << 2) | (payload[4] << 1) | (payload[5] << 0); + int b = 9; + b = (payload[8] << 1) | (payload[9] << 0); //combined b1 and b2 + int mco_a = 69; + //mco will tell us the number of octets to use in variable length MAC PDUs, need a table or something + mco_a = (payload[10] << 5) | (payload[11] << 4) | (payload[12] << 3) | (payload[13] << 2) | (payload[14] << 0) | (payload[15] << 0); + + //get the second mco after determining first message length, see what second mco is and plan accordingly + int mco_b = 69; + + //attempt CRC12 check to validate or reject PDU + int err = -2; + if (state->p2_is_lcch == 0) + { + int len = + err = crc12_xb_bridge(payload, 180-12); + if (err != 0) //CRC Failure, warn or skip. + { + if (SMAC[1] == 0x0) //NULL PDU Check, pass if NULL type + { + //fprintf (stderr, " NULL "); + } + else + { + fprintf (stderr, " CRC12 ERR "); + if (state->currentslot == 0) state->dmrburstL = 14; + else state->dmrburstR = 14; + goto END_SMAC; + } + } + } + if (state->p2_is_lcch == 1) + { + int len = 0; + //int len = mac_msg_len[SMAC[1]] * 8; + //if (len > 164) len = 164; //prevent potential stack smash or other issue. + len = 164; + err = crc16_lb_bridge(payload, len); + if (err != 0) //CRC Failure, warn or skip. + { + if (SMAC[1] == 0x0) //NULL PDU Check, pass if NULL type + { + //fprintf (stderr, " NULL "); + } + else + { + fprintf (stderr, " CRC16 ERR "); + state->p2_is_lcch = 0; //turn flag off here + //if (state->currentslot == 0) state->dmrburstL = 14; + //else state->dmrburstR = 14; + goto END_SMAC; + } + } + } + + + //remember, slots are inverted here, so set the opposite ones + //monitor, test, and remove these if they cause issues due to inversion + if (opcode == 0x0) + { + fprintf (stderr, " MAC_SIGNAL "); + fprintf (stderr, "%s", KMAG); + process_MAC_VPDU(opts, state, 1, SMAC); + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x1) + { + fprintf (stderr, " MAC_PTT "); + fprintf (stderr, "%s", KGRN); + //remember, slots are inverted here, so set the opposite ones + if (state->currentslot == 1) + { + //reset fourv_counter on PTT + state->fourv_counter[0] = 0; + + state->dmrburstL = 20; + fprintf (stderr, "\n VCH 0 - "); + + state->lastsrc = (SMAC[13] << 16) | (SMAC[14] << 8) | SMAC[15]; + state->lasttg = (SMAC[16] << 8) | SMAC[17]; + + fprintf (stderr, "TG %d ", state->lasttg); + fprintf (stderr, "SRC %d ", state->lastsrc); + + + state->payload_algid = SMAC[10]; + state->payload_keyid = (SMAC[11] << 8) | SMAC[12]; + state->payload_miP = (SMAC[1] << 56) | (SMAC[2] << 48) | (SMAC[3] << 40) | (SMAC[4] << 32) | + (SMAC[5] << 24) | (SMAC[6] << 16) | (SMAC[7] << 8) | (SMAC[8] << 0); + + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) + { + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n ALG ID 0x%02X", state->payload_algid); + fprintf (stderr, " KEY ID 0x%04X", state->payload_keyid); + fprintf (stderr, " MI 0x%016llX", state->payload_miP); + fprintf(stderr, " MPTT"); + // fprintf (stderr, " %s", KRED); + // fprintf (stderr, "ENC"); + } + + } + + if (state->currentslot == 0) + { + //reset fourv_counter on PTT + state->fourv_counter[1] = 0; + + state->payload_algidR = 0; + + state->dmrburstR = 20; + fprintf (stderr, "\n VCH 1 - "); + + state->lastsrcR = (SMAC[13] << 16) | (SMAC[14] << 8) | SMAC[15]; + state->lasttgR = (SMAC[16] << 8) | SMAC[17]; + + fprintf (stderr, "TG %d ", state->lasttgR); + fprintf (stderr, "SRC %d ", state->lastsrcR); + + + state->payload_algidR = SMAC[10]; + state->payload_keyidR = (SMAC[11] << 8) | SMAC[12]; + state->payload_miN = (SMAC[1] << 56) | (SMAC[2] << 48) | (SMAC[3] << 40) | (SMAC[4] << 32) | + (SMAC[5] << 24) | (SMAC[6] << 16) | (SMAC[7] << 8) | (SMAC[8] << 0); + + if (state->payload_algidR != 0x80 && state->payload_algidR != 0x0) + { + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n ALG ID 0x%02X", state->payload_algidR); + fprintf (stderr, " KEY ID 0x%04X", state->payload_keyidR); + fprintf (stderr, " MI 0x%016llX", state->payload_miN); + fprintf(stderr, " MPTT"); + // fprintf (stderr, " %s", KRED); + // fprintf (stderr, "ENC"); + } + + } + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x2) + { + fprintf (stderr, " MAC_END_PTT "); + fprintf (stderr, "%s", KRED); + //remember, slots are inverted here, so set the opposite ones + if (state->currentslot == 1) + { + //reset fourv_counter on PTT END + state->fourv_counter[0] = 0; + + state->dmrburstL = 23; + state->payload_algid = 0; //zero this out as well + state->payload_keyid = 0; //and this + + fprintf (stderr, "\n VCH 0 - "); + fprintf (stderr, "TG %d ", state->lasttg); + fprintf (stderr, "SRC %d ", state->lastsrc); + + //print it and then zero out + state->lastsrc = 0; + state->lasttg = 0; + } + if (state->currentslot == 0) + { + //reset fourv_counter on PTT END + state->fourv_counter[1] = 0; + + state->dmrburstR = 23; + state->payload_algidR = 0; + state->payload_keyidR = 0; + + fprintf (stderr, "\n VCH 1 - "); + fprintf (stderr, "TG %d ", state->lasttgR); + fprintf (stderr, "SRC %d ", state->lastsrcR); + + //print it and then zero out + state->lastsrcR = 0; + state->lasttgR = 0; + } + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x3) + { + if (state->currentslot == 1) state->dmrburstL = 24; + else state->dmrburstR = 24; + fprintf (stderr, " MAC_IDLE "); + process_MAC_VPDU(opts, state, 1, SMAC); + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x4) + { + if (state->currentslot == 1) state->dmrburstL = 21; + else state->dmrburstR = 21; + fprintf (stderr, " MAC_ACTIVE "); + fprintf (stderr, "%s", KGRN); + process_MAC_VPDU(opts, state, 1, SMAC); + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x6) + { + if (state->currentslot == 1) state->dmrburstL = 22; + else state->dmrburstR = 22; + fprintf (stderr, " MAC_HANGTIME "); + fprintf (stderr, "%s", KYEL); + process_MAC_VPDU(opts, state, 1, SMAC); + fprintf (stderr, "%s", KNRM); + } + + END_SMAC: + if (1 == 2) + { + //CRC Failure! + } + +} + +void process_FACCH_MAC_PDU (dsd_opts * opts, dsd_state * state, int payload[156]) +{ + //Figure out which PDU we are looking at, see above info on 8.4.1 + //reorganize bits into bytes and process accordingly + + unsigned long long int FMAC[24] = {0}; //19.5 bytes for FACCH MAC PDUs, add padding to end + int byte = 0; + int k = 0; + for (int j = 0; j < 19; j++) + { + for (int i = 0; i < 8; i++) + { + byte = byte << 1; + byte = byte | payload[k]; + k++; + } + FMAC[j] = byte; + byte = 0; //reset byte + } + FMAC[19] = (payload[152] << 7) | (payload[153] << 6) | (payload[154] << 5) | (payload[155] << 4); + + //add padding bytes so we can have a unified variable MAC PDU handler + for (int i = 0; i < 3; i++) + { + FMAC[i+20] = 0; + } + + int opcode = 0; + opcode = (payload[0] << 2) | (payload[1] << 1) | (payload[2] << 0); + int mac_offset = 0; + mac_offset = (payload[3] << 2) | (payload[4] << 1) | (payload[5] << 0); + + //attempt CRC check to validate or reject PDU + int err = -2; + if (state->p2_is_lcch == 0) + { + err = crc12_xb_bridge(payload, 156-12); + if (err != 0) //CRC Failure, warn or skip. + { + if (FMAC[1] == 0x0) //NULL PDU Check, pass if NULL + { + //fprintf (stderr, " NULL "); + } + else + { + fprintf (stderr, " CRC12 ERR "); + if (state->currentslot == 0) state->dmrburstL = 14; + else state->dmrburstR = 14; + goto END_FMAC; + } + } + } + + + //Not sure if a MAC Signal will come on a FACCH or not, so disable to prevent falsing + // if (opcode == 0x0) + // { + // fprintf (stderr, " MAC_SIGNAL "); + // fprintf (stderr, "%s", KMAG); + // process_MAC_VPDU(opts, state, 0, FMAC); + // fprintf (stderr, "%s", KNRM); + // } + + if (opcode == 0x1) + { + + fprintf (stderr, " MAC_PTT "); + fprintf (stderr, "%s", KGRN); + if (state->currentslot == 0) + { + //reset fourv_counter and dropbyte on PTT + state->fourv_counter[0] = 0; + + state->dmrburstL = 20; + fprintf (stderr, "\n VCH 0 - "); + + state->lastsrc = (FMAC[13] << 16) | (FMAC[14] << 8) | FMAC[15]; + state->lasttg = (FMAC[16] << 8) | FMAC[17]; + + fprintf (stderr, "TG %d ", state->lasttg); + fprintf (stderr, "SRC %d ", state->lastsrc); + + + state->payload_algid = FMAC[10]; + state->payload_keyid = (FMAC[11] << 8) | FMAC[12]; + state->payload_miP = (FMAC[1] << 56) | (FMAC[2] << 48) | (FMAC[3] << 40) | (FMAC[4] << 32) | + (FMAC[5] << 24) | (FMAC[6] << 16) | (FMAC[7] << 8) | (FMAC[8] << 0); + + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) + { + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n ALG ID 0x%02X", state->payload_algid); + fprintf (stderr, " KEY ID 0x%04X", state->payload_keyid); + fprintf (stderr, " MI 0x%016llX", state->payload_miP); + fprintf(stderr, " MPTT"); + // fprintf (stderr, " %s", KRED); + // fprintf (stderr, "ENC"); + } + + } + + if (state->currentslot == 1) + { + //reset fourv_counter and dropbyte on PTT + state->fourv_counter[1] = 0; + + state->dmrburstR = 20; + fprintf (stderr, "\n VCH 1 - "); + + state->lastsrcR = (FMAC[13] << 16) | (FMAC[14] << 8) | FMAC[15]; + state->lasttgR = (FMAC[16] << 8) | FMAC[17]; + + fprintf (stderr, "TG %d ", state->lasttgR); + fprintf (stderr, "SRC %d ", state->lastsrcR); + + + state->payload_algidR = FMAC[10]; + state->payload_keyidR = (FMAC[11] << 8) | FMAC[12]; + state->payload_miN = (FMAC[1] << 56) | (FMAC[2] << 48) | (FMAC[3] << 40) | (FMAC[4] << 32) | + (FMAC[5] << 24) | (FMAC[6] << 16) | (FMAC[7] << 8) | (FMAC[8] << 0); + + if (state->payload_algidR != 0x80 && state->payload_algidR != 0x0) + { + fprintf (stderr, "%s", KYEL); + fprintf (stderr, "\n ALG ID 0x%02X", state->payload_algidR); + fprintf (stderr, " KEY ID 0x%04X", state->payload_keyidR); + fprintf (stderr, " MI 0x%016llX", state->payload_miN); + fprintf(stderr, " MPTT"); + // fprintf (stderr, " %s", KRED); + // fprintf (stderr, "ENC"); + } + + } + fprintf (stderr, "%s", KNRM); + + } + if (opcode == 0x2) + { + fprintf (stderr, " MAC_END_PTT "); + fprintf (stderr, "%s", KRED); + if (state->currentslot == 0) + { + //reset fourv_counter and dropbyte on PTT END + state->fourv_counter[0] = 0; + + state->dmrburstL = 23; + state->payload_algid = 0; //zero this out as well + state->payload_keyid = 0; + + fprintf (stderr, "\n VCH 0 - "); + fprintf (stderr, "TG %d ", state->lasttg); + fprintf (stderr, "SRC %d ", state->lastsrc); + + //print it and then zero out + state->lastsrc = 0; + state->lasttg = 0; + } + if (state->currentslot == 1) + { + //reset fourv_counter and dropbyte on PTT END + state->fourv_counter[1] = 0; + + state->dmrburstR = 23; + state->payload_algidR = 0; //zero this out as well + state->payload_keyidR = 0; + + fprintf (stderr, "\n VCH 1 - "); + fprintf (stderr, "TG %d ", state->lasttgR); + fprintf (stderr, "SRC %d ", state->lastsrcR); + + //print it and then zero out + state->lastsrcR = 0; + state->lasttgR = 0; + } + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x3) + { + //what else should we zero out here? + //disable any of the lines below if issues are observed + if (state->currentslot == 0) + { + state->payload_algid = 0; + state->payload_keyid = 0; + state->dmrburstL = 24; + state->fourv_counter[0] = 0; + state->lastsrc = 0; + state->lasttg = 0; + + } + else + { + state->payload_algidR = 0; + state->payload_keyidR = 0; + state->dmrburstR = 24; + state->fourv_counter[1] = 0; + state->lastsrcR = 0; + state->lasttgR = 0; + + } + fprintf (stderr, " MAC_IDLE "); + process_MAC_VPDU(opts, state, 0, FMAC); + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x4) + { + if (state->currentslot == 0) state->dmrburstL = 21; + else state->dmrburstR = 21; + fprintf (stderr, " MAC_ACTIVE "); + fprintf (stderr, "%s", KGRN); + process_MAC_VPDU(opts, state, 0, FMAC); + fprintf (stderr, "%s", KNRM); + } + if (opcode == 0x6) + { + if (state->currentslot == 0) state->dmrburstL = 22; + else state->dmrburstR = 22; + fprintf (stderr, " MAC_HANGTIME "); + fprintf (stderr, "%s", KYEL); + process_MAC_VPDU(opts, state, 0, FMAC); + fprintf (stderr, "%s", KNRM); + } + + END_FMAC: + if (1 == 2) + { + //CRC Failure! + } + +} + +void process_FACCHc (dsd_opts * opts, dsd_state * state) +{ + //gather and process FACCH w/o scrambling (S-OEMI) so we know what to do with the containing data. + for (int i = 0; i < 72; i++) + { + facch[state->currentslot][i] = p2bit[i+2+(ts_counter*360)]; + } + //skip DUID 1 + for (int i = 0; i < 62; i++) + { + facch[state->currentslot][i+72] = p2bit[i+76+(ts_counter*360)]; + } + //skip sync + for (int i = 0; i < 22; i++) + { + facch[state->currentslot][i+134] = p2bit[i+180+(ts_counter*360)]; + } + //gather FACCH RS parity bits + for (int i = 0; i < 42; i++) + { + facch_rs[state->currentslot][i] = p2bit[i+202+(ts_counter*360)]; + } + //skip DUID 3 + for (int i = 0; i < 72; i++) + { + facch_rs[state->currentslot][i+42] = p2bit[i+246+(ts_counter*360)]; + } + + //send payload and parity to ez_rs28_facch for error correction + int ec = -2; + ec = ez_rs28_facch (facch[state->currentslot], facch_rs[state->currentslot]); + + int opcode = 0; + opcode = (facch[state->currentslot][0] << 2) | (facch[state->currentslot][1] << 1) | (facch[state->currentslot][2] << 0); + + if (state->currentslot == 0) + { + state->dmr_so = opcode; + } + else state->dmr_soR = opcode; + + if (ec > -1) //unsure of upper limit, CRC check will pass or fail from this point + { + process_FACCH_MAC_PDU (opts, state, facch[state->currentslot]); + } + else + { + fprintf(stderr, " R-S ERR "); + if (state->currentslot == 0) state->dmrburstL = 13; + else state->dmrburstR = 13; + } + +} + +void process_FACCHs (dsd_opts * opts, dsd_state * state) +{ + //gather and process FACCH w scrambling (S-OEMI) so we know what to do with the containing data. + for (int i = 0; i < 72; i++) + { + facch[state->currentslot][i] = p2xbit[i+2+(ts_counter*360)]; + } + //skip DUID 1 + for (int i = 0; i < 62; i++) + { + facch[state->currentslot][i+72] = p2xbit[i+76+(ts_counter*360)]; + } + //skip sync + for (int i = 0; i < 22; i++) + { + facch[state->currentslot][i+134] = p2xbit[i+180+(ts_counter*360)]; + } + //gather FACCh RS parity bits + for (int i = 0; i < 42; i++) + { + facch_rs[state->currentslot][i] = p2xbit[i+202+(ts_counter*360)]; + } + //skip DUID 3 + for (int i = 0; i < 72; i++) + { + facch_rs[state->currentslot][i+42] = p2xbit[i+246+(ts_counter*360)]; + } + + //send payload and parity to ez_rs28_facch for error correction + int ec = -2; + ec = ez_rs28_facch (facch[state->currentslot], facch_rs[state->currentslot]); + + int opcode = 0; + opcode = (facch[state->currentslot][0] << 2) | (facch[state->currentslot][1] << 1) | (facch[state->currentslot][2] << 0); + + if (state->currentslot == 0) + { + state->dmr_so = opcode; + } + else state->dmr_soR = opcode; + + if (ec > -1) //unsure of upper limit, CRC check will pass or fail from this point + { + process_FACCH_MAC_PDU (opts, state, facch[state->currentslot]); + } + else + { + fprintf(stderr, " R-S ERR "); + if (state->currentslot == 0) state->dmrburstL = 13; + else state->dmrburstR = 13; + } +} + +void process_SACCHc (dsd_opts * opts, dsd_state * state) +{ + //gather and process SACCH w/o scrambling (I-OEMI) so we know what to do with the containing data. + for (int i = 0; i < 72; i++) + { + sacch[state->currentslot][i] = p2bit[i+2+(ts_counter*360)]; + } + //skip DUID 1 + for (int i = 0; i < 108; i++) + { + sacch[state->currentslot][i+72] = p2bit[i+76+(ts_counter*360)]; + } + //start collecting parity + for (int i = 0; i < 60; i++) + { + sacch_rs[state->currentslot][i] = p2bit[i+184+(ts_counter*360)]; + } + //skip DUID 3 + for (int i = 0; i < 72; i++) + { + sacch_rs[state->currentslot][i+60] = p2bit[i+246+(ts_counter*360)]; + } + + //send payload and parity to ez_rs28_sacch for error correction + int ec = -2; + ec = ez_rs28_sacch (sacch[0], sacch_rs[0]); + + int opcode = 0; + opcode = (sacch[state->currentslot][0] << 2) | (sacch[state->currentslot][1] << 1) | (sacch[state->currentslot][2] << 0); + + //set inverse true for SACCH + if (state->currentslot == 0) + { + state->dmr_soR = opcode; + } + else state->dmr_so = opcode; + + if (ec > -1) //unsure of upper limit, CRC check will pass or fail from this point + { + process_SACCH_MAC_PDU (opts, state, sacch[state->currentslot]); + } + else + { + fprintf(stderr, " R-S ERR "); + if (state->currentslot == 0) state->dmrburstL = 13; + else state->dmrburstR = 13; + } + +} + +void process_SACCHs (dsd_opts * opts, dsd_state * state) +{ + //gather and process SACCH w scrambling (I-OEMI) so we know what to do with the containing data. + for (int i = 0; i < 72; i++) + { + sacch[state->currentslot][i] = p2xbit[i+2+(ts_counter*360)]; + } + //skip DUID 1 + for (int i = 0; i < 108; i++) + { + sacch[state->currentslot][i+72] = p2xbit[i+76+(ts_counter*360)]; + } + //start collecting parity + for (int i = 0; i < 60; i++) + { + sacch_rs[state->currentslot][i] = p2xbit[i+184+(ts_counter*360)]; + } + //skip DUID 3 + for (int i = 0; i < 72; i++) + { + sacch_rs[state->currentslot][i+60] = p2xbit[i+246+(ts_counter*360)]; + } + + //send payload and parity to ez_rs28_sacch for error correction + int ec = -2; + ec = ez_rs28_sacch (sacch[0], sacch_rs[0]); + + int opcode = 0; + opcode = (sacch[state->currentslot][0] << 2) | (sacch[state->currentslot][1] << 1) | (sacch[state->currentslot][2] << 0); + + //set inverse true for SACCH + if (state->currentslot == 0) + { + state->dmr_soR = opcode; + } + else state->dmr_so = opcode; + + if (ec > -1) //unsure of upper limit, CRC check will pass or fail from this point + { + process_SACCH_MAC_PDU (opts, state, sacch[state->currentslot]); + } + else + { + fprintf(stderr, " R-S ERR "); + if (state->currentslot == 0) state->dmrburstL = 13; + else state->dmrburstR = 13; + } + +} + +void process_ISCH (dsd_opts * opts, dsd_state * state) +{ + isch = 0; + for (int i = 0; i < 40; i++) + { + isch = isch << 1; + isch = isch | p2bit[i+320+(360*framing_counter)]; + } + + if (isch == 0x575D57F7FF) //S-ISCH frame sync, pass; + { + //do nothing + } + else + { + isch_decoded = isch_lookup(isch); + + if (isch_decoded > 0) + { + int uf_count = isch_decoded & 0x3; + int free = (isch_decoded >> 2) & 0x1; + int isch_loc = (isch_decoded >> 3) & 0x3; + int chan_num = (isch_decoded >> 5) & 0x3; + state->p2_vch_chan_num = chan_num; + + // fprintf (stderr, "\n I-ISCH = %010llX - Decoded = %02llX -", isch, isch_decoded); + // fprintf (stderr, " UF Count = %1X - Free = %1X - Loc = %1X Chan = %1X TS = %02d - OFF = %02d", uf_count, free, isch_loc, chan_num, framing_counter, state->p2_scramble_offset); + + //determine where the offset should be by first finding TS 0 + if (chan_num == 0 && isch_loc == 0) + { + //this rule seems to satisfy all conditions I've seen in all samples collected so far + state->p2_scramble_offset = 11 - framing_counter; + } + + } + else + { + //if -2(no return value) or -1(fec error) + } + + } + +} + +void process_4V (dsd_opts * opts, dsd_state * state) +{ + + w = csubset; + int b = 0; + int q = 0; + int r = 0; + int s = 0; + int t = 0; + for (int x = 0; x < 72; x++) + { + int ww = *w; + if (ww == 0) + { + b = c0[q]; + q++; + } + if (ww == 1) + { + b = c1[r]; + r++; + } + if (ww == 2) + { + b = c2[s]; + s++; + } + if (ww == 3) + { + b = c3[t]; + t++; + } + + ambe_fr1[*w][b] = p2xbit[x+2+vc_counter]; + ambe_fr2[*w][b] = p2xbit[x+76+vc_counter]; + ambe_fr3[*w][b] = p2xbit[x+172+vc_counter]; + ambe_fr4[*w][b] = p2xbit[x+246+vc_counter]; + w++; + } + + //collect our ESS_B fragments + for (int i = 0; i < 24; i++) + { + state->ess_b[state->currentslot][i+(state->fourv_counter[state->currentslot]*24)] = + p2xbit[i+148+vc_counter]; + } + + state->fourv_counter[state->currentslot]++; + //sanity check, reset if greater than 3 (bad signal or tuned away) + if (state->fourv_counter[state->currentslot] > 3) + { + state->fourv_counter[state->currentslot] = 0; + } + + if (opts->payload == 1) + { + fprintf (stderr, "\n"); + } + + processMbeFrame (opts, state, NULL, ambe_fr1, NULL); + processMbeFrame (opts, state, NULL, ambe_fr2, NULL); + processMbeFrame (opts, state, NULL, ambe_fr3, NULL); + processMbeFrame (opts, state, NULL, ambe_fr4, NULL); + +} + +void process_ESS (dsd_opts * opts, dsd_state * state) +{ + //collect and process ESS info (MI, Key ID, Alg ID) + //hand over to (RS 44,16,29) decoder to receive ESS values + + int payload[96] = {0}; //local storage for ESS_A and ESS_B arrays + for (int i = 0; i < 96; i++) + { + payload[i] = state->ess_b[state->currentslot][i]; + } + + int parity[168] = {0}; + for (int i = 0; i < 168; i++) + { + parity[i] = ess_a[state->currentslot][i]; + } + + int ec = 69; //initialize with a bad value + ec = ez_rs28_ess(payload, parity); //working now! + + int algid = 0; + for (short i = 0; i < 8; i++) + { + algid = algid << 1; + algid = algid | payload[i]; + } + + unsigned long long int essb_hex1 = 0; + unsigned long long int essb_hex2 = 0; + for (int i = 0; i < 32; i++) + { + essb_hex1 = essb_hex1 << 1; + essb_hex1 = essb_hex1 | payload[i]; + } + for (int i = 0; i < 64; i++) + { + essb_hex2 = essb_hex2 << 1; + essb_hex2 = essb_hex2 | payload[i+32]; + } + fprintf (stderr, "%s", KYEL); + + if (opts->payload == 1) + { + fprintf (stderr, "\n"); + fprintf (stderr, " VCH %d - ESS_B %08llX%016llX ERR = %02d", state->currentslot, essb_hex1, essb_hex2, ec); + } + + if (ec >= 0 && ec < 15) //corrected up to 14 errors and not -1 failure// + { + if (state->currentslot == 0) + { + state->payload_algid = (essb_hex1 >> 24) & 0xFF; + state->payload_keyid = (essb_hex1 >> 8) & 0xFFFF; + state->payload_miP = ((essb_hex1 & 0xFF) << 56) | ((essb_hex2 & 0xFFFFFFFFFFFFFF00) >> 8); + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) + { + fprintf (stderr, "\n VCH 0 -"); + fprintf (stderr, " ALG ID 0x%02X", state->payload_algid); + fprintf (stderr, " KEY ID 0x%04X", state->payload_keyid); + fprintf (stderr, " MI 0x%016llX", state->payload_miP); + fprintf(stderr, " ESSB"); + } + + } + if (state->currentslot == 1) + { + state->payload_algidR = (essb_hex1 >> 24) & 0xFF; + state->payload_keyidR = (essb_hex1 >> 8) & 0xFFFF; + state->payload_miN = ((essb_hex1 & 0xFF) << 56) | ((essb_hex2 & 0xFFFFFFFFFFFFFF00) >> 8); + if (state->payload_algidR != 0x80 && state->payload_algidR != 0x0) + { + fprintf (stderr, "\n VCH 1 -"); + fprintf (stderr, " ALG ID 0x%02X", state->payload_algidR); + fprintf (stderr, " KEY ID 0x%04X", state->payload_keyidR); + fprintf (stderr, " MI 0x%016llX", state->payload_miN); + fprintf(stderr, " ESSB"); + } + + } + } + if (ec == -1 || ec >= 15) + { + //ESS R-S Failure + } + fprintf (stderr, "%s", KNRM); + + state->fourv_counter[state->currentslot] = 0; //reset after processing + +} + +void process_2V (dsd_opts * opts, dsd_state * state) +{ + + w = csubset; + int b = 0; + int q = 0; + int r = 0; + int s = 0; + int t = 0; + for (int x = 0; x < 72; x++) + { + int ww = *w; + if (ww == 0) + { + b = c0[q]; + q++; + } + if (ww == 1) + { + b = c1[r]; + r++; + } + if (ww == 2) + { + b = c2[s]; + s++; + } + if (ww == 3) + { + b = c3[t]; + t++; + } + + ambe_fr1[*w][b] = p2xbit[x+2+vc_counter]; + ambe_fr2[*w][b] = p2xbit[x+76+vc_counter]; + w++; + } + + //collect ESS_A and then run process_ESS + for (short i = 0; i < 96; i++) + { + ess_a[state->currentslot][i] = p2xbit[i+148+vc_counter]; + } + + for (short i = 0; i < 72; i++) //load up ESS_A 2 + { + ess_a[state->currentslot][i+96] = p2xbit[i+246+vc_counter]; + } + + if (opts->payload == 1) + { + fprintf (stderr, "\n"); + } + + processMbeFrame (opts, state, NULL, ambe_fr1, NULL); + processMbeFrame (opts, state, NULL, ambe_fr2, NULL); + + process_ESS(opts, state); + +} + +//P2 Data Unit ID +void process_P2_DUID (dsd_opts * opts, dsd_state * state) +{ + //DUID exist on all P25p2 frames, need to check this so we can process the TS frame properly + vc_counter = 0; + int err_counter = 0; + + //add time to mirror printFrameSync + time_t now; + char * getTime(void) //get pretty hh:mm:ss timestamp + { + time_t t = time(NULL); + + char * curr; + char * stamp = asctime(localtime( & t)); + + curr = strtok(stamp, " "); + curr = strtok(NULL, " "); + curr = strtok(NULL, " "); + curr = strtok(NULL, " "); + + return curr; + } + + for (ts_counter = 0; ts_counter < 12; ts_counter++) + { + + p2_duid[0] = p2bit[0+(ts_counter*360)]; + p2_duid[1] = p2bit[1+(ts_counter*360)]; + p2_duid[2] = p2bit[74+(ts_counter*360)]; + p2_duid[3] = p2bit[75+(ts_counter*360)]; + p2_duid[4] = p2bit[244+(ts_counter*360)]; + p2_duid[5] = p2bit[245+(ts_counter*360)]; + p2_duid[6] = p2bit[318+(ts_counter*360)]; + p2_duid[7] = p2bit[319+(ts_counter*360)]; + + //process p2_duid with (8,4,4) encoding/decoding + int p2_duid_complete = 0; + for (int i = 0; i < 8; i++) + { + p2_duid_complete = p2_duid_complete << 1; + p2_duid_complete = p2_duid_complete | p2_duid[i]; + } + duid_decoded = duid_lookup[p2_duid_complete]; + + fprintf (stderr, "\n"); + fprintf (stderr,"%s ", getTime()); + fprintf (stderr, " P25p2 "); + + if (state->currentslot == 0) + { + state->currentslot = 1; + } + else + { + state->currentslot = 0; + } + + //print our VCH channel number, or print S for SACCH since its inverted + if (state->currentslot == 0 && duid_decoded != 3 && duid_decoded != 12) + { + fprintf (stderr, "VCH 0 "); + } + else if (state->currentslot == 1 && duid_decoded != 3 && duid_decoded != 12) + { + fprintf (stderr, "VCH 1 "); + } + else fprintf (stderr, "VCH S "); + + if (duid_decoded == 0) + { + fprintf (stderr, " 4V %d", state->fourv_counter[state->currentslot]+1); + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + process_4V (opts, state); + } + } + else if (duid_decoded == 6) + { + fprintf (stderr, " 2V"); + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + process_2V (opts, state); + } + } + else if (duid_decoded == 3) + { + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + process_SACCHs(opts, state); + } + } + else if (duid_decoded == 12) + { + process_SACCHc(opts, state); + } + else if (duid_decoded == 15) + { + process_FACCHc(opts, state); + } + else if (duid_decoded == 9) + { + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + process_FACCHs(opts, state); + } + } + else if (duid_decoded == 13) + { + //fprintf (stderr, " LCCH TDMA CC"); //w/o scrambling OECI + // if (state->currentslot == 0) state->dmrburstL = 30; + // else state->dmrburstR = 30; + state->p2_is_lcch = 1; + process_SACCHc(opts, state); + } + else if (duid_decoded == 4) + { + fprintf (stderr, " LCCH Sc"); //w/ scrambling + // if (state->currentslot == 0) state->dmrburstL = 31; + // else state->dmrburstR = 31; + if (state->p2_wacn != 0 && state->p2_cc != 0 && state->p2_sysid != 0) + { + state->p2_is_lcch = 1; + process_SACCHs(opts, state); + } + } + else + { + fprintf (stderr, " DUID ERR %d", duid_decoded); + if (state->currentslot == 0) state->dmrburstL = 12; + else state->dmrburstR = 12; + err_counter++; + } + if (err_counter > 1) //&& opts->aggressive_framesync == 1 + { + //zero out values when errs accumulate in DUID + //most likely cause will be signal drop or tuning away + state->payload_algid = 0; + state->payload_keyid = 0; + state->payload_algidR = 0; + state->payload_keyidR = 0; + state->lastsrc = 0; + state->lastsrcR = 0; + state->lasttg = 0; + state->lasttgR = 0; + state->p2_is_lcch = 0; + state->fourv_counter[0] = 0; + state->fourv_counter[1] = 0; + + goto END; + } + //since we are in a while loop, run ncursesPrinter here. + if (opts->use_ncurses_terminal == 1) + { + ncursesPrinter(opts, state); + } + //add 360 bits to each counter + vc_counter = vc_counter + 360; + + } + END: + if (1 == 2) + { + //go directly to END if 2 consecutive DUID errors occur + } + +} + +void processP2 (dsd_opts * opts, dsd_state * state) +{ + //hard code here for ease of use while figuring this out + //295 samples + // state->p2_wacn = 0xBEE00; //BEE00 + // state->p2_sysid = 0x295; //39A + // state->p2_cc = 0x290; //399 + + //adp_tdma.bin and NJ samples + // state->p2_wacn = 0xBEE00; //BEE00 + // state->p2_sysid = 0x39A; //39A //56C + // state->p2_cc = 0x399; //399 //561 + + //barrow samples + // state->p2_wacn = 0xBEE00; //BEE00 + // state->p2_sysid = 0x56C; //39A //56C + // state->p2_cc = 0x561; //399 //561 + + state->dmr_stereo = 1; //flag on now and flag off on exit + state->currentslot = 1; //start on 1, initial flip will make it zero again + + //collect and store a superframe worth of dibits for now, may change later to just 2 TS frames + p2_dibit_buffer (opts, state); + + //look at our ISCH values and determine location in superframe before running frame scramble + //12 is really the s-isch that initiated this, I just tacked it onto the very end for no reason + for (framing_counter = 0; framing_counter < 11; framing_counter++) //11, or 12 + { + process_ISCH (opts, state); //run ISCH in here so we know when to start descramble offset + } + + //frame_scramble runs lfsr and creates an array of unscrambled bits to pull from + process_Frame_Scramble (opts, state); + + //process DUID will run through all collected frames and handle them appropriately + process_P2_DUID (opts, state); + state->dmr_stereo = 0; //flag off + state->p2_is_lcch = 0; //flag off + fprintf (stderr, "\n"); //print a line break at the end } diff --git a/src/p25p1_block.cpp b/src/p25p1_block.cpp new file mode 100644 index 0000000..5c6d62b --- /dev/null +++ b/src/p25p1_block.cpp @@ -0,0 +1,276 @@ +/*------------------------------------------------------------------------------- + * p25p1_block.c + * P25 Block Bridge for TSBK Deinterleaving, CRC12 and CRC16 checks + * + * original copyrights for portions used below (OP25, Ossman, and LEH) + * + * LWVMOBILE + * 2022-09 DSD-FME Florida Man Edition + *-----------------------------------------------------------------------------*/ + +#include "dsd.h" +#include + +typedef std::vector bit_vector; + +/* adapted from wireshark/plugins/p25/packet-p25cai.c */ +/* Copyright 2008, Michael Ossmann */ +/* deinterleave and trellis1_2 decoding, count_bits, and find_min*/ +/* buf is assumed to be a buffer of 12 bytes */ + +typedef std::vector bit_vector; + +//ugly copy and paste below +static int count_bits(unsigned int n) { + int i = 0; + for (i = 0; n != 0; i++) + n &= n - 1; + return i; + } + +static int find_min(uint8_t list[], int len) { + int min = list[0]; + int index = 0; + int unique = 1; + int i; + + for (i = 1; i < len; i++) { + if (list[i] < min) { + min = list[i]; + index = i; + unique = 1; + } else if (list[i] == min) { + unique = 0; + } + } + /* return -1 if a minimum can't be found */ + if (!unique) + return -1; + + return index; + } + +static int block_deinterleave(bit_vector& bv, unsigned int start, uint8_t* buf) +{ + static const uint16_t deinterleave_tb[] = { + 0, 1, 2, 3, 52, 53, 54, 55, 100,101,102,103, 148,149,150,151, + 4, 5, 6, 7, 56, 57, 58, 59, 104,105,106,107, 152,153,154,155, + 8, 9, 10, 11, 60, 61, 62, 63, 108,109,110,111, 156,157,158,159, + 12, 13, 14, 15, 64, 65, 66, 67, 112,113,114,115, 160,161,162,163, + 16, 17, 18, 19, 68, 69, 70, 71, 116,117,118,119, 164,165,166,167, + 20, 21, 22, 23, 72, 73, 74, 75, 120,121,122,123, 168,169,170,171, + 24, 25, 26, 27, 76, 77, 78, 79, 124,125,126,127, 172,173,174,175, + 28, 29, 30, 31, 80, 81, 82, 83, 128,129,130,131, 176,177,178,179, + 32, 33, 34, 35, 84, 85, 86, 87, 132,133,134,135, 180,181,182,183, + 36, 37, 38, 39, 88, 89, 90, 91, 136,137,138,139, 184,185,186,187, + 40, 41, 42, 43, 92, 93, 94, 95, 140,141,142,143, 188,189,190,191, + 44, 45, 46, 47, 96, 97, 98, 99, 144,145,146,147, 192,193,194,195, + 48, 49, 50, 51 }; + + uint8_t hd[4]; + int b, d, j; + int state = 0; + uint8_t codeword; + + static const uint8_t next_words[4][4] = { + {0x2, 0xC, 0x1, 0xF}, + {0xE, 0x0, 0xD, 0x3}, + {0x9, 0x7, 0xA, 0x4}, + {0x5, 0xB, 0x6, 0x8} + }; + + memset(buf, 0, 12); + + for (b=0; b < 98*2; b += 4) { + codeword = (bv[start+deinterleave_tb[b+0]] << 3) + + (bv[start+deinterleave_tb[b+1]] << 2) + + (bv[start+deinterleave_tb[b+2]] << 1) + + bv[start+deinterleave_tb[b+3]] ; + + /* try each codeword in a row of the state transition table */ + for (j = 0; j < 4; j++) { + /* find Hamming distance for candidate */ + hd[j] = count_bits(codeword ^ next_words[state][j]); + } + /* find the dibit that matches the most codeword bits (minimum Hamming distance) */ + state = find_min(hd, 4); + /* error if minimum can't be found */ + if(state == -1) + return -1; // decode error, return failure + /* It also might be nice to report a condition where the minimum is + * non-zero, i.e. an error has been corrected. It probably shouldn't + * be a permanent failure, though. + * + * DISSECTOR_ASSERT(hd[state] == 0); + */ + + /* append dibit onto output buffer */ + d = b >> 2; // dibit ctr + if (d < 48) { + buf[d >> 2] |= state << (6 - ((d%4) * 2)); + } + } + return 0; + } + +int bd_bridge (int payload[196], uint8_t decoded[12]) +{ + std::vector vc(196,0); + int ec = 69; //initialize error value + + //gather payload from tsbk handler and pass it to the block_deinterleave function + //and then return its payload back to the tsbk handler + + //initialize our decoded byte buffer with zeroes + for (int i = 0; i < 12; i++) + { + decoded[i] = 0; + } + + //convert/load payload into a vector vc to pass to block_deinterleave + for (int i = 0; i < 196; i++) + { + vc[i] = payload[i]; + } + int block_count = 0; // + unsigned int start = block_count*196; + + ec = block_deinterleave(vc, start, decoded); + + return ec; +} + +//modified from the LEH ComputeCrcCCITT to accept variable len buffer bits +uint16_t ComputeCrcCCITT16b(const uint8_t buf[], unsigned int len) +{ + uint32_t i; + uint16_t CRC = 0x0000; /* Initialization value = 0x0000 */ + /* Polynomial x^16 + x^12 + x^5 + 1 + * Normal = 0x1021 + * Reciprocal = 0x0811 + * Reversed = 0x8408 + * Reversed reciprocal = 0x8810 */ + uint16_t Polynome = 0x1021; + for(i = 0; i < len; i++) + { + if(((CRC >> 15) & 1) ^ (buf[i] & 1)) + { + CRC = (CRC << 1) ^ Polynome; + } + else + { + CRC <<= 1; + } + } + + /* Invert the CRC */ + CRC ^= 0xFFFF; + + /* Return the CRC */ + return CRC; +} /* End ComputeCrcCCITT() */ + + +//modified from crc12_ok to run a quickie on 16 instead +static uint16_t crc16_ok(const uint8_t bits[], unsigned int len) { + uint16_t crc = 0; + // fprintf (stderr, "\n LEN = %d", len); + for (int i = 0; i < 16; i++) + { + crc = crc << 1; + crc = crc | bits[i+len]; + } + // uint16_t check = crc16b(bits,len); + uint16_t check = ComputeCrcCCITT16b(bits, len); + + if (crc == check) + { + //fprintf (stderr, " CRC = %04X %04X", crc, check); + return (0); + } + else + { + //fprintf (stderr, " CRC = %04X %04X", crc, check); + return (-1); + } + +} + +//TSBK/LCCH CRC16 x-bit bridge to crc16b and crc16b_okay, wip, may need multi pdu format as well +int crc16_lb_bridge (int payload[190], int len) +{ + int err = -2; + uint16_t crc = -2; + uint8_t buf[190] = {0}; + + for (int i = 0; i < len+16; i++) //add +16 here so we load the entire frame but only run crc on the len portion + { + buf[i] = payload[i]; + } + err = crc16_ok(buf, len); + return (err); +} + +//borrowing crc12 from OP25 +static uint16_t crc12(const uint8_t bits[], unsigned int len) { + uint16_t crc=0; + static const unsigned int K = 12; + //g12(x) = x12 + x11 + x7 + x4 + x2 + x + 1 + static const uint8_t poly[K+1] = {1,1,0,0,0,1,0,0,1,0,1,1,1}; // p25 p2 crc 12 poly + uint8_t buf[256]; + if (len+K > sizeof(buf)) { + //fprintf (stderr, "crc12: buffer length %u exceeds maximum %u\n", len+K, sizeof(buf)); + return 0; + } + memset (buf, 0, sizeof(buf)); + for (unsigned int i=0; i 199711L +#define register // Deprecated in C++11. +#endif // #if __cplusplus > 199711L +//end ifdef to fix compiler warnings #include "p25p1_check_hdu.h" #include "ReedSolomon.hpp" diff --git a/src/p25p1_check_ldu.cpp b/src/p25p1_check_ldu.cpp index cb198a9..94191a4 100644 --- a/src/p25p1_check_ldu.cpp +++ b/src/p25p1_check_ldu.cpp @@ -1,3 +1,8 @@ +//remove below ifdef if problems arise +#if __cplusplus > 199711L +#define register // Deprecated in C++11. +#endif // #if __cplusplus > 199711L +//end ifdef to fix compiler warnings #include "p25p1_check_ldu.h" diff --git a/src/p25p1_hdu.c b/src/p25p1_hdu.c index 36d0b4f..bccd092 100644 --- a/src/p25p1_hdu.c +++ b/src/p25p1_hdu.c @@ -474,14 +474,14 @@ processHDU(dsd_opts* opts, dsd_state* state) //set vc counter to 0 state->p25vc = 0; - if (state->payload_algid != 0x80) //print on payload == 1 + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) //print on payload == 1 { fprintf (stderr, "%s", KYEL); fprintf (stderr, " HDU ALG ID: 0x%02X KEY ID: 0x%02X MI: 0x%08llX%08llX%02llX MFID: 0x%02X", algidhex, kidhex, mihex1, mihex2, mihex3, state->payload_mfid); fprintf (stderr, "%s", KNRM); } - if (state->payload_algid != 0x80) //print on payload == 1 + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) //print on payload == 1 { fprintf (stderr, "%s", KRED); fprintf (stderr, " ENC \n"); diff --git a/src/p25p1_ldu2.c b/src/p25p1_ldu2.c index d5fe4d2..1f4c177 100644 --- a/src/p25p1_ldu2.c +++ b/src/p25p1_ldu2.c @@ -376,14 +376,14 @@ processLDU2 (dsd_opts * opts, dsd_state * state) } - if (state->payload_algid != 0x80) //print on payload == 1 + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) //print on payload == 1 { fprintf (stderr, "%s", KYEL); fprintf (stderr, " LDU2 ALG ID: 0x%02X KEY ID: 0x%02X MI: 0x%08llX%08llX%02llX", algidhex, kidhex, mihex1, mihex2, mihex3); fprintf (stderr, "%s", KNRM); } - if (state->payload_algid != 0x80) //print on payload == 1 + if (state->payload_algid != 0x80 && state->payload_algid != 0x0) //print on payload == 1 { fprintf (stderr, "%s", KRED); fprintf (stderr, " ENC \n"); diff --git a/src/p25p1_tsbk.c b/src/p25p1_tsbk.c new file mode 100644 index 0000000..d5ff484 --- /dev/null +++ b/src/p25p1_tsbk.c @@ -0,0 +1,122 @@ +/*------------------------------------------------------------------------------- + * p25p1_tsbk.c + * P25 TSBK Handler for Network Status Broadcast (WACN, SYSID, NAC/CC), etc. + * + * LWVMOBILE + * 2022-09 DSD-FME Florida Man Edition + *-----------------------------------------------------------------------------*/ + +#include "dsd.h" +void processTSBK(dsd_opts * opts, dsd_state * state) +{ + //process TSDU/TSBK and look for relevant data (WACN, SYSID, other goodies later on) + int tsbkbit[196] = {0}; //tsbk bit array, 196 trellis encoded bits + int tsbk_dibit[98] = {0}; + + int dibit = 0; + uint8_t tsbk_byte[12] = {0}; //12 byte return from bd_bridge (block_deinterleave) + int tsbk_decoded_bits[190] = {0}; //decoded bits from tsbk_bytes for sending to crc16_lb_bridge + int i, j, k, b; + int ec = -2; //error value returned from (block_deinterleave) + int checksum = -2; //checksum returned from crc16, 0 is valid, anything else is invalid + int skipdibit = 14; //initial status dibit will occur at 14, then add 35 each time it occurs + for (j = 0; j < 3; j++) + { + k = 0; + //originally skipped 303 dibits, instead we collect three reps of 101 (98 valid dibits) + for (i = 0; i < 101; i++) + { + //rip dibits directly from the symbol capture file + //this method causes issues for some reason, probably to do with mixing reading types? don't know why? + // if (opts->audio_in_type == 4) //4 + // { + // dibit = fgetc(opts->symbolfile); + // } + // else + // { + // dibit = getDibit(opts, state); + // if (opts->inverted_p2 == 1) + // { + // dibit = (dibit ^ 2); + // } + // } + + dibit = getDibit(opts, state); + + if (i+(j*101) != skipdibit) // + { + tsbk_dibit[k] = dibit; + tsbkbit[k*2] = (dibit >> 1) & 1; + tsbkbit[k*2+1] = (dibit & 1); + k++; + } + + if (i+(j*101) == skipdibit) // + { + skipdibit += 35; + } + + } + + //send tsbkbit to block_deinterleave and return tsbk_byte + ec = bd_bridge(tsbkbit, tsbk_byte); + + //too many bit manipulations! + k = 0; + for (i = 0; i < 12; i++) + { + for (j = 0; j < 8; j++) + { + tsbk_decoded_bits[k] = ((tsbk_byte[i] << j) & 0x80) >> 7; + k++; + } + } + + //crc check works now using the ComputeCrcCCITT method and not the OP25 method (bug?) + int err = -2; + err = crc16_lb_bridge(tsbk_decoded_bits, 80); + if (ec != 0) + { + //fprintf (stderr, "BAD BLOCK DEINTERLEAVE"); + } + if (err != 0) + { + //fprintf (stderr, "BAD CRC16"); + } + + //set our WACN and SYSID here now that we have valid ec and crc/checksum + if (err == 0 && ec == 0 && tsbk_byte[0] == 0x3B) + { + //only set IF these values aren't already hard set by the user + if (state->p2_hardset == 0) + { + state->p2_wacn = (tsbk_byte[3] << 12) | (tsbk_byte[4] << 4) | (tsbk_byte[5] >> 4); + state->p2_sysid = ((tsbk_byte[5] & 0xF) << 8) | tsbk_byte[6]; + fprintf (stderr, "%s",KCYN); + fprintf (stderr, "\n Network Status Broadcast TSBK \n"); + fprintf (stderr, " WACN [%05llX] SYSID [%03llX] NAC [%03llX]", state->p2_wacn, state->p2_sysid, state->p2_cc); + fprintf (stderr, "%s ", KNRM); + //state->p2_cc = state->nac; //set in frame_sync line 450 instead after valid bch check + } + + } + + // print dump for debug eval + if (opts->payload == 1) + { + fprintf (stderr, "%s",KCYN); + fprintf (stderr, "\n P25 TSBK Byte Payload "); + fprintf (stderr, " BD = %d CRC = %d \n ", ec, err); + for (i = 0; i < 12; i++) + { + fprintf (stderr, "[%02X]", tsbk_byte[i]); + } + fprintf (stderr, "%s ", KNRM); + } + + //reset for next rep + ec = -2; + checksum = -2; + } + fprintf (stderr, "\n"); //line break on loop end +}