1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

- Move all Strings to flash and optimize String usage, saving 4-5 kB of RAM.

- Replace const with constexpr where possible.

- Use default constructor instead of copy constructor for IPAddress variable initialization.

- Add MeshTypeConversionFunctions namespace around TypeConversionFunctions.

- Add MeshUtilityFunctions namespace around UtilityFunctions.

- Add ESP8266WIFIMESH_DISABLE_COMPATIBILITY preprocessor flag to retain compatibility with old code despite new namespaces.

- Add setLogEntryLifetimeMs and setBroadcastResponseTimeoutMs methods to EspnowMeshBackend.

- Move FloodingMesh constant definitions from header to .cpp file to reduce the risk of extra RAM consumption.

- Add deactivateAP method to FloodingMesh.

- Make deactivateAP static and add new non-static deactivateControlledAP method to MeshBackendBase.

- Add example of how to transfer null values using multiStrings to HelloEspnow.ino.

- Improve documentation.

- Improve comments.
This commit is contained in:
Anders 2019-12-21 16:53:18 +01:00
parent 962a23d253
commit a49f047096
27 changed files with 814 additions and 677 deletions

View File

@ -1,8 +1,12 @@
#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core.
#include <ESP8266WiFi.h>
#include <EspnowMeshBackend.h>
#include <TypeConversionFunctions.h>
#include <assert.h>
namespace TypeCast = MeshTypeConversionFunctions;
/**
NOTE: Although we could define the strings below as normal String variables,
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
@ -14,8 +18,8 @@
https://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/
const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
@ -41,7 +45,7 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance);
/* Create the mesh node object */
EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
EspnowMeshBackend espnowNode = EspnowMeshBackend(manageRequest, manageResponse, networkFilter, broadcastFilter, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
/**
Callback for when other nodes send you a request
@ -57,10 +61,10 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) {
// Of course, it is advised to adjust this approach based on RAM requirements.
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): ");
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
(void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
Serial.print("TCP/IP: ");
} else {
@ -72,7 +76,12 @@ String manageRequest(const String &request, MeshBackendBase &meshInstance) {
// If you need to print the whole String it is better to store it and print it in the loop() later.
// Note that request.substring will not work as expected if the String contains null values as data.
Serial.print("Request received: ");
Serial.println(request.substring(0, 100));
if (request.charAt(0) == 0) {
Serial.println(request); // substring will not work for multiStrings.
} else {
Serial.println(request.substring(0, 100));
}
/* return a string to send back */
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + ".");
@ -89,10 +98,10 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me
transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): ");
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
Serial.print("TCP/IP: ");
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
@ -129,12 +138,12 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
if (meshNameIndex >= 0) {
uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
espnowInstance->connectionQueue().push_back(networkIndex);
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
tcpIpInstance->connectionQueue().push_back(networkIndex);
} else {
Serial.println(String(F("Invalid mesh backend!")));
@ -345,6 +354,15 @@ void loop() {
espnowDelay(100); // Wait for responses (broadcasts can receive an unlimited number of responses, other transmissions can only receive one response).
// If you have a data array containing null values it is possible to transmit the raw data by making the array into a multiString as shown below.
// You can use String::c_str() or String::begin() to retreive the data array later.
// Note that certain String methods such as String::substring use null values to determine String length, which means they will not work as normal with multiStrings.
uint8_t dataArray[] = {0, '\'', 0, '\'', ' ', '(', 'n', 'u', 'l', 'l', ')', ' ', 'v', 'a', 'l', 'u', 'e'};
String espnowMessage = TypeCast::uint8ArrayToMultiString(dataArray, sizeof dataArray) + String(F(" from ")) + espnowNode.getMeshName() + espnowNode.getNodeID() + String(F("."));
Serial.println("\nTransmitting: " + espnowMessage);
espnowNode.attemptTransmission(espnowMessage, false);
espnowDelay(100); // Wait for response.
Serial.println("\nPerforming encrypted ESP-NOW transmissions.");
uint8_t targetBSSID[6] {0};
@ -352,7 +370,7 @@ void loop() {
// We can create encrypted connections to individual nodes so that all ESP-NOW communication with the node will be encrypted.
if (espnowNode.constConnectionQueue()[0].getBSSID(targetBSSID) && espnowNode.requestEncryptedConnection(targetBSSID) == ECS_CONNECTION_ESTABLISHED) {
// The WiFi scan will detect the AP MAC, but this will automatically be converted to the encrypted STA MAC by the framework.
String peerMac = macToString(targetBSSID);
String peerMac = TypeCast::macToString(targetBSSID);
Serial.println("Encrypted ESP-NOW connection with " + peerMac + " established!");

View File

@ -5,11 +5,15 @@
That way you will get instant confirmation of the mesh communication without checking the Serial Monitor.
*/
#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core.
#include <ESP8266WiFi.h>
#include <TypeConversionFunctions.h>
#include <assert.h>
#include <FloodingMesh.h>
namespace TypeCast = MeshTypeConversionFunctions;
/**
NOTE: Although we could define the strings below as normal String variables,
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
@ -21,8 +25,8 @@
https://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/
const char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
constexpr char exampleMeshName[] PROGMEM = "MeshNode_"; // The name of the mesh network. Used as prefix for the node SSID and to find other network nodes in the example networkFilter and broadcastFilter functions below.
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; // The password has to be min 8 and max 64 characters long, otherwise an AP which uses it will not be found during scans.
// A custom encryption key is required when using encrypted ESP-NOW transmissions. There is always a default Kok set, but it can be replaced if desired.
// All ESP-NOW keys below must match in an encrypted connection pair for encrypted communication to be possible.
@ -37,10 +41,10 @@ uint8_t espnowHashKey[16] = {0xEF, 0x44, 0x33, 0x0C, 0x33, 0x44, 0xFE, 0x44, //
bool meshMessageHandler(String &message, FloodingMesh &meshInstance);
/* Create the mesh node object */
FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
FloodingMesh floodingMesh = FloodingMesh(meshMessageHandler, FPSTR(exampleWiFiPassword), espnowEncryptedConnectionKey, espnowHashKey, FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
bool theOne = true;
String theOneMac = "";
String theOneMac;
bool useLED = false; // Change this to true if you wish the onboard LED to mark The One.
@ -58,7 +62,7 @@ bool useLED = false; // Change this to true if you wish the onboard LED to mark
bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter());
if (delimiterIndex == 0) {
Serial.print("Message received from STA MAC " + meshInstance.getEspnowMeshBackend().getSenderMac() + ": ");
Serial.print(String(F("Message received from STA MAC ")) + meshInstance.getEspnowMeshBackend().getSenderMac() + F(": "));
Serial.println(message.substring(2, 102));
String potentialMac = message.substring(2, 14);
@ -95,17 +99,17 @@ bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
previousTotalBroadcasts = totalBroadcasts;
if (totalReceivedBroadcasts % 50 == 0) {
Serial.println("missed/total: " + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts));
Serial.println(String(F("missed/total: ")) + String(missedBroadcasts) + '/' + String(totalReceivedBroadcasts));
}
if (totalReceivedBroadcasts % 500 == 0) {
Serial.println("Benchmark message: " + message.substring(0, 100));
Serial.println(String(F("Benchmark message: ")) + message.substring(0, 100));
}
}
}
} else {
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
// If you need to print the whole String it is better to store it and print it in the loop() later.
Serial.print("Message with origin " + meshInstance.getOriginMac() + " received: ");
Serial.print(String(F("Message with origin ")) + meshInstance.getOriginMac() + F(" received: "));
Serial.println(message.substring(0, 100));
}
@ -139,7 +143,7 @@ void setup() {
floodingMesh.activateAP();
uint8_t apMacArray[6] {0};
theOneMac = macToString(WiFi.softAPmacAddress(apMacArray));
theOneMac = TypeCast::macToString(WiFi.softAPmacAddress(apMacArray));
if (useLED) {
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
@ -150,7 +154,7 @@ void setup() {
// The main benefit of AEAD encryption is that it can be used with normal broadcasts (which are substantially faster than encryptedBroadcasts).
// The main drawbacks are that AEAD only encrypts the message data (not transmission metadata), transfers less data per message and lacks replay attack protection.
// When using AEAD, potential replay attacks must thus be handled manually.
//floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey("ChangeThisKeySeed_TODO"); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used.
//floodingMesh.getEspnowMeshBackend().setEspnowMessageEncryptionKey(F("ChangeThisKeySeed_TODO")); // The message encryption key should always be set manually. Otherwise a default key (all zeroes) is used.
//floodingMesh.getEspnowMeshBackend().setUseEncryptedMessages(true);
floodingMeshDelay(5000); // Give some time for user to start the nodes
@ -180,8 +184,8 @@ void loop() {
ledState = ledState ^ bool(benchmarkCount); // Make other nodes' LEDs alternate between on and off once benchmarking begins.
// Note: The maximum length of an unencrypted broadcast message is given by floodingMesh.maxUnencryptedMessageLength(). It is around 670 bytes by default.
floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + " is The One.");
Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms.");
floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + F(" is The One."));
Serial.println(String(F("Proclamation broadcast done in ")) + String(millis() - startTime) + F(" ms."));
timeOfLastProclamation = millis();
floodingMeshDelay(20);
@ -189,8 +193,8 @@ void loop() {
if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made
uint32_t startTime = millis();
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + ": Not a spoon in sight.");
Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms.");
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + F(": Not a spoon in sight."));
Serial.println(String(F("Benchmark broadcast done in ")) + String(millis() - startTime) + F(" ms."));
floodingMeshDelay(20);
}
}

View File

@ -1,8 +1,12 @@
#define ESP8266WIFIMESH_DISABLE_COMPATIBILITY // Excludes redundant compatibility code. Should be used for new code until the compatibility code is removed with release 3.0.0 of the Arduino core.
#include <ESP8266WiFi.h>
#include <TcpIpMeshBackend.h>
#include <TypeConversionFunctions.h>
#include <assert.h>
namespace TypeCast = MeshTypeConversionFunctions;
/**
NOTE: Although we could define the strings below as normal String variables,
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
@ -14,8 +18,8 @@
https://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/
const char exampleMeshName[] PROGMEM = "MeshNode_";
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
constexpr char exampleMeshName[] PROGMEM = "MeshNode_";
constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
unsigned int requestNumber = 0;
unsigned int responseNumber = 0;
@ -25,7 +29,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
/* Create the mesh node object */
TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), uint64ToString(ESP.getChipId()), true);
TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, networkFilter, FPSTR(exampleWiFiPassword), FPSTR(exampleMeshName), TypeCast::uint64ToString(ESP.getChipId()), true);
/**
Callback for when other nodes send you a request
@ -35,31 +39,26 @@ TcpIpMeshBackend tcpIpNode = TcpIpMeshBackend(manageRequest, manageResponse, net
@return The string to send back to the other node. For ESP-NOW, return an empy string ("") if no response should be sent.
*/
String manageRequest(const String &request, MeshBackendBase &meshInstance) {
// We do not store strings in flash (via F()) in this function.
// The reason is that the other node will be waiting for our response,
// so keeping the strings in RAM will give a (small) improvement in response time.
// Of course, it is advised to adjust this approach based on RAM requirements.
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): ");
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
(void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
Serial.print("TCP/IP: ");
Serial.print(F("TCP/IP: "));
} else {
Serial.print("UNKNOWN!: ");
Serial.print(F("UNKNOWN!: "));
}
/* Print out received message */
// Only show first 100 characters because printing a large String takes a lot of time, which is a bad thing for a callback function.
// If you need to print the whole String it is better to store it and print it in the loop() later.
// Note that request.substring will not work as expected if the String contains null values as data.
Serial.print("Request received: ");
Serial.print(F("Request received: "));
Serial.println(request.substring(0, 100));
/* return a string to send back */
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + ".");
return (String(F("Hello world response #")) + String(responseNumber++) + F(" from ") + meshInstance.getMeshName() + meshInstance.getNodeID() + F(" with AP MAC ") + WiFi.softAPmacAddress() + String('.'));
}
/**
@ -73,11 +72,11 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me
transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
// To get the actual class of the polymorphic meshInstance, do as follows (meshBackendCast replaces dynamic_cast since RTTI is disabled)
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): ");
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
Serial.print("TCP/IP: ");
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
Serial.print(F("TCP/IP: "));
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used.
// With TCP/IP the response will follow immediately after the request, so the stored message will not have changed.
@ -86,7 +85,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me
Serial.print(F("Request sent: "));
Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100));
} else {
Serial.print("UNKNOWN!: ");
Serial.print(F("UNKNOWN!: "));
}
/* Print out received message */
@ -113,15 +112,15 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
if (meshNameIndex >= 0) {
uint64_t targetNodeID = stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
uint64_t targetNodeID = TypeCast::stringToUint64(currentSSID.substring(meshNameIndex + meshInstance.getMeshName().length()));
if (targetNodeID < stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
if (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
espnowInstance->connectionQueue().push_back(networkIndex);
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
} else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
tcpIpInstance->connectionQueue().push_back(networkIndex);
} else {
Serial.println(String(F("Invalid mesh backend!")));
Serial.println(F("Invalid mesh backend!"));
}
}
}
@ -142,14 +141,14 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
// The default hook only returns true and does nothing else.
if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TS_TRANSMISSION_COMPLETE) {
// Our last request got a response, so time to create a new request.
meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + String(F(" from "))
+ meshInstance.getMeshName() + meshInstance.getNodeID() + String(F(".")));
meshInstance.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ")
+ meshInstance.getMeshName() + meshInstance.getNodeID() + String('.'));
}
} else {
Serial.println(String(F("Invalid mesh backend!")));
Serial.println(F("Invalid mesh backend!"));
}
return true;
@ -186,7 +185,7 @@ void setup() {
// Storing our message in the TcpIpMeshBackend instance is not required, but can be useful for organizing code, especially when using many TcpIpMeshBackend instances.
// Note that calling the multi-recipient tcpIpNode.attemptTransmission will replace the stored message with whatever message is transmitted.
tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + String(F(" from ")) + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String(F(".")));
tcpIpNode.setMessage(String(F("Hello world request #")) + String(requestNumber) + F(" from ") + tcpIpNode.getMeshName() + tcpIpNode.getNodeID() + String('.'));
tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
}
@ -217,7 +216,7 @@ void loop() {
} else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) {
// No need to do anything, transmission was successful.
} else {
Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String(F("!")));
Serial.println(String(F("Invalid transmission status for ")) + transmissionOutcome.SSID() + String('!'));
assert(F("Invalid transmission status returned from responseHandler!") && false);
}
}

View File

