Compare commits

...

6 Commits

Author SHA1 Message Date
Jonathan Bennett
3f56896b71 Point to upstream libch341 2024-12-20 17:07:34 -06:00
Jonathan Bennett
dc19431e9f Add include for musl compliance 2024-12-20 17:07:03 -06:00
Jonathan Bennett
c397b2fed4 Trunk 2024-12-20 17:06:37 -06:00
Jonathan Bennett
615a55ea13
Merge branch 'master' into ch341 2024-12-20 17:02:14 -06:00
Eric Severance
658459aaf3
Use encoded ServiceEnvelope in mqttQueue (#5619) 2024-12-20 14:59:23 -06:00
Eric Severance
e1de439a7f
Remove unnecessary memcpy for PKI crypto (#5608)
Some checks failed
CI / check (push) Blocked by required conditions
CI / build-esp32 (push) Blocked by required conditions
CI / build-esp32-s3 (push) Blocked by required conditions
CI / build-esp32-c3 (push) Blocked by required conditions
CI / build-esp32-c6 (push) Blocked by required conditions
CI / build-nrf52 (push) Blocked by required conditions
CI / build-rpi2040 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / package-raspbian (push) Waiting to run
CI / package-raspbian-armv7l (push) Waiting to run
CI / package-native (push) Waiting to run
CI / build-docker (push) Waiting to run
CI / after-checks (push) Blocked by required conditions
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
Flawfinder Scan / Flawfinder (push) Waiting to run
Semgrep Full Scan / semgrep-full (push) Has been cancelled
* Remove unnecessary memcpy for PKI crypto

* Update comment s/packet_id/id/

* Create a copy of bytes for each channel decrypt

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2024-12-19 19:14:27 -06:00
8 changed files with 160 additions and 161 deletions

View File

@ -26,7 +26,7 @@ lib_deps =
${radiolib_base.lib_deps} ${radiolib_base.lib_deps}
rweather/Crypto@^0.4.0 rweather/Crypto@^0.4.0
https://github.com/lovyan03/LovyanGFX.git#1401c28a47646fe00538d487adcb2eb3c72de805 https://github.com/lovyan03/LovyanGFX.git#1401c28a47646fe00538d487adcb2eb3c72de805
https://github.com/jp-bennett/libch341-spi-userspace#3b9aa09e6496ffdfce4011528697d482e29b4de0 https://github.com/pine64/libch341-spi-userspace#8695637adeabf5abf5601d8e82cb0ba19ce9ec46
build_flags = build_flags =
${arduino_base.build_flags} ${arduino_base.build_flags}

View File

@ -245,7 +245,7 @@ void setup()
// GPIO10 manages all peripheral power supplies // GPIO10 manages all peripheral power supplies
// Turn on peripheral power immediately after MUC starts. // Turn on peripheral power immediately after MUC starts.
// If some boards are turned on late, ESP32 will reset due to low voltage. // If some boards are turned on late, ESP32 will reset due to low voltage.
// ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
pinMode(KB_POWERON, OUTPUT); pinMode(KB_POWERON, OUTPUT);
digitalWrite(KB_POWERON, HIGH); digitalWrite(KB_POWERON, HIGH);
@ -424,7 +424,6 @@ void setup()
digitalWrite(AQ_SET_PIN, HIGH); digitalWrite(AQ_SET_PIN, HIGH);
#endif #endif
// Currently only the tbeam has a PMU // Currently only the tbeam has a PMU
// PMU initialization needs to be placed before i2c scanning // PMU initialization needs to be placed before i2c scanning
power = new Power(); power = new Power();

View File

@ -58,10 +58,16 @@ void CryptoEngine::clearKeys()
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256 * Encrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node. * for a specific node.
* *
* @param bytes is updated in place * @param toNode The MeshPacket `to` field.
* @param fromNode The MeshPacket `from` field.
* @param remotePublic The remote node's Curve25519 public key.
* @param packetId The MeshPacket `id` field.
* @param numBytes Number of bytes of plaintext in the bytes buffer.
* @param bytes Buffer containing plaintext input.
* @param bytesOut Output buffer to be populated with encrypted ciphertext.
*/ */
bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
{ {
uint8_t *auth; uint8_t *auth;
long extraNonceTmp = random(); long extraNonceTmp = random();
@ -93,14 +99,18 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
* Decrypt a packet's payload using a key generated with Curve25519 and SHA256 * Decrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node. * for a specific node.
* *
* @param bytes is updated in place * @param fromNode The MeshPacket `from` field.
* @param remotePublic The remote node's Curve25519 public key.
* @param packetId The MeshPacket `id` field.
* @param numBytes Number of bytes of ciphertext in the bytes buffer.
* @param bytes Buffer containing ciphertext input.
* @param bytesOut Output buffer to be populated with decrypted plaintext.
*/ */
bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum, bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
size_t numBytes, uint8_t *bytes, uint8_t *bytesOut) size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
{ {
uint8_t *auth; // set to last 8 bytes of text? const uint8_t *auth = bytes + numBytes - 12; // set to last 8 bytes of text?
uint32_t extraNonce; // pointer was not really used uint32_t extraNonce; // pointer was not really used
auth = bytes + numBytes - 12;
memcpy(&extraNonce, auth + 8, memcpy(&extraNonce, auth + 8,
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8); sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8);
LOG_INFO("Random nonce value: %d", extraNonce); LOG_INFO("Random nonce value: %d", extraNonce);

View File

@ -40,9 +40,9 @@ class CryptoEngine
void clearKeys(); void clearKeys();
void setDHPrivateKey(uint8_t *_private_key); void setDHPrivateKey(uint8_t *_private_key);
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum, virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
size_t numBytes, uint8_t *bytes, uint8_t *bytesOut); size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
virtual bool setDHPublicKey(uint8_t *publicKey); virtual bool setDHPublicKey(uint8_t *publicKey);
virtual void hash(uint8_t *bytes, size_t numBytes); virtual void hash(uint8_t *bytes, size_t numBytes);

View File

@ -37,7 +37,6 @@ static MemoryDynamic<meshtastic_MeshPacket> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool; Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__)); static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
/** /**
* Constructor * Constructor
@ -327,9 +326,6 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
} }
bool decrypted = false; bool decrypted = false;
ChannelIndex chIndex = 0; ChannelIndex chIndex = 0;
memcpy(bytes, p->encrypted.bytes,
rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize);
#if !(MESHTASTIC_EXCLUDE_PKI) #if !(MESHTASTIC_EXCLUDE_PKI)
// Attempt PKI decryption first // Attempt PKI decryption first
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr && if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
@ -337,7 +333,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
rawSize > MESHTASTIC_PKC_OVERHEAD) { rawSize > MESHTASTIC_PKC_OVERHEAD) {
LOG_DEBUG("Attempt PKI decryption"); LOG_DEBUG("Attempt PKI decryption");
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, ScratchEncrypted, if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, p->encrypted.bytes,
bytes)) { bytes)) {
LOG_INFO("PKI Decryption worked!"); LOG_INFO("PKI Decryption worked!");
memset(&p->decoded, 0, sizeof(p->decoded)); memset(&p->decoded, 0, sizeof(p->decoded));
@ -349,8 +345,6 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
p->pki_encrypted = true; p->pki_encrypted = true;
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32); memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
p->public_key.size = 32; p->public_key.size = 32;
// memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers
// chIndex = 8;
} else { } else {
LOG_ERROR("PKC Decrypted, but pb_decode failed!"); LOG_ERROR("PKC Decrypted, but pb_decode failed!");
return false; return false;
@ -367,6 +361,9 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
for (chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) { for (chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) {
// Try to use this hash/channel pair // Try to use this hash/channel pair
if (channels.decryptForHash(chIndex, p->channel)) { if (channels.decryptForHash(chIndex, p->channel)) {
// we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf. Create a
// fresh copy for each decrypt attempt.
memcpy(bytes, p->encrypted.bytes, rawSize);
// Try to decrypt the packet if we can // Try to decrypt the packet if we can
crypto->decrypt(p->from, p->id, rawSize, bytes); crypto->decrypt(p->from, p->id, rawSize, bytes);
@ -515,9 +512,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
*node->user.public_key.bytes); *node->user.public_key.bytes);
return meshtastic_Routing_Error_PKI_FAILED; return meshtastic_Routing_Error_PKI_FAILED;
} }
crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, ScratchEncrypted); crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, p->encrypted.bytes);
numbytes += MESHTASTIC_PKC_OVERHEAD; numbytes += MESHTASTIC_PKC_OVERHEAD;
memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes);
p->channel = 0; p->channel = 0;
p->pki_encrypted = true; p->pki_encrypted = true;
} else { } else {

View File

@ -24,6 +24,7 @@
#include <Throttle.h> #include <Throttle.h>
#include <assert.h> #include <assert.h>
#include <pb_decode.h> #include <pb_decode.h>
#include <utility>
MQTT *mqtt; MQTT *mqtt;
@ -31,10 +32,6 @@ namespace
{ {
constexpr int reconnectMax = 5; constexpr int reconnectMax = 5;
static MemoryDynamic<meshtastic_ServiceEnvelope> staticMqttPool;
Allocator<meshtastic_ServiceEnvelope> &mqttPool = staticMqttPool;
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets // FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
@ -528,39 +525,37 @@ void MQTT::publishNodeInfo()
} }
void MQTT::publishQueuedMessages() void MQTT::publishQueuedMessages()
{ {
if (!mqttQueue.isEmpty()) { if (mqttQueue.isEmpty())
LOG_DEBUG("Publish enqueued MQTT message"); return;
meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0);
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env);
std::string topic;
if (env->packet->pki_encrypted) {
topic = cryptTopic + "PKI/" + owner.id;
} else {
topic = cryptTopic + env->channel_id + "/" + owner.id;
}
LOG_INFO("publish %s, %u bytes from queue", topic.c_str(), numBytes);
publish(topic.c_str(), bytes, numBytes, false); LOG_DEBUG("Publish enqueued MQTT message");
const std::unique_ptr<QueueEntry> entry(mqttQueue.dequeuePtr(0));
LOG_INFO("publish %s, %u bytes from queue", entry->topic.c_str(), entry->envBytes.size());
publish(entry->topic.c_str(), entry->envBytes.data(), entry->envBytes.size(), false);
#if !defined(ARCH_NRF52) || \ #if !defined(ARCH_NRF52) || \
defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
if (moduleConfig.mqtt.json_enabled) { if (!moduleConfig.mqtt.json_enabled)
// handle json topic return;
auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet);
if (jsonString.length() != 0) { // handle json topic
std::string topicJson; const DecodedServiceEnvelope env(entry->envBytes.data(), entry->envBytes.size());
if (env->packet->pki_encrypted) { if (!env.validDecode || env.packet == NULL || env.channel_id == NULL)
topicJson = jsonTopic + "PKI/" + owner.id; return;
} else {
topicJson = jsonTopic + env->channel_id + "/" + owner.id; auto jsonString = MeshPacketSerializer::JsonSerialize(env.packet);
} if (jsonString.length() == 0)
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str()); return;
publish(topicJson.c_str(), jsonString.c_str(), false);
} std::string topicJson;
} if (env.packet->pki_encrypted) {
#endif // ARCH_NRF52 NRF52_USE_JSON topicJson = jsonTopic + "PKI/" + owner.id;
mqttPool.release(env); } else {
topicJson = jsonTopic + env.channel_id + "/" + owner.id;
} }
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
#endif // ARCH_NRF52 NRF52_USE_JSON
} }
void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex) void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex)
@ -599,59 +594,56 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
// Either encrypted packet (we couldn't decrypt) is marked as pki_encrypted, or we could decode the PKI encrypted packet // Either encrypted packet (we couldn't decrypt) is marked as pki_encrypted, or we could decode the PKI encrypted packet
bool isPKIEncrypted = mp_encrypted.pki_encrypted || mp_decoded.pki_encrypted; bool isPKIEncrypted = mp_encrypted.pki_encrypted || mp_decoded.pki_encrypted;
// If it was to a channel, check uplink enabled, else must be pki_encrypted // If it was to a channel, check uplink enabled, else must be pki_encrypted
if ((ch.settings.uplink_enabled && !isPKIEncrypted) || isPKIEncrypted) { if (!(ch.settings.uplink_enabled || isPKIEncrypted))
const char *channelId = isPKIEncrypted ? "PKI" : channels.getGlobalId(chIndex); return;
const char *channelId = isPKIEncrypted ? "PKI" : channels.getGlobalId(chIndex);
meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed(); LOG_DEBUG("MQTT onSend - Publish ");
env->channel_id = (char *)channelId; const meshtastic_MeshPacket *p;
env->gateway_id = owner.id; if (moduleConfig.mqtt.encryption_enabled) {
p = &mp_encrypted;
LOG_DEBUG("encrypted message");
} else if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
p = &mp_decoded;
LOG_DEBUG("portnum %i message", mp_decoded.decoded.portnum);
} else {
LOG_DEBUG("nothing, pkt not decrypted");
return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
}
LOG_DEBUG("MQTT onSend - Publish "); const meshtastic_ServiceEnvelope env = {
if (moduleConfig.mqtt.encryption_enabled) { .packet = const_cast<meshtastic_MeshPacket *>(p), .channel_id = const_cast<char *>(channelId), .gateway_id = owner.id};
env->packet = (meshtastic_MeshPacket *)&mp_encrypted; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env);
LOG_DEBUG("encrypted message"); std::string topic = cryptTopic + channelId + "/" + owner.id;
} else if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
env->packet = (meshtastic_MeshPacket *)&mp_decoded;
LOG_DEBUG("portnum %i message", env->packet->decoded.portnum);
} else {
LOG_DEBUG("nothing, pkt not decrypted");
mqttPool.release(env);
return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
}
if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env); LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
std::string topic = cryptTopic + channelId + "/" + owner.id; publish(topic.c_str(), bytes, numBytes, false);
LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
publish(topic.c_str(), bytes, numBytes, false);
#if !defined(ARCH_NRF52) || \ #if !defined(ARCH_NRF52) || \
defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ### defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
if (moduleConfig.mqtt.json_enabled) { if (!moduleConfig.mqtt.json_enabled)
// handle json topic return;
auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded); // handle json topic
if (jsonString.length() != 0) { auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded);
std::string topicJson = jsonTopic + channelId + "/" + owner.id; if (jsonString.length() == 0)
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), return;
jsonString.c_str()); std::string topicJson = jsonTopic + channelId + "/" + owner.id;
publish(topicJson.c_str(), jsonString.c_str(), false); LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
} publish(topicJson.c_str(), jsonString.c_str(), false);
}
#endif // ARCH_NRF52 NRF52_USE_JSON #endif // ARCH_NRF52 NRF52_USE_JSON
} else {
LOG_INFO("MQTT not connected, queue packet");
QueueEntry *entry;
if (mqttQueue.numFree() == 0) {
LOG_WARN("MQTT queue is full, discard oldest");
entry = mqttQueue.dequeuePtr(0);
} else { } else {
LOG_INFO("MQTT not connected, queue packet"); entry = new QueueEntry;
if (mqttQueue.numFree() == 0) {
LOG_WARN("MQTT queue is full, discard oldest");
meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0);
if (d)
mqttPool.release(d);
}
// make a copy of serviceEnvelope and queue it
meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env);
assert(mqttQueue.enqueue(copied, 0));
} }
mqttPool.release(env); entry->topic = std::move(topic);
entry->envBytes.assign(bytes, numBytes);
assert(mqttQueue.enqueue(entry, 0));
} }
} }
@ -660,73 +652,70 @@ void MQTT::perhapsReportToMap()
if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly())) if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
return; return;
if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs)) { if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs))
return; return;
} else {
if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
last_report_to_map = millis();
if (map_position_precision == 0)
LOG_WARN("MQTT Map report enabled, but precision is 0");
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
LOG_WARN("MQTT Map report enabled, but no position available");
return;
}
// Allocate ServiceEnvelope and fill it if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed();
se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id
se->gateway_id = owner.id;
// Allocate MeshPacket and fill it
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
mp->from = nodeDB->getNodeNum();
mp->to = NODENUM_BROADCAST;
mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
// Fill MapReport message
meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
mapReport.role = config.device.role;
mapReport.hw_model = owner.hw_model;
strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
mapReport.region = config.lora.region;
mapReport.modem_preset = config.lora.modem_preset;
mapReport.has_default_channel = channels.hasDefaultChannel();
// Set position with precision (same as in PositionModule)
if (map_position_precision < 32 && map_position_precision > 0) {
mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.latitude_i += (1 << (31 - map_position_precision));
mapReport.longitude_i += (1 << (31 - map_position_precision));
} else {
mapReport.latitude_i = localPosition.latitude_i;
mapReport.longitude_i = localPosition.longitude_i;
}
mapReport.altitude = localPosition.altitude;
mapReport.position_precision = map_position_precision;
mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
// Encode MapReport message and set it to MeshPacket in ServiceEnvelope
mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes),
&meshtastic_MapReport_msg, &mapReport);
se->packet = mp;
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se);
LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
publish(mapTopic.c_str(), bytes, numBytes, false);
// Release the allocated memory for ServiceEnvelope and MeshPacket
mqttPool.release(se);
packetPool.release(mp);
// Update the last report time
last_report_to_map = millis(); last_report_to_map = millis();
if (map_position_precision == 0)
LOG_WARN("MQTT Map report enabled, but precision is 0");
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
LOG_WARN("MQTT Map report enabled, but no position available");
return;
} }
// Allocate MeshPacket and fill it
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
mp->from = nodeDB->getNodeNum();
mp->to = NODENUM_BROADCAST;
mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
// Fill MapReport message
meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
mapReport.role = config.device.role;
mapReport.hw_model = owner.hw_model;
strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
mapReport.region = config.lora.region;
mapReport.modem_preset = config.lora.modem_preset;
mapReport.has_default_channel = channels.hasDefaultChannel();
// Set position with precision (same as in PositionModule)
if (map_position_precision < 32 && map_position_precision > 0) {
mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.latitude_i += (1 << (31 - map_position_precision));
mapReport.longitude_i += (1 << (31 - map_position_precision));
} else {
mapReport.latitude_i = localPosition.latitude_i;
mapReport.longitude_i = localPosition.longitude_i;
}
mapReport.altitude = localPosition.altitude;
mapReport.position_precision = map_position_precision;
mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
// Encode MapReport message into the MeshPacket
mp->decoded.payload.size =
pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport);
// Encode the MeshPacket into a binary ServiceEnvelope and publish
const meshtastic_ServiceEnvelope se = {
.packet = mp,
.channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id
.gateway_id = owner.id};
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se);
LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
publish(mapTopic.c_str(), bytes, numBytes, false);
// Release the allocated memory for MeshPacket
packetPool.release(mp);
// Update the last report time
last_report_to_map = millis();
} }
bool MQTT::isPrivateIpAddress(const char address[]) bool MQTT::isPrivateIpAddress(const char address[])

View File

@ -78,7 +78,11 @@ class MQTT : private concurrency::OSThread
void start() { setIntervalFromNow(0); }; void start() { setIntervalFromNow(0); };
protected: protected:
PointerQueue<meshtastic_ServiceEnvelope> mqttQueue; struct QueueEntry {
std::string topic;
std::basic_string<uint8_t> envBytes; // binary/pb_encode_to_bytes ServiceEnvelope
};
PointerQueue<QueueEntry> mqttQueue;
int reconnectCount = 0; int reconnectCount = 0;

View File

@ -6,6 +6,7 @@
#include <RadioLib.h> #include <RadioLib.h>
#include <csignal> #include <csignal>
#include <libpinedio-usb.h> #include <libpinedio-usb.h>
#include <unistd.h>
// include the library for Raspberry GPIO pins // include the library for Raspberry GPIO pins