Merge branch 'feature/RS-MS1AMsgtoAPRS_#9' into develop

pull/32/head
Geoffrey Merck 4 years ago
commit 16b798a767

@ -18,16 +18,16 @@ jobs:
name: Install dependencies
command: |
sudo apt-get update
sudo apt-get -y install libgtest-dev libcurl4-openssl-dev libboost-dev
sudo apt-get -y install libgtest-dev libcurl4-openssl-dev libboost-dev libgps-dev
- run:
name: "Build App"
command: "make -j 3 dstargateway"
command: "make -j 3 dstargateway USE_GPSD=1"
- run:
name: "Build Tests"
command: "make -j 3 tests"
command: "make -j 3 tests USE_GPSD=1"
- run:
name: "Run Tests"
command: "make run-tests"
command: "make run-tests USE_GPSD=1"
# Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:

@ -7,7 +7,71 @@
}
],
"files.associations": {
"new": "cpp"
"new": "cpp",
"type_traits": "cpp",
"array": "cpp",
"*.tcc": "cpp",
"chrono": "cpp",
"deque": "cpp",
"list": "cpp",
"vector": "cpp",
"optional": "cpp",
"stop_token": "cpp",
"any": "cpp",
"bitset": "cpp",
"memory": "cpp",
"random": "cpp",
"future": "cpp",
"memory_resource": "cpp",
"ranges": "cpp",
"mutex": "cpp",
"condition_variable": "cpp",
"forward_list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"algorithm": "cpp",
"cctype": "cpp",
"cstddef": "cpp",
"cstdlib": "cpp",
"cwchar": "cpp",
"atomic": "cpp",
"bit": "cpp",
"compare": "cpp",
"cstdint": "cpp",
"exception": "cpp",
"functional": "cpp",
"iterator": "cpp",
"string_view": "cpp",
"tuple": "cpp",
"utility": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"limits": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"typeinfo": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"complex": "cpp",
"concepts": "cpp",
"cstdio": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwctype": "cpp",
"istream": "cpp",
"numbers": "cpp",
"ratio": "cpp",
"semaphore": "cpp",
"streambuf": "cpp",
"system_error": "cpp",
"thread": "cpp",
"typeindex": "cpp",
"variant": "cpp"
},
"editor.tokenColorCustomizations": {
"textMateRules": [

18
.vscode/tasks.json vendored

@ -8,9 +8,14 @@
"type": "shell",
"command": "make",
"args": [
"-j3", "ENABLE_DEBUG=1"
"-j3",
"ENABLE_DEBUG=1",
"USE_GPSD=1"
],
"group": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
@ -19,12 +24,11 @@
"command": "make",
"args": [
"-j3",
"tests", "ENABLE_DEBUG=1"
"tests",
"ENABLE_DEBUG=1",
"USE_GPSD=1"
],
"group": {
"kind": "build",
"isDefault": true
},
"group": "build",
"problemMatcher": []
}
]

@ -28,25 +28,19 @@
#include "NMEASentenceCollector.h"
#include "GPSACollector.h"
#include "RSMS1AMessageCollector.h"
const unsigned int APRS_CSUM_LENGTH = 4U;
const unsigned int APRS_DATA_LENGTH = 300U;
const unsigned int SLOW_DATA_BLOCK_LENGTH = 6U;
const char APRS_OVERLAY = '\\';
const char APRS_SYMBOL = 'K';
#include "SlowDataCollectorThrottle.h"
CAPRSCollector::CAPRSCollector() :
m_collectors()
{
m_collectors.push_back(new CRSMS1AMessageCollector());
m_collectors.push_back(new CGPSACollector());
m_collectors.push_back(new CNMEASentenceCollector("$GPRMC"));
m_collectors.push_back(new CNMEASentenceCollector("$GPGGA"));
m_collectors.push_back(new CNMEASentenceCollector("$GPGLL"));
m_collectors.push_back(new CNMEASentenceCollector("$GPVTG"));
m_collectors.push_back(new CNMEASentenceCollector("$GPGSA"));
m_collectors.push_back(new CNMEASentenceCollector("$GPGSV"));
m_collectors.push_back(new CRSMS1AMessageCollector()); // we do not throttle messages, they have highest priority !
m_collectors.push_back(new CSlowDataCollectorThrottle(new CGPSACollector(), 10U));
m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGGA"), 10U));
m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGLL"), 10U));
m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPVTG"), 10U));
m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPRMC"), 10U));
m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGSA"), 10U));
m_collectors.push_back(new CSlowDataCollectorThrottle(new CNMEASentenceCollector("$GPGSV"), 10U));
}
CAPRSCollector::~CAPRSCollector()
@ -67,6 +61,7 @@ void CAPRSCollector::writeHeader(const std::string& callsign)
bool CAPRSCollector::writeData(const unsigned char* data)
{
bool ret = false;
for(auto collector : m_collectors) {
bool ret2 = collector->writeData(data);
ret = ret || ret2;
@ -99,3 +94,21 @@ unsigned int CAPRSCollector::getData(unsigned char dataType, unsigned char* data
}
return 0U;
}
void CAPRSCollector::getData(std::function<void(const std::string&)> dataHandler)
{
for(auto collector : m_collectors) {
std::string data;
if(collector->getData(data)) {
dataHandler(data);
collector->reset();
}
}
}
void CAPRSCollector::clock(unsigned int ms)
{
for(auto collector : m_collectors) {
collector->clock(ms);
}
}

@ -21,6 +21,7 @@
#define APRSCollector_H
#include <vector>
#include <functional>
#include "SlowDataCollector.h"
#include "Defs.h"
@ -47,9 +48,13 @@ public:
void sync();
unsigned int getData(unsigned char dataType, unsigned char* data, unsigned int length);
void getData(std::function<void(const std::string&)> dataHandler);
void clock(unsigned int ms);
private:
std::vector<CSlowDataCollector *> m_collectors;
std::vector<ISlowDataCollector *> m_collectors;
};
#endif

@ -106,6 +106,7 @@ void CAPRSEntry::clock(unsigned int ms)
{
m_linkStatus.clock(ms);
m_timer.clock(ms);
m_collector->clock(ms);
}
bool CAPRSEntry::isOK()

@ -29,7 +29,7 @@ CAPRSIdFrameProvider(20U) // Initial timeout of 20 seconds
}
bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector<std::string>& frames)
bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector<CAPRSFrame *>& frames)
{
if (entry == nullptr)
return false;
@ -107,24 +107,33 @@ bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, c
boost::replace_all(lat, ",", ".");
boost::replace_all(lon, ",", ".");
std::string output = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%caRNG%04.0lf/A=%06.0lf %s %s\r\n",
gateway.c_str(), gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
std::string body = CStringUtils::string_format(";%-7s%-2s*%02d%02d%02dz%s%cD%s%caRNG%04.0lf/A=%06.0lf %s %s\r\n",
entry->getCallsign().c_str(), entry->getBand().c_str(),
tm->tm_mday, tm->tm_hour, tm->tm_min,
lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N',
lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E',
entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str());
CAPRSFrame * frame = new CAPRSFrame(gateway + "-S",
"APD5T1",
{ "TCPIP*", "qAC" , gateway + "-GS" },
body, APFT_OBJECT);
frames.push_back(output);
frames.push_back(frame);
if (entry->getBand().length() == 1U) {
output = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&RNG%04.0lf/A=%06.0lf %s %s\r\n",
entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
body = CStringUtils::string_format("!%s%cD%s%c&RNG%04.0lf/A=%06.0lf %s %s\r\n",
lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N',
lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E',
entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str());
frames.push_back(output);
frame = new CAPRSFrame(entry->getCallsign() + "-" + entry->getBand(),
"APD5T2",
{ "TCPIP*", "qAC", entry->getCallsign() + "-" + entry->getBand() + "S"},
body, APFT_POSITION);
frames.push_back(frame);
}
setTimeout(20U * 60U);//20 minutes, plenty enough for fixed

@ -26,5 +26,5 @@ public:
CAPRSFixedIdFrameProvider();
protected:
virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<std::string>& frames);
virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<CAPRSFrame *>& frames);
};

@ -0,0 +1,47 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <boost/algorithm/string.hpp>
#include "APRSFormater.h"
#include "Log.h"
#include "StringUtils.h"
bool CAPRSFormater::frameToString(std::string& output, CAPRSFrame& frame)
{
// make sur we have the minimal stuff to build a correct aprs string
if(frame.getSource().empty()
|| frame.getDestination().empty()
|| frame.getBody().empty()) {
CLog::logWarning("Invalid APRS frame, missing source, destination or body");
return false;
}
auto path = boost::join_if(frame.getPath(), ",", [](std::string s) { return !string_is_blank_or_empty(s); });
CStringUtils::string_format_in_place(output, "%s>%s%s%s:%s",
frame.getSource().c_str(),
frame.getDestination().c_str(),
path.empty() ? "" : ",",
path.c_str(),
frame.getBody().c_str());
return true;
}

@ -18,16 +18,10 @@
#pragma once
#include "ReadAPRSFrameCallback.h"
#include "APRSFrame.h"
class CAPRSReader : public CReadAPRSFrameCallback
class CAPRSFormater
{
public:
CAPRSReader();
bool readAprsFrame(const std::string& aprsFrame);
private:
};
static bool frameToString(std::string& output, CAPRSFrame& frame);
};

