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 <ESP8266WiFi.h>
#include <EspnowMeshBackend.h> #include <EspnowMeshBackend.h>
#include <TypeConversionFunctions.h> #include <TypeConversionFunctions.h>
#include <assert.h> #include <assert.h>
namespace TypeCast = MeshTypeConversionFunctions;
/** /**
NOTE: Although we could define the strings below as normal String variables, 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). 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://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html 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. 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.
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 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. // 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. // 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); bool broadcastFilter(String &firstTransmission, EspnowMeshBackend &meshInstance);
/* Create the mesh node object */ /* 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 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. // 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) // 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"; String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); 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. (void)tcpIpInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
Serial.print("TCP/IP: "); Serial.print("TCP/IP: ");
} else { } 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. // 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. // Note that request.substring will not work as expected if the String contains null values as data.
Serial.print("Request received: "); Serial.print("Request received: ");
if (request.charAt(0) == 0) {
Serial.println(request); // substring will not work for multiStrings.
} else {
Serial.println(request.substring(0, 100)); Serial.println(request.substring(0, 100));
}
/* return a string to send back */ /* return a string to send back */
return ("Hello world response #" + String(responseNumber++) + " from " + meshInstance.getMeshName() + meshInstance.getNodeID() + " with AP MAC " + WiFi.softAPmacAddress() + "."); 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; 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) // 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"; String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? ", Encrypted transmission" : ", Unencrypted transmission";
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); 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: "); Serial.print("TCP/IP: ");
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used. // 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() */ /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
if (meshNameIndex >= 0) { 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 (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) { if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
espnowInstance->connectionQueue().push_back(networkIndex); 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); tcpIpInstance->connectionQueue().push_back(networkIndex);
} else { } else {
Serial.println(String(F("Invalid mesh backend!"))); 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). 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."); Serial.println("\nPerforming encrypted ESP-NOW transmissions.");
uint8_t targetBSSID[6] {0}; 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. // 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) { 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. // 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!"); 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. 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 <ESP8266WiFi.h>
#include <TypeConversionFunctions.h> #include <TypeConversionFunctions.h>
#include <assert.h> #include <assert.h>
#include <FloodingMesh.h> #include <FloodingMesh.h>
namespace TypeCast = MeshTypeConversionFunctions;
/** /**
NOTE: Although we could define the strings below as normal String variables, 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). 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://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html 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. 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.
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 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. // 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. // 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); bool meshMessageHandler(String &message, FloodingMesh &meshInstance);
/* Create the mesh node object */ /* 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; bool theOne = true;
String theOneMac = ""; String theOneMac;
bool useLED = false; // Change this to true if you wish the onboard LED to mark The One. 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) { bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter()); int32_t delimiterIndex = message.indexOf(meshInstance.metadataDelimiter());
if (delimiterIndex == 0) { 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)); Serial.println(message.substring(2, 102));
String potentialMac = message.substring(2, 14); String potentialMac = message.substring(2, 14);
@ -95,17 +99,17 @@ bool meshMessageHandler(String &message, FloodingMesh &meshInstance) {
previousTotalBroadcasts = totalBroadcasts; previousTotalBroadcasts = totalBroadcasts;
if (totalReceivedBroadcasts % 50 == 0) { 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) { if (totalReceivedBroadcasts % 500 == 0) {
Serial.println("Benchmark message: " + message.substring(0, 100)); Serial.println(String(F("Benchmark message: ")) + message.substring(0, 100));
} }
} }
} }
} else { } 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. // 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. // 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)); Serial.println(message.substring(0, 100));
} }
@ -139,7 +143,7 @@ void setup() {
floodingMesh.activateAP(); floodingMesh.activateAP();
uint8_t apMacArray[6] {0}; uint8_t apMacArray[6] {0};
theOneMac = macToString(WiFi.softAPmacAddress(apMacArray)); theOneMac = TypeCast::macToString(WiFi.softAPmacAddress(apMacArray));
if (useLED) { if (useLED) {
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output 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 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. // 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. // 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); //floodingMesh.getEspnowMeshBackend().setUseEncryptedMessages(true);
floodingMeshDelay(5000); // Give some time for user to start the nodes 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. 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. // 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."); floodingMesh.broadcast(String(floodingMesh.metadataDelimiter()) + String(ledState) + theOneMac + F(" is The One."));
Serial.println("Proclamation broadcast done in " + String(millis() - startTime) + " ms."); Serial.println(String(F("Proclamation broadcast done in ")) + String(millis() - startTime) + F(" ms."));
timeOfLastProclamation = millis(); timeOfLastProclamation = millis();
floodingMeshDelay(20); floodingMeshDelay(20);
@ -189,8 +193,8 @@ void loop() {
if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made if (millis() - loopStart > 23000) { // Start benchmarking the mesh once three proclamations have been made
uint32_t startTime = millis(); uint32_t startTime = millis();
floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + ": Not a spoon in sight."); floodingMesh.broadcast(String(benchmarkCount++) + String(floodingMesh.metadataDelimiter()) + F(": Not a spoon in sight."));
Serial.println("Benchmark broadcast done in " + String(millis() - startTime) + " ms."); Serial.println(String(F("Benchmark broadcast done in ")) + String(millis() - startTime) + F(" ms."));
floodingMeshDelay(20); 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 <ESP8266WiFi.h>
#include <TcpIpMeshBackend.h> #include <TcpIpMeshBackend.h>
#include <TypeConversionFunctions.h> #include <TypeConversionFunctions.h>
#include <assert.h> #include <assert.h>
namespace TypeCast = MeshTypeConversionFunctions;
/** /**
NOTE: Although we could define the strings below as normal String variables, 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). 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://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/ */
const char exampleMeshName[] PROGMEM = "MeshNode_"; constexpr char exampleMeshName[] PROGMEM = "MeshNode_";
const char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO"; constexpr char exampleWiFiPassword[] PROGMEM = "ChangeThisWiFiPassword_TODO";
unsigned int requestNumber = 0; unsigned int requestNumber = 0;
unsigned int responseNumber = 0; unsigned int responseNumber = 0;
@ -25,7 +29,7 @@ transmission_status_t manageResponse(const String &response, MeshBackendBase &me
void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance); void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance);
/* Create the mesh node object */ /* 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 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. @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) { 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) // 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"; String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
} 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. (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 { } else {
Serial.print("UNKNOWN!: "); Serial.print(F("UNKNOWN!: "));
} }
/* Print out received message */ /* 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. // 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. // 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. // 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)); Serial.println(request.substring(0, 100));
/* return a string to send back */ /* 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; 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) // 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"; String transmissionEncrypted = espnowInstance->receivedEncryptedTransmission() ? F(", Encrypted transmission") : F(", Unencrypted transmission");
Serial.print("ESP-NOW (" + espnowInstance->getSenderMac() + transmissionEncrypted + "): "); Serial.print(String(F("ESP-NOW (")) + espnowInstance->getSenderMac() + transmissionEncrypted + F("): "));
} else if (TcpIpMeshBackend *tcpIpInstance = meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) { } else if (TcpIpMeshBackend *tcpIpInstance = TypeCast::meshBackendCast<TcpIpMeshBackend *>(&meshInstance)) {
Serial.print("TCP/IP: "); Serial.print(F("TCP/IP: "));
// Getting the sent message like this will work as long as ONLY(!) TCP/IP is used. // 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. // 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.print(F("Request sent: "));
Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100)); Serial.println(tcpIpInstance->getCurrentMessage().substring(0, 100));
} else { } else {
Serial.print("UNKNOWN!: "); Serial.print(F("UNKNOWN!: "));
} }
/* Print out received message */ /* Print out received message */
@ -113,15 +112,15 @@ void networkFilter(int numberOfNetworks, MeshBackendBase &meshInstance) {
/* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */ /* Connect to any _suitable_ APs which contain meshInstance.getMeshName() */
if (meshNameIndex >= 0) { 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 (targetNodeID < TypeCast::stringToUint64(meshInstance.getNodeID())) {
if (EspnowMeshBackend *espnowInstance = meshBackendCast<EspnowMeshBackend *>(&meshInstance)) { if (EspnowMeshBackend *espnowInstance = TypeCast::meshBackendCast<EspnowMeshBackend *>(&meshInstance)) {
espnowInstance->connectionQueue().push_back(networkIndex); 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); tcpIpInstance->connectionQueue().push_back(networkIndex);
} else { } 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) { bool exampleTransmissionOutcomesUpdateHook(MeshBackendBase &meshInstance) {
// The default hook only returns true and does nothing else. // 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) { if (tcpIpInstance->latestTransmissionOutcomes().back().transmissionStatus() == TS_TRANSMISSION_COMPLETE) {
// Our last request got a response, so time to create a new request. // 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.setMessage(String(F("Hello world request #")) + String(++requestNumber) + F(" from ")
+ meshInstance.getMeshName() + meshInstance.getNodeID() + String(F("."))); + meshInstance.getMeshName() + meshInstance.getNodeID() + String('.'));
} }
} else { } else {
Serial.println(String(F("Invalid mesh backend!"))); Serial.println(F("Invalid mesh backend!"));
} }
return true; 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. // 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. // 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); tcpIpNode.setTransmissionOutcomesUpdateHook(exampleTransmissionOutcomesUpdateHook);
} }
@ -217,7 +216,7 @@ void loop() {
} else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) { } else if (transmissionOutcome.transmissionStatus() == TS_TRANSMISSION_COMPLETE) {
// No need to do anything, transmission was successful. // No need to do anything, transmission was successful.
} else { } 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); assert(F("Invalid transmission status returned from responseHandler!") && false);
} }
} }