@ -28,6 +28,8 @@
#include <bearssl/bearssl.h>
namespace TypeCast = MeshTypeConversionFunctions;
namespace
{
size_t _ctMinDataLength = 0;
@ -130,7 +132,7 @@ namespace
uint8_t hmac[hmacLength];
createBearsslHmac(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength);
return uint8ArrayToHexString(hmac, hmacLength);
return TypeCast::uint8ArrayToHexString(hmac, hmacLength);
}
void *createBearsslHmacCT(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -182,7 +184,7 @@ namespace
uint8_t hmac[hmacLength];
createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength);
return uint8ArrayToHexString(hmac, hmacLength);
return TypeCast::uint8ArrayToHexString(hmac, hmacLength);
}
}
@ -190,14 +192,14 @@ namespace CryptoInterface
{
void setCtMinDataLength(const size_t ctMinDataLength)
{
assert(ctMaxDataLength() - ctMinDataLength <= ctMaxDiff);
assert(ctMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF);
_ctMinDataLength = ctMinDataLength;
}
size_t ctMinDataLength() {return _ctMinDataLength;}
void setCtMaxDataLength(const size_t ctMaxDataLength)
{
assert(ctMaxDataLength - ctMinDataLength() <= ctMaxDiff);
assert(ctMaxDataLength - ctMinDataLength() <= CT_MAX_DIFF);
_ctMaxDataLength = ctMaxDataLength;
}
size_t ctMaxDataLength() {return _ctMaxDataLength;}
@ -230,7 +232,7 @@ namespace CryptoInterface
{
uint8_t hash[MD5_NATURAL_LENGTH];
md5Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, MD5_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, MD5_NATURAL_LENGTH);
}
void *md5Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -275,7 +277,7 @@ namespace CryptoInterface
{
uint8_t hash[SHA1_NATURAL_LENGTH];
sha1Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, SHA1_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, SHA1_NATURAL_LENGTH);
}
void *sha1Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -315,7 +317,7 @@ namespace CryptoInterface
{
uint8_t hash[SHA224_NATURAL_LENGTH];
sha224Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, SHA224_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, SHA224_NATURAL_LENGTH);
}
void *sha224Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -355,7 +357,7 @@ namespace CryptoInterface
{
uint8_t hash[SHA256_NATURAL_LENGTH];
sha256Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, SHA256_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, SHA256_NATURAL_LENGTH);
}
void *sha256Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -395,7 +397,7 @@ namespace CryptoInterface
{
uint8_t hash[SHA384_NATURAL_LENGTH];
sha384Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, SHA384_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, SHA384_NATURAL_LENGTH);
}
void *sha384Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -435,7 +437,7 @@ namespace CryptoInterface
{
uint8_t hash[SHA512_NATURAL_LENGTH];
sha512Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, SHA512_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, SHA512_NATURAL_LENGTH);
}
void *sha512Hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
@ -475,7 +477,7 @@ namespace CryptoInterface
{
uint8_t hash[MD5SHA1_NATURAL_LENGTH];
md5sha1Hash(message.c_str(), message.length(), hash);
return uint8ArrayToHexString(hash, MD5SHA1_NATURAL_LENGTH);
return TypeCast::uint8ArrayToHexString(hash, MD5SHA1_NATURAL_LENGTH);
}

View File

@ -71,7 +71,7 @@ namespace CryptoInterface
constexpr uint8_t ENCRYPTION_KEY_LENGTH = 32;
constexpr uint32_t ctMaxDiff = 1073741823; // 2^30 - 1
constexpr uint32_t CT_MAX_DIFF = 1073741823; // 2^30 - 1
/**
* This function allows for fine-tuning of the specifications for the constant time calculations.
@ -672,7 +672,7 @@ namespace CryptoInterface
// #################### HKDF ####################
/**
* KDF are functions that takes a variable length input, and provide a variable length output, meant to be used to derive subkeys from a master key.
* KDFs (key derivation functions) are functions that takes a variable length input, and provide a variable length output, meant to be used to derive subkeys from a master key.
* HKDF is a KDF defined by RFC 5869. It is based on HMAC. The provided implementation uses SHA-256 as the underlying hash function.
*
* This function initializes the HKDF implementation with the input data to use for HKDF processing.
@ -755,7 +755,7 @@ namespace CryptoInterface
*
* @param data An array containing the data to encrypt. The encrypted data is generated in place, so when the function returns the data array will contain the encrypted data.
* @param dataLength The length of the data array in bytes.
* @param key The secret encryption key to use.
* @param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long.
* @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation.
* @param keySaltLength The length of keySalt in bytes.
* @param resultingNonce The array that will store the nonce generated during encryption. Must be able to contain at least 12 bytes. The nonce is not secret and must be passed to the decryption function.
@ -780,7 +780,7 @@ namespace CryptoInterface
*
* @param data An array containing the data to decrypt. The decrypted data is generated in place, so when the function returns the data array will contain the decrypted data.
* @param dataLength The length of the data array in bytes.
* @param key The secret encryption key to use.
* @param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long.
* @param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation.
* @param keySaltLength The length of keySalt in bytes.
* @param encryptionNonce An array containing the nonce that was generated during encryption. The nonce should be 12 bytes.

View File

@ -48,6 +48,8 @@
#include "ESP8266WiFiMesh.h"
#include "TypeConversionFunctions.h"
namespace TypeCast = MeshTypeConversionFunctions;
#define SERVER_IP_ADDR "192.168.4.1"
const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress();
@ -74,7 +76,7 @@ ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHand
const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort)
: _server(serverPort)
{
updateNetworkNames(meshName, (nodeID != "" ? nodeID : uint64ToString(ESP.getChipId())));
updateNetworkNames(meshName, (nodeID != "" ? nodeID : TypeCast::uint64ToString(ESP.getChipId())));
_requestHandler = requestHandler;
_responseHandler = responseHandler;
setWiFiChannel(meshWiFiChannel);

View File

@ -29,6 +29,7 @@
#include "MeshCryptoInterface.h"
using EspnowProtocolInterpreter::espnowHashKeyLength;
namespace TypeCast = MeshTypeConversionFunctions;
EncryptedConnectionData::EncryptedConnectionData(const uint8_t peerStaMac[6], const uint8_t peerApMac[6], uint64_t peerSessionKey, uint64_t ownSessionKey, const uint8_t hashKey[espnowHashKeyLength])
: _peerSessionKey(peerSessionKey), _ownSessionKey(ownSessionKey)
@ -97,7 +98,7 @@ void EncryptedConnectionData::setPeerApMac(const uint8_t *peerApMac)
bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const
{
if(macEqual(peerMac, _peerStaMac) || macEqual(peerMac, _peerApMac))
if(MeshUtilityFunctions::macEqual(peerMac, _peerStaMac) || MeshUtilityFunctions::macEqual(peerMac, _peerApMac))
{
return true;
}
@ -130,12 +131,12 @@ uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const
{
uint8_t inputArray[8] {0};
uint8_t hmacArray[CryptoInterface::SHA256_NATURAL_LENGTH] {0};
CryptoInterface::sha256Hmac(uint64ToUint8Array(sessionKey, inputArray), 8, hashKey, hashKeyLength, hmacArray, CryptoInterface::SHA256_NATURAL_LENGTH);
CryptoInterface::sha256Hmac(TypeCast::uint64ToUint8Array(sessionKey, inputArray), 8, hashKey, hashKeyLength, hmacArray, CryptoInterface::SHA256_NATURAL_LENGTH);
/* HMAC truncation should be OK since hmac sha256 is a PRF and we are truncating to the leftmost (MSB) bits.
PRF: https://crypto.stackexchange.com/questions/26410/whats-the-gcm-sha-256-of-a-tls-protocol/26434#26434
Truncate to leftmost bits: https://tools.ietf.org/html/rfc2104#section-5 */
uint64_t newLeftmostBits = uint8ArrayToUint64(hmacArray) & EspnowProtocolInterpreter::uint64LeftmostBits;
uint64_t newLeftmostBits = TypeCast::uint8ArrayToUint64(hmacArray) & EspnowProtocolInterpreter::uint64LeftmostBits;
if(newLeftmostBits == 0)
newLeftmostBits = ((uint64_t)RANDOM_REG32 | (1 << 31)) << 32; // We never want newLeftmostBits == 0 since that would indicate an unencrypted transmission.
@ -158,13 +159,13 @@ String EncryptedConnectionData::serialize() const
// Returns: {"connectionState":{"duration":"123","password":"abc","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
return
JsonTranslator::jsonConnectionState
+ (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "")
+ JsonTranslator::jsonDesync + "\"" + String(desync()) + "\","
+ JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\","
+ JsonTranslator::jsonPeerSessionKey + "\"" + uint64ToString(getPeerSessionKey()) + "\","
+ JsonTranslator::jsonPeerStaMac + "\"" + macToString(_peerStaMac) + "\","
+ JsonTranslator::jsonPeerApMac + "\"" + macToString(_peerApMac) + "\"}}";
String(FPSTR(JsonTranslator::jsonConnectionState))
+ (temporary() ? String(FPSTR(JsonTranslator::jsonDuration)) + '\"' + String(temporary()->remainingDuration()) + F("\",") : emptyString)
+ FPSTR(JsonTranslator::jsonDesync) + '\"' + String(desync()) + F("\",")
+ FPSTR(JsonTranslator::jsonOwnSessionKey) + '\"' + TypeCast::uint64ToString(getOwnSessionKey()) + F("\",")
+ FPSTR(JsonTranslator::jsonPeerSessionKey) + '\"' + TypeCast::uint64ToString(getPeerSessionKey()) + F("\",")
+ FPSTR(JsonTranslator::jsonPeerStaMac) + '\"' + TypeCast::macToString(_peerStaMac) + F("\",")
+ FPSTR(JsonTranslator::jsonPeerApMac) + '\"' + TypeCast::macToString(_peerApMac) + F("\"}}");
}
const ExpiringTimeTracker *EncryptedConnectionData::temporary() const

View File

