@alteriom/painlessmesh 1.9.19 → 1.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +82 -63
  3. package/examples/alteriom/README.md +4 -4
  4. package/examples/alteriom/alteriom_custom_package_template.hpp +320 -0
  5. package/examples/alteriom/alteriom_sensor_package.hpp +1 -1
  6. package/examples/alteriom/mppt_example/alteriom_mppt_example.ino +208 -0
  7. package/examples/bridge_failover/bridge_failover.ino +17 -0
  8. package/examples/sendToInternet/CMakeLists.txt +54 -0
  9. package/examples/sendToInternet/PC_NODE_README.md +517 -0
  10. package/examples/sendToInternet/README.md +39 -1
  11. package/examples/sendToInternet/build.sh +153 -0
  12. package/examples/sendToInternet/mock_server_test.ino +361 -0
  13. package/examples/sendToInternet/pc_mesh_node.cpp +361 -0
  14. package/library.json +4 -1
  15. package/library.properties +1 -1
  16. package/package.json +3 -3
  17. package/src/AlteriomPainlessMesh.h +5 -13
  18. package/src/arduino/wifi.hpp +306 -100
  19. package/src/connection.cpp +10 -0
  20. package/src/painlessMesh.h +1 -14
  21. package/src/painlessmesh/connection.hpp +11 -16
  22. package/src/painlessmesh/gateway.hpp +0 -1061
  23. package/src/painlessmesh/mesh.hpp +29 -85
  24. package/src/painlessmesh/message_queue.hpp +1 -2
  25. package/src/painlessmesh/metrics.hpp +2 -262
  26. package/src/painlessmesh/validation.hpp +0 -143
  27. package/docs/README.md +0 -132
  28. package/docs/alteriom/overview.md +0 -531
  29. package/docs/api/core-api.md +0 -607
  30. package/docs/api/shared-gateway.md +0 -1207
  31. package/docs/architecture/mesh-architecture.md +0 -399
  32. package/docs/architecture/plugin-system.md +0 -517
  33. package/docs/getting-started/arduino-manual-install.md +0 -313
  34. package/docs/getting-started/first-mesh.md +0 -410
  35. package/docs/getting-started/installation.md +0 -275
  36. package/docs/getting-started/quickstart.md +0 -158
  37. package/docs/troubleshooting/common-issues.md +0 -679
  38. package/docs/troubleshooting/debugging.md +0 -455
  39. package/docs/troubleshooting/external-device-connection.md +0 -283
  40. package/docs/troubleshooting/faq.md +0 -574
  41. package/docs/tutorials/basic-examples.md +0 -718
@@ -352,6 +352,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
352
352
 
353
353
  if (!isExternalScheduler) {
354
354
  delete mScheduler;
355
+ mScheduler = nullptr;
355
356
  }
356
357
  }
357
358
 
@@ -876,7 +877,11 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
876
877
  * @return Number of available gateways
877
878
  */
878
879
  size_t getGatewayCount() {
879
- return getNodesWithInternet().size();
880
+ size_t count = 0;
881
+ for (const auto& bridge : knownBridges) {
882
+ if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) count++;
883
+ }
884
+ return count;
880
885
  }
881
886
 