View File

@ -28,6 +28,8 @@
#include <bearssl/bearssl.h> #include <bearssl/bearssl.h>
namespace TypeCast = MeshTypeConversionFunctions;
namespace namespace
{ {
size_t _ctMinDataLength = 0; size_t _ctMinDataLength = 0;
@ -130,7 +132,7 @@ namespace
uint8_t hmac[hmacLength]; uint8_t hmac[hmacLength];
createBearsslHmac(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, 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) 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]; uint8_t hmac[hmacLength];
createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, 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) void setCtMinDataLength(const size_t ctMinDataLength)
{ {
assert(ctMaxDataLength() - ctMinDataLength <= ctMaxDiff); assert(ctMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF);
_ctMinDataLength = ctMinDataLength; _ctMinDataLength = ctMinDataLength;
} }
size_t ctMinDataLength() {return _ctMinDataLength;} size_t ctMinDataLength() {return _ctMinDataLength;}
void setCtMaxDataLength(const size_t ctMaxDataLength) void setCtMaxDataLength(const size_t ctMaxDataLength)
{ {
assert(ctMaxDataLength - ctMinDataLength() <= ctMaxDiff); assert(ctMaxDataLength - ctMinDataLength() <= CT_MAX_DIFF);
_ctMaxDataLength = ctMaxDataLength; _ctMaxDataLength = ctMaxDataLength;
} }
size_t ctMaxDataLength() {return _ctMaxDataLength;} size_t ctMaxDataLength() {return _ctMaxDataLength;}
@ -230,7 +232,7 @@ namespace CryptoInterface
{ {
uint8_t hash[MD5_NATURAL_LENGTH]; uint8_t hash[MD5_NATURAL_LENGTH];
md5Hash(message.c_str(), message.length(), hash); 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) 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]; uint8_t hash[SHA1_NATURAL_LENGTH];
sha1Hash(message.c_str(), message.length(), hash); 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) 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]; uint8_t hash[SHA224_NATURAL_LENGTH];
sha224Hash(message.c_str(), message.length(), hash); 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) 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]; uint8_t hash[SHA256_NATURAL_LENGTH];
sha256Hash(message.c_str(), message.length(), hash); 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) 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]; uint8_t hash[SHA384_NATURAL_LENGTH];
sha384Hash(message.c_str(), message.length(), hash); 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) 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]; uint8_t hash[SHA512_NATURAL_LENGTH];
sha512Hash(message.c_str(), message.length(), hash); 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) 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]; uint8_t hash[MD5SHA1_NATURAL_LENGTH];
md5sha1Hash(message.c_str(), message.length(), hash); 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 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. * This function allows for fine-tuning of the specifications for the constant time calculations.
@ -672,7 +672,7 @@ namespace CryptoInterface
// #################### HKDF #################### // #################### 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. * 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. * 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 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 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 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 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. * @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 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 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 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 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. * @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 "ESP8266WiFiMesh.h"
#include "TypeConversionFunctions.h" #include "TypeConversionFunctions.h"
namespace TypeCast = MeshTypeConversionFunctions;
#define SERVER_IP_ADDR "192.168.4.1" #define SERVER_IP_ADDR "192.168.4.1"
const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress(); const IPAddress ESP8266WiFiMesh::emptyIP = IPAddress();
@ -74,7 +76,7 @@ ESP8266WiFiMesh::ESP8266WiFiMesh(ESP8266WiFiMesh::requestHandlerType requestHand
const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) const String &nodeID, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort)
: _server(serverPort) : _server(serverPort)
{ {
updateNetworkNames(meshName, (nodeID != "" ? nodeID : uint64ToString(ESP.getChipId()))); updateNetworkNames(meshName, (nodeID != "" ? nodeID : TypeCast::uint64ToString(ESP.getChipId())));
_requestHandler = requestHandler; _requestHandler = requestHandler;
_responseHandler = responseHandler; _responseHandler = responseHandler;
setWiFiChannel(meshWiFiChannel); setWiFiChannel(meshWiFiChannel);

View File

@ -29,6 +29,7 @@
#include "MeshCryptoInterface.h" #include "MeshCryptoInterface.h"
using EspnowProtocolInterpreter::espnowHashKeyLength; 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]) 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) : _peerSessionKey(peerSessionKey), _ownSessionKey(ownSessionKey)
@ -97,7 +98,7 @@ void EncryptedConnectionData::setPeerApMac(const uint8_t *peerApMac)
bool EncryptedConnectionData::connectedTo(const uint8_t *peerMac) const 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; return true;
} }
@ -130,12 +131,12 @@ uint64_t EncryptedConnectionData::incrementSessionKey(uint64_t sessionKey, const
{ {
uint8_t inputArray[8] {0}; uint8_t inputArray[8] {0};
uint8_t hmacArray[CryptoInterface::SHA256_NATURAL_LENGTH] {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. /* 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 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 */ 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) if(newLeftmostBits == 0)
newLeftmostBits = ((uint64_t)RANDOM_REG32 | (1 << 31)) << 32; // We never want newLeftmostBits == 0 since that would indicate an unencrypted transmission. 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"}} // Returns: {"connectionState":{"duration":"123","password":"abc","ownSK":"1A2","peerSK":"3B4","peerStaMac":"F2","peerApMac":"E3"}}
return return
JsonTranslator::jsonConnectionState String(FPSTR(JsonTranslator::jsonConnectionState))
+ (temporary() ? JsonTranslator::jsonDuration + "\"" + String(temporary()->remainingDuration()) + "\"," : "") + (temporary() ? String(FPSTR(JsonTranslator::jsonDuration)) + '\"' + String(temporary()->remainingDuration()) + F("\",") : emptyString)
+ JsonTranslator::jsonDesync + "\"" + String(desync()) + "\"," + FPSTR(JsonTranslator::jsonDesync) + '\"' + String(desync()) + F("\",")
+ JsonTranslator::jsonOwnSessionKey + "\"" + uint64ToString(getOwnSessionKey()) + "\"," + FPSTR(JsonTranslator::jsonOwnSessionKey) + '\"' + TypeCast::uint64ToString(getOwnSessionKey()) + F("\",")
+ JsonTranslator::jsonPeerSessionKey + "\"" + uint64ToString(getPeerSessionKey()) + "\"," + FPSTR(JsonTranslator::jsonPeerSessionKey) + '\"' + TypeCast::uint64ToString(getPeerSessionKey()) + F("\",")
+ JsonTranslator::jsonPeerStaMac + "\"" + macToString(_peerStaMac) + "\"," + FPSTR(JsonTranslator::jsonPeerStaMac) + '\"' + TypeCast::macToString(_peerStaMac) + F("\",")
+ JsonTranslator::jsonPeerApMac + "\"" + macToString(_peerApMac) + "\"}}"; + FPSTR(JsonTranslator::jsonPeerApMac) + '\"' + TypeCast::macToString(_peerApMac) + F("\"}}");
} }
const ExpiringTimeTracker *EncryptedConnectionData::temporary() const const ExpiringTimeTracker *EncryptedConnectionData::temporary() const

View File

@ -31,6 +31,8 @@ extern "C" {
using EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength; using EspnowProtocolInterpreter::espnowEncryptedConnectionKeyLength;
using EspnowProtocolInterpreter::espnowHashKeyLength; 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 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; static const uint64_t uint64MSB = 0x8000000000000000;
@ -62,7 +64,7 @@ uint32_t EspnowMeshBackend::_encryptionRequestTimeoutMs = 300;
bool EspnowMeshBackend::_espnowSendConfirmed = false; bool EspnowMeshBackend::_espnowSendConfirmed = false;
String EspnowMeshBackend::_ongoingPeerRequestNonce = ""; String EspnowMeshBackend::_ongoingPeerRequestNonce;
uint8_t EspnowMeshBackend::_ongoingPeerRequestMac[6] = {0}; uint8_t EspnowMeshBackend::_ongoingPeerRequestMac[6] = {0};
EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr; EspnowMeshBackend *EspnowMeshBackend::_ongoingPeerRequester = nullptr;
encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF; encrypted_connection_status_t EspnowMeshBackend::_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_SELF;
@ -116,7 +118,7 @@ EspnowMeshBackend::EspnowMeshBackend(requestHandlerType requestHandler, response
encryptedConnections.reserve(maxEncryptedConnections); encryptedConnections.reserve(maxEncryptedConnections);
setBroadcastFilter(broadcastFilter); setBroadcastFilter(broadcastFilter);
setSSID(ssidPrefix, "", ssidSuffix); setSSID(ssidPrefix, emptyString, ssidSuffix);
setMeshPassword(meshPassword); setMeshPassword(meshPassword);
setVerboseModeState(verboseMode); setVerboseModeState(verboseMode);
setWiFiChannel(meshWiFiChannel); setWiFiChannel(meshWiFiChannel);
@ -165,7 +167,7 @@ bool EspnowMeshBackend::activateEspnow()
if (esp_now_init()==0) if (esp_now_init()==0)
{ {
if(_espnowEncryptionKokSet && esp_now_set_kok(_espnowEncryptionKok, espnowEncryptedConnectionKeyLength)) // esp_now_set_kok returns 0 on success. 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) if(getEspnowRequestManager() == nullptr)
{ {
@ -176,23 +178,23 @@ bool EspnowMeshBackend::activateEspnow()
esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) { esp_now_register_send_cb([](uint8_t* mac, uint8_t sendStatus) {
if(_espnowSendConfirmed) if(_espnowSendConfirmed)
return; 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. _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. // 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. // 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. 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(String(F("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("My ESP-NOW STA MAC: ")) + WiFi.macAddress() + '\n'); // Get the station MAC address. The softAP MAC is different.
return true; return true;
} }
else else
{ {
warningPrint("ESP-NOW init failed!"); warningPrint(String(F("ESP-NOW init failed!")));
return false; return false;
} }
} }
@ -224,7 +226,7 @@ std::vector<EspnowNetworkInfo> & EspnowMeshBackend::connectionQueue()
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured()) 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; return _connectionQueue;
@ -253,7 +255,7 @@ void EspnowMeshBackend::performEspnowMaintenance(uint32_t estimatedMaxDuration)
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return;
} }
@ -429,6 +431,7 @@ void EspnowMeshBackend::espnowReceiveCallbackWrapper(uint8_t *macaddr, uint8_t *
if(useEncryptedMessages()) if(useEncryptedMessages())
{ {
// chacha20Poly1305Decrypt decrypts dataArray in place. // chacha20Poly1305Decrypt decrypts dataArray in place.
// We are using the protocol bytes as a key salt.
if(!CryptoInterface::chacha20Poly1305Decrypt(dataArray + espnowMetadataSize(), len - espnowMetadataSize(), getEspnowMessageEncryptionKey(), dataArray, if(!CryptoInterface::chacha20Poly1305Decrypt(dataArray + espnowMetadataSize(), len - espnowMetadataSize(), getEspnowMessageEncryptionKey(), dataArray,
espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize, dataArray + espnowProtocolBytesSize + 12)) 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); bool transmissionEncrypted = usesEncryption(receivedMessageID);
// Useful when debugging the protocol // 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"); //Serial.println(transmissionEncrypted ? " Encrypted" : " Unencrypted");
if(messageType == 'Q' || messageType == 'B') // Question (request) or Broadcast 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 }; 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 else if(messageType == 'S') // Synchronization request
@ -555,7 +558,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
int32_t messageHeaderEndIndex = message.indexOf(':'); int32_t messageHeaderEndIndex = message.indexOf(':');
String messageHeader = message.substring(0, messageHeaderEndIndex + 1); String messageHeader = message.substring(0, messageHeaderEndIndex + 1);
if(messageHeader == encryptedConnectionVerificationHeader) if(messageHeader == FPSTR(encryptedConnectionVerificationHeader))
{ {
if(encryptedCorrectly) if(encryptedCorrectly)
{ {
@ -563,13 +566,13 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1); String connectionRequestType = message.substring(messageHeaderEndIndex + 1, connectionRequestTypeEndIndex + 1);
connectionLogIterator encryptedConnection = connectionLogEndIterator(); connectionLogIterator encryptedConnection = connectionLogEndIterator();
if(!getEncryptedConnectionIterator(macaddr, encryptedConnection)) 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); temporaryEncryptedConnectionToPermanent(macaddr);
} }
else if(connectionRequestType == temporaryEncryptionRequestHeader) else if(connectionRequestType == FPSTR(temporaryEncryptionRequestHeader))
{ {
if(encryptedConnection->temporary()) // Should not change duration for existing permanent connections. 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 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 there is a espnowRequestManager, get it
if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager()) if(EspnowMeshBackend *currentEspnowRequestManager = getEspnowRequestManager())
{ {
String requestNonce = ""; String requestNonce;
if(JsonTranslator::getNonce(message, requestNonce) && requestNonce.length() >= 12) // The destination MAC address requires 12 characters. if(JsonTranslator::getNonce(message, requestNonce) && requestNonce.length() >= 12) // The destination MAC address requires 12 characters.
{ {
uint8_t destinationMac[6] = {0}; uint8_t destinationMac[6] = {0};
stringToMac(requestNonce, destinationMac); TypeCast::stringToMac(requestNonce, destinationMac);
uint8_t apMac[6] {0}; uint8_t apMac[6] {0};
WiFi.softAPmacAddress(apMac); WiFi.softAPmacAddress(apMac);
bool correctDestination = false; bool correctDestination = false;
if(macEqual(destinationMac, apMac)) if(MeshUtilityFunctions::macEqual(destinationMac, apMac))
{ {
correctDestination = true; correctDestination = true;
} }
@ -611,7 +614,7 @@ void EspnowMeshBackend::handlePeerRequest(uint8_t *macaddr, uint8_t *dataArray,
uint8_t staMac[6] {0}; uint8_t staMac[6] {0};
WiFi.macAddress(staMac); WiFi.macAddress(staMac);
if(macEqual(destinationMac, staMac)) if(MeshUtilityFunctions::macEqual(destinationMac, staMac))
{ {
correctDestination = true; 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) if(encryptedCorrectly)
removeEncryptedConnection(macaddr); removeEncryptedConnection(macaddr);
} }
else 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; using namespace EspnowProtocolInterpreter;
if(_ongoingPeerRequestNonce != "") if(!_ongoingPeerRequestNonce.isEmpty())
{ {
String message = espnowGetMessageContent(dataArray, len); String message = espnowGetMessageContent(dataArray, len);
String requestNonce = ""; String requestNonce;
if(JsonTranslator::getNonce(message, requestNonce) && requestNonce == _ongoingPeerRequestNonce) if(JsonTranslator::getNonce(message, requestNonce) && requestNonce == _ongoingPeerRequestNonce)
{ {
@ -659,7 +662,7 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
uint8_t apMacArray[6] = { 0 }; uint8_t apMacArray[6] = { 0 };
espnowGetTransmissionMac(dataArray, apMacArray); espnowGetTransmissionMac(dataArray, apMacArray);
if(messageHeader == basicConnectionInfoHeader) if(messageHeader == FPSTR(basicConnectionInfoHeader))
{ {
// encryptedConnectionEstablished(_ongoingPeerRequestResult) means we have already received a basicConnectionInfoHeader // encryptedConnectionEstablished(_ongoingPeerRequestResult) means we have already received a basicConnectionInfoHeader
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) && if(!encryptedConnectionEstablished(_ongoingPeerRequestResult) &&
@ -691,13 +694,13 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
if(!encryptedConnectionEstablished(_ongoingPeerRequestResult)) if(!encryptedConnectionEstablished(_ongoingPeerRequestResult))
{ {
// Adding connection failed, abort ongoing peer request. // 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()) if(JsonTranslator::getPassword(messageBody, messagePassword) && messagePassword == _ongoingPeerRequester->getMeshPassword())
{ {
@ -711,33 +714,33 @@ void EspnowMeshBackend::handlePeerRequestConfirmation(uint8_t *macaddr, uint8_t
encryptedConnection->setPeerSessionKey(peerSessionKey); encryptedConnection->setPeerSessionKey(peerSessionKey);
encryptedConnection->setOwnSessionKey(ownSessionKey); encryptedConnection->setOwnSessionKey(ownSessionKey);
if(messageHeader == encryptedConnectionInfoHeader) if(messageHeader == FPSTR(encryptedConnectionInfoHeader))
_ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED; _ongoingPeerRequestResult = ECS_CONNECTION_ESTABLISHED;
else if(messageHeader == softLimitEncryptedConnectionInfoHeader) else if(messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader))
_ongoingPeerRequestResult = ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED; _ongoingPeerRequestResult = ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED;
else else
assert(false && "Unknown _ongoingPeerRequestResult!"); assert(false && String(F("Unknown _ongoingPeerRequestResult!")));
} }
else else
{ {
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; _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)) if(JsonTranslator::verifyEncryptionRequestHmac(message, macaddr, apMacArray, _ongoingPeerRequester->getEspnowHashKey(), espnowHashKeyLength))
{ {
_ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER; _ongoingPeerRequestResult = ECS_MAX_CONNECTIONS_REACHED_PEER;
_ongoingPeerRequestNonce = ""; _ongoingPeerRequestNonce.clear();
} }
} }
else else
{ {
assert(messageHeader == basicConnectionInfoHeader || messageHeader == encryptedConnectionInfoHeader || assert(messageHeader == FPSTR(basicConnectionInfoHeader) || messageHeader == FPSTR(encryptedConnectionInfoHeader) ||
messageHeader == softLimitEncryptedConnectionInfoHeader || messageHeader == maxConnectionsReachedHeader); messageHeader == FPSTR(softLimitEncryptedConnectionInfoHeader) || messageHeader == FPSTR(maxConnectionsReachedHeader));
} }
} }
} }
@ -770,7 +773,7 @@ void EspnowMeshBackend::espnowReceiveCallback(uint8_t *macaddr, uint8_t *dataArr
char messageType = espnowGetMessageType(dataArray); char messageType = espnowGetMessageType(dataArray);
uint8_t transmissionsRemaining = espnowGetTransmissionsRemaining(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 // 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. // 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; 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) void EspnowMeshBackend::setCriticalHeapLevelBuffer(uint32_t bufferInBytes)
{ {
@ -1016,7 +1021,7 @@ uint64_t EspnowMeshBackend::generateMessageID(EncryptedConnectionLog *encryptedC
uint64_t EspnowMeshBackend::createSessionKey() 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); 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}; uint8_t encryptedMac[6] {0};
encryptedConnection->getEncryptedPeerMac(encryptedMac); 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()) if(encryptedConnection->desync())
{ {
espnowSendToNodeUnsynchronized(synchronizationRequestHeader, encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance); espnowSendToNodeUnsynchronized(FPSTR(synchronizationRequestHeader), encryptedMac, 'S', generateMessageID(encryptedConnection), espnowInstance);
if(encryptedConnection->desync()) if(encryptedConnection->desync())
{ {
@ -1090,7 +1095,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
MutexTracker mutexTracker(_espnowSendToNodeMutex); MutexTracker mutexTracker(_espnowSendToNodeMutex);
if(!mutexTracker.mutexCaptured()) 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; 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. // 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, // 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. // 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 ////// ////// Create transmission array //////
@ -1232,9 +1237,9 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
{ {
_transmissionsFailed++; _transmissionsFailed++;
staticVerboseModePrint("espnowSendToNode failed!"); staticVerboseModePrint(String(F("espnowSendToNode failed!")));
staticVerboseModePrint("Transmission #: " + String(transmissionsRequired - transmissionsRemaining) + "/" + String(transmissionsRequired)); staticVerboseModePrint(String(F("Transmission #: ")) + String(transmissionsRequired - transmissionsRemaining) + String('/') + String(transmissionsRequired));
staticVerboseModePrint("Transmission fail rate (up) " + String(getTransmissionFailRate())); staticVerboseModePrint(String(F("Transmission fail rate (up) ")) + String(getTransmissionFailRate()));
if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID) if(messageStart && encryptedConnection && !usesConstantSessionKey(messageType) && encryptedConnection->getOwnSessionKey() == messageID)
encryptedConnection->setDesync(true); encryptedConnection->setDesync(true);
@ -1247,7 +1252,7 @@ transmission_status_t EspnowMeshBackend::espnowSendToNodeUnsynchronized(const St
} while(transmissionsRemaining >= 0); } while(transmissionsRemaining >= 0);
// Useful when debugging the protocol // 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; return TS_TRANSMISSION_COMPLETE;
} }
@ -1267,7 +1272,7 @@ transmission_status_t EspnowMeshBackend::sendResponse(const String &message, uin
if(encryptedConnection) if(encryptedConnection)
{ {
encryptedConnection->getEncryptedPeerMac(encryptedMac); 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); return espnowSendToNodeUnsynchronized(message, encryptedConnection ? encryptedMac : targetBSSID, 'A', requestID, this);
@ -1387,7 +1392,7 @@ void EspnowMeshBackend::setUseEncryptedMessages(bool useEncryptedMessages)
MutexTracker mutexTracker(_espnowSendToNodeMutex); MutexTracker mutexTracker(_espnowSendToNodeMutex);
if(!mutexTracker.mutexCaptured()) 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; _useEncryptedMessages = useEncryptedMessages;
@ -1398,7 +1403,7 @@ bool EspnowMeshBackend::verifyPeerSessionKey(uint64_t sessionKey, const uint8_t
{ {
if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac))
{ {
return verifyPeerSessionKey(sessionKey, *encryptedConnection, macToUint64(peerMac), messageType); return verifyPeerSessionKey(sessionKey, *encryptedConnection, TypeCast::macToUint64(peerMac), messageType);
} }
return false; return false;
@ -1479,7 +1484,7 @@ void EspnowMeshBackend::clearAllScheduledResponses()
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
if(!responsesToSendMutexTracker.mutexCaptured()) 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(); responsesToSend.clear();
@ -1490,12 +1495,13 @@ void EspnowMeshBackend::deleteScheduledResponsesByRecipient(const uint8_t *recip
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
if(!responsesToSendMutexTracker.mutexCaptured()) 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(); ) 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); responseIterator = responsesToSend.erase(responseIterator);
} }
@ -1509,7 +1515,7 @@ void EspnowMeshBackend::setSenderMac(uint8_t *macArray)
std::copy_n(macArray, 6, _senderMac); 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) uint8_t *EspnowMeshBackend::getSenderMac(uint8_t *macArray)
{ {
std::copy_n(_senderMac, 6, macArray); std::copy_n(_senderMac, 6, macArray);
@ -1521,7 +1527,7 @@ void EspnowMeshBackend::setSenderAPMac(uint8_t *macArray)
std::copy_n(macArray, 6, _senderAPMac); 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) uint8_t *EspnowMeshBackend::getSenderAPMac(uint8_t *macArray)
{ {
std::copy_n(_senderAPMac, 6, macArray); std::copy_n(_senderAPMac, 6, macArray);
@ -1639,7 +1645,7 @@ encrypted_connection_status_t EspnowMeshBackend::addTemporaryEncryptedConnection
if(result == ECS_CONNECTION_ESTABLISHED) if(result == ECS_CONNECTION_ESTABLISHED)
{ {
if(!getEncryptedConnectionIterator(peerStaMac, encryptedConnection)) 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); encryptedConnection->setRemainingDuration(duration);
} }
@ -1680,7 +1686,7 @@ void EspnowMeshBackend::handlePostponedRemovals()
MutexTracker mutexTracker(_espnowTransmissionMutex); MutexTracker mutexTracker(_espnowTransmissionMutex);
if(!mutexTracker.mutexCaptured()) 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; return;
} }
@ -1699,7 +1705,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return ECS_REQUEST_TRANSMISSION_FAILED;
} }
@ -1715,7 +1721,8 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
return ECS_MAX_CONNECTIONS_REACHED_SELF; 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; _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
_ongoingPeerRequestNonce = requestNonce; _ongoingPeerRequestNonce = requestNonce;
_ongoingPeerRequester = this; _ongoingPeerRequester = this;
@ -1723,27 +1730,27 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
std::copy_n(peerMac, 6, _ongoingPeerRequestMac); std::copy_n(peerMac, 6, _ongoingPeerRequestMac);
String requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); 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) if(espnowSendToNode(requestMessage, peerMac, 'P') == TS_TRANSMISSION_COMPLETE)
{ {
uint32_t startTime = millis(); uint32_t startTime = millis();
// _ongoingPeerRequestNonce is set to "" when a peer confirmation response from the mac is received // _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. // For obvious reasons dividing by exactly 10 is a good choice.
ExpiringTimeTracker maxDurationTracker = ExpiringTimeTracker(getEncryptionRequestTimeout()/10); 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); delay(1);
} }
} }
if(_ongoingPeerRequestNonce != "") if(!_ongoingPeerRequestNonce.isEmpty())
{ {
// If nonce != "" we only received the basic connection info, so the pairing process is incomplete // If nonce != "" we only received the basic connection info, so the pairing process is incomplete
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
_ongoingPeerRequestNonce = ""; _ongoingPeerRequestNonce.clear();
} }
else if(encryptedConnectionEstablished(_ongoingPeerRequestResult)) else if(encryptedConnectionEstablished(_ongoingPeerRequestResult))
{ {
@ -1752,23 +1759,23 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker); requestMessage = encryptionRequestBuilder(requestNonce, existingTimeTracker);
else if(_ongoingPeerRequestResult == ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED) else if(_ongoingPeerRequestResult == ECS_SOFT_LIMIT_CONNECTION_ESTABLISHED)
// We will only get a soft limit connection. Adjust future actions based on this. // 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()); espnowHashKeyLength, getAutoEncryptionDuration());
else else
assert(false && "Unknown _ongoingPeerRequestResult during encrypted connection finalization!"); assert(false && String(F("Unknown _ongoingPeerRequestResult during encrypted connection finalization!")));
int32_t messageHeaderEndIndex = requestMessage.indexOf(':'); int32_t messageHeaderEndIndex = requestMessage.indexOf(':');
String messageHeader = requestMessage.substring(0, messageHeaderEndIndex + 1); String messageHeader = requestMessage.substring(0, messageHeaderEndIndex + 1);
String messageBody = requestMessage.substring(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 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()) && millis() - _ongoingPeerRequestEncryptionStart < getEncryptionRequestTimeout())
{ {
EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac); EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac);
if(!encryptedConnection) if(!encryptedConnection)
{ {
assert(encryptedConnection && "requestEncryptedConnectionKernel cannot find an encrypted connection!"); assert(encryptedConnection && String(F("requestEncryptedConnectionKernel cannot find an encrypted connection!")));
// requestEncryptedConnectionRemoval received. // requestEncryptedConnectionRemoval received.
_ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED; _ongoingPeerRequestResult = ECS_REQUEST_TRANSMISSION_FAILED;
} }
@ -1780,11 +1787,11 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
else else
{ {
// Finalize connection // Finalize connection
if(messageHeader == encryptionRequestHeader) if(messageHeader == FPSTR(encryptionRequestHeader))
{ {
temporaryEncryptedConnectionToPermanent(peerMac); temporaryEncryptedConnectionToPermanent(peerMac);
} }
else if(messageHeader == temporaryEncryptionRequestHeader) else if(messageHeader == FPSTR(temporaryEncryptionRequestHeader))
{ {
if(encryptedConnection->temporary()) if(encryptedConnection->temporary())
{ {
@ -1797,7 +1804,7 @@ encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnectionKerne
} }
else else
{ {
assert(false && "Unknown messageHeader during encrypted connection finalization!"); assert(false && String(F("Unknown messageHeader during encrypted connection finalization!")));
_ongoingPeerRequestResult = ECS_API_CALL_FAILED; _ongoingPeerRequestResult = ECS_API_CALL_FAILED;
} }
} }
@ -1839,19 +1846,19 @@ String EspnowMeshBackend::flexibleEncryptionRequestBuilder(const uint32_t minDur
uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ? uint32_t connectionDuration = minDurationMs >= existingTimeTracker.remainingDuration() ?
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) encrypted_connection_status_t EspnowMeshBackend::requestEncryptedConnection(uint8_t *peerMac)
{ {
using namespace std::placeholders; 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) encrypted_connection_status_t EspnowMeshBackend::requestTemporaryEncryptedConnection(uint8_t *peerMac, uint32_t durationMs)
{ {
using namespace std::placeholders; 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)); durationMs, getEspnowHashKey(), _1, _2));
} }
@ -1913,7 +1920,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect
{ {
uint8_t encryptedMac[6] {0}; uint8_t encryptedMac[6] {0};
connectionIterator->getEncryptedPeerMac(encryptedMac); 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; bool removalSucceeded = esp_now_del_peer(encryptedMac) == 0;
if(removalSucceeded) if(removalSucceeded)
@ -1926,7 +1933,7 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::removeEncryptedConnect
{ {
encryptedConnections.erase(connectionIterator); encryptedConnections.erase(connectionIterator);
} }
staticVerboseModePrint("Removal succeeded"); staticVerboseModePrint(String(F("Removal succeeded")));
// Not deleting encrypted responses here would cause them to be sent unencrypted, // 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. // 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 else
{ {
staticVerboseModePrint("Removal failed"); staticVerboseModePrint(String(F("Removal failed")));
return ECRO_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(); for(typename std::map<std::pair<macAndType_td, uint64_t>, T>::iterator entryIterator = logEntries.begin();
entryIterator != logEntries.end(); ) entryIterator != logEntries.end(); )
{ {
if(macAndTypeToUint64Mac(entryIterator->first.first) == macToUint64(peerMac)) if(macAndTypeToUint64Mac(entryIterator->first.first) == TypeCast::macToUint64(peerMac))
{ {
macFound = true; 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(); for(typename std::map<std::pair<uint64_t, uint64_t>, T>::iterator entryIterator = logEntries.begin();
entryIterator != logEntries.end(); ) entryIterator != logEntries.end(); )
{ {
if(entryIterator->first.first == macToUint64(peerMac)) if(entryIterator->first.first == TypeCast::macToUint64(peerMac))
{ {
macFound = true; macFound = true;
@ -2018,13 +2025,13 @@ encrypted_connection_removal_outcome_t EspnowMeshBackend::requestEncryptedConnec
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return ECRO_REMOVAL_REQUEST_FAILED;
} }
if(EncryptedConnectionLog *encryptedConnection = getEncryptedConnection(peerMac)) 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); return removeEncryptedConnectionUnprotected(peerMac);
} }
@ -2167,7 +2174,7 @@ transmission_status_t EspnowMeshBackend::initiateTransmission(const String &mess
if(verboseMode()) // Avoid string generation if not required if(verboseMode()) // Avoid string generation if not required
{ {
printAPInfo(recipientInfo); printAPInfo(recipientInfo);
verboseModePrint(F("")); verboseModePrint(emptyString);
} }
return initiateTransmissionKernel(message, targetBSSID); return initiateTransmissionKernel(message, targetBSSID);
@ -2197,12 +2204,12 @@ void EspnowMeshBackend::printTransmissionStatistics()
{ {
if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required if(verboseMode() && successfulTransmissions_AT > 0) // Avoid calculations if not required
{ {
verboseModePrint("Average duration of successful transmissions: " + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + " ms."); verboseModePrint(String(F("Average duration of successful transmissions: ")) + String(totalDurationWhenSuccessful_AT/successfulTransmissions_AT) + String(F(" ms.")));
verboseModePrint("Maximum duration of successful transmissions: " + String(maxTransmissionDuration_AT) + " ms."); verboseModePrint(String(F("Maximum duration of successful transmissions: ")) + String(maxTransmissionDuration_AT) + String(F(" ms.")));
} }
else 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); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return;
} }
@ -2220,7 +2227,7 @@ void EspnowMeshBackend::attemptTransmission(const String &message, bool scan, bo
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured()) 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 else
{ {
@ -2243,7 +2250,7 @@ transmission_status_t EspnowMeshBackend::attemptTransmission(const String &messa
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return TS_CONNECTION_FAILED;
} }
@ -2258,7 +2265,7 @@ encrypted_connection_status_t EspnowMeshBackend::initiateAutoEncryptingConnectio
if(verboseMode()) // Avoid string generation if not required if(verboseMode()) // Avoid string generation if not required
{ {
printAPInfo(recipientInfo); printAPInfo(recipientInfo);
verboseModePrint(F("")); verboseModePrint(emptyString);
} }
*existingEncryptedConnection = getEncryptedConnection(targetBSSID); *existingEncryptedConnection = getEncryptedConnection(targetBSSID);
@ -2279,7 +2286,7 @@ transmission_status_t EspnowMeshBackend::initiateAutoEncryptingTransmission(cons
if(encryptedConnectionEstablished(connectionStatus)) if(encryptedConnectionEstablished(connectionStatus))
{ {
uint8_t encryptedMac[6] {0}; 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); transmissionResult = initiateTransmissionKernel(message, targetBSSID);
} }
@ -2300,7 +2307,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message,
MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker outerMutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!outerMutexTracker.mutexCaptured()) 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; return;
} }
@ -2311,7 +2318,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message,
MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex); MutexTracker connectionQueueMutexTracker(_espnowConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured()) 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 else
{ {
@ -2324,7 +2331,7 @@ void EspnowMeshBackend::attemptAutoEncryptingTransmission(const String &message,
MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex); MutexTracker innerMutexTracker = MutexTracker(_espnowTransmissionMutex);
if(!innerMutexTracker.mutexCaptured()) 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; return;
} }
@ -2351,7 +2358,7 @@ transmission_status_t EspnowMeshBackend::attemptAutoEncryptingTransmission(const
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return TS_CONNECTION_FAILED;
} }
@ -2367,7 +2374,7 @@ void EspnowMeshBackend::broadcast(const String &message)
MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals); MutexTracker mutexTracker(_espnowTransmissionMutex, handlePostponedRemovals);
if(!mutexTracker.mutexCaptured()) 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; return;
} }
@ -2401,7 +2408,7 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
using namespace EspnowProtocolInterpreter; using namespace EspnowProtocolInterpreter;
// True if confirmationsIterator contains a peer request received from the same node we are currently sending a peer request to. // 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(); auto timeTrackerPointer = confirmationsIterator->temporary();
assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker assert(timeTrackerPointer); // peerRequestConfirmations should always expire and so should always have a timeTracker
@ -2434,13 +2441,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
if(!existingEncryptedConnection && if(!existingEncryptedConnection &&
((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections))) ((reciprocalPeerRequest && encryptedConnections.size() >= maxEncryptedConnections) || (!reciprocalPeerRequest && reservedEncryptedConnections() >= maxEncryptedConnections)))
{ {
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength),
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator); confirmationsIterator = peerRequestConfirmationsToSend.erase(confirmationsIterator);
} }
else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(basicConnectionInfoHeader, else if(espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(basicConnectionInfoHeader),
confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength),
sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. sendToDefaultBSSID ? defaultBSSID : unencryptedBSSID, 'C', generateMessageID(nullptr)) // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections.
== TS_TRANSMISSION_COMPLETE) == TS_TRANSMISSION_COMPLETE)
@ -2466,13 +2473,13 @@ void EspnowMeshBackend::sendPeerRequestConfirmations(const ExpiringTimeTracker *
} }
else 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) if(!existingEncryptedConnection)
{ {
// Send "node full" message // Send "node full" message
espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(maxConnectionsReachedHeader, espnowSendToNodeUnsynchronized(JsonTranslator::createEncryptionRequestHmacMessage(FPSTR(maxConnectionsReachedHeader),
confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength), confirmationsIterator->getPeerRequestNonce(), hashKey, espnowHashKeyLength),
defaultBSSID, 'C', generateMessageID(nullptr)); // Generates a new message ID to avoid sending encrypted sessionKeys over unencrypted connections. 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 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 if(existingEncryptedConnection->temporary() && // Should never change permanent connections
((reciprocalPeerRequest && encryptedConnections.size() > confirmationsIterator->getEncryptedConnectionsSoftLimit()) ((reciprocalPeerRequest && encryptedConnections.size() > confirmationsIterator->getEncryptedConnectionsSoftLimit())
|| (!reciprocalPeerRequest && reservedEncryptedConnections() > confirmationsIterator->getEncryptedConnectionsSoftLimit()))) || (!reciprocalPeerRequest && reservedEncryptedConnections() > confirmationsIterator->getEncryptedConnectionsSoftLimit())))
{ {
messageHeader = softLimitEncryptedConnectionInfoHeader; messageHeader = FPSTR(softLimitEncryptedConnectionInfoHeader);
} }
else else
{ {
messageHeader = encryptedConnectionInfoHeader; messageHeader = FPSTR(encryptedConnectionInfoHeader);
} }
// Send password and keys. // Send password and keys.
@ -2534,7 +2541,7 @@ void EspnowMeshBackend::sendEspnowResponses(const ExpiringTimeTracker *estimated
MutexTracker responsesToSendMutexTracker(_responsesToSendMutex); MutexTracker responsesToSendMutexTracker(_responsesToSendMutex);
if(!responsesToSendMutexTracker.mutexCaptured()) 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; uint32_t responseIndex = 0;
@ -2612,7 +2619,7 @@ uint8_t EspnowMeshBackend::numberOfEncryptedConnections()
uint8_t EspnowMeshBackend::reservedEncryptedConnections() uint8_t EspnowMeshBackend::reservedEncryptedConnections()
{ {
if(_ongoingPeerRequestNonce != "") if(!_ongoingPeerRequestNonce.isEmpty())
if(!getEncryptedConnection(_ongoingPeerRequestMac)) if(!getEncryptedConnection(_ongoingPeerRequestMac))
return encryptedConnections.size() + 1; // Reserve one connection spot if we are currently making a peer request to a new node. 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"}} // 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) String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac)
@ -2697,7 +2704,7 @@ String EspnowMeshBackend::serializeEncryptedConnection(const uint8_t *peerMac)
if(encryptedConnection) if(encryptedConnection)
return encryptedConnection->serialize(); return encryptedConnection->serialize();
else else
return ""; return emptyString;
} }
String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex) String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex)
@ -2705,5 +2712,5 @@ String EspnowMeshBackend::serializeEncryptedConnection(uint32_t connectionIndex)
if(connectionIndex < numberOfEncryptedConnections()) if(connectionIndex < numberOfEncryptedConnections())
return encryptedConnections[connectionIndex].serialize(); return encryptedConnections[connectionIndex].serialize();
else else
return ""; return emptyString;
} }