@ -22,20 +22,27 @@ CAPRSFrame::CAPRSFrame() :
m_source(),
m_destination(),
m_path(),
m_body(),
m_type(APFT_UNKNOWN)
{
}
CAPRSFrame::CAPRSFrame(const std::string& source, const std::string& destination, const std::vector<std::string>& path, APRS_FRAME_TYPE type) :
CAPRSFrame::CAPRSFrame(const std::string& source, const std::string& destination, const std::vector<std::string>& path, const std::string& body, APRS_FRAME_TYPE type) :
m_source(source),
m_destination(destination),
m_path(),
m_body(body),
m_type(type)
{
m_path.assign(path.begin(), path.end());
}
CAPRSFrame::~CAPRSFrame()
{
m_path.clear();
}
void CAPRSFrame::clear()
{
m_source.clear();

@ -25,12 +25,19 @@
enum APRS_FRAME_TYPE {
APFT_UNKNOWN = 0,
APFT_MESSAGE,
APFT_POSITION,
APFT_NMEA,
APFT_STATUS,
APFT_OBJECT,
APFT_WX,
APFT_TELEMETRY
};
class CAPRSFrame {
public:
CAPRSFrame();
CAPRSFrame(const std::string& source, const std::string& destination, const std::vector<std::string>& path, APRS_FRAME_TYPE type);
CAPRSFrame(const std::string& source, const std::string& destination, const std::vector<std::string>& path, const std::string& body, APRS_FRAME_TYPE type);
~CAPRSFrame();
void clear();
std::string& getSource() { return m_source; }

@ -16,7 +16,7 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef USE_DGPS
#ifdef USE_GPSD
#include <cmath>
#include <boost/algorithm/string.hpp>
@ -56,7 +56,7 @@ void CAPRSGPSDIdFrameProvider::close()
}
}
bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector<std::string>& frames)
bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector<CAPRSFrame *>& frames)
{
if(!m_hasConnection) {
this->start();
@ -169,40 +169,44 @@ bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, co
boost::replace_all(lat, ",", ".");
boost::replace_all(lon, ",", ".");
std::string output1 = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%ca/A=%06.0lf",
gateway.c_str(), gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
std::string body = CStringUtils::string_format(";%-7s%-2s*%02d%02d%02dz%s%cD%s%ca/A=%06.0lf",
entry->getCallsign().c_str(), entry->getBand().c_str(),
tm->tm_mday, tm->tm_hour, tm->tm_min,
lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N',
lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E',
rawAltitude * 3.28);
std::string output2;
if (bearingSet && velocitySet)
output2 = CStringUtils::string_format("%03.0lf/%03.0lf", rawBearing, rawVelocity * 0.539957F);
body.append(CStringUtils::string_format("%03.0lf/%03.0lf", rawBearing, rawVelocity * 0.539957F));
std::string output3;
output3 = CStringUtils::string_format("RNG%04.0lf %s %s\r\n", entry->getRange() * 0.6214, band.c_str(), desc.c_str());
body.append(CStringUtils::string_format("RNG%04.0lf %s %s\r\n", entry->getRange() * 0.6214, band.c_str(), desc.c_str()));
CLog::logDebug("APRS ==> %s%s%s", output1.c_str(), output2.c_str(), output3.c_str());
frames.push_back(output1.append(output2).append(output3));
CAPRSFrame * frame = new CAPRSFrame(gateway + "-S",
"APD5T1",
{ "TCPIP*", "qAC" , gateway + "-GS" },
body, APFT_OBJECT);
frames.push_back(frame);
if (entry->getBand().length() == 1U) {
if (altitudeSet)
output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&/A=%06.0lf",
entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
body = CStringUtils::string_format("%s%cD%s%c&/A=%06.0lf",
lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N',
lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E',
rawAltitude * 3.28);
else
output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&",
entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
body = CStringUtils::string_format("!%s%cD%s%c&",
lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N',
lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E');
CLog::logDebug("APRS ==> %s%s%s", output1.c_str(), output2.c_str(), output3.c_str());
frame = new CAPRSFrame(gateway,
"APD5T2",
{ "TCPIP*", "qAC" , gateway + "-GS" },
body, APFT_POSITION);
frames.push_back(output1.append(output2).append(output3));
frames.push_back(frame);
}
setTimeout(60U * 5U);//5 Minutes is plenty enough we aint an APRS tracker !

@ -35,7 +35,7 @@ public:
virtual void close();
protected:
virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<std::string>& frames);
virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<CAPRSFrame *>& frames);
private:
std::string m_gpsdAddress;

@ -21,15 +21,20 @@
#include <boost/algorithm/string.hpp>
#include <cmath>
#include <cassert>
#include <algorithm>
#include "StringUtils.h"
#include "Log.h"
#include "APRSWriter.h"
#include "APRSHandler.h"
#include "DStarDefines.h"
#include "Defs.h"
#include "Log.h"
#include "APRSFrame.h"
#include "APRSParser.h"
#include "APRSFormater.h"
#include "APRSUtils.h"
CAPRSWriter::CAPRSWriter(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address) :
CAPRSHandler::CAPRSHandler(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address) :
m_thread(NULL),
m_gateway(),
m_address(),
@ -42,14 +47,14 @@ m_idFrameProvider(nullptr)
assert(!gateway.empty());
assert(!password.empty());
m_thread = new CAPRSWriterThread(gateway, password, address, hostname, port);
m_thread = new CAPRSHandlerThread(gateway, password, address, hostname, port);
m_gateway = gateway;
m_gateway = m_gateway.substr(0, LONG_CALLSIGN_LENGTH - 1U);
boost::trim(m_gateway);
}
CAPRSWriter::~CAPRSWriter()
CAPRSHandler::~CAPRSHandler()
{
for(auto it = m_array.begin(); it != m_array.end(); it++) {
delete it->second;
@ -58,7 +63,7 @@ CAPRSWriter::~CAPRSWriter()
m_array.clear();
}
void CAPRSWriter::setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl)
void CAPRSHandler::setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl)
{
std::string temp = callsign;
temp.resize(LONG_CALLSIGN_LENGTH - 1U, ' ');
@ -67,12 +72,12 @@ void CAPRSWriter::setPort(const std::string& callsign, const std::string& band,
m_array[temp] = new CAPRSEntry(callsign, band, frequency, offset, range, latitude, longitude, agl);
}
bool CAPRSWriter::open()
bool CAPRSHandler::open()
{
return m_thread->start();
}
void CAPRSWriter::writeHeader(const std::string& callsign, const CHeaderData& header)
void CAPRSHandler::writeHeader(const std::string& callsign, const CHeaderData& header)
{
CAPRSEntry* entry = m_array[callsign];
if (entry == NULL) {
@ -87,7 +92,7 @@ void CAPRSWriter::writeHeader(const std::string& callsign, const CHeaderData& he
collector->writeHeader(header.getMyCall1());
}
void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data)
void CAPRSHandler::writeData(const std::string& callsign, const CAMBEData& data)
{
if (data.isEnd())
return;
@ -117,48 +122,31 @@ void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data)
return;
}
// Check the transmission timer
bool ok = entry->isOK();
if (!ok) {
collector->reset();
return;
}
unsigned int length = collector->getData(SLOW_DATA_TYPE_GPS, buffer, 400U);
std::string text((char*)buffer, length);
auto n = text.find(':');
if (n == std::string::npos) {
collector->reset();
return;
}
std::string header = text.substr(0, n);
std::string body = text.substr(n + 1U);
// If we already have a q-construct, don't send it on
n = header.find('q');
if (n != std::string::npos)
return;
// Remove the trailing \r
n = body.find('\r');
if (n != std::string::npos)
body = body.substr(0, n);
std::string output = CStringUtils::string_format("%s,qAR,%s-%s:%s", header.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), body.c_str());
collector->getData([=](const std::string& text)
{
CAPRSFrame frame;
if(!CAPRSParser::parseFrame(text, frame)) {
CLog::logWarning("Failed to parse DPRS Frame : %s", text.c_str());
return;
}
char ascii[500U];
::memset(ascii, 0x00, 500U);
for (unsigned int i = 0U; i < output.length(); i++)
ascii[i] = output[i];
// If we already have a q-construct, don't send it on
if(std::any_of(frame.getPath().begin(), frame.getPath().end(), [] (std::string s) { return !s.empty() && s[0] == 'q'; })) {
CLog::logWarning("DPRS Frame already has q construct, not forwarding to APRS-IS: %s", text.c_str());
return;
}
m_thread->write(ascii);
frame.getPath().push_back("qAR");
frame.getPath().push_back(CStringUtils::string_format("%s-%s", entry->getCallsign().c_str(), entry->getBand().c_str()));
std::string output ;
CAPRSFormater::frameToString(output, frame);
collector->reset();
m_thread->write(frame);
});
}
void CAPRSWriter::writeStatus(const std::string& callsign, const std::string status)
void CAPRSHandler::writeStatus(const std::string& callsign, const std::string status)
{
CAPRSEntry* entry = m_array[callsign];
if (entry == NULL) {
@ -169,7 +157,7 @@ void CAPRSWriter::writeStatus(const std::string& callsign, const std::string sta
entry->getStatus().setStatus(status);
}
void CAPRSWriter::clock(unsigned int ms)
void CAPRSHandler::clock(unsigned int ms)
{
m_thread->clock(ms);
@ -189,48 +177,54 @@ void CAPRSWriter::clock(unsigned int ms)
}
}
void CAPRSWriter::sendStatusFrame(CAPRSEntry * entry)
void CAPRSHandler::sendStatusFrame(CAPRSEntry * entry)
{
assert(entry != nullptr);
if(!m_thread->isConnected())
return;
auto linkStatus = entry->getStatus();
std::string body = boost::trim_copy(linkStatus.getStatus());
if(body[0] != '>')
body = '>' + body;
body.insert(0, ">");
std::string output = CStringUtils::string_format("%s-%s>APD5T3,TCPIP*,qAC,%s-%sS:%s\r\n",
entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
body.c_str());
std::string sourCall = entry->getCallsign() + '-' + entry->getBand();
CAPRSFrame frame(sourCall,
"APD5T3",
{ "TCPIP*", "qAC", sourCall + "S" },
body,
APFT_STATUS);
m_thread->write(output.c_str());
m_thread->write(frame);
}
void CAPRSWriter::sendIdFrames()
void CAPRSHandler::sendIdFrames()
{
if(m_thread->isConnected())
{
for(auto entry : m_array) {
std::vector<std::string> frames;
std::vector<CAPRSFrame *> frames;
if(m_idFrameProvider->buildAPRSFrames(m_gateway, entry.second, frames)) {
for(auto frame : frames) {
m_thread->write(frame.c_str());
m_thread->write(*frame);
delete frame;
}
}
}
}
}
bool CAPRSWriter::isConnected() const
bool CAPRSHandler::isConnected() const
{
return m_thread->isConnected();
}
void CAPRSWriter::close()
void CAPRSHandler::close()
{
if(m_idFrameProvider != nullptr) {
m_idFrameProvider->close();
@ -240,3 +234,8 @@ void CAPRSWriter::close()
m_thread->stop();
}
void CAPRSHandler::addReadAPRSCallback(IReadAPRSFrameCallback* cb)
{
m_thread->addReadAPRSCallback(cb);
}

@ -17,8 +17,7 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef APRSWriter_H
#define APRSWriter_H
#pragma once
#include "Defs.h"
@ -27,7 +26,7 @@
#include "APRSEntry.h"
#include "APRSWriterThread.h"
#include "APRSHandlerThread.h"
#include "UDPReaderWriter.h"
#include "APRSCollector.h"
#include "DStarDefines.h"
@ -36,10 +35,10 @@
#include "Timer.h"
#include "APRSIdFrameProvider.h"
class CAPRSWriter {
class CAPRSHandler {
public:
CAPRSWriter(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address);
~CAPRSWriter();
CAPRSHandler(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address);
~CAPRSHandler();
bool open();
@ -59,8 +58,10 @@ public:
void close();
void addReadAPRSCallback(IReadAPRSFrameCallback* cb);
private:
CAPRSWriterThread* m_thread;
CAPRSHandlerThread* m_thread;
std::string m_gateway;
in_addr m_address;
unsigned int m_port;
@ -71,5 +72,3 @@ private:
void sendStatusFrame(CAPRSEntry * entrry);
};
#endif

@ -21,18 +21,22 @@
#include <iostream>
#include <boost/algorithm/string.hpp>
#include "APRSWriterThread.h"
#include "APRSHandlerThread.h"
#include "DStarDefines.h"
#include "Utils.h"
#include "Defs.h"
#include "Log.h"
#include "Version.h"
#include "APRSFormater.h"
#include "APRSParser.h"
// #define DUMP_TX
const unsigned int APRS_TIMEOUT = 10U;
const unsigned int APRS_READ_TIMEOUT = 1U;
const unsigned int APRS_KEEP_ALIVE_TIMEOUT = 60U;
CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) :
CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) :
CThread(),
m_username(callsign),
m_password(password),
@ -42,9 +46,10 @@ m_queue(20U),
m_exit(false),
m_connected(false),
m_reconnectTimer(1000U),
m_keepAliveTimer(1000U, APRS_KEEP_ALIVE_TIMEOUT),
m_tries(0U),
m_APRSReadCallback(),
m_filter(""),
m_APRSReadCallbacks(),
m_filter(),
m_clientName(FULL_PRODUCT_NAME)
{
assert(!callsign.empty());
@ -59,7 +64,7 @@ m_clientName(FULL_PRODUCT_NAME)
m_ssid = m_ssid.substr(LONG_CALLSIGN_LENGTH - 1U, 1);
}
CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter, const std::string& clientName) :
CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter) :
CThread(),
m_username(callsign),
m_password(password),
@ -69,10 +74,11 @@ m_queue(20U),
m_exit(false),
m_connected(false),
m_reconnectTimer(1000U),
m_keepAliveTimer(1000U, APRS_KEEP_ALIVE_TIMEOUT),
m_tries(0U),
m_APRSReadCallback(),
m_APRSReadCallbacks(),
m_filter(filter),
m_clientName(clientName)
m_clientName(FULL_PRODUCT_NAME)
{
assert(!callsign.empty());
assert(!password.empty());
@ -86,16 +92,12 @@ m_clientName(clientName)
m_ssid = m_ssid.substr(LONG_CALLSIGN_LENGTH - 1U, 1);
}
CAPRSWriterThread::~CAPRSWriterThread()
CAPRSHandlerThread::~CAPRSHandlerThread()
{
std::vector<CReadAPRSFrameCallback *> callBacksCopy;
callBacksCopy.assign(m_APRSReadCallback.begin(), m_APRSReadCallback.end());
m_APRSReadCallback.clear();
std::vector<IReadAPRSFrameCallback *> callBacksCopy;
callBacksCopy.assign(m_APRSReadCallbacks.begin(), m_APRSReadCallbacks.end());
for(auto cb : callBacksCopy) {
delete cb;
}
m_APRSReadCallbacks.clear();
callBacksCopy.clear();
@ -103,7 +105,7 @@ CAPRSWriterThread::~CAPRSWriterThread()
m_password.clear();
}
bool CAPRSWriterThread::start()
bool CAPRSHandlerThread::start()
{
Create();
Run();
@ -111,7 +113,7 @@ bool CAPRSWriterThread::start()
return true;
}
void* CAPRSWriterThread::Entry()
void* CAPRSHandlerThread::Entry()
{
CLog::logInfo("Starting the APRS Writer thread");
@ -122,16 +124,22 @@ void* CAPRSWriterThread::Entry()
}
try {
m_keepAliveTimer.start();
while (!m_exit) {
if (!m_connected) {
Sleep(100U);
if (m_reconnectTimer.isRunning() && m_reconnectTimer.hasExpired()) {
m_reconnectTimer.stop();
CLog::logDebug("Trying to reconnect to the APRS server");
m_connected = connect();
if (!m_connected) {
CLog::logInfo("Reconnect attempt to the APRS server has failed");
startReconnectionTimer();
}
else {
m_keepAliveTimer.start();
}
}
}
@ -139,49 +147,43 @@ void* CAPRSWriterThread::Entry()
m_tries = 0U;
if(!m_queue.empty()){
char* p = m_queue.getData();
auto frameStr = m_queue.getData();
std::string text(p);
CLog::logInfo("APRS ==> %s", text.c_str());
CLog::logInfo("APRS ==> %s", frameStr.c_str());
::strcat(p, "\r\n");
bool ret = m_socket.write((unsigned char*)p, ::strlen(p));
bool ret = m_socket.writeLine(frameStr);
if (!ret) {
m_connected = false;
m_socket.close();
CLog::logInfo("Connection to the APRS thread has failed");
CLog::logInfo("Error when writing to the APRS server");
startReconnectionTimer();
}
delete[] p;
}
{
std::string line;
int length = m_socket.readLine(line, APRS_TIMEOUT);
int length = m_socket.readLine(line, APRS_READ_TIMEOUT);
/*if (length == 0)
CLog::logWarning(("No response from the APRS server after %u seconds", APRS_TIMEOUT);*/
if (length < 0) {
if (length < 0 || m_keepAliveTimer.hasExpired()) {
m_connected = false;
m_socket.close();
CLog::logError("Error when reading from the APRS server");
startReconnectionTimer();
}
if(line.length() > 0 && line[0] != '#')
CLog::logDebug("Received APRS Frame : %s", line.c_str());
if(length > 0 && line[0] != '#'//check if we have something and if that something is an APRS frame
&& m_APRSReadCallback.size() > 0U)//do we have someone wanting an APRS Frame?
{
for(auto cb : m_APRSReadCallback) {
cb->readAprsFrame(line);
else if(length > 0 && line[0] == '#') {
m_keepAliveTimer.start();
}
else if(line.length() > 0 && line[0] != '#') {
m_keepAliveTimer.start();
CLog::logDebug("APRS <== %s", line.c_str());
CAPRSFrame readFrame;
if(CAPRSParser::parseFrame(line, readFrame)) {
for(auto cb : m_APRSReadCallbacks) {
CAPRSFrame f(readFrame);
cb->readAPRSFrame(f);
}
}
}
}
}
}
@ -189,8 +191,8 @@ void* CAPRSWriterThread::Entry()
m_socket.close();
while (!m_queue.empty()) {
char* p = m_queue.getData();
delete[] p;
auto s = m_queue.getData();
s.clear();
}
}
catch (std::exception& e) {
@ -206,46 +208,48 @@ void* CAPRSWriterThread::Entry()
return NULL;
}
void CAPRSWriterThread::addReadAPRSCallback(CReadAPRSFrameCallback * cb)
void CAPRSHandlerThread::addReadAPRSCallback(IReadAPRSFrameCallback * cb)
{
assert(cb != nullptr);
m_APRSReadCallback.push_back(cb);
m_APRSReadCallbacks.push_back(cb);
}
void CAPRSWriterThread::write(const char* data)
void CAPRSHandlerThread::write(CAPRSFrame& frame)
{
assert(data != NULL);
if (!m_connected)
return;
unsigned int len = ::strlen(data);
std::string frameString;
if(CAPRSFormater::frameToString(frameString, frame)) {
boost::trim_if(frameString, [] (char c) { return c == '\r' || c == '\n'; }); // trim all CRLF, we will add our own, just to make sure we get rid of any garbage that might come from slow data
CLog::logTrace("Queued APRS Frame : %s", frameString.c_str());
frameString.append("\r\n");
char* p = new char[len + 5U];
::strcpy(p, data);
m_queue.addData(p);
m_queue.addData(frameString);
}
}
bool CAPRSWriterThread::isConnected() const
bool CAPRSHandlerThread::isConnected() const
{
return m_connected;
}
void CAPRSWriterThread::stop()
void CAPRSHandlerThread::stop()
{
m_exit = true;
Wait();
}
void CAPRSWriterThread::clock(unsigned int ms)
void CAPRSHandlerThread::clock(unsigned int ms)
{
m_reconnectTimer.clock(ms);
m_keepAliveTimer.clock(ms);
}
bool CAPRSWriterThread::connect()
bool CAPRSHandlerThread::connect()
{
m_socket.close();
bool ret = m_socket.open();
if (!ret)
return false;
@ -282,7 +286,7 @@ bool CAPRSWriterThread::connect()
return false;
}
if (length < 0) {
CLog::logInfo("Error when reading from the APRS server");
CLog::logInfo("Error when reading from the APRS server (connect)");
m_socket.close();
return false;
}
@ -294,13 +298,15 @@ bool CAPRSWriterThread::connect()
return true;
}
void CAPRSWriterThread::startReconnectionTimer()
void CAPRSHandlerThread::startReconnectionTimer()
{
// Clamp at a ten minutes reconnect time
m_tries++;
if (m_tries > 10U)
m_tries = 10U;
CLog::logDebug("Next APRS reconnection try in %u minute", m_tries);
m_reconnectTimer.setTimeout(m_tries * 60U);
m_reconnectTimer.start();
}

@ -26,19 +26,20 @@
#include "Timer.h"
#include "Thread.h"
#include "ReadAPRSFrameCallback.h"
#include "APRSFrame.h"
class CAPRSWriterThread : public CThread {
class CAPRSHandlerThread : public CThread {
public:
CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port);
CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter, const std::string& clientName);
virtual ~CAPRSWriterThread();
CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port);
CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter);
virtual ~CAPRSHandlerThread();
bool start();
bool isConnected() const;
void write(const char* data);
void write(CAPRSFrame& data);
void* Entry();
@ -46,19 +47,20 @@ public:
void clock(unsigned int ms);
void addReadAPRSCallback(CReadAPRSFrameCallback* cb);
void addReadAPRSCallback(IReadAPRSFrameCallback* cb);
private:
std::string m_username;
std::string m_password;
std::string m_ssid;
CTCPReaderWriterClient m_socket;
CRingBuffer<char*> m_queue;
CRingBuffer<std::string> m_queue;
bool m_exit;
bool m_connected;
CTimer m_reconnectTimer;
CTimer m_keepAliveTimer;
unsigned int m_tries;
std::vector<CReadAPRSFrameCallback *> m_APRSReadCallback;
std::vector<IReadAPRSFrameCallback *> m_APRSReadCallbacks;
std::string m_filter;
std::string m_clientName;

