2026-03-05 16:52:12 +01:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
|
|
|
|
// //
|
|
|
|
|
// 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 as version 3 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 V3 for more details. //
|
|
|
|
|
// //
|
|
|
|
|
// You should have received a copy of the GNU General Public License //
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
#include <QTime>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
#include "meshtasticdemoddecoder.h"
|
|
|
|
|
#include "meshtasticdemoddecoderlora.h"
|
|
|
|
|
#include "meshtasticdemodmsg.h"
|
|
|
|
|
|
|
|
|
|
MeshtasticDemodDecoder::MeshtasticDemodDecoder() :
|
|
|
|
|
m_codingScheme(MeshtasticDemodSettings::CodingTTY),
|
|
|
|
|
m_spreadFactor(0U),
|
|
|
|
|
m_deBits(0U),
|
|
|
|
|
m_nbSymbolBits(5),
|
|
|
|
|
m_nbParityBits(1),
|
|
|
|
|
m_hasCRC(true),
|
|
|
|
|
m_hasHeader(true),
|
|
|
|
|
m_packetLength(0U),
|
|
|
|
|
m_loRaBandwidth(250000U),
|
|
|
|
|
m_nbSymbols(0U),
|
|
|
|
|
m_nbCodewords(0U),
|
|
|
|
|
m_earlyEOM(false),
|
|
|
|
|
m_headerParityStatus((int) MeshtasticDemodSettings::ParityUndefined),
|
|
|
|
|
m_headerCRCStatus(false),
|
|
|
|
|
m_payloadParityStatus((int) MeshtasticDemodSettings::ParityUndefined),
|
|
|
|
|
m_payloadCRCStatus(false),
|
|
|
|
|
m_pipelineId(-1),
|
|
|
|
|
m_outputMessageQueue(nullptr),
|
|
|
|
|
m_headerFeedbackMessageQueue(nullptr)
|
|
|
|
|
{
|
|
|
|
|
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshtasticDemodDecoder::~MeshtasticDemodDecoder()
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void MeshtasticDemodDecoder::setNbSymbolBits(unsigned int spreadFactor, unsigned int deBits)
|
|
|
|
|
{
|
|
|
|
|
m_spreadFactor = spreadFactor;
|
|
|
|
|
|
|
|
|
|
if (deBits >= spreadFactor) {
|
|
|
|
|
m_deBits = m_spreadFactor - 1;
|
|
|
|
|
} else {
|
|
|
|
|
m_deBits = deBits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_nbSymbolBits = m_spreadFactor - m_deBits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MeshtasticDemodDecoder::decodeSymbols(const std::vector<unsigned short>& symbols, QByteArray& bytes)
|
|
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
if (m_nbSymbolBits >= 5)
|
2026-03-05 16:52:12 +01:00
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
unsigned int headerNbSymbolBits;
|
2026-03-05 16:52:12 +01:00
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
if (m_hasHeader && (m_spreadFactor > 2U)) {
|
|
|
|
|
headerNbSymbolBits = m_spreadFactor - 2U;
|
|
|
|
|
} else {
|
|
|
|
|
headerNbSymbolBits = m_nbSymbolBits;
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
MeshtasticDemodDecoderLoRa::decodeBytes(
|
|
|
|
|
bytes,
|
|
|
|
|
symbols,
|
|
|
|
|
m_nbSymbolBits,
|
|
|
|
|
headerNbSymbolBits,
|
|
|
|
|
m_hasHeader,
|
|
|
|
|
m_hasCRC,
|
|
|
|
|
m_nbParityBits,
|
|
|
|
|
m_packetLength,
|
|
|
|
|
m_earlyEOM,
|
|
|
|
|
m_headerParityStatus,
|
|
|
|
|
m_headerCRCStatus,
|
|
|
|
|
m_payloadParityStatus,
|
|
|
|
|
m_payloadCRCStatus
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
MeshtasticDemodDecoderLoRa::getCodingMetrics(
|
|
|
|
|
m_nbSymbolBits,
|
|
|
|
|
headerNbSymbolBits,
|
|
|
|
|
m_nbParityBits,
|
|
|
|
|
m_packetLength,
|
|
|
|
|
m_hasHeader,
|
|
|
|
|
m_hasCRC,
|
|
|
|
|
m_nbSymbols,
|
|
|
|
|
m_nbCodewords
|
|
|
|
|
);
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MeshtasticDemodDecoder::handleMessage(const Message& cmd)
|
|
|
|
|
{
|
|
|
|
|
if (MeshtasticDemodMsg::MsgLoRaHeaderProbe::match(cmd))
|
|
|
|
|
{
|
|
|
|
|
MeshtasticDemodMsg::MsgLoRaHeaderProbe& msg = (MeshtasticDemodMsg::MsgLoRaHeaderProbe&) cmd;
|
|
|
|
|
const std::vector<unsigned short>& symbols = msg.getSymbols();
|
|
|
|
|
|
|
|
|
|
bool hasCRC = msg.getHasCRC();
|
|
|
|
|
unsigned int nbParityBits = m_nbParityBits;
|
|
|
|
|
unsigned int packetLength = m_packetLength;
|
|
|
|
|
int headerParityStatus = (int) MeshtasticDemodSettings::ParityUndefined;
|
|
|
|
|
bool headerCRCStatus = false;
|
|
|
|
|
bool ldro = false;
|
|
|
|
|
unsigned int expectedSymbols = 0U;
|
|
|
|
|
bool valid = false;
|
|
|
|
|
|
|
|
|
|
if (symbols.size() >= 8U && msg.getHasHeader())
|
|
|
|
|
{
|
|
|
|
|
MeshtasticDemodDecoderLoRa::decodeHeader(
|
|
|
|
|
symbols,
|
|
|
|
|
msg.getHeaderNbSymbolBits(),
|
|
|
|
|
hasCRC,
|
|
|
|
|
nbParityBits,
|
|
|
|
|
packetLength,
|
|
|
|
|
headerParityStatus,
|
|
|
|
|
headerCRCStatus
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (headerCRCStatus && (packetLength > 0U) && (nbParityBits >= 1U) && (nbParityBits <= 4U))
|
|
|
|
|
{
|
|
|
|
|
const unsigned int spreadFactor = msg.getSpreadFactor();
|
|
|
|
|
const unsigned int bandwidth = msg.getBandwidth() > 0U ? msg.getBandwidth() : m_loRaBandwidth;
|
|
|
|
|
ldro = ((1U << spreadFactor) * 1000.0 / static_cast<double>(std::max(1U, bandwidth))) > 16.0;
|
|
|
|
|
const int denom = static_cast<int>(spreadFactor) - (ldro ? 2 : 0);
|
|
|
|
|
|
|
|
|
|
if (denom > 0)
|
|
|
|
|
{
|
|
|
|
|
const int numerator =
|
|
|
|
|
2 * static_cast<int>(packetLength)
|
|
|
|
|
- static_cast<int>(spreadFactor)
|
|
|
|
|
+ 2
|
|
|
|
|
+ 5 // explicit header path (!impl_head)
|
|
|
|
|
+ (hasCRC ? 4 : 0);
|
|
|
|
|
const int payloadBlocks = std::max(0, static_cast<int>(std::ceil(static_cast<double>(numerator) / static_cast<double>(denom))));
|
|
|
|
|
expectedSymbols = 8U + static_cast<unsigned int>(payloadBlocks) * (4U + nbParityBits);
|
|
|
|
|
valid = expectedSymbols >= 8U;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_headerFeedbackMessageQueue)
|
|
|
|
|
{
|
|
|
|
|
MeshtasticDemodMsg::MsgLoRaHeaderFeedback *feedback = MeshtasticDemodMsg::MsgLoRaHeaderFeedback::create(
|
|
|
|
|
msg.getFrameId(),
|
|
|
|
|
valid,
|
|
|
|
|
hasCRC,
|
|
|
|
|
nbParityBits,
|
|
|
|
|
packetLength,
|
|
|
|
|
ldro,
|
|
|
|
|
expectedSymbols,
|
|
|
|
|
headerParityStatus,
|
|
|
|
|
headerCRCStatus
|
|
|
|
|
);
|
|
|
|
|
m_headerFeedbackMessageQueue->push(feedback);
|
|
|
|
|
qDebug("MeshtasticDemodDecoder::handleMessage: header probe frameId=%u valid=%d len=%u CR=%u expected=%u",
|
|
|
|
|
msg.getFrameId(), valid ? 1 : 0, packetLength, nbParityBits, expectedSymbols);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (MeshtasticDemodMsg::MsgDecodeSymbols::match(cmd))
|
|
|
|
|
{
|
|
|
|
|
qDebug("MeshtasticDemodDecoder::handleMessage: MsgDecodeSymbols");
|
|
|
|
|
MeshtasticDemodMsg::MsgDecodeSymbols& msg = (MeshtasticDemodMsg::MsgDecodeSymbols&) cmd;
|
|
|
|
|
float msgSignalDb = msg.getSingalDb();
|
|
|
|
|
float msgNoiseDb = msg.getNoiseDb();
|
|
|
|
|
unsigned int msgSyncWord = msg.getSyncWord();
|
|
|
|
|
QDateTime dt = QDateTime::currentDateTime();
|
|
|
|
|
QString msgTimestamp = dt.toString(Qt::ISODateWithMs);
|
|
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
QByteArray msgBytes;
|
|
|
|
|
const std::vector<std::vector<float>>& msgMags = msg.getMagnitudes();
|
|
|
|
|
const bool canSoftDecode = !msgMags.empty()
|
|
|
|
|
&& (msgMags.size() >= msg.getSymbols().size())
|
|
|
|
|
&& (m_spreadFactor >= 5U)
|
|
|
|
|
&& (m_loRaBandwidth > 0U);
|
|
|
|
|
|
|
|
|
|
struct LoRaDecodeState
|
2026-03-05 16:52:12 +01:00
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
QByteArray bytes;
|
|
|
|
|
bool hasCRC;
|
|
|
|
|
unsigned int nbParityBits;
|
|
|
|
|
unsigned int packetLength;
|
|
|
|
|
unsigned int nbSymbols;
|
|
|
|
|
unsigned int nbCodewords;
|
|
|
|
|
bool earlyEOM;
|
|
|
|
|
int headerParityStatus;
|
|
|
|
|
bool headerCRCStatus;
|
|
|
|
|
int payloadParityStatus;
|
|
|
|
|
bool payloadCRCStatus;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto captureLoRaState = [this](const QByteArray& bytes) -> LoRaDecodeState {
|
|
|
|
|
LoRaDecodeState s;
|
|
|
|
|
s.bytes = bytes;
|
|
|
|
|
s.hasCRC = m_hasCRC;
|
|
|
|
|
s.nbParityBits = m_nbParityBits;
|
|
|
|
|
s.packetLength = m_packetLength;
|
|
|
|
|
s.nbSymbols = m_nbSymbols;
|
|
|
|
|
s.nbCodewords = m_nbCodewords;
|
|
|
|
|
s.earlyEOM = m_earlyEOM;
|
|
|
|
|
s.headerParityStatus = m_headerParityStatus;
|
|
|
|
|
s.headerCRCStatus = m_headerCRCStatus;
|
|
|
|
|
s.payloadParityStatus = m_payloadParityStatus;
|
|
|
|
|
s.payloadCRCStatus = m_payloadCRCStatus;
|
|
|
|
|
return s;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto restoreLoRaState = [this, &msgBytes](const LoRaDecodeState& s) {
|
|
|
|
|
msgBytes = s.bytes;
|
|
|
|
|
m_hasCRC = s.hasCRC;
|
|
|
|
|
m_nbParityBits = s.nbParityBits;
|
|
|
|
|
m_packetLength = s.packetLength;
|
|
|
|
|
m_nbSymbols = s.nbSymbols;
|
|
|
|
|
m_nbCodewords = s.nbCodewords;
|
|
|
|
|
m_earlyEOM = s.earlyEOM;
|
|
|
|
|
m_headerParityStatus = s.headerParityStatus;
|
|
|
|
|
m_headerCRCStatus = s.headerCRCStatus;
|
|
|
|
|
m_payloadParityStatus = s.payloadParityStatus;
|
|
|
|
|
m_payloadCRCStatus = s.payloadCRCStatus;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (canSoftDecode)
|
|
|
|
|
{
|
|
|
|
|
unsigned int headerNbSymbolBits;
|
|
|
|
|
|
|
|
|
|
if (m_hasHeader && (m_spreadFactor > 2U)) {
|
|
|
|
|
headerNbSymbolBits = m_spreadFactor - 2U;
|
|
|
|
|
} else {
|
|
|
|
|
headerNbSymbolBits = m_nbSymbolBits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MeshtasticDemodDecoderLoRa::decodeBytesSoft(
|
|
|
|
|
msgBytes,
|
|
|
|
|
msgMags,
|
|
|
|
|
msg.getSymbols(),
|
|
|
|
|
m_spreadFactor,
|
|
|
|
|
m_loRaBandwidth,
|
|
|
|
|
m_nbSymbolBits,
|
|
|
|
|
headerNbSymbolBits,
|
|
|
|
|
m_hasHeader,
|
|
|
|
|
m_hasCRC,
|
|
|
|
|
m_nbParityBits,
|
|
|
|
|
m_packetLength,
|
|
|
|
|
m_earlyEOM,
|
|
|
|
|
m_headerParityStatus,
|
|
|
|
|
m_headerCRCStatus,
|
|
|
|
|
m_payloadParityStatus,
|
|
|
|
|
m_payloadCRCStatus
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
MeshtasticDemodDecoderLoRa::getCodingMetrics(
|
|
|
|
|
m_nbSymbolBits,
|
|
|
|
|
headerNbSymbolBits,
|
|
|
|
|
m_nbParityBits,
|
|
|
|
|
m_packetLength,
|
|
|
|
|
m_hasHeader,
|
|
|
|
|
m_hasCRC,
|
|
|
|
|
m_nbSymbols,
|
|
|
|
|
m_nbCodewords
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const LoRaDecodeState softState = captureLoRaState(msgBytes);
|
|
|
|
|
|
|
|
|
|
// Soft path is canonical for gr-lora_sdr, but if this approximation misses CRC
|
|
|
|
|
// on noisy captures, retry hard decode once and keep whichever path validates.
|
|
|
|
|
if (m_hasCRC && !m_payloadCRCStatus)
|
2026-03-05 16:52:12 +01:00
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
QByteArray hardBytes;
|
|
|
|
|
decodeSymbols(msg.getSymbols(), hardBytes); // hard path updates decoder state
|
|
|
|
|
const LoRaDecodeState hardState = captureLoRaState(hardBytes);
|
2026-03-05 16:52:12 +01:00
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
if (hardState.payloadCRCStatus) {
|
|
|
|
|
restoreLoRaState(hardState);
|
2026-03-05 16:52:12 +01:00
|
|
|
} else {
|
2026-03-16 00:11:41 +01:00
|
|
|
restoreLoRaState(softState);
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-16 00:11:41 +01:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
decodeSymbols(msg.getSymbols(), msgBytes);
|
|
|
|
|
}
|
2026-03-05 16:52:12 +01:00
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
if (m_hasCRC && !m_payloadCRCStatus && (m_spreadFactor >= 5U))
|
|
|
|
|
{
|
|
|
|
|
const LoRaDecodeState baseState = captureLoRaState(msgBytes);
|
|
|
|
|
const unsigned int headerNbSymbolBits = (m_hasHeader && (m_spreadFactor > 2U))
|
|
|
|
|
? (m_spreadFactor - 2U)
|
|
|
|
|
: m_nbSymbolBits;
|
|
|
|
|
bool recovered = false;
|
|
|
|
|
|
|
|
|
|
for (int delta : {-1, 1})
|
2026-03-05 16:52:12 +01:00
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
std::vector<unsigned short> shifted = msg.getSymbols();
|
2026-03-05 16:52:12 +01:00
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
for (size_t i = 0; i < shifted.size(); i++)
|
2026-03-05 16:52:12 +01:00
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
const bool isHeader = m_hasHeader && (i < 8U);
|
|
|
|
|
const unsigned int bits = isHeader ? headerNbSymbolBits : m_nbSymbolBits;
|
|
|
|
|
const unsigned int mod = 1U << std::max(1U, bits);
|
|
|
|
|
const int s = static_cast<int>(shifted[i]);
|
|
|
|
|
const int v = (s + delta) % static_cast<int>(mod);
|
|
|
|
|
shifted[i] = static_cast<unsigned short>(v < 0 ? (v + static_cast<int>(mod)) : v);
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
QByteArray shiftedBytes;
|
|
|
|
|
decodeSymbols(shifted, shiftedBytes); // hard-path decode with adjusted symbol indices
|
|
|
|
|
const LoRaDecodeState shiftedState = captureLoRaState(shiftedBytes);
|
|
|
|
|
|
|
|
|
|
if (shiftedState.payloadCRCStatus)
|
|
|
|
|
{
|
|
|
|
|
restoreLoRaState(shiftedState);
|
|
|
|
|
recovered = true;
|
|
|
|
|
break;
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
if (!recovered) {
|
|
|
|
|
restoreLoRaState(baseState);
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 00:11:41 +01:00
|
|
|
qDebug(
|
|
|
|
|
"MeshtasticDemodDecoder::handleMessage: decode symbols=%zu bytes=%lld earlyEOM=%d hCRC=%d pCRC=%d hParity=%d pParity=%d",
|
|
|
|
|
msg.getSymbols().size(),
|
|
|
|
|
static_cast<long long>(msgBytes.size()),
|
|
|
|
|
m_earlyEOM ? 1 : 0,
|
|
|
|
|
m_headerCRCStatus ? 1 : 0,
|
|
|
|
|
m_payloadCRCStatus ? 1 : 0,
|
|
|
|
|
m_headerParityStatus,
|
|
|
|
|
m_payloadParityStatus
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (m_outputMessageQueue)
|
2026-03-05 16:52:12 +01:00
|
|
|
{
|
2026-03-16 00:11:41 +01:00
|
|
|
qDebug(
|
|
|
|
|
"MeshtasticDemodDecoder::handleMessage: push report ts=%s bytes=%lld pCRC=%d",
|
|
|
|
|
qPrintable(msgTimestamp),
|
|
|
|
|
static_cast<long long>(msgBytes.size()),
|
|
|
|
|
m_payloadCRCStatus ? 1 : 0
|
|
|
|
|
);
|
|
|
|
|
MeshtasticDemodMsg::MsgReportDecodeBytes *outputMsg = MeshtasticDemodMsg::MsgReportDecodeBytes::create(msgBytes);
|
|
|
|
|
outputMsg->setFrameId(msg.getFrameId());
|
|
|
|
|
outputMsg->setSyncWord(msgSyncWord);
|
|
|
|
|
outputMsg->setSignalDb(msgSignalDb);
|
|
|
|
|
outputMsg->setNoiseDb(msgNoiseDb);
|
|
|
|
|
outputMsg->setMsgTimestamp(msgTimestamp);
|
|
|
|
|
outputMsg->setPacketSize(getPacketLength());
|
|
|
|
|
outputMsg->setNbParityBits(getNbParityBits());
|
|
|
|
|
outputMsg->setHasCRC(getHasCRC());
|
|
|
|
|
outputMsg->setNbSymbols(getNbSymbols());
|
|
|
|
|
outputMsg->setNbCodewords(getNbCodewords());
|
|
|
|
|
outputMsg->setEarlyEOM(getEarlyEOM());
|
|
|
|
|
outputMsg->setHeaderParityStatus(getHeaderParityStatus());
|
|
|
|
|
outputMsg->setHeaderCRCStatus(getHeaderCRCStatus());
|
|
|
|
|
outputMsg->setPayloadParityStatus(getPayloadParityStatus());
|
|
|
|
|
outputMsg->setPayloadCRCStatus(getPayloadCRCStatus());
|
|
|
|
|
outputMsg->setPipelineMetadata(m_pipelineId, m_pipelineName, m_pipelinePreset);
|
|
|
|
|
outputMsg->setDechirpedSpectrum(msg.getDechirpedSpectrum());
|
|
|
|
|
m_outputMessageQueue->push(outputMsg);
|
2026-03-05 16:52:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MeshtasticDemodDecoder::handleInputMessages()
|
|
|
|
|
{
|
|
|
|
|
Message* message;
|
|
|
|
|
|
|
|
|
|
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
|
|
|
|
{
|
|
|
|
|
if (handleMessage(*message)) {
|
|
|
|
|
delete message;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|