AlteriomPainlessMesh v2.1.1
Easy ESP32/ESP8266 mesh networking with Alteriom IoT extensions
Loading...
Searching...
No Matches
gateway.hpp
Go to the documentation of this file.
1#ifndef _PAINLESS_MESH_GATEWAY_HPP_
2#define _PAINLESS_MESH_GATEWAY_HPP_
3
33#include "Arduino.h"
36
37#if defined(ESP32) || defined(ESP8266)
38#include <WiFiClient.h>
39#endif
43
44#include <functional>
45#include <map>
46#include <vector>
47
48namespace painlessmesh {
49namespace gateway {
50
52constexpr bool isValidMeshChannel(uint8_t channel) {
53 return channel >= 1 && channel <= 13;
54}
55
57constexpr bool shouldFollowBridgeChannel(uint32_t localNodeId,
58 uint32_t electedBridgeId,
59 uint8_t currentChannel,
60 uint8_t announcedChannel) {
61 return electedBridgeId != localNodeId &&
62 isValidMeshChannel(announcedChannel) &&
63 announcedChannel != currentChannel;
64}
65
70 uint8_t channel = 0;
71 int32_t rssi = 0;
72 // `push_back({channel, rssi})` needs a constructor under gnu++11 (the
73 // ESP32 Arduino 2.x core), where a struct with default member
74 // initializers is not an aggregate.
76 MeshChannelCandidate(uint8_t channel_, int32_t rssi_) : channel(channel_), rssi(rssi_) {}
77};
78
106inline uint8_t pickMeshChannel(const std::vector<MeshChannelCandidate>& candidates,
107 uint8_t avoidChannel, uint8_t routerChannel = 0) {
108 const MeshChannelCandidate* elsewhere = nullptr;
109 const MeshChannelCandidate* here = nullptr;
110 for (const auto& c : candidates) {
111 if (!isValidMeshChannel(c.channel)) continue;
112 if (c.channel == routerChannel) return c.channel;
113 const MeshChannelCandidate*& slot = (c.channel == avoidChannel) ? here : elsewhere;
114 if (slot == nullptr || c.rssi > slot->rssi) slot = &c;
115 }
116 if (elsewhere != nullptr) return elsewhere->channel;
117 if (here != nullptr) return here->channel;
118 return 0;
119}
120
127 bool valid = true;
129
130 ValidationResult() = default;
131 ValidationResult(bool v, const TSTRING& msg) : valid(v), errorMessage(msg) {}
132
133 explicit operator bool() const { return valid; }
134};
135
163 // ============================================
164 // Core Configuration
165 // ============================================
166
172 bool enabled = false;
173
181
189
190 // ============================================
191 // Internet Connectivity Checking
192 // ============================================
193
201 uint32_t internetCheckInterval = 30000;
202
210
217 uint16_t internetCheckPort = 53;
218
225 uint32_t internetCheckTimeout = 5000;
226
227 // ============================================
228 // Message Handling
229 // ============================================
230
237 uint8_t messageRetryCount = 3;
238
246 uint32_t retryInterval = 1000;
247
254 uint32_t duplicateTrackingTimeout = 60000;
255
263 uint16_t maxTrackedMessages = 500;
264
265 // ============================================
266 // Gateway Coordination
267 // ============================================
268
276 uint32_t gatewayHeartbeatInterval = 15000;
277
286 uint32_t gatewayFailureTimeout = 45000;
287
296
297 // ============================================
298 // Advanced Configuration
299 // ============================================
300
309
318
319 // ============================================
320 // Validation Methods
321 // ============================================
322
332 // If not enabled, configuration is valid (nothing to validate)
333 if (!enabled) {
334 return ValidationResult(true, "");
335 }
336
337 // Router SSID is required when enabled
338 if (routerSSID.length() == 0) {
339 return ValidationResult(false, "routerSSID is required when enabled");
340 }
341
342 // SSID length validation (max 32 characters per WiFi spec)
343 if (routerSSID.length() > 32) {
344 return ValidationResult(false,
345 "routerSSID exceeds maximum length of 32 characters");
346 }
347
348 // Password length validation (WPA2 max is 63 characters)
349 if (routerPassword.length() > 63) {
350 return ValidationResult(
351 false, "routerPassword exceeds maximum length of 63 characters");
352 }
353
354 // Internet check host validation
355 if (internetCheckHost.length() == 0) {
356 return ValidationResult(false, "internetCheckHost cannot be empty");
357 }
358
359 // Interval validations (minimum sensible values)
360 if (internetCheckInterval < 1000) {
361 return ValidationResult(
362 false, "internetCheckInterval must be at least 1000ms");
363 }
364
365 if (internetCheckTimeout < 100) {
366 return ValidationResult(false,
367 "internetCheckTimeout must be at least 100ms");
368 }
369
371 return ValidationResult(
372 false,
373 "internetCheckTimeout must be less than internetCheckInterval");
374 }
375
376 if (gatewayHeartbeatInterval < 1000) {
377 return ValidationResult(
378 false, "gatewayHeartbeatInterval must be at least 1000ms");
379 }
380
382 return ValidationResult(
383 false,
384 "gatewayFailureTimeout should be at least 2x gatewayHeartbeatInterval");
385 }
386
387 if (duplicateTrackingTimeout < 1000) {
388 return ValidationResult(
389 false, "duplicateTrackingTimeout must be at least 1000ms");
390 }
391
392 if (maxTrackedMessages < 10) {
393 return ValidationResult(false, "maxTrackedMessages must be at least 10");
394 }
395
396 if (retryInterval < 100) {
397 return ValidationResult(false, "retryInterval must be at least 100ms");
398 }
399
400 return ValidationResult(true, "");
401 }
402
413 return participateInElection && routerSSID.length() > 0;
414 }
415
421 bool hasRouterCredentials() const { return routerSSID.length() > 0; }
422
423};
424
432 bool available = false;
433 uint32_t lastCheckTime = 0;
434 uint32_t lastSuccessTime = 0;
435 uint32_t checkCount = 0;
436 uint32_t successCount = 0;
437 uint32_t failureCount = 0;
438 uint32_t lastLatencyMs = 0;
441 uint16_t checkPort = 0;
442
447 uint8_t getUptimePercent() const {
448 if (checkCount == 0) return 0;
449 return static_cast<uint8_t>((successCount * 100) / checkCount);
450 }
451
456 uint32_t getTimeSinceLastSuccess() const {
457 if (lastSuccessTime == 0) return UINT32_MAX;
458 return millis() - lastSuccessTime;
459 }
460
466 bool isStale(uint32_t maxAgeMs = 60000) const {
467 if (lastCheckTime == 0) return true;
468 return (millis() - lastCheckTime) > maxAgeMs;
469 }
470};
471
475typedef std::function<void(bool available)> InternetChangedCallback_t;
476
499 public:
501
506 void setConfig(const SharedGatewayConfig& config) {
507 checkHost_ = config.internetCheckHost;
508 checkPort_ = config.internetCheckPort;
509 checkInterval_ = config.internetCheckInterval;
510 checkTimeout_ = config.internetCheckTimeout;
511 status_.checkHost = checkHost_;
512 status_.checkPort = checkPort_;
513 }
514
520 void setCheckTarget(const TSTRING& host, uint16_t port = 53) {
521 checkHost_ = host;
522 checkPort_ = port;
523 status_.checkHost = checkHost_;
524 status_.checkPort = checkPort_;
525 }
526
531 void setCheckInterval(uint32_t intervalMs) {
532 checkInterval_ = intervalMs;
533 }
534
539 void setCheckTimeout(uint32_t timeoutMs) {
540 checkTimeout_ = timeoutMs;
541 }
542
548 connectivityChangedCallback_ = callback;
549 }
550
555 bool hasLocalInternet() const {
556 return status_.available;
557 }
558
564 return status_;
565 }
566
576 bool checkNow() {
577 // A full probe answers the question the on-demand budget exists for.
578 onDemandSpent_ = false;
579 status_.checkCount++;
580 status_.lastCheckTime = millis();
581
582 bool connected = performTcpCheck();
583
584 if (connected) {
585 status_.successCount++;
586 status_.lastSuccessTime = millis();
587 status_.lastError = "";
588 } else {
589 status_.failureCount++;
590 }
591
592 // Detect status change and fire callback
593 if (connected != status_.available) {
594 status_.available = connected;
595 if (connectivityChangedCallback_) {
596 connectivityChangedCallback_(connected);
597 }
598 }
599
600 return connected;
601 }
602
617 if (status_.available) return true;
618 if (onDemandSpent_) return false;
619 const bool connected = checkNow();
620 onDemandSpent_ = true;
621 return connected;
622 }
623
628 uint32_t getCheckInterval() const {
629 return checkInterval_;
630 }
631
636 uint32_t getCheckTimeout() const {
637 return checkTimeout_;
638 }
639
645 return checkHost_;
646 }
647
652 uint16_t getCheckPort() const {
653 return checkPort_;
654 }
655
659 void resetStats() {
660 status_.checkCount = 0;
661 status_.successCount = 0;
662 status_.failureCount = 0;
663 status_.lastCheckTime = 0;
664 status_.lastSuccessTime = 0;
665 status_.lastLatencyMs = 0;
666 status_.lastError = "";
667 }
668
669#ifdef PAINLESSMESH_BOOST
674 void setMockConnected(bool connected) {
675 mockConnected_ = connected;
676 }
677#endif
678
679 private:
689 bool performTcpCheck() {
690#ifdef PAINLESSMESH_BOOST
691 // Test environment - use mock value
692 if (mockConnected_) {
693 status_.lastLatencyMs = 10; // Simulated latency
694 return true;
695 }
696 status_.lastError = "Mock: No Internet in test environment";
697 return false;
698#elif defined(ESP32) || defined(ESP8266)
699 // No station, no uplink: answer at once instead of spending the connect
700 // timeout on a link that does not exist. This is what keeps the
701 // on-demand probe cheap on a bridge whose router is down.
702 if (WiFi.status() != WL_CONNECTED) {
703 status_.lastError = "Station not connected";
704 return false;
705 }
706 WiFiClient client;
707 uint32_t started = millis();
708#ifdef ESP32
709 bool connected = client.connect(checkHost_.c_str(), checkPort_, checkTimeout_);
710#else
711 // ESP8266's WiFiClient lacks ESP32's per-connect timeout overload, so the
712 // timeout is set on the client instead. It is milliseconds, not seconds:
713 // WiFiClient inherits Stream::setTimeout ("maximum milliseconds to wait")
714 // and connect() hands _timeout straight to WiFi.hostByName(), whose
715 // parameter is named timeout_ms. Dividing by 1000 here gave DNS five
716 // milliseconds to resolve, so the check failed every time and an ESP8266
717 // shared gateway never reported local Internet.
718 client.setTimeout(checkTimeout_);
719 bool connected = client.connect(checkHost_.c_str(), checkPort_);
720#endif
721 status_.lastLatencyMs = millis() - started;
722 if (!connected) {
723 status_.lastError = "TCP connectivity check failed";
724 return false;
725 }
726 client.stop();
727 return true;
728#else
729 status_.lastError = "Internet health checks are unsupported on this platform";
730 return false;
731#endif
732 }
733
734 // Configuration
735 TSTRING checkHost_ = "8.8.8.8";
736 uint16_t checkPort_ = 53;
737 uint32_t checkInterval_ = 30000;
738 uint32_t checkTimeout_ = 5000;
739
740 // State
741 InternetStatus status_;
742 InternetChangedCallback_t connectivityChangedCallback_;
743 bool onDemandSpent_ = false;
744
745#ifdef PAINLESSMESH_BOOST
746 bool mockConnected_ = false;
747#endif
748};
749
759enum class GatewayPriority : uint8_t {
761 PRIORITY_HIGH = 1,
762 PRIORITY_NORMAL = 2,
763 PRIORITY_LOW = 3
764};
765
816 public:
823 uint32_t messageId = 0;
824
831 uint32_t originNode = 0;
832
839 uint32_t timestamp = 0;
840
847 uint8_t priority = static_cast<uint8_t>(GatewayPriority::PRIORITY_NORMAL);
848
858
866
875 TSTRING contentType = "application/json";
876
883 uint8_t retryCount = 0;
884
891 bool requiresAck = false;
892
903 uint32_t requestNonce = 0;
904
912 static constexpr int numPackageFields = 10;
913
919 GatewayDataPackage() : SinglePackage(protocol::GATEWAY_DATA) {}
920
929 GatewayDataPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
930 messageId = jsonObj["msgId"];
931 originNode = jsonObj["origin"];
932 timestamp = jsonObj["ts"];
933 priority = jsonObj["prio"];
934 retryCount = jsonObj["retry"];
935 requiresAck = jsonObj["ack"] | false;
936 requestNonce = jsonObj["nonce"] | 0UL;
937
938#if ARDUINOJSON_VERSION_MAJOR < 7
939 if (jsonObj.containsKey("dest_url"))
940 destination = jsonObj["dest_url"].as<TSTRING>();
941 if (jsonObj.containsKey("payload"))
942 payload = jsonObj["payload"].as<TSTRING>();
943 if (jsonObj.containsKey("content"))
944 contentType = jsonObj["content"].as<TSTRING>();
945#else
946 if (jsonObj["dest_url"].is<TSTRING>())
947 destination = jsonObj["dest_url"].as<TSTRING>();
948 if (jsonObj["payload"].is<TSTRING>())
949 payload = jsonObj["payload"].as<TSTRING>();
950 if (jsonObj["content"].is<TSTRING>())
951 contentType = jsonObj["content"].as<TSTRING>();
952#endif
953 }
954
963 JsonObject addTo(JsonObject&& jsonObj) const {
964 jsonObj = SinglePackage::addTo(std::move(jsonObj));
965 jsonObj["msgId"] = messageId;
966 jsonObj["origin"] = originNode;
967 jsonObj["ts"] = timestamp;
968 jsonObj["prio"] = priority;
969 jsonObj["dest_url"] = destination;
970 jsonObj["payload"] = payload;
971 jsonObj["content"] = contentType;
972 jsonObj["retry"] = retryCount;
973 jsonObj["ack"] = requiresAck;
974 if (requestNonce != 0) jsonObj["nonce"] = requestNonce;
975 return jsonObj;
976 }
977
978#if ARDUINOJSON_VERSION_MAJOR < 7
986 size_t jsonObjectSize() const {
987 // noJsonFields (from base class) + numPackageFields (our fields)
988 return JSON_OBJECT_SIZE(noJsonFields + numPackageFields) + destination.length() +
989 payload.length() + contentType.length();
990 }
991#endif
992
1011 static uint32_t generateMessageId(uint32_t nodeId) {
1012 // The counter starts at a random point each boot. Starting at zero, the
1013 // first request after every reboot carried the same id as the first
1014 // request of the boot before -- and so the same X-Request-Id and
1015 // Idempotency-Key, which a service that remembers keys drops as a repeat.
1016 static uint16_t counter =
1017 static_cast<uint16_t>(validation::SecureRandom::generate());
1018 ++counter;
1019 if (counter == 0) ++counter;
1020 // Combine node ID (upper 16 bits) with counter (lower 16 bits)
1021 return ((nodeId & 0xFFFF) << 16) | counter;
1022 }
1023
1024};
1025
1056
1064inline bool transportErrorMayHaveReachedServer(int rawCode) {
1065 switch (rawCode) {
1069 return false;
1070 default:
1071 return true;
1072 }
1073}
1074
1095 bool success = false;
1096
1098 uint16_t ackStatus = 0;
1099
1101 bool transportError = false;
1102
1109 bool unverifiedStatus = false;
1110
1119 bool retryable = false;
1120
1126};
1127
1134static const size_t GATEWAY_RESPONSE_REASON_MAX = 240;
1135
1142static const size_t GATEWAY_RESPONSE_HEAD_BYTES = 512;
1143static const size_t GATEWAY_RESPONSE_TAIL_BYTES = 256;
1144
1150static const size_t GATEWAY_RESPONSE_SCAN_BYTES = 8192;
1151
1153static const uint32_t GATEWAY_RESPONSE_HEAD_TIMEOUT_MS = 250;
1154
1160static const uint32_t GATEWAY_RETRY_AFTER_MAX_MS = 60000;
1161
1162inline bool responseContains(const TSTRING& haystack, const char* needle) {
1163#if defined(PAINLESSMESH_BOOST)
1164 return haystack.find(needle) != std::string::npos;
1165#else
1166 return haystack.indexOf(needle) >= 0;
1167#endif
1168}
1169
1180 const TSTRING& body, size_t maxLen = GATEWAY_RESPONSE_REASON_MAX) {
1181 TSTRING text;
1182 bool inTag = false;
1183 bool pendingSpace = false;
1184 for (size_t i = 0; i < body.length(); ++i) {
1185 const char c = body[i];
1186 if (c == '<') {
1187 inTag = true;
1188 continue;
1189 }
1190 if (c == '>') {
1191 inTag = false;
1192 pendingSpace = true;
1193 continue;
1194 }
1195 if (inTag) continue;
1196 if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
1197 pendingSpace = true;
1198 continue;
1199 }
1200 if (pendingSpace && text.length() > 0) text += ' ';
1201 pendingSpace = false;
1202 text += c;
1203 }
1204 if (text.length() <= maxLen) return text;
1205
1206 static const char JOIN[] = " ... ";
1207 const size_t joinLength = sizeof(JOIN) - 1;
1208 const size_t room = maxLen > joinLength ? maxLen - joinLength : 0;
1209 const size_t head = room / 2;
1210 const size_t tail = room - head;
1211 TSTRING out;
1212 for (size_t i = 0; i < head; ++i) out += text[i];
1213 out += JOIN;
1214 for (size_t i = text.length() - tail; i < text.length(); ++i) out += text[i];
1215 return out;
1216}
1217
1229 public:
1231 bool add(char c) {
1232 ++seen_;
1233 if (head_.length() < GATEWAY_RESPONSE_HEAD_BYTES) {
1234 head_ += c;
1235 } else {
1236 tail_[(tailStart_ + tailLength_) % GATEWAY_RESPONSE_TAIL_BYTES] = c;
1237 if (tailLength_ < GATEWAY_RESPONSE_TAIL_BYTES) {
1238 ++tailLength_;
1239 } else {
1240 tailStart_ = (tailStart_ + 1) % GATEWAY_RESPONSE_TAIL_BYTES;
1241 }
1242 }
1243 return seen_ < GATEWAY_RESPONSE_SCAN_BYTES;
1244 }
1245
1246 void add(const TSTRING& text) {
1247 for (size_t i = 0; i < text.length(); ++i) {
1248 if (!add(text[i])) return;
1249 }
1250 }
1251
1252 TSTRING text() const {
1253 TSTRING out = head_;
1254 if (seen_ > head_.length() + tailLength_) out += " ... ";
1255 for (size_t i = 0; i < tailLength_; ++i) {
1256 out += tail_[(tailStart_ + i) % GATEWAY_RESPONSE_TAIL_BYTES];
1257 }
1258 return out;
1259 }
1260
1261 private:
1262 TSTRING head_;
1263 char tail_[GATEWAY_RESPONSE_TAIL_BYTES] = {};
1264 size_t tailStart_ = 0;
1265 size_t tailLength_ = 0;
1266 size_t seen_ = 0;
1267};
1268
1283 public:
1284 explicit ChunkedBodyDecoder(ResponseExcerpt& excerpt) : excerpt_(excerpt) {}
1285
1288 bool add(char c) {
1289 switch (state_) {
1290 case State::Size:
1291 if (c == '\r') {
1292 state_ = State::SizeLf;
1293 } else if (c == ';') {
1294 state_ = State::Extension;
1295 } else if (c == ' ' || c == '\t') {
1296 // Tolerated around the size, as many servers emit it.
1297 } else {
1298 const int digit = hexValue(c);
1299 if (digit < 0 || sizeDigits_ >= 8) return stop();
1300 remaining_ = (remaining_ << 4) | static_cast<uint32_t>(digit);
1301 ++sizeDigits_;
1302 }
1303 return true;
1304 case State::Extension:
1305 if (c == '\r') state_ = State::SizeLf;
1306 return true;
1307 case State::SizeLf:
1308 if (c != '\n' || sizeDigits_ == 0) return stop();
1309 sizeDigits_ = 0;
1310 if (remaining_ == 0) {
1311 state_ = State::Done;
1312 return false;
1313 }
1314 state_ = State::Data;
1315 return true;
1316 case State::Data:
1317 --remaining_;
1318 if (remaining_ == 0) state_ = State::DataCr;
1319 if (!excerpt_.add(c)) {
1320 state_ = State::Done;
1321 return false;
1322 }
1323 return true;
1324 case State::DataCr:
1325 if (c != '\r') return stop();
1326 state_ = State::DataLf;
1327 return true;
1328 case State::DataLf:
1329 if (c != '\n') return stop();
1330 state_ = State::Size;
1331 return true;
1332 case State::Done:
1333 return false;
1334 }
1335 return false;
1336 }
1337
1338 void add(const TSTRING& raw) {
1339 for (size_t i = 0; i < raw.length(); ++i) {
1340 if (!add(raw[i])) return;
1341 }
1342 }
1343
1345 bool complete() const { return state_ == State::Done && !malformed_; }
1346
1347 private:
1348 enum class State { Size, Extension, SizeLf, Data, DataCr, DataLf, Done };
1349
1350 static int hexValue(char c) {
1351 if (c >= '0' && c <= '9') return c - '0';
1352 if (c >= 'a' && c <= 'f') return c - 'a' + 10;
1353 if (c >= 'A' && c <= 'F') return c - 'A' + 10;
1354 return -1;
1355 }
1356
1357 bool stop() {
1358 malformed_ = true;
1359 state_ = State::Done;
1360 return false;
1361 }
1362
1363 ResponseExcerpt& excerpt_;
1364 State state_ = State::Size;
1365 uint32_t remaining_ = 0;
1366 uint8_t sizeDigits_ = 0;
1367 bool malformed_ = false;
1368};
1369
1371inline bool transferEncodingIsChunked(const TSTRING& value) {
1372 TSTRING lower;
1373 for (size_t i = 0; i < value.length(); ++i) {
1374 const char c = value[i];
1375 lower += (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
1376 }
1377 return responseContains(lower, "chunked");
1378}
1379
1388inline uint32_t parseRetryAfterMs(const TSTRING& value) {
1389 uint64_t seconds = 0;
1390 size_t digits = 0;
1391 for (size_t i = 0; i < value.length(); ++i) {
1392 const char c = value[i];
1393 if (c == ' ' || c == '\t') {
1394 if (digits == 0) continue;
1395 break;
1396 }
1397 if (c < '0' || c > '9') return 0;
1398 seconds = seconds * 10 + static_cast<uint64_t>(c - '0');
1399 if (seconds > 0xFFFFFFFFULL / 1000ULL) return 0xFFFFFFFFUL;
1400 ++digits;
1401 }
1402 return digits == 0 ? 0 : static_cast<uint32_t>(seconds * 1000ULL);
1403}
1404
1417inline TSTRING requestIdFor(uint32_t originNode, uint32_t messageId,
1418 uint32_t requestNonce = 0) {
1419 char buffer[40];
1420 if (requestNonce == 0) {
1421 snprintf(buffer, sizeof(buffer), "pm-%08x-%08x",
1422 static_cast<unsigned>(originNode), static_cast<unsigned>(messageId));
1423 } else {
1424 snprintf(buffer, sizeof(buffer), "pm-%08x-%08x-%08x",
1425 static_cast<unsigned>(originNode), static_cast<unsigned>(messageId),
1426 static_cast<unsigned>(requestNonce));
1427 }
1428 return TSTRING(buffer);
1429}
1430
1432inline uint32_t newRequestNonce() {
1433 uint32_t nonce = validation::SecureRandom::generate();
1434 return nonce != 0 ? nonce : 1;
1435}
1436
1452 const TSTRING& body = TSTRING()) {
1453 HttpRequestOutcome outcome;
1454 if (rawCode <= 0) {
1455 outcome.transportError = true;
1457 return outcome;
1458 }
1459 outcome.ackStatus =
1460 static_cast<uint16_t>(rawCode > 0xFFFF ? 0xFFFF : rawCode);
1461 outcome.success =
1462 rawCode == 200 || rawCode == 201 || rawCode == 202 || rawCode == 204;
1463 outcome.unverifiedStatus = !outcome.success && rawCode >= 200 && rawCode < 300;
1464 outcome.retryable = rawCode == 429 || rawCode == 503;
1465 if (!outcome.success) outcome.reason = summarizeResponseBody(body);
1466 return outcome;
1467}
1468
1477static const char GATEWAY_NOT_A_GATEWAY_PHRASE[] = "is not an Internet gateway";
1478
1489static const uint32_t GATEWAY_DNS_NEGATIVE_TTL_MS = 60000;
1490
1497inline TSTRING hostFromUrl(const TSTRING& url) {
1498 const char* s = url.c_str();
1499 const size_t n = url.length();
1500 size_t i = 0;
1501 for (size_t k = 0; k + 2 < n; ++k) {
1502 if (s[k] == ':' && s[k + 1] == '/' && s[k + 2] == '/') {
1503 i = k + 3;
1504 break;
1505 }
1506 }
1507 size_t j = i;
1508 while (j < n && s[j] != '/' && s[j] != '?' && s[j] != '#' && s[j] != ':') ++j;
1509 TSTRING host;
1510 for (size_t k = i; k < j; ++k) host += s[k];
1511 return host;
1512}
1513
1515inline bool looksLikeIpLiteral(const TSTRING& host) {
1516 if (host.length() == 0) return false;
1517 for (size_t i = 0; i < host.length(); ++i) {
1518 const char c = host[i];
1519 if (!(c >= '0' && c <= '9') && c != '.') return false;
1520 }
1521 return true;
1522}
1523
1532 public:
1533 // An enum, not a static const member: the test suite passes it to Catch2 by
1534 // reference, which needs a definition C++14 cannot give an in-class
1535 // constant without an out-of-line one.
1536 enum : size_t { SLOTS = 4 };
1537
1538 void remember(const TSTRING& host, uint32_t nowMs,
1539 uint32_t ttlMs = GATEWAY_DNS_NEGATIVE_TTL_MS) {
1540 Entry* slot = find(host);
1541 if (slot == nullptr) {
1542 slot = &entries_[0];
1543 for (size_t i = 0; i < SLOTS; ++i) {
1544 if (!entries_[i].used) {
1545 slot = &entries_[i];
1546 break;
1547 }
1548 if (static_cast<int32_t>(entries_[i].at - slot->at) < 0) slot = &entries_[i];
1549 }
1550 }
1551 slot->used = true;
1552 slot->host = host;
1553 slot->at = nowMs;
1554 slot->ttl = ttlMs;
1555 }
1556
1558 bool isFailing(const TSTRING& host, uint32_t nowMs, uint32_t* ageMs = nullptr) {
1559 Entry* slot = find(host);
1560 if (slot == nullptr) return false;
1561 const uint32_t age = nowMs - slot->at;
1562 if (age >= slot->ttl) {
1563 slot->used = false;
1564 return false;
1565 }
1566 if (ageMs != nullptr) *ageMs = age;
1567 return true;
1568 }
1569
1570 void forget(const TSTRING& host) {
1571 Entry* slot = find(host);
1572 if (slot != nullptr) slot->used = false;
1573 }
1574
1575 size_t size() const {
1576 size_t n = 0;
1577 for (size_t i = 0; i < SLOTS; ++i) n += entries_[i].used ? 1 : 0;
1578 return n;
1579 }
1580
1581 private:
1582 struct Entry {
1583 TSTRING host;
1584 uint32_t at = 0;
1585 uint32_t ttl = 0;
1586 bool used = false;
1587 };
1588
1589 Entry* find(const TSTRING& host) {
1590 for (size_t i = 0; i < SLOTS; ++i) {
1591 if (entries_[i].used && entries_[i].host == host) return &entries_[i];
1592 }
1593 return nullptr;
1594 }
1595
1596 Entry entries_[SLOTS];
1597};
1598
1652 public:
1659 uint32_t messageId = 0;
1660
1667 uint32_t originNode = 0;
1668
1675 bool success = false;
1676
1684 uint16_t httpStatus = 0;
1685
1693
1700 uint32_t timestamp = 0;
1701
1710
1719 int8_t retryable = -1;
1720
1727 uint32_t retryAfterMs = 0;
1728
1735 static constexpr int numPackageFields = 9;
1736
1742 GatewayAckPackage() : SinglePackage(protocol::GATEWAY_ACK) {}
1743
1752 GatewayAckPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
1753 messageId = jsonObj["msgId"];
1754 originNode = jsonObj["origin"];
1755 success = jsonObj["success"] | false;
1756 httpStatus = jsonObj["http"];
1757 timestamp = jsonObj["ts"];
1758
1759#if ARDUINOJSON_VERSION_MAJOR < 7
1760 if (jsonObj.containsKey("err"))
1761 error = jsonObj["err"].as<TSTRING>();
1762 if (jsonObj.containsKey("resp"))
1763 response = jsonObj["resp"].as<TSTRING>();
1764 if (jsonObj.containsKey("retry"))
1765 retryable = jsonObj["retry"].as<bool>() ? 1 : 0;
1766#else
1767 if (jsonObj["err"].is<TSTRING>())
1768 error = jsonObj["err"].as<TSTRING>();
1769 if (jsonObj["resp"].is<TSTRING>())
1770 response = jsonObj["resp"].as<TSTRING>();
1771 if (jsonObj["retry"].is<bool>())
1772 retryable = jsonObj["retry"].as<bool>() ? 1 : 0;
1773#endif
1774 retryAfterMs = jsonObj["retryAfter"] | 0UL;
1775 }
1776
1785 JsonObject addTo(JsonObject&& jsonObj) const {
1786 jsonObj = SinglePackage::addTo(std::move(jsonObj));
1787 jsonObj["msgId"] = messageId;
1788 jsonObj["origin"] = originNode;
1789 jsonObj["success"] = success;
1790 jsonObj["http"] = httpStatus;
1791 jsonObj["err"] = error;
1792 jsonObj["ts"] = timestamp;
1793 // Added in 2.1.0 and omitted when they say nothing, so an ack to a node
1794 // that predates them is the ack it always was.
1795 if (response.length() > 0) jsonObj["resp"] = response;
1796 if (retryable >= 0) jsonObj["retry"] = retryable == 1;
1797 if (retryAfterMs > 0) jsonObj["retryAfter"] = retryAfterMs;
1798 return jsonObj;
1799 }
1800
1801#if ARDUINOJSON_VERSION_MAJOR < 7
1809 size_t jsonObjectSize() const {
1810 // noJsonFields (from base class) + numPackageFields (our fields)
1811 return JSON_OBJECT_SIZE(noJsonFields + numPackageFields) + error.length() +
1812 response.length();
1813 }
1814#endif
1815
1816};
1817
1818// ===========================================================================
1819// Scheduler-stall protection for blocking gateway requests
1820// ===========================================================================
1821
1822// The socket timeouts below are derived from NODE_TIMEOUT rather than written
1823// as absolute numbers, so a gateway request cannot outlast the mesh watchdog in
1824// *any* build. That is not hypothetical tidiness: the host test environment
1825// overrides NODE_TIMEOUT to 5s (test/catch/Arduino.h shadows configuration.hpp
1826// wholesale via its include guard), so hardcoded defaults sized for the 10s
1827// production watchdog would blow the assertion below there.
1828//
1829// They live in this header, not in configuration.hpp, for the same reason --
1830// gateway.hpp is reached through both configurations, configuration.hpp is not.
1831//
1832// At the production NODE_TIMEOUT of 10s this gives 5000ms for the request and
1833// 2000ms for the captive-portal probe. Dividing by TASK_MILLISECOND first
1834// normalises out the scheduler resolution, so the result is milliseconds under
1835// _TASK_MICRO_RES too.
1836
1846#ifndef GATEWAY_HTTP_TIMEOUT_MS
1847#define GATEWAY_HTTP_TIMEOUT_MS ((NODE_TIMEOUT) / TASK_MILLISECOND / 5)
1848#endif
1849
1852#ifndef GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS
1853#define GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS ((NODE_TIMEOUT) / TASK_MILLISECOND / 10)
1854#endif
1855
1864#ifndef GATEWAY_DNS_TIMEOUT_MS
1865#define GATEWAY_DNS_TIMEOUT_MS ((NODE_TIMEOUT) / TASK_MILLISECOND / 10)
1866#endif
1867
1874#ifndef GATEWAY_CONNECTIVITY_CACHE_MS
1875#define GATEWAY_CONNECTIVITY_CACHE_MS 60000UL
1876#endif
1877
1900constexpr unsigned long gatewayBlockingBudgetMs() {
1901 return 2UL * static_cast<unsigned long>(GATEWAY_HTTP_TIMEOUT_MS) +
1902 2UL * static_cast<unsigned long>(GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS) +
1903 static_cast<unsigned long>(GATEWAY_DNS_TIMEOUT_MS);
1904}
1905
1906// A gateway that can block longer than the mesh watchdog partitions the mesh
1907// around itself (issues #318, #332). Catch what is expressible at compile time
1908// -- both socket waits of each HTTP call plus the DNS probe; see the ESP32
1909// resolver caveat above for the one wait this cannot cover. TASK_ constants
1910// are scaled by the scheduler's resolution, so the comparison is written in
1911// scheduler units to stay correct under _TASK_MICRO_RES too.
1912static_assert(gatewayBlockingBudgetMs() * TASK_MILLISECOND < NODE_TIMEOUT,
1913 "Gateway blocking budget (2x GATEWAY_HTTP_TIMEOUT_MS + 2x "
1914 "GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS + GATEWAY_DNS_TIMEOUT_MS) "
1915 "must stay below NODE_TIMEOUT, or a gateway request stalls the "
1916 "scheduler for longer than its peers are willing to wait and "
1917 "the mesh partitions around the gateway. Raise NODE_TIMEOUT if "
1918 "you need a longer HTTP timeout.");
1919
1928template <typename T>
1929bool reserveGatewayBlockingBudget(T& connection) {
1930 if (!connection.timeOutTask.isEnabled()) return false;
1931 connection.timeOutTask.adjust(static_cast<long>(
1932 gatewayBlockingBudgetMs() * TASK_MILLISECOND));
1933 return true;
1934}
1935
1982template <typename T>
1983size_t refreshPeerWatchdogs(T& mesh, unsigned long stalledMs) {
1984 if (stalledMs == 0) return 0; // nothing blocked, nothing to give back
1985 size_t refreshed = 0;
1986 for (auto&& connection : mesh.subs) {
1987 if (!connection) continue;
1988 if (!connection->timeOutTask.isEnabled()) continue;
1989 connection->timeOutTask.adjust(
1990 static_cast<long>(stalledMs * TASK_MILLISECOND));
1991 ++refreshed;
1992 }
1993 return refreshed;
1994}
1995
1996} // namespace gateway
1997} // namespace painlessmesh
1998
1999#endif // _PAINLESS_MESH_GATEWAY_HPP_
Removes HTTP/1.1 chunked transfer framing from a body read a byte at a time, and passes the content o...
Definition gateway.hpp:1282
bool add(char c)
Definition gateway.hpp:1288
void add(const TSTRING &raw)
Definition gateway.hpp:1338
ChunkedBodyDecoder(ResponseExcerpt &excerpt)
Definition gateway.hpp:1284
bool complete() const
Definition gateway.hpp:1345
Gateway Acknowledgment Package for delivery confirmations.
Definition gateway.hpp:1651
JsonObject addTo(JsonObject &&jsonObj) const
Serialize to JSON object.
Definition gateway.hpp:1785
TSTRING response
The start of the response body, summarized to one line.
Definition gateway.hpp:1709
size_t jsonObjectSize() const
Calculate JSON object size for ArduinoJson v6.
Definition gateway.hpp:1809
bool success
Delivery success status.
Definition gateway.hpp:1675
uint32_t messageId
Original message ID being acknowledged.
Definition gateway.hpp:1659
GatewayAckPackage()
Default constructor.
Definition gateway.hpp:1742
static constexpr int numPackageFields
Number of additional JSON fields in this package.
Definition gateway.hpp:1735
int8_t retryable
Whether the origin node may resend the identical request.
Definition gateway.hpp:1719
uint32_t timestamp
Acknowledgment timestamp.
Definition gateway.hpp:1700
uint32_t retryAfterMs
How long the server asked to wait before a retry, in milliseconds.
Definition gateway.hpp:1727
GatewayAckPackage(JsonObject jsonObj)
Construct from JSON object.
Definition gateway.hpp:1752
uint16_t httpStatus
HTTP response code (if applicable)
Definition gateway.hpp:1684
uint32_t originNode
Original sender node ID.
Definition gateway.hpp:1667
TSTRING error
Error message (if failed)
Definition gateway.hpp:1692
Gateway Data Package for routing Internet requests through mesh.
Definition gateway.hpp:815
uint32_t timestamp
Creation timestamp.
Definition gateway.hpp:839
uint8_t priority
Message priority (0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW)
Definition gateway.hpp:847
TSTRING destination
Destination URL or endpoint.
Definition gateway.hpp:857
TSTRING payload
Application payload data.
Definition gateway.hpp:865
GatewayDataPackage(JsonObject jsonObj)
Construct from JSON object.
Definition gateway.hpp:929
JsonObject addTo(JsonObject &&jsonObj) const
Serialize to JSON object.
Definition gateway.hpp:963
TSTRING contentType
MIME content type.
Definition gateway.hpp:875
uint32_t messageId
Unique message identifier.
Definition gateway.hpp:823
bool requiresAck
Whether acknowledgment is required.
Definition gateway.hpp:891
uint32_t requestNonce
Random value drawn once per sendToInternet() call.
Definition gateway.hpp:903
size_t jsonObjectSize() const
Calculate JSON object size for ArduinoJson v6.
Definition gateway.hpp:986
uint32_t originNode
Node ID that originated this message.
Definition gateway.hpp:831
uint8_t retryCount
Number of relay attempts.
Definition gateway.hpp:883
static constexpr int numPackageFields
Number of additional JSON fields in this package.
Definition gateway.hpp:912
GatewayDataPackage()
Default constructor.
Definition gateway.hpp:919
static uint32_t generateMessageId(uint32_t nodeId)
Generate a unique message ID.
Definition gateway.hpp:1011
Internet Health Checker class for periodic connectivity monitoring.
Definition gateway.hpp:498
void onConnectivityChanged(InternetChangedCallback_t callback)
Register callback for connectivity changes.
Definition gateway.hpp:547
void setCheckTimeout(uint32_t timeoutMs)
Set check timeout.
Definition gateway.hpp:539
void setCheckInterval(uint32_t intervalMs)
Set check interval.
Definition gateway.hpp:531
InternetStatus getStatus() const
Get detailed Internet status.
Definition gateway.hpp:563
bool checkNow()
Perform an immediate Internet connectivity check.
Definition gateway.hpp:576
uint32_t getCheckTimeout() const
Get check timeout.
Definition gateway.hpp:636
bool checkOnDemand()
Probe once outside the schedule, for a caller that needs the answer now (issue #450)
Definition gateway.hpp:616
uint16_t getCheckPort() const
Get check port.
Definition gateway.hpp:652
bool hasLocalInternet() const
Check if local Internet is currently available.
Definition gateway.hpp:555
uint32_t getCheckInterval() const
Get check interval.
Definition gateway.hpp:628
TSTRING getCheckHost() const
Get check host.
Definition gateway.hpp:644
void setConfig(const SharedGatewayConfig &config)
Configure the health checker with gateway settings.
Definition gateway.hpp:506
void setCheckTarget(const TSTRING &host, uint16_t port=53)
Set custom check host and port.
Definition gateway.hpp:520
void resetStats()
Reset all statistics.
Definition gateway.hpp:659
A few hosts that recently failed to resolve, and when.
Definition gateway.hpp:1531
@ SLOTS
Definition gateway.hpp:1536
void forget(const TSTRING &host)
Definition gateway.hpp:1570
size_t size() const
Definition gateway.hpp:1575
bool isFailing(const TSTRING &host, uint32_t nowMs, uint32_t *ageMs=nullptr)
Definition gateway.hpp:1558
void remember(const TSTRING &host, uint32_t nowMs, uint32_t ttlMs=GATEWAY_DNS_NEGATIVE_TTL_MS)
Definition gateway.hpp:1538
The start and the end of a response body, read a byte at a time.
Definition gateway.hpp:1228
bool add(char c)
Definition gateway.hpp:1231
TSTRING text() const
Definition gateway.hpp:1252
void add(const TSTRING &text)
Definition gateway.hpp:1246
Definition plugin.hpp:40
int noJsonFields
Definition plugin.hpp:49
SinglePackage(int type)
Definition plugin.hpp:51
static uint32_t generate()
Definition validation.hpp:54
#define NODE_TIMEOUT
Definition configuration.hpp:105
String TSTRING
Definition configuration.hpp:132
#define GATEWAY_HTTP_TIMEOUT_MS
Definition gateway.hpp:1847
#define GATEWAY_CAPTIVE_PORTAL_TIMEOUT_MS
Definition gateway.hpp:1853
#define GATEWAY_DNS_TIMEOUT_MS
Definition gateway.hpp:1865
uint32_t parseRetryAfterMs(const TSTRING &value)
The delay a Retry-After header asks for, in milliseconds.
Definition gateway.hpp:1388
HttpTransportError
HTTPClient transport errors, by what they say about the request.
Definition gateway.hpp:1043
@ HTTP_TRANSPORT_READ_TIMEOUT
sent, and no reply in time
Definition gateway.hpp:1054
@ HTTP_TRANSPORT_ENCODING
the reply arrived malformed; sent
Definition gateway.hpp:1052
@ HTTP_TRANSPORT_CONNECTION_REFUSED
no connection was made
Definition gateway.hpp:1044
@ HTTP_TRANSPORT_NOT_CONNECTED
closed before the reply began; sent
Definition gateway.hpp:1047
@ HTTP_TRANSPORT_NO_HTTP_SERVER
the reply was not HTTP; sent
Definition gateway.hpp:1050
@ HTTP_TRANSPORT_SEND_PAYLOAD_FAILED
the body never completed
Definition gateway.hpp:1046
@ HTTP_TRANSPORT_STREAM_WRITE
the reply could not be stored; sent
Definition gateway.hpp:1053
@ HTTP_TRANSPORT_TOO_LESS_RAM
out of memory sending or reading
Definition gateway.hpp:1051
@ HTTP_TRANSPORT_CONNECTION_LOST
dropped while reading the reply; sent
Definition gateway.hpp:1048
@ HTTP_TRANSPORT_NO_STREAM
no stream to send or read with
Definition gateway.hpp:1049
@ HTTP_TRANSPORT_SEND_HEADER_FAILED
the request line never completed
Definition gateway.hpp:1045
TSTRING summarizeResponseBody(const TSTRING &body, size_t maxLen=GATEWAY_RESPONSE_REASON_MAX)
Reduce a response body to one line fit for a log or an error string.
Definition gateway.hpp:1179
GatewayPriority
Priority levels for GatewayDataPackage messages.
Definition gateway.hpp:759
@ PRIORITY_HIGH
High priority - processed before normal.
@ PRIORITY_LOW
Low priority - processed when idle.
@ PRIORITY_CRITICAL
Critical messages - immediate processing.
@ PRIORITY_NORMAL
Normal priority - standard processing.
constexpr bool isValidMeshChannel(uint8_t channel)
Definition gateway.hpp:52
HttpRequestOutcome classifyHttpResult(int rawCode, const TSTRING &body=TSTRING())
Classify an HTTPClient result for the gateway acknowledgment.
Definition gateway.hpp:1451
bool reserveGatewayBlockingBudget(T &connection)
Definition gateway.hpp:1929
constexpr bool shouldFollowBridgeChannel(uint32_t localNodeId, uint32_t electedBridgeId, uint8_t currentChannel, uint8_t announcedChannel)
Definition gateway.hpp:57
bool looksLikeIpLiteral(const TSTRING &host)
Definition gateway.hpp:1515
constexpr unsigned long gatewayBlockingBudgetMs()
Wall-clock ceiling of the gateway's blocking calls on the GATEWAY_DATA path, in milliseconds (issue #...
Definition gateway.hpp:1900
TSTRING requestIdFor(uint32_t originNode, uint32_t messageId, uint32_t requestNonce=0)
The identifier the gateway sends with a request, as both X-Request-Id and Idempotency-Key.
Definition gateway.hpp:1417
bool transportErrorMayHaveReachedServer(int rawCode)
Can a request that failed with this transport error have reached the server?
Definition gateway.hpp:1064
bool transferEncodingIsChunked(const TSTRING &value)
Definition gateway.hpp:1371
uint8_t pickMeshChannel(const std::vector< MeshChannelCandidate > &candidates, uint8_t avoidChannel, uint8_t routerChannel=0)
Where a node that has lost the mesh should go.
Definition gateway.hpp:106
bool responseContains(const TSTRING &haystack, const char *needle)
Definition gateway.hpp:1162
std::function< void(bool available)> InternetChangedCallback_t
Callback type for Internet connectivity change events.
Definition gateway.hpp:475
TSTRING hostFromUrl(const TSTRING &url)
The host of an http(s) URL, without scheme, port, path or query.
Definition gateway.hpp:1497
uint32_t newRequestNonce()
Definition gateway.hpp:1432
size_t refreshPeerWatchdogs(T &mesh, unsigned long stalledMs)
Give every peer's running watchdog back the time a blocking call hid.
Definition gateway.hpp:1983
Definition wifi.hpp:34
The outcome of a gateway HTTP request, classified for the ack.
Definition gateway.hpp:1093
bool unverifiedStatus
Definition gateway.hpp:1109
TSTRING reason
Definition gateway.hpp:1125
uint16_t ackStatus
Definition gateway.hpp:1098
bool retryable
Definition gateway.hpp:1119
bool success
Definition gateway.hpp:1095
bool transportError
Definition gateway.hpp:1101
Structure to hold Internet connectivity check results.
Definition gateway.hpp:431
uint32_t checkCount
Total number of checks performed.
Definition gateway.hpp:435
uint32_t failureCount
Number of failed checks.
Definition gateway.hpp:437
bool available
Whether Internet is currently available.
Definition gateway.hpp:432
uint32_t getTimeSinceLastSuccess() const
Get time since last successful Internet check.
Definition gateway.hpp:456
bool isStale(uint32_t maxAgeMs=60000) const
Check if Internet status is stale (no recent check)
Definition gateway.hpp:466
uint32_t lastCheckTime
Timestamp of last check (millis)
Definition gateway.hpp:433
uint8_t getUptimePercent() const
Get the uptime percentage of Internet connectivity.
Definition gateway.hpp:447
TSTRING lastError
Error message from last failed check.
Definition gateway.hpp:439
uint32_t lastLatencyMs
Latency of last successful check in ms.
Definition gateway.hpp:438
uint32_t lastSuccessTime
Timestamp of last successful check (millis)
Definition gateway.hpp:434
uint32_t successCount
Number of successful checks.
Definition gateway.hpp:436
uint16_t checkPort
Port used for connectivity check.
Definition gateway.hpp:441
TSTRING checkHost
Host used for connectivity check.
Definition gateway.hpp:440
One occurrence of the mesh SSID seen by an all-channel scan.
Definition gateway.hpp:69
MeshChannelCandidate()
Definition gateway.hpp:75
MeshChannelCandidate(uint8_t channel_, int32_t rssi_)
Definition gateway.hpp:76
uint8_t channel
Definition gateway.hpp:70
int32_t rssi
Definition gateway.hpp:71
Configuration structure for Shared Gateway Mode.
Definition gateway.hpp:162
uint32_t duplicateTrackingTimeout
Timeout for tracking duplicate messages in milliseconds.
Definition gateway.hpp:254
uint8_t messageRetryCount
Number of retry attempts for message delivery.
Definition gateway.hpp:237
uint32_t internetCheckInterval
Interval between Internet connectivity checks in milliseconds.
Definition gateway.hpp:201
uint16_t internetCheckPort
Port for Internet connectivity check.
Definition gateway.hpp:217
bool maintainPermanentConnection
Whether to maintain a permanent connection to the router.
Definition gateway.hpp:317
bool hasRouterCredentials() const
Check if the configuration has valid router credentials.
Definition gateway.hpp:421
uint32_t gatewayHeartbeatInterval
Interval for broadcasting gateway heartbeat in milliseconds.
Definition gateway.hpp:276
TSTRING routerSSID
Router SSID to connect for Internet access.
Definition gateway.hpp:180
TSTRING internetCheckHost
Host to ping for Internet connectivity verification.
Definition gateway.hpp:209
bool canParticipateInElection() const
Check if this node can participate in gateway elections.
Definition gateway.hpp:412
bool enabled
Enable or disable shared gateway functionality.
Definition gateway.hpp:172
bool participateInElection
Whether this node should participate in gateway elections.
Definition gateway.hpp:295
uint16_t maxTrackedMessages
Maximum number of messages to track for deduplication.
Definition gateway.hpp:263
uint8_t relayedMessagePriority
Priority for relayed messages (0 = highest/CRITICAL)
Definition gateway.hpp:308
ValidationResult validate() const
Validate the configuration.
Definition gateway.hpp:331
uint32_t retryInterval
Interval between retry attempts in milliseconds.
Definition gateway.hpp:246
uint32_t gatewayFailureTimeout
Timeout for detecting gateway failure in milliseconds.
Definition gateway.hpp:286
TSTRING routerPassword
Router password for authentication.
Definition gateway.hpp:188
uint32_t internetCheckTimeout
Timeout for Internet connectivity check in milliseconds.
Definition gateway.hpp:225
Validation result structure for SharedGatewayConfig.
Definition gateway.hpp:126
bool valid
Definition gateway.hpp:127
ValidationResult(bool v, const TSTRING &msg)
Definition gateway.hpp:131
TSTRING errorMessage
Definition gateway.hpp:128