Skip to content

Commit 93d412f

Browse files
committed
added role-awareness
1 parent cc4d955 commit 93d412f

7 files changed

Lines changed: 86 additions & 65 deletions

File tree

src/mesh/mesh-pb-constants.h

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ static inline int get_max_num_nodes()
9999
#elif defined(ARCH_PORTDUINO)
100100
#define MAX_NUM_NODES 250 // native host: no flash/RAM constraint; match the ESP32-S3 top tier
101101
#else
102-
#define MAX_NUM_NODES 120 // nRF52840 (28 KB LittleFS) and generic ESP32
102+
#define MAX_NUM_NODES 120 // nRF52840 and generic ESP32 (inc. ESP32C3 etc.)
103103
#endif // platform
104104
#endif // MAX_NUM_NODES
105105

@@ -112,7 +112,7 @@ static inline int get_max_num_nodes()
112112
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_PORTDUINO)
113113
#define MAX_SATELLITE_NODES 250
114114
#else
115-
#define MAX_SATELLITE_NODES 40 // nRF52840 (28 KB LittleFS) and generic ESP32
115+
#define MAX_SATELLITE_NODES 40 // nRF52840 and generic ESP32 (inc. ESP32C3 etc.)
116116
#endif // platform
117117
#endif // MAX_SATELLITE_NODES
118118

@@ -127,12 +127,12 @@ static inline int get_max_num_nodes()
127127
// architecture.h via configuration.h) isn't defined this early in every include
128128
// chain. Backed by the raw-flash ring below LittleFS — see WarmNodeStore.h.
129129
#define WARM_NODE_COUNT 200
130-
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
131-
#define WARM_NODE_COUNT 2000 // PSRAM-backed when available; warm.dat ~80 KB
130+
#elif defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_PORTDUINO)
131+
#define WARM_NODE_COUNT 2000 // PSRAM-rich / native host; warm.dat ~80 KB
132132
#else
133-
#define WARM_NODE_COUNT 320
134-
#endif // platform
135-
#endif // WARM_NODE_COUNT
133+
#define WARM_NODE_COUNT 320 // Generic ESP32 (inc. ESP32C3 etc.)
134+
#endif // platform
135+
#endif // WARM_NODE_COUNT
136136

137137
/// Max number of channels allowed
138138
#define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0]))
@@ -152,18 +152,21 @@ static inline int get_max_num_nodes()
152152
#ifdef ARCH_STM32WL
153153
#define HAS_VARIABLE_HOPS 0
154154
#endif
155+
155156
#ifndef HAS_VARIABLE_HOPS
156157
#define HAS_VARIABLE_HOPS 1
157158
#endif
158159

159160
// Cache size for traffic management (number of nodes to track)
160-
// Can be overridden per-variant based on available memory
161+
// Can be overridden per-variant by defining before this header is included.
161162
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
162-
#if HAS_TRAFFIC_MANAGEMENT
163-
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000
164-
#else
163+
#if !HAS_TRAFFIC_MANAGEMENT
165164
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 0
166-
#endif // HAS_TRAFFIC_MANAGEMENT
165+
#elif defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_PORTDUINO)
166+
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048 // PSRAM-rich platforms
167+
#else
168+
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000
169+
#endif
167170
#endif // TRAFFIC_MANAGEMENT_CACHE_SIZE
168171

169172
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic

src/modules/TrafficManagementModule.cpp

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,9 @@ TrafficManagementModule::UnifiedCacheEntry *TrafficManagementModule::findOrCreat
315315
const uint8_t unknownAgePosScale =
316316
static_cast<uint8_t>(static_cast<uint8_t>((currentUnknownTick() - e.getUnknownTime()) & 0x0F) / 6);
317317
uint8_t recencyAge = posAge;
318-
if (e.rate_count != 0 && rateAgePosScale > recencyAge)
318+
if (e.getRateCount() != 0 && rateAgePosScale > recencyAge)
319319
recencyAge = rateAgePosScale;
320-
if (e.unknown_count != 0 && unknownAgePosScale > recencyAge)
320+
if (e.getUnknownCount() != 0 && unknownAgePosScale > recencyAge)
321321
recencyAge = unknownAgePosScale;
322322
const uint8_t recency = static_cast<uint8_t>(UINT8_MAX - recencyAge);
323323
if (!victim || (hasHop == victimHasHop ? recency < victimRecency : !hasHop)) {
@@ -876,20 +876,20 @@ int32_t TrafficManagementModule::runOnce()
876876
}
877877
}
878878