@ -31,7 +31,7 @@ CAPRSIdFrameProvider::~CAPRSIdFrameProvider()
}
bool CAPRSIdFrameProvider::buildAPRSFrames(const std::string& gateway, const CAPRSEntry * entry, std::vector<std::string> & frames)
bool CAPRSIdFrameProvider::buildAPRSFrames(const std::string& gateway, const CAPRSEntry * entry, std::vector<CAPRSFrame *> & frames)
{
assert(entry != nullptr);

@ -22,6 +22,7 @@
#include "Timer.h"
#include "APRSEntry.h"
#include "APRSFrame.h"
class CAPRSIdFrameProvider
{
@ -29,14 +30,14 @@ public:
CAPRSIdFrameProvider(unsigned int timeOut);
virtual ~CAPRSIdFrameProvider();
bool buildAPRSFrames(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<std::string>& frames);
bool buildAPRSFrames(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<CAPRSFrame *>& frames);
void clock(unsigned int ms) { m_timer.clock(ms); }
bool wantsToSend();
virtual void start() { };
virtual void close() { };
protected:
virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<std::string>& frames) = 0;
virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector<CAPRSFrame *>& frames) = 0;
void setTimeout(unsigned int timeout)
{

@ -20,7 +20,7 @@
#include "Log.h"
bool CAPRSParser::parseFrame(const std::string& frameStr, CAPRSFrame& frame)
{
{
frame.clear();
bool ret = false;
@ -44,12 +44,9 @@ bool CAPRSParser::parseFrame(const std::string& frameStr, CAPRSFrame& frame)
frame.getBody().assign(body);
setFrameType(frame);
if(frame.getType() == APFT_UNKNOWN) {
CLog::logInfo("Invalid or unsupported APRS frame : %s", frameStr);
}
else {
ret = true;
ret = parseInt(frame);
if(!ret) {
frame.clear();
}
}
}
@ -58,26 +55,106 @@ bool CAPRSParser::parseFrame(const std::string& frameStr, CAPRSFrame& frame)
return ret;
}
void CAPRSParser::setFrameType(CAPRSFrame& frame)
bool CAPRSParser::parseInt(CAPRSFrame& frame)
{
APRS_FRAME_TYPE type = APFT_UNKNOWN;
std::string body(frame.getBody());
unsigned char typeChar = frame.getBody()[0];
std::string body(frame.getBody().substr(1));//strip the type char for processing purposes
if(body.empty())
return false;
if(!body.empty()) {
switch (body[0])
{
case ':':
if(body[10] == ':' && std::all_of(body.begin() + 1, body.begin() + 10,
[](char c){ return c == ' ' || c == '-' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); }))
type = APFT_MESSAGE;
break;
default:
break;
}
}
switch (typeChar)
{
case '!':
if(body[0] == '!') {
// This is ultimeter 200 weather station
return false;
}
[[fallthrough]];
case '=':
case '/':
case '@':
{
if(body.length() < 10) return false;//enough chars to have a chance to parse it ?
/* Normal or compressed location packet, with or without
* timestamp, with or without messaging capability
*
* ! and / have messaging, / and @ have a prepended timestamp
*/
type = APFT_POSITION;
if(typeChar == '/' || typeChar== '@')//With a prepended timestamp, jump over it.
body = body.substr(7U);
auto posChar = body[0];
if(valid_sym_table_compressed(posChar)//Compressed format
&& body.length() >= 13){//we need at least 13 char
//icom unsupported, ignore for now
return false;//parse_aprs_compressed(pb, body, body_end);
}
else if(posChar >= '0' && posChar <= '9' //Normal uncompressed format
&& body.length() >=19){//we need at least 19 chars for it to be valid
// if(ensureIsIcomCompatible(packet))
// return Parse(packet.Raw(), packet);
}
}
break;
case '$' :
if(body.length() > 10) {
type = APFT_NMEA;
}
break;
case ':':
// we have either message or telemetry labels or telemetry EQNS
if(body[9] == ':'
&& std::all_of(body.begin(), body.begin() + 9, [](char c){ return c == ' ' || c == '-' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); })) {
type = APFT_MESSAGE;
//If reciepient is same as source and we donot have a sequence number at the end of message, Then it is telemetry
if(body.find(frame.getSource()) == 0U) {
auto eqnsPos = body.find("EQNS.");
auto parmPos = body.find("PARM.");
auto seqNumPos = body.find_last_of('{');
if((eqnsPos == 10U || parmPos == 10U) && seqNumPos == std::string::npos) {
type = APFT_TELEMETRY;
}
}
}
break;
case '>':
type = APFT_STATUS;
break;
case '#': /* Peet Bros U-II Weather Station */
case '*': /* Peet Bros U-I Weather Station */
case '_': /* Weather report without position */
type = APFT_WX;
break;
case '{':
type = APFT_UNKNOWN; //
break;
case 'T':
if(body[0] == '#') {
type = APFT_TELEMETRY;
}
break;
default:
type = APFT_UNKNOWN;
break;
}
frame.getType() = type;
if(type == APFT_UNKNOWN)
frame.clear();
return type != APFT_UNKNOWN;
}
bool CAPRSParser::valid_sym_table_compressed(unsigned char c)
{
return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A)
|| (c >= 0x61 && c <= 0x6A)); /* [\/\\A-Za-j] */
}
bool CAPRSParser::valid_sym_table_uncompressed(unsigned char c)
{
return (c == '/' || c == '\\' || (c >= 0x41 && c <= 0x5A)
|| (c >= 0x30 && c <= 0x39)); /* [\/\\A-Z0-9] */
}

@ -29,5 +29,7 @@ public:
static bool parseFrame(const std::string& frameStr, CAPRSFrame& frame);
private:
static void setFrameType(CAPRSFrame& frame);
static bool parseInt(CAPRSFrame& frame);
static bool valid_sym_table_compressed(unsigned char c);
static bool valid_sym_table_uncompressed(unsigned char c);
};

@ -0,0 +1,120 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <boost/algorithm/string.hpp>
#include "APRSUnit.h"
#include "APRSFormater.h"
#include "StringUtils.h"
#include "APRStoDPRS.h"
CAPRSUnit::CAPRSUnit(IRepeaterCallback * repeaterHandler) :
m_frameBuffer(20U),
m_status(APS_IDLE),
m_repeaterHandler(repeaterHandler),
m_headerData(nullptr),
m_slowData(nullptr),
m_out(0U),
m_seq(0U),
m_totalNeeded(0U),
m_timer(1000U, 2U),
m_start()
{
m_timer.start();
}
void CAPRSUnit::writeFrame(CAPRSFrame& frame)
{
auto frameCopy = new CAPRSFrame(frame);
frameCopy->getPath().clear();//path is of no use for us, just clear it
m_frameBuffer.push_back(frameCopy);
m_timer.start();
}
void CAPRSUnit::clock(unsigned int ms)
{
m_timer.clock(ms);
if(m_status == APS_IDLE && !m_frameBuffer.empty() && m_timer.hasExpired()) {
auto frame = m_frameBuffer.front();
m_frameBuffer.pop_front();
m_headerData = new CHeaderData();
std::string dprs, text;
if(!CAPRSToDPRS::aprsToDPRS(dprs, text, *m_headerData, *frame)) {
delete m_headerData;
m_headerData = nullptr;
return;
}
m_slowData = new CSlowDataEncoder();
m_slowData->setHeaderData(*m_headerData);
m_slowData->setGPSData(dprs);
m_slowData->setTextData(text);
m_totalNeeded = (m_slowData->getInterleavedDataLength() / (DATA_FRAME_LENGTH_BYTES)) * 2U;
m_repeaterHandler->process(*m_headerData, DIR_INCOMING, AS_INFO);
m_out = 0U;
m_seq = 0U;
m_start = std::chrono::high_resolution_clock::now();
m_status = APS_TRANSMIT;
return;
}
if(m_status == APS_TRANSMIT) {
unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_start).count();
needed /= DSTAR_FRAME_TIME_MS;
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
while (m_out < needed && m_out < m_totalNeeded) {
CAMBEData data;
data.setId(m_headerData->getId());
data.setSeq(m_seq);
if(m_out == m_totalNeeded - 1U)
data.setEnd(true);
::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
// Insert sync bytes when the sequence number is zero, slow data otherwise
if (m_seq == 0U) {
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
} else {
m_slowData->getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES);
m_out++;
}
data.setData(buffer, DV_FRAME_LENGTH_BYTES);
m_repeaterHandler->process(data, DIR_INCOMING, AS_INFO);
m_seq++;
if (m_seq == 21U) m_seq = 0U;
}
if(m_out >= m_totalNeeded) {
m_status = APS_IDLE;
delete m_headerData;
delete m_slowData;
}
}
}

@ -0,0 +1,57 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
#pragma once
#include <string>
#include <boost/circular_buffer.hpp>
#include <chrono>
#include "APRSFrame.h"
#include "RepeaterCallback.h"
#include "Timer.h"
#include "SlowDataEncoder.h"
enum APRSUNIT_STATUS {
APS_IDLE,
APS_WAIT,
APS_TRANSMIT
};
class CAPRSUnit
{
public:
CAPRSUnit(IRepeaterCallback * repeaterHandler);
void writeFrame(CAPRSFrame& aprsFrame);
void clock(unsigned ms);
private:
// CRingBuffer<CAPRSFrame *> m_frameBuffer;
boost::circular_buffer<CAPRSFrame *> m_frameBuffer;
APRSUNIT_STATUS m_status;
IRepeaterCallback * m_repeaterHandler;
CHeaderData * m_headerData;
CSlowDataEncoder * m_slowData;
unsigned int m_out;
unsigned int m_seq;
unsigned int m_totalNeeded;
CTimer m_timer;
std::chrono::high_resolution_clock::time_point m_start;
};

@ -31,4 +31,27 @@ void CAPRSUtils::dstarCallsignToAPRS(std::string& dstarCallsign)
}
boost::replace_all(dstarCallsign, " ", "-");//replace remaining blank with a -
}
}
}
unsigned int CAPRSUtils::calcGPSAIcomCRC(const std::string& gpsa)
{
unsigned int icomcrc = 0xFFFFU;
auto length = gpsa.length();
for (unsigned int j = 10U; j < length; j++) {
unsigned char ch = (unsigned char)gpsa[j];
for (unsigned int i = 0U; i < 8U; i++) {
bool xorflag = (((icomcrc ^ ch) & 0x01U) == 0x01U);
icomcrc >>= 1;
if (xorflag)
icomcrc ^= 0x8408U;
ch >>= 1;
}
}
return ~icomcrc & 0xFFFFU;
}