View File

@ -356,6 +356,32 @@ public:
*/ */
bool isEspnowRequestManager(); 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. * 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. * 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 void deleteExpiredLogEntries(std::list<T> &logEntries, uint32_t maxEntryLifetimeMs);
static uint32_t _logEntryLifetimeMs; static uint32_t _logEntryLifetimeMs;
static uint32_t logEntryLifetimeMs();
static uint32_t _broadcastResponseTimeoutMs; static uint32_t _broadcastResponseTimeoutMs;
static uint32_t broadcastResponseTimeoutMs();
static uint32_t _encryptionRequestTimeoutMs; static uint32_t _encryptionRequestTimeoutMs;

View File

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

View File

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

View File

@ -26,6 +26,14 @@
#include "TypeConversionFunctions.h" #include "TypeConversionFunctions.h"
#include "JsonTranslator.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 = {}; std::set<FloodingMesh *> FloodingMesh::availableFloodingMeshes = {};
char FloodingMesh::_metadataDelimiter = 23; char FloodingMesh::_metadataDelimiter = 23;
@ -103,6 +111,11 @@ void FloodingMesh::activateAP()
getEspnowMeshBackend().activateAP(); getEspnowMeshBackend().activateAP();
} }
void FloodingMesh::deactivateAP()
{
MeshBackendBase::deactivateAP();
}
void FloodingMesh::performMeshMaintenance() void FloodingMesh::performMeshMaintenance()
{ {
for(FloodingMesh *meshInstance : availableFloodingMeshes) for(FloodingMesh *meshInstance : availableFloodingMeshes)
@ -122,7 +135,7 @@ void FloodingMesh::performMeshInstanceMaintenance()
{ {
_macIgnoreList = messageData.first.substring(0, 12) + ','; // The message should contain the messageID first _macIgnoreList = messageData.first.substring(0, 12) + ','; // The message should contain the messageID first
encryptedBroadcastKernel(messageData.first); encryptedBroadcastKernel(messageData.first);
_macIgnoreList = ""; _macIgnoreList = emptyString;
} }
else else
{ {
@ -144,9 +157,9 @@ String FloodingMesh::serializeMeshState()
String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection(); String connectionState = getEspnowMeshBackend().serializeUnencryptedConnection();
return return
"{\"meshState\":{" String(F("{\"meshState\":{"))
+ connectionState.substring(1, connectionState.length() - 1) + "," + connectionState.substring(1, connectionState.length() - 1) + String(',')
+ createJsonEndPair(jsonMeshMessageCount, String(_messageCount)); + createJsonEndPair(FPSTR(jsonMeshMessageCount), String(_messageCount));
} }
void FloodingMesh::loadMeshState(const String &serializedMeshState) void FloodingMesh::loadMeshState(const String &serializedMeshState)
@ -154,12 +167,12 @@ void FloodingMesh::loadMeshState(const String &serializedMeshState)
using namespace JsonTranslator; using namespace JsonTranslator;
if(!getMeshMessageCount(serializedMeshState, _messageCount)) 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)) 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 }; char messageCountArray[5] = { 0 };
snprintf(messageCountArray, 5, "%04X", _messageCount++); snprintf(messageCountArray, 5, "%04X", _messageCount++);
uint8_t apMac[6] {0}; 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) void FloodingMesh::broadcast(const String &message)
@ -228,7 +241,7 @@ void FloodingMesh::setOriginMac(uint8_t *macArray)
std::copy_n(macArray, 6, _originMac); 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) uint8_t *FloodingMesh::getOriginMac(uint8_t *macArray)
{ {
std::copy_n(_originMac, 6, macArray); std::copy_n(_originMac, 6, macArray);
@ -273,7 +286,7 @@ EspnowMeshBackend &FloodingMesh::getEspnowMeshBackend()
bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID) bool FloodingMesh::insertPreliminaryMessageID(uint64_t messageID)
{ {
uint8_t apMacArray[6] = { 0 }; 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. return false; // The node should not receive its own messages.
auto insertionResult = _messageIDs.emplace(messageID, 0); // Returns std::pair<iterator,bool> 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) bool FloodingMesh::insertCompletedMessageID(uint64_t messageID)
{ {
uint8_t apMacArray[6] = { 0 }; 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. return false; // The node should not receive its own messages.
auto insertionResult = _messageIDs.emplace(messageID, MESSAGE_COMPLETE); // Returns std::pair<iterator,bool> 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. (void)meshInstance; // This is useful to remove a "unused parameter" compiler warning. Does nothing else.
String broadcastTarget = ""; String broadcastTarget;
String remainingRequest = request; String remainingRequest = request;
if(request.charAt(0) == metadataDelimiter()) if(request.charAt(0) == metadataDelimiter())
@ -376,7 +389,7 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
int32_t broadcastTargetEndIndex = request.indexOf(metadataDelimiter(), 1); int32_t broadcastTargetEndIndex = request.indexOf(metadataDelimiter(), 1);
if(broadcastTargetEndIndex == -1) if(broadcastTargetEndIndex == -1)
return ""; // metadataDelimiter not found return emptyString; // metadataDelimiter not found
broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter broadcastTarget = request.substring(1, broadcastTargetEndIndex + 1); // Include delimiter
remainingRequest.remove(0, broadcastTargetEndIndex + 1); remainingRequest.remove(0, broadcastTargetEndIndex + 1);
@ -385,14 +398,14 @@ String FloodingMesh::_defaultRequestHandler(const String &request, MeshBackendBa
int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter()); int32_t messageIDEndIndex = remainingRequest.indexOf(metadataDelimiter());
if(messageIDEndIndex == -1) 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)) if(insertCompletedMessageID(messageID))
{ {
uint8_t originMacArray[6] = { 0 }; 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; String message = remainingRequest;
message.remove(0, messageIDEndIndex + 1); // This approach avoids the null value removal of substring() 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; 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)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. (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() // Connect to any APs which contain meshInstance.getMeshName()
if(meshNameIndex >= 0) 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); espnowInstance->connectionQueue().push_back(networkIndex);
} }
@ -486,7 +499,7 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh
String targetMeshName = firstTransmission.substring(0, metadataEndIndex); 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 return false; // Broadcast is for another mesh network
} }
@ -497,7 +510,7 @@ bool FloodingMesh::_defaultBroadcastFilter(String &firstTransmission, EspnowMesh
if(messageIDEndIndex == -1) if(messageIDEndIndex == -1)
return false; // metadataDelimiter not found 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)) if(insertPreliminaryMessageID(messageID))
{ {

View File

@ -121,7 +121,8 @@ public:
void begin(); 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. * 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. * 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(); 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 * Performs maintenance for all available Flooding Mesh instances
*/ */
@ -298,9 +308,6 @@ protected:
private: private:
static const uint8_t MESSAGE_ID_LENGTH = 17; // 16 characters and one delimiter
static const uint8_t MESSAGE_COMPLETE = 255;
EspnowMeshBackend _espnowBackend; EspnowMeshBackend _espnowBackend;
messageHandlerType _messageHandler; messageHandlerType _messageHandler;
@ -318,7 +325,7 @@ private:
std::queue<messageQueueElementType> _messageIdOrder = {}; std::queue<messageQueueElementType> _messageIdOrder = {};
std::list<std::pair<String, bool>> _forwardingBacklog = {}; std::list<std::pair<String, bool>> _forwardingBacklog = {};
String _macIgnoreList = ""; String _macIgnoreList;
String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance); String _defaultRequestHandler(const String &request, MeshBackendBase &meshInstance);
transmission_status_t _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance); transmission_status_t _defaultResponseHandler(const String &response, MeshBackendBase &meshInstance);

View File

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

View File

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

View File

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

View File

@ -23,8 +23,6 @@
#include "TransmissionOutcome.h" #include "TransmissionOutcome.h"
#include "NetworkInfoBase.h" #include "NetworkInfoBase.h"
const String ESP8266_MESH_EMPTY_STRING = "";
typedef enum typedef enum
{ {
MB_TCP_IP = 0, MB_TCP_IP = 0,
@ -52,11 +50,40 @@ public:
virtual void begin() = 0; 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. * 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 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(); void restartAP();
/** /**
@ -104,8 +131,8 @@ public:
* @param newSSIDRoot The middle part of the new SSID. * @param newSSIDRoot The middle part of the new SSID.
* @param newSSIDSuffix The last 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, void setSSID(const String &newSSIDPrefix = emptyString, const String &newSSIDRoot = emptyString,
const String &newSSIDSuffix = ESP8266_MESH_EMPTY_STRING); const String &newSSIDSuffix = emptyString);
String getSSID() const; String getSSID() const;
/** /**
@ -300,7 +327,7 @@ private:
String _meshPassword; String _meshPassword;
uint8 _meshWiFiChannel; uint8 _meshWiFiChannel;
bool _verboseMode; bool _verboseMode;
String _message = ESP8266_MESH_EMPTY_STRING; String _message;
bool _scanHidden = false; bool _scanHidden = false;
bool _apHidden = false; bool _apHidden = false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,22 +25,25 @@
#include "TypeConversionFunctions.h" #include "TypeConversionFunctions.h"
#include "MutexTracker.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::_tcpIpTransmissionMutex = false;
bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false; bool TcpIpMeshBackend::_tcpIpConnectionQueueMutex = false;
String TcpIpMeshBackend::lastSSID = ""; String TcpIpMeshBackend::lastSSID;
bool TcpIpMeshBackend::staticIPActivated = false; 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. // 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::staticIP;
IPAddress TcpIpMeshBackend::gateway = IPAddress(192,168,4,1); IPAddress TcpIpMeshBackend::gateway(192,168,4,1);
IPAddress TcpIpMeshBackend::subnetMask = IPAddress(255,255,255,0); IPAddress TcpIpMeshBackend::subnetMask(255,255,255,0);
std::vector<TcpIpNetworkInfo> TcpIpMeshBackend::_connectionQueue = {}; std::vector<TcpIpNetworkInfo> TcpIpMeshBackend::_connectionQueue = {};
std::vector<TransmissionOutcome> TcpIpMeshBackend::_latestTransmissionOutcomes = {}; std::vector<TransmissionOutcome> TcpIpMeshBackend::_latestTransmissionOutcomes = {};
@ -50,7 +53,7 @@ TcpIpMeshBackend::TcpIpMeshBackend(requestHandlerType requestHandler, responseHa
const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort) const String &ssidSuffix, bool verboseMode, uint8 meshWiFiChannel, uint16_t serverPort)
: MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort) : MeshBackendBase(requestHandler, responseHandler, networkFilter, MB_TCP_IP), _server(serverPort)
{ {
setSSID(ssidPrefix, "", ssidSuffix); setSSID(ssidPrefix, emptyString, ssidSuffix);
setMeshPassword(meshPassword); setMeshPassword(meshPassword);
setVerboseModeState(verboseMode); setVerboseModeState(verboseMode);
setWiFiChannel(meshWiFiChannel); setWiFiChannel(meshWiFiChannel);
@ -62,7 +65,7 @@ std::vector<TcpIpNetworkInfo> & TcpIpMeshBackend::connectionQueue()
MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex); MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured()) 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; return _connectionQueue;
@ -112,13 +115,13 @@ bool TcpIpMeshBackend::transmissionInProgress(){return _tcpIpTransmissionMutex;}
void TcpIpMeshBackend::setTemporaryMessage(const String &newTemporaryMessage) {_temporaryMessage = newTemporaryMessage;} void TcpIpMeshBackend::setTemporaryMessage(const String &newTemporaryMessage) {_temporaryMessage = newTemporaryMessage;}
String TcpIpMeshBackend::getTemporaryMessage() {return _temporaryMessage;} String TcpIpMeshBackend::getTemporaryMessage() {return _temporaryMessage;}
void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage = "";} void TcpIpMeshBackend::clearTemporaryMessage() {_temporaryMessage.clear();}
String TcpIpMeshBackend::getCurrentMessage() String TcpIpMeshBackend::getCurrentMessage()
{ {
String message = getTemporaryMessage(); String message = getTemporaryMessage();
if(message == "") // If no temporary message stored if(message.isEmpty()) // If no temporary message stored
message = getMessage(); message = getMessage();
return message; return message;
@ -245,7 +248,7 @@ bool TcpIpMeshBackend::waitForClientTransmission(WiFiClient &currClient, uint32_
*/ */
transmission_status_t TcpIpMeshBackend::exchangeInfo(WiFiClient &currClient) 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'); currClient.print(getCurrentMessage() + '\r');
yield(); yield();
@ -301,7 +304,7 @@ transmission_status_t TcpIpMeshBackend::attemptDataTransferKernel()
currClient.setTimeout(_stationModeTimeoutMs); currClient.setTimeout(_stationModeTimeoutMs);
/* Connect to the node's server */ /* Connect to the node's server */
if (!currClient.connect(SERVER_IP_ADDR, getServerPort())) if (!currClient.connect(FPSTR(SERVER_IP_ADDR), getServerPort()))
{ {
fullStop(currClient); fullStop(currClient);
verboseModePrint(F("Server unavailable")); 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) 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 #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. // 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(); WiFi.disconnect();
yield(); 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(); String targetSSID = recipientInfo.SSID();
int32_t targetWiFiChannel = recipientInfo.wifiChannel(); int32_t targetWiFiChannel = recipientInfo.wifiChannel();
uint8_t targetBSSID[6] {0}; uint8_t targetBSSID[6] {0};
@ -433,7 +436,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
MutexTracker mutexTracker(_tcpIpTransmissionMutex); MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured()) 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; return;
} }
@ -465,7 +468,7 @@ void TcpIpMeshBackend::attemptTransmission(const String &message, bool scan, boo
MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex); MutexTracker connectionQueueMutexTracker(_tcpIpConnectionQueueMutex);
if(!connectionQueueMutexTracker.mutexCaptured()) 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 else
{ {
@ -494,7 +497,7 @@ transmission_status_t TcpIpMeshBackend::attemptTransmission(const String &messag
MutexTracker mutexTracker(_tcpIpTransmissionMutex); MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured()) 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; return TS_CONNECTION_FAILED;
} }
@ -527,7 +530,7 @@ void TcpIpMeshBackend::acceptRequests()
MutexTracker mutexTracker(_tcpIpTransmissionMutex); MutexTracker mutexTracker(_tcpIpTransmissionMutex);
if(!mutexTracker.mutexCaptured()) 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; return;
} }
@ -551,7 +554,7 @@ void TcpIpMeshBackend::acceptRequests()
/* Send the response back to the client */ /* Send the response back to the client */
if (_client.connected()) 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.print(response + '\r');
_client.flush(); _client.flush();
yield(); yield();