882
887
  /**
@@ -1005,6 +1010,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1005
1010
  using namespace logger;
1006
1011
  if (bridgeCleanupTask != nullptr) {
1007
1012
  bridgeCleanupTask->disable();
1013
+ // Task remains in scheduler (disabled) — negligible overhead.
1014
+ // The scheduler will skip it on each cycle.
1008
1015
  bridgeCleanupTask = nullptr;
1009
1016
  Log(GENERAL, "disableBridgeCleanup(): Cleanup task disabled\n");
1010
1017
  }
@@ -1405,10 +1412,13 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1405
1412
  bool cancelInternetRequest(uint32_t messageId) {
1406
1413
  auto it = pendingInternetRequests.find(messageId);
1407
1414
  if (it != pendingInternetRequests.end()) {
1408
- if (it->second.callback) {
1409
- it->second.callback(false, 0, "Request cancelled");
1410
- }
1415
+ auto callback = it->second.callback;
1411
1416
  pendingInternetRequests.erase(it);
1417
+ if (callback) {
1418
+ this->addTask([callback]() {
1419
+ callback(false, 0, "Request cancelled");
1420
+ });
1421
+ }
1412
1422
  Log(logger::GENERAL, "cancelInternetRequest(): Cancelled msgId=%u\n", messageId);
1413
1423
  return true;
1414
1424
  }
@@ -1541,19 +1551,22 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1541
1551
  // These errors indicate infrastructure issues that won't be fixed by retrying:
1542
1552
  // - "Router has no internet access" - WAN connection down
1543
1553
  // - "Gateway WiFi not connected" - ESP not associated with WiFi
1554
+ // - "Captive portal detected" - Router requires web authentication
1544
1555
  // Use find() for std::string (test env) or indexOf() for Arduino String
1545
- bool routerError = false, wifiError = false;
1556
+ bool routerError = false, wifiError = false, captivePortalError = false;
1546
1557
  #if defined(PAINLESSMESH_BOOST)
1547
1558
  // Test environment: TSTRING is std::string, use find()
1548
1559
  routerError = (ack.error.find("Router has no internet") != std::string::npos);
1549
1560
  wifiError = (ack.error.find("Gateway WiFi not connected") != std::string::npos);
1561
+ captivePortalError = (ack.error.find("Captive portal detected") != std::string::npos);
1550
1562
  #else
1551
1563
  // Arduino environment: TSTRING is String, use indexOf()
1552
1564
  routerError = (ack.error.indexOf("Router has no internet") >= 0);
1553
1565
  wifiError = (ack.error.indexOf("Gateway WiFi not connected") >= 0);
1566
+ captivePortalError = (ack.error.indexOf("Captive portal detected") >= 0);
1554
1567
  #endif
1555
1568
 
1556
- if (routerError || wifiError) {
1569
+ if (routerError || wifiError || captivePortalError) {
1557
1570
  isGatewayConnectivityError = true;
1558
1571
  Log(COMMUNICATION, "handleGatewayAck(): Gateway connectivity error detected (non-retryable): %s\n",
1559
1572
  ack.error.c_str());
@@ -1951,8 +1964,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1951
1964
  return rtcTime;
1952
1965
  }
1953
1966
  }
1954
- // Fallback to mesh time (microseconds)
1955
- return this->getNodeTime();
1967
+ // Fallback to mesh time (microseconds), converted to seconds for consistency
1968
+ return getNodeTime() / 1000000;
1956
1969
  }
1957
1970
 
1958
1971
  /**
@@ -2323,7 +2336,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
2323
2336
  auto tree = this->asNodeTree();
2324
2337
 
2325
2338
  // BFS to find hop count
2326
- std::queue<std::pair<uint32_t, uint8_t>> queue; // (nodeId, hops)
2339
+ std::queue<std::pair<uint32_t, uint16_t>> queue; // (nodeId, hops)
2327
2340
  std::set<uint32_t> visited;
2328
2341
 
2329
2342
  // Start from this node
@@ -2335,7 +2348,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
2335
2348
  queue.pop();
2336
2349
 
2337
2350
  uint32_t currentNode = current.first;
2338
- uint8_t hops = current.second;
2351
+ uint16_t hops = current.second;
2339
2352
 
2340
2353
  // Found the target
2341
2354
  if (currentNode == nodeId) {
@@ -2349,7 +2362,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
2349
2362
  for (auto neighbor : neighbors) {
2350
2363
  if (visited.find(neighbor) == visited.end()) {
2351
2364
  visited.insert(neighbor);
2352
- queue.push({neighbor, static_cast<uint8_t>(hops + 1)});
2365
+ queue.push({neighbor, static_cast<uint16_t>(hops + 1)});
2353
2366
  }
2354
2367
  }
2355
2368
  }
@@ -2478,7 +2491,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
2478
2491
  // Reconstruct path from parent map
2479
2492
  if (found) {
2480
2493
  uint32_t current = nodeId;
2481
- while (current != 0) {
2494
+ size_t maxIter = parent.size() + 1;
2495
+ while (current != 0 && maxIter-- > 0) {
2482
2496
  path.insert(path.begin(), current);
2483
2497
  current = parent[current];
2484
2498
  }
@@ -2523,7 +2537,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
2523
2537
  uint32_t totalMessagesDropped = 0;
2524
2538
  uint32_t totalLatency = 0;
2525
2539
  uint32_t latencySampleCount = 0;
2526
- int8_t sumRSSI = 0;
2540
+ int32_t sumRSSI = 0;
2527
2541
  int8_t minRSSI = 0;
2528
2542
  int8_t maxRSSI = -127;
2529
2543
  uint32_t rssiCount = 0;
@@ -2820,76 +2834,6 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
2820
2834
  return lastBridgeChange;
2821
2835
  }
2822
2836
 
2823
- /**
2824
- * Get Internet path to specific node
2825
- *
2826
- * Returns the routing path from the specified node to the Internet bridge.
2827
- * Path includes all intermediate nodes from source to bridge.
2828
- *
2829
- * \code
2830
- * auto path = mesh.getInternetPath(targetNodeId);
2831
- * Serial.print("Path to Internet: ");
2832
- * for (auto nodeId : path) {
2833
- * Serial.printf("%u -> ", nodeId);
2834
- * }
2835
- * Serial.println("Internet");
2836
- * \endcode
2837
- *
2838
- * @param nodeId Node to find path from
2839
- * @return Vector of node IDs representing the path (empty if no path)
2840
- */
2841
- std::vector<uint32_t> getInternetPath(uint32_t nodeId) {
2842
- std::vector<uint32_t> path;
2843
-
2844
- // Get primary bridge
2845
- auto primaryBridge = this->getPrimaryBridge();
2846
- if (primaryBridge == nullptr) {
2847
- Log(logger::GENERAL, "getInternetPath(): No bridge available\n");
2848
- return path;
2849
- }
2850
-
2851
- // If requesting path for the bridge itself
2852
- if (nodeId == primaryBridge->nodeId) {
2853
- path.push_back(nodeId);
2854
- return path;
2855
- }
2856
-
2857
- // Start with the target node
2858
- path.push_back(nodeId);
2859
-
2860
- // For now, simplified routing: if direct connection, add bridge
2861
- // TODO: Implement proper multi-hop path discovery
2862
- if (this->isConnected(primaryBridge->nodeId)) {
2863
- path.push_back(primaryBridge->nodeId);
2864
- }
2865
-
2866
- return path;
2867
- }
2868
-
2869
- /**
2870
- * Get bridge node ID for specific node
2871
- *
2872
- * Returns the bridge node ID that the specified node should use to reach Internet.
2873
- * For most cases, this is the primary bridge.
2874
- *
2875
- * \code
2876
- * uint32_t bridgeId = mesh.getBridgeForNodeId(targetNodeId);
2877
- * if (bridgeId != 0) {
2878
- * Serial.printf("Node %u uses bridge %u\n", targetNodeId, bridgeId);
2879
- * }
2880
- * \endcode
2881
- *
2882
- * @param nodeId Node to find bridge for
2883
- * @return Bridge node ID, or 0 if no bridge available
2884
- */
2885
- uint32_t getBridgeForNodeId(uint32_t nodeId) {
2886
- auto primaryBridge = this->getPrimaryBridge();
2887
- if (primaryBridge != nullptr) {
2888
- return primaryBridge->nodeId;
2889
- }
2890
- return 0;
2891
- }
2892
-
2893
2837
  /**
2894
2838
  * Export topology as DOT format (Graphviz)
2895
2839
  *
@@ -3250,7 +3194,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
3250
3194
  bool isExternalScheduler = false;
3251
3195
 
3252
3196
  /// Is the node a root node
3253
- bool shouldContainRoot;
3197
+ bool shouldContainRoot = false;
3254
3198
 
3255
3199
  Scheduler *mScheduler;
3256
3200
 
@@ -3265,7 +3209,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
3265
3209
  */
