dl-fldigi/src/spot/pskrep.cxx

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;
}