├── include ├── git_ver.h ├── colors.h ├── banner.h └── m17.h ├── key ├── aes_key.txt ├── sig_pri_key.txt └── sig_pub_key.txt ├── docs ├── m17-fme1.png ├── m17-fme2.png ├── pavucontrol_plumbing1.png ├── pavucontrol_plumbing2.png ├── gr-m17_float_symbol_filesink.png ├── Audio_Plumbing.md └── Install_Notes.md ├── src ├── git_ver.c.in ├── fec │ ├── crc16.c │ ├── convolution.c │ └── golay.c ├── utils │ ├── z.cpp │ ├── time.c │ └── utils.c ├── modem │ ├── test_pattern_generator.c │ ├── filter.c │ └── rfa_modulator.c ├── net │ ├── net_rig.c │ ├── net_tcp.c │ └── net_udp.c ├── encoder │ ├── m17_csd_encoder.c │ ├── m17_brt_encoder.c │ └── m17_ecdsa_encoder.c ├── decoder │ ├── m17_lich_decoder.c │ ├── m17_csd_decoder.c │ ├── m17_lsf_demodulator.c │ ├── m17_brt_demodulator.c │ ├── m17_str_demodulator.c │ ├── m17_pkt_demodulator.c │ └── m17_lsf_decoder.c ├── io │ ├── oss.c │ ├── audio.c │ ├── pulse.c │ ├── wav.c │ └── pulse_devices.c └── encryption │ └── ecdsa.c ├── samples ├── 8k1_voice.wav ├── m17_clear_voice_wav.wav ├── m17_sms_pkt_data_wav.wav ├── m17_clear_voice_dibit.bin ├── m17_clear_voice_float.sym ├── m17_sms_pkt_data_dibit.bin └── m17_sms_pkt_data_float_symbol_output.sym ├── scripts ├── cygwin_bat │ ├── example_options.txt │ ├── readme_cygwin_bat.txt │ ├── start-m17-fme.bat │ └── complete_usage_options.txt ├── install.sh ├── virtualsink.sh ├── rebuild.sh ├── cyg_rebuild.sh ├── download-and-install-ubuntu2404lts.sh ├── download-and-install-rhel.sh ├── download-and-install-deb.sh ├── download-and-install-arch.sh ├── download-and-install-cygwin.sh ├── download-and-install-macos.sh └── start-m17-fme-linux-terminal.sh ├── .gitmodules ├── .gitignore ├── cmake ├── FindLibSndFile.cmake ├── FindCODEC2.cmake ├── cmake_uninstall.cmake.in ├── git_revision.cmake.in ├── FindPulseAudio.cmake └── git_revision.cmake ├── CMakeLists.txt └── README.md /include/git_ver.h: -------------------------------------------------------------------------------- 1 | extern const char GIT_TAG[]; 2 | -------------------------------------------------------------------------------- /key/aes_key.txt: -------------------------------------------------------------------------------- 1 | 1234567890ABCDEF7777777777777777FEDCBA09876543218888888888888888 -------------------------------------------------------------------------------- /key/sig_pri_key.txt: -------------------------------------------------------------------------------- 1 | 66a87453dfef341b68907e51c0fdf9cb5ab9f53e6240d7a4b00398a3fee864cf -------------------------------------------------------------------------------- /docs/m17-fme1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/docs/m17-fme1.png -------------------------------------------------------------------------------- /docs/m17-fme2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/docs/m17-fme2.png -------------------------------------------------------------------------------- /src/git_ver.c.in: -------------------------------------------------------------------------------- 1 | #define _GIT_TAG "@GIT_TAG@" 2 | const char GIT_TAG[] = _GIT_TAG; 3 | -------------------------------------------------------------------------------- /samples/8k1_voice.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/8k1_voice.wav -------------------------------------------------------------------------------- /scripts/cygwin_bat/example_options.txt: -------------------------------------------------------------------------------- 1 | TODO: Add things to this after testing the installer portion. 2 | -------------------------------------------------------------------------------- /docs/pavucontrol_plumbing1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/docs/pavucontrol_plumbing1.png -------------------------------------------------------------------------------- /docs/pavucontrol_plumbing2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/docs/pavucontrol_plumbing2.png -------------------------------------------------------------------------------- /samples/m17_clear_voice_wav.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/m17_clear_voice_wav.wav -------------------------------------------------------------------------------- /samples/m17_sms_pkt_data_wav.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/m17_sms_pkt_data_wav.wav -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/micro-ecc"] 2 | path = src/micro-ecc 3 | url = https://github.com/M17-Project/micro-ecc 4 | -------------------------------------------------------------------------------- /samples/m17_clear_voice_dibit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/m17_clear_voice_dibit.bin -------------------------------------------------------------------------------- /samples/m17_clear_voice_float.sym: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/m17_clear_voice_float.sym -------------------------------------------------------------------------------- /samples/m17_sms_pkt_data_dibit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/m17_sms_pkt_data_dibit.bin -------------------------------------------------------------------------------- /docs/gr-m17_float_symbol_filesink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/docs/gr-m17_float_symbol_filesink.png -------------------------------------------------------------------------------- /key/sig_pub_key.txt: -------------------------------------------------------------------------------- 1 | 18b221c715ed5a5ab1ac484245c3a217d43e75ed12c9197ff2ca0603700ead5d84935d40d52d490877793fdc047d63efe5cd00eaa1afd61ac04330525c314b4c -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | cd .. 4 | mkdir build 5 | cd build 6 | cmake .. 7 | make -j $(nproc) 8 | sudo make install 9 | sudo ldconfig -------------------------------------------------------------------------------- /samples/m17_sms_pkt_data_float_symbol_output.sym: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwvmobile/m17-fme/HEAD/samples/m17_sms_pkt_data_float_symbol_output.sym -------------------------------------------------------------------------------- /scripts/cygwin_bat/readme_cygwin_bat.txt: -------------------------------------------------------------------------------- 1 | These .bat files are ONLY for use on the portable cygwin builds, do not use these in any other build environments! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.[ao] 2 | *.so* 3 | *.dylib 4 | build 5 | *.sh 6 | *.csv 7 | examples 8 | *.bin 9 | *.sym 10 | *.wav 11 | *.ans 12 | *.patch 13 | *.zip 14 | result -------------------------------------------------------------------------------- /scripts/virtualsink.sh: -------------------------------------------------------------------------------- 1 | pactl load-module module-null-sink sink_name=m17_sink1 sink_properties=device.description=M17_Sink1 2 | pactl load-module module-null-sink sink_name=m17_sink2 sink_properties=device.description=M17_Sink2 -------------------------------------------------------------------------------- /scripts/rebuild.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | clear 4 | printf "M17 Project - Florida Man Edition 5 | Automatic Git Pull and Rebuild\n\n" 6 | sleep 1 7 | ##pull latest from git 8 | git pull 9 | ##cd into your build folder## 10 | cd .. 11 | cd build 12 | cmake .. 13 | make -j $(nproc) 14 | sudo make install 15 | sudo ldconfig -------------------------------------------------------------------------------- /scripts/cyg_rebuild.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | clear 4 | printf "M17-FME Project M17 - Florida Man Edition 5 | Automatic Git Pull and Rebuild for Cygwin\n\n" 6 | sleep 1 7 | ##Open your clone folder## 8 | git pull 9 | sleep 2 10 | ##cd into your build folder## 11 | cd .. 12 | cd build 13 | cmake .. 14 | make -j $(nproc) 15 | make install 16 | -------------------------------------------------------------------------------- /cmake/FindLibSndFile.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Libsndfile 2 | # Once done this will define 3 | # 4 | # LIBSNDFILE_FOUND - System has LIBSNDFILE 5 | # LIBSNDFILE_INCLUDE_DIR - The SNDFILE include directory 6 | # LIBSNDFILE_LIBRARY - The library needed to use SNDFILE 7 | # 8 | 9 | find_path(LIBSNDFILE_INCLUDE_DIR sndfile.h) 10 | 11 | SET(LIBSNDFILE_NAMES ${LIBSNDFILE_NAMES} sndfile libsndfile) 12 | FIND_LIBRARY(LIBSNDFILE_LIBRARY NAMES ${LIBSNDFILE_NAMES}) 13 | 14 | include(FindPackageHandleStandardArgs) 15 | find_package_handle_standard_args(LibSndFile DEFAULT_MSG LIBSNDFILE_LIBRARY 16 | LIBSNDFILE_INCLUDE_DIR) 17 | -------------------------------------------------------------------------------- /src/fec/crc16.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * crc16.c 3 | * M17 Project - CRC16 Checksum 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | uint16_t crc16(const uint8_t *in, const uint16_t len) 12 | { 13 | uint32_t crc = 0xFFFF; 14 | uint16_t poly = 0x5935; 15 | for(uint16_t i=0; i 8 | # http://academic.cleardefinition.com 9 | # Iowa State University HCI Graduate Program/VRAC 10 | # 11 | # Copyright Iowa State University 2009-2010. 12 | # Distributed under the Boost Software License, Version 1.0. 13 | # (See accompanying file LICENSE_1_0.txt or copy at 14 | # http://www.boost.org/LICENSE_1_0.txt) 15 | 16 | set(HEAD_HASH) 17 | 18 | file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) 19 | 20 | string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) 21 | if(HEAD_CONTENTS MATCHES "ref") 22 | # named branch 23 | string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") 24 | if(EXISTS "@GIT_DIR@/${HEAD_REF}") 25 | configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 26 | elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") 27 | configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 28 | set(HEAD_HASH "${HEAD_REF}") 29 | endif() 30 | else() 31 | # detached HEAD 32 | configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) 33 | endif() 34 | 35 | if(NOT HEAD_HASH) 36 | file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) 37 | string(STRIP "${HEAD_HASH}" HEAD_HASH) 38 | endif() 39 | -------------------------------------------------------------------------------- /src/utils/time.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * time.c 3 | * M17 Project - Time and Date Functions 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | //WARNING: time(NULL) is an expensive lag causing call when we need low latency (unknown reason) 12 | 13 | //get HHmmss timestamp no colon (file operations) 14 | char * get_time() 15 | { 16 | char * curr = calloc(7, sizeof(char)); 17 | time_t t = time(NULL); 18 | struct tm * ptm = localtime(& t); 19 | sprintf(curr,"%02d%02d%02d", ptm->tm_hour, ptm->tm_min, ptm->tm_sec); 20 | return curr; 21 | } 22 | 23 | //get HH:mm:ss timestamp with colon (display / console output) 24 | char * get_time_n(time_t t) 25 | { 26 | char * curr = calloc(9, sizeof(char)); 27 | struct tm * ptm = localtime(& t); 28 | sprintf(curr, "%02d:%02d:%02d", ptm->tm_hour, ptm->tm_min, ptm->tm_sec); 29 | return curr; 30 | } 31 | 32 | //get YYYYMMDD without hyphen (file operations) 33 | char * get_date() 34 | { 35 | char * curr = calloc(25, sizeof(char)); 36 | time_t t = time(NULL); 37 | struct tm * ptm = localtime(& t); 38 | sprintf(curr,"%04d%02d%02d", ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday); 39 | return curr; 40 | } 41 | 42 | //get YYYY-MM-DD with hyphen (display / console output) 43 | char * get_date_n(time_t t) 44 | { 45 | char * curr = calloc(27, sizeof(char)); 46 | struct tm * ptm = localtime(& t); 47 | sprintf(curr, "%04d-%02d-%02d", ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday); 48 | return curr; 49 | } 50 | -------------------------------------------------------------------------------- /scripts/download-and-install-cygwin.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | cdir=$(pwd) 4 | clear 5 | printf "M17 Project: Florida Man Edition - Auto Installer For Cygwin. 6 | This will install the required and recommended packages, clone, build, and install M17-FME. 7 | This has been tested on Cygwin x86-64 as of 20250907. This may take a while! 8 | Please confirm that you wish to preceed by entering y below.\n\n" 9 | read -p "Do you wish to proceed? y/N " ANSWER 10 | ANSWER=$(printf "$ANSWER"|tr '[:upper:]' '[:lower:]') 11 | if [ "$ANSWER" = "y" ]; then 12 | 13 | #is this needed? 14 | LD_LIBRARY_PATH=/usr/local/lib 15 | export LD_LIBRARY_PATH 16 | echo $LD_LIBRARY_PATH 17 | 18 | #CODEC2 19 | cd $cdir 20 | printf "Installing codec2\n Please wait!\n" 21 | git clone https://github.com/drowe67/codec2.git 22 | cd codec2 23 | mkdir build 24 | cd build 25 | cmake .. 26 | make -j $(nproc) 27 | make install 28 | 29 | # RTL-SDR #leave here in case this is added in later on 30 | # cd $cdir 31 | # printf "Installing RTL-SDR\n Please wait!\n" 32 | # git clone https://github.com/lwvmobile/rtl-sdr.git 33 | # cd rtl-sdr 34 | # mkdir build 35 | # cd build 36 | # cmake .. 37 | # make -j $(nproc) 38 | # make install 39 | 40 | #M17-FME 41 | cd $cdir 42 | printf "Installing M17-FME\n Please wait!\n" 43 | git clone --recursive https://github.com/lwvmobile/m17-fme.git 44 | cd m17-fme 45 | mkdir build 46 | cd build 47 | cmake .. 48 | make -j $(nproc) 49 | make install 50 | cd $cdir 51 | 52 | #call the cyg_portable script 53 | sh m17-fme/scripts/cyg_portable.sh 54 | 55 | printf "Any issues, Please report to:\nhttps://github.com/lwvmobile/m17-fme/issues \n\n" 56 | 57 | else 58 | printf "Thank you, have a nice day!\n\n" 59 | fi -------------------------------------------------------------------------------- /src/net/net_rig.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * net_rig.c 3 | * M17 Project - RIGCTL Remote Functions 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | bool rigctl_set_frequency (int sockfd, long int freq) 12 | { 13 | char buf[BUFSIZE]; 14 | 15 | sprintf (buf, "F %ld\n", freq); 16 | tcp_socket_send(sockfd, buf); 17 | tcp_socket_receive(sockfd, buf); 18 | 19 | if (strcmp(buf, "RPRT 1\n") == 0 ) 20 | return false; 21 | 22 | return true; 23 | } 24 | 25 | bool rigctl_set_modulation_nfm (int sockfd, int bandwidth) 26 | { 27 | char buf[BUFSIZE]; 28 | //SDR++ has changed the token from FM to NFM, and back again, so just try both 29 | sprintf (buf, "M NFM %d\n", bandwidth); 30 | tcp_socket_send(sockfd, buf); 31 | tcp_socket_receive(sockfd, buf); 32 | 33 | //if it fails the first time, send the other token instead 34 | if (strcmp(buf, "RPRT 1\n") == 0 ) 35 | { 36 | sprintf (buf, "M FM %d\n", bandwidth); 37 | tcp_socket_send(sockfd, buf); 38 | tcp_socket_receive(sockfd, buf); 39 | } 40 | 41 | if (strcmp(buf, "RPRT 1\n") == 0 ) 42 | return false; 43 | 44 | return true; 45 | } 46 | 47 | bool rigctl_set_modulation_wfm (int sockfd, int bandwidth) 48 | { 49 | char buf[BUFSIZE]; 50 | sprintf (buf, "M WFM %d\n", bandwidth); 51 | tcp_socket_send(sockfd, buf); 52 | tcp_socket_receive(sockfd, buf); 53 | 54 | if (strcmp(buf, "RPRT 1\n") == 0 ) 55 | return false; 56 | 57 | return true; 58 | } 59 | 60 | bool tune_to_frequency (Super * super, long int frequency) 61 | { 62 | bool err = false; 63 | 64 | //RIGCTL Remote 65 | if (super->opts.use_rig_remote && super->opts.rig_remote_open) 66 | { 67 | err = rigctl_set_modulation_nfm (super->opts.rig_remote_sock, 12000); 68 | err = rigctl_set_frequency (super->opts.rig_remote_sock, frequency); 69 | } 70 | 71 | return err; 72 | } -------------------------------------------------------------------------------- /include/banner.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * banner.h 3 | * M17 Project - Just a Big Gaudy Banner and Dumb Swank 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | //Basic Banner 10 | static char * M17FME_banner[9] = { 11 | " ", 12 | " (C)", //NOTE: The (C) symbol is not meant to be a 'copyright, its a toggle switch indicator 13 | " ███╗ ███╗ ███╗ ███████╗ ███████╗███╗ ███╗███████╗", 14 | " ████╗ ████║ ████║ ╚════██║ ██╔════╝████╗ ████║██╔════╝", 15 | " ██╔████╔██║ ██╔██║ ██╔╝ █████╗ ██╔████╔██║█████╗ ", 16 | " ██║╚██╔╝██║ ╚═╝██║ ██╔╝ ██╔══╝ ██║╚██╔╝██║██╔══╝ ", 17 | " ██║ ╚═╝ ██║ ███████╗ ██╔╝ ██║ ██║ ╚═╝ ██║███████╗", 18 | " ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝", 19 | "M17 Project - Florida Man Edition " 20 | }; 21 | 22 | //Color Segmented Banner 23 | static char * M_banner[9] = { 24 | " ", 25 | " ", 26 | " ███╗ ███╗ ", 27 | " ████╗ ████║ ", 28 | " ██╔████╔██║ ", 29 | " ██║╚██╔╝██║ ", 30 | " ██║ ╚═╝ ██║ ", 31 | " ╚═╝ ╚═╝ ", 32 | " " 33 | }; 34 | 35 | static char * S_banner[9] = { 36 | " ", 37 | " ", 38 | " ███╗ ███████╗ ", 39 | " ████║ ╚════██║ ", 40 | " ██╔██║ ██╔╝ ", 41 | " ╚═╝██║ ██╔╝ ", 42 | " ███████╗ ██╔╝ ", 43 | " ╚══════╝ ╚═╝ ", 44 | " " 45 | }; 46 | 47 | static char * FME_banner[9] = { 48 | " ", 49 | " (C)", //NOTE: The (C) symbol is not meant to be a 'copyright, its a toggle switch indicator 50 | " ███████╗███╗ ███╗███████╗ ", 51 | " ██╔════╝████╗ ████║██╔════╝ ", 52 | " █████╗ ██╔████╔██║█████╗ ", 53 | " ██╔══╝ ██║╚██╔╝██║██╔══╝ ", 54 | " ██║ ██║ ╚═╝ ██║███████╗ ", 55 | " ╚═╝ ╚═╝ ╚═╝╚══════╝ ", 56 | " " 57 | }; -------------------------------------------------------------------------------- /src/net/net_tcp.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * net_tcp.c 3 | * M17 Project - Network Functions for TCP 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | struct sockaddr_in address; 12 | 13 | int tcp_socket_connect (char *hostname, int portno) 14 | { 15 | int sockfd; 16 | struct sockaddr_in serveraddr; 17 | struct hostent *server; 18 | 19 | 20 | /* socket: create the socket */ 21 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 22 | if (sockfd < 0) 23 | { 24 | fprintf(stderr,"ERROR opening socket\n"); 25 | error("ERROR opening socket"); 26 | } 27 | 28 | 29 | /* gethostbyname: get the server's DNS entry */ 30 | server = gethostbyname(hostname); 31 | if (server == NULL) 32 | { 33 | fprintf(stderr,"ERROR, no such host as %s\n", hostname); 34 | //exit(0); 35 | return (0); //return 0, check on other end and configure pulse input 36 | } 37 | 38 | /* build the server's Internet address */ 39 | bzero((char *) &serveraddr, sizeof(serveraddr)); 40 | serveraddr.sin_family = AF_INET; 41 | bcopy((char *)server->h_addr, 42 | (char *)&serveraddr.sin_addr.s_addr, server->h_length); 43 | serveraddr.sin_port = htons(portno); 44 | 45 | /* connect: create a connection with the server */ 46 | if (connect(sockfd, (const struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) 47 | { 48 | fprintf(stderr,"ERROR opening socket\n"); 49 | return (0); 50 | } 51 | 52 | return sockfd; 53 | } 54 | 55 | bool tcp_socket_send(int sockfd, char *buf) 56 | { 57 | int n; 58 | 59 | n = write(sockfd, buf, strlen(buf)); 60 | if (n < 0) 61 | error("ERROR writing to socket"); 62 | return true; 63 | } 64 | 65 | bool tcp_socket_receive(int sockfd, char *buf) 66 | { 67 | int n; 68 | 69 | n = read(sockfd, buf, BUFSIZE); 70 | if (n < 0) 71 | error("ERROR reading from socket"); 72 | buf[n]= '\0'; 73 | return true; 74 | } 75 | 76 | bool tcp_snd_audio_source_open (Super * super) 77 | { 78 | bool err = false; 79 | super->snd_src_in.audio_in_file = sf_open_fd(super->opts.tcp_input_sock, SFM_READ, super->snd_src_in.audio_in_file_info, 0); 80 | if (super->snd_src_in.audio_in_file != NULL) 81 | err = true; 82 | 83 | return err; 84 | } -------------------------------------------------------------------------------- /src/modem/filter.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * filter.c 3 | * M17 Project - Filter Related Functions 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | void hpfilter_init(hpfilter *filter, float cutoffFreqHz, float sampleTimeS) 13 | { 14 | 15 | float RC=0.0; 16 | RC=1.0/(2*PI*cutoffFreqHz); 17 | 18 | filter->coef=RC/(sampleTimeS+RC); 19 | 20 | filter->v_in[0]=0.0; 21 | filter->v_in[1]=0.0; 22 | 23 | filter->v_out[0]=0.0; 24 | filter->v_out[1]=0.0; 25 | 26 | } 27 | 28 | float hpfilter_update(hpfilter *filter, float v_in) 29 | { 30 | 31 | filter->v_in[1]=filter->v_in[0]; 32 | filter->v_in[0]=v_in; 33 | 34 | filter->v_out[1]=filter->v_out[0]; 35 | filter->v_out[0]=filter->coef * (filter->v_in[0] - filter->v_in[1]+filter->v_out[1]); 36 | 37 | return (filter->v_out[0]); 38 | 39 | } 40 | 41 | //high pass filter for Codec2 Digital Audio Output 42 | void hpfilter_d(Super * super, short * input, int len) 43 | { 44 | int i; 45 | 46 | //apply filtering 47 | for (i = 0; i < len; i++) 48 | input[i] = hpfilter_update(&super->hpf_d, input[i]); 49 | 50 | //boost gain by factor of 1.75f to compensate for audio level drop 51 | for (i = 0; i < len; i++) 52 | input[i] *= 1.75f; 53 | } 54 | 55 | //10x Upscale and RRC filtering lifted from M17_Implementations / libM17 56 | void upscale_and_rrc_output_filter (int * output_symbols, float * mem, short * baseband) 57 | { 58 | int i = 0; int j = 0; int k = 0; int x = 0; 59 | float mac = 0.0f; 60 | for (i = 0; i < 192; i++) 61 | { 62 | mem[0] = (float)output_symbols[i] * 7168.0f; 63 | 64 | for (j = 0; j < 10; j++) 65 | { 66 | 67 | mac = 0.0f; 68 | 69 | //calc the sum of products 70 | for (k = 0; k < 81; k++) 71 | mac += mem[k]*m17_rrc[k]*sqrtf(10.0); 72 | 73 | //shift the delay line right by 1 74 | for (k = 80; k > 0; k--) 75 | mem[k] = mem[k-1]; 76 | 77 | mem[0] = 0.0f; 78 | 79 | baseband[x++] = (short)mac; 80 | } 81 | } 82 | } 83 | 84 | short rrc_input_filter(float * mem, short sample) 85 | { 86 | int i = 0; 87 | int len = 79; 88 | float sum = 0.0f; 89 | float out = 0.0f; 90 | float gain = 10.0f; 91 | 92 | //push memory 93 | for (i = 0; i < (len-1); i++) 94 | mem[i] = mem[i+1]; 95 | mem[len-1] = (float)sample; 96 | 97 | for (i = 0; i < len; i++) 98 | sum += m17_input_rrc[i] * mem[i]; 99 | 100 | out = sum / gain; 101 | 102 | return (short)out; 103 | } 104 | 105 | void asljdf() 106 | { 107 | stfu(); 108 | } -------------------------------------------------------------------------------- /src/encoder/m17_csd_encoder.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_csd_encoder.c 3 | * M17 Project - Callsign Data Encoder 4 | * 5 | * LWVMOBILE 6 | * 2024-11 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | void encode_callsign_data(Super * super, char * d40, char * s40, unsigned long long int * dst, unsigned long long int * src) 13 | { 14 | 15 | //quell defined but not used warnings from m17.h 16 | stfu (); 17 | 18 | int i, j; 19 | 20 | //reset each time for user configurable values during session 21 | *dst = 0; 22 | *src = 0; 23 | 24 | //NOTE: When a # is passed, if not a known reserved string value, like #BROADCAST, 25 | //the user must pass the raw encoded CSD value >= 0xEE6B28000000 after the # 26 | 27 | //Source 28 | if (super->m17e.srcs[0] == '#') 29 | { 30 | //scan string to value, excluding # 31 | sscanf (super->m17e.srcs+1, "%llX", src); 32 | *src &= 0xFFFFFFFFFFFF; //truncate to 48-bits 33 | } 34 | 35 | else if (super->m17e.srcs[0] != 0) 36 | sprintf (s40, "%s", super->m17e.srcs); 37 | 38 | //Destination 39 | if (strcmp (super->m17e.dsts, "#BROADCAST") == 0) 40 | *dst = 0xFFFFFFFFFFFF; 41 | 42 | else if (strcmp (super->m17e.dsts, "#BROADCAS") == 0) 43 | *dst = 0xFFFFFFFFFFFF; 44 | 45 | else if (strcmp (super->m17e.dsts, "@BROADCAS") == 0) 46 | *dst = 0xFFFFFFFFFFFF; 47 | 48 | else if (strcmp (super->m17e.dsts, "#ALL") == 0) 49 | *dst = 0xFFFFFFFFFFFF; 50 | 51 | else if (strcmp (super->m17e.dsts, "@ALL") == 0) 52 | *dst = 0xFFFFFFFFFFFF; 53 | 54 | else if (strcmp (super->m17e.dsts, "BROADCAST") == 0) 55 | *dst = 0xFFFFFFFFFFFF; 56 | 57 | else if (strcmp (super->m17e.dsts, "ALL") == 0) 58 | *dst = 0xFFFFFFFFFFFF; 59 | 60 | else if (super->m17e.dsts[0] == '#') 61 | { 62 | //scan string to value, excluding # 63 | sscanf (super->m17e.dsts+1, "%llX", dst); 64 | *dst &= 0xFFFFFFFFFFFF; //truncate to 48-bits 65 | } 66 | 67 | else if (super->m17e.dsts[0] == '@') 68 | { 69 | //scan string to value, excluding @ 70 | sscanf (super->m17e.dsts+1, "%llX", dst); 71 | *dst &= 0xFFFFFFFFFFFF; //truncate to 48-bits 72 | } 73 | 74 | else if (super->m17e.dsts[0] != 0) 75 | sprintf (d40, "%s", super->m17e.dsts); 76 | 77 | //if dst and src not a reserved address, encode them now 78 | if (*dst < 0xEE6B28000000) 79 | { 80 | for(i = strlen((const char*)d40)-1; i >= 0; i--) 81 | { 82 | for(j = 0; j < 40; j++) 83 | { 84 | if(d40[i]==b40[j]) 85 | { 86 | *dst = *dst *40 + j; 87 | break; 88 | } 89 | } 90 | } 91 | } 92 | 93 | if (*src < 0xEE6B28000000) 94 | { 95 | for(i = strlen((const char*)s40)-1; i >= 0; i--) 96 | { 97 | for(j = 0; j < 40; j++) 98 | { 99 | if(s40[i]==b40[j]) 100 | { 101 | *src = *src * 40 + j; 102 | break; 103 | } 104 | } 105 | } 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /scripts/cygwin_bat/start-m17-fme.bat: -------------------------------------------------------------------------------- 1 | @REM Project M17: Florida Man Edition 2 | @REM M17-FME Cygwin Windows Portable Builds 3 | @REM 4 | @REM The source code of this software can be downloaded here: 5 | @REM https://github.com/lwvmobile/m17-fme 6 | @REM 7 | @REM If you paid for, or were otherwise fleeced for this software, DEMAND YOUR MONEY BACK! 8 | @REM 9 | @REM This software is designed to be used with an SDR receiver (like SDR++ or SDR#) with TCP input, 10 | @REM RTL Input, media file playback over virtual cables or null-sinks, or a Discriminator Tap input 11 | @REM 12 | @REM The complete options list can be seen by viewing complete_usage_options.txt 13 | @REM Many examples can be found in example_options.txt 14 | @REM 15 | @REM Enjoy! 16 | 17 | @REM Regarding the options string (or any string in .bat files) 18 | @REM If your string may contain spaces, set the first '"' before options, and then an ending '"' 19 | @REM Incorrect: set options="-V -i pulsevx -o pulserf " 20 | @REM Correct: set "options=-V -i pulsevx -o pulserf " 21 | 22 | @REM set options to pass to m17-fme 23 | set "options= -D -M 0:M17FME123:ALL -I -U 107.191.121.105:17000:R:C:NO -v 1 " 24 | 25 | @REM Set Date Time for log (sourced from: https://stackoverflow.com/questions/1192476/format-date-and-time-in-a-windows-batch-script) 26 | @echo off 27 | REM "%date: =0%" replaces spaces with zeros 28 | set d=%date: =0% 29 | REM "set yyyy=%d:~-4%" pulls the last 4 characters 30 | set yyyy=%d:~-4% 31 | set mm=%d:~4,2% 32 | set dd=%d:~7,2% 33 | set dow=%d:~0,3% 34 | set d=%yyyy%%mm%%dd% 35 | 36 | set t=%TIME: =0% 37 | REM "%t::=%" removes semi-colons 38 | REM Instead of above, you could use "%t::=-%" to 39 | REM replace semi-colons with hyphens (or any 40 | REM non-special character) 41 | set t=%t::=% 42 | set t=%t:.=% 43 | 44 | set datetimestr=%d%_%t% 45 | @REM @echo Long date time str = %datetimestr% 46 | 47 | @REM random number is used so that two log files don't share the same name 48 | set rnd=%RANDOM% 49 | 50 | @REM set log file relative filepath 51 | set "clog=.\logs\console_log_%datetimestr%_%rnd%.txt" 52 | set "elog=.\logs\event_log_%datetimestr%_%rnd%.txt" 53 | 54 | @REM create the log file now with touch 55 | .\m17-fme\touch.exe %clog% 56 | .\m17-fme\touch.exe %elog% 57 | 58 | @REM Launch Tail to display the console log and event log in a seperate console windows 59 | start .\m17-fme\tail.exe -n 40 -f %clog% 60 | start .\m17-fme\tail.exe -n 40 -f %elog% 61 | 62 | @REM output from pulse server routed to NUL to supress "capabilities dropped, nag messages, etc" messages 63 | .\m17-fme\pulseaudio.exe --start --no-cpu-limit=TRUE --exit-idle-time=600 2> NUL 64 | 65 | @REM start m17-fme with options and logs 66 | .\m17-fme\m17-fme.exe %options% -l %elog% 2> %clog% 67 | 68 | echo ---------------------------------------------------------------------------------- 69 | echo ---------------------------------------------------------------------------------- 70 | echo For any errors, see: %clog% 71 | echo Forward %clog% and Options: "%options%" 72 | echo to developer on Github or M17 Discord for troubleshooting. 73 | echo ---------------------------------------------------------------------------------- 74 | echo ---------------------------------------------------------------------------------- 75 | 76 | @REM Set pause to diplay above message 77 | PAUSE 78 | -------------------------------------------------------------------------------- /cmake/FindPulseAudio.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindPulseAudio 3 | # -------------- 4 | # Finds the PulseAudio library 5 | # 6 | # This will define the following variables:: 7 | # 8 | # PULSEAUDIO_FOUND - system has the PulseAudio library 9 | # PULSEAUDIO_INCLUDE_DIRS - the PulseAudio include directory 10 | # PULSEAUDIO_LIBRARIES - the libraries needed to use PulseAudio 11 | # PULSEAUDIO_DEFINITIONS - the definitions needed to use PulseAudio 12 | # 13 | # and the following imported targets:: 14 | # 15 | # PulseAudio::PulseAudio - The PulseAudio library 16 | 17 | if(PKG_CONFIG_FOUND) 18 | pkg_check_modules(PC_PULSEAUDIO libpulse>=11.0.0 QUIET) 19 | pkg_check_modules(PC_PULSEAUDIO_MAINLOOP libpulse-mainloop-glib>=11.0.0 QUIET) 20 | pkg_check_modules(PC_PULSEAUDIO_SIMPLE libpulse-simple>=11.0.0 QUIET) 21 | endif() 22 | 23 | find_path(PULSEAUDIO_INCLUDE_DIR NAMES pulse/pulseaudio.h pulse/simple.h 24 | PATHS ${PC_PULSEAUDIO_INCLUDEDIR} ${PC_PULSEAUDIO_INCLUDE_DIRS}) 25 | 26 | find_library(PULSEAUDIO_LIBRARY NAMES pulse libpulse 27 | PATHS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) 28 | 29 | find_library(PULSEAUDIO_SIMPLE_LIBRARY NAMES pulse-simple libpulse-simple 30 | PATHS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) 31 | 32 | find_library(PULSEAUDIO_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib 33 | PATHS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) 34 | 35 | if(PC_PULSEAUDIO_VERSION) 36 | set(PULSEAUDIO_VERSION_STRING ${PC_PULSEAUDIO_VERSION}) 37 | elseif(PULSEAUDIO_INCLUDE_DIR AND EXISTS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h") 38 | file(STRINGS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h" pulseaudio_version_str REGEX "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\".*\"\\).*") 39 | string(REGEX REPLACE "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\"([^\"]+)\"\\).*" "\\1" PULSEAUDIO_VERSION_STRING "${pulseaudio_version_str}") 40 | unset(pulseaudio_version_str) 41 | endif() 42 | 43 | include(FindPackageHandleStandardArgs) 44 | find_package_handle_standard_args(PulseAudio 45 | REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY PULSEAUDIO_SIMPLE_LIBRARY PULSEAUDIO_INCLUDE_DIR 46 | VERSION_VAR PULSEAUDIO_VERSION_STRING) 47 | 48 | if(PULSEAUDIO_FOUND) 49 | set(PULSEAUDIO_INCLUDE_DIRS ${PULSEAUDIO_INCLUDE_DIR}) 50 | set(PULSEAUDIO_LIBRARIES ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${PULSEAUDIO_SIMPLE_LIBRARY}) 51 | set(PULSEAUDIO_DEFINITIONS -DHAS_PULSEAUDIO=1) 52 | 53 | if(NOT TARGET PulseAudio::PulseAudioMainloop) 54 | add_library(PulseAudio::PulseAudioMainloop UNKNOWN IMPORTED) 55 | set_target_properties(PulseAudio::PulseAudioMainloop PROPERTIES 56 | IMPORTED_LOCATION "${PULSEAUDIO_MAINLOOP_LIBRARY}") 57 | endif() 58 | if(NOT TARGET PulseAudio::PulseAudio) 59 | add_library(PulseAudio::PulseAudio UNKNOWN IMPORTED) 60 | set_target_properties(PulseAudio::PulseAudio PROPERTIES 61 | IMPORTED_LOCATION "${PULSEAUDIO_LIBRARY}" 62 | INTERFACE_INCLUDE_DIRECTORIES "${PULSEAUDIO_INCLUDE_DIR}" 63 | INTERFACE_COMPILE_DEFINITIONS HAVE_LIBPULSE=1 64 | INTERFACE_LINK_LIBRARIES PulseAudio::PulseAudioMainloop) 65 | endif() 66 | endif() 67 | 68 | mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY PULSEAUDIO_SIMPLE_LIBRARY) 69 | -------------------------------------------------------------------------------- /src/decoder/m17_lich_decoder.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_lich_decoder.c 3 | * M17 Project - LICH Contents Assembly and Decoder 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | int decode_lich_contents(Super * super, uint8_t * lich_bits) 12 | { 13 | int i, j, err; 14 | err = 0; 15 | 16 | uint8_t lich[4][24]; 17 | uint8_t lich_decoded[48]; 18 | uint8_t temp[96]; 19 | bool g[4]; 20 | 21 | uint8_t lich_counter = 0; 22 | uint8_t lich_reserve = 0; UNUSED(lich_reserve); 23 | 24 | uint16_t crc_cmp = 0; 25 | uint16_t crc_ext = 0; 26 | uint8_t crc_err = 0; 27 | 28 | memset(lich, 0, sizeof(lich)); 29 | memset(lich_decoded, 0, sizeof(lich_decoded)); 30 | memset(temp, 0, sizeof(temp)); 31 | 32 | //execute golay 24,12 or 4 24-bit chunks and reorder into 4 12-bit chunks 33 | for (i = 0; i < 4; i++) 34 | { 35 | g[i] = true; 36 | 37 | for (j = 0; j < 24; j++) 38 | lich[i][j] = lich_bits[(i*24)+j]; 39 | 40 | g[i] = golay_24_12_decode(lich[i]); 41 | if(g[i] == false) 42 | { 43 | //track errors 44 | super->error.golay_err++; 45 | err = -1; 46 | } 47 | 48 | for (j = 0; j < 12; j++) 49 | lich_decoded[i*12+j] = lich[i][j]; 50 | 51 | } 52 | 53 | lich_counter = (uint8_t)convert_bits_into_output(&lich_decoded[40], 3); //lich_cnt 54 | lich_reserve = (uint8_t)convert_bits_into_output(&lich_decoded[43], 5); //lich_reserved 55 | 56 | //sanity check to prevent out of bounds 57 | if (lich_counter > 5) lich_counter = 5; 58 | 59 | if (err == 0) 60 | fprintf (stderr, "LC: %d/6 ", lich_counter+1); 61 | else fprintf (stderr, "LICH G24 ERR"); 62 | 63 | //reset enc bit counter on each 0 64 | // if (lich_counter == 0) 65 | // super->enc.bit_counter_d = 0; 66 | 67 | //transfer to storage 68 | for (i = 0; i < 40; i++) 69 | super->m17d.lsf[lich_counter*40+i] = lich_decoded[i]; 70 | 71 | if (super->opts.payload_verbosity >= 1) 72 | { 73 | fprintf (stderr, "\n"); 74 | fprintf (stderr, " LICH:"); 75 | for (i = 0; i < 6; i++) 76 | fprintf (stderr, " %02X", (uint8_t)convert_bits_into_output(&lich_decoded[i*8], 8)); 77 | } 78 | 79 | uint8_t lsf_packed[30]; 80 | memset (lsf_packed, 0, sizeof(lsf_packed)); 81 | 82 | if (lich_counter == 5) 83 | { 84 | 85 | //need to pack bytes for the sw5wwp variant of the crc (might as well, may be useful in the future) 86 | for (i = 0; i < 30; i++) 87 | lsf_packed[i] = (uint8_t)convert_bits_into_output(&super->m17d.lsf[i*8], 8); 88 | 89 | crc_cmp = crc16(lsf_packed, 28); 90 | crc_ext = (uint16_t)convert_bits_into_output(&super->m17d.lsf[224], 16); 91 | 92 | if (crc_cmp != crc_ext) crc_err = 1; 93 | 94 | if (crc_err == 0) 95 | decode_lsf_contents(super); 96 | else if (super->opts.allow_crc_failure == 1) 97 | decode_lsf_contents(super); 98 | 99 | if (super->opts.payload_verbosity >= 1) 100 | { 101 | fprintf (stderr, "\n LSF:"); 102 | for (i = 0; i < 30; i++) 103 | { 104 | if (i == 15) fprintf (stderr, "\n "); 105 | fprintf (stderr, " %02X", lsf_packed[i]); 106 | } 107 | fprintf (stderr, "\n (CRC CHK) E: %04X; C: %04X;", crc_ext, crc_cmp); 108 | } 109 | 110 | memset (super->m17d.lsf, 0, sizeof(super->m17d.lsf)); 111 | 112 | if (crc_err == 1) fprintf (stderr, "\n Embedded LSF CRC ERR"); 113 | 114 | //track errors 115 | if (crc_err == 1) super->error.lsf_emb_crc_err++; 116 | 117 | } 118 | 119 | return err; 120 | 121 | } -------------------------------------------------------------------------------- /src/io/oss.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * oss.c 3 | * M17 Project - Hot Garbage Audio Handling 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | #if !defined(__APPLE__) && !defined(__MACH__) 12 | 13 | void open_oss_output (Super * super) 14 | { 15 | 16 | int fmt; 17 | int speed = super->opts.output_sample_rate ; 18 | 19 | super->opts.oss_output_device = open (super->opts.oss_output_dev_str, O_RDWR); 20 | if (super->opts.oss_output_device == -1) 21 | { 22 | fprintf (stderr, "Error, couldn't open #1 %s\n", super->opts.oss_output_dev_str); 23 | super->opts.use_oss_output = 0; 24 | exit(1); 25 | } 26 | 27 | fmt = 0; 28 | if (ioctl (super->opts.oss_output_device, SNDCTL_DSP_RESET) < 0) 29 | { 30 | fprintf (stderr, "ioctl reset error \n"); 31 | } 32 | 33 | fmt = speed; 34 | if (ioctl (super->opts.oss_output_device, SNDCTL_DSP_SPEED, &fmt) < 0) 35 | { 36 | fprintf (stderr, "ioctl speed error \n"); 37 | } 38 | 39 | fmt = 0; 40 | if (ioctl (super->opts.oss_output_device, SNDCTL_DSP_STEREO, &fmt) < 0) 41 | { 42 | fprintf (stderr, "ioctl stereo error \n"); 43 | } 44 | 45 | fmt = AFMT_S16_LE; 46 | if (ioctl (super->opts.oss_output_device, SNDCTL_DSP_SETFMT, &fmt) < 0) 47 | { 48 | fprintf (stderr, "ioctl setfmt error \n"); 49 | } 50 | 51 | } 52 | 53 | void open_oss_input (Super * super) 54 | { 55 | 56 | int fmt; 57 | int speed = super->opts.input_sample_rate ; 58 | 59 | super->opts.oss_input_device = open (super->opts.oss_input_dev_str, O_RDWR); 60 | if (super->opts.oss_input_device == -1) 61 | { 62 | fprintf (stderr, "Error, couldn't open #1 %s\n", super->opts.oss_input_dev_str); 63 | super->opts.oss_input_device = 0; 64 | exit(1); 65 | } 66 | 67 | fmt = 0; 68 | if (ioctl (super->opts.oss_input_device, SNDCTL_DSP_RESET) < 0) 69 | { 70 | fprintf (stderr, "ioctl reset error \n"); 71 | } 72 | 73 | fmt = speed; 74 | if (ioctl (super->opts.oss_input_device, SNDCTL_DSP_SPEED, &fmt) < 0) 75 | { 76 | fprintf (stderr, "ioctl speed error \n"); 77 | } 78 | 79 | fmt = 0; 80 | if (ioctl (super->opts.oss_input_device, SNDCTL_DSP_STEREO, &fmt) < 0) 81 | { 82 | fprintf (stderr, "ioctl stereo error \n"); 83 | } 84 | 85 | fmt = AFMT_S16_LE; 86 | if (ioctl (super->opts.oss_input_device, SNDCTL_DSP_SETFMT, &fmt) < 0) 87 | { 88 | fprintf (stderr, "ioctl setfmt error \n"); 89 | } 90 | 91 | } 92 | 93 | short oss_input_read (Super * super) 94 | { 95 | short sample = 0; 96 | read (super->opts.oss_input_device, &sample, 2); 97 | return sample; 98 | } 99 | 100 | //Note: Won't be able to use both RF and VX output simultaneously over OSS (boo hoo) 101 | void oss_output_write (Super * super, short * out, size_t nsam) 102 | { 103 | int err = 0; UNUSED(err); 104 | write (super->opts.oss_output_device, out, nsam*2); 105 | } 106 | 107 | #else /* macOS - OSS not supported */ 108 | 109 | void open_oss_output (Super * super) 110 | { 111 | fprintf(stderr, "OSS audio output is not supported on macOS. Use PulseAudio or file output instead.\n"); 112 | super->opts.use_oss_output = 0; 113 | } 114 | 115 | void open_oss_input (Super * super) 116 | { 117 | fprintf(stderr, "OSS audio input is not supported on macOS. Use PulseAudio or file input instead.\n"); 118 | super->opts.oss_input_device = 0; 119 | } 120 | 121 | short oss_input_read (Super * super) 122 | { 123 | UNUSED(super); 124 | return 0; 125 | } 126 | 127 | void oss_output_write (Super * super, short * out, size_t nsam) 128 | { 129 | UNUSED(super); 130 | UNUSED(out); 131 | UNUSED(nsam); 132 | } 133 | 134 | #endif /* !__APPLE__ && !__MACH__ */ 135 | -------------------------------------------------------------------------------- /scripts/download-and-install-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # M17 Project - Florida Man Edition 4 | # macOS Install Script using Homebrew 5 | # 6 | # This script installs all dependencies and builds M17-FME on macOS 7 | # Requires Homebrew to be installed first (https://brew.sh) 8 | 9 | echo "" 10 | echo "===================================================================" 11 | echo "M17 Project - Florida Man Edition - macOS Installation Script" 12 | echo "===================================================================" 13 | echo "" 14 | 15 | # Check if Homebrew is installed 16 | if ! command -v brew &> /dev/null; then 17 | echo "❌ Homebrew is not installed. Please install it first:" 18 | echo " /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" 19 | exit 1 20 | fi 21 | 22 | echo "✅ Homebrew found: $(brew --version | head -n1)" 23 | echo "" 24 | 25 | # Update Homebrew 26 | echo "📦 Updating Homebrew..." 27 | brew update 28 | 29 | echo "" 30 | echo "📦 Installing required dependencies..." 31 | 32 | # Required dependencies 33 | echo " Installing cmake, make, git..." 34 | brew install cmake make git 35 | 36 | echo " Installing libsndfile (required)..." 37 | brew install libsndfile 38 | 39 | echo "" 40 | echo "📦 Installing recommended dependencies..." 41 | 42 | # Optional but recommended dependencies 43 | echo " Installing codec2 (for M17 voice support)..." 44 | brew install codec2 45 | 46 | echo " Installing ncurses (for terminal interface)..." 47 | brew install ncurses 48 | 49 | echo " Installing PulseAudio (for audio I/O)..." 50 | brew install pulseaudio 51 | 52 | echo " Installing wget and socat (utilities)..." 53 | brew install wget socat 54 | 55 | echo "" 56 | echo "📦 Installing build tools..." 57 | brew install gcc pkg-config 58 | 59 | echo "" 60 | echo "✅ All dependencies installed successfully!" 61 | echo "" 62 | 63 | # Check if we're in the m17-fme directory or need to download 64 | if [ ! -f "CMakeLists.txt" ] || [ ! -d "src" ]; then 65 | echo "📥 Downloading M17-FME source code..." 66 | if [ -d "m17-fme" ]; then 67 | rm -rf m17-fme 68 | fi 69 | git clone --recursive https://github.com/lwvmobile/m17-fme.git 70 | cd m17-fme 71 | else 72 | echo "📁 Found M17-FME source in current directory" 73 | fi 74 | 75 | echo "" 76 | echo "🔨 Building M17-FME..." 77 | 78 | # Create build directory 79 | if [ -d "build" ]; then 80 | echo " Cleaning previous build..." 81 | rm -rf build 82 | fi 83 | 84 | mkdir build 85 | cd build 86 | 87 | echo " Running cmake..." 88 | cmake .. 89 | 90 | if [ $? -ne 0 ]; then 91 | echo "❌ CMake configuration failed" 92 | exit 1 93 | fi 94 | 95 | echo " Compiling..." 96 | make 97 | 98 | if [ $? -ne 0 ]; then 99 | echo "❌ Compilation failed" 100 | exit 1 101 | fi 102 | 103 | echo "" 104 | echo "🔧 Installing M17-FME..." 105 | sudo make install 106 | 107 | if [ $? -eq 0 ]; then 108 | echo "" 109 | echo "🎉 M17-FME has been successfully installed!" 110 | echo "" 111 | echo "📝 macOS-specific notes:" 112 | echo " • PulseAudio may need to be started manually: 'pulseaudio --start'" 113 | echo " • For first-time PulseAudio use, you may need to configure audio permissions" 114 | echo " • OSS audio is not available on macOS - use PulseAudio or file I/O instead" 115 | echo " • If you encounter audio permission issues, check System Preferences > Security & Privacy > Microphone" 116 | echo "" 117 | echo "📖 Usage: Run 'm17-fme --help' for command line options" 118 | echo "📖 Documentation: See docs/Example_Usage.md for detailed usage examples" 119 | echo "" 120 | echo "🔊 To verify your installation, try:" 121 | echo " m17-fme --version" 122 | else 123 | echo "❌ Installation failed" 124 | exit 1 125 | fi 126 | -------------------------------------------------------------------------------- /src/decoder/m17_csd_decoder.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_csd_decoder.c 3 | * M17 Project - Callsign Data Decoder 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | void decode_callsign_data(Super * super, unsigned long long int dst, unsigned long long int src) 13 | { 14 | //quell defined but not used warnings from m17.h 15 | stfu (); 16 | 17 | int i; 18 | char c; 19 | char dst_csd[9]; memset (dst_csd, 0, 9*sizeof(char)); 20 | char src_csd[9]; memset (src_csd, 0, 9*sizeof(char)); 21 | 22 | if (dst == 0xFFFFFFFFFFFF) 23 | { 24 | fprintf (stderr, " DST: BROADCAST;"); 25 | sprintf (super->m17d.dst_csd_str, "BROADCAST"); 26 | } 27 | else if (dst == 0) 28 | { 29 | fprintf (stderr, " DST: RESERVED %012llX;", dst); 30 | sprintf (super->m17d.dst_csd_str, "RESERVED "); 31 | } 32 | 33 | else if (dst >= 0xEE6B28000000) 34 | { 35 | fprintf (stderr, " DST: RESERVED %012llX;", dst); 36 | sprintf (super->m17d.dst_csd_str, "RES: %012llX", dst); //can't fit the whole thing in here 37 | } 38 | 39 | else 40 | { 41 | fprintf (stderr, " DST: "); 42 | for (i = 0; i < 9; i++) 43 | { 44 | c = b40[dst % 40]; 45 | dst_csd[i] = c; 46 | fprintf (stderr, "%c", c); 47 | dst = dst / 40; 48 | } 49 | 50 | fprintf (stderr, ";"); 51 | 52 | //assign completed CSD to a more useful string instead 53 | sprintf (super->m17d.dst_csd_str, "%c%c%c%c%c%c%c%c%c", 54 | dst_csd[0], dst_csd[1], dst_csd[2], dst_csd[3], 55 | dst_csd[4], dst_csd[5], dst_csd[6], dst_csd[7], dst_csd[8]); 56 | 57 | //debug 58 | // fprintf (stderr, "DT: %s", super->m17d.dst_csd_str); 59 | } 60 | 61 | if (src == 0xFFFFFFFFFFFF) 62 | fprintf (stderr, " SRC: UNKNOWN FFFFFFFFFFFF;"); 63 | else if (src == 0) 64 | fprintf (stderr, " SRC: RESERVED %012llX;", src); 65 | else if (src >= 0xEE6B28000000) 66 | fprintf (stderr, " SRC: RESERVED %012llX;", src); 67 | else 68 | { 69 | fprintf (stderr, " SRC: "); 70 | for (i = 0; i < 9; i++) 71 | { 72 | c = b40[src % 40]; 73 | src_csd[i] = c; 74 | fprintf (stderr, "%c", c); 75 | src = src / 40; 76 | } 77 | 78 | fprintf (stderr, ";"); 79 | 80 | //assign completed CSD to a more useful string instead 81 | sprintf (super->m17d.src_csd_str, "%c%c%c%c%c%c%c%c%c", 82 | src_csd[0], src_csd[1], src_csd[2], src_csd[3], 83 | src_csd[4], src_csd[5], src_csd[6], src_csd[7], src_csd[8]); 84 | 85 | //debug 86 | // fprintf (stderr, "ST: %s", super->m17d.src_csd_str); 87 | } 88 | 89 | //debug 90 | // fprintf (stderr, " DST: %012llX SRC: %012llX", super->m17d.dst_csd_str, super->m17d.src_csd_str); 91 | 92 | } 93 | 94 | //version just for IP Frame SRC found in CONN, DISC etc 95 | void decode_callsign_src(Super * super, unsigned long long int src) 96 | { 97 | int i; 98 | char c; 99 | char src_csd[9]; memset (src_csd, 0, 9*sizeof(char)); 100 | 101 | if (src == 0xFFFFFFFFFFFF) 102 | fprintf (stderr, " SRC: UNKNOWN FFFFFFFFFFFF"); 103 | else if (src == 0) 104 | fprintf (stderr, " SRC: RESERVED %012llX", src); 105 | else if (src >= 0xEE6B28000000) 106 | fprintf (stderr, " SRC: RESERVED %012llX", src); 107 | else 108 | { 109 | fprintf (stderr, " SRC: "); 110 | for (i = 0; i < 9; i++) 111 | { 112 | c = b40[src % 40]; 113 | src_csd[i] = c; 114 | fprintf (stderr, "%c", c); 115 | src = src / 40; 116 | } 117 | 118 | //assign completed CSD to a more useful string instead 119 | sprintf (super->m17d.src_csd_str, "%c%c%c%c%c%c%c%c%c", 120 | src_csd[0], src_csd[1], src_csd[2], src_csd[3], 121 | src_csd[4], src_csd[5], src_csd[6], src_csd[7], src_csd[8]); 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /scripts/start-m17-fme-linux-terminal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Force bash for better compatibility (I think) 3 | # 4 | # Project M17: Florida Man Edition 5 | # 6 | # The source code of this software can be downloaded here: 7 | # https://github.com/lwvmobile/m17-fme 8 | # 9 | # If you paid for, or were otherwise fleeced for this software, DEMAND YOUR MONEY BACK! 10 | # 11 | # This software is designed to be used with an SDR receiver (like SDR++ or SDR#) with TCP input, 12 | # RTL Input, media file playback over virtual cables or null-sinks, or a Discriminator Tap input 13 | # 14 | # The complete options list can be seen by viewing m17-fme -h 15 | # 16 | # Enjoy! 17 | ### 18 | #### IMPORTANT! Set your m17-fme options here! #### 19 | # Example : opts=' -D -M 0:M17FME000:ALL -I -U 107.191.121.105:17000:R:C:NO -v 1 ' 20 | #### 21 | # set options to pass to m17-fme 22 | opts=' -D -M 0:M17FME000:ALL -I -U 107.191.121.105:17000:R:C:NO -v 1 ' 23 | #### 24 | # Initialize variables 25 | xe="0" 26 | gt="0" 27 | xt="0" 28 | ko="0" 29 | # Functions to check for available terminals 30 | chk_xe(){ 31 | # Check if x-terminal-emulator is installed 32 | xe=$(which x-terminal-emulator 2>/dev/null) 33 | if [ -z $xe ];then xe="0";else xe="1";fi 34 | } 35 | chk_gt(){ 36 | # Check if gnome-terminal is installed 37 | gt=$(which gnome-terminal 2>/dev/null) 38 | if [ -z $gt ];then gt="0";else gt="1";fi 39 | } 40 | chk_ko(){ 41 | # Check if Konsole is installed 42 | ko=$(which konsole 2>/dev/null) 43 | if [ -z $ko ];then ko="0";else ko="1";fi 44 | } 45 | chk_xt(){ 46 | # Check if xterm is installed 47 | xt=$(which xterm 2>/dev/null) 48 | if [ -z $xt ];then xt="0";else xt="1";fi 49 | } 50 | # Check for and set terminal 51 | chk_xe 52 | if [ $xe = "1" ];then 53 | term=$(which x-terminal-emulator 2>/dev/null) 54 | printf "Using X-Terminal-Emulator!\n" 55 | else 56 | chk_gt 57 | fi 58 | if [ $gt = "1" ];then 59 | term=$(which gnome-terminal 2>/dev/null) 60 | printf "Using Gnome-Terminal!\n" 61 | elif [ $xe = "0" ]&&[ $gt = "0" ];then 62 | chk_xt 63 | fi 64 | if [ $xt = "1" ];then 65 | term=$(which xterm 2>/dev/null) 66 | printf "Using XTerm!\n" 67 | elif [ $xe = "0" ]&&[ $gt = "0" ]&&[ $xt = "0" ];then 68 | chk_ko 69 | fi 70 | if [ $ko = "1" ];then 71 | term=$(which konsole 2>/dev/null) 72 | printf "Using Konsole!\n" 73 | fi 74 | # Bail if no terminal found 75 | if [ "$ko" -eq "0" ]&&[ "$gt" -eq "0" ]&&[ "$xt" -eq "0" ]&&[ "$xe" -eq "0" ];then printf "No known terminals available!\n";exit 1;fi 76 | # Get current directory 77 | wd=$(pwd) 78 | # Set date/time 79 | # dt=$(date +%F_%H%M-%S-%N) #with hyphens 80 | dt=$(date +%Y%m%d_%H%M%S) #just an underscore between date and time 81 | if [ ! -d "logs" ]; then 82 | mkdir logs 83 | fi 84 | # set log file relative filepath 85 | clog="$wd/logs/console_log_$dt.txt" 86 | elog="$wd/logs/event_log_$dt.txt" 87 | # create the log file now with touch 88 | touch $clog 89 | touch $elog 90 | # open new terminal windows to watch live log files 91 | if [ "$term" == "$(which konsole)" ]||[ "$term" == "$(which xterm)" ];then 92 | $term -e "tail -n 40 -f $clog" 2>/dev/null 93 | $term -e "tail -n 40 -f $elog" 2>/dev/null 94 | else 95 | $term --window -e "tail -n 40 -f $clog" 2>/dev/null 96 | $term --window -e "tail -n 40 -f $elog" 2>/dev/null 97 | fi 98 | # start m17-fme with options and log files 99 | m17-fme $opts -l $elog 2> $clog 100 | echo ------------------------------------------------------------------------------- 101 | echo ------------------------------------------------------------------------------- 102 | echo For any errors, see: $clog 103 | echo Forward $clog and 104 | echo Options: \" $opts \" 105 | echo to developer on Github or Radio Reference for troubleshooting. 106 | echo ------------------------------------------------------------------------------- 107 | echo ------------------------------------------------------------------------------- 108 | 109 | # Set pause to diplay above message 110 | read -p "Press ENTER to continue... " x 111 | 112 | -------------------------------------------------------------------------------- /docs/Audio_Plumbing.md: -------------------------------------------------------------------------------- 1 | 2 | # M17 Project - Florida Man Edition 3 | 4 | ## Audio Plumbing 5 | 6 | While using M17-FME to encode/modulate and decode/demodulate RF Audio, you will find it necessary to properly plumb (or route) audio from each input source and output sink properly. This mini tutorial aims to help you do such when using Pulse Audio. 7 | 8 | You will first want to locate and run the virtualsink.sh script file via `sh virtualsink.sh` command. This will create two null-sink, or 'virtual sinks' for us to plumb audio from point A to point B. 9 | 10 | Here are screenshots of a proper setup using pavucontrol, or "Pulse Audio Volume Control", having voice input from the encoder listening to your microphone, the RF Output going into the "M17_Sink" null-sink, and on the playback side, playing back voice to our speakers, with RF modulated audio being monitored on "M17-Sink" null-sink. You will find that session id values are available for easy matching. 11 | 12 | ![Audio 1](https://github.com/lwvmobile/m17-fme/blob/main/docs/pavucontrol_plumbing1.png) 13 | 14 | ![Audio 2](https://github.com/lwvmobile/m17-fme/blob/main/docs/pavucontrol_plumbing2.png) 15 | 16 | The general idea is that we want to route RF modulated audio into our M17_Sink and have our decoder listen to the same sink. Voice Input should always be from a microphone source and Voice Output should always be to Audio Hardware (speakers, headphones, etc). 17 | 18 | Note, that using the virtualsink.sh will create these sinks for us for the duration of your computers boot. If you reboot, they will not persist. Configuration files can be modified to always have these sinks available, or the `virtualsink.sh` script can be executed on login, but you must do so at your own risk and own research, it is out of the scope of this tutorial to create persistent sinks. 19 | 20 | In case of the usage of a headless environment and pavucontrol is not viable, you may use alternatives such as pulsemixer which is similar in functionality, just as a CLI tool and not a GUI tool. 21 | 22 | Users can now use the `-a` CLI argument to list out all pulse audio input sources and output sinks and pass an optional argument alongside `pulserf` and `pulsevx`, appending the name or index number to select the device each time. This method will work in both Linux environments as well as Cygwin builds (making sure to start the pulseaudio.exe server in the back first with the specified .bat file or command, see below). 23 | 24 | For Example: 25 | 26 | The Output (truncated) of `m17-fme -a` 27 | 28 | ``` 29 | =======[ Output Device #1 ]======= 30 | Description: Family 17h (Models 00h-0fh) HD Audio Controller Analog Stereo 31 | Name: alsa_output.pci-0000_0d_00.3.analog-stereo 32 | Index: 1 33 | 34 | =======[ Output Device #4 ]======= 35 | Description: M17_Sink1 36 | Name: m17_sink1 37 | Index: 4 38 | 39 | =======[ Output Device #5 ]======= 40 | Description: M17_Sink2 41 | Name: m17_sink2 42 | Index: 5 43 | 44 | =======[ Input Device #2 ]======= 45 | Description: Family 17h (Models 00h-0fh) HD Audio Controller Analog Stereo 46 | Name: alsa_input.pci-0000_0d_00.3.analog-stereo 47 | Index: 2 48 | 49 | =======[ Input Device #5 ]======= 50 | Description: Monitor of M17_Sink1 51 | Name: m17_sink1.monitor 52 | Index: 5 53 | 54 | =======[ Input Device #6 ]======= 55 | Description: Monitor of M17_Sink2 56 | Name: m17_sink2.monitor 57 | Index: 6 58 | ``` 59 | 60 | You might use any of the following for input and output options: 61 | 62 | ``` 63 | -i pulserf 64 | -i pulserf:6 65 | -i pulserf:m17_sink2.monitor 66 | 67 | -o pulsevx 68 | -o pulsevx:1 69 | -o pulsevx:alsa_output.pci-0000_0d_00.3.analog-stereo 70 | 71 | -o pulserf 72 | -o pulserf:5 73 | -o pulserf:m17_sink2 74 | ``` 75 | 76 | Something to keep in mind is that often times, you only need to set these values once, as a pulse audio server (or plugin) can often times remember these settings, even across reboots, as long as the same hardware is present for future use (real or null-sink). 77 | 78 | Cygwin Note: Running the supplied `0p - start-pulse-audio-backend.bat` file will not only start up the pulse audio server for you, but also setup your null-sinks, and enumerate all devices for you to use, making it an easy selection for your start up .bat files. See any notes in the precomplied .zip file for more information. -------------------------------------------------------------------------------- /src/utils/utils.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * utils.c 3 | * M17 Project - Misc Utility Functions 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | //input bit array, return output as up to a 64-bit value 12 | uint64_t convert_bits_into_output(uint8_t * input, int len) 13 | { 14 | int i; 15 | uint64_t output = 0; 16 | for(i = 0; i < len; i++) 17 | { 18 | output <<= 1; 19 | output |= (uint64_t)(input[i] & 1); 20 | } 21 | return output; 22 | } 23 | 24 | //take x amount of bits and pack into len amount of bytes (symmetrical) 25 | void pack_bit_array_into_byte_array (uint8_t * input, uint8_t * output, int len) 26 | { 27 | int i; 28 | for (i = 0; i < len; i++) 29 | output[i] = (uint8_t)convert_bits_into_output(&input[i*8], 8); 30 | } 31 | 32 | //take len amount of bits and pack into x amount of bytes (asymmetrical) 33 | void pack_bit_array_into_byte_array_asym (uint8_t * input, uint8_t * output, int len) 34 | { 35 | int i = 0; int k = len % 8; 36 | for (i = 0; i < len; i++) 37 | { 38 | output[i/8] <<= 1; 39 | output[i/8] |= input[i]; 40 | } 41 | //if any leftover bits that don't flush the last byte fully packed, shift them over left 42 | if (k) 43 | output[i/8] <<= 8-k; 44 | } 45 | 46 | //take len amount of bytes and unpack back into a bit array 47 | void unpack_byte_array_into_bit_array (uint8_t * input, uint8_t * output, int len) 48 | { 49 | int i = 0, k = 0; 50 | for (i = 0; i < len; i++) 51 | { 52 | output[k++] = (input[i] >> 7) & 1; 53 | output[k++] = (input[i] >> 6) & 1; 54 | output[k++] = (input[i] >> 5) & 1; 55 | output[k++] = (input[i] >> 4) & 1; 56 | output[k++] = (input[i] >> 3) & 1; 57 | output[k++] = (input[i] >> 2) & 1; 58 | output[k++] = (input[i] >> 1) & 1; 59 | output[k++] = (input[i] >> 0) & 1; 60 | } 61 | } 62 | 63 | //convenience function to convert a dibit buffer array into 64 | //a binary buffer array, len is length of input buffer 65 | void convert_dibit_array_into_binary_array (uint8_t * input, uint8_t * output, int len) 66 | { 67 | int i; 68 | for (i = 0; i < len; i++) 69 | { 70 | output[(i*2)+0] = (input[i] >> 0) & 1; 71 | output[(i*2)+1] = (input[i] >> 1) & 1; 72 | } 73 | } 74 | 75 | //left shift an input array of len x bytes one to the left (MSB-wards) 76 | void left_shift_byte_array (uint8_t * input, uint8_t * output, int len) 77 | { 78 | int i; 79 | output[len-1] = input[0]; //swing MSB position to LSB (byte) position 80 | for (i = 1; i < len; i++) //left shift other bytes one towards MSB 81 | output[i-1] = input[i]; 82 | } 83 | 84 | //right shift an input array of len x bytes one to the right (LSB-wards) 85 | void right_shift_byte_array (uint8_t * input, uint8_t * output, int len) 86 | { 87 | int i; 88 | output[0] = input[len-1]; //swing LSB position to MSB (byte) position 89 | for (i = 1; i < len; i++) //right shift other bytes one towards MSB 90 | output[i] = input[i-1]; 91 | } 92 | 93 | //input is user string of hex chars, output is uint8_t byte array, return value is len 94 | uint16_t convert_string_into_array (char * input, uint8_t * output) 95 | { 96 | //since we want this as octets, get strlen value, then divide by two 97 | uint16_t len = strlen((const char*)input); 98 | 99 | //if zero is returned, just do two 100 | if (len == 0) len = 2; 101 | 102 | //if odd number, then user didn't pass complete octets, but just add one to len value to make it even 103 | if (len&1) len++; 104 | 105 | //divide by two to get octet len 106 | len /= 2; 107 | 108 | char octet_char[3]; 109 | octet_char[2] = 0; 110 | uint16_t i = 0; 111 | uint16_t k = 0; 112 | 113 | //debug 114 | // fprintf (stderr, "\n String Len: %d; String Octets:", len); 115 | for (i = 0; i < len; i++) //<= 116 | { 117 | strncpy (octet_char, input+k, 2); 118 | octet_char[2] = 0; 119 | sscanf (octet_char, "%hhX", &output[i]); 120 | 121 | //debug 122 | // fprintf (stderr, " (%s)", octet_char); 123 | // fprintf (stderr, " %02X", output[i]); 124 | k += 2; 125 | } 126 | 127 | return len; 128 | } -------------------------------------------------------------------------------- /src/encoder/m17_brt_encoder.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_brt_encoder.c 3 | * M17 Project - BERT (Bit Error Rate Test) Frame Encoder 4 | * 5 | * LWVMOBILE 6 | * 2025-08 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | //encode and create audio of a M17 Project BERT signal 13 | void encode_brt(Super * super) 14 | { 15 | 16 | init_brt(); 17 | 18 | float mem[81]; 19 | 20 | //quell defined but not used warnings from m17.h 21 | stfu (); 22 | 23 | //initialize RRC memory buffer 24 | memset (mem, 0, 81*sizeof(float)); 25 | 26 | //Enable TX 27 | super->m17e.str_encoder_tx = 1; 28 | 29 | //if using the ncurses terminal, disable TX on startup until user toggles it with the '\' key, if not vox enabled 30 | if (super->opts.use_ncurses_terminal == 1 && super->opts.use_m17_str_encoder == 1 && super->m17e.str_encoder_vox == 0) 31 | super->m17e.str_encoder_tx = 0; 32 | 33 | int i, k, x; //basic utility counters 34 | 35 | uint8_t nil[368]; //empty array to send to RF during Preamble, EOT Marker, or Dead Air 36 | memset (nil, 0, sizeof(nil)); 37 | 38 | //send dead air with type 99 39 | for (i = 0; i < 25; i++) 40 | encode_rfa (super, nil, mem, 99); 41 | 42 | //send Preamble B 43 | encode_rfa (super, nil, mem, 33); 44 | 45 | //BERT LFSR 46 | uint16_t bert_lfsr_seed = 1; 47 | 48 | while (!exitflag) //while the software is running 49 | { 50 | 51 | uint8_t bert[201]; memset (bert, 0, sizeof(bert)); 52 | bert_lfsr_seed = brt_lfsr(bert_lfsr_seed, bert, 197); 53 | 54 | //initialize and start assembling the completed frame 55 | uint8_t bert_c[402]; memset (bert_c, 0, sizeof(bert_c)); 56 | 57 | //Use the convolutional encoder to encode the BERT Frame 58 | simple_conv_encoder (bert, bert_c, 201); 59 | 60 | uint8_t bert_p[368]; memset (bert_p, 0, sizeof(bert_p)); 61 | 62 | //use the P2 puncture to...puncture and collapse the BERT Frame 63 | k = 0; x = 0; 64 | for (i = 0; i < 34; i++) 65 | { 66 | bert_p[k++] = bert_c[x++]; 67 | bert_p[k++] = bert_c[x++]; 68 | bert_p[k++] = bert_c[x++]; 69 | bert_p[k++] = bert_c[x++]; 70 | bert_p[k++] = bert_c[x++]; 71 | //quit early on last set of i when 368 k bits reached 72 | //index from 0 to 367,so 368 is breakpoint with k++ 73 | if (k == 368) break; 74 | bert_p[k++] = bert_c[x++]; 75 | bert_p[k++] = bert_c[x++]; 76 | bert_p[k++] = bert_c[x++]; 77 | bert_p[k++] = bert_c[x++]; 78 | bert_p[k++] = bert_c[x++]; 79 | bert_p[k++] = bert_c[x++]; 80 | x++; 81 | } 82 | 83 | //debug 84 | // fprintf (stderr, "CONV: %03d; PUNC: %03d;", x, k); 85 | 86 | uint8_t bert_i[368]; memset (bert_i, 0, sizeof(bert_i)); 87 | 88 | //interleave the bit array using Quadratic Permutation Polynomial 89 | //function π(x) = (45x + 92x^2 ) mod 368 90 | for (i = 0; i < 368; i++) 91 | { 92 | x = ((45*i)+(92*i*i)) % 368; 93 | bert_i[x] = bert_p[i]; 94 | } 95 | 96 | uint8_t bert_s[368]; memset (bert_s, 0, sizeof(bert_s)); 97 | 98 | //scramble/randomize the frame 99 | for (i = 0; i < 368; i++) 100 | bert_s[i] = (bert_i[i] ^ m17_scramble[i]) & 1; 101 | 102 | //----------------------------------------- 103 | 104 | if (super->m17e.str_encoder_tx == 1) //when toggled on 105 | { 106 | //Enable sync 107 | super->demod.in_sync = 1; 108 | 109 | fprintf (stderr, "\n M17 BERT (ENCODER) LFSR %03X: ", bert_lfsr_seed); 110 | // if (super->opts.internal_loopback_decoder == 1) 111 | // demod_brt(super, bert_s, 1); //NOTE: This will not work, since we use the same static for both, it'll just keep resetting itself 112 | fprintf (stderr, " To Audio Out: %s", super->pa.pa_outrf_idx); 113 | 114 | #ifdef USE_PULSEAUDIO 115 | //debug show pulse input latency 116 | if (super->opts.use_pa_input == 1 && super->opts.demod_verbosity >= 2) 117 | { 118 | unsigned long long int latency = pa_simple_get_latency (super->pa.pa_input_device, NULL); 119 | fprintf (stderr, " Latency: %05lld;", latency); 120 | } 121 | #endif 122 | 123 | //convert bit array into symbols and RF/Audio 124 | encode_rfa (super, bert_s, mem, 3); 125 | 126 | } //end if (super->m17d.strencoder_tx) 127 | else bert_lfsr_seed = 1; //reset seed 128 | 129 | } 130 | 131 | super->demod.current_time = time(NULL); 132 | 133 | } -------------------------------------------------------------------------------- /src/decoder/m17_lsf_demodulator.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_lsf_demodulator.c 3 | * M17 Project - Link Setup Frame Demodulation and Debug 4 | * 5 | * LWVMOBILE 6 | * 2025-01 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | void demod_lsf(Super * super, uint8_t * input, int debug) 13 | { 14 | //quell defined but not used warnings from m17.h 15 | stfu (); 16 | 17 | int i; 18 | uint8_t dbuf[184]; //384-bit frame - 16-bit (8 symbol) sync pattern (184 dibits) 19 | float sbuf[184]; //float symbol buffer 20 | uint16_t soft_bit[2*SYM_PER_PLD]; //raw frame soft bits 21 | uint16_t d_soft_bit[2*SYM_PER_PLD]; //deinterleaved soft bits 22 | uint8_t viterbi_bytes[31]; //packed viterbi return bytes 23 | uint32_t error = 0; //viterbi error 24 | 25 | memset(dbuf, 0, sizeof(dbuf)); 26 | memset(sbuf, 0.0f, sizeof(sbuf)); 27 | memset(soft_bit, 0, sizeof(soft_bit)); 28 | memset(d_soft_bit, 0, sizeof(d_soft_bit)); 29 | memset(viterbi_bytes, 0, sizeof(viterbi_bytes)); 30 | 31 | //if not running in debug / encoder mode, then perform dibit collection 32 | if (debug == 0) 33 | { 34 | //load dibits into dibit buffer 35 | for (i = 0; i < 184; i++) 36 | dbuf[i] = get_dibit(super); 37 | 38 | //convert dbuf into a symbol array 39 | for (i = 0; i < 184; i++) 40 | { 41 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 42 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 43 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 44 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 45 | else sbuf[i] = +0.0f; 46 | } 47 | 48 | } 49 | else //we are debugging, and convert input to symbols 50 | { 51 | //load dibits into dibit buffer from input bits 52 | for (i = 0; i < 184; i++) 53 | dbuf[i] = (input[(i*2)+0] << 1) | input[(i*2)+1]; 54 | 55 | //convert dbuf into a symbol array 56 | for (i = 0; i < 184; i++) 57 | { 58 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 59 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 60 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 61 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 62 | else sbuf[i] = +0.0f; 63 | } 64 | } 65 | 66 | //libm17 magic 67 | //slice symbols to soft dibits 68 | slice_symbols(soft_bit, sbuf); 69 | 70 | //derandomize 71 | randomize_soft_bits(soft_bit); 72 | 73 | //deinterleave 74 | reorder_soft_bits(d_soft_bit, soft_bit); 75 | 76 | //viterbi 77 | error = viterbi_decode_punctured(viterbi_bytes, d_soft_bit, p1, 2*SYM_PER_PLD, 61); 78 | 79 | //track viterbi error / cost metric 80 | super->error.viterbi_err = (float)error/(float)0xFFFF; 81 | 82 | //TODO: BER Estimate 83 | // state->error.ber_estimate; 84 | 85 | //unpack into the lsf bit array 86 | memset (super->m17d.lsf, 0, sizeof(super->m17d.lsf)); 87 | unpack_byte_array_into_bit_array(viterbi_bytes+1, super->m17d.lsf, 30); 88 | 89 | uint8_t lsf_packed[30]; 90 | memset (lsf_packed, 0, sizeof(lsf_packed)); 91 | 92 | //need to pack bytes for the sw5wwp variant of the crc (might as well, may be useful in the future) 93 | for (i = 0; i < 30; i++) 94 | lsf_packed[i] = (uint8_t)convert_bits_into_output(&super->m17d.lsf[i*8], 8); 95 | 96 | uint16_t crc_cmp = crc16(lsf_packed, 28); 97 | uint16_t crc_ext = (uint16_t)convert_bits_into_output(&super->m17d.lsf[224], 16); 98 | int crc_err = 0; 99 | 100 | if (crc_cmp != crc_ext) crc_err = 1; 101 | 102 | if (crc_err == 0) 103 | decode_lsf_contents(super); 104 | else if (super->opts.allow_crc_failure == 1) 105 | decode_lsf_contents(super); 106 | 107 | if (super->opts.payload_verbosity >= 1) 108 | { 109 | fprintf (stderr, "\n LSF:"); 110 | for (i = 0; i < 30; i++) 111 | { 112 | if (i == 15) fprintf (stderr, "\n "); 113 | fprintf (stderr, " %02X", lsf_packed[i]); 114 | } 115 | fprintf (stderr, "\n (CRC CHK) E: %04X; C: %04X; Ve: %1.1f;", crc_ext, crc_cmp, (float)error/(float)0xFFFF); 116 | } 117 | 118 | if (crc_err == 1) fprintf (stderr, " CRC ERR"); 119 | 120 | //track errors 121 | if (crc_err == 1) super->error.lsf_hdr_crc_err++; 122 | 123 | //get rid of this if it costs too much CPU / skips / lags 124 | super->demod.sync_time = super->demod.current_time = time(NULL); 125 | 126 | //refresh ncurses printer, if enabled 127 | #ifdef USE_CURSES 128 | if (super->opts.use_ncurses_terminal == 1) 129 | print_ncurses_terminal(super); 130 | #endif 131 | 132 | } -------------------------------------------------------------------------------- /cmake/git_revision.cmake: -------------------------------------------------------------------------------- 1 | # - Returns a version string from Git 2 | # 3 | # These functions force a re-configure on each git commit so that you can 4 | # trust the values of the variables in your build system. 5 | # 6 | # get_git_head_revision( [ ...]) 7 | # 8 | # Returns the refspec and sha hash of the current head revision 9 | # 10 | # git_describe( [ ...]) 11 | # 12 | # Returns the results of git describe on the source tree, and adjusting 13 | # the output so that it tests false if an error occurs. 14 | # 15 | # git_get_exact_tag( [ ...]) 16 | # 17 | # Returns the results of git describe --exact-match on the source tree, 18 | # and adjusting the output so that it tests false if there was no exact 19 | # matching tag. 20 | # 21 | # Requires CMake 2.6 or newer (uses the 'function' command) 22 | # 23 | # Original Author: 24 | # 2009-2010 Ryan Pavlik 25 | # http://academic.cleardefinition.com 26 | # Iowa State University HCI Graduate Program/VRAC 27 | # 28 | # Copyright Iowa State University 2009-2010. 29 | # Distributed under the Boost Software License, Version 1.0. 30 | # (See accompanying file LICENSE_1_0.txt or copy at 31 | # http://www.boost.org/LICENSE_1_0.txt) 32 | 33 | if(__get_git_revision_description) 34 | return() 35 | endif() 36 | set(__get_git_revision_description YES) 37 | 38 | # We must run the following at "include" time, not at function call time, 39 | # to find the path to this module rather than the path to a calling list file 40 | get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) 41 | 42 | function(get_git_head_revision _refspecvar _hashvar) 43 | set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 44 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 45 | while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories 46 | set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") 47 | get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) 48 | if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) 49 | # We have reached the root directory, we are not in git 50 | set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 51 | set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 52 | return() 53 | endif() 54 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 55 | endwhile() 56 | # check if this is a submodule 57 | if(NOT IS_DIRECTORY ${GIT_DIR}) 58 | file(READ ${GIT_DIR} submodule) 59 | string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) 60 | get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) 61 | get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) 62 | endif() 63 | set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") 64 | if(NOT EXISTS "${GIT_DATA}") 65 | file(MAKE_DIRECTORY "${GIT_DATA}") 66 | endif() 67 | 68 | if(NOT EXISTS "${GIT_DIR}/HEAD") 69 | return() 70 | endif() 71 | set(HEAD_FILE "${GIT_DATA}/HEAD") 72 | configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) 73 | 74 | configure_file("cmake/git_revision.cmake.in" 75 | "${GIT_DATA}/grabRef.cmake" 76 | @ONLY) 77 | include("${GIT_DATA}/grabRef.cmake") 78 | 79 | set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) 80 | set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) 81 | endfunction() 82 | 83 | function(git_describe _var) 84 | if(NOT GIT_FOUND) 85 | find_package(Git QUIET) 86 | endif() 87 | get_git_head_revision(refspec hash) 88 | if(NOT GIT_FOUND) 89 | set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) 90 | return() 91 | endif() 92 | if(NOT hash) 93 | set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) 94 | return() 95 | endif() 96 | 97 | # TODO sanitize 98 | #if((${ARGN}" MATCHES "&&") OR 99 | # (ARGN MATCHES "||") OR 100 | # (ARGN MATCHES "\\;")) 101 | # message("Please report the following error to the project!") 102 | # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") 103 | #endif() 104 | 105 | #message(STATUS "Arguments to execute_process: ${ARGN}") 106 | 107 | execute_process(COMMAND 108 | "${GIT_EXECUTABLE}" 109 | describe 110 | ${hash} 111 | ${ARGN} 112 | WORKING_DIRECTORY 113 | "${CMAKE_SOURCE_DIR}" 114 | RESULT_VARIABLE 115 | res 116 | OUTPUT_VARIABLE 117 | out 118 | ERROR_QUIET 119 | OUTPUT_STRIP_TRAILING_WHITESPACE) 120 | if(NOT res EQUAL 0) 121 | set(out "${out}-${res}-NOTFOUND") 122 | endif() 123 | 124 | set(${_var} "${out}" PARENT_SCOPE) 125 | endfunction() 126 | 127 | function(git_get_exact_tag _var) 128 | git_describe(out --exact-match ${ARGN}) 129 | set(${_var} "${out}" PARENT_SCOPE) 130 | endfunction() 131 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2) 2 | project(m17-fme) 3 | 4 | SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/") 5 | 6 | #Set curses to ncurses, and wide true for ascii 7 | set(CURSES_NEED_NCURSES TRUE) 8 | set(CURSES_NEED_WIDE TRUE) 9 | 10 | #use cmake option -DCOLORS=OFF to disable color output 11 | option(COLORS 12 | "Build with Colors Enabled" ON) 13 | if (COLORS) 14 | add_definitions(-DPRETTY_COLORS) 15 | endif () 16 | 17 | #use cmake option -DOTAKD=OFF to disable OTA Key Delivery (moved to user option) 18 | # option(OTAKD 19 | # "Build with Over the Air Encryption Key Delivery" ON) 20 | # if (OTAKD) 21 | # add_definitions(-DOTA_KEY_DELIVERY) 22 | # endif () 23 | 24 | include(git_revision) 25 | git_describe(GIT_TAG) 26 | 27 | # Find PkgConfig first (required for dependency detection) 28 | find_package(PkgConfig REQUIRED) 29 | 30 | # macOS specific configurations 31 | if(APPLE) 32 | # Add Homebrew paths for macOS 33 | list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew" "/usr/local") 34 | 35 | # Set additional library and include paths for Homebrew packages 36 | set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} "/opt/homebrew/lib" "/usr/local/lib") 37 | set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "/opt/homebrew/include" "/usr/local/include") 38 | 39 | # Ensure we can find pkg-config in Homebrew paths 40 | find_program(PKG_CONFIG_EXECUTABLE pkg-config PATHS "/opt/homebrew/bin" "/usr/local/bin") 41 | if(PKG_CONFIG_EXECUTABLE) 42 | set(PKG_CONFIG_FOUND TRUE) 43 | endif() 44 | endif() 45 | 46 | find_package(LibSndFile REQUIRED) #required packaged need the REQUIRED argument 47 | find_package(PulseAudio) 48 | find_package(Curses) 49 | find_package(CODEC2) 50 | 51 | include_directories(SYSTEM ${LIBSNDFILE_INCLUDE_DIR}) 52 | set(LIBS ${LIBSNDFILE_LIBRARY}) 53 | 54 | # append optional libraries and directories and set definitions for optional function calls 55 | if(PULSEAUDIO_FOUND) 56 | include_directories(SYSTEM ${PULSEAUDIO_INCLUDE_DIRS}) 57 | list(APPEND LIBS ${PULSEAUDIO_SIMPLE_LIBRARY} ${PULSEAUDIO_LIBRARY}) 58 | add_definitions(-DUSE_PULSEAUDIO) 59 | endif(PULSEAUDIO_FOUND) 60 | 61 | if(CODEC2_FOUND) 62 | include_directories(SYSTEM ${CODEC2_INCLUDE_DIRS}) 63 | list(APPEND LIBS ${CODEC2_LIBRARIES}) 64 | add_definitions(-DUSE_CODEC2) 65 | endif(CODEC2_FOUND) 66 | 67 | if(CURSES_FOUND) 68 | include_directories(SYSTEM ${CURSES_INCLUDE_DIRS}) 69 | list(APPEND LIBS ${CURSES_LIBRARIES}) 70 | add_definitions(-DUSE_CURSES) 71 | endif(CURSES_FOUND) 72 | 73 | FILE(GLOB SRCS src/*.c src/decoder/*.c src/encoder/*.c src/encryption/*.c src/io/*.c src/fec/*.c src/modem/*.c src/net/*.c src/ncurses/*.c src/utils/*.c src/*.cpp src/utils/*.cpp) 74 | FILE(GLOB HEADERS include/*.h include/*.hpp) 75 | 76 | configure_file("src/git_ver.c.in" "${CMAKE_CURRENT_BINARY_DIR}/git_ver.c" @ONLY) 77 | list(APPEND SRCS "${CMAKE_CURRENT_BINARY_DIR}/git_ver.c") 78 | 79 | # Look and see if micro-ecc is available, if so, then the append to SRCS and HEADERS 80 | find_file(UECC NAMES uECC.h PATHS src/micro-ecc/) 81 | if ( EXISTS ${UECC} ) 82 | list(APPEND SRCS "src/micro-ecc/uECC.c") 83 | list(APPEND HEADERS "src/micro-ecc/uECC.h") #needed? 84 | add_definitions(-DUSE_UECC) 85 | message ("-- micro-ecc found") 86 | else() 87 | message ("-- micro-ecc not found") 88 | endif() 89 | 90 | include_directories("${PROJECT_SOURCE_DIR}/include") 91 | 92 | ADD_EXECUTABLE(m17-fme ${SRCS} ${HEADERS}) 93 | 94 | # macOS specific linking 95 | if(APPLE) 96 | # Add math library explicitly for macOS 97 | list(APPEND LIBS m) 98 | 99 | # Set RPATH for Homebrew libraries 100 | set_target_properties(m17-fme PROPERTIES 101 | INSTALL_RPATH "/opt/homebrew/lib;/usr/local/lib" 102 | BUILD_WITH_INSTALL_RPATH TRUE) 103 | endif() 104 | 105 | TARGET_LINK_LIBRARIES(m17-fme ${LIBS}) 106 | 107 | # debug with extra -Wpedantic when coding 108 | # target_compile_options(m17-fme PRIVATE -Wunused-but-set-variable -Wall -Wextra -Wpedantic $<$:-Wpointer-sign>) 109 | 110 | # normal, exclude -Wpedantic to mask warning on micro-ecc __128 111 | target_compile_options(m17-fme PRIVATE -Wunused-but-set-variable -Wall -Wextra $<$:-Wpointer-sign>) 112 | 113 | include(GNUInstallDirs) 114 | install(TARGETS m17-fme DESTINATION ${CMAKE_INSTALL_BINDIR}) 115 | 116 | # uninstall target 117 | configure_file( 118 | "cmake/cmake_uninstall.cmake.in" 119 | "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" 120 | IMMEDIATE @ONLY) 121 | 122 | add_custom_target(uninstall 123 | COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) -------------------------------------------------------------------------------- /src/net/net_udp.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * net_udp.c 3 | * M17 Project - Network Functions for UDP/IP Frame Input and Output 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | struct sockaddr_in addressM17; 12 | struct sockaddr_in addressM17duplex; 13 | 14 | int udp_socket_bind(char *hostname, int portno) 15 | { 16 | UNUSED(hostname); 17 | 18 | int sockfd; 19 | struct sockaddr_in serveraddr; 20 | 21 | /* socket: create the socket */ 22 | //UDP socket 23 | sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 24 | 25 | if (sockfd < 0) 26 | { 27 | fprintf(stderr,"ERROR opening UDP socket\n"); 28 | error("ERROR opening UDP socket"); 29 | } 30 | 31 | /* build the server's Internet address */ 32 | bzero((char *) &serveraddr, sizeof(serveraddr)); 33 | serveraddr.sin_family = AF_INET; 34 | serveraddr.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY 35 | serveraddr.sin_port = htons(portno); 36 | 37 | //Bind socket to listening 38 | if (bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) 39 | { 40 | perror("ERROR on binding UDP Port"); 41 | } 42 | 43 | //set these for non blocking when no samples to read 44 | struct timeval read_timeout; 45 | read_timeout.tv_sec = 0; 46 | read_timeout.tv_usec = 10; 47 | setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &read_timeout, sizeof read_timeout); 48 | 49 | return sockfd; 50 | } 51 | 52 | int m17_socket_blaster(Super * super, size_t nsam, void * data) 53 | { 54 | int err = 0; 55 | err = sendto(super->opts.m17_udp_sock, data, nsam, 0, (const struct sockaddr * ) & addressM17, sizeof(struct sockaddr_in)); 56 | 57 | //write IP Frame to file (any encoded) <--needs testing on text messages, may not want to do those on ad-hoc 58 | if (super->ip_io.use_ip_frame_out == 1) 59 | write_ip_frame_to_file(super, data, nsam); 60 | 61 | return (err); 62 | } 63 | 64 | int udp_socket_connectM17(Super * super) 65 | { 66 | long int err = 0; 67 | err = super->opts.m17_udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 68 | if (err < 0) 69 | { 70 | fprintf (stderr, " UDP Socket Error %ld\n", err); 71 | return (err); 72 | } 73 | 74 | // Don't think this is needed, but doesn't seem to hurt to keep it here either 75 | int broadcastEnable = 1; 76 | err = setsockopt(super->opts.m17_udp_sock, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)); 77 | 78 | if (err < 0) 79 | { 80 | fprintf (stderr, " UDP Broadcast Set Error %ld\n", err); 81 | return (err); 82 | } 83 | 84 | //set these for non blocking when no samples to read (or speed up responsiveness to ncurses) 85 | struct timeval read_timeout; 86 | read_timeout.tv_sec = 0; 87 | read_timeout.tv_usec = 10; 88 | err = setsockopt(super->opts.m17_udp_sock, SOL_SOCKET, SO_RCVTIMEO, &read_timeout, sizeof read_timeout); 89 | 90 | if (err < 0) 91 | { 92 | fprintf (stderr, " UDP Read Timeout Set Error %ld\n", err); 93 | return (err); 94 | } 95 | 96 | memset((char * ) & addressM17, 0, sizeof(addressM17)); 97 | addressM17.sin_family = AF_INET; 98 | // err = addressM17.sin_addr.s_addr = inet_addr(super->opts.m17_hostname); //old method, fallback if issues arise 99 | err = inet_aton(super->opts.m17_hostname, &addressM17.sin_addr); //inet_aton handles broadcast .255 addresses correctly in some environments (Raspbian GNU/Linux 11 (bullseye) armv7l) 100 | if (err < 0) 101 | { 102 | fprintf (stderr, " UDP inet_addr Error %ld\n", err); 103 | return (err); 104 | } 105 | 106 | addressM17.sin_port = htons(super->opts.m17_portno); 107 | if (err < 0) 108 | { 109 | fprintf (stderr, " UDP htons Error %ld\n", err); 110 | return (err); 111 | } 112 | 113 | return (0); //no error 114 | } 115 | 116 | int m17_socket_receiver(Super * super, void * data) 117 | { 118 | size_t err = 0; 119 | struct sockaddr_in cliaddr; 120 | socklen_t len = sizeof(cliaddr); 121 | 122 | //receive data from socket 123 | err = recvfrom(super->opts.m17_udp_sock, data, 1000, 0, (struct sockaddr * ) & addressM17, &len); 124 | 125 | return err; 126 | } 127 | 128 | int m17_socket_receiver_duplex(int m17_udp_socket_duplex, void * data) 129 | { 130 | size_t err = 0; 131 | struct sockaddr_in cliaddr; 132 | socklen_t len = sizeof(cliaddr); 133 | 134 | //receive data from socket 135 | err = recvfrom(m17_udp_socket_duplex, data, 1000, 0, (struct sockaddr * ) & addressM17duplex, &len); 136 | 137 | return err; 138 | } 139 | 140 | void error(char *msg) 141 | { 142 | perror(msg); 143 | exit(0); 144 | } -------------------------------------------------------------------------------- /src/io/audio.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * audio.c 3 | * M17 Project - Audio Related Functions 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | //convenience function to retrieve 1 short input sample from any hardware or sndfile rf audio 12 | short get_short_audio_input_sample (Super * super) 13 | { 14 | short sample = 0; 15 | 16 | //SNFILE Audio 17 | if (super->opts.use_snd_input == 1) 18 | sample = snd_input_read(super); 19 | 20 | #ifdef USE_PULSEAUDIO 21 | //PULSE AUDIO (obviously) 22 | else if (super->opts.use_pa_input == 1) 23 | sample = pa_input_read(super); 24 | else if (super->opts.use_pa_input_vx == 1) 25 | sample = pa_input_read_vx(super); 26 | #endif 27 | 28 | //OSS Hot Garbage 29 | else if (super->opts.use_oss_input == 1) 30 | sample = oss_input_read(super); 31 | 32 | //contribute sample to raw audio monitor for playback if enabled 33 | raw_audio_monitor (super, sample); 34 | 35 | return sample; 36 | } 37 | 38 | //simple 6x 8K to 48K upsample 39 | void upsample_6x(short input, short * output) 40 | { 41 | int i; 42 | for (i = 0; i < 6; i++) 43 | output[i] = input; 44 | } 45 | 46 | //generic rms function to use (use it after hpf) 47 | long int raw_rms(int16_t *samples, int len, int step) 48 | { 49 | 50 | int i; 51 | long int rms; 52 | long p, t, s; 53 | double dc, err; 54 | 55 | p = t = 0L; 56 | for (i=0; iopts.input_gain_rf; 76 | } 77 | 78 | //applies a float gain value to input voice samples 79 | void input_gain_vx (Super * super, short * input, int len) 80 | { 81 | int i; 82 | for (i = 0; i < len; i++) 83 | input[i] *= super->opts.input_gain_vx; 84 | } 85 | 86 | //applies a float gain value to output rf samples 87 | void output_gain_rf (Super * super, short * input, int len) 88 | { 89 | int i; 90 | for (i = 0; i < len; i++) 91 | input[i] *= super->opts.output_gain_rf; 92 | } 93 | 94 | //applies a float gain value to output voice samples 95 | void output_gain_vx (Super * super, short * input, int len) 96 | { 97 | int i; 98 | for (i = 0; i < len; i++) 99 | { 100 | input[i] *= super->opts.output_gain_vx; 101 | 102 | //clip gaurd 103 | if (input[i] > 32760) 104 | input[i] = 32760; 105 | else if (input[i] < -32760) 106 | input[i] = -32760; 107 | } 108 | } 109 | 110 | //reset auto gain at EOT (no_carrier) 111 | void reset_auto_gain_vx (Super * super) 112 | { 113 | memset (super->demod.max_history_buffer, 0, 256*sizeof(float)); 114 | super->demod.max_history_buffer_ptr = 0; 115 | super->opts.output_gain_vx = 1.0f; 116 | } 117 | 118 | //calculate and apply auto gain to any set of input sample 119 | void auto_gain_vx (Super * super, short * input, int len) 120 | { 121 | 122 | float abs_value = 0.0f; 123 | float max_samp = 0.0f; 124 | float max_buf = 0.0f; 125 | int max_mod = 12*2; //was 256 previously 126 | 127 | float gain_factor_clamp = 30.0f; //was 25.0f previously 128 | float target_amp_percent = 0.75f; //was 1.0f previously 129 | float target_amplitude = 16384.0f * target_amp_percent; 130 | 131 | for (int i = 0; i < len; i++) 132 | { 133 | abs_value = fabsf((float)input[i]); 134 | if (abs_value > max_samp) 135 | max_samp = abs_value; 136 | 137 | } 138 | super->demod.max_history_buffer[ super->demod.max_history_buffer_ptr % max_mod ] = max_samp; 139 | 140 | //debug 141 | // fprintf (stderr, " S Max: %f; ", max_samp); 142 | 143 | //lookup max history 144 | for (int i = super->demod.max_history_buffer_ptr; i < super->demod.max_history_buffer_ptr+max_mod; i++) 145 | { 146 | abs_value = super->demod.max_history_buffer[i%max_mod]; 147 | if (abs_value > max_buf) 148 | max_buf = abs_value; 149 | } 150 | 151 | //add a miniscule value so we don't divide by zero 152 | if (max_buf == 0.0f) 153 | max_buf += 0.01f; 154 | 155 | //debug 156 | // fprintf (stderr, " H Max: %f; ", max_buf); 157 | 158 | //make adjustment to gain 159 | if (super->opts.auto_gain_voice == 1) 160 | { 161 | float gain_factor = target_amplitude / max_buf; 162 | 163 | if (gain_factor > 0.01f && gain_factor <= gain_factor_clamp) 164 | super->opts.output_gain_vx = gain_factor; 165 | // else if (super->opts.output_gain_vx > 1.25f) //decay by 25% 166 | // super->opts.output_gain_vx -= 0.25f; 167 | else super->opts.output_gain_vx = 1.0f; //set to 100% 168 | 169 | //debug 170 | float dB = 20.0f * log10f(max_buf / 32768); 171 | if (super->opts.demod_verbosity >= 1) 172 | fprintf (stderr, " Gain Factor: %0.1f; dB: %0.1f;", gain_factor, dB); 173 | } 174 | 175 | //increment index pointer 176 | super->demod.max_history_buffer_ptr++; 177 | 178 | output_gain_vx(super, input, len); 179 | 180 | } 181 | 182 | void raw_audio_monitor (Super * super, short sample) 183 | { 184 | //add sample to the raw audio buffer 185 | super->demod.raw_audio_buffer[super->demod.raw_audio_buffer_ptr++] = sample; 186 | 187 | //if buffer is at saturation, playback and discharge buffer 188 | if (super->demod.raw_audio_buffer_ptr >= 960) 189 | { 190 | 191 | //playback buffered samples, if raw audio monitor is enabled and no sync 192 | if (super->opts.use_raw_audio_monitor && !super->demod.in_sync) 193 | { 194 | //Pulse Audio Playback 195 | #ifdef USE_PULSEAUDIO 196 | if (super->pa.pa_output_vx_is_open == 1) 197 | pulse_audio_output_vx(super, super->demod.raw_audio_buffer, 960); 198 | #else 199 | if (super->pa.pa_output_vx_is_open == 1) {} 200 | #endif 201 | 202 | //OSS 203 | else if (super->opts.oss_output_device) 204 | oss_output_write(super, super->demod.raw_audio_buffer, 960); 205 | 206 | //STDOUT 207 | else if (super->opts.stdout_pipe) 208 | write_stdout_pipe(super, super->demod.raw_audio_buffer, 960); 209 | 210 | } 211 | 212 | //discharge samples and reset pointer 213 | memset (super->demod.raw_audio_buffer, 0, 960*sizeof(short)); 214 | super->demod.raw_audio_buffer_ptr = 0; 215 | } 216 | } -------------------------------------------------------------------------------- /src/io/pulse.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * pulse.c 3 | * M17 Project - Pulse Audio Handler 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | #ifdef USE_PULSEAUDIO 12 | 13 | int err; char session_name[50]; char app_name[10]; 14 | void open_pulse_audio_input (Super * super) 15 | { 16 | char * dev = NULL; 17 | if (super->pa.pa_input_idx[0] != 0) 18 | dev = super->pa.pa_input_idx; 19 | 20 | //differentiate input names so that the mixer will remember the input device used locally 21 | if (super->opts.use_m17_rfa_decoder == 1 || super->opts.use_m17_duplex_mode == 1) 22 | { 23 | sprintf (session_name, "RF Input %04X", super->opts.random_number); 24 | sprintf (app_name, "M17-FME1"); 25 | } 26 | else 27 | { 28 | sprintf (session_name, "Voice Input %04X", super->opts.random_number); 29 | sprintf (app_name, "M17-FME2"); 30 | } 31 | super->pa.pa_input_device = pa_simple_new(NULL, app_name, PA_STREAM_RECORD, dev, session_name, &super->pa.input, NULL, &super->pa.inputlt, &err); 32 | super->pa.pa_input_is_open = 1; 33 | if (err != 0) 34 | { 35 | fprintf (stderr, "%s", pa_strerror(err)); 36 | #ifdef __CYGWIN__ 37 | fprintf (stderr, "Please make sure the Pulse Audio Server Backend is running first."); 38 | #endif 39 | exit(0); 40 | } 41 | } 42 | 43 | void open_pulse_audio_input_vx (Super * super) 44 | { 45 | char * dev = NULL; 46 | if (super->pa.pa_invx_idx[0] != 0) 47 | dev = super->pa.pa_invx_idx; 48 | 49 | sprintf (session_name, "Voice Input %04X", super->opts.random_number); 50 | sprintf (app_name, "M17-FME2"); 51 | 52 | super->pa.pa_input_device_vx = pa_simple_new(NULL, app_name, PA_STREAM_RECORD, dev, session_name, &super->pa.input, NULL, &super->pa.inputlt, &err); 53 | super->pa.pa_input_vx_is_open = 1; 54 | if (err != 0) 55 | { 56 | fprintf (stderr, "%s", pa_strerror(err)); 57 | #ifdef __CYGWIN__ 58 | fprintf (stderr, "Please make sure the Pulse Audio Server Backend is running first."); 59 | #endif 60 | exit(0); 61 | } 62 | } 63 | 64 | void open_pulse_audio_input_rf (Super * super) 65 | { 66 | char * dev = NULL; 67 | if (super->pa.pa_input_idx[0] != 0) 68 | dev = super->pa.pa_input_idx; 69 | 70 | sprintf (session_name, "RF Input %04X", super->opts.random_number); 71 | sprintf (app_name, "M17-FME1"); 72 | 73 | super->pa.pa_input_device = pa_simple_new(NULL, app_name, PA_STREAM_RECORD, dev, session_name, &super->pa.input, NULL, &super->pa.inputlt, &err); 74 | super->pa.pa_input_is_open = 1; 75 | if (err != 0) 76 | { 77 | fprintf (stderr, "%s", pa_strerror(err)); 78 | #ifdef __CYGWIN__ 79 | fprintf (stderr, "Please make sure the Pulse Audio Server Backend is running first."); 80 | #endif 81 | exit(0); 82 | } 83 | } 84 | 85 | void open_pulse_audio_output_rf (Super * super) 86 | { 87 | 88 | char * dev = NULL; 89 | if (super->pa.pa_outrf_idx[0] != 0) 90 | dev = super->pa.pa_outrf_idx; 91 | 92 | sprintf (session_name, "RF Output %04X", super->opts.random_number); 93 | super->pa.pa_output_device_rf = pa_simple_new(NULL, "M17-FME3", PA_STREAM_PLAYBACK, dev, session_name, &super->pa.output_rf, NULL, NULL, &err); 94 | super->pa.pa_output_rf_is_open = 1; 95 | if (err != 0) 96 | { 97 | fprintf (stderr, "%s", pa_strerror(err)); 98 | #ifdef __CYGWIN__ 99 | fprintf (stderr, "Please make sure the Pulse Audio Server Backend is running first."); 100 | #endif 101 | exit(0); 102 | } 103 | } 104 | 105 | void open_pulse_audio_output_vx (Super * super) 106 | { 107 | char * dev = NULL; 108 | if (super->pa.pa_outvx_idx[0] != 0) 109 | dev = super->pa.pa_outvx_idx; 110 | 111 | sprintf (session_name, "Voice Output %04X", super->opts.random_number); 112 | super->pa.pa_output_device_vx = pa_simple_new(NULL, "M17-FME4", PA_STREAM_PLAYBACK, dev, session_name, &super->pa.output_vx, NULL, NULL, &err); 113 | super->pa.pa_output_vx_is_open = 1; 114 | if (err != 0) 115 | { 116 | fprintf (stderr, "%s", pa_strerror(err)); 117 | #ifdef __CYGWIN__ 118 | fprintf (stderr, "Please make sure the Pulse Audio Server Backend is running first."); 119 | #endif 120 | exit(0); 121 | } 122 | } 123 | 124 | void close_pulse_audio_input (Super * super) 125 | { 126 | pa_simple_free (super->pa.pa_input_device); 127 | super->pa.pa_input_is_open = 0; 128 | } 129 | 130 | void close_pulse_audio_input_vx (Super * super) 131 | { 132 | pa_simple_free (super->pa.pa_input_device_vx); 133 | super->pa.pa_input_vx_is_open = 0; 134 | } 135 | 136 | void close_pulse_audio_output_rf (Super * super) 137 | { 138 | pa_simple_drain(super->pa.pa_output_device_rf, NULL); 139 | pa_simple_free (super->pa.pa_output_device_rf); 140 | super->pa.pa_output_rf_is_open = 0; 141 | } 142 | 143 | void close_pulse_audio_output_vx (Super * super) 144 | { 145 | pa_simple_drain(super->pa.pa_output_device_vx, NULL); 146 | pa_simple_free (super->pa.pa_output_device_vx); 147 | super->pa.pa_output_vx_is_open = 0; 148 | } 149 | 150 | //return a single short sample from pulse audio input 151 | short pa_input_read (Super * super) 152 | { 153 | short sample = 0; 154 | pa_simple_read(super->pa.pa_input_device, &sample, 2, &err); 155 | if (err != 0) 156 | { 157 | fprintf (stderr, "%s", pa_strerror(err)); 158 | // exit(0); //pretty sure the above, if error, will just core dump before we can look anyways 159 | } 160 | 161 | return sample; 162 | } 163 | 164 | //return a single short sample from pulse audio input 165 | short pa_input_read_vx (Super * super) 166 | { 167 | short sample = 0; 168 | pa_simple_read(super->pa.pa_input_device_vx, &sample, 2, &err); 169 | if (err != 0) 170 | { 171 | fprintf (stderr, "%s", pa_strerror(err)); 172 | // exit(0); //pretty sure the above, if error, will just core dump before we can look anyways 173 | } 174 | 175 | return sample; 176 | } 177 | 178 | void pulse_audio_output_rf(Super * super, short * out, size_t nsam) 179 | { 180 | pa_simple_write(super->pa.pa_output_device_rf, out, nsam*2, &err); 181 | if (err != 0) 182 | { 183 | fprintf (stderr, "%s", pa_strerror(err)); 184 | // exit(0); //pretty sure the condition on this is that it blocks until discharged 185 | } 186 | } 187 | 188 | void pulse_audio_output_vx(Super * super, short * out, size_t nsam) 189 | { 190 | pa_simple_write(super->pa.pa_output_device_vx, out, nsam*2, &err); 191 | if (err != 0) 192 | { 193 | fprintf (stderr, "%s", pa_strerror(err)); 194 | // exit(0); //pretty sure the condition on this is that it blocks until discharged 195 | } 196 | } 197 | 198 | #endif -------------------------------------------------------------------------------- /src/decoder/m17_brt_demodulator.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_brt_demodulator.c 3 | * M17 Project - BERT (Bit Error Rate Test) Frame Demodulation and Debug 4 | * 5 | * LWVMOBILE 6 | * 2025-08 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | static uint8_t bert_lfsr_bit_array[197]; 13 | static uint16_t bert_lfsr_seed; 14 | static uint16_t sync_error_count; 15 | 16 | uint16_t brt_lfsr (uint16_t seed, uint8_t * output, uint8_t len) 17 | { 18 | for (int i = 0; i < len; i++) 19 | { 20 | uint8_t bit = ((seed >> 8) ^ (seed >> 4)) & 1; 21 | seed <<= 1; 22 | seed |= bit; 23 | output[i] = bit; 24 | } 25 | seed &= 0x1FF; 26 | return seed; 27 | } 28 | 29 | void init_brt(void) 30 | { 31 | memset (bert_lfsr_bit_array, 0, sizeof(bert_lfsr_bit_array)); 32 | bert_lfsr_seed = brt_lfsr(1, bert_lfsr_bit_array, 197); 33 | sync_error_count = 0; 34 | } 35 | 36 | uint32_t prepare_brt(float * sbuf, uint8_t * output_bits) 37 | { 38 | 39 | uint16_t soft_bit[2*SYM_PER_PLD]; //raw frame soft bits 40 | uint16_t d_soft_bit[2*SYM_PER_PLD]; //deinterleaved soft bits 41 | uint8_t viterbi_bytes[31]; //packed viterbi return bytes 42 | uint32_t error = 0; //viterbi error 43 | 44 | memset(soft_bit, 0, sizeof(soft_bit)); 45 | memset(d_soft_bit, 0, sizeof(d_soft_bit)); 46 | memset(viterbi_bytes, 0, sizeof(viterbi_bytes)); 47 | 48 | //libm17 magic 49 | //slice symbols to soft dibits 50 | slice_symbols(soft_bit, sbuf); 51 | 52 | //derandomize 53 | randomize_soft_bits(soft_bit); 54 | 55 | //deinterleave 56 | reorder_soft_bits(d_soft_bit, soft_bit); 57 | 58 | //viterbi 59 | error = viterbi_decode_punctured(viterbi_bytes, d_soft_bit, p2, 368, 12); 60 | 61 | //debug 62 | // fprintf (stderr, "\n VB: "); 63 | // for (int i = 1; i < 26; i++) 64 | // fprintf (stderr, "%02X ", viterbi_bytes[i]); 65 | 66 | //load viterbi_bytes into bits for either data packets or voice packets 67 | unpack_byte_array_into_bit_array(viterbi_bytes, output_bits, 26); 68 | 69 | return error; 70 | 71 | } 72 | 73 | void demod_brt(Super * super, uint8_t * input, int debug) 74 | { 75 | //quell defined but not used warnings from m17.h 76 | stfu (); 77 | 78 | int i; 79 | 80 | uint8_t dbuf[184]; //384-bit frame - 16-bit (8 symbol) sync pattern (184 dibits) 81 | float sbuf[184]; //float symbol buffer 82 | 83 | memset (dbuf, 0, sizeof(dbuf)); 84 | memset (sbuf, 0, sizeof(sbuf)); 85 | 86 | //if not running in debug / encoder mode, then perform dibit collection 87 | if (debug == 0) 88 | { 89 | //load dibits into dibit buffer 90 | for (i = 0; i < 184; i++) 91 | dbuf[i] = get_dibit(super); 92 | 93 | //convert dbuf into a symbol array 94 | for (i = 0; i < 184; i++) 95 | { 96 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 97 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 98 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 99 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 100 | else sbuf[i] = +0.0f; 101 | } 102 | 103 | } 104 | else //we are debugging, and copy input to m17_rnd_bits, and convert input to symbols 105 | { 106 | 107 | //load dibits into dibit buffer from input bits 108 | for (i = 0; i < 184; i++) 109 | dbuf[i] = (input[(i*2)+0] << 1) | input[(i*2)+1]; 110 | 111 | //convert dbuf into a symbol array 112 | for (i = 0; i < 184; i++) 113 | { 114 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 115 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 116 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 117 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 118 | else sbuf[i] = +0.0f; 119 | } 120 | 121 | } 122 | 123 | uint8_t bert_bits[216]; //little extra, needed for the offset from viterbi 124 | memset (bert_bits, 0, sizeof(bert_bits)); 125 | 126 | uint32_t ve = prepare_brt(sbuf, bert_bits); 127 | 128 | uint8_t sync_error_bit_count = 0; 129 | uint8_t full_bit_error_count = 0; 130 | 131 | //debug flip a sync bit to test logic 132 | // bert_bits[7] ^= 1; 133 | 134 | //first 18 for sync period test (twice len of PRBS9) 135 | for (i = 0; i < 18; i++) 136 | { 137 | if (bert_bits[i+7] != bert_lfsr_bit_array[i]) //+7 offset 138 | { 139 | sync_error_bit_count++; 140 | full_bit_error_count++; 141 | } 142 | } 143 | 144 | //do remaining for full bit rx error 145 | for (i = 18; i < 197; i++) 146 | { 147 | if (bert_bits[i+7] != bert_lfsr_bit_array[i]) //+7 offset 148 | full_bit_error_count++; 149 | } 150 | 151 | if (super->opts.payload_verbosity > 0) 152 | { 153 | fprintf (stderr, "\n LFSR Bits: "); 154 | for (i = 0; i < 197; i++) 155 | fprintf (stderr, "%d", bert_lfsr_bit_array[i]); 156 | 157 | fprintf (stderr, "\n RX Bits: "); 158 | for (i = 0; i < 197; i++) 159 | fprintf (stderr, "%d", bert_bits[i+7]); //+7 offset 160 | } 161 | 162 | if (sync_error_bit_count != 0) 163 | { 164 | fprintf (stderr, "\n Sync Bit Error: %02d / 18; Full Bit Error: ??? / 197; Ve: %1.1f; LFSR: %03X;", sync_error_bit_count, (float)ve/(float)0xFFFF , bert_lfsr_seed); 165 | memset (bert_lfsr_bit_array, 0, sizeof(bert_lfsr_bit_array)); 166 | bert_lfsr_seed = brt_lfsr(1, bert_lfsr_bit_array, 197); 167 | sync_error_count++; 168 | fprintf (stderr, " LFSR Sync Attempt; %03d / 512;", sync_error_count); 169 | 170 | if (sync_error_count > 512) //9-bit LFSR should reset after 512 attempts, if it doesn't, then heavy reception error 171 | fprintf (stderr, " Sync Failure!"); 172 | } 173 | else if (full_bit_error_count > 18) 174 | { 175 | fprintf (stderr, "\n Sync Bit Error: %02d / 18; Full Bit Error: %03d / 197; Ve: %1.1f; LFSR: %03X;", sync_error_bit_count, full_bit_error_count, (float)ve/(float)0xFFFF , bert_lfsr_seed); 176 | memset (bert_lfsr_bit_array, 0, sizeof(bert_lfsr_bit_array)); 177 | bert_lfsr_seed = brt_lfsr(1, bert_lfsr_bit_array, 197); 178 | sync_error_count = 0; 179 | fprintf (stderr, " Heavy Bit Rate Error! Attempting Resync!"); 180 | } 181 | else 182 | { 183 | fprintf (stderr, "\n Sync Bit Error: %02d / 18; Full Bit Error: %03d / 197; Ve: %1.1f; LFSR: %03X;", sync_error_bit_count, full_bit_error_count, (float)ve/(float)0xFFFF , bert_lfsr_seed); 184 | bert_lfsr_seed = brt_lfsr(bert_lfsr_seed, bert_lfsr_bit_array, 197); //unsure if this advances by only 1, or by next 197 185 | sync_error_count = 0; 186 | fprintf (stderr, " LFSR Sync Okay;"); 187 | } 188 | 189 | super->demod.sync_time = super->demod.current_time = time(NULL); 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/decoder/m17_str_demodulator.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_str_demodulator.c 3 | * M17 Project - Stream Frame Demodulation and Debug 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | void demod_str(Super * super, uint8_t * input, int debug) 13 | { 14 | //quell defined but not used warnings from m17.h 15 | stfu (); 16 | 17 | int i, x; 18 | 19 | uint8_t dbuf[184]; //384-bit frame - 16-bit (8 symbol) sync pattern (184 dibits) 20 | float sbuf[184]; //float symbol buffer 21 | uint8_t m17_rnd_bits[368]; //368 bits that are still scrambled (randomized) 22 | uint8_t m17_int_bits[368]; //368 bits that are still interleaved 23 | uint8_t m17_bits[368]; //368 bits that have been de-interleaved and de-scramble 24 | uint8_t lich_bits[96]; 25 | int lich_err = -1; 26 | 27 | memset (dbuf, 0, sizeof(dbuf)); 28 | memset (sbuf, 0, sizeof(sbuf)); 29 | memset (m17_rnd_bits, 0, sizeof(m17_rnd_bits)); 30 | memset (m17_int_bits, 0, sizeof(m17_int_bits)); 31 | memset (m17_bits, 0, sizeof(m17_bits)); 32 | memset (lich_bits, 0, sizeof(lich_bits)); 33 | 34 | //if not running in debug / encoder mode, then perform dibit collection 35 | if (debug == 0) 36 | { 37 | //load dibits into dibit buffer 38 | for (i = 0; i < 184; i++) 39 | dbuf[i] = get_dibit(super); 40 | 41 | //convert dbuf into a bit array 42 | for (i = 0; i < 184; i++) 43 | { 44 | m17_rnd_bits[i*2+0] = (dbuf[i] >> 1) & 1; 45 | m17_rnd_bits[i*2+1] = (dbuf[i] >> 0) & 1; 46 | } 47 | 48 | //convert dbuf into a symbol array 49 | for (i = 0; i < 184; i++) 50 | { 51 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 52 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 53 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 54 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 55 | else sbuf[i] = +0.0f; 56 | } 57 | 58 | } 59 | else //we are debugging, and copy input to m17_rnd_bits, and convert input to symbols 60 | { 61 | memcpy (m17_rnd_bits, input, 368); 62 | 63 | //load dibits into dibit buffer from input bits 64 | for (i = 0; i < 184; i++) 65 | dbuf[i] = (input[(i*2)+0] << 1) | input[(i*2)+1]; 66 | 67 | //convert dbuf into a symbol array 68 | for (i = 0; i < 184; i++) 69 | { 70 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 71 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 72 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 73 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 74 | else sbuf[i] = +0.0f; 75 | } 76 | 77 | } 78 | 79 | //descramble the frame 80 | for (i = 0; i < 368; i++) 81 | m17_int_bits[i] = (m17_rnd_bits[i] ^ m17_scramble[i]) & 1; 82 | 83 | //deinterleave the bit array using Quadratic Permutation Polynomial 84 | //function π(x) = (45x + 92x^2 ) mod 368 85 | for (i = 0; i < 368; i++) 86 | { 87 | x = ((45*i)+(92*i*i)) % 368; 88 | m17_bits[i] = m17_int_bits[x]; 89 | } 90 | 91 | for (i = 0; i < 96; i++) 92 | lich_bits[i] = m17_bits[i]; 93 | 94 | //check lich first, and handle LSF chunk and completed LSF 95 | lich_err = decode_lich_contents(super, lich_bits); 96 | 97 | if (lich_err == 0) 98 | prepare_str(super, sbuf); 99 | 100 | //get rid of this if it costs too much CPU / skips / lags 101 | super->demod.sync_time = super->demod.current_time = time(NULL); 102 | 103 | //refresh ncurses printer, if enabled 104 | #ifdef USE_CURSES 105 | if (super->opts.use_ncurses_terminal == 1) 106 | print_ncurses_terminal(super); 107 | #endif 108 | 109 | } 110 | 111 | void prepare_str(Super * super, float * sbuf) 112 | { 113 | int i; 114 | uint16_t soft_bit[2*SYM_PER_PLD]; //raw frame soft bits 115 | uint16_t d_soft_bit[2*SYM_PER_PLD]; //deinterleaved soft bits 116 | uint8_t viterbi_bytes[31]; //packed viterbi return bytes 117 | uint32_t error = 0; //viterbi error 118 | 119 | uint8_t stream_bits[144]; //128+16 120 | uint8_t payload[128]; 121 | uint8_t end = 9; 122 | uint16_t fn = 0; 123 | 124 | memset(soft_bit, 0, sizeof(soft_bit)); 125 | memset(d_soft_bit, 0, sizeof(d_soft_bit)); 126 | memset(viterbi_bytes, 0, sizeof(viterbi_bytes)); 127 | 128 | memset (stream_bits, 0, sizeof(stream_bits)); 129 | memset (payload, 0, sizeof(payload)); 130 | 131 | //libm17 magic 132 | //slice symbols to soft dibits 133 | slice_symbols(soft_bit, sbuf); 134 | 135 | //derandomize 136 | randomize_soft_bits(soft_bit); 137 | 138 | //deinterleave 139 | reorder_soft_bits(d_soft_bit, soft_bit); 140 | 141 | //viterbi 142 | error = viterbi_decode_punctured(viterbi_bytes, d_soft_bit+96, p2, 272, 12); 143 | 144 | //track viterbi error / cost metric 145 | super->error.viterbi_err = (float)error/(float)0xFFFF; 146 | 147 | //TODO: BER Estimate 148 | // state->error.ber_estimate; 149 | 150 | //load viterbi_bytes into bits for either data packets or voice packets 151 | unpack_byte_array_into_bit_array(viterbi_bytes+1, stream_bits, 18); //18*8 = 144 152 | 153 | end = stream_bits[0]; 154 | fn = (uint16_t)convert_bits_into_output(&stream_bits[1], 15); 155 | 156 | //for scrambler seed calculation, if required (late entry) 157 | super->enc.scrambler_fn_d = fn; 158 | if (fn == 0) 159 | super->enc.scrambler_seed_d = super->enc.scrambler_key; 160 | 161 | //insert fn bits into meta 14 and meta 15 for Initialization Vector 162 | super->m17d.meta[14] = (uint8_t)convert_bits_into_output(&stream_bits[1], 7); 163 | super->m17d.meta[15] = (uint8_t)convert_bits_into_output(&stream_bits[8], 8); 164 | 165 | if (super->opts.payload_verbosity >= 1) 166 | { 167 | fprintf (stderr, " FSN: %04X", fn); 168 | //viterbi error 169 | fprintf (stderr, " Ve: %1.1f; ", (float)error/(float)0xFFFF); 170 | } 171 | 172 | if (end == 1) 173 | fprintf (stderr, " END;"); 174 | 175 | for (i = 0; i < 128; i++) 176 | payload[i] = stream_bits[i+16]; 177 | 178 | if (super->m17d.dt == 2 || super->m17d.dt == 3) 179 | decode_str_payload(super, payload, super->m17d.dt, fn%6); 180 | else super->m17d.dt = 15; 181 | 182 | //failsafe to still get ECDSA digest if bad initial LSF (better than not attempting it) 183 | //note: dt of 15 will now be rejected by payload decoder after digest 184 | if (super->m17d.ecdsa.keys_loaded == 1 && super->m17d.dt == 15) 185 | decode_str_payload(super, payload, super->m17d.dt, fn%6); 186 | 187 | if (super->opts.payload_verbosity >= 1 && super->m17d.dt == 15) 188 | { 189 | fprintf (stderr, "\n STREAM: "); 190 | for (i = 0; i < 18; i++) 191 | fprintf (stderr, "%02X", (uint8_t)convert_bits_into_output(&stream_bits[i*8], 8)); 192 | } 193 | } -------------------------------------------------------------------------------- /src/io/wav.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * wav.c 3 | * M17 Project - Wav File Handling 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | void open_wav_out_rf (Super * super) 12 | { 13 | SF_INFO info; 14 | info.samplerate = super->opts.output_sample_rate; 15 | info.channels = 1; 16 | info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16 | SF_ENDIAN_LITTLE; 17 | super->wav.wav_out_rf = sf_open (super->wav.wav_out_file_rf, SFM_WRITE, &info); 18 | 19 | if (super->wav.wav_out_rf == NULL) 20 | { 21 | fprintf (stderr,"Error - could not open RF wav output file %s\n", super->wav.wav_out_file_rf); 22 | return; 23 | } 24 | } 25 | 26 | void open_wav_out_vx (Super * super) 27 | { 28 | SF_INFO info; 29 | info.samplerate = super->opts.output_sample_rate; 30 | info.channels = 1; 31 | info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16 | SF_ENDIAN_LITTLE; 32 | super->wav.wav_out_vx = sf_open (super->wav.wav_out_file_vx, SFM_WRITE, &info); 33 | 34 | if (super->wav.wav_out_vx == NULL) 35 | { 36 | fprintf (stderr,"Error - could not open VX wav output file %s\n", super->wav.wav_out_file_vx); 37 | return; 38 | } 39 | } 40 | 41 | void open_wav_out_pc (Super * super) 42 | { 43 | SF_INFO info; 44 | info.samplerate = super->opts.output_sample_rate; 45 | info.channels = 1; 46 | info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16 | SF_ENDIAN_LITTLE; 47 | super->wav.wav_out_pc = sf_open (super->wav.wav_out_file_pc, SFM_WRITE, &info); 48 | 49 | if (super->wav.wav_out_pc == NULL) 50 | { 51 | fprintf (stderr,"Error - could not open Per Call wav output file %s\n", super->wav.wav_out_file_pc); 52 | return; 53 | } 54 | } 55 | 56 | void open_ogg_out_pc (Super * super) 57 | { 58 | SF_INFO info; 59 | info.samplerate = super->opts.output_sample_rate; 60 | info.channels = 1; 61 | info.format = SF_FORMAT_OGG | SF_FORMAT_OPUS; 62 | super->wav.wav_out_pc = sf_open (super->wav.wav_out_file_pc, SFM_WRITE, &info); 63 | 64 | if (super->wav.wav_out_pc == NULL) 65 | { 66 | fprintf (stderr,"Error - could not open Per Call ogg output file %s\n", super->wav.wav_out_file_pc); 67 | return; 68 | } 69 | } 70 | 71 | void setup_percall_filename (Super * super) 72 | { 73 | int i; 74 | char * datestr = get_date(); 75 | char * timestr = get_time(); 76 | 77 | //make a local copy that can be parsed to remove any special characters 78 | char src_csd[10]; memset (src_csd, 0, 10*sizeof(char)); 79 | char dst_csd[10]; memset (dst_csd, 0, 10*sizeof(char)); 80 | memcpy (src_csd, super->m17d.src_csd_str, 9); 81 | memcpy (dst_csd, super->m17d.dst_csd_str, 9); 82 | for (i = 0; i < 9; i++) 83 | { 84 | if (src_csd[i] == 0x20) //change 'space' to underscore 85 | src_csd[i] = 0x5F; 86 | 87 | if (dst_csd[i] == 0x20) //change 'space' to underscore 88 | dst_csd[i] = 0x5F; 89 | 90 | if (src_csd[i] == 0x2F) //change forward slash to underscore 91 | src_csd[i] = 0x5F; 92 | 93 | if (dst_csd[i] == 0x2F) //change forward slash to underscore 94 | dst_csd[i] = 0x5F; 95 | 96 | if (src_csd[i] == 0x2E) //change 'period / full stop' to underscore 97 | src_csd[i] = 0x5F; 98 | 99 | if (dst_csd[i] == 0x2E) //change 'period / full stop' to underscore 100 | dst_csd[i] = 0x5F; 101 | 102 | } 103 | 104 | //NOTE: .wav extension is not included, will be renamed with .wav when closed 105 | sprintf (super->wav.wav_out_file_pc, "%s/%s_%s_%04X_CAN_%d_SRC_%s_DST_%s", 106 | super->wav.wav_file_direct, datestr, timestr, super->opts.random_number, super->m17d.can, src_csd, dst_csd); 107 | 108 | free (datestr); free(timestr); 109 | 110 | //send per call to event_log_writer 111 | // event_log_writer (super, super->wav.wav_out_file_pc, 0xFD); //should I disable this? 112 | 113 | //depreciated .wav for per call 114 | // open_wav_out_pc(super); 115 | 116 | //.ogg file for per call 117 | open_ogg_out_pc(super); 118 | 119 | } 120 | 121 | void close_wav_out_rf (Super * super) 122 | { 123 | sf_close(super->wav.wav_out_rf); 124 | } 125 | 126 | void close_wav_out_vx (Super * super) 127 | { 128 | sf_close(super->wav.wav_out_vx); 129 | } 130 | 131 | void close_wav_out_pc (Super * super) 132 | { 133 | sf_close(super->wav.wav_out_pc); 134 | 135 | //give extension .wav after closing 136 | char newfilename[1037]; 137 | sprintf (newfilename, "%s.wav", super->wav.wav_out_file_pc); 138 | rename (super->wav.wav_out_file_pc, newfilename); 139 | 140 | //set pointer to NULL 141 | super->wav.wav_out_pc = NULL; 142 | 143 | //copy filename back for ncurses display 144 | memcpy(super->wav.wav_out_file_pc, newfilename, 1023); 145 | super->wav.wav_out_file_pc[1023] = 0; 146 | 147 | //send per call to event_log_writer 148 | // event_log_writer (super, super->wav.wav_out_file_pc, 0xFE); //should I disable this? 149 | } 150 | 151 | void close_ogg_out_pc (Super * super) 152 | { 153 | sf_close(super->wav.wav_out_pc); 154 | 155 | //give extension .ogg after closing 156 | char newfilename[1037]; 157 | sprintf (newfilename, "%s.ogg", super->wav.wav_out_file_pc); 158 | rename (super->wav.wav_out_file_pc, newfilename); 159 | 160 | //set pointer to NULL 161 | super->wav.wav_out_pc = NULL; 162 | 163 | //copy filename back for ncurses display 164 | memcpy(super->wav.wav_out_file_pc, newfilename, 1023); 165 | super->wav.wav_out_file_pc[1023] = 0; 166 | 167 | //send per call to event_log_writer 168 | // event_log_writer (super, super->wav.wav_out_file_pc, 0xFE); //should I disable this? 169 | } 170 | 171 | void write_wav_out_rf (Super * super, short * out, size_t nsam) 172 | { 173 | sf_write_short(super->wav.wav_out_rf, out, nsam); 174 | } 175 | 176 | void write_wav_out_vx (Super * super, short * out, size_t nsam) 177 | { 178 | sf_write_short(super->wav.wav_out_vx, out, nsam); 179 | } 180 | 181 | void write_snd_out_pc (Super * super, short * out, size_t nsam) 182 | { 183 | sf_write_short(super->wav.wav_out_pc, out, nsam); 184 | } 185 | 186 | //move? 187 | bool stdin_snd_audio_source_open (Super * super) 188 | { 189 | bool err = false; 190 | super->snd_src_in.audio_in_file = sf_open_fd(fileno(stdin), SFM_READ, super->snd_src_in.audio_in_file_info, 0); 191 | if (super->snd_src_in.audio_in_file != NULL) 192 | err = true; 193 | return err; 194 | } 195 | 196 | bool file_snd_audio_source_open (Super * super) 197 | { 198 | bool err = false; 199 | super->snd_src_in.audio_in_file = sf_open(super->snd_src_in.snd_in_filename, SFM_READ, super->snd_src_in.audio_in_file_info); 200 | if (super->snd_src_in.audio_in_file != NULL) 201 | err = true; 202 | return err; 203 | } 204 | 205 | short snd_input_read (Super * super) 206 | { 207 | short sample = 0; 208 | int result = sf_read_short(super->snd_src_in.audio_in_file, &sample, 1); 209 | if(result == 0) 210 | { 211 | sf_close(super->snd_src_in.audio_in_file); 212 | super->snd_src_in.audio_in_file = NULL; 213 | exitflag = 1; 214 | } 215 | return sample; 216 | } -------------------------------------------------------------------------------- /src/decoder/m17_pkt_demodulator.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_pkt_demodulator.c 3 | * M17 Project - Packet Frame Demodulation and Debug 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | void demod_pkt(Super * super, uint8_t * input, int debug) 13 | { 14 | //quell defined but not used warnings from m17.h 15 | stfu (); 16 | 17 | int i; 18 | uint8_t dbuf[184]; //384-bit frame - 16-bit (8 symbol) sync pattern (184 dibits) 19 | float sbuf[184]; //float symbol buffer 20 | uint16_t soft_bit[2*SYM_PER_PLD]; //raw frame soft bits 21 | uint16_t d_soft_bit[2*SYM_PER_PLD]; //deinterleaved soft bits 22 | uint8_t viterbi_bytes[31]; //packed viterbi return bytes 23 | uint32_t error = 0; //viterbi error 24 | 25 | memset(dbuf, 0, sizeof(dbuf)); 26 | memset(sbuf, 0.0f, sizeof(sbuf)); 27 | memset(soft_bit, 0, sizeof(soft_bit)); 28 | memset(d_soft_bit, 0, sizeof(d_soft_bit)); 29 | memset(viterbi_bytes, 0, sizeof(viterbi_bytes)); 30 | 31 | //if not running in debug / encoder mode, then perform dibit collection 32 | if (debug == 0) 33 | { 34 | //load dibits into dibit buffer 35 | for (i = 0; i < 184; i++) 36 | dbuf[i] = get_dibit(super); 37 | 38 | //convert dbuf into a symbol array 39 | for (i = 0; i < 184; i++) 40 | { 41 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 42 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 43 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 44 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 45 | else sbuf[i] = +0.0f; 46 | } 47 | 48 | } 49 | else //we are debugging, and convert input to symbols 50 | { 51 | //load dibits into dibit buffer from input bits 52 | for (i = 0; i < 184; i++) 53 | dbuf[i] = (input[(i*2)+0] << 1) | input[(i*2)+1]; 54 | 55 | //convert dbuf into a symbol array 56 | for (i = 0; i < 184; i++) 57 | { 58 | if (dbuf[i] == 0) sbuf[i] = +1.0f; 59 | else if (dbuf[i] == 1) sbuf[i] = +3.0f; 60 | else if (dbuf[i] == 2) sbuf[i] = -1.0f; 61 | else if (dbuf[i] == 3) sbuf[i] = -3.0f; 62 | else sbuf[i] = +0.0f; 63 | } 64 | } 65 | 66 | //libm17 magic 67 | //slice symbols to soft dibits 68 | slice_symbols(soft_bit, sbuf); 69 | 70 | //derandomize 71 | randomize_soft_bits(soft_bit); 72 | 73 | //deinterleave 74 | reorder_soft_bits(d_soft_bit, soft_bit); 75 | 76 | //viterbi 77 | error = viterbi_decode_punctured(viterbi_bytes, d_soft_bit, p3, 2*SYM_PER_PLD, 8); 78 | 79 | //track viterbi error / cost metric 80 | super->error.viterbi_err = (float)error/(float)0xFFFF; 81 | 82 | //TODO: BER Estimate 83 | // state->error.ber_estimate; 84 | 85 | uint8_t pkt_packed[26]; 86 | memset (pkt_packed, 0, sizeof(pkt_packed)); 87 | 88 | //pack to local 89 | memcpy (pkt_packed, viterbi_bytes+1, 26); 90 | 91 | //local variables 92 | uint8_t counter = (pkt_packed[25] >> 2) & 0x1F; 93 | uint8_t eot = (pkt_packed[25] >> 7) & 1; 94 | 95 | int ptr = super->m17d.pbc_ptr*25; 96 | 97 | //sanity check to we don't go out of bounds on memcpy and total (core dump) 98 | if (ptr > 825) ptr = 825; 99 | if (ptr < 0) ptr = 0; 100 | 101 | int total = ptr + counter - 3; //-3 if changes to M17_Implementations are made 102 | 103 | //sanity check on total 104 | if (total < 0 && eot == 1) total = 0; //this is from a bad decode, and caused a core dump on total being a negative value 105 | 106 | int end = ptr + 25; 107 | 108 | //debug counter and eot value 109 | if (super->opts.payload_verbosity) 110 | { 111 | if (!eot) fprintf (stderr, "CNT: %02d; PBC: %02d; EOT: %d; ", super->m17d.pbc_ptr, counter, eot); 112 | else fprintf (stderr, "CNT: %02d; LST: %02d; EOT: %d; ", super->m17d.pbc_ptr, counter, eot); 113 | // fprintf (stderr, "PTR: %d; Total: %d; ", ptr, total); //internal debug only 114 | fprintf (stderr, "Ve: %1.1f; ", (float)error/(float)0xFFFF); 115 | } 116 | else 117 | { 118 | if (!eot) fprintf (stderr, "PKT#: %02d;", counter); 119 | else fprintf (stderr, "PKT#: XX;"); 120 | } 121 | 122 | //put packet into storage 123 | memcpy (super->m17d.pkt+ptr, pkt_packed, 25); 124 | 125 | //individual frame packet 126 | if (super->opts.payload_verbosity >= 1) 127 | { 128 | fprintf (stderr, "\n pkt: "); 129 | for (i = 0; i < 26; i++) 130 | fprintf (stderr, "%02X", pkt_packed[i]); 131 | } 132 | 133 | //evaluate completed packet if eot bit is signalled in current packet 134 | if (eot) 135 | { 136 | //do a CRC check 137 | uint16_t crc_cmp = crc16(super->m17d.pkt, total+1); 138 | uint16_t crc_ext = (super->m17d.pkt[total+1] << 8) + super->m17d.pkt[total+2]; 139 | 140 | // optimal location? 141 | if (crc_cmp != crc_ext) 142 | fprintf (stderr, " (CRC ERR) "); 143 | 144 | //error tracking 145 | if (crc_cmp != crc_ext) super->error.pkt_crc_err++; 146 | 147 | //create and apply encryption keystream to super->m17d.pkt at this 148 | //point, after protocol byte, and prior to terminating byte and CRC 149 | if ((super->m17d.enc_et == 1 && super->enc.scrambler_key) || 150 | (super->m17d.enc_et == 2 && super->enc.aes_key_is_loaded) ) 151 | { 152 | 153 | //keystream bit and byte arrays 154 | uint8_t ks_bits[7680]; memset(ks_bits, 0, sizeof(ks_bits)); 155 | uint8_t ks_bytes[960]; memset(ks_bytes, 0, sizeof(ks_bytes)); 156 | 157 | enc_pkt_ks_creation(super, ks_bits, ks_bytes, 0); 158 | for (i = 1; i < total; i++) 159 | super->m17d.pkt[i] ^= ks_bytes[i-1]; 160 | 161 | //reset meta (iv) after use 162 | memset(super->m17d.meta, 0, sizeof(super->m17e.meta)); 163 | 164 | } 165 | 166 | //decode completed packet 167 | if (crc_cmp == crc_ext) 168 | decode_pkt_contents(super, super->m17d.pkt, total); 169 | 170 | else if (super->opts.allow_crc_failure == 1) 171 | decode_pkt_contents(super, super->m17d.pkt, total); 172 | 173 | // if (crc_cmp != crc_ext) 174 | // fprintf (stderr, " (CRC ERR) "); 175 | 176 | if (super->opts.payload_verbosity == 1) 177 | { 178 | fprintf (stderr, "\n PKT:"); 179 | for (i = 0; i < end; i++) 180 | { 181 | if ( (i%25) == 0 && i != 0) 182 | fprintf (stderr, "\n "); 183 | fprintf (stderr, " %02X", super->m17d.pkt[i]); 184 | } 185 | fprintf (stderr, "\n (CRC CHK) E: %04X; C: %04X;", crc_ext, crc_cmp); 186 | } 187 | 188 | //reset after processing 189 | memset (super->m17d.pkt, 0, sizeof(super->m17d.pkt)); 190 | super->m17d.pbc_ptr = 0; 191 | // super->m17d.dt = 15; //reset here, on OTAKD frames, if the subsequent LSF has an error, then it leaves this as DATA 192 | } 193 | 194 | //increment pbc counter last 195 | if (!eot) super->m17d.pbc_ptr++; 196 | 197 | //get rid of this if it costs too much CPU / skips / lags 198 | super->demod.sync_time = super->demod.current_time = time(NULL); 199 | 200 | //refresh ncurses printer, if enabled 201 | #ifdef USE_CURSES 202 | if (super->opts.use_ncurses_terminal == 1) 203 | print_ncurses_terminal(super); 204 | #endif 205 | 206 | } -------------------------------------------------------------------------------- /include/m17.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17.h 3 | * M17 Project - Common Static Arrays for M17 functions 4 | * 5 | * 6 | * LWVMOBILE 7 | * 2024-05 M17 Project - Florida Man Edition 8 | *-----------------------------------------------------------------------------*/ 9 | 10 | //M17 Project Frame Sync Patterns 11 | #define LSF_SYNC_BURST 0x55F7 12 | #define BRT_SYNC_BURST 0xDF55 13 | #define STR_SYNC_BURST 0xFF5D 14 | #define PKT_SYNC_BURST 0x75FF 15 | 16 | //Base40 Call Sign Data Character Set 17 | static char b40[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."; 18 | 19 | //scramble / randomization bit array 20 | static uint8_t m17_scramble[368] = { 21 | 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 22 | 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 23 | 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 24 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 25 | 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 26 | 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 27 | 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 28 | 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 29 | 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 30 | 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 31 | 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 32 | 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 33 | 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 34 | 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 35 | 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 36 | 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 37 | 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 38 | 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 39 | 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 40 | 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 41 | 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 42 | 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 43 | 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1 44 | }; 45 | 46 | //p1 puncture 47 | static uint8_t p1[61] = { 48 | 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 49 | 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 50 | 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 51 | 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1 52 | }; 53 | 54 | //p2 puncture 55 | static uint8_t p2[12] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}; 56 | 57 | //p3 puncture 58 | static uint8_t p3[8] = {1, 1, 1, 1, 1, 1, 1, 0}; 59 | 60 | //dibits-symbols map 61 | static int8_t symbol_map[4] = {+1, +3, -1, -3}; 62 | static int8_t inv_symbol_map[4] = {-1, -3, +1, +3}; 63 | 64 | //output sample RRC filter for 48kHz sample rate 65 | //alpha=0.5, span=8, sps=10, gain=sqrt(sps) 66 | static float m17_rrc[81] = 67 | { 68 | -0.003195702904062073f, -0.002930279157647190f, -0.001940667871554463f, 69 | -0.000356087678023658f, 0.001547011339077758f, 0.003389554791179751f, 70 | 0.004761898604225673f, 0.005310860846138910f, 0.004824746306020221f, 71 | 0.003297923526848786f, 0.000958710871218619f, -0.001749908029791816f, 72 | -0.004238694106631223f, -0.005881783042101693f, -0.006150256456781309f, 73 | -0.004745376707651645f, -0.001704189656473565f, 0.002547854551539951f, 74 | 0.007215575568844704f, 0.011231038205363532f, 0.013421952197060707f, 75 | 0.012730475385624438f, 0.008449554307303753f, 0.000436744366018287f, 76 | -0.010735380379191660f, -0.023726883538258272f, -0.036498030780605324f, 77 | -0.046500883189991064f, -0.050979050575999614f, -0.047340680079891187f, 78 | -0.033554880492651755f, -0.008513823955725943f, 0.027696543159614194f, 79 | 0.073664520037517042f, 0.126689053778116234f, 0.182990955139333916f, 80 | 0.238080025892859704f, 0.287235637987091563f, 0.326040247765297220f, 81 | 0.350895727088112619f, 0.359452932027607974f, 0.350895727088112619f, 82 | 0.326040247765297220f, 0.287235637987091563f, 0.238080025892859704f, 83 | 0.182990955139333916f, 0.126689053778116234f, 0.073664520037517042f, 84 | 0.027696543159614194f, -0.008513823955725943f, -0.033554880492651755f, 85 | -0.047340680079891187f, -0.050979050575999614f, -0.046500883189991064f, 86 | -0.036498030780605324f, -0.023726883538258272f, -0.010735380379191660f, 87 | 0.000436744366018287f, 0.008449554307303753f, 0.012730475385624438f, 88 | 0.013421952197060707f, 0.011231038205363532f, 0.007215575568844704f, 89 | 0.002547854551539951f, -0.001704189656473565f, -0.004745376707651645f, 90 | -0.006150256456781309f, -0.005881783042101693f, -0.004238694106631223f, 91 | -0.001749908029791816f, 0.000958710871218619f, 0.003297923526848786f, 92 | 0.004824746306020221f, 0.005310860846138910f, 0.004761898604225673f, 93 | 0.003389554791179751f, 0.001547011339077758f, -0.000356087678023658f, 94 | -0.001940667871554463f, -0.002930279157647190f, -0.003195702904062073f 95 | }; 96 | 97 | static float m17_input_rrc[79] = 98 | { 99 | -0.00926578400780053, -0.00613655162572969, -0.00112597856207517, +0.00489177725204249, 100 | +0.01071805138282269, +0.01505751553351295, +0.01679337935001369, +0.01525624514215629, 101 | +0.01042830577908502, +0.00303152272555990, -0.00553335329681881, -0.01340309982572337, 102 | -0.01859868234964252, -0.01944761739590459, -0.01500527193595174, -0.00538878803543439, 103 | +0.00805652591025353, +0.02281624415830727, +0.03551346769220807, +0.04244131815783876, 104 | +0.04025481153629372, +0.02671818654865632, +0.00138102165167049, -0.03394615682795165, 105 | -0.07502635967975885, -0.11540977897637611, -0.14703962203941534, -0.16119995609538576, 106 | -0.14969512896336504, -0.10610329539459686, -0.02692141246963491, +0.08757875030779196, 107 | +0.23293327870303457, +0.40060122101239920, +0.57863246963255030, +0.75282864799340680, 108 | +0.90826274144752200, +1.03096611316331990, +1.10956118565480130, +1.13661977236758150, 109 | +1.10956118565480130, +1.03096611316331990, +0.90826274144752200, +0.75282864799340680, 110 | +0.57863246963255030, +0.40060122101239920, +0.23293327870303457, +0.08757875030779196, 111 | -0.02692141246963491, -0.10610329539459686, -0.14969512896336504, -0.16119995609538576, 112 | -0.14703962203941534, -0.11540977897637611, -0.07502635967975885, -0.03394615682795165, 113 | +0.00138102165167049, +0.02671818654865632, +0.04025481153629372, 0.04244131815783876, 114 | +0.03551346769220807, +0.02281624415830727, +0.00805652591025353, -0.00538878803543439, 115 | -0.01500527193595174, -0.01944761739590459, -0.01859868234964252, -0.01340309982572337, 116 | -0.00553335329681881, +0.00303152272555990, +0.01042830577908502, +0.01525624514215629, 117 | +0.01679337935001369, +0.01505751553351295, +0.01071805138282269, +0.00489177725204249, 118 | -0.00112597856207517, -0.00613655162572969, -0.00926578400780053 119 | }; 120 | 121 | // syncword patterns (RX) as symbols 122 | static int8_t lsf_sync_symbols[8]={+3, +3, +3, +3, -3, -3, +3, -3}; 123 | static int8_t str_sync_symbols[8]={-3, -3, -3, -3, +3, +3, -3, +3}; 124 | static int8_t pkt_sync_symbols[8]={+3, -3, +3, +3, -3, -3, -3, -3}; 125 | static int8_t brt_sync_symbols[8]={-3, +3, -3, -3, +3, +3, +3, +3}; 126 | 127 | // symbol levels (RX) as float values 128 | static float symbol_levels[4]={-3.0, -1.0, +1.0, +3.0}; //3,2.0,1 129 | 130 | //hopefully there won't be any random linking issues since this function is in main.h 131 | static int stfu () 132 | { 133 | //quell defined but not used warnings from m17.h 134 | UNUSED(b40); UNUSED(m17_scramble); UNUSED(p1); UNUSED(p2); UNUSED(p3); UNUSED(symbol_map); UNUSED(inv_symbol_map); UNUSED(m17_rrc); 135 | UNUSED(m17_input_rrc); UNUSED(lsf_sync_symbols); UNUSED(str_sync_symbols); UNUSED(pkt_sync_symbols); UNUSED(brt_sync_symbols); UNUSED(symbol_levels); 136 | return 0; 137 | } -------------------------------------------------------------------------------- /src/encryption/ecdsa.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * ecdsa.c 3 | * M17 Project - Elliptic Curve Digital Signature Algorithm for Voice Stream 4 | * 5 | * LWVMOBILE 6 | * 2024-06 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | void ecdsa_generate_random_keys(Super * super) 12 | { 13 | 14 | #ifdef USE_UECC 15 | srand((unsigned int)time(NULL)); 16 | int i; int ok = 0; 17 | 18 | const struct uECC_Curve_t* curve = uECC_secp256r1(); 19 | 20 | //NOTE: It seems as is public and private key are reversed here, but I'm thinking our understanding of 21 | //the public key and private key may be in error, and in fact, may be reverse in naming/convention? 22 | //int uECC_make_key(uint8_t *public_key, uint8_t *private_key,uECC_Curve curve) 23 | ok = uECC_make_key(super->m17d.ecdsa.public_key, super->m17e.ecdsa.private_key, curve); 24 | 25 | fprintf (stderr, "\n"); 26 | fprintf (stderr, "Randomly Generated secp256r1 Signature Keys;"); 27 | fprintf (stderr, "\n"); 28 | fprintf (stderr, "Public Key: "); 29 | for (i = 0; i < 64; i++) 30 | fprintf (stderr, "%02X", super->m17d.ecdsa.public_key[i]); 31 | 32 | fprintf (stderr, "\n"); 33 | fprintf (stderr, "Private Key: "); 34 | for (i = 0; i < 32; i++) 35 | fprintf (stderr, "%02X", super->m17e.ecdsa.private_key[i]); 36 | 37 | if (ok) 38 | { 39 | super->m17e.ecdsa.keys_loaded = 1; //private key available 40 | super->m17d.ecdsa.keys_loaded = 1; //public key available 41 | // super->opts.use_otask = 1; //enable OTA Signature Key Delivery 42 | } 43 | 44 | #else 45 | UNUSED(super); 46 | #endif 47 | 48 | } 49 | 50 | //ECDSA Debug Signature Test 51 | void ecdsa_signature_debug_test() 52 | { 53 | #ifdef USE_UECC 54 | 55 | uint8_t priv_key[32] = {0x73, 0xd5, 0x45, 0xd4, 0xa9, 0xde, 0x94, 0xba, 0x4e, 0x22, 0x51, 0x5f, 0x6a, 0xc4, 0xcc, 0x03, 0x2a, 0x09, 0xe6, 0xc8, 0x47, 0xc8, 0x62, 0x97, 0x07, 0x51, 0xb0, 0x35, 0xcb, 0xb4, 0xfa, 0x70}; 56 | uint8_t pub_key[64] = {0xf9, 0x9e, 0x9a, 0xdc, 0xf7, 0xe5, 0xc1, 0x09, 0x56, 0xf0, 0x9d, 0x07, 0x84, 0x89, 0xb1, 0x70, 0x53, 0x37, 0x15, 0x11, 0x5c, 0xa0, 0x53, 0x5a, 0xb0, 0xa9, 0x62, 0x65, 0x34, 0xcb, 0x9e, 0x96, 0x5b, 0x43, 0x9f, 0x32, 0x1b, 0x62, 0xfc, 0xb6, 0xd1, 0x31, 0xe1, 0xb8, 0x72, 0xe8, 0xd8, 0x30, 0x4f, 0x45, 0xd9, 0xf6, 0xfb, 0x02, 0xb4, 0x1a, 0x33, 0xf6, 0xd8, 0x26, 0x65, 0xd9, 0xd9, 0xdb}; 57 | uint8_t digest[16] = {0xde, 0xad, 0xbe, 0xef}; 58 | uint8_t sig[64] = {0}; 59 | 60 | const struct uECC_Curve_t* curve = uECC_secp256r1(); 61 | 62 | printf("digest="); 63 | for(uint8_t i=0; im17d.ecdsa.public_key[i] = pub_key[i]; 102 | if (i == 16 || i == 32 || i == 48) 103 | fprintf (stderr, "\n "); 104 | fprintf (stderr, " %02X", super->m17d.ecdsa.public_key[i]); 105 | } 106 | 107 | fprintf (stderr, "\n"); 108 | fprintf (stderr, "PRI Key:"); 109 | for (i = 0; i < 32; i++) 110 | { 111 | super->m17e.ecdsa.private_key[i] = priv_key[i]; 112 | if (i == 16) fprintf (stderr, "\n "); 113 | fprintf (stderr, " %02X", super->m17e.ecdsa.private_key[i]); 114 | } 115 | 116 | super->m17e.ecdsa.keys_loaded = 1; //private key available 117 | super->m17d.ecdsa.keys_loaded = 1; //public key available 118 | 119 | #else 120 | UNUSED(super); 121 | #endif 122 | 123 | } 124 | 125 | //decoder side 126 | void ecdsa_signature_verification (Super * super) 127 | { 128 | 129 | #ifdef USE_UECC 130 | 131 | //pointers 132 | uint8_t * pub_key; 133 | uint8_t * digest; 134 | uint8_t * sig; 135 | const struct uECC_Curve_t * curve = uECC_secp256r1(); 136 | 137 | //set pointers to correct items 138 | pub_key = super->m17d.ecdsa.public_key; 139 | digest = super->m17d.ecdsa.last_stream_pyl; 140 | sig = super->m17d.ecdsa.signature; 141 | 142 | //debug 143 | // fprintf (stderr, "\n"); 144 | // fprintf (stderr, "Digest:"); 145 | // for (int i = 0; i < 16; i++) 146 | // fprintf (stderr, " %02X", digest[i]); 147 | // fprintf (stderr, "\n"); 148 | 149 | // fprintf (stderr, "\n"); 150 | // fprintf (stderr, "Signature:"); 151 | // for (int i = 0; i < 64; i++) 152 | // { 153 | // if (i == 16 || i == 32 || i == 48) 154 | // fprintf (stderr, "\n "); 155 | // fprintf (stderr, " %02X", sig[i]); 156 | // } 157 | 158 | 159 | //run verification 160 | int valid = 0; 161 | valid = uECC_verify(pub_key, digest, 16*sizeof(uint8_t), sig, curve); 162 | 163 | if (valid) fprintf (stderr, " Signature Valid;"); 164 | else fprintf (stderr, " Signature Invalid;"); 165 | 166 | #endif 167 | 168 | //reset payload after processing signature 169 | memset (super->m17d.ecdsa.curr_stream_pyl, 0, 16*sizeof(uint8_t)); 170 | memset (super->m17d.ecdsa.last_stream_pyl, 0, 16*sizeof(uint8_t)); 171 | memset (super->m17d.ecdsa.signature, 0, 64*sizeof(uint8_t)); 172 | 173 | } 174 | 175 | //encoder side 176 | void ecdsa_signature_creation (Super * super) 177 | { 178 | 179 | #ifdef USE_UECC 180 | 181 | //pointers 182 | uint8_t * priv_key; 183 | uint8_t * digest; 184 | uint8_t * sig; 185 | const struct uECC_Curve_t * curve = uECC_secp256r1(); 186 | 187 | //set pointers to correct items 188 | priv_key = super->m17e.ecdsa.private_key; 189 | digest = super->m17e.ecdsa.last_stream_pyl; 190 | sig = super->m17e.ecdsa.signature; 191 | 192 | //debug 193 | // fprintf (stderr, "\n"); 194 | // fprintf (stderr, "Digest:"); 195 | // for (int i = 0; i < 16; i++) 196 | // fprintf (stderr, " %02X", digest[i]); 197 | // fprintf (stderr, "\n"); 198 | 199 | //run signing 200 | int valid = 0; 201 | valid = uECC_sign(priv_key, digest, 16*sizeof(uint8_t), sig, curve); 202 | 203 | if (valid) fprintf (stderr, " Signature Success; \n"); 204 | else fprintf (stderr, " Signature Failure; \n"); 205 | 206 | fprintf (stderr, " Signature:"); 207 | for (int i = 0; i < 64; i++) 208 | { 209 | if ( (i != 0) && ((i%16) == 0) ) 210 | fprintf (stderr, "\n "); 211 | fprintf (stderr, " %02X", sig[i]); 212 | } 213 | 214 | #else 215 | UNUSED(super); 216 | #endif 217 | } 218 | -------------------------------------------------------------------------------- /src/decoder/m17_lsf_decoder.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_lsf_decoder.c 3 | * M17 Project - Link Setup Frame Contents Decoder 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | 11 | void decode_lsf_contents(Super * super) 12 | { 13 | int i; 14 | unsigned long long int lsf_dst = (unsigned long long int)convert_bits_into_output(&super->m17d.lsf[0], 48); 15 | unsigned long long int lsf_src = (unsigned long long int)convert_bits_into_output(&super->m17d.lsf[48], 48); 16 | uint16_t lsf_type = (uint16_t)convert_bits_into_output(&super->m17d.lsf[96], 16); 17 | 18 | //this is the way the spec/code expects you to read these bits 19 | uint8_t lsf_ps = (lsf_type >> 0) & 0x1; 20 | uint8_t lsf_dt = (lsf_type >> 1) & 0x3; 21 | uint8_t lsf_et = (lsf_type >> 3) & 0x3; 22 | uint8_t lsf_es = (lsf_type >> 5) & 0x3; 23 | uint8_t lsf_cn = (lsf_type >> 7) & 0xF; 24 | uint8_t lsf_rs = (lsf_type >> 11) & 0x1F; 25 | 26 | //NOTE TO SELF: Revert this change before merging 3.0.0-draft into main, or delete this snippet of code after merging 27 | //check lsf_src vs last_src_hex_value, if different, push call history (Reflectors may append a second src not in ECD to end of TX) 28 | if (super->m17d.last_src != 0 && super->m17d.last_src != lsf_src) 29 | push_call_history(super); 30 | super->m17d.last_src = lsf_src; //revert this second fix as well 31 | 32 | //decode behavior debug 33 | // lsf_rs |= 0x10; 34 | 35 | //ECDSA signature included 36 | uint8_t is_signed = 0; 37 | 38 | //Seperate the Signed bit from the reserved field, shift right once 39 | if (lsf_rs & 1) 40 | { 41 | is_signed = 1; 42 | lsf_rs >>= 1; 43 | } 44 | 45 | //if this field is not zero, then this is not a standard V2.0 or older spec'd LSF type, 46 | //if proposed LSF TYPE field changes occur, this region will be 0 for V2.0, and not 0 for newer 47 | if (lsf_rs != 0) 48 | { 49 | fprintf (stderr, " Unknown LSF TYPE;"); //V3.0 in future spec? 50 | goto LSF_END; 51 | } 52 | 53 | //store this so we can reference it for playing voice and/or decoding data, dst/src etc 54 | super->m17d.dt = lsf_dt; 55 | super->m17d.dst = lsf_dst; 56 | super->m17d.src = lsf_src; 57 | super->m17d.can = lsf_cn; 58 | 59 | fprintf (stderr, "\n"); 60 | decode_callsign_data(super, lsf_dst, lsf_src); 61 | 62 | for (i = 0; i < super->m17d.lockout_index; i++) 63 | { 64 | if (strncmp(super->m17d.src_csd_lockout[i], " ", 9) != 0) 65 | { 66 | if (strncmp(super->m17d.src_csd_str, super->m17d.src_csd_lockout[i], 9) == 0) 67 | { 68 | fprintf (stderr, " [LOCKOUT]"); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | fprintf (stderr, " CAN: %d;", lsf_cn); 75 | 76 | //only valid on Stream mode 77 | if (lsf_ps == 1) 78 | { 79 | if (lsf_dt == 0) fprintf (stderr, " Reserved"); 80 | if (lsf_dt == 1) fprintf (stderr, " Data"); 81 | if (lsf_dt == 2) fprintf (stderr, " Voice (3200bps)"); 82 | if (lsf_dt == 3) fprintf (stderr, " Voice (1600bps)"); 83 | 84 | fprintf (stderr, " Stream"); 85 | 86 | //debug type, et, es on misc things from other sources 87 | if (super->opts.payload_verbosity >= 1) 88 | { 89 | fprintf (stderr, "\n"); 90 | fprintf (stderr, " FT: %04X;", lsf_type); 91 | fprintf (stderr, " PS: %X;", lsf_ps); 92 | fprintf (stderr, " DT: %X;", lsf_dt); 93 | fprintf (stderr, " ET: %0X;", lsf_et); 94 | fprintf (stderr, " ES: %0X;", lsf_es); 95 | fprintf (stderr, " SIG: %X;", is_signed); 96 | fprintf (stderr, " RES: %X;", lsf_rs); 97 | } 98 | } 99 | 100 | //Packet Mode 101 | if (lsf_ps == 0) 102 | { 103 | fprintf (stderr, " Data PDU"); 104 | if (super->opts.payload_verbosity >= 1) 105 | fprintf (stderr, " FT: %04X;", lsf_type); 106 | super->m17d.dt = 20; 107 | } 108 | 109 | if (is_signed) 110 | fprintf (stderr, " Signed (secp256r1);"); 111 | 112 | if (lsf_et != 0) fprintf (stderr, "\n ENC:"); 113 | if (lsf_et == 1) 114 | { 115 | fprintf (stderr, " Scrambler; Subtype: %d;", lsf_es); 116 | if (lsf_es == 0) 117 | fprintf (stderr, " (8-bit);"); 118 | else if (lsf_es == 1) 119 | fprintf (stderr, " (16-bit);"); 120 | else if (lsf_es == 2) 121 | fprintf (stderr, " (24-bit);"); 122 | if (super->enc.scrambler_key != 0) 123 | fprintf (stderr, " Key: %X;", super->enc.scrambler_key); 124 | } 125 | 126 | int keylen = 32; 127 | if (lsf_et == 2) 128 | { 129 | fprintf (stderr, " AES"); 130 | if (lsf_es == 0) 131 | { 132 | keylen = 16; 133 | fprintf (stderr, " 128;"); 134 | } 135 | 136 | else if (lsf_es == 1) 137 | { 138 | keylen = 24; 139 | fprintf (stderr, " 192;"); 140 | } 141 | 142 | else if (lsf_es == 2) 143 | { 144 | keylen = 32; 145 | fprintf (stderr, " 256;"); 146 | } 147 | 148 | } 149 | 150 | super->m17d.enc_et = lsf_et; 151 | super->m17d.enc_st = lsf_es; 152 | 153 | //use lli and llabs instead (disabled due to m17-tools using truly random non-spec IV values) 154 | // long long int epoch = 1577836800LL; //Jan 1, 2020, 00:00:00 UTC 155 | // long long int tsn = ( (super->demod.current_time-epoch) & 0xFFFFFFFF); //current LSB 32-bit value 156 | // long long int tsi = (uint32_t)convert_bits_into_output(&super->m17d.lsf[112], 32); //OTA LSB 32-bit value 157 | // long long int dif = llabs(tsn-tsi); 158 | // if (lsf_et == 2 && dif > 3600) fprintf (stderr, " \n Warning! Time Difference > %lld secs; Potential NONCE/IV Replay!\n", dif); 159 | 160 | //debug 161 | // fprintf (stderr, "TSN: %ld; TSI: %ld; DIF: %lld;", tsn, tsi, dif); 162 | 163 | //pack meta bits into 14 bytes 164 | for (i = 0; i < 14; i++) 165 | super->m17d.meta[i] = (uint8_t)convert_bits_into_output(&super->m17d.lsf[(i*8)+112], 8); 166 | 167 | //using meta_sum in case some byte fields, particularly meta[0], are zero 168 | uint32_t meta_sum = 0; 169 | for (i = 0; i < 14; i++) 170 | meta_sum += super->m17d.meta[i]; 171 | 172 | //Decode Meta Data when not ENC (if meta field is populated with something) 173 | if (lsf_et == 0 && meta_sum != 0) 174 | { 175 | uint8_t meta[15]; meta[0] = lsf_es + 0x80; //add identifier for pkt decoder 176 | for (i = 0; i < 14; i++) 177 | meta[i+1] = super->m17d.meta[i]; 178 | fprintf (stderr, "\n "); 179 | decode_pkt_contents (super, meta, 15); //decode META 180 | } 181 | 182 | //if no Meta (debug) 183 | // if (lsf_et == 0 && meta_sum == 0) 184 | // fprintf (stderr, " Meta Null; "); 185 | 186 | //reset potential stale FN portion of meta field if ES enc PKT 187 | //comes in immediately after a Stream without a nocarrier reset 188 | if (lsf_ps == 0) //packet data indicator 189 | { 190 | super->m17d.meta[14] = 0; 191 | super->m17d.meta[15] = 0; 192 | } 193 | 194 | if (lsf_et == 2) 195 | { 196 | fprintf (stderr, " IV: "); 197 | for (i = 0; i < 16; i++) 198 | fprintf (stderr, "%02X", super->m17d.meta[i]); 199 | 200 | if (super->enc.aes_key_is_loaded) 201 | { 202 | fprintf (stderr, "\n Key: "); 203 | for (i = 0; i < keylen; i++) 204 | { 205 | // if (i == 16) fprintf (stderr, "\n "); 206 | fprintf (stderr, "%02X", super->enc.aes_key[i]); 207 | } 208 | 209 | } 210 | } 211 | 212 | //open a per call wav file here if stream voice, if enabled, if not already opened 213 | if (lsf_ps && super->opts.use_wav_out_pc && super->wav.wav_out_pc == NULL) 214 | setup_percall_filename(super); 215 | 216 | LSF_END: {} // 217 | 218 | } -------------------------------------------------------------------------------- /docs/Install_Notes.md: -------------------------------------------------------------------------------- 1 | 2 | # M17 Project - Florida Man Edition 3 | 4 | ## Auto Install Scripts 5 | 6 | M17-FME has auto install scripts for Debian/Ubuntu/Mint based distros, Red Hat/Fedora/RHEL based distros, and for Arch based distros. Simply downloading and running these scripts will download all dependencies and install for you. 7 | 8 | Debian / Ubuntu (22.04 LTS) / Mint 21 / Raspberry Pi OS 9 | ``` 10 | wget https://raw.githubusercontent.com/lwvmobile/m17-fme/main/scripts/download-and-install-deb.sh 11 | sh download-and-install-deb.sh 12 | ``` 13 | 14 | Debian 12 / Ubuntu 24.04 LTS (and newer) 15 | ``` 16 | wget https://raw.githubusercontent.com/lwvmobile/m17-fme/main/scripts/download-and-install-ubuntu2404lts.sh 17 | sh download-and-install-ubuntu2404lts.sh 18 | ``` 19 | 20 | Red Hat/Fedora/RHEL 21 | ``` 22 | wget https://raw.githubusercontent.com/lwvmobile/m17-fme/main/scripts/download-and-install-rhel.sh 23 | sh download-and-install-rhel.sh 24 | ``` 25 | 26 | Arch 27 | ``` 28 | wget https://raw.githubusercontent.com/lwvmobile/m17-fme/main/scripts/download-and-install-arch.sh 29 | sh download-and-install-arch.sh 30 | ``` 31 | 32 | macOS (Homebrew) 33 | ``` 34 | wget https://raw.githubusercontent.com/lwvmobile/m17-fme/main/scripts/download-and-install-macos.sh 35 | sh download-and-install-macos.sh 36 | ``` 37 | 38 | ### Windows Cygwin Builds 39 | 40 | Cygwin builds now have an experimental semi-automatic installer, to run, follow steps below: 41 | 42 | Note: If you already have DSD-FME compiled with Cygwin64 using this method, you can skip to the second step, or if you prefer to handpick packages to install, use the itemized packages below to guide you. 43 | 44 | Open Windows PowerShell (not Command Prompt) and copy and paste all of this in all at once. 45 | 46 | ``` 47 | Invoke-WebRequest https://cygwin.com/setup-x86_64.exe -OutFile setup-x86_64.exe 48 | .\setup-x86_64.exe --packages nano,libpulse-devel,libpulse-mainloop-glib0,libpulse-simple0,libpulse0,pulseaudio,pulseaudio-debuginfo,pulseaudio-equalizer,pulseaudio-module-x11,pulseaudio-module-zeroconf,pulseaudio-utils,sox-fmt-pulseaudio,libusb0,libusb1.0,libusb1.0-debuginfo,libusb1.0-devel,libncurses++w10,libncurses-devel,libncursesw10,ncurses,cmake,gcc-core,gcc-debuginfo,gcc-objc,git,make,socat,sox,sox-fmt-ao,zip,unzip,wget,gcc-g++,libsndfile-devel 49 | 50 | ``` 51 | 52 | Pick a Mirror. http://www.gtlib.gatech.edu mirror seems relatively fast. Nurse the Cygwin installer by clicking next and waiting for it to finish. Ignore the warning popup telling you to install libusb from sourceforge. Install the [Zadig](https://zadig.akeo.ie/ "Zadig") driver instead, if you haven't already and have an RTL Dongle. After Cygwin finishes installing, the installer sh script will download and run, be patient, it may also take a little while. 53 | 54 | Then: 55 | 56 | ``` 57 | C:\cygwin64\bin\mintty.exe /bin/bash -l -c "wget https://raw.githubusercontent.com/lwvmobile/m17-fme/refs/heads/main/scripts/download-and-install-cygwin.sh; sh download-and-install-cygwin.sh; m17-fme;" 58 | 59 | ``` 60 | 61 | After the sh script finishes, m17-fme should open. If not, then double click on the Cygwin Terminal desktop shortcut, and try running `m17-fme`. If you chose to create a portable version, you will find the folder and zip file in the `C:\cygwin64\home\username` directory if using the default cygwin64 folder location. 62 | 63 | You can also update your versions by using the cyg_rebuild.sh script `sh cyg_rebuild.sh`. 64 | 65 | ## How to Build (Manual Install) 66 | 67 | ### Dependencies 68 | 69 | Install Dependencies. Although M17-FME can be built with only the most minimal dependencies to make it highly modular and also highly portable, it is highly recommended to install all dependencies if possible, or there will be no nicer features like Ncurses Terminal w/ KB shortcuts, pulse audio input and output, and no Codec2 Support (which if you want M17 voice, you need Codec2). 70 | 71 | Debian / Ubuntu (22.04 LTS and lower) / Mint 21 / Raspberry Pi OS 72 | ``` 73 | sudo apt update 74 | 75 | recommended: 76 | sudo apt install cmake make build-essential git wget libsndfile1-dev libcodec2-dev libncurses5 libncurses5-dev libncursesw5-dev libpulse-dev pavucontrol socat 77 | 78 | required: 79 | sudo apt install cmake make build-essential git libsndfile1-dev 80 | 81 | optional: 82 | sudo apt install libcodec2-dev libncurses5 libncurses5-dev libncursesw5-dev libpulse-dev pavucontrol wget socat 83 | 84 | ``` 85 | 86 | 87 | Debian 12 / Ubuntu 24.04 LTS (newer libncurses packages) 88 | 89 | ``` 90 | sudo apt update 91 | 92 | recommended: 93 | sudo apt install cmake make build-essential git wget libsndfile1-dev libcodec2-dev libncurses-dev libncurses6 libpulse-dev pavucontrol socat 94 | 95 | required: 96 | sudo apt install cmake make build-essential git libsndfile1-dev 97 | 98 | optional: 99 | sudo apt install libcodec2-dev libncurses-dev libncurses6 libpulse-dev pavucontrol wget socat 100 | ``` 101 | 102 | Red Hat/Fedora/RHEL 103 | ``` 104 | sudo dnf update 105 | 106 | recommended: 107 | sudo dnf install libsndfile-devel pulseaudio-libs-devel cmake git ncurses ncurses-devel gcc wget pavucontrol gcc-c++ codec2-devel 108 | 109 | required: 110 | sudo dnf install cmake build-essential git libsndfile-devel gcc-c++ wget 111 | 112 | optional: 113 | sudo dnf install codec2-devel ncurses ncurses-devel pulseaudio-libs-devel pavucontrol wget socat 114 | 115 | ``` 116 | 117 | Arch (Note, running a full system upgrade is highly advised, or you risk breaking dependency links and borking your system) 118 | ``` 119 | sudo pacman -Syu 120 | 121 | recommended: 122 | sudo pacman -S libpulse cmake ncurses codec2 base-devel libsndfile git wget 123 | 124 | required: 125 | sudo pacman install cmake base-devel git libsndfile 126 | 127 | optional: 128 | sudo apt install codec2 ncurses libpulse pavucontrol wget socat 129 | 130 | ``` 131 | 132 | macOS (Homebrew) 133 | ``` 134 | # First, install Homebrew if not already installed 135 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 136 | 137 | # Update Homebrew 138 | brew update 139 | 140 | # Install required dependencies 141 | brew install cmake make git libsndfile 142 | 143 | # Install recommended dependencies 144 | brew install codec2 ncurses pulseaudio pkg-config gcc wget socat 145 | 146 | # Note: PulseAudio may need to be started manually: 147 | # brew services start pulseaudio 148 | # or: /opt/homebrew/opt/pulseaudio/bin/pulseaudio --exit-idle-time=-1 --verbose 149 | 150 | required: 151 | brew install cmake make git libsndfile 152 | 153 | optional: 154 | brew install codec2 ncurses pulseaudio pkg-config gcc wget socat 155 | ``` 156 | 157 | ### Pull, Compile, and Install 158 | 159 | ``` 160 | git clone --recursive https://github.com/lwvmobile/m17-fme.git 161 | cd m17-fme 162 | mkdir build 163 | cd build 164 | cmake .. 165 | make 166 | sudo make install 167 | ``` 168 | 169 | ### macOS-Specific Notes 170 | 171 | When building on macOS, please note the following: 172 | 173 | **Audio Support:** 174 | - OSS audio is not available on macOS. Use PulseAudio, file I/O, or network input/output instead. 175 | - PulseAudio may need to be started manually after installation: 176 | ``` 177 | brew services start pulseaudio 178 | ``` 179 | - For first-time PulseAudio use, you may need to configure audio permissions in System Preferences > Security & Privacy > Microphone. 180 | 181 | **Dependencies:** 182 | - All dependencies are available through Homebrew 183 | - The build system automatically detects macOS and adjusts compiler/linker settings 184 | - RPATH is configured to work with Homebrew library locations 185 | 186 | **Known Limitations:** 187 | - OSS audio (`/dev/dsp`) is not supported on macOS 188 | - Some warning messages during compilation are normal and don't affect functionality 189 | 190 | **Verification:** 191 | After installation, verify M17-FME is working: 192 | ``` 193 | m17-fme --help 194 | ``` 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/modem/rfa_modulator.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * rfa_modulator.c 3 | * M17 Project - RF Audio Encoder / Modulator 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | //convert bit array into symbols and RF/Audio 13 | void encode_rfa (Super * super, uint8_t * input, float * mem, int type) 14 | { 15 | 16 | //quell defined but not used warnings from m17.h 17 | stfu (); 18 | 19 | //NOTE: type numbers as following: 20 | //Single Digit numbers 1,2,3,4 are LSF, STR, BRT, and PKT 21 | //Double Digit numbers 11,33,55 are Preamble A, Preamble B, and EOT Marker 22 | //Double Digit numbers 77,88,99 are Test Pattern A, Test Pattern B, and Dead Air 23 | 24 | int i, j, k, x; UNUSED(k); UNUSED(x); 25 | 26 | //Preamble A - 0x7777 (+3, -3, +3, -3, +3, -3, +3, -3) 27 | uint8_t m17_preamble_a[16] = {0,1,1,1, 0,1,1,1, 0,1,1,1, 0,1,1,1}; 28 | 29 | //Preamble B - 0xEEEE (-3, +3, -3, +3, -3, +3, -3, +3) 30 | uint8_t m17_preamble_b[16] = {1,1,1,0, 1,1,1,0, 1,1,1,0, 1,1,1,0}; 31 | 32 | //EOT Marker - 0x555D 33 | uint8_t m17_eot_marker[16] = {0,1,0,1, 0,1,0,1, 0,1,0,1, 1,1,0,1}; 34 | 35 | //LSF frame sync pattern - 0x55F7 +3, +3, +3, +3, -3, -3, +3, -3 36 | uint8_t m17_lsf_fs[16] = {0,1,0,1, 0,1,0,1, 1,1,1,1, 0,1,1,1}; 37 | 38 | //STR frame sync pattern - 0xFF5D (-3, -3, -3, -3, +3, +3, -3, +3) 39 | uint8_t m17_str_fs[16] = {1,1,1,1, 1,1,1,1 ,0,1,0,1, 1,1,0,1}; 40 | 41 | //PKT frame sync pattern - 0x75FF (-3, -3, -3, -3, +3, +3, -3, +3) 42 | uint8_t m17_pkt_fs[16] = {0,1,1,1, 0,1,0,1,1,1,1,1,1,1,1,1}; 43 | 44 | //BRT frame sync pattern - 0xDF55 (-3, +3, -3, -3, +3, +3, +3, +3) 45 | uint8_t m17_brt_fs[16] = {1,1,0,1, 1,1,1,1, 0,1,0,1, 0,1,0,1}; 46 | 47 | //Test Pattern Pseudo Sine Wav - (-3, -1, +1, +3, +3, +1, -1, -3) 48 | uint8_t m17_tst_pt_a[16] = {1,1,1,0, 0,0,0,1, 0,1,0,0, 1,0,1,1}; 49 | 50 | //Test Pattern PseudoTriangle - ( -3, -1, +1, +3, +1, -1) 51 | uint8_t m17_tst_pt_b[12] = {1,1, 1,0, 0,0, 0,1, 0,0, 1,0}; 52 | 53 | //load bits into a dibit array plus the framesync bits 54 | uint8_t output_dibits[192]; memset (output_dibits, 0, sizeof(output_dibits)); 55 | 56 | //Preamble (just repeat the preamble 12 times to make 192 symbols) 57 | if (type == 11) //A Pattern prepends LSF (last symbol opposite of first symbol to prevent zero-crossing) 58 | { 59 | for (i = 0; i < 192; i++) 60 | output_dibits[i] = (m17_preamble_a[ (i*2+0)%16 ] << 1) + (m17_preamble_a[ (i*2+1)%16 ] << 0); 61 | } 62 | 63 | //Preamble (just repeat the preamble 12 times to make 192 symbols) 64 | if (type == 33) //B Pattern prepends BRT (last symbol opposite of first symbol to prevent zero-crossing) 65 | { 66 | for (i = 0; i < 192; i++) 67 | output_dibits[i] = (m17_preamble_b[ (i*2+0)%16 ] << 1) + (m17_preamble_b[ (i*2+1)%16 ] << 0); 68 | } 69 | 70 | //EOT Marker (just repeat the EOT marker 12 times to make 192 symbols) 71 | if (type == 55) 72 | { 73 | for (i = 0; i < 192; i++) 74 | output_dibits[i] = (m17_eot_marker[ (i*2+0)%16 ] << 1) + (m17_eot_marker[ (i*2+1)%16 ] << 0); 75 | } 76 | 77 | //Test Pattern A (just repeat the pattern 12 times to make 192 symbols) 78 | if (type == 77) 79 | { 80 | for (i = 0; i < 192; i++) 81 | output_dibits[i] = (m17_tst_pt_a[ (i*2+0)%16 ] << 1) + (m17_tst_pt_a[ (i*2+1)%16 ] << 0); 82 | } 83 | 84 | //Test Pattern B 85 | if (type == 88) //(just repeat the pattern 16 times to make 192 symbols) 86 | { 87 | for (i = 0; i < 192; i++) 88 | output_dibits[i] = (m17_tst_pt_b[ (i*2+0)%12 ] << 1) + (m17_tst_pt_b[ (i*2+1)%12 ] << 0); 89 | } 90 | 91 | //load frame sync pattern 92 | if (type == 1) //LSF 93 | { 94 | for (i = 0; i < 8; i++) 95 | output_dibits[i] = (m17_lsf_fs[i*2+0] << 1) + (m17_lsf_fs[i*2+1] << 0); 96 | } 97 | 98 | if (type == 2) //Stream 99 | { 100 | for (i = 0; i < 8; i++) 101 | output_dibits[i] = (m17_str_fs[i*2+0] << 1) + (m17_str_fs[i*2+1] << 0); 102 | } 103 | 104 | if (type == 3) //BRT 105 | { 106 | for (i = 0; i < 8; i++) 107 | output_dibits[i] = (m17_brt_fs[i*2+0] << 1) + (m17_brt_fs[i*2+1] << 0); 108 | } 109 | 110 | if (type == 4) //PKT 111 | { 112 | for (i = 0; i < 8; i++) 113 | output_dibits[i] = (m17_pkt_fs[i*2+0] << 1) + (m17_pkt_fs[i*2+1] << 0); 114 | } 115 | 116 | //load rest of frame (if not preamble, EOT marker, or dead air) 117 | if (type < 5) 118 | { 119 | for (i = 0; i < 184; i++) 120 | output_dibits[i+8] = (input[i*2+0] << 1) + (input[i*2+1] << 0); 121 | } 122 | 123 | //convert to symbols 124 | int output_symbols[192]; memset (output_symbols, 0, 192*sizeof(int)); 125 | if (super->opts.inverted_signal) 126 | { 127 | for (i = 0; i < 192; i++) 128 | output_symbols[i] = inv_symbol_map[output_dibits[i]]; 129 | } 130 | else 131 | { 132 | for (i = 0; i < 192; i++) 133 | output_symbols[i] = symbol_map[output_dibits[i]]; 134 | } 135 | //symbols to audio 136 | 137 | //upsample 10x 138 | int output_up[192*10]; memset (output_up, 0, 192*10*sizeof(int)); 139 | for (i = 0; i < 192; i++) 140 | { 141 | for (j = 0; j < 10; j++) 142 | output_up[(i*10)+j] = output_symbols[i]; 143 | } 144 | 145 | //craft baseband with deviation + filter 146 | short baseband[1920]; memset (baseband, 0, 1920*sizeof(short)); 147 | 148 | //simple, no filtering 149 | if (super->opts.disable_rrc_filter == 1) 150 | { 151 | for (i = 0; i < 1920; i++) 152 | baseband[i] = output_up[i] * 7168.0f; 153 | } 154 | 155 | //version w/ filtering lifted from M17_Implementations / libM17 156 | else if (super->opts.disable_rrc_filter == 0) 157 | upscale_and_rrc_output_filter (output_symbols, mem, baseband); 158 | 159 | //Apply Gain to Output 160 | output_gain_rf (super, baseband, 1920); 161 | 162 | //dead air type, output to all enabled formats zero sample to simulate dead air 163 | //NOTE: 25 rounds is approximately 1 second even, seems optimal 164 | if (type == 99) 165 | { 166 | memset (output_dibits, 0xFF, sizeof(output_dibits)); //NOTE: 0xFF works better on bin files 167 | memset (baseband, 0, 1920*sizeof(short)); 168 | } 169 | 170 | //save dibits to DSD-FME compatible "symbol" capture bin file format 171 | if (super->opts.dibit_out) //use -C output.bin to use this format for output 172 | { 173 | for (i = 0; i < 192; i++) 174 | fputc (output_dibits[i], super->opts.dibit_out); 175 | } 176 | 177 | //save symbol stream format (M17_Implementations), if opened 178 | if (super->opts.float_symbol_out) 179 | { 180 | float val = 0; 181 | for (i = 0; i < 192; i++) 182 | { 183 | val = (float)output_symbols[i]; 184 | fwrite(&val, sizeof(float), 1, super->opts.float_symbol_out); //sizeof(float) is 4 (usually) 185 | } 186 | } 187 | 188 | //STDOUT (if not internally decoding or using rf stream decoder) 189 | if (super->opts.stdout_pipe && super->opts.internal_loopback_decoder == 0) 190 | write_stdout_pipe(super, baseband, 1920); 191 | 192 | //OSS output (if not internally decoding or using rf stream decoder) 193 | if (super->opts.use_oss_output == 1 && super->opts.internal_loopback_decoder == 0) 194 | oss_output_write(super, baseband, 1920); 195 | 196 | //don't send 'dead air' out over pulse audio devices, or may incur some lag 197 | if (type != 99) 198 | { 199 | //Pulse Audio (if RF Stream is open) 200 | #ifdef USE_PULSEAUDIO 201 | if (super->pa.pa_output_rf_is_open == 1) 202 | pulse_audio_output_rf(super, baseband, 1920); 203 | #endif 204 | 205 | } 206 | 207 | //write to rf wav file 208 | if (super->wav.wav_out_rf != NULL) 209 | { 210 | write_wav_out_rf(super, baseband, 1920); 211 | sf_write_sync (super->wav.wav_out_rf); 212 | } 213 | 214 | } 215 | 216 | -------------------------------------------------------------------------------- /src/io/pulse_devices.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * pulse-devices.c 3 | * M17 Project - Pulse Audio Sink and Source Enumeration 4 | * 5 | * based on gist found here 6 | * https://gist.github.com/andrewrk/6470f3786d05999fcb48 7 | * 8 | * LWVMOBILE 9 | * 2024-05 M17 Project - Florida Man Edition 10 | *-----------------------------------------------------------------------------*/ 11 | 12 | #include "main.h" 13 | 14 | #ifdef USE_PULSEAUDIO 15 | 16 | // This callback gets called when our context changes state. We really only 17 | // care about when it's ready or if it has failed 18 | void pa_state_cb(pa_context *c, void *userdata) 19 | { 20 | pa_context_state_t state; 21 | int *pa_ready = userdata; 22 | 23 | state = pa_context_get_state(c); 24 | switch (state) 25 | { 26 | // There are just here for reference 27 | case PA_CONTEXT_UNCONNECTED: 28 | case PA_CONTEXT_CONNECTING: 29 | case PA_CONTEXT_AUTHORIZING: 30 | case PA_CONTEXT_SETTING_NAME: 31 | default: 32 | break; 33 | case PA_CONTEXT_FAILED: 34 | case PA_CONTEXT_TERMINATED: 35 | *pa_ready = 2; 36 | break; 37 | case PA_CONTEXT_READY: 38 | *pa_ready = 1; 39 | break; 40 | } 41 | } 42 | 43 | // pa_mainloop will call this function when it's ready to tell us about a sink. 44 | // Since we're not threading, there's no need for mutexes on the devicelist 45 | // structure 46 | void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) 47 | { 48 | pa_devicelist_t *pa_devicelist = userdata; 49 | int ctr = 0; 50 | 51 | UNUSED(c); 52 | 53 | // If eol is set to a positive number, you're at the end of the list 54 | if (eol > 0) 55 | return; 56 | 57 | // We know we've allocated 16 slots to hold devices. Loop through our 58 | // structure and find the first one that's "uninitialized." Copy the 59 | // contents into it and we're done. If we receive more than 16 devices, 60 | // they're going to get dropped. You could make this dynamically allocate 61 | // space for the device list, but this is a simple example. 62 | 63 | for (ctr = 0; ctr < 16; ctr++) 64 | { 65 | if (! pa_devicelist[ctr].initialized) 66 | { 67 | strncpy(pa_devicelist[ctr].name, l->name, 511); 68 | strncpy(pa_devicelist[ctr].description, l->description, 255); 69 | pa_devicelist[ctr].index = l->index; 70 | pa_devicelist[ctr].initialized = 1; 71 | break; 72 | } 73 | } 74 | } 75 | 76 | // See above. This callback is pretty much identical to the previous 77 | void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) 78 | { 79 | pa_devicelist_t *pa_devicelist = userdata; 80 | int ctr = 0; 81 | 82 | UNUSED(c); 83 | 84 | if (eol > 0) 85 | return; 86 | 87 | for (ctr = 0; ctr < 16; ctr++) 88 | { 89 | if (! pa_devicelist[ctr].initialized) 90 | { 91 | strncpy(pa_devicelist[ctr].name, l->name, 511); 92 | strncpy(pa_devicelist[ctr].description, l->description, 255); 93 | pa_devicelist[ctr].index = l->index; 94 | pa_devicelist[ctr].initialized = 1; 95 | break; 96 | } 97 | } 98 | } 99 | 100 | int pa_get_devicelist(pa_devicelist_t *input, pa_devicelist_t *output) 101 | { 102 | // Define our pulse audio loop and connection variables 103 | pa_mainloop *pa_ml; 104 | pa_mainloop_api *pa_mlapi; 105 | pa_operation *pa_op; 106 | pa_context *pa_ctx; 107 | 108 | 109 | // We'll need these state variables to keep track of our requests 110 | int state = 0; 111 | int pa_ready = 0; 112 | 113 | // Initialize our device lists 114 | memset(input, 0, sizeof(pa_devicelist_t) * 16); 115 | memset(output, 0, sizeof(pa_devicelist_t) * 16); 116 | 117 | // Create a mainloop API and connection to the default server 118 | pa_ml = pa_mainloop_new(); 119 | pa_mlapi = pa_mainloop_get_api(pa_ml); 120 | pa_ctx = pa_context_new(pa_mlapi, "test"); 121 | 122 | // This function connects to the pulse server 123 | pa_context_connect(pa_ctx, NULL, 0, NULL); 124 | 125 | 126 | // This function defines a callback so the server will tell us it's state. 127 | // Our callback will wait for the state to be ready. The callback will 128 | // modify the variable to 1 so we know when we have a connection and it's 129 | // ready. 130 | // If there's an error, the callback will set pa_ready to 2 131 | pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); 132 | 133 | // Now we'll enter into an infinite loop until we get the data we receive 134 | // or if there's an error 135 | for (;;) 136 | { 137 | // We can't do anything until PA is ready, so just iterate the mainloop 138 | // and continue 139 | if (pa_ready == 0) 140 | { 141 | pa_mainloop_iterate(pa_ml, 1, NULL); 142 | continue; 143 | } 144 | // We couldn't get a connection to the server, so exit out 145 | if (pa_ready == 2) 146 | { 147 | pa_context_disconnect(pa_ctx); 148 | pa_context_unref(pa_ctx); 149 | pa_mainloop_free(pa_ml); 150 | return -1; 151 | } 152 | // At this point, we're connected to the server and ready to make 153 | // requests 154 | switch (state) 155 | { 156 | // State 0: we haven't done anything yet 157 | case 0: 158 | // This sends an operation to the server. pa_sinklist_info is 159 | // our callback function and a pointer to our devicelist will 160 | // be passed to the callback The operation ID is stored in the 161 | // pa_op variable 162 | pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, output); 163 | 164 | // Update state for next iteration through the loop 165 | state++; 166 | break; 167 | case 1: 168 | // Now we wait for our operation to complete. When it's 169 | // complete our pa_output_devicelist is filled out, and we move 170 | // along to the next state 171 | if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) 172 | { 173 | pa_operation_unref(pa_op); 174 | 175 | // Now we perform another operation to get the source 176 | // (input device) list just like before. This time we pass 177 | // a pointer to our input structure 178 | pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, input); 179 | // Update the state so we know what to do next 180 | state++; 181 | } 182 | break; 183 | case 2: 184 | if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) 185 | { 186 | // Now we're done, clean up and disconnect and return 187 | pa_operation_unref(pa_op); 188 | pa_context_disconnect(pa_ctx); 189 | pa_context_unref(pa_ctx); 190 | pa_mainloop_free(pa_ml); 191 | return 0; 192 | } 193 | break; 194 | default: 195 | // We should never see this state 196 | fprintf(stderr, "in state %d\n", state); 197 | return -1; 198 | } 199 | 200 | // Iterate the main loop and go again. The second argument is whether 201 | // or not the iteration should block until something is ready to be 202 | // done. Set it to zero for non-blocking. 203 | pa_mainloop_iterate(pa_ml, 1, NULL); 204 | 205 | } 206 | } 207 | 208 | int pulse_list() { 209 | 210 | fprintf (stderr, "\n"); 211 | int ctr; 212 | 213 | // This is where we'll store the input device list 214 | pa_devicelist_t pa_input_devicelist[16]; 215 | 216 | // This is where we'll store the output device list 217 | pa_devicelist_t pa_output_devicelist[16]; 218 | 219 | if (pa_get_devicelist(pa_input_devicelist, pa_output_devicelist) < 0) 220 | { 221 | fprintf(stderr, "failed to get device list\n"); 222 | return 1; 223 | } 224 | 225 | for (ctr = 0; ctr < 16; ctr++) 226 | { 227 | if (! pa_output_devicelist[ctr].initialized) 228 | break; 229 | 230 | printf("=======[ Output Device #%d ]=======\n", ctr+1); 231 | printf("Description: %s\n", pa_output_devicelist[ctr].description); 232 | printf("Name: %s\n", pa_output_devicelist[ctr].name); 233 | printf("Index: %d\n", pa_output_devicelist[ctr].index); 234 | printf("\n"); 235 | } 236 | 237 | for (ctr = 0; ctr < 16; ctr++) 238 | { 239 | if (! pa_input_devicelist[ctr].initialized) 240 | break; 241 | 242 | printf("=======[ Input Device #%d ]=======\n", ctr+1); 243 | printf("Description: %s\n", pa_input_devicelist[ctr].description); 244 | printf("Name: %s\n", pa_input_devicelist[ctr].name); 245 | printf("Index: %d\n", pa_input_devicelist[ctr].index); 246 | printf("\n"); 247 | } 248 | return 0; 249 | } 250 | 251 | #endif -------------------------------------------------------------------------------- /src/encoder/m17_ecdsa_encoder.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * m17_ecdsa_encoder.c 3 | * M17 Project - Stream Voice Encoder Signature 4 | * 5 | * LWVMOBILE 6 | * 2024-06 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | #include "main.h" 10 | #include "m17.h" 11 | 12 | //encode and create audio of a M17 Project Stream Frame Signature 13 | void encode_str_ecdsa(Super * super, uint8_t lich_cnt, uint8_t * m17_lsf, float * mem, int use_ip, int udpport, uint8_t * sid) 14 | { 15 | 16 | //quell defined but not used warnings from m17.h 17 | stfu(); 18 | 19 | ecdsa_signature_creation(super); 20 | 21 | //unpack signed payload into bits to be sent over payload 22 | uint8_t sig_bits[512]; memset(sig_bits, 0, 512*sizeof(uint8_t)); 23 | unpack_byte_array_into_bit_array(super->m17e.ecdsa.signature, sig_bits, 64); 24 | 25 | int i, j, k, x, y, z; //basic utility counters 26 | 27 | //Standard IP Framing 28 | uint8_t magic[4] = {0x4D, 0x31, 0x37, 0x20}; 29 | int udp_return = 0; UNUSED(udp_return); 30 | uint8_t m17_ip_frame[432]; memset (m17_ip_frame, 0, sizeof(m17_ip_frame)); 31 | uint8_t m17_ip_packed[54]; memset (m17_ip_packed, 0, sizeof(m17_ip_packed)); 32 | uint16_t ip_crc = 0; 33 | 34 | //frame sequence number and eot bit 35 | uint16_t fsn = 0x7FFC; //starting value for ECDSA Signature 36 | uint8_t eot = 0; 37 | 38 | uint8_t lsf_chunk[6][48]; //40 bit chunks of link information spread across 6 frames 39 | memset (lsf_chunk, 0, sizeof(lsf_chunk)); 40 | 41 | 42 | y = 0; //counter for sig_bits 43 | for (z = 0; z < 4; z++) //loop to send ECDSA Signature in 4 Frame Payload 44 | { 45 | 46 | //initialize and start assembling the completed frame 47 | 48 | //Data/Voice Portion of Stream Data Link Layer w/ FSN 49 | uint8_t m17_v1[148]; memset (m17_v1, 0, sizeof(m17_v1)); 50 | 51 | //Data/Voice Portion of Stream Data Link Layer w/ FSN (after Convolutional Encode) 52 | uint8_t m17_v1c[296]; memset (m17_v1c, 0, sizeof(m17_v1c)); 53 | 54 | //Data/Voice Portion of Stream Data Link Layer w/ FSN (after P2 Puncturing) 55 | uint8_t m17_v1p[272]; memset (m17_v1p, 0, sizeof(m17_v1p)); 56 | 57 | //LSF Chunk + LICH CNT of Stream Data Link Layer 58 | uint8_t m17_l1[48]; memset (m17_l1, 0, sizeof(m17_l1)); 59 | 60 | //LSF Chunk + LICH CNT of Stream Data Link Layer (after Golay 24,12 Encoding) 61 | uint8_t m17_l1g[96]; memset (m17_l1g, 0, sizeof(m17_l1g)); 62 | 63 | //Type 4c - Combined LSF Content Chuck and Voice/Data (96 + 272) 64 | uint8_t m17_t4c[368]; memset (m17_t4c, 0, sizeof(m17_t4c)); 65 | 66 | //Type 4i - Interleaved Bits 67 | uint8_t m17_t4i[368]; memset (m17_t4i, 0, sizeof(m17_t4i)); 68 | 69 | //Type 4s - Interleaved Bits with Scrambling Applied 70 | uint8_t m17_t4s[368]; memset (m17_t4s, 0, sizeof(m17_t4s)); 71 | 72 | //Load in Signature Bits here 73 | for (i = 0; i < 128; i++) 74 | m17_v1[i+16] = sig_bits[y++]; 75 | 76 | if (z == 3) //set EOT bit on 4th pass 77 | m17_v1[0] = 1; 78 | else m17_v1[0] = 0; 79 | 80 | //set current frame number as bits 1-15 of the v1 stream 81 | for (i = 0; i < 15; i++) 82 | m17_v1[i+1] = ( (uint8_t)(fsn >> (14-i)) ) &1; 83 | 84 | //Use the convolutional encoder to encode the voice / data stream 85 | simple_conv_encoder (m17_v1, m17_v1c, 148); 86 | 87 | //use the P2 puncture to...puncture and collapse the voice / data stream 88 | k = 0; x = 0; 89 | for (i = 0; i < 25; i++) 90 | { 91 | m17_v1p[k++] = m17_v1c[x++]; 92 | m17_v1p[k++] = m17_v1c[x++]; 93 | m17_v1p[k++] = m17_v1c[x++]; 94 | m17_v1p[k++] = m17_v1c[x++]; 95 | m17_v1p[k++] = m17_v1c[x++]; 96 | m17_v1p[k++] = m17_v1c[x++]; 97 | m17_v1p[k++] = m17_v1c[x++]; 98 | m17_v1p[k++] = m17_v1c[x++]; 99 | //quit early on last set of i when 272 k bits reached 100 | //index from 0 to 271,so 272 is breakpoint with k++ 101 | if (k == 272) break; 102 | m17_v1p[k++] = m17_v1c[x++]; 103 | m17_v1p[k++] = m17_v1c[x++]; 104 | m17_v1p[k++] = m17_v1c[x++]; 105 | x++; 106 | } 107 | 108 | //add punctured voice / data bits to the combined frame 109 | for (i = 0; i < 272; i++) 110 | m17_t4c[i+96] = m17_v1p[i]; 111 | 112 | //load up the lsf chunk for this cnt 113 | for (i = 0; i < 40; i++) 114 | lsf_chunk[lich_cnt][i] = m17_lsf[((lich_cnt)*40)+i]; 115 | 116 | //update lich_cnt in the current LSF chunk 117 | lsf_chunk[lich_cnt][40] = (lich_cnt >> 2) & 1; 118 | lsf_chunk[lich_cnt][41] = (lich_cnt >> 1) & 1; 119 | lsf_chunk[lich_cnt][42] = (lich_cnt >> 0) & 1; 120 | 121 | //encode with golay 24,12 and load into m17_l1g 122 | golay_24_12_encode (lsf_chunk[lich_cnt]+00, m17_l1g+00); 123 | golay_24_12_encode (lsf_chunk[lich_cnt]+12, m17_l1g+24); 124 | golay_24_12_encode (lsf_chunk[lich_cnt]+24, m17_l1g+48); 125 | golay_24_12_encode (lsf_chunk[lich_cnt]+36, m17_l1g+72); 126 | 127 | //add lsf chunk to the combined frame 128 | for (i = 0; i < 96; i++) 129 | m17_t4c[i] = m17_l1g[i]; 130 | 131 | //interleave the bit array using Quadratic Permutation Polynomial 132 | //function π(x) = (45x + 92x^2 ) mod 368 133 | for (i = 0; i < 368; i++) 134 | { 135 | x = ((45*i)+(92*i*i)) % 368; 136 | m17_t4i[x] = m17_t4c[i]; 137 | } 138 | 139 | //scramble/randomize the frame 140 | for (i = 0; i < 368; i++) 141 | m17_t4s[i] = (m17_t4i[i] ^ m17_scramble[i]) & 1; 142 | 143 | //----------------------------------------- 144 | 145 | fprintf (stderr, "\n M17 Stream (ENCODER): "); 146 | if (super->opts.internal_loopback_decoder == 1) 147 | demod_str(super, m17_t4s, 1); 148 | else fprintf (stderr, " To Audio Out: %s", super->pa.pa_outrf_idx); 149 | 150 | //show UDP if active 151 | if (use_ip == 1 && lich_cnt != 5 && fsn != 0x7FFF) 152 | fprintf (stderr, " UDP: %s:%d", super->opts.m17_hostname, udpport); 153 | 154 | fprintf (stderr, " Sending Signature (secp256r1);"); 155 | 156 | //convert bit array into symbols and RF/Audio 157 | encode_rfa (super, m17_t4s, mem, 2); 158 | 159 | //Contruct an IP frame using previously created arrays 160 | memset (m17_ip_frame, 0, sizeof(m17_ip_frame)); 161 | memset (m17_ip_packed, 0, sizeof(m17_ip_packed)); 162 | 163 | //add MAGIC 164 | k = 0; 165 | for (j = 0; j < 4; j++) 166 | { 167 | for (i = 0; i < 8; i++) 168 | m17_ip_frame[k++] = (magic[j] >> (7-i)) &1; 169 | } 170 | 171 | //add StreamID 172 | for (j = 0; j < 2; j++) 173 | { 174 | for (i = 0; i < 8; i++) 175 | m17_ip_frame[k++] = (sid[j] >> (7-i)) &1; 176 | } 177 | 178 | //add the current LSF, sans CRC 179 | for (i = 0; i < 224; i++) 180 | m17_ip_frame[k++] = m17_lsf[i]; 181 | 182 | if (z == 3) //set EOT bit on 4th pass 183 | eot = 1; 184 | 185 | //add eot bit flag 186 | m17_ip_frame[k++] = eot&1; 187 | 188 | //add current fsn value 189 | for (i = 0; i < 15; i++) 190 | m17_ip_frame[k++] = (fsn >> (14-i))&1; 191 | 192 | //add signature bits 193 | for (i = 0; i < 128; i++) 194 | m17_ip_frame[k++] = m17_v1[i+16]; 195 | 196 | //pack current bit array into a byte array for a CRC check 197 | for (i = 0; i < 52; i++) 198 | m17_ip_packed[i] = (uint8_t)convert_bits_into_output(&m17_ip_frame[i*8], 8); 199 | ip_crc = crc16(m17_ip_packed, 52); 200 | 201 | //add CRC value to the ip frame 202 | for (i = 0; i < 16; i++) 203 | m17_ip_frame[k++] = (ip_crc >> (15-i))&1; 204 | 205 | //pack CRC into the byte array as well 206 | for (i = 52; i < 54; i++) 207 | m17_ip_packed[i] = (uint8_t)convert_bits_into_output(&m17_ip_frame[i*8], 8); 208 | 209 | //Send packed IP frame to UDP port if enabled 210 | if (use_ip == 1) 211 | udp_return = m17_socket_blaster (super, 54, m17_ip_packed); 212 | 213 | //increment lich_cnt, reset on 6 214 | lich_cnt++; 215 | if (lich_cnt == 6) 216 | lich_cnt = 0; 217 | 218 | //increment frame sequency number 219 | fsn++; 220 | 221 | } 222 | 223 | //reset ECSDA payloads 224 | memset (super->m17e.ecdsa.curr_stream_pyl, 0, 16*sizeof(uint8_t)); 225 | memset (super->m17e.ecdsa.last_stream_pyl, 0, 16*sizeof(uint8_t)); 226 | memset (super->m17e.ecdsa.signature, 0, 64*sizeof(uint8_t)); 227 | 228 | //reset decoder side as well for loopback decoder 229 | memset (super->m17d.ecdsa.curr_stream_pyl, 0, 16*sizeof(uint8_t)); 230 | memset (super->m17d.ecdsa.last_stream_pyl, 0, 16*sizeof(uint8_t)); 231 | memset (super->m17d.ecdsa.signature, 0, 64*sizeof(uint8_t)); 232 | 233 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # M17 Project - Florida Man Edition 3 | 4 | ## Information 5 | 6 | M17-FME is a stand-alone encoder and decoder of [M17 Project](https://m17project.org/ "M17") protocol. 7 | M17-FME uses the open source [Codec2](https://github.com/drowe67/codec2 "Codec2") vocoder, and built on the specifications openly and freely provided under [M17 Protocol Specifications Part I - Air Interface](https://spec.m17project.org/ "M17 Protocol Specifications Part I - Air Interface"). M17-FME should only be considered an 'educational' tool for radio enthusiasts and people interested in tinkering. It is also in an early beta stage, and as such, is prone to error and may have changing functionality over time as features are added, removed, reworked, etc. This tool should never be used for any commercial or critical needs, and any use for ill or malicious intent (jamming, DDOS, trolling, warfare, etc) is NOT CONDONED or TOLERATED! 8 | 9 | ![M17-FME](https://github.com/lwvmobile/m17-fme/blob/main/docs/m17-fme1.png) 10 | 11 | ![M17-FME](https://github.com/lwvmobile/m17-fme/blob/main/docs/m17-fme2.png) 12 | 13 | ## Functionality 14 | 15 | ### Voice Stream 16 | 17 | Voice Stream Encoding and Decoding (RF Audio). Voice Stream Frames can be decoded from any RF Audio source, and M17-FME supports reading in from a Pulse Audio Input Source, OSS Input Source "/dev/dsp", S16LE 48k/1 STDIN Input, TCP Linked S16LE 48k/1 Network Source [SDR++](https://github.com/AlexandreRouma/SDRPlusPlus "SDR++"), sndfile compatible S16LE 48k/1 .wav and .rrc files generated by any compatible M17 project source or recorded from OTA from an actual radio, float symbol input files generated by [M17_Implementations](https://github.com/M17-Project/M17_Implementations "M17_Implementations"), and dibit capture format (capture.bin) generated by [DSD-FME](https://github.com/lwvmobile/dsd-fme "DSD-FME"). 18 | 19 | When operating in encoding mode, Voice Stream can be modulated in FSK4 48k/1 S16LE RF Audio output to Pulse Audio Sink, OSS Audio Sink, STDOUT, .wav file, float symbol output file, or dibit output file. Also, while in Encoding Mode, encoded data can also be sent back into the decoder via a loopback functionality which will, in turn, decode the encoded bitstream, with audio output being either decoded voice output, encoded RF output, or both through seperate output sinks, along with any file formats specified. 20 | 21 | When operating in decoding mode, RF audio can be captured and saved with floating symbol output file, and decoded voice can be saved on a 'per call' basis with file creation for each new call after an EOT signal or a no-sync period. 22 | 23 | Voice Encoding and Decoding support both Codec2 3200 bps mode "full rate" and Codec2 1600 bps "half rate" modes, per specification. To encode in 1600 bps mode, the user on needs to specify some Arbitrary Data to the encoder, which is in turn handled as an embedded SMS message of up to 48 ASCII Characters to be decoded every superframe. 24 | 25 | ### Packet Data 26 | 27 | Packet Data Encoding and Decoding (RF Audio). Same input and output methods as listed above are available for Packet Data. Currently, only SMS text message protocol is expressely supported by Packet Data Encoding, while all standardized protocols (GNSS, etc) are decoded via the decoder if from another source. Users can encode an SMS message of up to 821 UTF-8 characters. Users can also enter raw encoded packet data as a string of hex octets to be encoded by the packet encoder (up to 823 octets). Packet Data can now also be easily entered (i.e., SMS text messages, or raw data) via the ncurses terminal during Stream Encoding or TX and RX Mode, rather than at the CLI, which is still useful for one time data transmissions. 28 | 29 | ### BERT (Bit Error Rate Test) 30 | 31 | M17-FME can encode the M17 BERT (Bit Error Rate Test) as described in 2.10 and Appendix G for RF Application and also Demodulate and Synchronize to same BERT Mode Test for RF. Encoded frames can be sent over Audio Sink, saved to wav file, dibit output file, or symbol output file. BERT Encoding is available on CLI only. 32 | 33 | ### UDP/IP Frame Format 34 | 35 | M17-FME is capable of transmitting and receiving UDP frames based on [M17 Protocol Specifications Part II - Internet Interface](https://github.com/M17-Project/M17_inet "M17 Protocol Specifications Part II - Internet Interface"). 36 | 37 | Two modes of operation are currently available. Reflector Client mode will allow users to connect to active [MREFD](https://github.com/n7tae/mrefd "MREFD") reflectors either in Listen Only Mode (LSTN), or with transmit capabilities (CONN). Users must have a valid callsign/license to transmit on an active reflector and must affirm with YES before starting any session. Reflector Client mode is currently only available as a subset of the TX and RX mode described below for TX capability, but Listen Only Mode is also available in the older UDP IP Frame Decoder . 38 | 39 | Adhoc mode is also available, and allows multiple clients to talk to one another on a shared network by binding a UDP port and broadcasting over the broadcast address for the network subnet. Adhoc mode does not connect to a reflector and does not transmit over the air. 40 | 41 | Encryption is disabled on Reflector Client Mode, but is permissable on Adhoc mode. 42 | 43 | Please note, IP6 is currently not supported, but will be supported in a future update. 44 | 45 | ### TX and RX Mode 46 | 47 | A new encoder and decoder routine has been written for M17-FME. Now, users can both encode and transmit when not RX, AND listen for and decode M17 traffic when not TX, either over RF, or via UDP/IP. Keep in mind, that a user can only use RF or IP, but not both at the same time for TX and RX Mode operations. See below linked Example Usage for more information on using TX and RX Mode. Most all functionality of the seperate encoder and decoder has been retained for TX and RX Mode, with the exception of Voice Activated Transmit (Vox), which is only available on the pure encoder. Also, keep in mind that TX and RX Mode will require the use of both the ncurses terminal AND the use of pulse audio, no other input or output methods will work with TX and RX Mode due to its simultaneous encode and decode operations, and needing to gracefully open and close input and output streams on demand in the software. 48 | 49 | ### Encryption 50 | 51 | M17-FME supports both the encryption and decryption of Voice Stream using AES and Scrambler modes per specification. M17-FME also (experimentally and unofficially) supports encryption and decryption of Packet Data using AES and Scrambler modes. The official stance from M17-FME is that encryption should be used as a tool, and in the context of M17-FME, it is available as a learning tool. When using encryption mode with "OTAKD" enabled, M17-FME will craft and send Packet Data formatted with your encryption key, encryption type, and encryption subtype at the start of any TX. "Over the Air Key Delivery" or "OTAKD" was devised as a method to both allow and learn form the use of encryption, but to also freely and openly provide the encryption key to others to use while decoding. This 'format' is NOT per M17 specification, but is a method devised internally to allow the use of encryption while sharing the key for others. OTAKD can be enabled by using the `-O` command line option, or using the `O` or `o` keyboard shortcut in the Ncurses Terminal. 52 | 53 | NOTE: Encryption Modes and Ncurses Shortcuts pertaining to Encryption will be disabled during any IP Reflector Client Sessions. 54 | 55 | ### secp256r1 Signatures with Private and Public Keys 56 | 57 | An emerging standard in M17 is the use of private and public keys to generate secp256r1 signatures and attach them to the tail end of transmissions. M17-FME can both encode and decode secp256r1 signatures using either private keys for encoding, public keys for decoding, or a combination of both. Users need only to generate and specify keys to be imported with the `-k` and `-K` option. See the included key folder for example keys. Keys can be generated in multiple ways, for example, using the command: `openssl ecparam -name prime256v1 -genkey -noout -out keys.pem && openssl pkey -in keys.pem -text > keys.txt && rm keys.pem` and then copy and pasting the resulting public and private key into txt files like in the example, or by using the `-5` command line option. Note: Using the first method mentioned, on the public key, discard the first octet of the public key, or octet 0x04. 58 | 59 | Similar to OTAKD for Encryption, OTASK format has been created for delivering signature public keys over the air. OTASK can be enabled at the command line via `-Q` option, or enabled or sent one time via the `P` and `p` options. Random Key Pairs can be generated at the command line via the `-3` option as mentioned above and used during session, or generated for future use with the `-5` command line option. Simialrly, Key Pairs can be generated in the Ncurses Terminal with the `-3` keyboard shortcut, and enabled or sent one time with the `P` and `p` keys. 60 | 61 | ### How to Use 62 | 63 | Please see [Example Usage](https://github.com/lwvmobile/m17-fme/blob/main/docs/Example_Usage.md "Example Usage") for a complete set of use case scenarios and configuration. 64 | 65 | ### How to Build 66 | 67 | Please see [Install Notes](https://github.com/lwvmobile/m17-fme/blob/main/docs/Install_Notes.md "Install Notes") for information on cloning and compiling M17-FME. 68 | 69 | -------------------------------------------------------------------------------- /src/fec/golay.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------------- 2 | * golay.c 3 | * M17 Project - Golay 24_12 Encoder and Decoder 4 | * 5 | * LWVMOBILE 6 | * 2024-05 M17 Project - Florida Man Edition 7 | *-----------------------------------------------------------------------------*/ 8 | 9 | /////////////////////////////////////////////////////////////////////////////////// 10 | // Copyright (C) 2016 Edouard Griffiths, F4EXB. // 11 | // // 12 | // This program is free software; you can redistribute it and/or modify // 13 | // it under the terms of the GNU General Public License as published by // 14 | // the Free Software Foundation as version 3 of the License, or // 15 | // // 16 | // This program is distributed in the hope that it will be useful, // 17 | // but WITHOUT ANY WARRANTY; without even the implied warranty of // 18 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // 19 | // GNU General Public License V3 for more details. // 20 | // // 21 | // You should have received a copy of the GNU General Public License // 22 | // along with this program. If not, see . // 23 | /////////////////////////////////////////////////////////////////////////////////// 24 | 25 | #include "main.h" 26 | 27 | unsigned char Golay_24_12_m_corr[4096][3]; //!< up to 3 bit error correction by syndrome index 28 | 29 | //!< Generator matrix of bits 30 | const unsigned char Golay_24_12_m_G[24*12] = { 31 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 32 | 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 33 | 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 34 | 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 35 | 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 36 | 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 37 | 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 38 | 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 43 | }; 44 | 45 | //!< Parity check matrix of bits 46 | const unsigned char Golay_24_12_m_H[24*12] = { 47 | 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48 | 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49 | 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50 | 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 51 | 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 52 | 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 53 | 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 54 | 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 55 | 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 56 | 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 57 | 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 58 | 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 59 | }; 60 | 61 | void golay_24_12_init() 62 | { 63 | int i1 = 0, i2 = 0, i3 = 0, ir = 0, ip = 0; 64 | int syndromeI = 0, syndromeIP = 0; 65 | int ip1 = 0, ip2 = 0, ip3 = 0; 66 | int syndromeIP1 = 0, syndromeIP2 = 0, syndromeIP3 = 0; 67 | 68 | memset (Golay_24_12_m_corr, 0xFF, 3*4096); 69 | 70 | for (i1 = 0; i1 < 12; i1++) 71 | { 72 | for (i2 = i1+1; i2 < 12; i2++) 73 | { 74 | for (i3 = i2+1; i3 < 12; i3++) 75 | { 76 | // 3 bit patterns 77 | syndromeI = 0; 78 | 79 | for (ir = 0; ir < 12; ir++) 80 | { 81 | syndromeI += ((Golay_24_12_m_H[24*ir + i1] + Golay_24_12_m_H[24*ir + i2] + Golay_24_12_m_H[24*ir + i3]) % 2) << (11-ir); 82 | } 83 | 84 | Golay_24_12_m_corr[syndromeI][0] = i1; 85 | Golay_24_12_m_corr[syndromeI][1] = i2; 86 | Golay_24_12_m_corr[syndromeI][2] = i3; 87 | } 88 | 89 | // 2 bit patterns 90 | syndromeI = 0; 91 | 92 | for (ir = 0; ir < 12; ir++) 93 | { 94 | syndromeI += ((Golay_24_12_m_H[24*ir + i1] + Golay_24_12_m_H[24*ir + i2]) % 2) << (11-ir); 95 | } 96 | 97 | Golay_24_12_m_corr[syndromeI][0] = i1; 98 | Golay_24_12_m_corr[syndromeI][1] = i2; 99 | 100 | // 1 possible bit flip left in the parity part 101 | for (ip = 0; ip < 12; ip++) 102 | { 103 | syndromeIP = syndromeI ^ (1 << (11-ip)); 104 | Golay_24_12_m_corr[syndromeIP][0] = i1; 105 | Golay_24_12_m_corr[syndromeIP][1] = i2; 106 | Golay_24_12_m_corr[syndromeIP][2] = 12 + ip; 107 | } 108 | } 109 | 110 | // single bit patterns 111 | syndromeI = 0; 112 | 113 | for (ir = 0; ir < 12; ir++) 114 | { 115 | syndromeI += Golay_24_12_m_H[24*ir + i1] << (11-ir); 116 | } 117 | 118 | Golay_24_12_m_corr[syndromeI][0] = i1; 119 | 120 | for (ip1 = 0; ip1 < 12; ip1++) // 1 more bit flip in parity 121 | { 122 | syndromeIP1 = syndromeI ^ (1 << (11-ip1)); 123 | Golay_24_12_m_corr[syndromeIP1][0] = i1; 124 | Golay_24_12_m_corr[syndromeIP1][1] = 12 + ip1; 125 | 126 | for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity 127 | { 128 | syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); 129 | Golay_24_12_m_corr[syndromeIP2][0] = i1; 130 | Golay_24_12_m_corr[syndromeIP2][1] = 12 + ip1; 131 | Golay_24_12_m_corr[syndromeIP2][2] = 12 + ip2; 132 | } 133 | } 134 | } 135 | 136 | // no bit patterns (in message) -> all in parity 137 | for (ip1 = 0; ip1 < 12; ip1++) // 1 bit flip in parity 138 | { 139 | syndromeIP1 = (1 << (11-ip1)); 140 | Golay_24_12_m_corr[syndromeIP1][0] = 12 + ip1; 141 | 142 | for (ip2 = ip1+1; ip2 < 12; ip2++) // 1 more bit flip in parity 143 | { 144 | syndromeIP2 = syndromeIP1 ^ (1 << (11-ip2)); 145 | Golay_24_12_m_corr[syndromeIP2][0] = 12 + ip1; 146 | Golay_24_12_m_corr[syndromeIP2][1] = 12 + ip2; 147 | 148 | for (ip3 = ip2+1; ip3 < 12; ip3++) // 1 more bit flip in parity 149 | { 150 | syndromeIP3 = syndromeIP2 ^ (1 << (11-ip3)); 151 | Golay_24_12_m_corr[syndromeIP3][0] = 12 + ip1; 152 | Golay_24_12_m_corr[syndromeIP3][1] = 12 + ip2; 153 | Golay_24_12_m_corr[syndromeIP3][2] = 12 + ip3; 154 | } 155 | } 156 | } 157 | } 158 | 159 | void golay_24_12_encode(unsigned char *origBits, unsigned char *encodedBits) 160 | { 161 | int i = 0, j = 0; 162 | // memset(encodedBits, 0, 24); 163 | for (i = 0; i < 12; i++) 164 | { 165 | for (j = 0; j < 24; j++) 166 | encodedBits[j] += origBits[i] * Golay_24_12_m_G[24*i + j]; 167 | } 168 | for (i = 0; i < 24; i++) 169 | encodedBits[i] %= 2; 170 | } 171 | 172 | bool golay_24_12_decode(unsigned char *rxBits) 173 | { 174 | unsigned int syndromeI = 0; // syndrome index 175 | int is = 0; 176 | int i = 0; 177 | 178 | for (is = 0; is < 12; is++) 179 | { 180 | syndromeI += (((rxBits[0] * Golay_24_12_m_H[24*is + 0]) 181 | + (rxBits[1] * Golay_24_12_m_H[24*is + 1]) 182 | + (rxBits[2] * Golay_24_12_m_H[24*is + 2]) 183 | + (rxBits[3] * Golay_24_12_m_H[24*is + 3]) 184 | + (rxBits[4] * Golay_24_12_m_H[24*is + 4]) 185 | + (rxBits[5] * Golay_24_12_m_H[24*is + 5]) 186 | + (rxBits[6] * Golay_24_12_m_H[24*is + 6]) 187 | + (rxBits[7] * Golay_24_12_m_H[24*is + 7]) 188 | + (rxBits[8] * Golay_24_12_m_H[24*is + 8]) 189 | + (rxBits[9] * Golay_24_12_m_H[24*is + 9]) 190 | + (rxBits[10] * Golay_24_12_m_H[24*is + 10]) 191 | + (rxBits[11] * Golay_24_12_m_H[24*is + 11]) 192 | + (rxBits[12] * Golay_24_12_m_H[24*is + 12]) 193 | + (rxBits[13] * Golay_24_12_m_H[24*is + 13]) 194 | + (rxBits[14] * Golay_24_12_m_H[24*is + 14]) 195 | + (rxBits[15] * Golay_24_12_m_H[24*is + 15]) 196 | + (rxBits[16] * Golay_24_12_m_H[24*is + 16]) 197 | + (rxBits[17] * Golay_24_12_m_H[24*is + 17]) 198 | + (rxBits[18] * Golay_24_12_m_H[24*is + 18]) 199 | + (rxBits[19] * Golay_24_12_m_H[24*is + 19]) 200 | + (rxBits[20] * Golay_24_12_m_H[24*is + 20]) 201 | + (rxBits[21] * Golay_24_12_m_H[24*is + 21]) 202 | + (rxBits[22] * Golay_24_12_m_H[24*is + 22]) 203 | + (rxBits[23] * Golay_24_12_m_H[24*is + 23])) % 2) << (11-is); 204 | } 205 | 206 | if (syndromeI > 0) 207 | { 208 | i = 0; 209 | 210 | for (; i < 3; i++) 211 | { 212 | if (Golay_24_12_m_corr[syndromeI][i] == 0xFF) 213 | { 214 | break; 215 | } 216 | else 217 | { 218 | rxBits[Golay_24_12_m_corr[syndromeI][i]] ^= 1; // flip bit 219 | } 220 | } 221 | 222 | if (i == 0) 223 | { 224 | return false; 225 | } 226 | } 227 | 228 | return true; 229 | } -------------------------------------------------------------------------------- /scripts/cygwin_bat/complete_usage_options.txt: -------------------------------------------------------------------------------- 1 | M17 Project - Florida Man Edition 2 | Build Version: 2025-46-g4672f58 3 | Specification Version: 2.0; 4 | Specification Date: Aug 26, 2025 5 | Session Number: 1919 6 | 7 | Usage: m17-fme [options] Start the Program 8 | or: m17-fme -h Show Help 9 | 10 | Display Options: 11 | 12 | -N Use NCurses Terminal 13 | m17-fme -N 2> log.txt 14 | -v Payload Verbosity Level 15 | -d Demodulator Verbosity Level 16 | 17 | Device Options: 18 | 19 | -a List All Pulse Audio Input Sources and Output Sinks (devices). 20 | 21 | Input Options: 22 | 23 | -i Audio input device (default is pulserf) 24 | pulserf for pulse audio RFA input 25 | pulserf:6 or pulserf:m17_sink2.monitor for pulse audio RFA input on m17_sink2 (see -a) 26 | pulsevx for pulse audio Voice / Mic input 27 | pulsedxv for pulse audio Voice / Mic input on TX and RX Mode Operation 28 | pulsevx:2, pulsedxv:2, or pulsevx:alsa_input.pci-0000_0d_00.3.analog-stereo for pulse audio Voice / Mic input on device (see -a) 29 | - for STDIN input (specify encoder or decoder options below) 30 | (Note: When using STDIN, Ncurses Keyboard Shortcuts Disabled) 31 | (padsp wrapper required for OSS audio on Linux) 32 | /dev/dsp for OSS audio 33 | udp for UDP Frame Input (default localhost:17000) 34 | udp:192.168.7.8:17001:A for M17 UDP/IP Adhoc input (Address, Port, (A)dhock) 35 | udp:192.168.7.8:17001:R:C:YES for M17 UDP/IP Reflector input (Address, Port, (R)eflector, Module, Affirmation (See Below)) 36 | tcp for Network Audio TCP Source at 48000 (SDR++) 37 | tcp:192.168.7.5:7355 for Network Audio TCP Source at 48000 (SDR++) 38 | -w 48k/1 SNDFile Compatible RF Audio .wav or .rrc input file 39 | -c DSD-FME Compatible Dibit/Symbol Capture Bin input file (from RF Encoder) 40 | -f Float Symbol input file (from RF Encoder and M17_Implementations) 41 | -^ IP Frame input file (from M17-FME IP Frame Decoder) 42 | 43 | Output Options: 44 | 45 | -o Audio output device (default is pulsevx) 46 | pulserf for pulse audio RFA output 47 | pulserf:5 or pulserf:m17_sink2 for pulse audio RFA output on m17_sink2 (see -a) 48 | pulsevx for pulse audio Voice / Loopback output 49 | pulsedxv for pulse audio Voice output on TX and RX Mode Operation 50 | pulsevx:1, pulsedxv:1, or pulsevx:alsa_output.pci-0000_0d_00.3.analog-stereo for pulse audio Voice / Loopback output on device (see -a) 51 | - for STDOUT output (specify encoder or decoder options below) 52 | (Note: Don't use Ncurses Terminal w/ STDOUT enabled) 53 | (padsp wrapper required for OSS audio on Linux) 54 | /dev/dsp for OSS audio 55 | (OSS Can only do either RF output, or VX output, 56 | not both at the same time, specify encoder and decoder options below) 57 | udp for UDP Frame Output (default localhost:17000) 58 | udp:192.168.7.8:17001 for M17 UDP/IP blaster output (Target Address and Port) 59 | m17udp:192.168.7.8:17001 for M17 UDP/IP blaster output (Target Address and Port) 60 | -W 48k/1 SNDFile Compatible RF Audio .wav output file 61 | -C DSD-FME Compatible Dibit/Symbol Capture Bin output file 62 | -F Float Symbol output file (M17_Implementations Compatible) 63 | -* IP Frame output file (from M17-FME IP Frame Decoder) 64 | 65 | Encoder Options: 66 | 67 | -V Enable the Stream Voice Encoder 68 | -P Enable the Packet Data Encoder 69 | -! Enable the Bit Error Rate Test (BERT) Encoder (RFA only) 70 | -I Enable IP Frame Output with defaults (can be combined with Loopback or RFA output) 71 | -L Enable Internal Encoder Loopback Decoder (must be used with pulsevx output) 72 | -X Enable Voice Activated TX (Vox) on Stream Voice Encoder 73 | -s Input Squelch v RMS Level (Vox) on Stream Voice Encoder 74 | -x Modulate Inverted Polarity on RF Output 75 | -K Load secp256r1 Private Key from file. (see example key: key/sig_pri_key.txt) 76 | 77 | Encoder Input Strings: 78 | 79 | -M Set M17 CAN:SRC:DST 80 | (example: -M 1:N0CALL:SP5WWP) 81 | Note: Use an Underscore (_) for any required spaces when using a reflector, etc. 82 | -U Set UDP/IP Frame HOST:PORT:MODE:MODULE:AFFIRMATION 83 | (example: -U 127.0.0.1:17001:A) 84 | (example: -U 127.0.0.1:17001:R:C:YES) 85 | Note: Affirmation YES means you have a valid callsign and verify you are allowed to legally TX 86 | -S Enter SMS Message (up to 821 UTF-8 characters) for Packet Data Encoder 87 | (example: -S 'Hello World! This is a text message') 88 | -A Enter SMS Message (Up to 48 ASCII characters) For Stream Voice Encoder (Arbitrary Data). Enables 1600 mode. 89 | (example: -A 'Hello World! This is arbitrary data on 1600') 90 | -R Enter RAW Data for Packet Data Encoder as Hex Octets (up to 823 octets). 91 | (example: -R 81F0F2B42B20ABC500C80424064000) for Packet GNSS Position @ Wally World) 92 | 93 | (NOTE: Using Meta Fields is not compatible with Using Encryption!) 94 | -Y Enter META Data for Stream Voice Encoder as Text String (Up to 13 UTF-8 characters, single segment only); 95 | (example: -Y 'Hello World!!') for Meta Text 96 | -Z Enter META Data for Stream Voice Encoder as Hex Octets (1 Meta Type Octet + 14 Hex Octets Max); 97 | (example: -Z 01F0F2B42B20ABC500C80424064000) for Meta GNSS Position @ Wally World 98 | 99 | Decoder Options: 100 | 101 | -r Enable RFA Demodulator and Decoding of Stream and Packet Data 102 | -x Demodulate Inverted Polarity on RF Input 103 | -m Enable Analog / Raw Input Signal Monitor on RF Input (when no sync) 104 | -l Enable Event Log File: date_time_m17fme_eventlog.txt 105 | -u Enable UDP IP Frame Decoder and Connect to default localhost:17000 106 | -p Per Call decoded voice wav file saving into current directory ./m17wav folder 107 | -k Load secp256r1 Public Key from file. (see example key: key/sig_pub_key.txt) 108 | -j Load Callsign Lockout from file. (txt file, each line with 9 characters) 109 | 110 | TX and RX Options: 111 | 112 | -D Enable TX and RX Mode (Send and Receive over RF or IP Frame) 113 | Current Implementation Requires Pulse Audio and Ncurses Availability for RF Mode. 114 | RF Example (w/ Multiple Audio Devices or Virtual / Null Sinks): 115 | m17-fme -D 2> m17e.txt 116 | 117 | IP Frame Example(s): 118 | 119 | Adhoc Mode: 120 | LAN Machine 1: m17-fme -D 2> m17e.txt -I -U 192.168.7.255:17000:A 121 | LAN MAchine 2: m17-fme -D 2> m17e.txt -I -U 192.168.7.255:17000:A 122 | Note: Adhoc Mode does not require the use of a module selection. 123 | 124 | Reflector Client Mode: 125 | LSTN MODE: m17-fme -D 2> m17e.txt -M 0:M17FME000:ALL -I -U 112.213.34.65:17000:R:C:NO 126 | CONN MODE: m17-fme -D 2> m17e.txt -M 0:SP5WWP__D:ALL -I -U 112.213.34.65:17000:R:C:YES 127 | Note: Using Reflector mode, you must enter all fields, including R for reflector, module 128 | and YES to affirm you have a valid callsign for TX. NO is Listen Only (LSTN) Mode. 129 | 130 | Encryption Options: 131 | 132 | (NOTE: Encoder and Decoder share same values here) 133 | -e Enter Scrambler Key Value (up to 24-bit / 6 Hex Character) 134 | (example: -e ABCDEF) 135 | -E Enter AES Key Value (in single quote, space every 16 chars) 136 | (example: -E '0520C1B0220AFBCA 16FB1330764B26EC 5C34A197764C147A 15FBA7515ED8BCFC') 137 | (example: -E '0520C1B0220AFBCA 16FB1330764B26EC') 138 | (Limiting significant key value to first 32 characters to maintain compatibility) 139 | -J Load AES Key from file. (see example key: key/aes_key.txt) 140 | -O Send OTA Key Delivery Packets for AES and Scrambler Keys 141 | -Q Send OTA Key Delivery Packets for Signature Public Keys 142 | 143 | Debug Options: 144 | 145 | -1 Generate Random One Time Use 24-bit Scrambler Key 146 | -2 Generate Random One Time Use 256-bit AES Key. 147 | -3 Generate Random Keys For secp256r1 Signatures. Enable Signing and Verification. 148 | -5 Generate Random Keys For secp256r1 Signatures, and exit. 149 | -4 Permit Data Decoding on CRC Failure (not recommended). 150 | -6 Open All Pulse Input / Output and IP Frame Defaults and Send Voice Stream. (Fire Everything!). 151 | -7 Disable Symbol Timing Correction. 152 | -8 Disable High Pass Filter on CODEC2 Output. 153 | -9 Enable RRC Filter on RF Audio Encoding / Decoding. 154 | -0 Disable RRC Filter on RF Audio Encoding / Decoding. 155 | 156 | Quick Examples: 157 | 158 | Stream Voice Encoder with Mic Input (pulsevx) RF Output (pulserf), float symbol file output (float.sym) 159 | m17-fme -i pulsevx -o pulserf -V -F float.sym -N 2> m17encoder.txt 160 | (Note: When Using Ncurses Terminal with Encoding and Not Vox, use '\' key to toggle TX) 161 | 162 | RF Demodulator for Stream Voice and Data Packet with Decoded Voice Output (pulsevx) 163 | m17-fme -i pulserf -o pulsevx -r -N 2> m17decoder.txt 164 | 165 | Stream Voice Encoder with Mic Input (pulsevx) IP Frame Output Adhoc Host and Port 166 | m17-fme -i pulsevx -o udp:192.168.7.255:17000:A -V -I -N 2> m17encoder.txt 167 | 168 | IP Frame Decoder for Voice Stream and Packet Data Adhoc Host and Port (Adhoc 0.0.0.0:17000) 169 | m17-fme -i udp -u -o pulsevx -N 2> m17decoder.txt 170 | 171 | Packet Data Encoder with SMS Message to Adhoc IP Frame Output to custom port and RF Audio Output 172 | m17-fme -o pulserf -P -S 'This is a text message' -M 1:M17-FME:ALL -I -U 127.0.0.1:17001:A 173 | 174 | IP Frame Decoder for Voice Stream and Packet Data Bound to Adhoc Custom Address and Port 175 | m17-fme -i udp:127.0.0.1:17001:A -N 2> m17decoder.txt 176 | 177 | IP Frame Decoder for Voice Stream and Packet Data Connect to Reflector Address, Port, R, Module in LSTN mode 178 | m17-fme -i udp:172.234.217.28:17000:R:C -M 0:M17FME0:ALL -o pulsevx -N 2> m17decoder.txt 179 | --------------------------------------------------------------------------------