3266
3210
  bool semaphoreTake() {
3267
3211
  #ifdef ESP32
3268
- return xSemaphoreTake(xSemaphore, (TickType_t)100) == pdTRUE; // Was 10
3212
+ return xSemaphoreTake(xSemaphore, (TickType_t)1000) == pdTRUE;
3269
3213
  #else
3270
3214
  return true;
3271
3215
  #endif
@@ -104,9 +104,8 @@ public:
104
104
  * Constructor
105
105
  * @param maxSize Maximum number of messages in queue
106
106
  */
107
- explicit MessageQueue(uint32_t maxSize = 1000)
107
+ explicit MessageQueue(uint32_t maxSize = 1000)
108
108
  : maxQueueSize(maxSize), nextMessageId(1) {
109
- messages.reserve(maxSize);
110
109
  }
111
110
 
112
111
  /**
@@ -33,8 +33,7 @@ class Timer {
33
33
 
34
34
  uint32_t elapsed_us() const {
35
35
  #ifdef ESP32
36
- return static_cast<uint32_t>((esp_timer_get_time() - start_time_us_) /
37
- 1000);
36
+ return static_cast<uint32_t>(esp_timer_get_time() - start_time_us_);
38
37
  #else
39
38
  return elapsed_ms() * 1000; // Fallback to millisecond precision
40
39
  #endif
@@ -58,266 +57,7 @@ class Timer {
58
57
  #endif
59
58
  };
60
59
 
61
- /**
62
- * Message statistics tracking
63
- */
64
- struct MessageStats {
65
- uint32_t messages_sent = 0;
66
- uint32_t messages_received = 0;
67
- uint32_t messages_dropped = 0;
68
- uint32_t messages_retransmitted = 0;
69
- uint32_t bytes_sent = 0;
70
- uint32_t bytes_received = 0;
71
- uint32_t parse_errors = 0;
72
- uint32_t validation_errors = 0;
73
-
74
- // Latency tracking
75
- uint32_t min_latency_ms = UINT32_MAX;
76
- uint32_t max_latency_ms = 0;
77
- uint32_t total_latency_ms = 0;
78
- uint32_t latency_samples = 0;
79
-
80
- void record_sent_message(size_t bytes) {
81
- messages_sent++;
82
- bytes_sent += bytes;
83
- }
84
-
85
- void record_received_message(size_t bytes) {
86
- messages_received++;
87
- bytes_received += bytes;
88
- }
89
-
90
- void record_dropped_message() { messages_dropped++; }
91
-
92
- void record_retransmission() { messages_retransmitted++; }
93
-
94
- void record_parse_error() { parse_errors++; }
95
-
96
- void record_validation_error() { validation_errors++; }
97
-
98
- void record_latency(uint32_t latency_ms) {
99
- min_latency_ms = std::min(min_latency_ms, latency_ms);
100
- max_latency_ms = std::max(max_latency_ms, latency_ms);
101
- total_latency_ms += latency_ms;
102
- latency_samples++;
103
- }
104
-
105
- uint32_t average_latency_ms() const {
106
- return latency_samples > 0 ? total_latency_ms / latency_samples : 0;
107
- }
108
-
109
- double throughput_bps() const {
110
- #ifdef ARDUINO
111
- uint32_t uptime_s = millis() / 1000;
112
- #else
113
- // For non-Arduino builds, use a simpler approach or disable this feature
114
- uint32_t uptime_s = 1; // Avoid division by zero
115
- #endif
116
- return uptime_s > 0 ? (bytes_sent + bytes_received) * 8.0 / uptime_s : 0.0;
117
- }
118
-
119
- double packet_loss_rate() const {
120
- uint32_t total_attempted = messages_sent + messages_dropped;
121
- return total_attempted > 0
122
- ? static_cast<double>(messages_dropped) / total_attempted
123
- : 0.0;
124
- }
125
-
126
- void reset() { *this = MessageStats{}; }
127
- };
128
-
129
- /**
130
- * Memory usage tracking
131
- */
132
- struct MemoryStats {
133
- uint32_t heap_free = 0;
134
- uint32_t heap_max_alloc = 0;
135
- uint32_t heap_min_free = UINT32_MAX;
136
- uint32_t psram_free = 0;
137
- uint32_t stack_high_water = 0;
138
-
139
- void update() {
140
- #ifdef ESP32
141
- heap_free = ESP.getFreeHeap();
142
- heap_max_alloc = ESP.getMaxAllocHeap();
143
- heap_min_free = std::min(heap_min_free, heap_free);
144
-
145
- #ifdef BOARD_HAS_PSRAM
146
- psram_free = ESP.getFreePsram();
147
- #endif
148
-
149
- stack_high_water = uxTaskGetStackHighWaterMark(NULL);
150
- #elif defined(ESP8266)
151
- heap_free = ESP.getFreeHeap();
152
- heap_max_alloc = ESP.getMaxFreeBlockSize();
153
- heap_min_free = std::min(heap_min_free, heap_free);
154
- #endif
155
- }
156
-
157
- bool is_memory_critical() const {
158
- return heap_free < 10000; // Less than 10KB free is critical
159
- }
160
-
161
- bool is_memory_low() const {
162
- return heap_free < 20000; // Less than 20KB free is low
163
- }
164
- };
165
-
166
- /**
167
- * Network topology metrics
168
- */
169
- struct TopologyStats {
170
- uint32_t node_count = 0;
171
- uint32_t connection_count = 0;
172
- uint32_t max_hops = 0;
173
- uint32_t connection_changes = 0;
174
- uint32_t failed_connections = 0;
175
-
176
- void record_topology_change(uint32_t nodes, uint32_t connections,
177
- uint32_t hops) {
178
- node_count = nodes;
179
- connection_count = connections;
180
- max_hops = hops;
181
- connection_changes++;
182
- }
183
-
184
- void record_failed_connection() { failed_connections++; }
185
-
186
- double connection_stability() const {
187
- return connection_changes > 0
188
- ? 1.0 - static_cast<double>(failed_connections) /
189
- connection_changes
190
- : 1.0;
191
- }
192
- };
193
-
194
- /**
195
- * Comprehensive metrics collector
196
- */
197
- class MetricsCollector {
198
- public:
199
- MetricsCollector() : start_time_(get_current_time()) {}
200
-
201
- MessageStats& message_stats() { return message_stats_; }
202
- MemoryStats& memory_stats() { return memory_stats_; }
203
- TopologyStats& topology_stats() { return topology_stats_; }
204
-
205
- const MessageStats& message_stats() const { return message_stats_; }
206
- const MemoryStats& memory_stats() const { return memory_stats_; }
207
- const TopologyStats& topology_stats() const { return topology_stats_; }
208
-
209
- /**
210
- * Update all metrics (call periodically)
211
- */
212
- void update() {
213
- memory_stats_.update();
214
- last_update_ = get_current_time();
215
- }
216
-
217
- /**
218
- * Get uptime in seconds
219
- */
220
- uint32_t uptime_seconds() const {
221
- return (get_current_time() - start_time_) / 1000;
222
- }
223
-
224
- /**
225
- * Generate JSON status report
226
- */
227
- TSTRING generate_status_json() const {
228
- TSTRING json = "{";
229
- json += "\"uptime\":";
230
- json += std::to_string(uptime_seconds());
231
- json += ",";
232
- json += "\"messages\":{";
233
- json += "\"sent\":";
234
- json += std::to_string(message_stats_.messages_sent);
235
- json += ",";
236
- json += "\"received\":";
237
- json += std::to_string(message_stats_.messages_received);
238
- json += ",";
239
- json += "\"dropped\":";
240
- json += std::to_string(message_stats_.messages_dropped);
241
- json += ",";
242
- json += "\"avg_latency\":";
243
- json += std::to_string(message_stats_.average_latency_ms());
244
- json += ",";
245
- json += "\"throughput\":";
246
- json += std::to_string(message_stats_.throughput_bps());
247
- json += ",";
248
- json += "\"packet_loss\":";
249
- json += std::to_string(message_stats_.packet_loss_rate());
250
- json += "},";
251
- json += "\"memory\":{";
252
- json += "\"heap_free\":";
253
- json += std::to_string(memory_stats_.heap_free);
254
- json += ",";
255
- json += "\"heap_min\":";
256
- json += std::to_string(memory_stats_.heap_min_free);
257
- json += ",";
258
- json += "\"critical\":";
259
- json += (memory_stats_.is_memory_critical() ? "true" : "false");
260
- json += "},";
261
- json += "\"topology\":{";
262
- json += "\"nodes\":";
263
- json += std::to_string(topology_stats_.node_count);
264
- json += ",";
265
- json += "\"connections\":";
266
- json += std::to_string(topology_stats_.connection_count);
267
- json += ",";
268
- json += "\"max_hops\":";
269
- json += std::to_string(topology_stats_.max_hops);
270
- json += ",";
271
- json += "\"stability\":";
272
- json += std::to_string(topology_stats_.connection_stability());
273
- json += "}";
274
- json += "}";
275
- return json;
276
- }
277
-
278
- /**
279
- * Reset all metrics
280
- */
281
- void reset() {
282
- message_stats_.reset();
283
- topology_stats_ = TopologyStats{};
284
- start_time_ = get_current_time();
285
- }
286
-
287
- /**
288
- * Check if metrics indicate performance issues
289
- */
290
- bool has_performance_issues() const {
291
- return memory_stats_.is_memory_critical() ||
292
- message_stats_.packet_loss_rate() > 0.1 || // > 10% packet loss
293
- topology_stats_.connection_stability() < 0.8; // < 80% stability
294
- }
295
-
296
- private:
297
- uint32_t get_current_time() const {
298
- #ifdef ARDUINO
299
- return millis();
300
- #else
301
- auto now = std::chrono::steady_clock::now();
302
- auto duration = now.time_since_epoch();
303
- return std::chrono::duration_cast<std::chrono::milliseconds>(duration)
304
- .count();
305
- #endif
306
- }
307
-
308
- MessageStats message_stats_;
309
- MemoryStats memory_stats_;
310
- TopologyStats topology_stats_;
311
- uint32_t start_time_;
312
- uint32_t last_update_ = 0;
313
- };
314
-
315
- // Global metrics instance (optional, can be disabled)
316
- #ifdef PAINLESS_MESH_ENABLE_METRICS
317
- extern MetricsCollector global_metrics;
318
- #endif
319
-
320
60
  } // namespace metrics