View File

@ -33,9 +33,10 @@ 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 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
} }
namespace MeshTypeConversionFunctions
String uint64ToString(uint64_t number, byte base)
{ {
String uint64ToString(uint64_t number, byte base)
{
assert(2 <= base && base <= 36); assert(2 <= base && base <= 36);
String result; String result;
@ -58,10 +59,10 @@ String uint64ToString(uint64_t number, byte base)
std::reverse( result.begin(), result.end() ); std::reverse( result.begin(), result.end() );
return result; return result;
} }
uint64_t stringToUint64(const String &string, byte base) uint64_t stringToUint64(const String &string, byte base)
{ {
assert(2 <= base && base <= 36); assert(2 <= base && base <= 36);
uint64_t result = 0; uint64_t result = 0;
@ -84,10 +85,10 @@ uint64_t stringToUint64(const String &string, byte base)
} }
return result; return result;
} }
String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength) String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength)
{ {
String hexString; String hexString;
if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF) if(!hexString.reserve(2*arrayLength)) // Each uint8_t will become two characters (00 to FF)
return emptyString; return emptyString;
@ -99,10 +100,10 @@ String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength)
} }
return hexString; return hexString;
} }
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength) 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 assert(hexString.length() >= arrayLength*2); // Each array element can hold two hexString characters
for(uint32_t i = 0; i < arrayLength; ++i) for(uint32_t i = 0; i < arrayLength; ++i)
@ -111,10 +112,10 @@ uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uin
} }
return uint8Array; return uint8Array;
} }
String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength) String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength)
{ {
String multiString; String multiString;
if(!multiString.reserve(arrayLength)) if(!multiString.reserve(arrayLength))
return emptyString; return emptyString;
@ -134,10 +135,10 @@ String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength)
uint8Array[arrayLength - 1] = finalChar; uint8Array[arrayLength - 1] = finalChar;
return multiString; return multiString;
} }
String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength) String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength)
{ {
String multiString; String multiString;
if(!multiString.reserve(arrayLength)) if(!multiString.reserve(arrayLength))
return emptyString; return emptyString;
@ -155,26 +156,26 @@ String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t array
} }
return multiString; return multiString;
} }
String macToString(const uint8_t *mac) String macToString(const uint8_t *mac)
{ {
return uint8ArrayToHexString(mac, 6); return MeshTypeConversionFunctions::uint8ArrayToHexString(mac, 6);
} }
uint8_t *stringToMac(const String &macString, uint8_t *macArray) uint8_t *stringToMac(const String &macString, uint8_t *macArray)
{ {
return hexStringToUint8Array(macString, macArray, 6); return MeshTypeConversionFunctions::hexStringToUint8Array(macString, macArray, 6);
} }
uint64_t macToUint64(const uint8_t *macArray) 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]; 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; return result;
} }
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray) uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray)
{ {
assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes assert(macValue <= 0xFFFFFFFFFFFF); // Overflow will occur if value can't fit within 6 bytes
macArray[5] = macValue; macArray[5] = macValue;
@ -185,10 +186,10 @@ uint8_t *uint64ToMac(uint64_t macValue, uint8_t *macArray)
macArray[0] = macValue >> 40; macArray[0] = macValue >> 40;
return macArray; return macArray;
} }
uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray) uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray)
{ {
resultArray[7] = value; resultArray[7] = value;
resultArray[6] = value >> 8; resultArray[6] = value >> 8;
resultArray[5] = value >> 16; resultArray[5] = value >> 16;
@ -199,22 +200,22 @@ uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *resultArray)
resultArray[0] = value >> 56; resultArray[0] = value >> 56;
return resultArray; return resultArray;
} }
uint64_t uint8ArrayToUint64(const uint8_t *inputArray) 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 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]; | (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7];
return result; return result;
} }
/** /**
* Helper function for meshBackendCast. * Helper function for meshBackendCast.
*/ */
template <typename T> template <typename T>
T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType) T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t resultClassType)
{ {
if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType) if(meshBackendBaseInstance && meshBackendBaseInstance->getClassType() == resultClassType)
{ {
return static_cast<T>(meshBackendBaseInstance); return static_cast<T>(meshBackendBaseInstance);
@ -223,16 +224,17 @@ T attemptPointerCast(MeshBackendBase *meshBackendBaseInstance, mesh_backend_t re
{ {
return nullptr; return nullptr;
} }
} }
template <> template <>
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance) EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
{ {
return attemptPointerCast<EspnowMeshBackend *>(meshBackendBaseInstance, MB_ESP_NOW); return attemptPointerCast<EspnowMeshBackend *>(meshBackendBaseInstance, MB_ESP_NOW);
} }
template <> template <>
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance) TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance)
{ {
return attemptPointerCast<TcpIpMeshBackend *>(meshBackendBaseInstance, MB_TCP_IP); return attemptPointerCast<TcpIpMeshBackend *>(meshBackendBaseInstance, MB_TCP_IP);
}
} }

