@alteriom/painlessmesh 1.6.1 → 1.7.2

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 (129) hide show
  1. package/CHANGELOG.md +380 -143
  2. package/LICENSE +674 -674
  3. package/README.md +477 -434
  4. package/RELEASE_GUIDE.md +504 -418
  5. package/docs/DOCUMENTATION_MIGRATION_PLAN.md +175 -175
  6. package/docs/IMPLEMENTATION_PLAN_MESH_TOPOLOGY.md +1062 -0
  7. package/docs/MESH_TOPOLOGY_GUIDE.md +992 -0
  8. package/docs/MESH_TOPOLOGY_PROGRESS.md +422 -0
  9. package/docs/MQTT_BRIDGE_COMMANDS.md +894 -0
  10. package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +324 -0
  11. package/docs/MQTT_COMMAND_SCHEMA_PROPOSAL.md +576 -0
  12. package/docs/MQTT_SCHEMA_COMPLIANCE.md +285 -0
  13. package/docs/MQTT_SCHEMA_PROPOSALS.md +446 -0
  14. package/docs/MQTT_SCHEMA_REVIEW.md +690 -0
  15. package/docs/OTA_COMMANDS_REFERENCE.md +554 -0
  16. package/docs/PHASE1_GUIDE.md +349 -0
  17. package/docs/PHASE2_GUIDE.md +543 -0
  18. package/docs/README.md +77 -70
  19. package/docs/SCHEMA_VALIDATION_CHECKLIST.md +222 -0
  20. package/docs/alteriom/overview.md +507 -507
  21. package/docs/api/core-api.md +606 -606
  22. package/docs/architecture/mesh-architecture.md +378 -378
  23. package/docs/architecture/plugin-system.md +516 -516
  24. package/docs/getting-started/first-mesh.md +409 -409
  25. package/docs/getting-started/installation.md +274 -274
  26. package/docs/getting-started/quickstart.md +157 -157
  27. package/docs/improvements/FEATURE_PROPOSALS.md +337 -0
  28. package/docs/improvements/PHASE1_IMPLEMENTATION.md +325 -0
  29. package/docs/improvements/PHASE2_IMPLEMENTATION.md +567 -0
  30. package/docs/improvements/README.md +86 -68
  31. package/docs/improvements/ota-and-status-enhancements.md +911 -0
  32. package/docs/improvements/ota-status-architecture-diagrams.md +658 -0
  33. package/docs/improvements/ota-status-quick-reference.md +284 -0
  34. package/docs/platformio-publishing.md +255 -0
  35. package/docs/platformio-setup-summary.md +121 -0
  36. package/docs/troubleshooting/common-issues.md +520 -520
  37. package/docs/troubleshooting/faq.md +472 -472
  38. package/docs/tutorials/basic-examples.md +717 -717
  39. package/docs/wiki/API-Reference.md +245 -245
  40. package/docs/wiki/Complete-Documentation.md +122 -122
  41. package/examples/alteriom/README.md +139 -81
  42. package/examples/alteriom/alteriom.ino +186 -185
  43. package/examples/alteriom/alteriom_sensor_package.hpp +240 -127
  44. package/examples/alteriom/platformio.ini +24 -24
  45. package/examples/alteriomImproved/alteriom_sensor_package.hpp +224 -0
  46. package/examples/{alteriom → alteriomImproved}/improved_sensor_node.ino +245 -245
  47. package/examples/alteriomImproved/platformio.ini +25 -0
  48. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +224 -0
  49. package/examples/alteriomPhase1/phase1_features.ino +242 -0
  50. package/examples/alteriomPhase1/platformio.ini +25 -0
  51. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +224 -0
  52. package/examples/alteriomPhase2/phase2_features.ino +186 -0
  53. package/examples/alteriomPhase2/platformio.ini +25 -0
  54. package/examples/{alteriom → alteriomSensorNode}/alteriom_sensor_node.ino +183 -183
  55. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +224 -0
  56. package/examples/alteriomSensorNode/platformio.ini +25 -0
  57. package/examples/basic/basic.ino +66 -66
  58. package/examples/basic/platformio.ini +25 -25
  59. package/examples/bridge/bridge.ino +51 -51
  60. package/examples/bridge/mesh_event_publisher.hpp +253 -0
  61. package/examples/bridge/mesh_topology_reporter.hpp +303 -0
  62. package/examples/bridge/mqtt_command_bridge.hpp +459 -0
  63. package/examples/bridge/mqtt_status_bridge.hpp +519 -0
  64. package/examples/bridge/platformio.ini +25 -25
  65. package/examples/echoNode/echoNode.ino +33 -33
  66. package/examples/echoNode/platformio.ini +25 -25
  67. package/examples/logClient/logClient.ino +109 -109
  68. package/examples/logClient/platformio.ini +25 -25
  69. package/examples/logServer/logServer.ino +81 -81
  70. package/examples/logServer/platformio.ini +25 -25
  71. package/examples/meshCommandNode/alteriom_sensor_package.hpp +235 -0
  72. package/examples/meshCommandNode/meshCommandNode.ino +263 -0
  73. package/examples/meshCommandNode/platformio.ini +25 -0
  74. package/examples/mqttBridge/mqttBridge.ino +118 -118
  75. package/examples/mqttBridge/platformio.ini +26 -26
  76. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +235 -0
  77. package/examples/mqttCommandBridge/mesh_event_publisher.hpp +253 -0
  78. package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +303 -0
  79. package/examples/mqttCommandBridge/mqttCommandBridge.ino +252 -0
  80. package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +459 -0
  81. package/examples/mqttCommandBridge/platformio.ini +26 -0
  82. package/examples/mqttStatusBridge/mqttStatusBridge.ino +216 -0
  83. package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +522 -0
  84. package/examples/mqttStatusBridge/platformio.ini +26 -0
  85. package/examples/mqttTopologyTest/README.md +467 -0
  86. package/examples/mqttTopologyTest/mqttTopologyTest.ino +748 -0
  87. package/examples/mqttTopologyTest/platformio.ini +26 -0
  88. package/examples/namedMesh/namedMesh.ino +97 -97
  89. package/examples/namedMesh/platformio.ini +25 -25
  90. package/examples/otaReceiver/otaReceiver.ino +79 -79
  91. package/examples/otaReceiver/platformio.ini +25 -25
  92. package/examples/otaSender/otaSender.ino +160 -151
  93. package/examples/otaSender/platformio.ini +25 -25
  94. package/examples/startHere/platformio.ini +25 -25
  95. package/examples/startHere/startHere.ino +159 -159
  96. package/examples/webServer/platformio.ini +27 -27
  97. package/examples/webServer/webServer.ino +89 -89
  98. package/keywords.txt +48 -48
  99. package/library.json +55 -34
  100. package/library.properties +10 -10
  101. package/package.json +86 -78
  102. package/src/AlteriomPainlessMesh.h +97 -97
  103. package/src/arduino/wifi.hpp +365 -365
  104. package/src/boost/asynctcp.hpp +279 -279
  105. package/src/painlessMesh.h +70 -70
  106. package/src/painlessMeshSTA.cpp +236 -236
  107. package/src/painlessMeshSTA.h +58 -58
  108. package/src/painlessTaskOptions.h +4 -4
  109. package/src/painlessmesh/base64.hpp +111 -111
  110. package/src/painlessmesh/buffer.hpp +229 -229
  111. package/src/painlessmesh/callback.hpp +91 -91
  112. package/src/painlessmesh/configuration.hpp +77 -77
  113. package/src/painlessmesh/connection.hpp +192 -192
  114. package/src/painlessmesh/layout.hpp +188 -188
  115. package/src/painlessmesh/logger.hpp +158 -158
  116. package/src/painlessmesh/memory.hpp +119 -119
  117. package/src/painlessmesh/mesh.hpp +761 -560
  118. package/src/painlessmesh/metrics.hpp +322 -322
  119. package/src/painlessmesh/ntp.hpp +263 -263
  120. package/src/painlessmesh/ota.hpp +582 -553
  121. package/src/painlessmesh/plugin.hpp +188 -188
  122. package/src/painlessmesh/protocol.hpp +813 -813
  123. package/src/painlessmesh/router.hpp +322 -322
  124. package/src/painlessmesh/tcp.hpp +71 -71
  125. package/src/painlessmesh/validation.hpp +238 -238
  126. package/src/plugin/performance.hpp +214 -214
  127. package/src/plugin/remote.hpp +64 -64
  128. package/src/scheduler.cpp +10 -10
  129. package/src/wifi.cpp +2 -2