@ -31,6 +31,8 @@ extern "C" {
using EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength;
using EspnowProtocolInterpreter::espnowHashKeyLength;
namespace TypeCast = MeshTypeConversionFunctions;
static const uint8_t maxEncryptedConnections = 6; // This is limited by the ESP-NOW API. Max 6 in AP or AP+STA mode. Max 10 in STA mode. See "ESP-NOW User Guide" for more info.
static const uint64_t uint64MSB = 0x8000000000000000;
@ -62,7 +64,7 @@ uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 300;
bool EspnowMeshBackend::_espnowSendConfirmed = false;
String EspnowMeshBackend::_ongoingPeerRequestNonce = "";
String EspnowMeshBackend::_ongoingPeerRequestNonce;
uint8_t EspnowMeshBackend::_ongoingPeerRequestMac[6] = {0};
EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr;
encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF;
@ -116,7 +118,7 @@ EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, response
encryptedConnections.reserve(maxEncryptedConnections);
setBroadcastFilter(broadcastFilter);
setSSID(ssidPrefix, "", ssidSuffix);
setSSID(ssidPrefix, emptyString, ssidSuffix);
setMeshPassword(meshPassword);
setVerboseModeState(verboseMode);
setWiFiChannel(meshWiFiChannel);
@ -165,7 +167,7 @@ bool EspnowMeshBackend::activateEspnow()
if (esp_now_init()==0)
{
if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptedConnectionKeyLength)) // esp_now_set_kok returns 0 on success.
warningPrint("Failed to set ESP-NOW KoK!");
warningPrint(String(F("Failed to set ESP-NOW KoK!")));
if(getEspnowRequestManager() == nullptr)
{
@ -176,23 +178,23 @@ bool EspnowMeshBackend::activateEspnow()
esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) {
if(_espnowSendConfirmed)
return;
else if(!sendStatus && macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK.
else if(!sendStatus && MeshUtilityFunctions::macEqual(mac, _transmissionTargetBSSID)) // sendStatus == 0 when send was OK.
_espnowSendConfirmed = true; // We do not want to reset this to false. That only happens before transmissions. Otherwise subsequent failed send attempts may obscure an initial successful one.
});
// Role must be set before adding peers. Cannot be changed while having peers.
// With ESP_NOW_ROLE_CONTROLLER, we always transmit from the station interface, which gives predictability.
if(esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER)) // esp_now_set_self_role returns 0 on success.
warningPrint("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?");
warningPrint(String(F("Failed to set ESP-NOW role! Maybe ESP-NOW peers are already added?")));
verboseModePrint("ESP-NOW activated.");
verboseModePrint("My ESP-NOW STA MAC: " + WiFi.macAddress() + '\n'); // Get the station MAC address. The softAP MAC is different.
verboseModePrint(String(F("ESP-NOW activated.")));
verboseModePrint(String(F("My ESP-NOW STA MAC: ")) + WiFi.macAddress() + '\n'); // Get the station MAC address. The softAP MAC is different.
return true;
}
else
{
warningPrint("ESP-NOW init failed!");
warningPrint(String(F("ESP-NOW init failed!")));
return false;
}
}
@ -224,7 +226,7 @@ std::vector<EspnowNetworkInfo> & EspnowMeshBackend::connectionQueue()
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!");
assert(false && String(F("ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!")));
}
return _connectionQueue;
@ -253,7 +255,7 @@ void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration)
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call performEspnowMaintenance from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call performEspnowMaintenance from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -429,6 +431,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *
if(useEncryptedMessages())
{
// chacha20Poly1305Decrypt decrypts dataArray in place.
// We are using the protocol bytes as a key salt.
if(!CryptoInterface::chacha20Poly1305Decrypt(dataArray + espnowMetadataSize(), len - espnowMetadataSize(), getEspnowMessageEncryptionKey(), dataArray,
espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize + 12))
{
@ -436,11 +439,11 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *
}
}
uint64_t uint64StationMac = macToUint64(macaddr);
uint64_t uint64StationMac = TypeCast::macToUint64(macaddr);
bool transmissionEncrypted = usesEncryption(receivedMessageID);
// Useful when debugging the protocol
//Serial.print("Received from Mac: " + macToString(macaddr) + " ID: " + uint64ToString(receivedMessageID));
//Serial.print("Received from Mac: " + TypeCast::macToString(macaddr) + " ID: " + TypeCast::uint64ToString(receivedMessageID));
//Serial.println(transmissionEncrypted ? " Encrypted" : " Unencrypted");
if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast
@ -512,7 +515,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *
{
uint8_t macArray[6] = { 0 };
requestSender->espnowReceiveCallback(uint64ToMac(requestMac, macArray), dataArray, len);
requestSender->espnowReceiveCallback(TypeCast::uint64ToMac(requestMac, macArray), dataArray, len);
}
}
else if(messageType == 'S') // Synchronization request
@ -555,7 +558,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
int32_t messageHeaderEndIndex = message.indexOf(':');
String messageHeader = message.substring(0, messageHeaderEndIndex + 1);
if(messageHeader == encryptedConnectionVerificationHeader)
if(messageHeader == FPSTR(encryptedConnectionVerificationHeader))
{
if(encryptedCorrectly)
{
@ -563,13 +566,13 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1);
connectionLogIterator encryptedConnection = connectionLogEndIterator();
if(!getEncryptedConnectionIterator(macaddr, encryptedConnection))
assert(false && "We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly.");
assert(false && String(F("We must have an encrypted connection if we received an encryptedConnectionVerificationHeader which was encryptedCorrectly.")));
if(connectionRequestType == encryptionRequestHeader)
if(connectionRequestType == FPSTR(encryptionRequestHeader))
{
temporaryEncryptedConnectionToPermanent(macaddr);
}
else if(connectionRequestType == temporaryEncryptionRequestHeader)
else if(connectionRequestType == FPSTR(temporaryEncryptionRequestHeader))
{
if(encryptedConnection->temporary()) // Should not change duration for existing permanent connections.
{
@ -582,27 +585,27 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
}
else
{
assert(false && "Unknown P-type verification message received!");
assert(false && String(F("Unknown P-type verification message received!")));
}
}
}
else if(messageHeader == encryptionRequestHeader || messageHeader == temporaryEncryptionRequestHeader)
else if(messageHeader == FPSTR(encryptionRequestHeader) || messageHeader == FPSTR(temporaryEncryptionRequestHeader))
{
// If there is a espnowRequestManager, get it
if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager())
{
String requestNonce = "";
String requestNonce;
if(JsonTranslator::getNonce(message, requestNonce) && requestNonce.length() >= 12) // The destination MAC address requires 12 characters.
{
uint8_t destinationMac[6] = {0};
stringToMac(requestNonce, destinationMac);
TypeCast::stringToMac(requestNonce, destinationMac);
uint8_t apMac[6] {0};
WiFi.softAPmacAddress(apMac);
bool correctDestination = false;
if(macEqual(destinationMac, apMac))
if(MeshUtilityFunctions::macEqual(destinationMac, apMac))
{
correctDestination = true;
}
@ -611,7 +614,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
uint8_t staMac[6] {0};
WiFi.macAddress(staMac);
if(macEqual(destinationMac, staMac))
if(MeshUtilityFunctions::macEqual(destinationMac, staMac))
{
correctDestination = true;
}
@ -624,14 +627,14 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
}
}
}
else if(messageHeader == encryptedConnectionRemovalRequestHeader)
else if(messageHeader == FPSTR(encryptedConnectionRemovalRequestHeader))
{
if(encryptedCorrectly)
removeEncryptedConnection(macaddr);
}
else
{
assert(false && "Unknown P-type message received!");
assert(false && String(F("Unknown P-type message received!")));
}
}
}
@ -646,10 +649,10 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
using namespace EspnowProtocolInterpreter;
if(_ongoingPeerRequestNonce != "")
if(!_ongoingPeerRequestNonce.isEmpty())
{
String message = espnowGetMessageContent(dataArray, len);
String requestNonce = "";
String requestNonce;
if(JsonTranslator::getNonce(message, requestNonce) && requestNonce == _ongoingPeerRequestNonce)
{
@ -659,7 +662,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
uint8_t apMacArray[6] = { 0 };
espnowGetTransmissionMac(dataArray, apMacArray);
if(messageHeader == basicConnectionInfoHeader)
if(messageHeader == FPSTR(basicConnectionInfoHeader))
{
// encryptedConnectionEstablished(_ongoingPeerRequestResult) means we have already received a basicConnectionInfoHeader
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) &&
@ -691,13 +694,13 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult))
{
// Adding connection failed, abort ongoing peer request.
_ongoingPeerRequestNonce = "";
_ongoingPeerRequestNonce.clear();
}
}
}
else if(messageHeader == encryptedConnectionInfoHeader || messageHeader == softLimitEncryptedConnectionInfoHeader)
else if(messageHeader == FPSTR(encryptedConnectionInfoHeader) || messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader))
{
String messagePassword = "";
String messagePassword;
if(JsonTranslator::getPassword(messageBody, messagePassword) && messagePassword == _ongoingPeerRequester->getMeshPassword())
{
@ -711,33 +714,33 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
encryptedConnection->setPeerSessionKey(peerSessionKey);
encryptedConnection->setOwnSessionKey(ownSessionKey);
if(messageHeader == encryptedConnectionInfoHeader)
if(messageHeader == FPSTR(encryptedConnectionInfoHeader))
_ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED;
else if(messageHeader == softLimitEncryptedConnectionInfoHeader)
else if(messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader))
_ongoingPeerRequestResult = ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED;
else
assert(false && "Unknown _ongoingPeerRequestResult!");
assert(false && String(F("Unknown _ongoingPeerRequestResult!")));
}
else
{
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
}
_ongoingPeerRequestNonce = "";
_ongoingPeerRequestNonce.clear();
}
}
else if(messageHeader == maxConnectionsReachedHeader)
else if(messageHeader == FPSTR(maxConnectionsReachedHeader))
{
if(JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength))
{
_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER;
_ongoingPeerRequestNonce = "";
_ongoingPeerRequestNonce.clear();
}
}
else
{
assert(messageHeader == basicConnectionInfoHeader || messageHeader == encryptedConnectionInfoHeader ||
messageHeader == softLimitEncryptedConnectionInfoHeader || messageHeader == maxConnectionsReachedHeader);
assert(messageHeader == FPSTR(basicConnectionInfoHeader) || messageHeader == FPSTR(encryptedConnectionInfoHeader) ||
messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader) || messageHeader == FPSTR(maxConnectionsReachedHeader));
}
}
}
@ -770,7 +773,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr
char messageType = espnowGetMessageType(dataArray);
uint8_t transmissionsRemaining = espnowGetTransmissionsRemaining(dataArray);
uint64_t uint64Mac = macToUint64(macaddr);
uint64_t uint64Mac = TypeCast::macToUint64(macaddr);
// The MAC is 6 bytes so two bytes of uint64Mac are free. We must include the messageType there since it is possible that we will
// receive both a request and a response that shares the same messageID from the same uint64Mac, being distinguished only by the messageType.
@ -915,15 +918,17 @@ bool EspnowMeshBackend::encryptedConnectionEstablished(encrypted_connection_stat
return connectionStatus > 0;
}
uint32_t EspnowMeshBackend::logEntryLifetimeMs()
void EspnowMeshBackend::setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs)
{
return _logEntryLifetimeMs;
_logEntryLifetimeMs = logEntryLifetimeMs;
}
uint32_t EspnowMeshBackend::logEntryLifetimeMs() { return _logEntryLifetimeMs; }
uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs()
void EspnowMeshBackend::setBroadcastResponseTimeoutMs(uint32_t broadcastResponseTimeoutMs)
{
return _broadcastResponseTimeoutMs;
_broadcastResponseTimeoutMs = broadcastResponseTimeoutMs;
}
uint32_t EspnowMeshBackend::broadcastResponseTimeoutMs() { return _broadcastResponseTimeoutMs; }
void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes)
{
@ -1016,7 +1021,7 @@ uint64_t EspnowMeshBackend::generateMessageID(EncryptedConnectionLog *encryptedC
uint64_t EspnowMeshBackend::createSessionKey()
{
uint64_t newSessionKey = randomUint64();
uint64_t newSessionKey = MeshUtilityFunctions::randomUint64();
return EspnowProtocolInterpreter::usesEncryption(newSessionKey) ? newSessionKey : (newSessionKey | ((uint64_t)RANDOM_REG32) << 32 | uint64MSB);
}
@ -1063,11 +1068,11 @@ transmission_status_t EspnowMeshBackend::espnowSendToNode(const String &message,
uint8_t encryptedMac[6] {0};
encryptedConnection->getEncryptedPeerMac(encryptedMac);
assert(esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!");
assert(esp_now_is_peer_exist(encryptedMac) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!")));
if(encryptedConnection->desync())
{
espnowSendToNodeUnsynchronized(synchronizationRequestHeader, encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance);
espnowSendToNodeUnsynchronized(FPSTR(synchronizationRequestHeader), encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance);
if(encryptedConnection->desync())
{
@ -1090,7 +1095,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
MutexTracker mutexTracker(_espnowSendToNodeMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! espnowSendToNode already in progress. Don't call espnowSendToNode from callbacks as this will make it impossible to know which transmissions succeed! Aborting.");
assert(false && String(F("ERROR! espnowSendToNode already in progress. Don't call espnowSendToNode from callbacks as this will make it impossible to know which transmissions succeed! Aborting.")));
return TS_TRANSMISSION_FAILED;
}
@ -1131,7 +1136,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
// If we are sending the last transmission of a request we should store the sent request in the log no matter if we receive an ack for the final transmission or not.
// That way we will always be ready to receive the response to the request when there is a chance the request message was transmitted successfully,
// even if the final ack for the request message was lost.
storeSentRequest(macToUint64(_transmissionTargetBSSID), messageID, RequestData(*espnowInstance));
storeSentRequest(TypeCast::macToUint64(_transmissionTargetBSSID), messageID, RequestData(*espnowInstance));
}
////// Create transmission array //////
@ -1232,9 +1237,9 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
{
_transmissionsFailed++;
staticVerboseModePrint("espnowSendToNode failed!");
staticVerboseModePrint("Transmission #: " + String(transmissionsRequired - transmissionsRemaining) + "/" + String(transmissionsRequired));
staticVerboseModePrint("Transmission fail rate (up) " + String(getTransmissionFailRate()));
staticVerboseModePrint(String(F("espnowSendToNode failed!")));
staticVerboseModePrint(String(F("Transmission #: ")) + String(transmissionsRequired - transmissionsRemaining) + String('/') + String(transmissionsRequired));
staticVerboseModePrint(String(F("Transmission fail rate (up) ")) + String(getTransmissionFailRate()));
if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID)
encryptedConnection->setDesync(true);
@ -1247,7 +1252,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
} while(transmissionsRemaining >= 0);
// Useful when debugging the protocol
//staticVerboseModePrint("Sent to Mac: " + macToString(_transmissionTargetBSSID) + " ID: " + uint64ToString(messageID));
//staticVerboseModePrint("Sent to Mac: " + TypeCast::macToString(_transmissionTargetBSSID) + " ID: " + TypeCast::uint64ToString(messageID));
return TS_TRANSMISSION_COMPLETE;
}
@ -1267,7 +1272,7 @@ transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uin
if(encryptedConnection)
{
encryptedConnection->getEncryptedPeerMac(encryptedMac);
assert(esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!");
assert(esp_now_is_peer_exist(encryptedMac) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!")));
}
return espnowSendToNodeUnsynchronized(message, encryptedConnection ? encryptedMac : targetBSSID, 'A', requestID, this);
@ -1387,7 +1392,7 @@ void EspnowMeshBackend::setUseEncryptedMessages(bool useEncryptedMessages)
MutexTracker mutexTracker(_espnowSendToNodeMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! espnowSendToNode in progress. Don't call setUseEncryptedMessages from non-hook callbacks since this may modify the ESP-NOW transmission parameters during ongoing transmissions! Aborting.");
assert(false && String(F("ERROR! espnowSendToNode in progress. Don't call setUseEncryptedMessages from non-hook callbacks since this may modify the ESP-NOW transmission parameters during ongoing transmissions! Aborting.")));
}
_useEncryptedMessages = useEncryptedMessages;
@ -1398,7 +1403,7 @@ bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t
{
if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac))
{
return verifyPeerSessionKey(sessionKey, *encryptedConnection, macToUint64(peerMac), messageType);
return verifyPeerSessionKey(sessionKey, *encryptedConnection, TypeCast::macToUint64(peerMac), messageType);
}
return false;
@ -1479,7 +1484,7 @@ void EspnowMeshBackend::clearAllScheduledResponses()
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
if(!responsesToSendMutexTracker.mutexCaptured())
{
assert(false && "ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! responsesToSend locked. Don't call clearAllScheduledResponses from callbacks as this may corrupt program state! Aborting.")));
}
responsesToSend.clear();
@ -1490,12 +1495,13 @@ void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recip
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
if(!responsesToSendMutexTracker.mutexCaptured())
{
assert(false && "ERROR! responsesToSend locked. Don't call deleteScheduledResponsesByRecipient from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! responsesToSend locked. Don't call deleteScheduledResponsesByRecipient from callbacks as this may corrupt program state! Aborting.")));
}
for(auto responseIterator = responsesToSend.begin(); responseIterator != responsesToSend.end(); )
{
if(macEqual(responseIterator->getRecipientMac(), recipientMac) && (!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID())))
if(MeshUtilityFunctions::macEqual(responseIterator->getRecipientMac(), recipientMac) &&
(!encryptedOnly || EspnowProtocolInterpreter::usesEncryption(responseIterator->getRequestID())))
{
responseIterator = responsesToSend.erase(responseIterator);
}
@ -1509,7 +1515,7 @@ void EspnowMeshBackend::setSenderMac(uint8_t *macArray)
std::copy_n(macArray, 6, _senderMac);
}
String EspnowMeshBackend::getSenderMac() {return macToString(_senderMac);}
String EspnowMeshBackend::getSenderMac() {return TypeCast::macToString(_senderMac);}
uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray)
{
std::copy_n(_senderMac, 6, macArray);
@ -1521,7 +1527,7 @@ void EspnowMeshBackend::setSenderAPMac(uint8_t *macArray)
std::copy_n(macArray, 6, _senderAPMac);
}
String EspnowMeshBackend::getSenderAPMac() {return macToString(_senderAPMac);}
String EspnowMeshBackend::getSenderAPMac() {return TypeCast::macToString(_senderAPMac);}
uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray)
{
std::copy_n(_senderAPMac, 6, macArray);
@ -1639,7 +1645,7 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection
if(result == ECS_CONNECTION_ESTABLISHED)
{
if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection))
assert(false && "No connection found despite being added in addTemporaryEncryptedConnection.");
assert(false && String(F("No connection found despite being added in addTemporaryEncryptedConnection.")));
encryptedConnection->setRemainingDuration(duration);
}
@ -1680,7 +1686,7 @@ void EspnowMeshBackend::handlePostponedRemovals()
MutexTracker mutexTracker(_espnowTransmissionMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call handlePostponedRemovals from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -1699,7 +1705,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call requestEncryptedConnection from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call requestEncryptedConnection from callbacks as this may corrupt program state! Aborting.")));
return ECS_REQUEST_TRANSMISSION_FAILED;
}
@ -1715,7 +1721,8 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
return ECS_MAX_CONNECTIONS_REACHED_SELF;
}
String requestNonce = macToString(peerMac) + uint64ToString(randomUint64()) + uint64ToString(randomUint64());
String requestNonce = TypeCast::macToString(peerMac) + TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64())
+ TypeCast::uint64ToString(MeshUtilityFunctions::randomUint64());
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
_ongoingPeerRequestNonce = requestNonce;
_ongoingPeerRequester = this;
@ -1723,27 +1730,27 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
std::copy_n(peerMac, 6, _ongoingPeerRequestMac);
String requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
verboseModePrint("Sending encrypted connection request to: " + macToString(peerMac));
verboseModePrint(String(F("Sending encrypted connection request to: ")) + TypeCast::macToString(peerMac));
if(espnowSendToNode(requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE)
{
uint32_t startTime = millis();
// _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received
while(millis() - startTime < getEncryptionRequestTimeout() && _ongoingPeerRequestNonce != "")
while(millis() - startTime < getEncryptionRequestTimeout() && !_ongoingPeerRequestNonce.isEmpty())
{
// For obvious reasons dividing by exactly 10 is a good choice.
ExpiringTimeTracker maxDurationTracker = ExpiringTimeTracker(getEncryptionRequestTimeout()/10);
sendPeerRequestConfirmations(&maxDurationTracker); // Must be called before delay() to ensure _ongoingPeerRequestNonce != "" is still true, so reciprocal peer request order is preserved.
sendPeerRequestConfirmations(&maxDurationTracker); // Must be called before delay() to ensure !_ongoingPeerRequestNonce.isEmpty() is still true, so reciprocal peer request order is preserved.
delay(1);
}
}
if(_ongoingPeerRequestNonce != "")
if(!_ongoingPeerRequestNonce.isEmpty())
{
// If nonce != "" we only received the basic connection info, so the pairing process is incomplete
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
_ongoingPeerRequestNonce = "";
_ongoingPeerRequestNonce.clear();
}
else if(encryptedConnectionEstablished(_ongoingPeerRequestResult))
{
@ -1752,23 +1759,23 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
else if(_ongoingPeerRequestResult == ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED)
// We will only get a soft limit connection. Adjust future actions based on this.
requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(temporaryEncryptionRequestHeader, requestNonce, getEspnowHashKey(),
requestMessage = JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, getEspnowHashKey(),
espnowHashKeyLength, getAutoEncryptionDuration());
else
assert(false && "Unknown _ongoingPeerRequestResult during encrypted connection finalization!");
assert(false && String(F("Unknown _ongoingPeerRequestResult during encrypted connection finalization!")));
int32_t messageHeaderEndIndex = requestMessage.indexOf(':');
String messageHeader = requestMessage.substring(0, messageHeaderEndIndex + 1);
String messageBody = requestMessage.substring(messageHeaderEndIndex + 1);
// If we do not get an ack within getEncryptionRequestTimeout() the peer has probably had the time to delete the temporary encrypted connection.
if(espnowSendToNode(encryptedConnectionVerificationHeader + requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE
if(espnowSendToNode(String(FPSTR(encryptedConnectionVerificationHeader)) + requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE
&& millis() - _ongoingPeerRequestEncryptionStart < getEncryptionRequestTimeout())
{
EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac);
if(!encryptedConnection)
{
assert(encryptedConnection && "requestEncryptedConnectionKernel cannot find an encrypted connection!");
assert(encryptedConnection && String(F("requestEncryptedConnectionKernel cannot find an encrypted connection!")));
// requestEncryptedConnectionRemoval received.
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
}
@ -1780,11 +1787,11 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
else
{
// Finalize connection
if(messageHeader == encryptionRequestHeader)
if(messageHeader == FPSTR(encryptionRequestHeader))
{
temporaryEncryptedConnectionToPermanent(peerMac);
}
else if(messageHeader == temporaryEncryptionRequestHeader)
else if(messageHeader == FPSTR(temporaryEncryptionRequestHeader))
{
if(encryptedConnection->temporary())
{
@ -1797,7 +1804,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
}
else
{
assert(false && "Unknown messageHeader during encrypted connection finalization!");
assert(false && String(F("Unknown messageHeader during encrypted connection finalization!")));
_ongoingPeerRequestResult = ECS_API_CALL_FAILED;
}
}
@ -1839,19 +1846,19 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur
uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ?
minDurationMs : existingTimeTracker.remainingDuration();
return createEncryptionRequestHmacMessage(temporaryEncryptionRequestHeader, requestNonce, hashKey, espnowHashKeyLength, connectionDuration);
return createEncryptionRequestHmacMessage(FPSTR(temporaryEncryptionRequestHeader), requestNonce, hashKey, espnowHashKeyLength, connectionDuration);
}
encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac)
{
using namespace std::placeholders;
return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::encryptionRequestHeader, 0, getEspnowHashKey(), _1, _2));
return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::encryptionRequestHeader), 0, getEspnowHashKey(), _1, _2));
}
encrypted_connection_status_t EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs)
{
using namespace std::placeholders;
return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, EspnowProtocolInterpreter::temporaryEncryptionRequestHeader,
return requestEncryptedConnectionKernel(peerMac, std::bind(defaultEncryptionRequestBuilder, FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader),
durationMs, getEspnowHashKey(), _1, _2));
}
@ -1913,7 +1920,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect
{
uint8_t encryptedMac[6] {0};
connectionIterator->getEncryptedPeerMac(encryptedMac);
staticVerboseModePrint("Removing connection " + macToString(encryptedMac) + "... ", false);
staticVerboseModePrint(String(F("Removing connection ")) + TypeCast::macToString(encryptedMac) + String(F("... ")), false);
bool removalSucceeded = esp_now_del_peer(encryptedMac) == 0;
if(removalSucceeded)
@ -1926,7 +1933,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect
{
encryptedConnections.erase(connectionIterator);
}
staticVerboseModePrint("Removal succeeded");
staticVerboseModePrint(String(F("Removal succeeded")));
// Not deleting encrypted responses here would cause them to be sent unencrypted,
// exposing the peer session key which can be misused later if the encrypted connection is re-established.
@ -1942,7 +1949,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect
}
else
{
staticVerboseModePrint("Removal failed");
staticVerboseModePrint(String(F("Removal failed")));
return ECRO_REMOVAL_FAILED;
}
}
@ -1961,7 +1968,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::map<std::pair<macAndType_td, uin
for(typename std::map<std::pair<macAndType_td, uint64_t>, T>::iterator entryIterator = logEntries.begin();
entryIterator != logEntries.end(); )
{
if(macAndTypeToUint64Mac(entryIterator->first.first) == macToUint64(peerMac))
if(macAndTypeToUint64Mac(entryIterator->first.first) == TypeCast::macToUint64(peerMac))
{
macFound = true;
@ -1989,7 +1996,7 @@ void EspnowMeshBackend::deleteEntriesByMac(std::map<std::pair<uint64_t, uint64_t
for(typename std::map<std::pair<uint64_t, uint64_t>, T>::iterator entryIterator = logEntries.begin();
entryIterator != logEntries.end(); )
{
if(entryIterator->first.first == macToUint64(peerMac))
if(entryIterator->first.first == TypeCast::macToUint64(peerMac))
{
macFound = true;
@ -2018,13 +2025,13 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::requestEncryptedConnec
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call requestEncryptedConnectionRemoval from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call requestEncryptedConnectionRemoval from callbacks as this may corrupt program state! Aborting.")));
return ECRO_REMOVAL_REQUEST_FAILED;
}
if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac))
{
if(espnowSendToNode(encryptedConnectionRemovalRequestHeader, peerMac, 'P') == TS_TRANSMISSION_COMPLETE)
if(espnowSendToNode(FPSTR(encryptedConnectionRemovalRequestHeader), peerMac, 'P') == TS_TRANSMISSION_COMPLETE)
{
return removeEncryptedConnectionUnprotected(peerMac);
}
@ -2167,7 +2174,7 @@ transmission_status_t EspnowMeshBackend::initiateTransmission(const String &mess
if(verboseMode()) // Avoid string generation if not required
{
printAPInfo(recipientInfo);
verboseModePrint(F(""));
verboseModePrint(emptyString);
}
return initiateTransmissionKernel(message, targetBSSID);
@ -2197,12 +2204,12 @@ void EspnowMeshBackend::printTransmissionStatistics()
{
if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required
{
verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms.");
verboseModePrint("Maximum duration of successful transmissions: " + String(maxTransmissionDuration_AT) + " ms.");
verboseModePrint(String(F("Average duration of successful transmissions: ")) + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + String(F(" ms.")));
verboseModePrint(String(F("Maximum duration of successful transmissions: ")) + String(maxTransmissionDuration_AT) + String(F(" ms.")));
}
else
{
verboseModePrint("No successful transmission.");
verboseModePrint(String(F("No successful transmission.")));
}
}
@ -2211,7 +2218,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -2220,7 +2227,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
}
else
{
@ -2243,7 +2250,7 @@ transmission_status_t EspnowMeshBackend::attemptTransmission(const String &messa
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
return TS_CONNECTION_FAILED;
}
@ -2258,7 +2265,7 @@ encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnectio
if(verboseMode()) // Avoid string generation if not required
{
printAPInfo(recipientInfo);
verboseModePrint(F(""));
verboseModePrint(emptyString);
}
*existingEncryptedConnection = getEncryptedConnection(targetBSSID);
@ -2279,7 +2286,7 @@ transmission_status_t EspnowMeshBackend::initiateAutoEncryptingTransmission(cons
if(encryptedConnectionEstablished(connectionStatus))
{
uint8_t encryptedMac[6] {0};
assert(getEncryptedMac(targetBSSID, encryptedMac) && esp_now_is_peer_exist(encryptedMac) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!");
assert(getEncryptedMac(targetBSSID, encryptedMac) && esp_now_is_peer_exist(encryptedMac) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!")));
transmissionResult = initiateTransmissionKernel(message, targetBSSID);
}
@ -2300,7 +2307,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message,
MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!outerMutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -2311,7 +2318,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message,
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
assert(false && "ERROR! connectionQueue locked. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! connectionQueue locked. Don't call attemptAutoEncryptingTransmission from callbacks as this may corrupt program state! Aborting.")));
}
else
{
@ -2324,7 +2331,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message,
MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex);
if(!innerMutexTracker.mutexCaptured())
{
assert(false && "ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting.");
assert(false && String(F("ERROR! Unable to recapture Mutex in attemptAutoEncryptingTransmission. Aborting.")));
return;
}
@ -2351,7 +2358,7 @@ transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
return TS_CONNECTION_FAILED;
}
@ -2367,7 +2374,7 @@ void EspnowMeshBackend::broadcast(const String &message)
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Transmission in progress. Don't call broadcast from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Transmission in progress. Don't call broadcast from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -2401,7 +2408,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
using namespace EspnowProtocolInterpreter;
// True if confirmationsIterator contains a peer request received from the same node we are currently sending a peer request to.
bool reciprocalPeerRequest = initialOngoingPeerRequestNonce != "" && confirmationsIterator->connectedTo(_ongoingPeerRequestMac);
bool reciprocalPeerRequest = !initialOngoingPeerRequestNonce.isEmpty() && confirmationsIterator->connectedTo(_ongoingPeerRequestMac);
auto timeTrackerPointer = confirmationsIterator->temporary();
assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker
@ -2434,13 +2441,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
if(!existingEncryptedConnection &&
((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections)))
{
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader,
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength),
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator);
}
else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(basicConnectionInfoHeader,
else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader),
confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength),
sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
== TS_TRANSMISSION_COMPLETE)
@ -2466,13 +2473,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
}
else
{
warningPrint("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned.");
warningPrint(String(F("WARNING! Ignoring received encrypted connection request since no EspnowRequestManager is assigned.")));
}
if(!existingEncryptedConnection)
{
// Send "node full" message
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader,
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength),
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
}
@ -2483,19 +2490,19 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
delay(5); // Give some time for the peer to add an encrypted connection
assert(esp_now_is_peer_exist(defaultBSSID) > 0 && "ERROR! Attempting to send content marked as encrypted via unencrypted connection!");
assert(esp_now_is_peer_exist(defaultBSSID) > 0 && String(F("ERROR! Attempting to send content marked as encrypted via unencrypted connection!")));
String messageHeader = "";
String messageHeader;
if(existingEncryptedConnection->temporary() && // Should never change permanent connections
((reciprocalPeerRequest && encryptedConnections.size() > confirmationsIterator->getEncryptedConnectionsSoftLimit())
|| (!reciprocalPeerRequest && reservedEncryptedConnections() > confirmationsIterator->getEncryptedConnectionsSoftLimit())))
{
messageHeader = softLimitEncryptedConnectionInfoHeader;
messageHeader = FPSTR(softLimitEncryptedConnectionInfoHeader);
}
else
{
messageHeader = encryptedConnectionInfoHeader;
messageHeader = FPSTR(encryptedConnectionInfoHeader);
}
// Send password and keys.
@ -2534,7 +2541,7 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
if(!responsesToSendMutexTracker.mutexCaptured())
{
assert(false && "ERROR! responsesToSend locked. Don't call sendEspnowResponses from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! responsesToSend locked. Don't call sendEspnowResponses from callbacks as this may corrupt program state! Aborting.")));
}
uint32_t responseIndex = 0;
@ -2612,7 +2619,7 @@ uint8_t EspnowMeshBackend::numberOfEncryptedConnections()
uint8_t EspnowMeshBackend::reservedEncryptedConnections()
{
if(_ongoingPeerRequestNonce != "")
if(!_ongoingPeerRequestNonce.isEmpty())
if(!getEncryptedConnection(_ongoingPeerRequestMac))
return encryptedConnections.size() + 1; // Reserve one connection spot if we are currently making a peer request to a new node.
@ -2684,7 +2691,7 @@ String EspnowMeshBackend::serializeUnencryptedConnection()
// Returns: {"connectionState":{"unsyncMsgID":"123"}}
return jsonConnectionState + createJsonEndPair(jsonUnsynchronizedMessageID, String(_unsynchronizedMessageID));
return String(FPSTR(jsonConnectionState)) + createJsonEndPair(FPSTR(jsonUnsynchronizedMessageID), String(_unsynchronizedMessageID));
}
String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac)
@ -2697,7 +2704,7 @@ String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac)
if(encryptedConnection)
return encryptedConnection->serialize();
else
return "";
return emptyString;
}
String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex)
@ -2705,5 +2712,5 @@ String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex)
if(connectionIndex < numberOfEncryptedConnections())
return encryptedConnections[connectionIndex].serialize();
else
return "";
return emptyString;
}

