mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-08 06:02:05 +00:00
277 lines
12 KiB
C++
277 lines
12 KiB
C++
#include "NextHopRouter.h"
|
|
|
|
NextHopRouter::NextHopRouter() {}
|
|
|
|
PendingPacket::PendingPacket(meshtastic_MeshPacket *p, uint8_t numRetransmissions)
|
|
{
|
|
packet = p;
|
|
this->numRetransmissions = numRetransmissions - 1; // We subtract one, because we assume the user just did the first send
|
|
}
|
|
|
|
/**
|
|
* Send a packet
|
|
*/
|
|
ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
|
|
{
|
|
// Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see)
|
|
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
|
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
|
|
|
p->next_hop = getNextHop(p->to, p->relay_node); // set the next hop
|
|
LOG_DEBUG("Setting next hop for packet with dest %x to %x", p->to, p->next_hop);
|
|
|
|
// 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
|
|
|
|
return Router::send(p);
|
|
}
|
|
|
|
bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
|
{
|
|
bool wasFallback = false;
|
|
bool weWereNextHop = false;
|
|
if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
|
|
printPacket("Ignore dupe incoming msg", p);
|
|
rxDupe++;
|
|
stopRetransmission(p->from, p->id);
|
|
|
|
// If it was a fallback to flooding, try to relay again
|
|
if (wasFallback) {
|
|
LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node);
|
|
// Check if it's still in the Tx queue, if not, we have to relay it again
|
|
if (!findInTxQueue(p->from, p->id))
|
|
perhapsRelay(p);
|
|
} else {
|
|
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
|
|
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
|
|
if (isRepeated) {
|
|
if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack)
|
|
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
|
} else if (!weWereNextHop) {
|
|
perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return Router::shouldFilterReceived(p);
|
|
}
|
|
|
|
void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
|
{
|
|
NodeNum ourNodeNum = getNodeNum();
|
|
uint8_t ourRelayID = nodeDB->getLastByteOfNodeNum(ourNodeNum);
|
|
bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
|
|
(p->decoded.request_id != 0 || p->decoded.reply_id != 0);
|
|
if (isAckorReply) {
|
|
// Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is
|
|
// not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination
|
|
if (p->from != 0) {
|
|
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
|
if (origTx) {
|
|
// Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
|
|
// the destination
|
|
if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
|
|
(wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
|
|
if (origTx->next_hop != p->relay_node) { // Not already set
|
|
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
|
|
origTx->next_hop = p->relay_node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!isToUs(p)) {
|
|
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
|
|
// stop retransmission for the original packet
|
|
stopRetransmission(p->to, p->decoded.request_id); // for original packet, from = to and id = request_id
|
|
}
|
|
}
|
|
|
|
perhapsRelay(p);
|
|
|
|
// handle the packet as normal
|
|
Router::sniffReceived(p, c);
|
|
}
|
|
|
|
/* Check if we should be relaying this packet if so, do so. */
|
|
bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
|
|
{
|
|
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
|
|
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
|
|
if (isRebroadcaster()) {
|
|
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
|
LOG_INFO("Relaying received message coming from %x", p->relay_node);
|
|
|
|
tosend->hop_limit--; // bump down the hop count
|
|
NextHopRouter::send(tosend);
|
|
|
|
return true;
|
|
} else {
|
|
LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the next hop for a destination, given the relay node
|
|
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
|
*/
|
|
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
|
{
|
|
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
|
|
if (isBroadcast(to))
|
|
return NO_NEXT_HOP_PREFERENCE;
|
|
|
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
|
|
if (node && node->next_hop) {
|
|
// We are careful not to return the relay node as the next hop
|
|
if (node->next_hop != relay_node) {
|
|
// LOG_DEBUG("Next hop for 0x%x is 0x%x", to, node->next_hop);
|
|
return node->next_hop;
|
|
} else
|
|
LOG_WARN("Next hop for 0x%x is 0x%x, same as relayer; set no pref", to, node->next_hop);
|
|
}
|
|
return NO_NEXT_HOP_PREFERENCE;
|
|
}
|
|
|
|
PendingPacket *NextHopRouter::findPendingPacket(GlobalPacketId key)
|
|
{
|
|
auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
|
|
if (old != pending.end()) {
|
|
return &old->second;
|
|
} else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Stop any retransmissions we are doing of the specified node/packet ID pair
|
|
*/
|
|
bool NextHopRouter::stopRetransmission(NodeNum from, PacketId id)
|
|
{
|
|
auto key = GlobalPacketId(from, id);
|
|
return stopRetransmission(key);
|
|
}
|
|
|
|
bool NextHopRouter::stopRetransmission(GlobalPacketId key)
|
|
{
|
|
auto old = findPendingPacket(key);
|
|
if (old) {
|
|
auto p = old->packet;
|
|
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
|
|
to avoid canceling a transmission if it was ACKed super fast via MQTT */
|
|
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
|
|
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater
|
|
if (isFromUs(p) || (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
|
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
|
|
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
|
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it
|
|
cancelSending(getFrom(p), p->id);
|
|
// now free the pooled copy for retransmission too
|
|
packetPool.release(p);
|
|
}
|
|
}
|
|
auto numErased = pending.erase(key);
|
|
assert(numErased == 1);
|
|
return true;
|
|
} else
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
|
|
*/
|
|
PendingPacket *NextHopRouter::startRetransmission(meshtastic_MeshPacket *p, uint8_t numReTx)
|
|
{
|
|
auto id = GlobalPacketId(p);
|
|
auto rec = PendingPacket(p, numReTx);
|
|
|
|
stopRetransmission(getFrom(p), p->id);
|
|
|
|
setNextTx(&rec);
|
|
pending[id] = rec;
|
|
|
|
return &pending[id];
|
|
}
|
|
|
|
/**
|
|
* Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
|
|
*/
|
|
int32_t NextHopRouter::doRetransmissions()
|
|
{
|
|
uint32_t now = millis();
|
|
int32_t d = INT32_MAX;
|
|
|
|
// FIXME, we should use a better datastructure rather than walking through this map.
|
|
// for(auto el: pending) {
|
|
for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
|
|
++nextIt; // we use this odd pattern because we might be deleting it...
|
|
auto &p = it->second;
|
|
|
|
bool stillValid = true; // assume we'll keep this record around
|
|
|
|
// FIXME, handle 51 day rolloever here!!!
|
|
if (p.nextTxMsec <= now) {
|
|
if (p.numRetransmissions == 0) {
|
|
if (isFromUs(p.packet)) {
|
|
LOG_DEBUG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
|
|
p.packet->id);
|
|
sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
|
|
}
|
|
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
|
|
stopRetransmission(it->first);
|
|
stillValid = false; // just deleted it
|
|
} else {
|
|
LOG_DEBUG("Sending retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
|
|
p.packet->id, p.numRetransmissions);
|
|
|
|
if (!isBroadcast(p.packet->to)) {
|
|
if (p.numRetransmissions == 1) {
|
|
// Last retransmission, reset next_hop (fallback to FloodingRouter)
|
|
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
|
|
// Also reset it in the nodeDB
|
|
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
|
|
if (sentTo) {
|
|
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));
|
|
} else {
|
|
NextHopRouter::send(packetPool.allocCopy(*p.packet));
|
|
}
|
|
} 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));
|
|
}
|
|
|
|
// Queue again
|
|
--p.numRetransmissions;
|
|
setNextTx(&p);
|
|
}
|
|
}
|
|
|
|
if (stillValid) {
|
|
// Update our desired sleep delay
|
|
int32_t t = p.nextTxMsec - now;
|
|
|
|
d = min(t, d);
|
|
}
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
void NextHopRouter::setNextTx(PendingPacket *pending)
|
|
{
|
|
assert(iface);
|
|
auto d = iface->getRetransmissionMsec(pending->packet);
|
|
pending->nextTxMsec = millis() + d;
|
|
LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
|
|
printPacket("", pending->packet);
|
|
setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
|
|
} |