From 2c023134f8fb0d1c35836b2c4c993e2459236bef Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 29 Apr 2018 19:30:00 -0700 Subject: [PATCH 001/553] update to MMDVM.README and reflist.sh --- MMDVM.README | 5 +++++ reflist.sh | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/MMDVM.README b/MMDVM.README index 6ca8758..c9f9cb0 100644 --- a/MMDVM.README +++ b/MMDVM.README @@ -11,6 +11,11 @@ locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" befor your start. On a Raspberry Pi, you can do all of this with the configureation menu: "sudo raspi-config". +If you are using a device that uses the GPIO header on the raspberry pi, you need to +disable the serial0 console in the /boot/cmdline.txt file: Remove the reference to +"console=serial0,115200" in this file. You should also disable bluetooth by adding: +"dtoverlay=pi3-disable-bt" (without the quotes) to the end of the /boot/config.txt. + 1) Install the only external library you need: sudo apt-get install libconfig++-dev Yeah! No wxWidgets! diff --git a/reflist.sh b/reflist.sh index aa50572..bdaf8ac 100755 --- a/reflist.sh +++ b/reflist.sh @@ -11,4 +11,4 @@ awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt -/bin/rm -f D{Extra,Plus,DSC}_Hosts.txt +/bin/rm -f D{Extra,Plus,DCS}_Hosts.txt From c3e6d43865c5315d1b6d959350235c5545239fec Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 30 Apr 2018 10:37:36 -0700 Subject: [PATCH 002/553] cleaned up REF mheard problem --- QnetGateway.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index d292dca..c32eef7 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1529,7 +1529,8 @@ void CQnetGateway::process() ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], @@ -1783,13 +1784,14 @@ void CQnetGateway::process() /*** if YRCALL is CQCQCQ, set dest_rptr ***/ if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; + // if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + // band_txt[i].dest_rptr[0] = '\0'; } ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + (band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], From 973f1d113218c4f7773446e0f4c866474472ef8d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 30 Apr 2018 10:53:13 -0700 Subject: [PATCH 003/553] bad edit --- QnetGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index c32eef7..15728e8 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1791,7 +1791,7 @@ void CQnetGateway::process() ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - (band_txt[i].lh_yrcall, + band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], From 0baae420ff078fd201b8243e5b2fbf4bbb3bfbee Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 9 May 2018 21:49:40 -0700 Subject: [PATCH 004/553] fixed comparison in IRCDDBApp::findServerUser --- ircddb/IRCDDBApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ircddb/IRCDDBApp.cpp b/ircddb/IRCDDBApp.cpp index a2e7f7c..340629a 100644 --- a/ircddb/IRCDDBApp.cpp +++ b/ircddb/IRCDDBApp.cpp @@ -375,7 +375,7 @@ bool IRCDDBApp::findServerUser() for (it=d->user.begin(); it!=d->user.end(); ++it) { IRCDDBAppUserObject u = it->second; - if (0==u.nick.compare(0, 2, "s-") && u.op && !d->myNick.compare(u.nick) && 0==u.nick.compare(d->bestServer)) { + if (0==u.nick.compare(0, 2, "s-") && u.op && d->myNick.compare(u.nick) && 0==u.nick.compare(d->bestServer)) { d->currentServer = u.nick; found = true; break; From 26b52b66df5e354ef06cdb377ea0286e5749c500 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 21 May 2018 12:16:38 -0700 Subject: [PATCH 005/553] QnetLink in a class --- QnetLink.cpp | 489 ++++++++++++++++++--------------------------------- QnetLink.h | 161 +++++++++++++++++ 2 files changed, 328 insertions(+), 322 deletions(-) create mode 100644 QnetLink.h diff --git a/QnetLink.cpp b/QnetLink.cpp index 690dacf..5b9ebdb 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -44,7 +44,6 @@ #include #include #include -/* Required for Binary search trees using C++ STL */ #include #include #include @@ -53,169 +52,24 @@ #include #include #include "versions.h" +#include "QnetLink.h" + using namespace libconfig; -/*** version number must be x.xx ***/ -#define VERSION LINK_VERSION -#define CALL_SIZE 8 -#define IP_SIZE 15 -#define QUERY_SIZE 56 -#define MAXHOSTNAMELEN 64 -#define TIMEOUT 50 - -/* configuration data */ -static std::string login_call; -static std::string owner; -static std::string to_g2_external_ip; -static std::string my_g2_link_ip; -static std::string gwys; -static std::string status_file; -static std::string announce_dir; -static bool qso_details; -static bool bool_rptr_ack; -static bool announce; -static int rmt_xrf_port; -static int rmt_ref_port; -static int rmt_dcs_port; -static int my_g2_link_port; -static int to_g2_external_port; -static int delay_between; -static int delay_before; -static char link_at_startup[CALL_SIZE+1]; -static unsigned int max_dongles; -static unsigned int saved_max_dongles; -static long rf_inactivity_timer[3]; - -static unsigned char REF_ACK[3] = { 3, 96, 0 }; - -// This is the data payload in the map: inbound_list -// This is for inbound dongles - -struct inbound { - char call[CALL_SIZE + 1]; // the callsign of the remote - struct sockaddr_in sin; // IP and port of remote - short countdown; // if countdown expires, the connection is terminated - char mod; // A B C This user talked on this module - char client; // dvap, dvdongle -}; - -// the Key in this inbound_list map is the unique IP address of the remote -static std::map inbound_list; -static std::set admin; -static std::set link_unlink_user; -static std::set link_blacklist; - -#define LH_MAX_SIZE 39 -typedef std::map dt_lh_type; -static dt_lh_type dt_lh_list; - -static struct { - char to_call[CALL_SIZE + 1]; - struct sockaddr_in toDst4; - char from_mod; - char to_mod; - short countdown; - bool is_connected; - unsigned char in_streamid[2]; // incoming from remote systems - unsigned char out_streamid[2]; // outgoing to remote systems -} to_remote_g2[3]; - -// broadcast for data arriving from xrf to local rptr -static struct { - unsigned char xrf_streamid[2]; // streamid from xrf - unsigned char rptr_streamid[2][2]; // generated streamid to rptr(s) -} brd_from_xrf; -static unsigned char from_xrf_torptr_brd[56]; -static short brd_from_xrf_idx = 0; - -// broadcast for data arriving from local rptr to xrf -static struct { - unsigned char from_rptr_streamid[2]; - unsigned char to_rptr_streamid[2][2]; -} brd_from_rptr; -static unsigned char fromrptr_torptr_brd[56]; -static short brd_from_rptr_idx = 0; - -static struct { - unsigned char streamid[2]; - time_t last_time; // last time RF user talked -} tracing[3] = { - { {0,0}, 0 }, - { {0,0}, 0 }, - { {0,0}, 0 } -}; - -// input from remote -static int xrf_g2_sock = -1; -static int ref_g2_sock = -1; -static int dcs_g2_sock = -1; -static struct sockaddr_in fromDst4; - -// After we receive it from remote g2, -// we must feed it to our local repeater. -static struct sockaddr_in toLocalg2; - -// input from our own local repeater -static int rptr_sock = -1; -static struct sockaddr_in fromRptr; - -static fd_set fdset; -static struct timeval tv; - -static std::atomic keep_running(true); - -// Used to validate incoming donglers -static regex_t preg; - -const char* G2_html = "" - "" - "" - "
" - "REPEATER QnetGateway v1.0+" - "
"; - -// the map of remotes -// key is the callsign, data is the host -typedef std::map gwy_list_type; -static gwy_list_type gwy_list; - -static unsigned char queryCommand[QUERY_SIZE]; - -// START: TEXT crap -static char dtmf_mycall[3][CALL_SIZE + 1] = { {""}, {""}, {""} }; -static bool new_group[3] = { true, true, true }; -static int header_type = 0; -static bool GPS_seen[3] = { false, false, false }; -unsigned char tmp_txt[3]; -static char *p_tmp2 = NULL; -// END: TEXT crap - -// this is used for the "dashboard and qso_details" to avoid processing multiple headers -static struct { - unsigned char sid[2]; -} old_sid[3] = { - { {0x00, 0x00} }, - { {0x00, 0x00} }, - { {0x00, 0x00} } -}; - -static bool load_gwys(const std::string &filename); -static void calcPFCS(unsigned char *packet, int len); -static bool read_config(char *); -static bool srv_open(); -static void srv_close(); -static void sigCatch(int signum); -static void g2link(char from_mod, char *call, char to_mod); -static void runit(); -static void print_status_file(); -static void send_heartbeat(); -static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr); -static void audio_notify(char *notify_msg); -static void rptr_ack(short i); -static void AudioNotifyThread(char *arg); -static void RptrAckThread(char *arg); - -static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr) +std::atomic CQnetLink::keep_running(true); + +CQnetLink::CQnetLink() +{ + memset(tracing, 0, 3 * sizeof(struct tracing_tag)); + memset(dtmf_mycall, 0, 3 * (CALL_SIZE+1)); + memset(old_sid, 0, 6); +} + +CQnetLink::~CQnetLink() +{ +} + +bool CQnetLink::resolve_rmt(char *name, int type, struct sockaddr_in *addr) { struct addrinfo hints; struct addrinfo *res; @@ -245,21 +99,20 @@ static bool resolve_rmt(char *name, int type, struct sockaddr_in *addr) } /* send keepalive to donglers */ -static void send_heartbeat() +void CQnetLink::send_heartbeat() { - inbound *inbound_ptr; bool removed = false; for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); - if (inbound_ptr->countdown >= 0) - inbound_ptr->countdown --; + if (inbound->countdown >= 0) + inbound->countdown --; - if (inbound_ptr->countdown < 0) { + if (inbound->countdown < 0) { removed = true; - printf("call=%s timeout, removing %s, users=%d\n", inbound_ptr->call, pos->first.c_str(), (int)inbound_list.size() - 1); + printf("call=%s timeout, removing %s, users=%d\n", inbound->call, pos->first.c_str(), (int)inbound_list.size() - 1); free(pos->second); pos->second = NULL; @@ -270,7 +123,7 @@ static void send_heartbeat() print_status_file(); } -static void rptr_ack(short i) +void CQnetLink::rptr_ack(short i) { static char mod_and_RADIO_ID[3][22]; @@ -296,14 +149,14 @@ static void rptr_ack(short i) memcpy(mod_and_RADIO_ID[i] + 1, "NOT LINKED", 10); } try { - std::async(std::launch::async, RptrAckThread, mod_and_RADIO_ID[i]); + std::async(std::launch::async, &CQnetLink::RptrAckThread, this, mod_and_RADIO_ID[i]); } catch (const std::exception &e) { printf("Failed to start RptrAckThread(). Exception: %s\n", e.what()); } return; } -static void RptrAckThread(char *arg) +void CQnetLink::RptrAckThread(char *arg) { char from_mod = arg[0]; char RADIO_ID[21]; @@ -435,7 +288,7 @@ static void RptrAckThread(char *arg) } } -static void print_status_file() +void CQnetLink::print_status_file() { FILE *statusfp = fopen(status_file.c_str(), "w"); if (!statusfp) @@ -450,8 +303,8 @@ static void print_status_file() /* print connected donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound *inbound_ptr = (inbound *)pos->second; - fprintf(statusfp, fstr, 'p', inbound_ptr->call, 'p', pos->first.c_str(), tm1.tm_mon+1,tm1.tm_mday,tm1.tm_year % 100, tm1.tm_hour,tm1.tm_min,tm1.tm_sec); + SINBOUND *inbound = (SINBOUND *)pos->second; + fprintf(statusfp, fstr, 'p', inbound->call, 'p', pos->first.c_str(), tm1.tm_mon+1,tm1.tm_mday,tm1.tm_year % 100, tm1.tm_hour,tm1.tm_min,tm1.tm_sec); } /* print linked repeaters-reflectors */ @@ -466,7 +319,7 @@ static void print_status_file() } /* Open text file of repeaters, reflectors */ -static bool load_gwys(const std::string &filename) +bool CQnetLink::load_gwys(const std::string &filename) { char inbuf[1024]; const char *delim = " "; @@ -479,9 +332,6 @@ static bool load_gwys(const std::string &filename) char payload[MAXHOSTNAMELEN + 1 + 5 + 1]; unsigned short j; - gwy_list_type::iterator gwy_pos; - std::pair gwy_insert_pair; - printf("Trying to open file %s\n", filename.c_str()); FILE *fp = fopen(filename.c_str(), "r"); if (fp == NULL) { @@ -552,13 +402,10 @@ static bool load_gwys(const std::string &filename) /* copy the payload(host port) */ sprintf(payload, "%s %s", host, port); - gwy_pos = gwy_list.find(call); + auto gwy_pos = gwy_list.find(call); if (gwy_pos == gwy_list.end()) { - gwy_insert_pair = gwy_list.insert(std::pair(call,payload)); - if (gwy_insert_pair.second) - printf("Added Call=[%s], payload=[%s]\n",call, payload); - else - printf("Failed to add: Call=[%s], payload=[%s]\n",call, payload); + gwy_list[call] = payload; + printf("Added Call=[%s], payload=[%s]\n",call, payload); } else printf("Call [%s] is duplicate\n", call); } @@ -569,7 +416,7 @@ static bool load_gwys(const std::string &filename) } /* compute checksum */ -static void calcPFCS(unsigned char *packet, int len) +void CQnetLink::calcPFCS(unsigned char *packet, int len) { unsigned short crc_tabccitt[256] = { 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, @@ -621,7 +468,7 @@ static void calcPFCS(unsigned char *packet, int len) return; } -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) +bool CQnetLink::get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) @@ -632,7 +479,7 @@ bool get_value(const Config &cfg, const char *path, int &value, int min, int max return true; } -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) +bool CQnetLink::get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) @@ -643,7 +490,7 @@ bool get_value(const Config &cfg, const char *path, double &value, double min, d return true; } -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) +bool CQnetLink::get_value(const Config &cfg, const char *path, bool &value, bool default_value) { if (! cfg.lookupValue(path, value)) value = default_value; @@ -651,7 +498,7 @@ bool get_value(const Config &cfg, const char *path, bool &value, bool default_va return true; } -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +bool CQnetLink::get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) { if (cfg.lookupValue(path, value)) { int l = value.length(); @@ -666,7 +513,7 @@ bool get_value(const Config &cfg, const char *path, std::string &value, int min, } /* process configuration file */ -static bool read_config(char *cfgFile) +bool CQnetLink::read_config(const char *cfgFile) { unsigned short i; Config cfg; @@ -678,11 +525,11 @@ static bool read_config(char *cfgFile) } catch(const FileIOException &fioex) { printf("Can't read %s\n", cfgFile); - return false; + return true; } catch(const ParseException &pex) { printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return false; + return true; } std::string value; @@ -691,7 +538,7 @@ static bool read_config(char *cfgFile) int l = login_call.length(); if (l<3 || l>CALL_SIZE-2) { printf("Call '%s' is invalid length!\n", login_call.c_str()); - return false; + return true; } else { for (i=0; iREPEATER QnetGateway v1.0+"); printf("sending link request from mod %c to link with: [%s] mod %c [%s]\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod, payload); sendto(dcs_g2_sock, link_request, 519, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); @@ -1178,7 +1024,7 @@ static void g2link(char from_mod, char *call, char to_mod) } /* signal catching function */ -static void sigCatch(int signum) +void CQnetLink::sigCatch(int signum) { /* do NOT do any serious work here */ if ((signum == SIGTERM) || (signum == SIGINT)) @@ -1186,7 +1032,7 @@ static void sigCatch(int signum) return; } -static void runit() +void CQnetLink::Process() { socklen_t fromlen; int recvlen; @@ -1214,7 +1060,6 @@ static void runit() char call[CALL_SIZE + 1]; char ip[IP_SIZE + 1]; - inbound *inbound_ptr; bool found = false; char cmd_2_dcs[23]; @@ -1263,8 +1108,7 @@ static void runit() if (dcs_g2_sock > max_nfds) max_nfds = dcs_g2_sock; - printf("xrf=%d, dcs=%d, ref=%d, rptr=%d, MAX+1=%d\n", - xrf_g2_sock, dcs_g2_sock, ref_g2_sock, rptr_sock, max_nfds + 1); + printf("xrf=%d, dcs=%d, ref=%d, rptr=%d, MAX+1=%d\n", xrf_g2_sock, dcs_g2_sock, ref_g2_sock, rptr_sock, max_nfds + 1); if (strlen(link_at_startup) >= 8) { if ((link_at_startup[0] == 'A') || (link_at_startup[0] == 'B') || (link_at_startup[0] == 'C')) { @@ -1814,16 +1658,16 @@ static void runit() /* send data to donglers */ /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { + SINBOUND *inbound = (SINBOUND *)pos->second; + if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { readBuffer[0] = (unsigned char)(58 & 0xFF); readBuffer[1] = (unsigned char)(58 >> 8 & 0x1F); readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); memcpy(readBuffer + 2, readBuffer2, 56); - sendto(ref_g2_sock, readBuffer, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, readBuffer, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } else - inbound_ptr->mod = readBuffer2[25]; + inbound->mod = readBuffer2[25]; } /* send the data to the repeater/reflector that is linked to our RPT1 */ @@ -1974,15 +1818,15 @@ static void runit() /* send data to donglers */ /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { + SINBOUND *inbound = (SINBOUND *)pos->second; + if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { readBuffer[0] = (unsigned char)(29 & 0xFF); readBuffer[1] = (unsigned char)(29 >> 8 & 0x1F); readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); memcpy(readBuffer + 2, readBuffer2, 27); - sendto(ref_g2_sock, readBuffer, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, readBuffer, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -2091,7 +1935,7 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested LH list\n", inbound_ptr->call, ip); /* header is 10 bytes */ @@ -2177,7 +2021,7 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested linked repeaters list\n", inbound_ptr->call, ip); /* header is 8 bytes */ @@ -2268,7 +2112,6 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; // printf("Remote station %s %s requested connected user list\n", inbound_ptr->call, ip); /* header is 8 bytes */ @@ -2286,14 +2129,14 @@ static void runit() for (pos = inbound_list.begin(), i_idx = 0; pos != inbound_list.end(); pos++, i_idx++) { /* each entry has 20 bytes */ readBuffer2[8 + (20 * j_idx)] = ' '; - inbound_ptr = (inbound *)pos->second; + SINBOUND *inbound = (SINBOUND *)pos->second; - readBuffer2[8 + (20 * j_idx)] = inbound_ptr->mod; - strcpy((char *)readBuffer2 + 9 + (20 * j_idx), inbound_ptr->call); + readBuffer2[8 + (20 * j_idx)] = inbound->mod; + strcpy((char *)readBuffer2 + 9 + (20 * j_idx), inbound->call); readBuffer2[17 + (20 * j_idx)] = 0; /* readBuffer2[18 + (20 * j_idx)] = 0; */ - readBuffer2[18 + (20 * j_idx)] = inbound_ptr->client; + readBuffer2[18 + (20 * j_idx)] = inbound->client; readBuffer2[19 + (20 * j_idx)] = 0; readBuffer2[20 + (20 * j_idx)] = 0x0d; readBuffer2[21 + (20 * j_idx)] = 0x4d; @@ -2351,7 +2194,7 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested date\n", inbound_ptr->call, ip); time(<ime); @@ -2381,7 +2224,7 @@ static void runit() (readBuffer2[3] == 0)) { auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested version\n", inbound_ptr->call, ip); readBuffer2[0] = 9; @@ -2420,9 +2263,9 @@ static void runit() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; - if (memcmp(inbound_ptr->call, "1NFO", 4) != 0) - printf("Call %s disconnected\n", inbound_ptr->call); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (memcmp(inbound->call, "1NFO", 4) != 0) + printf("Call %s disconnected\n", inbound->call); free(pos->second); pos->second = NULL; inbound_list.erase(pos); @@ -2566,9 +2409,9 @@ static void runit() /* find out if it is a connected dongle */ auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; + SINBOUND *inbound = (SINBOUND *)pos->second; found = true; - inbound_ptr->countdown = TIMEOUT; + inbound->countdown = TIMEOUT; /*** ip is same, do not update port memcpy((char *)&(inbound_ptr->sin),(char *)&fromDst4, sizeof(struct sockaddr_in)); ***/ @@ -2625,25 +2468,25 @@ static void runit() sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } else { /* add the dongle to the inbound list */ - inbound_ptr = (inbound *)malloc(sizeof(inbound)); - if (inbound_ptr) { - inbound_ptr->countdown = TIMEOUT; - memcpy((char *)&(inbound_ptr->sin),(char *)&fromDst4, sizeof(struct sockaddr_in)); - strcpy(inbound_ptr->call, call); + SINBOUND *inbound = (SINBOUND *)malloc(sizeof(SINBOUND)); + if (inbound) { + inbound->countdown = TIMEOUT; + memcpy((char *)&(inbound->sin),(char *)&fromDst4, sizeof(struct sockaddr_in)); + strcpy(inbound->call, call); - inbound_ptr->mod = ' '; + inbound->mod = ' '; if (memcmp(readBuffer2 + 20, "AP", 2) == 0) - inbound_ptr->client = 'A'; /* dvap */ + inbound->client = 'A'; /* dvap */ else if (memcmp(readBuffer2 + 20, "DV019999", 8) == 0) - inbound_ptr->client = 'H'; /* spot */ + inbound->client = 'H'; /* spot */ else - inbound_ptr->client = 'D'; /* dongle */ + inbound->client = 'D'; /* dongle */ - auto insert_pair = inbound_list.insert(std::pair(ip, inbound_ptr)); + auto insert_pair = inbound_list.insert(std::pair(ip, inbound)); if (insert_pair.second) { - if (memcmp(inbound_ptr->call, "1NFO", 4) != 0) - printf("new CALL=%s, DONGLE-p, ip=%s, users=%d\n", inbound_ptr->call,ip, (int)inbound_list.size()); + if (memcmp(inbound->call, "1NFO", 4) != 0) + printf("new CALL=%s, DONGLE-p, ip=%s, users=%d\n", inbound->call,ip, (int)inbound_list.size()); readBuffer2[0] = 8; readBuffer2[4] = 79; @@ -2656,9 +2499,9 @@ static void runit() print_status_file(); } else { - printf("failed to add CALL=%s,ip=%s\n",inbound_ptr->call,ip); - free(inbound_ptr); - inbound_ptr = NULL; + printf("failed to add CALL=%s,ip=%s\n",inbound->call,ip); + free(inbound); + inbound = NULL; readBuffer2[0] = 8; readBuffer2[4] = 70; @@ -2702,8 +2545,8 @@ static void runit() if (!found) { auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; - inbound_ptr->countdown = TIMEOUT; + SINBOUND *inbound = (SINBOUND *)pos->second; + inbound->countdown = TIMEOUT; found = true; } } @@ -2750,8 +2593,8 @@ static void runit() if (i == 3) { pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - inbound_ptr = (inbound *)pos->second; - memcpy(source_stn, inbound_ptr->call, 8); + SINBOUND *inbound = (SINBOUND *)pos->second; + memcpy(source_stn, inbound->call, 8); } } @@ -2813,11 +2656,11 @@ static void runit() /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { + sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } else - inbound_ptr->mod = readBuffer2[27]; + inbound->mod = readBuffer2[27]; } if ((to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && @@ -2861,8 +2704,7 @@ static void runit() for (i = 0; i < 3; i++) { if (memcmp(old_sid[i].sid, readBuffer2 + 14, 2) == 0) { if (qso_details) - printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", - readBuffer2[14],readBuffer2[15],recvlen2,inet_ntoa(fromDst4.sin_addr)); + printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", readBuffer2[14], readBuffer2[15], recvlen2, inet_ntoa(fromDst4.sin_addr)); memset(old_sid[i].sid, 0x00, 2); @@ -2876,9 +2718,9 @@ static void runit() /* send the data to the donglers */ for (pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - if (fromDst4.sin_addr.s_addr != inbound_ptr->sin.sin_addr.s_addr) { - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { + sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -3043,9 +2885,9 @@ static void runit() /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; + SINBOUND *inbound = (SINBOUND *)pos->second; for (j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -3081,16 +2923,15 @@ static void runit() /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } if ((dcs_buf[45] & 0x40) != 0) { memset(old_sid[i].sid, 0x00, 2); if (qso_details) - printf("END from dcs: streamID=%d,%d, %d bytes from IP=%s\n", - dcs_buf[43],dcs_buf[44], recvlen2,inet_ntoa(fromDst4.sin_addr)); + printf("END from dcs: streamID=%d,%d, %d bytes from IP=%s\n", dcs_buf[43],dcs_buf[44], recvlen2, inet_ntoa(fromDst4.sin_addr)); to_remote_g2[i].in_streamid[0] = 0x00; to_remote_g2[i].in_streamid[1] = 0x00; @@ -3446,9 +3287,9 @@ static void runit() memcpy(&readBuffer2[36], "CQCQCQ ", 8); for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; + SINBOUND *inbound = (SINBOUND *)pos->second; for (j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -3582,8 +3423,8 @@ static void runit() memcpy(readBuffer2 + 17, readBuffer + 20, 12); for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -3774,7 +3615,7 @@ static void runit() } } -void audio_notify(char *msg) +void CQnetLink::audio_notify(char *msg) { if (!announce) return; @@ -3791,14 +3632,14 @@ void audio_notify(char *msg) strcpy(notify_msg[i], msg); try { - std::async(std::launch::async, AudioNotifyThread, notify_msg[i]); + std::async(std::launch::async, &CQnetLink::AudioNotifyThread, this, notify_msg[i]); } catch (const std::exception &e) { printf ("Failed to start AudioNotifyThread(). Exception: %s\n", e.what()); } return; } -static void AudioNotifyThread(char *arg) +void CQnetLink::AudioNotifyThread(char *arg) { char notify_msg[64]; @@ -4001,27 +3842,18 @@ static void AudioNotifyThread(char *arg) return; } -int main(int argc, char **argv) +bool CQnetLink::Init(const char *cfgfile) { - short i, j; struct sigaction act; - char unlink_request[CALL_SIZE + 3]; - inbound *inbound_ptr; - - char cmd_2_dcs[19]; tzset(); setvbuf(stdout, (char *)NULL, _IOLBF, 0); - if (argc != 2) { - printf("Usage: ./g2_link g2_link.cfg\n"); - return 1; - } int rc = regcomp(&preg, "^(([1-9][A-Z])|([A-Z][0-9])|([A-Z][A-Z][0-9]))[0-9A-Z]*[A-Z][ ]*[ A-RT-Z]$", REG_EXTENDED | REG_NOSUB); if (rc != 0) { printf("The IRC regular expression is NOT valid\n"); - return 1; + return true; } act.sa_handler = sigCatch; @@ -4029,14 +3861,14 @@ int main(int argc, char **argv) act.sa_flags = SA_RESTART; if (sigaction(SIGTERM, &act, 0) != 0) { printf("sigaction-TERM failed, error=%d\n", errno); - return 1; + return true; } if (sigaction(SIGINT, &act, 0) != 0) { printf("sigaction-INT failed, error=%d\n", errno); - return 1; + return true; } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); to_remote_g2[i].to_mod = ' '; @@ -4059,30 +3891,29 @@ int main(int argc, char **argv) brd_from_rptr.to_rptr_streamid[1][0] = brd_from_rptr.to_rptr_streamid[1][1] = 0x00; brd_from_rptr_idx = 0; - do { - /* process configuration file */ - if (!read_config(argv[1])) { - printf("Failed to process config file %s\n", argv[1]); - break; - } - print_status_file(); - - /* Open DB */ - if (!load_gwys(gwys)) - break; - - /* create our server */ - if (!srv_open()) { - printf("srv_open() failed\n"); - break; - } + /* process configuration file */ + if (read_config(cfgfile)) { + printf("Failed to process config file %s\n", cfgfile); + return true; + } + print_status_file(); - printf("g2_link %s initialized...entering processing loop\n", VERSION); - runit(); - printf("Leaving processing loop...\n"); + /* Open DB */ + if (!load_gwys(gwys)) + return true; - } while (false); + /* create our server */ + if (!srv_open()) { + printf("srv_open() failed\n"); + return true; + } + return false; +} +void CQnetLink::Shutdown() +{ + char unlink_request[CALL_SIZE + 3]; + char cmd_2_dcs[19]; /* Clear connections */ queryCommand[0] = 5; @@ -4090,7 +3921,7 @@ int main(int argc, char **argv) queryCommand[2] = 24; queryCommand[3] = 0; queryCommand[4] = 0; - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if (to_remote_g2[i].to_call[0] != '\0') { if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); @@ -4099,7 +3930,7 @@ int main(int argc, char **argv) unlink_request[8] = to_remote_g2[i].from_mod; unlink_request[9] = ' '; unlink_request[10] = '\0'; - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } else { strcpy(cmd_2_dcs, owner.c_str()); @@ -4108,7 +3939,7 @@ int main(int argc, char **argv) cmd_2_dcs[10] = '\0'; memcpy(cmd_2_dcs + 11, to_remote_g2[i].to_call, 8); - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(dcs_g2_sock, cmd_2_dcs, 19, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } } @@ -4126,14 +3957,28 @@ int main(int argc, char **argv) /* tell inbound dongles we are down */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - inbound_ptr = (inbound *)pos->second; - sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(inbound_ptr->sin), sizeof(struct sockaddr_in)); + SINBOUND *inbound = (SINBOUND *)pos->second; + sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } inbound_list.clear(); print_status_file(); srv_close(); - printf("g2_link exiting\n"); - return 0; + return; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + printf("Usage: ./g2_link g2_link.cfg\n"); + return 1; + } + CQnetLink qnlink; + if (qnlink.Init(argv[1])) + return 1; + printf("g2_link %s initialized...entering processing loop\n", VERSION); + qnlink.Process(); + printf("g2_link exiting\n"); + qnlink.Shutdown(); } diff --git a/QnetLink.h b/QnetLink.h new file mode 100644 index 0000000..40b9454 --- /dev/null +++ b/QnetLink.h @@ -0,0 +1,161 @@ +#pragma once + +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include "versions.h" +using namespace libconfig; + +/*** version number must be x.xx ***/ +#define VERSION LINK_VERSION +#define CALL_SIZE 8 +#define IP_SIZE 15 +#define QUERY_SIZE 56 +#define MAXHOSTNAMELEN 64 +#define TIMEOUT 50 +#define LH_MAX_SIZE 39 + +// This is the data payload in the map: inbound_list +// This is for inbound dongles +typedef struct inbound_tag { + char call[CALL_SIZE + 1]; // the callsign of the remote + struct sockaddr_in sin; // IP and port of remote + short countdown; // if countdown expires, the connection is terminated + char mod; // A B C This user talked on this module + char client; // dvap, dvdongle +} SINBOUND; + +class CQnetLink { +public: + // functions + CQnetLink(); + ~CQnetLink(); + bool Init(const char *cfgfile); + void Process(); + void Shutdown(); +private: + // functions + bool load_gwys(const std::string &filename); + void calcPFCS(unsigned char *packet, int len); + bool read_config(const char *); + bool srv_open(); + void srv_close(); + static void sigCatch(int signum); + void g2link(char from_mod, char *call, char to_mod); + void print_status_file(); + void send_heartbeat(); + bool resolve_rmt(char *name, int type, struct sockaddr_in *addr); + void audio_notify(char *notify_msg); + void rptr_ack(short i); + void AudioNotifyThread(char *arg); + void RptrAckThread(char *arg); + bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value); + bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value); + bool get_value(const Config &cfg, const char *path, bool &value, bool default_value); + bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value); + + /* configuration data */ + std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, announce_dir; + bool only_admin_login, only_link_unlink, qso_details, bool_rptr_ack, announce; + int rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port, delay_between, delay_before; + char link_at_startup[CALL_SIZE+1]; + unsigned int max_dongles, saved_max_dongles; + long rf_inactivity_timer[3]; + const unsigned char REF_ACK[3] = { 3, 96, 0 }; + + // the Key in this inbound_list map is the unique IP address of the remote + std::map inbound_list; + + std::set admin, link_unlink_user, link_blacklist; + + std::map dt_lh_list; + + struct to_remote_g2_tag { + char to_call[CALL_SIZE + 1]; + struct sockaddr_in toDst4; + char from_mod; + char to_mod; + short countdown; + bool is_connected; + unsigned char in_streamid[2]; // incoming from remote systems + unsigned char out_streamid[2]; // outgoing to remote systems + } to_remote_g2[3]; + + // broadcast for data arriving from xrf to local rptr + struct brd_from_xrf_tag { + unsigned char xrf_streamid[2]; // streamid from xrf + unsigned char rptr_streamid[2][2]; // generated streamid to rptr(s) + } brd_from_xrf; + unsigned char from_xrf_torptr_brd[56]; + short brd_from_xrf_idx; + + // broadcast for data arriving from local rptr to xrf + struct brd_from_rptr_tag { + unsigned char from_rptr_streamid[2]; + unsigned char to_rptr_streamid[2][2]; + } brd_from_rptr; + unsigned char fromrptr_torptr_brd[56]; + short brd_from_rptr_idx; + + struct tracing_tag { + unsigned char streamid[2]; + time_t last_time; // last time RF user talked + } tracing[3]; + + // input from remote + int xrf_g2_sock, ref_g2_sock, dcs_g2_sock, rptr_sock; + struct sockaddr_in fromDst4; + + // After we receive it from remote g2, + // we must feed it to our local repeater. + struct sockaddr_in toLocalg2; + + // input from our own local repeater + struct sockaddr_in fromRptr; + + fd_set fdset; + struct timeval tv; + + static std::atomic keep_running; + + // Used to validate incoming donglers + regex_t preg; + + // the map of remotes + // key is the callsign, data is the host + std::map gwy_list; + + unsigned char queryCommand[QUERY_SIZE]; + + // START: TEXT crap + char dtmf_mycall[3][CALL_SIZE + 1]; + bool new_group[3]; + int header_type; + bool GPS_seen[3]; + unsigned char tmp_txt[3]; + char *p_tmp2; + // END: TEXT crap + + // this is used for the "dashboard and qso_details" to avoid processing multiple headers + struct old_sid_tag { + unsigned char sid[2]; + } old_sid[3]; +}; From 3a83d2ec0c5127479bcace9dad82e17fa8def74d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 21 May 2018 13:29:28 -0700 Subject: [PATCH 006/553] further condensing of QnetLink --- QnetLink.cpp | 130 ++++++++++++++++++++++++--------------------------- 1 file changed, 61 insertions(+), 69 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 5b9ebdb..eb36b7d 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -1034,13 +1034,7 @@ void CQnetLink::sigCatch(int signum) void CQnetLink::Process() { - socklen_t fromlen; - int recvlen; - int recvlen2; - short i,j,k; - char temp_repeater[CALL_SIZE + 1]; time_t tnow = 0, hb = 0; - int rc = 0; char *p = NULL; @@ -1112,6 +1106,7 @@ void CQnetLink::Process() if (strlen(link_at_startup) >= 8) { if ((link_at_startup[0] == 'A') || (link_at_startup[0] == 'B') || (link_at_startup[0] == 'C')) { + char temp_repeater[CALL_SIZE + 1]; memset(temp_repeater, ' ', CALL_SIZE); memcpy(temp_repeater, link_at_startup + 1, 6); temp_repeater[CALL_SIZE] = '\0'; @@ -1178,7 +1173,7 @@ void CQnetLink::Process() (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[2].toDst4), sizeof(to_remote_g2[2].toDst4)); - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { /* check for timeouts from remote */ if (to_remote_g2[i].to_call[0] != '\0') { if (to_remote_g2[i].countdown >= 0) @@ -1223,7 +1218,7 @@ void CQnetLink::Process() sendto(ref_g2_sock, queryCommand, 5, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); /* zero out any other entries here that match that system */ - for (j = 0; j < 3; j++) { + for (int j=0; j<3; j++) { if (j != i) { if ((to_remote_g2[j].toDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[j].toDst4.sin_port == htons(rmt_ref_port))) { @@ -1243,7 +1238,7 @@ void CQnetLink::Process() unlink_request[9] = ' '; unlink_request[10] = '\0'; - for (j = 0; j < 5; j++) + for (int j=0; j<5; j++) sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { strcpy(cmd_2_dcs, owner.c_str()); @@ -1252,7 +1247,7 @@ void CQnetLink::Process() cmd_2_dcs[10] = '\0'; memcpy(cmd_2_dcs + 11, to_remote_g2[i].to_call, 8); - for (j=0; j<2; j++) + for (int j=0; j<2; j++) sendto(dcs_g2_sock, cmd_2_dcs, 19 ,0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } @@ -1285,8 +1280,8 @@ void CQnetLink::Process() (void)select(max_nfds + 1,&fdset,0,0,&tv); if (FD_ISSET(xrf_g2_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen2 = recvfrom(xrf_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen2 = recvfrom(xrf_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); ip[IP_SIZE] = '\0'; @@ -1299,7 +1294,7 @@ void CQnetLink::Process() if (recvlen2 == (CALL_SIZE + 1)) { found = false; /* Find out if it is a keepalive from a repeater */ - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { found = true; @@ -1328,7 +1323,7 @@ void CQnetLink::Process() /* A packet of length (CALL_SIZE + 6) is either an ACK or a NAK from repeater-reflector */ /* Because we sent a request before asking to link */ - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { if ((memcmp((char *)readBuffer2 + 10, "ACK", 3) == 0) && @@ -1381,7 +1376,7 @@ void CQnetLink::Process() */ /* Check our linked repeaters/reflectors */ - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { if (to_remote_g2[i].to_mod == readBuffer2[8]) { @@ -1463,7 +1458,7 @@ void CQnetLink::Process() /* link request from remote repeater that is not yet linked to our system */ /* find out which of our local modules the remote repeater is interested in */ - i = -1; + int i = -1; if (readBuffer2[9] == 'A') i = 0; else if (readBuffer2[9] == 'B') @@ -1478,7 +1473,7 @@ void CQnetLink::Process() printf("Incoming link from %s,%s but not found in gwys.txt\n",call,ip); i = -1; } else { - rc = regexec(&preg, call, 0, NULL, 0); + int rc = regexec(&preg, call, 0, NULL, 0); if (rc != 0) { printf("Invalid repeater %s,%s requesting to link\n", call, ip); i = -1; @@ -1547,7 +1542,7 @@ void CQnetLink::Process() /* reset countdown and protect against hackers */ found = false; - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { to_remote_g2[i].countdown = TIMEOUT; @@ -1574,7 +1569,7 @@ void CQnetLink::Process() /* A reflector will send to us its own RPT1 */ /* A repeater will send to us our RPT1 */ - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { /* it is a reflector, reflector's rpt1 */ @@ -1607,7 +1602,7 @@ void CQnetLink::Process() /* At this point, all data have our RPT1 and RPT2 */ /* send the data to the repeater/reflector that is linked to our RPT1 */ - i = -1; + int i = -1; if (readBuffer2[25] == 'A') i = 0; else if (readBuffer2[25] == 'B') @@ -1674,14 +1669,14 @@ void CQnetLink::Process() /* Is there another local module linked to the remote same xrf mod ? */ /* If Yes, then broadcast */ - k = i + 1; + int k = i + 1; if (k < 3) { brd_from_xrf_idx = 0; streamid_raw = (readBuffer2[12] * 256U) + readBuffer2[13]; /* We can only enter this loop up to 2 times max */ - for (j = k; j < 3; j++) { + for (int j=k; j<3; j++) { /* it is a remote gateway, not a dongle user */ if ((fromDst4.sin_addr.s_addr == to_remote_g2[j].toDst4.sin_addr.s_addr) && /* it is xrf */ @@ -1799,7 +1794,7 @@ void CQnetLink::Process() } } else if (found) { if ((readBuffer2[14] & 0x40) != 0) { - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if (memcmp(old_sid[i].sid, readBuffer2 + 12, 2) == 0) { if (qso_details) printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", @@ -1856,7 +1851,7 @@ void CQnetLink::Process() } } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((to_remote_g2[i].is_connected) && (to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && (memcmp(to_remote_g2[i].in_streamid, readBuffer2 + 12, 2) == 0)) { @@ -1915,8 +1910,8 @@ void CQnetLink::Process() } if (FD_ISSET(ref_g2_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen2 = recvfrom(ref_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen2 = recvfrom(ref_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); ip[IP_SIZE] = '\0'; @@ -2036,7 +2031,7 @@ void CQnetLink::Process() readBuffer2[6] = tmp[0]; readBuffer2[7] = tmp[1]; - for (i = 0, i_idx = 0; i < 3; i++, i_idx++) { + for (int i=0, i_idx=0; i<3; i++, i_idx++) { /* each entry has 20 bytes */ if (to_remote_g2[i].to_mod != ' ') { if (i == 0) @@ -2245,7 +2240,7 @@ void CQnetLink::Process() /* reply with the same DISCONNECT */ sendto(ref_g2_sock, readBuffer2, 5, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { printf("Call %s disconnected\n", to_remote_g2[i].to_call); @@ -2273,9 +2268,9 @@ void CQnetLink::Process() print_status_file(); } - for (i = 0; i < 3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + for (int i=0; i<3; i++) { + if ((fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr) && + (to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port))) { found = true; if ((recvlen2 == 5) && (readBuffer2[0] == 5) && @@ -2290,7 +2285,7 @@ void CQnetLink::Process() queryCommand[3] = 0; memcpy(queryCommand + 4, login_call.c_str(), CALL_SIZE); - for (j = 11; j > 3; j--) { + for (int j=11; j>3; j--) { if (queryCommand[j] == ' ') queryCommand[j] = '\0'; else @@ -2308,7 +2303,7 @@ void CQnetLink::Process() } } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; @@ -2382,7 +2377,7 @@ void CQnetLink::Process() } } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; @@ -2391,13 +2386,12 @@ void CQnetLink::Process() (readBuffer2[1] == 192) && (readBuffer2[2] == 3) && (readBuffer2[3] == 0)) { - j = i; to_remote_g2[i].countdown = TIMEOUT; } } } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; @@ -2441,7 +2435,7 @@ void CQnetLink::Process() /* verify callsign */ memcpy(call, readBuffer2 + 4, CALL_SIZE); call[CALL_SIZE] = '\0'; - for (i = 7; i > 0; i--) { + for (int i=7; i>0; i--) { if (call[i] == '\0') call[i] = ' '; else @@ -2535,7 +2529,7 @@ void CQnetLink::Process() (readBuffer2[10] == 0x20)) { /* Is it one of the donglers or repeaters-reflectors */ found = false; - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { to_remote_g2[i].countdown = TIMEOUT; @@ -2570,7 +2564,8 @@ void CQnetLink::Process() /* A dongleR will send to us our RPT1 */ /* It is from a repeater-reflector, correct rpt1, rpt2 and re-compute pfcs */ - for (i = 0; i < 3; i++) { + int i; + for (i=0; i<3; i++) { if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) && ( @@ -2701,7 +2696,7 @@ void CQnetLink::Process() } } else if (found) { if ((readBuffer2[16] & 0x40) != 0) { - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if (memcmp(old_sid[i].sid, readBuffer2 + 14, 2) == 0) { if (qso_details) printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", readBuffer2[14], readBuffer2[15], recvlen2, inet_ntoa(fromDst4.sin_addr)); @@ -2724,7 +2719,7 @@ void CQnetLink::Process() } } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if ((to_remote_g2[i].is_connected) && (to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && (memcmp(to_remote_g2[i].in_streamid, readBuffer2 + 14, 2) == 0)) { @@ -2777,8 +2772,8 @@ void CQnetLink::Process() } if (FD_ISSET(dcs_g2_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen2 = recvfrom(dcs_g2_sock, (char *)dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen2 = recvfrom(dcs_g2_sock, (char *)dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); ip[IP_SIZE] = '\0'; @@ -2791,7 +2786,8 @@ void CQnetLink::Process() source_stn[8] = '\0'; /* find out our local module */ - for (i = 0; i < 3; i++) { + int i; + for (i=0; i<3; i++) { if ((to_remote_g2[i].is_connected) && (fromDst4.sin_addr.s_addr = to_remote_g2[i].toDst4.sin_addr.s_addr) && (memcmp(dcs_buf + 7, to_remote_g2[i].to_call, 7) == 0) && @@ -2880,13 +2876,13 @@ void CQnetLink::Process() calcPFCS(readBuffer2 + 2, 56); /* send the header to the local gateway/repeater */ - for (j = 0; j < 5; j++) + for (int j=0; j<5; j++) sendto(rptr_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -2947,7 +2943,7 @@ void CQnetLink::Process() ; /* is this a keepalive 22 bytes */ else if (recvlen2 == 22) { - i = -1; + int i = -1; if (dcs_buf[17] == 'A') i = 0; else if (dcs_buf[17] == 'B') @@ -2984,7 +2980,7 @@ void CQnetLink::Process() } } } else if (recvlen2 == 14) { /* is this a reply to our link/unlink request: 14 bytes */ - i = -1; + int i = -1; if (dcs_buf[8] == 'A') i = 0; else if (dcs_buf[8] == 'B') @@ -3045,8 +3041,8 @@ void CQnetLink::Process() } if (FD_ISSET(rptr_sock, &fdset)) { - fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(rptr_sock, (char *)readBuffer, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen = recvfrom(rptr_sock, (char *)readBuffer, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && (readBuffer[6] == 0x73) && @@ -3069,7 +3065,7 @@ void CQnetLink::Process() memcpy(call, readBuffer + 44, 8); call[8] = '\0'; - i = -1; + int i = -1; if (readBuffer[35] == 'A') i = 0; else if (readBuffer[35] == 'B') @@ -3134,6 +3130,7 @@ void CQnetLink::Process() ) { printf("link request denied, unauthorized user [%s]\n", call); } else { + char temp_repeater[CALL_SIZE + 1]; memset(temp_repeater, ' ', CALL_SIZE); memcpy(temp_repeater, readBuffer + 36, CALL_SIZE - 2); temp_repeater[CALL_SIZE] = '\0'; @@ -3164,7 +3161,8 @@ void CQnetLink::Process() if (to_remote_g2[i].to_call[0] != '\0') { if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { /* Check to see if any other local bands are linked to that same IP */ - for (j = 0; j < 3; j++) { + int j; + for (j=0; j<3; j++) { if (j != i) { if ((to_remote_g2[j].toDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[j].toDst4.sin_port == htons(rmt_ref_port))) { @@ -3191,7 +3189,7 @@ void CQnetLink::Process() unlink_request[9] = ' '; unlink_request[10] = '\0'; - for (j = 0; j < 5; j++) + for (int j=0; j<5; j++) sendto(xrf_g2_sock, unlink_request, CALL_SIZE+3, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } else { strcpy(cmd_2_dcs, owner.c_str()); @@ -3200,12 +3198,11 @@ void CQnetLink::Process() cmd_2_dcs[10] = '\0'; memcpy(cmd_2_dcs + 11, to_remote_g2[i].to_call, 8); - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(dcs_g2_sock, cmd_2_dcs, 19, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - printf("Unlinked from [%s] mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Unlinked from [%s] mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); sprintf(notify_msg, "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); audio_notify(notify_msg); @@ -3231,10 +3228,7 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } else { sprintf(notify_msg, "%c_id.dat_%s_NOT_LINKED", readBuffer[35], owner.c_str()); @@ -3243,9 +3237,7 @@ void CQnetLink::Process() } else if ((readBuffer[43] == 'X') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can execute scripts if (readBuffer[42] != ' ') { memset(system_cmd, '\0', sizeof(system_cmd)); - snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", - announce_dir.c_str(), - readBuffer[42], call, readBuffer[35]); + snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), readBuffer[42], call, readBuffer[35]); printf("Executing %s\n", system_cmd); system(system_cmd); } @@ -3288,7 +3280,7 @@ void CQnetLink::Process() for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } @@ -3305,7 +3297,7 @@ void CQnetLink::Process() brd_from_rptr_idx = 0; streamid_raw = (readBuffer[14] * 256U) + readBuffer[15]; - for (j = 0; j < 3; j++) { + for (int j=0; j<3; j++) { if ((j != i) && (to_remote_g2[j].is_connected) && (memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) == 0) && @@ -3387,10 +3379,10 @@ void CQnetLink::Process() /* inform XRF about the source */ readBuffer2[13] = to_remote_g2[i].from_mod; - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(xrf_g2_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else { - for (j=0; j<5; j++) + for (int j=0; j<5; j++) sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { @@ -3428,7 +3420,7 @@ void CQnetLink::Process() } } - for (i=0; i<3; i++) { + for (int i=0; i<3; i++) { if ((to_remote_g2[i].is_connected) && (memcmp(to_remote_g2[i].out_streamid, readBuffer + 14, 2) == 0)) { /* check for broadcast */ @@ -3536,7 +3528,7 @@ void CQnetLink::Process() } } - for (i = 0; i < 3; i++) { + for (int i=0; i<3; i++) { if (memcmp(tracing[i].streamid, readBuffer + 14, 2) == 0) { /* update the last time RF user talked */ tracing[i].last_time = time(NULL); From 2034f7d12044db985c8acfc254b24b36a0e4f770 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 22 May 2018 15:31:07 -0700 Subject: [PATCH 007/553] begin to add QnetTypeDefs --- QnetLink.cpp | 127 ++++++++++++++++++++++++------------------------- QnetLink.h | 2 +- QnetTypeDefs.h | 4 +- 3 files changed, 65 insertions(+), 68 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index eb36b7d..2ca5135 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -51,8 +51,10 @@ #include #include #include + #include "versions.h" #include "QnetLink.h" +#include "QnetTypeDefs.h" using namespace libconfig; @@ -161,7 +163,6 @@ void CQnetLink::RptrAckThread(char *arg) char from_mod = arg[0]; char RADIO_ID[21]; memcpy(RADIO_ID, arg + 1, 21); - unsigned char buf[56]; unsigned int aseed; time_t tnow = 0; unsigned char silence[12] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8, 0x16, 0x29, 0xf5 }; @@ -188,101 +189,98 @@ void CQnetLink::RptrAckThread(char *arg) printf("sending ACK+text, mod:[%c], RADIO_ID=[%s]\n", from_mod, RADIO_ID); - memcpy(buf,"DSVT", 4); - buf[4] = 0x10; - buf[5] = 0x00; - buf[6] = 0x00; - buf[7] = 0x00; + SDSVT dsvt; + + memcpy(dsvt.title, "DSVT", 4); + dsvt.config = 0x10; + dsvt.flaga[0] = dsvt.flaga[1] = dsvt.flaga[2] = 0x0; - buf[8] = 0x20; - buf[9] = 0x00; - buf[10] = 0x01; - buf[11] = 0x00; + dsvt.id = 0x20; + dsvt.flagb[0] =dsvt.flagb[2] = 0x0; + dsvt.flagb[1] = 0x1; - buf[12] = streamid_raw / 256U; - buf[13] = streamid_raw % 256U; - buf[14] = 0x80; - buf[15] = 0x01; /* we do not want to set this to 0x01 */ - buf[16] = 0x00; - buf[17] = 0x00; + dsvt.streamid = htons(streamid_raw); + dsvt.counter = 0x80; + dsvt.hdr.flag[0] = 0x1; + dsvt.hdr.flag[1] = dsvt.hdr.flag[2] = 0x0; - memcpy(buf + 18, owner.c_str(), CALL_SIZE); - buf[25] = from_mod; + memcpy(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt1[7] = from_mod; - memcpy(buf + 26, owner.c_str(), CALL_SIZE); - buf[33] = 'G'; + memcpy(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; - memcpy(buf + 34, "CQCQCQ ", CALL_SIZE); + memcpy(dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); - memcpy(buf + 42, owner.c_str(), CALL_SIZE); - buf[49] = from_mod; + memcpy(dsvt.hdr.mycall, owner.c_str(), CALL_SIZE); + dsvt.hdr.mycall[7] = from_mod; - memcpy(buf + 50, "RPTR", 4); - calcPFCS(buf,56); - sendto(rptr_sock, buf, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); + memcpy(dsvt.hdr.sfx, "RPTR", 4); + calcPFCS(dsvt.title,56); + sendto(rptr_sock, dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); - buf[4] = 0x20; - memcpy(buf + 15, silence, 9); + dsvt.config = 0x20; + memcpy(dsvt.vasd.voice, silence, 9); /* start sending silence + announcement text */ for (int i=0; i<10; i++) { - buf[14] = (unsigned char)i; + dsvt.counter = (unsigned char)i; switch (i) { case 0: - buf[24] = 0x55; - buf[25] = 0x2d; - buf[26] = 0x16; + dsvt.vasd.text[0] = 0x55; + dsvt.vasd.text[1] = 0x2d; + dsvt.vasd.text[2] = 0x16; break; case 1: - buf[24] = '@' ^ 0x70; - buf[25] = RADIO_ID[0] ^ 0x4f; - buf[26] = RADIO_ID[1] ^ 0x93; + dsvt.vasd.text[0] = '@' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[0] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[1] ^ 0x93; break; case 2: - buf[24] = RADIO_ID[2] ^ 0x70; - buf[25] = RADIO_ID[3] ^ 0x4f; - buf[26] = RADIO_ID[4] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[2] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[3] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[4] ^ 0x93; break; case 3: - buf[24] = 'A' ^ 0x70; - buf[25] = RADIO_ID[5] ^ 0x4f; - buf[26] = RADIO_ID[6] ^ 0x93; + dsvt.vasd.text[0] = 'A' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[5] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[6] ^ 0x93; break; case 4: - buf[24] = RADIO_ID[7] ^ 0x70; - buf[25] = RADIO_ID[8] ^ 0x4f; - buf[26] = RADIO_ID[9] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[7] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[8] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[9] ^ 0x93; break; case 5: - buf[24] = 'B' ^ 0x70; - buf[25] = RADIO_ID[10] ^ 0x4f; - buf[26] = RADIO_ID[11] ^ 0x93; + dsvt.vasd.text[0] = 'B' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[10] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[11] ^ 0x93; break; case 6: - buf[24] = RADIO_ID[12] ^ 0x70; - buf[25] = RADIO_ID[13] ^ 0x4f; - buf[26] = RADIO_ID[14] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[12] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[13] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[14] ^ 0x93; break; case 7: - buf[24] = 'C' ^ 0x70; - buf[25] = RADIO_ID[15] ^ 0x4f; - buf[26] = RADIO_ID[16] ^ 0x93; + dsvt.vasd.text[0] = 'C' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[15] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[16] ^ 0x93; break; case 8: - buf[24] = RADIO_ID[17] ^ 0x70; - buf[25] = RADIO_ID[18] ^ 0x4f; - buf[26] = RADIO_ID[19] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[17] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[18] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[19] ^ 0x93; break; case 9: - buf[14] |= 0x40; - buf[24] = 0x16; - buf[25] = 0x29; - buf[26] = 0xf5; + dsvt.counter |= 0x40; + dsvt.vasd.text[0] = 0x16; + dsvt.vasd.text[1] = 0x29; + dsvt.vasd.text[2] = 0xf5; break; } - sendto(rptr_sock, buf, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); + sendto(rptr_sock, dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); if (i < 9) std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); } @@ -1283,9 +1281,9 @@ void CQnetLink::Process() socklen_t fromlen = sizeof(struct sockaddr_in); int recvlen2 = recvfrom(xrf_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); - strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); + strncpy(ip, inet_ntoa(fromDst4.sin_addr), IP_SIZE); ip[IP_SIZE] = '\0'; - strncpy(call, (char *)readBuffer2,CALL_SIZE); + strncpy(call, (char *)readBuffer2, CALL_SIZE); call[CALL_SIZE] = '\0'; /* a packet of length (CALL_SIZE + 1) is a keepalive from a repeater/reflector */ @@ -3421,8 +3419,7 @@ void CQnetLink::Process() } for (int i=0; i<3; i++) { - if ((to_remote_g2[i].is_connected) && - (memcmp(to_remote_g2[i].out_streamid, readBuffer + 14, 2) == 0)) { + if ((to_remote_g2[i].is_connected) && (memcmp(to_remote_g2[i].out_streamid, readBuffer + 14, 2) == 0)) { /* check for broadcast */ if (memcmp(brd_from_rptr.from_rptr_streamid, readBuffer + 14, 2) == 0) { memcpy(fromrptr_torptr_brd, "DSVT", 4); diff --git a/QnetLink.h b/QnetLink.h index 40b9454..85d98f8 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -90,7 +90,7 @@ private: struct to_remote_g2_tag { char to_call[CALL_SIZE + 1]; - struct sockaddr_in toDst4; + struct sockaddr_in toDst4; // sin_port is in network byte order char from_mod; char to_mod; short countdown; diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index 8c85972..6ff7668 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -19,7 +19,7 @@ // for communicating with the g2 gateway on the internal port #pragma pack(push, 1) // we need to be sure these structures don't have any dead space -typedef struct pkt_tag { +typedef struct dstr_tag { unsigned char pkt_id[4]; // 0 "DSTR" unsigned short counter; // 4 unsigned char flag[3]; // 6 { 0x73, 0x12, 0x00 } @@ -93,7 +93,7 @@ typedef struct dsvt_tag { // for mmdvm #pragma pack(push, 1) -typedef struct mmdvm_tag { // offset size +typedef struct dsrp_tag { // offset size unsigned char title[4]; // "DSRP" 0 unsigned char tag; // Poll : 0xA 4 // Header : busy ? 0x22 : 0x20 From f688066897e6dedbd0916d3721cf13aeb0b952c5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 22 May 2018 17:34:10 -0700 Subject: [PATCH 008/553] AudioNotifyThread uses SDSVT --- QnetLink.cpp | 99 ++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 2ca5135..0c4f94f 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3636,7 +3636,6 @@ void CQnetLink::AudioNotifyThread(char *arg) unsigned short rlen = 0; size_t nread = 0; - unsigned char dstar_buf[56]; bool useTEXT = false; short int TEXT_idx = 0; char RADIO_ID[21]; @@ -3707,6 +3706,7 @@ void CQnetLink::AudioNotifyThread(char *arg) } /* stupid DVTOOL + 4 byte num_of_records */ + unsigned char dstar_buf[10]; nread = fread(dstar_buf, 10, 1, fp); if (nread != 1) { printf("Cant read first 10 bytes from %s\n", temp_file); @@ -3737,93 +3737,86 @@ void CQnetLink::AudioNotifyThread(char *arg) break; } - nread = fread(dstar_buf, rlen, 1, fp); + SDSVT dsvt; + nread = fread(dsvt.title, rlen, 1, fp); if (nread == 1) { - if (memcmp(dstar_buf, "DSVT", 4) != 0) { + if (memcmp(dsvt.title, "DSVT", 4) != 0) { printf("DVST not found in %s\n", temp_file); break; } - if (dstar_buf[8] != 0x20) { + if (dsvt.id != 0x20) { printf("Not Voice type in %s\n", temp_file); break; } - if (dstar_buf[4] == 0x10) - ; - else if (dstar_buf[4] == 0x20) - ; - else { + if (dsvt.config!=0x10 && dsvt.config!=0x20) { printf("Not a valid record type in %s\n", temp_file); break; } - dstar_buf[12] = streamid_raw / 256U; - dstar_buf[13] = streamid_raw % 256U; + dsvt.streamid = htons(streamid_raw); if (rlen == 56) { - dstar_buf[15] = 0x01; + dsvt.hdr.flag[0] = 0x01; - memcpy(dstar_buf + 18, owner.c_str(), CALL_SIZE); - dstar_buf[25] = mod; + memcpy(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt1[7] = mod; - memcpy(dstar_buf + 26, owner.c_str(), CALL_SIZE); - dstar_buf[33] = 'G'; + memcpy(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; - memcpy(dstar_buf + 34, "CQCQCQ ", 8); + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); - memcpy(dstar_buf + 42, owner.c_str(), CALL_SIZE); - dstar_buf[48] = ' '; - dstar_buf[49] = ' '; + memcpy(dsvt.hdr.mycall, owner.c_str(), CALL_SIZE); + dsvt.hdr.mycall[6] = dsvt.hdr.mycall[7] = ' '; - memcpy(dstar_buf + 50, "RPTR", 4); - calcPFCS(dstar_buf, 56); + memcpy(dsvt.hdr.sfx, "RPTR", 4); + calcPFCS(dsvt.title, 56); } else { if (useTEXT) { - if ((dstar_buf[24] != 0x55) || - (dstar_buf[25] != 0x2d) || - (dstar_buf[26] != 0x16)) { + if ((dsvt.vasd.text[0] != 0x55) || (dsvt.vasd.text[1] != 0x2d) || (dsvt.vasd.text[2] != 0x16)) { if (TEXT_idx == 0) { - dstar_buf[24] = '@' ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = '@' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 2) { - dstar_buf[24] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 5) { - dstar_buf[24] = 'A' ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = 'A' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 7) { - dstar_buf[24] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 10) { - dstar_buf[24] = 'B' ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = 'B' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 12) { - dstar_buf[24] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 15) { - dstar_buf[24] = 'C' ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = 'C' ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else if (TEXT_idx == 17) { - dstar_buf[24] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstar_buf[25] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstar_buf[26] = RADIO_ID[TEXT_idx++] ^ 0x93; + dsvt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; + dsvt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; + dsvt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; } else { - dstar_buf[24] = 0x70; - dstar_buf[25] = 0x4f; - dstar_buf[26] = 0x93; + dsvt.vasd.text[0] = 0x70; + dsvt.vasd.text[1] = 0x4f; + dsvt.vasd.text[2] = 0x93; } } } } - sendto(rptr_sock, dstar_buf, rlen, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + sendto(rptr_sock, dsvt.title, rlen, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); } std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); } From 97db5708f3196b68cb8b4fbe8e6db6d59a9029dc Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 24 May 2018 19:28:26 -0700 Subject: [PATCH 009/553] char arrays replaced with structs --- MMDVM.README | 2 +- QnetLink.cpp | 1775 +++++++++++++++++++++----------------------------- QnetLink.h | 27 +- 3 files changed, 766 insertions(+), 1038 deletions(-) diff --git a/MMDVM.README b/MMDVM.README index c9f9cb0..7fb6a8a 100644 --- a/MMDVM.README +++ b/MMDVM.README @@ -69,7 +69,7 @@ disable the serial0 console in the /boot/cmdline.txt file: Remove the reference entries, at the end of the file. If you find you can no longer connect to a system, it may be because its IP address has changed. You can execute either script again, copy it to - /usr/local/etc, and then: either reboot you system, or put " L" in your + /usr/local/etc, and then: either reboot you system, or put " F" in your URField and key your radio, or: sudo systemctl restart qnlink 14) We have a gwys.txt file and a qn.cfg in the build directory, so we are ready diff --git a/QnetLink.cpp b/QnetLink.cpp index 0c4f94f..4b032cb 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -54,7 +54,6 @@ #include "versions.h" #include "QnetLink.h" -#include "QnetTypeDefs.h" using namespace libconfig; @@ -828,10 +827,8 @@ bool CQnetLink::srv_open() to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; - to_remote_g2[i].out_streamid[0] = 0x00; - to_remote_g2[i].out_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; + to_remote_g2[i].out_streamid = 0x0; } return true; } @@ -956,8 +953,7 @@ void CQnetLink::g2link(char from_mod, char *call, char to_mod) to_remote_g2[i].to_mod = to_mod; to_remote_g2[i].countdown = TIMEOUT; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid= 0x0; /* is it XRF? */ if (port_i == rmt_xrf_port) { @@ -1046,8 +1042,6 @@ void CQnetLink::Process() char tmp1[CALL_SIZE + 1]; char tmp2[36]; // 8 for rpt1 + 24 for time_t in std::string format - unsigned char readBuffer[100]; // 58 or 29 or 32, max is 58 - unsigned char readBuffer2[1024]; unsigned char dcs_buf[1000];; char call[CALL_SIZE + 1]; @@ -1179,20 +1173,17 @@ void CQnetLink::Process() if (to_remote_g2[i].countdown < 0) { /* maybe remote system has changed IP */ - printf("Unlinked from [%s] mod %c, TIMEOUT...\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Unlinked from [%s] mod %c, TIMEOUT...\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); sprintf(notify_msg, "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); @@ -1204,8 +1195,7 @@ void CQnetLink::Process() if (((tnow - tracing[i].last_time) > rf_inactivity_timer[i]) && (rf_inactivity_timer[i] > 0)) { tracing[i].last_time = 0; - printf("Unlinked from [%s] mod %c, local RF inactivity...\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Unlinked from [%s] mod %c, local RF inactivity...\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { queryCommand[0] = 5; @@ -1226,7 +1216,7 @@ void CQnetLink::Process() to_remote_g2[j].to_mod = ' '; to_remote_g2[j].countdown = 0; to_remote_g2[j].is_connected = false; - to_remote_g2[j].in_streamid[0] = to_remote_g2[j].in_streamid[1] = 0x00; + to_remote_g2[j].in_streamid = 0x0; } } } @@ -1254,12 +1244,10 @@ void CQnetLink::Process() to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } @@ -1279,132 +1267,106 @@ void CQnetLink::Process() if (FD_ISSET(xrf_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen2 = recvfrom(xrf_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); + unsigned char buf[100]; + int length = recvfrom(xrf_g2_sock, buf, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); strncpy(ip, inet_ntoa(fromDst4.sin_addr), IP_SIZE); ip[IP_SIZE] = '\0'; - strncpy(call, (char *)readBuffer2, CALL_SIZE); + memcpy(call, buf, CALL_SIZE); call[CALL_SIZE] = '\0'; - /* a packet of length (CALL_SIZE + 1) is a keepalive from a repeater/reflector */ + /* A packet of length (CALL_SIZE + 1) is a keepalive from a repeater/reflector */ /* If it is from a dongle, it is either a keepalive or a request to connect */ - if (recvlen2 == (CALL_SIZE + 1)) { + if (length == (CALL_SIZE + 1)) { found = false; /* Find out if it is a keepalive from a repeater */ for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_xrf_port)) { found = true; if (!to_remote_g2[i].is_connected) { tracing[i].last_time = time(NULL); to_remote_g2[i].is_connected = true; - printf("Connected from: %.*s\n", recvlen2 - 1, readBuffer2); + printf("Connected from: %.*s\n", length - 1, buf); print_status_file(); strcpy(linked_remote_system, to_remote_g2[i].to_call); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } to_remote_g2[i].countdown = TIMEOUT; } } - } else if (recvlen2 == (CALL_SIZE + 6)) { + } else if (length == (CALL_SIZE + 6)) { /* A packet of length (CALL_SIZE + 6) is either an ACK or a NAK from repeater-reflector */ /* Because we sent a request before asking to link */ - for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { - if ((memcmp((char *)readBuffer2 + 10, "ACK", 3) == 0) && - (to_remote_g2[i].from_mod == readBuffer2[8])) { - if (!to_remote_g2[i].is_connected) { - tracing[i].last_time = time(NULL); + for (int i=0; i<3; i++) { + if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + if (0==memcmp(buf + 10, "ACK", 3) && to_remote_g2[i].from_mod==buf[8]) { + if (!to_remote_g2[i].is_connected) { + tracing[i].last_time = time(NULL); - to_remote_g2[i].is_connected = true; - printf("Connected from: [%s] %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - print_status_file(); + to_remote_g2[i].is_connected = true; + printf("Connected from: [%s] %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + print_status_file(); - strcpy(linked_remote_system, to_remote_g2[i].to_call); - space_p = strchr(linked_remote_system, ' '); - if (space_p) - *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); - audio_notify(notify_msg); - } - } else if ((memcmp((char *)readBuffer2 + 10, "NAK", 3) == 0) && - (to_remote_g2[i].from_mod == readBuffer2[8])) { - printf("Link module %c to [%s] %c is rejected\n", - to_remote_g2[i].from_mod, to_remote_g2[i].to_call, - to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", - to_remote_g2[i].from_mod); + strcpy(linked_remote_system, to_remote_g2[i].to_call); + space_p = strchr(linked_remote_system, ' '); + if (space_p) + *space_p = '\0'; + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); + } + } else if (0==memcmp(buf + 10, "NAK", 3) && to_remote_g2[i].from_mod==buf[8]) { + printf("Link module %c to [%s] %c is rejected\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - to_remote_g2[i].to_call[0] = '\0'; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; - to_remote_g2[i].countdown = 0; - to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + audio_notify(notify_msg); - print_status_file(); - } + to_remote_g2[i].to_call[0] = '\0'; + memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].countdown = 0; + to_remote_g2[i].is_connected = false; + to_remote_g2[i].in_streamid = 0x0; + + print_status_file(); } } - } else if (recvlen2 == CALL_SIZE + 3) { - /* - A packet of length (CALL_SIZE + 3) is a request - from a remote repeater to link-unlink with our repeater - */ + } + } else if (length == CALL_SIZE + 3) { + // A packet of length (CALL_SIZE + 3) is a request + // from a remote repeater to link-unlink with our repeater /* Check our linked repeaters/reflectors */ for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { - if (to_remote_g2[i].to_mod == readBuffer2[8]) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_xrf_port)) { + if (to_remote_g2[i].to_mod == buf[8]) { /* unlink request from remote repeater that we know */ - if (readBuffer2[9] == ' ') { - printf("Received: %.*s\n", recvlen2 - 1, readBuffer2); - printf("Module %c to [%s] %c is unlinked\n", - to_remote_g2[i].from_mod, - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + if (buf[9] == ' ') { + printf("Received: %.*s\n", length - 1, buf); + printf("Module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); sprintf(notify_msg, "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } else /* link request from a remote repeater that we know */ - if ( - ((i == 0) && (readBuffer2[9] == 'A')) || - ((i == 1) && (readBuffer2[9] == 'B')) || - ((i == 2) && (readBuffer2[9] == 'C')) - ) { + if ((i==0 && buf[9]=='A') || (i==1 && buf[9]=='B') || (i==2 && buf[9]=='C')) { /* I HAVE TO ADD CODE here to PREVENT the REMOTE NODE @@ -1412,41 +1374,35 @@ void CQnetLink::Process() more than one of our local modules */ - printf("Received: %.*s\n", recvlen2 - 1, readBuffer2); + printf("Received: %.*s\n", length - 1, buf); - strncpy(to_remote_g2[i].to_call, (char *)readBuffer2,CALL_SIZE); + memcpy(to_remote_g2[i].to_call, buf, CALL_SIZE); to_remote_g2[i].to_call[CALL_SIZE] = '\0'; memcpy(&(to_remote_g2[i].toDst4), &fromDst4, sizeof(struct sockaddr_in)); to_remote_g2[i].toDst4.sin_port = htons(rmt_xrf_port); - to_remote_g2[i].to_mod = readBuffer2[8]; + to_remote_g2[i].to_mod = buf[8]; to_remote_g2[i].countdown = TIMEOUT; to_remote_g2[i].is_connected = true; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; - printf("Module %c to [%s] %c linked\n", - readBuffer2[9], - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Module %c to [%s] %c linked\n", buf[9], to_remote_g2[i].to_call, to_remote_g2[i].to_mod); tracing[i].last_time = time(NULL); print_status_file(); /* send back an ACK */ - memcpy(readBuffer2 + 10, "ACK", 4); - sendto(xrf_g2_sock, readBuffer2, CALL_SIZE+6, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + memcpy(buf + 10, "ACK", 4); + sendto(xrf_g2_sock, buf, CALL_SIZE+6, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - if (to_remote_g2[i].from_mod != readBuffer2[9]) { - to_remote_g2[i].from_mod = readBuffer2[9]; + if (to_remote_g2[i].from_mod != buf[9]) { + to_remote_g2[i].from_mod = buf[9]; strcpy(linked_remote_system, to_remote_g2[i].to_call); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } } @@ -1457,18 +1413,18 @@ void CQnetLink::Process() /* link request from remote repeater that is not yet linked to our system */ /* find out which of our local modules the remote repeater is interested in */ int i = -1; - if (readBuffer2[9] == 'A') + if (buf[9] == 'A') i = 0; - else if (readBuffer2[9] == 'B') + else if (buf[9] == 'B') i = 1; - else if (readBuffer2[9] == 'C') + else if (buf[9] == 'C') i = 2; /* Is this repeater listed in gwys.txt? */ auto gwy_pos = gwy_list.find(call); if (gwy_pos == gwy_list.end()) { /* We did NOT find this repeater in gwys.txt, reject the incoming link request */ - printf("Incoming link from %s,%s but not found in gwys.txt\n",call,ip); + printf("Incoming link from %s,%s but not found in gwys.txt\n", call, ip); i = -1; } else { int rc = regexec(&preg, call, 0, NULL, 0); @@ -1481,8 +1437,7 @@ void CQnetLink::Process() if (i >= 0) { /* Is the local repeater module linked to anything ? */ if (to_remote_g2[i].to_mod == ' ') { - if ((readBuffer2[8] == 'A') || (readBuffer2[8] == 'B') || (readBuffer2[8] == 'C') || - (readBuffer2[8] == 'D') || (readBuffer2[8] == 'E')) { + if (buf[8]>='A' && buf[8]<='E') { /* I HAVE TO ADD CODE here to PREVENT the REMOTE NODE from LINKING one of their remote modules to @@ -1494,49 +1449,40 @@ void CQnetLink::Process() to_remote_g2[i].to_call[CALL_SIZE] = '\0'; memcpy(&(to_remote_g2[i].toDst4), &fromDst4, sizeof(struct sockaddr_in)); to_remote_g2[i].toDst4.sin_port = htons(rmt_xrf_port); - to_remote_g2[i].from_mod = readBuffer2[9]; - to_remote_g2[i].to_mod = readBuffer2[8]; + to_remote_g2[i].from_mod = buf[9]; + to_remote_g2[i].to_mod = buf[8]; to_remote_g2[i].countdown = TIMEOUT; to_remote_g2[i].is_connected = true; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); tracing[i].last_time = time(NULL); - printf("Received: %.*s\n", recvlen2 - 1, readBuffer2); - printf("Module %c to [%s] %c linked\n", - to_remote_g2[i].from_mod, - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Received: %.*s\n", length - 1, buf); + printf("Module %c to [%s] %c linked\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); strcpy(linked_remote_system, to_remote_g2[i].to_call); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); /* send back an ACK */ - memcpy(readBuffer2 + 10, "ACK", 4); - sendto(xrf_g2_sock, readBuffer2, CALL_SIZE+6, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + memcpy(buf + 10, "ACK", 4); + sendto(xrf_g2_sock, buf, CALL_SIZE+6, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } } else { if (fromDst4.sin_addr.s_addr != to_remote_g2[i].toDst4.sin_addr.s_addr) { /* Our repeater module is linked to another repeater-reflector */ - memcpy(readBuffer2 + 10, "NAK", 4); + memcpy(buf + 10, "NAK", 4); fromDst4.sin_port = htons(rmt_xrf_port); - sendto(xrf_g2_sock, readBuffer2, CALL_SIZE+6, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(xrf_g2_sock, buf, CALL_SIZE+6, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } } - } else if ( ((recvlen2 == 56) || (recvlen2 == 27)) && - (0 == memcmp(readBuffer2, "DSVT", 4)) && - ((readBuffer2[4] == 0x10) || (readBuffer2[4] == 0x20)) && - (readBuffer2[8] == 0x20)) { + } else if ((length==56 || length==27) && 0==memcmp(buf, "DSVT", 4) && (buf[4]==0x10 || buf[4]==0x20) && buf[8]==0x20) { /* reset countdown and protect against hackers */ found = false; @@ -1548,42 +1494,34 @@ void CQnetLink::Process() } } - /* process header */ + SDSVT dsvt; memcpy(dsvt.title, buf, length); // copy to struct - if ((recvlen2 == 56) && found) { + /* process header */ + if ((length == 56) && found) { memset(source_stn, ' ', 9); source_stn[8] = '\0'; /* some bad hotspot programs out there using INCORRECT flag */ - if (readBuffer2[15] == 0x40) - readBuffer2[15] = 0x00; - else if (readBuffer2[15] == 0x48) - readBuffer2[15] = 0x08; - else if (readBuffer2[15] == 0x60) - readBuffer2[15] = 0x20; - else if (readBuffer2[15] == 0x68) - readBuffer2[15] = 0x28; + if (dsvt.hdr.flag[0]==0x40U || dsvt.hdr.flag[0]==0x48U || dsvt.hdr.flag[0]==0x60U || dsvt.hdr.flag[0]==0x68U) + dsvt.hdr.flag[0] -= 0x40; /* A reflector will send to us its own RPT1 */ /* A repeater will send to us our RPT1 */ for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_xrf_port)) { /* it is a reflector, reflector's rpt1 */ - if ((memcmp(readBuffer2 + 18, to_remote_g2[i].to_call, 7) == 0) && - (readBuffer2[25] == to_remote_g2[i].to_mod)) { - memcpy(&readBuffer2[18], owner.c_str(), CALL_SIZE); - readBuffer2[25] = to_remote_g2[i].from_mod; - memcpy(&readBuffer2[34], "CQCQCQ ", 8); + if (0==memcmp(dsvt.hdr.rpt1, to_remote_g2[i].to_call, 7) && dsvt.hdr.rpt1[7]==to_remote_g2[i].to_mod) { + memcpy(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; + memcpy(dsvt.hdr.urcall, "CQCQCQ ", 8); memcpy(source_stn, to_remote_g2[i].to_call, 8); source_stn[7] = to_remote_g2[i].to_mod; break; } else /* it is a repeater, our rpt1 */ - if ((memcmp(readBuffer2 + 18, owner.c_str(), CALL_SIZE-1)) && - (readBuffer2[25] == to_remote_g2[i].from_mod)) { + if (memcmp(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE-1) && dsvt.hdr.rpt1[7]==to_remote_g2[i].from_mod) { memcpy(source_stn, to_remote_g2[i].to_call, 8); source_stn[7] = to_remote_g2[i].to_mod; break; @@ -1593,41 +1531,37 @@ void CQnetLink::Process() /* somebody's crazy idea of having a personal callsign in RPT2 */ /* we must set it to our gateway callsign */ - memcpy(&readBuffer2[26], owner.c_str(), CALL_SIZE); - readBuffer2[33] = 'G'; - calcPFCS(readBuffer2,56); + memcpy(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(dsvt.title, 56); /* At this point, all data have our RPT1 and RPT2 */ /* send the data to the repeater/reflector that is linked to our RPT1 */ int i = -1; - if (readBuffer2[25] == 'A') + if (dsvt.hdr.rpt1[7] == 'A') i = 0; - else if (readBuffer2[25] == 'B') + else if (dsvt.hdr.rpt1[7] == 'B') i = 1; - else if (readBuffer2[25] == 'C') + else if (dsvt.hdr.rpt1[7] == 'C') i = 2; /* are we sure that RPT1 is our system? */ - if ((memcmp(readBuffer2 + 18, owner.c_str(), CALL_SIZE-1) == 0) && (i >= 0)) { + if (0==memcmp(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE-1) && i>=0) { /* Last Heard */ - if (memcmp(old_sid[i].sid, readBuffer2 + 12, 2) != 0) { + if (old_sid[i].sid != dsvt.streamid) { if (qso_details) - printf("START from remote g2: streamID=%d,%d, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - readBuffer2[12],readBuffer2[13], - readBuffer2[15], readBuffer2[16], readBuffer2[17], - &readBuffer2[42], - &readBuffer2[50], &readBuffer2[34], - &readBuffer2[18], &readBuffer2[26], - recvlen2,inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", + ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.mycall, dsvt.hdr.sfx, + dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, length, inet_ntoa(fromDst4.sin_addr), source_stn); // put user into tmp1 - memcpy(tmp1, readBuffer2 + 42, 8); + memcpy(tmp1, dsvt.hdr.mycall, 8); tmp1[8] = '\0'; // delete the user if exists for (auto dt_lh_pos = dt_lh_list.begin(); dt_lh_pos != dt_lh_list.end(); dt_lh_pos++) { - if (strcmp((char *)dt_lh_pos->second.c_str(), tmp1) == 0) { + if (0 == strcmp((char *)dt_lh_pos->second.c_str(), tmp1)) { dt_lh_list.erase(dt_lh_pos); break; } @@ -1639,28 +1573,29 @@ void CQnetLink::Process() } // add user time(&tnow); - sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], readBuffer2[25]); + sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], dsvt.hdr.rpt1[7]); dt_lh_list[tmp2] = tmp1; - memcpy(old_sid[i].sid, readBuffer2 + 12, 2); + old_sid[i].sid = dsvt.streamid; } /* relay data to our local G2 */ - sendto(rptr_sock, readBuffer2,56,0,(struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + sendto(rptr_sock, dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); /* send data to donglers */ /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { - readBuffer[0] = (unsigned char)(58 & 0xFF); - readBuffer[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); - memcpy(readBuffer + 2, readBuffer2, 56); + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + memcpy(rdsvt.dsvt.title, dsvt.title, 56); - sendto(ref_g2_sock, readBuffer, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } else - inbound->mod = readBuffer2[25]; + inbound->mod = dsvt.hdr.rpt1[7]; } /* send the data to the repeater/reflector that is linked to our RPT1 */ @@ -1671,134 +1606,124 @@ void CQnetLink::Process() if (k < 3) { brd_from_xrf_idx = 0; - streamid_raw = (readBuffer2[12] * 256U) + readBuffer2[13]; + streamid_raw = ntohs(dsvt.streamid); /* We can only enter this loop up to 2 times max */ for (int j=k; j<3; j++) { /* it is a remote gateway, not a dongle user */ - if ((fromDst4.sin_addr.s_addr == to_remote_g2[j].toDst4.sin_addr.s_addr) && + if (fromDst4.sin_addr.s_addr==to_remote_g2[j].toDst4.sin_addr.s_addr && /* it is xrf */ - (to_remote_g2[j].toDst4.sin_port == htons(rmt_xrf_port)) && - (memcmp(to_remote_g2[j].to_call, "XRF", 3) == 0) && + to_remote_g2[j].toDst4.sin_port==htons(rmt_xrf_port) && + 0==memcmp(to_remote_g2[j].to_call, "XRF", 3) && /* it is the same xrf and xrf module */ - (memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) == 0) && - (to_remote_g2[j].to_mod == to_remote_g2[i].to_mod)) { + 0==memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) && + to_remote_g2[j].to_mod==to_remote_g2[i].to_mod) { /* send the packet to another module of our local repeater: this is multi-link */ /* generate new packet */ - memcpy(from_xrf_torptr_brd, readBuffer2, 56); + memcpy(from_xrf_torptr_brd.title, dsvt.title, 56); /* different repeater module */ - from_xrf_torptr_brd[25] = to_remote_g2[j].from_mod; + from_xrf_torptr_brd.hdr.rpt1[7] = to_remote_g2[j].from_mod; /* assign new streamid */ - streamid_raw ++; + streamid_raw++; if (streamid_raw == 0) - streamid_raw ++; - from_xrf_torptr_brd[12] = streamid_raw / 256U; - from_xrf_torptr_brd[13] = streamid_raw % 256U; + streamid_raw++; + from_xrf_torptr_brd.streamid = htons(streamid_raw); - calcPFCS(from_xrf_torptr_brd, 56); + calcPFCS(from_xrf_torptr_brd.title, 56); /* send the data to the local gateway/repeater */ - sendto(rptr_sock, from_xrf_torptr_brd, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + sendto(rptr_sock, from_xrf_torptr_brd.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); /* save streamid for use with the audio packets that will arrive after this header */ - brd_from_xrf.xrf_streamid[0] = readBuffer2[12]; - brd_from_xrf.xrf_streamid[1] = readBuffer2[13]; - brd_from_xrf.rptr_streamid[brd_from_xrf_idx][0] = from_xrf_torptr_brd[12]; - brd_from_xrf.rptr_streamid[brd_from_xrf_idx][1] = from_xrf_torptr_brd[13]; + brd_from_xrf.xrf_streamid = dsvt.streamid; + brd_from_xrf.rptr_streamid[brd_from_xrf_idx] = from_xrf_torptr_brd.streamid; brd_from_xrf_idx ++; } } } - if ((to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - to_remote_g2[i].is_connected) { + if (to_remote_g2[i].toDst4.sin_addr.s_addr!=fromDst4.sin_addr.s_addr && to_remote_g2[i].is_connected) { if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { if ( /*** (memcmp(readBuffer2 + 42, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 34, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[15] == 0x00) || /* normal */ - (readBuffer2[15] == 0x08) || /* EMR */ - (readBuffer2[15] == 0x20) || /* BK */ - (readBuffer2[15] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 26, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[33] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[12]; - to_remote_g2[i].in_streamid[1] = readBuffer2[13]; + (memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) == 0) && /* CQ calls only */ + (dsvt.hdr.flag[0] == 0x00 || /* normal */ + dsvt.hdr.flag[0] == 0x08 || /* EMR */ + dsvt.hdr.flag[0] == 0x20 || /* BK */ + dsvt.hdr.flag[0] == 0x28) && /* EMR + BK */ + 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = dsvt.streamid; /* inform XRF about the source */ - readBuffer2[11] = to_remote_g2[i].from_mod; + dsvt.flagb[2] = to_remote_g2[i].from_mod; - memcpy((char *)readBuffer2 + 18, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[25] = to_remote_g2[i].to_mod; - memcpy((char *)readBuffer2 + 26, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[33] = 'G'; - calcPFCS(readBuffer2, 56); + memcpy(dsvt.hdr.rpt1, to_remote_g2[i].to_call, CALL_SIZE); + dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memcpy(dsvt.hdr.rpt2, to_remote_g2[i].to_call, CALL_SIZE); + dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(dsvt.title, 56); - sendto(xrf_g2_sock, readBuffer2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(xrf_g2_sock, dsvt.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { if ( /*** (memcmp(readBuffer2 + 42, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 34, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[15] == 0x00) || /* normal */ - (readBuffer2[15] == 0x08) || /* EMR */ - (readBuffer2[15] == 0x20) || /* BK */ - (readBuffer2[15] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 26, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[33] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[12]; - to_remote_g2[i].in_streamid[1] = readBuffer2[13]; - - readBuffer[0] = (unsigned char)(58 & 0xFF); - readBuffer[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); - - memcpy(readBuffer + 2, readBuffer2, 56); - - memset(readBuffer + 20, ' ', CALL_SIZE); - memcpy(readBuffer + 20, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer[27] = to_remote_g2[i].to_mod; - memset(readBuffer + 28, ' ', CALL_SIZE); - memcpy(readBuffer + 28, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer[35] = 'G'; - memcpy(&readBuffer[36], "CQCQCQ ", 8); - - calcPFCS(readBuffer + 2, 56); - - sendto(ref_g2_sock, readBuffer, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && /* CQ calls only */ + (dsvt.hdr.flag[0] == 0x00 || /* normal */ + dsvt.hdr.flag[0] == 0x08 || /* EMR */ + dsvt.hdr.flag[0] == 0x20 || /* BK */ + dsvt.hdr.flag[0] == 0x28) && /* EMR + BK */ + 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = dsvt.streamid; + + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, dsvt.title, 56); + + memset(rdsvt.dsvt.hdr.rpt1, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].to_call, strlen(to_remote_g2[i].to_call)); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memset(rdsvt.dsvt.hdr.rpt2, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].to_call, strlen(to_remote_g2[i].to_call)); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); + + calcPFCS(rdsvt.dsvt.title, 56); + + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { if ( /*** (memcmp(readBuffer2 + 42, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 34, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[15] == 0x00) || /* normal */ - (readBuffer2[15] == 0x08) || /* EMR */ - (readBuffer2[15] == 0x20) || /* BK */ - (readBuffer2[15] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 26, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[33] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[12]; - to_remote_g2[i].in_streamid[1] = readBuffer2[13]; - - memcpy(xrf_2_dcs[i].mycall, readBuffer2 + 42, 8); - memcpy(xrf_2_dcs[i].sfx, readBuffer2 + 50, 4); + 0==memcmp(dsvt.hdr.urcall, "CQCQCQ", 6) && /* CQ calls only */ + (dsvt.hdr.flag[0] == 0x00 || /* normal */ + dsvt.hdr.flag[0] == 0x08 || /* EMR */ + dsvt.hdr.flag[0] == 0x20 || /* BK */ + dsvt.hdr.flag[0] == 0x28) && /* EMR + BK */ + 0==memcmp(dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = dsvt.streamid; + + memcpy(xrf_2_dcs[i].mycall, dsvt.hdr.mycall, CALL_SIZE); + memcpy(xrf_2_dcs[i].sfx, dsvt.hdr.sfx, 4); xrf_2_dcs[i].dcs_rptr_seq = 0; } } } } - } else if (found) { - if ((readBuffer2[14] & 0x40) != 0) { + } else if (found) { // length is 27 + if ((dsvt.counter & 0x40) != 0) { for (int i=0; i<3; i++) { - if (memcmp(old_sid[i].sid, readBuffer2 + 12, 2) == 0) { + if (old_sid[i].sid == dsvt.streamid) { if (qso_details) - printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", - readBuffer2[12],readBuffer2[13],recvlen2,inet_ntoa(fromDst4.sin_addr)); - - memset(old_sid[i].sid, 0x00, 2); + printf("END from remote g2: streamID=%04x, %d bytes from IP=%s\n", ntohs(dsvt.streamid), length, inet_ntoa(fromDst4.sin_addr)); + old_sid[i].sid = 0x0; break; } @@ -1806,71 +1731,65 @@ void CQnetLink::Process() } /* relay data to our local G2 */ - sendto(rptr_sock, readBuffer2, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + sendto(rptr_sock, dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); /* send data to donglers */ /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { - readBuffer[0] = (unsigned char)(29 & 0xFF); - readBuffer[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); - memcpy(readBuffer + 2, readBuffer2, 27); + memcpy(rdsvt.dsvt.title, dsvt.title, 27); - sendto(ref_g2_sock, readBuffer, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } /* do we have to broadcast ? */ - if (memcmp(brd_from_xrf.xrf_streamid, readBuffer2 + 12, 2) == 0) { - memcpy(from_xrf_torptr_brd, readBuffer2, 27); - - if ((brd_from_xrf.rptr_streamid[0][0] != 0x00) || - (brd_from_xrf.rptr_streamid[0][1] != 0x00)) { - from_xrf_torptr_brd[12] = brd_from_xrf.rptr_streamid[0][0]; - from_xrf_torptr_brd[13] = brd_from_xrf.rptr_streamid[0][1]; - sendto(rptr_sock, from_xrf_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + if (brd_from_xrf.xrf_streamid == dsvt.streamid) { + memcpy(from_xrf_torptr_brd.title, dsvt.title, 27); + + if (brd_from_xrf.rptr_streamid[0] != 0x0) { + from_xrf_torptr_brd.streamid = brd_from_xrf.rptr_streamid[0]; + sendto(rptr_sock, from_xrf_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); } - if ((brd_from_xrf.rptr_streamid[1][0] != 0x00) || - (brd_from_xrf.rptr_streamid[1][1] != 0x00)) { - from_xrf_torptr_brd[12] = brd_from_xrf.rptr_streamid[1][0]; - from_xrf_torptr_brd[13] = brd_from_xrf.rptr_streamid[1][1]; - sendto(rptr_sock, from_xrf_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + if (brd_from_xrf.rptr_streamid[1] != 0x0) { + from_xrf_torptr_brd.streamid = brd_from_xrf.rptr_streamid[1]; + sendto(rptr_sock, from_xrf_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); } - if ((readBuffer2[14] & 0x40) != 0) { - brd_from_xrf.xrf_streamid[0] = brd_from_xrf.xrf_streamid[1] = 0x00; - brd_from_xrf.rptr_streamid[0][0] = brd_from_xrf.rptr_streamid[0][1] = 0x00; - brd_from_xrf.rptr_streamid[1][0] = brd_from_xrf.rptr_streamid[1][1] = 0x00; + if (dsvt.counter & 0x40) { + brd_from_xrf.xrf_streamid = brd_from_xrf.rptr_streamid[0] = brd_from_xrf.rptr_streamid[1] = 0x0; brd_from_xrf_idx = 0; } } for (int i=0; i<3; i++) { - if ((to_remote_g2[i].is_connected) && - (to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - (memcmp(to_remote_g2[i].in_streamid, readBuffer2 + 12, 2) == 0)) { + if (to_remote_g2[i].is_connected && to_remote_g2[i].toDst4.sin_addr.s_addr!=fromDst4.sin_addr.s_addr && to_remote_g2[i].in_streamid==dsvt.streamid) { if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { /* inform XRF about the source */ - readBuffer2[11] = to_remote_g2[i].from_mod; + dsvt.flagb[2] = to_remote_g2[i].from_mod; - sendto(xrf_g2_sock, readBuffer2, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(xrf_g2_sock, dsvt.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) { - readBuffer[0] = (unsigned char)(29 & 0xFF); - readBuffer[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer[1] = (unsigned char)(readBuffer[1] | 0xFFFFFF80); + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); - memcpy(readBuffer + 2, readBuffer2, 27); + memcpy(rdsvt.dsvt.title, dsvt.title, 27); - sendto(ref_g2_sock, readBuffer, 29, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { memset(dcs_buf, 0x00, 600); dcs_buf[0] = dcs_buf[1] = dcs_buf[2] = '0'; dcs_buf[3] = '1'; - dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x00; + dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x0; memcpy(dcs_buf + 7, to_remote_g2[i].to_call, 8); dcs_buf[14] = to_remote_g2[i].to_mod; memcpy(dcs_buf + 15, owner.c_str(), CALL_SIZE); @@ -1878,26 +1797,24 @@ void CQnetLink::Process() memcpy(dcs_buf + 23, "CQCQCQ ", 8); memcpy(dcs_buf + 31, xrf_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, xrf_2_dcs[i].sfx, 4); - dcs_buf[43] = readBuffer2[12]; /* streamid0 */ - dcs_buf[44] = readBuffer2[13]; /* streamid1 */ - dcs_buf[45] = readBuffer2[14]; /* cycle sequence */ - memcpy(dcs_buf + 46, readBuffer2 + 15, 12); + memcpy(dcs_buf + 43, &dsvt.streamid, 2); + dcs_buf[45] = dsvt.counter; /* cycle sequence */ + memcpy(dcs_buf + 46, dsvt.vasd.voice, 12); dcs_buf[58] = (xrf_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; dcs_buf[59] = (xrf_2_dcs[i].dcs_rptr_seq >> 8) & 0xff; dcs_buf[60] = (xrf_2_dcs[i].dcs_rptr_seq >> 16) & 0xff; - xrf_2_dcs[i].dcs_rptr_seq ++; + xrf_2_dcs[i].dcs_rptr_seq++; dcs_buf[61] = 0x01; dcs_buf[62] = 0x00; - sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - if ((readBuffer2[14] & 0x40) != 0) { - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + if (dsvt.counter & 0x40) { + to_remote_g2[i].in_streamid = 0x0; } break; } @@ -1909,19 +1826,16 @@ void CQnetLink::Process() if (FD_ISSET(ref_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen2 = recvfrom(ref_g2_sock, (char *)readBuffer2, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); + unsigned char buf[100]; + int length = recvfrom(ref_g2_sock, buf, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); - strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); + strncpy(ip, inet_ntoa(fromDst4.sin_addr), IP_SIZE); ip[IP_SIZE] = '\0'; found = false; /* LH */ - if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 7) && - (readBuffer2[3] == 0)) { + if (length==4 && buf[0]==4 && buf[1]==192 && buf[2]==7 && buf[3]==0) { unsigned short j_idx = 0; unsigned short k_idx = 0; unsigned char tmp[2]; @@ -1934,54 +1848,54 @@ void CQnetLink::Process() /* header is 10 bytes */ /* reply type */ - readBuffer2[2] = 7; - readBuffer2[3] = 0; + buf[2] = 7; + buf[3] = 0; /* it looks like time_t here */ time(&tnow); - memcpy((char *)readBuffer2 + 6, (char *)&tnow, sizeof(time_t)); + memcpy(buf + 6, (char *)&tnow, sizeof(time_t)); for (auto r_dt_lh_pos = dt_lh_list.rbegin(); r_dt_lh_pos != dt_lh_list.rend(); r_dt_lh_pos++) { /* each entry has 24 bytes */ /* start at position 10 to bypass the header */ - strcpy((char *)readBuffer2 + 10 + (24 * j_idx), r_dt_lh_pos->second.c_str()); + strcpy((char *)buf + 10 + (24 * j_idx), r_dt_lh_pos->second.c_str()); p = strchr((char *)r_dt_lh_pos->first.c_str(), '='); if (p) { - memcpy((char *)readBuffer2 + 18 + (24 * j_idx), p + 2, 8); + memcpy((char *)buf + 18 + (24 * j_idx), p + 2, 8); /* if local or local w/gps */ - if ((p[1] == 'l') || (p[1] == 'g')) - readBuffer2[18 + (24 * j_idx) + 6] = *(p + 1); + if (p[1]=='l' || p[1]=='g') + buf[18 + (24 * j_idx) + 6] = *(p + 1); *p = '\0'; tnow = atol(r_dt_lh_pos->first.c_str()); *p = '='; - memcpy((char *)readBuffer2 + 26 + (24 * j_idx), &tnow, sizeof(time_t)); + memcpy(buf + 26 + (24 * j_idx), &tnow, sizeof(time_t)); } else { - memcpy((char *)readBuffer2 + 18 + (24 * j_idx), "ERROR ", 8); + memcpy(buf + 18 + (24 * j_idx), "ERROR ", 8); time(&tnow); - memcpy((char *)readBuffer2 + 26 + (24 * j_idx), &tnow, sizeof(time_t)); + memcpy(buf + 26 + (24 * j_idx), &tnow, sizeof(time_t)); } - readBuffer2[30 + (24 * j_idx)] = 0; - readBuffer2[31 + (24 * j_idx)] = 0; - readBuffer2[32 + (24 * j_idx)] = 0; - readBuffer2[33 + (24 * j_idx)] = 0; + buf[30 + (24 * j_idx)] = 0; + buf[31 + (24 * j_idx)] = 0; + buf[32 + (24 * j_idx)] = 0; + buf[33 + (24 * j_idx)] = 0; j_idx++; /* process 39 entries at a time */ if (j_idx == 39) { /* 39 * 24 = 936 + 10 header = 946 */ - readBuffer2[0] = 0xb2; - readBuffer2[1] = 0xc3; + buf[0] = 0xb2; + buf[1] = 0xc3; /* 39 entries */ - readBuffer2[4] = 0x27; - readBuffer2[5] = 0x00; + buf[4] = 0x27; + buf[5] = 0x00; - sendto(ref_g2_sock, readBuffer2, 946, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 946, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); j_idx = 0; } @@ -1990,22 +1904,18 @@ void CQnetLink::Process() if (j_idx != 0) { k_idx = 10 + (j_idx * 24); memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[0] = tmp[0]; - readBuffer2[1] = tmp[1] | 0xc0; + buf[0] = tmp[0]; + buf[1] = tmp[1] | 0xc0; memcpy(tmp, (char *)&j_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, k_idx, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, k_idx, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } /* linked repeaters request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 5) && - (readBuffer2[3] == 0)) { + } else if (length==4 && buf[0]==4 && buf[1]==192 && buf[2]==5 && buf[3]==0) { unsigned short i_idx = 0; unsigned short j_idx = 0; unsigned short k_idx = 0; @@ -2020,54 +1930,51 @@ void CQnetLink::Process() /* header is 8 bytes */ /* reply type */ - readBuffer2[2] = 5; - readBuffer2[3] = 1; + buf[2] = 5; + buf[3] = 1; /* we can have up to 3 linked systems */ total = 3; memcpy(tmp, (char *)&total, 2); - readBuffer2[6] = tmp[0]; - readBuffer2[7] = tmp[1]; + buf[6] = tmp[0]; + buf[7] = tmp[1]; for (int i=0, i_idx=0; i<3; i++, i_idx++) { /* each entry has 20 bytes */ if (to_remote_g2[i].to_mod != ' ') { if (i == 0) - readBuffer2[8 + (20 * j_idx)] = 'A'; + buf[8 + (20 * j_idx)] = 'A'; else if (i == 1) - readBuffer2[8 + (20 * j_idx)] = 'B'; + buf[8 + (20 * j_idx)] = 'B'; else if (i == 2) - readBuffer2[8 + (20 * j_idx)] = 'C'; - - strcpy((char *)readBuffer2 + 9 + (20 * j_idx), to_remote_g2[i].to_call); - readBuffer2[16 + (20 * j_idx)] = to_remote_g2[i].to_mod; - - readBuffer2[17 + (20 * j_idx)] = 0; - readBuffer2[18 + (20 * j_idx)] = 0; - readBuffer2[19 + (20 * j_idx)] = 0; - readBuffer2[20 + (20 * j_idx)] = 0x50; - readBuffer2[21 + (20 * j_idx)] = 0x04; - readBuffer2[22 + (20 * j_idx)] = 0x32; - readBuffer2[23 + (20 * j_idx)] = 0x4d; - readBuffer2[24 + (20 * j_idx)] = 0x9f; - readBuffer2[25 + (20 * j_idx)] = 0xdb; - readBuffer2[26 + (20 * j_idx)] = 0x0e; - readBuffer2[27 + (20 * j_idx)] = 0; + buf[8 + (20 * j_idx)] = 'C'; + + strcpy((char *)buf + 9 + (20 * j_idx), to_remote_g2[i].to_call); + buf[16 + (20 * j_idx)] = to_remote_g2[i].to_mod; + + buf[17 + (20 * j_idx)] = buf[18 + (20 * j_idx)] = buf[19 + (20 * j_idx)] = 0; + buf[20 + (20 * j_idx)] = 0x50; + buf[21 + (20 * j_idx)] = 0x04; + buf[22 + (20 * j_idx)] = 0x32; + buf[23 + (20 * j_idx)] = 0x4d; + buf[24 + (20 * j_idx)] = 0x9f; + buf[25 + (20 * j_idx)] = 0xdb; + buf[26 + (20 * j_idx)] = 0x0e; + buf[27 + (20 * j_idx)] = 0; j_idx++; if (j_idx == 39) { /* 20 bytes for each user, so 39 * 20 = 780 bytes + 8 bytes header = 788 */ - readBuffer2[0] = 0x14; - readBuffer2[1] = 0xc3; + buf[0] = 0x14; + buf[1] = 0xc3; k_idx = i_idx - 38; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; - - sendto(ref_g2_sock, readBuffer2,788,0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + buf[4] = tmp[0]; + buf[5] = tmp[1]; + sendto(ref_g2_sock, buf, 788, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); j_idx = 0; } } @@ -2076,8 +1983,8 @@ void CQnetLink::Process() if (j_idx != 0) { k_idx = 8 + (j_idx * 20); memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[0] = tmp[0]; - readBuffer2[1] = tmp[1] | 0xc0; + buf[0] = tmp[0]; + buf[1] = tmp[1] | 0xc0; if (i_idx > j_idx) k_idx = i_idx - j_idx; @@ -2085,18 +1992,14 @@ void CQnetLink::Process() k_idx = 0; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } /* connected user list request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 6) && - (readBuffer2[3] == 0)) { + } else if (length==4 && buf[0]==4 && buf[1]==192 && buf[2]==6 && buf[3]==0) { unsigned short i_idx = 0; unsigned short j_idx = 0; unsigned short k_idx = 0; @@ -2106,53 +2009,51 @@ void CQnetLink::Process() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { // printf("Remote station %s %s requested connected user list\n", inbound_ptr->call, ip); - /* header is 8 bytes */ - /* reply type */ - readBuffer2[2] = 6; - readBuffer2[3] = 0; + buf[2] = 6; + buf[3] = 0; /* total connected users */ total = inbound_list.size(); memcpy(tmp, (char *)&total, 2); - readBuffer2[6] = tmp[0]; - readBuffer2[7] = tmp[1]; + buf[6] = tmp[0]; + buf[7] = tmp[1]; for (pos = inbound_list.begin(), i_idx = 0; pos != inbound_list.end(); pos++, i_idx++) { /* each entry has 20 bytes */ - readBuffer2[8 + (20 * j_idx)] = ' '; + buf[8 + (20 * j_idx)] = ' '; SINBOUND *inbound = (SINBOUND *)pos->second; - readBuffer2[8 + (20 * j_idx)] = inbound->mod; - strcpy((char *)readBuffer2 + 9 + (20 * j_idx), inbound->call); + buf[8 + (20 * j_idx)] = inbound->mod; + strcpy((char *)buf + 9 + (20 * j_idx), inbound->call); - readBuffer2[17 + (20 * j_idx)] = 0; + buf[17 + (20 * j_idx)] = 0; /* readBuffer2[18 + (20 * j_idx)] = 0; */ - readBuffer2[18 + (20 * j_idx)] = inbound->client; - readBuffer2[19 + (20 * j_idx)] = 0; - readBuffer2[20 + (20 * j_idx)] = 0x0d; - readBuffer2[21 + (20 * j_idx)] = 0x4d; - readBuffer2[22 + (20 * j_idx)] = 0x37; - readBuffer2[23 + (20 * j_idx)] = 0x4d; - readBuffer2[24 + (20 * j_idx)] = 0x6f; - readBuffer2[25 + (20 * j_idx)] = 0x98; - readBuffer2[26 + (20 * j_idx)] = 0x04; - readBuffer2[27 + (20 * j_idx)] = 0; + buf[18 + (20 * j_idx)] = inbound->client; + buf[19 + (20 * j_idx)] = 0; + buf[20 + (20 * j_idx)] = 0x0d; + buf[21 + (20 * j_idx)] = 0x4d; + buf[22 + (20 * j_idx)] = 0x37; + buf[23 + (20 * j_idx)] = 0x4d; + buf[24 + (20 * j_idx)] = 0x6f; + buf[25 + (20 * j_idx)] = 0x98; + buf[26 + (20 * j_idx)] = 0x04; + buf[27 + (20 * j_idx)] = 0; j_idx++; if (j_idx == 39) { /* 20 bytes for each user, so 39 * 20 = 788 bytes + 8 bytes header = 788 */ - readBuffer2[0] = 0x14; - readBuffer2[1] = 0xc3; + buf[0] = 0x14; + buf[1] = 0xc3; k_idx = i_idx - 38; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, 788, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 788, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); j_idx = 0; } @@ -2161,8 +2062,8 @@ void CQnetLink::Process() if (j_idx != 0) { k_idx = 8 + (j_idx * 20); memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[0] = tmp[0]; - readBuffer2[1] = tmp[1] | 0xc0; + buf[0] = tmp[0]; + buf[1] = tmp[1] | 0xc0; if (i_idx > j_idx) k_idx = i_idx - j_idx; @@ -2170,18 +2071,14 @@ void CQnetLink::Process() k_idx = 0; memcpy(tmp, (char *)&k_idx, 2); - readBuffer2[4] = tmp[0]; - readBuffer2[5] = tmp[1]; + buf[4] = tmp[0]; + buf[5] = tmp[1]; - sendto(ref_g2_sock, readBuffer2, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 8+(j_idx*20), 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } } /* date request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 8) && - (readBuffer2[3] == 0)) { + } else if (length== 4 && buf[0]==4 && buf[1]==192 && buf[2]==8 && buf[3]==0) { time_t ltime; struct tm tm; @@ -2193,64 +2090,44 @@ void CQnetLink::Process() time(<ime); localtime_r(<ime,&tm); - readBuffer2[0] = 34; - readBuffer2[1] = 192; - readBuffer2[2] = 8; - readBuffer2[3] = 0; - readBuffer2[4] = 0xb5; - readBuffer2[5] = 0xae; - readBuffer2[6] = 0x37; - readBuffer2[7] = 0x4d; - snprintf((char *)readBuffer2 + 8, 1024 - 1, - "20%02d/%02d/%02d %02d:%02d:%02d %5.5s", - tm.tm_year % 100, tm.tm_mon+1,tm.tm_mday, - tm.tm_hour,tm.tm_min,tm.tm_sec, - (tzname[0] == NULL)?" ":tzname[0]); - - sendto(ref_g2_sock, readBuffer2, 34, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + buf[0] = 34; + buf[4] = 0xb5; + buf[5] = 0xae; + buf[6] = 0x37; + buf[7] = 0x4d; + snprintf((char *)buf + 8, 99, "20%02d/%02d/%02d %02d:%02d:%02d %5.5s", + tm.tm_year % 100, tm.tm_mon+1,tm.tm_mday, tm.tm_hour,tm.tm_min,tm.tm_sec, + (tzname[0] == NULL)?" ":tzname[0]); + + sendto(ref_g2_sock, buf, 34, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } /* version request */ - } else if ((recvlen2 == 4) && - (readBuffer2[0] == 4) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 3) && - (readBuffer2[3] == 0)) { + } else if (length== 4 && buf[0]==4 && buf[1]==192 && buf[2]==3 && buf[3]==0) { auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { //SINBOUND *inbound = (SINBOUND *)pos->second; // printf("Remote station %s %s requested version\n", inbound_ptr->call, ip); - readBuffer2[0] = 9; - readBuffer2[1] = 192; - readBuffer2[2] = 3; - readBuffer2[3] = 0; - strncpy((char *)readBuffer2 + 4, VERSION, 4); - readBuffer2[8] = 0; + buf[0] = 9; + strncpy((char *)buf + 4, VERSION, 4); + buf[8] = 0; - sendto(ref_g2_sock, readBuffer2, 9, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 9, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } - } else if ((recvlen2 == 5) && - (readBuffer2[0] == 5) && - (readBuffer2[1] == 0) && - (readBuffer2[2] == 24) && - (readBuffer2[3] == 0) && - (readBuffer2[4] == 0)) { + } else if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==0) { /* reply with the same DISCONNECT */ - sendto(ref_g2_sock, readBuffer2, 5, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, buf, 5, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port)) { printf("Call %s disconnected\n", to_remote_g2[i].to_call); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; } } @@ -2267,15 +2144,9 @@ void CQnetLink::Process() } for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port))) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port)) { found = true; - if ((recvlen2 == 5) && - (readBuffer2[0] == 5) && - (readBuffer2[1] == 0) && - (readBuffer2[2] == 24) && - (readBuffer2[3] == 0) && - (readBuffer2[4] == 1)) { + if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==1) { printf("Connected to call %s\n", to_remote_g2[i].to_call); queryCommand[0] = 28; queryCommand[1] = 192; @@ -2294,7 +2165,7 @@ void CQnetLink::Process() // ATTENTION: I should ONLY send once for each distinct // remote IP, so get out of the loop immediately - sendto(ref_g2_sock, queryCommand,28,0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); + sendto(ref_g2_sock, queryCommand, 28, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); break; } @@ -2305,19 +2176,12 @@ void CQnetLink::Process() if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; - if ((recvlen2 == 8) && - (readBuffer2[0] == 8) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 4) && - (readBuffer2[3] == 0)) { - if ((readBuffer2[4] == 79) && - (readBuffer2[5] == 75) && - (readBuffer2[6] == 82)) { + if (length==8 && buf[0]==8 && buf[1]==192 && buf[2]==4 && buf[3]==0) { + if (buf[4]== 79 && buf[5]==75 && buf[6]==82) { if (!to_remote_g2[i].is_connected) { to_remote_g2[i].is_connected = true; to_remote_g2[i].countdown = TIMEOUT; - printf("Login OK to call %s mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + printf("Login OK to call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); print_status_file(); tracing[i].last_time = time(NULL); @@ -2326,50 +2190,33 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } - } else if ((readBuffer2[4] == 70) && - (readBuffer2[5] == 65) && - (readBuffer2[6] == 73) && - (readBuffer2[7] == 76)) { - printf("Login failed to call %s mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", - to_remote_g2[i].from_mod); + } else if (buf[4]==70 && buf[5]==65 && buf[6]==73 && buf[7]==76) { + printf("Login failed to call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + + sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; - } else if ((readBuffer2[4] == 66) && - (readBuffer2[5] == 85) && - (readBuffer2[6] == 83) && - (readBuffer2[7] == 89)) { - printf("Busy or unknown status from call %s mod %c\n", - to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", - to_remote_g2[i].from_mod); + to_remote_g2[i].in_streamid = 0x0; + } else if (buf[4]==66 && buf[5]==85 && buf[6]==83 && buf[7]==89) { + printf("Busy or unknown status from call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); + + sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; } } } @@ -2379,21 +2226,16 @@ void CQnetLink::Process() if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; - if ((recvlen2 == 24) && - (readBuffer2[0] == 24) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 3) && - (readBuffer2[3] == 0)) { + if (length==24 && buf[0]==24 && buf[1]==192 && buf[2]==3 && buf[3]==0) { to_remote_g2[i].countdown = TIMEOUT; } } } for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port)) { found = true; - if (recvlen2 == 3) + if (length == 3) to_remote_g2[i].countdown = TIMEOUT; } } @@ -2415,23 +2257,14 @@ void CQnetLink::Process() and it is not a connected dongle. In this case, this must be an INCOMING dongle request */ - if ((recvlen2 == 5) && - (readBuffer2[0] == 5) && - (readBuffer2[1] == 0) && - (readBuffer2[2] == 24) && - (readBuffer2[3] == 0) && - (readBuffer2[4] == 1)) { + if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==1) { if ((inbound_list.size() + 1) > max_dongles) printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip, (int)inbound_list.size()); else - sendto(ref_g2_sock, readBuffer2, 5, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); - } else if ((recvlen2 == 28) && - (readBuffer2[0] == 28) && - (readBuffer2[1] == 192) && - (readBuffer2[2] == 4) && - (readBuffer2[3] == 0)) { + sendto(ref_g2_sock, buf, 5, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + } else if (length==28 && buf[0]==28 && buf[1]==192 && buf[2]==4 && buf[3]==0) { /* verify callsign */ - memcpy(call, readBuffer2 + 4, CALL_SIZE); + memcpy(call, buf + 4, CALL_SIZE); call[CALL_SIZE] = '\0'; for (int i=7; i>0; i--) { if (call[i] == '\0') @@ -2440,9 +2273,8 @@ void CQnetLink::Process() break; } - if (memcmp(call, "1NFO", 4) != 0) - printf("Inbound DONGLE-p CALL=%s, ip=%s, DV=%.8s\n", - call, ip, readBuffer2 + 20); + if (memcmp(call, "1NFO", 4)) + printf("Inbound DONGLE-p CALL=%s, ip=%s, DV=%.8s\n", call, ip, buf + 20); if ((inbound_list.size() + 1) > max_dongles) printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip, (int)inbound_list.size()); @@ -2451,13 +2283,13 @@ void CQnetLink::Process() else if (regexec(&preg, call, 0, NULL, 0) != 0) { printf("Invalid dongle callsign: CALL=%s,ip=%s\n", call, ip); - readBuffer2[0] = 8; - readBuffer2[4] = 70; - readBuffer2[5] = 65; - readBuffer2[6] = 73; - readBuffer2[7] = 76; + buf[0] = 8; + buf[4] = 70; + buf[5] = 65; + buf[6] = 73; + buf[7] = 76; - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } else { /* add the dongle to the inbound list */ SINBOUND *inbound = (SINBOUND *)malloc(sizeof(SINBOUND)); @@ -2468,9 +2300,9 @@ void CQnetLink::Process() inbound->mod = ' '; - if (memcmp(readBuffer2 + 20, "AP", 2) == 0) + if (memcmp(buf + 20, "AP", 2) == 0) inbound->client = 'A'; /* dvap */ - else if (memcmp(readBuffer2 + 20, "DV019999", 8) == 0) + else if (memcmp(buf + 20, "DV019999", 8) == 0) inbound->client = 'H'; /* spot */ else inbound->client = 'D'; /* dongle */ @@ -2480,14 +2312,13 @@ void CQnetLink::Process() if (memcmp(inbound->call, "1NFO", 4) != 0) printf("new CALL=%s, DONGLE-p, ip=%s, users=%d\n", inbound->call,ip, (int)inbound_list.size()); - readBuffer2[0] = 8; - readBuffer2[4] = 79; - readBuffer2[5] = 75; - readBuffer2[6] = 82; - readBuffer2[7] = 87; - - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + buf[0] = 8; + buf[4] = 79; + buf[5] = 75; + buf[6] = 82; + buf[7] = 87; + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); print_status_file(); } else { @@ -2495,41 +2326,34 @@ void CQnetLink::Process() free(inbound); inbound = NULL; - readBuffer2[0] = 8; - readBuffer2[4] = 70; - readBuffer2[5] = 65; - readBuffer2[6] = 73; - readBuffer2[7] = 76; + buf[0] = 8; + buf[4] = 70; + buf[5] = 65; + buf[6] = 73; + buf[7] = 76; - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } } else { printf("malloc() failed for call=%s,ip=%s\n",call,ip); - readBuffer2[0] = 8; - readBuffer2[4] = 70; - readBuffer2[5] = 65; - readBuffer2[6] = 73; - readBuffer2[7] = 76; + buf[0] = 8; + buf[4] = 70; + buf[5] = 65; + buf[6] = 73; + buf[7] = 76; - sendto(ref_g2_sock, readBuffer2, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); + sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } } } } - if ( ((recvlen2 == 58) || - (recvlen2 == 29) || - (recvlen2 == 32)) && - (memcmp(readBuffer2 + 2, "DSVT", 4) == 0) && - ((readBuffer2[6] == 0x10) || - (readBuffer2[6] == 0x20)) && - (readBuffer2[10] == 0x20)) { + if ((length==58 || length==29 || length==32) && 0==memcmp(buf + 2, "DSVT", 4) && (buf[6]==0x10 || buf[6]==0x20) && buf[10]==0x20) { /* Is it one of the donglers or repeaters-reflectors */ found = false; for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port)) { to_remote_g2[i].countdown = TIMEOUT; found = true; } @@ -2543,40 +2367,33 @@ void CQnetLink::Process() } } - if ((recvlen2 == 58) && found) { + SREFDSVT rdsvt; memcpy(rdsvt.head, buf, length); // copy to struct + + if (length==58 && found) { memset(source_stn, ' ', 9); source_stn[8] = '\0'; /* some bad hotspot programs out there using INCORRECT flag */ - if (readBuffer2[17] == 0x40) - readBuffer2[17] = 0x00; - else if (readBuffer2[17] == 0x48) - readBuffer2[17] = 0x08; - else if (readBuffer2[17] == 0x60) - readBuffer2[17] = 0x20; - else if (readBuffer2[17] == 0x68) - readBuffer2[17] = 0x28; + if (rdsvt.dsvt.hdr.flag[0]==0x40U || rdsvt.dsvt.hdr.flag[0]==0x48U || rdsvt.dsvt.hdr.flag[0]==0x60U || rdsvt.dsvt.hdr.flag[0]==0x68U) + rdsvt.dsvt.hdr.flag[0] -= 0x40U; /* A reflector will send to us its own RPT1 */ /* A repeater will send to us its own RPT1 */ - /* A dongleR will send to us our RPT1 */ + /* A dongler will send to us our RPT1 */ /* It is from a repeater-reflector, correct rpt1, rpt2 and re-compute pfcs */ int i; for (i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) && + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_ref_port) && ( - ((memcmp(readBuffer2 + 20, to_remote_g2[i].to_call, 7) == 0) && - (readBuffer2[27] == to_remote_g2[i].to_mod)) || - ((memcmp(readBuffer2 + 28, to_remote_g2[i].to_call, 7) == 0) && - (readBuffer2[35] == to_remote_g2[i].to_mod)) + (0==memcmp(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].to_call, 7) && rdsvt.dsvt.hdr.rpt1[7]==to_remote_g2[i].to_mod) || + (0==memcmp(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].to_call, 7) && rdsvt.dsvt.hdr.rpt2[7]==to_remote_g2[i].to_mod) )) { - memcpy(&readBuffer2[20], owner.c_str(), CALL_SIZE); - readBuffer2[27] = to_remote_g2[i].from_mod; - memcpy(&readBuffer2[36], "CQCQCQ ", 8); + memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); - memcpy(source_stn, to_remote_g2[i].to_call, 8); + memcpy(source_stn, to_remote_g2[i].to_call, CALL_SIZE); source_stn[7] = to_remote_g2[i].to_mod; break; @@ -2593,35 +2410,32 @@ void CQnetLink::Process() /* somebody's crazy idea of having a personal callsign in RPT2 */ /* we must set it to our gateway callsign */ - memcpy(&readBuffer2[28], owner.c_str(), CALL_SIZE); - readBuffer2[35] = 'G'; - calcPFCS(readBuffer2 + 2,56); + memcpy(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(rdsvt.dsvt.title, 56); /* At this point, all data have our RPT1 and RPT2 */ i = -1; - if (readBuffer2[27] == 'A') + if (rdsvt.dsvt.hdr.rpt1[7] == 'A') i = 0; - else if (readBuffer2[27] == 'B') + else if (rdsvt.dsvt.hdr.rpt1[7] == 'B') i = 1; - else if (readBuffer2[27] == 'C') + else if (rdsvt.dsvt.hdr.rpt1[7] == 'C') i = 2; /* are we sure that RPT1 is our system? */ - if ((memcmp(readBuffer2 + 20, owner.c_str(), CALL_SIZE-1) == 0) && (i >= 0)) { + if (0==memcmp(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE-1) && i>=0) { /* Last Heard */ - if (memcmp(old_sid[i].sid, readBuffer2 + 14, 2) != 0) { + if (old_sid[i].sid != rdsvt.dsvt.streamid) { if (qso_details) - printf("START from remote g2: streamID=%d,%d, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - readBuffer2[14],readBuffer2[15], - readBuffer2[17], readBuffer2[18], readBuffer2[19], - &readBuffer2[44], - &readBuffer2[52], &readBuffer2[36], - &readBuffer2[20], &readBuffer2[28], - recvlen2, inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", + rdsvt.dsvt.streamid, rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], + rdsvt.dsvt.hdr.mycall, rdsvt.dsvt.hdr.sfx, rdsvt.dsvt.hdr.urcall, rdsvt.dsvt.hdr.rpt1, rdsvt.dsvt.hdr.rpt2, + length, inet_ntoa(fromDst4.sin_addr), source_stn); // put user into tmp1 - memcpy(tmp1, readBuffer2 + 44, 8); + memcpy(tmp1, rdsvt.dsvt.hdr.mycall, 8); tmp1[8] = '\0'; // delete the user if exists @@ -2638,68 +2452,64 @@ void CQnetLink::Process() } // add user time(&tnow); - sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], readBuffer2[27]); + sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], rdsvt.dsvt.hdr.rpt1[7]); dt_lh_list[tmp2] = tmp1; - memcpy(old_sid[i].sid, readBuffer2 + 14, 2); + old_sid[i].sid = rdsvt.dsvt.streamid; } /* send the data to the local gateway/repeater */ - sendto(rptr_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + sendto(rptr_sock, rdsvt.dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } else - inbound->mod = readBuffer2[27]; + inbound->mod = rdsvt.dsvt.hdr.rpt1[7]; } - if ((to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - to_remote_g2[i].is_connected) { + if (to_remote_g2[i].toDst4.sin_addr.s_addr!=fromDst4.sin_addr.s_addr && to_remote_g2[i].is_connected) { if ( /*** (memcmp(readBuffer2 + 44, owner, 8) != 0) && ***/ /* block repeater announcements */ - (memcmp(readBuffer2 + 36, "CQCQCQ", 6) == 0) && /* CQ calls only */ - ((readBuffer2[17] == 0x00) || /* normal */ - (readBuffer2[17] == 0x08) || /* EMR */ - (readBuffer2[17] == 0x20) || /* BK */ - (readBuffer2[17] == 0x28)) && /* EMR + BK */ - (memcmp(readBuffer2 + 28, owner.c_str(), CALL_SIZE-1) == 0) && /* rpt2 must be us */ - (readBuffer2[35] == 'G')) { - to_remote_g2[i].in_streamid[0] = readBuffer2[14]; - to_remote_g2[i].in_streamid[1] = readBuffer2[15]; - - if ((to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) || - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - memcpy((char *)readBuffer2 + 20, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[27] = to_remote_g2[i].to_mod; - memcpy((char *)readBuffer2 + 28, to_remote_g2[i].to_call, CALL_SIZE); - readBuffer2[35] = 'G'; - calcPFCS(readBuffer2 + 2, 56); + 0==memcmp(rdsvt.dsvt.hdr.urcall, "CQCQCQ", 6) && /* CQ calls only */ + (rdsvt.dsvt.hdr.flag[0]==0x00 || /* normal */ + rdsvt.dsvt.hdr.flag[0]==0x08 || /* EMR */ + rdsvt.dsvt.hdr.flag[0]==0x20 || /* BK */ + rdsvt.dsvt.hdr.flag[7]==0x28) && /* EMR + BK */ + 0==memcmp(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE-1) && /* rpt2 must be us */ + rdsvt.dsvt.hdr.rpt2[7] == 'G') { + to_remote_g2[i].in_streamid = rdsvt.dsvt.streamid; + + if (to_remote_g2[i].toDst4.sin_port==htons(rmt_xrf_port) || to_remote_g2[i].toDst4.sin_port== htons(rmt_ref_port)) { + memcpy(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].to_call, CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memcpy(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].to_call, CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + calcPFCS(rdsvt.dsvt.title, 56); if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; - - sendto(xrf_g2_sock, readBuffer2 + 2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; + sendto(xrf_g2_sock, rdsvt.dsvt.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { - memcpy(ref_2_dcs[i].mycall, readBuffer2 + 44, 8); - memcpy(ref_2_dcs[i].sfx, readBuffer2 + 52, 4); + memcpy(ref_2_dcs[i].mycall, rdsvt.dsvt.hdr.mycall, 8); + memcpy(ref_2_dcs[i].sfx, rdsvt.dsvt.hdr.sfx, 4); ref_2_dcs[i].dcs_rptr_seq = 0; } } } } } else if (found) { - if ((readBuffer2[16] & 0x40) != 0) { + if (rdsvt.dsvt.counter & 0x40U) { for (int i=0; i<3; i++) { - if (memcmp(old_sid[i].sid, readBuffer2 + 14, 2) == 0) { + if (old_sid[i].sid == rdsvt.dsvt.streamid) { if (qso_details) - printf("END from remote g2: streamID=%d,%d, %d bytes from IP=%s\n", readBuffer2[14], readBuffer2[15], recvlen2, inet_ntoa(fromDst4.sin_addr)); + printf("END from remote g2: streamID=%04x, %d bytes from IP=%s\n", rdsvt.dsvt.streamid, length, inet_ntoa(fromDst4.sin_addr)); - memset(old_sid[i].sid, 0x00, 2); + old_sid[i].sid = 0x0; break; } @@ -2707,32 +2517,29 @@ void CQnetLink::Process() } /* send the data to the local gateway/repeater */ - sendto(rptr_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + sendto(rptr_sock, rdsvt.dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); /* send the data to the donglers */ for (pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; if (fromDst4.sin_addr.s_addr != inbound->sin.sin_addr.s_addr) { - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } for (int i=0; i<3; i++) { - if ((to_remote_g2[i].is_connected) && - (to_remote_g2[i].toDst4.sin_addr.s_addr != fromDst4.sin_addr.s_addr) && - (memcmp(to_remote_g2[i].in_streamid, readBuffer2 + 14, 2) == 0)) { + if (to_remote_g2[i].is_connected && to_remote_g2[i].toDst4.sin_addr.s_addr!=fromDst4.sin_addr.s_addr && to_remote_g2[i].in_streamid==rdsvt.dsvt.streamid) { if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; - - sendto(xrf_g2_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; + sendto(xrf_g2_sock, rdsvt.dsvt.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) - sendto(ref_g2_sock, readBuffer2, 29, 0,(struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0,(struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { memset(dcs_buf, 0x00, 600); dcs_buf[0] = dcs_buf[1] = dcs_buf[2] = '0'; dcs_buf[3] = '1'; - dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x00; + dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x0; memcpy(dcs_buf + 7, to_remote_g2[i].to_call, 8); dcs_buf[14] = to_remote_g2[i].to_mod; memcpy(dcs_buf + 15, owner.c_str(), CALL_SIZE); @@ -2740,10 +2547,10 @@ void CQnetLink::Process() memcpy(dcs_buf + 23, "CQCQCQ ", 8); memcpy(dcs_buf + 31, ref_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, ref_2_dcs[i].sfx, 4); - dcs_buf[43] = readBuffer2[14]; /* streamid0 */ - dcs_buf[44] = readBuffer2[15]; /* streamid1 */ - dcs_buf[45] = readBuffer2[16]; /* cycle sequence */ - memcpy(dcs_buf + 46, readBuffer2 + 17, 12); + dcs_buf[43] = buf[14]; /* streamid0 */ + dcs_buf[44] = buf[15]; /* streamid1 */ + dcs_buf[45] = buf[16]; /* cycle sequence */ + memcpy(dcs_buf + 46, rdsvt.dsvt.vasd.voice, 12); dcs_buf[58] = (ref_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; dcs_buf[59] = (ref_2_dcs[i].dcs_rptr_seq >> 8) & 0xff; @@ -2757,9 +2564,8 @@ void CQnetLink::Process() sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - if ((readBuffer2[16] & 0x40) != 0) { - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + if (rdsvt.dsvt.counter & 0x40) { + to_remote_g2[i].in_streamid = 0x0; } break; } @@ -2771,25 +2577,22 @@ void CQnetLink::Process() if (FD_ISSET(dcs_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen2 = recvfrom(dcs_g2_sock, (char *)dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); + int length = recvfrom(dcs_g2_sock, dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); - strncpy(ip, inet_ntoa(fromDst4.sin_addr),IP_SIZE); + strncpy(ip, inet_ntoa(fromDst4.sin_addr), IP_SIZE); ip[IP_SIZE] = '\0'; /* header, audio */ - if ((dcs_buf[0] == '0') && (dcs_buf[1] == '0') && - (dcs_buf[2] == '0') && (dcs_buf[3] == '1')) { - if (recvlen2 == 100) { + if (dcs_buf[0]=='0' && dcs_buf[1]=='0' && dcs_buf[2]=='0' && dcs_buf[3]=='1') { + if (length == 100) { memset(source_stn, ' ', 9); source_stn[8] = '\0'; /* find out our local module */ int i; for (i=0; i<3; i++) { - if ((to_remote_g2[i].is_connected) && - (fromDst4.sin_addr.s_addr = to_remote_g2[i].toDst4.sin_addr.s_addr) && - (memcmp(dcs_buf + 7, to_remote_g2[i].to_call, 7) == 0) && - (to_remote_g2[i].to_mod == dcs_buf[14])) { + if (to_remote_g2[i].is_connected && fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && + 0==memcmp(dcs_buf + 7, to_remote_g2[i].to_call, 7) && to_remote_g2[i].to_mod==dcs_buf[14]) { memcpy(source_stn, to_remote_g2[i].to_call, 8); source_stn[7] = to_remote_g2[i].to_mod; break; @@ -2799,22 +2602,19 @@ void CQnetLink::Process() /* Is it our local module */ if (i < 3) { /* Last Heard */ - if (memcmp(old_sid[i].sid, dcs_buf + 43, 2) != 0) { + if (memcmp(&old_sid[i].sid, dcs_buf + 43, 2)) { if (qso_details) - printf("START from dcs: streamID=%d,%d, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - dcs_buf[43],dcs_buf[44], - &dcs_buf[31], - &dcs_buf[39], &dcs_buf[23], - &dcs_buf[7], &dcs_buf[15], - recvlen2,inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from dcs: streamID=%02x%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", + dcs_buf[44],dcs_buf[43], &dcs_buf[31], &dcs_buf[39], &dcs_buf[23], &dcs_buf[7], &dcs_buf[15], + length, inet_ntoa(fromDst4.sin_addr), source_stn); // put user into tmp1 memcpy(tmp1, dcs_buf + 31, 8); tmp1[8] = '\0'; // delete the user if exists - for (auto dt_lh_pos = dt_lh_list.begin(); dt_lh_pos != dt_lh_list.end(); dt_lh_pos++) { - if (strcmp((char *)dt_lh_pos->second.c_str(), tmp1) == 0) { + for (auto dt_lh_pos=dt_lh_list.begin(); dt_lh_pos!=dt_lh_list.end(); dt_lh_pos++) { + if (strcmp(dt_lh_pos->second.c_str(), tmp1) == 0) { dt_lh_list.erase(dt_lh_pos); break; } @@ -2829,118 +2629,106 @@ void CQnetLink::Process() sprintf(tmp2, "%ld=r%.6s%c%c", tnow, source_stn, source_stn[7], to_remote_g2[i].from_mod); dt_lh_list[tmp2] = tmp1; - memcpy(old_sid[i].sid, dcs_buf + 43, 2); + memcpy(&old_sid[i].sid, dcs_buf + 43, 2); } to_remote_g2[i].countdown = TIMEOUT; /* new stream ? */ - if ((to_remote_g2[i].in_streamid[0] != dcs_buf[43]) || - (to_remote_g2[i].in_streamid[1] != dcs_buf[44])) { - to_remote_g2[i].in_streamid[0] = dcs_buf[43]; - to_remote_g2[i].in_streamid[1] = dcs_buf[44]; + if (memcmp(&to_remote_g2[i].in_streamid, dcs_buf+43, 2)) { + memcpy(&to_remote_g2[i].in_streamid, dcs_buf+43, 2); dcs_seq[i] = 0xff; /* generate our header */ - - readBuffer2[0] = (unsigned char)(58 & 0xFF); - readBuffer2[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x10; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = 0x20; - readBuffer2[11] = 0x00; - readBuffer2[12] = 0x01; + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + memcpy(rdsvt.dsvt.title + 2, "DSVT", 4); + rdsvt.dsvt.config = 0x10; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; + rdsvt.dsvt.id = 0x20; + rdsvt.dsvt.flagb[0] = 0x00; + rdsvt.dsvt.flagb[1] = 0x01; if (to_remote_g2[i].from_mod == 'A') - readBuffer2[13] = 0x03; + rdsvt.dsvt.flagb[2] = 0x03; else if (to_remote_g2[i].from_mod == 'B') - readBuffer2[13] = 0x01; + rdsvt.dsvt.flagb[2] = 0x01; else - readBuffer2[13] = 0x02; - readBuffer2[14] = dcs_buf[43]; - readBuffer2[15] = dcs_buf[44]; - readBuffer2[16] = 0x80; - readBuffer2[17] = readBuffer2[18] = readBuffer2[19] = 0x00; - memcpy(readBuffer2 + 20, owner.c_str(), CALL_SIZE); - readBuffer2[27] = to_remote_g2[i].from_mod; - memcpy(readBuffer2 + 28, owner.c_str(), CALL_SIZE); - readBuffer2[35] = 'G'; - memcpy(readBuffer2 + 36, "CQCQCQ ", 8); - memcpy(readBuffer2 + 44, dcs_buf + 31, 8); - memcpy(readBuffer2 + 52, dcs_buf + 39, 4); - calcPFCS(readBuffer2 + 2, 56); + rdsvt.dsvt.flagb[2] = 0x02; + memcpy(&rdsvt.dsvt.streamid, dcs_buf+43, 2); + rdsvt.dsvt.counter = 0x80; + rdsvt.dsvt.hdr.flag[0] = rdsvt.dsvt.hdr.flag[1] = rdsvt.dsvt.hdr.flag[2] = 0x00; + memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; + memcpy(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", 8); + memcpy(rdsvt.dsvt.hdr.mycall, dcs_buf + 31, 8); + memcpy(rdsvt.dsvt.hdr.sfx, dcs_buf + 39, 4); + calcPFCS(rdsvt.dsvt.title, 56); /* send the header to the local gateway/repeater */ for (int j=0; j<5; j++) - sendto(rptr_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + sendto(rptr_sock, rdsvt.dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; for (int j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } - if ((to_remote_g2[i].in_streamid[0] == dcs_buf[43]) && - (to_remote_g2[i].in_streamid[1] == dcs_buf[44]) && - (dcs_seq[i] != dcs_buf[45])) { + if (0==memcmp(&to_remote_g2[i].in_streamid, dcs_buf+43, 2) && dcs_seq[i]!=dcs_buf[45]) { dcs_seq[i] = dcs_buf[45]; - - readBuffer2[0] = (unsigned char)(29 & 0xFF); - readBuffer2[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x20; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = 0x20; - readBuffer2[11] = 0x00; - readBuffer2[12] = 0x01; + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x20; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; + rdsvt.dsvt.id = 0x20; + rdsvt.dsvt.flagb[0] = 0x00; + rdsvt.dsvt.flagb[1] = 0x01; if (to_remote_g2[i].from_mod == 'A') - readBuffer2[13] = 0x03; + rdsvt.dsvt.flagb[2] = 0x03; else if (to_remote_g2[i].from_mod == 'B') - readBuffer2[13] = 0x01; + rdsvt.dsvt.flagb[2] = 0x01; else - readBuffer2[13] = 0x02; - readBuffer2[14] = dcs_buf[43]; - readBuffer2[15] = dcs_buf[44]; - readBuffer2[16] = dcs_buf[45]; - memcpy(readBuffer2 + 17, dcs_buf + 46, 12); + rdsvt.dsvt.flagb[2] = 0x02; + memcpy(&rdsvt.dsvt.streamid, dcs_buf+43, 2); + rdsvt.dsvt.counter = dcs_buf[45]; + memcpy(rdsvt.dsvt.vasd.voice, dcs_buf+46, 12); /* send the data to the local gateway/repeater */ - sendto(rptr_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + sendto(rptr_sock, rdsvt.dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } if ((dcs_buf[45] & 0x40) != 0) { - memset(old_sid[i].sid, 0x00, 2); + old_sid[i].sid = 0x0; if (qso_details) - printf("END from dcs: streamID=%d,%d, %d bytes from IP=%s\n", dcs_buf[43],dcs_buf[44], recvlen2, inet_ntoa(fromDst4.sin_addr)); + printf("END from dcs: streamID=%04x, %d bytes from IP=%s\n", rdsvt.dsvt.streamid, length, inet_ntoa(fromDst4.sin_addr)); - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; dcs_seq[i] = 0xff; } } } } - } else if ((dcs_buf[0] == 'E') && (dcs_buf[1] == 'E') && - (dcs_buf[2] == 'E') && (dcs_buf[3] == 'E')) + } else if (dcs_buf[0]=='E' && dcs_buf[1]=='E' && dcs_buf[2]=='E' && dcs_buf[3]=='E') ; - else if (recvlen2 == 35) + else if (length == 35) ; /* is this a keepalive 22 bytes */ - else if (recvlen2 == 22) { + else if (length == 22) { int i = -1; if (dcs_buf[17] == 'A') i = 0; @@ -2951,12 +2739,10 @@ void CQnetLink::Process() /* It is one of our valid repeaters */ // DG1HT from owner 8 to 7 - if ((i >= 0) && (memcmp(dcs_buf + 9, owner.c_str(), CALL_SIZE-1) == 0)) { + if (i>=0 && 0==memcmp(dcs_buf + 9, owner.c_str(), CALL_SIZE-1)) { /* is that the remote system that we asked to connect to? */ - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) && - (memcmp(to_remote_g2[i].to_call, dcs_buf, 7) == 0) && - (to_remote_g2[i].to_mod == dcs_buf[7])) { + if (fromDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[i].toDst4.sin_port==htons(rmt_dcs_port) && + 0==memcmp(to_remote_g2[i].to_call, dcs_buf, 7) && to_remote_g2[i].to_mod==dcs_buf[7]) { if (!to_remote_g2[i].is_connected) { tracing[i].last_time = time(NULL); @@ -2968,16 +2754,13 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } to_remote_g2[i].countdown = TIMEOUT; } } - } else if (recvlen2 == 14) { /* is this a reply to our link/unlink request: 14 bytes */ + } else if (length == 14) { /* is this a reply to our link/unlink request: 14 bytes */ int i = -1; if (dcs_buf[8] == 'A') i = 0; @@ -3023,12 +2806,10 @@ void CQnetLink::Process() to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } @@ -3040,53 +2821,46 @@ void CQnetLink::Process() if (FD_ISSET(rptr_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen = recvfrom(rptr_sock, (char *)readBuffer, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); + SDSTR dstr; + int length = recvfrom(rptr_sock, dstr.pkt_id, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); - if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && - (readBuffer[6] == 0x73) && - (readBuffer[7] == 0x12) && - ((memcmp(readBuffer,"DSTR", 4) == 0) || (memcmp(readBuffer,"CCS_", 4) == 0)) && - (readBuffer[10] == 0x20) && - (readBuffer[8] == 0x00) && - ((readBuffer[9] == 0x30) || (readBuffer[9] == 0x13) || (readBuffer[9] == 0x16)) ) { + if ((length==58 || length==29 || length==32) && dstr.flag[0]==0x73 && dstr.flag[1] == 0x12 && dstr.flag[2] ==0x0 && + (0==memcmp(dstr.pkt_id,"DSTR", 4) || 0==memcmp(dstr.pkt_id,"CCS_", 4)) && dstr.vpkt.icm_id==0x20 && + (dstr.remaining==0x30 || dstr.remaining==0x13 || dstr.remaining==0x16)) { - if (recvlen == 58) { + if (length == 58) { if (qso_details) - printf("START from local g2: cntr=%02x %02x, streamID=%d,%d, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - readBuffer[4], readBuffer[5], - readBuffer[14], readBuffer[15], - readBuffer[17], readBuffer[18], readBuffer[19], - readBuffer + 44, readBuffer + 52, readBuffer + 36, - readBuffer + 28, readBuffer + 20, recvlen, inet_ntoa(fromRptr.sin_addr)); + printf("START from local g2: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", + dstr.counter, dstr.vpkt.streamid, dstr.vpkt.hdr.flag[0], dstr.vpkt.hdr.flag[1], dstr.vpkt.hdr.flag[2], + dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, length, inet_ntoa(fromRptr.sin_addr)); /* save mycall */ - memcpy(call, readBuffer + 44, 8); + memcpy(call, dstr.vpkt.hdr.my, 8); call[8] = '\0'; int i = -1; - if (readBuffer[35] == 'A') + if (dstr.vpkt.hdr.r1[7] == 'A') i = 0; - else if (readBuffer[35] == 'B') + else if (dstr.vpkt.hdr.r1[7] == 'B') i = 1; - else if (readBuffer[35] == 'C') + else if (dstr.vpkt.hdr.r1[7] == 'C') i = 2; if (i >= 0) { - memcpy(dtmf_mycall[i], readBuffer + 44, 8); + memcpy(dtmf_mycall[i], dstr.vpkt.hdr.my, 8); dtmf_mycall[i][8] = '\0'; new_group[i] = true; GPS_seen[i] = false; /* Last Heard */ - //put user into tmp1 - memcpy(tmp1, readBuffer + 44, 8); + memcpy(tmp1, dstr.vpkt.hdr.my, 8); tmp1[8] = '\0'; // delete the user if exists - for (auto dt_lh_pos = dt_lh_list.begin(); dt_lh_pos != dt_lh_list.end(); dt_lh_pos++) { - if (strcmp((char *)dt_lh_pos->second.c_str(), tmp1) == 0) { + for (auto dt_lh_pos=dt_lh_list.begin(); dt_lh_pos!=dt_lh_list.end(); dt_lh_pos++) { + if (strcmp(dt_lh_pos->second.c_str(), tmp1) == 0) { dt_lh_list.erase(dt_lh_pos); break; } @@ -3098,28 +2872,25 @@ void CQnetLink::Process() } /* add user */ time(&tnow); - if (memcmp(readBuffer,"CCS_", 4) == 0) - sprintf(tmp2, "%ld=r%.7s%c", tnow, "-->CCS ", readBuffer[35]); + if (0 == memcmp(dstr.pkt_id,"CCS_", 4)) + sprintf(tmp2, "%ld=r%.7s%c", tnow, "-->CCS ", dstr.vpkt.hdr.r1[7]); else - sprintf(tmp2, "%ld=l%.8s", tnow, readBuffer + 28); + sprintf(tmp2, "%ld=l%.8s", tnow, dstr.vpkt.hdr.r1); dt_lh_list[tmp2] = tmp1; - memcpy(readBuffer, "DSTR", 4); + memcpy(dstr.pkt_id, "DSTR", 4); - tracing[i].streamid[0] = readBuffer[14]; - tracing[i].streamid[1] = readBuffer[15]; + tracing[i].streamid = dstr.vpkt.streamid; tracing[i].last_time = time(NULL); } - if ((memcmp(readBuffer + 36, "CQCQCQ", 6) != 0) && (i >= 0)) { - if ((memcmp(readBuffer + 36, owner.c_str(), CALL_SIZE-1) != 0) && - (readBuffer[43] == 'L') && - (memcmp(readBuffer + 20, owner.c_str(), CALL_SIZE-1) == 0) && - (readBuffer[27] == 'G') && - ((readBuffer[17] == 0x00) || - (readBuffer[17] == 0x08) || - (readBuffer[17] == 0x20) || - (readBuffer[17] == 0x28))) { + if (memcmp(dstr.vpkt.hdr.ur, "CQCQCQ", 6) && i>=0) { + if (memcmp(dstr.vpkt.hdr.ur, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.ur[7] == 'L' && + 0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.r2[7] == 'G' && + (dstr.vpkt.hdr.flag[0]==0x00 || + dstr.vpkt.hdr.flag[0]==0x08 || + dstr.vpkt.hdr.flag[0]==0x20 || + dstr.vpkt.hdr.flag[0]==0x28)) { if ( // if there is a black list, is he in the blacklist? (link_blacklist.size() && link_blacklist.end()!=link_blacklist.find(call)) || @@ -3130,14 +2901,14 @@ void CQnetLink::Process() } else { char temp_repeater[CALL_SIZE + 1]; memset(temp_repeater, ' ', CALL_SIZE); - memcpy(temp_repeater, readBuffer + 36, CALL_SIZE - 2); + memcpy(temp_repeater, dstr.vpkt.hdr.ur, CALL_SIZE - 2); temp_repeater[CALL_SIZE] = '\0'; if ((to_remote_g2[i].to_call[0] == '\0') || /* not linked */ ((to_remote_g2[i].to_call[0] != '\0') && /* waiting for a link reply that may never arrive */ !to_remote_g2[i].is_connected)) - g2link(readBuffer[35], temp_repeater, readBuffer[42]); + g2link(dstr.vpkt.hdr.r1[7], temp_repeater, dstr.vpkt.hdr.ur[6]); else if (to_remote_g2[i].is_connected) { strcpy(linked_remote_system, to_remote_g2[i].to_call); space_p = strchr(linked_remote_system, ' '); @@ -3147,7 +2918,7 @@ void CQnetLink::Process() audio_notify(notify_msg); } } - } else if ((readBuffer[43] == 'U') && (readBuffer[36] == ' ')) { + } else if (0==memcmp(dstr.vpkt.hdr.ur, " U", CALL_SIZE)) { if ( // if there is a black list, is he in the blacklist? (link_blacklist.size() && link_blacklist.end()!=link_blacklist.find(call)) || @@ -3162,10 +2933,9 @@ void CQnetLink::Process() int j; for (j=0; j<3; j++) { if (j != i) { - if ((to_remote_g2[j].toDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[j].toDst4.sin_port == htons(rmt_ref_port))) { - printf("Info: Local %c is also linked to %s (different module) %c\n", - to_remote_g2[j].from_mod, + if (to_remote_g2[j].toDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && + to_remote_g2[j].toDst4.sin_port==htons(rmt_ref_port)) { + printf("Info: Local %c is also linked to %s (different module) %c\n", to_remote_g2[j].from_mod, to_remote_g2[j].to_call, to_remote_g2[j].to_mod); break; } @@ -3207,20 +2977,18 @@ void CQnetLink::Process() /* now zero out this entry */ to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = 0x0; print_status_file(); } else { - sprintf(notify_msg, "%c_already_unlinked.dat_UNLINKED", readBuffer[35]); + sprintf(notify_msg, "%c_already_unlinked.dat_UNLINKED", dstr.vpkt.hdr.r1[7]); audio_notify(notify_msg); } } - } else if ((readBuffer[43] == 'I') && (readBuffer[36] == ' ')) { + } else if (0 == memcmp(dstr.vpkt.hdr.ur, " I", CALL_SIZE)) { if (to_remote_g2[i].is_connected) { strcpy(linked_remote_system, to_remote_g2[i].to_call); space_p = strchr(linked_remote_system, ' '); @@ -3229,163 +2997,145 @@ void CQnetLink::Process() sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } else { - sprintf(notify_msg, "%c_id.dat_%s_NOT_LINKED", readBuffer[35], owner.c_str()); + sprintf(notify_msg, "%c_id.dat_%s_NOT_LINKED", dstr.vpkt.hdr.r1[7], owner.c_str()); audio_notify(notify_msg); } - } else if ((readBuffer[43] == 'X') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can execute scripts - if (readBuffer[42] != ' ') { + } else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[7]=='X' && admin.find(call)!=admin.end()) { // only ADMIN can execute scripts + if (dstr.vpkt.hdr.ur[6] != ' ') { memset(system_cmd, '\0', sizeof(system_cmd)); - snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), readBuffer[42], call, readBuffer[35]); + snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), dstr.vpkt.hdr.ur[6], call, dstr.vpkt.hdr.r1[7]); printf("Executing %s\n", system_cmd); system(system_cmd); } - } else if ((readBuffer[42] == 'D') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can block dongle users - if (readBuffer[43] == '1') { + } else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[6]=='D' && admin.find(call)!=admin.end()) { // only ADMIN can block dongle users + if (dstr.vpkt.hdr.ur[7] == '1') { max_dongles = saved_max_dongles; printf("Dongle connections are now allowed\n"); - } else if (readBuffer[43] == '0') { + } else if (dstr.vpkt.hdr.ur[7] == '0') { inbound_list.clear(); max_dongles = 0; printf("Dongle connections are now disallowed\n"); } - } else if ((readBuffer[43] == 'F') && (readBuffer[36] == ' ') && (admin.find(call) != admin.end())) { // only ADMIN can reload gwys.txt + } else if (0==memcmp(dstr.vpkt.hdr.ur, " F", CALL_SIZE) && admin.find(call)!=admin.end()) { // only ADMIN can reload gwys.txt gwy_list.clear(); load_gwys(gwys); } } /* send data to the donglers */ + SREFDSVT rdsvt; if (inbound_list.size() > 0) { - readBuffer2[0] = (unsigned char)(58 & 0xFF); - readBuffer2[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x10; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 44); - memcpy(readBuffer2 + 20, owner.c_str(), CALL_SIZE); - readBuffer2[27] = readBuffer[35]; - memcpy(readBuffer2 + 28, owner.c_str(), CALL_SIZE); - readBuffer2[35] = 'G'; - memcpy(&readBuffer2[36], "CQCQCQ ", 8); + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x10; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; + rdsvt.dsvt.id = dstr.vpkt.icm_id; + rdsvt.dsvt.flagb[0] = dstr.vpkt.dst_rptr_id; + rdsvt.dsvt.flagb[1] = dstr.vpkt.snd_rptr_id; + rdsvt.dsvt.flagb[2] = dstr.vpkt.snd_term_id; + memcpy(&rdsvt.dsvt.streamid, &dstr.vpkt.streamid, 44); + memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt1[7] = dstr.vpkt.hdr.r1[7]; + memcpy(rdsvt.dsvt.hdr.rpt2, owner.c_str(), CALL_SIZE); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", 8); + calcPFCS(rdsvt.dsvt.title, 56); for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; for (int j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } if (i >= 0) { /* do we have to broadcast ? */ /* make sure the source is linked to xrf */ - if ((to_remote_g2[i].is_connected) && - (memcmp(to_remote_g2[i].to_call, "XRF", 3) == 0) && - /* only CQCQCQ */ - (memcmp(readBuffer + 20, owner.c_str(), CALL_SIZE-1) == 0) && - (memcmp(readBuffer + 36, "CQCQCQ", 6) == 0) && - (readBuffer[27] == 'G')) { + if (to_remote_g2[i].is_connected && 0==memcmp(to_remote_g2[i].to_call, "XRF", 3) && + 0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.r2[7]=='G' && + 0==memcmp(dstr.vpkt.hdr.ur, "CQCQCQ", 6)) { brd_from_rptr_idx = 0; - streamid_raw = (readBuffer[14] * 256U) + readBuffer[15]; + streamid_raw = ntohs(dstr.vpkt.streamid); for (int j=0; j<3; j++) { - if ((j != i) && - (to_remote_g2[j].is_connected) && - (memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) == 0) && - (to_remote_g2[j].to_mod == to_remote_g2[i].to_mod) && - (to_remote_g2[j].to_mod != 'E')) { - memcpy(fromrptr_torptr_brd, "DSVT", 4); - fromrptr_torptr_brd[4] = 0x10; - fromrptr_torptr_brd[5] = 0x00; - fromrptr_torptr_brd[6] = 0x00; - fromrptr_torptr_brd[7] = 0x00; - fromrptr_torptr_brd[8] = readBuffer[10]; - fromrptr_torptr_brd[9] = readBuffer[11]; - fromrptr_torptr_brd[10] = readBuffer[12]; - fromrptr_torptr_brd[11] = readBuffer[13]; - memcpy(fromrptr_torptr_brd + 12, readBuffer + 14, 44); - - streamid_raw ++; - if (streamid_raw == 0) - streamid_raw ++; - fromrptr_torptr_brd[12] = streamid_raw / 256U; - fromrptr_torptr_brd[13] = streamid_raw % 256U; - - memcpy(fromrptr_torptr_brd + 18, owner.c_str(), CALL_SIZE); - fromrptr_torptr_brd[25] = to_remote_g2[j].from_mod; - memcpy(fromrptr_torptr_brd + 26, owner.c_str(), CALL_SIZE); - fromrptr_torptr_brd[33] = 'G'; - - memcpy(fromrptr_torptr_brd + 34, "CQCQCQ ", 8); - - calcPFCS(fromrptr_torptr_brd, 56); - - sendto(xrf_g2_sock, fromrptr_torptr_brd, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); - - brd_from_rptr.from_rptr_streamid[0] = readBuffer[14]; - brd_from_rptr.from_rptr_streamid[1] = readBuffer[15]; - brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx][0] = fromrptr_torptr_brd[12]; - brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx][1] = fromrptr_torptr_brd[13]; + if (j!=i && to_remote_g2[j].is_connected && + 0==memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) && + to_remote_g2[j].to_mod==to_remote_g2[i].to_mod && + to_remote_g2[j].to_mod!='E') { + memcpy(fromrptr_torptr_brd.title, "DSVT", 4); + fromrptr_torptr_brd.config = 0x10; + fromrptr_torptr_brd.flaga[0] = fromrptr_torptr_brd.flaga[1] = fromrptr_torptr_brd.flaga[2] = 0x0; + fromrptr_torptr_brd.id = dstr.vpkt.icm_id; + fromrptr_torptr_brd.flagb[0] = dstr.vpkt.dst_rptr_id; + fromrptr_torptr_brd.flagb[1] = dstr.vpkt.snd_rptr_id; + fromrptr_torptr_brd.flagb[2] = dstr.vpkt.snd_term_id; + memcpy(&fromrptr_torptr_brd.streamid, &dstr.vpkt.streamid, 44); + + if (++streamid_raw == 0) + streamid_raw++; + fromrptr_torptr_brd.streamid = htons(streamid_raw); + + memcpy(fromrptr_torptr_brd.hdr.rpt1, owner.c_str(), CALL_SIZE); + fromrptr_torptr_brd.hdr.rpt1[7] = to_remote_g2[j].from_mod; + memcpy(fromrptr_torptr_brd.hdr.rpt2, owner.c_str(), CALL_SIZE); + fromrptr_torptr_brd.hdr.rpt2[7] = 'G'; + + memcpy(fromrptr_torptr_brd.hdr.urcall, "CQCQCQ ", CALL_SIZE); + + calcPFCS(fromrptr_torptr_brd.title, 56); + + sendto(xrf_g2_sock, fromrptr_torptr_brd.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + + brd_from_rptr.from_rptr_streamid = dstr.vpkt.streamid; + brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx] = fromrptr_torptr_brd.streamid; brd_from_rptr_idx ++; } } } if (to_remote_g2[i].is_connected) { - if ((memcmp(readBuffer + 20, owner.c_str(), 7) == 0) && - (memcmp(readBuffer + 36, "CQCQCQ", 6) == 0) && - (readBuffer[27] == 'G')) { - to_remote_g2[i].out_streamid[0] = readBuffer[14]; - to_remote_g2[i].out_streamid[1] = readBuffer[15]; - - if ((to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) || - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - readBuffer2[0] = (unsigned char)(58 & 0xFF); - readBuffer2[1] = (unsigned char)(58 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x10; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 44); - memset(readBuffer2 + 20, ' ', CALL_SIZE); - memcpy(readBuffer2 + 20, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer2[27] = to_remote_g2[i].to_mod; - memset(readBuffer2 + 28, ' ', CALL_SIZE); - memcpy(readBuffer2 + 28, to_remote_g2[i].to_call, - strlen(to_remote_g2[i].to_call)); - readBuffer2[35] = 'G'; - memcpy(&readBuffer2[36], "CQCQCQ ", 8); - - calcPFCS(readBuffer2 + 2,56); + if (0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), 7) && 0==memcmp(dstr.vpkt.hdr.ur, "CQCQCQ", 6) && dstr.vpkt.hdr.r2[7] == 'G') { + to_remote_g2[i].out_streamid = dstr.vpkt.streamid; + + if (to_remote_g2[i].toDst4.sin_port==htons(rmt_xrf_port) || to_remote_g2[i].toDst4.sin_port== htons(rmt_ref_port)) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(58 & 0xFF); + rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x10; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x0; + rdsvt.dsvt.id = dstr.vpkt.icm_id; + rdsvt.dsvt.flagb[0] = dstr.vpkt.dst_rptr_id; + rdsvt.dsvt.flagb[1] = dstr.vpkt.snd_rptr_id; + rdsvt.dsvt.flagb[2] = dstr.vpkt.snd_term_id; + memcpy(&rdsvt.dsvt.streamid, &dstr.vpkt.streamid, 44); + memset(rdsvt.dsvt.hdr.rpt1, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt1, to_remote_g2[i].to_call, strlen(to_remote_g2[i].to_call)); + rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].to_mod; + memset(rdsvt.dsvt.hdr.rpt2, ' ', CALL_SIZE); + memcpy(rdsvt.dsvt.hdr.rpt2, to_remote_g2[i].to_call, strlen(to_remote_g2[i].to_call)); + rdsvt.dsvt.hdr.rpt2[7] = 'G'; + memcpy(rdsvt.dsvt.hdr.urcall, "CQCQCQ ", CALL_SIZE); + calcPFCS(rdsvt.dsvt.title, 56); if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; - + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; + calcPFCS(rdsvt.dsvt.title, 56); for (int j=0; j<5; j++) - sendto(xrf_g2_sock, readBuffer2+2, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(xrf_g2_sock, rdsvt.dsvt.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else { for (int j=0; j<5; j++) - sendto(ref_g2_sock, readBuffer2, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 58, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { - memcpy(rptr_2_dcs[i].mycall, readBuffer + 44, 8); - memcpy(rptr_2_dcs[i].sfx, readBuffer + 52, 4); + memcpy(rptr_2_dcs[i].mycall, dstr.vpkt.hdr.my, CALL_SIZE); + memcpy(rptr_2_dcs[i].sfx, dstr.vpkt.hdr.nm, 4); rptr_2_dcs[i].dcs_rptr_seq = 0; } } @@ -3393,106 +3143,95 @@ void CQnetLink::Process() } } else { if (inbound_list.size() > 0) { - readBuffer2[0] = (unsigned char)(29 & 0xFF); - readBuffer2[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x20; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 3); - if (recvlen == 29) - memcpy(readBuffer2 + 17, readBuffer + 17, 12); + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x20; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x0; + rdsvt.dsvt.id = dstr.vpkt.icm_id; + rdsvt.dsvt.flagb[0] = dstr.vpkt.dst_rptr_id; + rdsvt.dsvt.flagb[1] = dstr.vpkt.snd_rptr_id; + rdsvt.dsvt.flagb[2] = dstr.vpkt.snd_term_id; + memcpy(&rdsvt.dsvt.streamid, &dstr.vpkt.streamid, 3); + if (length == 29) + memcpy(rdsvt.dsvt.vasd.voice, dstr.vpkt.vasd.voice, 12); else - memcpy(readBuffer2 + 17, readBuffer + 20, 12); + memcpy(rdsvt.dsvt.vasd.voice, dstr.vpkt.vasd1.voice, 12); for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, (struct sockaddr *)&(inbound->sin), sizeof(struct sockaddr_in)); } } for (int i=0; i<3; i++) { - if ((to_remote_g2[i].is_connected) && (memcmp(to_remote_g2[i].out_streamid, readBuffer + 14, 2) == 0)) { + if (to_remote_g2[i].is_connected && to_remote_g2[i].out_streamid==dstr.vpkt.streamid) { /* check for broadcast */ - if (memcmp(brd_from_rptr.from_rptr_streamid, readBuffer + 14, 2) == 0) { - memcpy(fromrptr_torptr_brd, "DSVT", 4); - fromrptr_torptr_brd[4] = 0x10; - fromrptr_torptr_brd[5] = 0x00; - fromrptr_torptr_brd[6] = 0x00; - fromrptr_torptr_brd[7] = 0x00; - fromrptr_torptr_brd[8] = readBuffer[10]; - fromrptr_torptr_brd[9] = readBuffer[11]; - fromrptr_torptr_brd[10] = readBuffer[12]; - fromrptr_torptr_brd[11] = readBuffer[13]; - memcpy(fromrptr_torptr_brd + 12, readBuffer + 14, 3); - - if (recvlen == 29) - memcpy(fromrptr_torptr_brd + 15, readBuffer + 17, 12); + if (brd_from_rptr.from_rptr_streamid == dstr.vpkt.streamid) { + memcpy(fromrptr_torptr_brd.title, "DSVT", 4); + fromrptr_torptr_brd.config = 0x10; + fromrptr_torptr_brd.flaga[0] = fromrptr_torptr_brd.flaga[1] = fromrptr_torptr_brd.flaga[2] = 0x0; + fromrptr_torptr_brd.id = dstr.vpkt.icm_id; + fromrptr_torptr_brd.flagb[0] = dstr.vpkt.dst_rptr_id; + fromrptr_torptr_brd.flagb[1] = dstr.vpkt.snd_rptr_id; + fromrptr_torptr_brd.flagb[2] = dstr.vpkt.snd_term_id; + memcpy(&fromrptr_torptr_brd.streamid, &dstr.vpkt.streamid, 3); + + if (length == 29) + memcpy(fromrptr_torptr_brd.vasd.voice, dstr.vpkt.vasd.voice, 12); else - memcpy(fromrptr_torptr_brd + 15, readBuffer + 20, 12); + memcpy(fromrptr_torptr_brd.vasd.voice, dstr.vpkt.vasd1.voice, 12); - if ((brd_from_rptr.to_rptr_streamid[0][0] != 0x00) || - (brd_from_rptr.to_rptr_streamid[0][1] != 0x00)) { - fromrptr_torptr_brd[12] = brd_from_rptr.to_rptr_streamid[0][0]; - fromrptr_torptr_brd[13] = brd_from_rptr.to_rptr_streamid[0][1]; - sendto(xrf_g2_sock, fromrptr_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + if (brd_from_rptr.to_rptr_streamid[0]) { + fromrptr_torptr_brd.streamid = brd_from_rptr.to_rptr_streamid[0]; + sendto(xrf_g2_sock, fromrptr_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); } - if ((brd_from_rptr.to_rptr_streamid[1][0] != 0x00) || - (brd_from_rptr.to_rptr_streamid[1][1] != 0x00)) { - fromrptr_torptr_brd[12] = brd_from_rptr.to_rptr_streamid[1][0]; - fromrptr_torptr_brd[13] = brd_from_rptr.to_rptr_streamid[1][1]; - sendto(xrf_g2_sock, fromrptr_torptr_brd, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + if (brd_from_rptr.to_rptr_streamid[1]) { + fromrptr_torptr_brd.streamid = brd_from_rptr.to_rptr_streamid[1]; + sendto(xrf_g2_sock, fromrptr_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); } - if ((readBuffer[16] & 0x40) != 0) { - brd_from_rptr.from_rptr_streamid[0] = brd_from_rptr.from_rptr_streamid[1] = 0x00; - brd_from_rptr.to_rptr_streamid[0][0] = brd_from_rptr.to_rptr_streamid[0][1] = 0x00; - brd_from_rptr.to_rptr_streamid[1][0] = brd_from_rptr.to_rptr_streamid[1][1] = 0x00; + if (dstr.vpkt.ctrl & 0x40U) { + brd_from_rptr.from_rptr_streamid = brd_from_rptr.to_rptr_streamid[0] = brd_from_rptr.to_rptr_streamid[1] = 0x0; brd_from_rptr_idx = 0; } } - if ((to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) || - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { - readBuffer2[0] = (unsigned char)(29 & 0xFF); - readBuffer2[1] = (unsigned char)(29 >> 8 & 0x1F); - readBuffer2[1] = (unsigned char)(readBuffer2[1] | 0xFFFFFF80); - - memcpy(readBuffer2 + 2, "DSVT", 4); - readBuffer2[6] = 0x20; - readBuffer2[7] = 0x00; - readBuffer2[8] = 0x00; - readBuffer2[9] = 0x00; - readBuffer2[10] = readBuffer[10]; - readBuffer2[11] = readBuffer[11]; - readBuffer2[12] = readBuffer[12]; - readBuffer2[13] = readBuffer[13]; - memcpy(readBuffer2 + 14, readBuffer + 14, 3); - if (recvlen == 29) - memcpy(readBuffer2 + 17, readBuffer + 17, 12); + if (to_remote_g2[i].toDst4.sin_port==htons(rmt_xrf_port) || to_remote_g2[i].toDst4.sin_port== htons(rmt_ref_port)) { + SREFDSVT rdsvt; + rdsvt.head[0] = (unsigned char)(29 & 0xFF); + rdsvt.head[1] = (unsigned char)(29 >> 8 & 0x1F); + rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); + + memcpy(rdsvt.dsvt.title, "DSVT", 4); + rdsvt.dsvt.config = 0x20; + rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; + rdsvt.dsvt.id = dstr.vpkt.icm_id; + rdsvt.dsvt.flagb[0] = dstr.vpkt.dst_rptr_id; + rdsvt.dsvt.flagb[1] = dstr.vpkt.snd_rptr_id; + rdsvt.dsvt.flagb[2] = dstr.vpkt.snd_term_id; + memcpy(&rdsvt.dsvt.streamid, &dstr.vpkt.streamid, 3); + if (length == 29) + memcpy(rdsvt.dsvt.vasd.voice, dstr.vpkt.vasd.voice, 12); else - memcpy(readBuffer2 + 17, readBuffer + 20, 12); + memcpy(rdsvt.dsvt.vasd.voice, dstr.vpkt.vasd1.voice, 12); if (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port)) { /* inform XRF about the source */ - readBuffer2[13] = to_remote_g2[i].from_mod; + rdsvt.dsvt.flagb[2] = to_remote_g2[i].from_mod; - sendto(xrf_g2_sock, readBuffer2+2, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(xrf_g2_sock, rdsvt.dsvt.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port)) - sendto(ref_g2_sock, readBuffer2, 29, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + sendto(ref_g2_sock, rdsvt.head, 29, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); } else if (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) { - memset(dcs_buf, 0x00, 600); + memset(dcs_buf, 0x0, 600); dcs_buf[0] = dcs_buf[1] = dcs_buf[2] = '0'; dcs_buf[3] = '1'; - dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x00; + dcs_buf[4] = dcs_buf[5] = dcs_buf[6] = 0x0; memcpy(dcs_buf + 7, to_remote_g2[i].to_call, 8); dcs_buf[14] = to_remote_g2[i].to_mod; memcpy(dcs_buf + 15, owner.c_str(), CALL_SIZE); @@ -3500,16 +3239,15 @@ void CQnetLink::Process() memcpy(dcs_buf + 23, "CQCQCQ ", 8); memcpy(dcs_buf + 31, rptr_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, rptr_2_dcs[i].sfx, 4); - dcs_buf[43] = readBuffer[14]; /* streamid0 */ - dcs_buf[44] = readBuffer[15]; /* streamid1 */ - dcs_buf[45] = readBuffer[16]; /* cycle sequence */ - memcpy(dcs_buf + 46, readBuffer + 17, 12); + memcpy(dcs_buf + 43, &dstr.vpkt.streamid, 2); + dcs_buf[45] = dstr.vpkt.ctrl; /* cycle sequence */ + memcpy(dcs_buf + 46, dstr.vpkt.vasd.voice, 12); dcs_buf[58] = (rptr_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; dcs_buf[59] = (rptr_2_dcs[i].dcs_rptr_seq >> 8) & 0xff; dcs_buf[60] = (rptr_2_dcs[i].dcs_rptr_seq >> 16) & 0xff; - rptr_2_dcs[i].dcs_rptr_seq ++; + rptr_2_dcs[i].dcs_rptr_seq++; dcs_buf[61] = 0x01; dcs_buf[62] = 0x00; @@ -3517,24 +3255,21 @@ void CQnetLink::Process() sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - if ((readBuffer[16] & 0x40) != 0) { - to_remote_g2[i].out_streamid[0] = 0x00; - to_remote_g2[i].out_streamid[1] = 0x00; + if (dstr.vpkt.ctrl & 0x40U) { + to_remote_g2[i].out_streamid = 0x0; } break; } } for (int i=0; i<3; i++) { - if (memcmp(tracing[i].streamid, readBuffer + 14, 2) == 0) { + if (tracing[i].streamid == dstr.vpkt.streamid) { /* update the last time RF user talked */ tracing[i].last_time = time(NULL); - if ((readBuffer[16] & 0x40) != 0) { + if (dstr.vpkt.ctrl & 0x40U) { if (qso_details) - printf("END from local g2: cntr=%02x %02x, streamID=%d,%d, %d bytes\n", - readBuffer[4], readBuffer[5], - readBuffer[14],readBuffer[15],recvlen); + printf("END from local g2: cntr=%04x, streamID=%04x, %d bytes\n", dstr.counter, ntohs(dstr.vpkt.streamid), length); if (bool_rptr_ack) rptr_ack(i); @@ -3543,22 +3278,20 @@ void CQnetLink::Process() new_group[i] = true; GPS_seen[i] = false; - tracing[i].streamid[0] = 0x00; - tracing[i].streamid[1] = 0x00; + tracing[i].streamid = 0x0; } else { if (!GPS_seen[i]) { - if (recvlen == 29) - memcpy(tmp_txt, readBuffer + 26, 3); + if (length == 29) + memcpy(tmp_txt, dstr.vpkt.vasd.text, 3); else - memcpy(tmp_txt, readBuffer + 29, 3); + memcpy(tmp_txt, dstr.vpkt.vasd1.text, 3); - if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { + if (tmp_txt[0]!=0x55 || tmp_txt[1]!=0x2d || tmp_txt[2]!=0x16) { if (new_group[i]) { tmp_txt[0] = tmp_txt[0] ^ 0x70; header_type = tmp_txt[0] & 0xf0; - - if ((header_type == 0x50) || /* header */ - (header_type == 0xc0)) /* squelch */ + // header squelch + if (header_type== 0x50 || header_type==0xc0) new_group[i] = false; else if (header_type == 0x30) { /* GPS or GPS id or APRS */ GPS_seen[i] = true; @@ -3853,24 +3586,16 @@ bool CQnetLink::Init(const char *cfgfile) for (int i=0; i<3; i++) { to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].to_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].to_mod = to_remote_g2[i].from_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; - to_remote_g2[i].out_streamid[0] = 0x00; - to_remote_g2[i].out_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = to_remote_g2[i].out_streamid = 0x0; } - brd_from_xrf.xrf_streamid[0] = brd_from_xrf.xrf_streamid[1] = 0x00; - brd_from_xrf.rptr_streamid[0][0] = brd_from_xrf.rptr_streamid[0][1] = 0x00; - brd_from_xrf.rptr_streamid[1][0] = brd_from_xrf.rptr_streamid[1][1] = 0x00; + brd_from_xrf.xrf_streamid = brd_from_xrf.rptr_streamid[0] = brd_from_xrf.rptr_streamid[1] = 0x0; brd_from_xrf_idx = 0; - brd_from_rptr.from_rptr_streamid[0] = brd_from_rptr.from_rptr_streamid[1] = 0x00; - brd_from_rptr.to_rptr_streamid[0][0] = brd_from_rptr.to_rptr_streamid[0][1] = 0x00; - brd_from_rptr.to_rptr_streamid[1][0] = brd_from_rptr.to_rptr_streamid[1][1] = 0x00; + brd_from_rptr.from_rptr_streamid = brd_from_rptr.to_rptr_streamid[0] = brd_from_rptr.to_rptr_streamid[1] = 0x0; brd_from_rptr_idx = 0; /* process configuration file */ @@ -3927,14 +3652,10 @@ void CQnetLink::Shutdown() } to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].from_mod = ' '; - to_remote_g2[i].to_mod = ' '; + to_remote_g2[i].from_mod = to_remote_g2[i].to_mod = ' '; to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; - to_remote_g2[i].in_streamid[0] = 0x00; - to_remote_g2[i].in_streamid[1] = 0x00; - to_remote_g2[i].out_streamid[0] = 0x00; - to_remote_g2[i].out_streamid[1] = 0x00; + to_remote_g2[i].in_streamid = to_remote_g2[i].out_streamid = 0x0; } /* tell inbound dongles we are down */ diff --git a/QnetLink.h b/QnetLink.h index 85d98f8..887f95d 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -22,6 +22,8 @@ #include #include "versions.h" +#include "QnetTypeDefs.h" + using namespace libconfig; /*** version number must be x.xx ***/ @@ -33,6 +35,11 @@ using namespace libconfig; #define TIMEOUT 50 #define LH_MAX_SIZE 39 +typedef struct refdsvt_tag { + unsigned char head[2]; + SDSVT dsvt; +} SREFDSVT; + // This is the data payload in the map: inbound_list // This is for inbound dongles typedef struct inbound_tag { @@ -95,28 +102,28 @@ private: char to_mod; short countdown; bool is_connected; - unsigned char in_streamid[2]; // incoming from remote systems - unsigned char out_streamid[2]; // outgoing to remote systems + unsigned short in_streamid; // incoming from remote systems + unsigned short out_streamid; // outgoing to remote systems } to_remote_g2[3]; // broadcast for data arriving from xrf to local rptr struct brd_from_xrf_tag { - unsigned char xrf_streamid[2]; // streamid from xrf - unsigned char rptr_streamid[2][2]; // generated streamid to rptr(s) + unsigned short xrf_streamid; // streamid from xrf + unsigned short rptr_streamid[2]; // generated streamid to rptr(s) } brd_from_xrf; - unsigned char from_xrf_torptr_brd[56]; + SDSVT from_xrf_torptr_brd; short brd_from_xrf_idx; // broadcast for data arriving from local rptr to xrf struct brd_from_rptr_tag { - unsigned char from_rptr_streamid[2]; - unsigned char to_rptr_streamid[2][2]; + unsigned short from_rptr_streamid; + unsigned short to_rptr_streamid[2]; } brd_from_rptr; - unsigned char fromrptr_torptr_brd[56]; + SDSVT fromrptr_torptr_brd; short brd_from_rptr_idx; struct tracing_tag { - unsigned char streamid[2]; + unsigned short streamid; time_t last_time; // last time RF user talked } tracing[3]; @@ -156,6 +163,6 @@ private: // this is used for the "dashboard and qso_details" to avoid processing multiple headers struct old_sid_tag { - unsigned char sid[2]; + unsigned short sid; } old_sid[3]; }; From 9846217fea7000aefc8b16d8a7a0453b0c9ef5b6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 25 May 2018 11:42:05 -0700 Subject: [PATCH 010/553] better streamid generator --- Makefile | 20 ++++++++++---------- QnetDVAP.cpp | 10 +++++----- QnetDVRPTR.cpp | 6 ++++-- QnetLink.cpp | 8 ++------ QnetLink.h | 3 +++ QnetRemote.cpp | 5 +++-- QnetVoice.cpp | 5 +++-- Random.cpp | 38 ++++++++++++++++++++++++++++++++++++++ Random.h | 29 +++++++++++++++++++++++++++++ 9 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 Random.cpp create mode 100644 Random.h diff --git a/Makefile b/Makefile index 04ebe27..91bfeaa 100644 --- a/Makefile +++ b/Makefile @@ -43,23 +43,23 @@ all : $(PROGRAMS) qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread -qnlink : QnetLink.o - g++ $(CPPFLAGS) -o qnlink QnetLink.o $(LDFLAGS) -pthread +qnlink : QnetLink.o Random.o + g++ $(CPPFLAGS) -o qnlink QnetLink.o Random.o $(LDFLAGS) -pthread qnrelay : QnetRelay.o g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) -qndvap : QnetDVAP.o DVAPDongle.o $(DSTROBJS) - g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o $(DSTROBJS) $(LDFLAGS) -pthread +qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) + g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) $(LDFLAGS) -pthread -qndvrptr : QnetDVRPTR.o $(DSTROBJS) - g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o $(DSTROBJS) $(LDFLAGS) +qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o + g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o $(DSTROBJS) $(LDFLAGS) -qnremote : QnetRemote.o - g++ $(CPPFLAGS) -o qnremote QnetRemote.o $(LDFLAGS) +qnremote : QnetRemote.o Random.o + g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o $(LDFLAGS) -qnvoice : QnetVoice.o - g++ $(CPPFLAGS) -o qnvoice QnetVoice.o $(LDFLAGS) +qnvoice : QnetVoice.o Random.o + g++ $(CPPFLAGS) -o qnvoice QnetVoice.o Random.o $(LDFLAGS) %.o : %.cpp g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 76648f0..540e97a 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -50,6 +50,7 @@ using namespace libconfig; #include "DVAPDongle.h" #include "QnetTypeDefs.h" +#include "Random.h" #define VERSION DVAP_VERSION #define CALL_SIZE 8 @@ -110,6 +111,7 @@ extern void dstar_dv_init(); extern int dstar_dv_decode(const unsigned char *d, int data[3]); CDVAPDongle dongle; +CRandom Random; static void calcPFCS(unsigned char *packet, unsigned char *pfcs) { @@ -469,7 +471,7 @@ static void readFrom20000() while ((space < 1) && keep_running) usleep(5); SDVAP_REGISTER dr; - stream_id_to_dvap = (rand_r(&aseed) % 65535U) + 1U; + stream_id_to_dvap = Random.NewStreamID(); dr.header = 0xa02f; dr.frame.streamid = stream_id_to_dvap; dr.frame.framepos = 0x80; @@ -734,7 +736,6 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) struct sigaction act; time_t tnow = 0; unsigned char silence[12] = { 0x4e,0x8d,0x32,0x88,0x26,0x1a,0x3f,0x61,0xe8,0x70,0x4f,0x93 }; - unsigned int aseed_ack = 0; act.sa_handler = sig_catch; sigemptyset(&act.sa_mask); @@ -754,9 +755,8 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) sleep(DELAY_BEFORE); time(&tnow); - aseed_ack = tnow + getpid(); - uint16_t stream_id_to_dvap = (rand_r(&aseed_ack) % 65535U) + 1U; + uint16_t stream_id_to_dvap = Random.NewStreamID(); // HEADER while ((space < 1) && keep_running) @@ -1082,7 +1082,7 @@ static void ReadDVAPThread() net_buf.vpkt.dst_rptr_id = 0x00; net_buf.vpkt.snd_rptr_id = 0x01; net_buf.vpkt.snd_term_id = SND_TERM_ID; - streamid_raw = (rand_r(&aseed) % 65535U) + 1U; + streamid_raw = Random.NewStreamID(); net_buf.vpkt.streamid = streamid_raw; net_buf.vpkt.ctrl = 0x80; sequence = 0; diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index 670a646..84533bd 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -25,6 +25,8 @@ #include #include +#include "Random.h" + using namespace libconfig; #define VERSION DVRPTR_VERSION @@ -2676,7 +2678,7 @@ int main(int argc, const char **argv) dstar_dv_init(); time(&tNow); - srand(tNow + getpid()); + CRandom Random; time(&time_rqst); @@ -2932,7 +2934,7 @@ int main(int argc, const char **argv) Send_Network_Header[11] = 0x00; Send_Network_Header[12] = 0x01; Send_Network_Header[13] = SND_TERM_ID; - streamid_raw = (::rand() % 65535U) + 1U; + streamid_raw = Random.NewStreamID(); Send_Network_Header[14] = streamid_raw / 256U; Send_Network_Header[15] = streamid_raw % 256U; Send_Network_Header[16] = 0x80; diff --git a/QnetLink.cpp b/QnetLink.cpp index 4b032cb..d72c2e3 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -162,7 +162,6 @@ void CQnetLink::RptrAckThread(char *arg) char from_mod = arg[0]; char RADIO_ID[21]; memcpy(RADIO_ID, arg + 1, 21); - unsigned int aseed; time_t tnow = 0; unsigned char silence[12] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8, 0x16, 0x29, 0xf5 }; struct sigaction act; @@ -180,9 +179,8 @@ void CQnetLink::RptrAckThread(char *arg) } time(&tnow); - aseed = tnow + pthread_self(); - u_int16_t streamid_raw = (::rand_r(&aseed) % 65535U) + 1U; + short int streamid_raw = Random.NewStreamID(); sleep(delay_before); @@ -3377,7 +3375,6 @@ void CQnetLink::AudioNotifyThread(char *arg) char mod; char *p = NULL; u_int16_t streamid_raw = 0; - unsigned int aseed; time_t tnow = 0; struct sigaction act; @@ -3453,7 +3450,6 @@ void CQnetLink::AudioNotifyThread(char *arg) } time(&tnow); - aseed = tnow + pthread_self(); while (keep_running) { /* 2 byte length */ @@ -3462,7 +3458,7 @@ void CQnetLink::AudioNotifyThread(char *arg) break; if (rlen == 56) - streamid_raw = (::rand_r(&aseed) % 65535U) + 1U; + streamid_raw = Random.NewStreamID(); else if (rlen == 27) ; else { diff --git a/QnetLink.h b/QnetLink.h index 887f95d..51a057a 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -23,6 +23,7 @@ #include #include "versions.h" #include "QnetTypeDefs.h" +#include "Random.h" using namespace libconfig; @@ -165,4 +166,6 @@ private: struct old_sid_tag { unsigned short sid; } old_sid[3]; + + CRandom Random; }; diff --git a/QnetRemote.cpp b/QnetRemote.cpp index c01894a..73fdb62 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -36,6 +36,7 @@ #include #include "QnetTypeDefs.h" +#include "Random.h" using namespace libconfig; @@ -283,7 +284,7 @@ int main(int argc, char *argv[]) RADIO_ID.resize(20, ' '); time(&tNow); - srand(tNow + getpid()); + CRandom Random; if (dst_open(IP_ADDRESS.c_str(), PORT)) return 1; @@ -306,7 +307,7 @@ int main(int argc, char *argv[]) pkt.vpkt.snd_term_id = 0x02; else pkt.vpkt.snd_term_id = 0x00; - streamid_raw = (unsigned short)(::rand() & 0xFFFF); + streamid_raw = Random.NewStreamID(); pkt.vpkt.streamid = htons(streamid_raw); pkt.vpkt.ctrl = 0x80; pkt.vpkt.hdr.flag[0] = pkt.vpkt.hdr.flag[1] = pkt.vpkt.hdr.flag[2] = 0x00; diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 2f14134..b168634 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -36,6 +36,7 @@ #include #include "QnetTypeDefs.h" +#include "Random.h" using namespace libconfig; @@ -304,7 +305,7 @@ int main(int argc, char *argv[]) sleep(PLAY_WAIT); time(&tNow); - srand(tNow + getpid()); + CRandom Random; if (dst_open(IP_ADDRESS.c_str(), PORT)) return 1; @@ -318,7 +319,7 @@ int main(int argc, char *argv[]) break; } if (rlen == 56) - streamid_raw = (short)(::rand() & 0xFFFF); + streamid_raw = Random.NewStreamID(); else if (rlen == 27) ; else { diff --git a/Random.cpp b/Random.cpp new file mode 100644 index 0000000..06d2bc1 --- /dev/null +++ b/Random.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include + +#include "Random.h" + +CRandom::CRandom() +{ + srandom(getpid()); +} + +CRandom::~CRandom() {} + +short CRandom::NewStreamID() +{ + short r = 0; + while (0 == r) + r = 0xffff & random(); + return r; +} diff --git a/Random.h b/Random.h new file mode 100644 index 0000000..cbda881 --- /dev/null +++ b/Random.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +class CRandom +{ +public: + CRandom(); + + ~CRandom(); + + short NewStreamID(); +}; From 68ccb676f7bdf6b9b2ffa45b39a32343375c401e Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 25 May 2018 11:46:26 -0700 Subject: [PATCH 011/553] NewStreamID returns unsigned short --- Random.cpp | 6 +++--- Random.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Random.cpp b/Random.cpp index 06d2bc1..ad087a7 100644 --- a/Random.cpp +++ b/Random.cpp @@ -29,10 +29,10 @@ CRandom::CRandom() CRandom::~CRandom() {} -short CRandom::NewStreamID() +unsigned short CRandom::NewStreamID() { - short r = 0; + unsigned short r = 0; while (0 == r) - r = 0xffff & random(); + r = 0xffffu & random(); return r; } diff --git a/Random.h b/Random.h index cbda881..390830d 100644 --- a/Random.h +++ b/Random.h @@ -25,5 +25,5 @@ public: ~CRandom(); - short NewStreamID(); + unsigned short NewStreamID(); }; From 2f36400d12d93b9ab12aa7b8f15f1c1df0cbb179 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 27 May 2018 13:13:18 -0700 Subject: [PATCH 012/553] QnetLink Version 6.0.0 --- versions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.h b/versions.h index 932c849..c8fa022 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! #define IRCDDB_VERSION "linux-qngateway-6.1.0" -#define LINK_VERSION "5.2.0" +#define LINK_VERSION "6.0.0" #define DVAP_VERSION "linux-qndvap-5.1.1" #define DVRPTR_VERSION "linux-qndvrptr-5.1.0" #define MMDVM_VERSION "mmdvm-qnmodem-0.1.0" From f43e9634d1ab2d729d4b6de7e486b437d1a35ff2 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 30 May 2018 11:49:28 -0700 Subject: [PATCH 013/553] better random numbers in IRCProtocol --- ircddb/IRCProtocol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ircddb/IRCProtocol.cpp b/ircddb/IRCProtocol.cpp index eb2837a..e85ba29 100644 --- a/ircddb/IRCProtocol.cpp +++ b/ircddb/IRCProtocol.cpp @@ -10,6 +10,7 @@ IRCProtocol::IRCProtocol(IRCApplication *app, const std::string &callsign, const std::string &password, const std::string &channel, const std::string &versionInfo) { + srand(time(NULL)); this->password = password; this->channel = channel; this->app = app; From bc591f93ab27ec3627d5fc67ba3fc3202276dbfa Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 31 May 2018 06:49:07 -0700 Subject: [PATCH 014/553] fixed problems with dvap.cfg and reflist --- qn.dvap.cfg | 2 +- reflist.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qn.dvap.cfg b/qn.dvap.cfg index 835c675..15514eb 100644 --- a/qn.dvap.cfg +++ b/qn.dvap.cfg @@ -23,7 +23,7 @@ module = { } } -glink = { +link = { # add the callsigns that can shutdown or reboot your system # admin = [ "XX0XXX" , "YY0YYY" ] // only these users can execute scripts diff --git a/reflist.sh b/reflist.sh index bdaf8ac..def0502 100755 --- a/reflist.sh +++ b/reflist.sh @@ -11,4 +11,4 @@ awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt -/bin/rm -f D{Extra,Plus,DCS}_Hosts.txt +/bin/rm -f D{Extra,Plus,CS}_Hosts.txt From afa32de002eb181aef4caf3186b9197548794460 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 1 Jun 2018 14:05:47 -0700 Subject: [PATCH 015/553] get_yrcall_from_cache expansion --- QnetGateway.cpp | 2 +- versions.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 15728e8..b97a47e 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -531,7 +531,7 @@ int CQnetGateway::get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char * return 2; } - if ((*mod != 'A') && (*mod != 'B') && (*mod != 'C')) { + if (*mod == 'G') { printf("Invalid module %c\n", *mod); return 2; } diff --git a/versions.h b/versions.h index c8fa022..f9c2499 100644 --- a/versions.h +++ b/versions.h @@ -1,5 +1,5 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "linux-qngateway-6.1.0" +#define IRCDDB_VERSION "linux-qngateway-6.1.1" #define LINK_VERSION "6.0.0" #define DVAP_VERSION "linux-qndvap-5.1.1" #define DVRPTR_VERSION "linux-qndvrptr-5.1.0" From 5ea923469daee95f91d984951e78cd5a7f15d1c7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 26 Jun 2018 09:07:23 -0700 Subject: [PATCH 016/553] custom builds & .cfg and gwys.txt are installed as symbolic links --- Makefile | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 91bfeaa..99da015 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,16 @@ IRCOBJS = $(IRC)/IRCDDB.o $(IRC)/IRCClient.o $(IRC)/IRCReceiver.o $(IRC)/IRCMess SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -PROGRAMS=qngateway qnlink qnrelay qndvap qndvrptr qnremote qnvoice -all : $(PROGRAMS) +ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr +MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay +DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap +DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr + +all : $(ALL_PROGRAMS) +mmdvm : $(MDV_PROGRAMS) +dvap : $(DVP_PROGRAMS) +dvrptr : $(DVR_PROGRAMS) qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread @@ -71,11 +78,11 @@ clean: -include $(DEPS) -install : qngateway qnlink qnrelay +install : $(MDV_PROGRAMS) ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/cp -f qn.cfg $(CFGDIR) + /bin/ln -s qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -83,7 +90,7 @@ install : qngateway qnlink qnrelay ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/cp -f gwys.txt $(CFGDIR) + /bin/ln -s gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -96,11 +103,11 @@ install : qngateway qnlink qnrelay systemctl daemon-reload systemctl start qnrelay.service -installdvap : qngateway qnlink qndvap +installdvap : $(DVP_PROGRAMS) ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/cp -f qn.cfg $(CFGDIR) + /bin/ln -s qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -108,7 +115,7 @@ installdvap : qngateway qnlink qndvap ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/cp -f gwys.txt $(CFGDIR) + /bin/ln -s gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -121,11 +128,11 @@ installdvap : qngateway qnlink qndvap systemctl daemon-reload systemctl start qndvap.service -installdvrptr : qngateway qnlink qndvrptr +installdvrptr : $(DVR_PROGRAMS) ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/cp -f qn.cfg $(CFGDIR) + /bin/ln -s qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -133,7 +140,7 @@ installdvrptr : qngateway qnlink qndvrptr ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/cp -f gwys.txt $(CFGDIR) + /bin/ln -s gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -147,7 +154,7 @@ installdvrptr : qngateway qnlink qndvrptr systemctl start qndvrptr.service installdtmf : qndtmf - /bin/cp -f qndtmf $(BINDIR) + /bin/ln -s qndtmf $(BINDIR) /bin/cp -f system/qndtmf.service $(SYSDIR) systemctl enable qndtmf.service systemctl daemon-reload @@ -155,7 +162,7 @@ installdtmf : qndtmf installmmdvm : /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) - /bin/cp -f $(MMPATH)/MMDVM.qn $(CFGDIR) + /bin/ln -s $(MMPATH)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) /bin/cp -f system/mmdvm.timer $(SYSDIR) systemctl enable mmdvm.timer From b6e9b46227a70d11657f34893ebaec85ef4ff5af Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 26 Jun 2018 09:59:32 -0700 Subject: [PATCH 017/553] put gwys.txt, qn.cfg and MMDVM.qn into the install targets --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 99da015..d94590b 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ clean: -include $(DEPS) -install : $(MDV_PROGRAMS) +install : $(MDV_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) @@ -103,7 +103,7 @@ install : $(MDV_PROGRAMS) systemctl daemon-reload systemctl start qnrelay.service -installdvap : $(DVP_PROGRAMS) +installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) @@ -128,7 +128,7 @@ installdvap : $(DVP_PROGRAMS) systemctl daemon-reload systemctl start qndvap.service -installdvrptr : $(DVR_PROGRAMS) +installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) @@ -160,7 +160,7 @@ installdtmf : qndtmf systemctl daemon-reload systemctl start qndtmf.service -installmmdvm : +installmmdvm : $(MMPATH)/MMDVMhost $(MMPATH)/MMDVM.qn /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) /bin/ln -s $(MMPATH)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) From 22df4d03db4b670268ba01e10da0c929e1c1240a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 26 Jun 2018 17:34:36 -0700 Subject: [PATCH 018/553] incomplete save --- QnetGateway.cpp | 190 +++++++++++++++++++++++++++++------------------- QnetGateway.h | 17 +++-- versions.h | 11 +-- 3 files changed, 130 insertions(+), 88 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index b97a47e..d763352 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -170,47 +170,47 @@ void CQnetGateway::calcPFCS(unsigned char *packet, int len) return; } -bool CQnetGateway::get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) +bool CQnetGateway::get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) value = default_value; } else value = default_value; - printf("%s = [%d]\n", path, value); + printf("%s = [%d]\n", path.c_str(), value); return true; } -bool CQnetGateway::get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) +bool CQnetGateway::get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) value = default_value; } else value = default_value; - printf("%s = [%lg]\n", path, value); + printf("%s = [%lg]\n", path.c_str(), value); return true; } -bool CQnetGateway::get_value(const Config &cfg, const char *path, bool &value, bool default_value) +bool CQnetGateway::get_value(const Config &cfg, const std::string path, bool &value, bool default_value) { if (! cfg.lookupValue(path, value)) value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); + printf("%s = [%s]\n", path.c_str(), value ? "true" : "false"); return true; } -bool CQnetGateway::get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +bool CQnetGateway::get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value) { if (cfg.lookupValue(path, value)) { int l = value.length(); if (lmax) { - printf("%s is invalid\n", path); + printf("%s is invalid\n", path.c_str()); return false; } } else value = default_value; - printf("%s = [%s]\n", path, value.c_str()); + printf("%s = [%s]\n", path.c_str(), value.c_str()); return true; } @@ -230,8 +230,9 @@ bool CQnetGateway::read_config(char *cfgFile) printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); return true; } - - if (! get_value(cfg, "ircddb.login", owner, 3, CALL_SIZE-2, "UNDEFINED")) + // ircddb + std::string path("ircddb."); + if (! get_value(cfg, path+"login", owner, 3, CALL_SIZE-2, "UNDEFINED")) return true; OWNER = owner; ToLower(owner); @@ -239,36 +240,59 @@ bool CQnetGateway::read_config(char *cfgFile) printf("OWNER=[%s]\n", OWNER.c_str()); OWNER.resize(CALL_SIZE, ' '); + if (! get_value(cfg, path+"host", ircddb.ip, 3, MAXHOSTNAMELEN, "rr.openquad.net")) + return true; + + get_value(cfg, path+"port", ircddb.port, 1000, 65535, 9007); + + if(! get_value(cfg, path+"password", irc_pass, 0, 512, "1111111111111111")) + return true; + + // modules + is_icom = is_not_icom = false; for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; + path += '.'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) { - printf("module type '%s' is invalid\n", type.c_str()); - return true; - } rptr.mod[m].defined = true; - if (0 == strcasecmp(type.c_str(), "dvap")) + if (0 == strcasecmp(type.c_str(), "icom")) { + rptr.mod[m].package_version = ICOM_VERSION; + is_icom = true; + } if (0 == strcasecmp(type.c_str(), "dvap")) { rptr.mod[m].package_version = DVAP_VERSION; - else if (0 == strcasecmp(type.c_str(), "dvrptr")) + is_not_icom = true; + } else if (0 == strcasecmp(type.c_str(), "dvrptr")) { rptr.mod[m].package_version = DVRPTR_VERSION; - else + is_not_icom = true; + } else if (0 == strcasecmp(type.c_str(), "mmdvm")) { rptr.mod[m].package_version = MMDVM_VERSION; - if (! get_value(cfg, std::string(path+".ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, "127.0.0.1")) + is_not_icom = true; + } else { + printf("module type '%s' is invalid\n", type.c_str()); + return true; + } + if (is_icom && is_not_icom) { + printf("cannot define both icom and non-icom modules\n"); return true; - get_value(cfg, std::string(path+".port").c_str(), rptr.mod[m].portip.port, 16000, 65535, 19998+m); - get_value(cfg, std::string(path+".frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); - get_value(cfg, std::string(path+".offset").c_str(), rptr.mod[m].offset,-1.0e12, 1.0e12, 0.0); - get_value(cfg, std::string(path+".range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); - get_value(cfg, std::string(path+".agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); - get_value(cfg, std::string(path+".latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); - get_value(cfg, std::string(path+".longitude").c_str(), rptr.mod[m].longitude, -180.0, 180.0, 0.0); - if (! cfg.lookupValue(path+".desc1", rptr.mod[m].desc1)) + } + if (is_not_icom) { + if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, "127.0.0.1")) + return true; + get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, 19998+m); + } + get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); + get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset,-1.0e12, 1.0e12, 0.0); + get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); + get_value(cfg, std::string(path+"agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); + get_value(cfg, std::string(path+"latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); + get_value(cfg, std::string(path+"longitude").c_str(), rptr.mod[m].longitude, -180.0, 180.0, 0.0); + if (! cfg.lookupValue(path+"desc1", rptr.mod[m].desc1)) rptr.mod[m].desc1 = ""; - if (! cfg.lookupValue(path+".desc2", rptr.mod[m].desc2)) + if (! cfg.lookupValue(path+"desc2", rptr.mod[m].desc2)) rptr.mod[m].desc2 = ""; - if (! get_value(cfg, std::string(path+".url").c_str(), rptr.mod[m].url, 0, 80, "github.com/n7tae/QnetGateway")) + if (! get_value(cfg, std::string(path+"url").c_str(), rptr.mod[m].url, 0, 80, "github.com/n7tae/QnetGateway")) return true; // truncate strings if (rptr.mod[m].desc1.length() > 20) @@ -283,78 +307,85 @@ bool CQnetGateway::read_config(char *cfgFile) rptr.mod[m].defined = false; } if (false==rptr.mod[0].defined && false==rptr.mod[1].defined && false==rptr.mod[2].defined) { - printf("No repeaters defined!\n"); + printf("No modules defined!\n"); return true; } - if (! get_value(cfg, "file.status", status_file, 1, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) + // gateway + path = "gateway."; + if (! get_value(cfg, path+"local_irc_ip", local_irc_ip, 7, IP_SIZE, "0.0.0.0")) return true; - if (! get_value(cfg, "gateway.local_irc_ip", local_irc_ip, 7, IP_SIZE, "0.0.0.0")) + if (! get_value(cfg, path+"external.ip", g2_external.ip, 7, IP_SIZE, "0.0.0.0")) return true; - get_value(cfg, "gateway.send_qrgs_maps", bool_send_qrgs, true); + get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - if (! get_value(cfg, "aprs.host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) + if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, "0.0.0.0")) return true; - get_value(cfg, "aprs.port", rptr.aprs.port, 10000, 65535, 14580); + get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, 19000); - get_value(cfg, "aprs.interval", rptr.aprs_interval, 40, 1000, 40); + get_value(cfg, path+"regen_header", bool_regen_header, true); - if (! get_value(cfg, "aprs.filter", rptr.aprs_filter, 0, 512, "")) - return true; + get_value(cfg, path+"aprs_send", bool_send_aprs, true); - if (! get_value(cfg, "gateway.external.ip", g2_external.ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, "gateway.external.port", g2_external.port, 1024, 65535, 40000); + get_value(cfg, path+"send_qrgs_maps", bool_send_qrgs, true); - if (! get_value(cfg, "gateway.internal.ip", g2_internal.ip, 7, IP_SIZE, "0.0.0.0")) + // APRS + path = "aprs."; + if (! get_value(cfg, path+"host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) return true; - get_value(cfg, "gateway.internal.port", g2_internal.port, 16000, 65535, 19000); - - if (! get_value(cfg, "link.outgoing_ip", g2_link.ip, 7, IP_SIZE, "127.0.0.1")) - return true; + get_value(cfg, path+"port", rptr.aprs.port, 10000, 65535, 14580); - get_value(cfg, "link.port", g2_link.port, 16000, 65535, 18997); + get_value(cfg, path+"interval", rptr.aprs_interval, 40, 1000, 40); - get_value(cfg, "log.qso", bool_qso_details, false); - - get_value(cfg, "log.irc", bool_irc_debug, false); + if (! get_value(cfg, path+"filter", rptr.aprs_filter, 0, 512, "")) + return true; - get_value(cfg, "log.dtmf", bool_dtmf_debug, false); + // log + path = "log."; + get_value(cfg, path+"qso", bool_qso_details, false); - get_value(cfg, "gateway.regen_header", bool_regen_header, true); + get_value(cfg, path+"irc", bool_irc_debug, false); - get_value(cfg, "gateway.aprs_send", bool_send_aprs, true); + get_value(cfg, path+"dtmf", bool_dtmf_debug, false); + if (! get_value(cfg, "link.outgoing_ip", g2_link.ip, 7, IP_SIZE, "127.0.0.1")) + return true; - if (! get_value(cfg, "file.echotest", echotest_dir, 2, FILENAME_MAX, "/tmp")) + // file + path = "file."; + if (! get_value(cfg, path+"echotest", echotest_dir, 2, FILENAME_MAX, "/tmp")) return true; - get_value(cfg, "timing.play.wait", play_wait, 1, 10, 2); + if (! get_value(cfg, path+"dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) + return true; - get_value(cfg, "timing.play.delay", play_delay, 9, 25, 19); + if (! get_value(cfg, path+"status", status_file, 1, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) + return true; - get_value(cfg, "timing.timeeout.echo", echotest_rec_timeout, 1, 10, 1); + // link + path = "link."; + get_value(cfg, path+"port", g2_link.port, 16000, 65535, 18997); - get_value(cfg, "timing.timeout.voicemail", voicemail_rec_timeout, 1, 10, 1); + if (! get_value(cfg, path+"ip", g2_link.ip, 7, 15, "127.0.0.1")) + return true; - get_value(cfg, "timing.timeout.remote_g2", from_remote_g2_timeout, 1, 10, 2); + // timing + path = "timing.play."; + get_value(cfg, path+"wait", play_wait, 1, 10, 2); - get_value(cfg, "timing.timeout.local_rptr", from_local_rptr_timeout, 1, 10, 1); + get_value(cfg, path+"delay", play_delay, 9, 25, 19); - if (! get_value(cfg, "ircddb.host", ircddb.ip, 3, MAXHOSTNAMELEN, "rr.openquad.net")) - return true; + path = "timing.timeout."; + get_value(cfg, path+"echo", echotest_rec_timeout, 1, 10, 1); - get_value(cfg, "ircddb.port", ircddb.port, 1000, 65535, 9007); + get_value(cfg, path+"voicemail", voicemail_rec_timeout, 1, 10, 1); - if(! get_value(cfg, "ircddb.password", irc_pass, 0, 512, "1111111111111111")) - return true; + get_value(cfg, path+"remote_g2", from_remote_g2_timeout, 1, 10, 2); - if (! get_value(cfg, "file.dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) - return true; + get_value(cfg, path+"local_rptr", from_local_rptr_timeout, 1, 10, 1); return false; } @@ -670,6 +701,15 @@ void CQnetGateway::process() ii->kickWatchdog(IRCDDB_VERSION); + if (is_icom) { // send INIT to Icom Stack + unsigned char buf[10]; + memset(buf, 0, 10); + memcpy(buf, "INIT", 4); + buf[6] = 0x73U; + sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&, sizeof(struct sockaddr_in)); + printf("Waiting for ICOM controller...\n"); + } + while (keep_running) { for (int i=0; i<3; i++) { /* echotest recording timed out? */ @@ -2446,13 +2486,8 @@ int CQnetGateway::init(char *cfgfile) return 1; } - rptr.mod[0].band = "23cm"; - rptr.mod[1].band = "70cm"; - rptr.mod[2].band = "2m"; - - for (i = 0; i < 3; i++) { + for (i = 0; i < 3; i++) memset(&band_txt[0], 0, sizeof(SBANDTXT)); - } /* process configuration file */ if ( read_config(cfgfile) ) { @@ -2472,6 +2507,9 @@ int CQnetGateway::init(char *cfgfile) rptr.mod[0].call += "-A"; rptr.mod[1].call += "-B"; rptr.mod[2].call += "-C"; + rptr.mod[0].band = "23cm"; + rptr.mod[1].band = "70cm"; + rptr.mod[2].band = "2m"; printf("Repeater callsigns: [%s] [%s] [%s]\n", rptr.mod[0].call.c_str(), rptr.mod[1].call.c_str(), rptr.mod[2].call.c_str()); if (bool_send_aprs) { @@ -2520,7 +2558,9 @@ int CQnetGateway::init(char *cfgfile) return 1; } - /* Open udp INTERNAL port (default: 19000) */ + // Open G2 INTERNAL: + // default non-icom 127.0.0.1:19000 + // default icom 172.10.0.5:20000 srv_sock = open_port(g2_internal); if (0 > srv_sock) { printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); diff --git a/QnetGateway.h b/QnetGateway.h index 17adabe..197c21a 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -85,7 +85,12 @@ class CQnetGateway { public: CQnetGateway(); ~CQnetGateway(); + void process(); + int init(char *cfgfile); + private: + bool is_icom, is_not_icom; + SPORTIP g2_internal, g2_external, g2_link, ircddb; std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass; @@ -155,10 +160,10 @@ private: // read configuration file bool read_config(char *); - bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value); - bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value); - bool get_value(const Config &cfg, const char *path, bool &value, bool default_value); - bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value); + bool get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value); + bool get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value); + bool get_value(const Config &cfg, const std::string path, bool &value, bool default_value); + bool get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value); /* aprs functions, borrowed from my retired IRLP node 4201 */ void gps_send(short int rptr_idx); @@ -169,8 +174,4 @@ private: void set_dest_rptr(int mod_ndx, char *dest_rptr); bool validate_csum(SBANDTXT &bt, bool is_gps); - -public: - void process(); - int init(char *cfgfile); }; diff --git a/versions.h b/versions.h index f9c2499..1eee3ce 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,7 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "linux-qngateway-6.1.1" -#define LINK_VERSION "6.0.0" -#define DVAP_VERSION "linux-qndvap-5.1.1" -#define DVRPTR_VERSION "linux-qndvrptr-5.1.0" -#define MMDVM_VERSION "mmdvm-qnmodem-0.1.0" +#define IRCDDB_VERSION "QnetGateway-7.0.0" +#define LINK_VERSION "QnetLink-6.0.0" +#define DVAP_VERSION "QnetDVAP-5.1.1" +#define DVRPTR_VERSION "QnetDVRPTR-5.1.0" +#define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" +#define ICOM_VERSION IRCDDB_VERSION From ffc10ae3f2cf31d34ab23584bce22806a11cfde2 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 27 Jun 2018 21:58:50 -0700 Subject: [PATCH 019/553] ICOM Stack support --- Makefile | 44 ++ QnetGateway.cpp | 1960 ++++++++++++++++++++++++----------------------- 2 files changed, 1044 insertions(+), 960 deletions(-) diff --git a/Makefile b/Makefile index d94590b..e6dae12 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr +ICM_PROGRAMS=qngateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) mmdvm : $(MDV_PROGRAMS) @@ -103,6 +104,25 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg systemctl daemon-reload systemctl start qnrelay.service +installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg + ######### QnetGateway ######### + /bin/cp -f qngateway $(BINDIR) + /bin/cp -f qnremote qnvoice $(BINDIR) + /bin/ln -s qn.cfg $(CFGDIR) + /bin/cp -f system/qngateway.service $(SYSDIR) + systemctl enable qngateway.service + systemctl daemon-reload + systemctl start qngateway.service + ######### QnetLink ######### + /bin/cp -f qnlink $(BINDIR) + /bin/cp -f announce/*.dat $(CFGDIR) + /bin/ln -s gwys.txt $(CFGDIR) + /bin/cp -f exec_?.sh $(CFGDIR) + /bin/cp -f system/qnlink.service $(SYSDIR) + systemctl enable qnlink.service + systemctl daemon-reload + systemctl start qnlink.service + installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) @@ -208,6 +228,30 @@ uninstall : /bin/rm -f $(BINDIR)/qnrelay systemctl daemon-reload +uninstallicom : + ######### QnetGateway ######### + systemctl stop qngateway.service + systemctl disable qngateway.service + /bin/rm -f $(SYSDIR)/qngateway.service + /bin/rm -f $(BINDIR)/qngateway + /bin/rm -f $(BINDIR)/qnremote + /bin/rm -f $(BINDIR)/qnvoice + /bin/rm -f $(CFGDIR)/qn.cfg + ######### QnetLink ######### + systemctl stop qnlink.service + systemctl disable qnlink.service + /bin/rm -f $(SYSDIR)/qnlink.service + /bin/rm -f $(BINDIR)/qnlink + /bin/rm -f $(CFGDIR)/already_linked.dat + /bin/rm -f $(CFGDIR)/already_unlinked.dat + /bin/rm -f $(CFGDIR)/failed_linked.dat + /bin/rm -f $(CFGDIR)/id.dat + /bin/rm -f $(CFGDIR)/linked.dat + /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/RPT_STATUS.txt + /bin/rm -f $(CFGDIR)/gwys.txt + /bin/rm -f $(CFGDIR)/exec_?.sh + uninstalldvap : ######### QnetGateway ######### systemctl stop qngateway.service diff --git a/QnetGateway.cpp b/QnetGateway.cpp index d763352..fdb3d2c 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -64,6 +64,9 @@ extern void dstar_dv_init(); extern int dstar_dv_decode(const unsigned char *d, int data[3]); static std::atomic keep_running(true); +static std::atomic G2_COUNTER_OUT(0); +static unsigned short OLD_REPLY_SEQ = 0; +static unsigned short NEW_REPLY_SEQ = 0; /* signal catching function */ static void sigCatch(int signum) @@ -277,11 +280,10 @@ bool CQnetGateway::read_config(char *cfgFile) printf("cannot define both icom and non-icom modules\n"); return true; } - if (is_not_icom) { - if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, "127.0.0.1")) - return true; - get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, 19998+m); - } + + if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, is_icom ? "172.16.0.20" : "127.0.0.1")) + return true; + get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, is_icom ? 20000 : 19998+m); get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset,-1.0e12, 1.0e12, 0.0); get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); @@ -306,9 +308,31 @@ bool CQnetGateway::read_config(char *cfgFile) } else rptr.mod[m].defined = false; } - if (false==rptr.mod[0].defined && false==rptr.mod[1].defined && false==rptr.mod[2].defined) { + if (! is_icom && ! is_not_icom) { printf("No modules defined!\n"); return true; + } else if (is_icom) { // make sure all ICOM modules have the same IP and port number + std::string addr; + int port; + for (int i=0; i<3; i++) { + if (rptr.mod[i].defined) { + if (addr.size()) { + if (addr.compare(rptr.mod[i].portip.ip) || port!=rptr.mod[i].portip.port) { + printf("all defined ICOM modules must have the same IP and port number!\n"); + return true; + } + } else { + addr = rptr.mod[i].portip.ip; + port = rptr.mod[i].portip.port; + } + } + } + for (int i=0; i<3; i++) { + if (! rptr.mod[i].defined) { + rptr.mod[i].portip.ip = addr; + rptr.mod[i].portip.port = port; + } + } } // gateway @@ -321,10 +345,10 @@ bool CQnetGateway::read_config(char *cfgFile) get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, "0.0.0.0")) + if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, is_icom ? "172.16.0.1" : "0.0.0.0")) return true; - get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, 19000); + get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, is_icom ? 20000 : 19000); get_value(cfg, path+"regen_header", bool_regen_header, true); @@ -701,15 +725,34 @@ void CQnetGateway::process() ii->kickWatchdog(IRCDDB_VERSION); - if (is_icom) { // send INIT to Icom Stack - unsigned char buf[10]; + if (is_icom) { + // send INIT to Icom Stack + unsigned char buf[500]; memset(buf, 0, 10); memcpy(buf, "INIT", 4); buf[6] = 0x73U; - sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&, sizeof(struct sockaddr_in)); + // we can use the module a band_addr for INIT + sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); printf("Waiting for ICOM controller...\n"); + + // get the acknowledgement from the ICOM Stack + while (keep_running) { + socklen_t fromlength = sizeof(struct sockaddr_in); + int recvlen = recvfrom(srv_sock, buf, 500, 0, (struct sockaddr *)&fromRptr, &fromlength); + if (10==recvlen && 0==memcmp(buf, "INIT", 4) && 0x72U==buf[6] && 0x0U==buf[7]) { + OLD_REPLY_SEQ = 256U * buf[4] + buf[5]; + NEW_REPLY_SEQ = OLD_REPLY_SEQ + 1; + G2_COUNTER_OUT = NEW_REPLY_SEQ; + unsigned int ui = G2_COUNTER_OUT; + printf("SYNC: old=%u, new=%u out=%u\n", OLD_REPLY_SEQ, NEW_REPLY_SEQ, ui); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + printf("Detected ICOM controller!\n"); } + while (keep_running) { for (int i=0; i<3; i++) { /* echotest recording timed out? */ @@ -884,7 +927,7 @@ void CQnetGateway::process() g2buflen, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = toRptr[i].G2_COUNTER; + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter rptrbuf.flag[0] = 0x73; rptrbuf.flag[1] = 0x12; rptrbuf.flag[2] = 0x00; @@ -904,13 +947,10 @@ void CQnetGateway::process() /* time it, in case stream times out */ time(&toRptr[i].last_time); - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; } } - } else { + } else { // g2buflen == 27 if (g2buf.counter & 0x40) { if (bool_qso_details) printf("END from g2: streamID=%04x, %d bytes from IP=%s\n", g2buf.streamid, g2buflen,inet_ntoa(fromDst4.sin_addr)); @@ -923,7 +963,7 @@ void CQnetGateway::process() if ((toRptr[i].streamid==g2buf.streamid) && (toRptr[i].adr == fromDst4.sin_addr.s_addr)) { memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = toRptr[i].G2_COUNTER; + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); rptrbuf.flag[0] = 0x73; rptrbuf.flag[1] = 0x12; rptrbuf.flag[2] = 0x00; @@ -936,9 +976,6 @@ void CQnetGateway::process() /* timeit */ time(&toRptr[i].last_time); - /* bump G2 counter */ - toRptr[i].G2_COUNTER++; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; /* End of stream ? */ @@ -970,18 +1007,15 @@ void CQnetGateway::process() if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { printf("Re-generating header for streamID=%04x\n", g2buf.streamid); - toRptr[i].saved_hdr[5] = (unsigned char)(toRptr[i].G2_COUNTER & 0xff); - toRptr[i].saved_hdr[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); + toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); + toRptr[i].saved_hdr[5] = (unsigned char)((is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++) & 0xff); /* re-generate/send the header */ sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - /* bump G2 counter */ - toRptr[i].G2_COUNTER++; - /* send this audio packet to repeater */ memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = toRptr[i].G2_COUNTER; + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); rptrbuf.flag[0] = 0x73; rptrbuf.flag[1] = 0x12; rptrbuf.flag[2] = 0x00; @@ -998,9 +1032,6 @@ void CQnetGateway::process() /* time it, in case stream times out */ time(&toRptr[i].last_time); - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; } @@ -1019,787 +1050,745 @@ void CQnetGateway::process() socklen_t fromlen = sizeof(struct sockaddr_in); int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); - /* DV */ - if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && - (rptrbuf.flag[0] == 0x73) && (rptrbuf.flag[1] == 0x12) && - (0 == memcmp(rptrbuf.pkt_id,"DSTR", 4)) && - (rptrbuf.vpkt.icm_id == 0x20) && (rptrbuf.flag[2] == 0x00) && - ((rptrbuf.remaining == 0x30) || /* 48 bytes follow */ - (rptrbuf.remaining == 0x13) || /* 19 bytes follow */ - (rptrbuf.remaining == 0x16)) ) { /* 22 bytes follow */ + if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { + ///////////////////////////////////////////////////////////////////// + // some ICOM handshaking... + if (is_icom && 10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr + NEW_REPLY_SEQ = ntohs(rptrbuf.counter); + if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { + G2_COUNTER_OUT = NEW_REPLY_SEQ; + OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; + } else + OLD_REPLY_SEQ = NEW_REPLY_SEQ; + } else if (is_icom && 0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { + rptrbuf.flag[0] = 0x72U; + memset(rptrbuf.flag+1, 0x0U, 3); + sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + // end of ICOM handshaking + ///////////////////////////////////////////////////////////////////// + } else if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && + (rptrbuf.flag[0] == 0x73) && (rptrbuf.flag[1] == 0x12) && + (rptrbuf.vpkt.icm_id == 0x20) && (rptrbuf.flag[2] == 0x00) && + ((rptrbuf.remaining == 0x30) || /* 48 bytes follow */ + (rptrbuf.remaining == 0x13) || /* 19 bytes follow */ + (rptrbuf.remaining == 0x16)) ) { /* 22 bytes follow */ + if (is_icom) { // acknowledge packet to ICOM + SDSTR reply; + memcpy(reply.pkt_id, "DSTR", 4); + reply.counter = rptrbuf.counter; + reply.flag[0] = 0x72U; + memset(reply.flag+1, 0, 3); + sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + } - if (recvlen == 58) { + if (recvlen == 58) { - if (bool_qso_details) - printf("START from rptr: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - rptrbuf.counter, - rptrbuf.vpkt.streamid, - rptrbuf.vpkt.hdr.flag[0], rptrbuf.vpkt.hdr.flag[1], rptrbuf.vpkt.hdr.flag[2], - rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, rptrbuf.vpkt.hdr.ur, - rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, recvlen, inet_ntoa(fromRptr.sin_addr)); + if (bool_qso_details) + printf("START from rptr: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", + ntohs(rptrbuf.counter), + ntohs(rptrbuf.vpkt.streamid), + rptrbuf.vpkt.hdr.flag[0], rptrbuf.vpkt.hdr.flag[1], rptrbuf.vpkt.hdr.flag[2], + rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, rptrbuf.vpkt.hdr.ur, + rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, recvlen, inet_ntoa(fromRptr.sin_addr)); - if ((memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* MYCALL is NOT this repeater */ - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BREAK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28))) { /* EMR + BREAK */ + if ((memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ + /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* MYCALL is NOT this repeater */ + ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ + (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ + (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BREAK */ + (rptrbuf.vpkt.hdr.flag[0] == 0x28))) { /* EMR + BREAK */ - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (got a header)\n", i); - dtmf_last_frame[i] = 0; - dtmf_counter[i] = 0; - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; + if (i>=0 && i<3) { + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (got a header)\n", i); + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0; + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; - /* Initialize the LAST HEARD data for the band */ + /* Initialize the LAST HEARD data for the band */ - band_txt[i].streamID = rptrbuf.vpkt.streamid; + band_txt[i].streamID = rptrbuf.vpkt.streamid; - memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); + memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); - memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); - band_txt[i].lh_mycall[8] = '\0'; + memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); + band_txt[i].lh_mycall[8] = '\0'; - memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); - band_txt[i].lh_sfx[4] = '\0'; + memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); + band_txt[i].lh_sfx[4] = '\0'; - memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); - band_txt[i].lh_yrcall[8] = '\0'; + memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); + band_txt[i].lh_yrcall[8] = '\0'; - memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); - band_txt[i].lh_rpt1[8] = '\0'; + memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); + band_txt[i].lh_rpt1[8] = '\0'; - memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].lh_rpt2[8] = '\0'; + memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].lh_rpt2[8] = '\0'; - time(&band_txt[i].last_time); + time(&band_txt[i].last_time); - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].txt_stats_sent = false; + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].txt_stats_sent = false; - band_txt[i].dest_rptr[0] = '\0'; + band_txt[i].dest_rptr[0] = '\0'; - /* try to process GPS mode: GPRMC and ID */ - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - // band_txt[i].gps_last_time = 0; DO NOT reset it + /* try to process GPS mode: GPRMC and ID */ + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + // band_txt[i].gps_last_time = 0; DO NOT reset it - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; - /* select the band for aprs processing, and lock on the stream ID */ - if (bool_send_aprs) - aprs->SelectBand(i, rptrbuf.vpkt.streamid); + /* select the band for aprs processing, and lock on the stream ID */ + if (bool_send_aprs) + aprs->SelectBand(i, rptrbuf.vpkt.streamid); + } } - } - - /* Is MYCALL valid ? */ - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); - temp_radio_user[8] = '\0'; - int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); + /* Is MYCALL valid ? */ + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); + temp_radio_user[8] = '\0'; - if (mycall_valid == REG_NOERROR) - ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); - else { - if (mycall_valid == REG_NOMATCH) - printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); - else - printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); - } + int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); - /* send data qnlink */ - if (mycall_valid == REG_NOERROR) - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + if (mycall_valid == REG_NOERROR) + ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); + else { + if (mycall_valid == REG_NOMATCH) + printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); + else + printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); + } - if ((mycall_valid == REG_NOERROR) && - (memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) != 0) && /* not a reflector */ - (memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) != 0) && /* not a reflector */ - (memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) != 0) && /* not a reflector */ - (rptrbuf.vpkt.hdr.ur[0] != ' ') && /* must have something */ - (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) != 0)) { /* urcall is NOT CQCQCQ */ - if ((rptrbuf.vpkt.hdr.ur[0] == '/') && /* urcall starts with a slash */ - (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ - (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ - (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ - - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ - ) { - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6) != 0) { /* the value after the slash in urcall, is NOT this repeater */ - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - /* YRCALL=/repeater + mod */ - /* YRCALL=/KJ4NHFB */ - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); - temp_radio_user[6] = ' '; - temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; - if (temp_radio_user[7] == ' ') - temp_radio_user[7] = 'A'; - temp_radio_user[CALL_SIZE] = '\0'; - - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); - if (result) { /* it is a repeater */ - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that saved port instead of the default port - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set yrcall, can NOT let it be slash and repeater + module */ - memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); - - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - // send to remote gateway - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - 56); - - time(&(to_remote_g2[i].last_time)); - } - } - } - } - } else if ((memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) != 0) && /* urcall is not this repeater */ - (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ - (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ - (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ - - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ - ) { - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); - temp_radio_user[8] = '\0'; - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); - if (result) { - /* destination is a remote system */ - if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { + /* send data qnlink */ + if (mycall_valid == REG_NOERROR) + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + if ((mycall_valid == REG_NOERROR) && + (memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) != 0) && /* not a reflector */ + (memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) != 0) && /* not a reflector */ + (memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) != 0) && /* not a reflector */ + (rptrbuf.vpkt.hdr.ur[0] != ' ') && /* must have something */ + (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) != 0)) { /* urcall is NOT CQCQCQ */ + if ((rptrbuf.vpkt.hdr.ur[0] == '/') && /* urcall starts with a slash */ + (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ + ((rptrbuf.vpkt.hdr.r1[7] == 'A') || + (rptrbuf.vpkt.hdr.r1[7] == 'B') || + (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ + (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ + (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ + /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ + + ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ + (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ + (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ + (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ + ) { + if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6) != 0) { /* the value after the slash in urcall, is NOT this repeater */ int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; if (i>=0 && i<3) { /* one radio user on a repeater module at a time */ if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - /* send to remote gateway */ - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s, port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - 56); - - time(&(to_remote_g2[i].last_time)); + /* YRCALL=/repeater + mod */ + /* YRCALL=/KJ4NHFB */ + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); + temp_radio_user[6] = ' '; + temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; + if (temp_radio_user[7] == ' ') + temp_radio_user[7] = 'A'; + temp_radio_user[CALL_SIZE] = '\0'; + + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); + if (result) { /* it is a repeater */ + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that saved port instead of the default port + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set yrcall, can NOT let it be slash and repeater + module */ + memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); + + /* set PFCS */ + calcPFCS(g2buf.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + // send to remote gateway + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("Routing to IP=%s port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", + inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.streamid, g2buf.hdr.mycall, + g2buf.hdr.sfx, g2buf.hdr.urcall, + g2buf.hdr.rpt1, g2buf.hdr.rpt2, + 56); + + time(&(to_remote_g2[i].last_time)); + } } } - } else { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* the user we are trying to contact is on our gateway */ - /* make sure they are on a different module */ - if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { - /* - The remote repeater has been set, lets fill in the dest_rptr - so that later we can send that to the LIVE web site - */ - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[7] = temp_mod; - band_txt[i].dest_rptr[8] = '\0'; - - i = temp_mod - 'A'; + } + } else if ((memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) != 0) && /* urcall is not this repeater */ + (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ + ((rptrbuf.vpkt.hdr.r1[7] == 'A') || + (rptrbuf.vpkt.hdr.r1[7] == 'B') || + (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ + (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ + (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ + /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ + + ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ + (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ + (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ + (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ + ) { + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); + temp_radio_user[8] = '\0'; + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); + if (result) { + /* destination is a remote system */ + if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set PFCS */ + calcPFCS(g2buf.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + /* send to remote gateway */ + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("Routing to IP=%s, port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", + inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.streamid, g2buf.hdr.mycall, + g2buf.hdr.sfx, g2buf.hdr.urcall, + g2buf.hdr.rpt1, g2buf.hdr.rpt2, + 56); + + time(&(to_remote_g2[i].last_time)); + } + } + } else { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - /* valid destination repeater module? */ - if (i>=0 && i<3) { + if (i>=0 && i<3) { + /* the user we are trying to contact is on our gateway */ + /* make sure they are on a different module */ + if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { /* - toRptr[i] : receiving from a remote system or cross-band - band_txt[i] : local RF is talking. + The remote repeater has been set, lets fill in the dest_rptr + so that later we can send that to the LIVE web site */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[7] = temp_mod; + band_txt[i].dest_rptr[8] = '\0'; + + i = temp_mod - 'A'; + + /* valid destination repeater module? */ + if (i>=0 && i<3) { + /* + toRptr[i] : receiving from a remote system or cross-band + band_txt[i] : local RF is talking. + */ + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - rptrbuf.vpkt.hdr.r2[7] = temp_mod; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); + rptrbuf.vpkt.hdr.r2[7] = temp_mod; + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; + /* bump the G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER++; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } } - } - } else - printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); + } else + printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); + } } } } - } - } else if ((rptrbuf.vpkt.hdr.ur[7] == '0') && - (rptrbuf.vpkt.hdr.ur[6] == 'C') && - (rptrbuf.vpkt.hdr.ur[0] == ' ')) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - unlink(vm[i].file); - printf("removed voicemail file: %s\n", vm[i].file); - vm[i].file[0] = '\0'; - } else - printf("No voicemail to clear or still recording\n"); - } - } else if ((rptrbuf.vpkt.hdr.ur[7] == '0') && - (rptrbuf.vpkt.hdr.ur[6] == 'R') && - (rptrbuf.vpkt.hdr.ur[0] == ' ')) { - int i = -1; - switch (rptrbuf.vpkt.hdr.r1[7]) { - case 'A': - i = 0; - break; - case 'B': - i = 1; - break; - case 'C': - i = 2; - break; - } - - if (i >= 0) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); - } catch (const std::exception &e) { - printf("Filed to start voicemail playback. Exception: %s\n", e.what()); - } - } else - printf("No voicemail to recall or still recording\n"); - } - } else if ((rptrbuf.vpkt.hdr.ur[7] == '0') && - (rptrbuf.vpkt.hdr.ur[6] == 'S') && - (rptrbuf.vpkt.hdr.ur[0] == ' ')) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (vm[i].fd >= 0) - printf("Already recording for voicemail on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", - echotest_dir.c_str(), - rptrbuf.vpkt.hdr.r1[7], - "voicemail.dat"); - - vm[i].fd = open(tempfile, - O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (vm[i].fd < 0) - printf("Failed to create file %s for voicemail\n", tempfile); - else { - strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", - rptrbuf.vpkt.hdr.r1[7], - vm[i].file); + } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + unlink(vm[i].file); + printf("removed voicemail file: %s\n", vm[i].file); + vm[i].file[0] = '\0'; + } else + printf("No voicemail to clear or still recording\n"); + } + } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - time(&vm[i].last_time); - vm[i].streamid = rptrbuf.vpkt.streamid; + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); + } catch (const std::exception &e) { + printf("Filed to start voicemail playback. Exception: %s\n", e.what()); + } + } else + printf("No voicemail to recall or still recording\n"); + } + } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(vm[i].fd, "DVTOOL", 6); - (void)write(vm[i].fd, &num_recs, 4); - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); + if (i>=0 && i<3) { + if (vm[i].fd >= 0) + printf("Already recording for voicemail on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", + echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], + "voicemail.dat"); + + vm[i].fd = open(tempfile, + O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (vm[i].fd < 0) + printf("Failed to create file %s for voicemail\n", tempfile); + else { + strcpy(vm[i].file, tempfile); + printf("Recording mod %c for voicemail into file:[%s]\n", + rptrbuf.vpkt.hdr.r1[7], vm[i].file); + + time(&vm[i].last_time); + vm[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + rec_len = 56; + (void)write(vm[i].fd, "DVTOOL", 6); + (void)write(vm[i].fd, &num_recs, 4); + (void)write(vm[i].fd, &rec_len, 2); + (void)write(vm[i].fd, &recbuf, rec_len); + } } } - } - } else if (('E' == rptrbuf.vpkt.hdr.ur[7]) && (' ' == rptrbuf.vpkt.hdr.ur[0])) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (recd[i].fd >= 0) - printf("Already recording for echotest on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), - rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - - recd[i].fd = open(tempfile, - O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (recd[i].fd < 0) - printf("Failed to create file %s for echotest\n", tempfile); - else { - strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", - rptrbuf.vpkt.hdr.r1[7], recd[i].file); + } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - time(&recd[i].last_time); - recd[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(recd[i].fd, "DVTOOL", 6); - (void)write(recd[i].fd, &num_recs, 4); - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); + if (i>=0 && i<3) { + if (recd[i].fd >= 0) + printf("Already recording for echotest on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), + rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); + + recd[i].fd = open(tempfile, + O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (recd[i].fd < 0) + printf("Failed to create file %s for echotest\n", tempfile); + else { + strcpy(recd[i].file, tempfile); + printf("Recording mod %c for echotest into file:[%s]\n", + rptrbuf.vpkt.hdr.r1[7], recd[i].file); + + time(&recd[i].last_time); + recd[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + rec_len = 56; + (void)write(recd[i].fd, "DVTOOL", 6); + (void)write(recd[i].fd, &num_recs, 4); + (void)write(recd[i].fd, &rec_len, 2); + (void)write(recd[i].fd, &recbuf, rec_len); + } } } - } - /* check for cross-banding */ - } else if (0 == (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6)) && /* yrcall is CQCQCQ */ - (0 == memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7)) && /* rpt1 is this repeater */ - (0 == memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7)) && /* rpt2 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod of rpt1 is A,B,C */ - ((rptrbuf.vpkt.hdr.r2[7] == 'A') || - (rptrbuf.vpkt.hdr.r2[7] == 'B') || - (rptrbuf.vpkt.hdr.r2[7] == 'C')) && /* !!! usually a G of rpt2, but we see A,B,C */ - (rptrbuf.vpkt.hdr.r2[7] != rptrbuf.vpkt.hdr.r1[7])) { /* cross-banding? make sure NOT the same */ - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[8] = '\0'; - } + /* check for cross-banding */ + } else if (0 == (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6)) && /* yrcall is CQCQCQ */ + (0 == memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7)) && /* rpt1 is this repeater */ + (0 == memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7)) && /* rpt2 is this repeater */ + ((rptrbuf.vpkt.hdr.r1[7] == 'A') || + (rptrbuf.vpkt.hdr.r1[7] == 'B') || + (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod of rpt1 is A,B,C */ + ((rptrbuf.vpkt.hdr.r2[7] == 'A') || + (rptrbuf.vpkt.hdr.r2[7] == 'B') || + (rptrbuf.vpkt.hdr.r2[7] == 'C')) && /* !!! usually a G of rpt2, but we see A,B,C */ + (rptrbuf.vpkt.hdr.r2[7] != rptrbuf.vpkt.hdr.r1[7])) { /* cross-banding? make sure NOT the same */ + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[8] = '\0'; + } - i = rptrbuf.vpkt.hdr.r2[7] - 'A'; + i = rptrbuf.vpkt.hdr.r2[7] - 'A'; - /* valid destination repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] : receiving from a remote system or cross-band - // band_txt[i] : local RF is talking. - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); + /* valid destination repeater module? */ + if (i>=0 && i<3) { + // toRptr[i] : receiving from a remote system or cross-band + // band_txt[i] : local RF is talking. + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - /* bump the G2 counter */ - toRptr[i].G2_COUNTER ++; + /* bump the G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER ++; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } } } - } - } else { // recvlen != 58 - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - time(&band_txt[i].last_time); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { // end of voice data - if (dtmf_buf_count[i] > 0) { - dtmf_file = dtmf_dir; - dtmf_file.push_back('/'); - dtmf_file.push_back('A'+i); - dtmf_file += "_mod_DTMF_NOTIFY"; - if (bool_dtmf_debug) - printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); - FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); - if (dtmf_fp) { - fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); - fclose(dtmf_fp); - } else - printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - - - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - dtmf_counter[i] = 0; - dtmf_last_frame[i] = 0; - } - - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_yrcall, - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].num_dv_frames, - band_txt[i].num_dv_silent_frames, - band_txt[i].num_bit_errors); - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - - band_txt[i].dest_rptr[0] = '\0'; + } else { // recvlen is 29 or 32 + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + time(&band_txt[i].last_time); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { // end of voice data + if (dtmf_buf_count[i] > 0) { + dtmf_file = dtmf_dir; + dtmf_file.push_back('/'); + dtmf_file.push_back('A'+i); + dtmf_file += "_mod_DTMF_NOTIFY"; + if (bool_dtmf_debug) + printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); + FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); + if (dtmf_fp) { + fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); + fclose(dtmf_fp); + } else + printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - } else { // not the end of the voice stream - int ber_data[3]; - int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); - if (ber_data[0] == 0xf85) - band_txt[i].num_dv_silent_frames++; - band_txt[i].num_bit_errors += ber_errs; - band_txt[i].num_dv_frames++; - - if ((ber_data[0] & 0x0ffc) == 0xfc0) { - dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); - if (dtmf_counter[i] > 0) { - if (dtmf_last_frame[i] != dtmf_digit) - dtmf_counter[i] = 0; + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + dtmf_counter[i] = 0; + dtmf_last_frame[i] = 0; } - dtmf_last_frame[i] = dtmf_digit; - dtmf_counter[i]++; - - if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { - if (dtmf_buf_count[i] < MAX_DTMF_BUF) { - const char *dtmf_chars = "147*2580369#ABCD"; - dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; - dtmf_buf_count[i]++; + + ii->sendHeardWithTXStats(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_yrcall, + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].num_dv_frames, + band_txt[i].num_dv_silent_frames, + band_txt[i].num_bit_errors); + + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + } else { // not the end of the voice stream + int ber_data[3]; + int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); + if (ber_data[0] == 0xf85) + band_txt[i].num_dv_silent_frames++; + band_txt[i].num_bit_errors += ber_errs; + band_txt[i].num_dv_frames++; + + if ((ber_data[0] & 0x0ffc) == 0xfc0) { + dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); + if (dtmf_counter[i] > 0) { + if (dtmf_last_frame[i] != dtmf_digit) + dtmf_counter[i] = 0; } - } - const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); - } else - dtmf_counter[i] = 0; + dtmf_last_frame[i] = dtmf_digit; + dtmf_counter[i]++; + + if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { + if (dtmf_buf_count[i] < MAX_DTMF_BUF) { + const char *dtmf_chars = "147*2580369#ABCD"; + dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; + dtmf_buf_count[i]++; + } + } + const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; + memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); + } else + dtmf_counter[i] = 0; + } + break; } - break; } - } - - if (recvlen == 29) - memcpy(tmp_txt, rptrbuf.vpkt.vasd.text, 3); - else - memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); - // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); + if (recvlen == 29) + memcpy(tmp_txt, rptrbuf.vpkt.vasd.text, 3); + else + memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - /* extract 20-byte RADIO ID */ - if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - if (new_group[i]) { - tmp_txt[0] = tmp_txt[0] ^ 0x70; - header_type = tmp_txt[0] & 0xf0; - - if ((header_type == 0x50) || /* header */ - (header_type == 0xc0)) { /* squelch */ - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } else if (header_type == 0x30) { /* GPS or GPS id or APRS */ - new_group[i] = false; - to_print[i] = tmp_txt[0] & 0x0f; - ABC_grp[i] = false; - if (to_print[i] > 5) - to_print[i] = 5; - else if (to_print[i] < 1) - to_print[i] = 1; - - if ((to_print[i] > 1) && (to_print[i] <= 5)) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + /* extract 20-byte RADIO ID */ + if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { + // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); + // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); + + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + if (new_group[i]) { + tmp_txt[0] = tmp_txt[0] ^ 0x70; + header_type = tmp_txt[0] & 0xf0; + + if ((header_type == 0x50) || /* header */ + (header_type == 0xc0)) { /* squelch */ + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } else if (header_type == 0x30) { /* GPS or GPS id or APRS */ + new_group[i] = false; + to_print[i] = tmp_txt[0] & 0x0f; + ABC_grp[i] = false; + if (to_print[i] > 5) + to_print[i] = 5; + else if (to_print[i] < 1) + to_print[i] = 1; + + if ((to_print[i] > 1) && (to_print[i] <= 5)) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && ((tmp_txt[1] ^ 0x4f) == '$')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + /* fresh GPS string, re-initialize */ + if ((to_print[i] == 5) && ((tmp_txt[1] ^ 0x4f) == '$')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].temp_line_cnt++; - } + /* do not copy CR, NL */ + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].temp_line_cnt++; + } - if (((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); + if (((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] -= 2; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] -= 2; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } + /* do not copy CR, NL */ + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } - if ((tmp_txt[1] ^ 0x4f) == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); + if ((tmp_txt[1] ^ 0x4f) == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((tmp_txt[1] ^ 0x4f) == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[1] ^ 0x4f) == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] --; - } - } else if (header_type == 0x40) { /* ABC text */ - new_group[i] = false; - to_print[i] = 3; - ABC_grp[i] = true; - C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03)?true:false; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt++; - - /* - We should NOT see any more text, - if we already processed text, - so blank out the codes. - */ - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; + to_print[i] --; } - } - - if (band_txt[i].txt_cnt >= 20) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - /*** - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - ***/ - // printf("TEXT1=[%s]\n", band_txt[i].txt); - band_txt[i].txt_cnt = 0; - } - } else { - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - } else { - if (to_print[i] == 3) { - if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].txt_cnt ++; + } else if (header_type == 0x40) { /* ABC text */ + new_group[i] = false; + to_print[i] = 3; + ABC_grp[i] = true; + C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03)?true:false; band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt ++; + band_txt[i].txt_cnt++; band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt ++; + band_txt[i].txt_cnt++; /* We should NOT see any more text, @@ -1818,35 +1807,131 @@ void CQnetGateway::process() } } - if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { + if (band_txt[i].txt_cnt >= 20) { band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - if (!band_txt[i].txt_stats_sent) { - /*** if YRCALL is CQCQCQ, set dest_rptr ***/ - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - // if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - // band_txt[i].dest_rptr[0] = '\0'; + /*** + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].dest_rptr, + band_txt[i].txt); + ***/ + // printf("TEXT1=[%s]\n", band_txt[i].txt); + band_txt[i].txt_cnt = 0; + } + } else { + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } + } else { + if (to_print[i] == 3) { + if (ABC_grp[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].txt_cnt ++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].txt_cnt ++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].txt_cnt ++; + + /* + We should NOT see any more text, + if we already processed text, + so blank out the codes. + */ + if (band_txt[i].txt_stats_sent) { + if (recvlen == 29) { + rptrbuf.vpkt.vasd.text[0] = 0x70; + rptrbuf.vpkt.vasd.text[1] = 0x4f; + rptrbuf.vpkt.vasd.text[2] = 0x93; + } else { + rptrbuf.vpkt.vasd1.text[0] = 0x70; + rptrbuf.vpkt.vasd1.text[1] = 0x4f; + rptrbuf.vpkt.vasd1.text[2] = 0x93; } + } - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_yrcall, - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); - band_txt[i].txt_stats_sent = true; + if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + if (!band_txt[i].txt_stats_sent) { + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + // if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + // band_txt[i].dest_rptr[0] = '\0'; + } + + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_yrcall, + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].dest_rptr, + band_txt[i].txt); + // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); + band_txt[i].txt_stats_sent = true; + } + band_txt[i].txt_cnt = 0; + } + if (C_seen[i]) + C_seen[i] = false; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].temp_line_cnt++; + } + + if ( + ((tmp_txt[0] ^ 0x70) == '\r') || + ((tmp_txt[1] ^ 0x4f) == '\r') || + ((tmp_txt[2] ^ 0x93) == '\r') + ) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (((tmp_txt[0] ^ 0x70) == '\n') || + ((tmp_txt[1] ^ 0x4f) == '\n') || + ((tmp_txt[2] ^ 0x93) == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - band_txt[i].txt_cnt = 0; } - if (C_seen[i]) - C_seen[i] = false; - } else { + } else if (to_print[i] == 2) { /* something went wrong? all bets are off */ if (band_txt[i].temp_line_cnt > 200) { printf("Reached the limit in the OLD gps mode\n"); @@ -1863,16 +1948,8 @@ void CQnetGateway::process() band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].temp_line_cnt++; - } - if ( - ((tmp_txt[0] ^ 0x70) == '\r') || - ((tmp_txt[1] ^ 0x4f) == '\r') || - ((tmp_txt[2] ^ 0x93) == '\r') - ) { + if (((tmp_txt[0] ^ 0x70) == '\r') || ((tmp_txt[1] ^ 0x4f) == '\r')) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1884,171 +1961,94 @@ void CQnetGateway::process() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || - ((tmp_txt[1] ^ 0x4f) == '\n') || - ((tmp_txt[2] ^ 0x93) == '\n')) { + } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } - } - } else if (to_print[i] == 2) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } - - if (((tmp_txt[0] ^ 0x70) == '\r') || ((tmp_txt[1] ^ 0x4f) == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); + } else if (to_print[i] == 1) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } else if (to_print[i] == 1) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } + /* do not copy CR, NL */ + if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].temp_line_cnt++; + } - if ((tmp_txt[0] ^ 0x70) == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); + if ((tmp_txt[0] ^ 0x70) == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((tmp_txt[0] ^ 0x70) == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[0] ^ 0x70) == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; } + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; } - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; + break; } - break; } } - } - - /* send data to qnlink */ - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - /* aprs processing */ - if (bool_send_aprs) - // streamID seq audio+text size - aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); - - for (int i=0; i<3; i++) { - /* find out if data must go to the remote G2 */ - if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x20; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; - memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); - if (recvlen == 29) - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + /* send data to qnlink */ + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - time(&(to_remote_g2[i].last_time)); + /* aprs processing */ + if (bool_send_aprs) + // streamID seq audio+text size + aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); - /* Is this the end-of-stream */ - if (rptrbuf.vpkt.ctrl & 0x40) { - memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - break; - } else - /* Is the data to be recorded for echotest */ - if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { - time(&recd[i].last_time); - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + for (int i=0; i<3; i++) { + /* find out if data must go to the remote G2 */ + if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x20; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; + memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - rec_len = 27; - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); + uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); + time(&(to_remote_g2[i].last_time)); - /* we are in echotest mode, so play it back */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); - } catch (const std::exception &e) { - printf("failed to start PlayFileThread. Exception: %s\n", e.what()); - // When the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } + /* Is this the end-of-stream */ + if (rptrbuf.vpkt.ctrl & 0x40) { + memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; } break; } else - /* Is the data to be recorded for voicemail */ - if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { - time(&vm[i].last_time); + /* Is the data to be recorded for echotest */ + if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { + time(&recd[i].last_time); memcpy(recbuf.title, "DSVT", 4); recbuf.config = 0x20; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; @@ -2059,43 +2059,87 @@ void CQnetGateway::process() memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); rec_len = 27; - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); + (void)write(recd[i].fd, &rec_len, 2); + (void)write(recd[i].fd, &recbuf, rec_len); if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* we are in echotest mode, so play it back */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); + } catch (const std::exception &e) { + printf("failed to start PlayFileThread. Exception: %s\n", e.what()); + // When the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } } break; } else - /* or maybe this is cross-banding data */ - if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); - - /* bump G2 counter */ - toRptr[i].G2_COUNTER ++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - /* End of stream ? */ - if (rptrbuf.vpkt.ctrl & 0x40) { - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; + /* Is the data to be recorded for voicemail */ + if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { + time(&vm[i].last_time); + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x20; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + rec_len = 27; + (void)write(vm[i].fd, &rec_len, 2); + (void)write(vm[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); } break; - } - } + } else + /* or maybe this is cross-banding data */ + if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* timeit */ + time(&toRptr[i].last_time); + + /* bump G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER ++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (rptrbuf.vpkt.ctrl & 0x40) { + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; + } + } - if (rptrbuf.vpkt.ctrl & 0x40) { - if (bool_qso_details) - printf("END from rptr: cntr=%04x, streamID=%04x, %d bytes\n", rptrbuf.counter, rptrbuf.vpkt.streamid, recvlen); + if (rptrbuf.vpkt.ctrl & 0x40) { + if (bool_qso_details) + printf("END from rptr: cntr=%04x, streamID=%04x, %d bytes\n", ntohs(rptrbuf.counter), ntohs(rptrbuf.vpkt.streamid), recvlen); + } } } } @@ -2328,12 +2372,10 @@ void CQnetGateway::APRSBeaconThread() void CQnetGateway::PlayFileThread(char *file) { - unsigned short rlen = 0; - unsigned char dstar_buf[56]; - unsigned char rptr_buf[58]; - short int i = 0; - struct sigaction act; + SDSTR dstr; + SDSVT dsvt; + struct sigaction act; act.sa_handler = sigCatch; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; @@ -2358,14 +2400,14 @@ void CQnetGateway::PlayFileThread(char *file) return; } - size_t nread = fread(dstar_buf, 10, 1, fp); + size_t nread = fread(dstr.pkt_id, 10, 1, fp); if (nread != 1) { printf("Cant read first 10 bytes in %s\n", file); fclose(fp); return; } - if (memcmp(dstar_buf, "DVTOOL", 6) != 0) { + if (memcmp(dstr.pkt_id, "DVTOOL", 6) != 0) { printf("DVTOOL keyword not found in %s\n", file); fclose(fp); return; @@ -2373,6 +2415,7 @@ void CQnetGateway::PlayFileThread(char *file) sleep(play_wait); while (keep_running) { + unsigned short rlen; nread = fread(&rlen, 2, 1, fp); if (nread != 1) break; @@ -2381,54 +2424,51 @@ void CQnetGateway::PlayFileThread(char *file) printf("Expected 56 bytes or 27 bytes, found %d\n", rlen); break; } - nread = fread(dstar_buf, rlen, 1, fp); + nread = fread(dsvt.title, rlen, 1, fp); if (nread == 1) { - if (memcmp(dstar_buf, "DSVT", 4) != 0) { + if (memcmp(dsvt.title, "DSVT", 4) != 0) { printf("DVST keyword not found in %s\n", file); break; } - if (dstar_buf[8] != 0x20) { + if (dsvt.id != 0x20) { printf("Not Voice type in %s\n", file); break; } - if ((dstar_buf[4] != 0x10) && (dstar_buf[4] != 0x20)) { + if ((dsvt.config != 0x10) && (dsvt.config != 0x20)) { printf("Not a valid record type in %s\n",file); break; } + int i; if (rlen == 56) { /* which module is this recording for? */ - if (dstar_buf[25] == 'A') - i = 0; - else if (dstar_buf[25] == 'B') - i = 1; - else if (dstar_buf[25] == 'C') - i = 2; - - memcpy(rptr_buf, "DSTR", 4); - rptr_buf[5] = (unsigned char)(toRptr[i].G2_COUNTER & 0xff); - rptr_buf[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); - rptr_buf[6] = 0x73; - rptr_buf[7] = 0x12; - rptr_buf[8] = 0x00; - rptr_buf[9] = 0x30; - rptr_buf[10] = 0x20; - memcpy(rptr_buf + 11, dstar_buf + 9, 47); + i = dsvt.hdr.rpt1[7] - 'A'; + if (i<0 || i>2) { + printf("found a bad module destination for this file!\n"); + fclose(fp); + return; + } + + memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x00; + dstr.remaining = 0x30; + dstr.vpkt.icm_id = 0x20; + memcpy(&dstr.vpkt.dst_rptr_id, dsvt.flagb, 47); /* We did not change anything */ // calcPFCS(rptr_buf, 58); } else { - rptr_buf[5] = (unsigned char)(toRptr[i].G2_COUNTER & 0xff); - rptr_buf[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); - rptr_buf[9] = 0x13; - memcpy(rptr_buf + 11, dstar_buf + 9, 18); + dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + dstr.remaining = 0x13; + memcpy(&dstr.vpkt.dst_rptr_id, dsvt.flagb, 18); } - sendto(srv_sock, rptr_buf, rlen + 2, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - toRptr[i].G2_COUNTER ++; + sendto(srv_sock, dstr.pkt_id, rlen+2, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); std::this_thread::sleep_for(std::chrono::milliseconds(play_delay)); } @@ -2560,7 +2600,7 @@ int CQnetGateway::init(char *cfgfile) // Open G2 INTERNAL: // default non-icom 127.0.0.1:19000 - // default icom 172.10.0.5:20000 + // default icom 172.16.0.1:20000 srv_sock = open_port(g2_internal); if (0 > srv_sock) { printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); From 513020b919946605e048ad78925c8e00d7d03490 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 28 Jun 2018 07:36:59 -0700 Subject: [PATCH 020/553] fixed errors with symbolic links --- Makefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index e6dae12..08043f9 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -91,7 +91,7 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -108,7 +108,7 @@ installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -116,7 +116,7 @@ installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -127,7 +127,7 @@ installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -135,7 +135,7 @@ installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -152,7 +152,7 @@ installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -160,7 +160,7 @@ installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -174,7 +174,7 @@ installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg systemctl start qndvrptr.service installdtmf : qndtmf - /bin/ln -s qndtmf $(BINDIR) + /bin/ln -s $(shell pwd)/qndtmf $(BINDIR) /bin/cp -f system/qndtmf.service $(SYSDIR) systemctl enable qndtmf.service systemctl daemon-reload @@ -182,7 +182,7 @@ installdtmf : qndtmf installmmdvm : $(MMPATH)/MMDVMhost $(MMPATH)/MMDVM.qn /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) - /bin/ln -s $(MMPATH)/MMDVM.qn $(CFGDIR) + cd $(MMPATH) ; /bin/ln -s $(shell pwd)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) /bin/cp -f system/mmdvm.timer $(SYSDIR) systemctl enable mmdvm.timer From b55268fc676d6f44626ab8fc9729af9c03d17147 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 28 Jun 2018 08:09:29 -0700 Subject: [PATCH 021/553] fixed symbolic link errors --- Makefile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index d94590b..7361e32 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -90,7 +90,7 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -107,7 +107,7 @@ installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -115,7 +115,7 @@ installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -132,7 +132,7 @@ installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s qn.cfg $(CFGDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -140,7 +140,7 @@ installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s gwys.txt $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) /bin/cp -f exec_?.sh $(CFGDIR) /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service @@ -154,7 +154,7 @@ installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg systemctl start qndvrptr.service installdtmf : qndtmf - /bin/ln -s qndtmf $(BINDIR) + /bin/ln -s $(shell pwd)/qndtmf $(BINDIR) /bin/cp -f system/qndtmf.service $(SYSDIR) systemctl enable qndtmf.service systemctl daemon-reload @@ -162,7 +162,7 @@ installdtmf : qndtmf installmmdvm : $(MMPATH)/MMDVMhost $(MMPATH)/MMDVM.qn /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) - /bin/ln -s $(MMPATH)/MMDVM.qn $(CFGDIR) + cd $(MMPATH) ; /bin/ln -s $(shell pwd)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) /bin/cp -f system/mmdvm.timer $(SYSDIR) systemctl enable mmdvm.timer From 5ea82cc621fd0e552be3c2e6121ff7a79ff4357a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 28 Jun 2018 10:16:52 -0700 Subject: [PATCH 022/553] icom additions --- Makefile | 1 + qn.everything.cfg | 4 +- qn.icom.cfg | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 qn.icom.cfg diff --git a/Makefile b/Makefile index 08043f9..2db4283 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ all : $(ALL_PROGRAMS) mmdvm : $(MDV_PROGRAMS) dvap : $(DVP_PROGRAMS) dvrptr : $(DVR_PROGRAMS) +icom : $(ICM_PROGRAMS) qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread diff --git a/qn.everything.cfg b/qn.everything.cfg index e9dfa08..ca3cc03 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -1,10 +1,10 @@ -# g2_ircddb Configuration +# Qnet Gateway Configuration ircddb = { login = "CHANGEME!!!!"; # login callsign for the ircDDB network # host = "rr.openquad.net" # other include group1-irc.ircddb.net # port = 9007 # not a good idea to change! -# password = "1111111111111" # not needed for Openquad +# password = "1111111111111" # not needed for rr.openquad.net } gateway = { diff --git a/qn.icom.cfg b/qn.icom.cfg new file mode 100644 index 0000000..8547e7c --- /dev/null +++ b/qn.icom.cfg @@ -0,0 +1,139 @@ +# Qnet Gateway Configuration + +ircddb = { + login = "CHANGEME!!!!"; # login callsign for the ircDDB network +# host = "rr.openquad.net" # other include group1-irc.ircddb.net +# port = 9007 # not a good idea to change! +# password = "1111111111111" # not needed for rr.openquad.net +} + +gateway = { +# regen_header = true # regenerate headers from incoming data +# send_qrgs_maps = true # send frequecy, offset, cooridinates and url to irc-server +# local_irc_ip = "0.0.0.0" # 0.0.0.0 means accept any incoming connections +# aprs_send = true # send info to aprs +# ip = "127.0.0.1" # where the gateway is running + + external = { +# ip = "0.0.0.0" +# port = 40000 + } + + internal = { +# ip = "172.16.0.1" +# port = 20000 + } +} + +#module = { + a = { # an ICOM full stack might consist of up to three module + # Sorry, the 23 cm data module is not yet supported + # 23 cm module will use "a" + # 70 cm module will use "b" + # 2 M module will use "c" +# type = "icom" # you must define at least one module by uncommenting the type +# ip = "172.16.0.20" # all icom modules should have the same IP address +# port = 20000 # all icom modules should have the same UDP port +# frequency = 0 # in MHz, if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +# offset = 0 +# range = 0 # the range of this repeater, in meters. 1609.344 meters is 1.0 miles +# agl = 0 # the height above ground level for this repeater's antenna +# latitude = 0 # you can leave this unspecified for a mobile rig +# longitude = 0 # like the latitude +# desc1 = "" # maximum of 20 characters, most special symbols are not allowed +# desc2 = "" # just like desc1 +# url = "github.com/n7tae/g2_ircddb" # 80 characters max + } + + b = { +# type = "icom" +# ip = "172.16.0.20" # all icom modules should have the same IP address +# port = 20000 # all icom modules should have the same UDP port +# frequency = 0 +# offset = 0 +# range = 0.0 +# agl = 0.0 +# latitude = 0.0 +# longitude = 0.0 +# desc1 = "" +# desc2 = "" +# url = "github.com/n7tae/g2_ircddb" + } + + c = { +# type = "icom" +# ip = "172.16.0.20" # all icom modules should have the same IP address +# port = 20000 # all icom modules should have the same UDP port +# frequency = 0 +# dvap_offset = 0 +# range = 0.0 +# agl = 0.0 +# latitude = 0.0 +# longitude = 0.0 +# desc1 = "" +# desc2 = "" +# url = "github.com/n7tae/QnetGateway" + } +} + +log = { + # debuging and extra logging switches +# qso = false # QSO info goes into the log +# irc = false # IRC debug info +# dtmf = false # DTMF debug info +} + +aprs = { # APRS.NET connect info +# host = "rotate.aprs.net" +# port = 14580 +# interval = 40 +# filter = "" +} + +link = { +# link_at_start = "NONE" # Link to a reflector at startup. +# to link repeater module B to REF001 C, use "BREF001C" +# ref_login = "" # for loging into REF reflectors, if undefined, ircddb.username will be used +# admin = [ "CALL1", "CALL2", "CALL3" ] # only these users can execute scripts, block dongles and reload the gwys.txt +# # you probabaly want you own callsign in the admin list! +# link_unlink = [ "CALL4", "CALL5", "CALL6" ] # if defined, only these users can link and unlink a repeater +# no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist +# # if the blacklist is defined (even if it's empty), the link_unlink will not be read +# incoming_ip = "0.0.0.0" # incoming ip address, "0.0.0.0" means accepts all connections. +# ip = "127.0.0.1" # where g2_link is running +# port = 18997 # port for communications to g2_link +# ref_port = 20001 # port for REF linking, don't change +# xrf_port = 30001 # port for XRF linking, don't change +# dcs_port = 30051 # port for DCS linking, don't change +# announce = true # do link, unlink, etc. announcements +# acknowledge = true # send text acknowledgement on key-up +# max_dongles = 5 # maximum number of linked hotspots +} + +file = { +# status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services +# DTMF = "/tmp" # +# echotest = "/tmp" # echo dat files will end up here +# gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is. +# announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements +} + +timing = { + timeout = { +# echo = 1 # delay time in seconds for echo +# voicemail = 1 # delay time for voicemail +# remote_g2 = 2 # after this many seconds with no packets, we assume the tx is closed +# local_rptr = 1 # local timeout, in seconds + } + + play = { +# wait = 2 # seconds before playback occurs, between 1 and 10 +# delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds + } + + inactivity = { +# a = 0 # unlink repeater if no activity for this many minutes +# b = 0 # zero mean there will be no timer +# c = 0 + } +} From f099c2d1a6769d826843f47409223115a3e20801 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 28 Jun 2018 10:41:36 -0700 Subject: [PATCH 023/553] icom mod error --- QnetGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index fdb3d2c..884a831 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -263,7 +263,7 @@ bool CQnetGateway::read_config(char *cfgFile) if (0 == strcasecmp(type.c_str(), "icom")) { rptr.mod[m].package_version = ICOM_VERSION; is_icom = true; - } if (0 == strcasecmp(type.c_str(), "dvap")) { + } else if (0 == strcasecmp(type.c_str(), "dvap")) { rptr.mod[m].package_version = DVAP_VERSION; is_not_icom = true; } else if (0 == strcasecmp(type.c_str(), "dvrptr")) { From 1b3be29032751b4224cf55d5d864792e4ccc1a6d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 28 Jun 2018 16:43:53 -0700 Subject: [PATCH 024/553] example correction --- qn.icom.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qn.icom.cfg b/qn.icom.cfg index 8547e7c..3fe812b 100644 --- a/qn.icom.cfg +++ b/qn.icom.cfg @@ -25,7 +25,7 @@ gateway = { } } -#module = { +module = { a = { # an ICOM full stack might consist of up to three module # Sorry, the 23 cm data module is not yet supported # 23 cm module will use "a" From aa99b21b4037bb33818700fca8babc8fca7b853e Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 28 Jun 2018 21:13:56 -0700 Subject: [PATCH 025/553] swapped r1,r2 in dvap and SDSTR --- DVAPDongle.h | 4 ++-- QnetDVAP.cpp | 16 +++++++++------- QnetTypeDefs.h | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/DVAPDongle.h b/DVAPDongle.h index b4d0636..78f6fce 100644 --- a/DVAPDongle.h +++ b/DVAPDongle.h @@ -18,7 +18,7 @@ */ #include - + enum REPLY_TYPE { RT_TIMEOUT, RT_ERR, @@ -66,8 +66,8 @@ typedef struct dvp_register_tag { union { struct { unsigned char flag[3]; - unsigned char rpt1[8]; unsigned char rpt2[8]; + unsigned char rpt1[8]; unsigned char urcall[8]; unsigned char mycall[8]; unsigned char sfx[4]; diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 540e97a..57c7266 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -402,12 +402,12 @@ static void readFrom20000() } /* check the module and gateway */ - if (net_buf.vpkt.hdr.r2[7] != RPTR_MOD) { + if (net_buf.vpkt.hdr.r1[7] != RPTR_MOD) { FD_CLR(insock, &readfd); break; } - memcpy(net_buf.vpkt.hdr.r1, OWNER, 7); - net_buf.vpkt.hdr.r1[7] = 'G'; + memcpy(net_buf.vpkt.hdr.r2, OWNER, 7); + net_buf.vpkt.hdr.r2[7] = 'G'; if (memcmp(RPTR, OWNER, RPTR_SIZE) != 0) { // restriction mode @@ -447,7 +447,7 @@ static void readFrom20000() net_buf.vpkt.streamid, net_buf.vpkt.hdr.flag[0], net_buf.vpkt.hdr.flag[1], net_buf.vpkt.hdr.flag[2], net_buf.vpkt.hdr.my, net_buf.vpkt.hdr.nm, net_buf.vpkt.hdr.ur, - net_buf.vpkt.hdr.r2, net_buf.vpkt.hdr.r1); + net_buf.vpkt.hdr.r1, net_buf.vpkt.hdr.r2); /* save the streamid that is winning */ streamid = net_buf.vpkt.streamid; @@ -945,7 +945,7 @@ static void ReadDVAPThread() printf("From DVAP: flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s\n", dr.frame.hdr.flag[0], dr.frame.hdr.flag[1], dr.frame.hdr.flag[2], dr.frame.hdr.mycall, dr.frame.hdr.sfx, dr.frame.hdr.urcall, - dr.frame.hdr.rpt2, dr.frame.hdr.rpt1); + dr.frame.hdr.rpt1, dr.frame.hdr.rpt2); ok = true; @@ -971,12 +971,14 @@ static void ReadDVAPThread() } } - memcpy(&net_buf.vpkt.hdr, dr.frame.hdr.flag, 41); // copy the header + memcpy(&net_buf.vpkt.hdr, dr.frame.hdr.flag, 41); // copy the header, but... + memcpy(net_buf.vpkt.hdr.r1, dr.frame.hdr.rpt1, 8); // swap r1 <--> r2 + memcpy(net_buf.vpkt.hdr.r2, dr.frame.hdr.rpt2, 8); // Internet Labs DVAP Dongle Tech. Ref. V 1.01 has it backwards! /* RPT1 must always be the repeater + module */ memcpy(net_buf.vpkt.hdr.r1, RPTR_and_MOD, 8); /* copy RPT2 */ - memcpy(net_buf.vpkt.hdr.r2, dr.frame.hdr.rpt1, 8); + memcpy(net_buf.vpkt.hdr.r2, dr.frame.hdr.rpt2, 8); /* RPT2 must also be valid */ if ((net_buf.vpkt.hdr.r2[7] == 'A') || diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index 6ff7668..0e9b392 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -39,8 +39,8 @@ typedef struct dstr_tag { union { struct { unsigned char flag[3]; // 17 - unsigned char r2[8]; // 20 - unsigned char r1[8]; // 28 + unsigned char r1[8]; // 20 + unsigned char r2[8]; // 28 unsigned char ur[8]; // 36 unsigned char my[8]; // 44 unsigned char nm[4]; // 52 From bb5d053cc4294b89c3f4108cf36a8e298f383ab3 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 06:29:17 -0700 Subject: [PATCH 026/553] MMDVMHost install --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7361e32..2ba0994 100644 --- a/Makefile +++ b/Makefile @@ -160,8 +160,8 @@ installdtmf : qndtmf systemctl daemon-reload systemctl start qndtmf.service -installmmdvm : $(MMPATH)/MMDVMhost $(MMPATH)/MMDVM.qn - /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) +installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM.qn + ( /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) ) cd $(MMPATH) ; /bin/ln -s $(shell pwd)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) /bin/cp -f system/mmdvm.timer $(SYSDIR) From 88969001843ecdf425ba040aa48fdb1161ffea18 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 08:30:34 -0700 Subject: [PATCH 027/553] ln -s for MMDVM.qn --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2ba0994..a01f0c6 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ installdtmf : qndtmf installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM.qn ( /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) ) - cd $(MMPATH) ; /bin/ln -s $(shell pwd)/MMDVM.qn $(CFGDIR) + /bin/ln -s $(shell pwd)/$(MMPATH)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) /bin/cp -f system/mmdvm.timer $(SYSDIR) systemctl enable mmdvm.timer From 64db41c04504541664874e8b86378c659d3155f2 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 12:52:42 -0700 Subject: [PATCH 028/553] improved qso logging --- QnetGateway.cpp | 32 +++++---------- QnetRelay.cpp | 107 ++++++++++++++++++++++++------------------------ QnetRelay.h | 2 +- QnetTypeDefs.h | 2 +- versions.h | 1 + 5 files changed, 68 insertions(+), 76 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 884a831..634295f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -918,13 +918,9 @@ void CQnetGateway::process() (g2buf.hdr.flag[0] == 0x28) || (g2buf.hdr.flag[0] == 0x40))) { if (bool_qso_details) - printf("START from g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s and port %u\n", - g2buf.streamid, - g2buf.hdr.flag[0], g2buf.hdr.flag[1], g2buf.hdr.flag[2], - g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - g2buflen, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + printf("START DVST G2, id=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", + ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, + g2buf.hdr.urcall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); memcpy(rptrbuf.pkt_id, "DSTR", 4); rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter @@ -951,10 +947,8 @@ void CQnetGateway::process() } } } else { // g2buflen == 27 - if (g2buf.counter & 0x40) { - if (bool_qso_details) - printf("END from g2: streamID=%04x, %d bytes from IP=%s\n", g2buf.streamid, g2buflen,inet_ntoa(fromDst4.sin_addr)); - } + if (bool_qso_details && g2buf.counter & 0x40) + printf("Send G2 end of stream: id=%04x\n", ntohs(g2buf.streamid)); /* find out which repeater module to send the data to */ int i; @@ -1084,12 +1078,10 @@ void CQnetGateway::process() if (recvlen == 58) { if (bool_qso_details) - printf("START from rptr: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - ntohs(rptrbuf.counter), - ntohs(rptrbuf.vpkt.streamid), - rptrbuf.vpkt.hdr.flag[0], rptrbuf.vpkt.hdr.flag[1], rptrbuf.vpkt.hdr.flag[2], - rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, rptrbuf.vpkt.hdr.ur, - rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, recvlen, inet_ntoa(fromRptr.sin_addr)); + printf("START DSTR RPTR: cntr=%04x id=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", + ntohs(rptrbuf.counter), ntohs(rptrbuf.vpkt.streamid), + rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, + rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); if ((memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* MYCALL is NOT this repeater */ @@ -2136,10 +2128,8 @@ void CQnetGateway::process() } } - if (rptrbuf.vpkt.ctrl & 0x40) { - if (bool_qso_details) - printf("END from rptr: cntr=%04x, streamID=%04x, %d bytes\n", ntohs(rptrbuf.counter), ntohs(rptrbuf.vpkt.streamid), recvlen); - } + if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) + printf("END DSTR RPTR: cntr=%04x, id=%04x\n", ntohs(rptrbuf.counter), ntohs(rptrbuf.vpkt.streamid)); } } } diff --git a/QnetRelay.cpp b/QnetRelay.cpp index bf5656b..7b3d771 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -230,42 +230,42 @@ int CQnetRelay::SendTo(const int fd, const unsigned char *buf, const int size, c bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) { if (29==len || 58==len) { //here is dstar data - SDSTR buf; - ::memcpy(buf.pkt_id, raw, len); // transfer raw data to SDSTR struct + SDSTR dstr; + ::memcpy(dstr.pkt_id, raw, len); // transfer raw data to SDSTR struct - SDSRP pkt; // destination + SDSRP dsrp; // destination // fill in some inital stuff - ::memcpy(pkt.title, "DSRP", 4); - pkt.voice.id = buf.vpkt.streamid; - pkt.voice.seq = buf.vpkt.ctrl; + ::memcpy(dsrp.title, "DSRP", 4); + dsrp.voice.id = dstr.vpkt.streamid; // voice or header is the same position + dsrp.voice.seq = dstr.vpkt.ctrl; // ditto if (29 == len) { // write an AMBE packet - pkt.tag = 0x21U; - if (pkt.voice.seq & 0x40) -// printf("INFO: ProcessGateway: sending voice end-of-stream\n"); - ; - else if (pkt.voice.seq > 20) - printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", pkt.voice.seq); - pkt.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf - memcpy(pkt.voice.ambe, buf.vpkt.vasd.voice, 12); - int ret = SendTo(msock, pkt.title, 21, MMDVM_IP, MMDVM_IN_PORT); + dsrp.tag = 0x21U; + if (log_qso && dsrp.voice.seq&0x40) + printf("Sent DSRP end of streamid=%04x\n", ntohs(dsrp.voice.id)); + else if (dsrp.voice.seq > 20) + printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", dsrp.voice.seq); + dsrp.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf + memcpy(dsrp.voice.ambe, dstr.vpkt.vasd.voice, 12); + int ret = SendTo(msock, dsrp.title, 21, MMDVM_IP, MMDVM_IN_PORT); if (ret != 21) { printf("ERROR: ProcessGateway: Could not write AMBE mmdvm packet\n"); return true; } } else { // write a Header packet - pkt.tag = 0x20U; - pkt.header.id = buf.vpkt.streamid; - if (pkt.header.seq) { + dsrp.tag = 0x20U; + if (dsrp.header.seq) { // printf("DEBUG: ProcessGateway: unexpected pkt.header.seq %d, resetting to 0\n", pkt.header.seq); - pkt.header.seq = 0; + dsrp.header.seq = 0; } - memcpy(pkt.header.flag, buf.vpkt.hdr.flag, 41); - int ret = SendTo(msock, pkt.title, 49, MMDVM_IP, MMDVM_IN_PORT); + memcpy(dsrp.header.flag, dstr.vpkt.hdr.flag, 41); + int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT); if (ret != 49) { printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n"); return true; } - printf("INFO: ProcessGateway: sent header to port %u pkt = '%s'\n", MMDVM_IN_PORT, std::string((char *)pkt.header.r2, 36).c_str()); + if (log_qso) + printf("Sent DSRP to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MMDVM_IN_PORT, ntohs(dsrp.header.id), + dsrp.header.ur, dsrp.header.r1, dsrp.header.r2, dsrp.header.my, dsrp.header.nm); } } else @@ -277,53 +277,58 @@ bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) { static short old_id = 0U; static short stream_id = 0U; - SDSRP mpkt; + SDSRP dsrp; if (len < 65) - ::memcpy(mpkt.title, raw, len); // transfer raw data to SDSRP struct + ::memcpy(dsrp.title, raw, len); // transfer raw data to SDSRP struct if (49==len || 21==len) { // grab the stream id if this is a header if (49 == len) { - stream_id = mpkt.header.id; + stream_id = dsrp.header.id; if (old_id == stream_id) return false; old_id = stream_id; } - SDSTR gpkt; // destination + SDSTR dstr; // destination // sets most of the params - ::memcpy(gpkt.pkt_id, "DSTR", 4); - gpkt.counter = COUNTER++; - gpkt.flag[0] = 0x73; - gpkt.flag[1] = 0x12; - gpkt.flag[2] = 0x0; - gpkt.vpkt.icm_id = 0x20; - gpkt.vpkt.dst_rptr_id = 0x0; - gpkt.vpkt.snd_rptr_id = 0x1; - gpkt.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); - gpkt.vpkt.streamid = stream_id; + ::memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(COUNTER++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x0; + dstr.vpkt.icm_id = 0x20; + dstr.vpkt.dst_rptr_id = 0x0; + dstr.vpkt.snd_rptr_id = 0x1; + dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); + dstr.vpkt.streamid = stream_id; if (49 == len) { // header - gpkt.remaining = 0x30; - gpkt.vpkt.ctrl = 0x80; - ::memcpy(gpkt.vpkt.hdr.flag, mpkt.header.flag, 41); - int ret = SendTo(msock, gpkt.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); + dstr.remaining = 0x30; + dstr.vpkt.ctrl = 0x80; + memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); + int ret = SendTo(msock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); if (ret != 58) { printf("ERROR: ProcessMMDVM: Could not write gateway header packet\n"); return true; } - printf("INFO: ProcessMMDVM: sent header to port %u pkt = '%s'\n", G2_IN_PORT, std::string((char *)gpkt.vpkt.hdr.r2, 36).c_str()); + if (log_qso) + printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), + dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); } else if (21 == len) { // ambe - gpkt.remaining = 0x16; - gpkt.vpkt.ctrl = mpkt.header.seq; - ::memcpy(gpkt.vpkt.vasd.voice, mpkt.voice.ambe, 12); - int ret = SendTo(msock, gpkt.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + dstr.remaining = 0x16; + dstr.vpkt.ctrl = dsrp.header.seq; + memcpy(dstr.vpkt.vasd.voice, dsrp.voice.ambe, 12); + int ret = SendTo(msock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + if (log_qso && dstr.vpkt.ctrl&0x40) + printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); + if (ret != 29) { printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); return true; } } - } else if (len < 65 && mpkt.tag == 0xAU) { + } else if (len < 65 && dsrp.tag == 0xAU) { // printf("MMDVM Poll: '%s'\n", (char *)mpkt.poll_msg); } else printf("DEBUG: ProcessMMDVM: unusual packet len=%d\n", len); @@ -469,11 +474,7 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) } else return true; - GetValue(cfg, "timing.play.delay", DELAY_BETWEEN, 9, 25, 19); - - GetValue(cfg, "timing.play.wait", DELAY_BEFORE, 1, 10, 2); - - GetValue(cfg, std::string(mmdvm_path+".packet_wait").c_str(), WAIT_FOR_PACKETS, 6, 100, 25); + GetValue(cfg, "log.qso", log_qso, false); return false; } @@ -495,8 +496,8 @@ int main(int argc, const char **argv) } if ('-' == argv[1][0]) { - printf("\nMMDVM Modem Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", MMDVM_VERSION); - printf("MMDVM Modem comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("\nQnetRelay Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", RELAY_VERSION); + printf("QnetRelay comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); return 0; } diff --git a/QnetRelay.h b/QnetRelay.h index e890dac..ea3e614 100644 --- a/QnetRelay.h +++ b/QnetRelay.h @@ -62,7 +62,7 @@ private: char OWNER[CALL_SIZE + 1]; std::string MMDVM_IP, G2_INTERNAL_IP; unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT; - int WAIT_FOR_PACKETS, DELAY_BEFORE, DELAY_BETWEEN; + bool log_qso; // parameters int msock, gsock; diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index 0e9b392..b6714fc 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -108,7 +108,7 @@ typedef struct dsrp_tag { // offset size // 0x01 Dstar Relay Unavailable unsigned char r2[8]; // Repeater 2 11 unsigned char r1[8]; // Repeater 1 19 - unsigned char yr[8]; // Your Call 27 + unsigned char ur[8]; // Your Call 27 unsigned char my[8]; // My Call 35 unsigned char nm[4]; // Name 43 unsigned short pfcs; // checksum 47 49 diff --git a/versions.h b/versions.h index 1eee3ce..2c7bcf5 100644 --- a/versions.h +++ b/versions.h @@ -2,6 +2,7 @@ #define IRCDDB_VERSION "QnetGateway-7.0.0" #define LINK_VERSION "QnetLink-6.0.0" #define DVAP_VERSION "QnetDVAP-5.1.1" +#define RELAY_VERSION "QnetRelay-0.2.0" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION From 391cb2fd29163db41a692f693e7a90e217f4d570 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 13:39:56 -0700 Subject: [PATCH 029/553] copy header by members --- QnetRelay.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 7b3d771..dddc76e 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -257,7 +257,14 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) // printf("DEBUG: ProcessGateway: unexpected pkt.header.seq %d, resetting to 0\n", pkt.header.seq); dsrp.header.seq = 0; } - memcpy(dsrp.header.flag, dstr.vpkt.hdr.flag, 41); + //memcpy(dsrp.header.flag, dstr.vpkt.hdr.flag, 41); + memcpy(dsrp.header.flag, dstr.vptk.hdr.flags, 3); + memcpy(dsrp.header.r1, dstr.vpkt.hdr.r1, 8); + memcpy(dsrp.header.r2, dstr.vpkt.hdr.r2, 8); + memcpy(dsrp.header.ur, dstr.vpkt.hdr.ur, 8); + memcpy(dstr.header.my, dstr.vpkt.hdr.my, 8); + memcpy(dstr.header.nm, dstr.vpkt.hdr.nm, 4); + dsrp.header.pfcs = dstr.vpkt.hdr.pfcs; int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT); if (ret != 49) { printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n"); @@ -306,7 +313,14 @@ bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) if (49 == len) { // header dstr.remaining = 0x30; dstr.vpkt.ctrl = 0x80; - memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); + //memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); + memcpy(dstr.vpkt.hdr.flags, dsrp.header.flag, 3); + memcpy(dstr.vpkt.hdr.r1, dsrp.header.r1, 8); + memcpy(dstr.vpkt.hdr.r2, dsrp.header.r2, 8); + memcpy(dstr.vpkt.hdr.ur, dsrp.header.ur, 8); + memcpy(dstr.vpkt.hdr.my, dsrp.header.my, 8); + memcpy(dstr.vpkt.hdr.nm, dsrp.header.nm, 4); + dstr.vpkt.hdr.pfcs = dsrp.header.pfcs; int ret = SendTo(msock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); if (ret != 58) { printf("ERROR: ProcessMMDVM: Could not write gateway header packet\n"); From 63019c693de085f62aa8add9ff6b532a81159150 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 13:50:20 -0700 Subject: [PATCH 030/553] typos --- QnetRelay.cpp | 28 ++++++++++++++-------------- QnetTypeDefs.h | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/QnetRelay.cpp b/QnetRelay.cpp index dddc76e..dd2ad48 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -258,13 +258,13 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) dsrp.header.seq = 0; } //memcpy(dsrp.header.flag, dstr.vpkt.hdr.flag, 41); - memcpy(dsrp.header.flag, dstr.vptk.hdr.flags, 3); - memcpy(dsrp.header.r1, dstr.vpkt.hdr.r1, 8); - memcpy(dsrp.header.r2, dstr.vpkt.hdr.r2, 8); - memcpy(dsrp.header.ur, dstr.vpkt.hdr.ur, 8); - memcpy(dstr.header.my, dstr.vpkt.hdr.my, 8); - memcpy(dstr.header.nm, dstr.vpkt.hdr.nm, 4); - dsrp.header.pfcs = dstr.vpkt.hdr.pfcs; + memcpy(dsrp.header.flag, dstr.vpkt.hdr.flag, 3); + memcpy(dsrp.header.r1, dstr.vpkt.hdr.r1, 8); + memcpy(dsrp.header.r2, dstr.vpkt.hdr.r2, 8); + memcpy(dsrp.header.ur, dstr.vpkt.hdr.ur, 8); + memcpy(dsrp.header.my, dstr.vpkt.hdr.my, 8); + memcpy(dsrp.header.nm, dstr.vpkt.hdr.nm, 4); + memcpy(dsrp.header.pfcs, dstr.vpkt.hdr.pfcs, 2); int ret = SendTo(msock, dsrp.title, 49, MMDVM_IP, MMDVM_IN_PORT); if (ret != 49) { printf("ERROR: ProcessGateway: Could not write Header mmdvm packet\n"); @@ -314,13 +314,13 @@ bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) dstr.remaining = 0x30; dstr.vpkt.ctrl = 0x80; //memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); - memcpy(dstr.vpkt.hdr.flags, dsrp.header.flag, 3); - memcpy(dstr.vpkt.hdr.r1, dsrp.header.r1, 8); - memcpy(dstr.vpkt.hdr.r2, dsrp.header.r2, 8); - memcpy(dstr.vpkt.hdr.ur, dsrp.header.ur, 8); - memcpy(dstr.vpkt.hdr.my, dsrp.header.my, 8); - memcpy(dstr.vpkt.hdr.nm, dsrp.header.nm, 4); - dstr.vpkt.hdr.pfcs = dsrp.header.pfcs; + memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 3); + memcpy(dstr.vpkt.hdr.r1, dsrp.header.r1, 8); + memcpy(dstr.vpkt.hdr.r2, dsrp.header.r2, 8); + memcpy(dstr.vpkt.hdr.ur, dsrp.header.ur, 8); + memcpy(dstr.vpkt.hdr.my, dsrp.header.my, 8); + memcpy(dstr.vpkt.hdr.nm, dsrp.header.nm, 4); + memcpy(dstr.vpkt.hdr.pfcs, dsrp.header.pfcs, 2); int ret = SendTo(msock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); if (ret != 58) { printf("ERROR: ProcessMMDVM: Could not write gateway header packet\n"); diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index b6714fc..7f1ed34 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -111,7 +111,7 @@ typedef struct dsrp_tag { // offset size unsigned char ur[8]; // Your Call 27 unsigned char my[8]; // My Call 35 unsigned char nm[4]; // Name 43 - unsigned short pfcs; // checksum 47 49 + unsigned char pfcs[2]; // checksum 47 49 } header; struct { unsigned short id; // random id number 5 From ded3d6ba2d51295f6735be78e00467e9ce8452df Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 15:13:03 -0700 Subject: [PATCH 031/553] cleanup log --- QnetGateway.cpp | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 634295f..bb4c83f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -918,7 +918,7 @@ void CQnetGateway::process() (g2buf.hdr.flag[0] == 0x28) || (g2buf.hdr.flag[0] == 0x40))) { if (bool_qso_details) - printf("START DVST G2, id=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", + printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.urcall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); @@ -948,7 +948,7 @@ void CQnetGateway::process() } } else { // g2buflen == 27 if (bool_qso_details && g2buf.counter & 0x40) - printf("Send G2 end of stream: id=%04x\n", ntohs(g2buf.streamid)); + printf("id=%04x\n END G2", ntohs(g2buf.streamid)); /* find out which repeater module to send the data to */ int i; @@ -1078,8 +1078,8 @@ void CQnetGateway::process() if (recvlen == 58) { if (bool_qso_details) - printf("START DSTR RPTR: cntr=%04x id=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", - ntohs(rptrbuf.counter), ntohs(rptrbuf.vpkt.streamid), + printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", + ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); @@ -1251,12 +1251,9 @@ void CQnetGateway::process() for (int j=0; j<5; j++) sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - printf("Routing to IP=%s port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - 56); + printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); time(&(to_remote_g2[i].last_time)); } @@ -1328,12 +1325,9 @@ void CQnetGateway::process() for (int j=0; j<5; j++) sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - printf("Routing to IP=%s, port=%u, streamID=%04x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, - g2buf.hdr.sfx, g2buf.hdr.urcall, - g2buf.hdr.rpt1, g2buf.hdr.rpt2, - 56); + printf("Routing to IP=%s:%u id=%04x my=%.8s sfx=%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", + inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.streamid, g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); time(&(to_remote_g2[i].last_time)); } @@ -2129,7 +2123,7 @@ void CQnetGateway::process() } if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) - printf("END DSTR RPTR: cntr=%04x, id=%04x\n", ntohs(rptrbuf.counter), ntohs(rptrbuf.vpkt.streamid)); + printf("id=%04x, cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); } } } From 228ec898a706842f76f048b4f14b4c7aba7f55f6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 15:30:04 -0700 Subject: [PATCH 032/553] fixed format --- QnetGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index bb4c83f..9b0569f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -948,7 +948,7 @@ void CQnetGateway::process() } } else { // g2buflen == 27 if (bool_qso_details && g2buf.counter & 0x40) - printf("id=%04x\n END G2", ntohs(g2buf.streamid)); + printf("id=%04x END G2\n", ntohs(g2buf.streamid)); /* find out which repeater module to send the data to */ int i; From a9563d108b3b6f770d6ed5ecde2a30cb1e385313 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 17:25:42 -0700 Subject: [PATCH 033/553] test for icom --- QnetGateway.cpp | 10 +++++----- QnetRemote.cpp | 12 +++++++----- QnetVoice.cpp | 18 ++++++++++-------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 9b0569f..c2bf8ad 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -252,7 +252,7 @@ bool CQnetGateway::read_config(char *cfgFile) return true; // modules - is_icom = is_not_icom = false; + bool is_icom = is_not_icom = false; for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; @@ -260,16 +260,16 @@ bool CQnetGateway::read_config(char *cfgFile) std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { rptr.mod[m].defined = true; - if (0 == strcasecmp(type.c_str(), "icom")) { + if (0 == type.compare("icom")) { rptr.mod[m].package_version = ICOM_VERSION; is_icom = true; - } else if (0 == strcasecmp(type.c_str(), "dvap")) { + } else if (0 == type.compare("dvap")) { rptr.mod[m].package_version = DVAP_VERSION; is_not_icom = true; - } else if (0 == strcasecmp(type.c_str(), "dvrptr")) { + } else if (0 == type.compare("dvrptr")) { rptr.mod[m].package_version = DVRPTR_VERSION; is_not_icom = true; - } else if (0 == strcasecmp(type.c_str(), "mmdvm")) { + } else if (0 == type.compare("mmdvm")) { rptr.mod[m].package_version = MMDVM_VERSION; is_not_icom = true; } else { diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 73fdb62..c6c1274 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -50,6 +50,7 @@ short streamid_raw = 0; bool isdefined[3] = { false, false, false }; std::string REPEATER, IP_ADDRESS; int PORT, PLAY_WAIT, PLAY_DELAY; +bool is_icom = false; unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; @@ -197,10 +198,11 @@ bool read_config(const char *cfgFile) path += m + 'a'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) { + if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("icom")) { printf("module type '%s' is invalid\n", type.c_str()); return true; } + is_icom = type.compare("icom") ? false : true; isdefined[m] = true; } } @@ -209,10 +211,10 @@ bool read_config(const char *cfgFile) return true; } - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, "127.0.0.1")) + if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, is_icom ? "172.16.0.1" : "127.0.0.1")) return true; - get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, 19000); + get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, is_icom ? 20000 : 19000); get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); @@ -234,11 +236,11 @@ int main(int argc, char *argv[]) if (argc != 4) { printf("Usage: %s \n", argv[0]); - printf("Example: %s c n7tae xrf757cl\n", argv[0]); + printf("Example: %s c n7tae xrf757al\n", argv[0]); printf("Where...\n"); printf(" c is the local repeater module\n"); printf(" n7tae is the value of mycall\n"); - printf(" xrf757cl is the value of yourcall, in this case this is a Link command\n\n"); + printf(" xrf757al is the value of yourcall, in this case this is a Link command\n\n"); return 0; } diff --git a/QnetVoice.cpp b/QnetVoice.cpp index b168634..d688898 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -50,6 +50,7 @@ short streamid_raw = 0; int moduleport[3] = { 0, 0, 0 }; std::string REPEATER, IP_ADDRESS; int PORT, PLAY_WAIT, PLAY_DELAY; +bool is_icom = false; unsigned short crc_tabccitt[256] = { 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, @@ -196,11 +197,12 @@ bool read_config(const char *cfgFile) path += m + 'a'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm")) { + if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && strcasecmp(type.c_str(), "icom")) { printf("module type '%s' is invalid\n", type.c_str()); return true; } - get_value(cfg, std::string(path+".port").c_str(), moduleport[m], 1000, 65535, 19998+m); + is_icom = strcasecmp(type.c_str(), "icom") ? false : true; + get_value(cfg, std::string(path+".port").c_str(), moduleport[m], 1000, 65535, is_icom ? 20000 : 19998+m); } } if (0==moduleport[0] && 0==moduleport[1] && 0==moduleport[2]) { @@ -208,7 +210,7 @@ bool read_config(const char *cfgFile) return true; } - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, "127.0.0.1")) + if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, is_icom ? "172.16.0.20" : "127.0.0.1")) return true; get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); @@ -332,7 +334,7 @@ int main(int argc, char *argv[]) printf("Read %d byte packet from %s\n", (int)nread*rlen, argv[3]); if (rlen == 56) printf("rpt1=%.8s rpt2=%.8s urcall=%.8s, mycall=%.8s, sfx=%.4s\n", - dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx); + dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx); else printf("streamid=%04X counter=%02X\n", dsvt.streamid, dsvt.counter); if (nread == 1) { @@ -368,9 +370,9 @@ int main(int argc, char *argv[]) dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i]; memset(dstr.vpkt.hdr.r2, ' ', 36); memcpy(dstr.vpkt.hdr.r2, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r1[7] = 'G'; + dstr.vpkt.hdr.r1[7] = module; memcpy(dstr.vpkt.hdr.r1, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r2[7] = module; + dstr.vpkt.hdr.r2[7] = 'G'; memcpy(dstr.vpkt.hdr.ur, "CQCQCQ", 6); /* yrcall */ memcpy(dstr.vpkt.hdr.my, mycall.c_str(), mycall.size()); memcpy(dstr.vpkt.hdr.nm, "QNET", 4); @@ -423,8 +425,8 @@ int main(int argc, char *argv[]) int sent = sendto(sockDst, dstr.pkt_id, rlen + 2,0, (struct sockaddr *)&toDst, sizeof(toDst)); if (sent == 58) - printf("Sent DSTR HDR r2=%.8s r1=%.8s ur=%.8s my=%.8s nm=%.4s\n", - dstr.vpkt.hdr.r2, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + printf("Sent DSTR HDR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); else if (sent == 29) printf("Sent DSTR DATA streamid=%04X, ctrl=%02X\n", dstr.vpkt.streamid, dstr.vpkt.ctrl); else From 16fc4424016804f670fdf656d1efca5251d65692 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 17:43:09 -0700 Subject: [PATCH 034/553] port needs to be a short int --- QnetVoice.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index d688898..a5b8a47 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -93,7 +93,7 @@ void calcPFCS(unsigned char rawbytes[58]) } -bool dst_open(const char *ip, const int port) +bool dst_open(const char *ip, const short port) { int reuse = 1; @@ -309,7 +309,8 @@ int main(int argc, char *argv[]) time(&tNow); CRandom Random; - if (dst_open(IP_ADDRESS.c_str(), PORT)) + short int sport = (short int)PORT; + if (dst_open(IP_ADDRESS.c_str(), sport)) return 1; // Read and reformat and write packets From fb0782c6703e212442319d3bc587a29d3a492858 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 18:58:05 -0700 Subject: [PATCH 035/553] changed IP --- QnetVoice.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index a5b8a47..22e97a1 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -48,7 +48,8 @@ FILE *fp = NULL; time_t tNow = 0; short streamid_raw = 0; int moduleport[3] = { 0, 0, 0 }; -std::string REPEATER, IP_ADDRESS; +std::string moduleip[3]; +std::string REPEATER; int PORT, PLAY_WAIT, PLAY_DELAY; bool is_icom = false; @@ -203,6 +204,8 @@ bool read_config(const char *cfgFile) } is_icom = strcasecmp(type.c_str(), "icom") ? false : true; get_value(cfg, std::string(path+".port").c_str(), moduleport[m], 1000, 65535, is_icom ? 20000 : 19998+m); + if (! get_value(cfg, std::string(path+".ip").c_str(), moduleip[m], 7, 15, is_icom ? "172.16.0.20" : "127.0.0.1")) + return true; } } if (0==moduleport[0] && 0==moduleport[1] && 0==moduleport[2]) { @@ -210,9 +213,6 @@ bool read_config(const char *cfgFile) return true; } - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, is_icom ? "172.16.0.20" : "127.0.0.1")) - return true; - get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19); @@ -266,6 +266,7 @@ int main(int argc, char *argv[]) } PORT = moduleport[module - 'A']; + std::string IP_ADDRESS(moduleip[module - 'A']); if (0 == PORT) { printf("module %c has no port defined!\n", module); return 1; @@ -301,7 +302,7 @@ int main(int argc, char *argv[]) memset(RADIO_ID, ' ', 20); RADIO_ID[20] = '\0'; - memcpy(RADIO_ID, "QnetVoice AMBE Data", 19); + memcpy(RADIO_ID, "QnetVoice", 9); unsigned long int delay = PLAY_DELAY * 1000L; sleep(PLAY_WAIT); @@ -312,6 +313,7 @@ int main(int argc, char *argv[]) short int sport = (short int)PORT; if (dst_open(IP_ADDRESS.c_str(), sport)) return 1; + printf("Opened %s:%u for writing\n", IP_ADDRESS.c_str(), sport); // Read and reformat and write packets while (true) { @@ -364,7 +366,15 @@ int main(int argc, char *argv[]) dstr.vpkt.icm_id = 0x20; dstr.vpkt.dst_rptr_id = dsvt.flagb[0]; dstr.vpkt.snd_rptr_id = dsvt.flagb[1]; - dstr.vpkt.snd_term_id = dsvt.flagb[2]; + //dstr.vpkt.snd_term_id = dsvt.flagb[2]; + if (module == 'A') + dstr.vpkt.snd_term_id = 0x03; + else if (module == 'B') + dstr.vpkt.snd_term_id = 0x01; + else if (module == 'C') + dstr.vpkt.snd_term_id = 0x02; + else + dstr.vpkt.snd_term_id = 0x00; dstr.vpkt.streamid = htons(streamid_raw); dstr.vpkt.ctrl = dsvt.counter; for (int i=0; i<3; i++) From d6a1cd6b7de8aa5e93285f2f6e8e3fe7a8166669 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 19:17:27 -0700 Subject: [PATCH 036/553] add bind() to socket --- QnetVoice.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 22e97a1..dac03fe 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -96,13 +96,14 @@ void calcPFCS(unsigned char rawbytes[58]) bool dst_open(const char *ip, const short port) { - int reuse = 1; - sockDst = socket(PF_INET,SOCK_DGRAM,0); if (sockDst == -1) { printf("Failed to create DSTAR socket\n"); return true; } + fcntl(sockDst,F_SETFL,O_NONBLOCK); + + int reuse = 1; if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { close(sockDst); sockDst = -1; @@ -114,7 +115,12 @@ bool dst_open(const char *ip, const short port) toDst.sin_port = htons(port); toDst.sin_addr.s_addr = inet_addr(ip); - fcntl(sockDst,F_SETFL,O_NONBLOCK); + if (bind(sockDst, (struct sockaddr *)&toDst, sizeof(struct sockaddr_in)) != 0) { + printf("Failed to bind %s:%d, errno=%d, %s\n", ip, port, errno, strerror(errno)); + close(sockDst); + sockDst = -1; + return true; + } return false; } @@ -379,8 +385,8 @@ int main(int argc, char *argv[]) dstr.vpkt.ctrl = dsvt.counter; for (int i=0; i<3; i++) dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i]; - memset(dstr.vpkt.hdr.r2, ' ', 36); - memcpy(dstr.vpkt.hdr.r2, REPEATER.c_str(), REPEATER.size()); + memset(dstr.vpkt.hdr.flag+3, ' ', 36); + memcpy(dstr.vpkt.hdr.flag+3, REPEATER.c_str(), REPEATER.size()); dstr.vpkt.hdr.r1[7] = module; memcpy(dstr.vpkt.hdr.r1, REPEATER.c_str(), REPEATER.size()); dstr.vpkt.hdr.r2[7] = 'G'; From fa5c302d4b3e5afb32cdb1f3244ea016aea49eaa Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 19:22:50 -0700 Subject: [PATCH 037/553] rpt2 --- QnetVoice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index dac03fe..dc913f9 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -386,9 +386,9 @@ int main(int argc, char *argv[]) for (int i=0; i<3; i++) dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i]; memset(dstr.vpkt.hdr.flag+3, ' ', 36); - memcpy(dstr.vpkt.hdr.flag+3, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r1[7] = module; memcpy(dstr.vpkt.hdr.r1, REPEATER.c_str(), REPEATER.size()); + dstr.vpkt.hdr.r1[7] = module; + memcpy(dstr.vpkt.hdr.r2, REPEATER.c_str(), REPEATER.size()); dstr.vpkt.hdr.r2[7] = 'G'; memcpy(dstr.vpkt.hdr.ur, "CQCQCQ", 6); /* yrcall */ memcpy(dstr.vpkt.hdr.my, mycall.c_str(), mycall.size()); From 05784268d5a19c310f58fd66bdb01699b1654770 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 29 Jun 2018 19:36:04 -0700 Subject: [PATCH 038/553] removed bind --- QnetVoice.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index dc913f9..495501a 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -115,12 +115,12 @@ bool dst_open(const char *ip, const short port) toDst.sin_port = htons(port); toDst.sin_addr.s_addr = inet_addr(ip); - if (bind(sockDst, (struct sockaddr *)&toDst, sizeof(struct sockaddr_in)) != 0) { - printf("Failed to bind %s:%d, errno=%d, %s\n", ip, port, errno, strerror(errno)); - close(sockDst); - sockDst = -1; - return true; - } +// if (bind(sockDst, (struct sockaddr *)&toDst, sizeof(struct sockaddr_in)) != 0) { +// printf("Failed to bind %s:%d, errno=%d, %s\n", ip, port, errno, strerror(errno)); +// close(sockDst); +// sockDst = -1; +// return true; +// } return false; } From b3c87556581a5e3181a56088fb20ef8042e0949c Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 07:28:51 -0700 Subject: [PATCH 039/553] better port management --- QnetVoice.cpp | 87 ++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 495501a..a0abb48 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -42,15 +42,17 @@ using namespace libconfig; #define VERSION "v3.1" -int sockDst = -1; -struct sockaddr_in toDst; +int sockFrm = -1; FILE *fp = NULL; time_t tNow = 0; -short streamid_raw = 0; +unsigned short streamid_raw = 0U; int moduleport[3] = { 0, 0, 0 }; std::string moduleip[3]; std::string REPEATER; -int PORT, PLAY_WAIT, PLAY_DELAY; +std::string FROM_ADDRESS; +int FROM_PORT; + +int PLAY_WAIT, PLAY_DELAY; bool is_icom = false; unsigned short crc_tabccitt[256] = { @@ -94,41 +96,43 @@ void calcPFCS(unsigned char rawbytes[58]) } -bool dst_open(const char *ip, const short port) +bool dst_open(const char *ip, const unsigned short int port) { - sockDst = socket(PF_INET,SOCK_DGRAM,0); - if (sockDst == -1) { + sockFrm = socket(PF_INET,SOCK_DGRAM,0); + if (sockFrm == -1) { printf("Failed to create DSTAR socket\n"); return true; } - fcntl(sockDst,F_SETFL,O_NONBLOCK); + fcntl(sockFrm,F_SETFL,O_NONBLOCK); int reuse = 1; - if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - close(sockDst); - sockDst = -1; + if (setsockopt(sockFrm,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + close(sockFrm); + sockFrm = -1; printf("setsockopt DSTAR REUSE failed\n"); return true; } - memset(&toDst,0,sizeof(struct sockaddr_in)); - toDst.sin_family = AF_INET; - toDst.sin_port = htons(port); - toDst.sin_addr.s_addr = inet_addr(ip); - -// if (bind(sockDst, (struct sockaddr *)&toDst, sizeof(struct sockaddr_in)) != 0) { -// printf("Failed to bind %s:%d, errno=%d, %s\n", ip, port, errno, strerror(errno)); -// close(sockDst); -// sockDst = -1; -// return true; -// } + + struct sockaddr_in fromAddr; + memset(&fromAddr, 0, sizeof(struct sockaddr_in)); + fromAddr.sin_family = AF_INET; + fromAddr.sin_port = htons(port); + fromAddr.sin_addr.s_addr = inet_addr(ip); + + if (bind(sockFrm, (struct sockaddr *)&fromAddr, sizeof(struct sockaddr_in)) != 0) { + printf("Failed to bind %s:%d, errno=%d, %s\n", ip, port, errno, strerror(errno)); + close(sockFrm); + sockFrm = -1; + return true; + } return false; } void dst_close() { - if (sockDst != -1) { - close(sockDst); - sockDst = -1; + if (sockFrm != -1) { + close(sockFrm); + sockFrm = -1; } return; } @@ -199,6 +203,10 @@ bool read_config(const char *cfgFile) REPEATER.resize(6, ' '); printf("REPEATER=[%s]\n", REPEATER.c_str()); + if (! get_value(cfg, "gateway.ip", FROM_ADDRESS, 7, 15, "127.0.0.1")) + return true; + get_value(cfg, "gateway.internal.ip", FROM_PORT, 1000, 65535, 19000); + for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; @@ -271,12 +279,17 @@ int main(int argc, char *argv[]) return 1; } - PORT = moduleport[module - 'A']; - std::string IP_ADDRESS(moduleip[module - 'A']); - if (0 == PORT) { + // set up the destination port + int m = module - 'A'; + if (0 >= moduleport[m]) { printf("module %c has no port defined!\n", module); return 1; } + struct sockaddr_in toAddr; + memset(&toAddr, 0, sizeof(struct sockaddr_in)); + toAddr.sin_family = AF_INET; + toAddr.sin_addr.s_addr = inet_addr(moduleip[m].c_str()); + toAddr.sin_port = htons((unsigned short int)moduleport[m]); if (strlen(argv[2]) > 8) { printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]); @@ -316,10 +329,10 @@ int main(int argc, char *argv[]) time(&tNow); CRandom Random; - short int sport = (short int)PORT; - if (dst_open(IP_ADDRESS.c_str(), sport)) + unsigned short int uiport = (unsigned short int)FROM_PORT; + if (dst_open(FROM_ADDRESS.c_str(), uiport)) return 1; - printf("Opened %s:%u for writing\n", IP_ADDRESS.c_str(), sport); + printf("Opened %s:%u for writing\n", FROM_ADDRESS.c_str(), uiport); // Read and reformat and write packets while (true) { @@ -341,11 +354,6 @@ int main(int argc, char *argv[]) /* read the packet */ nread = fread(dsvt.title, rlen, 1, fp); printf("Read %d byte packet from %s\n", (int)nread*rlen, argv[3]); - if (rlen == 56) - printf("rpt1=%.8s rpt2=%.8s urcall=%.8s, mycall=%.8s, sfx=%.4s\n", - dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx); - else - printf("streamid=%04X counter=%02X\n", dsvt.streamid, dsvt.counter); if (nread == 1) { if (memcmp(dsvt.title, "DSVT", 4) != 0) { printf("DVST title not found\n"); @@ -364,6 +372,7 @@ int main(int argc, char *argv[]) dstr.counter = htons(G2_COUNTER++); if (rlen == 56) { + printf("r1=%.8s r2=%.8s ur=%.8s, my=%.8s/%.4s\n", dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx); memcpy(dstr.pkt_id, "DSTR", 4); dstr.flag[0] = 0x73; dstr.flag[1] = 0x12; @@ -440,13 +449,11 @@ int main(int argc, char *argv[]) } } - int sent = sendto(sockDst, dstr.pkt_id, rlen + 2,0, (struct sockaddr *)&toDst, sizeof(toDst)); + int sent = sendto(sockFrm, dstr.pkt_id, rlen + 2,0, (struct sockaddr *)&toAddr, sizeof(toAddr)); if (sent == 58) printf("Sent DSTR HDR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); - else if (sent == 29) - printf("Sent DSTR DATA streamid=%04X, ctrl=%02X\n", dstr.vpkt.streamid, dstr.vpkt.ctrl); - else + else if (sent != 29) printf("ERROR: sendto returned %d!\n", sent); } usleep(delay); From 6b205412e678654392ecd89d8eaff381b10dbf3d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 07:31:42 -0700 Subject: [PATCH 040/553] less logging --- QnetVoice.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index a0abb48..dc49a84 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -353,7 +353,6 @@ int main(int argc, char *argv[]) /* read the packet */ nread = fread(dsvt.title, rlen, 1, fp); - printf("Read %d byte packet from %s\n", (int)nread*rlen, argv[3]); if (nread == 1) { if (memcmp(dsvt.title, "DSVT", 4) != 0) { printf("DVST title not found\n"); From edaa0c5905255628bd7429b289b20559570ae3b0 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 12:13:35 -0700 Subject: [PATCH 041/553] Qnet Struct processing --- QnetGateway.cpp | 38 +++++++++++++++++++++++++++++++------- QnetLink.cpp | 22 +++++++++++----------- QnetTypeDefs.h | 2 +- QnetVoice.cpp | 4 ++-- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index c2bf8ad..de2bf56 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -807,7 +807,7 @@ void CQnetGateway::process() // Send end_of_audio to local repeater. // Let the repeater re-initialize - end_of_audio.counter = toRptr[i].G2_COUNTER; + end_of_audio.counter = is_icom ? G2_COUNTER_OUT++ :toRptr[i].G2_COUNTER++; if (i == 0) end_of_audio.vpkt.snd_term_id = 0x03; else if (i == 1) @@ -820,7 +820,6 @@ void CQnetGateway::process() for (int j=0; j<2; j++) sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - toRptr[i].G2_COUNTER++; toRptr[i].streamid = 0; toRptr[i].adr = 0; @@ -929,7 +928,20 @@ void CQnetGateway::process() rptrbuf.flag[2] = 0x00; rptrbuf.remaining = 0x30; rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); + //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); + rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; + rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; + rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; + rptrbuf.vpkt.streamid = g2buf.streamid; + rptrbuf.vpkt.ctrl = g2buf.ctrl; + memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); + memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); + memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); + memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); /* save the header */ @@ -947,7 +959,7 @@ void CQnetGateway::process() } } } else { // g2buflen == 27 - if (bool_qso_details && g2buf.counter & 0x40) + if (bool_qso_details && g2buf.ctrl & 0x40) printf("id=%04x END G2\n", ntohs(g2buf.streamid)); /* find out which repeater module to send the data to */ @@ -973,7 +985,7 @@ void CQnetGateway::process() toRptr[i].sequence = rptrbuf.vpkt.ctrl; /* End of stream ? */ - if (g2buf.counter & 0x40) { + if (g2buf.ctrl & 0x40) { /* clear the saved header */ memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); toRptr[i].saved_adr = 0; @@ -990,7 +1002,7 @@ void CQnetGateway::process() if ((i == 3) && bool_regen_header) { /* check if this a continuation of audio that timed out */ - if (g2buf.counter & 0x40) + if (g2buf.ctrl & 0x40) ; /* we do not care about end-of-QSO */ else { /* for which repeater this stream has timed out ? */ @@ -2442,7 +2454,19 @@ void CQnetGateway::PlayFileThread(char *file) dstr.flag[2] = 0x00; dstr.remaining = 0x30; dstr.vpkt.icm_id = 0x20; - memcpy(&dstr.vpkt.dst_rptr_id, dsvt.flagb, 47); + //memcpy(&dstr.vpkt.dst_rptr_id, dsvt.flagb, 47); + dstr.vpkt.dst_rptr_id = dsvt.flagb[0]; + dstr.vpkt.snd_rptr_id = dsvt.flagb[1]; + dstr.vpkt.snd_term_id = dsvt.flagb[2]; + dstr.vpkt.streamid = dsvt.streamid; + dstr.vpkt.ctrl = dsvt.ctrl; + memcpy(dstr.vpkt.hdr.flag, dsvt.hdr.flag, 3); + memcpy(dstr.vpkt.hdr.r1, dsvt.hdr.rpt1, 8); + memcpy(dstr.vpkt.hdr.r2, dsvt.hdr.rpt2, 8); + memcpy(dstr.vpkt.hdr.ur, dsvt.hdr.urcall, 8); + memcpy(dstr.vpkt.hdr.my, dsvt.hdr.mycall, 8); + memcpy(dstr.vpkt.hdr.nm, dsvt.hdr.sfx, 4); + memcpy(dstr.vpkt.hdr.pfcs, dsvt.hdr.pfcs, 2); /* We did not change anything */ // calcPFCS(rptr_buf, 58); diff --git a/QnetLink.cpp b/QnetLink.cpp index d72c2e3..6e856bf 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -197,7 +197,7 @@ void CQnetLink::RptrAckThread(char *arg) dsvt.flagb[1] = 0x1; dsvt.streamid = htons(streamid_raw); - dsvt.counter = 0x80; + dsvt.ctrl = 0x80; dsvt.hdr.flag[0] = 0x1; dsvt.hdr.flag[1] = dsvt.hdr.flag[2] = 0x0; @@ -223,7 +223,7 @@ void CQnetLink::RptrAckThread(char *arg) /* start sending silence + announcement text */ for (int i=0; i<10; i++) { - dsvt.counter = (unsigned char)i; + dsvt.ctrl = (unsigned char)i; switch (i) { case 0: dsvt.vasd.text[0] = 0x55; @@ -271,7 +271,7 @@ void CQnetLink::RptrAckThread(char *arg) dsvt.vasd.text[2] = RADIO_ID[19] ^ 0x93; break; case 9: - dsvt.counter |= 0x40; + dsvt.ctrl |= 0x40; dsvt.vasd.text[0] = 0x16; dsvt.vasd.text[1] = 0x29; dsvt.vasd.text[2] = 0xf5; @@ -1716,7 +1716,7 @@ void CQnetLink::Process() } } } else if (found) { // length is 27 - if ((dsvt.counter & 0x40) != 0) { + if ((dsvt.ctrl & 0x40) != 0) { for (int i=0; i<3; i++) { if (old_sid[i].sid == dsvt.streamid) { if (qso_details) @@ -1761,7 +1761,7 @@ void CQnetLink::Process() sendto(rptr_sock, from_xrf_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); } - if (dsvt.counter & 0x40) { + if (dsvt.ctrl & 0x40) { brd_from_xrf.xrf_streamid = brd_from_xrf.rptr_streamid[0] = brd_from_xrf.rptr_streamid[1] = 0x0; brd_from_xrf_idx = 0; } @@ -1796,7 +1796,7 @@ void CQnetLink::Process() memcpy(dcs_buf + 31, xrf_2_dcs[i].mycall, 8); memcpy(dcs_buf + 39, xrf_2_dcs[i].sfx, 4); memcpy(dcs_buf + 43, &dsvt.streamid, 2); - dcs_buf[45] = dsvt.counter; /* cycle sequence */ + dcs_buf[45] = dsvt.ctrl; /* cycle sequence */ memcpy(dcs_buf + 46, dsvt.vasd.voice, 12); dcs_buf[58] = (xrf_2_dcs[i].dcs_rptr_seq >> 0) & 0xff; @@ -1811,7 +1811,7 @@ void CQnetLink::Process() sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - if (dsvt.counter & 0x40) { + if (dsvt.ctrl & 0x40) { to_remote_g2[i].in_streamid = 0x0; } break; @@ -2501,7 +2501,7 @@ void CQnetLink::Process() } } } else if (found) { - if (rdsvt.dsvt.counter & 0x40U) { + if (rdsvt.dsvt.ctrl & 0x40U) { for (int i=0; i<3; i++) { if (old_sid[i].sid == rdsvt.dsvt.streamid) { if (qso_details) @@ -2562,7 +2562,7 @@ void CQnetLink::Process() sendto(dcs_g2_sock, dcs_buf, 100, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - if (rdsvt.dsvt.counter & 0x40) { + if (rdsvt.dsvt.ctrl & 0x40) { to_remote_g2[i].in_streamid = 0x0; } break; @@ -2655,7 +2655,7 @@ void CQnetLink::Process() else rdsvt.dsvt.flagb[2] = 0x02; memcpy(&rdsvt.dsvt.streamid, dcs_buf+43, 2); - rdsvt.dsvt.counter = 0x80; + rdsvt.dsvt.ctrl = 0x80; rdsvt.dsvt.hdr.flag[0] = rdsvt.dsvt.hdr.flag[1] = rdsvt.dsvt.hdr.flag[2] = 0x00; memcpy(rdsvt.dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); rdsvt.dsvt.hdr.rpt1[7] = to_remote_g2[i].from_mod; @@ -2697,7 +2697,7 @@ void CQnetLink::Process() else rdsvt.dsvt.flagb[2] = 0x02; memcpy(&rdsvt.dsvt.streamid, dcs_buf+43, 2); - rdsvt.dsvt.counter = dcs_buf[45]; + rdsvt.dsvt.ctrl = dcs_buf[45]; memcpy(rdsvt.dsvt.vasd.voice, dcs_buf+46, 12); /* send the data to the local gateway/repeater */ diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index 7f1ed34..cde2948 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -72,7 +72,7 @@ typedef struct dsvt_tag { unsigned char id; // 8 0x20 unsigned char flagb[3]; // 9 0x0 0x1 0x1 unsigned short streamid;// 12 - unsigned char counter; // 14 hdr: 0x80 vsad: framecounter (mod 21) + unsigned char ctrl; // 14 hdr: 0x80 vsad: framecounter (mod 21) union { struct { // index unsigned char flag[3]; // 15 diff --git a/QnetVoice.cpp b/QnetVoice.cpp index dc49a84..253add2 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -390,7 +390,7 @@ int main(int argc, char *argv[]) else dstr.vpkt.snd_term_id = 0x00; dstr.vpkt.streamid = htons(streamid_raw); - dstr.vpkt.ctrl = dsvt.counter; + dstr.vpkt.ctrl = dsvt.ctrl; for (int i=0; i<3; i++) dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i]; memset(dstr.vpkt.hdr.flag+3, ' ', 36); @@ -404,7 +404,7 @@ int main(int argc, char *argv[]) calcPFCS(dstr.pkt_id); } else { dstr.remaining = 0x13; - dstr.vpkt.ctrl = dsvt.counter; + dstr.vpkt.ctrl = dsvt.ctrl; memcpy(dstr.vpkt.vasd.voice, dsvt.vasd.voice, 12); if ((dstr.vpkt.vasd.text[0] != 0x55) || (dstr.vpkt.vasd.text[1] != 0x2d) || (dstr.vpkt.vasd.text[2] != 0x16)) { From c49a7c30b818b654f445368341594b35b561c3a9 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 12:18:04 -0700 Subject: [PATCH 042/553] installmmdvm typo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2db4283..cf7c7f1 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,7 @@ installdtmf : qndtmf systemctl daemon-reload systemctl start qndtmf.service -installmmdvm : $(MMPATH)/MMDVMhost $(MMPATH)/MMDVM.qn +installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM.qn /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) cd $(MMPATH) ; /bin/ln -s $(shell pwd)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) From 31cf2ab11d9864428eecfeb025bbdd5f3de10140 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 12:25:01 -0700 Subject: [PATCH 043/553] MMDVM.qn symbolic link --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cf7c7f1..6511ff7 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,7 @@ installdtmf : qndtmf installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM.qn /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) - cd $(MMPATH) ; /bin/ln -s $(shell pwd)/MMDVM.qn $(CFGDIR) + /bin/ln -s $(shell pwd)/$(MMPATH)/MMDVM.qn $(CFGDIR) /bin/cp -f system/mmdvm.service $(SYSDIR) /bin/cp -f system/mmdvm.timer $(SYSDIR) systemctl enable mmdvm.timer From 54e997f0796c77919660ccaf0f8ce097f9353f41 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 17:27:39 -0700 Subject: [PATCH 044/553] module type log message --- QnetGateway.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index de2bf56..bf9d114 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -259,6 +259,7 @@ bool CQnetGateway::read_config(char *cfgFile) path += '.'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { + printf("%s = [%s]\n", std::string(path+".type").c_str(), type.c_str()); rptr.mod[m].defined = true; if (0 == type.compare("icom")) { rptr.mod[m].package_version = ICOM_VERSION; From 5a7c26efeea70f5c2a2b2fc4881eb768d8843594 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 17:37:30 -0700 Subject: [PATCH 045/553] more log messages --- QnetGateway.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index bf9d114..b126f01 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -259,7 +259,7 @@ bool CQnetGateway::read_config(char *cfgFile) path += '.'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - printf("%s = [%s]\n", std::string(path+".type").c_str(), type.c_str()); + printf("%s = [%s]\n", std::string(path+"type").c_str(), type.c_str()); rptr.mod[m].defined = true; if (0 == type.compare("icom")) { rptr.mod[m].package_version = ICOM_VERSION; @@ -751,7 +751,8 @@ void CQnetGateway::process() std::this_thread::sleep_for(std::chrono::milliseconds(100)); } printf("Detected ICOM controller!\n"); - } + } else + printf("Skipping ICOM initialization\n"); while (keep_running) { From 6b1440e6ede1d79ab3d6d9b79891b10eaa1587ea Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 18:05:22 -0700 Subject: [PATCH 046/553] is_icom redeclaration --- QnetGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index b126f01..b26de1e 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -252,7 +252,7 @@ bool CQnetGateway::read_config(char *cfgFile) return true; // modules - bool is_icom = is_not_icom = false; + is_icom = is_not_icom = false; for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; From 95c883fcff10c98384312f39a9fe9a46c0f56e46 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 30 Jun 2018 20:01:58 -0700 Subject: [PATCH 047/553] SDSTR r1 r2 reversal --- QnetTypeDefs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index cde2948..41aac94 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -39,8 +39,8 @@ typedef struct dstr_tag { union { struct { unsigned char flag[3]; // 17 - unsigned char r1[8]; // 20 - unsigned char r2[8]; // 28 + unsigned char r2[8]; // 20 + unsigned char r1[8]; // 28 unsigned char ur[8]; // 36 unsigned char my[8]; // 44 unsigned char nm[4]; // 52 From 685dfb1c6464fa17921f7f576e961c91598c8d0a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 1 Jul 2018 15:46:28 -0700 Subject: [PATCH 048/553] minor cleanup --- QnetGateway.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index b26de1e..0eb7a8a 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1161,7 +1161,7 @@ void CQnetGateway::process() /* select the band for aprs processing, and lock on the stream ID */ if (bool_send_aprs) - aprs->SelectBand(i, rptrbuf.vpkt.streamid); + aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); } } @@ -1241,7 +1241,9 @@ void CQnetGateway::process() g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); /* set rpt1 */ memset(g2buf.hdr.rpt1, ' ', 8); memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); @@ -1252,6 +1254,8 @@ void CQnetGateway::process() g2buf.hdr.rpt2[7] = 'G'; /* set yrcall, can NOT let it be slash and repeater + module */ memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); /* set PFCS */ calcPFCS(g2buf.title, 56); @@ -1318,7 +1322,9 @@ void CQnetGateway::process() g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&g2buf.streamid, &rptrbuf.vpkt.streamid, 44); + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); /* set rpt1 */ memset(g2buf.hdr.rpt1, ' ', 8); memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); @@ -1328,8 +1334,12 @@ void CQnetGateway::process() memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); g2buf.hdr.rpt2[7] = 'G'; /* set PFCS */ + memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); calcPFCS(g2buf.title, 56); + // The remote repeater has been set, lets fill in the dest_rptr // so that later we can send that to the LIVE web site memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); @@ -1421,7 +1431,7 @@ void CQnetGateway::process() try { std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); } catch (const std::exception &e) { - printf("Filed to start voicemail playback. Exception: %s\n", e.what()); + printf("Failed to start voicemail playback. Exception: %s\n", e.what()); } } else printf("No voicemail to recall or still recording\n"); @@ -1434,19 +1444,14 @@ void CQnetGateway::process() printf("Already recording for voicemail on mod %d\n", i); else { memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", - echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], - "voicemail.dat"); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); - vm[i].fd = open(tempfile, - O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (vm[i].fd < 0) printf("Failed to create file %s for voicemail\n", tempfile); else { strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", - rptrbuf.vpkt.hdr.r1[7], vm[i].file); + printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); time(&vm[i].last_time); vm[i].streamid = rptrbuf.vpkt.streamid; @@ -1460,10 +1465,10 @@ void CQnetGateway::process() recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); recbuf.hdr.rpt2[7] = 'G'; memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); @@ -1583,7 +1588,7 @@ void CQnetGateway::process() if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { time(&band_txt[i].last_time); - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { // end of voice data + if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data if (dtmf_buf_count[i] > 0) { dtmf_file = dtmf_dir; dtmf_file.push_back('/'); From 41b51476c194fb2ea7975aa025f4abf3ee1cc844 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 4 Jul 2018 11:37:26 -0700 Subject: [PATCH 049/553] swapped r1 r2 from g2 and PlayFileThread --- QnetGateway.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 0eb7a8a..cf9546f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -937,8 +937,13 @@ void CQnetGateway::process() rptrbuf.vpkt.streamid = g2buf.streamid; rptrbuf.vpkt.ctrl = g2buf.ctrl; memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + if (is_icom) { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); + } else { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + } memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); @@ -2468,8 +2473,13 @@ void CQnetGateway::PlayFileThread(char *file) dstr.vpkt.streamid = dsvt.streamid; dstr.vpkt.ctrl = dsvt.ctrl; memcpy(dstr.vpkt.hdr.flag, dsvt.hdr.flag, 3); - memcpy(dstr.vpkt.hdr.r1, dsvt.hdr.rpt1, 8); - memcpy(dstr.vpkt.hdr.r2, dsvt.hdr.rpt2, 8); + if (is_icom) { + memcpy(dstr.vpkt.hdr.r1, dsvt.hdr.rpt2, 8); + memcpy(dstr.vpkt.hdr.r2, dsvt.hdr.rpt1, 8); + } else { + memcpy(dstr.vpkt.hdr.r1, dsvt.hdr.rpt1, 8); + memcpy(dstr.vpkt.hdr.r2, dsvt.hdr.rpt2, 8); + } memcpy(dstr.vpkt.hdr.ur, dsvt.hdr.urcall, 8); memcpy(dstr.vpkt.hdr.my, dsvt.hdr.mycall, 8); memcpy(dstr.vpkt.hdr.nm, dsvt.hdr.sfx, 4); From 6dffcdc9f2bae5f9afd307ec83fbe823f9e8409a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 4 Jul 2018 13:03:30 -0700 Subject: [PATCH 050/553] band_txt init --- QnetGateway.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index cf9546f..7f57fff 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2577,6 +2577,37 @@ int CQnetGateway::init(char *cfgfile) rptr.mod[2].band = "2m"; printf("Repeater callsigns: [%s] [%s] [%s]\n", rptr.mod[0].call.c_str(), rptr.mod[1].call.c_str(), rptr.mod[2].call.c_str()); + for (i = 0; i < 3; i++) { + rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; + band_txt[i].streamID[0] = band_txt[i].streamID[1] = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].txt_stats_sent = false; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + band_txt[i].gps_last_time = 0; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + } + if (bool_send_aprs) { aprs = new CAPRS(&rptr); if (aprs) From 0fc02873ce9351f0c8286c2bf9482a11411d7add Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 4 Jul 2018 13:05:51 -0700 Subject: [PATCH 051/553] band_txt streamID is a short word --- QnetGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 7f57fff..f545bf1 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2579,7 +2579,7 @@ int CQnetGateway::init(char *cfgfile) for (i = 0; i < 3; i++) { rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; - band_txt[i].streamID[0] = band_txt[i].streamID[1] = 0; + band_txt[i].streamID = 0; band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; band_txt[i].lh_mycall[0] = '\0'; band_txt[i].lh_sfx[0] = '\0'; From 2c650426e68c38821209a5e30a8743fdcb419aed Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 7 Jul 2018 14:46:38 -0700 Subject: [PATCH 052/553] formatting --- QnetGateway.cpp | 200 +++++++++++++++++------------------------------- 1 file changed, 69 insertions(+), 131 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index f545bf1..517d6fc 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -862,8 +862,7 @@ void CQnetGateway::process() if (to_remote_g2[i].toDst4.sin_addr.s_addr != 0) { time(&t_now); if ((t_now - to_remote_g2[i].last_time) > from_local_rptr_timeout) { - printf("Inactivity from local rptr mod %d, removing stream id %04x\n", - i, to_remote_g2[i].streamid); + printf("Inactivity from local rptr mod %d, removing stream id %04x\n", i, to_remote_g2[i].streamid); memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); to_remote_g2[i].streamid = 0; @@ -898,10 +897,8 @@ void CQnetGateway::process() } - if ( ((g2buflen == 56) || (g2buflen == 27)) && - (0==memcmp(g2buf.title, "DSVT", 4)) && - ((g2buf.config == 0x10) || (g2buf.config == 0x20)) && /* header or voiceframe */ - (g2buf.id == 0x20)) { /* voice type */ + if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && + (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { if (g2buflen == 56) { // Find out the local repeater module IP/port to send the data to @@ -920,8 +917,8 @@ void CQnetGateway::process() (g2buf.hdr.flag[0] == 0x40))) { if (bool_qso_details) printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", - ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, - g2buf.hdr.urcall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, + g2buf.hdr.urcall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); memcpy(rptrbuf.pkt_id, "DSTR", 4); rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter @@ -973,8 +970,7 @@ void CQnetGateway::process() int i; for (i=0; i<3; i++) { /* streamid match ? */ - if ((toRptr[i].streamid==g2buf.streamid) && - (toRptr[i].adr == fromDst4.sin_addr.s_addr)) { + if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { memcpy(rptrbuf.pkt_id, "DSTR", 4); rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); rptrbuf.flag[0] = 0x73; @@ -1017,7 +1013,7 @@ void CQnetGateway::process() /* match saved stream ? */ if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { /* repeater module is inactive ? */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { printf("Re-generating header for streamID=%04x\n", g2buf.streamid); toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); @@ -1079,12 +1075,9 @@ void CQnetGateway::process() sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); // end of ICOM handshaking ///////////////////////////////////////////////////////////////////// - } else if ( ((recvlen == 58) || (recvlen == 29) || (recvlen == 32)) && - (rptrbuf.flag[0] == 0x73) && (rptrbuf.flag[1] == 0x12) && - (rptrbuf.vpkt.icm_id == 0x20) && (rptrbuf.flag[2] == 0x00) && - ((rptrbuf.remaining == 0x30) || /* 48 bytes follow */ - (rptrbuf.remaining == 0x13) || /* 19 bytes follow */ - (rptrbuf.remaining == 0x16)) ) { /* 22 bytes follow */ + } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && + rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && + (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { if (is_icom) { // acknowledge packet to ICOM SDSTR reply; memcpy(reply.pkt_id, "DSTR", 4); @@ -1098,16 +1091,14 @@ void CQnetGateway::process() if (bool_qso_details) printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", - ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), - rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, - rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); + ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, + rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); - if ((memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* MYCALL is NOT this repeater */ - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BREAK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28))) { /* EMR + BREAK */ + if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater + (rptrbuf.vpkt.hdr.flag[0]==0x00 || // normal + rptrbuf.vpkt.hdr.flag[0]==0x08 || // EMR + rptrbuf.vpkt.hdr.flag[0]==0x20 || // BREAK + rptrbuf.vpkt.hdr.flag[0]==0x28)) { // EMR + BREAK (0x1, announcements are not allowed) int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; @@ -1190,27 +1181,23 @@ void CQnetGateway::process() if (mycall_valid == REG_NOERROR) sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - if ((mycall_valid == REG_NOERROR) && - (memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) != 0) && /* not a reflector */ - (memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) != 0) && /* not a reflector */ - (memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) != 0) && /* not a reflector */ - (rptrbuf.vpkt.hdr.ur[0] != ' ') && /* must have something */ - (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) != 0)) { /* urcall is NOT CQCQCQ */ - if ((rptrbuf.vpkt.hdr.ur[0] == '/') && /* urcall starts with a slash */ - (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ - (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ - (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ - - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ - ) { - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6) != 0) { /* the value after the slash in urcall, is NOT this repeater */ + if (mycall_valid==REG_NOERROR && + memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector + memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && + memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && + rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something + memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6)) { // urcall is NOT CQCQCQ + if (rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + (rptrbuf.vpkt.hdr.flag[0]== 0x00 || // normal + rptrbuf.vpkt.hdr.flag[0]== 0x08 || // EMR + rptrbuf.vpkt.hdr.flag[0]== 0x20 || // BK + rptrbuf.vpkt.hdr.flag[0]== 0x28)) {// EMR + BK + + if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; if (i>=0 && i<3) { @@ -1283,20 +1270,17 @@ void CQnetGateway::process() } } } - } else if ((memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) != 0) && /* urcall is not this repeater */ - (memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) == 0) && /* rpt1 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod is A,B,C */ - (memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) == 0) && /* rpt2 is this repeater */ - (rptrbuf.vpkt.hdr.r2[7] == 'G') && /* local Gateway */ - /*** (memcmp(rptrbuf + 44, OWNER, 7) != 0) && ***/ /* mycall is NOT this repeater */ - - ((rptrbuf.vpkt.hdr.flag[0] == 0x00) || /* normal */ - (rptrbuf.vpkt.hdr.flag[0] == 0x08) || /* EMR */ - (rptrbuf.vpkt.hdr.flag[0] == 0x20) || /* BK */ - (rptrbuf.vpkt.hdr.flag[0] == 0x28)) /* EMR + BK */ - ) { + } else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + + (rptrbuf.vpkt.hdr.flag[0]==0x00 || // normal + rptrbuf.vpkt.hdr.flag[0]==0x08 || // EMR + rptrbuf.vpkt.hdr.flag[0]==0x20 || // BK + rptrbuf.vpkt.hdr.flag[0]==0x28)) { // EMR + BK + memset(temp_radio_user, ' ', 8); memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); @@ -1354,7 +1338,7 @@ void CQnetGateway::process() for (int j=0; j<5; j++) sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - printf("Routing to IP=%s:%u id=%04x my=%.8s sfx=%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", + printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), g2buf.streamid, g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); @@ -1495,18 +1479,14 @@ void CQnetGateway::process() printf("Already recording for echotest on mod %d\n", i); else { memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), - rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - recd[i].fd = open(tempfile, - O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, - S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (recd[i].fd < 0) printf("Failed to create file %s for echotest\n", tempfile); else { strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", - rptrbuf.vpkt.hdr.r1[7], recd[i].file); + printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); time(&recd[i].last_time); recd[i].streamid = rptrbuf.vpkt.streamid; @@ -1538,16 +1518,12 @@ void CQnetGateway::process() } } /* check for cross-banding */ - } else if (0 == (memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6)) && /* yrcall is CQCQCQ */ - (0 == memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7)) && /* rpt1 is this repeater */ - (0 == memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7)) && /* rpt2 is this repeater */ - ((rptrbuf.vpkt.hdr.r1[7] == 'A') || - (rptrbuf.vpkt.hdr.r1[7] == 'B') || - (rptrbuf.vpkt.hdr.r1[7] == 'C')) && /* mod of rpt1 is A,B,C */ - ((rptrbuf.vpkt.hdr.r2[7] == 'A') || - (rptrbuf.vpkt.hdr.r2[7] == 'B') || - (rptrbuf.vpkt.hdr.r2[7] == 'C')) && /* !!! usually a G of rpt2, but we see A,B,C */ - (rptrbuf.vpkt.hdr.r2[7] != rptrbuf.vpkt.hdr.r1[7])) { /* cross-banding? make sure NOT the same */ + } else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C + (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with + rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; if (i>=0 && i<3) { @@ -1559,7 +1535,7 @@ void CQnetGateway::process() i = rptrbuf.vpkt.hdr.r2[7] - 'A'; - /* valid destination repeater module? */ + // valid destination repeater module? if (i>=0 && i<3) { // toRptr[i] : receiving from a remote system or cross-band // band_txt[i] : local RF is talking. @@ -1617,18 +1593,9 @@ void CQnetGateway::process() dtmf_last_frame[i] = 0; } - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_yrcall, - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].num_dv_frames, - band_txt[i].num_dv_silent_frames, - band_txt[i].num_bit_errors); + ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, + band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); band_txt[i].streamID = 0; band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; @@ -1687,13 +1654,8 @@ void CQnetGateway::process() else memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); - // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); - - /* extract 20-byte RADIO ID */ + // extract 20-byte RADIO ID if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { - // printf("%x%x%x\n", tmp_txt[0], tmp_txt[1], tmp_txt[2]); - // printf("%c%c%c\n", tmp_txt[0] ^ 0x70, tmp_txt[1] ^ 0x4f, tmp_txt[2] ^ 0x93); for (int i=0; i<3; i++) { if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { @@ -1701,8 +1663,7 @@ void CQnetGateway::process() tmp_txt[0] = tmp_txt[0] ^ 0x70; header_type = tmp_txt[0] & 0xf0; - if ((header_type == 0x50) || /* header */ - (header_type == 0xc0)) { /* squelch */ + if ((header_type == 0x50) || /* header */ (header_type == 0xc0)) { /* squelch */ new_group[i] = false; to_print[i] = 0; ABC_grp[i] = false; @@ -1819,19 +1780,6 @@ void CQnetGateway::process() if (band_txt[i].txt_cnt >= 20) { band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - /*** - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - ***/ - // printf("TEXT1=[%s]\n", band_txt[i].txt); band_txt[i].txt_cnt = 0; } } else { @@ -1851,11 +1799,10 @@ void CQnetGateway::process() band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; band_txt[i].txt_cnt ++; - /* - We should NOT see any more text, - if we already processed text, - so blank out the codes. - */ + // We should NOT see any more text, + // if we already processed text, + // so blank out the codes. + if (band_txt[i].txt_stats_sent) { if (recvlen == 29) { rptrbuf.vpkt.vasd.text[0] = 0x70; @@ -1878,18 +1825,9 @@ void CQnetGateway::process() // band_txt[i].dest_rptr[0] = '\0'; } - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - //(strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_yrcall, - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx,band_txt[i].lh_yrcall, + band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], + band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); band_txt[i].txt_stats_sent = true; } band_txt[i].txt_cnt = 0; @@ -1923,7 +1861,7 @@ void CQnetGateway::process() ((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r') ) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + if (0 == memcmp(band_txt[i].temp_line, "$GPRMC", 6)) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; } else if (band_txt[i].temp_line[0] != '$') { From f7f8a15f5b8da30c7ebca002295095976b3564f7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 9 Jul 2018 10:25:11 -0700 Subject: [PATCH 053/553] reflist.sh source --- reflist.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reflist.sh b/reflist.sh index def0502..b8a58a6 100755 --- a/reflist.sh +++ b/reflist.sh @@ -1,12 +1,12 @@ #!/bin/bash -wget ftp://dschost1.w6kd.com/DExtra_Hosts.txt || wget ftp://dschost2.w6kd.com/DExtra_Hosts.txt -wget ftp://dschost1.w6kd.com/DPlus_Hosts.txt || wget ftp://dschost2.w6kd.com/DPlus_Hosts.txt -wget ftp://dschost1.w6kd.com/DCS_Hosts.txt || wget ftp://dschost2.w6kd.com/DCS_Hosts.txt +/usr/bin/wget http://www.pistar.uk/downloads/DExtra_Hosts.txt +/usr/bin/wget http://www.pistar.uk/downloads/DPlus_Hosts.txt +/usr/bin/wget http://www.pistar.uk/downloads/DCS_Hosts.txt /bin/rm -f gwys.txt -echo "# Downloaded from dschost1.w6kd.com `date`" > gwys.txt +echo "# Downloaded from www.pistar.uk `date`" > gwys.txt awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt From 22d88757226adaaab4d92497fdefe80850d5c0ac Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 9 Jul 2018 10:25:59 -0700 Subject: [PATCH 054/553] reflist.sh source --- reflist.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reflist.sh b/reflist.sh index def0502..b8a58a6 100755 --- a/reflist.sh +++ b/reflist.sh @@ -1,12 +1,12 @@ #!/bin/bash -wget ftp://dschost1.w6kd.com/DExtra_Hosts.txt || wget ftp://dschost2.w6kd.com/DExtra_Hosts.txt -wget ftp://dschost1.w6kd.com/DPlus_Hosts.txt || wget ftp://dschost2.w6kd.com/DPlus_Hosts.txt -wget ftp://dschost1.w6kd.com/DCS_Hosts.txt || wget ftp://dschost2.w6kd.com/DCS_Hosts.txt +/usr/bin/wget http://www.pistar.uk/downloads/DExtra_Hosts.txt +/usr/bin/wget http://www.pistar.uk/downloads/DPlus_Hosts.txt +/usr/bin/wget http://www.pistar.uk/downloads/DCS_Hosts.txt /bin/rm -f gwys.txt -echo "# Downloaded from dschost1.w6kd.com `date`" > gwys.txt +echo "# Downloaded from www.pistar.uk `date`" > gwys.txt awk '$1 ~ /^REF/ { printf "%s %s 20001\n", $1, $2 }' DPlus_Hosts.txt >> gwys.txt awk '$1 ~ /^XRF/ { printf "%s %s 30001\n", $1, $2 }' DExtra_Hosts.txt >> gwys.txt awk '$1 ~ /^DCS/ { printf "%s %s 30051\n", $1, $2 }' DCS_Hosts.txt >> gwys.txt From 143637a6e271dcc3c9dd22a4e70637322792ab5d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 12 Jul 2018 20:31:10 -0700 Subject: [PATCH 055/553] flag1 is 0 for qnlink AudioNotifyThread() --- QnetLink.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 6e856bf..fab70d6 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3487,7 +3487,7 @@ void CQnetLink::AudioNotifyThread(char *arg) dsvt.streamid = htons(streamid_raw); if (rlen == 56) { - dsvt.hdr.flag[0] = 0x01; + dsvt.hdr.flag[0] = 0x0; memcpy(dsvt.hdr.rpt1, owner.c_str(), CALL_SIZE); dsvt.hdr.rpt1[7] = mod; From 1feb7cd1bfb2ee7f4a92e487b1ae5ecbcd03dc6c Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 06:38:19 -0700 Subject: [PATCH 056/553] make clean rm's ALL_PROGRAMS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6511ff7..6b046e8 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ qnvoice : QnetVoice.o Random.o .PHONY: clean clean: - $(RM) $(OBJS) $(DEPS) $(PROGRAMS) + $(RM) $(OBJS) $(DEPS) $(ALL_PROGRAMS) -include $(DEPS) From ccb31872f702005d5eabe63dfd7264b2f40cc7c0 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 14:29:58 -0700 Subject: [PATCH 057/553] different way to qnvoice --- QnetLink.cpp | 24 ++++++++++++++++++++++++ QnetLink.h | 2 +- qn.everything.cfg | 1 + qn.icom.cfg | 3 ++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index fab70d6..402a414 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -41,6 +41,8 @@ #include #include +#include +#include #include #include #include @@ -682,6 +684,8 @@ bool CQnetLink::read_config(const char *cfgFile) if (! get_value(cfg, "file.status", status_file, 2, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) return true; + get_value(cfg, "file.qnvoicefile", qnvoice_file, 2, FILENAME_MAX, "/tmp/qnvoice.txt"); + get_value(cfg, "timing.play.delay", delay_between, 9, 25, 19); get_value(cfg, "link.acknowledge", bool_rptr_ack, true); @@ -1254,6 +1258,26 @@ void CQnetLink::Process() time(&hb); } + // play a qnvoice file if it is specified + std::ifstream voicefile(qnvoice_file.c_str(), std::ifstream::in); + if (voicefile) { + char line[FILENAME_MAX]; + voicefile.getline(line, FILENAME_MAX); + // trim whitespace + char *start = line; + while (isspace(*start)) + start++; + char *end = start + strlen(start) - 1; + while (isspace(*end)) + *end-- = (char)0; + // anthing reasonable left? + if (strlen(start) > 2) + audio_notify(start); + //clean-up + voicefile.close(); + remove(qnvoice_file.c_str()); + } + FD_ZERO(&fdset); FD_SET(xrf_g2_sock,&fdset); FD_SET(dcs_g2_sock,&fdset); diff --git a/QnetLink.h b/QnetLink.h index 51a057a..1f6ef3b 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -81,7 +81,7 @@ private: bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value); /* configuration data */ - std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, announce_dir; + std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, qnvoice_file, announce_dir; bool only_admin_login, only_link_unlink, qso_details, bool_rptr_ack, announce; int rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port, delay_between, delay_before; char link_at_startup[CALL_SIZE+1]; diff --git a/qn.everything.cfg b/qn.everything.cfg index ca3cc03..4408baf 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -285,6 +285,7 @@ file = { # status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services # DTMF = "/tmp" # # echotest = "/tmp" # echo dat files will end up here +# qnvoicefile = /tmp/qnvoice.txt # where qnvoice will create the play command # gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is. # announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements } diff --git a/qn.icom.cfg b/qn.icom.cfg index 3fe812b..a80488c 100644 --- a/qn.icom.cfg +++ b/qn.icom.cfg @@ -31,7 +31,7 @@ module = { # 23 cm module will use "a" # 70 cm module will use "b" # 2 M module will use "c" -# type = "icom" # you must define at least one module by uncommenting the type +# type = "icom" # you must define at least one module by uncommenting the type # ip = "172.16.0.20" # all icom modules should have the same IP address # port = 20000 # all icom modules should have the same UDP port # frequency = 0 # in MHz, if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage @@ -114,6 +114,7 @@ file = { # status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services # DTMF = "/tmp" # # echotest = "/tmp" # echo dat files will end up here +# qnvoicefile = /tmp/qnvoice.txt # where qnvoice will create the play command # gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is. # announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements } From 3f330c4fea18b6dee2e4543e341529dc0b36d164 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 15:32:09 -0700 Subject: [PATCH 058/553] simplified QnetVoice.cpp --- QnetVoice.cpp | 329 +++++--------------------------------------------- 1 file changed, 28 insertions(+), 301 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 253add2..16dd514 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -18,124 +18,20 @@ */ #include -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include #include -#include "QnetTypeDefs.h" -#include "Random.h" using namespace libconfig; -#define VERSION "v3.1" - -int sockFrm = -1; -FILE *fp = NULL; -time_t tNow = 0; -unsigned short streamid_raw = 0U; -int moduleport[3] = { 0, 0, 0 }; -std::string moduleip[3]; +bool isamod[3] = { false, false, false }; std::string REPEATER; -std::string FROM_ADDRESS; -int FROM_PORT; - -int PLAY_WAIT, PLAY_DELAY; -bool is_icom = false; - -unsigned short crc_tabccitt[256] = { - 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, - 0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e,0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876, - 0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd,0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5, - 0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c,0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974, - 0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb,0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3, - 0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a,0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72, - 0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9,0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1, - 0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738,0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70, - 0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7,0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff, - 0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036,0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e, - 0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5,0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd, - 0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134,0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c, - 0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3,0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb, - 0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232,0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a, - 0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1,0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9, - 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 -}; - - - -void calcPFCS(unsigned char rawbytes[58]) -{ - unsigned short crc_dstar_ffff = 0xffff; - unsigned short tmp, short_c; - short int i; - - for (i = 17; i < 56 ; i++) { - short_c = 0x00ff & (unsigned short)rawbytes[i]; - tmp = (crc_dstar_ffff & 0x00ff) ^ short_c; - crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp]; - } - crc_dstar_ffff = ~crc_dstar_ffff; - tmp = crc_dstar_ffff; +std::string announce_dir; +std::string qnvoice_file; - rawbytes[56] = (unsigned char)(crc_dstar_ffff & 0xff); - rawbytes[57] = (unsigned char)((tmp >> 8) & 0xff); - return; - -} - -bool dst_open(const char *ip, const unsigned short int port) -{ - sockFrm = socket(PF_INET,SOCK_DGRAM,0); - if (sockFrm == -1) { - printf("Failed to create DSTAR socket\n"); - return true; - } - fcntl(sockFrm,F_SETFL,O_NONBLOCK); - - int reuse = 1; - if (setsockopt(sockFrm,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - close(sockFrm); - sockFrm = -1; - printf("setsockopt DSTAR REUSE failed\n"); - return true; - } - - struct sockaddr_in fromAddr; - memset(&fromAddr, 0, sizeof(struct sockaddr_in)); - fromAddr.sin_family = AF_INET; - fromAddr.sin_port = htons(port); - fromAddr.sin_addr.s_addr = inet_addr(ip); - - if (bind(sockFrm, (struct sockaddr *)&fromAddr, sizeof(struct sockaddr_in)) != 0) { - printf("Failed to bind %s:%d, errno=%d, %s\n", ip, port, errno, strerror(errno)); - close(sockFrm); - sockFrm = -1; - return true; - } - return false; -} - -void dst_close() -{ - if (sockFrm != -1) { - close(sockFrm); - sockFrm = -1; - } - return; -} bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) { @@ -203,10 +99,6 @@ bool read_config(const char *cfgFile) REPEATER.resize(6, ' '); printf("REPEATER=[%s]\n", REPEATER.c_str()); - if (! get_value(cfg, "gateway.ip", FROM_ADDRESS, 7, 15, "127.0.0.1")) - return true; - get_value(cfg, "gateway.internal.ip", FROM_PORT, 1000, 65535, 19000); - for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; @@ -216,20 +108,13 @@ bool read_config(const char *cfgFile) printf("module type '%s' is invalid\n", type.c_str()); return true; } - is_icom = strcasecmp(type.c_str(), "icom") ? false : true; - get_value(cfg, std::string(path+".port").c_str(), moduleport[m], 1000, 65535, is_icom ? 20000 : 19998+m); - if (! get_value(cfg, std::string(path+".ip").c_str(), moduleip[m], 7, 15, is_icom ? "172.16.0.20" : "127.0.0.1")) - return true; + isamod[m] = true; } } - if (0==moduleport[0] && 0==moduleport[1] && 0==moduleport[2]) { - printf("No repeaters defined!\n"); - return true; - } - get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); + get_value(cfg, "file.announce_dir", announce_dir, 2, FILENAME_MAX, "/usr/local/etc"); - get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19); + get_value(cfg, "file.qnvoice_file", qnvoice_file, 2, FILENAME_MAX, "/tmp/qnvoice.txt"); return false; } @@ -243,20 +128,14 @@ void ToUpper(std::string &str) int main(int argc, char *argv[]) { - unsigned short rlen = 0; - static unsigned short G2_COUNTER = 0; - size_t nread = 0; - SDSVT dsvt; - SDSTR dstr; char RADIO_ID[21]; - short int TEXT_idx = 0; if (argc != 4) { - printf("Usage: %s \n", argv[0]); + printf("Usage: %s = moduleport[m]) { - printf("module %c has no port defined!\n", module); - return 1; - } - struct sockaddr_in toAddr; - memset(&toAddr, 0, sizeof(struct sockaddr_in)); - toAddr.sin_family = AF_INET; - toAddr.sin_addr.s_addr = inet_addr(moduleip[m].c_str()); - toAddr.sin_port = htons((unsigned short int)moduleport[m]); - - if (strlen(argv[2]) > 8) { - printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]); - return 1; - } - std::string mycall(argv[2]); - ToUpper(mycall); + char pathname[FILENAME_MAX]; + snprintf(pathname, FILENAME_MAX, "%s/%s", announce_dir.c_str(), argv[2]); - fp = fopen(argv[3], "rb"); + FILE *fp = fopen(argv[3], "rb"); if (!fp) { - printf("Failed to open file %s for reading\n", argv[3]); - return 1; - } - - /* DVTOOL + 4 byte num_of_records */ - unsigned char buf[10]; - nread = fread(buf, 10, 1, fp); - if (nread != 1) { - printf("Cant read first 10 bytes\n"); - fclose(fp); - return 1; - } - if (0 != memcmp(buf, "DVTOOL", 6)) { - printf("DVTOOL signature not found in %s\n", argv[3]); - fclose(fp); + printf("Failed to find file %s for reading\n", argv[2]); return 1; } + fclose(fp); - memset(RADIO_ID, ' ', 20); + memset(RADIO_ID, '_', 20); RADIO_ID[20] = '\0'; - memcpy(RADIO_ID, "QnetVoice", 9); - - unsigned long int delay = PLAY_DELAY * 1000L; - sleep(PLAY_WAIT); + unsigned int len = strlen(argv[3]); + strncpy(RADIO_ID, argv[3], len > 20 ? 20 : len); + for (int i=0; i<20; i++) + if (isspace(RADIO_ID[i])) + RADIO_ID[i] = '_'; - time(&tNow); - CRandom Random; - - unsigned short int uiport = (unsigned short int)FROM_PORT; - if (dst_open(FROM_ADDRESS.c_str(), uiport)) + fp = fopen(qnvoice_file.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_%s_%s\n", module, argv[3], RADIO_ID); + fclose(fp); + } else { + printf("Failed to open %s for writing", qnvoice_file.c_str()); return 1; - printf("Opened %s:%u for writing\n", FROM_ADDRESS.c_str(), uiport); - - // Read and reformat and write packets - while (true) { - /* 2 byte length */ - nread = fread(&rlen, 2, 1, fp); - if (nread != 1) { - printf("End-Of-File\n"); - break; - } - if (rlen == 56) - streamid_raw = Random.NewStreamID(); - else if (rlen == 27) - ; - else { - printf("Wrong packet size!\n"); - return 1; - } - - /* read the packet */ - nread = fread(dsvt.title, rlen, 1, fp); - if (nread == 1) { - if (memcmp(dsvt.title, "DSVT", 4) != 0) { - printf("DVST title not found\n"); - return 1; - } - - if (dsvt.id != 0x20) { - printf("Not Voice type\n"); - return 1; - } - - if (dsvt.config!=0x10 && dsvt.config!=0x20) { - printf("Not a valid record type\n"); - return 1; - } - - dstr.counter = htons(G2_COUNTER++); - if (rlen == 56) { - printf("r1=%.8s r2=%.8s ur=%.8s, my=%.8s/%.4s\n", dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.urcall, dsvt.hdr.mycall, dsvt.hdr.sfx); - memcpy(dstr.pkt_id, "DSTR", 4); - dstr.flag[0] = 0x73; - dstr.flag[1] = 0x12; - dstr.flag[2] = 0x00; - dstr.remaining = 0x30; - dstr.vpkt.icm_id = 0x20; - dstr.vpkt.dst_rptr_id = dsvt.flagb[0]; - dstr.vpkt.snd_rptr_id = dsvt.flagb[1]; - //dstr.vpkt.snd_term_id = dsvt.flagb[2]; - if (module == 'A') - dstr.vpkt.snd_term_id = 0x03; - else if (module == 'B') - dstr.vpkt.snd_term_id = 0x01; - else if (module == 'C') - dstr.vpkt.snd_term_id = 0x02; - else - dstr.vpkt.snd_term_id = 0x00; - dstr.vpkt.streamid = htons(streamid_raw); - dstr.vpkt.ctrl = dsvt.ctrl; - for (int i=0; i<3; i++) - dstr.vpkt.hdr.flag[i] = dsvt.hdr.flag[i]; - memset(dstr.vpkt.hdr.flag+3, ' ', 36); - memcpy(dstr.vpkt.hdr.r1, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r1[7] = module; - memcpy(dstr.vpkt.hdr.r2, REPEATER.c_str(), REPEATER.size()); - dstr.vpkt.hdr.r2[7] = 'G'; - memcpy(dstr.vpkt.hdr.ur, "CQCQCQ", 6); /* yrcall */ - memcpy(dstr.vpkt.hdr.my, mycall.c_str(), mycall.size()); - memcpy(dstr.vpkt.hdr.nm, "QNET", 4); - calcPFCS(dstr.pkt_id); - } else { - dstr.remaining = 0x13; - dstr.vpkt.ctrl = dsvt.ctrl; - memcpy(dstr.vpkt.vasd.voice, dsvt.vasd.voice, 12); - - if ((dstr.vpkt.vasd.text[0] != 0x55) || (dstr.vpkt.vasd.text[1] != 0x2d) || (dstr.vpkt.vasd.text[2] != 0x16)) { - if (TEXT_idx == 0) { - dstr.vpkt.vasd.text[0] = '@' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 2) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 5) { - dstr.vpkt.vasd.text[0] = 'A' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 7) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 10) { - dstr.vpkt.vasd.text[0] = 'B' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 12) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 15) { - dstr.vpkt.vasd.text[0] = 'C' ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else if (TEXT_idx == 17) { - dstr.vpkt.vasd.text[0] = RADIO_ID[TEXT_idx++] ^ 0x70; - dstr.vpkt.vasd.text[1] = RADIO_ID[TEXT_idx++] ^ 0x4f; - dstr.vpkt.vasd.text[2] = RADIO_ID[TEXT_idx++] ^ 0x93; - } else { - dstr.vpkt.vasd.text[0] = 0x70; - dstr.vpkt.vasd.text[1] = 0x4f; - dstr.vpkt.vasd.text[2] = 0x93; - } - } - } - - int sent = sendto(sockFrm, dstr.pkt_id, rlen + 2,0, (struct sockaddr *)&toAddr, sizeof(toAddr)); - if (sent == 58) - printf("Sent DSTR HDR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", - dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); - else if (sent != 29) - printf("ERROR: sendto returned %d!\n", sent); - } - usleep(delay); } - dst_close(); - fclose(fp); + return 0; } From 4b3a08186ddbdd05cca2fd09a5b09b5cabe36039 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 15:37:11 -0700 Subject: [PATCH 059/553] wrong argv index --- QnetVoice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 16dd514..23bed91 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -161,7 +161,7 @@ int main(int argc, char *argv[]) char pathname[FILENAME_MAX]; snprintf(pathname, FILENAME_MAX, "%s/%s", announce_dir.c_str(), argv[2]); - FILE *fp = fopen(argv[3], "rb"); + FILE *fp = fopen(argv[2], "rb"); if (!fp) { printf("Failed to find file %s for reading\n", argv[2]); return 1; From 2965afaf44ea2d8365c28feccb1e238499abbc01 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 15:42:09 -0700 Subject: [PATCH 060/553] no REPEATER, not needed --- QnetVoice.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 23bed91..cb393df 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -28,7 +28,6 @@ using namespace libconfig; bool isamod[3] = { false, false, false }; -std::string REPEATER; std::string announce_dir; std::string qnvoice_file; @@ -94,11 +93,6 @@ bool read_config(const char *cfgFile) return true; } - if (! get_value(cfg, "ircddb.login", REPEATER, 3, 6, "UNDEFINED")) - return true; - REPEATER.resize(6, ' '); - printf("REPEATER=[%s]\n", REPEATER.c_str()); - for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; @@ -144,12 +138,6 @@ int main(int argc, char *argv[]) if (read_config(cfgfile.c_str())) return 1; - if (REPEATER.size() > 6) { - printf("repeaterCallsign can not be more than 6 characters, %s is invalid\n", REPEATER.c_str()); - return 1; - } - ToUpper(REPEATER); - char module = argv[1][0]; if (islower(module)) module = toupper(module); @@ -161,9 +149,9 @@ int main(int argc, char *argv[]) char pathname[FILENAME_MAX]; snprintf(pathname, FILENAME_MAX, "%s/%s", announce_dir.c_str(), argv[2]); - FILE *fp = fopen(argv[2], "rb"); + FILE *fp = fopen(pathname, "rb"); if (!fp) { - printf("Failed to find file %s for reading\n", argv[2]); + printf("Failed to find file %s for reading\n", pathname); return 1; } fclose(fp); From 97c17a2f5a1c65bebed7361183c4d941e522dd9d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 15:43:34 -0700 Subject: [PATCH 061/553] wrong index --- QnetVoice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index cb393df..b76b5bb 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -167,7 +167,7 @@ int main(int argc, char *argv[]) fp = fopen(qnvoice_file.c_str(), "w"); if (fp) { - fprintf(fp, "%c_%s_%s\n", module, argv[3], RADIO_ID); + fprintf(fp, "%c_%s_%s\n", module, argv[2], RADIO_ID); fclose(fp); } else { printf("Failed to open %s for writing", qnvoice_file.c_str()); From 606b1aa791e53e0d0385b812a5abefd990119c1a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 13 Jul 2018 17:58:15 -0700 Subject: [PATCH 062/553] better qnvoice prompt --- QnetVoice.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/QnetVoice.cpp b/QnetVoice.cpp index b76b5bb..46d4ce4 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -125,11 +125,12 @@ int main(int argc, char *argv[]) char RADIO_ID[21]; if (argc != 4) { - printf("Usage: %s \n", argv[0]); printf("Where...\n"); - printf(" module is one of your modules: A, B or C\n"); - printf(" dvtoolFile is an existing dvtool file, like \"unlinked.dat\"\n"); - printf(" txtMsg is an up to 20-character text message\n"); + printf(" is one of your modules: A, B or C\n"); + printf(" is an installed voice file in the configured\n"); + printf(" directory, for example \"unlinked.dat\"\n"); + printf(" is an up to 20-character text message\n"); return 0; } From 35eec883f8df76c5d9a71e5a4484212f1f6561f8 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 14 Jul 2018 14:51:10 -0700 Subject: [PATCH 063/553] updated docs --- BUILDING | 84 +++++++++++++++++++++++++++++-------------------------- README.md | 2 +- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/BUILDING b/BUILDING index 2e8ccfb..01710ff 100644 --- a/BUILDING +++ b/BUILDING @@ -35,8 +35,9 @@ sudo apt-get upgrade sudo apt-get install make g++ unzip git libconfig++-dev If you are building a QnetGateway + MMDVMHost system, please use the instructions -in the MMDVM.README file. If you are building a QnetGateway for a DVAP or a -DVRPTR_V1 read on... +in the MMDVM.README file. If you are building a QnetGateway for an Icom repeater, +use the qn.icom.cfg configuration file as a starting point for your configuration. +If you are building a QnetGateway for a DVAP or a DVRPTR_V1 read on... git clone git://github.com/n7tae/QnetGateway.git @@ -44,20 +45,28 @@ This will create a QnetGateway directory with everything you need to build a dva dvrptr ircddb gateway. The first thing to do is change to the build directory with "cd QnetGateway" and then -type "make" to build all the QnetGateway executables. If you need DTMFS then also -execute "make g2link_test". - -Next, create your qn.cfg configuration file. There are two example for you to look +choose a target to make. There are targets for each of the supported devices: +. "make icom" will build all programs needed for the Icom repeater. +. "make dvap" will build all programs needed for the DVAP Dongle. +. "make dvrptr" will build all programs needed for the DVRPTR_V1. +. "make mmdvm" will build all programs needed for MMDVMHost support. (You need + to download and build MMDVMHost separately, see the MMDVM.README file for more info. +. "make" will build all the QnetGateway executables. + +Next, create your qn.cfg configuration file. There are three example for you to look at: . qn.everything.cfg contains all parameter with lengthly comments about what each parameter does. The definitions that are commented out are defined with their default value. . qn.dvap.cfg is the simplest possible configuration for a 2m DVAP. If you have a 70cm DVAP rename the module to "b" and change the frequency. +. qn.icom.cfg is the starting place for configuring an Icom repeater. Please note + that QnetGateway doesn't support the 23cm data only module in the Icom repeater + stack. -Remeber the everything file contain detailed comments about all of the values you -can set. Just read through it and edit accordingly. In the end you will need -a configuration file called "qn.cfg". +Remeber the everything file or the icom file contain detailed comments about all of +the values you can set. Just read through it and edit accordingly. In the end you will +need a configuration file called "qn.cfg". Additional information about the configuration as well as other important and useful features are also in the CONFIGURING file. @@ -79,38 +88,36 @@ problem"! There are MANY OTHER gateways to which you can connect. Executing get_gwys_list.sh will download a HUGE list of reflectors and gateways from www.va3uv.com with port -address that may need port-forwarding to your sytem. I have provided anotherscript, -'get_reflectors.sh' that will download the same list from va3uv.com, but filter it -so that it only contains DCS x-reflectors (DCSXXX), DStar reflectors (REFXXX) and -X-reflectors (XRFXXX) and it will put all x-reflectors on port 20001 so you -probably won't need any port-forwarding on your home router. +address that may need port-forwarding to your sytem. There is another script, reflist.sh, that will download REF, XRF and DCS reflectors from another source. This is probably the preferred method to getting a gwys.txt -file. +file. This source is extremely up-to-date. + +Based on the above discussion, execute either "./reflist.sh" or "./get_gwy_list.sh". -Based on the above discussion, execute either "./reflist.sh", "./get_reflectors.sh" or -"./get_gwy_list.sh". +If you plan on using DTMF, you need to copy "qndtmf.sh" to "qndtmf". This is the +file that interprets dtmf command and executes them. As supplied, it parses the +DTMF string and executes a QnetRemote command to perform linking and unlinking +and other useful tasks. It is a /bin/bash script so you can modify it! You can +change the commands or create new commands. -If you plan on using DTMFS, you can also edit proc_qnlinktest to add new -dtmfs commands. +You are now ready to install your QnetGateway system. If you are installing an +MMDVM-based system, follow the instructions in MMDVM.README. -Then install your system. you have two choices, either DVAP or DVRPTR_V1 by -typing "sudo make installdvap" or "sudo make installdvrptr", respectively. -This should get you up and running. It will take a few minutes for QnetGateway -to get fully connected to the IRCDDB network. +To install either DVAP or DVRPTR_V1, type "sudo make installdvap" or "sudo make +installdvrptr", respectively. If you are installing on an Icom repeater, type +"sudo make installicom". This should get you up and running. It will take a few +minutes for QnetGateway to get fully connected to the IRCDDB network. Finally, if you want/need DTMFS, type "sudo make installdtmfs". The service scripts in /lib/systemd/system and everything else in /usr/local: The executables will be in /usr/local/bin and the qn.cfg file and other data -will be in /usr/local/etc. - -If you find that you need to modify the configuration file, remember that the -installed systems read the /usr/local/etc/qn.cfg file, NOT THE ONE IN YOUR -BUILD DIRECTORY. To keep them synced, it is good admin practice to modify the -qn.cfg file in your build directory and then do a "sudo make uninstall" followed by an "sudo make install. +will be in /usr/local/etc. Please note that the qn.cfg file and the gwys.txt +file are actually symbolic links and point back to the files you created in +the build directory. So if you want modify them later, modify the files in the +build directory. If you are having trouble connecting, use journalctl to view the log output of each process. "sudo journalctl -u will show the log from that @@ -118,21 +125,18 @@ service. The QnetGateway service is qngateway, QnetLink is qnlink, QnetDVAP is qndvap and QnetDVRPTR is qndvrptr. The beginning of each log file will report the values of all the configuration -parameters (even the ones you didn't specify in qm.cfg) and after that +parameters (even the ones you didn't specify in qn.cfg) and after that you will see the verbose reports of what each service is doing. These logs are invaluable for traking down problems with your qn.cfg file. You can see in real time what is being added to the logs during operation appending "-f" to the journalctl command. -You can clean up the build directory of intermediate *.o files with "make clean" -or, you can remove the intermediate *.o files and binary executables with "make -realclean". Note that "make realclean" only removes the binary files from your -build directory and not the copies you installed into /usr/local/bin with the -"sudo make install..." command. +You can clean up the build directory of intermediate *.o files, *.d file and the +executables with "make clean". -If you want to uninstall everything return to the build directory and type either -"sudo make unistalldvap" or "sudo make uninstalldvrptr" and possibly "sudo make -uninstalldtmfs". This will shutdown the services and remove the service scripts -and everything from /usr/local. +If you want to uninstall everything return to the build directory and type +"sudo make unistalldvap", "sudo make uninstalldvrptr" or "sudo make uninstallicom" +and possibly "sudo make uninstalldtmfs". This will shutdown the services and +remove the service scripts and everything from /usr/local. Tom Early, n7tae (at) arrl (dot) net diff --git a/README.md b/README.md index c479291..066d369 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ QnetGateway =========== -The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle and the DVRPTR_V1. It is *incredibly easy* to build and install the system. +The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle, the DVRPTR_V1 and now the Icom repeater. It is *incredibly easy* to build and install the system. For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file. From 6227c6dcf324f44c6a97019841c521e3f5a511d0 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 09:47:25 -0700 Subject: [PATCH 064/553] QnetITAP V#0 --- .gitignore | 1 + Makefile | 62 +++- QnetITAP.cpp | 701 ++++++++++++++++++++++++++++++++++++++++++ QnetITAP.h | 127 ++++++++ system/qnitap.service | 10 + versions.h | 1 + 6 files changed, 901 insertions(+), 1 deletion(-) create mode 100644 QnetITAP.cpp create mode 100644 QnetITAP.h create mode 100644 system/qnitap.service diff --git a/.gitignore b/.gitignore index ad00755..8690511 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o *.d +qnitap qndvap qndvrptr qnlink diff --git a/Makefile b/Makefile index 6b046e8..e9f26bd 100644 --- a/Makefile +++ b/Makefile @@ -37,10 +37,11 @@ SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr +ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr +TAP_PROGRAMS=qngateway qnlink qnremote qnvoice qnitap ICM_PROGRAMS=qngateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) @@ -48,6 +49,7 @@ mmdvm : $(MDV_PROGRAMS) dvap : $(DVP_PROGRAMS) dvrptr : $(DVR_PROGRAMS) icom : $(ICM_PROGRAMS) +itap : $(TAP_PROGRAMS) qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread @@ -58,6 +60,9 @@ qnlink : QnetLink.o Random.o qnrelay : QnetRelay.o g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) +qnitap : QnetITAP.o Random.o + g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o $(LDFLAGS) + qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) $(LDFLAGS) -pthread @@ -105,6 +110,31 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg systemctl daemon-reload systemctl start qnrelay.service +installitap : $(TAP_PROGRAMS) gwys.txt qn.cfg + ######### QnetGateway ######### + /bin/cp -f qngateway $(BINDIR) + /bin/cp -f qnremote qnvoice $(BINDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) + /bin/cp -f system/qngateway.service $(SYSDIR) + systemctl enable qngateway.service + systemctl daemon-reload + systemctl start qngateway.service + ######### QnetLink ######### + /bin/cp -f qnlink $(BINDIR) + /bin/cp -f announce/*.dat $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) + /bin/cp -f exec_?.sh $(CFGDIR) + /bin/cp -f system/qnlink.service $(SYSDIR) + systemctl enable qnlink.service + systemctl daemon-reload + systemctl start qnlink.service + ######### QnetITAP ######### + /bin/cp -f qnitap $(BINDIR) + /bin/cp -f system/qnitap.service $(SYSDIR) + systemctl enable qnitap.service + systemctl daemon-reload + systemctl start qnitap.service + installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) @@ -229,6 +259,36 @@ uninstall : /bin/rm -f $(BINDIR)/qnrelay systemctl daemon-reload +uninstallitap : + ######### QnetGateway ######### + systemctl stop qngateway.service + systemctl disable qngateway.service + /bin/rm -f $(SYSDIR)/qngateway.service + /bin/rm -f $(BINDIR)/qngateway + /bin/rm -f $(BINDIR)/qnremote + /bin/rm -f $(BINDIR)/qnvoice + /bin/rm -f $(CFGDIR)/qn.cfg + ######### QnetLink ######### + systemctl stop qnlink.service + systemctl disable qnlink.service + /bin/rm -f $(SYSDIR)/qnlink.service + /bin/rm -f $(BINDIR)/qnlink + /bin/rm -f $(CFGDIR)/already_linked.dat + /bin/rm -f $(CFGDIR)/already_unlinked.dat + /bin/rm -f $(CFGDIR)/failed_linked.dat + /bin/rm -f $(CFGDIR)/id.dat + /bin/rm -f $(CFGDIR)/linked.dat + /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/RPT_STATUS.txt + /bin/rm -f $(CFGDIR)/gwys.txt + /bin/rm -f $(CFGDIR)/exec_?.sh + ######### QnetITAP ######### + systemctl stop qnitap.service + systemctl disable qnitap.service + /bin/rm -f $(SYSDIR)/qnitap.service + /bin/rm -f $(BINDIR)/qnitap + systemctl daemon-reload + uninstallicom : ######### QnetGateway ######### systemctl stop qngateway.service diff --git a/QnetITAP.cpp b/QnetITAP.cpp new file mode 100644 index 0000000..648e26e --- /dev/null +++ b/QnetITAP.cpp @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "versions.h" +#include "QnetITAP.h" +#include "QnetTypeDefs.h" + +std::atomic CQnetITAP::keep_running(true); + +CQnetITAP::CQnetITAP() : +COUNTER(0) +{ +} + +CQnetITAP::~CQnetITAP() +{ +} + +bool CQnetITAP::Initialize(const char *cfgfile) +{ + if (ReadConfig(cfgfile)) + return true; + + struct sigaction act; + act.sa_handler = &CQnetITAP::SignalCatch; + sigemptyset(&act.sa_mask); + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGHUP, &act, 0) != 0) { + printf("sigaction-HUP failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return true; + } + + return false; +} + +int CQnetITAP::OpenITAP() +{ + int fd = open(ITAP_DEVICE.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (fd < 0) { + printf("Failed to open device [%s], error=%d, message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + return -1; + } + + if (isatty(fd) == 0) { + printf("Device %s is not a tty device\n", ITAP_DEVICE.c_str()); + close(fd); + return -1; + } + + static termios t; + if (tcgetattr(fd, &t) < 0) { + printf("tcgetattr failed for %s, error=%d, message-%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + t.c_cflag |= CS8; + t.c_oflag &= ~(OPOST); + t.c_cc[VMIN] = 0; + t.c_cc[VTIME] = 10; + + cfsetospeed(&t, B38400); + cfsetispeed(&t, B38400); + + if (tcsetattr(fd, TCSANOW, &t) < 0) { + printf("tcsetattr failed for %s, error=%dm message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +int CQnetITAP::OpenSocket(const std::string &address, const unsigned short port) +{ + if (! port) { + printf("ERROR: OpenSocket: non-zero port must be specified.\n"); + return -1; + } + + int fd = ::socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + printf("Cannot create the UDP socket, err: %d, %s\n", errno, strerror(errno)); + return -1; + } + + sockaddr_in addr; + ::memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (! address.empty()) { + addr.sin_addr.s_addr = ::inet_addr(address.c_str()); + if (addr.sin_addr.s_addr == INADDR_NONE) { + printf("The local address is invalid - %s\n", address.c_str()); + close(fd); + return -1; + } + } + + int reuse = 1; + if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + printf("Cannot set the UDP socket %s:%u option, err: %d, %s\n", address.c_str(), port, errno, strerror(errno)); + close(fd); + return -1; + } + + if (::bind(fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { + printf("Cannot bind the UDP socket %s:%u address, err: %d, %s\n", address.c_str(), port, errno, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) +{ + // Shamelessly adapted from Jonathan G4KLX's CIcomController::GetResponse() + // Get the start of the frame or nothing at all + int ret = ::read(serfd, buf, 1U); + if (ret < 0) { + printf("Error when reading first byte from the Icom radio %d: %s", errno, strerror(errno)); + return RT_ERROR; + } + + if (ret == 0) + return RT_TIMEOUT; + + if (buf[0U] == 0xFFU) + return RT_TIMEOUT; + + unsigned int length = buf[0U]; + + if (length >= 100U) { + printf("Invalid data received from the Icom radio"); + return RT_ERROR; + } + + unsigned int offset = 1U; + + while (offset < length) { + ret = ::read(serfd, buf + offset, length - offset); + if (ret < 0) { + printf("Error when reading buffer from the Icom radio %d: %s", errno, strerror(errno)); + return RT_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + switch (buf[1U]) { + case 0x03U: + return RT_PONG; + case 0x10U: + return RT_HEADER; + case 0x12U: + if ((buf[3U] & 0x40U) == 0x40U) + return RT_EOT; + else + return RT_DATA; + case 0x21U: + return RT_HEADER_ACK; + case 0x23U: + return RT_DATA_ACK; + default: + return RT_UNKNOWN; + } +} + +void CQnetITAP::Run(const char *cfgfile) +{ + if (Initialize(cfgfile)) + return; + + serfd = OpenITAP(); + if (serfd < 0) + return; + + gsock = OpenSocket(G2_INTERNAL_IP, G2_OUT_PORT); + if (gsock < 0) { + ::close(serfd); + return; + } + + vsock = OpenSocket(std::string("0.0.0.0"), MMDVM_OUT_PORT); + if (vsock < 0) { + ::close(serfd); + ::close(gsock); + return; + } + + printf("vsock=%d, gsock=%d serfd=%d\n", vsock, gsock, serfd); + + keep_running = true; + + while (keep_running) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(serfd, &readfds); + FD_SET(gsock, &readfds); + int maxfs = (serfd > gsock) ? serfd : gsock; + + // don't care about writefds and exceptfds: + // and we'll wait as long as needed + int ret = ::select(maxfs+1, &readfds, NULL, NULL, NULL); + if (ret < 0) { + printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno)); + break; + } + if (ret == 0) + continue; + + // there is something to read! + unsigned char buf[100]; + sockaddr_in addr; + memset(&addr, 0, sizeof(sockaddr_in)); + socklen_t size = sizeof(sockaddr); + ssize_t len; + REPLY_TYPE rt = RT_NOTHING; + + if (FD_ISSET(serfd, &readfds)) { + rt = GetITAPData(buf); + + if (rt == RT_ERROR) { + printf("ERROR: Run: recvfrom(USB) return error %d, %s\n", errno, strerror(errno)); + break; + } + + if (rt == RT_TIMEOUT) + continue; + + } + + if (FD_ISSET(gsock, &readfds)) { + len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); + + if (len < 0) { + printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno)); + break; + } + + if (ntohs(addr.sin_port) != G2_IN_PORT) + printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); + + } + + if (len == 0) { + printf("DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); + continue; + } + + if (rt != RT_NOTHING) { + //printf("read %d bytes from ITAP\n", (int)buf[0]); + if (RT_DATA==rt || RT_HEADER==rt || RT_EOT==rt) { + if (ProcessITAP(buf)) + break; + } else { + switch (rt) { + case RT_HEADER_ACK: + printf("DEBUG: Run: got header acknowledgement\n"); + break; + case RT_DATA_ACK: + printf("DEBUG: Run: got data acknowledgement\n"); + break; + case RT_PONG: + printf("DEBUG: Run: got pong\n"); + break; + case RT_TIMEOUT: + printf("DEBUG: Run: got timeout\n"); + break; + default: + break; + } + } + } else if (0 == ::memcmp(buf, "DSTR", 4)) { + //printf("read %d bytes from QnetGateway\n", (int)len); + if (ProcessGateway(len, buf)) + break; + } else { + char title[5]; + for (int i=0; i<4; i++) + title[i] = (buf[i]>=0x20u && buf[i]<0x7fu) ? buf[i] : '.'; + title[4] = '\0'; + printf("DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); + } + } + + ::close(serfd); + ::close(gsock); + ::close(vsock); +} + +int CQnetITAP::SendTo(const unsigned char *buf) +{ + unsigned int ptr = 0; + unsigned int len = buf[0]; + + while (ptr < len) { + ssize_t n = ::write(serfd, buf + ptr, len - ptr); + if (n < 0) { + printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); + return -1; + } + + if (n > 0) + ptr += n; + } + + return len; +} + +int CQnetITAP::SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port) +{ + sockaddr_in addr; + ::memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ::inet_addr(address.c_str()); + addr.sin_port = htons(port); + + int len = ::sendto(fd, buf, size, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); + if (len < 0) + printf("ERROR: SendTo: fd=%d failed sendto %s:%u err: %d, %s\n", fd, address.c_str(), port, errno, strerror(errno)); + else if (len != size) + printf("ERROR: SendTo: fd=%d tried to sendto %s:%u %d bytes, actually sent %d.\n", fd, address.c_str(), port, size, len); + return len; +} + +bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) +{ + static unsigned int counter = 0; + if (29==len || 58==len) { //here is dstar data + SDSTR dstr; + ::memcpy(dstr.pkt_id, raw, len); // transfer raw data to SDSTR struct + + SITAP itap; // destination + if (58 == len) { // write a Header packet + counter = 0; + itap.length = 41U; + itap.type = 0x20; + memcpy(itap.header.flag, dstr.vpkt.hdr.flag, 3); + memcpy(itap.header.r1, dstr.vpkt.hdr.r1, 8); + memcpy(itap.header.r2, dstr.vpkt.hdr.r2, 8); + memcpy(itap.header.ur, dstr.vpkt.hdr.ur, 8); + memcpy(itap.header.my, dstr.vpkt.hdr.my, 8); + memcpy(itap.header.nm, dstr.vpkt.hdr.nm, 4); + int ret = SendTo(&itap.length); + if (ret != 49) { + printf("41: ProcessGateway: Could not write Header ITAP packet\n"); + return true; + } + if (log_qso) + printf("Sent ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), + itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm); + } else { // write an AMBE packet + itap.length = 17U; + itap.type = 0x22U; + itap.voice.counter = counter++; + itap.voice.sequence = dstr.vpkt.ctrl; + if (log_qso && dstr.vpkt.ctrl&0x40) + printf("Sent ITAP end of stream\n"); + else if (dstr.vpkt.ctrl > 20) + printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); + memcpy(itap.voice.ambe, dstr.vpkt.vasd.voice, 12); + itap.voice.end = 0xFFU; + int ret = SendTo(&itap.length); + if (ret != 17) { + printf("ERROR: ProcessGateway: Could not write AMBE ITAP packet\n"); + return true; + } + } + + } else + printf("DEBUG: ProcessGateway: unusual packet size read len=%d\n", len); + return false; +} + +bool CQnetITAP::ProcessITAP(const unsigned char *buf) +{ + static short stream_id = 0U; + SITAP itap; + unsigned int len = buf[0]; + if (len < 42) + ::memcpy(&itap.length, buf, len); // transfer raw data to SDSRP struct + + if (41==len || 16==len) { + // create a stream id if this is a header + if (41 == len) + stream_id = random.NewStreamID(); + + SDSTR dstr; // destination + // sets most of the params + ::memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(COUNTER++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x0; + dstr.vpkt.icm_id = 0x20; + dstr.vpkt.dst_rptr_id = 0x0; + dstr.vpkt.snd_rptr_id = 0x1; + dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); + dstr.vpkt.streamid = stream_id; + + if (41 == len) { // header + dstr.remaining = 0x30; + dstr.vpkt.ctrl = 0x80; + //memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); + memcpy(dstr.vpkt.hdr.flag, itap.header.flag, 3); + memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); + memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); + memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); + memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); + memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); + calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); + int ret = SendTo(vsock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); + if (ret != 58) { + printf("ERROR: ProcessITAP: Could not write gateway header packet\n"); + return true; + } + if (log_qso) + printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), + dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + } else if (16 == len) { // ambe + dstr.remaining = 0x16; + dstr.vpkt.ctrl = itap.voice.sequence; + memcpy(dstr.vpkt.vasd.voice, itap.voice.ambe, 12); + int ret = SendTo(vsock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + if (log_qso && dstr.vpkt.ctrl&0x40) + printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); + + if (ret != 29) { + printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); + return true; + } + } +// } else if (len < 65 && dsrp.tag == 0xAU) { +// printf("MMDVM Poll: '%s'\n", (char *)mpkt.poll_msg); + } else + printf("DEBUG: ProcessMMDVM: unusual packet len=%d\n", (int)buf[0]); + return false; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value) +{ + if (cfg.lookupValue(path, value)) { + if (value < min || value > max) + value = default_value; + } else + value = default_value; + printf("%s = [%d]\n", path, value); + return true; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value) +{ + if (cfg.lookupValue(path, value)) { + if (value < min || value > max) + value = default_value; + } else + value = default_value; + printf("%s = [%lg]\n", path, value); + return true; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, bool &value, const bool default_value) +{ + if (! cfg.lookupValue(path, value)) + value = default_value; + printf("%s = [%s]\n", path, value ? "true" : "false"); + return true; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +{ + if (cfg.lookupValue(path, value)) { + int l = value.length(); + if (lmax) { + printf("%s value '%s' is wrong size\n", path, value.c_str()); + return false; + } + } else + value = default_value; + printf("%s = [%s]\n", path, value.c_str()); + return true; +} + +// process configuration file and return true if there was a problem +bool CQnetITAP::ReadConfig(const char *cfgFile) +{ + Config cfg; + + printf("Reading file %s\n", cfgFile); + // Read the file. If there is an error, report it and exit. + try { + cfg.readFile(cfgFile); + } + catch(const FileIOException &fioex) { + printf("Can't read %s\n", cfgFile); + return true; + } + catch(const ParseException &pex) { + printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + return true; + } + + std::string itap_path, value; + int i; + for (i=0; i<3; i++) { + itap_path = "module."; + itap_path += ('a' + i); + if (cfg.lookupValue(itap_path + ".type", value)) { + if (0 == strcasecmp(value.c_str(), "itap")) + break; + } + } + if (i >= 3) { + printf("itap not defined in any module!\n"); + return true; + } + RPTR_MOD = 'A' + i; + int repeater_module = i; + MMDVM_OUT_PORT = (unsigned short int)(i + 19998); + + if (cfg.lookupValue(std::string(itap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { + int l = value.length(); + if (l<3 || l>CALL_SIZE-2) { + printf("Call '%s' is invalid length!\n", value.c_str()); + return true; + } else { + for (i=0; iCALL_SIZE-2) { + printf("Call '%s' is invalid length!\n", value.c_str()); + return true; + } else { + for (i=0; i> 8) ^ crc_tabccitt[tmp]; + } + crc_dstar_ffff = ~crc_dstar_ffff; + tmp = crc_dstar_ffff; + + pfcs[0] = (unsigned char)(crc_dstar_ffff & 0xff); + pfcs[1] = (unsigned char)((tmp >> 8) & 0xff); + + return; +} + +int main(int argc, const char **argv) +{ + setbuf(stdout, NULL); + if (2 != argc) { + printf("usage: %s path_to_config_file\n", argv[0]); + printf(" %s --version\n", argv[0]); + return 1; + } + + if ('-' == argv[1][0]) { + printf("\nQnetITAP Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", ITAP_VERSION); + printf("QnetITAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + CQnetITAP qnitap; + + qnitap.Run(argv[1]); + + printf("%s is closing.\n", argv[0]); + + return 0; +} diff --git a/QnetITAP.h b/QnetITAP.h new file mode 100644 index 0000000..6083d2f --- /dev/null +++ b/QnetITAP.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include + +#include +#include "Random.h" // for streamid generation + +using namespace libconfig; + +#define CALL_SIZE 8 +#define IP_SIZE 15 + +enum REPLY_TYPE { + RT_TIMEOUT, + RT_ERROR, + RT_UNKNOWN, + RT_HEADER, + RT_DATA, + RT_EOT, + RT_HEADER_ACK, + RT_DATA_ACK, + RT_PONG, + RT_NOTHING +}; + +// Icom Terminal and Access Point Mode data structure +#pragma pack(push, 1) +typedef struct itap_tag { + unsigned char length; + // 41 for header (42 for writing) + // 16 for voice (17 for writing) + unsigned char type; + // 0x03U pong + // 0x10U header from icom + // 0x12U data from icom (it's EOT if voice.sequence bit 0x40 is set) + // 0x20U header to icom + // 0x21U header acknowledgement + // 0x22U data to icom + // 0x23U data acknowledgement + union { + struct { + unsigned char flag[3]; + unsigned char r2[8]; + unsigned char r1[8]; + unsigned char ur[8]; + unsigned char my[8]; + unsigned char nm[4]; + unsigned char end; // 0xFFU for writing + } header; + struct { + unsigned char counter; // ordinal counter is reset with each header + unsigned char sequence; // is modulo 21 + unsigned char ambe[9]; + unsigned char text[3]; + unsigned char end; // 0xFFU for writing + } voice; + }; +} SITAP; +#pragma pack(pop) + +class CQnetITAP +{ +public: + // functions + CQnetITAP(); + ~CQnetITAP(); + void Run(const char *cfgfile); + + // data + static std::atomic keep_running; + +private: + // functions + bool Initialize(const char *cfgfile); + static void SignalCatch(const int signum); + bool ProcessGateway(const int len, const unsigned char *raw); + bool ProcessITAP(const unsigned char *raw); + int OpenSocket(const std::string &address, const unsigned short port); + int OpenITAP(); + int SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port); + int SendTo(const unsigned char *buf); + REPLY_TYPE GetITAPData(unsigned char *buf); + void calcPFCS(const unsigned char *packet, unsigned char *pfcs); + + // read configuration file + bool ReadConfig(const char *); + bool GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value); + bool GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value); + bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value); + bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value); + + // config data + char RPTR_MOD; + char RPTR[CALL_SIZE + 1]; + char OWNER[CALL_SIZE + 1]; + std::string ITAP_DEVICE, G2_INTERNAL_IP; + unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT; + bool log_qso; + + // parameters + int serfd, gsock, vsock; + unsigned char tapcounter; + unsigned short COUNTER; + + // helpers + CRandom random; +}; diff --git a/system/qnitap.service b/system/qnitap.service new file mode 100644 index 0000000..aa52f76 --- /dev/null +++ b/system/qnitap.service @@ -0,0 +1,10 @@ +[Unit] +Description=QnetITAP +After=systemd-user-session.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/qnitap /usr/local/etc/qn.cfg + +[Install] +WantedBy=multi-user.target diff --git a/versions.h b/versions.h index 2c7bcf5..8fb5e86 100644 --- a/versions.h +++ b/versions.h @@ -3,6 +3,7 @@ #define LINK_VERSION "QnetLink-6.0.0" #define DVAP_VERSION "QnetDVAP-5.1.1" #define RELAY_VERSION "QnetRelay-0.2.0" +#define ITAP_VERSION "QnetITAP-0.0.0" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION From 148ba8aeebee7e3d881ea718866f18a454064cbb Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 11:42:50 -0700 Subject: [PATCH 065/553] poll and ping --- QnetITAP.cpp | 36 +++++++++++++++++++++++------------- QnetITAP.h | 2 +- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 648e26e..eec0fbd 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -245,6 +245,7 @@ void CQnetITAP::Run(const char *cfgfile) printf("vsock=%d, gsock=%d serfd=%d\n", vsock, gsock, serfd); keep_running = true; + unsigned poll_counter = 0; while (keep_running) { fd_set readfds; @@ -253,9 +254,13 @@ void CQnetITAP::Run(const char *cfgfile) FD_SET(gsock, &readfds); int maxfs = (serfd > gsock) ? serfd : gsock; + struct timeval tv; + tv.tv_sec = (poll_counter >= 18) ? 1 : 0; + tv.tv_usec = (poll_counter >= 18) ? 0 : 100000; + // don't care about writefds and exceptfds: - // and we'll wait as long as needed - int ret = ::select(maxfs+1, &readfds, NULL, NULL, NULL); + // and we'll wait for 100 ms or 1 s, depending on ; + int ret = ::select(maxfs+1, &readfds, NULL, NULL, &tv); if (ret < 0) { printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno)); break; @@ -282,9 +287,7 @@ void CQnetITAP::Run(const char *cfgfile) if (rt == RT_TIMEOUT) continue; - } - - if (FD_ISSET(gsock, &readfds)) { + } else if (FD_ISSET(gsock, &readfds)) { len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); if (len < 0) { @@ -295,10 +298,17 @@ void CQnetITAP::Run(const char *cfgfile) if (ntohs(addr.sin_port) != G2_IN_PORT) printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); - } - - if (len == 0) { - printf("DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); + } else { + // nothing to read, so do the polling or pinging + if (poll_counter < 18) { + unsigned char poll[3] = { 0xffu, 0xffu, 0xffu }; + ::memcpy(buf, poll, 3); + poll_counter++; + } else { + unsigned char ping[3] = { 0x02u, 0x02u, 0xffu }; + ::memcpy(buf, ping, 3); + } + SendTo((unsigned char)0x03U, buf); continue; } @@ -343,10 +353,10 @@ void CQnetITAP::Run(const char *cfgfile) ::close(vsock); } -int CQnetITAP::SendTo(const unsigned char *buf) +int CQnetITAP::SendTo(const unsigned char length, const unsigned char *buf) { unsigned int ptr = 0; - unsigned int len = buf[0]; + const unsigned int len = (int)length; while (ptr < len) { ssize_t n = ::write(serfd, buf + ptr, len - ptr); @@ -396,7 +406,7 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) memcpy(itap.header.ur, dstr.vpkt.hdr.ur, 8); memcpy(itap.header.my, dstr.vpkt.hdr.my, 8); memcpy(itap.header.nm, dstr.vpkt.hdr.nm, 4); - int ret = SendTo(&itap.length); + int ret = SendTo(itap.length, &itap.length); if (ret != 49) { printf("41: ProcessGateway: Could not write Header ITAP packet\n"); return true; @@ -415,7 +425,7 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); memcpy(itap.voice.ambe, dstr.vpkt.vasd.voice, 12); itap.voice.end = 0xFFU; - int ret = SendTo(&itap.length); + int ret = SendTo(itap.length, &itap.length); if (ret != 17) { printf("ERROR: ProcessGateway: Could not write AMBE ITAP packet\n"); return true; diff --git a/QnetITAP.h b/QnetITAP.h index 6083d2f..ab4e8c4 100644 --- a/QnetITAP.h +++ b/QnetITAP.h @@ -98,7 +98,7 @@ private: int OpenSocket(const std::string &address, const unsigned short port); int OpenITAP(); int SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port); - int SendTo(const unsigned char *buf); + int SendTo(const unsigned char length, const unsigned char *buf); REPLY_TYPE GetITAPData(unsigned char *buf); void calcPFCS(const unsigned char *packet, unsigned char *pfcs); From ee0e62a2caa4205b6db4563c388c5eca5f745be9 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 12:24:42 -0700 Subject: [PATCH 066/553] tweaks to polling --- QnetITAP.cpp | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index eec0fbd..32cb45e 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -246,6 +246,7 @@ void CQnetITAP::Run(const char *cfgfile) keep_running = true; unsigned poll_counter = 0; + bool is_alive = false; while (keep_running) { fd_set readfds; @@ -265,14 +266,23 @@ void CQnetITAP::Run(const char *cfgfile) printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno)); break; } - if (ret == 0) + + if (0 == ret) { + // nothing to read, so do the polling or pinging + unsigned char buf[3]; + if (poll_counter++ < 18) { + unsigned char poll[3] = { 0xffu, 0xffu, 0xffu }; + ::memcpy(buf, poll, 3); + } else { + unsigned char ping[3] = { 0x02u, 0x02u, 0xffu }; + ::memcpy(buf, ping, 3); + } + SendTo((unsigned char)0x03U, buf); continue; + } // there is something to read! unsigned char buf[100]; - sockaddr_in addr; - memset(&addr, 0, sizeof(sockaddr_in)); - socklen_t size = sizeof(sockaddr); ssize_t len; REPLY_TYPE rt = RT_NOTHING; @@ -288,6 +298,9 @@ void CQnetITAP::Run(const char *cfgfile) continue; } else if (FD_ISSET(gsock, &readfds)) { + sockaddr_in addr; + memset(&addr, 0, sizeof(sockaddr_in)); + socklen_t size = sizeof(sockaddr); len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); if (len < 0) { @@ -298,18 +311,6 @@ void CQnetITAP::Run(const char *cfgfile) if (ntohs(addr.sin_port) != G2_IN_PORT) printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); - } else { - // nothing to read, so do the polling or pinging - if (poll_counter < 18) { - unsigned char poll[3] = { 0xffu, 0xffu, 0xffu }; - ::memcpy(buf, poll, 3); - poll_counter++; - } else { - unsigned char ping[3] = { 0x02u, 0x02u, 0xffu }; - ::memcpy(buf, ping, 3); - } - SendTo((unsigned char)0x03U, buf); - continue; } if (rt != RT_NOTHING) { @@ -326,10 +327,13 @@ void CQnetITAP::Run(const char *cfgfile) printf("DEBUG: Run: got data acknowledgement\n"); break; case RT_PONG: - printf("DEBUG: Run: got pong\n"); + if (! is_alive) { + printf("Icom Radio is connected.\n"); + is_alive = true; + } break; case RT_TIMEOUT: - printf("DEBUG: Run: got timeout\n"); + printf("DEBUG: Run: got a timeout.\n"); break; default: break; @@ -339,12 +343,6 @@ void CQnetITAP::Run(const char *cfgfile) //printf("read %d bytes from QnetGateway\n", (int)len); if (ProcessGateway(len, buf)) break; - } else { - char title[5]; - for (int i=0; i<4; i++) - title[i] = (buf[i]>=0x20u && buf[i]<0x7fu) ? buf[i] : '.'; - title[4] = '\0'; - printf("DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); } } From bbb0c2a5d1d189f5a480261a7e9480c9836f319d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 13:27:55 -0700 Subject: [PATCH 067/553] better handling of icoming Icom data --- QnetITAP.cpp | 119 ++++++++++++++++++++++++--------------------------- QnetITAP.h | 3 -- 2 files changed, 55 insertions(+), 67 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 32cb45e..fd0c926 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -190,7 +190,7 @@ REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) while (offset < length) { ret = ::read(serfd, buf + offset, length - offset); if (ret < 0) { - printf("Error when reading buffer from the Icom radio %d: %s", errno, strerror(errno)); + printf("Error when reading buffer from the Icom radio %d: %s\n", errno, strerror(errno)); return RT_ERROR; } @@ -207,10 +207,7 @@ REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) case 0x10U: return RT_HEADER; case 0x12U: - if ((buf[3U] & 0x40U) == 0x40U) - return RT_EOT; - else - return RT_DATA; + return RT_DATA; case 0x21U: return RT_HEADER_ACK; case 0x23U: @@ -315,7 +312,7 @@ void CQnetITAP::Run(const char *cfgfile) if (rt != RT_NOTHING) { //printf("read %d bytes from ITAP\n", (int)buf[0]); - if (RT_DATA==rt || RT_HEADER==rt || RT_EOT==rt) { + if (RT_DATA==rt || RT_HEADER==rt) { if (ProcessITAP(buf)) break; } else { @@ -422,7 +419,6 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) else if (dstr.vpkt.ctrl > 20) printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); memcpy(itap.voice.ambe, dstr.vpkt.vasd.voice, 12); - itap.voice.end = 0xFFU; int ret = SendTo(itap.length, &itap.length); if (ret != 17) { printf("ERROR: ProcessGateway: Could not write AMBE ITAP packet\n"); @@ -439,64 +435,59 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) { static short stream_id = 0U; SITAP itap; - unsigned int len = buf[0]; - if (len < 42) - ::memcpy(&itap.length, buf, len); // transfer raw data to SDSRP struct - - if (41==len || 16==len) { - // create a stream id if this is a header - if (41 == len) - stream_id = random.NewStreamID(); - - SDSTR dstr; // destination - // sets most of the params - ::memcpy(dstr.pkt_id, "DSTR", 4); - dstr.counter = htons(COUNTER++); - dstr.flag[0] = 0x73; - dstr.flag[1] = 0x12; - dstr.flag[2] = 0x0; - dstr.vpkt.icm_id = 0x20; - dstr.vpkt.dst_rptr_id = 0x0; - dstr.vpkt.snd_rptr_id = 0x1; - dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); - dstr.vpkt.streamid = stream_id; - - if (41 == len) { // header - dstr.remaining = 0x30; - dstr.vpkt.ctrl = 0x80; - //memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); - memcpy(dstr.vpkt.hdr.flag, itap.header.flag, 3); - memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); - memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); - memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); - memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); - memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); - calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); - int ret = SendTo(vsock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); - if (ret != 58) { - printf("ERROR: ProcessITAP: Could not write gateway header packet\n"); - return true; - } - if (log_qso) - printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), - dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); - } else if (16 == len) { // ambe - dstr.remaining = 0x16; - dstr.vpkt.ctrl = itap.voice.sequence; - memcpy(dstr.vpkt.vasd.voice, itap.voice.ambe, 12); - int ret = SendTo(vsock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); - if (log_qso && dstr.vpkt.ctrl&0x40) - printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); - - if (ret != 29) { - printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); - return true; - } + unsigned int len = (0x10U == buf[1]) ? 41 : 16; + ::memcpy(&itap.length, buf, len); // transfer raw data to SITAP struct + + // create a stream id if this is a header + if (41 == len) + stream_id = random.NewStreamID(); + + SDSTR dstr; // destination + // sets most of the params + ::memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(COUNTER++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x0; + dstr.vpkt.icm_id = 0x20; + dstr.vpkt.dst_rptr_id = 0x0; + dstr.vpkt.snd_rptr_id = 0x1; + dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); + dstr.vpkt.streamid = stream_id; + + if (41 == len) { // header + dstr.remaining = 0x30; + dstr.vpkt.ctrl = 0x80; + + memcpy(dstr.vpkt.hdr.flag, itap.header.flag, 3); + memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); + memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); + memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); + memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); + memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); + calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); + int ret = SendTo(vsock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); + if (ret != 58) { + printf("ERROR: ProcessITAP: Could not write gateway header packet\n"); + return true; } -// } else if (len < 65 && dsrp.tag == 0xAU) { -// printf("MMDVM Poll: '%s'\n", (char *)mpkt.poll_msg); - } else - printf("DEBUG: ProcessMMDVM: unusual packet len=%d\n", (int)buf[0]); + if (log_qso) + printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), + dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + } else if (16 == len) { // ambe + dstr.remaining = 0x16; + dstr.vpkt.ctrl = itap.voice.sequence; + memcpy(dstr.vpkt.vasd.voice, itap.voice.ambe, 12); + int ret = SendTo(vsock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + if (ret != 29) { + printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); + return true; + } + + if (log_qso && (dstr.vpkt.ctrl & 0x40)) + printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); + } + return false; } diff --git a/QnetITAP.h b/QnetITAP.h index ab4e8c4..69786a9 100644 --- a/QnetITAP.h +++ b/QnetITAP.h @@ -36,7 +36,6 @@ enum REPLY_TYPE { RT_UNKNOWN, RT_HEADER, RT_DATA, - RT_EOT, RT_HEADER_ACK, RT_DATA_ACK, RT_PONG, @@ -65,14 +64,12 @@ typedef struct itap_tag { unsigned char ur[8]; unsigned char my[8]; unsigned char nm[4]; - unsigned char end; // 0xFFU for writing } header; struct { unsigned char counter; // ordinal counter is reset with each header unsigned char sequence; // is modulo 21 unsigned char ambe[9]; unsigned char text[3]; - unsigned char end; // 0xFFU for writing } voice; }; } SITAP; From b026274c5a8c813e4b877607a37029a8b0872ef8 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 14:14:35 -0700 Subject: [PATCH 068/553] reading the usb device --- QnetITAP.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index fd0c926..88999ee 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -165,6 +165,7 @@ int CQnetITAP::OpenSocket(const std::string &address, const unsigned short port) REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) { // Shamelessly adapted from Jonathan G4KLX's CIcomController::GetResponse() + // and CSerialController::read() // Get the start of the frame or nothing at all int ret = ::read(serfd, buf, 1U); if (ret < 0) { @@ -188,17 +189,23 @@ REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) unsigned int offset = 1U; while (offset < length) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(serfd, &fds); + int n = ::select(serfd+1, &fds, NULL, NULL, NULL); // wait untill it's ready. won't return a zero. + if (n < 0) { + printf("ERROR: GetITAPData: select returned error %d: %s\n", errno, strerror(errno)); + return RT_ERROR; + } + ret = ::read(serfd, buf + offset, length - offset); - if (ret < 0) { + if (ret < 0 && errno!=EAGAIN) { printf("Error when reading buffer from the Icom radio %d: %s\n", errno, strerror(errno)); return RT_ERROR; } if (ret > 0) offset += ret; - - if (ret == 0) - std::this_thread::sleep_for(std::chrono::milliseconds(5)); } switch (buf[1U]) { @@ -286,10 +293,8 @@ void CQnetITAP::Run(const char *cfgfile) if (FD_ISSET(serfd, &readfds)) { rt = GetITAPData(buf); - if (rt == RT_ERROR) { - printf("ERROR: Run: recvfrom(USB) return error %d, %s\n", errno, strerror(errno)); + if (rt == RT_ERROR) break; - } if (rt == RT_TIMEOUT) continue; From 4702ad3ce2f446c1ef91abdc2854c35df63c46ef Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 14:30:43 -0700 Subject: [PATCH 069/553] teach others about itap --- QnetGateway.cpp | 3 +++ QnetRemote.cpp | 2 +- QnetVoice.cpp | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 517d6fc..1fbcad4 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -273,6 +273,9 @@ bool CQnetGateway::read_config(char *cfgFile) } else if (0 == type.compare("mmdvm")) { rptr.mod[m].package_version = MMDVM_VERSION; is_not_icom = true; + } else if (0 == type.compare("itap")) { + rptr.mod[m].package_version = ITAP_VERSION; + is_not_icom = true; } else { printf("module type '%s' is invalid\n", type.c_str()); return true; diff --git a/QnetRemote.cpp b/QnetRemote.cpp index c6c1274..e1bb139 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -198,7 +198,7 @@ bool read_config(const char *cfgFile) path += m + 'a'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("icom")) { + if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("icom") && type.compare("itap")) { printf("module type '%s' is invalid\n", type.c_str()); return true; } diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 46d4ce4..6173bf6 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -98,7 +98,8 @@ bool read_config(const char *cfgFile) path += m + 'a'; std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && strcasecmp(type.c_str(), "icom")) { + if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && + strcasecmp(type.c_str(), "icom") && strcasecmp(type.c_str(), "itap")) { printf("module type '%s' is invalid\n", type.c_str()); return true; } From 88e8482a33f8a99aca07638daeffa92bbd3b3d02 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 16:59:24 -0700 Subject: [PATCH 070/553] writing to ITAP --- QnetITAP.cpp | 18 ++++++++++-------- QnetITAP.h | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 88999ee..8e37890 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -361,8 +361,10 @@ int CQnetITAP::SendTo(const unsigned char length, const unsigned char *buf) while (ptr < len) { ssize_t n = ::write(serfd, buf + ptr, len - ptr); if (n < 0) { - printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); - return -1; + if (EAGAIN != errno) { + printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); + return -1; + } } if (n > 0) @@ -406,16 +408,16 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) memcpy(itap.header.ur, dstr.vpkt.hdr.ur, 8); memcpy(itap.header.my, dstr.vpkt.hdr.my, 8); memcpy(itap.header.nm, dstr.vpkt.hdr.nm, 4); - int ret = SendTo(itap.length, &itap.length); - if (ret != 49) { - printf("41: ProcessGateway: Could not write Header ITAP packet\n"); + itap.header.end = 0xFFU; + if (42 != SendTo(42U, &itap.length)) { + printf("ERROR: ProcessGateway: Could not write Header ITAP packet\n"); return true; } if (log_qso) printf("Sent ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm); } else { // write an AMBE packet - itap.length = 17U; + itap.length = 16U; itap.type = 0x22U; itap.voice.counter = counter++; itap.voice.sequence = dstr.vpkt.ctrl; @@ -424,8 +426,8 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) else if (dstr.vpkt.ctrl > 20) printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); memcpy(itap.voice.ambe, dstr.vpkt.vasd.voice, 12); - int ret = SendTo(itap.length, &itap.length); - if (ret != 17) { + itap.voice.end = 0xFFU; + if (17 != SendTo(17U, &itap.length)) { printf("ERROR: ProcessGateway: Could not write AMBE ITAP packet\n"); return true; } diff --git a/QnetITAP.h b/QnetITAP.h index 69786a9..e716172 100644 --- a/QnetITAP.h +++ b/QnetITAP.h @@ -64,12 +64,14 @@ typedef struct itap_tag { unsigned char ur[8]; unsigned char my[8]; unsigned char nm[4]; + unsigned char end; // 0xFFU for sending } header; struct { unsigned char counter; // ordinal counter is reset with each header unsigned char sequence; // is modulo 21 unsigned char ambe[9]; unsigned char text[3]; + unsigned char end; // 0xFFU for sending } voice; }; } SITAP; From 5c8779f3e4ef3857c20b86c09ecc8a0c56e9d3e5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 17:14:01 -0700 Subject: [PATCH 071/553] removed acknowledge logs --- QnetITAP.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 8e37890..6d218c5 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -322,12 +322,12 @@ void CQnetITAP::Run(const char *cfgfile) break; } else { switch (rt) { - case RT_HEADER_ACK: - printf("DEBUG: Run: got header acknowledgement\n"); - break; - case RT_DATA_ACK: - printf("DEBUG: Run: got data acknowledgement\n"); - break; + //case RT_HEADER_ACK: + // printf("DEBUG: Run: got header acknowledgement\n"); + // break; + //case RT_DATA_ACK: + // printf("DEBUG: Run: got data acknowledgement\n"); + // break; case RT_PONG: if (! is_alive) { printf("Icom Radio is connected.\n"); From 233f8a39859977615dc27b77d64f91aadd082d81 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 17:33:48 -0700 Subject: [PATCH 072/553] counter should be a uchar --- QnetITAP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 6d218c5..e5ae13e 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -392,7 +392,7 @@ int CQnetITAP::SendTo(const int fd, const unsigned char *buf, const int size, co bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) { - static unsigned int counter = 0; + static unsigned char counter = 0; if (29==len || 58==len) { //here is dstar data SDSTR dstr; ::memcpy(dstr.pkt_id, raw, len); // transfer raw data to SDSTR struct @@ -460,7 +460,7 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) dstr.vpkt.dst_rptr_id = 0x0; dstr.vpkt.snd_rptr_id = 0x1; dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); - dstr.vpkt.streamid = stream_id; + dstr.vpkt.streamid = htons(stream_id); if (41 == len) { // header dstr.remaining = 0x30; From 5238d037ba413c7327dd7859193aaf8279c127eb Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 17:49:48 -0700 Subject: [PATCH 073/553] ntohs for shorts --- QnetLink.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 402a414..5a2dab1 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -676,7 +676,7 @@ bool CQnetLink::read_config(const char *cfgFile) return true; get_value(cfg, "gateway.external.port", to_g2_external_port, 1024, 65535, 40000); - get_value(cfg, "gateway.log.qso", qso_details, true); + get_value(cfg, "log.qso", qso_details, true); if (! get_value(cfg, "file.gwys", gwys, 2, FILENAME_MAX, "/usr/local/etc/gwys.txt")) return true; @@ -2452,7 +2452,7 @@ void CQnetLink::Process() if (old_sid[i].sid != rdsvt.dsvt.streamid) { if (qso_details) printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - rdsvt.dsvt.streamid, rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], + ntohs(rdsvt.dsvt.streamid), rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.flag[0], rdsvt.dsvt.hdr.mycall, rdsvt.dsvt.hdr.sfx, rdsvt.dsvt.hdr.urcall, rdsvt.dsvt.hdr.rpt1, rdsvt.dsvt.hdr.rpt2, length, inet_ntoa(fromDst4.sin_addr), source_stn); @@ -2529,7 +2529,7 @@ void CQnetLink::Process() for (int i=0; i<3; i++) { if (old_sid[i].sid == rdsvt.dsvt.streamid) { if (qso_details) - printf("END from remote g2: streamID=%04x, %d bytes from IP=%s\n", rdsvt.dsvt.streamid, length, inet_ntoa(fromDst4.sin_addr)); + printf("END from remote g2: streamID=%04x, %d bytes from IP=%s\n", ntohs(rdsvt.dsvt.streamid), length, inet_ntoa(fromDst4.sin_addr)); old_sid[i].sid = 0x0; @@ -2737,7 +2737,7 @@ void CQnetLink::Process() old_sid[i].sid = 0x0; if (qso_details) - printf("END from dcs: streamID=%04x, %d bytes from IP=%s\n", rdsvt.dsvt.streamid, length, inet_ntoa(fromDst4.sin_addr)); + printf("END from dcs: streamID=%04x, %d bytes from IP=%s\n", ntohs(rdsvt.dsvt.streamid), length, inet_ntoa(fromDst4.sin_addr)); to_remote_g2[i].in_streamid = 0x0; dcs_seq[i] = 0xff; @@ -2853,7 +2853,7 @@ void CQnetLink::Process() if (length == 58) { if (qso_details) printf("START from local g2: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - dstr.counter, dstr.vpkt.streamid, dstr.vpkt.hdr.flag[0], dstr.vpkt.hdr.flag[1], dstr.vpkt.hdr.flag[2], + ntohs(dstr.counter), ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.flag[0], dstr.vpkt.hdr.flag[1], dstr.vpkt.hdr.flag[2], dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, length, inet_ntoa(fromRptr.sin_addr)); /* save mycall */ @@ -3291,7 +3291,7 @@ void CQnetLink::Process() if (dstr.vpkt.ctrl & 0x40U) { if (qso_details) - printf("END from local g2: cntr=%04x, streamID=%04x, %d bytes\n", dstr.counter, ntohs(dstr.vpkt.streamid), length); + printf("END from local g2: cntr=%04x, streamID=%04x, %d bytes\n", ntohs(dstr.counter), ntohs(dstr.vpkt.streamid), length); if (bool_rptr_ack) rptr_ack(i); From 4e1790821bd6e834be1f892271223830ddd013b6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 18:41:40 -0700 Subject: [PATCH 074/553] fixing terminal mode --- QnetITAP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index e5ae13e..4f57292 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -182,7 +182,7 @@ REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) unsigned int length = buf[0U]; if (length >= 100U) { - printf("Invalid data received from the Icom radio"); + printf("Invalid data received from the Icom radio, length=%d\n", length); return RT_ERROR; } From 375a904c332fb503c1b50bdc454e0296ec2206d6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 19:38:45 -0700 Subject: [PATCH 075/553] terminal mode support --- QnetITAP.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 4f57292..5b0668f 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -466,10 +466,29 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) dstr.remaining = 0x30; dstr.vpkt.ctrl = 0x80; + memcpy(dstr.vpkt.hdr.flag, itap.header.flag, 3); - memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); - memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); - memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); + if (0 == memcmp(itap.header.r1, "DIRECT", 6)) { + // Terminal Mode! + memcpy(dstr.vpkt.hdr.r1, RPTR, 7); // build r1 + dstr.vpkt.hdr.r1[7] = RPTR_MOD; // with module + memcpy(dstr.vpkt.hdr.r2, RPTR, 7); // build r1 + dstr.vpkt.hdr.r2[7] = 'G'; // with gateway + if (' ' == itap.header.ur[2]) { + // it's command, we have to right-shift it! + memset(dstr.vpkt.hdr.ur, ' ', 8); // first file ur with spaces + if (' ' == itap.header.ur[1]) + dstr.vpkt.hdr.ur[7] = itap.header.ur[0]; // one char command, like "E" or "I" + else + memcpy(dstr.vpkt.hdr.ur+6, itap.header.ur, 2); // two char command, like "HX" or "S0" + } else + memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); // ur is at least 3 chars + } else { + // Access Point Mode + memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); + memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); + memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); // ur is at least 3 chars + } memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); From 709cde72d1a952c4a6a772671392e36ef1b1d433 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 31 Jul 2018 21:30:01 -0700 Subject: [PATCH 076/553] systemd restart qnitap and minor QnetITAP.cpp clean-up --- QnetITAP.cpp | 15 ++++++++++----- system/qnitap.service | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 5b0668f..113f8b8 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -1,5 +1,7 @@ /* * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * CQnetITAP::GetITAPData() is based on some code that is... * Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify @@ -466,16 +468,18 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) dstr.remaining = 0x30; dstr.vpkt.ctrl = 0x80; - memcpy(dstr.vpkt.hdr.flag, itap.header.flag, 3); + + ////////////////// Terminal or Access ///////////////////////// if (0 == memcmp(itap.header.r1, "DIRECT", 6)) { // Terminal Mode! memcpy(dstr.vpkt.hdr.r1, RPTR, 7); // build r1 dstr.vpkt.hdr.r1[7] = RPTR_MOD; // with module - memcpy(dstr.vpkt.hdr.r2, RPTR, 7); // build r1 + memcpy(dstr.vpkt.hdr.r2, RPTR, 7); // build r2 dstr.vpkt.hdr.r2[7] = 'G'; // with gateway - if (' ' == itap.header.ur[2]) { - // it's command, we have to right-shift it! + if (' '==itap.header.ur[2] && ' '!=itap.header.ur[0]) { + // it's a command because it has as space in the 3rd position, we have to right-justify it! + // Terminal Mode left justifies short commands. memset(dstr.vpkt.hdr.ur, ' ', 8); // first file ur with spaces if (' ' == itap.header.ur[1]) dstr.vpkt.hdr.ur[7] = itap.header.ur[0]; // one char command, like "E" or "I" @@ -487,8 +491,9 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) // Access Point Mode memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); - memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); // ur is at least 3 chars + memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); } + memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); diff --git a/system/qnitap.service b/system/qnitap.service index aa52f76..d3a7e6a 100644 --- a/system/qnitap.service +++ b/system/qnitap.service @@ -5,6 +5,7 @@ After=systemd-user-session.service [Service] Type=simple ExecStart=/usr/local/bin/qnitap /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target From 28c29673e322b570dfb7ff73e742f74f2519e0fc Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 1 Aug 2018 07:21:49 -0700 Subject: [PATCH 077/553] changed default value for timing.play.wait to 1 sec --- QnetGateway.cpp | 2 +- QnetLink.cpp | 2 +- QnetRemote.cpp | 2 +- qn.everything.cfg | 2 +- qn.icom.cfg | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 1fbcad4..7a42a24 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -402,7 +402,7 @@ bool CQnetGateway::read_config(char *cfgFile) // timing path = "timing.play."; - get_value(cfg, path+"wait", play_wait, 1, 10, 2); + get_value(cfg, path+"wait", play_wait, 1, 10, 1); get_value(cfg, path+"delay", play_delay, 9, 25, 19); diff --git a/QnetLink.cpp b/QnetLink.cpp index 5a2dab1..238cfb2 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -695,7 +695,7 @@ bool CQnetLink::read_config(const char *cfgFile) if (! get_value(cfg, "file.announce_dir", announce_dir, 2, FILENAME_MAX, "/usr/local/etc")) return true; - get_value(cfg, "timing.play.wait", delay_before, 1, 10, 2); + get_value(cfg, "timing.play.wait", delay_before, 1, 10, 1); memset(link_at_startup, 0, CALL_SIZE+1); if (get_value(cfg, "link.link_at_start", value, 5, CALL_SIZE, "NONE")) { diff --git a/QnetRemote.cpp b/QnetRemote.cpp index e1bb139..77363a5 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -216,7 +216,7 @@ bool read_config(const char *cfgFile) get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, is_icom ? 20000 : 19000); - get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 2); + get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 1); get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19); diff --git a/qn.everything.cfg b/qn.everything.cfg index 4408baf..8045207 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -299,7 +299,7 @@ timing = { } play = { -# wait = 2 # seconds before playback occurs, between 1 and 10 +# wait = 1 # seconds before playback occurs, between 1 and 10 # delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds } diff --git a/qn.icom.cfg b/qn.icom.cfg index a80488c..7fb2aea 100644 --- a/qn.icom.cfg +++ b/qn.icom.cfg @@ -128,7 +128,7 @@ timing = { } play = { -# wait = 2 # seconds before playback occurs, between 1 and 10 +# wait = 1 # seconds before playback occurs, between 1 and 10 # delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds } From d0a66ab86c779307dea63b08ecbc56093aef2cdc Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 1 Aug 2018 08:38:04 -0700 Subject: [PATCH 078/553] fixed bug wrt position report --- QnetGateway.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 7a42a24..87004d2 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -289,7 +289,7 @@ bool CQnetGateway::read_config(char *cfgFile) return true; get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, is_icom ? 20000 : 19998+m); get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); - get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset,-1.0e12, 1.0e12, 0.0); + get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset, -1.0e12, 1.0e12, 0.0); get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); get_value(cfg, std::string(path+"agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); get_value(cfg, std::string(path+"latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); @@ -2448,7 +2448,7 @@ void CQnetGateway::PlayFileThread(char *file) void CQnetGateway::qrgs_and_maps() { - for(int i=0; i<3; i++) { + for (int i=0; i<3; i++) { std::string rptrcall = OWNER; rptrcall.resize(CALL_SIZE-1); rptrcall += i + 'A'; @@ -2519,7 +2519,7 @@ int CQnetGateway::init(char *cfgfile) printf("Repeater callsigns: [%s] [%s] [%s]\n", rptr.mod[0].call.c_str(), rptr.mod[1].call.c_str(), rptr.mod[2].call.c_str()); for (i = 0; i < 3; i++) { - rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; + //rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; band_txt[i].streamID = 0; band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; band_txt[i].lh_mycall[0] = '\0'; From 9ed87506538fb8436b0d31a8d39b1192dbcbc415 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 1 Aug 2018 11:14:43 -0700 Subject: [PATCH 079/553] clear flag[0] 0x40 from AP mode --- QnetGateway.cpp | 2 +- QnetITAP.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 87004d2..b6e79b4 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2088,7 +2088,7 @@ void CQnetGateway::process() } if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) - printf("id=%04x, cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); + printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); } } } diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 113f8b8..716dedb 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -492,6 +492,7 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); + dstr.vpkt.hdr.flag[0] &= ~0x40U; // clear this bit } memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); From 8807b5347beab1aeda1e66fdb9f3aefdb9aae382 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 1 Aug 2018 12:35:04 -0700 Subject: [PATCH 080/553] first ITAP release --- BUILDING | 33 +++++++++++++---------- ITAP.README | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 ++ qn.itap.cfg | 32 ++++++++++++++++++++++ versions.h | 4 +-- 5 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 ITAP.README create mode 100644 qn.itap.cfg diff --git a/BUILDING b/BUILDING index 01710ff..b63a84e 100644 --- a/BUILDING +++ b/BUILDING @@ -1,6 +1,6 @@ -Creating a DVAP or a Version 1 DVRPTR hotspot based on a Raspberry Pi or a BeagleBone -Black that can connect to both DStar reflectors as well as XREF reflectors based on -QnetGateway software is easy. +Creating a hotspot based on a Raspberry Pi or a BeagleBone Black that can connect to +XRF and DCS reflectors as well as XREF reflectors based on QnetGateway software is +easy. Start with a Raspberry Pi with the latest Raspbian image (see http://raspberrypi.org) or most any other computer with the latest Debian-based image. For this latest version @@ -8,17 +8,15 @@ of QnetGateway requires the c++ compiler of at least version 4.9. Even if you are building an MMDVMHost-based hot-spot, the executables and their source code are very small, so you can get away with a system with minimum storage -capacity. We have build gui-less versions on two gigabyte partitions! +capacity. We have build gui-less versions on two gigabyte partitions! You don't need +to overclock the RPi for QnetGateway, the default clock rate is just fine. -On the RPi, do "sudo raspi-config" and expand the partition, change the password -for the 'pi' user and do any other configuration setup. You don't need to overclock -the RPi for QnetGateway, the default clock rate is just fine. - -If you are using a DVAP Dongle or a DVRPTR_V1, login and plug in your device to see -if the OS is recognizing it. The kernel should auto load drivers and you will see -that with the "lsusb" command. The DVAP uses a FTDI chip and the DVRPTR uses Atmel. -If you don't see an approprite output from "lsusb" after your device is plugged in, -you need to enable it by executing: +If you are using a DVAP Dongle or a DVRPTR_V1, or connecting to an Icom Terminal and +Access Point enabled radio,login and plug in your device to see if the OS is +recognizing it. The kernel should auto load drivers and you will see that with the +"lsusb" command. The DVAP and the Icom radio digital-to-serial cable uses a FTDI chip +and the DVRPTR uses Atmel. If you don't see an approprite output from "lsusb" after +your device is plugged in, you need to enable it by executing: sudo depmod sudo modprobe @@ -47,11 +45,13 @@ dvrptr ircddb gateway. The first thing to do is change to the build directory with "cd QnetGateway" and then choose a target to make. There are targets for each of the supported devices: . "make icom" will build all programs needed for the Icom repeater. +. "make itap" will build all programs needed for Icom Terminal and Access mode. . "make dvap" will build all programs needed for the DVAP Dongle. . "make dvrptr" will build all programs needed for the DVRPTR_V1. . "make mmdvm" will build all programs needed for MMDVMHost support. (You need to download and build MMDVMHost separately, see the MMDVM.README file for more info. -. "make" will build all the QnetGateway executables. +. "make" will build all the QnetGateway executables. This is useful if you are + experimenting around with lots of different devices. Next, create your qn.cfg configuration file. There are three example for you to look at: @@ -63,6 +63,8 @@ at: . qn.icom.cfg is the starting place for configuring an Icom repeater. Please note that QnetGateway doesn't support the 23cm data only module in the Icom repeater stack. +. qn.itap.cfg is a simple configuration file for Icom's Terminal and Access Point + Mode. Please read ITAP.README for more information. Remeber the everything file or the icom file contain detailed comments about all of the values you can set. Just read through it and edit accordingly. In the end you will @@ -105,6 +107,9 @@ change the commands or create new commands. You are now ready to install your QnetGateway system. If you are installing an MMDVM-based system, follow the instructions in MMDVM.README. +If you are installing Icom's Terminal and Access Point mode, please follow the +instructions in ITAP.README. + To install either DVAP or DVRPTR_V1, type "sudo make installdvap" or "sudo make installdvrptr", respectively. If you are installing on an Icom repeater, type "sudo make installicom". This should get you up and running. It will take a few diff --git a/ITAP.README b/ITAP.README new file mode 100644 index 0000000..a47408e --- /dev/null +++ b/ITAP.README @@ -0,0 +1,77 @@ + + Building QnetGateway support for Icom's Terminal and Access Point Mode + + Copyright (C) 2018 by Thomas A. Early N7TAE + +I'll assume you'll be doing this on a Raspberry Pi, but any modern Debian-based +system should work. It just needs a g++ compiler with version greater than 4.9. +These instructions assume you have configured your system with the locale, keyboard +and time zone. When choosing locale, always choose a "UTF-8" version of your +locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" before +your start. On a Raspberry Pi, you can do all of this with the configureation menu: +"sudo raspi-config". + +1) Install the only external library you need: sudo apt-get install libconfig++-dev + Yeah! No wxWidgets! + +2) From your home directory, clone the QnetGateway software: + git clone git://github.com/n7tae/QnetGateway.git + +3) Get into the build directory: cd QnetGateway + +4) Then compile: make itap + If you are building on a multi-core computer, don't forget the -j option! + On a Raspberry Pi 2 or 3, use "-j4". Sorry, a Raspberry Pi Zero or a Beagle + Bone Black just has a single core. + +5) You need a configuration file called qn.cfg for QnetGateway. A good, nearly + working config file is qn.itap.cfg. Copy it to qn.cfg and edit it. The default + device for the Icom digital cable is "/dev/ttyUSB0". If you have muliple + USB devices on your system the device might end up somewhere else. Do "ls /dev" + before and after plugging in your cable to figure out where it is. If it's + not on /dev/ttyUSB0, uncomment the device line and put in the correct device. + +6) You need a gwys.txt file for all the systems to which you may wish to link. + If you want to be able to link to repeaters: ./get_gwy_list.sh + If you are only interested in linking to reflectors: ./reflist.sh + This will download and format your gwys.txt file. If the reflector(s) or + repeater(s) you use most often are not present in the gwys.txt file, you can + add them manually, using the same syntax as the existing entries, at the end + of the file. If you find you can no longer connect to a system, it may be + because its IP address has changed. You can execute either script again, copy + it to /usr/local/etc, and then: either reboot you system, or put " F" in + your URField and key your radio, or: sudo systemctl restart qnlink + +7) Now it's time to get the Icom radio ready. Plug in the digital cable to the + radio and use the approprite cable to connect to your hot-spot. Turn on the + radio and press the menu key and go to the "DV Gateway" menu item and press + the enter key and select either Access Point or Terminal mode. If you select + Access point, adjust the frequency in the usual way. + + If you are using Access Point mode, you'll probably want to turn down the + volume and disable other audio prompts from the radio that is operating + as an access point. Please refer to the Icom manual to do this. + +8) We have a gwys.txt file and a qn.cfg in the build directory, so we are ready + to install and start the three QnetGateway services: sudo make installitap + + You should be up and running now! Congratulations! + +9) You can see the log of any of the 3 services that make up the QnetGateway + system: + sudo journalctl -u qngateway -f + sudo journalctl -u qnlink -f + sudo journalctl -u qnitap -f + + You can do all three of these in one terminal window (that you can detach from!) + by using "screen": sudo apt-get install screen + If you don't know how to use screen: http://aperiodic.net/screen/quick_reference + Being able to detach from a screen session is very useful, especially if you are + operating "headless"! + +10) DTMF is _not_ enabled by default if you want it, you need to do two things: + First, create a working DTMF script. In the build directory: cp qndtmf.sh qndtmf + Then, install the DTMF service: sudo make installdtmf + You should be good to go, The DTMF command "00" should announce the linked + status of you module. See DTMF+REMOTE.README for more information. + diff --git a/README.md b/README.md index 066d369..f0219b9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ QnetGateway The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle, the DVRPTR_V1 and now the Icom repeater. It is *incredibly easy* to build and install the system. +The Qnet Gateway program now includes support for Icom's new Terminal and Access Point modes. For more information, please read the ITAP.README file. + For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file. To get started, clone the software to your Linux device: diff --git a/qn.itap.cfg b/qn.itap.cfg new file mode 100644 index 0000000..e991b63 --- /dev/null +++ b/qn.itap.cfg @@ -0,0 +1,32 @@ +# g2_ircddb Configuration for me + +ircddb = { + login = "XX0XXX" +# If you are not using rr.openquad.net, you need to specify the host and possibly the password. +# +# host = "some.server.host" // others include group1-irc.ircddb.net +# password = "1111111111111" // not needed for rr.openquad.net +} + +module = { + c = { // change the module to "b" if you are operating on the UHF band + type = "itap" +# device = "/dev/ttyUSB0" // if your serial-to-usb cable ends up on another device, then specify here +# uncomment and set if you want the following to appear on you ircddb host website. +# frequency = 145.5 // this is the default value, chose a quiet frequency +# range = 0.0 // in meters (1609.344 is one mile) +# agl = 0.0 // in meters +# latitude = 0.000000 // north is positive +# longitude = 0.000000 // east is positive +# desc1 = "Location1" // up to 20 chars +# desc2 = "location2" // up to 20 chars + } +} + +link = { +# add the callsigns that can shutdown or reboot your system +# admin = [ "XX0XXX" , "YY0YYY" ] // only these users can execute scripts + +# link to the reflector of your choice. the first character is the module you are linking. +# link_at_start = "CREF001C" +} diff --git a/versions.h b/versions.h index 8fb5e86..0bee857 100644 --- a/versions.h +++ b/versions.h @@ -1,9 +1,9 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.0.0" +#define IRCDDB_VERSION "QnetGateway-7.0.1" #define LINK_VERSION "QnetLink-6.0.0" #define DVAP_VERSION "QnetDVAP-5.1.1" #define RELAY_VERSION "QnetRelay-0.2.0" -#define ITAP_VERSION "QnetITAP-0.0.0" +#define ITAP_VERSION "QnetITAP-0.1.0" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION From d194dfcea10f9fb00b0a25e857012dc0e0a4af05 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 1 Aug 2018 13:02:12 -0700 Subject: [PATCH 081/553] log reporting for Relay and ITAP --- QnetITAP.cpp | 4 ++-- QnetRelay.cpp | 4 ++-- versions.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 716dedb..8c77b1b 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -423,9 +423,9 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) itap.type = 0x22U; itap.voice.counter = counter++; itap.voice.sequence = dstr.vpkt.ctrl; - if (log_qso && dstr.vpkt.ctrl&0x40) + if (log_qso && (dstr.vpkt.ctrl & 0x40)) printf("Sent ITAP end of stream\n"); - else if (dstr.vpkt.ctrl > 20) + if ((dstr.vpkt.ctrl & ~0x40U) > 20) printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); memcpy(itap.voice.ambe, dstr.vpkt.vasd.voice, 12); itap.voice.end = 0xFFU; diff --git a/QnetRelay.cpp b/QnetRelay.cpp index dd2ad48..17705cc 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -240,9 +240,9 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) dsrp.voice.seq = dstr.vpkt.ctrl; // ditto if (29 == len) { // write an AMBE packet dsrp.tag = 0x21U; - if (log_qso && dsrp.voice.seq&0x40) + if (log_qso && (dsrp.voice.seq & 0x40)) printf("Sent DSRP end of streamid=%04x\n", ntohs(dsrp.voice.id)); - else if (dsrp.voice.seq > 20) + if ((dsrp.voice.seq & ~0x40U) > 20) printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", dsrp.voice.seq); dsrp.voice.err = 0; // NOT SURE WHERE TO GET THIS FROM THE INPUT buf memcpy(dsrp.voice.ambe, dstr.vpkt.vasd.voice, 12); diff --git a/versions.h b/versions.h index 0bee857..3a757a9 100644 --- a/versions.h +++ b/versions.h @@ -2,8 +2,8 @@ #define IRCDDB_VERSION "QnetGateway-7.0.1" #define LINK_VERSION "QnetLink-6.0.0" #define DVAP_VERSION "QnetDVAP-5.1.1" -#define RELAY_VERSION "QnetRelay-0.2.0" -#define ITAP_VERSION "QnetITAP-0.1.0" +#define RELAY_VERSION "QnetRelay-0.2.1" +#define ITAP_VERSION "QnetITAP-0.1.1" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION From 200914f8f7df4e345e6f8dd1373b0cd847a18e8b Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 2 Aug 2018 11:46:43 -0700 Subject: [PATCH 082/553] AP mode --- ITAP.README | 6 +++++- README.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ITAP.README b/ITAP.README index a47408e..40debe1 100644 --- a/ITAP.README +++ b/ITAP.README @@ -46,12 +46,16 @@ your start. On a Raspberry Pi, you can do all of this with the configureation me radio and use the approprite cable to connect to your hot-spot. Turn on the radio and press the menu key and go to the "DV Gateway" menu item and press the enter key and select either Access Point or Terminal mode. If you select - Access point, adjust the frequency in the usual way. + Access Point, adjust the frequency in the usual way. If you are using Access Point mode, you'll probably want to turn down the volume and disable other audio prompts from the radio that is operating as an access point. Please refer to the Icom manual to do this. + Please note that Access Point Mode is a work in progress. There are still + significant performance issues, especially when the radio in AP Mode is + transmitting. It is usable, but just barely. Terminal Mode works well. + 8) We have a gwys.txt file and a qn.cfg in the build directory, so we are ready to install and start the three QnetGateway services: sudo make installitap diff --git a/README.md b/README.md index f0219b9..2adf1ab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ QnetGateway The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost (and all of its supported repeater modems) as well as the DVAP Dongle, the DVRPTR_V1 and now the Icom repeater. It is *incredibly easy* to build and install the system. -The Qnet Gateway program now includes support for Icom's new Terminal and Access Point modes. For more information, please read the ITAP.README file. +The Qnet Gateway program now includes support for Icom's new Terminal mode. Access Point mode is still having some performance issues and we will be working on this. For more information, please read the ITAP.README file. For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file. From 31bf51d09b3cf2817dacc3a46cf89fa596d7565f Mon Sep 17 00:00:00 2001 From: Colby Ross Date: Sat, 18 Aug 2018 19:49:38 -0400 Subject: [PATCH 083/553] Correcting default ICOM ip addresses. (#6) --- QnetGateway.cpp | 6 +++--- QnetRemote.cpp | 2 +- qn.icom.cfg | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index b6e79b4..c39a743 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -285,7 +285,7 @@ bool CQnetGateway::read_config(char *cfgFile) return true; } - if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, is_icom ? "172.16.0.20" : "127.0.0.1")) + if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, is_icom ? "172.16.0.1" : "127.0.0.1")) return true; get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, is_icom ? 20000 : 19998+m); get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); @@ -349,7 +349,7 @@ bool CQnetGateway::read_config(char *cfgFile) get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, is_icom ? "172.16.0.1" : "0.0.0.0")) + if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, is_icom ? "172.16.0.20" : "0.0.0.0")) return true; get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, is_icom ? 20000 : 19000); @@ -2597,7 +2597,7 @@ int CQnetGateway::init(char *cfgfile) // Open G2 INTERNAL: // default non-icom 127.0.0.1:19000 - // default icom 172.16.0.1:20000 + // default icom 172.16.0.20:20000 srv_sock = open_port(g2_internal); if (0 > srv_sock) { printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 77363a5..6eb6436 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -211,7 +211,7 @@ bool read_config(const char *cfgFile) return true; } - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, is_icom ? "172.16.0.1" : "127.0.0.1")) + if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, is_icom ? "172.16.0.20" : "127.0.0.1")) return true; get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, is_icom ? 20000 : 19000); diff --git a/qn.icom.cfg b/qn.icom.cfg index 7fb2aea..7e32f61 100644 --- a/qn.icom.cfg +++ b/qn.icom.cfg @@ -20,7 +20,7 @@ gateway = { } internal = { -# ip = "172.16.0.1" +# ip = "172.16.0.20" # port = 20000 } } @@ -32,7 +32,7 @@ module = { # 70 cm module will use "b" # 2 M module will use "c" # type = "icom" # you must define at least one module by uncommenting the type -# ip = "172.16.0.20" # all icom modules should have the same IP address +# ip = "172.16.0.1" # all icom modules should have the same IP address # port = 20000 # all icom modules should have the same UDP port # frequency = 0 # in MHz, if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage # offset = 0 @@ -47,7 +47,7 @@ module = { b = { # type = "icom" -# ip = "172.16.0.20" # all icom modules should have the same IP address +# ip = "172.16.0.1" # all icom modules should have the same IP address # port = 20000 # all icom modules should have the same UDP port # frequency = 0 # offset = 0 @@ -62,7 +62,7 @@ module = { c = { # type = "icom" -# ip = "172.16.0.20" # all icom modules should have the same IP address +# ip = "172.16.0.1" # all icom modules should have the same IP address # port = 20000 # all icom modules should have the same UDP port # frequency = 0 # dvap_offset = 0 From 8eaa69557a3a5f1981b5ec7793fe6cca32de7851 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 28 Aug 2018 11:17:25 -0700 Subject: [PATCH 084/553] DCS bug fix --- ITAP.README | 4 ++-- QnetLink.cpp | 2 +- versions.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ITAP.README b/ITAP.README index 40debe1..30a2560 100644 --- a/ITAP.README +++ b/ITAP.README @@ -11,7 +11,7 @@ locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" befor your start. On a Raspberry Pi, you can do all of this with the configureation menu: "sudo raspi-config". -1) Install the only external library you need: sudo apt-get install libconfig++-dev +1) Install the only external library you need: sudo apt install libconfig++-dev Yeah! No wxWidgets! 2) From your home directory, clone the QnetGateway software: @@ -68,7 +68,7 @@ your start. On a Raspberry Pi, you can do all of this with the configureation me sudo journalctl -u qnitap -f You can do all three of these in one terminal window (that you can detach from!) - by using "screen": sudo apt-get install screen + by using "screen": sudo apt install screen If you don't know how to use screen: http://aperiodic.net/screen/quick_reference Being able to detach from a screen session is very useful, especially if you are operating "headless"! diff --git a/QnetLink.cpp b/QnetLink.cpp index 238cfb2..8dd2d50 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -2666,7 +2666,7 @@ void CQnetLink::Process() rdsvt.head[0] = (unsigned char)(58 & 0xFF); rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); rdsvt.head[1] = (unsigned char)(rdsvt.head[1] | 0xFFFFFF80); - memcpy(rdsvt.dsvt.title + 2, "DSVT", 4); + memcpy(rdsvt.dsvt.title, "DSVT", 4); rdsvt.dsvt.config = 0x10; rdsvt.dsvt.flaga[0] = rdsvt.dsvt.flaga[1] = rdsvt.dsvt.flaga[2] = 0x00; rdsvt.dsvt.id = 0x20; diff --git a/versions.h b/versions.h index 3a757a9..9cefc65 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! #define IRCDDB_VERSION "QnetGateway-7.0.1" -#define LINK_VERSION "QnetLink-6.0.0" +#define LINK_VERSION "QnetLink-6.0.1" #define DVAP_VERSION "QnetDVAP-5.1.1" #define RELAY_VERSION "QnetRelay-0.2.1" #define ITAP_VERSION "QnetITAP-0.1.1" From d78bd10bfe1cf25f7b79c0378ad4103458047373 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 6 Sep 2018 12:39:35 -0700 Subject: [PATCH 085/553] orderly start-up --- system/mmdvm.service | 3 ++- system/qndvap.service | 3 ++- system/qndvrptr.service | 3 ++- system/qngateway.service | 4 +++- system/qnitap.service | 3 ++- system/qnlink.service | 4 +++- system/qnrelay.service | 4 +++- 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/system/mmdvm.service b/system/mmdvm.service index 58f2760..55cf290 100644 --- a/system/mmdvm.service +++ b/system/mmdvm.service @@ -1,6 +1,7 @@ [Unit] Description=MMDVMHost -After=systemd-user-session.service +Requires=qnrelay.service +After=systemd-user-session.service qnrelay.service [Service] Type=simple diff --git a/system/qndvap.service b/system/qndvap.service index 38031bb..c5ec6fb 100644 --- a/system/qndvap.service +++ b/system/qndvap.service @@ -1,6 +1,7 @@ [Unit] Description=QnetDVAP -After=systemd-user-session.service +Requires=qngateway.service +After=systemd-user-session.service qngateway.service [Service] Type=simple diff --git a/system/qndvrptr.service b/system/qndvrptr.service index 18869cc..79a15bb 100644 --- a/system/qndvrptr.service +++ b/system/qndvrptr.service @@ -1,6 +1,7 @@ [Unit] Description=QnetDVRPTR -After=systemd-user-session.service +Requires=qngateway.service +After=systemd-user-session.service qngateway.service [Service] Type=simple diff --git a/system/qngateway.service b/system/qngateway.service index 1068d3c..911ccac 100644 --- a/system/qngateway.service +++ b/system/qngateway.service @@ -1,10 +1,12 @@ [Unit] Description=QnetGateway -After=systemd-user-session.service +Requires=network.target +After=systemd-user-session.service network.target [Service] Type=simple ExecStart=/usr/local/bin/qngateway /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target diff --git a/system/qnitap.service b/system/qnitap.service index d3a7e6a..41ec92a 100644 --- a/system/qnitap.service +++ b/system/qnitap.service @@ -1,6 +1,7 @@ [Unit] Description=QnetITAP -After=systemd-user-session.service +Requires=qngateway.service +After=systemd-user-session.service qngateway.service [Service] Type=simple diff --git a/system/qnlink.service b/system/qnlink.service index 12ca713..f1dbc83 100644 --- a/system/qnlink.service +++ b/system/qnlink.service @@ -1,10 +1,12 @@ [Unit] Description=QnetLink -After=systemd-user-session.service +Requires=qngateway.service +After=systemd-user-session.service qngateway.service [Service] Type=simple ExecStart=/usr/local/bin/qnlink /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target diff --git a/system/qnrelay.service b/system/qnrelay.service index 823744d..9dfdf94 100644 --- a/system/qnrelay.service +++ b/system/qnrelay.service @@ -1,10 +1,12 @@ [Unit] Description=QnetRelay -After=systemd-user-session.service +Requires=qngateway.service +After=systemd-user-session.service qngateway.service [Service] Type=simple ExecStart=/usr/local/bin/qnrelay /usr/local/etc/qn.cfg +Restart=always [Install] WantedBy=multi-user.target From b1cb145475608277393da7ca84e987011923541c Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 7 Sep 2018 18:27:40 -0700 Subject: [PATCH 086/553] kill timer --- QnetITAP.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 8c77b1b..0ad044a 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -253,6 +253,7 @@ void CQnetITAP::Run(const char *cfgfile) keep_running = true; unsigned poll_counter = 0; bool is_alive = false; + std::chrono::steady_clock::time_point lastdata = std::chrono::steady_clock::now(); while (keep_running) { fd_set readfds; @@ -273,17 +274,23 @@ void CQnetITAP::Run(const char *cfgfile) break; } + // check for a dead or disconnected radio + std::chrono::steady_clock::duration sincelastdata = std::chrono::steady_clock::now() - lastdata; + double deadtime = sincelastdata.count() * std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den; + if (10.0 < deadtime) { + print("no activity from radio for 10 sec. Exiting...\n"); + break; + } + if (0 == ret) { // nothing to read, so do the polling or pinging - unsigned char buf[3]; if (poll_counter++ < 18) { unsigned char poll[3] = { 0xffu, 0xffu, 0xffu }; - ::memcpy(buf, poll, 3); + SendTo((unsigned char)0x03U, poll); } else { unsigned char ping[3] = { 0x02u, 0x02u, 0xffu }; - ::memcpy(buf, ping, 3); + SendTo((unsigned char)0x03U, ping); } - SendTo((unsigned char)0x03U, buf); continue; } @@ -318,6 +325,7 @@ void CQnetITAP::Run(const char *cfgfile) } if (rt != RT_NOTHING) { + lastdata = std::chrono::steady_clock::now(); //printf("read %d bytes from ITAP\n", (int)buf[0]); if (RT_DATA==rt || RT_HEADER==rt) { if (ProcessITAP(buf)) @@ -336,9 +344,6 @@ void CQnetITAP::Run(const char *cfgfile) is_alive = true; } break; - case RT_TIMEOUT: - printf("DEBUG: Run: got a timeout.\n"); - break; default: break; } From f5edb41fa2fbd31d8350e9f6dd8acea44fae188a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 7 Sep 2018 18:33:27 -0700 Subject: [PATCH 087/553] printf --- QnetITAP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 0ad044a..be8e271 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -278,7 +278,7 @@ void CQnetITAP::Run(const char *cfgfile) std::chrono::steady_clock::duration sincelastdata = std::chrono::steady_clock::now() - lastdata; double deadtime = sincelastdata.count() * std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den; if (10.0 < deadtime) { - print("no activity from radio for 10 sec. Exiting...\n"); + printf("no activity from radio for 10 sec. Exiting...\n"); break; } From f95437218955e418d3815935183cb0a05cd114d5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 7 Sep 2018 19:20:52 -0700 Subject: [PATCH 088/553] version bump --- versions.h | 2 +- versions.h.gch | Bin 0 -> 1909168 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 versions.h.gch diff --git a/versions.h b/versions.h index 9cefc65..9cfef06 100644 --- a/versions.h +++ b/versions.h @@ -3,7 +3,7 @@ #define LINK_VERSION "QnetLink-6.0.1" #define DVAP_VERSION "QnetDVAP-5.1.1" #define RELAY_VERSION "QnetRelay-0.2.1" -#define ITAP_VERSION "QnetITAP-0.1.1" +#define ITAP_VERSION "QnetITAP-0.2.0" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION diff --git a/versions.h.gch b/versions.h.gch new file mode 100644 index 0000000000000000000000000000000000000000..6526d52818e5444f5cd7fedf3f9e239bef4fed47 GIT binary patch literal 1909168 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 spbswRFbq4WQ_ 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%0D}HW|8;7IyxcNrwak%tnHt!g`Po3)Pdf)cT;b=W> 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%0ADID5B3myG!h$7Ij~A=Gfe(u{Zn;$Q z!iS~Oo-Gz9d{`>wwpg6-VX=H(DtY0q{R^ZmCuf3ut3f+Y@!4BYAmcOH4d8xCIeu5Y~d3#)cI KXFJS)bo?)sqyjYn literal 0 HcmV?d00001 From 5eceaf08241e310c2064614f85a3f3591d706b91 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 8 Sep 2018 07:35:44 -0700 Subject: [PATCH 089/553] use shutdown --- exec_H.sh | 2 +- exec_R.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exec_H.sh b/exec_H.sh index ee14f1e..3cf53e3 100755 --- a/exec_H.sh +++ b/exec_H.sh @@ -1,2 +1,2 @@ #!/bin/sh -halt +shutdown - h now diff --git a/exec_R.sh b/exec_R.sh index d7cfcc0..7e4b8af 100755 --- a/exec_R.sh +++ b/exec_R.sh @@ -1,2 +1,2 @@ #!/bin/sh -reboot +shutdown -r now From 1e5a5ac56da4ef9b9c8e0b8c4c861413074fdb1c Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 8 Sep 2018 07:36:28 -0700 Subject: [PATCH 090/553] shutdown --- exec_H.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exec_H.sh b/exec_H.sh index 3cf53e3..40db820 100755 --- a/exec_H.sh +++ b/exec_H.sh @@ -1,2 +1,2 @@ #!/bin/sh -shutdown - h now +shutdown -h now From 12251d7d25dfe20fc17a2584b76186dfa5a61f7f Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 9 Sep 2018 12:28:38 -0700 Subject: [PATCH 091/553] typos and inbound dongle check --- .gitignore | 1 + QnetLink.cpp | 4 ++-- qn.everything.cfg | 6 +++--- versions.h | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 8690511..cda52ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o *.d +*.gch qnitap qndvap qndvrptr diff --git a/QnetLink.cpp b/QnetLink.cpp index 8dd2d50..5293478 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -2300,8 +2300,8 @@ void CQnetLink::Process() if ((inbound_list.size() + 1) > max_dongles) printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip, (int)inbound_list.size()); - else if (admin.size() && (admin.find(call) == admin.end())) - printf("Incoming call [%s] from %s not an ADMIN\n", call, ip); + //else if (admin.size() && (admin.find(call) == admin.end())) + // printf("Incoming call [%s] from %s not an ADMIN\n", call, ip); else if (regexec(&preg, call, 0, NULL, 0) != 0) { printf("Invalid dongle callsign: CALL=%s,ip=%s\n", call, ip); diff --git a/qn.everything.cfg b/qn.everything.cfg index 8045207..280e2f8 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -266,7 +266,7 @@ link = { # to link repeater module B to REF001 C, use "BREF001C" # ref_login = "" # for loging into REF reflectors, if undefined, ircddb.username will be used # admin = [ "CALL1", "CALL2", "CALL3" ] # only these users can execute scripts, block dongles and reload the gwys.txt - # you probabaly want you own callsign in the admin list! + # you probably want you own callsign in the admin list! # link_unlink = [ "CALL4", "CALL5", "CALL6" ] # if defined, only these users can link and unlink a repeater # no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist # if the blacklist is defined (even if it's empty), the link_unlink will not be read @@ -277,8 +277,8 @@ link = { # xrf_port = 30001 # port for XRF linking, don't change # dcs_port = 30051 # port for DCS linking, don't change # announce = true # do link, unlink, etc. announcements -# acknowledge = true # send text acknowledgement on key-up -# max_dongles = 5 # maximum number of linked hotspots +# acknowledge = true # send text acknowledgment on key-up +# max_dongles = 5 # maximum number of linked hot-spots } file = { diff --git a/versions.h b/versions.h index 9cfef06..9d787f6 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! #define IRCDDB_VERSION "QnetGateway-7.0.1" -#define LINK_VERSION "QnetLink-6.0.1" +#define LINK_VERSION "QnetLink-6.0.2" #define DVAP_VERSION "QnetDVAP-5.1.1" #define RELAY_VERSION "QnetRelay-0.2.1" #define ITAP_VERSION "QnetITAP-0.2.0" From a2b9e0a1b3754096bb62ce895dfaafa9d36ae060 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 9 Sep 2018 12:31:32 -0700 Subject: [PATCH 092/553] gch in make --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e9f26bd..7f31f11 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ qnvoice : QnetVoice.o Random.o .PHONY: clean clean: - $(RM) $(OBJS) $(DEPS) $(ALL_PROGRAMS) + $(RM) $(OBJS) $(DEPS) $(ALL_PROGRAMS) *.gch -include $(DEPS) From f68177e5ecfa69980a76a12d44e9ae89ccaef4d1 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 20 Sep 2018 07:11:28 -0700 Subject: [PATCH 093/553] Delete versions.h.gch --- versions.h.gch | Bin 1909168 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 versions.h.gch diff --git a/versions.h.gch b/versions.h.gch deleted file mode 100644 index 6526d52818e5444f5cd7fedf3f9e239bef4fed47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909168 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 spbswRFbq4WQ_ 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%0D}HW|8;7IyxcNrwak%tnHt!g`Po3)Pdf)cT;b=W> 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%0ADID5B3myG!h$7Ij~A=Gfe(u{Zn;$Q z!iS~Oo-Gz9d{`>wwpg6-VX=H(DtY0q{R^ZmCuf3ut3f+Y@!4BYAmcOH4d8xCIeu5Y~d3#)cI KXFJS)bo?)sqyjYn From 50b02038e33444dbac3345b7dd1a0a3e70d20d34 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 25 Sep 2018 05:04:17 -0700 Subject: [PATCH 094/553] typo --- ITAP.README | 3 +-- versions.h.gch | Bin 1909168 -> 0 bytes 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 versions.h.gch diff --git a/ITAP.README b/ITAP.README index 30a2560..df0147e 100644 --- a/ITAP.README +++ b/ITAP.README @@ -8,7 +8,7 @@ system should work. It just needs a g++ compiler with version greater than 4.9. These instructions assume you have configured your system with the locale, keyboard and time zone. When choosing locale, always choose a "UTF-8" version of your locale. And make sure you do "sudo apt-get update && sudo apt-get upgrade" before -your start. On a Raspberry Pi, you can do all of this with the configureation menu: +your start. On a Raspberry Pi, you can do all of this with the configuration menu: "sudo raspi-config". 1) Install the only external library you need: sudo apt install libconfig++-dev @@ -78,4 +78,3 @@ your start. On a Raspberry Pi, you can do all of this with the configureation me Then, install the DTMF service: sudo make installdtmf You should be good to go, The DTMF command "00" should announce the linked status of you module. See DTMF+REMOTE.README for more information. - diff --git a/versions.h.gch b/versions.h.gch deleted file mode 100644 index 6526d52818e5444f5cd7fedf3f9e239bef4fed47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909168 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 spbswRFbq4WQ_ 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%0D}HW|8;7IyxcNrwak%tnHt!g`Po3)Pdf)cT;b=W> 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%0ADID5B3myG!h$7Ij~A=Gfe(u{Zn;$Q z!iS~Oo-Gz9d{`>wwpg6-VX=H(DtY0q{R^ZmCuf3ut3f+Y@!4BYAmcOH4d8xCIeu5Y~d3#)cI KXFJS)bo?)sqyjYn From f9acf25f851d8b581dfe126da03e95d54b8f0ca0 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 25 Sep 2018 13:30:03 -0700 Subject: [PATCH 095/553] correct log g2 --- QnetGateway.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index c39a743..6306ba4 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -920,8 +920,8 @@ void CQnetGateway::process() (g2buf.hdr.flag[0] == 0x40))) { if (bool_qso_details) printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", - ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, - g2buf.hdr.urcall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, + g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); memcpy(rptrbuf.pkt_id, "DSTR", 4); rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter From b4adc4417fd1b9d03f06cd6f45ce323fc19d5110 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 26 Sep 2018 12:37:41 -0700 Subject: [PATCH 096/553] process cracked into 3 --- QnetGateway.cpp | 2572 ++++++++++++++++++++++++----------------------- QnetGateway.h | 8 +- 2 files changed, 1294 insertions(+), 1286 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 6306ba4..26b7eda 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -662,1436 +662,1440 @@ bool CQnetGateway::get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, return false; } -/* run the main loop for QnetGateway */ -void CQnetGateway::process() +bool CQnetGateway::Flag_is_ok(unsigned char flag) { - SDSVT g2buf; - fd_set fdset; + // normal break emr emr+break + return 0x00U==flag || 0x08U==flag || 0x20U==flag || 0x28U==flag; +} - char temp_radio_user[CALL_SIZE + 1]; - char temp_mod; - time_t t_now; +void CQnetGateway::ProcessTimeouts() +{ + for (int i=0; i<3; i++) { + time_t t_now; + /* echotest recording timed out? */ + if (recd[i].last_time != 0) { + time(&t_now); + if ((t_now - recd[i].last_time) > echotest_rec_timeout) { + printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", + i, recd[i].streamid); + + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* START: echotest thread setup */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); + } catch (const std::exception &e) { + printf("Failed to start echotest thread. Exception: %s\n", e.what()); + // when the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } + /* END: echotest thread setup */ + } + } - char arearp_cs[CALL_SIZE + 1]; - char zonerp_cs[CALL_SIZE + 1]; - char ip[IP_SIZE + 1]; + /* voicemail recording timed out? */ + if (vm[i].last_time != 0) { + time(&t_now); + if ((t_now - vm[i].last_time) > voicemail_rec_timeout) { + printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", + i, vm[i].streamid); + + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + } - char tempfile[FILENAME_MAX + 1]; - long num_recs = 0L; - short int rec_len = 56; + // any stream going to local repeater timed out? + if (toRptr[i].last_time != 0) { + time(&t_now); + // The stream can be from a cross-band, or from a remote system, + // so we could use either FROM_LOCAL_RPTR_TIMEOUT or FROM_REMOTE_G2_TIMEOUT + // but FROM_REMOTE_G2_TIMEOUT makes more sense, probably is a bigger number + if ((t_now - toRptr[i].last_time) > from_remote_g2_timeout) { + printf("Inactivity to local rptr mod index %d, removing stream id %04x\n", i, toRptr[i].streamid); + + // Send end_of_audio to local repeater. + // Let the repeater re-initialize + end_of_audio.counter = is_icom ? G2_COUNTER_OUT++ :toRptr[i].G2_COUNTER++; + if (i == 0) + end_of_audio.vpkt.snd_term_id = 0x03; + else if (i == 1) + end_of_audio.vpkt.snd_term_id = 0x01; + else + end_of_audio.vpkt.snd_term_id = 0x02; + end_of_audio.vpkt.streamid = toRptr[i].streamid; + end_of_audio.vpkt.ctrl = toRptr[i].sequence | 0x40; + + for (int j=0; j<2; j++) + sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + toRptr[i].last_time = 0; + } + } - std::future aprs_future, irc_data_future; + /* any stream coming from local repeater timed out ? */ + if (band_txt[i].last_time != 0) { + time(&t_now); + if ((t_now - band_txt[i].last_time) > from_local_rptr_timeout) { + /* This local stream never went to a remote system, so trace the timeout */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) + printf("Inactivity from local rptr band %d, removing stream id %04x\n", i, band_txt[i].streamID); - // dtmf stuff - int dtmf_buf_count[3] = {0, 0, 0}; - char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; - int dtmf_last_frame[3] = { 0, 0, 0 }; - unsigned int dtmf_counter[3] = { 0, 0, 0 }; + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0x0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; - /* START: TEXT crap */ - bool new_group[3] = { true, true, true }; - int header_type = 0; - short to_print[3] = { 0, 0, 0 }; - bool ABC_grp[3] = { false, false, false }; - bool C_seen[3] = { false, false, false }; - unsigned char tmp_txt[3]; - /* END: TEXT crap */ + band_txt[i].last_time = 0; - int max_nfds = 0; + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; - dstar_dv_init(); + band_txt[i].dest_rptr[0] = '\0'; - if (g2_sock > max_nfds) - max_nfds = g2_sock; - if (srv_sock > max_nfds) - max_nfds = srv_sock; - printf("g2=%d, srv=%d, MAX+1=%d\n", g2_sock, srv_sock, max_nfds + 1); + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + } + } - /* start the beacon thread */ - if (bool_send_aprs) { - try { - aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); - } catch (const std::exception &e) { - printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); + /* any stream from local repeater to a remote gateway timed out ? */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr != 0) { + time(&t_now); + if ((t_now - to_remote_g2[i].last_time) > from_local_rptr_timeout) { + printf("Inactivity from local rptr mod %d, removing stream id %04x\n", i, to_remote_g2[i].streamid); + + memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } } - if (aprs_future.valid()) - printf("APRS beacon thread started\n"); } +} - try { - irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); - } catch (const std::exception &e) { - printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); - keep_running = false; +void CQnetGateway::ProcessRouting() +{ + SDSVT g2buf; + socklen_t fromlen = sizeof(struct sockaddr_in); + int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); + + // save incoming port for mobile systems + if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { + printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } else { + if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { + printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } } - if (keep_running) - printf("get_irc_data thread started\n"); - ii->kickWatchdog(IRCDDB_VERSION); + if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { + if (g2buflen == 56) { + + // Find out the local repeater module IP/port to send the data to + int i = g2buf.hdr.rpt1[7] - 'A'; + + /* valid repeater module? */ + if (i>=0 && i<3) { + // toRptr[i] is active if a remote system is talking to it or + // toRptr[i] is receiving data from a cross-band + if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { + if (bool_qso_details) + printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x30; + rptrbuf.vpkt.icm_id = 0x20; + //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); + rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; + rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; + rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; + rptrbuf.vpkt.streamid = g2buf.streamid; + rptrbuf.vpkt.ctrl = g2buf.ctrl; + memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); + if (is_icom) { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); + } else { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + } + memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); + memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); + memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); + memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); - if (is_icom) { - // send INIT to Icom Stack - unsigned char buf[500]; - memset(buf, 0, 10); - memcpy(buf, "INIT", 4); - buf[6] = 0x73U; - // we can use the module a band_addr for INIT - sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - printf("Waiting for ICOM controller...\n"); + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - // get the acknowledgement from the ICOM Stack - while (keep_running) { - socklen_t fromlength = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, buf, 500, 0, (struct sockaddr *)&fromRptr, &fromlength); - if (10==recvlen && 0==memcmp(buf, "INIT", 4) && 0x72U==buf[6] && 0x0U==buf[7]) { - OLD_REPLY_SEQ = 256U * buf[4] + buf[5]; - NEW_REPLY_SEQ = OLD_REPLY_SEQ + 1; - G2_COUNTER_OUT = NEW_REPLY_SEQ; - unsigned int ui = G2_COUNTER_OUT; - printf("SYNC: old=%u, new=%u out=%u\n", OLD_REPLY_SEQ, NEW_REPLY_SEQ, ui); - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - printf("Detected ICOM controller!\n"); - } else - printf("Skipping ICOM initialization\n"); + /* save the header */ + memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); + toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; + /* This is the active streamid */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; - while (keep_running) { - for (int i=0; i<3; i++) { - /* echotest recording timed out? */ - if (recd[i].last_time != 0) { - time(&t_now); - if ((t_now - recd[i].last_time) > echotest_rec_timeout) { - printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", - i, recd[i].streamid); - - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* START: echotest thread setup */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); - } catch (const std::exception &e) { - printf("Failed to start echotest thread. Exception: %s\n", e.what()); - // when the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - /* END: echotest thread setup */ + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; } } + } else { // g2buflen == 27 + if (bool_qso_details && g2buf.ctrl & 0x40) + printf("id=%04x END G2\n", ntohs(g2buf.streamid)); - /* voicemail recording timed out? */ - if (vm[i].last_time != 0) { - time(&t_now); - if ((t_now - vm[i].last_time) > voicemail_rec_timeout) { - printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", - i, vm[i].streamid); - - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + /* find out which repeater module to send the data to */ + int i; + for (i=0; i<3; i++) { + /* streamid match ? */ + if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining= 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* timeit */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (g2buf.ctrl & 0x40) { + /* clear the saved header */ + memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); + toRptr[i].saved_adr = 0; + + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; } } - // any stream going to local repeater timed out? - if (toRptr[i].last_time != 0) { - time(&t_now); - // The stream can be from a cross-band, or from a remote system, - // so we could use either FROM_LOCAL_RPTR_TIMEOUT or FROM_REMOTE_G2_TIMEOUT - // but FROM_REMOTE_G2_TIMEOUT makes more sense, probably is a bigger number - if ((t_now - toRptr[i].last_time) > from_remote_g2_timeout) { - printf("Inactivity to local rptr mod index %d, removing stream id %04x\n", i, toRptr[i].streamid); - - // Send end_of_audio to local repeater. - // Let the repeater re-initialize - end_of_audio.counter = is_icom ? G2_COUNTER_OUT++ :toRptr[i].G2_COUNTER++; - if (i == 0) - end_of_audio.vpkt.snd_term_id = 0x03; - else if (i == 1) - end_of_audio.vpkt.snd_term_id = 0x01; - else - end_of_audio.vpkt.snd_term_id = 0x02; - end_of_audio.vpkt.streamid = toRptr[i].streamid; - end_of_audio.vpkt.ctrl = toRptr[i].sequence | 0x40; + /* no match ? */ + if ((i == 3) && bool_regen_header) { + /* check if this a continuation of audio that timed out */ - for (int j=0; j<2; j++) - sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + if (g2buf.ctrl & 0x40) + ; /* we do not care about end-of-QSO */ + else { + /* for which repeater this stream has timed out ? */ + for (i = 0; i < 3; i++) { + /* match saved stream ? */ + if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { + /* repeater module is inactive ? */ + if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { + printf("Re-generating header for streamID=%04x\n", g2buf.streamid); + toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); + toRptr[i].saved_hdr[5] = (unsigned char)((is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++) & 0xff); - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - toRptr[i].last_time = 0; - } - } + /* re-generate/send the header */ + sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - /* any stream coming from local repeater timed out ? */ - if (band_txt[i].last_time != 0) { - time(&t_now); - if ((t_now - band_txt[i].last_time) > from_local_rptr_timeout) { - /* This local stream never went to a remote system, so trace the timeout */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) - printf("Inactivity from local rptr band %d, removing stream id %04x\n", i, band_txt[i].streamID); + /* send this audio packet to repeater */ + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0x0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - band_txt[i].last_time = 0; + /* make sure that any more audio arriving will be accepted */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - band_txt[i].dest_rptr[0] = '\0'; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; + } + break; + } + } } } + } + } +} - /* any stream from local repeater to a remote gateway timed out ? */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr != 0) { - time(&t_now); - if ((t_now - to_remote_g2[i].last_time) > from_local_rptr_timeout) { - printf("Inactivity from local rptr mod %d, removing stream id %04x\n", i, to_remote_g2[i].streamid); +void CQnetGateway::ProcessRepeater() +{ + // dtmf stuff + int dtmf_buf_count[3] = {0, 0, 0}; + char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; + int dtmf_last_frame[3] = { 0, 0, 0 }; + unsigned int dtmf_counter[3] = { 0, 0, 0 }; - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - } - } + // text stuff + bool new_group[3] = { true, true, true }; + int header_type = 0; + short to_print[3] = { 0, 0, 0 }; + bool ABC_grp[3] = { false, false, false }; + bool C_seen[3] = { false, false, false }; + unsigned char tmp_txt[3]; - /* wait 20 ms max */ - FD_ZERO(&fdset); - FD_SET(g2_sock, &fdset); - FD_SET(srv_sock, &fdset); - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 20000; /* 20 ms */ - (void)select(max_nfds + 1, &fdset, 0, 0, &tv); + char temp_radio_user[CALL_SIZE + 1]; + char temp_mod; - /* process packets coming from remote G2 */ - if (FD_ISSET(g2_sock, &fdset)) { - socklen_t fromlen = sizeof(struct sockaddr_in); - int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); + char arearp_cs[CALL_SIZE + 1]; + char zonerp_cs[CALL_SIZE + 1]; + char ip[IP_SIZE + 1]; - // save incoming port for mobile systems - if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { - printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } else { - if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { - printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } + char tempfile[FILENAME_MAX + 1]; + long num_recs = 0L; + short int rec_len = 56; + + SDSVT g2buf; + + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); + + if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { + ///////////////////////////////////////////////////////////////////// + // some ICOM handshaking... + if (is_icom && 10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr + NEW_REPLY_SEQ = ntohs(rptrbuf.counter); + if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { + G2_COUNTER_OUT = NEW_REPLY_SEQ; + OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; + } else + OLD_REPLY_SEQ = NEW_REPLY_SEQ; + } else if (is_icom && 0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { + rptrbuf.flag[0] = 0x72U; + memset(rptrbuf.flag+1, 0x0U, 3); + sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + // end of ICOM handshaking + ///////////////////////////////////////////////////////////////////// + } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { + if (is_icom) { // acknowledge packet to ICOM + SDSTR reply; + memcpy(reply.pkt_id, "DSTR", 4); + reply.counter = rptrbuf.counter; + reply.flag[0] = 0x72U; + memset(reply.flag+1, 0, 3); + sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); } + if (recvlen == 58) { - if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && - (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { - if (g2buflen == 56) { + if (bool_qso_details) + printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); - // Find out the local repeater module IP/port to send the data to - int i = g2buf.hdr.rpt1[7] - 'A'; + if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - /* valid repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] is active if a remote system is talking to it or - // toRptr[i] is receiving data from a cross-band - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0) && - ((g2buf.hdr.flag[0] == 0x00) || - (g2buf.hdr.flag[0] == 0x01) || /* allow the announcements from g2_link */ - (g2buf.hdr.flag[0] == 0x08) || - (g2buf.hdr.flag[0] == 0x20) || - (g2buf.hdr.flag[0] == 0x28) || - (g2buf.hdr.flag[0] == 0x40))) { - if (bool_qso_details) - printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", - ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, - g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x30; - rptrbuf.vpkt.icm_id = 0x20; - //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); - rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; - rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; - rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; - rptrbuf.vpkt.streamid = g2buf.streamid; - rptrbuf.vpkt.ctrl = g2buf.ctrl; - memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - if (is_icom) { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - } else { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); - } - memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); - memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); - memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); - memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + if (i>=0 && i<3) { + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (got a header)\n", i); + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0; + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; - /* save the header */ - memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); - toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; + /* Initialize the LAST HEARD data for the band */ - /* This is the active streamid */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; + band_txt[i].streamID = rptrbuf.vpkt.streamid; - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } + memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); + band_txt[i].lh_mycall[8] = '\0'; + + memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); + band_txt[i].lh_sfx[4] = '\0'; + + memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); + band_txt[i].lh_yrcall[8] = '\0'; + + memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); + band_txt[i].lh_rpt1[8] = '\0'; + + memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].lh_rpt2[8] = '\0'; + + time(&band_txt[i].last_time); + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].txt_stats_sent = false; + + band_txt[i].dest_rptr[0] = '\0'; + + /* try to process GPS mode: GPRMC and ID */ + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + // band_txt[i].gps_last_time = 0; DO NOT reset it + + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + /* select the band for aprs processing, and lock on the stream ID */ + if (bool_send_aprs) + aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); } - } else { // g2buflen == 27 - if (bool_qso_details && g2buf.ctrl & 0x40) - printf("id=%04x END G2\n", ntohs(g2buf.streamid)); - - /* find out which repeater module to send the data to */ - int i; - for (i=0; i<3; i++) { - /* streamid match ? */ - if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining= 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); + } - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + /* Is MYCALL valid ? */ + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); + temp_radio_user[8] = '\0'; - /* End of stream ? */ - if (g2buf.ctrl & 0x40) { - /* clear the saved header */ - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; + int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; + if (mycall_valid == REG_NOERROR) + ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); + else { + if (mycall_valid == REG_NOMATCH) + printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); + else + printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); + } + + /* send data qnlink */ + if (mycall_valid == REG_NOERROR) + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + if ( mycall_valid==REG_NOERROR && + memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector + memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && + memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && + rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something + memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ + { + if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) + { + if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + /* YRCALL=/repeater + mod */ + /* YRCALL=/KJ4NHFB */ + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); + temp_radio_user[6] = ' '; + temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; + if (temp_radio_user[7] == ' ') + temp_radio_user[7] = 'A'; + temp_radio_user[CALL_SIZE] = '\0'; + + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); + if (result) { /* it is a repeater */ + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that saved port instead of the default port + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set yrcall, can NOT let it be slash and repeater + module */ + memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + + /* set PFCS */ + calcPFCS(g2buf.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + // send to remote gateway + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); + + time(&(to_remote_g2[i].last_time)); + } + } } - break; } } + else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - /* no match ? */ - if ((i == 3) && bool_regen_header) { - /* check if this a continuation of audio that timed out */ - - if (g2buf.ctrl & 0x40) - ; /* we do not care about end-of-QSO */ - else { - /* for which repeater this stream has timed out ? */ - for (i = 0; i < 3; i++) { - /* match saved stream ? */ - if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { - /* repeater module is inactive ? */ - if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { - printf("Re-generating header for streamID=%04x\n", g2buf.streamid); - - toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); - toRptr[i].saved_hdr[5] = (unsigned char)((is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++) & 0xff); - /* re-generate/send the header */ - sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); + temp_radio_user[8] = '\0'; + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); + if (result) { + /* destination is a remote system */ + if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set PFCS */ + memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + calcPFCS(g2buf.title, 56); + + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + /* send to remote gateway */ + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", + inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.streamid, g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); + + time(&(to_remote_g2[i].last_time)); + } + } + } + else + { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* the user we are trying to contact is on our gateway */ + /* make sure they are on a different module */ + if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { + /* + The remote repeater has been set, lets fill in the dest_rptr + so that later we can send that to the LIVE web site + */ + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[7] = temp_mod; + band_txt[i].dest_rptr[8] = '\0'; + + i = temp_mod - 'A'; + + /* valid destination repeater module? */ + if (i>=0 && i<3) { + /* + toRptr[i] : receiving from a remote system or cross-band + band_txt[i] : local RF is talking. + */ + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - /* send this audio packet to repeater */ - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + rptrbuf.vpkt.hdr.r2[7] = temp_mod; + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - /* make sure that any more audio arriving will be accepted */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + /* bump the G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER++; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } } - break; + else + printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); } } } } } - } - FD_CLR (g2_sock,&fdset); - } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - /* process data coming from local repeater modules */ - if (FD_ISSET(srv_sock, &fdset)) { - socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); - - if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { - ///////////////////////////////////////////////////////////////////// - // some ICOM handshaking... - if (is_icom && 10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr - NEW_REPLY_SEQ = ntohs(rptrbuf.counter); - if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { - G2_COUNTER_OUT = NEW_REPLY_SEQ; - OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; - } else - OLD_REPLY_SEQ = NEW_REPLY_SEQ; - } else if (is_icom && 0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { - rptrbuf.flag[0] = 0x72U; - memset(rptrbuf.flag+1, 0x0U, 3); - sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - // end of ICOM handshaking - ///////////////////////////////////////////////////////////////////// - } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && - rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && - (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { - if (is_icom) { // acknowledge packet to ICOM - SDSTR reply; - memcpy(reply.pkt_id, "DSTR", 4); - reply.counter = rptrbuf.counter; - reply.flag[0] = 0x72U; - memset(reply.flag+1, 0, 3); - sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + unlink(vm[i].file); + printf("removed voicemail file: %s\n", vm[i].file); + vm[i].file[0] = '\0'; + } else + printf("No voicemail to clear or still recording\n"); } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (recvlen == 58) { + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); + } catch (const std::exception &e) { + printf("Failed to start voicemail playback. Exception: %s\n", e.what()); + } + } else + printf("No voicemail to recall or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (bool_qso_details) - printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", - ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, - rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); + if (i>=0 && i<3) { + if (vm[i].fd >= 0) + printf("Already recording for voicemail on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); + + vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (vm[i].fd < 0) + printf("Failed to create file %s for voicemail\n", tempfile); + else { + strcpy(vm[i].file, tempfile); + printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); + + time(&vm[i].last_time); + vm[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + rec_len = 56; + (void)write(vm[i].fd, "DVTOOL", 6); + (void)write(vm[i].fd, &num_recs, 4); + (void)write(vm[i].fd, &rec_len, 2); + (void)write(vm[i].fd, &recbuf, rec_len); + } + } + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (recd[i].fd >= 0) + printf("Already recording for echotest on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); + + recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (recd[i].fd < 0) + printf("Failed to create file %s for echotest\n", tempfile); + else { + strcpy(recd[i].file, tempfile); + printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); + + time(&recd[i].last_time); + recd[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + rec_len = 56; + (void)write(recd[i].fd, "DVTOOL", 6); + (void)write(recd[i].fd, &num_recs, 4); + (void)write(recd[i].fd, &rec_len, 2); + (void)write(recd[i].fd, &recbuf, rec_len); + } + } + } + /* check for cross-banding */ + } + else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C + (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with + rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater - (rptrbuf.vpkt.hdr.flag[0]==0x00 || // normal - rptrbuf.vpkt.hdr.flag[0]==0x08 || // EMR - rptrbuf.vpkt.hdr.flag[0]==0x20 || // BREAK - rptrbuf.vpkt.hdr.flag[0]==0x28)) { // EMR + BREAK (0x1, announcements are not allowed) + if (i>=0 && i<3) { + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[8] = '\0'; + } - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + i = rptrbuf.vpkt.hdr.r2[7] - 'A'; - if (i>=0 && i<3) { - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (got a header)\n", i); - dtmf_last_frame[i] = 0; - dtmf_counter[i] = 0; - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; + // valid destination repeater module? + if (i>=0 && i<3) { + // toRptr[i] : receiving from a remote system or cross-band + // band_txt[i] : local RF is talking. + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); - /* Initialize the LAST HEARD data for the band */ + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); - band_txt[i].streamID = rptrbuf.vpkt.streamid; + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; - memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); - band_txt[i].lh_mycall[8] = '\0'; + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); - band_txt[i].lh_sfx[4] = '\0'; + /* bump the G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER ++; - memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); - band_txt[i].lh_yrcall[8] = '\0'; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } + } + else + { // recvlen is 29 or 32 + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + time(&band_txt[i].last_time); + + if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data + if (dtmf_buf_count[i] > 0) { + dtmf_file = dtmf_dir; + dtmf_file.push_back('/'); + dtmf_file.push_back('A'+i); + dtmf_file += "_mod_DTMF_NOTIFY"; + if (bool_dtmf_debug) + printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); + FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); + if (dtmf_fp) { + fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); + fclose(dtmf_fp); + } else + printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); - band_txt[i].lh_rpt1[8] = '\0'; - memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].lh_rpt2[8] = '\0'; + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + dtmf_counter[i] = 0; + dtmf_last_frame[i] = 0; + } - time(&band_txt[i].last_time); + ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].txt_stats_sent = false; + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; - band_txt[i].dest_rptr[0] = '\0'; + band_txt[i].last_time = 0; - /* try to process GPS mode: GPRMC and ID */ - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - // band_txt[i].gps_last_time = 0; DO NOT reset it + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; + band_txt[i].dest_rptr[0] = '\0'; - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; - /* select the band for aprs processing, and lock on the stream ID */ - if (bool_send_aprs) - aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); - } } + else + { // not the end of the voice stream + int ber_data[3]; + int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); + if (ber_data[0] == 0xf85) + band_txt[i].num_dv_silent_frames++; + band_txt[i].num_bit_errors += ber_errs; + band_txt[i].num_dv_frames++; + + if ((ber_data[0] & 0x0ffc) == 0xfc0) { + dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); + if (dtmf_counter[i] > 0) { + if (dtmf_last_frame[i] != dtmf_digit) + dtmf_counter[i] = 0; + } + dtmf_last_frame[i] = dtmf_digit; + dtmf_counter[i]++; + + if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { + if (dtmf_buf_count[i] < MAX_DTMF_BUF) { + const char *dtmf_chars = "147*2580369#ABCD"; + dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; + dtmf_buf_count[i]++; + } + } + const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; + memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); + } else + dtmf_counter[i] = 0; + } + break; + } + } - /* Is MYCALL valid ? */ - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); - temp_radio_user[8] = '\0'; + if (recvlen == 29) + memcpy(tmp_txt, rptrbuf.vpkt.vasd.text, 3); + else + memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); + // extract 20-byte RADIO ID + if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { - if (mycall_valid == REG_NOERROR) - ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); - else { - if (mycall_valid == REG_NOMATCH) - printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); - else - printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); - } + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + if (new_group[i]) { + tmp_txt[0] = tmp_txt[0] ^ 0x70; + header_type = tmp_txt[0] & 0xf0; - /* send data qnlink */ - if (mycall_valid == REG_NOERROR) - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - if (mycall_valid==REG_NOERROR && - memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector - memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && - memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && - rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something - memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6)) { // urcall is NOT CQCQCQ - if (rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - (rptrbuf.vpkt.hdr.flag[0]== 0x00 || // normal - rptrbuf.vpkt.hdr.flag[0]== 0x08 || // EMR - rptrbuf.vpkt.hdr.flag[0]== 0x20 || // BK - rptrbuf.vpkt.hdr.flag[0]== 0x28)) {// EMR + BK - - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - /* YRCALL=/repeater + mod */ - /* YRCALL=/KJ4NHFB */ - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); - temp_radio_user[6] = ' '; - temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; - if (temp_radio_user[7] == ' ') - temp_radio_user[7] = 'A'; - temp_radio_user[CALL_SIZE] = '\0'; - - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); - if (result) { /* it is a repeater */ - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that saved port instead of the default port - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set yrcall, can NOT let it be slash and repeater + module */ - memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - // send to remote gateway - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", - ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); - - time(&(to_remote_g2[i].last_time)); - } - } - } + if ((header_type == 0x50) || /* header */ (header_type == 0xc0)) { /* squelch */ + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; } - } else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - - (rptrbuf.vpkt.hdr.flag[0]==0x00 || // normal - rptrbuf.vpkt.hdr.flag[0]==0x08 || // EMR - rptrbuf.vpkt.hdr.flag[0]==0x20 || // BK - rptrbuf.vpkt.hdr.flag[0]==0x28)) { // EMR + BK - - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); - temp_radio_user[8] = '\0'; - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); - if (result) { - /* destination is a remote system */ - if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set PFCS */ - memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - calcPFCS(g2buf.title, 56); - - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - /* send to remote gateway */ - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); - - time(&(to_remote_g2[i].last_time)); - } + else if (header_type == 0x30) { /* GPS or GPS id or APRS */ + new_group[i] = false; + to_print[i] = tmp_txt[0] & 0x0f; + ABC_grp[i] = false; + if (to_print[i] > 5) + to_print[i] = 5; + else if (to_print[i] < 1) + to_print[i] = 1; + + if ((to_print[i] > 1) && (to_print[i] <= 5)) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - } else { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - /* the user we are trying to contact is on our gateway */ - /* make sure they are on a different module */ - if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { - /* - The remote repeater has been set, lets fill in the dest_rptr - so that later we can send that to the LIVE web site - */ - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[7] = temp_mod; - band_txt[i].dest_rptr[8] = '\0'; - - i = temp_mod - 'A'; - - /* valid destination repeater module? */ - if (i>=0 && i<3) { - /* - toRptr[i] : receiving from a remote system or cross-band - band_txt[i] : local RF is talking. - */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - - rptrbuf.vpkt.hdr.r2[7] = temp_mod; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } else - printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); + /* fresh GPS string, re-initialize */ + if ((to_print[i] == 5) && ((tmp_txt[1] ^ 0x4f) == '$')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; } - } - } - } - } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - unlink(vm[i].file); - printf("removed voicemail file: %s\n", vm[i].file); - vm[i].file[0] = '\0'; - } else - printf("No voicemail to clear or still recording\n"); - } - } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + /* do not copy CR, NL */ + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].temp_line_cnt++; + } - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); - } catch (const std::exception &e) { - printf("Failed to start voicemail playback. Exception: %s\n", e.what()); + if (((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] -= 2; } - } else - printf("No voicemail to recall or still recording\n"); - } - } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + else + { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - if (i>=0 && i<3) { - if (vm[i].fd >= 0) - printf("Already recording for voicemail on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); - - vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (vm[i].fd < 0) - printf("Failed to create file %s for voicemail\n", tempfile); - else { - strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); - - time(&vm[i].last_time); - vm[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(vm[i].fd, "DVTOOL", 6); - (void)write(vm[i].fd, &num_recs, 4); - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); + /* do not copy CR, NL */ + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } + + if ((tmp_txt[1] ^ 0x4f) == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((tmp_txt[1] ^ 0x4f) == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] --; } } - } - } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + else if (header_type == 0x40) { /* ABC text */ + new_group[i] = false; + to_print[i] = 3; + ABC_grp[i] = true; + C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03)?true:false; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].txt_cnt++; + + /* + We should NOT see any more text, + if we already processed text, + so blank out the codes. + */ + if (band_txt[i].txt_stats_sent) { + if (recvlen == 29) { + rptrbuf.vpkt.vasd.text[0] = 0x70; + rptrbuf.vpkt.vasd.text[1] = 0x4f; + rptrbuf.vpkt.vasd.text[2] = 0x93; + } else { + rptrbuf.vpkt.vasd1.text[0] = 0x70; + rptrbuf.vpkt.vasd1.text[1] = 0x4f; + rptrbuf.vpkt.vasd1.text[2] = 0x93; + } + } - if (i>=0 && i<3) { - if (recd[i].fd >= 0) - printf("Already recording for echotest on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - - recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (recd[i].fd < 0) - printf("Failed to create file %s for echotest\n", tempfile); - else { - strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); - - time(&recd[i].last_time); - recd[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(recd[i].fd, "DVTOOL", 6); - (void)write(recd[i].fd, &num_recs, 4); - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); + if (band_txt[i].txt_cnt >= 20) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + band_txt[i].txt_cnt = 0; } + } else { + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; } } - /* check for cross-banding */ - } else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C - (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with - rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[8] = '\0'; - } - - i = rptrbuf.vpkt.hdr.r2[7] - 'A'; + else + { + if (to_print[i] == 3) { + if (ABC_grp[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].txt_cnt ++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].txt_cnt ++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].txt_cnt ++; + + // We should NOT see any more text, + // if we already processed text, + // so blank out the codes. + + if (band_txt[i].txt_stats_sent) { + if (recvlen == 29) { + rptrbuf.vpkt.vasd.text[0] = 0x70; + rptrbuf.vpkt.vasd.text[1] = 0x4f; + rptrbuf.vpkt.vasd.text[2] = 0x93; + } else { + rptrbuf.vpkt.vasd1.text[0] = 0x70; + rptrbuf.vpkt.vasd1.text[1] = 0x4f; + rptrbuf.vpkt.vasd1.text[2] = 0x93; + } + } - // valid destination repeater module? - if (i>=0 && i<3) { - // toRptr[i] : receiving from a remote system or cross-band - // band_txt[i] : local RF is talking. - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); + if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + if (!band_txt[i].txt_stats_sent) { + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + // if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + // band_txt[i].dest_rptr[0] = '\0'; + } - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx,band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].txt_stats_sent = true; + } + band_txt[i].txt_cnt = 0; + } + if (C_seen[i]) + C_seen[i] = false; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + /* do not copy CR, NL */ + if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].temp_line_cnt++; + } - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; + if ( + ((tmp_txt[0] ^ 0x70) == '\r') || + ((tmp_txt[1] ^ 0x4f) == '\r') || + ((tmp_txt[2] ^ 0x93) == '\r') + ) { + if (0 == memcmp(band_txt[i].temp_line, "$GPRMC", 6)) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + } else if (to_print[i] == 2) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* do not copy CR, NL */ + if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].temp_line_cnt++; + } + if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].temp_line_cnt++; + } - /* bump the G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER ++; + if (((tmp_txt[0] ^ 0x70) == '\r') || ((tmp_txt[1] ^ 0x4f) == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } else if (to_print[i] == 1) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } - } else { // recvlen is 29 or 32 - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - time(&band_txt[i].last_time); - - if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data - if (dtmf_buf_count[i] > 0) { - dtmf_file = dtmf_dir; - dtmf_file.push_back('/'); - dtmf_file.push_back('A'+i); - dtmf_file += "_mod_DTMF_NOTIFY"; - if (bool_dtmf_debug) - printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); - FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); - if (dtmf_fp) { - fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); - fclose(dtmf_fp); - } else - printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - - - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - dtmf_counter[i] = 0; - dtmf_last_frame[i] = 0; + /* do not copy CR, NL */ + if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + band_txt[i].temp_line_cnt++; } - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, - band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - - band_txt[i].dest_rptr[0] = '\0'; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - } else { // not the end of the voice stream - int ber_data[3]; - int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); - if (ber_data[0] == 0xf85) - band_txt[i].num_dv_silent_frames++; - band_txt[i].num_bit_errors += ber_errs; - band_txt[i].num_dv_frames++; - - if ((ber_data[0] & 0x0ffc) == 0xfc0) { - dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); - if (dtmf_counter[i] > 0) { - if (dtmf_last_frame[i] != dtmf_digit) - dtmf_counter[i] = 0; + if ((tmp_txt[0] ^ 0x70) == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); } - dtmf_last_frame[i] = dtmf_digit; - dtmf_counter[i]++; - - if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { - if (dtmf_buf_count[i] < MAX_DTMF_BUF) { - const char *dtmf_chars = "147*2580369#ABCD"; - dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; - dtmf_buf_count[i]++; - } - } - const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); - } else - dtmf_counter[i] = 0; + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((tmp_txt[0] ^ 0x70) == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } } - break; + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; } + break; } + } + } + /* send data to qnlink */ + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + /* aprs processing */ + if (bool_send_aprs) + // streamID seq audio+text size + aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); + + for (int i=0; i<3; i++) { + /* find out if data must go to the remote G2 */ + if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x20; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; + memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); if (recvlen == 29) - memcpy(tmp_txt, rptrbuf.vpkt.vasd.text, 3); + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); else - memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - - // extract 20-byte RADIO ID - if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { - - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - if (new_group[i]) { - tmp_txt[0] = tmp_txt[0] ^ 0x70; - header_type = tmp_txt[0] & 0xf0; - - if ((header_type == 0x50) || /* header */ (header_type == 0xc0)) { /* squelch */ - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } else if (header_type == 0x30) { /* GPS or GPS id or APRS */ - new_group[i] = false; - to_print[i] = tmp_txt[0] & 0x0f; - ABC_grp[i] = false; - if (to_print[i] > 5) - to_print[i] = 5; - else if (to_print[i] < 1) - to_print[i] = 1; - - if ((to_print[i] > 1) && (to_print[i] <= 5)) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && ((tmp_txt[1] ^ 0x4f) == '$')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].temp_line_cnt++; - } + time(&(to_remote_g2[i].last_time)); - if (((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] -= 2; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + /* Is this the end-of-stream */ + if (rptrbuf.vpkt.ctrl & 0x40) { + memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + break; + } + else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest + time(&recd[i].last_time); + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x20; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + rec_len = 27; + (void)write(recd[i].fd, &rec_len, 2); + (void)write(recd[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* we are in echotest mode, so play it back */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); + } catch (const std::exception &e) { + printf("failed to start PlayFileThread. Exception: %s\n", e.what()); + // When the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } + } + break; + } + else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail + time(&vm[i].last_time); + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x20; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + rec_len = 27; + (void)write(vm[i].fd, &rec_len, 2); + (void)write(vm[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + break; + } + else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } + /* timeit */ + time(&toRptr[i].last_time); - if ((tmp_txt[1] ^ 0x4f) == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[1] ^ 0x4f) == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] --; - } - } else if (header_type == 0x40) { /* ABC text */ - new_group[i] = false; - to_print[i] = 3; - ABC_grp[i] = true; - C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03)?true:false; + /* bump G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER ++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt++; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt++; + /* End of stream ? */ + if (rptrbuf.vpkt.ctrl & 0x40) { + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; + } + } - /* - We should NOT see any more text, - if we already processed text, - so blank out the codes. - */ - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; - } - } + if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) + printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); + } + } + } +} - if (band_txt[i].txt_cnt >= 20) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - band_txt[i].txt_cnt = 0; - } - } else { - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - } else { - if (to_print[i] == 3) { - if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].txt_cnt ++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt ++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt ++; - - // We should NOT see any more text, - // if we already processed text, - // so blank out the codes. - - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; - } - } +/* run the main loop for QnetGateway */ +void CQnetGateway::Process() +{ + dstar_dv_init(); - if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - if (!band_txt[i].txt_stats_sent) { - /*** if YRCALL is CQCQCQ, set dest_rptr ***/ - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - // if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - // band_txt[i].dest_rptr[0] = '\0'; - } - - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx,band_txt[i].lh_yrcall, - band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], - band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); - band_txt[i].txt_stats_sent = true; - } - band_txt[i].txt_cnt = 0; - } - if (C_seen[i]) - C_seen[i] = false; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + int max_nfds = 0; + if (g2_sock > max_nfds) + max_nfds = g2_sock; + if (srv_sock > max_nfds) + max_nfds = srv_sock; + printf("g2=%d, srv=%d, MAX+1=%d\n", g2_sock, srv_sock, max_nfds + 1); - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].temp_line_cnt++; - } + std::future aprs_future, irc_data_future; + if (bool_send_aprs) { // start the beacon thread + try { + aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); + } catch (const std::exception &e) { + printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); + } + if (aprs_future.valid()) + printf("APRS beacon thread started\n"); + } - if ( - ((tmp_txt[0] ^ 0x70) == '\r') || - ((tmp_txt[1] ^ 0x4f) == '\r') || - ((tmp_txt[2] ^ 0x93) == '\r') - ) { - if (0 == memcmp(band_txt[i].temp_line, "$GPRMC", 6)) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || - ((tmp_txt[1] ^ 0x4f) == '\n') || - ((tmp_txt[2] ^ 0x93) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - } else if (to_print[i] == 2) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + try { // start the IRC read thread + irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); + } catch (const std::exception &e) { + printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); + keep_running = false; + } + if (keep_running) + printf("get_irc_data thread started\n"); - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].temp_line_cnt++; - } + ii->kickWatchdog(IRCDDB_VERSION); - if (((tmp_txt[0] ^ 0x70) == '\r') || ((tmp_txt[1] ^ 0x4f) == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } else if (to_print[i] == 1) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } + if (is_icom) { + // send INIT to Icom Stack + unsigned char buf[500]; + memset(buf, 0, 10); + memcpy(buf, "INIT", 4); + buf[6] = 0x73U; + // we can use the module a band_addr for INIT + sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + printf("Waiting for ICOM controller...\n"); - /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].temp_line_cnt++; - } + // get the acknowledgement from the ICOM Stack + while (keep_running) { + socklen_t fromlength = sizeof(struct sockaddr_in); + int recvlen = recvfrom(srv_sock, buf, 500, 0, (struct sockaddr *)&fromRptr, &fromlength); + if (10==recvlen && 0==memcmp(buf, "INIT", 4) && 0x72U==buf[6] && 0x0U==buf[7]) { + OLD_REPLY_SEQ = 256U * buf[4] + buf[5]; + NEW_REPLY_SEQ = OLD_REPLY_SEQ + 1; + G2_COUNTER_OUT = NEW_REPLY_SEQ; + unsigned int ui = G2_COUNTER_OUT; + printf("SYNC: old=%u, new=%u out=%u\n", OLD_REPLY_SEQ, NEW_REPLY_SEQ, ui); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + printf("Detected ICOM controller!\n"); + } else + printf("Skipping ICOM initialization\n"); - if ((tmp_txt[0] ^ 0x70) == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[0] ^ 0x70) == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - } - break; - } - } - } - /* send data to qnlink */ - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + while (keep_running) { + ProcessTimeouts(); - /* aprs processing */ - if (bool_send_aprs) - // streamID seq audio+text size - aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); - - for (int i=0; i<3; i++) { - /* find out if data must go to the remote G2 */ - if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x20; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; - memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); - if (recvlen == 29) - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - time(&(to_remote_g2[i].last_time)); - - /* Is this the end-of-stream */ - if (rptrbuf.vpkt.ctrl & 0x40) { - memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - break; - } else - /* Is the data to be recorded for echotest */ - if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { - time(&recd[i].last_time); - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - rec_len = 27; - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* we are in echotest mode, so play it back */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); - } catch (const std::exception &e) { - printf("failed to start PlayFileThread. Exception: %s\n", e.what()); - // When the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - } - break; - } else - /* Is the data to be recorded for voicemail */ - if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { - time(&vm[i].last_time); - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - rec_len = 27; - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } - break; - } else - /* or maybe this is cross-banding data */ - if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); - - /* bump G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER ++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - /* End of stream ? */ - if (rptrbuf.vpkt.ctrl & 0x40) { - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; - } - } + // wait 20 ms max + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(g2_sock, &fdset); + FD_SET(srv_sock, &fdset); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 20000; // 20 ms + (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) - printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); - } - } - } + // process packets coming from remote G2 + if (FD_ISSET(g2_sock, &fdset)) { + ProcessRouting(); + FD_CLR (g2_sock,&fdset); + } + + // process packets coming from local repeater modules + if (FD_ISSET(srv_sock, &fdset)) { + ProcessRepeater(); FD_CLR (srv_sock,&fdset); } } @@ -2461,7 +2465,7 @@ void CQnetGateway::qrgs_and_maps() return; } -int CQnetGateway::init(char *cfgfile) +int CQnetGateway::Init(char *cfgfile) { short int i; struct sigaction act; @@ -2932,8 +2936,8 @@ int main(int argc, char **argv) return 1; } CQnetGateway QnetGateway; - if (QnetGateway.init(argv[1])) + if (QnetGateway.Init(argv[1])) return 1; - QnetGateway.process(); + QnetGateway.Process(); printf("Leaving processing loop...\n"); } diff --git a/QnetGateway.h b/QnetGateway.h index 197c21a..833af6b 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -85,8 +85,8 @@ class CQnetGateway { public: CQnetGateway(); ~CQnetGateway(); - void process(); - int init(char *cfgfile); + void Process(); + int Init(char *cfgfile); private: bool is_icom, is_not_icom; @@ -157,6 +157,10 @@ private: void PlayFileThread(char *file); void compute_aprs_hash(); void APRSBeaconThread(); + void ProcessTimeouts(); + void ProcessRouting(); + void ProcessRepeater(); + bool Flag_is_ok(unsigned char flag); // read configuration file bool read_config(char *); From ea7c61c82e8af2f76394cbfa44b7787637c81142 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 26 Sep 2018 13:08:00 -0700 Subject: [PATCH 097/553] The Routing to msg streamid fixed --- QnetGateway.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 26b7eda..9acbb71 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1249,9 +1249,7 @@ void CQnetGateway::ProcessRepeater() for (int j=0; j<5; j++) sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", - inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.streamid, g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); + printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); time(&(to_remote_g2[i].last_time)); } From dfb222ef44ea523bab3e213ab5282af0d70b9f51 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 26 Sep 2018 19:27:25 -0700 Subject: [PATCH 098/553] SDSTR reversed r1, r2 --- QnetTypeDefs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index 41aac94..cde2948 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -39,8 +39,8 @@ typedef struct dstr_tag { union { struct { unsigned char flag[3]; // 17 - unsigned char r2[8]; // 20 - unsigned char r1[8]; // 28 + unsigned char r1[8]; // 20 + unsigned char r2[8]; // 28 unsigned char ur[8]; // 36 unsigned char my[8]; // 44 unsigned char nm[4]; // 52 From b1618607ee28d55c665a9a194647289d69bf7caa Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 26 Sep 2018 20:20:55 -0700 Subject: [PATCH 099/553] inbound g2 r1, r2 reverse --- QnetGateway.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 9acbb71..2eb350e 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -832,13 +832,13 @@ void CQnetGateway::ProcessRouting() rptrbuf.vpkt.streamid = g2buf.streamid; rptrbuf.vpkt.ctrl = g2buf.ctrl; memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - if (is_icom) { + //if (is_icom) { memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - } else { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); - } + //} else { + // memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + // memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + //} memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); From f52bc87c4d570dd3d4f29a1acb94f45ec04f12f6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 27 Sep 2018 11:28:27 -0700 Subject: [PATCH 100/553] DVAP incoming G2 header r1, r2 --- QnetDVAP.cpp | 13 ++++++++++--- QnetRelay.cpp | 5 ++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 57c7266..e202bb8 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -401,6 +401,13 @@ static void readFrom20000() continue; } + if ('G' == net_buf.vpkt.hdr.r1[7]) { + unsigned char tmp[8]; + memcpy(tmp, net_buf.vpkt.hdr.r1, 8); + memcpy(net_buf.vpkt.hdr.r1, net_buf.vpkt.hdr.r2, 8); + memcpy(net_buf.vpkt.hdr.r2, tmp, 8); + } + /* check the module and gateway */ if (net_buf.vpkt.hdr.r1[7] != RPTR_MOD) { FD_CLR(insock, &readfd); @@ -768,11 +775,11 @@ static void RptrAckThread(SDVAP_ACK_ARG *parg) dr.frame.seq = 0; dr.frame.hdr.flag[0] = 0x01; dr.frame.hdr.flag[1] = dr.frame.hdr.flag[2] = 0x00; - memcpy(dr.frame.hdr.rpt1, RPTR_and_MOD, 8); - memcpy(dr.frame.hdr.rpt2, RPTR_and_G, 8); + memcpy(dr.frame.hdr.rpt2, RPTR_and_MOD, 8); + memcpy(dr.frame.hdr.rpt1, RPTR_and_G, 8); memcpy(dr.frame.hdr.urcall, mycall, 8); memcpy(dr.frame.hdr.mycall, RPTR_and_MOD, 8); - memcpy(dr.frame.hdr.sfx, (unsigned char *)" ", 4); + memcpy(dr.frame.hdr.sfx, (unsigned char *)"DVAP", 4); calcPFCS(dr.frame.hdr.flag, dr.frame.hdr.pfcs); dongle.SendRegister(dr); std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_BETWEEN)); diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 17705cc..16de12e 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -327,15 +327,14 @@ bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) return true; } if (log_qso) - printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), - dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); } else if (21 == len) { // ambe dstr.remaining = 0x16; dstr.vpkt.ctrl = dsrp.header.seq; memcpy(dstr.vpkt.vasd.voice, dsrp.voice.ambe, 12); int ret = SendTo(msock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); if (log_qso && dstr.vpkt.ctrl&0x40) - printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); + printf("Sent DSTR end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); if (ret != 29) { printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); From f15de760ba3588c9866cc299eb6ceaf795f27ab6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 27 Sep 2018 15:24:33 -0700 Subject: [PATCH 101/553] some version bumps --- versions.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/versions.h b/versions.h index 9d787f6..306f926 100644 --- a/versions.h +++ b/versions.h @@ -1,8 +1,8 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.0.1" +#define IRCDDB_VERSION "QnetGateway-7.1.0" #define LINK_VERSION "QnetLink-6.0.2" -#define DVAP_VERSION "QnetDVAP-5.1.1" -#define RELAY_VERSION "QnetRelay-0.2.1" +#define DVAP_VERSION "QnetDVAP-5.1.2" +#define RELAY_VERSION "QnetRelay-0.2.2" #define ITAP_VERSION "QnetITAP-0.2.0" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" From ffa08c787a1084be84d1795a2d4cdd5cc987e853 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Sep 2018 09:29:33 -0700 Subject: [PATCH 102/553] revert slow data processing --- QnetGateway.cpp | 65 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 2eb350e..cf828d6 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1573,16 +1573,16 @@ void CQnetGateway::ProcessRepeater() else memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - // extract 20-byte RADIO ID + /* extract 20-byte RADIO ID */ if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { - for (int i=0; i<3; i++) { if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { if (new_group[i]) { tmp_txt[0] = tmp_txt[0] ^ 0x70; header_type = tmp_txt[0] & 0xf0; - if ((header_type == 0x50) || /* header */ (header_type == 0xc0)) { /* squelch */ + // header squelch + if ((header_type == 0x50) || (header_type == 0xc0)) { new_group[i] = false; to_print[i] = 0; ABC_grp[i] = false; @@ -1637,9 +1637,7 @@ void CQnetGateway::ProcessRepeater() band_txt[i].temp_line_cnt = 0; } to_print[i] -= 2; - } - else - { + } else { /* something went wrong? all bets are off */ if (band_txt[i].temp_line_cnt > 200) { printf("Reached the limit in the OLD gps mode\n"); @@ -1703,16 +1701,28 @@ void CQnetGateway::ProcessRepeater() if (band_txt[i].txt_cnt >= 20) { band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + /*** + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].dest_rptr, + band_txt[i].txt); + ***/ + // printf("TEXT1=[%s]\n", band_txt[i].txt); band_txt[i].txt_cnt = 0; } - } else { + } + else { new_group[i] = false; to_print[i] = 0; ABC_grp[i] = false; } - } - else - { + } else { if (to_print[i] == 3) { if (ABC_grp[i]) { band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; @@ -1724,10 +1734,9 @@ void CQnetGateway::ProcessRepeater() band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; band_txt[i].txt_cnt ++; - // We should NOT see any more text, - // if we already processed text, - // so blank out the codes. - + /* We should NOT see any more text, + if we already processed text, + so blank out the codes. */ if (band_txt[i].txt_stats_sent) { if (recvlen == 29) { rptrbuf.vpkt.vasd.text[0] = 0x70; @@ -1746,11 +1755,21 @@ void CQnetGateway::ProcessRepeater() /*** if YRCALL is CQCQCQ, set dest_rptr ***/ if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { set_dest_rptr(i, band_txt[i].dest_rptr); - // if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - // band_txt[i].dest_rptr[0] = '\0'; + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; } - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx,band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].dest_rptr, + band_txt[i].txt); + // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); band_txt[i].txt_stats_sent = true; } band_txt[i].txt_cnt = 0; @@ -1780,11 +1799,11 @@ void CQnetGateway::ProcessRepeater() } if ( - ((tmp_txt[0] ^ 0x70) == '\r') || - ((tmp_txt[1] ^ 0x4f) == '\r') || - ((tmp_txt[2] ^ 0x93) == '\r') + ((tmp_txt[0] ^ 0x70) == '\r') || + ((tmp_txt[1] ^ 0x4f) == '\r') || + ((tmp_txt[2] ^ 0x93) == '\r') ) { - if (0 == memcmp(band_txt[i].temp_line, "$GPRMC", 6)) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; } else if (band_txt[i].temp_line[0] != '$') { @@ -1795,7 +1814,9 @@ void CQnetGateway::ProcessRepeater() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { + } else if (((tmp_txt[0] ^ 0x70) == '\n') || + ((tmp_txt[1] ^ 0x4f) == '\n') || + ((tmp_txt[2] ^ 0x93) == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } From 1ec58d1287e264d2481701b6b31da0c36f2b8ee4 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Sep 2018 11:47:27 -0700 Subject: [PATCH 103/553] simplify slowdata values --- QnetGateway.cpp | 95 +++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index cf828d6..b304c9d 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1560,7 +1560,10 @@ void CQnetGateway::ProcessRepeater() } } const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); + if (recvlen == 29) + memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); + else + memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); } else dtmf_counter[i] = 0; } @@ -1575,10 +1578,15 @@ void CQnetGateway::ProcessRepeater() /* extract 20-byte RADIO ID */ if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { + + // first, unscramble + tmp_txt[0] ^= 0x70u; + tmp_txt[1] ^= 0x4fu; + tmp_txt[2] ^= 0x93u; + for (int i=0; i<3; i++) { if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { if (new_group[i]) { - tmp_txt[0] = tmp_txt[0] ^ 0x70; header_type = tmp_txt[0] & 0xf0; // header squelch @@ -1605,22 +1613,22 @@ void CQnetGateway::ProcessRepeater() } /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && ((tmp_txt[1] ^ 0x4f) == '$')) { + if ((to_print[i] == 5) && (tmp_txt[1] == '$')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; + if ((tmp_txt[2] != '\r') && (tmp_txt[2] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2]; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[1] ^ 0x4f) == '\r') || ((tmp_txt[2] ^ 0x93) == '\r')) { + if ((tmp_txt[1] == '\r') || (tmp_txt[2] == '\r')) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1632,7 +1640,7 @@ void CQnetGateway::ProcessRepeater() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[1] ^ 0x4f) == '\n') || ((tmp_txt[2] ^ 0x93) == '\n')) { + } else if ((tmp_txt[1] == '\n') || (tmp_txt[2] == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -1646,12 +1654,12 @@ void CQnetGateway::ProcessRepeater() } /* do not copy CR, NL */ - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; band_txt[i].temp_line_cnt++; } - if ((tmp_txt[1] ^ 0x4f) == '\r') { + if (tmp_txt[1] == '\r') { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1663,7 +1671,7 @@ void CQnetGateway::ProcessRepeater() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[1] ^ 0x4f) == '\n') { + } else if (tmp_txt[1] == '\n') { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -1674,12 +1682,12 @@ void CQnetGateway::ProcessRepeater() new_group[i] = false; to_print[i] = 3; ABC_grp[i] = true; - C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03)?true:false; + C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03) ? true : false; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1]; band_txt[i].txt_cnt++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2]; band_txt[i].txt_cnt++; /* @@ -1725,14 +1733,14 @@ void CQnetGateway::ProcessRepeater() } else { if (to_print[i] == 3) { if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0] ^ 0x70; - band_txt[i].txt_cnt ++; + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0]; + band_txt[i].txt_cnt++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1] ^ 0x4f; - band_txt[i].txt_cnt ++; + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1]; + band_txt[i].txt_cnt++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2] ^ 0x93; - band_txt[i].txt_cnt ++; + band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2]; + band_txt[i].txt_cnt++; /* We should NOT see any more text, if we already processed text, @@ -1785,24 +1793,20 @@ void CQnetGateway::ProcessRepeater() } /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + if ((tmp_txt[0] != '\r') && (tmp_txt[0] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0]; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[2] ^ 0x93) != '\r') && ((tmp_txt[2] ^ 0x93) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2] ^ 0x93; + if ((tmp_txt[2] != '\r') && (tmp_txt[2] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2]; band_txt[i].temp_line_cnt++; } - if ( - ((tmp_txt[0] ^ 0x70) == '\r') || - ((tmp_txt[1] ^ 0x4f) == '\r') || - ((tmp_txt[2] ^ 0x93) == '\r') - ) { + if ( (tmp_txt[0] == '\r') || (tmp_txt[1] == '\r') || (tmp_txt[2] == '\r') ) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1814,9 +1818,8 @@ void CQnetGateway::ProcessRepeater() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || - ((tmp_txt[1] ^ 0x4f) == '\n') || - ((tmp_txt[2] ^ 0x93) == '\n')) { + } + else if ((tmp_txt[0] == '\n') || (tmp_txt[1] == '\n') ||(tmp_txt[2] == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -1830,16 +1833,16 @@ void CQnetGateway::ProcessRepeater() } /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + if ((tmp_txt[0] != '\r') && (tmp_txt[0] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0]; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[1] ^ 0x4f) != '\r') && ((tmp_txt[1] ^ 0x4f) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1] ^ 0x4f; + if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; band_txt[i].temp_line_cnt++; } - if (((tmp_txt[0] ^ 0x70) == '\r') || ((tmp_txt[1] ^ 0x4f) == '\r')) { + if ((tmp_txt[0] == '\r') || (tmp_txt[1] == '\r')) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1851,7 +1854,7 @@ void CQnetGateway::ProcessRepeater() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (((tmp_txt[0] ^ 0x70) == '\n') || ((tmp_txt[1] ^ 0x4f) == '\n')) { + } else if ((tmp_txt[0] == '\n') || (tmp_txt[1] == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -1864,12 +1867,12 @@ void CQnetGateway::ProcessRepeater() } /* do not copy CR, NL */ - if (((tmp_txt[0] ^ 0x70) != '\r') && ((tmp_txt[0] ^ 0x70) != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0] ^ 0x70; + if ((tmp_txt[0] != '\r') && (tmp_txt[0] != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0]; band_txt[i].temp_line_cnt++; } - if ((tmp_txt[0] ^ 0x70) == '\r') { + if (tmp_txt[0] == '\r') { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1881,7 +1884,7 @@ void CQnetGateway::ProcessRepeater() } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[0] ^ 0x70) == '\n') { + } else if (tmp_txt[0] == '\n') { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } From 08fbc35cd3d28396f0255810f683e29a1f358751 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Sep 2018 12:29:02 -0700 Subject: [PATCH 104/553] slow data in new sub --- QnetGateway.cpp | 645 ++++++++++++++++++++++++------------------------ QnetGateway.h | 1 + 2 files changed, 320 insertions(+), 326 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index b304c9d..5d24f91 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -961,11 +961,11 @@ void CQnetGateway::ProcessRepeater() // text stuff bool new_group[3] = { true, true, true }; - int header_type = 0; + //int header_type = 0; short to_print[3] = { 0, 0, 0 }; bool ABC_grp[3] = { false, false, false }; bool C_seen[3] = { false, false, false }; - unsigned char tmp_txt[3]; + //unsigned char tmp_txt[3]; char temp_radio_user[CALL_SIZE + 1]; char temp_mod; @@ -1572,331 +1572,9 @@ void CQnetGateway::ProcessRepeater() } if (recvlen == 29) - memcpy(tmp_txt, rptrbuf.vpkt.vasd.text, 3); + ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid, new_group, to_print, ABC_grp, C_seen); else - memcpy(tmp_txt, rptrbuf.vpkt.vasd1.text, 3); - - /* extract 20-byte RADIO ID */ - if ((tmp_txt[0] != 0x55) || (tmp_txt[1] != 0x2d) || (tmp_txt[2] != 0x16)) { - - // first, unscramble - tmp_txt[0] ^= 0x70u; - tmp_txt[1] ^= 0x4fu; - tmp_txt[2] ^= 0x93u; - - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - if (new_group[i]) { - header_type = tmp_txt[0] & 0xf0; - - // header squelch - if ((header_type == 0x50) || (header_type == 0xc0)) { - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - else if (header_type == 0x30) { /* GPS or GPS id or APRS */ - new_group[i] = false; - to_print[i] = tmp_txt[0] & 0x0f; - ABC_grp[i] = false; - if (to_print[i] > 5) - to_print[i] = 5; - else if (to_print[i] < 1) - to_print[i] = 1; - - if ((to_print[i] > 1) && (to_print[i] <= 5)) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && (tmp_txt[1] == '$')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; - band_txt[i].temp_line_cnt++; - } - if ((tmp_txt[2] != '\r') && (tmp_txt[2] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2]; - band_txt[i].temp_line_cnt++; - } - - if ((tmp_txt[1] == '\r') || (tmp_txt[2] == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[1] == '\n') || (tmp_txt[2] == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] -= 2; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; - band_txt[i].temp_line_cnt++; - } - - if (tmp_txt[1] == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (tmp_txt[1] == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] --; - } - } - else if (header_type == 0x40) { /* ABC text */ - new_group[i] = false; - to_print[i] = 3; - ABC_grp[i] = true; - C_seen[i] = ((tmp_txt[0] & 0x0f) == 0x03) ? true : false; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1]; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2]; - band_txt[i].txt_cnt++; - - /* - We should NOT see any more text, - if we already processed text, - so blank out the codes. - */ - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; - } - } - - if (band_txt[i].txt_cnt >= 20) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - /*** - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - ***/ - // printf("TEXT1=[%s]\n", band_txt[i].txt); - band_txt[i].txt_cnt = 0; - } - } - else { - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - } else { - if (to_print[i] == 3) { - if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[0]; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[1]; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = tmp_txt[2]; - band_txt[i].txt_cnt++; - - /* We should NOT see any more text, - if we already processed text, - so blank out the codes. */ - if (band_txt[i].txt_stats_sent) { - if (recvlen == 29) { - rptrbuf.vpkt.vasd.text[0] = 0x70; - rptrbuf.vpkt.vasd.text[1] = 0x4f; - rptrbuf.vpkt.vasd.text[2] = 0x93; - } else { - rptrbuf.vpkt.vasd1.text[0] = 0x70; - rptrbuf.vpkt.vasd1.text[1] = 0x4f; - rptrbuf.vpkt.vasd1.text[2] = 0x93; - } - } - - if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - if (!band_txt[i].txt_stats_sent) { - /*** if YRCALL is CQCQCQ, set dest_rptr ***/ - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; - } - - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); - band_txt[i].txt_stats_sent = true; - } - band_txt[i].txt_cnt = 0; - } - if (C_seen[i]) - C_seen[i] = false; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((tmp_txt[0] != '\r') && (tmp_txt[0] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0]; - band_txt[i].temp_line_cnt++; - } - if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; - band_txt[i].temp_line_cnt++; - } - if ((tmp_txt[2] != '\r') && (tmp_txt[2] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[2]; - band_txt[i].temp_line_cnt++; - } - - if ( (tmp_txt[0] == '\r') || (tmp_txt[1] == '\r') || (tmp_txt[2] == '\r') ) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - else if ((tmp_txt[0] == '\n') || (tmp_txt[1] == '\n') ||(tmp_txt[2] == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - } else if (to_print[i] == 2) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((tmp_txt[0] != '\r') && (tmp_txt[0] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0]; - band_txt[i].temp_line_cnt++; - } - if ((tmp_txt[1] != '\r') && (tmp_txt[1] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[1]; - band_txt[i].temp_line_cnt++; - } - - if ((tmp_txt[0] == '\r') || (tmp_txt[1] == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((tmp_txt[0] == '\n') || (tmp_txt[1] == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } else if (to_print[i] == 1) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((tmp_txt[0] != '\r') && (tmp_txt[0] != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp_txt[0]; - band_txt[i].temp_line_cnt++; - } - - if (tmp_txt[0] == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (tmp_txt[0] == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - } - break; - } - } - } + ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid, new_group, to_print, ABC_grp, C_seen); /* send data to qnlink */ sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); @@ -2033,6 +1711,321 @@ void CQnetGateway::ProcessRepeater() } } +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) +{ + unsigned char header_type; + + /* extract 20-byte RADIO ID */ + if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { + + // first, unscramble + unsigned char tmp0 = data[0] ^ 0x70u; + unsigned char tmp1 = data[1] ^ 0x4fu; + unsigned char tmp2 = data[2] ^ 0x93u; + + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == sid) { + if (new_group[i]) { + header_type = tmp0 & 0xf0; + + // header squelch + if ((header_type == 0x50) || (header_type == 0xc0)) { + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } + else if (header_type == 0x30) { /* GPS or GPS id or APRS */ + new_group[i] = false; + to_print[i] = tmp0 & 0x0f; + ABC_grp[i] = false; + if (to_print[i] > 5) + to_print[i] = 5; + else if (to_print[i] < 1) + to_print[i] = 1; + + if ((to_print[i] > 1) && (to_print[i] <= 5)) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* fresh GPS string, re-initialize */ + if ((to_print[i] == 5) && (tmp1 == '$')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((tmp1 != '\r') && (tmp1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + band_txt[i].temp_line_cnt++; + } + if ((tmp2 != '\r') && (tmp2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp2; + band_txt[i].temp_line_cnt++; + } + + if ((tmp1 == '\r') || (tmp2 == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((tmp1 == '\n') || (tmp2 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] -= 2; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((tmp1 != '\r') && (tmp1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + band_txt[i].temp_line_cnt++; + } + + if (tmp1 == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (tmp1 == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] --; + } + } + else if (header_type == 0x40) { /* ABC text */ + new_group[i] = false; + to_print[i] = 3; + ABC_grp[i] = true; + C_seen[i] = ((tmp0 & 0x0f) == 0x03) ? true : false; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp1; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp2; + band_txt[i].txt_cnt++; + + /* + We should NOT see any more text, + if we already processed text, + so blank out the codes. + */ + if (band_txt[i].txt_stats_sent) { + data[0] = 0x70; + data[1] = 0x4f; + data[2] = 0x93; + } + + if (band_txt[i].txt_cnt >= 20) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + /*** + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].dest_rptr, + band_txt[i].txt); + ***/ + // printf("TEXT1=[%s]\n", band_txt[i].txt); + band_txt[i].txt_cnt = 0; + } + } + else { + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } + } else { + if (to_print[i] == 3) { + if (ABC_grp[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = tmp0; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp1; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = tmp2; + band_txt[i].txt_cnt++; + + /* We should NOT see any more text, + if we already processed text, + so blank out the codes. */ + if (band_txt[i].txt_stats_sent) { + data[0] = 0x70; + data[1] = 0x4f; + data[2] = 0x93; + } + + if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + if (!band_txt[i].txt_stats_sent) { + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, + band_txt[i].lh_sfx, + (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", + band_txt[i].lh_rpt1, + band_txt[i].lh_rpt2, + band_txt[i].flags[0], + band_txt[i].flags[1], + band_txt[i].flags[2], + band_txt[i].dest_rptr, + band_txt[i].txt); + // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); + band_txt[i].txt_stats_sent = true; + } + band_txt[i].txt_cnt = 0; + } + if (C_seen[i]) + C_seen[i] = false; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((tmp0 != '\r') && (tmp0 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp0; + band_txt[i].temp_line_cnt++; + } + if ((tmp1 != '\r') && (tmp1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + band_txt[i].temp_line_cnt++; + } + if ((tmp2 != '\r') && (tmp2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp2; + band_txt[i].temp_line_cnt++; + } + + if ( (tmp0 == '\r') || (tmp1 == '\r') || (tmp2 == '\r') ) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + else if ((tmp0 == '\n') || (tmp1 == '\n') ||(tmp2 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + } else if (to_print[i] == 2) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((tmp0 != '\r') && (tmp0 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp0; + band_txt[i].temp_line_cnt++; + } + if ((tmp1 != '\r') && (tmp1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + band_txt[i].temp_line_cnt++; + } + + if ((tmp0 == '\r') || (tmp1 == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((tmp0 == '\n') || (tmp1 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } else if (to_print[i] == 1) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((tmp0 != '\r') && (tmp0 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp0; + band_txt[i].temp_line_cnt++; + } + + if (tmp0 == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (tmp0 == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; + } + break; + } + } + } +} + /* run the main loop for QnetGateway */ void CQnetGateway::Process() { diff --git a/QnetGateway.h b/QnetGateway.h index 833af6b..f7bddbc 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -160,6 +160,7 @@ private: void ProcessTimeouts(); void ProcessRouting(); void ProcessRepeater(); + void ProcessSlowData(unsigned char *data, unsigned short sid, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen); bool Flag_is_ok(unsigned char flag); // read configuration file From c57de0d577db0f3bb077c58e6c0cac141d0e702b Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Sep 2018 13:46:59 -0700 Subject: [PATCH 105/553] some are static --- QnetGateway.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 5d24f91..c6107d1 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -954,17 +954,17 @@ void CQnetGateway::ProcessRouting() void CQnetGateway::ProcessRepeater() { // dtmf stuff - int dtmf_buf_count[3] = {0, 0, 0}; - char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; - int dtmf_last_frame[3] = { 0, 0, 0 }; - unsigned int dtmf_counter[3] = { 0, 0, 0 }; + static int dtmf_buf_count[3] = {0, 0, 0}; + static char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; + static int dtmf_last_frame[3] = { 0, 0, 0 }; + static unsigned int dtmf_counter[3] = { 0, 0, 0 }; // text stuff - bool new_group[3] = { true, true, true }; + static bool new_group[3] = { true, true, true }; //int header_type = 0; - short to_print[3] = { 0, 0, 0 }; - bool ABC_grp[3] = { false, false, false }; - bool C_seen[3] = { false, false, false }; + static short to_print[3] = { 0, 0, 0 }; + static bool ABC_grp[3] = { false, false, false }; + static bool C_seen[3] = { false, false, false }; //unsigned char tmp_txt[3]; char temp_radio_user[CALL_SIZE + 1]; @@ -1572,9 +1572,9 @@ void CQnetGateway::ProcessRepeater() } if (recvlen == 29) - ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid, new_group, to_print, ABC_grp, C_seen); + ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); else - ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid, new_group, to_print, ABC_grp, C_seen); + ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); /* send data to qnlink */ sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); @@ -1711,9 +1711,9 @@ void CQnetGateway::ProcessRepeater() } } -void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) { - unsigned char header_type; + static unsigned char header_type = 0x0u; /* extract 20-byte RADIO ID */ if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { From f6237d409c88d507b7e2103d2f1afbd5e189b50f Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Sep 2018 14:16:02 -0700 Subject: [PATCH 106/553] redesigned Process() --- QnetGateway.cpp | 1845 +++++++++++++++++++++++------------------------ QnetGateway.h | 4 +- 2 files changed, 917 insertions(+), 932 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index c6107d1..02e14ba 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -787,934 +787,8 @@ void CQnetGateway::ProcessTimeouts() } } -void CQnetGateway::ProcessRouting() +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) { - SDSVT g2buf; - socklen_t fromlen = sizeof(struct sockaddr_in); - int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); - - // save incoming port for mobile systems - if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { - printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } else { - if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { - printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } - } - - if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { - if (g2buflen == 56) { - - // Find out the local repeater module IP/port to send the data to - int i = g2buf.hdr.rpt1[7] - 'A'; - - /* valid repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] is active if a remote system is talking to it or - // toRptr[i] is receiving data from a cross-band - if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { - if (bool_qso_details) - printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x30; - rptrbuf.vpkt.icm_id = 0x20; - //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); - rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; - rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; - rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; - rptrbuf.vpkt.streamid = g2buf.streamid; - rptrbuf.vpkt.ctrl = g2buf.ctrl; - memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - //if (is_icom) { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - //} else { - // memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - // memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); - //} - memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); - memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); - memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); - memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* save the header */ - memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); - toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; - - /* This is the active streamid */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } else { // g2buflen == 27 - if (bool_qso_details && g2buf.ctrl & 0x40) - printf("id=%04x END G2\n", ntohs(g2buf.streamid)); - - /* find out which repeater module to send the data to */ - int i; - for (i=0; i<3; i++) { - /* streamid match ? */ - if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining= 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - /* End of stream ? */ - if (g2buf.ctrl & 0x40) { - /* clear the saved header */ - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; - - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; - } - } - - /* no match ? */ - if ((i == 3) && bool_regen_header) { - /* check if this a continuation of audio that timed out */ - - if (g2buf.ctrl & 0x40) - ; /* we do not care about end-of-QSO */ - else { - /* for which repeater this stream has timed out ? */ - for (i = 0; i < 3; i++) { - /* match saved stream ? */ - if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { - /* repeater module is inactive ? */ - if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { - printf("Re-generating header for streamID=%04x\n", g2buf.streamid); - - toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); - toRptr[i].saved_hdr[5] = (unsigned char)((is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++) & 0xff); - - /* re-generate/send the header */ - sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* send this audio packet to repeater */ - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* make sure that any more audio arriving will be accepted */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - } - break; - } - } - } - } - } - } -} - -void CQnetGateway::ProcessRepeater() -{ - // dtmf stuff - static int dtmf_buf_count[3] = {0, 0, 0}; - static char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; - static int dtmf_last_frame[3] = { 0, 0, 0 }; - static unsigned int dtmf_counter[3] = { 0, 0, 0 }; - - // text stuff - static bool new_group[3] = { true, true, true }; - //int header_type = 0; - static short to_print[3] = { 0, 0, 0 }; - static bool ABC_grp[3] = { false, false, false }; - static bool C_seen[3] = { false, false, false }; - //unsigned char tmp_txt[3]; - - char temp_radio_user[CALL_SIZE + 1]; - char temp_mod; - - char arearp_cs[CALL_SIZE + 1]; - char zonerp_cs[CALL_SIZE + 1]; - char ip[IP_SIZE + 1]; - - char tempfile[FILENAME_MAX + 1]; - long num_recs = 0L; - short int rec_len = 56; - - SDSVT g2buf; - - socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); - - if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { - ///////////////////////////////////////////////////////////////////// - // some ICOM handshaking... - if (is_icom && 10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr - NEW_REPLY_SEQ = ntohs(rptrbuf.counter); - if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { - G2_COUNTER_OUT = NEW_REPLY_SEQ; - OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; - } else - OLD_REPLY_SEQ = NEW_REPLY_SEQ; - } else if (is_icom && 0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { - rptrbuf.flag[0] = 0x72U; - memset(rptrbuf.flag+1, 0x0U, 3); - sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - // end of ICOM handshaking - ///////////////////////////////////////////////////////////////////// - } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { - if (is_icom) { // acknowledge packet to ICOM - SDSTR reply; - memcpy(reply.pkt_id, "DSTR", 4); - reply.counter = rptrbuf.counter; - reply.flag[0] = 0x72U; - memset(reply.flag+1, 0, 3); - sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - } - - if (recvlen == 58) { - - if (bool_qso_details) - printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); - - if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (got a header)\n", i); - dtmf_last_frame[i] = 0; - dtmf_counter[i] = 0; - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - - /* Initialize the LAST HEARD data for the band */ - - band_txt[i].streamID = rptrbuf.vpkt.streamid; - - memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); - - memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); - band_txt[i].lh_mycall[8] = '\0'; - - memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); - band_txt[i].lh_sfx[4] = '\0'; - - memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); - band_txt[i].lh_yrcall[8] = '\0'; - - memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); - band_txt[i].lh_rpt1[8] = '\0'; - - memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].lh_rpt2[8] = '\0'; - - time(&band_txt[i].last_time); - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].txt_stats_sent = false; - - band_txt[i].dest_rptr[0] = '\0'; - - /* try to process GPS mode: GPRMC and ID */ - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - // band_txt[i].gps_last_time = 0; DO NOT reset it - - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - /* select the band for aprs processing, and lock on the stream ID */ - if (bool_send_aprs) - aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); - } - } - - /* Is MYCALL valid ? */ - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); - temp_radio_user[8] = '\0'; - - int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); - - if (mycall_valid == REG_NOERROR) - ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); - else { - if (mycall_valid == REG_NOMATCH) - printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); - else - printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); - } - - /* send data qnlink */ - if (mycall_valid == REG_NOERROR) - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - if ( mycall_valid==REG_NOERROR && - memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector - memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && - memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && - rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something - memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ - { - if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) - { - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - /* YRCALL=/repeater + mod */ - /* YRCALL=/KJ4NHFB */ - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); - temp_radio_user[6] = ' '; - temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; - if (temp_radio_user[7] == ' ') - temp_radio_user[7] = 'A'; - temp_radio_user[CALL_SIZE] = '\0'; - - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); - if (result) { /* it is a repeater */ - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that saved port instead of the default port - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set yrcall, can NOT let it be slash and repeater + module */ - memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - // send to remote gateway - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", - ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); - - time(&(to_remote_g2[i].last_time)); - } - } - } - } - } - else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); - temp_radio_user[8] = '\0'; - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); - if (result) { - /* destination is a remote system */ - if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set PFCS */ - memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - calcPFCS(g2buf.title, 56); - - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - /* send to remote gateway */ - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); - - time(&(to_remote_g2[i].last_time)); - } - } - } - else - { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* the user we are trying to contact is on our gateway */ - /* make sure they are on a different module */ - if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { - /* - The remote repeater has been set, lets fill in the dest_rptr - so that later we can send that to the LIVE web site - */ - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[7] = temp_mod; - band_txt[i].dest_rptr[8] = '\0'; - - i = temp_mod - 'A'; - - /* valid destination repeater module? */ - if (i>=0 && i<3) { - /* - toRptr[i] : receiving from a remote system or cross-band - band_txt[i] : local RF is talking. - */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - - rptrbuf.vpkt.hdr.r2[7] = temp_mod; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } - else - printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); - } - } - } - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - unlink(vm[i].file); - printf("removed voicemail file: %s\n", vm[i].file); - vm[i].file[0] = '\0'; - } else - printf("No voicemail to clear or still recording\n"); - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); - } catch (const std::exception &e) { - printf("Failed to start voicemail playback. Exception: %s\n", e.what()); - } - } else - printf("No voicemail to recall or still recording\n"); - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (vm[i].fd >= 0) - printf("Already recording for voicemail on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); - - vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (vm[i].fd < 0) - printf("Failed to create file %s for voicemail\n", tempfile); - else { - strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); - - time(&vm[i].last_time); - vm[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(vm[i].fd, "DVTOOL", 6); - (void)write(vm[i].fd, &num_recs, 4); - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); - } - } - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (recd[i].fd >= 0) - printf("Already recording for echotest on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - - recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (recd[i].fd < 0) - printf("Failed to create file %s for echotest\n", tempfile); - else { - strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); - - time(&recd[i].last_time); - recd[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - rec_len = 56; - (void)write(recd[i].fd, "DVTOOL", 6); - (void)write(recd[i].fd, &num_recs, 4); - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); - } - } - } - /* check for cross-banding */ - } - else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C - (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with - rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[8] = '\0'; - } - - i = rptrbuf.vpkt.hdr.r2[7] - 'A'; - - // valid destination repeater module? - if (i>=0 && i<3) { - // toRptr[i] : receiving from a remote system or cross-band - // band_txt[i] : local RF is talking. - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); - - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER ++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } - } - else - { // recvlen is 29 or 32 - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - time(&band_txt[i].last_time); - - if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data - if (dtmf_buf_count[i] > 0) { - dtmf_file = dtmf_dir; - dtmf_file.push_back('/'); - dtmf_file.push_back('A'+i); - dtmf_file += "_mod_DTMF_NOTIFY"; - if (bool_dtmf_debug) - printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); - FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); - if (dtmf_fp) { - fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); - fclose(dtmf_fp); - } else - printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - - - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - dtmf_counter[i] = 0; - dtmf_last_frame[i] = 0; - } - - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - - band_txt[i].dest_rptr[0] = '\0'; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - } - else - { // not the end of the voice stream - int ber_data[3]; - int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); - if (ber_data[0] == 0xf85) - band_txt[i].num_dv_silent_frames++; - band_txt[i].num_bit_errors += ber_errs; - band_txt[i].num_dv_frames++; - - if ((ber_data[0] & 0x0ffc) == 0xfc0) { - dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); - if (dtmf_counter[i] > 0) { - if (dtmf_last_frame[i] != dtmf_digit) - dtmf_counter[i] = 0; - } - dtmf_last_frame[i] = dtmf_digit; - dtmf_counter[i]++; - - if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { - if (dtmf_buf_count[i] < MAX_DTMF_BUF) { - const char *dtmf_chars = "147*2580369#ABCD"; - dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; - dtmf_buf_count[i]++; - } - } - const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - if (recvlen == 29) - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); - else - memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); - } else - dtmf_counter[i] = 0; - } - break; - } - } - - if (recvlen == 29) - ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); - else - ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); - - /* send data to qnlink */ - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - /* aprs processing */ - if (bool_send_aprs) - // streamID seq audio+text size - aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); - - for (int i=0; i<3; i++) { - /* find out if data must go to the remote G2 */ - if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x20; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; - memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); - if (recvlen == 29) - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - time(&(to_remote_g2[i].last_time)); - - /* Is this the end-of-stream */ - if (rptrbuf.vpkt.ctrl & 0x40) { - memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - break; - } - else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest - time(&recd[i].last_time); - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - rec_len = 27; - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* we are in echotest mode, so play it back */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); - } catch (const std::exception &e) { - printf("failed to start PlayFileThread. Exception: %s\n", e.what()); - // When the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - } - break; - } - else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail - time(&vm[i].last_time); - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - rec_len = 27; - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } - break; - } - else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); - - /* bump G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER ++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - /* End of stream ? */ - if (rptrbuf.vpkt.ctrl & 0x40) { - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; - } - } - - if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) - printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); - } - } - } -} - -void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) -{ - static unsigned char header_type = 0x0u; - /* extract 20-byte RADIO ID */ if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { @@ -2029,6 +1103,19 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) /* run the main loop for QnetGateway */ void CQnetGateway::Process() { + // dtmf stuff + int dtmf_buf_count[3] = {0, 0, 0}; + char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; + int dtmf_last_frame[3] = { 0, 0, 0 }; + unsigned int dtmf_counter[3] = { 0, 0, 0 }; + + // text stuff + bool new_group[3] = { true, true, true }; + unsigned char header_type = 0; + short to_print[3] = { 0, 0, 0 }; + bool ABC_grp[3] = { false, false, false }; + bool C_seen[3] = { false, false, false }; + dstar_dv_init(); int max_nfds = 0; @@ -2104,13 +1191,913 @@ void CQnetGateway::Process() // process packets coming from remote G2 if (FD_ISSET(g2_sock, &fdset)) { - ProcessRouting(); + SDSVT g2buf; + socklen_t fromlen = sizeof(struct sockaddr_in); + int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); + + // save incoming port for mobile systems + if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { + printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } else { + if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { + printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } + } + + if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { + if (g2buflen == 56) { + + // Find out the local repeater module IP/port to send the data to + int i = g2buf.hdr.rpt1[7] - 'A'; + + /* valid repeater module? */ + if (i>=0 && i<3) { + // toRptr[i] is active if a remote system is talking to it or + // toRptr[i] is receiving data from a cross-band + if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { + if (bool_qso_details) + printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x30; + rptrbuf.vpkt.icm_id = 0x20; + //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); + rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; + rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; + rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; + rptrbuf.vpkt.streamid = g2buf.streamid; + rptrbuf.vpkt.ctrl = g2buf.ctrl; + memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); + //if (is_icom) { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); + //} else { + // memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + // memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + //} + memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); + memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); + memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); + memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); + + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* save the header */ + memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); + toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; + + /* This is the active streamid */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } else { // g2buflen == 27 + if (bool_qso_details && g2buf.ctrl & 0x40) + printf("id=%04x END G2\n", ntohs(g2buf.streamid)); + + /* find out which repeater module to send the data to */ + int i; + for (i=0; i<3; i++) { + /* streamid match ? */ + if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining= 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* timeit */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (g2buf.ctrl & 0x40) { + /* clear the saved header */ + memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); + toRptr[i].saved_adr = 0; + + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; + } + } + + /* no match ? */ + if ((i == 3) && bool_regen_header) { + /* check if this a continuation of audio that timed out */ + + if (g2buf.ctrl & 0x40) + ; /* we do not care about end-of-QSO */ + else { + /* for which repeater this stream has timed out ? */ + for (i = 0; i < 3; i++) { + /* match saved stream ? */ + if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { + /* repeater module is inactive ? */ + if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { + printf("Re-generating header for streamID=%04x\n", g2buf.streamid); + + toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); + toRptr[i].saved_hdr[5] = (unsigned char)((is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++) & 0xff); + + /* re-generate/send the header */ + sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* send this audio packet to repeater */ + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* make sure that any more audio arriving will be accepted */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + } + break; + } + } + } + } + } + } FD_CLR (g2_sock,&fdset); } // process packets coming from local repeater modules if (FD_ISSET(srv_sock, &fdset)) { - ProcessRepeater(); + char temp_radio_user[CALL_SIZE + 1]; + char temp_mod; + + char arearp_cs[CALL_SIZE + 1]; + char zonerp_cs[CALL_SIZE + 1]; + char ip[IP_SIZE + 1]; + + char tempfile[FILENAME_MAX + 1]; + long num_recs = 0L; + short int rec_len = 56; + + SDSVT g2buf; + + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); + + if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { + ///////////////////////////////////////////////////////////////////// + // some ICOM handshaking... + if (is_icom && 10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr + NEW_REPLY_SEQ = ntohs(rptrbuf.counter); + if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { + G2_COUNTER_OUT = NEW_REPLY_SEQ; + OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; + } else + OLD_REPLY_SEQ = NEW_REPLY_SEQ; + } else if (is_icom && 0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { + rptrbuf.flag[0] = 0x72U; + memset(rptrbuf.flag+1, 0x0U, 3); + sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + // end of ICOM handshaking + ///////////////////////////////////////////////////////////////////// + } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { + if (is_icom) { // acknowledge packet to ICOM + SDSTR reply; + memcpy(reply.pkt_id, "DSTR", 4); + reply.counter = rptrbuf.counter; + reply.flag[0] = 0x72U; + memset(reply.flag+1, 0, 3); + sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + } + + if (recvlen == 58) { + + if (bool_qso_details) + printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); + + if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { + + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (got a header)\n", i); + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0; + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + + /* Initialize the LAST HEARD data for the band */ + + band_txt[i].streamID = rptrbuf.vpkt.streamid; + + memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); + + memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); + band_txt[i].lh_mycall[8] = '\0'; + + memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); + band_txt[i].lh_sfx[4] = '\0'; + + memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); + band_txt[i].lh_yrcall[8] = '\0'; + + memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); + band_txt[i].lh_rpt1[8] = '\0'; + + memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].lh_rpt2[8] = '\0'; + + time(&band_txt[i].last_time); + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].txt_stats_sent = false; + + band_txt[i].dest_rptr[0] = '\0'; + + /* try to process GPS mode: GPRMC and ID */ + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + // band_txt[i].gps_last_time = 0; DO NOT reset it + + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + /* select the band for aprs processing, and lock on the stream ID */ + if (bool_send_aprs) + aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); + } + } + + /* Is MYCALL valid ? */ + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); + temp_radio_user[8] = '\0'; + + int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); + + if (mycall_valid == REG_NOERROR) + ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); + else { + if (mycall_valid == REG_NOMATCH) + printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); + else + printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); + } + + /* send data qnlink */ + if (mycall_valid == REG_NOERROR) + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + if ( mycall_valid==REG_NOERROR && + memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector + memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && + memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && + rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something + memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ + { + if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) + { + if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + /* YRCALL=/repeater + mod */ + /* YRCALL=/KJ4NHFB */ + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); + temp_radio_user[6] = ' '; + temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; + if (temp_radio_user[7] == ' ') + temp_radio_user[7] = 'A'; + temp_radio_user[CALL_SIZE] = '\0'; + + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); + if (result) { /* it is a repeater */ + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that saved port instead of the default port + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set yrcall, can NOT let it be slash and repeater + module */ + memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + + /* set PFCS */ + calcPFCS(g2buf.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + // send to remote gateway + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); + + time(&(to_remote_g2[i].last_time)); + } + } + } + } + } + else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { + + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); + temp_radio_user[8] = '\0'; + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); + if (result) { + /* destination is a remote system */ + if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set PFCS */ + memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + calcPFCS(g2buf.title, 56); + + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + /* send to remote gateway */ + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); + + time(&(to_remote_g2[i].last_time)); + } + } + } + else + { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* the user we are trying to contact is on our gateway */ + /* make sure they are on a different module */ + if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { + /* + The remote repeater has been set, lets fill in the dest_rptr + so that later we can send that to the LIVE web site + */ + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[7] = temp_mod; + band_txt[i].dest_rptr[8] = '\0'; + + i = temp_mod - 'A'; + + /* valid destination repeater module? */ + if (i>=0 && i<3) { + /* + toRptr[i] : receiving from a remote system or cross-band + band_txt[i] : local RF is talking. + */ + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); + + rptrbuf.vpkt.hdr.r2[7] = temp_mod; + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); + + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + /* bump the G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } + else + printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); + } + } + } + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + unlink(vm[i].file); + printf("removed voicemail file: %s\n", vm[i].file); + vm[i].file[0] = '\0'; + } else + printf("No voicemail to clear or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); + } catch (const std::exception &e) { + printf("Failed to start voicemail playback. Exception: %s\n", e.what()); + } + } else + printf("No voicemail to recall or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (vm[i].fd >= 0) + printf("Already recording for voicemail on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); + + vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (vm[i].fd < 0) + printf("Failed to create file %s for voicemail\n", tempfile); + else { + strcpy(vm[i].file, tempfile); + printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); + + time(&vm[i].last_time); + vm[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + rec_len = 56; + (void)write(vm[i].fd, "DVTOOL", 6); + (void)write(vm[i].fd, &num_recs, 4); + (void)write(vm[i].fd, &rec_len, 2); + (void)write(vm[i].fd, &recbuf, rec_len); + } + } + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (recd[i].fd >= 0) + printf("Already recording for echotest on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); + + recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (recd[i].fd < 0) + printf("Failed to create file %s for echotest\n", tempfile); + else { + strcpy(recd[i].file, tempfile); + printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); + + time(&recd[i].last_time); + recd[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + rec_len = 56; + (void)write(recd[i].fd, "DVTOOL", 6); + (void)write(recd[i].fd, &num_recs, 4); + (void)write(recd[i].fd, &rec_len, 2); + (void)write(recd[i].fd, &recbuf, rec_len); + } + } + } + /* check for cross-banding */ + } + else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C + (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with + rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[8] = '\0'; + } + + i = rptrbuf.vpkt.hdr.r2[7] - 'A'; + + // valid destination repeater module? + if (i>=0 && i<3) { + // toRptr[i] : receiving from a remote system or cross-band + // band_txt[i] : local RF is talking. + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); + + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); + + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + /* bump the G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER ++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } + } + else + { // recvlen is 29 or 32 + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + time(&band_txt[i].last_time); + + if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data + if (dtmf_buf_count[i] > 0) { + dtmf_file = dtmf_dir; + dtmf_file.push_back('/'); + dtmf_file.push_back('A'+i); + dtmf_file += "_mod_DTMF_NOTIFY"; + if (bool_dtmf_debug) + printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); + FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); + if (dtmf_fp) { + fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); + fclose(dtmf_fp); + } else + printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); + + + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + dtmf_counter[i] = 0; + dtmf_last_frame[i] = 0; + } + + ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); + + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + } + else + { // not the end of the voice stream + int ber_data[3]; + int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); + if (ber_data[0] == 0xf85) + band_txt[i].num_dv_silent_frames++; + band_txt[i].num_bit_errors += ber_errs; + band_txt[i].num_dv_frames++; + + if ((ber_data[0] & 0x0ffc) == 0xfc0) { + dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); + if (dtmf_counter[i] > 0) { + if (dtmf_last_frame[i] != dtmf_digit) + dtmf_counter[i] = 0; + } + dtmf_last_frame[i] = dtmf_digit; + dtmf_counter[i]++; + + if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { + if (dtmf_buf_count[i] < MAX_DTMF_BUF) { + const char *dtmf_chars = "147*2580369#ABCD"; + dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; + dtmf_buf_count[i]++; + } + } + const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; + if (recvlen == 29) + memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); + else + memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); + } else + dtmf_counter[i] = 0; + } + break; + } + } + + if (recvlen == 29) + ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid, header_type, new_group, to_print, ABC_grp, C_seen); + else + ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid, header_type, new_group, to_print, ABC_grp, C_seen); + + /* send data to qnlink */ + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + /* aprs processing */ + if (bool_send_aprs) + // streamID seq audio+text size + aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); + + for (int i=0; i<3; i++) { + /* find out if data must go to the remote G2 */ + if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x20; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; + memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); + if (recvlen == 29) + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + time(&(to_remote_g2[i].last_time)); + + /* Is this the end-of-stream */ + if (rptrbuf.vpkt.ctrl & 0x40) { + memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + break; + } + else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest + time(&recd[i].last_time); + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x20; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + rec_len = 27; + (void)write(recd[i].fd, &rec_len, 2); + (void)write(recd[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* we are in echotest mode, so play it back */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); + } catch (const std::exception &e) { + printf("failed to start PlayFileThread. Exception: %s\n", e.what()); + // When the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } + } + break; + } + else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail + time(&vm[i].last_time); + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x20; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + rec_len = 27; + (void)write(vm[i].fd, &rec_len, 2); + (void)write(vm[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + break; + } + else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* timeit */ + time(&toRptr[i].last_time); + + /* bump G2 counter */ + if (is_icom) + G2_COUNTER_OUT++; + else + toRptr[i].G2_COUNTER ++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (rptrbuf.vpkt.ctrl & 0x40) { + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; + } + } + + if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) + printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); + } + } + } FD_CLR (srv_sock,&fdset); } } diff --git a/QnetGateway.h b/QnetGateway.h index f7bddbc..00c0a13 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -158,9 +158,7 @@ private: void compute_aprs_hash(); void APRSBeaconThread(); void ProcessTimeouts(); - void ProcessRouting(); - void ProcessRepeater(); - void ProcessSlowData(unsigned char *data, unsigned short sid, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen); + void ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen); bool Flag_is_ok(unsigned char flag); // read configuration file From a1f27bc33ab529496dc3f4e893e9f8ece2ad5801 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Sep 2018 16:07:04 -0700 Subject: [PATCH 107/553] minor cleanup --- QnetGateway.cpp | 111 +++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 68 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 02e14ba..22b71af 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -793,14 +793,14 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { // first, unscramble - unsigned char tmp0 = data[0] ^ 0x70u; - unsigned char tmp1 = data[1] ^ 0x4fu; - unsigned char tmp2 = data[2] ^ 0x93u; + unsigned char c1 = data[0] ^ 0x70u; + unsigned char c2 = data[1] ^ 0x4fu; + unsigned char c3 = data[2] ^ 0x93u; for (int i=0; i<3; i++) { if (band_txt[i].streamID == sid) { if (new_group[i]) { - header_type = tmp0 & 0xf0; + header_type = c1 & 0xf0; // header squelch if ((header_type == 0x50) || (header_type == 0xc0)) { @@ -810,7 +810,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } else if (header_type == 0x30) { /* GPS or GPS id or APRS */ new_group[i] = false; - to_print[i] = tmp0 & 0x0f; + to_print[i] = c1 & 0x0f; ABC_grp[i] = false; if (to_print[i] > 5) to_print[i] = 5; @@ -826,22 +826,22 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && (tmp1 == '$')) { + if ((to_print[i] == 5) && (c2 == '$')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } /* do not copy CR, NL */ - if ((tmp1 != '\r') && (tmp1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; band_txt[i].temp_line_cnt++; } - if ((tmp2 != '\r') && (tmp2 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp2; + if ((c3 != '\r') && (c3 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; band_txt[i].temp_line_cnt++; } - if ((tmp1 == '\r') || (tmp2 == '\r')) { + if ((c2 == '\r') || (c3 == '\r')) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -853,7 +853,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if ((tmp1 == '\n') || (tmp2 == '\n')) { + } else if ((c2 == '\n') || (c3 == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -867,12 +867,12 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } /* do not copy CR, NL */ - if ((tmp1 != '\r') && (tmp1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; band_txt[i].temp_line_cnt++; } - if (tmp1 == '\r') { + if (c2 == '\r') { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -884,7 +884,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (tmp1 == '\n') { + } else if (c2 == '\n') { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -895,19 +895,17 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi new_group[i] = false; to_print[i] = 3; ABC_grp[i] = true; - C_seen[i] = ((tmp0 & 0x0f) == 0x03) ? true : false; + C_seen[i] = ((c1 & 0x0f) == 0x03) ? true : false; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp1; + band_txt[i].txt[band_txt[i].txt_cnt] = c2; band_txt[i].txt_cnt++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp2; + band_txt[i].txt[band_txt[i].txt_cnt] = c3; band_txt[i].txt_cnt++; - /* - We should NOT see any more text, + /* We should NOT see any more text, if we already processed text, - so blank out the codes. - */ + so blank out the codes. */ if (band_txt[i].txt_stats_sent) { data[0] = 0x70; data[1] = 0x4f; @@ -916,19 +914,6 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi if (band_txt[i].txt_cnt >= 20) { band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - /*** - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - ***/ - // printf("TEXT1=[%s]\n", band_txt[i].txt); band_txt[i].txt_cnt = 0; } } @@ -940,13 +925,13 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } else { if (to_print[i] == 3) { if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = tmp0; + band_txt[i].txt[band_txt[i].txt_cnt] = c1; band_txt[i].txt_cnt++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp1; + band_txt[i].txt[band_txt[i].txt_cnt] = c2; band_txt[i].txt_cnt++; - band_txt[i].txt[band_txt[i].txt_cnt] = tmp2; + band_txt[i].txt[band_txt[i].txt_cnt] = c3; band_txt[i].txt_cnt++; /* We should NOT see any more text, @@ -968,17 +953,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi band_txt[i].dest_rptr[0] = '\0'; } - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, - band_txt[i].lh_sfx, - (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", - band_txt[i].lh_rpt1, - band_txt[i].lh_rpt2, - band_txt[i].flags[0], - band_txt[i].flags[1], - band_txt[i].flags[2], - band_txt[i].dest_rptr, - band_txt[i].txt); - // printf("TEXT2=[%s], destination repeater=[%s]\n", band_txt[i].txt, band_txt[i].dest_rptr); + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); band_txt[i].txt_stats_sent = true; } band_txt[i].txt_cnt = 0; @@ -994,20 +969,20 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } /* do not copy CR, NL */ - if ((tmp0 != '\r') && (tmp0 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp0; + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; band_txt[i].temp_line_cnt++; } - if ((tmp1 != '\r') && (tmp1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; band_txt[i].temp_line_cnt++; } - if ((tmp2 != '\r') && (tmp2 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp2; + if ((c3 != '\r') && (c3 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; band_txt[i].temp_line_cnt++; } - if ( (tmp0 == '\r') || (tmp1 == '\r') || (tmp2 == '\r') ) { + if ( (c1 == '\r') || (c2 == '\r') || (c3 == '\r') ) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1020,7 +995,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } - else if ((tmp0 == '\n') || (tmp1 == '\n') ||(tmp2 == '\n')) { + else if ((c1 == '\n') || (c2 == '\n') ||(c3 == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -1034,16 +1009,16 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } /* do not copy CR, NL */ - if ((tmp0 != '\r') && (tmp0 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp0; + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; band_txt[i].temp_line_cnt++; } - if ((tmp1 != '\r') && (tmp1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp1; + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; band_txt[i].temp_line_cnt++; } - if ((tmp0 == '\r') || (tmp1 == '\r')) { + if ((c1 == '\r') || (c2 == '\r')) { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1055,7 +1030,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if ((tmp0 == '\n') || (tmp1 == '\n')) { + } else if ((c1 == '\n') || (c2 == '\n')) { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } @@ -1068,12 +1043,12 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } /* do not copy CR, NL */ - if ((tmp0 != '\r') && (tmp0 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = tmp0; + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; band_txt[i].temp_line_cnt++; } - if (tmp0 == '\r') { + if (c1 == '\r') { if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; @@ -1085,7 +1060,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi } band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; - } else if (tmp0 == '\n') { + } else if (c1 == '\n') { band_txt[i].temp_line[0] = '\0'; band_txt[i].temp_line_cnt = 0; } From 633e32e916d82e396b932f519a85ecb57fec5591 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 7 Oct 2018 14:58:04 -0700 Subject: [PATCH 108/553] This is how the Icom Stack sends data to the gateway --- QnetTypeDefs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index cde2948..41aac94 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -39,8 +39,8 @@ typedef struct dstr_tag { union { struct { unsigned char flag[3]; // 17 - unsigned char r1[8]; // 20 - unsigned char r2[8]; // 28 + unsigned char r2[8]; // 20 + unsigned char r1[8]; // 28 unsigned char ur[8]; // 36 unsigned char my[8]; // 44 unsigned char nm[4]; // 52 From 33c8142b8a10113cd6c31f7c1b63c2cda5ce0ff5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 7 Oct 2018 15:11:56 -0700 Subject: [PATCH 109/553] Clean-up DSVT (incoming g2) to DSTR format --- QnetGateway.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 22b71af..9794140 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1209,13 +1209,8 @@ void CQnetGateway::Process() rptrbuf.vpkt.streamid = g2buf.streamid; rptrbuf.vpkt.ctrl = g2buf.ctrl; memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - //if (is_icom) { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - //} else { - // memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - // memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); - //} + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); From c35e27066dc45fe4c9f1d207aa109a67d698dcbf Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 7 Oct 2018 16:24:21 -0700 Subject: [PATCH 110/553] incoming g2 gets reversed r1 and r2 for icom stack --- QnetGateway.cpp | 9 +++++++-- QnetLink.cpp | 10 +++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 9794140..5be8922 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1209,8 +1209,13 @@ void CQnetGateway::Process() rptrbuf.vpkt.streamid = g2buf.streamid; rptrbuf.vpkt.ctrl = g2buf.ctrl; memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + if (is_icom) { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); + } else { + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); + } memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); diff --git a/QnetLink.cpp b/QnetLink.cpp index 5293478..a0689de 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -1509,8 +1509,7 @@ void CQnetLink::Process() found = false; for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { + if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_xrf_port))) { to_remote_g2[i].countdown = TIMEOUT; found = true; } @@ -1524,8 +1523,7 @@ void CQnetLink::Process() source_stn[8] = '\0'; /* some bad hotspot programs out there using INCORRECT flag */ - if (dsvt.hdr.flag[0]==0x40U || dsvt.hdr.flag[0]==0x48U || dsvt.hdr.flag[0]==0x60U || dsvt.hdr.flag[0]==0x68U) - dsvt.hdr.flag[0] -= 0x40; + if (dsvt.hdr.flag[0]==0x40U || dsvt.hdr.flag[0]==0x48U || dsvt.hdr.flag[0]==0x60U || dsvt.hdr.flag[0]==0x68U) dsvt.hdr.flag[0] -= 0x40; /* A reflector will send to us its own RPT1 */ /* A repeater will send to us our RPT1 */ @@ -1573,9 +1571,7 @@ void CQnetLink::Process() /* Last Heard */ if (old_sid[i].sid != dsvt.streamid) { if (qso_details) - printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", - ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.mycall, dsvt.hdr.sfx, - dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, length, inet_ntoa(fromDst4.sin_addr), source_stn); + printf("START from remote g2: streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s, source=%.8s\n", ntohs(dsvt.streamid), dsvt.hdr.flag[0], dsvt.hdr.flag[1], dsvt.hdr.flag[2], dsvt.hdr.mycall, dsvt.hdr.sfx, dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, length, inet_ntoa(fromDst4.sin_addr), source_stn); // put user into tmp1 memcpy(tmp1, dsvt.hdr.mycall, 8); From d1e7c5fd91cb3cf8779378560f0f1064db39a1b1 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 7 Oct 2018 19:37:11 -0700 Subject: [PATCH 111/553] All incoming g2 gets reversed --- QnetGateway.cpp | 9 ++------- QnetITAP.cpp | 6 ++---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 5be8922..1ff254f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1209,13 +1209,8 @@ void CQnetGateway::Process() rptrbuf.vpkt.streamid = g2buf.streamid; rptrbuf.vpkt.ctrl = g2buf.ctrl; memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - if (is_icom) { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - } else { - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt2, 8); - } + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); diff --git a/QnetITAP.cpp b/QnetITAP.cpp index be8e271..0176917 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -421,8 +421,7 @@ bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) return true; } if (log_qso) - printf("Sent ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), - itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm); + printf("Sent ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), itap.header.ur, itap.header.r2, itap.header.r1, itap.header.my, itap.header.nm); } else { // write an AMBE packet itap.length = 16U; itap.type = 0x22U; @@ -509,8 +508,7 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) return true; } if (log_qso) - printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), - dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); } else if (16 == len) { // ambe dstr.remaining = 0x16; dstr.vpkt.ctrl = itap.voice.sequence; From 2201b99ad1a8036cb1062119cba3f08556181756 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 7 Oct 2018 19:57:46 -0700 Subject: [PATCH 112/553] changed DSRP qso log --- QnetRelay.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 16de12e..40a3f41 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -271,8 +271,7 @@ bool CQnetRelay::ProcessGateway(const int len, const unsigned char *raw) return true; } if (log_qso) - printf("Sent DSRP to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MMDVM_IN_PORT, ntohs(dsrp.header.id), - dsrp.header.ur, dsrp.header.r1, dsrp.header.r2, dsrp.header.my, dsrp.header.nm); + printf("Sent DSRP to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", MMDVM_IN_PORT, ntohs(dsrp.header.id), dsrp.header.ur, dsrp.header.r2, dsrp.header.r1, dsrp.header.my, dsrp.header.nm); } } else From fd6a42cd766f55ad22125b53d3f4390e31284226 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 7 Oct 2018 20:45:55 -0700 Subject: [PATCH 113/553] version bump --- versions.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/versions.h b/versions.h index 306f926..8ebcf24 100644 --- a/versions.h +++ b/versions.h @@ -1,9 +1,9 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.1.0" +#define IRCDDB_VERSION "QnetGateway-7.2.0" #define LINK_VERSION "QnetLink-6.0.2" #define DVAP_VERSION "QnetDVAP-5.1.2" -#define RELAY_VERSION "QnetRelay-0.2.2" -#define ITAP_VERSION "QnetITAP-0.2.0" +#define RELAY_VERSION "QnetRelay-0.2.3" +#define ITAP_VERSION "QnetITAP-0.2.1" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION From 0023fc9f918538bacdb08686bf29077f63f443f3 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 9 Oct 2018 07:02:10 -0700 Subject: [PATCH 114/553] new echo and voicemail --- QnetGateway.cpp | 273 +++++++++++++++++++++++++----------------------- QnetGateway.h | 5 +- 2 files changed, 146 insertions(+), 132 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 1ff254f..1764a7c 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -56,7 +56,6 @@ #include "IRCDDB.h" #include "IRCutils.h" #include "versions.h" -#include "QnetTypeDefs.h" #include "QnetGateway.h" @@ -676,8 +675,7 @@ void CQnetGateway::ProcessTimeouts() if (recd[i].last_time != 0) { time(&t_now); if ((t_now - recd[i].last_time) > echotest_rec_timeout) { - printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", - i, recd[i].streamid); + printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", i, recd[i].streamid); recd[i].streamid = 0; recd[i].last_time = 0; @@ -687,7 +685,7 @@ void CQnetGateway::ProcessTimeouts() /* START: echotest thread setup */ try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); } catch (const std::exception &e) { printf("Failed to start echotest thread. Exception: %s\n", e.what()); // when the echotest thread runs, it deletes the file, @@ -702,8 +700,7 @@ void CQnetGateway::ProcessTimeouts() if (vm[i].last_time != 0) { time(&t_now); if ((t_now - vm[i].last_time) > voicemail_rec_timeout) { - printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", - i, vm[i].streamid); + printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", i, vm[i].streamid); vm[i].streamid = 0; vm[i].last_time = 0; @@ -1334,8 +1331,6 @@ void CQnetGateway::Process() char ip[IP_SIZE + 1]; char tempfile[FILENAME_MAX + 1]; - long num_recs = 0L; - short int rec_len = 56; SDSVT g2buf; @@ -1690,8 +1685,9 @@ void CQnetGateway::Process() if (i>=0 && i<3) { /* voicemail file is closed */ if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + snprintf(vm[i].message, 21, "VOICEMAIL ON MOD %c ", 'A'+i); try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, vm[i].file); + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(vm[i])); } catch (const std::exception &e) { printf("Failed to start voicemail playback. Exception: %s\n", e.what()); } @@ -1737,11 +1733,7 @@ void CQnetGateway::Process() calcPFCS(recbuf.title, 56); - rec_len = 56; - (void)write(vm[i].fd, "DVTOOL", 6); - (void)write(vm[i].fd, &num_recs, 4); - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); + memcpy(vm[i].header.title, recbuf.title, 56); } } } @@ -1762,7 +1754,7 @@ void CQnetGateway::Process() else { strcpy(recd[i].file, tempfile); printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); - + snprintf(recd[i].message, 21, "ECHO ON MODULE %c ", 'A' + i); time(&recd[i].last_time); recd[i].streamid = rptrbuf.vpkt.streamid; @@ -1784,11 +1776,7 @@ void CQnetGateway::Process() calcPFCS(recbuf.title, 56); - rec_len = 56; - (void)write(recd[i].fd, "DVTOOL", 6); - (void)write(recd[i].fd, &num_recs, 4); - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); + memcpy(recd[i].header.title, recbuf.title, 56); } } } @@ -1974,22 +1962,24 @@ void CQnetGateway::Process() else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest time(&recd[i].last_time); - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + //memcpy(recbuf.title, "DSVT", 4); + //recbuf.config = 0x20; + //recbuf.id = rptrbuf.vpkt.icm_id; + //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; + //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + (void)write(recd[i].fd, rptrbuf.vpkt.vasd.voice, 9); else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + (void)write(recd[i].fd, rptrbuf.vpkt.vasd1.voice, 9); - rec_len = 27; - (void)write(recd[i].fd, &rec_len, 2); - (void)write(recd[i].fd, &recbuf, rec_len); + //rec_len = 27; + //(void)write(recd[i].fd, &rec_len, 2); + //(void)write(recd[i].fd, &recbuf, rec_len); if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { recd[i].streamid = 0; @@ -2000,7 +1990,7 @@ void CQnetGateway::Process() /* we are in echotest mode, so play it back */ try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, recd[i].file); + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); } catch (const std::exception &e) { printf("failed to start PlayFileThread. Exception: %s\n", e.what()); // When the echotest thread runs, it deletes the file, @@ -2013,22 +2003,24 @@ void CQnetGateway::Process() else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail time(&vm[i].last_time); - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x20; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + //memcpy(recbuf.title, "DSVT", 4); + //recbuf.config = 0x20; + //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + //recbuf.id = rptrbuf.vpkt.icm_id; + //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); if (recvlen == 29) - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + (void)write(vm[i].fd, rptrbuf.vpkt.vasd.voice, 9); else - memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + (void)write(vm[i].fd, rptrbuf.vpkt.vasd1.voice, 9); - rec_len = 27; - (void)write(vm[i].fd, &rec_len, 2); - (void)write(vm[i].fd, &recbuf, rec_len); + //rec_len = 27; + //(void)write(vm[i].fd, &rec_len, 2); + //(void)write(vm[i].fd, &recbuf, rec_len); if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { vm[i].streamid = 0; @@ -2295,10 +2287,11 @@ void CQnetGateway::APRSBeaconThread() return; } -void CQnetGateway::PlayFileThread(char *file) +void CQnetGateway::PlayFileThread(SECHO &edata) { SDSTR dstr; - SDSVT dsvt; + const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; + const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; struct sigaction act; act.sa_handler = sigCatch; @@ -2317,108 +2310,127 @@ void CQnetGateway::PlayFileThread(char *file) return; } - printf("File to playback:[%s]\n", file); + printf("File to playback:[%s]\n", edata.file); - FILE *fp = fopen(file, "rb"); - if (!fp) { - printf("Failed to open file %s\n", file); + struct stat sbuf; + if (stat(edata.file, &sbuf)) { + fprintf(stderr, "Can't stat %s\n", edata.file); return; } - size_t nread = fread(dstr.pkt_id, 10, 1, fp); - if (nread != 1) { - printf("Cant read first 10 bytes in %s\n", file); - fclose(fp); + if (sbuf.st_size % 9) + printf("Warning %s file size is %ld (not a multiple of 9)!\n", edata.file, sbuf.st_size); + int ambeblocks = (int)sbuf.st_size / 9; + + FILE *fp = fopen(edata.file, "rb"); + if (!fp) { + fprintf(stderr, "Failed to open file %s\n", edata.file); return; } - if (memcmp(dstr.pkt_id, "DVTOOL", 6) != 0) { - printf("DVTOOL keyword not found in %s\n", file); - fclose(fp); + int i = edata.header.hdr.rpt1[7] - 'A'; + if (i<0 || i>2) { + fprintf(stderr, "unknown module suffix '%s'\n", edata.header.hdr.rpt1); return; } sleep(play_wait); - while (keep_running) { - unsigned short rlen; - nread = fread(&rlen, 2, 1, fp); - if (nread != 1) - break; - if ((rlen != 56) && (rlen != 27)) { - printf("Expected 56 bytes or 27 bytes, found %d\n", rlen); - break; - } - nread = fread(dsvt.title, rlen, 1, fp); + // reformat the header and send it + memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x00; + dstr.remaining = 0x30; + dstr.vpkt.icm_id = 0x20; + dstr.vpkt.dst_rptr_id = edata.header.flagb[0]; + dstr.vpkt.snd_rptr_id = edata.header.flagb[1]; + dstr.vpkt.snd_term_id = edata.header.flagb[2]; + dstr.vpkt.streamid = edata.header.streamid; + dstr.vpkt.ctrl = 0x80u; + memcpy(dstr.vpkt.hdr.flag, edata.header.hdr.flag, 3); + memcpy(dstr.vpkt.hdr.r1, edata.header.hdr.rpt1, 8); + memcpy(dstr.vpkt.hdr.r2, edata.header.hdr.rpt2, 8); + memcpy(dstr.vpkt.hdr.ur, "CQCQCQ ", 8); + memcpy(dstr.vpkt.hdr.my, edata.header.hdr.mycall, 8); + memcpy(dstr.vpkt.hdr.nm, edata.header.hdr.sfx, 4); + calcPFCS(dstr.pkt_id, 58); + + sendto(srv_sock, dstr.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + dstr.remaining = 0x13U; + + for (i=0; i2) { - printf("found a bad module destination for this file!\n"); - fclose(fp); - return; - } - - memcpy(dstr.pkt_id, "DSTR", 4); - dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); - dstr.flag[0] = 0x73; - dstr.flag[1] = 0x12; - dstr.flag[2] = 0x00; - dstr.remaining = 0x30; - dstr.vpkt.icm_id = 0x20; - //memcpy(&dstr.vpkt.dst_rptr_id, dsvt.flagb, 47); - dstr.vpkt.dst_rptr_id = dsvt.flagb[0]; - dstr.vpkt.snd_rptr_id = dsvt.flagb[1]; - dstr.vpkt.snd_term_id = dsvt.flagb[2]; - dstr.vpkt.streamid = dsvt.streamid; - dstr.vpkt.ctrl = dsvt.ctrl; - memcpy(dstr.vpkt.hdr.flag, dsvt.hdr.flag, 3); - if (is_icom) { - memcpy(dstr.vpkt.hdr.r1, dsvt.hdr.rpt2, 8); - memcpy(dstr.vpkt.hdr.r2, dsvt.hdr.rpt1, 8); - } else { - memcpy(dstr.vpkt.hdr.r1, dsvt.hdr.rpt1, 8); - memcpy(dstr.vpkt.hdr.r2, dsvt.hdr.rpt2, 8); - } - memcpy(dstr.vpkt.hdr.ur, dsvt.hdr.urcall, 8); - memcpy(dstr.vpkt.hdr.my, dsvt.hdr.mycall, 8); - memcpy(dstr.vpkt.hdr.nm, dsvt.hdr.sfx, 4); - memcpy(dstr.vpkt.hdr.pfcs, dsvt.hdr.pfcs, 2); - - /* We did not change anything */ - // calcPFCS(rptr_buf, 58); + dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + dstr.vpkt.ctrl = (unsigned char)(i % 21); + if (0x0U == dstr.vpkt.ctrl) { + memcpy(dstr.vpkt.vasd.text, sdsync, 3); } else { - dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); - dstr.remaining = 0x13; - memcpy(&dstr.vpkt.dst_rptr_id, dsvt.flagb, 18); + switch (i) { + case 1: + dstr.vpkt.vasd.text[0] = '@' ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[0] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[1] ^ 0x93; + break; + case 2: + dstr.vpkt.vasd.text[0] = edata.message[2] ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[3] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[4] ^ 0x93; + break; + case 3: + dstr.vpkt.vasd.text[0] = 'A' ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[5] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[6] ^ 0x93; + break; + case 4: + dstr.vpkt.vasd.text[0] = edata.message[7] ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[8] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[9] ^ 0x93; + break; + case 5: + dstr.vpkt.vasd.text[0] = 'B' ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[10] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[11] ^ 0x93; + break; + case 6: + dstr.vpkt.vasd.text[0] = edata.message[12] ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[13] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[14] ^ 0x93; + break; + case 7: + dstr.vpkt.vasd.text[0] = 'C' ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[15] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[16] ^ 0x93; + break; + case 8: + dstr.vpkt.vasd.text[0] = edata.message[17] ^ 0x70; + dstr.vpkt.vasd.text[1] = edata.message[18] ^ 0x4f; + dstr.vpkt.vasd.text[2] = edata.message[19] ^ 0x93; + break; + default: + memcpy(dstr.vpkt.vasd.text, sdsilence, 3); + break; + } } + if (i+1 == ambeblocks) + dstr.vpkt.ctrl |= 0x40U; - sendto(srv_sock, dstr.pkt_id, rlen+2, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + sendto(srv_sock, dstr.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); std::this_thread::sleep_for(std::chrono::milliseconds(play_delay)); } } fclose(fp); - if (!strstr(file, "voicemail.dat")) - unlink(file); printf("Finished playing\n"); + // if it's an echo file, delete it! + if (strstr(edata.file, "echotest.dat")) { + unlink(edata.file); + edata.file[0] = edata.message[0] = '\0'; + } return; } @@ -2739,8 +2751,7 @@ void CQnetGateway::gps_send(short int rptr_idx) return; } if (memcmp(band_txt[rptr_idx].gpid, band_txt[rptr_idx].lh_mycall, CALL_SIZE) != 0) { - printf("MYCALL [%s] does not match first 8 characters of GPS ID [%.8s]\n", - band_txt[rptr_idx].lh_mycall, band_txt[rptr_idx].gpid); + printf("MYCALL [%s] does not match first 8 characters of GPS ID [%.8s]\n", band_txt[rptr_idx].lh_mycall, band_txt[rptr_idx].gpid); band_txt[rptr_idx].gprmc[0] = '\0'; band_txt[rptr_idx].gpid[0] = '\0'; return; diff --git a/QnetGateway.h b/QnetGateway.h index 00c0a13..4cc4d52 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -17,6 +17,7 @@ */ #include +#include "QnetTypeDefs.h" #include "aprs.h" @@ -31,6 +32,8 @@ typedef struct echo_tag { time_t last_time; unsigned short streamid; int fd; + char message[24]; + SDSVT header; char file[FILENAME_MAX + 1]; } SECHO; @@ -154,7 +157,7 @@ private: void GetIRCDataThread(); int get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); bool get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); - void PlayFileThread(char *file); + void PlayFileThread(SECHO &edata); void compute_aprs_hash(); void APRSBeaconThread(); void ProcessTimeouts(); From 7715f5dcc6c33cadf2cc0fecd39fbc6e8b8634db Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 9 Oct 2018 07:54:01 -0700 Subject: [PATCH 115/553] segv fix --- QnetGateway.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 1764a7c..a242346 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2328,8 +2328,8 @@ void CQnetGateway::PlayFileThread(SECHO &edata) return; } - int i = edata.header.hdr.rpt1[7] - 'A'; - if (i<0 || i>2) { + int mod = edata.header.hdr.rpt1[7] - 'A'; + if (mod<0 || mod>2) { fprintf(stderr, "unknown module suffix '%s'\n", edata.header.hdr.rpt1); return; } @@ -2338,7 +2338,7 @@ void CQnetGateway::PlayFileThread(SECHO &edata) // reformat the header and send it memcpy(dstr.pkt_id, "DSTR", 4); - dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[mod].G2_COUNTER++); dstr.flag[0] = 0x73; dstr.flag[1] = 0x12; dstr.flag[2] = 0x00; @@ -2357,15 +2357,15 @@ void CQnetGateway::PlayFileThread(SECHO &edata) memcpy(dstr.vpkt.hdr.nm, edata.header.hdr.sfx, 4); calcPFCS(dstr.pkt_id, 58); - sendto(srv_sock, dstr.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + sendto(srv_sock, dstr.pkt_id, 58, 0, (struct sockaddr *)&toRptr[mod].band_addr, sizeof(struct sockaddr_in)); dstr.remaining = 0x13U; - for (i=0; i Date: Wed, 10 Oct 2018 15:29:13 -0700 Subject: [PATCH 116/553] reworked audio_notify and AudioNotifyThread --- QnetGateway.h | 9 -- QnetLink.cpp | 285 ++++++++++++++------------------- QnetLink.h | 4 +- QnetTypeDefs.h | 9 ++ announce/already_linked.dat | Bin 3287 -> 1152 bytes announce/already_unlinked.dat | Bin 3519 -> 1368 bytes announce/connected2network.dat | Bin 0 -> 876 bytes announce/failed_link.dat | Bin 0 -> 996 bytes announce/failed_linked.dat | Bin 3229 -> 0 bytes announce/id.dat | Bin 2997 -> 744 bytes announce/linked.dat | Bin 2707 -> 960 bytes announce/notincache.dat | Bin 0 -> 1728 bytes announce/unlinked.dat | Bin 2997 -> 1080 bytes 13 files changed, 131 insertions(+), 176 deletions(-) create mode 100644 announce/connected2network.dat create mode 100644 announce/failed_link.dat delete mode 100644 announce/failed_linked.dat create mode 100644 announce/notincache.dat diff --git a/QnetGateway.h b/QnetGateway.h index 4cc4d52..757a92c 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -28,15 +28,6 @@ using namespace libconfig; #define CALL_SIZE 8 #define MAX_DTMF_BUF 32 -typedef struct echo_tag { - time_t last_time; - unsigned short streamid; - int fd; - char message[24]; - SDSVT header; - char file[FILENAME_MAX + 1]; -} SECHO; - typedef struct to_remote_g2_tag { unsigned short streamid; struct sockaddr_in toDst4; diff --git a/QnetLink.cpp b/QnetLink.cpp index a0689de..6cd1d04 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -164,7 +164,6 @@ void CQnetLink::RptrAckThread(char *arg) char from_mod = arg[0]; char RADIO_ID[21]; memcpy(RADIO_ID, arg + 1, 21); - time_t tnow = 0; unsigned char silence[12] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8, 0x16, 0x29, 0xf5 }; struct sigaction act; @@ -180,8 +179,6 @@ void CQnetLink::RptrAckThread(char *arg) return; } - time(&tnow); - short int streamid_raw = Random.NewStreamID(); sleep(delay_before); @@ -1348,7 +1345,7 @@ void CQnetLink::Process() } else if (0==memcmp(buf + 10, "NAK", 3) && to_remote_g2[i].from_mod==buf[8]) { printf("Link module %c to [%s] %c is rejected\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + sprintf(notify_msg, "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; @@ -2214,7 +2211,7 @@ void CQnetLink::Process() } else if (buf[4]==70 && buf[5]==65 && buf[6]==73 && buf[7]==76) { printf("Login failed to call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + sprintf(notify_msg, "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; @@ -2226,7 +2223,7 @@ void CQnetLink::Process() } else if (buf[4]==66 && buf[5]==85 && buf[6]==83 && buf[7]==89) { printf("Busy or unknown status from call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_linked.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); + sprintf(notify_msg, "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; @@ -2818,7 +2815,7 @@ void CQnetLink::Process() to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_linked.dat_UNLINKED", + sprintf(notify_msg, "%c_failed_link.dat_UNLINKED", to_remote_g2[i].from_mod); audio_notify(notify_msg); @@ -3360,8 +3357,7 @@ void CQnetLink::audio_notify(char *msg) if (!announce) return; - short int i = 0; - static char notify_msg[3][64]; + short int i = -1; if (*msg == 'A') i = 0; @@ -3370,202 +3366,161 @@ void CQnetLink::audio_notify(char *msg) else if (*msg == 'C') i = 2; - strcpy(notify_msg[i], msg); + if (i < 0) { + fprintf(stderr, "Improper module in msg '%s'\n", msg); + return; + } + + SECHO edata; + char *p = strstr(msg, ".dat"); + if (NULL == p) { + fprintf(stderr, "Improper AMBE data file in msg '%s'\n", msg); + return; + } + if ('_' == p[4]) { + std::string message(p+5); + message.resize(20, ' '); + strcpy(edata.message, message.c_str()); + for (int i=0; i<20; i++) { + if ('_' == edata.message[i]) + edata.message[i] = ' '; + } + } else { + strcpy(edata.message, "QnetGateway Message "); + } + p[4] = '\0'; + snprintf(edata.file, FILENAME_MAX, "%s/%s", announce_dir.c_str(), msg+2); + + memcpy(edata.header.title, "DSVT", 4); + edata.header.config = 0x10U; + edata.header.flaga[0] = edata.header.flaga[1] = edata.header.flaga[2] = 0x0U; + edata.header.id = 0x20; + edata.header.streamid = Random.NewStreamID(); + edata.header.ctrl = 0x80U; + edata.header.hdr.flag[0] = edata.header.hdr.flag[1] = edata.header.hdr.flag[2] = 0x0U; + memcpy(edata.header.hdr.rpt1, owner.c_str(), CALL_SIZE); + edata.header.hdr.rpt1[7] = msg[0]; + memcpy(edata.header.hdr.rpt2, owner.c_str(), CALL_SIZE); + edata.header.hdr.rpt2[7] = 'G'; + memcpy(edata.header.hdr.urcall, "CQCQCQ ", CALL_SIZE); + memcpy(edata.header.hdr.mycall, owner.c_str(), CALL_SIZE); + memcpy(edata.header.hdr.sfx, "RPTR", 4); + calcPFCS(edata.header.title, 56); + try { - std::async(std::launch::async, &CQnetLink::AudioNotifyThread, this, notify_msg[i]); + std::async(std::launch::async, &CQnetLink::AudioNotifyThread, this, std::ref(edata)); } catch (const std::exception &e) { printf ("Failed to start AudioNotifyThread(). Exception: %s\n", e.what()); } return; } -void CQnetLink::AudioNotifyThread(char *arg) +void CQnetLink::AudioNotifyThread(SECHO &edata) { - char notify_msg[64]; - - strcpy(notify_msg, (char *)arg); - - unsigned short rlen = 0; - size_t nread = 0; - bool useTEXT = false; - short int TEXT_idx = 0; - char RADIO_ID[21]; - char temp_file[FILENAME_MAX + 1]; - FILE *fp = NULL; - char mod; - char *p = NULL; - u_int16_t streamid_raw = 0; - time_t tnow = 0; struct sigaction act; - - /* example: A_linked.dat_LINKED_TO_XRF005_A */ - /* example: A_unlinked.dat */ - /* example: A_failed_linked.dat */ - act.sa_handler = sigCatch; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; if (sigaction(SIGTERM, &act, 0) != 0) { - printf("sigaction-TERM failed, error=%d\n", errno); + fprintf(stderr, "sigaction-TERM failed, error=%d\n", errno); return; } if (sigaction(SIGINT, &act, 0) != 0) { - printf("sigaction-INT failed, error=%d\n", errno); + fprintf(stderr, "sigaction-INT failed, error=%d\n", errno); return; } - memset(RADIO_ID, ' ', 20); - RADIO_ID[20] = '\0'; - - mod = notify_msg[0]; + char mod = edata.header.hdr.rpt1[7]; if ((mod != 'A') && (mod != 'B') && (mod != 'C')) { - printf("Invalid module %c in %s\n", mod, notify_msg); - return; - } - - p = strstr(notify_msg, ".dat"); - if (!p) { - printf("Incorrect filename in %s\n", notify_msg); + fprintf(stderr, "Invalid module %c in %s\n", mod, edata.file); return; } - if (p[4] == '_') { - useTEXT = true; - memcpy(RADIO_ID, p + 5, (strlen(p + 5) > 20)?20:strlen(p + 5)); - for (TEXT_idx = 0; TEXT_idx < 20; TEXT_idx++) { - RADIO_ID[TEXT_idx] = toupper(RADIO_ID[TEXT_idx]); - if (RADIO_ID[TEXT_idx] == '_') - RADIO_ID[TEXT_idx] = ' '; - } - TEXT_idx = 0; - p[4] = '\0'; - } else - useTEXT = false; - sleep(delay_before); - memset(temp_file, '\0', sizeof(temp_file)); - snprintf(temp_file, FILENAME_MAX, "%s/%s", announce_dir.c_str(), notify_msg + 2); - printf("sending File:[%s], mod:[%c], RADIO_ID=[%s]\n", temp_file, mod, RADIO_ID); + printf("sending File:[%s], mod:[%c], RADIO_ID=[%s]\n", edata.file, mod, edata.message); - fp = fopen(temp_file, "rb"); - if (!fp) { - printf("Failed to open file %s for reading\n", temp_file); + struct stat sbuf; + if (stat(edata.file, &sbuf)) { + fprintf(stderr, "can't stat %s\n", edata.file); return; } - /* stupid DVTOOL + 4 byte num_of_records */ - unsigned char dstar_buf[10]; - nread = fread(dstar_buf, 10, 1, fp); - if (nread != 1) { - printf("Cant read first 10 bytes from %s\n", temp_file); - fclose(fp); - return; - } - if (memcmp(dstar_buf, "DVTOOL", 6) != 0) { - printf("DVTOOL keyword not found in %s\n", temp_file); - fclose(fp); + if (sbuf.st_size % 9) + printf("Warning %s file size is %ld (not a multiple of 9)!\n", edata.file, sbuf.st_size); + int ambeblocks = (int)sbuf.st_size / 9; + + + FILE *fp = fopen(edata.file, "rb"); + if (!fp) { + fprintf(stderr, "Failed to open file %s for reading\n", edata.file); return; } - time(&tnow); + sendto(rptr_sock, edata.header.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); - while (keep_running) { - /* 2 byte length */ - nread = fread(&rlen, 2, 1, fp); - if (nread != 1) - break; + edata.header.config = 0x20U; - if (rlen == 56) - streamid_raw = Random.NewStreamID(); - else if (rlen == 27) - ; - else { - printf("Not 56-byte and not 27-byte in %s\n", temp_file); - break; - } + for (int i=0; i2^O_|& zGrX)#&FPeFm>bjD@@J)vIV-fU#$`^e7sAL0=J8 z1K9~tx~attc%NC+L%d(BmTg*T0IP}54X%v@+6=f%Qapd{u09cEDOajxFW7Q|CHM|^ zP(yu#IUg zJ3u-Ej+lw9xbY>JV{S?v;v&Fp3e?g)cI9`yHafQ%*mC%u0wWa zU5jCx&*B7nmw5Mtc_>|PtOd{W412fliwHnFebp}xaT@;V3k+<&oH>lSfBa({n4vj! zG3c;eofK^H67;kQg?lvM=t8frL>Fq?(0&h!nqQQ-?MXwC+^OH#G0 zh*b@KP`LWi=^d4S7P#x9jx?P}Q-yM+d$#V^-OOj_gN<7h)y_}nwg!S{o21#KHte_vnwbJGvtlnB#;Iq7VZ5VEJ! znCoc)2|EU$J}B^unV;oTOdP<85lJ z=#;3?J)aTG@d-U+znZ{Uid8RW>#>4va=CA(vy#JAqn$>dWrpRFPRtd9jjzX&f`oj# zaWLglo`6|%7>DlJ*ob9|%)tqs;QlYMCFMa!n`hvzsqQtYHFano*p$-pN^{d{69GKP z!I9U5WWNu;9$qiPV$F~L|@20f{W)I zb!L~?Kpl5C)uKpw%nkk5yIuw{RG&;}(VbLr_djsMX{Bf;7b-)@k>~&3H3jw`%;*l+ ziHgu|e0V3ZkS#TA_5fS9#E)!@jijc6mnf9>-jUmf(B36@e*3LaZ*?#@vuAg^mOr7w GgZ~3_Gf)Zu literal 3287 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 diff --git a/announce/connected2network.dat b/announce/connected2network.dat new file mode 100644 index 0000000000000000000000000000000000000000..74277cd1331f4be482502e634c4a985796f82293 GIT binary patch literal 876 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 Tqq=@U(A;-q=dXz2&==uQ0+e&^a1>u!!ki^yA7 zIwW^Sqbs(na8c6>b{Um2u8y0RokrNHXa4)#_rCvppNG#PjgB%x%srm=v-vFCgpd(Z z6U?rY@>wL+K@J`R#U#~>!Tf={b1y(}f-m2iPEF zlfqHBZYubV6Ee5u--FSE)4WXQDAucDI`|B2z2k+G`K^oK^E>Nu#O{|S4Q#PLr&r50 zO*nAvjspQ-qUjfdgJ?1?xy=k6@~+odpM^ zRyIZZ`mCVTmGaKw~{$` z$|2wNbx4@hyH;d^`M(dxsPO_xK3LVpBn>34)Z4@M&~n5zHgB~w3vvMoJ)h!PEq4b; za~6M2BQ*6AIEnKR^P}pq=Sy&b_-4Tuo|R-Atc-7Y!WRf{bHTAzTbgtIin0dqB5ylz z$XOxX2j?u0ZT2OjLwCWe)IGnA6kOCP@XTP-pX;Jupc64zXCKf?G&Et5%K*ecjI0cTW4T3}eZ)MPMg N5ee`5Y?GG&{tpq%bLjv8 literal 2997 zcmaLZeNfYN90%~RjXl7g$9W4(X>Jjc@p3~?Y733ovkqCmkk0UnTNIq*LEIUPB_LXi8c!)vZh<^Bo&*fI5P}6XIgUTP&-m?+-R*Vn@6*29y$=hb zdA#Eo%kw(saFigLfte>E7#pn9Zn5RF=#Z#r1VI8f-$R2Vp1u$yEM#BIkst(#43Can zUw7I_B3ns}RI*>5D&USx^1XLFi#Af8R;?3xs#Tf1%g-Vfl>}%jV(6K*0OC-IT1*gf zlO_Sgqn0^G4SvHj0NN@xlW71F(C+>6F@f_d(40h6QZH*WaKT|oQeJ?{L*(fK%}GWj z&FV{5wql1$e2d(zY{hu%)#tB&&mVQmjc_;Y^btN4*A;v73<#}^; zTJ8oQ(^Me~{M+RKwi7_s&y^LXm+Ld<$^m4ThUey*(l0uW0qF3LPd0nez2oFA05L<^ zc{w>HdM^fa!OWR~9bSR6C8vNS_SYDrM~k4f+8IDxF`9I(r9OgM2q3=PF(&AI zK-=1cbKcVBn2BZp3HsRVoHL^SOA`PR_0QKgkyM0nZ>^5w7kEHnX#=^4^jmVJA?=mK=XT5mllkgV_ac0mLc$kHz1n;ba^D@io(g;2T9w;eP;VJEMGeFyqo!C!sH!U=ZewlDWbRXe-1! zW$zDC=I>0M0EQ(Q4xR0h%(YE-14z~xl9$%JgibpFw5vzYt{og$N`mIJmmQj}@a%KD zod6^$#m1F#$FAb*(65ZDWJzUC%-rq(AW72vM~i>`cE zUTG?Twl(_QY2S>!__i59Lc1%<_~LX{uMj}u6%VyJ>z*j)I)Efep;xDF{JDtF0396jZCFyCav=jiM(rf)qq(EYM|}WfHoQdmj!3{8E&|A!l4$N? zjOWIC0c3Yj)c0+EPxZJ5pu=Y6nS+W8W~&N77Yr@&l~+b`%dK=UOXU%A!C`S|x4$aY z=z)M8OwnC0(^+X!w%b)6!giI1D5d*rNAKIe2d$R0wNK!LEFH{J3awNgJH=)}+Y2Nu m9n5+`qin7gJRwU5v!0}(C2HBnRt}!NT{8S>Mc;TVtNkB9LYCwJ diff --git a/announce/linked.dat b/announce/linked.dat index 21047f80812d65c44e30956582a5bf5e82ebfdb5..af56abd6bd41dd017005db67b3fa30a8ccb3cb78 100644 GIT binary patch literal 960 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 literal 0 HcmV?d00001 diff --git a/announce/unlinked.dat b/announce/unlinked.dat index b2151dcc012e5101835d3c1193e88289bfe15362..6f2396f799b69d467ee45db76cff671c44b30491 100644 GIT binary patch literal 1080 zcmW;Le^AnA90zdCsZ(!?BL<|7%dO>)sn`xRx7eX=6U-WOP7xDDQ=GJkOpxn~ZO-O8 zW>P}NU^z?4^qg~OWk{}N_@k+L;S4C*&7IDs5Qo~L%zeK7_qxx0?s=Z~^L(BPZrbp0 z0o8}p*~hEkM*jYF0kv@tPtjAsO*1{OgKyMV)7x@F#W`T>!|h?iOY+hfFw*{E%7nXC z=>;C~e#hox9&>#KCW>$`KETX|Nx>m?N!tR4R#r>EiRM_Ek`~7^fp7FkYUBF`1LMI4 z_8&=9d{gNvxQS|{)~v7@8Ze3T&EM|zm}ltvk-7Z#ssqKOLbOMHV)=B5cJLrLkuZ60 zgHU>M6KuHG(hySA`nUqz{C2&!`n;3z0XXRDRQ(g*x+;J0K$87(-;`>+7<}?Sd(XN} zmC_E`@$A{Ex#aU4C;EP7_& zEpYadL&_`@f1wB8(pY10g8M4;Y|P!kqq@qNTt3()iQ1pw(eYLb_-47UEpy3z`wV2y z?6|auB|lm~_Yy}eY~B^x99r-iZ!vD`u8c(VP3I@XdvNC7Ppsh029eqNTJp~YV8M~y zwY`<$;bmY>9!qgmV!xgVj-ycs$S&fEePE=vY*b?H!tVgDq!yZ)FHg^izz++^Mwcn6 z903Ot>=)e`)x=tG!xJOLwUek~gA<8%u5PkD&H%=nZJ!-^sg;L1Q8kq7>{gbb4r0GT zKn73a)ZqBcaH#Vy`5elmm)DUCtBd_mAJB}Ddw1#|D8Sujhjx|kCAtD`I_f|kPx1R1 z;P#rIbQ_`lb!f))f6sNwwb5J9Tr*LYrKbNco>s*JA9iRM@;tOtk?~wn^4i(X>Jjc@p3~?Y733ovkqCmkk0UnTNIq*LEIUPB_LXi8c!)vZh<^Bo&*fI5P}6XIgUTP&-m?+-R*Vn@6*29y$=hb zdA#Eo%kw(saFigLfte>E7#pn9Zn5RF=#Z#r1VI8f-$R2Vp1u$yEM#BIkst(#43Can zUw7I_B3ns}RI*>5D&USx^1XLFi#Af8R;?3xs#Tf1%g-Vfl>}%jV(6K*0OC-IT1*gf zlO_Sgqn0^G4SvHj0NN@xlW71F(C+>6F@f_d(40h6QZH*WaKT|oQeJ?{L*(fK%}GWj z&FV{5wql1$e2d(zY{hu%)#tB&&mVQmjc_;Y^btN4*A;v73<#}^; zTJ8oQ(^Me~{M+RKwi7_s&y^LXm+Ld<$^m4ThUey*(l0uW0qF3LPd0nez2oFA05L<^ zc{w>HdM^fa!OWR~9bSR6C8vNS_SYDrM~k4f+8IDxF`9I(r9OgM2q3=PF(&AI zK-=1cbKcVBn2BZp3HsRVoHL^SOA`PR_0QKgkyM0nZ>^5w7kEHnX#=^4^jmVJA?=mK=XT5mllkgV_ac0mLc$kHz1n;ba^D@io(g;2T9w;eP;VJEMGeFyqo!C!sH!U=ZewlDWbRXe-1! zW$zDC=I>0M0EQ(Q4xR0h%(YE-14z~xl9$%JgibpFw5vzYt{og$N`mIJmmQj}@a%KD zod6^$#m1F#$FAb*(65ZDWJzUC%-rq(AW72vM~i>`cE zUTG?Twl(_QY2S>!__i59Lc1%<_~LX{uMj}u6%VyJ>z*j)I)Efep;xDF{JDtF0396jZCFyCav=jiM(rf)qq(EYM|}WfHoQdmj!3{8E&|A!l4$N? zjOWIC0c3Yj)c0+EPxZJ5pu=Y6nS+W8W~&N77Yr@&l~+b`%dK=UOXU%A!C`S|x4$aY z=z)M8OwnC0(^+X!w%b)6!giI1D5d*rNAKIe2d$R0wNK!LEFH{J3awNgJH=)}+Y2Nu m9n5+`qin7gJRwU5v!0}(C2HBnRt}!NT{8S>Mc;TVtNkB9LYCwJ From a6c92bac79e84eeda6ceaf6ce39223ef9a60c633 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 10 Oct 2018 19:42:22 -0700 Subject: [PATCH 117/553] updated announcements --- announce/already_linked.dat | Bin 1152 -> 864 bytes announce/already_unlinked.dat | Bin 1368 -> 1026 bytes announce/connected2network.dat | Bin 876 -> 657 bytes announce/failed_link.dat | Bin 996 -> 747 bytes announce/id.dat | Bin 744 -> 558 bytes announce/linked.dat | Bin 960 -> 720 bytes announce/notincache.dat | Bin 1728 -> 1296 bytes announce/unlinked.dat | Bin 1080 -> 810 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/announce/already_linked.dat b/announce/already_linked.dat index 0c42d4853f175d69d24454c34f0c422e2ea37e71..488382e9d00c908b60e177688a52640c9a610c4e 100644 GIT binary patch literal 864 zcmV-m1E2hGVo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P$RcuAyK9CujT@F5+<>5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8w$;S~(8HF?D>!*bVjxu45(E4wt;o`Y9| z?+=rt8P)OZK^Y;H{Zmw{1NmKrEH)0{t(`++d_3>#zhx=`Mg1ls)0pd?-N**2V0`{CfsS*fR5Lw&S^;0t*+@ zSOXpWa2+!y8&Mq&Bji<5OS4mS0?bAJQ&n`wIo{>(9{EZ;5e*fG$oCjA+yh})L>X6) z2LrWj0EwGLAT1@~IZ>}h=Fgv>S~S5R1J8Shg`7PdPl3`U>^0MM#z)t5iGe4lWQbRL q=M52^O_|& zGrX)#&FPeFm>bjD@@J)vIV-fU#$`^e7sAL0=J8 z1K9~tx~attc%NC+L%d(BmTg*T0IP}54X%v@+6=f%Qapd{u09cEDOajxFW7Q|CHM|^ zP(yu#IUg zJ3u-Ej+lw9xbY>JV{S?v;v&Fp3e?g)cI9`yHafQ%*mC%u0wWa zU5jCx&*B7nmw5Mtc_>|PtOd{W412fliwHnFebp}xaT@;V3k+<&oH>lSfBa({n4vj! zG3c;eofK^H67;kQg?lvM=t8frL>Fq?(0&h!nqQQ-?MXwC+^OH#G0 zh*b@KP`LWi=^d4S7P#x9jx?P}Q-yM+d$#V^-OOj_gN<7h)y_}nwg!S{o21#KHte_vnwbJGvtlnB#;Iq7VZ5VEJ! znCoc)2|EU$J}B^unV;oTOdP<85lJ z=#;3?J)aTG@d-U+znZ{Uid8RW>#>4va=CA(vy#JAqn$>dWrpRFPRtd9jjzX&f`oj# zaWLglo`6|%7>DlJ*ob9|%)tqs;QlYMCFMa!n`hvzsqQtYHFano*p$-pN^{d{69GKP z!I9U5WWNu;9$qiPV$F~L|@20f{W)I zb!L~?Kpl5C)uKpw%nkk5yIuw{RG&;}(VbLr_djsMX{Bf;7b-)@k>~&3H3jw`%;*l+ ziHgu|e0V3ZkS#TA_5fS9#E)!@jijc6mnf9>-jUmf(B36@e*3LaZ*?#@vuAg^mOr7w GgZ~3_Gf)Zu diff --git a/announce/already_unlinked.dat b/announce/already_unlinked.dat index e579ccc6c3d0af2ca258db54fbbd8e7e73d79086..f76105c9a3515d21a08f921bfc92529388194889 100644 GIT binary patch literal 1026 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 literal 876 zcmW;KdrXp17zXenlxb;VW@63kVrt2%d4-jVrD>9l8JeZ!9mw233o)crTISuDEeaFO zRt9+~bS!B`$gH$hL3qK-XR~SQbXhAF&x68X&oOIuJ zqSVmZ!>sxaPI~m`|2cTI7(X`iy)!Eld`uI-h$yiy+l+Ra`5GMFL*xKHT9P!hr``2= zA$XW}+HtG)Z3-Sdb3cvhq9Rx1gH?0~@pvWjEdWeNb~6~fF)PJr&Zj3DsV{E61fMt| z#*A8Qk|M#2H5GdP4+1v>yj(sw5Rymwr3DM;U%e45^5Y{`=AE>%nQUq9>*+ z3zujFnCit8>waRWAv1bcdMsspG5Jg?10SB)YZguj|EK;C0Ejt>YtQDj%4AW!|*gBl!9pdJjTX zb|PICJsCV(Zrv&E$Xu+gWIamQpnXat{>pZ*Z^xV=CLj`w4~Z4bpYBH-cmG)JdWmn=27 z_N+%=kJXPei*}Z*@`O3szCJL;SBe#y3haBq6L#|t!g9mC(47i>3;~zTgf9_jCsx~K zs71UI!RD9+%O>wX8I$0#ebc$0WC)cAUd`#xu21AWMgKg0s;HRk#QjNvygD!v6EBtB zTMM4;!+zXfVI#c-9%!LN;SMNUlfjJ0Xp`-NWibjoa*a*JskPs&c^I@JQucElI20up z%fNWLmw88=x2TP9Es;AhK=t9-`0*_WJjXk~o}R6@Y2J%n2l_+K9|?CK)orIhrN4*{ z%QU99ZPLB_^74JO8w=3~xUy%O$41SkvD?pXe-)%%quH){vRKk=R!?1MAyB5T{So%> zZV8*hKd0)Y(TZph(MJecp|x~8kdTlYj2!(AVi8v>N;yAON>Ret6Q2C7)e5@CaKRY`0_UhE7rwf$TJMw4MzS0aKyGf6(+?q zZbo6u)WHcWC;B9KMBTY`;vMj(NXE2@Qic;)Lorgf06&8(q0zw)u#W~>G`HWAb(%$| zQc#C{ha&XcZ%==zKlY@P{!L+lF*?WMwq41QTABFnyZA4O%yl=r#`${(o5h@Gt)s5l zw--#k%_^JZnNI;nmHCLv=~*qGxoAo0k2Kx=WOn33@3w_moSrs6P3w-wJ6TUHkkb1U{u2m#qi5^ySsbtIfute)M~S%m-yUiW%F1; z#^UT~aTNPXGmn*M`JaO)2@!;i@3%bc2WNg`>m~`cF2BLC0lvoE{)^GxU>iL_7hl%= zq6*Bqol$9~3HIvpF0N`xR+7LZV~_`qSKZ_R?tb9@q)=(&sKAoH z+##>QL)XH=S8?wKPKwX}^#>nD(L6c{4fSo{(MG|VRzK>@F|hZJ)fi7h55E&!Eg;F% zw=eNR!Bx00{Eso~>{oE8bEF>EEtfZf)p_=EhSOZ!5ZDKG!sj_hg;zrD-6B^5woc74 zL;mRZ^z`Xg^fD9J>A}6+8a;6kd0We?r`#gSD0@?Upo=pJI#*PUu}Mnj7fx5QD$_XADI*(DC_TI+0J8t(8Qep>`i4%|&9(ezgN~ z%$XJ5=p1uC$2cCL*BFf>PMFp37rH{14#|{g?am|97 zm-PbX8}LsQ(`*as?!O6sQGzmd9P1nxg7tfN>ql1x96;u$pl3ns>~y$u2y#Zp?BC{M zTTTSeFXCz2qb9WOVEcYb{9gBsx@}-Ck!iGOP$7Zfxd49r>yCnPWX7gciG8hQ)bp#r z8F67$^@5mUi6AtG^Ge?uaB9jy*y85o`pPY zr+&PhF;GPZOJ+ZJ&2BC;7J|dO`hE$Wq&gWmw^7k#ReL5s0uKgDCnPBgNms$YM1~Kv z&>J0W@U9PH>}yBP@dp=Hgrj{@V4}yyBWEllc)fGI!#%j5}JhZWDLd{HurE(vg{eB)m&CS|JW4MMp2c8TiX8n;#RURr?225nhUsn`nJn03MVTicW(~ z8DK&M4QeqL^8;nbU|R*5r_|yo(sWo#13_;Mc-ayswnujVZctWzGD!)R^J%*W)86DV zJ6*2J+Z&#GVo(3d!tuKW2?pxX41;SDhb!VG^$fyyQZ)->rBf)6t~+dEnknhe?2MgB zYmkEaCJ|84^%{s{aOVh2*fM>Qanzz=NVQYymJdIIcp6P??RsQ=NE6;yAdb8qOKaoc z(|B6gUxS^FyRS1C#@*SebYe1}2~KXF;QJ#a^Mp*QjT!(s6Dh=(T4x|ZzqYR5yy w(^y?hut~4AgUke2PMdR>LuKq?VP4_(bN2vWjztG2V)0v6V&iy1sn85;P#xe5E&u=k literal 744 zcmW;JeK6Aj7zc1pBk#v9LU}o|P1nh~rA|1cZQd_o8)h52F>Q0+e&^a1>u!!ki^yA7 zIwW^Sqbs(na8c6>b{Um2u8y0RokrNHXa4)#_rCvppNG#PjgB%x%srm=v-vFCgpd(Z z6U?rY@>wL+K@J`R#U#~>!Tf={b1y(}f-m2iPEF zlfqHBZYubV6Ee5u--FSE)4WXQDAucDI`|B2z2k+G`K^oK^E>Nu#O{|S4Q#PLr&r50 zO*nAvjspQ-qUjfdgJ?1?xy=k6@~+odpM^ zRyIZZ`mCVTmGaKw~{$` z$|2wNbx4@hyH;d^`M(dxsPO_xK3LVpBn>34)Z4@M&~n5zHgB~w3vvMoJ)h!PEq4b; za~6M2BQ*6AIEnKR^P}pq=Sy&b_-4Tuo|R-Atc-7Y!WRf{bHTAzTbgtIin0dqB5ylz z$XOxX2j?u0ZT2OjLwCWe)IGnA6kOCP@XTP-pX;Jupc64zXCKf?G&Et5%K*ecjI0cTW4T3}eZ)MPMg N5ee`5Y?GG&{tpq%bLjv8 diff --git a/announce/linked.dat b/announce/linked.dat index af56abd6bd41dd017005db67b3fa30a8ccb3cb78..3c68d8af25d373119f60d06cce7eeeb50faecc88 100644 GIT binary patch literal 720 zcmV;>0x$h=Vo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P$RcuAyK9CujT@F5+<>5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8wSHBA!0-=M-}Xz1Hos2fqR@ zJAUW!J~xku?nABPlAXr~!98wqNt!7UJPn~7U+b4JGNsCaRr8MK(lHIB zGRXUSTAp$H3tK1aSt-!TT>G6y&;ux5T49LGcasND3T<9;x;fZISx^R6EnsO^V@QyX zF3#l&nG=G#Vi^`4I-H#KcrX)C*aM=5m%)_ z3e41Wl_P~kY|bi4Y!O=>a{=1X`P!#Kn>zIk(2?DSRg3f~p3H|~$p?BPK~ktaUdB%) C+h2qL literal 960 zcmW;KdrXsO6b5hfVu zZ^AkB`n)B$p3~1MpB2S6!oC@oe_#C&_%C@rOIV;OiO==TB74-;Ge4Ye>pTUs8861y z8F{rEaMyVARc21h$0E2PLhhozxe$8-_Ix}dU!zo%P~k`6b3f}xRD-#2?ca0lAMC1V z4YJckZ)aAvxP&Z_J&dPqI>K%BF05l%9lrPIyg67|TD5%bp*@$((xBE6zoy9Po8Vi* zS453#eVqrLca9e6L_f67UW$#p(1BXI7A!JoedNg!1l7eUmSGOe%1{4eNC|^O|P1MMD`?Y zhhs;`$`pB*bkEern8ZHc1}}>p!Cql;Y;vcQMrowC&B)9z0 ze)x7J-?Q^ZOJxu2e4Oa>93KfFXAAAyzw>i^b5m}6C;Mk~>Iy{s*$ zlr2-V0r>ov$=q@Crkkdz;^nzxf)}SPaxjzApM0X_%m?D-rQn8+8Imx5_VdX6_-kUO zm5M5Iy()iJ2LtcGngS4?9O%P$Bkf@d9bhc~&h@B_9%WLZrU~s`^HYUy)xEnp3!9A- z&Xg?3dlFjmuYs$|G zJs8#SpghfIaHKUkS_0TX&qtzcN4%zJH^AQe>xWoXQ&~IX^6KSJ6}HGaYF}W2eYav6 z<%;_4dl*n1oQci;Os-7i4uejuT_)`!1bpI5F$foPwL&c!XyhC7CpR#P%0C`5P2z5?>3M{AyikD-L`>+GAb5%}(z+*C@=P)Q zTAA7=m*&!FbXIQ{9!GTI{S^dX-V)9l{fE$4qwD>!Ms9v|)~Eo16S)6(B;MER8~NLz z{onEX*NOk;gi~axT6_Ay-^kb1`N?4IOSISC6X=yyOJ#dFrrOTkRu+_g~$n$w>Fig+F~!AvV*s#1{7# z{_f(VUr0dNb(%-bPIDidN)PH3s9x_-mah7z58$qKoZ@ZgJ<4J$y}7TtZATljnx`W` zQm)s8)x*an5pT}oV=3?U;0Gq~68`#ix8hePcOSty{jwPO=oY0*1ix!)Riq?Qip8-Z zM_`hT>SkPWK@DoL3OgWjKa*<~)4YGnnA3ZHgJH$W$xsk92{~7RlJ6qhw^SMoB_=FT z?Vt!re`|1&zo(nclgvi{M9=Qiu4#4X$QG|sga9O1mCrL$Y|J9^FhX+A_S z97@Em#oxs@%Uvrm|6&@ww$V#7_W^z`v~83 s)L15(USmt$j17Sk*yxWQtT^s5-OnPo&*sI{*Lx literal 1728 zcmZY9do+}J7zglN%Vok5X4Ir9Dwh(P7}Yd7V}?YOu~f@2Rx_lXa*0HXoJkS6YesZj zI&xbTcC1iD^9^Sr<3_kQ1(>t)l}$Rx|_+l;P!TtrGJA{%Cb>54c4|FGqk?rv9Tbt8Flhp8JXh+PULNZ{ z560uhL@8!do;$$iu5~wSsivz%;5ye_j%d7&PXVXDit-=Sth%KRju9?Q9pk+`tp!dd z4Uc|y2oj_7JQOa>yHzxd(ZHz}C%^Q~48B3nP0*QU2+XgEpM%SAm@Ss-d{qb7JsJDx zeP6xjxD3?!1L=2f1je_ZvqeU%R9V=U{89iuQgr`b^6YvQw3m4M_M`aYJ(YBDpCPt> zI-}j~5cs`Lmt<8Fj(=rDcIzJ)!MXa zPTXE_Tb@+Yy>4ZK2Ux#W`$TPjBN3fHSs=0E$MCanfbsiQn7{Uf(lx;8$5-;trdqR# zQQb9d+ z^Y{YMocIUSf5h>N-4ZO5zJTe45&pdjrU_}E(`QBCrJL-)a< z=9?HAINR|%;N?-AGtR)f1nspvqSl>iwIor6-5(DwJpKfotb}{ zIml}RpxI;#L){O9ajk15)#Rv*5-@SD$g)dt;7khat`SnbXTyN;2b8@ErGmwi9tzk< zu*p;bQM`?ASOYFp?1>1UWf)+=gx1blw=5@HbccFR6Dpk|Y@1Jm&11_f0&47XQTBKW zye07rD_Re%;hRQE)%0W8gX=zYhGqDNZtVuAYENHCT5gX=IX8E2*s@fqr6&ZJWW;;b z>gI`5!2}EZ+C>FbY&%#cEAWQdGDRw7pcE{ zqklz0&DhT1*M6-7;4%x|)&)F^R|m$Evb|?}81;IvJCW4E4%%jAErxo<;$J3vgG{t0 z!9Tp4w0LgJOZ5cz`bC{f6_?yX|1OV-G}X$U4tWOnl5+El#9V99UGPVCm7AsAI}17R zPkocp0{%O{BjAC#$$o5flHfL&6~<5ZJut5$0gHu$SG+KcOq6Fv&8gy6mF0ow;7|B< yM^!|E(Pc2FW50?L|3rQjn9i~0EBP6An}MhERb8T`it^BW8r`OYr!;+LH~4Q(TU9pz diff --git a/announce/unlinked.dat b/announce/unlinked.dat index 6f2396f799b69d467ee45db76cff671c44b30491..f69f734d9a933414c8d47bd7ec63470b0553acc9 100644 GIT binary patch literal 810 zcmV+_1J(R+Vo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P$RcuAyK9CujT@F5+<>5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8wZnzq!Pv%w~BnVt`3a}=bT^S%1-0dXr| zeD=*NOR5@kL|k244+`e8+a!yOb4$uPxf8o*^oxDB_I#)G=e4<<$1t%cyXus!+3%+G zYy)<%N~M0Tv;7>kge6PsqNWS?HVN+WvB(ziXJ< zHZjc*{}a6lC|SFsqB@nc^KFHR&fql%k40q)#i&_ILB@b!!eh8u?h9KF8J?gM(D5oC zTUA5Gc+??Rb^}<>K`Gv;buY&#kN}G-$>tGLTtorBz#SiRLTyHfeNg$k$O7;zywcL0*t?UX-(+VKvk(f8Nx7l@M`ANos6s+uT8t4*&oF literal 1080 zcmW;Le^AnA90zdCsZ(!?BL<|7%dO>)sn`xRx7eX=6U-WOP7xDDQ=GJkOpxn~ZO-O8 zW>P}NU^z?4^qg~OWk{}N_@k+L;S4C*&7IDs5Qo~L%zeK7_qxx0?s=Z~^L(BPZrbp0 z0o8}p*~hEkM*jYF0kv@tPtjAsO*1{OgKyMV)7x@F#W`T>!|h?iOY+hfFw*{E%7nXC z=>;C~e#hox9&>#KCW>$`KETX|Nx>m?N!tR4R#r>EiRM_Ek`~7^fp7FkYUBF`1LMI4 z_8&=9d{gNvxQS|{)~v7@8Ze3T&EM|zm}ltvk-7Z#ssqKOLbOMHV)=B5cJLrLkuZ60 zgHU>M6KuHG(hySA`nUqz{C2&!`n;3z0XXRDRQ(g*x+;J0K$87(-;`>+7<}?Sd(XN} zmC_E`@$A{Ex#aU4C;EP7_& zEpYadL&_`@f1wB8(pY10g8M4;Y|P!kqq@qNTt3()iQ1pw(eYLb_-47UEpy3z`wV2y z?6|auB|lm~_Yy}eY~B^x99r-iZ!vD`u8c(VP3I@XdvNC7Ppsh029eqNTJp~YV8M~y zwY`<$;bmY>9!qgmV!xgVj-ycs$S&fEePE=vY*b?H!tVgDq!yZ)FHg^izz++^Mwcn6 z903Ot>=)e`)x=tG!xJOLwUek~gA<8%u5PkD&H%=nZJ!-^sg;L1Q8kq7>{gbb4r0GT zKn73a)ZqBcaH#Vy`5elmm)DUCtBd_mAJB}Ddw1#|D8Sujhjx|kCAtD`I_f|kPx1R1 z;P#rIbQ_`lb!f))f6sNwwb5J9Tr*LYrKbNco>s*JA9iRM@;tOtk?~wn^4i Date: Wed, 10 Oct 2018 21:29:44 -0700 Subject: [PATCH 118/553] byte bit-reverse --- announce/already_linked.dat | Bin 864 -> 864 bytes announce/already_unlinked.dat | Bin 1026 -> 1026 bytes announce/connected2network.dat | Bin 657 -> 657 bytes announce/failed_link.dat | Bin 747 -> 747 bytes announce/id.dat | Bin 558 -> 558 bytes announce/linked.dat | Bin 720 -> 720 bytes announce/notincache.dat | Bin 1296 -> 1296 bytes announce/unlinked.dat | Bin 810 -> 810 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/announce/already_linked.dat b/announce/already_linked.dat index 488382e9d00c908b60e177688a52640c9a610c4e..432de5920c573f9f0c6dc2be6debb98f69907bb6 100644 GIT binary patch literal 864 zcmV-m1E2g3MiL_<27F-n4n~wS!3KQsybeYZD$twyAskq6{kJb>?3;9gse<$tE5r4kL5wXu zaE!g$YaD#&$>uM~d}Dlmx7t!U`1)ZdUvIR3z?51xrLOWO_=d^ZaW`pZWj(s=7;`(n z80Lxpy*v4_*v5sQ4(Z(CpxMQbQsVGk^Wop%A+AuJ9Cz(4^O;j7i63|MZ~K`-rRdu0 zH}4>z43C`{ePzwQ&|lTf7+dG~wdj&}sUFdtel`8i5b&WJ#o(dj;{io(7x`uFv|s*=hr)9RP^zKWLaF5S~P+jOXo zyP1HBoQM<6PPAG!`<_&CYycI^`J>k0`^uMJ3mM84ZRf}g?`Ny-6leb%&inUeuNLR@ zC`_{KfycA%yr`RvYTN&@ZkyZ0Tnz?_otj~s0#Fs+&$KqQDe zbv4$q&u{-`c6AQUlFENh>_&u(%QnnK{m>z0>HZWwcB1U|J+8@v-aB`**u8gYoj+~Ki*&x8%)pEZO5H?K+x#R z8o;}I9W8&*Bk%8ppCK2l1 q=dhH3ZH7A#`tlOCAA*8I(>sOyC?24eLj}8Z&D?I|6hrftXZ4`+?AI^= literal 864 zcmV-m1E2hGVo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P$RcuAyK9CujT@F5+<>5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8w$;S~(8HF?D>!*bVjxu45(E4wt;o`Y9| z?+=rt8P)OZK^Y;H{Zmw{1NmKrEH)0{t(`++d_3>#zhx=`Mg1ls)0pd?-N**2V0`{CfsS*fR5Lw&S^;0t*+@ zSOXpWa2+!y8&Mq&Bji<5OS4mS0?bAJQ&n`wIo{>(9{EZ;5e*fG$oCjA+yh})L>X6) z2LrWj0EwGLAT1@~IZ>}h=Fgv>S~S5R1J8Shg`7PdPl3`U>^0MM#z)t5iGe4lWQbRL q=M5$twyAskq6{kJb>?3;9gse<$tE5r4kL5wXu zaE!g$YaD#&$>uM~d}Dlmx7t!U`1)ZdUvIR3z?51xrLOWO_=d^ZaW`pZWj(s=7;`(n z80Lxpy*v4_*v5sQ4(Z(CpxMQbQsVGk^Wop%A+AuJ9Cz(4^O;j7i63|MZ~K`-rRdu0 zH}4>z43C`{ePzwQ&|lTf7+dG~wdj&}sUFdtel`8i5b&WJ#o(dj;{io(7x`uFv|s*=hr)9RP^zKWLaF5S~P+jOXo zyP1HBoQM<6PPAG!`<_&CYycI^`J>k0`^uMJ3mM84ZRf}g?`Ny-6leb%&inUeuNLR@ zC`_{KfycA%yr`RvYTN&@ZkyZ0Tnz^PT#^vV0uV!@zdZZ?3_T?_=--+mH*b|y|x zus`{@xGa5%u8BDPLTzn1$j!jI+JreD`Bz7U;XV~r6G=yh+A<=XjGnG zimY37Ub$9=FH8Nu`N0WMnrauFUw@Onfh);-?$=aGONM{&-R|%jcpgsmv}VPA@ecpL z5^NQ@xbbaHu=q_;sax}PjJ(Lq*^D0Jbe>{T{{@kLeeJ)!xgemfRZTE&X`mL0ijYYL zSz$6^cbXU9*Bkjp$MMUQ`Ic?$ahTO31_~OU%S$@;`i2;b1$*SnvXcme<5Cv?^}JhQ z21~_Qs->FlumT7s(F)dW%(<%4;~p>6s>fL_Y6lJioyD<2l!%v5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8wVYyX(+5q;{;E;x|5p|yZZH(%<9rww_Qp>-Rsr7UA?&rOVL6( zpQJTja?jBVWEziwxhh{-0TotMvtRlPA6I~{)Ct`4wUh=0$;XFS+xMP4BTEgBJclxH zJwrktw|a&iv3)}U%{Tdn`Ts?;2u$-VP+EL)QO#w4>v3Kl-i&Idb5FAV@fKpC09B3h zaFMihMEuE0v9FshkOU3SBy+J@MQo{7A$CBU`AG$G@cJhy%ALKk(M~1DSit$^8>|h< zOCk!o_SpD_KV7~n(Ohox|CgKoWpPgK!8Z{a*EfHq)P{`{`oFh&rG#;b5!#MY7AkrA zQ8n5wdYx)v^wPimpM#K!Syt$|`u@p%fz*@Uck3!r(qaA&yLS&*9(Z!EEoP&Bk8uC} zNH(aQ9gnth4Ii?KmD)4-S>+{LH=?C{$=J#K1b~-#Z)O?G(RPTO@ewVVAo5GR+P9EniDq=aJt; z+4W|?Am&$H97{IAD#YeBL0wy|>~VaS(@hl#GTXPRf|5xEs`BgAYTotL9J}!Sn?|b? z(R!I%?~JSRRT}ebm38jT$~2afb$sA?`|Hq(a0o#trrq({c*9-aG!Ey=ci&ZNmx|QG zf|S`HzFY24eY=oXCH%Gmq~5+1%Pd~+n1@1w(nBrF@%yCe?~>PF$mE~Kd)w5eQuw`UNz%*V$feMEe zaG`j>2N52F4Mc%i)+VkNxp~)7SPmP)$z2iVqG3!Fw>pY*w^a?2+>77nG#OkN)6*vc z{eZW4Raezaypg|6O_BoH`UP1WATa0$~ba4^}>DX!N_~=z66+OjngJ` z&Dn+vVM~Lo+J5U~b{Sj8`#PU%tF%3s-g)*%_d0tmoS(IQj$~S(gS)5Io``ZFt$Aq# zgQJh%0^OUn1%hXuJes4z;2BUBLiHQ``p-vwco(EO6&j?)$1%=}{}g zK`EOAecE>lzTFAxq(5yyC3}4+(=>Yb8R0@fOTx6%kKZL%_etye6DRp&-rFnVZPB0K zOfCOw#%7dPcCjB3;Cz5O5OMQ4jWtCAQ=6Zi0q4U6&@YMwo0ZC=K}H8Kv#4BNMM&BP r>-z0R*o1gYfff-gYv15~f|8+|{aOb$IYEbgox!)&AVA$93QSI2trI@E diff --git a/announce/failed_link.dat b/announce/failed_link.dat index cad9d3796e891ab18ab77d7fd4af1b2e59a4f2ce..4704abb42f07a2a9bef9336c03866b977cd63359 100644 GIT binary patch literal 747 zcmV+qau;@Yl%h~CgOsvE{nMNj&Lh>aO{=nnBBO+iH$O);^H%uXxt9z zb!hz^s8g<`JGSMS|8{ejvsP*BsoZCiUh!f7#MY3SXkCKUm96`=o)|4eN^j1%94Msd z(*qt|>7L>_?G>>Ow29n|0k`StG49=rTr0b z(=27}woAR=Pfxxr*wBmM9Zlw0<6^Vrjob5f{-`Co#hZ0rO&Uu!YVx`kgbHQ#zld-5 zc97Xa{N-1ri%}Mci{cPkg{>|;2?+_Jb709!ot6gTwMX2t;%%>A_2<3~CWX z#fP$@Lm&}flLKh+FT@&ieAQY4j#^?KS~*ymoY?qcnlPE02HPB@e(<>1CV5#b#olp* zTCv51kkQ$DwT>f-hco0p^X|OA#5zjhnCk0|B0|fa!u6h68uws9}4kK;6CnKnP#tI_JsAQwe zc4CGzD}#{KOkGp_92W1rsO*`qn;wZ(Ot z?dUSSvsBq9neu?*sh<(kmzuQsofeXp@hrQ)CT=If_idq?IeBdSve$9r+?u-CE5DoE zI^I+od!&n8-YW|aahZwQUzpLhwu9@2=h-|_2dbeDYKqI6vc)+;*u);>1PcQ@3Z;%C d*JskPs&c^I@JQucElI20up z%fNWLmw88=x2TP9Es;AhK=t9-`0*_WJjXk~o}R6@Y2J%n2l_+K9|?CK)orIhrN4*{ z%QU99ZPLB_^74JO8w=3~xUy%O$41SkvD?pXe-)%%quH){vRKk=R!?1MAyB5T{So%> zZV8*hKd0)Y(TZph(MJecp|x~8kdTlYj2!(AVi8v>N;yAON>Ret6Q2C7)e5@CaKRY`0_UhE7rwf$TJMw4MzS0aKyGf6(+?q zZbo6u)WHcWC;B9KMBTY`;vMj(NXE2@Qic;)Lorgf06&8(q0zw)u#W~>G`HWAb(%$| zQc#C{ha&XcZ%==zKlY@P{!L+lF*?WMwq41QTABFnyZA4O%yl=r#`${(o5h@Gt)s5l zw--#k%_^JZnNI;nmHCLv=~*qGxoAo0k2Kx=WOn33@3w_moSrs6P3w-wJ6TUHkkb1U{u2m#qi zAtIcg_dOnPXGc2Ve_O8`Jx5uT$)AX+s_G#Pt^U z_Bgh>cPr8K^~QQPhD9!z6zP=MqT=-Zm><(r+4#t%s^8!$haS(KOaH!-=A z)(JshWQYojuUHYr4(AXu8%(~5jw?lm60OQtY4H3(9$2zAw_YZ`63Bb%1aUoh($>cZ z%N|-AC#u#3B=M@;Ma_zQM6}vQTeF94)

Bsi(?A_?S^?#O^ zXcHn>s?tdKH)-e{EnJkybj`Sh3t<%!-fxU5#eOBU1>M3R^!5B w%Nn{e4U+Y(!80Hla@o!q!lpNdhI)r@&hG&FaiZYlMvvNRM#mmPl?yO73SOQce*gdg literal 558 zcmV+}0@3|&Vo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P!%ctIp-JhMri0dN|?7^bJR zcxDOQ1A#U{JrAaJl_)U$WFP#4Xh=c@DXUq*7I@nBFRmx`Tpp=Hgrj{@V4}yyBWEllc)fGI!#%j5}JhZWDLd{HurE(vg{eB)m&CS|JW4MMp2c8TiX8n;#RURr?225nhUsn`nJn03MVTicW(~ z8DK&M4QeqL^8;nbU|R*5r_|yo(sWo#13_;Mc-ayswnujVZctWzGD!)R^J%*W)86DV zJ6*2J+Z&#GVo(3d!tuKW2?pxX41;SDhb!VG^$fyyQZ)->rBf)6t~+dEnknhe?2MgB zYmkEaCJ|84^%{s{aOVh2*fM>Qanzz=NVQYymJdIIcp6P??RsQ=NE6;yAdb8qOKaoc z(|B6gUxS^FyRS1C#@*SebYe1}2~KXF;QJ#a^Mp*QjT!(s6Dh=(T4x|ZzqYR5yy w(^y?hut~4AgUke2PMdR>LuKq?VP4_(bN2vWjztG2V)0v6V&iy1sn85;P#xe5E&u=k diff --git a/announce/linked.dat b/announce/linked.dat index 3c68d8af25d373119f60d06cce7eeeb50faecc88..85742797fbda1474214547f9ada2efb5e2d3a4b1 100644 GIT binary patch literal 720 zcmV;>0x$gzMiL_<27F-n4n~wS!3KQsybeYZD$twyAskq6{kJb>?3;9gse<$tE5r4kL5wXu zaE!g$YaD#&$>uM~d}Dlmx7t!U`1)ZdUvIR3z?51xrLOWO_=d^ZaW`pZWj(s=7;`(n z80Lxpy*v4_*v5sQ4(Z(CpxMQbQsVGk^Wop%A+AuJ9Cz(4^O;j7i63|MZ~K`-rRdu0 zH}4>z43C`{ePzwQ&|lTf7+dG~wdj&}sUFdtel`8i5b&WJ#o(dj;{io(7x`uFv|s*=hr)9RP^zKWLaF5S~P+jOXo zyP1HBoQM<6PPAG!`<_&CYycI^`J>k0`^uMJ3mM84ZRf}g?`Ny-6leb%&inUeuNLR@ zC`_{KfycA%yr`RvYTN&@ZkyZ0Tnz@lP%?|`Rt1BH?-R1(8Xj#%yR&hsTS^r^c4~{z zAY=NMJ{7K-@hF>1+~mTz&+rurY6s{GaI#awP^s9g*=?$pM0tEq=P1tLy=%A4;Qc`K z+0x$h=Vo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P$RcuAyK9CujT@F5+<>5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8wSHBA!0-=M-}Xz1Hos2fqR@ zJAUW!J~xku?nABPlAXr~!98wqNt!7UJPn~7U+b4JGNsCaRr8MK(lHIB zGRXUSTAp$H3tK1aSt-!TT>G6y&;ux5T49LGcasND3T<9;x;fZISx^R6EnsO^V@QyX zF3#l&nG=G#Vi^`4I-H#KcrX)C*aM=5m%)_ z3e41Wl_P~kY|bi4Y!O=>a{=1X`P!#Kn>zIk(2?DSRg3f~p3H|~$p?BPK~ktaUdB%) C+h2qL diff --git a/announce/notincache.dat b/announce/notincache.dat index c1b6c1bed76d68870f4f6407a35d3688097e5100..bd08f4850be4e8e7918cab22e134bb2733cdfba2 100644 GIT binary patch literal 1296 zcmYk4c~sH|0LL39qBdSpkI*qSJW45#)E1dn_3&917N%C1KFm9*Fgpw{A~VCn3sAu; zopPvn<22J}B9w`lQon2(SfNv!=kkngZLR(HeV^~=``;Ieg_)XyDM5=+Y_@#|m@?A} z#lp~E$ivX=++ezPQBV#M98|^Nk>au-qIz>u`2&w_TG8CbHfEqBG6 z?O|E{M9DD>-kF9!Mno-gVnFCrt|x2WIM#4HJtMAA%nn0a_B5r`Pa3YBibr|9MXwol zG12NZ05pEay>TUc>bkU;8`$(j`E<{ijN?&$fm|i9c*qSb%&Y7SBl2AQ9u3*7+(ykT z;`o@!=YqQnY|&$a6o8Orh?_5mv{KaVK+E{aJJvN>tmFjE6tcGAyO7zzXGdk#ucq4s zTech)pPq)c5m#>lY);%+0vG?4F#PZ2AJfF0yT4R2?Xs z6#noF8*t2xbGd?74BEF+qT~KsOsGlv9mi;9e{p9{x)N)mTNTT=I4y9@{ zDl+e}CeluDC9fF%7M-92Ye&XVeTFyF$Q=EiZA?-!NXyEtzMgapw835`6CM)A1*hl( z8#t_z9+C)i=Zsf6U37lmMoDwo{R7hE#%m2(2SN2`Ly(a)9TRODRGmUxxA}O4l#`Y; zU^v2pI14R*zuYs)vO;DQOht>VHRqeyzTdza8-Z z9ld`Ymv2re3|*XbxlZ(rd|iF?$a8Zyy2cgPjdM&wp}RssAPM-is^FAWZy|)1aR3!t zVDn_V1KL@(dg58Z>58D)cc8xArSXRH++K?#qa2 z?AKYm`9Rxu`#n$r?B!*Uz77aadb{qHi>t?$-IVzsje8H?f(2v5N2QC+EFx@)TM)OK zjlU=lptu_}2#p5``k2)#mK8<;=l8bEUnN!cg^cW5W+0O3O(808A?}yjijTr!Z(7$3s*>L*RIYD8#ET2 zi{km?&Uz-+)d(l!Iv=}6kCk#83~I3PX69%J4Jp1j!hj&^+MBJf%K@V+gB(n&ZEBc< zHkTf+cB?dtl)I(pzu)PC`qhFs;~aLGbVGq}SwJe!1&SrgH8E$&qI!a_RVw7fz^mR- zO9Yi8zti7T(Z~FL+vTm<6QaWtb4xM|jQg$@F3JVml}%U?KUd6m{@{q8PHee*U%cb+ sG#XQ$$c$;7?gL(zO{thAK%ZhDss~pFiR?%kG@m7^U@^)*+?Abw0>JNyH~;_u literal 1296 zcmYk4eK^wz0LSxM!!nPssihT_w=`O|mDze`o8+y`Q601Gv{~y`SmdQco~4K|Fa0KZ zy!6Q1sc>^b5m}6C;Mk~>Iy{s*$ zlr2-V0r>ov$=q@Crkkdz;^nzxf)}SPaxjzApM0X_%m?D-rQn8+8Imx5_VdX6_-kUO zm5M5Iy()iJ2LtcGngS4?9O%P$Bkf@d9bhc~&h@B_9%WLZrU~s`^HYUy)xEnp3!9A- z&Xg?3dlFjmuYs$|G zJs8#SpghfIaHKUkS_0TX&qtzcN4%zJH^AQe>xWoXQ&~IX^6KSJ6}HGaYF}W2eYav6 z<%;_4dl*n1oQci;Os-7i4uejuT_)`!1bpI5F$foPwL&c!XyhC7CpR#P%0C`5P2z5?>3M{AyikD-L`>+GAb5%}(z+*C@=P)Q zTAA7=m*&!FbXIQ{9!GTI{S^dX-V)9l{fE$4qwD>!Ms9v|)~Eo16S)6(B;MER8~NLz z{onEX*NOk;gi~axT6_Ay-^kb1`N?4IOSISC6X=yyOJ#dFrrOTkRu+_g~$n$w>Fig+F~!AvV*s#1{7# z{_f(VUr0dNb(%-bPIDidN)PH3s9x_-mah7z58$qKoZ@ZgJ<4J$y}7TtZATljnx`W` zQm)s8)x*an5pT}oV=3?U;0Gq~68`#ix8hePcOSty{jwPO=oY0*1ix!)Riq?Qip8-Z zM_`hT>SkPWK@DoL3OgWjKa*<~)4YGnnA3ZHgJH$W$xsk92{~7RlJ6qhw^SMoB_=FT z?Vt!re`|1&zo(nclgvi{M9=Qiu4#4X$QG|sga9O1mCrL$Y|J9^FhX+A_S z97@Em#oxs@%Uvrm|6&@ww$V#7_W^z`v~83 s)L15(USmt$j17Sk*yxWQtT^s5-OnPo&*sI{*Lx diff --git a/announce/unlinked.dat b/announce/unlinked.dat index f69f734d9a933414c8d47bd7ec63470b0553acc9..0686ad2f40085cafc688b8f5025f26fde5b8776e 100644 GIT binary patch literal 810 zcmV+_1J(QvMiL_<27F-n4n~wS!3KQsybeYZD$twyAskq6{kJb>?3;9gse<$tE5r4kL5wXu zaE!g$YaD#&$>uM~d}Dlmx7t!U`1)ZdUvIR3z?51xrLOWO_=d^ZaW`pZWj(s=7;`(n z80Lxpy*v4_*v5sQ4(Z(CpxMQbQsVGk^Wop%A+AuJ9Cz(4^O;j7i63|MZ~K`-rRdu0 zH}4>z43C`{ePzwQ&|lTf7+dG~wdj&}sUFdtel`8i5b&WJ#o(dj;{io(7x`uFv|s*=hr)9RP^zKWLaF5S~P+jOXo zyP1HBoQM<6PPAG!`<_&CYycI^`J>k0`^uMJ3mM84ZRf}g?`Ny-6leb%&inUeuNLR@ zC`_{KfycA%yr`RvYTN&@ZkyZ0Tnz?}-kd>=PDDhO`Z}5^U7mM?%JhP)7(PsxaWXzZ zmJ^=a)##+R!iJlQNQw_n0H3abEWjX z=$rd?-mrxhz3iF&T937&%IywfR5Dq zK5w(s(p6Z_B09R-@K9$>+a%F3&eBs{oygthFVVj3Z$9NO=dGPN;|z`D-B&5KoA+fe zHo$Hj$^Q2~pYt}YQl)-%&A&LUA*9mRMO6u9)IdI~R9im?G^CQDnitu}TYpZ4{ni;< zY>cxA|H!?N6q?<|MO>-P&$gkFa|f*8@uH?sqZOJ`f@1*&L&hDNchK7Kn0W=r3y)Oz z+N#219xH_EZonFIf|PreuJq#+2>{X5lV^y^IwF951GxCkLbhTNzA8r0`o}&vo)#sR olyYN&6YGkoN8*K`f_e##dMVBMhO8^J{(CEasR)h|l2$g>+wae`+yDRo literal 810 zcmV+_1J(R+Vo1cqU_J&PaAGOUgJ3?7J#b=3)Wl#u1|P$RcuAyK9CujT@F5+<>5ykF ze5FuA&H93s1IGnC-v-&q^MM(56$t7O*ByNugg6=wzwPv9H`y*hl|e6P)WffNf-$tb z4l%u3);K;FlV|jkKE^(N?ORHmA76&#`t~jU11VZ;rFBnaA7PW5j_j6Zro3G@7|z`N z7-y0Hz1*LT8)KpQaF;uW1)HPsN=Fa6&xiX5gmntJIPSN!&zZ_(k@)Vf_TQO8r59V* z?Dqr(F!8wZnzq!Pv%w~BnVt`3a}=bT^S%1-0dXr| zeD=*NOR5@kL|k244+`e8+a!yOb4$uPxf8o*^oxDB_I#)G=e4<<$1t%cyXus!+3%+G zYy)<%N~M0Tv;7>kge6PsqNWS?HVN+WvB(ziXJ< zHZjc*{}a6lC|SFsqB@nc^KFHR&fql%k40q)#i&_ILB@b!!eh8u?h9KF8J?gM(D5oC zTUA5Gc+??Rb^}<>K`Gv;buY&#kN}G-$>tGLTtorBz#SiRLTyHfeNg$k$O7;zywcL0*t?UX-(+VKvk(f8Nx7l@M`ANos6s+uT8t4*&oF From 3b53e36b8cf05c51adace6c8dc02006006e4a816 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 11 Oct 2018 11:24:45 -0700 Subject: [PATCH 119/553] quadnet and notincache announcements --- QnetGateway.cpp | 39 ++++++++++++++++++++++++++++++++++++++- QnetGateway.h | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index a242346..f00ce1c 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -389,7 +389,10 @@ bool CQnetGateway::read_config(char *cfgFile) if (! get_value(cfg, path+"dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) return true; - if (! get_value(cfg, path+"status", status_file, 1, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) + if (! get_value(cfg, path+"status", status_file, 2, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) + return true; + + if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, ".tmp/qnvoice.txt")) return true; // link @@ -478,10 +481,36 @@ void CQnetGateway::GetIRCDataThread() } short threshold = 0; + bool not_announced[3]; + for (int i=0; i<3; i++) + not_announced[i] = this->rptr.mod[i].defined; // announce to all modules that are defined! + bool is_quadnet = (0 == ircddb.ip.compare("rr.openquad.net")); while (keep_running) { threshold++; if (threshold >= 100) { int rc = ii->getConnectionState(); + if (rc > 5 && rc < 8 && is_quadnet) { + char ch = '\0'; + if (not_announced[0]) + ch = 'A'; + else if (not_announced[1]) + ch = 'B'; + else if (not_announced[2]) + ch = 'C'; + if (ch) { + // we need to announce, but can we? + struct stat sbuf; + if (stat(qnvoicefile.c_str(), &sbuf)) { + // yes, there is no qnvoicefile, so create it + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_connected2network.dat_WELCOME_TO_QUADNET", ch); + fclose(fp); + not_announced[ch - 'A'] = false; + } + } + } + } if ((rc == 0) || (rc == 10)) { if (last_status != 0) { printf("irc status=%d, probable disconnect...\n", rc); @@ -1664,6 +1693,14 @@ void CQnetGateway::Process() } } } + else + { // Not in cache, please try again! + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", rptrbuf.vpkt.hdr.r1[7]); + fclose(fp); + } + } } } else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { diff --git a/QnetGateway.h b/QnetGateway.h index 757a92c..5dd476e 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -87,7 +87,7 @@ private: SPORTIP g2_internal, g2_external, g2_link, ircddb; - std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass; + std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs; From 0406bf552fb5c30b1906d6d020a4fa7706f6837d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 11 Oct 2018 11:52:08 -0700 Subject: [PATCH 120/553] moved irc data thread priorities in process loop. --- QnetGateway.cpp | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index f00ce1c..4e97e6b 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -392,7 +392,7 @@ bool CQnetGateway::read_config(char *cfgFile) if (! get_value(cfg, path+"status", status_file, 2, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) return true; - if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, ".tmp/qnvoice.txt")) + if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, "/tmp/qnvoice.txt")) return true; // link @@ -486,31 +486,32 @@ void CQnetGateway::GetIRCDataThread() not_announced[i] = this->rptr.mod[i].defined; // announce to all modules that are defined! bool is_quadnet = (0 == ircddb.ip.compare("rr.openquad.net")); while (keep_running) { - threshold++; - if (threshold >= 100) { - int rc = ii->getConnectionState(); - if (rc > 5 && rc < 8 && is_quadnet) { - char ch = '\0'; - if (not_announced[0]) - ch = 'A'; - else if (not_announced[1]) - ch = 'B'; - else if (not_announced[2]) - ch = 'C'; - if (ch) { - // we need to announce, but can we? - struct stat sbuf; - if (stat(qnvoicefile.c_str(), &sbuf)) { - // yes, there is no qnvoicefile, so create it - FILE *fp = fopen(qnvoicefile.c_str(), "w"); - if (fp) { - fprintf(fp, "%c_connected2network.dat_WELCOME_TO_QUADNET", ch); - fclose(fp); - not_announced[ch - 'A'] = false; - } - } + int rc = ii->getConnectionState(); + if (rc > 5 && rc < 8 && is_quadnet) { + char ch = '\0'; + if (not_announced[0]) + ch = 'A'; + else if (not_announced[1]) + ch = 'B'; + else if (not_announced[2]) + ch = 'C'; + if (ch) { + // we need to announce, but can we? + struct stat sbuf; + if (stat(qnvoicefile.c_str(), &sbuf)) { + // yes, there is no qnvoicefile, so create it + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_connected2network.dat_WELCOME_TO_QUADNET", ch); + fclose(fp); + not_announced[ch - 'A'] = false; + } else + fprintf(stderr, "could not open %s\n", qnvoicefile.c_str()); } } + } + threshold++; + if (threshold >= 100) { if ((rc == 0) || (rc == 10)) { if (last_status != 0) { printf("irc status=%d, probable disconnect...\n", rc); From 9126af9662dc32a73fb5157686fcbc453590da24 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 11 Oct 2018 12:50:57 -0700 Subject: [PATCH 121/553] service script dependancy and Makefile cleanup and new exec_G.sh script --- Makefile | 35 +++++------------------------------ exec_G.sh | 2 ++ system/mmdvm.service | 1 - system/qndvap.service | 1 - system/qndvrptr.service | 1 - system/qnitap.service | 1 - system/qnlink.service | 4 ++-- system/qnrelay.service | 1 - 8 files changed, 9 insertions(+), 37 deletions(-) create mode 100644 exec_G.sh diff --git a/Makefile b/Makefile index 7f31f11..14893f7 100644 --- a/Makefile +++ b/Makefile @@ -243,12 +243,7 @@ uninstall : systemctl disable qnlink.service /bin/rm -f $(SYSDIR)/qnlink.service /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/*.dat /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh @@ -273,12 +268,7 @@ uninstallitap : systemctl disable qnlink.service /bin/rm -f $(SYSDIR)/qnlink.service /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/*.dat /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh @@ -303,12 +293,7 @@ uninstallicom : systemctl disable qnlink.service /bin/rm -f $(SYSDIR)/qnlink.service /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/*.dat /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh @@ -327,12 +312,7 @@ uninstalldvap : systemctl disable qnlink.service /bin/rm -f $(SYSDIR)/qnlink.service /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/*.dat /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh @@ -357,12 +337,7 @@ uninstalldvrptr : systemctl disable qnlink.service /bin/rm -f $(SYSDIR)/qnlink.service /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/already_linked.dat - /bin/rm -f $(CFGDIR)/already_unlinked.dat - /bin/rm -f $(CFGDIR)/failed_linked.dat - /bin/rm -f $(CFGDIR)/id.dat - /bin/rm -f $(CFGDIR)/linked.dat - /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/*.dat /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh diff --git a/exec_G.sh b/exec_G.sh new file mode 100644 index 0000000..a8e47a5 --- /dev/null +++ b/exec_G.sh @@ -0,0 +1,2 @@ +#!/bin/sh +systemctl restart qngateway diff --git a/system/mmdvm.service b/system/mmdvm.service index 55cf290..3460508 100644 --- a/system/mmdvm.service +++ b/system/mmdvm.service @@ -1,6 +1,5 @@ [Unit] Description=MMDVMHost -Requires=qnrelay.service After=systemd-user-session.service qnrelay.service [Service] diff --git a/system/qndvap.service b/system/qndvap.service index c5ec6fb..525d01c 100644 --- a/system/qndvap.service +++ b/system/qndvap.service @@ -1,6 +1,5 @@ [Unit] Description=QnetDVAP -Requires=qngateway.service After=systemd-user-session.service qngateway.service [Service] diff --git a/system/qndvrptr.service b/system/qndvrptr.service index 79a15bb..ae21d75 100644 --- a/system/qndvrptr.service +++ b/system/qndvrptr.service @@ -1,6 +1,5 @@ [Unit] Description=QnetDVRPTR -Requires=qngateway.service After=systemd-user-session.service qngateway.service [Service] diff --git a/system/qnitap.service b/system/qnitap.service index 41ec92a..ebe7ee6 100644 --- a/system/qnitap.service +++ b/system/qnitap.service @@ -1,6 +1,5 @@ [Unit] Description=QnetITAP -Requires=qngateway.service After=systemd-user-session.service qngateway.service [Service] diff --git a/system/qnlink.service b/system/qnlink.service index f1dbc83..8c68ea0 100644 --- a/system/qnlink.service +++ b/system/qnlink.service @@ -1,7 +1,7 @@ [Unit] Description=QnetLink -Requires=qngateway.service -After=systemd-user-session.service qngateway.service +Requires=network.target +After=systemd-user-session.service network.target [Service] Type=simple diff --git a/system/qnrelay.service b/system/qnrelay.service index 9dfdf94..4d2f2b9 100644 --- a/system/qnrelay.service +++ b/system/qnrelay.service @@ -1,6 +1,5 @@ [Unit] Description=QnetRelay -Requires=qngateway.service After=systemd-user-session.service qngateway.service [Service] From cdb4ef72951e167d1b71baf596c67a3c6662a2c7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 11 Oct 2018 13:04:02 -0700 Subject: [PATCH 122/553] exec permission for exec_G.sh --- exec_G.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 exec_G.sh diff --git a/exec_G.sh b/exec_G.sh old mode 100644 new mode 100755 From 0ff3ea12d53eb95d829f6c0c61ad2e1ff8034d89 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 12 Oct 2018 06:26:24 -0700 Subject: [PATCH 123/553] version bump --- versions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versions.h b/versions.h index 8ebcf24..bad3b36 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.2.0" -#define LINK_VERSION "QnetLink-6.0.2" +#define IRCDDB_VERSION "QnetGateway-7.3.0" +#define LINK_VERSION "QnetLink-6.1.1" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" #define ITAP_VERSION "QnetITAP-0.2.1" From e9645fad8272cc991b51cbc353ca14f069664c2b Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 13 Oct 2018 17:29:34 -0700 Subject: [PATCH 124/553] DPlus authorization and "empty txt message" bug --- DPlusAuthenticator.cpp | 177 +++++++++++++++++++++++++++ DPlusAuthenticator.h | 40 +++++++ ITAP.README | 3 + MMDVM.README | 3 + Makefile | 4 +- QnetGateway.cpp | 60 +++++++--- QnetGateway.h | 4 +- QnetLink.cpp | 27 +++-- QnetLink.h | 7 +- TCPReaderWriterClient.cpp | 243 ++++++++++++++++++++++++++++++++++++++ TCPReaderWriterClient.h | 56 +++++++++ qn.dvap.cfg | 12 ++ qn.everything.cfg | 12 ++ qn.icom.cfg | 12 ++ qn.itap.cfg | 12 ++ qn.mmdvm.cfg | 12 ++ versions.h | 4 +- 17 files changed, 658 insertions(+), 30 deletions(-) create mode 100644 DPlusAuthenticator.cpp create mode 100644 DPlusAuthenticator.h create mode 100644 TCPReaderWriterClient.cpp create mode 100644 TCPReaderWriterClient.h diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp new file mode 100644 index 0000000..8dea5d2 --- /dev/null +++ b/DPlusAuthenticator.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2010-2015 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DPlusAuthenticator.h" +//#include "DStarDefines.h" +//#include "Utils.h" +//#include "Defs.h" + +CDPlusAuthenticator::CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address) : +m_loginCallsign(loginCallsign), +m_address(address) +{ + assert(loginCallsign.size()); + + Trim(m_loginCallsign); +} + +CDPlusAuthenticator::~CDPlusAuthenticator() +{ +} + +bool CDPlusAuthenticator::Process(std::map &gwy_map, const bool reflectors, const bool repeaters) +// return true if everything went okay +{ + struct addrinfo hints, *infoptr; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; // AF_INET means IPv4 only addresses + hints.ai_socktype = SOCK_STREAM; + + int result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); + if (result) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result)); + return false; + } + + struct addrinfo *p; + char host[256]; + + bool success = false; + + for (p = infoptr; p != NULL && !success; p = p->ai_next) { + getnameinfo(p->ai_addr, p->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST); + printf("Trying %s from %s\n", host, m_address.c_str()); + success = authenticate(m_loginCallsign, std::string(host), gwy_map, reflectors, repeaters); + } + + freeaddrinfo(infoptr); + return success; +} + +bool CDPlusAuthenticator::authenticate(const std::string &callsign, const std::string &hostname, std::map &gwy_map, const bool reflectors, const bool repeaters) +{ + CTCPReaderWriterClient socket(hostname, 20001U); + + bool ret = socket.open(); + if (!ret) + return false; + + unsigned char* buffer = new unsigned char[4096U]; + ::memset(buffer, ' ', 56U); + + buffer[0U] = 0x38U; + buffer[1U] = 0xC0U; + buffer[2U] = 0x01U; + buffer[3U] = 0x00U; + + ::memcpy(buffer+4, callsign.c_str(), callsign.size()); + ::memcpy(buffer+12, "DV019999", 8); + ::memcpy(buffer+28, "W7IB2", 5); + ::memcpy(buffer+40, "DHS0257", 7); + + ret = socket.write(buffer, 56U); + if (!ret) { + socket.close(); + delete[] buffer; + return false; + } + + ret = read(socket, buffer, 2U); + + while (ret) { + unsigned int len = (buffer[1U] & 0x0FU) * 256U + buffer[0U]; + + // Ensure that we get exactly len - 2U bytes from the TCP stream + ret = read(socket, buffer + 2U, len - 2U); + if (!ret) { + fprintf(stderr, "Short read from %s:20001", hostname.c_str()); + return false; + } + + if ((buffer[1U] & 0xC0U) != 0xC0U || buffer[2U] != 0x01U) { + fprintf(stderr, "Invalid packet received from %s:20001", hostname.c_str()); + return false; + } + + for (unsigned int i = 8U; (i + 25U) < len; i += 26U) { + std::string address((char *)(buffer + i)); + std::string name((char *)(buffer + i + 16U)); + + Trim(address); + Trim(name); + + // Get the active flag + bool active = (buffer[i + 25U] & 0x80U) == 0x80U; + + // An empty name or IP address or an inactive gateway/reflector is not added + if (address.size()>0U && name.size()>0U && active) { + if (reflectors && 0==name.compare(0, 3, "REF")) + gwy_map[name] = address.append(" 20001"); + else if (repeaters && name.compare(0, 3, "REF")) + gwy_map[name] = address.append(" 20001"); + } + } + + ret = read(socket, buffer, 2U); + } + + printf("Authorized DPlus with %s using callsign %s\n", hostname.c_str(), callsign.c_str()); + printf("Added %ld DPlus gateways\n", gwy_map.size()); + socket.close(); + + delete[] buffer; + + return true; +} + +void CDPlusAuthenticator::Trim(std::string &s) +{ + auto it = s.begin(); + while (it!=s.end() && isspace(*it)) + s.erase(it); + auto rit = s.rbegin(); + while (rit!=s.rend() && isspace(*rit)) { + s.resize(s.size() - 1); + rit = s.rbegin(); + } +} + +bool CDPlusAuthenticator::read(CTCPReaderWriterClient &socket, unsigned char *buffer, unsigned int len) const +{ + unsigned int offset = 0U; + + do { + int n = socket.read(buffer + offset, len - offset, 10U); + if (n < 0) + return false; + + offset += n; + } while ((len - offset) > 0U); + + return true; +} diff --git a/DPlusAuthenticator.h b/DPlusAuthenticator.h new file mode 100644 index 0000000..c9e712a --- /dev/null +++ b/DPlusAuthenticator.h @@ -0,0 +1,40 @@ +#pragma once +/* + * Copyright (C) 2010-2013 by Jonathan Naylor G4KLX + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +**/ + +#include +#include +#include +#include "TCPReaderWriterClient.h" + +class CDPlusAuthenticator { +public: + CDPlusAuthenticator(const std::string &loginCallsign, const std::string &address); + ~CDPlusAuthenticator(); + + bool Process(std::map &gwy_map, const bool reflectors, const bool repeaters); + +private: + std::string m_loginCallsign; + std::string m_address; + + void Trim(std::string &s); + bool authenticate(const std::string &callsign, const std::string &hostname, std::map &gwy_map, const bool reflectors, const bool repeaters); + bool read(CTCPReaderWriterClient &socket, unsigned char *buffer, unsigned int len) const; +}; diff --git a/ITAP.README b/ITAP.README index df0147e..3165fbb 100644 --- a/ITAP.README +++ b/ITAP.README @@ -30,6 +30,9 @@ your start. On a Raspberry Pi, you can do all of this with the configuration men USB devices on your system the device might end up somewhere else. Do "ls /dev" before and after plugging in your cable to figure out where it is. If it's not on /dev/ttyUSB0, uncomment the device line and put in the correct device. + If you are planning on linking to a Trust DPlus (REF) reflector or repeater, + please look over the "dplus" section to enable this. You need to already be + registered (www.dstargateway.org) to do this. 6) You need a gwys.txt file for all the systems to which you may wish to link. If you want to be able to link to repeaters: ./get_gwy_list.sh diff --git a/MMDVM.README b/MMDVM.README index 7fb6a8a..30f01a3 100644 --- a/MMDVM.README +++ b/MMDVM.README @@ -59,6 +59,9 @@ disable the serial0 console in the /boot/cmdline.txt file: Remove the reference 12) You need a configuration file called qn.cfg for QnetGateway. A good, nearly working config file is qn.mmdvm.cfg. Copy it to qn.cfg and edit it. + If you are planning on linking to a Trust DPlus (REF) reflector or repeater, + please look over the "dplus" section to enable this. You need to already be + registered (www.dstargateway.org) to do this. 13) You need a gwys.txt file for all the systems to which you may wish to link. If you want to be able to link to repeaters: ./get_gwy_list.sh diff --git a/Makefile b/Makefile index 14893f7..ec079af 100644 --- a/Makefile +++ b/Makefile @@ -54,8 +54,8 @@ itap : $(TAP_PROGRAMS) qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread -qnlink : QnetLink.o Random.o - g++ $(CPPFLAGS) -o qnlink QnetLink.o Random.o $(LDFLAGS) -pthread +qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o + g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o $(LDFLAGS) -pthread qnrelay : QnetRelay.o g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 4e97e6b..d8e90f6 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -814,6 +814,11 @@ void CQnetGateway::ProcessTimeouts() } } +// new_group is true if we are processing the first voice packet of a 2-voice packet pair. The high order nibble of the first byte of +// this first packet specifed the type of slow data that is being sent. +// the to_print is an integer that counts down how many 2-voice-frame pairs remain to be processed. +// ABC_grp means that we are processing a 20-character message. +// C_seen means that we are processing the last 2-voice-frame packet on a 20 character message. void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) { /* extract 20-byte RADIO ID */ @@ -829,7 +834,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi if (new_group[i]) { header_type = c1 & 0xf0; - // header squelch + // header squelch if ((header_type == 0x50) || (header_type == 0xc0)) { new_group[i] = false; to_print[i] = 0; @@ -933,7 +938,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi /* We should NOT see any more text, if we already processed text, so blank out the codes. */ - if (band_txt[i].txt_stats_sent) { + if (band_txt[i].sent_key_on_msg) { data[0] = 0x70; data[1] = 0x4f; data[2] = 0x93; @@ -944,12 +949,26 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi band_txt[i].txt_cnt = 0; } } - else { + else { // header type is not header, squelch, gps or message new_group[i] = false; to_print[i] = 0; ABC_grp[i] = false; } - } else { + } + else { // not a new_group, this is the second of a two-voice-frame pair + if (! band_txt[i].sent_key_on_msg && vPacketCount > 100) { + // 100 voice packets received and still no 20-char message! + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + band_txt[i].txt[0] = '\0'; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + // we have the 20-character message, send it to the server... + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } if (to_print[i] == 3) { if (ABC_grp[i]) { band_txt[i].txt[band_txt[i].txt_cnt] = c1; @@ -964,7 +983,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi /* We should NOT see any more text, if we already processed text, so blank out the codes. */ - if (band_txt[i].txt_stats_sent) { + if (band_txt[i].sent_key_on_msg) { data[0] = 0x70; data[1] = 0x4f; data[2] = 0x93; @@ -972,16 +991,16 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - if (!band_txt[i].txt_stats_sent) { + if ( ! band_txt[i].sent_key_on_msg) { /*** if YRCALL is CQCQCQ, set dest_rptr ***/ if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { set_dest_rptr(i, band_txt[i].dest_rptr); if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) band_txt[i].dest_rptr[0] = '\0'; } - + // we have the 20-character message, send it to the server... ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); - band_txt[i].txt_stats_sent = true; + band_txt[i].sent_key_on_msg = true; } band_txt[i].txt_cnt = 0; } @@ -995,7 +1014,7 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsi band_txt[i].temp_line_cnt = 0; } - /* do not copy CR, NL */ + /* do not copy carrige return or newline */ if ((c1 != '\r') && (c1 != '\n')) { band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; band_txt[i].temp_line_cnt++; @@ -1394,7 +1413,7 @@ void CQnetGateway::Process() } if (recvlen == 58) { - + vPacketCount = 0U; if (bool_qso_details) printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); @@ -1435,7 +1454,7 @@ void CQnetGateway::Process() band_txt[i].txt[0] = '\0'; band_txt[i].txt_cnt = 0; - band_txt[i].txt_stats_sent = false; + band_txt[i].sent_key_on_msg = false; band_txt[i].dest_rptr[0] = '\0'; @@ -1896,7 +1915,18 @@ void CQnetGateway::Process() dtmf_counter[i] = 0; dtmf_last_frame[i] = 0; } - + if (! band_txt[i].sent_key_on_msg) { + band_txt[i].txt[0] = '\0'; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + // we have the 20-character message, send it to the server... + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + // send the "key off" message, this will end up in the openquad.net Last Heard webpage. ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); band_txt[i].streamID = 0; @@ -1955,8 +1985,8 @@ void CQnetGateway::Process() break; } } - - if (recvlen == 29) + vPacketCount++; + if (recvlen == 29) // process the slow data from every voice packet ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid, header_type, new_group, to_print, ABC_grp, C_seen); else ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid, header_type, new_group, to_print, ABC_grp, C_seen); @@ -2558,7 +2588,7 @@ int CQnetGateway::Init(char *cfgfile) band_txt[i].txt[0] = '\0'; band_txt[i].txt_cnt = 0; - band_txt[i].txt_stats_sent = false; + band_txt[i].sent_key_on_msg = false; band_txt[i].dest_rptr[0] = '\0'; diff --git a/QnetGateway.h b/QnetGateway.h index 5dd476e..15e6dd6 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -58,7 +58,7 @@ typedef struct band_txt_tag { time_t last_time; char txt[64]; // Only 20 are used unsigned short txt_cnt; - bool txt_stats_sent; + bool sent_key_on_msg; char dest_rptr[CALL_SIZE + 1]; @@ -93,6 +93,8 @@ private: int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; + unsigned int vPacketCount; + std::map portmap; // data needed for aprs login and aprs beacon diff --git a/QnetLink.cpp b/QnetLink.cpp index 6cd1d04..da8cd5e 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -37,7 +37,6 @@ #include #include -#include #include #include @@ -45,16 +44,13 @@ #include #include #include -#include -#include -#include -#include #include #include #include #include #include "versions.h" +#include "DPlusAuthenticator.h" #include "QnetLink.h" using namespace libconfig; @@ -315,6 +311,15 @@ void CQnetLink::print_status_file() /* Open text file of repeaters, reflectors */ bool CQnetLink::load_gwys(const std::string &filename) { + // DPlus Authenticate + if (dplus_authorize) { + CDPlusAuthenticator auth(owner, std::string("auth.dstargateway.org")); + if (auth.Process(gwy_list, dplus_reflectors, dplus_repeaters)) + fprintf(stdout, "DPlus Authorization complete.\n"); + else + fprintf(stderr, "DPlus Authorization failed!\n"); + } + char inbuf[1024]; const char *delim = " "; @@ -397,11 +402,11 @@ bool CQnetLink::load_gwys(const std::string &filename) sprintf(payload, "%s %s", host, port); auto gwy_pos = gwy_list.find(call); - if (gwy_pos == gwy_list.end()) { - gwy_list[call] = payload; + if (gwy_pos == gwy_list.end()) printf("Added Call=[%s], payload=[%s]\n",call, payload); - } else - printf("Call [%s] is duplicate\n", call); + else + printf("%s %s has been redefined!\n", call, payload); + gwy_list[call] = payload; } fclose(fp); @@ -714,6 +719,10 @@ bool CQnetLink::read_config(const char *cfgFile) rf_inactivity_timer[i] = timer; } + get_value(cfg, "dplus.authorize", dplus_authorize, false); + get_value(cfg, "dplus.use_reflectors", dplus_reflectors, true); + get_value(cfg, "dplus.use_repeaters", dplus_repeaters, true); + return false; } diff --git a/QnetLink.h b/QnetLink.h index 3d81e1c..2b4cb08 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -19,7 +19,11 @@ */ #include - +#include +#include +#include +#include +#include #include #include "versions.h" #include "QnetTypeDefs.h" @@ -83,6 +87,7 @@ private: /* configuration data */ std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, qnvoice_file, announce_dir; bool only_admin_login, only_link_unlink, qso_details, bool_rptr_ack, announce; + bool dplus_authorize, dplus_reflectors, dplus_repeaters; int rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port, delay_between, delay_before; char link_at_startup[CALL_SIZE+1]; unsigned int max_dongles, saved_max_dongles; diff --git a/TCPReaderWriterClient.cpp b/TCPReaderWriterClient.cpp new file mode 100644 index 0000000..c26c365 --- /dev/null +++ b/TCPReaderWriterClient.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2010-2013 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "TCPReaderWriterClient.h" +//#include "UDPReaderWriter.h" +#include +#include +#include +#include + + +CTCPReaderWriterClient::CTCPReaderWriterClient(const std::string &address, unsigned int port, const std::string &localAddress) : +m_address(address), +m_port(port), +m_localAddress(localAddress), +m_fd(-1) +{ + assert(address.size()); + assert(port > 0U); +} + +CTCPReaderWriterClient::CTCPReaderWriterClient(int fd) : +m_address(), +m_port(0U), +m_localAddress(), +m_fd(fd) +{ + assert(fd >= 0); +} + +CTCPReaderWriterClient::CTCPReaderWriterClient() : +m_address(), +m_port(0U), +m_localAddress(), +m_fd(-1) +{ +} + +CTCPReaderWriterClient::~CTCPReaderWriterClient() +{ +} + +bool CTCPReaderWriterClient::open(const std::string& address, unsigned int port, const std::string& localAddress) +{ + m_address = address; + m_port = port; + m_localAddress = localAddress; + + return open(); +} + +bool CTCPReaderWriterClient::open() +{ + if (m_fd != -1) + return true; + + if (0 == m_address.size() || m_port == 0U) + return false; + + m_fd = ::socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + fprintf(stderr, "Cannot create the TCP client socket, err=%d\n", errno); + return false; + } + + if (m_localAddress.size()) { + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = 0U; + addr.sin_addr.s_addr = ::inet_addr(m_localAddress.c_str()); + if (addr.sin_addr.s_addr == INADDR_NONE) { + fprintf(stderr, "The address is invalid - %s\n", m_localAddress.c_str()); + close(); + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { + fprintf(stderr, "Cannot bind the TCP client address, err=%d\n", errno); + close(); + return false; + } + } + + struct sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + addr.sin_addr = lookup(m_address); + + if (addr.sin_addr.s_addr == INADDR_NONE) { + close(); + return false; + } + + if (::connect(m_fd, (sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) { + fprintf(stderr, "Cannot connect the TCP client socket, err=%d\n", errno); + close(); + return false; + } + + int noDelay = 1; + if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&noDelay, sizeof(noDelay)) == -1) { + fprintf(stderr, "Cannot set the TCP client socket option, err=%d\n", errno); + close(); + return false; + } + + return true; +} + +int CTCPReaderWriterClient::read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs) +{ + assert(buffer != NULL); + assert(length > 0U); + assert(m_fd != -1); + + // Check that the recv() won't block + fd_set readFds; + FD_ZERO(&readFds); + FD_SET(m_fd, &readFds); + + // Return after timeout + timeval tv; + tv.tv_sec = secs; + tv.tv_usec = msecs * 1000; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { + fprintf(stderr, "Error returned from TCP client select, err=%d\n", errno); + return -1; + } + + if (!FD_ISSET(m_fd, &readFds)) + return 0; + + ssize_t len = ::recv(m_fd, (char*)buffer, length, 0); + if (len == 0) { + return -2; + } else if (len < 0) { + fprintf(stderr, "Error returned from recv, err=%d\n", errno); + return -1; + } + + return len; +} + +int CTCPReaderWriterClient::readLine(std::string& line, unsigned int secs) +{ + //maybe there is a better way to do this like reading blocks, pushing them for later calls + //Nevermind, we'll read one char at a time for the time being. + unsigned char c; + int resultCode; + int len = 0; + line = ""; + + do + { + resultCode = read(&c, 1, secs); + if(resultCode == 1){ + line += c; + len++; + } + }while(c != '\n' && resultCode == 1); + + return resultCode <= 0 ? resultCode : len; +} + +bool CTCPReaderWriterClient::write(const unsigned char* buffer, unsigned int length) +{ + assert(buffer != NULL); + assert(length > 0U); + assert(m_fd != -1); + + ssize_t ret = ::send(m_fd, (char *)buffer, length, 0); + if (ret != ssize_t(length)) { + fprintf(stderr, "Error returned from send, err=%d\n", errno); + return false; + } + + return true; +} + +bool CTCPReaderWriterClient::writeLine(const std::string& line) +{ + std::string lineCopy(line); + if(lineCopy.size() > 0 && lineCopy.at(lineCopy.size() - 1) != '\n') + lineCopy.append("\n"); + + //stupidly write one char after the other + size_t len = lineCopy.size(); + bool result = true; + for(size_t i = 0; i < len && result; i++){ + unsigned char c = lineCopy.at(i); + result = write(&c , 1); + } + + return result; +} + +void CTCPReaderWriterClient::close() +{ + if (m_fd != -1) { + ::close(m_fd); + m_fd = -1; + } +} + +in_addr CTCPReaderWriterClient::lookup(const std::string &hostname) +{ + in_addr addr; + in_addr_t address = ::inet_addr(hostname.c_str()); + if (address != in_addr_t(-1)) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.c_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + fprintf(stderr, "Cannot find address for host %s", hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +} diff --git a/TCPReaderWriterClient.h b/TCPReaderWriterClient.h new file mode 100644 index 0000000..3470010 --- /dev/null +++ b/TCPReaderWriterClient.h @@ -0,0 +1,56 @@ +#pragma once + +/* end of inma once */ +/* + * Copyright (C) 2010,2011,2012,2013 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CTCPReaderWriterClient { +public: + CTCPReaderWriterClient(const std::string &address, unsigned int port, const std::string &localAddress = std::string("")); + CTCPReaderWriterClient(int fd); + CTCPReaderWriterClient(); + ~CTCPReaderWriterClient(); + + bool open(const std::string &address, unsigned int port, const std::string &localAddress = std::string("")); + bool open(); + + int read(unsigned char *buffer, unsigned int length, unsigned int secs, unsigned int msecs = 0U); + int readLine(std::string &line, unsigned int secs); + bool write(const unsigned char* buffer, unsigned int length); + bool writeLine(const std::string &line); + in_addr lookup(const std::string &hostname); + + void close(); + +private: + std::string m_address; + unsigned short m_port; + std::string m_localAddress; + int m_fd; +}; diff --git a/qn.dvap.cfg b/qn.dvap.cfg index 15514eb..92b466a 100644 --- a/qn.dvap.cfg +++ b/qn.dvap.cfg @@ -30,3 +30,15 @@ link = { # link to the reflector of your choice. the first character is the module you are linking. # link_at_start = "CREF001C" } + +dplus = { +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. + +# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters +# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors +# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters + +# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +} diff --git a/qn.everything.cfg b/qn.everything.cfg index 280e2f8..5b2f8c6 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -281,6 +281,18 @@ link = { # max_dongles = 5 # maximum number of linked hot-spots } +dplus = { +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. + +# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters +# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors +# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters + +# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +} + file = { # status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services # DTMF = "/tmp" # diff --git a/qn.icom.cfg b/qn.icom.cfg index 7e32f61..2a64798 100644 --- a/qn.icom.cfg +++ b/qn.icom.cfg @@ -110,6 +110,18 @@ link = { # max_dongles = 5 # maximum number of linked hotspots } +dplus = { +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. + +# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters +# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors +# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters + +# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +} + file = { # status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services # DTMF = "/tmp" # diff --git a/qn.itap.cfg b/qn.itap.cfg index e991b63..584770e 100644 --- a/qn.itap.cfg +++ b/qn.itap.cfg @@ -30,3 +30,15 @@ link = { # link to the reflector of your choice. the first character is the module you are linking. # link_at_start = "CREF001C" } + +dplus = { +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. + +# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters +# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors +# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters + +# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +} diff --git a/qn.mmdvm.cfg b/qn.mmdvm.cfg index 36bf9cc..97270cc 100644 --- a/qn.mmdvm.cfg +++ b/qn.mmdvm.cfg @@ -41,3 +41,15 @@ link = { admin = [ "AA0AAA" , "BB1BBB" , "CC3CCC" ] } + +dplus = { +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. + +# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters +# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors +# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters + +# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +} diff --git a/versions.h b/versions.h index bad3b36..502fd8f 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.3.0" -#define LINK_VERSION "QnetLink-6.1.1" +#define IRCDDB_VERSION "QnetGateway-7.4.0" +#define LINK_VERSION "QnetLink-6.2.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" #define ITAP_VERSION "QnetITAP-0.2.1" From 3c25355faa3944d9d847947832142a933d1c0df7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 13 Oct 2018 19:52:22 -0700 Subject: [PATCH 125/553] dplus names must be 8 chars long! --- DPlusAuthenticator.cpp | 3 ++- QnetLink.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp index 8dea5d2..47b158e 100644 --- a/DPlusAuthenticator.cpp +++ b/DPlusAuthenticator.cpp @@ -124,6 +124,7 @@ bool CDPlusAuthenticator::authenticate(const std::string &callsign, const std::s Trim(address); Trim(name); + name.resize(8, ' '); // Get the active flag bool active = (buffer[i + 25U] & 0x80U) == 0x80U; @@ -141,7 +142,7 @@ bool CDPlusAuthenticator::authenticate(const std::string &callsign, const std::s } printf("Authorized DPlus with %s using callsign %s\n", hostname.c_str(), callsign.c_str()); - printf("Added %ld DPlus gateways\n", gwy_map.size()); + printf("Added %d DPlus gateways\n", (int)gwy_map.size()); socket.close(); delete[] buffer; diff --git a/QnetLink.cpp b/QnetLink.cpp index da8cd5e..45416ad 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -402,14 +402,14 @@ bool CQnetLink::load_gwys(const std::string &filename) sprintf(payload, "%s %s", host, port); auto gwy_pos = gwy_list.find(call); - if (gwy_pos == gwy_list.end()) - printf("Added Call=[%s], payload=[%s]\n",call, payload); - else + if (gwy_pos != gwy_list.end()) printf("%s %s has been redefined!\n", call, payload); gwy_list[call] = payload; } fclose(fp); + for (auto it=gwy_list.begin(); it!=gwy_list.end(); it++) + printf("%s %s\n", it->first.c_str(), it->second.c_str()); printf("Added %d gateways\n", (int)gwy_list.size()); return true; } From 35331fcb48e51aff9ba480773cc7bee18449128d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 14 Oct 2018 05:11:51 -0700 Subject: [PATCH 126/553] Play notincache after user's transmission --- QnetGateway.cpp | 21 ++++++++++++++------- QnetGateway.h | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index d8e90f6..40bd27a 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1714,12 +1714,8 @@ void CQnetGateway::Process() } } else - { // Not in cache, please try again! - FILE *fp = fopen(qnvoicefile.c_str(), "w"); - if (fp) { - fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", rptrbuf.vpkt.hdr.r1[7]); - fclose(fp); - } + { + playNotInCache = true; // we need to wait until user's transmission is over } } } @@ -1929,6 +1925,16 @@ void CQnetGateway::Process() // send the "key off" message, this will end up in the openquad.net Last Heard webpage. ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); + if (playNotInCache) { + // Not in cache, please try again! + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", band_txt[i].lh_rpt1[7]); + fclose(fp); + } + playNotInCache = false; + } + band_txt[i].streamID = 0; band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; band_txt[i].lh_mycall[0] = '\0'; @@ -1947,7 +1953,6 @@ void CQnetGateway::Process() band_txt[i].num_dv_frames = 0; band_txt[i].num_dv_silent_frames = 0; band_txt[i].num_bit_errors = 0; - } else { // not the end of the voice stream @@ -2557,6 +2562,8 @@ int CQnetGateway::Init(char *cfgfile) return 1; } + playNotInCache = false; + /* build the repeater callsigns for aprs */ rptr.mod[0].call = OWNER; for (i=OWNER.length(); i; i--) diff --git a/QnetGateway.h b/QnetGateway.h index 15e6dd6..7cf66bc 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -89,7 +89,7 @@ private: std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; - bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs; + bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs, playNotInCache; int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; From 1f8ae3d571520391eb1f1d3b068d1d72eea229af Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 14 Oct 2018 06:25:27 -0700 Subject: [PATCH 127/553] documentation update --- BUILDING | 30 ++++++++- CONFIGURING | 181 ++++++++++++---------------------------------------- 2 files changed, 68 insertions(+), 143 deletions(-) diff --git a/BUILDING b/BUILDING index b63a84e..677b684 100644 --- a/BUILDING +++ b/BUILDING @@ -45,7 +45,7 @@ dvrptr ircddb gateway. The first thing to do is change to the build directory with "cd QnetGateway" and then choose a target to make. There are targets for each of the supported devices: . "make icom" will build all programs needed for the Icom repeater. -. "make itap" will build all programs needed for Icom Terminal and Access mode. +. "make itap" will build all programs needed for Icom Terminal mode. . "make dvap" will build all programs needed for the DVAP Dongle. . "make dvrptr" will build all programs needed for the DVRPTR_V1. . "make mmdvm" will build all programs needed for MMDVMHost support. (You need @@ -73,6 +73,27 @@ need a configuration file called "qn.cfg". Additional information about the configuration as well as other important and useful features are also in the CONFIGURING file. +Authorization to the the Trust DPlus system is first obtained by registering, +see www.dstargateway.org for instructions. Once registered, make sure to enable +it in you software, please see the "dplus" section in the example cfg files. +By default DPlus authorization is off. When you turn it on, the authorizing server +will provide QnetLink with a list of REF reflectors and repeaters on the DPlus +system. In the "dplus" section of you qn.cfg file, you have control over which +systems you want added to your gwy database. Your gwys.txt file is read AFTER +DPlus authorization so any entries their will over-write REF any downloaded +from the DPlus authorization server. + +The information downloaded from the DPlus server is dynamic and will change +from hour to hour. You can update QnetLink by sending " F" to your system. +This will purge the current table and re-authorize with the DPlus server and +then reload gwys.txt. + +Because of the way DPlus authorization works, QnetLink can't actually confirm +that the authorization was successful. If your system is unlinked after trying +to transmit into a DPlus system, it means that your authorization was +unsuccessful. This might indicate that their may be a problem with your +DPlus registration. + The gwys.txt file is the internet address and port numbers for any gateway you would like your ircddb gateway to be able to connect to. The one delivered with this package is special: It has only DCS reflectors, X-reflectors and DStar @@ -94,9 +115,12 @@ address that may need port-forwarding to your sytem. There is another script, reflist.sh, that will download REF, XRF and DCS reflectors from another source. This is probably the preferred method to getting a gwys.txt -file. This source is extremely up-to-date. +file. This source is extremely up-to-date, and it also contains the DPlus REF +reflectors. If you want to use data from the DPlus authorization server, be sure +to remove any REF definitions that might over-write DPlus entries. -Based on the above discussion, execute either "./reflist.sh" or "./get_gwy_list.sh". +Based on the above discussion, execute either "./reflist.sh" or "./get_gwy_list.sh" +and then edit it to your needs. If you plan on using DTMF, you need to copy "qndtmf.sh" to "qndtmf". This is the file that interprets dtmf command and executes them. As supplied, it parses the diff --git a/CONFIGURING b/CONFIGURING index a9e4f20..3d0d601 100644 --- a/CONFIGURING +++ b/CONFIGURING @@ -46,10 +46,13 @@ module = { } link = { - link_at_start = "CREF020A" admin = [ "XX0XXX"] } +dplus = { + authorize = true +} + ------------------------------------------------------------------- Of course, you can add other parameters, such as latitude and longitude, @@ -70,84 +73,7 @@ G2_ircDDB adapted from the OpenG2 DESCRIPTION ================= -QnetGateway is a Dstar G2 gateway for the Dstar network with ircDDB routing -and USroot routing. It runs on Linux(as a Linux service). -ircddb originated from the OpenG2 with only one change. - Instead of using the local Postgres database server, - we use the remote IRC database server.(group2 or group1) - So, the difference between OpenG2 and ircddb is 0.01% -This new software QnetGateway has been approved for use on the ircDDB network. - -IRC Gateways such as g2_ircdb (other IRC gateways copied from our OpenG2) -connect to group2 server(USA/Canada) or group1 server(Europe). -What are these group1, group2 servers? Group1 and group2 servers are really -the combination of 2 programs running together: - -These are: ----- An IRC (Internet Relay Chat) program, in this case it is the inspIRCd - program ----- the LDAP (Light Directory Access Protocol) program - -So, ircDDB database servers are NOTHING new, they were designed by groups -of people 20-30 years ago, before even the ICOM Dstar came out. -The German IRC team, copied the above programs (IRC, LDAP) from those groups, -and they called it group1 and group2. - -The reason for that was that an IRC(Internet Relay Chat) server, sends data -immediately, while the ICOM dstar network sends data 5 minutes later after a -user keyed up a Dstar repeater. So, IRC is faster, but it overloads your -home-network. - -Using a PERSONAL callsign to set up an ircDBB gateway -===================================================== -In qn.cfg, set OWNER equal to your personal callsign. -In QnetLink.cfg, set OWNER equal to your personal callsign. -In your repeater config file, set OWNER equal to your personal callsign. - -Using a REPEATER callsign to set up an ircDBB gateway -===================================================== -In QnetGateway.cfg, set OWNER equal to a REPEATER callsign (that you received -from the ham authority of your Country). -In QnetLink.cfg, set OWNER equal to a REPEATER callsign (that you received -from the ham authority of your Country) In your repeater config file, set -OWNER equal to a REPEATER callsign (that you received from the ham authority -of your Country). - -However, since the IRC database servers(group2 in Canada/USA, group1 in -Europe) do NOT allow personal callsigns to be used as gateways, then you may -be asking what is the point? - -Here is the logic: -In some Countries, it is very difficult to receive a GATEWAY (club/group) -callsign, sometimes it takes years. In cases such as these, hams only have -their own personal callsign to use. So, how do we get around the problem when -the IRC servers group1, group2 require a GATEWAY callsign, but the user only -has a PERSONAL callsign? - -Some groups of hams got together and installed their own IRC database server. -An IRC database server (like group1, group2) is really these 2 basic programs: - ----- inspIRCd server software which is an IRC server (Internet Relay Chat) - which was designed in 1988. One such IRC program is inspIRCd which was - chosen by the German IRC team for use in their group1, group2 installations. - ---- LDAP server software, which is the Light Directory Access Protocol server, - that was written in 1993. - -These 2 programs inspIRCd + LDAP make up the the IRC database servers as we know -them today and were installed together by the IRC team on group1, group2 -installations. - -That is all there is to it. - -When LDAP runs, it checks the Gateway password as listed in QnetGateway.cfg: -IRC_PASS=... When LDAP does NOT run, then the IRC_PASS is NOT checked. - -So, some groups of hams, have installed the inspIRCd server, without installing -the LDAP server. In such an installation, then the IRC_PASS is NOT checked, so -you can use your personal callsign. - -g2_ircDDB supports the following commands in YRCALL +QnetGateway supports the following commands in YRCALL Note: In the commands that folow, _ is a SPACE. @@ -200,22 +126,15 @@ system, respectively. Also note that rpt1 is passed to these scripts\ so you can use this as an input parameter for your scripts. Only admins can execute scripts, so set QnetLink.admin to your callsign -7) -Enabling and disabling INCOMING HotSpotNode connections: -To Enable: -YRCALL=_ _ _ _ _ _ D1 -To Disable: -YRCALL=_ _ _ _ _ _ D0 - -Required software to run the QnetGateway gateway correctly: ---- QnetGateway: The G2 audio gateway. +Required software to run QnetGateway correctly: +--- QnetGateway: The G2 audio gateway. This handle routing using an IRCddb + network and also handle echo and voicemail and some audio + notifications. --- QnetLink: This communicates with QnetGateway to link the local G2 gateway - to reflectors. Note: QnetLink is NOT required if you only make - routing calls or talk locally on the repeater. ---- rptr: This is our dstar repeater software that uses a GMSK adapter/modem. - Instead of rptr, you can use our QnetDVAP dstar repeater software - which uses a DVAP device. Intead of rptr, you can use our QnetDVRPTR - dstar repeater software which uses the DV-RPTR modem(dg1ht). + to reflectors. +--- QnetDVAP, QnetDVRPTR, QNetITAP (can only be used with a compatible + Icom radio that supports Terminal mode) or QnetRelay (used with + MMDVMHost-compatible modems like the ZUMspot or DVMega). ROUTING methods =============== @@ -243,6 +162,14 @@ Example of CALLSIGN routing: RPT1=KJ4NHF B RPT2=KJ4NHF G +Example of GROUP routing: + Lets say you want to connect to the DSTAR1 group from your + local repeater module: + MYCALL=KI4LKF + YRCALL=DSTAR1 + RPT1=KJ4NHF B + RPT2=KJ4NHF G + Example of Cross-Band routing: Lets say that your repeater is KJ4NHF, and you are currently on your local repeater module B, your callsign is KI4LKF @@ -257,15 +184,9 @@ DTMF decoding and processing ============================= Prepare the software to decode and process DTMF tones ----------------------------------------------------- -Edit the Shell script proc_qnlinktest.sh -Correct the value for G2 to be the local G2 gateway -callsign(same value in qn.cfg ----> OWNER) -Edit G2_INT_IP as follows: - If your QnetGateway.cfg, says "G2_INTERNAL_IP=0.0.0.0" then set - G2_INT_IP=127.0.0.1. - If your QnetGateway, says G2_INTERNAL_IP= - then set G2_INT_IP equal to the exact value of G2_INTERNAL_IP. -Edit G2_INT_PORT to be equal to G2_INTERNAL_PORT in QnetGateway.cfg. +Copy the Shell script qndtmf.sh to qndtmf and make any +changes/additions/subtractions to the script then install your +DTMF script with "sudo make installdtmf" Note: When local RF user has entered dtmf tones on the Dstar HT and then PTT is released, QnetGateway will print the whole @@ -273,18 +194,6 @@ Note: When local RF user has entered dtmf tones on the Dstar HT dtmf file under /tmp. How to enter DTMF tones correctly on your Dstar HT --------------------------------------------------- -If you want to have perfect DTMF decoding/processing in QnetGateway, -follow these suggestions: - - 1) Hold down each dtmf key for at least 150 milliseconds. - 2) Leave at least 250 milliseconds of "silence", when you go from - one dtmf key to the next dtmf key. - -If you use a DTMF autodialer on your Dstar radio, you can program -the above numbers easily into the ICOM's radio "auto-dialer". - -Note: MAXIMUM dtmf sequence is up to 32 dtmf tones. What dtmf tones are being decoded and processed ----------------------------------------------- @@ -311,7 +220,7 @@ To unlink (from any reflector xrf or ref): # To get the "link status": 0 or 00 Note: -You can extend the shell script to do more things. like force +You can extend your dtmf shell script to do more things. like force your repeater to ID itself. Any YRCALL command that can be executed by g2link_test, can be added to the shell script. Basically, the Linux shell script proc_QnetGateway_dtmfs.sh converts the decoded @@ -320,19 +229,21 @@ dtmf tones into YRCALL commands using g2link_test program. =========== QnetLink is a small program that is used to link a local RF repeater band to a remote reflector. QnetLink software is used by our QnetGateway -(an IRCddb gateway) and by our g2_ccs (a CCS gateway). +(an IRCddb gateway). -Before we begin, there are some dat files included in the QnetLink -ZIP package. These dat files are: +Before we begin, there are some dat files included that are the voice +prompts used by QnetGateway and QnetLink: +connected2network.dat +notincache.dat already_linked.dat already_unlinked.dat -failed_linked.dat +failed_link.dat linked.dat unlinked.dat id.dat -All of the above dat files are dvtool format files and they are used +All of the above dat files are special AMBE format files and they are used when certain events take place. For example, when the link between your gateway and a reflector is established, then the file "linked.dat" will be played over RF, so anyone listening on your repeater, will hear the @@ -341,14 +252,13 @@ change these files, unless you want to have your own personal voice played over RF. These dat files were created using a computer and they are NOT anyone's voice. -The only dat file most hams will need to change is id.dat. The file -id.dat contains the audio "UNLINKED" and nothing else. When the gateway +The only file most hams will need to change is id.dat. When the gateway is not linked and the RF user sets YRCALL=_______I to request the status of the link, the file id.dat will be played back over RF. But you should create your own id.dat file that should identify your own repeater with extra information if you like. -For example, you could create your own dvtool audio file that contains +For example, you could create your own dat audio file that contains the audio: "This is repeater ...". A simple way to create your own recorded "repeater identification file" id.dat is to use your Dstar HT and set YRCALL command: YRCALL=______S0 and key up your repeater. @@ -357,14 +267,9 @@ x_voicemail.dat where x is one of A.B or C. Now copy that file: sudo cp -f /tmp/C_voicemail.dat /usr/local/etc/it.dat You may be thinking why did we put all this source code inside QnetLink -instead of putting it all inside the Gateway software QnetGateway (or -g2_ccs CCS gateway software). Because it is NOT a good design to burden -the G2 gateway with code that is not DSTAR protocol and reflectors are -NOT part of Dstar and they are not Dstar protocol. - -The only code that should be in a gateway, is G2 protocol code. This way -we keep the gateway (QnetGateway) clean of any unneccesary source -code logic and containing only pure G2/D-Star logic. +instead of putting it all inside the Gateway software QnetGateway. +Having divided the functionality produces a smaller memory and resource +requirement for your computer. QnetDVAP @@ -375,14 +280,10 @@ to maximum level. QnetDVRPTR The serial number required in the qn.cfg file can most easily be obtained -by examining the /var/log/QnetDVRPTR.log file once the board has been -powered up by the BBB or RasPi. Once you know the board serial number, -edit /usr/local/etc/g2.cfg. Please note that once installed, you -need to edit the configuration files in /usr/local/etc, not where you -build the software. You need to be root to edit files in /usr/local/etc. -After editing /usr/local/etc/g2.cfg file you can restart the effected -program with "sudo service QnetDVRPTR restart". Or you can alway just -reboot with "sudo reboot". +by examining the log file once the board has been powered up. Once you +know the board serial number, edit g2.cfg. After editing g2.cfg file you +can restart the effected program with "sudo systemctl restart qndvrptr". +Or you can always just reboot with "sudo reboot" or "sudo shutdown -r now". Rig specific parameters are in "module.x.rf_rx_level", "module.x.inverse.rx" and "module.x.inverse.tx". You need to play with these to work best with your rig. From eb4ff4151a10cd924f2cb9ff68d17a0fa06fe1c1 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 14 Oct 2018 16:28:45 -0700 Subject: [PATCH 128/553] typo in examples --- qn.everything.cfg | 2 +- qn.icom.cfg | 2 +- qn.itap.cfg | 2 +- qn.mmdvm.cfg | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qn.everything.cfg b/qn.everything.cfg index 5b2f8c6..40d75cf 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -290,7 +290,7 @@ dplus = { # use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors # use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters -# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. } file = { diff --git a/qn.icom.cfg b/qn.icom.cfg index 2a64798..d883923 100644 --- a/qn.icom.cfg +++ b/qn.icom.cfg @@ -119,7 +119,7 @@ dplus = { # use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors # use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters -# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. } file = { diff --git a/qn.itap.cfg b/qn.itap.cfg index 584770e..d8f1e18 100644 --- a/qn.itap.cfg +++ b/qn.itap.cfg @@ -40,5 +40,5 @@ dplus = { # use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors # use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters -# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. } diff --git a/qn.mmdvm.cfg b/qn.mmdvm.cfg index 97270cc..9aa264e 100644 --- a/qn.mmdvm.cfg +++ b/qn.mmdvm.cfg @@ -51,5 +51,5 @@ dplus = { # use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors # use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters -# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. +# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. } From 12dbf3190ecc4409998798156848599dc2bc8bf5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 14 Oct 2018 16:37:14 -0700 Subject: [PATCH 129/553] have to set a login callsign --- QnetGateway.cpp | 4 ++++ versions.h | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 40bd27a..900f156 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -236,6 +236,10 @@ bool CQnetGateway::read_config(char *cfgFile) std::string path("ircddb."); if (! get_value(cfg, path+"login", owner, 3, CALL_SIZE-2, "UNDEFINED")) return true; + if (0 == owner.compare("UNDEFINED")) { + fprintf(stderr, "You must specify your lisensed callsign in ircddb.login\n"); + return true; + } OWNER = owner; ToLower(owner); ToUpper(OWNER); diff --git a/versions.h b/versions.h index 502fd8f..b774c33 100644 --- a/versions.h +++ b/versions.h @@ -1,5 +1,5 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.0" +#define IRCDDB_VERSION "QnetGateway-7.4.1" #define LINK_VERSION "QnetLink-6.2.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" From 3ea2c762f7ec5498b07cd4e380ec8121b040336e Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 15 Oct 2018 17:03:54 -0700 Subject: [PATCH 130/553] don't play notincache.dat if it looks like a linking command --- QnetGateway.cpp | 3 ++- versions.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 900f156..f107d05 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1719,7 +1719,8 @@ void CQnetGateway::Process() } else { - playNotInCache = true; // we need to wait until user's transmission is over + if ('L' != rptrbuf.vpkt.hdr.ur[7]) // as long as this doesn't look like a linking command + playNotInCache = true; // we need to wait until user's transmission is over } } } diff --git a/versions.h b/versions.h index b774c33..b1f1ac4 100644 --- a/versions.h +++ b/versions.h @@ -1,5 +1,5 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.1" +#define IRCDDB_VERSION "QnetGateway-7.4.2" #define LINK_VERSION "QnetLink-6.2.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" From 911e829a3f921b71aa2ac53ef72573a95c3ba6d7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 16 Oct 2018 22:45:07 -0700 Subject: [PATCH 131/553] aprs fix --- QnetGateway.cpp | 4 ++-- QnetGateway.h | 2 +- aprs.cpp | 2 +- versions.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index f107d05..b985bef 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -823,7 +823,7 @@ void CQnetGateway::ProcessTimeouts() // the to_print is an integer that counts down how many 2-voice-frame pairs remain to be processed. // ABC_grp means that we are processing a 20-character message. // C_seen means that we are processing the last 2-voice-frame packet on a 20 character message. -void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char &header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) { /* extract 20-byte RADIO ID */ if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { @@ -2007,7 +2007,7 @@ void CQnetGateway::Process() /* aprs processing */ if (bool_send_aprs) // streamID seq audio+text size - aprs->ProcessText(rptrbuf.vpkt.streamid, rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); + aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); for (int i=0; i<3; i++) { /* find out if data must go to the remote G2 */ diff --git a/QnetGateway.h b/QnetGateway.h index 7cf66bc..fbf1f6c 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -154,7 +154,7 @@ private: void compute_aprs_hash(); void APRSBeaconThread(); void ProcessTimeouts(); - void ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen); + void ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char &header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen); bool Flag_is_ok(unsigned char flag); // read configuration file diff --git a/aprs.cpp b/aprs.cpp index 4390add..263e284 100644 --- a/aprs.cpp +++ b/aprs.cpp @@ -70,7 +70,7 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha } if ((rptr_idx < 0) || (rptr_idx > 2)) { - // printf("ERROR in aprs_process_text: rptr_idx %d is invalid\n", rptr_idx); + printf("ERROR in aprs_process_text: rptr_idx %d is invalid\n", rptr_idx); return; } diff --git a/versions.h b/versions.h index b1f1ac4..016bbcd 100644 --- a/versions.h +++ b/versions.h @@ -1,5 +1,5 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.2" +#define IRCDDB_VERSION "QnetGateway-7.4.3" #define LINK_VERSION "QnetLink-6.2.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" From 5f16de827c5954c8514f5b0bad9b0e3628c17bfb Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 17 Oct 2018 12:33:16 -0700 Subject: [PATCH 132/553] clean up aprs and slow data --- QnetGateway.cpp | 17 +++++------------ QnetGateway.h | 10 ++++++++-- aprs.cpp | 4 +--- aprs.h | 2 +- versions.h | 2 +- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index b985bef..bd9e2cb 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -823,7 +823,7 @@ void CQnetGateway::ProcessTimeouts() // the to_print is an integer that counts down how many 2-voice-frame pairs remain to be processed. // ABC_grp means that we are processing a 20-character message. // C_seen means that we are processing the last 2-voice-frame packet on a 20 character message. -void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char &header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen) +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) { /* extract 20-byte RADIO ID */ if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { @@ -1134,13 +1134,6 @@ void CQnetGateway::Process() int dtmf_last_frame[3] = { 0, 0, 0 }; unsigned int dtmf_counter[3] = { 0, 0, 0 }; - // text stuff - bool new_group[3] = { true, true, true }; - unsigned char header_type = 0; - short to_print[3] = { 0, 0, 0 }; - bool ABC_grp[3] = { false, false, false }; - bool C_seen[3] = { false, false, false }; - dstar_dv_init(); int max_nfds = 0; @@ -1997,17 +1990,17 @@ void CQnetGateway::Process() } vPacketCount++; if (recvlen == 29) // process the slow data from every voice packet - ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid, header_type, new_group, to_print, ABC_grp, C_seen); + ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); else - ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid, header_type, new_group, to_print, ABC_grp, C_seen); + ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); /* send data to qnlink */ sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); /* aprs processing */ if (bool_send_aprs) - // streamID seq audio+text size - aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice, (recvlen == 29)?12:15); + // streamID seq audio+text + aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice); for (int i=0; i<3; i++) { /* find out if data must go to the remote G2 */ diff --git a/QnetGateway.h b/QnetGateway.h index fbf1f6c..a6a6bb1 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -83,13 +83,19 @@ public: int Init(char *cfgfile); private: - bool is_icom, is_not_icom; + // text stuff + bool new_group[3] = { true, true, true }; + unsigned char header_type = 0; + short to_print[3] = { 0, 0, 0 }; + bool ABC_grp[3] = { false, false, false }; + bool C_seen[3] = { false, false, false }; SPORTIP g2_internal, g2_external, g2_link, ircddb; std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs, playNotInCache; + bool is_icom, is_not_icom; int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; @@ -154,7 +160,7 @@ private: void compute_aprs_hash(); void APRSBeaconThread(); void ProcessTimeouts(); - void ProcessSlowData(unsigned char *data, unsigned short sid, unsigned char &header_type, bool *new_group, short *to_print, bool *ABC_grp, bool *C_seen); + void ProcessSlowData(unsigned char *data, unsigned short sid); bool Flag_is_ok(unsigned char flag); // read configuration file diff --git a/aprs.cpp b/aprs.cpp index 263e284..7b809c9 100644 --- a/aprs.cpp +++ b/aprs.cpp @@ -52,7 +52,7 @@ void CAPRS::SelectBand(short int rptr_idx, unsigned short streamID) // Parameter len is either 12 or 15, because we took passed over the first 17 bytes // in the repeater data // Paramter seq is the byte at pos# 16(counting from zero) in the repeater data -void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf, unsigned int len) +void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf) { unsigned char aprs_data[200]; char aprs_buf[1024]; @@ -60,8 +60,6 @@ void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned cha short int rptr_idx = -1; - len = len; - for (short int i = 0; i < 3; i++) { if (streamID == aprs_streamID[i].streamID) { rptr_idx = i; diff --git a/aprs.h b/aprs.h index ac97c2d..6d304de 100644 --- a/aprs.h +++ b/aprs.h @@ -58,7 +58,7 @@ public: ~CAPRS(); SRPTR *m_rptr; void SelectBand(short int rptr_idx, unsigned short streamID); - void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf, unsigned int len); + void ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf); ssize_t WriteSock(char *buffer, size_t n); void Open(const std::string OWNER); void Init(); diff --git a/versions.h b/versions.h index 016bbcd..06d324a 100644 --- a/versions.h +++ b/versions.h @@ -1,5 +1,5 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.3" +#define IRCDDB_VERSION "QnetGateway-7.4.4" #define LINK_VERSION "QnetLink-6.2.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" From 8a710be50d52d202e5613b6b7521bb5dd08970fc Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 17 Oct 2018 17:37:34 -0700 Subject: [PATCH 133/553] comment for CAPRS::ProcessText --- aprs.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/aprs.cpp b/aprs.cpp index 7b809c9..cc47504 100644 --- a/aprs.cpp +++ b/aprs.cpp @@ -49,8 +49,6 @@ void CAPRS::SelectBand(short int rptr_idx, unsigned short streamID) // Parameter buf is either: // 12 bytes(packet from repeater was 29 bytes) or // 15 bytes(packet from repeater was 32 bytes) -// Parameter len is either 12 or 15, because we took passed over the first 17 bytes -// in the repeater data // Paramter seq is the byte at pos# 16(counting from zero) in the repeater data void CAPRS::ProcessText(unsigned short streamID, unsigned char seq, unsigned char *buf) { From 87ab07c0974ec2bd456bf48d9f5795ca892cb3e5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 17 Oct 2018 18:23:01 -0700 Subject: [PATCH 134/553] Attempt to contact DPlus round-robin server three times --- DPlusAuthenticator.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp index 47b158e..2b51855 100644 --- a/DPlusAuthenticator.cpp +++ b/DPlusAuthenticator.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -54,8 +56,18 @@ bool CDPlusAuthenticator::Process(std::map &gwy_map, c int result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); if (result) { - fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result)); - return false; + fprintf(stderr, "1st attempt: getaddrinfo: %s\n", gai_strerror(result)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); + if (result) { + fprintf(stderr, "2nd attempt: getaddrinfo: %s\n", gai_strerror(result)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); + if (result) { + fprintf(stderr, "3rd attempt: getaddrinfo: %s\n", gai_strerror(result)); + return false; + } + } } struct addrinfo *p; From 5fbda881cce1f99815682cb26f34afccd88bdccc Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 19 Oct 2018 11:32:48 -0700 Subject: [PATCH 135/553] gateway not found audio prompt --- CONFIGURING | 3 ++- QnetLink.cpp | 2 ++ announce/gatewaynotfound.dat | Bin 0 -> 927 bytes versions.h | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 announce/gatewaynotfound.dat diff --git a/CONFIGURING b/CONFIGURING index 3d0d601..32a9b12 100644 --- a/CONFIGURING +++ b/CONFIGURING @@ -236,6 +236,7 @@ prompts used by QnetGateway and QnetLink: connected2network.dat notincache.dat +gatewaynotfound.dat already_linked.dat already_unlinked.dat failed_link.dat @@ -255,7 +256,7 @@ are NOT anyone's voice. The only file most hams will need to change is id.dat. When the gateway is not linked and the RF user sets YRCALL=_______I to request the status of the link, the file id.dat will be played back over RF. But -you should create your own id.dat file that should identify your own +you can create your own id.dat file that should identify your own repeater with extra information if you like. For example, you could create your own dat audio file that contains diff --git a/QnetLink.cpp b/QnetLink.cpp index 45416ad..e962c6e 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -928,6 +928,8 @@ void CQnetLink::g2link(char from_mod, char *call, char to_mod) auto gwy_pos = gwy_list.find(call); if (gwy_pos == gwy_list.end()) { + sprintf(notify_msg, "%c_gatewaynotfound.dat_GATEWAY_NOT_FOUND", from_mod); + audio_notify(notify_msg); printf("%s not found in gwy list\n", call); return; } diff --git a/announce/gatewaynotfound.dat b/announce/gatewaynotfound.dat new file mode 100644 index 0000000000000000000000000000000000000000..2c1de69e0d19f74aaa783f820cc0752e2ee74746 GIT binary patch literal 927 zcmV;Q17Q3PMiL_<27F-n4n~wS!3KQsybeYZD0w@W^jc)pkZXymEvX6cp+~fuVN%T;* zdwTJhD9I)>b>{Zj-nxs+;uxdEje9CHOmGAtJggb=Av@vx9O?etzP0~5WiK7Z9O zF_~fHwV2~H4dms;<$<~-6fA1tBw*Z{qcnWHWe8lrJG6aot8{VZkh!clnOi~w>*5r! znhTxI&t4M67y@{Xn&`&3IwFbxKwM6K+&QACVXUDaJ&1lT;4?b98}6Z*G_AXUmFz(2 z8D-~;ELy1(<96GAue`t&)cf||evLQ4#Fw`4&o&ulHqbe)(MJ|rWGe8n>(<%Ssx>DM z-0E;r>6w!Mdp@!gNy*>CipI0f5y{#}H$eMaiq@CoNij~vw4#>6o#?W@`I){ILzT(B zzL{9h&`k!3xq`mR+#Y+ze81p&$h}y9P2Bg?UR&T?L(R+Zn@2R*AKQdW;D#*2+pxxtVzuyex`S+DUsNQekiq%pyl|T)r2VJstrq-JwyIVHQFEC zrMXA%K4ytucDR{U{5i3a*q7WIS*4D??Ox>h9>pFCFPtTD_*qA8sb@b9S%}?3HW+p! z`Ok?SLK41(ll{Z-7({yC*{my@om|+$S^&HnD!tgU0FTj&@{#DB`%EeFD?y35n`1nT zD)Bbsjl1Uz4QJsOYmxZjJWc4~XYJ_Q_eKw~t#tLczjK5sqd~i-j2L`!T3D8R$&|Qr zMwk+UuTHF3aB4YPmJNs5{`l$JTY^Z~ociyME!3G>C!$tVBTWh~m1)Y;oOHa-5D?1-s@(x%H(?|dBM*y)V!a&Lru;VKM}hUfwYIh6N#9{6a62$-0yR>sHnJn-%u z^WY4AgbEUGm&2ycT7ZZvs|Jh&CVOQPGFe{&m%2-#7F#vu!~yKw%q~d*z_;ECh>f%; zhd-(PjF#uRQc8%dbi0Z|GQq2oG)$PCD4-FiFB?5tsVt^Zky9@a{*fxIa7PJ9^HR9X B*g*gQ literal 0 HcmV?d00001 diff --git a/versions.h b/versions.h index 06d324a..5b27055 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! #define IRCDDB_VERSION "QnetGateway-7.4.4" -#define LINK_VERSION "QnetLink-6.2.0" +#define LINK_VERSION "QnetLink-6.2.1" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" #define ITAP_VERSION "QnetITAP-0.2.1" From d20f576abc263bd20e90b330e166a346d0481d18 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 20 Oct 2018 15:42:56 -0700 Subject: [PATCH 136/553] wait on authorization --- DPlusAuthenticator.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp index 2b51855..e559152 100644 --- a/DPlusAuthenticator.cpp +++ b/DPlusAuthenticator.cpp @@ -54,21 +54,18 @@ bool CDPlusAuthenticator::Process(std::map &gwy_map, c hints.ai_family = AF_INET; // AF_INET means IPv4 only addresses hints.ai_socktype = SOCK_STREAM; - int result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); - if (result) { - fprintf(stderr, "1st attempt: getaddrinfo: %s\n", gai_strerror(result)); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + int result = EAI_AGAIN; + while (EAI_AGAIN == result) { result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); - if (result) { - fprintf(stderr, "2nd attempt: getaddrinfo: %s\n", gai_strerror(result)); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); - if (result) { - fprintf(stderr, "3rd attempt: getaddrinfo: %s\n", gai_strerror(result)); - return false; - } + if (EAI_AGAIN == result) { + fprintf(stdout, "getaddrinfo not ready: please wait..."); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); } } + if (result) { + fprintf(stderr, "DPlus Authroization failed: %s\n", gai_strerror(result)); + return false; + } struct addrinfo *p; char host[256]; From b16367e412d4e4398bf5ef12bc514e190cc7b172 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 20 Oct 2018 16:01:28 -0700 Subject: [PATCH 137/553] 5 sec loop --- DPlusAuthenticator.cpp | 4 ++-- ircddb/IRCClient.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp index e559152..306cfa5 100644 --- a/DPlusAuthenticator.cpp +++ b/DPlusAuthenticator.cpp @@ -58,8 +58,8 @@ bool CDPlusAuthenticator::Process(std::map &gwy_map, c while (EAI_AGAIN == result) { result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); if (EAI_AGAIN == result) { - fprintf(stdout, "getaddrinfo not ready: please wait..."); - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + fprintf(stdout, "getaddrinfo not ready: please wait...\n"); + std::this_thread::sleep_for(std::chrono::seconds(5)); } } if (result) { diff --git a/ircddb/IRCClient.cpp b/ircddb/IRCClient.cpp index 83b17d6..2498c30 100644 --- a/ircddb/IRCClient.cpp +++ b/ircddb/IRCClient.cpp @@ -10,8 +10,7 @@ #include #include -IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, - const std::string &versionInfo, const std::string &localAddr) +IRCClient::IRCClient(IRCApplication *app, const std::string &update_channel, const std::string &hostName, unsigned int port, const std::string &callsign, const std::string &password, const std::string &versionInfo, const std::string &localAddr) { safeStringCopy(host_name, hostName.c_str(), sizeof host_name); From a807147661abf7cdf30df29ecbb6287665a0c9cc Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 20 Oct 2018 17:31:48 -0700 Subject: [PATCH 138/553] version bump --- versions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.h b/versions.h index 5b27055..f8bcfc0 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! #define IRCDDB_VERSION "QnetGateway-7.4.4" -#define LINK_VERSION "QnetLink-6.2.1" +#define LINK_VERSION "QnetLink-6.2.2" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" #define ITAP_VERSION "QnetITAP-0.2.1" From 3546a93c19b535ae9125ee749ad2b30aa8287511 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 21 Oct 2018 15:45:33 -0700 Subject: [PATCH 139/553] read index --- DPlusAuthenticator.cpp | 5 ++-- QnetLink.cpp | 19 +++++++++++++ QnetLink.h | 3 ++ announce/speak.dat | Bin 0 -> 20016 bytes announce/speak.index | 63 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 announce/speak.dat create mode 100644 announce/speak.index diff --git a/DPlusAuthenticator.cpp b/DPlusAuthenticator.cpp index 306cfa5..98b03b4 100644 --- a/DPlusAuthenticator.cpp +++ b/DPlusAuthenticator.cpp @@ -55,11 +55,12 @@ bool CDPlusAuthenticator::Process(std::map &gwy_map, c hints.ai_socktype = SOCK_STREAM; int result = EAI_AGAIN; - while (EAI_AGAIN == result) { + int count = 0; + while (EAI_AGAIN==result && 20>count++) { // we'll wait up to 60 seconds for this to work result = getaddrinfo(m_address.c_str(), NULL, &hints, &infoptr); if (EAI_AGAIN == result) { fprintf(stdout, "getaddrinfo not ready: please wait...\n"); - std::this_thread::sleep_for(std::chrono::seconds(5)); + std::this_thread::sleep_for(std::chrono::seconds(3)); } } if (result) { diff --git a/QnetLink.cpp b/QnetLink.cpp index e962c6e..2dfc28c 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -66,6 +66,7 @@ CQnetLink::CQnetLink() CQnetLink::~CQnetLink() { + speak.clear(); } bool CQnetLink::resolve_rmt(char *name, int type, struct sockaddr_in *addr) @@ -3596,6 +3597,24 @@ bool CQnetLink::Init(const char *cfgfile) printf("srv_open() failed\n"); return true; } + + speak.resize(62); + std::string index(announce_dir); + index.append("/speak.index"); + std::ifstream voicefile(index.c_str(), std::ifstream::in); + if (voicefile) { + for (int i=0; i<62; i++) { + std::string name, offset, size; + voicefile >> name >> offset >> size; + if (name.size() && offset.size() && size.size()) { + unsigned long of = std::stoul(offset); + unsigned long sz = std::stoul(size); + speak[i] = 1000U * of + sz; + printf("%s at %ld, %ld long\n", name.c_str(), of, sz); + } + } + voicefile.close(); + } return false; } diff --git a/QnetLink.h b/QnetLink.h index 2b4cb08..5155276 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -173,4 +174,6 @@ private: } old_sid[3]; CRandom Random; + + std::vector speak; }; diff --git a/announce/speak.dat b/announce/speak.dat new file mode 100644 index 0000000000000000000000000000000000000000..046de1645733d5589dc4e0b39b4b6697f85d2129 GIT binary patch literal 20016 zcmV))K#IQ(MiL_<27F-n4n~wS!3KQsybeYZDPJ^_y@n_*k^tuvvqr8f*Wn}A4BS-Kl=vH;-bJ{kiLH|TrQ$eqm-F@wyZDf zPt>w9({u?U!rzeV^4oU{FQ3J|lT<;+?RkqN&!%&8HvM{p*%pq~_m;gr>2@lPUQ2Dg zaUP)}oBGQx-D^OMnDRYS{BF5n!}6Z`<6w+Pz5NpUzC3Dd)n*mvw#OjpwSDWMeyRK- zvLte&WWHD6981OE=yR$vBN}rCG+bPo!RNdHme|aC5V7E-GRMdxK&M_LoLlpwQL;DPAP_K9uO-LMwUy>>ZTLkd*RIp}l#waK!iyG;5??k4stG zXBUCIds$9Da7y4>_*z5sKWY3XlwX0fOJFl3MZ5!V**&vK4K)4dc_A6RZc!;8ViAkXo5Bp#e`xp zS82)4gyg!=Lb?G(w0|NT8ytm6N7NqXjB9#XyCXCgMuc0PY9KWJccX|ijkptYV85e` z>5F?ABd`ml3}ZiQC>1sobkL)}*}3gSTi}gQ*@@WWBpj&mR-^3OdtOd_nI%5hn0H>U@TCOxkXdi!{AC+p z^t>DC%sr_G(|Ei%ZTy`j8{s#}nD>Uy_g6u#y`S%&4m4S9PmG#vuDlV6i)piRMIoNZ zO!MZs*|vM8rpMs2^}a9*rLyStrrY%v`jTN36F##x2D^tJ5|H@phq`Hi4Gbfce#8_? z2L=rFzz#$fGh+-$Q2aiH78BuvbfEG+XO<(N0#IY{a;BI{g@rr^2rh&cBO(WU0lFf@ z3PM1ceVNRZJg5`&**^TGsd%~~6$pTyyrF%hQc{KXq;KWfD(MC{Nw)OZc#P}cwHXn} zjk;Eeohe=MQn5W>g!nxcG4t#l!RC17LJ@4`dQ;y>w!lDe$bQ4dx}j!KsQg^P_E~Li z0V@0*N5(j1UeT!h9>K<0;%4Gzh}=PkJnJ$?lI&f>*4iR^Lt^9|$JS}(M$l&1UP0Dr zCVHc3i!hLQUc zDUo@Jx3QVqfNN9oWf+$dMG^zjMPSl4c7&LE27nyK%o=B$3j>NkQ*si7m>WU_WKuFN z2b3G33T!aAE`$&>;D&IpyjtfHVrB&#Cc-YomqwvL9g=( zz>IobZoe^7@)(G_fwfAjfTE@CTGz*_bil#&v6nQBRZssE9C7Of~O_NU4dZd(gTuIrWGQUBrCN0>k%uT^W` z=w4HcnWNhfdd2pGUzmaItP#=6Gvpw^sVB$=|JDv z!WIi>idvQ;)^5(wiKHuj=7j#m(ZB6P{5rz&{qDS&m%H*NP5Yc}d z>9aPpmY2t+I6As08O~JtW{mB-jDWM7Og--Or3uXA+6PkzE+`mdiXRx*ITT(<$gA0u zyLk&J)cZ@vo@%r6gVD~Cx4Lmj&{d%2`w)y+C<8FJ7o)cx$gJH6mF$lZ1}VM;Y!Tsu z^9CXA2wbwKr8}&{YENl!LtkE!gwDnar>lfDDFf3_n4T-y!O=vlPk z)3k_o2-UhSsa=%RLx{~eDoR?qXhh7JJ{*`TI11qKfW3kSICmI$i0r%GJBv9QrE7_t z$;C-$BTEd~A63Pjcz$+5xgFQ6c=Qs$wT*vWw77UWQ+uU6-JIC?7V!1C9Nm;#wGzSc z=v~9+X=!TUE~xrLw)pvFcDVH!U({J_IYwuw+DE1;xn<|Pm72kxzr)rMewn&Mp6vnZ zgYfOXnZHen8FPqwdvNIw@j0Ml%~y;}~nx zpKnF#Vx%5ICJ7ML3Ti}nYraO-Rbd`@7=mDU3%cPA(^e);S@b>>DR5b+3N&N>+!d5h@5jhp803j z{NP-}{z#@~!>8z4H(fucWT2qYU%T?zL~m_Qm=Bmy(RLCn(vn69>$*)SB1BrH2m-Af z>m>#|``hR!SGhhr-+cbC8)-QvCN?QD1beKEifJ)dse}tSnfrM&^^m_%Im_?BC z9YD?oLPv!b5~^D^1~Q96tf6$aOlua&2_z{qP7t)H?xhlNA(UWUzMH~`nt=jE(a>t7 zlzM~;ICTO#Cms`_iBR!!dPNvo1Oq(Qz$%55JAn^y!MYA-o-0F!Kr;9~gpezN1Z;uu zu3|1i7w>OK5d4g64d|%?S@YfaVa#nlzivU8xNHB-@y4g1nAvxhZ{_P;(wdS*Jt_6u zCZLv5*>uX_`i&R5l1m+mbK8e6>ylGX8l<0h{{J!Ae><$dRDRzP-*UVb=-RlUXwwxT`RddT zdjNUkZkf-Q0=R&r>`$DI)tu2%U|pNo=hlz`W{;Sqyc+JhqRV;{eHKUhGuJo*;kA$Nm6}xw&vPiHj22646(CDvuR4&rn-m zS}UVhw7Sv9B^o>5cjUgZ#XX2~!z3=2(7`QeFvHj6*#76I-XPg=w5=U~^<6xL((LS* z|8{>efmHO#`fqi4;-Mp*t=M54oFM%)YOR=OEKDfvWu&0p_Z1F&Th_3s-FFQPw{xfe z%sRJ4A0c$%E8*TRw5UXjabT!w#A$BR;lR1$DuDCmecV5aqKzCX#k@2}Fy5vn`jU?c z4%^`_j#?myA#s9?4j3?kpK(&q8YPTM;T~n_26jXiY6lM_X2=TV8UsU&NOJaSWR_~A z0W4>X5=IycLW4vy^y^0wn86vgi>0uSh)aspOC*h70{|OeeIrBe+((zv+%#uC~#1X;u3n@677A@Q0|+*xI=I=-e7Q?l~IEvx$573|xRMGoqEmQKW` zb>GBVsI%ha`;=ebiYV&pi=LA0R}X7}0Bf6cfB%_aCEcmdANy&U{xo(pu7MW9&1oeC zl&OijfvG`_f54p~GC}W&0d}x6!H_z`ZM25g2ji9klBuke&~D`bLge*&bNI}Nj0({P zj;bkMWgc>-&j4hKTBjO9r2`10h#G|+6M+g~;Jz{@lq+HnXn}Gbrydi5g;;|0Dg_WL z!i8iZv~neqdItd<6%KA>lv;(2JSWP=K#rnAQu)6j#)PCWyrz5LXYpO*eBJ!&@V(_- zYec^A{=>cX-}@z9?$4+P=pAboTRGoNsjzx+rd{Ft{uA>OQD54xp9ed~)?s~$x|2HS zvv+sgD>J~E^v+w}Og*(Y-9zqQ+Me!*<4U2+8E_(h8O?3Ux?SvAD@HHdX`F)^IVC>V zO5V}qf^dd3QmxR9uh65Z|gbJn{ z3nKlq=v4L~*tf#~u@a33yYq$N++nhWmh9iKB%!6YL#8Pra?drrz4d=0h5u0Ft%h~$$!gb2xz+koCl}bM@YQaCK&k@B2>o%G$ z2IFv7XGvj;M18{(S69pLx7YdF5lzK-ZM{zdtC7{GI5qh$9?leXX)ZTH=+dH-bs1b@ zQT)lO^K}_*ZE+ooo0D-NIG)1$$<`HaO6^kZDL%K8^a7~N9}JPQ@t|>6IF@ct+ck`k za4O`FoU?TpDxXp^2-nDIlQQ7KxEHi~l>+02LK>vI;drUAiAh)RKwTR~P^wB}x? z8dBwo5O<2JfQY)ok3r&83da}&g9b!lx-JHiGl2$Z(mEDKoD0B`fxJGY5DP&8NVE8_jhIkTumP$f=zzXE-6|szoXM7d=6G|K z2IbL|_4b-`hK26!m3PKCbgDaqFF z3)`E>4(s$^QA<2aKG?(IIYU~EotUZHji1d4ADUL_8-?D_Ic}YLxE19aNI@S;eJ;w$ z(jw6Q#d5YG^>r%g_){fk!i_nUDd2OZXLZ1IBAFFRJEi-@D6Am?00_nCo$0D@B#c0_ z+=!(%Acg{Xv??klmMh@_XrT0N1r$PMi!7AHItLO1;00)a_-X_aLZ*cz1_~->kSk(@ zSQD`Eh#+v1=0kg|Dk2gC4b<7#tk7s>7f$bhp9S>&v40A0{sffGUu#6Yz1~1Gm|fvV zZ+E*nQpsF(4bLXaNeS`!e?04Vm^o{KdG|%^veYb-`M>c#zr8k$(CHm#fB$b6btjZq z|E*7P9{3E{9R2Nm=@E^Q=zabDzb6$f7peZ?kF38@JPL|ez;%uQ&lpCcQq6E_kf5vJ zrd39I&Q>nG)TXk)Tgr@KfB1;mq}lhR!6?MY85oNN;_h<8@%S=pQb^!hiQr+XpuiSz zWEygYoJt3dAVrQEXB-1z2zb%_DrTAk;tpKmwhm{M8$*I*LHL2_CRii`aInnqwHvWhI(Ak9s!-S;fRJ>0>5KDw& z$XtE^%5tf)~1}W)}TF|BBNx0vnAacqdLl=8rUM7H86@ACK4l|23VjzengT| zCJ!X1z#gR<1Hq4Ag0LQDkSm}FbaA{6gdPK7iC9qnc14tG#Em>vNDgNhBjF8HaQt6H zoLmLgEN3tInK7qJHFnBA!_LbqZ=~xc_?8is$tFQ-~OeDP&#X z**1xHJFzy9n6>nq#d~P9hgP6c^Xt~0gNP;rggF3f3O$S`xY8=%a4Q}mGO)VROF zvLTIe=ikY^&CddlKnghWLNP#{cW|Rxz17Jh69w!hxfQVd^ zbRH&}17nY1!}h!ju=%DKpWS{|BbG{f(Yfy!#h3&;&9nbhAGz6E7pkbfnOoSN#m!0m zp9gV`pH1$I_R5b~;n}tXxcQ7KD*StBCtfYIyg=YOe!#mmM*cpz*=L&RZ4z9k(-c?W zs`?Axl8#V)89n1^-wNy2Z>e6!+Ul{xxnEoh#)rCq;~P(iU4Vcurn?+@d99o`4-e0u zvtWY#);&wwST0&Z{)st;&h!`oVRsX=efHG~NMh_RO3(MPvblm}EfvvzMG|U;0UTC{ zUL=rWXAd}K3=*ZD69W%yGxh>zm>YqFSimxVL=s{Lfi&j?7G@d~!VheK@VlZ0f+7WR z;TqKkwhL#3Iq%4N1sGE03n*qr!HkxAP_lCuu>Z}D0L{qp@!47d6k^+7u}_kP&Z>}1 z>x4*Nf)ri+YOJ^Lp_Vk??1}290_CC}3kN1%dfIql(RsbJn3Ug0v2=@AO zL9qk_{Q9|G9x#bF_=w&o?#U&o6@ZUcR|gBQw8LV8(~lmRB@QwD znl{E9%h)PouMhMm^Tdp#NVo`T zbhyNZB4QHYlafB&j4~Tkanxp@zM+7bRV*u_#O5LZd3Eton`y|Vx$B z8=wkMfH0rHobZr>nQbyALdVyq0z$fcN3MJe2_M!-aXFm^J@ z*0i|fl2A#=A#T9An#_T}nIq+`{hWl-4-)m)?A_fyaO}m96~)*du0vT9xM5?A7_I}T zz_Z5qwAz8X`EX%`bd7yotk`}U@^H)?#opUwa)2(F-Fr3u@NXCYoOrA3t?d^9aj(;z zC5&)BCRf|G&!tJB2Gr|@((x&*Y<)gOo7eT32%D2dyPn+-C_GeYtm^824v^;Os>A)N zT)J~Zb%W)jfPo|`GkcsAAZmt)de-)C@%G5Upjg!55w^swViG#yl)a0Ga>o{O<%B%M zjC$r8V&x7TMa()z7CWI0TyVO2BpO0y11PpeV#X3`hYTDBN;ZX*J79xMQgYc3Hpt-` zSn}u=MvyyV25e!rgM${}fQ_*%y<3m@Y^Sx@^HJz&G6M9y|F-x%gI((@ty*&4q*AvQ_$H}^o+)(lUK3r znutlefp*k`>roPa4Y|`QFz%fc|HwO`7CIs;yoa2sLyq&c8MT{~oB}oxGu0~v%;4v| zNrHcY*Mrm z1{xa!2Y4WMegzV8rG*^!gbpBtb4LR%>ge+VGAI=OD~QDHwdM&9p-4s=^;D#bIO)5u zSJn4*+Pm72Og@>F-I)c4)l@blb3(Y5u~ICPo?>iKu`<~aC{BroXLL1id^ z%Ib{Kf1FIqY-lUkslEbnMR&_irGdVl_+ed>7s9+_D8ODG-g}(A0-$jWKX-A(Tjq!| zJ7-{GfO8=X3++1#;Ov<~B4O;mS5ZpQ00w&8Wo`2NV?>BLI;~KFfF1^%3LA-{h zh4o~Rjb-MPq`?J)o!u-XOFLCoc0adTrfUg^2h7tkL#^7QCB?LmVTxq((kt(84g^g@6lzK&!a)gE?mI@w+gmR!6 zQRu?hOumYSoLx^cPR1BgCJroTMsmlPLPrY>^~%4&lKUbaaOvc=`Z%D#gtEvA8}D&R_69ee)#+Wl<4}`!&Z9vA=TyYv`K|%Eea(pj@Io}2xlj?n8FkL#V@!6 zROIR#O9kC!36}PnpF@^wo0hg@!r%LLzL#iJCFlWfw38XoKiZt}1A_f=}LyTCFG9KifJAedSQm_sN5G&%1XyE)F zgq{Ne0Bj(94uptOfE!h*f=)sPBEpAK?3Apdwjom6BQ!{zW`IKV+VjKOYDAV>J1ctblTMf{WZj#vuW;3)yY=bJ5|@xtgGlk!ZJNh%ZBUJ@ z*ja@07w6<{D2)ev`Vr@Z{2(shAC|WB(=1wws;T~&8WBF$Q_~EyvNV9NMaY%ddM643 zFIeB_f;PthYfD-2Ezzq`k-R zeX~0AHn}y^9#aF%CsN@uk9)x$PUu`7n5Nr8GRW22nOCnn9y$yaI$EuJ=9)^8cbhPV zs`sSv zqOb;M7Bip%Y!P%GrIb5EfLx=tDg=}}p$9|}z6wSfBVdkj;qVTI0(znwVcEobBZwNH z%|;xUoO#l_p@i{BHmsN^3D6RZdmQ}S7|N?t6!+Dd*IRmQp?rDh;&NLquC{-^KZDmo zMr)&?n9;kJUN`uEq~j^W5LtJqWrN1Lmh#0VDsk7gFP_m!9c%N)_MrOL-!!_i)e3~e zw30KwUyrL>>azxji45XU!;0_@@s9wGwhO$WwYP8q1 z?B{XaXKSsSCA^as$$r`Ioy$|F)7gi)bMgHi!*OqT3~%V0g{swgu1@j!B;?wf&6F89 z3yUYB{r_h8GFc7X%iM4qYe|Z|ZO&P48mS&IKmT7qfGV^$(F#(QVxoHGYJ2EY#u1#M zfj&m9%+#+qIO0~4#Io!KGGX9^BtjJM$39sjj3ffuegv3GhJ_H^g&I4+0DSWHVnvc-I}VhpKm||d01|oRbV7)BMvx**BDv!7d&7h)Jv=j_tP|jv8z^R4 z?&9D+ieukBTz@^~-bs*rYELh<=~_juxGKR}!Tp(W3<@{AYoM_ibxpmt+)P%T7=Kkf zwfue*p}P5fKDX>WQ=lJr4vZzbz~zDd?VRpooGM%B{AHg#E!82lo-)x#SbQUD0nSrM z*4waDyBdS{_g0;{4-xX7SNFc|^TFq6zuDPrPA-(tyw4onH*aiGQ1H*3hM~CJ+uX>i zTo~N=6;27no7@a~=@?kjwaR^nxMXFn1N?)tHp_-}jC4Qq7Cfj%y;`(4wdtsEbxvUL z)OyDlQs;#rXO4bF9zvvn0M!aADC|$MLh3_Kkl6?`{T>*nThn*~=O_Lt zB5O<OT&VNQfPg+}M<4Kmn;Q9>;p|Vpqi=$#z2m%3;iy{K z`?J9rC~j9d815F&5Q@RCz4fYe0oeJGjK}=^DWKy#iM+aGWX_w{${+1ml;bQUubYl8_qwX5S-G6xWS6srfZ(ld{%2L!bW8srocqKi;)ydGy3 zD?= zffn+36^g>x#eZu+|32#Cy^y?TZ~woCYuUkC>*P)QmwpN4|7WMYFV&(p;9R=JeI)_! z-TGouc0(K3Ey z9#SU@2nCAbqlAJ}l*KRQ<|8&J(#4Q14oU`^LZpK*wt@;K7z^M4WWq8Egq#~g0(>EI zdSsqL<%JYA!Vbh913-m*v3?R~nggK$R1r3AL=-~Bhb-iZ4hEbXpaf{Jej0}adO)0I ziAG{Xl5$1>EY`?=CLS|Kqe~dbHv^s;kz<^e&`ScG8=Q$*mzIA-kOJvZIz}*Z0tO>W z#iZuu3S|~L$c4JOPEO~Ldyl}{KCq-4%_H5RYuPKw+FQr@|J0InnVV@Woap_v?u{6K zRe!w94%LX6YhHOS*gG)HSY;Q#W4H{>ncwM@j$@daQ;nW(ukJBTJpkQbzHrY3xYduj zLN2m6(D(F#^Yofw46Y?|&41<&8p|kbVulYxfI@pbd-fb3d_pSY#eysP!j9Uj{8&rWzs`#2y&SvSvCRV}IWlz3AHe+} zB5-3^rc=mTz@Bq(7E4yFoFO_1QPTrNN^S;>8qZhr#*X%+in(5vu`ysf<&q24^8a=A z-L9^2<#-Ug2HB`QP;i|YT-2_II?5-KToT^MC8j8{Ts@ht8blHk^GjgiK2nDsQfC1S z6%Gn!lsiL$d;<0w#U2|X0!Y%dDkhXWp@?*IwBCV)f{_>9KVgOLqEV2MFJZjwK340( zTjK1fYQvlgFf;_%qKvH|yJ*1bxJA+1e1a`BYXEP9_d!zIFO3S7f_GKO$}6w2XW|mW z#kj7iugK2P*Sx5CocA}P*TII`>tEka&Q2W_?73*(%6Iz$O_swd+TLfMNE045)%76XQP z`*yy6=8$SJ{hQ$e8>a?Z<}dc|Mn;4X8IJ63WK&* z5+lM5XyCkl#0~>Cngu?zex?!=hNyRVj3?rR8!6Q&xn66=5HeVh4#zrPV3=x((W%Uk z`JRke+g}>LWXV#Xt<0nmS#SCE|HWG^XK?qFly}}rBp#bokU!rQ*wCZOCfwa!P|O*_ zUzms-j$;{?bN0kfYo>xKnde9miZg`%Ccr0-^kIo6ro;k0a}oQTolwMkWJtIXiJgB49y$Y!C5SEmq7 z_KBtBL}j*c{SJ+>m`fFooLvl@Y!?ffx1P%{>a;T~KD(UH;B1zMsI!|K%=@nbeq3R@ zItJn>l?j}I@7|~ZQKZX7y~-T~s01XsgEr_7keVQKvB2V>2b^gF56>3A$b+bm;1+Q7 zQyQfj8v}rJqPTuVkYZ*BBsa_kCL9Bzj7Z_IW+ak&B@aB6#44l|JHQKQF|e^JCRii{ zY_gULri6Ne6>(@vupkgBRbw&?&%E3iYbj|BLiMG4$#vK*Vuk#;m-*i5Z896*-ID0J zo~;QuCxMn}8hf9T+cUXwVi zjx>#?nBYrXO6GFqq&)8yoRNY&-xU9<8)}$_I>OzYa(}Lo`ug!Asq9W^59XV9B1)ug z=uevd@^YwsX>ZZ1Uh;C@2J#CVm`FBp7=dSa-mY;p(TUs)Z4$oLrs?GNh4&JO|D`29O&=4QNm}5=Isypay90GAe`; zGr)#)p#BaAoD1U$P%*dy1`-ot26RKPHb)u*qJcorusR@$0>+?yS0>u#JS%=o><~|1 z#ey1$<#@A7Pk|g-EH4&o*pxil;tW3v{cg=1!GH{8cON~e`$^PKa%9RYpxQ{}*um2r zC%Ks2_ncn)3rK*OM~+&$O;q#voc&BIF&ALY>^}QIDK;nL1?b!5jhkm@S4z2EyX{YW zkrANjU14fcI` zie?CiL9=uKV%FylIns?q1p^vHlp7-gKoRy1MwBbV4pe}!dgqX8CkHr1$aZ8DdPfI5 zwTL<<5+lHcc+<8TW}FKkj9?M;1}2m{qKROlusTEvQh*SB*$O7+h)VL%Nf^xVXFh5J z(fOwiw?LTUYTup!SmCFNAvgh;+k!4b2x^W<%rH<^V;XY#kfAT!?L2dr_+=FHO|H_( z4*6s{z_6OL&kkEGIx_j1&{cmqauYZW#y88~T%#XJa?cQITOac!|J%B@ao7J}sygVW z_)!I&#``QgxBo)GF4LP>bAnhVVBF2I%#`k(E->tnlBmPOST;Hx6~Vm0_qj4MKGxMg zLA$xIW2+Rki$8{)XbO^Mf^St@V*(-t_{r&(^RTACA#gYhrLK}HiGzjj9p64elq%;E z8v@kKr?^pq0sxX!P9+d}M~g6|%5G(pYNrJR1&EA=v?v6Ha7UnGq!>b^fdsb>3Pc2om`de|1e6Y##Vkprka_)5Zo-~h!xK~WEQDehKzG=*jFPImMrw*U@TfhwNIo1Ky7Q-|_B2KrZ99~fsiF42ONz?Ru&TfPXEuwi$fpsgb5%T;6aKBa;6@Se6~ z_s!tUd=rYESv%YjEs2uChjoZ@l#h#t+u z@H!yX^*8v%FJgikLS5?cq~dN6yJZ16@R~w~l6u99H06#O2N)ZI2UwwaCMT9VLJDY5 zwqj(EV&;zwRE%mw5OQV#B=!mpq#6q&hI~`JZU>TjqyZ#GfGXt{1LKc$!g8J@jOrtS z_PTwvy)@VX$M77NKSByyWB$3%?1_7On!DFzINOc-dWsv7aB5rP&`b$U7tAaYo>WSC zU5(R-2F8@hbqHzv;C$eJ_1+z_%$tp^|7EOPr;Hg>*qUh}S_>SE5k!CLBD7$rFo^Wv?%3M}|Gx|ATUfN!UKa z+w4XYERZN?kAO-NaFnNI4nDvjus{Z1ckuF~#{dgkiRQ=Gj0Zd^J){0;=ef!@Ow`K36aJ{$^OqZV-L0#J-o< znrqf6DZGO7l=_UM4W-bNu+Pp5>+p6U`?1&74LPleCYqN+rW!48O^CkMNrsDVd;ZMk z5=H*VdR%Im$8>U0=z;M~kgjpMNbkrN;K{Jh!&9Q7TqYWulHrC4x94E?}=+12d-$S84Ffa^zUW!h7Rjls8EwOHt$R48EtNoyh|S(z>Mkr^7`DP zNs+9XiH;a*PmP&G+J*o4gUeR1z)!)exguHeHt=%jt44}|mdyJktX?OQLYGD)CC#h(e>1McRfQ2AB)M2xu_8 z8U`K{BZ*{@_zngZGvJC`5WJp6jPj9$+N(e7hNKeWNY6?GPvQh}eco^^BF%VN=jnUM zV>IoWLzele$!UbBTiNUhdzHu+oEuTZIPk3qQ`{dz&UxfoQKW#oijqk>arM9XnyyL; zdR+Di@3puSOEG)Jii1%;Q#foyu-GqzD3CJFLM93(8q71@|SAX&z{-u0WHj=;cq|I0>81rT!JFu$>kngb$8jI~tvwDNv_De+fa43uyF=FvW!`kr&E8@9QtDY$ip1Ra*cj>yNs_rjOZ`ai#YXr<`@FG z*%}w{uW&XBSc?nn7xy)w3#-8~VX#Z#hB_tp-5Zjf#iS_U1eJsw=HWD&w}@jV4vnvX zqG20LTH>MTBv|JLTp-j!1ei*Mj|9|;%7+FS0Dw_-g&qf#I|2Ye!LS-99s|OIT=Vwb zNuV)fj6Bt9eukb>M+H2#&ORp?6QPGdaJ-!WoH-MLbpKISf|zR&kW;tnULc4nkqKIj zR&Ib60uwt7D(au5x)WX0cnvkNq%cUC8E(}Uh~vuzCHJ zJo^f%#j#)i)n4+cg5u!adryhw)^?GM-TNMn<*wsyoSpq99i@iyp^W+G=N-HHL~8eN z!5#^;f*+5!5MQf`x$*28uiKn09t!tNe>>s`Gb&s>=+a8XglYsieslb>?kaIW&<`>E zO2nR8q{#?p%1gaE{-<4%aEr=(Hl8daT?_{6Yr0tYOSR%S&WLMM*| z)J_Ve91GwIeBrVpW{^98h)6T|4h9xGzzSS2ydFlBJ7NlS;P@g&90P!WaKSblMs^GArnR7p^x> zIt6}*>1)Yca{VgSj(O1dd30XSw3${oot z+11qBKel>+>yIjvNs~V+4lqPe*_{3$J8zj2i?7W36W- z=kg}zhC?V`mw-O~J#vRAWh_HY-q($CTX-dx;HOZ}-PpAeV0xnxpR|f%5D89-vsGW& zKPy`U>KETMiMi>UiNO5##?1T3*bayq?PiGY=qd5!*?r(yzh?bD`_@v>y0;VlR-%!2 z5>9%ctB`{@!7cA}u}h5GTT{O*WhV?qzWsk{>G)CK(5qZxBg|Xxg00FNtONbXOFfmM z_&O>+jY;9P)4#HC)4s|ag^Th!`_?y%w|lY{p3&1}X(*Q_LzWsw)guAYS_Pa+MTl|)7Fy>CD5eT3XO;`Vj9fChD&`z30uE5IHg+YPN~Mh?1jueh5K`oY zJVuN@N0uW3hETA$vu6-0k}!4&+^7w{Fa~$MFUV(Pm@0VCK2`Y19-crLt6Uq_wED@t z2_uw);niGI{?9AFFdnJAQNJkX`-h;pAJ?bbDf?FBsoc@0+a|+i=$+nEzsqRHR^|O) zVO%aT-txGu8-Ha^yU?%Mke_gfSR!HQZn++JnEV#vZs~}cZ-P(Kh0Eox6@WtSSq_}s zmJ_nL33zu9lHT_Ya=X73=48Y0=TMBS)S5)oHTAiiJ-7}=!!q%!dGP|twSwAD;mkWr ztrC_#T>E=MyA2h$T|8PY1I{ZQzI-BT*o*@Tr@EOcQ~wii$_c()He>Vmj|buYn<{!S zyrHRwiV;46!9I;@;lgqtqV&EkQ}B}#qKO2sL|NB+W)>5(7vOF{y-K{8K#W6W_VO$i zn`Q$65tx2u9&%+0DAbBR<(4y{3tZB4W+f0}_*boX(c+l@%_}`#xDNAA1 zbFFx=0PmKo5R-)20ICW+b(cGU+n(1sD_q^pgdD4UPoQq^PpO`62Kpmk5!9XBag={j z>g=NAU;p_H?jgKY`8(s+oG4=G9-1&&W2$&7YXHZV3dY=+0;r;#Nxjv6-z<*R z^w2K{OMmyb6=>T^-g8S`nw!WG`3W9<3fi1TTFV9W-NyQzk7_Fc+)RV&*cgL39e^7} zu37C^o{y29o6a2Q1Kq`2jM?5_suRqEOWv1`-{cBXrh-3F&N(DPa(F0;4%OPwFg;4xF5PGHT1b(&xyF8)4Bsqp)8r1pwqa|VG-t~ zIIhr25vZoMjoSo@o`&LpwcR~WJ{YT%_B56F%T@|^mm?P#*P#Z<@}#9F%} zE8btuGTsWWvuhsx-WE&qZEFMAbA$79?ymb*zYCh4!}Q9lB#iokd~Z#O(Au85-&%eS z?2+jn!}h?qko?vU5fE!JCYqni|7$=(RErOQVKEbX%gemO_RWVke0(RLFis z5OQP<09L{dW}Xw{i+saydS#Gu1`a&r&K?Jt8$tkN;68rm9zw-~6qn553Zxlei9Dxj z3TBWifdh1tvR+1v8}wE!4={H|m@|{JwXPu!=kVDPgng#g|1fCKI1cG_O?pVhOOkfojd9xftu5T$FVdT!<|5aT{n^SVz zTDcaS)a6``;y)`34GCi$)QGzn>hYqoN4s}H0up7r!Kjk3<``)rP0wUFxr?Si#sEk{ z+IoZ-N(YY!hKdTLk}JUuOb|9A1|AdP20-JsVn-TUM+7+3$O^=s8^8-#6FLqD9vk6+ zd|6ox*GU@{mD;h4V;qDEpCZcu+E;A=?aW63?LGK47CKD@*6%)(tr=c z0%E)JZ#2*=A(|6uD`I}2Yo`JlOKU`8&P!*6GMOiQ5y;}F1Q@#luWoh80}Lqa9nYKM z^OIf*`G@ccdCnI_a;g0lRPBSd*(<5EknpIYw$CiQFO*e{yAs(R59;&dgx53QZ;0Zf zWyHTR>mKOtu8|iES zdSQzhgOlB_Sn<^y*zV9erM+%o`0=Yc7v-keOnu!ohKlQGlUzYZs!3@^d&z5U9&C0B*vFJ90L-5$u2N4c-^u|3LA#!FB8(`%$VoPmp) zzT3kosQ^Bl!mFHE@>PBM5unq~P>Z1Uj&#(rx6Xr;C7mTZ%2&R7G;3jpzKQOz5YfvD zX@pM49zrJ#IQ4=$M;0^S3qY{&4#bcv0lN)E^1uRY;k0%I7GmXr7=*|Q#S}Y32Y7&b z3I!G;;fZW%OXz%udaUL5( zrhB@y0@$co?Ixa*F#`FH3c956nSHRrZ?AMlHjEg*y`4KM%LtL0=^Rd{_2z4!9ofcQ zHakD1sTfJ%J@sgNZ>+sd2555_iA&ksMSJ*4_c*uQ_*=j{T&lAth+0|i$fi)8#q6B- z0+XAeE?CKyMr z)qCqnVhNvO-&2}6gNkAhrO_;lp7>3Us!;Ri8`=QMpL!)O2?9Vg#!fShUmvOX~Kmx;?OE)7Bip( zRI)Y#gq#Z_h-kv_+{J>^!WVHMxDp2(3&Mb0s6|0y&N#;w)l7GQMH`#^OWL--G;TA zIEpxzOo-6^9Qg7|#Yeu6v_1UBsTkj5J<0g_jxV`qQy}TN;m3TfUiuQvo8i~rY+lP) z_}y{Nk9gR+Lg?IKzr5l7Lb{w9Z}&{O`US1_)yIALYxt9VvozBW=<%F`s>U`;R+xG% zmFb)G)q2ZZ>))fw6y3<&&ZPUFo0Q9v%$>lAhOgH{lI`2rNQLH7Wmt*SXUqn!(W6NP zf4**spLYgR)9HUuD%Um=p{bBeaFogwDk7)3T0 zrUqgmm_-}Nc7c!z@qYkH=(}c&JNU%v#F}p-fIIwk529or;)Y`D^JBNz59J;bfAT5b zK%Lma;2d^*7$J%K*M?sHq=g6hU1fZa@WRtSlwIL}u0ERx(V5@vjNYV>+uisd`_9fi z&>BJ8m(3*nbR_6Lzt>x4np@jn;qR7RAHLDMix;j^TY$|OpG=91wMnx5l-wwVl96RG zOdUVUceb-4ar8ZkK8@pdn$aS8%|@iYlL)fs{O-olvN3|9*ZtB|Q!hpyVzrl``ZkD0 zG9ZUUKz6D^MUr|(h7?tZ3Zxzb0}5#Jur7vR!jd8(E1-84dnF!nar%eDhG#^U5{Mi= z#xe#0fLeTs&`{bM;{-~b4csysdZ3OYD$Iz7F!&1%i-gijzz8cD#jMm~GDdG56vlafF-c)Ep|5y1t^~%?Ff!YN>s)y6|YT`HdlABep_NZ$A z#bF1{yA!@*gQ2Rr?cQIK%4X+Yt=*u2v>8xcCqx29#vDpUi6|9_Y9tg&Mh^(qz+%Ue zYJ>e6l@^`ChyOk(eO1ljpz{4Myef551@ced@| z?@XGSQ{8lnsP@bfp3_eyDE-6dY_8E&OsZz)Y*YV|QAeripLV;;lG$&0skauniq_lw zxGSGAAj{Ovo>;icqq2(@^JU(z_Ltzw)^9&gbCOiH*R=VK9=N#w1mhH8g*?jk%67x@ zs)#8Rh5MeHG@?@fg;jP>nsPi?EXfvfh`~;h8B4$u7nWN^8Zt|kCRKVXMUV?ObK6w@ z%fXDm$65U1I##9@dgPBJwoW!h79#=*WYVyHMG$&~iZnNbIwz1TfdOO_ejcQn0|F0x zvG4*%lq=v0c*3-Hg%EP&i!}DgItHEtU;<#1KA26QVR!Agcm5AX8gpzEY6F4_rVevD zmWRW7E2Iv4-Jk%beiy`mQ%>O^G`?@=gaR64Xu&{#$AkjgZ&Y$hgTjp2T5~P3ersY3 zD-HHPgW{~)Ti6@>ohlHW-`k&9X5-bR-8y}RzUra{c7*uj-~Tx3mLbY8&E@-Gr47jU z@FGj>eu3!HHPGv~I%=A}~RAzgBO9 zniDOGZN$<~r5qyYUM}~1dSVU(JA|;O<`V{lOYANfCVo37h)eD505>*wMF2WmD|n!Q zAB2p1y(=gedLLwjTR))`rM^Fd8Y>v>YQC)S{lncDzq@s})qLq}y!rZrd)1lehW4vkOz4=eQZ9<5XuZwAuagebVB`v)8rCy5QSmIxfP}y2vOrd#6g-g9 zu@-3*P7nW-ovk{l1{FMo+)l@sTICKrRD>D_kUJudR8anAWfpP=2`smadL@#22Y?(k z4n9U66XAet!1xXZ6g$8OTrj*A2b>#14{&iXE`=5YpaW=u{x-Oxn8!J%ixcP@2KY|o z)I^=#*-5^+DcvhVky=x}JiVmEimCZy6g?r?m87wmXFPnY;e;d5nf+$0@vQ~0tz7^0 z%`kzYBDooH@4LA(#gOg!>E|shN~;mNmzy+Q)$&90=5z$IY)Nc3vhO#VsB+Y@>TTop z1X#6Oi5yRWiX#lRkClnWvNd|>nrClVv!3qZ2?as-e{#f=n3it|GV(3 zhu{XpkSpKCCb9pe8-JOo zg_TcBg4JC(ZqVvNzNR%pX^ud~Xi8oa_FpcF}=9pyEmuJ5bL(a(|$h58QQGaaPC<$9T>JxJgDxa^=uKttjFcb6osW+!qFiSSS z;rolqQy!r*d0PEUx|EFbNq{tC&aO}<@Q`b?Ae^lwvhcM{lCwBUMVfl$h%A+c7Ul{< zqL^j5${HpVGXaETllE%IlzQY2G&Vvy$DR|x3S0pE3Pu(q0}5dAvQs2gbqW%4rVzT~wr!|pUAOg4e@`*y3nKFUs0iFGU zp#8AQTT=f4zK~op>L^#=akPkCpus9LV6YSXI5uItW?({T?0vgbTR8*K3;mz(cN}X! zHQ>0}`@P(I4%!9(63GRb;D1P+tM=QaPWgYU?Ba2AY%1#&n6#b|X%f$m`hMxZcV*~n zhpJvn^0arZTG;OV7xueFHEnGn$32IfPnJ9+5s+=KUv^%+3C?@eE=2%y=^v|tzSbfn za61ue{LE9PfE$x&vU>cx!nk5$Et+d#yo{J>QTrC$l<{t|5T&jyV*YNU5L1F(OJ?l! LWHf2x3p9e#wo_pP literal 0 HcmV?d00001 diff --git a/announce/speak.index b/announce/speak.index new file mode 100644 index 0000000..30996eb --- /dev/null +++ b/announce/speak.index @@ -0,0 +1,63 @@ +A 0 29 +B 30 32 +C 63 34 +D 98 32 +E 131 26 +F 158 31 +G 190 36 +H 227 31 +I 259 28 +J 288 36 +K 325 28 +L 354 28 +M 383 34 +N 418 32 +O 451 29 +P 481 32 +Q 514 34 +R 549 29 +S 579 33 +T 613 28 +U 642 24 +V 667 44 +W 712 40 +X 753 33 +Y 787 31 +Z 819 36 +alpha 856 38 +bravo 895 38 +charlie 934 37 +delta 972 37 +echo 1010 33 +foxtrot 1044 56 +golf 1101 38 +hotel 1140 39 +india 1180 36 +juliette 1217 39 +kilo 1257 33 +lima 1291 41 +mike 1333 33 +november 1367 38 +oscar 1406 40 +papa 1447 35 +quebec 1483 36 +romeo 1520 39 +sierra 1560 35 +tango 1596 40 +uniform 1637 45 +victor 1683 34 +whiskey 1718 33 +X-ray 1752 40 +yankee 1793 39 +zulu 1833 38 +1 1872 34 +2 1907 28 +3 1936 37 +4 1974 35 +5 2010 37 +6 2048 35 +7 2084 38 +8 2123 28 +9 2152 37 +0 2190 33 + From cc47e27cdb7cd9fbd742110cda0bb0d9e4fb40ce Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 21 Oct 2018 16:19:48 -0700 Subject: [PATCH 140/553] changed index name --- QnetLink.cpp | 15 +++++++-------- announce/{speak.index => index.dat} | 0 2 files changed, 7 insertions(+), 8 deletions(-) rename announce/{speak.index => index.dat} (100%) diff --git a/QnetLink.cpp b/QnetLink.cpp index 2dfc28c..994b49f 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3598,22 +3598,21 @@ bool CQnetLink::Init(const char *cfgfile) return true; } - speak.resize(62); std::string index(announce_dir); - index.append("/speak.index"); - std::ifstream voicefile(index.c_str(), std::ifstream::in); - if (voicefile) { + index.append("/index.dat"); + std::ifstream indexfile(index.c_str(), std::ifstream::in); + if (indexfile) { for (int i=0; i<62; i++) { std::string name, offset, size; - voicefile >> name >> offset >> size; + indexfile >> name >> offset >> size; if (name.size() && offset.size() && size.size()) { unsigned long of = std::stoul(offset); unsigned long sz = std::stoul(size); - speak[i] = 1000U * of + sz; - printf("%s at %ld, %ld long\n", name.c_str(), of, sz); + speak.push_back(1000U * of + sz); } } - voicefile.close(); + printf("read %ld indicies from %s\n", speak.size(), index.c_str()); + indexfile.close(); } return false; } diff --git a/announce/speak.index b/announce/index.dat similarity index 100% rename from announce/speak.index rename to announce/index.dat From 1043161de555de0a83ab659543761985202abf92 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 21 Oct 2018 16:21:43 -0700 Subject: [PATCH 141/553] formatting --- QnetLink.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 994b49f..0137a5a 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3611,7 +3611,7 @@ bool CQnetLink::Init(const char *cfgfile) speak.push_back(1000U * of + sz); } } - printf("read %ld indicies from %s\n", speak.size(), index.c_str()); + printf("read %d indicies from %s\n", (unsigned int)speak.size(), index.c_str()); indexfile.close(); } return false; From 29723c6159835d0d99a36f184e84504958f19668 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 22 Oct 2018 09:04:03 -0700 Subject: [PATCH 142/553] play sentence --- QnetLink.cpp | 114 ++++++++++++++++++++++++++++++++----------------- QnetTypeDefs.h | 1 + versions.h | 2 +- 3 files changed, 76 insertions(+), 41 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 0137a5a..ce979be 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -2799,11 +2799,8 @@ void CQnetLink::Process() /* It is one of our valid repeaters */ if ((i >= 0) && (memcmp(dcs_buf, owner.c_str(), CALL_SIZE) == 0)) { /* It is from a remote that we contacted */ - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) && - (to_remote_g2[i].from_mod == dcs_buf[8])) { - if ((to_remote_g2[i].to_mod == dcs_buf[9]) && - (memcmp(dcs_buf + 10, "ACK", 3) == 0)) { + if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_dcs_port)) && (to_remote_g2[i].from_mod == dcs_buf[8])) { + if ((to_remote_g2[i].to_mod == dcs_buf[9]) && (memcmp(dcs_buf + 10, "ACK", 3) == 0)) { to_remote_g2[i].countdown = TIMEOUT; if (!to_remote_g2[i].is_connected) { tracing[i].last_time = time(NULL); @@ -2816,19 +2813,13 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", - to_remote_g2[i].from_mod, - linked_remote_system, - to_remote_g2[i].to_mod); + sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); audio_notify(notify_msg); } } else if (memcmp(dcs_buf + 10, "NAK", 3) == 0) { - printf("Link module %c to [%s] %c is unlinked\n", - to_remote_g2[i].from_mod, to_remote_g2[i].to_call, - to_remote_g2[i].to_mod); + printf("Link module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_link.dat_UNLINKED", - to_remote_g2[i].from_mod); + sprintf(notify_msg, "%c_failed_link.dat_UNLINKED", to_remote_g2[i].from_mod); audio_notify(notify_msg); to_remote_g2[i].to_call[0] = '\0'; @@ -2851,15 +2842,11 @@ void CQnetLink::Process() SDSTR dstr; int length = recvfrom(rptr_sock, dstr.pkt_id, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); - if ((length==58 || length==29 || length==32) && dstr.flag[0]==0x73 && dstr.flag[1] == 0x12 && dstr.flag[2] ==0x0 && - (0==memcmp(dstr.pkt_id,"DSTR", 4) || 0==memcmp(dstr.pkt_id,"CCS_", 4)) && dstr.vpkt.icm_id==0x20 && - (dstr.remaining==0x30 || dstr.remaining==0x13 || dstr.remaining==0x16)) { + if ((length==58 || length==29 || length==32) && dstr.flag[0]==0x73 && dstr.flag[1] == 0x12 && dstr.flag[2] ==0x0 && (0==memcmp(dstr.pkt_id,"DSTR", 4) || 0==memcmp(dstr.pkt_id,"CCS_", 4)) && dstr.vpkt.icm_id==0x20 && (dstr.remaining==0x30 || dstr.remaining==0x13 || dstr.remaining==0x16)) { if (length == 58) { if (qso_details) - printf("START from local g2: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", - ntohs(dstr.counter), ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.flag[0], dstr.vpkt.hdr.flag[1], dstr.vpkt.hdr.flag[2], - dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, length, inet_ntoa(fromRptr.sin_addr)); + printf("START from local g2: cntr=%04x, streamID=%04x, flags=%02x:%02x:%02x, my=%.8s, sfx=%.4s, ur=%.8s, rpt1=%.8s, rpt2=%.8s, %d bytes fromIP=%s\n", ntohs(dstr.counter), ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.flag[0], dstr.vpkt.hdr.flag[1], dstr.vpkt.hdr.flag[2], dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm, dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, length, inet_ntoa(fromRptr.sin_addr)); /* save mycall */ memcpy(call, dstr.vpkt.hdr.my, 8); @@ -2912,12 +2899,7 @@ void CQnetLink::Process() } if (memcmp(dstr.vpkt.hdr.ur, "CQCQCQ", 6) && i>=0) { - if (memcmp(dstr.vpkt.hdr.ur, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.ur[7] == 'L' && - 0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.r2[7] == 'G' && - (dstr.vpkt.hdr.flag[0]==0x00 || - dstr.vpkt.hdr.flag[0]==0x08 || - dstr.vpkt.hdr.flag[0]==0x20 || - dstr.vpkt.hdr.flag[0]==0x28)) { + if (memcmp(dstr.vpkt.hdr.ur, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.ur[7] == 'L' && 0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.r2[7] == 'G' && (dstr.vpkt.hdr.flag[0]==0x00 || dstr.vpkt.hdr.flag[0]==0x08 || dstr.vpkt.hdr.flag[0]==0x20 || dstr.vpkt.hdr.flag[0]==0x28)) { if ( // if there is a black list, is he in the blacklist? (link_blacklist.size() && link_blacklist.end()!=link_blacklist.find(call)) || @@ -3081,17 +3063,12 @@ void CQnetLink::Process() if (i >= 0) { /* do we have to broadcast ? */ /* make sure the source is linked to xrf */ - if (to_remote_g2[i].is_connected && 0==memcmp(to_remote_g2[i].to_call, "XRF", 3) && - 0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.r2[7]=='G' && - 0==memcmp(dstr.vpkt.hdr.ur, "CQCQCQ", 6)) { + if (to_remote_g2[i].is_connected && 0==memcmp(to_remote_g2[i].to_call, "XRF", 3) && 0==memcmp(dstr.vpkt.hdr.r2, owner.c_str(), CALL_SIZE-1) && dstr.vpkt.hdr.r2[7]=='G' && 0==memcmp(dstr.vpkt.hdr.ur, "CQCQCQ", 6)) { brd_from_rptr_idx = 0; streamid_raw = ntohs(dstr.vpkt.streamid); for (int j=0; j<3; j++) { - if (j!=i && to_remote_g2[j].is_connected && - 0==memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) && - to_remote_g2[j].to_mod==to_remote_g2[i].to_mod && - to_remote_g2[j].to_mod!='E') { + if (j!=i && to_remote_g2[j].is_connected && 0==memcmp(to_remote_g2[j].to_call, to_remote_g2[i].to_call, 8) && to_remote_g2[j].to_mod==to_remote_g2[i].to_mod && to_remote_g2[j].to_mod!='E') { memcpy(fromrptr_torptr_brd.title, "DSVT", 4); fromrptr_torptr_brd.config = 0x10; fromrptr_torptr_brd.flaga[0] = fromrptr_torptr_brd.flaga[1] = fromrptr_torptr_brd.flaga[2] = 0x0; @@ -3384,6 +3361,8 @@ void CQnetLink::audio_notify(char *msg) } SECHO edata; + + edata.is_linked = (NULL == strstr(msg, "_linked.dat_LINKED_")) ? false : true; char *p = strstr(msg, ".dat"); if (NULL == p) { fprintf(stderr, "Improper AMBE data file in msg '%s'\n", msg); @@ -3474,17 +3453,18 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) edata.header.config = 0x20U; - for (int i=0; i Date: Mon, 22 Oct 2018 09:21:18 -0700 Subject: [PATCH 143/553] offset errors --- QnetLink.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index ce979be..9358d90 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3531,6 +3531,11 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) // create the speak sentence std::string say("2"); say.append(edata.message + 7); + auto rit = say.rbegin(); + while (isspace(*rit)) { + say.resize(say.size()-1); + rit = say.rbegin(); + } // play it for (auto it=say.begin(); it!=say.end(); it++) { @@ -3540,7 +3545,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) if ('A' <= *it && *it <= 'Z') offset = speak[*it - 'A' + (lastch ? 26 : 0)]; else if ('1' <= *it && *it <= '9') - offset = speak[*it - '1']; + offset = speak[*it - '1' + 52]; else if ('0' == *it) offset = speak[61]; if (offset) { From 9ab25c75c3c3e761c44f287d26f87328d2a624d7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Mon, 22 Oct 2018 16:26:56 -0700 Subject: [PATCH 144/553] better old gps handling and moved SECHO out of QnetTypeDefs.h --- QnetGateway.cpp | 6 +++--- QnetGateway.h | 1 + QnetLink.h | 1 + QnetTypeDefs.h | 10 ---------- SEcho.h | 28 ++++++++++++++++++++++++++++ versions.h | 4 ++-- 6 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 SEcho.h diff --git a/QnetGateway.cpp b/QnetGateway.cpp index bd9e2cb..00fb04a 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2844,7 +2844,7 @@ void CQnetGateway::gps_send(short int rptr_idx) printf("GPRMC=[%s]\n", band_txt[rptr_idx].gprmc); printf("GPS id=[%s]\n",band_txt[rptr_idx].gpid); - if (validate_csum(band_txt[rptr_idx], false) || validate_csum(band_txt[rptr_idx], true)) + if (validate_csum(band_txt[rptr_idx], false)) // || validate_csum(band_txt[rptr_idx], true)) return; /* now convert GPS into APRS and send it */ @@ -2899,7 +2899,7 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) printf("Invalid North or South indicator in latitude\n"); return; } - if (strlen(lat_str) != 9) { + if (strlen(lat_str) > 9) { printf("Invalid latitude\n"); return; } @@ -2922,7 +2922,7 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) printf("Invalid East or West indicator in longitude\n"); return; } - if (strlen(lon_str) != 10) { + if (strlen(lon_str) > 10) { printf("Invalid longitude\n"); return; } diff --git a/QnetGateway.h b/QnetGateway.h index a6a6bb1..b8212f0 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -18,6 +18,7 @@ #include #include "QnetTypeDefs.h" +#include "SEcho.h" #include "aprs.h" diff --git a/QnetLink.h b/QnetLink.h index 5155276..34c8b05 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -28,6 +28,7 @@ #include #include "versions.h" #include "QnetTypeDefs.h" +#include "SEcho.h" #include "Random.h" using namespace libconfig; diff --git a/QnetTypeDefs.h b/QnetTypeDefs.h index db175b5..41aac94 100644 --- a/QnetTypeDefs.h +++ b/QnetTypeDefs.h @@ -123,13 +123,3 @@ typedef struct dsrp_tag { // offset size }; } SDSRP; #pragma pack(pop) - -typedef struct echo_tag { - bool is_linked; - time_t last_time; - unsigned short streamid; - int fd; - char message[24]; - SDSVT header; - char file[FILENAME_MAX + 1]; -} SECHO; diff --git a/SEcho.h b/SEcho.h new file mode 100644 index 0000000..cb9fc5c --- /dev/null +++ b/SEcho.h @@ -0,0 +1,28 @@ +#pragma once +/* + * Copyright 2018 by Thomas Early, N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +typedef struct echo_tag { + bool is_linked; + time_t last_time; + unsigned short streamid; + int fd; + char message[24]; + SDSVT header; + char file[FILENAME_MAX + 1]; +} SECHO; diff --git a/versions.h b/versions.h index 0280387..b7556b7 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.4" -#define LINK_VERSION "QnetLink-6.3.0" +#define IRCDDB_VERSION "QnetGateway-7.4.5" +#define LINK_VERSION "QnetLink-6.3.1" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" #define ITAP_VERSION "QnetITAP-0.2.1" From 097eab517264e7b8510d942cbb6ccb9cd7b2d600 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 23 Oct 2018 14:22:04 -0700 Subject: [PATCH 145/553] count was redefined --- QnetLink.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 9358d90..3c25f94 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3456,7 +3456,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) int count; const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; - for (int count=0; count Date: Wed, 24 Oct 2018 14:32:34 -0700 Subject: [PATCH 146/553] messages play after keyup --- QnetLink.cpp | 140 +++++++++++++++++++++------------------------------ QnetLink.h | 4 +- qndtmf.sh | 3 +- versions.h | 2 +- 4 files changed, 61 insertions(+), 88 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 3c25f94..d9cec5c 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -875,7 +875,6 @@ void CQnetLink::g2link(char from_mod, char *call, char to_mod) char linked_remote_system[CALL_SIZE + 1]; char *space_p = 0; - char notify_msg[64]; char host[MAXHOSTNAMELEN + 1]; char port_s[5 + 1]; @@ -929,8 +928,7 @@ void CQnetLink::g2link(char from_mod, char *call, char to_mod) auto gwy_pos = gwy_list.find(call); if (gwy_pos == gwy_list.end()) { - sprintf(notify_msg, "%c_gatewaynotfound.dat_GATEWAY_NOT_FOUND", from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_gatewaynotfound.dat_GATEWAY_NOT_FOUND", from_mod); printf("%s not found in gwy list\n", call); return; } @@ -1018,8 +1016,7 @@ void CQnetLink::g2link(char from_mod, char *call, char to_mod) space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } else printf("status from %s %c pending\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); } @@ -1043,7 +1040,6 @@ void CQnetLink::Process() char *p = NULL; - char notify_msg[64]; char *space_p = 0; char linked_remote_system[CALL_SIZE + 1]; char unlink_request[CALL_SIZE + 3]; @@ -1093,7 +1089,6 @@ void CQnetLink::Process() char source_stn[9]; - memset(notify_msg, '\0', sizeof(notify_msg)); time(&hb); if (xrf_g2_sock > max_nfds) @@ -1133,9 +1128,7 @@ void CQnetLink::Process() if ((to_remote_g2[1].toDst4.sin_port == htons(rmt_xrf_port)) && (strcmp(to_remote_g2[1].to_call, to_remote_g2[0].to_call) != 0)) sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, (struct sockaddr *)&(to_remote_g2[1].toDst4), sizeof(to_remote_g2[1].toDst4)); - if ((to_remote_g2[2].toDst4.sin_port == htons(rmt_xrf_port)) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[0].to_call) != 0) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) + if ((to_remote_g2[2].toDst4.sin_port == htons(rmt_xrf_port)) && (strcmp(to_remote_g2[2].to_call, to_remote_g2[0].to_call) != 0) && (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) sendto(xrf_g2_sock, owner.c_str(), CALL_SIZE+1, 0, (struct sockaddr *)&(to_remote_g2[2].toDst4), sizeof(to_remote_g2[2].toDst4)); /* send heartbeat to linked DCS reflectors */ @@ -1165,15 +1158,10 @@ void CQnetLink::Process() if (to_remote_g2[0].is_connected && (to_remote_g2[0].toDst4.sin_port == htons(rmt_ref_port))) sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[0].toDst4), sizeof(to_remote_g2[0].toDst4)); - if (to_remote_g2[1].is_connected && - (to_remote_g2[1].toDst4.sin_port == htons(rmt_ref_port)) && - (strcmp(to_remote_g2[1].to_call, to_remote_g2[0].to_call) != 0)) + if (to_remote_g2[1].is_connected && (to_remote_g2[1].toDst4.sin_port == htons(rmt_ref_port)) && (strcmp(to_remote_g2[1].to_call, to_remote_g2[0].to_call) != 0)) sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[1].toDst4), sizeof(to_remote_g2[1].toDst4)); - if (to_remote_g2[2].is_connected && - (to_remote_g2[2].toDst4.sin_port == htons(rmt_ref_port)) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[0].to_call) != 0) && - (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) + if (to_remote_g2[2].is_connected && (to_remote_g2[2].toDst4.sin_port == htons(rmt_ref_port)) && (strcmp(to_remote_g2[2].to_call, to_remote_g2[0].to_call) != 0) && (strcmp(to_remote_g2[2].to_call, to_remote_g2[1].to_call) != 0)) sendto(ref_g2_sock, REF_ACK, 3, 0, (struct sockaddr *)&(to_remote_g2[2].toDst4), sizeof(to_remote_g2[2].toDst4)); for (int i=0; i<3; i++) { @@ -1186,8 +1174,7 @@ void CQnetLink::Process() /* maybe remote system has changed IP */ printf("Unlinked from [%s] mod %c, TIMEOUT...\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -1250,8 +1237,7 @@ void CQnetLink::Process() sendto(dcs_g2_sock, cmd_2_dcs, 19 ,0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(to_remote_g2[i].toDst4)); } - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -1268,6 +1254,7 @@ void CQnetLink::Process() } // play a qnvoice file if it is specified + // this could be coming from qnvoice or qngateway (connected2network or notincache) std::ifstream voicefile(qnvoice_file.c_str(), std::ifstream::in); if (voicefile) { char line[FILENAME_MAX]; @@ -1281,7 +1268,7 @@ void CQnetLink::Process() *end-- = (char)0; // anthing reasonable left? if (strlen(start) > 2) - audio_notify(start); + PlayAudioNotifyThread(start); //clean-up voicefile.close(); remove(qnvoice_file.c_str()); @@ -1326,8 +1313,7 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } to_remote_g2[i].countdown = TIMEOUT; @@ -1351,14 +1337,12 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } else if (0==memcmp(buf + 10, "NAK", 3) && to_remote_g2[i].from_mod==buf[8]) { printf("Link module %c to [%s] %c is rejected\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -1384,8 +1368,7 @@ void CQnetLink::Process() printf("Received: %.*s\n", length - 1, buf); printf("Module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -1433,8 +1416,7 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } } @@ -1497,8 +1479,7 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); /* send back an ACK */ memcpy(buf + 10, "ACK", 4); @@ -2141,7 +2122,8 @@ void CQnetLink::Process() sendto(ref_g2_sock, buf, 9, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); } - } else if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==0) { + } + else if (length==5 && buf[0]==5 && buf[1]==0 && buf[2]==24 && buf[3]==0 && buf[4]==0) { /* reply with the same DISCONNECT */ sendto(ref_g2_sock, buf, 5, 0, (struct sockaddr *)&fromDst4, sizeof(struct sockaddr_in)); @@ -2200,8 +2182,7 @@ void CQnetLink::Process() } for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; if (length==8 && buf[0]==8 && buf[1]==192 && buf[2]==4 && buf[3]==0) { if (buf[4]== 79 && buf[5]==75 && buf[6]==82) { @@ -2217,14 +2198,12 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } else if (buf[4]==70 && buf[5]==65 && buf[6]==73 && buf[7]==76) { printf("Login failed to call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -2235,8 +2214,7 @@ void CQnetLink::Process() } else if (buf[4]==66 && buf[5]==85 && buf[6]==83 && buf[7]==89) { printf("Busy or unknown status from call %s mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_failed_link.dat_FAILED_TO_LINK", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -2250,8 +2228,7 @@ void CQnetLink::Process() } for (int i=0; i<3; i++) { - if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && - (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { + if ((fromDst4.sin_addr.s_addr == to_remote_g2[i].toDst4.sin_addr.s_addr) && (to_remote_g2[i].toDst4.sin_port == htons(rmt_ref_port))) { found = true; if (length==24 && buf[0]==24 && buf[1]==192 && buf[2]==3 && buf[3]==0) { to_remote_g2[i].countdown = TIMEOUT; @@ -2781,8 +2758,7 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } to_remote_g2[i].countdown = TIMEOUT; } @@ -2813,14 +2789,12 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } else if (memcmp(dcs_buf + 10, "NAK", 3) == 0) { printf("Link module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_failed_link.dat_UNLINKED", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_failed_link.dat_UNLINKED", to_remote_g2[i].from_mod); to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); @@ -2923,8 +2897,7 @@ void CQnetLink::Process() space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_already_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_already_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } } } else if (0==memcmp(dstr.vpkt.hdr.ur, " U", CALL_SIZE)) { @@ -2942,10 +2915,8 @@ void CQnetLink::Process() int j; for (j=0; j<3; j++) { if (j != i) { - if (to_remote_g2[j].toDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && - to_remote_g2[j].toDst4.sin_port==htons(rmt_ref_port)) { - printf("Info: Local %c is also linked to %s (different module) %c\n", to_remote_g2[j].from_mod, - to_remote_g2[j].to_call, to_remote_g2[j].to_mod); + if (to_remote_g2[j].toDst4.sin_addr.s_addr==to_remote_g2[i].toDst4.sin_addr.s_addr && to_remote_g2[j].toDst4.sin_port==htons(rmt_ref_port)) { + printf("Info: Local %c is also linked to %s (different module) %c\n", to_remote_g2[j].from_mod, to_remote_g2[j].to_call, to_remote_g2[j].to_mod); break; } } @@ -2980,8 +2951,7 @@ void CQnetLink::Process() } printf("Unlinked from [%s] mod %c\n", to_remote_g2[i].to_call, to_remote_g2[i].to_mod); - sprintf(notify_msg, "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); /* now zero out this entry */ to_remote_g2[i].to_call[0] = '\0'; @@ -2993,30 +2963,30 @@ void CQnetLink::Process() print_status_file(); } else { - sprintf(notify_msg, "%c_already_unlinked.dat_UNLINKED", dstr.vpkt.hdr.r1[7]); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_already_unlinked.dat_UNLINKED", dstr.vpkt.hdr.r1[7]); } } - } else if (0 == memcmp(dstr.vpkt.hdr.ur, " I", CALL_SIZE)) { + } + else if (0 == memcmp(dstr.vpkt.hdr.ur, " I", CALL_SIZE)) { if (to_remote_g2[i].is_connected) { strcpy(linked_remote_system, to_remote_g2[i].to_call); space_p = strchr(linked_remote_system, ' '); if (space_p) *space_p = '\0'; - sprintf(notify_msg, "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_linked.dat_LINKED_%s_%c", to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod); } else { - sprintf(notify_msg, "%c_id.dat_%s_NOT_LINKED", dstr.vpkt.hdr.r1[7], owner.c_str()); - audio_notify(notify_msg); + sprintf(notify_msg[i], "%c_id.dat_%s_NOT_LINKED", dstr.vpkt.hdr.r1[7], owner.c_str()); } - } else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[7]=='X' && admin.find(call)!=admin.end()) { // only ADMIN can execute scripts + } + else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[7]=='X' && admin.find(call)!=admin.end()) { // only ADMIN can execute scripts if (dstr.vpkt.hdr.ur[6] != ' ') { memset(system_cmd, '\0', sizeof(system_cmd)); snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), dstr.vpkt.hdr.ur[6], call, dstr.vpkt.hdr.r1[7]); printf("Executing %s\n", system_cmd); system(system_cmd); } - } else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[6]=='D' && admin.find(call)!=admin.end()) { // only ADMIN can block dongle users + } + else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[6]=='D' && admin.find(call)!=admin.end()) { // only ADMIN can block dongle users if (dstr.vpkt.hdr.ur[7] == '1') { max_dongles = saved_max_dongles; printf("Dongle connections are now allowed\n"); @@ -3025,7 +2995,8 @@ void CQnetLink::Process() max_dongles = 0; printf("Dongle connections are now disallowed\n"); } - } else if (0==memcmp(dstr.vpkt.hdr.ur, " F", CALL_SIZE) && admin.find(call)!=admin.end()) { // only ADMIN can reload gwys.txt + } + else if (0==memcmp(dstr.vpkt.hdr.ur, " F", CALL_SIZE) && admin.find(call)!=admin.end()) { // only ADMIN can reload gwys.txt gwy_list.clear(); load_gwys(gwys); } @@ -3145,7 +3116,8 @@ void CQnetLink::Process() } } } - } else { + } + else { // length is 29 or 32 if (inbound_list.size() > 0) { SREFDSVT rdsvt; rdsvt.head[0] = (unsigned char)(29 & 0xFF); @@ -3275,8 +3247,10 @@ void CQnetLink::Process() if (qso_details) printf("END from local g2: cntr=%04x, streamID=%04x, %d bytes\n", ntohs(dstr.counter), ntohs(dstr.vpkt.streamid), length); - if (bool_rptr_ack) - rptr_ack(i); + if ('\0' == notify_msg[i][0]) { + if (bool_rptr_ack) + rptr_ack(i); + } memset(dtmf_mycall[i], 0, sizeof(dtmf_mycall[i])); new_group[i] = true; @@ -3338,24 +3312,21 @@ void CQnetLink::Process() } FD_CLR (rptr_sock,&fdset); } + for (int i=0; i<3; i++) { + if (notify_msg[i][0] && 0x0U == tracing[i].streamid) { + PlayAudioNotifyThread(notify_msg[i]); + notify_msg[i][0] = '\0'; + } + } } } -void CQnetLink::audio_notify(char *msg) +void CQnetLink::PlayAudioNotifyThread(char *msg) { - if (!announce) + if (! announce) return; - short int i = -1; - - if (*msg == 'A') - i = 0; - else if (*msg == 'B') - i = 1; - else if (*msg == 'C') - i = 2; - - if (i < 0) { + if (msg[0]<'A' || msg[0]>'C') { fprintf(stderr, "Improper module in msg '%s'\n", msg); return; } @@ -3601,6 +3572,7 @@ bool CQnetLink::Init(const char *cfgfile) } for (int i=0; i<3; i++) { + notify_msg[i][0] = '\0'; to_remote_g2[i].to_call[0] = '\0'; memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); to_remote_g2[i].to_mod = to_remote_g2[i].from_mod = ' '; diff --git a/QnetLink.h b/QnetLink.h index 34c8b05..cce3223 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -77,8 +77,8 @@ private: void print_status_file(); void send_heartbeat(); bool resolve_rmt(char *name, int type, struct sockaddr_in *addr); - void audio_notify(char *msg); void rptr_ack(short i); + void PlayAudioNotifyThread(char *msg); void AudioNotifyThread(SECHO &edata); void RptrAckThread(char *arg); bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value); @@ -103,6 +103,8 @@ private: std::map dt_lh_list; + char notify_msg[3][64]; + struct to_remote_g2_tag { char to_call[CALL_SIZE + 1]; struct sockaddr_in toDst4; // sin_port is in network byte order diff --git a/qndtmf.sh b/qndtmf.sh index 129ddfe..3e4a15b 100755 --- a/qndtmf.sh +++ b/qndtmf.sh @@ -39,7 +39,7 @@ do echo elif [[ "$CMD" = "**" ]] ; then echo Load Hosts on local band $LOCAL_BAND requested by $LUSER - qnremote ${LOCAL_BAND} "$LUSER" L >/dev/null 2>&1 + qnremote ${LOCAL_BAND} "$LUSER" F >/dev/null 2>&1 else LEN=${#CMD} if [[ "$LEN" = "6" ]] ; then @@ -131,4 +131,3 @@ do done exit 0 - diff --git a/versions.h b/versions.h index b7556b7..2e14048 100644 --- a/versions.h +++ b/versions.h @@ -1,6 +1,6 @@ // version strings must be 55 characters or less! #define IRCDDB_VERSION "QnetGateway-7.4.5" -#define LINK_VERSION "QnetLink-6.3.1" +#define LINK_VERSION "QnetLink-6.4.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" #define ITAP_VERSION "QnetITAP-0.2.1" From b949b82146ff7c126327f952739e76f803415a7b Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 25 Oct 2018 07:20:16 -0700 Subject: [PATCH 147/553] moved last_beacon_time declaration out of thread loop --- QnetGateway.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 00fb04a..83b0d00 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2207,6 +2207,7 @@ void CQnetGateway::APRSBeaconThread() time_t last_keepalive_time; time(&last_keepalive_time); + time_t last_beacon_time = 0; /* This thread is also saying to the APRS_HOST that we are ALIVE */ while (keep_running) { if (aprs->GetSock() == -1) { @@ -2218,7 +2219,6 @@ void CQnetGateway::APRSBeaconThread() } time(&tnow); - time_t last_beacon_time = 0; if ((tnow - last_beacon_time) > (rptr.aprs_interval * 60)) { for (short int i=0; i<3; i++) { if (rptr.mod[i].desc[0] != '\0') { From 4135df942d5f22d048b7e2ed488914a6e675c805 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 25 Oct 2018 15:01:28 -0700 Subject: [PATCH 148/553] change NEMA APRS icon to a red dot. --- QnetGateway.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 83b0d00..051d7e4 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2915,7 +2915,7 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) return; } /* secondary table */ - strcat(buf, "\\"); + strcat(buf, "/"); if (lon_str && lon_EW) { if ((*lon_EW != 'E') && (*lon_EW != 'W')) { @@ -2939,7 +2939,7 @@ void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) } /* Just this symbolcode only */ - strcat(buf, "k"); + strcat(buf, "/"); strncat(buf, band_txt[rptr_idx].gpid + 13, 32); // printf("Built APRS from old GPS mode=[%s]\n", buf); From ed6acf6b222599f265d5360127d75ead1cf5f8b7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 25 Oct 2018 15:02:44 -0700 Subject: [PATCH 149/553] version bump --- versions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.h b/versions.h index 2e14048..a7b4b13 100644 --- a/versions.h +++ b/versions.h @@ -1,5 +1,5 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.5" +#define IRCDDB_VERSION "QnetGateway-7.4.6" #define LINK_VERSION "QnetLink-6.4.0" #define DVAP_VERSION "QnetDVAP-5.1.2" #define RELAY_VERSION "QnetRelay-0.2.3" From b5fa2eca2e6abebe4fe7ea24727ec406d26ce622 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 8 Dec 2018 14:26:59 -0700 Subject: [PATCH 150/553] split off icom exe --- .gitignore | 1 + Makefile | 23 +- QnetGateway.cpp | 141 +- QnetGateway.h | 1 - QnetIcomGateway.cpp | 2984 +++++++++++++++++++++++++++++++++++++ system/qnigateway.service | 12 + 6 files changed, 3033 insertions(+), 129 deletions(-) create mode 100644 QnetIcomGateway.cpp create mode 100644 system/qnigateway.service diff --git a/.gitignore b/.gitignore index cda52ac..7593a15 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ qndvap qndvrptr qnlink qngateway +qnigateway qnremote qnvoice qnrelay diff --git a/Makefile b/Makefile index ec079af..7bda92b 100644 --- a/Makefile +++ b/Makefile @@ -37,12 +37,12 @@ SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap +ALL_PROGRAMS=qnigateway qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr TAP_PROGRAMS=qngateway qnlink qnremote qnvoice qnitap -ICM_PROGRAMS=qngateway qnlink qnremote qnvoice +ICM_PROGRAMS=qnigateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) mmdvm : $(MDV_PROGRAMS) @@ -51,6 +51,9 @@ dvrptr : $(DVR_PROGRAMS) icom : $(ICM_PROGRAMS) itap : $(TAP_PROGRAMS) +qnigateway : $(IRCOBJS) QnetIcomGateway.o aprs.o + g++ $(CPPFLAGS) -o qnigateway QnetIcomGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread + qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread @@ -137,13 +140,13 @@ installitap : $(TAP_PROGRAMS) gwys.txt qn.cfg installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### - /bin/cp -f qngateway $(BINDIR) + /bin/cp -f qnicomgateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) - /bin/cp -f system/qngateway.service $(SYSDIR) - systemctl enable qngateway.service + /bin/cp -f system/qnigateway.service $(SYSDIR) + systemctl enable qnigateway.service systemctl daemon-reload - systemctl start qngateway.service + systemctl start qnigateway.service ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) @@ -281,10 +284,10 @@ uninstallitap : uninstallicom : ######### QnetGateway ######### - systemctl stop qngateway.service - systemctl disable qngateway.service - /bin/rm -f $(SYSDIR)/qngateway.service - /bin/rm -f $(BINDIR)/qngateway + systemctl stop qnigateway.service + systemctl disable qnigateway.service + /bin/rm -f $(SYSDIR)/qnigateway.service + /bin/rm -f $(BINDIR)/qnigateway /bin/rm -f $(BINDIR)/qnremote /bin/rm -f $(BINDIR)/qnvoice /bin/rm -f $(CFGDIR)/qn.cfg diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 051d7e4..516730f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -63,9 +63,6 @@ extern void dstar_dv_init(); extern int dstar_dv_decode(const unsigned char *d, int data[3]); static std::atomic keep_running(true); -static std::atomic G2_COUNTER_OUT(0); -static unsigned short OLD_REPLY_SEQ = 0; -static unsigned short NEW_REPLY_SEQ = 0; /* signal catching function */ static void sigCatch(int signum) @@ -254,8 +251,7 @@ bool CQnetGateway::read_config(char *cfgFile) if(! get_value(cfg, path+"password", irc_pass, 0, 512, "1111111111111111")) return true; - // modules - is_icom = is_not_icom = false; + // module for (short int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; @@ -263,34 +259,26 @@ bool CQnetGateway::read_config(char *cfgFile) std::string type; if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { printf("%s = [%s]\n", std::string(path+"type").c_str(), type.c_str()); - rptr.mod[m].defined = true; - if (0 == type.compare("icom")) { - rptr.mod[m].package_version = ICOM_VERSION; - is_icom = true; - } else if (0 == type.compare("dvap")) { + if (0 == type.compare("dvap")) { rptr.mod[m].package_version = DVAP_VERSION; - is_not_icom = true; + rptr.mod[m].defined = true; } else if (0 == type.compare("dvrptr")) { rptr.mod[m].package_version = DVRPTR_VERSION; - is_not_icom = true; + rptr.mod[m].defined = true; } else if (0 == type.compare("mmdvm")) { rptr.mod[m].package_version = MMDVM_VERSION; - is_not_icom = true; + rptr.mod[m].defined = true; } else if (0 == type.compare("itap")) { rptr.mod[m].package_version = ITAP_VERSION; - is_not_icom = true; + rptr.mod[m].defined = true; } else { printf("module type '%s' is invalid\n", type.c_str()); return true; } - if (is_icom && is_not_icom) { - printf("cannot define both icom and non-icom modules\n"); - return true; - } - if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, is_icom ? "172.16.0.1" : "127.0.0.1")) + if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, "127.0.0.1")) return true; - get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, is_icom ? 20000 : 19998+m); + get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, 19998+m); get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset, -1.0e12, 1.0e12, 0.0); get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); @@ -315,31 +303,9 @@ bool CQnetGateway::read_config(char *cfgFile) } else rptr.mod[m].defined = false; } - if (! is_icom && ! is_not_icom) { + if (! (rptr.mod[0].defined || rptr.mod[1].defined || rptr.mod[2].defined)) { printf("No modules defined!\n"); return true; - } else if (is_icom) { // make sure all ICOM modules have the same IP and port number - std::string addr; - int port; - for (int i=0; i<3; i++) { - if (rptr.mod[i].defined) { - if (addr.size()) { - if (addr.compare(rptr.mod[i].portip.ip) || port!=rptr.mod[i].portip.port) { - printf("all defined ICOM modules must have the same IP and port number!\n"); - return true; - } - } else { - addr = rptr.mod[i].portip.ip; - port = rptr.mod[i].portip.port; - } - } - } - for (int i=0; i<3; i++) { - if (! rptr.mod[i].defined) { - rptr.mod[i].portip.ip = addr; - rptr.mod[i].portip.port = port; - } - } } // gateway @@ -352,10 +318,10 @@ bool CQnetGateway::read_config(char *cfgFile) get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, is_icom ? "172.16.0.20" : "0.0.0.0")) + if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, "0.0.0.0")) return true; - get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, is_icom ? 20000 : 19000); + get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, 19000); get_value(cfg, path+"regen_header", bool_regen_header, true); @@ -755,7 +721,7 @@ void CQnetGateway::ProcessTimeouts() // Send end_of_audio to local repeater. // Let the repeater re-initialize - end_of_audio.counter = is_icom ? G2_COUNTER_OUT++ :toRptr[i].G2_COUNTER++; + end_of_audio.counter = toRptr[i].G2_COUNTER++; if (i == 0) end_of_audio.vpkt.snd_term_id = 0x03; else if (i == 1) @@ -1165,35 +1131,6 @@ void CQnetGateway::Process() ii->kickWatchdog(IRCDDB_VERSION); - if (is_icom) { - // send INIT to Icom Stack - unsigned char buf[500]; - memset(buf, 0, 10); - memcpy(buf, "INIT", 4); - buf[6] = 0x73U; - // we can use the module a band_addr for INIT - sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - printf("Waiting for ICOM controller...\n"); - - // get the acknowledgement from the ICOM Stack - while (keep_running) { - socklen_t fromlength = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, buf, 500, 0, (struct sockaddr *)&fromRptr, &fromlength); - if (10==recvlen && 0==memcmp(buf, "INIT", 4) && 0x72U==buf[6] && 0x0U==buf[7]) { - OLD_REPLY_SEQ = 256U * buf[4] + buf[5]; - NEW_REPLY_SEQ = OLD_REPLY_SEQ + 1; - G2_COUNTER_OUT = NEW_REPLY_SEQ; - unsigned int ui = G2_COUNTER_OUT; - printf("SYNC: old=%u, new=%u out=%u\n", OLD_REPLY_SEQ, NEW_REPLY_SEQ, ui); - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - printf("Detected ICOM controller!\n"); - } else - printf("Skipping ICOM initialization\n"); - - while (keep_running) { ProcessTimeouts(); @@ -1239,7 +1176,7 @@ void CQnetGateway::Process() printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); // bump the counter + rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); // bump the counter rptrbuf.flag[0] = 0x73; rptrbuf.flag[1] = 0x12; rptrbuf.flag[2] = 0x00; @@ -1285,7 +1222,7 @@ void CQnetGateway::Process() /* streamid match ? */ if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); rptrbuf.flag[0] = 0x73; rptrbuf.flag[1] = 0x12; rptrbuf.flag[2] = 0x00; @@ -1329,15 +1266,15 @@ void CQnetGateway::Process() if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { printf("Re-generating header for streamID=%04x\n", g2buf.streamid); - toRptr[i].saved_hdr[4] = (unsigned char)(((is_icom ? G2_COUNTER_OUT : toRptr[i].G2_COUNTER) >> 8) & 0xff); - toRptr[i].saved_hdr[5] = (unsigned char)((is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++) & 0xff); + toRptr[i].saved_hdr[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); + toRptr[i].saved_hdr[5] = (unsigned char)((toRptr[i].G2_COUNTER++) & 0xff); /* re-generate/send the header */ sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); /* send this audio packet to repeater */ memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[i].G2_COUNTER++); + rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); rptrbuf.flag[0] = 0x73; rptrbuf.flag[1] = 0x12; rptrbuf.flag[2] = 0x00; @@ -1384,30 +1321,7 @@ void CQnetGateway::Process() int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { - ///////////////////////////////////////////////////////////////////// - // some ICOM handshaking... - if (is_icom && 10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr - NEW_REPLY_SEQ = ntohs(rptrbuf.counter); - if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { - G2_COUNTER_OUT = NEW_REPLY_SEQ; - OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; - } else - OLD_REPLY_SEQ = NEW_REPLY_SEQ; - } else if (is_icom && 0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { - rptrbuf.flag[0] = 0x72U; - memset(rptrbuf.flag+1, 0x0U, 3); - sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - // end of ICOM handshaking - ///////////////////////////////////////////////////////////////////// - } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { - if (is_icom) { // acknowledge packet to ICOM - SDSTR reply; - memcpy(reply.pkt_id, "DSTR", 4); - reply.counter = rptrbuf.counter; - reply.flag[0] = 0x72U; - memset(reply.flag+1, 0, 3); - sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - } + if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { if (recvlen == 58) { vPacketCount = 0U; @@ -1696,10 +1610,7 @@ void CQnetGateway::Process() time(&toRptr[i].last_time); /* bump the G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER++; + toRptr[i].G2_COUNTER++; toRptr[i].sequence = rptrbuf.vpkt.ctrl; } @@ -1870,10 +1781,7 @@ void CQnetGateway::Process() time(&toRptr[i].last_time); /* bump the G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER ++; + toRptr[i].G2_COUNTER ++; toRptr[i].sequence = rptrbuf.vpkt.ctrl; } @@ -2109,10 +2017,7 @@ void CQnetGateway::Process() time(&toRptr[i].last_time); /* bump G2 counter */ - if (is_icom) - G2_COUNTER_OUT++; - else - toRptr[i].G2_COUNTER ++; + toRptr[i].G2_COUNTER ++; toRptr[i].sequence = rptrbuf.vpkt.ctrl; @@ -2409,7 +2314,7 @@ void CQnetGateway::PlayFileThread(SECHO &edata) // reformat the header and send it memcpy(dstr.pkt_id, "DSTR", 4); - dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[mod].G2_COUNTER++); + dstr.counter = htons(toRptr[mod].G2_COUNTER++); dstr.flag[0] = 0x73; dstr.flag[1] = 0x12; dstr.flag[2] = 0x00; @@ -2436,7 +2341,7 @@ void CQnetGateway::PlayFileThread(SECHO &edata) int nread = fread(dstr.vpkt.vasd.voice, 9, 1, fp); if (nread == 1) { - dstr.counter = htons(is_icom ? G2_COUNTER_OUT++ : toRptr[mod].G2_COUNTER++); + dstr.counter = htons(toRptr[mod].G2_COUNTER++); dstr.vpkt.ctrl = (unsigned char)(i % 21); if (0x0U == dstr.vpkt.ctrl) { memcpy(dstr.vpkt.vasd.text, sdsync, 3); diff --git a/QnetGateway.h b/QnetGateway.h index b8212f0..8d4bd2a 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -96,7 +96,6 @@ private: std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs, playNotInCache; - bool is_icom, is_not_icom; int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; diff --git a/QnetIcomGateway.cpp b/QnetIcomGateway.cpp new file mode 100644 index 0000000..b078f27 --- /dev/null +++ b/QnetIcomGateway.cpp @@ -0,0 +1,2984 @@ +/* + * Copyright (C) 2010 by Scott Lawson KI4LKF + * Copyright (C) 2017-2018 by Thomas Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* by KI4LKF, N7TAE */ +/* + QnetGateway is a dstar G2 gateway, using irc routing + adapted from the OpenG2 G2 gateway + Version 2.61 or higher will use ONLY the irc mechanism of routing + and it will NOT use any local Postgres databases or any TRUST(s) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "IRCDDB.h" +#include "IRCutils.h" +#include "versions.h" +#include "QnetGateway.h" + + +extern void dstar_dv_init(); +extern int dstar_dv_decode(const unsigned char *d, int data[3]); + +static std::atomic keep_running(true); +static std::atomic G2_COUNTER_OUT(0); +static unsigned short OLD_REPLY_SEQ = 0; +static unsigned short NEW_REPLY_SEQ = 0; + +/* signal catching function */ +static void sigCatch(int signum) +{ + /* do NOT do any serious work here */ + if ((signum == SIGTERM) || (signum == SIGINT)) + keep_running = false; + + return; +} + +void CQnetGateway::set_dest_rptr(int mod_ndx, char *dest_rptr) +{ + FILE *statusfp = fopen(status_file.c_str(), "r"); + if (statusfp) { + setvbuf(statusfp, (char *)NULL, _IOLBF, 0); + + char statusbuf[1024]; + while (fgets(statusbuf, 1020, statusfp) != NULL) { + char *p = strchr(statusbuf, '\r'); + if (p) + *p = '\0'; + p = strchr(statusbuf, '\n'); + if (p) + *p = '\0'; + + const char *delim = ","; + char *saveptr = NULL; + char *status_local_mod = strtok_r(statusbuf, delim, &saveptr); + char *status_remote_stm = strtok_r(NULL, delim, &saveptr); + char *status_remote_mod = strtok_r(NULL, delim, &saveptr); + + if (!status_local_mod || !status_remote_stm || !status_remote_mod) + continue; + + if ( ((*status_local_mod == 'A') && (mod_ndx == 0)) || + ((*status_local_mod == 'B') && (mod_ndx == 1)) || + ((*status_local_mod == 'C') && (mod_ndx == 2)) ) { + strncpy(dest_rptr, status_remote_stm, CALL_SIZE); + dest_rptr[7] = *status_remote_mod; + dest_rptr[CALL_SIZE] = '\0'; + break; + } + } + fclose(statusfp); + } + return; +} + +/* compute checksum */ +void CQnetGateway::calcPFCS(unsigned char *packet, int len) +{ + const unsigned short crc_tabccitt[256] = { + 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, + 0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e,0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876, + 0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd,0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5, + 0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c,0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974, + 0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb,0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3, + 0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a,0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72, + 0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9,0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1, + 0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738,0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70, + 0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7,0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff, + 0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036,0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e, + 0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5,0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd, + 0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134,0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c, + 0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3,0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb, + 0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232,0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a, + 0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1,0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9, + 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 + }; + unsigned short crc_dstar_ffff = 0xffff; + short int low, high; + unsigned short tmp; + + switch (len) { + case 56: + low = 15; + high = 54; + break; + case 58: + low = 17; + high = 56; + break; + default: + return; + } + + for (unsigned short int i = low; i < high ; i++) { + unsigned short short_c = 0x00ff & (unsigned short)packet[i]; + tmp = (crc_dstar_ffff & 0x00ff) ^ short_c; + crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp]; + } + crc_dstar_ffff = ~crc_dstar_ffff; + tmp = crc_dstar_ffff; + + if (len == 56) { + packet[54] = (unsigned char)(crc_dstar_ffff & 0xff); + packet[55] = (unsigned char)((tmp >> 8) & 0xff); + } else { + packet[56] = (unsigned char)(crc_dstar_ffff & 0xff); + packet[57] = (unsigned char)((tmp >> 8) & 0xff); + } + + return; +} + +bool CQnetGateway::get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value) +{ + if (cfg.lookupValue(path, value)) { + if (value < min || value > max) + value = default_value; + } else + value = default_value; + printf("%s = [%d]\n", path.c_str(), value); + return true; +} + +bool CQnetGateway::get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value) +{ + if (cfg.lookupValue(path, value)) { + if (value < min || value > max) + value = default_value; + } else + value = default_value; + printf("%s = [%lg]\n", path.c_str(), value); + return true; +} + +bool CQnetGateway::get_value(const Config &cfg, const std::string path, bool &value, bool default_value) +{ + if (! cfg.lookupValue(path, value)) + value = default_value; + printf("%s = [%s]\n", path.c_str(), value ? "true" : "false"); + return true; +} + +bool CQnetGateway::get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value) +{ + if (cfg.lookupValue(path, value)) { + int l = value.length(); + if (lmax) { + printf("%s is invalid\n", path.c_str()); + return false; + } + } else + value = default_value; + printf("%s = [%s]\n", path.c_str(), value.c_str()); + return true; +} + +/* process configuration file */ +bool CQnetGateway::read_config(char *cfgFile) +{ + Config cfg; + + printf("Reading file %s\n", cfgFile); + // Read the file. If there is an error, report it and exit. + try { + cfg.readFile(cfgFile); + } catch(const FileIOException &fioex) { + printf("Can't read %s\n", cfgFile); + return true; + } catch(const ParseException &pex) { + printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + return true; + } + // ircddb + std::string path("ircddb."); + if (! get_value(cfg, path+"login", owner, 3, CALL_SIZE-2, "UNDEFINED")) + return true; + if (0 == owner.compare("UNDEFINED")) { + fprintf(stderr, "You must specify your lisensed callsign in ircddb.login\n"); + return true; + } + OWNER = owner; + ToLower(owner); + ToUpper(OWNER); + printf("OWNER=[%s]\n", OWNER.c_str()); + OWNER.resize(CALL_SIZE, ' '); + + if (! get_value(cfg, path+"host", ircddb.ip, 3, MAXHOSTNAMELEN, "rr.openquad.net")) + return true; + + get_value(cfg, path+"port", ircddb.port, 1000, 65535, 9007); + + if(! get_value(cfg, path+"password", irc_pass, 0, 512, "1111111111111111")) + return true; + + // modules + bool is_icom = false; + bool is_not_icom = false; + for (short int m=0; m<3; m++) { + std::string path = "module."; + path += m + 'a'; + path += '.'; + std::string type; + if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { + printf("%s = [%s]\n", std::string(path+"type").c_str(), type.c_str()); + rptr.mod[m].defined = true; + if (0 == type.compare("icom")) { + rptr.mod[m].package_version = ICOM_VERSION; + is_icom = true; + } else if (0 == type.compare("dvap")) { + rptr.mod[m].package_version = DVAP_VERSION; + is_not_icom = true; + } else if (0 == type.compare("dvrptr")) { + rptr.mod[m].package_version = DVRPTR_VERSION; + is_not_icom = true; + } else if (0 == type.compare("mmdvm")) { + rptr.mod[m].package_version = MMDVM_VERSION; + is_not_icom = true; + } else if (0 == type.compare("itap")) { + rptr.mod[m].package_version = ITAP_VERSION; + is_not_icom = true; + } else { + printf("module type '%s' is invalid\n", type.c_str()); + return true; + } + if (is_icom && is_not_icom) { + printf("cannot define both icom and non-icom modules\n"); + return true; + } + + if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, is_icom ? "172.16.0.1" : "127.0.0.1")) + return true; + get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, is_icom ? 20000 : 19998+m); + get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); + get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset, -1.0e12, 1.0e12, 0.0); + get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); + get_value(cfg, std::string(path+"agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); + get_value(cfg, std::string(path+"latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); + get_value(cfg, std::string(path+"longitude").c_str(), rptr.mod[m].longitude, -180.0, 180.0, 0.0); + if (! cfg.lookupValue(path+"desc1", rptr.mod[m].desc1)) + rptr.mod[m].desc1 = ""; + if (! cfg.lookupValue(path+"desc2", rptr.mod[m].desc2)) + rptr.mod[m].desc2 = ""; + if (! get_value(cfg, std::string(path+"url").c_str(), rptr.mod[m].url, 0, 80, "github.com/n7tae/QnetGateway")) + return true; + // truncate strings + if (rptr.mod[m].desc1.length() > 20) + rptr.mod[m].desc1.resize(20); + if (rptr.mod[m].desc2.length() > 20) + rptr.mod[m].desc2.resize(20); + // make the long description for the log + if (rptr.mod[m].desc1.length()) + rptr.mod[m].desc = rptr.mod[m].desc1 + ' '; + rptr.mod[m].desc += rptr.mod[m].desc2; + } else + rptr.mod[m].defined = false; + } + if (! is_icom && ! is_not_icom) { + printf("No modules defined!\n"); + return true; + } else if (is_icom) { // make sure all ICOM modules have the same IP and port number + std::string addr; + int port; + for (int i=0; i<3; i++) { + if (rptr.mod[i].defined) { + if (addr.size()) { + if (addr.compare(rptr.mod[i].portip.ip) || port!=rptr.mod[i].portip.port) { + printf("all defined ICOM modules must have the same IP and port number!\n"); + return true; + } + } else { + addr = rptr.mod[i].portip.ip; + port = rptr.mod[i].portip.port; + } + } + } + for (int i=0; i<3; i++) { + if (! rptr.mod[i].defined) { + rptr.mod[i].portip.ip = addr; + rptr.mod[i].portip.port = port; + } + } + } + + // gateway + path = "gateway."; + if (! get_value(cfg, path+"local_irc_ip", local_irc_ip, 7, IP_SIZE, "0.0.0.0")) + return true; + + if (! get_value(cfg, path+"external.ip", g2_external.ip, 7, IP_SIZE, "0.0.0.0")) + return true; + + get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); + + if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, "172.16.0.20")) + return true; + + get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, 20000); + + get_value(cfg, path+"regen_header", bool_regen_header, true); + + get_value(cfg, path+"aprs_send", bool_send_aprs, true); + + get_value(cfg, path+"send_qrgs_maps", bool_send_qrgs, true); + + // APRS + path = "aprs."; + if (! get_value(cfg, path+"host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) + return true; + + get_value(cfg, path+"port", rptr.aprs.port, 10000, 65535, 14580); + + get_value(cfg, path+"interval", rptr.aprs_interval, 40, 1000, 40); + + if (! get_value(cfg, path+"filter", rptr.aprs_filter, 0, 512, "")) + return true; + + // log + path = "log."; + get_value(cfg, path+"qso", bool_qso_details, false); + + get_value(cfg, path+"irc", bool_irc_debug, false); + + get_value(cfg, path+"dtmf", bool_dtmf_debug, false); + if (! get_value(cfg, "link.outgoing_ip", g2_link.ip, 7, IP_SIZE, "127.0.0.1")) + return true; + + // file + path = "file."; + if (! get_value(cfg, path+"echotest", echotest_dir, 2, FILENAME_MAX, "/tmp")) + return true; + + if (! get_value(cfg, path+"dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) + return true; + + if (! get_value(cfg, path+"status", status_file, 2, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) + return true; + + if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, "/tmp/qnvoice.txt")) + return true; + + // link + path = "link."; + get_value(cfg, path+"port", g2_link.port, 16000, 65535, 18997); + + if (! get_value(cfg, path+"ip", g2_link.ip, 7, 15, "127.0.0.1")) + return true; + + // timing + path = "timing.play."; + get_value(cfg, path+"wait", play_wait, 1, 10, 1); + + get_value(cfg, path+"delay", play_delay, 9, 25, 19); + + path = "timing.timeout."; + get_value(cfg, path+"echo", echotest_rec_timeout, 1, 10, 1); + + get_value(cfg, path+"voicemail", voicemail_rec_timeout, 1, 10, 1); + + get_value(cfg, path+"remote_g2", from_remote_g2_timeout, 1, 10, 2); + + get_value(cfg, path+"local_rptr", from_local_rptr_timeout, 1, 10, 1); + + return false; +} + +// Create ports +int CQnetGateway::open_port(const SPORTIP &pip) +{ + struct sockaddr_in sin; + + int sock = socket(PF_INET, SOCK_DGRAM, 0); + if (0 > sock) { + printf("Failed to create socket on %s:%d, errno=%d, %s\n", pip.ip.c_str(), pip.port, errno, strerror(errno)); + return -1; + } + fcntl(sock, F_SETFL, O_NONBLOCK); + + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons(pip.port); + sin.sin_addr.s_addr = inet_addr(pip.ip.c_str()); + + int reuse = 1; + if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + printf("Cannot set the UDP socket (port %u) option, err: %d, %s\n", pip.port, errno, strerror(errno)); + return -1; + } + + if (bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) != 0) { + printf("Failed to bind %s:%d, errno=%d, %s\n", pip.ip.c_str(), pip.port, errno, strerror(errno)); + close(sock); + return -1; + } + + return sock; +} + +/* receive data from the irc server and save it */ +void CQnetGateway::GetIRCDataThread() +{ + std::string user, rptr, gateway, ipaddr; + DSTAR_PROTOCOL proto; + IRCDDB_RESPONSE_TYPE type; + struct sigaction act; + short last_status = 0; + + act.sa_handler = sigCatch; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("GetIRCDataThread: sigaction-TERM failed, error=%d\n", errno); + keep_running = false; + return; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("GetIRCDataThread: sigaction-INT failed, error=%d\n", errno); + keep_running = false; + return; + } + if (sigaction(SIGPIPE, &act, 0) != 0) { + printf("GetIRCDataThread: sigaction-PIPE failed, error=%d\n", errno); + keep_running = false; + return; + } + + short threshold = 0; + bool not_announced[3]; + for (int i=0; i<3; i++) + not_announced[i] = this->rptr.mod[i].defined; // announce to all modules that are defined! + bool is_quadnet = (0 == ircddb.ip.compare("rr.openquad.net")); + while (keep_running) { + int rc = ii->getConnectionState(); + if (rc > 5 && rc < 8 && is_quadnet) { + char ch = '\0'; + if (not_announced[0]) + ch = 'A'; + else if (not_announced[1]) + ch = 'B'; + else if (not_announced[2]) + ch = 'C'; + if (ch) { + // we need to announce, but can we? + struct stat sbuf; + if (stat(qnvoicefile.c_str(), &sbuf)) { + // yes, there is no qnvoicefile, so create it + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_connected2network.dat_WELCOME_TO_QUADNET", ch); + fclose(fp); + not_announced[ch - 'A'] = false; + } else + fprintf(stderr, "could not open %s\n", qnvoicefile.c_str()); + } + } + } + threshold++; + if (threshold >= 100) { + if ((rc == 0) || (rc == 10)) { + if (last_status != 0) { + printf("irc status=%d, probable disconnect...\n", rc); + last_status = 0; + } + } else if (rc == 7) { + if (last_status != 2) { + printf("irc status=%d, probable connect...\n", rc); + last_status = 2; + } + } else { + if (last_status != 1) { + printf("irc status=%d, probable connect...\n", rc); + last_status = 1; + } + } + threshold = 0; + } + + while (((type = ii->getMessageType()) != IDRT_NONE) && keep_running) { + if (type == IDRT_USER) { + ii->receiveUser(user, rptr, gateway, ipaddr); + if (!user.empty()) { + if (!rptr.empty() && !gateway.empty() && !ipaddr.empty()) { + if (bool_irc_debug) + printf("C-u:%s,%s,%s,%s\n", user.c_str(), rptr.c_str(), gateway.c_str(), ipaddr.c_str()); + + pthread_mutex_lock(&irc_data_mutex); + + user2rptr_map[user] = rptr; + rptr2gwy_map[rptr] = gateway; + gwy2ip_map[gateway] = ipaddr; + + pthread_mutex_unlock(&irc_data_mutex); + + // printf("%d users, %d repeaters, %d gateways\n", user2rptr_map.size(), rptr2gwy_map.size(), gwy2ip_map.size()); + + } + } + } else if (type == IDRT_REPEATER) { + ii->receiveRepeater(rptr, gateway, ipaddr, proto); + if (!rptr.empty()) { + if (!gateway.empty() && !ipaddr.empty()) { + if (bool_irc_debug) + printf("C-r:%s,%s,%s\n", rptr.c_str(), gateway.c_str(), ipaddr.c_str()); + + pthread_mutex_lock(&irc_data_mutex); + + rptr2gwy_map[rptr] = gateway; + gwy2ip_map[gateway] = ipaddr; + + pthread_mutex_unlock(&irc_data_mutex); + + // printf("%d repeaters, %d gateways\n", rptr2gwy_map.size(), gwy2ip_map.size()); + + } + } + } else if (type == IDRT_GATEWAY) { + ii->receiveGateway(gateway, ipaddr, proto); + if (!gateway.empty() && !ipaddr.empty()) { + if (bool_irc_debug) + printf("C-g:%s,%s\n", gateway.c_str(),ipaddr.c_str()); + + pthread_mutex_lock(&irc_data_mutex); + + gwy2ip_map[gateway] = ipaddr; + + pthread_mutex_unlock(&irc_data_mutex); + + // printf("%d gateways\n", gwy2ip_map.size()); + + } + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + printf("GetIRCDataThread exiting...\n"); + return; +} + +/* return codes: 0=OK(found it), 1=TRY AGAIN, 2=FAILED(bad data) */ +int CQnetGateway::get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU) +{ + char temp[CALL_SIZE + 1]; + + memset(arearp_cs, ' ', CALL_SIZE); + arearp_cs[CALL_SIZE] = '\0'; + memset(zonerp_cs, ' ', CALL_SIZE); + zonerp_cs[CALL_SIZE] = '\0'; + *mod = ' '; + + /* find the user in the CACHE */ + if (RoU == 'U') { + auto user_pos = user2rptr_map.find(call); + if (user_pos != user2rptr_map.end()) { + memcpy(arearp_cs, user_pos->second.c_str(), 7); + *mod = user_pos->second.c_str()[7]; + } else + return 1; + } else if (RoU == 'R') { + memcpy(arearp_cs, call, 7); + *mod = call[7]; + } else { + printf("Invalid specification %c for RoU\n", RoU); + return 2; + } + + if (*mod == 'G') { + printf("Invalid module %c\n", *mod); + return 2; + } + + memcpy(temp, arearp_cs, 7); + temp[7] = *mod; + temp[CALL_SIZE] = '\0'; + + auto rptr_pos = rptr2gwy_map.find(temp); + if (rptr_pos != rptr2gwy_map.end()) { + memcpy(zonerp_cs, rptr_pos->second.c_str(), CALL_SIZE); + zonerp_cs[CALL_SIZE] = '\0'; + + auto gwy_pos = gwy2ip_map.find(zonerp_cs); + if (gwy_pos != gwy2ip_map.end()) { + strncpy(ip, gwy_pos->second.c_str(), IP_SIZE); + ip[IP_SIZE] = '\0'; + return 0; + } else { + /* printf("Could not find IP for Gateway %s\n", zonerp_cs); */ + return 1; + } + } else { + /* printf("Could not find Gateway for repeater %s\n", temp); */ + return 1; + } + + return 2; +} + +bool CQnetGateway::get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU) +{ + pthread_mutex_lock(&irc_data_mutex); + int rc = get_yrcall_rptr_from_cache(call, arearp_cs, zonerp_cs, mod, ip, RoU); + pthread_mutex_unlock(&irc_data_mutex); + if (rc == 0) + return true; + else if (rc == 2) + return false; + + /* at this point, the data is not in cache */ + /* report the irc status */ + int status = ii->getConnectionState(); + // printf("irc status=%d\n", status); + if (status != 7) { + printf("Remote irc database not ready, irc status is not 7, try again\n"); + return false; + } + + /* request data from irc server */ + if (RoU == 'U') { + printf("User [%s] not in local cache, try again\n", call); + /*** YRCALL=KJ4NHFBL ***/ + if (((call[6] == 'A') || (call[6] == 'B') || (call[6] == 'C')) && (call[7] == 'L')) + printf("If this was a gateway link request, that is ok\n"); + + if (!ii->findUser(call)) { + printf("findUser(%s): Network error\n", call); + return false; + } + } else if (RoU == 'R') { + printf("Repeater [%s] not in local cache, try again\n", call); + if (!ii->findRepeater(call)) { + printf("findRepeater(%s): Network error\n", call); + return false; + } + } + + return false; +} + +bool CQnetGateway::Flag_is_ok(unsigned char flag) +{ + // normal break emr emr+break + return 0x00U==flag || 0x08U==flag || 0x20U==flag || 0x28U==flag; +} + +void CQnetGateway::ProcessTimeouts() +{ + for (int i=0; i<3; i++) { + time_t t_now; + /* echotest recording timed out? */ + if (recd[i].last_time != 0) { + time(&t_now); + if ((t_now - recd[i].last_time) > echotest_rec_timeout) { + printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", i, recd[i].streamid); + + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* START: echotest thread setup */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); + } catch (const std::exception &e) { + printf("Failed to start echotest thread. Exception: %s\n", e.what()); + // when the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } + /* END: echotest thread setup */ + } + } + + /* voicemail recording timed out? */ + if (vm[i].last_time != 0) { + time(&t_now); + if ((t_now - vm[i].last_time) > voicemail_rec_timeout) { + printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", i, vm[i].streamid); + + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + } + + // any stream going to local repeater timed out? + if (toRptr[i].last_time != 0) { + time(&t_now); + // The stream can be from a cross-band, or from a remote system, + // so we could use either FROM_LOCAL_RPTR_TIMEOUT or FROM_REMOTE_G2_TIMEOUT + // but FROM_REMOTE_G2_TIMEOUT makes more sense, probably is a bigger number + if ((t_now - toRptr[i].last_time) > from_remote_g2_timeout) { + printf("Inactivity to local rptr mod index %d, removing stream id %04x\n", i, toRptr[i].streamid); + + // Send end_of_audio to local repeater. + // Let the repeater re-initialize + end_of_audio.counter = G2_COUNTER_OUT++; + if (i == 0) + end_of_audio.vpkt.snd_term_id = 0x03; + else if (i == 1) + end_of_audio.vpkt.snd_term_id = 0x01; + else + end_of_audio.vpkt.snd_term_id = 0x02; + end_of_audio.vpkt.streamid = toRptr[i].streamid; + end_of_audio.vpkt.ctrl = toRptr[i].sequence | 0x40; + + for (int j=0; j<2; j++) + sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + toRptr[i].last_time = 0; + } + } + + /* any stream coming from local repeater timed out ? */ + if (band_txt[i].last_time != 0) { + time(&t_now); + if ((t_now - band_txt[i].last_time) > from_local_rptr_timeout) { + /* This local stream never went to a remote system, so trace the timeout */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) + printf("Inactivity from local rptr band %d, removing stream id %04x\n", i, band_txt[i].streamID); + + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0x0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + } + } + + /* any stream from local repeater to a remote gateway timed out ? */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr != 0) { + time(&t_now); + if ((t_now - to_remote_g2[i].last_time) > from_local_rptr_timeout) { + printf("Inactivity from local rptr mod %d, removing stream id %04x\n", i, to_remote_g2[i].streamid); + + memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + } + } +} + +// new_group is true if we are processing the first voice packet of a 2-voice packet pair. The high order nibble of the first byte of +// this first packet specifed the type of slow data that is being sent. +// the to_print is an integer that counts down how many 2-voice-frame pairs remain to be processed. +// ABC_grp means that we are processing a 20-character message. +// C_seen means that we are processing the last 2-voice-frame packet on a 20 character message. +void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) +{ + /* extract 20-byte RADIO ID */ + if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { + + // first, unscramble + unsigned char c1 = data[0] ^ 0x70u; + unsigned char c2 = data[1] ^ 0x4fu; + unsigned char c3 = data[2] ^ 0x93u; + + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == sid) { + if (new_group[i]) { + header_type = c1 & 0xf0; + + // header squelch + if ((header_type == 0x50) || (header_type == 0xc0)) { + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } + else if (header_type == 0x30) { /* GPS or GPS id or APRS */ + new_group[i] = false; + to_print[i] = c1 & 0x0f; + ABC_grp[i] = false; + if (to_print[i] > 5) + to_print[i] = 5; + else if (to_print[i] < 1) + to_print[i] = 1; + + if ((to_print[i] > 1) && (to_print[i] <= 5)) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* fresh GPS string, re-initialize */ + if ((to_print[i] == 5) && (c2 == '$')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } + if ((c3 != '\r') && (c3 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; + band_txt[i].temp_line_cnt++; + } + + if ((c2 == '\r') || (c3 == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((c2 == '\n') || (c3 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] -= 2; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } + + if (c2 == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (c2 == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + to_print[i] --; + } + } + else if (header_type == 0x40) { /* ABC text */ + new_group[i] = false; + to_print[i] = 3; + ABC_grp[i] = true; + C_seen[i] = ((c1 & 0x0f) == 0x03) ? true : false; + + band_txt[i].txt[band_txt[i].txt_cnt] = c2; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = c3; + band_txt[i].txt_cnt++; + + /* We should NOT see any more text, + if we already processed text, + so blank out the codes. */ + if (band_txt[i].sent_key_on_msg) { + data[0] = 0x70; + data[1] = 0x4f; + data[2] = 0x93; + } + + if (band_txt[i].txt_cnt >= 20) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + band_txt[i].txt_cnt = 0; + } + } + else { // header type is not header, squelch, gps or message + new_group[i] = false; + to_print[i] = 0; + ABC_grp[i] = false; + } + } + else { // not a new_group, this is the second of a two-voice-frame pair + if (! band_txt[i].sent_key_on_msg && vPacketCount > 100) { + // 100 voice packets received and still no 20-char message! + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + band_txt[i].txt[0] = '\0'; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + // we have the 20-character message, send it to the server... + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + if (to_print[i] == 3) { + if (ABC_grp[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = c1; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = c2; + band_txt[i].txt_cnt++; + + band_txt[i].txt[band_txt[i].txt_cnt] = c3; + band_txt[i].txt_cnt++; + + /* We should NOT see any more text, + if we already processed text, + so blank out the codes. */ + if (band_txt[i].sent_key_on_msg) { + data[0] = 0x70; + data[1] = 0x4f; + data[2] = 0x93; + } + + if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { + band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; + if ( ! band_txt[i].sent_key_on_msg) { + /*** if YRCALL is CQCQCQ, set dest_rptr ***/ + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + // we have the 20-character message, send it to the server... + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + band_txt[i].txt_cnt = 0; + } + if (C_seen[i]) + C_seen[i] = false; + } else { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy carrige return or newline */ + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; + band_txt[i].temp_line_cnt++; + } + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } + if ((c3 != '\r') && (c3 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; + band_txt[i].temp_line_cnt++; + } + + if ( (c1 == '\r') || (c2 == '\r') || (c3 == '\r') ) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + else if ((c1 == '\n') || (c2 == '\n') ||(c3 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + } else if (to_print[i] == 2) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; + band_txt[i].temp_line_cnt++; + } + if ((c2 != '\r') && (c2 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; + band_txt[i].temp_line_cnt++; + } + + if ((c1 == '\r') || (c2 == '\r')) { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if ((c1 == '\n') || (c2 == '\n')) { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } else if (to_print[i] == 1) { + /* something went wrong? all bets are off */ + if (band_txt[i].temp_line_cnt > 200) { + printf("Reached the limit in the OLD gps mode\n"); + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + + /* do not copy CR, NL */ + if ((c1 != '\r') && (c1 != '\n')) { + band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; + band_txt[i].temp_line_cnt++; + } + + if (c1 == '\r') { + if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { + memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; + } else if (band_txt[i].temp_line[0] != '$') { + memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); + band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; + if (bool_send_aprs && !band_txt[i].is_gps_sent) + gps_send(i); + } + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } else if (c1 == '\n') { + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + } + } + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; + } + break; + } + } + } +} + +/* run the main loop for QnetGateway */ +void CQnetGateway::Process() +{ + // dtmf stuff + int dtmf_buf_count[3] = {0, 0, 0}; + char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; + int dtmf_last_frame[3] = { 0, 0, 0 }; + unsigned int dtmf_counter[3] = { 0, 0, 0 }; + + dstar_dv_init(); + + int max_nfds = 0; + if (g2_sock > max_nfds) + max_nfds = g2_sock; + if (srv_sock > max_nfds) + max_nfds = srv_sock; + printf("g2=%d, srv=%d, MAX+1=%d\n", g2_sock, srv_sock, max_nfds + 1); + + std::future aprs_future, irc_data_future; + if (bool_send_aprs) { // start the beacon thread + try { + aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); + } catch (const std::exception &e) { + printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); + } + if (aprs_future.valid()) + printf("APRS beacon thread started\n"); + } + + try { // start the IRC read thread + irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); + } catch (const std::exception &e) { + printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); + keep_running = false; + } + if (keep_running) + printf("get_irc_data thread started\n"); + + ii->kickWatchdog(IRCDDB_VERSION); + + // send INIT to Icom Stack + unsigned char buf[500]; + memset(buf, 0, 10); + memcpy(buf, "INIT", 4); + buf[6] = 0x73U; + // we can use the module a band_addr for INIT + sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + printf("Waiting for ICOM controller...\n"); + + // get the acknowledgement from the ICOM Stack + while (keep_running) { + socklen_t fromlength = sizeof(struct sockaddr_in); + int recvlen = recvfrom(srv_sock, buf, 500, 0, (struct sockaddr *)&fromRptr, &fromlength); + if (10==recvlen && 0==memcmp(buf, "INIT", 4) && 0x72U==buf[6] && 0x0U==buf[7]) { + OLD_REPLY_SEQ = 256U * buf[4] + buf[5]; + NEW_REPLY_SEQ = OLD_REPLY_SEQ + 1; + G2_COUNTER_OUT = NEW_REPLY_SEQ; + unsigned int ui = G2_COUNTER_OUT; + printf("SYNC: old=%u, new=%u out=%u\n", OLD_REPLY_SEQ, NEW_REPLY_SEQ, ui); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + printf("Detected ICOM controller!\n"); + + while (keep_running) { + ProcessTimeouts(); + + // wait 20 ms max + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(g2_sock, &fdset); + FD_SET(srv_sock, &fdset); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 20000; // 20 ms + (void)select(max_nfds + 1, &fdset, 0, 0, &tv); + + // process packets coming from remote G2 + if (FD_ISSET(g2_sock, &fdset)) { + SDSVT g2buf; + socklen_t fromlen = sizeof(struct sockaddr_in); + int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); + + // save incoming port for mobile systems + if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { + printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } else { + if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { + printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } + } + + if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { + if (g2buflen == 56) { + + // Find out the local repeater module IP/port to send the data to + int i = g2buf.hdr.rpt1[7] - 'A'; + + /* valid repeater module? */ + if (i>=0 && i<3) { + // toRptr[i] is active if a remote system is talking to it or + // toRptr[i] is receiving data from a cross-band + if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { + if (bool_qso_details) + printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(G2_COUNTER_OUT++); // bump the counter + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x30; + rptrbuf.vpkt.icm_id = 0x20; + //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); + rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; + rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; + rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; + rptrbuf.vpkt.streamid = g2buf.streamid; + rptrbuf.vpkt.ctrl = g2buf.ctrl; + memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); + memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); + memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); + memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); + + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* save the header */ + memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); + toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; + + /* This is the active streamid */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } else { // g2buflen == 27 + if (bool_qso_details && g2buf.ctrl & 0x40) + printf("id=%04x END G2\n", ntohs(g2buf.streamid)); + + /* find out which repeater module to send the data to */ + int i; + for (i=0; i<3; i++) { + /* streamid match ? */ + if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(G2_COUNTER_OUT++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining= 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* timeit */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (g2buf.ctrl & 0x40) { + /* clear the saved header */ + memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); + toRptr[i].saved_adr = 0; + + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; + } + } + + /* no match ? */ + if ((i == 3) && bool_regen_header) { + /* check if this a continuation of audio that timed out */ + + if (g2buf.ctrl & 0x40) + ; /* we do not care about end-of-QSO */ + else { + /* for which repeater this stream has timed out ? */ + for (i = 0; i < 3; i++) { + /* match saved stream ? */ + if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { + /* repeater module is inactive ? */ + if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { + printf("Re-generating header for streamID=%04x\n", g2buf.streamid); + + toRptr[i].saved_hdr[4] = (unsigned char)((G2_COUNTER_OUT >> 8) & 0xff); + toRptr[i].saved_hdr[5] = (unsigned char)(G2_COUNTER_OUT++ & 0xff); + + /* re-generate/send the header */ + sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* send this audio packet to repeater */ + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(G2_COUNTER_OUT++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* make sure that any more audio arriving will be accepted */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + } + break; + } + } + } + } + } + } + FD_CLR (g2_sock,&fdset); + } + + // process packets coming from local repeater modules + if (FD_ISSET(srv_sock, &fdset)) { + char temp_radio_user[CALL_SIZE + 1]; + char temp_mod; + + char arearp_cs[CALL_SIZE + 1]; + char zonerp_cs[CALL_SIZE + 1]; + char ip[IP_SIZE + 1]; + + char tempfile[FILENAME_MAX + 1]; + + SDSVT g2buf; + + socklen_t fromlen = sizeof(struct sockaddr_in); + int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); + + if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { + ///////////////////////////////////////////////////////////////////// + // some ICOM handshaking... + if (10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr + NEW_REPLY_SEQ = ntohs(rptrbuf.counter); + if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { + G2_COUNTER_OUT = NEW_REPLY_SEQ; + OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; + } else + OLD_REPLY_SEQ = NEW_REPLY_SEQ; + } else if (0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { + rptrbuf.flag[0] = 0x72U; + memset(rptrbuf.flag+1, 0x0U, 3); + sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + // end of ICOM handshaking + ///////////////////////////////////////////////////////////////////// + } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { + SDSTR reply; + memcpy(reply.pkt_id, "DSTR", 4); + reply.counter = rptrbuf.counter; + reply.flag[0] = 0x72U; + memset(reply.flag+1, 0, 3); + sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); + if (recvlen == 58) { + vPacketCount = 0U; + if (bool_qso_details) + printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); + + if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { + + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (got a header)\n", i); + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0; + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + + /* Initialize the LAST HEARD data for the band */ + + band_txt[i].streamID = rptrbuf.vpkt.streamid; + + memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); + + memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); + band_txt[i].lh_mycall[8] = '\0'; + + memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); + band_txt[i].lh_sfx[4] = '\0'; + + memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); + band_txt[i].lh_yrcall[8] = '\0'; + + memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); + band_txt[i].lh_rpt1[8] = '\0'; + + memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].lh_rpt2[8] = '\0'; + + time(&band_txt[i].last_time); + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].sent_key_on_msg = false; + + band_txt[i].dest_rptr[0] = '\0'; + + /* try to process GPS mode: GPRMC and ID */ + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + // band_txt[i].gps_last_time = 0; DO NOT reset it + + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + /* select the band for aprs processing, and lock on the stream ID */ + if (bool_send_aprs) + aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); + } + } + + /* Is MYCALL valid ? */ + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); + temp_radio_user[8] = '\0'; + + int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); + + if (mycall_valid == REG_NOERROR) + ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); + else { + if (mycall_valid == REG_NOMATCH) + printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); + else + printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); + } + + /* send data qnlink */ + if (mycall_valid == REG_NOERROR) + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + if ( mycall_valid==REG_NOERROR && + memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector + memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && + memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && + rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something + memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ + { + if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) + { + if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + /* YRCALL=/repeater + mod */ + /* YRCALL=/KJ4NHFB */ + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); + temp_radio_user[6] = ' '; + temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; + if (temp_radio_user[7] == ' ') + temp_radio_user[7] = 'A'; + temp_radio_user[CALL_SIZE] = '\0'; + + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); + if (result) { /* it is a repeater */ + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that saved port instead of the default port + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set yrcall, can NOT let it be slash and repeater + module */ + memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + + /* set PFCS */ + calcPFCS(g2buf.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + // send to remote gateway + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); + + time(&(to_remote_g2[i].last_time)); + } + } + } + } + } + else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { + + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); + temp_radio_user[8] = '\0'; + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); + if (result) { + /* destination is a remote system */ + if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set PFCS */ + memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + calcPFCS(g2buf.title, 56); + + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + /* send to remote gateway */ + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); + + time(&(to_remote_g2[i].last_time)); + } + } + } + else + { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* the user we are trying to contact is on our gateway */ + /* make sure they are on a different module */ + if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { + /* + The remote repeater has been set, lets fill in the dest_rptr + so that later we can send that to the LIVE web site + */ + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[7] = temp_mod; + band_txt[i].dest_rptr[8] = '\0'; + + i = temp_mod - 'A'; + + /* valid destination repeater module? */ + if (i>=0 && i<3) { + /* + toRptr[i] : receiving from a remote system or cross-band + band_txt[i] : local RF is talking. + */ + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); + + rptrbuf.vpkt.hdr.r2[7] = temp_mod; + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); + + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + /* bump the G2 counter */ + G2_COUNTER_OUT++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } + else + printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); + } + } + } + else + { + if ('L' != rptrbuf.vpkt.hdr.ur[7]) // as long as this doesn't look like a linking command + playNotInCache = true; // we need to wait until user's transmission is over + } + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + unlink(vm[i].file); + printf("removed voicemail file: %s\n", vm[i].file); + vm[i].file[0] = '\0'; + } else + printf("No voicemail to clear or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + snprintf(vm[i].message, 21, "VOICEMAIL ON MOD %c ", 'A'+i); + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(vm[i])); + } catch (const std::exception &e) { + printf("Failed to start voicemail playback. Exception: %s\n", e.what()); + } + } else + printf("No voicemail to recall or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (vm[i].fd >= 0) + printf("Already recording for voicemail on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); + + vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (vm[i].fd < 0) + printf("Failed to create file %s for voicemail\n", tempfile); + else { + strcpy(vm[i].file, tempfile); + printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); + + time(&vm[i].last_time); + vm[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + memcpy(vm[i].header.title, recbuf.title, 56); + } + } + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + if (recd[i].fd >= 0) + printf("Already recording for echotest on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); + + recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (recd[i].fd < 0) + printf("Failed to create file %s for echotest\n", tempfile); + else { + strcpy(recd[i].file, tempfile); + printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); + snprintf(recd[i].message, 21, "ECHO ON MODULE %c ", 'A' + i); + time(&recd[i].last_time); + recd[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + memcpy(recd[i].header.title, recbuf.title, 56); + } + } + } + /* check for cross-banding */ + } + else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C + (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with + rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[8] = '\0'; + } + + i = rptrbuf.vpkt.hdr.r2[7] - 'A'; + + // valid destination repeater module? + if (i>=0 && i<3) { + // toRptr[i] : receiving from a remote system or cross-band + // band_txt[i] : local RF is talking. + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); + + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); + + sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + /* bump the G2 counter */ + G2_COUNTER_OUT++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + } + } + } + } + else + { // recvlen is 29 or 32 + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + time(&band_txt[i].last_time); + + if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data + if (dtmf_buf_count[i] > 0) { + dtmf_file = dtmf_dir; + dtmf_file.push_back('/'); + dtmf_file.push_back('A'+i); + dtmf_file += "_mod_DTMF_NOTIFY"; + if (bool_dtmf_debug) + printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); + FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); + if (dtmf_fp) { + fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); + fclose(dtmf_fp); + } else + printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); + + + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + dtmf_counter[i] = 0; + dtmf_last_frame[i] = 0; + } + if (! band_txt[i].sent_key_on_msg) { + band_txt[i].txt[0] = '\0'; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; + } + // we have the 20-character message, send it to the server... + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + // send the "key off" message, this will end up in the openquad.net Last Heard webpage. + ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); + + if (playNotInCache) { + // Not in cache, please try again! + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", band_txt[i].lh_rpt1[7]); + fclose(fp); + } + playNotInCache = false; + } + + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + } + else + { // not the end of the voice stream + int ber_data[3]; + int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); + if (ber_data[0] == 0xf85) + band_txt[i].num_dv_silent_frames++; + band_txt[i].num_bit_errors += ber_errs; + band_txt[i].num_dv_frames++; + + if ((ber_data[0] & 0x0ffc) == 0xfc0) { + dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); + if (dtmf_counter[i] > 0) { + if (dtmf_last_frame[i] != dtmf_digit) + dtmf_counter[i] = 0; + } + dtmf_last_frame[i] = dtmf_digit; + dtmf_counter[i]++; + + if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { + if (dtmf_buf_count[i] < MAX_DTMF_BUF) { + const char *dtmf_chars = "147*2580369#ABCD"; + dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; + dtmf_buf_count[i]++; + } + } + const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; + if (recvlen == 29) + memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); + else + memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); + } else + dtmf_counter[i] = 0; + } + break; + } + } + vPacketCount++; + if (recvlen == 29) // process the slow data from every voice packet + ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); + else + ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); + + /* send data to qnlink */ + sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + + /* aprs processing */ + if (bool_send_aprs) + // streamID seq audio+text + aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice); + + for (int i=0; i<3; i++) { + /* find out if data must go to the remote G2 */ + if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x20; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; + memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); + if (recvlen == 29) + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + + uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + time(&(to_remote_g2[i].last_time)); + + /* Is this the end-of-stream */ + if (rptrbuf.vpkt.ctrl & 0x40) { + memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + break; + } + else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest + time(&recd[i].last_time); + + //memcpy(recbuf.title, "DSVT", 4); + //recbuf.config = 0x20; + //recbuf.id = rptrbuf.vpkt.icm_id; + //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; + //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + (void)write(recd[i].fd, rptrbuf.vpkt.vasd.voice, 9); + else + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + (void)write(recd[i].fd, rptrbuf.vpkt.vasd1.voice, 9); + + //rec_len = 27; + //(void)write(recd[i].fd, &rec_len, 2); + //(void)write(recd[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* we are in echotest mode, so play it back */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); + } catch (const std::exception &e) { + printf("failed to start PlayFileThread. Exception: %s\n", e.what()); + // When the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); + } + } + break; + } + else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail + time(&vm[i].last_time); + + //memcpy(recbuf.title, "DSVT", 4); + //recbuf.config = 0x20; + //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + //recbuf.id = rptrbuf.vpkt.icm_id; + //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + if (recvlen == 29) + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + (void)write(vm[i].fd, rptrbuf.vpkt.vasd.voice, 9); + else + //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + (void)write(vm[i].fd, rptrbuf.vpkt.vasd1.voice, 9); + + //rec_len = 27; + //(void)write(vm[i].fd, &rec_len, 2); + //(void)write(vm[i].fd, &recbuf, rec_len); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + break; + } + else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data + sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + + /* timeit */ + time(&toRptr[i].last_time); + + /* bump G2 counter */ + G2_COUNTER_OUT++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (rptrbuf.vpkt.ctrl & 0x40) { + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; + } + } + + if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) + printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); + } + } + } + FD_CLR (srv_sock,&fdset); + } + } + + // thread clean-up + if (bool_send_aprs) { + if (aprs_future.valid()) + aprs_future.get(); + } + irc_data_future.get(); + return; +} + +void CQnetGateway::compute_aprs_hash() +{ + short hash = 0x73e2; + char rptr_sign[CALL_SIZE + 1]; + + strcpy(rptr_sign, OWNER.c_str()); + char *p = strchr(rptr_sign, ' '); + if (!p) { + printf("Failed to build repeater callsign for aprs hash\n"); + return; + } + *p = '\0'; + p = rptr_sign; + short int len = strlen(rptr_sign); + + for (short int i=0; i < len; i+=2) { + hash ^= (*p++) << 8; + hash ^= (*p++); + } + printf("aprs hash code=[%d] for %s\n", hash, OWNER.c_str()); + rptr.aprs_hash = hash; + + return; +} + +void CQnetGateway::APRSBeaconThread() +{ + char snd_buf[512]; + char rcv_buf[512]; + time_t tnow = 0; + + struct sigaction act; + + /* + Every 20 seconds, the remote APRS host sends a KEEPALIVE packet-comment + on the TCP/APRS port. + If we have not received any KEEPALIVE packet-comment after 5 minutes + we must assume that the remote APRS host is down or disappeared + or has dropped the connection. In these cases, we must re-connect. + There are 3 keepalive packets in one minute, or every 20 seconds. + In 5 minutes, we should have received a total of 15 keepalive packets. + */ + short THRESHOLD_COUNTDOWN = 15; + + act.sa_handler = sigCatch; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("APRSBeaconThread: sigaction-TERM failed, error=%d\n", errno); + return; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("APRSBeaconThread: sigaction-INT failed, error=%d\n", errno); + return; + } + if (sigaction(SIGPIPE, &act, 0) != 0) { + printf("APRSBeaconThread: sigaction-PIPE failed, error=%d\n", errno); + return; + } + + time_t last_keepalive_time; + time(&last_keepalive_time); + + time_t last_beacon_time = 0; + /* This thread is also saying to the APRS_HOST that we are ALIVE */ + while (keep_running) { + if (aprs->GetSock() == -1) { + aprs->Open(OWNER); + if (aprs->GetSock() == -1) + sleep(1); + else + THRESHOLD_COUNTDOWN = 15; + } + + time(&tnow); + if ((tnow - last_beacon_time) > (rptr.aprs_interval * 60)) { + for (short int i=0; i<3; i++) { + if (rptr.mod[i].desc[0] != '\0') { + float tmp_lat = fabs(rptr.mod[i].latitude); + float tmp_lon = fabs(rptr.mod[i].longitude); + float lat = floor(tmp_lat); + float lon = floor(tmp_lon); + lat = (tmp_lat - lat) * 60.0F + lat * 100.0F; + lon = (tmp_lon - lon) * 60.0F + lon * 100.0F; + + char lat_s[15], lon_s[15]; + if (lat >= 1000.0F) + sprintf(lat_s, "%.2f", lat); + else if (lat >= 100.0F) + sprintf(lat_s, "0%.2f", lat); + else if (lat >= 10.0F) + sprintf(lat_s, "00%.2f", lat); + else + sprintf(lat_s, "000%.2f", lat); + + if (lon >= 10000.0F) + sprintf(lon_s, "%.2f", lon); + else if (lon >= 1000.0F) + sprintf(lon_s, "0%.2f", lon); + else if (lon >= 100.0F) + sprintf(lon_s, "00%.2f", lon); + else if (lon >= 10.0F) + sprintf(lon_s, "000%.2f", lon); + else + sprintf(lon_s, "0000%.2f", lon); + + /* send to aprs */ + sprintf(snd_buf, "%s>APJI23,TCPIP*,qAC,%sS:!%s%cD%s%c&RNG%04u %s %s", + rptr.mod[i].call.c_str(), rptr.mod[i].call.c_str(), + lat_s, (rptr.mod[i].latitude < 0.0) ? 'S' : 'N', + lon_s, (rptr.mod[i].longitude < 0.0) ? 'W' : 'E', + (unsigned int)rptr.mod[i].range, rptr.mod[i].band.c_str(), rptr.mod[i].desc.c_str()); + + // printf("APRS Beacon =[%s]\n", snd_buf); + strcat(snd_buf, "\r\n"); + + while (keep_running) { + if (aprs->GetSock() == -1) { + aprs->Open(OWNER); + if (aprs->GetSock() == -1) + sleep(1); + else + THRESHOLD_COUNTDOWN = 15; + } else { + int rc = aprs->WriteSock(snd_buf, strlen(snd_buf)); + if (rc < 0) { + if ((errno == EPIPE) || + (errno == ECONNRESET) || + (errno == ETIMEDOUT) || + (errno == ECONNABORTED) || + (errno == ESHUTDOWN) || + (errno == EHOSTUNREACH) || + (errno == ENETRESET) || + (errno == ENETDOWN) || + (errno == ENETUNREACH) || + (errno == EHOSTDOWN) || + (errno == ENOTCONN)) { + printf("send_aprs_beacon: APRS_HOST closed connection,error=%d\n",errno); + close(aprs->GetSock()); + aprs->SetSock( -1 ); + } else if (errno == EWOULDBLOCK) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } else { + /* Cant do nothing about it */ + printf("send_aprs_beacon failed, error=%d\n", errno); + break; + } + } else { + // printf("APRS beacon sent\n"); + break; + } + } + int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); + if (rc > 0) + THRESHOLD_COUNTDOWN = 15; + } + } + int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); + if (rc > 0) + THRESHOLD_COUNTDOWN = 15; + } + time(&last_beacon_time); + } + /* + Are we still receiving from APRS host ? + */ + int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); + if (rc < 0) { + if ((errno == EPIPE) || + (errno == ECONNRESET) || + (errno == ETIMEDOUT) || + (errno == ECONNABORTED) || + (errno == ESHUTDOWN) || + (errno == EHOSTUNREACH) || + (errno == ENETRESET) || + (errno == ENETDOWN) || + (errno == ENETUNREACH) || + (errno == EHOSTDOWN) || + (errno == ENOTCONN)) { + printf("send_aprs_beacon: recv error: APRS_HOST closed connection,error=%d\n",errno); + close(aprs->GetSock()); + aprs->SetSock( -1 ); + } + } else if (rc == 0) { + printf("send_aprs_beacon: recv: APRS shutdown\n"); + close(aprs->GetSock()); + aprs->SetSock( -1 ); + } else + THRESHOLD_COUNTDOWN = 15; + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + /* 20 seconds passed already ? */ + time(&tnow); + if ((tnow - last_keepalive_time) > 20) { + /* we should be receving keepalive packets ONLY if the connection is alive */ + if (aprs->GetSock() >= 0) { + if (THRESHOLD_COUNTDOWN > 0) + THRESHOLD_COUNTDOWN--; + + if (THRESHOLD_COUNTDOWN == 0) { + printf("APRS host keepalive timeout\n"); + close(aprs->GetSock()); + aprs->SetSock( -1 ); + } + } + /* reset timer */ + time(&last_keepalive_time); + } + } + printf("APRS beacon thread exiting...\n"); + return; +} + +void CQnetGateway::PlayFileThread(SECHO &edata) +{ + SDSTR dstr; + const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; + const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; + + struct sigaction act; + act.sa_handler = sigCatch; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return; + } + if (sigaction(SIGPIPE, &act, 0) != 0) { + printf("sigaction-PIPE failed, error=%d\n", errno); + return; + } + + printf("File to playback:[%s]\n", edata.file); + + struct stat sbuf; + if (stat(edata.file, &sbuf)) { + fprintf(stderr, "Can't stat %s\n", edata.file); + return; + } + + if (sbuf.st_size % 9) + printf("Warning %s file size is %ld (not a multiple of 9)!\n", edata.file, sbuf.st_size); + int ambeblocks = (int)sbuf.st_size / 9; + + FILE *fp = fopen(edata.file, "rb"); + if (!fp) { + fprintf(stderr, "Failed to open file %s\n", edata.file); + return; + } + + int mod = edata.header.hdr.rpt1[7] - 'A'; + if (mod<0 || mod>2) { + fprintf(stderr, "unknown module suffix '%s'\n", edata.header.hdr.rpt1); + return; + } + + sleep(play_wait); + + // reformat the header and send it + memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(G2_COUNTER_OUT++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x00; + dstr.remaining = 0x30; + dstr.vpkt.icm_id = 0x20; + dstr.vpkt.dst_rptr_id = edata.header.flagb[0]; + dstr.vpkt.snd_rptr_id = edata.header.flagb[1]; + dstr.vpkt.snd_term_id = edata.header.flagb[2]; + dstr.vpkt.streamid = edata.header.streamid; + dstr.vpkt.ctrl = 0x80u; + memcpy(dstr.vpkt.hdr.flag, edata.header.hdr.flag, 3); + memcpy(dstr.vpkt.hdr.r1, edata.header.hdr.rpt1, 8); + memcpy(dstr.vpkt.hdr.r2, edata.header.hdr.rpt2, 8); + memcpy(dstr.vpkt.hdr.ur, "CQCQCQ ", 8); + memcpy(dstr.vpkt.hdr.my, edata.header.hdr.mycall, 8); + memcpy(dstr.vpkt.hdr.nm, edata.header.hdr.sfx, 4); + calcPFCS(dstr.pkt_id, 58); + + sendto(srv_sock, dstr.pkt_id, 58, 0, (struct sockaddr *)&toRptr[mod].band_addr, sizeof(struct sockaddr_in)); + + dstr.remaining = 0x13U; + + for (int i=0; irptrQTH(rptrcall, rptr.mod[i].latitude, rptr.mod[i].longitude, rptr.mod[i].desc1, rptr.mod[i].desc2, rptr.mod[i].url, rptr.mod[i].package_version); + if (rptr.mod[i].frequency) + ii->rptrQRG(rptrcall, rptr.mod[i].frequency, rptr.mod[i].offset, rptr.mod[i].range, rptr.mod[i].agl); + } + + return; +} + +int CQnetGateway::Init(char *cfgfile) +{ + short int i; + struct sigaction act; + + setvbuf(stdout, (char *)NULL, _IOLBF, 0); + + + /* Used to validate MYCALL input */ + int rc = regcomp(&preg, "^(([1-9][A-Z])|([A-Z][0-9])|([A-Z][A-Z][0-9]))[0-9A-Z]*[A-Z][ ]*[ A-RT-Z]$", REG_EXTENDED | REG_NOSUB); + if (rc != REG_NOERROR) { + printf("The IRC regular expression is NOT valid\n"); + return 1; + } + + act.sa_handler = sigCatch; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return 1; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return 1; + } + if (sigaction(SIGPIPE, &act, 0) != 0) { + printf("sigaction-PIPE failed, error=%d\n", errno); + return 1; + } + + for (i = 0; i < 3; i++) + memset(&band_txt[0], 0, sizeof(SBANDTXT)); + + /* process configuration file */ + if ( read_config(cfgfile) ) { + printf("Failed to process config file %s\n", cfgfile); + return 1; + } + + playNotInCache = false; + + /* build the repeater callsigns for aprs */ + rptr.mod[0].call = OWNER; + for (i=OWNER.length(); i; i--) + if (! isspace(OWNER[i-1])) + break; + rptr.mod[0].call.resize(i); + + rptr.mod[1].call = rptr.mod[0].call; + rptr.mod[2].call = rptr.mod[0].call; + rptr.mod[0].call += "-A"; + rptr.mod[1].call += "-B"; + rptr.mod[2].call += "-C"; + rptr.mod[0].band = "23cm"; + rptr.mod[1].band = "70cm"; + rptr.mod[2].band = "2m"; + printf("Repeater callsigns: [%s] [%s] [%s]\n", rptr.mod[0].call.c_str(), rptr.mod[1].call.c_str(), rptr.mod[2].call.c_str()); + + for (i = 0; i < 3; i++) { + //rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; + + band_txt[i].last_time = 0; + + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].sent_key_on_msg = false; + + band_txt[i].dest_rptr[0] = '\0'; + + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + band_txt[i].gps_last_time = 0; + + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + + } + + if (bool_send_aprs) { + aprs = new CAPRS(&rptr); + if (aprs) + aprs->Init(); + else { + printf("aprs class init failed!\nAPRS will be turned off"); + bool_send_aprs = false; + } + } + compute_aprs_hash(); + + ii = new CIRCDDB(ircddb.ip, ircddb.port, owner, irc_pass, IRCDDB_VERSION, local_irc_ip); + bool ok = ii->open(); + if (!ok) { + printf("irc open failed\n"); + return 1; + } + + rc = ii->getConnectionState(); + printf("Waiting for irc connection status of 2\n"); + i = 0; + while (rc < 2) { + printf("irc status=%d\n", rc); + if (rc < 2) { + i++; + sleep(5); + } else + break; + + if (!keep_running) + break; + + if (i > 5) { + printf("We can not wait any longer...\n"); + break; + } + rc = ii->getConnectionState(); + } + + /* udp port 40000 must open first */ + g2_sock = open_port(g2_external); + if (0 > g2_sock) { + printf("Can't open %s:%d\n", g2_external.ip.c_str(), g2_external.port); + return 1; + } + + // Open G2 INTERNAL: + // default non-icom 127.0.0.1:19000 + // default icom 172.16.0.20:20000 + srv_sock = open_port(g2_internal); + if (0 > srv_sock) { + printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); + return 1; + } + + for (i = 0; i < 3; i++) { + // recording for echotest on local repeater modules + recd[i].last_time = 0; + recd[i].streamid = 0; + recd[i].fd = -1; + memset(recd[i].file, 0, sizeof(recd[i].file)); + + // recording for voicemail on local repeater modules + vm[i].last_time = 0; + vm[i].streamid = 0; + vm[i].fd = -1; + memset(vm[i].file, 0, sizeof(vm[i].file)); + + snprintf(vm[i].file, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), 'A'+i, "voicemail.dat"); + + if (access(vm[i].file, F_OK) != 0) + memset(vm[i].file, 0, sizeof(vm[i].file)); + else + printf("Loaded voicemail file: %s for mod %d\n", vm[i].file, i); + + // the repeater modules run on these ports + memset(&toRptr[i],0,sizeof(toRptr[i])); + + memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); + toRptr[i].saved_adr = 0; + + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + + toRptr[i].band_addr.sin_family = AF_INET; + toRptr[i].band_addr.sin_addr.s_addr = inet_addr(rptr.mod[i].portip.ip.c_str()); + toRptr[i].band_addr.sin_port = htons(rptr.mod[i].portip.port); + + toRptr[i].last_time = 0; + toRptr[i].G2_COUNTER = 0; + + toRptr[i].sequence = 0x0; + } + + /* + Initialize the end_of_audio that will be sent to the local repeater + when audio from remote G2 has timed out + */ + memcpy(end_of_audio.pkt_id, "DSTR", 4); + end_of_audio.flag[0] = 0x73; + end_of_audio.flag[1] = 0x12; + end_of_audio.flag[2] = 0x00; + end_of_audio.remaining = 0x13; + end_of_audio.vpkt.icm_id = 0x20; + end_of_audio.vpkt.dst_rptr_id = 0x00; + end_of_audio.vpkt.snd_rptr_id = 0x01; + memset(end_of_audio.vpkt.vasd.voice, '\0', 9); + end_of_audio.vpkt.vasd.text[0] = 0x70; + end_of_audio.vpkt.vasd.text[1] = 0x4f; + end_of_audio.vpkt.vasd.text[2] = 0x93; + + /* to remote systems */ + for (i = 0; i < 3; i++) { + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + + /* where to send packets to qnlink */ + memset(&plug, 0, sizeof(struct sockaddr_in)); + plug.sin_family = AF_INET; + plug.sin_port = htons(g2_link.port); + plug.sin_addr.s_addr = inet_addr(g2_link.ip.c_str()); + + printf("QnetGateway...entering processing loop\n"); + + if (bool_send_qrgs) + qrgs_and_maps(); + return 0; +} + +CQnetGateway::CQnetGateway() +{ +} + +CQnetGateway::~CQnetGateway() +{ + if (srv_sock != -1) { + close(srv_sock); + printf("Closed G2_INTERNAL_PORT\n"); + } + + if (g2_sock != -1) { + close(g2_sock); + printf("Closed G2_EXTERNAL_PORT\n"); + } + + if (bool_send_aprs) { + if (aprs->GetSock() != -1) { + close(aprs->GetSock()); + printf("Closed APRS\n"); + } + delete aprs; + } + + for (int i=0; i<3; i++) { + recd[i].last_time = 0; + recd[i].streamid = 0; + if (recd[i].fd >= 0) { + close(recd[i].fd); + unlink(recd[i].file); + } + } + + ii->close(); + delete ii; + + printf("QnetGateway exiting\n"); +} + +bool CQnetGateway::validate_csum(SBANDTXT &bt, bool is_gps) +{ + const char *name = is_gps ? "GPS" : "GPRMC"; + char *s = is_gps ? bt.gpid : bt.gprmc; + char *p = strrchr(s, '*'); + if (!p) { + // BAD news, something went wrong + printf("Missing asterisk before checksum in %s\n", name); + bt.gprmc[0] = bt.gpid[0] = '\0'; + return true; + } else { + *p = '\0'; + // verify csum in GPRMC + bool ok = verify_gps_csum(s + 1, p + 1); + if (!ok) { + printf("csum in %s not good\n", name); + bt.gprmc[0] = bt.gpid[0] = '\0'; + return true; + } + } + return false; +} + +void CQnetGateway::gps_send(short int rptr_idx) +{ + time_t tnow = 0; + static char old_mycall[CALL_SIZE + 1] = { " " }; + + if ((rptr_idx < 0) || (rptr_idx > 2)) { + printf("ERROR in gps_send: rptr_idx %d is invalid\n", rptr_idx); + return; + } + + if (band_txt[rptr_idx].gprmc[0] == '\0') { + band_txt[rptr_idx].gpid[0] = '\0'; + printf("missing GPS ID\n"); + return; + } + if (band_txt[rptr_idx].gpid[0] == '\0') { + band_txt[rptr_idx].gprmc[0] = '\0'; + printf("Missing GPSRMC\n"); + return; + } + if (memcmp(band_txt[rptr_idx].gpid, band_txt[rptr_idx].lh_mycall, CALL_SIZE) != 0) { + printf("MYCALL [%s] does not match first 8 characters of GPS ID [%.8s]\n", band_txt[rptr_idx].lh_mycall, band_txt[rptr_idx].gpid); + band_txt[rptr_idx].gprmc[0] = '\0'; + band_txt[rptr_idx].gpid[0] = '\0'; + return; + } + + /* if new station, reset last time */ + if (strcmp(old_mycall, band_txt[rptr_idx].lh_mycall) != 0) { + strcpy(old_mycall, band_txt[rptr_idx].lh_mycall); + band_txt[rptr_idx].gps_last_time = 0; + } + + /* do NOT process often */ + time(&tnow); + if ((tnow - band_txt[rptr_idx].gps_last_time) < 31) + return; + + printf("GPRMC=[%s]\n", band_txt[rptr_idx].gprmc); + printf("GPS id=[%s]\n",band_txt[rptr_idx].gpid); + + if (validate_csum(band_txt[rptr_idx], false)) // || validate_csum(band_txt[rptr_idx], true)) + return; + + /* now convert GPS into APRS and send it */ + build_aprs_from_gps_and_send(rptr_idx); + + band_txt[rptr_idx].is_gps_sent = true; + time(&(band_txt[rptr_idx].gps_last_time)); + return; +} + +void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) +{ + char buf[512]; + const char *delim = ","; + + char *saveptr = NULL; + + /*** dont care about the rest */ + + strcpy(buf, band_txt[rptr_idx].lh_mycall); + char *p = strchr(buf, ' '); + if (p) { + if (band_txt[rptr_idx].lh_mycall[7] != ' ') { + *p = '-'; + *(p + 1) = band_txt[rptr_idx].lh_mycall[7]; + *(p + 2) = '>'; + *(p + 3) = '\0'; + } else { + *p = '>'; + *(p + 1) = '\0'; + } + } else + strcat(buf, ">"); + + strcat(buf, "APDPRS,DSTAR*,qAR,"); + strcat(buf, rptr.mod[rptr_idx].call.c_str()); + strcat(buf, ":!"); + + //GPRMC = + strtok_r(band_txt[rptr_idx].gprmc, delim, &saveptr); + //time_utc = + strtok_r(NULL, delim, &saveptr); + //nav = + strtok_r(NULL, delim, &saveptr); + char *lat_str = strtok_r(NULL, delim, &saveptr); + char *lat_NS = strtok_r(NULL, delim, &saveptr); + char *lon_str = strtok_r(NULL, delim, &saveptr); + char *lon_EW = strtok_r(NULL, delim, &saveptr); + + if (lat_str && lat_NS) { + if ((*lat_NS != 'N') && (*lat_NS != 'S')) { + printf("Invalid North or South indicator in latitude\n"); + return; + } + if (strlen(lat_str) > 9) { + printf("Invalid latitude\n"); + return; + } + if (lat_str[4] != '.') { + printf("Invalid latitude\n"); + return; + } + lat_str[7] = '\0'; + strcat(buf, lat_str); + strcat(buf, lat_NS); + } else { + printf("Invalid latitude\n"); + return; + } + /* secondary table */ + strcat(buf, "/"); + + if (lon_str && lon_EW) { + if ((*lon_EW != 'E') && (*lon_EW != 'W')) { + printf("Invalid East or West indicator in longitude\n"); + return; + } + if (strlen(lon_str) > 10) { + printf("Invalid longitude\n"); + return; + } + if (lon_str[5] != '.') { + printf("Invalid longitude\n"); + return; + } + lon_str[8] = '\0'; + strcat(buf, lon_str); + strcat(buf, lon_EW); + } else { + printf("Invalid longitude\n"); + return; + } + + /* Just this symbolcode only */ + strcat(buf, "/"); + strncat(buf, band_txt[rptr_idx].gpid + 13, 32); + + // printf("Built APRS from old GPS mode=[%s]\n", buf); + strcat(buf, "\r\n"); + + if (-1 == aprs->WriteSock(buf, strlen(buf))) { + if ((errno == EPIPE) || (errno == ECONNRESET) || (errno == ETIMEDOUT) || (errno == ECONNABORTED) || + (errno == ESHUTDOWN) || (errno == EHOSTUNREACH) || (errno == ENETRESET) || (errno == ENETDOWN) || + (errno == ENETUNREACH) || (errno == EHOSTDOWN) || (errno == ENOTCONN)) { + printf("build_aprs_from_gps_and_send: APRS_HOST closed connection, error=%d\n", errno); + close(aprs->GetSock()); + aprs->SetSock( -1 ); + } else + printf("build_aprs_from_gps_and_send: send error=%d\n", errno); + } + return; +} + +bool CQnetGateway::verify_gps_csum(char *gps_text, char *csum_text) +{ + short computed_csum = 0; + char computed_csum_text[16]; + + short int len = strlen(gps_text); + for (short int i=0; i Date: Wed, 12 Dec 2018 13:26:05 -0700 Subject: [PATCH 151/553] qngateway uses unix sockets --- Makefile | 14 +- QnetGateway.cpp | 1746 +++++++++++++++++++++---------------------- QnetGateway.h | 19 +- QnetIcomGateway.cpp | 2 +- QnetIcomGateway.h | 182 +++++ UnixDgramSocket.cpp | 125 ++++ UnixDgramSocket.h | 46 ++ 7 files changed, 1240 insertions(+), 894 deletions(-) create mode 100644 QnetIcomGateway.h create mode 100644 UnixDgramSocket.cpp create mode 100644 UnixDgramSocket.h diff --git a/Makefile b/Makefile index 7bda92b..37e6ac0 100644 --- a/Makefile +++ b/Makefile @@ -54,22 +54,22 @@ itap : $(TAP_PROGRAMS) qnigateway : $(IRCOBJS) QnetIcomGateway.o aprs.o g++ $(CPPFLAGS) -o qnigateway QnetIcomGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread -qngateway : $(IRCOBJS) QnetGateway.o aprs.o - g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread +qngateway : $(IRCOBJS) QnetGateway.o aprs.o UnixDgramSocket.o + g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o UnixDgramSocket.o $(IRCOBJS) $(LDFLAGS) -pthread -qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o +qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o $(LDFLAGS) -pthread -qnrelay : QnetRelay.o +qnrelay : QnetRelay.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) -qnitap : QnetITAP.o Random.o +qnitap : QnetITAP.o Random.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o $(LDFLAGS) -qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) +qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) $(LDFLAGS) -pthread -qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o +qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o $(DSTROBJS) $(LDFLAGS) qnremote : QnetRemote.o Random.o diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 516730f..21f3239 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -252,7 +252,7 @@ bool CQnetGateway::read_config(char *cfgFile) return true; // module - for (short int m=0; m<3; m++) { + for (int m=0; m<3; m++) { std::string path = "module."; path += m + 'a'; path += '.'; @@ -276,9 +276,11 @@ bool CQnetGateway::read_config(char *cfgFile) return true; } - if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, "127.0.0.1")) - return true; - get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, 19998+m); + char unixsockname[16]; + snprintf(unixsockname, 16, "modem2gate%d", m); + get_value(cfg, path+"togateway", modem2gate[m], 1, FILENAME_MAX, unixsockname); + snprintf(unixsockname, 16, "gate2modem%d", m); + get_value(cfg, path+"fromgateway", gate2modem[m], 1, FILENAME_MAX, unixsockname); get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset, -1.0e12, 1.0e12, 0.0); get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); @@ -318,17 +320,15 @@ bool CQnetGateway::read_config(char *cfgFile) get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, 19000); - get_value(cfg, path+"regen_header", bool_regen_header, true); get_value(cfg, path+"aprs_send", bool_send_aprs, true); get_value(cfg, path+"send_qrgs_maps", bool_send_qrgs, true); + get_value(cfg, path+"tolink", gate2link, 1, FILENAME_MAX, "gate2link"); + get_value(cfg, path+"fromlink", link2gate, 1, FILENAME_MAX, "link2gate"); + // APRS path = "aprs."; if (! get_value(cfg, path+"host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) @@ -348,8 +348,6 @@ bool CQnetGateway::read_config(char *cfgFile) get_value(cfg, path+"irc", bool_irc_debug, false); get_value(cfg, path+"dtmf", bool_dtmf_debug, false); - if (! get_value(cfg, "link.outgoing_ip", g2_link.ip, 7, IP_SIZE, "127.0.0.1")) - return true; // file path = "file."; @@ -365,13 +363,6 @@ bool CQnetGateway::read_config(char *cfgFile) if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, "/tmp/qnvoice.txt")) return true; - // link - path = "link."; - get_value(cfg, path+"port", g2_link.port, 16000, 65535, 18997); - - if (! get_value(cfg, path+"ip", g2_link.ip, 7, 15, "127.0.0.1")) - return true; - // timing path = "timing.play."; get_value(cfg, path+"wait", play_wait, 1, 10, 1); @@ -732,7 +723,7 @@ void CQnetGateway::ProcessTimeouts() end_of_audio.vpkt.ctrl = toRptr[i].sequence | 0x40; for (int j=0; j<2; j++) - sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + Gate2Modem[i].Write(end_of_audio.pkt_id, 29); toRptr[i].streamid = 0; @@ -1091,952 +1082,944 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) } } -/* run the main loop for QnetGateway */ -void CQnetGateway::Process() +void CQnetGateway::ProcessG2(ssize_t g2buflen, SDSVT &g2buf) { - // dtmf stuff - int dtmf_buf_count[3] = {0, 0, 0}; - char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; - int dtmf_last_frame[3] = { 0, 0, 0 }; - unsigned int dtmf_counter[3] = { 0, 0, 0 }; - - dstar_dv_init(); - - int max_nfds = 0; - if (g2_sock > max_nfds) - max_nfds = g2_sock; - if (srv_sock > max_nfds) - max_nfds = srv_sock; - printf("g2=%d, srv=%d, MAX+1=%d\n", g2_sock, srv_sock, max_nfds + 1); - - std::future aprs_future, irc_data_future; - if (bool_send_aprs) { // start the beacon thread - try { - aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); - } catch (const std::exception &e) { - printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); - } - if (aprs_future.valid()) - printf("APRS beacon thread started\n"); - } - - try { // start the IRC read thread - irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); - } catch (const std::exception &e) { - printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); - keep_running = false; - } - if (keep_running) - printf("get_irc_data thread started\n"); - - ii->kickWatchdog(IRCDDB_VERSION); - - while (keep_running) { - ProcessTimeouts(); - - // wait 20 ms max - fd_set fdset; - FD_ZERO(&fdset); - FD_SET(g2_sock, &fdset); - FD_SET(srv_sock, &fdset); - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 20000; // 20 ms - (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - - // process packets coming from remote G2 - if (FD_ISSET(g2_sock, &fdset)) { - SDSVT g2buf; - socklen_t fromlen = sizeof(struct sockaddr_in); - int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); - - // save incoming port for mobile systems - if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { - printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } else { - if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { - printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { + if (g2buflen == 56) { + // Find out the local repeater module IP/port to send the data to + int i = g2buf.hdr.rpt1[7] - 'A'; + + /* valid repeater module? */ + if (i>=0 && i<3) { + // toRptr[i] is active if a remote system is talking to it or + // toRptr[i] is receiving data from a cross-band + if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { + if (bool_qso_details) + printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); // bump the counter + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x30; + rptrbuf.vpkt.icm_id = 0x20; + //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); + rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; + rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; + rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; + rptrbuf.vpkt.streamid = g2buf.streamid; + rptrbuf.vpkt.ctrl = g2buf.ctrl; + memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); + memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); + memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); + memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); + memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); + memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); + memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); + + Gate2Modem[i].Write(rptrbuf.pkt_id, 58); + + /* save the header */ + memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); + toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; + + /* This is the active streamid */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; } } - - if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { - if (g2buflen == 56) { - - // Find out the local repeater module IP/port to send the data to - int i = g2buf.hdr.rpt1[7] - 'A'; - - /* valid repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] is active if a remote system is talking to it or - // toRptr[i] is receiving data from a cross-band - if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { - if (bool_qso_details) - printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); // bump the counter - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x30; - rptrbuf.vpkt.icm_id = 0x20; - //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); - rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; - rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; - rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; - rptrbuf.vpkt.streamid = g2buf.streamid; - rptrbuf.vpkt.ctrl = g2buf.ctrl; - memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); - memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); - memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); - memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* save the header */ - memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); - toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; - - /* This is the active streamid */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } + } else { // g2buflen == 27 + if (bool_qso_details && g2buf.ctrl & 0x40) + printf("id=%04x END G2\n", ntohs(g2buf.streamid)); + + /* find out which repeater module to send the data to */ + int i; + for (i=0; i<3; i++) { + /* streamid match ? */ + if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining= 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + Gate2Modem[i].Write(rptrbuf.pkt_id, 29); + + /* timeit */ + time(&toRptr[i].last_time); + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (g2buf.ctrl & 0x40) { + /* clear the saved header */ + memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); + toRptr[i].saved_adr = 0; + + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; } - } else { // g2buflen == 27 - if (bool_qso_details && g2buf.ctrl & 0x40) - printf("id=%04x END G2\n", ntohs(g2buf.streamid)); - - /* find out which repeater module to send the data to */ - int i; - for (i=0; i<3; i++) { - /* streamid match ? */ - if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining= 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); + break; + } + } - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + /* no match ? */ + if ((i == 3) && bool_regen_header) { + /* check if this a continuation of audio that timed out */ + + if (g2buf.ctrl & 0x40) + ; /* we do not care about end-of-QSO */ + else { + /* for which repeater this stream has timed out ? */ + for (i = 0; i < 3; i++) { + /* match saved stream ? */ + if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { + /* repeater module is inactive ? */ + if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { + printf("Re-generating header for streamID=%04x\n", g2buf.streamid); + + toRptr[i].saved_hdr[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); + toRptr[i].saved_hdr[5] = (unsigned char)((toRptr[i].G2_COUNTER++) & 0xff); + + /* re-generate/send the header */ + Gate2Modem[i].Write(toRptr[i].saved_hdr, 58); + + /* send this audio packet to repeater */ + memcpy(rptrbuf.pkt_id, "DSTR", 4); + rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); + rptrbuf.flag[0] = 0x73; + rptrbuf.flag[1] = 0x12; + rptrbuf.flag[2] = 0x00; + rptrbuf.remaining = 0x13; + rptrbuf.vpkt.icm_id = 0x20; + memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); + + Gate2Modem[i].Write(rptrbuf.pkt_id, 29); + + /* make sure that any more audio arriving will be accepted */ + toRptr[i].streamid = g2buf.streamid; + toRptr[i].adr = fromDst4.sin_addr.s_addr; + + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - /* End of stream ? */ - if (g2buf.ctrl & 0x40) { - /* clear the saved header */ - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; + toRptr[i].sequence = rptrbuf.vpkt.ctrl; - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; } break; } } + } + } + } + } +} - /* no match ? */ - if ((i == 3) && bool_regen_header) { - /* check if this a continuation of audio that timed out */ - - if (g2buf.ctrl & 0x40) - ; /* we do not care about end-of-QSO */ - else { - /* for which repeater this stream has timed out ? */ - for (i = 0; i < 3; i++) { - /* match saved stream ? */ - if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { - /* repeater module is inactive ? */ - if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { - printf("Re-generating header for streamID=%04x\n", g2buf.streamid); - - toRptr[i].saved_hdr[4] = (unsigned char)((toRptr[i].G2_COUNTER >> 8) & 0xff); - toRptr[i].saved_hdr[5] = (unsigned char)((toRptr[i].G2_COUNTER++) & 0xff); - - /* re-generate/send the header */ - sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* send this audio packet to repeater */ - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(toRptr[i].G2_COUNTER++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); +void CQnetGateway::ProcessModem(int mod) +{ + char temp_radio_user[CALL_SIZE + 1]; + char temp_mod; - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + char arearp_cs[CALL_SIZE + 1]; + char zonerp_cs[CALL_SIZE + 1]; + char ip[IP_SIZE + 1]; + char tempfile[FILENAME_MAX + 1]; + SDSVT g2buf; - /* make sure that any more audio arriving will be accepted */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; + int recvlen = Modem2Gate[mod].Read(rptrbuf.pkt_id, 58); - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { + if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + if (recvlen == 58) { + vPacketCount = 0U; + if (bool_qso_details) + printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); - } - break; - } - } - } - } - } - } - FD_CLR (g2_sock,&fdset); - } + if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - // process packets coming from local repeater modules - if (FD_ISSET(srv_sock, &fdset)) { - char temp_radio_user[CALL_SIZE + 1]; - char temp_mod; + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - char arearp_cs[CALL_SIZE + 1]; - char zonerp_cs[CALL_SIZE + 1]; - char ip[IP_SIZE + 1]; + if (i>=0 && i<3) { + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (got a header)\n", i); + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0; + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; - char tempfile[FILENAME_MAX + 1]; + /* Initialize the LAST HEARD data for the band */ - SDSVT g2buf; + band_txt[i].streamID = rptrbuf.vpkt.streamid; - socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); + memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); - if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { - if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { + memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); + band_txt[i].lh_mycall[8] = '\0'; - if (recvlen == 58) { - vPacketCount = 0U; - if (bool_qso_details) - printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); + memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); + band_txt[i].lh_sfx[4] = '\0'; - if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { + memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); + band_txt[i].lh_yrcall[8] = '\0'; - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); + band_txt[i].lh_rpt1[8] = '\0'; - if (i>=0 && i<3) { - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (got a header)\n", i); - dtmf_last_frame[i] = 0; - dtmf_counter[i] = 0; - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; + memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].lh_rpt2[8] = '\0'; - /* Initialize the LAST HEARD data for the band */ + time(&band_txt[i].last_time); - band_txt[i].streamID = rptrbuf.vpkt.streamid; + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; + band_txt[i].sent_key_on_msg = false; - memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); + band_txt[i].dest_rptr[0] = '\0'; - memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); - band_txt[i].lh_mycall[8] = '\0'; + /* try to process GPS mode: GPRMC and ID */ + band_txt[i].temp_line[0] = '\0'; + band_txt[i].temp_line_cnt = 0; + band_txt[i].gprmc[0] = '\0'; + band_txt[i].gpid[0] = '\0'; + band_txt[i].is_gps_sent = false; + // band_txt[i].gps_last_time = 0; DO NOT reset it - memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); - band_txt[i].lh_sfx[4] = '\0'; + new_group[i] = true; + to_print[i] = 0; + ABC_grp[i] = false; - memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); - band_txt[i].lh_yrcall[8] = '\0'; + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; - memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); - band_txt[i].lh_rpt1[8] = '\0'; + /* select the band for aprs processing, and lock on the stream ID */ + if (bool_send_aprs) + aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); + } + } - memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].lh_rpt2[8] = '\0'; + /* Is MYCALL valid ? */ + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); + temp_radio_user[8] = '\0'; - time(&band_txt[i].last_time); + int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].sent_key_on_msg = false; + if (mycall_valid == REG_NOERROR) + ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); + else { + if (mycall_valid == REG_NOMATCH) + printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); + else + printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); + } - band_txt[i].dest_rptr[0] = '\0'; + /* send data qnlink */ + if (mycall_valid == REG_NOERROR) + Gate2Link.Write(rptrbuf.pkt_id, recvlen); + + if ( mycall_valid==REG_NOERROR && + memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector + memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && + memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && + rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something + memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ + { + if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) + { + if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - /* try to process GPS mode: GPRMC and ID */ - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - // band_txt[i].gps_last_time = 0; DO NOT reset it - - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - /* select the band for aprs processing, and lock on the stream ID */ - if (bool_send_aprs) - aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + /* YRCALL=/repeater + mod */ + /* YRCALL=/KJ4NHFB */ + + memset(temp_radio_user, ' ', 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); + temp_radio_user[6] = ' '; + temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; + if (temp_radio_user[7] == ' ') + temp_radio_user[7] = 'A'; + temp_radio_user[CALL_SIZE] = '\0'; + + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); + if (result) { /* it is a repeater */ + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that saved port instead of the default port + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set yrcall, can NOT let it be slash and repeater + module */ + memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + + /* set PFCS */ + calcPFCS(g2buf.title, 56); + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + // send to remote gateway + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", + ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), + g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); + + time(&(to_remote_g2[i].last_time)); + } + } } } + } + else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater + rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway + Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { + - /* Is MYCALL valid ? */ memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); + memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); temp_radio_user[8] = '\0'; + bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); + if (result) { + /* destination is a remote system */ + if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* one radio user on a repeater module at a time */ + if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { + uint32_t address; + /* set the destination */ + to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; + memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); + to_remote_g2[i].toDst4.sin_family = AF_INET; + to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x10; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; + g2buf.id = rptrbuf.vpkt.icm_id; + g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; + g2buf.streamid = rptrbuf.vpkt.streamid; + g2buf.ctrl = rptrbuf.vpkt.ctrl; + memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); + /* set rpt1 */ + memset(g2buf.hdr.rpt1, ' ', 8); + memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); + g2buf.hdr.rpt1[7] = temp_mod; + /* set rpt2 */ + memset(g2buf.hdr.rpt2, ' ', 8); + memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); + g2buf.hdr.rpt2[7] = 'G'; + /* set PFCS */ + memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); + memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); + memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); + calcPFCS(g2buf.title, 56); + + + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); + band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + + /* send to remote gateway */ + for (int j=0; j<5; j++) + sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + + printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); + + time(&(to_remote_g2[i].last_time)); + } + } + } + else + { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* the user we are trying to contact is on our gateway */ + /* make sure they are on a different module */ + if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { + /* + The remote repeater has been set, lets fill in the dest_rptr + so that later we can send that to the LIVE web site + */ + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[7] = temp_mod; + band_txt[i].dest_rptr[8] = '\0'; + + i = temp_mod - 'A'; + + /* valid destination repeater module? */ + if (i>=0 && i<3) { + /* + toRptr[i] : receiving from a remote system or cross-band + band_txt[i] : local RF is talking. + */ + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); + rptrbuf.vpkt.hdr.r2[7] = temp_mod; + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); - if (mycall_valid == REG_NOERROR) - ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); - else { - if (mycall_valid == REG_NOMATCH) - printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); - else - printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); - } + Gate2Modem[i].Write(rptrbuf.pkt_id, 58); - /* send data qnlink */ - if (mycall_valid == REG_NOERROR) - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; - if ( mycall_valid==REG_NOERROR && - memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector - memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && - memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && - rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something - memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ - { - if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) - { - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - /* YRCALL=/repeater + mod */ - /* YRCALL=/KJ4NHFB */ - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); - temp_radio_user[6] = ' '; - temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; - if (temp_radio_user[7] == ' ') - temp_radio_user[7] = 'A'; - temp_radio_user[CALL_SIZE] = '\0'; - - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); - if (result) { /* it is a repeater */ - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that saved port instead of the default port - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set yrcall, can NOT let it be slash and repeater + module */ - memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - // send to remote gateway - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", - ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); - - time(&(to_remote_g2[i].last_time)); - } - } - } - } - } - else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); - temp_radio_user[8] = '\0'; - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); - if (result) { - /* destination is a remote system */ - if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set PFCS */ - memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - calcPFCS(g2buf.title, 56); - - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - /* send to remote gateway */ - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); - - time(&(to_remote_g2[i].last_time)); - } - } - } - else - { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + /* bump the G2 counter */ + toRptr[i].G2_COUNTER++; - if (i>=0 && i<3) { - /* the user we are trying to contact is on our gateway */ - /* make sure they are on a different module */ - if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { - /* - The remote repeater has been set, lets fill in the dest_rptr - so that later we can send that to the LIVE web site - */ - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[7] = temp_mod; - band_txt[i].dest_rptr[8] = '\0'; - - i = temp_mod - 'A'; - - /* valid destination repeater module? */ - if (i>=0 && i<3) { - /* - toRptr[i] : receiving from a remote system or cross-band - band_txt[i] : local RF is talking. - */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - - rptrbuf.vpkt.hdr.r2[7] = temp_mod; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - toRptr[i].G2_COUNTER++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } + toRptr[i].sequence = rptrbuf.vpkt.ctrl; } - else - printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); } } - } - else - { - if ('L' != rptrbuf.vpkt.hdr.ur[7]) // as long as this doesn't look like a linking command - playNotInCache = true; // we need to wait until user's transmission is over + else + printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); } } } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - unlink(vm[i].file); - printf("removed voicemail file: %s\n", vm[i].file); - vm[i].file[0] = '\0'; - } else - printf("No voicemail to clear or still recording\n"); - } + else + { + if ('L' != rptrbuf.vpkt.hdr.ur[7]) // as long as this doesn't look like a linking command + playNotInCache = true; // we need to wait until user's transmission is over } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - snprintf(vm[i].message, 21, "VOICEMAIL ON MOD %c ", 'A'+i); - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(vm[i])); - } catch (const std::exception &e) { - printf("Failed to start voicemail playback. Exception: %s\n", e.what()); - } - } else - printf("No voicemail to recall or still recording\n"); + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + unlink(vm[i].file); + printf("removed voicemail file: %s\n", vm[i].file); + vm[i].file[0] = '\0'; + } else + printf("No voicemail to clear or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + + if (i>=0 && i<3) { + /* voicemail file is closed */ + if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { + snprintf(vm[i].message, 21, "VOICEMAIL ON MOD %c ", 'A'+i); + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(vm[i])); + } catch (const std::exception &e) { + printf("Failed to start voicemail playback. Exception: %s\n", e.what()); } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + } else + printf("No voicemail to recall or still recording\n"); + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - if (vm[i].fd >= 0) - printf("Already recording for voicemail on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); - - vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (vm[i].fd < 0) - printf("Failed to create file %s for voicemail\n", tempfile); - else { - strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); - - time(&vm[i].last_time); - vm[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - memcpy(vm[i].header.title, recbuf.title, 56); - } - } + if (i>=0 && i<3) { + if (vm[i].fd >= 0) + printf("Already recording for voicemail on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); + + vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (vm[i].fd < 0) + printf("Failed to create file %s for voicemail\n", tempfile); + else { + strcpy(vm[i].file, tempfile); + printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); + + time(&vm[i].last_time); + vm[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + memcpy(vm[i].header.title, recbuf.title, 56); } } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + } + } + else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - if (recd[i].fd >= 0) - printf("Already recording for echotest on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - - recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (recd[i].fd < 0) - printf("Failed to create file %s for echotest\n", tempfile); - else { - strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); - snprintf(recd[i].message, 21, "ECHO ON MODULE %c ", 'A' + i); - time(&recd[i].last_time); - recd[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - memcpy(recd[i].header.title, recbuf.title, 56); - } - } + if (i>=0 && i<3) { + if (recd[i].fd >= 0) + printf("Already recording for echotest on mod %d\n", i); + else { + memset(tempfile, '\0', sizeof(tempfile)); + snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); + + recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (recd[i].fd < 0) + printf("Failed to create file %s for echotest\n", tempfile); + else { + strcpy(recd[i].file, tempfile); + printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); + snprintf(recd[i].message, 21, "ECHO ON MODULE %c ", 'A' + i); + time(&recd[i].last_time); + recd[i].streamid = rptrbuf.vpkt.streamid; + + memcpy(recbuf.title, "DSVT", 4); + recbuf.config = 0x10; + recbuf.id = rptrbuf.vpkt.icm_id; + recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; + recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; + recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; + recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; + memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); + memset(recbuf.hdr.rpt1, ' ', 8); + memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; + memset(recbuf.hdr.rpt2, ' ', 8); + memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); + recbuf.hdr.rpt2[7] = 'G'; + memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); + + calcPFCS(recbuf.title, 56); + + memcpy(recd[i].header.title, recbuf.title, 56); } - /* check for cross-banding */ } - else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C - (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with - rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; + } + /* check for cross-banding */ + } + else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ + 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater + 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater + (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C + (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with + rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same + int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - if (i>=0 && i<3) { - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[8] = '\0'; - } + if (i>=0 && i<3) { + // The remote repeater has been set, lets fill in the dest_rptr + // so that later we can send that to the LIVE web site + memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); + band_txt[i].dest_rptr[8] = '\0'; + } - i = rptrbuf.vpkt.hdr.r2[7] - 'A'; + i = rptrbuf.vpkt.hdr.r2[7] - 'A'; - // valid destination repeater module? - if (i>=0 && i<3) { - // toRptr[i] : receiving from a remote system or cross-band - // band_txt[i] : local RF is talking. - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); + // valid destination repeater module? + if (i>=0 && i<3) { + // toRptr[i] : receiving from a remote system or cross-band + // band_txt[i] : local RF is talking. + if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { + printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); + rptrbuf.vpkt.hdr.r1[7] = 'G'; + calcPFCS(rptrbuf.pkt_id, 58); - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + Gate2Modem[i].Write(rptrbuf.pkt_id, 58); - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; + /* This is the active streamid */ + toRptr[i].streamid = rptrbuf.vpkt.streamid; + toRptr[i].adr = fromRptr.sin_addr.s_addr; - /* time it, in case stream times out */ - time(&toRptr[i].last_time); + /* time it, in case stream times out */ + time(&toRptr[i].last_time); - /* bump the G2 counter */ - toRptr[i].G2_COUNTER ++; + /* bump the G2 counter */ + toRptr[i].G2_COUNTER ++; - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } + toRptr[i].sequence = rptrbuf.vpkt.ctrl; } } - else - { // recvlen is 29 or 32 - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - time(&band_txt[i].last_time); - - if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data - if (dtmf_buf_count[i] > 0) { - dtmf_file = dtmf_dir; - dtmf_file.push_back('/'); - dtmf_file.push_back('A'+i); - dtmf_file += "_mod_DTMF_NOTIFY"; - if (bool_dtmf_debug) - printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); - FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); - if (dtmf_fp) { - fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); - fclose(dtmf_fp); - } else - printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - - - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - dtmf_counter[i] = 0; - dtmf_last_frame[i] = 0; - } - if (! band_txt[i].sent_key_on_msg) { - band_txt[i].txt[0] = '\0'; - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; - } - // we have the 20-character message, send it to the server... - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); - band_txt[i].sent_key_on_msg = true; - } - // send the "key off" message, this will end up in the openquad.net Last Heard webpage. - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); - - if (playNotInCache) { - // Not in cache, please try again! - FILE *fp = fopen(qnvoicefile.c_str(), "w"); - if (fp) { - fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", band_txt[i].lh_rpt1[7]); - fclose(fp); - } - playNotInCache = false; - } - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; + } + } + else + { // recvlen is 29 or 32 + for (int i=0; i<3; i++) { + if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { + time(&band_txt[i].last_time); + + if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data + if (dtmf_buf_count[i] > 0) { + dtmf_file = dtmf_dir; + dtmf_file.push_back('/'); + dtmf_file.push_back('A'+i); + dtmf_file += "_mod_DTMF_NOTIFY"; + if (bool_dtmf_debug) + printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); + FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); + if (dtmf_fp) { + fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); + fclose(dtmf_fp); + } else + printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - band_txt[i].dest_rptr[0] = '\0'; - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; + if (bool_dtmf_debug) + printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); + memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); + dtmf_buf_count[i] = 0; + dtmf_counter[i] = 0; + dtmf_last_frame[i] = 0; + } + if (! band_txt[i].sent_key_on_msg) { + band_txt[i].txt[0] = '\0'; + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { + set_dest_rptr(i, band_txt[i].dest_rptr); + if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) + band_txt[i].dest_rptr[0] = '\0'; } - else - { // not the end of the voice stream - int ber_data[3]; - int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); - if (ber_data[0] == 0xf85) - band_txt[i].num_dv_silent_frames++; - band_txt[i].num_bit_errors += ber_errs; - band_txt[i].num_dv_frames++; - - if ((ber_data[0] & 0x0ffc) == 0xfc0) { - dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); - if (dtmf_counter[i] > 0) { - if (dtmf_last_frame[i] != dtmf_digit) - dtmf_counter[i] = 0; - } - dtmf_last_frame[i] = dtmf_digit; - dtmf_counter[i]++; - - if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { - if (dtmf_buf_count[i] < MAX_DTMF_BUF) { - const char *dtmf_chars = "147*2580369#ABCD"; - dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; - dtmf_buf_count[i]++; - } - } - const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - if (recvlen == 29) - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); - else - memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); - } else - dtmf_counter[i] = 0; + // we have the 20-character message, send it to the server... + ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); + band_txt[i].sent_key_on_msg = true; + } + // send the "key off" message, this will end up in the openquad.net Last Heard webpage. + ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); + + if (playNotInCache) { + // Not in cache, please try again! + FILE *fp = fopen(qnvoicefile.c_str(), "w"); + if (fp) { + fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", band_txt[i].lh_rpt1[7]); + fclose(fp); } - break; + playNotInCache = false; } - } - vPacketCount++; - if (recvlen == 29) // process the slow data from every voice packet - ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); - else - ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); - /* send data to qnlink */ - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); + band_txt[i].streamID = 0; + band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; + band_txt[i].lh_mycall[0] = '\0'; + band_txt[i].lh_sfx[0] = '\0'; + band_txt[i].lh_yrcall[0] = '\0'; + band_txt[i].lh_rpt1[0] = '\0'; + band_txt[i].lh_rpt2[0] = '\0'; - /* aprs processing */ - if (bool_send_aprs) - // streamID seq audio+text - aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice); - - for (int i=0; i<3; i++) { - /* find out if data must go to the remote G2 */ - if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x20; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; - memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); - if (recvlen == 29) - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); + band_txt[i].last_time = 0; - uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); + band_txt[i].txt[0] = '\0'; + band_txt[i].txt_cnt = 0; - time(&(to_remote_g2[i].last_time)); + band_txt[i].dest_rptr[0] = '\0'; - /* Is this the end-of-stream */ - if (rptrbuf.vpkt.ctrl & 0x40) { - memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; + band_txt[i].num_dv_frames = 0; + band_txt[i].num_dv_silent_frames = 0; + band_txt[i].num_bit_errors = 0; + } + else + { // not the end of the voice stream + int ber_data[3]; + int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); + if (ber_data[0] == 0xf85) + band_txt[i].num_dv_silent_frames++; + band_txt[i].num_bit_errors += ber_errs; + band_txt[i].num_dv_frames++; + + if ((ber_data[0] & 0x0ffc) == 0xfc0) { + dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); + if (dtmf_counter[i] > 0) { + if (dtmf_last_frame[i] != dtmf_digit) + dtmf_counter[i] = 0; } - break; - } - else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest - time(&recd[i].last_time); - - //memcpy(recbuf.title, "DSVT", 4); - //recbuf.config = 0x20; - //recbuf.id = rptrbuf.vpkt.icm_id; - //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - (void)write(recd[i].fd, rptrbuf.vpkt.vasd.voice, 9); - else - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - (void)write(recd[i].fd, rptrbuf.vpkt.vasd1.voice, 9); - - //rec_len = 27; - //(void)write(recd[i].fd, &rec_len, 2); - //(void)write(recd[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* we are in echotest mode, so play it back */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); - } catch (const std::exception &e) { - printf("failed to start PlayFileThread. Exception: %s\n", e.what()); - // When the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); + dtmf_last_frame[i] = dtmf_digit; + dtmf_counter[i]++; + + if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { + if (dtmf_buf_count[i] < MAX_DTMF_BUF) { + const char *dtmf_chars = "147*2580369#ABCD"; + dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; + dtmf_buf_count[i]++; } } - break; - } - else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail - time(&vm[i].last_time); - - //memcpy(recbuf.title, "DSVT", 4); - //recbuf.config = 0x20; - //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - //recbuf.id = rptrbuf.vpkt.icm_id; - //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); + const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; if (recvlen == 29) - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - (void)write(vm[i].fd, rptrbuf.vpkt.vasd.voice, 9); + memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); else - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - (void)write(vm[i].fd, rptrbuf.vpkt.vasd1.voice, 9); - - //rec_len = 27; - //(void)write(vm[i].fd, &rec_len, 2); - //(void)write(vm[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } - break; - } - else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); + memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); + } else + dtmf_counter[i] = 0; + } + break; + } + } + vPacketCount++; + if (recvlen == 29) // process the slow data from every voice packet + ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); + else + ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); + + /* send data to qnlink */ + Gate2Link.Write(rptrbuf.pkt_id, recvlen); + + /* aprs processing */ + if (bool_send_aprs) + // streamID seq audio+text + aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice); + + for (int i=0; i<3; i++) { + /* find out if data must go to the remote G2 */ + if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { + memcpy(g2buf.title, "DSVT", 4); + g2buf.config = 0x20; + g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; + memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); + if (recvlen == 29) + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); + else + memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - /* timeit */ - time(&toRptr[i].last_time); + uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; + // if the address is in the portmap, we'll use that port instead of the default + auto theAddress = portmap.find(address); + to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); + sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - /* bump G2 counter */ - toRptr[i].G2_COUNTER ++; + time(&(to_remote_g2[i].last_time)); - toRptr[i].sequence = rptrbuf.vpkt.ctrl; + /* Is this the end-of-stream */ + if (rptrbuf.vpkt.ctrl & 0x40) { + memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); + to_remote_g2[i].streamid = 0; + to_remote_g2[i].last_time = 0; + } + break; + } + else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest + time(&recd[i].last_time); - /* End of stream ? */ - if (rptrbuf.vpkt.ctrl & 0x40) { - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; + if (recvlen == 29) + (void)write(recd[i].fd, rptrbuf.vpkt.vasd.voice, 9); + else + (void)write(recd[i].fd, rptrbuf.vpkt.vasd1.voice, 9); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + recd[i].streamid = 0; + recd[i].last_time = 0; + close(recd[i].fd); + recd[i].fd = -1; + // printf("Closed echotest audio file:[%s]\n", recd[i].file); + + /* we are in echotest mode, so play it back */ + try { + std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); + } catch (const std::exception &e) { + printf("failed to start PlayFileThread. Exception: %s\n", e.what()); + // When the echotest thread runs, it deletes the file, + // Because the echotest thread did NOT start, we delete the file here + unlink(recd[i].file); } } + break; + } + else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail + time(&vm[i].last_time); - if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) - printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); + if (recvlen == 29) + (void)write(vm[i].fd, rptrbuf.vpkt.vasd.voice, 9); + else + (void)write(vm[i].fd, rptrbuf.vpkt.vasd1.voice, 9); + + if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { + vm[i].streamid = 0; + vm[i].last_time = 0; + close(vm[i].fd); + vm[i].fd = -1; + // printf("Closed voicemail audio file:[%s]\n", vm[i].file); + } + break; + } + else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data + Gate2Modem[i].Write(rptrbuf.pkt_id, 29); + + /* timeit */ + time(&toRptr[i].last_time); + + /* bump G2 counter */ + toRptr[i].G2_COUNTER ++; + + toRptr[i].sequence = rptrbuf.vpkt.ctrl; + + /* End of stream ? */ + if (rptrbuf.vpkt.ctrl & 0x40) { + toRptr[i].last_time = 0; + toRptr[i].streamid = 0; + toRptr[i].adr = 0; + } + break; } } + + if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) + printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); + } + } + } +} + +void CQnetGateway::AddFDSet(int &max, int newfd, fd_set *set) +{ + if (newfd > max) + max = newfd; + FD_SET(newfd, set); +} + +/* run the main loop for QnetGateway */ +void CQnetGateway::Process() +{ + // dtmf stuff initialize + for (int i=0; i<3; i++) { + dtmf_buf_count[i] = 0; + dtmf_buf[i][0] = '\0'; + dtmf_last_frame[i] = 0; + dtmf_counter[i] = 0U; + } + + dstar_dv_init(); + + std::future aprs_future, irc_data_future; + if (bool_send_aprs) { // start the beacon thread + try { + aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); + } catch (const std::exception &e) { + printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); + } + if (aprs_future.valid()) + printf("APRS beacon thread started\n"); + } + + try { // start the IRC read thread + irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); + } catch (const std::exception &e) { + printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); + keep_running = false; + } + if (keep_running) + printf("get_irc_data thread started\n"); + + ii->kickWatchdog(IRCDDB_VERSION); + + while (keep_running) { + ProcessTimeouts(); + + // wait 20 ms max + int max_nfds = 0; + fd_set fdset; + FD_ZERO(&fdset); + AddFDSet(max_nfds, g2_sock, &fdset); + AddFDSet(max_nfds, Link2Gate.GetFD(), &fdset); + for (int i=0; i<3; i++) + if (rptr.mod[i].defined) + AddFDSet(max_nfds, Modem2Gate[i].GetFD(), &fdset); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 20000; // 20 ms + (void)select(max_nfds + 1, &fdset, 0, 0, &tv); + + // process packets coming from remote G2 or g2_link + if (FD_ISSET(g2_sock, &fdset)) { + SDSVT g2buf; + socklen_t fromlen = sizeof(struct sockaddr_in); + ssize_t g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); + + // save incoming port for mobile systems + if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { + printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } else { + if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { + printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + } + } + ProcessG2(g2buflen, g2buf); + FD_CLR(g2_sock, &fdset); + } + + if (FD_ISSET(Link2Gate.GetFD(), &fdset)) { + SDSVT g2buf; + ssize_t g2buflen = Link2Gate.Read(g2buf.title, 56); + ProcessG2(g2buflen, g2buf); + FD_CLR(Link2Gate.GetFD(), &fdset); + } + + // process packets coming from local repeater modules + for (int mod=0; mod<3; mod++) { + if (rptr.mod[mod].defined && FD_ISSET(Modem2Gate[mod].GetFD(), &fdset)) { + ProcessModem(mod); + FD_CLR (Modem2Gate[mod].GetFD(), &fdset); } - FD_CLR (srv_sock,&fdset); } } @@ -2333,7 +2316,7 @@ void CQnetGateway::PlayFileThread(SECHO &edata) memcpy(dstr.vpkt.hdr.nm, edata.header.hdr.sfx, 4); calcPFCS(dstr.pkt_id, 58); - sendto(srv_sock, dstr.pkt_id, 58, 0, (struct sockaddr *)&toRptr[mod].band_addr, sizeof(struct sockaddr_in)); + Gate2Modem[mod].Write(dstr.pkt_id, 58); dstr.remaining = 0x13U; @@ -2395,7 +2378,7 @@ void CQnetGateway::PlayFileThread(SECHO &edata) if (i+1 == ambeblocks) dstr.vpkt.ctrl |= 0x40U; - sendto(srv_sock, dstr.pkt_id, 29, 0, (struct sockaddr *)&toRptr[mod].band_addr, sizeof(struct sockaddr_in)); + Gate2Modem[mod].Write(dstr.pkt_id, 29); std::this_thread::sleep_for(std::chrono::milliseconds(play_delay)); } @@ -2561,16 +2544,17 @@ int CQnetGateway::Init(char *cfgfile) return 1; } - // Open G2 INTERNAL: - // default non-icom 127.0.0.1:19000 - // default icom 172.16.0.20:20000 - srv_sock = open_port(g2_internal); - if (0 > srv_sock) { - printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); + // Open unix sockets between qngateway and qnlink + if (Gate2Link.Open(gate2link.c_str())) + return 1; + if (Link2Gate.Open(link2gate.c_str())) return 1; - } - for (i = 0; i < 3; i++) { + for (i=0; i<3; i++) { + if (rptr.mod[i].defined) { // open unix sockets between qngateway and each defined modem + if (Gate2Modem[i].Open(gate2modem[i].c_str()) || Modem2Gate[i].Open(modem2gate[i].c_str())) + return 1; + } // recording for echotest on local repeater modules recd[i].last_time = 0; recd[i].streamid = 0; @@ -2633,12 +2617,6 @@ int CQnetGateway::Init(char *cfgfile) to_remote_g2[i].last_time = 0; } - /* where to send packets to qnlink */ - memset(&plug, 0, sizeof(struct sockaddr_in)); - plug.sin_family = AF_INET; - plug.sin_port = htons(g2_link.port); - plug.sin_addr.s_addr = inet_addr(g2_link.ip.c_str()); - printf("QnetGateway...entering processing loop\n"); if (bool_send_qrgs) @@ -2652,9 +2630,11 @@ CQnetGateway::CQnetGateway() CQnetGateway::~CQnetGateway() { - if (srv_sock != -1) { - close(srv_sock); - printf("Closed G2_INTERNAL_PORT\n"); + Gate2Link.Close(); + Link2Gate.Close(); + for (int i=0; i<3; i++) { + Gate2Modem[i].Close(); + Modem2Gate[i].Close(); } if (g2_sock != -1) { diff --git a/QnetGateway.h b/QnetGateway.h index 8d4bd2a..b111474 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -19,7 +19,7 @@ #include #include "QnetTypeDefs.h" #include "SEcho.h" - +#include "UnixDgramSocket.h" #include "aprs.h" using namespace libconfig; @@ -91,7 +91,12 @@ private: bool ABC_grp[3] = { false, false, false }; bool C_seen[3] = { false, false, false }; - SPORTIP g2_internal, g2_external, g2_link, ircddb; + SPORTIP g2_external, ircddb; + + CUnixDgramReader Link2Gate, Modem2Gate[3]; + CUnixDgramWriter Gate2Link, Gate2Modem[3]; + + std::string gate2link, link2gate, gate2modem[3], modem2gate[3]; std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; @@ -124,7 +129,6 @@ private: STOREPEATER toRptr[3]; // 0=A, 1=B, 2=C // input from our own local repeater modules - int srv_sock = -1; SDSTR rptrbuf; // 58 or 29 or 32, max is 58 struct sockaddr_in fromRptr; @@ -151,6 +155,13 @@ private: pthread_mutex_t irc_data_mutex = PTHREAD_MUTEX_INITIALIZER; + // dtmf stuff + int dtmf_buf_count[3]; + char dtmf_buf[3][MAX_DTMF_BUF + 1]; + int dtmf_last_frame[3]; + unsigned int dtmf_counter[3]; + + void AddFDSet(int &max, int newfd, fd_set *set); int open_port(const SPORTIP &pip); void calcPFCS(unsigned char *packet, int len); void GetIRCDataThread(); @@ -161,6 +172,8 @@ private: void APRSBeaconThread(); void ProcessTimeouts(); void ProcessSlowData(unsigned char *data, unsigned short sid); + void ProcessG2(ssize_t g2buflen, SDSVT &g2buf); + void ProcessModem(int mod); bool Flag_is_ok(unsigned char flag); // read configuration file diff --git a/QnetIcomGateway.cpp b/QnetIcomGateway.cpp index b078f27..9ab4941 100644 --- a/QnetIcomGateway.cpp +++ b/QnetIcomGateway.cpp @@ -56,7 +56,7 @@ #include "IRCDDB.h" #include "IRCutils.h" #include "versions.h" -#include "QnetGateway.h" +#include "QnetIcomGateway.h" extern void dstar_dv_init(); diff --git a/QnetIcomGateway.h b/QnetIcomGateway.h new file mode 100644 index 0000000..8d4bd2a --- /dev/null +++ b/QnetIcomGateway.h @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2018 by Thomas Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "QnetTypeDefs.h" +#include "SEcho.h" + +#include "aprs.h" + +using namespace libconfig; + +#define IP_SIZE 15 +#define MAXHOSTNAMELEN 64 +#define CALL_SIZE 8 +#define MAX_DTMF_BUF 32 + +typedef struct to_remote_g2_tag { + unsigned short streamid; + struct sockaddr_in toDst4; + time_t last_time; +} STOREMOTEG2; + +typedef struct torepeater_tag { + // help with header re-generation + unsigned char saved_hdr[58]; // repeater format + uint32_t saved_adr; + + unsigned short streamid; + uint32_t adr; + struct sockaddr_in band_addr; + time_t last_time; + std::atomic G2_COUNTER; + unsigned char sequence; +} STOREPEATER; + +typedef struct band_txt_tag { + unsigned short streamID; + unsigned char flags[3]; + char lh_mycall[CALL_SIZE + 1]; + char lh_sfx[5]; + char lh_yrcall[CALL_SIZE + 1]; + char lh_rpt1[CALL_SIZE + 1]; + char lh_rpt2[CALL_SIZE + 1]; + time_t last_time; + char txt[64]; // Only 20 are used + unsigned short txt_cnt; + bool sent_key_on_msg; + + char dest_rptr[CALL_SIZE + 1]; + + // try to process GPS mode: GPRMC and ID + char temp_line[256]; + unsigned short temp_line_cnt; + char gprmc[256]; + char gpid[256]; + bool is_gps_sent; + time_t gps_last_time; + + int num_dv_frames; + int num_dv_silent_frames; + int num_bit_errors; +} SBANDTXT; + +class CQnetGateway { +public: + CQnetGateway(); + ~CQnetGateway(); + void Process(); + int Init(char *cfgfile); + +private: + // text stuff + bool new_group[3] = { true, true, true }; + unsigned char header_type = 0; + short to_print[3] = { 0, 0, 0 }; + bool ABC_grp[3] = { false, false, false }; + bool C_seen[3] = { false, false, false }; + + SPORTIP g2_internal, g2_external, g2_link, ircddb; + + std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; + + bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs, playNotInCache; + + int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; + + unsigned int vPacketCount; + + std::map portmap; + + // data needed for aprs login and aprs beacon + // RPTR defined in aprs.h + SRPTR rptr; + + // local repeater modules being recorded + // This is for echotest and voicemail + SECHO recd[3], vm[3]; + SDSVT recbuf; // 56 or 27, max is 56 + + // the streamids going to remote Gateways from each local module + STOREMOTEG2 to_remote_g2[3]; // 0=A, 1=B, 2=C + + // input from remote G2 gateway + int g2_sock = -1; + struct sockaddr_in fromDst4; + + // Incoming data from remote systems + // must be fed into our local repeater modules. + STOREPEATER toRptr[3]; // 0=A, 1=B, 2=C + + // input from our own local repeater modules + int srv_sock = -1; + SDSTR rptrbuf; // 58 or 29 or 32, max is 58 + struct sockaddr_in fromRptr; + + SDSTR end_of_audio; + + // send packets to g2_link + struct sockaddr_in plug; + + // for talking with the irc server + CIRCDDB *ii; + // for handling APRS stuff + CAPRS *aprs; + + // text coming from local repeater bands + SBANDTXT band_txt[3]; // 0=A, 1=B, 2=C + + /* Used to validate MYCALL input */ + regex_t preg; + + // CACHE used to cache users, repeaters, + // gateways, IP numbers coming from the irc server + + std::map user2rptr_map, rptr2gwy_map, gwy2ip_map; + + pthread_mutex_t irc_data_mutex = PTHREAD_MUTEX_INITIALIZER; + + int open_port(const SPORTIP &pip); + void calcPFCS(unsigned char *packet, int len); + void GetIRCDataThread(); + int get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); + bool get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); + void PlayFileThread(SECHO &edata); + void compute_aprs_hash(); + void APRSBeaconThread(); + void ProcessTimeouts(); + void ProcessSlowData(unsigned char *data, unsigned short sid); + bool Flag_is_ok(unsigned char flag); + + // read configuration file + bool read_config(char *); + bool get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value); + bool get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value); + bool get_value(const Config &cfg, const std::string path, bool &value, bool default_value); + bool get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value); + +/* aprs functions, borrowed from my retired IRLP node 4201 */ + void gps_send(short int rptr_idx); + bool verify_gps_csum(char *gps_text, char *csum_text); + void build_aprs_from_gps_and_send(short int rptr_idx); + + void qrgs_and_maps(); + + void set_dest_rptr(int mod_ndx, char *dest_rptr); + bool validate_csum(SBANDTXT &bt, bool is_gps); +}; diff --git a/UnixDgramSocket.cpp b/UnixDgramSocket.cpp new file mode 100644 index 0000000..799a29b --- /dev/null +++ b/UnixDgramSocket.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2018 by Thomas Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "UnixDgramSocket.h" + +CUnixDgramReader::CUnixDgramReader() : fd(-1) {} + +CUnixDgramReader::~CUnixDgramReader() +{ + Close(); +} + +bool CUnixDgramReader::Open(const char *path) // returns true on failure +{ + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "CUnixDgramReader::Open: socket() failed: %s\n", strerror(errno)); + return true; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); + + int rval = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rval < 0) { + fprintf(stderr, "CUnixDgramReader::Open: bind() failed: %s\n", strerror(errno)); + close(fd); + fd = -1; + return true; + } + return false; +} + +ssize_t CUnixDgramReader::Read(void *buf, size_t size) +{ + if (fd >= 0) + return read(fd, buf, size); + return -1; +} + +void CUnixDgramReader::Close() +{ + if (fd >= 0) + close(fd); + fd = -1; +} + +int CUnixDgramReader::GetFD() +{ + return fd; +} + +CUnixDgramWriter::CUnixDgramWriter() : fd(-1) {} + +CUnixDgramWriter::~CUnixDgramWriter() +{ + Close(); +} + +bool CUnixDgramWriter::Open(const char *path) // returns true on failure +{ + fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "CUnixDgramWriter::Open: socket() failed: %s\n", strerror(errno)); + return true; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); + + int rval = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rval < 0) { + fprintf(stderr, "CUnixDgramWriter::Open: connect() failed: %s\n", strerror(errno)); + close(fd); + fd = -1; + return true; + } + return false; +} + +ssize_t CUnixDgramWriter::Write(void *buf, size_t size) +{ + if (fd >= 0) + return write(fd, buf, size); + return -1; +} + +void CUnixDgramWriter::Close() +{ + if (fd >= 0) + close(fd); + fd = -1; +} + +int CUnixDgramWriter::GetFD() +{ + return fd; +} diff --git a/UnixDgramSocket.h b/UnixDgramSocket.h new file mode 100644 index 0000000..6a79a6f --- /dev/null +++ b/UnixDgramSocket.h @@ -0,0 +1,46 @@ +#pragma once +/* + * Copyright (C) 2018 by Thomas Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +class CUnixDgramReader +{ +public: + CUnixDgramReader(); + ~CUnixDgramReader(); + bool Open(const char *path); + ssize_t Read(void *buf, size_t size); + void Close(); + int GetFD(); +private: + int fd; +}; + +class CUnixDgramWriter +{ +public: + CUnixDgramWriter(); + ~CUnixDgramWriter(); + bool Open(const char *path); + ssize_t Write(void *buf, size_t size); + void Close(); + int GetFD(); +private: + int fd; +}; From 1549de9d20bbd33acaa43999fd378d4d1459b6b0 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 14 Dec 2018 14:25:51 -0700 Subject: [PATCH 152/553] qnlink uses unix sockets --- Makefile | 2 +- QnetLink.cpp | 110 ++++++++++++++++++--------------------------------- QnetLink.h | 10 +++-- 3 files changed, 45 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index 37e6ac0..51ca862 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ qngateway : $(IRCOBJS) QnetGateway.o aprs.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o UnixDgramSocket.o $(IRCOBJS) $(LDFLAGS) -pthread qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o $(LDFLAGS) -pthread + g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o $(LDFLAGS) -pthread qnrelay : QnetRelay.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) diff --git a/QnetLink.cpp b/QnetLink.cpp index d9cec5c..8f9cd2c 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -210,7 +210,7 @@ void CQnetLink::RptrAckThread(char *arg) memcpy(dsvt.hdr.sfx, "RPTR", 4); calcPFCS(dsvt.title,56); - sendto(rptr_sock, dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); + Link2Gate.Write(dsvt.title, 56); std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); dsvt.config = 0x20; @@ -273,7 +273,7 @@ void CQnetLink::RptrAckThread(char *arg) dsvt.vasd.text[2] = 0xf5; break; } - sendto(rptr_sock, dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(toLocalg2)); + Link2Gate.Write(dsvt.title, 27); if (i < 9) std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); } @@ -671,13 +671,8 @@ bool CQnetLink::read_config(const char *cfgFile) get_value(cfg, "link.xrf_port", rmt_xrf_port, 10000, 65535, 30001); get_value(cfg, "link.dcs_port", rmt_dcs_port, 10000, 65535, 30051); - if (! get_value(cfg, "link.incoming_ip", my_g2_link_ip, 7, IP_SIZE, "0.0.0.0")) - return true; - get_value(cfg, "link.port", my_g2_link_port, 10000, 65535, 18997); - - if (! get_value(cfg, "gateway.internal.ip", to_g2_external_ip, 7, IP_SIZE, "0.0.0.0")) - return true; - get_value(cfg, "gateway.external.port", to_g2_external_port, 1024, 65535, 40000); + get_value(cfg, "gateway.tolink", gate2link, 1, FILENAME_MAX, "gate2link"); + get_value(cfg, "gateway.fromlink", link2gate, 1, FILENAME_MAX, "link2gate"); get_value(cfg, "log.qso", qso_details, true); @@ -746,8 +741,7 @@ bool CQnetLink::srv_open() sin.sin_addr.s_addr = inet_addr(my_g2_link_ip.c_str()); sin.sin_port = htons(rmt_xrf_port); if (bind(xrf_g2_sock,(struct sockaddr *)&sin,sizeof(struct sockaddr_in)) != 0) { - printf("Failed to bind gateway socket on port %d for XRF, errno=%d\n", - rmt_xrf_port ,errno); + printf("Failed to bind gateway socket on port %d for XRF, errno=%d\n", rmt_xrf_port ,errno); close(xrf_g2_sock); xrf_g2_sock = -1; return false; @@ -790,44 +784,19 @@ bool CQnetLink::srv_open() return false; } - /* create our repeater socket */ - rptr_sock = socket(PF_INET,SOCK_DGRAM,0); - if (rptr_sock == -1) { - printf("Failed to create repeater socket,errno=%d\n",errno); - close(dcs_g2_sock); - dcs_g2_sock = -1; - close(xrf_g2_sock); - xrf_g2_sock = -1; - close(ref_g2_sock); - ref_g2_sock = -1; - return false; - } - fcntl(rptr_sock,F_SETFL,O_NONBLOCK); - - memset(&sin,0,sizeof(struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = inet_addr(my_g2_link_ip.c_str()); - sin.sin_port = htons(my_g2_link_port); - if (bind(rptr_sock,(struct sockaddr *)&sin,sizeof(struct sockaddr_in)) != 0) { - printf("Failed to bind repeater socket on port %d, errno=%d\n", - my_g2_link_port,errno); + /* create our gateway unix sockets */ + if (Gate2Link.Open(gate2link.c_str()) || Link2Gate.Open(link2gate.c_str())) { close(dcs_g2_sock); dcs_g2_sock = -1; - close(rptr_sock); - rptr_sock = -1; close(xrf_g2_sock); xrf_g2_sock = -1; close(ref_g2_sock); ref_g2_sock = -1; + Gate2Link.Close(); + Link2Gate.Close(); return false; } - /* the local G2 external runs on this IP and port */ - memset(&toLocalg2,0,sizeof(struct sockaddr_in)); - toLocalg2.sin_family = AF_INET; - toLocalg2.sin_addr.s_addr = inet_addr(to_g2_external_ip.c_str()); - toLocalg2.sin_port = htons(to_g2_external_port); - /* initialize all remote links */ for (i = 0; i < 3; i++) { to_remote_g2[i].to_call[0] = '\0'; @@ -855,10 +824,8 @@ void CQnetLink::srv_close() printf("Closed rmt_dcs_port\n"); } - if (rptr_sock != -1) { - close(rptr_sock); - printf("Closed my_g2_link_port\n"); - } + Gate2Link.Close(); + Link2Gate.Close(); if (ref_g2_sock != -1) { close(ref_g2_sock); @@ -1095,12 +1062,12 @@ void CQnetLink::Process() max_nfds = xrf_g2_sock; if (ref_g2_sock > max_nfds) max_nfds = ref_g2_sock; - if (rptr_sock > max_nfds) - max_nfds = rptr_sock; if (dcs_g2_sock > max_nfds) max_nfds = dcs_g2_sock; + if (Gate2Link.GetFD() > max_nfds) + max_nfds = Gate2Link.GetFD(); - printf("xrf=%d, dcs=%d, ref=%d, rptr=%d, MAX+1=%d\n", xrf_g2_sock, dcs_g2_sock, ref_g2_sock, rptr_sock, max_nfds + 1); + printf("xrf=%d, dcs=%d, ref=%d, gateway=%d, MAX+1=%d\n", xrf_g2_sock, dcs_g2_sock, ref_g2_sock, Gate2Link.GetFD(), max_nfds + 1); if (strlen(link_at_startup) >= 8) { if ((link_at_startup[0] == 'A') || (link_at_startup[0] == 'B') || (link_at_startup[0] == 'C')) { @@ -1275,13 +1242,13 @@ void CQnetLink::Process() } FD_ZERO(&fdset); - FD_SET(xrf_g2_sock,&fdset); - FD_SET(dcs_g2_sock,&fdset); - FD_SET(ref_g2_sock,&fdset); - FD_SET(rptr_sock,&fdset); + FD_SET(xrf_g2_sock, &fdset); + FD_SET(dcs_g2_sock, &fdset); + FD_SET(ref_g2_sock, &fdset); + FD_SET(Gate2Link.GetFD(), &fdset); tv.tv_sec = 0; tv.tv_usec = 20000; - (void)select(max_nfds + 1,&fdset,0,0,&tv); + (void)select(max_nfds + 1, &fdset, 0, 0, &tv); if (FD_ISSET(xrf_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); @@ -1588,7 +1555,7 @@ void CQnetLink::Process() } /* relay data to our local G2 */ - sendto(rptr_sock, dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(dsvt.title, 56); /* send data to donglers */ /* no changes here */ @@ -1643,13 +1610,13 @@ void CQnetLink::Process() calcPFCS(from_xrf_torptr_brd.title, 56); /* send the data to the local gateway/repeater */ - sendto(rptr_sock, from_xrf_torptr_brd.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(from_xrf_torptr_brd.title, 56); /* save streamid for use with the audio packets that will arrive after this header */ brd_from_xrf.xrf_streamid = dsvt.streamid; brd_from_xrf.rptr_streamid[brd_from_xrf_idx] = from_xrf_torptr_brd.streamid; - brd_from_xrf_idx ++; + brd_from_xrf_idx++; } } } @@ -1739,7 +1706,7 @@ void CQnetLink::Process() } /* relay data to our local G2 */ - sendto(rptr_sock, dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(dsvt.title, 27); /* send data to donglers */ /* no changes here */ @@ -1763,12 +1730,12 @@ void CQnetLink::Process() if (brd_from_xrf.rptr_streamid[0] != 0x0) { from_xrf_torptr_brd.streamid = brd_from_xrf.rptr_streamid[0]; - sendto(rptr_sock, from_xrf_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(from_xrf_torptr_brd.title, 27); } if (brd_from_xrf.rptr_streamid[1] != 0x0) { from_xrf_torptr_brd.streamid = brd_from_xrf.rptr_streamid[1]; - sendto(rptr_sock, from_xrf_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(from_xrf_torptr_brd.title, 27); } if (dsvt.ctrl & 0x40) { @@ -2463,7 +2430,7 @@ void CQnetLink::Process() } /* send the data to the local gateway/repeater */ - sendto(rptr_sock, rdsvt.dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 56); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { @@ -2521,7 +2488,7 @@ void CQnetLink::Process() } /* send the data to the local gateway/repeater */ - sendto(rptr_sock, rdsvt.dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 27); /* send the data to the donglers */ for (pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { @@ -2674,7 +2641,7 @@ void CQnetLink::Process() /* send the header to the local gateway/repeater */ for (int j=0; j<5; j++) - sendto(rptr_sock, rdsvt.dsvt.title, 56, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 56); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { @@ -2707,7 +2674,7 @@ void CQnetLink::Process() memcpy(rdsvt.dsvt.vasd.voice, dcs_buf+46, 12); /* send the data to the local gateway/repeater */ - sendto(rptr_sock, rdsvt.dsvt.title, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(rdsvt.dsvt.title, 27); /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { @@ -2811,10 +2778,9 @@ void CQnetLink::Process() FD_CLR (dcs_g2_sock,&fdset); } - if (FD_ISSET(rptr_sock, &fdset)) { - socklen_t fromlen = sizeof(struct sockaddr_in); + if (FD_ISSET(Gate2Link.GetFD(), &fdset)) { SDSTR dstr; - int length = recvfrom(rptr_sock, dstr.pkt_id, 100, 0, (struct sockaddr *)&fromRptr,&fromlen); + int length = Gate2Link.Read(dstr.pkt_id, 100); if ((length==58 || length==29 || length==32) && dstr.flag[0]==0x73 && dstr.flag[1] == 0x12 && dstr.flag[2] ==0x0 && (0==memcmp(dstr.pkt_id,"DSTR", 4) || 0==memcmp(dstr.pkt_id,"CCS_", 4)) && dstr.vpkt.icm_id==0x20 && (dstr.remaining==0x30 || dstr.remaining==0x13 || dstr.remaining==0x16)) { @@ -3062,7 +3028,7 @@ void CQnetLink::Process() calcPFCS(fromrptr_torptr_brd.title, 56); - sendto(xrf_g2_sock, fromrptr_torptr_brd.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(fromrptr_torptr_brd.title, 56); brd_from_rptr.from_rptr_streamid = dstr.vpkt.streamid; brd_from_rptr.to_rptr_streamid[brd_from_rptr_idx] = fromrptr_torptr_brd.streamid; @@ -3163,12 +3129,12 @@ void CQnetLink::Process() if (brd_from_rptr.to_rptr_streamid[0]) { fromrptr_torptr_brd.streamid = brd_from_rptr.to_rptr_streamid[0]; - sendto(xrf_g2_sock, fromrptr_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(fromrptr_torptr_brd.title, 27); } if (brd_from_rptr.to_rptr_streamid[1]) { fromrptr_torptr_brd.streamid = brd_from_rptr.to_rptr_streamid[1]; - sendto(xrf_g2_sock, fromrptr_torptr_brd.title, 27, 0, (struct sockaddr *)&toLocalg2,sizeof(struct sockaddr_in)); + Link2Gate.Write(fromrptr_torptr_brd.title, 27); } if (dstr.vpkt.ctrl & 0x40U) { @@ -3310,7 +3276,7 @@ void CQnetLink::Process() } } } - FD_CLR (rptr_sock,&fdset); + FD_CLR (Gate2Link.GetFD(), &fdset); } for (int i=0; i<3; i++) { if (notify_msg[i][0] && 0x0U == tracing[i].streamid) { @@ -3420,7 +3386,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) return; } - sendto(rptr_sock, edata.header.title, 56, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(edata.header.title, 56); edata.header.config = 0x20U; @@ -3483,7 +3449,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) } if (count+1 == ambeblocks && ! edata.is_linked) edata.header.ctrl |= 0x40U; - sendto(rptr_sock, edata.header.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(edata.header.title, 27); } std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); } @@ -3536,7 +3502,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) memcpy(edata.header.vasd.text, edata.header.ctrl ? sdsilence : sdsync, 3); if (i+1==size && lastch) edata.header.ctrl |= 0x40U; // signal the last voiceframe (of the last character) - sendto(rptr_sock, edata.header.title, 27, 0, (struct sockaddr *)&toLocalg2, sizeof(struct sockaddr_in)); + Link2Gate.Write(edata.header.title, 27); } std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); } diff --git a/QnetLink.h b/QnetLink.h index cce3223..1bb4604 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -30,6 +30,7 @@ #include "QnetTypeDefs.h" #include "SEcho.h" #include "Random.h" +#include "UnixDgramSocket.h" using namespace libconfig; @@ -138,12 +139,13 @@ private: } tracing[3]; // input from remote - int xrf_g2_sock, ref_g2_sock, dcs_g2_sock, rptr_sock; + int xrf_g2_sock, ref_g2_sock, dcs_g2_sock; struct sockaddr_in fromDst4; - // After we receive it from remote g2, - // we must feed it to our local repeater. - struct sockaddr_in toLocalg2; + // unix sockets to gateway + std::string link2gate, gate2link; + CUnixDgramReader Gate2Link; + CUnixDgramWriter Link2Gate; // input from our own local repeater struct sockaddr_in fromRptr; From 20419fa3c56f6040b2ead5d5d2c983a34afef7d7 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 15 Dec 2018 12:07:46 -0700 Subject: [PATCH 153/553] qndvap uses unix sockets --- Makefile | 2 +- QnetDVAP.cpp | 191 +++++++++++++++++++++------------------------------ 2 files changed, 78 insertions(+), 115 deletions(-) diff --git a/Makefile b/Makefile index 51ca862..03afad2 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ qnitap : QnetITAP.o Random.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o $(LDFLAGS) qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o - g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) $(LDFLAGS) -pthread + g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o $(DSTROBJS) $(LDFLAGS) -pthread qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o $(DSTROBJS) $(LDFLAGS) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index e202bb8..c210657 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -51,6 +51,7 @@ using namespace libconfig; #include "DVAPDongle.h" #include "QnetTypeDefs.h" #include "Random.h" +#include "UnixDgramSocket.h" #define VERSION DVAP_VERSION #define CALL_SIZE 8 @@ -62,15 +63,17 @@ typedef struct dvap_ack_arg_tag { float ber; } SDVAP_ACK_ARG; +// assigned module, must be A, B or C +static int assigned_module; +// unix sockets +static std::string modem2gate("modem2gate"), gate2modem("gate2modem"); +static CUnixDgramReader Gate2Modem; +static CUnixDgramWriter Modem2Gate; /* Default configuration data */ static char RPTR[RPTR_SIZE + 1]; static char OWNER[RPTR_SIZE + 1]; static char RPTR_MOD; -static char RPTR_VIRTUAL_IP[IP_SIZE + 1]; -static int RPTR_PORT; -static char G2_INTERNAL_IP[IP_SIZE + 1]; -static int G2_PORT; static char DVP_SERIAL[64]; /* APxxxxxx */ static int DVP_FREQ; /* between 144000000 and 148000000 */ static int DVP_PWR; /* between -12 and 10 */ @@ -81,15 +84,12 @@ static int REMOTE_TIMEOUT; /* 1 second */ static int DELAY_BETWEEN; static int DELAY_BEFORE; static bool RPTR_ACK; -static char INVALID_YRCALL_KEY[CALL_SIZE + 1]; static int inactiveMax = 25; /* helper data */ static unsigned char SND_TERM_ID; static char RPTR_and_G[9]; static char RPTR_and_MOD[9]; -static int insock = -1; -static struct sockaddr_in outaddr; static int serfd = -1; static bool busy20000 = false; std::atomic keep_running(true); @@ -157,7 +157,7 @@ static void sig_catch(int signum) exit(0); } -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) +static bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) @@ -168,7 +168,7 @@ bool get_value(const Config &cfg, const char *path, int &value, int min, int max return true; } -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) +static bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) @@ -179,7 +179,7 @@ bool get_value(const Config &cfg, const char *path, double &value, double min, d return true; } -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) +static bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) { if (! cfg.lookupValue(path, value)) value = default_value; @@ -187,7 +187,7 @@ bool get_value(const Config &cfg, const char *path, bool &value, bool default_va return true; } -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +static bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) { if (cfg.lookupValue(path, value)) { int l = value.length(); @@ -204,7 +204,6 @@ bool get_value(const Config &cfg, const char *path, std::string &value, int min, /* process configuration file */ static int read_config(const char *cfgFile) { - int i; Config cfg; printf("Reading file %s\n", cfgFile); @@ -213,28 +212,32 @@ static int read_config(const char *cfgFile) cfg.readFile(cfgFile); } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + fprintf(stderr, "Can't read %s\n", cfgFile); return 1; } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); return 1; } - std::string dvap_path, value; - for (i=0; i<3; i++) { - dvap_path = "module."; - dvap_path += ('a' + i); - if (cfg.lookupValue(dvap_path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "dvap")) - break; + std::string value; + std::string dvap_path("module."); + dvap_path.append(1, 'a' + assigned_module); + if (cfg.lookupValue(dvap_path + ".type", value)) { + if (value.compare("dvap")) { + fprintf(stderr, "assigned module '%c' type is not 'dvap'\n", 'a' + assigned_module); + return 1; } - } - if (i >= 3) { - printf("dvap not defined in any module!\n"); + } else { + fprintf(stderr, "%s is not defined!\n", dvap_path.c_str()); return 1; } - RPTR_MOD = 'A' + i; + RPTR_MOD = 'A' + assigned_module; + char unixsockname[16]; + snprintf(unixsockname, 16, "gate2modem%d", assigned_module); + get_value(cfg, std::string(dvap_path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); + snprintf(unixsockname, 16, "modem2gate%d", assigned_module); + get_value(cfg, std::string(dvap_path+".togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); if (cfg.lookupValue(std::string(dvap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { int l = value.length(); @@ -242,7 +245,7 @@ static int read_config(const char *cfgFile) printf("Call '%s' is invalid length!\n", value.c_str()); return 1; } else { - for (i=0; i r2 memcpy(net_buf.vpkt.hdr.r2, dr.frame.hdr.rpt2, 8); // Internet Labs DVAP Dongle Tech. Ref. V 1.01 has it backwards! @@ -1073,7 +1036,7 @@ static void ReadDVAPThread() memcpy(spack.spkt.mycall, net_buf.vpkt.hdr.my, 8); memcpy(spack.spkt.rpt, OWNER, 7); spack.spkt.rpt[7] = RPTR_MOD; - sendto(insock, spack.pkt_id, 26, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); + Modem2Gate.Write(spack.pkt_id, 26); // Before we send the data to the local gateway, // set RPT1, RPT2 to be the local gateway @@ -1096,7 +1059,7 @@ static void ReadDVAPThread() net_buf.vpkt.ctrl = 0x80; sequence = 0; calcPFCS((unsigned char *)&(net_buf.vpkt.hdr), net_buf.vpkt.hdr.pfcs); - sendto(insock, &net_buf, 58, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); + Modem2Gate.Write(net_buf.pkt_id, 58); // local RF user keying up, start timer dvap_busy = true; @@ -1117,7 +1080,7 @@ static void ReadDVAPThread() if (the_end) net_buf.vpkt.ctrl = sequence | 0x40; memcpy(&net_buf.vpkt.vasd, &dr.frame.vad.voice, 12); - sendto(insock, &net_buf, 29, 0, (struct sockaddr *)&outaddr, sizeof(outaddr)); + Modem2Gate.Write(net_buf.pkt_id, 29); int ber_data[3]; int ber_errs = dstar_dv_decode(net_buf.vpkt.vasd.voice, ber_data); From 0daf0371a263461f08f7ebfb1c15ba743a27c0fb Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 15 Dec 2018 14:16:26 -0700 Subject: [PATCH 154/553] qndvrptr uses unix sockets --- Makefile | 6 +- QnetDVAP.cpp | 4 +- QnetDVRPTR.cpp | 216 ++++++++++++++++++++----------------------------- 3 files changed, 94 insertions(+), 132 deletions(-) diff --git a/Makefile b/Makefile index 03afad2..40aca0f 100644 --- a/Makefile +++ b/Makefile @@ -61,16 +61,16 @@ qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDg g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o $(LDFLAGS) -pthread qnrelay : QnetRelay.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) + g++ $(CPPFLAGS) -o qnrelay QnetRelay.o UnixDgramSocket.o $(LDFLAGS) qnitap : QnetITAP.o Random.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o $(LDFLAGS) + g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o UnixDgramSocket.o $(LDFLAGS) qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o $(DSTROBJS) $(LDFLAGS) -pthread qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o $(DSTROBJS) $(LDFLAGS) + g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o UnixDgramSocket.o $(DSTROBJS) $(LDFLAGS) qnremote : QnetRemote.o Random.o g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o $(LDFLAGS) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index c210657..73adb7e 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -67,7 +67,7 @@ typedef struct dvap_ack_arg_tag { static int assigned_module; // unix sockets -static std::string modem2gate("modem2gate"), gate2modem("gate2modem"); +static std::string modem2gate, gate2modem; static CUnixDgramReader Gate2Modem; static CUnixDgramWriter Modem2Gate; /* Default configuration data */ @@ -616,7 +616,7 @@ int main(int argc, const char **argv) rc = read_config(argv[2]); if (rc != 0) { - printf("Failed to process config file %s\n", argv[1]); + printf("Failed to process config file %s\n", argv[2]); return 1; } diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index 84533bd..44f6ccf 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -26,6 +26,7 @@ #include #include "Random.h" +#include "UnixDgramSocket.h" using namespace libconfig; @@ -58,7 +59,6 @@ static int rqst_count = 6; static unsigned streamid[2] = {0x00, 0x00}; static unsigned char start_Header[8]= {0xD0,0x03,0x00,0x16,0x01,0x00,0x00,0x00}; static unsigned char ptt_off[8]= {0xD0,0x03,0x00,0x1A,0x01,0xff,0x00,0x00}; -static int insock = -1; static int read_config(const char *cfgFile); static void readFrom20000(); @@ -82,13 +82,14 @@ static unsigned char Modem_Init2[12]= {0xD0,0x07,0x00,0x14,0xC0,0x04,0x00,0x57,0 static unsigned char Modem_STATUS[6]= {0xD0,0x01,0x00,0x10,0x00,0x00}; // Status Abfragr static unsigned char Modem_SERIAL[6]= {0xD0,0x01,0x00,0x12,0x00,0x00}; +static int assigned_module; +static std::string gate2modem, modem2gate; +CUnixDgramWriter Modem2Gate; +CUnixDgramReader Gate2Modem; + static char DVRPTR_SERIAL[16]; static char DVCALL[RPTR_SIZE + 1] = {"ABCDEF"}; static char RPTR[RPTR_SIZE + 1] = {"ABCDEF"}; -static char GATEWAY_IP[IP_SIZE + 1] = {"127.0.0.1"}; -static int GATEWAY_PORT = 20000; -static char DVRPTR_INTERNAL_IP[IP_SIZE + 1] = {"127.0.0.1"}; -static int DVRPTR_INTERNAL_PORT = 20000; static char DVRPTR_MOD = 'B'; static int RF_AUDIO_Level = 10; static bool DUPLEX = true; @@ -102,7 +103,6 @@ static bool ok = false; static bool RX_Inverse = 0; static bool TX_Inverse = 0; static int TX_DELAY = 250; /* in milliseconds */ -static struct sockaddr_in outaddr; static unsigned char SND_TERM_ID = 0x00; static char DVCALL_and_G[9]; static char DVCALL_and_MOD[9]; @@ -114,7 +114,6 @@ static char myRPT1[10]; //RX from HF RPT1 static char myUR[10]; static char myCall[10]; static char myCall2[10]; -static char INVALID_YRCALL_KEY[CALL_SIZE + 1] = { "" }; char Ergebnis[250]; @@ -1805,7 +1804,7 @@ static void calcPFCS(unsigned char packet[58])//Netzwerk CRC return; } -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) +static bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) { if (cfg.lookupValue(path, value)) { if (value < min || value > max) @@ -1816,18 +1815,18 @@ bool get_value(const Config &cfg, const char *path, int &value, int min, int max return true; } -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) +//static bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) +//{ +// if (cfg.lookupValue(path, value)) { +// if (value < min || value > max) +// value = default_value; +// } else +// value = default_value; +// printf("%s = [%lg]\n", path, value); +// return true; +//} + +static bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) { if (! cfg.lookupValue(path, value)) value = default_value; @@ -1835,7 +1834,7 @@ bool get_value(const Config &cfg, const char *path, bool &value, bool default_va return true; } -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +static bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) { if (cfg.lookupValue(path, value)) { int l = value.length(); @@ -1861,28 +1860,32 @@ static int read_config(const char *cfgFile) cfg.readFile(cfgFile); } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + fprintf(stderr, "Can't read %s\n", cfgFile); return 1; } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); return 1; } - std::string path, value; - for (i=0; i<3; i++) { - path = "module."; - path += ('a' + i); - if (cfg.lookupValue(path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "dvrptr")) - break; + std::string value; + std::string path("module."); + path += ('a' + assigned_module); + if (cfg.lookupValue(path + ".type", value)) { + if (value.compare("dvrptr")) { + fprintf(stderr, "module %c is not type 'dvrptr'\n", 'a' + assigned_module); + return 1; } - } - if (i >= 3) { - printf("dvrptr not defined in any module!\n"); + } else { + fprintf(stderr, "module %c is not defined\n", 'a' + assigned_module); return 1; } DVRPTR_MOD = 'A' + i; + char unixsockname[16]; + snprintf(unixsockname, 16, "gate2modem%d", assigned_module); + get_value(cfg, std::string(path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); + snprintf(unixsockname, 16, "modem2gate%d", assigned_module); + get_value(cfg, std::string(path+".togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); if (cfg.lookupValue(std::string(path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { int l = value.length(); @@ -1940,18 +1943,6 @@ static int read_config(const char *cfgFile) REMOTE_TIMEOUT = 3; printf("timing.timeout.remote_g2 = [%d]\n", REMOTE_TIMEOUT); - if (get_value(cfg, std::string(path+".invalid_prefix").c_str(), value, 1, CALL_SIZE, "XXX")) { - value.resize(CALL_SIZE, ' '); - for (i=0; i Date: Sun, 16 Dec 2018 11:02:59 -0700 Subject: [PATCH 155/553] qnrelay and qnitap use unix sockets --- QnetITAP.cpp | 194 +++++++++++++++++--------------------------------- QnetITAP.h | 18 +++-- QnetRelay.cpp | 138 ++++++++++++++++++----------------- QnetRelay.h | 19 +++-- 4 files changed, 163 insertions(+), 206 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index 0176917..f24753e 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -46,8 +46,9 @@ std::atomic CQnetITAP::keep_running(true); -CQnetITAP::CQnetITAP() : -COUNTER(0) +CQnetITAP::CQnetITAP(int mod) +: assigned_module(mod) +, COUNTER(0) { } @@ -76,6 +77,9 @@ bool CQnetITAP::Initialize(const char *cfgfile) return true; } + if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) + return true; + return false; } @@ -120,50 +124,6 @@ int CQnetITAP::OpenITAP() return fd; } -int CQnetITAP::OpenSocket(const std::string &address, const unsigned short port) -{ - if (! port) { - printf("ERROR: OpenSocket: non-zero port must be specified.\n"); - return -1; - } - - int fd = ::socket(PF_INET, SOCK_DGRAM, 0); - if (fd < 0) { - printf("Cannot create the UDP socket, err: %d, %s\n", errno, strerror(errno)); - return -1; - } - - sockaddr_in addr; - ::memset(&addr, 0, sizeof(sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = htonl(INADDR_ANY); - - if (! address.empty()) { - addr.sin_addr.s_addr = ::inet_addr(address.c_str()); - if (addr.sin_addr.s_addr == INADDR_NONE) { - printf("The local address is invalid - %s\n", address.c_str()); - close(fd); - return -1; - } - } - - int reuse = 1; - if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - printf("Cannot set the UDP socket %s:%u option, err: %d, %s\n", address.c_str(), port, errno, strerror(errno)); - close(fd); - return -1; - } - - if (::bind(fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { - printf("Cannot bind the UDP socket %s:%u address, err: %d, %s\n", address.c_str(), port, errno, strerror(errno)); - close(fd); - return -1; - } - - return fd; -} - REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) { // Shamelessly adapted from Jonathan G4KLX's CIcomController::GetResponse() @@ -235,20 +195,9 @@ void CQnetITAP::Run(const char *cfgfile) if (serfd < 0) return; - gsock = OpenSocket(G2_INTERNAL_IP, G2_OUT_PORT); - if (gsock < 0) { - ::close(serfd); - return; - } - - vsock = OpenSocket(std::string("0.0.0.0"), MMDVM_OUT_PORT); - if (vsock < 0) { - ::close(serfd); - ::close(gsock); - return; - } - - printf("vsock=%d, gsock=%d serfd=%d\n", vsock, gsock, serfd); + int ug2m = Gate2Modem.GetFD(); + int um2g = Modem2Gate.GetFD(); + printf("gate2modem=%d, modem2gate=%d seral=%d\n", ug2m, um2g, serfd); keep_running = true; unsigned poll_counter = 0; @@ -259,8 +208,8 @@ void CQnetITAP::Run(const char *cfgfile) fd_set readfds; FD_ZERO(&readfds); FD_SET(serfd, &readfds); - FD_SET(gsock, &readfds); - int maxfs = (serfd > gsock) ? serfd : gsock; + FD_SET(ug2m, &readfds); + int maxfs = (serfd > ug2m) ? serfd : ug2m; struct timeval tv; tv.tv_sec = (poll_counter >= 18) ? 1 : 0; @@ -308,20 +257,13 @@ void CQnetITAP::Run(const char *cfgfile) if (rt == RT_TIMEOUT) continue; - } else if (FD_ISSET(gsock, &readfds)) { - sockaddr_in addr; - memset(&addr, 0, sizeof(sockaddr_in)); - socklen_t size = sizeof(sockaddr); - len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); + } else if (FD_ISSET(ug2m, &readfds)) { + len = Gate2Modem.Read(buf, 100); if (len < 0) { printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno)); break; } - - if (ntohs(addr.sin_port) != G2_IN_PORT) - printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); - } if (rt != RT_NOTHING) { @@ -356,8 +298,8 @@ void CQnetITAP::Run(const char *cfgfile) } ::close(serfd); - ::close(gsock); - ::close(vsock); + Gate2Modem.Close(); + Modem2Gate.Close(); } int CQnetITAP::SendTo(const unsigned char length, const unsigned char *buf) @@ -381,22 +323,6 @@ int CQnetITAP::SendTo(const unsigned char length, const unsigned char *buf) return len; } -int CQnetITAP::SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port) -{ - sockaddr_in addr; - ::memset(&addr, 0, sizeof(sockaddr_in)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = ::inet_addr(address.c_str()); - addr.sin_port = htons(port); - - int len = ::sendto(fd, buf, size, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); - if (len < 0) - printf("ERROR: SendTo: fd=%d failed sendto %s:%u err: %d, %s\n", fd, address.c_str(), port, errno, strerror(errno)); - else if (len != size) - printf("ERROR: SendTo: fd=%d tried to sendto %s:%u %d bytes, actually sent %d.\n", fd, address.c_str(), port, size, len); - return len; -} - bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) { static unsigned char counter = 0; @@ -502,18 +428,18 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); - int ret = SendTo(vsock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); + int ret = Modem2Gate.Write(dstr.pkt_id, 58); if (ret != 58) { printf("ERROR: ProcessITAP: Could not write gateway header packet\n"); return true; } if (log_qso) - printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + printf("Sent DSTR to gateway, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); } else if (16 == len) { // ambe dstr.remaining = 0x16; dstr.vpkt.ctrl = itap.voice.sequence; memcpy(dstr.vpkt.vasd.voice, itap.voice.ambe, 12); - int ret = SendTo(vsock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + int ret = Modem2Gate.Write(dstr.pkt_id, 29); if (ret != 29) { printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); return true; @@ -581,31 +507,33 @@ bool CQnetITAP::ReadConfig(const char *cfgFile) cfg.readFile(cfgFile); } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + fprintf(stderr, "Can't read %s\n", cfgFile); return true; } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); return true; } - std::string itap_path, value; - int i; - for (i=0; i<3; i++) { - itap_path = "module."; - itap_path += ('a' + i); - if (cfg.lookupValue(itap_path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "itap")) - break; + std::string value; + std::string itap_path("module."); + itap_path.append(1, 'a' + assigned_module); + if (cfg.lookupValue(itap_path + ".type", value)) { + if (value.compare("itap")) { + fprintf(stderr, "assigned module %c is not 'itap'\n", 'a' + assigned_module); + return true; } - } - if (i >= 3) { - printf("itap not defined in any module!\n"); + } else { + fprintf(stderr, "assigned module %c not defined\n", 'a' + assigned_module); return true; } - RPTR_MOD = 'A' + i; - int repeater_module = i; - MMDVM_OUT_PORT = (unsigned short int)(i + 19998); + RPTR_MOD = 'A' + assigned_module; + + char unixsockname[16]; + snprintf(unixsockname, 16, "gate2modem%d", assigned_module); + GetValue(cfg, std::string(itap_path+".togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); + snprintf(unixsockname, 16, "modem2gate%d", assigned_module); + GetValue(cfg, std::string(itap_path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); if (cfg.lookupValue(std::string(itap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { int l = value.length(); @@ -613,7 +541,7 @@ bool CQnetITAP::ReadConfig(const char *cfgFile) printf("Call '%s' is invalid length!\n", value.c_str()); return true; } else { - for (i=0; i #include "Random.h" // for streamid generation +#include "UnixDgramSocket.h" using namespace libconfig; @@ -81,7 +82,7 @@ class CQnetITAP { public: // functions - CQnetITAP(); + CQnetITAP(int mod); ~CQnetITAP(); void Run(const char *cfgfile); @@ -89,14 +90,14 @@ public: static std::atomic keep_running; private: + int assigned_module; + unsigned short COUNTER; // functions bool Initialize(const char *cfgfile); static void SignalCatch(const int signum); bool ProcessGateway(const int len, const unsigned char *raw); bool ProcessITAP(const unsigned char *raw); - int OpenSocket(const std::string &address, const unsigned short port); int OpenITAP(); - int SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port); int SendTo(const unsigned char length, const unsigned char *buf); REPLY_TYPE GetITAPData(unsigned char *buf); void calcPFCS(const unsigned char *packet, unsigned char *pfcs); @@ -112,15 +113,18 @@ private: char RPTR_MOD; char RPTR[CALL_SIZE + 1]; char OWNER[CALL_SIZE + 1]; - std::string ITAP_DEVICE, G2_INTERNAL_IP; - unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT; + std::string ITAP_DEVICE; bool log_qso; // parameters - int serfd, gsock, vsock; + int serfd; unsigned char tapcounter; - unsigned short COUNTER; // helpers CRandom random; + + // unix sockets + std::string modem2gate, gate2modem; + CUnixDgramWriter Modem2Gate; + CUnixDgramReader Gate2Modem; }; diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 40a3f41..a0da66c 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -38,7 +38,8 @@ std::atomic CQnetRelay::keep_running(true); -CQnetRelay::CQnetRelay() : +CQnetRelay::CQnetRelay(int mod) : +assigned_module(mod), seed(time(NULL)), COUNTER(0) { @@ -116,22 +117,21 @@ int CQnetRelay::OpenSocket(const std::string &address, unsigned short port) return fd; } -void CQnetRelay::Run(const char *cfgfile) +bool CQnetRelay::Run(const char *cfgfile) { if (Initialize(cfgfile)) - return; + return true; msock = OpenSocket(MMDVM_IP, MMDVM_OUT_PORT); if (msock < 0) - return; + return true; - gsock = OpenSocket(G2_INTERNAL_IP, G2_OUT_PORT); - if (gsock < 0) { - ::close(msock); - return; - } + if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) + return true; - printf("msock=%d, gsock=%d\n", msock, gsock); + int fd = Gate2Modem.GetFD(); + + printf("msock=%d, gateway=%d\n", msock, fd); keep_running = true; @@ -139,8 +139,8 @@ void CQnetRelay::Run(const char *cfgfile) fd_set readfds; FD_ZERO(&readfds); FD_SET(msock, &readfds); - FD_SET(gsock, &readfds); - int maxfs = (msock > gsock) ? msock : gsock; + FD_SET(fd, &readfds); + int maxfs = (msock > fd) ? msock : fd; // don't care about writefds and exceptfds: // and we'll wait as long as needed @@ -163,30 +163,26 @@ void CQnetRelay::Run(const char *cfgfile) len = ::recvfrom(msock, buf, 100, 0, (sockaddr *)&addr, &size); if (len < 0) { - printf("ERROR: Run: recvfrom(mmdvm) return error %d, %s\n", errno, strerror(errno)); + fprintf(stderr, "ERROR: Run: recvfrom(mmdvm) return error %d: %s\n", errno, strerror(errno)); break; } if (ntohs(addr.sin_port) != MMDVM_IN_PORT) - printf("DEBUG: Run: read from msock but port was %u, expected %u.\n", ntohs(addr.sin_port), MMDVM_IN_PORT); + fprintf(stderr, "DEBUG: Run: read from msock but port was %u, expected %u.\n", ntohs(addr.sin_port), MMDVM_IN_PORT); } - if (FD_ISSET(gsock, &readfds)) { - len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); + if (FD_ISSET(fd, &readfds)) { + len = Gate2Modem.Read(buf, 100); if (len < 0) { - printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno)); + fprintf(stderr, "ERROR: Run: Gate2Modem.Read() returned error %d: %s\n", errno, strerror(errno)); break; } - - if (ntohs(addr.sin_port) != G2_IN_PORT) - printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); - } if (len == 0) { - printf("DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); + fprintf(stderr, "DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); continue; } @@ -203,12 +199,14 @@ void CQnetRelay::Run(const char *cfgfile) for (int i=0; i<4; i++) title[i] = (buf[i]>=0x20u && buf[i]<0x7fu) ? buf[i] : '.'; title[4] = '\0'; - printf("DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); + fprintf(stderr, "DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); } } ::close(msock); - ::close(gsock); + Gate2Modem.Close(); + Modem2Gate.Close(); + return false; } int CQnetRelay::SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port) @@ -320,18 +318,18 @@ bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) memcpy(dstr.vpkt.hdr.my, dsrp.header.my, 8); memcpy(dstr.vpkt.hdr.nm, dsrp.header.nm, 4); memcpy(dstr.vpkt.hdr.pfcs, dsrp.header.pfcs, 2); - int ret = SendTo(msock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); + int ret = Modem2Gate.Write(dstr.pkt_id, 58); if (ret != 58) { printf("ERROR: ProcessMMDVM: Could not write gateway header packet\n"); return true; } if (log_qso) - printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + printf("Sent DSTR streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dstr.vpkt.streamid), dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); } else if (21 == len) { // ambe dstr.remaining = 0x16; dstr.vpkt.ctrl = dsrp.header.seq; memcpy(dstr.vpkt.vasd.voice, dsrp.voice.ambe, 12); - int ret = SendTo(msock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + int ret = Modem2Gate.Write(dstr.pkt_id, 29); if (log_qso && dstr.vpkt.ctrl&0x40) printf("Sent DSTR end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); @@ -410,30 +408,32 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) return true; } - std::string mmdvm_path, value; - int i; - for (i=0; i<3; i++) { - mmdvm_path = "module."; - mmdvm_path += ('a' + i); - if (cfg.lookupValue(mmdvm_path + ".type", value)) { - if (0 == strcasecmp(value.c_str(), "mmdvm")) - break; + std::string value; + std::string mmdvm_path("module."); + mmdvm_path.append(1, 'a' + assigned_module); + if (cfg.lookupValue(mmdvm_path + ".type", value)) { + if (value.compare("mmdvm")) { + fprintf(stderr, "assigned module is not 'mmdvm' type!\n"); + return true; } - } - if (i >= 3) { - printf("mmdvm not defined in any module!\n"); + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); return true; } - RPTR_MOD = 'A' + i; - int repeater_module = i; + RPTR_MOD = 'A' + assigned_module; + char unixsockname[16]; + snprintf(unixsockname, 16, "gate2module%d", assigned_module); + GetValue(cfg, std::string(mmdvm_path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); + snprintf(unixsockname, 16, "module2gate%d", assigned_module); + GetValue(cfg, std::string(mmdvm_path+",togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); if (cfg.lookupValue(std::string(mmdvm_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { int l = value.length(); if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); + fprintf(stderr, "Call '%s' is invalid length!\n", value.c_str()); return true; } else { - for (i=0; iCALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); + fprintf(stderr, "Call '%s' is invalid length!\n", value.c_str()); return true; } else { - for (i=0; i #include #include - #include +#include "UnixDgramSocket.h" + using namespace libconfig; #define CALL_SIZE 8 @@ -33,9 +34,9 @@ class CQnetRelay { public: // functions - CQnetRelay(); + CQnetRelay(int mod); ~CQnetRelay(); - void Run(const char *cfgfile); + bool Run(const char *cfgfile); // data static std::atomic keep_running; @@ -56,16 +57,22 @@ private: bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value); bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value); + // Unix sockets + int assigned_module; + std::string gate2modem, modem2gate; + CUnixDgramWriter Modem2Gate; + CUnixDgramReader Gate2Modem; + // config data char RPTR_MOD; char RPTR[CALL_SIZE + 1]; char OWNER[CALL_SIZE + 1]; - std::string MMDVM_IP, G2_INTERNAL_IP; - unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT; + std::string MMDVM_IP; + unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT; bool log_qso; // parameters - int msock, gsock; + int msock; unsigned int seed; unsigned short COUNTER; }; From 5910117048738b703ce3ed5cb1562094a2db3fb3 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 22 Dec 2018 07:13:14 -0700 Subject: [PATCH 156/553] qnremote uses unix sockets and a new configuration schema --- ConfigureBase.cpp | 244 ++++ ConfigureBase.h | 47 + Makefile | 11 +- QnetIcomGateway.cpp | 2984 ------------------------------------------- QnetIcomGateway.h | 182 --- QnetRemote.cpp | 241 ++-- UnixDgramSocket.cpp | 2 +- UnixDgramSocket.h | 2 +- defaults | 166 +++ qn.everything.cfg | 24 +- 10 files changed, 540 insertions(+), 3363 deletions(-) create mode 100644 ConfigureBase.cpp create mode 100644 ConfigureBase.h delete mode 100644 QnetIcomGateway.cpp delete mode 100644 QnetIcomGateway.h create mode 100644 defaults diff --git a/ConfigureBase.cpp b/ConfigureBase.cpp new file mode 100644 index 0000000..7583bb2 --- /dev/null +++ b/ConfigureBase.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include "ConfigureBase.h" + +CConfigureBase::CConfigureBase() +{ +} + +CConfigureBase::~CConfigureBase() +{ + defaults.empty(); +} + +char *CConfigureBase::Trim(char *s) +{ + size_t len = strlen(s); + while (len && isspace(s[len-1])) + s[--len] = '\0'; + while (*s && len && isspace(*s)) + len = strlen(++s); + return s; +} + +bool CConfigureBase::ReadConfigFile(const char *configfile, std::map &amap) +{ + FILE *fp = fopen(configfile, "r"); + if (fp) { + char line[128]; + if (fgets(line, 128, fp)) { + char *key = strtok(line, "="); + key = Trim(key); + if (strlen(key) && '#' != *key) { + char *val = strtok(NULL, "\r\n"); + char *val2 = Trim(val); + if ('\'' == *val2) + val = strtok(val2, "'"); + else + val = strtok(val2, "# \t"); + amap[key] = val; + } + } + fclose(fp); + return false; + } + fprintf(stderr, "could not open file %s\n", configfile); + return true; +} + +bool CConfigureBase::Initialize(const char *file) +{ + std::string filename(CFG_DIR); + filename.append("/qndefaults"); + if (ReadConfigFile(filename.c_str(), defaults)) + return true; + return ReadConfigFile(file, cfg); +} + +bool CConfigureBase::GetDefaultBool(const std::string &path, const std::string &mod, bool &dvalue) +{ + std::string value; + if (GetDefaultString(path, mod, value)) + return true; // No default value defined! + if ('0'==value.at(0) || 'f'==value.at(0) || 'F'==value.at(0)) + dvalue = false; + else if ('1'==value.at(0) || 't'==value.at(0) || 'T'==value.at(0)) + dvalue = true; + else { + fprintf(stderr, "%s=%s doesn't seem to be a boolean!\n", path.c_str(), value.c_str()); + return true; + } + return false; +} + +bool CConfigureBase::GetDefaultDouble(const std::string &path, const std::string &mod, double &dvalue) +{ + std::string value; + if (GetDefaultString(path, mod, value)) + return true; // No default value defined! + dvalue = std::stod(value); + return false; +} + +bool CConfigureBase::GetDefaultInt(const std::string &path, const std::string &mod, int &dvalue) +{ + std::string value; + if (GetDefaultString(path, mod, value)) + return true; // No default value defined! + dvalue = std::stoi(value); + return false; +} + +bool CConfigureBase::GetDefaultString(const std::string &path, const std::string &mod, std::string &dvalue) +{ + std::string search, search_again; + if (mod.empty()) { + search = path + "_d"; // there is no mod, so this is a simple search + } else { + search_again = mod; // we're looking from a module value. We may have to look for non-generic module parameters + if (0==path.compare(0, 7, "module_") && ('a'==path.at(7) || 'b'==path.at(7) || 'c'==path.at(7)) && '_'==path.at(8)) { + // path begins with module_{a|b|c}_ + if (0==mod.compare("dvrptr") || 0==mod.compare("dvap") || 0==mod.compare("mmdvm") || 0==mod.compare("itap")) { + // and the module is recognized + search = path; + search.replace(7, 1, 1, 'x'); + search_again += path.substr(8); // now the search_again path might look like dvap_frequency, for example. + } else { + fprintf(stderr, "Unrecognized module type = '%s'\n", mod.c_str()); + return true; + } + } else { + fprintf(stderr, "%s looks like an ilformed request from module '%s'\n", path.c_str(), mod.c_str()); + return true; + } + } + auto it = defaults.find(search); + if (defaults.end() == it) { + it = defaults.find(search_again); + if (defaults.end() == it) { + fprintf(stderr, "%s has no default value!\n", path.c_str()); + return true; + } + } + dvalue = it->second; + return false; +} + +bool CConfigureBase::GetValue(const std::string &path, const std::string &mod, bool &value) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + bool dvalue; + if (GetDefaultBool(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str()); + return true; + } + value = dvalue; // found a value in the defaults + } else { // found a value in the cfg file + char c = it->second.at(0); + if ('0'==c || 'f'==c || 'F'==c) + value = false; + else if ('1'==c || 't'==c || 'T'==c) + value = true; + else { + fprintf(stderr, "%s=%s doesn't seem to define a boolean\n", path.c_str(), it->second.c_str()); + return true; + } + } + printf("%s = %s\n", path.c_str(), value ? "true" : "false"); + return false; +} + +bool CConfigureBase::GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + double dvalue; + if (GetDefaultDouble(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file!\n", path.c_str()); + return true; + } + if (dvalue < min || dvalue > max) { + fprintf(stderr, "Default value %s=%g is out of acceptable range\n", path.c_str(), value); + return true; + } + value = dvalue; + } else { + value = std::stod(it->second); + if (value < min || value > max) { + fprintf(stderr, "%s=%g is out of acceptable range\n", path.c_str(), value); + return true; + } + } + printf("%s = %g\n", path.c_str(), value); + return false; +} + +bool CConfigureBase::GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max) +{ + auto it = cfg.find(path); + if (cfg.end() == it) { + int dvalue; + if (GetDefaultInt(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file or the defaults file\n", path.c_str()); + return true; + } + if (dvalue < min || dvalue > max) { + fprintf(stderr, "Default value %s=%d is out of acceptable range\n", path.c_str(), value); + return true; + } + value = dvalue; + } else { + value = std::stoi(it->second); + if (value < min || value > max) { + fprintf(stderr, "%s=%s is out of acceptable range\n", path.c_str(), it->second.c_str()); + return true; + } + } + printf("%s = %d\n", path.c_str(), value); + return false; +} + +bool CConfigureBase::GetValue(const std::string &path, const std::string &mod, std::string &value, int min, int max) +{ + auto it = cfg.find(path); + if (cfg.end() != it) { + std::string dvalue; + if (GetDefaultString(path, mod, dvalue)) { + fprintf(stderr, "%s not found in either the cfg file for the defaults file\n", path.c_str()); + return true; + } + int l = dvalue.length(); + if (lmax) { + printf("Default value %s='%s' is wrong size\n", path.c_str(), value.c_str()); + return true; + } + } else { + value = it->second; + int l = value.length(); + if (lmax) { + printf("%s='%s' is wrong size\n", path.c_str(), value.c_str()); + return true; + } + } + printf("%s = '%s'\n", path.c_str(), value.c_str()); + return false; +} diff --git a/ConfigureBase.h b/ConfigureBase.h new file mode 100644 index 0000000..50861f5 --- /dev/null +++ b/ConfigureBase.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 by Thomas A. Early N7TAE + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include + +class CConfigureBase { +public: + CConfigureBase(); + virtual ~CConfigureBase(); + virtual bool ReadCfgFile() = 0; + bool Initialize(const char *configfile); + +protected: + std::map cfg; + + char *Trim(char *s); + bool ReadConfigFile(const char *file, std::map &amap); + bool GetValue(const std::string &path, const std::string &mod, bool &value); + bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max); + bool GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max); + bool GetValue(const std::string &path, const std::string &mod, std::string &value, const int min, const int max); +private: + std::map defaults; + + bool GetDefaultBool (const std::string &key, const std::string &mod, bool &dval); + bool GetDefaultDouble(const std::string &key, const std::string &mod, double &dval); + bool GetDefaultInt (const std::string &key, const std::string &mod, int &dval); + bool GetDefaultString(const std::string &key, const std::string &mod, std::string &dval); +}; diff --git a/Makefile b/Makefile index 40aca0f..76c8dc4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Copyright (c) 2018 by Thomas A. Early N7TAE +# Copyright (c) 2018-2019 by Thomas A. Early N7TAE # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -42,18 +42,13 @@ MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr TAP_PROGRAMS=qngateway qnlink qnremote qnvoice qnitap -ICM_PROGRAMS=qnigateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) mmdvm : $(MDV_PROGRAMS) dvap : $(DVP_PROGRAMS) dvrptr : $(DVR_PROGRAMS) -icom : $(ICM_PROGRAMS) itap : $(TAP_PROGRAMS) -qnigateway : $(IRCOBJS) QnetIcomGateway.o aprs.o - g++ $(CPPFLAGS) -o qnigateway QnetIcomGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread - qngateway : $(IRCOBJS) QnetGateway.o aprs.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o UnixDgramSocket.o $(IRCOBJS) $(LDFLAGS) -pthread @@ -72,8 +67,8 @@ qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o UnixDgramSocket.o $(DSTROBJS) $(LDFLAGS) -qnremote : QnetRemote.o Random.o - g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o $(LDFLAGS) +qnremote : QnetRemote.o Random.o UnixDgramSocket.o ConfigureBase.o + g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o UnixDgramSocket.o ConfigureBase.o $(LDFLAGS) qnvoice : QnetVoice.o Random.o g++ $(CPPFLAGS) -o qnvoice QnetVoice.o Random.o $(LDFLAGS) diff --git a/QnetIcomGateway.cpp b/QnetIcomGateway.cpp deleted file mode 100644 index 9ab4941..0000000 --- a/QnetIcomGateway.cpp +++ /dev/null @@ -1,2984 +0,0 @@ -/* - * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2017-2018 by Thomas Early N7TAE - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -/* by KI4LKF, N7TAE */ -/* - QnetGateway is a dstar G2 gateway, using irc routing - adapted from the OpenG2 G2 gateway - Version 2.61 or higher will use ONLY the irc mechanism of routing - and it will NOT use any local Postgres databases or any TRUST(s) -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "IRCDDB.h" -#include "IRCutils.h" -#include "versions.h" -#include "QnetIcomGateway.h" - - -extern void dstar_dv_init(); -extern int dstar_dv_decode(const unsigned char *d, int data[3]); - -static std::atomic keep_running(true); -static std::atomic G2_COUNTER_OUT(0); -static unsigned short OLD_REPLY_SEQ = 0; -static unsigned short NEW_REPLY_SEQ = 0; - -/* signal catching function */ -static void sigCatch(int signum) -{ - /* do NOT do any serious work here */ - if ((signum == SIGTERM) || (signum == SIGINT)) - keep_running = false; - - return; -} - -void CQnetGateway::set_dest_rptr(int mod_ndx, char *dest_rptr) -{ - FILE *statusfp = fopen(status_file.c_str(), "r"); - if (statusfp) { - setvbuf(statusfp, (char *)NULL, _IOLBF, 0); - - char statusbuf[1024]; - while (fgets(statusbuf, 1020, statusfp) != NULL) { - char *p = strchr(statusbuf, '\r'); - if (p) - *p = '\0'; - p = strchr(statusbuf, '\n'); - if (p) - *p = '\0'; - - const char *delim = ","; - char *saveptr = NULL; - char *status_local_mod = strtok_r(statusbuf, delim, &saveptr); - char *status_remote_stm = strtok_r(NULL, delim, &saveptr); - char *status_remote_mod = strtok_r(NULL, delim, &saveptr); - - if (!status_local_mod || !status_remote_stm || !status_remote_mod) - continue; - - if ( ((*status_local_mod == 'A') && (mod_ndx == 0)) || - ((*status_local_mod == 'B') && (mod_ndx == 1)) || - ((*status_local_mod == 'C') && (mod_ndx == 2)) ) { - strncpy(dest_rptr, status_remote_stm, CALL_SIZE); - dest_rptr[7] = *status_remote_mod; - dest_rptr[CALL_SIZE] = '\0'; - break; - } - } - fclose(statusfp); - } - return; -} - -/* compute checksum */ -void CQnetGateway::calcPFCS(unsigned char *packet, int len) -{ - const unsigned short crc_tabccitt[256] = { - 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, - 0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e,0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876, - 0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd,0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5, - 0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c,0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974, - 0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb,0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3, - 0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a,0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72, - 0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9,0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1, - 0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738,0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70, - 0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7,0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff, - 0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036,0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e, - 0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5,0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd, - 0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134,0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c, - 0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3,0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb, - 0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232,0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a, - 0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1,0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9, - 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 - }; - unsigned short crc_dstar_ffff = 0xffff; - short int low, high; - unsigned short tmp; - - switch (len) { - case 56: - low = 15; - high = 54; - break; - case 58: - low = 17; - high = 56; - break; - default: - return; - } - - for (unsigned short int i = low; i < high ; i++) { - unsigned short short_c = 0x00ff & (unsigned short)packet[i]; - tmp = (crc_dstar_ffff & 0x00ff) ^ short_c; - crc_dstar_ffff = (crc_dstar_ffff >> 8) ^ crc_tabccitt[tmp]; - } - crc_dstar_ffff = ~crc_dstar_ffff; - tmp = crc_dstar_ffff; - - if (len == 56) { - packet[54] = (unsigned char)(crc_dstar_ffff & 0xff); - packet[55] = (unsigned char)((tmp >> 8) & 0xff); - } else { - packet[56] = (unsigned char)(crc_dstar_ffff & 0xff); - packet[57] = (unsigned char)((tmp >> 8) & 0xff); - } - - return; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path.c_str(), value); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path.c_str(), value); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path.c_str(), value ? "true" : "false"); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path.c_str()); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path.c_str(), value.c_str()); - return true; -} - -/* process configuration file */ -bool CQnetGateway::read_config(char *cfgFile) -{ - Config cfg; - - printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - // ircddb - std::string path("ircddb."); - if (! get_value(cfg, path+"login", owner, 3, CALL_SIZE-2, "UNDEFINED")) - return true; - if (0 == owner.compare("UNDEFINED")) { - fprintf(stderr, "You must specify your lisensed callsign in ircddb.login\n"); - return true; - } - OWNER = owner; - ToLower(owner); - ToUpper(OWNER); - printf("OWNER=[%s]\n", OWNER.c_str()); - OWNER.resize(CALL_SIZE, ' '); - - if (! get_value(cfg, path+"host", ircddb.ip, 3, MAXHOSTNAMELEN, "rr.openquad.net")) - return true; - - get_value(cfg, path+"port", ircddb.port, 1000, 65535, 9007); - - if(! get_value(cfg, path+"password", irc_pass, 0, 512, "1111111111111111")) - return true; - - // modules - bool is_icom = false; - bool is_not_icom = false; - for (short int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; - path += '.'; - std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - printf("%s = [%s]\n", std::string(path+"type").c_str(), type.c_str()); - rptr.mod[m].defined = true; - if (0 == type.compare("icom")) { - rptr.mod[m].package_version = ICOM_VERSION; - is_icom = true; - } else if (0 == type.compare("dvap")) { - rptr.mod[m].package_version = DVAP_VERSION; - is_not_icom = true; - } else if (0 == type.compare("dvrptr")) { - rptr.mod[m].package_version = DVRPTR_VERSION; - is_not_icom = true; - } else if (0 == type.compare("mmdvm")) { - rptr.mod[m].package_version = MMDVM_VERSION; - is_not_icom = true; - } else if (0 == type.compare("itap")) { - rptr.mod[m].package_version = ITAP_VERSION; - is_not_icom = true; - } else { - printf("module type '%s' is invalid\n", type.c_str()); - return true; - } - if (is_icom && is_not_icom) { - printf("cannot define both icom and non-icom modules\n"); - return true; - } - - if (! get_value(cfg, std::string(path+"ip").c_str(), rptr.mod[m].portip.ip, 7, IP_SIZE, is_icom ? "172.16.0.1" : "127.0.0.1")) - return true; - get_value(cfg, std::string(path+"port").c_str(), rptr.mod[m].portip.port, 16000, 65535, is_icom ? 20000 : 19998+m); - get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); - get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset, -1.0e12, 1.0e12, 0.0); - get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); - get_value(cfg, std::string(path+"agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); - get_value(cfg, std::string(path+"latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); - get_value(cfg, std::string(path+"longitude").c_str(), rptr.mod[m].longitude, -180.0, 180.0, 0.0); - if (! cfg.lookupValue(path+"desc1", rptr.mod[m].desc1)) - rptr.mod[m].desc1 = ""; - if (! cfg.lookupValue(path+"desc2", rptr.mod[m].desc2)) - rptr.mod[m].desc2 = ""; - if (! get_value(cfg, std::string(path+"url").c_str(), rptr.mod[m].url, 0, 80, "github.com/n7tae/QnetGateway")) - return true; - // truncate strings - if (rptr.mod[m].desc1.length() > 20) - rptr.mod[m].desc1.resize(20); - if (rptr.mod[m].desc2.length() > 20) - rptr.mod[m].desc2.resize(20); - // make the long description for the log - if (rptr.mod[m].desc1.length()) - rptr.mod[m].desc = rptr.mod[m].desc1 + ' '; - rptr.mod[m].desc += rptr.mod[m].desc2; - } else - rptr.mod[m].defined = false; - } - if (! is_icom && ! is_not_icom) { - printf("No modules defined!\n"); - return true; - } else if (is_icom) { // make sure all ICOM modules have the same IP and port number - std::string addr; - int port; - for (int i=0; i<3; i++) { - if (rptr.mod[i].defined) { - if (addr.size()) { - if (addr.compare(rptr.mod[i].portip.ip) || port!=rptr.mod[i].portip.port) { - printf("all defined ICOM modules must have the same IP and port number!\n"); - return true; - } - } else { - addr = rptr.mod[i].portip.ip; - port = rptr.mod[i].portip.port; - } - } - } - for (int i=0; i<3; i++) { - if (! rptr.mod[i].defined) { - rptr.mod[i].portip.ip = addr; - rptr.mod[i].portip.port = port; - } - } - } - - // gateway - path = "gateway."; - if (! get_value(cfg, path+"local_irc_ip", local_irc_ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - if (! get_value(cfg, path+"external.ip", g2_external.ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - - if (! get_value(cfg, path+"internal.ip", g2_internal.ip, 7, IP_SIZE, "172.16.0.20")) - return true; - - get_value(cfg, path+"internal.port", g2_internal.port, 16000, 65535, 20000); - - get_value(cfg, path+"regen_header", bool_regen_header, true); - - get_value(cfg, path+"aprs_send", bool_send_aprs, true); - - get_value(cfg, path+"send_qrgs_maps", bool_send_qrgs, true); - - // APRS - path = "aprs."; - if (! get_value(cfg, path+"host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) - return true; - - get_value(cfg, path+"port", rptr.aprs.port, 10000, 65535, 14580); - - get_value(cfg, path+"interval", rptr.aprs_interval, 40, 1000, 40); - - if (! get_value(cfg, path+"filter", rptr.aprs_filter, 0, 512, "")) - return true; - - // log - path = "log."; - get_value(cfg, path+"qso", bool_qso_details, false); - - get_value(cfg, path+"irc", bool_irc_debug, false); - - get_value(cfg, path+"dtmf", bool_dtmf_debug, false); - if (! get_value(cfg, "link.outgoing_ip", g2_link.ip, 7, IP_SIZE, "127.0.0.1")) - return true; - - // file - path = "file."; - if (! get_value(cfg, path+"echotest", echotest_dir, 2, FILENAME_MAX, "/tmp")) - return true; - - if (! get_value(cfg, path+"dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) - return true; - - if (! get_value(cfg, path+"status", status_file, 2, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) - return true; - - if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, "/tmp/qnvoice.txt")) - return true; - - // link - path = "link."; - get_value(cfg, path+"port", g2_link.port, 16000, 65535, 18997); - - if (! get_value(cfg, path+"ip", g2_link.ip, 7, 15, "127.0.0.1")) - return true; - - // timing - path = "timing.play."; - get_value(cfg, path+"wait", play_wait, 1, 10, 1); - - get_value(cfg, path+"delay", play_delay, 9, 25, 19); - - path = "timing.timeout."; - get_value(cfg, path+"echo", echotest_rec_timeout, 1, 10, 1); - - get_value(cfg, path+"voicemail", voicemail_rec_timeout, 1, 10, 1); - - get_value(cfg, path+"remote_g2", from_remote_g2_timeout, 1, 10, 2); - - get_value(cfg, path+"local_rptr", from_local_rptr_timeout, 1, 10, 1); - - return false; -} - -// Create ports -int CQnetGateway::open_port(const SPORTIP &pip) -{ - struct sockaddr_in sin; - - int sock = socket(PF_INET, SOCK_DGRAM, 0); - if (0 > sock) { - printf("Failed to create socket on %s:%d, errno=%d, %s\n", pip.ip.c_str(), pip.port, errno, strerror(errno)); - return -1; - } - fcntl(sock, F_SETFL, O_NONBLOCK); - - memset(&sin, 0, sizeof(struct sockaddr_in)); - sin.sin_family = AF_INET; - sin.sin_port = htons(pip.port); - sin.sin_addr.s_addr = inet_addr(pip.ip.c_str()); - - int reuse = 1; - if (::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - printf("Cannot set the UDP socket (port %u) option, err: %d, %s\n", pip.port, errno, strerror(errno)); - return -1; - } - - if (bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) != 0) { - printf("Failed to bind %s:%d, errno=%d, %s\n", pip.ip.c_str(), pip.port, errno, strerror(errno)); - close(sock); - return -1; - } - - return sock; -} - -/* receive data from the irc server and save it */ -void CQnetGateway::GetIRCDataThread() -{ - std::string user, rptr, gateway, ipaddr; - DSTAR_PROTOCOL proto; - IRCDDB_RESPONSE_TYPE type; - struct sigaction act; - short last_status = 0; - - act.sa_handler = sigCatch; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART; - if (sigaction(SIGTERM, &act, 0) != 0) { - printf("GetIRCDataThread: sigaction-TERM failed, error=%d\n", errno); - keep_running = false; - return; - } - if (sigaction(SIGINT, &act, 0) != 0) { - printf("GetIRCDataThread: sigaction-INT failed, error=%d\n", errno); - keep_running = false; - return; - } - if (sigaction(SIGPIPE, &act, 0) != 0) { - printf("GetIRCDataThread: sigaction-PIPE failed, error=%d\n", errno); - keep_running = false; - return; - } - - short threshold = 0; - bool not_announced[3]; - for (int i=0; i<3; i++) - not_announced[i] = this->rptr.mod[i].defined; // announce to all modules that are defined! - bool is_quadnet = (0 == ircddb.ip.compare("rr.openquad.net")); - while (keep_running) { - int rc = ii->getConnectionState(); - if (rc > 5 && rc < 8 && is_quadnet) { - char ch = '\0'; - if (not_announced[0]) - ch = 'A'; - else if (not_announced[1]) - ch = 'B'; - else if (not_announced[2]) - ch = 'C'; - if (ch) { - // we need to announce, but can we? - struct stat sbuf; - if (stat(qnvoicefile.c_str(), &sbuf)) { - // yes, there is no qnvoicefile, so create it - FILE *fp = fopen(qnvoicefile.c_str(), "w"); - if (fp) { - fprintf(fp, "%c_connected2network.dat_WELCOME_TO_QUADNET", ch); - fclose(fp); - not_announced[ch - 'A'] = false; - } else - fprintf(stderr, "could not open %s\n", qnvoicefile.c_str()); - } - } - } - threshold++; - if (threshold >= 100) { - if ((rc == 0) || (rc == 10)) { - if (last_status != 0) { - printf("irc status=%d, probable disconnect...\n", rc); - last_status = 0; - } - } else if (rc == 7) { - if (last_status != 2) { - printf("irc status=%d, probable connect...\n", rc); - last_status = 2; - } - } else { - if (last_status != 1) { - printf("irc status=%d, probable connect...\n", rc); - last_status = 1; - } - } - threshold = 0; - } - - while (((type = ii->getMessageType()) != IDRT_NONE) && keep_running) { - if (type == IDRT_USER) { - ii->receiveUser(user, rptr, gateway, ipaddr); - if (!user.empty()) { - if (!rptr.empty() && !gateway.empty() && !ipaddr.empty()) { - if (bool_irc_debug) - printf("C-u:%s,%s,%s,%s\n", user.c_str(), rptr.c_str(), gateway.c_str(), ipaddr.c_str()); - - pthread_mutex_lock(&irc_data_mutex); - - user2rptr_map[user] = rptr; - rptr2gwy_map[rptr] = gateway; - gwy2ip_map[gateway] = ipaddr; - - pthread_mutex_unlock(&irc_data_mutex); - - // printf("%d users, %d repeaters, %d gateways\n", user2rptr_map.size(), rptr2gwy_map.size(), gwy2ip_map.size()); - - } - } - } else if (type == IDRT_REPEATER) { - ii->receiveRepeater(rptr, gateway, ipaddr, proto); - if (!rptr.empty()) { - if (!gateway.empty() && !ipaddr.empty()) { - if (bool_irc_debug) - printf("C-r:%s,%s,%s\n", rptr.c_str(), gateway.c_str(), ipaddr.c_str()); - - pthread_mutex_lock(&irc_data_mutex); - - rptr2gwy_map[rptr] = gateway; - gwy2ip_map[gateway] = ipaddr; - - pthread_mutex_unlock(&irc_data_mutex); - - // printf("%d repeaters, %d gateways\n", rptr2gwy_map.size(), gwy2ip_map.size()); - - } - } - } else if (type == IDRT_GATEWAY) { - ii->receiveGateway(gateway, ipaddr, proto); - if (!gateway.empty() && !ipaddr.empty()) { - if (bool_irc_debug) - printf("C-g:%s,%s\n", gateway.c_str(),ipaddr.c_str()); - - pthread_mutex_lock(&irc_data_mutex); - - gwy2ip_map[gateway] = ipaddr; - - pthread_mutex_unlock(&irc_data_mutex); - - // printf("%d gateways\n", gwy2ip_map.size()); - - } - } - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - printf("GetIRCDataThread exiting...\n"); - return; -} - -/* return codes: 0=OK(found it), 1=TRY AGAIN, 2=FAILED(bad data) */ -int CQnetGateway::get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU) -{ - char temp[CALL_SIZE + 1]; - - memset(arearp_cs, ' ', CALL_SIZE); - arearp_cs[CALL_SIZE] = '\0'; - memset(zonerp_cs, ' ', CALL_SIZE); - zonerp_cs[CALL_SIZE] = '\0'; - *mod = ' '; - - /* find the user in the CACHE */ - if (RoU == 'U') { - auto user_pos = user2rptr_map.find(call); - if (user_pos != user2rptr_map.end()) { - memcpy(arearp_cs, user_pos->second.c_str(), 7); - *mod = user_pos->second.c_str()[7]; - } else - return 1; - } else if (RoU == 'R') { - memcpy(arearp_cs, call, 7); - *mod = call[7]; - } else { - printf("Invalid specification %c for RoU\n", RoU); - return 2; - } - - if (*mod == 'G') { - printf("Invalid module %c\n", *mod); - return 2; - } - - memcpy(temp, arearp_cs, 7); - temp[7] = *mod; - temp[CALL_SIZE] = '\0'; - - auto rptr_pos = rptr2gwy_map.find(temp); - if (rptr_pos != rptr2gwy_map.end()) { - memcpy(zonerp_cs, rptr_pos->second.c_str(), CALL_SIZE); - zonerp_cs[CALL_SIZE] = '\0'; - - auto gwy_pos = gwy2ip_map.find(zonerp_cs); - if (gwy_pos != gwy2ip_map.end()) { - strncpy(ip, gwy_pos->second.c_str(), IP_SIZE); - ip[IP_SIZE] = '\0'; - return 0; - } else { - /* printf("Could not find IP for Gateway %s\n", zonerp_cs); */ - return 1; - } - } else { - /* printf("Could not find Gateway for repeater %s\n", temp); */ - return 1; - } - - return 2; -} - -bool CQnetGateway::get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU) -{ - pthread_mutex_lock(&irc_data_mutex); - int rc = get_yrcall_rptr_from_cache(call, arearp_cs, zonerp_cs, mod, ip, RoU); - pthread_mutex_unlock(&irc_data_mutex); - if (rc == 0) - return true; - else if (rc == 2) - return false; - - /* at this point, the data is not in cache */ - /* report the irc status */ - int status = ii->getConnectionState(); - // printf("irc status=%d\n", status); - if (status != 7) { - printf("Remote irc database not ready, irc status is not 7, try again\n"); - return false; - } - - /* request data from irc server */ - if (RoU == 'U') { - printf("User [%s] not in local cache, try again\n", call); - /*** YRCALL=KJ4NHFBL ***/ - if (((call[6] == 'A') || (call[6] == 'B') || (call[6] == 'C')) && (call[7] == 'L')) - printf("If this was a gateway link request, that is ok\n"); - - if (!ii->findUser(call)) { - printf("findUser(%s): Network error\n", call); - return false; - } - } else if (RoU == 'R') { - printf("Repeater [%s] not in local cache, try again\n", call); - if (!ii->findRepeater(call)) { - printf("findRepeater(%s): Network error\n", call); - return false; - } - } - - return false; -} - -bool CQnetGateway::Flag_is_ok(unsigned char flag) -{ - // normal break emr emr+break - return 0x00U==flag || 0x08U==flag || 0x20U==flag || 0x28U==flag; -} - -void CQnetGateway::ProcessTimeouts() -{ - for (int i=0; i<3; i++) { - time_t t_now; - /* echotest recording timed out? */ - if (recd[i].last_time != 0) { - time(&t_now); - if ((t_now - recd[i].last_time) > echotest_rec_timeout) { - printf("Inactivity on echotest recording mod %d, removing stream id=%04x\n", i, recd[i].streamid); - - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* START: echotest thread setup */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); - } catch (const std::exception &e) { - printf("Failed to start echotest thread. Exception: %s\n", e.what()); - // when the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - /* END: echotest thread setup */ - } - } - - /* voicemail recording timed out? */ - if (vm[i].last_time != 0) { - time(&t_now); - if ((t_now - vm[i].last_time) > voicemail_rec_timeout) { - printf("Inactivity on voicemail recording mod %d, removing stream id=%04x\n", i, vm[i].streamid); - - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } - } - - // any stream going to local repeater timed out? - if (toRptr[i].last_time != 0) { - time(&t_now); - // The stream can be from a cross-band, or from a remote system, - // so we could use either FROM_LOCAL_RPTR_TIMEOUT or FROM_REMOTE_G2_TIMEOUT - // but FROM_REMOTE_G2_TIMEOUT makes more sense, probably is a bigger number - if ((t_now - toRptr[i].last_time) > from_remote_g2_timeout) { - printf("Inactivity to local rptr mod index %d, removing stream id %04x\n", i, toRptr[i].streamid); - - // Send end_of_audio to local repeater. - // Let the repeater re-initialize - end_of_audio.counter = G2_COUNTER_OUT++; - if (i == 0) - end_of_audio.vpkt.snd_term_id = 0x03; - else if (i == 1) - end_of_audio.vpkt.snd_term_id = 0x01; - else - end_of_audio.vpkt.snd_term_id = 0x02; - end_of_audio.vpkt.streamid = toRptr[i].streamid; - end_of_audio.vpkt.ctrl = toRptr[i].sequence | 0x40; - - for (int j=0; j<2; j++) - sendto(srv_sock, end_of_audio.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - toRptr[i].last_time = 0; - } - } - - /* any stream coming from local repeater timed out ? */ - if (band_txt[i].last_time != 0) { - time(&t_now); - if ((t_now - band_txt[i].last_time) > from_local_rptr_timeout) { - /* This local stream never went to a remote system, so trace the timeout */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) - printf("Inactivity from local rptr band %d, removing stream id %04x\n", i, band_txt[i].streamID); - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0x0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - - band_txt[i].dest_rptr[0] = '\0'; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - } - } - - /* any stream from local repeater to a remote gateway timed out ? */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr != 0) { - time(&t_now); - if ((t_now - to_remote_g2[i].last_time) > from_local_rptr_timeout) { - printf("Inactivity from local rptr mod %d, removing stream id %04x\n", i, to_remote_g2[i].streamid); - - memset(&(to_remote_g2[i].toDst4),0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - } - } -} - -// new_group is true if we are processing the first voice packet of a 2-voice packet pair. The high order nibble of the first byte of -// this first packet specifed the type of slow data that is being sent. -// the to_print is an integer that counts down how many 2-voice-frame pairs remain to be processed. -// ABC_grp means that we are processing a 20-character message. -// C_seen means that we are processing the last 2-voice-frame packet on a 20 character message. -void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) -{ - /* extract 20-byte RADIO ID */ - if ((data[0] != 0x55) || (data[1] != 0x2d) || (data[2] != 0x16)) { - - // first, unscramble - unsigned char c1 = data[0] ^ 0x70u; - unsigned char c2 = data[1] ^ 0x4fu; - unsigned char c3 = data[2] ^ 0x93u; - - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == sid) { - if (new_group[i]) { - header_type = c1 & 0xf0; - - // header squelch - if ((header_type == 0x50) || (header_type == 0xc0)) { - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - else if (header_type == 0x30) { /* GPS or GPS id or APRS */ - new_group[i] = false; - to_print[i] = c1 & 0x0f; - ABC_grp[i] = false; - if (to_print[i] > 5) - to_print[i] = 5; - else if (to_print[i] < 1) - to_print[i] = 1; - - if ((to_print[i] > 1) && (to_print[i] <= 5)) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* fresh GPS string, re-initialize */ - if ((to_print[i] == 5) && (c2 == '$')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((c2 != '\r') && (c2 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; - band_txt[i].temp_line_cnt++; - } - if ((c3 != '\r') && (c3 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; - band_txt[i].temp_line_cnt++; - } - - if ((c2 == '\r') || (c3 == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((c2 == '\n') || (c3 == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] -= 2; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((c2 != '\r') && (c2 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; - band_txt[i].temp_line_cnt++; - } - - if (c2 == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (c2 == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - to_print[i] --; - } - } - else if (header_type == 0x40) { /* ABC text */ - new_group[i] = false; - to_print[i] = 3; - ABC_grp[i] = true; - C_seen[i] = ((c1 & 0x0f) == 0x03) ? true : false; - - band_txt[i].txt[band_txt[i].txt_cnt] = c2; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = c3; - band_txt[i].txt_cnt++; - - /* We should NOT see any more text, - if we already processed text, - so blank out the codes. */ - if (band_txt[i].sent_key_on_msg) { - data[0] = 0x70; - data[1] = 0x4f; - data[2] = 0x93; - } - - if (band_txt[i].txt_cnt >= 20) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - band_txt[i].txt_cnt = 0; - } - } - else { // header type is not header, squelch, gps or message - new_group[i] = false; - to_print[i] = 0; - ABC_grp[i] = false; - } - } - else { // not a new_group, this is the second of a two-voice-frame pair - if (! band_txt[i].sent_key_on_msg && vPacketCount > 100) { - // 100 voice packets received and still no 20-char message! - /*** if YRCALL is CQCQCQ, set dest_rptr ***/ - band_txt[i].txt[0] = '\0'; - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; - } - // we have the 20-character message, send it to the server... - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); - band_txt[i].sent_key_on_msg = true; - } - if (to_print[i] == 3) { - if (ABC_grp[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = c1; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = c2; - band_txt[i].txt_cnt++; - - band_txt[i].txt[band_txt[i].txt_cnt] = c3; - band_txt[i].txt_cnt++; - - /* We should NOT see any more text, - if we already processed text, - so blank out the codes. */ - if (band_txt[i].sent_key_on_msg) { - data[0] = 0x70; - data[1] = 0x4f; - data[2] = 0x93; - } - - if ((band_txt[i].txt_cnt >= 20) || C_seen[i]) { - band_txt[i].txt[band_txt[i].txt_cnt] = '\0'; - if ( ! band_txt[i].sent_key_on_msg) { - /*** if YRCALL is CQCQCQ, set dest_rptr ***/ - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; - } - // we have the 20-character message, send it to the server... - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); - band_txt[i].sent_key_on_msg = true; - } - band_txt[i].txt_cnt = 0; - } - if (C_seen[i]) - C_seen[i] = false; - } else { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy carrige return or newline */ - if ((c1 != '\r') && (c1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; - band_txt[i].temp_line_cnt++; - } - if ((c2 != '\r') && (c2 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; - band_txt[i].temp_line_cnt++; - } - if ((c3 != '\r') && (c3 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c3; - band_txt[i].temp_line_cnt++; - } - - if ( (c1 == '\r') || (c2 == '\r') || (c3 == '\r') ) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - else if ((c1 == '\n') || (c2 == '\n') ||(c3 == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - } else if (to_print[i] == 2) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((c1 != '\r') && (c1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; - band_txt[i].temp_line_cnt++; - } - if ((c2 != '\r') && (c2 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c2; - band_txt[i].temp_line_cnt++; - } - - if ((c1 == '\r') || (c2 == '\r')) { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if ((c1 == '\n') || (c2 == '\n')) { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } else if (to_print[i] == 1) { - /* something went wrong? all bets are off */ - if (band_txt[i].temp_line_cnt > 200) { - printf("Reached the limit in the OLD gps mode\n"); - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - - /* do not copy CR, NL */ - if ((c1 != '\r') && (c1 != '\n')) { - band_txt[i].temp_line[band_txt[i].temp_line_cnt] = c1; - band_txt[i].temp_line_cnt++; - } - - if (c1 == '\r') { - if (memcmp(band_txt[i].temp_line, "$GPRMC", 6) == 0) { - memcpy(band_txt[i].gprmc, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gprmc[band_txt[i].temp_line_cnt] = '\0'; - } else if (band_txt[i].temp_line[0] != '$') { - memcpy(band_txt[i].gpid, band_txt[i].temp_line, band_txt[i].temp_line_cnt); - band_txt[i].gpid[band_txt[i].temp_line_cnt] = '\0'; - if (bool_send_aprs && !band_txt[i].is_gps_sent) - gps_send(i); - } - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } else if (c1 == '\n') { - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - } - } - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - } - break; - } - } - } -} - -/* run the main loop for QnetGateway */ -void CQnetGateway::Process() -{ - // dtmf stuff - int dtmf_buf_count[3] = {0, 0, 0}; - char dtmf_buf[3][MAX_DTMF_BUF + 1] = { {""}, {""}, {""} }; - int dtmf_last_frame[3] = { 0, 0, 0 }; - unsigned int dtmf_counter[3] = { 0, 0, 0 }; - - dstar_dv_init(); - - int max_nfds = 0; - if (g2_sock > max_nfds) - max_nfds = g2_sock; - if (srv_sock > max_nfds) - max_nfds = srv_sock; - printf("g2=%d, srv=%d, MAX+1=%d\n", g2_sock, srv_sock, max_nfds + 1); - - std::future aprs_future, irc_data_future; - if (bool_send_aprs) { // start the beacon thread - try { - aprs_future = std::async(std::launch::async, &CQnetGateway::APRSBeaconThread, this); - } catch (const std::exception &e) { - printf("Failed to start the APRSBeaconThread. Exception: %s\n", e.what()); - } - if (aprs_future.valid()) - printf("APRS beacon thread started\n"); - } - - try { // start the IRC read thread - irc_data_future = std::async(std::launch::async, &CQnetGateway::GetIRCDataThread, this); - } catch (const std::exception &e) { - printf("Failed to start GetIRCDataThread. Exception: %s\n", e.what()); - keep_running = false; - } - if (keep_running) - printf("get_irc_data thread started\n"); - - ii->kickWatchdog(IRCDDB_VERSION); - - // send INIT to Icom Stack - unsigned char buf[500]; - memset(buf, 0, 10); - memcpy(buf, "INIT", 4); - buf[6] = 0x73U; - // we can use the module a band_addr for INIT - sendto(srv_sock, buf, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - printf("Waiting for ICOM controller...\n"); - - // get the acknowledgement from the ICOM Stack - while (keep_running) { - socklen_t fromlength = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, buf, 500, 0, (struct sockaddr *)&fromRptr, &fromlength); - if (10==recvlen && 0==memcmp(buf, "INIT", 4) && 0x72U==buf[6] && 0x0U==buf[7]) { - OLD_REPLY_SEQ = 256U * buf[4] + buf[5]; - NEW_REPLY_SEQ = OLD_REPLY_SEQ + 1; - G2_COUNTER_OUT = NEW_REPLY_SEQ; - unsigned int ui = G2_COUNTER_OUT; - printf("SYNC: old=%u, new=%u out=%u\n", OLD_REPLY_SEQ, NEW_REPLY_SEQ, ui); - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - printf("Detected ICOM controller!\n"); - - while (keep_running) { - ProcessTimeouts(); - - // wait 20 ms max - fd_set fdset; - FD_ZERO(&fdset); - FD_SET(g2_sock, &fdset); - FD_SET(srv_sock, &fdset); - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 20000; // 20 ms - (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - - // process packets coming from remote G2 - if (FD_ISSET(g2_sock, &fdset)) { - SDSVT g2buf; - socklen_t fromlen = sizeof(struct sockaddr_in); - int g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); - - // save incoming port for mobile systems - if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { - printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } else { - if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { - printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); - } - } - - if ( (g2buflen==56 || g2buflen==27) && 0==memcmp(g2buf.title, "DSVT", 4) && (g2buf.config==0x10 || g2buf.config==0x20) && g2buf.id==0x20) { - if (g2buflen == 56) { - - // Find out the local repeater module IP/port to send the data to - int i = g2buf.hdr.rpt1[7] - 'A'; - - /* valid repeater module? */ - if (i>=0 && i<3) { - // toRptr[i] is active if a remote system is talking to it or - // toRptr[i] is receiving data from a cross-band - if (0==toRptr[i].last_time && 0==band_txt[i].last_time && (Flag_is_ok(g2buf.hdr.flag[0]) || 0x01U==g2buf.hdr.flag[0] || 0x40U==g2buf.hdr.flag[0])) { - if (bool_qso_details) - printf("id=%04x G2 start, ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s IP=%s:%u\n", ntohs(g2buf.streamid), g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx, inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(G2_COUNTER_OUT++); // bump the counter - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x30; - rptrbuf.vpkt.icm_id = 0x20; - //memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 47); - rptrbuf.vpkt.dst_rptr_id = g2buf.flagb[0]; - rptrbuf.vpkt.snd_rptr_id = g2buf.flagb[1]; - rptrbuf.vpkt.snd_term_id = g2buf.flagb[2]; - rptrbuf.vpkt.streamid = g2buf.streamid; - rptrbuf.vpkt.ctrl = g2buf.ctrl; - memcpy(rptrbuf.vpkt.hdr.flag, g2buf.hdr.flag, 3); - memcpy(rptrbuf.vpkt.hdr.r1, g2buf.hdr.rpt2, 8); - memcpy(rptrbuf.vpkt.hdr.r2, g2buf.hdr.rpt1, 8); - memcpy(rptrbuf.vpkt.hdr.ur, g2buf.hdr.urcall, 8); - memcpy(rptrbuf.vpkt.hdr.my, g2buf.hdr.mycall, 8); - memcpy(rptrbuf.vpkt.hdr.nm, g2buf.hdr.sfx, 4); - memcpy(rptrbuf.vpkt.hdr.pfcs, g2buf.hdr.pfcs, 2); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* save the header */ - memcpy(toRptr[i].saved_hdr, rptrbuf.pkt_id, 58); - toRptr[i].saved_adr = fromDst4.sin_addr.s_addr; - - /* This is the active streamid */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } else { // g2buflen == 27 - if (bool_qso_details && g2buf.ctrl & 0x40) - printf("id=%04x END G2\n", ntohs(g2buf.streamid)); - - /* find out which repeater module to send the data to */ - int i; - for (i=0; i<3; i++) { - /* streamid match ? */ - if (toRptr[i].streamid==g2buf.streamid && toRptr[i].adr==fromDst4.sin_addr.s_addr) { - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(G2_COUNTER_OUT++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining= 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - /* End of stream ? */ - if (g2buf.ctrl & 0x40) { - /* clear the saved header */ - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; - - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; - } - } - - /* no match ? */ - if ((i == 3) && bool_regen_header) { - /* check if this a continuation of audio that timed out */ - - if (g2buf.ctrl & 0x40) - ; /* we do not care about end-of-QSO */ - else { - /* for which repeater this stream has timed out ? */ - for (i = 0; i < 3; i++) { - /* match saved stream ? */ - if (0==memcmp(toRptr[i].saved_hdr + 14, &g2buf.streamid, 2) && toRptr[i].saved_adr==fromDst4.sin_addr.s_addr) { - /* repeater module is inactive ? */ - if (toRptr[i].last_time==0 && band_txt[i].last_time==0) { - printf("Re-generating header for streamID=%04x\n", g2buf.streamid); - - toRptr[i].saved_hdr[4] = (unsigned char)((G2_COUNTER_OUT >> 8) & 0xff); - toRptr[i].saved_hdr[5] = (unsigned char)(G2_COUNTER_OUT++ & 0xff); - - /* re-generate/send the header */ - sendto(srv_sock, toRptr[i].saved_hdr, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* send this audio packet to repeater */ - memcpy(rptrbuf.pkt_id, "DSTR", 4); - rptrbuf.counter = htons(G2_COUNTER_OUT++); - rptrbuf.flag[0] = 0x73; - rptrbuf.flag[1] = 0x12; - rptrbuf.flag[2] = 0x00; - rptrbuf.remaining = 0x13; - rptrbuf.vpkt.icm_id = 0x20; - memcpy(&rptrbuf.vpkt.dst_rptr_id, g2buf.flagb, 18); - - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* make sure that any more audio arriving will be accepted */ - toRptr[i].streamid = g2buf.streamid; - toRptr[i].adr = fromDst4.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - } - break; - } - } - } - } - } - } - FD_CLR (g2_sock,&fdset); - } - - // process packets coming from local repeater modules - if (FD_ISSET(srv_sock, &fdset)) { - char temp_radio_user[CALL_SIZE + 1]; - char temp_mod; - - char arearp_cs[CALL_SIZE + 1]; - char zonerp_cs[CALL_SIZE + 1]; - char ip[IP_SIZE + 1]; - - char tempfile[FILENAME_MAX + 1]; - - SDSVT g2buf; - - socklen_t fromlen = sizeof(struct sockaddr_in); - int recvlen = recvfrom(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&fromRptr, &fromlen); - - if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { - ///////////////////////////////////////////////////////////////////// - // some ICOM handshaking... - if (10==recvlen && 0x72==rptrbuf.flag[0]) { // ACK from rptr - NEW_REPLY_SEQ = ntohs(rptrbuf.counter); - if (NEW_REPLY_SEQ == OLD_REPLY_SEQ) { - G2_COUNTER_OUT = NEW_REPLY_SEQ; - OLD_REPLY_SEQ = NEW_REPLY_SEQ - 1; - } else - OLD_REPLY_SEQ = NEW_REPLY_SEQ; - } else if (0x73U==rptrbuf.flag[0] && (0x21U==rptrbuf.flag[1] || 0x11U==rptrbuf.flag[1] || 0x0U==rptrbuf.flag[1])) { - rptrbuf.flag[0] = 0x72U; - memset(rptrbuf.flag+1, 0x0U, 3); - sendto(srv_sock, rptrbuf.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - // end of ICOM handshaking - ///////////////////////////////////////////////////////////////////// - } else if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { - SDSTR reply; - memcpy(reply.pkt_id, "DSTR", 4); - reply.counter = rptrbuf.counter; - reply.flag[0] = 0x72U; - memset(reply.flag+1, 0, 3); - sendto(srv_sock, reply.pkt_id, 10, 0, (struct sockaddr *)&toRptr[0].band_addr, sizeof(struct sockaddr_in)); - if (recvlen == 58) { - vPacketCount = 0U; - if (bool_qso_details) - printf("id=%04x cntr=%04x start RPTR ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s ip=%s\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter), rptrbuf.vpkt.hdr.ur, rptrbuf.vpkt.hdr.r1, rptrbuf.vpkt.hdr.r2, rptrbuf.vpkt.hdr.my, rptrbuf.vpkt.hdr.nm, inet_ntoa(fromRptr.sin_addr)); - - if (0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (got a header)\n", i); - dtmf_last_frame[i] = 0; - dtmf_counter[i] = 0; - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - - /* Initialize the LAST HEARD data for the band */ - - band_txt[i].streamID = rptrbuf.vpkt.streamid; - - memcpy(band_txt[i].flags, rptrbuf.vpkt.hdr.flag, 3); - - memcpy(band_txt[i].lh_mycall, rptrbuf.vpkt.hdr.my, 8); - band_txt[i].lh_mycall[8] = '\0'; - - memcpy(band_txt[i].lh_sfx, rptrbuf.vpkt.hdr.nm, 4); - band_txt[i].lh_sfx[4] = '\0'; - - memcpy(band_txt[i].lh_yrcall, rptrbuf.vpkt.hdr.ur, 8); - band_txt[i].lh_yrcall[8] = '\0'; - - memcpy(band_txt[i].lh_rpt1, rptrbuf.vpkt.hdr.r1, 8); - band_txt[i].lh_rpt1[8] = '\0'; - - memcpy(band_txt[i].lh_rpt2, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].lh_rpt2[8] = '\0'; - - time(&band_txt[i].last_time); - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].sent_key_on_msg = false; - - band_txt[i].dest_rptr[0] = '\0'; - - /* try to process GPS mode: GPRMC and ID */ - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - // band_txt[i].gps_last_time = 0; DO NOT reset it - - new_group[i] = true; - to_print[i] = 0; - ABC_grp[i] = false; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - /* select the band for aprs processing, and lock on the stream ID */ - if (bool_send_aprs) - aprs->SelectBand(i, ntohs(rptrbuf.vpkt.streamid)); - } - } - - /* Is MYCALL valid ? */ - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.my, 8); - temp_radio_user[8] = '\0'; - - int mycall_valid = regexec(&preg, temp_radio_user, 0, NULL, 0); - - if (mycall_valid == REG_NOERROR) - ; // printf("MYCALL [%s] passed IRC expression validation\n", temp_radio_user); - else { - if (mycall_valid == REG_NOMATCH) - printf("MYCALL [%s] failed IRC expression validation\n", temp_radio_user); - else - printf("Failed to validate MYCALL [%s], regexec error=%d\n", temp_radio_user, mycall_valid); - } - - /* send data qnlink */ - if (mycall_valid == REG_NOERROR) - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - if ( mycall_valid==REG_NOERROR && - memcmp(rptrbuf.vpkt.hdr.ur, "XRF", 3) && // not a reflector - memcmp(rptrbuf.vpkt.hdr.ur, "REF", 3) && - memcmp(rptrbuf.vpkt.hdr.ur, "DCS", 3) && - rptrbuf.vpkt.hdr.ur[0]!=' ' && // must have something - memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) ) // urcall is NOT CQCQCQ - { - if ( rptrbuf.vpkt.hdr.ur[0]=='/' && // repeater routing! - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // with a valid module - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - Flag_is_ok(rptrbuf.vpkt.hdr.flag[0]) ) - { - if (memcmp(rptrbuf.vpkt.hdr.ur+1, OWNER.c_str(), 6)) { // the value after the slash is NOT this repeater - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - /* YRCALL=/repeater + mod */ - /* YRCALL=/KJ4NHFB */ - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur+1, 6); - temp_radio_user[6] = ' '; - temp_radio_user[7] = rptrbuf.vpkt.hdr.ur[7]; - if (temp_radio_user[7] == ' ') - temp_radio_user[7] = 'A'; - temp_radio_user[CALL_SIZE] = '\0'; - - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'R'); - if (result) { /* it is a repeater */ - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that saved port instead of the default port - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end()) ? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set yrcall, can NOT let it be slash and repeater + module */ - memcpy(g2buf.hdr.urcall, "CQCQCQ ", 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - - /* set PFCS */ - calcPFCS(g2buf.title, 56); - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - // send to remote gateway - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("id=%04x Routing to IP=%s:%u ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", - ntohs(g2buf.streamid), inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), - g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2, g2buf.hdr.mycall, g2buf.hdr.sfx); - - time(&(to_remote_g2[i].last_time)); - } - } - } - } - } - else if (memcmp(rptrbuf.vpkt.hdr.ur, OWNER.c_str(), 7) && // urcall is not this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt1 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A'&& rptrbuf.vpkt.hdr.r1[7]<='C') && // mod is A,B,C - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt2 is this repeater - rptrbuf.vpkt.hdr.r2[7]=='G' && // local Gateway - Flag_is_ok(rptrbuf.vpkt.hdr.flag[0])) { - - - memset(temp_radio_user, ' ', 8); - memcpy(temp_radio_user, rptrbuf.vpkt.hdr.ur, 8); - temp_radio_user[8] = '\0'; - bool result = get_yrcall_rptr(temp_radio_user, arearp_cs, zonerp_cs, &temp_mod, ip, 'U'); - if (result) { - /* destination is a remote system */ - if (memcmp(zonerp_cs, OWNER.c_str(), 7) != 0) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* one radio user on a repeater module at a time */ - if (to_remote_g2[i].toDst4.sin_addr.s_addr == 0) { - uint32_t address; - /* set the destination */ - to_remote_g2[i].streamid = rptrbuf.vpkt.streamid; - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].toDst4.sin_family = AF_INET; - to_remote_g2[i].toDst4.sin_addr.s_addr = address = inet_addr(ip); - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x10; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0x00; - g2buf.id = rptrbuf.vpkt.icm_id; - g2buf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - g2buf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - g2buf.flagb[2] = rptrbuf.vpkt.snd_term_id; - g2buf.streamid = rptrbuf.vpkt.streamid; - g2buf.ctrl = rptrbuf.vpkt.ctrl; - memcpy(g2buf.hdr.flag, rptrbuf.vpkt.hdr.flag, 3); - /* set rpt1 */ - memset(g2buf.hdr.rpt1, ' ', 8); - memcpy(g2buf.hdr.rpt1, arearp_cs, strlen(arearp_cs)); - g2buf.hdr.rpt1[7] = temp_mod; - /* set rpt2 */ - memset(g2buf.hdr.rpt2, ' ', 8); - memcpy(g2buf.hdr.rpt2, zonerp_cs, strlen(zonerp_cs)); - g2buf.hdr.rpt2[7] = 'G'; - /* set PFCS */ - memcpy(g2buf.hdr.urcall, rptrbuf.vpkt.hdr.ur, 8); - memcpy(g2buf.hdr.mycall, rptrbuf.vpkt.hdr.my, 8); - memcpy(g2buf.hdr.sfx, rptrbuf.vpkt.hdr.nm, 4); - calcPFCS(g2buf.title, 56); - - - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, g2buf.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; - - /* send to remote gateway */ - for (int j=0; j<5; j++) - sendto(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - printf("Routing to IP=%s:%u id=%04x my=%.8s/%.4s ur=%.8s rpt1=%.8s rpt2=%.8s\n", inet_ntoa(to_remote_g2[i].toDst4.sin_addr), ntohs(to_remote_g2[i].toDst4.sin_port), ntohs(g2buf.streamid), g2buf.hdr.mycall, g2buf.hdr.sfx, g2buf.hdr.urcall, g2buf.hdr.rpt1, g2buf.hdr.rpt2); - - time(&(to_remote_g2[i].last_time)); - } - } - } - else - { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* the user we are trying to contact is on our gateway */ - /* make sure they are on a different module */ - if (temp_mod != rptrbuf.vpkt.hdr.r1[7]) { - /* - The remote repeater has been set, lets fill in the dest_rptr - so that later we can send that to the LIVE web site - */ - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[7] = temp_mod; - band_txt[i].dest_rptr[8] = '\0'; - - i = temp_mod - 'A'; - - /* valid destination repeater module? */ - if (i>=0 && i<3) { - /* - toRptr[i] : receiving from a remote system or cross-band - band_txt[i] : local RF is talking. - */ - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("CALLmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], temp_mod); - - rptrbuf.vpkt.hdr.r2[7] = temp_mod; - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - G2_COUNTER_OUT++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } - else - printf("icom rule: no routing from %.8s to %s%c\n", rptrbuf.vpkt.hdr.r1, arearp_cs, temp_mod); - } - } - } - else - { - if ('L' != rptrbuf.vpkt.hdr.ur[7]) // as long as this doesn't look like a linking command - playNotInCache = true; // we need to wait until user's transmission is over - } - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " C0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - unlink(vm[i].file); - printf("removed voicemail file: %s\n", vm[i].file); - vm[i].file[0] = '\0'; - } else - printf("No voicemail to clear or still recording\n"); - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " R0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - /* voicemail file is closed */ - if ((vm[i].fd == -1) && (vm[i].file[0] != '\0')) { - snprintf(vm[i].message, 21, "VOICEMAIL ON MOD %c ", 'A'+i); - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(vm[i])); - } catch (const std::exception &e) { - printf("Failed to start voicemail playback. Exception: %s\n", e.what()); - } - } else - printf("No voicemail to recall or still recording\n"); - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " S0", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (vm[i].fd >= 0) - printf("Already recording for voicemail on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr. r1[7], "voicemail.dat"); - - vm[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (vm[i].fd < 0) - printf("Failed to create file %s for voicemail\n", tempfile); - else { - strcpy(vm[i].file, tempfile); - printf("Recording mod %c for voicemail into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], vm[i].file); - - time(&vm[i].last_time); - vm[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.size()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - memcpy(vm[i].header.title, recbuf.title, 56); - } - } - } - } - else if (0 == memcmp(rptrbuf.vpkt.hdr.ur, " E", 8)) { - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - if (recd[i].fd >= 0) - printf("Already recording for echotest on mod %d\n", i); - else { - memset(tempfile, '\0', sizeof(tempfile)); - snprintf(tempfile, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), rptrbuf.vpkt.hdr.r1[7], "echotest.dat"); - - recd[i].fd = open(tempfile, O_CREAT | O_WRONLY | O_EXCL | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (recd[i].fd < 0) - printf("Failed to create file %s for echotest\n", tempfile); - else { - strcpy(recd[i].file, tempfile); - printf("Recording mod %c for echotest into file:[%s]\n", rptrbuf.vpkt.hdr.r1[7], recd[i].file); - snprintf(recd[i].message, 21, "ECHO ON MODULE %c ", 'A' + i); - time(&recd[i].last_time); - recd[i].streamid = rptrbuf.vpkt.streamid; - - memcpy(recbuf.title, "DSVT", 4); - recbuf.config = 0x10; - recbuf.id = rptrbuf.vpkt.icm_id; - recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 44); - memset(recbuf.hdr.rpt1, ' ', 8); - memcpy(recbuf.hdr.rpt1, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt1[7] = rptrbuf.vpkt.hdr.r1[7]; - memset(recbuf.hdr.rpt2, ' ', 8); - memcpy(recbuf.hdr.rpt2, OWNER.c_str(), OWNER.length()); - recbuf.hdr.rpt2[7] = 'G'; - memcpy(recbuf.hdr.urcall, "CQCQCQ ", 8); - - calcPFCS(recbuf.title, 56); - - memcpy(recd[i].header.title, recbuf.title, 56); - } - } - } - /* check for cross-banding */ - } - else if ( 0==memcmp(rptrbuf.vpkt.hdr.ur, "CQCQCQ", 6) && // yrcall is CQCQCQ - 0==memcmp(rptrbuf.vpkt.hdr.r2, OWNER.c_str(), 7) && // rpt1 is this repeater - 0==memcmp(rptrbuf.vpkt.hdr.r1, OWNER.c_str(), 7) && // rpt2 is this repeater - (rptrbuf.vpkt.hdr.r1[7]>='A' && rptrbuf.vpkt.hdr.r1[7]<='C') && // mod of rpt1 is A,B,C - (rptrbuf.vpkt.hdr.r2[7]>='A' && rptrbuf.vpkt.hdr.r2[7]<='C') && // !!! usually G on rpt2, but we see A,B,C with - rptrbuf.vpkt.hdr.r2[7]!=rptrbuf.vpkt.hdr.r1[7] ) { // cross-banding? make sure NOT the same - int i = rptrbuf.vpkt.hdr.r1[7] - 'A'; - - if (i>=0 && i<3) { - // The remote repeater has been set, lets fill in the dest_rptr - // so that later we can send that to the LIVE web site - memcpy(band_txt[i].dest_rptr, rptrbuf.vpkt.hdr.r2, 8); - band_txt[i].dest_rptr[8] = '\0'; - } - - i = rptrbuf.vpkt.hdr.r2[7] - 'A'; - - // valid destination repeater module? - if (i>=0 && i<3) { - // toRptr[i] : receiving from a remote system or cross-band - // band_txt[i] : local RF is talking. - if ((toRptr[i].last_time == 0) && (band_txt[i].last_time == 0)) { - printf("ZONEmode cross-banding from mod %c to %c\n", rptrbuf.vpkt.hdr.r1[7], rptrbuf.vpkt.hdr.r2[7]); - - rptrbuf.vpkt.hdr.r1[7] = 'G'; - calcPFCS(rptrbuf.pkt_id, 58); - - sendto(srv_sock, rptrbuf.pkt_id, 58, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* This is the active streamid */ - toRptr[i].streamid = rptrbuf.vpkt.streamid; - toRptr[i].adr = fromRptr.sin_addr.s_addr; - - /* time it, in case stream times out */ - time(&toRptr[i].last_time); - - /* bump the G2 counter */ - G2_COUNTER_OUT++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - } - } - } - } - else - { // recvlen is 29 or 32 - for (int i=0; i<3; i++) { - if (band_txt[i].streamID == rptrbuf.vpkt.streamid) { - time(&band_txt[i].last_time); - - if (rptrbuf.vpkt.ctrl & 0x40) { // end of voice data - if (dtmf_buf_count[i] > 0) { - dtmf_file = dtmf_dir; - dtmf_file.push_back('/'); - dtmf_file.push_back('A'+i); - dtmf_file += "_mod_DTMF_NOTIFY"; - if (bool_dtmf_debug) - printf("Saving dtmfs=[%s] into file: [%s]\n", dtmf_buf[i], dtmf_file.c_str()); - FILE *dtmf_fp = fopen(dtmf_file.c_str(), "w"); - if (dtmf_fp) { - fprintf(dtmf_fp, "%s\n%s", dtmf_buf[i], band_txt[i].lh_mycall); - fclose(dtmf_fp); - } else - printf("Failed to create dtmf file %s\n", dtmf_file.c_str()); - - - if (bool_dtmf_debug) - printf("resetting dtmf[%d] (printed dtmf code %s from %s)\n", i, dtmf_buf[i], band_txt[i].lh_mycall); - memset(dtmf_buf[i], 0, sizeof(dtmf_buf[i])); - dtmf_buf_count[i] = 0; - dtmf_counter[i] = 0; - dtmf_last_frame[i] = 0; - } - if (! band_txt[i].sent_key_on_msg) { - band_txt[i].txt[0] = '\0'; - if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) { - set_dest_rptr(i, band_txt[i].dest_rptr); - if (memcmp(band_txt[i].dest_rptr, "REF", 3) == 0) - band_txt[i].dest_rptr[0] = '\0'; - } - // we have the 20-character message, send it to the server... - ii->sendHeardWithTXMsg(band_txt[i].lh_mycall, band_txt[i].lh_sfx, (strstr(band_txt[i].lh_yrcall,"REF") == NULL)?band_txt[i].lh_yrcall:"CQCQCQ ", band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].dest_rptr, band_txt[i].txt); - band_txt[i].sent_key_on_msg = true; - } - // send the "key off" message, this will end up in the openquad.net Last Heard webpage. - ii->sendHeardWithTXStats(band_txt[i].lh_mycall, band_txt[i].lh_sfx, band_txt[i].lh_yrcall, band_txt[i].lh_rpt1, band_txt[i].lh_rpt2, band_txt[i].flags[0], band_txt[i].flags[1], band_txt[i].flags[2], band_txt[i].num_dv_frames, band_txt[i].num_dv_silent_frames, band_txt[i].num_bit_errors); - - if (playNotInCache) { - // Not in cache, please try again! - FILE *fp = fopen(qnvoicefile.c_str(), "w"); - if (fp) { - fprintf(fp, "%c_notincache.dat_NOT_IN_CACHE\n", band_txt[i].lh_rpt1[7]); - fclose(fp); - } - playNotInCache = false; - } - - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - - band_txt[i].dest_rptr[0] = '\0'; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - } - else - { // not the end of the voice stream - int ber_data[3]; - int ber_errs = dstar_dv_decode(rptrbuf.vpkt.vasd.voice, ber_data); - if (ber_data[0] == 0xf85) - band_txt[i].num_dv_silent_frames++; - band_txt[i].num_bit_errors += ber_errs; - band_txt[i].num_dv_frames++; - - if ((ber_data[0] & 0x0ffc) == 0xfc0) { - dtmf_digit = (ber_data[0] & 0x03) | ((ber_data[2] & 0x60) >> 3); - if (dtmf_counter[i] > 0) { - if (dtmf_last_frame[i] != dtmf_digit) - dtmf_counter[i] = 0; - } - dtmf_last_frame[i] = dtmf_digit; - dtmf_counter[i]++; - - if ((dtmf_counter[i] == 5) && (dtmf_digit >= 0) && (dtmf_digit <= 15)) { - if (dtmf_buf_count[i] < MAX_DTMF_BUF) { - const char *dtmf_chars = "147*2580369#ABCD"; - dtmf_buf[i][ dtmf_buf_count[i] ] = dtmf_chars[dtmf_digit]; - dtmf_buf_count[i]++; - } - } - const unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; - if (recvlen == 29) - memcpy(rptrbuf.vpkt.vasd.voice, silence, 9); - else - memcpy(rptrbuf.vpkt.vasd1.voice, silence, 9); - } else - dtmf_counter[i] = 0; - } - break; - } - } - vPacketCount++; - if (recvlen == 29) // process the slow data from every voice packet - ProcessSlowData(rptrbuf.vpkt.vasd.text, rptrbuf.vpkt.streamid); - else - ProcessSlowData(rptrbuf.vpkt.vasd1.text, rptrbuf.vpkt.streamid); - - /* send data to qnlink */ - sendto(srv_sock, rptrbuf.pkt_id, recvlen, 0, (struct sockaddr *)&plug, sizeof(struct sockaddr_in)); - - /* aprs processing */ - if (bool_send_aprs) - // streamID seq audio+text - aprs->ProcessText(ntohs(rptrbuf.vpkt.streamid), rptrbuf.vpkt.ctrl, rptrbuf.vpkt.vasd.voice); - - for (int i=0; i<3; i++) { - /* find out if data must go to the remote G2 */ - if (to_remote_g2[i].streamid == rptrbuf.vpkt.streamid) { - memcpy(g2buf.title, "DSVT", 4); - g2buf.config = 0x20; - g2buf.flaga[0] = g2buf.flaga[1] = g2buf.flaga[2] = 0; - memcpy(&g2buf.id, &rptrbuf.vpkt.icm_id, 7); - if (recvlen == 29) - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - else - memcpy(g2buf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - - uint32_t address = to_remote_g2[i].toDst4.sin_addr.s_addr; - // if the address is in the portmap, we'll use that port instead of the default - auto theAddress = portmap.find(address); - to_remote_g2[i].toDst4.sin_port = htons((theAddress==portmap.end())? g2_external.port : theAddress->second); - sendto(g2_sock, g2buf.title, 27, 0, (struct sockaddr *)&(to_remote_g2[i].toDst4), sizeof(struct sockaddr_in)); - - time(&(to_remote_g2[i].last_time)); - - /* Is this the end-of-stream */ - if (rptrbuf.vpkt.ctrl & 0x40) { - memset(&to_remote_g2[i].toDst4,0,sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - break; - } - else if (recd[i].fd>=0 && recd[i].streamid==rptrbuf.vpkt.streamid) { // Is the data to be recorded for echotest - time(&recd[i].last_time); - - //memcpy(recbuf.title, "DSVT", 4); - //recbuf.config = 0x20; - //recbuf.id = rptrbuf.vpkt.icm_id; - //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[20] = 0; - //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - (void)write(recd[i].fd, rptrbuf.vpkt.vasd.voice, 9); - else - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - (void)write(recd[i].fd, rptrbuf.vpkt.vasd1.voice, 9); - - //rec_len = 27; - //(void)write(recd[i].fd, &rec_len, 2); - //(void)write(recd[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - recd[i].streamid = 0; - recd[i].last_time = 0; - close(recd[i].fd); - recd[i].fd = -1; - // printf("Closed echotest audio file:[%s]\n", recd[i].file); - - /* we are in echotest mode, so play it back */ - try { - std::async(std::launch::async, &CQnetGateway::PlayFileThread, this, std::ref(recd[i])); - } catch (const std::exception &e) { - printf("failed to start PlayFileThread. Exception: %s\n", e.what()); - // When the echotest thread runs, it deletes the file, - // Because the echotest thread did NOT start, we delete the file here - unlink(recd[i].file); - } - } - break; - } - else if ((vm[i].fd >= 0) && (vm[i].streamid==rptrbuf.vpkt.streamid)) { // Is the data to be recorded for voicemail - time(&vm[i].last_time); - - //memcpy(recbuf.title, "DSVT", 4); - //recbuf.config = 0x20; - //recbuf.flaga[0] = recbuf.flaga[1] = recbuf.flaga[2] = 0; - //recbuf.id = rptrbuf.vpkt.icm_id; - //recbuf.flagb[0] = rptrbuf.vpkt.dst_rptr_id; - //recbuf.flagb[1] = rptrbuf.vpkt.snd_rptr_id; - //recbuf.flagb[2] = rptrbuf.vpkt.snd_term_id; - //memcpy(&recbuf.streamid, &rptrbuf.vpkt.streamid, 3); - if (recvlen == 29) - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd.voice, 12); - (void)write(vm[i].fd, rptrbuf.vpkt.vasd.voice, 9); - else - //memcpy(recbuf.vasd.voice, rptrbuf.vpkt.vasd1.voice, 12); - (void)write(vm[i].fd, rptrbuf.vpkt.vasd1.voice, 9); - - //rec_len = 27; - //(void)write(vm[i].fd, &rec_len, 2); - //(void)write(vm[i].fd, &recbuf, rec_len); - - if ((rptrbuf.vpkt.ctrl & 0x40) != 0) { - vm[i].streamid = 0; - vm[i].last_time = 0; - close(vm[i].fd); - vm[i].fd = -1; - // printf("Closed voicemail audio file:[%s]\n", vm[i].file); - } - break; - } - else if ((toRptr[i].streamid==rptrbuf.vpkt.streamid) && (toRptr[i].adr == fromRptr.sin_addr.s_addr)) { // or maybe this is cross-banding data - sendto(srv_sock, rptrbuf.pkt_id, 29, 0, (struct sockaddr *)&toRptr[i].band_addr, sizeof(struct sockaddr_in)); - - /* timeit */ - time(&toRptr[i].last_time); - - /* bump G2 counter */ - G2_COUNTER_OUT++; - - toRptr[i].sequence = rptrbuf.vpkt.ctrl; - - /* End of stream ? */ - if (rptrbuf.vpkt.ctrl & 0x40) { - toRptr[i].last_time = 0; - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - } - break; - } - } - - if (bool_qso_details && rptrbuf.vpkt.ctrl&0x40) - printf("id=%04x cntr=%04x END RPTR\n", ntohs(rptrbuf.vpkt.streamid), ntohs(rptrbuf.counter)); - } - } - } - FD_CLR (srv_sock,&fdset); - } - } - - // thread clean-up - if (bool_send_aprs) { - if (aprs_future.valid()) - aprs_future.get(); - } - irc_data_future.get(); - return; -} - -void CQnetGateway::compute_aprs_hash() -{ - short hash = 0x73e2; - char rptr_sign[CALL_SIZE + 1]; - - strcpy(rptr_sign, OWNER.c_str()); - char *p = strchr(rptr_sign, ' '); - if (!p) { - printf("Failed to build repeater callsign for aprs hash\n"); - return; - } - *p = '\0'; - p = rptr_sign; - short int len = strlen(rptr_sign); - - for (short int i=0; i < len; i+=2) { - hash ^= (*p++) << 8; - hash ^= (*p++); - } - printf("aprs hash code=[%d] for %s\n", hash, OWNER.c_str()); - rptr.aprs_hash = hash; - - return; -} - -void CQnetGateway::APRSBeaconThread() -{ - char snd_buf[512]; - char rcv_buf[512]; - time_t tnow = 0; - - struct sigaction act; - - /* - Every 20 seconds, the remote APRS host sends a KEEPALIVE packet-comment - on the TCP/APRS port. - If we have not received any KEEPALIVE packet-comment after 5 minutes - we must assume that the remote APRS host is down or disappeared - or has dropped the connection. In these cases, we must re-connect. - There are 3 keepalive packets in one minute, or every 20 seconds. - In 5 minutes, we should have received a total of 15 keepalive packets. - */ - short THRESHOLD_COUNTDOWN = 15; - - act.sa_handler = sigCatch; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART; - if (sigaction(SIGTERM, &act, 0) != 0) { - printf("APRSBeaconThread: sigaction-TERM failed, error=%d\n", errno); - return; - } - if (sigaction(SIGINT, &act, 0) != 0) { - printf("APRSBeaconThread: sigaction-INT failed, error=%d\n", errno); - return; - } - if (sigaction(SIGPIPE, &act, 0) != 0) { - printf("APRSBeaconThread: sigaction-PIPE failed, error=%d\n", errno); - return; - } - - time_t last_keepalive_time; - time(&last_keepalive_time); - - time_t last_beacon_time = 0; - /* This thread is also saying to the APRS_HOST that we are ALIVE */ - while (keep_running) { - if (aprs->GetSock() == -1) { - aprs->Open(OWNER); - if (aprs->GetSock() == -1) - sleep(1); - else - THRESHOLD_COUNTDOWN = 15; - } - - time(&tnow); - if ((tnow - last_beacon_time) > (rptr.aprs_interval * 60)) { - for (short int i=0; i<3; i++) { - if (rptr.mod[i].desc[0] != '\0') { - float tmp_lat = fabs(rptr.mod[i].latitude); - float tmp_lon = fabs(rptr.mod[i].longitude); - float lat = floor(tmp_lat); - float lon = floor(tmp_lon); - lat = (tmp_lat - lat) * 60.0F + lat * 100.0F; - lon = (tmp_lon - lon) * 60.0F + lon * 100.0F; - - char lat_s[15], lon_s[15]; - if (lat >= 1000.0F) - sprintf(lat_s, "%.2f", lat); - else if (lat >= 100.0F) - sprintf(lat_s, "0%.2f", lat); - else if (lat >= 10.0F) - sprintf(lat_s, "00%.2f", lat); - else - sprintf(lat_s, "000%.2f", lat); - - if (lon >= 10000.0F) - sprintf(lon_s, "%.2f", lon); - else if (lon >= 1000.0F) - sprintf(lon_s, "0%.2f", lon); - else if (lon >= 100.0F) - sprintf(lon_s, "00%.2f", lon); - else if (lon >= 10.0F) - sprintf(lon_s, "000%.2f", lon); - else - sprintf(lon_s, "0000%.2f", lon); - - /* send to aprs */ - sprintf(snd_buf, "%s>APJI23,TCPIP*,qAC,%sS:!%s%cD%s%c&RNG%04u %s %s", - rptr.mod[i].call.c_str(), rptr.mod[i].call.c_str(), - lat_s, (rptr.mod[i].latitude < 0.0) ? 'S' : 'N', - lon_s, (rptr.mod[i].longitude < 0.0) ? 'W' : 'E', - (unsigned int)rptr.mod[i].range, rptr.mod[i].band.c_str(), rptr.mod[i].desc.c_str()); - - // printf("APRS Beacon =[%s]\n", snd_buf); - strcat(snd_buf, "\r\n"); - - while (keep_running) { - if (aprs->GetSock() == -1) { - aprs->Open(OWNER); - if (aprs->GetSock() == -1) - sleep(1); - else - THRESHOLD_COUNTDOWN = 15; - } else { - int rc = aprs->WriteSock(snd_buf, strlen(snd_buf)); - if (rc < 0) { - if ((errno == EPIPE) || - (errno == ECONNRESET) || - (errno == ETIMEDOUT) || - (errno == ECONNABORTED) || - (errno == ESHUTDOWN) || - (errno == EHOSTUNREACH) || - (errno == ENETRESET) || - (errno == ENETDOWN) || - (errno == ENETUNREACH) || - (errno == EHOSTDOWN) || - (errno == ENOTCONN)) { - printf("send_aprs_beacon: APRS_HOST closed connection,error=%d\n",errno); - close(aprs->GetSock()); - aprs->SetSock( -1 ); - } else if (errno == EWOULDBLOCK) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } else { - /* Cant do nothing about it */ - printf("send_aprs_beacon failed, error=%d\n", errno); - break; - } - } else { - // printf("APRS beacon sent\n"); - break; - } - } - int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); - if (rc > 0) - THRESHOLD_COUNTDOWN = 15; - } - } - int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); - if (rc > 0) - THRESHOLD_COUNTDOWN = 15; - } - time(&last_beacon_time); - } - /* - Are we still receiving from APRS host ? - */ - int rc = recv(aprs->GetSock(), rcv_buf, sizeof(rcv_buf), 0); - if (rc < 0) { - if ((errno == EPIPE) || - (errno == ECONNRESET) || - (errno == ETIMEDOUT) || - (errno == ECONNABORTED) || - (errno == ESHUTDOWN) || - (errno == EHOSTUNREACH) || - (errno == ENETRESET) || - (errno == ENETDOWN) || - (errno == ENETUNREACH) || - (errno == EHOSTDOWN) || - (errno == ENOTCONN)) { - printf("send_aprs_beacon: recv error: APRS_HOST closed connection,error=%d\n",errno); - close(aprs->GetSock()); - aprs->SetSock( -1 ); - } - } else if (rc == 0) { - printf("send_aprs_beacon: recv: APRS shutdown\n"); - close(aprs->GetSock()); - aprs->SetSock( -1 ); - } else - THRESHOLD_COUNTDOWN = 15; - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - /* 20 seconds passed already ? */ - time(&tnow); - if ((tnow - last_keepalive_time) > 20) { - /* we should be receving keepalive packets ONLY if the connection is alive */ - if (aprs->GetSock() >= 0) { - if (THRESHOLD_COUNTDOWN > 0) - THRESHOLD_COUNTDOWN--; - - if (THRESHOLD_COUNTDOWN == 0) { - printf("APRS host keepalive timeout\n"); - close(aprs->GetSock()); - aprs->SetSock( -1 ); - } - } - /* reset timer */ - time(&last_keepalive_time); - } - } - printf("APRS beacon thread exiting...\n"); - return; -} - -void CQnetGateway::PlayFileThread(SECHO &edata) -{ - SDSTR dstr; - const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; - const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; - - struct sigaction act; - act.sa_handler = sigCatch; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART; - if (sigaction(SIGTERM, &act, 0) != 0) { - printf("sigaction-TERM failed, error=%d\n", errno); - return; - } - if (sigaction(SIGINT, &act, 0) != 0) { - printf("sigaction-INT failed, error=%d\n", errno); - return; - } - if (sigaction(SIGPIPE, &act, 0) != 0) { - printf("sigaction-PIPE failed, error=%d\n", errno); - return; - } - - printf("File to playback:[%s]\n", edata.file); - - struct stat sbuf; - if (stat(edata.file, &sbuf)) { - fprintf(stderr, "Can't stat %s\n", edata.file); - return; - } - - if (sbuf.st_size % 9) - printf("Warning %s file size is %ld (not a multiple of 9)!\n", edata.file, sbuf.st_size); - int ambeblocks = (int)sbuf.st_size / 9; - - FILE *fp = fopen(edata.file, "rb"); - if (!fp) { - fprintf(stderr, "Failed to open file %s\n", edata.file); - return; - } - - int mod = edata.header.hdr.rpt1[7] - 'A'; - if (mod<0 || mod>2) { - fprintf(stderr, "unknown module suffix '%s'\n", edata.header.hdr.rpt1); - return; - } - - sleep(play_wait); - - // reformat the header and send it - memcpy(dstr.pkt_id, "DSTR", 4); - dstr.counter = htons(G2_COUNTER_OUT++); - dstr.flag[0] = 0x73; - dstr.flag[1] = 0x12; - dstr.flag[2] = 0x00; - dstr.remaining = 0x30; - dstr.vpkt.icm_id = 0x20; - dstr.vpkt.dst_rptr_id = edata.header.flagb[0]; - dstr.vpkt.snd_rptr_id = edata.header.flagb[1]; - dstr.vpkt.snd_term_id = edata.header.flagb[2]; - dstr.vpkt.streamid = edata.header.streamid; - dstr.vpkt.ctrl = 0x80u; - memcpy(dstr.vpkt.hdr.flag, edata.header.hdr.flag, 3); - memcpy(dstr.vpkt.hdr.r1, edata.header.hdr.rpt1, 8); - memcpy(dstr.vpkt.hdr.r2, edata.header.hdr.rpt2, 8); - memcpy(dstr.vpkt.hdr.ur, "CQCQCQ ", 8); - memcpy(dstr.vpkt.hdr.my, edata.header.hdr.mycall, 8); - memcpy(dstr.vpkt.hdr.nm, edata.header.hdr.sfx, 4); - calcPFCS(dstr.pkt_id, 58); - - sendto(srv_sock, dstr.pkt_id, 58, 0, (struct sockaddr *)&toRptr[mod].band_addr, sizeof(struct sockaddr_in)); - - dstr.remaining = 0x13U; - - for (int i=0; irptrQTH(rptrcall, rptr.mod[i].latitude, rptr.mod[i].longitude, rptr.mod[i].desc1, rptr.mod[i].desc2, rptr.mod[i].url, rptr.mod[i].package_version); - if (rptr.mod[i].frequency) - ii->rptrQRG(rptrcall, rptr.mod[i].frequency, rptr.mod[i].offset, rptr.mod[i].range, rptr.mod[i].agl); - } - - return; -} - -int CQnetGateway::Init(char *cfgfile) -{ - short int i; - struct sigaction act; - - setvbuf(stdout, (char *)NULL, _IOLBF, 0); - - - /* Used to validate MYCALL input */ - int rc = regcomp(&preg, "^(([1-9][A-Z])|([A-Z][0-9])|([A-Z][A-Z][0-9]))[0-9A-Z]*[A-Z][ ]*[ A-RT-Z]$", REG_EXTENDED | REG_NOSUB); - if (rc != REG_NOERROR) { - printf("The IRC regular expression is NOT valid\n"); - return 1; - } - - act.sa_handler = sigCatch; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART; - if (sigaction(SIGTERM, &act, 0) != 0) { - printf("sigaction-TERM failed, error=%d\n", errno); - return 1; - } - if (sigaction(SIGINT, &act, 0) != 0) { - printf("sigaction-INT failed, error=%d\n", errno); - return 1; - } - if (sigaction(SIGPIPE, &act, 0) != 0) { - printf("sigaction-PIPE failed, error=%d\n", errno); - return 1; - } - - for (i = 0; i < 3; i++) - memset(&band_txt[0], 0, sizeof(SBANDTXT)); - - /* process configuration file */ - if ( read_config(cfgfile) ) { - printf("Failed to process config file %s\n", cfgfile); - return 1; - } - - playNotInCache = false; - - /* build the repeater callsigns for aprs */ - rptr.mod[0].call = OWNER; - for (i=OWNER.length(); i; i--) - if (! isspace(OWNER[i-1])) - break; - rptr.mod[0].call.resize(i); - - rptr.mod[1].call = rptr.mod[0].call; - rptr.mod[2].call = rptr.mod[0].call; - rptr.mod[0].call += "-A"; - rptr.mod[1].call += "-B"; - rptr.mod[2].call += "-C"; - rptr.mod[0].band = "23cm"; - rptr.mod[1].band = "70cm"; - rptr.mod[2].band = "2m"; - printf("Repeater callsigns: [%s] [%s] [%s]\n", rptr.mod[0].call.c_str(), rptr.mod[1].call.c_str(), rptr.mod[2].call.c_str()); - - for (i = 0; i < 3; i++) { - //rptr.mod[i].frequency = rptr.mod[i].offset = rptr.mod[i].latitude = rptr.mod[i].longitude = rptr.mod[i].agl = rptr.mod[i].range = 0.0; - band_txt[i].streamID = 0; - band_txt[i].flags[0] = band_txt[i].flags[1] = band_txt[i].flags[2] = 0; - band_txt[i].lh_mycall[0] = '\0'; - band_txt[i].lh_sfx[0] = '\0'; - band_txt[i].lh_yrcall[0] = '\0'; - band_txt[i].lh_rpt1[0] = '\0'; - band_txt[i].lh_rpt2[0] = '\0'; - - band_txt[i].last_time = 0; - - band_txt[i].txt[0] = '\0'; - band_txt[i].txt_cnt = 0; - band_txt[i].sent_key_on_msg = false; - - band_txt[i].dest_rptr[0] = '\0'; - - band_txt[i].temp_line[0] = '\0'; - band_txt[i].temp_line_cnt = 0; - band_txt[i].gprmc[0] = '\0'; - band_txt[i].gpid[0] = '\0'; - band_txt[i].is_gps_sent = false; - band_txt[i].gps_last_time = 0; - - band_txt[i].num_dv_frames = 0; - band_txt[i].num_dv_silent_frames = 0; - band_txt[i].num_bit_errors = 0; - - } - - if (bool_send_aprs) { - aprs = new CAPRS(&rptr); - if (aprs) - aprs->Init(); - else { - printf("aprs class init failed!\nAPRS will be turned off"); - bool_send_aprs = false; - } - } - compute_aprs_hash(); - - ii = new CIRCDDB(ircddb.ip, ircddb.port, owner, irc_pass, IRCDDB_VERSION, local_irc_ip); - bool ok = ii->open(); - if (!ok) { - printf("irc open failed\n"); - return 1; - } - - rc = ii->getConnectionState(); - printf("Waiting for irc connection status of 2\n"); - i = 0; - while (rc < 2) { - printf("irc status=%d\n", rc); - if (rc < 2) { - i++; - sleep(5); - } else - break; - - if (!keep_running) - break; - - if (i > 5) { - printf("We can not wait any longer...\n"); - break; - } - rc = ii->getConnectionState(); - } - - /* udp port 40000 must open first */ - g2_sock = open_port(g2_external); - if (0 > g2_sock) { - printf("Can't open %s:%d\n", g2_external.ip.c_str(), g2_external.port); - return 1; - } - - // Open G2 INTERNAL: - // default non-icom 127.0.0.1:19000 - // default icom 172.16.0.20:20000 - srv_sock = open_port(g2_internal); - if (0 > srv_sock) { - printf("Can't open %s:%d\n", g2_internal.ip.c_str(), g2_internal.port); - return 1; - } - - for (i = 0; i < 3; i++) { - // recording for echotest on local repeater modules - recd[i].last_time = 0; - recd[i].streamid = 0; - recd[i].fd = -1; - memset(recd[i].file, 0, sizeof(recd[i].file)); - - // recording for voicemail on local repeater modules - vm[i].last_time = 0; - vm[i].streamid = 0; - vm[i].fd = -1; - memset(vm[i].file, 0, sizeof(vm[i].file)); - - snprintf(vm[i].file, FILENAME_MAX, "%s/%c_%s", echotest_dir.c_str(), 'A'+i, "voicemail.dat"); - - if (access(vm[i].file, F_OK) != 0) - memset(vm[i].file, 0, sizeof(vm[i].file)); - else - printf("Loaded voicemail file: %s for mod %d\n", vm[i].file, i); - - // the repeater modules run on these ports - memset(&toRptr[i],0,sizeof(toRptr[i])); - - memset(toRptr[i].saved_hdr, 0, sizeof(toRptr[i].saved_hdr)); - toRptr[i].saved_adr = 0; - - toRptr[i].streamid = 0; - toRptr[i].adr = 0; - - toRptr[i].band_addr.sin_family = AF_INET; - toRptr[i].band_addr.sin_addr.s_addr = inet_addr(rptr.mod[i].portip.ip.c_str()); - toRptr[i].band_addr.sin_port = htons(rptr.mod[i].portip.port); - - toRptr[i].last_time = 0; - toRptr[i].G2_COUNTER = 0; - - toRptr[i].sequence = 0x0; - } - - /* - Initialize the end_of_audio that will be sent to the local repeater - when audio from remote G2 has timed out - */ - memcpy(end_of_audio.pkt_id, "DSTR", 4); - end_of_audio.flag[0] = 0x73; - end_of_audio.flag[1] = 0x12; - end_of_audio.flag[2] = 0x00; - end_of_audio.remaining = 0x13; - end_of_audio.vpkt.icm_id = 0x20; - end_of_audio.vpkt.dst_rptr_id = 0x00; - end_of_audio.vpkt.snd_rptr_id = 0x01; - memset(end_of_audio.vpkt.vasd.voice, '\0', 9); - end_of_audio.vpkt.vasd.text[0] = 0x70; - end_of_audio.vpkt.vasd.text[1] = 0x4f; - end_of_audio.vpkt.vasd.text[2] = 0x93; - - /* to remote systems */ - for (i = 0; i < 3; i++) { - memset(&to_remote_g2[i].toDst4, 0, sizeof(struct sockaddr_in)); - to_remote_g2[i].streamid = 0; - to_remote_g2[i].last_time = 0; - } - - /* where to send packets to qnlink */ - memset(&plug, 0, sizeof(struct sockaddr_in)); - plug.sin_family = AF_INET; - plug.sin_port = htons(g2_link.port); - plug.sin_addr.s_addr = inet_addr(g2_link.ip.c_str()); - - printf("QnetGateway...entering processing loop\n"); - - if (bool_send_qrgs) - qrgs_and_maps(); - return 0; -} - -CQnetGateway::CQnetGateway() -{ -} - -CQnetGateway::~CQnetGateway() -{ - if (srv_sock != -1) { - close(srv_sock); - printf("Closed G2_INTERNAL_PORT\n"); - } - - if (g2_sock != -1) { - close(g2_sock); - printf("Closed G2_EXTERNAL_PORT\n"); - } - - if (bool_send_aprs) { - if (aprs->GetSock() != -1) { - close(aprs->GetSock()); - printf("Closed APRS\n"); - } - delete aprs; - } - - for (int i=0; i<3; i++) { - recd[i].last_time = 0; - recd[i].streamid = 0; - if (recd[i].fd >= 0) { - close(recd[i].fd); - unlink(recd[i].file); - } - } - - ii->close(); - delete ii; - - printf("QnetGateway exiting\n"); -} - -bool CQnetGateway::validate_csum(SBANDTXT &bt, bool is_gps) -{ - const char *name = is_gps ? "GPS" : "GPRMC"; - char *s = is_gps ? bt.gpid : bt.gprmc; - char *p = strrchr(s, '*'); - if (!p) { - // BAD news, something went wrong - printf("Missing asterisk before checksum in %s\n", name); - bt.gprmc[0] = bt.gpid[0] = '\0'; - return true; - } else { - *p = '\0'; - // verify csum in GPRMC - bool ok = verify_gps_csum(s + 1, p + 1); - if (!ok) { - printf("csum in %s not good\n", name); - bt.gprmc[0] = bt.gpid[0] = '\0'; - return true; - } - } - return false; -} - -void CQnetGateway::gps_send(short int rptr_idx) -{ - time_t tnow = 0; - static char old_mycall[CALL_SIZE + 1] = { " " }; - - if ((rptr_idx < 0) || (rptr_idx > 2)) { - printf("ERROR in gps_send: rptr_idx %d is invalid\n", rptr_idx); - return; - } - - if (band_txt[rptr_idx].gprmc[0] == '\0') { - band_txt[rptr_idx].gpid[0] = '\0'; - printf("missing GPS ID\n"); - return; - } - if (band_txt[rptr_idx].gpid[0] == '\0') { - band_txt[rptr_idx].gprmc[0] = '\0'; - printf("Missing GPSRMC\n"); - return; - } - if (memcmp(band_txt[rptr_idx].gpid, band_txt[rptr_idx].lh_mycall, CALL_SIZE) != 0) { - printf("MYCALL [%s] does not match first 8 characters of GPS ID [%.8s]\n", band_txt[rptr_idx].lh_mycall, band_txt[rptr_idx].gpid); - band_txt[rptr_idx].gprmc[0] = '\0'; - band_txt[rptr_idx].gpid[0] = '\0'; - return; - } - - /* if new station, reset last time */ - if (strcmp(old_mycall, band_txt[rptr_idx].lh_mycall) != 0) { - strcpy(old_mycall, band_txt[rptr_idx].lh_mycall); - band_txt[rptr_idx].gps_last_time = 0; - } - - /* do NOT process often */ - time(&tnow); - if ((tnow - band_txt[rptr_idx].gps_last_time) < 31) - return; - - printf("GPRMC=[%s]\n", band_txt[rptr_idx].gprmc); - printf("GPS id=[%s]\n",band_txt[rptr_idx].gpid); - - if (validate_csum(band_txt[rptr_idx], false)) // || validate_csum(band_txt[rptr_idx], true)) - return; - - /* now convert GPS into APRS and send it */ - build_aprs_from_gps_and_send(rptr_idx); - - band_txt[rptr_idx].is_gps_sent = true; - time(&(band_txt[rptr_idx].gps_last_time)); - return; -} - -void CQnetGateway::build_aprs_from_gps_and_send(short int rptr_idx) -{ - char buf[512]; - const char *delim = ","; - - char *saveptr = NULL; - - /*** dont care about the rest */ - - strcpy(buf, band_txt[rptr_idx].lh_mycall); - char *p = strchr(buf, ' '); - if (p) { - if (band_txt[rptr_idx].lh_mycall[7] != ' ') { - *p = '-'; - *(p + 1) = band_txt[rptr_idx].lh_mycall[7]; - *(p + 2) = '>'; - *(p + 3) = '\0'; - } else { - *p = '>'; - *(p + 1) = '\0'; - } - } else - strcat(buf, ">"); - - strcat(buf, "APDPRS,DSTAR*,qAR,"); - strcat(buf, rptr.mod[rptr_idx].call.c_str()); - strcat(buf, ":!"); - - //GPRMC = - strtok_r(band_txt[rptr_idx].gprmc, delim, &saveptr); - //time_utc = - strtok_r(NULL, delim, &saveptr); - //nav = - strtok_r(NULL, delim, &saveptr); - char *lat_str = strtok_r(NULL, delim, &saveptr); - char *lat_NS = strtok_r(NULL, delim, &saveptr); - char *lon_str = strtok_r(NULL, delim, &saveptr); - char *lon_EW = strtok_r(NULL, delim, &saveptr); - - if (lat_str && lat_NS) { - if ((*lat_NS != 'N') && (*lat_NS != 'S')) { - printf("Invalid North or South indicator in latitude\n"); - return; - } - if (strlen(lat_str) > 9) { - printf("Invalid latitude\n"); - return; - } - if (lat_str[4] != '.') { - printf("Invalid latitude\n"); - return; - } - lat_str[7] = '\0'; - strcat(buf, lat_str); - strcat(buf, lat_NS); - } else { - printf("Invalid latitude\n"); - return; - } - /* secondary table */ - strcat(buf, "/"); - - if (lon_str && lon_EW) { - if ((*lon_EW != 'E') && (*lon_EW != 'W')) { - printf("Invalid East or West indicator in longitude\n"); - return; - } - if (strlen(lon_str) > 10) { - printf("Invalid longitude\n"); - return; - } - if (lon_str[5] != '.') { - printf("Invalid longitude\n"); - return; - } - lon_str[8] = '\0'; - strcat(buf, lon_str); - strcat(buf, lon_EW); - } else { - printf("Invalid longitude\n"); - return; - } - - /* Just this symbolcode only */ - strcat(buf, "/"); - strncat(buf, band_txt[rptr_idx].gpid + 13, 32); - - // printf("Built APRS from old GPS mode=[%s]\n", buf); - strcat(buf, "\r\n"); - - if (-1 == aprs->WriteSock(buf, strlen(buf))) { - if ((errno == EPIPE) || (errno == ECONNRESET) || (errno == ETIMEDOUT) || (errno == ECONNABORTED) || - (errno == ESHUTDOWN) || (errno == EHOSTUNREACH) || (errno == ENETRESET) || (errno == ENETDOWN) || - (errno == ENETUNREACH) || (errno == EHOSTDOWN) || (errno == ENOTCONN)) { - printf("build_aprs_from_gps_and_send: APRS_HOST closed connection, error=%d\n", errno); - close(aprs->GetSock()); - aprs->SetSock( -1 ); - } else - printf("build_aprs_from_gps_and_send: send error=%d\n", errno); - } - return; -} - -bool CQnetGateway::verify_gps_csum(char *gps_text, char *csum_text) -{ - short computed_csum = 0; - char computed_csum_text[16]; - - short int len = strlen(gps_text); - for (short int i=0; i -#include "QnetTypeDefs.h" -#include "SEcho.h" - -#include "aprs.h" - -using namespace libconfig; - -#define IP_SIZE 15 -#define MAXHOSTNAMELEN 64 -#define CALL_SIZE 8 -#define MAX_DTMF_BUF 32 - -typedef struct to_remote_g2_tag { - unsigned short streamid; - struct sockaddr_in toDst4; - time_t last_time; -} STOREMOTEG2; - -typedef struct torepeater_tag { - // help with header re-generation - unsigned char saved_hdr[58]; // repeater format - uint32_t saved_adr; - - unsigned short streamid; - uint32_t adr; - struct sockaddr_in band_addr; - time_t last_time; - std::atomic G2_COUNTER; - unsigned char sequence; -} STOREPEATER; - -typedef struct band_txt_tag { - unsigned short streamID; - unsigned char flags[3]; - char lh_mycall[CALL_SIZE + 1]; - char lh_sfx[5]; - char lh_yrcall[CALL_SIZE + 1]; - char lh_rpt1[CALL_SIZE + 1]; - char lh_rpt2[CALL_SIZE + 1]; - time_t last_time; - char txt[64]; // Only 20 are used - unsigned short txt_cnt; - bool sent_key_on_msg; - - char dest_rptr[CALL_SIZE + 1]; - - // try to process GPS mode: GPRMC and ID - char temp_line[256]; - unsigned short temp_line_cnt; - char gprmc[256]; - char gpid[256]; - bool is_gps_sent; - time_t gps_last_time; - - int num_dv_frames; - int num_dv_silent_frames; - int num_bit_errors; -} SBANDTXT; - -class CQnetGateway { -public: - CQnetGateway(); - ~CQnetGateway(); - void Process(); - int Init(char *cfgfile); - -private: - // text stuff - bool new_group[3] = { true, true, true }; - unsigned char header_type = 0; - short to_print[3] = { 0, 0, 0 }; - bool ABC_grp[3] = { false, false, false }; - bool C_seen[3] = { false, false, false }; - - SPORTIP g2_internal, g2_external, g2_link, ircddb; - - std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; - - bool bool_send_qrgs, bool_irc_debug, bool_dtmf_debug, bool_regen_header, bool_qso_details, bool_send_aprs, playNotInCache; - - int play_wait, play_delay, echotest_rec_timeout, voicemail_rec_timeout, from_remote_g2_timeout, from_local_rptr_timeout, dtmf_digit; - - unsigned int vPacketCount; - - std::map portmap; - - // data needed for aprs login and aprs beacon - // RPTR defined in aprs.h - SRPTR rptr; - - // local repeater modules being recorded - // This is for echotest and voicemail - SECHO recd[3], vm[3]; - SDSVT recbuf; // 56 or 27, max is 56 - - // the streamids going to remote Gateways from each local module - STOREMOTEG2 to_remote_g2[3]; // 0=A, 1=B, 2=C - - // input from remote G2 gateway - int g2_sock = -1; - struct sockaddr_in fromDst4; - - // Incoming data from remote systems - // must be fed into our local repeater modules. - STOREPEATER toRptr[3]; // 0=A, 1=B, 2=C - - // input from our own local repeater modules - int srv_sock = -1; - SDSTR rptrbuf; // 58 or 29 or 32, max is 58 - struct sockaddr_in fromRptr; - - SDSTR end_of_audio; - - // send packets to g2_link - struct sockaddr_in plug; - - // for talking with the irc server - CIRCDDB *ii; - // for handling APRS stuff - CAPRS *aprs; - - // text coming from local repeater bands - SBANDTXT band_txt[3]; // 0=A, 1=B, 2=C - - /* Used to validate MYCALL input */ - regex_t preg; - - // CACHE used to cache users, repeaters, - // gateways, IP numbers coming from the irc server - - std::map user2rptr_map, rptr2gwy_map, gwy2ip_map; - - pthread_mutex_t irc_data_mutex = PTHREAD_MUTEX_INITIALIZER; - - int open_port(const SPORTIP &pip); - void calcPFCS(unsigned char *packet, int len); - void GetIRCDataThread(); - int get_yrcall_rptr_from_cache(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); - bool get_yrcall_rptr(char *call, char *arearp_cs, char *zonerp_cs, char *mod, char *ip, char RoU); - void PlayFileThread(SECHO &edata); - void compute_aprs_hash(); - void APRSBeaconThread(); - void ProcessTimeouts(); - void ProcessSlowData(unsigned char *data, unsigned short sid); - bool Flag_is_ok(unsigned char flag); - - // read configuration file - bool read_config(char *); - bool get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value); - bool get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value); - bool get_value(const Config &cfg, const std::string path, bool &value, bool default_value); - bool get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value); - -/* aprs functions, borrowed from my retired IRLP node 4201 */ - void gps_send(short int rptr_idx); - bool verify_gps_csum(char *gps_text, char *csum_text); - void build_aprs_from_gps_and_send(short int rptr_idx); - - void qrgs_and_maps(); - - void set_dest_rptr(int mod_ndx, char *dest_rptr); - bool validate_csum(SBANDTXT &bt, bool is_gps); -}; diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 6eb6436..8f94cac 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2018 by Thomas A. Early N7TAE + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,25 +32,20 @@ #include #include #include -#include #include #include "QnetTypeDefs.h" #include "Random.h" +#include "ConfigureBase.h" +#include "UnixDgramSocket.h" -using namespace libconfig; - -#define VERSION "v1.0" - -int sockDst = -1; -struct sockaddr_in toDst; +#define VERSION "v2.0" +char module; time_t tNow = 0; short streamid_raw = 0; -bool isdefined[3] = { false, false, false }; -std::string REPEATER, IP_ADDRESS; -int PORT, PLAY_WAIT, PLAY_DELAY; -bool is_icom = false; +std::string REPEATER; +int PLAY_WAIT, PLAY_DELAY; unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; @@ -74,9 +69,37 @@ unsigned short crc_tabccitt[256] = { 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 }; -void calcPFCS(unsigned char rawbytes[58]) +class CConfigure : public CConfigureBase { +public: + bool ReadCfgFile() + { + const std::string estr; + std::string type; + std::string path = "module_"; + path.append(1, module); + if (GetValue(path, estr, type, 1, 16)) { + fprintf(stderr, "%s not found!\n", path.c_str()); + return true; + } + if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("itap")) { + fprintf(stderr, "module type '%s is invalid!\n", type.c_str()); + return true; + } + if (GetValue(path+"_callsign", type, REPEATER, 3, 6)) { + if (GetValue("ircddb.login", estr, REPEATER, 3, 6)) { + fprintf(stderr, "no Callsign for the repeater was found!\n"); + return true; + } + } + GetValue("timing_play_wait", estr, PLAY_WAIT, 1,10); + GetValue("timing_play_delay", estr, PLAY_DELAY, 15, 25); + return false; + } +}; +void calcPFCS(unsigned char rawbytes[58]) +{ unsigned short crc_dstar_ffff = 0xffff; unsigned short tmp, short_c; short int i; @@ -94,135 +117,6 @@ void calcPFCS(unsigned char rawbytes[58]) return; } -bool dst_open(const char *ip, const int port) -{ - int reuse = 1; - - sockDst = socket(PF_INET,SOCK_DGRAM,0); - if (sockDst == -1) { - printf("Failed to create DSTAR socket\n"); - return true; - } - if (setsockopt(sockDst,SOL_SOCKET,SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { - close(sockDst); - sockDst = -1; - printf("setsockopt DSTAR REUSE failed\n"); - return true; - } - memset(&toDst,0,sizeof(struct sockaddr_in)); - toDst.sin_family = AF_INET; - toDst.sin_port = htons(port); - toDst.sin_addr.s_addr = inet_addr(ip); - - fcntl(sockDst,F_SETFL,O_NONBLOCK); - return false; -} - -void dst_close() -{ - if (sockDst != -1) { - close(sockDst); - sockDst = -1; - } - return; -} - -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - -/* process configuration file */ -bool read_config(const char *cfgFile) -{ - Config cfg; - - printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - - if (! get_value(cfg, "ircddb.login", REPEATER, 3, 6, "UNDEFINED")) - return true; - REPEATER.resize(6, ' '); - printf("REPEATER=[%s]\n", REPEATER.c_str()); - - for (short int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; - std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("icom") && type.compare("itap")) { - printf("module type '%s' is invalid\n", type.c_str()); - return true; - } - is_icom = type.compare("icom") ? false : true; - isdefined[m] = true; - } - } - if (false==isdefined[0] && false==isdefined[1] && false==isdefined[2]) { - printf("No repeaters defined!\n"); - return true; - } - - if (! get_value(cfg, "gateway.internal.ip", IP_ADDRESS, 7, 15, is_icom ? "172.16.0.20" : "127.0.0.1")) - return true; - - get_value(cfg, "gateway.internal.port", PORT, 16000, 65535, is_icom ? 20000 : 19000); - - get_value(cfg, "timing.play.wait", PLAY_WAIT, 1, 10, 1); - - get_value(cfg, "timing.play.delay", PLAY_DELAY, 9, 25, 19); - - return false; -} - void ToUpper(std::string &str) { for (unsigned int i=0; i \n", argv[0]); - printf("Example: %s c n7tae xrf757al\n", argv[0]); - printf("Where...\n"); - printf(" c is the local repeater module\n"); - printf(" n7tae is the value of mycall\n"); - printf(" xrf757al is the value of yourcall, in this case this is a Link command\n\n"); + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Example: %s c n7tae xrf757al\n", argv[0]); + fprintf(stderr, "Where...\n"); + fprintf(stderr, " c is the local repeater module\n"); + fprintf(stderr, " n7tae is the value of mycall\n"); + fprintf(stderr, " xrf757al is the value of yourcall, in this case this is a Link command\n\n"); return 0; } + switch (argv[1][0]) { + case '0': + case 'a': + case 'A': + module = 'A'; + break; + case '1': + case 'b': + case 'B': + module = 'B'; + break; + case '2': + case 'c': + case 'C': + module = 'C'; + break; + default: + fprintf(stderr, "module must be 0, a, A, 1, b, B, 2, c or C, not %s\n", argv[1]); + return 1; + } + std::string cfgfile(CFG_DIR); cfgfile += "/qn.cfg"; - if (read_config(cfgfile.c_str())) + CConfigure config; + if (config.Initialize(cfgfile.c_str())) + return 1; + + if (config.ReadCfgFile()) return 1; if (REPEATER.size() > 6) { @@ -255,14 +174,6 @@ int main(int argc, char *argv[]) } ToUpper(REPEATER); - char module = argv[1][0]; - if (islower(module)) - module = toupper(module); - if ((module != 'A') && (module != 'B') && (module != 'C')) { - printf("module must be one of A B C\n"); - return 1; - } - if (strlen(argv[2]) > 8) { printf("MYCALL can not be more than 8 characters, %s is invalid\n", argv[2]); return 1; @@ -287,8 +198,10 @@ int main(int argc, char *argv[]) time(&tNow); CRandom Random; - - if (dst_open(IP_ADDRESS.c_str(), PORT)) + CUnixDgramWriter ToGateway; + std::string togateway("modem2gate"); + togateway.append(1, module-'A'+'0'); + if (ToGateway.Open(togateway.c_str())) return 1; SDSTR pkt; @@ -328,10 +241,10 @@ int main(int argc, char *argv[]) calcPFCS(pkt.pkt_id); // send the header - int sent = sendto(sockDst, pkt.pkt_id, 58, 0, (struct sockaddr *)&toDst, sizeof(toDst)); + int sent = ToGateway.Write(pkt.pkt_id, 58); if (sent != 58) { printf("%s: ERROR: Couldn't send header!\n", argv[0]); - dst_close(); + ToGateway.Close(); return 1; } @@ -400,13 +313,13 @@ int main(int argc, char *argv[]) break; } - sent = sendto(sockDst,pkt.pkt_id, 29, 0, (struct sockaddr *)&toDst, sizeof(toDst)); + sent = ToGateway.Write(pkt.pkt_id, 29); if (sent != 29) { printf("%s: ERROR: could not send voice packet %d\n", argv[0], i); - dst_close(); + ToGateway.Close(); return 1; } } - dst_close(); + ToGateway.Close(); return 0; } diff --git a/UnixDgramSocket.cpp b/UnixDgramSocket.cpp index 799a29b..ca22754 100644 --- a/UnixDgramSocket.cpp +++ b/UnixDgramSocket.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 by Thomas Early N7TAE + * Copyright (C) 2019 by Thomas Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/UnixDgramSocket.h b/UnixDgramSocket.h index 6a79a6f..7242955 100644 --- a/UnixDgramSocket.h +++ b/UnixDgramSocket.h @@ -1,6 +1,6 @@ #pragma once /* - * Copyright (C) 2018 by Thomas Early N7TAE + * Copyright (C) 2019 by Thomas Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/defaults b/defaults new file mode 100644 index 0000000..6847e00 --- /dev/null +++ b/defaults @@ -0,0 +1,166 @@ +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +######################################################################################################################### +# # +# QnetGateway Default Parameter Values # +# # +######################################################################################################################### +# What follows need to also be valid bash shell variable definitions, therefore: +# No white space on either side of the equal sign (=) +# String values should be quoted if they contain any special chars, including white space +# If a string value is a simple word, it doesn't need to be quoted +# Use the single quote (') for quoting strings, not the double quote(") +# Comments can come after a key=value definition, introduced by a pound-sign (#) + +########################################################################################################################## +# +# IRCDDB - You MUST use a legal callsign for logging into any IRC network +# +ircddb_login_d='' # login callsign for the ircDDB network +ircddb_host_d='rr.openquad.net' # other irc networks include group1-irc.ircddb.net +ircddb_port_d=9007 # not a good idea to change! +ircddb_password_d='1111111111111' # not needed for rr.openquad.net + +########################################################################################################################## +# +# GATEWAY +# +gateway_regen_header_d=true # regenerate headers from incoming data +gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server +gateway_external_ip_d='0.0.0.0' # this means accept a connection from any source +gateway_external_port_d=40000 # don't change + +########################################################################################################################## +# +# APRS - for tracking users and also this repeater. +# +aprs_enable_d=true # send info to APRS +aprs_host_d='rotate.aprs.net' # the APRS network server +aprs_port_d=14580 # and port +aprs_interval_d=40 # keep-alive in minutes +aprs_filter_d='' # advanced feature + +########################################################################################################################## +# +# LINK - controls the behaviour of QnetLink (qnlink) +# +link_admin_d='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt +link_link_unlink_d='' # if defined, comma-separated list of users that can link and unlink a repeater +#link_no_link_unlink_d='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist + # if the blacklist is defined (even if it's empty), the link_unlink will not be read +link_incoming_ip_d='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection. +link_ref_port_d=20001 # port for REF linking, don't change +link_xrf_port_d=30001 # port for XRF linking, don't change +link_dcs_port_d=30051 # port for DCS linking, don't change +link_announce_d=true # do link, unlink, etc. announcements +link_acknowledge_d=true # send text acknowledgment on key-up +link_max_dongles_d=5 # maximum number of linked hot-spots + +########################################################################################################################## +# +# GENERIC MODULE - These will be defined for any and all defined modules +# +module_x_link_at_start='' # For example, set to 'REF001CL' to link module to 1-charlie when the module starts. +module_x_inactivity=0 # if no activity for this many minutes unlink reflector. Zero means no timer. +module_x_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +module_x_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +module_x_acknowledge=false # Do you want an ACK back? +module_x_ack_delay=250 # millisecond delay before acknowledgement +module_x_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +module_x_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +module_x_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +module_x_agl=0 # the height above ground level for this repeater's antenna +module_x_latitude=0 # you can leave this unspecified for a mobile rig +module_x_longitude=0 # like the latitude +module_x_desc1='' # maximum of 20 characters, most special symbols are not allowed +module_x_desc2='' # just like desc1 +module_x_url='github.com/n7tae/g2_ircddb' # 80 characters max + +########################################################################################################################## +# +# DVAP - Special parameters when: module.x='dvap' +# +dvap_power=10 # TX power level: -12 to 10, 10 is maximum power +dvap_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best +dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case + +########################################################################################################################## +# +# MMDVM - Special parameters when: module_x='mmdvm' +# +mmdvm_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program +mmdvm_gateway_port=20010 # which port will QnetRelay be sending on +mmdvm_local_port=20011 # which port will MMDVMHost be sending on + +########################################################################################################################## +# +# DVRPTR - Special parameters when: module_x='dvrptr' +# +# if you don't know what your DVRPTR serial number is, look in the log file after running qndvrptr +dvrptr_serial_number='00.00.00.00' # the DVRPTR serial number +dvrptr_rf_on='RFON' # put this in YRCALL to disable the channel +dvrptr_rf_off='RFOFF' # put this in YRCALL to enable the channel +dvrptr_rf_rx_level=80 # see the DVRPTR V1 manual +dvrptr_duplex=false # set to true if the module is duplex +dvrptr_tx_delay=250 # milliseconds to allow for switching from rx to tx +dvrptr_rqst_count=10 # number of 2-sec intervals before the an unresponsive system is killed +dvrptr_inverse_rx=true # if you're not hearing anything, try false +dvrptr_inverse_tx=true # if you're not being heard, try false + +########################################################################################################################## +# +# LOGGING - Control extra logging - useful for debugging +# +log_qso_d=false # QSO info goes into the log +log_irc_d=false # IRC debug info +log_dtmf_d=false # DTMF debug info + +########################################################################################################################## +# +# DPLUS - Control of dplus (trust system) linking to repeaters and REF reflectors +# +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. +dplus_ref_login_d='' # for logging into REF reflectors, if undefined or empty, ircddb_login will be used +dplus_authorize_d=false # set to true if you want to use the closed-source DPlus reflectors and/or repeaters +dplus_use_reflectors_d=true # set to false if you are not going to link to DPlus reflectors +dplus_use_repeaters_d=true # set to false if you are not going to link to DPlus repeaters +# any gateways in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. + +########################################################################################################################## +# +# FILE - where important QnetGateway files and directories are found. +# +file_status_d='/usr/local/etc/rptr_status' # where repeater status info is passed between services +file_dtmf_d='/tmp' # where DTMF is decoded +file_echotest_d='/tmp' # echo dat files will end up here +file_qnvoicefile_d='/tmp/qnvoice.txt' # where qnvoice will create the play command +file_gwys_d='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is. +file_announce_dir_d='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements + +########################################################################################################################## +# +# TIMINGS - for controlling how to deal with timing issues +# +# most users will not have to override any of these default values +timing_timeout_echo_d=1 # seconds before we assume echo has timed out +timing_timeout_voicemail_d=1 # seconds before we assume voicemail has timed out +timing_timeout_remote_g2_d=2 # after this many seconds with no packets, we assume the tx is closed +timing_timeout_local_rptr_d=1 # local repeater timeout, in seconds +timing_play_wait_d=1 # seconds before echo or voicemail playback occurs, between 1 and 10 +timing_play_delay_d=19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1 or 2 ms diff --git a/qn.everything.cfg b/qn.everything.cfg index 40d75cf..df3306f 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -10,19 +10,12 @@ ircddb = { gateway = { # regen_header = true # regenerate headers from incoming data # send_qrgs_maps = true # send frequecy, offset, cooridinates and url to irc-server -# local_irc_ip = "0.0.0.0" # 0.0.0.0 means accept any incoming connections # aprs_send = true # send info to aprs -# ip = "127.0.0.1" # where the gateway is running external = { # ip = "0.0.0.0" # port = 40000 } - - internal = { -# ip = "0.0.0.0" -# port = 19000 - } } module = { @@ -34,8 +27,6 @@ module = { # 70 cm modules will use "b" # 2 M module will use "c" type = "mmdvm" -# ip = "127.0.0.1" -# port = 19998 # default for mod a, you usually don't need to specify this # frequency = 0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage # offset = 0 # range = 0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles @@ -116,9 +107,6 @@ module = { # If you don't know what it is, run the program and look in the log file! # serial_number = "00.00.00.00" -# internal_ip = "0.0.0.0" # the dvrptr address, usually leave this alone -# port = 19999 # module defaults: A=20000, B=19999, C=19998 - # Some settings for your DVRPTR modem (see DVRPTR V1 manual for more info). # rf_rx_level = 80 @@ -227,15 +215,6 @@ module = { # the url of your repeater, 80 chars max # url = "github.com/n7tae/QnetGateway" - - # where other g2 programs find this repeater software -# ip = "127.0.0.1" # where is the device running? must be a "dotted number" - - # the internal ip of this program, "0.0.0.0" is usually best -# internal_ip = "0.0.0.0" - - # port number default: A:19998 B:19999 C:20000 -# port = 20000 # default for mod C } } @@ -243,6 +222,7 @@ module = { mmdvm = { # these need to be the same as they are in your MMDVM.ini file (in the [D-Star Network] section # If you change them there, then change them here! +# internal_ip = "0.0.0.0" # gateway_port = 20010 # local_port = 20011 } @@ -271,8 +251,6 @@ link = { # no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist # if the blacklist is defined (even if it's empty), the link_unlink will not be read # incoming_ip = "0.0.0.0" # incoming ip address, "0.0.0.0" means accepts all connections. -# ip = "127.0.0.1" # where g2_link is running -# port = 18997 # port for communications to g2_link # ref_port = 20001 # port for REF linking, don't change # xrf_port = 30001 # port for XRF linking, don't change # dcs_port = 30051 # port for DCS linking, don't change From beb8851578bf0c2c7cf4614e7106d04ab8a30518 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 22 Dec 2018 13:58:52 -0700 Subject: [PATCH 157/553] simplified cfg class --- Makefile | 28 +++++++------- ConfigureBase.cpp => QnetConfigure.cpp | 29 +++++++------- ConfigureBase.h => QnetConfigure.h | 18 ++++----- QnetRemote.cpp | 53 ++++++++++++-------------- defaults | 8 ++++ 5 files changed, 70 insertions(+), 66 deletions(-) rename ConfigureBase.cpp => QnetConfigure.cpp (89%) rename ConfigureBase.h => QnetConfigure.h (94%) diff --git a/Makefile b/Makefile index 76c8dc4..8fabab5 100644 --- a/Makefile +++ b/Makefile @@ -49,26 +49,26 @@ dvap : $(DVP_PROGRAMS) dvrptr : $(DVR_PROGRAMS) itap : $(TAP_PROGRAMS) -qngateway : $(IRCOBJS) QnetGateway.o aprs.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o UnixDgramSocket.o $(IRCOBJS) $(LDFLAGS) -pthread +qngateway : $(IRCOBJS) QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o $(IRCOBJS) $(LDFLAGS) -pthread -qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o $(LDFLAGS) -pthread +qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) -pthread -qnrelay : QnetRelay.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qnrelay QnetRelay.o UnixDgramSocket.o $(LDFLAGS) +qnrelay : QnetRelay.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qnrelay QnetRelay.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) -qnitap : QnetITAP.o Random.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o UnixDgramSocket.o $(LDFLAGS) +qnitap : QnetITAP.o Random.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) -qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o - g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o $(DSTROBJS) $(LDFLAGS) -pthread +qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) $(LDFLAGS) -pthread -qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o - g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o UnixDgramSocket.o $(DSTROBJS) $(LDFLAGS) +qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) $(LDFLAGS) -qnremote : QnetRemote.o Random.o UnixDgramSocket.o ConfigureBase.o - g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o UnixDgramSocket.o ConfigureBase.o $(LDFLAGS) +qnremote : QnetRemote.o Random.o UnixDgramSocket.o QnetConfigure.o + g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) qnvoice : QnetVoice.o Random.o g++ $(CPPFLAGS) -o qnvoice QnetVoice.o Random.o $(LDFLAGS) diff --git a/ConfigureBase.cpp b/QnetConfigure.cpp similarity index 89% rename from ConfigureBase.cpp rename to QnetConfigure.cpp index 7583bb2..3d4f813 100644 --- a/ConfigureBase.cpp +++ b/QnetConfigure.cpp @@ -18,18 +18,19 @@ #include #include -#include "ConfigureBase.h" +#include "QnetConfigure.h" -CConfigureBase::CConfigureBase() +CQnetConfigure::CQnetConfigure() { } -CConfigureBase::~CConfigureBase() +CQnetConfigure::~CQnetConfigure() { defaults.empty(); + cfg.empty(); } -char *CConfigureBase::Trim(char *s) +char *CQnetConfigure::Trim(char *s) { size_t len = strlen(s); while (len && isspace(s[len-1])) @@ -39,7 +40,7 @@ char *CConfigureBase::Trim(char *s) return s; } -bool CConfigureBase::ReadConfigFile(const char *configfile, std::map &amap) +bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map &amap) { FILE *fp = fopen(configfile, "r"); if (fp) { @@ -64,7 +65,7 @@ bool CConfigureBase::ReadConfigFile(const char *configfile, std::map #include -class CConfigureBase { +class CQnetConfigure { public: - CConfigureBase(); - virtual ~CConfigureBase(); - virtual bool ReadCfgFile() = 0; + CQnetConfigure(); + virtual ~CQnetConfigure(); + //virtual bool ReadCfgFile() = 0; bool Initialize(const char *configfile); - -protected: - std::map cfg; - - char *Trim(char *s); - bool ReadConfigFile(const char *file, std::map &amap); bool GetValue(const std::string &path, const std::string &mod, bool &value); bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max); bool GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max); bool GetValue(const std::string &path, const std::string &mod, std::string &value, const int min, const int max); + private: std::map defaults; + std::map cfg; + char *Trim(char *s); + bool ReadConfigFile(const char *file, std::map &amap); bool GetDefaultBool (const std::string &key, const std::string &mod, bool &dval); bool GetDefaultDouble(const std::string &key, const std::string &mod, double &dval); bool GetDefaultInt (const std::string &key, const std::string &mod, int &dval); diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 8f94cac..7d72e14 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -36,7 +36,7 @@ #include "QnetTypeDefs.h" #include "Random.h" -#include "ConfigureBase.h" +#include "QnetConfigure.h" #include "UnixDgramSocket.h" #define VERSION "v2.0" @@ -69,34 +69,32 @@ unsigned short crc_tabccitt[256] = { 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 }; -class CConfigure : public CConfigureBase +CQnetConfigure cfg; + +bool ReadCfgFile() { -public: - bool ReadCfgFile() - { - const std::string estr; - std::string type; - std::string path = "module_"; - path.append(1, module); - if (GetValue(path, estr, type, 1, 16)) { - fprintf(stderr, "%s not found!\n", path.c_str()); - return true; - } - if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("itap")) { - fprintf(stderr, "module type '%s is invalid!\n", type.c_str()); + const std::string estr; + std::string type; + std::string path = "module_"; + path.append(1, module); + if (cfg.GetValue(path, estr, type, 1, 16)) { + fprintf(stderr, "%s not found!\n", path.c_str()); + return true; + } + if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("itap")) { + fprintf(stderr, "module type '%s is invalid!\n", type.c_str()); + return true; + } + if (cfg.GetValue(path+"_callsign", type, REPEATER, 3, 6)) { + if (cfg.GetValue("ircddb.login", estr, REPEATER, 3, 6)) { + fprintf(stderr, "no Callsign for the repeater was found!\n"); return true; } - if (GetValue(path+"_callsign", type, REPEATER, 3, 6)) { - if (GetValue("ircddb.login", estr, REPEATER, 3, 6)) { - fprintf(stderr, "no Callsign for the repeater was found!\n"); - return true; - } - } - GetValue("timing_play_wait", estr, PLAY_WAIT, 1,10); - GetValue("timing_play_delay", estr, PLAY_DELAY, 15, 25); - return false; } -}; + cfg.GetValue("timing_play_wait", estr, PLAY_WAIT, 1,10); + cfg.GetValue("timing_play_delay", estr, PLAY_DELAY, 15, 25); + return false; +} void calcPFCS(unsigned char rawbytes[58]) { @@ -161,11 +159,10 @@ int main(int argc, char *argv[]) std::string cfgfile(CFG_DIR); cfgfile += "/qn.cfg"; - CConfigure config; - if (config.Initialize(cfgfile.c_str())) + if (cfg.Initialize(cfgfile.c_str())) return 1; - if (config.ReadCfgFile()) + if (ReadCfgFile()) return 1; if (REPEATER.size() > 6) { diff --git a/defaults b/defaults index 6847e00..0a50f56 100644 --- a/defaults +++ b/defaults @@ -43,6 +43,8 @@ gateway_regen_header_d=true # regenerate headers from incoming data gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server gateway_external_ip_d='0.0.0.0' # this means accept a connection from any source gateway_external_port_d=40000 # don't change +gateway_tolink_d='gate2link' # Unix sockets between qngateway and QnetLink +gateway_fromlink_d='link2gate' # all Unix sockets are on the file system, but hidden from view ########################################################################################################################## # @@ -88,6 +90,12 @@ module_x_latitude=0 # you can leave this unspecified for a mobile rig module_x_longitude=0 # like the latitude module_x_desc1='' # maximum of 20 characters, most special symbols are not allowed module_x_desc2='' # just like desc1 +module_x_gate2modem0='gate2modem0' # Unix Sockets between a modem and the gateway +module_x_gate2modem1='gate2modem1' # 0 is for A, 1 is for B and 2 is for C +module_x_gate2modem2='gate2modem2' +module_x_modem2gate0='modem2gate0' +module_x_modem2gate1='modem2gate1' +module_x_modem2gate2='modem2gate2' module_x_url='github.com/n7tae/g2_ircddb' # 80 characters max ########################################################################################################################## From b4dc0cfb2cb50484f0a517acfc899329a76e327a Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 22 Dec 2018 15:52:03 -0700 Subject: [PATCH 158/553] qngateway uses CQnetConfigure --- Makefile | 2 +- QnetGateway.cpp | 222 ++++++++++++++---------------------------------- QnetGateway.h | 7 -- defaults | 3 +- 4 files changed, 67 insertions(+), 167 deletions(-) diff --git a/Makefile b/Makefile index 8fabab5..dba5182 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ IRC=ircddb # or, you can choose this for a much smaller executable without debugging help CPPFLAGS=-W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\" -LDFLAGS=-L/usr/lib -lconfig++ -lrt +LDFLAGS=-L/usr/lib -lrt DSTROBJS = $(IRC)/dstar_dv.o $(IRC)/golay23.o IRCOBJS = $(IRC)/IRCDDB.o $(IRC)/IRCClient.o $(IRC)/IRCReceiver.o $(IRC)/IRCMessageQueue.o $(IRC)/IRCProtocol.o $(IRC)/IRCMessage.o $(IRC)/IRCDDBApp.o $(IRC)/IRCutils.o $(DSTROBJS) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 21f3239..0acce7f 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -56,9 +56,9 @@ #include "IRCDDB.h" #include "IRCutils.h" #include "versions.h" +#include "QnetConfigure.h" #include "QnetGateway.h" - extern void dstar_dv_init(); extern int dstar_dv_decode(const unsigned char *d, int data[3]); @@ -169,141 +169,74 @@ void CQnetGateway::calcPFCS(unsigned char *packet, int len) return; } -bool CQnetGateway::get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path.c_str(), value); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path.c_str(), value); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path.c_str(), value ? "true" : "false"); - return true; -} - -bool CQnetGateway::get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path.c_str()); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path.c_str(), value.c_str()); - return true; -} - /* process configuration file */ bool CQnetGateway::read_config(char *cfgFile) { - Config cfg; - - printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); - return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + const std::string estr; // an empty string + CQnetConfigure cfg; + if (cfg.Initialize(cfgFile)) return true; - } + // ircddb - std::string path("ircddb."); - if (! get_value(cfg, path+"login", owner, 3, CALL_SIZE-2, "UNDEFINED")) - return true; - if (0 == owner.compare("UNDEFINED")) { - fprintf(stderr, "You must specify your lisensed callsign in ircddb.login\n"); + std::string path("ircddb_"); + if (cfg.GetValue(path+"login", estr, owner, 3, CALL_SIZE-2)) return true; - } OWNER = owner; ToLower(owner); ToUpper(OWNER); - printf("OWNER=[%s]\n", OWNER.c_str()); + printf("OWNER='%s'\n", OWNER.c_str()); OWNER.resize(CALL_SIZE, ' '); - if (! get_value(cfg, path+"host", ircddb.ip, 3, MAXHOSTNAMELEN, "rr.openquad.net")) + if (cfg.GetValue(path+"host", estr, ircddb.ip, 3, MAXHOSTNAMELEN)) return true; - get_value(cfg, path+"port", ircddb.port, 1000, 65535, 9007); + if (cfg.GetValue(path+"port", estr, ircddb.port, 1000, 65535)) + return true; - if(! get_value(cfg, path+"password", irc_pass, 0, 512, "1111111111111111")) + if (cfg.GetValue(path+"password", estr, irc_pass, 0, 512)) return true; // module for (int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; - path += '.'; + path.assign("module_"); + path.append(1, 'a' + m); std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - printf("%s = [%s]\n", std::string(path+"type").c_str(), type.c_str()); + if (cfg.GetValue(path, estr, type, 1, 8)) { + rptr.mod[m].defined = false; + } else { + printf("Found Module: %s = '%s'\n", path.c_str(), type.c_str()); if (0 == type.compare("dvap")) { rptr.mod[m].package_version = DVAP_VERSION; - rptr.mod[m].defined = true; } else if (0 == type.compare("dvrptr")) { rptr.mod[m].package_version = DVRPTR_VERSION; - rptr.mod[m].defined = true; } else if (0 == type.compare("mmdvm")) { rptr.mod[m].package_version = MMDVM_VERSION; - rptr.mod[m].defined = true; } else if (0 == type.compare("itap")) { rptr.mod[m].package_version = ITAP_VERSION; - rptr.mod[m].defined = true; } else { printf("module type '%s' is invalid\n", type.c_str()); return true; } + rptr.mod[m].defined = true; + + path.append(1, '_'); + cfg.GetValue(path+"togateway", type, modem2gate[m], 1, FILENAME_MAX); + cfg.GetValue(path+"fromgateway", type, gate2modem[m], 1, FILENAME_MAX); + cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0e12); + cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); + cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); + cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); + cfg.GetValue(path+"latitude", type, rptr.mod[m].latitude, -90.0, 90.0); + cfg.GetValue(path+"longitude", type, rptr.mod[m].longitude, -180.0, 180.0); + cfg.GetValue(path+"desc1", type, rptr.mod[m].desc1, 0, 20); + cfg.GetValue(path+"desc2", type, rptr.mod[m].desc2, 0, 20); + cfg.GetValue(path+"url", type, rptr.mod[m].url, 0, 80); - char unixsockname[16]; - snprintf(unixsockname, 16, "modem2gate%d", m); - get_value(cfg, path+"togateway", modem2gate[m], 1, FILENAME_MAX, unixsockname); - snprintf(unixsockname, 16, "gate2modem%d", m); - get_value(cfg, path+"fromgateway", gate2modem[m], 1, FILENAME_MAX, unixsockname); - get_value(cfg, std::string(path+"frequency").c_str(), rptr.mod[m].frequency, 0.0, 1.0e12, 0.0); - get_value(cfg, std::string(path+"offset").c_str(), rptr.mod[m].offset, -1.0e12, 1.0e12, 0.0); - get_value(cfg, std::string(path+"range").c_str(), rptr.mod[m].range, 0.0, 1609344.0, 0.0); - get_value(cfg, std::string(path+"agl").c_str(), rptr.mod[m].agl, 0.0, 1000.0, 0.0); - get_value(cfg, std::string(path+"latitude").c_str(), rptr.mod[m].latitude, -90.0, 90.0, 0.0); - get_value(cfg, std::string(path+"longitude").c_str(), rptr.mod[m].longitude, -180.0, 180.0, 0.0); - if (! cfg.lookupValue(path+"desc1", rptr.mod[m].desc1)) - rptr.mod[m].desc1 = ""; - if (! cfg.lookupValue(path+"desc2", rptr.mod[m].desc2)) - rptr.mod[m].desc2 = ""; - if (! get_value(cfg, std::string(path+"url").c_str(), rptr.mod[m].url, 0, 80, "github.com/n7tae/QnetGateway")) - return true; - // truncate strings - if (rptr.mod[m].desc1.length() > 20) - rptr.mod[m].desc1.resize(20); - if (rptr.mod[m].desc2.length() > 20) - rptr.mod[m].desc2.resize(20); // make the long description for the log if (rptr.mod[m].desc1.length()) rptr.mod[m].desc = rptr.mod[m].desc1 + ' '; rptr.mod[m].desc += rptr.mod[m].desc2; - } else - rptr.mod[m].defined = false; + } } if (! (rptr.mod[0].defined || rptr.mod[1].defined || rptr.mod[2].defined)) { printf("No modules defined!\n"); @@ -311,72 +244,45 @@ bool CQnetGateway::read_config(char *cfgFile) } // gateway - path = "gateway."; - if (! get_value(cfg, path+"local_irc_ip", local_irc_ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - if (! get_value(cfg, path+"external.ip", g2_external.ip, 7, IP_SIZE, "0.0.0.0")) - return true; - - get_value(cfg, path+"external.port", g2_external.port, 1024, 65535, 40000); - - get_value(cfg, path+"regen_header", bool_regen_header, true); - - get_value(cfg, path+"aprs_send", bool_send_aprs, true); - - get_value(cfg, path+"send_qrgs_maps", bool_send_qrgs, true); - - get_value(cfg, path+"tolink", gate2link, 1, FILENAME_MAX, "gate2link"); - get_value(cfg, path+"fromlink", link2gate, 1, FILENAME_MAX, "link2gate"); + path.assign("gateway_"); + cfg.GetValue(path+"local_irc_ip", estr, local_irc_ip, 7, IP_SIZE); + cfg.GetValue(path+"external.ip", estr, g2_external.ip, 7, IP_SIZE); + cfg.GetValue(path+"external.port", estr, g2_external.port, 1024, 65535); + cfg.GetValue(path+"regen_header", estr, bool_regen_header); + cfg.GetValue(path+"send_qrgs_maps", estr, bool_send_qrgs); + cfg.GetValue(path+"tolink", estr, gate2link, 1, FILENAME_MAX); + cfg.GetValue(path+"fromlink", estr, link2gate, 1, FILENAME_MAX); // APRS - path = "aprs."; - if (! get_value(cfg, path+"host", rptr.aprs.ip, 7, MAXHOSTNAMELEN, "rotate.aprs.net")) - return true; - - get_value(cfg, path+"port", rptr.aprs.port, 10000, 65535, 14580); - - get_value(cfg, path+"interval", rptr.aprs_interval, 40, 1000, 40); - - if (! get_value(cfg, path+"filter", rptr.aprs_filter, 0, 512, "")) - return true; + path.assign("aprs_"); + cfg.GetValue(path+"enable", estr, bool_send_aprs); + cfg.GetValue(path+"host", estr, rptr.aprs.ip, 7, MAXHOSTNAMELEN); + cfg.GetValue(path+"port", estr, rptr.aprs.port, 10000, 65535); + cfg.GetValue(path+"interval", estr, rptr.aprs_interval, 40, 1000); + cfg.GetValue(path+"filter", estr, rptr.aprs_filter, 0, 512); // log - path = "log."; - get_value(cfg, path+"qso", bool_qso_details, false); - - get_value(cfg, path+"irc", bool_irc_debug, false); - - get_value(cfg, path+"dtmf", bool_dtmf_debug, false); + path.assign("log_"); + cfg.GetValue(path+"qso", estr, bool_qso_details); + cfg.GetValue(path+"irc", estr, bool_irc_debug); + cfg.GetValue(path+"dtmf", estr, bool_dtmf_debug); // file - path = "file."; - if (! get_value(cfg, path+"echotest", echotest_dir, 2, FILENAME_MAX, "/tmp")) - return true; - - if (! get_value(cfg, path+"dtmf", dtmf_dir, 2,FILENAME_MAX, "/tmp")) - return true; - - if (! get_value(cfg, path+"status", status_file, 2, FILENAME_MAX, "/usr/local/etc/RPTR_STATUS.txt")) - return true; - - if (! get_value(cfg, path+"qnvoicefile", qnvoicefile, 2, FILENAME_MAX, "/tmp/qnvoice.txt")) - return true; + path.assign("file_"); + cfg.GetValue(path+"echotest", estr, echotest_dir, 2, FILENAME_MAX); + cfg.GetValue(path+"dtmf", estr, dtmf_dir, 2, FILENAME_MAX); + cfg.GetValue(path+"status", estr, status_file, 2, FILENAME_MAX); + cfg.GetValue(path+"qnvoicefile", estr, qnvoicefile, 2, FILENAME_MAX); // timing - path = "timing.play."; - get_value(cfg, path+"wait", play_wait, 1, 10, 1); - - get_value(cfg, path+"delay", play_delay, 9, 25, 19); - - path = "timing.timeout."; - get_value(cfg, path+"echo", echotest_rec_timeout, 1, 10, 1); - - get_value(cfg, path+"voicemail", voicemail_rec_timeout, 1, 10, 1); - - get_value(cfg, path+"remote_g2", from_remote_g2_timeout, 1, 10, 2); - - get_value(cfg, path+"local_rptr", from_local_rptr_timeout, 1, 10, 1); + path.assign("timing_play_"); + cfg.GetValue(path+"wait", estr, play_wait, 1, 10); + cfg.GetValue(path+"delay", estr, play_delay, 9, 25); + path.assign("timing_timeout_"); + cfg.GetValue(path+"echo", estr, echotest_rec_timeout, 1, 10); + cfg.GetValue(path+"voicemail", estr, voicemail_rec_timeout, 1, 10); + cfg.GetValue(path+"remote_g2", estr, from_remote_g2_timeout, 1, 10); + cfg.GetValue(path+"local_rptr", estr, from_local_rptr_timeout, 1, 10); return false; } diff --git a/QnetGateway.h b/QnetGateway.h index b111474..d08cb37 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -16,14 +16,11 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include #include "QnetTypeDefs.h" #include "SEcho.h" #include "UnixDgramSocket.h" #include "aprs.h" -using namespace libconfig; - #define IP_SIZE 15 #define MAXHOSTNAMELEN 64 #define CALL_SIZE 8 @@ -178,10 +175,6 @@ private: // read configuration file bool read_config(char *); - bool get_value(const Config &cfg, const std::string path, int &value, int min, int max, int default_value); - bool get_value(const Config &cfg, const std::string path, double &value, double min, double max, double default_value); - bool get_value(const Config &cfg, const std::string path, bool &value, bool default_value); - bool get_value(const Config &cfg, const std::string path, std::string &value, int min, int max, const char *default_value); /* aprs functions, borrowed from my retired IRLP node 4201 */ void gps_send(short int rptr_idx); diff --git a/defaults b/defaults index 0a50f56..9b536e5 100644 --- a/defaults +++ b/defaults @@ -39,8 +39,9 @@ ircddb_password_d='1111111111111' # not needed for rr.openquad.net # # GATEWAY # -gateway_regen_header_d=true # regenerate headers from incoming data +gateway_bool_regen_header_d=true # regenerate headers from incoming data gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server +gateway_local_irc_ip_d='0.0.0.0' # the local port on the gateway for the IRC tcp socket gateway_external_ip_d='0.0.0.0' # this means accept a connection from any source gateway_external_port_d=40000 # don't change gateway_tolink_d='gate2link' # Unix sockets between qngateway and QnetLink From f1eb7897d6b933708fb8ebc01414f66786299836 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 23 Dec 2018 11:08:28 -0700 Subject: [PATCH 159/553] qnlink uses CQnetConfigure --- QnetConfigure.cpp | 14 +- QnetConfigure.h | 1 + QnetGateway.cpp | 2 +- QnetGateway.h | 2 +- QnetLink.cpp | 359 ++++++++++++++++------------------------------ QnetLink.h | 18 +-- defaults | 13 +- 7 files changed, 151 insertions(+), 258 deletions(-) diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp index 3d4f813..04c56c6 100644 --- a/QnetConfigure.cpp +++ b/QnetConfigure.cpp @@ -74,6 +74,11 @@ bool CQnetConfigure::Initialize(const char *file) return ReadConfigFile(file, cfg); } +bool CQnetConfigure::KeyExists(const std::string &key) +{ + return (cfg.end() != cfg.find(key)); +} + bool CQnetConfigure::GetDefaultBool(const std::string &path, const std::string &mod, bool &dvalue) { std::string value; @@ -134,10 +139,8 @@ bool CQnetConfigure::GetDefaultString(const std::string &path, const std::string auto it = defaults.find(search); if (defaults.end() == it) { it = defaults.find(search_again); - if (defaults.end() == it) { - fprintf(stderr, "%s has no default value!\n", path.c_str()); + if (defaults.end() == it) return true; - } } dvalue = it->second; return false; @@ -221,7 +224,7 @@ bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, i bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, std::string &value, int min, int max) { auto it = cfg.find(path); - if (cfg.end() != it) { + if (cfg.end() == it) { std::string dvalue; if (GetDefaultString(path, mod, dvalue)) { fprintf(stderr, "%s not found in either the cfg file for the defaults file\n", path.c_str()); @@ -232,8 +235,9 @@ bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, s printf("Default value %s='%s' is wrong size\n", path.c_str(), value.c_str()); return true; } + value.assign(dvalue); } else { - value = it->second; + value.assign(it->second); int l = value.length(); if (lmax) { printf("%s='%s' is wrong size\n", path.c_str(), value.c_str()); diff --git a/QnetConfigure.h b/QnetConfigure.h index 3215988..97ff257 100644 --- a/QnetConfigure.h +++ b/QnetConfigure.h @@ -31,6 +31,7 @@ public: bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max); bool GetValue(const std::string &path, const std::string &mod, int &value, const int min, const int max); bool GetValue(const std::string &path, const std::string &mod, std::string &value, const int min, const int max); + bool KeyExists(const std::string &key); private: std::map defaults; diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 0acce7f..047b358 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2017-2018 by Thomas Early N7TAE + * Copyright (C) 2017-2019 by Thomas Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/QnetGateway.h b/QnetGateway.h index d08cb37..fc05ec8 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 by Thomas Early N7TAE + * Copyright (C) 2018,2019 by Thomas Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/QnetLink.cpp b/QnetLink.cpp index 8f9cd2c..5d3cbfa 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2010 by Scott Lawson KI4LKF - * Copyright (C) 2015,2018 by Thomas A. Early N7TAE + * Copyright (C) 2015,2018,2019 by Thomas A. Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,6 +51,7 @@ #include "versions.h" #include "DPlusAuthenticator.h" +#include "QnetConfigure.h" #include "QnetLink.h" using namespace libconfig; @@ -468,256 +469,137 @@ void CQnetLink::calcPFCS(unsigned char *packet, int len) return; } -bool CQnetLink::get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) +void CQnetLink::ToUpper(std::string &s) { - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%u]\n", path, value); - return true; + for (auto it=s.begin(); it!=s.end(); it++) + if (islower(*it)) + *it = toupper(*it); } -bool CQnetLink::get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) +void CQnetLink::UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters) { - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool CQnetLink::get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); // Skip delimiters at beginning. + std::string::size_type pos = str.find_first_of(delimiters, lastPos); // Find first non-delimiter. + + while (std::string::npos != pos || std::string::npos != lastPos) { + std::string element = str.substr(lastPos, pos-lastPos); + if (element.length()>=3 && element.length()<=6) { + ToUpper(element); + element.resize(CALL_SIZE, ' '); + set.insert(element); // Found a token, add it to the list. + } else + fprintf(stderr, "found bad callsign in list: %s\n", str.c_str()); + lastPos = str.find_first_not_of(delimiters, pos); // Skip delimiters. + pos = str.find_first_of(delimiters, lastPos); // Find next non-delimiter. + } } -bool CQnetLink::get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +void CQnetLink::PrintCallsigns(const std::string &key, const std::set &set) { - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s='%s' is has to be between %d and %d characters\n", path, value.c_str(), min, max); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; + printf("%s = [ ", key.c_str()); + for (auto it=set.begin(); it!=set.end(); it++) { + if (it != set.begin()) + printf(", "); + printf("%s", (*it).c_str()); + } + printf(" ]"); } /* process configuration file */ bool CQnetLink::read_config(const char *cfgFile) { - unsigned short i; - Config cfg; + CQnetConfigure cfg; + const std::string estr; // an empty string printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + if (cfg.Initialize(cfgFile)) return true; - } - catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + + std::string key("ircddb_login"); + if (cfg.GetValue(key, estr, owner, 3, 6)) return true; + ToUpper(owner); + owner.resize(CALL_SIZE, ' '); + + if (cfg.GetValue("dplus_ref_login", estr, login_call, 3, 6)) + login_call.assign(owner); + else { + ToUpper(login_call); + login_call.resize(CALL_SIZE, ' '); } - std::string value; - std::string key = "link.ref_login"; - if (cfg.lookupValue(key, login_call) || cfg.lookupValue("ircddb.login", login_call)) { - int l = login_call.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", login_call.c_str()); - return true; - } else { - for (i=0; i2 && l<=CALL_SIZE-2) { - for (unsigned int j=0; j2 && l2 && l2 && l<=CALL_SIZE-2) { - for (i=0; i= 8) { - if ((link_at_startup[0] == 'A') || (link_at_startup[0] == 'B') || (link_at_startup[0] == 'C')) { - char temp_repeater[CALL_SIZE + 1]; - memset(temp_repeater, ' ', CALL_SIZE); - memcpy(temp_repeater, link_at_startup + 1, 6); - temp_repeater[CALL_SIZE] = '\0'; - printf("sleep for 15 before link at startup\n"); - sleep(15); - g2link(link_at_startup[0], temp_repeater, link_at_startup[7]); + // initialize all request links + bool first = true; + for (int i=0; i<3; i++) { + if (8 == link_at_startup[i].length()) { + if (first) { + printf("sleep for 15 sec before link at startup\n"); + sleep(15); + first = false; + } + g2link('A'+i, link_at_startup[i].substr(0, 6).c_str(), link_at_startup[i].at(7)); } - memset(link_at_startup, '\0', sizeof(link_at_startup)); } while (keep_running) { @@ -2944,12 +2826,19 @@ void CQnetLink::Process() sprintf(notify_msg[i], "%c_id.dat_%s_NOT_LINKED", dstr.vpkt.hdr.r1[7], owner.c_str()); } } - else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[7]=='X' && admin.find(call)!=admin.end()) { // only ADMIN can execute scripts - if (dstr.vpkt.hdr.ur[6] != ' ') { - memset(system_cmd, '\0', sizeof(system_cmd)); - snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), dstr.vpkt.hdr.ur[6], call, dstr.vpkt.hdr.r1[7]); - printf("Executing %s\n", system_cmd); - system(system_cmd); + else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[7]=='X') { // execute a script + if (dstr.vpkt.hdr.ur[6] != ' ') { // there has to be a char here + bool user_ok = true; + if (admin.size()>0 && admin.end()==admin.find(call)) { // only admins (if defined) can execute scripts + printf("%s not found in the link_admin list!\n", call); + user_ok = false; + } + if (user_ok) { + memset(system_cmd, '\0', sizeof(system_cmd)); + snprintf(system_cmd, FILENAME_MAX, "%s/exec_%c.sh %s %c &", announce_dir.c_str(), dstr.vpkt.hdr.ur[6], call, dstr.vpkt.hdr.r1[7]); + printf("Executing %s\n", system_cmd); + system(system_cmd); + } } } else if (0==memcmp(dstr.vpkt.hdr.ur, " ", 6) && dstr.vpkt.hdr.ur[6]=='D' && admin.find(call)!=admin.end()) { // only ADMIN can block dongle users diff --git a/QnetLink.h b/QnetLink.h index 1bb4604..8b62045 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -1,7 +1,7 @@ #pragma once /* - * Copyright (C) 2018 by Thomas A. Early N7TAE + * Copyright (C) 2018-2019 by Thomas A. Early N7TAE * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,15 +25,12 @@ #include #include #include -#include #include "versions.h" #include "QnetTypeDefs.h" #include "SEcho.h" #include "Random.h" #include "UnixDgramSocket.h" -using namespace libconfig; - /*** version number must be x.xx ***/ #define VERSION LINK_VERSION #define CALL_SIZE 8 @@ -68,13 +65,16 @@ public: void Shutdown(); private: // functions + void ToUpper(std::string &s); + void UnpackCallsigns(const std::string &str, std::set &set, const std::string &delimiters = ","); + void PrintCallsigns(const std::string &key, const std::set &set); bool load_gwys(const std::string &filename); void calcPFCS(unsigned char *packet, int len); bool read_config(const char *); bool srv_open(); void srv_close(); static void sigCatch(int signum); - void g2link(char from_mod, char *call, char to_mod); + void g2link(const char from_mod, const char *call, const char to_mod); void print_status_file(); void send_heartbeat(); bool resolve_rmt(char *name, int type, struct sockaddr_in *addr); @@ -82,19 +82,15 @@ private: void PlayAudioNotifyThread(char *msg); void AudioNotifyThread(SECHO &edata); void RptrAckThread(char *arg); - bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value); - bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value); - bool get_value(const Config &cfg, const char *path, bool &value, bool default_value); - bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value); /* configuration data */ std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, qnvoice_file, announce_dir; bool only_admin_login, only_link_unlink, qso_details, bool_rptr_ack, announce; bool dplus_authorize, dplus_reflectors, dplus_repeaters; int rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port, delay_between, delay_before; - char link_at_startup[CALL_SIZE+1]; + std::string link_at_startup[3]; unsigned int max_dongles, saved_max_dongles; - long rf_inactivity_timer[3]; + int rf_inactivity_timer[3]; const unsigned char REF_ACK[3] = { 3, 96, 0 }; // the Key in this inbound_list map is the unique IP address of the remote diff --git a/defaults b/defaults index 9b536e5..7a15238 100644 --- a/defaults +++ b/defaults @@ -25,12 +25,15 @@ # If a string value is a simple word, it doesn't need to be quoted # Use the single quote (') for quoting strings, not the double quote(") # Comments can come after a key=value definition, introduced by a pound-sign (#) +# +# if a definition is commented out, it means that key has no default value. And it is +# include here just as a reference. ########################################################################################################################## # # IRCDDB - You MUST use a legal callsign for logging into any IRC network # -ircddb_login_d='' # login callsign for the ircDDB network +#ircddb_login_d='' # login callsign for the ircDDB network ircddb_host_d='rr.openquad.net' # other irc networks include group1-irc.ircddb.net ircddb_port_d=9007 # not a good idea to change! ircddb_password_d='1111111111111' # not needed for rr.openquad.net @@ -61,8 +64,8 @@ aprs_filter_d='' # advanced feature # # LINK - controls the behaviour of QnetLink (qnlink) # -link_admin_d='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt -link_link_unlink_d='' # if defined, comma-separated list of users that can link and unlink a repeater +#link_admin_d='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt +#link_link_unlink_d='' # if defined, comma-separated list of users that can link and unlink a repeater #link_no_link_unlink_d='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist # if the blacklist is defined (even if it's empty), the link_unlink will not be read link_incoming_ip_d='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection. @@ -77,7 +80,7 @@ link_max_dongles_d=5 # maximum number of linked hot-spots # # GENERIC MODULE - These will be defined for any and all defined modules # -module_x_link_at_start='' # For example, set to 'REF001CL' to link module to 1-charlie when the module starts. +#module_x_link_at_start='' # For example, set to 'REF001 C' to link module to 1-charlie when the module starts. module_x_inactivity=0 # if no activity for this many minutes unlink reflector. Zero means no timer. module_x_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. module_x_packet_wait=25 # how many milliseconds to wait on packets in a voicestream @@ -145,7 +148,7 @@ log_dtmf_d=false # DTMF debug info # The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! # You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, # even if QnetLink reports a successful authorization. -dplus_ref_login_d='' # for logging into REF reflectors, if undefined or empty, ircddb_login will be used +dplus_ref_login_d='' # for logging into REF reflectors, if empty, ircddb_login will be used dplus_authorize_d=false # set to true if you want to use the closed-source DPlus reflectors and/or repeaters dplus_use_reflectors_d=true # set to false if you are not going to link to DPlus reflectors dplus_use_repeaters_d=true # set to false if you are not going to link to DPlus repeaters From 93ec44e055ddc9aa729c8261b8df441e6922be61 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 26 Dec 2018 12:16:17 -0700 Subject: [PATCH 160/553] qnrelay uses QnetConfigure --- QnetConfigure.h | 1 - QnetRelay.cpp | 130 ++++++------------------------------------------ QnetRelay.h | 9 ---- versions.h | 14 +++--- 4 files changed, 23 insertions(+), 131 deletions(-) diff --git a/QnetConfigure.h b/QnetConfigure.h index 97ff257..efb404f 100644 --- a/QnetConfigure.h +++ b/QnetConfigure.h @@ -25,7 +25,6 @@ class CQnetConfigure { public: CQnetConfigure(); virtual ~CQnetConfigure(); - //virtual bool ReadCfgFile() = 0; bool Initialize(const char *configfile); bool GetValue(const std::string &path, const std::string &mod, bool &value); bool GetValue(const std::string &path, const std::string &mod, double &value, const double min, const double max); diff --git a/QnetRelay.cpp b/QnetRelay.cpp index a0da66c..0fd72c2 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -35,6 +35,7 @@ #include "versions.h" #include "QnetRelay.h" #include "QnetTypeDefs.h" +#include "QnetConfigure.h" std::atomic CQnetRelay::keep_running(true); @@ -345,75 +346,22 @@ bool CQnetRelay::ProcessMMDVM(const int len, const unsigned char *raw) return false; } -bool CQnetRelay::GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool CQnetRelay::GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool CQnetRelay::GetValue(const Config &cfg, const char *path, bool &value, const bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool CQnetRelay::GetValue(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s value '%s' is wrong size\n", path, value.c_str()); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - // process configuration file and return true if there was a problem bool CQnetRelay::ReadConfig(const char *cfgFile) { - Config cfg; - + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + if (cfg.Initialize(cfgFile)) return true; - } - catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - std::string value; - std::string mmdvm_path("module."); + const std::string estr; // an empty GetDefaultString + std::string type; + std::string mmdvm_path("module_"); mmdvm_path.append(1, 'a' + assigned_module); - if (cfg.lookupValue(mmdvm_path + ".type", value)) { - if (value.compare("mmdvm")) { - fprintf(stderr, "assigned module is not 'mmdvm' type!\n"); + if (cfg.KeyExists(mmdvm_path)) { + cfg.GetValue(mmdvm_path, estr, type, 1, 16); + if (type.compare("mmdvm")) { + fprintf(stderr, "%s = %s is not 'mmdvm' type!\n", mmdvm_path.c_str(), type.c_str()); return true; } } else { @@ -421,63 +369,17 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) return true; } RPTR_MOD = 'A' + assigned_module; - char unixsockname[16]; - snprintf(unixsockname, 16, "gate2module%d", assigned_module); - GetValue(cfg, std::string(mmdvm_path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); - snprintf(unixsockname, 16, "module2gate%d", assigned_module); - GetValue(cfg, std::string(mmdvm_path+",togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); - - if (cfg.lookupValue(std::string(mmdvm_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - fprintf(stderr, "Call '%s' is invalid length!\n", value.c_str()); - return true; - } else { - for (int i=0; iCALL_SIZE-2) { - fprintf(stderr, "Call '%s' is invalid length!\n", value.c_str()); - return true; - } else { - for (int i=0; i #include -#include #include #include "UnixDgramSocket.h" -using namespace libconfig; - #define CALL_SIZE 8 #define IP_SIZE 15 @@ -52,10 +49,6 @@ private: // read configuration file bool ReadConfig(const char *); - bool GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value); - bool GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value); - bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value); - bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value); // Unix sockets int assigned_module; @@ -65,8 +58,6 @@ private: // config data char RPTR_MOD; - char RPTR[CALL_SIZE + 1]; - char OWNER[CALL_SIZE + 1]; std::string MMDVM_IP; unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT; bool log_qso; diff --git a/versions.h b/versions.h index a7b4b13..ab25e36 100644 --- a/versions.h +++ b/versions.h @@ -1,9 +1,9 @@ // version strings must be 55 characters or less! -#define IRCDDB_VERSION "QnetGateway-7.4.6" -#define LINK_VERSION "QnetLink-6.4.0" -#define DVAP_VERSION "QnetDVAP-5.1.2" -#define RELAY_VERSION "QnetRelay-0.2.3" -#define ITAP_VERSION "QnetITAP-0.2.1" -#define DVRPTR_VERSION "QnetDVRPTR-5.1.0" -#define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" +#define IRCDDB_VERSION "QnetGateway-8.0.0" +#define LINK_VERSION "QnetLink7.0.0" +#define DVAP_VERSION "QnetDVAP-6.0.0" +#define RELAY_VERSION "QnetRelay-1.0.0" +#define ITAP_VERSION "QnetITAP-1.0.0" +#define DVRPTR_VERSION "QnetDVRPTR-6.0.0" +#define MMDVM_VERSION "QnetGateway-MMDVM-1.0.0" #define ICOM_VERSION IRCDDB_VERSION From 1563af8e27e8b6aae11f661331615d51e65747e8 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 26 Dec 2018 13:07:45 -0700 Subject: [PATCH 161/553] qnitap uses QnetConfigure --- QnetITAP.cpp | 137 ++++++++++++-------------------------------------- QnetITAP.h | 11 +--- QnetRelay.cpp | 2 +- 3 files changed, 34 insertions(+), 116 deletions(-) diff --git a/QnetITAP.cpp b/QnetITAP.cpp index f24753e..f2b1965 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -43,6 +43,7 @@ #include "versions.h" #include "QnetITAP.h" #include "QnetTypeDefs.h" +#include "QnetConfigure.h" std::atomic CQnetITAP::keep_running(true); @@ -403,9 +404,9 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) ////////////////// Terminal or Access ///////////////////////// if (0 == memcmp(itap.header.r1, "DIRECT", 6)) { // Terminal Mode! - memcpy(dstr.vpkt.hdr.r1, RPTR, 7); // build r1 + memcpy(dstr.vpkt.hdr.r1, RPTR.c_str(), 7); // build r1 dstr.vpkt.hdr.r1[7] = RPTR_MOD; // with module - memcpy(dstr.vpkt.hdr.r2, RPTR, 7); // build r2 + memcpy(dstr.vpkt.hdr.r2, RPTR.c_str(), 7); // build r2 dstr.vpkt.hdr.r2[7] = 'G'; // with gateway if (' '==itap.header.ur[2] && ' '!=itap.header.ur[0]) { // it's a command because it has as space in the 3rd position, we have to right-justify it! @@ -452,74 +453,21 @@ bool CQnetITAP::ProcessITAP(const unsigned char *buf) return false; } -bool CQnetITAP::GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool CQnetITAP::GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool CQnetITAP::GetValue(const Config &cfg, const char *path, bool &value, const bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool CQnetITAP::GetValue(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s value '%s' is wrong size\n", path, value.c_str()); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - // process configuration file and return true if there was a problem bool CQnetITAP::ReadConfig(const char *cfgFile) { - Config cfg; - + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - fprintf(stderr, "Can't read %s\n", cfgFile); - return true; - } - catch(const ParseException &pex) { - fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + if (cfg.Initialize(cfgFile)) return true; - } - std::string value; - std::string itap_path("module."); + const std::string estr; // an empty string + std::string type; + std::string itap_path("module_"); itap_path.append(1, 'a' + assigned_module); - if (cfg.lookupValue(itap_path + ".type", value)) { - if (value.compare("itap")) { + if (cfg.KeyExists(itap_path)) { + cfg.GetValue(itap_path, estr, type, 1, 16); + if (type.compare("itap")) { fprintf(stderr, "assigned module %c is not 'itap'\n", 'a' + assigned_module); return true; } @@ -529,55 +477,34 @@ bool CQnetITAP::ReadConfig(const char *cfgFile) } RPTR_MOD = 'A' + assigned_module; - char unixsockname[16]; - snprintf(unixsockname, 16, "gate2modem%d", assigned_module); - GetValue(cfg, std::string(itap_path+".togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); - snprintf(unixsockname, 16, "modem2gate%d", assigned_module); - GetValue(cfg, std::string(itap_path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); + cfg.GetValue(itap_path+"_device", type, ITAP_DEVICE, 7, FILENAME_MAX); + cfg.GetValue(itap_path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); + cfg.GetValue(itap_path+"_modem2gate"+std::to_string(assigned_module), type, modem2gate, 1, FILENAME_MAX); - if (cfg.lookupValue(std::string(itap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); + itap_path.append("_callsign"); + if (cfg.KeyExists(itap_path)) { + if (cfg.GetValue(itap_path, type, RPTR, 3, 6)) return true; - } else { - for (int i=0; iCALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return true; - } else { - for (int i=0; i6) { + printf("Call '%s' is invalid length!\n", RPTR.c_str()); return true; + } else { + for (int i=0; i #include -#include #include #include "Random.h" // for streamid generation #include "UnixDgramSocket.h" -using namespace libconfig; - #define CALL_SIZE 8 #define IP_SIZE 15 @@ -104,16 +101,10 @@ private: // read configuration file bool ReadConfig(const char *); - bool GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value); - bool GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value); - bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value); - bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value); // config data char RPTR_MOD; - char RPTR[CALL_SIZE + 1]; - char OWNER[CALL_SIZE + 1]; - std::string ITAP_DEVICE; + std::string ITAP_DEVICE, RPTR; bool log_qso; // parameters diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 0fd72c2..b09f435 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -379,7 +379,7 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) cfg.GetValue(mmdvm_path+"+gateway_port", type, i, 10000, 65535); MMDVM_OUT_PORT = (unsigned short)i; - cfg.GetValue("log.qso", estr, log_qso); + cfg.GetValue("log_qso", estr, log_qso); return false; } From 70bdffbf3fa0df598a5a15a21366b6100c2b0825 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 27 Dec 2018 09:52:59 -0700 Subject: [PATCH 162/553] qndvap uses QnetConfigure --- DVAPDongle.cpp | 4 +- DVAPDongle.h | 4 +- QnetDVAP.cpp | 223 +++++++++++++++---------------------------------- defaults | 22 +++-- 4 files changed, 86 insertions(+), 167 deletions(-) diff --git a/DVAPDongle.cpp b/DVAPDongle.cpp index 6971ca8..c5f3059 100644 --- a/DVAPDongle.cpp +++ b/DVAPDongle.cpp @@ -36,7 +36,7 @@ CDVAPDongle::~CDVAPDongle() { } -bool CDVAPDongle::Initialize(char *serialno, int frequency, int offset, int power, int squelch) +bool CDVAPDongle::Initialize(const char *serialno, const int frequency, const int offset, int const power, const int squelch) { bool ok = false; char device[128]; @@ -276,7 +276,7 @@ bool CDVAPDongle::syncit() return false; } -bool CDVAPDongle::get_ser(char *dvp, char *dvap_serial_number) +bool CDVAPDongle::get_ser(const char *dvp, const char *dvap_serial_number) { unsigned cnt = 0; REPLY_TYPE reply; diff --git a/DVAPDongle.h b/DVAPDongle.h index 78f6fce..0c2c542 100644 --- a/DVAPDongle.h +++ b/DVAPDongle.h @@ -88,7 +88,7 @@ class CDVAPDongle public: CDVAPDongle(); ~CDVAPDongle(); - bool Initialize(char *serialno, int frequency, int offset, int power, int squelch); + bool Initialize(const char *serialno, const int frequency, const int offset, const int power, const int squelch); REPLY_TYPE GetReply(SDVAP_REGISTER &dr); void Stop(); int KeepAlive(); @@ -107,7 +107,7 @@ class CDVAPDongle int read_from_dvp(void* buf, unsigned int len); int write_to_dvp(const void* buf, const unsigned int len); bool syncit(); - bool get_ser(char *dvp, char *dvap_serial_number); + bool get_ser(const char *dvp, const char *dvap_serial_number); bool get_name(); bool get_fw(); bool set_modu(); diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 73adb7e..d62e5a0 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -45,17 +45,15 @@ #include #include #include -#include -using namespace libconfig; #include "DVAPDongle.h" #include "QnetTypeDefs.h" #include "Random.h" #include "UnixDgramSocket.h" +#include "QnetConfigure.h" #define VERSION DVAP_VERSION #define CALL_SIZE 8 -#define RPTR_SIZE 8 #define IP_SIZE 15 typedef struct dvap_ack_arg_tag { @@ -71,10 +69,10 @@ static std::string modem2gate, gate2modem; static CUnixDgramReader Gate2Modem; static CUnixDgramWriter Modem2Gate; /* Default configuration data */ -static char RPTR[RPTR_SIZE + 1]; -static char OWNER[RPTR_SIZE + 1]; +static std::string RPTR; +static std::string OWNER; static char RPTR_MOD; -static char DVP_SERIAL[64]; /* APxxxxxx */ +static std::string DVP_SERIAL; /* APxxxxxx */ static int DVP_FREQ; /* between 144000000 and 148000000 */ static int DVP_PWR; /* between -12 and 10 */ static int DVP_SQL; /* between -128 and -45 */ @@ -98,7 +96,7 @@ static unsigned int space = 0; static unsigned int aseed = 0; /* helper routines */ -static int read_config(const char *cfgFile); +static bool read_config(const char *cfgFile); static void sig_catch(int signum); static int open_sock(); static void readFrom20000(); @@ -157,152 +155,68 @@ static void sig_catch(int signum) exit(0); } -static bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -static bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -static bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -static bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - /* process configuration file */ -static int read_config(const char *cfgFile) +static bool read_config(const char *cfgFile) { - Config cfg; + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - fprintf(stderr, "Can't read %s\n", cfgFile); - return 1; - } - catch(const ParseException &pex) { - fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return 1; - } + if (cfg.Initialize(cfgFile)) + return true; - std::string value; - std::string dvap_path("module."); + const std::string estr; // an empty string + std::string type; + std::string dvap_path("module_"); dvap_path.append(1, 'a' + assigned_module); - if (cfg.lookupValue(dvap_path + ".type", value)) { - if (value.compare("dvap")) { + if (cfg.KeyExists(dvap_path)) { + cfg.GetValue(dvap_path, estr, type, 1, 16); + if (type.compare("dvap")) { fprintf(stderr, "assigned module '%c' type is not 'dvap'\n", 'a' + assigned_module); - return 1; + return true; } } else { fprintf(stderr, "%s is not defined!\n", dvap_path.c_str()); - return 1; + return true; } RPTR_MOD = 'A' + assigned_module; - char unixsockname[16]; - snprintf(unixsockname, 16, "gate2modem%d", assigned_module); - get_value(cfg, std::string(dvap_path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); - snprintf(unixsockname, 16, "modem2gate%d", assigned_module); - get_value(cfg, std::string(dvap_path+".togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); - - if (cfg.lookupValue(std::string(dvap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (int i=0; iCALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (int i=0; i 60) { spack.counter = C_COUNTER++; - memcpy(spack.spkt.mycall, OWNER, 7); + memcpy(spack.spkt.mycall, OWNER.c_str(), 7); spack.spkt.mycall[7] = 'S'; - memcpy(spack.spkt.rpt, OWNER, 7); + memcpy(spack.spkt.rpt, OWNER.c_str(), 7); spack.spkt.rpt[7] = 'S'; Modem2Gate.Write(spack.pkt_id, 26); S_ctrl_msg_time = tnow; @@ -955,7 +868,7 @@ static void ReadDVAPThread() (net_buf.vpkt.hdr.r2[7] == 'B') || (net_buf.vpkt.hdr.r2[7] == 'C') || (net_buf.vpkt.hdr.r2[7] == 'G')) - memcpy(net_buf.vpkt.hdr.r2, RPTR, 7); + memcpy(net_buf.vpkt.hdr.r2, RPTR.c_str(), 7); else memset(net_buf.vpkt.hdr.r2, ' ', 8); @@ -973,9 +886,9 @@ static void ReadDVAPThread() that means that mycall, rpt1, rpt2 must be equal to RPTR otherwise we drop the rf data */ - if (memcmp(RPTR, OWNER, RPTR_SIZE) != 0) { - if (memcmp(net_buf.vpkt.hdr.my, RPTR, RPTR_SIZE) != 0) { - printf("mycall=[%.8s], not equal to %s\n", net_buf.vpkt.hdr.my, RPTR); + if (RPTR.compare(OWNER)) { + if (memcmp(net_buf.vpkt.hdr.my, RPTR.c_str(), CALL_SIZE) != 0) { + printf("mycall=[%.8s], not equal to %s\n", net_buf.vpkt.hdr.my, RPTR.c_str()); ok = false; } } else if (memcmp(net_buf.vpkt.hdr.my, " ", 8) == 0) { @@ -1034,15 +947,15 @@ static void ReadDVAPThread() /* for icom g2 */ spack.counter = C_COUNTER++; memcpy(spack.spkt.mycall, net_buf.vpkt.hdr.my, 8); - memcpy(spack.spkt.rpt, OWNER, 7); + memcpy(spack.spkt.rpt, OWNER.c_str(), 7); spack.spkt.rpt[7] = RPTR_MOD; Modem2Gate.Write(spack.pkt_id, 26); // Before we send the data to the local gateway, // set RPT1, RPT2 to be the local gateway - memcpy(net_buf.vpkt.hdr.r1, OWNER, 7); + memcpy(net_buf.vpkt.hdr.r1, OWNER.c_str(), 7); if (net_buf.vpkt.hdr.r2[7] != ' ') - memcpy(net_buf.vpkt.hdr.r2, OWNER, 7); + memcpy(net_buf.vpkt.hdr.r2, OWNER.c_str(), 7); memcpy(net_buf.pkt_id, "DSTR", 4); net_buf.counter = C_COUNTER++; diff --git a/defaults b/defaults index 7a15238..a5c09ed 100644 --- a/defaults +++ b/defaults @@ -102,14 +102,6 @@ module_x_modem2gate1='modem2gate1' module_x_modem2gate2='modem2gate2' module_x_url='github.com/n7tae/g2_ircddb' # 80 characters max -########################################################################################################################## -# -# DVAP - Special parameters when: module.x='dvap' -# -dvap_power=10 # TX power level: -12 to 10, 10 is maximum power -dvap_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best -dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case - ########################################################################################################################## # # MMDVM - Special parameters when: module_x='mmdvm' @@ -118,6 +110,20 @@ mmdvm_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program mmdvm_gateway_port=20010 # which port will QnetRelay be sending on mmdvm_local_port=20011 # which port will MMDVMHost be sending on +########################################################################################################################## +# +# ITAP - Special parameters when: module_x='itap' +# +device='/dev/ttyUSB0' # where the serial-to-USB cable show up + +########################################################################################################################## +# +# DVAP - Special parameters when: module.x='dvap' +# +dvap_power=10 # TX power level: -12 to 10, 10 is maximum power +dvap_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best +dvap_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case + ########################################################################################################################## # # DVRPTR - Special parameters when: module_x='dvrptr' From 538b5942f09584d6389f428a5dacb0e01640a082 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 27 Dec 2018 23:31:11 -0700 Subject: [PATCH 163/553] qndvrptr uses QnetConfigure --- QnetDVRPTR.cpp | 232 +++++++++++++++---------------------------------- 1 file changed, 71 insertions(+), 161 deletions(-) diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index 44f6ccf..c9b939e 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -23,17 +23,14 @@ #include #include "versions.h" #include -#include #include "Random.h" #include "UnixDgramSocket.h" - -using namespace libconfig; +#include "QnetConfigure.h" #define VERSION DVRPTR_VERSION #define BAUD B115200 #define CALL_SIZE 8 -#define RPTR_SIZE 8 #define IP_SIZE 15 /*** BER stuff ***/ @@ -60,7 +57,7 @@ static unsigned streamid[2] = {0x00, 0x00}; static unsigned char start_Header[8]= {0xD0,0x03,0x00,0x16,0x01,0x00,0x00,0x00}; static unsigned char ptt_off[8]= {0xD0,0x03,0x00,0x1A,0x01,0xff,0x00,0x00}; -static int read_config(const char *cfgFile); +static bool read_config(const char *cfgFile); static void readFrom20000(); static bool check_serial(); @@ -87,22 +84,22 @@ static std::string gate2modem, modem2gate; CUnixDgramWriter Modem2Gate; CUnixDgramReader Gate2Modem; -static char DVRPTR_SERIAL[16]; -static char DVCALL[RPTR_SIZE + 1] = {"ABCDEF"}; -static char RPTR[RPTR_SIZE + 1] = {"ABCDEF"}; +static std::string DVRPTR_SERIAL; +static char DVCALL[CALL_SIZE + 1]; +static char RPTR[CALL_SIZE + 1]; static char DVRPTR_MOD = 'B'; static int RF_AUDIO_Level = 10; static bool DUPLEX = true; -static long ACK_DELAY = 200000; +static int ACK_DELAY = 200000; static int DELAY_BETWEEN = 20000; static bool RPTR_ACK = true; -static char ENABLE_RF[RPTR_SIZE + 1] = {" "}; -static char DISABLE_RF[RPTR_SIZE + 1] = {" "}; +static char ENABLE_RF[CALL_SIZE + 1]; +static char DISABLE_RF[CALL_SIZE + 1]; static bool IS_ENABLED = true; static bool ok = false; static bool RX_Inverse = 0; static bool TX_Inverse = 0; -static int TX_DELAY = 250; /* in milliseconds */ +static int TX_DELAY; /* in milliseconds */ static unsigned char SND_TERM_ID = 0x00; static char DVCALL_and_G[9]; static char DVCALL_and_MOD[9]; @@ -1784,6 +1781,13 @@ static unsigned short crc_tabccitt[256] = { 0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 }; +static void CleanCall(std::string &callsign) { + for (auto it=callsign.begin(); it!=callsign.end(); it++) + if (islower(*it)) + *it = toupper(*it); + callsign.resize(CALL_SIZE, ' '); +} + static void calcPFCS(unsigned char packet[58])//Netzwerk CRC { unsigned short crc_dstar_ffff = 0xffff; @@ -1804,172 +1808,80 @@ static void calcPFCS(unsigned char packet[58])//Netzwerk CRC return; } -static bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -//static bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -//{ -// if (cfg.lookupValue(path, value)) { -// if (value < min || value > max) -// value = default_value; -// } else -// value = default_value; -// printf("%s = [%lg]\n", path, value); -// return true; -//} - -static bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -static bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s='%s' is wrong size, must be between %d and %d\n", path, value.c_str(), min, max); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - /* process configuration file */ -static int read_config(const char *cfgFile) +static bool read_config(const char *cfgFile) { - int i; - Config cfg; + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } - catch(const FileIOException &fioex) { - fprintf(stderr, "Can't read %s\n", cfgFile); - return 1; - } - catch(const ParseException &pex) { - fprintf(stderr, "Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return 1; - } + if (cfg.Initialize(cfgFile)) + return true; - std::string value; - std::string path("module."); + const std::string estr; // an empty string + std::string type; + std::string path("module_"); path += ('a' + assigned_module); - if (cfg.lookupValue(path + ".type", value)) { - if (value.compare("dvrptr")) { - fprintf(stderr, "module %c is not type 'dvrptr'\n", 'a' + assigned_module); - return 1; + if (cfg.KeyExists(path)) { + if (cfg.GetValue(path, estr, type, 1, 16) || type.compare("dvrptr")) { + fprintf(stderr, "%s = %s is not 'dvrptr'!\n", path.c_str(), type.c_str()); + return true; } } else { fprintf(stderr, "module %c is not defined\n", 'a' + assigned_module); return 1; } - DVRPTR_MOD = 'A' + i; - char unixsockname[16]; - snprintf(unixsockname, 16, "gate2modem%d", assigned_module); - get_value(cfg, std::string(path+".fromgateway").c_str(), gate2modem, 1, FILENAME_MAX, unixsockname); - snprintf(unixsockname, 16, "modem2gate%d", assigned_module); - get_value(cfg, std::string(path+".togateway").c_str(), modem2gate, 1, FILENAME_MAX, unixsockname); - - if (cfg.lookupValue(std::string(path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { - int l = value.length(); - if (l<3 || l>CALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (i=0; iCALL_SIZE-2) { - printf("Call '%s' is invalid length!\n", value.c_str()); - return 1; - } else { - for (i=0; i> 8; - get_value(cfg, std::string(path+".rqst_count").c_str(), RQST_COUNT, 6, 20, 10); + cfg.GetValue(path+"_rqst_count", type, RQST_COUNT, 6, 20); + + cfg.GetValue(path+"_inverse_rx", type, RX_Inverse); + cfg.GetValue(path+"_inverse_tx", type, TX_Inverse); - get_value(cfg, std::string(path+".inverse.rx").c_str(), RX_Inverse, true); - get_value(cfg, std::string(path+".inverse.tx").c_str(), TX_Inverse, true); + path.assign("timing_"); + cfg.GetValue(path+"timeout_remote_g2", estr, REMOTE_TIMEOUT, 1, 10); + + cfg.GetValue(path+"_play_delay", estr, DELAY_BETWEEN, 10, 25); + DELAY_BETWEEN *= 1000; inactiveMax = (REMOTE_TIMEOUT * 1000000) / 400; printf("... computed max number of loops %d, each loop is 400 microseconds\n", inactiveMax); @@ -2079,7 +1991,7 @@ static void send_ack(char *a_call, float ber) memcpy(Send_Modem_Header + 35, DVCALL_and_MOD, 8); memcpy(Send_Modem_Header + 43, "RPTR", 4); - if (memcmp(RPTR, DVCALL, RPTR_SIZE) != 0) { + if (memcmp(RPTR, DVCALL, CALL_SIZE) != 0) { memcpy(Send_Modem_Header + 11, RPTR, 7); memcpy(Send_Modem_Header + 19, RPTR, 7); @@ -2248,7 +2160,7 @@ static void readFrom20000() } memcpy(recv_buf.rf_hdr.rpt1, DVCALL_and_G, 8); - if (memcmp(RPTR, DVCALL, RPTR_SIZE) != 0) { + if (memcmp(RPTR, DVCALL, CALL_SIZE) != 0) { memcpy(recv_buf.rf_hdr.rpt1, RPTR, 7); memcpy(recv_buf.rf_hdr.rpt2, RPTR, 7); @@ -2515,7 +2427,7 @@ bool check_serial() puffer[1] = 0x00; sprintf(temp_dvrptr_serial, "%02X.%02X.%02X.%02X", puffer[4], puffer[5], puffer[6], puffer[7]); printf("Device %s has serial=[%s]\n", dvrptr_device, temp_dvrptr_serial); - if (strcmp(temp_dvrptr_serial, DVRPTR_SERIAL) == 0) { + if (strcmp(temp_dvrptr_serial, DVRPTR_SERIAL.c_str()) == 0) { printf("Device %s serial number matches DVRPTR_SERIAL in dvrptr.cfg\n", dvrptr_device); match = true; } @@ -2550,7 +2462,6 @@ int main(int argc, const char **argv) int InitCount = 1; short seq_no = 0; unsigned char puffer[200]; - int rc; char Temp_Text[200]; time_t last_RF_time = 0; time_t tNow = 0; @@ -2588,8 +2499,7 @@ int main(int argc, const char **argv) return 1; } - rc = read_config(argv[2]); - if (rc != 0) { + if (read_config(argv[2])) { fprintf(stderr, "Failed to process config file %s\n", argv[2]); return 1; } @@ -2806,8 +2716,8 @@ int main(int argc, const char **argv) that means that mycall, rpt1, rpt2 must be equal to RPTR otherwise we drop the rf data */ - if (memcmp(RPTR, DVCALL, RPTR_SIZE) != 0) { - if (memcmp(myCall, RPTR, RPTR_SIZE) != 0) { + if (memcmp(RPTR, DVCALL, CALL_SIZE) != 0) { + if (memcmp(myCall, RPTR, CALL_SIZE) != 0) { printf("mycall=[%.8s], not equal to %s\n", myCall, RPTR); ok = false; } From 0053338b2474e1b9ee21fc03e4ed6cdea9ffbf6c Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 27 Dec 2018 23:47:24 -0700 Subject: [PATCH 164/553] qnvoice uses QnetConfigure --- Makefile | 6 ++-- QnetVoice.cpp | 82 ++++++++------------------------------------------- 2 files changed, 16 insertions(+), 72 deletions(-) diff --git a/Makefile b/Makefile index dba5182..7d229ad 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -ALL_PROGRAMS=qnigateway qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap +ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr @@ -70,8 +70,8 @@ qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o QnetConfigure.o qnremote : QnetRemote.o Random.o UnixDgramSocket.o QnetConfigure.o g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) -qnvoice : QnetVoice.o Random.o - g++ $(CPPFLAGS) -o qnvoice QnetVoice.o Random.o $(LDFLAGS) +qnvoice : QnetVoice.o Random.o QnetConfigure.o + g++ $(CPPFLAGS) -o qnvoice QnetVoice.o Random.o QnetConfigure.o $(LDFLAGS) %.o : %.cpp g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ diff --git a/QnetVoice.cpp b/QnetVoice.cpp index 6173bf6..553c75a 100644 --- a/QnetVoice.cpp +++ b/QnetVoice.cpp @@ -22,84 +22,28 @@ #include #include -#include - - -using namespace libconfig; +#include "QnetConfigure.h" bool isamod[3] = { false, false, false }; std::string announce_dir; std::string qnvoice_file; - -bool get_value(const Config &cfg, const char *path, int &value, int min, int max, int default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%d]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, double &value, double min, double max, double default_value) -{ - if (cfg.lookupValue(path, value)) { - if (value < min || value > max) - value = default_value; - } else - value = default_value; - printf("%s = [%lg]\n", path, value); - return true; -} - -bool get_value(const Config &cfg, const char *path, bool &value, bool default_value) -{ - if (! cfg.lookupValue(path, value)) - value = default_value; - printf("%s = [%s]\n", path, value ? "true" : "false"); - return true; -} - -bool get_value(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) -{ - if (cfg.lookupValue(path, value)) { - int l = value.length(); - if (lmax) { - printf("%s is invalid\n", path); - return false; - } - } else - value = default_value; - printf("%s = [%s]\n", path, value.c_str()); - return true; -} - /* process configuration file */ bool read_config(const char *cfgFile) { - Config cfg; + CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); - // Read the file. If there is an error, report it and exit. - try { - cfg.readFile(cfgFile); - } catch(const FileIOException &fioex) { - printf("Can't read %s\n", cfgFile); + if (cfg.Initialize(cfgFile)) return true; - } catch(const ParseException &pex) { - printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); - return true; - } - for (short int m=0; m<3; m++) { - std::string path = "module."; - path += m + 'a'; + for (int m=0; m<3; m++) { + std::string path("module_"); + path.append(std::to_string(m)); std::string type; - if (cfg.lookupValue(std::string(path+".type").c_str(), type)) { - if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && - strcasecmp(type.c_str(), "icom") && strcasecmp(type.c_str(), "itap")) { + if (cfg.KeyExists(path)) { + cfg.GetValue(path, "", type, 1, 16); + if (strcasecmp(type.c_str(), "dvap") && strcasecmp(type.c_str(), "dvrptr") && strcasecmp(type.c_str(), "mmdvm") && strcasecmp(type.c_str(), "itap")) { printf("module type '%s' is invalid\n", type.c_str()); return true; } @@ -107,9 +51,9 @@ bool read_config(const char *cfgFile) } } - get_value(cfg, "file.announce_dir", announce_dir, 2, FILENAME_MAX, "/usr/local/etc"); - - get_value(cfg, "file.qnvoice_file", qnvoice_file, 2, FILENAME_MAX, "/tmp/qnvoice.txt"); + std::string path("file_"); + cfg.GetValue(path+"announce_dir", "", announce_dir, 2, FILENAME_MAX); + cfg.GetValue(path+"qnvoice_file", "", qnvoice_file, 2, FILENAME_MAX); return false; } @@ -134,13 +78,13 @@ int main(int argc, char *argv[]) printf(" is an up to 20-character text message\n"); return 0; } + char module = argv[1][0]; std::string cfgfile(CFG_DIR); cfgfile += "/qn.cfg"; if (read_config(cfgfile.c_str())) return 1; - char module = argv[1][0]; if (islower(module)) module = toupper(module); if ((module != 'A') && (module != 'B') && (module != 'C')) { From 606b342921e3f2107c96916ea306d64503ac13f8 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 28 Dec 2018 11:21:43 -0700 Subject: [PATCH 165/553] updated cfg files --- defaults | 2 +- qn.dvap.cfg | 47 +--- qn.everything.cfg | 540 ++++++++++++++++++++-------------------------- qn.icom.cfg | 152 ------------- qn.itap.cfg | 45 +--- qn.mmdvm.cfg | 54 +---- 6 files changed, 258 insertions(+), 582 deletions(-) delete mode 100644 qn.icom.cfg diff --git a/defaults b/defaults index a5c09ed..4ca3b34 100644 --- a/defaults +++ b/defaults @@ -114,7 +114,7 @@ mmdvm_local_port=20011 # which port will MMDVMHost be sending on # # ITAP - Special parameters when: module_x='itap' # -device='/dev/ttyUSB0' # where the serial-to-USB cable show up +itap_device='/dev/ttyUSB0' # where the serial-to-USB cable show up ########################################################################################################################## # diff --git a/qn.dvap.cfg b/qn.dvap.cfg index 92b466a..43f04cb 100644 --- a/qn.dvap.cfg +++ b/qn.dvap.cfg @@ -1,44 +1,9 @@ -# g2_ircddb Configuration for me +# A Simple Configuration for a 2M DVAP -ircddb = { - login = "XX0XXX" -# If you are not using rr.openquad.net, you need to specify the host and possibly the password. -# -# host = "some.server.host" // others include group1-irc.ircddb.net -# password = "1111111111111" // not needed for rr.openquad.net -} +ircddb_login='Q0ABC' -module = { - c = { // change the module to "b" if you have a 70cm DVAP - type = "dvap" - serial_number = "AP123456" // your serial number is visible through the case - frequency = 145.5 // this is the default value, chose a quiet frequency -# uncomment and set if you want the following to appear on you ircddb host website. -# range = 0.0 // in meters (1609.344 is one mile) -# agl = 0.0 // in meters -# latitude = 0.000000 // north is positive -# longitude = 0.000000 // east is positive -# desc1 = "Location1" // up to 20 chars -# desc2 = "location2" // up to 20 chars - } -} +module_c='dvap' # change to 'module_b' if you have a 70cm dvap +module_c_serial_number='AP123456' # your serial number is visible through the case +module_c_frequency=146.5 # in MHz, chose a quiet frequency -link = { -# add the callsigns that can shutdown or reboot your system -# admin = [ "XX0XXX" , "YY0YYY" ] // only these users can execute scripts - -# link to the reflector of your choice. the first character is the module you are linking. -# link_at_start = "CREF001C" -} - -dplus = { -# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! -# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, -# even if QnetLink reports a successful authorization. - -# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters -# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors -# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters - -# any values specified in you gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. -} +#dplus_authorize=true # uncomment if you want to link to the legacy D-Plus system diff --git a/qn.everything.cfg b/qn.everything.cfg index df3306f..a838e5c 100644 --- a/qn.everything.cfg +++ b/qn.everything.cfg @@ -1,301 +1,239 @@ -# Qnet Gateway Configuration - -ircddb = { - login = "CHANGEME!!!!"; # login callsign for the ircDDB network -# host = "rr.openquad.net" # other include group1-irc.ircddb.net -# port = 9007 # not a good idea to change! -# password = "1111111111111" # not needed for rr.openquad.net -} - -gateway = { -# regen_header = true # regenerate headers from incoming data -# send_qrgs_maps = true # send frequecy, offset, cooridinates and url to irc-server -# aprs_send = true # send info to aprs - - external = { -# ip = "0.0.0.0" -# port = 40000 - } -} - -module = { - a = { # An MMDVMHost module is shown - # make sure this module letter agrees with the [D-Star] Module letter in MMDVM.ini - # in MMDVM.ini its uppercase, here use lower case. - # We show a here, but the convention on this software is: - # 23 cm modules will use "a" - # 70 cm modules will use "b" - # 2 M module will use "c" - type = "mmdvm" -# frequency = 0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage -# offset = 0 -# range = 0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles -# agl = 0 # the height above ground level for this repeater's antenna -# latitude = 0 # you can leave this unspecified for a mobile rig -# longitude = 0 # like the latitude -# desc1 = "" # maximum of 20 characters, most special symbols are not allowed -# desc2 = "" # just like desc1 -# url = "github.com/n7tae/g2_ircddb" # 80 characters max - # In review, for an MMDVM module, you only need to specify the type = "mmdvm" and - # make sure the module letter agrees with what's in "MMDVM.ini. - # Also see the mmdvm section below, although it usually doesn't need specifying. - } - - b = { - # a DVRPTR connected to a 70cm radio is shown in this example - - # type must be defined. This is how the dvrptr program finds the config params. - type = "dvrptr" - - # If you operate in "restriction mode", set callsign equal to your personal callsign - # Otherwise do not set callsign and we will use ircddb.username -# callsign = "" - - # the frequency of your DVRPTR in MHz. -# frequency = 145.5 - - # the TX/RX offset in MHz, use 0.0 for simplex -# offset = 0 - - # the range of this repeater, in meters 1609.344 meters is 1.0 miles -# range = 0.0 - - # the height above ground level for you repeater's antenna -# agl = 0.0 - - # the latitude of your repeater -# latitude = 0.0 - - # the longitude of your repeater -# longitude = 0.0 - - # This is used in a loop to wait for packets from your local gateway - # This is in milliseconds -# packet_wait = 25 - - # description of repeater, part one and two 20 char max each -# desc1 = "" -# desc2 = "" - - # the url of your repeater, 80 chars max, defaults to "github.com/n7tae/g2_ircddb" -# url = "" - - # If you want to enable/disable the repeater, set these options. - # Each of these options can NOT be more than 8 characters. - # Each of these options can NOT be another user's callsign. - # Each of these options can NOT be another repeater or reflector. - # Each of these options can NOT be a YRCALL command. - # If these options are set, then they can NOT be equal to each other. - # Using the above options, if you use YRCALL=RFISOFF in your radio - # then the repeater will be OFF and no audio will be copied over local RF - # and no audio will be accepted from any remote system. - # these command are disabled by default - # You can choose your own command strings, if you want to enable these - rf_control = { -# on = "RFISON" -# off = "RFISOFF" - } - - # To protect the repeater owners from bad STN programs out there - # and to also protect the repeater owners from RF users that abuse the STN stuff - # Reject values in YRCALL that start with STN - # If you want to allow the local RF users to subscribe to remote STN groups, - # then set it to XXX or something that is invalid like 123 -# invalid_prefix = "XXX" - - # Your DVRPTR V1 serial number - # If you don't know what it is, run the program and look in the log file! -# serial_number = "00.00.00.00" - - # Some settings for your DVRPTR modem (see DVRPTR V1 manual for more info). -# rf_rx_level = 80 - - # If you need duplex, set it to true -# duplex = false - - # Do you want an ACK back ? -# acknowledge = false - - # ACK delay in milliseconds(ms) - # Minimum is 1, maximum is 999 - # If you do not get a repeater ACK, then make it a higher number - # Some radios get the ACK in 250ms, other radios require 750ms -# ack_delay = 300 - - # This is the TX-delay in milliseconds and it is used to delay the PTT. - # Seme radios have "SLOW" switching time, - # If your radio switches slow from RX to TX, then give your radio more time to switch, maybe 250 ms - # If your radio switches fast from RX to TX, then you could set it to 100 ms or maybe less - # But the best value should be the one to match your radio, so read the manual for your radio. - # In tests that were done for SLOW and FAST radios, we set it to 100 for FAST radios and 250 for SLOW radios. -# tx_delay = 250 - - # Dead firmware ? - # Lets say that you set RQST_COUNT=10 - # When there is NO local RF activity, (we do NOT receive anything from the DV-RPTR modem), - # then every 2 seconds we request the status from the DV-RPTR modem. - # If the DV-RPTR modem does NOT reply to our command, then after sending the command 10 times(RQST_COUNT) - # we have to assume the firmware in the DV-RPTR modem is DEAD. - # So, we send the command every 2 sedonds, and after sending the command 10 times, - # that is about (2 * 10) = 20 seconds, - # the repeater software will stop if the DV-RPTR modem does not respond after 20 seconds( 2 seconds * 10 times ) - # and then the service script will restart the repeater software. - # This is used to protect the repeater owner from BAD firmware. - # Minimum value is 6. - # If you see in the log this: "Modem is not responding... shuttting down" - # Then that means that the firmware died and the DV-RPTR modem stopped responding. - # You can increase the value of RQST_COUNT if you have a slow computer, - # (or maybe your computer is running too many programs and can not service the USB/serial fast enough) - # but we were informed that this is a bug in the firmware and they are trying to fix it. - # So, increasing the value for RQST_COUNT to higher than 10, does not make much sense. -# rqst_count = 10 - - # These values depend on what type of tranceiver is connected to your DV-RPTR modem - # Use either true or false - inverse = { -# rx = true -# tx = true - } - } - - c = { - # a 2m DVAP is shown as an example - - # type must be defined. This is how the dvap_rptr program finds the config params. - type = "dvap" - - # If you operate in "restriction mode", set RPTR equal to your personal callsign - # Otherwise do not set callsign and it will use ircddb.username -# callsign = "" - - # TX DVAP power level -12 to 10, 10 is maximum poower -# power = 10 - - # Squelch, -128 to -45, -100 to -80 usually works best -# squelch = -100 - - # To protect the repeater owners from bad STN programs out there - # and to also protect the repeater owners from RF users that abuse the STN stuff - # Reject values in YRCALL that start with STN - # If you want to allow the local RF users to subscribe to remote STN groups, - # then set it to XXX or something that is invalid like 123 -# ivalid_prefix = "XXX" - - # The serial number of you DVAP is visible through the bottom of the case -# serial_number = "APXXXXXX" - - # the DVAP will send a blip to acknowledge a transmission -# acknowledge = false - - # the frequency of your DVAP in MHz. -# frequency = 145.5 - - # this is for tweaking the frequency of your DVAP, see the owner's manual -# dvap_offset = 0 - - # the range of this repeater, in meters 1609.344 meters is 1.0 miles -# range = 0.0 - - # the height above ground level for you repeater's antenna -# agl = 0.0 - - # the latitude of your repeater -# latitude = 0.0 - - # the longitude of your repeater -# longitude = 0.0 - - # This is used in a loop to wait for packets from your local gateway - # This is in milliseconds -# packet_wait = 25 - - # description of repeater, part one and two 20 char max each -# desc1 = "" -# desc2 = "" - - # the url of your repeater, 80 chars max -# url = "github.com/n7tae/QnetGateway" - } -} - -# you only need this mmdvm section if you set non-standard D-Star ports on your MMDVMHost software -mmdvm = { -# these need to be the same as they are in your MMDVM.ini file (in the [D-Star Network] section -# If you change them there, then change them here! -# internal_ip = "0.0.0.0" -# gateway_port = 20010 -# local_port = 20011 -} - -log = { - # debuging and extra logging switches -# qso = false # QSO info goes into the log -# irc = false # IRC debug info -# dtmf = false # DTMF debug info -} - -aprs = { # APRS.NET connect info -# host = "rotate.aprs.net" -# port = 14580 -# interval = 40 -# filter = "" -} - -link = { -# link_at_start = "NONE" # Link to a reflector at startup. -# to link repeater module B to REF001 C, use "BREF001C" -# ref_login = "" # for loging into REF reflectors, if undefined, ircddb.username will be used -# admin = [ "CALL1", "CALL2", "CALL3" ] # only these users can execute scripts, block dongles and reload the gwys.txt - # you probably want you own callsign in the admin list! -# link_unlink = [ "CALL4", "CALL5", "CALL6" ] # if defined, only these users can link and unlink a repeater -# no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist - # if the blacklist is defined (even if it's empty), the link_unlink will not be read -# incoming_ip = "0.0.0.0" # incoming ip address, "0.0.0.0" means accepts all connections. -# ref_port = 20001 # port for REF linking, don't change -# xrf_port = 30001 # port for XRF linking, don't change -# dcs_port = 30051 # port for DCS linking, don't change -# announce = true # do link, unlink, etc. announcements -# acknowledge = true # send text acknowledgment on key-up -# max_dongles = 5 # maximum number of linked hot-spots -} - -dplus = { -# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! -# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, -# even if QnetLink reports a successful authorization. - -# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters -# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors -# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters - -# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. -} - -file = { -# status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services -# DTMF = "/tmp" # -# echotest = "/tmp" # echo dat files will end up here -# qnvoicefile = /tmp/qnvoice.txt # where qnvoice will create the play command -# gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is. -# announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements -} - -timing = { - timeout = { -# echo = 1 # delay time in seconds for echo -# voicemail = 1 # delay time for voicemail -# remote_g2 = 2 # after this many seconds with no packets, we assume the tx is closed -# local_rptr = 1 # local timeout, in seconds - } - - play = { -# wait = 1 # seconds before playback occurs, between 1 and 10 -# delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds - } - - inactivity = { -# a = 0 # unlink repeater if no activity for this many minutes -# b = 0 # zero mean there will be no timer -# c = 0 - } -} +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +######################################################################################################################### +# # +# qn.everything.cfg example configuration file # +# # +######################################################################################################################### +# What follows need to also be valid bash shell variable definitions, therefore: +# No white space on either side of the equal sign (=) +# String values should be quoted if they contain any special chars, including white space +# If a string value is a simple word, it doesn't need to be quoted +# Use the single quote (') for quoting strings, not the double quote(") +# Comments can come after a key=value definition, introduced by a pound-sign (#) +# +# if a definition is commented out, it means that key has that value as its default value. +# You don't need to uncomment it if that value is acceptable. In fact, you can remove the commented +# line altogether to simplify you qn.cfg file! In fact, you can remove all these comments and blank +# lines! + +########################################################################################################################## +# +# IRCDDB - You MUST use a legal callsign for logging into any IRC network +# +# you must specify you legal Callsign to enable QnetGateway +ircddb_login='' # login callsign for the ircDDB network +#ircddb_host='rr.openquad.net' # other irc networks include group1-irc.ircddb.net and group2-irc.ircddb.net +#ircddb_port=9007 # not a good idea to change! +#ircddb_password='1111111111111' # not needed for rr.openquad.net + +########################################################################################################################## +# +# GATEWAY +# +# Very few users will need to specify anything in the 'gateway' section! +#gateway_bool_regen_header=true # regenerate headers from incoming data +#gateway_send_qrgs_maps=true # send frequency, offset, coordinates and url to irc-server +#gateway_local_irc_ip='0.0.0.0' # the local port on the gateway for the IRC tcp socket +#gateway_external_ip='0.0.0.0' # this means accept a connection from any source +#gateway_external_port=40000 # don't change +#gateway_tolink='gate2link' # Unix sockets between qngateway and QnetLink +#gateway_fromlink='link2gate' # all Unix sockets are on the file system, but hidden from view + +########################################################################################################################## +# +# APRS - for tracking users and also this repeater. +# +#aprs_enable=true # uncomment and set this to 'false' if you don't wany the gateway interacting with APRS +#aprs_host='rotate.aprs.net' # the APRS network server +#aprs_port=14580 # and port +#aprs_interval=40 # keep-alive in minutes +#aprs_filter='' # advanced feature + +########################################################################################################################## +# +# LINK - controls the behaviour of QnetLink (qnlink) +# +#link_admin='' # these comma-separated list of users can execute scripts, block dongles, reload the gwys.txt + # if empty, everyone has admin privileges. +#link_link_unlink='' # if defined, comma-separated list of users that can link and unlink a repeater +#link_no_link_unlink='' # if defined, comma-separated list of users that cannot link or unlink, it's a blacklist. + # if a blacklist is defined and not empty, the link_unlink will not be read +#link_incoming_ip='0.0.0.0' # incoming ip address of qnlink, '0.0.0.0' means accepts any connection. +#link_ref_port=20001 # port for REF linking, don't change +#link_xrf_port=30001 # port for XRF linking, don't change +#link_dcs_port=30051 # port for DCS linking, don't change +#link_announce=true # do link, unlink, etc. announcements +#link_acknowledge=true # send text acknowledgment on key-up +#link_max_dongles=5 # maximum number of linked hot-spots + +########################################################################################################################## +# +# Here is an example MMDVM module on 70cm channel B +# +module_b='mmdvm' +#module_b_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_b_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_b_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_b_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_b_acknowledge=false # Do you want an ACK back? +#module_b_ack_delay=250 # millisecond delay before acknowledgement +#module_b_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_b_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_b_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_b_agl=0 # the height above ground level for this repeater's antenna +#module_b_latitude=0 # you can leave this unspecified for a mobile rig +#module_b_longitude=0 # like the latitude +#module_b_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_b_desc2='' # just like desc1 +#module_b_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_b_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_b_url='github.com/n7tae/g2_ircddb' # 80 characters max +# MMDVM - Special parameters when: module_b='mmdvm' +#module_b_internal_ip='0.0.0.0' # where MMDVMHost will find the QnetRelay program +#module_b_gateway_port=20010 # which port will QnetRelay be sending on +#module_b_local_port=20011 # which port will MMDVMHost be sending on + +########################################################################################################################## +# +# Here is an example ICOM Terminal and Access Point (ITAP) module specified on 2m channel C +# +module_c='itap' +#module_c_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_c_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_c_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_c_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_c_acknowledge=false # Do you want an ACK back? +#module_c_ack_delay=250 # millisecond delay before acknowledgement +#module_c_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_c_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_c_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_c_agl=0 # the height above ground level for this repeater's antenna +#module_c_latitude=0 # you can leave this unspecified for a mobile rig +#module_c_longitude=0 # like the latitude +#module_c_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_c_desc2='' # just like desc1 +#module_c_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_c_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_c_url='github.com/n7tae/g2_ircddb' # 80 characters max +# ITAP - Special parameters when: module_c='itap' +#module_c_device='/dev/ttyUSB0' # where the serial-to-USB cable shows up + +########################################################################################################################## +# +# DVAP - Here is an example 2M dvap +# +module_c='itap' +#module_c_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_c_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_c_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_c_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_c_acknowledge=false # Do you want an ACK back? +#module_c_ack_delay=250 # millisecond delay before acknowledgement +#module_c_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_c_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_c_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_c_agl=0 # the height above ground level for this repeater's antenna +#module_c_latitude=0 # you can leave this unspecified for a mobile rig +#module_c_longitude=0 # like the latitude +#module_c_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_c_desc2='' # just like desc1 +#module_c_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_c_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_c_url='github.com/n7tae/g2_ircddb' # 80 characters max +# DVAP - Special parameters when: module_c='dvap' +#module_c_power=10 # TX power level: -12 to 10, 10 is maximum power +#module_c_squelch=-100 # RX Squelch: -128 to -45, -100 to -80 usually works best +#module_c_serial_number='APXXXXXX' # The serial number of your DVAP is visible through the bottom of the case + +########################################################################################################################## +# +# DVRPTR - Here is an example 70cm dvrptr +# +module_b='dvrptr' +#module_b_link_at_start='' # For example, set to 'REF001 C' to link module to reflector 1-charlie when the module starts. +#module_b_inactivity=0 # if no activity for this many minutes unlink any linked reflector. Zero means no timer. +#module_b_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. +#module_b_packet_wait=25 # how many milliseconds to wait on packets in a voicestream +#module_b_acknowledge=false # Do you want an ACK back? +#module_b_ack_delay=250 # millisecond delay before acknowledgement +#module_b_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage +#module_b_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak +#module_b_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles +#module_b_agl=0 # the height above ground level for this repeater's antenna +#module_b_latitude=0 # you can leave this unspecified for a mobile rig +#module_b_longitude=0 # like the latitude +#module_b_desc1='' # maximum of 20 characters, most special symbols are not allowed +#module_b_desc2='' # just like desc1 +#module_b_gate2modem1='gate2modem1' # Unix Sockets between a modem and the gateway +#module_b_modem2gate1='modem2gate1' # 0 is for A, 1 is for B and 2 is for C +#module_b_url='github.com/n7tae/g2_ircddb' # 80 characters max +# DVRPTR - Special parameters when: module_b='dvrptr' +# if you don't know what your DVRPTR serial number is, look in the log file after running qndvrptr +#module_b_serial_number='00.00.00.00' # the DVRPTR serial number +#module_b_rf_on='RFON' # put this in YRCALL to disable the channel +#module_b_rf_off='RFOFF' # put this in YRCALL to enable the channel +#module_b_rf_rx_level=80 # see the DVRPTR V1 manual +#module_b_duplex=false # set to true if the module is duplex +#module_b_tx_delay=250 # milliseconds to allow for switching from rx to tx +#module_b_rqst_count=10 # number of 2-sec intervals before the an unresponsive system is killed +#module_b_inverse_rx=true # if you're not hearing anything, try false +#module_b_inverse_tx=true # if you're not being heard, try false + +########################################################################################################################## +# +# LOGGING - Control extra logging - useful for debugging +# +#log_qso=false # QSO info goes into the log +#log_irc=false # IRC debug info +#log_dtmf=false # DTMF debug info + +########################################################################################################################## +# +# DPLUS - Control of dplus (trust system) linking to repeaters and REF reflectors +# +# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! +# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, +# even if QnetLink reports a successful authorization. +#dplus_authorize=false # to enable, uncomment and set to true to link to DPlus reflectors and/or repeaters +#dplus_ref_login='' # for logging into REF reflectors, if empty, ircddb_login will be used +#dplus_use_reflectors=true # set to false if you are not going to link to DPlus reflectors +#dplus_use_repeaters=true # set to false if you are not going to link to DPlus repeaters +# any gateways in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. + +########################################################################################################################## +# +# FILE - where important QnetGateway files and directories are found. +# +#file_status='/usr/local/etc/rptr_status' # where repeater status info is passed between services +#file_dtmf='/tmp' # where DTMF is decoded +#file_echotest='/tmp' # echo dat files will end up here +#file_qnvoicefile='/tmp/qnvoice.txt' # where qnvoice will create the play command +#file_gwys='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is. +#file_announce_dir='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements + +########################################################################################################################## +# +# TIMINGS - for controlling how to deal with timing issues +# +# most users will not have to override any of these default values +#timing_timeout_echo=1 # seconds before we assume echo has timed out +#timing_timeout_voicemail=1 # seconds before we assume voicemail has timed out +#timing_timeout_remote_g2=2 # after this many seconds with no packets, we assume the tx is closed +#timing_timeout_local_rptr=1 # local repeater timeout, in seconds +#timing_play_wait=1 # seconds before echo or voicemail playback occurs, between 1 and 10 +#timing_play_delay=19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1 or 2 ms diff --git a/qn.icom.cfg b/qn.icom.cfg deleted file mode 100644 index d883923..0000000 --- a/qn.icom.cfg +++ /dev/null @@ -1,152 +0,0 @@ -# Qnet Gateway Configuration - -ircddb = { - login = "CHANGEME!!!!"; # login callsign for the ircDDB network -# host = "rr.openquad.net" # other include group1-irc.ircddb.net -# port = 9007 # not a good idea to change! -# password = "1111111111111" # not needed for rr.openquad.net -} - -gateway = { -# regen_header = true # regenerate headers from incoming data -# send_qrgs_maps = true # send frequecy, offset, cooridinates and url to irc-server -# local_irc_ip = "0.0.0.0" # 0.0.0.0 means accept any incoming connections -# aprs_send = true # send info to aprs -# ip = "127.0.0.1" # where the gateway is running - - external = { -# ip = "0.0.0.0" -# port = 40000 - } - - internal = { -# ip = "172.16.0.20" -# port = 20000 - } -} - -module = { - a = { # an ICOM full stack might consist of up to three module - # Sorry, the 23 cm data module is not yet supported - # 23 cm module will use "a" - # 70 cm module will use "b" - # 2 M module will use "c" -# type = "icom" # you must define at least one module by uncommenting the type -# ip = "172.16.0.1" # all icom modules should have the same IP address -# port = 20000 # all icom modules should have the same UDP port -# frequency = 0 # in MHz, if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage -# offset = 0 -# range = 0 # the range of this repeater, in meters. 1609.344 meters is 1.0 miles -# agl = 0 # the height above ground level for this repeater's antenna -# latitude = 0 # you can leave this unspecified for a mobile rig -# longitude = 0 # like the latitude -# desc1 = "" # maximum of 20 characters, most special symbols are not allowed -# desc2 = "" # just like desc1 -# url = "github.com/n7tae/g2_ircddb" # 80 characters max - } - - b = { -# type = "icom" -# ip = "172.16.0.1" # all icom modules should have the same IP address -# port = 20000 # all icom modules should have the same UDP port -# frequency = 0 -# offset = 0 -# range = 0.0 -# agl = 0.0 -# latitude = 0.0 -# longitude = 0.0 -# desc1 = "" -# desc2 = "" -# url = "github.com/n7tae/g2_ircddb" - } - - c = { -# type = "icom" -# ip = "172.16.0.1" # all icom modules should have the same IP address -# port = 20000 # all icom modules should have the same UDP port -# frequency = 0 -# dvap_offset = 0 -# range = 0.0 -# agl = 0.0 -# latitude = 0.0 -# longitude = 0.0 -# desc1 = "" -# desc2 = "" -# url = "github.com/n7tae/QnetGateway" - } -} - -log = { - # debuging and extra logging switches -# qso = false # QSO info goes into the log -# irc = false # IRC debug info -# dtmf = false # DTMF debug info -} - -aprs = { # APRS.NET connect info -# host = "rotate.aprs.net" -# port = 14580 -# interval = 40 -# filter = "" -} - -link = { -# link_at_start = "NONE" # Link to a reflector at startup. -# to link repeater module B to REF001 C, use "BREF001C" -# ref_login = "" # for loging into REF reflectors, if undefined, ircddb.username will be used -# admin = [ "CALL1", "CALL2", "CALL3" ] # only these users can execute scripts, block dongles and reload the gwys.txt -# # you probabaly want you own callsign in the admin list! -# link_unlink = [ "CALL4", "CALL5", "CALL6" ] # if defined, only these users can link and unlink a repeater -# no_link_unlink = [ "CALL7", "CALL8", "CALL9" ] # if defined, these users cannot link or unlink, it's a blacklist -# # if the blacklist is defined (even if it's empty), the link_unlink will not be read -# incoming_ip = "0.0.0.0" # incoming ip address, "0.0.0.0" means accepts all connections. -# ip = "127.0.0.1" # where g2_link is running -# port = 18997 # port for communications to g2_link -# ref_port = 20001 # port for REF linking, don't change -# xrf_port = 30001 # port for XRF linking, don't change -# dcs_port = 30051 # port for DCS linking, don't change -# announce = true # do link, unlink, etc. announcements -# acknowledge = true # send text acknowledgement on key-up -# max_dongles = 5 # maximum number of linked hotspots -} - -dplus = { -# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! -# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, -# even if QnetLink reports a successful authorization. - -# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters -# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors -# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters - -# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. -} - -file = { -# status = "/usr/local/etc/rptr_status" # where repeater status info is passed between services -# DTMF = "/tmp" # -# echotest = "/tmp" # echo dat files will end up here -# qnvoicefile = /tmp/qnvoice.txt # where qnvoice will create the play command -# gwys = "/usr/local/etc/gwys.txt" # where the list of gateways and reflectors (with ports) is. -# announce_dir = "/usr/local/etc" # where are the *.dat files for the verbal link, unlink, etc. announcements -} - -timing = { - timeout = { -# echo = 1 # delay time in seconds for echo -# voicemail = 1 # delay time for voicemail -# remote_g2 = 2 # after this many seconds with no packets, we assume the tx is closed -# local_rptr = 1 # local timeout, in seconds - } - - play = { -# wait = 1 # seconds before playback occurs, between 1 and 10 -# delay = 19 # microseconds between frames playback, if echo sounds bad, adjust this up or down 1,2 microseconds - } - - inactivity = { -# a = 0 # unlink repeater if no activity for this many minutes -# b = 0 # zero mean there will be no timer -# c = 0 - } -} diff --git a/qn.itap.cfg b/qn.itap.cfg index d8f1e18..8ae92e8 100644 --- a/qn.itap.cfg +++ b/qn.itap.cfg @@ -1,44 +1,7 @@ -# g2_ircddb Configuration for me +# A Simple Configuration File for an ICOM using Terminal and Access Point Mode -ircddb = { - login = "XX0XXX" -# If you are not using rr.openquad.net, you need to specify the host and possibly the password. -# -# host = "some.server.host" // others include group1-irc.ircddb.net -# password = "1111111111111" // not needed for rr.openquad.net -} +ircddb_login='Q0Q0ABC' -module = { - c = { // change the module to "b" if you are operating on the UHF band - type = "itap" -# device = "/dev/ttyUSB0" // if your serial-to-usb cable ends up on another device, then specify here -# uncomment and set if you want the following to appear on you ircddb host website. -# frequency = 145.5 // this is the default value, chose a quiet frequency -# range = 0.0 // in meters (1609.344 is one mile) -# agl = 0.0 // in meters -# latitude = 0.000000 // north is positive -# longitude = 0.000000 // east is positive -# desc1 = "Location1" // up to 20 chars -# desc2 = "location2" // up to 20 chars - } -} +module_c='itap' -link = { -# add the callsigns that can shutdown or reboot your system -# admin = [ "XX0XXX" , "YY0YYY" ] // only these users can execute scripts - -# link to the reflector of your choice. the first character is the module you are linking. -# link_at_start = "CREF001C" -} - -dplus = { -# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! -# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, -# even if QnetLink reports a successful authorization. - -# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters -# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors -# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters - -# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. -} +#dplus_authorize=true # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters diff --git a/qn.mmdvm.cfg b/qn.mmdvm.cfg index 9aa264e..d4484fa 100644 --- a/qn.mmdvm.cfg +++ b/qn.mmdvm.cfg @@ -3,53 +3,15 @@ # Please see qn.everything.cfg for many configurable items. # New-bees beware, it is possible to configure your system # to a non-functional state. Nearly all configure items -# already have good default vaules, but the few below +# already have good default vaules, but the two below # HAVE TO BE SET BY YOU!!! -ircddb = { +ircddb_login='Q0ABC' -# Use your callsign, the default network is QuadNet2 - login = "YOUR CALLSIGN" +module_x='mmdvm' +# Change the "x" to the lowercase equivilent of the module assignment in you MMDVM.qn initialization file. +# Use B in the .ini file and b in this file for 70cm and C and c for 2M. See the qn.everything.cfg file if you want to include +# location data for your repeater/hot-spot. (Location data in your MMDVM.qn ini file will +# not make it to the D-Star network.) -} - -module = { - -# Change the "x" to the lowercase equivilent of the module -# assignment in you MMDVM.qn initialization file. -# Use B and b for 70cm and C and c for 2M. - - x = { - - type = "mmdvm" - -# See the qn.everything.cfg file if you want to include -# location data for your repeater/hot-spot. -# (Location data in your MMDVM.qn ini file will -# not make it to the D-Star network.) - - } -} - -link = { - -# Only callsigns listed in the admin list can execute -# Linux scripts from you radio. Two are already provided: -# _ _ _ _ _ _ R X reboots your system, and -# _ _ _ _ _ _ H X halts it. - - admin = [ "AA0AAA" , "BB1BBB" , "CC3CCC" ] - -} - -dplus = { -# The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! -# You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, -# even if QnetLink reports a successful authorization. - -# authorize = false # uncomment and set to true if you want to use the closed-source DPlus reflectors and/or repeaters -# use_reflectors = true # uncomment and set to false if you are not going to link to DPlus reflectors -# use_repeaters = true # uncomment and set to false if you are not going to link to DPlus repeaters - -# any values specified in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. -} +#dplus_authorize='true' # uncomment if you want to use the closed-source DPlus reflectors and/or repeaters From 6744860651ebe36bec84b4b3505a51166f01cc14 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 28 Dec 2018 11:26:35 -0700 Subject: [PATCH 166/553] tweaks --- qn.dvap.cfg | 2 +- qn.itap.cfg | 2 +- qn.mmdvm.cfg | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/qn.dvap.cfg b/qn.dvap.cfg index 43f04cb..75b4dfe 100644 --- a/qn.dvap.cfg +++ b/qn.dvap.cfg @@ -1,6 +1,6 @@ # A Simple Configuration for a 2M DVAP -ircddb_login='Q0ABC' +ircddb_login='Q1ABC' module_c='dvap' # change to 'module_b' if you have a 70cm dvap module_c_serial_number='AP123456' # your serial number is visible through the case diff --git a/qn.itap.cfg b/qn.itap.cfg index 8ae92e8..04c464f 100644 --- a/qn.itap.cfg +++ b/qn.itap.cfg @@ -1,6 +1,6 @@ # A Simple Configuration File for an ICOM using Terminal and Access Point Mode -ircddb_login='Q0Q0ABC' +ircddb_login='Q1ABC' module_c='itap' diff --git a/qn.mmdvm.cfg b/qn.mmdvm.cfg index d4484fa..c2d3e6d 100644 --- a/qn.mmdvm.cfg +++ b/qn.mmdvm.cfg @@ -3,12 +3,13 @@ # Please see qn.everything.cfg for many configurable items. # New-bees beware, it is possible to configure your system # to a non-functional state. Nearly all configure items -# already have good default vaules, but the two below +# already have good default values, but the two below # HAVE TO BE SET BY YOU!!! -ircddb_login='Q0ABC' +ircddb_login='Q1ABC' module_x='mmdvm' + # Change the "x" to the lowercase equivilent of the module assignment in you MMDVM.qn initialization file. # Use B in the .ini file and b in this file for 70cm and C and c for 2M. See the qn.everything.cfg file if you want to include # location data for your repeater/hot-spot. (Location data in your MMDVM.qn ini file will From 7adfc39cd101d4d21cf6ac8db1c7e1fbb60edb59 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sat, 29 Dec 2018 10:49:57 -0700 Subject: [PATCH 167/553] Makefile improvements --- Makefile | 272 ++++++++++---------------------------- system/qnigateway.service | 12 -- 2 files changed, 68 insertions(+), 216 deletions(-) delete mode 100644 system/qnigateway.service diff --git a/Makefile b/Makefile index 7d229ad..5f96df4 100644 --- a/Makefile +++ b/Makefile @@ -38,40 +38,37 @@ OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap -MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay -DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap -DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr -TAP_PROGRAMS=qngateway qnlink qnremote qnvoice qnitap +BASE_PROGRAMS=qngateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) -mmdvm : $(MDV_PROGRAMS) -dvap : $(DVP_PROGRAMS) -dvrptr : $(DVR_PROGRAMS) -itap : $(TAP_PROGRAMS) +relay : qnrelay +dvap : qndvap +dvrptr : qndvrptr +itap : qnitap -qngateway : $(IRCOBJS) QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o $(IRCOBJS) $(LDFLAGS) -pthread +qngateway : QnetGateway.o aprs.o UnixDgramSocket.o QnetConfigure.o $(IRCOBJS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qnlink QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) -pthread + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread qnrelay : QnetRelay.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qnrelay QnetRelay.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) qnitap : QnetITAP.o Random.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) $(LDFLAGS) -pthread +qndvap : QnetDVAP.o DVAPDongle.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread -qndvrptr : QnetDVRPTR.o $(DSTROBJS) Random.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qndvrptr QnetDVRPTR.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) $(LDFLAGS) +qndvrptr : QnetDVRPTR.o Random.o UnixDgramSocket.o QnetConfigure.o $(DSTROBJS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) qnremote : QnetRemote.o Random.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o qnremote QnetRemote.o Random.o UnixDgramSocket.o QnetConfigure.o $(LDFLAGS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) qnvoice : QnetVoice.o Random.o QnetConfigure.o - g++ $(CPPFLAGS) -o qnvoice QnetVoice.o Random.o QnetConfigure.o $(LDFLAGS) + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) %.o : %.cpp g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ @@ -83,11 +80,12 @@ clean: -include $(DEPS) -install : $(MDV_PROGRAMS) gwys.txt qn.cfg +installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) /bin/cp -f qnremote qnvoice $(BINDIR) /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) + /bin/cp -f defaults $(CFGDIR) /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload @@ -101,106 +99,39 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg systemctl enable qnlink.service systemctl daemon-reload systemctl start qnlink.service + +installrelay : qnrelay ######### QnetRelay ######### - /bin/cp -f qnrelay $(BINDIR) - /bin/cp -f system/qnrelay.service $(SYSDIR) - systemctl enable qnrelay.service + /bin/ln -f qnrelay $(BINDIR)/qnrelay$(MODULE) + /bin/cp -f system/qnrelay$(MODULE).service $(SYSDIR) + systemctl enable qnrelay$(MODULE).service systemctl daemon-reload - systemctl start qnrelay.service + systemctl start qnrelay$(MODULE).service + ######### MMDVMHost ######### -installitap : $(TAP_PROGRAMS) gwys.txt qn.cfg - ######### QnetGateway ######### - /bin/cp -f qngateway $(BINDIR) - /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) - /bin/cp -f system/qngateway.service $(SYSDIR) - systemctl enable qngateway.service - systemctl daemon-reload - systemctl start qngateway.service - ######### QnetLink ######### - /bin/cp -f qnlink $(BINDIR) - /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) - /bin/cp -f exec_?.sh $(CFGDIR) - /bin/cp -f system/qnlink.service $(SYSDIR) - systemctl enable qnlink.service - systemctl daemon-reload - systemctl start qnlink.service +installitap : qnitap ######### QnetITAP ######### - /bin/cp -f qnitap $(BINDIR) - /bin/cp -f system/qnitap.service $(SYSDIR) - systemctl enable qnitap.service - systemctl daemon-reload - systemctl start qnitap.service - -installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg - ######### QnetGateway ######### - /bin/cp -f qnicomgateway $(BINDIR) - /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) - /bin/cp -f system/qnigateway.service $(SYSDIR) - systemctl enable qnigateway.service - systemctl daemon-reload - systemctl start qnigateway.service - ######### QnetLink ######### - /bin/cp -f qnlink $(BINDIR) - /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) - /bin/cp -f exec_?.sh $(CFGDIR) - /bin/cp -f system/qnlink.service $(SYSDIR) - systemctl enable qnlink.service + /bin/ln -f qnitap $(BINDIR)/qnitap$(MODULE) + /bin/cp -f system/qnitap$(MODULE).service $(SYSDIR) + systemctl enable qnitap$(MODULE).service systemctl daemon-reload - systemctl start qnlink.service + systemctl start qnitap$(MODULE).service -installdvap : $(DVP_PROGRAMS) gwys.txt qn.cfg - ######### QnetGateway ######### - /bin/cp -f qngateway $(BINDIR) - /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) - /bin/cp -f system/qngateway.service $(SYSDIR) - systemctl enable qngateway.service - systemctl daemon-reload - systemctl start qngateway.service - ######### QnetLink ######### - /bin/cp -f qnlink $(BINDIR) - /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) - /bin/cp -f exec_?.sh $(CFGDIR) - /bin/cp -f system/qnlink.service $(SYSDIR) - systemctl enable qnlink.service - systemctl daemon-reload - systemctl start qnlink.service +installdvap : qndvap ######### QnetDVAP ######### - /bin/cp -f qndvap $(BINDIR) - /bin/cp -f system/qndvap.service $(SYSDIR) - systemctl enable qndvap.service + /bin/ln -f qndvap $(BINDIR)/qndvap$(MODULE) + /bin/cp -f system/qndvap$(MODULE).service $(SYSDIR) + systemctl enable qndvap$(MODULE).service systemctl daemon-reload - systemctl start qndvap.service + systemctl start qndvap$(MODULE).service -installdvrptr : $(DVR_PROGRAMS) gwys.txt qn.cfg - ######### QnetGateway ######### - /bin/cp -f qngateway $(BINDIR) - /bin/cp -f qnremote qnvoice $(BINDIR) - /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) - /bin/cp -f system/qngateway.service $(SYSDIR) - systemctl enable qngateway.service - systemctl daemon-reload - systemctl start qngateway.service - ######### QnetLink ######### - /bin/cp -f qnlink $(BINDIR) - /bin/cp -f announce/*.dat $(CFGDIR) - /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) - /bin/cp -f exec_?.sh $(CFGDIR) - /bin/cp -f system/qnlink.service $(SYSDIR) - systemctl enable qnlink.service - systemctl daemon-reload - systemctl start qnlink.service +installdvrptr : qndvrptr ######### QnetDVRPTR ######### - /bin/cp -f qndvrptr $(BINDIR) - /bin/cp -f system/qndvrptr.service $(SYSDIR) - systemctl enable qndvrptr.service + /bin/ln -f qndvrptr $(BINDIR)/qndvrptr$(MODULE) + /bin/cp -f system/qndvrptr$(MODULE).service $(SYSDIR) + systemctl enable qndvrptr$(MODULE).service systemctl daemon-reload - systemctl start qndvrptr.service + systemctl start qndvrptr$(MODULE).service installdtmf : qndtmf /bin/ln -s $(shell pwd)/qndtmf $(BINDIR) @@ -209,25 +140,25 @@ installdtmf : qndtmf systemctl daemon-reload systemctl start qndtmf.service -installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM.qn - /bin/cp -f $(MMPATH)/MMDVMHost $(BINDIR) - /bin/ln -s $(shell pwd)/$(MMPATH)/MMDVM.qn $(CFGDIR) - /bin/cp -f system/mmdvm.service $(SYSDIR) - /bin/cp -f system/mmdvm.timer $(SYSDIR) - systemctl enable mmdvm.timer +installmmdvm : $(MMPATH)/MMDVMHost $(MMPATH)/MMDVM$(MODULE).qn + /bin/ln -f $(MMPATH)/MMDVMHost $(BINDIR)/MMDVMHost$(MODULE) + /bin/ln -s $(shell pwd)/$(MMPATH)/MMDVM$(MODULE).qn $(CFGDIR) + /bin/cp -f system/mmdvm$(MODULE).service $(SYSDIR) + /bin/cp -f system/mmdvm.timer $(SYSDIR)/mmdvm$(MODULE).timer + systemctl enable mmdvm$(MODULE).timer systemctl daemon-reload - systemctl start mmdvm.service + systemctl start mmdvm$(MODULE).service uninstallmmdvm : systemctl stop mmdvm.service systemctl disable mmdvm.timer /bin/rm -f $(SYSDIR)/mmdvm.service - /bin/rm -f $(SYSDIR)/mmdvm.timer + /bin/rm -f $(SYSDIR)/mmdvm$(MODULE).timer /bin/rm -f $(BINDIR)/MMDVMHost /bin/rm -f $(CFGDIR)/MMDVM.qn sudo systemctl daemon-reload -uninstall : +uninstallbase : ######### QnetGateway ######### systemctl stop qngateway.service systemctl disable qngateway.service @@ -236,6 +167,7 @@ uninstall : /bin/rm -f $(BINDIR)/qnremote /bin/rm -f $(BINDIR)/qnvoice /bin/rm -f $(CFGDIR)/qn.cfg + /bin/rm -f $(CFGDIR)/defaults ######### QnetLink ######### systemctl stop qnlink.service systemctl disable qnlink.service @@ -245,105 +177,37 @@ uninstall : /bin/rm -f $(CFGDIR)/RPT_STATUS.txt /bin/rm -f $(CFGDIR)/gwys.txt /bin/rm -f $(CFGDIR)/exec_?.sh + +uninstallrelay : ######### QnetRelay ######### - systemctl stop qnrelay.service - systemctl disable qnrelay.service - /bin/rm -f $(SYSDIR)/qnrelay.service - /bin/rm -f $(BINDIR)/qnrelay + systemctl stop qnrelay$(MODULE).service + systemctl disable qnrelay$(MODULE).service + /bin/rm -f $(SYSDIR)/qnrelay$(MODULE).service + /bin/rm -f $(BINDIR)/qnrelay$(MODULE) systemctl daemon-reload uninstallitap : - ######### QnetGateway ######### - systemctl stop qngateway.service - systemctl disable qngateway.service - /bin/rm -f $(SYSDIR)/qngateway.service - /bin/rm -f $(BINDIR)/qngateway - /bin/rm -f $(BINDIR)/qnremote - /bin/rm -f $(BINDIR)/qnvoice - /bin/rm -f $(CFGDIR)/qn.cfg - ######### QnetLink ######### - systemctl stop qnlink.service - systemctl disable qnlink.service - /bin/rm -f $(SYSDIR)/qnlink.service - /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/*.dat - /bin/rm -f $(CFGDIR)/RPT_STATUS.txt - /bin/rm -f $(CFGDIR)/gwys.txt - /bin/rm -f $(CFGDIR)/exec_?.sh ######### QnetITAP ######### - systemctl stop qnitap.service - systemctl disable qnitap.service - /bin/rm -f $(SYSDIR)/qnitap.service - /bin/rm -f $(BINDIR)/qnitap + systemctl stop qnitap$(MODULE).service + systemctl disable qnitap$(MODULE).service + /bin/rm -f $(SYSDIR)/qnitap$(MODULE).service + /bin/rm -f $(BINDIR)/qnitap$(MODULE) systemctl daemon-reload -uninstallicom : - ######### QnetGateway ######### - systemctl stop qnigateway.service - systemctl disable qnigateway.service - /bin/rm -f $(SYSDIR)/qnigateway.service - /bin/rm -f $(BINDIR)/qnigateway - /bin/rm -f $(BINDIR)/qnremote - /bin/rm -f $(BINDIR)/qnvoice - /bin/rm -f $(CFGDIR)/qn.cfg - ######### QnetLink ######### - systemctl stop qnlink.service - systemctl disable qnlink.service - /bin/rm -f $(SYSDIR)/qnlink.service - /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/*.dat - /bin/rm -f $(CFGDIR)/RPT_STATUS.txt - /bin/rm -f $(CFGDIR)/gwys.txt - /bin/rm -f $(CFGDIR)/exec_?.sh - uninstalldvap : - ######### QnetGateway ######### - systemctl stop qngateway.service - systemctl disable qngateway.service - /bin/rm -f $(SYSDIR)/qngateway.service - /bin/rm -f $(BINDIR)/qngateway - /bin/rm -f $(BINDIR)/qnremote - /bin/rm -f $(BINDIR)/qnvoice - /bin/rm -f $(CFGDIR)/qn.cfg - ######### QnetLink ######### - systemctl stop qnlink.service - systemctl disable qnlink.service - /bin/rm -f $(SYSDIR)/qnlink.service - /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/*.dat - /bin/rm -f $(CFGDIR)/RPT_STATUS.txt - /bin/rm -f $(CFGDIR)/gwys.txt - /bin/rm -f $(CFGDIR)/exec_?.sh ######### QnetDVAP ######### - systemctl stop qndvap.service - systemctl disable qndvap.service - /bin/rm -f $(SYSDIR)/qndvap.service - /bin/rm -f $(BINDIR)/qndvap + systemctl stop qndvap$(MODULE).service + systemctl disable qndvap$(MODULE).service + /bin/rm -f $(SYSDIR)/qndvap$(MODULE).service + /bin/rm -f $(BINDIR)/qndvap$(MODULE) systemctl daemon-reload uninstalldvrptr : - ######### QnetGateway ######### - systemctl stop qngateway.service - systemctl disable qngateway.service - /bin/rm -f $(SYSDIR)/qngateway.service - /bin/rm -f $(BINDIR)/qngateway - /bin/rm -f $(BINDIR)/qnremote - /bin/rm -f $(BINDIR)/qnvoice - /bin/rm -f $(CFGDIR)/qn.cfg - ######### QnetLink ######### - systemctl stop qnlink.service - systemctl disable qnlink.service - /bin/rm -f $(SYSDIR)/qnlink.service - /bin/rm -f $(BINDIR)/qnlink - /bin/rm -f $(CFGDIR)/*.dat - /bin/rm -f $(CFGDIR)/RPT_STATUS.txt - /bin/rm -f $(CFGDIR)/gwys.txt - /bin/rm -f $(CFGDIR)/exec_?.sh ######### QnetDVRPTR ######### - systemctl stop qndvrptr.service - systemctl disable qndvrptr.service - /bin/rm -f $(SYSDIR)/qndvrptr.service - /bin/rm -f $(BINDIR)/qndvrptr + systemctl stop qndvrptr$(MODULE).service + systemctl disable qndvrptr$(MODULE).service + /bin/rm -f $(SYSDIR)/qndvrptr$(MODULE).service + /bin/rm -f $(BINDIR)/qndvrptr$(MODULE) systemctl daemon-reload uninstalldtmf : diff --git a/system/qnigateway.service b/system/qnigateway.service deleted file mode 100644 index a4d92b5..0000000 --- a/system/qnigateway.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=QnetIcomGateway -Requires=network.target -After=systemd-user-session.service network.target - -[Service] -Type=simple -ExecStart=/usr/local/bin/qnigateway /usr/local/etc/qn.cfg -Restart=always - -[Install] -WantedBy=multi-user.target From 5c70a67ac41640d103ba30b68ccbf32c4adcf6d3 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 30 Dec 2018 11:27:37 -0700 Subject: [PATCH 168/553] improved assigned_module treatment --- QnetDVAP.cpp | 68 +++++++++++++++++++++++++++++++++++------------ QnetDVRPTR.cpp | 60 ++++++++++++++++++++++++++++++------------ QnetITAP.cpp | 66 ++++++++++++++++++++++++++++++++-------------- QnetRelay.cpp | 71 +++++++++++++++++++++++++++++++++++--------------- 4 files changed, 192 insertions(+), 73 deletions(-) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index d62e5a0..09ddb9d 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -167,16 +167,39 @@ static bool read_config(const char *cfgFile) const std::string estr; // an empty string std::string type; std::string dvap_path("module_"); - dvap_path.append(1, 'a' + assigned_module); - if (cfg.KeyExists(dvap_path)) { - cfg.GetValue(dvap_path, estr, type, 1, 16); - if (type.compare("dvap")) { - fprintf(stderr, "assigned module '%c' type is not 'dvap'\n", 'a' + assigned_module); + if (0 > assigned_module) { + // we need to find the lone dvap module + for (int i=0; i<3; i++) { + std::string test(dvap_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("dvap")) { + fprintf(stderr, "%s = '%s', expecting 'dvap'!\n", test.c_str(), type.c_str()); + return true; + } + dvap_path.assign(test); + assigned_module = i; + break; + } + } + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'dvap' module found\n!"); return true; } } else { - fprintf(stderr, "%s is not defined!\n", dvap_path.c_str()); - return true; + // make sure dvap module is defined + dvap_path.append(1, 'a' + assigned_module); + if (cfg.KeyExists(dvap_path)) { + cfg.GetValue(dvap_path, estr, type, 1, 16); + if (type.compare("dvap")) { + fprintf(stderr, "%s = %s is not 'dvap' type!\n", dvap_path.c_str(), type.c_str()); + return true; + } + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); + return true; + } } RPTR_MOD = 'A' + assigned_module; cfg.GetValue(dvap_path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); @@ -502,33 +525,44 @@ int main(int argc, const char **argv) setvbuf(stdout, NULL, _IOLBF, 0); printf("dvap_rptr VERSION %s\n", VERSION); - if (argc != 3) { - fprintf(stderr, "Usage: %s assigned_module dvap_rptr.cfg\n", argv[0]); + if (argc != 2) { + fprintf(stderr, "Usage: %s dvap_rptr.cfg\n", argv[0]); return 1; } + if ('-' == argv[1][0]) { + printf("\nQnetDVAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", DVAP_VERSION); + printf("QnetDVAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + const char *qn = strstr(argv[0], "qndvap"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qndvap' in %s!\n", argv[0]); + return 1; + } + qn += 6; + switch (argv[1][0]) { - case '0': + case NULL: + assigned_module = -1; + break; case 'a': - case 'A': assigned_module = 0; break; - case '1': case 'b': - case 'B': assigned_module = 1; break; - case '2': case 'c': - case 'C': assigned_module = 2; break; default: - fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be 0, a, A, 1, b, B, 2, c or C\n", argv[1]); + fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be a, b or c\n", argv[1]); return 1; } - if (read_config(argv[2])) { + if (read_config(argv[1])) { printf("Failed to process config file %s\n", argv[2]); return 1; } diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index c9b939e..44520f1 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -1820,15 +1820,39 @@ static bool read_config(const char *cfgFile) const std::string estr; // an empty string std::string type; std::string path("module_"); - path += ('a' + assigned_module); - if (cfg.KeyExists(path)) { - if (cfg.GetValue(path, estr, type, 1, 16) || type.compare("dvrptr")) { - fprintf(stderr, "%s = %s is not 'dvrptr'!\n", path.c_str(), type.c_str()); + if (0 > assigned_module) { + // we need to find the lone dvrptr module + for (int i=0; i<3; i++) { + std::string test(path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("dvrptr")) { + fprintf(stderr, "%s = '%s', expecting 'dvrptr'!\n", test.c_str(), type.c_str()); + return true; + } + path.assign(test); + assigned_module = i; + break; + } + } + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'dvrptr' module found\n!"); return true; } } else { - fprintf(stderr, "module %c is not defined\n", 'a' + assigned_module); - return 1; + // make sure dvrptr module is defined + path.append(1, 'a' + assigned_module); + if (cfg.KeyExists(path)) { + cfg.GetValue(path, estr, type, 1, 16); + if (type.compare("dvrptr")) { + fprintf(stderr, "%s = %s is not 'dvrptr' type!\n", path.c_str(), type.c_str()); + return true; + } + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); + return true; + } } DVRPTR_MOD = 'A' + assigned_module; cfg.GetValue(path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); @@ -2473,33 +2497,37 @@ int main(int argc, const char **argv) setvbuf(stdout, NULL, _IOLBF, 0); printf("dvrptr VERSION %s\n", VERSION); - if (argc != 3) { - fprintf(stderr, "Usage: %s assigned_module dvrptr.cfg\n", argv[0]); + if (argc != 2) { + fprintf(stderr, "Usage: %s dvrptr.cfg\n", argv[0]); return 1; } + if ('-' == argv[1][0]) { + printf("\nQnetDVRPTR Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", DVRPTR_VERSION); + printf("QnetDVRPTR comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + switch (argv[1][0]) { - case '0': + case NULL: + assigned_module = -1; + break; case 'a': - case 'A': assigned_module = 0; break; - case '1': case 'b': - case 'B': assigned_module = 1; break; - case '2': case 'c': - case 'C': assigned_module = 2; break; default: - fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be 0, a, A, 1, b, B, 2, c or C\n", argv[1]); + fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be a, b or c\n", argv[1]); return 1; } - if (read_config(argv[2])) { + if (read_config(argv[1])) { fprintf(stderr, "Failed to process config file %s\n", argv[2]); return 1; } diff --git a/QnetITAP.cpp b/QnetITAP.cpp index f2b1965..c3deeb3 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -464,16 +464,39 @@ bool CQnetITAP::ReadConfig(const char *cfgFile) const std::string estr; // an empty string std::string type; std::string itap_path("module_"); - itap_path.append(1, 'a' + assigned_module); - if (cfg.KeyExists(itap_path)) { - cfg.GetValue(itap_path, estr, type, 1, 16); - if (type.compare("itap")) { - fprintf(stderr, "assigned module %c is not 'itap'\n", 'a' + assigned_module); + if (0 > assigned_module) { + // we need to find the lone itap module + for (int i=0; i<3; i++) { + std::string test(itap_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("itap")) { + fprintf(stderr, "%s = '%s', expecting 'itap'!\n", test.c_str(), type.c_str()); + return true; + } + itap_path.assign(test); + assigned_module = i; + break; + } + } + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'itap' module found\n!"); return true; } } else { - fprintf(stderr, "assigned module %c not defined\n", 'a' + assigned_module); - return true; + // make sure itap module is defined + itap_path.append(1, 'a' + assigned_module); + if (cfg.KeyExists(itap_path)) { + cfg.GetValue(itap_path, estr, type, 1, 16); + if (type.compare("itap")) { + fprintf(stderr, "%s = %s is not 'itap' type!\n", itap_path.c_str(), type.c_str()); + return true; + } + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); + return true; + } } RPTR_MOD = 'A' + assigned_module; @@ -556,41 +579,46 @@ void CQnetITAP::calcPFCS(const unsigned char *packet, unsigned char *pfcs) int main(int argc, const char **argv) { setbuf(stdout, NULL); - if (3 != argc) { - fprintf(stderr, "usage: %s assigned_module path_to_config_file\n", argv[0]); + if (2 != argc) { + fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]); return 1; - } else { - printf("\nQnetITAP Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", ITAP_VERSION); + } + + if ('-' == argv[1][0]) { + printf("\nQnetITAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", ITAP_VERSION); printf("QnetITAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); return 0; } + const char *qn = strstr(argv[0], "qnitap"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qnitap' in %s!\n", argv[0]); + return 1; + } + qn += 6; int assigned_module; switch (argv[1][0]) { - case '0': + case NULL: + assigned_module = -1; + break; case 'a': - case 'A': assigned_module = 0; break; - case '1': case 'b': - case 'B': assigned_module = 1; break; - case '2': case 'c': - case 'C': assigned_module = 2; break; default: - fprintf(stderr, "assigned module must be 0, a, A, 1, b, B, 2, c or C\n"); + fprintf(stderr, "assigned module must be a, b or c\n"); return 1; } CQnetITAP qnitap(assigned_module); - qnitap.Run(argv[2]); + qnitap.Run(argv[1]); printf("%s is closing.\n", argv[0]); diff --git a/QnetRelay.cpp b/QnetRelay.cpp index b09f435..cb57f53 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -355,18 +355,42 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) return true; const std::string estr; // an empty GetDefaultString - std::string type; + std::string mmdvm_path("module_"); - mmdvm_path.append(1, 'a' + assigned_module); - if (cfg.KeyExists(mmdvm_path)) { - cfg.GetValue(mmdvm_path, estr, type, 1, 16); - if (type.compare("mmdvm")) { - fprintf(stderr, "%s = %s is not 'mmdvm' type!\n", mmdvm_path.c_str(), type.c_str()); + std::string type; + if (0 > assigned_module) { + // we need to find the lone mmdvm module + for (int i=0; i<3; i++) { + std::string test(mmdvm_path); + test.append(1, 'a'+i); + if (cfg.KeyExists(test)) { + cfg.GetValue(test, estr, type, 1, 16); + if (type.compare("mmdvm")) { + fprintf(stderr, "%s = '%s', expecting 'mmdvm'!\n", test.c_str(), type.c_str()); + return true; + } + mmdvm_path.assign(test); + assigned_module = i; + break; + } + } + if (0 > assigned_module) { + fprintf(stderr, "Error: no 'mmdvm' module found\n!"); return true; } } else { - fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); - return true; + // make sure mmdvm module is defined + mmdvm_path.append(1, 'a' + assigned_module); + if (cfg.KeyExists(mmdvm_path)) { + cfg.GetValue(mmdvm_path, estr, type, 1, 16); + if (type.compare("mmdvm")) { + fprintf(stderr, "%s = %s is not 'mmdvm' type!\n", mmdvm_path.c_str(), type.c_str()); + return true; + } + } else { + fprintf(stderr, "Module '%c' is not defined.\n", 'a'+assigned_module); + return true; + } } RPTR_MOD = 'A' + assigned_module; @@ -394,41 +418,46 @@ void CQnetRelay::SignalCatch(const int signum) int main(int argc, const char **argv) { setbuf(stdout, NULL); - if (3 != argc) { - fprintf(stderr, "usage: %s assigned_module path_to_config_file\n", argv[0]); + if (2 != argc) { + fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]); return 1; - } else { - printf("\nQnetRelay Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", RELAY_VERSION); + } + + if ('-' == argv[1][0]) { + printf("\nQnetRelay Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", RELAY_VERSION); printf("QnetRelay comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); return 0; } + const char *qn = strstr(argv[0], "qnrelay"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qnrelay' in %s!\n", argv[0]); + return 1; + } + qn += 7; int module; - switch (argv[1][0]) { - case '0': + switch (*qn) { + case NULL: + module = -1; + break; case 'a': - case 'A': module = 0; break; - case '1': case 'b': - case 'B': module = 1; break; - case '2': case 'c': - case 'C': module = 2; break; default: - fprintf(stderr, "assigned module must be 0, a, A, 1, b, B, 2, c or C\n"); + fprintf(stderr, "assigned module must be a, b or c\n"); return 1; } CQnetRelay qnmmdvm(module); - bool trouble = qnmmdvm.Run(argv[2]); + bool trouble = qnmmdvm.Run(argv[1]); printf("%s is closing.\n", argv[0]); From 731614dac6c5dba3dee313ad558dc23abf0fe0e2 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 2 Jan 2019 21:05:50 -0700 Subject: [PATCH 169/553] first configure script --- QnetGateway.cpp | 16 +- configure | 579 ++++++++++++++++++++++++++++++++++++++++++++++++ defaults | 23 +- 3 files changed, 598 insertions(+), 20 deletions(-) create mode 100755 configure diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 047b358..9a64966 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -226,11 +226,11 @@ bool CQnetGateway::read_config(char *cfgFile) cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); - cfg.GetValue(path+"latitude", type, rptr.mod[m].latitude, -90.0, 90.0); - cfg.GetValue(path+"longitude", type, rptr.mod[m].longitude, -180.0, 180.0); - cfg.GetValue(path+"desc1", type, rptr.mod[m].desc1, 0, 20); - cfg.GetValue(path+"desc2", type, rptr.mod[m].desc2, 0, 20); - cfg.GetValue(path+"url", type, rptr.mod[m].url, 0, 80); + cfg.GetValue("gateway_latitude", type, rptr.mod[m].latitude, -90.0, 90.0); + cfg.GetValue("gateway_longitude", type, rptr.mod[m].longitude, -180.0, 180.0); + cfg.GetValue("gateway_desc1", type, rptr.mod[m].desc1, 0, 20); + cfg.GetValue("gateway_desc2", type, rptr.mod[m].desc2, 0, 20); + cfg.GetValue("gateway_url", type, rptr.mod[m].url, 0, 80); // make the long description for the log if (rptr.mod[m].desc1.length()) @@ -246,9 +246,9 @@ bool CQnetGateway::read_config(char *cfgFile) // gateway path.assign("gateway_"); cfg.GetValue(path+"local_irc_ip", estr, local_irc_ip, 7, IP_SIZE); - cfg.GetValue(path+"external.ip", estr, g2_external.ip, 7, IP_SIZE); - cfg.GetValue(path+"external.port", estr, g2_external.port, 1024, 65535); - cfg.GetValue(path+"regen_header", estr, bool_regen_header); + cfg.GetValue(path+"ip", estr, g2_external.ip, 7, IP_SIZE); + cfg.GetValue(path+"port", estr, g2_external.port, 1024, 65535); + cfg.GetValue(path+"header_regen", estr, bool_regen_header); cfg.GetValue(path+"send_qrgs_maps", estr, bool_send_qrgs); cfg.GetValue(path+"tolink", estr, gate2link, 1, FILENAME_MAX); cfg.GetValue(path+"fromlink", estr, link2gate, 1, FILENAME_MAX); diff --git a/configure b/configure new file mode 100755 index 0000000..6f1d44d --- /dev/null +++ b/configure @@ -0,0 +1,579 @@ +#!/bin/bash +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +SetBooleanValue () { + local nvname + local cv + if [ -z $2 ]; then + if [ -z ${!1+x} ]; then + dvname=${1}_d + cv=${!dvname} + else + cv=${!1} + fi + if [[ $cv == [tT]* ]]; then + eval ${1}=false + else + eval ${1}=true + fi + elif [[ "$2" == [tT]* ]]; then + eval ${1}=true + else + eval ${1}=false + fi +} + +EvaluateVar () { + if [ -z ${!1+x} ]; then + if [ -z "${!2}" ]; then + echo "'' " + else + echo "${!2} " + fi + else + if [ -z "${!1}" ]; then + echo "''" + else + echo "${!1}" + fi + fi +} + +EndMenu () { + echo + echo "u to unset the value of key (revert to the default value)." + echo "q to return to the main menu" + read -p "Please input: # omit value to toggle a true/false : " key value +} + +LinkMenu () { + key='' + while [[ "$key" != q* ]];do + clear + echo + echo " Link/D-Plus Menu" + echo + echo " 'CSV' means Comma Separated Values (of callsigns)" + echo " An empty CVS means everybody has permission" + echo -n "ad : CSV of calls that can execute scripts = "; EvaluateVar link_admin{,_d} + echo -n "li : CSV of calls that can link and unlink = "; EvaluateVar link_link_unlink{,_d} + echo -n "n : CSV of calls that cannot link&unlink = "; EvaluateVar link_no_link_unlink{,_d} + echo -n "i : Incoming IP address of QnetLink = "; EvaluateVar link_incoming_ip{,_d} + echo -n "r : UDP port for REF linking = "; EvaluateVar link_ref_port{,_d} + echo -n "x : UDP port for XRF linking = "; EvaluateVar link_xrf_port{,_d} + echo -n "d : DCS port for XRF linking = "; EvaluateVar link_dcs_port{,_d} + echo -n "an : Announce linking = "; EvaluateVar link_announce{,_d} + echo -n "ac : Acknowledge link on each keyup = "; EvaluateVar link_acknowledge{,_d} + echo -n "m : Maximum # of dongles allowed = "; EvaluateVar link_max_dongles{,_d} + echo + echo " Legacy D-Plus Repeaters and Reflectors" + echo -n "au : Authorize Legacy D-Plus Linking = "; EvaluateVar dplus_authorize{,_d} + echo -n "lo : Login call for authorization server = "; EvaluateVar dplus_ref_login{,_d} + echo -n "RF : Add legacy reflectors to gateway list = "; EvaluateVar dplus_use_reflectors{,_d} + echo -n "RP : add legacy repeaters to gateway list = "; EvaluateVar dplus_use_repeaters{,_d} + EndMenu + + if [[ "$key" == ad* ]]; then link_admin="${value^^}" + elif [[ "$key" == li* ]]; then link_link_unlink="${value^^}" + elif [[ "$key" == n* ]]; then link_no_link_unlink="${value^^}" + elif [[ "$key" == i* ]]; then link_incoming_ip="$value" + elif [[ "$key" == r* ]]; then link_ref_port="$value" + elif [[ "$key" == x* ]]; then link_xrf_port="$value" + elif [[ "$key" == d* ]]; then link_dcs_port="$value" + elif [[ "$key" == an* ]]; then SetBooleanValue link_announce "$value" + elif [[ "$key" == ac* ]]; then SetBooleanValue link_acknowledge "$value" + elif [[ "$key" == m* ]]; then link_max_dongles="$value" + elif [[ "$key" == au* ]]; then SetBooleanValue dplus_authorize "$value" + elif [[ "$key" == lo* ]]; then dplus_ref_login="${value^^}" + elif [[ "$key" == RF* ]]; then SetBooleanValue dplus_use_reflectors "$value" + elif [[ "$key" == RP* ]]; then SetBooleanValue dplus_use_repeaters "$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == ad* ]]; then unset link_admin + elif [[ "$value" == li* ]]; then unset link_link_unlink + elif [[ "$value" == n* ]]; then unset link_no_link_unlink + elif [[ "$value" == i* ]]; then unset link_incoming_ip + elif [[ "$value" == r* ]]; then unset link_ref_port + elif [[ "$value" == x* ]]; then unset link_xrf_port + elif [[ "$value" == d* ]]; then unset link_dcs_port + elif [[ "$value" == an* ]]; then unset link_announce + elif [[ "$value" == ac* ]]; then unset link_acknowledge + elif [[ "$value" == m* ]]; then unset link_max_dongles + elif [[ "$value" == au* ]]; then unset dplus_authorize + elif [[ "$value" == lo* ]]; then unset dplus_ref_login + elif [[ "$value" == RF* ]]; then unset dplus_use_reflectors + elif [[ "$value" == RP* ]]; then unset dplus_use_repeaters + fi + fi + done +} + +FileMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " Files/Logs/Timings Menu" + echo + echo " Files and directories" + echo -n "sf : Repeater status file :"; EvaluateVar file_status{,_d} + echo -n "ed : Echo/Voicemail directory :"; EvaluateVar file_echotest{,_d} + echo -n "dd : DTMF directory :"; EvaluateVar file_dtmf{,_d} + echo -n "vf : QnetVoice filename :"; EvaluateVar file_qnvoicefile{,_d} + echo -n "gf : Gateways filename :"; EvaluateVar file_gwys{,_d} + echo -n "ad : Announce directory :"; EvaluateVar file_announce_dir{,_d} + echo + echo " Logging file (in /usr/local/var)" + echo -n "cl : Call(QSO) logging :"; EvaluateVar log_qso{,_d} + echo -n "il : IRC logging :"; EvaluateVar log_irc{,_d} + echo -n "dl : DTMF logging :"; EvaluateVar log_dtmf{,_d} + echo + echo " Timing controls" + echo -n "et : Echo timeout (sec) :"; EvaluateVar timing_timeout_echo{,_d} + echo -n "vt : Voicemail timeout (sec) :"; EvaluateVar timing_timeout_voicemail{,_d} + echo -n "gt : G2 timeout (sec) :"; EvaluateVar timing_timeout_remote_g2{,_d} + echo -n "rt : Repeater timeout (sec) :"; EvaluateVar timing_timeout_local_rptr{,_d} + echo -n "pw : Echo play wait (sec) :"; EvaluateVar timing_play_wait{,_d} + echo -n "pd : Echo play delay (msec) :"; EvaluateVar timing_play_delay{,_d} + EndMenu + + if [[ "$key" == sf* ]]; then file_status="$value" + elif [[ "$key" == ed* ]]; then file_echotest="$value" + elif [[ "$key" == dd* ]]; then file_dtmf="$value" + elif [[ "$key" == vf* ]]; then file_qnvoicefile="$value" + elif [[ "$key" == gf* ]]; then file_gwys="$value" + elif [[ "$key" == ad* ]]; then file_announce_dir="$value" + elif [[ "$key" == cl* ]]; then SetBooleanValue log_qso "$value" + elif [[ "$key" == il* ]]; then SetBooleanValue log_irc "$value" + elif [[ "$key" == dl* ]]; then SetBooleanValue log_dtmf "$value" + elif [[ "$key" == et* ]]; then timing_timeout_echo="$value" + elif [[ "$key" == vt* ]]; then timing_timeout_voicemail="$value" + elif [[ "$key" == gt* ]]; then timing_timeout_remote_g2="$value" + elif [[ "$key" == rt* ]]; then timing_timeout_local_rptr="$value" + elif [[ "$key" == pw* ]]; then timing_play_wait="$value" + elif [[ "$key" == pd* ]]; then timing_play_delay="$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == sf* ]]; then unset file_status + elif [[ "$value" == ed* ]]; then unset file_echotest + elif [[ "$value" == dd* ]]; then unset file_dtmf + elif [[ "$value" == vf* ]]; then unset file_qnvoicefile + elif [[ "$value" == gf* ]]; then unset file_gwys + elif [[ "$value" == ad* ]]; then unset file_announce_dir + elif [[ "$value" == cl* ]]; then unset log_qso + elif [[ "$value" == il* ]]; then unset log_irc + elif [[ "$value" == dl* ]]; then unset log_dtmf + elif [[ "$value" == et* ]]; then unset timing_timeout_echo + elif [[ "$value" == vt* ]]; then unset timing_timeout_voicemail + elif [[ "$value" == gt* ]]; then unset timing_timeout_remote_g2 + elif [[ "$value" == rt* ]]; then unset timing_timeout_local_rptr + elif [[ "$value" == pw* ]]; then unset timing_play_wait + elif [[ "$value" == pd* ]]; then unset timing_play_delay + fi + fi + done +} + +IrcddbMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " IRCDDB Menu" + echo + echo "l : Login - must be a legal callsign = '${ircddb_login}'" + echo -n "h : Hostname for IRC Server = "; EvaluateVar ircddb_host{,_d} + echo -n "po : IRC TCP port = "; EvaluateVar ircddb_port{,_d} + echo -n "pa : IRC Password = "; EvaluateVar ircddb_password{,_d} + EndMenu + + if [[ "$key" == l* ]]; then ircddb_login="${value^^}" + elif [[ "$key" == h* ]]; then ircddb_host="$value" + elif [[ "$key" == po* ]]; then ircddb_port="$value" + elif [[ "$key" == pa* ]]; then ircddb_password="$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == h* ]]; then unset ircddb_host + elif [[ "$value" == po* ]]; then unset ircddb_port + elif [[ "$value" == pa* ]]; then unset ircddb_password + fi + fi + done +} + +GateMenu () { + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " Gateway/APRS Menu" + echo + echo " Gateway Option - default values are usually best" + echo -n "r : Regenerate Headers = "; EvaluateVar gateway_header_regen{,_d} + echo -n "s : Send IRC network Module Info = "; EvaluateVar gateway_send_qrgs_maps{,_d} + echo -n "i : IRC TCP local network address = "; EvaluateVar gateway_local_irc_ip{,_d} + echo -n "p : IRC TCP port number = "; EvaluateVar gateway_port{,_d} + echo -n "t : UNIX socket to QnetLink = "; EvaluateVar gateway_tolink{,_d} + echo -n "f : UNIX socket from QnetLink = "; EvaluateVar gateway_fromlink{,_d} + echo -n "la : Latitude (-90.0 to 90.0) = "; EvaluateVar gateway_latitude{,_d} + echo -n "lo : Longitude (-180.0 to 180.0) = "; EvaluateVar gateway_longitude{,_d} + echo -n "d1 : Description #1 (20 chars max) = "; EvaluateVar gateway_desc1{,_d} + echo -n "d2 : Description #1 (20 chars max) = "; EvaluateVar gateway_desc2{,_d} + echo -n "w ; URL (80 char max) = "; EvaluateVar gateway_url{,_d} + echo + echo " APRS - Repeater/User position tracking" + echo -n "e : Enable APRS Tracking = "; EvaluateVar aprs_enable{,_d} + echo -n "h : APRS hostname = "; EvaluateVar aprs_host{,_d} + echo -n "ap : APRS TCP port number = "; EvaluateVar aprs_port{,_d} + echo -n "k : APRS Keep-alive interval (min) = "; EvaluateVar aprs_interval{,_d} + echo -n "af : APRS Filter (experimental) = "; EvaluateVar aprs_filter{,_d} + EndMenu + + if [[ "$key" == r* ]]; then SetBooleanValue gateway_header_regen "$value" + elif [[ "$key" == s* ]]; then SetBooleanValue gateway_send_qrgs_maps "$value" + elif [[ "$key" == i* ]]; then gateway_local_irc_ip="$value" + elif [[ "$key" == p* ]]; then gateway_port="$value" + elif [[ "$key" == t* ]]; then gateway_tolink="$value" + elif [[ "$key" == f* ]]; then gateway_fromlink="$value" + elif [[ "$key" == la* ]]; then gateway_latitude="$value" + elif [[ "$key" == lo* ]]; then gateway_longitude="$value" + elif [[ "$key" == d1* ]]; then gateway_desc1="${value:0:20}" + elif [[ "$key" == d2* ]]; then gateway_desc2="${value:0:20}" + elif [[ "$key" == w* ]]; then gateway_url="${value:0:80}" + elif [[ "$key" == e* ]]; then SetBooleanValue aprs_enable "$value" + elif [[ "$key" == h* ]]; then aprs_host="$value" + elif [[ "$key" == ap* ]]; then aprs_port="$value" + elif [[ "$key" == k* ]]; then aprs_interval="$value" + elif [[ "$key" == af* ]]; then aprs_filter="$value" + elif [[ "$key" == u* ]]; then + if [[ "$value" == h* ]]; then unset gateway_header_regen + elif [[ "$value" == s* ]]; then unset gateway_send_qrgs_maps + elif [[ "$value" == l* ]]; then unset gateway_local_irc_ip + elif [[ "$value" == i* ]]; then unset gateway_ip + elif [[ "$value" == p* ]]; then unset gateway_port + elif [[ "$value" == t* ]]; then unset gateway_tolink + elif [[ "$value" == f* ]]; then unset gateway_fromlink + elif [[ "$value" == la* ]]; then unset gateway_latitude + elif [[ "$value" == lo* ]]; then unset gateway_longitude + elif [[ "$value" == d1* ]]; then unset gateway_desc1 + elif [[ "$value" == d2* ]]; then unset gateway_desc2 + elif [[ "$value" == w* ]]; then unset gateway_url + elif [[ "$value" == e* ]]; then unset aprs_enable + elif [[ "$value" == h* ]]; then unset aprs_host + elif [[ "$value" == ap* ]]; then unset aprs_port + elif [[ "$value" == k* ]]; then unset aprs_interval + elif [[ "$value" == af* ]]; then unset aprs_filter + fi + fi + done +} + +ModuleMenu () { + mod=module_${1} + if [[ $1 == a ]]; then + nmod=0 + elif [[ $1 == b ]]; then + nmod=1 + else + nmod=2 + fi + clear + if [ -z ${!mod} ]; then + echo + echo " Select a Module type" + echo + echo "1 : DVAP Dongle" + echo "2 : DVRPTR V1" + echo "3 : ICOM Terminal and Access Point Mode" + echo "4 : MMDVMHost-based Sytem" + echo + echo " Anything else will return without selecting" + echo + echo -n "Select Module Type : " + read key unused + if [[ "$key" == 1 ]]; then eval ${mod}=dvap + elif [[ "$key" == 2 ]]; then eval ${mod}=dvrptr + elif [[ "$key" == 3 ]]; then eval ${mod}=itap + elif [[ "$key" == 4 ]]; then eval ${mod}=mmdvm + else return + fi + fi + key='' + while [[ "$key" != q* ]]; do + clear + echo + echo " Module ${1^^} Menu ($mod=${!mod})" + echo + echo -n "ls : Link at startup (must be 8 chars) = "; EvaluateVar {${mod},module_x}_link_at_start + echo -n "cs : Callsign (uses ircddb_login if empty) = "; EvaluateVar {${mod},module_x}_callsign + echo -n "fr : Frequency in MHz = "; EvaluateVar {${mod},module_x}_frequency + echo -n "of : Offset in Hz = "; EvaluateVar {${mod},module_x}_offset + echo -n "ra : Range (in meters, 1 mile=1609.344 meters) = "; EvaluateVar {${mod},module_x}_range + echo -n "ag : Above ground level (in meters) = "; EvaluateVar {${mod},module_x}_agl + echo -n "uf : UNIX Socket from gateway = "; EvaluateVar {${mod},module_x}_gate2modem$nmod + echo -n "ut : UNIX Socket from gateway = "; EvaluateVar {${mod},module_x}_modem2gate$nmod + echo -n "in : Inactivity for this many minutes unlinks = "; EvaluateVar {${mod},module_x}_inactivity + echo -n "wa : Wait this many msec for the next packet = "; EvaluateVar {${mod},module_x}_packet_wait + echo -n "ac : Send acknowledgment on each transmission = "; EvaluateVar {${mod},module_x}_acknowledge + echo -n "ad : acknowledgment delay (in msec) = "; EvaluateVar {${mod},module_x}_ack_delay + if [[ "${!mod}" == dvap ]]; then + echo -n "po : Power (in dBm from -12 to 10) = "; EvaluateVar {${mod},dvap}_power + echo -n "sq : Squelch (in dBm from -128 to -45) = "; EvaluateVar {${mod},dvap}_squelch + echo -n "sn : Serial # (visible through the case) = "; EvaluateVar {${mod},dvap}_serial_number + elif [[ "${!mod}" == dvrptr ]]; then + echo -n "sn : Serial # (run once and look in log) = "; EvaluateVar {${mod},dvrptr}_serial_number + echo -n "rn : Callsign to turn RF on = "; EvaluateVar {${mod},dvrptr}_rf_on + echo -n "rf : Callsign to turn RF off = "; EvaluateVar {${mod},dvrptr}_rf_off + echo -n "rl : Receiver level = "; EvaluateVar {${mod},dvrptr}_rx_level + echo -n "du : Is duplex = "; EvaluateVar {${mod},dvrptr}_duplex + echo -n "td : Transmitter delay (in msec) for tx/rx switch = "; EvaluateVar {${mod},dvrptr}_tx_delay + echo -n "rq : # of 2 sec interval before system reset = "; EvaluateVar {${mod},dvrptr}_rqst_count + echo -n "ir : Inverse phase of receiver = "; EvaluateVar {${mod},dvrptr}_inverse_rx + echo -n "it : Inverse phase of transmitter = "; EvaluateVar {${mod},dvrptr}_inverse_tx + elif [[ "${!mod}" == itap ]]; then + echo -n "dv : USB device path = "; EvaluateVar {${mod},itap}_device + elif [[ "${!mod}" == mmdvm ]]; then + echo -n "ip : Internal IP address = "; EvaluateVar {${mod},mmdvm}_internal_ip + echo -n "gp : Gateway port number = "; EvaluateVar {${mod},mmdvm}_gateway_port + echo -n "lp : Local port number = "; EvaluateVar {${mod},mmdvm}_local_port + fi + echo "xx : Delete this module" + EndMenu + + if [[ "$key" == ls* ]]; then + value="${value:0:8}" + eval ${mod}_link_at_start="'${value^^}'" + elif [[ "$key" == cs* ]]; then eval ${mod}_callsign="${value^^}" + elif [[ "$key" == fr* ]]; then eval ${mod}_frequency="$value" + elif [[ "$key" == of* ]]; then eval ${mod}_offset="$value" + elif [[ "$key" == ra* ]]; then eval ${mod}_range="$value" + elif [[ "$key" == ag* ]]; then eval ${mod}_agl="$value" + elif [[ "$key" == uf* ]]; then eval ${mod}_gate2modem${nmod}="$value" + elif [[ "$key" == ut* ]]; then eval ${mod}_modem2gate${nmod}="$value" + elif [[ "$key" == in* ]]; then eval ${mod}_inactivity="$value" + elif [[ "$key" == wa* ]]; then eval ${mod}_packet_wait="$value" + elif [[ "$key" == ac* ]]; then SetBooleanValue ${mod}_acknowledge "$value" + elif [[ "$key" == ad* ]]; then eval ${mod}_ack_delay="$value" + elif [[ "$key" == po* ]]; then eval ${mod}_power="$value" + elif [[ "$key" == sq* ]]; then eval ${mod}_squelch="$value" + elif [[ "$key" == sn* ]]; then eval ${mod}_serial_number="$value" + elif [[ "$key" == rn* ]]; then eval ${mod}_rf_on="$value" + elif [[ "$key" == rf* ]]; then eval ${mod}_rf_off="$value" + elif [[ "$key" == rl* ]]; then eval ${mod}_rx_level="$value" + elif [[ "$key" == du* ]]; then SetBooleanValue ${mod}_duplex "$value" + elif [[ "$key" == td* ]]; then eval ${mod}_tx_delay="$value" + elif [[ "$key" == rq* ]]; then eval ${mod}_rqst_count="$value" + elif [[ "$key" == ir* ]]; then SetBooleanValue ${mod}_inverse_rx "$value" + elif [[ "$key" == it* ]]; then SetBooleanValue ${mod}_inverse_tx "$value" + elif [[ "$key" == dv* ]]; then eval ${mod}_device="$value" + elif [[ "$key" == ip* ]]; then eval ${mod}_internal_ip="$value" + elif [[ "$key" == gp* ]]; then eval ${mod}_gateway_port="$value" + elif [[ "$key" == lp* ]]; then eval ${mod}_local_port="$value" + elif [[ "$key" == xx* ]]; then + unset ${mod}_{link_at_start,callsign,frequency,offset,range,agl,{gate2modem,modem2gate}${nmod}} + unset ${mod}_{inactivity,packet_wait,acknowledge,ack_delay,power,squelch,serial_number,rf_o{n,ff},rx_level} + unset ${mod}_{duplex,tx_delay,rqst_count,inverse_{t,r}x,device,internal_ip,{gateway,local}_port} + unset ${mod} + return + elif [[ "$key" == u* ]]; then + if [[ "$value" == ls* ]]; then unset ${mod}_link_at_start + elif [[ "$value" == cs* ]]; then unset ${mod}_callsign + elif [[ "$value" == fr* ]]; then unset ${mod}_frequency + elif [[ "$value" == of* ]]; then unset ${mod}_offset + elif [[ "$value" == ra* ]]; then unset ${mod}_range + elif [[ "$value" == ag* ]]; then unset ${mod}_agl + elif [[ "$value" == uf* ]]; then unset ${mod}_gate2modem${nmod} + elif [[ "$value" == ut* ]]; then unset ${mod}_modem2gate${nmod} + elif [[ "$value" == in* ]]; then unset ${mod}_inactivity + elif [[ "$value" == wa* ]]; then unset ${mod}_packet_wait + elif [[ "$value" == ac* ]]; then unset ${mod}_acknowledge + elif [[ "$value" == ad* ]]; then unset ${mod}_ack_delay + elif [[ "$value" == po* ]]; then unset ${mod}_power + elif [[ "$value" == sq* ]]; then unset ${mod}_squelch + elif [[ "$value" == sn* ]]; then unset ${mod}_serial_number + elif [[ "$value" == rn* ]]; then unset ${mod}_rf_on + elif [[ "$value" == rf* ]]; then unset ${mod}_rf_off + elif [[ "$value" == rl* ]]; then unset ${mod}_rx_level + elif [[ "$value" == du* ]]; then unset ${mod}_duplex + elif [[ "$value" == td* ]]; then unset ${mod}_tx_delay + elif [[ "$value" == rq* ]]; then unset ${mod}_rqst_count + elif [[ "$value" == ir* ]]; then unset ${mod}_inverse_rx + elif [[ "$value" == it* ]]; then unset ${mod}_inverse_tx + elif [[ "$value" == dv* ]]; then unset ${mod}_device + elif [[ "$value" == ip* ]]; then unset ${mod}_internal_ip + elif [[ "$value" == gp* ]]; then unset ${mod}_gateway_port + elif [[ "$value" == lp* ]]; then unset ${mod}_local_port + fi + fi + done +} + +WriteCFGFile () { + local m n p q outFile + outFile='./qn.cfg' + echo "# Created on `date`" > $outFile + # gateway_ section + echo "ircddb_login='$ircddb_login'" >> $outFile + [ -z "${ircddb_host+x}" ] || echo "ircddb_host='${ircddb_host}'" >> $outFile + [ -z "${ircddb_port+x}" ] || echo "ircddb_host=${ircddb_port}" >> $outFile + [ -z "${ircddb_password+x}" ] || echo "ircddb_password='${ircddb_password}'" >> $outFile + + # module_?_ section + for m in a b c + do + if [[ "$m" == a ]]; then + n=0 + elif [[ "$m" == b ]]; then + n=1 + else + n=2 + fi + p="module_$m" + if [ -n "${!p}" ]; then + echo "${p}=${!p}" >> $outFile + q=${p}_link_at_start; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_callsign; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_frequency; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_range; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_agl; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_gate2modem${n}; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_modem2gate${n}; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_inactivity; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_packet_wait; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_acknowledge; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_ack_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + if [[ "${!p}" == "dvap" ]]; then + q=${p}_power; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_squelch; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + elif [[ "${!p}" == "dvrptr" ]]; then + q=${p}_serial_number; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_rf_on; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_rf_off; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_rx_level; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_duplex; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_tx_delay; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_rqst_count; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_inverse_rx; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_inverse_tx; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + elif [[ "${!p}" == "itap" ]]; then + q=${p}_device; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + elif [[ "${!p}" == "mmdvm" ]]; then + q=${p}_internal_ip; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_gateway_port; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + q=${p}_local_port; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile + fi + fi + done + # gateway_section + [ -z "${gateway_header_regen+x}" ] || echo "gateway_header_regen=${gateway_header_regen}" >> $outFile + [ -z "${gateway_send_qrgs_maps+x}" ] || echo "gateway_send_qrgs_maps=${gateway_send_qrgs_maps}" >> $outFile + [ -z "${gateway_local_irc_ip+x}" ] || echo "gateway_local_irc_ip='${gateway_local_irc_ip}'" >> $outFile + [ -z "${gateway_port+x}" ] || echo "gateway_port=${gateway_port}" >> $outFile + [ -z "${gateway_tolink+x}" ] || echo "gateway_tolink=${gateway_tolink}" >> $outFile + [ -z "${gateway_fromlink+x}" ] || echo "gateway_fromlink=${gateway_fromlink}" >> $outFile + [ -z "${gateway_latitude+x}" ] || echo "gateway_latitude=${gateway_latitude}" >> $outFile + [ -z "${gateway_longitude+x}" ] || echo "gateway_longitude=${gateway_longitude}" >> $outFile + [ -z "${gateway_desc1+x}" ] || echo "gateway_desc1='${gateway_desc1}'" >> $outFile + [ -z "${gateway_desc2+x}" ] || echo "gateway_desc2='${gateway_desc2}'" >> $outFile + [ -z "${gateway_url+x}" ] || echo "gateway_url='${gateway_url}'" >> $outFile + # arps_ section + [ -z "${aprs_enable+x}" ] || echo "aprs_enable='{aprs_enable}" >> $outFile + [ -z "${aprs_host+x}" ] || echo "aprs_host='${aprs_host}'" >> $outFile + [ -z "${aprs_port+x}" ] || echo "aprs_port=${aprs_port}" >> $outFile + [ -z "${aprs_interval+x}" ] || echo "aprs_interval=${aprs_interval}" >> $outFile + [ -z "${aprs_filter+x}" ] || echo "aprs_filter='${aprs_filter}'" >> $outFile + # link_ section + [ -z "${link_admin+x}" ] || echo "link_admin='${link_admin}'" >> $outFile + [ -z "${link_link_unlink+x}" ] || echo "link_link_unlink='${link_link_unlink}'" >> $outFile + [ -z "${link_no_link_unlink+x}" ] || echo "link_no_link_unlink='${link_no_link_unlink}'" >> $outFile + [ -z "${link_incoming_ip+x}" ] || echo "link_incoming_ip='${link_incoming_ip}'" >> $outFile + [ -z "${link_ref_port+x}" ] || echo "link_ref_port=${link_ref_port}" >> $outFile + [ -z "${link_xrf_port+x}" ] || echo "link_xrf_port=${link_xrf_port}" >> $outFile + [ -z "${link_dcs_port+x}" ] || echo "link_dcs_port=${link_dcs_port}" >> $outFile + [ -z "${link_announce+x}" ] || echo "link_announce=${link_announce}" >> $outFile + [ -z "${link_acknowledge+x}" ] || echo "link_acknowledge=${link_acknowledge}" >> $outFile + [ -z "${link_max_dongles+x}" ] || echo "link_max_dongles=${link_max_dongles}" >> $outFile + # log_ section + [ -z "${log_qso+x}" ] || echo "log_qso=${log_qso}" >> $outFile + [ -z "${log_irc+x}" ] || echo "log_irc=${log_irc}" >> $outFile + [ -z "${log_dtmf+x}" ] || echo "log_dtmf=${log_dtmf}" >> $outFile + # dplus_ section + [ -z "${dplus_authorize+x}" ] || echo "dplus_authorize=${dplus_authorize}" >> $outFile + [ -z "${dplus_ref_login+x}" ] || echo "dplus_ref_login='${dplus_ref_login}'" >> $outFile + [ -z "${dplus_use_repeaters+x}" ] || echo "dplus_use_repeaters=${dplus_use_repeaters}" >> $outFile + [ -z "${dplus_use_reflectors+x}" ] || echo "dplus_use_reflectors=${dplus_use_reflectors}" >> $outFile + # file_ section + [ -z "${file_gwys+x}" ] || echo "file_gwys='${file_gwys}'" >> $outFile + [ -z "${file_dtmf+x}" ] || echo "file_dtmf='${file_dtmf}'" >> $outFile + [ -z "${file_status+x}" ] || echo "file_status='${file_status}'" >> $outFile + [ -z "${file_echotest+x}" ] || echo "file_echotest='${file_echotest}'" >> $outFile + [ -z "${file_qnvoicefile+x}" ] || echo "file_qnvoicefile='${file_qnvoicefile}'" >> $outFile + [ -z "${file_announce_dir+x}" ] || echo "file_announce_dir='${file_announce_dir}'" >> $outFile + # timing_ section + [ -z "${timing_timeout_echo+x}" ] || echo "timing_timeout_echo=${timing_timeout_echo}" >> $outFile + [ -z "${timing_timeout_remote_g2+x}" ] || echo "timing_timeout_remote_g2=${timing_timeout_remote_g2}" >> $outFile + [ -z "${timing_timeout_voicemail+x}" ] || echo "timing_timeout_voicemail=${timing_timeout_voicemail}" >> $outFile + [ -z "${timing_timeout_local_rptr+x}" ] || echo "timing_timeout_local_rptr=${timing_timeout_local_rptr}" >> $outFile + [ -z "${timing_play_wait+x}" ] || echo "timing_play_wait=${timing_play_wait}" >> $outFile + [ -z "${timing_play_delay+x}" ] || echo "timing_play_delay=${timing_play_delay}" >> $outFile +} + +# source files +if [ -e ./defaults ]; then + source ./defaults +else + echo 'Error: ./defaults not found!' + exit 1 +fi +if [ -e ./qn.cfg ]; then + source ./qn.cfg +else + echo 'No configuration file found...' + sleep 1 +fi + +# main loop +while [[ "$ans" != q* ]] +do + clear + echo + echo " Main Menu" + echo + echo -n "a : Module A - "; if [ -z $module_a ]; then echo ""; else echo "${module_a^^}"; fi + echo -n "b : Module B - "; if [ -z $module_b ]; then echo ""; else echo "${module_b^^}"; fi + echo -n "c : Module C - "; if [ -z $module_c ]; then echo ""; else echo "${module_c^^}"; fi + echo "i : IRCDDB Menu - login = '${ircddb_login}'" + echo "g : Gateway/APRS Menu - default values are usually fine" + echo "l : Link/D-Plus Menu - linking access" + echo "f : Files/Logs/Timings - miscellaneous parameters" + echo + if [ -n $module_a ] || [ -n $module_b ] || [ -n $module_c ] && [ -n $ircddb_login ]; then + echo "w : Write qn.cfg configuration file (overwrites any existing file)" + echo + fi + read -p "q to quit: " ans + + if [[ "$ans" == a* ]]; then ModuleMenu a + elif [[ "$ans" == b* ]]; then ModuleMenu b + elif [[ "$ans" == c* ]]; then ModuleMenu c + elif [[ "$ans" == i* ]]; then IrcddbMenu + elif [[ "$ans" == g* ]]; then GateMenu + elif [[ "$ans" == l* ]]; then LinkMenu + elif [[ "$ans" == f* ]]; then FileMenu + elif [[ "$ans" == w* ]]; then WriteCFGFile + fi +done +exit 0 diff --git a/defaults b/defaults index 4ca3b34..1f3fc01 100644 --- a/defaults +++ b/defaults @@ -42,13 +42,17 @@ ircddb_password_d='1111111111111' # not needed for rr.openquad.net # # GATEWAY # -gateway_bool_regen_header_d=true # regenerate headers from incoming data +gateway_header_regen_d=true # regenerate headers from incoming data gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server -gateway_local_irc_ip_d='0.0.0.0' # the local port on the gateway for the IRC tcp socket -gateway_external_ip_d='0.0.0.0' # this means accept a connection from any source -gateway_external_port_d=40000 # don't change +gateway_local_irc_ip_d='0.0.0.0' # the local port on the gateway for the IRC TCP socket +gateway_port_d=40000 # don't change gateway_tolink_d='gate2link' # Unix sockets between qngateway and QnetLink gateway_fromlink_d='link2gate' # all Unix sockets are on the file system, but hidden from view +gateway_latitude_d=0 # you can leave this unspecified for a mobile rig +gateway_longitude_d=0 # like the latitude +gateway_desc1_d='' # maximum of 20 characters, most special symbols are not allowed +gateway_desc2_d='' # just like desc1 +gateway_url_d='github.com/n7tae/QnetGateway' # 80 characters max ########################################################################################################################## # @@ -80,27 +84,22 @@ link_max_dongles_d=5 # maximum number of linked hot-spots # # GENERIC MODULE - These will be defined for any and all defined modules # -#module_x_link_at_start='' # For example, set to 'REF001 C' to link module to 1-charlie when the module starts. +module_x_link_at_start='' # For example, set to 'REF001 C' to link module to 1-charlie when the module starts. module_x_inactivity=0 # if no activity for this many minutes unlink reflector. Zero means no timer. module_x_callsign='' # if you operate in a 'restriction mode', use your personal callsign. Usually leave this empty. module_x_packet_wait=25 # how many milliseconds to wait on packets in a voicestream module_x_acknowledge=false # Do you want an ACK back? -module_x_ack_delay=250 # millisecond delay before acknowledgement +module_x_ack_delay=250 # millisecond delay before acknowledgment module_x_frequency=0 # if you specify here, this frequency will show up on the QuadNet USER GATEWAYS webpage module_x_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak module_x_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles module_x_agl=0 # the height above ground level for this repeater's antenna -module_x_latitude=0 # you can leave this unspecified for a mobile rig -module_x_longitude=0 # like the latitude -module_x_desc1='' # maximum of 20 characters, most special symbols are not allowed -module_x_desc2='' # just like desc1 module_x_gate2modem0='gate2modem0' # Unix Sockets between a modem and the gateway module_x_gate2modem1='gate2modem1' # 0 is for A, 1 is for B and 2 is for C module_x_gate2modem2='gate2modem2' module_x_modem2gate0='modem2gate0' module_x_modem2gate1='modem2gate1' module_x_modem2gate2='modem2gate2' -module_x_url='github.com/n7tae/g2_ircddb' # 80 characters max ########################################################################################################################## # @@ -154,8 +153,8 @@ log_dtmf_d=false # DTMF debug info # The following settings do not affect your ability to use dplus linking to XRF or XLX reflectors! # You must be registered on the DPlus system, see www.dstargateway.org, otherwise authorization will fail, # even if QnetLink reports a successful authorization. -dplus_ref_login_d='' # for logging into REF reflectors, if empty, ircddb_login will be used dplus_authorize_d=false # set to true if you want to use the closed-source DPlus reflectors and/or repeaters +dplus_ref_login_d='' # for logging into REF reflectors, if empty, ircddb_login will be used dplus_use_reflectors_d=true # set to false if you are not going to link to DPlus reflectors dplus_use_repeaters_d=true # set to false if you are not going to link to DPlus repeaters # any gateways in your gwys.txt file will override any reflectors or repeaters that DPlus authorization returns. From e72ed87b2fe8f2b36945b015e595e479bdf7a9f6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 2 Jan 2019 21:07:07 -0700 Subject: [PATCH 170/553] added qn.cfg to the ignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7593a15..52c0f32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.o *.d *.gch +qn.cfg qnitap qndvap qndvrptr From 5b1396ac419bdad2744af5fc652dd5a1a89d6d5f Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 3 Jan 2019 09:34:34 -0700 Subject: [PATCH 171/553] changed unix port names between gateway and modems --- QnetDVAP.cpp | 4 ++-- QnetDVRPTR.cpp | 4 ++-- QnetGateway.cpp | 4 ++-- QnetITAP.cpp | 6 +++--- QnetRelay.cpp | 4 ++-- configure | 13 +++---------- defaults | 12 ++++++------ 7 files changed, 20 insertions(+), 27 deletions(-) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 09ddb9d..8b9b267 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -202,8 +202,8 @@ static bool read_config(const char *cfgFile) } } RPTR_MOD = 'A' + assigned_module; - cfg.GetValue(dvap_path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(dvap_path+"_modem2gate"+std::to_string(assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue(dvap_path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); + cfg.GetValue(dvap_path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); if (cfg.KeyExists(dvap_path+"_callsign")) { if (cfg.GetValue(dvap_path+"_callsign", type, RPTR, 3, 6)) return true; diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index 44520f1..c9fc4cb 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -1855,8 +1855,8 @@ static bool read_config(const char *cfgFile) } } DVRPTR_MOD = 'A' + assigned_module; - cfg.GetValue(path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(path+"_modem2gate"+std::to_string(assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue(path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); + cfg.GetValue(path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); std::string call; if (cfg.GetValue("ircddb_login", type, call, 3, 6)) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 9a64966..a2767f3 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -220,8 +220,8 @@ bool CQnetGateway::read_config(char *cfgFile) rptr.mod[m].defined = true; path.append(1, '_'); - cfg.GetValue(path+"togateway", type, modem2gate[m], 1, FILENAME_MAX); - cfg.GetValue(path+"fromgateway", type, gate2modem[m], 1, FILENAME_MAX); + cfg.GetValue(path+"modem2gate"+std::string(1, 'a'+m), type, modem2gate[m], 1, FILENAME_MAX); + cfg.GetValue(path+"gate2modem"+std::string(1, 'a'+m), type, gate2modem[m], 1, FILENAME_MAX); cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0e12); cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); diff --git a/QnetITAP.cpp b/QnetITAP.cpp index c3deeb3..f2db07b 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -198,7 +198,7 @@ void CQnetITAP::Run(const char *cfgfile) int ug2m = Gate2Modem.GetFD(); int um2g = Modem2Gate.GetFD(); - printf("gate2modem=%d, modem2gate=%d seral=%d\n", ug2m, um2g, serfd); + printf("gate2modem=%d, modem2gate=%d serial=%d\n", ug2m, um2g, serfd); keep_running = true; unsigned poll_counter = 0; @@ -501,8 +501,8 @@ bool CQnetITAP::ReadConfig(const char *cfgFile) RPTR_MOD = 'A' + assigned_module; cfg.GetValue(itap_path+"_device", type, ITAP_DEVICE, 7, FILENAME_MAX); - cfg.GetValue(itap_path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(itap_path+"_modem2gate"+std::to_string(assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue(itap_path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); + cfg.GetValue(itap_path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); itap_path.append("_callsign"); if (cfg.KeyExists(itap_path)) { diff --git a/QnetRelay.cpp b/QnetRelay.cpp index cb57f53..81ca076 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -394,8 +394,8 @@ bool CQnetRelay::ReadConfig(const char *cfgFile) } RPTR_MOD = 'A' + assigned_module; - cfg.GetValue(mmdvm_path+"_gate2modem"+std::to_string(assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(mmdvm_path+"_modem2gate"+std::to_string(assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue(mmdvm_path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); + cfg.GetValue(mmdvm_path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); cfg.GetValue(mmdvm_path+"_internal_ip", type, MMDVM_IP, 7, IP_SIZE); int i; cfg.GetValue(mmdvm_path+"_local_port", type, i, 10000, 65535); diff --git a/configure b/configure index 6f1d44d..4193014 100755 --- a/configure +++ b/configure @@ -420,7 +420,7 @@ ModuleMenu () { } WriteCFGFile () { - local m n p q outFile + local m p q outFile outFile='./qn.cfg' echo "# Created on `date`" > $outFile # gateway_ section @@ -432,13 +432,6 @@ WriteCFGFile () { # module_?_ section for m in a b c do - if [[ "$m" == a ]]; then - n=0 - elif [[ "$m" == b ]]; then - n=1 - else - n=2 - fi p="module_$m" if [ -n "${!p}" ]; then echo "${p}=${!p}" >> $outFile @@ -448,8 +441,8 @@ WriteCFGFile () { q=${p}_offset; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_range; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_agl; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile - q=${p}_gate2modem${n}; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile - q=${p}_modem2gate${n}; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_gate2modem${m}; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile + q=${p}_modem2gate${m}; [ -z ${!q+x} ] || echo "${q}='${!q}'" >> $outFile q=${p}_inactivity; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_packet_wait; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile q=${p}_acknowledge; [ -z ${!q+x} ] || echo "${q}=${!q}" >> $outFile diff --git a/defaults b/defaults index 1f3fc01..6a1c212 100644 --- a/defaults +++ b/defaults @@ -94,12 +94,12 @@ module_x_frequency=0 # if you specify here, this frequency will show up on module_x_offset=0 # usually the duplex tx-rx offset, but for dvap, it's a frequency tweak module_x_range=0 # the range of this repeater, in meters 1609.344 meters is 1.0 miles module_x_agl=0 # the height above ground level for this repeater's antenna -module_x_gate2modem0='gate2modem0' # Unix Sockets between a modem and the gateway -module_x_gate2modem1='gate2modem1' # 0 is for A, 1 is for B and 2 is for C -module_x_gate2modem2='gate2modem2' -module_x_modem2gate0='modem2gate0' -module_x_modem2gate1='modem2gate1' -module_x_modem2gate2='modem2gate2' +module_x_gate2modem0='gate2modema' # Unix Sockets between a modem and the gateway +module_x_gate2modem1='gate2modemb' +module_x_gate2modem2='gate2modemc' +module_x_modem2gate0='modem2gatea' +module_x_modem2gate1='modem2gateb' +module_x_modem2gate2='modem2gatec' ########################################################################################################################## # From 36873fbd3adfe3b4104ca90cc82aebb9805f5865 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Tue, 8 Jan 2019 22:25:17 -0700 Subject: [PATCH 172/553] qnadmin started --- BUILDING | 42 ++++++--- Makefile | 12 +-- README.md | 6 +- qnadmin | 196 ++++++++++++++++++++++++++++++++++++++++++ configure => qnconfig | 2 +- 5 files changed, 240 insertions(+), 18 deletions(-) create mode 100755 qnadmin rename configure => qnconfig (99%) diff --git a/BUILDING b/BUILDING index 677b684..5d5943a 100644 --- a/BUILDING +++ b/BUILDING @@ -30,11 +30,17 @@ already have all or most of these but it still doesn't hurt to be sure: sudo apt-get update sudo apt-get upgrade -sudo apt-get install make g++ unzip git libconfig++-dev +sudo apt-get install make g++ unzip git If you are building a QnetGateway + MMDVMHost system, please use the instructions in the MMDVM.README file. If you are building a QnetGateway for an Icom repeater, -use the qn.icom.cfg configuration file as a starting point for your configuration. +you'll need to switch branches of this repository after downloading it (see below): + +git checkout lastudp + +Then you can use the qn.icom.cfg configuration file as a starting point for your +configuration. + If you are building a QnetGateway for a DVAP or a DVRPTR_V1 read on... git clone git://github.com/n7tae/QnetGateway.git @@ -44,7 +50,6 @@ dvrptr ircddb gateway. The first thing to do is change to the build directory with "cd QnetGateway" and then choose a target to make. There are targets for each of the supported devices: -. "make icom" will build all programs needed for the Icom repeater. . "make itap" will build all programs needed for Icom Terminal mode. . "make dvap" will build all programs needed for the DVAP Dongle. . "make dvrptr" will build all programs needed for the DVRPTR_V1. @@ -53,20 +58,37 @@ choose a target to make. There are targets for each of the supported devices: . "make" will build all the QnetGateway executables. This is useful if you are experimenting around with lots of different devices. -Next, create your qn.cfg configuration file. There are three example for you to look -at: -. qn.everything.cfg contains all parameter with lengthly comments about what +Next, create your qn.cfg configuration file. Use the menu-driven configuration +script. Type: + +./configure + +You can configure up to three different modules: a, b and/or c. + +The configure script will show you the default values of every parameter the +QnetGateway programs use. In most cases, the defaults are just fine and you don't +need to override them with your own values. Mostly you need to specify you callsign +for the IRC login, specify at least one module and most users will want to enable +the D-Plus legacy authorization. For a DVAP Dongle or a DVRPTR V1, there are some +parameters that must be specified, like the serial number. After you are happy with +your configuration, be sure to write it out. After you install and try out your +system, you may find that you need to change some configuration values. In that +case just start the configure script again. It will read the current qn.cfg file +when it start. + +Of course, you can always build your own qn.cfg file. There are three example for +you to look at: +. qn.everything.cfg contains all parameter with comments about what each parameter does. The definitions that are commented out are defined with their default value. . qn.dvap.cfg is the simplest possible configuration for a 2m DVAP. If you have a 70cm DVAP rename the module to "b" and change the frequency. -. qn.icom.cfg is the starting place for configuring an Icom repeater. Please note - that QnetGateway doesn't support the 23cm data only module in the Icom repeater - stack. +. qn.mmdvm.cfg is the starting place for configuring an MMDVMHost repeater. Be sure + the module assignment agrees with the module configured in your MMDVM.cfg file. . qn.itap.cfg is a simple configuration file for Icom's Terminal and Access Point Mode. Please read ITAP.README for more information. -Remeber the everything file or the icom file contain detailed comments about all of +Remeber the 'everything' file file contain detailed comments about all of the values you can set. Just read through it and edit accordingly. In the end you will need a configuration file called "qn.cfg". diff --git a/Makefile b/Makefile index 5f96df4..b93b78d 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload - systemctl start qngateway.service +# systemctl start qngateway.service ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) @@ -98,7 +98,7 @@ installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service systemctl daemon-reload - systemctl start qnlink.service +# systemctl start qnlink.service installrelay : qnrelay ######### QnetRelay ######### @@ -106,7 +106,7 @@ installrelay : qnrelay /bin/cp -f system/qnrelay$(MODULE).service $(SYSDIR) systemctl enable qnrelay$(MODULE).service systemctl daemon-reload - systemctl start qnrelay$(MODULE).service +# systemctl start qnrelay$(MODULE).service ######### MMDVMHost ######### installitap : qnitap @@ -115,7 +115,7 @@ installitap : qnitap /bin/cp -f system/qnitap$(MODULE).service $(SYSDIR) systemctl enable qnitap$(MODULE).service systemctl daemon-reload - systemctl start qnitap$(MODULE).service +# systemctl start qnitap$(MODULE).service installdvap : qndvap ######### QnetDVAP ######### @@ -123,7 +123,7 @@ installdvap : qndvap /bin/cp -f system/qndvap$(MODULE).service $(SYSDIR) systemctl enable qndvap$(MODULE).service systemctl daemon-reload - systemctl start qndvap$(MODULE).service +# systemctl start qndvap$(MODULE).service installdvrptr : qndvrptr ######### QnetDVRPTR ######### @@ -131,7 +131,7 @@ installdvrptr : qndvrptr /bin/cp -f system/qndvrptr$(MODULE).service $(SYSDIR) systemctl enable qndvrptr$(MODULE).service systemctl daemon-reload - systemctl start qndvrptr$(MODULE).service +# systemctl start qndvrptr$(MODULE).service installdtmf : qndtmf /bin/ln -s $(shell pwd)/qndtmf $(BINDIR) diff --git a/README.md b/README.md index 2adf1ab..a81b4dc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,11 @@ The QnetGateway is an D-Star IRCDDB gateway application that supports MMDVMHost The Qnet Gateway program now includes support for Icom's new Terminal mode. Access Point mode is still having some performance issues and we will be working on this. For more information, please read the ITAP.README file. -For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file. +For building a QnetGateway + MMDVMHost system, see the MMDVM.README file. To build QnetGateway that uses a DVAP Dongle or DVRPTR V1, see the BUILDING file. To build QnetGateway for an Icom Repeater Stack, switch to another branch after cloning the repository: + +``` +git checkout lastudp +``` To get started, clone the software to your Linux device: diff --git a/qnadmin b/qnadmin new file mode 100755 index 0000000..b05b7e4 --- /dev/null +++ b/qnadmin @@ -0,0 +1,196 @@ +#!/bin/bash +# +# Copyright (c) 2019 by Thomas A. Early N7TAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +InstallSystem () { + make ${1}installbase + if [ $ndvap .gt 0 ]; then + if [ $ndvap .eq 1 ]; then + make ${1}installdvap + else + make MODULE=$advap[0] ${1}installdvap + make MODULE=$advap[1] ${1}installdvap + if [ $ndvap .eq 3 ]; then + make MODULE=$advap[2] ${1}installdvap + endif + fi + fi + if [ $ndvrptr .gt 0 ]; then + if [ $ndvrptr .eq 1 ]; then + make ${1}installdvrptr + else + make MODULE=$advrptr[0] ${1}installdvrptr + make MODULE=$advrptr[1] ${1}installdvrptr + if [ $ndvrptr .eq 3 ]; then + make MODULE=$advrptr[2] ${1}installdvrptr + endif + fi + fi + if [ $nitap .gt 0 ]; then + if [ $nitap .eq 1 ]; then + make ${1}installitap + else + make MODULE=$aitap[0] ${1}installitap + make MODULE=$aitap[1] ${1}installitap + if [ $nitap .eq 3 ]; then + make MODULE=$aitap[2] ${1}installitap + endif + fi + fi + if [ $nmmdvm .gt 0 ]; then + if [ $nmmdvm .eq 1 ]; then + make ${1}installrelay + else + make MODULE=$ammdvm[0] ${1}installrelay + make MODULE=$ammdvm[1] ${1}installrelay + if [ $nmmdvm .eq 3 ]; then + make MODULE=$advap[2] ${1}installrelay + endif + fi + fi +} + +BaseStatus () { + ActiveGate=$( systemctl show -p ActiveState --value qngateway ) + SubGate=$( systemctl show -p SubState --value qngateway ) + ActiveLink=$( systemctl show -p ActiveState --value qnlink ) + SubLink=$( systemctl show -p SubState --value qnlink ) + ActiveDTMF=$( systemctl show -p ActiveState --value qndtmf ) + SubDTMF=$( systemctl show -p SubState --value qndtmf ) + echo -n "QnetGateway " + if [[ "$ActiveGate" == "inactive" ]]; then + echo "is not installed" + else + echo "ActiveState os $ActiveGate SubState is $SubGate" + fi + echo -n "QnetLink " + if [[ "$ActiveLink" == "inactive" ]]; then + echo "is not installed" + else + echo "ActiveState is $ActiveLink SubState is $SubLink" + fi + echo -n "DTMF " + if [[ "$ActiveDTMF" == "inactive" ]]; then + echo "is not installed" + else + echo "ActiveState is $ActiveDTMF SubState is $SubDTMF" + fi +} + +ModuleStatus () { + if [ -z ${3} ]; then + echo "Module ${2^^} - EMPTY" + else + mcvar="n${3}" + if [[ ${!mcar} > 1 ]]; then + process="${3}${2}" + else + process="${3}" + fi + ActiveState=$( systemctl show -p ActiveState --value $process ) + SubState=$( systemctl show -p SubState --value $process ) + echo -n "Module ${2^^} - ${3^^}" + if [[ "$ActiveState" == "inactive" ]]; then + echo " is not installed" + else + echo " ActiveState is $ActiveState SubState is $SubState" + fi + fi +} + +# get defined modules from the config file +if [ -e qn.cfg ]; then + source <( grep "^module_[abc]=" qn.cfg ) + if [ -z "$module_a" ] && [ -z "$module_b" ] && [ -z "$module_c" ]; then + echo "No moudules defined in the qn.cfg file!" + exit 1 + fi +else + "ERROR: can't find the qn.cfg file" + exit 1 +fi + +# get the installation directory from the make file +if [ -e makefile ]; then + MAKEFILE=makefile +elif [ -e Makefile ]; then + MAKEFILE=Makefile +else + echo "ERROR: There is no Makefile or makefile" + exit 1 +fi +source <( grep "^BINDIR=" $MAKEFILE ) +if [ -z $BINDIR ]; then + echo "ERROR: The BINDIR definition in $MAKEFILE is empty!" + exit 1 +fi +if [ ! -d "$BINDIR" ]; then + echo "ERROR: The BINDIR directory $BINDIR is not a directory!" + exit 1 +fi + +ndvap=0 +ndvrptr=0 +nitap=0 +nmmdvm=0 + +for m in a b c ; do + mod=module_${m} + if [ -z ${!mod} ]; then continue; fi + type=${!mod} + if [[ "$type" == 'dvap' ]]; then + advap[${ndvap}]=${m} + ndvap=$((ndvap + 1)) + elif [[ "$type" == 'dvrptr' ]]; then + advrptr[$ndvap]=${m} + ndvrptr=$((ndvrptr + 1)) + elif [[ "$type" == 'itap' ]]; then + aitap[${nitap}]=${m} + nitap=$((nitap + 1)) + elif [[ "$type" == 'mmdvm' ]]; then + ammdvm[${nmmdvm}]=${m} + nmmdvm=$((nmmdvm + 1)) + fi +done + +MODULE_COUNT=$((ndvap + ndvrptr + nitap + nmmdvm)) + +while [[ "$ans" != q* ]]; do + clear + echo + echo " Qnet Administration Menu" + echo + BaseStatus + ModuleStatus 0 a "$module_a" + ModuleStatus 1 b "$module_b" + ModuleStatus 2 c "$module_c" + echo + if [[ "$ActiveGate" == "inactive" ]] || [[ "$ActiveLink" == "inactive" ]]; then + echo "i : Install configured system" + echo "d : Install DTMF" + fi + echo + read -p "q to quit. Command: " ans + + # EXECUTE COMMANDS + if [[ "$ans" == i* ]]; then + InstallSystem + elif [[ "$ans" == d* ]]; then + make installdtmf + fi +done + +exit 0 diff --git a/configure b/qnconfig similarity index 99% rename from configure rename to qnconfig index 4193014..f40c461 100755 --- a/configure +++ b/qnconfig @@ -62,7 +62,7 @@ EndMenu () { LinkMenu () { key='' - while [[ "$key" != q* ]];do + while [[ "$key" != q* ]]; do clear echo echo " Link/D-Plus Menu" From 0c56f919483ffbb20525fde86fd6085fa97ac75f Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 9 Jan 2019 08:55:46 -0700 Subject: [PATCH 173/553] endif? --- qnadmin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qnadmin b/qnadmin index b05b7e4..e7b9a72 100755 --- a/qnadmin +++ b/qnadmin @@ -25,7 +25,7 @@ InstallSystem () { make MODULE=$advap[1] ${1}installdvap if [ $ndvap .eq 3 ]; then make MODULE=$advap[2] ${1}installdvap - endif + fi fi fi if [ $ndvrptr .gt 0 ]; then @@ -36,7 +36,7 @@ InstallSystem () { make MODULE=$advrptr[1] ${1}installdvrptr if [ $ndvrptr .eq 3 ]; then make MODULE=$advrptr[2] ${1}installdvrptr - endif + fi fi fi if [ $nitap .gt 0 ]; then @@ -47,7 +47,7 @@ InstallSystem () { make MODULE=$aitap[1] ${1}installitap if [ $nitap .eq 3 ]; then make MODULE=$aitap[2] ${1}installitap - endif + fi fi fi if [ $nmmdvm .gt 0 ]; then @@ -58,7 +58,7 @@ InstallSystem () { make MODULE=$ammdvm[1] ${1}installrelay if [ $nmmdvm .eq 3 ]; then make MODULE=$advap[2] ${1}installrelay - endif + fi fi fi } From 132e76884921827af28c8ce4d9887c803c785f02 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 9 Jan 2019 09:32:21 -0700 Subject: [PATCH 174/553] better InstallSystem --- Makefile | 1 + qnadmin | 64 +++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index b93b78d..2ce6ef1 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap BASE_PROGRAMS=qngateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) +base : $(BASE_PROGRAMS) relay : qnrelay dvap : qndvap dvrptr : qndvrptr diff --git a/qnadmin b/qnadmin index e7b9a72..85e4f2c 100755 --- a/qnadmin +++ b/qnadmin @@ -16,48 +16,65 @@ # along with this program. If not, see . InstallSystem () { - make ${1}installbase - if [ $ndvap .gt 0 ]; then + local n + if [ -z == ${1} ]; then + n=$( grep processor /proc/cpuinfo | wc -l ) + make base -j$n + fi + sudo make ${1}installbase + if [ $ndvap -gt 0 ]; then + if [ -z == ${1} ]; then + make qndvap -j$n + fi if [ $ndvap .eq 1 ]; then - make ${1}installdvap + sudo make ${1}installdvap else - make MODULE=$advap[0] ${1}installdvap - make MODULE=$advap[1] ${1}installdvap + sudo make MODULE=$advap[0] ${1}installdvap + sudo make MODULE=$advap[1] ${1}installdvap if [ $ndvap .eq 3 ]; then - make MODULE=$advap[2] ${1}installdvap + sudo make MODULE=$advap[2] ${1}installdvap fi fi fi - if [ $ndvrptr .gt 0 ]; then + if [ $ndvrptr -gt 0 ]; then + if [ -z == ${1} ]; then + make qndvrptr -j$n + fi if [ $ndvrptr .eq 1 ]; then - make ${1}installdvrptr + sudo make ${1}installdvrptr else - make MODULE=$advrptr[0] ${1}installdvrptr - make MODULE=$advrptr[1] ${1}installdvrptr + sudo make MODULE=$advrptr[0] ${1}installdvrptr + sudo make MODULE=$advrptr[1] ${1}installdvrptr if [ $ndvrptr .eq 3 ]; then - make MODULE=$advrptr[2] ${1}installdvrptr + sudo make MODULE=$advrptr[2] ${1}installdvrptr fi fi fi - if [ $nitap .gt 0 ]; then + if [ $nitap -gt 0 ]; then + if [ -z == ${1} ]; then + make qnitap -j$n + fi if [ $nitap .eq 1 ]; then - make ${1}installitap + sudo make ${1}installitap else - make MODULE=$aitap[0] ${1}installitap - make MODULE=$aitap[1] ${1}installitap + sudo make MODULE=$aitap[0] ${1}installitap + sudo make MODULE=$aitap[1] ${1}installitap if [ $nitap .eq 3 ]; then - make MODULE=$aitap[2] ${1}installitap + sudo make MODULE=$aitap[2] ${1}installitap fi fi fi - if [ $nmmdvm .gt 0 ]; then + if [ $nmmdvm -gt 0 ]; then + if [ -z == ${1} ]; then + make qnrelay -j$n + fi if [ $nmmdvm .eq 1 ]; then - make ${1}installrelay + sudo make ${1}installrelay else - make MODULE=$ammdvm[0] ${1}installrelay - make MODULE=$ammdvm[1] ${1}installrelay + sudo make MODULE=$ammdvm[0] ${1}installrelay + sudo make MODULE=$ammdvm[1] ${1}installrelay if [ $nmmdvm .eq 3 ]; then - make MODULE=$advap[2] ${1}installrelay + sudo make MODULE=$advap[2] ${1}installrelay fi fi fi @@ -182,6 +199,7 @@ while [[ "$ans" != q* ]]; do echo "i : Install configured system" echo "d : Install DTMF" fi + echo "c : Clean (remove temporary files and locally build executibles)" echo read -p "q to quit. Command: " ans @@ -189,7 +207,9 @@ while [[ "$ans" != q* ]]; do if [[ "$ans" == i* ]]; then InstallSystem elif [[ "$ans" == d* ]]; then - make installdtmf + sudo make installdtmf + elif [[ "$ans" == c* ]]; then + make clean fi done From 8bc8ad9984e678322441f68a08ad06b2d385b317 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 9 Jan 2019 09:39:10 -0700 Subject: [PATCH 175/553] missed some binary ops --- qnadmin | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/qnadmin b/qnadmin index 85e4f2c..79e8e2e 100755 --- a/qnadmin +++ b/qnadmin @@ -19,6 +19,7 @@ InstallSystem () { local n if [ -z == ${1} ]; then n=$( grep processor /proc/cpuinfo | wc -l ) + echo "Detected $n processors for make" make base -j$n fi sudo make ${1}installbase @@ -26,12 +27,12 @@ InstallSystem () { if [ -z == ${1} ]; then make qndvap -j$n fi - if [ $ndvap .eq 1 ]; then + if [ $ndvap -eq 1 ]; then sudo make ${1}installdvap else sudo make MODULE=$advap[0] ${1}installdvap sudo make MODULE=$advap[1] ${1}installdvap - if [ $ndvap .eq 3 ]; then + if [ $ndvap -eq 3 ]; then sudo make MODULE=$advap[2] ${1}installdvap fi fi @@ -40,12 +41,12 @@ InstallSystem () { if [ -z == ${1} ]; then make qndvrptr -j$n fi - if [ $ndvrptr .eq 1 ]; then + if [ $ndvrptr -eq 1 ]; then sudo make ${1}installdvrptr else sudo make MODULE=$advrptr[0] ${1}installdvrptr sudo make MODULE=$advrptr[1] ${1}installdvrptr - if [ $ndvrptr .eq 3 ]; then + if [ $ndvrptr -eq 3 ]; then sudo make MODULE=$advrptr[2] ${1}installdvrptr fi fi @@ -54,12 +55,12 @@ InstallSystem () { if [ -z == ${1} ]; then make qnitap -j$n fi - if [ $nitap .eq 1 ]; then + if [ $nitap -eq 1 ]; then sudo make ${1}installitap else sudo make MODULE=$aitap[0] ${1}installitap sudo make MODULE=$aitap[1] ${1}installitap - if [ $nitap .eq 3 ]; then + if [ $nitap -eq 3 ]; then sudo make MODULE=$aitap[2] ${1}installitap fi fi @@ -68,12 +69,12 @@ InstallSystem () { if [ -z == ${1} ]; then make qnrelay -j$n fi - if [ $nmmdvm .eq 1 ]; then + if [ $nmmdvm -eq 1 ]; then sudo make ${1}installrelay else sudo make MODULE=$ammdvm[0] ${1}installrelay sudo make MODULE=$ammdvm[1] ${1}installrelay - if [ $nmmdvm .eq 3 ]; then + if [ $nmmdvm -eq 3 ]; then sudo make MODULE=$advap[2] ${1}installrelay fi fi From 2149e116cce920b7553c3eb5bfdb03164bb98d41 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 9 Jan 2019 09:45:28 -0700 Subject: [PATCH 176/553] further qnadmin fixes --- qnadmin | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/qnadmin b/qnadmin index 79e8e2e..f3b6a25 100755 --- a/qnadmin +++ b/qnadmin @@ -17,14 +17,14 @@ InstallSystem () { local n - if [ -z == ${1} ]; then + if [ -z ${1} ]; then n=$( grep processor /proc/cpuinfo | wc -l ) echo "Detected $n processors for make" make base -j$n fi sudo make ${1}installbase if [ $ndvap -gt 0 ]; then - if [ -z == ${1} ]; then + if [ -z ${1} ]; then make qndvap -j$n fi if [ $ndvap -eq 1 ]; then @@ -38,7 +38,7 @@ InstallSystem () { fi fi if [ $ndvrptr -gt 0 ]; then - if [ -z == ${1} ]; then + if [ -z ${1} ]; then make qndvrptr -j$n fi if [ $ndvrptr -eq 1 ]; then @@ -52,7 +52,7 @@ InstallSystem () { fi fi if [ $nitap -gt 0 ]; then - if [ -z == ${1} ]; then + if [ -z ${1} ]; then make qnitap -j$n fi if [ $nitap -eq 1 ]; then @@ -66,7 +66,7 @@ InstallSystem () { fi fi if [ $nmmdvm -gt 0 ]; then - if [ -z == ${1} ]; then + if [ -z ${1} ]; then make qnrelay -j$n fi if [ $nmmdvm -eq 1 ]; then @@ -198,6 +198,7 @@ while [[ "$ans" != q* ]]; do echo if [[ "$ActiveGate" == "inactive" ]] || [[ "$ActiveLink" == "inactive" ]]; then echo "i : Install configured system" + echo "u : Uninstall System" echo "d : Install DTMF" fi echo "c : Clean (remove temporary files and locally build executibles)" @@ -207,6 +208,8 @@ while [[ "$ans" != q* ]]; do # EXECUTE COMMANDS if [[ "$ans" == i* ]]; then InstallSystem + elif [[ "$ans" == u* ]]; then + InstallSystem un elif [[ "$ans" == d* ]]; then sudo make installdtmf elif [[ "$ans" == c* ]]; then From cf3f3f31de5a6802a079a27b8ab8642b61847a1d Mon Sep 17 00:00:00 2001 From: Tom Early Date: Wed, 9 Jan 2019 12:41:21 -0700 Subject: [PATCH 177/553] It is 'defaults', not 'qndefaults' --- QnetConfigure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp index 04c56c6..025a52b 100644 --- a/QnetConfigure.cpp +++ b/QnetConfigure.cpp @@ -68,7 +68,7 @@ bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map Date: Wed, 9 Jan 2019 13:06:24 -0700 Subject: [PATCH 178/553] config debugging --- QnetConfigure.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp index 025a52b..2d5cbd6 100644 --- a/QnetConfigure.cpp +++ b/QnetConfigure.cpp @@ -56,6 +56,7 @@ bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map Date: Wed, 9 Jan 2019 13:40:11 -0700 Subject: [PATCH 179/553] more changes for config --- QnetConfigure.cpp | 3 +-- defaults | 12 ++++++------ qnadmin | 9 ++++++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp index 2d5cbd6..200e388 100644 --- a/QnetConfigure.cpp +++ b/QnetConfigure.cpp @@ -45,7 +45,7 @@ bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map Date: Thu, 10 Jan 2019 06:31:02 -0700 Subject: [PATCH 180/553] read_config bugs --- QnetLink.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 5d3cbfa..ae1af44 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -521,16 +521,9 @@ bool CQnetLink::read_config(const char *cfgFile) ToUpper(owner); owner.resize(CALL_SIZE, ' '); - if (cfg.GetValue("dplus_ref_login", estr, login_call, 3, 6)) - login_call.assign(owner); - else { - ToUpper(login_call); - login_call.resize(CALL_SIZE, ' '); - } - int modules = 0; - key.assign("module_"); for (int i=0; i<3; i++) { + key.assign("module_"); key.append(1, 'a'+i); if (cfg.KeyExists(key)) { std::string modem_type; @@ -557,7 +550,7 @@ bool CQnetLink::read_config(const char *cfgFile) csv.clear(); key.assign("link_no_link_unlink"); if (cfg.KeyExists(key)) { - cfg.GetValue(key, estr, csv, 0, 10250); + cfg.GetValue(key, estr, csv, 0, 10240); UnpackCallsigns(csv, link_blacklist); PrintCallsigns(key, link_blacklist); } else { @@ -600,6 +593,13 @@ bool CQnetLink::read_config(const char *cfgFile) cfg.GetValue(key+"authorize", estr, dplus_authorize); cfg.GetValue(key+"use_reflectors", estr, dplus_reflectors); cfg.GetValue(key+"use_repeaters", estr, dplus_repeaters); + cfg.GetValue(key+"ref_login", estr, login_call, 0, 6); + if (login_call.length() < 4) + login_call.assign(owner); + else { + ToUpper(login_call); + login_call.resize(CALL_SIZE, ' '); + } return false; } From 2bea84e0c9a1aef4f9cadc08a9f55e255c85c162 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 08:40:30 -0700 Subject: [PATCH 181/553] rewrote CUnixDgramWriter --- QnetDVAP.cpp | 4 ++-- QnetDVRPTR.cpp | 4 ++-- QnetGateway.cpp | 8 +++---- QnetITAP.cpp | 7 +++--- QnetLink.cpp | 5 ++--- QnetRelay.cpp | 4 ++-- QnetRemote.cpp | 6 +---- UnixDgramSocket.cpp | 54 ++++++++++++++++----------------------------- UnixDgramSocket.h | 7 +++--- 9 files changed, 37 insertions(+), 62 deletions(-) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 8b9b267..a1b9cd2 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -252,7 +252,8 @@ static bool read_config(const char *cfgFile) static int open_sock() { - if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) return 1; return 0; } @@ -648,7 +649,6 @@ int main(int argc, const char **argv) readthread.get(); Gate2Modem.Close(); - Modem2Gate.Close(); printf("dvap_rptr exiting\n"); return 0; } diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index c9fc4cb..68eb1a8 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -2559,7 +2559,8 @@ int main(int argc, const char **argv) strcpy(DVCALL_and_MOD, DVCALL); DVCALL_and_MOD[7] = DVRPTR_MOD; - if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) return 1; if (RX_Inverse == true) { @@ -3087,7 +3088,6 @@ int main(int argc, const char **argv) } } - Modem2Gate.Close(); Gate2Modem.Close(); printf("dvrptr exiting...\n"); diff --git a/QnetGateway.cpp b/QnetGateway.cpp index a2767f3..2f3c950 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -2451,14 +2451,14 @@ int CQnetGateway::Init(char *cfgfile) } // Open unix sockets between qngateway and qnlink - if (Gate2Link.Open(gate2link.c_str())) - return 1; + Gate2Link.SetUp(gate2link.c_str()); if (Link2Gate.Open(link2gate.c_str())) return 1; for (i=0; i<3; i++) { if (rptr.mod[i].defined) { // open unix sockets between qngateway and each defined modem - if (Gate2Modem[i].Open(gate2modem[i].c_str()) || Modem2Gate[i].Open(modem2gate[i].c_str())) + Gate2Modem[i].SetUp(gate2modem[i].c_str()); + if (Modem2Gate[i].Open(modem2gate[i].c_str())) return 1; } // recording for echotest on local repeater modules @@ -2536,10 +2536,8 @@ CQnetGateway::CQnetGateway() CQnetGateway::~CQnetGateway() { - Gate2Link.Close(); Link2Gate.Close(); for (int i=0; i<3; i++) { - Gate2Modem[i].Close(); Modem2Gate[i].Close(); } diff --git a/QnetITAP.cpp b/QnetITAP.cpp index f2db07b..bb61e56 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -78,7 +78,8 @@ bool CQnetITAP::Initialize(const char *cfgfile) return true; } - if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) return true; return false; @@ -197,8 +198,7 @@ void CQnetITAP::Run(const char *cfgfile) return; int ug2m = Gate2Modem.GetFD(); - int um2g = Modem2Gate.GetFD(); - printf("gate2modem=%d, modem2gate=%d serial=%d\n", ug2m, um2g, serfd); + printf("gate2modem=%d, serial=%d\n", ug2m, serfd); keep_running = true; unsigned poll_counter = 0; @@ -300,7 +300,6 @@ void CQnetITAP::Run(const char *cfgfile) ::close(serfd); Gate2Modem.Close(); - Modem2Gate.Close(); } int CQnetITAP::SendTo(const unsigned char length, const unsigned char *buf) diff --git a/QnetLink.cpp b/QnetLink.cpp index ae1af44..e67e425 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -667,7 +667,8 @@ bool CQnetLink::srv_open() } /* create our gateway unix sockets */ - if (Gate2Link.Open(gate2link.c_str()) || Link2Gate.Open(link2gate.c_str())) { + Link2Gate.SetUp(link2gate.c_str()); + if (Gate2Link.Open(gate2link.c_str())) { close(dcs_g2_sock); dcs_g2_sock = -1; close(xrf_g2_sock); @@ -675,7 +676,6 @@ bool CQnetLink::srv_open() close(ref_g2_sock); ref_g2_sock = -1; Gate2Link.Close(); - Link2Gate.Close(); return false; } @@ -707,7 +707,6 @@ void CQnetLink::srv_close() } Gate2Link.Close(); - Link2Gate.Close(); if (ref_g2_sock != -1) { close(ref_g2_sock); diff --git a/QnetRelay.cpp b/QnetRelay.cpp index 81ca076..9b0b69a 100644 --- a/QnetRelay.cpp +++ b/QnetRelay.cpp @@ -127,7 +127,8 @@ bool CQnetRelay::Run(const char *cfgfile) if (msock < 0) return true; - if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) + Modem2Gate.SetUp(modem2gate.c_str()); + if (Gate2Modem.Open(gate2modem.c_str())) return true; int fd = Gate2Modem.GetFD(); @@ -206,7 +207,6 @@ bool CQnetRelay::Run(const char *cfgfile) ::close(msock); Gate2Modem.Close(); - Modem2Gate.Close(); return false; } diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 7d72e14..78c3380 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -198,8 +198,7 @@ int main(int argc, char *argv[]) CUnixDgramWriter ToGateway; std::string togateway("modem2gate"); togateway.append(1, module-'A'+'0'); - if (ToGateway.Open(togateway.c_str())) - return 1; + ToGateway.SetUp(togateway.c_str()); SDSTR pkt; memcpy(pkt.pkt_id,"DSTR", 4); @@ -241,7 +240,6 @@ int main(int argc, char *argv[]) int sent = ToGateway.Write(pkt.pkt_id, 58); if (sent != 58) { printf("%s: ERROR: Couldn't send header!\n", argv[0]); - ToGateway.Close(); return 1; } @@ -313,10 +311,8 @@ int main(int argc, char *argv[]) sent = ToGateway.Write(pkt.pkt_id, 29); if (sent != 29) { printf("%s: ERROR: could not send voice packet %d\n", argv[0], i); - ToGateway.Close(); return 1; } } - ToGateway.Close(); return 0; } diff --git a/UnixDgramSocket.cpp b/UnixDgramSocket.cpp index ca22754..67f776a 100644 --- a/UnixDgramSocket.cpp +++ b/UnixDgramSocket.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include "UnixDgramSocket.h" @@ -75,51 +74,36 @@ int CUnixDgramReader::GetFD() return fd; } -CUnixDgramWriter::CUnixDgramWriter() : fd(-1) {} +CUnixDgramWriter::CUnixDgramWriter() {} -CUnixDgramWriter::~CUnixDgramWriter() -{ - Close(); -} +CUnixDgramWriter::~CUnixDgramWriter() {} -bool CUnixDgramWriter::Open(const char *path) // returns true on failure +void CUnixDgramWriter::SetUp(const char *path) // returns true on failure { - fd = socket(AF_UNIX, SOCK_DGRAM, 0); - if (fd < 0) { - fprintf(stderr, "CUnixDgramWriter::Open: socket() failed: %s\n", strerror(errno)); - return true; - } - - struct sockaddr_un addr; + // setup the socket address memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path+1, path, sizeof(addr.sun_path)-2); +} +ssize_t CUnixDgramWriter::Write(void *buf, size_t size) +{ + // open the socket + int fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (fd < 0) { + fprintf(stderr, "Failed to open socket %s\n", addr.sun_path+1); + return -1; + } + // connect to the receiver int rval = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); if (rval < 0) { - fprintf(stderr, "CUnixDgramWriter::Open: connect() failed: %s\n", strerror(errno)); + fprintf(stderr, "Failed to connect to socket %s\n", addr.sun_path+1); close(fd); - fd = -1; - return true; + return -1; } - return false; -} -ssize_t CUnixDgramWriter::Write(void *buf, size_t size) -{ - if (fd >= 0) - return write(fd, buf, size); - return -1; -} + ssize_t written = write(fd, buf, size); -void CUnixDgramWriter::Close() -{ - if (fd >= 0) - close(fd); - fd = -1; -} - -int CUnixDgramWriter::GetFD() -{ - return fd; + close(fd); + return written; } diff --git a/UnixDgramSocket.h b/UnixDgramSocket.h index 7242955..102d99f 100644 --- a/UnixDgramSocket.h +++ b/UnixDgramSocket.h @@ -18,6 +18,7 @@ */ #include +#include class CUnixDgramReader { @@ -37,10 +38,8 @@ class CUnixDgramWriter public: CUnixDgramWriter(); ~CUnixDgramWriter(); - bool Open(const char *path); + void SetUp(const char *path); ssize_t Write(void *buf, size_t size); - void Close(); - int GetFD(); private: - int fd; + struct sockaddr_un addr; }; From 196b5bed0f4a66896814f24943a91dc767dd6188 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 12:59:14 -0700 Subject: [PATCH 182/553] fixing read_config --- QnetGateway.cpp | 10 +++++----- defaults | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 2f3c950..4391cb8 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -226,11 +226,11 @@ bool CQnetGateway::read_config(char *cfgFile) cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); - cfg.GetValue("gateway_latitude", type, rptr.mod[m].latitude, -90.0, 90.0); - cfg.GetValue("gateway_longitude", type, rptr.mod[m].longitude, -180.0, 180.0); - cfg.GetValue("gateway_desc1", type, rptr.mod[m].desc1, 0, 20); - cfg.GetValue("gateway_desc2", type, rptr.mod[m].desc2, 0, 20); - cfg.GetValue("gateway_url", type, rptr.mod[m].url, 0, 80); + cfg.GetValue("gateway_latitude", estr, rptr.mod[m].latitude, -90.0, 90.0); + cfg.GetValue("gateway_longitude", estr, rptr.mod[m].longitude, -180.0, 180.0); + cfg.GetValue("gateway_desc1", estr, rptr.mod[m].desc1, 0, 20); + cfg.GetValue("gateway_desc2", estr, rptr.mod[m].desc2, 0, 20); + cfg.GetValue("gateway_url", estr, rptr.mod[m].url, 0, 80); // make the long description for the log if (rptr.mod[m].desc1.length()) diff --git a/defaults b/defaults index 0986329..a4193a0 100644 --- a/defaults +++ b/defaults @@ -45,6 +45,7 @@ ircddb_password_d='1111111111111' # not needed for rr.openquad.net gateway_header_regen_d=true # regenerate headers from incoming data gateway_send_qrgs_maps_d=true # send frequency, offset, coordinates and url to irc-server gateway_local_irc_ip_d='0.0.0.0' # the local port on the gateway for the IRC TCP socket +gateway_ip_d='0.0.0.0' # the g2 port gateway_port_d=40000 # don't change gateway_tolink_d='gate2link' # Unix sockets between qngateway and QnetLink gateway_fromlink_d='link2gate' # all Unix sockets are on the file system, but hidden from view From 752c37efd61ef306e0d7c52f6897b92e17bd4994 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 13:05:04 -0700 Subject: [PATCH 183/553] bug in main() --- QnetDVAP.cpp | 276 +++++++++++++++++++++++++-------------------------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index a1b9cd2..9467332 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -515,144 +515,6 @@ static void readFrom20000() return; } -int main(int argc, const char **argv) -{ - struct sigaction act; - int rc = -1; - time_t tnow = 0; - time_t ackpoint = 0; - short cnt = 0; - - setvbuf(stdout, NULL, _IOLBF, 0); - printf("dvap_rptr VERSION %s\n", VERSION); - - if (argc != 2) { - fprintf(stderr, "Usage: %s dvap_rptr.cfg\n", argv[0]); - return 1; - } - - if ('-' == argv[1][0]) { - printf("\nQnetDVAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", DVAP_VERSION); - printf("QnetDVAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); - printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); - return 0; - } - - const char *qn = strstr(argv[0], "qndvap"); - if (NULL == qn) { - fprintf(stderr, "Error finding 'qndvap' in %s!\n", argv[0]); - return 1; - } - qn += 6; - - switch (argv[1][0]) { - case NULL: - assigned_module = -1; - break; - case 'a': - assigned_module = 0; - break; - case 'b': - assigned_module = 1; - break; - case 'c': - assigned_module = 2; - break; - default: - fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be a, b or c\n", argv[1]); - return 1; - } - - if (read_config(argv[1])) { - printf("Failed to process config file %s\n", argv[2]); - return 1; - } - - if (RPTR.length() != 8) { - printf("Bad RPTR value, length must be exactly 8 bytes\n"); - return 1; - } - if ((RPTR_MOD != 'A') && (RPTR_MOD != 'B') && (RPTR_MOD != 'C')) { - printf("Bad RPTR_MOD value, must be one of A or B or C\n"); - return 1; - } - - if (RPTR_MOD == 'A') - SND_TERM_ID = 0x03; - else if (RPTR_MOD == 'B') - SND_TERM_ID = 0x01; - else if (RPTR_MOD == 'C') - SND_TERM_ID = 0x02; - - strcpy(RPTR_and_G, RPTR.c_str()); - RPTR_and_G[7] = 'G'; - - strcpy(RPTR_and_MOD, RPTR.c_str()); - RPTR_and_MOD[7] = RPTR_MOD; - - time(&tnow); - aseed = tnow + getpid(); - - act.sa_handler = sig_catch; - sigemptyset(&act.sa_mask); - if (sigaction(SIGTERM, &act, 0) != 0) { - printf("sigaction-TERM failed, error=%d\n", errno); - return 1; - } - if (sigaction(SIGHUP, &act, 0) != 0) { - printf("sigaction-HUP failed, error=%d\n", errno); - return 1; - } - if (sigaction(SIGINT, &act, 0) != 0) { - printf("sigaction-INT failed, error=%d\n", errno); - return 1; - } - - /* open dvp */ - if (!dongle.Initialize(DVP_SERIAL.c_str(), DVP_FREQ, DVP_OFF, DVP_PWR, DVP_SQL)) - return 1; - - rc = open_sock(); - if (rc != 0) { - dongle.Stop(); - close(serfd); - return 1; - } - printf("DVAP opened and initialized!\n"); - dstar_dv_init(); - - std::future readthread; - try { - readthread = std::async(std::launch::async, ReadDVAPThread); - } catch (const std::exception &e) { - printf("Unable to start ReadDVAPThread(). Exception: %s\n", e.what()); - keep_running = false; - } - printf("Started ReadDVAPThread()\n"); - - while (keep_running) { - time(&tnow); - if ((tnow - ackpoint) > 2) { - rc = dongle.KeepAlive(); - if (rc < 0) { - cnt ++; - if (cnt > 5) { - printf("Could not send KEEPALIVE signal to dvap 5 times...exiting\n"); - keep_running = false; - } - } else - cnt = 0; - ackpoint = tnow; - } - readFrom20000(); - } - - readthread.get(); - Gate2Modem.Close(); - printf("dvap_rptr exiting\n"); - return 0; -} - static void RptrAckThread(SDVAP_ACK_ARG *parg) { char mycall[8]; @@ -1074,3 +936,141 @@ static void ReadDVAPThread() keep_running = false; return; } + +int main(int argc, const char **argv) +{ + struct sigaction act; + int rc = -1; + time_t tnow = 0; + time_t ackpoint = 0; + short cnt = 0; + + setvbuf(stdout, NULL, _IOLBF, 0); + printf("dvap_rptr VERSION %s\n", VERSION); + + if (argc != 2) { + fprintf(stderr, "Usage: %s dvap_rptr.cfg\n", argv[0]); + return 1; + } + + if ('-' == argv[1][0]) { + printf("\nQnetDVAP Version #%s Copyright (C) 2018-2019 by Thomas A. Early N7TAE\n", DVAP_VERSION); + printf("QnetDVAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + const char *qn = strstr(argv[0], "qndvap"); + if (NULL == qn) { + fprintf(stderr, "Error finding 'qndvap' in %s!\n", argv[0]); + return 1; + } + qn += 6; + + switch (*qn) { + case NULL: + assigned_module = -1; + break; + case 'a': + assigned_module = 0; + break; + case 'b': + assigned_module = 1; + break; + case 'c': + assigned_module = 2; + break; + default: + fprintf(stderr, "ERROR: '%s' is not a valid module\nassigned module must be a, b or c\n", argv[1]); + return 1; + } + + if (read_config(argv[1])) { + printf("Failed to process config file %s\n", argv[2]); + return 1; + } + + if (RPTR.length() != 8) { + printf("Bad RPTR value, length must be exactly 8 bytes\n"); + return 1; + } + if ((RPTR_MOD != 'A') && (RPTR_MOD != 'B') && (RPTR_MOD != 'C')) { + printf("Bad RPTR_MOD value, must be one of A or B or C\n"); + return 1; + } + + if (RPTR_MOD == 'A') + SND_TERM_ID = 0x03; + else if (RPTR_MOD == 'B') + SND_TERM_ID = 0x01; + else if (RPTR_MOD == 'C') + SND_TERM_ID = 0x02; + + strcpy(RPTR_and_G, RPTR.c_str()); + RPTR_and_G[7] = 'G'; + + strcpy(RPTR_and_MOD, RPTR.c_str()); + RPTR_and_MOD[7] = RPTR_MOD; + + time(&tnow); + aseed = tnow + getpid(); + + act.sa_handler = sig_catch; + sigemptyset(&act.sa_mask); + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return 1; + } + if (sigaction(SIGHUP, &act, 0) != 0) { + printf("sigaction-HUP failed, error=%d\n", errno); + return 1; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return 1; + } + + /* open dvp */ + if (!dongle.Initialize(DVP_SERIAL.c_str(), DVP_FREQ, DVP_OFF, DVP_PWR, DVP_SQL)) + return 1; + + rc = open_sock(); + if (rc != 0) { + dongle.Stop(); + close(serfd); + return 1; + } + printf("DVAP opened and initialized!\n"); + dstar_dv_init(); + + std::future readthread; + try { + readthread = std::async(std::launch::async, ReadDVAPThread); + } catch (const std::exception &e) { + printf("Unable to start ReadDVAPThread(). Exception: %s\n", e.what()); + keep_running = false; + } + printf("Started ReadDVAPThread()\n"); + + while (keep_running) { + time(&tnow); + if ((tnow - ackpoint) > 2) { + rc = dongle.KeepAlive(); + if (rc < 0) { + cnt ++; + if (cnt > 5) { + printf("Could not send KEEPALIVE signal to dvap 5 times...exiting\n"); + keep_running = false; + } + } else + cnt = 0; + ackpoint = tnow; + } + readFrom20000(); + } + + readthread.get(); + Gate2Modem.Close(); + printf("dvap_rptr exiting\n"); + return 0; +} From f2369c112ff7037eef285f176b003288338eebf5 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 13:13:44 -0700 Subject: [PATCH 184/553] start procs on install --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 2ce6ef1..025478d 100644 --- a/Makefile +++ b/Makefile @@ -90,7 +90,7 @@ installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg /bin/cp -f system/qngateway.service $(SYSDIR) systemctl enable qngateway.service systemctl daemon-reload -# systemctl start qngateway.service + systemctl start qngateway.service ######### QnetLink ######### /bin/cp -f qnlink $(BINDIR) /bin/cp -f announce/*.dat $(CFGDIR) @@ -99,7 +99,7 @@ installbase : $(BASE_PROGRAMS) gwys.txt qn.cfg /bin/cp -f system/qnlink.service $(SYSDIR) systemctl enable qnlink.service systemctl daemon-reload -# systemctl start qnlink.service + systemctl start qnlink.service installrelay : qnrelay ######### QnetRelay ######### @@ -107,7 +107,7 @@ installrelay : qnrelay /bin/cp -f system/qnrelay$(MODULE).service $(SYSDIR) systemctl enable qnrelay$(MODULE).service systemctl daemon-reload -# systemctl start qnrelay$(MODULE).service + systemctl start qnrelay$(MODULE).service ######### MMDVMHost ######### installitap : qnitap @@ -116,7 +116,7 @@ installitap : qnitap /bin/cp -f system/qnitap$(MODULE).service $(SYSDIR) systemctl enable qnitap$(MODULE).service systemctl daemon-reload -# systemctl start qnitap$(MODULE).service + systemctl start qnitap$(MODULE).service installdvap : qndvap ######### QnetDVAP ######### @@ -124,7 +124,7 @@ installdvap : qndvap /bin/cp -f system/qndvap$(MODULE).service $(SYSDIR) systemctl enable qndvap$(MODULE).service systemctl daemon-reload -# systemctl start qndvap$(MODULE).service + systemctl start qndvap$(MODULE).service installdvrptr : qndvrptr ######### QnetDVRPTR ######### @@ -132,7 +132,7 @@ installdvrptr : qndvrptr /bin/cp -f system/qndvrptr$(MODULE).service $(SYSDIR) systemctl enable qndvrptr$(MODULE).service systemctl daemon-reload -# systemctl start qndvrptr$(MODULE).service + systemctl start qndvrptr$(MODULE).service installdtmf : qndtmf /bin/ln -s $(shell pwd)/qndtmf $(BINDIR) From f5633e6f854b33ad6fcecf5cf6c017738e7fa3a6 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 14:14:07 -0700 Subject: [PATCH 185/553] qnremote bug fixes --- QnetRemote.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 78c3380..7e60c48 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -41,10 +41,10 @@ #define VERSION "v2.0" -char module; +int module; time_t tNow = 0; short streamid_raw = 0; -std::string REPEATER; +std::string REPEATER, togateway; int PLAY_WAIT, PLAY_DELAY; unsigned char silence[9] = { 0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8 }; @@ -76,21 +76,26 @@ bool ReadCfgFile() const std::string estr; std::string type; std::string path = "module_"; - path.append(1, module); + path.append(1, 'a'+module); if (cfg.GetValue(path, estr, type, 1, 16)) { fprintf(stderr, "%s not found!\n", path.c_str()); return true; } if (type.compare("dvap") && type.compare("dvrptr") && type.compare("mmdvm") && type.compare("itap")) { - fprintf(stderr, "module type '%s is invalid!\n", type.c_str()); + fprintf(stderr, "module type '%s' is invalid!\n", type.c_str()); return true; } - if (cfg.GetValue(path+"_callsign", type, REPEATER, 3, 6)) { + cfg.GetValue(path+"_callsign", type, REPEATER, 0, 6); + if (REPEATER.length() < 4) { if (cfg.GetValue("ircddb.login", estr, REPEATER, 3, 6)) { fprintf(stderr, "no Callsign for the repeater was found!\n"); return true; } } + path.append("_modem2gate"); + path.append(1, 'a'+module); + cfg.GetValue(path, type, togateway, 1, FILENAME_MAX); + cfg.GetValue("timing_play_wait", estr, PLAY_WAIT, 1,10); cfg.GetValue("timing_play_delay", estr, PLAY_DELAY, 15, 25); return false; @@ -140,17 +145,17 @@ int main(int argc, char *argv[]) case '0': case 'a': case 'A': - module = 'A'; + module = 0; break; case '1': case 'b': case 'B': - module = 'B'; + module = 1; break; case '2': case 'c': case 'C': - module = 'C'; + module = 2; break; default: fprintf(stderr, "module must be 0, a, A, 1, b, B, 2, c or C, not %s\n", argv[1]); @@ -196,8 +201,6 @@ int main(int argc, char *argv[]) time(&tNow); CRandom Random; CUnixDgramWriter ToGateway; - std::string togateway("modem2gate"); - togateway.append(1, module-'A'+'0'); ToGateway.SetUp(togateway.c_str()); SDSTR pkt; @@ -210,11 +213,11 @@ int main(int argc, char *argv[]) pkt.vpkt.icm_id = 0x20; pkt.vpkt.dst_rptr_id = 0x00; pkt.vpkt.snd_rptr_id = 0x01; - if (module == 'A') + if (module == 0) pkt.vpkt.snd_term_id = 0x03; - else if (module == 'B') + else if (module == 1) pkt.vpkt.snd_term_id = 0x01; - else if (module == 'C') + else if (module == 2) pkt.vpkt.snd_term_id = 0x02; else pkt.vpkt.snd_term_id = 0x00; @@ -224,8 +227,10 @@ int main(int argc, char *argv[]) pkt.vpkt.hdr.flag[0] = pkt.vpkt.hdr.flag[1] = pkt.vpkt.hdr.flag[2] = 0x00; REPEATER.resize(7, ' '); - memcpy(pkt.vpkt.hdr.r2, std::string(REPEATER + 'G').c_str(), 8); - memcpy(pkt.vpkt.hdr.r1, std::string(REPEATER + module).c_str(), 8); + memcpy(pkt.vpkt.hdr.r2, REPEATER.c_str(), 8); + pkt.vpkt.hdr.r2[7] = 'G'; + memcpy(pkt.vpkt.hdr.r1, REPEATER.c_str(), 8); + pkt.vpkt.hdr.r1[7] = 'A' + module; mycall.resize(8, ' '); memcpy(pkt.vpkt.hdr.my, mycall.c_str(), 8); memcpy(pkt.vpkt.hdr.nm, "QNET", 4); From a0feb96d2ded3c8c8092de099c753834d8fcf469 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 14:53:47 -0700 Subject: [PATCH 186/553] fix to ReadCfgFile() --- QnetRemote.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetRemote.cpp b/QnetRemote.cpp index 7e60c48..7d0e01b 100644 --- a/QnetRemote.cpp +++ b/QnetRemote.cpp @@ -87,7 +87,7 @@ bool ReadCfgFile() } cfg.GetValue(path+"_callsign", type, REPEATER, 0, 6); if (REPEATER.length() < 4) { - if (cfg.GetValue("ircddb.login", estr, REPEATER, 3, 6)) { + if (cfg.GetValue("ircddb_login", estr, REPEATER, 3, 6)) { fprintf(stderr, "no Callsign for the repeater was found!\n"); return true; } From ec0efc5908c6df7dabcf135613cf8985e1bde457 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 15:04:53 -0700 Subject: [PATCH 187/553] fix for min length check of config value --- QnetConfigure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp index 200e388..2fef362 100644 --- a/QnetConfigure.cpp +++ b/QnetConfigure.cpp @@ -231,7 +231,7 @@ bool CQnetConfigure::GetValue(const std::string &path, const std::string &mod, s return true; } int l = dvalue.length(); - if (lmax) { + if (min-1>=l || l>max) { printf("Default value %s='%s' is wrong size\n", path.c_str(), value.c_str()); return true; } From d4c1d112967bca58ccc43eb3fd229de59a659a95 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 21:12:53 -0700 Subject: [PATCH 188/553] fixed trouble with log_qso --- QnetLink.cpp | 2 +- defaults | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index e67e425..c825de4 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -577,7 +577,7 @@ bool CQnetLink::read_config(const char *cfgFile) cfg.GetValue(key+"tolink", estr, gate2link, 1, FILENAME_MAX); cfg.GetValue(key+"fromlink", estr, link2gate, 1, FILENAME_MAX); - cfg.GetValue("log.qso", estr, qso_details); + cfg.GetValue("log_qso", estr, qso_details); key.assign("file_"); cfg.GetValue(key+"gwys", estr, gwys, 2, FILENAME_MAX); diff --git a/defaults b/defaults index a4193a0..e31ad89 100644 --- a/defaults +++ b/defaults @@ -49,10 +49,10 @@ gateway_ip_d='0.0.0.0' # the g2 port gateway_port_d=40000 # don't change gateway_tolink_d='gate2link' # Unix sockets between qngateway and QnetLink gateway_fromlink_d='link2gate' # all Unix sockets are on the file system, but hidden from view -gateway_latitude_d=0 # you can leave this unspecified for a mobile rig -gateway_longitude_d=0 # like the latitude -gateway_desc1_d='' # maximum of 20 characters, most special symbols are not allowed -gateway_desc2_d='' # just like desc1 +gateway_latitude_d=0 # you can leave this unspecified for a mobile rig +gateway_longitude_d=0 # like the latitude +gateway_desc1_d='' # maximum of 20 characters, most special symbols are not allowed +gateway_desc2_d='' # just like desc1 gateway_url_d='github.com/n7tae/QnetGateway' # 80 characters max ########################################################################################################################## From 34ec29a0cffa09a5e9d2cda9c9b430c3cdb71bdd Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 21:30:25 -0700 Subject: [PATCH 189/553] fixed link_at_start bug --- QnetLink.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index c825de4..bf5b28a 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -959,7 +959,9 @@ void CQnetLink::Process() sleep(15); first = false; } - g2link('A'+i, link_at_startup[i].substr(0, 6).c_str(), link_at_startup[i].at(7)); + std::string node(link_at_startup[i].substr(0, 6)); + node.resize(CALL_SIZE, ' '); + g2link('A'+i, node.c_str(), link_at_startup[i].at(7)); } } From debdd555324d52f55ecba6e1493cdc546ca29427 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Thu, 10 Jan 2019 23:42:15 -0700 Subject: [PATCH 190/553] fixed bug with empty default string value --- QnetConfigure.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/QnetConfigure.cpp b/QnetConfigure.cpp index 2fef362..d60542f 100644 --- a/QnetConfigure.cpp +++ b/QnetConfigure.cpp @@ -51,9 +51,12 @@ bool CQnetConfigure::ReadConfigFile(const char *configfile, std::map Date: Fri, 11 Jan 2019 12:49:35 -0700 Subject: [PATCH 191/553] fixed bug not reading link_imcoming_ip --- QnetLink.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/QnetLink.cpp b/QnetLink.cpp index bf5b28a..1aa5350 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -564,6 +564,7 @@ bool CQnetLink::read_config(const char *cfgFile) } key.assign("link_"); + cfg.GetValue(key+"incoming_ip", estr, my_g2_link_ip, 7, IP_SIZE); cfg.GetValue(key+"ref_port", estr, rmt_ref_port, 10000, 65535); cfg.GetValue(key+"xrf_port", estr, rmt_xrf_port, 10000, 65535); cfg.GetValue(key+"dcs_port", estr, rmt_dcs_port, 10000, 65535); From 91bbc891a03805974c1f79f11e9243b44a105588 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 11 Jan 2019 18:23:49 -0700 Subject: [PATCH 192/553] aprs_enable bug fixed --- qnconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qnconfig b/qnconfig index f40c461..0014d46 100755 --- a/qnconfig +++ b/qnconfig @@ -483,7 +483,7 @@ WriteCFGFile () { [ -z "${gateway_desc2+x}" ] || echo "gateway_desc2='${gateway_desc2}'" >> $outFile [ -z "${gateway_url+x}" ] || echo "gateway_url='${gateway_url}'" >> $outFile # arps_ section - [ -z "${aprs_enable+x}" ] || echo "aprs_enable='{aprs_enable}" >> $outFile + [ -z "${aprs_enable+x}" ] || echo "aprs_enable=${aprs_enable}" >> $outFile [ -z "${aprs_host+x}" ] || echo "aprs_host='${aprs_host}'" >> $outFile [ -z "${aprs_port+x}" ] || echo "aprs_port=${aprs_port}" >> $outFile [ -z "${aprs_interval+x}" ] || echo "aprs_interval=${aprs_interval}" >> $outFile From c4a0d3dc4265566a1249d78098f41a1f852edd76 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 11 Jan 2019 19:15:44 -0700 Subject: [PATCH 193/553] removed delay_between in message playback. --- QnetLink.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 1aa5350..c0f7a34 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3285,7 +3285,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; for (count=0; count Date: Fri, 11 Jan 2019 19:41:38 -0700 Subject: [PATCH 194/553] debugging on --- Makefile | 4 ++-- qnadmin | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 025478d..e5f328a 100644 --- a/Makefile +++ b/Makefile @@ -24,10 +24,10 @@ SYSDIR=/lib/systemd/system IRC=ircddb # use this if you want debugging help in the case of a crash -#CPPFLAGS=-g -ggdb -W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\" +CPPFLAGS=-g -ggdb -W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\" # or, you can choose this for a much smaller executable without debugging help -CPPFLAGS=-W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\" +#CPPFLAGS=-W -Wall -std=c++11 -Iircddb -DCFG_DIR=\"$(CFGDIR)\" LDFLAGS=-L/usr/lib -lrt diff --git a/qnadmin b/qnadmin index ae0b212..dadc78b 100755 --- a/qnadmin +++ b/qnadmin @@ -92,7 +92,7 @@ BaseStatus () { if [[ "$ActiveGate" == "inactive" ]]; then echo "is not installed" else - echo "ActiveState os $ActiveGate SubState is $SubGate" + echo "ActiveState is $ActiveGate SubState is $SubGate" fi echo -n "QnetLink " if [[ "$ActiveLink" == "inactive" ]]; then From a57c10a8598cce67d6f4d8868824efeefa3992cb Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 11 Jan 2019 20:48:25 -0700 Subject: [PATCH 195/553] better shutdown? --- QnetGateway.cpp | 11 +++++++---- QnetLink.cpp | 38 ++++++++++++++++++++------------------ UnixDgramSocket.cpp | 2 ++ 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 4391cb8..4d1613d 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1894,7 +1894,7 @@ void CQnetGateway::Process() (void)select(max_nfds + 1, &fdset, 0, 0, &tv); // process packets coming from remote G2 or g2_link - if (FD_ISSET(g2_sock, &fdset)) { + if (keep_running && FD_ISSET(g2_sock, &fdset)) { SDSVT g2buf; socklen_t fromlen = sizeof(struct sockaddr_in); ssize_t g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); @@ -1902,7 +1902,10 @@ void CQnetGateway::Process() // save incoming port for mobile systems if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port);FD_ISSET(g2_sock, &fdset)) { + SDSVT g2buf; + socklen_t fromlen = sizeof(struct sockaddr_in); + ssize_t g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); } else { if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); @@ -1913,7 +1916,7 @@ void CQnetGateway::Process() FD_CLR(g2_sock, &fdset); } - if (FD_ISSET(Link2Gate.GetFD(), &fdset)) { + if (keep_running && FD_ISSET(Link2Gate.GetFD(), &fdset)) { SDSVT g2buf; ssize_t g2buflen = Link2Gate.Read(g2buf.title, 56); ProcessG2(g2buflen, g2buf); @@ -1922,7 +1925,7 @@ void CQnetGateway::Process() // process packets coming from local repeater modules for (int mod=0; mod<3; mod++) { - if (rptr.mod[mod].defined && FD_ISSET(Modem2Gate[mod].GetFD(), &fdset)) { + if (keep_running && rptr.mod[mod].defined && FD_ISSET(Modem2Gate[mod].GetFD(), &fdset)) { ProcessModem(mod); FD_CLR (Modem2Gate[mod].GetFD(), &fdset); } diff --git a/QnetLink.cpp b/QnetLink.cpp index c0f7a34..ba4bd69 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -968,7 +968,7 @@ void CQnetLink::Process() while (keep_running) { time(&tnow); - if ((tnow - hb) > 0) { + if (keep_running && (tnow - hb) > 0) { /* send heartbeat to connected donglers */ send_heartbeat(); @@ -1108,18 +1108,20 @@ void CQnetLink::Process() // this could be coming from qnvoice or qngateway (connected2network or notincache) std::ifstream voicefile(qnvoice_file.c_str(), std::ifstream::in); if (voicefile) { - char line[FILENAME_MAX]; - voicefile.getline(line, FILENAME_MAX); - // trim whitespace - char *start = line; - while (isspace(*start)) - start++; - char *end = start + strlen(start) - 1; - while (isspace(*end)) - *end-- = (char)0; - // anthing reasonable left? - if (strlen(start) > 2) - PlayAudioNotifyThread(start); + if (keep_running) { + char line[FILENAME_MAX]; + voicefile.getline(line, FILENAME_MAX); + // trim whitespace + char *start = line; + while (isspace(*start)) + start++; + char *end = start + strlen(start) - 1; + while (isspace(*end)) + *end-- = (char)0; + // anthing reasonable left? + if (strlen(start) > 2) + PlayAudioNotifyThread(start); + } //clean-up voicefile.close(); remove(qnvoice_file.c_str()); @@ -1134,7 +1136,7 @@ void CQnetLink::Process() tv.tv_usec = 20000; (void)select(max_nfds + 1, &fdset, 0, 0, &tv); - if (FD_ISSET(xrf_g2_sock, &fdset)) { + if (keep_running && FD_ISSET(xrf_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); unsigned char buf[100]; int length = recvfrom(xrf_g2_sock, buf, 100, 0, (struct sockaddr *)&fromDst4, &fromlen); @@ -1683,7 +1685,7 @@ void CQnetLink::Process() FD_CLR (xrf_g2_sock,&fdset); } - if (FD_ISSET(ref_g2_sock, &fdset)) { + if (keep_running && FD_ISSET(ref_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); unsigned char buf[100]; int length = recvfrom(ref_g2_sock, buf, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); @@ -2430,7 +2432,7 @@ void CQnetLink::Process() FD_CLR (ref_g2_sock,&fdset); } - if (FD_ISSET(dcs_g2_sock, &fdset)) { + if (keep_running && FD_ISSET(dcs_g2_sock, &fdset)) { socklen_t fromlen = sizeof(struct sockaddr_in); int length = recvfrom(dcs_g2_sock, dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); @@ -2662,7 +2664,7 @@ void CQnetLink::Process() FD_CLR (dcs_g2_sock,&fdset); } - if (FD_ISSET(Gate2Link.GetFD(), &fdset)) { + if (keep_running && FD_ISSET(Gate2Link.GetFD(), &fdset)) { SDSTR dstr; int length = Gate2Link.Read(dstr.pkt_id, 100); @@ -3170,7 +3172,7 @@ void CQnetLink::Process() FD_CLR (Gate2Link.GetFD(), &fdset); } for (int i=0; i<3; i++) { - if (notify_msg[i][0] && 0x0U == tracing[i].streamid) { + if (keep_running && notify_msg[i][0] && 0x0U == tracing[i].streamid) { PlayAudioNotifyThread(notify_msg[i]); notify_msg[i][0] = '\0'; } diff --git a/UnixDgramSocket.cpp b/UnixDgramSocket.cpp index 67f776a..7c3a046 100644 --- a/UnixDgramSocket.cpp +++ b/UnixDgramSocket.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,7 @@ bool CUnixDgramReader::Open(const char *path) // returns true on failure fprintf(stderr, "CUnixDgramReader::Open: socket() failed: %s\n", strerror(errno)); return true; } + fcntl(fd, F_SETFL, O_NONBLOCK); struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); From eb77008050a8f8fbbe6f7d5a22b1e752cad84074 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 11 Jan 2019 21:11:28 -0700 Subject: [PATCH 196/553] try again --- QnetGateway.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 4d1613d..1d1b653 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -1902,10 +1902,7 @@ void CQnetGateway::Process() // save incoming port for mobile systems if (portmap.end() == portmap.find(fromDst4.sin_addr.s_addr)) { printf("New g2 contact at %s on port %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port)); - portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port);FD_ISSET(g2_sock, &fdset)) { - SDSVT g2buf; - socklen_t fromlen = sizeof(struct sockaddr_in); - ssize_t g2buflen = recvfrom(g2_sock, g2buf.title, 56, 0, (struct sockaddr *)&fromDst4, &fromlen); + portmap[fromDst4.sin_addr.s_addr] = ntohs(fromDst4.sin_port); } else { if (ntohs(fromDst4.sin_port) != portmap[fromDst4.sin_addr.s_addr]) { printf("New g2 port from %s is now %u, it was %u\n", inet_ntoa(fromDst4.sin_addr), ntohs(fromDst4.sin_port), portmap[fromDst4.sin_addr.s_addr]); From d294fe265c2b5fd6bd06659fb94f985d0e5b6d9f Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 11 Jan 2019 21:45:00 -0700 Subject: [PATCH 197/553] fixed qnvoice_file --- QnetGateway.cpp | 2 +- QnetLink.cpp | 2 +- defaults | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 1d1b653..3965fd3 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -272,7 +272,7 @@ bool CQnetGateway::read_config(char *cfgFile) cfg.GetValue(path+"echotest", estr, echotest_dir, 2, FILENAME_MAX); cfg.GetValue(path+"dtmf", estr, dtmf_dir, 2, FILENAME_MAX); cfg.GetValue(path+"status", estr, status_file, 2, FILENAME_MAX); - cfg.GetValue(path+"qnvoicefile", estr, qnvoicefile, 2, FILENAME_MAX); + cfg.GetValue(path+"qnvoice_file", estr, qnvoicefile, 2, FILENAME_MAX); // timing path.assign("timing_play_"); diff --git a/QnetLink.cpp b/QnetLink.cpp index ba4bd69..9ebb878 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -583,7 +583,7 @@ bool CQnetLink::read_config(const char *cfgFile) key.assign("file_"); cfg.GetValue(key+"gwys", estr, gwys, 2, FILENAME_MAX); cfg.GetValue(key+"status", estr, status_file, 2, FILENAME_MAX); - cfg.GetValue(key+"qnvoicefile", estr, qnvoice_file, 2, FILENAME_MAX); + cfg.GetValue(key+"qnvoice_file", estr, qnvoice_file, 2, FILENAME_MAX); cfg.GetValue(key+"announce_dir", estr, announce_dir, 2, FILENAME_MAX); key.assign("timing_play_"); diff --git a/defaults b/defaults index e31ad89..ef9a2d5 100644 --- a/defaults +++ b/defaults @@ -167,7 +167,7 @@ dplus_use_repeaters_d=true # set to false if you are not going to link to DPlus file_status_d='/usr/local/etc/rptr_status' # where repeater status info is passed between services file_dtmf_d='/tmp' # where DTMF is decoded file_echotest_d='/tmp' # echo dat files will end up here -file_qnvoicefile_d='/tmp/qnvoice.txt' # where qnvoice will create the play command +file_qnvoice_file_d='/tmp/qnvoice.txt' # where qnvoice will create the play command file_gwys_d='/usr/local/etc/gwys.txt' # where the list of gateways and reflectors (with ports) is. file_announce_dir_d='/usr/local/etc' # where the *.dat files are for the verbal link, unlink, etc. announcements From ac57b523657adc8735177f26c3f76c842c618105 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Fri, 11 Jan 2019 21:51:59 -0700 Subject: [PATCH 198/553] added back delay_between --- QnetLink.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QnetLink.cpp b/QnetLink.cpp index 9ebb878..f79f06a 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -3287,7 +3287,7 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; for (count=0; count Date: Sun, 13 Jan 2019 09:51:28 -0700 Subject: [PATCH 199/553] ModuleMenu set nmod to letter, not number --- qnconfig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qnconfig b/qnconfig index 0014d46..ce11fbd 100755 --- a/qnconfig +++ b/qnconfig @@ -282,11 +282,11 @@ GateMenu () { ModuleMenu () { mod=module_${1} if [[ $1 == a ]]; then - nmod=0 + nmod='a' elif [[ $1 == b ]]; then - nmod=1 + nmod='b' else - nmod=2 + nmod='c' fi clear if [ -z ${!mod} ]; then @@ -322,7 +322,7 @@ ModuleMenu () { echo -n "ra : Range (in meters, 1 mile=1609.344 meters) = "; EvaluateVar {${mod},module_x}_range echo -n "ag : Above ground level (in meters) = "; EvaluateVar {${mod},module_x}_agl echo -n "uf : UNIX Socket from gateway = "; EvaluateVar {${mod},module_x}_gate2modem$nmod - echo -n "ut : UNIX Socket from gateway = "; EvaluateVar {${mod},module_x}_modem2gate$nmod + echo -n "ut : UNIX Socket to gateway = "; EvaluateVar {${mod},module_x}_modem2gate$nmod echo -n "in : Inactivity for this many minutes unlinks = "; EvaluateVar {${mod},module_x}_inactivity echo -n "wa : Wait this many msec for the next packet = "; EvaluateVar {${mod},module_x}_packet_wait echo -n "ac : Send acknowledgment on each transmission = "; EvaluateVar {${mod},module_x}_acknowledge From 1fafb6e61648d64fe37ce53f01ffb58554cd37d4 Mon Sep 17 00:00:00 2001 From: Tom Early Date: Sun, 13 Jan 2019 16:10:59 -0700 Subject: [PATCH 200/553] single modem2gate --- QnetDVAP.cpp | 4 ++-- QnetDVRPTR.cpp | 4 ++-- QnetGateway.cpp | 46 ++++++++++++++++++++++----------------------- QnetGateway.h | 6 +++--- QnetITAP.cpp | 4 ++-- QnetLink.cpp | 10 +++++----- QnetRelay.cpp | 4 ++-- UnixDgramSocket.cpp | 9 +++++++-- defaults | 14 ++++++-------- qnconfig | 31 +++++++++++++++++------------- 10 files changed, 69 insertions(+), 63 deletions(-) diff --git a/QnetDVAP.cpp b/QnetDVAP.cpp index 9467332..b6dbc92 100644 --- a/QnetDVAP.cpp +++ b/QnetDVAP.cpp @@ -202,8 +202,8 @@ static bool read_config(const char *cfgFile) } } RPTR_MOD = 'A' + assigned_module; - cfg.GetValue(dvap_path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(dvap_path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX); + cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX); if (cfg.KeyExists(dvap_path+"_callsign")) { if (cfg.GetValue(dvap_path+"_callsign", type, RPTR, 3, 6)) return true; diff --git a/QnetDVRPTR.cpp b/QnetDVRPTR.cpp index 68eb1a8..2823304 100644 --- a/QnetDVRPTR.cpp +++ b/QnetDVRPTR.cpp @@ -1855,8 +1855,8 @@ static bool read_config(const char *cfgFile) } } DVRPTR_MOD = 'A' + assigned_module; - cfg.GetValue(path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX); + cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX); std::string call; if (cfg.GetValue("ircddb_login", type, call, 3, 6)) diff --git a/QnetGateway.cpp b/QnetGateway.cpp index 3965fd3..ba8430c 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -220,17 +220,10 @@ bool CQnetGateway::read_config(char *cfgFile) rptr.mod[m].defined = true; path.append(1, '_'); - cfg.GetValue(path+"modem2gate"+std::string(1, 'a'+m), type, modem2gate[m], 1, FILENAME_MAX); - cfg.GetValue(path+"gate2modem"+std::string(1, 'a'+m), type, gate2modem[m], 1, FILENAME_MAX); cfg.GetValue(path+"frequency", type, rptr.mod[m].frequency, 0.0, 1.0e12); cfg.GetValue(path+"offset", type, rptr.mod[m].offset, -1.0e12, 1.0e12); cfg.GetValue(path+"range", type, rptr.mod[m].range, 0.0, 1609344.0); cfg.GetValue(path+"agl", type, rptr.mod[m].agl, 0.0, 1000.0); - cfg.GetValue("gateway_latitude", estr, rptr.mod[m].latitude, -90.0, 90.0); - cfg.GetValue("gateway_longitude", estr, rptr.mod[m].longitude, -180.0, 180.0); - cfg.GetValue("gateway_desc1", estr, rptr.mod[m].desc1, 0, 20); - cfg.GetValue("gateway_desc2", estr, rptr.mod[m].desc2, 0, 20); - cfg.GetValue("gateway_url", estr, rptr.mod[m].url, 0, 80); // make the long description for the log if (rptr.mod[m].desc1.length()) @@ -250,8 +243,19 @@ bool CQnetGateway::read_config(char *cfgFile) cfg.GetValue(path+"port", estr, g2_external.port, 1024, 65535); cfg.GetValue(path+"header_regen", estr, bool_regen_header); cfg.GetValue(path+"send_qrgs_maps", estr, bool_send_qrgs); - cfg.GetValue(path+"tolink", estr, gate2link, 1, FILENAME_MAX); - cfg.GetValue(path+"fromlink", estr, link2gate, 1, FILENAME_MAX); + cfg.GetValue(path+"gate2link", estr, gate2link, 1, FILENAME_MAX); + cfg.GetValue(path+"link2gate", estr, link2gate, 1, FILENAME_MAX); + cfg.GetValue(path+"modem2gate", estr, modem2gate, 1, FILENAME_MAX); + for (int m=0; m<3; m++) { + if (rptr.mod[m].defined) { + cfg.GetValue(path+"gate2modem"+std::string(1, 'a'+m), estr, gate2modem[m], 1, FILENAME_MAX); + cfg.GetValue(path+"latitude", estr, rptr.mod[m].latitude, -90.0, 90.0); + cfg.GetValue(path+"longitude", estr, rptr.mod[m].longitude, -180.0, 180.0); + cfg.GetValue(path+"desc1", estr, rptr.mod[m].desc1, 0, 20); + cfg.GetValue(path+"desc2", estr, rptr.mod[m].desc2, 0, 20); + cfg.GetValue(path+"url", estr, rptr.mod[m].url, 0, 80); + } + } // APRS path.assign("aprs_"); @@ -1131,7 +1135,7 @@ void CQnetGateway::ProcessG2(ssize_t g2buflen, SDSVT &g2buf) } } -void CQnetGateway::ProcessModem(int mod) +void CQnetGateway::ProcessModem() { char temp_radio_user[CALL_SIZE + 1]; char temp_mod; @@ -1142,7 +1146,7 @@ void CQnetGateway::ProcessModem(int mod) char tempfile[FILENAME_MAX + 1]; SDSVT g2buf; - int recvlen = Modem2Gate[mod].Read(rptrbuf.pkt_id, 58); + int recvlen = Modem2Gate.Read(rptrbuf.pkt_id, 58); if (0 == memcmp(rptrbuf.pkt_id, "DSTR", 4)) { if ( (recvlen==58 || recvlen==29 || recvlen==32) && rptrbuf.flag[0]==0x73 && rptrbuf.flag[1]==0x12 && rptrbuf.flag[2]==0x0 && rptrbuf.vpkt.icm_id==0x20 && (rptrbuf.remaining==0x30 || rptrbuf.remaining==0x13 || rptrbuf.remaining==0x16) ) { @@ -1885,9 +1889,7 @@ void CQnetGateway::Process() FD_ZERO(&fdset); AddFDSet(max_nfds, g2_sock, &fdset); AddFDSet(max_nfds, Link2Gate.GetFD(), &fdset); - for (int i=0; i<3; i++) - if (rptr.mod[i].defined) - AddFDSet(max_nfds, Modem2Gate[i].GetFD(), &fdset); + AddFDSet(max_nfds, Modem2Gate.GetFD(), &fdset); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 20000; // 20 ms @@ -1921,11 +1923,9 @@ void CQnetGateway::Process() } // process packets coming from local repeater modules - for (int mod=0; mod<3; mod++) { - if (keep_running && rptr.mod[mod].defined && FD_ISSET(Modem2Gate[mod].GetFD(), &fdset)) { - ProcessModem(mod); - FD_CLR (Modem2Gate[mod].GetFD(), &fdset); - } + if (keep_running && FD_ISSET(Modem2Gate.GetFD(), &fdset)) { + ProcessModem(); + FD_CLR(Modem2Gate.GetFD(), &fdset); } } @@ -2454,12 +2454,12 @@ int CQnetGateway::Init(char *cfgfile) Gate2Link.SetUp(gate2link.c_str()); if (Link2Gate.Open(link2gate.c_str())) return 1; + if (Modem2Gate.Open(modem2gate.c_str())) + return 1; for (i=0; i<3; i++) { if (rptr.mod[i].defined) { // open unix sockets between qngateway and each defined modem Gate2Modem[i].SetUp(gate2modem[i].c_str()); - if (Modem2Gate[i].Open(modem2gate[i].c_str())) - return 1; } // recording for echotest on local repeater modules recd[i].last_time = 0; @@ -2537,9 +2537,7 @@ CQnetGateway::CQnetGateway() CQnetGateway::~CQnetGateway() { Link2Gate.Close(); - for (int i=0; i<3; i++) { - Modem2Gate[i].Close(); - } + Modem2Gate.Close(); if (g2_sock != -1) { close(g2_sock); diff --git a/QnetGateway.h b/QnetGateway.h index fc05ec8..72c37ee 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -90,10 +90,10 @@ private: SPORTIP g2_external, ircddb; - CUnixDgramReader Link2Gate, Modem2Gate[3]; + CUnixDgramReader Link2Gate, Modem2Gate; CUnixDgramWriter Gate2Link, Gate2Modem[3]; - std::string gate2link, link2gate, gate2modem[3], modem2gate[3]; + std::string gate2link, link2gate, gate2modem[3], modem2gate; std::string OWNER, owner, local_irc_ip, status_file, dtmf_dir, dtmf_file, echotest_dir, irc_pass, qnvoicefile; @@ -170,7 +170,7 @@ private: void ProcessTimeouts(); void ProcessSlowData(unsigned char *data, unsigned short sid); void ProcessG2(ssize_t g2buflen, SDSVT &g2buf); - void ProcessModem(int mod); + void ProcessModem(); bool Flag_is_ok(unsigned char flag); // read configuration file diff --git a/QnetITAP.cpp b/QnetITAP.cpp index bb61e56..189cedd 100644 --- a/QnetITAP.cpp +++ b/QnetITAP.cpp @@ -500,8 +500,8 @@ bool CQnetITAP::ReadConfig(const char *cfgFile) RPTR_MOD = 'A' + assigned_module; cfg.GetValue(itap_path+"_device", type, ITAP_DEVICE, 7, FILENAME_MAX); - cfg.GetValue(itap_path+"_gate2modem"+std::string(1, 'a'+assigned_module), type, gate2modem, 1, FILENAME_MAX); - cfg.GetValue(itap_path+"_modem2gate"+std::string(1, 'a'+assigned_module), type, modem2gate, 1, FILENAME_MAX); + cfg.GetValue("gateway_gate2modem"+std::string(1, 'a'+assigned_module), estr, gate2modem, 1, FILENAME_MAX); + cfg.GetValue("gateway_modem2gate", estr, modem2gate, 1, FILENAME_MAX); itap_path.append("_callsign"); if (cfg.KeyExists(itap_path)) { diff --git a/QnetLink.cpp b/QnetLink.cpp index f79f06a..ad0fe5f 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -212,7 +212,7 @@ void CQnetLink::RptrAckThread(char *arg) memcpy(dsvt.hdr.sfx, "RPTR", 4); calcPFCS(dsvt.title,56); Link2Gate.Write(dsvt.title, 56); - std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)); + //std::this_thread::sleep_for(std::chrono::milliseconds(delay_between)) dsvt.config = 0x20; memcpy(dsvt.vasd.voice, silence, 9); @@ -575,8 +575,8 @@ bool CQnetLink::read_config(const char *cfgFile) saved_max_dongles = max_dongles = (unsigned int)maxdongle; key.assign("gateway_"); - cfg.GetValue(key+"tolink", estr, gate2link, 1, FILENAME_MAX); - cfg.GetValue(key+"fromlink", estr, link2gate, 1, FILENAME_MAX); + cfg.GetValue(key+"gate2link", estr, gate2link, 1, FILENAME_MAX); + cfg.GetValue(key+"link2gate", estr, link2gate, 1, FILENAME_MAX); cfg.GetValue("log_qso", estr, qso_details); @@ -3287,7 +3287,6 @@ void CQnetLink::AudioNotifyThread(SECHO &edata) const unsigned char sdsync[3] = { 0x55U, 0x2DU, 0x16U }; const unsigned char sdsilence[3] = { 0x16U, 0x29U, 0xF5U }; for (count=0; count