View File

@ -32,7 +32,9 @@
#include "TcpIpMeshBackend.h" #include "TcpIpMeshBackend.h"
#include "EspnowMeshBackend.h" #include "EspnowMeshBackend.h"
/** namespace MeshTypeConversionFunctions
{
/**
* 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 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. * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
* *
@ -40,9 +42,9 @@
* @param base The radix to convert "number" into. Must be between 2 and 36. * @param base The radix to convert "number" into. Must be between 2 and 36.
* @return A string of "number" encoded in radix "base". * @return A string of "number" encoded in radix "base".
*/ */
String uint64ToString(uint64_t number, byte base = 16); 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 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. * Note that using a base higher than 16 increases likelihood of randomly generating SSID strings containing controversial words.
* *
@ -50,9 +52,9 @@ String uint64ToString(uint64_t number, byte base = 16);
* @param base The radix of "string". Must be between 2 and 36. * @param base The radix of "string". Must be between 2 and 36.
* @return A uint64_t of the string, using radix "base" during decoding. * @return A uint64_t of the string, using radix "base" during decoding.
*/ */
uint64_t stringToUint64(const String &string, byte base = 16); 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. * 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. * All array elements will be padded with zeroes to ensure they are converted to 2 String characters each.
* *
@ -60,9 +62,9 @@ uint64_t stringToUint64(const String &string, byte base = 16);
* @param arrayLength The size of uint8Array, in bytes. * @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. * @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); 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. * 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. * There must be 2 String characters for each array element. Use padding with zeroes where required.
* *
@ -71,9 +73,9 @@ String uint8ArrayToHexString(const uint8_t *uint8Array, uint32_t arrayLength);
* @param arrayLength The number of bytes to fill in uint8Array. * @param arrayLength The number of bytes to fill in uint8Array.
* @return A pointer to the uint8Array. * @return A pointer to the uint8Array.
*/ */
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength); uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uint32_t arrayLength);
/** /**
* Stores the exact values of uint8Array in a String, even null values. * 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. * 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. * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead.
@ -84,9 +86,9 @@ uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, uin
* @param arrayLength The size of uint8Array, in bytes. * @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. * @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); String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength);
/** /**
* Stores the exact values of uint8Array in a String, even null values. * 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. * 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. * In these cases, it may be helpful to use String::c_str() or String::begin() to access the String data buffer directly instead.
@ -97,79 +99,84 @@ String uint8ArrayToMultiString(uint8_t *uint8Array, uint32_t arrayLength);
* @param arrayLength The size of uint8Array, in bytes. * @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. * @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); String bufferedUint8ArrayToMultiString(const uint8_t *uint8Array, uint32_t arrayLength);
/** /**
* Takes a uint8_t array and converts the first 6 bytes to a hexadecimal string. * 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. * @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. * @return A hexadecimal string representation of the mac.
*/ */
String macToString(const uint8_t *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. * 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 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. * @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. * @return The macArray.
*/ */
uint8_t *stringToMac(const String &macString, uint8_t *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. * 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. * @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. * @return A uint64_t representation of the mac.
*/ */
uint64_t macToUint64(const uint8_t *macArray); 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. * 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 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. * @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. * @return The macArray.
*/ */
uint8_t *uint64ToMac(uint64_t macValue, uint8_t *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. * 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 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. * @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. * @return The resultArray.
*/ */
uint8_t *uint64ToUint8Array(uint64_t value, uint8_t *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. * 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. * @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. * @return A uint64_t representation of the first 8 bytes of the array.
*/ */
uint64_t uint8ArrayToUint64(const uint8_t *inputArray); uint64_t uint8ArrayToUint64(const uint8_t *inputArray);
/** /**
* Conversion function that can be used on MeshBackend classes instead of dynamic_cast since RTTI is disabled. * 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 T The MeshBackend class pointer type to cast the meshBackendBaseInstance pointer into.
* @param meshBackendBaseInstance The instance pointer to cast. * @param meshBackendBaseInstance The instance pointer to cast.
* @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise. * @return A pointer of type T to meshBackendBaseInstance if meshBackendBaseInstance is of type T. nullptr otherwise.
*/ */
template <typename T> template <typename T>
T meshBackendCast(MeshBackendBase *meshBackendBaseInstance) T meshBackendCast(MeshBackendBase *meshBackendBaseInstance)
{ {
// The only valid template arguments are handled by the template specializations below, so ending up here is an error. // 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, 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!"); "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). #ifndef ESP8266WIFIMESH_DISABLE_COMPATIBILITY
template <> using namespace MeshTypeConversionFunctions; // Required to retain backwards compatibility. TODO: Remove in core release 3.0.0
EspnowMeshBackend *meshBackendCast<EspnowMeshBackend *>(MeshBackendBase *meshBackendBaseInstance); #endif
template <>
TcpIpMeshBackend *meshBackendCast<TcpIpMeshBackend *>(MeshBackendBase *meshBackendBaseInstance);
#endif #endif

View File

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

View File

@ -28,8 +28,11 @@
#include <inttypes.h> #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 #endif