MMDVMHost/YSFControl.cpp
Andy Taylor 9b8a6c1e2c Always emit source_info and reflector fields in JSON output
Several JSON message builders conditionally omit fields when their
values are empty or null:
 - DMRSlot, P25Control, NXDNControl: source_info omitted when the
   callsign/ID lookup returns an empty string
 - DStarControl: reflector omitted in RF start messages and when
   the reflector pointer is null in network start messages
 - YSFControl: reflector omitted in RF start messages

Downstream consumers (such as Display-Driver) that access these
fields via nlohmann::json const operator[] crash with an assertion
failure when the key is absent.

Always emit these fields with an empty string default so that the
JSON schema is consistent regardless of lookup results or message
source.
2026-03-17 15:04:54 +00:00

1389 lines
34 KiB
C++

/*
* Copyright (C) 2015-2021,2023,2025 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; version 2 of the License.
*
* 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.
*/
#include "YSFControl.h"
#include "Utils.h"
#include "Sync.h"
#include "Log.h"
#if defined(USE_YSF)
#include <cstdio>
#include <cassert>
#include <cstring>
#include <ctime>
const unsigned int RSSI_COUNT = 10U; // 10 * 100ms = 1000ms
const unsigned int BER_COUNT = 10U; // 10 * 100ms = 1000ms
CYSFControl::CYSFControl(const std::string& callsign, bool selfOnly, CYSFNetwork* network, unsigned int timeout, bool duplex, bool lowDeviation, bool remoteGateway, CRSSIInterpolator* rssiMapper) :
m_callsign(nullptr),
m_selfCallsign(nullptr),
m_selfOnly(selfOnly),
m_network(network),
m_duplex(duplex),
m_lowDeviation(lowDeviation),
m_remoteGateway(remoteGateway),
m_queue(5000U, "YSF Control"),
m_rfState(RPT_RF_STATE::LISTENING),
m_netState(RPT_NET_STATE::IDLE),
m_rfTimeoutTimer(1000U, timeout),
m_netTimeoutTimer(1000U, timeout),
m_packetTimer(1000U, 0U, 200U),
m_networkWatchdog(1000U, 0U, 1500U),
m_elapsed(),
m_rfFrames(0U),
m_netFrames(0U),
m_netLost(0U),
m_rfErrs(0U),
m_rfBits(1U),
m_netBits(1U),
m_rfSource(nullptr),
m_rfDest(nullptr),
m_netSource(nullptr),
m_netDest(nullptr),
m_lastFICH(),
m_netN(0U),
m_rfPayload(),
m_netPayload(),
m_rssiMapper(rssiMapper),
m_rssi(0),
m_maxRSSI(0),
m_minRSSI(0),
m_aveRSSI(0),
m_rssiCountTotal(0U),
m_rssiAccum(0),
m_rssiCount(0U),
m_bitsCount(0U),
m_bitErrsAccum(0U),
m_enabled(true)
{
assert(rssiMapper != nullptr);
m_rfPayload.setUplink(callsign);
m_rfPayload.setDownlink(callsign);
m_netPayload.setDownlink(callsign);
m_netSource = new unsigned char[YSF_CALLSIGN_LENGTH];
m_netDest = new unsigned char[YSF_CALLSIGN_LENGTH];
m_callsign = new unsigned char[YSF_CALLSIGN_LENGTH];
std::string node = callsign;
node.resize(YSF_CALLSIGN_LENGTH, ' ');
for (unsigned int i = 0U; i < YSF_CALLSIGN_LENGTH; i++)
m_callsign[i] = node.at(i);
m_selfCallsign = new unsigned char[YSF_CALLSIGN_LENGTH];
::memset(m_selfCallsign, 0x00U, YSF_CALLSIGN_LENGTH);
for (unsigned int i = 0U; i < callsign.length(); i++)
m_selfCallsign[i] = callsign.at(i);
}
CYSFControl::~CYSFControl()
{
delete[] m_netSource;
delete[] m_netDest;
delete[] m_callsign;
delete[] m_selfCallsign;
}
bool CYSFControl::writeModem(unsigned char *data, unsigned int len)
{
assert(data != nullptr);
if (!m_enabled)
return false;
unsigned char type = data[0U];
if ((type == TAG_LOST) && (m_rfState == RPT_RF_STATE::AUDIO)) {
if (m_rssi != 0) {
LogMessage("YSF, transmission lost from %10.10s to %10.10s, %.1f seconds, BER: %.1f%%, RSSI: %d/%d/%d dBm", m_rfSource, m_rfDest, float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
writeJSONRF("lost", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
} else {
LogMessage("YSF, transmission lost from %10.10s to %10.10s, %.1f seconds, BER: %.1f%%", m_rfSource, m_rfDest, float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits));
writeJSONRF("lost", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits));
}
writeEndRF();
return false;
}
if ((type == TAG_LOST) && (m_rfState == RPT_RF_STATE::REJECTED)) {
m_rfPayload.reset();
m_rfSource = nullptr;
m_rfDest = nullptr;
m_rfState = RPT_RF_STATE::LISTENING;
return false;
}
if (type == TAG_LOST) {
m_rfPayload.reset();
m_rfState = RPT_RF_STATE::LISTENING;
return false;
}
// Have we got RSSI bytes on the end?
if (len == (YSF_FRAME_LENGTH_BYTES + 4U)) {
uint16_t raw = 0U;
raw |= (data[122U] << 8) & 0xFF00U;
raw |= (data[123U] << 0) & 0x00FFU;
// Convert the raw RSSI to dBm
m_rssi = m_rssiMapper->interpolate(raw);
if (m_rssi != 0)
LogDebug("YSF, raw RSSI: %u, reported RSSI: %d dBm", raw, m_rssi);
if (m_rssi < m_minRSSI)
m_minRSSI = m_rssi;
if (m_rssi > m_maxRSSI)
m_maxRSSI = m_rssi;
m_aveRSSI += m_rssi;
m_rssiCountTotal++;
m_rssiAccum += m_rssi;
m_rssiCount++;
}
CYSFFICH fich;
bool valid = fich.decode(data + 2U);
if (!valid) {
unsigned char fi = m_lastFICH.getFI();
unsigned char ft = m_lastFICH.getFT();
unsigned char fn = m_lastFICH.getFN();
unsigned char bt = m_lastFICH.getBT();
unsigned char bn = m_lastFICH.getBN();
if (fi == YSF_FI_COMMUNICATIONS && ft > 0U) {
fn++;
if (fn > ft) {
fn = 0U;
if (bt > 0U)
bn++;
}
}
m_lastFICH.setFI(YSF_FI_COMMUNICATIONS);
m_lastFICH.setFN(fn);
m_lastFICH.setBN(bn);
} else {
m_lastFICH = fich;
}
#ifdef notdef
// Stop repeater packets coming through, unless we're acting as a remote gateway
if (m_remoteGateway) {
unsigned char mr = m_lastFICH.getMR();
if (mr != YSF_MR_BUSY)
return false;
} else {
unsigned char mr = m_lastFICH.getMR();
if (mr == YSF_MR_BUSY)
return false;
}
#endif
unsigned char dt = m_lastFICH.getDT();
bool ret = false;
switch (dt) {
case YSF_DT_VOICE_FR_MODE:
ret = processVWData(valid, data);
break;
case YSF_DT_VD_MODE1:
case YSF_DT_VD_MODE2:
ret = processDNData(valid, data);
break;
case YSF_DT_DATA_FR_MODE:
ret = processFRData(valid, data);
break;
default:
break;
}
return ret;
}
bool CYSFControl::processVWData(bool valid, unsigned char *data)
{
unsigned char fi = m_lastFICH.getFI();
unsigned char dgid = m_lastFICH.getDGId();
if (valid && fi == YSF_FI_HEADER) {
if (m_rfState == RPT_RF_STATE::LISTENING) {
bool valid = m_rfPayload.processHeaderData(data + 2U);
if (!valid)
return false;
m_rfSource = m_rfPayload.getSource();
if (m_selfOnly) {
bool ret = checkCallsign(m_rfSource);
if (!ret) {
LogMessage("YSF, invalid access attempt from %10.10s to DG-ID %u", m_rfSource, dgid);
m_rfState = RPT_RF_STATE::REJECTED;
writeJSONRF("rejected", "voice_vw", m_rfSource, dgid);
return true;
}
}
unsigned char cm = m_lastFICH.getCM();
if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2)
m_rfDest = (unsigned char*)"ALL ";
else
m_rfDest = m_rfPayload.getDest();
m_rfFrames = 0U;
m_rfErrs = 0U;
m_rfBits = 1U;
m_rfTimeoutTimer.start();
m_rfState = RPT_RF_STATE::AUDIO;
m_minRSSI = m_rssi;
m_maxRSSI = m_rssi;
m_aveRSSI = m_rssi;
m_rssiCountTotal = 1U;
m_rssiAccum = m_rssi;
m_rssiCount = 1U;
m_bitErrsAccum = 0U;
m_bitsCount = 0U;
LogMessage("YSF, received RF header from %10.10s to DG-ID %u", m_rfSource, dgid);
writeJSONRF("start", "voice_vw", m_rfSource, dgid);
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
}
} else if (valid && (fi == YSF_FI_TERMINATOR)) {
if (m_rfState == RPT_RF_STATE::REJECTED) {
m_rfPayload.reset();
m_rfSource = nullptr;
m_rfDest = nullptr;
m_rfState = RPT_RF_STATE::LISTENING;
} else if (m_rfState == RPT_RF_STATE::AUDIO) {
m_rfPayload.processHeaderData(data + 2U);
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_EOT;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
if (m_rssi != 0) {
LogMessage("YSF, received RF end of transmission from %10.10s to DG-ID %u, %.1f seconds, BER: %.1f%%, RSSI: %d/%d/%d dBm", m_rfSource, dgid, float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
writeJSONRF("end", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
} else {
LogMessage("YSF, received RF end of transmission from %10.10s to DG-ID %u, %.1f seconds, BER: %.1f%%", m_rfSource, dgid, float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits));
writeJSONRF("end", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits));
}
writeEndRF();
}
} else {
if (m_rfState == RPT_RF_STATE::AUDIO) {
// If valid is false, update the m_lastFICH for this transmission
if (!valid) {
// XXX Check these values
m_lastFICH.setFT(0U);
m_lastFICH.setFN(0U);
}
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
unsigned char fn = fich.getFN();
unsigned char ft = fich.getFT();
if (fn == 0U && ft == 1U) {
// The first packet after the header is odd
m_rfPayload.processVoiceFRModeData(data + 2U);
unsigned int errors = m_rfPayload.processVoiceFRModeAudio2(data + 2U);
m_rfErrs += errors;
m_rfBits += 288U;
LogDebug("YSF, V Mode 3, seq %u, AMBE FEC %u/288 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 2.88F);
} else {
unsigned int errors = m_rfPayload.processVoiceFRModeAudio5(data + 2U);
m_rfErrs += errors;
m_rfBits += 720U;
LogDebug("YSF, V Mode 3, seq %u, AMBE FEC %u/720 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 7.2F);
writeJSONBER(720U, errors);
}
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
}
}
return false;
}
bool CYSFControl::processDNData(bool valid, unsigned char *data)
{
unsigned char fi = m_lastFICH.getFI();
unsigned char dgid = m_lastFICH.getDGId();
if (valid && fi == YSF_FI_HEADER) {
if (m_rfState == RPT_RF_STATE::LISTENING) {
bool valid = m_rfPayload.processHeaderData(data + 2U);
if (!valid)
return false;
m_rfSource = m_rfPayload.getSource();
if (m_selfOnly) {
bool ret = checkCallsign(m_rfSource);
if (!ret) {
LogMessage("YSF, invalid access attempt from %10.10s to DG-ID %u", m_rfSource, dgid);
m_rfState = RPT_RF_STATE::REJECTED;
writeJSONRF("rejected", "voice_dn", m_rfSource, dgid);
return true;
}
}
unsigned char cm = m_lastFICH.getCM();
if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2)
m_rfDest = (unsigned char*)"ALL ";
else
m_rfDest = m_rfPayload.getDest();
m_rfFrames = 0U;
m_rfErrs = 0U;
m_rfBits = 1U;
m_rfTimeoutTimer.start();
m_rfState = RPT_RF_STATE::AUDIO;
m_minRSSI = m_rssi;
m_maxRSSI = m_rssi;
m_aveRSSI = m_rssi;
m_rssiCountTotal = 1U;
m_rssiAccum = m_rssi;
m_rssiCount = 1U;
m_bitErrsAccum = 0U;
m_bitsCount = 0U;
LogMessage("YSF, received RF header from %10.10s to DG-ID %u", m_rfSource, dgid);
writeJSONRF("start", "voice_dn", m_rfSource, dgid);
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
}
} else if (valid && (fi == YSF_FI_TERMINATOR)) {
if (m_rfState == RPT_RF_STATE::REJECTED) {
m_rfPayload.reset();
m_rfSource = nullptr;
m_rfDest = nullptr;
m_rfState = RPT_RF_STATE::LISTENING;
} else if (m_rfState == RPT_RF_STATE::AUDIO) {
m_rfPayload.processHeaderData(data + 2U);
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_EOT;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
if (m_rssi != 0) {
LogMessage("YSF, received RF end of transmission from %10.10s to DG-ID %u, %.1f seconds, BER: %.1f%%, RSSI: %d/%d/%d dBm", m_rfSource, dgid, float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
writeJSONRF("end", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
} else {
LogMessage("YSF, received RF end of transmission from %10.10s to DG-ID %u, %.1f seconds, BER: %.1f%%", m_rfSource, dgid, float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits));
writeJSONRF("end", float(m_rfFrames) / 10.0F, float(m_rfErrs * 100U) / float(m_rfBits));
}
writeEndRF();
}
} else {
if (m_rfState == RPT_RF_STATE::AUDIO) {
// If valid is false, update the m_lastFICH for this transmission
if (!valid) {
unsigned char ft = m_lastFICH.getFT();
unsigned char fn = m_lastFICH.getFN() + 1U;
if (fn > ft)
fn = 0U;
m_lastFICH.setFN(fn);
}
CSync::addYSFSync(data + 2U);
unsigned char fn = m_lastFICH.getFN();
unsigned char dt = m_lastFICH.getDT();
switch (dt) {
case YSF_DT_VD_MODE1: {
m_rfPayload.processVDMode1Data(data + 2U, fn);
unsigned int errors = m_rfPayload.processVDMode1Audio(data + 2U);
m_rfErrs += errors;
m_rfBits += 235U;
LogDebug("YSF, V/D Mode 1, seq %u, AMBE FEC %u/235 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 2.35F);
writeJSONBER(235U, errors);
}
break;
case YSF_DT_VD_MODE2: {
m_rfPayload.processVDMode2Data(data + 2U, fn);
unsigned int errors = m_rfPayload.processVDMode2Audio(data + 2U);
m_rfErrs += errors;
m_rfBits += 405U;
LogDebug("YSF, V/D Mode 2, seq %u, Repetition FEC %u/405 (%.1f%%)", m_rfFrames % 128, errors, float(errors) / 4.05F);
writeJSONBER(405U, errors);
}
break;
default:
break;
}
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
} else if (valid && (m_rfState == RPT_RF_STATE::LISTENING)) {
// Only use clean frames for late entry.
unsigned char fn = m_lastFICH.getFN();
unsigned char dt = m_lastFICH.getDT();
switch (dt) {
case YSF_DT_VD_MODE1:
valid = m_rfPayload.processVDMode1Data(data + 2U, fn);
break;
case YSF_DT_VD_MODE2:
valid = m_rfPayload.processVDMode2Data(data + 2U, fn);
break;
default:
valid = false;
break;
}
if (!valid)
return false;
unsigned char cm = m_lastFICH.getCM();
if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2)
m_rfDest = (unsigned char*)"ALL ";
else
m_rfDest = m_rfPayload.getDest();
m_rfSource = m_rfPayload.getSource();
if (m_rfSource == nullptr || m_rfDest == nullptr)
return false;
if (m_selfOnly) {
bool ret = checkCallsign(m_rfSource);
if (!ret) {
LogMessage("YSF, invalid access attempt from %10.10s to DG-ID %u", m_rfSource, dgid);
m_rfState = RPT_RF_STATE::REJECTED;
writeJSONRF("rejected", "voice_dn", m_rfSource, dgid);
return true;
}
}
m_rfFrames = 0U;
m_rfErrs = 0U;
m_rfBits = 1U;
m_rfTimeoutTimer.start();
m_rfState = RPT_RF_STATE::AUDIO;
m_minRSSI = m_rssi;
m_maxRSSI = m_rssi;
m_aveRSSI = m_rssi;
m_rssiCountTotal = 1U;
m_rssiAccum = m_rssi;
m_rssiCount = 1U;
m_bitErrsAccum = 0U;
m_bitsCount = 0U;
// Build a new header and transmit it
unsigned char buffer[YSF_FRAME_LENGTH_BYTES + 2U];
CSync::addYSFSync(buffer + 2U);
CYSFFICH fich = m_lastFICH;
fich.setFI(YSF_FI_HEADER);
fich.encode(buffer + 2U);
unsigned char csd1[20U], csd2[20U];
memcpy(csd1 + YSF_CALLSIGN_LENGTH, m_rfSource, YSF_CALLSIGN_LENGTH);
memset(csd2, ' ', YSF_CALLSIGN_LENGTH + YSF_CALLSIGN_LENGTH);
if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2)
memset(csd1 + 0U, '*', YSF_CALLSIGN_LENGTH);
else
memcpy(csd1 + 0U, m_rfDest, YSF_CALLSIGN_LENGTH);
CYSFPayload payload;
payload.writeHeader(buffer + 2U, csd1, csd2);
buffer[0U] = TAG_DATA;
buffer[1U] = 0x00U;
writeNetwork(buffer, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(buffer + 2U);
writeQueueRF(buffer);
}
LogMessage("YSF, received RF late entry from %10.10s to DG-ID %u", m_rfSource, dgid);
writeJSONRF("late_entry", "voice_dn", m_rfSource, dgid);
CSync::addYSFSync(data + 2U);
fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
}
}
return false;
}
bool CYSFControl::processFRData(bool valid, unsigned char *data)
{
unsigned char fi = m_lastFICH.getFI();
unsigned char dgid = m_lastFICH.getDGId();
if (valid && fi == YSF_FI_HEADER) {
if (m_rfState == RPT_RF_STATE::LISTENING) {
valid = m_rfPayload.processHeaderData(data + 2U);
if (!valid)
return false;
m_rfSource = m_rfPayload.getSource();
if (m_selfOnly) {
bool ret = checkCallsign(m_rfSource);
if (!ret) {
LogMessage("YSF, invalid access attempt from %10.10s to DG-ID %u", m_rfSource, dgid);
m_rfState = RPT_RF_STATE::REJECTED;
writeJSONRF("rejected", "data_fr", m_rfSource, dgid);
return true;
}
}
unsigned char cm = m_lastFICH.getCM();
if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2)
m_rfDest = (unsigned char*)"ALL ";
else
m_rfDest = m_rfPayload.getDest();
m_rfFrames = 0U;
m_rfState = RPT_RF_STATE::DATA;
m_minRSSI = m_rssi;
m_maxRSSI = m_rssi;
m_aveRSSI = m_rssi;
m_rssiCountTotal = 1U;
m_rssiAccum = m_rssi;
m_rssiCount = 1U;
m_bitErrsAccum = 0U;
m_bitsCount = 0U;
LogMessage("YSF, received RF header from %10.10s to DG-ID %u", m_rfSource, dgid);
writeJSONRF("start", "data_fr", m_rfSource, dgid);
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
}
} else if (valid && fi == YSF_FI_TERMINATOR) {
if (m_rfState == RPT_RF_STATE::REJECTED) {
m_rfPayload.reset();
m_rfSource = nullptr;
m_rfDest = nullptr;
m_rfState = RPT_RF_STATE::LISTENING;
} else if (m_rfState == RPT_RF_STATE::DATA) {
m_rfPayload.processHeaderData(data + 2U);
CSync::addYSFSync(data + 2U);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_EOT;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
if (m_rssi != 0) {
LogMessage("YSF, received RF end of transmission from %10.10s to DG-ID %u, %.1f seconds, RSSI: %d/%d/%d dBm", m_rfSource, dgid, float(m_rfFrames) / 10.0F, m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
writeJSONRF("end", float(m_rfFrames) / 10.0F, 0.0F, m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
} else {
LogMessage("YSF, received RF end of transmission from %10.10s to DG-ID %u, %.1f seconds", m_rfSource, dgid, float(m_rfFrames) / 10.0F);
writeJSONRF("end", float(m_rfFrames) / 10.0F, 0.0F);
}
writeEndRF();
}
} else {
if (m_rfState == RPT_RF_STATE::DATA) {
// If valid is false, update the m_lastFICH for this transmission
if (!valid) {
unsigned char ft = m_lastFICH.getFT();
unsigned char fn = m_lastFICH.getFN() + 1U;
if (fn > ft)
fn = 0U;
m_lastFICH.setFN(fn);
}
CSync::addYSFSync(data + 2U);
unsigned char fn = m_lastFICH.getFN();
m_rfPayload.processDataFRModeData(data + 2U, fn);
CYSFFICH fich = m_lastFICH;
fich.encode(data + 2U);
data[0U] = TAG_DATA;
data[1U] = 0x00U;
writeNetwork(data, m_rfFrames % 128U);
if (m_duplex) {
fich.setMR(m_remoteGateway ? YSF_MR_NOT_BUSY : YSF_MR_BUSY);
fich.setDev(m_lowDeviation);
fich.encode(data + 2U);
writeQueueRF(data);
}
m_rfFrames++;
writeJSONRSSI();
return true;
}
}
return false;
}
unsigned int CYSFControl::readModem(unsigned char* data)
{
assert(data != nullptr);
if (m_queue.isEmpty())
return 0U;
unsigned char len = 0U;
m_queue.getData(&len, 1U);
m_queue.getData(data, len);
return len;
}
void CYSFControl::writeEndRF()
{
m_rfState = RPT_RF_STATE::LISTENING;
m_rfTimeoutTimer.stop();
m_rfPayload.reset();
// These variables are free'd by YSFPayload
m_rfSource = nullptr;
m_rfDest = nullptr;
if (m_netState == RPT_NET_STATE::IDLE) {
if (m_network != nullptr)
m_network->reset();
}
}
void CYSFControl::writeEndNet()
{
m_netState = RPT_NET_STATE::IDLE;
m_netTimeoutTimer.stop();
m_networkWatchdog.stop();
m_packetTimer.stop();
m_netPayload.reset();
if (m_network != nullptr)
m_network->reset();
}
void CYSFControl::writeNetwork()
{
unsigned char data[200U];
unsigned int length = m_network->read(data);
if (length == 0U)
return;
if (!m_enabled)
return;
if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE))
return;
m_networkWatchdog.start();
bool gateway = ::memcmp(data + 4U, m_callsign, YSF_CALLSIGN_LENGTH) == 0;
unsigned char n = (data[34U] & 0xFEU) >> 1;
bool end = (data[34U] & 0x01U) == 0x01U;
CYSFFICH fich;
bool valid = fich.decode(data + 35U);
unsigned char dgid = 0U;
if (valid)
dgid = fich.getDGId();
if (!m_netTimeoutTimer.isRunning()) {
if (end)
return;
::memcpy(m_netSource, data + 14U, YSF_CALLSIGN_LENGTH);
::memcpy(m_netDest, data + 24U, YSF_CALLSIGN_LENGTH);
if (::memcmp(m_netSource, " ", 10U) != 0 && ::memcmp(m_netDest, " ", 10U) != 0) {
LogMessage("YSF, received network data from %10.10s to DG-ID %u at %10.10s", m_netSource, dgid, data + 4U);
writeJSONNet("start", m_netSource, dgid, data + 4U);
}
m_netTimeoutTimer.start();
m_netPayload.reset();
m_packetTimer.start();
m_elapsed.start();
m_netState = RPT_NET_STATE::AUDIO;
m_netFrames = 0U;
m_netLost = 0U;
m_netBits = 1U;
m_netN = 0U;
} else {
// Check for duplicate frames, if we can
if (m_netN == n)
return;
}
data[33U] = end ? TAG_EOT : TAG_DATA;
data[34U] = 0x00U;
if (valid) {
unsigned char dt = fich.getDT();
unsigned char fn = fich.getFN();
unsigned char ft = fich.getFT();
unsigned char fi = fich.getFI();
unsigned char cm = fich.getCM();
if (::memcmp(m_netDest, " ", YSF_CALLSIGN_LENGTH) == 0) {
if (cm == YSF_CM_GROUP1 || cm == YSF_CM_GROUP2)
::memcpy(m_netDest, "ALL ", YSF_CALLSIGN_LENGTH);
}
if (m_remoteGateway) {
fich.setVoIP(false);
fich.setMR(YSF_MR_DIRECT);
} else {
fich.setVoIP(true);
fich.setMR(YSF_MR_BUSY);
}
fich.setDev(m_lowDeviation);
fich.encode(data + 35U);
// Set the downlink callsign
switch (fi) {
case YSF_FI_HEADER: {
bool ok = m_netPayload.processHeaderData(data + 35U);
if (ok)
processNetCallsigns(data, dgid);
}
break;
case YSF_FI_TERMINATOR:
m_netPayload.processHeaderData(data + 35U);
break;
case YSF_FI_COMMUNICATIONS:
switch (dt) {
case YSF_DT_VD_MODE1: {
bool ok = m_netPayload.processVDMode1Data(data + 35U, fn, gateway);
if (ok)
processNetCallsigns(data, dgid);
m_netPayload.processVDMode1Audio(data + 35U);
m_netBits += 235U;
}
break;
case YSF_DT_VD_MODE2: {
bool ok = m_netPayload.processVDMode2Data(data + 35U, fn, gateway);
if (ok)
processNetCallsigns(data, dgid);
m_netPayload.processVDMode2Audio(data + 35U);
m_netBits += 135U;
}
break;
case YSF_DT_DATA_FR_MODE:
m_netPayload.processDataFRModeData(data + 35U, fn, gateway);
break;
case YSF_DT_VOICE_FR_MODE:
if (fn == 0U && ft == 1U) {
// The first packet after the header is odd
m_netPayload.processVoiceFRModeData(data + 35U);
m_netPayload.processVoiceFRModeAudio2(data + 35U);
m_netBits += 288U;
} else {
m_netPayload.processVoiceFRModeAudio5(data + 35U);
m_netBits += 720U;
}
break;
default:
break;
}
break;
default:
break;
}
}
writeQueueNet(data + 33U);
m_packetTimer.start();
m_netFrames++;
m_netN = n;
if (end) {
LogMessage("YSF, received network end of transmission from %10.10s to DG-ID %u at %10.10s, %.1f seconds, %u%% packet loss", m_netSource, dgid, data + 4U, float(m_netFrames) / 10.0F, (m_netLost * 100U) / m_netFrames);
writeJSONNet("end", float(m_netFrames) / 10.0F, (m_netLost * 100U) / m_netFrames);
writeEndNet();
}
}
void CYSFControl::clock(unsigned int ms)
{
if (m_network != nullptr)
writeNetwork();
if (!m_enabled)
return;
m_rfTimeoutTimer.clock(ms);
m_netTimeoutTimer.clock(ms);
if (m_netState == RPT_NET_STATE::AUDIO) {
m_networkWatchdog.clock(ms);
if (m_networkWatchdog.hasExpired()) {
LogMessage("YSF, network watchdog has expired, %.1f seconds, %u%% packet loss", float(m_netFrames) / 10.0F, (m_netLost * 100U) / m_netFrames);
writeJSONNet("lost", float(m_netFrames) / 10.0F, (m_netLost * 100U) / m_netFrames);
writeEndNet();
}
}
}
void CYSFControl::writeQueueRF(const unsigned char *data)
{
assert(data != nullptr);
if (m_netState != RPT_NET_STATE::IDLE)
return;
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
return;
unsigned char len = YSF_FRAME_LENGTH_BYTES + 2U;
unsigned int space = m_queue.freeSpace();
if (space < (len + 1U)) {
LogError("YSF, overflow in the System Fusion RF queue");
return;
}
m_queue.addData(&len, 1U);
m_queue.addData(data, len);
}
void CYSFControl::writeQueueNet(const unsigned char *data)
{
assert(data != nullptr);
if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired())
return;
unsigned char len = YSF_FRAME_LENGTH_BYTES + 2U;
unsigned int space = m_queue.freeSpace();
if (space < (len + 1U)) {
LogError("YSF, overflow in the System Fusion RF queue");
return;
}
m_queue.addData(&len, 1U);
m_queue.addData(data, len);
}
void CYSFControl::writeNetwork(const unsigned char *data, unsigned int count)
{
assert(data != nullptr);
if (m_network == nullptr)
return;
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
return;
m_network->write(m_rfSource, m_rfDest, data + 2U, count, data[0U] == TAG_EOT);
}
bool CYSFControl::checkCallsign(const unsigned char* callsign) const
{
return ::memcmp(callsign, m_selfCallsign, ::strlen((char*)m_selfCallsign)) == 0;
}
void CYSFControl::processNetCallsigns(const unsigned char* data, unsigned char dgid)
{
assert(data != nullptr);
if (::memcmp(m_netSource, " ", 10U) == 0 || ::memcmp(m_netDest, " ", 10U) == 0) {
if (::memcmp(m_netSource, " ", YSF_CALLSIGN_LENGTH) == 0) {
unsigned char* source = m_netPayload.getSource();
if (source != nullptr)
::memcpy(m_netSource, source, YSF_CALLSIGN_LENGTH);
}
if (::memcmp(m_netDest, " ", YSF_CALLSIGN_LENGTH) == 0) {
unsigned char* dest = m_netPayload.getDest();
if (dest != nullptr)
::memcpy(m_netDest, dest, YSF_CALLSIGN_LENGTH);
}
if (::memcmp(m_netSource, " ", 10U) != 0 && ::memcmp(m_netDest, " ", 10U) != 0) {
LogMessage("YSF, received network data from %10.10s to DG-ID %u at %10.10s", m_netSource, dgid, data + 4U);
writeJSONNet("start", m_netSource, dgid, data + 4U);
}
}
}
bool CYSFControl::isBusy() const
{
return (m_rfState != RPT_RF_STATE::LISTENING) || (m_netState != RPT_NET_STATE::IDLE);
}
void CYSFControl::enable(bool enabled)
{
if (!enabled && m_enabled) {
m_queue.clear();
// Reset the RF section
switch (m_rfState) {
case RPT_RF_STATE::LISTENING:
case RPT_RF_STATE::REJECTED:
case RPT_RF_STATE::INVALID:
break;
default:
if (m_rfTimeoutTimer.isRunning()) {
LogMessage("YSF, RF user has timed out");
}
break;
}
m_rfState = RPT_RF_STATE::LISTENING;
m_rfTimeoutTimer.stop();
m_rfPayload.reset();
// These variables are free'd by YSFPayload
m_rfSource = nullptr;
m_rfDest = nullptr;
// Reset the networking section
switch(m_netState) {
case RPT_NET_STATE::IDLE:
break;
default:
if (m_netTimeoutTimer.isRunning()) {
LogMessage("YSF, network user has timed out");
}
break;
}
m_netState = RPT_NET_STATE::IDLE;
m_netTimeoutTimer.stop();
m_networkWatchdog.stop();
m_packetTimer.stop();
m_netPayload.reset();
}
m_enabled = enabled;
}
void CYSFControl::writeJSONRSSI()
{
if (m_rssi == 0)
return;
if (m_rssiCount >= RSSI_COUNT) {
nlohmann::json json;
json["timestamp"] = CUtils::createTimestamp();
json["mode"] = "YSF";
json["value"] = m_rssiAccum / int(m_rssiCount);
WriteJSON("RSSI", json);
m_rssiAccum = 0;
m_rssiCount = 0U;
}
}
void CYSFControl::writeJSONBER(unsigned int bits, unsigned int errs)
{
m_bitsCount += bits;
m_bitErrsAccum += errs;
if (m_bitsCount >= (BER_COUNT * bits)) {
nlohmann::json json;
json["timestamp"] = CUtils::createTimestamp();
json["mode"] = "YSF";
json["value"] = float(m_bitErrsAccum * 100U) / float(m_bitsCount);
WriteJSON("BER", json);
m_bitErrsAccum = 0U;
m_bitsCount = 1U;
}
}
void CYSFControl::writeJSONRF(const char* action, const char* mode, const unsigned char* source, unsigned char dgid)
{
assert(action != nullptr);
assert(mode != nullptr);
assert(source != nullptr);
nlohmann::json json;
writeJSONRF(json, action, source, dgid);
json["mode"] = mode;
WriteJSON("YSF", json);
}
void CYSFControl::writeJSONRF(const char* action, float duration, float ber)
{
assert(action != nullptr);
nlohmann::json json;
writeJSONRF(json, action);
json["duration"] = duration;
json["ber"] = ber;
WriteJSON("YSF", json);
}
void CYSFControl::writeJSONRF(const char* action, float duration, float ber, int minRSSI, int maxRSSI, int aveRSSI)
{
assert(action != nullptr);
nlohmann::json json;
writeJSONRF(json, action);
json["duration"] = duration;
json["ber"] = ber;
nlohmann::json rssi;
rssi["min"] = minRSSI;
rssi["max"] = maxRSSI;
rssi["ave"] = aveRSSI;
json["rssi"] = rssi;
WriteJSON("YSF", json);
}
void CYSFControl::writeJSONNet(const char* action, const unsigned char* source, unsigned char dgid, const unsigned char* reflector)
{
assert(action != nullptr);
assert(source != nullptr);
assert(reflector != nullptr);
nlohmann::json json;
writeJSONNet(json, action, source, dgid);
json["reflector"] = convertBuffer(reflector);
WriteJSON("YSF", json);
}
void CYSFControl::writeJSONNet(const char* action, float duration, unsigned int loss)
{
assert(action != nullptr);
nlohmann::json json;
writeJSONNet(json, action);
json["duration"] = duration;
json["loss"] = loss;
WriteJSON("YSF", json);
}
void CYSFControl::writeJSONRF(nlohmann::json& json, const char* action)
{
assert(action != nullptr);
json["timestamp"] = CUtils::createTimestamp();
json["action"] = action;
}
void CYSFControl::writeJSONRF(nlohmann::json& json, const char* action, const unsigned char* source, unsigned char dgid)
{
assert(action != nullptr);
assert(source != nullptr);
json["timestamp"] = CUtils::createTimestamp();
json["source_cs"] = convertBuffer(source);
json["source"] = "rf";
json["action"] = action;
json["dg-id"] = int(dgid);
json["reflector"] = "";
}
void CYSFControl::writeJSONNet(nlohmann::json& json, const char* action)
{
assert(action != nullptr);
json["timestamp"] = CUtils::createTimestamp();
json["action"] = action;
}
void CYSFControl::writeJSONNet(nlohmann::json& json, const char* action, const unsigned char* source, unsigned char dgid)
{
assert(action != nullptr);
assert(source != nullptr);
json["timestamp"] = CUtils::createTimestamp();
json["source_cs"] = convertBuffer(source);
json["source"] = "network";
json["action"] = action;
json["dg-id"] = int(dgid);
}
std::string CYSFControl::convertBuffer(const unsigned char* buffer) const
{
assert(buffer != nullptr);
std::string callsign((char*)buffer, 10U);
size_t pos = callsign.find_first_of(' ');
if (pos != std::string::npos)
callsign = callsign.substr(0U, pos);
return callsign;
}
#endif