diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 952a6d2be37..b07a3bd47b1 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -110,7 +110,10 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp) } printPacket("Forwarding to phone", mp); - sendToPhone(packetPool.allocCopy(*mp)); + if (auto *c = packetPool.allocCopy(*mp)) + sendToPhone(c); + else + LOG_DEBUG("phone-forward dropped: packetPool exhausted"); return 0; } @@ -205,7 +208,10 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p) DEBUG_HEAP_BEFORE; auto a = packetPool.allocCopy(p); DEBUG_HEAP_AFTER("MeshService::handleToRadio", a); - sendToMesh(a, RX_SRC_USER); + if (a) + sendToMesh(a, RX_SRC_USER); + else + LOG_WARN("handleToRadio: packetPool exhausted, dropping ToRadio packet"); bool loopback = false; // if true send any packet the phone sends back itself (for testing) if (loopback) { @@ -225,6 +231,10 @@ bool MeshService::cancelSending(PacketId id) ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id) { meshtastic_QueueStatus *copied = queueStatusPool.allocCopy(qs); + if (!copied) { + LOG_DEBUG("sendQueueStatusToPhone: queueStatusPool exhausted, skipping"); + return ERRNO_UNKNOWN; + } copied->res = res; copied->mesh_packet_id = mesh_packet_id; @@ -265,7 +275,12 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh auto a = packetPool.allocCopy(*p); DEBUG_HEAP_AFTER("MeshService::sendToMesh", a); - sendToPhone(a); + if (a) + sendToPhone(a); + // else: packetPool exhausted; the cc-to-phone copy is dropped here. + // The original packet is still queued to the mesh; phone visibility + // of cc'd local sends depends on whether the RX path forwards it, + // which is not guaranteed for non-broadcast / non-local-dest sends. } // Router may ask us to release the packet if it wasn't sent diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index e8613d45729..c58a5c306de 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -31,8 +31,11 @@ ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p) // If it's from us, ReliableRouter already handles retransmissions if want_ack is set. If a next hop is set and hop limit is // not 0 or want_ack is set, start retransmissions - if ((!isFromUs(p) || !p->want_ack) && p->next_hop != NO_NEXT_HOP_PREFERENCE && (p->hop_limit > 0 || p->want_ack)) - startRetransmission(packetPool.allocCopy(*p)); // start retransmission for relayed packet + if ((!isFromUs(p) || !p->want_ack) && p->next_hop != NO_NEXT_HOP_PREFERENCE && (p->hop_limit > 0 || p->want_ack)) { + if (auto *retxCopy = packetPool.allocCopy(*p)) + startRetransmission(retxCopy); // start retransmission for relayed packet + // else: pool exhausted; relayed packet is still sent below — just no retransmission + } return Router::send(p); } @@ -146,6 +149,10 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) if (isRebroadcaster()) { if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) { meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + if (!tosend) { + LOG_WARN("Rebroadcast skipped: packetPool exhausted (from=%x)", p->relay_node); + return false; + } LOG_INFO("Rebroadcast received message coming from %x", p->relay_node); // If exhausting hops, force hop_limit = 0 regardless of other logic @@ -319,14 +326,14 @@ int32_t NextHopRouter::doRetransmissions() LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to); sentTo->next_hop = NO_NEXT_HOP_PREFERENCE; } - FloodingRouter::send(packetPool.allocCopy(*p.packet)); + if (auto *c = packetPool.allocCopy(*p.packet)) FloodingRouter::send(c); } else { - NextHopRouter::send(packetPool.allocCopy(*p.packet)); + if (auto *c = packetPool.allocCopy(*p.packet)) NextHopRouter::send(c); } } else { // Note: we call the superclass version because we don't want to have our version of send() add a new // retransmission record - FloodingRouter::send(packetPool.allocCopy(*p.packet)); + if (auto *c = packetPool.allocCopy(*p.packet)) FloodingRouter::send(c); } // Queue again diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 42c24c783f6..d1571b422bb 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -21,7 +21,9 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p) auto copy = packetPool.allocCopy(*p); DEBUG_HEAP_AFTER("ReliableRouter::send", copy); - startRetransmission(copy, NUM_RELIABLE_RETX); + if (copy) + startRetransmission(copy, NUM_RELIABLE_RETX); + // else: pool exhausted; the packet itself is still sent — just no retry } /* If we have pending retransmissions, add the airtime of this packet to it, because during that time we cannot receive an diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index ffeb7c5393d..ca686da4cb0 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -384,12 +384,15 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) return encodeResult; // FIXME - this isn't a valid ErrorCode } #if !MESHTASTIC_EXCLUDE_MQTT - // Only publish to MQTT if we're the original transmitter of the packet - if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) { + // Only publish to MQTT if we're the original transmitter of the packet. + // Skip on packetPool exhaustion (p_decoded == nullptr) — onSend would + // dereference the reference and crash; better to drop the MQTT publish + // for this one packet than panic the radio. + if (p_decoded && moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) { mqtt->onSend(*p, *p_decoded, chIndex); } #endif - packetPool.release(p_decoded); + packetPool.release(p_decoded); // release() handles nullptr } #if HAS_UDP_MULTICAST diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index bddb6276bb3..f86322f1d3f 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -197,8 +197,12 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast } } auto decompressedCopy = packetPool.allocCopy(mp); + if (!decompressedCopy) { + LOG_DEBUG("AtakPluginModule: packetPool exhausted, skipping decompressed phone forward"); + return; + } decompressedCopy->decoded.payload.size = - pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload), + pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload.bytes), meshtastic_TAKPacket_fields, &uncompressed); service->sendToPhone(decompressedCopy); diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 4de4792416a..a29aeef9d93 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -61,12 +61,15 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes // if user has changed while packet was not for us, inform phone if (hasChanged && !wasBroadcast && !isToUs(&mp)) { auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis + if (packetCopy) { + // Re-encode the user protobuf, as we have stripped out the user.id + packetCopy->decoded.payload.size = pb_encode_to_bytes( + packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p); - // Re-encode the user protobuf, as we have stripped out the user.id - packetCopy->decoded.payload.size = pb_encode_to_bytes( - packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p); - - service->sendToPhone(packetCopy); + service->sendToPhone(packetCopy); + } else { + LOG_DEBUG("NodeInfoModule: packetPool exhausted, skipping phone forward"); + } } pruneLastNodeInfoCache();