You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
823 lines
20 KiB
823 lines
20 KiB
/*
|
|
* Copyright (C) 2018-2020 by Thomas A. Early N7TAE
|
|
*
|
|
* CQnetITAP::GetITAPData() is based on some code that is...
|
|
* Copyright (C) 2011-2015,2018,2020 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 <exception>
|
|
#include <cstdio>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <csignal>
|
|
#include <ctime>
|
|
#include <cstdlib>
|
|
#include <netdb.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <termios.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <memory>
|
|
|
|
#include "QnetITAP.h"
|
|
#include "QnetTypeDefs.h"
|
|
#include "QnetConfigure.h"
|
|
#include "Timer.h"
|
|
|
|
#define ITAP_VERSION "QnetITAP-40419"
|
|
|
|
bool CQnetITAP::Initialize(const std::string &cfgfile)
|
|
{
|
|
if (ReadConfig(cfgfile))
|
|
return true;
|
|
|
|
std::string name("Gate2Modem");
|
|
std::string(1, RPTR_MOD);
|
|
printf("Opening %s\n", name.c_str());
|
|
if (FromGate.Open(name.c_str()))
|
|
return true;
|
|
name.assign("Modem");
|
|
name.append(1, RPTR_MOD);
|
|
name.append("2Gate");
|
|
ToGate.SetUp(name.c_str());
|
|
|
|
serfd = OpenITAP();
|
|
if (serfd < 0)
|
|
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;
|
|
}
|
|
|
|
void CQnetITAP::DumpSerialPacket(const char *title, const unsigned char *buf)
|
|
{
|
|
printf("%s: ", title);
|
|
if (buf[0] > 41)
|
|
{
|
|
printf("UNKNOWN: length=%u", (unsigned)buf[0]);
|
|
return;
|
|
}
|
|
SITAP itap;
|
|
memcpy(&itap, buf, buf[0]);
|
|
switch (itap.type)
|
|
{
|
|
case 0x03U: //pong
|
|
printf("Pong\n");
|
|
break;
|
|
case 0x10U: // header
|
|
case 0x20U:
|
|
printf("Header ur=%8.8s r1=%8.8s r2=%8.8s my=%8.8s/%4.4s", itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm);
|
|
break;
|
|
case 0x12U: // data
|
|
case 0x22U:
|
|
printf("Data count=%u seq=%u f=%02u%02u%02u a=%02u%02u%02u%02u%02u%02u%02u%02u%02u t=%02u%02u%02u\n", itap.voice.counter, itap.voice.sequence, itap.header.flag[0], itap.header.flag[1], itap.header.flag[2], itap.voice.ambe[0], itap.voice.ambe[1], itap.voice.ambe[2], itap.voice.ambe[3], itap.voice.ambe[4], itap.voice.ambe[5], itap.voice.ambe[6], itap.voice.ambe[7], itap.voice.ambe[8], itap.voice.text[0], itap.voice.text[1], itap.voice.text[2]);
|
|
break;
|
|
case 0x11U: // header acknowledgement
|
|
case 0x21U:
|
|
printf("Header acknowledgement code=%02u", itap.header.flag[0]);
|
|
break;
|
|
case 0x13U: // data acknowledgment
|
|
case 0x23U:
|
|
printf("Data acknowledgement seq=%02u code=%02u", itap.header.flag[0], itap.header.flag[1]);
|
|
break;
|
|
default:
|
|
printf("UNKNOWN packet buf[0] = 0x%02u\n", buf[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf)
|
|
{
|
|
// Some methods adapted from Jonathan G4KLX's CIcomController::GetResponse()
|
|
// and CSerialController::read()
|
|
|
|
// Get the buffer size 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)
|
|
{
|
|
|
|
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()
|
|
{
|
|
unsigned int poll_counter = 0;
|
|
bool initialized = false;
|
|
bool alive = true;
|
|
acknowledged = true;
|
|
CTimer ackTimer;
|
|
CTimer lastdataTimer;
|
|
CTimer pingTimer;
|
|
double pingtime = 0.001;
|
|
const double ackwait = AP_MODE ? 0.4 : 0.06;
|
|
int ug2m = FromGate.GetFD();
|
|
printf("gate2modem=%d, serial=%d\n", ug2m, serfd);
|
|
|
|
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 = 0;
|
|
tv.tv_usec = 5000;
|
|
|
|
// don't care about writefds and exceptfds:
|
|
// and we'll wait for 5 ms
|
|
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
|
|
if (10.0 < lastdataTimer.time())
|
|
{
|
|
printf("no activity from radio for 10 sec. Restarting...\n");
|
|
alive = false;
|
|
}
|
|
|
|
if (alive && keep_running && (pingTimer.time() >= pingtime))
|
|
{
|
|
if (poll_counter < 18 )
|
|
{
|
|
const unsigned char poll[2] = { 0xffu, 0xffu };
|
|
SendToIcom(poll);
|
|
if (poll_counter++ == 17)
|
|
pingtime = 1.0;
|
|
}
|
|
else
|
|
{
|
|
const unsigned char ping[2] = { 0x02u, 0x02u };
|
|
SendToIcom(ping);
|
|
}
|
|
pingTimer.start();
|
|
}
|
|
|
|
unsigned char buf[100];
|
|
|
|
if (keep_running && FD_ISSET(serfd, &readfds)) // there is something to read from the Icom!
|
|
{
|
|
switch (GetITAPData(buf))
|
|
{
|
|
case RT_ERROR:
|
|
alive = false;
|
|
break;
|
|
case RT_HEADER:
|
|
{
|
|
unsigned char ack_header[3] = { 0x03U, 0x11U, 0x0U };
|
|
SendToIcom(ack_header);
|
|
}
|
|
if (ProcessITAP(buf))
|
|
keep_running = false;
|
|
lastdataTimer.start();
|
|
break;
|
|
case RT_DATA:
|
|
{
|
|
unsigned char ack_voice[4] = { 0x04U, 0x13U, 0x0U, 0x0U };
|
|
ack_voice[2] = buf[2];
|
|
SendToIcom(ack_voice);
|
|
}
|
|
if (ProcessITAP(buf))
|
|
keep_running = false;
|
|
lastdataTimer.start();
|
|
break;
|
|
case RT_PONG:
|
|
if (! initialized)
|
|
{
|
|
if (LOG_DEBUG)
|
|
{
|
|
auto count = queue.size();
|
|
if (count)
|
|
printf("%u packets in queue. Icom radio is connected.", (unsigned int)count);
|
|
}
|
|
else
|
|
printf("Icom Radio is connected.\n");
|
|
initialized = true;
|
|
}
|
|
lastdataTimer.start();
|
|
break;
|
|
case RT_HEADER_ACK:
|
|
if (acknowledged)
|
|
{
|
|
fprintf(stderr, "ERROR: Header already acknowledged!\n");
|
|
}
|
|
else
|
|
{
|
|
if (0x0U == buf[2])
|
|
acknowledged = true;
|
|
}
|
|
lastdataTimer.start();
|
|
break;
|
|
case RT_DATA_ACK:
|
|
if (acknowledged)
|
|
{
|
|
fprintf(stderr, "ERROR: voice frame %d already acknowledged!\n", (int)buf[2]);
|
|
}
|
|
else
|
|
{
|
|
if (0x0U == buf[3])
|
|
acknowledged = true;
|
|
}
|
|
lastdataTimer.start();
|
|
break;
|
|
case RT_TIMEOUT: // nothing or 0xff
|
|
break;
|
|
default:
|
|
if (buf[0] != 255)
|
|
DumpSerialPacket("GetITAPData returned", buf);
|
|
break;
|
|
}
|
|
FD_CLR(serfd, &readfds);
|
|
}
|
|
|
|
if (keep_running && FD_ISSET(ug2m, &readfds))
|
|
{
|
|
ssize_t len = FromGate.Read(buf, 100);
|
|
|
|
if (len < 0)
|
|
{
|
|
printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno));
|
|
break;
|
|
}
|
|
|
|
if (initialized && alive && (0 == memcmp(buf, "DSVT", 4)))
|
|
{ // we won't process the incoming gateway packet if the system isn't initialized and alive
|
|
if (ProcessGateway(len, buf))
|
|
break;
|
|
}
|
|
FD_CLR(ug2m, &readfds);
|
|
}
|
|
|
|
// send queued frames
|
|
if (keep_running)
|
|
{
|
|
if (acknowledged)
|
|
{
|
|
if (initialized)
|
|
{
|
|
if (! queue.empty())
|
|
{
|
|
CFrame frame = queue.front();
|
|
queue.pop();
|
|
if (alive) {
|
|
SendToIcom(frame.data());
|
|
ackTimer.start();
|
|
acknowledged = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // we are waiting on an acknowledgement
|
|
{
|
|
if (ackTimer.time() >= ackwait)
|
|
{
|
|
fprintf(stderr, "Icom failure suspected, restarting...\n");
|
|
alive = false;
|
|
}
|
|
}
|
|
}
|
|
if (! alive)
|
|
{
|
|
close(serfd);
|
|
poll_counter = 0;
|
|
pingtime = 0.001;
|
|
initialized = false;
|
|
alive = acknowledged = true;
|
|
lastdataTimer.start();
|
|
pingTimer.start();
|
|
serfd = OpenITAP();
|
|
if (serfd < 0)
|
|
{
|
|
keep_running = false;
|
|
}
|
|
else
|
|
{
|
|
while (! queue.empty())
|
|
queue.pop();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CQnetITAP::Close()
|
|
{
|
|
close(serfd);
|
|
FromGate.Close();
|
|
}
|
|
|
|
void CQnetITAP::SendToIcom(const unsigned char *buf)
|
|
{
|
|
ssize_t n;
|
|
unsigned int ptr = 0;
|
|
unsigned int length = (0xffu == buf[0]) ? 2 : buf[0];
|
|
|
|
while (ptr < length)
|
|
{
|
|
n = write(serfd, buf + ptr, length - ptr);
|
|
if (n < 0)
|
|
{
|
|
if (EAGAIN != errno)
|
|
{
|
|
printf("Error %d writing to %s: %s\n", errno, ITAP_DEVICE.c_str(), strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
ptr += n;
|
|
}
|
|
|
|
n = 0; // send an ending 0xffu
|
|
while (0 == n)
|
|
{
|
|
const unsigned char push = 0xffu;
|
|
n = write(serfd, &push, 1);
|
|
if (n < 0)
|
|
{
|
|
if (EAGAIN != errno)
|
|
{
|
|
printf("Error %d writing to %s: %s\n", errno, ITAP_DEVICE.c_str(), strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw)
|
|
{
|
|
static unsigned char counter = 0;
|
|
if (27==len || 56==len) //here is dstar data
|
|
{
|
|
SDSVT dsvt;
|
|
memcpy(dsvt.title, raw, len); // transfer raw data to SDSVT struct
|
|
|
|
SITAP itap; // destination
|
|
if (56 == len) // write a Header packet
|
|
{
|
|
counter = 0;
|
|
itap.length = 41U;
|
|
itap.type = 0x20;
|
|
memcpy(itap.header.flag, dsvt.hdr.flag, 3);
|
|
if (RPTR_MOD == dsvt.hdr.rpt2[7])
|
|
{
|
|
memcpy(itap.header.r1, dsvt.hdr.rpt2, 8);
|
|
memcpy(itap.header.r2, dsvt.hdr.rpt1, 8);
|
|
}
|
|
else
|
|
{
|
|
memcpy(itap.header.r1, dsvt.hdr.rpt1, 8);
|
|
memcpy(itap.header.r2, dsvt.hdr.rpt2, 8);
|
|
}
|
|
memcpy(itap.header.ur, dsvt.hdr.urcall, 8);
|
|
memcpy(itap.header.my, dsvt.hdr.mycall, 8);
|
|
memcpy(itap.header.nm, dsvt.hdr.sfx, 4);
|
|
if (LOG_QSO)
|
|
printf("Queued 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 = 16U;
|
|
itap.type = 0x22U;
|
|
itap.voice.counter = counter++;
|
|
itap.voice.sequence = dsvt.ctrl;
|
|
if (LOG_QSO && (dsvt.ctrl & 0x40))
|
|
printf("Queued ITAP end of stream\n");
|
|
if ((dsvt.ctrl & ~0x40U) > 20)
|
|
printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence);
|
|
memcpy(itap.voice.ambe, dsvt.vasd.voice, 12);
|
|
}
|
|
queue.push(CFrame(&itap.length));
|
|
|
|
}
|
|
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();
|
|
|
|
SDSVT dsvt; // destination
|
|
// sets most of the params
|
|
memcpy(dsvt.title, "DSVT", 4);
|
|
dsvt.config = (len==41) ? 0x10U : 0x20U;
|
|
memset(dsvt.flaga, 0U, 3U);
|
|
dsvt.id = 0x20;
|
|
dsvt.flagb[0] = 0x0;
|
|
dsvt.flagb[1] = 0x1;
|
|
dsvt.flagb[2] = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3);
|
|
dsvt.streamid = htons(stream_id);
|
|
|
|
if (41 == len) // header
|
|
{
|
|
dsvt.ctrl = 0x80;
|
|
|
|
memcpy(dsvt.hdr.flag, itap.header.flag, 3);
|
|
|
|
////////////////// Terminal or Access /////////////////////////
|
|
if (0 == memcmp(itap.header.r1, "DIRECT", 6))
|
|
{
|
|
// Terminal Mode!
|
|
memcpy(dsvt.hdr.rpt1, RPTR.c_str(), 7); // build r1
|
|
dsvt.hdr.rpt1[7] = RPTR_MOD; // with module
|
|
memcpy(dsvt.hdr.rpt2, RPTR.c_str(), 7); // build r2
|
|
dsvt.hdr.rpt2[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(dsvt.hdr.urcall, ' ', 8); // first file ur with spaces
|
|
if (' ' == itap.header.ur[1])
|
|
dsvt.hdr.urcall[7] = itap.header.ur[0]; // one char command, like "E" or "I"
|
|
else
|
|
memcpy(dsvt.hdr.urcall+6, itap.header.ur, 2); // two char command, like "HX" or "S0"
|
|
}
|
|
else
|
|
memcpy(dsvt.hdr.urcall, itap.header.ur, 8); // ur is at least 3 chars
|
|
}
|
|
else
|
|
{
|
|
// Access Point Mode
|
|
memcpy(dsvt.hdr.rpt1, itap.header.r1, 8);
|
|
memcpy(dsvt.hdr.rpt2, itap.header.r2, 8);
|
|
memcpy(dsvt.hdr.urcall, itap.header.ur, 8);
|
|
dsvt.hdr.flag[0] &= ~0x40U; // clear this bit
|
|
}
|
|
|
|
memcpy(dsvt.hdr.mycall, itap.header.my, 8);
|
|
memcpy(dsvt.hdr.sfx, itap.header.nm, 4);
|
|
calcPFCS(dsvt.hdr.flag, dsvt.hdr.pfcs);
|
|
if (ToGate.Write(dsvt.title, 56))
|
|
{
|
|
printf("ERROR: ProcessITAP: Could not write gateway header packet\n");
|
|
return true;
|
|
}
|
|
if (LOG_QSO)
|
|
printf("Sent DSVT to gateway, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ntohs(dsvt.streamid), dsvt.hdr.urcall, dsvt.hdr.rpt1, dsvt.hdr.rpt2, dsvt.hdr.mycall, dsvt.hdr.sfx);
|
|
}
|
|
else if (16 == len) // ambe
|
|
{
|
|
dsvt.ctrl = itap.voice.sequence;
|
|
memcpy(dsvt.vasd.voice, itap.voice.ambe, 12);
|
|
if (ToGate.Write(dsvt.title, 27))
|
|
{
|
|
printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n");
|
|
return true;
|
|
}
|
|
|
|
if (LOG_QSO && (dsvt.ctrl & 0x40))
|
|
printf("Sent dsvt end of streamid=%04x\n", ntohs(dsvt.streamid));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// process configuration file and return true if there was a problem
|
|
bool CQnetITAP::ReadConfig(const std::string &cfgFile)
|
|
{
|
|
CQnetConfigure cfg;
|
|
printf("Reading file %s\n", cfgFile.c_str());
|
|
if (cfg.Initialize(cfgFile))
|
|
return true;
|
|
|
|
const std::string estr; // an empty string
|
|
std::string type;
|
|
std::string itap_path("module_");
|
|
if (0 > m_index)
|
|
{
|
|
// 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"))
|
|
continue; // this ain't it!
|
|
itap_path.assign(test);
|
|
m_index = i;
|
|
break;
|
|
}
|
|
}
|
|
if (0 > m_index)
|
|
{
|
|
fprintf(stderr, "Error: no 'itap' module found\n!");
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// make sure itap module is defined
|
|
itap_path.append(1, 'a' + m_index);
|
|
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'+m_index);
|
|
return true;
|
|
}
|
|
}
|
|
RPTR_MOD = 'A' + m_index;
|
|
|
|
cfg.GetValue(itap_path+"_device", type, ITAP_DEVICE, 7, FILENAME_MAX);
|
|
cfg.GetValue(itap_path+"_ap_mode", type, AP_MODE);
|
|
|
|
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<l; i++)
|
|
{
|
|
if (islower(RPTR[i]))
|
|
RPTR[i] = toupper(RPTR[i]);
|
|
}
|
|
RPTR.resize(CALL_SIZE, ' ');
|
|
}
|
|
|
|
cfg.GetValue("log_qso", estr, LOG_QSO);
|
|
cfg.GetValue("log_debug", estr, LOG_DEBUG);
|
|
return false;
|
|
}
|
|
|
|
void CQnetITAP::calcPFCS(const unsigned char *packet, unsigned char *pfcs)
|
|
{
|
|
unsigned short crc_dstar_ffff = 0xffff;
|
|
unsigned short tmp, short_c;
|
|
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
|
|
};
|
|
|
|
for (int i = 0; i < 39 ; i++)
|
|
{
|
|
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;
|
|
|
|
pfcs[0] = (unsigned char)(crc_dstar_ffff & 0xff);
|
|
pfcs[1] = (unsigned char)((tmp >> 8) & 0xff);
|
|
|
|
return;
|
|
}
|
|
|
|
std::unique_ptr<CQnetITAP> pitap;
|
|
|
|
static void SignalHandler(int sig)
|
|
{
|
|
switch (sig)
|
|
{
|
|
case SIGINT:
|
|
case SIGHUP:
|
|
case SIGTERM:
|
|
if (pitap)
|
|
pitap->Stop();
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Caught an unknown signal: %d\n", sig);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
std::signal(SIGINT, SignalHandler);
|
|
std::signal(SIGHUP, SignalHandler);
|
|
std::signal(SIGTERM, SignalHandler);
|
|
|
|
setbuf(stdout, NULL);
|
|
if (2 != argc)
|
|
{
|
|
fprintf(stderr, "usage: %s path_to_config_file\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
if ('-' == argv[1][0])
|
|
{
|
|
printf("%s Copyright (C) 2018-2024 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");
|
|
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 (*qn)
|
|
{
|
|
case 0:
|
|
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, "assigned module must be a, b or c\n");
|
|
return 1;
|
|
}
|
|
|
|
pitap = std::unique_ptr<CQnetITAP>(new CQnetITAP(assigned_module));
|
|
|
|
if (!pitap)
|
|
{
|
|
fprintf(stderr, "Could not make a CQnetITAP\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (pitap->Initialize(argv[1]))
|
|
{
|
|
pitap.reset();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
pitap->Run();
|
|
|
|
pitap->Close();
|
|
|
|
printf("%s is closing.\n", argv[0]);
|
|
|
|
pitap.reset();
|
|
|
|
return 0;
|
|
}
|