879-
// Check and clear expired rate limit data (gated on feature; presence: rate_count != 0)
880-
if (cfg.rate_limit_enabled && cache[i].rate_count != 0) {
879+
// Check and clear expired rate limit data (gated on feature; presence: getRateCount() != 0)
880+
if (cfg.rate_limit_enabled && cache[i].getRateCount() != 0) {
881881
if ((static_cast<uint8_t>(nowRateTick - cache[i].getRateTime()) & 0x0F) >= rateTtlTicks) {
882-
cache[i].rate_count = 0;
882+
cache[i].setRateCount(0);
883883
cache[i].setRateTime(0);
884884
} else {
885885
anyValid = true;
886886
}
887887
}
888888

889-
// Check and clear expired unknown tracking data (gated on feature; presence: unknown_count != 0)
890-
if (cfg.drop_unknown_enabled && cache[i].unknown_count != 0) {
889+
// Check and clear expired unknown tracking data (gated on feature; presence: getUnknownCount() != 0)
890+
if (cfg.drop_unknown_enabled && cache[i].getUnknownCount() != 0) {
891891
if ((static_cast<uint8_t>(nowUnknownTick - cache[i].getUnknownTime()) & 0x0F) >= unknownTtlTicks) {
892-
cache[i].unknown_count = 0;
892+
cache[i].setUnknownCount(0);
893893
cache[i].setUnknownTime(0);
894894
} else {
895895
anyValid = true;
@@ -1149,30 +1149,40 @@ bool TrafficManagementModule::isRateLimited(NodeNum from, uint32_t nowMs)
11491149
if (!entry)
11501150
return false;
11511151

1152+
// Refresh cached role from hot store when we have a confirmed user record; leave
1153+
// stale value otherwise so evicted-from-both-stores nodes retain their last role.
1154+
if (nodeDB) {
1155+
const meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(from);
1156+
if (nodeInfoLiteHasUser(info))
1157+
entry->setCachedRole(static_cast<uint8_t>(std::min(15, (int)info->role)));
1158+
}
1159+
11521160
// Window ticks: clamp to [1,15] so zero windowMs (config error) opens a new window.
11531161
const uint8_t windowTicks = static_cast<uint8_t>(std::min(static_cast<uint32_t>(15), windowMs / kRateTimeTickMs));
11541162
const uint8_t nowRateTick = currentRateTick();
11551163
const bool windowExpired =
1156-
isNew || entry->rate_count == 0 ||
1164+
isNew || entry->getRateCount() == 0 ||
11571165
((static_cast<uint8_t>(nowRateTick - entry->getRateTime()) & 0x0F) >= std::max(static_cast<uint8_t>(1), windowTicks));
11581166
if (windowExpired) {
11591167
entry->setRateTime(nowRateTick);
1160-
entry->rate_count = 1;
1168+
entry->setRateCount(1);
11611169
return false;
11621170
}
11631171

1164-
// Increment counter (saturates at 255)
1165-
saturatingIncrement(entry->rate_count);
1172+
// Increment counter, saturating at 63 (6-bit field max).
1173+
const uint8_t cur = entry->getRateCount();
1174+
if (cur < 0x3F)
1175+
entry->setRateCount(static_cast<uint8_t>(cur + 1));
11661176

1167-
// Check against threshold (uint8_t max is 255, but config is uint32_t)
1177+
// Threshold capped at 60 so a saturated reading (63) always exceeds it.
11681178
uint32_t threshold = moduleConfig.traffic_management.rate_limit_max_packets;
1169-
if (threshold > 255)
1170-
threshold = 255;
1179+
if (threshold > 60)
1180+
threshold = 60;
11711181

1172-
bool limited = entry->rate_count > threshold;
1173-
if (limited || entry->rate_count == threshold) {
1174-
TM_LOG_DEBUG("Rate limit 0x%08x: count=%u threshold=%u -> %s", from, entry->rate_count, threshold,
1175-
limited ? "DROP" : "at-limit");
1182+
const uint8_t count = entry->getRateCount();
1183+
bool limited = count > threshold;
1184+
if (limited || count == threshold) {
1185+
TM_LOG_DEBUG("Rate limit 0x%08x: count=%u threshold=%u -> %s", from, count, threshold, limited ? "DROP" : "at-limit");
11761186
}
11771187
return limited;
11781188
#endif
@@ -1197,28 +1207,31 @@ bool TrafficManagementModule::shouldDropUnknown(const meshtastic_MeshPacket *p,
11971207
if (!entry)
11981208
return false;
11991209

1200-
// Check if window has expired (presence: unknown_count != 0)
1210+
// Check if window has expired (presence: getUnknownCount() != 0)
12011211
const uint8_t nowUnknownTick = currentUnknownTick();
1202-
const bool windowExpired = isNew || entry->unknown_count == 0 ||
1212+
const bool windowExpired = isNew || entry->getUnknownCount() == 0 ||
12031213
((static_cast<uint8_t>(nowUnknownTick - entry->getUnknownTime()) & 0x0F) >= kUnknownWindowTicks);
12041214
if (windowExpired) {
12051215
entry->setUnknownTime(nowUnknownTick);
1206-
entry->unknown_count = 0;
1216+
entry->setUnknownCount(0);
12071217
}
12081218

1209-
// Increment counter (saturates at 255). Same saturation handling as
1210-
// isRateLimited: without it, a clamped threshold of 255 can never fire.
1211-
const bool alreadySaturated = (entry->unknown_count == UINT8_MAX);
1212-
saturatingIncrement(entry->unknown_count);
1219+
// Increment counter, saturating at 63 (6-bit field max). With threshold
1220+
// capped at 60, a saturated reading always exceeds the limit — no special
1221+
// already-saturated edge case needed.
1222+
const uint8_t cur = entry->getUnknownCount();
1223+
if (cur < 0x3F)
1224+
entry->setUnknownCount(static_cast<uint8_t>(cur + 1));
12131225

1214-
// Check against threshold
1226+
// Threshold capped at 60 so a saturated reading (63) always exceeds it.
12151227
uint32_t threshold = moduleConfig.traffic_management.unknown_packet_threshold;
1216-
if (threshold > 255)
1217-
threshold = 255;
1228+
if (threshold > 60)
1229+
threshold = 60;
12181230

1219-
bool drop = entry->unknown_count > threshold || (alreadySaturated && threshold == 255);
1220-
if (drop || entry->unknown_count == threshold) {
1221-
TM_LOG_DEBUG("Unknown packets 0x%08x: count=%u threshold=%u -> %s", p->from, entry->unknown_count, threshold,
1231+
const uint8_t count = entry->getUnknownCount();
1232+
bool drop = count > threshold;
1233+
if (drop || count == threshold) {
1234+
TM_LOG_DEBUG("Unknown packets 0x%08x: count=%u threshold=%u -> %s", p->from, count, threshold,
12221235
drop ? "DROP" : "at-limit");
12231236
}
12241237
return drop;

src/modules/TrafficManagementModule.h

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,29 +96,46 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
9696
// Layout:
9797
// [0-3] node - NodeNum (4 bytes, 0 = empty slot)
9898
// [4] pos_fingerprint - 4 bits lat + 4 bits lon (0 = no position seen)
99-
// [5] rate_count - Packets in current rate window (0 = no window active)
100-
// [6] unknown_count - Unknown packets in window (0 = no window active)
99+
// [5] rate_count - [7:6] role[3:2] | [5:0] packets in rate window (0 = no window active)
100+
// [6] unknown_count - [7:6] role[1:0] | [5:0] unknown packets in window (0 = no window active)
101101
// [7] pos_time - Position tick (uint8, free-running 360 s/tick)
102102
// [8] rate_unknown_time - [7:4] rate nibble (300 s/tick) | [3:0] unknown nibble (60 s/tick)
103103
// [9] next_hop - Last-byte relay to reach `node` (0 = none)
104104
//
105+
// The 4-bit device role (bits [7:6] of rate_count paired with [7:6] of unknown_count)
106+
// caches the sender's meshtastic_Config_DeviceConfig_Role as a third fallback after the
107+
// hot store and warm store, for nodes evicted from both. Max encodable value is 15.
108+
//
105109
// Presence sentinels (no epoch, no +1 offset needed):
106110
// pos active: pos_fingerprint != 0
107-
// rate active: rate_count != 0
108-
// unknown active: unknown_count != 0
111+
// rate active: getRateCount() != 0 (low 6 bits only)
112+
// unknown active: getUnknownCount() != 0 (low 6 bits only)
109113
//
110114
// next_hop: routing hint written only from ACK-confirmed NextHopRouter decisions.
111115
// No TTL — keeps the slot alive across maintenance sweeps.
112116
//
117+
#if _meshtastic_Config_DeviceConfig_Role_MAX > 15
118+
#warning "Device role enum max exceeds 15 — TMM 4-bit role cache (rate_count[7:6]/unknown_count[7:6]) will truncate new values"
119+
#endif
113120
struct __attribute__((packed)) UnifiedCacheEntry {
114121
NodeNum node;
115122
uint8_t pos_fingerprint;
116-
uint8_t rate_count;
117-
uint8_t unknown_count;
123+
uint8_t rate_count; // [7:6] = role[3:2], [5:0] = count (max 63)
124+
uint8_t unknown_count; // [7:6] = role[1:0], [5:0] = count (max 63)
118125
uint8_t pos_time;
119126
uint8_t rate_unknown_time;
120127
uint8_t next_hop;
121128

129+
uint8_t getRateCount() const { return rate_count & 0x3F; }
130+
void setRateCount(uint8_t c) { rate_count = static_cast<uint8_t>((rate_count & 0xC0) | (c & 0x3F)); }
131+
uint8_t getUnknownCount() const { return unknown_count & 0x3F; }
132+
void setUnknownCount(uint8_t c) { unknown_count = static_cast<uint8_t>((unknown_count & 0xC0) | (c & 0x3F)); }
133+
uint8_t getCachedRole() const { return static_cast<uint8_t>(((rate_count >> 6) << 2) | (unknown_count >> 6)); }
134+
void setCachedRole(uint8_t role)
135+
{
136+
rate_count = static_cast<uint8_t>((rate_count & 0x3F) | ((role >> 2) << 6));
137+
unknown_count = static_cast<uint8_t>((unknown_count & 0x3F) | ((role & 0x03) << 6));
138+
}
122139
uint8_t getRateTime() const { return (rate_unknown_time >> 4) & 0x0F; }
123140
uint8_t getUnknownTime() const { return rate_unknown_time & 0x0F; }
124141
void setRateTime(uint8_t t) { rate_unknown_time = static_cast<uint8_t>((rate_unknown_time & 0x0F) | ((t & 0x0F) << 4)); }
@@ -131,7 +148,7 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
131148
// =========================================================================
132149
//
133150
// Plain array, linear scan (same idiom as WarmNodeStore). A lookup walks at
134-
// most cacheSize() × 11 B — microseconds at LoRa packet rates, not worth a
151+
// most cacheSize() × 10 B — microseconds at LoRa packet rates, not worth a
135152
// hash table. Insertion on a full cache evicts the stalest entry,
136153
// preferring entries without a next_hop hint (those are the long-tail
137154
// routing state this cache exists to keep).
@@ -158,8 +175,8 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
158175
//
159176
// Presence sentinels (no +1 offset needed; count fields serve as guards):
160177
// pos active: pos_fingerprint != 0 (zero fingerprint astronomically unlikely)
161-
// rate active: rate_count != 0
162-
// unknown active: unknown_count != 0
178+
// rate active: getRateCount() != 0 (low 6 bits; high 2 bits are cached role)
179+
// unknown active: getUnknownCount() != 0
163180
//
164181
static constexpr uint32_t kPosTimeTickMs = 360'000UL; // 6 min/tick
165182
static constexpr uint32_t kRateTimeTickMs = 300'000UL; // 5 min/tick

variants/esp32s3/heltec_v4/variant.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@
3333
#ifndef HAS_TRAFFIC_MANAGEMENT
3434
#define HAS_TRAFFIC_MANAGEMENT 1
3535
#endif
36-
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
37-
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
38-
#endif
3936

4037
// ---- GC1109 RF FRONT END CONFIGURATION ----
4138
// The Heltec V4.2 uses a GC1109 FEM chip with integrated PA and LNA

variants/esp32s3/heltec_v4_r8/variant.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@
3131
#ifndef HAS_TRAFFIC_MANAGEMENT
3232
#define HAS_TRAFFIC_MANAGEMENT 1
3333
#endif
34-
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
35-
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
36-
#endif
3734

3835
// ---- KCT8103L RF FRONT END CONFIGURATION ----
3936
// The Heltec V4.3 uses a KCT8103L FEM chip with integrated PA and LNA

variants/esp32s3/station-g2/variant.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2
1414
#ifndef HAS_TRAFFIC_MANAGEMENT
1515
#define HAS_TRAFFIC_MANAGEMENT 1
1616
#endif
17-
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
18-
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
19-
#endif
2017

2118
/*
2219
#define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage

variants/native/portduino/variant.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,3 @@
1616
#ifndef HAS_VARIABLE_HOPS
1717
#define HAS_VARIABLE_HOPS 1
1818
#endif
19-
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
20-
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 2048
21-
#endif

0 commit comments

Comments
 (0)