kopia lustrzana https://github.com/jamescoxon/dl-fldigi
879 wiersze
26 KiB
C++
879 wiersze
26 KiB
C++
// ----------------------------------------------------------------------------
|
|
// pskrep.cxx
|
|
//
|
|
// Copyright (C) 2008-2009
|
|
// Stelios Bounanos, M0GLD
|
|
//
|
|
// This is a client for N1DQ's PSK Automatic Propagation Reporter
|
|
// (see http://pskreporter.info/). Philip Gladstone, N1DQ, is
|
|
// thanked for his helpful explanation of the protocol.
|
|
//
|
|
//
|
|
// This file is part of fldigi.
|
|
//
|
|
// fldigi 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 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// fldigi 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 <http://www.gnu.org/licenses/>.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef __MINGW32__
|
|
# include "compat.h"
|
|
#endif
|
|
|
|
#if HAVE_SYS_UTSNAME_H
|
|
# include <sys/utsname.h>
|
|
#endif
|
|
|
|
#include <sys/time.h>
|
|
#if HAVE_ARPA_INET_H
|
|
# include <arpa/inet.h>
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
|
|
#include <string>
|
|
#include <deque>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
|
|
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
|
|
# define MAP_TYPE std::tr1::unordered_map
|
|
#define HASH_TYPE std::tr1::hash
|
|
# include <tr1/unordered_map>
|
|
#else
|
|
// use the non-standard gnu hash_map on gcc <= 4.0.x,
|
|
// which has a broken tr1::unordered_map::operator=
|
|
# define MAP_TYPE __gnu_cxx::hash_map
|
|
#define HASH_TYPE __gnu_cxx::hash
|
|
# include <ext/hash_map>
|
|
namespace __gnu_cxx {
|
|
// define the missing hash specialisation for std::string
|
|
// using the 'const char*' hash function
|
|
template<> struct hash<std::string> {
|
|
size_t operator()(const std::string& s) const { return __stl_hash_string(s.c_str()); }
|
|
};
|
|
}
|
|
#endif
|
|
|
|
#include <FL/Fl.H>
|
|
|
|
#include "socket.h"
|
|
#include "re.h"
|
|
#include "debug.h"
|
|
#include "util.h"
|
|
#include "trx.h"
|
|
#include "fl_digi.h"
|
|
#include "main.h"
|
|
#include "configuration.h"
|
|
#include "globals.h"
|
|
#include "spot.h"
|
|
|
|
#include "pskrep.h"
|
|
|
|
LOG_FILE_SOURCE(debug::LOG_SPOTTER);
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
// Try to flush the report queue every SEND_INTERVAL seconds.
|
|
#define SEND_INTERVAL 300
|
|
|
|
// Ignore reports that are less than DUP_INTERVAL seconds older than
|
|
// a previously sent report for the same callsign and frequency band.
|
|
// Sent reports are also garbage-collected after DUP_INTERVAL seconds.
|
|
#define DUP_INTERVAL 1800
|
|
|
|
// The first TEMPLATE_THRESHOLD packets will contain the long templates;
|
|
// the next TEMPLATE_THRESHOLD packets will include the short templates
|
|
#define TEMPLATE_THRESHOLD 3
|
|
// Resend short templates every TEMPLATE_INTERVAL seconds
|
|
#define TEMPLATE_INTERVAL 1800
|
|
|
|
// Maximum send size
|
|
#define DGRAM_MAX (1500-14-24-8)
|
|
|
|
#define PSKREP_QUEUE_FILE "pskrqueue.txt"
|
|
#define PSKREP_ID_FILE "pskrkey.txt"
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
using namespace std;
|
|
|
|
enum status_t { PSKR_STATUS_NEW, PSKR_STATUS_PENDING, PSKR_STATUS_SENT };
|
|
enum rtype_t { PSKREP_AUTO = 1, PSKREP_LOG = 2, PSKREP_MANUAL = 3 };
|
|
|
|
struct rcpt_report_t
|
|
{
|
|
rcpt_report_t(trx_mode m = 0, long long f = 0, time_t t = 0,
|
|
rtype_t p = PSKREP_AUTO, string loc = "")
|
|
: mode(m), freq(f), rtime(t), rtype(p),
|
|
status(PSKR_STATUS_NEW), locator(loc) { }
|
|
|
|
trx_mode mode;
|
|
long long freq;
|
|
time_t rtime;
|
|
rtype_t rtype;
|
|
|
|
status_t status;
|
|
|
|
string locator;
|
|
};
|
|
|
|
// A band_map_t holds a list of reception reports (for a particular callsign and band)
|
|
typedef deque<rcpt_report_t> band_map_t;
|
|
// A call_map_t holds reception reports for a particular callsign
|
|
typedef MAP_TYPE<band_t, band_map_t, HASH_TYPE<int> > call_map_t;
|
|
// A container of this type holds all reception reports, sorted by callsign and band
|
|
typedef MAP_TYPE<string, call_map_t> queue_t;
|
|
|
|
class pskrep_sender
|
|
{
|
|
public:
|
|
pskrep_sender(const string& call, const string& loc, const string& ant,
|
|
const string& host_, const string& port_,
|
|
const string& long_id_, const string& short_id_);
|
|
~pskrep_sender();
|
|
|
|
bool append(const string& callsign, const band_map_t::value_type& r);
|
|
bool send(void);
|
|
|
|
private:
|
|
void write_station_info(void);
|
|
void write_preamble(void);
|
|
|
|
string recv_callsign, recv_locator, recv_antenna;
|
|
string host, port;
|
|
string long_id, short_id;
|
|
|
|
static const unsigned char long_station_info_template[];
|
|
static const unsigned char short_station_info_template[];
|
|
static const unsigned char rcpt_record_template[];
|
|
vector<unsigned char> long_station_info;
|
|
vector<unsigned char> short_station_info;
|
|
|
|
uint32_t identifier;
|
|
uint32_t sequence_number;
|
|
|
|
unsigned template_count;
|
|
time_t last_template;
|
|
|
|
Socket* send_socket;
|
|
unsigned char* dgram;
|
|
size_t dgram_size;
|
|
size_t report_offset;
|
|
|
|
void create_socket(void);
|
|
pthread_t resolver_thread;
|
|
static void* resolver(void* obj);
|
|
|
|
static const char hexsym[];
|
|
|
|
static size_t pad(size_t len, size_t mult);
|
|
};
|
|
|
|
|
|
class pskrep
|
|
{
|
|
public:
|
|
pskrep(const string& call, const string& loc, const string& ant,
|
|
const string& host, const string& port,
|
|
const string& long_id, const string& short_id,
|
|
bool reg_auto, bool reg_log, bool reg_manual);
|
|
~pskrep();
|
|
|
|
static void recv(trx_mode mode, int afreq, const char* str, const regmatch_t* calls, size_t len, void* obj);
|
|
static void log(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj);
|
|
static void manual(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj);
|
|
bool progress(void);
|
|
unsigned count(void) { return new_count; }
|
|
|
|
static fre_t locator_re;
|
|
private:
|
|
|
|
void append(string call, const char* loc, long long freq, trx_mode mode, time_t rtime, rtype_t rtype);
|
|
void gc(void);
|
|
|
|
void load_queue(void);
|
|
void save_queue(void);
|
|
|
|
static bool not_sent(const band_map_t::value_type& r)
|
|
{
|
|
return r.status != PSKR_STATUS_SENT;
|
|
}
|
|
|
|
queue_t queue;
|
|
pskrep_sender sender;
|
|
unsigned new_count;
|
|
};
|
|
|
|
fre_t pskrep::locator_re("[a-r]{2}[0-9]{2}[a-x]{2}", REG_EXTENDED | REG_NOSUB | REG_ICASE);
|
|
|
|
#define SHORT_ID_SIZE 4
|
|
#define LONG_ID_SIZE 8
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
static string error_string;
|
|
|
|
static bool pskrep_check(void)
|
|
{
|
|
struct {
|
|
const string* var;
|
|
const char* msg;
|
|
} check[] = {
|
|
{ &progdefaults.myCall, "callsign" },
|
|
{ &progdefaults.myLocator, "locator" },
|
|
{ &progdefaults.myAntenna, "antenna info" },
|
|
};
|
|
for (size_t i = 0; i < sizeof(check)/sizeof(*check); i++) {
|
|
if (check[i].var->empty()) {
|
|
error_string.assign("Error: missing ").append(check[i].msg);
|
|
return false;
|
|
}
|
|
}
|
|
if (!pskrep::locator_re.match(progdefaults.myLocator.c_str())) {
|
|
error_string = "Error: bad Maidenhead locator\ncheck Configure->Operator->Locator";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const char* pskrep_error(void)
|
|
{
|
|
return error_string.c_str();
|
|
}
|
|
|
|
static void pskrep_progress(void* obj)
|
|
{
|
|
if (reinterpret_cast<pskrep*>(obj)->progress())
|
|
Fl::add_timeout(SEND_INTERVAL, pskrep_progress, obj);
|
|
else
|
|
pskrep_stop();
|
|
}
|
|
|
|
static void pskrep_make_id(string& id, size_t len)
|
|
{
|
|
id.resize(len);
|
|
|
|
ifstream f("/dev/urandom");
|
|
if (f) {
|
|
for (size_t i = 0; i < len; i++)
|
|
while ((id[i] = f.get()) != EOF && !isgraph(id[i]));
|
|
f.close();
|
|
}
|
|
else {
|
|
unsigned seed = time(NULL);
|
|
if (!progdefaults.myCall.empty())
|
|
seed ^= simple_hash_str((const unsigned char*)progdefaults.myCall.c_str());
|
|
srand(seed);
|
|
for (size_t i = 0; i < len; i++)
|
|
while (!isgraph(id[i] = rand() % 0x7F));
|
|
}
|
|
}
|
|
|
|
static pskrep* pskr = 0;
|
|
|
|
bool pskrep_start(void)
|
|
{
|
|
if (pskr)
|
|
return true;
|
|
else if (!pskrep_check())
|
|
return false;
|
|
|
|
// get identifier
|
|
string fname = TempDir;
|
|
fname.append(PSKREP_ID_FILE);
|
|
ifstream in(fname.c_str());
|
|
string long_id, short_id;
|
|
if (in)
|
|
in >> long_id >> short_id;
|
|
if (!in || in.eof()) {
|
|
in.close();
|
|
pskrep_make_id(long_id, LONG_ID_SIZE);
|
|
pskrep_make_id(short_id, SHORT_ID_SIZE);
|
|
|
|
ofstream out(fname.c_str());
|
|
if (out)
|
|
out << long_id << ' ' << short_id << '\n';
|
|
else
|
|
LOG_ERROR("Could not write identifiers (\"%s\", \"%s\") to %s",
|
|
long_id.c_str(), short_id.c_str(), fname.c_str());
|
|
}
|
|
|
|
pskr = new pskrep(progdefaults.myCall, progdefaults.myLocator, progdefaults.myAntenna,
|
|
progdefaults.pskrep_host, progdefaults.pskrep_port, long_id, short_id,
|
|
progdefaults.pskrep_auto, progdefaults.pskrep_log, true);
|
|
Fl::add_timeout(SEND_INTERVAL, pskrep_progress, pskr);
|
|
|
|
return true;
|
|
}
|
|
|
|
void pskrep_stop(void)
|
|
{
|
|
Fl::remove_timeout(pskrep_progress, pskr);
|
|
|
|
delete pskr;
|
|
pskr = 0;
|
|
}
|
|
|
|
unsigned pskrep_count(void)
|
|
{
|
|
return pskr ? pskr->count() : 0;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
pskrep::pskrep(const string& call, const string& loc, const string& ant,
|
|
const string& host, const string& port,
|
|
const string& long_id, const string& short_id,
|
|
bool reg_auto, bool reg_log, bool reg_manual)
|
|
: sender(call, loc, ant, host, port, long_id, short_id), new_count(0)
|
|
{
|
|
if (reg_auto)
|
|
spot_register_recv(pskrep::recv, this, PSKREP_RE, REG_EXTENDED | REG_ICASE);
|
|
if (reg_log)
|
|
spot_register_log(pskrep::log, this);
|
|
if (reg_manual)
|
|
spot_register_manual(pskrep::manual, this);
|
|
load_queue();
|
|
}
|
|
|
|
pskrep::~pskrep()
|
|
{
|
|
spot_unregister_recv(pskrep::recv, this);
|
|
spot_unregister_log(pskrep::log, this);
|
|
spot_unregister_manual(pskrep::manual, this);
|
|
save_queue();
|
|
}
|
|
|
|
// This function is called by spot_recv() when its buffer matches our PSKREP_RE
|
|
void pskrep::recv(trx_mode mode, int afreq, const char* str, const regmatch_t* calls, size_t len, void* obj)
|
|
{
|
|
if (unlikely(calls[PSKREP_RE_INDEX].rm_so == -1 || calls[PSKREP_RE_INDEX].rm_eo == -1))
|
|
return;
|
|
|
|
string call(str + calls[PSKREP_RE_INDEX].rm_so, calls[PSKREP_RE_INDEX].rm_eo - calls[PSKREP_RE_INDEX].rm_so);
|
|
long long freq = afreq;
|
|
if (!wf->USB())
|
|
freq = -freq;
|
|
freq += wf->rfcarrier();
|
|
LOG_DEBUG("Spotted \"%s\" in buffer \"%s\"", call.c_str(), str);
|
|
|
|
reinterpret_cast<pskrep*>(obj)->append(call.c_str(), "", freq,
|
|
active_modem->get_mode(), time(NULL), PSKREP_AUTO);
|
|
}
|
|
|
|
// This function is called by spot_log()
|
|
void pskrep::log(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj)
|
|
{
|
|
reinterpret_cast<pskrep*>(obj)->append(call, loc, freq, mode, rtime, PSKREP_LOG);
|
|
}
|
|
|
|
// This function is called by spot_manual()
|
|
void pskrep::manual(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj)
|
|
{
|
|
reinterpret_cast<pskrep*>(obj)->append(call, loc, freq, mode, rtime, PSKREP_MANUAL);
|
|
}
|
|
|
|
void pskrep::append(string call, const char* loc, long long freq, trx_mode mode, time_t rtime, rtype_t rtype)
|
|
{
|
|
if (unlikely(call.empty()))
|
|
return;
|
|
transform(call.begin(), call.end(), call.begin(), static_cast<int (*)(int)>(toupper));
|
|
|
|
if (!progdefaults.pskrep_qrg)
|
|
freq = 0LL;
|
|
|
|
if (*loc && !locator_re.match(loc))
|
|
loc = "";
|
|
|
|
band_map_t& bandq = queue[call][band(freq)];
|
|
if (bandq.empty() || rtime - bandq.back().rtime >= DUP_INTERVAL) { // add new
|
|
bandq.push_back(rcpt_report_t(mode, freq, rtime, rtype, loc));
|
|
LOG_VERBOSE("Added (call=\"%s\", loc=\"%s\", mode=\"%s\", freq=%lld, time=%jd, type=%u)",
|
|
call.c_str(), loc, mode_info[mode].adif_name, freq, (intmax_t)rtime, rtype);
|
|
new_count++;
|
|
save_queue();
|
|
}
|
|
else if (!bandq.empty()) {
|
|
band_map_t::value_type& r = bandq.back();
|
|
if (r.status != PSKR_STATUS_SENT && *loc && r.locator != loc) { // update last
|
|
r.locator = loc;
|
|
r.rtype = rtype;
|
|
LOG_VERBOSE("Updated (call=\"%s\", loc=\"%s\", mode=\"%s\", freq=%lld, time=%jd, type=%u)",
|
|
call.c_str(), loc, mode_info[r.mode].adif_name, r.freq, (intmax_t)r.rtime, rtype);
|
|
save_queue();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle queued reports
|
|
bool pskrep::progress(void)
|
|
{
|
|
if (queue.empty())
|
|
return true;
|
|
|
|
unsigned nrep = 0;
|
|
bool sender_full = false;
|
|
for (queue_t::iterator i = queue.begin(); i != queue.end(); ++i) {
|
|
for (call_map_t::iterator j = i->second.begin(); j != i->second.end(); ++j) {
|
|
for (band_map_t::iterator k = j->second.begin(); k != j->second.end(); ++k) {
|
|
switch (k->status) {
|
|
case PSKR_STATUS_NEW:
|
|
if ((sender_full = !sender.append(i->first, *k)))
|
|
goto send_reports;
|
|
k->status = PSKR_STATUS_PENDING;
|
|
nrep++;
|
|
break;
|
|
case PSKR_STATUS_PENDING: // sent in last cycle
|
|
k->status = PSKR_STATUS_SENT;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
send_reports:
|
|
LOG_VERBOSE("Found %u new report(s)", nrep);
|
|
if (nrep) {
|
|
if (!sender.send()) {
|
|
LOG_ERROR("Sender failed, disabling pskreporter");
|
|
return false;
|
|
}
|
|
return progress();
|
|
}
|
|
|
|
gc();
|
|
save_queue();
|
|
return true;
|
|
}
|
|
|
|
// Delete sent reports that are older than DUP_INTERVAL seconds
|
|
void pskrep::gc(void)
|
|
{
|
|
time_t threshold = time(NULL) - DUP_INTERVAL;
|
|
unsigned rm = 0;
|
|
|
|
for (queue_t::iterator i = queue.begin(); i != queue.end() ; ) {
|
|
for (call_map_t::iterator j = i->second.begin(); j != i->second.end() ; ) {
|
|
band_map_t& b = j->second;
|
|
band_map_t::iterator k = find_if(b.begin(), b.end(), not_sent);
|
|
if (k != b.begin() && k == b.end())
|
|
--k;
|
|
rm += k - b.begin();
|
|
k = b.erase(b.begin(), k);
|
|
|
|
if (k != b.end() && k->status == PSKR_STATUS_SENT && k->rtime <= threshold) {
|
|
b.erase(k);
|
|
rm++;
|
|
}
|
|
|
|
if (b.empty())
|
|
i->second.erase(j++);
|
|
else
|
|
++j;
|
|
}
|
|
if (i->second.empty())
|
|
queue.erase(i++);
|
|
else
|
|
++i;
|
|
}
|
|
|
|
LOG_DEBUG("Removed %u sent report(s)", rm);
|
|
}
|
|
|
|
static ostream& operator<<(ostream& out, const rcpt_report_t& r);
|
|
static istream& operator>>(istream& in, rcpt_report_t& r);
|
|
static ostream& operator<<(ostream& out, const queue_t& q);
|
|
static istream& operator>>(istream& in, queue_t& q);
|
|
|
|
void pskrep::save_queue(void)
|
|
{
|
|
string fname = TempDir;
|
|
fname.append(PSKREP_QUEUE_FILE);
|
|
ofstream out(fname.c_str());
|
|
|
|
if (out)
|
|
out << queue;
|
|
else
|
|
LOG_ERROR("Could not write %s", fname.c_str());
|
|
}
|
|
|
|
void pskrep::load_queue(void)
|
|
{
|
|
string fname = TempDir;
|
|
fname.append(PSKREP_QUEUE_FILE);
|
|
ifstream in(fname.c_str());
|
|
if (!in)
|
|
return;
|
|
|
|
in >> queue;
|
|
// restore pending reports as new
|
|
for (queue_t::iterator i = queue.begin(); i != queue.end(); ++i)
|
|
for (call_map_t::iterator j = i->second.begin(); j != i->second.end(); ++j)
|
|
for (band_map_t::iterator k = j->second.begin(); k != j->second.end(); ++k)
|
|
if (k->status == PSKR_STATUS_PENDING)
|
|
k->status = PSKR_STATUS_NEW;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
// Text fields must be <= 254 bytes
|
|
#define MAX_TEXT_SIZE 254
|
|
// Records must be padded to a multiple of 4
|
|
#define PAD 4
|
|
|
|
pskrep_sender::pskrep_sender(const string& call, const string& loc, const string& ant,
|
|
const string& host_, const string& port_,
|
|
const string& long_id_, const string& short_id_)
|
|
: recv_callsign(call, 0, MAX_TEXT_SIZE), recv_locator(loc, 0, MAX_TEXT_SIZE),
|
|
recv_antenna(ant, 0, MAX_TEXT_SIZE),
|
|
host(host_, 0, MAX_TEXT_SIZE), port(port_, 0, MAX_TEXT_SIZE),
|
|
long_id(long_id_, 0, LONG_ID_SIZE), short_id(short_id_, 0, SHORT_ID_SIZE),
|
|
sequence_number(0), template_count(2 * TEMPLATE_THRESHOLD), last_template(0),
|
|
send_socket(0), dgram_size(0), report_offset(0)
|
|
{
|
|
create_socket();
|
|
dgram = new unsigned char[DGRAM_MAX];
|
|
write_station_info();
|
|
}
|
|
|
|
pskrep_sender::~pskrep_sender()
|
|
{
|
|
delete send_socket;
|
|
delete [] dgram;
|
|
}
|
|
|
|
// fldigi uses 0x0219 as the long station info template id (bytes 4,5)
|
|
const unsigned char pskrep_sender::long_station_info_template[] = {
|
|
0x00, 0x03, 0x00, 0x34, 0x02, 0x19, 0x00, 0x05, 0x00, 0x00,
|
|
0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverCallsign
|
|
0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverLocator
|
|
0x80, 0x0C, 0x00, 0x08, 0x00, 0x00, 0x76, 0x8F, // persistentIdentifier
|
|
0x80, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // decoderSoftware
|
|
0x80, 0x09, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // anntennaInformation
|
|
0x00, 0x00
|
|
};
|
|
|
|
// fldigi uses 0x0218 as the short station info template id (bytes 4,5)
|
|
const unsigned char pskrep_sender::short_station_info_template[] = {
|
|
0x00, 0x03, 0x00, 0x24, 0x02, 0x18, 0x00, 0x03, 0x00, 0x00,
|
|
0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverCallsign
|
|
0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverLocator
|
|
0x80, 0x0C, 0x00, 0x08, 0x00, 0x00, 0x76, 0x8F, // persistentIdentifier
|
|
0x00, 0x00
|
|
};
|
|
|
|
void pskrep_sender::write_station_info(void)
|
|
{
|
|
char prog_info[MAX_TEXT_SIZE];
|
|
size_t prog_len;
|
|
|
|
prog_len = snprintf(prog_info, sizeof(prog_info), "%s", PACKAGE_TARNAME "-" PACKAGE_VERSION);
|
|
prog_len = MIN(prog_len, sizeof(prog_info));
|
|
struct utsname u;
|
|
if (uname(&u) != -1) {
|
|
prog_len += snprintf(prog_info+prog_len, sizeof(prog_info)-prog_len, "/%s-%s", u.sysname, u.machine);
|
|
prog_len = MIN(prog_len, sizeof(prog_info));
|
|
}
|
|
|
|
size_t call_len = recv_callsign.length(),
|
|
loc_len = recv_locator.length(),
|
|
ant_len = recv_antenna.length();
|
|
|
|
size_t long_len, short_len;
|
|
// Long station info length
|
|
long_len = 4 + // 4-byte header
|
|
1 + call_len + // 1-byte call length + call string length
|
|
1 + loc_len + // 1-byte loc length + loc string length
|
|
8 + // 8-byte identifier
|
|
1 + prog_len + // 1-byte prog length + prog string length
|
|
1 + ant_len; // 1-byte ant length + ant string length
|
|
long_len = pad(long_len, PAD);
|
|
// Short station info length
|
|
short_len = 4 + // 4-byte header
|
|
1 + call_len + // 1-byte call length + call string length
|
|
1 + loc_len + // 1-byte loc length + loc string length
|
|
8; // 8-byte identifier
|
|
short_len = pad(short_len, PAD);
|
|
|
|
long_station_info.resize(long_len);
|
|
short_station_info.resize(short_len);
|
|
unsigned char* p;
|
|
size_t npad;
|
|
|
|
// Write the long station info
|
|
p = &long_station_info[0];
|
|
// header
|
|
memcpy(p, long_station_info_template + 4, 2); p += 2;
|
|
*reinterpret_cast<uint16_t*>(p) = htons(long_len); p += sizeof(uint16_t);
|
|
// call
|
|
*p++ = call_len; memcpy(p, recv_callsign.data(), call_len); p += call_len;
|
|
// locator
|
|
*p++ = loc_len; memcpy(p, recv_locator.data(), loc_len); p += loc_len;
|
|
// identifier
|
|
memcpy(p, long_id.data(), LONG_ID_SIZE); p += LONG_ID_SIZE;
|
|
// program
|
|
*p++ = prog_len; memcpy(p, prog_info, prog_len); p += prog_len;
|
|
// antenna
|
|
*p++ = ant_len; memcpy(p, recv_antenna.data(), ant_len); p += ant_len;
|
|
// pad
|
|
npad = &long_station_info[0] + long_len - p;
|
|
if (npad)
|
|
memset(p, 0, npad);
|
|
LOG_DEBUG("long_station_info=\"%s\"", str2hex(&long_station_info[0], long_len));
|
|
|
|
// Write the short station info
|
|
p = &short_station_info[0];
|
|
// header
|
|
memcpy(p, short_station_info_template + 4, 2); p += 2;
|
|
*reinterpret_cast<uint16_t*>(p) = htons(short_len); p += sizeof(uint16_t);
|
|
// call
|
|
*p++ = call_len; memcpy(p, recv_callsign.data(), call_len); p += call_len;
|
|
// locator
|
|
*p++ = loc_len; memcpy(p, recv_locator.data(), loc_len); p += loc_len;
|
|
// identifier
|
|
memcpy(p, long_id.data(), LONG_ID_SIZE); p += LONG_ID_SIZE;
|
|
// pad
|
|
npad = &short_station_info[0] + short_len - p;
|
|
if (npad)
|
|
memset(p, 0, npad);
|
|
LOG_DEBUG("short_station_info=\"%s\"", str2hex(&short_station_info[0], short_len));
|
|
}
|
|
|
|
// fldigi uses 0x022C as the reception record template id (bytes 4,5)
|
|
const unsigned char pskrep_sender::rcpt_record_template[] = {
|
|
0x00, 0x02, 0x00, 0x34, 0x02, 0x2C, 0x00, 0x06,
|
|
0x80, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // senderCallsign
|
|
0x00, 0x96, 0x00, 0x04, // flowStartSeconds
|
|
0x80, 0x05, 0x00, 0x04, 0x00, 0x00, 0x76, 0x8F, // frequency
|
|
0x80, 0x0A, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // mode (adif string)
|
|
0x80, 0x03, 0xff, 0xff, 0x00, 0x00, 0x76, 0x8F, // senderLocator (if known)
|
|
0x80, 0x0B, 0x00, 0x01, 0x00, 0x00, 0x76, 0x8F // flags (informationSource)
|
|
};
|
|
|
|
void pskrep_sender::write_preamble(void)
|
|
{
|
|
time_t now = time(NULL);
|
|
unsigned char* p = dgram;
|
|
|
|
// header
|
|
*p++ = 0x00; *p++ = 0x0A; /* length written later */ p += 2;
|
|
/* time written later */ p += sizeof(uint32_t);
|
|
*reinterpret_cast<uint32_t*>(p) = htonl(sequence_number); p += sizeof(uint32_t);
|
|
memcpy(p, short_id.data(), SHORT_ID_SIZE); p += SHORT_ID_SIZE;
|
|
|
|
const unsigned char* station_info_template;
|
|
size_t tlen;
|
|
vector<unsigned char>* station_info;
|
|
|
|
if (template_count == 0 && now - last_template >= TEMPLATE_INTERVAL)
|
|
template_count = TEMPLATE_THRESHOLD;
|
|
if (template_count > TEMPLATE_THRESHOLD) {
|
|
station_info_template = long_station_info_template;
|
|
tlen = sizeof(long_station_info_template);
|
|
station_info = &long_station_info;
|
|
}
|
|
else if (template_count >= 0) {
|
|
station_info_template = short_station_info_template;
|
|
tlen = sizeof(short_station_info_template);
|
|
station_info = &short_station_info;
|
|
}
|
|
if (template_count > 0) {
|
|
memcpy(p, rcpt_record_template, sizeof(rcpt_record_template)); p += sizeof(rcpt_record_template);
|
|
memcpy(p, station_info_template, tlen); p += tlen;
|
|
template_count--;
|
|
last_template = now;
|
|
}
|
|
|
|
// station info record
|
|
memcpy(p, &(*station_info)[0], station_info->size()); p += station_info->size();
|
|
|
|
report_offset = p - dgram;
|
|
// write report record header
|
|
memcpy(p, rcpt_record_template + 4, 2); p += 2; /* length written later */ p += sizeof(uint16_t);
|
|
|
|
dgram_size = p - dgram;
|
|
}
|
|
|
|
bool pskrep_sender::append(const string& callsign, const band_map_t::value_type& r)
|
|
{
|
|
if (dgram_size == 0)
|
|
write_preamble();
|
|
|
|
size_t call_len = callsign.length();
|
|
call_len = MIN(MAX_TEXT_SIZE, call_len);
|
|
|
|
const char* mode = mode_info[r.mode].adif_name;
|
|
size_t mode_len = strlen(mode);
|
|
mode_len = MIN(MAX_TEXT_SIZE, mode_len);
|
|
|
|
size_t loc_len = MIN(MAX_TEXT_SIZE, r.locator.length());
|
|
|
|
// call_len + call + time + freq + mode_len + mode + loc_len + loc + info
|
|
size_t rlen = 1 + call_len + 4 + 4 + 1 + mode_len + 1 + loc_len + 1;
|
|
|
|
if (pad(rlen, PAD) + dgram_size > DGRAM_MAX) // datagram full
|
|
return false;
|
|
|
|
|
|
LOG_INFO("Appending report (call=%s mode=%s freq=%lld time=%jd type=%u)",
|
|
callsign.c_str(), mode_info[r.mode].adif_name, r.freq, (intmax_t)r.rtime, r.rtype);
|
|
|
|
unsigned char* start = dgram + dgram_size;
|
|
unsigned char* p = start;
|
|
// call
|
|
*p++ = call_len; memcpy(p, callsign.data(), call_len); p += call_len;
|
|
// 4-byte reception time
|
|
*reinterpret_cast<uint32_t*>(p) = htonl(r.rtime); p += sizeof(uint32_t);
|
|
// 4-byte freq
|
|
*reinterpret_cast<uint32_t*>(p) = htonl(r.freq); p += sizeof(uint32_t);
|
|
// mode
|
|
*p++ = mode_len; memcpy(p, mode, mode_len); p += mode_len;
|
|
// locator
|
|
*p++ = loc_len; memcpy(p, r.locator.data(), loc_len); p += loc_len;
|
|
// info source
|
|
*p++ = r.rtype;
|
|
|
|
LOG_DEBUG(" \"%s\"", str2hex(start, p - start));
|
|
|
|
dgram_size += rlen;
|
|
return true;
|
|
}
|
|
|
|
void* pskrep_sender::resolver(void* obj)
|
|
{
|
|
pskrep_sender* s = reinterpret_cast<pskrep_sender*>(obj);
|
|
try {
|
|
s->send_socket = new Socket(Address(s->host.c_str(), s->port.c_str(), "udp"));
|
|
s->send_socket->connect();
|
|
}
|
|
catch (const SocketException& e) {
|
|
LOG_ERROR("Could not resolve %s: %s", s->host.c_str(), e.what());
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void pskrep_sender::create_socket(void)
|
|
{
|
|
if (pthread_create(&resolver_thread, NULL, resolver, this) != 0)
|
|
LOG_PERROR("pthread_create");
|
|
}
|
|
|
|
bool pskrep_sender::send(void)
|
|
{
|
|
if (!send_socket)
|
|
return false;
|
|
|
|
// empty dgram or no reports (shouldn't happen)
|
|
if (dgram_size == 0 || dgram_size == report_offset + 4) {
|
|
LOG_DEBUG("Not sending empty dgram: %" PRIuSZ " %" PRIuSZ "", dgram_size, report_offset);
|
|
return false;
|
|
}
|
|
|
|
// Finish writing the report record:
|
|
// do we need padding?
|
|
size_t npad = (dgram_size - report_offset) % PAD;
|
|
if (npad) {
|
|
npad = PAD - npad;
|
|
memset(dgram + dgram_size, 0x0, npad);
|
|
dgram_size += npad;
|
|
}
|
|
// write length
|
|
*reinterpret_cast<uint16_t*>(dgram + report_offset + 2) = htons(dgram_size - report_offset);
|
|
|
|
// finish writing the datagram
|
|
*reinterpret_cast<uint16_t*>(dgram + 2) = htons(dgram_size);
|
|
*reinterpret_cast<uint32_t*>(dgram + 4) = htonl(time(NULL));
|
|
|
|
bool ret;
|
|
LOG_DEBUG("Sending datagram (%" PRIuSZ "): \"%s\"", dgram_size, str2hex(dgram, dgram_size));
|
|
try {
|
|
if ((size_t)send_socket->send(dgram, dgram_size) != dgram_size)
|
|
throw SocketException("short write");
|
|
ret = true;
|
|
}
|
|
catch (const SocketException& e) {
|
|
LOG_ERROR("Could not send datagram to %s port %s: %s", host.c_str(), port.c_str(), e.what());
|
|
ret = false;
|
|
}
|
|
|
|
// increment this regardless of any errors
|
|
sequence_number++;
|
|
dgram_size = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Pad len to a multiple of mult
|
|
size_t pskrep_sender::pad(size_t len, size_t mult)
|
|
{
|
|
size_t r = len % mult;
|
|
return r ? len + mult - r : len;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
static istream& operator>>(istream& in, rtype_t& t)
|
|
{
|
|
int i;
|
|
in >> i;
|
|
t = static_cast<rtype_t>(i);
|
|
return in;
|
|
}
|
|
static istream& operator>>(istream& in, status_t& t)
|
|
{
|
|
int i;
|
|
in >> i;
|
|
t = static_cast<status_t>(i);
|
|
return in;
|
|
}
|
|
static istream& operator>>(istream& in, rcpt_report_t& r)
|
|
{
|
|
in >> r.mode >> r.freq >> r.rtime >> r.rtype >> r.status >> r.locator;
|
|
if (*r.locator.c_str() == '?') r.locator.clear();
|
|
return in;
|
|
}
|
|
|
|
static ostream& operator<<(ostream& out, const rcpt_report_t& r)
|
|
{
|
|
return out << r.mode << ' ' << r.freq << ' ' << r.rtime << ' '
|
|
<< r.rtype << ' ' << r.status << ' ' << (r.locator.empty() ? "?" : r.locator);
|
|
}
|
|
static ostream& operator<<(ostream& out, const queue_t& q)
|
|
{
|
|
for (queue_t::const_iterator i = q.begin(); i != q.end(); ++i)
|
|
for (call_map_t::const_iterator j = i->second.begin(); j != i->second.end(); ++j)
|
|
for (band_map_t::const_iterator k = j->second.begin(); k != j->second.end(); ++k)
|
|
out << *k << " " << j->first << " " << i->first << '\n';
|
|
|
|
return out;
|
|
}
|
|
static istream& operator>>(istream& in, queue_t& q)
|
|
{
|
|
rcpt_report_t rep;
|
|
int band;
|
|
string call;
|
|
|
|
while (in >> rep >> band >> call)
|
|
q[call][static_cast<band_t>(band)].push_back(rep);
|
|
|
|
return in;
|
|
}
|