@ -24,4 +24,5 @@ class CAPRSUtils
{
public:
static void dstarCallsignToAPRS(std::string& dstarCallsign);
static unsigned int calcGPSAIcomCRC(const std::string& gpsa);
};

@ -0,0 +1,73 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <boost/algorithm/string.hpp>
#include "APRStoDPRS.h"
#include "Log.h"
#include "RSMS1AMessageBuilder.h"
bool CAPRSToDPRS::aprsToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame)
{
dprs.clear();
text.clear();
switch (frame.getType())
{
case APFT_MESSAGE :
return messageToDPRS(dprs, text, header, frame);
default:
break;
}
return false;
}
bool CAPRSToDPRS::messageToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame)
{
auto frameBody = frame.getBody();
if(frameBody.length() < 11 || frameBody[0] != ':' || frameBody[10] != ':') {
CLog::logDebug("Invalid APRS message body : %s", frameBody.c_str());
return false;
}
// extract recipient
auto recipient = boost::trim_copy(frameBody.substr(1, 9));
if(recipient.empty()) {
CLog::logDebug("APRS message has no recipient");
return false;
}
auto dashPos = recipient.find_first_of('-');
if(dashPos != std::string::npos)
recipient = recipient.substr(0, dashPos);
//extract message body
auto messageBody = boost::trim_copy(frameBody.substr(11));
header.setId(header.createId());
header.setMyCall1(frame.getSource());
header.setMyCall2("MSG");
header.setYourCall(recipient);
CRSMS1AMessageBuilder::buildMessage(dprs, frame.getSource(), recipient, messageBody);
text = messageBody;
return true;
}

@ -16,18 +16,19 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include "APRSReader.h"
#include <string>
#include <utility>
#include "HeaderData.h"
#include "APRSFrame.h"
#include "APRSParser.h"
CAPRSReader::CAPRSReader()
class CAPRSToDPRS
{
public:
static bool aprsToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame);
}
bool CAPRSReader::readAprsFrame(const std::string& aprsFrame)
{
auto bla = aprsFrame;
return false;
}
private:
static bool messageToDPRS(std::string& dprs, std::string& text, CHeaderData& header, CAPRSFrame& frame);
};