View File

@ -356,6 +356,32 @@ public:
*/
bool isEspnowRequestManager();
/**
* Set the duration of most ESP-NOW log entries. Used for all ESP-NOW communication except for broadcasts and encrypted connection requests.
* Setting the duration too long may cause the node to run out of RAM, especially if there is intense transmission activity.
* Setting the duration too short may cause ESP-NOW transmissions to stop working, or make the node receive the same transmission multiple times.
*
* Set to 2500 ms by default.
*
* @param logEntryLifetimeMs The duration to use for most ESP-NOW log entries, in milliseconds.
*/
static void setLogEntryLifetimeMs(uint32_t logEntryLifetimeMs);
static uint32_t logEntryLifetimeMs();
/**
* Set the duration during which sent ESP-NOW broadcast are stored in the log and can receive responses.
* This is shorter by default than logEntryLifetimeMs() in order to preserve RAM since broadcasts are always kept in the log until they expire,
* whereas normal transmissions are only kept till they receive a response.
* Setting the duration too long may cause the node to run out of RAM, especially if there is intense broadcast activity.
* Setting the duration too short may cause ESP-NOW broadcasts to stop working, or make the node never receive responses to broadcasts.
*
* Set to 1000 ms by default.
*
* @param broadcastResponseTimeoutMs The duration sent ESP-NOW broadcasts will be stored in the log, in milliseconds.
*/
static void setBroadcastResponseTimeoutMs(uint32_t broadcastResponseTimeoutMs);
static uint32_t broadcastResponseTimeoutMs();
/**
* Change the key used by this EspnowMeshBackend instance for creating encrypted ESP-NOW connections.
* Will apply to any new received requests for encrypted connection if this EspnowMeshBackend instance is the current request manager.
@ -1052,9 +1078,7 @@ private:
static void deleteExpiredLogEntries(std::list<T> &logEntries, uint32_t maxEntryLifetimeMs);
static uint32_t _logEntryLifetimeMs;
static uint32_t logEntryLifetimeMs();
static uint32_t _broadcastResponseTimeoutMs;
static uint32_t broadcastResponseTimeoutMs();
static uint32_t _encryptionRequestTimeoutMs;

View File

@ -27,6 +27,8 @@
#include <algorithm>
#include "EspnowMeshBackend.h"
namespace TypeCast = MeshTypeConversionFunctions;
namespace EspnowProtocolInterpreter
{
uint8_t espnowMetadataSize()
@ -42,7 +44,7 @@ namespace EspnowProtocolInterpreter
{
uint8_t messageSize = transmissionLength - espnowMetadataSize();
messageContent = uint8ArrayToMultiString(transmissionDataArray + espnowMetadataSize(), messageSize);
messageContent = TypeCast::uint8ArrayToMultiString(transmissionDataArray + espnowMetadataSize(), messageSize);
}
return messageContent;
@ -65,7 +67,7 @@ namespace EspnowProtocolInterpreter
uint64_t espnowGetTransmissionMac(const uint8_t *transmissionDataArray)
{
return macToUint64(transmissionDataArray + espnowTransmissionMacIndex);
return TypeCast::macToUint64(transmissionDataArray + espnowTransmissionMacIndex);
}
uint8_t *espnowGetTransmissionMac(const uint8_t *transmissionDataArray, uint8_t *resultArray)
@ -76,12 +78,12 @@ namespace EspnowProtocolInterpreter
uint64_t espnowGetMessageID(const uint8_t *transmissionDataArray)
{
return uint8ArrayToUint64(transmissionDataArray + espnowMessageIDIndex);
return TypeCast::uint8ArrayToUint64(transmissionDataArray + espnowMessageIDIndex);
}
uint8_t *espnowSetMessageID(uint8_t *transmissionDataArray, uint64_t messageID)
{
return uint64ToUint8Array(messageID, transmissionDataArray + espnowMessageIDIndex);
return TypeCast::uint64ToUint8Array(messageID, transmissionDataArray + espnowMessageIDIndex);
}
bool usesEncryption(uint64_t messageID)

View File

@ -39,27 +39,27 @@
namespace EspnowProtocolInterpreter
{
const String synchronizationRequestHeader = "Synchronization request.";
const String encryptionRequestHeader = "AddEC:"; // Add encrypted connection
const String temporaryEncryptionRequestHeader = "AddTEC:"; // Add temporary encrypted connection
const String basicConnectionInfoHeader = "BasicCI:"; // Basic connection info
const String encryptedConnectionInfoHeader = "EncryptedCI:"; // Encrypted connection info
const String softLimitEncryptedConnectionInfoHeader = "SLEncryptedCI:"; // Soft limit encrypted connection info
const String maxConnectionsReachedHeader = "ECS_MAX_CONNECTIONS_REACHED_PEER:";
const String encryptedConnectionVerificationHeader = "ECVerified:"; // Encrypted connection verified
const String encryptedConnectionRemovalRequestHeader = "RemoveEC:"; // Remove encrypted connection
constexpr char synchronizationRequestHeader[] PROGMEM = "Synchronization request.";
constexpr char encryptionRequestHeader[] PROGMEM = "AddEC:"; // Add encrypted connection
constexpr char temporaryEncryptionRequestHeader[] PROGMEM = "AddTEC:"; // Add temporary encrypted connection
constexpr char basicConnectionInfoHeader[] PROGMEM = "BasicCI:"; // Basic connection info
constexpr char encryptedConnectionInfoHeader[] PROGMEM = "EncryptedCI:"; // Encrypted connection info
constexpr char softLimitEncryptedConnectionInfoHeader[] PROGMEM = "SLEncryptedCI:"; // Soft limit encrypted connection info
constexpr char maxConnectionsReachedHeader[] PROGMEM = "ECS_MAX_CONNECTIONS_REACHED_PEER:";
constexpr char encryptedConnectionVerificationHeader[] PROGMEM = "ECVerified:"; // Encrypted connection verified
constexpr char encryptedConnectionRemovalRequestHeader[] PROGMEM = "RemoveEC:"; // Remove encrypted connection
const uint8_t espnowMessageTypeIndex = 0;
const uint8_t espnowTransmissionsRemainingIndex = 1;
const uint8_t espnowTransmissionMacIndex = 2;
const uint8_t espnowMessageIDIndex = 8;
constexpr uint8_t espnowMessageTypeIndex = 0;
constexpr uint8_t espnowTransmissionsRemainingIndex = 1;
constexpr uint8_t espnowTransmissionMacIndex = 2;
constexpr uint8_t espnowMessageIDIndex = 8;
constexpr uint8_t espnowProtocolBytesSize = 16;
constexpr uint8_t aeadMetadataSize = 28;
uint8_t espnowMetadataSize();
const uint8_t espnowEncryptedConnectionKeyLength = 16; // This is restricted to exactly 16 bytes by the ESP-NOW API. It should not be changed unless the ESP-NOW API is changed.
const uint8_t espnowHashKeyLength = 16; // This can be changed to any value up to 255. Common values are 16 and 32.
constexpr uint8_t espnowEncryptedConnectionKeyLength = 16; // This is restricted to exactly 16 bytes by the ESP-NOW API. It should not be changed unless the ESP-NOW API is changed.
constexpr uint8_t espnowHashKeyLength = 16; // This can be changed to any value up to 255. Common values are 16 and 32.
constexpr uint64_t uint64LeftmostBits = 0xFFFFFFFF00000000;

View File

@ -26,6 +26,14 @@
#include "TypeConversionFunctions.h"
#include "JsonTranslator.h"
namespace TypeCast = MeshTypeConversionFunctions;
namespace
{
constexpr uint8_t MESSAGE_ID_LENGTH = 17; // 16 characters and one delimiter
constexpr uint8_t MESSAGE_COMPLETE = 255;
}
std::set<FloodingMesh *> FloodingMesh::availableFloodingMeshes = {};
char FloodingMesh::_metadataDelimiter = 23;
@ -103,6 +111,11 @@ void FloodingMesh::activateAP()
getEspnowMeshBackend().activateAP();
}
void FloodingMesh::deactivateAP()
{
MeshBackendBase::deactivateAP();
}
void FloodingMesh::performMeshMaintenance()
{
for(FloodingMesh *meshInstance : availableFloodingMeshes)
@ -122,7 +135,7 @@ void FloodingMesh::performMeshInstanceMaintenance()
{
_macIgnoreList = messageData.first.substring(0, 12) + ','; // The message should contain the messageID first
encryptedBroadcastKernel(messageData.first);
_macIgnoreList = "";
_macIgnoreList = emptyString;
}
else
{
@ -144,9 +157,9 @@ String FloodingMesh::serializeMeshState()
String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection();
return
"{\"meshState\":{"
+ connectionState.substring(1, connectionState.length() - 1) + ","
+ createJsonEndPair(jsonMeshMessageCount, String(_messageCount));
String(F("{\"meshState\":{"))
+ connectionState.substring(1, connectionState.length() - 1) + String(',')
+ createJsonEndPair(FPSTR(jsonMeshMessageCount), String(_messageCount));
}
void FloodingMesh::loadMeshState(const String &serializedMeshState)
@ -154,12 +167,12 @@ void FloodingMesh::loadMeshState(const String &serializedMeshState)
using namespace JsonTranslator;
if(!getMeshMessageCount(serializedMeshState, _messageCount))
getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain MeshMessageCount. Using default instead.");
getEspnowMeshBackend().warningPrint(String(F("WARNING! serializedMeshState did not contain MeshMessageCount. Using default instead.")));
String connectionState = "";
String connectionState;
if(!getConnectionState(serializedMeshState, connectionState) || !getEspnowMeshBackend().addUnencryptedConnection(connectionState))
{
getEspnowMeshBackend().warningPrint("WARNING! serializedMeshState did not contain unsynchronizedMessageID. Using default instead.");
getEspnowMeshBackend().warningPrint(String(F("WARNING! serializedMeshState did not contain unsynchronizedMessageID. Using default instead.")));
}
}
@ -168,7 +181,7 @@ String FloodingMesh::generateMessageID()
char messageCountArray[5] = { 0 };
snprintf(messageCountArray, 5, "%04X", _messageCount++);
uint8_t apMac[6] {0};
return macToString(WiFi.softAPmacAddress(apMac)) + String(messageCountArray); // We use the AP MAC address as ID since it is what shows up during WiFi scans
return TypeCast::macToString(WiFi.softAPmacAddress(apMac)) + String(messageCountArray); // We use the AP MAC address as ID since it is what shows up during WiFi scans
}
void FloodingMesh::broadcast(const String &message)
@ -228,7 +241,7 @@ void FloodingMesh::setOriginMac(uint8_t *macArray)
std::copy_n(macArray, 6, _originMac);
}
String FloodingMesh::getOriginMac() { return macToString(_originMac); }
String FloodingMesh::getOriginMac() { return TypeCast::macToString(_originMac); }
uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray)
{
std::copy_n(_originMac, 6, macArray);
@ -273,7 +286,7 @@ EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend()
bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID)
{
uint8_t apMacArray[6] = { 0 };
if(messageID >> 16 == macToUint64(WiFi.softAPmacAddress(apMacArray)))
if(messageID >> 16 == TypeCast::macToUint64(WiFi.softAPmacAddress(apMacArray)))
return false; // The node should not receive its own messages.
auto insertionResult = _messageIDs.emplace(messageID, 0); // Returns std::pair<iterator,bool>
@ -291,7 +304,7 @@ bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID)
bool FloodingMesh::insertCompletedMessageID(uint64_t messageID)
{
uint8_t apMacArray[6] = { 0 };
if(messageID >> 16 == macToUint64(WiFi.softAPmacAddress(apMacArray)))
if(messageID >> 16 == TypeCast::macToUint64(WiFi.softAPmacAddress(apMacArray)))
return false; // The node should not receive its own messages.
auto insertionResult = _messageIDs.emplace(messageID, MESSAGE_COMPLETE); // Returns std::pair<iterator,bool>
@ -368,7 +381,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
{
(void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
String broadcastTarget = "";
String broadcastTarget;
String remainingRequest = request;
if(request.charAt(0) == metadataDelimiter())
@ -376,7 +389,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
int32_t broadcastTargetEndIndex = request.indexOf(metadataDelimiter(), 1);
if(broadcastTargetEndIndex == -1)
return ""; // metadataDelimiter not found
return emptyString; // metadataDelimiter not found
broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter
remainingRequest.remove(0, broadcastTargetEndIndex + 1);
@ -385,14 +398,14 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter());
if(messageIDEndIndex == -1)
return ""; // metadataDelimiter not found
return emptyString; // metadataDelimiter not found
uint64_t messageID = stringToUint64(remainingRequest.substring(0, messageIDEndIndex));
uint64_t messageID = TypeCast::stringToUint64(remainingRequest.substring(0, messageIDEndIndex));
if(insertCompletedMessageID(messageID))
{
uint8_t originMacArray[6] = { 0 };
setOriginMac(uint64ToMac(messageID >> 16, originMacArray)); // messageID consists of MAC + 16 bit counter
setOriginMac(TypeCast::uint64ToMac(messageID >> 16, originMacArray)); // messageID consists of MAC + 16 bit counter
String message = remainingRequest;
message.remove(0, messageIDEndIndex + 1); // This approach avoids the null value removal of substring()
@ -405,7 +418,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
}
}
return "";
return emptyString;
}
/**
@ -419,7 +432,7 @@ transmission_status_t FloodingMesh::_defaultResponseHandler(const String &respon
{
transmission_status_t statusCode = TS_TRANSMISSION_COMPLETE;
getEspnowMeshBackend().warningPrint("WARNING! Response to FloodingMesh broadcast received, but none is expected!");
getEspnowMeshBackend().warningPrint(String(F("WARNING! Response to FloodingMesh broadcast received, but none is expected!")));
(void)response; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
(void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
@ -444,9 +457,9 @@ void FloodingMesh::_defaultNetworkFilter(int numberOfNetworks, MeshBackendBase &
// Connect to any APs which contain meshInstance.getMeshName()
if(meshNameIndex >= 0)
{
if(_macIgnoreList.indexOf(macToString(WiFi.BSSID(networkIndex))) == -1) // If the BSSID is not in the ignore list
if(_macIgnoreList.indexOf(TypeCast::macToString(WiFi.BSSID(networkIndex))) == -1) // If the BSSID is not in the ignore list
{
if(EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance))
if(EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance))
{
espnowInstance->connectionQueue().push_back(networkIndex);
}
@ -486,7 +499,7 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh
String targetMeshName = firstTransmission.substring(0, metadataEndIndex);
if(targetMeshName != "" && meshInstance.getMeshName() != targetMeshName)
if(!targetMeshName.isEmpty() && meshInstance.getMeshName() != targetMeshName)
{
return false; // Broadcast is for another mesh network
}
@ -497,7 +510,7 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh
if(messageIDEndIndex == -1)
return false; // metadataDelimiter not found
uint64_t messageID = stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex));
uint64_t messageID = TypeCast::stringToUint64(firstTransmission.substring(metadataEndIndex + 1, messageIDEndIndex));
if(insertPreliminaryMessageID(messageID))
{

View File

@ -121,7 +121,8 @@ public:
void begin();
/**
* Makes it possible to find the node through scans, and also makes it possible to recover from an encrypted ESP-NOW connection where only the other node is encrypted.
* Activate the WiFi access point of this ESP8266.
* This makes it possible to find the node through scans, and also makes it possible to recover from an encrypted ESP-NOW connection where only the other node is encrypted.
* Required for encryptedBroadcast() usage, but also slows down the start-up of the node.
*
* Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated.
@ -130,6 +131,15 @@ public:
*/
void activateAP();
/**
* Deactivate the WiFi access point of this ESP8266.
*
* Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated.
* Thus the AP is shared by all backends.
* All FloodingMesh instances can still broadcast messages though, even if their AP is not visible.
*/
static void deactivateAP();
/**
* Performs maintenance for all available Flooding Mesh instances
*/
@ -298,9 +308,6 @@ protected:
private:
static const uint8_t MESSAGE_ID_LENGTH = 17; // 16 characters and one delimiter
static const uint8_t MESSAGE_COMPLETE = 255;
EspnowMeshBackend _espnowBackend;
messageHandlerType _messageHandler;
@ -318,7 +325,7 @@ private:
std::queue<messageQueueElementType> _messageIdOrder = {};
std::list<std::pair<String, bool>> _forwardingBacklog = {};
String _macIgnoreList = "";
String _macIgnoreList;
String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance);
transmission_status_t _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance);

