Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ void Mesh::begin() {

void Mesh::loop() {
Dispatcher::loop();
processNextHopRetries();
}

bool Mesh::allowPacketForward(const mesh::Packet* packet) {
Expand All @@ -30,6 +31,12 @@ uint32_t Mesh::getCADFailRetryDelay() const {
return _rng->nextInt(1, 4)*120;
}

uint32_t Mesh::getNextHopConfirmTimeout(const Packet* packet) const {
// allow for the next hop's own (possibly randomised) forwarding delay, its airtime to repeat, plus margin
uint32_t airtime = _radio->getEstAirtimeFor(packet->getRawLength());
return airtime * 3 + 2000;
}

int Mesh::searchPeersByHash(const uint8_t* hash) {
return 0; // not found
}
Expand All @@ -39,6 +46,11 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int
}

DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->isRouteDirect()) {
// any overheard direct packet may be the next hop repeating one of ours -- check before anything else
checkNextHopConfirm(pkt);
}

if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
if (pkt->path_len < MAX_PATH_SIZE) {
uint8_t i = 0;
Expand Down Expand Up @@ -102,6 +114,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
_tables->markSeen(pkt);
removeSelfFromPath(pkt);

if (pkt->getPathHashCount() > 0) { // only worth tracking if there is a further hop to overhear
registerNextHopConfirm(pkt);
}

uint32_t d = getDirectRetransmitDelay(pkt);
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
}
Expand Down Expand Up @@ -341,6 +357,64 @@ void Mesh::removeSelfFromPath(Packet* pkt) {
}
}

void Mesh::registerNextHopConfirm(const Packet* pkt) {
if (!getNextHopReliabilityEnabled() || getNextHopMaxRetries() == 0) return;

for (int i = 0; i < MAX_PENDING_NEXTHOP_CONFIRMS; i++) {
auto& e = _pending_confirms[i];
if (!e.active) {
e.active = true;
e.retries = 0;
e.pkt = *pkt; // keep a copy, so it can be resent unchanged if not confirmed
pkt->calculatePacketHash(e.hash);
e.deadline = futureMillis(getNextHopConfirmTimeout(pkt));
return;
}
}
MESH_DEBUG_PRINTLN("%s Mesh::registerNextHopConfirm(): pending table full, skipping reliability tracking", getLogDateTime());
}

void Mesh::checkNextHopConfirm(const Packet* pkt) {
uint8_t hash[MAX_HASH_SIZE];
bool calculated = false;

for (int i = 0; i < MAX_PENDING_NEXTHOP_CONFIRMS; i++) {
auto& e = _pending_confirms[i];
if (!e.active) continue;

if (!calculated) {
pkt->calculatePacketHash(hash);
calculated = true;
}
if (memcmp(hash, e.hash, MAX_HASH_SIZE) == 0) {
e.active = false; // next hop has repeated it -- confirmed, no retry needed
}
}
}

void Mesh::processNextHopRetries() {
for (int i = 0; i < MAX_PENDING_NEXTHOP_CONFIRMS; i++) {
auto& e = _pending_confirms[i];
if (!e.active || !millisHasNowPassed(e.deadline)) continue;

if (e.retries >= getNextHopMaxRetries()) {
MESH_DEBUG_PRINTLN("%s Mesh::processNextHopRetries(): giving up, no confirm heard after %d retries", getLogDateTime(), (uint32_t)e.retries);
e.active = false;
continue;
}

Packet* retry_pkt = obtainNewPacket();
if (retry_pkt == NULL) {
e.deadline = futureMillis(100); // packet pool busy, back off briefly and try again
continue;
}
*retry_pkt = e.pkt;
e.retries++;
e.deadline = futureMillis(getNextHopConfirmTimeout(&e.pkt));
sendPacket(retry_pkt, 0); // resend immediately, same priority as a fresh direct forward
}
}

DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
uint8_t n = packet->getPathHashCount();
if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit()
Expand Down Expand Up @@ -709,6 +783,10 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
} else {
pri = 0;
}

if (packet->getPathHashCount() > 0) { // there's a next hop to listen for repeating this
registerNextHopConfirm(packet);
}
}
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, pri, delay_millis);
Expand Down
51 changes: 51 additions & 0 deletions src/Mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ class MeshTables {
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
};

#ifndef MAX_PENDING_NEXTHOP_CONFIRMS
#define MAX_PENDING_NEXTHOP_CONFIRMS 4 // max concurrent direct packets awaiting next-hop confirmation
#endif

/**
* \brief Tracks a direct (path-routed) packet this node has repeated, while it waits to
* overhear the next hop repeating it in turn (as implicit confirmation of receipt).
*/
struct PendingNextHopConfirm {
bool active;
uint8_t retries;
uint32_t deadline; // millis() at which to retry (or give up if retries exhausted)
uint8_t hash[MAX_HASH_SIZE];
Packet pkt; // copy of the packet as it was (re)transmitted, for resending
};

/**
* \brief The next layer in the basic Dispatcher task, Mesh recognises the particular Payload TYPES,
* and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets.
Expand All @@ -28,12 +44,30 @@ class Mesh : public Dispatcher {
RTCClock* _rtc;
RNG* _rng;
MeshTables* _tables;
PendingNextHopConfirm _pending_confirms[MAX_PENDING_NEXTHOP_CONFIRMS];

void removeSelfFromPath(Packet* packet);
void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis);
//void routeRecvAcks(Packet* packet, uint32_t delay_millis);
DispatcherAction forwardMultipartDirect(Packet* pkt);

/**
* \brief Start tracking 'pkt' (just repeated by this node) until the next hop is heard repeating it.
*/
void registerNextHopConfirm(const Packet* pkt);

/**
* \brief Check an incoming direct packet against the pending-confirm table, and mark any
* match as confirmed (the next hop has repeated it, so no retry is needed).
*/
void checkNextHopConfirm(const Packet* pkt);

/**
* \brief Called each loop(), resends any pending packets whose confirm deadline has passed,
* up to getNextHopMaxRetries() times, then drops them.
*/
void processNextHopRetries();

protected:
DispatcherAction onRecvPacket(Packet* pkt) override;

Expand Down Expand Up @@ -71,6 +105,22 @@ class Mesh : public Dispatcher {
*/
virtual uint8_t getExtraAckTransmitCount() const;

/**
* \returns true if 'next-hop reliability' (listen-for-repeat retry) is enabled for repeated
* direct packets. Default is enabled wherever allowPacketForward() also permits forwarding.
*/
virtual bool getNextHopReliabilityEnabled() const { return true; }

/**
* \returns max number of retries (resends) attempted, if no repeat from the next hop is heard.
*/
virtual uint8_t getNextHopMaxRetries() const { return 3; }

/**
* \returns number of milliseconds to wait for the next hop to repeat 'packet', before retrying.
*/
virtual uint32_t getNextHopConfirmTimeout(const Packet* packet) const;

/**
* \brief Perform search of local DB of peers/contacts.
* \returns Number of peers with matching hash
Expand Down Expand Up @@ -169,6 +219,7 @@ class Mesh : public Dispatcher {
Mesh(Radio& radio, MillisecondClock& ms, RNG& rng, RTCClock& rtc, PacketManager& mgr, MeshTables& tables)
: Dispatcher(radio, ms, mgr), _rng(&rng), _rtc(&rtc), _tables(&tables)
{
memset(_pending_confirms, 0, sizeof(_pending_confirms));
}

MeshTables* getTables() const { return _tables; }
Expand Down