@ -133,9 +133,9 @@ bool CDStarGatewayApp::createThread()
// Setup APRS
TAPRS aprsConfig;
config.getAPRS(aprsConfig);
CAPRSWriter * aprsWriter = NULL;
CAPRSHandler * aprsWriter = NULL;
if(aprsConfig.enabled && !aprsConfig.password.empty()) {
aprsWriter = new CAPRSWriter(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address);
aprsWriter = new CAPRSHandler(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address);
if(aprsWriter->open()) {
#ifdef USE_GPSD
CAPRSIdFrameProvider * idFrameProvider = aprsConfig.m_positionSource == POSSRC_GPSD ? (CAPRSIdFrameProvider *)new CAPRSGPSDIdFrameProvider(gpsdConfig.m_address, gpsdConfig.m_port)

@ -21,17 +21,17 @@
#include "DStarGatewayStatusData.h"
CIRCDDBGatewayStatusData::CIRCDDBGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus) :
CDStarGatewayStatusData::CDStarGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus) :
m_ircDDBStatus(ircDDBStatus),
m_dprsStatus(dprsStatus)
{
}
CIRCDDBGatewayStatusData::~CIRCDDBGatewayStatusData()
CDStarGatewayStatusData::~CDStarGatewayStatusData()
{
}
void CIRCDDBGatewayStatusData::setRepeater(unsigned int n, const std::string& callsign, LINK_STATUS linkStatus, const std::string& linkCallsign, const std::string& incoming)
void CDStarGatewayStatusData::setRepeater(unsigned int n, const std::string& callsign, LINK_STATUS linkStatus, const std::string& linkCallsign, const std::string& incoming)
{
assert(n < 4U);
@ -41,50 +41,50 @@ void CIRCDDBGatewayStatusData::setRepeater(unsigned int n, const std::string& ca
m_incoming[n] = incoming;
}
void CIRCDDBGatewayStatusData::setDongles(const std::string& dongles)
void CDStarGatewayStatusData::setDongles(const std::string& dongles)
{
m_dongles = dongles;
}
IRCDDB_STATUS CIRCDDBGatewayStatusData::getIrcDDBStatus() const
IRCDDB_STATUS CDStarGatewayStatusData::getIrcDDBStatus() const
{
return m_ircDDBStatus;
}
bool CIRCDDBGatewayStatusData::getDPRSStatus() const
bool CDStarGatewayStatusData::getDPRSStatus() const
{
return m_dprsStatus;
}
std::string CIRCDDBGatewayStatusData::getCallsign(unsigned int n) const
std::string CDStarGatewayStatusData::getCallsign(unsigned int n) const
{
assert(n < 4U);
return m_callsign[n];
}
LINK_STATUS CIRCDDBGatewayStatusData::getLinkStatus(unsigned int n) const
LINK_STATUS CDStarGatewayStatusData::getLinkStatus(unsigned int n) const
{
assert(n < 4U);
return m_linkStatus[n];
}
std::string CIRCDDBGatewayStatusData::getLinkCallsign(unsigned int n) const
std::string CDStarGatewayStatusData::getLinkCallsign(unsigned int n) const
{
assert(n < 4U);
return m_linkCallsign[n];
}
std::string CIRCDDBGatewayStatusData::getIncoming(unsigned int n) const
std::string CDStarGatewayStatusData::getIncoming(unsigned int n) const
{
assert(n < 4U);
return m_incoming[n];
}
std::string CIRCDDBGatewayStatusData::getDongles() const
std::string CDStarGatewayStatusData::getDongles() const
{
return m_dongles;
}

@ -22,10 +22,10 @@
#include "Defs.h"
class CIRCDDBGatewayStatusData {
class CDStarGatewayStatusData {
public:
CIRCDDBGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus);
~CIRCDDBGatewayStatusData();
CDStarGatewayStatusData(IRCDDB_STATUS ircDDBStatus, bool dprsStatus);
~CDStarGatewayStatusData();
void setRepeater(unsigned int n, const std::string& callsign, LINK_STATUS linkStatus, const std::string& linkCallsign, const std::string& incoming);

@ -424,10 +424,10 @@ void* CDStarGatewayThread::Entry()
}
catch (std::exception& e) {
std::string message(e.what());
CLog::logError("Exception raised in the main thread - \"%s\"", message.c_str());
CLog::logFatal("Exception raised in the main thread - \"%s\"", message.c_str());
}
catch (...) {
CLog::logError("Unknown exception raised in the main thread");
CLog::logFatal("Unknown exception raised in the main thread");
}
CLog::logInfo("Stopping the ircDDB Gateway thread");
@ -638,7 +638,7 @@ void CDStarGatewayThread::setLog(bool enabled)
m_logEnabled = enabled;
}
void CDStarGatewayThread::setAPRSWriter(CAPRSWriter* writer)
void CDStarGatewayThread::setAPRSWriter(CAPRSHandler* writer)
{
m_aprsWriter = writer;
}
@ -1203,13 +1203,13 @@ void CDStarGatewayThread::writeStatus()
file.close();
}
CIRCDDBGatewayStatusData* CDStarGatewayThread::getStatus() const
CDStarGatewayStatusData* CDStarGatewayThread::getStatus() const
{
bool aprsStatus = false;
if (m_aprsWriter != NULL)
aprsStatus = m_aprsWriter->isConnected();
CIRCDDBGatewayStatusData* status = new CIRCDDBGatewayStatusData(m_lastStatus, aprsStatus);
CDStarGatewayStatusData* status = new CDStarGatewayStatusData(m_lastStatus, aprsStatus);
for (unsigned int i = 0U; i < 4U; i++) {
std::string callsign, linkCallsign;

@ -32,7 +32,7 @@
#include "RemoteHandler.h"
#include "CacheManager.h"
#include "CallsignList.h"
#include "APRSWriter.h"
#include "APRSHandler.h"
#include "IRCDDB.h"
#include "Timer.h"
#include "Defs.h"
@ -72,7 +72,7 @@ virtual void addRepeater(const std::string& callsign, const std::string& band, c
virtual void setCCS(bool enabled, const std::string& host);
#endif
virtual void setLog(bool enabled);
virtual void setAPRSWriter(CAPRSWriter* writer);
virtual void setAPRSWriter(CAPRSHandler* writer);
virtual void setInfoEnabled(bool enabled);
virtual void setEchoEnabled(bool enabled);
virtual void setDTMFEnabled(bool enabled);
@ -83,7 +83,7 @@ virtual void addRepeater(const std::string& callsign, const std::string& band, c
virtual void setBlackList(CCallsignList* list);
virtual void setRestrictList(CCallsignList* list);
virtual CIRCDDBGatewayStatusData* getStatus() const;
virtual CDStarGatewayStatusData* getStatus() const;
virtual void kill();
@ -109,7 +109,7 @@ private:
#if defined(ENABLE_NAT_TRAVERSAL)
CNatTraversalHandler* m_natTraversal;
#endif
CAPRSWriter* m_aprsWriter;
CAPRSHandler* m_aprsWriter;
CIRCDDB* m_irc;
CCacheManager m_cache;
TEXT_LANG m_language;

@ -25,6 +25,7 @@
#include "GPSACollector.h"
#include "StringUtils.h"
#include "Log.h"
#include "APRSUtils.h"
const unsigned int APRS_CSUM_LENGTH = 4U;
@ -44,46 +45,23 @@ bool CGPSACollector::isValidGPSA(const std::string& gpsa)
if(gpsa.length() < 10U || !boost::starts_with(gpsa, "$$CRC"))
return false;
auto csum = calcCRC(gpsa);
auto csum = CAPRSUtils::calcGPSAIcomCRC(gpsa);
auto csumStr = CStringUtils::string_format("%04X", csum);
auto expectedCsum = gpsa.substr(5U, APRS_CSUM_LENGTH);
bool res = ::strcasecmp(csumStr.c_str(), expectedCsum.c_str()) == 0;
return res;
}
unsigned int CGPSACollector::calcCRC(const std::string& gpsa)
{
unsigned int icomcrc = 0xFFFFU;
auto length = gpsa.length();
for (unsigned int j = 10U; j < length; j++) {
unsigned char ch = (unsigned char)gpsa[j];
for (unsigned int i = 0U; i < 8U; i++) {
bool xorflag = (((icomcrc ^ ch) & 0x01U) == 0x01U);
icomcrc >>= 1;
if (xorflag)
icomcrc ^= 0x8408U;
ch >>= 1;
}
}
return ~icomcrc & 0xFFFFU;
}
unsigned int CGPSACollector::getDataInt(unsigned char * data, unsigned int length)
{
if(data == nullptr || length == 0U || getSentence().empty())
if(data == nullptr || length == 0U)
return 0U;
auto aprsFrame = getSentence();
if(aprsFrame.length() < 11U)
std::string aprsFrame;
if(!getDataInt(aprsFrame))
return 0U;
aprsFrame = aprsFrame.substr(10).append("\r\n");
auto aprsFrameLen = aprsFrame.length();
if(length < aprsFrameLen) {
@ -96,4 +74,19 @@ unsigned int CGPSACollector::getDataInt(unsigned char * data, unsigned int lengt
}
return aprsFrameLen;
}
bool CGPSACollector::getDataInt(std::string& data)
{
if(getSentence().empty())
return false;
data.clear();
auto aprsFrame = getSentence();
if(aprsFrame.length() < 11U)//do we have enough data beyond CRC?
return false;
data = aprsFrame.substr(10);
return true;
}

@ -30,6 +30,7 @@ public:
protected:
unsigned int getDataInt(unsigned char * data, unsigned int length);
bool getDataInt(std::string& data);
bool isValidSentence(const std::string& sentence);
private:

@ -76,18 +76,12 @@ unsigned char CNMEASentenceCollector::calcXOR(const std::string& nmea)
unsigned int CNMEASentenceCollector::getDataInt(unsigned char * data, unsigned int length)
{
if(data == nullptr || length == 0U || getMyCall().empty() || getSentence().empty())
if(data == nullptr || length == 0U)
return 0U;
auto nmea = getSentence();
fixUpNMEATimeStamp(nmea);
std::string fromCall = getMyCall();
CAPRSUtils::dstarCallsignToAPRS(fromCall);
std::string aprsFrame(fromCall);
aprsFrame.append("-5>GPS30,DSTAR*:")
.append(nmea)
.append("\r\n");
std::string aprsFrame;
if(!getDataInt(aprsFrame))
return 0U;
auto aprsFrameLen = aprsFrame.length();
if(length < aprsFrameLen) {
@ -102,6 +96,25 @@ unsigned int CNMEASentenceCollector::getDataInt(unsigned char * data, unsigned i
return aprsFrameLen;
}
bool CNMEASentenceCollector::getDataInt(std::string& data)
{
if(getMyCall().empty() || getSentence().empty())
return false;
data.clear();
auto nmea = getSentence();
fixUpNMEATimeStamp(nmea);
std::string fromCall = getMyCall();
CAPRSUtils::dstarCallsignToAPRS(fromCall);
std::string aprsFrame(fromCall);
aprsFrame.append("-5>GPS30,DSTAR*:")
.append(nmea);
data.assign(aprsFrame);
return true;
}
// When set on manual position Icom radios send 000000.00 as NMEA timestamps
// this is a dirty hack to correct this issue. Actually I am not sure about APRS
// software being peeky about this except APRS.fi

@ -30,6 +30,7 @@ public:
protected:
unsigned int getDataInt(unsigned char * data, unsigned int length);
bool getDataInt(std::string& data);
bool isValidSentence(const std::string& sentence);
private:

@ -1,8 +1,9 @@
- [1. Introduction](#1-introduction)
- [2. Current State](#2-current-state)
- [2.1. Code sanity](#21-code-sanity)
- [2.2. Code Credit](#22-code-credit)
- [2.3. Features](#23-features)
- [2.2. Code Credits](#22-code-credits)
- [2.3. Thanks](#23-thanks)
- [2.4. Features](#24-features)
- [3. Building and installing](#3-building-and-installing)
- [3.1. Initial setup](#31-initial-setup)
- [3.2. Get latest stable version (recommended)](#32-get-latest-stable-version-recommended)
@ -34,11 +35,14 @@ The current code is working, yet ugly IMHO as it is a mix of C and C++ of variou
The code has also been amended to no longer rely on compiler defines for paths like log or data. These can be set in configuration file.
Quite a few classes are more or less copy/paste from each other some sanitization by using base classes or template classes would greatly improve code maintainibility. Maybe one day ;)
## 2.2. Code Credit
## 2.2. Code Credits
- Jonathan Naylor G4KLX (The original author of [ircddbGateway](https://github.com/g4klx/ircDDBGateway))
- Thomas A. Early N7TAE (Code taken from his [smart-group](https://github.com/n7tae/smart-group-server) software)
- Geoffrey Merck F4FXL / KC3FRA [That's me !](https://github.com/F4FXL/)
## 2.3. Features
## 2.3. Thanks
- Cyrille F1MHV / DF1CHB for the testing
- Jonathan Naylor G4KLX for all the work ahead
## 2.4. Features
All the features found in ircddbGateway are supposed to be working. I have mixed feelings about putting these back in or not.
Features that where left out :
@ -117,6 +121,7 @@ the testing framwework used is Google Test.
# 5. Version History
## 5.1. Version 0.5
- [Improvement] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9))
- [Bugfix] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14))
- [Bugfix] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13))
- [Bugfix] Trying to connect to ghost ircDDB when no ircDDB is configured

@ -0,0 +1,157 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <boost/algorithm/string.hpp>
#include "RSMS1AMessageBuilder.h"
#include "StringUtils.h"
std::vector<signed char> CRSMS1AMessageBuilder::m_charsToEscape = {-17, 0, 17, 19, -2, -25, 26, -3, -1, 36, 13, 44};
void CRSMS1AMessageBuilder::buildMessage(std::string& message, const std::string& sender, const std::string& recipient, const std::string body)
{
auto bodyCrc = calculateBodyCRC(body);
std::string bodyTmp;
escapeBody(bodyTmp, body + (char)bodyCrc);
std::string header = CStringUtils::string_format("%s,%s,0011", sender.c_str(), recipient.c_str());
signed char c1, c2;
calcMsgIcomCRC(header, c1, c2);
header.push_back(c1);
header.push_back(c2);
message = "$$Msg," + header + bodyTmp + '\r';
}
signed char CRSMS1AMessageBuilder::calculateBodyCRC(const std::string& body)
{
if(body.length() == 1)
return body[0];
signed int num = 0;
for(signed char c : body) {
num += c;
}
signed int res = (num & 255);
if(res >= 128)
res -= 128;
return (signed char)res;
}
void CRSMS1AMessageBuilder::escapeBody(std::string& output, const std::string& body)
{
output.clear();
for(char c : body) {
if(std::find(m_charsToEscape.begin(), m_charsToEscape.end(), c) != m_charsToEscape.end()) {
output.push_back('o');
}
output.push_back(c);
}
}
void CRSMS1AMessageBuilder::calcMsgIcomCRC(const std::string& msg, signed char& c1, signed char& c2)
{
int num = 0;
for(unsigned int i = 0U; i < msg.length(); i++) {
num += msg[i];
}
c1 = doWhatever((signed char)((num >> 4) & 15));
c2 = doWhatever((signed char)(num & 15));
}
signed char CRSMS1AMessageBuilder::doWhatever(signed char b2) {
int i;
int i2 = b2 & 255;
if (i2 >= 0 && i2 <= 9) {
i = b2 + 48;
} else if (10 > i2 || i2 > 15) {
return 0;
} else {
i = b2 + 55;
}
return (signed char) i;
}
RSMS1A_PARSE_STATUS CRSMS1AMessageBuilder::parseMessage(std::string& sender, std::string& recipient, std::string& body, const std::string& message)
{
sender.clear();
recipient.clear();
body.clear();
if(!boost::starts_with(message, "$$Msg,"))
return RSMS_FAIL;
auto firstCommaPos = message.find_first_of(',');
if(firstCommaPos == std::string::npos || (firstCommaPos + 1) >= message.length())
return RSMS_FAIL;
auto secondCommaPos = message.find_first_of(',', firstCommaPos + 1);
if(secondCommaPos == std::string::npos || (secondCommaPos + 1) >= message.length())
return RSMS_FAIL;
auto thirdCommaPos = message.find_first_of(',', secondCommaPos + 1);
if(thirdCommaPos == std::string::npos || (thirdCommaPos + 1 + 6) >= message.length())
return RSMS_FAIL;
sender.assign(message.substr(firstCommaPos + 1, secondCommaPos - firstCommaPos - 1));
recipient.assign(message.substr(secondCommaPos + 1, thirdCommaPos - secondCommaPos - 1));
body.assign(message.substr(thirdCommaPos + 1 + 6));
unescapeBody(body, std::string(body));
if(body.length() >= 2U) body.resize(body.length() - 2U);
// build a message out of what we received in order to check if all checksums matches
// use only header of message to match checksum
std::string messagecheck;
buildMessage(messagecheck, sender, recipient, " ");
messagecheck.resize(messagecheck.length() - 3U);// get rid of body, body check byte and trailing \r
if(messagecheck != message.substr(0, messagecheck.length())) {
sender.clear();
recipient.clear();
body.clear();
return RSMS_FAIL; // we do not allow any errors in message header
}
//rebuild complete messsage with body
buildMessage(messagecheck, sender, recipient, body);
if(messagecheck != message)
return RSMS_ERRONEOUS_MSG; // we allow erros to occur in the message body
return RSMS_OK;
}
void CRSMS1AMessageBuilder::unescapeBody(std::string& output, const std::string& body)
{
output.clear();
if(body.empty())
return;
for(unsigned int i = 0U; i < body.length(); i++) {
auto c = body[i];
auto next = body[i + 1U];
if(c == 'o' && std::find(m_charsToEscape.begin(), m_charsToEscape.end(), next) != m_charsToEscape.end()) {
output.push_back(next);
i++;
continue;
}
output.push_back(c);
}
}

@ -0,0 +1,45 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
#pragma once
#include <string>
#include <vector>
enum RSMS1A_PARSE_STATUS {
RSMS_FAIL,
RSMS_ERRONEOUS_MSG,
RSMS_OK
};
class CRSMS1AMessageBuilder
{
public:
static void buildMessage(std::string& message, const std::string& sender, const std::string& recipient, const std::string body);
static RSMS1A_PARSE_STATUS parseMessage(std::string& sender, std::string& recipient, std::string& body, const std::string& message);
private:
static void calcMsgIcomCRC(const std::string& msg, signed char& c1, signed char& c2);
static void escapeBody(std::string& output, const std::string& body);
static void unescapeBody(std::string& output, const std::string& body);
static void escapeBytes(std::vector<char> output, const std::vector<char> input);
static signed char calculateBodyCRC(const std::string& body);
static signed char doWhatever(signed char b2);
static std::vector<signed char> m_charsToEscape;
};

@ -30,6 +30,7 @@
#include "Log.h"
#include "Utils.h"
#include "APRSUtils.h"
#include "RSMS1AMessageBuilder.h"
const unsigned int APRS_CSUM_LENGTH = 4U;
@ -47,103 +48,24 @@ bool CRSMS1AMessageCollector::isValidSentence(const std::string& sentence)
bool CRSMS1AMessageCollector::isValidMsg(const std::string& msg)
{
if(msg.empty() || !boost::starts_with(msg, "$$Msg"))
return false;
// CUtils::dump("RS-MS1A:", (unsigned char *)msg.c_str(), msg.length());
std::vector<std::string> splits;
boost::split(splits, msg, boost::is_any_of(","));
bool ret = splits.size() >= 4
&& !splits[1].empty()
&& !splits[2].empty();
return ret;
//TODO 2022-01-01 figure out what the heck it is about thic strange CRCs
// CUtils::dump("RS-MS1A:", (unsigned char *)gpsa.c_str(), gpsa.length() + 1U);
// CLog::logDebug("RS-MS1A: %s", gpsa.c_str());
// auto thirdCommaPos = CStringUtils::find_nth(gpsa, 0U, ',', 3);
// auto csum = calcCRC(gpsa, thirdCommaPos + 6 + 1, gpsa.length() - thirdCommaPos - 2U - 6U);
// auto csumStr = CStringUtils::string_format("%06X", csum);
// CLog::logDebug("RS-MS1A CRC: %s", csumStr.c_str());
// auto expectedCsum = gpsa.substr(5U, APRS_CSUM_LENGTH);
// bool res = ::strcasecmp(csumStr.c_str(), expectedCsum.c_str()) == 0;
// return res;
}
unsigned int CRSMS1AMessageCollector::calcCRC(const std::string& gpsa, unsigned int start, unsigned int length)
{
unsigned int icomcrc = 0xFFFFU;
auto end = start + length;
if(end > gpsa.length()) {
end = gpsa.length();
}
for (unsigned int j = start; j < end; j++) {
unsigned char ch = (unsigned char)gpsa[j];
for (unsigned int i = 0U; i < 8U; i++) {
bool xorflag = (((icomcrc ^ ch) & 0x01U) == 0x01U);
icomcrc >>= 1;
if (xorflag)
icomcrc ^= 0x8408U;
ch >>= 1;
}
}
return ~icomcrc & 0xFFFFU;
CLog::logDebug("RS-MS1A");
// checking validity involves parsing, so we do minumum checks here. Big chekc done in getDataInt
return !msg.empty() && boost::starts_with(msg, "$$Msg");
}
unsigned int CRSMS1AMessageCollector::getDataInt(unsigned char * data, unsigned int length)
{
if(data == nullptr || length == 0U || getSentence().empty())
if(data == nullptr || length == 0U)
return 0U;
auto sentence = getSentence();
std::vector<std::string> splits;
boost::split(splits, sentence, boost::is_any_of(","));
bool ret = splits.size() >= 4
&& !splits[1].empty()
&& !splits[2].empty();
if(!ret) {
std::string aprsFrame;
if(!getDataInt(aprsFrame))
return 0U;
}
auto sender = splits[1];
auto recipient = CUtils::ToUpper(splits[2]);
for(unsigned int i = 0;i < 3; i++) {
splits.erase(splits.begin());
}
auto message = boost::join(splits, ",");
if(message.length() > 6)
message = message.substr(6);
auto seqNum = rand() % 0xFFFFFU;
CAPRSUtils::dstarCallsignToAPRS(sender);
CAPRSUtils::dstarCallsignToAPRS(recipient);
recipient.resize(9, ' ');
message.resize(std::max<int>(0 , ((int)message.length()) - 2));
//unescape commas in message body
boost::replace_all(message, "o,", ",");
auto aprsFrame = CStringUtils::string_format("%s-5>APDPRS,DSTAR*::%s:%s{%05X\r\n", sender.c_str(), recipient.c_str(), message.c_str(), seqNum);
auto aprsFrameLen = aprsFrame.length();
if(length < aprsFrameLen) {
CLog::logDebug("Not enough space to copy GPS-A APRS frame");
CLog::logDebug("Not enough space to copy RSMS1A message frame");
return 0U;
}
@ -152,4 +74,29 @@ unsigned int CRSMS1AMessageCollector::getDataInt(unsigned char * data, unsigned
}
return aprsFrameLen;
}
bool CRSMS1AMessageCollector::getDataInt(std::string& data)
{
if(getSentence().empty())
return false;
std::string sender, recipient, body, sentence;
sentence = getSentence();
auto parseRes = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, sentence);
CUtils::dump(CStringUtils::string_format("RS-MS1A Message parsed with result %s", parseRes == RSMS_OK ? "OK" : (parseRes == RSMS_ERRONEOUS_MSG ? "Error in msg body" : "failed")).c_str(),
(unsigned char *)sentence.c_str(), sentence.length());
if(parseRes == RSMS_FAIL)
return false;
CAPRSUtils::dstarCallsignToAPRS(sender);
CAPRSUtils::dstarCallsignToAPRS(recipient);
recipient.resize(9U, ' ');
auto seqNum = rand() % 0xFFFFFU;
CStringUtils::string_format_in_place(data, "%s-5>APDPRS,DSTAR*::%s:%s{%05X", sender.c_str(), recipient.c_str(), body.c_str(), seqNum);
return true;
}

@ -30,6 +30,7 @@ public:
protected:
unsigned int getDataInt(unsigned char * data, unsigned int length);
bool getDataInt(std::string& data);
bool isValidSentence(const std::string& sentence);
private:

@ -19,10 +19,13 @@
#pragma once
#include <string>
#include <functional>
class CReadAPRSFrameCallback
#include "APRSFrame.h"
class IReadAPRSFrameCallback
{
public:
virtual ~CReadAPRSFrameCallback(){ }
virtual bool readAprsFrame(const std::string& aprsFrame) = 0;
virtual void readAPRSFrame(CAPRSFrame& aprsFrame) = 0;
};

@ -59,7 +59,7 @@ bool CRepeaterHandler::m_dtmfEnabled = true;
CHeaderLogger* CRepeaterHandler::m_headerLogger = NULL;
CAPRSWriter* CRepeaterHandler::m_aprsWriter = NULL;
CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL;
CCallsignList* CRepeaterHandler::m_restrictList = NULL;
@ -226,6 +226,7 @@ m_heardTimer(1000U, 0U, 100U) // 100ms
m_echo = new CEchoUnit(this, callsign);
m_infoAudio = new CAudioUnit(this, callsign);
m_version = new CVersionUnit(this, callsign);
m_aprsUnit = new CAPRSUnit(this);
#ifdef USE_DRATS
if (dratsEnabled) {
@ -366,7 +367,7 @@ void CRepeaterHandler::setHeaderLogger(CHeaderLogger* logger)
m_headerLogger = logger;
}
void CRepeaterHandler::setAPRSWriter(CAPRSWriter* writer)
void CRepeaterHandler::setAPRSWriter(CAPRSHandler* writer)
{
m_aprsWriter = writer;
}
@ -1416,6 +1417,9 @@ void CRepeaterHandler::clockInt(unsigned int ms)
m_echo->clock(ms);
m_version->clock(ms);
if(m_aprsUnit != nullptr)
m_aprsUnit->clock(ms);
m_linkReconnectTimer.clock(ms);
m_watchdogTimer.clock(ms);
m_queryTimer.clock(ms);
@ -2422,6 +2426,10 @@ void CRepeaterHandler::startupInt()
m_irc->rptrQTH(callsign, m_latitude, m_longitude, m_description1, m_description2, m_url);
}
if(m_aprsWriter != nullptr) {
m_aprsWriter->addReadAPRSCallback(this);
}
#ifdef USE_CCS
m_ccsHandler = new CCCSHandler(this, m_rptCallsign, m_index + 1U, m_latitude, m_longitude, m_frequency, m_offset, m_description1, m_description2, m_url, CCS_PORT + m_index);
#endif
@ -3069,6 +3077,13 @@ void CRepeaterHandler::triggerInfo()
}
}
void CRepeaterHandler::readAPRSFrame(CAPRSFrame& frame)
{
if(m_aprsUnit != nullptr) {
m_aprsUnit->writeFrame(frame);
}
}
#ifdef USE_CCS
bool CRepeaterHandler::isCCSCommand(const std::string& command) const
{

@ -48,7 +48,7 @@
#include "CCSHandler.h"
#endif
#include "StatusData.h"
#include "APRSWriter.h"
#include "APRSHandler.h"
#include "HeardData.h"
#include "AudioUnit.h"
#include "EchoUnit.h"
@ -58,11 +58,13 @@
#include "Timer.h"
#include "DTMF.h"
#include "Defs.h"
#include "ReadAPRSFrameCallback.h"
#include "APRSUnit.h"
#include <netinet/in.h>
class CRepeaterHandler : public IRepeaterCallback, public IReflectorCallback, public ICCSCallback {
class CRepeaterHandler : public IRepeaterCallback, public IReflectorCallback, public ICCSCallback, public IReadAPRSFrameCallback {
public:
static void initialise(unsigned int maxRepeaters);
@ -82,7 +84,7 @@ public:
static void setDPlusEnabled(bool enabled);
static void setDCSEnabled(bool enabled);
static void setHeaderLogger(CHeaderLogger* logger);
static void setAPRSWriter(CAPRSWriter* writer);
static void setAPRSWriter(CAPRSHandler* writer);
static void setInfoEnabled(bool enabled);
static void setEchoEnabled(bool enabled);
static void setDTMFEnabled(bool enabled);
@ -142,6 +144,8 @@ public:
virtual void ccsLinkFailed(const std::string& dtmf, DIRECTION direction);
virtual void ccsLinkEnded(const std::string& callsign, DIRECTION direction);
virtual void readAPRSFrame(CAPRSFrame& frame);
protected:
#ifdef USE_DRATS
CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3);
@ -178,7 +182,7 @@ private:
static CHeaderLogger* m_headerLogger;
static CAPRSWriter* m_aprsWriter;
static CAPRSHandler* m_aprsWriter;
static CCallsignList* m_whiteList;
static CCallsignList* m_blackList;
@ -275,6 +279,9 @@ private:
// Version information
CVersionUnit* m_version;
// APRS to DPRS
CAPRSUnit* m_aprsUnit;
#ifdef USE_DRATS
// D-RATS handler
CDRATSServer* m_drats;

@ -36,8 +36,6 @@ public:
assert(length > 0U);
m_buffer = new T[length];
::memset(m_buffer, 0x00, length * sizeof(T));
}
~CRingBuffer()

@ -32,6 +32,7 @@ protected:
bool addData(const unsigned char * data);
virtual unsigned int getDataInt(unsigned char * data, unsigned int length) = 0;
virtual bool getDataInt(std::string& data) = 0;
static void dstarCallsignToAPRS(std::string& call);
std::string getSentence();

@ -69,24 +69,6 @@ bool CSlowDataCollector::writeData(const unsigned char* data)
break;
}
// unsigned char rxDataType = (m_buffer[0] & SLOW_DATA_TYPE_MASK);
// switch (rxDataType)
// {
// case SLOW_DATA_TYPE_MASK: CLog::logDebug("SLOW_DATA_TYPE_MASK "); break;
// case SLOW_DATA_TYPE_GPS: CLog::logDebug("SLOW_DATA_TYPE_GPS "); break;
// case SLOW_DATA_TYPE_TEXT: CLog::logDebug("SLOW_DATA_TYPE_TEXT "); break;
// case SLOW_DATA_TYPE_HEADER: CLog::logDebug("SLOW_DATA_TYPE_HEADER "); break;
// case SLOW_DATA_TYPE_MESSAGE: CLog::logDebug("SLOW_DATA_TYPE_MESSAGE "); break;
// case SLOW_DATA_TYPE_FAST_DATA1: CLog::logDebug("SLOW_DATA_TYPE_FAST_DATA1 "); break;
// case SLOW_DATA_TYPE_FAST_DATA2: CLog::logDebug("SLOW_DATA_TYPE_FAST_DATA2 "); break;
// case SLOW_DATA_TYPE_SQUELCH: CLog::logDebug("SLOW_DATA_TYPE_SQUELCH "); break;
// case SLOW_DATA_LENGTH_MASK: CLog::logDebug("SLOW_DATA_LENGTH_MASK "); break;
// default:
// CLog::logDebug("!!!!!!!!!!!!!!! %X", rxDataType);
// break;
// };
if((m_buffer[0] & SLOW_DATA_TYPE_MASK) == m_slowDataType)
return addData(m_buffer + 1U);
@ -103,10 +85,14 @@ unsigned int CSlowDataCollector::getData(unsigned char * data, unsigned int leng
return getDataInt(data, length);
}
bool CSlowDataCollector::getData(std::string& data)
{
return getDataInt(data);
}
void CSlowDataCollector::reset()
{
m_state = SS_FIRST;
m_myCall.clear();
resetInt();
}

@ -24,7 +24,23 @@
#include "DStarDefines.h"
#include "Defs.h"
class CSlowDataCollector
class ISlowDataCollector
{
public:
virtual ~ISlowDataCollector() { } ;
virtual std::string getMyCall() const = 0;
virtual void setMyCall(const std::string& mycall) = 0;
virtual bool writeData(const unsigned char* data) = 0;
virtual void sync() = 0;
virtual unsigned int getData(unsigned char* data, unsigned int length) = 0;
virtual bool getData(std::string& data) = 0;
virtual void reset() = 0;
virtual unsigned char getDataType() = 0;
virtual void clock(unsigned int ms) = 0;
};
class CSlowDataCollector : public ISlowDataCollector
{
public:
CSlowDataCollector(unsigned char slowDataType);
@ -35,12 +51,15 @@ public:
bool writeData(const unsigned char* data);
void sync();
unsigned int getData(unsigned char* data, unsigned int length);
bool getData(std::string& data);
void reset();
unsigned char getDataType();
void clock(unsigned int) { };
protected:
virtual bool addData(const unsigned char* data) = 0;
virtual unsigned int getDataInt(unsigned char* data, unsigned int length) = 0;
virtual bool getDataInt(std::string& data) = 0;
virtual void resetInt() { }
private:

@ -0,0 +1,98 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <cassert>
#include "SlowDataCollectorThrottle.h"
CSlowDataCollectorThrottle::CSlowDataCollectorThrottle(ISlowDataCollector* collector, unsigned int timeout) :
m_collector(collector),
m_timer(1000U, timeout),
m_isFirst(true)
{
assert(collector != nullptr);
}
CSlowDataCollectorThrottle::~CSlowDataCollectorThrottle()
{
delete m_collector;
}
std::string CSlowDataCollectorThrottle::getMyCall() const
{
return m_collector->getMyCall();
}
void CSlowDataCollectorThrottle::setMyCall(const std::string& mycall)
{
m_isFirst = true;
m_collector->setMyCall(mycall);
}
bool CSlowDataCollectorThrottle::writeData(const unsigned char* data)
{
m_isComplete = false;
bool complete = m_collector->writeData(data);
if(complete){
if(m_isFirst) {
m_isFirst = false;
m_isComplete = true;
m_timer.start();
return true;
}
if(m_timer.hasExpired()) {
m_isComplete = true;
m_timer.start();
return true;
}
}
return false;
}
void CSlowDataCollectorThrottle::sync()
{
m_collector->sync();
}
unsigned int CSlowDataCollectorThrottle::getData(unsigned char* data, unsigned int length)
{
if(m_isComplete)
return m_collector->getData(data, length);
return 0U;
}
bool CSlowDataCollectorThrottle::getData(std::string& data)
{
if(m_isComplete)
return m_collector->getData(data);
return false;
}
void CSlowDataCollectorThrottle::reset()
{
m_timer.start();
m_collector->reset();
}
unsigned char CSlowDataCollectorThrottle::getDataType()
{
return m_collector->getDataType();
}
void CSlowDataCollectorThrottle::clock(unsigned int ms)
{
m_timer.clock(ms);
}

@ -0,0 +1,46 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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.
*/
#pragma once
#include <string>
#include "SlowDataCollector.h"
#include "Timer.h"
class CSlowDataCollectorThrottle : public ISlowDataCollector
{
public:
CSlowDataCollectorThrottle(ISlowDataCollector* collector, unsigned int timeout);
~CSlowDataCollectorThrottle();
std::string getMyCall() const;
void setMyCall(const std::string& mycall);
bool writeData(const unsigned char* data);
void sync();
unsigned int getData(unsigned char* data, unsigned int length);
bool getData(std::string& data);
void reset();
unsigned char getDataType();
void clock(unsigned int ms);
private:
ISlowDataCollector* m_collector;
CTimer m_timer;
bool m_isFirst;
bool m_isComplete;
};

@ -18,6 +18,9 @@
#include "CCITTChecksum.h"
#include "DStarDefines.h"
// Only works for positive numbers
#define roundUpToMultipleOf(n, m)(((n + m - 1) / m) * m)
const unsigned int SLOW_DATA_BLOCK_SIZE = 6U;
const unsigned int SLOW_DATA_FULL_BLOCK_SIZE = SLOW_DATA_BLOCK_SIZE * 10U;
@ -130,14 +133,14 @@ void CSlowDataEncoder::getInterleavedData(unsigned char* data)
getHeaderData(data);
else {
buildInterleavedData();
getData(m_interleavedData, data, m_interleavedPtr, m_gpsDataFullSize);
getData(m_interleavedData, data, m_interleavedPtr, m_interleavedDataFullSize);
}
}
void CSlowDataEncoder::buildInterleavedData()
{
//first build interleaved data if we do not have it
if(!m_interleavedData)
//first build interleaved data if we do not h(x + multiple - 1 - (x % multiple))ave it
if(m_interleavedData == nullptr)
{
getInterleavedDataLength();
m_interleavedData = new unsigned char[m_interleavedDataFullSize];
@ -145,33 +148,35 @@ void CSlowDataEncoder::buildInterleavedData()
unsigned int textPtr = 0U;
unsigned int gpsPtr = 0U;
unsigned int headerPtr = 0U;
//now proceed with data copying, according to this document http://www.qsl.net/kb9mwr/projects/dv/dstar/Slow%20Data.pdf
if(m_textData && m_gpsData){
for(unsigned int interleavedPtr = 0; interleavedPtr < m_interleavedDataFullSize; interleavedPtr += SLOW_DATA_BLOCK_SIZE){
if(m_textData != nullptr && m_gpsData != nullptr){
unsigned int interleavedPtr = 0;
while(textPtr < TEXT_SIZE || gpsPtr < m_gpsDataSize){
if(textPtr < TEXT_SIZE
&& ((interleavedPtr / SLOW_DATA_BLOCK_SIZE) & 0x01U) == 0)
{
::memcpy(m_interleavedData + interleavedPtr, m_textData + textPtr, SLOW_DATA_BLOCK_SIZE);
textPtr += SLOW_DATA_BLOCK_SIZE;
}
else if(gpsPtr < m_gpsDataSize){
else if(gpsPtr < m_gpsDataSize) {
::memcpy(m_interleavedData + interleavedPtr, m_gpsData + gpsPtr, SLOW_DATA_BLOCK_SIZE);
gpsPtr += SLOW_DATA_BLOCK_SIZE;
}
else if(m_headerData && headerPtr < HEADER_SIZE){
::memcpy(m_interleavedData + interleavedPtr, m_headerData + headerPtr, SLOW_DATA_BLOCK_SIZE);
headerPtr += SLOW_DATA_BLOCK_SIZE;
}
interleavedPtr += SLOW_DATA_BLOCK_SIZE;
}
if(m_headerData != nullptr) {
//append header dat in one block at the end
::memcpy(m_interleavedData + roundUpToMultipleOf(interleavedPtr, SLOW_DATA_FULL_BLOCK_SIZE), m_headerData, HEADER_SIZE);
}
}
else if(m_textData && !m_gpsData && m_headerData){
else if(m_textData != nullptr && m_gpsData == nullptr && m_headerData != nullptr){
//according to above doc, header and text are not interleaved, just on after the other. filler bytes between resync bytes.
::memcpy(m_interleavedData, m_textData, SLOW_DATA_FULL_BLOCK_SIZE);
::memcpy(m_interleavedData + SLOW_DATA_FULL_BLOCK_SIZE, m_headerData, SLOW_DATA_FULL_BLOCK_SIZE);
}
else if(!m_textData && m_gpsData && m_headerData){
else if(m_textData == nullptr && m_gpsData != nullptr && m_headerData != nullptr){
//could not find any spec about this particular case, let's put the data one after the other
::memcpy(m_interleavedData, m_gpsData, SLOW_DATA_FULL_BLOCK_SIZE);
::memcpy(m_interleavedData + SLOW_DATA_FULL_BLOCK_SIZE, m_headerData, SLOW_DATA_FULL_BLOCK_SIZE);
@ -184,9 +189,9 @@ unsigned int CSlowDataEncoder::getInterleavedDataLength()
//calculate size (including filler bytes);
m_interleavedDataFullSize = 0U;
if(m_textData) m_interleavedDataFullSize += TEXT_SIZE;
if(m_headerData) m_interleavedDataFullSize += HEADER_SIZE;
if(m_headerData) m_interleavedDataFullSize += SLOW_DATA_FULL_BLOCK_SIZE; // the is because header data is transmitted as one contiguous block. Unused bytes fileld with 'f'
if(m_gpsData) m_interleavedDataFullSize += m_gpsDataSize;
m_interleavedDataFullSize = SLOW_DATA_FULL_BLOCK_SIZE * (1U + ((m_interleavedDataFullSize - 1U) / SLOW_DATA_FULL_BLOCK_SIZE));
m_interleavedDataFullSize = roundUpToMultipleOf(m_interleavedDataFullSize, SLOW_DATA_FULL_BLOCK_SIZE); //SLOW_DATA_FULL_BLOCK_SIZE * (1U + ((m_interleavedDataFullSize - 1U) / SLOW_DATA_FULL_BLOCK_SIZE));
return m_interleavedDataFullSize;
}
@ -340,15 +345,15 @@ void CSlowDataEncoder::setGPSData(const std::string& gpsData)
if((gpsDataStrLen = gpsData.size()) > 0){
unsigned int gpsDataPos;
unsigned int strPos = 0;
m_gpsDataSize = 1U + ((gpsDataStrLen - 1U) / 6U);//to make room for the type bytes
m_gpsDataSize += gpsDataStrLen;
m_gpsDataFullSize = SLOW_DATA_FULL_BLOCK_SIZE * (1U + ((m_gpsDataSize - 1U) / SLOW_DATA_FULL_BLOCK_SIZE));
unsigned int typeBytesCount = 1U + ((gpsDataStrLen - 1U) / SLOW_DATA_BLOCK_SIZE);//to make room for the type bytes
m_gpsDataSize = typeBytesCount + gpsDataStrLen;
m_gpsDataFullSize = roundUpToMultipleOf(m_gpsDataSize, SLOW_DATA_FULL_BLOCK_SIZE);
m_gpsData = new unsigned char[m_gpsDataFullSize];
::memset(m_gpsData, 'f', m_gpsDataFullSize);
for(gpsDataPos = 0; gpsDataPos < m_gpsDataFullSize;){
unsigned int dataLen = gpsDataStrLen - strPos < 5U ? gpsDataStrLen - strPos : 5U;
for(gpsDataPos = 0; gpsDataPos < m_gpsDataSize;){
unsigned int dataLen = gpsDataStrLen - strPos< 5U ? gpsDataStrLen - strPos : 5U;
m_gpsData[gpsDataPos++] = SLOW_DATA_TYPE_GPS | dataLen;
for(unsigned int i = 0U; i < dataLen; i++){

@ -21,8 +21,11 @@
#include <memory>
#include <string>
#include <stdexcept>
#include <algorithm>
#define string_right(s,l) (s.substr(s.length() - l, l))
#define string_is_blank(s) (std::all_of(s.begin(), s.end(), [](char c) { return c == ' '; }))
#define string_is_blank_or_empty(s) (s.empty() || std::all_of(s.begin(), s.end(), [](char c) { return c == ' '; }))
class CStringUtils {
public:

@ -201,19 +201,15 @@ bool CTCPReaderWriterClient::write(const unsigned char* buffer, unsigned int len
bool CTCPReaderWriterClient::writeLine(const std::string& line)
{
std::string lineCopy(line);
if(lineCopy.size() > 0 && lineCopy.at(lineCopy.size() - 1) != '\n')
lineCopy.push_back('\n');
//stupidly write one char after the other
size_t len = lineCopy.size();
bool result = true;
for(size_t i = 0; i < len && result; i++){
unsigned char c = lineCopy.at(i);
result = write(&c , 1);
}
if(line.empty())
return true;
bool ret = write((unsigned char *)line.c_str(), line.length());
if(line[line.length() - 1] != '\n')//make sure we send a newline
ret = writeLine("\n") && ret;
return result;
return ret;
}
void CTCPReaderWriterClient::close()

@ -0,0 +1,115 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../APRSFormater.h"
class APRSFormater_frameToString : public ::testing::Test {
};
TEST_F(APRSFormater_frameToString, EmptyFrame) {
CAPRSFrame aprsFrame("", "", { }, "", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds");
}
TEST_F(APRSFormater_frameToString, SourceOnly) {
CAPRSFrame aprsFrame("N0CALL", "", { }, "", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds");
}
TEST_F(APRSFormater_frameToString, DestinationOnly) {
CAPRSFrame aprsFrame("", "APRS", { }, "", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds");
}
TEST_F(APRSFormater_frameToString, PathOnly) {
CAPRSFrame aprsFrame("", "", { "WIDE1-1, WIDE2-1" }, "", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds");
}
TEST_F(APRSFormater_frameToString, BodyOnly) {
CAPRSFrame aprsFrame("", "", { }, "Lorem Ipsum", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STREQ(output.c_str(), "This should be left untouched if the test succeeds");
}
TEST_F(APRSFormater_frameToString, CorrectWithoutPath) {
CAPRSFrame aprsFrame("N0CALL", "APRS", { }, "Lorem Ipsum", APFT_UNKNOWN);
std::string output("This should NOT be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STREQ(output.c_str(), "N0CALL>APRS:Lorem Ipsum");
}
TEST_F(APRSFormater_frameToString, CorrectWithPath) {
CAPRSFrame aprsFrame("N0CALL", "APRS", { "WIDE1-1", "WIDE2-2" }, "Lorem Ipsum", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STREQ(output.c_str(), "N0CALL>APRS,WIDE1-1,WIDE2-2:Lorem Ipsum");
}
TEST_F(APRSFormater_frameToString, WithSomeEmptyPath) {
CAPRSFrame aprsFrame("N0CALL", "APRS", { "WIDE1-1", "", "WIDE2-2" }, "Lorem Ipsum", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STREQ(output.c_str(), "N0CALL>APRS,WIDE1-1,WIDE2-2:Lorem Ipsum");
}
TEST_F(APRSFormater_frameToString, WithSomeBlankPath) {
CAPRSFrame aprsFrame("N0CALL", "APRS", { "WIDE1-1", "", "WIDE2-2" }, "Lorem Ipsum", APFT_UNKNOWN);
std::string output("This should be left untouched if the test succeeds");
bool retVal = CAPRSFormater::frameToString(output, aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STREQ(output.c_str(), "N0CALL>APRS,WIDE1-1,WIDE2-2:Lorem Ipsum");
}

@ -20,86 +20,182 @@
#include "../../APRSParser.h"
class APRSParser_parseAPRSFrame : public ::testing::Test {
};
TEST_F(APRSParser_parseAPRSFrame, EmpyString) {
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}
TEST_F(APRSParser_parseAPRSFrame, NoSourceCallsign) {
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame(">APRS::F4ABC Test Message", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}
TEST_F(APRSParser_parseAPRSFrame, NoDestCallsign) {
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>::F4ABC Test Message", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
namespace APRSParserTests
{
class APRSParser_parseAPRSFrame : public ::testing::Test {
};
TEST_F(APRSParser_parseAPRSFrame, EmpyString)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}
TEST_F(APRSParser_parseAPRSFrame, NoSourceCallsign)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame(">APRS::F4ABC Test Message", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}
TEST_F(APRSParser_parseAPRSFrame, NoDestCallsign)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>::F4ABC Test Message", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}
TEST_F(APRSParser_parseAPRSFrame, CorrectMessageFrameWithDigipeater)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>APRS,WIDE1-1,WIDE2-2::F4ABC :Test Message", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "N0CALL");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 2);
EXPECT_STREQ(aprsFrame.getPath()[0].c_str(), "WIDE1-1");
EXPECT_STREQ(aprsFrame.getPath()[1].c_str(), "WIDE2-2");
}
TEST_F(APRSParser_parseAPRSFrame, CorrectMessageFrameWithoutDigipeater)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>APRS::F4ABC :Test Message", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "N0CALL");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, InvalidMessageFrame)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>APRS::F4ABC&@#$:Test Message", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}
TEST_F(APRSParser_parseAPRSFrame, ID51)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F4FXL-8>API51,DSTAR:!1234.56N/12345.67E[/A=000886QRV DStar\r\r\n", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "!1234.56N/12345.67E[/A=000886QRV DStar\r\r\n");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "API51");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F4FXL-8");
EXPECT_EQ(aprsFrame.getType(), APFT_POSITION);
EXPECT_EQ(aprsFrame.getPath().size(), 1);
EXPECT_STREQ(aprsFrame.getPath()[0].c_str(), "DSTAR");
}
TEST_F(APRSParser_parseAPRSFrame, telemetryLabels)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS::F5ZEE-C :PARM.PA Temp", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F5ZEE-C :PARM.PA Temp");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C");
EXPECT_EQ(aprsFrame.getType(), APFT_TELEMETRY);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, telemetryEQNS)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS::F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C");
EXPECT_EQ(aprsFrame.getType(), APFT_TELEMETRY);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, telemetryEQNSWithSeqnum)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS::F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0{ABCD", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F5ZEE-C :EQNS.0,0.16016,-40,0,0,0,0,0,0,0,0,0,0,0,0{ABCD");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, telemetryReport)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F5ZEE-C>APRS:T#581,342,000,000,000,000,00000000", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "T#581,342,000,000,000,000,00000000");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F5ZEE-C");
EXPECT_EQ(aprsFrame.getType(), APFT_TELEMETRY);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, messageToSelf)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F4ABC>APRS::F4ABC :Test Message{ABCD", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message{ABCD");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F4ABC");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, messageToSelfNoSeqNum)
{
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("F4ABC>APRS::F4ABC :Test Message", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F4ABC");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
}
TEST_F(APRSParser_parseAPRSFrame, CorrectMessageFrameWithDigipeater) {
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>APRS,WIDE1-1,WIDE2-2::F4ABC :Test Message", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "N0CALL");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 2);
EXPECT_STREQ(aprsFrame.getPath()[0].c_str(), "WIDE1-1");
EXPECT_STREQ(aprsFrame.getPath()[1].c_str(), "WIDE2-2");
}
TEST_F(APRSParser_parseAPRSFrame, CorrectMessageFrameWithoutDigipeater) {
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>APRS::F4ABC :Test Message", aprsFrame);
EXPECT_TRUE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ":F4ABC :Test Message");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "APRS");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "N0CALL");
EXPECT_EQ(aprsFrame.getType(), APFT_MESSAGE);
EXPECT_EQ(aprsFrame.getPath().size(), 0);
}
TEST_F(APRSParser_parseAPRSFrame, InvalideMessageFrame) {
CAPRSFrame aprsFrame;
bool retVal = CAPRSParser::parseFrame("N0CALL>APRS::F4ABC&@#$:Test Message", aprsFrame);
EXPECT_FALSE(retVal);
EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "");
EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "");
EXPECT_EQ(aprsFrame.getType(), APFT_UNKNOWN);
EXPECT_EQ(aprsFrame.getPath().size(), 0U);
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../APRStoDPRS.h"
namespace APRStoDPRSTests
{
class APRStoDPRS_aprsToDPRS : public ::testing::Test {
};
TEST_F(APRStoDPRS_aprsToDPRS, validMessage)
{
CAPRSFrame frame("KC3FRA", "APRS", {"WIDE1-1", "WIDE2-2"}, ":F4FXL :Salut, comment vas tu?", APFT_MESSAGE);
std::string dprs, text;
CHeaderData header;
bool ret = CAPRSToDPRS::aprsToDPRS(dprs, text, header, frame);
EXPECT_TRUE(ret);
EXPECT_STREQ(dprs.c_str(), "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r");
EXPECT_STREQ(text.c_str(), "Salut, comment vas tu?");
EXPECT_STREQ(header.getMyCall1().c_str(), "KC3FRA ");
EXPECT_STREQ(header.getMyCall2().c_str(), "MSG ");
EXPECT_STREQ(header.getYourCall().c_str(), "F4FXL ");
}
TEST_F(APRStoDPRS_aprsToDPRS, validMessageRecipientWithSSID)
{
CAPRSFrame frame("KC3FRA", "APRS", {"WIDE1-1", "WIDE2-2"}, ":F4FXL-7 :Salut, comment vas tu?", APFT_MESSAGE);
std::string dprs, text;
CHeaderData header;
bool ret = CAPRSToDPRS::aprsToDPRS(dprs, text, header, frame);
EXPECT_TRUE(ret);
EXPECT_STREQ(dprs.c_str(), "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r");
EXPECT_STREQ(text.c_str(), "Salut, comment vas tu?");
EXPECT_STREQ(header.getMyCall1().c_str(), "KC3FRA ");
EXPECT_STREQ(header.getMyCall2().c_str(), "MSG ");
EXPECT_STREQ(header.getYourCall().c_str(), "F4FXL ");
}
TEST_F(APRStoDPRS_aprsToDPRS, emptyRecipient)
{
CAPRSFrame frame("KC3FRA", "APRS", {"WIDE1-1", "WIDE2-2"}, ": :Salut, comment vas tu?", APFT_MESSAGE);
std::string dprs, text;
CHeaderData header;
bool ret = CAPRSToDPRS::aprsToDPRS(dprs, text, header, frame);
EXPECT_FALSE(ret);
EXPECT_STREQ(dprs.c_str(), "");
EXPECT_STREQ(text.c_str(), "");
EXPECT_STREQ(header.getMyCall1().c_str(), " ");
EXPECT_STREQ(header.getMyCall2().c_str(), " ");
EXPECT_STREQ(header.getYourCall().c_str(), " ");
}
}

@ -0,0 +1,86 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../RSMS1AMessageBuilder.h"
namespace RSMS1AMessageBuilderTests
{
class RSMS1AMessageBuilder_buildMessage : public ::testing::Test {
};
TEST_F(RSMS1AMessageBuilder_buildMessage, ABC)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "ABC");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118ABCF\r");
}
TEST_F(RSMS1AMessageBuilder_buildMessage, A)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "A");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118AA\r");
}
TEST_F(RSMS1AMessageBuilder_buildMessage, AA)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "AA");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118AA\02\r");
}
TEST_F(RSMS1AMessageBuilder_buildMessage, SalutCommentVasTu)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "Salut, comment vas tu?");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r");
}
TEST_F(RSMS1AMessageBuilder_buildMessage, escapeComma)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", ",");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118o,o,\r");
}
TEST_F(RSMS1AMessageBuilder_buildMessage, INeedMoreDollars)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL", "I need more $$$$");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL,001118I need more o$o$o$o$\x08\r");
}
//"$$Msg,KC3FRA,F4FXL 7,00116Fhello\024\r"
TEST_F(RSMS1AMessageBuilder_buildMessage, hello)
{
std::string message;
CRSMS1AMessageBuilder::buildMessage(message, "KC3FRA", "F4FXL 7", "hello");
EXPECT_STREQ(message.c_str(), "$$Msg,KC3FRA,F4FXL 7,00116Fhello\024\r");
}
};

@ -0,0 +1,67 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../RSMS1AMessageBuilder.h"
namespace RSMS1AMessageBuilderTests
{
class RSMS1AMessageBuilder_parseMessage : public ::testing::Test {
};
TEST_F(RSMS1AMessageBuilder_parseMessage, NoError)
{
std::string message = "$$Msg,KC3FRA,F4FXL,001118Saluto, comment vas tu?z\r";
std::string sender, recipient, body;
auto ret = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, message);
EXPECT_EQ(ret, RSMS_OK);
EXPECT_STREQ(sender.c_str(), "KC3FRA");
EXPECT_STREQ(recipient.c_str(), "F4FXL");
EXPECT_STREQ(body.c_str(), "Salut, comment vas tu?");
}
TEST_F(RSMS1AMessageBuilder_parseMessage, ErrorInMessage)
{
std::string message = "$$Msg,KC3FRA,F4FXL,001118Saluto, comlent vas tu?z\r";
std::string sender, recipient, body;
auto ret = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, message);
EXPECT_EQ(ret, RSMS_ERRONEOUS_MSG);
EXPECT_STREQ(sender.c_str(), "KC3FRA");
EXPECT_STREQ(recipient.c_str(), "F4FXL");
EXPECT_STREQ(body.c_str(), "Salut, comlent vas tu?");
}
TEST_F(RSMS1AMessageBuilder_parseMessage, ErrorInHeader)
{
std::string message = "$$Msg,KC3FRB,F4FXL,001118Saluto, comment vas tu?z\r";
std::string sender, recipient, body;
auto ret = CRSMS1AMessageBuilder::parseMessage(sender, recipient, body, message);
EXPECT_EQ(ret, RSMS_FAIL);
EXPECT_STREQ(sender.c_str(), "");
EXPECT_STREQ(recipient.c_str(), "");
EXPECT_STREQ(body.c_str(), "");
}
};

@ -0,0 +1,78 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../SlowDataEncoder.h"
#include "../../DStarDefines.h"
namespace SlowDataEncoderTests
{
class SlowDataEncoder_gpsData : public ::testing::Test {
};
TEST_F(SlowDataEncoder_gpsData, gpsDataCorrectlySet)
{
CSlowDataEncoder encoder;
encoder.setGPSData("ABCDEFGHIJKLMN");
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getGPSData(buffer);
encoder.getGPSData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E');
encoder.getGPSData(buffer);
encoder.getGPSData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'I');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'J');
encoder.getGPSData(buffer);
encoder.getGPSData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x4U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'K');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'M');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'N');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
// all the remaining data shall be filled with 'f'
for(unsigned int i = 18; i < 60U; i+= 6U) {
encoder.getGPSData(buffer);
encoder.getGPSData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
}
}

@ -0,0 +1,139 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../SlowDataEncoder.h"
#include "../../DStarDefines.h"
namespace SlowDataEncoderTests
{
class SlowDataEncoder_headerData : public ::testing::Test {
};
TEST_F(SlowDataEncoder_headerData, headerDataCorrectlySet)
{
CHeaderData header;
header.setFlags('1', '2', '3');
header.setMyCall1("F4FXL");
header.setMyCall2("5100");
header.setYourCall("CQCQCQ");
header.setRptCall1("F5ZEE B");
header.setRptCall2("F5ZEE G");
CSlowDataEncoder encoder;
encoder.setHeaderData(header);
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '1');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '2');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '3');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'F');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, '5');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Z');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'E');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'E');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'F');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '5');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'Z');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'E');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'B');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'C');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Q');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'C');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'Q');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'C');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'Q');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'F');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '4');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'F');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'X');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '5');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '1');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '0');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '0');
EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); // we do not check the actual result of the CRC, we just make sure is inot 'f'
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U);
EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); // done with crc check, remaining shall be filled with 'f'
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
encoder.getHeaderData(buffer);
encoder.getHeaderData(buffer + 3);
// all the remaining data shall be filled with 'f'
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
}