View File

@ -27,16 +27,18 @@
#include "TypeConversionFunctions.h"
#include "MeshCryptoInterface.h"
namespace TypeCast = MeshTypeConversionFunctions;
namespace JsonTranslator
{
String createJsonPair(const String &valueIdentifier, const String &value)
{
return valueIdentifier + "\"" + value + "\",";
return valueIdentifier + '\"' + value + F("\",");
}
String createJsonEndPair(const String &valueIdentifier, const String &value)
{
return valueIdentifier + "\"" + value + "\"}}";
return valueIdentifier + '\"' + value + F("\"}}");
}
String createEncryptedConnectionInfo(const String &infoHeader, const String &requestNonce, const String &authenticationPassword, uint64_t ownSessionKey, uint64_t peerSessionKey)
@ -44,33 +46,33 @@ namespace JsonTranslator
// Returns: Encrypted connection info:{"arguments":{"nonce":"1F2","password":"abc","ownSK":"3B4","peerSK":"1A2"}}
return
infoHeader + "{\"arguments\":{"
+ createJsonPair(jsonNonce, requestNonce)
+ createJsonPair(jsonPassword, authenticationPassword)
+ createJsonPair(jsonOwnSessionKey, uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver.
+ createJsonEndPair(jsonPeerSessionKey, uint64ToString(ownSessionKey));
infoHeader + String(F("{\"arguments\":{"))
+ createJsonPair(FPSTR(jsonNonce), requestNonce)
+ createJsonPair(FPSTR(jsonPassword), authenticationPassword)
+ createJsonPair(FPSTR(jsonOwnSessionKey), TypeCast::uint64ToString(peerSessionKey)) // Exchanges session keys since it should be valid for the receiver.
+ createJsonEndPair(FPSTR(jsonPeerSessionKey), TypeCast::uint64ToString(ownSessionKey));
}
String createEncryptionRequestIntro(const String &requestHeader, uint32_t duration)
{
return
requestHeader + "{\"arguments\":{"
+ (requestHeader == EspnowProtocolInterpreter::temporaryEncryptionRequestHeader ? createJsonPair(jsonDuration, String(duration)) : "");
requestHeader + String(F("{\"arguments\":{"))
+ (requestHeader == FPSTR(EspnowProtocolInterpreter::temporaryEncryptionRequestHeader) ? createJsonPair(FPSTR(jsonDuration), String(duration)) : emptyString);
}
String createEncryptionRequestEnding(const String &requestNonce)
{
return createJsonEndPair(jsonNonce, requestNonce);
return createJsonEndPair(FPSTR(jsonNonce), requestNonce);
}
String createEncryptionRequestHmacMessage(const String &requestHeader, const String &requestNonce, const uint8_t *hashKey, uint8_t hashKeyLength, uint32_t duration)
{
String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(jsonNonce, requestNonce);
String mainMessage = createEncryptionRequestIntro(requestHeader, duration) + createJsonPair(FPSTR(jsonNonce), requestNonce);
uint8_t staMac[6] {0};
uint8_t apMac[6] {0};
String requesterStaApMac = macToString(WiFi.macAddress(staMac)) + macToString(WiFi.softAPmacAddress(apMac));
String requesterStaApMac = TypeCast::macToString(WiFi.macAddress(staMac)) + TypeCast::macToString(WiFi.softAPmacAddress(apMac));
String hmac = MeshCryptoInterface::createMeshHmac(requesterStaApMac + mainMessage, hashKey, hashKeyLength);
return mainMessage + createJsonEndPair(jsonHmac, hmac);
return mainMessage + createJsonEndPair(FPSTR(jsonHmac), hmac);
}
bool verifyEncryptionRequestHmac(const String &encryptionRequestHmacMessage, const uint8_t *requesterStaMac, const uint8_t *requesterApMac,
@ -78,15 +80,15 @@ namespace JsonTranslator
{
using MeshCryptoInterface::verifyMeshHmac;
String hmac = "";
String hmac;
if(getHmac(encryptionRequestHmacMessage, hmac))
{
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(jsonHmac);
int32_t hmacStartIndex = encryptionRequestHmacMessage.indexOf(FPSTR(jsonHmac));
if(hmacStartIndex < 0)
return false;
if(hmac.length() == 2*CryptoInterface::SHA256_NATURAL_LENGTH // We know that each HMAC byte should become 2 String characters due to uint8ArrayToHexString.
&& verifyMeshHmac(macToString(requesterStaMac) + macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
&& verifyMeshHmac(TypeCast::macToString(requesterStaMac) + TypeCast::macToString(requesterApMac) + encryptionRequestHmacMessage.substring(0, hmacStartIndex), hmac, hashKey, hashKeyLength))
{
return true;
}
@ -118,11 +120,11 @@ namespace JsonTranslator
bool getConnectionState(const String &jsonString, String &result)
{
int32_t startIndex = jsonString.indexOf(jsonConnectionState);
int32_t startIndex = jsonString.indexOf(FPSTR(jsonConnectionState));
if(startIndex < 0)
return false;
int32_t endIndex = jsonString.indexOf("}");
int32_t endIndex = jsonString.indexOf('}');
if(endIndex < 0)
return false;
@ -132,7 +134,7 @@ namespace JsonTranslator
bool getPassword(const String &jsonString, String &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonPassword);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPassword));
if(startIndex < 0)
return false;
@ -146,7 +148,7 @@ namespace JsonTranslator
bool getOwnSessionKey(const String &jsonString, uint64_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonOwnSessionKey);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonOwnSessionKey));
if(startIndex < 0)
return false;
@ -154,13 +156,13 @@ namespace JsonTranslator
if(endIndex < 0)
return false;
result = stringToUint64(jsonString.substring(startIndex, endIndex));
result = TypeCast::stringToUint64(jsonString.substring(startIndex, endIndex));
return true;
}
bool getPeerSessionKey(const String &jsonString, uint64_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonPeerSessionKey);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerSessionKey));
if(startIndex < 0)
return false;
@ -168,13 +170,13 @@ namespace JsonTranslator
if(endIndex < 0)
return false;
result = stringToUint64(jsonString.substring(startIndex, endIndex));
result = TypeCast::stringToUint64(jsonString.substring(startIndex, endIndex));
return true;
}
bool getPeerStaMac(const String &jsonString, uint8_t *resultArray)
{
int32_t startIndex = getStartIndex(jsonString, jsonPeerStaMac);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerStaMac));
if(startIndex < 0)
return false;
@ -182,13 +184,13 @@ namespace JsonTranslator
if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long
return false;
stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
TypeCast::stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
return true;
}
bool getPeerApMac(const String &jsonString, uint8_t *resultArray)
{
int32_t startIndex = getStartIndex(jsonString, jsonPeerApMac);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonPeerApMac));
if(startIndex < 0)
return false;
@ -196,13 +198,13 @@ namespace JsonTranslator
if(endIndex < 0 || endIndex - startIndex != 12) // Mac String is always 12 characters long
return false;
stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
TypeCast::stringToMac(jsonString.substring(startIndex, endIndex), resultArray);
return true;
}
bool getDuration(const String &jsonString, uint32_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonDuration);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonDuration));
if(startIndex < 0)
return false;
@ -212,7 +214,7 @@ namespace JsonTranslator
bool getNonce(const String &jsonString, String &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonNonce);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonNonce));
if(startIndex < 0)
return false;
@ -226,7 +228,7 @@ namespace JsonTranslator
bool getHmac(const String &jsonString, String &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonHmac);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonHmac));
if(startIndex < 0)
return false;
@ -240,7 +242,7 @@ namespace JsonTranslator
bool getDesync(const String &jsonString, bool &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonDesync);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonDesync));
if(startIndex < 0)
return false;
@ -250,7 +252,7 @@ namespace JsonTranslator
bool getUnsynchronizedMessageID(const String &jsonString, uint32_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonUnsynchronizedMessageID);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonUnsynchronizedMessageID));
if(startIndex < 0)
return false;
@ -260,7 +262,7 @@ namespace JsonTranslator
bool getMeshMessageCount(const String &jsonString, uint16_t &result)
{
int32_t startIndex = getStartIndex(jsonString, jsonMeshMessageCount);
int32_t startIndex = getStartIndex(jsonString, FPSTR(jsonMeshMessageCount));
if(startIndex < 0)
return false;

View File

@ -29,18 +29,18 @@
namespace JsonTranslator
{
const String jsonConnectionState = "{\"connectionState\":{";
const String jsonPassword = "\"password\":";
const String jsonOwnSessionKey = "\"ownSK\":";
const String jsonPeerSessionKey = "\"peerSK\":";
const String jsonPeerStaMac = "\"peerStaMac\":";
const String jsonPeerApMac = "\"peerApMac\":";
const String jsonDuration = "\"duration\":";
const String jsonNonce = "\"nonce\":";
const String jsonHmac = "\"hmac\":";
const String jsonDesync = "\"desync\":";
const String jsonUnsynchronizedMessageID = "\"unsyncMsgID\":";
const String jsonMeshMessageCount = "\"meshMsgCount\":";
constexpr char jsonConnectionState[] PROGMEM = "{\"connectionState\":{";
constexpr char jsonPassword[] PROGMEM = "\"password\":";
constexpr char jsonOwnSessionKey[] PROGMEM = "\"ownSK\":";
constexpr char jsonPeerSessionKey[] PROGMEM = "\"peerSK\":";
constexpr char jsonPeerStaMac[] PROGMEM = "\"peerStaMac\":";
constexpr char jsonPeerApMac[] PROGMEM = "\"peerApMac\":";
constexpr char jsonDuration[] PROGMEM = "\"duration\":";
constexpr char jsonNonce[] PROGMEM = "\"nonce\":";
constexpr char jsonHmac[] PROGMEM = "\"hmac\":";
constexpr char jsonDesync[] PROGMEM = "\"desync\":";
constexpr char jsonUnsynchronizedMessageID[] PROGMEM = "\"unsyncMsgID\":";
constexpr char jsonMeshMessageCount[] PROGMEM = "\"meshMsgCount\":";
String createJsonPair(const String &valueIdentifier, const String &value);
String createJsonEndPair(const String &valueIdentifier, const String &value);

View File

@ -22,6 +22,8 @@
#include <assert.h>
namespace TypeCast = MeshTypeConversionFunctions;
MeshBackendBase *MeshBackendBase::apController = nullptr;
bool MeshBackendBase::_scanMutex = false;
@ -38,7 +40,7 @@ MeshBackendBase::MeshBackendBase(requestHandlerType requestHandler, responseHand
MeshBackendBase::~MeshBackendBase()
{
deactivateAP();
deactivateControlledAP();
}
void MeshBackendBase::setClassType(mesh_backend_t classType)
@ -51,8 +53,7 @@ mesh_backend_t MeshBackendBase::getClassType() {return _classType;}
void MeshBackendBase::activateAP()
{
// Deactivate active AP to avoid two servers using the same port, which can lead to crashes.
if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController())
currentAPController->deactivateAP();
deactivateAP();
activateAPHook();
@ -67,6 +68,12 @@ void MeshBackendBase::activateAPHook()
}
void MeshBackendBase::deactivateAP()
{
if(MeshBackendBase *currentAPController = MeshBackendBase::getAPController())
currentAPController->deactivateControlledAP();
}
bool MeshBackendBase::deactivateControlledAP()
{
if(isAPController())
{
@ -77,7 +84,11 @@ void MeshBackendBase::deactivateAP()
// Since there is no active AP controller now, make the apController variable point to nothing.
apController = nullptr;
return true;
}
return false;
}
void MeshBackendBase::deactivateAPHook()
@ -126,11 +137,11 @@ uint8 MeshBackendBase::getWiFiChannel() const
void MeshBackendBase::setSSID(const String &newSSIDPrefix, const String &newSSIDRoot, const String &newSSIDSuffix)
{
if(newSSIDPrefix != "")
if(!newSSIDPrefix.isEmpty())
_SSIDPrefix = newSSIDPrefix;
if(newSSIDRoot != "")
if(!newSSIDRoot.isEmpty())
_SSIDRoot = newSSIDRoot;
if(newSSIDSuffix != "")
if(!newSSIDSuffix.isEmpty())
_SSIDSuffix = newSSIDSuffix;
String newSSID = _SSIDPrefix + _SSIDRoot + _SSIDSuffix;
@ -158,14 +169,14 @@ String MeshBackendBase::getSSIDPrefix() const {return _SSIDPrefix;}
void MeshBackendBase::setSSIDRoot(const String &newSSIDRoot)
{
setSSID("", newSSIDRoot);
setSSID(emptyString, newSSIDRoot);
}
String MeshBackendBase::getSSIDRoot() const {return _SSIDRoot;}
void MeshBackendBase::setSSIDSuffix(const String &newSSIDSuffix)
{
setSSID("", "", newSSIDSuffix);
setSSID(emptyString, emptyString, newSSIDSuffix);
}
String MeshBackendBase::getSSIDSuffix() const {return _SSIDSuffix;}
@ -250,7 +261,7 @@ void MeshBackendBase::scanForNetworks(bool scanAllWiFiChannels)
MutexTracker mutexTracker(_scanMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! Scan already in progress. Don't call scanForNetworks from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! Scan already in progress. Don't call scanForNetworks from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -279,7 +290,7 @@ void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo)
String mainNetworkIdentifier = apNetworkInfo.SSID();
if(mainNetworkIdentifier == NetworkInfoBase::defaultSSID) // If SSID not provided, use BSSID instead
{
mainNetworkIdentifier = macToString(apNetworkInfo.BSSID());
mainNetworkIdentifier = TypeCast::macToString(apNetworkInfo.BSSID());
}
verboseModePrint(String(F("AP acquired: ")) + mainNetworkIdentifier + String(F(", Ch:")) + String(apNetworkInfo.wifiChannel()) + ' ', false);
@ -287,7 +298,7 @@ void MeshBackendBase::printAPInfo(const NetworkInfoBase &apNetworkInfo)
if(apNetworkInfo.RSSI() != NetworkInfoBase::defaultRSSI)
{
verboseModePrint(String('(') + String(apNetworkInfo.RSSI()) + String(F("dBm) ")) +
(apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : ""), false);
(apNetworkInfo.encryptionType() == ENC_TYPE_NONE ? String(F("open")) : emptyString), false);
}
verboseModePrint(F("... "), false);

View File

@ -23,8 +23,6 @@
#include "TransmissionOutcome.h"
#include "NetworkInfoBase.h"
const String ESP8266_MESH_EMPTY_STRING = "";
typedef enum
{
MB_TCP_IP = 0,
@ -52,11 +50,40 @@ public:
virtual void begin() = 0;
/**
* Each AP requires a separate server port. If two AP:s are using the same server port, they will not be able to have both server instances active at the same time.
* Activate the WiFi access point of this ESP8266.
*
* For TCP/IP, each AP requires a separate server port. If two AP:s are using the same server port, they will not be able to have both server instances active at the same time.
* This is managed automatically by the activateAP method.
*
* Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated.
* Thus the AP is shared by all backends.
*/
void activateAP();
void deactivateAP();
/**
* Deactivate the WiFi access point of this ESP8266, regardless of which MeshBackendBase instance is in control of the AP.
*
* Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated.
* Thus the AP is shared by all backends.
*/
static void deactivateAP();
/**
* Deactivate the WiFi access point of this ESP8266, provided that this MeshBackendBase instance is in control of the AP (which normally is the case for the MeshBackendBase instance that did the last activateAP() call).
*
* Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated.
* Thus the AP is shared by all backends.
*
* @return True if the AP was deactivated. False otherwise.
*/
bool deactivateControlledAP();
/**
* Restart the WiFi access point of this ESP8266.
*
* Note that only one AP can be active at a time in total (there is only one WiFi radio on the ESP8266), and this will always be the one which was last activated.
* Thus the AP is shared by all backends.
*/
void restartAP();
/**
@ -104,8 +131,8 @@ public:
* @param newSSIDRoot The middle part of the new SSID.
* @param newSSIDSuffix The last part of the new SSID.
*/
void setSSID(const String &newSSIDPrefix = ESP8266_MESH_EMPTY_STRING, const String &newSSIDRoot = ESP8266_MESH_EMPTY_STRING,
const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING);
void setSSID(const String &newSSIDPrefix = emptyString, const String &newSSIDRoot = emptyString,
const String &newSSIDSuffix = emptyString);
String getSSID() const;
/**
@ -300,7 +327,7 @@ private:
String _meshPassword;
uint8 _meshWiFiChannel;
bool _verboseMode;
String _message = ESP8266_MESH_EMPTY_STRING;
String _message;
bool _scanHidden = false;
bool _apHidden = false;

View File

@ -48,7 +48,7 @@ private:
uint8_t _transmissionsReceived = 0;
uint8_t _transmissionsExpected;
String _totalMessage = "";
String _totalMessage;
};

View File

@ -60,7 +60,7 @@ private:
public:
String SSID = "";
String SSID;
int wifiChannel = NETWORK_INFO_DEFAULT_INT;
uint8_t *BSSID = NULL;
int networkIndex = NETWORK_INFO_DEFAULT_INT;

View File

@ -25,7 +25,7 @@
#include "NetworkInfoBase.h"
uint8_t * const NetworkInfoBase::defaultBSSID = nullptr;
const String NetworkInfoBase::defaultSSID = "";
const String NetworkInfoBase::defaultSSID;
const int32_t NetworkInfoBase::defaultWifiChannel = NETWORK_INFO_DEFAULT_INT;
const uint8_t NetworkInfoBase::defaultEncryptionType = 0;
const int32_t NetworkInfoBase::defaultRSSI = ~0;

View File

@ -57,9 +57,9 @@ private:
uint64_t _requestID;
bool _requestEncrypted;
String _authenticationPassword = "";
String _authenticationPassword;
uint8_t _encryptedConnectionsSoftLimit;
String _peerRequestNonce = "";
String _peerRequestNonce;
};
#endif

View File

@ -52,7 +52,7 @@ private:
uint8_t _recipientMacArray[6] {0};
uint8_t *_recipientMac = nullptr;
String _message = "";
String _message;
uint64_t _requestID = 0;
};

View File

@ -25,22 +25,25 @@
#include "TypeConversionFunctions.h"
#include "MutexTracker.h"
#define SERVER_IP_ADDR "192.168.4.1"
namespace
{
constexpr char SERVER_IP_ADDR[] PROGMEM = "192.168.4.1";
}
const IPAddress TcpIpMeshBackend::emptyIP = IPAddress();
const IPAddress TcpIpMeshBackend::emptyIP;
bool TcpIpMeshBackend::_tcpIpTransmissionMutex = false;
bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false;
String TcpIpMeshBackend::lastSSID = "";
String TcpIpMeshBackend::lastSSID;
bool TcpIpMeshBackend::staticIPActivated = false;
String TcpIpMeshBackend::_temporaryMessage = "";
String TcpIpMeshBackend::_temporaryMessage;
// IP needs to be at the same subnet as server gateway (192.168.4 in this case). Station gateway ip must match ip for server.
IPAddress TcpIpMeshBackend::staticIP = emptyIP;
IPAddress TcpIpMeshBackend::gateway = IPAddress(192,168,4,1);
IPAddress TcpIpMeshBackend::subnetMask = IPAddress(255,255,255,0);
IPAddress TcpIpMeshBackend::staticIP;
IPAddress TcpIpMeshBackend::gateway(192,168,4,1);
IPAddress TcpIpMeshBackend::subnetMask(255,255,255,0);
std::vector<TcpIpNetworkInfo> TcpIpMeshBackend::_connectionQueue = {};
std::vector<TransmissionOutcome> TcpIpMeshBackend::_latestTransmissionOutcomes = {};
@ -50,7 +53,7 @@ TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHa
const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort)
: MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort)
{
setSSID(ssidPrefix, "", ssidSuffix);
setSSID(ssidPrefix, emptyString, ssidSuffix);
setMeshPassword(meshPassword);
setVerboseModeState(verboseMode);
setWiFiChannel(meshWiFiChannel);
@ -62,7 +65,7 @@ std::vector<TcpIpNetworkInfo> & TcpIpMeshBackend::connectionQueue()
MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
assert(false && "ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!");
assert(false && String(F("ERROR! connectionQueue locked. Don't call connectionQueue() from callbacks other than NetworkFilter as this may corrupt program state!")));
}
return _connectionQueue;
@ -112,13 +115,13 @@ bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;}
void TcpIpMeshBackend::setTemporaryMessage(const String &newTemporaryMessage) {_temporaryMessage = newTemporaryMessage;}
String TcpIpMeshBackend::getTemporaryMessage() {return _temporaryMessage;}
void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage = "";}
void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage.clear();}
String TcpIpMeshBackend::getCurrentMessage()
{
String message = getTemporaryMessage();
if(message == "") // If no temporary message stored
if(message.isEmpty()) // If no temporary message stored
message = getMessage();
return message;
@ -245,7 +248,7 @@ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_
*/
transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient)
{
verboseModePrint("Transmitting"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string.
verboseModePrint(String(F("Transmitting")));
currClient.print(getCurrentMessage() + '\r');
yield();
@ -301,7 +304,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel()
currClient.setTimeout(_stationModeTimeoutMs);
/* Connect to the node's server */
if (!currClient.connect(SERVER_IP_ADDR, getServerPort()))
if (!currClient.connect(FPSTR(SERVER_IP_ADDR), getServerPort()))
{
fullStop(currClient);
verboseModePrint(F("Server unavailable"));
@ -342,7 +345,7 @@ void TcpIpMeshBackend::initiateConnectionToAP(const String &targetSSID, int targ
*/
transmission_status_t TcpIpMeshBackend::connectToNode(const String &targetSSID, int targetChannel, uint8_t *targetBSSID)
{
if(staticIPActivated && lastSSID != "" && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact.
if(staticIPActivated && !lastSSID.isEmpty() && lastSSID != targetSSID) // So we only do this once per connection, in case there is a performance impact.
{
#if LWIP_VERSION_MAJOR >= 2
// Can be used with Arduino core for ESP8266 version 2.4.2 or higher with lwIP2 enabled to keep static IP on even during network switches.
@ -398,7 +401,7 @@ transmission_status_t TcpIpMeshBackend::initiateTransmission(const TcpIpNetworkI
WiFi.disconnect();
yield();
assert(recipientInfo.SSID() != ""); // We need at least SSID to connect
assert(!recipientInfo.SSID().isEmpty()); // We need at least SSID to connect
String targetSSID = recipientInfo.SSID();
int32_t targetWiFiChannel = recipientInfo.wifiChannel();
uint8_t targetBSSID[6] {0};
@ -433,7 +436,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -465,7 +468,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured())
{
assert(false && "ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! connectionQueue locked. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
}
else
{
@ -494,7 +497,7 @@ transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &messag
MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! TCP/IP transmission in progress. Don't call attemptTransmission from callbacks as this may corrupt program state! Aborting.")));
return TS_CONNECTION_FAILED;
}
@ -527,7 +530,7 @@ void TcpIpMeshBackend::acceptRequests()
MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured())
{
assert(false && "ERROR! TCP/IP transmission in progress. Don't call acceptRequests from callbacks as this may corrupt program state! Aborting.");
assert(false && String(F("ERROR! TCP/IP transmission in progress. Don't call acceptRequests from callbacks as this may corrupt program state! Aborting.")));
return;
}
@ -551,7 +554,7 @@ void TcpIpMeshBackend::acceptRequests()
/* Send the response back to the client */
if (_client.connected())
{
verboseModePrint("Responding"); // Not storing strings in flash (via F()) to avoid performance impacts when using the string.
verboseModePrint(String(F("Responding")));
_client.print(response + '\r');
_client.flush();
yield();

View File

@ -33,206 +33,208 @@ namespace
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35}; // Lower case letters
}
String uint64ToString(uint64_t number, byte base)
namespace MeshTypeConversionFunctions
{
assert(2 <= base && base <= 36);
String result;
if(base == 16)
String uint64ToString(uint64_t number, byte base)
{
do {
result += chars[ number % base ];
number >>= 4; // We could write number /= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from.
} while ( number );
}
else
{
do {
result += chars[ number % base ];
number /= base;
} while ( number );
}
assert(2 <= base && base <= 36);
std::reverse( result.begin(), result.end() );
String result;
return result;
}
uint64_t stringToUint64(const String &string, byte base)
{
assert(2 <= base && base <= 36);
uint64_t result = 0;
if(base == 16)
{
for(uint32_t i = 0; i < string.length(); ++i)
if(base == 16)
{
result <<= 4; // We could write result *= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from.
result += charValues[string.charAt(i) - '0'];
do {
result += chars[ number % base ];
number >>= 4; // We could write number /= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from.
} while ( number );
}
}
else
{
for(uint32_t i = 0; i < string.length(); ++i)
else
{
result *= base;
result += charValues[string.charAt(i) - '0'];
do {
result += chars[ number % base ];
number /= base;
} while ( number );
}
std::reverse( result.begin(), result.end() );
return result;
}
uint64_t stringToUint64(const String &string, byte base)
{
assert(2 <= base && base <= 36);
uint64_t result = 0;
if(base == 16)
{
for(uint32_t i = 0; i < string.length(); ++i)
{
result <<= 4; // We could write result *= 16; and the compiler would optimize it to a shift, but the explicit shift notation makes it clearer where the speed-up comes from.
result += charValues[string.charAt(i) - '0'];
}
}
else
{
for(uint32_t i = 0; i < string.length(); ++i)
{
result *= base;
result += charValues[string.charAt(i) - '0'];
}
}
return result;
}
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength)
{
String hexString;
if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF)
return emptyString;
for(uint32_t i = 0; i < arrayLength; ++i)
{
hexString += chars[ uint8Array[i] >> 4 ];
hexString += chars[ uint8Array[i] % 16 ];
}
return hexString;
}
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength)
{
assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters
for(uint32_t i = 0; i < arrayLength; ++i)
{
uint8Array[i] = (charValues[hexString.charAt(i*2) - '0'] << 4) + charValues[hexString.charAt(i*2 + 1) - '0'];
}
return uint8Array;
}
String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength)
{
String multiString;
if(!multiString.reserve(arrayLength))
return emptyString;
// Ensure we have a NULL terminated character array so the String() constructor knows where to stop.
char finalChar = uint8Array[arrayLength - 1];
uint8Array[arrayLength - 1] = 0;
multiString += (char *)(uint8Array);
while(multiString.length() < arrayLength - 1)
{
multiString += (char)0; // String construction only stops for null values, so we need to add those manually.
multiString += (char *)(uint8Array + multiString.length());
}
multiString += finalChar;
uint8Array[arrayLength - 1] = finalChar;
return multiString;
}
String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength)
{
String multiString;
if(!multiString.reserve(arrayLength))
return emptyString;
// Ensure we have a NULL terminated character array so the String() constructor knows where to stop.
uint8_t bufferedData[arrayLength + 1];
std::copy_n(uint8Array, arrayLength, bufferedData);
bufferedData[arrayLength] = 0;
multiString += (char *)(bufferedData);
while(multiString.length() < arrayLength)
{
multiString += (char)0; // String construction only stops for null values, so we need to add those manually.
multiString += (char *)(bufferedData + multiString.length());
}
return multiString;
}
String macToString(const uint8_t *mac)
{
return MeshTypeConversionFunctions::uint8ArrayToHexString(mac, 6);
}
uint8_t *stringToMac(const String &macString, uint8_t *macArray)
{
return MeshTypeConversionFunctions::hexStringToUint8Array(macString, macArray, 6);
}
uint64_t macToUint64(const uint8_t *macArray)
{
uint64_t result = (uint64_t)macArray[0] << 40 | (uint64_t)macArray[1] << 32 | (uint64_t)macArray[2] << 24 | (uint64_t)macArray[3] << 16 | (uint64_t)macArray[4] << 8 | (uint64_t)macArray[5];
return result;
}
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray)
{
assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes
macArray[5] = macValue;
macArray[4] = macValue >> 8;
macArray[3] = macValue >> 16;
macArray[2] = macValue >> 24;
macArray[1] = macValue >> 32;
macArray[0] = macValue >> 40;
return macArray;
}
uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray)
{
resultArray[7] = value;
resultArray[6] = value >> 8;
resultArray[5] = value >> 16;
resultArray[4] = value >> 24;
resultArray[3] = value >> 32;
resultArray[2] = value >> 40;
resultArray[1] = value >> 48;
resultArray[0] = value >> 56;
return resultArray;
}
uint64_t uint8ArrayToUint64(const uint8_t *inputArray)
{
uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32
| (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7];
return result;
}
/**
* Helper function for meshBackendCast.
*/
template <typename T>
T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType)
{
if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType)
{
return static_cast<T>(meshBackendBaseInstance);
}
else
{
return nullptr;
}
}
return result;
}
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength)
{
String hexString;
if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF)
return emptyString;
for(uint32_t i = 0; i < arrayLength; ++i)
template <>
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
{
hexString += chars[ uint8Array[i] >> 4 ];
hexString += chars[ uint8Array[i] % 16 ];
return attemptPointerCast<EspnowMeshBackend *>(meshBackendBaseInstance, MB_ESP_NOW);
}
return hexString;
}
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength)
{
assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters
for(uint32_t i = 0; i < arrayLength; ++i)
template <>
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
{
uint8Array[i] = (charValues[hexString.charAt(i*2) - '0'] << 4) + charValues[hexString.charAt(i*2 + 1) - '0'];
}
return uint8Array;
}
String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength)
{
String multiString;
if(!multiString.reserve(arrayLength))
return emptyString;
// Ensure we have a NULL terminated character array so the String() constructor knows where to stop.
char finalChar = uint8Array[arrayLength - 1];
uint8Array[arrayLength - 1] = 0;
multiString += (char *)(uint8Array);
while(multiString.length() < arrayLength - 1)
{
multiString += (char)0; // String construction only stops for null values, so we need to add those manually.
multiString += (char *)(uint8Array + multiString.length());
}
multiString += finalChar;
uint8Array[arrayLength - 1] = finalChar;
return multiString;
}
String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength)
{
String multiString;
if(!multiString.reserve(arrayLength))
return emptyString;
// Ensure we have a NULL terminated character array so the String() constructor knows where to stop.
uint8_t bufferedData[arrayLength + 1];
std::copy_n(uint8Array, arrayLength, bufferedData);
bufferedData[arrayLength] = 0;
multiString += (char *)(bufferedData);
while(multiString.length() < arrayLength)
{
multiString += (char)0; // String construction only stops for null values, so we need to add those manually.
multiString += (char *)(bufferedData + multiString.length());
}
return multiString;
}
String macToString(const uint8_t *mac)
{
return uint8ArrayToHexString(mac, 6);
}
uint8_t *stringToMac(const String &macString, uint8_t *macArray)
{
return hexStringToUint8Array(macString, macArray, 6);
}
uint64_t macToUint64(const uint8_t *macArray)
{
uint64_t result = (uint64_t)macArray[0] << 40 | (uint64_t)macArray[1] << 32 | (uint64_t)macArray[2] << 24 | (uint64_t)macArray[3] << 16 | (uint64_t)macArray[4] << 8 | (uint64_t)macArray[5];
return result;
}
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray)
{
assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes
macArray[5] = macValue;
macArray[4] = macValue >> 8;
macArray[3] = macValue >> 16;
macArray[2] = macValue >> 24;
macArray[1] = macValue >> 32;
macArray[0] = macValue >> 40;
return macArray;
}
uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray)
{
resultArray[7] = value;
resultArray[6] = value >> 8;
resultArray[5] = value >> 16;
resultArray[4] = value >> 24;
resultArray[3] = value >> 32;
resultArray[2] = value >> 40;
resultArray[1] = value >> 48;
resultArray[0] = value >> 56;
return resultArray;
}
uint64_t uint8ArrayToUint64(const uint8_t *inputArray)
{
uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32
| (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7];
return result;
}
/**
* Helper function for meshBackendCast.
*/
template <typename T>
T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType)
{
if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType)
{
return static_cast<T>(meshBackendBaseInstance);
}
else
{
return nullptr;
return attemptPointerCast<TcpIpMeshBackend *>(meshBackendBaseInstance, MB_TCP_IP);
}
}
template <>
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
{
return attemptPointerCast<EspnowMeshBackend *>(meshBackendBaseInstance, MB_ESP_NOW);
}
template <>
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
{
return attemptPointerCast<TcpIpMeshBackend *>(meshBackendBaseInstance, MB_TCP_IP);
}

