-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Extra repeats #10962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
NomDeTom
wants to merge
9
commits into
meshtastic:develop
Choose a base branch
from
NomDeTom:extra-repeats
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Extra repeats #10962
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
727d162
Tolerate a configurable number of heard repeats before cancelling our…
NomDeTom 949c539
Suppress extra-repeat tolerance when the mesh is busy or dense
NomDeTom c136191
split into separate module
NomDeTom 7b82e32
fixed latent bug
NomDeTom eaf9b7c
decrypt first, dummy
NomDeTom 9397f74
more tests
NomDeTom aafd917
moredebug
NomDeTom 8fa58b6
nitpicks
NomDeTom 5aa96ea
cppcheck
NomDeTom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| #include "RepeatScalingModule.h" | ||
| #include "DebugConfiguration.h" | ||
| #include "airtime.h" | ||
| #include "configuration.h" | ||
| #include "modules/HopScalingModule.h" | ||
|
|
||
| RepeatScalingModule *repeatScalingModule; | ||
|
|
||
| // Design notes for getDupeCancelThreshold()'s policy (kept here rather than inline): | ||
| // | ||
| // Historically we cancel our own queued rebroadcast the instant we hear one duplicate from another | ||
| // node. This module lets selected packet types instead tolerate a few heard duplicates first, | ||
| // trading a little extra airtime for better delivery odds. Thresholds live in a compile-time | ||
| // per-portnum switch (not runtime config) because the right values aren't yet settled. | ||
| // | ||
| // The switch applies uniformly to broadcasts and DMs - NextHopRouter routes duplicates through the | ||
| // same FloodingRouter::perhapsCancelDupe -> shouldCancelDupe path - so a DM of a listed portnum | ||
| // gets the same tolerance as a broadcast of it, and only ever for a *decodable* DM (a DM not | ||
| // addressed to us can't be decoded by us). | ||
| // | ||
| // When the portnum can't be determined (packet still encrypted, no noteScheduled() cache hit), we | ||
| // fall back to a next_hop gate: NO_NEXT_HOP_PREFERENCE (flood-relayed) gets text-message tolerance | ||
| // since it has the same uncertain single-path shape; a specific next_hop does not, as delivery | ||
| // there is already backed by the sender's end-to-end ACK/retry. | ||
| // | ||
| // Either way, meshTooBusyForExtraRepeats() forces the threshold back to 1 on a busy/dense mesh. | ||
|
|
||
| namespace | ||
| { | ||
| // Thresholds above which the mesh is busy/dense enough that extra repeats aren't worth the airtime. | ||
| constexpr float BUSY_CHANNEL_UTIL_PERCENT = 10.0f; | ||
| constexpr float BUSY_AIR_UTIL_TX_PERCENT = 4.0f; | ||
| constexpr uint16_t BUSY_DIRECT_ACTIVE_NODES = 10; | ||
|
|
||
| // True if channel/air utilization or direct-neighbor density says the mesh is too busy for extra | ||
| // repeats. Logs which condition tripped. | ||
| bool meshTooBusyForExtraRepeats() | ||
| { | ||
| if (airTime && airTime->channelUtilizationPercent() > BUSY_CHANNEL_UTIL_PERCENT) { | ||
| LOG_DEBUG("[REPEATSCALE] Mesh busy: chUtil=%.1f%% > %.1f%%", airTime->channelUtilizationPercent(), | ||
| BUSY_CHANNEL_UTIL_PERCENT); | ||
| return true; | ||
| } | ||
| if (airTime && airTime->utilizationTXPercent() > BUSY_AIR_UTIL_TX_PERCENT) { | ||
| LOG_DEBUG("[REPEATSCALE] Mesh busy: airUtilTX=%.1f%% > %.1f%%", airTime->utilizationTXPercent(), | ||
| BUSY_AIR_UTIL_TX_PERCENT); | ||
| return true; | ||
| } | ||
| #if HAS_VARIABLE_HOPS | ||
| // perHop[0] is HopScalingModule's estimate of active direct (hop_away == 0) neighbors. | ||
| if (hopScalingModule && hopScalingModule->getLastPerHopCounts().perHop[0] > BUSY_DIRECT_ACTIVE_NODES) { | ||
| LOG_DEBUG("[REPEATSCALE] Mesh busy: directActiveNodes=%u > %u", hopScalingModule->getLastPerHopCounts().perHop[0], | ||
| BUSY_DIRECT_ACTIVE_NODES); | ||
| return true; | ||
| } | ||
| #endif | ||
| return false; | ||
| } | ||
| } // namespace | ||
|
|
||
| // Per-portnum duplicate-tolerance threshold; see the design notes above for the full rationale. | ||
| uint8_t RepeatScalingModule::getDupeCancelThreshold(const meshtastic_MeshPacket *p) | ||
| { | ||
| const int32_t portnum = resolvePortnum(p); | ||
|
|
||
| uint8_t threshold; | ||
| if (portnum >= 0) { | ||
| switch (portnum) { | ||
| case meshtastic_PortNum_TEXT_MESSAGE_APP: | ||
| case meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP: | ||
| // User-visible chat: no broadcast ACK/retry safety net, so tolerate one heard repeat. | ||
| threshold = 2; | ||
| break; | ||
| default: | ||
| threshold = 1; | ||
| break; | ||
| } | ||
| } else { | ||
| // Portnum unknowable (undecodable packet): fall back to the plaintext next_hop header. | ||
| threshold = (p->next_hop == NO_NEXT_HOP_PREFERENCE) ? 2 : 1; | ||
| LOG_DEBUG("[REPEATSCALE] portnum unknown for 0x%08x from=0x%08x; next_hop=0x%x -> threshold=%u", p->id, p->from, | ||
| p->next_hop, threshold); | ||
| } | ||
|
|
||
| // A busy/dense mesh overrides any extra tolerance decided above. | ||
| if (threshold > 1 && meshTooBusyForExtraRepeats()) { | ||
| LOG_DEBUG("[REPEATSCALE] portnum=%d wanted threshold=%u but mesh is busy; falling back to 1", portnum, threshold); | ||
| return 1; | ||
| } | ||
|
|
||
| return threshold; | ||
| } | ||
|
|
||
| uint8_t RepeatScalingModule::registerDupeHeard(NodeNum sender, PacketId id) | ||
| { | ||
| for (auto &entry : dupeCounts) { | ||
| if (entry.id == id && entry.sender == sender) { | ||
| if (entry.count < UINT8_MAX) | ||
| entry.count++; | ||
| return entry.count; | ||
| } | ||
| } | ||
| // Not tracked yet: claim the next ring slot, evicting whatever was there. | ||
| DupeCountEntry &slot = dupeCounts[dupeCountsNextSlot]; | ||
| dupeCountsNextSlot = (dupeCountsNextSlot + 1) % DUPE_COUNT_TRACKER_SIZE; | ||
| slot.sender = sender; | ||
| slot.id = id; | ||
| slot.count = 1; | ||
| slot.portnum = -1; // no noteScheduled() preceded this | ||
| return slot.count; | ||
| } | ||
|
|
||
| void RepeatScalingModule::clearDupeCount(NodeNum sender, PacketId id) | ||
| { | ||
| for (auto &entry : dupeCounts) { | ||
| if (entry.id == id && entry.sender == sender) { | ||
| entry.sender = 0; | ||
| entry.id = 0; | ||
| entry.count = 0; | ||
| entry.portnum = -1; | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void RepeatScalingModule::noteScheduled(NodeNum sender, PacketId id, int32_t portnum) | ||
| { | ||
| for (auto &entry : dupeCounts) { | ||
| if (entry.id == id && entry.sender == sender) { | ||
| entry.portnum = portnum; | ||
| return; | ||
| } | ||
| } | ||
| // Not tracked yet: claim the next ring slot. count starts at 0 (not 1) since scheduling our | ||
| // own rebroadcast is not itself a heard duplicate. | ||
| DupeCountEntry &slot = dupeCounts[dupeCountsNextSlot]; | ||
| dupeCountsNextSlot = (dupeCountsNextSlot + 1) % DUPE_COUNT_TRACKER_SIZE; | ||
| slot.sender = sender; | ||
| slot.id = id; | ||
| slot.count = 0; | ||
| slot.portnum = portnum; | ||
| } | ||
|
|
||
| int32_t RepeatScalingModule::lookupNotedPortnum(NodeNum sender, PacketId id) const | ||
| { | ||
| for (const auto &entry : dupeCounts) { | ||
| if (entry.id == id && entry.sender == sender) | ||
| return entry.portnum; | ||
| } | ||
| return -1; | ||
| } | ||
|
|
||
| int32_t RepeatScalingModule::resolvePortnum(const meshtastic_MeshPacket *p) const | ||
| { | ||
| return (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) ? p->decoded.portnum | ||
| : lookupNotedPortnum(p->from, p->id); | ||
| } | ||
|
|
||
| uint8_t RepeatScalingModule::getToleratedDupeCount(NodeNum sender, PacketId id) const | ||
| { | ||
| for (const auto &entry : dupeCounts) { | ||
| if (entry.id == id && entry.sender == sender) | ||
| return entry.count; | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| bool RepeatScalingModule::shouldCancelDupe(const meshtastic_MeshPacket *p) | ||
| { | ||
| const uint8_t threshold = getDupeCancelThreshold(p); | ||
| const uint8_t dupesHeard = registerDupeHeard(p->from, p->id); | ||
| const int32_t portnum = resolvePortnum(p); // for logging only | ||
|
|
||
| if (dupesHeard >= threshold) { | ||
| LOG_INFO("[REPEATSCALE] Giving up own rebroadcast of 0x%08x from=0x%08x portnum=%d: heard %u/%u duplicate(s)", p->id, | ||
| p->from, portnum, dupesHeard, threshold); | ||
| clearDupeCount(p->from, p->id); | ||
| return true; | ||
| } | ||
|
|
||
| LOG_DEBUG("[REPEATSCALE] Tolerated duplicate %u/%u of 0x%08x from=0x%08x portnum=%d: will still transmit our own " | ||
| "rebroadcast", | ||
| dupesHeard, threshold, p->id, p->from, portnum); | ||
| return false; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| #pragma once | ||
|
|
||
| #include "MeshTypes.h" | ||
| #include "mesh/mesh-pb-constants.h" | ||
|
|
||
| /** | ||
| * RepeatScalingModule owns the "how many duplicate rebroadcasts of a packet we ourselves have | ||
| * queued to rebroadcast should we tolerate before giving up" decision (see | ||
| * FloodingRouter::perhapsCancelDupe, its sole caller). | ||
| * | ||
| * The historical behavior is to cancel our own queued rebroadcast as soon as we hear the very | ||
| * first duplicate from another node. This module allows some packet types (see | ||
| * getDupeCancelThreshold in RepeatScalingModule.cpp) to instead tolerate a configurable number of | ||
| * heard duplicates first, trading a little extra airtime for better delivery odds - unless the | ||
| * mesh is already busy/dense, in which case it always falls back to the historical behavior. | ||
| */ | ||
| class RepeatScalingModule | ||
| { | ||
| public: | ||
| RepeatScalingModule() = default; | ||
| virtual ~RepeatScalingModule() = default; | ||
|
|
||
| // Note a heard duplicate for (p->from, p->id); returns true (and clears tracking) once the | ||
| // per-portnum threshold is reached, meaning the caller should cancel its own rebroadcast. | ||
| // Virtual so FloodingRouter role-gating tests can substitute a double. | ||
| virtual bool shouldCancelDupe(const meshtastic_MeshPacket *p); | ||
|
|
||
| // Cache the portnum of a rebroadcast we've scheduled, so later encrypted duplicates of it can | ||
| // still be classified by getDupeCancelThreshold(). Pass -1 if it couldn't be decoded. | ||
| void noteScheduled(NodeNum sender, PacketId id, int32_t portnum); | ||
|
|
||
| // Duplicates heard (and tolerated) so far for (sender, id), or 0. For logging at TX time. | ||
| uint8_t getToleratedDupeCount(NodeNum sender, PacketId id) const; | ||
|
|
||
| protected: | ||
| // Duplicates to tolerate before cancelling our own rebroadcast. Virtual so tests can inject a | ||
| // threshold without relying on a real portnum case. | ||
| virtual uint8_t getDupeCancelThreshold(const meshtastic_MeshPacket *p); | ||
|
|
||
| // Ephemeral ring buffer of per-(sender, id) heard-duplicate counts (not persistent state). | ||
| uint8_t registerDupeHeard(NodeNum sender, PacketId id); | ||
| void clearDupeCount(NodeNum sender, PacketId id); | ||
| int32_t lookupNotedPortnum(NodeNum sender, PacketId id) const; | ||
|
|
||
| private: | ||
| // Decoded portnum if available, else the one cached by noteScheduled() (or -1). | ||
| int32_t resolvePortnum(const meshtastic_MeshPacket *p) const; | ||
|
|
||
| static constexpr uint8_t DUPE_COUNT_TRACKER_SIZE = 8; | ||
| struct DupeCountEntry { | ||
| NodeNum sender = 0; | ||
| PacketId id = 0; | ||
| uint8_t count = 0; | ||
| int32_t portnum = -1; | ||
| }; | ||
| DupeCountEntry dupeCounts[DUPE_COUNT_TRACKER_SIZE]; | ||
| uint8_t dupeCountsNextSlot = 0; | ||
| }; | ||
|
|
||
| extern RepeatScalingModule *repeatScalingModule; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.