@alteriom/painlessmesh 1.6.1
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.
- package/CHANGELOG.md +144 -0
- package/LICENSE +674 -0
- package/README.md +434 -0
- package/RELEASE_GUIDE.md +419 -0
- package/docs/DOCUMENTATION_MIGRATION_PLAN.md +176 -0
- package/docs/README.md +71 -0
- package/docs/alteriom/overview.md +508 -0
- package/docs/api/core-api.md +607 -0
- package/docs/architecture/mesh-architecture.md +379 -0
- package/docs/architecture/plugin-system.md +517 -0
- package/docs/getting-started/first-mesh.md +410 -0
- package/docs/getting-started/installation.md +275 -0
- package/docs/getting-started/quickstart.md +158 -0
- package/docs/improvements/README.md +69 -0
- package/docs/troubleshooting/common-issues.md +521 -0
- package/docs/troubleshooting/faq.md +473 -0
- package/docs/tutorials/basic-examples.md +718 -0
- package/docs/wiki/API-Reference.md +246 -0
- package/docs/wiki/Complete-Documentation.md +123 -0
- package/examples/alteriom/README.md +82 -0
- package/examples/alteriom/alteriom.ino +186 -0
- package/examples/alteriom/alteriom_sensor_node.ino +184 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +128 -0
- package/examples/alteriom/improved_sensor_node.ino +246 -0
- package/examples/alteriom/platformio.ini +25 -0
- package/examples/basic/basic.ino +66 -0
- package/examples/basic/platformio.ini +25 -0
- package/examples/bridge/bridge.ino +51 -0
- package/examples/bridge/platformio.ini +25 -0
- package/examples/echoNode/echoNode.ino +33 -0
- package/examples/echoNode/platformio.ini +25 -0
- package/examples/logClient/logClient.ino +109 -0
- package/examples/logClient/platformio.ini +25 -0
- package/examples/logServer/logServer.ino +81 -0
- package/examples/logServer/platformio.ini +25 -0
- package/examples/mqttBridge/mqttBridge.ino +118 -0
- package/examples/mqttBridge/platformio.ini +26 -0
- package/examples/namedMesh/namedMesh.ino +97 -0
- package/examples/namedMesh/platformio.ini +25 -0
- package/examples/otaReceiver/otaReceiver.ino +79 -0
- package/examples/otaReceiver/platformio.ini +25 -0
- package/examples/otaSender/nodemcu32s_connections.JPG +0 -0
- package/examples/otaSender/otaSender.ino +151 -0
- package/examples/otaSender/platformio.ini +25 -0
- package/examples/startHere/platformio.ini +25 -0
- package/examples/startHere/startHere.ino +159 -0
- package/examples/webServer/platformio.ini +27 -0
- package/examples/webServer/webServer.ino +89 -0
- package/keywords.txt +49 -0
- package/library.json +34 -0
- package/library.properties +11 -0
- package/package.json +78 -0
- package/src/AlteriomPainlessMesh.h +98 -0
- package/src/arduino/wifi.hpp +365 -0
- package/src/boost/asynctcp.hpp +279 -0
- package/src/painlessMesh.h +70 -0
- package/src/painlessMeshSTA.cpp +236 -0
- package/src/painlessMeshSTA.h +58 -0
- package/src/painlessTaskOptions.h +4 -0
- package/src/painlessmesh/base64.hpp +111 -0
- package/src/painlessmesh/buffer.hpp +229 -0
- package/src/painlessmesh/callback.hpp +91 -0
- package/src/painlessmesh/configuration.hpp +77 -0
- package/src/painlessmesh/connection.hpp +192 -0
- package/src/painlessmesh/layout.hpp +188 -0
- package/src/painlessmesh/logger.hpp +158 -0
- package/src/painlessmesh/memory.hpp +120 -0
- package/src/painlessmesh/mesh.hpp +560 -0
- package/src/painlessmesh/metrics.hpp +323 -0
- package/src/painlessmesh/ntp.hpp +263 -0
- package/src/painlessmesh/ota.hpp +553 -0
- package/src/painlessmesh/plugin.hpp +188 -0
- package/src/painlessmesh/protocol.hpp +813 -0
- package/src/painlessmesh/router.hpp +322 -0
- package/src/painlessmesh/tcp.hpp +71 -0
- package/src/painlessmesh/validation.hpp +239 -0
- package/src/plugin/performance.hpp +214 -0
- package/src/plugin/remote.hpp +64 -0
- package/src/scheduler.cpp +10 -0
- package/src/wifi.cpp +2 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#ifndef _PAINLESS_MESH_ROUTER_HPP_
|
|
2
|
+
#define _PAINLESS_MESH_ROUTER_HPP_
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <memory>
|
|
6
|
+
|
|
7
|
+
#include "painlessmesh/callback.hpp"
|
|
8
|
+
#include "painlessmesh/layout.hpp"
|
|
9
|
+
#include "painlessmesh/logger.hpp"
|
|
10
|
+
#include "painlessmesh/protocol.hpp"
|
|
11
|
+
|
|
12
|
+
extern painlessmesh::logger::LogClass Log;
|
|
13
|
+
|
|
14
|
+
namespace painlessmesh {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Helper functions to route messages
|
|
18
|
+
*/
|
|
19
|
+
namespace router {
|
|
20
|
+
template <class T>
|
|
21
|
+
std::shared_ptr<T> findRoute(layout::Layout<T> tree,
|
|
22
|
+
std::function<bool(std::shared_ptr<T>)> func) {
|
|
23
|
+
auto route = std::find_if(tree.subs.begin(), tree.subs.end(), func);
|
|
24
|
+
if (route == tree.subs.end()) return NULL;
|
|
25
|
+
return (*route);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
template <class T>
|
|
29
|
+
std::shared_ptr<T> findRoute(layout::Layout<T> tree, uint32_t nodeId) {
|
|
30
|
+
return findRoute<T>(tree, [nodeId](std::shared_ptr<T> s) {
|
|
31
|
+
return layout::contains((*s), nodeId);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
template <class T, class U>
|
|
36
|
+
bool send(T& package, std::shared_ptr<U> conn, bool priority = false) {
|
|
37
|
+
painlessmesh::protocol::Variant variant(package);
|
|
38
|
+
TSTRING msg;
|
|
39
|
+
variant.printTo(msg);
|
|
40
|
+
return conn->addMessage(msg, priority);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
template <class T, class U>
|
|
44
|
+
bool send(T&& package, std::shared_ptr<U> conn, bool priority = false) {
|
|
45
|
+
painlessmesh::protocol::Variant variant(package);
|
|
46
|
+
TSTRING msg;
|
|
47
|
+
variant.printTo(msg);
|
|
48
|
+
return conn->addMessage(msg, priority);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
template <class U>
|
|
52
|
+
bool send(protocol::Variant& variant, std::shared_ptr<U> conn,
|
|
53
|
+
bool priority = false) {
|
|
54
|
+
TSTRING msg;
|
|
55
|
+
variant.printTo(msg);
|
|
56
|
+
return conn->addMessage(msg, priority);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
template <class U>
|
|
60
|
+
bool send(protocol::Variant&& variant, std::shared_ptr<U> conn,
|
|
61
|
+
bool priority = false) {
|
|
62
|
+
TSTRING msg;
|
|
63
|
+
variant.printTo(msg);
|
|
64
|
+
return conn->addMessage(msg, priority);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
template <class T, class U>
|
|
68
|
+
bool send(T& package, layout::Layout<U> layout) {
|
|
69
|
+
painlessmesh::protocol::Variant variant(package);
|
|
70
|
+
TSTRING msg;
|
|
71
|
+
variant.printTo(msg);
|
|
72
|
+
auto conn = findRoute<U>(layout, variant.dest());
|
|
73
|
+
if (conn) return conn->addMessage(msg);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
template <class U>
|
|
78
|
+
bool send(protocol::Variant& variant, layout::Layout<U> layout) {
|
|
79
|
+
TSTRING msg;
|
|
80
|
+
variant.printTo(msg);
|
|
81
|
+
auto conn = findRoute<U>(layout, variant.dest());
|
|
82
|
+
if (conn) return conn->addMessage(msg);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
template <class T, class U>
|
|
87
|
+
bool send(T&& package, layout::Layout<U> layout) {
|
|
88
|
+
painlessmesh::protocol::Variant variant(package);
|
|
89
|
+
TSTRING msg;
|
|
90
|
+
variant.printTo(msg);
|
|
91
|
+
auto conn = findRoute<U>(layout, variant.dest());
|
|
92
|
+
if (conn) return conn->addMessage(msg);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
template <class U>
|
|
97
|
+
bool send(protocol::Variant&& variant, layout::Layout<U> layout) {
|
|
98
|
+
TSTRING msg;
|
|
99
|
+
variant.printTo(msg);
|
|
100
|
+
auto conn = findRoute<U>(layout, variant.dest());
|
|
101
|
+
if (conn) return conn->addMessage(msg);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
template <class T, class U>
|
|
106
|
+
size_t broadcast(T& package, layout::Layout<U> layout, uint32_t exclude) {
|
|
107
|
+
painlessmesh::protocol::Variant variant(package);
|
|
108
|
+
TSTRING msg;
|
|
109
|
+
variant.printTo(msg);
|
|
110
|
+
size_t i = 0;
|
|
111
|
+
for (auto&& conn : layout.subs) {
|
|
112
|
+
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
113
|
+
auto sent = conn->addMessage(msg);
|
|
114
|
+
if (sent) ++i;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return i;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
template <class T, class U>
|
|
121
|
+
size_t broadcast(T&& package, layout::Layout<U> layout, uint32_t exclude) {
|
|
122
|
+
painlessmesh::protocol::Variant variant(package);
|
|
123
|
+
TSTRING msg;
|
|
124
|
+
variant.printTo(msg);
|
|
125
|
+
size_t i = 0;
|
|
126
|
+
for (auto&& conn : layout.subs) {
|
|
127
|
+
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
128
|
+
auto sent = conn->addMessage(msg);
|
|
129
|
+
if (sent) ++i;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return i;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
template <class T>
|
|
136
|
+
size_t broadcast(protocol::Variant& variant, layout::Layout<T> layout,
|
|
137
|
+
uint32_t exclude) {
|
|
138
|
+
TSTRING msg;
|
|
139
|
+
variant.printTo(msg);
|
|
140
|
+
size_t i = 0;
|
|
141
|
+
for (auto&& conn : layout.subs) {
|
|
142
|
+
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
143
|
+
auto sent = conn->addMessage(msg);
|
|
144
|
+
if (sent) ++i;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return i;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
template <class T>
|
|
151
|
+
size_t broadcast(protocol::Variant&& variant, layout::Layout<T> layout,
|
|
152
|
+
uint32_t exclude) {
|
|
153
|
+
TSTRING msg;
|
|
154
|
+
variant.printTo(msg);
|
|
155
|
+
size_t i = 0;
|
|
156
|
+
for (auto&& conn : layout.subs) {
|
|
157
|
+
if (conn->nodeId != 0 && conn->nodeId != exclude) {
|
|
158
|
+
auto sent = conn->addMessage(msg);
|
|
159
|
+
if (sent) ++i;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return i;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
template <class T>
|
|
166
|
+
void routePackage(layout::Layout<T> layout, std::shared_ptr<T> connection,
|
|
167
|
+
const TSTRING& pkg, callback::MeshPackageCallbackList<T> cbl,
|
|
168
|
+
uint32_t receivedAt) {
|
|
169
|
+
using namespace logger;
|
|
170
|
+
Log(COMMUNICATION, "routePackage(): Recvd from %u: %s\n", connection->nodeId,
|
|
171
|
+
pkg.c_str());
|
|
172
|
+
#if ARDUINOJSON_VERSION_MAJOR == 7
|
|
173
|
+
protocol::Variant variant(pkg);
|
|
174
|
+
if (variant.error) {
|
|
175
|
+
Log(ERROR,
|
|
176
|
+
"routePackage(): parsing failed. err=%u, total_length=%d, data=%s<--\n",
|
|
177
|
+
variant.error, pkg.length(), pkg.c_str());
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (variant.routing() == SINGLE && variant.dest() != layout.getNodeId()) {
|
|
182
|
+
// Send on without further processing
|
|
183
|
+
send<T>(variant, layout);
|
|
184
|
+
return;
|
|
185
|
+
} else if (variant.routing() == BROADCAST) {
|
|
186
|
+
broadcast<T>(variant, layout, connection->nodeId);
|
|
187
|
+
}
|
|
188
|
+
auto calls = cbl.execute(variant.type(), variant, connection, receivedAt);
|
|
189
|
+
if (calls == 0)
|
|
190
|
+
Log(DEBUG, "routePackage(): No callbacks executed; %u, %s\n",
|
|
191
|
+
variant.type(), pkg.c_str());
|
|
192
|
+
#else
|
|
193
|
+
static size_t baseCapacity = 512;
|
|
194
|
+
// Using a ptr so we can overwrite it if we need to grow capacity.
|
|
195
|
+
// Bug in copy constructor with grown capacity can cause segmentation fault
|
|
196
|
+
auto variant =
|
|
197
|
+
std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
|
|
198
|
+
while (variant->error == DeserializationError::NoMemory &&
|
|
199
|
+
baseCapacity <= 20480) {
|
|
200
|
+
// Not enough memory, adapt scaling (variant::capacityScaling) and log the
|
|
201
|
+
// new value
|
|
202
|
+
Log(DEBUG,
|
|
203
|
+
"routePackage(): parsing failed. err=%u, increasing capacity: %u\n",
|
|
204
|
+
variant->error, baseCapacity);
|
|
205
|
+
baseCapacity += 256;
|
|
206
|
+
variant =
|
|
207
|
+
std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
|
|
208
|
+
}
|
|
209
|
+
if (variant->error) {
|
|
210
|
+
Log(ERROR,
|
|
211
|
+
"routePackage(): parsing failed. err=%u, total_length=%d, data=%s<--\n",
|
|
212
|
+
variant->error, pkg.length(), pkg.c_str());
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (variant->routing() == SINGLE && variant->dest() != layout.getNodeId()) {
|
|
217
|
+
// Send on without further processing
|
|
218
|
+
send<T>((*variant), layout);
|
|
219
|
+
return;
|
|
220
|
+
} else if (variant->routing() == BROADCAST) {
|
|
221
|
+
broadcast<T>((*variant), layout, connection->nodeId);
|
|
222
|
+
}
|
|
223
|
+
auto calls = cbl.execute(variant->type(), (*variant), connection, receivedAt);
|
|
224
|
+
if (calls == 0)
|
|
225
|
+
Log(DEBUG, "routePackage(): No callbacks executed; %u, %s\n",
|
|
226
|
+
variant->type(), pkg.c_str());
|
|
227
|
+
#endif
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
template <class T, class U>
|
|
231
|
+
void handleNodeSync(T& mesh, protocol::NodeTree newTree,
|
|
232
|
+
std::shared_ptr<U> conn) {
|
|
233
|
+
Log(logger::SYNC, "handleNodeSync(): with %u\n", conn->nodeId);
|
|
234
|
+
|
|
235
|
+
if (!conn->validSubs(newTree)) {
|
|
236
|
+
Log(logger::SYNC, "handleNodeSync(): invalid new connection\n");
|
|
237
|
+
Log.remote("Invalid connection to %u\n", conn->nodeId);
|
|
238
|
+
conn->close();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (conn->newConnection) {
|
|
243
|
+
auto oldConnection = router::findRoute<U>(mesh, newTree.nodeId);
|
|
244
|
+
if (oldConnection) {
|
|
245
|
+
Log(logger::SYNC,
|
|
246
|
+
"handleNodeSync(): already connected to %u. Closing the new "
|
|
247
|
+
"connection \n",
|
|
248
|
+
newTree.nodeId);
|
|
249
|
+
Log.remote("Already connected to %u\n", newTree.nodeId);
|
|
250
|
+
conn->close();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
auto remoteNodeId = newTree.nodeId;
|
|
254
|
+
mesh.addTask([&mesh, remoteNodeId]() {
|
|
255
|
+
Log(logger::CONNECTION, "newConnectionTask():\n");
|
|
256
|
+
Log(logger::CONNECTION, "newConnectionTask(): adding %u now= %u\n",
|
|
257
|
+
remoteNodeId, mesh.getNodeTime());
|
|
258
|
+
mesh.newConnectionCallbacks.execute(remoteNodeId);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Initially interval is every 10 seconds,
|
|
262
|
+
// this will slow down to TIME_SYNC_INTERVAL
|
|
263
|
+
// after first succesfull sync
|
|
264
|
+
// TODO move it to a new connection callback and use initTimeSync from
|
|
265
|
+
// ntp.hpp
|
|
266
|
+
conn->timeSyncTask.set(10 * TASK_SECOND, TASK_FOREVER, [conn, &mesh]() {
|
|
267
|
+
Log(logger::S_TIME, "timeSyncTask(): %u\n", conn->nodeId);
|
|
268
|
+
mesh.startTimeSync(conn);
|
|
269
|
+
});
|
|
270
|
+
mesh.mScheduler->addTask(conn->timeSyncTask);
|
|
271
|
+
if (conn->station)
|
|
272
|
+
// We are STA, request time immediately
|
|
273
|
+
conn->timeSyncTask.enable();
|
|
274
|
+
else
|
|
275
|
+
// We are the AP, give STA the change to initiate time sync
|
|
276
|
+
conn->timeSyncTask.enableDelayed();
|
|
277
|
+
conn->newConnection = false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (conn->updateSubs(newTree)) {
|
|
281
|
+
auto nodeId = newTree.nodeId;
|
|
282
|
+
mesh.addTask(
|
|
283
|
+
[&mesh, nodeId]() { mesh.changedConnectionCallbacks.execute(nodeId); });
|
|
284
|
+
} else {
|
|
285
|
+
conn->nodeSyncTask.delay();
|
|
286
|
+
mesh.stability += std::min(1000 - mesh.stability, (size_t)25);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
template <class T, typename U>
|
|
291
|
+
callback::MeshPackageCallbackList<U> addPackageCallback(
|
|
292
|
+
callback::MeshPackageCallbackList<U>&& callbackList, T& mesh) {
|
|
293
|
+
// REQUEST type,
|
|
294
|
+
callbackList.onPackage(
|
|
295
|
+
protocol::NODE_SYNC_REQUEST,
|
|
296
|
+
[&mesh](protocol::Variant& variant, std::shared_ptr<U> connection,
|
|
297
|
+
uint32_t receivedAt) {
|
|
298
|
+
auto newTree = variant.to<protocol::NodeSyncRequest>();
|
|
299
|
+
handleNodeSync<T, U>(mesh, newTree, connection);
|
|
300
|
+
send<protocol::NodeSyncReply>(
|
|
301
|
+
connection->reply(std::move(mesh.asNodeTree())), connection, true);
|
|
302
|
+
return false;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Reply type just handle it
|
|
306
|
+
callbackList.onPackage(
|
|
307
|
+
protocol::NODE_SYNC_REPLY,
|
|
308
|
+
[&mesh](protocol::Variant& variant, std::shared_ptr<U> connection,
|
|
309
|
+
uint32_t receivedAt) {
|
|
310
|
+
auto newTree = variant.to<protocol::NodeSyncReply>();
|
|
311
|
+
handleNodeSync<T, U>(mesh, newTree, connection);
|
|
312
|
+
connection->timeOutTask.disable();
|
|
313
|
+
return false;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return callbackList;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
} // namespace router
|
|
320
|
+
} // namespace painlessmesh
|
|
321
|
+
|
|
322
|
+
#endif
|
|
@@ -0,0 +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
|
|
@@ -0,0 +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
|
+
|
|
239
|
+
#endif // _PAINLESS_MESH_VALIDATION_HPP_
|