View File

@ -32,144 +32,151 @@
#include "TcpIpMeshBackend.h"
#include "EspnowMeshBackend.h"
/**
* Note that using base 10 instead of 16 increases conversion time by roughly a factor of 5, due to unfavourable 64-bit arithmetic.
* Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
*
* @param number The number to convert to a string with radix "base".
* @param base The radix to convert "number" into. Must be between 2 and 36.
* @return A string of "number" encoded in radix "base".
*/
String uint64ToString(uint64_t number, byte base = 16);
/**
* Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic.
* Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
*
* @param string The string to convert to uint64_t. String must use radix "base".
* @param base The radix of "string". Must be between 2 and 36.
* @return A uint64_t of the string, using radix "base" during decoding.
*/
uint64_t stringToUint64(const String &string, byte base = 16);
/**
* Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
* All array elements will be padded with zeroes to ensure they are converted to 2 String characters each.
*
* @param uint8Array The array to make into a HEX String.
* @param arrayLength The size of uint8Array, in bytes.
* @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed.
*/
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength);
/**
* Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String.
* There must be 2 String characters for each array element. Use padding with zeroes where required.
*
* @param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters.
* @param uint8Array The array to fill with the contents of the hexString.
* @param arrayLength The number of bytes to fill in uint8Array.
* @return A pointer to the uint8Array.
*/
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength);
/**
* Stores the exact values of uint8Array in a String, even null values.
* Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring.
* In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead.
*
* The unbuffered version temporarily edits uint8Array during execution, but restores the array to its original state when returning in a controlled manner.
*
* @param uint8Array The array to make into a multiString.
* @param arrayLength The size of uint8Array, in bytes.
* @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed.
*/
String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength);
/**
* Stores the exact values of uint8Array in a String, even null values.
* Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring.
* In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead.
*
* The buffered version is slower and uses more memory than the unbuffered version, but can operate on const arrays.
*
* @param uint8Array The array to make into a multiString.
* @param arrayLength The size of uint8Array, in bytes.
* @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed.
*/
String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength);
/**
* Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string.
*
* @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total.
* @return A hexadecimal string representation of the mac.
*/
String macToString(const uint8_t *mac);
/**
* Takes a String and converts the first 12 characters to uint8_t numbers which are stored in the macArray from low to high index. Assumes hexadecimal number encoding.
*
* @param macString A String which begins with the mac address to store in the array as a hexadecimal number.
* @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes.
* @return The macArray.
*/
uint8_t *stringToMac(const String &macString, uint8_t *macArray);
/**
* Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB.
*
* @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total.
* @return A uint64_t representation of the mac.
*/
uint64_t macToUint64(const uint8_t *macArray);
/**
* Takes a uint64_t value and stores the bits of the first 6 bytes (LSB) in a uint8_t array. Assumes index 0 of the array should contain MSB.
*
* @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes.
* @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes.
* @return The macArray.
*/
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray);
/**
* Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB.
*
* @param value The uint64_t value to convert to a uint8_t array.
* @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes.
* @return The resultArray.
*/
uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray);
/**
* Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB.
*
* @param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes.
* @return A uint64_t representation of the first 8 bytes of the array.
*/
uint64_t uint8ArrayToUint64(const uint8_t *inputArray);
/**
* Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled.
*
* @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into.
* @param meshBackendBaseInstance The instance pointer to cast.
* @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise.
*/
template <typename T>
T meshBackendCast(MeshBackendBase *meshBackendBaseInstance)
namespace MeshTypeConversionFunctions
{
// The only valid template arguments are handled by the template specializations below, so ending up here is an error.
static_assert(std::is_same<T, EspnowMeshBackend *>::value || std::is_same<T, TcpIpMeshBackend *>::value,
"Error: Invalid MeshBackend class type. Make sure the template argument to meshBackendCast is supported!");
/**
* Note that using base 10 instead of 16 increases conversion time by roughly a factor of 5, due to unfavourable 64-bit arithmetic.
* Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
*
* @param number The number to convert to a string with radix "base".
* @param base The radix to convert "number" into. Must be between 2 and 36.
* @return A string of "number" encoded in radix "base".
*/
String uint64ToString(uint64_t number, byte base = 16);
/**
* Note that using base 10 instead of 16 increases conversion time by roughly a factor of 2, due to unfavourable 64-bit arithmetic.
* Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
*
* @param string The string to convert to uint64_t. String must use radix "base".
* @param base The radix of "string". Must be between 2 and 36.
* @return A uint64_t of the string, using radix "base" during decoding.
*/
uint64_t stringToUint64(const String &string, byte base = 16);
/**
* Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
* All array elements will be padded with zeroes to ensure they are converted to 2 String characters each.
*
* @param uint8Array The array to make into a HEX String.
* @param arrayLength The size of uint8Array, in bytes.
* @return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed.
*/
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength);
/**
* Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String.
* There must be 2 String characters for each array element. Use padding with zeroes where required.
*
* @param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters.
* @param uint8Array The array to fill with the contents of the hexString.
* @param arrayLength The number of bytes to fill in uint8Array.
* @return A pointer to the uint8Array.
*/
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength);
/**
* Stores the exact values of uint8Array in a String, even null values.
* Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring.
* In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead.
*
* The unbuffered version temporarily edits uint8Array during execution, but restores the array to its original state when returning in a controlled manner.
*
* @param uint8Array The array to make into a multiString.
* @param arrayLength The size of uint8Array, in bytes.
* @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed.
*/
String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength);
/**
* Stores the exact values of uint8Array in a String, even null values.
* Note that Strings containing null values will look like several separate Strings to methods that rely on null values to find the String end, such as String::substring.
* In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead.
*
* The buffered version is slower and uses more memory than the unbuffered version, but can operate on const arrays.
*
* @param uint8Array The array to make into a multiString.
* @param arrayLength The size of uint8Array, in bytes.
* @return Normally a String containing the same data as the uint8Array. An empty String if the memory allocation for the String failed.
*/
String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength);
/**
* Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string.
*
* @param mac A uint8_t array with the mac address to convert to a string. Should be 6 bytes in total.
* @return A hexadecimal string representation of the mac.
*/
String macToString(const uint8_t *mac);
/**
* Takes a String and converts the first 12 characters to uint8_t numbers which are stored in the macArray from low to high index. Assumes hexadecimal number encoding.
*
* @param macString A String which begins with the mac address to store in the array as a hexadecimal number.
* @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes.
* @return The macArray.
*/
uint8_t *stringToMac(const String &macString, uint8_t *macArray);
/**
* Takes a uint8_t array and converts the first 6 bytes to a uint64_t. Assumes index 0 of the array contains MSB.
*
* @param macArray A uint8_t array with the mac address to convert to a uint64_t. Should be 6 bytes in total.
* @return A uint64_t representation of the mac.
*/
uint64_t macToUint64(const uint8_t *macArray);
/**
* Takes a uint64_t value and stores the bits of the first 6 bytes (LSB) in a uint8_t array. Assumes index 0 of the array should contain MSB.
*
* @param macValue The uint64_t value to convert to a mac array. Value must fit within 6 bytes.
* @param macArray A uint8_t array that will hold the mac address once the function returns. Should have a size of at least 6 bytes.
* @return The macArray.
*/
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray);
/**
* Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB.
*
* @param value The uint64_t value to convert to a uint8_t array.
* @param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes.
* @return The resultArray.
*/
uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray);
/**
* Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB.
*
* @param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes.
* @return A uint64_t representation of the first 8 bytes of the array.
*/
uint64_t uint8ArrayToUint64(const uint8_t *inputArray);
/**
* Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled.
*
* @param T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into.
* @param meshBackendBaseInstance The instance pointer to cast.
* @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise.
*/
template <typename T>
T meshBackendCast(MeshBackendBase *meshBackendBaseInstance)
{
// The only valid template arguments are handled by the template specializations below, so ending up here is an error.
static_assert(std::is_same<T, EspnowMeshBackend *>::value || std::is_same<T, TcpIpMeshBackend *>::value,
"Error: Invalid MeshBackend class type. Make sure the template argument to meshBackendCast is supported!");
}
// These template specializations allow us to put the main template functionality in the .cpp file (which gives better encapsulation).
template <>
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
template <>
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
}
// These template specializations allow us to put the main template functionality in the .cpp file (which gives better encapsulation).
template <>
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
template <>
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
#ifndef ESP8266WIFIMESH_DISABLE_COMPATIBILITY
using namespace MeshTypeConversionFunctions; // Required to retain backwards compatibility. TODO: Remove in core release 3.0.0
#endif
#endif

View File

@ -26,20 +26,23 @@
#include "UtilityFunctions.h"
#include <esp8266_peri.h>
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo)
namespace MeshUtilityFunctions
{
for(int i = 0; i <= 5; i++)
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo)
{
if(macOne[i] != macTwo[i])
for(int i = 0; i <= 5; i++)
{
return false;
if(macOne[i] != macTwo[i])
{
return false;
}
}
return true;
}
return true;
}
uint64_t randomUint64()
{
return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32);
uint64_t randomUint64()
{
return (((uint64_t)RANDOM_REG32 << 32) | (uint64_t)RANDOM_REG32);
}
}

View File

@ -28,8 +28,11 @@
#include <inttypes.h>
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo);
namespace MeshUtilityFunctions
{
bool macEqual(const uint8_t *macOne, const uint8_t *macTwo);
uint64_t randomUint64();
uint64_t randomUint64();
}
#endif