@ -0,0 +1,865 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include <cstring>
#include "../../SlowDataEncoder.h"
#include "../../DStarDefines.h"
namespace SlowDataEncoderTests
{
class SlowDataEncoder_interleavedData : public ::testing::Test {
};
TEST_F(SlowDataEncoder_interleavedData, gpsAndHeaderData)
{
// Header is never interleaved, text and header are sent as two blocks
CHeaderData header;
unsigned char headerData[RADIO_HEADER_LENGTH_BYTES];
::memset(headerData, 'H', RADIO_HEADER_LENGTH_BYTES);
header.setData(headerData, RADIO_HEADER_LENGTH_BYTES, false);
//here we only test for correct interleaving
CSlowDataEncoder encoder;
encoder.setGPSData("GGGGGGGG"); // 8 times G
encoder.setHeaderData(header);
for(unsigned int i = 0U; i < 2U; i++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x3U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
// all the remaining data shall be filled with 'f' until next block
for(unsigned int j = 12; j < 60U; j+= 6U) {
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f');// this is checksum byte, just make sure is is not 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U);
EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f');// this is checksum byte, just make sure is is not 'f'
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
//remaining data is only 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
TEST_F(SlowDataEncoder_interleavedData, textAndHeaderData)
{
// Header is never interleaved, text and header are sent as two blocks
CHeaderData header;
unsigned char headerData[RADIO_HEADER_LENGTH_BYTES];
::memset(headerData, 'H', RADIO_HEADER_LENGTH_BYTES);
header.setData(headerData, RADIO_HEADER_LENGTH_BYTES, false);
//here we only test for correct interleaving
CSlowDataEncoder encoder;
encoder.setTextData("TTTTTT"); // 6 times T
encoder.setHeaderData(header);
auto dataLen = encoder.getInterleavedDataLength();
EXPECT_EQ(dataLen, 120); //2* 60
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'T');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'T');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'T');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
// all the remaining data shall be filled with 'f' until next block
for(unsigned int j = 24; j < 60U; j+= 6U) {
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f');// this is checksum byte, just make sure is is not 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U);
EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f');// this is checksum byte, just make sure is is not 'f'
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
//remaining data is only 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
TEST_F(SlowDataEncoder_interleavedData, gpsTextAndHeaderData)
{
CHeaderData header;
unsigned char headerData[RADIO_HEADER_LENGTH_BYTES];
::memset(headerData, 'H', RADIO_HEADER_LENGTH_BYTES);
header.setData(headerData, RADIO_HEADER_LENGTH_BYTES, false);
//here we only test for correct interleaving
CSlowDataEncoder encoder;
encoder.setTextData("TTTTTT"); // 6 times T
encoder.setGPSData("GGGGGGGGGGGGGGGGGGGGGG"); // 22 times G
encoder.setHeaderData(header);
auto dataLen = encoder.getInterleavedDataLength();
EXPECT_EQ(dataLen, 120);// including data type bytes we need 54 (20 + 5 + 22 + 6) bytes, this shall be rounded up to next block size multiple, in this case 60
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'T');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'T');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'T');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
// header is not interleaved, attached as one contiguous block, but it only starts at the nex block
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'H');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'H');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'H');
EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f');// this is checksum byte, just make sure is is not 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U);
EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f');// this is checksum byte, just make sure is is not 'f'
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
//remaining data is only 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
TEST_F(SlowDataEncoder_interleavedData, gpsAndTextData)
{
//here we only test for correct interleaving
CSlowDataEncoder encoder;
encoder.setTextData("TTTTTT"); // 6 times T
encoder.setGPSData("GGGGGGGGGGGGGGGGGGGGGG"); // 22 times G
auto dataLen = encoder.getInterleavedDataLength();
EXPECT_EQ(dataLen, 60);// including data type bytes we need 54 (20 + 5 + 22 + 6) bytes, this shall be rounded up to next block size multiple, in this case 60
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'T');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'T');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'T');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'T');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'G');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'G');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
//remaining shall only be 'f"
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
TEST_F(SlowDataEncoder_interleavedData, onlyGPSData)
{
CSlowDataEncoder encoder;
encoder.setGPSData("ABCDEFGHIJKLMN");
auto dataLen = encoder.getInterleavedDataLength();
EXPECT_EQ(dataLen, 60);
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'H');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'I');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'J');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_GPS | 0x4U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'K');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'M');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'N');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
// all the remaining data shall be filled with 'f'
for(unsigned int i = 18; i < 60U; i+= 6U) {
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
}
TEST_F(SlowDataEncoder_interleavedData, onlyTextData)
{
CSlowDataEncoder encoder;
encoder.setTextData("ABCDEFG");
auto dataLen = encoder.getInterleavedDataLength();
EXPECT_EQ(dataLen, 60);
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
// all the remaining data shall be filled with 'f'
for(unsigned int i = 24; i < 60U; i+= 6U) {
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
}
TEST_F(SlowDataEncoder_interleavedData, onlyHeaderData)
{
CHeaderData header;
header.setFlags('1', '2', '3');
header.setMyCall1("F4FXL");
header.setMyCall2("5100");
header.setYourCall("CQCQCQ");
header.setRptCall1("F5ZEE B");
header.setRptCall2("F5ZEE G");
CSlowDataEncoder encoder;
encoder.setHeaderData(header);
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '1');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '2');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '3');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'F');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, '5');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Z');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'E');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'E');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'G');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'F');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '5');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'Z');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'E');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'B');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'C');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'Q');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'C');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'Q');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'C');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'Q');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'F');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '4');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'F');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'X');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'L');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x5U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, '5');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, '1');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, '0');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, '0');
EXPECT_NE(buffer[5] ^ SCRAMBLER_BYTE3, 'f'); // we do not check the actual result of the CRC, we just make sure is inot 'f'
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_HEADER | 0x1U);
EXPECT_NE(buffer[1] ^ SCRAMBLER_BYTE2, 'f'); // done with crc check, remaining shall be filled with 'f'
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
encoder.getInterleavedData(buffer);
encoder.getInterleavedData(buffer + 3);
// all the remaining data shall be filled with 'f'
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
}

