/* * 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 * 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" #include "QnetConfigure.h" std::atomic CQnetITAP::keep_running(true); CQnetITAP::CQnetITAP(int mod) : assigned_module(mod) , 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; } if (Gate2Modem.Open(gate2modem.c_str()) || Modem2Gate.Open(modem2gate.c_str())) 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; } 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) { 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, length=%d\n", length); return RT_ERROR; } 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 && 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; } switch (buf[1U]) { case 0x03U: return RT_PONG; case 0x10U: return RT_HEADER; case 0x12U: 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; 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; bool is_alive = false; std::chrono::steady_clock::time_point lastdata = std::chrono::steady_clock::now(); while (keep_running) { fd_set readfds; FD_ZERO(&readfds); FD_SET(serfd, &readfds); FD_SET(ug2m, &readfds); int maxfs = (serfd > ug2m) ? serfd : ug2m; 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 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; } // 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) { printf("no activity from radio for 10 sec. Exiting...\n"); break; } if (0 == ret) { // nothing to read, so do the polling or pinging if (poll_counter++ < 18) { unsigned char poll[3] = { 0xffu, 0xffu, 0xffu }; SendTo((unsigned char)0x03U, poll); } else { unsigned char ping[3] = { 0x02u, 0x02u, 0xffu }; SendTo((unsigned char)0x03U, ping); } continue; } // there is something to read! unsigned char buf[100]; ssize_t len; REPLY_TYPE rt = RT_NOTHING; if (FD_ISSET(serfd, &readfds)) { rt = GetITAPData(buf); if (rt == RT_ERROR) break; if (rt == RT_TIMEOUT) continue; } 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 (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)) 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: if (! is_alive) { printf("Icom Radio is connected.\n"); is_alive = true; } break; default: break; } } } else if (0 == ::memcmp(buf, "DSTR", 4)) { //printf("read %d bytes from QnetGateway\n", (int)len); if (ProcessGateway(len, buf)) break; } } ::close(serfd); Gate2Modem.Close(); Modem2Gate.Close(); } int CQnetITAP::SendTo(const unsigned char length, const unsigned char *buf) { unsigned int ptr = 0; const unsigned int len = (int)length; while (ptr < len) { ssize_t n = ::write(serfd, buf + ptr, len - ptr); if (n < 0) { if (EAGAIN != errno) { printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); return -1; } } if (n > 0) ptr += n; } return len; } bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) { 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 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); 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.r2, itap.header.r1, itap.header.my, itap.header.nm); } else { // write an AMBE packet itap.length = 16U; 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"); 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; if (17 != SendTo(17U, &itap.length)) { 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 = (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 = htons(stream_id); if (41 == len) { // header 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.c_str(), 7); // build r1 dstr.vpkt.hdr.r1[7] = RPTR_MOD; // with module 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! // 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" 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); dstr.vpkt.hdr.flag[0] &= ~0x40U; // clear this bit } 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 = 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 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 = Modem2Gate.Write(dstr.pkt_id, 29); 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; } // process configuration file and return true if there was a problem bool CQnetITAP::ReadConfig(const char *cfgFile) { CQnetConfigure cfg; printf("Reading file %s\n", cfgFile); if (cfg.Initialize(cfgFile)) return true; 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); return true; } } else { fprintf(stderr, "assigned module %c not defined\n", 'a' + assigned_module); return true; } 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); itap_path.append("_callsign"); if (cfg.KeyExists(itap_path)) { if (cfg.GetValue(itap_path, type, RPTR, 3, 6)) return true; } else { itap_path.assign("ircddb_login"); if (cfg.KeyExists(itap_path)) { if (cfg.GetValue(itap_path, estr, RPTR, 3, 6)) return true; } } int l = RPTR.length(); if (l<3 || l>6) { printf("Call '%s' is invalid length!\n", RPTR.c_str()); return true; } else { for (int 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 (3 != argc) { fprintf(stderr, "usage: %s assigned_module 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); 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; } int assigned_module; switch (argv[1][0]) { case '0': 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"); return 1; } CQnetITAP qnitap(assigned_module); qnitap.Run(argv[2]); printf("%s is closing.\n", argv[0]); return 0; }