321
61
  } // namespace painlessmesh
322
62
 
323
- #endif // _PAINLESS_MESH_METRICS_HPP_
63
+ #endif // _PAINLESS_MESH_METRICS_HPP_
@@ -8,12 +8,9 @@
8
8
  * security and robustness of the mesh network.
9
9
  */
10
10
 
11
- #include <list>
12
- #include <map>
13
11
  #ifndef ARDUINO
14
12
  #include <chrono>
15
13
  #endif
16
- #include "ArduinoJson.h"
17
14
  #include "painlessmesh/configuration.hpp"
18
15
 
19
16
  namespace painlessmesh {
@@ -45,146 +42,6 @@ struct ValidationConfig {
45
42
  bool strict_type_checking = true; // Enable strict type validation
46
43
  };
47
44
 
48
- /**
49
- * Rate limiting for preventing message spam
50
- */
51
- class RateLimiter {
52
- public:
53
- RateLimiter(size_t max_messages_per_second = 10, size_t window_size_ms = 1000)
54
- : max_messages_(max_messages_per_second), window_size_(window_size_ms) {}
55
-
56
- bool allow_message(uint32_t node_id) {
57
- uint32_t current_time = get_current_time();
58
- auto& history = node_history_[node_id];
59
-
60
- // Remove old entries outside the window
61
- while (!history.empty() &&
62
- (current_time - history.front()) > window_size_) {
63
- history.pop_front();
64
- }
65
-
66
- // Check if limit is exceeded
67
- if (history.size() >= max_messages_) {
68
- return false;
69
- }
70
-
71
- // Record this message
72
- history.push_back(current_time);
73
- return true;
74
- }
75
-
76
- void clear_node_history(uint32_t node_id) { node_history_.erase(node_id); }
77
-
78
- void clear_all_history() { node_history_.clear(); }
79
-
80
- private:
81
- uint32_t get_current_time() const {
82
- #ifdef ARDUINO
83
- return millis();
84
- #else
85
- auto now = std::chrono::steady_clock::now();
86
- auto duration = now.time_since_epoch();
87
- return std::chrono::duration_cast<std::chrono::milliseconds>(duration)
88
- .count();
89
- #endif
90
- }
91
-
92
- size_t max_messages_;
93
- size_t window_size_;
94
- std::map<uint32_t, std::list<uint32_t>> node_history_;
95
- };
96
-
97
- /**
98
- * JSON message validator
99
- */
100
- class MessageValidator {
101
- public:
102
- explicit MessageValidator(const ValidationConfig& config = ValidationConfig{})
103
- : config_(config) {}
104
-
105
- /**
106
- * Validate a JSON message for basic structure and security
107
- */
108
- ValidationResult validate_message(const JsonObject& obj,
109
- size_t message_size = 0) const {
110
- // Check message size
111
- if (message_size > config_.max_message_size) {
112
- return ValidationResult::MESSAGE_TOO_LARGE;
113
- }
114
-
115
- // Check required fields based on message type
116
- if (!obj["type"].is<int>()) {
117
- return ValidationResult::MISSING_REQUIRED_FIELD;
118
- }
119
-
120
- // Validate node IDs if present
121
- if (obj["from"].is<uint32_t>()) {
122
- uint32_t from_id = obj["from"].as<uint32_t>();
123
- if (!is_valid_node_id(from_id)) {
124
- return ValidationResult::INVALID_NODE_ID;
125
- }
126
- }
127
-
128
- if (obj["dest"].is<uint32_t>()) {
129
- uint32_t dest_id = obj["dest"].as<uint32_t>();
130
- if (dest_id != 0 && !is_valid_node_id(dest_id)) { // 0 is broadcast
131
- return ValidationResult::INVALID_NODE_ID;
132
- }
133
- }
134
-
135
- // Validate string fields
136
- for (JsonPair pair : obj) {
137
- if (pair.value().is<const char*>()) {
138
- const char* str_value = pair.value().as<const char*>();
139
- if (strlen(str_value) > config_.max_string_length) {
140
- return ValidationResult::INVALID_FIELD_VALUE;
141
- }
142
- }
143
- }
144
-
145
- return ValidationResult::VALID;
146
- }
147
-
148
- /**
149
- * Validate node ID range
150
- */
151
- bool is_valid_node_id(uint32_t node_id) const {
152
- return node_id >= config_.min_node_id && node_id <= config_.max_node_id;
153
- }
154
-
155
- /**
156
- * Get validation error message
157
- */
158
- const char* get_error_message(ValidationResult result) const {
159
- switch (result) {
160
- case ValidationResult::VALID:
161
- return "Valid";
162
- case ValidationResult::INVALID_JSON:
163
- return "Invalid JSON format";
164
- case ValidationResult::MISSING_REQUIRED_FIELD:
165
- return "Missing required field";
166
- case ValidationResult::INVALID_FIELD_TYPE:
167
- return "Invalid field type";
168
- case ValidationResult::INVALID_FIELD_VALUE:
169
- return "Invalid field value";
170
- case ValidationResult::MESSAGE_TOO_LARGE:
171
- return "Message too large";
172
- case ValidationResult::INVALID_NODE_ID:
173
- return "Invalid node ID";
174
- case ValidationResult::RATE_LIMIT_EXCEEDED:
175
- return "Rate limit exceeded";
176
- default:
177
- return "Unknown error";
178
- }
179
- }
180
-
181
- const ValidationConfig& get_config() const { return config_; }
182
- void set_config(const ValidationConfig& config) { config_ = config; }
183
-
184
- private:
185
- ValidationConfig config_;
186
- };
187
-
188
45
  /**
189
46
  * Secure random number generation for mesh operations
190
47
  */