@@ -1,71 +1,71 @@
1
- #ifndef _PAINLESS_MESH_TCP_HPP_
2
- #define _PAINLESS_MESH_TCP_HPP_
3
-
4
- #include <list>
5
-
6
- #include "Arduino.h"
7
- #include "painlessmesh/configuration.hpp"
8
-
9
- #include "painlessmesh/logger.hpp"
10
-
11
- namespace painlessmesh {
12
- namespace tcp {
13
- inline uint32_t encodeNodeId(const uint8_t *hwaddr) {
14
- using namespace painlessmesh::logger;
15
- Log(GENERAL, "encodeNodeId():\n");
16
- uint32_t value = 0;
17
-
18
- value |= hwaddr[2] << 24; // Big endian (aka "network order"):
19
- value |= hwaddr[3] << 16;
20
- value |= hwaddr[4] << 8;
21
- value |= hwaddr[5];
22
- return value;
23
- }
24
-
25
- template <class T, class M>
26
- void initServer(AsyncServer &server, M &mesh) {
27
- using namespace logger;
28
- server.setNoDelay(true);
29
-
30
- server.onClient(
31
- [&mesh](void *arg, AsyncClient *client) {
32
- if (mesh.semaphoreTake()) {
33
- Log(CONNECTION, "New AP connection incoming\n");
34
- auto conn = std::make_shared<T>(client, &mesh, false);
35
- conn->initTasks();
36
- mesh.subs.push_back(conn);
37
- mesh.semaphoreGive();
38
- }
39
- },
40
- NULL);
41
- server.begin();
42
- }
43
-
44
- template <class T, class M>
45
- void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh) {
46
- using namespace logger;
47
- client.onError([&mesh](void *, AsyncClient *client, int8_t err) {
48
- if (mesh.semaphoreTake()) {
49
- Log(CONNECTION, "tcp_err(): error trying to connect %d\n", err);
50
- mesh.droppedConnectionCallbacks.execute(0, true);
51
- mesh.semaphoreGive();
52
- }
53
- });
54
-
55
- client.onConnect(
56
- [&mesh](void *, AsyncClient *client) {
57
- if (mesh.semaphoreTake()) {
58
- Log(CONNECTION, "New STA connection incoming\n");
59
- auto conn = std::make_shared<T>(client, &mesh, true);
60
- conn->initTasks();
61
- mesh.subs.push_back(conn);
62
- mesh.semaphoreGive();
63
- }
64
- },
65
- NULL);
66
-
67
- client.connect(ip, port);
68
- }
69
- } // namespace tcp
70
- } // namespace painlessmesh
71
- #endif
1
+ #ifndef _PAINLESS_MESH_TCP_HPP_
2
+ #define _PAINLESS_MESH_TCP_HPP_
3
+
4
+ #include <list>
5
+
6
+ #include "Arduino.h"
7
+ #include "painlessmesh/configuration.hpp"
8
+
9
+ #include "painlessmesh/logger.hpp"
10
+
11
+ namespace painlessmesh {
12
+ namespace tcp {
13
+ inline uint32_t encodeNodeId(const uint8_t *hwaddr) {
14
+ using namespace painlessmesh::logger;
15
+ Log(GENERAL, "encodeNodeId():\n");
16
+ uint32_t value = 0;
17
+
18
+ value |= hwaddr[2] << 24; // Big endian (aka "network order"):
19
+ value |= hwaddr[3] << 16;
20
+ value |= hwaddr[4] << 8;
21
+ value |= hwaddr[5];
22
+ return value;
23
+ }
24
+
25
+ template <class T, class M>
26
+ void initServer(AsyncServer &server, M &mesh) {
27
+ using namespace logger;
28
+ server.setNoDelay(true);
29
+
30
+ server.onClient(
31
+ [&mesh](void *arg, AsyncClient *client) {
32
+ if (mesh.semaphoreTake()) {
33
+ Log(CONNECTION, "New AP connection incoming\n");
34
+ auto conn = std::make_shared<T>(client, &mesh, false);
35
+ conn->initTasks();
36
+ mesh.subs.push_back(conn);
37
+ mesh.semaphoreGive();
38
+ }
39
+ },
40
+ NULL);
41
+ server.begin();
42
+ }
43
+
44
+ template <class T, class M>
45
+ void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh) {
46
+ using namespace logger;
47
+ client.onError([&mesh](void *, AsyncClient *client, int8_t err) {
48
+ if (mesh.semaphoreTake()) {
49
+ Log(CONNECTION, "tcp_err(): error trying to connect %d\n", err);
50
+ mesh.droppedConnectionCallbacks.execute(0, true);
51
+ mesh.semaphoreGive();
52
+ }
53
+ });
54
+
55
+ client.onConnect(
56
+ [&mesh](void *, AsyncClient *client) {
57
+ if (mesh.semaphoreTake()) {
58
+ Log(CONNECTION, "New STA connection incoming\n");
59
+ auto conn = std::make_shared<T>(client, &mesh, true);
60
+ conn->initTasks();
61
+ mesh.subs.push_back(conn);
62
+ mesh.semaphoreGive();
63
+ }
64
+ },
65
+ NULL);
66
+
67
+ client.connect(ip, port);
68
+ }
69
+ } // namespace tcp
70
+ } // namespace painlessmesh
71
+ #endif
@@ -1,239 +1,239 @@
1
- #ifndef _PAINLESS_MESH_VALIDATION_HPP_
2
- #define _PAINLESS_MESH_VALIDATION_HPP_
3
-
4
- /**
5
- * Input validation and security utilities for painlessMesh
6
- *
7
- * This module provides comprehensive validation utilities to improve
8
- * security and robustness of the mesh network.
9
- */
10
-
11
- #include <list>
12
- #include <map>
13
- #ifndef ARDUINO
14
- #include <chrono>
15
- #endif
16
- #include "ArduinoJson.h"
17
- #include "painlessmesh/configuration.hpp"
18
-
19
- namespace painlessmesh {
20
- namespace validation {
21
-
22
- /**
23
- * Message validation result
24
- */
25
- enum class ValidationResult {
26
- VALID = 0,
27
- INVALID_JSON,
28
- MISSING_REQUIRED_FIELD,
29
- INVALID_FIELD_TYPE,
30
- INVALID_FIELD_VALUE,
31
- MESSAGE_TOO_LARGE,
32
- INVALID_NODE_ID,
33
- RATE_LIMIT_EXCEEDED
34
- };
35
-
36
- /**
37
- * Configuration for message validation
38
- */
39
- struct ValidationConfig {
40
- size_t max_message_size = 8192; // Maximum message size in bytes
41
- size_t max_string_length = 1024; // Maximum string field length
42
- uint32_t min_node_id = 1; // Minimum valid node ID
43
- uint32_t max_node_id = 0xFFFFFFFF; // Maximum valid node ID
44
- size_t max_nesting_depth = 10; // Maximum JSON nesting depth
45
- bool strict_type_checking = true; // Enable strict type validation
46
- };
47
-
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
- /**
189
- * Secure random number generation for mesh operations
190
- */
191
- class SecureRandom {
192
- public:
193
- /**
194
- * Generate a cryptographically secure random number
195
- * Falls back to pseudo-random if hardware RNG is not available
196
- */
197
- static uint32_t generate() {
198
- #ifdef ESP32
199
- return esp_random();
200
- #elif defined(ESP8266)
201
- return RANDOM_REG32;
202
- #else
203
- // Fallback for other platforms - use system rand with better seeding
204
- static bool seeded = false;
205
- if (!seeded) {
206
- #ifdef ARDUINO
207
- // Arduino-compatible seeding
208
- srand(millis() ^ analogRead(A0));
209
- #else
210
- // Non-Arduino platforms with std::chrono
211
- auto now = std::chrono::steady_clock::now();
212
- auto duration = now.time_since_epoch();
213
- uint32_t seed =
214
- std::chrono::duration_cast<std::chrono::microseconds>(duration)
215
- .count();
216
- srand(seed);
217
- #endif
218
- seeded = true;
219
- }
220
- return ((uint32_t)rand() << 16) | rand();
221
- #endif
222
- }
223
-
224
- /**
225
- * Generate random bytes into buffer
226
- */
227
- static void generate_bytes(uint8_t* buffer, size_t length) {
228
- for (size_t i = 0; i < length; i += sizeof(uint32_t)) {
229
- uint32_t random_val = generate();
230
- size_t copy_len = std::min(sizeof(uint32_t), length - i);
231
- memcpy(buffer + i, &random_val, copy_len);
232
- }
233
- }
234
- };
235
-
236
- } // namespace validation
237
- } // namespace painlessmesh
238
-
1
+ #ifndef _PAINLESS_MESH_VALIDATION_HPP_
2
+ #define _PAINLESS_MESH_VALIDATION_HPP_
3
+
4
+ /**
5
+ * Input validation and security utilities for painlessMesh
6
+ *
7
+ * This module provides comprehensive validation utilities to improve
8
+ * security and robustness of the mesh network.
9
+ */
10
+
11
+ #include <list>
12
+ #include <map>
13
+ #ifndef ARDUINO
14
+ #include <chrono>
15
+ #endif
16
+ #include "ArduinoJson.h"
17
+ #include "painlessmesh/configuration.hpp"
18
+
19
+ namespace painlessmesh {
20
+ namespace validation {
21
+
22
+ /**
23
+ * Message validation result
24
+ */
25
+ enum class ValidationResult {
26
+ VALID = 0,
27
+ INVALID_JSON,
28
+ MISSING_REQUIRED_FIELD,
29
+ INVALID_FIELD_TYPE,
30
+ INVALID_FIELD_VALUE,
31
+ MESSAGE_TOO_LARGE,
32
+ INVALID_NODE_ID,
33
+ RATE_LIMIT_EXCEEDED
34
+ };
35
+
36
+ /**
37
+ * Configuration for message validation
38
+ */
39
+ struct ValidationConfig {
40
+ size_t max_message_size = 8192; // Maximum message size in bytes
41
+ size_t max_string_length = 1024; // Maximum string field length
42
+ uint32_t min_node_id = 1; // Minimum valid node ID
43
+ uint32_t max_node_id = 0xFFFFFFFF; // Maximum valid node ID
44
+ size_t max_nesting_depth = 10; // Maximum JSON nesting depth
45
+ bool strict_type_checking = true; // Enable strict type validation
46
+ };
47
+
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
+ /**
189
+ * Secure random number generation for mesh operations
190
+ */
191
+ class SecureRandom {
192
+ public:
193
+ /**
194
+ * Generate a cryptographically secure random number
195
+ * Falls back to pseudo-random if hardware RNG is not available
196
+ */
197
+ static uint32_t generate() {
198
+ #ifdef ESP32
199
+ return esp_random();
200
+ #elif defined(ESP8266)
201
+ return RANDOM_REG32;
202
+ #else
203
+ // Fallback for other platforms - use system rand with better seeding
204
+ static bool seeded = false;
205
+ if (!seeded) {
206
+ #ifdef ARDUINO
207
+ // Arduino-compatible seeding
208
+ srand(millis() ^ analogRead(A0));
209
+ #else
210
+ // Non-Arduino platforms with std::chrono
211
+ auto now = std::chrono::steady_clock::now();
212
+ auto duration = now.time_since_epoch();
213
+ uint32_t seed =
214
+ std::chrono::duration_cast<std::chrono::microseconds>(duration)
215
+ .count();
216
+ srand(seed);
217
+ #endif
218
+ seeded = true;
219
+ }
220
+ return ((uint32_t)rand() << 16) | rand();
221
+ #endif
222
+ }
223
+
224
+ /**
225
+ * Generate random bytes into buffer
226
+ */
227
+ static void generate_bytes(uint8_t* buffer, size_t length) {
228
+ for (size_t i = 0; i < length; i += sizeof(uint32_t)) {
229
+ uint32_t random_val = generate();
230
+ size_t copy_len = std::min(sizeof(uint32_t), length - i);
231
+ memcpy(buffer + i, &random_val, copy_len);
232
+ }
233
+ }
234
+ };
235
+
236
+ } // namespace validation
237
+ } // namespace painlessmesh
238
+
239
239
  #endif // _PAINLESS_MESH_VALIDATION_HPP_