diff --git a/Makefile b/Makefile index 4ffadc2..194716a 100644 --- a/Makefile +++ b/Makefile @@ -52,8 +52,8 @@ modem : qnmodem qngateway : QnetGateway.o aprs.o UnixDgramSocket.o TCPReaderWriterClient.o QnetConfigure.o QnetDB.o CacheManager.o DStarDecode.o $(IRCOBJS) g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -l sqlite3 -pthread -qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o UnixDgramSocket.o QnetConfigure.o - g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -pthread +qnlink : QnetLink.o DPlusAuthenticator.o TCPReaderWriterClient.o UnixDgramSocket.o QnetConfigure.o QnetDB.o + g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) -l sqlite3 -pthread qnrelay : QnetRelay.o UnixDgramSocket.o QnetConfigure.o g++ $(CPPFLAGS) -o $@ $^ $(LDFLAGS) diff --git a/QnetDB.cpp b/QnetDB.cpp index 7e17431..149cc70 100644 --- a/QnetDB.cpp +++ b/QnetDB.cpp @@ -21,27 +21,55 @@ bool CQnetDB::Open(const char *name) { - if (sqlite3_open(name, &db)) + if (sqlite3_open(name, &db)) { + fprintf(stderr, "CQnetDB::Open: can't open %s\n", name); return true; + } else + return false; +} - std::string sql = "DROP TABLE IF EXISTS LHEARD;"; +bool CQnetDB::Init() +{ + std::string sql("DROP TABLE IF EXISTS LHEARD;"); char *eMsg; if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { - fprintf(stderr, "CQnetDB::Open drop table error: %s\n", eMsg); + fprintf(stderr, "CQnetDB::Open drop table LHEARD error: %s\n", eMsg); sqlite3_free(eMsg); return true; } - sql = "CREATE TABLE LHEARD(" + sql.assign("CREATE TABLE LHEARD(" "mycall TEXT PRIMARY KEY, " "sfx TEXT, " "urcall TEXT, " "lasttime INT NOT NULL" - ") WITHOUT ROWID;"; + ") WITHOUT ROWID;"); + + if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { + fprintf(stderr, "CQnetDB::Open create table LHEARD error: %s\n", eMsg); + sqlite3_free(eMsg); + return true; + } + + sql.assign("DROP TABLE IF EXISTS LINKSTATUS;"); + + if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { + fprintf(stderr, "CQnetDB::Open drop table LINKSTATUS error: %s\n", eMsg); + sqlite3_free(eMsg); + return true; + } + + sql.assign("CREATE TABLE LINKSTATUS(" + "ip_address TEXT PRIMARY KEY, " + "from_mod TEXT NOT NULL, " + "to_callsign TEXT NOT NULL, " + "to_mod TEXT NOT NULL, " + "linked_time INT NOT NULL" + ") WITHOUT ROWID;"); if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { - fprintf(stderr, "CQnetDB::Open create table error: %s\n", eMsg); + fprintf(stderr, "CQnetDB::Open create table LINKSTATUS error: %s\n", eMsg); sqlite3_free(eMsg); return true; } @@ -49,11 +77,11 @@ bool CQnetDB::Open(const char *name) return false; } -bool CQnetDB::Update(const char *mycall, const char *sfx, const char *urcall) +bool CQnetDB::UpdateLH(const char *mycall, const char *sfx, const char *urcall) { if (NULL == db) return false; - std::string sql = "REPLACE INTO LHEARD (mycall,sfx,urcall,lasttime) VALUES ('"; + std::string sql("REPLACE INTO LHEARD (mycall,sfx,urcall,lasttime) VALUES ('"); sql.append(mycall); sql.append("','"); sql.append(sfx); @@ -64,10 +92,83 @@ bool CQnetDB::Update(const char *mycall, const char *sfx, const char *urcall) char *eMsg; if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { - fprintf(stderr, "CQnetDB::Update error: %s\n", eMsg); + fprintf(stderr, "CQnetDB::UpdateLH error: %s\n", eMsg); sqlite3_free(eMsg); return true; } return false; } + +bool CQnetDB::UpdateLS(const char *address, const char from_mod, const char *to_callsign, const char to_mod, time_t linked_time) +{ + if (NULL == db) + return false; + std::string sql = "REPLACE INTO LINKSTATUS (ip_address,from_mod,to_callsign,to_mod,linked_time) VALUES ('"; + sql.append(address); + sql.append("','"); + sql.append(1, from_mod); + sql.append("','"); + sql.append(to_callsign); + sql.append("','"); + sql.append(1, to_mod); + sql.append("',"); + sql.append(std::to_string(linked_time).c_str()); + sql.append(");"); + + char *eMsg; + if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { + fprintf(stderr, "CQnetDB::UpdateLS error: %s\n", eMsg); + sqlite3_free(eMsg); + return true; + } + + return false; +} + +bool CQnetDB::DeleteLS(const char *address) +{ + if (NULL == db) + return false; + std::string sql("DELETE FROM LINKSTATUS WHERE ip_address=='"); + sql.append(address); + sql.append("';"); + + char *eMsg; + if (SQLITE_OK != sqlite3_exec(db, sql.c_str(), NULL, 0, &eMsg)) { + fprintf(stderr, "CQnetDB::DeleteLS error: %s\n", eMsg); + sqlite3_free(eMsg); + return true; + } + + return false; +} + +bool CQnetDB::FindLS(const char mod, std::list &linklist) +{ + if (NULL == db) + return false; + std::string sql("SELECT ip_address,to_callsign,to_mod,linked_time FROM LINKSTATUS WHERE from_mod=='"); + sql.append(1, mod); + sql.append("';"); + + sqlite3_stmt *stmt; + int rval = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0); + if (SQLITE_OK != rval) { + fprintf(stderr, "CQnetDB::FindLS error: %d\n", rval); + return true; + } + + while (SQLITE_ROW == sqlite3_step(stmt)) { + std::string cs((const char *)sqlite3_column_text(stmt, 1)); + std::string mod((const char *)sqlite3_column_text(stmt, 2)); + if (mod.at(0) != 'p') { + cs.resize(7, ' '); + cs.append(mod); + } + linklist.push_back(CLink(cs, sqlite3_column_text(stmt, 0), sqlite3_column_int(stmt, 3))); + } + + sqlite3_finalize(stmt); + return false; +} diff --git a/QnetDB.h b/QnetDB.h index 1f3c4ea..3e4f6a4 100644 --- a/QnetDB.h +++ b/QnetDB.h @@ -19,13 +19,44 @@ #include #include +#include +#include + +class CLink { +public: + CLink(const std::string &call, const unsigned char *addr, time_t ltime) : callsign(call) , address((const char *)addr) , linked_time(ltime) {} + + CLink(const CLink &from) + { + callsign.assign(from.callsign); + address.assign(from.address), + linked_time=from.linked_time; + } + + CLink &operator=(const CLink &from) + { + callsign.assign(from.callsign); + address.assign(from.address), + linked_time=from.linked_time; + return *this; + } + + ~CLink() {} + + std::string callsign, address; + time_t linked_time; +}; class CQnetDB { public: CQnetDB() : db(NULL) {} ~CQnetDB() { if (db) sqlite3_close(db); } bool Open(const char *name); - bool Update(const char *mycall, const char *sfx, const char *urcall); + bool Init(); + bool UpdateLH(const char *mycall, const char *sfx, const char *urcall); + bool UpdateLS(const char *address, const char from_mod, const char *to_callsign, const char to_mod, time_t connect_time); + bool DeleteLS(const char *address); + bool FindLS(const char mod, std::list &linklist); private: sqlite3 *db; diff --git a/QnetGateway.cpp b/QnetGateway.cpp index bbb1757..a4697e2 100644 --- a/QnetGateway.cpp +++ b/QnetGateway.cpp @@ -114,42 +114,19 @@ void CQnetGateway::PrintCallsigns(const std::string &key, const std::set linklist; + if (qnDB.FindLS(mod, linklist)) + return; - 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; + auto count = linklist.size(); + if (count != 1) + printf("set_dest_rptr() returned %d link sets\n", int(count)); + if (0 == count) + return; + + call.assign(linklist.front().callsign); } /* compute checksum */ @@ -330,7 +307,6 @@ bool CQnetGateway::ReadConfig(char *cfgFile) path.assign("file_"); cfg.GetValue(path+"echotest", estr, FILE_ECHOTEST, 2, FILENAME_MAX); cfg.GetValue(path+"dtmf", estr, FILE_DTMF, 2, FILENAME_MAX); - cfg.GetValue(path+"status", estr, FILE_STATUS, 2, FILENAME_MAX); cfg.GetValue(path+"qnvoice_file", estr, FILE_QNVOICE_FILE, 2, FILENAME_MAX); // timing @@ -805,11 +781,8 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) // 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'; - } + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) + set_dest_rptr(i+'A', band_txt[i].dest_rptr); int x = FindIndex(i); if (x >= 0) @@ -840,11 +813,9 @@ void CQnetGateway::ProcessSlowData(unsigned char *data, unsigned short sid) 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'; - } + if (memcmp(band_txt[i].lh_yrcall, "CQCQCQ", 6) == 0) + set_dest_rptr(i+'A', band_txt[i].dest_rptr); + // we have the 20-character message, send it to the server... int x = FindIndex(i); if (x >= 0) @@ -999,7 +970,7 @@ void CQnetGateway::ProcessG2(const ssize_t g2buflen, const SDSVT &g2buf, const i std::string urcall((const char *)g2buf.hdr.urcall, 8); rtrim(mycall); rtrim(sfx); - qnDB.Update(mycall.c_str(), sfx.c_str(), urcall.c_str()); + qnDB.UpdateLH(mycall.c_str(), sfx.c_str(), urcall.c_str()); } Gate2Modem[i].Write(g2buf.title, 56); @@ -1319,8 +1290,7 @@ void CQnetGateway::ProcessModem() // 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, dsvt.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + band_txt[i].dest_rptr.assign((char *)dsvt.hdr.rpt1, 8); // send to remote gateway for (int j=0; j<5; j++) @@ -1373,8 +1343,7 @@ void CQnetGateway::ProcessModem() // 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, dsvt.hdr.rpt1, 8); - band_txt[i].dest_rptr[CALL_SIZE] = '\0'; + band_txt[i].dest_rptr.assign((char *)dsvt.hdr.rpt1, 8); /* send to remote gateway */ for (int j=0; j<5; j++) @@ -1397,9 +1366,8 @@ void CQnetGateway::ProcessModem() 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, dsvt.hdr.rpt2, 8); - band_txt[i].dest_rptr[7] = rptr.at(7); - band_txt[i].dest_rptr[8] = '\0'; + band_txt[i].dest_rptr.assign((char *)dsvt.hdr.rpt2, 7); + band_txt[i].dest_rptr.append(1, rptr.at(7)); i = rptr.at(7) - 'A'; @@ -1557,8 +1525,7 @@ void CQnetGateway::ProcessModem() 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, dsvt.hdr.rpt2, 8); - band_txt[i].dest_rptr[8] = '\0'; + band_txt[i].dest_rptr.append((char *)dsvt.hdr.rpt2, 8); } i = dsvt.hdr.rpt2[7] - 'A'; @@ -1618,9 +1585,7 @@ void CQnetGateway::ProcessModem() 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'; + set_dest_rptr(i+'A', band_txt[i].dest_rptr); } int x = FindIndex(i); if (x >= 0) @@ -2259,8 +2224,29 @@ bool CQnetGateway::Init(char *cfgfile) std::signal(SIGINT, sigCatch); std::signal(SIGHUP, sigCatch); - for (i = 0; i < 3; i++) - memset(&band_txt[0], 0, sizeof(SBANDTXT)); + for (i=0; i<3; i++) { + band_txt[i].streamID = 0; + memset(band_txt[i].flags, 0, 3); + memset(band_txt[i].lh_mycall, 0, 9); + memset(band_txt[i].lh_sfx, 0, 5); + memset(band_txt[i].lh_yrcall, 0, 9); + memset(band_txt[i].lh_rpt1, 0, 9); + memset(band_txt[i].lh_rpt2, 0, 9); + band_txt[i].last_time = 0; + memset(band_txt[i].txt, 0, 64); // Only 20 are used + band_txt[i].txt_cnt = 0; + band_txt[i].sent_key_on_msg = false; + band_txt[i].dest_rptr.clear(); + memset(band_txt[i].temp_line, 0, 256); + band_txt[i].temp_line_cnt = 0U; + memset(band_txt[i].gprmc, 0, 256); + memset(band_txt[i].gpid, 0, 256); + 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; + } /* process configuration file */ if ( ReadConfig(cfgfile) ) { @@ -2269,13 +2255,12 @@ bool CQnetGateway::Init(char *cfgfile) } // open database - if (showLastHeard) { - std::string dbname(CFG_DIR); - dbname.append("/"); - dbname.append(DASH_SQL_NAME); - if (qnDB.Open(dbname.c_str())) - return true; - } + std::string fname(CFG_DIR); + fname.append("/"); + fname.append(DASH_SQL_NAME); + if (qnDB.Open(fname.c_str()) || qnDB.Init()) + return true; + playNotInCache = false; diff --git a/QnetGateway.h b/QnetGateway.h index fdb3693..9004052 100644 --- a/QnetGateway.h +++ b/QnetGateway.h @@ -61,7 +61,7 @@ using SBANDTXT = struct band_txt_tag { unsigned short txt_cnt; bool sent_key_on_msg; - char dest_rptr[CALL_SIZE + 1]; + std::string dest_rptr; // try to process GPS mode: GPRMC and ID char temp_line[256]; @@ -103,7 +103,7 @@ private: std::string gate2link, link2gate, gate2modem[3], modem2gate; - std::string OWNER, owner, FILE_STATUS, FILE_DTMF, FILE_ECHOTEST, IRCDDB_PASSWORD[2], FILE_QNVOICE_FILE, DASH_SQL_NAME, DASH_SHOW_ORDER; + std::string OWNER, owner, FILE_DTMF, FILE_ECHOTEST, IRCDDB_PASSWORD[2], FILE_QNVOICE_FILE, DASH_SQL_NAME, DASH_SHOW_ORDER; bool GATEWAY_SEND_QRGS_MAP, GATEWAY_HEADER_REGEN, APRS_ENABLE, playNotInCache, showLastHeard; bool LOG_DEBUG, LOG_IRC, LOG_DTMF, LOG_QSO; @@ -191,6 +191,6 @@ private: void qrgs_and_maps(); - void set_dest_rptr(int mod_ndx, char *dest_rptr); + void set_dest_rptr(const char mod, std::string &call); bool validate_csum(SBANDTXT &bt, bool is_gps); }; diff --git a/QnetLink.cpp b/QnetLink.cpp index fbe3be3..9df0e96 100644 --- a/QnetLink.cpp +++ b/QnetLink.cpp @@ -52,10 +52,13 @@ #include "QnetConfigure.h" #include "QnetLink.h" -#define LINK_VERSION "QnetLink-7.3" +#define LINK_VERSION "QnetLink-327" #ifndef BIN_DIR #define BIN_DIR "/usr/local/bin" #endif +#ifndef CFG_DIR +#define CFG_DIR "/usr/local/etc" +#endif std::atomic CQnetLink::keep_running(true); @@ -121,8 +124,6 @@ bool CQnetLink::resolve_rmt(const char *name, const unsigned short port, CSockAd /* send keepalive to donglers */ void CQnetLink::send_heartbeat() { - bool removed = false; - for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; sendto(ref_g2_sock, REF_ACK, 3, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); @@ -131,16 +132,12 @@ void CQnetLink::send_heartbeat() inbound->countdown --; if (inbound->countdown < 0) { - removed = true; 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; + qnDB.DeleteLS(pos->first.c_str()); + delete pos->second; inbound_list.erase(pos); } } - if (removed) - print_status_file(); } void CQnetLink::rptr_ack(short i) @@ -290,41 +287,6 @@ void CQnetLink::RptrAckThread(char *arg) } } -void CQnetLink::print_status_file() -{ - FILE *statusfp = fopen(status_file.c_str(), "w"); - if (!statusfp) - printf("Failed to create status file %s\n", status_file.c_str()); - else { - setvbuf(statusfp, (char *)NULL, _IOLBF, 0); - struct tm tm1; - time_t tnow; - const char *fstr = "%c,%s,%c,%s,%02d%02d%02d,%02d:%02d:%02d\n"; - time(&tnow); - localtime_r(&tnow, &tm1); - - /* print connected donglers */ - for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { - fprintf(statusfp, fstr, 'p', pos->second->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 */ - SLINKFAMILY fam; - memcpy(fam.title, "LINK", 4); - for (int i=0; i<3;i++) { - if (to_remote_g2[i].is_connected) { - fprintf(statusfp, fstr, to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, to_remote_g2[i].addr.GetAddress(), tm1.tm_mon+1, tm1.tm_mday ,tm1.tm_year % 100, tm1.tm_hour, tm1.tm_min, tm1.tm_sec); - // also inform gateway - fam.family[i] = to_remote_g2[i].addr.GetFamily(); - } else { - fam.family[i] = AF_UNSPEC; - } - } - Link2Gate.Write(fam.title, sizeof(SLINKFAMILY)); - fclose(statusfp); - } -} - /* Open text file of repeaters, reflectors */ bool CQnetLink::load_gwys(const std::string &filename) { @@ -611,7 +573,6 @@ 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+"qnvoice_file", estr, qnvoice_file, 2, FILENAME_MAX); cfg.GetValue(key+"announce_dir", estr, announce_dir, 2, FILENAME_MAX); @@ -632,6 +593,8 @@ bool CQnetLink::read_config(const char *cfgFile) } cfg.GetValue(key+"priority", estr, dplus_priority); + cfg.GetValue("dash_sql_filename", estr, dash_sql_name, 2, 32); + return false; } @@ -899,7 +862,6 @@ void CQnetLink::g2link(const char from_mod, const char *call, const char to_mod) to_remote_g2[i].is_connected = true; printf("Local module %c is also connected to %s %c\n", from_mod, call, to_mod); - print_status_file(); tracing[i].last_time = time(NULL); // announce it here @@ -940,7 +902,7 @@ void CQnetLink::Process() unsigned char dcs_buf[1000];; char call[CALL_SIZE + 1]; - char ip[INET6_ADDRSTRLEN + 1]; + std::string ip; bool found = false; unsigned char your[3] = { 'C', 'C', 'C' }; @@ -1067,7 +1029,7 @@ void CQnetLink::Process() printf("Unlinked from [%s] mod %c, TIMEOUT...\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); sprintf(notify_msg[i], "%c_unlinked.dat_LINK_TIMEOUT", to_remote_g2[i].from_mod); - + qnDB.DeleteLS(to_remote_g2[i].addr.GetAddress()); if (to_remote_g2[i].auto_link) { char cs[CALL_SIZE+1]; memcpy(cs, to_remote_g2[i].cs, CALL_SIZE+1); // call is passed by pointer so we have to copy it @@ -1080,10 +1042,6 @@ void CQnetLink::Process() to_remote_g2[i].is_connected = false; to_remote_g2[i].in_streamid = 0x0; } - - - print_status_file(); - } } @@ -1134,7 +1092,7 @@ void CQnetLink::Process() for (int j=0; j<2; j++) sendto(dcs_g2_sock, cmd_2_dcs, 19 ,0, to_remote_g2[i].addr.GetPointer(), to_remote_g2[i].addr.GetSize()); } - + qnDB.DeleteLS(to_remote_g2[i].addr.GetAddress()); sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED_TIMEOUT", to_remote_g2[i].from_mod); to_remote_g2[i].cs[0] = '\0'; @@ -1143,8 +1101,6 @@ void CQnetLink::Process() to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; to_remote_g2[i].in_streamid = 0x0; - - print_status_file(); } } } @@ -1188,8 +1144,7 @@ void CQnetLink::Process() unsigned char buf[100]; int length = recvfrom(xrf_g2_sock, buf, 100, 0, fromDst4.GetPointer(), &fromlen); - strncpy(ip, fromDst4.GetAddress(), INET6_ADDRSTRLEN); - ip[INET6_ADDRSTRLEN] = '\0'; + ip.assign(fromDst4.GetAddress()); memcpy(call, buf, CALL_SIZE); call[CALL_SIZE] = '\0'; @@ -1207,7 +1162,6 @@ void CQnetLink::Process() to_remote_g2[i].is_connected = true; printf("Connected from: %.*s\n", length - 1, buf); - print_status_file(); strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); @@ -1231,7 +1185,7 @@ void CQnetLink::Process() to_remote_g2[i].is_connected = true; printf("Connected from: [%s] %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); - print_status_file(); + qnDB.UpdateLS(fromDst4.GetAddress(), to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, time(NULL)); strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); @@ -1250,8 +1204,6 @@ void CQnetLink::Process() to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; to_remote_g2[i].in_streamid = 0x0; - - print_status_file(); } } } @@ -1267,7 +1219,7 @@ void CQnetLink::Process() 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].cs, to_remote_g2[i].to_mod); - + qnDB.DeleteLS(to_remote_g2[i].addr.GetAddress()); sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); to_remote_g2[i].cs[0] = '\0'; @@ -1276,8 +1228,6 @@ void CQnetLink::Process() to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; to_remote_g2[i].in_streamid = 0x0; - - print_status_file(); } else /* link request from a remote repeater that we know */ if ((i==0 && buf[9]=='A') || (i==1 && buf[9]=='B') || (i==2 && buf[9]=='C')) { @@ -1302,7 +1252,7 @@ void CQnetLink::Process() tracing[i].last_time = time(NULL); - print_status_file(); + /* send back an ACK */ memcpy(buf + 10, "ACK", 4); @@ -1336,12 +1286,12 @@ void CQnetLink::Process() 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.c_str()); i = -1; } else { int rc = regexec(&preg, call, 0, NULL, 0); if (rc != 0) { - printf("Invalid repeater %s,%s requesting to link\n", call, ip); + printf("Invalid repeater %s,%s requesting to link\n", call, ip.c_str()); i = -1; } } @@ -1366,9 +1316,8 @@ void CQnetLink::Process() to_remote_g2[i].is_connected = true; to_remote_g2[i].in_streamid = 0x0; - print_status_file(); - tracing[i].last_time = time(NULL); + qnDB.UpdateLS(to_remote_g2[i].addr.GetAddress(), to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, tracing[i].last_time); 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].cs, to_remote_g2[i].to_mod); @@ -1494,7 +1443,9 @@ void CQnetLink::Process() /* no changes here */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; - if (! (fromDst4 == inbound->addr)) { + if (fromDst4 == inbound->addr) { + inbound->mod = dsvt.hdr.rpt1[7]; + } else { SREFDSVT rdsvt; rdsvt.head[0] = (unsigned char)(58 & 0xFF); rdsvt.head[1] = (unsigned char)(58 >> 8 & 0x1F); @@ -1502,8 +1453,7 @@ void CQnetLink::Process() memcpy(rdsvt.dsvt.title, dsvt.title, 56); sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); - } else - inbound->mod = dsvt.hdr.rpt1[7]; + } } /* send the data to the repeater/reflector that is linked to our RPT1 */ @@ -1737,8 +1687,7 @@ void CQnetLink::Process() unsigned char buf[100]; int length = recvfrom(ref_g2_sock, buf, 100, 0, (struct sockaddr *)&fromDst4,&fromlen); - strncpy(ip, fromDst4.GetAddress(), INET6_ADDRSTRLEN+1); - ip[INET_ADDRSTRLEN] = '\0'; + ip.assign(fromDst4.GetAddress()); found = false; @@ -2052,14 +2001,13 @@ void CQnetLink::Process() auto pos = inbound_list.find(ip); if (pos != inbound_list.end()) { - SINBOUND *inbound = (SINBOUND *)pos->second; + qnDB.DeleteLS(pos->first.c_str()); + SINBOUND *inbound = pos->second; if (memcmp(inbound->call, "1NFO", 4) != 0) printf("Call %s disconnected\n", inbound->call); - free(pos->second); - pos->second = NULL; + delete pos->second; inbound_list.erase(pos); } - print_status_file(); } for (int i=0; i<3; i++) { @@ -2100,9 +2048,9 @@ void CQnetLink::Process() 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].cs, to_remote_g2[i].to_mod); - print_status_file(); tracing[i].last_time = time(NULL); + qnDB.UpdateLS(to_remote_g2[i].addr.GetAddress(), to_remote_g2[i].from_mod, linked_remote_system, to_remote_g2[i].to_mod, tracing[i].last_time); strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); @@ -2173,7 +2121,7 @@ void CQnetLink::Process() */ 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()); + printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip.c_str(), (int)inbound_list.size()); else 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) { @@ -2188,14 +2136,14 @@ void CQnetLink::Process() } if (memcmp(call, "1NFO", 4)) - printf("Inbound DONGLE-p CALL=%s, ip=%s, DV=%.8s\n", call, ip, buf + 20); + printf("Inbound DONGLE-p CALL=%s, ip=%s, DV=%.8s\n", call, ip.c_str(), 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()); + printf("Inbound DONGLE-p connection from %s but over the max_dongles limit of %d\n", ip.c_str(), (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); + // printf("Incoming call [%s] from %s not an ADMIN\n", call, ip.c_str()); else if (regexec(&preg, call, 0, NULL, 0) != 0) { - printf("Invalid dongle callsign: CALL=%s,ip=%s\n", call, ip); + printf("Invalid dongle callsign: CALL=%s,ip=%s\n", call, ip.c_str()); buf[0] = 8; buf[4] = 70; @@ -2206,7 +2154,7 @@ void CQnetLink::Process() 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)); + SINBOUND *inbound = new SINBOUND; if (inbound) { inbound->countdown = TIMEOUT; inbound->addr = fromDst4; @@ -2224,38 +2172,28 @@ void CQnetLink::Process() auto insert_pair = inbound_list.insert(std::pair(ip, inbound)); if (insert_pair.second) { 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()); + printf("new CALL=%s, DONGLE-p, ip=%s, users=%d\n", inbound->call, ip.c_str(), (int)inbound_list.size()); buf[0] = 8; - buf[4] = 79; - buf[5] = 75; - buf[6] = 82; - buf[7] = 87; + memcpy(buf+4, "OKAY", 4); sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); - print_status_file(); + qnDB.UpdateLS(ip.c_str(), 'p', inbound->call, 'p', time(NULL)); } else { - printf("failed to add CALL=%s,ip=%s\n",inbound->call,ip); - free(inbound); - inbound = NULL; + printf("failed to add CALL=%s,ip=%s\n", inbound->call, ip.c_str()); + delete inbound; buf[0] = 8; - buf[4] = 70; - buf[5] = 65; - buf[6] = 73; - buf[7] = 76; + memcpy(buf+4, "FAIL", 4); sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } } else { - printf("malloc() failed for call=%s,ip=%s\n",call,ip); + printf("new SINBOUND failed for call=%s,ip=%s\n", call, ip.c_str()); buf[0] = 8; - buf[4] = 70; - buf[5] = 65; - buf[6] = 73; - buf[7] = 76; + memcpy(buf+4, "FAIL", 4); sendto(ref_g2_sock, buf, 8, 0, (struct sockaddr *)&fromDst4, sizeof(fromDst4)); } @@ -2378,10 +2316,10 @@ void CQnetLink::Process() /* send the data to the donglers */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; - if (! (fromDst4 == inbound->addr)) { - sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); - } else + if (fromDst4 == inbound->addr) inbound->mod = rdsvt.dsvt.hdr.rpt1[7]; + else + sendto(ref_g2_sock, rdsvt.head, 58, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } if ((! (to_remote_g2[i].addr==fromDst4)) && to_remote_g2[i].is_connected) { @@ -2493,8 +2431,7 @@ void CQnetLink::Process() socklen_t fromlen = sizeof(struct sockaddr_in); int length = recvfrom(dcs_g2_sock, dcs_buf, 1000, 0, (struct sockaddr *)&fromDst4, &fromlen); - strncpy(ip, fromDst4.GetAddress(), INET6_ADDRSTRLEN); - ip[INET6_ADDRSTRLEN] = '\0'; + ip.assign(fromDst4.GetAddress()); /* header, audio */ if (dcs_buf[0]=='0' && dcs_buf[1]=='0' && dcs_buf[2]=='0' && dcs_buf[3]=='1') { @@ -2658,7 +2595,7 @@ void CQnetLink::Process() to_remote_g2[i].is_connected = true; printf("Connected from: %.*s\n", 8, dcs_buf); - print_status_file(); + qnDB.UpdateLS(to_remote_g2[i].addr.GetAddress(), to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod, tracing[i].last_time); strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); @@ -2689,7 +2626,7 @@ void CQnetLink::Process() to_remote_g2[i].is_connected = true; printf("Connected from: %.*s\n", 8, to_remote_g2[i].cs); - print_status_file(); + qnDB.UpdateLS(to_remote_g2[i].addr.GetAddress(), to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].from_mod, tracing[i].last_time); strcpy(linked_remote_system, to_remote_g2[i].cs); space_p = strchr(linked_remote_system, ' '); @@ -2701,15 +2638,13 @@ void CQnetLink::Process() printf("Link module %c to [%s] %c is unlinked\n", to_remote_g2[i].from_mod, to_remote_g2[i].cs, to_remote_g2[i].to_mod); sprintf(notify_msg[i], "%c_failed_link.dat_UNLINKED", to_remote_g2[i].from_mod); - + qnDB.DeleteLS(to_remote_g2[i].addr.GetAddress()); to_remote_g2[i].cs[0] = '\0'; to_remote_g2[i].addr.Clear(); 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(); } } } @@ -2854,7 +2789,7 @@ void CQnetLink::Process() printf("Unlinked from [%s] mod %c\n", to_remote_g2[i].cs, to_remote_g2[i].to_mod); sprintf(notify_msg[i], "%c_unlinked.dat_UNLINKED", to_remote_g2[i].from_mod); - + qnDB.DeleteLS(to_remote_g2[i].addr.GetAddress()); /* now zero out this entry */ to_remote_g2[i].cs[0] = '\0'; to_remote_g2[i].addr.Clear(); @@ -2862,8 +2797,6 @@ void CQnetLink::Process() to_remote_g2[i].countdown = 0; to_remote_g2[i].is_connected = false; to_remote_g2[i].in_streamid = 0x0; - - print_status_file(); } else { sprintf(notify_msg[i], "%c_already_unlinked.dat_UNLINKED", dsvt.hdr.rpt1[7]); } @@ -3423,7 +3356,12 @@ bool CQnetLink::Init(const char *cfgfile) printf("Failed to process config file %s\n", cfgfile); return true; } - print_status_file(); + // open sqlite + std::string fname(CFG_DIR); + fname.append("/"); + fname.append(dash_sql_name); + if (qnDB.Open(fname.c_str())) + return true; /* Open DB */ if (!load_gwys(gwys)) @@ -3503,11 +3441,11 @@ void CQnetLink::Shutdown() /* tell inbound dongles we are down */ for (auto pos = inbound_list.begin(); pos != inbound_list.end(); pos++) { SINBOUND *inbound = (SINBOUND *)pos->second; + qnDB.DeleteLS(pos->first.c_str()); sendto(ref_g2_sock, queryCommand, 5, 0, inbound->addr.GetPointer(), inbound->addr.GetSize()); } inbound_list.clear(); - print_status_file(); srv_close(); return; diff --git a/QnetLink.h b/QnetLink.h index 77fb87b..5471edd 100644 --- a/QnetLink.h +++ b/QnetLink.h @@ -32,6 +32,7 @@ #include "UnixDgramSocket.h" #include "SockAddress.h" #include "Timer.h" +#include "QnetDB.h" /*** version number must be x.xx ***/ #define CALL_SIZE 8 @@ -66,6 +67,12 @@ using SINBOUND = struct inbound_tag { char client; // dvap, dvdongle }; +using STRACING = struct tracing_tag { + unsigned short streamid; + time_t last_time; // last time RF user talked +}; + + class CQnetLink { public: // functions @@ -86,7 +93,6 @@ private: void srv_close(); static void sigCatch(int signum); void g2link(const char from_mod, const char *call, const char to_mod); - void print_status_file(); void send_heartbeat(); bool resolve_rmt(const char *name, const unsigned short port, CSockAddress &addr); void rptr_ack(short i); @@ -95,7 +101,7 @@ private: void RptrAckThread(char *arg); /* configuration data */ - std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, status_file, qnvoice_file, announce_dir; + std::string login_call, owner, to_g2_external_ip, my_g2_link_ip, gwys, dash_sql_name, qnvoice_file, announce_dir; bool only_admin_login, only_link_unlink, qso_details, log_debug, bool_rptr_ack, announce; bool dplus_authorize, dplus_reflectors, dplus_repeaters, dplus_priority; unsigned short rmt_xrf_port, rmt_ref_port, rmt_dcs_port, my_g2_link_port, to_g2_external_port; @@ -132,10 +138,7 @@ private: SDSVT fromrptr_torptr_brd; short brd_from_rptr_idx; - struct tracing_tag { - unsigned short streamid; - time_t last_time; // last time RF user talked - } tracing[3]; + STRACING tracing[3]; // input from remote int xrf_g2_sock, ref_g2_sock, dcs_g2_sock; @@ -178,6 +181,6 @@ private: } old_sid[3]; CRandom Random; - + CQnetDB qnDB; std::vector speak; }; diff --git a/defaults b/defaults index 25af934..42b4c27 100644 --- a/defaults +++ b/defaults @@ -193,7 +193,6 @@ dplus_priority_d=true # set to true if you want DPlus reflector read after # # 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='/var/local' # echo dat files will end up here file_qnvoice_file_d='/tmp/qnvoice.txt' # where qnvoice will create the play command diff --git a/index.php b/index.php index 200451e..10f8cb9 100644 --- a/index.php +++ b/index.php @@ -58,23 +58,6 @@ function GetIP(string $type) return $ip; } -function GetStatus(string $mod, array &$kv) -{ - $mod = strtoupper(substr($mod, 0, 1)); - if (array_key_exists('file_status', $kv)) - $file = $kv['file_status']; - else - $file = '/usr/local/etc/rptr_status'; - if ($lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) { - foreach ($lines as $line) { - $words = explode(',', $line); - if ($words[0] == $mod) - return $words; - } - } - return explode(',', ',,,,,'); -} - function SecToString(int $sec) { if ($sec >= 86400) return sprintf("%0.2f days", $sec/86400); @@ -194,33 +177,53 @@ foreach($showlist as $section) { break; case 'MO': echo 'Modules:
', "\n"; + $dbname = $cfgdir.'/'.GetCFGValues('dash_sql_filename'); + $db = new SQLiet3($dbname, SQLITE3_OPEN_READ_ONLY); echo "\n"; - echo '', "\n"; + echo '', "\n"; foreach (array('a', 'b', 'c') as $mod) { $module = 'module_'.$mod; if (array_key_exists($module, $cfg)) { - $configured[] = strtoupper($mod); $freq = 0.0; if (array_key_exists($module.'_tx_frequency', $cfg)) $freq = $cfg[$module.'_tx_frequency']; else if (array_key_exists($module.'_frequency', $cfg)) $freq = $cfg[$module.'_frequency']; - $stat = GetStatus($mod, $cfg); - if (8==strlen($stat[1]) && 1==strlen($stat[2])) - $linkstatus = substr($stat[1], 0, 7).$stat[2]; - else - $linkstatus = 'Unlinked'; - echo '',"\n"; + $ss = 'SELECT ip_address,to_callsign,to_mod,strftime("%s","now")-link_time FROM LINKSTATUS WHERE from_mod=' . "'" . strtoupper($mod) . "';"; + if ($stmnt = $db->prepare($ss)) { + if ($result = $stmnt->execute()) { + if ($row = $result->FetchArray(SQLITE3_NUM)) { + $linkstatus = str_pad(trim($row[1]), 7).$row[2]; + $address = $row[0]; + $ctime = SecToString(intval($row[3])); + } else { + $linkstatus = 'Unlinked'; + $address = ''; + $ctime = ''; + } + $result->finalize(); + } + $stmnt->close(); + } + echo '', "\n"; } } echo '
ModuleModemFrequencyLinkLink IP
ModuleModemFrequencyLinkLinked TimeLink IP
',strtoupper($mod),'',$cfg[$module],'',$freq,'',$linkstatus,'',$stat[3],'
', strtoupper($mod), '',$cfg[$module], '', $freq, '', $linkstatus, '', $ctime, '', $address, '

', "\n"; + $db->close(); break; case 'UR': echo 'Send URCall:
', "\n"; echo '
', "\n"; - if (count($configured) > 1) { + $mods = array(); + foreach (array('a', 'b', 'c') as $mod) { + $module = 'module_'.$mod; + if (array_key_exists($module, $cfg)) { + $mods[] = strtoupper($mod); + } + } + if (count($mods) > 1) { echo 'Module: ', "\n"; - foreach ($configured as $mod) { + foreach ($mods as $mod) { echo '', $mod, '
', "\n"; } } else