Merge branch 'develop' into mqtt_buffer_protection

This commit is contained in:
Ben Meadors 2025-10-13 06:56:54 -05:00 committed by GitHub
commit b1e00613d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 25 deletions

View File

@ -72,6 +72,7 @@ void PhoneAPI::handleStartConfig()
LOG_INFO("Start API client config"); LOG_INFO("Start API client config");
nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
nodeInfoQueue.clear();
resetReadIndex(); resetReadIndex();
} }
@ -94,6 +95,7 @@ void PhoneAPI::close()
fromRadioScratch = {}; fromRadioScratch = {};
toRadioScratch = {}; toRadioScratch = {};
nodeInfoForPhone = {}; nodeInfoForPhone = {};
nodeInfoQueue.clear();
packetForPhone = NULL; packetForPhone = NULL;
filesManifest.clear(); filesManifest.clear();
fromRadioNum = 0; fromRadioNum = 0;
@ -431,17 +433,25 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break; break;
case STATE_SEND_OTHER_NODEINFOS: { case STATE_SEND_OTHER_NODEINFOS: {
LOG_DEBUG("Send known nodes");
if (nodeInfoForPhone.num == 0 && !nodeInfoQueue.empty()) {
// Serve the next cached node without re-reading from the DB iterator.
nodeInfoForPhone = nodeInfoQueue.front();
nodeInfoQueue.pop_front();
}
if (nodeInfoForPhone.num != 0) { if (nodeInfoForPhone.num != 0) {
// Just in case we stored a different user.id in the past, but should never happen going forward // Just in case we stored a different user.id in the past, but should never happen going forward
sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num); sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num);
LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard, LOG_DEBUG("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name); nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
fromRadioScratch.node_info = nodeInfoForPhone; fromRadioScratch.node_info = nodeInfoForPhone;
// Stay in current state until done sending nodeinfos nodeInfoForPhone = {};
nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time prefetchNodeInfos();
} else { } else {
LOG_DEBUG("Done sending nodeinfo"); LOG_DEBUG("Done sending nodeinfo");
nodeInfoQueue.clear();
state = STATE_SEND_FILEMANIFEST; state = STATE_SEND_FILEMANIFEST;
// Go ahead and send that ID right now // Go ahead and send that ID right now
return getFromRadio(buf); return getFromRadio(buf);
@ -545,6 +555,30 @@ void PhoneAPI::releaseQueueStatusPhonePacket()
} }
} }
void PhoneAPI::prefetchNodeInfos()
{
bool added = false;
// Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
while (nodeInfoQueue.size() < kNodePrefetchDepth) {
auto nextNode = nodeDB->readNextMeshNode(readIndex);
if (!nextNode)
break;
auto info = TypeConversions::ConvertToNodeInfo(nextNode);
bool isUs = info.num == nodeDB->getNodeNum();
info.hops_away = isUs ? 0 : info.hops_away;
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
info.snr = isUs ? 0 : info.snr;
info.via_mqtt = isUs ? false : info.via_mqtt;
info.is_favorite = info.is_favorite || isUs;
nodeInfoQueue.push_back(info);
added = true;
}
if (added)
onNowHasData(0);
}
void PhoneAPI::releaseMqttClientProxyPhonePacket() void PhoneAPI::releaseMqttClientProxyPhonePacket()
{ {
if (mqttClientProxyMessageForPhone) { if (mqttClientProxyMessageForPhone) {
@ -581,20 +615,8 @@ bool PhoneAPI::available()
return true; return true;
case STATE_SEND_OTHER_NODEINFOS: case STATE_SEND_OTHER_NODEINFOS:
if (nodeInfoForPhone.num == 0) { if (nodeInfoQueue.empty())
auto nextNode = nodeDB->readNextMeshNode(readIndex); prefetchNodeInfos();
if (nextNode) {
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(nextNode);
bool isUs = nodeInfoForPhone.num == nodeDB->getNodeNum();
nodeInfoForPhone.hops_away = isUs ? 0 : nodeInfoForPhone.hops_away;
nodeInfoForPhone.last_heard = isUs ? getValidTime(RTCQualityFromNet) : nodeInfoForPhone.last_heard;
nodeInfoForPhone.snr = isUs ? 0 : nodeInfoForPhone.snr;
nodeInfoForPhone.via_mqtt = isUs ? false : nodeInfoForPhone.via_mqtt;
nodeInfoForPhone.is_favorite = nodeInfoForPhone.is_favorite || isUs; // Our node is always a favorite
onNowHasData(0);
}
}
return true; // Always say we have something, because we might need to advance our state machine return true; // Always say we have something, because we might need to advance our state machine
case STATE_SEND_PACKETS: { case STATE_SEND_PACKETS: {
if (!queueStatusPacketForPhone) if (!queueStatusPacketForPhone)

View File

@ -3,6 +3,7 @@
#include "Observer.h" #include "Observer.h"
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
#include "meshtastic/portnums.pb.h" #include "meshtastic/portnums.pb.h"
#include <deque>
#include <iterator> #include <iterator>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@ -79,6 +80,10 @@ class PhoneAPI
/// We temporarily keep the nodeInfo here between the call to available and getFromRadio /// We temporarily keep the nodeInfo here between the call to available and getFromRadio
meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default; meshtastic_NodeInfo nodeInfoForPhone = meshtastic_NodeInfo_init_default;
// Prefetched node info entries ready for immediate transmission to the phone.
std::deque<meshtastic_NodeInfo> nodeInfoQueue;
// Tunable size of the node info cache so we can keep BLE reads non-blocking.
static constexpr size_t kNodePrefetchDepth = 4;
meshtastic_ToRadio toRadioScratch = { meshtastic_ToRadio toRadioScratch = {
0}; // this is a static scratch object, any data must be copied elsewhere before returning 0}; // this is a static scratch object, any data must be copied elsewhere before returning
@ -158,6 +163,8 @@ class PhoneAPI
void releaseQueueStatusPhonePacket(); void releaseQueueStatusPhonePacket();
void prefetchNodeInfos();
void releaseMqttClientProxyPhonePacket(); void releaseMqttClientProxyPhonePacket();
void releaseClientNotification(); void releaseClientNotification();

View File

@ -48,6 +48,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
uint8_t queue_size = 0; uint8_t queue_size = 0;
uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0}; uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
size_t numBytes = 0; size_t numBytes = 0;
bool hasChecked = false;
bool phoneWants = false;
protected: protected:
virtual int32_t runOnce() override virtual int32_t runOnce() override
@ -60,7 +62,11 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
LOG_DEBUG("Queue_size %u", queue_size); LOG_DEBUG("Queue_size %u", queue_size);
queue_size = 0; queue_size = 0;
} }
// Note: phoneWants/hasChecked logic removed since onRead() handles getFromRadio() directly if (!hasChecked && phoneWants) {
// Pull fresh data while we're outside of the NimBLE callback context.
numBytes = getFromRadio(fromRadioBytes);
hasChecked = true;
}
// the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
return INT32_MAX; return INT32_MAX;
@ -117,6 +123,8 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
bluetoothPhoneAPI->queue_size++; bluetoothPhoneAPI->queue_size++;
bluetoothPhoneAPI->setIntervalFromNow(0); bluetoothPhoneAPI->setIntervalFromNow(0);
} }
} else {
LOG_DEBUG("Drop duplicate ToRadio packet (%u bytes)", val.length());
} }
} }
}; };
@ -129,17 +137,32 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
virtual void onRead(NimBLECharacteristic *pCharacteristic) virtual void onRead(NimBLECharacteristic *pCharacteristic)
#endif #endif
{ {
bluetoothPhoneAPI->phoneWants = true;
bluetoothPhoneAPI->setIntervalFromNow(0);
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex); std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
// Get fresh data immediately when client reads if (!bluetoothPhoneAPI->hasChecked) {
bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes); // Fetch payload on demand; prefetch keeps this fast for the first read.
bluetoothPhoneAPI->numBytes = bluetoothPhoneAPI->getFromRadio(bluetoothPhoneAPI->fromRadioBytes);
bluetoothPhoneAPI->hasChecked = true;
}
// Set the characteristic value with whatever data we have
pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes); pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
if (bluetoothPhoneAPI->numBytes != 0) {
#ifdef NIMBLE_TWO
// Notify immediately so subscribed clients see the packet without an extra read.
pCharacteristic->notify(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes, BLE_HS_CONN_HANDLE_NONE);
#else
pCharacteristic->notify();
#endif
}
if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
bluetoothPhoneAPI->setIntervalFromNow(0); bluetoothPhoneAPI->setIntervalFromNow(0);
bluetoothPhoneAPI->numBytes = 0; bluetoothPhoneAPI->numBytes = 0;
bluetoothPhoneAPI->hasChecked = false;
bluetoothPhoneAPI->phoneWants = false;
} }
}; };
@ -271,6 +294,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
bluetoothPhoneAPI->close(); bluetoothPhoneAPI->close();
bluetoothPhoneAPI->numBytes = 0; bluetoothPhoneAPI->numBytes = 0;
bluetoothPhoneAPI->queue_size = 0; bluetoothPhoneAPI->queue_size = 0;
bluetoothPhoneAPI->hasChecked = false;
bluetoothPhoneAPI->phoneWants = false;
} }
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
@ -278,6 +303,15 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
#ifdef NIMBLE_TWO #ifdef NIMBLE_TWO
// Restart Advertising // Restart Advertising
ble->startAdvertising(); ble->startAdvertising();
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
if (!pAdvertising->start(0)) {
if (pAdvertising->isAdvertising()) {
LOG_DEBUG("BLE advertising already running");
} else {
LOG_ERROR("BLE failed to restart advertising");
}
}
#endif #endif
} }
}; };
@ -401,15 +435,18 @@ void NimbleBluetooth::setupService()
// Define the characteristics that the app is looking for // Define the characteristics that the app is looking for
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE); ToRadioCharacteristic = bleService->createCharacteristic(TORADIO_UUID, NIMBLE_PROPERTY::WRITE);
FromRadioCharacteristic = bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ); // Allow notifications so phones can stream FromRadio without polling.
FromRadioCharacteristic =
bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); fromNumCharacteristic = bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ);
logRadioCharacteristic = logRadioCharacteristic =
bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U); bleService->createCharacteristic(LOGRADIO_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ, 512U);
} else { } else {
ToRadioCharacteristic = bleService->createCharacteristic( ToRadioCharacteristic = bleService->createCharacteristic(
TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC); TORADIO_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_AUTHEN | NIMBLE_PROPERTY::WRITE_ENC);
FromRadioCharacteristic = bleService->createCharacteristic( FromRadioCharacteristic =
FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); bleService->createCharacteristic(FROMRADIO_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_AUTHEN |
NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::NOTIFY);
fromNumCharacteristic = fromNumCharacteristic =
bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ | bleService->createCharacteristic(FROMNUM_UUID, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC); NIMBLE_PROPERTY::READ_AUTHEN | NIMBLE_PROPERTY::READ_ENC);