@ -0,0 +1,87 @@
/*
* Copyright (C) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA
*
* 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 <gtest/gtest.h>
#include "../../SlowDataEncoder.h"
#include "../../DStarDefines.h"
namespace SlowDataEncoderTests
{
class SlowDataEncoder_textData : public ::testing::Test {
};
TEST_F(SlowDataEncoder_textData, textDataCorrectlySet)
{
CSlowDataEncoder encoder;
encoder.setTextData("ABCDEFG");
for(unsigned int testCount = 0U; testCount < 2U; testCount++) {
unsigned char buffer[6U];
encoder.getTextData(buffer);
encoder.getTextData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x0U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'A');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'B');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'C');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'D');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'E');
encoder.getTextData(buffer);
encoder.getTextData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x1U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'F');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'G');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getTextData(buffer);
encoder.getTextData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x2U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
encoder.getTextData(buffer);
encoder.getTextData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, SLOW_DATA_TYPE_TEXT | 0x3U);
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, ' ');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, ' ');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, ' ');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, ' ');
// all the remaining data shall be filled with 'f'
for(unsigned int i = 24; i < 60U; i+= 6U) {
encoder.getTextData(buffer);
encoder.getTextData(buffer + 3);
EXPECT_EQ(buffer[0] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[1] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[2] ^ SCRAMBLER_BYTE3, 'f');
EXPECT_EQ(buffer[3] ^ SCRAMBLER_BYTE1, 'f');
EXPECT_EQ(buffer[4] ^ SCRAMBLER_BYTE2, 'f');
EXPECT_EQ(buffer[5] ^ SCRAMBLER_BYTE3, 'f');
}
}
}
}

@ -19,6 +19,8 @@
#include <gtest/gtest.h>
#include "../../Utils.h"
#include "../../StringUtils.h"
#include "../../Log.h"
class Utils_swap_endian_be : public ::testing::Test {
@ -31,4 +33,13 @@ TEST_F(Utils_swap_endian_be, SwapUINT32_be) {
uint32_t res = CUtils::swap_endian_be(test);
EXPECT_EQ(res, 0x12345678U);
}
}
TEST_F(Utils_swap_endian_be, blabla) {
std::vector<char> charsToEscape = {-17, 0, 17, 19, 128-2, -25, 26, 128-3, -1, 36, 13, 44};
for(unsigned int i = 1U; i < charsToEscape.size(); i++) {
CLog::logDebug("%08x - %08x", charsToEscape[i], (unsigned char)charsToEscape[i]);
}
}

Loading…
Cancel
Save

Powered by TurnKey Linux.